这是我参与「第四届青训营」笔记创作活动的的第4天
一个优秀的框架的实现,必然有属于它的开发初衷,可能是因为它解决了一些复杂且麻烦的问题,或者是因为能够为开发者带来便利,那么我们在学习新技术的时候,就很有必要了解这门技术的设计思路,这不仅对我们技术的提高有所帮助,同时也能让我们较快地从“懵”的状态切换到“清醒”的状态。
下面我们来看下 React 的设计思路是怎么一回事。
React 的设计思路
针对 UI 编程痛点:
- UI 不会自动更新,需要手动调用 DOM 进行更新;
- 欠缺基本的代码层面的封装和隔离,代码层面没有组件化。
- UI 之间的数据依赖关系,需要手动维护,如果依赖链路长,则会遇到“Callback Hell”。
响应式与转换式:
什么是转换式系统:给定输入求解输出,比如编译器的实现,数值计算等场景。
什么是响应式系统:监听事件,由消息驱动,需要有一个监控系统去关注事件,并对事件做出响应,更新UI界面。
对于一个 web 应用,显而易见的,我们需要用响应式系统去实现,因为用户不可能手动输入参数并运行程序去求解输出,这也不符合我们的操作习惯。那么,响应式系统使用监听事件的方式,感知用户的操作行为,并响应它,使得页面达到用户的预期效果,且内部业务逻辑确保输出数据的准确性。
通过响应式编程,React 可以:
- 状态更新,
UI也会进行更新 - 前端代码组件化,可复用,可封装
- 状态之间的互相依赖关系,只需声明即可
React hooks 的写法
函数组件:
import React, { useState } from 'react';
function Example() {
// 声明一个新的叫做 “count” 的 state 变量
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
State hook
const [count, setCount] = useState(0);
useState 就是一个 Hook ,0就是count的初始值,通过调用setCount可以更改count的值。
我们可以在一个组件中多次使用 State Hook:
import React, { useState } from 'react';
function Example() {
// 声明多个 state 变量!
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Effect Hook
useEffect(() => {
// code
});
当我们调用 useEffect 时,就是在告诉 React 在完成对 DOM 的更改后运行这个“副作用”函数。
副作用函数还可以通过返回一个函数来指定如何“清除”副作用:
useEffect(() => {
// 设置定时器
return () => {
// 清除定时器
};
});
跟 useState 一样,你可以在组件中多次使用 useEffect。
生命周期
挂载阶段
当组件实例被创建并插入 DOM 中时,其生命周期调用顺序如下:
constructor(): 在 React 组件挂载之前,会调用它的构造函数。getDerivedStateFromProps(): 在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。render(): render() 方法是 class 组件中唯一必须实现的方法。componentDidMount(): 在组件挂载后(插入 DOM 树中)立即调用。
更新阶段
当组件的 props 或 state 发生变化时会触发更新。组件更新的生命周期调用顺序如下:
getDerivedStateFromProps(): 在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。根据 shouldComponentUpdate() 的返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响。shouldComponentUpdate():当 props 或 state 发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用。render(): render() 方法是 class 组件中唯一必须实现的方法。getSnapshotBeforeUpdate(): 在最近一次渲染输出(提交到 DOM 节点)之前调用。componentDidUpdate(): 在更新后会被立即调用。
卸载阶段
当组件从 DOM 中移除时会调用如下方法:
componentWillUnmount(): 在组件卸载及销毁之前直接调用。
diff 算法
diff 操作本身是有性能损耗的,React 文档中提到,即使在最前沿的算法中,将前后两棵树完全比对的算法的复杂程度为O(n³),其中n是树中元素的数量。
为了降低算法复杂度,React 的 diff 设置了三个限制条件,以确保复杂度能降为O(n):
- 只对同级元素进行 diff 。如果一个
DOM 节点在前后两次更新中跨越了层级,那么 React 不会复用该节点。 - 只复用同类型的 DOM 节点。如果元素由
div变为p,React 会销毁div及其子孙节点,并新建p及其子孙节点。 - 通过元素的
key属性判断是否可以复用。