React的底层逻辑(入门梳理) | 青训营笔记

529 阅读7分钟

这是我参与「第四届青训营 」笔记创作活动的第10天

摘要

本文旨在根据老师的思路,较为简单且清晰的对React框架的底层原理有一个入门级的梳理,使初学者对这一框架有一个初步的认识

React的设计思路

首先通过一个例子了解一下前端开发中UI编程的痛点
如图:
image.png

这是Apple官网选购商品的界面,当点击不同配色时会有相应图片的变化,并且配合有价格的变化,如图

image.png
从开发的角度只需要给卡片绑定点击事件,做出相应的DOM变化即可,这可以看作一种过程化地编程,针对小型的数据操作哈或许并没有任何影响,但对于一个网页甚至于网站要面对的是大量的数据渲染与更新,这样的方式确实暴露了一些缺陷:

  1. 状态更新,UI不会自动更新,需要手动地调用DOM,进行更新
  2. 欠缺基本的代码层面地封装与隔离,代码层面没有组件化
  3. UI之间的数据依赖关系,需要手动维护,如果依赖链路长,则会遇到"Callback Hell"

而这些在响应式开发中可以得到很好地解决

转换式系统

根据既定地输入求解输出,例如编译器将一种编码转换为另一种编码

响应式系统

关于页面事件地变化,根据事件地变化更新状态随之改变UI,例如前端监控系统 image.png 由以上地介绍可见,在响应式地编程方式下,很好的解决了以上的痛点

  1. 状态更新,UI自动更新(痛点1)
  2. 前端代码组件化,可封装,可复用(痛点2)
  3. 状态之间地互相依赖关系,只需要申明即可(痛点3)

以上就是React的设计思路,那么根据以上的思路,之前的示例就会有如下的解决方式:
将页面的各个部分组件化

image.png ⚠⚠⚠注意: 上面的树状结构并不是页面的DOM结构

总结:


总结下来,React的设计与实现可以归结为以下三点:
  1. 组件是组件的组合/原子组件
  2. 组件内拥有状态,外部不可见
  3. 父组件可以将状态传进子组件

基于以上几点,我们可以推测针对currentValue这个属性,其应该属于哪一个组件

image.png 事实上,根据状态共享及状态传递的方向上我们可以得知,是这一属性应该属于root这一父组件,只有当该属性属于父组件时,其下的诸如顶栏,型号这些子组件才可以共享状态

image.png
知道了这一点之后,我们再来讨论整个的状态变化过程: 我们都知道,在前端开发中,对函数给予了很大的权限,它甚至可以被当作变量或者值来传递,这也就容许了函数可以在组件之间传递,所以针对价格改变这一行为,实际上是执行某一改变价格的函数(此处该函数被定义为onChangeValue())来实现的,而这一函数是被传递的,是由root组件传递给具体显示价格的子组件(型号选择顶栏),然后在触发onClick事件事件之后执行该函数,改变了root中的currentValue,再共享给各个组件

image.png
⚠⚠⚠注意: 虽然好像此处看上有父组件对子组件的参数传输,以及子组件执行之后的参数更新返回,但是实际上并不是,子组件只是单纯的执行父组件交代的行为,接受父组件传递的参数,并不是对父组件进行改变,所以在React中还是单向数据流
那么随之而来的也暴露了几个问题

  • 如何解决状态的不合理上升
  • 组件更新后如何更新DOM

⚠这些问题会在之后有所解释
通过以上的了解,我们要也应该认识到React组件化的而编程思想,同时也应该总结出,组件设计应该注意以下几点

  • 组件声明了状态和UI的映射
  • 组件有props(共享状态)/state(私有状态)两种状态
  • “组件”可由其他组件拼装而成

接下来我们就可以了解一下React组件化的代码是怎么编写的,如下:
image.png 如上我们可以看出,react的编写具有以下几个特点:

  • 组件内部拥有私有状态state
  • 组件接受外部提供的props状态提供复用性
  • 根据当前的一个state和props返回一个UI

这与我们之前组件化的设计是相符的,事实上React使用一种叫做JFX(Javascript和XML)HTML-in-JavaScript语法,并且React支持两种书风格,分别是classhook,以上图片就一种hooks书写方式的应用,也是较为普遍的应用方式

React的实现

接下来则正式介绍React的实现,通过之前对于设计思路的了解,不难发现React的实现有着以下几个问题:

  1. JFX是不符合JS语法的,而浏览器是只能解析JS语法格式编写的代码的
  2. React编写的代码返回的DOM结构发生改变时如何更新
  3. state/props发生更新式需要重新执行render函数

problem 1

直接转义为JS即可。 image.png

problem 2

使用虚拟DOM(Virtual DOM)
Virtual DOM 是一种用于和真实 DOM同步,而在JS内存中维护的一个对象,它具有和DOM类似的树状结构,并和DOM金额已建立一一对应的关系,它赋予了React声明式的API:根据开发者意愿让UI匹配状态,从而将开发者从属性操作、事件处理和手动DOM更新中解放出来。
而我们都知道在网页当中,DOM节点的递归查找,整体刷新是非常消耗性能的,因此React采用一种计算Diff的方式,仅改变需要改变的节点即可。这也解决了之前我们currentValue属性那个例子中提出的更新DOM的问题。

image.png 当节点发生改变时,会生成新的虚拟DOM,与原来的虚拟DOM进行比较,更新不同的,最后再改变真实页面的DOM。
那么,问题就是如何编写这样一个Diff算法???

How to diff?

image.png
这其中就要权衡计算速度与更新次数之间的一个关系 事实上,最好的diff算法需要O(n^3)的时间复杂度,所以通常我们舍弃理论最小的算法,换取时间,在取舍之后形成了现在在使用的Heuristic算法(启发式算法)简单来讲它遵循以下规则:

  1. 不同类型DOM元素 =====>替换
  2. 同类型DOM元素 =====>更新
  3. 同类型组件元素=====>递归

React状态管理库

在开篇时我们引入的那个例子中,我们发现状态的上升容易发生上升不合理的问题,不合理的上升会导致某一组件的任务过于繁重,但是组件的树状组织结构下,为了实现状态的共享,状态上升到父组件又是必需的,因此,React提出了状态管理库的方式来统一管理组件的状态。

image.png 每一个组件都将从状态库中获取共享的状态
当下盛行的状态管理库如下:

image.png

到此我们就React底层的设计思路及实现有了一个浅显的梳理,此篇仅作个人学习笔记,若有错误,还望各位同学指正