├── .gitignore ├── docs ├── introduction │ ├── README.md │ ├── Motivation.md │ ├── CoreConcepts.md │ ├── ThreePrinciples.md │ └── Examples.md ├── Feedback.md ├── advanced │ ├── README.md │ ├── AsyncFlow.md │ └── NextSteps.md ├── recipes │ ├── README.md │ ├── MigratingToRedux.md │ ├── IsolatingSubapps.md │ ├── StructuringReducers.md │ ├── UsingObjectSpreadOperator.md │ └── reducers │ │ ├── SplittingReducerLogic.md │ │ ├── BasicReducerStructure.md │ │ ├── PrerequisiteConcepts.md │ │ ├── ReusingReducerLogic.md │ │ ├── UsingCombineReducers.md │ │ ├── InitializingState.md │ │ ├── ImmutableUpdatePatterns.md │ │ └── BeyondCombineReducers.md ├── basics │ ├── README.md │ ├── Store.md │ ├── Actions.md │ └── DataFlow.md ├── api │ ├── README.md │ ├── compose.md │ ├── createStore.md │ ├── bindActionCreators.md │ └── combineReducers.md ├── usage │ ├── index.md │ ├── IsolatingSubapps.md │ ├── structuring-reducers │ │ ├── StructuringReducers.md │ │ ├── SplittingReducerLogic.md │ │ ├── BasicReducerStructure.md │ │ ├── PrerequisiteConcepts.md │ │ ├── UsingCombineReducers.md │ │ ├── BeyondCombineReducers.md │ │ ├── InitializingState.md │ │ └── ReusingReducerLogic.md │ ├── MigratingToRedux.md │ ├── UsingObjectSpreadOperator.md │ ├── CodeSplitting.md │ └── Troubleshooting.md ├── tutorials │ └── tutorials-index.md ├── faq │ ├── Miscellaneous.md │ ├── Reducers.md │ └── StoreSetup.md ├── README.md └── Troubleshooting.md ├── CHANGELOG.md ├── book.json ├── PATRONS.md ├── LICENSE.md └── SUMMARY.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .tmp 4 | /app/vendor/ 5 | .idea 6 | .DS_Store 7 | node_modules 8 | bower_components 9 | coverage 10 | -------------------------------------------------------------------------------- /docs/introduction/README.md: -------------------------------------------------------------------------------- 1 | ## Введение 2 | 3 | * [Мотивация](Motivation.md) 4 | * [Три принципа](ThreePrinciples.md) 5 | * [Предшественники](PriorArt.md) 6 | * [Экосистема](Ecosystem.md) 7 | * [Примеры](Examples.md) 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # История изменений 2 | 3 | Этот проект придерживается [Semantic Versioning](http://semver.org/). 4 | Каждый релиз, наряду с инструкциями по миграции, документирован на странице Github [Releases](https://github.com/reactjs/redux/releases). 5 | -------------------------------------------------------------------------------- /docs/Feedback.md: -------------------------------------------------------------------------------- 1 | # Обратная связь 2 | 3 | Мы высоко ценим обратную связь от нашего сообщества. Вы можете оставить пожелания и сообщения об ошибках на [Product Pains](https://productpains.com/product/redux). 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /book.json: -------------------------------------------------------------------------------- 1 | { 2 | "gitbook": ">=3.2.1", 3 | "title": "Redux", 4 | "plugins": ["edit-link", "prism", "-highlight", "github", "anchorjs"], 5 | "pluginsConfig": { 6 | "edit-link": { 7 | "base": "https://github.com/rajdee/redux-in-russian/tree/master", 8 | "label": "Edit This Page" 9 | }, 10 | "github": { 11 | "url": "https://github.com/rajdee/redux-in-russian/" 12 | }, 13 | "theme-default": { 14 | "styles": { 15 | "website": "build/gitbook.css" 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /docs/advanced/README.md: -------------------------------------------------------------------------------- 1 | # Продвинутый уровень (Advanced) 2 | 3 | В [базовом пошаговом руководстве](../basics/README.md) мы научились строить простое Redux приложение. В этом руководстве мы узнаем как дополнить картину AJAX и роутингом. 4 | 5 | * [Асинхронные экшены (Async Actions)](AsyncActions.md) 6 | * [Асинхронный поток (Async Flow)](AsyncFlow.md) 7 | * [Мидлвары (Middleware)](Middleware.md) 8 | * [Использование с React Router (Usage with React Router)](UsageWithReactRouter.md) 9 | * [Пример: Reddit API (Example: Reddit API)](ExampleRedditAPI.md) 10 | * [Следующие шаги (Next Steps)](NextSteps.md) 11 | -------------------------------------------------------------------------------- /docs/recipes/README.md: -------------------------------------------------------------------------------- 1 | # Рецепты 2 | 3 | Вот некоторые примеры использования и фрагменты кода, чтобы вы стартовали с Redux в реальном приложении. Они предполагают, что вы понимаете темы в [начальном](../basics/README.md) и [продвинутом](../advanced/README.md) руководствах. 4 | 5 | * [Переход на Redux](MigratingToRedux.md) 6 | * [Использование оператора расширения](UsingObjectSpreadOperator.md) 7 | * [Упрощение шаблона](ReducingBoilerplate.md) 8 | * [Серверный рендеринг](ServerRendering.md) 9 | * [Написание тестов](WritingTests.md) 10 | * [Вычисление полученных данных](ComputingDerivedData.md) 11 | * [Реализация истории отмен](ImplementingUndoHistory.md) 12 | * [Изолирование саб-приложений](IsolatingSubapps.md) 13 | * [Использования Immutable.JS с Redux](UsingImmutableJS.md) 14 | -------------------------------------------------------------------------------- /PATRONS.md: -------------------------------------------------------------------------------- 1 | # Спонсоры 2 | 3 | Работа над Redux была [финансирована фондом](https://www.patreon.com/reactdx). 4 | Познакомьтесь с некоторыми из выдающихся компаний и частных лиц, которые сделали это возможным: 5 | 6 | * [Webflow](https://github.com/webflow) 7 | * [Ximedes](https://www.ximedes.com/) 8 | * [HauteLook](http://hautelook.github.io/) 9 | * [Ken Wheeler](http://kenwheeler.github.io/) 10 | * [Chung Yen Li](https://www.facebook.com/prototocal.lee) 11 | * [Sunil Pai](https://twitter.com/threepointone) 12 | * [Charlie Cheever](https://twitter.com/ccheever) 13 | * [Eugene G](https://twitter.com/e1g) 14 | * [Matt Apperson](https://twitter.com/mattapperson) 15 | * [Jed Watson](https://twitter.com/jedwatson) 16 | * [Sasha Aickin](https://twitter.com/xander76) 17 | * [Stefan Tennigkeit](https://twitter.com/whobubble) 18 | * [Sam Vincent](https://twitter.com/samvincent) 19 | * Olegzandr Denman 20 | -------------------------------------------------------------------------------- /docs/basics/README.md: -------------------------------------------------------------------------------- 1 | # Основы 2 | 3 | Это руководство научит вас основам того, как работает Redux-приложение , шаг за шагом. 4 | В этом руководстве мы рассмотрим процесс создания простого приложения Todo. 5 | 6 | Хотя концепции, изложенные в руководствах «Базовый» и «Расширенный», по-прежнему актуальны, эти страницы являются одними из самых старых частей нашей документации. Вскоре мы обновим эти руководства, чтобы улучшить объяснения и покажем некоторые шаблоны, которые проще и легче использовать. Следите за этими обновлениями. Мы также реорганизуем нашу документацию, чтобы упростить поиск информации. 7 | 8 | **Мы рекомендуем начать с [руководства Redux Essentials](../tutorials/essentials/part-1-overview-concepts)**, поскольку оно охватывает ключевые моменты, которые вам нужно знать о том, как начать использовать Redux для написания реальных приложений. 9 | 10 | * [Экшены (Actions)](Actions.md) 11 | * [Редьюсеры (Reducers)](Reducers.md) 12 | * [Стор (Store)](Store.md) 13 | * [Поток данных (Data Flow)](DataFlow.md) 14 | * [Использвание с React](UsageWithReact.md) 15 | * [Пример: Todo List](ExampleTodoList.md) 16 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Dan Abramov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/api/README.md: -------------------------------------------------------------------------------- 1 | # Ссылка на API 2 | 3 | Redux имеет скромный API. Он определяет лишь набор соглашений по использованию, например, [редьюсеры](../Glossary.md#reducer) и предлагает несколько вспомогательных функций для того, чтобы объединить эти соглашения. 4 | 5 | В этом разделе полностью будет описан Redux API. Имейте в виду, что Redux ориентирован только на управление состоянием (state). В реальном приложении, вы будете использовать UI-биндинги, такие как [react-redux](https://github.com/gaearon/react-redux). 6 | 7 | ### Высокоуровневые экспорты 8 | 9 | * [createStore(reducer, [preloadedState], [enhancer])](createStore.md) 10 | * [combineReducers(reducers)](combineReducers.md) 11 | * [applyMiddleware(...middlewares)](applyMiddleware.md) 12 | * [bindActionCreators(actionCreators, dispatch)](bindActionCreators.md) 13 | * [compose(...functions)](compose.md) 14 | 15 | ### API стора 16 | 17 | * [Store](Store.md) 18 | * [getState()](Store.md#getState) 19 | * [dispatch(action)](Store.md#dispatch) 20 | * [subscribe(listener)](Store.md#subscribe) 21 | * [replaceReducer(nextReducer)](Store.md#replaceReducer) 22 | 23 | ### Импорты 24 | 25 | Каждая функция описана, как экспорт верхнего уровня. Вы можете импортировать любые из них, например: 26 | 27 | #### ES6 28 | 29 | ```js 30 | import { createStore } from 'redux' 31 | ``` 32 | 33 | #### ES5 (CommonJS) 34 | 35 | ```js 36 | var createStore = require('redux').createStore 37 | ``` 38 | 39 | #### ES5 (UMD build) 40 | 41 | ```js 42 | var createStore = Redux.createStore 43 | ``` 44 | -------------------------------------------------------------------------------- /docs/advanced/AsyncFlow.md: -------------------------------------------------------------------------------- 1 | # Асинхронный поток 2 | 3 | Без [мидлвара (middleware)](Middleware.md) Redux стор поддерживает только [синхронный поток данных](../basics/DataFlow.md). Это то, что вы получаете по умолчанию с [`createStore()`](../api/createStore.md). 4 | 5 | Вы можете расширить [`createStore()`](../api/createStore.md) с помощью [`applyMiddleware()`](../api/applyMiddleware.md). Это не обязательно, но это позволит вам [выразить асинхронные экшены в удобном виде](AsyncActions.md). 6 | 7 | Асинхронный мидлвар, типа [redux-thunk](https://github.com/gaearon/redux-thunk) или [redux-promise](https://github.com/acdlite/redux-promise), оборачивает метод стора [`dispatch()`](../api/Store.md#dispatch) и позволяет вам вызывать что-то, что не является экшеном, например, функции или Промисы. Любые мидлвары, которые вы используете, могут интерпретировать все, что вы вызываете, и, в свою очередь, могут передать экшены следующему мидлвару в цепочке. Например, мидлвар промисов (Promise middleware) может перехватывать промисы и отправлять пару начало/конец экшенов асинхронно в ответ на каждый промис. 8 | 9 | Когда последний мидлвар в цепочке отправляет экшен, он должен быть простым объектом. Дальше в дело вступает [синхронный поток данных Redux](../basics/DataFlow.md). 10 | 11 | Изучите [полные исходные коды асинхронного примера](ExampleRedditAPI.md). 12 | 13 | ## Следующие шаги 14 | 15 | Сейчас вы видели пример того, что мидлвар может сделать в Redux. Пришло время узнать, как это на самом деле работает и как вы можете создать свой собственный. Перейдите к следующему подробному разделу о [мидлваре](Middleware.md). 16 | -------------------------------------------------------------------------------- /docs/api/compose.md: -------------------------------------------------------------------------------- 1 | # `compose(...functions)` 2 | 3 | Объединяет функции справа налево. 4 | 5 | Это утилита функционального программирования, которая включена в Redux для удобства. 6 | Вы можете использовать ее, чтобы применить несколько [расширителей стора](../Glossary.md#store-enhancer) последовательно. 7 | 8 | #### Параметры 9 | 10 | 1. (*аргументы*): функции для композиции. Ожидается, что каждая функция принимает один параметр. Ее возвращаемое значение будет представлено в качестве аргумента для функции стоящей слева, и так далее. Исключением является самый правый аргумент, который может принимать несколько параметров, поскольку он будет обеспечивать подпись для полученной в результате функции. 11 | 12 | #### Возвращает 13 | 14 | (*Функция*): конечная функция полученая путем композиции переданных функций справа налево. 15 | 16 | #### Пример 17 | 18 | В этом примере показано, как использовать `compose` для расширения [стора](Store.md) с [`applyMiddleware`](applyMiddleware.md) и несколькоми инструментами для разработки из пакета [redux devtools](https://github.com/gaearon/redux-devtools). 19 | 20 | ```js 21 | import { createStore, applyMiddleware, compose } from 'redux' 22 | import thunk from 'redux-thunk' 23 | import DevTools from './containers/DevTools' 24 | import reducer from '../reducers' 25 | 26 | const store = createStore( 27 | reducer, 28 | compose( 29 | applyMiddleware(thunk), 30 | DevTools.instrument() 31 | ) 32 | ) 33 | ``` 34 | 35 | #### Советы 36 | 37 | * `compose` позволяет вам писать глубоко вложенные функции преобразований без дрейфа вправо (rightward drift) в коде. Не предоставляйте ей слишком многого! 38 | -------------------------------------------------------------------------------- /docs/usage/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: index 3 | title: Usage Guides Index 4 | sidebar_label: Usage Guides Index 5 | --- 6 | 7 | # Usage Guides 8 | 9 | The Usage Guides section provides practical guidance on how to correctly use Redux in real-world applications, including project setup and architecture, patterns, practices, and techniques. 10 | 11 | :::info Prerequisites 12 | 13 | The pages in this category assume you understand the core Redux terms and concepts explained in [the "Redux Fundamentals" tutorial](../tutorials/fundamentals/part-1-overview.md), including actions, reducers, stores, immutability, React-Redux, and async logic. 14 | 15 | ::: 16 | 17 | ## Setup and Organization 18 | 19 | This section covers information on how to set up and organize Redux-based projects. 20 | 21 | - [Configuring Your Store](ConfiguringYourStore.md) 22 | - [Code Splitting](CodeSplitting.md) 23 | - [Server Rendering](ServerRendering.md) 24 | - [Isolating Redux Sub-Apps](IsolatingSubapps.md) 25 | 26 | ## Code Quality 27 | 28 | This section provides information on tools and techniques used to improve the quality of your Redux code. 29 | 30 | - [Usage with TypeScript](UsageWithTypescript.md) 31 | - [Writing Tests](WritingTests.md) 32 | - [Troubleshooting](Troubleshooting.md) 33 | 34 | ## Redux Logic and Patterns 35 | 36 | This section provides information about typical Redux patterns and approaches for writing different kinds of Redux logic. 37 | 38 | - [Structuring Reducers](structuring-reducers/StructuringReducers.md) 39 | - [Reducing Boilerplate](ReducingBoilerplate.md) 40 | - [Deriving Data with Selectors](../usage/deriving-data-selectors.md) 41 | - [Implementing Undo History](ImplementingUndoHistory.md) 42 | -------------------------------------------------------------------------------- /docs/tutorials/tutorials-index.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: tutorials-index 3 | slug: index 4 | title: 'Redux Tutorials Index' 5 | sidebar_label: 'Tutorials Index' 6 | description: 'Overview of the Redux tutorial pages' 7 | --- 8 | 9 | import LiteYouTubeEmbed from 'react-lite-youtube-embed'; 10 | import 'react-lite-youtube-embed/dist/LiteYouTubeEmbed.css' 11 | 12 | # Redux Tutorials Index 13 | 14 | ## Redux Official Tutorials 15 | 16 | The [**Quick Start** page](./quick-start.md) briefly shows the basics of setting up a Redux Toolkit + React application, and the [**TypeScript Quick Start** page](./typescript.md) shows how to set up Redux Toolkit and React for use with TypeScript. 17 | 18 | We have two different full-size tutorials: 19 | 20 | - The [**Redux Essentials tutorial**](./essentials/part-1-overview-concepts) is a "top-down" tutorial that teaches "how to use Redux the right way", using our latest recommended APIs and best practices. 21 | - The [**Redux Fundamentals tutorial**](./fundamentals/part-1-overview.md) is a "bottom-up" tutorial that teaches "how Redux works" from first principles and without any abstractions, and why standard Redux usage patterns exist. 22 | 23 | :::tip 24 | 25 | **We recommend starting with the [Redux Essentials tutorial](./essentials/part-1-overview-concepts)**, since it covers the key points you need to know about how to get started using Redux to write actual applications. 26 | 27 | ::: 28 | 29 | ## Additional Resources 30 | 31 | ### Learn Modern Redux Livestream 32 | 33 | Redux maintainer Mark Erikson appeared on the "Learn with Jason" show to explain how we recommend using Redux today. The show includes a live-coded example app that shows how to use Redux Toolkit and React-Redux hooks with Typescript, as well as the new RTK Query data fetching APIs: 34 | 35 | 39 | -------------------------------------------------------------------------------- /docs/introduction/Motivation.md: -------------------------------------------------------------------------------- 1 | # Мотивация 2 | 3 | По мере того как требования к одностраничным JavaScript приложениям становятся все более высокими, **мы вынуждены управлять все большим количеством состояний (State)** с помощью JavaScript. Эти состояния могут включать в себя ответы сервера, кэшированные данные, а также данные, созданные локально, но еще не сохраненные на сервере. Это также относится к UI-состояниям, таким как активный маршрут (route), выделенный таб, показанный спиннер или нумерация страниц и т.д. 4 | 5 | Управлять постоянно изменяющимися состояниями сложно. Если модель может обновить другую модель, то представление может обновить модель, которая обновляет другую модель, а это, в свою очередь, может вызвать обновление другого представления. В какой-то момент вы больше не знаете, что происходит в вашем приложении. **Вы больше не можете контролировать когда, почему и как состояние обновилось**. Когда система становится непрозрачной и недетерминированной, трудно выявить ошибки или добавлять новую функциональность. 6 | 7 | Это достаточно скверно, принимая во внимание **новые требования, становящиеся обычными для фронтэнд-разработки**, такие как: обработка оптимистичных обновлений (optimistic updates), рендер на сервере, извлечение данных перед выполнением перехода на страницу и так далее. Как frontend-разработчики, мы пытаемся совладать со сложностью, с которой мы никогда не имели дела прежде, и поэтому неизбежно задаемся вопросом: [настало время сдаться?](http://www.quirksmode.org/blog/archives/2015/07/stop_pushing_th.html) 8 | 9 | Эта сложность возникает из-за того, что **мы смешиваем две концепции**, которые очень нелегки для понимания: **изменения (mutation) и асинхронность (asynchronicity).** Я называю их [Ментос и Кола](https://en.wikipedia.org/wiki/Diet_Coke_and_Mentos_eruption). Обе эти концепции могут быть прекрасными по отдельности, но вместе они превращаются в бардак. Библиотеки, аналогичные [React](http://facebook.github.io/react), пытаются решить эту проблему на уровне представления, удаляя асинхронность и прямое манипулирование DOM. Тем не менее, React оставляет управление состоянием данных за вами. И тут в дело вступает Redux. 10 | 11 | Идя по следам [Flux](http://facebook.github.io/flux), [CQRS](http://martinfowler.com/bliki/CQRS.html) и [Event Sourcing](http://martinfowler.com/eaaDev/EventSourcing.html), **Redux пытается сделать изменения состояния предсказуемыми**, путем введения некоторых ограничений на то, как и когда могут произойти обновления. Эти ограничения отражены в [трех принципах](ThreePrinciples.md) Redux. 12 | -------------------------------------------------------------------------------- /docs/introduction/CoreConcepts.md: -------------------------------------------------------------------------------- 1 | # Основные понятия 2 | 3 | Imagine your app’s state is described as a plain object. For example, the state of a todo app might look like this: 4 | 5 | ```js 6 | { 7 | todos: [{ 8 | text: 'Eat food', 9 | completed: true 10 | }, { 11 | text: 'Exercise', 12 | completed: false 13 | }], 14 | visibilityFilter: 'SHOW_COMPLETED' 15 | } 16 | ``` 17 | 18 | This object is like a “model” except that there are no setters. This is so that different parts of the code can’t change the state arbitrarily, causing hard-to-reproduce bugs. 19 | 20 | To change something in the state, you need to dispatch an action. An action is a plain JavaScript object (notice how we don’t introduce any magic?) that describes what happened. Here are a few example actions: 21 | 22 | ```js 23 | { type: 'ADD_TODO', text: 'Go to swimming pool' } 24 | { type: 'TOGGLE_TODO', index: 1 } 25 | { type: 'SET_VISIBILITY_FILTER', filter: 'SHOW_ALL' } 26 | ``` 27 | 28 | Enforcing that every change is described as an action lets us have a clear understanding of what’s going on in the app. If something changed, we know why it changed. Actions are like breadcrumbs of what has happened. 29 | Finally, to tie state and actions together, we write a function called a reducer. Again, nothing magical about it—it’s just a function that takes state and action as arguments, and returns the next state of the app. 30 | It would be hard to write such a function for a big app, so we write smaller functions managing parts of the state: 31 | 32 | ```js 33 | function visibilityFilter(state = 'SHOW_ALL', action) { 34 | if (action.type === 'SET_VISIBILITY_FILTER') { 35 | return action.filter 36 | } else { 37 | return state 38 | } 39 | } 40 | 41 | function todos(state = [], action) { 42 | switch (action.type) { 43 | case 'ADD_TODO': 44 | return state.concat([{ text: action.text, completed: false }]) 45 | case 'TOGGLE_TODO': 46 | return state.map((todo, index) => 47 | action.index === index 48 | ? { text: todo.text, completed: !todo.completed } 49 | : todo 50 | ) 51 | default: 52 | return state 53 | } 54 | } 55 | ``` 56 | 57 | And we write another reducer that manages the complete state of our app by calling those two reducers for the corresponding state keys: 58 | 59 | ```js 60 | function todoApp(state = {}, action) { 61 | return { 62 | todos: todos(state.todos, action), 63 | visibilityFilter: visibilityFilter(state.visibilityFilter, action) 64 | } 65 | } 66 | ``` 67 | 68 | This is basically the whole idea of Redux. Note that we haven’t used any Redux APIs. It comes with a few utilities to facilitate this pattern, but the main idea is that you describe how your state is updated over time in response to action objects, and 90% of the code you write is just plain JavaScript, with no use of Redux itself, its APIs, or any magic. -------------------------------------------------------------------------------- /docs/recipes/MigratingToRedux.md: -------------------------------------------------------------------------------- 1 | # Мигрирование на Redux 2 | 3 | Redux — это не монолитный фреймворк, а набор соглашений и [нескольких функций, заставляющих их работать вместе](../api/README.md). Большая часть вашего “Redux кода” не будет даже использовать Redux API, поскольку большую часть времени вы будете писать функции. 4 | 5 | Это позволяет легко переходить как на, так и с Redux. 6 | Мы не хотим вас ограничивать! 7 | 8 | ## Мигрирование с Flux 9 | 10 | [Редьюсеры](../Glossary.md#редьюсер-reducer) несут в себе “суть” Flux-сторов, так что возможно постепенно перенести существующий Flux-проект на Redux. При этом не важно, используете ли вы [Flummox](http://github.com/acdlite/flummox), [Alt](http://github.com/goatslacker/alt), [стандартный Flux](https://github.com/facebook/flux) или любую другую Flux-библиотеку. 11 | 12 | Также это позволяет делать обратное: мигрировать с Redux на любую из этих библиотек с использованием тех же шагов. 13 | 14 | Ваш процесс перехода будет выглядеть примерно так: 15 | 16 | * Добавляете функцию `createFluxStore(reducer)`, которая создает Flux-стор, совместимый с вашим существующим приложение, из функции-редьюсера. Внутри она может выглядеть как [`createStore`](../api/createStore.md) ([источник](https://github.com/reactjs/redux/blob/master/src/createStore.js)) реализация из Redux. Ее обработчик должен просто вызывать `reducer` на любой экшен, сохранять следующее состояние, и выделять изменения. 17 | 18 | * Это позволяет вам постепенно переписывать каждый Flux-стор в приложении на редьюсер, но все еще поддерживать `createFluxStore(reducer)`, так что остальная часть вашего приложения не знает, что происходит переход, и видит Flux-стор. 19 | 20 | * Как только вы переписали свои сторы, вы обнаружите, что вам надо избавиться от определенных анти-паттернов Flux, таких как использование API или генерирование экшенов внутри стора. Ваш Flux код будет легче после того, как вы перенесете это в редьюсеры. 21 | 22 | * Когда вы перенесли все ваши Flux-сторы на редьюсеры, вы можете заменить Flux-библиотеку одним Redux-стором и объединить все эти только что созданные редьюсеры с помощью [`combineReducers(reducers)`](../api/combineReducers.md). 23 | 24 | * Теперь все, что осталось, — это переписать интерфейс с [использованием react-redux](../basics/UsageWithReact.md) или аналогов. 25 | 26 | * В конце, вам могут понадобиться некоторые Redux-идеомы, такие как мидлвар, для дальнейшего упрощения асинхронного кода. 27 | 28 | ## Мигрирование с Backbone 29 | 30 | Слой модели Backbone довольно сильно отличается от Redux, поэтому мы не предлагаем смешивать их. Если возможно, лучше переписать слой модели вашего приложения с нуля вместо подсоединения Backbone к Redux. Однако, если переписывание невозможно, вы можете использовать [backbone-redux](https://github.com/redbooth/backbone-redux), чтобы постепенно перейти и сохранить Redux-стор синхронизированным с Backbone моделями и коллекциями. 31 | -------------------------------------------------------------------------------- /docs/usage/IsolatingSubapps.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: isolating-redux-sub-apps 3 | title: Isolating Redux Sub-Apps 4 | --- 5 | 6 | # Isolating Redux Sub-Apps 7 | 8 | Consider the case of a “big” app (contained in a `` component) 9 | that embeds smaller “sub-apps” (contained in `` components): 10 | 11 | ```js 12 | import React, { Component } from 'react' 13 | import SubApp from './subapp' 14 | 15 | class BigApp extends Component { 16 | render() { 17 | return ( 18 |
19 | 20 | 21 | 22 |
23 | ) 24 | } 25 | } 26 | ``` 27 | 28 | These ``s will be completely independent. They won't share data or 29 | actions, and won't see or communicate with each other. 30 | 31 | It's best not to mix this approach with standard Redux reducer composition. 32 | For typical web apps, stick with reducer composition. For 33 | “product hubs”, “dashboards”, or enterprise software that groups disparate 34 | tools into a unified package, give the sub-app approach a try. 35 | 36 | The sub-app approach is also useful for large teams that are divided by product 37 | or feature verticals. These teams can ship sub-apps independently or in combination 38 | with an enclosing “app shell”. 39 | 40 | Below is a sub-app's root connected component. 41 | As usual, it can render more components, connected or not, as children. 42 | Usually we'd render it in `` and be done with it. 43 | 44 | ```js 45 | class App extends Component { ... } 46 | export default connect(mapStateToProps)(App) 47 | ``` 48 | 49 | However, we don't have to call `ReactDOM.render()` 50 | if we're interested in hiding the fact that the sub-app component is a Redux app. 51 | 52 | Maybe we want to be able to run multiple instances of it in the same “bigger” app 53 | and keep it as a complete black box, with Redux being an implementation detail. 54 | 55 | To hide Redux behind a React API, we can wrap it in a special component that 56 | initializes the store in the constructor: 57 | 58 | ```js 59 | import React, { Component } from 'react' 60 | import { Provider } from 'react-redux' 61 | import { createStore } from 'redux' 62 | import reducer from './reducers' 63 | import App from './App' 64 | 65 | class SubApp extends Component { 66 | constructor(props) { 67 | super(props) 68 | this.store = createStore(reducer) 69 | } 70 | 71 | render() { 72 | return ( 73 | 74 | 75 | 76 | ) 77 | } 78 | } 79 | ``` 80 | 81 | This way every instance will be independent. 82 | 83 | This pattern is _not_ recommended for parts of the same app that share data. 84 | However, it can be useful when the bigger app has zero access to the smaller apps' internals, 85 | and we'd like to keep the fact that they are implemented with Redux as an implementation detail. 86 | Each component instance will have its own store, so they won't “know” about each other. 87 | -------------------------------------------------------------------------------- /docs/usage/structuring-reducers/StructuringReducers.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: structuring-reducers 3 | title: Structuring Reducers 4 | description: 'Structuring Reducers > Intro: overview and contents' 5 | --- 6 | 7 | # Structuring Reducers 8 | 9 | At its core, Redux is really a fairly simple design pattern: all your "write" logic goes into a single function, and the only way to run that logic is to give Redux a plain object that describes something that has happened. The Redux store calls that write logic function and passes in the current state tree and the descriptive object, the write logic function returns some new state tree, and the Redux store notifies any subscribers that the state tree has changed. 10 | 11 | Redux puts some basic constraints on how that write logic function should work. As described in ["Redux Fundamentals" Part 3: State, Actions, and Reducers](../../tutorials/fundamentals/part-3-state-actions-reducers.md), it has to have a signature of `(previousState, action) => newState`, is known as a **_reducer function_**, and must be _pure_ and predictable. 12 | 13 | Beyond that, Redux does not really care how you actually structure your logic inside that reducer function, as long as it obeys those basic rules. This is both a source of freedom and a source of confusion. However, there are a number of common patterns that are widely used when writing reducers, as well as a number of related topics and concepts to be aware of. As an application grows, these patterns play a crucial role in managing reducer code complexity, handling real-world data, and optimizing UI performance. 14 | 15 | ### Prerequisite Concepts for Writing Reducers 16 | 17 | Some of these concepts are already described elsewhere in the Redux documentation. Others are generic and applicable outside of Redux itself, and there are numerous existing articles that cover these concepts in detail. These concepts and techniques form the foundation of writing solid Redux reducer logic. 18 | 19 | It is vital that these Prerequisite Concepts are **thoroughly understood** before moving on to more advanced and Redux-specific techniques. A recommended reading list is available at: 20 | 21 | #### [Prerequisite Concepts](PrerequisiteConcepts.md) 22 | 23 | Standard Redux architecture relies on using plain JS objects and arrays for your state. If you're using an alternate approach for some reason, the details may differ based on your approach, but many of the principles will still apply. 24 | 25 | ### Reducer Concepts and Techniques 26 | 27 | - [Basic Reducer Structure](BasicReducerStructure.md) 28 | - [Splitting Reducer Logic](SplittingReducerLogic.md) 29 | - [Refactoring Reducers Example](RefactoringReducersExample.md) 30 | - [Using `combineReducers`](UsingCombineReducers.md) 31 | - [Beyond `combineReducers`](BeyondCombineReducers.md) 32 | - [Normalizing State Shape](NormalizingStateShape.md) 33 | - [Updating Normalized Data](UpdatingNormalizedData.md) 34 | - [Reusing Reducer Logic](ReusingReducerLogic.md) 35 | - [Immutable Update Patterns](ImmutableUpdatePatterns.md) 36 | - [Initializing State](InitializingState.md) 37 | -------------------------------------------------------------------------------- /docs/usage/MigratingToRedux.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: migrating-to-redux 3 | title: Migrating to Redux 4 | --- 5 | 6 | # Migrating to Redux 7 | 8 | Redux is not a monolithic framework, but a set of contracts and a [few functions that make them work together](../api/README.md). The majority of your “Redux code” will not even use Redux APIs, as most of the time you'll be writing functions. 9 | 10 | This makes it easy to migrate both to and from Redux. 11 | We don't want to lock you in! 12 | 13 | ## From Flux 14 | 15 | [Reducers](../understanding/thinking-in-redux/Glossary.md#reducer) capture “the essence” of Flux Stores, so it's possible to gradually migrate an existing Flux project towards Redux, whether you are using [Flummox](https://github.com/acdlite/flummox), [Alt](http://github.com/goatslacker/alt), [traditional Flux](https://github.com/facebook/flux), or any other Flux library. 16 | 17 | Your process will look like this: 18 | 19 | - Create a function called `createFluxStore(reducer)` that creates a Flux store compatible with your existing app from a reducer function. Internally it might look similar to [`createStore`](../api/createStore.md) ([source](https://github.com/reduxjs/redux/blob/v4.0.5/src/createStore.js)) implementation from Redux. Its dispatch handler should just call the `reducer` for any action, store the next state, and emit change. 20 | 21 | - This allows you to gradually rewrite every Flux Store in your app as a reducer, but still export `createFluxStore(reducer)` so the rest of your app is not aware that this is happening and sees the Flux stores. 22 | 23 | - As you rewrite your Stores, you will find that you need to avoid certain Flux anti-patterns such as fetching API inside the Store, or triggering actions inside the Stores. Your Flux code will be easier to follow once you port it to be based on reducers! 24 | 25 | - When you have ported all of your Flux Stores to be implemented on top of reducers, you can replace the Flux library with a single Redux store, and combine those reducers you already have into one using [`combineReducers(reducers)`](../api/combineReducers.md). 26 | 27 | - Now all that's left to do is to port the UI to [use React-Redux](../tutorials/fundamentals/part-5-ui-and-react.md) or equivalent. 28 | 29 | - Finally, you might want to begin using some Redux idioms like middleware to further simplify your asynchronous code. 30 | 31 | ## From Backbone 32 | 33 | Backbone's model layer is quite different from Redux, so we don't suggest mixing them. If possible, it is best that you rewrite your app's model layer from scratch instead of connecting Backbone to Redux. However, if a rewrite is not feasible, you may use [backbone-redux](https://github.com/redbooth/backbone-redux) to migrate gradually, and keep the Redux store in sync with Backbone models and collections. 34 | 35 | If your Backbone codebase is too big for a quick rewrite or you don't want to manage interactions between store and models, use [backbone-redux-migrator](https://github.com/naugtur/backbone-redux-migrator) to help your two codebases coexist while keeping healthy separation. Once your rewrite finishes, Backbone code can be discarded and your Redux application can work on its own once you configure router. 36 | -------------------------------------------------------------------------------- /docs/recipes/IsolatingSubapps.md: -------------------------------------------------------------------------------- 1 | # Изолирование субприложений Redux 2 | 3 | Предcтавьте себе "большое" приложение (содержащееся в компоненте ``), 4 | которое включает в себя меньшие субприложения (содержащиеся в компонентах ``) 5 | 6 | ```js 7 | import React, { Component } from 'react' 8 | import SubApp from './subapp' 9 | 10 | class BigApp extends Component { 11 | render() { 12 | return ( 13 |
14 | 15 | 16 | 17 |
18 | ) 19 | } 20 | } 21 | ``` 22 | 23 | Эти компоненты `` будут полностью независимы. Они не будут разделять `состояние` или `экшены`, 24 | не будут видеть друг друга или взаимодействовать между собой. 25 | 26 | Не стоит смешивать такой подход со стандартной композицией `редьюсеров` в Redux. 27 | Для обычных веб-приложений лучше всего придерживаться композиции редьюсеров. 28 | Попробуйте использовать подход субприложений для "концентраторов продуктов", дашбордов или enterprise-приложений, которые группируют несоизмеримые вещи в единый пакет приложения. 29 | 30 | Он также может быть полезен для больших команд, которые делят ответственность по продуктам или фичам. 31 | Такие команды могут поставлять отдельные субприложения по отдельности или в рамках общей оболочки продукта. 32 | 33 | Ниже представлен корневой компонент субприложения, соединённый с Redux. 34 | Как и любой другой компонент, он может рендерить другие компоненты, как соединённые с Redux, так и нет. 35 | Обычно мы хотим отрендерить корневой компонент внутри ``. 36 | 37 | ```js 38 | class App extends Component { ... } 39 | export default connect(mapStateToProps)(App) 40 | ``` 41 | 42 | При этом, нам не нужно вызывать `ReactDOM.render()`, 43 | если мы заинтересованы в сокрытии того, что наш компонент-субприложение использует Redux. 44 | Возможно, мы хотим запустить множество таких субприложений внутри "большого" приложения, 45 | и чтобы каждый из них был "чёрным ящиком", а Redux был бы всего лишь деталью их реализации. 46 | 47 | Для того, чтобы спрятать Redux за React API, мы можем обернуть его в специальный компонент, 48 | который инициализирует `стор` в конструкторе: 49 | 50 | ```js 51 | import React, { Component } from 'react' 52 | import { Provider } from 'react-redux' 53 | import { createStore } from 'redux' 54 | import reducer from './reducers' 55 | import App from './App' 56 | 57 | class SubApp extends Component { 58 | constructor(props) { 59 | super(props) 60 | this.store = createStore(reducer) 61 | } 62 | 63 | render() { 64 | return ( 65 | 66 | 67 | 68 | ) 69 | } 70 | } 71 | ``` 72 | 73 | Таким образом, каждый экземпляр субприложения будет полностью независимым. 74 | 75 | Этот паттерн *не* рекомендуется для частей одного и того же приложения, которые имеют общие данные. 76 | Тем не менее, он может быть полезен в случае, когда "большое" приложение не имеет доступа к внутренностям субприложений, 77 | а мы бы хотели оставить факт использования ими Redux деталью реализации. 78 | В этом случае, каждый экземпляр компонента-субприложения будет иметь собственный `стора`, и они не будут "знать" друг о друге. 79 | 80 | -------------------------------------------------------------------------------- /docs/recipes/StructuringReducers.md: -------------------------------------------------------------------------------- 1 | # Структурирование редьюсеров 2 | 3 | В основе своей, Redux следует достаточно простой концепции: вся ваша логика "записи" находится в одной функции, а единственный способ её вызвать - это дать Redux простой объект, объясняющий, что что-то произошло. Redux-стор запускает функцию с логикой записи, передавая её текущее дерево состояния и объект описания; на основании этого функция возвращает новое состояние, а Redux информирует подписчиков, что оно изменилось. 4 | 5 | Redux накладывает некоторые базовые ограничения на то, как именно должна работать функция записи. Как описано в разделе [Редьюсеры](../basics/Reducers.md), она обязана иметь сигнатуру `(previousState, action) => newState` (**сигнатура редьюсера**), должна быть *чистой* и предсказуемой. 6 | 7 | Для Redux совершенно не важно, как именно реализована ваша редьюсер-функция, если она следует правилам, описанным выше. Это даёт нам свободу действий, но, в то же время, может стать источником беспорядка. В связи с этим, существует несколько широко используемых паттернов написания редьюсеров, а также множество связанных с ними тем и вещей, которых стоит опасаться. С ростом приложения, эти паттерны сыграют ключевую роль в управлении сложностью кода редьюсеров, обработке реальных данных и оптимизации производительности UI. 8 | 9 | 10 | ### Базовые концепции написания редьюсеров 11 | 12 | Некоторые из этих концепций уже были описаны в других частях документации Redux, другие же являются общеупотребимыми, и могут быть применены не только в Redux. Написано множество статей, которые в деталях рассказывают об их использовании. Эти техники и концепции формируют базу для написания качественной логики редьюсеров Redux. 13 | 14 | Важно, чтобы вы **действительно поняли** эти базовые концепции перед тем, как двигаться к более сложным и Redux-специфичным. Рекомендуемый их список их находится здесь: 15 | #### [Базовые концепции](./reducers/PrerequisiteConcepts.md) 16 | 17 | Также важно отметить, что некоторые из этих концепций могут быть неприменимы в связи с архитектурными особенностями приложения. Например, приложение, использующее Maps из Immutable.js для хранения данных, почти наверняка будет иметь отличающуюся логику редьюсеров от случая, когда используются нативные объекты JavaScript. Эта документация, в основном, предполагает использование объектов JavaScript, но многие принципы будут применимы и с другими инструментами. 18 | 19 | 20 | 21 | ### Концепции и техники написания редьюсеров 22 | 23 | - [Базовая структура редьюсера](./reducers/BasicReducerStructure.md) 24 | - [Разделение логики редьюсеров](./reducers/SplittingReducerLogic.md) 25 | - [Пример рефакторинга логики редьюсеров](./reducers/RefactoringReducersExample.md) 26 | - [Использование `combineReducers`](./reducers/UsingCombineReducers.md) 27 | - [Редьюсеры без `combineReducers`](./reducers/BeyondCombineReducers.md) 28 | - [Нормализация состояния](./reducers/NormalizingStateShape.md) 29 | - [Обновление нормализованных данных](./reducers/UpdatingNormalizedData.md) 30 | - [Переиспользование логики редьюсеров](./reducers/ReusingReducerLogic.md) 31 | - [Паттерны иммутабельного обновления](./reducers/ImmutableUpdatePatterns.md) 32 | - [Инициализация состояния](./reducers/InitializingState.md) 33 | -------------------------------------------------------------------------------- /docs/recipes/UsingObjectSpreadOperator.md: -------------------------------------------------------------------------------- 1 | # Использование оператора расширения 2 | 3 | Поскольку одним из основных постулатов Redux является "никогда не изменять состояние напрямую", вам часто придется использовать [`Object.assign()`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) для создания копий объектов с новыми или измененными значениями. Например, в примере `todoApp` ниже используется `Object.assign()` для возвращения нового `состояния` объекта с обновленным `visibilityFilter` полем: 4 | 5 | ```js 6 | function todoApp(state = initialState, action) { 7 | switch (action.type) { 8 | case SET_VISIBILITY_FILTER: 9 | return Object.assign({}, state, { 10 | visibilityFilter: action.filter 11 | }) 12 | default: 13 | return state 14 | } 15 | } 16 | ``` 17 | 18 | Несмотря на эффективность, `Object.assign()` может довольно быстро сделать простые редьюсеры трудно читаемыми. 19 | 20 | Альтернативный вариант — использовать [object spread syntax](https://github.com/sebmarkbage/ecmascript-rest-spread), предложенный для следующих версий JavaScript, который позволяет вам использовать spread-оператор (`...`) для копирования перечисляемых свойств одного объекта в другой в более сжатой форме записи. Оператор расширения концептуально похож на [array spread operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator) ES6. Мы можем упростить пример `todoApp`, используя object spread syntax: 21 | 22 | ```js 23 | function todoApp(state = initialState, action) { 24 | switch (action.type) { 25 | case SET_VISIBILITY_FILTER: 26 | return { ...state, visibilityFilter: action.filter } 27 | default: 28 | return state 29 | } 30 | } 31 | ``` 32 | 33 | Преимущество использования оператора расширения становится более очевидно при составлении сложных объектов. В примере ниже `getAddedIds` сопоставляет массив с `ids` массиву объектов со значениями, которые возвращает `getProduct` и `getQuantity`. 34 | 35 | ```js 36 | return getAddedIds(state.cart).map(id => Object.assign( 37 | {}, 38 | getProduct(state.products, id), 39 | { 40 | quantity: getQuantity(state.cart, id) 41 | } 42 | )) 43 | ``` 44 | 45 | Оператор расширения позволяет нам упрощать вызов `map`: 46 | 47 | ```js 48 | return getAddedIds(state.cart).map(id => ({ 49 | ...getProduct(state.products, id), 50 | quantity: getQuantity(state.cart, id) 51 | })) 52 | ``` 53 | 54 | Пока оператор расширения все еще на стадии предложения в ECMAScript, вам требуется использовать транспилер, такой как [Babel](http://babeljs.io/), для использования оператора в продакшн-версии. Вы можете использовать существующий пресет `es2015`, установить [`babel-plugin-transform-object-rest-spread`](http://babeljs.io/docs/plugins/transform-object-rest-spread/) и добавить его отдельно в массив `plugins` в вашем файле `.babelrc`. 55 | 56 | ```js 57 | { 58 | "presets": ["es2015"], 59 | "plugins": ["transform-object-rest-spread"] 60 | } 61 | ``` 62 | 63 | Помните, что это все еще экспериментальная функция языка и она может измениться в будущем. Однако, некоторые большие проекты, такие как [React Native](https://github.com/facebook/react-native), уже широко используют это, так что с уверенностью можно сказать, что будет хорошая автоматическая миграция, если функция изменится. 64 | -------------------------------------------------------------------------------- /docs/faq/Miscellaneous.md: -------------------------------------------------------------------------------- 1 | # Redux FAQ: Разное 2 | 3 | ## Содержание 4 | 5 | - [Существуют ли большие, "настоящие" проекты на Redux?](#miscellaneous-real-projects) 6 | - [Как мне реализовать аутентификацию в Redux?](#miscellaneous-authentication) 7 | 8 | ## Разное 9 | 10 | 11 | ### Существуют ли большие, "настоящие" проекты на Redux? 12 | 13 | Да, множество! Вот несколько примеров: 14 | 15 | - [Twitter's mobile site](https://twitter.com/necolas/status/727538799966715904) 16 | - [Wordpress's new admin page](https://github.com/Automattic/wp-calypso) 17 | - [Firefox's new debugger](https://github.com/jlongster/debugger.html) 18 | - [Mozilla's experimental browser testbed](https://github.com/mozilla/tofino) 19 | - [The HyperTerm terminal application](https://github.com/zeit/hyperterm) 20 | 21 | И еще очень-очень много! Redux Addons Catalog имеет **[список основанных на Redux приложений и примеров](https://github.com/markerikson/redux-ecosystem-links/blob/master/apps-and-examples.md)**, что указывает на целый ряд реальных приложений, больших и маленьких. 22 | 23 | #### Дополнительная информация 24 | 25 | **Документация** 26 | 27 | - [Введение: Примеры](/docs/introduction/Examples.md) 28 | 29 | **Обсуждения** 30 | 31 | - [Reddit: Large open source react/redux projects?](https://www.reddit.com/r/reactjs/comments/496db2/large_open_source_reactredux_projects/) 32 | - [HN: Is there any huge web application built using Redux?](https://news.ycombinator.com/item?id=10710240) 33 | 34 | 35 | 36 | ### Как мне реализовать аутентификацию в Redux? 37 | 38 | Аутентификация необходима во всех реальных приложениях. Когда разговор идет об аутентификации, Вы должны помнить, что ничего не меняется от Вашей организации приложения, и Вы должны реализовывать аутентификацию тем же путем, что и любой другой функционал. Это относительно просто: 39 | 40 | 1. Создайте экшены для `LOGIN_SUCCESS`, `LOGIN_FAILURE`, и т.д. 41 | 42 | 2. Создайте генераторы экшенов, которые будут брать учетные данные, флаг для обозначения успешной аутентификации, и токен или сообщение об ошибке в качестве полезной нагрузки. 43 | 44 | 3. Создайте асинхронный генератор экшенов с помощью Redux Thunk мидлвара или любого другого мидлвара, который Вы считаете пригодным для отправки запросов по API и который возвращает токен, если данные верны. Затем сохраните токен в локальном сторе или покажите сообщение пользователю, если запрос неудачный. Вы можете улучшить эти сайд эффекты в генераторе экшенов, написанном на предыдущем шаге. 45 | 46 | 4. Создайте редьюсер, который возвращает следующее состояние для каждого возможного исхода аутентификации (`LOGIN_SUCCESS`, `LOGIN_FAILURE`, и т.д.). 47 | 48 | #### Дополнительная информация 49 | 50 | **Статьи** 51 | 52 | - [Authentication with JWT by Auth0](https://auth0.com/blog/2016/01/04/secure-your-react-and-redux-app-with-jwt-authentication/) 53 | - [Tips to Handle Authentication in Redux](https://medium.com/@MattiaManzati/tips-to-handle-authentication-in-redux-2-introducing-redux-saga-130d6872fbe7) 54 | 55 | **Примеры** 56 | 57 | - [react-redux-jwt-auth-example](https://github.com/joshgeller/react-redux-jwt-auth-example) 58 | 59 | **Библиотеки** 60 | 61 | - [Redux Addons Catalog: Use Cases - Authentication](https://github.com/markerikson/redux-ecosystem-links/blob/master/use-cases.md#authentication) 62 | -------------------------------------------------------------------------------- /docs/api/createStore.md: -------------------------------------------------------------------------------- 1 | # `createStore(reducer, [preloadedState], [enhancer])` 2 | 3 | Создает Redux [стор](Store.md) которое хранит полное дерево состояния вашего приложения. 4 | Оно должно быть единственным стором в вашем приложении. 5 | 6 | #### Параметры 7 | 8 | 1. `reducer` *(Function)*: [Функция редьюсера](../Glossary.md#reducer) которая возвращает следующее [дерево состояния](../Glossary.md#state), принимая текущее состояние и [экшен](../Glossary.md#action) к обработке. 9 | 10 | 2. [`preloadedState`] *(any)*: Начальное состояние. Вы можете дополнительно указать это для гидрирования состояния с сервера в универсальных приложениях или для восстановления предыдущей сериализированной сессии пользователя. Если вы создали `редьюсер` с помощью [` combineReducers`](combineReducers.md) - это должно быть простым объектом с той же формой, как и ключи переданные ему. Иначе, вы можете передать все, что ваш `редьюсер` может понять. 11 | 12 | 3. [`enhancer`] *(Function)*: Расширитель стора. Вы можете дополнительно указать это, чтобы расширить стор такими сторонними возможностями, как мидлвары (middleware), путешествия во времени, персистентность, и т.д. Единственный расширитель стора, который поставляется с Redux, это [`applyMiddleware()`](applyMiddleware.md). 13 | 14 | #### Возвращает 15 | 16 | ([*`Store`*](Store.md)): объект, который содержит полное состояние вашего приложения. Единственный способ изменить его состояние — путем [отправки экшенов](Store.md#dispatch). Вы можете также [подписаться](Store.md#subscribe) на изменения его состояния, чтобы обновить пользовательский интерфейс. 17 | 18 | #### Пример 19 | 20 | ```js 21 | import { createStore } from 'redux' 22 | 23 | function todos(state = [], action) { 24 | switch (action.type) { 25 | case 'ADD_TODO': 26 | return state.concat([action.text]) 27 | default: 28 | return state 29 | } 30 | } 31 | 32 | const store = createStore(todos, ['Use Redux']) 33 | 34 | store.dispatch({ 35 | type: 'ADD_TODO', 36 | text: 'Read the docs' 37 | }) 38 | 39 | console.log(store.getState()) 40 | // [ 'Use Redux', 'Read the docs' ] 41 | ``` 42 | 43 | #### Советы 44 | 45 | * Не создавайте более одного стора в приложении! Вместо этого используйте [`combineReducers`](combineReducers.md) для создания единого корневого редьюсера из нескольких. 46 | 47 | * Выбор формата состояния на ваше усмотрение. Можно использовать простые объекты или что-то вроде [Immutable](http://facebook.github.io/immutable-js/). Если вы не уверены, начните с простых объектов. 48 | 49 | * Если ваше состояние является простым объектом, убедитесь, что вы никогда его не изменяете! Например, вместо того, чтобы возвращать что-то вроде `Object.assign (state, newData)` из ваших редьюсеров, возвращайте `Object.assign ({}, state, newData)`. Таким образом, не переопределяется предыдущее `состояние`. Вы также можете написать `return { ...state, ...newData }` если вы включите [object spread operator proposal](../recipes/UsingObjectSpreadOperator.md). 50 | 51 | * Для универсальных приложений, которые выполняются на сервере, создавайте экземпляр стора с каждым запросом, так чтобы они были изолированы. Отправляйте несколько извлеченных экшенов для экземпляра стора и ждите их завершения перед рендерингом приложения на сервере. 52 | 53 | * Когда стор создан, Redux отправляет фиктивный экшен для вашего редьюсера, для заполнения стора с начальным состоянием. Вам не надо обрабатывать фиктивный экшен напрямую. Просто помните, что ваш редьюсер должен возвращать какое-то начальное состояние, если состояние переданное ему в качестве первого аргумента не `определено (undefined)` и все готово. 54 | 55 | * Чтобы использовать несколько расширителей стора вы можете использовать [`compose()`](compose.md). 56 | -------------------------------------------------------------------------------- /docs/introduction/ThreePrinciples.md: -------------------------------------------------------------------------------- 1 | # Три принципа 2 | 3 | Redux может быть описан тремя фундаментальными принципами: 4 | 5 | ### Единственный источник правды 6 | 7 | **[Состояние](../Glossary.md#state) всего вашего приложения сохранено в дереве объектов внутри одного [стора](../Glossary.md#store).** 8 | 9 | Это облегчает создание универсальных приложений. Состояние на сервере может быть сериализировано и отправлено на клиент без дополнительных усилий. Это упрощает отладку приложения, когда мы имеем дело с единственным деревом состояния. Вы также можете сохранять состояние вашего приложения для ускорения процесса разработки. И с единственным деревом состояния вы получаете функциональность типа Undo/Redo из коробки. 10 | 11 | ```js 12 | console.log(store.getState()) 13 | 14 | { 15 | visibilityFilter: 'SHOW_ALL', 16 | todos: [ 17 | { 18 | text: 'Consider using Redux', 19 | completed: true, 20 | }, 21 | { 22 | text: 'Keep all state in a single tree', 23 | completed: false 24 | } 25 | ] 26 | } 27 | ``` 28 | 29 | ### Состояние только для чтения 30 | 31 | **Единственный способ изменить состояние — это применить [экшен](../Glossary.md#action) — объект, который описывает, что случится.** 32 | 33 | Это гарантирует, что представления или функции, реагирующие на события сети (network callbacks), никогда не изменят состояние напрямую. Поскольку все изменения централизованы и применяются последовательно в строгом порядке, поэтому нет необходимости следить за ["гонкой состояний"](https://ru.wikipedia.org/wiki/%D0%A1%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5_%D0%B3%D0%BE%D0%BD%D0%BA%D0%B8). Экшены — это всего лишь простые объекты, поэтому они могут быть залогированы, сериализированы, сохранены и затем воспроизведены для отладки или тестирования. 34 | 35 | ```js 36 | store.dispatch({ 37 | type: 'COMPLETE_TODO', 38 | index: 1 39 | }) 40 | 41 | store.dispatch({ 42 | type: 'SET_VISIBILITY_FILTER', 43 | filter: 'SHOW_COMPLETED' 44 | }) 45 | ``` 46 | 47 | ### Мутации написаны, как чистые функции 48 | 49 | **Для определения того, как дерево состояния будет трансформировано экшенами, вы пишете чистые [редьюсеры](../Glossary.md#reducer).** 50 | 51 | Редьюсеры — это просто чистые функции, которые берут предыдущее состояние и экшен и возвращают новое состояние. 52 | Не забывайте возвращать новый объект состояния вместо того, чтобы изменять предыдущее. Вы можете начать с одного редьюсера, но в дальнейшем, когда ваше приложение разрастется, вы можете разделить его на более маленькие редьюсеры, которые управляют отдельными частями дерева состояния. Поскольку редьюсеры — это просто функции, вы можете контролировать порядок, в котором они вызываются, отправлять дополнительные данные или даже писать переиспользуемые редьюсеры для общих задач, например, для пагинации. 53 | 54 | ```js 55 | function visibilityFilter(state = 'SHOW_ALL', action) { 56 | switch (action.type) { 57 | case 'SET_VISIBILITY_FILTER': 58 | return action.filter 59 | default: 60 | return state 61 | } 62 | } 63 | 64 | function todos(state = [], action) { 65 | switch (action.type) { 66 | case 'ADD_TODO': 67 | return [ 68 | ...state, 69 | { 70 | text: action.text, 71 | completed: false 72 | } 73 | ] 74 | case 'COMPLETE_TODO': 75 | return state.map((todo, index) => { 76 | if (index === action.index) { 77 | return Object.assign({}, todo, { 78 | completed: true 79 | }) 80 | } 81 | return todo 82 | }) 83 | default: 84 | return state 85 | } 86 | } 87 | 88 | import { combineReducers, createStore } from 'redux' 89 | let reducer = combineReducers({ visibilityFilter, todos }) 90 | let store = createStore(reducer) 91 | ``` 92 | 93 | Вот и все! Теперь вы знаете все о Redux. 94 | -------------------------------------------------------------------------------- /docs/recipes/reducers/SplittingReducerLogic.md: -------------------------------------------------------------------------------- 1 | # Splitting Up Reducer Logic 2 | 3 | For any meaningful application, putting *all* your update logic into a single reducer function is quickly going to become unmaintainable. While there's no single rule for how long a function should be, it's generally agreed that functions should be relatively short and ideally only do one specific thing. Because of this, it's good programming practice to take pieces of code that are very long or do many different things, and break them into smaller pieces that are easier to understand. 4 | 5 | Since a Redux reducer is *just* a function, the same concept applies. You can split some of your reducer logic out into another function, and call that new function from the parent function. 6 | 7 | These new functions would typically fall into one of three categories: 8 | 9 | 1. Small utility functions containing some reusable chunk of logic that is needed in multiple places (which may or may not be actually related to the specific business logic) 10 | 2. Functions for handling a specific update case, which often need parameters other than the typical `(state, action)` pair 11 | 3. Functions which handle *all* updates for a given slice of state. These functions do generally have the typical `(state, action)` parameter signature 12 | 13 | 14 | For clarity, these terms will be used to distinguish between different types of functions and different use cases: 15 | 16 | - ***reducer***: any function with the signature `(state, action) -> newState` (ie, any function that *could* be used as an argument to `Array.reduce`) 17 | - ***root reducer***: the reducer function that is actually passed as the first argument to `createStore`. This is the only part of the reducer logic that _must_ have the `(state, action) -> newState` signature. 18 | - ***slice reducer***: a reducer that is being used to handle updates to one specific slice of the state tree, usually done by passing it to `combineReducers` 19 | - ***case function***: a function that is being used to handle the update logic for a specific action. This may actually be a reducer function, or it may require other parameters to do its work properly. 20 | - ***higher-order reducer***: a function that takes a reducer function as an argument, and/or returns a new reducer function as a result (such as `combineReducers`, or `redux-undo`) 21 | 22 | The term "*sub-reducer*" has also been used in various discussions to mean any function that is not the root reducer, although the term is not very precise. Some people may also refer to some functions as "*business logic*" (functions that relate to application-specific behavior) or "*utility functions*" (generic functions that are not application-specific). 23 | 24 | 25 | Breaking down a complex process into smaller, more understandable parts is usually described with the term ***[functional decomposition](http://stackoverflow.com/questions/947874/what-is-functional-decomposition)***. This term and concept can be applied generically to any code. However, in Redux it is *very* common to structure reducer logic using approach #3, where update logic is delegated to other functions based on slice of state. Redux refers to this concept as ***reducer composition***, and it is by far the most widely-used approach to structuring reducer logic. In fact, it's so common that Redux includes a utility function called [`combineReducers()`](../../api/combineReducers.md), which specifically abstracts the process of delegating work to other reducer functions based on slices of state. However, it's important to note that it is not the *only* pattern that can be used. In fact, it's entirely possible to use all three approaches for splitting up logic into functions, and usually a good idea as well. The [Refactoring Reducers](./RefactoringReducersExample.md) section shows some examples of this in action. 26 | -------------------------------------------------------------------------------- /docs/usage/UsingObjectSpreadOperator.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: using-object-spread-operator 3 | title: Using Object Spread Operator 4 | --- 5 | 6 | # Using Object Spread Operator 7 | 8 | Since one of the core tenets of Redux is to never mutate state, you'll often find yourself using [`Object.assign()`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) to create copies of objects with new or updated values. For example, in the `todoApp` below `Object.assign()` is used to return a new `state` object with an updated `visibilityFilter` property: 9 | 10 | ```js 11 | function todoApp(state = initialState, action) { 12 | switch (action.type) { 13 | case SET_VISIBILITY_FILTER: 14 | return Object.assign({}, state, { 15 | visibilityFilter: action.filter 16 | }) 17 | default: 18 | return state 19 | } 20 | } 21 | ``` 22 | 23 | While effective, using `Object.assign()` can quickly make simple reducers difficult to read given its rather verbose syntax. 24 | 25 | An alternative approach is to use the [object spread syntax](https://github.com/tc39/proposal-object-rest-spread) recently added to the JavaScript specification. It lets you use the spread (`...`) operator to copy enumerable properties from one object to another in a more succinct way. The object spread operator is conceptually similar to the ES6 [array spread operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator). We can simplify the `todoApp` example above by using the object spread syntax: 26 | 27 | ```js 28 | function todoApp(state = initialState, action) { 29 | switch (action.type) { 30 | case SET_VISIBILITY_FILTER: 31 | return { ...state, visibilityFilter: action.filter } 32 | default: 33 | return state 34 | } 35 | } 36 | ``` 37 | 38 | The advantage of using the object spread syntax becomes more apparent when you're composing complex objects. Below `getAddedIds` maps an array of `id` values to an array of objects with values returned from `getProduct` and `getQuantity`. 39 | 40 | ```js 41 | return getAddedIds(state.cart).map(id => 42 | Object.assign({}, getProduct(state.products, id), { 43 | quantity: getQuantity(state.cart, id) 44 | }) 45 | ) 46 | ``` 47 | 48 | Object spread lets us simplify the above `map` call to: 49 | 50 | ```js 51 | return getAddedIds(state.cart).map(id => ({ 52 | ...getProduct(state.products, id), 53 | quantity: getQuantity(state.cart, id) 54 | })) 55 | ``` 56 | 57 | While the object spread syntax is a [Stage 4](https://github.com/tc39/proposal-object-rest-spread#status-of-this-proposal) proposal for ECMAScript and accepted for the 2018 specification release, you will still need to use a transpiler such as [Babel](https://babeljs.io/) to use it in production systems. You should use the [`env`](https://github.com/babel/babel/tree/master/packages/babel-preset-env) preset, install [`@babel/plugin-proposal-object-rest-spread`](https://babeljs.io/docs/en/babel-plugin-proposal-object-rest-spread) and add it individually to the `plugins` array in your `.babelrc`. 58 | 59 | ```json 60 | { 61 | "presets": ["@babel/preset-env"], 62 | "plugins": ["@babel/plugin-proposal-object-rest-spread"] 63 | } 64 | ``` 65 | 66 | > ##### Note on Object Spread Operator 67 | 68 | > Like the Array Spread Operator, the Object Spread Operator creates a [shallow clone](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax#Spread_in_object_literals) of the original object. In other words, for multidimensional source objects, elements in the copied object at a depth greater than one are mere references to the source object (with the exception of [primitives](https://developer.mozilla.org/en-US/docs/Glossary/Primitive), which are copied). Thus, you cannot reliably use the Object Spread Operator (`...`) for deep cloning objects. 69 | -------------------------------------------------------------------------------- /docs/introduction/Examples.md: -------------------------------------------------------------------------------- 1 | # Примеры 2 | 3 | Redux распространяется с несколькими примерами в своих [исходных кодах](https://github.com/reactjs/redux/tree/master/examples). 4 | **Для того, чтобы запустить любой из них, достаточно склонировать репозиторий и запустить `npm install` в родительской директории и в директории примера.** 5 | 6 | >##### Особенности при копировании 7 | >Если вы скопировали примеры Redux из их директорий, удалите эти строки из файлов `webpack.config.js`: 8 | > 9 | >```js 10 | >alias: { 11 | > 'redux': path.join(__dirname, '..', '..', 'src') 12 | >}, 13 | >``` 14 | >и 15 | >```js 16 | >{ 17 | > test: /\.js$/, 18 | > loaders: ['babel'], 19 | > include: path.join(__dirname, '..', '..', 'src') 20 | >}, 21 | ``` 22 | > 23 | > Иначе они будут пытаться вызвать Redux из родительской `src` папки и сборка сломается. 24 | 25 | ## Счетчик 26 | 27 | Запуск примера [счетчика](https://github.com/reactjs/redux/tree/master/examples/counter): 28 | 29 | ``` 30 | git clone https://github.com/gaearon/redux.git 31 | 32 | cd redux 33 | npm install 34 | 35 | cd examples/counter 36 | npm install 37 | npm start 38 | 39 | open http://localhost:3000/ 40 | ``` 41 | 42 | Тут показаны: 43 | 44 | * Базовый шаблон разработки Redux; 45 | * Тестирование. 46 | 47 | ## TodoMVC 48 | 49 | Запуск [TodoMVC](https://github.com/reactjs/redux/tree/master/examples/todomvc): 50 | 51 | ``` 52 | git clone https://github.com/gaearon/redux.git 53 | 54 | cd redux 55 | npm install 56 | 57 | cd examples/todomvc 58 | npm install 59 | npm start 60 | 61 | open http://localhost:3000/ 62 | ``` 63 | 64 | Тут показаны: 65 | 66 | * Базовый шаблон разработки Redux с двумя редьюсерами; 67 | * Обновление хранимой информации; 68 | * Тестирование. 69 | 70 | ## Async 71 | 72 | Запуск [Async](https://github.com/reactjs/redux/tree/master/examples/async): 73 | 74 | ``` 75 | git clone https://github.com/gaearon/redux.git 76 | 77 | cd redux 78 | npm install 79 | 80 | cd examples/async 81 | npm install 82 | npm start 83 | 84 | open http://localhost:3000/ 85 | ``` 86 | 87 | Тут показаны: 88 | 89 | * Базовый шаблон асинхронной разработки Redux с [redux-thunk](https://github.com/gaearon/redux-thunk); 90 | * Кеширование запросов и вывод спиннера, пока данные подгружаются; 91 | * Инвалидация закэшированных данных. 92 | 93 | ## Real World 94 | 95 | Запуск примера [Real World](https://github.com/reactjs/redux/tree/master/examples/real-world): 96 | 97 | ``` 98 | git clone https://github.com/gaearon/redux.git 99 | 100 | cd redux 101 | npm install 102 | 103 | cd examples/real-world 104 | npm install 105 | npm start 106 | 107 | open http://localhost:3000/ 108 | ``` 109 | 110 | Тут показаны: 111 | 112 | * Real-world пример асинхронной работы с Redux; 113 | * Сохранение объектов в упорядоченном кэше объектов; 114 | * Отдельный middleware для вызовов API; 115 | * Кеширование запросов и вывод спиннера, пока данные подгружаются; 116 | * Пагинация; 117 | * Роутинг. 118 | 119 | ## Shopping Cart 120 | 121 | Запуск примера [Shopping Cart](https://github.com/reactjs/redux/tree/master/examples/shopping-cart): 122 | 123 | ``` 124 | git clone https://github.com/gaearon/redux.git 125 | 126 | cd redux 127 | npm install 128 | 129 | cd examples/shopping-cart 130 | npm install 131 | npm start 132 | 133 | open http://localhost:3000/ 134 | ``` 135 | 136 | Тут показаны: 137 | 138 | * Нормализованное состояние 139 | * Явное отслеживание ID сущностей 140 | * Компоновка редьюсеров 141 | * Использование очередей наряду с редьюсерами 142 | * Откат при ошибке 143 | * Безопасное распространение экшенов по условию 144 | * Использование только React Redux для привязки генераторов экшенов 145 | * Условные мидлвары (на примере логирования) 146 | 147 | ## Больше примеров 148 | 149 | Больше примеров вы сможете найти в [Awesome Redux](https://github.com/xgrommx/awesome-redux). 150 | -------------------------------------------------------------------------------- /docs/basics/Store.md: -------------------------------------------------------------------------------- 1 | # Стор (Store) 2 | 3 | В предыдущих разделах мы определили [экшены](Actions.md), которые представляют факт того, что "что-то случилось" и [редьюсеры](Reducers.md), которые обновляют состояние (state) в соответствии с этими экшенами. 4 | 5 | **Стор (Store)** — это объект, который соединяет эти части вместе. Стор берет на себя следующие задачи: 6 | 7 | * содержит состояние приложения (application state); 8 | * предоставляет доступ к состоянию с помощью [`getState()`](../api/Store.md#getState); 9 | * предоставляет возможность обновления состояния с помощью [`dispatch(action)`](../api/Store.md#dispatch); 10 | * Обрабатывает отмену регистрации слушателей с помощью функции, возвращаемой [`subscribe(listener)`](../api/Store.md#subscribelistener). 11 | 12 | Важно отметить, что у вас будет только один стор в Redux-приложении. Если Вы захотите разделить логику обработки данных, то нужно будет использовать [композицию редьюсеров (reducer composition)](Reducers.md#splitting-reducers) вместо использования множества сторов (stores). 13 | 14 | Очень легко создать стор (Store), если у Вас есть редьюсер. В [предыдущем разделе](Reducers.md) мы использовали [`combineReducers()`](../api/combineReducers.md) для комбинирования несколько редьюсеров в один глобальный редьюсер. Теперь мы их импортируем и передадим в [`createStore()`](../api/createStore.md). 15 | 16 | ```js 17 | import { createStore } from 'redux' 18 | import todoApp from './reducers' 19 | let store = createStore(todoApp) 20 | ``` 21 | 22 | Вы можете объявить начальное состояние, передав его вторым аргументом в [`createStore()`](../api/createStore.md). Это полезно для пробрасывания состояния на клиент из состояния приложения Redux, работающего на сервере. 23 | 24 | ```js 25 | const store = createStore(todoApp, window.STATE_FROM_SERVER) 26 | ``` 27 | 28 | ## Отправка экшенов (Dispatching Actions) 29 | 30 | На текущий момент у нас есть созданный стор, давайте проверим, как работает наше приложение! Даже без любого UI мы уже можем проверить логику обновления состояния. 31 | 32 | ```js 33 | import { 34 | addTodo, 35 | toggleTodo, 36 | setVisibilityFilter, 37 | VisibilityFilters 38 | } from './actions' 39 | 40 | // Выведем в консоль начальное состояние 41 | console.log(store.getState()) 42 | 43 | // Каждый раз при обновлении состояния - выводим его 44 | // Отметим, что subscribe() возвращает функцию для отмены регистрации слушателя 45 | const unsubscribe = store.subscribe(() => console.log(store.getState())) 46 | 47 | // Отправим несколько экшенов 48 | store.dispatch(addTodo('Learn about actions')) 49 | store.dispatch(addTodo('Learn about reducers')) 50 | store.dispatch(addTodo('Learn about store')) 51 | store.dispatch(toggleTodo(0)) 52 | store.dispatch(toggleTodo(1)) 53 | store.dispatch(setVisibilityFilter(VisibilityFilters.SHOW_COMPLETED)) 54 | 55 | // Прекратим слушать обновление состояния 56 | unsubscribe() 57 | ``` 58 | 59 | Вы можете видеть, как выполнение кода, приведенного выше, меняет состояние, содержащееся в сторе: 60 | 61 | 62 | 63 | Мы смогли определить поведение нашего приложения даже до того, как начали создавать какой-то UI. Мы не будем делать этого в этом руководстве, но с этого момента Вы можете писать тесты для редьюсеров и генераторов экшенов (action creators). Вам не нужно будет ничего "мокать", потому что редьюсеры — это просто [чистые](../introduction/ThreePrinciples.md#changes-are-made-with-pure-functions) функции. Вызывайте их и делайте проверки (make assertions) того, что они возвращают. 64 | 65 | ## Исходный код (Source Code) 66 | 67 | #### `index.js` 68 | 69 | ```js 70 | import { createStore } from 'redux' 71 | import todoApp from './reducers' 72 | 73 | let store = createStore(todoApp) 74 | ``` 75 | 76 | ## Следующие шаги 77 | 78 | Перед тем как создавать UI для нашего todo-приложения, мы сделаем небольшую прогулку, чтобы посмотреть, [что такое поток данных (data flows) в Redux-приложении](DataFlow.md). 79 | 80 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | ## Содержание 2 | 3 | * [Памятка](../README.md) 4 | * [Введение](introduction/README.md) 5 | * [Мотивация](introduction/Motivation.md) 6 | * [Начало работы с Redux](Introduction/GettingStarted.md) 7 | * [Основные понятия ](introduction/CoreConcepts.md) 8 | * [Три принципа](introduction/ThreePrinciples.md) 9 | * [Предшественники](introduction/PriorArt.md) 10 | * [Образовательные ресурсы](introduction/LearningResources.md) 11 | * [Экосистема](introduction/Ecosystem.md) 12 | * [Примеры](introduction/Examples.md) 13 | * [Основы](basics/README.md) 14 | * [Экшены](basics/Actions.md) 15 | * [Редьюсеры](basics/Reducers.md) 16 | * [Стор](basics/Store.md) 17 | * [Поток данных](basics/DataFlow.md) 18 | * [Использование с React](basics/UsageWithReact.md) 19 | * [Пример: Todo List](basics/ExampleTodoList.md) 20 | * [Продвинутое использование](advanced/README.md) 21 | * [Асинхронные экшены](advanced/AsyncActions.md) 22 | * [Асинхронные потоки](advanced/AsyncFlow.md) 23 | * [Мидлвар](advanced/Middleware.md) 24 | * [Использование с React Router](advanced/UsageWithReactRouter.md) 25 | * [Пример: Reddit API](advanced/ExampleRedditAPI.md) 26 | * [Следующие шаги](advanced/NextSteps.md) 27 | * [Рецепты](recipes/README.md) 28 | * [Настройка вашего стора](recipes/ConfiguringYourStore.md) 29 | * [Мигрирование на Redux](recipes/MigratingToRedux.md) 30 | * [Использование оператора расширения](recipes/UsingObjectSpreadOperator.md) 31 | * [Упрощение шаблона](recipes/ReducingBoilerplate.md) 32 | * [Серверный рендеринг](recipes/ServerRendering.md) 33 | * [Написание тестов](recipes/WritingTests.md) 34 | * [Оперирование полученными данными](recipes/ComputingDerivedData.md) 35 | * [Реализация истории отмен](recipes/ImplementingUndoHistory.md) 36 | * [Изолирование саб-приложений](recipes/IsolatingSubapps.md) 37 | * [Структурирование редьюсеров](recipes/StructuringReducers.md) 38 | * [Предварительные концепциии](recipes/reducers/PrerequisiteConcepts.md) 39 | * [Базовая структура редьюсера](recipes/reducers/BasicReducerStructure.md) 40 | * [Разделение логики редьюсера](recipes/reducers/SplittingReducerLogic.md) 41 | * [Рефакторинг примера редьюсеров](recipes/reducers/RefactoringReducersExample.md) 42 | * [Использование `combineReducers`](recipes/reducers/UsingCombineReducers.md) 43 | * [За пределами `combineReducers`](recipes/reducers/BeyondCombineReducers.md) 44 | * [Нормализация состояния](recipes/reducers/NormalizingStateShape.md) 45 | * [Обновление нормализованных данных](recipes/reducers/UpdatingNormalizedData.md) 46 | * [Переиспользование логики редьюсера](recipes/reducers/ReusingReducerLogic.md) 47 | * [Паттерны иммутабельного обновления](recipes/reducers/ImmutableUpdatePatterns.md) 48 | * [Инициализирование состояния](recipes/reducers/InitializingState.md) 49 | * [Использование Immutable.JS с Redux](recipes/UsingImmutableJS.md) 50 | * [Учебные пособия](tutorials/tutorials-index.md) 51 | * [FAQ](FAQ.md) 52 | * [Главное](faq/General.md) 53 | * [Редьюсеры](faq/Reducers.md) 54 | * [Организация состояния(State)](faq/OrganizingState.md) 55 | * [Настройка стора(Store)](faq/StoreSetup.md) 56 | * [Экшены (Actions)](faq/Actions.md) 57 | * [Иммутабельные данные](faq/ImmutableData.md) 58 | * [Структура кода](faq/CodeStructure.md) 59 | * [Производительность](faq/Performance.md) 60 | * [Проектные решения](faq/DesignDecisions.md) 61 | * [React Redux](faq/ReactRedux.md) 62 | * [Разное](faq/Miscellaneous.md) 63 | * [Поиск неисправностей](Troubleshooting.md) 64 | * [Глоссарий](Glossary.md) 65 | * [Справочник по API](api/README.md) 66 | * [createStore](api/createStore.md) 67 | * [Store](api/Store.md) 68 | * [combineReducers](api/combineReducers.md) 69 | * [applyMiddleware](api/applyMiddleware.md) 70 | * [bindActionCreators](api/bindActionCreators.md) 71 | * [compose](api/compose.md) 72 | * [История изменений](/CHANGELOG.md) 73 | * [Спонсоры](/PATRONS.md) 74 | * [Обратная связь](Feedback.md) 75 | 76 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | ## Содержание 2 | 3 | * [Памятка](/README.md) 4 | * [Введение](/docs/introduction/README.md) 5 | * [Мотивация](/docs/introduction/Motivation.md) 6 | * [Базовые концепции ](/docs/introduction/CoreConcepts.md) 7 | * [Три принципа](/docs/introduction/ThreePrinciples.md) 8 | * [Предшественники](/docs/introduction/PriorArt.md) 9 | * [Образовательные ресурсы](introduction/LearningResources.md) 10 | * [Экосистема](/docs/introduction/Ecosystem.md) 11 | * [Примеры](/docs/introduction/Examples.md) 12 | * [Основы](/docs/basics/README.md) 13 | * [Экшены](/docs/basics/Actions.md) 14 | * [Редьюсеры](/docs/basics/Reducers.md) 15 | * [Стор](/docs/basics/Store.md) 16 | * [Поток данных](/docs/basics/DataFlow.md) 17 | * [Использование с React](/docs/basics/UsageWithReact.md) 18 | * [Пример: Todo List](/docs/basics/ExampleTodoList.md) 19 | * [Продвинутое использование](/docs/advanced/README.md) 20 | * [Асинхронные экшены](/docs/advanced/AsyncActions.md) 21 | * [Асинхронные потоки](/docs/advanced/AsyncFlow.md) 22 | * [Мидлвар](/docs/advanced/Middleware.md) 23 | * [Использование с React Router](/docs/advanced/UsageWithReactRouter.md) 24 | * [Пример: Reddit API](/docs/advanced/ExampleRedditAPI.md) 25 | * [Следующие шаги](advanced/NextSteps.md) 26 | * [Рецепты](/docs/recipes/README.md) 27 | * [Мигрирование на Redux](/docs/recipes/MigratingToRedux.md) 28 | * [Использование оператора расширения](/docs/recipes/UsingObjectSpreadOperator.md) 29 | * [Упрощение шаблона](/docs/recipes/ReducingBoilerplate.md) 30 | * [Серверный рендеринг](/docs/recipes/ServerRendering.md) 31 | * [Написание тестов](/docs/recipes/WritingTests.md) 32 | * [Оперирование полученными данными](/docs/recipes/ComputingDerivedData.md) 33 | * [Реализация истории отмен](/docs/recipes/ImplementingUndoHistory.md) 34 | * [Изолирование саб-приложений](/docs/recipes/IsolatingSubapps.md) 35 | * [Структурирование редьюсеров](/docs/recipes/StructuringReducers.md) 36 | * [Предварительные концепциии](/docs/recipes/reducers/PrerequisiteConcepts.md) 37 | * [Базовая структура редьюсера](/docs/recipes/reducers/BasicReducerStructure.md) 38 | * [Разделение логики редьюсера](/docs/recipes/reducers/SplittingReducerLogic.md) 39 | * [Рефакторинг примера редьюсеров](/docs/recipes/reducers/RefactoringReducersExample.md) 40 | * [Использование `combineReducers`](/docs/recipes/reducers/UsingCombineReducers.md) 41 | * [За пределами `combineReducers`](/docs/recipes/reducers/BeyondCombineReducers.md) 42 | * [Нормализация состояния](/docs/recipes/reducers/NormalizingStateShape.md) 43 | * [Обновление нормализованных данных](/docs/recipes/reducers/UpdatingNormalizedData.md) 44 | * [Переиспользование логики редьюсера](/docs/recipes/reducers/ReusingReducerLogic.md) 45 | * [Паттерны иммутабельного обновления](/docs/recipes/reducers/ImmutableUpdatePatterns.md) 46 | * [Инициализирование состояния](/docs/recipes/reducers/InitializingState.md) 47 | * [FAQ](/docs/FAQ.md) 48 | * [Главное](/docs/faq/General.md) 49 | * [Редьюсеры](/docs/faq/Reducers.md) 50 | * [Организация состояния(State)](/docs/faq/OrganizingState.md) 51 | * [Настройка стора(Store)](/docs/faq/StoreSetup.md) 52 | * [Экшены(Actions)](/docs/faq/Actions.md) 53 | * [Структура кода](/docs/faq/CodeStructure.md) 54 | * [Производительность](/docs/faq/Performance.md) 55 | * [Проектные решения](faq/DesignDecisions.md) 56 | * [React Redux](/docs/faq/ReactRedux.md) 57 | * [Разное](/docs/faq/Miscellaneous.md) 58 | * [Поиск неисправностей](/docs/Troubleshooting.md) 59 | * [Глоссарий](/docs/Glossary.md) 60 | * [Справочник по API](/docs/api/README.md) 61 | * [createStore](/docs/api/createStore.md) 62 | * [Store](/docs/api/Store.md) 63 | * [combineReducers](/docs/api/combineReducers.md) 64 | * [applyMiddleware](/docs/api/applyMiddleware.md) 65 | * [bindActionCreators](/docs/api/bindActionCreators.md) 66 | * [compose](/docs/api/compose.md) 67 | * [История изменений](/CHANGELOG.md) 68 | * [Спонсоры](/PATRONS.md) 69 | * [Обратная связь](/docs/Feedback.md) 70 | -------------------------------------------------------------------------------- /docs/usage/structuring-reducers/SplittingReducerLogic.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: splitting-reducer-logic 3 | title: Splitting Reducer Logic 4 | sidebar_label: Splitting Reducer Logic 5 | description: 'Structuring Reducers > Splitting Reducer Logic: Terms for different reducer use cases' 6 | --- 7 | 8 | # Splitting Up Reducer Logic 9 | 10 | For any meaningful application, putting _all_ your update logic into a single reducer function is quickly going to become unmaintainable. While there's no single rule for how long a function should be, it's generally agreed that functions should be relatively short and ideally only do one specific thing. Because of this, it's good programming practice to take pieces of code that are very long or do many different things, and break them into smaller pieces that are easier to understand. 11 | 12 | Since a Redux reducer is _just_ a function, the same concept applies. You can split some of your reducer logic out into another function, and call that new function from the parent function. 13 | 14 | These new functions would typically fall into one of three categories: 15 | 16 | 1. Small utility functions containing some reusable chunk of logic that is needed in multiple places (which may or may not be actually related to the specific business logic) 17 | 2. Functions for handling a specific update case, which often need parameters other than the typical `(state, action)` pair 18 | 3. Functions which handle _all_ updates for a given slice of state. These functions do generally have the typical `(state, action)` parameter signature 19 | 20 | For clarity, these terms will be used to distinguish between different types of functions and different use cases: 21 | 22 | - **_reducer_**: any function with the signature `(state, action) -> newState` (ie, any function that _could_ be used as an argument to `Array.prototype.reduce`) 23 | - **_root reducer_**: the reducer function that is actually passed as the first argument to `createStore`. This is the only part of the reducer logic that _must_ have the `(state, action) -> newState` signature. 24 | - **_slice reducer_**: a reducer that is being used to handle updates to one specific slice of the state tree, usually done by passing it to `combineReducers` 25 | - **_case function_**: a function that is being used to handle the update logic for a specific action. This may actually be a reducer function, or it may require other parameters to do its work properly. 26 | - **_higher-order reducer_**: a function that takes a reducer function as an argument, and/or returns a new reducer function as a result (such as `combineReducers`, or `redux-undo`) 27 | 28 | The term "_sub-reducer_" has also been used in various discussions to mean any function that is not the root reducer, although the term is not very precise. Some people may also refer to some functions as "_business logic_" (functions that relate to application-specific behavior) or "_utility functions_" (generic functions that are not application-specific). 29 | 30 | Breaking down a complex process into smaller, more understandable parts is usually described with the term **_[functional decomposition](https://stackoverflow.com/questions/947874/what-is-functional-decomposition)_**. This term and concept can be applied generically to any code. However, in Redux it is _very_ common to structure reducer logic using approach #3, where update logic is delegated to other functions based on slice of state. Redux refers to this concept as **_reducer composition_**, and it is by far the most widely-used approach to structuring reducer logic. In fact, it's so common that Redux includes a utility function called [`combineReducers()`](../../api/combineReducers.md), which specifically abstracts the process of delegating work to other reducer functions based on slices of state. However, it's important to note that it is not the _only_ pattern that can be used. In fact, it's entirely possible to use all three approaches for splitting up logic into functions, and usually a good idea as well. The [Refactoring Reducers](./RefactoringReducersExample.md) section shows some examples of this in action. 31 | -------------------------------------------------------------------------------- /docs/api/bindActionCreators.md: -------------------------------------------------------------------------------- 1 | # `bindActionCreators(actionCreators, dispatch)` 2 | 3 | Преобразует объект, значениями которого являются [генераторы экшенов](../Glossary.md#action-creator), в объект с теми же ключами, но генераторами экшенов, обернутыми в вызов [`dispatch`](Store.md#dispatch), т.ч. они могут быть вызваны напрямую. 4 | 5 | Как правило, вы просто должны вызвать [`dispatch`](Store.md#dispatch) прямо в вашем инстансе [`Store`](Store.md). Если вы используете Redux c React, то [react-redux](https://github.com/gaearon/react-redux) предоставит вам [`dispatch`](Store.md#dispatch) функцию, т.ч. вы также сможете вызвать его напрямую. 6 | 7 | Единственный случай использования для `bindActionCreators` - это когда вы хотите передать некоторые генераторы экшенов (action creators) вниз к компоненту, который ничего не знает о Redux и вы не хотите передавать ему [`dispatch`](Store.md#dispatch) или Redux стор. 8 | 9 | Для удобства, вы также можете передать одну функцию в качестве первого аргумента и получить функцию в ответ. 10 | 11 | #### Параметры 12 | 13 | 1. `actionCreators` (*Функция* или *Объект*): [Генератор экшена](../Glossary.md#action-creator) или объект, значениями которого являются генераторы экшенов. 14 | 15 | 2. `dispatch` (*Функция*): [`dispatch`](Store.md#dispatch) функция доступная в инстансе [`Store`](Store.md). 16 | 17 | #### Возвращает 18 | 19 | (*Функция* или *Объект*): Объект повторяющий исходный объект, но с функциями непосредственно отправляющими экшен (action), возвращаемый соответствующим генератором экшенов. Если вы передаете единственную функцию, как `actionCreators`, возвращаемое значение также будет единственной функцией. 20 | 21 | #### Пример 22 | 23 | #### `TodoActionCreators.js` 24 | 25 | ```js 26 | export function addTodo(text) { 27 | return { 28 | type: 'ADD_TODO', 29 | text 30 | } 31 | } 32 | 33 | export function removeTodo(id) { 34 | return { 35 | type: 'REMOVE_TODO', 36 | id 37 | } 38 | } 39 | ``` 40 | 41 | #### `SomeComponent.js` 42 | 43 | ```js 44 | import { Component } from 'react' 45 | import { bindActionCreators } from 'redux' 46 | import { connect } from 'react-redux' 47 | 48 | import * as TodoActionCreators from './TodoActionCreators' 49 | console.log(TodoActionCreators) 50 | // { 51 | // addTodo: Function, 52 | // removeTodo: Function 53 | // } 54 | 55 | class TodoListContainer extends Component { 56 | constructor(props) { 57 | super(props); 58 | 59 | const {dispatch} = props; 60 | 61 | // Это хороший случай использования для bindActionCreators: 62 | // Вы хотите, чтобы дочерний компонент, ничего не знал о Redux. 63 | // Теперь мы создаем связанные версии этих функций, чтобы мы могли 64 |     // позже передать их нашему потомку. 65 | 66 | this.boundActionCreators = bindActionCreators(TodoActionCreators, dispatch) 67 | console.log(this.boundActionCreators) 68 | // { 69 | // addTodo: Function, 70 | // removeTodo: Function 71 | // } 72 | } 73 | 74 | componentDidMount() { 75 | // Injected by react-redux: 76 | let { dispatch } = this.props 77 | 78 | // Примечание: так не будет работать: 79 | // TodoActionCreators.addTodo('Use Redux') 80 | 81 | // Вы просто вызываете функцию, которая создает экшен. 82 | // Вы также должны диспатчнуть экшен (action)! 83 | 84 | // Так будет работать: 85 | let action = TodoActionCreators.addTodo('Use Redux') 86 | dispatch(action) 87 | } 88 | 89 | render() { 90 | // Injected by react-redux: 91 | let { todos } = this.props 92 | 93 | return 94 | 95 | // Альтернативой для bindActionCreators может быть передача вниз 96 | // только dispatch функции, но тогда ваш дочерний компонент 97 | // должен импортировать генераторы экшенов и знать о них. 98 | 99 | // return 100 | } 101 | } 102 | 103 | export default connect( 104 | state => ({ todos: state.todos }) 105 | )(TodoListContainer) 106 | ``` 107 | 108 | #### Советы 109 | 110 | * Вы можете спросить: почему мы не привязываем генераторы экшенов сразу к инстансу стора, как в классическом Flux? Проблема в том, что это не будет хорошо работать с универсальными приложениями, которые необходимо рендерить на сервере. Скорее всего, вы хотите иметь отдельный инстанс стора для каждого запроса, чтобы вы могли подготовить их с различными данными, но связывание генераторов экшенов во время их определения, означает, что вы привязаны к одному инстансу стора для всех запросов. 111 | 112 | * Если вы используете ES5, вместо синтаксиса `import * ` вы можете просто передать `require('./TodoActionCreators')` в `bindActionCreators` в качестве первого аргумента. Единственное, что его волнует, чтобы значения аргументов `actionCreators` были функциями. Модульная система не имеет значения. 113 | -------------------------------------------------------------------------------- /docs/faq/Reducers.md: -------------------------------------------------------------------------------- 1 | # Redux FAQ: Редьюсеры 2 | 3 | ## Содержание 4 | 5 | - [Как мне передавать состояние(state) между двумя редьюсерами? Должен ли я использовать combineReducers?](#reducers-share-state) 6 | - [Должен ли я использовать оператор switch для обработки экшенов (actions)?](#reducers-use-switch) 7 | 8 | 9 | 10 | ## Редьюсеры 11 | 12 | 13 | ### Как мне передавать состояние(state) между двумя редьюсерами? Должен ли я использовать `combineReducers`? 14 | 15 | Рекомендуемая структура Redux-стора — это разделение объекта состояния на несколько “частей” или “областей” по ключу, и предоставление отдельной функции-редьюсера для управления каждой частью данных. Это похоже на то, как стандартная Flux структура имеет несколько независимых сторов, а Redux предоставляет утилиту [`комбинация редьюсеров (combineReducers)`](/docs/api/combineReducers.md) для упрощения реализации этого подхода. Однако, важно заметить, что `комбинация редьюсеров` *необязательна* — это просто функция для совместного использования имеющихся редьюсеров (по одному на каждую часть состояния) с простыми JavaScript-объектами для данных. 16 | 17 | У многих пользователей возникает необходимость реализовать обмен данными между двумя редьюсерами, но `комбинация редьюсеров` не позволяет им сделать это. Существует несколько подходов для решения этой задачи: 18 | 19 | * Если редьюсеру нужны данные из другой части состояния, то дерево состояния требуется переорганизовать так, чтобы один редьюсер охватывал больше данных. 20 | * Вам может понадобиться написать некоторые стандартные функции для обработки некоторых из этих экшенов. Это требует обязательной замены `комбинации редьюсеров` на Вашу собственную функцию-редьюсер верхнего порядка. Вы также можете использовать такие утилиты, как [reduce-reducers](https://github.com/acdlite/reduce-reducers), для запуска `комбинации редьюсеров` для обработки большинства экшенов, но также можно запустить более специализированный редьюсер для конкретных экшенов, который скрещивает части состояния. 21 | * [Асинхронные генераторы экшенов](/docs/advanced/AsyncActions.md#async-action-creators), такие как [redux-thunk](https://github.com/gaearon/redux-thunk), имеют доступ ко всему состоянию через `getState()`. Генератор экшенов может извлечь дополнительные данные из состояния и передать их в экшен, таким образом, каждый редьюсер имеет достаточно информации для обновления своей части состояния. 22 | 23 | В общем, помните, что редьюсеры — это всего лишь функции. Вы можете организовать их и разделить по своему усмотрению, и вам следует разбить их на более мелкие переиспользуемые функции (“композиция редьюсеров”). Пока Вы это делаете, Вы можете передавать в нестандартном третьем аргументе необходимые для вычисления следующего состояния дополнительные данные от родительского редьюсера к дочернему. Только Вам надо убедиться, что соблюдается главное правило редьюсеров: `(state, action) => newState`, и состояние не изменяется напрямую. 24 | 25 | #### Дополнительная информация 26 | 27 | **Документация** 28 | - [API: combineReducers](/docs/api/combineReducers.md) 29 | - [Рецепты: Структурирование редьюсеров](/docs/recipes/StructuringReducers.md) 30 | 31 | **Обсуждения** 32 | - [#601: A concern on combineReducers, when an action is related to multiple reducers](https://github.com/reactjs/redux/issues/601) 33 | - [#1400: Is passing top-level state object to branch reducer an anti-pattern?](https://github.com/reactjs/redux/issues/1400) 34 | - [Stack Overflow: Accessing other parts of the state when using combined reducers?](http://stackoverflow.com/questions/34333979/accessing-other-parts-of-the-state-when-using-combined-reducers) 35 | - [Stack Overflow: Reducing an entire subtree with redux combineReducers](http://stackoverflow.com/questions/34427851/reducing-an-entire-subtree-with-redux-combinereducers) 36 | - [Sharing State Between Redux Reducers](https://invalidpatent.wordpress.com/2016/02/18/sharing-state-between-redux-reducers/) 37 | 38 | 39 | 40 | ### Должен ли я использовать оператор `switch` для обработки экшенов (actions) 41 | 42 | Нет, вы можете использовать любой подход, с помощью которого вы бы хотели реагировать на экшенов в редьюсере. Оператор `switch` является наиболее распространенным подходом, но так же отлично подойдет и оператор `if`, таблица соответствия функций или создание функции, которая абстрагирует все это. На самом деле, пока Redux требует, чтобы объект экшенов содержал поле `type`, логике вашего редьюсера даже не придется полагаться на то, чтобы управлять экшеном. 43 | Тем не менее, стандартный подход, безусловно, с помощью инструкции `switch` или таблицы поиска, основанной на `type`. 44 | 45 | 46 | #### Дополнительная информация 47 | 48 | **Документация** 49 | - [Рецепты: Упрощение шаблона](/docs/recipes/ReducingBoilerplate.md) 50 | - [Рецепты: Структурирование редьюсеров - Разделение логики редьюсера](/docs/recipes/reducers/SplittingReducerLogic.md) 51 | 52 | **Обсуждения** 53 | - [#883: take away the huge switch block](https://github.com/reactjs/redux/issues/883) 54 | - [#1167: Reducer without switch](https://github.com/reactjs/redux/issues/1167) 55 | -------------------------------------------------------------------------------- /docs/recipes/reducers/BasicReducerStructure.md: -------------------------------------------------------------------------------- 1 | # Basic Reducer Structure and State Shape 2 | 3 | ## Basic Reducer Structure 4 | First and foremost, it's important to understand that your entire application really only has **one single reducer function**: the function that you've passed into `createStore` as the first argument. That one single reducer function ultimately needs to do several things: 5 | 6 | - The first time the reducer is called, the `state` value will be `undefined`. The reducer needs to handle this case by supplying a default state value before handling the incoming action. 7 | - It needs to look at the previous state and the dispatched action, and determine what kind of work needs to be done 8 | - Assuming actual changes need to occur, it needs to create new objects and arrays with the updated data and return those 9 | - If no changes are needed, it should return the existing state as-is. 10 | 11 | The simplest possible approach to writing reducer logic is to put everything into a single function declaration, like this: 12 | 13 | ```js 14 | function counter(state, action) { 15 | if (typeof state === 'undefined') { 16 | state = 0; // If state is undefined, initialize it with a default value 17 | } 18 | 19 | if (action.type === 'INCREMENT') { 20 | return state + 1; 21 | } 22 | else if (action.type === 'DECREMENT') { 23 | return state - 1; 24 | } 25 | else { 26 | return state; // In case an action is passed in we don't understand 27 | } 28 | } 29 | ``` 30 | 31 | Notice that this simple function fulfills all the basic requirements. It returns a default value if none exists, initializing the store; it determines what sort of update needs to be done based on the type of the action, and returns new values; and it returns the previous state if no work needs to be done. 32 | 33 | There are some simple tweaks that can be made to this reducer. First, repeated `if`/`else` statements quickly grow tiresome, so it's very common to use `switch` statements instead. Second, we can use ES6's default parameter values to handle the initial "no existing data" case. With those changes, the reducer would look like: 34 | 35 | ```js 36 | function counter(state = 0, action) { 37 | switch (action.type) { 38 | case 'INCREMENT': 39 | return state + 1; 40 | case 'DECREMENT': 41 | return state - 1; 42 | default: 43 | return state; 44 | } 45 | } 46 | ``` 47 | 48 | This is the basic structure that a typical Redux reducer function uses. 49 | 50 | ## Basic State Shape 51 | 52 | Redux encourages you to think about your application in terms of the data you need to manage. The data at any given point in time is the "*state*" of your application, and the structure and organization of that state is typically referred to as its "*shape*". The shape of your state plays a major role in how you structure your reducer logic. 53 | 54 | A Redux state usually has a plain Javascript object as the top of the state tree. (It is certainly possible to have another type of data instead, such as a single number, an array, or a specialized data structure, but most libraries assume that the top-level value is a plain object.) The most common way to organize data within that top-level object is to further divide data into sub-trees, where each top-level key represents some "domain" or "slice" of related data. For example, a basic Todo app's state might look like: 55 | 56 | ```js 57 | { 58 | visibilityFilter: 'SHOW_ALL', 59 | todos: [ 60 | { 61 | text: 'Consider using Redux', 62 | completed: true, 63 | }, 64 | { 65 | text: 'Keep all state in a single tree', 66 | completed: false 67 | } 68 | ] 69 | } 70 | ``` 71 | 72 | In this example, `todos` and `visibilityFilter` are both top-level keys in the state, and each represents a "slice" of data for some particular concept. 73 | 74 | Most applications deal with multiple types of data, which can be broadly divided into three categories: 75 | 76 | - _Domain data_: data that the application needs to show, use, or modify (such as "all of the Todos retrieved from the server") 77 | - _App state_: data that is specific to the application's behavior (such as "Todo #5 is currently selected", or "there is a request in progress to fetch Todos") 78 | - _UI state_: data that represents how the UI is currently displayed (such as "The EditTodo modal dialog is currently open") 79 | 80 | 81 | Because the store represents the core of your application, you should **define your state shape in terms of your domain data and app state, not your UI component tree**. As an example, a shape of `state.leftPane.todoList.todos` would be a bad idea, because the idea of "todos" is central to the whole application, not just a single part of the UI. The `todos` slice should be at the top of the state tree instead. 82 | 83 | There will *rarely* be a 1-to-1 correspondence between your UI tree and your state shape. The exception to that might be if you are explicitly tracking various aspects of UI data in your Redux store as well, but even then the shape of the UI data and the shape of the domain data would likely be different. 84 | 85 | A typical app's state shape might look roughly like: 86 | 87 | ```js 88 | { 89 | domainData1 : {}, 90 | domainData2 : {}, 91 | appState1 : {}, 92 | appState2 : {}, 93 | ui : { 94 | uiState1 : {}, 95 | uiState2 : {}, 96 | } 97 | } 98 | ``` -------------------------------------------------------------------------------- /docs/usage/structuring-reducers/BasicReducerStructure.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: basic-reducer-structure 3 | title: Basic Reducer Structure 4 | sidebar_label: Basic Reducer Structure 5 | description: 'Structuring Reducers > Basic Reducer Structure: Overview of how reducer functions work with Redux state' 6 | --- 7 | 8 | # Basic Reducer Structure and State Shape 9 | 10 | ## Basic Reducer Structure 11 | 12 | First and foremost, it's important to understand that your entire application really only has **one single reducer function**: the function that you've passed into `createStore` as the first argument. That one single reducer function ultimately needs to do several things: 13 | 14 | - The first time the reducer is called, the `state` value will be `undefined`. The reducer needs to handle this case by supplying a default state value before handling the incoming action. 15 | - It needs to look at the previous state and the dispatched action, and determine what kind of work needs to be done 16 | - Assuming actual changes need to occur, it needs to create new objects and arrays with the updated data and return those 17 | - If no changes are needed, it should return the existing state as-is. 18 | 19 | The simplest possible approach to writing reducer logic is to put everything into a single function declaration, like this: 20 | 21 | ```js 22 | function counter(state, action) { 23 | if (typeof state === 'undefined') { 24 | state = 0 // If state is undefined, initialize it with a default value 25 | } 26 | 27 | if (action.type === 'INCREMENT') { 28 | return state + 1 29 | } else if (action.type === 'DECREMENT') { 30 | return state - 1 31 | } else { 32 | return state // In case an action is passed in we don't understand 33 | } 34 | } 35 | ``` 36 | 37 | Notice that this simple function fulfills all the basic requirements. It returns a default value if none exists, initializing the store; it determines what sort of update needs to be done based on the type of the action, and returns new values; and it returns the previous state if no work needs to be done. 38 | 39 | There are some simple tweaks that can be made to this reducer. First, repeated `if`/`else` statements quickly grow tiresome, so it's very common to use `switch` statements instead. Second, we can use ES6's default parameter values to handle the initial "no existing data" case. With those changes, the reducer would look like: 40 | 41 | ```js 42 | function counter(state = 0, action) { 43 | switch (action.type) { 44 | case 'INCREMENT': 45 | return state + 1 46 | case 'DECREMENT': 47 | return state - 1 48 | default: 49 | return state 50 | } 51 | } 52 | ``` 53 | 54 | This is the basic structure that a typical Redux reducer function uses. 55 | 56 | ## Basic State Shape 57 | 58 | Redux encourages you to think about your application in terms of the data you need to manage. The data at any given point in time is the "_state_" of your application, and the structure and organization of that state is typically referred to as its "_shape_". The shape of your state plays a major role in how you structure your reducer logic. 59 | 60 | A Redux state usually has a plain Javascript object as the top of the state tree. (It is certainly possible to have another type of data instead, such as a single number, an array, or a specialized data structure, but most libraries assume that the top-level value is a plain object.) The most common way to organize data within that top-level object is to further divide data into sub-trees, where each top-level key represents some "domain" or "slice" of related data. For example, a basic Todo app's state might look like: 61 | 62 | ```js 63 | { 64 | visibilityFilter: 'SHOW_ALL', 65 | todos: [ 66 | { 67 | text: 'Consider using Redux', 68 | completed: true, 69 | }, 70 | { 71 | text: 'Keep all state in a single tree', 72 | completed: false 73 | } 74 | ] 75 | } 76 | ``` 77 | 78 | In this example, `todos` and `visibilityFilter` are both top-level keys in the state, and each represents a "slice" of data for some particular concept. 79 | 80 | Most applications deal with multiple types of data, which can be broadly divided into three categories: 81 | 82 | - _Domain data_: data that the application needs to show, use, or modify (such as "all of the Todos retrieved from the server") 83 | - _App state_: data that is specific to the application's behavior (such as "Todo #5 is currently selected", or "there is a request in progress to fetch Todos") 84 | - _UI state_: data that represents how the UI is currently displayed (such as "The EditTodo modal dialog is currently open") 85 | 86 | Because the store represents the core of your application, you should **define your state shape in terms of your domain data and app state, not your UI component tree**. As an example, a shape of `state.leftPane.todoList.todos` would be a bad idea, because the idea of "todos" is central to the whole application, not just a single part of the UI. The `todos` slice should be at the top of the state tree instead. 87 | 88 | There will _rarely_ be a 1-to-1 correspondence between your UI tree and your state shape. The exception to that might be if you are explicitly tracking various aspects of UI data in your Redux store as well, but even then the shape of the UI data and the shape of the domain data would likely be different. 89 | 90 | A typical app's state shape might look roughly like: 91 | 92 | ```js 93 | { 94 | domainData1 : {}, 95 | domainData2 : {}, 96 | appState1 : {}, 97 | appState2 : {}, 98 | ui : { 99 | uiState1 : {}, 100 | uiState2 : {}, 101 | } 102 | } 103 | ``` 104 | -------------------------------------------------------------------------------- /docs/recipes/reducers/PrerequisiteConcepts.md: -------------------------------------------------------------------------------- 1 | # Prerequisite Reducer Concepts 2 | 3 | 4 | As described in [Reducers](../../basics/Reducers.md), a Redux reducer function: 5 | 6 | - Should have a signature of `(previousState, action) => newState`, similar to the type of function you would pass to [`Array.prototype.reduce(reducer, ?initialValue)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce) 7 | - Should be "pure", which means the reducer: 8 | - Does not _perform side effects_ (such as calling API's or modifying non-local objects or variables). 9 | - Does not _call non-pure functions_ (like `Date.now` or `Math.random`). 10 | - Does not _mutate_ its arguments. If the reducer updates state, it should not _modify_ the **existing** state object in-place. Instead, it should generate a **new** object containing the necessary changes. The same approach should be used for any sub-objects within state that the reducer updates. 11 | 12 | >##### Note on immutability, side effects, and mutation 13 | > Mutation is discouraged because it generally breaks time-travel debugging, and React Redux's `connect` function: 14 | > - For time traveling, the Redux DevTools expect that replaying recorded actions would output a state value, but not change anything else. **Side effects like mutation or asynchronous behavior will cause time travel to alter behavior between steps, breaking the application**. 15 | > - For React Redux, `connect` checks to see if the props returned from a `mapStateToProps` function have changed in order to determine if a component needs to update. To improve performance, `connect` takes some shortcuts that rely on the state being immutable, and uses shallow reference equality checks to detect changes. This means that **changes made to objects and arrays by direct mutation will not be detected, and components will not re-render**. 16 | > 17 | > Other side effects like generating unique IDs or timestamps in a reducer also make the code unpredictable and harder to debug and test. 18 | 19 | 20 | Because of these rules, it's important that the following core concepts are fully understood before moving on to other specific techniques for organizing Redux reducers: 21 | 22 | #### Redux Reducer Basics 23 | 24 | **Key concepts**: 25 | 26 | - Thinking in terms of state and state shape 27 | - Delegating update responsibility by slice of state (*reducer composition*) 28 | - Higher order reducers 29 | - Defining reducer initial state 30 | 31 | **Reading list**: 32 | 33 | - [Redux Docs: Reducers](../../basics/Reducers.md) 34 | - [Redux Docs: Reducing Boilerplate](../ReducingBoilerplate.md) 35 | - [Redux Docs: Implementing Undo History](../ImplementingUndoHistory.md) 36 | - [Redux Docs: `combineReducers`](../../api/combineReducers.md) 37 | - [The Power of Higher-Order Reducers](http://slides.com/omnidan/hor#/) 38 | - [Stack Overflow: Store initial state and `combineReducers`](http://stackoverflow.com/questions/33749759/read-stores-initial-state-in-redux-reducer) 39 | - [Stack Overflow: State key names and `combineReducers`](http://stackoverflow.com/questions/35667775/state-in-redux-react-app-has-a-property-with-the-name-of-the-reducer) 40 | 41 | 42 | #### Pure Functions and Side Effects 43 | 44 | **Key Concepts**: 45 | 46 | - Side effects 47 | - Pure functions 48 | - How to think in terms of combining functions 49 | 50 | **Reading List**: 51 | 52 | - [The Little Idea of Functional Programming](http://jaysoo.ca/2016/01/13/functional-programming-little-ideas/) 53 | - [Understanding Programmatic Side Effects](http://web24studios.com/2015/10/understanding-programmatic-side-effects/) 54 | - [Learning Functional Programming in Javascript](https://youtu.be/e-5obm1G_FY) 55 | - [An Introduction to Reasonably Pure Functional Programming](https://www.sitepoint.com/an-introduction-to-reasonably-pure-functional-programming/) 56 | 57 | 58 | 59 | #### Immutable Data Management 60 | 61 | **Key Concepts**: 62 | 63 | - Mutability vs immutability 64 | - Immutably updating objects and arrays safely 65 | - Avoiding functions and statements that mutate state 66 | 67 | **Reading List**: 68 | 69 | - [Pros and Cons of Using Immutability With React](http://reactkungfu.com/2015/08/pros-and-cons-of-using-immutability-with-react-js/) 70 | - [Javascript and Immutability](http://t4d.io/javascript-and-immutability/) 71 | - [Immutable Data using ES6 and Beyond](http://wecodetheweb.com/2016/02/12/immutable-javascript-using-es6-and-beyond/) 72 | - [Immutable Data from Scratch](https://ryanfunduk.com/articles/immutable-data-from-scratch/) 73 | - [Redux Docs: Using the Object Spread Operator](../UsingObjectSpreadOperator.md) 74 | 75 | 76 | #### Normalizing Data 77 | 78 | **Key Concepts**: 79 | 80 | - Database structure and organization 81 | - Splitting relational/nested data up into separate tables 82 | - Storing a single definition for a given item 83 | - Referring to items by IDs 84 | - Using objects keyed by item IDs as lookup tables, and arrays of IDs to track ordering 85 | - Associating items in relationships 86 | 87 | 88 | **Reading List**: 89 | 90 | - [Database Normalization in Simple English](http://www.essentialsql.com/get-ready-to-learn-sql-database-normalization-explained-in-simple-english/) 91 | - [Idiomatic Redux: Normalizing the State Shape](https://egghead.io/lessons/javascript-redux-normalizing-the-state-shape) 92 | - [Normalizr Documentation](https://github.com/paularmstrong/normalizr) 93 | - [Redux Without Profanity: Normalizr](https://tonyhb.gitbooks.io/redux-without-profanity/content/normalizer.html) 94 | - [Querying a Redux Store](https://medium.com/@adamrackis/querying-a-redux-store-37db8c7f3b0f) 95 | - [Wikipedia: Associative Entity](https://en.wikipedia.org/wiki/Associative_entity) 96 | - [Database Design: Many-to-Many](http://www.tomjewett.com/dbdesign/dbdesign.php?page=manymany.php) 97 | - [Avoiding Accidental Complexity When Structuring Your App State](https://medium.com/@talkol/avoiding-accidental-complexity-when-structuring-your-app-state-6e6d22ad5e2a) 98 | -------------------------------------------------------------------------------- /docs/basics/Actions.md: -------------------------------------------------------------------------------- 1 | # Экшены (Actions) 2 | 3 | Во-первых, давайте определим некоторые экшены. 4 | 5 | **Экшены** — это структуры, которые передают данные из вашего приложения в стор. Они являются *единственными* источниками информации для стора. Вы отправляете их в стор, используя метод [`store.dispatch()`](../api/Store.md#dispatch). 6 | 7 | Например, вот экшен, которое представляет добавление нового пункта в список дел: 8 | 9 | ```js 10 | const ADD_TODO = 'ADD_TODO' 11 | ``` 12 | 13 | ```js 14 | { 15 | type: ADD_TODO, 16 | text: 'Build my first Redux app' 17 | } 18 | ``` 19 | 20 | Экшены — это обычные JavaScript-объекты. Экшены должны иметь поле `type`, которое указывает на тип исполняемого экшена. Типы должны быть, как правило, заданы, как строковые константы. После того, как ваше приложение разрастется, вы можете захотеть переместить их в отдельный модуль. 21 | 22 | ```js 23 | import { ADD_TODO, REMOVE_TODO } from '../actionTypes' 24 | ``` 25 | 26 | >##### Примечание к шаблону разработки 27 | 28 | >Вам не нужно определять константы типа экшенов в отдельном файле или даже определять их и вовсе. Для небольшого проекта было бы проще просто использовать строковые литералы для типов экшенов. Однако есть некоторые преимущества в явном объявлении констант в больших проектах. Прочтите [Reducing Boilerplate](../recipes/ReducingBoilerplate.md) для знакомства с большим количеством практических советов, позволяющих хранить вашу кодовую базу в чистоте. 29 | 30 | Кроме `type`, структуру объекта экшенов вы можете строить на ваше усмотрение. Если вам интересно, изучите [Flux Standard Action](https://github.com/acdlite/flux-standard-action) для рекомендаций о том, как могут строится экшены. 31 | 32 | Мы добавим еще один тип экшена, который будет отмечать задачу, как выполненную. Мы обращаемся к конкретному todo по `index`, потому что мы храним их в виде массива. В реальном приложении разумнее генерировать уникальный ID каждый раз, когда что-то новое создается. 33 | 34 | ```js 35 | { 36 | type: TOGGLE_TODO, 37 | index: 5 38 | } 39 | ``` 40 | 41 | Это хорошая идея, передавать как можно меньше данных в каждом экшене. Например, лучше отправить `index`, чем весь объект todo. 42 | 43 | Наконец, мы добавим еще один тип экшена для изменения видимых, в данный момент, задач. 44 | 45 | ```js 46 | { 47 | type: SET_VISIBILITY_FILTER, 48 | filter: SHOW_COMPLETED 49 | } 50 | ``` 51 | 52 | ## Генераторы экшенов (Action Creators) 53 | 54 | **Генераторы экшенов (Action Creators)** — не что иное, как функции, которые создают экшены. Довольно просто путать термины “action” и “action creator,” поэтому постарайтесь использовать правильный термин. 55 | 56 | В Redux генераторы экшенов (action creators) просто возвращают action: 57 | 58 | ```js 59 | function addTodo(text) { 60 | return { 61 | type: ADD_TODO, 62 | text 63 | } 64 | } 65 | ``` 66 | 67 | Это делает их более переносимыми и легкими для тестирования. 68 | 69 | В [традиционной реализации Flux](http://facebook.github.io/flux), генераторы экшенов (action creators) при выполнении часто вызывают dispatch, примерно так: 70 | 71 | ```js 72 | function addTodoWithDispatch(text) { 73 | const action = { 74 | type: ADD_TODO, 75 | text 76 | } 77 | dispatch(action) 78 | } 79 | ``` 80 | 81 | В Redux это *не* так. 82 | Вместо того чтобы на самом деле начать отправку, передайте результат в функцию `dispatch()`: 83 | 84 | ```js 85 | dispatch(addTodo(text)) 86 | dispatch(completeTodo(index)) 87 | ``` 88 | 89 | Кроме того, вы можете создать **связанный генератор экшена (bound action creator)**, который автоматически запускает отправку экшена: 90 | 91 | ```js 92 | const boundAddTodo = (text) => dispatch(addTodo(text)) 93 | const boundCompleteTodo = (index) => dispatch(completeTodo(index)) 94 | ``` 95 | 96 | Теперь вы можете вызвать его напрямую: 97 | 98 | ``` 99 | boundAddTodo(text) 100 | boundCompleteTodo(index) 101 | ``` 102 | 103 | Доступ к функции `dispatch()` может быть получен непосредственно из стора (store) [`store.dispatch()`](../api/Store.md#dispatch), но, что более вероятно, вы будете получать доступ к ней при помощи чего-то типа `connect()` из [react-redux](http://github.com/gaearon/react-redux). Вы можете использовать функцию [`bindActionCreators()`](../api/bindActionCreators.md) для автоматического привязывания большого количества генераторов экшенов (action creators) к функции `dispatch()`. 104 | 105 | Генератор экшены так же могут быть асинхронными и иметь сайд-эффекты. Вы можете почитать про [асинхронные экшены](../advanced/AsyncActions.md) в [расширенном руководстве](../advanced/README.md), чтобы узнать, как обрабатывать ответы AJAX и создавать генераторы действий в асинхронном потоке управления. Не переходите к асинхронным экшенам до тех пор, пока вы не завершите базовое руководство, так как оно охватывает другие важные концепции, которые необходимы для продвинутого руководства и асинхронных экшенов. 106 | 107 | ## Исходный код 108 | 109 | ### `actions.js` 110 | 111 | ```js 112 | /* 113 | * типы экшенов 114 | */ 115 | 116 | export const ADD_TODO = 'ADD_TODO' 117 | export const TOGGLE_TODO = 'TOGGLE_TODO' 118 | export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER' 119 | 120 | /* 121 | * другие константы 122 | */ 123 | 124 | export const VisibilityFilters = { 125 | SHOW_ALL: 'SHOW_ALL', 126 | SHOW_COMPLETED: 'SHOW_COMPLETED', 127 | SHOW_ACTIVE: 'SHOW_ACTIVE' 128 | } 129 | 130 | /* 131 | * генераторы экшенов 132 | */ 133 | 134 | export function addTodo(text) { 135 | return { type: ADD_TODO, text } 136 | } 137 | 138 | export function toggleTodo(index) { 139 | return { type: TOGGLE_TODO, index } 140 | } 141 | 142 | export function setVisibilityFilter(filter) { 143 | return { type: SET_VISIBILITY_FILTER, filter } 144 | } 145 | ``` 146 | 147 | ## Дальнейшие шаги 148 | 149 | Теперь давайте [создадим несколько редьюсеров](Reducers.md) для того, чтобы описать как будет обновляться состояние (state), когда мы отправляем эти экшены (actions)! 150 | -------------------------------------------------------------------------------- /docs/basics/DataFlow.md: -------------------------------------------------------------------------------- 1 | # Поток данных (Data Flow) 2 | 3 | Архитектура Redux вращается вокруг **строго однонаправленного потока данных**. 4 | 5 | Это значит, что все данные в приложении следуют одному паттерну жизненного цикла, делая логику вашего приложения более предсказуемой и легкой для понимания. Также это способствует большей упорядоченности данных (data normalization), так что в конечном итоге у вас не будет нескольких изолированных копий одних и тех же данных, которые ничего не знают друг о друге. 6 | 7 | Если вы до сих пор не убеждены, прочтите [Мотивацию](../introduction/Motivation.md) и [The Case for Flux](https://medium.com/@dan_abramov/the-case-for-flux-379b7d1982c6) для ознакомления с убедительными аргументами в пользу однонаправленного потока данных. Хотя [Redux — это не совсем Flux](../introduction/PriorArt.md), он дает такие же основные преимущества. 8 | 9 | Жизненный цикл данных в любом Redux-приложении включает в себя 4 шага: 10 | 11 | 1. **Вы вызываете** [`store.dispatch(action)`](../api/Store.md#dispatch). 12 | 13 | Экшен — это простой javascript-объект, который описывает *что случилось*. Например: 14 | 15 | ```js 16 | { type: 'LIKE_ARTICLE', articleId: 42 } 17 | { type: 'FETCH_USER_SUCCESS', response: { id: 3, name: 'Mary' } } 18 | { type: 'ADD_TODO', text: 'Read the Redux docs.' } 19 | ``` 20 | 21 | Думайте о экшене, как об очень коротком фрагменте новостей. "Мэри залайкала статью 42" или "'Прочитать документацию Redux' было добавлено в todo-список". 22 | 23 | Вы можете вызвать [`store.dispatch(action)`](../api/Store.md#dispatch) из любого места Вашего приложения, включая компоненты и XHR-колбеки или даже с запланированными интервалами. 24 | 25 | 2. **Redux-стор вызывает функцию-редьюсер, который вы ему передали.** 26 | 27 | Стор передаст два аргумента при вызове редьюсера: текущее дерево состояния (current state tree) и экшен (action). Например, в todo-приложении главный редьюсер может принимать что-то такое: 28 | 29 | ```js 30 | // Текущее состояние приложения (список дел и выбранный фильтр) 31 | let previousState = { 32 | visibleTodoFilter: 'SHOW_ALL', 33 | todos: [ 34 | { 35 | text: 'Read the docs.', 36 | complete: false 37 | } 38 | ] 39 | } 40 | 41 | // Выполнение экшена (добавление дела) 42 | let action = { 43 | type: 'ADD_TODO', 44 | text: 'Understand the flow.' 45 | } 46 | 47 | // Ваш редьюсер возвращает следующее состояние приложения 48 | let nextState = todoApp(previousState, action) 49 | ``` 50 | 51 | Обратите внимание на то, что редьюсер — это чистая функция. Он только *вычисляет* следующее состояние. Он должен быть совершенно предсказуемым: тип возвращаемых данных не должен меняться, если на вход подаются данные одного типа. Он не должен совершать никаких сайд-эффектов, таких как обращение к API или маршрутизация по приложению. Все это должно происходить только после того, как экшен будет совершен. 52 | 53 | 3. **Главный редьюсер может комбинировать результат работы нескольких редьюсеров в единственное дерево состояния приложения.** 54 | 55 | Каким образом вы будете структурировать главный редьюсер, зависит только от Вас. Redux поставляется с хелпером [`combineReducers()`](../api/combineReducers.md), полезным для "разделения" главного редьюсера на отдельные функции, которые управляют отдельными ветвями дерева состояния. 56 | 57 | [`combineReducers()`](../api/combineReducers.md) работает следующим образом. Допустим, у вас есть два редьюсера: один для списка todo-дел, второй — для выбранного сейчас режима отображения этого списка: 58 | 59 | ```js 60 | function todos(state = [], action) { 61 | // как-то вычисляет nextState... 62 | return nextState 63 | } 64 | 65 | function visibleTodoFilter(state = 'SHOW_ALL', action) { 66 | // как-то вычисляет nextState... 67 | return nextState 68 | } 69 | 70 | let todoApp = combineReducers({ 71 | todos, 72 | visibleTodoFilter 73 | }) 74 | ``` 75 | 76 | Когда вы инициируете экшен, `todoApp`, которое вернул `combineReducers`, вызовет оба редьюсера: 77 | 78 | ```js 79 | let nextTodos = todos(state.todos, action) 80 | let nextVisibleTodoFilter = visibleTodoFilter(state.visibleTodoFilter, action) 81 | ``` 82 | 83 | Затем оба набора состояний будут снова собраны в единое состояние: 84 | 85 | ```js 86 | return { 87 | todos: nextTodos, 88 | visibleTodoFilter: nextVisibleTodoFilter 89 | } 90 | ``` 91 | 92 | Так как [`combineReducers()`](../api/combineReducers.md) — это просто удобная утилита, вы совершено не обязаны ее использовать. Вы можете написать главный редьюсер самостоятельно! 93 | 94 | 95 | 96 | 4. **Redux-стор сохраняет полное дерево состояния, которое возвращает главный редьюсер.** 97 | 98 | Это новое дерево является следующим состоянием Вашего приложения! Каждый слушатель, зарегистрированный с помощью [`store.subscribe(listener)`](../api/Store.md#subscribe), будет вызван. Слушатели могут вызывать [`store.getState()`](../api/Store.md#getState) для получения текущего состояния приложения. 99 | 100 | Теперь UI может быть обновлен для отражения нового состояния приложения. Если вы используете такие биндинги (bindings), как [React Redux](https://github.com/gaearon/react-redux), то это та точка, в которой стоит вызвать `component.setState(newState)` 101 | 102 | ## Следующие шаги 103 | 104 | Теперь, когда вы знаете, как работает Redux, давайте [свяжем его с React приложением](UsageWithReact.md). 105 | 106 | >##### Заметка для опытных пользователей 107 | 108 | > Если Вы уже знакомы с основными концепциями и уже освоили это обучающее руководство, то не забудьте посетить [асинхронный поток (async flow)](../advanced/AsyncFlow.md) в [руководстве для опытных](../advanced/README.md) для изучения того, как именно мидлвары изменяют [асинхронные экшены](../advanced/AsyncActions.md) прежде чем они достигнут редьюсера. 109 | -------------------------------------------------------------------------------- /docs/advanced/NextSteps.md: -------------------------------------------------------------------------------- 1 | # Next Steps 2 | 3 | If you landed in this section, you might be wondering at this point, "what should I do now?". Here is where we provide some essential tips/suggestions on how to diverge from creating trivial TodoMVC apps to a real world application. 4 | 5 | ## Tips & Considerations For The Real World 6 | 7 | Whenever we decide to create a new project, we tend to bypass several aspects that in the future may slow us down. In a real world project we have to consider several things before we start coding, such as: how to configure a `store`, `store` size, data structure, state model, middlewares, environment, async transactions, immutability, etc.. 8 | 9 | The above are some of the main considerations we have to think about beforehand. It's not an easy task, but there are some strategies for making it go smoothly. 10 | 11 | ### UI vs State 12 | 13 | One of the biggest challenges developers face when using Redux is to _describe UI state with data_. The majority of software programs out there are just data transformation, and having the clear understanding that UIs are simply data beautifully presented facilitates the process of building them. 14 | 15 | _Nicolas Hery_ describes it really well in _"[Describing UI state with data](http://nicolashery.com/describing-ui-state-with-data/)"_. Also, it's always good to know _[When to use Redux](https://medium.com/@fastphrase/when-to-use-redux-f0aa70b5b1e2)_, because a lot of times _[You Might Not Need Redux](https://medium.com/@dan_abramov/you-might-not-need-redux-be46360cf367)_ 16 | 17 | ### Configure a Store 18 | 19 | To configure a `store` we have to make major considerations on which middleware to use. There are several libraries out there, but the most popular ones are: 20 | 21 | #### Perform Asynchronous dispatch 22 | 23 | - [redux-thunk](https://github.com/gaearon/redux-thunk) 24 | - Redux Thunk middleware allows you to write action creators that return a function instead of an action. The thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met. It incorporates the methods `dispatch` and `getState` as parameters. 25 | - [redux-saga](https://github.com/redux-saga/redux-saga) 26 | - redux-saga is a library that aims to make the execution of application side effects (e.g., asynchronous tasks like data fetching and impure procedures such as accessing the browser cache) manageable and efficient. It's simple to test, as it uses the ES6 feature called `generators`, making the flow as easy to read as synchronous code. 27 | - [redux-observable](https://github.com/redux-observable/redux-observable) 28 | - redux-observable is a middleware for redux that is inspired by redux-thunk. It allows developers to dispatch a function that returns an `Observable`, `Promise` or `iterable` of action(s). When the observable emits an action, or the promise resolves an action, or the iterable gives an action out, that action is then dispatched as usual. 29 | 30 | #### Development Purposes / debug 31 | 32 | - [redux-devtools](https://github.com/reduxjs/redux-devtools) 33 | - Redux DevTools is a set of tools for your Redux development workflow. 34 | - [redux-logger](https://github.com/evgenyrodionov/redux-logger) 35 | - redux-logger logs all actions that are being dispatched to the store. 36 | 37 | To be able to choose one of these libraries we must take into account whether we are building a small or large application. Usability, code standards, and JavaScript knowledge may also be considered. All of them are similar. 38 | 39 | **Tip**: Think of middlewares as **skills** you give to your `store`. i.e: By attributing the `redux-thunk` to your store, you're giving the `store` the ability to dispatch async actions. 40 | 41 | ### Naming Convention 42 | 43 | A big source of confusion when it comes to a large project is what to name things. This is often just as important as the code itself. Defining a naming convention for your actions at the very beginning of a project and sticking to that convention helps you to scale up as the scope of the project grows. 44 | 45 | Great source: 46 | [A Simple Naming Convention for Action Creators in Redux](https://decembersoft.com/posts/a-simple-naming-convention-for-action-creators-in-redux-js/) 47 | and 48 | [Redux Patterns and Anti-Patterns](https://tech.affirm.com/redux-patterns-and-anti-patterns-7d80ef3d53bc) 49 | 50 | **Tip**: Set up an opinionated code formatter, such as [Prettier](https://github.com/prettier/prettier). 51 | 52 | ### Scalability 53 | 54 | There is no magic to analyze and predict how much your application is going to grow. But it's okay! Redux's simplistic foundation means it will adapt to many kinds of applications as they grow. Here are some resources on how to build up your application in a sensible manner: 55 | 56 | - [Taming Large React Applications with Redux](http://slides.com/joelkanzelmeyer/taming-large-redux-apps#/) 57 | - [Real-World React and Redux - part l](https://dzone.com/articles/real-world-reactjs-and-redux-part-1) 58 | - [Real-World React and Redux - part ll](https://dzone.com/articles/real-world-reactjs-and-redux-part-2) 59 | - [Redux: Architecting and scaling a new web app at the NY Times](https://www.youtube.com/watch?v=lI3IcjFg9Wk) 60 | 61 | **Tip**: It's great to plan things beforehand, but don't get caught up in ["analysis paralysis"](https://en.wikipedia.org/wiki/Analysis_paralysis). Done is always better than perfect, after all. And [Redux makes refactoring easy](https://blog.boldlisting.com/so-youve-screwed-up-your-redux-store-or-why-redux-makes-refactoring-easy-400e19606c71) if you need to. 62 | 63 | With all that being said, the best practice is to keep coding and learning. Participate in [issues](https://github.com/reduxjs/redux/issues) and [StackOverFlow questions](https://stackoverflow.com/questions/tagged/redux). Helping others is a great way of mastering Redux. 64 | 65 | **Tip**: A repository with an extensive amount of content about best practices and Redux architecture is shared by @markerikson at [react-redux-links](https://github.com/markerikson/react-redux-links). -------------------------------------------------------------------------------- /docs/recipes/reducers/ReusingReducerLogic.md: -------------------------------------------------------------------------------- 1 | # Reusing Reducer Logic 2 | 3 | As an application grows, common patterns in reducer logic will start to emerge. You may find several parts of your reducer logic doing the same kinds of work for different types of data, and want to reduce duplication by reusing the same common logic for each data type. Or, you may want to have multiple "instances" of a certain type of data being handled in the store. However, the global structure of a Redux store comes with some trade-offs: it makes it easy to track the overall state of an application, but can also make it harder to "target" actions that need to update a specific piece of state, particularly if you are using `combineReducers`. 4 | 5 | As an example, let's say that we want to track multiple counters in our application, named A, B, and C. We define our initial `counter` reducer, and we use `combineReducers` to set up our state: 6 | 7 | ```js 8 | function counter(state = 0, action) { 9 | switch (action.type) { 10 | case 'INCREMENT': 11 | return state + 1; 12 | case 'DECREMENT': 13 | return state - 1; 14 | default: 15 | return state; 16 | } 17 | } 18 | 19 | const rootReducer = combineReducers({ 20 | counterA : counter, 21 | counterB : counter, 22 | counterC : counter 23 | }); 24 | ``` 25 | 26 | Unfortunately, this setup has a problem. Because `combineReducers` will call each slice reducer with the same action, dispatching `{type : 'INCREMENT'}` will actually cause _all three_ counter values to be incremented, not just one of them. We need some way to wrap the `counter` logic so that we can ensure that only the counter we care about is updated. 27 | 28 | 29 | ## Customizing Behavior with Higher-Order Reducers 30 | 31 | As defined in [Splitting Reducer Logic](SplittingReducerLogic.md), a _higher-order reducer_ is a function that takes a reducer function as an argument, and/or returns a new reducer function as a result. It can also be viewed as a "reducer factory". `combineReducers` is one example of a higher-order reducer. We can use this pattern to create specialized versions of our own reducer functions, with each version only responding to specific actions. 32 | 33 | The two most common ways to specialize a reducer are to generate new action constants with a given prefix or suffix, or to attach additional info inside the action object. Here's what those might look like: 34 | 35 | ```js 36 | function createCounterWithNamedType(counterName = '') { 37 | return function counter(state = 0, action) { 38 | switch (action.type) { 39 | case `INCREMENT_${counterName}`: 40 | return state + 1; 41 | case `DECREMENT_${counterName}`: 42 | return state - 1; 43 | default: 44 | return state; 45 | } 46 | } 47 | } 48 | 49 | function createCounterWithNameData(counterName = '') { 50 | return function counter(state = 0, action) { 51 | const {name} = action; 52 | if(name !== counterName) return state; 53 | 54 | switch (action.type) { 55 | case `INCREMENT`: 56 | return state + 1; 57 | case `DECREMENT`: 58 | return state - 1; 59 | default: 60 | return state; 61 | } 62 | } 63 | } 64 | ``` 65 | 66 | We should now be able to use either of these to generate our specialized counter reducers, and then dispatch actions that will affect the portion of the state that we care about: 67 | 68 | ```js 69 | const rootReducer = combineReducers({ 70 | counterA : createCounterWithNamedType('A'), 71 | counterB : createCounterWithNamedType('B'), 72 | counterC : createCounterWithNamedType('C'), 73 | }); 74 | 75 | store.dispatch({type : 'INCREMENT_B'}); 76 | console.log(store.getState()); 77 | // {counterA : 0, counterB : 1, counterC : 0} 78 | ``` 79 | 80 | 81 | We could also vary the approach somewhat, and create a more generic higher-order reducer that accepts both a given reducer function and a name or identifier: 82 | 83 | ```js 84 | function counter(state = 0, action) { 85 | switch (action.type) { 86 | case 'INCREMENT': 87 | return state + 1; 88 | case 'DECREMENT': 89 | return state - 1; 90 | default: 91 | return state; 92 | } 93 | } 94 | 95 | function createNamedWrapperReducer(reducerFunction, reducerName) { 96 | return (state, action) => { 97 | const {name} = action; 98 | const isInitializationCall = state === undefined; 99 | if(name !== reducerName && !isInitializationCall) return state; 100 | 101 | return reducerFunction(state, action); 102 | } 103 | } 104 | 105 | const rootReducer = combineReducers({ 106 | counterA : createNamedWrapperReducer(counter, 'A'), 107 | counterB : createNamedWrapperReducer(counter, 'B'), 108 | counterC : createNamedWrapperReducer(counter, 'C'), 109 | }); 110 | ``` 111 | 112 | You could even go as far as to make a generic filtering higher-order reducer: 113 | 114 | ```js 115 | function createFilteredReducer(reducerFunction, reducerPredicate) { 116 | return (state, action) => { 117 | const isInitializationCall = state === undefined; 118 | const shouldRunWrappedReducer = reducerPredicate(action) || isInitializationCall; 119 | return shouldRunWrappedReducer ? reducerFunction(state, action) : state; 120 | } 121 | } 122 | 123 | const rootReducer = combineReducers({ 124 | // check for suffixed strings 125 | counterA : createFilteredReducer(counter, action => action.type.endsWith('_A')), 126 | // check for extra data in the action 127 | counterB : createFilteredReducer(counter, action => action.name === 'B'), 128 | // respond to all 'INCREMENT' actions, but never 'DECREMENT' 129 | counterC : createFilteredReducer(counter, action => action.type === 'INCREMENT') 130 | }; 131 | ``` 132 | 133 | 134 | These basic patterns allow you to do things like having multiple instances of a smart connected component within the UI, or reuse common logic for generic capabilities such as pagination or sorting. 135 | -------------------------------------------------------------------------------- /docs/usage/structuring-reducers/PrerequisiteConcepts.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: prerequisite-concepts 3 | title: Prerequisite Concepts 4 | sidebar_label: Prerequisite Concepts 5 | description: 'Structuring Reducers > Prerequisite Concepts: Key concepts to understand when using Redux' 6 | --- 7 | 8 | # Prerequisite Reducer Concepts 9 | 10 | As described in ["Redux Fundamentals" Part 3: State, Actions, and Reducers](../../tutorials/fundamentals/part-3-state-actions-reducers.md), a Redux reducer function: 11 | 12 | - Should have a signature of `(previousState, action) => newState`, similar to the type of function you would pass to [`Array.prototype.reduce(reducer, ?initialValue)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce) 13 | - Should be "pure", which means the reducer: 14 | - Does not _perform side effects_ (such as calling API's or modifying non-local objects or variables). 15 | - Does not _call non-pure functions_ (like `Date.now` or `Math.random`). 16 | - Does not _mutate_ its arguments. If the reducer updates state, it should not _modify_ the **existing** state object in-place. Instead, it should generate a **new** object containing the necessary changes. The same approach should be used for any sub-objects within state that the reducer updates. 17 | 18 | > ##### Note on immutability, side effects, and mutation 19 | > 20 | > Mutation is discouraged because it generally breaks time-travel debugging, and React Redux's `connect` function: 21 | > 22 | > - For time traveling, the Redux DevTools expect that replaying recorded actions would output a state value, but not change anything else. **Side effects like mutation or asynchronous behavior will cause time travel to alter behavior between steps, breaking the application**. 23 | > - For React Redux, `connect` checks to see if the props returned from a `mapStateToProps` function have changed in order to determine if a component needs to update. To improve performance, `connect` takes some shortcuts that rely on the state being immutable, and uses shallow reference equality checks to detect changes. This means that **changes made to objects and arrays by direct mutation will not be detected, and components will not re-render**. 24 | > 25 | > Other side effects like generating unique IDs or timestamps in a reducer also make the code unpredictable and harder to debug and test. 26 | 27 | Because of these rules, it's important that the following core concepts are fully understood before moving on to other specific techniques for organizing Redux reducers: 28 | 29 | #### Redux Reducer Basics 30 | 31 | **Key concepts**: 32 | 33 | - Thinking in terms of state and state shape 34 | - Delegating update responsibility by slice of state (_reducer composition_) 35 | - Higher order reducers 36 | - Defining reducer initial state 37 | 38 | **Reading list**: 39 | 40 | - ["Redux Fundamentals" Part 3: State, Actions, and Reducers](../../tutorials/fundamentals/part-3-state-actions-reducers.md) 41 | - [Redux Docs: Reducing Boilerplate](../ReducingBoilerplate.md) 42 | - [Redux Docs: Implementing Undo History](../ImplementingUndoHistory.md) 43 | - [Redux Docs: `combineReducers`](../../api/combineReducers.md) 44 | - [The Power of Higher-Order Reducers](https://slides.com/omnidan/hor#/) 45 | - [Stack Overflow: Store initial state and `combineReducers`](https://stackoverflow.com/questions/33749759/read-stores-initial-state-in-redux-reducer) 46 | - [Stack Overflow: State key names and `combineReducers`](https://stackoverflow.com/questions/35667775/state-in-redux-react-app-has-a-property-with-the-name-of-the-reducer) 47 | 48 | #### Pure Functions and Side Effects 49 | 50 | **Key Concepts**: 51 | 52 | - Side effects 53 | - Pure functions 54 | - How to think in terms of combining functions 55 | 56 | **Reading List**: 57 | 58 | - [The Little Idea of Functional Programming](http://jaysoo.ca/2016/01/13/functional-programming-little-ideas/) 59 | - [Understanding Programmatic Side-Effects](https://c2fo.io/c2fo/programming/2016/05/11/understanding-programmatic-side-effects/) 60 | - [Learning Functional Programming in Javascript](https://youtu.be/e-5obm1G_FY) 61 | - [An Introduction to Reasonably Pure Functional Programming](https://www.sitepoint.com/an-introduction-to-reasonably-pure-functional-programming/) 62 | 63 | #### Immutable Data Management 64 | 65 | **Key Concepts**: 66 | 67 | - Mutability vs immutability 68 | - Immutably updating objects and arrays safely 69 | - Avoiding functions and statements that mutate state 70 | 71 | **Reading List**: 72 | 73 | - [Pros and Cons of Using Immutability With React](https://reactkungfu.com/2015/08/pros-and-cons-of-using-immutability-with-react-js/) 74 | - [Immutable Data using ES6 and Beyond](https://wecodetheweb.com/2016/02/12/immutable-javascript-using-es6-and-beyond/) 75 | - [Immutable Data from Scratch](https://ryanfunduk.com/articles/immutable-data-from-scratch/) 76 | - [Redux Docs: Using the Object Spread Operator](../UsingObjectSpreadOperator.md) 77 | 78 | #### Normalizing Data 79 | 80 | **Key Concepts**: 81 | 82 | - Database structure and organization 83 | - Splitting relational/nested data up into separate tables 84 | - Storing a single definition for a given item 85 | - Referring to items by IDs 86 | - Using objects keyed by item IDs as lookup tables, and arrays of IDs to track ordering 87 | - Associating items in relationships 88 | 89 | **Reading List**: 90 | 91 | - [Database Normalization in Simple English](https://www.essentialsql.com/get-ready-to-learn-sql-database-normalization-explained-in-simple-english/) 92 | - [Idiomatic Redux: Normalizing the State Shape](https://egghead.io/lessons/javascript-redux-normalizing-the-state-shape) 93 | - [Normalizr Documentation](https://github.com/paularmstrong/normalizr) 94 | - [Redux Without Profanity: Normalizr](https://tonyhb.gitbooks.io/redux-without-profanity/content/normalizer.html) 95 | - [Querying a Redux Store](https://medium.com/@adamrackis/querying-a-redux-store-37db8c7f3b0f) 96 | - [Wikipedia: Associative Entity](https://en.wikipedia.org/wiki/Associative_entity) 97 | - [Database Design: Many-to-Many](https://web.csulb.edu/colleges/coe/cecs/dbdesign/dbdesign.php?page=manymany.php) 98 | - [Avoiding Accidental Complexity When Structuring Your App State](https://medium.com/@talkol/avoiding-accidental-complexity-when-structuring-your-app-state-6e6d22ad5e2a) 99 | -------------------------------------------------------------------------------- /docs/recipes/reducers/UsingCombineReducers.md: -------------------------------------------------------------------------------- 1 | # Using `combineReducers` 2 | 3 | ## Core Concepts 4 | 5 | 6 | The most common state shape for a Redux app is a plain Javascript object containing "slices" of domain-specific data at each top-level key. Similarly, the most common approach to writing reducer logic for that state shape is to have "slice reducer" functions, each with the same `(state, action)` signature, and each responsible for managing all updates to that specific slice of state. Multiple slice reducers can respond to the same action, independently update their own slice as needed, and the updated slices are combined into the new state object. 7 | 8 | Because this pattern is so common, Redux provides the `combineReducers` utility to implement that behavior. It is an example of a _higher-order reducer_, which takes an object full of slice reducer functions, and returns a new reducer function. 9 | 10 | There are several important ideas to be aware of when using `combineReducers`: 11 | 12 | 13 | - First and foremost, `combineReducers` is simply **a utility function to simplify the most common use case when writing Redux reducers**. You are *not* required to use it in your own application, and it does *not* handle every possible scenario. It is entirely possible to write reducer logic without using it, and it is quite common to need to write custom reducer logic for cases that `combineReducer` does not handle. (See [Beyond `combineReducers`](./BeyondCombineReducers.md) for examples and suggestions.) 14 | - While Redux itself is not opinionated about how your state is organized, `combineReducers` enforces several rules to help users avoid common errors. (See [`combineReducers`](../../api/combineReducers.md) for details.) 15 | - One frequently asked question is whether Redux "calls all reducers" when dispatching an action. Since there really is only one root reducer function, the default answer is "no, it does not". However, `combineReducers` has specific behavior that _does_ work that way. In order to assemble the new state tree, `combineReducers` will call each slice reducer with its current slice of state and the current action, giving the slice reducer a chance to respond and update its slice of state if needed. So, in that sense, using `combineReducers` _does_ "call all reducers", or at least all of the slice reducers it is wrapping. 16 | - You can use it at all levels of your reducer structure, not just to create the root reducer. It's very common to have multiple combined reducers in various places, which are composed together to create the root reducer. 17 | 18 | 19 | ## Defining State Shape 20 | 21 | There are two ways to define the initial shape and contents of your store's state. First, the `createStore` function can take `preloadedState` as its second argument. This is primarily intended for initializing the store with state that was previously persisted elsewhere, such as the browser's localStorage. The other way is for the root reducer to return the initial state value when the state argument is `undefined`. These two approaches are described in more detail in [Initializing State](./InitializingState.md), but there are some additional concerns to be aware of when using `combineReducers`. 22 | 23 | `combineReducers` takes an object full of slice reducer functions, and creates a function that outputs a corresponding state object with the same keys. This means that if no preloaded state is provided to `createStore`, the naming of the keys in the input slice reducer object will define the naming of the keys in the output state object. The correlation between these names is not always apparent, especially when using ES6 features such as default module exports and object literal shorthands. 24 | 25 | Here's an example of how use of ES6 object literal shorthand with `combineReducers` can define the state shape: 26 | 27 | ```js 28 | // reducers.js 29 | export default theDefaultReducer = (state = 0, action) => state; 30 | 31 | export const firstNamedReducer = (state = 1, action) => state; 32 | 33 | export const secondNamedReducer = (state = 2, action) => state; 34 | 35 | 36 | // rootReducer.js 37 | import {combineReducers, createStore} from "redux"; 38 | 39 | import theDefaultReducer, {firstNamedReducer, secondNamedReducer} from "./reducers"; 40 | 41 | // Use ES6 object literal shorthand syntax to define the object shape 42 | const rootReducer = combineReducers({ 43 | theDefaultReducer, 44 | firstNamedReducer, 45 | secondNamedReducer 46 | }); 47 | 48 | const store = createStore(rootReducer); 49 | console.log(store.getState()); 50 | // {theDefaultReducer : 0, firstNamedReducer : 1, secondNamedReducer : 2} 51 | ``` 52 | 53 | Notice that because we used the ES6 shorthand for defining an object literal, the key names in the resulting state are the same as the variable names from the imports. This may not always be the desired behavior, and is often a cause of confusion for those who aren't as familiar with ES6 syntax. 54 | 55 | Also, the resulting names are a bit odd. It's generally not a good practice to actually include words like "reducer" in your state key names - the keys should simply reflect the domain or type of data they hold. This means we should either explicitly specify the names of the keys in the slice reducer object to define the keys in the output state object, or carefully rename the variables for the imported slice reducers to set up the keys when using the shorthand object literal syntax. 56 | 57 | A better usage might look like: 58 | 59 | ```js 60 | import {combineReducers, createStore} from "redux"; 61 | 62 | // Rename the default import to whatever name we want. We can also rename a named import. 63 | import defaultState, {firstNamedReducer, secondNamedReducer as secondState} from "./reducers"; 64 | 65 | const rootReducer = combineReducers({ 66 | defaultState, // key name same as the carefully renamed default export 67 | firstState : firstNamedReducer, // specific key name instead of the variable name 68 | secondState, // key name same as the carefully renamed named export 69 | }); 70 | 71 | const reducerInitializedStore = createStore(rootReducer); 72 | console.log(reducerInitializedStore.getState()); 73 | // {defaultState : 0, firstState : 1, secondState : 2} 74 | ``` 75 | 76 | This state shape better reflects the data involved, because we took care to set up the keys we passed to `combineReducers`. 77 | -------------------------------------------------------------------------------- /docs/usage/structuring-reducers/UsingCombineReducers.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: using-combinereducers 3 | title: Using combineReducers 4 | description: 'Structuring Reducers > Using combineReducers: Explanations of how combineReducers works in practice' 5 | hide_title: true 6 | --- 7 | 8 |   9 | 10 | # Using `combineReducers` 11 | 12 | ## Core Concepts 13 | 14 | The most common state shape for a Redux app is a plain Javascript object containing "slices" of domain-specific data at each top-level key. Similarly, the most common approach to writing reducer logic for that state shape is to have "slice reducer" functions, each with the same `(state, action)` signature, and each responsible for managing all updates to that specific slice of state. Multiple slice reducers can respond to the same action, independently update their own slice as needed, and the updated slices are combined into the new state object. 15 | 16 | Because this pattern is so common, Redux provides the `combineReducers` utility to implement that behavior. It is an example of a _higher-order reducer_, which takes an object full of slice reducer functions, and returns a new reducer function. 17 | 18 | There are several important ideas to be aware of when using `combineReducers`: 19 | 20 | - First and foremost, `combineReducers` is simply **a utility function to simplify the most common use case when writing Redux reducers**. You are _not_ required to use it in your own application, and it does _not_ handle every possible scenario. It is entirely possible to write reducer logic without using it, and it is quite common to need to write custom reducer logic for cases that `combineReducer` does not handle. (See [Beyond `combineReducers`](./BeyondCombineReducers.md) for examples and suggestions.) 21 | - While Redux itself is not opinionated about how your state is organized, `combineReducers` enforces several rules to help users avoid common errors. (See [`combineReducers`](../../api/combineReducers.md) for details.) 22 | - One frequently asked question is whether Redux "calls all reducers" when dispatching an action. Since there really is only one root reducer function, the default answer is "no, it does not". However, `combineReducers` has specific behavior that _does_ work that way. In order to assemble the new state tree, `combineReducers` will call each slice reducer with its current slice of state and the current action, giving the slice reducer a chance to respond and update its slice of state if needed. So, in that sense, using `combineReducers` _does_ "call all reducers", or at least all of the slice reducers it is wrapping. 23 | - You can use it at all levels of your reducer structure, not just to create the root reducer. It's very common to have multiple combined reducers in various places, which are composed together to create the root reducer. 24 | 25 | ## Defining State Shape 26 | 27 | There are two ways to define the initial shape and contents of your store's state. First, the `createStore` function can take `preloadedState` as its second argument. This is primarily intended for initializing the store with state that was previously persisted elsewhere, such as the browser's localStorage. The other way is for the root reducer to return the initial state value when the state argument is `undefined`. These two approaches are described in more detail in [Initializing State](./InitializingState.md), but there are some additional concerns to be aware of when using `combineReducers`. 28 | 29 | `combineReducers` takes an object full of slice reducer functions, and creates a function that outputs a corresponding state object with the same keys. This means that if no preloaded state is provided to `createStore`, the naming of the keys in the input slice reducer object will define the naming of the keys in the output state object. The correlation between these names is not always apparent, especially when using ES6 features such as default module exports and object literal shorthands. 30 | 31 | Here's an example of how use of ES6 object literal shorthand with `combineReducers` can define the state shape: 32 | 33 | ```js 34 | // reducers.js 35 | export default theDefaultReducer = (state = 0, action) => state 36 | 37 | export const firstNamedReducer = (state = 1, action) => state 38 | 39 | export const secondNamedReducer = (state = 2, action) => state 40 | 41 | // rootReducer.js 42 | import { combineReducers, createStore } from 'redux' 43 | 44 | import theDefaultReducer, { 45 | firstNamedReducer, 46 | secondNamedReducer 47 | } from './reducers' 48 | 49 | // Use ES6 object literal shorthand syntax to define the object shape 50 | const rootReducer = combineReducers({ 51 | theDefaultReducer, 52 | firstNamedReducer, 53 | secondNamedReducer 54 | }) 55 | 56 | const store = createStore(rootReducer) 57 | console.log(store.getState()) 58 | // {theDefaultReducer : 0, firstNamedReducer : 1, secondNamedReducer : 2} 59 | ``` 60 | 61 | Notice that because we used the ES6 shorthand for defining an object literal, the key names in the resulting state are the same as the variable names from the imports. This may not always be the desired behavior, and is often a cause of confusion for those who aren't as familiar with ES6 syntax. 62 | 63 | Also, the resulting names are a bit odd. It's generally not a good practice to actually include words like "reducer" in your state key names - the keys should simply reflect the domain or type of data they hold. This means we should either explicitly specify the names of the keys in the slice reducer object to define the keys in the output state object, or carefully rename the variables for the imported slice reducers to set up the keys when using the shorthand object literal syntax. 64 | 65 | A better usage might look like: 66 | 67 | ```js 68 | import { combineReducers, createStore } from 'redux' 69 | 70 | // Rename the default import to whatever name we want. We can also rename a named import. 71 | import defaultState, { 72 | firstNamedReducer, 73 | secondNamedReducer as secondState 74 | } from './reducers' 75 | 76 | const rootReducer = combineReducers({ 77 | defaultState, // key name same as the carefully renamed default export 78 | firstState: firstNamedReducer, // specific key name instead of the variable name 79 | secondState // key name same as the carefully renamed named export 80 | }) 81 | 82 | const reducerInitializedStore = createStore(rootReducer) 83 | console.log(reducerInitializedStore.getState()) 84 | // {defaultState : 0, firstState : 1, secondState : 2} 85 | ``` 86 | 87 | This state shape better reflects the data involved, because we took care to set up the keys we passed to `combineReducers`. 88 | -------------------------------------------------------------------------------- /docs/api/combineReducers.md: -------------------------------------------------------------------------------- 1 | # `combineReducers(reducers)` 2 | 3 | Как только ваше приложение становится все более сложным, вы захотите разделить ваш [функцию редьюсер](../Glossary.md#reducer) на отдельные функции, которые управляют независимыми частями [состояния](../Glossary.md#state). 4 | 5 | Вспомогательная функция `combineReducers` преобразует объект, значениями которого являются различные функции редьюсеры, в одну функцию редьюсер, которую можно передать в метод [`createStore`](createStore.md). 6 | 7 | Результирующий редьюсер вызывает вложенные редьюсеры и собирает их результаты в единый объект состояния. 8 | **Состояние, созданное именами `combineReducers()`, сохраняет состояние каждого редуктора под их ключами, переданные в `combineReducers()`** 9 | 10 | Пример: 11 | ``` 12 | rootReducer = combineReducers({potato: potatoReducer, tomato: tomatoReducer}) 13 | // Это создаст следующий объект состояния 14 | { 15 | potato: { 16 | // ... potatoes и другое состояние управляемое potatoReducer ... 17 | }, 18 | tomato: { 19 | // ... tomatoes и другое состояние управляемое tomatoReducer, возможно, какой-нибудь хороший соус? ... 20 | } 21 | } 22 | ``` 23 | 24 | Вы можете управлять именами ключей состояний, используя разные ключи для редьюсеров в переданном объекте. Например, вы можете называть `combineReducers({ todos: myTodosReducer, counter: myCounterReducer })` для формы состояния как `{todos, counter}`. 25 | 26 | Популярное соглашение - назвать редьюсеры после разделения состояний, которыми они управляют, поэтому вы можете использовать сокращенную обозначение свойства ES6: `combReducers ({counter, todos})`. Это эквивалентно написанию `combReducers ({counter: counter, todos: todos})`. 27 | 28 | 29 | > ##### Примечания для пользователей Flux 30 | > 31 | > Эта функция поможет вам организовать ваши редьюсеры для управления их собственными частями состояния, подобно тому, как вы бы имели различные Flux сторы для управления разными состояниями. С Redux у вас есть только один стор, но `combineReducers` помогает вам сохранять такое же логическое разделение между редьюсерами. 32 | 33 | #### Параметры 34 | 35 | 1. `reducers` (*Object*): объект, значения которого соответствуют различным функциям редьюсерам, которые должны быть объединены в один. Ниже идут примечания для некоторых правил, которым должен следовать каждый переданный редьюсер. 36 | 37 | > Ранее документация предлагала использовать ES6-синтаксис `import * as reducers` для получения объекта редьюсеров. Это было источником многочисленной путаницы, поэтому сейчас рекомендуется экспортировать один редьюсер, полученный с помощью `combineReducers()` из `reducers/index.js` вместо этого. Ниже приведен пример. 38 | 39 | #### Возвращает 40 | 41 | (* Function *): редьюсер, который вызывает каждый редьюсер внутри объекта `reducers` и создает объект состояния с той же формой. 42 | 43 | #### Примечания 44 | 45 | Эта функция слегка самоуверенная и искажена, чтобы помочь новичкам избежать общих ошибок. Именно поэтому она пытается применять некоторые правила, которым не нужно следовать, если вы пишете корневой редьюсер вручную. 46 | 47 | Любой редьюсер передаваемый `combineReducers` должен соответствовать этим правилам: 48 | 49 | * Для любых экшенов, которые не определены, он должен возвращать `state`, переданный ему в качестве первого аргумента. 50 | 51 | * Он никогда не должен возвращать ` undefined `. Это очень легко сделать по ошибке через предыдущие `return`, поэтому `combineReducers` создает исключение, если вы сделали это, предотвращая появление ошибки где-нибудь еще. 52 | 53 | * Если `state` переданный ему ` не определен (undefined)`, то он должен возвратить начальное состояние (state) для этого конкретного редьюсера. Согласно предыдущему правилу, начальное состояние (state) не должно быть равно `undefined`. Это удобно указывать с ES6-синтаксисом опциональных аргументов, но вы можете также явно проверить первый аргумент на `undefined`. 54 | 55 | В то время как `combineReducers` пытается проверить, что ваши редьюсеры соответствуют некоторым из этих правил, вам следует помнить о них и сделать все возможное, чтобы следовать им. `combineReducers` проверит ваши редьюсеры, передав им` undefined`; это делается, даже если вы указываете начальное состояние на `Redux.createStore(combineReducers(...), initialState)`. Поэтому вы **должны** гарантировать, что ваши редьюсеры будут работать должным образом при получении `undefined` в качестве state, даже если вы никогда не намереваетесь фактически получить` undefined` в своем собственном коде. 56 | 57 | #### Example 58 | 59 | #### `reducers/todos.js` 60 | 61 | #### `reducers/todos.js` 62 | 63 | ```js 64 | export default function todos(state = [], action) { 65 | switch (action.type) { 66 | case 'ADD_TODO': 67 | return state.concat([action.text]) 68 | default: 69 | return state 70 | } 71 | } 72 | ``` 73 | 74 | #### `reducers/counter.js` 75 | 76 | ```js 77 | export default function counter(state = 0, action) { 78 | switch (action.type) { 79 | case 'INCREMENT': 80 | return state + 1 81 | case 'DECREMENT': 82 | return state - 1 83 | default: 84 | return state 85 | } 86 | } 87 | ``` 88 | 89 | #### `reducers/index.js` 90 | 91 | ```js 92 | import { combineReducers } from 'redux' 93 | import todos from './todos' 94 | import counter from './counter' 95 | 96 | export default combineReducers({ 97 | todos, 98 | counter 99 | }) 100 | ``` 101 | 102 | #### `App.js` 103 | 104 | ```js 105 | import { createStore } from 'redux' 106 | import reducer from './reducers/index' 107 | 108 | const store = createStore(reducer) 109 | console.log(store.getState()) 110 | // { 111 | // counter: 0, 112 | // todos: [] 113 | // } 114 | 115 | store.dispatch({ 116 | type: 'ADD_TODO', 117 | text: 'Use Redux' 118 | }) 119 | console.log(store.getState()) 120 | // { 121 | // counter: 0, 122 | // todos: [ 'Use Redux' ] 123 | // } 124 | ``` 125 | 126 | #### Советы 127 | 128 | * Этот помощник – это всего лишь удобство! Вы можете написать свой собственный `combineReducers` который [работает иначе](https://github.com/acdlite/reduce-reducers) или даже вручную собрать объект состояния из вложенного редьюсера и написать родительскую функцию-редьюсер явно, как можно было бы написать любую другую функцию. 129 | 130 | * Вы можете вызвать `combineReducers` на любом уровне иерархии редьюсера. Это не обязательно должно произойти наверху. На самом деле, вы можете использовать его снова, чтобы разделить "детские" редьюсеры, которые получаются слишком сложными, на независимых "внуков" и так далее. 131 | -------------------------------------------------------------------------------- /docs/usage/CodeSplitting.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: code-splitting 3 | title: Code Splitting 4 | --- 5 | 6 | # Code Splitting 7 | 8 | In large web applications, it is often desirable to split up the app code into multiple JS bundles that can be loaded on-demand. This strategy, called 'code splitting', helps to increase performance of your application by reducing the size of the initial JS payload that must be fetched. 9 | 10 | To code split with Redux, we want to be able to dynamically add reducers to the store. However, Redux really only has a single root reducer function. This root reducer is normally generated by calling `combineReducers()` or a similar function when the application is initialized. In order to dynamically add more reducers, we need to call that function again to re-generate the root reducer. Below, we discuss some approaches to solving this problem and reference two libraries that provide this functionality. 11 | 12 | ## Basic Principle 13 | 14 | ### Using `replaceReducer` 15 | 16 | The Redux store exposes a `replaceReducer` function, which replaces the current active root reducer function with a new root reducer function. Calling it will swap the internal reducer function reference, and dispatch an action to help any newly-added slice reducers initialize themselves: 17 | 18 | ```js 19 | const newRootReducer = combineReducers({ 20 | existingSlice: existingSliceReducer, 21 | newSlice: newSliceReducer 22 | }) 23 | 24 | store.replaceReducer(newRootReducer) 25 | ``` 26 | 27 | ## Reducer Injection Approaches 28 | 29 | ### Defining an `injectReducer` function 30 | 31 | We will likely want to call `store.replaceReducer()` from anywhere in the application. Because of that, it's helpful 32 | to define a reusable `injectReducer()` function that keeps references to all of the existing slice reducers, and attach 33 | that to the store instance. 34 | 35 | ```js 36 | import { createStore } from 'redux' 37 | 38 | // Define the Reducers that will always be present in the application 39 | const staticReducers = { 40 | users: usersReducer, 41 | posts: postsReducer 42 | } 43 | 44 | // Configure the store 45 | export default function configureStore(initialState) { 46 | const store = createStore(createReducer(), initialState) 47 | 48 | // Add a dictionary to keep track of the registered async reducers 49 | store.asyncReducers = {} 50 | 51 | // Create an inject reducer function 52 | // This function adds the async reducer, and creates a new combined reducer 53 | store.injectReducer = (key, asyncReducer) => { 54 | store.asyncReducers[key] = asyncReducer 55 | store.replaceReducer(createReducer(store.asyncReducers)) 56 | } 57 | 58 | // Return the modified store 59 | return store 60 | } 61 | 62 | function createReducer(asyncReducers) { 63 | return combineReducers({ 64 | ...staticReducers, 65 | ...asyncReducers 66 | }) 67 | } 68 | ``` 69 | 70 | Now, one just needs to call `store.injectReducer` to add a new reducer to the store. 71 | 72 | ### Using a 'Reducer Manager' 73 | 74 | Another approach is to create a 'Reducer Manager' object, which keeps track of all the registered reducers and exposes a `reduce()` function. Consider the following example: 75 | 76 | ```js 77 | export function createReducerManager(initialReducers) { 78 | // Create an object which maps keys to reducers 79 | const reducers = { ...initialReducers } 80 | 81 | // Create the initial combinedReducer 82 | let combinedReducer = combineReducers(reducers) 83 | 84 | // An array which is used to delete state keys when reducers are removed 85 | let keysToRemove = [] 86 | 87 | return { 88 | getReducerMap: () => reducers, 89 | 90 | // The root reducer function exposed by this object 91 | // This will be passed to the store 92 | reduce: (state, action) => { 93 | // If any reducers have been removed, clean up their state first 94 | if (keysToRemove.length > 0) { 95 | state = { ...state } 96 | for (let key of keysToRemove) { 97 | delete state[key] 98 | } 99 | keysToRemove = [] 100 | } 101 | 102 | // Delegate to the combined reducer 103 | return combinedReducer(state, action) 104 | }, 105 | 106 | // Adds a new reducer with the specified key 107 | add: (key, reducer) => { 108 | if (!key || reducers[key]) { 109 | return 110 | } 111 | 112 | // Add the reducer to the reducer mapping 113 | reducers[key] = reducer 114 | 115 | // Generate a new combined reducer 116 | combinedReducer = combineReducers(reducers) 117 | }, 118 | 119 | // Removes a reducer with the specified key 120 | remove: key => { 121 | if (!key || !reducers[key]) { 122 | return 123 | } 124 | 125 | // Remove it from the reducer mapping 126 | delete reducers[key] 127 | 128 | // Add the key to the list of keys to clean up 129 | keysToRemove.push(key) 130 | 131 | // Generate a new combined reducer 132 | combinedReducer = combineReducers(reducers) 133 | } 134 | } 135 | } 136 | 137 | const staticReducers = { 138 | users: usersReducer, 139 | posts: postsReducer 140 | } 141 | 142 | export function configureStore(initialState) { 143 | const reducerManager = createReducerManager(staticReducers) 144 | 145 | // Create a store with the root reducer function being the one exposed by the manager. 146 | const store = createStore(reducerManager.reduce, initialState) 147 | 148 | // Optional: Put the reducer manager on the store so it is easily accessible 149 | store.reducerManager = reducerManager 150 | } 151 | ``` 152 | 153 | To add a new reducer, one can now call `store.reducerManager.add("asyncState", asyncReducer)`. 154 | 155 | To remove a reducer, one can now call `store.reducerManager.remove("asyncState")` 156 | 157 | ## Libraries and Frameworks 158 | 159 | There are a few good libraries out there that can help you add the above functionality automatically: 160 | 161 | - [`redux-dynamic-modules`](https://github.com/Microsoft/redux-dynamic-modules): 162 | This library introduces the concept of a 'Redux Module', which is a bundle of Redux artifacts (reducers, middleware) that should be dynamically loaded. It also exposes a React higher-order component to load 'modules' when areas of the application come online. Additionally, it has integrations with libraries like `redux-thunk` and `redux-saga` which also help dynamically load their artifacts (thunks, sagas). 163 | - [Redux Ecosystem Links: Reducers - Dynamic Reducer Injection](https://github.com/markerikson/redux-ecosystem-links/blob/master/reducers.md#dynamic-reducer-injection) 164 | -------------------------------------------------------------------------------- /docs/recipes/reducers/InitializingState.md: -------------------------------------------------------------------------------- 1 | # Initializing State 2 | 3 | There are two main ways to initialize state for your application. The `createStore` method can accept an optional `preloadedState` value as its second argument. Reducers can also specify an initial value by looking for an incoming state argument that is `undefined`, and returning the value they'd like to use as a default. This can either be done with an explicit check inside the reducer, or by using the ES6 default argument value syntax: `function myReducer(state = someDefaultValue, action)`. 4 | 5 | It's not always immediately clear how these two approaches interact. Fortunately, the process does follow some predictable rules. Here's how the pieces fit together. 6 | 7 | 8 | ## Summary 9 | 10 | Without `combineReducers()` or similar manual code, `preloadedState` always wins over `state = ...` in the reducer because the `state` passed to the reducer *is* `preloadedState` and *is not* `undefined`, so the ES6 argument syntax doesn't apply. 11 | 12 | With `combineReducers()` the behavior is more nuanced. Those reducers whose state is specified in `preloadedState` will receive that state. Other reducers will receive `undefined` *and because of that* will fall back to the `state = ...` default argument they specify. 13 | 14 | **In general, `preloadedState` wins over the state specified by the reducer. This lets reducers specify initial data that makes sense *to them* as default arguments, but also allows loading existing data (fully or partially) when you're hydrating the store from some persistent storage or the server.** 15 | 16 | 17 | ## In Depth 18 | 19 | 20 | ### Single Simple Reducer 21 | First let's consider a case where you have a single reducer. Say you don't use `combineReducers()`. 22 | 23 | Then your reducer might look like this: 24 | 25 | ```js 26 | function counter(state = 0, action) { 27 | switch (action.type) { 28 | case 'INCREMENT': return state + 1; 29 | case 'DECREMENT': return state - 1; 30 | default: return state; 31 | } 32 | } 33 | ``` 34 | 35 | Now let's say you create a store with it. 36 | 37 | ```js 38 | import { createStore } from 'redux'; 39 | let store = createStore(counter); 40 | console.log(store.getState()); // 0 41 | ``` 42 | 43 | The initial state is zero. Why? Because the second argument to `createStore` was `undefined`. This is the `state` passed to your reducer the first time. When Redux initializes it dispatches a "dummy" action to fill the state. So your `counter` reducer was called with `state` equal to `undefined`. **This is exactly the case that "activates" the default argument.** Therefore, `state` is now `0` as per the default `state` value (`state = 0`). This state (`0`) will be returned. 44 | 45 | Let's consider a different scenario: 46 | 47 | ```js 48 | import { createStore } from 'redux'; 49 | let store = createStore(counter, 42); 50 | console.log(store.getState()); // 42 51 | ``` 52 | 53 | Why is it `42`, and not `0`, this time? Because `createStore` was called with `42` as the second argument. This argument becomes the `state` passed to your reducer along with the dummy action. **This time, `state` is not undefined (it's `42`!), so ES6 default argument syntax has no effect.** The `state` is `42`, and `42` is returned from the reducer. 54 | 55 | 56 | ### Combined Reducers 57 | 58 | Now let's consider a case where you use `combineReducers()`. 59 | You have two reducers: 60 | 61 | ```js 62 | function a(state = 'lol', action) { 63 | return state; 64 | } 65 | 66 | function b(state = 'wat', action) { 67 | return state; 68 | } 69 | ``` 70 | 71 | The reducer generated by `combineReducers({ a, b })` looks like this: 72 | 73 | ```js 74 | // const combined = combineReducers({ a, b }) 75 | function combined(state = {}, action) { 76 | return { 77 | a: a(state.a, action), 78 | b: b(state.b, action) 79 | }; 80 | } 81 | ``` 82 | 83 | If we call `createStore` without the `preloadedState`, it's going to initialize the `state` to `{}`. Therefore, `state.a` and `state.b` will be `undefined` by the time it calls `a` and `b` reducers. **Both `a` and `b` reducers will receive `undefined` as *their* `state` arguments, and if they specify default `state` values, those will be returned.** This is how the combined reducer returns a `{ a: 'lol', b: 'wat' }` state object on the first invocation. 84 | 85 | ```js 86 | import { createStore } from 'redux'; 87 | let store = createStore(combined); 88 | console.log(store.getState()); // { a: 'lol', b: 'wat' } 89 | ``` 90 | 91 | Let's consider a different scenario: 92 | 93 | ```js 94 | import { createStore } from 'redux'; 95 | let store = createStore(combined, { a: 'horse' }); 96 | console.log(store.getState()); // { a: 'horse', b: 'wat' } 97 | ``` 98 | 99 | Now I specified the `preloadedState` as the argument to `createStore()`. The state returned from the combined reducer *combines* the initial state I specified for the `a` reducer with the `'wat'` default argument specified that `b` reducer chose itself. 100 | 101 | Let's recall what the combined reducer does: 102 | 103 | ```js 104 | // const combined = combineReducers({ a, b }) 105 | function combined(state = {}, action) { 106 | return { 107 | a: a(state.a, action), 108 | b: b(state.b, action) 109 | }; 110 | } 111 | ``` 112 | 113 | In this case, `state` was specified so it didn't fall back to `{}`. It was an object with `a` field equal to `'horse'`, but without the `b` field. This is why the `a` reducer received `'horse'` as its `state` and gladly returned it, but the `b` reducer received `undefined` as its `state` and thus returned *its idea* of the default `state` (in our example, `'wat'`). This is how we get `{ a: 'horse', b: 'wat' }` in return. 114 | 115 | 116 | ## Recap 117 | 118 | To sum this up, if you stick to Redux conventions and return the initial state from reducers when they're called with `undefined` as the `state` argument (the easiest way to implement this is to specify the `state` ES6 default argument value), you're going to have a nice useful behavior for combined reducers. **They will prefer the corresponding value in the `preloadedState` object you pass to the `createStore()` function, but if you didn't pass any, or if the corresponding field is not set, the default `state` argument specified by the reducer is chosen instead.** This approach works well because it provides both initialization and hydration of existing data, but lets individual reducers reset their state if their data was not preserved. Of course you can apply this pattern recursively, as you can use `combineReducers()` on many levels, or even compose reducers manually by calling reducers and giving them the relevant part of the state tree. 119 | -------------------------------------------------------------------------------- /docs/usage/structuring-reducers/BeyondCombineReducers.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: beyond-combinereducers 3 | title: Beyond combineReducers 4 | description: 'Structuring Reducers > Beyond combineReducers: Examples of reducer logic for other use cases not handled by combineReducers' 5 | hide_title: true 6 | --- 7 | 8 |   9 | 10 | # Beyond `combineReducers` 11 | 12 | The `combineReducers` utility included with Redux is very useful, but is deliberately limited to handle a single common use case: updating a state tree that is a plain Javascript object, by delegating the work of updating each slice of state to a specific slice reducer. It does _not_ handle other use cases, such as a state tree made up of Immutable.js Maps, trying to pass other portions of the state tree as an additional argument to a slice reducer, or performing "ordering" of slice reducer calls. It also does not care how a given slice reducer does its work. 13 | 14 | The common question, then, is "How can I use `combineReducers` to handle these other use cases?". The answer to that is simply: "you don't - you probably need to use something else". **Once you go past the core use case for `combineReducers`, it's time to use more "custom" reducer logic**, whether it be specific logic for a one-off use case, or a reusable function that could be widely shared. Here's some suggestions for dealing with a couple of these typical use cases, but feel free to come up with your own approaches. 15 | 16 | ## Sharing data between slice reducers 17 | 18 | Similarly, if `sliceReducerA` happens to need some data from `sliceReducerB`'s slice of state in order to handle a particular action, or `sliceReducerB` happens to need the entire state as an argument, `combineReducers` does not handle that itself. This could be resolved by writing a custom function that knows to pass the needed data as an additional argument in those specific cases, such as: 19 | 20 | ```js 21 | function combinedReducer(state, action) { 22 | switch (action.type) { 23 | case 'A_TYPICAL_ACTION': { 24 | return { 25 | a: sliceReducerA(state.a, action), 26 | b: sliceReducerB(state.b, action) 27 | } 28 | } 29 | case 'SOME_SPECIAL_ACTION': { 30 | return { 31 | // specifically pass state.b as an additional argument 32 | a: sliceReducerA(state.a, action, state.b), 33 | b: sliceReducerB(state.b, action) 34 | } 35 | } 36 | case 'ANOTHER_SPECIAL_ACTION': { 37 | return { 38 | a: sliceReducerA(state.a, action), 39 | // specifically pass the entire state as an additional argument 40 | b: sliceReducerB(state.b, action, state) 41 | } 42 | } 43 | default: 44 | return state 45 | } 46 | } 47 | ``` 48 | 49 | Another alternative to the "shared-slice updates" issue would be to simply put more data into the action. This is easily accomplished using thunk functions or a similar approach, per this example: 50 | 51 | ```js 52 | function someSpecialActionCreator() { 53 | return (dispatch, getState) => { 54 | const state = getState() 55 | const dataFromB = selectImportantDataFromB(state) 56 | 57 | dispatch({ 58 | type: 'SOME_SPECIAL_ACTION', 59 | payload: { 60 | dataFromB 61 | } 62 | }) 63 | } 64 | } 65 | ``` 66 | 67 | Because the data from B's slice is already in the action, the parent reducer doesn't have to do anything special to make that data available to `sliceReducerA`. 68 | 69 | A third approach would be to use the reducer generated by `combineReducers` to handle the "simple" cases where each slice reducer can update itself independently, but also use another reducer to handle the "special" cases where data needs to be shared across slices. Then, a wrapping function could call both of those reducers in turn to generate the final result: 70 | 71 | ```js 72 | const combinedReducer = combineReducers({ 73 | a: sliceReducerA, 74 | b: sliceReducerB 75 | }) 76 | 77 | function crossSliceReducer(state, action) { 78 | switch (action.type) { 79 | case 'SOME_SPECIAL_ACTION': { 80 | return { 81 | // specifically pass state.b as an additional argument 82 | a: handleSpecialCaseForA(state.a, action, state.b), 83 | b: sliceReducerB(state.b, action) 84 | } 85 | } 86 | default: 87 | return state 88 | } 89 | } 90 | 91 | function rootReducer(state, action) { 92 | const intermediateState = combinedReducer(state, action) 93 | const finalState = crossSliceReducer(intermediateState, action) 94 | return finalState 95 | } 96 | ``` 97 | 98 | As it turns out, there's a useful utility called [reduce-reducers](https://github.com/acdlite/reduce-reducers) that can make that process easier. It simply takes multiple reducers and runs `reduce()` on them, passing the intermediate state values to the next reducer in line: 99 | 100 | ```js 101 | // Same as the "manual" rootReducer above 102 | const rootReducer = reduceReducers(combinedReducers, crossSliceReducer) 103 | ``` 104 | 105 | Note that if you use `reduceReducers`, you should make sure that the first reducer in the list is able to define the initial state, since the later reducers will generally assume that the entire state already exists and not try to provide defaults. 106 | 107 | ## Further Suggestions 108 | 109 | Again, it's important to understand that Redux reducers are _just_ functions. While `combineReducers` is useful, it's just one tool in the toolbox. Functions can contain conditional logic other than switch statements, functions can be composed to wrap each other, and functions can call other functions. Maybe you need one of your slice reducers to be able to reset its state, and to only respond to specific actions overall. You could do: 110 | 111 | ```js 112 | const undoableFilteredSliceA = compose( 113 | undoReducer, 114 | filterReducer('ACTION_1', 'ACTION_2'), 115 | sliceReducerA 116 | ) 117 | const rootReducer = combineReducers({ 118 | a: undoableFilteredSliceA, 119 | b: normalSliceReducerB 120 | }) 121 | ``` 122 | 123 | Note that `combineReducers` doesn't know or care that there's anything special about the reducer function that's responsible for managing `a`. We didn't need to modify `combineReducers` to specifically know how to undo things - we just built up the pieces we needed into a new composed function. 124 | 125 | Also, while `combineReducers` is the one reducer utility function that's built into Redux, there's a wide variety of third-party reducer utilities that have published for reuse. The [Redux Addons Catalog](https://github.com/markerikson/redux-ecosystem-links) lists many of the third-party utilities that are available. Or, if none of the published utilities solve your use case, you can always write a function yourself that does just exactly what you need. 126 | -------------------------------------------------------------------------------- /docs/recipes/reducers/ImmutableUpdatePatterns.md: -------------------------------------------------------------------------------- 1 | # Immutable Update Patterns 2 | 3 | The articles listed in [Prerequisite Concepts#Immutable Data Management](PrerequisiteConcepts.md#immutable-data-management) give a number of good examples for how to perform basic update operations immutably, such as updating a field in an object or adding an item to the end of an array. However, reducers will often need to use those basic operations in combination to perform more complicated tasks. Here are some examples for some of the more common tasks you might have to implement. 4 | 5 | ## Updating Nested Objects 6 | 7 | The key to updating nested data is **that _every_ level of nesting must be copied and updated appropriately**. This is often a difficult concept for those learning Redux, and there are some specific problems that frequently occur when trying to update nested objects. These lead to accidental direct mutation, and should be avoided. 8 | 9 | ##### Common Mistake #1: New variables that point to the same objects 10 | 11 | 12 | Defining a new variable does _not_ create a new actual object - it only creates another reference to the same object. An example of this error would be: 13 | 14 | ```js 15 | function updateNestedState(state, action) { 16 | let nestedState = state.nestedState; 17 | // ERROR: this directly modifies the existing object reference - don't do this! 18 | nestedState.nestedField = action.data; 19 | 20 | return { 21 | ...state, 22 | nestedState 23 | }; 24 | } 25 | ``` 26 | 27 | This function does correctly return a shallow copy of the top-level state object, but because the `nestedState` variable was still pointing at the existing object, the state was directly mutated. 28 | 29 | 30 | ##### Common Mistake #2: Only making a shallow copy of one level 31 | 32 | Another common version of this error looks like this: 33 | 34 | ```js 35 | function updateNestedState(state, action) { 36 | // Problem: this only does a shallow copy! 37 | let newState = {...state}; 38 | 39 | // ERROR: nestedState is still the same object! 40 | newState.nestedState.nestedField = action.data; 41 | 42 | return newState; 43 | } 44 | ``` 45 | 46 | Doing a shallow copy of the top level is _not_ sufficient - the `nestedState` object should be copied as well. 47 | 48 | 49 | ##### Correct Approach: Copying All Levels of Nested Data 50 | 51 | Unfortunately, the process of correctly applying immutable updates to deeply nested state can easily become verbose and hard to read. Here's what an example of updating `state.first.second[someId].fourth` might look like: 52 | 53 | ```js 54 | function updateVeryNestedField(state, action) { 55 | return { 56 | ....state, 57 | first : { 58 | ...state.first, 59 | second : { 60 | ...state.first.second, 61 | [action.someId] : { 62 | ...state.first.second[action.someId], 63 | fourth : action.someValue 64 | } 65 | } 66 | } 67 | } 68 | } 69 | ``` 70 | 71 | Obviously, each layer of nesting makes this harder to read, and gives more chances to make mistakes. This is one of several reasons why you are encouraged to keep your state flattened, and compose reducers as much as possible. 72 | 73 | 74 | ## Inserting and Removing Items in Arrays 75 | 76 | Normally, a Javascript array's contents are modified using mutative functions like `push`, `unshift`, and `splice`. Since we don't want to mutate state directly in reducers, those should normally be avoided. Because of that, you might see "insert" or "remove" behavior written like this: 77 | 78 | ```js 79 | function insertItem(array, action) { 80 | return [ 81 | ...array.slice(0, action.index), 82 | action.item, 83 | ...array.slice(action.index) 84 | ] 85 | } 86 | 87 | function removeItem(array, action) { 88 | return [ 89 | ...array.slice(0, action.index), 90 | ...array.slice(action.index + 1) 91 | ]; 92 | } 93 | ``` 94 | 95 | However, remember that the key is that the _original in-memory reference_ is not modified. **As long as we make a copy first, we can safely mutate the copy**. Note that this is true for both arrays and objects, but nested values still must be updated using the same rules. 96 | 97 | This means that we could also write the insert and remove functions like this: 98 | 99 | ```js 100 | function insertItem(array, action) { 101 | let newArray = array.slice(); 102 | newArray.splice(action.index, 0, action.item); 103 | return newArray; 104 | } 105 | 106 | function removeItem(array, action) { 107 | let newArray = array.slice(); 108 | newArray.splice(action.index, 1); 109 | return newArray; 110 | } 111 | ``` 112 | 113 | The remove function could also be implemented as: 114 | 115 | ```js 116 | function removeItem(array, action) { 117 | return array.filter( (item, index) => index !== action.index); 118 | } 119 | ``` 120 | 121 | ## Updating an Item in an Array 122 | 123 | Updating one item in an array can be accomplished by using `Array.map`, returning a new value for the item we want to update, and returning the existing values for all other items: 124 | 125 | ```js 126 | function updateObjectInArray(array, action) { 127 | return array.map( (item, index) => { 128 | if(index !== action.index) { 129 | // This isn't the item we care about - keep it as-is 130 | return item; 131 | } 132 | 133 | // Otherwise, this is the one we want - return an updated value 134 | return { 135 | ...item, 136 | ...action.item 137 | }; 138 | }); 139 | } 140 | ``` 141 | 142 | 143 | 144 | ## Immutable Update Utility Libraries 145 | 146 | Because writing immutable update code can become tedious, there are a number of utility libraries that try to abstract out the process. These libraries vary in APIs and usage, but all try to provide a shorter and more succinct way of writing these updates. Some, like [dot-prop-immutable](https://github.com/debitoor/dot-prop-immutable), take string paths for commands: 147 | ```js 148 | state = dotProp.set(state, `todos.${index}.complete`, true) 149 | ``` 150 | 151 | Others, like [immutability-helper](https://github.com/kolodny/immutability-helper) (a fork of the now-deprecated React Immutability Helpers addon), use nested values and helper functions: 152 | ```js 153 | var collection = [1, 2, {a: [12, 17, 15]}]; 154 | var newCollection = update(collection, {2: {a: {$splice: [[1, 1, 13, 14]]}}}); 155 | ``` 156 | 157 | They can provide a useful alternative to writing manual immutable update logic. 158 | 159 | A list of many immutable update utilities can be found in the [Immutable Data#Immutable Update Utilities](https://github.com/markerikson/redux-ecosystem-links/blob/master/immutable-data.md#immutable-update-utilities) section of the [Redux Addons Catalog](https://github.com/markerikson/redux-ecosystem-links). -------------------------------------------------------------------------------- /docs/faq/StoreSetup.md: -------------------------------------------------------------------------------- 1 | # Redux FAQ: Настройка стора 2 | 3 | ## Содержание 4 | 5 | - [Могу/должен ли я создавать несколько сторов? Могу ли я импортировать мой стор напрямую и использовать его в компонентах?](#store-setup-multiple-stores) 6 | - [Нормально ли использовать более одного мидлвара в моем расширителе стора ? В чем разница между next и dispatch в функции мидлвара?](#store-setup-middleware-chains) 7 | - [Как мне подписаться на получение только части стора? Могу ли я получить запущенный экшен, как часть подписки?](#store-setup-subscriptions) 8 | 9 | ## Настройка стора 10 | 11 | 12 | ### Могу/должен ли я создавать несколько сторов? Могу ли я импортировать мой стор напрямую и использовать его в компонентах? 13 | 14 | Оригинальная Flux-архитектура предполагает наличие нескольких “сторов” в приложении, каждое из которых хранит определенную область данных. Однако, это может привести к тому, что потребуется создать стор, который будет ждать обновления другого. Redux исключает такой случай, так как разделение данных на области достигается дроблением одного редьюсера на более мелкие. 15 | 16 | Как и с другими вопросами по Redux, технически *возможно* создать несколько различных сторов, но шаблон предполагает только один стор. Наличие единственного стора дает возможность использовать Redux DevTools, делать сохранение и восстановление данных (для отладки по time-travel) и упрощает логику подписки. 17 | 18 | Веские причины по использованию нескольких сторов в Redux должны включать: 19 | 20 | * Решение проблем с производительностью, связанные со слишком частыми обновлениями некоторых частей сторов во время длительного профилирования приложения. 21 | * Выделение Redux-приложения в компонент более крупного приложения. В этом случае Вам, возможно, потребуется стор для экземпляра главного компонента. 22 | 23 | Однако, создание новых сторов не должно быть первым, что Вы будете делать, особенно если Вы пришли из Flux. Для начала, попробуйте композицию редьюсеров. Используйте несколько сторов только в том случае, если композиция не решила Вашей проблемы. 24 | 25 | Кроме того, пока у Вас есть *возможность* передавать экземпляр Вашего стора напрямую, не рекомендуется множить сторы в Redux. Если Вы создаете экземпляр стора и передаете его из модуля, то это будет синглтон. Это значит, что будет сложно выделить Redux-приложение в качестве компонента большего приложения, если нет необходимости или возможности сделать на сервере, потому что на сервере вы можете создать разделенные экземпляры сторов для каждого запроса. 26 | 27 | С [React Redux](https://github.com/reactjs/react-redux) классы-оболочки, созданные при помощи функции `connect()`, ищут `props.store`, если оно существует. Но будет лучше, если Вы обернете Ваш главный компонент в `` и позволите React Redux позаботится о пробросе ссылки на стор по всему дереву компонентов. Таким образом, компонентам не нужно заботиться о получении доступа к стору, а будущее отделение Redux-приложения или возможность серверного рендеринга становится проще. 28 | 29 | #### Дополнительная информация 30 | 31 | **Документация** 32 | - [API: Store](/docs/api/Store.md) 33 | 34 | **Обсуждения** 35 | - [#1346: Is it bad practice to just have a 'stores' directory?](https://github.com/reactjs/redux/issues/1436) 36 | - [Stack Overflow: Redux multiple stores, why not?](http://stackoverflow.com/questions/33619775/redux-multiple-stores-why-not) 37 | - [Stack Overflow: Accessing Redux state in an action creator](http://stackoverflow.com/questions/35667249/accessing-redux-state-in-an-action-creator) 38 | - [Gist: Breaking out of Redux paradigm to isolate apps](https://gist.github.com/gaearon/eeee2f619620ab7b55673a4ee2bf8400) 39 | 40 | 41 | 42 | ### Нормально ли использовать более одного мидлвара в моем расширителе стора? В чем разница между next и dispatch в функции мидлвара? 43 | 44 | Redux мидлвар ведет себя, как связанный список. Каждая мидлвар-функция может также вызвать `next(action)` для передачи экшена следующему мидлвару в цепочке. Вызов `dispatch(action)` перезапустит процесс и начнет с начала цепочки. Или можно вообще ничего не вызывать, чтобы остановить дальнейшую обработку экшена. 45 | 46 | Упомянутая выше цепочка мидлваров описывается в аргументах функции `applyMiddleware`, которая используется при создании стора. Определение нескольких цепочек не будет корректно работать, так как они будут иметь сильно отличающиеся объявления `dispatch`, и отличающиеся цепочки будут эффективно отключены. 47 | 48 | #### Дополнительная информация 49 | 50 | **Документация** 51 | - [Продвинутое использование: Middleware](/docs/advanced/Middleware.md) 52 | - [API: applyMiddleware](/docs/api/applyMiddleware.md) 53 | 54 | **Обсуждения** 55 | - [#1051: Shortcomings of the current applyMiddleware and composing createStore](https://github.com/reactjs/redux/issues/1051) 56 | - [Understanding Redux Middleware](https://medium.com/@meagle/understanding-87566abcfb7a) 57 | - [Exploring Redux Middleware](http://blog.krawaller.se/posts/exploring-redux-middleware/) 58 | 59 | 60 | 61 | ### Как мне подписаться на получение только части стора? Могу ли я получить запущенный экшен, как часть подписки? 62 | 63 | Redux предоставляет единственный метод `store.subscribe` для объявления прослушивателей обновлений стора. Обработчики не получают состояние в качестве аргумента — они лишь сообщают, что *что-то* изменилось. Затем в прослушивателе можно вызывать `getState()`, чтобы получить текущее значение состояния. 64 | 65 | Этот API, как низкоуровневый примитив без зависимостей или усложнений, может быть использован для построения высокоуровневой логики подписок. UI-привязки, такие как React Redux, могут создавать подписчков для каждого подключенного компонента. Это дает возможность писать функции, которые могут грамотно сравнивать старое состояние и новое и выполнять дополнительные экшены, если определенные части были изменены. Примеры включают [redux-watch](https://github.com/jprichardson/redux-watch) и [redux-subscribe](https://github.com/ashaffer/redux-subscribe), которые предлагают различные подходы определения подписчиков и отлова изменений. 66 | 67 | Новое состояние не передается прослушивателям, чтобы упростить реализацию расширенных возможностей стора, таких как Redux DevTools. Кроме того, подписчики предназначены для реагирования на само значение состояния, а не на экшен. Мидлвар может быть использован, если экшен важен и нуждается в специальной обработке. 68 | 69 | #### Дополнительная информация 70 | 71 | **Документация** 72 | - [Основы: Стор](/docs/basics/Store.md) 73 | - [API: Store](/docs/api/Store.md) 74 | 75 | **Обсуждения** 76 | - [#303: subscribe API with state as an argument](https://github.com/reactjs/redux/issues/303) 77 | - [#580: Is it possible to get action and state in store.subscribe?](https://github.com/reactjs/redux/issues/580) 78 | - [#922: Proposal: add subscribe to middleware API](https://github.com/reactjs/redux/issues/922) 79 | - [#1057: subscribe listener can get action param?](https://github.com/reactjs/redux/issues/1057) 80 | - [#1300: Redux is great but major feature is missing](https://github.com/reactjs/redux/issues/1300) 81 | 82 | **Библиотеки** 83 | - [Redux Addons Catalog: Store Change Subscriptions](https://github.com/markerikson/redux-ecosystem-links/blob/master/store.md#store-change-subscriptions) 84 | -------------------------------------------------------------------------------- /docs/usage/structuring-reducers/InitializingState.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: initializing-state 3 | title: Initializing State 4 | description: 'Structuring Reducers > Initializing State: How Redux state is initialized' 5 | --- 6 | 7 | # Initializing State 8 | 9 | There are two main ways to initialize state for your application. The `createStore` method can accept an optional `preloadedState` value as its second argument. Reducers can also specify an initial value by looking for an incoming state argument that is `undefined`, and returning the value they'd like to use as a default. This can either be done with an explicit check inside the reducer, or by using the ES6 default argument value syntax: `function myReducer(state = someDefaultValue, action)`. 10 | 11 | It's not always immediately clear how these two approaches interact. Fortunately, the process does follow some predictable rules. Here's how the pieces fit together. 12 | 13 | ## Summary 14 | 15 | Without `combineReducers()` or similar manual code, `preloadedState` always wins over `state = ...` in the reducer because the `state` passed to the reducer _is_ `preloadedState` and _is not_ `undefined`, so the ES6 argument syntax doesn't apply. 16 | 17 | With `combineReducers()` the behavior is more nuanced. Those reducers whose state is specified in `preloadedState` will receive that state. Other reducers will receive `undefined` _and because of that_ will fall back to the `state = ...` default argument they specify. 18 | 19 | **In general, `preloadedState` wins over the state specified by the reducer. This lets reducers specify initial data that makes sense _to them_ as default arguments, but also allows loading existing data (fully or partially) when you're hydrating the store from some persistent storage or the server.** 20 | 21 | _Note: Reducers whose initial state is populated using `preloadedState` will **still need to provide a default value** to handle when passed a `state` of `undefined`. All reducers are passed `undefined` on initialization, so they should be written such that when given `undefined`, some value should be returned. This can be any non-`undefined` value; there's no need to duplicate the section of `preloadedState` here as the default._ 22 | 23 | ## In Depth 24 | 25 | ### Single Simple Reducer 26 | 27 | First let's consider a case where you have a single reducer. Say you don't use `combineReducers()`. 28 | 29 | Then your reducer might look like this: 30 | 31 | ```js 32 | function counter(state = 0, action) { 33 | switch (action.type) { 34 | case 'INCREMENT': 35 | return state + 1 36 | case 'DECREMENT': 37 | return state - 1 38 | default: 39 | return state 40 | } 41 | } 42 | ``` 43 | 44 | Now let's say you create a store with it. 45 | 46 | ```js 47 | import { createStore } from 'redux' 48 | const store = createStore(counter) 49 | console.log(store.getState()) // 0 50 | ``` 51 | 52 | The initial state is zero. Why? Because the second argument to `createStore` was `undefined`. This is the `state` passed to your reducer the first time. When Redux initializes it dispatches a "dummy" action to fill the state. So your `counter` reducer was called with `state` equal to `undefined`. **This is exactly the case that "activates" the default argument.** Therefore, `state` is now `0` as per the default `state` value (`state = 0`). This state (`0`) will be returned. 53 | 54 | Let's consider a different scenario: 55 | 56 | ```js 57 | import { createStore } from 'redux' 58 | const store = createStore(counter, 42) 59 | console.log(store.getState()) // 42 60 | ``` 61 | 62 | Why is it `42`, and not `0`, this time? Because `createStore` was called with `42` as the second argument. This argument becomes the `state` passed to your reducer along with the dummy action. **This time, `state` is not undefined (it's `42`!), so ES6 default argument syntax has no effect.** The `state` is `42`, and `42` is returned from the reducer. 63 | 64 | ### Combined Reducers 65 | 66 | Now let's consider a case where you use `combineReducers()`. 67 | You have two reducers: 68 | 69 | ```js 70 | function a(state = 'lol', action) { 71 | return state 72 | } 73 | 74 | function b(state = 'wat', action) { 75 | return state 76 | } 77 | ``` 78 | 79 | The reducer generated by `combineReducers({ a, b })` looks like this: 80 | 81 | ```js 82 | // const combined = combineReducers({ a, b }) 83 | function combined(state = {}, action) { 84 | return { 85 | a: a(state.a, action), 86 | b: b(state.b, action) 87 | } 88 | } 89 | ``` 90 | 91 | If we call `createStore` without the `preloadedState`, it's going to initialize the `state` to `{}`. Therefore, `state.a` and `state.b` will be `undefined` by the time it calls `a` and `b` reducers. **Both `a` and `b` reducers will receive `undefined` as _their_ `state` arguments, and if they specify default `state` values, those will be returned.** This is how the combined reducer returns a `{ a: 'lol', b: 'wat' }` state object on the first invocation. 92 | 93 | ```js 94 | import { createStore } from 'redux' 95 | const store = createStore(combined) 96 | console.log(store.getState()) // { a: 'lol', b: 'wat' } 97 | ``` 98 | 99 | Let's consider a different scenario: 100 | 101 | ```js 102 | import { createStore } from 'redux' 103 | const store = createStore(combined, { a: 'horse' }) 104 | console.log(store.getState()) // { a: 'horse', b: 'wat' } 105 | ``` 106 | 107 | Now I specified the `preloadedState` as the argument to `createStore()`. The state returned from the combined reducer _combines_ the initial state I specified for the `a` reducer with the `'wat'` default argument specified that `b` reducer chose itself. 108 | 109 | Let's recall what the combined reducer does: 110 | 111 | ```js 112 | // const combined = combineReducers({ a, b }) 113 | function combined(state = {}, action) { 114 | return { 115 | a: a(state.a, action), 116 | b: b(state.b, action) 117 | } 118 | } 119 | ``` 120 | 121 | In this case, `state` was specified so it didn't fall back to `{}`. It was an object with `a` field equal to `'horse'`, but without the `b` field. This is why the `a` reducer received `'horse'` as its `state` and gladly returned it, but the `b` reducer received `undefined` as its `state` and thus returned _its idea_ of the default `state` (in our example, `'wat'`). This is how we get `{ a: 'horse', b: 'wat' }` in return. 122 | 123 | ## Recap 124 | 125 | To sum this up, if you stick to Redux conventions and return the initial state from reducers when they're called with `undefined` as the `state` argument (the easiest way to implement this is to specify the `state` ES6 default argument value), you're going to have a nice useful behavior for combined reducers. **They will prefer the corresponding value in the `preloadedState` object you pass to the `createStore()` function, but if you didn't pass any, or if the corresponding field is not set, the default `state` argument specified by the reducer is chosen instead.** This approach works well because it provides both initialization and hydration of existing data, but lets individual reducers reset their state if their data was not preserved. Of course you can apply this pattern recursively, as you can use `combineReducers()` on many levels, or even compose reducers manually by calling reducers and giving them the relevant part of the state tree. 126 | -------------------------------------------------------------------------------- /docs/recipes/reducers/BeyondCombineReducers.md: -------------------------------------------------------------------------------- 1 | # Beyond `combineReducers` 2 | 3 | The `combineReducers` utility included with Redux is very useful, but is deliberately limited to handle a single common use case: updating a state tree that is a plain Javascript object, by delegating the work of updating each slice of state to a specific slice reducer. It does _not_ handle other use cases, such as a state tree made up of Immutable.js Maps, trying to pass other portions of the state tree as an additional argument to a slice reducer, or performing "ordering" of slice reducer calls. It also does not care how a given slice reducer does its work. 4 | 5 | The common question, then, is "How can I use `combineReducers` to handle these other use cases?". The answer to that is simply: "you don't - you probably need to use something else". **Once you go past the core use case for `combineReducers`, it's time to use more "custom" reducer logic**, whether it be specific logic for a one-off use case, or a reusable function that could be widely shared. Here's some suggestions for dealing with a couple of these typical use cases, but feel free to come up with your own approaches. 6 | 7 | 8 | ## Using slice reducers with Immutable.js objects 9 | 10 | Since `combineReducers` currently only works with plain Javascript objects, an application that uses an Immutable.js Map object for the top of its state tree could not use `combineReducers` to manage that Map. Since many developers do use Immutable.js, there are a number of published utilities that provide equivalent functionality, such as [redux-immutable](https://github.com/gajus/redux-immutable). This package provides its own implementation of `combineReducers` that knows how to iterate over an Immutable Map instead of a plain Javascript object. 11 | 12 | 13 | ## Sharing data between slice reducers 14 | 15 | Similarly, if `sliceReducerA` happens to need some data from `sliceReducerB`'s slice of state in order to handle a particular action, or `sliceReducerB` happens to need the entire state as an argument, `combineReducers` does not handle that itself. This could be resolved by writing a custom function that knows to pass the needed data as an additional argument in those specific cases, such as: 16 | 17 | ```js 18 | function combinedReducer(state, action) { 19 | switch(action.type) { 20 | case "A_TYPICAL_ACTION" : { 21 | return { 22 | a : sliceReducerA(state.a, action), 23 | b : sliceReducerB(state.b, action) 24 | }; 25 | } 26 | case "SOME_SPECIAL_ACTION" : { 27 | return { 28 | // specifically pass state.b as an additional argument 29 | a : sliceReducerA(state.a, action, state.b), 30 | b : sliceReducerB(state.b, action) 31 | } 32 | } 33 | case "ANOTHER_SPECIAL_ACTION" : { 34 | return { 35 | a : sliceReducerA(state.a, action), 36 | // specifically pass the entire state as an additional argument 37 | b : sliceReducerB(state.b, action, state) 38 | } 39 | } 40 | default: return state; 41 | } 42 | } 43 | ``` 44 | 45 | Another alternative to the "shared-slice updates" issue would be to simply put more data into the action. This is easily accomplished using thunk functions or a similar approach, per this example: 46 | 47 | ```js 48 | function someSpecialActionCreator() { 49 | return (dispatch, getState) => { 50 | const state = getState(); 51 | const dataFromB = selectImportantDataFromB(state); 52 | 53 | dispatch({ 54 | type : "SOME_SPECIAL_ACTION", 55 | payload : { 56 | dataFromB 57 | } 58 | }); 59 | } 60 | } 61 | ``` 62 | 63 | Because the data from B's slice is already in the action, the parent reducer doesn't have to do anything special to make that data available to `sliceReducerA`. 64 | 65 | 66 | A third approach would be to use the reducer generated by `combineReducers` to handle the "simple" cases where each slice reducer can update itself independently, but also use another reducer to handle the "special" cases where data needs to be shared across slices. Then, a wrapping function could call both of those reducers in turn to generate the final result: 67 | 68 | ```js 69 | const combinedReducer = combineReducers({ 70 | a : sliceReducerA, 71 | b : sliceReducerB 72 | }); 73 | 74 | function crossSliceReducer(state, action) { 75 | switch(action.type) { 76 | case "SOME_SPECIAL_ACTION" : { 77 | return { 78 | // specifically pass state.b as an additional argument 79 | a : handleSpecialCaseForA(state.a, action, state.b), 80 | b : sliceReducerB(state.b, action) 81 | } 82 | } 83 | default : return state; 84 | } 85 | } 86 | 87 | function rootReducer(state, action) { 88 | const intermediateState = combinedReducer(state, action); 89 | const finalState = crossSliceReducer(intermediateState, action); 90 | return finalState; 91 | } 92 | ``` 93 | 94 | As it turns out, there's a useful utility called [reduce-reducers](https://github.com/acdlite/reduce-reducers) that can make that process easier. It simply takes multiple reducers and runs `reduce()` on them, passing the intermediate state values to the next reducer in line: 95 | 96 | ```js 97 | // Same as the "manual" rootReducer above 98 | const rootReducer = reduceReducers(combinedReducers, crossSliceReducer); 99 | ``` 100 | 101 | Note that if you use `reduceReducers`, you should make sure that the first reducer in the list is able to define the initial state, since the later reducers will generally assume that the entire state already exists and not try to provide defaults. 102 | 103 | 104 | ## Further Suggestions 105 | 106 | Again, it's important to understand that Redux reducers are _just_ functions. While `combineReducers` is useful, it's just one tool in the toolbox. Functions can contain conditional logic other than switch statements, functions can be composed to wrap each other, and functions can call other functions. Maybe you need one of your slice reducers to be able to reset its state, and to only respond to specific actions overall. You could do: 107 | 108 | ```js 109 | const undoableFilteredSliceA = compose(undoReducer, filterReducer("ACTION_1", "ACTION_2"), sliceReducerA); 110 | const rootReducer = combineReducers({ 111 | a : undoableFilteredSliceA, 112 | b : normalSliceReducerB 113 | }); 114 | ``` 115 | 116 | Note that `combineReducers` doesn't know or care that there's anything special about the reducer function that's responsible for managing `a`. We didn't need to modify `combineReducers` to specifically know how to undo things - we just built up the pieces we needed into a new composed function. 117 | 118 | Also, while `combineReducers` is the one reducer utility function that's built into Redux, there's a wide variety of third-party reducer utilities that have published for reuse. The [Redux Addons Catalog](https://github.com/markerikson/redux-ecosystem-links) lists many of the third-party utilities that are available. Or, if none of the published utilities solve your use case, you can always write a function yourself that does just exactly what you need. 119 | 120 | -------------------------------------------------------------------------------- /docs/Troubleshooting.md: -------------------------------------------------------------------------------- 1 | # Troubleshooting 2 | 3 | This is a place to share common problems and solutions to them. 4 | The examples use React, but you should still find them useful if you use something else. 5 | 6 | ### Nothing happens when I dispatch an action 7 | 8 | Sometimes, you are trying to dispatch an action, but your view does not update. Why does this happen? There may be several reasons for this. 9 | 10 | #### Never mutate reducer arguments 11 | 12 | It is tempting to modify the `state` or `action` passed to you by Redux. Don't do this! 13 | 14 | Redux assumes that you never mutate the objects it gives to you in the reducer. **Every single time, you must return the new state object.** Even if you don't use a library like [Immutable](https://facebook.github.io/immutable-js/), you need to completely avoid mutation. 15 | 16 | Immutability is what lets [react-redux](https://github.com/gaearon/react-redux) efficiently subscribe to fine-grained updates of your state. It also enables great developer experience features such as time travel with [redux-devtools](http://github.com/reduxjs/redux-devtools). 17 | 18 | For example, a reducer like this is wrong because it mutates the state: 19 | 20 | ```js 21 | function todos(state = [], action) { 22 | switch (action.type) { 23 | case 'ADD_TODO': 24 | // Wrong! This mutates state 25 | state.push({ 26 | text: action.text, 27 | completed: false 28 | }) 29 | return state 30 | case 'COMPLETE_TODO': 31 | // Wrong! This mutates state[action.index]. 32 | state[action.index].completed = true 33 | return state 34 | default: 35 | return state 36 | } 37 | } 38 | ``` 39 | 40 | It needs to be rewritten like this: 41 | 42 | ```js 43 | function todos(state = [], action) { 44 | switch (action.type) { 45 | case 'ADD_TODO': 46 | // Return a new array 47 | return [ 48 | ...state, 49 | { 50 | text: action.text, 51 | completed: false 52 | } 53 | ] 54 | case 'COMPLETE_TODO': 55 | // Return a new array 56 | return state.map((todo, index) => { 57 | if (index === action.index) { 58 | // Copy the object before mutating 59 | return Object.assign({}, todo, { 60 | completed: true 61 | }) 62 | } 63 | return todo 64 | }) 65 | default: 66 | return state 67 | } 68 | } 69 | ``` 70 | 71 | It's more code, but it's exactly what makes Redux predictable and efficient. If you want to have less code, you can use a helper like [`React.addons.update`](https://facebook.github.io/react/docs/update.html) to write immutable transformations with a terse syntax: 72 | 73 | ```js 74 | // Before: 75 | return state.map((todo, index) => { 76 | if (index === action.index) { 77 | return Object.assign({}, todo, { 78 | completed: true 79 | }) 80 | } 81 | return todo 82 | }) 83 | 84 | // After 85 | return update(state, { 86 | [action.index]: { 87 | completed: { 88 | $set: true 89 | } 90 | } 91 | }) 92 | ``` 93 | 94 | Finally, to update objects, you'll need something like `_.extend` from Underscore, or better, an [`Object.assign`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) polyfill. 95 | 96 | Make sure that you use `Object.assign` correctly. For example, instead of returning something like `Object.assign(state, newData)` from your reducers, return `Object.assign({}, state, newData)`. This way you don't override the previous `state`. 97 | 98 | You can also enable the [object spread operator proposal](recipes/UsingObjectSpreadOperator.md) for a more succinct syntax: 99 | 100 | ```js 101 | // Before: 102 | return state.map((todo, index) => { 103 | if (index === action.index) { 104 | return Object.assign({}, todo, { 105 | completed: true 106 | }) 107 | } 108 | return todo 109 | }) 110 | 111 | // After: 112 | return state.map((todo, index) => { 113 | if (index === action.index) { 114 | return { ...todo, completed: true } 115 | } 116 | return todo 117 | }) 118 | ``` 119 | 120 | Note that experimental language features are subject to change. 121 | 122 | Also keep an eye out for nested state objects that need to be deeply copied. Both `_.extend` and `Object.assign` make a shallow copy of the state. See [Updating Nested Objects](./recipes/structuring-reducers/ImmutableUpdatePatterns.md#updating-nested-objects) for suggestions on how to deal with nested state objects. 123 | 124 | #### Don't forget to call [`dispatch(action)`](api/Store.md#dispatchaction) 125 | 126 | If you define an action creator, calling it will _not_ automatically dispatch the action. For example, this code will do nothing: 127 | 128 | #### `TodoActions.js` 129 | 130 | ```js 131 | export function addTodo(text) { 132 | return { type: 'ADD_TODO', text } 133 | } 134 | ``` 135 | 136 | #### `AddTodo.js` 137 | 138 | ```js 139 | import React, { Component } from 'react' 140 | import { addTodo } from './TodoActions' 141 | 142 | class AddTodo extends Component { 143 | handleClick() { 144 | // Won't work! 145 | addTodo('Fix the issue') 146 | } 147 | 148 | render() { 149 | return 150 | } 151 | } 152 | ``` 153 | 154 | It doesn't work because your action creator is just a function that _returns_ an action. It is up to you to actually dispatch it. We can't bind your action creators to a particular Store instance during the definition because apps that render on the server need a separate Redux store for every request. 155 | 156 | The fix is to call [`dispatch()`](api/Store.md#dispatchaction) method on the [store](api/Store.md) instance: 157 | 158 | ```js 159 | handleClick() { 160 | // Works! (but you need to grab store somehow) 161 | store.dispatch(addTodo('Fix the issue')) 162 | } 163 | ``` 164 | 165 | If you're somewhere deep in the component hierarchy, it is cumbersome to pass the store down manually. This is why [react-redux](https://github.com/gaearon/react-redux) lets you use a `connect` [higher-order component](https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750) that will, apart from subscribing you to a Redux store, inject `dispatch` into your component's props. 166 | 167 | The fixed code looks like this: 168 | 169 | #### `AddTodo.js` 170 | 171 | ```js 172 | import React, { Component } from 'react' 173 | import { connect } from 'react-redux' 174 | import { addTodo } from './TodoActions' 175 | 176 | class AddTodo extends Component { 177 | handleClick() { 178 | // Works! 179 | this.props.dispatch(addTodo('Fix the issue')) 180 | } 181 | 182 | render() { 183 | return 184 | } 185 | } 186 | 187 | // In addition to the state, `connect` puts `dispatch` in our props. 188 | export default connect()(AddTodo) 189 | ``` 190 | 191 | You can then pass `dispatch` down to other components manually, if you want to. 192 | 193 | #### Make sure mapStateToProps is correct 194 | 195 | It's possible you're correctly dispatching an action and applying your reducer but the corresponding state is not being correctly translated into props. 196 | 197 | ## Something else doesn't work 198 | 199 | Ask around on the **#redux** [Reactiflux](http://reactiflux.com/) Discord channel, or [create an issue](https://github.com/reduxjs/redux/issues). 200 | If you figure it out, [edit this document](https://github.com/reduxjs/redux/edit/master/docs/Troubleshooting.md) as a courtesy to the next person having the same problem. -------------------------------------------------------------------------------- /docs/usage/Troubleshooting.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: troubleshooting 3 | title: Troubleshooting 4 | --- 5 | 6 | # Troubleshooting 7 | 8 | This is a place to share common problems and solutions to them. 9 | The examples use React, but you should still find them useful if you use something else. 10 | 11 | ### Nothing happens when I dispatch an action 12 | 13 | Sometimes, you are trying to dispatch an action, but your view does not update. Why does this happen? There may be several reasons for this. 14 | 15 | #### Never mutate reducer arguments 16 | 17 | It is tempting to modify the `state` or `action` passed to you by Redux. Don't do this! 18 | 19 | Redux assumes that you never mutate the objects it gives to you in the reducer. **Every single time, you must return the new state object.** Even if you don't use a library like [Immer](https://github.com/immerjs/immer), you need to completely avoid mutation. 20 | 21 | Immutability is what lets [react-redux](https://github.com/gaearon/react-redux) efficiently subscribe to fine-grained updates of your state. It also enables great developer experience features such as time travel with [redux-devtools](https://github.com/reduxjs/redux-devtools). 22 | 23 | For example, a reducer like this is wrong because it mutates the state: 24 | 25 | ```js 26 | function todos(state = [], action) { 27 | switch (action.type) { 28 | case 'ADD_TODO': 29 | // Wrong! This mutates state 30 | state.push({ 31 | text: action.text, 32 | completed: false 33 | }) 34 | return state 35 | case 'COMPLETE_TODO': 36 | // Wrong! This mutates state[action.index]. 37 | state[action.index].completed = true 38 | return state 39 | default: 40 | return state 41 | } 42 | } 43 | ``` 44 | 45 | It needs to be rewritten like this: 46 | 47 | ```js 48 | function todos(state = [], action) { 49 | switch (action.type) { 50 | case 'ADD_TODO': 51 | // Return a new array 52 | return [ 53 | ...state, 54 | { 55 | text: action.text, 56 | completed: false 57 | } 58 | ] 59 | case 'COMPLETE_TODO': 60 | // Return a new array 61 | return state.map((todo, index) => { 62 | if (index === action.index) { 63 | // Copy the object before mutating 64 | return Object.assign({}, todo, { 65 | completed: true 66 | }) 67 | } 68 | return todo 69 | }) 70 | default: 71 | return state 72 | } 73 | } 74 | ``` 75 | 76 | It's more code, but it's exactly what makes Redux predictable and efficient. If you want to have less code, you can use a helper like [`React.addons.update`](https://facebook.github.io/react/docs/update.html) to write immutable transformations with a terse syntax: 77 | 78 | ```js 79 | // Before: 80 | return state.map((todo, index) => { 81 | if (index === action.index) { 82 | return Object.assign({}, todo, { 83 | completed: true 84 | }) 85 | } 86 | return todo 87 | }) 88 | 89 | // After 90 | return update(state, { 91 | [action.index]: { 92 | completed: { 93 | $set: true 94 | } 95 | } 96 | }) 97 | ``` 98 | 99 | Finally, to update objects, you'll need something like `_.extend` from Underscore, or better, an [`Object.assign`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) polyfill. 100 | 101 | Make sure that you use `Object.assign` correctly. For example, instead of returning something like `Object.assign(state, newData)` from your reducers, return `Object.assign({}, state, newData)`. This way you don't override the previous `state`. 102 | 103 | You can also use the object spread operator proposal for a more succinct syntax: 104 | 105 | ```js 106 | // Before: 107 | return state.map((todo, index) => { 108 | if (index === action.index) { 109 | return Object.assign({}, todo, { 110 | completed: true 111 | }) 112 | } 113 | return todo 114 | }) 115 | 116 | // After: 117 | return state.map((todo, index) => { 118 | if (index === action.index) { 119 | return { ...todo, completed: true } 120 | } 121 | return todo 122 | }) 123 | ``` 124 | 125 | Note that experimental language features are subject to change. 126 | 127 | Also keep an eye out for nested state objects that need to be deeply copied. Both `_.extend` and `Object.assign` make a shallow copy of the state. See [Updating Nested Objects](./usage/structuring-reducers/ImmutableUpdatePatterns.md#updating-nested-objects) for suggestions on how to deal with nested state objects. 128 | 129 | #### Don't forget to call [`dispatch(action)`](api/Store.md#dispatchaction) 130 | 131 | If you define an action creator, calling it will _not_ automatically dispatch the action. For example, this code will do nothing: 132 | 133 | #### `TodoActions.js` 134 | 135 | ```js 136 | export function addTodo(text) { 137 | return { type: 'ADD_TODO', text } 138 | } 139 | ``` 140 | 141 | #### `AddTodo.js` 142 | 143 | ```js 144 | import React, { Component } from 'react' 145 | import { addTodo } from './TodoActions' 146 | 147 | class AddTodo extends Component { 148 | handleClick() { 149 | // Won't work! 150 | addTodo('Fix the issue') 151 | } 152 | 153 | render() { 154 | return 155 | } 156 | } 157 | ``` 158 | 159 | It doesn't work because your action creator is just a function that _returns_ an action. It is up to you to actually dispatch it. We can't bind your action creators to a particular Store instance during the definition because apps that render on the server need a separate Redux store for every request. 160 | 161 | The fix is to call [`dispatch()`](api/Store.md#dispatchaction) method on the [store](api/Store.md) instance: 162 | 163 | ```js 164 | handleClick() { 165 | // Works! (but you need to grab store somehow) 166 | store.dispatch(addTodo('Fix the issue')) 167 | } 168 | ``` 169 | 170 | If you're somewhere deep in the component hierarchy, it is cumbersome to pass the store down manually. This is why [react-redux](https://github.com/gaearon/react-redux) lets you use a `connect` [higher-order component](https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750) that will, apart from subscribing you to a Redux store, inject `dispatch` into your component's props. 171 | 172 | The fixed code looks like this: 173 | 174 | #### `AddTodo.js` 175 | 176 | ```js 177 | import React, { Component } from 'react' 178 | import { connect } from 'react-redux' 179 | import { addTodo } from './TodoActions' 180 | 181 | class AddTodo extends Component { 182 | handleClick() { 183 | // Works! 184 | this.props.dispatch(addTodo('Fix the issue')) 185 | } 186 | 187 | render() { 188 | return 189 | } 190 | } 191 | 192 | // In addition to the state, `connect` puts `dispatch` in our props. 193 | export default connect()(AddTodo) 194 | ``` 195 | 196 | You can then pass `dispatch` down to other components manually, if you want to. 197 | 198 | #### Make sure mapStateToProps is correct 199 | 200 | It's possible you're correctly dispatching an action and applying your reducer but the corresponding state is not being correctly translated into props. 201 | 202 | ## Something else doesn't work 203 | 204 | Ask around on the **#redux** [Reactiflux](https://www.reactiflux.com/) Discord channel, or [create an issue](https://github.com/reduxjs/redux/issues). 205 | 206 | If you figure it out, [edit this document](https://github.com/reduxjs/redux/edit/master/docs/usage/Troubleshooting.md) as a courtesy to the next person having the same problem. 207 | -------------------------------------------------------------------------------- /docs/usage/structuring-reducers/ReusingReducerLogic.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: reusing-reducer-logic 3 | title: Reusing Reducer Logic 4 | description: 'Structuring Reducers > Reusing Reducer Logic: Patterns for creating reusable reducers' 5 | --- 6 | 7 | # Reusing Reducer Logic 8 | 9 | As an application grows, common patterns in reducer logic will start to emerge. You may find several parts of your reducer logic doing the same kinds of work for different types of data, and want to reduce duplication by reusing the same common logic for each data type. Or, you may want to have multiple "instances" of a certain type of data being handled in the store. However, the global structure of a Redux store comes with some trade-offs: it makes it easy to track the overall state of an application, but can also make it harder to "target" actions that need to update a specific piece of state, particularly if you are using `combineReducers`. 10 | 11 | As an example, let's say that we want to track multiple counters in our application, named A, B, and C. We define our initial `counter` reducer, and we use `combineReducers` to set up our state: 12 | 13 | ```js 14 | function counter(state = 0, action) { 15 | switch (action.type) { 16 | case 'INCREMENT': 17 | return state + 1 18 | case 'DECREMENT': 19 | return state - 1 20 | default: 21 | return state 22 | } 23 | } 24 | 25 | const rootReducer = combineReducers({ 26 | counterA: counter, 27 | counterB: counter, 28 | counterC: counter 29 | }) 30 | ``` 31 | 32 | Unfortunately, this setup has a problem. Because `combineReducers` will call each slice reducer with the same action, dispatching `{type : 'INCREMENT'}` will actually cause _all three_ counter values to be incremented, not just one of them. We need some way to wrap the `counter` logic so that we can ensure that only the counter we care about is updated. 33 | 34 | ## Customizing Behavior with Higher-Order Reducers 35 | 36 | As defined in [Splitting Reducer Logic](SplittingReducerLogic.md), a _higher-order reducer_ is a function that takes a reducer function as an argument, and/or returns a new reducer function as a result. It can also be viewed as a "reducer factory". `combineReducers` is one example of a higher-order reducer. We can use this pattern to create specialized versions of our own reducer functions, with each version only responding to specific actions. 37 | 38 | The two most common ways to specialize a reducer are to generate new action constants with a given prefix or suffix, or to attach additional info inside the action object. Here's what those might look like: 39 | 40 | ```js 41 | function createCounterWithNamedType(counterName = '') { 42 | return function counter(state = 0, action) { 43 | switch (action.type) { 44 | case `INCREMENT_${counterName}`: 45 | return state + 1 46 | case `DECREMENT_${counterName}`: 47 | return state - 1 48 | default: 49 | return state 50 | } 51 | } 52 | } 53 | 54 | function createCounterWithNameData(counterName = '') { 55 | return function counter(state = 0, action) { 56 | const { name } = action 57 | if (name !== counterName) return state 58 | 59 | switch (action.type) { 60 | case `INCREMENT`: 61 | return state + 1 62 | case `DECREMENT`: 63 | return state - 1 64 | default: 65 | return state 66 | } 67 | } 68 | } 69 | ``` 70 | 71 | We should now be able to use either of these to generate our specialized counter reducers, and then dispatch actions that will affect the portion of the state that we care about: 72 | 73 | ```js 74 | const rootReducer = combineReducers({ 75 | counterA: createCounterWithNamedType('A'), 76 | counterB: createCounterWithNamedType('B'), 77 | counterC: createCounterWithNamedType('C') 78 | }) 79 | 80 | store.dispatch({ type: 'INCREMENT_B' }) 81 | console.log(store.getState()) 82 | // {counterA : 0, counterB : 1, counterC : 0} 83 | ``` 84 | 85 | We could also vary the approach somewhat, and create a more generic higher-order reducer that accepts both a given reducer function and a name or identifier: 86 | 87 | ```js 88 | function counter(state = 0, action) { 89 | switch (action.type) { 90 | case 'INCREMENT': 91 | return state + 1 92 | case 'DECREMENT': 93 | return state - 1 94 | default: 95 | return state 96 | } 97 | } 98 | 99 | function createNamedWrapperReducer(reducerFunction, reducerName) { 100 | return (state, action) => { 101 | const { name } = action 102 | const isInitializationCall = state === undefined 103 | if (name !== reducerName && !isInitializationCall) return state 104 | 105 | return reducerFunction(state, action) 106 | } 107 | } 108 | 109 | const rootReducer = combineReducers({ 110 | counterA: createNamedWrapperReducer(counter, 'A'), 111 | counterB: createNamedWrapperReducer(counter, 'B'), 112 | counterC: createNamedWrapperReducer(counter, 'C') 113 | }) 114 | ``` 115 | 116 | You could even go as far as to make a generic filtering higher-order reducer: 117 | 118 | ```js 119 | function createFilteredReducer(reducerFunction, reducerPredicate) { 120 | return (state, action) => { 121 | const isInitializationCall = state === undefined; 122 | const shouldRunWrappedReducer = reducerPredicate(action) || isInitializationCall; 123 | return shouldRunWrappedReducer ? reducerFunction(state, action) : state; 124 | } 125 | } 126 | 127 | const rootReducer = combineReducers({ 128 | // check for suffixed strings 129 | counterA : createFilteredReducer(counter, action => action.type.endsWith('_A')), 130 | // check for extra data in the action 131 | counterB : createFilteredReducer(counter, action => action.name === 'B'), 132 | // respond to all 'INCREMENT' actions, but never 'DECREMENT' 133 | counterC : createFilteredReducer(counter, action => action.type === 'INCREMENT') 134 | }; 135 | ``` 136 | 137 | These basic patterns allow you to do things like having multiple instances of a smart connected component within the UI, or reuse common logic for generic capabilities such as pagination or sorting. 138 | 139 | In addition to generating reducers this way, you might also want to generate action creators using the same approach, and could generate them both at the same time with helper functions. See [Action/Reducer Generators](https://github.com/markerikson/redux-ecosystem-links/blob/master/action-reducer-generators.md) and [Reducers](https://github.com/markerikson/redux-ecosystem-links/blob/master/reducers.md) libraries for action/reducer utilities. 140 | 141 | ## Collection / Item Reducer Pattern 142 | 143 | This pattern allows you to have multiple states and use a common reducer to update each state based on an additional parameter inside the action object. 144 | 145 | ```js 146 | function counterReducer(state, action) { 147 | switch(action.type) { 148 | case "INCREMENT" : return state + 1; 149 | case "DECREMENT" : return state - 1; 150 | } 151 | } 152 | 153 | function countersArrayReducer(state, action) { 154 | switch(action.type) { 155 | case "INCREMENT": 156 | case "DECREMENT": 157 | return state.map( (counter, index) => { 158 | if(index !== action.index) return counter; 159 | return counterReducer(counter, action); 160 | }); 161 | default: 162 | return state; 163 | } 164 | } 165 | 166 | function countersMapReducer(state, action) { 167 | switch(action.type) { 168 | case "INCREMENT": 169 | case "DECREMENT": 170 | return { 171 | ...state, 172 | state[action.name] : counterReducer(state[action.name], action) 173 | }; 174 | default: 175 | return state; 176 | } 177 | } 178 | ``` 179 | --------------------------------------------------------------------------------