如何在React中使用Reducer(详细教程)

1,319 阅读4分钟

自从React钩子发布后,函数组件可以使用状态和副作用。有两个钩子用于React的现代状态管理:useState和useReducer。本教程将逐步介绍React中的useReducer例子,让你开始使用这个用于状态管理的React Hook。

React中的还原器

如果你还没有听说过还原器的概念或在JavaScript中的实现,你应该在这里阅读更多关于它们的信息。JavaScript中的还原器。本教程是建立在这些知识之上的,所以要为接下来的内容做好准备。下面的函数是一个用于管理项目列表的状态转换的还原器函数。

const todoReducer = (state, action) => {
  switch (action.type) {
    case 'DO_TODO':
      return state.map(todo => {
        if (todo.id === action.id) {
          return { ...todo, complete: true };
        } else {
          return todo;
        }
      });
    case 'UNDO_TODO':
      return state.map(todo => {
        if (todo.id === action.id) {
          return { ...todo, complete: false };
        } else {
          return todo;
        }
      });
    default:
      return state;
  }
};

有两种类型的动作,相当于两个状态转换。它们被用来将Todo项目的complete 布尔值切换为真或假。作为一个额外的有效载荷,需要一个标识符,它来自传入动作的有效载荷。

const todos = [
  {
    id: 'a',
    task: 'Learn React',
    complete: false,
  },
  {
    id: 'b',
    task: 'Learn Firebase',
    complete: false,
  },
];

在代码中,减速器函数可以通过以下方式使用,包括一个初始状态和动作:

const todos = [
  {
    id: 'a',
    task: 'Learn React',
    complete: false,
  },
  {
    id: 'b',
    task: 'Learn Firebase',
    complete: false,
  },
];

const action = {
  type: 'DO_TODO',
  id: 'a',
};

const newTodos = todoReducer(todos, action);

console.log(newTodos);
// [
//   {
//     id: 'a',
//     task: 'Learn React',
//     complete: true,
//   },
//   {
//     id: 'b',
//     task: 'Learn Firebase',
//     complete: false,
//   },
// ]

到目前为止,这里展示的一切都与React无关。如果你在理解减速器的概念上有任何困难,请从头开始重温参考的《JavaScript中的减速器》教程。现在,让我们深入了解React的useReducer钩子,逐步在React中整合减速器。

React的useReducer钩子

useReducer钩子用于复杂的状态和状态转换。它接收一个减速器函数和初始状态作为输入,并返回当前状态和一个调度函数作为数组解构的输出:

const initialTodos = [
  {
    id: 'a',
    task: 'Learn React',
    complete: false,
  },
  {
    id: 'b',
    task: 'Learn Firebase',
    complete: false,
  },
];

const todoReducer = (state, action) => {
  switch (action.type) {
    case 'DO_TODO':
      return state.map(todo => {
        if (todo.id === action.id) {
          return { ...todo, complete: true };
        } else {
          return todo;
        }
      });
    case 'UNDO_TODO':
      return state.map(todo => {
        if (todo.id === action.id) {
          return { ...todo, complete: false };
        } else {
          return todo;
        }
      });
    default:
      return state;
  }
};

const [todos, dispatch] = useReducer(todoReducer, initialTodos);

派遣函数可以用来向还原器发送一个动作,这将隐含地改变当前状态:

const [todos, dispatch] = React.useReducer(todoReducer, initialTodos);
dispatch({ type: 'DO_TODO', id: 'a' });

如果不在React组件中执行,前面的例子就不会起作用,但它展示了如何通过调度一个动作来改变状态。让我们看看这在React组件中会是什么样子。我们将从一个渲染项目列表的React组件开始。每个项目都有一个复选框作为受控组件

import React from 'react';

const initialTodos = [
  {
    id: 'a',
    task: 'Learn React',
    complete: false,
  },
  {
    id: 'b',
    task: 'Learn Firebase',
    complete: false,
  },
];

const App = () => {
  const handleChange = () => {};

  return (
    <ul>
      {initialTodos.map(todo => (
        <li key={todo.id}>
          <label>
            <input
              type="checkbox"
              checked={todo.complete}
              onChange={handleChange}
            />
            {todo.task}
          </label>
        </li>
      ))}
    </ul>
  );
};

export default App;

现在还不能用处理函数来改变一个项目的状态。然而,在我们这样做之前,我们需要将项目列表作为我们的useReducer钩子的初始状态,用之前定义的reducer函数使其成为有状态的。

import React from 'react';

const initialTodos = [...];

const todoReducer = (state, action) => {
  switch (action.type) {
    case 'DO_TODO':
      return state.map(todo => {
        if (todo.id === action.id) {
          return { ...todo, complete: true };
        } else {
          return todo;
        }
      });
    case 'UNDO_TODO':
      return state.map(todo => {
        if (todo.id === action.id) {
          return { ...todo, complete: false };
        } else {
          return todo;
        }
      });
    default:
      return state;
  }
};

const App = () => {
  const [todos, dispatch] = React.useReducer(
    todoReducer,
    initialTodos
  );

  const handleChange = () => {};

  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>
          ...
        </li>
      ))}
    </ul>
  );
};

export default App;

现在我们可以使用处理程序来为我们的还原器函数分配一个动作。由于我们需要id 作为Todo项目的标识符,以便切换它的complete 标志,我们可以通过使用一个封装的箭头函数在处理程序函数中传递该项目。

const App = () => {
  const [todos, dispatch] = React.useReducer(
    todoReducer,
    initialTodos
  );

  const handleChange = todo => {
    dispatch({ type: 'DO_TODO', id: todo.id });
  };

  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>
          <label>
            <input
              type="checkbox"
              checked={todo.complete}
              onChange={() => handleChange(todo)}
            />
            {todo.task}
          </label>
        </li>
      ))}
    </ul>
  );
};

不过这种实现方式只有一种效果。Todo项目可以被完成,但该操作不能通过使用我们的reducer的第二个状态转换而被逆转。让我们在我们的处理程序中通过检查一个Todo项目是否完成来实现这一行为。

const App = () => {
  const [todos, dispatch] = React.useReducer(
    todoReducer,
    initialTodos
  );

  const handleChange = todo => {
    dispatch({
      type: todo.complete ? 'UNDO_TODO' : 'DO_TODO',
      id: todo.id,
    });
  };

  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>
          <label>
            <input
              type="checkbox"
              checked={todo.complete}
              onChange={() => handleChange(todo)}
            />
            {todo.task}
          </label>
        </li>
      ))}
    </ul>
  );
};

根据我们的Todo项目的状态,为我们的reducer函数分派正确的动作。之后,React组件再次被渲染,但使用来自useReducer钩子的新状态。展示的useReducer例子可以在这个GitHub仓库里找到。


React的useReducer钩子是在React中管理状态的一种强大的方式。它可以与useState和useContext一起用于React的现代状态管理。而且,它经常被用来支持复杂状态和状态转换的useState。毕竟,useReducer钩子对那些还不需要Redux for React的中等规模的应用程序来说是很有吸引力的。