preface
Redux should be relatively familiar to those on the front end of the React stack. The simplicity of its code and clever design has been praised by everyone. In addition, Redux’s notes are perfect and easy to read. Originally was also forced to read the source code, now also forget almost. I started rereading Redux because I was planning to do some work on it recently, and it was very profitable.
The basic concepts of Redux are not described in detail here. You can refer to the Redux Chinese documentation.
Read the source code
There are a lot of bulls who have provided a lot of reading experience. I feel that it is not advisable to forcibly read the source code at the beginning, as I had read the first time redux, can only say that food tasteless, now forget all.
Should be more familiar with its basic usage, have a question or interest to read it better, combined with documents or examples, the complete process to go.
In addition to direct source repository clone down, local run, really do not understand the breakpoint with go in.
To do not understand the place, may be some methods are not familiar with, at this time to find its specific usage and purpose
Really do not understand can be combined with the online source code examples, and other people’s ideas contrast, see where their understanding of deviation.
In a word, I hope that after reading it, I will be inspired and have a deeper understanding and learning, rather than just saying and reading it.
Redux provides the following methods:
export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose
}
Copy the code
The following article looks at the implementation of each method in the order of the Redux Chinese documentation examples.
The action and actionCreater
Definitions and Concepts
Actions are essentially ordinary JavaScript objects and the actionCreater in Redux is the method that generates the action
/ / is addTodo actionCreater
function addTodo(text) {
// The object of return is action
return {
type: ADD_TODO,
text
}
}
Copy the code
In a traditional Flux implementation, a Dispatch is typically triggered when an action creation function is called. In Redux, you simply pass the results of the action creation function to the Dispatch () method to initiate a dispatch process.
dispatch(addTodo(text))
/ / or
const boundAddTodo = text= > dispatch(addTodo(text))
Copy the code
Of course, in practice, we don’t need to dispatch manually every time (in this case simply synchronizing actionCreater). React-redux provides connect() to do this for us.
With bindActionCreators(), you can automatically bind multiple action creators to the Dispatch () method.
Without touching on Connect, let’s take a look at how bindActionCreators came about.
Before we look, we can venture a guess at what we would do if we were to provide a warper and bind two methods together:
function a (){
/ *... * /
};
function b(f){
/ *... * /
return f()
}
Copy the code
B calls a (forget all else) and binds it with a c
function c(){
return (a)= > b(a)
}
Copy the code
That’s what it looks like. So let’s see how it works
bindActionCreators()
First look at the source code:
// Bind a single actionCreator
function bindActionCreator(actionCreator, dispatch) {
// Dispatch the method to avoid creating manual calls to the action.
return (. args) = >dispatch(actionCreator(... args)) }export default function bindActionCreators(actionCreators, dispatch) {
// function a single actionCreator calls bindActionCreator directly
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
// check, otherwise throw error
if (typeofactionCreators ! = ='object' || actionCreators === null) {
throw new Error(Error message)}// Get the keys array for traversal
var keys = Object.keys(actionCreators)
var boundActionCreators = {}
for (var i = 0; i < keys.length; i++) {
var key = keys[i]
var actionCreator = actionCreators[key]
// Verify the binding in turn
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
/ / return
return boundActionCreators
}
Copy the code
The method is divided into two parts
The first is bindActionCreator
Encapsulate a single ActionCreator method
function bindActionCreator(actionCreator, dispatch) {
// Dispatch the method to avoid creating manual calls to the action.
return (. args) = >dispatch(actionCreator(... args)) }Copy the code
The actionCreators expectation of bindActionCreators is an object, actionCreator, and you can imagine that you would iterate over the properties of that object and call bindActionCreator in turn
The following bindActionCreators action is to handle this object
- Typeof actionCreators === ‘function’ separate method, which ends with a direct call to bindActionCreator
- If it is not an object or null, an error is thrown
- For the object, iterate over the key, get the packaged boundActionCreators and return it
// function a single actionCreator calls bindActionCreator directly
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
// check, otherwise throw error
if (typeofactionCreators ! = ='object' || actionCreators === null) {
throw new Error(Error message)}// Get the keys array for traversal
var keys = Object.keys(actionCreators)
var boundActionCreators = {}
for (var i = 0; i < keys.length; i++) {
var key = keys[i]
var actionCreator = actionCreators[key]
// Verify the binding in turn
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
Copy the code
This results in the bundled actionCreators, without requiring a manual call to Dispatch (in the simple case of synchronization)
reducer
Actions only describe what happens and provide source data. Reducer is needed to deal with the specific actions (detailed introduction will be skipped). In Redux applications, all states are stored in a single object
When reducer processes multiple ATcion, it is rather tedious and needs to be disassembled as follows:
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter
})
case ADD_TODO:
return Object.assign({}, state, {
todos: [
...state.todos,
{
text: action.text,
completed: false}]})case TOGGLE_TODO:
return Object.assign({}, state, {
todos: state.todos.map((todo, index) = > {
if (index === action.index) {
return Object.assign({}, todo, {
completed: !todo.completed
})
}
return todo
})
})
default:
return state
}
}
Copy the code
When splitting is needed, it should be better for each reducer to deal only with the state of the relevant part rather than all the state, for example:
/ / reducer1
function reducer1(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return state.reducer1.a
// Compare state to just state.reducer1, which is obviously better
return state.a
}
Copy the code
Each Reducer is responsible for managing only its share of the global state. State parameters of each Reducer are different, corresponding to the part of state data it manages
In this way, the reducer input parameters need to be managed in the main function, as follows:
function todoApp(state = {}, action) {
return {
visibilityFilter: visibilityFilter(state.visibilityFilter, action),
todos: todos(state.todos, action)
}
}
Copy the code
Of course redux provides the combineReducers() method
import { combineReducers } from 'redux'
const todoApp = combineReducers({
visibilityFilter,
todos
})
Copy the code
So let’s take a look at combineReducers how to achieve
combineReducers
Let me put up the full code
export default function combineReducers(reducers) {
// Obtain the reducer key. If the reducer key is not processed, it is the reducer method name
var reducerKeys = Object.keys(reducers)
var finalReducers = {}
// Construct finalReducers which is the total reducer
for (var i = 0; i < reducerKeys.length; i++) {
var key = reducerKeys[i]
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
var finalReducerKeys = Object.keys(finalReducers)
var sanityError
try {
// check the specification
assertReducerSanity(finalReducers)
} catch (e) {
sanityError = e
}
return function combination(state = {}, action) {
if (sanityError) {
throw sanityError
}
// Alarm information
if(process.env.NODE_ENV ! = ='production') {
var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action)
if (warningMessage) {
warning(warningMessage)
}
}
/** * When action changes, * traverse finalReducers, execute reducer and assign a value to nextState, * return current or nextState ** / based on whether the state of the corresponding key has changed
// state Indicates whether to change the flag
var hasChanged = false
var nextState = {}
// in sequence
for (var i = 0; i < finalReducerKeys.length; i++) {
var key = finalReducerKeys[i]
var reducer = finalReducers[key]
// Get the state attribute of the corresponding key
var previousStateForKey = state[key]
// Only the corresponding key data is processed
var nextStateForKey = reducer(previousStateForKey, action)
// Cannot return undefined, otherwise throw error
if (typeof nextStateForKey === 'undefined') {
var errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
// Assign the new state to the nextState object
nextState[key] = nextStateForKey
// Whether to change the processinghasChanged = hasChanged || nextStateForKey ! == previousStateForKey }// Return state as appropriate
return hasChanged ? nextState : state
}
}
Copy the code
The ginseng
Let’s start with the input parameter: reducers
- That is the set of reducer objects that need to be merged.
- This can be obtained by importing * as reducers
- The reducer should also handle the default case. If state is undefined or undefined action, it should not return undefined. What is returned is a total Reducer that can call each incoming method and pass in the corresponding state properties.
Traverse the reducers
Since it’s a collection of objects, we’re going to iterate over them, so the first few steps are going to do that.
The objective of the reducer key is to process the state of the corresponding key for each submethod
var reducerKeys = Object.keys(reducers)
var finalReducers = {}
// Construct finalReducers which is the total reducer
for (var i = 0; i < reducerKeys.length; i++) {
var key = reducerKeys[i]
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
// Get finalReducers for the following traversal call
var finalReducerKeys = Object.keys(finalReducers)
Copy the code
Then there is the specification validation, which is required as a framework and can be skipped
combination
Return a function
-
When the action is dispatched, this method mainly distributes different states to the corresponding Reducer process and returns the latest state
-
First, identify the variable:
// state Indicates whether to change the flag
var hasChanged = false
var nextState = {}
Copy the code
-
Do the traversal finalReducers to save the original previousStateForKey
-
Then distribute corresponding attributes to corresponding reducer for processing to obtain nextStateForKey
Check nextStateForKey first. Because reducer requires compatibility, undefined is not allowed, and errors are cast if undefined.
Normally nextState for Key is assigned to the corresponding key of nextState
-
Compare the two states to see if they are equal. If they are equal, hasChanged is set to true. At the end of the loop, a new state, nextState, is obtained
for (var i = 0; i < finalReducerKeys.length; i++) {
var key = finalReducerKeys[i]
var reducer = finalReducers[key]
// Get the state attribute of the corresponding key
var previousStateForKey = state[key]
// Only the corresponding key data is processed
var nextStateForKey = reducer(previousStateForKey, action)
// Cannot return undefined, otherwise throw error
if (typeof nextStateForKey === 'undefined') {
/ /...
}
// Assign the new state to the nextState object
nextState[key] = nextStateForKey
// Whether to change the processinghasChanged = hasChanged || nextStateForKey ! == previousStateForKey }Copy the code
Return the old and new states according to hasChanged.
// Return state as appropriate
return hasChanged ? nextState : state
Copy the code
Here the combineReducers end.
conclusion
Share half this time, which is a bit too much, and record the rest next time. Let’s improve ourselves and learn together.
Refer to the article
Redux Chinese document