redux 源码浅析

redux 源码浅析

redux 版本号: "redux": "4.0.5"

redux 做为一个十分经常使用的状态容器库, 你们都应该见识过, 他很小巧, 只有 2kb, 可是珍贵的是他的 reducerdispatch 这种思想方式react

在阅读此文以前, 先了解/使用 redux 相关知识点, 才能更好地阅读本文git

入口文件

入口是在 redux/src/index.js 中, 在入口文件中只作了一件事件, 就是引入文件, 集中导出
如今咱们根据他导出的方法, 来进行分析github

createStore

这个是 redux 最主要的 APIredux

使用

搭配这使用方法一块儿, 能够更好的浏览源码segmentfault

createStore(reducer, [preloadedState], [enhancer])api

他的主要功能就是建立一个 store, 将 reducer 转换到 store数组

参数

一共可接受三个参数:promise

  1. reducer (函数): 一个返回下一个状态树的还原函数,给定当前状态树和一个要处理的动做。
  2. [preloadedState] (任意值): 初始值, 能够是来自于 storage 中的; 若是你用combinedReducers产生了reducer,这必须是一个普通对象,其类型与传递给它的键相同。
    也能够自由地传递任何你的reducer可以理解的东西。
  3. [enhancer] (函数): store 的加强器, 能够选择性的加强, 用代码来讲就是 enhancer(createStore)(reducer, preloadedState), enhancer
    接受的参数就是 createStore, 一样地他也须要 return 一个相似于 createStore 的结果, 也就是说, 只有咱们返回的是 一个像 createStore 的东西,
    他的具体实现咱们就能够有不少微调 这里附上一篇探讨 enhancerapplyMiddleware 的文章 https://juejin.cn/post/684490...
// 简单的例子:

function counterReducer(state, action) {
    switch (action.type) {
        case 'counter/incremented':
            return {value: state.value + 1}
        case 'counter/decremented':
            return {value: state.value - 1}
        default:
            return state
    }
}


let store = createStore(counterReducer, {
    value: 12345
})

store

createStore 返回的固然是一个 store, 他有本身的 apiapp

getState

返回应用程序的当前状态树异步

const state = store.getState()

dispatch(action)

这个其实不用我多说, 会 redux 的都应该知道这个

store.dispatch({type: 'counter/incremented'})

subscribe(listener)

添加一个监听器, 每当 action dispatch 的时候, 都会调用 listener, 在 listener 中可使用 getState 来获取当前的状态树

const unsubscribe = store.subscribe(() => {
    console.log('listener run')
    const current = store.getState()
    if (current.value === 12350) {
        unsubscribe()
    }
})

展现一个场景, 监听事件, 当达到某个条件以后, 解除监听事件

replaceReducer(nextReducer)

使用一个 reducer 替换当前的 reducer,对于 reducers 实现动态加载,也能够为 Redux 实现热重载机制

源码解析

createStore 文件是在 redux/src/createStore.js 中, 他接受的参数就是上面咱们说的那三个, 返回的也就是 store

首先是一段参数的判断, 以及 enhancer 的返回

// 为了适配 createStore(reducer, enhancer) 的状况
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
}

if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
        throw new Error('Expected the enhancer to be a function.')
    }
    // enhancer 的使用场景
    return enhancer(createStore)(reducer, preloadedState)
}

接下来定义一些变量和函数

let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
let isDispatching = false


// 若是相等 , 作了一层浅拷贝  将 currentListeners 同步到 nextListeners 中
// 避免相互影响
function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
        nextListeners = currentListeners.slice()
    }
}

store.getState

function getState() {
    // isDispatching 默认为 false, 表示当前 store 是否正在 dispatch
    if (isDispatching) {
        throw new Error('//...')
    }

    // 直接返回当前 state , 默认为入参 preloadedState
    return currentState
}

store.subscribe

// 忽略了错误判断
function subscribe(listener) {
    let isSubscribed = true

    // 同步 nextListeners , currentListeners
    ensureCanMutateNextListeners()

    // 将 listener 加入 nextListeners 
    nextListeners.push(listener)

    // 返回解除监听函数
    return function unsubscribe() {
        if (!isSubscribed) {
            // 若是 isSubscribed 已经为 false 了 则 return
            // 状况 1, 已经执行过unsubscribe了一次
            return
        }

        // flag
        isSubscribed = false

        // 同步 nextListeners , currentListeners
        ensureCanMutateNextListeners()
        const index = nextListeners.indexOf(listener)
        nextListeners.splice(index, 1)
        // 搜索 监听器, 删除
        currentListeners = null
    }
}

store.dispatch

function dispatch(action) {
    // 省略了 action 的 错误抛出
    // 总结:  action  必须是一个 Object  且 action.type 必须有值存在

    // 若是当前正在 isDispatching 则抛出 错误(通常来讲不存在

    try {
        isDispatching = true
        // 执行 reducer, 须要注意的是 currentReducer 不能为异步函数
        currentState = currentReducer(currentState, action)
    } finally {
        isDispatching = false
    }

    //  将 nextListeners 赋值给 currentListeners 执行 nextListeners 里面的监听器
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
        const listener = listeners[i]
        listener()
    }

    // 返回 action
    return action
}

store.replaceReducer

function replaceReducer(nextReducer) {
    // 若是 nextReducer 不是函数则抛出错误

    // 直接替换
    currentReducer = nextReducer

    // 相似 ActionTypes.INIT.  替换值
    dispatch({type: ActionTypes.REPLACE})
}

store.observable

还有一个额外的 observable 对象:

// 一个 Symbol.observable 的 polyfill
import $$observable from 'symbol-observable'

function observable() {
    // subscribe 就是 store.subscribe
    const outerSubscribe = subscribe
    return {
        subscribe(observer) {
            // 若是 observer 不是对象或者为 null 则抛出错误

            function observeState() {
                if (observer.next) {
                    // next 的入参为 固然 reducer 的值
                    observer.next(getState())
                }
            }

            observeState()

            // 添加了监听
            const unsubscribe = outerSubscribe(observeState)
            return {unsubscribe}
        },

        // 获取到当前 对象, $$observable 值是一个 symbol
        [$$observable]() {
            return this
        }
    }
}

这里使用了 tc39 里未上线的标准代码 Symbol.observable, 若是你使用或者了解过 rxjs, 那么这个对于你来讲就是很简单的, 若是不熟悉,
能够看看这篇文章: https://juejin.cn/post/684490...

剩余代码

function createStore() {
    // 省略

    // 初始化了下值
    dispatch({type: ActionTypes.INIT})

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

combineReducers

使用

// 能够接受多个 reducer, 实现一种 module 的功能
rootReducer = combineReducers({potato: potatoReducer, tomato: tomatoReducer})


// 返回值
{
    potato: {
        // 某些属性
    }
,
    tomato: {
        // 某些属性
    }
}


const store = createStore(rootReducer, {
    potato: {
        // 初始值
    }
})

有一点须要注意的是, reducer 都是须要默认值的,如:

function counterReducer(state = {value: 0}, action) {
    //...
}

源码解析

combineReducers

先看 combineReducers 执行以后产生了什么

function combineReducers(reducers) {
    // 第一步是获取 key, 他是一个数组
    const reducerKeys = Object.keys(reducers)
    const finalReducers = {}

    // 遍历 reducers, 赋值到 finalReducers 中, 确保 reducer 是一个函数, 不是函数则过滤
    for (let i = 0; i < reducerKeys.length; i++) {
        const key = reducerKeys[i]

        // 省略 reducers[key] 若是是 undefined 抛出错误

        if (typeof reducers[key] === 'function') {
            finalReducers[key] = reducers[key]
        }
    }

    // finalReducerKeys 通常来讲是和 reducerKeys 相同的
    const finalReducerKeys = Object.keys(finalReducers)

    //定义了两个遍历
    let unexpectedKeyCache
    let shapeAssertionError

    try {
        // 此函数后面会详细讲述
        // 答题做用就是确认 finalReducers 中都是有初始值的
        assertReducerShape(finalReducers)
    } catch (e) {
        shapeAssertionError = e
    }
    //...
}

再看他又返回了什么(记住结果必然也是一个 reducer)

function combineReducers(reducers) {

    //...


    return function combination(state = {}, action) {
        // 若是 assertReducerShape 出错则抛出错误
        if (shapeAssertionError) {
            throw shapeAssertionError
        }

        // 忽略非 production 代码

        // 预先定义一些变量
        let hasChanged = false
        const nextState = {}

        // 循环 finalReducerKeys 
        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 再次生成值

            // 若是 nextStateForKey === undefined 则再次抛出异常

            // 给 nextState 赋值
            nextState[key] = nextStateForKey

            // 判断是否改变 (初始值是 false)  判断简单的使用 !== 来比较
            // 若是已经为 true   就一直为 true 了
            hasChanged = hasChanged || nextStateForKey !== previousStateForKey
        }

        // 循环后再次对 true 作出判断
        // 是否少了 reducer 而形成误判
        hasChanged =
            hasChanged || finalReducerKeys.length !== Object.keys(state).length

        // 若是改变了 返回新值, 不然返回旧值
        return hasChanged ? nextState : state
    }
}

combineReducers 基本就是上述两个函数的结合, 经过循环遍历全部的 reducer 计算出值

assertReducerShape

function assertReducerShape(reducers) {
    Object.keys(reducers).forEach(key => {
        // 遍历参数里的 reducer
        const reducer = reducers[key]

        //执行初始操做 产生初始值都有初始值
        const initialState = reducer(undefined, {type: ActionTypes.INIT})

        //...   若是 initialState 是 undefined 则抛出错误


        // 若是 reducer 执行未知操做  返回的是 undefined 则抛出错误
        // 情景: 当前 reducer 使用了 ActionTypes.INIT 来产生值, 这可以经过上一步
        // 但在这一步就会被检测出来
        if (
            typeof reducer(undefined, {
                type: ActionTypes.PROBE_UNKNOWN_ACTION()
            }) === 'undefined'
        ) {
            //... 抛出错误
        }
    })
}

这里咱们能够知道一点, 全部 reducer 咱们都必需要有一个初始值, 并且他不能是 undefined, 能够是 null

compose

这里须要先讲 compose 的使用 才能顺带过渡到下面

使用

就如他的名字, 是用来组合函数的, 接受刀哥函数, 返回执行的最终函数:

// 这里经常使用来 连接多个中间件
const store = createStore(
    reducer,
    compose(applyMiddleware(thunk), DevTools.instrument())
)

源码解析

他的源码也很简单:

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

    if (funcs.length === 1) {
        return funcs[0]
    }
    // 上面都是 控制, 参数数量为 0 和 1 的状况


    // 这里是重点, 将循环接收到的函数数组
    return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

咱们将 reduce 的运行再度装饰下:

// reduce 中没有初始值的时候, 第一个 `prevValue` 是取  `funcs[0]` 的值

funcs.reduce((prevValue, currentFunc) => (...args) => prevValue(currentFunc(...args)))

reducer 返回的就是 这样一个函数 (...args) => prevValue(currentFunc(...args)), 一层一层嵌套成一个函数

举一个简单的输入例子:

var foo = compose(val => val + 10, () => 1)

foo 打印:

(...args) => a(b(...args))

执行 foo() , 返回 11

applyMiddleware

使用

applyMiddleware 是使用在 createStore 中的 enhancer 参数来加强 redux 的做用

可兼容多种三方插件, 例如 redux-thunk, redux-promise, redux-saga 等等

这里使用官网的一个例子做为展现:

function logger({getState}) {
    // next 就是真正的 store.dispatch
    return next => action => {
        console.log('will dispatch', action)

        const returnValue = next(action)

        console.log('state after dispatch', getState())

        return returnValue
    }
}

const store = createStore(rootReducer, {
    counter: {value: 12345}
}, applyMiddleware(logger))

源码解析

default

function applyMiddleware(...middlewares) {
    return createStore => (...args) => {
        // 由于使用了 enhancer 参数, 他的内部没有 createStore 的东西, 因此这里须要从新 createStore
        const store = createStore(...args)
        let dispatch = () => {
            // 在中间件中 不容许使用 dispatch
            throw new Error(
                // 省略报错...
            )
        }

        // 这是要传递的参数
        const middlewareAPI = {
            getState: store.getState,
            dispatch: (...args) => dispatch(...args)
        }

        // 从新 map 全部 middlewares 返回须要的结果
        const chain = middlewares.map(middleware => middleware(middlewareAPI))

        // 这里就是咱们上面的 compose 相关的代码, 返回的结果 再次执行 获得真正的 dispatch
        dispatch = compose(...chain)(store.dispatch)

        // 返回 store 和 dispatch
        return {
            ...store,
            dispatch
        }
    }
}

这里咱们须要从新捋一捋函数的执行, 中间件以上述的 logger 为例子

applyMiddleware(logger) -> 返回的是一个函数(createStore) => (...args) => {/*省略*/} 我把他记为中间件函数 1
也就是说 applyMiddleware(logger) === (createStore) => (...args) => {/*省略*/}

这个函数将在 createStore 中使用 enhancer(createStore)(reducer, preloadedState) 这里的 enhancer 就是中间件函数 1 经过 createStore
的执行咱们能够发现
store === createStore(reducer, preloadedState, enhancer) === enhancer(createStore)(reducer, preloadedState)
=== applyMiddleware(logger)(createStore)(reducer, preloadedState)
=== ((createStore) => (...args) => {/*省略*/})(createStore)(reducer, preloadedState)
=== 中间件函数 1 中的{/*省略*/} 返回结果 经过这一层的推论咱们能够得出 store === 中间件函数 1中的 {/*省略*/} 返回结果

bindActionCreators

使用

这个 API 主要是用来方便 dispatch 的 他接受 2 个参数 , 第一个是对象或函数, 第二个就是 dispatch 返回值的类型很第一个参数相同

首先咱们要定义建立 action 的函数

function increment(value) {
    return {
        type: 'counter/incremented',
        payload: value
    }
}

function decrement(value) {
    return {
        type: 'counter/decremented',
        payload: value
    }
}

使用状况 1:

function App(props) {
    const {dispatch} = props

    // 由于在 hooks 中使用 加上了 useMemo
    const fn = useMemo(() => bindActionCreators(increment, dispatch), [dispatch])

    return (
        <div className="App">
            <div>
                val: {props.value}
            </div>
            <button onClick={() => {
                fn(100)
            }}>plus
            </button>
        </div>
    );
}

使用状况 2:

function App(props) {
    const {dispatch} = props

    const fn = useMemo(() => bindActionCreators({
        increment,
        decrement
    }, dispatch), [dispatch])


    // 若是想用 decrement 也是这样调用 fn.decrement(100)
    return (
        <div className="App">
            <div>
                val: {props.value}
            </div>
            <button onClick={() => {
                fn.increment(100)
            }}>plus
            </button>
        </div>
    );
}

源码解析

function bindActionCreator(actionCreator, dispatch) {
    return function () {
        // 执行 dispatch(actionCreator()) === dispatch({type:''}) 
        return dispatch(actionCreator.apply(this, arguments))
    }
}

function bindActionCreators(actionCreators, dispatch) {
    if (typeof actionCreators === 'function') {
        // 若是是函数直接执行 bindActionCreator
        return bindActionCreator(actionCreators, dispatch)
    }

    if (typeof actionCreators !== 'object' || actionCreators === null) {
        throw new Error(/*省略*/)
    }

    // 定义变量
    const boundActionCreators = {}
    
    // 由于是对象 循环遍历, 可是 for in 效率太差
    for (const key in actionCreators) {
        const actionCreator = actionCreators[key]
       
       // 过滤
        if (typeof actionCreator === 'function') {
            // 和函数一样 执行 bindActionCreator 而且赋值到 boundActionCreators 中
            boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
        }
    }
    return boundActionCreators
}

bindActionCreators 的源码相对简单一点, 理解起来相对也容易不少

总结

redux 中设计的不少地方都是很巧妙的,而且短小精悍, 值得你们做为首次源码阅读的选择

若是我讲的有什么问题, 还望不吝指教

相关文章: react-redux 源码浅析

本文代码仓库: https://github.com/Grewer/rea...

参考文档:

相关文章
相关标签/搜索