https://v.qq.com/x/page/e0544loifex.html
简明前端史我对于Web前端历史的划分会站在数据以及代码可维护性的角度,把前端历史划分为古典时代、中世纪和文艺复兴三个阶段。
作为前端工程师,怎样把数据传递给用户,并把用户的想法意图转化为数据,这是我们要解决的本质问题。
人脑是无法直接读懂数据的,于是我们有了UI,UI成为了人与数据之间的桥梁。UI捕获用户的输入,然后UI按照数据源的接口对数据源进行变更操作。数据源根据变更后的最新数据按照UI能理解的格式进行渲染并传输到UI,最后UI用人们能理解的格式展现数据。
这就是我们早在90年代就开始使用的最传统的BS架构。
对于当时的Web应用来说,数据源只有一个,就是远程GDB Server。所有对于数据的变更操作都是用URL来区分不同的请求。所有被试图更新的操作都靠刷新完整页面来进行。浏览器维护的history通过记录UI变化来维护不同状态的切换,最典型的使用场景就是浏览器提供的前进后退按钮。
总结我们当时的Web框架非常可靠,不容易出错,即使出了错也很容易定位。浏览器帮我们搞定了大部分事情,开发者只需关心UI渲染html+css。这种架构的设计简单明了,学习成本低。
古典时代:AJXJ来了(2005)
它对于前端开发来说是最好的时代,但是对于用户来说这却是最坏的时代。
AJAX带来的变化
以前只有单一数据源,现在又多了很多本地的临时数据。
以前只通过URL进行数据变更,现在增加了AJAX异步请求,而且同时用户输入会使得UI对本地存放的临时数据进行修改。
曾经是后端统一直出html,现在变成前端直接通过DOM操作进行局部渲染。
曾经浏览器的前进后退功能都无效了,数据状态只能靠自己管理。
总结
应用中有多个数据源,维护多个数据源之间的一致性将变得非常困难。
因为多个数据源之间是有关联的,导致应用内会有多处代码来操作同一处数据,预测一个代码带来的数据变更愈发困难。
整个应用内的任何代码都能随便修改DOM,当出现问题的时候不知道谁修改了DOM。
状态管理更是无从谈起。
从此前端代码变得复杂又混乱。BUG越来越多,程序员需要加班修BUG。页面经过多次迭代,代码无法维护,程序员又得加班重构。
中世纪:React(2003)
因此我将2005年之后的这段时期定义为“中世纪”,无尽的BUG,无尽的黑暗。
到2013年出现了转机,Facebook开源了React。
React强势把应用拆分成组件树,每个组件内的数据由state和props构成。Props由父组件传进来,state则是内部维护的一个本地状态。state和props的任何变化都会触发组件的重新渲染。
裸用React
每个组件都有自己本地的state,而React间组件的通信非常繁琐。所以要依靠React组件之间的通信去同步多个state之间的数据将变得非常痛苦。
React没有对数据变更进行约束。
在UI渲染方面React做得很好,没有DOM操作,与真实DOM隔离。为我们省去了很多关于渲染性能优化的工作。
React+Redux(2015)
Redux诞生于2015年,诞生不久就被官方输入了React的豪华全家桶之内。
Redux要搭配React使用首先就要摈弃React组件内部的state。这时React就将回归纯渲染,意味着传给最顶层的父组件一个Props数据,整个组件树构成的view就渲染出来了。
因此我们可以把React组件树抽象为一个函数。
这是一个纯函数,意味着输入一个确定的参数Props,它就会输出一个确定的view。只要输入的Props不变,那么输出的view就一定不会改变。
React+Redux数据流
React和Redux结合使用有一点需要注意的是,Redux启用了一个中间件的机制,中间件可以拦截全局触发的action,并根据自己拦截的action按需进行修改或再次触发其它action。
这个中间件的设计非常强大,使得Redux的扩展性得到很大的提升。
Redux三大原则
单一数据源。
state只读,只能通过触发action来进行更改。
action通过reducer来修改state,reducer必须为纯函数。
时间旅行
我们只要拿到最初始的state和用户会话中触发的所有action,我们就能一一还原出本次会话的所有空间状态。
又因为reducer渲染成view本身也是一个纯函数,我们就能通过state还原当前用户会话的所有UI变化。
Redux官方称这种变化为时间旅行。
总结
React+Redux的架构只有一个数据源,就是React的全局state。所有变更都统一由action触发,页面的渲染则统一由React组件树来完成。“时间旅行”的特性使状态管理变得非常容易。
文艺复兴
我把2013年至今的这段时间定义为“文艺复兴”时代,前端代码重新变得清晰有序,化繁为简。
但是Redux看似简单,用起来却不容易。
大象关冰箱with Redux
用Redux来解决“把大象关进冰箱要几步”这个经典哲学问题。
为了区分action,首先要定义三个不同的action type:“打开冰箱门”、“把大象关进去”、“关上冰箱门”。
然后需要实现三个actionCreator,去创建并触发这三个不同的action。
最后还要实现三个reducer去处理这三个action。
当我第一次看到Redux文档的时候我好像突然顿悟了,但当我第一次写Redux应用的时候,我的内心是崩溃的。
Redux在处理异步这方面也是有问题的。
它并没有明确规定异步处理应该放在哪一层来做,这导致每个开发都有自己的理解。
因此在一个Redux项目里,AJAX请求满天飞,写出来的代码简直没法看。
Redux的模块化
任何大型应用都无法避免多人协同开发,而协同开发一定离不开模块化。然而Redux官方并没有提及模块化方面的实践思路。
每个模块都拥有自己的action/actionType/reducer。
我们必须要保证不同模块的actionType避免重名。
模块之间要具备通信功能。
解决模块动态加载破坏了reducer纯净的问题。
Redux的API
Redux一共对外暴露了10个API,其中有5个与Redux的扩展性相关。这说明Redux需要被扩展和加强。
综上所述,Redux只提供了核心的状态管理器,并为此实现了尽可能简化的API。缺乏约束的设计使得Redux社区出现了N种最佳实践,这对于社区来说是好事,但对于普通开发者来说则未必。所以我觉得Redux不适合直接用于生产环境。
因此,我觉得我们需要一款框架对Redux进行封装和约束。
duxjs
duxjs是一个可用于生产环境的、基于React+Redux的前端框架。duxjs的部分思想借鉴了ducks,它的部分API设计则借鉴了choo。
duxjs特性
声明式API,没有样板代码。
模块化/组件化,可嵌套,可动态加载。
统一的异步处理。
duxjs同时也支持同构、热替换以及插件功能。
组件是duxjs中对于业务进行封装的最小容器。组件内可以定义自己的state、actions,selectors、views、subscriptions和components。组件内可以定义子组件,说明组件之间可以嵌套,最终形成一个组件树。
duxjs把actions分为同步action和异步action。
同步action严格按照Flux的action标准进行约束,整个action实体都可被序列化。
每个异步action都有一个effect,异步操作都会写在effect里面。除了effect还有子action,子action必须是同步的action。现在每个异步action分别默认附带了三个子action。也可以扩展更多自己的子action。Effect的异步任务执行过程中可以触发任何其它action。
State
定义组件内的初始化state。
Selector
对外暴露的state数据接口。
View
集成与组件相关的view。
Subscriptions
订阅来自外部系统的消息,比如websocket、全局键盘事件以及jsbridge通知。
Module
在组件之外我们还有一个模块的概念,就是module。duxjs的组件可以形成组件树,模块就是这个组件树的容器。和组件一样,模块也能定义在组件中成为子模块。
模块和组件的区别就在于,同一个模块内,同一个module组件是耦合的。同一模块内不同组件定义的所有action与module 和selector都共享空间,而模块与模块之间是完全解耦的。
在component中定义子模块,这里我们支持模块的静态加载和动态加载两种方式。
子模块如果向父模块通信,首先父模块在定义子模块的时候,还需要定义好想监听的函数。父模块对子模块的特点action进行监听,当监听被触发时可以就可以做一些想做的事件。