主要做状态管理
useState
两个功能:
- State 变量 用于保存渲染间的数据。
- State
setter函数 更新变量并触发 React 再次渲染组件。
语法
const [thing, setThing] = useState(0);- useState 参数:初始值
- useState 返回的结果:
- 第一个是保存的上次渲染的值
- 第二个是更新 state 变量并触发 React 重新渲染组件的函数
state 是不可变的(只读的)
当修改一个对象 state 时,需要创建一个新对象并把它传递给 state 的设置函数
对象
如果只修改对象中的部分值,使用展开语法:
setPerson({
...person, // 复制上一个 person 中的所有字段
firstName: e.target.value // 但是覆盖 firstName 字段
});数组
| 避免使用 (会改变原始数组) | 推荐使用 (会返回一个新数组) | |
|---|---|---|
| 添加元素 | push,unshift | concat,[...arr] 展开语法(例子) |
| 删除元素 | pop,shift,splice | filter,slice(例子) |
| 替换元素 | splice,arr[i] = ... 赋值 | map(例子) |
| 排序 | reverse,sort | 先将数组复制一份(例子) |
为什么在 React 中不推荐直接修改 state?
有以下几个原因:
- 优化:React 常见的 优化策略 依赖于如果之前的 props 或者 state 的值和下一次相同就跳过渲染。通过对象地址是否改变可以快速判断是否需要优化
- 快照:每次渲染类似于快照,记录当时的 state
- 简单:React 并不需要像很多“响应式”的解决方案一样去劫持对象的属性、总是用代理把对象包裹起来,或者在初始化时做其他工作。React 中直接修改对象属性值并不会触发重渲染
setState 执行机制
- 在组件生命周期或 React 合成事件中,setState 是异步
- 在 setTimeout 或者原生 dom 事件中,setState 是同步
class Example extends React.Component {
constructor() {
super()
this.state = {
val: 0
}
}
componentDidMount() {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val)
// 第 1 次 log
this.setState({ val: this.state.val + 1 })
console.log(this.state.val)
// 第 2 次 log
setTimeout(() => {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val)
// 第 3 次 log
this.setState({ val: this.state.val + 1 })
console.log(this.state.val)
// 第 4 次 log
}, 0)
}
render() {
return null
}
}
// 0, 0, 2, 3
// componentDidMount中是异步的,状态不会立即更新,当前this.state.val仍然是0
// setTimeout中的代码是同步的,而且此时React已经处理了前两个setState的批处理更新,将val更新为1setState 多次执行
由于 React 会对 state 更新进行批处理,导致多次 setState 修改只会有最后一次有效
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>
</>
)
}
// 相当于:
setNumber(0 + 1);
setNumber(0 + 1);
setNumber(0 + 1);解决方法:给 setState 传入一个 根据队列中的前一个 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>
</>
)
}Info
- 设置 state 不会更改现有渲染中的变量,但会请求一次新的渲染。
- React 会在事件处理函数执行完成之后处理 state 更新。这被称为批处理。
- 要在一个事件中多次更新某些 state,可以使用 setNumber(n ⇒ n + 1) 更新函数。
不用 state 的情况
如果一个值可以基于现有的 props 或 state 计算得出,不要把它作为一个 state,而是在渲染期间直接计算这个值。
useReducer
对于拥有许多状态更新逻辑的组件来说,可以使用 reducer 整合状态逻辑
Reducer 是处理状态的另一种方式。可以通过三个步骤将 useState 迁移到 useReducer:
- 编写 一个 reducer 函数替换掉原来更新 state 的逻辑
- 在组件中 使用 useReducer
- 将设置状态的逻辑 修改 成 dispatch 一个 action
1. 写 reducer:
export default function myReducer(states, action) {
switch (action.type) {
// // 每个 case 块包装到 { 和 } 花括号中,保证独立的作用域
case 1: {
return ... // 给 React 返回更新后的状态
},
case 2: {
return ... // 给 React 返回更新后的状态
},
// ...
}
}- 声明当前状态(states)作为第一个参数
- 声明 action 对象作为第二个参数
- 从 reducer 返回 下一个 状态(React 会将旧的状态设置为这个最新的状态)
2. 使用 useReducer:
import { useReducer } from 'react';
import myReducer from './myReducer.js';
const initialData = ...
export default function App() {
const [states, dispatch] = useReducer(myReducer, initialData)
// ...
}useReducer 钩子接受 2 个参数:
- 一个 reducer 函数
- 一个初始的 state
它返回如下内容:
- 一个有状态的值
- 一个 dispatch 函数(用来 “派发” 用户操作给 reducer)
3. 使用 dispatch 触发更新:
export default function App() {
const [states, dispatch] = useReducer(myReducer, initialData)
function handleState(val) {
// dispatch 传入的对象就叫 action
dispatch({
// 针对特定的组件
type: 'what_happened',
// 其它字段放这里
});
}
// ...
}