使用 Reducer 和 Context 来拓展你的应用

420 阅读2分钟

reducer可以state管理,并管理其更新逻辑. context则用于组件深层传递数据, 两者结合, 有助于组件的干净整洁, 数据的专注.

总体步骤是

  1. 创建context
  2. 将reducer人的state和dispatch放入context
  3. 在组件树的任何地方使用context, 通过context获取到state和dispatch, 并完成逻辑更新

示例(codesandbox地址):

App.jsx, TasksProvider内集合了reducer和context的逻辑, 使得App.jsx的组件内容更加简洁

import AddTask from './AddTask.js';
import TaskList from './TaskList.js';
import { TasksProvider } from './TasksContext.js';

export default function TaskApp() {
  return (
    <TasksProvider>
      <h1>Day off in Kyoto</h1>
      <AddTask />
      <TaskList />
    </TasksProvider>
  );
}

TasksContext.jsx

  1. 使用createContext创建了TasksContext, TasksDispatchContext, 用来分别存储tasks和dispatch
  2. 使用useReducer分别创建了tasks, dispatch, 这个是reducer的功能
  3. 定义了useTasks, useTasksDispatch两个函数, 分别使用useContext使用了TasksContext, TasksDispatchContext
  4. TasksProvider组件内, 在每个xxx.Provider内传入value, 此处分别传入了reducer产生的tasks,dispatch, 使得TasksProvider下的任意一级子组件可以获取tasks和dispatch
import { createContext, useContext, useReducer } from 'react';

const TasksContext = createContext(null);

const TasksDispatchContext = createContext(null);

export function TasksProvider({ children }) {
  const [tasks, dispatch] = useReducer(
    tasksReducer,
    initialTasks
  );

  return (
    <TasksContext.Provider value={tasks}>
      <TasksDispatchContext.Provider value={dispatch}>
        {children}
      </TasksDispatchContext.Provider>
    </TasksContext.Provider>
  );
}

export function useTasks() {
  return useContext(TasksContext);
}

export function useTasksDispatch() {
  return useContext(TasksDispatchContext);
}

function tasksReducer(tasks, action) {
  switch (action.type) {
    case 'added': {
      return [...tasks, {
        id: action.id,
        text: action.text,
        done: false
      }];
    }
    case 'changed': {
      return tasks.map(t => {
        if (t.id === action.task.id) {
          return action.task;
        } else {
          return t;
        }
      });
    }
    case 'deleted': {
      return tasks.filter(t => t.id !== action.id);
    }
    default: {
      throw Error('Unknown action: ' + action.type);
    }
  }
}

const initialTasks = [
  { id: 0, text: 'Philosopher’s Path', done: true },
  { id: 1, text: 'Visit the temple', done: false },
  { id: 2, text: 'Drink matcha', done: false }
];

AddTask.jsx 业务逻辑不重要, 重要的是按钮的点击事件中, dispatch是通过TasksContext.jsx中的useTasksDispatch获取的

import { useState } from 'react';
import { useTasksDispatch } from './TasksContext.jsx';

export default function AddTask() {
  const [text, setText] = useState('');
  const dispatch = useTasksDispatch();
  return (
    <>
      <input
        placeholder="Add task"
        value={text}
        onChange={e => setText(e.target.value)}
      />
      <button onClick={() => {
        setText('');
        dispatch({
          type: 'added',
          id: nextId++,
          text: text,
        }); 
      }}>Add</button>
    </>
  );
}

let nextId = 3;
``

TaskList.jsx
这里的tasks是从TasksContext.jsx中的useTasks获取的, 同理dispatch也是从useTasksDispatch获取的

import { useState } from 'react'; import { useTasks, useTasksDispatch } from './TasksContext.jsx';

export default function TaskList() { const tasks = useTasks(); return (

    {tasks.map(task => (
  • ))}
); }

function Task({ task }) { const [isEditing, setIsEditing] = useState(false); const dispatch = useTasksDispatch(); let taskContent; if (isEditing) { taskContent = ( <> <input value={task.text} onChange={e => { dispatch({ type: 'changed', task: { ...task, text: e.target.value } }); }} /> <button onClick={() => setIsEditing(false)}> Save </> ); } else { taskContent = ( <> {task.text} <button onClick={() => setIsEditing(true)}> Edit </> ); } return ( <input type="checkbox" checked={task.done} onChange={e => { dispatch({ type: 'changed', task: { ...task, done: e.target.checked } }); }} /> {taskContent} <button onClick={() => { dispatch({ type: 'deleted', id: task.id }); }}> Delete ); } ``