前端--react使用 Reducer 和 Context 拓展应用和组件
Reducer 可以整合组件的状态更新逻辑。Context 可以将信息深入传递给其他组件。你可以组合使用它们来共同管理一个复杂页面的状态。
结合使用 reducer 和 context
状态被 reducer 所管理。reducer 函数包含了所有的状态更新逻辑并在此文件的底部声明:
App.js

import { useReducer } from 'react'; import AddTask from './AddTask.js'; import TaskList from './TaskList.js'; export default function TaskApp() { const [tasks, dispatch] = useReducer( tasksReducer, initialTasks ); function handleAddTask(text) { dispatch({ type: 'added', id: nextId++, text: text, }); } function handleChangeTask(task) { dispatch({ type: 'changed', task: task }); } function handleDeleteTask(taskId) { dispatch({ type: 'deleted', id: taskId }); } return ( <> <h1>Day off in Kyoto</h1> <AddTask onAddTask={handleAddTask} /> <TaskList tasks={tasks} onChangeTask={handleChangeTask} onDeleteTask={handleDeleteTask} /> </> ); } 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); } } } let nextId = 3; 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.js

import { useState } from 'react'; export default function AddTask({ onAddTask }) { const [text, setText] = useState(''); return ( <> <input placeholder="Add task" value={text} onChange={e => setText(e.target.value)} /> <button onClick={() => { setText(''); onAddTask(text); }}>Add</button> </> ) }
TastList.js

import { useState } from 'react'; export default function TaskList({ tasks, onChangeTask, onDeleteTask }) { return ( <ul> {tasks.map(task => ( <li key={task.id}> <Task task={task} onChange={onChangeTask} onDelete={onDeleteTask} /> </li> ))} </ul> ); } function Task({ task, onChange, onDelete }) { const [isEditing, setIsEditing] = useState(false); let taskContent; if (isEditing) { taskContent = ( <> <input value={task.text} onChange={e => { onChange({ ...task, text: e.target.value }); }} /> <button onClick={() => setIsEditing(false)}> Save </button> </> ); } else { taskContent = ( <> {task.text} <button onClick={() => setIsEditing(true)}> Edit </button> </> ); } return ( <label> <input type="checkbox" checked={task.done} onChange={e => { onChange({ ...task, done: e.target.checked }); }} /> {taskContent} <button onClick={() => onDelete(task.id)}> Delete </button> </label> ); }
Reducer 有助于保持事件处理程序的简短明了。但随着应用规模越来越庞大,你就可能会遇到别的困难。目前,tasks
状态和 dispatch
函数仅在顶级 TaskApp
组件中可用。要让其他组件读取任务列表或更改它,你必须显式 传递 当前状态和事件处理程序,将其作为 props。
例如,TaskApp
将 一系列 task 和事件处理程序传递给 TaskList
:
TaskList
将事件处理程序传递给 Task
:
在像这样的小示例里这样做没什么问题,但是如果你有成百上千个中间组件,传递所有状态和函数可能会非常麻烦!
这就是为什么,比起通过 props 传递它们,你可能想把 tasks
状态和 dispatch
函数都 放入context中。这样,所有的在 TaskApp
组件树之下的组件都不必一直往下传 props 而可以直接读取 tasks 和 dispatch 函数。
下面将介绍如何结合使用 reducer 和 context:
- 创建 context。
- 将 state 和 dispatch 放入 context。
- 在组件树的任何地方 使用 context。
Step 1: 创建 context
useReducer
返回当前的 tasks
和 dispatch
函数来让你更新它们:
为了将它们从组件树往下传,你将 创建 两个不同的 context:
TasksContext
提供当前的 tasks 列表。TasksDispatchContext
提供了一个函数可以让组件分发动作。
将它们从单独的文件导出,以便以后可以从其他文件导入它们:

import { useReducer } from 'react'; import AddTask from './AddTask.js'; import TaskList from './TaskList.js'; export default function TaskApp() { const [tasks, dispatch] = useReducer( tasksReducer, initialTasks ); function handleAddTask(text) { dispatch({ type: 'added', id: nextId++, text: text, }); } function handleChangeTask(task) { dispatch({ type: 'changed', task: task }); } function handleDeleteTask(taskId) { dispatch({ type: 'deleted', id: taskId }); } return ( <> <h1>Day off in Kyoto</h1> <AddTask onAddTask={handleAddTask} /> <TaskList tasks={tasks} onChangeTask={handleChangeTask} onDeleteTask={handleDeleteTask} /> </> ); } 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); } } } let nextId = 3; 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 } ];

import { createContext } from 'react'; export const TasksContext = createContext(null); export const TasksDispatchContext = createContext(null);

import { useState } from 'react'; export default function AddTask({ onAddTask }) { const [text, setText] = useState(''); return ( <> <input placeholder="Add task" value={text} onChange={e => setText(e.target.value)} /> <button onClick={() => { setText(''); onAddTask(text); }}>Add</button> </> ) }

import { useState } from 'react'; export default function TaskList({ tasks, onChangeTask, onDeleteTask }) { return ( <ul> {tasks.map(task => ( <li key={task.id}> <Task task={task} onChange={onChangeTask} onDelete={onDeleteTask} /> </li> ))} </ul> ); } function Task({ task, onChange, onDelete }) { const [isEditing, setIsEditing] = useState(false); let taskContent; if (isEditing) { taskContent = ( <> <input value={task.text} onChange={e => { onChange({ ...task, text: e.target.value }); }} /> <button onClick={() => setIsEditing(false)}> Save </button> </> ); } else { taskContent = ( <> {task.text} <button onClick={() => setIsEditing(true)}> Edit </button> </> ); } return ( <label> <input type="checkbox" checked={task.done} onChange={e => { onChange({ ...task, done: e.target.checked }); }} /> {taskContent} <button onClick={() => onDelete(task.id)}> Delete </button> </label> ); }
在这里,你把 null
作为默认值传递给两个 context。实际值是由 TaskApp
组件提供的。
Step 2: 将 state 和 dispatch 函数放入 context
现在,你可以将所有的 context 导入 TaskApp
组件。获取 useReducer()
返回的 tasks
和 dispatch
并将它们提供给整个组件树:
现在,你可以同时通过 props 和 context 传递信息:

import { useReducer } from 'react'; import AddTask from './AddTask.js'; import TaskList from './TaskList.js'; import { TasksContext, TasksDispatchContext } from './TasksContext.js'; export default function TaskApp() { const [tasks, dispatch] = useReducer( tasksReducer, initialTasks ); function handleAddTask(text) { dispatch({ type: 'added', id: nextId++, text: text, }); } function handleChangeTask(task) { dispatch({ type: 'changed', task: task }); } function handleDeleteTask(taskId) { dispatch({ type: 'deleted', id: taskId }); } return ( <TasksContext value={tasks}> <TasksDispatchContext value={dispatch}> <h1>Day off in Kyoto</h1> <AddTask onAddTask={handleAddTask} /> <TaskList tasks={tasks} onChangeTask={handleChangeTask} onDeleteTask={handleDeleteTask} /> </TasksDispatchContext> </TasksContext> ); } 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); } } } let nextId = 3; 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 } ];

import { createContext } from 'react'; export const TasksContext = createContext(null); export const TasksDispatchContext = createContext(null);

import { useState } from 'react'; export default function AddTask({ onAddTask }) { const [text, setText] = useState(''); return ( <> <input placeholder="Add task" value={text} onChange={e => setText(e.target.value)} /> <button onClick={() => { setText(''); onAddTask(text); }}>Add</button> </> ) }

import { useState } from 'react'; export default function TaskList({ tasks, onChangeTask, onDeleteTask }) { return ( <ul> {tasks.map(task => ( <li key={task.id}> <Task task={task} onChange={onChangeTask} onDelete={onDeleteTask} /> </li> ))} </ul> ); } function Task({ task, onChange, onDelete }) { const [isEditing, setIsEditing] = useState(false); let taskContent; if (isEditing) { taskContent = ( <> <input value={task.text} onChange={e => { onChange({ ...task, text: e.target.value }); }} /> <button onClick={() => setIsEditing(false)}> Save </button> </> ); } else { taskContent = ( <> {task.text} <button onClick={() => setIsEditing(true)}> Edit </button> </> ); } return ( <label> <input type="checkbox" checked={task.done} onChange={e => { onChange({ ...task, done: e.target.checked }); }} /> {taskContent} <button onClick={() => onDelete(task.id)}> Delete </button> </label> ); }
在下一步中,你将删除通过 props 传递的代码。
Step 3: 在组件树中的任何地方使用 context
现在你不需要将 tasks 和事件处理程序在组件树中传递:
相反,任何需要 tasks 的组件都可以从 TasksContext
中读取它:
任何组件都可以从 context 中读取 dispatch
函数并调用它,从而更新任务列表:
TaskApp
组件不会向下传递任何事件处理程序,TaskList
也不会。每个组件都会读取它需要的 context:

import { useReducer } from 'react'; import AddTask from './AddTask.js'; import TaskList from './TaskList.js'; import { TasksContext, TasksDispatchContext } from './TasksContext.js'; export default function TaskApp() { const [tasks, dispatch] = useReducer( tasksReducer, initialTasks ); return ( <TasksContext value={tasks}> <TasksDispatchContext value={dispatch}> <h1>Day off in Kyoto</h1> <AddTask /> <TaskList /> </TasksDispatchContext> </TasksContext> ); } 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 } ];

import { createContext } from 'react'; export const TasksContext = createContext(null); export const TasksDispatchContext = createContext(null);

import { useState, useContext } from 'react'; import { TasksDispatchContext } from './TasksContext.js'; export default function AddTask() { const [text, setText] = useState(''); const dispatch = useContext(TasksDispatchContext); 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;

import { useState, useContext } from 'react'; import { TasksContext, TasksDispatchContext } from './TasksContext.js'; export default function TaskList() { const tasks = useContext(TasksContext); return ( <ul> {tasks.map(task => ( <li key={task.id}> <Task task={task} /> </li> ))} </ul> ); } function Task({ task }) { const [isEditing, setIsEditing] = useState(false); const dispatch = useContext(TasksDispatchContext); 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 </button> </> ); } else { taskContent = ( <> {task.text} <button onClick={() => setIsEditing(true)}> Edit </button> </> ); } return ( <label> <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 </button> </label> ); }
state 仍然 “存在于” 顶层 Task
组件中,由 useReducer
进行管理。不过,组件树里的组件只要导入这些 context 之后就可以获取 tasks
和 dispatch
。
将相关逻辑迁移到一个文件当中
这不是必须的,但你可以通过将 reducer 和 context 移动到单个文件中来进一步整理组件。目前,“TasksContext.js” 仅包含两个 context 声明:
来给这个文件添加更多代码!将 reducer 移动到此文件中,然后声明一个新的 TasksProvider
组件。此组件将所有部分连接在一起:
- 它将管理 reducer 的状态。
- 它将提供现有的 context 给组件树。
- 它将 把 children 作为 prop,所以你可以传递 JSX。
这将使 TaskApp
组件更加直观:

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> ); }

import { createContext, useReducer } from 'react'; export const TasksContext = createContext(null); export const TasksDispatchContext = createContext(null); export function TasksProvider({ children }) { const [tasks, dispatch] = useReducer( tasksReducer, initialTasks ); return ( <TasksContext value={tasks}> <TasksDispatchContext value={dispatch}> {children} </TasksDispatchContext> </TasksContext> ); } 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 } ];

import { useState, useContext } from 'react'; import { TasksDispatchContext } from './TasksContext.js'; export default function AddTask() { const [text, setText] = useState(''); const dispatch = useContext(TasksDispatchContext); 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;

import { useState, useContext } from 'react'; import { TasksContext, TasksDispatchContext } from './TasksContext.js'; export default function TaskList() { const tasks = useContext(TasksContext); return ( <ul> {tasks.map(task => ( <li key={task.id}> <Task task={task} /> </li> ))} </ul> ); } function Task({ task }) { const [isEditing, setIsEditing] = useState(false); const dispatch = useContext(TasksDispatchContext); 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 </button> </> ); } else { taskContent = ( <> {task.text} <button onClick={() => setIsEditing(true)}> Edit </button> </> ); } return ( <label> <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 </button> </label> ); }
TasksContext.js
中导出使用 context 的函数:组件可以通过以下函数读取 context:
这不会改变任何行为,但它会允许你之后进一步分割这些 context 或向这些函数添加一些逻辑。
现在所有的 context 和 reducer 连接部分都在 TasksContext.js
中。这保持了组件的干净和整洁,让我们专注于它们显示的内容,而不是它们从哪里获得数据:

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> ); }

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 value={tasks}> <TasksDispatchContext value={dispatch}> {children} </TasksDispatchContext> </TasksContext> ); } 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 } ];

import { useState } from 'react'; import { useTasksDispatch } from './TasksContext.js'; 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;

import { useState } from 'react'; import { useTasks, useTasksDispatch } from './TasksContext.js'; export default function TaskList() { const tasks = useTasks(); return ( <ul> {tasks.map(task => ( <li key={task.id}> <Task task={task} /> </li> ))} </ul> ); } 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 </button> </> ); } else { taskContent = ( <> {task.text} <button onClick={() => setIsEditing(true)}> Edit </button> </> ); } return ( <label> <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 </button> </label> ); }
你可以将 TasksProvider
视为页面的一部分,它知道如何处理 tasks。useTasks
用来读取它们,useTasksDispatch
用来从组件树下的任何组件更新它们。
工作1-3年,基础知识,多看书。多跟着别人做项目,学习经验。 工作3-5年,新知识,高级知识,自己独立做项目,总结经验。尝试不同的语言。 工作5-8年,工作职位,要从设计,管理方面要求自己,可以尝试走管理路线(项目经理或cto)。 工作10年及以上, 自己做些项目,产品,尝试为创业做准备。 上大学和不上大学区别很大,上品牌大学和普通大学区别也很大,后天的努力最大。 ---无论它是在遥远的远方,还是在出发的地方、哪里有希望哪里就是我们的方向;终点、只不过是梦想起飞的地方。