使用 useReducer 改进代码
我们的《学习 React》第二版系列文章的下一篇是关于的useReducer
。
考虑一下这个Checkbox
组件。这个组件是一个保存简单状态的完美示例。复选框要么被选中,要么未被选中。checked
是状态值,setChecked
是一个用于更改状态的函数。当组件首次渲染时, 的值checked
将是false
:
function Checkbox() {
const [checked, setChecked] = useState(false);
return (
<>
<input
type="checkbox"
value={checked}
onChange={() => setChecked(checked => !checked)}
/>
{checked ? "checked" : "not checked"}
</>
);
}
这种方法效果很好,但是该函数的一个区域可能会引起警报:
onChange={() => setChecked(checked => !checked)}
仔细看看。乍一看还行,但我们是不是在制造麻烦?我们发送了一个函数,它接受 的当前值checked
,并返回它的反值!checked
。这可能比实际需要的更复杂。开发人员很容易发送错误的信息,从而破坏整个流程。与其这样处理,为什么不提供一个函数作为切换开关呢?
让我们添加一个名为的函数toggle
来做同样的事情:调用setChecked
并返回当前值的反面checked
:
function Checkbox() {
const [checked, setChecked] = useState(false);
function toggle() {
setChecked(checked => !checked);
}
return (
<>
<input type="checkbox" value={checked} onChange={toggle} />
{checked ? "checked" : "not checked"}
</>
);
}
这样更好。onChange
它被设置为一个可预测的值:toggle
函数。我们知道该函数每次、每次使用时会做什么。我们还可以更进一步,让每次使用复选框组件时都能得到更可预测的结果。还记得我们setChecked
在toggle
函数中发送的那个函数吗?
setChecked(checked => !checked);
现在,我们将checked => !checked
用另一个名称来指代这个函数:reducer。Reducer函数最简单的定义是,它接受当前状态并返回一个新状态。如果checked
是false
,它应该返回相反的状态true
。与其将此行为硬编码到onChange
事件中,不如将逻辑抽象为一个始终产生相同结果的 Reducer 函数。在useState
组件中,我们将使用useReducer
:
function Checkbox() {
const [checked, toggle] = useReducer(checked => !checked, false);
return (
<>
<input type="checkbox" value={checked} onChange={toggle} />
{checked ? "checked" : "not checked"}
</>
);
}
useReducer
接受 Reducer 函数和初始状态false
。然后我们将设置将调用 Reducer 函数的onChange
函数。toggle
我们之前的 Reducerchecked => !checked
就是一个很好的例子。如果给一个函数提供相同的输入,那么应该得到相同的输出。这个概念源自Array.reduce
JavaScript。reduce
它本质上和 Reducer 的功能相同:它接受一个函数(将所有值 Reduce 为一个值)和一个初始值,并返回一个值。
Array.reduce
接受一个 Reducer 函数和一个初始值。对于numbers
数组中的每个值,都会调用 Reducer 函数,直到返回一个值。
const numbers = [28, 34, 67, 68];
numbers.reduce((number, nextNumber) => number + nextNumber, 0); // 197
发送给 Reducer 的函数Array.reduce
接受两个参数。你也可以向 Reducer 函数发送多个参数:
function Numbers() {
const [number, setNumber] = useReducer(
(number, newNumber) => number + newNumber,
0
);
return <h1 onClick={() => setNumber(30)}>{number}</h1>;
}
每次我们点击时h1
,我们都会将总数增加 30。
useReducer 处理复杂状态
useReducer
随着状态变得越来越复杂,它可以帮助我们更可预测地处理状态更新。考虑一个包含用户数据的对象:
const firstUser = {
id: "0391-3233-3201",
firstName: "Bill",
lastName: "Wilson",
city: "Missoula",
state: "Montana",
email: "bwilson@mtnwilsons.com",
admin: false
};
然后我们有一个名为的组件User
,它将设置firstUser
为初始状态,并且该组件显示适当的数据:
function User() {
const [user, setUser] = useState(firstUser);
return (
<div>
<h1>
{user.firstName} {user.lastName} - {user.admin ? "Admin" : "User"}
</h1>
<p>Email: {user.email}</p>
<p>
Location: {user.city}, {user.state}
</p>
<button>Make Admin</button>
</div>
);
}
管理状态时的一个常见错误是覆盖状态:
<button
onClick={() => {
setUser({ admin: true });
}}
>
Make Admin
</button>
这样做会覆盖来自的状态firstUser
,并将其替换为我们发送给setUser
函数的内容:{admin: true}
。可以通过传播来自用户的当前值,然后覆盖该admin
值来解决此问题:
<button
onClick={() => {
setUser({ ...user, admin: true });
}}
>
Make Admin
</button>
这将获取初始状态并推送新的键/值:{admin: true}
。我们需要在每个 中重写此逻辑onClick
,这很容易出错。我明天回来的时候可能会忘记这样做。
function User() {
const [user, setUser] = useReducer(
(user, newDetails) => ({ ...user, ...newDetails }),
firstUser
);
...
}
然后将新的状态值发送newDetails
给 Reducer,并将其推送到对象中:
<button
onClick={() => {
setUser({ admin: true });
}}
>
Make Admin
</button>
当状态包含多个子值,或者下一个状态依赖于前一个状态时,此模式非常有用。这里我们利用了传播的力量。教会每个人传播,他们只会传播一天。教会每个人使用 Reducer,他们就会终生传播。
鏂囩珷鏉ユ簮锛�https://dev.to/eveporcello/improving-code-with-usereducer-2lda