Redux source code analysis

What is the story

Redux is a JavaScript state container that provides predictable state management. Redux is an architectural pattern that is different from React-Redux.

Workflow for Redux

  • First, the user issues the action
store.dispatch(action);
Copy the code
  • Store calls Reducer and returns the new state
let nextState = todoApp(previousState, action);
Copy the code
  • Whenever state changes, store calls the listener function
store.subscribe(listener);
Copy the code
  • Rerender the view

Why redux

The state of a component, you need to share a state you need to be able to get a component anywhere you need to change the global state of a component you need to change the state of another component

Between “modules (components) need to share data”, and “data can be arbitrarily modified with unpredictable results”.

Learn what the React team did: Raise the bar for changing data. It can’t be changed directly, it needs to do something I allow, and then tell me you want to change it.

All operations on the data must go through the Dispatch function.

An implementation of mini Redux

The use of the story

import { createStore } from 'redux'

// Reducer handle function, parameters are state and new action
function counter(state=0, action) {
  console.log(state, action)
  switch (action.type) {
    case '1':
      return state + 1
    case '1':
      return state - 1
    default:
      return 10}}// Create a new type of data store
const store = createStore(counter)
// store.getState() gets the share status
const init = store.getState()
console.log('At first${init}`)
function listener(){
  const current = store.getState()
  console.log(` is now${current}`)}// The listener is executed each time state is changed
store.subscribe(listener)
// Submit a request for status change, and store. Dispatch changes the status
store.dispatch({ type: '1' })
store.dispatch({ type: '1' })
store.dispatch({ type: '1' })
store.dispatch({ type: '1' })
store.dispatch({ type: '1' })
Copy the code

The realization of the createStore

export function createStore(reducer) {
    let currentState = {}; // Internal state
    let currentListeners = []; // Current listener

    const getState = () = > currentState
    const subscribe = (listener) = > {
    	currentListeners.push(listener)
    }
    const dispatch = (action) = > {
        currentState = reducer(currentState,action)
        currentListeners.forEach(v= > v())
        return action
    }
    dispatch({type:'@@WINNIE_REDUX/INIT'})
    return {getState,subscribe,dispatch}
}
Copy the code

Why react-redux?

React: redux: redux: redux: redux: redux: redux: Redux: Redux: Redux: Redux: Redux: Redux

What is state promotion: When a state is dependent or affected by multiple components, the state is promoted to the nearest common parent of the component for management, using props to pass data or functions to manage the dependent or affected behavior.

The problem: If requirements are constantly changing, shared state is passed from one layer to another or promoted endlessly, it is not conducive to code organization and maintenance

Solution: Put the shared state on the parent component’s context. All components of the parent component can be retrieved directly from the context, so there is no need to pass the shared state layer by layer

New issues: Storing and retrieving data directly from context enhances component coupling; And all components can change the state of the context just as anyone can change the shared state, causing the program to run unpredictably.

Solution: Combine context and store. Store data can only be modified with dispatch, and the context gets the state from the store. You can track the progress of changes

Final solution: React-Redux

The context of the react

Global variable of a subtree in the component tree.

Once a component puts some state into its context, all of its children can access that state directly without passing it through an intermediate component.

A component’s context can only be accessed by its children, not its parent.

The getChildContext method is the procedure for setting the context

ChildContextTypes works similarly to the propsType validation component props parameter. But it is the object that validates the return of getChildContext.

Why check? Because context is a dangerous feature, according to the React team, the idea is to make dangerous things complicated and raise the bar so that people won’t use them. ChildContextTypes must be written if you are setting the context for the component.

A component can use the getChildContext method to return an object that is the context of the subtree. The component providing the context must provide childContextTypes as the declaration and validation of the context.

Redux uses this mechanism to provide us with convenient state management services

The react – redux implementation

Provider- Provides the context globally. We put store in the context, and all of the children can go directly to store. The container component renders the nested content as its own child component

export class Provider extends React.Component {
    static childContextTypes = {
        store: PropTypes.object
    }
    getChildContext() {
        return {
            store: this.store
        }
    }
    constructor(props,context) {
        super(props,context)
        this.store = props.store
    }
    render() {
        return this.props.children
    }
}
Copy the code

Why do connect implementations use Connect

  • Repeat logic: The state is placed in the parent component, and the child component needs to fetch it from the context when it is used. There is a lot of repeat logic
  • Dependency on context: Subcomponents depend on context. There is no context in the component tree. Components are not reusable

The connect parameters

  • WrapComponent: Contains this component as an argument in a new component, ConnectComponent, which will fetch the Store from the context. Then pull the data from the store and pass it to the WrapComponent via props

But each component needs different data in the store, so we need to tell the higher-level component what data we need.

  • MapStateToProps: Accepts the result of store.getState() as an argument and returns an object that was generated based on state. Tell CONNECT how to fetch data from the Store and pass the result back to the wrapped component
const { store } = this.context
let stateProps = mapStateToProps(store.getState())
Copy the code
  • Connect listens for data changes and then rerenders, with store.subscribe listening for data changes and calling update again
store.subscribe(() = > this.update())
Copy the code
  • MapDispatchToProps: In addition to getting data, we also need store dispatchprops to tell the component how to trigger the dispatch. Dispatches are received and specific actions are triggered by dispatches
// Connect is responsible for linking components and putting data to redux into component properties
// 1 is responsible for receiving a component, putting some data in state, and returning a component
// 2, the component can be notified when data changes
export const connect = (mapStateToProps = state => state,mapDispatchToProps={}) = > (WrapComponent) = > {
    return class ConnectComponent extends React.Component {
        / / get the context
        static contextTypes = {
            store: PropTypes.object
        }
        constructor(props,context) {
            super(props);
            this.state = {
                props: {}}}componentDidMount() {
            const {store} = this.context;
            store.subscribe(() = > this.update())
            this.update()
        }
        update() {
            // Get mapStateToProps and mapDispatchToProps and put them in this.props
            const {store} = this.context;
            const stateProps = mapStateToProps(store.getState());
            // The method cannot be given directly because dispatch is required
            const dispatchProps = bindActionCreators(mapDispatchToProps,store.dispatch)
            // const dispatchProps = mapDispatchToProps(store.dispatch, this.props)
            this.setState({
                props: {... this.state.props, ... dispatchProps, ... stateProps } }) }render() {
            return (
                <WrapComponent {. this.state.props} / >)}}}function bindActionCreator(creator,dispatch) {
    return (. args) = >dispatch(creator(... args)) }function bindActionCreators(creators,dispatch) {
    let bnound = {};
    Object.keys(creators).forEach(v= > {
        let creator = creators[v];
        bnound[v] = bindActionCreator(creator,dispatch)
    })
    return bnound;
}
Copy the code

Difference between Mobx and Redux

Redux stores data in a single store, while Mobx stores data in scattered stores

Redux uses plain Objects to store data, requiring manual handling of changes. Mobx applies to an Observable that stores data and automatically processes the response when the data changes

Redux uses immutable state, which means that the state is read-only and cannot be changed directly. Instead, a new state should be returned, using pure functions. State in MOBx is mutable and can be changed directly

Mobx is relatively simple, in which there is a lot of abstraction, MOBx uses more object-oriented programming thinking; Redux can be complicated because the functional programming ideas are not so easy to master and require a range of middleware to handle asynchracy and side effects

Conclusion: Mobx is sufficient for simple, small applications. But can’t some big, complex applications use Mobx? I don’t think so. In fact, if you can properly organize the code structure, clear dependencies, some complex scenarios can be suitable for MobX.

The middleware

Usage:

const store = createStore(counter,applyMiddleware(thunk,arrThunk))
Copy the code

applyMiddleware:

export function applyMiddleware(. middlewares) {
    return createStore= > (. args) = > {
        conststore = createStore(... args);let dispatch = store.dispatch;
        const midApi = {
            getState: store.getState,
            dispatch: (. args) = >dispatch(... args), }// dispatch = middleware(midApi)(store.dispatch)
        const middlewareChain = middlewares.map(middleware= >middleware(midApi)) dispatch = compose(... middlewareChain)(store.dispatch);return {
            ...store,
            dispatch
        }
    }
}
// compose(fn1,fn2,fn3)
// fn1(fn2(fn3))
export function compose(. funcs) {
    if(funcs.length === 0) {
        return arg= > arg
    }
    if(funcs.length === 1) {
        return funcs[0]}return funcs.reduce((res,item) = > (. args) = >res(item(... args))) }Copy the code

thunk:

 const thunk = ({dispatch,getState}) = > next= > action= > {
    if(typeof action === 'function') {
        return action(dispatch,getState)
    }
    return next(action);
 }
 export default thunk;
Copy the code

arrThunk:


const arrThunk = ({dispatch,getState}) = > next= > action= > {
    if(Array.isArray(action)) {
        return action.forEach(v= > dispatch(v))
    }
   return next(action);
}
export default arrThunk;
Copy the code