Redux has recently studied the source code and remains small and focused as part of the React stack. There are only a few hundred lines of code, which includes state machines, publish-subscribe patterns, and higher-order functions, and it’s certainly worth learning from. (The following source slightly modified and annotated)
1. Three Principles
Single data source
The state of the entire application is stored in an object tree, and this object tree is unique
State is read-only
The only way to change state is to fire an action, which is a normal object with an event
Use pure functions to perform modifications
To describe how an action changes the state tree, we need to reducer
Source directory
util/isPlainObject
// Check whether it is a pure object, not a constructorexport default function isPlainObject(obj) {
if(typeof obj ! ='object' || obj === null) {
return false;
}
letproto = obj; // get the obj final _proto_ pointerwhile(Object.getPrototypeOf(proto)) {
proto = Object.getPrototypeOf(proto)
}
return Object.getPrototypeOf(obj) === proto;
}
Copy the code
This pure object determination determines whether the initial _proto_ reference and the final _proto_ reference of the object are the same
util/actionTypes.js
const ActionTypes = {
INIT: '@@redux/INIT'
}
export default ActionTypes;
Copy the code
createStore.js
As the C bit in REdux, the methods include getState, Dispatch, subscribe methods
export default function createStore(reducer, preloadedState) {
if(typeof reducer ! ='function') {
throw new Error('Reducer is not a function')}letcurrentReducer = reducer; // State handlerletcurrentState = preloadedState; // Initial stateletcurrentListeners = []; // Listen on the function queuefunction getState() {
return currentState;
}
function dispatch(action) {
if(! isPlainObject(action)) { throw new Error('Action is not pure object')}if(typeof action.type == 'undefined') {
throw new Error('Action type undefined'CurrentState = createReducer(currentState, action); // Publish subscribe modefor(let i=0; i<currentListeners.length; i++) {
const listener = currentListeners[i];
listener();
}
returnaction; } / / subscribefunction subscribe(listener) {
let subscribed = true; currentListeners.push(listener); // Unsubscribereturn function unsubscribe() {// Repeat subscription optimizationif(! subscribed) {return ;
}
const index = currentListeners.indexOf(listener);
currentListeners.splice(index, 1);
subscirbed = false; }} // create store initialization value dispatch({type:ActionTypes.INIT});
return{getState,// return status dispatch,// dispatch action subscirbe}}Copy the code
combineReducers.js
Merge multiple reducer into one object for easy maintenance
export default function(reducers) {// Return an array of reducers names const reducerKeys = object.keys (reducers);return function (state = {}, action) {
const nextState = {};
for(leti = 0; i < reducerKeys.length; i++) { const key = reducerKeys[i]; const reducer = reducers[key]; const previousStateForKey = state[key]; // Old state const nextStateForKey = Reducer (previousStateForKey, action); // New state nextState[key] = nextStateForKey; }returnnextState; }}Copy the code
bindActionCreators.js
Integrate dispatches into actions
function bindActionCreator(actionCreator, dispatch) {
return function() {
returndispatch(actionCreator.apply(this, The arguments)}} / * * * * @ param {Function | Object} actionCreators * @ param {Function} dispatch of redux Store * effective dispatch method @ returns {Function | Object} if return objects similar to the original Object, but each Object element to add the * / dispatch methodexport default function bindActionCreators(actionCreators, dispatch) {
if(typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
if(typeof actionCreators ! = ='object' || actionCreators === null) {
throw new Error(
`bindShould ActionCreators be an object or function type and not receive ${ActionCreators === null?'null': Typeof actionCreators}. '+' is not quoted as such"import ActionCreators from"Rather than"import * as ActionCreators from"? ')} // Const boundActionCreators = {}for (const key in actionCreators) {
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
Copy the code
compose.js
In this part, reduce method and high-order function are flexibly applied to realize the execution of parameter function from right to left
/** * * @param {... Any} funcs * @returns contains function arguments executed from right to left. For example, compose(f, g, h) equals (... args) => f(g(h(... args))) */export default functioncompose(... funcs) {if(funcs.length === 0) {
return arg => arg
}
if(funcs.length === 1) {
return funcs[0]
}
returnfuncs.reduce((a, b) => (... args) => a(b(... args))) }Copy the code
applyMiddleware.js
import compose from './compose'
export default functionapplyMiddleware(... middlewares) {returncreateStore => (... args) => { const store = createStore(... args)let dispatch = () => {
throw new Error('Do not run dispatches while constructing middle keys, otherwise other middle keys will not receive dispatches') } const middlewareAPI = { getState: store.getState, dispatch: (... args) => dispatch(... args) } const chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(... chain)(store.dispatch)return {
...store,
dispatch
}
}
}
Copy the code
index.js
import createStore from './createStore';
import combineReducers from './combineReducers';
import bindActionCreators from './bindActionCreators';
import applyMiddleware from './applyMiddleware';
exportCreateStore,// Create warehouse combineReducers,// merge reducersbindActionCreators,// Bind the actionCreator and Dispatch methods to applyMiddleware}Copy the code
Intermediate key Practice
Redux-thunk middle key implementation
function createThunkMiddleware(extraArgument) {
return ({dispatch, getState}) => next => action => {
if(typeof action === 'function') {
return action(dispatch, getState, extraArgument)
} else {
next(action)
}
}
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
Copy the code
redux-promise
// Promise: YesthenMethodfunction isPromise(obj) {
return! obj&&(typeof obj =='object' || typeof obj == 'function') && (typeof obj.then == 'function')}export default function({dispatch, getState}) {
return next => action => {
returnisPromise(action.payload)? action.payload.then(result => { dispatch({... action, payload: result}) }).catch((error) => { dispatch({... action, payload:error, error:true});
return Promise.reject(error)
}) : next(action)
}
}
Copy the code
The React – Redux implementation
connect.js
import React, {Component} from 'react';
import { bindActionCreators } from '.. /redux';
import ReduxContext from './context';
export default function(mapStateToProps, mapDispatchToProps) {
return function(WrappedComponent) {
return class extends Component {
static contextType = ReduxContext;
constructor(props, context) {
super(props);
this.state = mapStateToProps(context.store.getState());
}
componentDidMount() {
this.unsubscribe = this.context.store.subscribe(() => {
this.setState(mapStateToProps(this.context.store.getState()))
})
}
componentWillMount() {
this.unsubscribe()
}
render() {
let actions = {}
if(typeof mapDispatchToProps == 'function') {
actions = mapDispatchToProps(this.context.store.dispatch)
} else {
actions = bindActionCreators(mapDispatchToProps, this.context.store.dispatch);
}
return<WrappedComponent dispatch={this.context.store.dispatch} {... this.state} {... actions} /> } } } }Copy the code
Provider.js
import React, { Component } from 'react';
import ReduxContext from './context';
export default class Provider extends Component {
render() {
return (
<ReduxContext.Provider value ={{store: this.props.state}}>
{this.props.children}
</ReduxContext.Provider>
)
}
}
Copy the code
context.js
import React from 'react';
const ReduxContext = React.createContext(null);
export default ReduxContext;
Copy the code
index.js
import Provider from './Provider';
import connect from './connect';
export {
Provider,
connect
}
Copy the code