这是我参与「第四届青训营 」笔记创作活动的第8天
React 响应式编程
重点知识点介绍
- react 历史与应用
- react 的设计思路
- hooks 的写法
- react 的实现
- 状态管理库
- 应用级框架科普
详细知识介绍
一、历史与应用
2010年 脸书在其php生态中,引入了xhp框架,首次引入了组合式组件思想,启发了后来的react设计
2011年 FaxJS诞生,也就是后面的React原型
2013年 React 正式开源
2014 - 今天 生态大爆发,各种围绕React的新工具/框架开始涌现
只需要关注状态如何变化,以及状态变化之间的关系,无需关注dom如何改变,这是react所做的,了解一个事物从其历史上去了解会明白该事物是如何一步步演变的,如何形成的,更有利于明白它的思想
React的应用场景有:
- Three Fiber 3D绘制
- 前端应用开发
- 移动原生应用开发
- 桌面应用开发
- VR开发
二、设计思路
通过一个小小的例子来引入
如图一个手机商品页面展示,右上角显示当前手机的价格
在下面可以选择手机的配置,那么对应不同的配置都要去更新对应的手机价格
如果用原生的写:
思路: 给上面的配置写一个 onclick 事件配合callback回调函数,手动调用接口去改变样式
需要挂载许多点击事件,在回调函数中还要写很多改变样式,类似于过程性编程了
- 状态更新时,UI不会自动更新,需要手动地调用DOM进行更新
- 欠缺基本的代码层面的封装和隔离
- 需要手动去维护,过程性思想,依赖链长的话需要一层一层的去改细节,陷入 “callback地狱”
由此React引出一套响应式系统来解决,需要注意的是 转换式系统 != 响应式系统
转换式系统主要是数值计算,给定输入求解输出。
响应式的思想:
前端不需要很多计算转换,而是需要监听事件,事件发生时改变UI或者引发其他事件,事件发生会扩散,引起一系列的改变
由此响应式编程需要:
- 状态更新,UI自动更新
- 前端代码组件化,可复用,可更新
- 状态之间的互相依赖关系,只需声明即可
组件化实现:
真实DOM不是JS内部的一个变量,而是浏览器的一个变量,DOM本身只能通过去调用DOM的api来修改,如图这种划分并不是DOM树,而是我们自己的一种划分。DOM和UI之间存在映射关系,将组件进行划分成树的样式,每个组件都有自己的状态,外界不可见,并且父组件可以将状态传入子组件内部,来控制子组件的运转
当前价格只能属于Root节点,这样顶栏和配置面板才能共享这一价格数据,但很危险,状态不合理上升
如果想让两个组件共享,则需要往上放,寻找二者最近的父节点或以上的节点
针对于局部性的状态,就可以放在一起
思考: React是单向数据流,还是双向的
永远只能是父组件给子组件传东西,子组件不能给父组件传东西,但这并不意味着子组件不能去改变父组件状态,父组件可以通过给子组件传函数来改变
组件由此应该有:
- 组件声明了状态和UI的映射
- 组件有Props/State两种()
- 组件
那么组件的代码应该长什么样子?
- 组件内部拥有私有状态state
- 组件接收外部的Props状态提供复用性
- 根据当前的 State/Props,返回一个UI
组件的生命周期(理解)
一个组件要显示在UI上需要挂载,需要执行constructor,每当状态发生改变和卸载的时候都要重新执行render,计算后的结果会根据react提供的方法来实现
组件挂载后经过render函数,render函数就是这个组件函数
三、 Hooks的写法
在[react]类组件(class)写法中,有setState和生命周期对状态进行管理,但是在函数组件中不存在这些,故引入hooks(版本: >=16.8),使开发者在非class的情况下使用更多react特性。 hooks就是useState和useEffect
上述例子中,useState相当于constructor,完成数据的初始化
父作用,如果一个组件内部只是单纯的执行语句,不对组件外部造成影响,对组件内部造成影响,但如果对外部造成影响的话,和外部系统有交互的话,就需要引入父作用,
useEffect相当于componentDidMount和componentDidUpdate两个生命周期
一旦有父作用就有useEffect这样一个函数,父作用往往有一个明确的时间点, 第一个参数是一个函数,第二个参数是一个数组(若为空数组的话,则只在挂载的时候执行一次,若为空则一直执行)
四、react 的实现
1. 语法转换
react写的.jsx文件肯定是不能直接运行的,不符合js的语法,需要先进行转译后才能在浏览器上正常运行,节点通过react的creatElement函数来添加
2.返回的JSX更新时,如何更新DOM的
本身return的是一个类似于dom但不是dom的东西,每次更新jsx就会要去更新UI
客观事实:dom的操作是耗费性能的,尤其是先去掉旧的全部dom,再添加更新后的dom
由此我们想到,可以只去更新那些更新部分的dom节点,通过虚拟DOM和diff算法,去算不同的部分然后更新
再次强调,JS内没有DOM这一变量,DOM是浏览器里的变量,我们修改DOM都是通过api来实现的。虚拟DOM实现了JS里拿到DOM
如下图,虚拟DOM和真实DOM是一一对应的
JS在有了虚拟DOM后就可以进行计算,diff算法,只改变改动的部分
性能提升 How to Diff ?
既要满足计算速度快,又要保证更新的部分少
完美的最小Diff算法,需要O(n^3)的复杂度,时间复杂度很高
Heuristic O(n) Algorithm 启发式算法,虽然不是全局最优,但是局部最优,在当前情形下是最优的
牺牲理论最小Diff,换取时间,得到了O(n)复杂度的算法:算法本身的逻辑可能没有得到严格的证明,但在语义上或者直觉上可能是对的。虽不是最优解,但是最适配且合理的感觉
算法思想是:
两个树从根节点,逐个递归的比较,根据比较结果执行操作。最坏情况下就是全部节点遍历一遍,O(n)
五、状态管理库
手机案列: 状态上升,可能会所有状态都会堆积到根节点上
将状态抽离出来,状态不一定非得在组件里,设立一个状态仓库去管理,这样就可以避免状态的不合理上升问题。但有一个最大的坏处,降低了组件的复用性,组件依赖于实际的外部store中,最好只在里面放距离非常远或者不可复用的状态放在里面。
理解这个状态究竟应该是归类于组件单独拥有,还是整个app拥有的,才好决定哪些状态放在管理库里
推荐的状态管理库:
状态机:有很多种状态,状态之间存在转换,当有事件出入时候会根据事件类型和当前状态发生状态转变和事件迁移
六、应用级框架推荐
react本身只是个JavaScript的library,本身没有配置其他的东西
- Next.js
- Modern.js 内置了非常多的开箱可用的功能
- Blitz
小结
这节课中感觉重要的是理解react的设计思路和react的实现部分,react如何被设计出来的,响应式编程的思想,react实现部分应该解决的问题和如何解决的,当然其他部分也要有一定的简单认识