今天看啥  ›  专栏  ›  litongqian

Redux源码简析(版本3.5.2)

litongqian  · 掘金  ·  · 2020-02-25 09:44
阅读 29

Redux源码简析(版本3.5.2)

为什么要写这篇文章?

以前想写来着,发现有太多这种文章,就不想去总结了,结果太长时间没做web开发了,就忘记了,在阿里面试被问了好几次,回答的都不很理想,所以还是那句话好记性不如烂笔头,更何况还没有好记性!

先看一个示例:

import React,{Component,PropTypes} from 'react'
import ReactDOM from  'react-dom'
import {createStore } from 'redux'
import {Provider,connect }from 'react-redux'

// React component
class Counter extends Component {
    render() {
        const {value,onIncreaseClick} = this.props
        return ( 
           <div>
              <span>{value} </span>
              <button onClick={onIncreaseClick}>Increase</button> 
           </div>
        )
     }
   }
     const increaseAction = {
            type: 'increase'
        }

        function counter(state = {
            count: 0
        },
        action) {
            const count = state.count
            switch (action.type) {
            case 'increase':
                return {
                    count:
                    count + 1
                }
            default:
                return state
            }
        }

        const store = createStore(counter)
        function mapStateToProps(state) {
            return {
                value: state.count
            }
        }
        function mapDispatchToProps(dispatch) {
            return {
                onIncreaseClick: () = >dispatch(increaseAction)
            }
        }
        const App = connect(mapStateToProps, mapDispatchToProps)(Counter)

        ReactDOM.render( 
         <Provider store = {store}>
          <App/>
         </Provider>,
       document.getElementById('root'))复制代码

这是Redux在react中的应用,其实redux和react是没有关系的,redux可以在其他任何框架中使用

这里只是通过react-redux将react和redux结合在一起了!

接下来我们先抛弃react我们来分析源码:

createStore源码

function createStore(reducer, preloadedState, enhancer) {
    let currentReducer = reducer 
    let currentState = preloadedState 
    let currentListeners = []
    let nextListeners = currentListeners
    function getState() {
        return currentState
    }
    function subscribe(listener) {
        nextListeners.push(listener)
    }
    function dispatch(action) {
        currentState = currentReducer(currentState, action) const listeners = currentListeners = nextListeners
        for (let i = 0; i < listeners.length; i++) {
            const listener = listeners[i] listener()
        }
        return action
    }
    function replaceReducer(nextReducer) {
        currentReducer = nextReducer dispatch({
            type: ActionTypes.INIT
        })
    }
    dispatch({
        type: ActionTypes.INIT
    }) 
  return {
        dispatch,
        subscribe,
        getState,
        replaceReducer,
    }
}复制代码

简化后的代码可以看到createStore接受三个参数,后面两个参数先忽略,也就是createStore接受传入一个reducer

返回:

return {    
    dispatch,
    subscribe,
    getState,
    replaceReducer,
  }复制代码

可以很容易看到返回的store就是一个订阅发布设计模式

  • dispatch: 用于action的分发,改变store里面的state
  • subscribe: 注册listener,store里面state发生改变后,执行该listener
  • getState: 读取store里面的state

  • replaceReducer: 替换reducer,改变state修改的逻辑

subscribe传入function订阅

dispatch(action)发布消息,将action和当前的state传入定义好的reducer得到新的state

接着通知之前通过store.subscribe订阅消息的函数,这样看是不是特别简单


// 先写一个,成为reducer
function count (state, action) {
    state=state || 2020;
    switch (action.type) {
        case 'add':
            return {
                year: state.year + 1
            };
        case 'sub':
            return {
                year: state.year - 1
            }
        default :
            return state;
    }
}
var store = createStore(count);
// store里面的数据发生改变时,触发的回调函数
store.subscribe(function () {
      console.log('the year is: ', store.getState().year);
});

var action = { type: 'add' };
// 改变store里面的方法
store.dispatch(action); // 'the year is: 2021复制代码



很显然,只有一个 reducer 是 hold 不住我们整个应用中所有 action 操作的,会变得很难维护

怎么办呢,这时候要写多个reducer,就用到了

使用combineReducers

var reducer_0 = function(state = {},action) {
    switch (action.type) {
    case 'SAY_SOMETHING':
        return {...state,
            message: action.value
        }
    default:
        return state;
    }
}

var reducer_1 = function(state = {},action) {
    switch (action.type) {
      case 'SAY_SOMETHING':
        return {...state,
            message: action.value
        }
      case 'DO_SOMETHING':
        // ...        
      case 'LEARN_SOMETHING':
        // ...        
      default:
        return state;
    }
}复制代码

和并多个reducer,使用combineReducers直接搞定

import {
    createStore,
    combineReducers
}
from 'redux'
var reducer = combineReducers({
    user: reducer_0,
    items: reducer_1
})复制代码

接下来看下combineReducers是怎么做到的

function combineReducers(reducers) {
    const reducerKeys = Object.keys(reducers)
    const finalReducers = {}

    for (let i = 0; i < reducerKeys.length; i++) {
        const key = reducerKeys[i]
        if (typeof reducers[key] === 'function') {
            finalReducers[key] = reducers[key]
        }
    }

    const finalReducerKeys = Object.keys(finalReducers)

    return function combination(state = {},
    action) {
        const nextState = {}
        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) 
            nextState[key] = nextStateForKey 
           hasChanged = hasChanged || nextStateForKey !== previousStateForKey
        }
        return hasChanged ? nextState: state
    }
}复制代码

combineReducers方法将多个子reducers合并为一个对象finalReducers

当dispatch(action)触发合并后的combination,combination通过key在调用各个子模块,返回state,最后合并为最新的nextState,是不是很简单

看到这里可能会有疑问,dispatch(action)后就是触发reducers,那异步请求怎么办呢?

那就想办法呗,既然reducer是获取更新State,异步请求是获取最新的数据,那只能在reducer之前加一层!

// 通常来说中间件是在某个应用中 A 和 B 部分中间的那一块,
//  dispatch(action)-----> reducers
// 变成:
// dispatch(action) ---> middleware 1 ---> middleware 2 ---> middleware 3 ...--> reducers

中间件middleware

要发送异步请求就需要中间件,先看一个最简单的中间件redux-thunk.

// 我们为异步 action creator 提供的中间件叫 thunk middleware// 它的代码在:https://github.com/gaearon/redux-thunk.

var thunkMiddleware = function({dispatch,getState}) {
    return function(next) {
        return function(action) {
            return typeof action === 'function' ? action(dispatch, getState) : next(action)
        }
    }复制代码

 如上所述,中间件由三个嵌套的函数构成(会依次调用):
  1. 第一层向其余两层提供分发函数和 getState 函数(因为你的中间件或 action creator 可能需要从 state 中读取数据)
  2. 第二层提供 next 函数,它允许你显式的将处理过的输入传递给下一个中间件或 Redux(这样 Redux 才能调用所有 reducer)。
  3. 第三层提供从上一个中间件或从 dispatch 传递来的 action,
这个 action 可以调用下一个中间件(让 action 继续流动) 或者 以想要的方式处理 action。


代码非常简单,来结合一个例子看看中间件怎么使用:

import {
    createStore,
    applyMiddleware
} from 'redux'

//将redux-thunk代码直接放在这里方便阅读
var thunkMiddleware = function({dispatch,getState}) {
    return function(next) {
        return function(action) {
            return typeof action === 'function' ? action(dispatch, getState) : next(action)
        }
    }
}

var middleware = applyMiddleware(thunkMiddleware) 
var reducer = function(state = {},action) {
    switch (action.type) {
    case 'SAY':
        return Object.assign(state, {
            message: action.value
        })
    default:
        return state
    }
}

const store = createStore(reducer, undefined, middleware)

// 现在 store 的 middleware 已经准备好了,再来尝试分发我们的异步 action:
var asyncSayAction = function(message) {
    return function(dispatch) {
        setTimeout(function() {
            console.log(new Date(), 'Dispatch action now:') dispatch({
                type: 'SAY',
                message
            })
        },
        2000)
    }
}

store.dispatch(asyncSayAction('Hi'));复制代码

// dispatch(action) ---> middleware 1 ---> middleware 2 ---> middleware 3 ...--> reducers

由上图可知dispatch是要先经过中间件的,之前提交dispatch(action)都是对象{type:'SAY'},

redux-thunk是通过提交action判读是不是function,决定下一步操作

接下来分析中间件是怎么加入redux的

applyMiddleware

function applyMiddleware(...middlewares) {
    return function(createStore) {

        return function(reducer, preloadedState, enhancer) {

            const store = createStore(reducer, preloadedState, enhancer) 
            let dispatch = store.dispatch 
            let chain = []
            const middlewareAPI = {
                getState: store.getState,
                dispatch: (action) = >dispatch(action)
            }
            chain = middlewares.map(middleware = >middleware(middlewareAPI)) 
            dispatch = compose(...chain)(store.dispatch)
            return {
                ...store,
                dispatch
            }
        }
    }
}复制代码



根据createStore的传入参数调用enhancer(createStore)(reducer, preloadedState)

即applyMiddleware返回的函数

function(createStore) {

        return function(reducer, preloadedState, enhancer) {

            const store = createStore(reducer, preloadedState, enhancer) 
            let dispatch = store.dispatch 
            let chain = []
            const middlewareAPI = {
                getState: store.getState,
                dispatch: (action) = >dispatch(action)
            }
            chain = middlewares.map(middleware = >middleware(middlewareAPI)) 
            dispatch = compose(...chain)(store.dispatch)
            return {
                ...store,
                dispatch
            }
        }
}复制代码

这个函数先创建store,跟没有中间件的流程一样,接着看是怎么把中间件加入到

通过chain = middlewares.map(middleware => middleware(middlewareAPI))

传入第一层提供分发函数和 getState 函数(因为你的中间件或 action creator 可能需要从 state 中读取数据)

还是看redux-thunk

var thunkMiddleware = function({dispatch,getState}) {
    return function(next) {
        return function(action) {
            return typeof action === 'function' ? action(dispatch, getState) : next(action)
        }
    }
    }
复制代码

接着通过 dispatch = compose(...chain)(store.dispatch)

组装中间件,返回的是一个包装函数,最好自己打断点调试看一下,

返回的包装函数为dispatch,当我们再次使用dispatch提交的时候就会先调用中间件,

中间件中的next代表下一个中间件,一直到最后一个中间件结束,调用的next是store.dispatch

然后出发reducer

 dispatch(action) ---> middleware 1 ---> middleware 2(第一个next) ---> middleware 3(第二个next) ...store.dispatch(最后一个next) --> reducer



打断点可以清楚的看出上面redux-thunk中间件

由于这里只用到一个中间件redux-thunk,所以next直接是包装前dispatch,调用后直接触发reducer

到这里redux就结束了,是不是很简单,中间件的加入就是对原有dispatch的再包装,包装的代码有点难懂,要好好的理解闭包和高阶函数打断点才能看懂哟!!!

接下来看redux是怎么和react结合的:

基本使用

目前已经有现成的工具react-redux来实现二者的结合:

react-redux提供了Provider  connect两个工具

Provider -- 提供store

React通过Context属性,属性(props)直接给子孙component,无须通过props层层传递, Provider仅仅起到获得store,然后将其传递给子孙元素而已:

export default class Provider extends Component {  getChildContext() {    return { store: this.store }  }  constructor(props, context) {    super(props, context)    this.store = props.store  }  render() {    return Children.only(this.props.children)  }}复制代码

是不是很简单!

接下来看看复杂的connect

connect是一个高阶函数,首先传入mapStateToProps、mapDispatchToProps,然后返回一个生产Component的函数(wrapWithConnect)

看看connect是怎么实现高阶组件,更新数据的

function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) {
    const mapState = mapStateToProps let mapDispatch = mapDispatchToProps

    return function wrapWithConnect(WrappedComponent) {
        class Connect extends Component {
    
            constructor(props, context) {
                super(props, context) 
                this.version = version 
                this.store = props.store || context.store 
                const storeState = this.store.getState() 
                this.state = {
                    storeState
                }
            }
            trySubscribe() {
                if (shouldSubscribe && !this.unsubscribe) {
                    this.unsubscribe = this.store.subscribe(this.handleChange.bind(this)) this.handleChange()
                }
            }
            componentDidMount() {
                this.trySubscribe()
            }

            componentWillReceiveProps(nextProps) {
                if (!pure || !shallowEqual(nextProps, this.props)) {
                    this.haveOwnPropsChanged = true
                }
            }


            handleChange() {
                const storeState = this.store.getState() this.setState({
                    storeState
                })
            }
            render() {
                this.renderedElement = createElement(WrappedComponent, this.mergedProps)
                return this.renderedElement
            }
        }

        Connect.displayName = connectDisplayName Connect.WrappedComponent = WrappedComponent Connect.contextTypes = {
            store: storeShape
        }
        Connect.propTypes = {
            store: storeShape
        }

        return hoistStatics(Connect, WrappedComponent)
    }
}复制代码


将connect代码简化后可以看到,HOC高阶组件就是对自定义组件的封装

封装后的组件,通过获取redux的store,然后通过this.store.subscribe监听数据变化

trySubscribe() {
                if (shouldSubscribe && !this.unsubscribe) {
                    this.unsubscribe = this.store.subscribe(this.handleChange.bind(this)) this.handleChange()
                }
            }复制代码

一但数据有变化触发handleChange

调用setState触发render,在render中获取需要的参数合并传递给自定义组件完成更新:

handleChange() {
                const storeState = this.store.getState() this.setState({
                    storeState
                })
            }复制代码

到这里就接近尾声了,只要理解原理,以后再使用是不是就很容易上手1

最后从网上搞了张图,非常好!







原文地址:访问原文地址
快照地址: 访问文章快照