| title | Масштабування з використанням контексту та редюсера |
|---|
Редюсери дають змогу об'єднати логіку оновлення стану компонента. Контекст дає змогу передавати інформацію у глибину до інших компонентів. Можна поєднати редюсери і контекст, щоб управляти станом складного інтерфейсу.
- Як поєднати редюсер із контекстом
- Як уникнути передавання стану та відправляння подій через пропси
- Як зберігати логіку контексту і стану в окремому файлі
Як ми вже бачили у прикладі з цього розділу, стан керується редюсером. Функція редюсера містить всю логіку оновлення стану і оголошена в кінці файлу:
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>Вихідний день у Кіото</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: 'Шлях філософа', done: true },
{ id: 1, text: 'Відвідати храм', done: false },
{ id: 2, text: 'Випити матчу', done: false }
];import { useState } from 'react';
export default function AddTask({ onAddTask }) {
const [text, setText] = useState('');
return (
<>
<input
placeholder="Додати завдання"
value={text}
onChange={e => setText(e.target.value)}
/>
<button onClick={() => {
setText('');
onAddTask(text);
}}>Додати</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)}>
Зберегти
</button>
</>
);
} else {
taskContent = (
<>
{task.text}
<button onClick={() => setIsEditing(true)}>
Редагувати
</button>
</>
);
}
return (
<label>
<input
type="checkbox"
checked={task.done}
onChange={e => {
onChange({
...task,
done: e.target.checked
});
}}
/>
{taskContent}
<button onClick={() => onDelete(task.id)}>
Видалити
</button>
</label>
);
}button { margin: 5px; }
li { list-style-type: none; }
ul, li { margin: 0; padding: 0; }Редюсер допомагає тримати обробники подій короткими і зрозумілими. Проте може виникнути проблема, коли ваш застосунок зросте. Наразі стан tasks та функція dispatch доступні тільки в компоненті TaskApp на верхньому рівні. Щоб інші компоненти могли отримувати та оновлювати перелік завдань, потрібно явно передавати поточний стан і обробники подій, які змінюють його, як пропси.
Наприклад, TaskApp передає перелік завдань і обробники подій компоненту TaskList:
<TaskList
tasks={tasks}
onChangeTask={handleChangeTask}
onDeleteTask={handleDeleteTask}
/>А TaskList передає обробники компоненту Task:
<Task
task={task}
onChange={onChangeTask}
onDelete={onDeleteTask}
/>У невеликому прикладі це працює добре, але якщо у вас є десятки або сотні вкладених компонентів, передавати так увесь стан і функції може бути досить неприємно!
Тому, як альтернатива передаванню через пропси, ви можете помістити і стан tasks, і функцію dispatch в контекст. У такий спосіб, будь-який компонент, що знаходиться нижче TaskApp у дереві, може мати доступ та надсилати події, уникнувши передавання пропсів через безліч компонентів ("prop drilling").
Ось як можна поєднати редюсер із контекстом:
- Створіть контекст.
- Помістіть стан і функцію dispatch у контекст.
- Використовуйте контекст будь-де в дереві компонентів.
Хук useReducer повертає поточний стан tasks та функцію dispatch, яка дає змогу оновлювати цей стан:
const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);Щоб передати їх далі деревом, створіть два окремих контексти:
TasksContextнадає поточний перелік завдань.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>Вихідний день у Кіото</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: 'Шлях філософа', done: true },
{ id: 1, text: 'Відвідати Храм', done: false },
{ id: 2, text: 'Випити матчу', 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="Додати завдання"
value={text}
onChange={e => setText(e.target.value)}
/>
<button onClick={() => {
setText('');
onAddTask(text);
}}>Додати</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)}>
Зберегти
</button>
</>
);
} else {
taskContent = (
<>
{task.text}
<button onClick={() => setIsEditing(true)}>
Редагувати
</button>
</>
);
}
return (
<label>
<input
type="checkbox"
checked={task.done}
onChange={e => {
onChange({
...task,
done: e.target.checked
});
}}
/>
{taskContent}
<button onClick={() => onDelete(task.id)}>
Видалити
</button>
</label>
);
}button { margin: 5px; }
li { list-style-type: none; }
ul, li { margin: 0; padding: 0; }Тут ви передаєте null як початкове значення для обох контекстів. Дійсні значення будуть надані компонентом TaskApp.
Тепер ви можете імпортувати обидва контексти у ваш компонент TaskApp. Візьміть tasks та dispatch, повернуті хуком useReducer(), і надайте їх всьому вкладеному дереву компонентів:
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>
);
}Наразі ви передаєте інформацію як через пропси, так і через контекст:
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>Вихідний день у Кіото</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: 'Шлях філософа', done: true },
{ id: 1, text: 'Відвідати Храм', done: false },
{ id: 2, text: 'Випити матчу', 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="Додати завдання"
value={text}
onChange={e => setText(e.target.value)}
/>
<button onClick={() => {
setText('');
onAddTask(text);
}}>Додати</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)}>
Зберегти
</button>
</>
);
} else {
taskContent = (
<>
{task.text}
<button onClick={() => setIsEditing(true)}>
Редагувати
</button>
</>
);
}
return (
<label>
<input
type="checkbox"
checked={task.done}
onChange={e => {
onChange({
...task,
done: e.target.checked
});
}}
/>
{taskContent}
<button onClick={() => onDelete(task.id)}>
Видалити
</button>
</label>
);
}button { margin: 5px; }
li { list-style-type: none; }
ul, li { margin: 0; padding: 0; }Далі видалимо передавання пропсів.
Крок 3: Використовуйте контекст будь-де у дереві компонентів {/step-3-use-context-anywhere-in-the-tree/}
Тепер вам не потрібно передавати перелік завдань або обробники подій скрізь усе дерево компонентів:
<TasksContext value={tasks}>
<TasksDispatchContext value={dispatch}>
<h1>Вихідний день у Кіото</h1>
<AddTask />
<TaskList />
</TasksDispatchContext>
</TasksContext>Натомість будь-який компонент, якому потрібен перелік завдань, може мати його з TasksContext:
export default function TaskList() {
const tasks = useContext(TasksContext);
// ...Щоб оновити перелік завдань, будь-який компонент може взяти функцію dispatch із контексту та викликати її:
export default function AddTask() {
const [text, setText] = useState('');
const dispatch = useContext(TasksDispatchContext);
// ...
return (
// ...
<button onClick={() => {
setText('');
dispatch({
type: 'added',
id: nextId++,
text: text,
});
}}>Додати</button>
// ...Компонент TaskApp не передає жодних обробників подій, і TaskList також не передає нічого компоненту Task. Кожен компонент вибирає лише потрібний йому контекст:
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>Вихідний день у Кіото</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: 'Шлях філософа', done: true },
{ id: 1, text: 'Відвідати Храм', done: false },
{ id: 2, text: 'Випити матчу', 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="Додати завдання"
value={text}
onChange={e => setText(e.target.value)}
/>
<button onClick={() => {
setText('');
dispatch({
type: 'added',
id: nextId++,
text: text,
});
}}>Додати</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)}>
Зберегти
</button>
</>
);
} else {
taskContent = (
<>
{task.text}
<button onClick={() => setIsEditing(true)}>
Редагувати
</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
});
}}>
Видалити
</button>
</label>
);
}button { margin: 5px; }
li { list-style-type: none; }
ul, li { margin: 0; padding: 0; }Стан все ще "живе" у компоненті TaskApp на верхньому рівні та керується за допомогою useReducer. Але його tasks і dispatch тепер доступні кожному компоненту нижче в дереві завдяки імпорту та використанню контекстів.
Це не обов'язково, але ви можете ще більше спростити компоненти, перемістивши і редюсер, і контекст в один файл. Наразі TasksContext.js містить лише два оголошення контексту:
import { createContext } from 'react';
export const TasksContext = createContext(null);
export const TasksDispatchContext = createContext(null);Цей файл скоро стане більш наповненим! Додамо в нього редюсер. Потім оголосимо новий компонент TasksProvider. Цей компонент зв'яже всі частини разом. Він:
- Керуватиме станом за допомогою редюсера.
- Надаватиме обидва контексти ієрархічно нижчим компонентам.
- Матиме проп
children, щоб було можливим передавати в нього JSX.
export function TasksProvider({ children }) {
const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
return (
<TasksContext value={tasks}>
<TasksDispatchContext value={dispatch}>
{children}
</TasksDispatchContext>
</TasksContext>
);
}Це усуває всю складну логіку з компонента TaskApp:
import AddTask from './AddTask.js';
import TaskList from './TaskList.js';
import { TasksProvider } from './TasksContext.js';
export default function TaskApp() {
return (
<TasksProvider>
<h1>Вихідний день у Кіото</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: 'Шлях філософа', done: true },
{ id: 1, text: 'Відвідати Храм', done: false },
{ id: 2, text: 'Випити матчу', 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="Додати завдання"
value={text}
onChange={e => setText(e.target.value)}
/>
<button onClick={() => {
setText('');
dispatch({
type: 'added',
id: nextId++,
text: text,
});
}}>Додати</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)}>
Зберегти
</button>
</>
);
} else {
taskContent = (
<>
{task.text}
<button onClick={() => setIsEditing(true)}>
Редагувати
</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
});
}}>
Видалити
</button>
</label>
);
}button { margin: 5px; }
li { list-style-type: none; }
ul, li { margin: 0; padding: 0; }Ви також можете експортувати функції, які використовують контекст, із файлу TasksContext.js:
export function useTasks() {
return useContext(TasksContext);
}
export function useTasksDispatch() {
return useContext(TasksDispatchContext);
}За допомогою цих функцій компоненти можуть взаємодіяти з контекстом.
const tasks = useTasks();
const dispatch = useTasksDispatch();Це не змінює функціональність, але дає змогу згодом розділити контексти або додати логіку. Тепер усі налаштування контексту та редюсера знаходяться у файлі TasksContext.js. Це допомагає зберегти компоненти чистими і неперевантаженими, зосередженими на відображенні даних, а не на їхньому отриманні.
import AddTask from './AddTask.js';
import TaskList from './TaskList.js';
import { TasksProvider } from './TasksContext.js';
export default function TaskApp() {
return (
<TasksProvider>
<h1>Вихідний день у Кіото</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: 'Шлях філософа', done: true },
{ id: 1, text: 'Відвідати Храм', done: false },
{ id: 2, text: 'Випити матчу', 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="Додати завдання"
value={text}
onChange={e => setText(e.target.value)}
/>
<button onClick={() => {
setText('');
dispatch({
type: 'added',
id: nextId++,
text: text,
});
}}>Додати</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)}>
Зберегти
</button>
</>
);
} else {
taskContent = (
<>
{task.text}
<button onClick={() => setIsEditing(true)}>
Редагувати
</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
});
}}>
Видалити
</button>
</label>
);
}button { margin: 5px; }
li { list-style-type: none; }
ul, li { margin: 0; padding: 0; }Ви можете вважати TasksProvider частиною інтерфейсу, яка відповідає за роботу з завданнями, useTasks — способом доступу до завдань, а useTasksDispatch — способом їхнього оновлення з будь-якого компонента, що знаходиться нижче в дереві компонентів.
Функції на кшталт useTasks та useTasksDispatch називаються хуками користувача (Custom Hooks). Ваша функція вважається хуком користувача, якщо її назва починається з use. Це дає змогу використовувати інші хуки, як-от useContext, всередині неї.
Коли застосунок зростає, у вас може з'явитися багато пар "контекст-редюсер". Це потужний спосіб масштабувати застосунок і ділитися станом без зайвих зусиль щоразу, коли потрібно отримати дані глибоко в дереві компонентів.
- Ви можете об'єднати редюсер із контекстом, щоб будь-який компонент міг отримати та оновити стан, що вище деревом.
- Щоб надати стан і функцію
dispatchкомпонентам нижче:- Створіть два контексти (для стану і для функцій
dispatch). - Надайте обидва контексти компоненту, який використовує редюсер.
- Використовуйте будь-який із цих контекстів у компонентах, яким вони потрібні.
- Створіть два контексти (для стану і для функцій
- Щоб ще більше спростити компоненти, винесіть відповідну логіку в один файл.
- Експортуйте компонент, наприклад
TasksProvider, який надає контекст. - Ви також можете експортувати хуки користувача, як-от
useTasksтаuseTasksDispatch, для доступу до контексту.
- Експортуйте компонент, наприклад
- У застосунку може бути багато схожих на цю пар "контекст-редюсер".