使用 useReducer 改进代码

2025-06-08

使用 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函数。我们知道该函数每次、每次使用时会做什么。我们还可以更进一步,让每次使用复选框组件时都能得到更可预测的结果。还记得我们setCheckedtoggle函数中发送的那个函数吗?

setChecked(checked => !checked);

现在,我们将checked => !checked用另一个名称来指代这个函数:reducer。Reducer函数最简单的定义是,它接受当前状态并返回一个新状态。如果checkedfalse,它应该返回相反的状态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.reduceJavaScript。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
PREV
🧑‍🎨是时候停止消费并开始创造
NEXT
使用 Electron、React Native 和 Expo 制作桌面应用程序