useReducer 是一个 React 钩子,可让你将 reducer 添加到组件中。

¥useReducer is a React Hook that lets you add a reducer to your component.

const [state, dispatch] = useReducer(reducer, initialArg, init?)

参考

¥Reference

useReducer(reducer, initialArg, init?)

在组件的顶层调用 useReducer 以使用 reducer。 管理其状态

¥Call useReducer at the top level of your component to manage its state with a reducer.

import { useReducer } from 'react';

function reducer(state, action) {
// ...
}

function MyComponent() {
const [state, dispatch] = useReducer(reducer, { age: 42 });
// ...

请参阅下面的更多示例。

¥See more examples below.

参数

¥Parameters

  • reducer:reducer 函数指定如何更新状态。它必须是纯粹的,应该以状态和动作作为参数,并且应该返回下一个状态。状态和动作可以是任何类型。

    ¥reducer: The reducer function that specifies how the state gets updated. It must be pure, should take the state and action as arguments, and should return the next state. State and action can be of any types.

  • initialArg:计算初始状态的值。它可以是任何类型的值。如何从中计算初始状态取决于下一个 init 参数。

    ¥initialArg: The value from which the initial state is calculated. It can be a value of any type. How the initial state is calculated from it depends on the next init argument.

  • 可选 init:应该返回初始状态的初始化函数。如果未指定,则初始状态设置为 initialArg。否则,初始状态设置为调用 init(initialArg) 的结果。

    ¥optional init: The initializer function that should return the initial state. If it’s not specified, the initial state is set to initialArg. Otherwise, the initial state is set to the result of calling init(initialArg).

返回

¥Returns

useReducer 返回一个恰好包含两个值的数组:

¥useReducer returns an array with exactly two values:

  1. 当前状态。在第一次渲染期间,它被设置为 init(initialArg)initialArg(如果没有 init)。

    ¥The current state. During the first render, it’s set to init(initialArg) or initialArg (if there’s no init).

  2. dispatch 函数 允许你将状态更新为不同的值并触发重新渲染。

    ¥The dispatch function that lets you update the state to a different value and trigger a re-render.

注意事项

¥Caveats

  • useReducer 是一个 Hook,所以你只能在你的组件的顶层或者你自己的钩子中调用它。你不能在循环或条件内调用它。如果需要,提取一个新组件并将状态移入其中。

    ¥useReducer is a Hook, so you can only call it at the top level of your component or your own Hooks. You can’t call it inside loops or conditions. If you need that, extract a new component and move the state into it.

  • dispatch 函数具有稳定的标识,因此你经常会看到它从副作用依赖中省略,但包含它不会导致副作用触发。如果 linter 允许你在没有错误的情况下省略依赖,那么这样做是安全的。详细了解如何删除副作用依赖。

    ¥The dispatch function has a stable identity, so you will often see it omitted from Effect dependencies, but including it will not cause the Effect to fire. If the linter lets you omit a dependency without errors, it is safe to do. Learn more about removing Effect dependencies.

  • 在严格模式下,React 将调用你的 reducer 和初始化器两次,以便 帮助你发现意外杂质 这是仅开发行为,不会影响生产。如果你的 reducer 和 initializer 是纯的(它们应该是),这应该不会影响你的逻辑。其中一个调用的结果将被忽略。

    ¥In Strict Mode, React will call your reducer and initializer twice in order to help you find accidental impurities. This is development-only behavior and does not affect production. If your reducer and initializer are pure (as they should be), this should not affect your logic. The result from one of the calls is ignored.


dispatch 函数

¥dispatch function

useReducer 返回的 dispatch 函数允许你将状态更新为不同的值并触发重新渲染。你需要将操作作为唯一参数传递给 dispatch 函数:

¥The dispatch function returned by useReducer lets you update the state to a different value and trigger a re-render. You need to pass the action as the only argument to the dispatch function:

const [state, dispatch] = useReducer(reducer, { age: 42 });

function handleClick() {
dispatch({ type: 'incremented_age' });
// ...

React 会将下一个状态设置为调用你为当前 state 提供的 reducer 函数以及你传递给 dispatch 的操作的结果。

¥React will set the next state to the result of calling the reducer function you’ve provided with the current state and the action you’ve passed to dispatch.

参数

¥Parameters

  • action:用户执行的操作。它可以是任何类型的值。按照惯例,一个动作通常是一个对象,它有一个 type 属性来标识它,也可以是带有附加信息的其他属性。

    ¥action: The action performed by the user. It can be a value of any type. By convention, an action is usually an object with a type property identifying it and, optionally, other properties with additional information.

返回

¥Returns

dispatch 函数没有返回值。

¥dispatch functions do not have a return value.

注意事项

¥Caveats

  • dispatch 函数仅更新下一次渲染的状态变量。如果你在调用 dispatch 函数后读取状态变量,则 你仍然会得到旧的值 在你调用之前显示在屏幕上。

    ¥The dispatch function only updates the state variable for the next render. If you read the state variable after calling the dispatch function, you will still get the old value that was on the screen before your call.

  • 如果你提供的新值与当前的 state 相同(通过 Object.is 比较确定),React 将跳过重新渲染组件及其子组件。这是一个优化。React 可能仍然需要在忽略结果之前调用你的组件,但它不应该影响你的代码。

    ¥If the new value you provide is identical to the current state, as determined by an Object.is comparison, React will skip re-rendering the component and its children. This is an optimization. React may still need to call your component before ignoring the result, but it shouldn’t affect your code.

  • React 批量状态更新。 在所有事件处理程序运行并调用其 set 函数后更新屏幕。这可以防止在单个事件期间多次重新渲染。在极少数情况下,你需要强制 React 提前更新屏幕,例如访问 DOM,你可以使用 flushSync

    ¥React batches state updates. It updates the screen after all the event handlers have run and have called their set functions. This prevents multiple re-renders during a single event. In the rare case that you need to force React to update the screen earlier, for example to access the DOM, you can use flushSync.


用法

¥Usage

向组件添加 reducer

¥Adding a reducer to a component

在组件的顶层调用 useReducer 以使用 reducer。 管理状态

¥Call useReducer at the top level of your component to manage state with a reducer.

import { useReducer } from 'react';

function reducer(state, action) {
// ...
}

function MyComponent() {
const [state, dispatch] = useReducer(reducer, { age: 42 });
// ...

useReducer 返回一个恰好包含两项的数组:

¥useReducer returns an array with exactly two items:

  1. 此状态变量的当前状态,初始设置为你提供的初始状态

    ¥The current state of this state variable, initially set to the initial state you provided.

  2. dispatch 函数 可让你更改它以响应交互。

    ¥The dispatch function that lets you change it in response to interaction.

要更新屏幕上的内容,请调用 dispatch 并使用表示用户所做操作的对象,称为操作:

¥To update what’s on the screen, call dispatch with an object representing what the user did, called an action:

function handleClick() {
dispatch({ type: 'incremented_age' });
}

React 会将当前状态和操作传递给你的 reducer 函数。你的 reducer 将计算并返回下一个状态。React 将存储下一个状态,用它渲染你的组件,并更新 UI。

¥React will pass the current state and the action to your reducer function. Your reducer will calculate and return the next state. React will store that next state, render your component with it, and update the UI.

import { useReducer } from 'react';

function reducer(state, action) {
  if (action.type === 'incremented_age') {
    return {
      age: state.age + 1
    };
  }
  throw Error('Unknown action.');
}

export default function Counter() {
  const [state, dispatch] = useReducer(reducer, { age: 42 });

  return (
    <>
      <button onClick={() => {
        dispatch({ type: 'incremented_age' })
      }}>
        Increment age
      </button>
      <p>Hello! You are {state.age}.</p>
    </>
  );
}

useReduceruseState 非常相似,但它允许你将状态更新逻辑从事件处理程序移动到组件外部的单个函数中。阅读更多关于 useStateuseReducer 之间选择。

¥useReducer is very similar to useState, but it lets you move the state update logic from event handlers into a single function outside of your component. Read more about choosing between useState and useReducer.


编写 reducer 函数

¥Writing the reducer function

reducer 函数声明如下:

¥A reducer function is declared like this:

function reducer(state, action) {
// ...
}

然后你需要填写将计算并返回下一个状态的代码。按照惯例,通常将其写为 switch 声明。 对于 switch 中的每个 case,计算并返回一些下一个状态。

¥Then you need to fill in the code that will calculate and return the next state. By convention, it is common to write it as a switch statement. For each case in the switch, calculate and return some next state.

function reducer(state, action) {
switch (action.type) {
case 'incremented_age': {
return {
name: state.name,
age: state.age + 1
};
}
case 'changed_name': {
return {
name: action.nextName,
age: state.age
};
}
}
throw Error('Unknown action: ' + action.type);
}

动作可以有任何形状。按照惯例,传递具有标识操作的 type 属性的对象是很常见的。它应该包括 reducer 计算下一个状态所需的最少必要信息。

¥Actions can have any shape. By convention, it’s common to pass objects with a type property identifying the action. It should include the minimal necessary information that the reducer needs to compute the next state.

function Form() {
const [state, dispatch] = useReducer(reducer, { name: 'Taylor', age: 42 });

function handleButtonClick() {
dispatch({ type: 'incremented_age' });
}

function handleInputChange(e) {
dispatch({
type: 'changed_name',
nextName: e.target.value
});
}
// ...

动作类型名称是你的组件的本地名称。每个操作都描述了一次交互,即使这会导致数据发生多次更改。 状态的形状是任意的,但通常是一个对象或一个数组。

¥The action type names are local to your component. Each action describes a single interaction, even if that leads to multiple changes in data. The shape of the state is arbitrary, but usually it’ll be an object or an array.

阅读 将状态逻辑提取到 reducer 中 以了解更多信息。

¥Read extracting state logic into a reducer to learn more.

易犯错误

状态是只读的。不要修改状态中的任何对象或数组:

¥State is read-only. Don’t modify any objects or arrays in state:

function reducer(state, action) {
switch (action.type) {
case 'incremented_age': {
// 🚩 Don't mutate an object in state like this:
state.age = state.age + 1;
return state;
}

而是,总是从你的 reducer 返回新对象:

¥Instead, always return new objects from your reducer:

function reducer(state, action) {
switch (action.type) {
case 'incremented_age': {
// ✅ Instead, return a new object
return {
...state,
age: state.age + 1
};
}

阅读 更新状态中的对象更新状态数组 以了解更多信息。

¥Read updating objects in state and updating arrays in state to learn more.

Basic useReducer examples

例子 1 / 3:
表单(对象)

¥Form (object)

在此示例中,reducer 管理一个具有两个字段的状态对象:nameage

¥In this example, the reducer manages a state object with two fields: name and age.

import { useReducer } from 'react';

function reducer(state, action) {
  switch (action.type) {
    case 'incremented_age': {
      return {
        name: state.name,
        age: state.age + 1
      };
    }
    case 'changed_name': {
      return {
        name: action.nextName,
        age: state.age
      };
    }
  }
  throw Error('Unknown action: ' + action.type);
}

const initialState = { name: 'Taylor', age: 42 };

export default function Form() {
  const [state, dispatch] = useReducer(reducer, initialState);

  function handleButtonClick() {
    dispatch({ type: 'incremented_age' });
  }

  function handleInputChange(e) {
    dispatch({
      type: 'changed_name',
      nextName: e.target.value
    }); 
  }

  return (
    <>
      <input
        value={state.name}
        onChange={handleInputChange}
      />
      <button onClick={handleButtonClick}>
        Increment age
      </button>
      <p>Hello, {state.name}. You are {state.age}.</p>
    </>
  );
}


避免重新创建初始状态

¥Avoiding recreating the initial state

React 会保存一次初始状态,并在下一次渲染时忽略它。

¥React saves the initial state once and ignores it on the next renders.

function createInitialState(username) {
// ...
}

function TodoList({ username }) {
const [state, dispatch] = useReducer(reducer, createInitialState(username));
// ...

尽管 createInitialState(username) 的结果仅用于初始渲染,但你仍会在每次渲染时调用此函数。如果要创建大型数组或执行昂贵的计算,这可能会造成浪费。

¥Although the result of createInitialState(username) is only used for the initial render, you’re still calling this function on every render. This can be wasteful if it’s creating large arrays or performing expensive calculations.

为了解决这个问题,你可以将其作为初始化函数传递给 useReducer 作为第三个参数:

¥To solve this, you may pass it as an initializer function to useReducer as the third argument instead:

function createInitialState(username) {
// ...
}

function TodoList({ username }) {
const [state, dispatch] = useReducer(reducer, username, createInitialState);
// ...

请注意,你传递的是 createInitialState,它是函数本身,而不是 createInitialState(),它是调用它的结果。这样,初始化后不会重新创建初始状态。

¥Notice that you’re passing createInitialState, which is the function itself, and not createInitialState(), which is the result of calling it. This way, the initial state does not get re-created after initialization.

在上面的示例中,createInitialState 采用 username 参数。如果你的初始化程序不需要任何信息来计算初始状态,你可以将 null 作为第二个参数传递给 useReducer

¥In the above example, createInitialState takes a username argument. If your initializer doesn’t need any information to compute the initial state, you may pass null as the second argument to useReducer.

The difference between passing an initializer and passing the initial state directly

例子 1 / 2:
传递初始化函数

¥Passing the initializer function

这个例子传递了初始化函数,所以 createInitialState 函数只在初始化期间运行。它不会在组件重新渲染时运行,例如当你在输入中键入时。

¥This example passes the initializer function, so the createInitialState function only runs during initialization. It does not run when component re-renders, such as when you type into the input.

import { useReducer } from 'react';

function createInitialState(username) {
  const initialTodos = [];
  for (let i = 0; i < 50; i++) {
    initialTodos.push({
      id: i,
      text: username + "'s task #" + (i + 1)
    });
  }
  return {
    draft: '',
    todos: initialTodos,
  };
}

function reducer(state, action) {
  switch (action.type) {
    case 'changed_draft': {
      return {
        draft: action.nextDraft,
        todos: state.todos,
      };
    };
    case 'added_todo': {
      return {
        draft: '',
        todos: [{
          id: state.todos.length,
          text: state.draft
        }, ...state.todos]
      }
    }
  }
  throw Error('Unknown action: ' + action.type);
}

export default function TodoList({ username }) {
  const [state, dispatch] = useReducer(
    reducer,
    username,
    createInitialState
  );
  return (
    <>
      <input
        value={state.draft}
        onChange={e => {
          dispatch({
            type: 'changed_draft',
            nextDraft: e.target.value
          })
        }}
      />
      <button onClick={() => {
        dispatch({ type: 'added_todo' });
      }}>Add</button>
      <ul>
        {state.todos.map(item => (
          <li key={item.id}>
            {item.text}
          </li>
        ))}
      </ul>
    </>
  );
}


故障排除

¥Troubleshooting

我已经发送了一个动作,但是日志记录给了我旧的状态值

¥I’ve dispatched an action, but logging gives me the old state value

调用 dispatch 函数不会更改运行代码中的状态:

¥Calling the dispatch function does not change state in the running code:

function handleClick() {
console.log(state.age); // 42

dispatch({ type: 'incremented_age' }); // Request a re-render with 43
console.log(state.age); // Still 42!

setTimeout(() => {
console.log(state.age); // Also 42!
}, 5000);
}

这是因为 状态的行为类似于快照。 更新状态请求另一个具有新状态值的渲染,但不会影响你已经运行的事件处理程序中的 state JavaScript 变量。

¥This is because states behaves like a snapshot. Updating state requests another render with the new state value, but does not affect the state JavaScript variable in your already-running event handler.

如果需要猜测下一个状态值,可以自己调用 reducer 手动计算:

¥If you need to guess the next state value, you can calculate it manually by calling the reducer yourself:

const action = { type: 'incremented_age' };
dispatch(action);

const nextState = reducer(state, action);
console.log(state); // { age: 42 }
console.log(nextState); // { age: 43 }

我已经发送了一个动作,但屏幕没有更新

¥I’ve dispatched an action, but the screen doesn’t update

如果下一个状态等于前一个状态(由 Object.is 比较确定),React 将忽略你的更新。当你直接更改状态中的对象或数组时,通常会发生这种情况:

¥React will ignore your update if the next state is equal to the previous state, as determined by an Object.is comparison. This usually happens when you change an object or an array in state directly:

function reducer(state, action) {
switch (action.type) {
case 'incremented_age': {
// 🚩 Wrong: mutating existing object
state.age++;
return state;
}
case 'changed_name': {
// 🚩 Wrong: mutating existing object
state.name = action.nextName;
return state;
}
// ...
}
}

你改变了一个现有的 state 对象并返回它,所以 React 忽略了更新。要解决此问题,你需要确保你始终是 更新状态中的对象更新状态数组 而不是改变它们:

¥You mutated an existing state object and returned it, so React ignored the update. To fix this, you need to ensure that you’re always updating objects in state and updating arrays in state instead of mutating them:

function reducer(state, action) {
switch (action.type) {
case 'incremented_age': {
// ✅ Correct: creating a new object
return {
...state,
age: state.age + 1
};
}
case 'changed_name': {
// ✅ Correct: creating a new object
return {
...state,
name: action.nextName
};
}
// ...
}
}

调度后我的 reducer 状态的一部分变得未定义

¥A part of my reducer state becomes undefined after dispatching

确保每个 case 分支在返回新状态时复制所有现有字段:

¥Make sure that every case branch copies all of the existing fields when returning the new state:

function reducer(state, action) {
switch (action.type) {
case 'incremented_age': {
return {
...state, // Don't forget this!
age: state.age + 1
};
}
// ...

如果没有上面的 ...state,返回的下一个状态将只包含 age 字段,没有其他内容。

¥Without ...state above, the returned next state would only contain the age field and nothing else.


调度后我的整个 reducer 状态变得未定义

¥My entire reducer state becomes undefined after dispatching

如果你的状态意外变为 undefined,你可能在其中一种情况下忘记了 return 状态,或者你的操作类型与任何 case 语句都不匹配。要查找原因,请在 switch 之外抛出一个错误:

¥If your state unexpectedly becomes undefined, you’re likely forgetting to return state in one of the cases, or your action type doesn’t match any of the case statements. To find why, throw an error outside the switch:

function reducer(state, action) {
switch (action.type) {
case 'incremented_age': {
// ...
}
case 'edited_name': {
// ...
}
}
throw Error('Unknown action: ' + action.type);
}

你还可以使用 TypeScript 等静态类型检查器来捕获此类错误。

¥You can also use a static type checker like TypeScript to catch such mistakes.


我收到错误:“太多的重新渲染”

¥I’m getting an error: “Too many re-renders”

你可能会收到一条错误消息:Too many re-renders. React limits the number of renders to prevent an infinite loop. 通常,这意味着你在渲染期间无条件地分派一个动作,因此你的组件进入一个循环:render、dispatch(导致渲染)、render、dispatch(导致渲染)等等。通常,这是由于指定事件处理程序的错误造成的:

¥You might get an error that says: Too many re-renders. React limits the number of renders to prevent an infinite loop. Typically, this means that you’re unconditionally dispatching an action during render, so your component enters a loop: render, dispatch (which causes a render), render, dispatch (which causes a render), and so on. Very often, this is caused by a mistake in specifying an event handler:

// 🚩 Wrong: calls the handler during render
return <button onClick={handleClick()}>Click me</button>

// ✅ Correct: passes down the event handler
return <button onClick={handleClick}>Click me</button>

// ✅ Correct: passes down an inline function
return <button onClick={(e) => handleClick(e)}>Click me</button>

如果找不到此错误的原因,请单击控制台中错误旁边的箭头并查看 JavaScript 堆栈以查找导致错误的特定 dispatch 函数调用。

¥If you can’t find the cause of this error, click on the arrow next to the error in the console and look through the JavaScript stack to find the specific dispatch function call responsible for the error.


我的 reducer 或初始化函数运行两次

¥My reducer or initializer function runs twice

严格模式 中,React 将调用你的 reducer 和 initializer 函数两次。这不应该破坏你的代码。

¥In Strict Mode, React will call your reducer and initializer functions twice. This shouldn’t break your code.

这种仅用于开发的行为可以帮助你 保持组件纯粹。 React 使用其中一个调用的结果,并忽略另一个调用的结果。只要你的组件、初始化器和缩减器函数是纯函数,这就不会影响你的逻辑。但是,如果它们不小心不纯,这有助于你发现错误。

¥This development-only behavior helps you keep components pure. React uses the result of one of the calls, and ignores the result of the other call. As long as your component, initializer, and reducer functions are pure, this shouldn’t affect your logic. However, if they are accidentally impure, this helps you notice the mistakes.

例如,这个不纯的 reducer 函数会改变状态中的数组:

¥For example, this impure reducer function mutates an array in state:

function reducer(state, action) {
switch (action.type) {
case 'added_todo': {
// 🚩 Mistake: mutating state
state.todos.push({ id: nextId++, text: action.text });
return state;
}
// ...
}
}

因为 React 两次调用你的 reducer 函数,你会看到 todo 被添加了两次,所以你会知道有一个错误。在这个例子中,你可以通过 替换数组而不是改变它 来修复错误:

¥Because React calls your reducer function twice, you’ll see the todo was added twice, so you’ll know that there is a mistake. In this example, you can fix the mistake by replacing the array instead of mutating it:

function reducer(state, action) {
switch (action.type) {
case 'added_todo': {
// ✅ Correct: replacing with new state
return {
...state,
todos: [
...state.todos,
{ id: nextId++, text: action.text }
]
};
}
// ...
}
}

现在这个 reducer 函数是纯粹的,额外调用它不会对行为产生影响。这就是为什么 React 调用它两次可以帮助你发现错误。只有组件、初始化器和缩减器函数需要是纯函数。事件处理程序不需要是纯粹的,因此 React 永远不会调用你的事件处理程序两次。

¥Now that this reducer function is pure, calling it an extra time doesn’t make a difference in behavior. This is why React calling it twice helps you find mistakes. Only component, initializer, and reducer functions need to be pure. Event handlers don’t need to be pure, so React will never call your event handlers twice.

阅读 保持组件纯粹 以了解更多信息。

¥Read keeping components pure to learn more.


React 中文网 - 粤ICP备13048890号