react dispatch_另类 react-hot-loader 自主研发之路

35a9b9fd0082dbbe4ae900792f73e431.png

react-hot-loader

关于 react-hot-loader 想必各位React的拥趸者都曾听闻。简单来讲,他的出现就是为了开发React应用的时候,可以在浏览器快速更新修改过的代码而不会丢失状态。

本文当然不是一篇解读react-hot-loader源码的文章。文章会提出一些有别于react-hot-loader的实现方式并介绍相关工程方法。当然在介绍本文提出的react-hot方法前,还是简单回顾一下react-hot-loader的实现,以做对比。

Hot Reloading in React( Dan Abramov撰文,主要涉及 React Hot Loader 3的实现

react-hot-loader的实现主要借助了Proxy的思想(不过并非是指ES6的Proxy)。这里发挥神效的主要是react-proxy, 我们先来看一段代码示例,就能够大概理解其proxy思想:

import 

对于上面注释4,可能有的小朋友会表示疑惑。这里其实也道出了为什么要使用代理模式,如果不使用代理模式,代码修改以后会重新生成实例进行挂载,之前的状态也就随之丢失,而如果使用代理则不会。当然Dan在文章中也指出,从长远来看,这种方式可能会更好:以某种方式剥离dom上的实例和状态,然后再重新将实例和状态挂载到原剥离位置。不过该方式由于会涉及组件hooks的介入,会产生做或不做的两难境地。

另类 react-hot-loader

看了大神Dan的作品后,来看一下另类react-hot-loader实现。

Disclaimer

所在团队在我做「另类react-hot-loader」时还没有广泛使用Webpack作为打包工具
所在团队使用技术栈为React + Redux

TL;DR

问一个哲学问题:如何能够从A -> B (也就是从一个丢失状态的页面到有状态的页面) ?

答案似乎只有两个,一个是在时间上知道如何从A -> B, 也就是HOW。另一个是,你是上帝,你不需要知道HOW,你完全能够做到从无到有。或者换一种说法,你可以开启上帝模式,不需要知道如何从无到有,只需要是什么就可以了,也就是WHAT。

不同于react-hot-loader从WHAT出发的做法。本方案就是从HOW的角度来对状态进行还原。

How React?

持久化:

React state 的改变只有通过 this.setState 来实现。React组件中执行的在最后一个hook,componentDidMount或者componentDidUpdate, 而如果最后一个执行hook为componentDidMount,即可认为是stateless组件,它的状态有父亲传入的props提供。所以,持久化应该在componentDidUpdate中做。这里选取的技术方案是存储到浏览器的 WebStorage中。

componentDidUpdate

还原:

状态还原应该在组件挂载就绪的时候进行,所以我们从浏览器的 WebStorage 中恢复之前存储的 state,应该在componentDidMount中进行。

componentDidMount

到现在,似乎我们解决问题的主要方法已经得到了,但是仍然存在一些很紧要而不能忽视的问题。

  1. 问:现在的解决方案,解决了单组件的状态 persist & hydrate,但是对于页面中多组件,兄弟、父子关系,在代码运行时,如何确定组件 state 保存的 key?
    答:由于每次刷新页面都是可类比的(当然也不能排除不可类比的情况),因此即使存在着很复杂的组件兄弟、父子关系,组件挂载(mount) 的时序也都是相同的。那么就可以通过时序建立不同组件与 state 的映射关系。这里最简单的方法就是添加一个全局的累加器,在每个组件初始化的时候累加。
constructor
  1. 问:现在的解决方案和代码有着很强的耦合,如何解耦、实现逻辑复用?
    答:可以用 Hoc 解决。只要把我们的组件作为一个参数传递给一个方法,返回一个新的具有 Persist & Hydrate 能力的新组件即可。如下:
const 

关于 React state 的 Persist & Hydrate 解决方案, 要感谢 Jared Palmr 的启发。

How Redux?

而Redux的状态恢复相对于React就要简单很多了,Redux本身遵循的单一数据源、单向数据流函数思想,只要我们能够获取欲恢复状态的Redux store中的state就可以达到恢复状态的目的。幸运的是,已经有可以直接参考的Redux DevTools,该插件的enhancer方法实现了对 Redux state 的 persist & hydrate。 原理是增强 store,扩展 dispatch 方法,当有新的 dispatch 产生时就把 store 中唯一的 state 存储到 WebStorage; createStore 的时候再把之前存储在 WebStorage 中的 state 取出作为 initialState。enhancer 方法代码片段如下:

export 

工程化方案

由于团队内部当时使用了一款自研的armor打包工具,和Webpack有很大差异,所以不能像react-hot-loader那样用loader的方式实现,不过同loader的原理大同小异,都需要对源码进行侵入改造。而armor和Webpack类似也存在enrty的概念,因此,我们这里可以把代码复制一份进行侵入性改造,然后将entry指向改造后的代码即可。

目录结构

4770fb1486e12d55b27b5e635ae41faf.png

如图是一个应用的目录结构,高亮部分为工程生成的中间目录。分别为

  • .timemachine/
    前文所述的 React Hoc 以及 Redux enhancer 都需要对代码进行侵入。为避免代码侵入,同时希望能够使其工具化,面向用户屏蔽这些技术细节。这里将 app/ 全量复制到新建的文件目录 .timemachine/ 下,再进行 React Hoc 以及 Redux enhancer 的改造。
  • .entry
    saga 项目使用 armor 做构建工具,armor 的entry配置支持根目录下的 .entry/.entry.json, 在.entry中放置.timemachine/下的入口:
    .timemachine/page/**/index.jsx .timemachine/page/**/index.scss
  • .build/***/nice-product/.timemachine/
    由于上面我们改变了构建入口,因此生成了.build/***/nice-product/.timemachine/,而非.build/***/nice-product/app/

动态过程

上面描述的是工程化的中间产物,这里描述用户 构建项目 -> 修改代码 -> 自动构建 这整个动态过程中的技术细节。下图描述了从使用自定义的 just watch --timeMachine 命令开始,中间使用 node 借助于上面提到的中间产物,得到我们想要的最终产物 .build/**/app/

25a3f1da6c67c80922c7d9e64eaa8d35.png

干货

「另类 react-hot-loader」源码:

https://github.com/licaomeng/react-time-machine​github.com

参考

[1] https://medium.com/@dan_abramov/the-death-of-react-hot-loader-765fa791d7c4

[2] https://medium.com/@dan_abramov/hot-reloading-in-react-1140438583bf

[3] https://github.com/jaredpalmer/react-persist

[4] https://github.com/zalmoxisus/redux-devtools-extension

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值