写点什么

人人能读懂 redux 原理剖析

  • 2023-02-23
    浙江
  • 本文字数:5948 字

    阅读完需:约 20 分钟

一、Redux 是什么?

众所周知,Redux 最早运用于 React 框架中,是一个全局状态管理器。Redux 解决了在开发过程中数据无限层层传递而引发的一系列问题,因此我们有必要来了解一下 Redux 到底是如何实现的?

二、Redux 的核心思想?


Redux 主要分为几个部分:dispatchreducerstate。我们着重看下 dispatch,该方法是 Redux 流程的第一步,在用户界面中通过执行 dispatch,传入相对应的 action 对象参数,action 是一个描述类型的对象,紧接着执行 reducer,最后整体返回一个 store 对象,我们来看下这部分的源码:


// 主函数createStore// 返回一个store对象export default function createStore(reducer, preloadedState, enhancer) {  // 增强器  if (typeof enhancer !== 'undefined') {    if (typeof enhancer !== 'function') {      throw new Error('Expected the enhancer to be a function.')    }
return enhancer(createStore)(reducer, preloadedState) }
if (typeof reducer !== 'function') { throw new Error('Expected the reducer to be a function.') }
let currentReducer = reducer let currentState = preloadedState let currentListeners = [] let nextListeners = currentListeners let isDispatching = false
// 获取最终的state function getState() { if (isDispatching) { throw new Error( 'You may not call store.getState() while the reducer is executing. ' + 'The reducer has already received the state as an argument. ' + 'Pass it down from the top reducer instead of reading it from the store.' ) }
return currentState }
// dispatch // 参数action function dispatch(action) { // 校验传入的action // action必须是个对象,否则抛出错误信息 if (!isPlainObject(action)) { throw new Error( 'Actions must be plain objects. ' + 'Use custom middleware for async actions.' ) }
// 检验action对象的必要属性 // type属性是action对象必要的属性 // 如果传入的action没有type属性,则抛出错误信息 if (typeof action.type === 'undefined') { throw new Error( 'Actions may not have an undefined "type" property. ' + 'Have you misspelled a constant?' ) }
if (isDispatching) { throw new Error('Reducers may not dispatch actions.') }
try { isDispatching = true
// 执行传入的reducer函数 // 返回state,给currentState赋值 currentState = currentReducer(currentState, action) } finally { // 一个dispatch执行完,还原状态 isDispatching = false }
// 执行订阅函数队列 // dispatch执行的同时会一并执行订阅队列 const listeners = (currentListeners = nextListeners) for (let i = 0; i < listeners.length; i++) { const listener = listeners[i] listener() }
// 返回action return action }
// When a store is created, an "INIT" action is dispatched so that every // reducer returns their initial state. This effectively populates // the initial state tree.
// 默认执行一次dispatch,做初始化 dispatch({ type: ActionTypes.INIT })
// 返回一个store对象 return { dispatch, subscribe, getState, ... }}
复制代码


通过源码我们可以基本清楚,通过执行 createStore 方法,最终会返回一个 store 对象,该对象主要暴露几个属性,我们主要关注比较常用的:dispatchgetStategetState,看下实际用例:


import createStore from 'redux'
// 创建一个reducerfunction reducer(state={}, action) { switch(action.type) { case 'TEST': return { ...state, test: 'test success' } }}
// 返回storeconst store = createStore(reducer, initState={})
// 执行dispatchstore.dispatch({ type: 'TEST'})
const state = store.getState() // 返回 {test: 'TEST'}
复制代码

三、Redux 中间件原理

接下来我们来探讨 Redux 的另一个重要组成部分---中间件。什么是 Redux 的中间件?Redux 中间件其实是通过重写 createStore 来增强和扩展原来的 dispatch 方法,使其能够在执行 dispatch 的同时可以同步执行其它方法,比如 redux-thunk 就是一个处理异步的中间件:


function createThunkMiddleware(extraArgument) {    // 中间件规定格式    // 闭包返回三层嵌套  return ({ dispatch, getState }) => next => action => {    if (typeof action === 'function') {      return action(dispatch, getState, extraArgument);    }
return next(action); };}
const thunk = createThunkMiddleware();thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
复制代码


下载了中间件,那么我们来看下如何使用中间件:


import createStore, {applyMiddleWare} from 'reduximport reduxThunk from 'redux-thunk'// 创建一个reducerfunction reducer(state={}, action) {    switch(action.type) {        case 'TEST':        return {            ...state,            test: 'test success'        }    }}// 返回store// 中间件作为applyMiddleWare的参数传入createStoreconst store = createStore(reducer, initState={},applyMiddleWare(reduxThunk))
复制代码


我们会发现,中间件的使用方式是用 applyMiddleWare 把中间件作为参数传入 createStore 中,那么 applyMiddleWare 是如何实现的?在这之前我们先看下 createStore 方法的第三个参数是什么,我们回看下 createStore 源码:


export default function createStore(reducer, preloadedState, enhancer) {  ...  // 增强器  // 第三个参数是enhancer,也就是我们传入的applyMiddleWare  if (typeof enhancer !== 'undefined') {    if (typeof enhancer !== 'function') {      throw new Error('Expected the enhancer to be a function.')    }
// 在这里return了enhancer结果 // 传入了createStore,reducer,preloadedState // 实际上是重写了createStore return enhancer(createStore)(reducer, preloadedState) }
...}
复制代码


看完了 enhancer 的实际作用,我们可以弄清楚 applyMiddleWare 的实现原理,请看源码:


import compose from './compose'
// 传入middlewares中间件export default function applyMiddleware(...middlewares) { // 闭包嵌套返回2个方法 return createStore => (...args) => { // 返回store 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) // 返回一个全新的dispatch方法,不污染原来的dispatch }
// 执行中间件第一层方法 // 回顾下中间的格式:({getState, dispatch}) => next => action => next(action) // 这里会比较绕 const chain = middlewares.map(middleware => middleware(middlewareAPI)) // 返回一个中间件的函数集合[next => action => next(action), next => action => next(action)]
// 使用compose聚合chain函数集合 // 返回新的dispatch dispatch = compose(...chain)(store.dispatch)
return { ...store, dispatch } }}
复制代码


这里可能会让人很疑惑,不大清楚的童鞋可以先看下中间件的规范写法,这里还有一个重要的函数 compose,我们来看下 compose 怎么处理 chain 函数集合的,请看源码:


参考 前端进阶面试题详细解答


/** * Composes single-argument functions from right to left. The rightmost * function can take multiple arguments as it provides the signature for * the resulting composite function. * * @param {...Function} funcs The functions to compose. * @returns {Function} A function obtained by composing the argument functions * from right to left. For example, compose(f, g, h) is identical to doing * (...args) => f(g(h(...args))). */
// 传入聚合函数集合// 集合为:[next => action => next(action), next => action => next(action)]// 返回一个新的函数: (arg) => arg export default function compose(...funcs) { // 判断如果没有则返回一个新函数 // 可以联想一下dispatch的定义 // function dispatch(action) { ... return action } if (funcs.length === 0) { return arg => arg }
// 判断如果只有一个中间件,则直接返回第一个 if (funcs.length === 1) { return funcs[0] }
// 这里用了reduce函数 // 把后一个的中间件的结果当成参数传递给下一个中间件 // 函数列表的每个函数执行后返回的还是一个函数:action => next(action) // 这个函数就是新的dispatch // 最后返回函数:(...args) => action => args(action) return funcs.reduce((a, b) => (...args) => a(b(...args)))}
复制代码


compose 的源码及其简洁,但是很精髓,几乎是整个中间件最出彩的地方。通过 reduce 把每个中间件都执行一遍,并且是通过管道式的传输,把每个中间件的返回结果当成参数传递给下一个中间件,实现了剥洋葱式的中间件模式。这里比较难理解,新手可以先写几个简单的中间件,然后再去慢慢理解为什么要这么处理,理解后就会知道这段代码有多简洁了。

四、手写一个 Redux

源码解析完了,我们来简单实现一个 redux

createStore

// 判断值是否是对象类型function isPlainObject(obj) {    if(!obj) {        reutrn false    }
return Object.prototype.toString.call(obj) === '[object, Object]'}
export default createStore(reducer, enhancer) { // 先判断有没有传入中间件 // 有则之间返回 if(typeof enhancer !== 'undefined') { // 必需是个函数 // 因为需要传参 if(typeof enhancer !== 'function') { return }
return enhancer(createStore)(reducer) }
let state = {} // 初始化state let listeners = [] // 发布订阅函数队列
// 定义getState 函数 function getState() { // 直接返回state return state }
// 定义dispatch 函数 function dispatch(action) { try{ // 执行reducer, 返回state state = reducer(state, action) }catch(e) { console.log('dispatch error: 'e) }
// 订阅 listeners.forEach(listener => listener())
// 返回action return action }
// 定义subscribe 函数 function subscribe(listener) { if(!listener) { return }
// 必需是回掉函数 // 因为需要在dispatch里执行 if(typeof listener !== 'function') { return }
Listeners.push(listener) }
// 返回对象:包含getState, dispatch, subscribe 三个方法 return { getState, dispatch, subscribe }}
复制代码

compose

    function compose(...funs) {        if(!funs) {            return arg => arg        }
if(funs.length === 1) { return funs[0] }
// 遍历传入函数,返回一个新函数 return funs.reduce((a,b) => (...args) => a(b(...args))) }
复制代码

applyMiddleWare

import compose from './compose'
function applyMiddleWare(...middlewares) { return createStore => reducer => { // 先返回一个store const store = createStore(reducer)
// 创建middleApi const middleApi = { getState: store.getState, dispatch: (...args) => store.dispatch(...args) // 返回一个新的dispatch }
// 注入middleApi // 并返回函数集合 const chain = middlewares.map(middleWare => middleWare(middleApi))
// 通过compose函数,执行所有中间件,并返回一个新的dispatch const dispatch = compose(...chain)(store.dispatch)
// 返回store对象 return { getState: store.getState, dispatch } }}
复制代码

logger 中间件

    function logger({getState, dispatch}) {        return function(next) {            return function(action) {                console.log('prev')                next(action)                console.log('done')            }        }    }
复制代码

测试

    import createStore from './myCreateStore'    import applyMiddleWare from './myApplyMiddleWare'    import logger from './logger'
// 创建reducer function reducer(state={}, action) { switch(action.type) { case 'TEST': return { ...state, test: 'test success' } } }
// 引入中间件 const middleware = applyMiddleWare(logger)
const store = createStore(reducer, middleware) // 返回{getState, dispatch}
复制代码

总结

至此一个完整的 redux 我们就已经分析完了,个人认为中间件的 compose 这里是比较不好理解的点,但是只要明白中间件主要要解决的是增强 dispatch 函数,就可以顺着这个思路去理解。接着再试着写几个中间件,进一步理解为什么中间件的格式需要返回嵌套的三层函数,明白了这两个点,redux 的原理也就基本能够明白了,有问题欢迎在评论中指出。


用户头像

还未添加个人签名 2022-09-05 加入

还未添加个人简介

评论

发布
暂无评论
人人能读懂redux原理剖析_前端_夏天的味道123_InfoQ写作社区