Масштабирование с помощью Reducer и Context
Редьюсеры позволяют объединить логику обновления состояния компонента. Контекст позволяет передавать информацию глубоко вниз другим компонентам. Вы можете объединить редьюсеры и контекст вместе для управления состоянием сложного экрана.
Вы узнаете
- Как объединить редьюсер с контекстом
- Как избежать передачи состояния и dispatch через пропсы
- Как хранить логику контекста и состояния в отдельном файле
Объединение редьюсера с контекстом
В этом примере извведения в редьюсеры, состоянием управляет редьюсер. Функция редьюсера содержит всю логику обновления состояния и объявлена внизу этого файла:
Редьюсер помогает сохранять обработчики событий короткими и лаконичными. Однако по мере роста вашего приложения вы можете столкнуться с другой трудностью.В настоящее время состояниеtasksи функцияdispatchдоступны только в компоненте верхнего уровняTaskApp.Чтобы позволить другим компонентам читать список задач или изменять его, вам нужно явнопроброситьтекущее состояние и обработчики событий, которые его изменяют, через пропсы.
Например,TaskAppпередает список задач и обработчики событий вTaskList:
И TaskListпередает обработчики событий вTask:
В небольшом примере, подобном этому, это работает хорошо, но если у вас есть десятки или сотни компонентов посередине, пробрасывание всего состояния и функций может быть довольно утомительным!
Вот почему, как альтернативу их передаче через пропсы, вы можете поместить как состояниеtasks, так и функциюdispatch в контекст.Таким образом, любой компонент нижеTaskAppв дереве может читать задачи и отправлять действия без утомительного «пробрасывания пропсов».
Вот как можно объединить редьюсер с контекстом:
- Создайтеконтекст.
- Поместитесостояние и dispatch в контекст.
- Используйтеконтекст в любом месте дерева.
Шаг 1: Создание контекста
ХукuseReducerвозвращает текущий списокtasksи функциюdispatch, которая позволяет их обновлять:
Чтобы передать их вниз по дереву, вам нужносоздатьдва отдельных контекста:
TasksContextпредоставляет текущий список задач.TasksDispatchContextпредоставляет функцию, которая позволяет компонентам отправлять действия.
Экспортируйте их из отдельного файла, чтобы позже можно было импортировать их из других файлов:
Здесь вы передаётеnullв качестве значения по умолчанию для обоих контекстов. Фактические значения будут предоставлены компонентомTaskApp.
Шаг 2: Поместите состояние и dispatch в контекст
Теперь вы можете импортировать оба контекста в вашем компонентеTaskApp. Возьмитеtasks и dispatch, возвращаемыеuseReducer(), ипредоставьте ихвсему дереву ниже:
На данный момент вы передаёте информацию как через пропсы, так и через контекст:
На следующем шаге вы уберёте передачу пропсов.
Шаг 3: Используйте контекст в любом месте дерева
Теперь вам не нужно передавать список задач или обработчики событий вниз по дереву:
Вместо этого любой компонент, которому нужен список задач, может прочитать его изTasksContext:
Чтобы обновить список задач, любой компонент может прочитать функциюdispatchиз контекста и вызвать её:
КомпонентTaskAppне передаёт никакие обработчики событий вниз, и компонентTaskListтакже не передаёт никакие обработчики событий компонентуTask.Каждый компонент читает нужный ему контекст:
Состояние по-прежнему «живёт» в компоненте верхнего уровняTaskAppи управляется с помощьюuseReducer.Но егоtasks и dispatchтеперь доступны каждому компоненту ниже в дереве через импорт и использование этих контекстов.
Перенос всей связующей логики в один файл
Вы не обязаны этого делать, но можно ещё больше упростить компоненты, переместив и редюсер, и контекст в один файл. В данный момент файлTasksContext.jsсодержит только два объявления контекстов:
Этот файл скоро станет перегруженным! Вы переместите редюсер в тот же файл. Затем объявите новый компонентTasksProviderв том же файле. Этот компонент свяжет все части вместе:
- Он будет управлять состоянием с помощью редюсера.
- Он будет предоставлять оба контекста компонентам ниже.
- Он будетпринимать children как проп, чтобы вы могли передавать ему JSX.
Это удаляет всю сложность и связующую логику из вашего компонентаTaskApp:
Вы также можете экспортировать функции, которыеиспользуютконтекст из файлаTasksContext.js:
Когда компоненту нужно прочитать контекст, он может сделать это через эти функции:
Это никак не меняет поведение, но позволяет в дальнейшем разделить эти контексты или добавить логику в эти функции.Теперь вся настройка контекста и редюсера находится в файлеTasksContext.js. Это позволяет компонентам оставаться чистыми и незагромождёнными, сосредоточенными на том, что они отображают, а не на том, откуда они получают данные:
Можно представить, чтоTasksProvider— это часть экрана, которая знает, как работать с задачами,useTasks— способ их чтения, аuseTasksDispatch— способ их обновления из любого компонента ниже в дереве.
Примечание
Функции, такие какuseTasks и useTasksDispatch, называютсяПользовательскими хуками.Ваша функция считается пользовательским хуком, если её имя начинается сuse. Это позволяет использовать внутри неё другие хуки, например,useContext.
По мере роста вашего приложения у вас может появиться много таких пар контекст-редьюсер. Это мощный способ масштабировать приложение иподнимать состояниебез лишних усилий, когда вам нужно получить доступ к данным глубоко в дереве.
Итоги
- Вы можете объединить редьюсер с контекстом, чтобы любой компонент мог читать и обновлять состояние выше него.
- Чтобы предоставить состояние и функцию dispatch компонентам ниже:
- Создайте два контекста (для состояния и для функций dispatch).
- Предоставьте оба контекста из компонента, который использует редьюсер.
- Используйте любой из контекстов в компонентах, которым нужно их читать.
- Вы можете дополнительно упростить компоненты, переместив всю связующую логику в один файл.
- Вы можете экспортировать компонент, например
TasksProvider, который предоставляет контекст. - Вы также можете экспортировать пользовательские хуки, такие как
useTasksиuseTasksDispatch, для чтения этого контекста.
- Вы можете экспортировать компонент, например
- В вашем приложении может быть много таких пар контекст-редьюсер.
