自己实现一个Redux
Redux是一个可预测的状态容器,提供可预测的状态管理。
什么是状态?状态其实也就是数据,管理状态也就是对数据的管理。那么什么是可预测的状态管理呢?能够监听数据的变化,获取变化的来源,在发生变化时可以触发某些动作,即为状态管理(个人理解)。
如上所示,就是一个最简单的状态管理,实现了数据基本的管理,但是,并未实现监听数据的变化。因此,此处采用订阅者模式,实现数据变化的监听:
代码很简单,订阅事件&触发事件,实现了监听data状态的变化。以上其实就是Redux的核心内容。项目放在github上,欢迎star
问题1
通过changeState方法可以随意更改state,不受控制。
Redux是“可预测的状态管理器”,因此能够引起状态变化的,必须是在我们可预测的范围内。也就是说,如果引起状态的变化不在我们定义好的变化之内,则拒绝处理该变化,Redux是如何做到这一点的呢?talk is cheap, show me the code:
原来如此!通过敢敢单单的switch case语句即可实现。(至于这个方法为什么叫reducer,后面再解释)
原本的createStore里的changeState方法也需要配合改动(方法名字改成dispatch,意为“派发”动作,更符合其功能):
触发动作时,则需要用以下这种形式:
动作发起者: 洞幺洞幺,我是type为“CHANGE_NAME”的动作
处理动作者:收到,已处理完毕,并返回给你变化后的状态,over
动作发起者: 洞尧洞尧,我是type为“cxk”的动作
处理动作者: 非法类型的动作,拒绝处理,返回初始状态,over
问题2
当store里的数据比较多时,用同一个reducer处理,必然会导致函数很臃肿:
最简单的解决方法,就是按照功能模块对reducer进行拆分:
那么接下来要做的就是,如何把多个reducer组合起来,并最终返回变化后的state。首先将多个reducer按照模块的形式组合在一起:
然后编写一个combineReducer方法,当每次dispatch了action,会依次触发所有reducer,并最终返回变化后的state:
这里可以解释为什么叫reducer了,从上述代码中可以看出,每个reducer其实是(prev, action) => newState这样的一个结构,类似于Array.prototype.reduce的结构,因此称之为reducer。
问题3
想必也能猜到问题3是什么了,上文将reducer按照模块拆分了,但是state本身还没有拆分,如果state过于庞杂,也是不好滴。话不多说,拆:
同时,在createStore里新增一句代码,用于初始化整个state:
因为type不为任何值,因此所有的reducer都会按照default返回初始值,这样就完成了state的初始化,并且在createStore时,也不用再传入initalState了。
middleware(中间件)
国际惯例,什么是middleware?
它提供的是位于action被发起之后,到达reducer之前的扩展点。你可以利用Redux middleware来进行日志记录、创建崩溃报告、调用异步接口或者路由等等。
(网上找的)
说白了,middleware就是对dispatch的扩展,可以利用它实现定制化功能。
举个栗子,我们要实现dispatch时,添加一个日志记录的功能,记录一下action触发前后的状态以及是哪个action触发的,那么可以重写dispatch方法:
假如说我们这时候又想添加一个创建崩溃报告的功能,那么可以这样重写dispatch方法:
那么问题来了,如果我们想要同时具有这两个功能呢?机智的我一下子就想到了,把这俩写到一起不就ok了?
真的是简单到爆了。等等...好像有哪里不对?如果以后想加入更多的功能呢?难道也要把他们都塞进这个新的dispatch方法里面?很明显,不行,那样只会导致dispatch方法越来越臃肿,难以维护。根据我们多年的经验,必然是又要进行拆分了。
从上面的loggerMiddleware和exceptionMiddleware的合作上,可以看出,多个中间件之间的调用顺序应该是类似于
A = (action) => { B(action) }
这样的一种链式结构。为了使middleware更为通用,应该将B作为参数传递进去,因此,可以写出这种结构:
A = (B) => (action) => { B(action) }
这时候会发现,在任意一个中间件里,都有可能用到store,因此,将store作为顶级参数传递进去:
A = (store) => (B) => (action) => { B(action) }
代码如下:
抽离公共方法
每次使用中间件时,都要写一些重复的代码。因此Redux内部有一个方法,可以实现这种链式调用:
(简单的示例,实际的方法比这个更为复杂)
方法实现的比较巧妙,每次将经过上一个中间件处理过的dispatch作为参数传递给下一个中间件,这样就形成了链式调用:
store.dispatch = applyMiddleware(store, loggerMiddleware, exceptionMiddleware)
异步action
在实际项目中,异步是不可避免的。之前的代码中,当我们每次dispatch一个action时,action只能是一个plain object(普通对象,这里指的是直接继承于Object的对象),否则reducer无法成功解析(按照官方文档的意思,action必须是“可序列化的”,这部分暂时不是很理解)。
和 state 一样,可序列化的 action 使得若干 Redux 的经典特性变得可能,比如时间旅行调试器、录制和重放 action。若使用 Symbol 等去定义 type 值,或者用 instanceof 对 action 做自检查都会破坏这些特性。字符串是可序列化的、自解释型,所以是更好的选择。
因为性能原因,我们无法强制序列化 action,所以 Redux 只会校验 action 是否是普通对象,以及 type 是否定义。
然鹅在实际开发中,存在大量的异步场景,例如最常见的请求调用。我们可以利用中间件,让dispatch可以直接接收一个Function类型的action。Redux就提供了这样一个中间件redux-thunk:
代码其实也很简单,判断了一下action是否是函数,是的话则执行这个action。用这个中间件去增强dispatch的功能,就可以按照下面这种形式写异步的action了:
调用时:
dispatch(getCardInfoAction())
tips:无论嵌套执行了多少个middleware,最终被dispatch的那个action,仍然必须是一个plain object,将处理流程变回同步方式。
当 middleware 链中的最后一个 middleware 开始 dispatch action 时,这个 action 必须是一个普通对象。这是 同步式的 Redux 数据流 开始的地方(译注:这里应该是指,你可以使用任意多异步的 middleware 去做你想做的事情,但是需要使用普通对象作为最后一个被 dispatch 的 action ,来将处理流程带回同步方式)。
小结
到这里,redux的大部分功能其实已经基本实现了,剩余的还有一些细节部分,例如将f1 => f2 => f3(action)这种链式调用变为f1(f2(f3(action)))这种调用形式的compose方法(用在applyMiddleware方法内部);createStore方法添加有中间件/无中间件的处理;store.subscribe订阅后的退订以及各种判断的处理等。
Redux的源码短小精悍,使用纯JavaScript实现,不依赖任何第三方库,因此也适用于各种框架。其完整的流程图:
参考:
自己实现一个Redux的更多相关文章
- 一起学习造轮子(二):从零开始写一个Redux
本文是一起学习造轮子系列的第二篇,本篇我们将从零开始写一个小巧完整的Redux,本系列文章将会选取一些前端比较经典的轮子进行源码分析,并且从零开始逐步实现,本系列将会学习Promises/A+,Red ...
- 手写一个Redux,深入理解其原理
Redux可是一个大名鼎鼎的库,很多地方都在用,我也用了几年了,今天这篇文章就是自己来实现一个Redux,以便于深入理解他的原理.我们还是老套路,从基本的用法入手,然后自己实现一个Redux来替代源码 ...
- 【React】360- 完全理解 redux(从零实现一个 redux)
点击上方"前端自习课"关注,学习起来~ 前言 记得开始接触 react 技术栈的时候,最难理解的地方就是 redux.全是新名词:reducer.store.dispatch.mi ...
- 基于react+react-router+redux+socket.io+koa开发一个聊天室
最近练手开发了一个项目,是一个聊天室应用.项目虽不大,但是使用到了react, react-router, redux, socket.io,后端开发使用了koa,算是一个比较综合性的案例,很多概念和 ...
- 我的第一个 react redux demo
最近学习react redux,先前看过了几本书和一些博客之类的,感觉还不错,比如<深入浅出react和redux>,<React全栈++Redux+Flux+webpack+Bab ...
- 我的一个配置redux(实现一次存储与调用方法)之旅
前言 : 今天呢,就配置一下redux,redux的重要性呢,就叭叭叭一大堆,什么也没有带着配置一次来的重要,因为许多涉及到的属性和方法,用法是活的,但格式是需要记忆的. 过程中不要嫌我唠叨,有的地方 ...
- RxJS + Redux + React = Amazing!(译二)
今天,我将Youtube上的<RxJS + Redux + React = Amazing!>的后半部分翻译(+机译)了下来,以供国内的同学学习,英文听力好的同学可以直接看原版视频: ht ...
- 史上最全的 Redux 源码分析
前言 用 React + Redux 已经一段时间了,记得刚开始用Redux 的时候感觉非常绕,总搞不起里面的关系,如果大家用一段时间Redux又看了它的源码话,对你的理解会有很大的帮助.看完后,在回 ...
- 用redux构建购物车
很久没更新博客了,最近要用到react,再来跟大家分享一个redux案例吧. [ {"id": 1, "title": "iPad 4 Mini&qu ...
随机推荐
- 弹性盒子FlexBox简介(一)
一.理解弹性盒子 弹性盒子是CSS3的一种新的布局模式. CSS3弹性盒子(Flexible Box或flexbox),是一种当页面需要适应不同的屏幕大小以及设备类型时,确保元素拥有恰当的行为的布局方 ...
- 超大文件上传方案(PHP)
前段时间做视频上传业务,通过网页上传视频到服务器. 视频大小 小则几十M,大则 1G+,以一般的HTTP请求发送数据的方式的话,会遇到的问题:1,文件过大,超出服务端的请求大小限制:2,请求时间过长, ...
- 校赛 你的粪坑V2
原题 今天举办程序设计比赛,2点30分开始,然而你睡到了2点25分,紧张的你将头发梳成大人模样,敷上一层最贵的面膜,穿着滑板鞋,以飞一般的速度奔向计算机学院准备参加程序设计竞赛!冠军是你的! 然而路上 ...
- 微信小程序自定义底部导航栏组件+跳转
微信小程序本来封装有底部导航栏,但对于想自定义样式和方法的开发者来说,这并不是很好. 参考链接:https://github.com/ljybill/miniprogram-utils/tree/ma ...
- 【CF1247F】Tree Factory(构造)
题意:给定一棵n个点的树,要求将一条可以随意标号的链通过若干次操作变成这棵树 一次操作是指若v不为根且v的父亲不为根,则将v以及v的子树移到v的父亲的父亲上 要求给出标号方案,操作次数以及方案 n&l ...
- 多项式总结(unfinished)
试试以二级标题为主的格式. 多项式相关 注:本篇博客不包含\(FFT\)基础姿势.如果您想要阅读本篇博客,请确保自己对\(FFT,NTT\)有基本的认识并且能够独立写出代码. 多项式是什么? 左转数学 ...
- WinSetupFromUSB - 超简单制作多合一系统安装启动U盘的工具 (支持Win/PE/Linux启动盘)
很多同学都喜欢将电脑凌乱不堪的系统彻底重装以获得一个"全新的开始",但你会发现如今很多电脑都已经没有光驱了,因此制作一个U盘版的系统安装启动盘备用是非常必要的. 我们之前推荐过 I ...
- Springboot-H2DB
为什么在Springboot中用H2DB 用Springboot开发需要连接数据库的程序的时候,使用H2DB作为内存数据库可以很方便的调试程序. 怎么用 1.加入依赖 <dependency&g ...
- 关于MonoBehaviour的单例通用规则
长久以来,对于基于MonoBehaviour的单例总是心有梗结,总觉得用得很忐忑,今天,终于有时间思考和总结了下,理清了想通了.代码和注释如下: 其中GameLogic是我们自己的控制游戏生命周期的管 ...
- SpringBoot 切换国际化
git:https://github.com/xiaozhuanfeng/demoProj 代码结构: application.properties: spring.messages.basename ...