useReducer
usesSate 只有 setState 一个方法修改 state,useReducer 相当于能自定义多个修改 state 的方法。useReducer 是一个 React Hook,它允许你向组件里面添加一个 reducer。
使用方式
const [state, dispatch] = useReducer(reducer, initialArg, init?)
传入的参数是
- 用于更新 state 的纯函数 reducer 。reducer接受两个参数—— state 和 action 。返回值是更新后的 state 。
- initialArg 是初始化 state 的任意值。如果第三个参数 init 存在,则根据 init 的逻辑计算初始值;否则,直接使用 initialArg 作为初始值。
- 可选参数 init 是用于计算初始值的函数,接受 initialArg 作为参数,返回值为 state 的初始值。
返回值有两个
- 当前的状态 state ;
- dispatch 函数,用于更新 state 并触发组件的重新渲染;
注意事项:
- useReducer 是一个 Hook,所以只能在 组件的顶层作用域 或自定义 Hook 中调用,而不能在循环或条件语句中调用。
Reducer
Reducer 函数可以将组件所有状态更新逻辑整合到一个外部函数当中。可以通过三个步骤将 useState
迁移到 useReducer
:
- 将设置状态的逻辑 修改 成 dispatch 的一个 action;
- 编写 一个 reducer 函数;
- 在你的组件中 使用 reducer。
现在组件中有一个 count 的状态,需要增加两个 button ,一个对 count 进行 + 1 的操作,一个对 ount 进行 - 1 的操作。使用设置状态 setCount 的写法是
const [count, setCount] = useState(0)
const handleaAdd = () => {
setCount(count + 1)
}
const handleReduce = () => {
setCount(count - 1)
}
使用Reducer来管理状态需要通过事件处理程序 dispatch 一个 action 来指明用户 “做了什么”。所以在下面,我们不需要通过 setCount 来设置 count ,而是dispatch 一个 “+1”或“ - 1”的action。
const handleaAdd = () => {
dispatch({
type: 'add'
})
}
const handleReduce = () => {
dispatch({
type: 'reduce'
})
}
这时候我们传给 dispatch 的参数是一个 action 对象。这个 action 对象是一个普通的 JavaScript 对象。action 对象中的数据结构是由开发者自己决定的。不过通常会包含“执行了什么操作”的信息,也就是 type 字段。
下一步需要编写一个 reducer 函数。这个 reducer 函数就是我们需要放置状态更新逻辑的地方。reducer接受两个参数—— state 和 action 。并且会返回更新后的参数。并且 reducer 函数可以在组件之外声明。
function countReducer(state, action) {
if (action.type === 'add') {
return state + 1
} else if (action.type === 'reducer') {
return state - 1
}
}
一般来说可以使用 swtich 的写法,会提高代码的可读性:
function countReducer(state, action) {
swtich(action.type) {
case 'add': return state + 1
case 'reduce': return state - 1
default: return state
}
}
dispatch函数
useReducer 返回值中第二个元素就是 dispatch 函数。 dispatch 函数允许你更新 state 并触发组件的重新渲染,它需要传入一个 action 作为参数。当调用 dispatch 函数的时候,React就会调用 reducer 函数来根据传入的 action 更新 state 。
注意:
- dispatch 函数本身没有返回值。
- dispatch 函数 是为下一次渲染而更新 state。因此在调用 dispatch 函数后读取 state 并不会拿到更新后的值,也就是说只能获取到调用前的值。可以理解为我们使用设置状态 setState 来直接对 state 赋值的情况。
- 如果你提供的新值与当前的 state 相同),React 会 跳过组件和子组件的重新渲染。
组件中使用
按照上面的例子,最初更新 count 的方法是
import { useState } from "react"
export default function UseReducerMemo () {
const [count, setCount] = useState(0)
const handleaAdd = () => {
setCount(count + 1)
}
const handleReduce = () => {
setCount(count - 1)
}
return (
<div>
<div>count: {count}</div>
<div>
<button onClick={handleaAdd}>add</button>
<button onClick={handleReduce}>reduce</button>
</div>
</div>
)
}
这个时候我们创建一个 reducer 函数,将更新 count 的逻辑放在 reducer 函数中。
import { useReducer } from "react"
function countReducer(state, action) {
if (action.type === 'add') {
return state + 1
} else if (action.type === 'reducer') {
return state - 1
}
}
export default function UseReducerMemo () {
const [count, countDispatch] = useReducer(countReducer, 0)
const handleaAdd = () => {
countDispatch({
type: 'add'
})
}
const handleReduce = () => {
countDispatch({
type: 'reducer'
})
}
return (
<div>
<div>count: {count}</div>
<div>
<button onClick={handleaAdd}>add</button>
<button onClick={handleReduce}>reduce</button>
</div>
</div>
)
}
注意:
- state 是只读的。即使是对象或数组也不要尝试修改它,而是返回新的对象。否则,React 使用 Object.is 比较更新前后的 state,如果 它们相等就会跳过这次更新。这就会导致页面并没有更新。
- 调用 dispatch 并不会更新本次渲染函数中的变量。是因为 state 就像一张快照一样,每次渲染函数内部拥有自己独立的props、state和变量。所以 dispatch 之后打印出来的变量仍然没有发生变化。