Introduction
Redux is one of the most used state management libraries available today for web applications. Most of the developers use that, but did not know how it works behind the scenes.
Some time ago I decided to read the Redux codebase, to better understand the implementation of the library that I used in some jobs. In this work, I did some notes, and this article is a more complete version of that.
Disclaimer: This article tries to be a deep dive into Redux. It is not a tutorial and it requires a basic knowledge about Redux, the idea here is to understand the internals and not teach how to use.
Я создаю нативное приложение для реагирования, но получаю следующую ошибку Error: Expected the root reducer to be a function. Instead, received: 'undefined'
У меня есть файл index.js, который экспортирует редукторы.
import { combineReducers } from "redux";
const Reducers = combineReducers();
export default Reducers;
В файле App.js я создаю магазин const store = createStore(rootReducers, composeWithDevTools(applyMiddleware(thunk)));
Но когда я запускаю expo start
, я получаю указанную выше ошибку. Я экспортирую функцию редуктора, верно?
When running with npm run dev
and loading the first page, I got:
[1] Error: Expected the reducer to be a function.
[1] at createStore (/somedir/node_modules/redux/lib/createStore.js:63:11)
[1] at /somedir/node_modules/redux/lib/applyMiddleware.js:37:19
[1] at createStore (create.js:25:17)
[1] at server.js:71:17
[1] at Layer.handle [as handle_request] (/somedir/node_modules/express/lib/router/layer.js:95:5)
[1] at trim_prefix (/somedir/node_modules/express/lib/router/index.js:312:13)
[1] at /somedir/node_modules/express/lib/router/index.js:280:7
[1] at Function.process_params (/somedir/node_modules/express/lib/router/index.js:330:12)
[1] at next (/somedir/node_modules/express/lib/router/index.js:271:10)
[1] at SendStream.error (/somedir/node_modules/serve-static/index.js:120:7)
This seems to be due to CommonJS require method in src/redux/create.js returning an object instead of the ES6 import’s function:
const reducer = import reducer from './modules/reducer';
Instead, this fixed it for me:
import reducer from './modules/reducer';
Are there any unwanted side effects with this change? Should I issues a pull request?
Да, вы экспортируете функцию редуктора. Однако вы неправильно используете функцию combReducers :
Вспомогательная функция combReducers превращает объект, значения которого являются различными функциями сокращения, в единую функцию сокращения, которую вы можете передать в createStore.
Функция ожидает единственный аргумент:
Объект, значения которого соответствуют разным редукционным функциям, которые необходимо объединить в одну.
Вы звоните
combineReducers
без аргументов, поэтому первый аргумент становится
undefined
, поэтому вы получаете
Error: Expected the root reducer to be a function. Instead, received: 'undefined'
. Вот игрушечный пример того, как его можно использовать:
export default function firstReducer(state = {}, action) {
switch (action.type) {
// do something based on the action.type
default:
// default case just returns the state
return state;
}
}
export default function secondReducer(state = {}, action) {
switch (action.type) {
// do something based on the action.type
default:
// default case just returns the state
return state;
}
}
rootReducer = combineReducers({first: firstReducer, second: secondReducer})
// This would produce the following state object
{
first: {
// state managed by firstReducer
},
second: {
// state managed by secondReducer
}
}
Я попробовал простой пример реакции, редукса, ajax и следовал учебному пособию по Reddit API, но Я не могу создать магазин и получить ошибку:
Uncaught Error: Expected the reducer to be a function.
import { createStore, applyMiddleware } from 'redux'
var thunkMiddleware = require('redux-thunk');
var createLogger = require('redux-logger');
var rootReducer = require('./reducers.js');
const loggerMiddleware = createLogger();
function configureStore(initialState) {
return createStore(
rootReducer,
initialState,
applyMiddleware(
thunkMiddleware,
loggerMiddleware
)
)
}
const store = configureStore();
< Сильный > rootReducer.js
import { combineReducers } from 'redux';
function products(state = {
isFetching: false,
didInvalidate: false,
items: []
}, action) {
switch (action.type) {
case 'REQUEST_PRODUCTS':
return Object.assign({}, state, {
isFetching: true,
didInvalidate: false
})
case 'RECEIVE_PRODUCTS':
return Object.assign({}, state, {
isFetching: false,
didInvalidate: false,
items: action.posts,
lastUpdated: action.receivedAt
})
default:
return state
}
}
function specialPosts(state = { }, action) {
switch (action.type) {
case RECEIVE_SPECPOSTS:
case REQUEST_SPECPOSTS:
return Object.assign({}, state, {
req: true
})
default:
return state
}
}
const rootReducer = combineReducers({
products,
specialPosts
});
export default rootReducer;
Тип rootReducer является объектом, но почему? Должен ли я изменить функцию createStore
на rootReducer.default
?
return createStore(
rootReducer.default,
initialState,
applyMiddleware(
thunkMiddleware,
loggerMiddleware
)
)
"redux-logger": "^2.6.1",
"react-redux": "^4.4.1",
"react-redux-provide": "^5.2.3",
"redux": "^3.3.1",
"redux-thunk": "^2.0.1",
I stuck with this error:
Uncaught Error: Expected the root reducer to be a function. Instead, received:»
I’ve tried every answer I could find but none of them worked i hope you guys can help me
import {combineReducers} from 'redux'
import changeCategoryReducer from './changeCategoryReducer'
import categoryListReducer from './categoryListReducer'
import productListReducer from './productListReducer'
import cartReducer from './cartReducer'
import saveProductReducer from './saveProductReducer'
const rootReducer = combineReducers({
changeCategoryReducer,
categoryListReducer,
productListReducer,
cartReducer,
saveProductReducer
});
export default rootReducer;
import {createStore, applyMiddleware} from 'redux'
import * as rootReducer from './index.js'
import thunk from 'redux-thunk'
export default function configureStore(){
return createStore(rootReducer, applyMiddleware(thunk))
}
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './components/root/App';
import reportWebVitals from './reportWebVitals';
import 'bootstrap/dist/css/bootstrap.min.css';
import { Provider } from 'react-redux'
import configureStore from './redux/reducers/configureStore'
import 'alertifyjs/build/css/alertify.min.css'
import { BrowserRouter } from 'react-router-dom'
const store = configureStore();
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<BrowserRouter>
<Provider store={store}>
<App />
</Provider>
</BrowserRouter>
</React.StrictMode>
);
reportWebVitals();
You’re not correctly importing your rootReducer
in the store creation.
Instead of importing *
, you should import the default
export:
import {createStore, applyMiddleware} from 'redux'
import rootReducer from './index.js' // <-- change here
import thunk from 'redux-thunk'
export default function configureStore(){
return createStore(rootReducer, applyMiddleware(thunk))
}
using import * as rootReducer from './index.js'
means you end up with an object like:
rootReducer: {
default: createReducer(...)
}
* Creates a store enhancer that applies middleware to the dispatch method
* of the Redux store. This is handy for a variety of tasks, such as expressing
* asynchronous actions in a concise manner, or logging every action payload.
* See `redux-thunk` package as an example of the Redux middleware.
* Because middleware is potentially asynchronous, this should be the first
* store enhancer in the composition chain.
* Note that each middleware will be given the `dispatch` and `getState` functions
* as named arguments.
* Turns an object whose values are action creators, into an object with the
* same keys, but with every function wrapped into a `dispatch` call so they
* may be invoked directly. This is just a convenience method, as you can call
* `store.dispatch(MyActionCreators.doSomething())` yourself just fine.
* For convenience, you can also pass a single function as the first argument,
* and get a function in return.
* creator functions. One handy way to obtain it is to use ES6 `import * as`
* syntax. You may also pass a single function.
* every action creator wrapped into the `dispatch` call. If you passed a
* function as `actionCreators`, the return value will also be a single
// eslint-disable-line no-eq-null
‘bindActionCreators expected an object or a function, instead received ‘
‘Did you write «import ActionCreators from» instead of «import * as ActionCreators from»?’
* These are private action types reserved by Redux.
* For any unknown actions, you must return the current state.
* If the current state is undefined, you must return the initial state.
* Do not reference these action types directly in your code.
* Creates a Redux store that holds the state tree.
* The only way to change the data in the store is to call `dispatch()` on it.
* There should only be a single store in your app. To specify how different
* parts of the state tree respond to actions, you may combine several reducers
* into a single reducer function by using `combineReducers`.
* the current state tree and the action to handle.
* to hydrate the state from the server in universal apps, or to restore a
* If you use `combineReducers` to produce the root reducer function, this must be
* an object with the same shape as `combineReducers` keys.
* and subscribe to changes.
‘Expected the reducer to be a function.’
* Reads the state tree managed by the store.
* Adds a change listener. It will be called any time an action is dispatched,
* and some part of the state tree may potentially have changed. You may then
* call `getState()` to read the current state tree inside the callback.
* Dispatches an action. It is the only way to trigger a state change.
* The `reducer` function, used to create the store, will be called with the
* current state tree and the given `action`. Its return value will
* be considered the **next** state of the tree, and the change listeners
* will be notified.
* The base implementation only supports plain object actions. If you want to
* dispatch a Promise, an Observable, a thunk, or something else, you need to
* wrap your store creating function into the corresponding middleware. For
* example, see the documentation for the `redux-thunk` package. Even the
* middleware will eventually dispatch plain object actions using this method.
* sessions, or use the time travelling `redux-devtools`. An action must have
* a `type` property which may not be `undefined`. It is a good idea to use
* string constants for action types.
* Note that, if you use a custom middleware, it may wrap `dispatch()` to
* return something else (for example, a Promise you can await).
‘Actions must be plain objects. ‘
‘Use custom middleware for async actions.’
‘Actions may not have an undefined «type» property. ‘
‘Have you misspelled a constant?’
‘Reducers may not dispatch actions.’
* Replaces the reducer currently used by the store to calculate the state.
* You might need this if your app implements code splitting and you want to
* load some of the reducers dynamically. You might also need this if you
* implement a hot reloading mechanism for Redux.
// When a store is created, an «INIT» action is dispatched so that every
// reducer returns their initial state. This effectively populates
// the initial state tree.
/* eslint-disable no-console */
‘» returned undefined handling ‘
‘To ignore an action, you must explicitly return the previous state.’
‘initialState argument passed to createStore’
‘previous state received by the reducer’
‘Store does not have a valid reducer. Make sure the argument passed ‘
‘to combineReducers is an object whose values are reducers.’
‘ has unexpected type of «‘
‘» found in ‘
‘Expected to find one of the known reducer keys instead: ‘
‘». Unexpected keys will be ignored.’
‘» returned undefined during initialization. ‘
‘If the state passed to the reducer is undefined, you must ‘
‘explicitly return the initial state. The initial state may ‘
‘not be undefined.’
‘» returned undefined when probed with a random type. ‘
‘Don\’t try to handle ‘
‘ or other actions in «redux/*» ‘
‘namespace. They are considered private. Instead, you must return the ‘
‘current state for any unknown actions, unless it is undefined, ‘
‘in which case you must return the initial state, regardless of the ‘
‘action type. The initial state may not be undefined.’
* Turns an object whose values are different reducer functions, into a single
* reducer function. It will call every child reducer, and gather their results
* into a single state object, whose keys correspond to the keys of the passed
* reducer functions.
* reducer functions that need to be combined into one. One handy way to obtain
* it is to use ES6 `import * as reducers` syntax. The reducers may never return
* undefined for any action. Instead, they should return their initial state
* if the state passed to them was undefined, and the current state for any
* unrecognized action.
* passed object, and builds a state object with the same shape.
* **We recommend using the `configureStore` method
* Redux Toolkit is our recommended approach for writing Redux logic today,
* including store setup, reducers, data fetching, and more.
* `configureStore` from Redux Toolkit is an improved version of `createStore` that
* simplifies setup and helps avoid common bugs.
* You should not be using the `redux` core package by itself today, except for learning purposes.
* The `createStore` method from the core `redux` package will not be removed, but we encourage
* If you want to use `createStore` without this visual deprecation warning, use
* the `legacy_createStore` import instead:
* **We recommend using the `configureStore` method
* Redux Toolkit is our recommended approach for writing Redux logic today,
* including store setup, reducers, data fetching, and more.
* `configureStore` from Redux Toolkit is an improved version of `createStore` that
* simplifies setup and helps avoid common bugs.
* You should not be using the `redux` core package by itself today, except for learning purposes.
* The `createStore` method from the core `redux` package will not be removed, but we encourage
* If you want to use `createStore` without this visual deprecation warning, use
* the `legacy_createStore` import instead:
‘It looks like you are passing several store enhancers to ‘
‘createStore(). This is not supported. Instead, compose them ‘
‘together to a single function. See https://redux.js.org/tutorials/fundamentals/part-4-store#creating-a-store-with-enhancers for an example.’
* This makes a shallow copy of currentListeners so we can use
* nextListeners as a temporary list while dispatching.
* This prevents any bugs around consumers calling
* subscribe/unsubscribe in the middle of a dispatch.
* Reads the state tree managed by the store.
‘You may not call store.getState() while the reducer is executing. ‘
‘The reducer has already received the state as an argument. ‘
‘Pass it down from the top reducer instead of reading it from the store.’
* Adds a change listener. It will be called any time an action is dispatched,
* and some part of the state tree may potentially have changed. You may then
* call `getState()` to read the current state tree inside the callback.
* 1. The subscriptions are snapshotted just before every `dispatch()` call.
* If you subscribe or unsubscribe while the listeners are being invoked, this
* will not have any effect on the `dispatch()` that is currently in progress.
* However, the next `dispatch()` call, whether nested or not, will use a more
* recent snapshot of the subscription list.
* 2. The listener should not expect to see all state changes, as the state
* might have been updated multiple times during a nested `dispatch()` before
* the listener is called. It is, however, guaranteed that all subscribers
* registered before the `dispatch()` started will be called with the latest
* state by the time it exits.
‘You may not call store.subscribe() while the reducer is executing. ‘
‘If you would like to be notified after the store has been updated, subscribe from a ‘
‘component and invoke store.getState() in the callback to access the latest state. ‘
‘You may not unsubscribe from a store listener while the reducer is executing. ‘
* Dispatches an action. It is the only way to trigger a state change.
* The `reducer` function, used to create the store, will be called with the
* current state tree and the given `action`. Its return value will
* be considered the **next** state of the tree, and the change listeners
* will be notified.
* The base implementation only supports plain object actions. If you want to
* dispatch a Promise, an Observable, a thunk, or something else, you need to
* wrap your store creating function into the corresponding middleware. For
* example, see the documentation for the `redux-thunk` package. Even the
* middleware will eventually dispatch plain object actions using this method.
* sessions, or use the time travelling `redux-devtools`. An action must have
* a `type` property which may not be `undefined`. It is a good idea to use
* string constants for action types.
* Note that, if you use a custom middleware, it may wrap `dispatch()` to
* return something else (for example, a Promise you can await).
‘. You may need to add middleware to your store setup to handle dispatching other values, such as ‘redux-thunk’ to handle dispatching functions. See https://redux.js.org/tutorials/fundamentals/part-4-store#middleware and https://redux.js.org/tutorials/fundamentals/part-6-async-logic#using-the-redux-thunk-middleware for examples.`
‘Actions may not have an undefined «type» property. You may have misspelled an action type string constant.’
‘Reducers may not dispatch actions.’
* Replaces the reducer currently used by the store to calculate the state.
* You might need this if your app implements code splitting and you want to
* load some of the reducers dynamically. You might also need this if you
* implement a hot reloading mechanism for Redux.
// This action has a similar effect to ActionTypes.INIT.
// Any reducers that existed in both the new and old rootReducer
// will receive the previous state. This effectively populates
// the new state tree with any relevant data from the old one.
* Interoperability point for observable/reactive libraries.
* The minimal observable subscription method.
* The observer object should have a `next` method.
* be used to unsubscribe the observable from the store, and prevent further
* emission of values from the observable.
// When a store is created, an «INIT» action is dispatched so that every
// reducer returns their initial state. This effectively populates
// the initial state tree.
* Creates a Redux store that holds the state tree.
* **We recommend using `configureStore` from the
* The only way to change the data in the store is to call `dispatch()` on it.
* There should only be a single store in your app. To specify how different
* parts of the state tree respond to actions, you may combine several reducers
* into a single reducer function by using `combineReducers`.
* the current state tree and the action to handle.
* to hydrate the state from the server in universal apps, or to restore a
* If you use `combineReducers` to produce the root reducer function, this must be
* an object with the same shape as `combineReducers` keys.
* to enhance the store with third-party capabilities such as middleware,
* time travel, persistence, etc. The only store enhancer that ships with Redux
* is `applyMiddleware()`.
* and subscribe to changes.
* Creates a Redux store that holds the state tree.
* **We recommend using `configureStore` from the
* The only way to change the data in the store is to call `dispatch()` on it.
* There should only be a single store in your app. To specify how different
* parts of the state tree respond to actions, you may combine several reducers
* into a single reducer function by using `combineReducers`.
* the current state tree and the action to handle.
* to hydrate the state from the server in universal apps, or to restore a
* If you use `combineReducers` to produce the root reducer function, this must be
* an object with the same shape as `combineReducers` keys.
* to enhance the store with third-party capabilities such as middleware,
* time travel, persistence, etc. The only store enhancer that ships with Redux
* is `applyMiddleware()`.
* and subscribe to changes.
5 ответов
const rootReducer = combineReducers({
products,
specialPosts
});
const store = createStore( rootReducer, applyMiddleware( thunkMiddleware ));
2 Апр 2016 в 19:56
Проблема была в том, что rootReducer был импортирован командой «require» (ES5):
var rootReducer = require('./reducers.js');
Если вы импортируете его с помощью метода ES6, он автоматически сохранит файл rootReducer.js по умолчанию автоматически в rootReducer, как вы и ожидали:
import rootReducer from './reducers';
17 Апр 2016 в 13:42
Я использую VS Code Insiders, и иногда изменения не сохраняются правильно. Поэтому, если вы выполнили все вышеперечисленное и ошибка все еще существует, перейдите к каждому файлу и нажмите STRG + S. Это решило проблему для меня.
4 Янв 2018 в 10:07
import {createStore,applyMiddleware} from 'redux';
export default function configureStore(initialState) {
return createStore(
[],
{},
applyMiddleware()
);
}
import {createStore,applyMiddleware} from 'redux';
export default function configureStore(initialState) {
return createStore(
()=>[],
{},
applyMiddleware()
);
}
24 Янв 2019 в 07:06
3 ответа
Вы не передаете никакого редуктора в свой combineReducers()
. Добавьте два или более редьюсера в функцию combReducers. Нравится
const rootReducer = combineReducers({
appReducer: appReducer,
authReducer: authReducer,
});
25 Авг 2021 в 16:44
Да, вы экспортируете функцию редуктора. Однако вы неправильно используете функцию combineReducers:
Вспомогательная функция combReducers превращает объект, значениями которого являются различные функции сокращения, в единую функцию уменьшения, которую вы можете передать в createStore.
Функция combineReducers
ожидает один аргумент:
Объект, значения которого соответствуют разным редуцирующим функциям, которые необходимо объединить в одну.
Вы вызываете combineReducers
без каких-либо аргументов, поэтому первый аргумент становится undefined
, поэтому вы получаете Error: Expected the root reducer to be a function. Instead, received: 'undefined'
. Вот игрушечный пример того, как его можно использовать:
export default function firstReducer(state = {}, action) {
switch (action.type) {
// do something based on the action.type
default:
// default case just returns the state
return state;
}
}
export default function secondReducer(state = {}, action) {
switch (action.type) {
// do something based on the action.type
default:
// default case just returns the state
return state;
}
}
rootReducer = combineReducers({first: firstReducer, second: secondReducer})
// This would produce the following state object
{
first: {
// state managed by firstReducer
},
second: {
// state managed by secondReducer
}
}
25 Авг 2021 в 17:02
Я столкнулся с той же проблемой. В моем случае проблема заключалась в неправильном импорте редуктора. Если вы написали rootReducer в reducers.js следующим образом:
export function rootReducer() {
...
}
Пожалуйста, используйте его при создании такого магазина.
import { rootReducer } from './reducers';
const store = createStore(rootReducers);
Но если вы создали rootReducer вот так
export default function rootReducer() {
...
}
Вы должны импортировать его без фигурных скобок.
import rootReducer from './reducers';
const store = createStore(rootReducers);
5 Янв 2022 в 03:02
Dispatch
As shown on this basic example of Redux Documentation (https://redux.js.org/introduction/getting-started#basic-example), after using createStore
and having the store object available, the way to dispatch an action is to call the dispatch
method.
`Actions must be plain objects. Instead, the actual type was: '
'. You may need to add middleware to your store setup to handle dispatching other values, such as 'redux-thunk' to handle dispatching functions. See https://redux.js.org/tutorials/fundamentals/part-4-store#middleware and https://redux.js.org/tutorials/fundamentals/part-6-async-logic#using-the-redux-thunk-middleware for examples.`
Actions may not have an undefined "type" property. You may have misspelled an action type string constant.
Reducers may not dispatch actions.
The dispatch method is a simple function with only one objective, update the state.
`Actions must be plain objects. Instead, the actual type was: '
'. You may need to add middleware to your store setup to handle dispatching other values, such as 'redux-thunk' to handle dispatching functions. See https://redux.js.org/tutorials/fundamentals/part-4-store#middleware and https://redux.js.org/tutorials/fundamentals/part-6-async-logic#using-the-redux-thunk-middleware for examples.`
Actions may not have an undefined "type" property. You may have misspelled an action type string constant.
After the verifications, they do a try statement to update the state. First, they update the isDispatching flag to true (as we explained above), and then, they call the reducer function passing the last version of the state variable and the action object.
The reducer will get the type of the action and based on that, will create a new version of the state. Then, they return this new state and that is assigned to the currentState
variable.
This part of the code is inside a try statement, so basically, if the reducer function throws any error, this does not break the redux work. This makes the code safer on runtime. Finally, they update the isDispatching
to false, to maintain that work of the isDispatching
flag verifications.
Another important point here, that explains the reason that Redux documentation says that the reducer function has to be a pure function, can be understood here. As you can see, Redux uses a simple variable to hold the state and use this variable as argument to the reducer function.
As the state is an object, it is a reference pointer, so if you mutate the argument on the reducer function, you mutate the currentState
variable that is inside the store. And as the return of the reducer will be assigned to the currentState
variable, if you mutate that, will basically set to the same reference that was assigned before.
It generates some issues as:
- Break time-travel features because all state changes, that should create different state ‘versions’, will be always the same, with the same content.
- Can cause bugs related with the huge number of mutations and reassign to the same reference at the same time.
- Can impact on changes verification, because some libraries, such as react-redux, for example, use shallow equality as the way to compare changes, as shallow equality relies on reference comparison, sometimes the state changed, but will not cause updates and re-renders.
After all this state update, they need to run the listeners to notify the subscribers that the state changed. We will talk more about this in the next section.
IsDispatching FLAG
The redux library has a lot of verifications, but one appears a lot of times: this is the verification of isDispatching
. The idea of that is to prevent changes on the variables when the dispatch function is being called. The point is to prevent bugs with changes being made on the execution.
The default value is false. The value is changed to true inside the try that updates the state. At that moment, if other methods as getState
, subscribe
, unsubscribe
, dispatch
are called, this function has verifications that throw an error, warning that these methods can not be executed correctly at that time.
See an example of isDispatching verification below:
You may not call store.getState() while the reducer is executing.
The reducer has already received the state as an argument.
Pass it down from the top reducer instead of reading it from the store.
Returning to the dataflow, it can be divided in 2 big parts:
- Dispatch action and update state.
- Notify state change to subscribers.
Notify
The notification process of Redux is made by the method called subscribe
. It is basically an observer design pattern, this method allows adding a listener function that is executed after a state update.
We can see the hole code of the subscribe
method below:
`Expected the listener to be a function. Instead, received: '
You may not call store.subscribe() while the reducer is executing.
If you would like to be notified after the store has been updated, subscribe from a
component and invoke store.getState() in the callback to access the latest state.
You may not unsubscribe from a store listener while the reducer is executing.
In the subscribe method, first, it is made 2 basic verifications, one for the isDispatching
and another to the listener argument, verifying if the type of the argument is really a function, to make sure that it will not break when it is called on state changes.
Then, it came to the main point of this function: add a new listener as a subscriber.
To do that, first they create a variable called isSubscribed
assigning to true. The idea of this variable is to keep the internal state of that listener on the subscribers array, if it is there or not. It is important to notice that the return of the subscribe
function is an unsubscribe
function.
So, using the concept of closure, this variable isSubscribed
is held in this unsubscribe
function. The idea is use this variable as a verification, if the listener is subscribed, the function executes the work to remove this listener from the array, if not, then do nothing.
Besides that, other 2 functions are executed:
- One called
ensureCanMutateNextListeners
- The push of the
nextListeners
array, that actually adds the listener to be executed in the future.
About the ensureCanMutateNextListeners
:
To understand this function, we need to understand the difference between currentListeners and nextListeners.
-
currentListeners
: is the variable that keeps the listeners that are being executed or that were executed on runtime. -
nextListeners
: is the variable that keeps the next version of listeners to be executed. This is the variable that gives the push on the subscribe function, to add a new listener. On the dispatch function, after the state update, the currentListener receives the reference of nextListeners, so if there are new listeners, they will be executed.
The point of this function is that after the dispatch, the nextListeners
and currentListeners
are basically the same, pointing to the same reference. The issue is that if we just give a push to nextListeners
, we are affecting the currentListeners
variable and if a dispatch is happening at that moment, it can cause bugs.
To avoid that, they created this ensureCanMutateNextListeners
function.The idea is just do a shallow copy of currentListeners
, creating a new reference. This way, if we update nextListeners
, we do not affect currentListeners
.
Finally, to close the notify process, on dispatch
function, after the state update, all the actual listeners callbacks are called.
As explained above, the currentListeners receive the nextListeners reference and this is assigned in the listeners variable. Then, they use a simple for loop to call all the listeners. This way, redux notifies all subscribers that a state update happened.
Dataflow
The data flow of Redux is the base of the library. It is one of the first things that we learn when we start to study Redux.
You dispatch an action, that is a plain object, to the store. This updates the state using the reducer function and this new state returns to the application, updating the UI.
One important thing to understand here is the architecture of Redux. It consists of the core that handles the basic features, such as dispatch actions, update the store and notify the state updates.
Another part is the bindings, the most popular one that is supported by the Redux core team is the React one, called react-redux. This module connects the Redux core to react applications, creating HOC and Hooks that the react developers use to develop the UIs in the end.
Our focus in this article will be the Redux core. Mainly, the store object. There is the place where the state tree is created and where it is provided the dispatch
and subscribe
methods. The both are the most important methods to Redux data flow work.
To create the store, you have a function called createStore
. This function accepts 3 arguments:
- the reducer function.
- the preloaded state object or most known as initialState. This is useful for universal apps or SSR applications, because it allows the user to add a first state before the hydration process. Another use for this is when some library stores the state in local storage and reloads the state in the next section.
- the enhancer (this is the argument that allows the use of middlewares, and will be the theme of another article).
In the creation of the store, the function does a lot of verifications to see if the reducer passed is really a function and if the preloadedState is a real object.
It looks like you are passing several store enhancers to
createStore(). This is not supported. Instead, compose them
together to a single function. See https://redux.js.org/tutorials/fundamentals/part-4-store#creating-a-store-with-enhancers for an example.
`Expected the root reducer to be a function. Instead, received: '
Then, the function returns the store object.
Internally, they create some important variables, these variables work as properties of the store object.
- currentReducer: this variable receives the reducer function argument, that will be used to create the new state.
-
currentState: this variable will keep the state itself, it starts receiving the
preloadedState
, but can be updated by other methods. - currentListeners: this variable keeps the array of listeners, that is callback functions that are executed when the state is updated. (we will dive deep into this topic later in this article).
- nextListeners: this variable works as a temporary list to new listeners, to avoid some bugs when new listeners when a dispatch or notify work is in progress.
RootReducer
/* index.js */
import AppReducer from './reducers';
// import {AppReducer} from './reducers';
// bugs : must be a root reducer, can not be use as a module
/* reducers.js */
// initial state
const initialState = {
angular: 0,
react: 0,
vuejs: 0
};
// reducers update state
const AppReducers = (state = initialState, action) => {
switch (action.type) {
case 'VOTE_ANGULAR':
console.log("Vote Angular!");
return (
Object.assign(
{},
state,
{
angular: state.angular + 1
}
)
);
case 'VOTE_REACT':
console.log("Vote React!");
return (
Object.assign(
{},
state,
{
react: state.react + 1
}
)
);
case 'VOTE_VUEJS':
console.log("Vote Vue.jsc!");
return (
Object.assign(
{},
state,
{
vuejs: state.vuejs + 1
}
)
);
default:
return state;
}
};
export {AppReducers};
export default AppReducers;
7 Июл 2017 в 04:11
Get state
Imagine that a subscriber is called after a state update and wants to use the new state on the UI. How to do this? There is a function called getState
.
You may not call store.getState() while the reducer is executing.
The reducer has already received the state as an argument.
Pass it down from the top reducer instead of reading it from the store.
This function is the simplest of the entire library. A basic verification about the isDispatching
is executed and after that, it is just returned the currentState
variable.