React状态管理详解

news/2024/11/22 4:23:25/

概述

React中的状态管理是其核心机制之一,它决定了组件的渲染和交互行为。以下是对React中状态管理工作原理的详细解释:

一、状态的定义与分类

在React中,状态(state)是组件记忆信息的一种方式,它决定了组件的渲染输出。状态可以是任何类型的数据,如数字、字符串、对象或数组等。根据状态的使用范围,可以将其分为本地状态和全局状态。

  1. 本地状态:本地状态是指仅在组件内部使用的状态,由组件自身维护和更新,不会被其他组件访问或修改。本地状态对于处理组件私有的数据非常有用。
  2. 全局状态:全局状态是指可以被多个组件共享和访问的状态,它通常用于存储跨组件的共享数据,如用户认证信息、主题设置等。

二、本地状态管理

本地状态管理主要通过React的useState钩子函数实现。useState是一个React Hook,它接受一个初始状态值,并返回一个包含当前状态值和一个用于更新状态的函数的数组。

  1. 初始化状态:当组件首次渲染时,useState会接受一个初始状态值,并将其作为组件的初始状态。
  2. 更新状态:当需要更新状态时,可以调用useState返回的更新函数,并传入新的状态值。React会重新渲染该组件,以反映状态的变化。

三、全局状态管理

全局状态管理在React中有多种实现方式,其中最常用的是Redux和React Context。

  1. Redux

    • 核心概念:Redux是一个独立于React的状态管理库,它提供了store(存储状态)、action(描述状态变更)和reducer(处理状态变更)三个核心概念。
    • 工作原理:通过定义一个全局的store来存储应用的状态,当需要更新状态时,通过dispatch函数发送一个action到reducer。Reducer是一个纯函数,它接受当前状态和action作为参数,并返回一个新的状态。然后,store会使用这个新状态来更新应用的状态树。
    • 优势:Redux具有强大的中间件支持(如Redux Thunk、Redux Saga等),可用于处理异步操作、日志记录等。此外,Redux DevTools等工具可用于调试和查看状态变化。
  2. React Context

    • 工作原理:React Context提供了一种在组件树中传递数据的方式,而无需在每一层组件中手动传递props。通过创建一个Context对象和一个Provider组件,可以将数据从顶层组件传递到下层组件。
    • 使用场景:React Context适用于简单的全局状态共享场景,如主题切换、用户认证等。对于复杂的状态管理需求,建议使用Redux等更强大的工具。

四、状态更新的优化

在React中,状态更新是一个性能敏感的操作。为了优化性能,React使用了一些策略来减少不必要的重新渲染。

  1. 批处理更新:React会合并同一事件循环中的所有状态更新,并在事件处理结束后只进行一次重新渲染。
  2. 纯函数组件:使用函数组件和React Hooks可以更容易地实现性能优化。例如,使用useMemouseCallback等Hooks可以避免不必要的计算和渲染。
  3. 不可变数据结构:使用不可变数据结构可以减少状态比较的开销,并提高性能。

五、总结

React中的状态管理是一个复杂而强大的机制,它支持本地状态和全局状态的管理。通过理解状态的定义、分类以及管理方式,可以更有效地开发React应用。在实际开发中,可以根据具体的需求和项目特点来选择合适的状态管理方案。

State _ 如同一张快照

     设置 state 只会为下一次渲染变更 state 的值。在第一次渲染期间,number0。这也就解释了为什么在 那次渲染中的 onClick 处理函数中,即便在调用了 setNumber(number + 1) 之后,number 的值也仍然是 0

import { useState } from 'react';export default function Counter() {const [number, setNumber] = useState(0);return (<><h1>{number}</h1><button onClick={() => {setNumber(number + 1);setNumber(number + 1);setNumber(number + 1);}}>+3</button></>)
}

类似!一个 state 变量的值永远不会在一次渲染的内部发生变化

import { useState } from 'react';export default function Counter() {const [number, setNumber] = useState(0);return (<><h1>{number}</h1><button onClick={() => {setNumber(number + 5);alert(number);}}>+5</button></>)
}//结果
//setNumber(0 + 5);
//alert(0);import { useState } from 'react';export default function Counter() {const [number, setNumber] = useState(0);return (<><h1>{number}</h1><button onClick={() => {setNumber(number + 5);setTimeout(() => {alert(number);}, 3000);}}>+5</button></>)
}//结果
//setNumber(0 + 5);
//setTimeout(() => {
//  alert(0);
//}, 3000);

State_状态更新函数

 

在下次渲染前多次更新同一个 state 

import { useState } from 'react';export default function Counter() {const [number, setNumber] = useState(0);return (<><h1>{number}</h1><button onClick={() => {setNumber(n => n + 1);setNumber(n => n + 1);setNumber(n => n + 1);}}>+3</button></>)
}
//每次+3

下面是 React 在执行事件处理函数时处理这几行代码的过程:

  1. setNumber(n => n + 1)n => n + 1 是一个函数。React 将它加入队列。
  2. setNumber(n => n + 1)n => n + 1 是一个函数。React 将它加入队列。
  3. setNumber(n => n + 1)n => n + 1 是一个函数。React 将它加入队列。

当你在下次渲染期间调用 useState 时,React 会遍历队列。之前的 number state 的值是 0,所以这就是 React 作为参数 n 传递给第一个更新函数的值。然后 React 会获取你上一个更新函数的返回值,并将其作为 n 传递给下一个更新函数,以此类推:

更新队列n返回值
n => n + 100 + 1 = 1
n => n + 111 + 1 = 2
n => n + 122 + 1 = 3

React 会保存 3 为最终结果并从 useState 中返回。

这就是为什么在上面的示例中点击“+3”正确地将值增加“+3”。

 

import { useState } from 'react';export default function Counter() {const [number, setNumber] = useState(0);return (<><h1>{number}</h1><button onClick={() => {setNumber(number + 5);setNumber(n => n + 1);setNumber(42);}}>增加数字</button></>)
}

以下是 React 在执行事件处理函数时处理这几行代码的过程:

  1. setNumber(number + 5)number 为 0,所以 setNumber(0 + 5)。React 将 “替换为 5 添加到其队列中。
  2. setNumber(n => n + 1)n => n + 1 是一个更新函数。React 将该函数添加到其队列中。
  3. setNumber(42):React 将 替换为 42 添加到其队列中。

在下一次渲染期间,React 会遍历 state 队列:

更新队列n返回值
“替换为 50(未使用)5
n => n + 155 + 1 = 6
“替换为 426(未使用)42

然后 React 会保存 42 为最终结果并从 useState 中返回。

总而言之,以下是你可以考虑传递给 setNumber state 设置函数的内容:

  • 一个更新函数(例如:n => n + 1)会被添加到队列中。
  • 任何其他的值(例如:数字 5)会导致“替换为 5”被添加到队列中,已经在队列中的内容会被忽略。

事件处理函数执行完成后,React 将触发重新渲染。在重新渲染期间,React 将处理队列。更新函数会在渲染期间执行,因此 更新函数必须是 纯函数 并且只 返回 结果。不要尝试从它们内部设置 state 或者执行其他副作用。在严格模式下,React 会执行每个更新函数两次(但是丢弃第二个结果)以便帮助你发现错误。

 更新State对象

更新嵌套对象※
setPerson({...person, // 复制其它字段的数据 artwork: { // 替换 artwork 字段 ...person.artwork, // 复制之前 person.artwork 中的数据city: 'New Delhi' // 但是将 city 的值替换为 New Delhi!}
});
 Immer库
  1. 运行 npm install use-immer 添加 Immer 依赖
  2. 用 import { useImmer } from 'use-immer' 替换掉 import { useState } from 'react'
 const [person, updatePerson] = useImmer({name: 'Niki de Saint Phalle',artwork: {title: 'Blue Nana',city: 'Hamburg',image: 'https://i.imgur.com/Sd1AgUOm.jpg',}});function handleNameChange(e) {updatePerson(draft => {draft.name = e.target.value;});}function handleTitleChange(e) {updatePerson(draft => {draft.artwork.title = e.target.value;});}function handleCityChange(e) {updatePerson(draft => {draft.artwork.city = e.target.value;});}function handleImageChange(e) {updatePerson(draft => {draft.artwork.image = e.target.value;});}

更新State中数组

避免改变原数组,推荐使用返回新数组的

//替换push改变原数组
setArtists( // 替换 state[ // 是通过传入一个新数组实现的...artists, // 新数组包含原数组的所有元素{ id: nextId++, name: name } // 并在末尾添加了一个新的元素]
);//替换unshift
setArtists([{ id: nextId++, name: name },...artists // 将原数组中的元素放在末尾
]);//filter过滤不满足条件的(删除)
setArtists(artists.filter(a => a.id !== artist.id)
);
//数组中插入对象const insertAt = 1; // 可能是任何索引const nextArtists = [// 插入点之前的元素:...artists.slice(0, insertAt),// 新的元素:{ id: nextId++, name: name },// 插入点之后的元素:...artists.slice(insertAt)//翻转先拷贝,再拷贝基础上翻转const nextList = [...list];nextList.reverse();setList(nextList);//更新内部对象
setMyList(myList.map(artwork => {if (artwork.id === artworkId) {// 创建包含变更的*新*对象return { ...artwork, seen: nextSeen };} else {// 没有变更return artwork;}
}));

 Immmer库

const [myList, updateMyList] = useImmer(initialList);const [yourList, updateYourList] = useImmer(initialList);function handleToggleMyList(id, nextSeen) {updateMyList(draft => {const artwork = draft.find(a =>a.id === id);artwork.seen = nextSeen;});}function handleToggleYourList(artworkId, nextSeen) {updateYourList(draft => {const artwork = draft.find(a =>a.id === artworkId);artwork.seen = nextSeen;});}


http://www.ppmy.cn/news/1548934.html

相关文章

【Linux庖丁解牛】—Linux基本指令(下)!

目录 1、grep指令 2、zip/unzip指令 3、sz/rz指令 4、tar指令 ​编辑 5、scp指令 6、bc指令 7、uname –r指令 8、重要的几个热键 9、关机 10、完结撒花 1、grep指令 grep是文本过滤器&#xff0c;其作用是在指定的文件中过滤出包含你指定字符串的内容&#xff0c;…

基于isSpring的PPT转换

背景 PPT课件目前还是一项在教学中高度频繁使用的工具&#xff0c;对于在线教学就更为重要了。如何把PPT转换为在线web&#xff0c;同时保留更多的PPT特性&#xff08;动画、音效、视频&#xff09;呢&#xff1f;这里介绍一种基于iSpring的PPT转换工具。用以解决在线PPT的这一…

【Chatgpt】如何通过分层Prompt生成更加细致的图文内容

如何通过分层Prompt生成更加细致的图文内容 利用ChatGPT和类似的生成式AI模型&#xff0c;通过分层Prompt设计可以生成更具层次感和细节的图文内容。分层Prompt的核心在于将需求分解成多层次的指令&#xff0c;从宏观到微观逐步细化&#xff0c;最终形成高质量的内容输出。 一…

【字符串】给定一个字符串 text 和字符串列表 words,返回 words 中每个单词在 text 中的位置(要求最终的位置从小到大进行排序)

# 给定一个字符串 text 返回 words 中每个单词在 text 中的位置 #&#xff08;要求最终的位置从小到大进行排序&#xff09;text input("请输入text的内容&#xff1a;")words input("请输入words的内容&#xff1a;")words words.split()result []for…

非同质化代币 (NFTs)

如何创建一个NFT 要创建一个 NFT&#xff0c;你需要&#xff1a; 将图像上传到像 Arweave 这样的 IPFS 网络上。将 JSON 元数据上传到像 Arweave 这样的 IPFS 网络上。调用 Metaplex 创建一个用于该 NFT 的账户。 上传到 Arweave TSPython Press </> button to view…

RPC-健康检测机制

什么是健康检测&#xff1f; 在真实环境中服务提供方是以一个集群的方式提供服务&#xff0c;这对于服务调用方来说&#xff0c;就是一个接口会有多个服务提供方同时提供服务&#xff0c;调用方在每次发起请求的时候都可以拿到一个可用的连接。 健康检测&#xff0c;能帮助从连…

Python从0到100(七十三):Python OpenCV-OpenCV实现手势虚拟拖拽

前言&#xff1a; 零基础学Python&#xff1a;Python从0到100最新最全教程。 想做这件事情很久了&#xff0c;这次我更新了自己所写过的所有博客&#xff0c;汇集成了Python从0到100&#xff0c;共一百节课&#xff0c;帮助大家一个月时间里从零基础到学习Python基础语法、Pyth…

C语言 蓝桥杯某例题解决方案(查找完数)

蓝桥杯原题&#xff1a; 一个数如果恰好等于它的因子之和&#xff0c;这个数就称为“完数”。例如6 1 2 3.编程找出1000以内的所有完数。 这个题没有很大的难点&#xff0c;与我们上一个解决的问题“质因数分解”不同&#xff0c;它不需要判断因数是否是质数&#xff0c;因此…