这是我参与「第四届青训营 」笔记创作活动的第10天
摘要
本文旨在根据老师的思路,较为简单且清晰的对React框架的底层原理有一个入门级的梳理,使初学者对这一框架有一个初步的认识
React的设计思路
首先通过一个例子了解一下前端开发中UI编程的痛点
如图:
这是Apple官网选购商品的界面,当点击不同配色时会有相应图片的变化,并且配合有价格的变化,如图
从开发的角度只需要给卡片绑定点击事件,做出相应的DOM变化即可,这可以看作一种过程化地编程,针对小型的数据操作哈或许并没有任何影响,但对于一个网页甚至于网站要面对的是大量的数据渲染与更新,这样的方式确实暴露了一些缺陷:
- 状态更新,UI不会自动更新,需要手动地调用DOM,进行更新
- 欠缺基本的代码层面地封装与隔离,代码层面没有组件化
- UI之间的数据依赖关系,需要手动维护,如果依赖链路长,则会遇到"Callback Hell"
而这些在响应式开发中可以得到很好地解决
转换式系统
根据既定地输入求解输出,例如编译器将一种编码转换为另一种编码
响应式系统
关于页面事件地变化,根据事件地变化更新状态随之改变UI,例如前端监控系统
由以上地介绍可见,在响应式地编程方式下,很好的解决了以上的痛点
- 状态更新,UI自动更新(痛点1)
- 前端代码组件化,可封装,可复用(痛点2)
- 状态之间地互相依赖关系,只需要申明即可(痛点3)
以上就是React的设计思路,那么根据以上的思路,之前的示例就会有如下的解决方式:
将页面的各个部分组件化
⚠⚠⚠注意: 上面的树状结构并不是页面的
DOM结构
总结:
总结下来,React的设计与实现可以归结为以下三点:
- 组件是组件的组合/原子组件
- 组件内拥有状态,外部不可见
- 父组件可以将状态传进子组件
基于以上几点,我们可以推测针对currentValue这个属性,其应该属于哪一个组件
事实上,根据状态共享及状态传递的方向上我们可以得知,是这一属性应该属于
root这一父组件,只有当该属性属于父组件时,其下的诸如顶栏,型号这些子组件才可以共享状态
知道了这一点之后,我们再来讨论整个的状态变化过程:
我们都知道,在前端开发中,对函数给予了很大的权限,它甚至可以被当作变量或者值来传递,这也就容许了函数可以在组件之间传递,所以针对价格改变这一行为,实际上是执行某一改变价格的函数(此处该函数被定义为onChangeValue())来实现的,而这一函数是被传递的,是由root组件传递给具体显示价格的子组件(型号选择和顶栏),然后在触发onClick事件事件之后执行该函数,改变了root中的currentValue,再共享给各个组件
⚠⚠⚠注意: 虽然好像此处看上有父组件对子组件的参数传输,以及子组件执行之后的参数更新返回,但是实际上并不是,子组件只是单纯的执行父组件交代的行为,接受父组件传递的参数,并不是对父组件进行改变,所以在React中还是单向数据流
那么随之而来的也暴露了几个问题
- 如何解决状态的不合理上升
- 组件更新后如何更新DOM
⚠这些问题会在之后有所解释
通过以上的了解,我们要也应该认识到React组件化的而编程思想,同时也应该总结出,组件设计应该注意以下几点
- 组件声明了状态和UI的映射
- 组件有props(共享状态)/state(私有状态)两种状态
- “组件”可由其他组件拼装而成
接下来我们就可以了解一下React组件化的代码是怎么编写的,如下:
如上我们可以看出,react的编写具有以下几个特点:
- 组件内部拥有私有状态state
- 组件接受外部提供的props状态提供复用性
- 根据当前的一个state和props返回一个UI
这与我们之前组件化的设计是相符的,事实上React使用一种叫做JFX(Javascript和XML)的HTML-in-JavaScript语法,并且React支持两种书风格,分别是class与hook,以上图片就一种hooks书写方式的应用,也是较为普遍的应用方式
React的实现
接下来则正式介绍React的实现,通过之前对于设计思路的了解,不难发现React的实现有着以下几个问题:
- JFX是不符合JS语法的,而浏览器是只能解析JS语法格式编写的代码的
- React编写的代码返回的DOM结构发生改变时如何更新
- state/props发生更新式需要重新执行render函数
problem 1
直接转义为JS即可。
problem 2
使用虚拟DOM(Virtual DOM)
Virtual DOM 是一种用于和真实 DOM同步,而在JS内存中维护的一个对象,它具有和DOM类似的树状结构,并和DOM金额已建立一一对应的关系,它赋予了React声明式的API:根据开发者意愿让UI匹配状态,从而将开发者从属性操作、事件处理和手动DOM更新中解放出来。
而我们都知道在网页当中,DOM节点的递归查找,整体刷新是非常消耗性能的,因此React采用一种计算Diff的方式,仅改变需要改变的节点即可。这也解决了之前我们currentValue属性那个例子中提出的更新DOM的问题。
当节点发生改变时,会生成新的虚拟DOM,与原来的虚拟DOM进行比较,更新不同的,最后再改变真实页面的DOM。
那么,问题就是如何编写这样一个Diff算法???
How to diff?
这其中就要权衡计算速度与更新次数之间的一个关系
事实上,最好的diff算法需要O(n^3)的时间复杂度,所以通常我们舍弃理论最小的算法,换取时间,在取舍之后形成了现在在使用的Heuristic算法(启发式算法)简单来讲它遵循以下规则:
- 不同类型DOM元素 =====>替换
- 同类型DOM元素 =====>更新
- 同类型组件元素=====>递归
React状态管理库
在开篇时我们引入的那个例子中,我们发现状态的上升容易发生上升不合理的问题,不合理的上升会导致某一组件的任务过于繁重,但是组件的树状组织结构下,为了实现状态的共享,状态上升到父组件又是必需的,因此,React提出了状态管理库的方式来统一管理组件的状态。
每一个组件都将从状态库中获取共享的状态
当下盛行的状态管理库如下:
到此我们就React底层的设计思路及实现有了一个浅显的梳理,此篇仅作个人学习笔记,若有错误,还望各位同学指正