mominger / blog

Tech blog
45 stars 3 forks source link

Analysis of Redux source code #36

Open mominger opened 2 years ago

mominger commented 2 years ago

The upper part is the English version, and the lower part is the Chinese version, with the same content. If there are any wrong, or you have anything hard to understand, pls feel free to let me know.many thx.

Overview

Analyze the principle and source code of Redux

1. Redux data flow

redux_image

Action: plain javascript object,define what happened Reducer: fn(state,action), return new state State: {},define state of ui component Store: manage action and reducer and state

2. Invoke flow

callback

3. Source code

source code

The master branch is ts files (d.ts), and the 4.x branch is js files for easy analysis

3.1 Entrance

core_js

index.js source code

public_core_functions

3.2 createStore.js

createStore source code

  export default function createStore(reducer, preloadedState, enhancer) {
        // first,validate passed params
        ...

        // define internal variables
        let currentReducer = reducer
        let currentState = preloadedState
        let currentListeners = []
        let nextListeners = currentListeners
        let isDispatching = false

        //copy currentListeners into nextListeners
        //subcribe/unsubcribe uses nextListeners,dispatch uses currentListeners
        function ensureCanMutateNextListeners() {
          if (nextListeners === currentListeners) {
            nextListeners = currentListeners.slice()
          }
        }

        //read state
        function getState() {
           ...
           return currentState
        }

        //triggered when state changes
        function subscribe(listener) {
          ...
          //add into nextListeners
          nextListeners.push(listener)
          return function unsubscribe() {
            //remove the listener from nextListeners
            isSubscribed = false

            ensureCanMutateNextListeners()
            const index = nextListeners.indexOf(listener)
            nextListeners.splice(index, 1)
            currentListeners = null
          }
        }

        function dispatch(action) {
          //first,validate passed params)
          ...
           try {
            isDispatching = true
            //exec reducer
            currentState = currentReducer(currentState, action)
          } finally {
            isDispatching = false
          }

          //triger every listener
          const listeners = (currentListeners = nextListeners)
          for (let i = 0; i < listeners.length; i++) {
            const listener = listeners[i]
            listener()
          }

          return action
        }

         //1. accept a reducer to replace the current reducer
         //2. then execute dispatch({ type: ActionTypes.REPLACE}) to initialize the state of the store
        function replaceReducer(nextReducer) {
          ...
          currentReducer = nextReducer
          dispatch({ type: ActionTypes.REPLACE })
        }

         dispatch({ type: ActionTypes.INIT })

    return {
      dispatch,
      subscribe,
      getState,
      replaceReducer,
      [$$observable]: observable,
    }
  }

dispatch (publish), getState (get state), subscribe (listen) three core APIs The unsubscribe method is the return value of the subscribe method subscribe: add callback function to nextListeners, unsubscribe: delete from nextListeners dispatch: execute reducer->execute each listener A state update process of redux: dispatch trigger -> execute reducer -> execute all listeners (executing a subscribe will add a listener to the nextListeners array)

3.3 combineReducers.js

combineReducers source code


export default function combineReducers(reducers) {
...
return function combination(state = {}, action) {
...
// each reducer will actually be executed
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
const previousStateForKey = state[key]
const nextStateForKey = reducer(previousStateForKey, action)
...
}
...
}
> Generally, one module has one reducer, and multiple reducers are combined into a large reducer by combineReducers
> combineReducers will still execute all reducers every time, [but the official said that there will be no performance problems in doing so](https://redux.js.org/faq/performance#wont-calling-all-my-reducers-for-each-action-be-slow)

#### 3.4 bindActionCreators.js
[bindActionCreators source code](https://github.com/reduxjs/redux/blob/4.x/src/bindActionCreators.js)

//After the action is executed, dispatch is called automatically function bindActionCreator(actionCreator, dispatch) { return function () { return dispatch(actionCreator.apply(this, arguments)) } }

export default function bindActionCreators(actionCreators, dispatch) { ...

const boundActionCreators = {}
const boundActionCreators = {}
//iterate the object and generate a function that wraps the dispatch according to the corresponding key
for (const key in actionCreators) {
  const actionCreator = actionCreators[key]
  if (typeof actionCreator === 'function') {
    boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
  }
}
return boundActionCreators

}

> bind actionCreator and dispatch together

Example using bindActionCreators

const mapStateToProps = (state: any) => ({ todos: state.todos })

const mapDispatchToProps = (dispatch: Dispatch) => bindActionCreators( todoActions, dispatch ) export default connect(mapStateToProps, mapDispatchToProps)(App)

// using in business code as bellow this.props.todo({})

//note: only use mapDispatchToProps and pass in the Actions object, the effect is the same as above, so not used much const mapDispatchToProps = { ...todoActions }

> applyMiddleware.js, compose.js are placed below for separate analysis

### 4. Redux middleware
#### 4.1 Invoke flow after adding middleware
![call2](https://raw.githubusercontent.com/mominger/MyPublicFiles/master/img/20220217201830.png)
>Middleware is used to handle side effects, such as asynchronous requests

#### 4.2  How to use

// add a middleware const store = createStore(reducer, applyMiddleware(middlewareA));


#### 4.3  Source code
##### 4.3.1 applyMiddleware.js
[applyMiddleware source code](https://github.com/reduxjs/redux/blob/4.x/src/applyMiddleware.js)

export default function applyMiddleware(...middlewares) { return (createStore) => (...args) => { const store = createStore(...args) let dispatch = () => { throw new Error( 'Dispatching while constructing your middleware is not allowed. ' + 'Other middleware would not be applied to this dispatch.' ) }

  //define the parameters passed to the middleware: {getState,dispatch}
  const middlewareAPI = {
    getState: store.getState,
    dispatch: (...args) => dispatch(...args),
  }
  //pass to each middleware: {getState,dispatch}
  const chain = middlewares.map((middleware) => middleware(middlewareAPI))
  //pass to each middleware: {store.dispatch}
  dispatch = compose(...chain)(store.dispatch)

  return {
    ...store,
    dispatch,
  }
}

}

#### 4.3.2  compose.js
[compose source code](https://github.com/reduxjs/redux/blob/4.x/src/compose.js)

export default function compose(...funcs) { if (funcs.length === 0) { return (arg) => arg }

if (funcs.length === 1) {
  return funcs[0]
}

return funcs.reduce((a, b) => (...args) => a(b(...args)))

}


#### 4.4 Simplified version

//for understanding the principle of applyMiddleware const applyMiddleware = (middlewares) => { middlewares.forEach((middleware) => { const dispatch = store.dispatch; store.dispatch = middleware(store)(dispatch); }); };


The effect of compose

//execute compose const dispatch = compose(f1, f2, f3); dispatch(args);

//is same with f1(f2(f3()))


#### 4.5 Example building middleware
Simplified version

const logMiddleware = (store) => { const next = store.dispatch;

store.dispatch = (action) => {
  next(action);
  console.log('the time:', new Date());
};

};

Transform into a shape that conforms to redux middleware

// ({ dispatch, getState }) corresponds to const chain = middlewares.map(middleware => middleware(middlewareAPI)) // next parameter corresponds to dispatch = compose(...chain)(store.dispatch),which is each middleware(rewritten dispatch) const logMiddleware = ({ dispatch, getState }) => (next) => (action) => { next(action); console.log('time:', new Date().getTime()); };


_The following is the Chinese version, the same content as above_

### Overview
> 分析redux的原理和源码

###  1. Redux的数据流
![redux_image](https://raw.githubusercontent.com/mominger/MyPublicFiles/master/img/20220215105821.png)
> Action: 纯函数,定义发生了什么
> Reducer: fn(state,action), return new state
> State: {},定义组件状态
> Store: 管理action、reducer、state

###  2. 调用流程
![callback](https://raw.githubusercontent.com/mominger/MyPublicFiles/master/img/20220217200702.png)

###  3. 源码
[源码地址](https://github.com/reduxjs/redux/tree/4.x)
> master分支是typescript,点进去是d.ts.4.x分支是js,方便分析
####  3.1 入口
![core_js](https://raw.githubusercontent.com/mominger/MyPublicFiles/master/img/20220307201637.png)

[index.js 源码](https://github.com/reduxjs/redux/blob/4.x/src/index.js)

![public_core_functions](https://raw.githubusercontent.com/mominger/MyPublicFiles/master/img/20220307201319.png)

####  3.2 createStore.js
[createStore 源码](https://github.com/reduxjs/redux/blob/4.x/src/createStore.js)

export default function createStore(reducer, preloadedState, enhancer) { //首先验证传进来的参数 ...

    //定义内部变量
    let currentReducer = reducer
    let currentState = preloadedState
    let currentListeners = []
    let nextListeners = currentListeners
    let isDispatching = false

    //备份currentListeners到nextListeners
    //subcribe/unsubcribe用nextListeners,dispatch用currentListeners
    function ensureCanMutateNextListeners() {
      if (nextListeners === currentListeners) {
        nextListeners = currentListeners.slice()
      }
    }

    //读取State方法
    function getState() {
       ...
       return currentState
    }

    //状态改变时会被触发
    function subscribe(listener) {
      ...
      //塞入nextListeners里
      nextListeners.push(listener)
      return function unsubscribe() {
        //remove the listener from nextListeners
        isSubscribed = false

        ensureCanMutateNextListeners()
        const index = nextListeners.indexOf(listener)
        nextListeners.splice(index, 1)
        currentListeners = null
      }
    }

    function dispatch(action) {
      //首先验证传进来的参数
      ...
       try {
        isDispatching = true
        //执行reducer
        currentState = currentReducer(currentState, action)
      } finally {
        isDispatching = false
      }

      //最后就是触发每一个监听器
      const listeners = (currentListeners = nextListeners)
      for (let i = 0; i < listeners.length; i++) {
        const listener = listeners[i]
        listener()
      }

      return action
    }

    //1. 接受一个reducer替换的当前reducer,
    //2. 然后执行dispatch({ type: ActionTypes.REPLACE}) ,初始化store的状态
    function replaceReducer(nextReducer) {
      ...
      currentReducer = nextReducer
      dispatch({ type: ActionTypes.REPLACE })
    }

    dispatch({ type: ActionTypes.INIT })
return {
  dispatch,
  subscribe,
  getState,
  replaceReducer,
  [$$observable]: observable,
}

}

> dispatch(发布),getState(获取状态),subscribe(监听)三个核心API
> unsubscribe方法是subscribe方法的返回值
> subscribe: 把回调函数加入nextListeners,unsubscribe: 从nextListeners里删除
> dispatch: 执行reducer->执行每一个listener
> redux的一次状态更新流程就是 dispatch触发->reducer执行-> 执行所有listener(执行一次subscribe会增加一个listener到 nextListeners 数组) 

####  3.3 combineReducers.js
[combineReducers 源码](https://github.com/reduxjs/redux/blob/4.x/src/createStore.js)

export default function combineReducers(reducers) { ... return function combination(state = {}, action) { ... // 实际会执行每一个reducer for (let i = 0; i < finalReducerKeys.length; i++) { const key = finalReducerKeys[i] const reducer = finalReducers[key] const previousStateForKey = state[key] const nextStateForKey = reducer(previousStateForKey, action) ... } ... }

>一般一个模块一个 reducer ,多个 reducer 通过 combineReducers  合成一个大的reducer
> combineReducers 仍会每次 执行所有的reducer, [但是官方称这么做不会有性能问题](https://redux.js.org/faq/performance#wont-calling-all-my-reducers-for-each-action-be-slow)

####  3.4 bindActionCreators.js
[bindActionCreators 源码](https://github.com/reduxjs/redux/blob/4.x/src/bindActionCreators.js)

//执行action后,自动dispatch function bindActionCreator(actionCreator, dispatch) { return function () { return dispatch(actionCreator.apply(this, arguments)) } }

export default function bindActionCreators(actionCreators, dispatch) { ...

const boundActionCreators = {}
const boundActionCreators = {}
//遍历对象,根据相应的key,生成包裹dispatch的函数
for (const key in actionCreators) {
  const actionCreator = actionCreators[key]
  if (typeof actionCreator === 'function') {
    boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
  }
}
return boundActionCreators

}

> 将actionCreator和dispatch绑定在一起

举例使用bindActionCreators

const mapStateToProps = (state: any) => ({ todos: state.todos })

const mapDispatchToProps = (dispatch: Dispatch) => bindActionCreators( todoActions, dispatch ) export default connect(mapStateToProps, mapDispatchToProps)(App)

// 在业务代码里如下调用 this.props.todo({})

//注意: 只使用mapDispatchToProps,传入Actions对象,效果和上面一致, 所以用得不多 const mapDispatchToProps = { ...todoActions }

> applyMiddleware.js,compose.js 放到下面单独分析

###  4. redux middleware
####  4.1 增加中间件后的调用流程
![call2](https://raw.githubusercontent.com/mominger/MyPublicFiles/master/img/20220217201830.png)
> 中间件是用来处理副作用的,如异步请求

####  4.2  如何使用

// 添加一个中间件 const store = createStore(reducer, applyMiddleware(middlewareA));


####  4.3  源码
#####  4.3.1 applyMiddleware.js
[applyMiddleware 源码](https://github.com/reduxjs/redux/blob/4.x/src/applyMiddleware.js)

export default function applyMiddleware(...middlewares) { return (createStore) => (...args) => { const store = createStore(...args) let dispatch = () => { throw new Error( 'Dispatching while constructing your middleware is not allowed. ' + 'Other middleware would not be applied to this dispatch.' ) }

  //定义传给中间件的参数: {getState,dispatch}
  const middlewareAPI = {
    getState: store.getState,
    dispatch: (...args) => dispatch(...args),
  }
  //给每个中间件传递: {getState,dispatch}
  const chain = middlewares.map((middleware) => middleware(middlewareAPI))
  //给每个中间件传递: {store.dispatch}
  dispatch = compose(...chain)(store.dispatch)

  return {
    ...store,
    dispatch,
  }
}

}

####  4.3.2  compose.js
[compose 源码](https://github.com/reduxjs/redux/blob/4.x/src/compose.js)

export default function compose(...funcs) { if (funcs.length === 0) { return (arg) => arg }

if (funcs.length === 1) {
  return funcs[0]
}

return funcs.reduce((a, b) => (...args) => a(b(...args)))

}


####  4.4 简化写法

//用于理解applyMiddleware的原理 const applyMiddleware = (middlewares) => { middlewares.forEach((middleware) => { const dispatch = store.dispatch; store.dispatch = middleware(store)(dispatch); }); };


compose 的效果

//调用compose const dispatch = compose(f1, f2, f3); dispatch(args);

//相当于 f1(f2(f3()))


####  4.5 举例构建一个middleware
简化写法

const logMiddleware = (store) => { const next = store.dispatch;

store.dispatch = (action) => {
  next(action);
  console.log('the time:', new Date());
};

};

改造成符合redux middleware的代码形状

// ({ dispatch, getState }) 对应 const chain = middlewares.map(middleware => middleware(middlewareAPI)) // next 参数 对应 dispatch = compose(...chain)(store.dispatch),是每个Middleware(重写后的dispatch) const logMiddleware = ({ dispatch, getState }) => (next) => (action) => { next(action); console.log('time:', new Date().getTime()); };