Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Analysis of Redux source code #36

Open
mominger opened this issue Mar 18, 2022 · 0 comments
Open

Analysis of Redux source code #36

mominger opened this issue Mar 18, 2022 · 0 comments

Comments

@mominger
Copy link
Owner

mominger commented Mar 18, 2022

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

3.4 bindActionCreators.js

bindActionCreators source code

  //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

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

  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

  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<typeof dispatch>(...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

Action: 纯函数,定义发生了什么
Reducer: fn(state,action), return new state
State: {},定义组件状态
Store: 管理action、reducer、state

2. 调用流程

callback

3. 源码

源码地址

master分支是typescript,点进去是d.ts.4.x分支是js,方便分析

3.1 入口

core_js

index.js 源码

public_core_functions

3.2 createStore.js

createStore 源码

  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 源码

  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, 但是官方称这么做不会有性能问题

3.4 bindActionCreators.js

bindActionCreators 源码

  //执行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

中间件是用来处理副作用的,如异步请求

4.2 如何使用

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

4.3 源码

4.3.1 applyMiddleware.js

applyMiddleware 源码

  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 源码

  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<typeof dispatch>(...chain)(store.dispatch),是每个Middleware(重写后的dispatch)
  const logMiddleware = ({ dispatch, getState }) => (next) => (action) => {
    next(action);
    console.log('time:', new Date().getTime());
  };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant