redux源码解读

背景

因为就得去实习了。所以打算开始补补坑。比如自己阅读源码的计划。所以今天来聊聊redux的源码。后续会有redux-thunk和react-redux的源码阅读。搞定这些的话,就开始阅读一个node的库的源码了,比如eventproxy和anywhere。

开始

  • 总览, redux的文件结构

文件看起来貌似不少,其实,要理解redux的内部实现,主要就看 createStore.js

,applyMiddleware.js ,combineReducers.js和compose.js。下面从createStore.js开始看。

  • createStore.js

    export default function createStore(reducer, preloadedState, enhancer) {// 如果第二个参数没有传入初始的state,而是传入了enhancer(为applyMiddleware调用的返回值), 那就将第二个参数,即preloadedState赋值给enhancerif (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {enhancer = preloadedStatepreloadedState = undefined}// 如果传入了enhancer,但enhancer不是一个函数,报错if (typeof enhancer !== 'undefined') {if (typeof enhancer !== 'function') {throw new Error('Expected the enhancer to be a function.')}// 反之, 执行。注意此处。此处意味着,如果createStore的时候传入了enhancer,是会将createStore传入enhancer中,执行enhancer, 而enhancer的返回值也是一个函数。具体的可以等到下面我们讲解applyMiddleware,看完你就知道到底发生了什么。return enhancer(createStore)(reducer, preloadedState)}// 如果没传入enhancer,就继续下面的逻辑// reducer是要求为一个函数的,如果不是一个函数,报错if (typeof reducer !== 'function') {throw new Error('Expected the reducer to be a function.')}    ..........// 最后createStore就会返回dispatch,subscribe, getState等几个常用的apireturn {dispatch,subscribe,getState,replaceReducer,[$$observable]: observable};    
    }
    复制代码

    上面的代码给大家展览了下createStore这个函数大概做了什么,其实就是封装了一些api,最后暴露给用户使用。接下来看一下各个api的实现:

    先看一下私有变量的定义

      let currentReducer = reducer // 就是reducer嘛let currentState = preloadedState // 就是传入的初始state嘛let currentListeners = [] // 当前的监听器队列let nextListeners = currentListeners // 未来的监听器队列let isDispatching = false //  标志是否正在dispatch
    复制代码

    getState : 用来获取store中的state的。因为redux是不允许用户直接操作state,对于state的获取,是得通过getState的api来获取store内部的state。

      function getState() {// 如果正在dispatch的话, 说明新的state正在计算中,现在的state是旧的,为了确保用户能获得新的// state,所以要加一个判断,如果是正在dispatch的话,就报错,反之,返回现在的stateif (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}
    复制代码

    subscribe :redux提供了用户一个监听state变化的api,这个尤为重要,如果没有这个api的暴露,react-redux应该就比较实现了。

      function subscribe(listener) {// listener是state变化时的回调,必须是个函数if (typeof listener !== 'function') {throw new Error('Expected the listener to be a function.')}// 如果是正在dispatch中,就报错。因为要确保state变化时,监听器的队列也必须是最新的。所以监听器的注册要在计算新的state之前。if (isDispatching) {throw new Error('You may not call store.subscribe() while the reducer is executing. ' +'If you would like to be notified after the store has been updated, subscribe from a ' +'component and invoke store.getState() in the callback to access the latest state. ' +'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.')}// 标志是否注册,额,其实个人感觉没啥必要。不过仔细想想,应该是防止用户多次调用取消监听的函数。let isSubscribed = true// 其实这个函数就是判断当前的监听器队列和未来的是否一样,如果不一样那就将当前的赋值给未来的,额,还是不是很理解为什么得这么实现,可能是为了达到数据不可变的效果,避免压进新的回调时,导致当前的监听器队列也有这个回调ensureCanMutateNextListeners()// 将回调压进未来的监听器队列中nextListeners.push(listener)// 注册监听器后会返回一个取消监听的函数return function unsubscribe() {// 如果是已经调用该函数取消监听了,就返回if (!isSubscribed) {return}if (isDispatching) {throw new Error('You may not unsubscribe from a store listener while the reducer is executing. ' +'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.')}// 标志已经取消了isSubscribed = falseensureCanMutateNextListeners()// 删除const index = nextListeners.indexOf(listener)nextListeners.splice(index, 1)}}
    复制代码

    dispatch : 该函数是与getState对应的,getState是读,那dispatch就是写。redux的改动state,只能通过发起一个dispatch,传达一个action给reducer,reducer会根据action和currentState以及自己的内部实现逻辑,来计算出新的state,从而达到写的目的。

      function dispatch(action) {// action要求是一个简单对象,而一个简单对象就是指通过对象字面量和new Object()创建的对象,如果不是就报错。if (!isPlainObject(action)) {throw new Error('Actions must be plain objects. ' +'Use custom middleware for async actions.')}// reducer内部是根据action的type属性来switch-case,决定用什么逻辑来计算state的,所以type属性是必须的。if (typeof action.type === 'undefined') {throw new Error('Actions may not have an undefined "type" property. ' +'Have you misspelled a constant?')}// 如果是已经在dispatch的,就报错,避免不一致if (isDispatching) {throw new Error('Reducers may not dispatch actions.')}// 这里就是计算新的state,并赋值给currentStatetry {isDispatching = truecurrentState = currentReducer(currentState, action)} finally {isDispatching = false}// state更新了后,就如之前我们所说的subscribe,将注册的回调都触发一遍。大家要注意这里,是都触发一遍哦!这个点了解,react-redux的一些原理会比较容易理解。const listeners = (currentListeners = nextListeners)for (let i = 0; i < listeners.length; i++) {const listener = listeners[i]listener()}return action}
    复制代码

    以上就是createStore的大致实现。这个函数难度不大,更多的是一个了解redux的入口。咱们从这个入口来一点点挖掘别的代码的实现。下面先从combineReducers开始

  • combineReducers

    • 这个函数是用来整合多个reducers的, 因为createStore只接受一个reducer。
    • 这个函数分为两部分,第一部分是检验用户传入的reducers的准确性。第二部分就是计算state的逻辑。第一部分大家看一看也就了解了为什么,咱们主要看看第二部分
    export default function combineReducers(reducers) {................return function combination(state = {}, action) {if (shapeAssertionError) {throw shapeAssertionError}if (process.env.NODE_ENV !== 'production') {const warningMessage = getUnexpectedStateShapeWarningMessage(state,finalReducers,action,unexpectedKeyCache)if (warningMessage) {warning(warningMessage)}}// hasChanged来标志是否计算出了新的statelet hasChanged = false// 这个就是存储新的state的const nextState = {}// emmmm, 就是遍历每一个reducer,把action传进去,计算statefor (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没有返回新的state,就报错if (typeof nextStateForKey === 'undefined') {const errorMessage = getUndefinedStateErrorMessage(key, action)throw new Error(errorMessage)}nextState[key] = nextStateForKey// 此处来判断是否有新的statehasChanged = hasChanged || nextStateForKey !== previousStateForKey}// 根据该标志,决定返回原来的state, 还是新的statereturn hasChanged ? nextState : state}
    }
    复制代码

    这个整合的过程就是将所有的reducer存在一个对象里。当dispatch一个action的时候,通过遍历每一个reducer, 来计算出每个reducer的state, 其中用到的优化就是每遍历一个reducer就会判断新旧的state是否发生了变化, 最后决定是返回旧state还是新state。最后得到的state的数据结构类似存reducer的数据结构,就是键为reducer的名字,值为对应reducer的值。这个部分其实也不难。下面继续,我们看看applyMiddleware的实现

  • applyMiddleware

    这个部分就是用来扩展redux的功能的。因为redux的最原始的功能就是操作state,管理state。如果我们需要在这个基础上,根据需求扩展一些功能,就需要通过使用别人编写好的中间件,或者自己编写的中间件来达到需求。比如,发起一个dispatch时,我们为了方便调试,不愿意每次自己手动console.log出这个action,这个时候编写一个logger中间件,就可以自动打印出每次dispatch发起的action,就很容易方便我们测试。又比如,我们要处理异步的action,就可以使用redux-thunk和redux-saga这类中间件。总之,该函数为我们提供了无限的可能。

    我们一点点来看代码:

    export default function applyMiddleware(...middlewares) {return createStore => (...args) => {const store = createStore(...args)............return {...store,dispatch}}
    复制代码

    先看个总览的,注意到applyMiddleware接受不定数量的中间件,然后返回一个接受一个creatStore作为参数,返回一个函数的函数。还记得我们在creatStore的时候么?那里有个场景就是

      if (typeof enhancer !== 'undefined') {if (typeof enhancer !== 'function') {throw new Error('Expected the enhancer to be a function.')}return enhancer(createStore)(reducer, preloadedState)}
    复制代码

    当enhancer不为空且为函数时,就执行该函数,并return回去,作为creatStore的返回值。而这个enhancer是什么呢?翻看redux的文档:

    const store = createStore(reducers, applyMiddleware(a, b, c));
    复制代码

    哦!enhancer就是applyMiddleware的结果,就是一个 creatStore => (...args) = {}的函数。那看下enhancer的代码:

      return createStore => (...args) => {const store = createStore(...args)// 1、也许有的同学一开始看到这个会有点蒙蔽, 我当时看到也是觉得奇怪, 这个dispatch的逻辑不对劲// 而且, 还把这个dispatch作为middleware的参数传了进去,代表在中间件时使用dispatch的逻辑是这个// 但是看到下面, dispatch = compose(...chain)(store.dispatch)// 还行, 根据作用域链, 我们可以知道在中间件中调用dispatch的时候, 其实就是调用了这个dispatch, 而不是一开始声明的逻辑// 而这个dispatch是已经经过compose的包装的了.逻辑到这里的时候就很清楚了let dispatch = () => {throw new Error(`Dispatching while constructing your middleware is not allowed. ` +`Other middleware would not be applied to this dispatch.`)}const middlewareAPI = {getState: store.getState,dispatch: (...args) => dispatch(...args)}// 2、compose是如何将中间件串联在一起的?// 首先一个最简单的中间件的格式: store => next => action => {}// 这一行代码就是传入了store, 获得了 next => action => {} 的函数const chain = middlewares.map(middleware => middleware(middlewareAPI))// 这一行代码其实拆分成两行// const composeRes = compose(...chain);// dispatch = composeRes(store.dispatch);// 第一行是通过compose, 将一个 这样 next => action => {} 的数组组合成 (...args) => f(g(b(...args))) 这么一个函数// 第二行通过传入store.dispatch, 这个store.dispatch就是最后一个 next => action => {}的next参数// 传入后 (...args) => f(g(b(...args)) 就会执行, 执行时, store.dispacth作为b的next传入, b函数结果action => {}会作为// g的next传入, 以此类推. 所以最后dispatch作为有中间件的store的dispatch属性输出, 当用户调用dispatch时, 中间件就会一个一个// 执行完逻辑后, 将执行权给下一个, 直到原始的store.dispacth, 最后计算出新的statedispatch = compose(...chain)(store.dispatch)return {...store,dispatch}}
    复制代码

    跟着上面的注释,大家应该能弄懂enhancer的原理。我这里总结一下,enhancer接收一个creatStore,会在内部创建一个store,然后对该store进行增强,增强的部位在于dispatch。增强的具体方式是通过compose来构造一个dispatch链,链的具体形式就是**[中间件1,中间件2, ......, 中间件N, store.dispatch]** ,然后将增强的dispatch作为store新的dispatch暴露给用户。那用户每次dispatch的时候,就会依次执行每个中间件,执行完当前的,会将执行权交给下一个,直到reducer中,计算出新的state。

结语

网上讲解redux的源码很多,我这篇也是其中一个,主要是我个人学习源码后的,一种记录方式,加深自己印象,也为了之后忘了可以快速重温。redux其实实现上不难,但是思想上真是精髓。程序员的编码能力是一个刚需,但是设计思想是要借他山之玉,来攻石的。站在巨人的肩膀上看远方,希望自己多阅读他人的源码,在能了解原理更好运用的同时,以后自己也能创造出好用的轮子。谢谢大家花时间观看。另外,附源码地址:github.com/Juliiii/sou… ,欢迎大家star和fork ,也欢迎大家和我讨论

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/451859.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

sql语句update中多个case/when的写法

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 又如&#xff1a; update xxxx_xxxx set xxx_typeCASE WHEN xxx_type 0 THENYXLX-0WHEN xxx_type 1 THENYXLX-1WHEN xxx_type 2 THE…

Redis-ha(sentinel)搭建

服务器描述&#xff1a;本次搭建是用来测试&#xff0c;所以是在一台服务器上搭建三个redis服务&#xff08;一主两从&#xff09; 服务角色 端口 Redis.conf名称 sentinel配置文件名称 sentinel端口 redis日志路径 sentinel路劲 主(master) 6379 redis.conf sentine…

学成在线--26.课程图片管理(图片删除)

文章目录一. 需求分析二. API三. 服务端开发1. Dao2. Service3. Controller四. 前端开发1. API方法2. 页面1.before-remove钩子方法2.handleRemove钩子方法一. 需求分析 课程图片上传成功后&#xff0c;可以重新上传&#xff0c;方法是先删除现有图片再上传新图片&#xff1b;…

警惕开源代码库中的安全隐患

最近的一项研究发现&#xff0c; 在调查的31个流行库&#xff08;框架&#xff09;的1261个版本中&#xff0c;超过三分之一存在已知的安全漏洞&#xff0c;大约四分之一的下载文件已经被污染。 该项研究由Aspect Security和Sonatype发起。Aspect Security是一家评估软件安全漏…

jsp注释

jsp注释 <%--注释内容--%> html注释 <!--注释内容-->

线程间的协作(3)——管道输入/输出流

2019独角兽企业重金招聘Python工程师标准>>> 1.管道输入/输出流类 分为两类&#xff0c;字节流管道类&#xff08;PipedInputStream/PipedOutputStream&#xff09;和字符流管道类&#xff08;PipedReader/ PipedWriter&#xff09;。这两个IO流实现了可以在不同的任…

windows简易版本 Redis 使用 demo样例(ssm框架下)

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1. 在网上下载 windows 版本 的Redis 。下载了直接解压出来 &#xff1a; 2. 双击 redis-server.exe 启动服务&#xff08;如下图&#…

Redhat7.3安装配置Telnet详细教程

请参考&#xff1a;https://blog.csdn.net/weixin_39934520/article/details/84835949 谢谢楼主分享&#xff01;

程序员的半衰期只有15年?

曾在Google工作负责过技术工作的科技编辑 Matt Heusser总结了他在Google的生活经历&#xff0c;得出结论&#xff1a; 作为程序员&#xff0c;你只有15年时间。Matt 写道当我在Google工作时&#xff0c;发现Google大部分人都是20出头的年轻人&#xff0c;他们经历的很多事情都是…

EasyNVR、EasyDSS二次开发之:RTMP、HLS流在web页面进行无插件播放示例Demo代码

不管是基于EasyNVR还是EasyDSS&#xff0c;都是支持无插件直播&#xff0c;这也是未来视频直播的一个趋势。对于传统的浏览器插件播放谁用谁知道&#xff1b; 以上是软件自带播放展示 背景需求 对于EasyNVR和EasyDSS的使用方式大概分为两大类&#xff0c;一类是直接将软件作为视…

jsp中%@ % 与% % 与%! %

<% %> 有个符号的&#xff0c;叫做指令用来提供整个JSP 网页相关的信息&#xff0c;并且用来设定JSP网页的相关属性&#xff0c; 例如&#xff1a;网页的编码方式、语法、信息等。<% %>这个叫做小脚本&#xff0c;是写java代码的<%! %>这个是jsp中脚本声明&a…

Hadoop的学习路线图

目录&#xff1a;.1.Hadoop家族产品2.Hadoop家族学习路线图 Hadoop家族产品截止到2013年&#xff0c;根据cloudera的统计&#xff0c;Hadoop家族产品已经达到20个&#xff01;接下来&#xff0c;我把这20个产品&#xff0c;分成了2类。第一类&#xff0c;是我已经掌握的第二…

new TypeToken<List>>(){}.getType() 是什么意思

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1. 项目中代码&#xff1a; List<AppVersion> redisList new Gson().fromJson(json, new TypeToken<List<AppVersion>…

11--移除重复节点

编写代码&#xff0c;移除未排序链表中的重复节点。保留最开始出现的节点。 示例1: 输入&#xff1a;[1, 2, 3, 3, 2, 1] 输出&#xff1a;[1, 2, 3] 示例2: 输入&#xff1a;[1, 1, 1, 1, 2] 输出&#xff1a;[1, 2]

信息图:程序员/开发人员实际在用哪些工具

BestVendor.com的工作人员在全球范围内采访了500名重要开发人员&#xff0c;在调查询问他们实际使用的工具后&#xff0c;制作了一张信息图&#xff0c;如下。 这张信息图覆盖10个方面的工具&#xff1a;Bug 追踪、数据库、开发框架、集成开发环境&#xff08;IDE&#xff09;、…

class特性

每个HTML元素都可以附带一个class特性。有时候&#xff0c;你希望有一种方法可以指定多个元素并将这些元素和页面上的其他元素区分出来&#xff0c;而不是单独指定文档中的某个元素。 <!DOCTYPE html> <!-- To change this license header, choose License Headers in…

Xcode代码提示联想功能失效,按command键点不进去类库,提示“?”

一大早电脑重启了下&#xff0c;打开项目之后出现了一堆问号&#xff0c;怀疑是Xcode 抽风了&#xff0c;本着怀疑的态度&#xff0c;新建了项目&#xff0c;一波操作下来是正常的&#xff0c;代码能联想&#xff0c;command也好使。于是在网上找答案&#xff0c;终于在这里找到…

12-- 缺失的第一个正数

文章目录1.问题描述2.解题代码1.问题描述 给你一个未排序的整数数组&#xff0c;请你找出其中没有出现的最小的正整数。 示例 1: 输入: [1,2,0] 输出: 3 示例 2: 输入: [3,4,-1,1] 输出: 2 示例 3: 输入: [7,8,9,11,12] 输出: 1 提示&#xff1a; 你的算法的时间复杂度应…

java中的private public protected

1、public&#xff1a;public表明该数据成员、成员函数是对所有用户开放的&#xff0c;所有用户都可以直接进行调用 2、private&#xff1a;private表示私有&#xff0c;私有的意思就是除了class自己之外&#xff0c;任何人都不可以直接使用&#xff0c;私有财产神圣不可侵…