这是我参与「第四届青训营 」笔记创作活动的第2天,今天给大家分享一下我观看课程《响应式系统与React》做的笔记,结合了自己的一些资料参考和理解。
1. 响应式与转换式
转换式系统,给定一些输入来求解输出,可以理解为将原数据转换为结果,例如编译器、数值计算。
响应式系统是事件导向,监听事件并做出事件反馈,是一种消息驱动,比如监控系统和UI界面,不用等待输入。
React就是一种响应式系统,能够利用响应式的特点,精准缓解传统UI编程的痛点。
1.1 响应式编程
事件触发->执行既定回调->状态变更->UI更新
所以React就实现了:
-
状态更新,UI自动更新
-
组件化、可复用、可封装
-
状态之间的互相依赖关系只需要声明,无需手动维护
例如,A=B+C,只要声明BC,A无需手动更新
2. 组件化
-
组件是 组件的组合或原子组件,也就是由别的组件组成或者自己本身是最小粒度的组件
-
组件内有状态,外部不可见
-
父组件可将状态传入组件内部,用于父组件对子组件的控制;
具体体现在组件开放一些接口,用于外部使用(接口化的体现)
2.1 组件设计
- 组件声明了状态和UI的映射
- 组件有Props/State两种状态
- 组件可由其他组件拼装而成
由以上设计思路,可以得到实现一个组件的三部曲:
- 组件内部有私有状态State
- 接受外部Props状态,作为接口,提供复用性
- 根据当前的state或props,返回一个具有状态依赖的UI
在划分组件时,某些共用的状态归属的确定原则是:状态归属于两个节点向上寻找到最近的祖宗结点,
3. 生命周期
主要分为Mounting,Updating和Unmounting三个周期,通过props、state等状态触发DOM和refs的更新。
4. Hooks
是一些可以挂载到组件的生命周期中使用的函数,用于让函数组件能够使用state等React特性。
注意,Hooks不能在条件或循环中使用。
以下简单介绍几个常用的hook。
4.1 State Hook
用于让函数组件可以使用state,并进行状态的读写操作;
const [xxx,setXxx] = React.useState(initValue)
// 参数: 第一次初始化指定的值在内部作缓存,这个值只会赋一次
// 返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数,因此可以解构
接收到的setXxx与setState一样有以下两种写法:
setXxx(newValue);
// 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
setXxx(value => newValue);
// 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值
4.2 Effect Hook
可以在函数组件中使用类似类组件中的生命周期函数,
// 下面函数可以看作是componentDidMount(),componentDidUpdate(),componentWillUnmount() 的组合
React.useEffect(() => {
// 在此可以执行任何带副作用操作,也就是DidMount
return () => { // 在组件卸载前执行 即WillUnmount
// 在此做一些收尾工作, 比如清除定时器/取消订阅等
}
}, [stateValue])
// 如果指定的是[], 回调函数只会在第一次render()后执行
// 如果statehook接收到的state值,则每当传入的值发生改变,就会触发此函数,相当于Update
4.3 Ref Hook
useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。
用法:
-
可以用于选中DOM元素,便于操作,此时不用传初始值,React会自动把DOM挂载到ref上
const inputEl = useRef(value); <input ref={inputEl} type="text" /> -
保存可变值,例如全局变量,因为每次渲染都会返回同一个ref对象
5. React的实现
实现react的语法需要面临以下问题
5.1 JSX不符合JS标准语法
通过转译,把JSX转为JS语法,这样就可以被JS解析器解析。
转译是指将一种语言的语法转为另一种语言,比如说TS转JS,而编译是指将代码转为机器可以识别的例如二进制编码的东西。
5.2 返回的JSX改变时如何更新DOM
React是用虚拟DOM实现的,类似DOM但不是DOM,是一种DOM片段。
它用于和真实DOM同步,是JS内存中维护的一个对象(真实DOM不是JS对象,是浏览器内部维护的状态),具有与DOM类似的树状结构,同时可以与DOM建立一一对应的关系。
由于真实DOM的操作较耗费性能,因此使用虚拟DOM可以很好地减轻性能消耗。
修改的过程是通过Diffing算法,找出发生变化的虚拟DOM结点,重新渲染此部分结点及其子结点。因此父节点的状态更新会引起所有子节点的更新,可能造成性能问题。
5.3 如何执行Diff
两棵树进行递归比较,有以下情况
- 替换:不同类型的元素
- 更新:同类型的DOM元素
- 递归:同类型的组件元素
这样可以实现O(n)的复杂度,也就是遍历一次即可。
6. 状态管理库
6.1 简介
实际上就是把状态抽离到UI外部的一个存储单元,进行统一管理,之后组件可以通过这个存储单元获取状态。
那有什么缺点呢?
前面说到,组件需要维护内部的状态,这是为了组件的复用性。如果组件依赖了外部的状态,那么是一种强耦合,会降低复用性。另外,一个组件所依赖的状态一般不应该被其他状态访问到,除非是一些所有组件都需要使用的状态。
推荐的状态管理库:
- redux
- xstate
- mobx
- recoil
6.2 状态机
有一个当前状态,收到外部事件来迁移到下一个状态。例如红绿灯,时间到了表示外部事件,则转换为下一个灯。
7. 应用级框架推荐
- NEXT.JS
- MODERN.JS
- Blitz:无API思想的全栈开发框架,无需API调用和CRUD逻辑,适合前后端紧密的项目