React系列七:Reducer 和 Context

React系列108037 阅读0

Reducer 可以整合组件的状态更新逻辑。Context 可以将信息深入传递给其他组件。你可以组合使用它们来共同管理一个复杂页面的状态。

第一步:创建context

const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);

这里的tasksReducer是我们用于处理tasks的方法,如新增,删除,修改等。

initialTasks是初始化tasks的默认值。

tasks是任务列表、dispatch用于调用tasksReducer来操作tasks。

接下来需要将tasks和dispatch往组件下传,让组件树下方的组件都能够调用来操作任务列表,故而需要创建两个context:

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

第二步: 将 state 和 dispatch 函数放入 context

import { TasksContext, TasksDispatchContext } from './TasksContext.js';
​
export default function TaskApp() {
const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
// ...
return (
<TasksContext value={tasks}>
<TasksDispatchContext value={dispatch}>
...
</TasksDispatchContext>
</TasksContext>
);
}

第三步: 在组件树中的任何地方使用 context

<TasksContext value={tasks}>
<TasksDispatchContext value={dispatch}>
<h1>Day off in Kyoto</h1>
<AddTask />
<TaskList />
</TasksDispatchContext>
</TasksContext>

现在其中的组件都能够使用dispatch来操作tasks,下面是TaskList使用tasks和Task组件使用dispatch的方法的示例

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 仍然 “存在于” 顶层 TaskApp组件中,由 useReducer 进行管理。不过,组件树里的组件只要导入这些 context 之后就可以获取 tasks 和 dispatch。

代码优化

<TasksContext value={tasks}>
<TasksDispatchContext value={dispatch}>
<h1>Day off in Kyoto</h1>
<AddTask />
<TaskList />
</TasksDispatchContext>
</TasksContext>

这样的两成嵌套我们会觉得有点繁琐,我们将 reducer 和 context 移动到单个文件中来进一步整理组件。

新建TasksProvider.js文件

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 }
];
​

我们在这个文件中集合了reducer和context的逻辑,再次使用时,我们只需要像这样:

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

这样的代码更简洁直观了。

我们在使用的地方也可以优化:

const tasks = useContext(TasksContext);
const dispatch = useContext(TasksDispatchContext);

我们只需要在TasksProvider文件中将

export const TasksContext = createContext(null);
export const TasksDispatchContext = createContext(null)

改成:

export function useTasks() {
return useContext(TasksContext);
}
​
export function useTasksDispatch() {
return useContext(TasksDispatchContext);
}

我们就能够这样使用了:

const tasks = useTasks();
const dispatch = useTasksDispatch();

好处是更简洁,逻辑更清晰,在组件中使用也不用手动导入TasksContext和TasksDispatchContext了。

现在所有的 context 和 reducer 连接部分都在 TasksProvider.js 中。这保持了组件的干净和整洁,让我们专注于它们显示的内容,而不是它们从哪里获得数据。

你可以将 TasksProvider 视为页面的一部分,它知道如何处理 tasks。useTasks 用来读取它们,useTasksDispatch 用来从组件树下的任何组件更新它们。

像 useTasks 和 useTasksDispatch 这样的函数被称为 自定义 Hook。 如果你的函数名以 use 开头,它就被认为是一个自定义 Hook。这让你可以使用其他 Hook,比如 useContext。

评论

发表评论