一、类组件渲染过程
1.创建一个构造函数(类)
- 要求必须继承React.Component/PureComponent这个类
- 我们习惯于使用ES6中的class创建类「因为方便」
- 必须给当前类设置一个render的方法「放在其原型上」:在render方法中,返回需要渲染的视图
2.从调用类组件的内部原理
「new Vote({...})」开始,类组件内部发生的事情
2.1. 初始化属性 && 规则校验
先规则校验,校验完毕后,再处理属性的其他操作!!
- 方案一:
jsx
constructor(props) {
super(props); //会把传递进来的属性挂载到this实例上
console.log(this.props); //获取到传递的属性
}
- 方案二:
即便我们自己不再constructor中处理「或者constructor都没写」,在constructor处理完毕后,React内部也会把传递的props挂载到实例上;
所以在其他的函数中,只要保证this是实例,就可以基于this.props获取传递的属性!
同样this.props获取的属性对象也是被冻结的{只读的} Object.isFrozen(this.props)->true
2.2. 初始化状态
状态:后期修改状态,可以触发视图的更新
需要手动初始化,如果我们没有去做相关的处理,则默认会往实例上挂载一个state,初始值是null
- 手动处理:
jsx
state = {
...
};
- 修改状态,控制视图更新
this.state.xxx=xxx :这种操作仅仅是修改了状态值,但是无法让视图更新
想让视图更新,我们需要基于React.Component.prototype提供的方法操作
jsx
@1 this.setState(partialState) 既可以修改状态,也可以让视图更新 「推荐」
+ partialState:部分状态
this.setState({
xxx:xxx
});
@2 this.forceUpdate() 强制更新
2.3. 触发componentWillMount
周期函数(钩子函数):组件第一次渲染之前
钩子函数:在程序运行到某个阶段,我们可以基于提供一个处理函数,让开发者在这个阶段做一些自定义的事情
- 此周期函数,目前是不安全的「虽然可以用,但是未来可能要被移除了,所以不建议使用」
- 控制会抛出黄色警告「为了不抛出警告,我们可以暂时用 UNSAFE_componentWillMount」
- 如果开启了React.StrictMode「React的严格模式」,则我们使用 UNSAFE_componentWillMount 这样的周期函数,控制台会直接抛出红色警告错误!!
React.StrictMode VS "use strict"
- "use strict":JS的严格模式
- React.StrictMode:React的严格模式,它会去检查React中一些不规范的语法、或者是一些不建议使用的API等!!
2.4. 触发render周期函数:渲染
2.5. 触发 componentDidMount
周期函数:第一次渲染完毕
- 已经把virtualDOM变为真实DOM了「所以我们可以获取真实DOM了」
- ...
二、组件更新的过程--第一种
组件内部的状态被修改,组件会更新
1. 触发 shouldComponentUpdate 周期函数:是否允许更新
jsx
shouldComponentUpdate(nextProps, nextState) {
// nextState:存储要修改的最新状态
// this.state:存储的还是修改前的状态「此时状态还没有改变」
console.log(this.state, nextState);
// 此周期函数需要返回true/false
// 返回true:允许更新,会继续执行下一个操作
// 返回false:不允许更新,接下来啥都不处理
return true;
}
2. 触发 componentWillUpdate 周期函数:更新之前
- 此周期函数也是不安全的
- 在这个阶段,状态/属性还没有被修改
3. 修改状态值/属性值「让this.state.xxx改为最新的值」
4. 触发 render 周期函数:组件更新
- 按照最新的状态/属性,把返回的JSX编译为virtualDOM
- 和上一次渲染出来的virtualDOM进行对比「DOM-DIFF」
- 把差异的部分进行渲染「渲染为真实的DOM」
5. 触发 componentDidUpdate 周期函数:组件更新完毕
特殊说明:如果我们是基于 this.forceUpdate() 强制更新视图,会跳过 shouldComponentUpdate 周期函数的校验,直接从 WillUpdate 开始进行更新「也就是:视图一定会触发更新」!
三、组件更新的逻辑--第二种
父组件更新,触发的子组件更新
1. 触发 componentWillReceiveProps 周期函数:接收最新属性之前
- 周期函数是不安全的
jsx
UNSAFE_componentWillReceiveProps(nextProps) {
// this.props:存储之前的属性
// nextProps:传递进来的最新属性值
console.log('componentWillReceiveProps:', this.props, nextProps);
}
2. 触发 shouldComponentUpdate 周期函数
3. ......
四、组件卸载的逻辑
-
触发 componentWillUnmount 周期函数:组件销毁之前
-
销毁
-
父子组件嵌套,处理机制上遵循深度优先原则:
父组件在操作中,遇到子组件,一定是把子组件处理完,父组件才能继续处理
- 父组件第一次渲染 父 willMount -> 父 render「子 willMount -> 子 render -> 子didMount」 -> 父didMount
- 父组件更新: 父 shouldUpdate -> 父willUpdate -> 父 render 「子willReceiveProps -> 子 shouldUpdate -> 子willUpdate -> 子 render -> 子 didUpdate」-> 父 didUpdate
- 父组件销毁: 父 willUnmount -> 处理中「子willUnmount -> 子销毁」-> 父销毁
- 生命周期总结:
- 初次渲染:componentWillMount > render > componentDidMount
- 组件自身状态变化的更新:shouldComponentUpdate > componentWillUpdate > render > componentDidUpdate
- 父组触发子组件更新:componentWillReciveProps > shildComponentUpdate > componentWillUpdate > render > componentDidMount
- 不安全的生命周期:3个带will的:componentWillMount componentWillUpdate componentWillReciveProps
五、特性和原理
1.setState
用法:
jsx
this.setState([partialState],[callback])
partialState:支持部分状态更改
jsx
this.setState({
x:100 //不论总共有多少状态,我们只修改了x,其余的状态不动
});
callback:在状态更改/视图更新完毕后触发执行「也可以说只要执行了setState,callback一定会执行」
- 发生在componentDidUpdate周期函数之后「DidUpdate会在任何状态更改后都触发执行;而回调函数方式,可以在指定状态更新后处理一些事情;」
- 特殊:即便我们基于shouldComponentUpdate阻止了状态/视图的更新,DidUpdate周期函数肯定不会执行了,但是我们设置的这个callback回调函数依然会被触发执行!!
- 类似于Vue框架中的$nextTick!!
在React18中: setState操作都是异步的「不论是在哪执行,例如:合成事件、周期函数、定时器...」
- 实现状态的批处理「统一处理」
- 有效减少更新次数,降低性能消耗
- 有效管理代码执行的逻辑顺序
- ...
原理:利用了更新队列「updater」机制来处理的
- 在当前相同的时间段内「浏览器此时可以处理的事情中」,遇到setState会立即放入到更新队列中!
- 此时状态/视图还未更新
- 当所有的代码操作结束,会"刷新队列"「通知更新队列中的任务执行」:把所有放入的setState合并在一起执行,只触发一次视图更新「批处理操作」
React16中: 在同步代码块中是异步的,而在异步代码块中则是同步的
- 如果在合成事件「jsx元素中基于onXxx绑定的事件」、周期函数中,setState的操作是异步的!!
- 但是如果setState出现在其他异步操作中「例如:定时器、手动获取DOM元素做的事件绑定等」,它将变为同步的操作「立即更新状态和让视图渲染」!!
原理:所有setState都默认在下一个微任务队列中执行,如果你让他本身就在异步队列中执行,那么它会跳过默认的任务队列直接执行。
2.PureComponent和Component
- Component就是正常写法,没有其他特性
- PureComponent会自动添加一个shouldComponentUpdate周期函数,并且在该函数中,对新老状态做了一个钱比较,如果状态值一样,则跳过更新!
- 在pureComponent中你如果还是想更新,可以调用forceUppdate强制更新
- 你也可以自己实现一个深度比较的方法,在component的shuoldComponentUpdate中自己进行判断
jsx
// 检测是否为对象
const isObject = function isObject(obj) {
return obj !== null && /^(object|function)$/.test(typeof obj);
};
// 对象浅比较的方法
const shallowEqual = function shallowEqual(objA, objB) {
if (!isObject(objA) || !isObject(objB)) return false;
if (objA === objB) return true;
// 先比较成员的数量
let keysA = Reflect.ownKeys(objA),
keysB = Reflect.ownKeys(objB);
if (keysA.length !== keysB.length) return false;
// 数量一致,再逐一比较内部的成员「只比较第一级:浅比较」
for (let i = 0; i < keysA.length; i++) {
let key = keysA[i];
// 如果一个对象中有这个成员,一个对象中没有;或者,都有这个成员,但是成员值不一样;都应该被判定为不相同!!
if (!objB.hasOwnProperty(key) || !Object.is(objA[key], objB[key])) {
return false;
}
}
// 以上都处理完,发现没有不相同的成员,则认为两个对象是相等的
return true;
};
shouldComponentUpdate(nextProps, nextState) {
let { props, state } = this;
return !shallowEqual(props, nextProps) || !shallowEqual(state, nextState);
}
3.ref
1.受控组件和非受控组件
- 受控组件:基于修改数据/状态,让视图更新,达到需要的效果 「推荐」
- 非受控组件:基于ref获取DOM元素,我们操作DOM元素,来实现需求和效果「偶尔」
2.基于ref获取DOM元素的语法
- 给需要获取的元素设置ref='xxx',后期基于this.refs.xxx去获取相应的DOM元素
jsx
// 「不推荐使用:在React.StrictMode模式下会报错」
<h2 ref="titleBox">...</h2>
获取:
jsx
this.refs.titleBox
- 把ref属性值设置为一个函数
jsx
ref={x=>this.xxx=x}
- x是函数的形参:存储的就是当前DOM元素
- 然后我们获取的DOM元素"x"直接挂在到实例的某个属性上(例如:box2)
获取:
jsx
this.xxx
- 基于React.createRef()方法创建一个REF对象
jsx
this.xxx=React.createRef(); //=> this.xxx={current:null}
ref={REF对象(this.xxx)}
获取:
jsx
this.xxx.current
3.原理:在render渲染的时候,会获取virtualDOM的ref属性
- 如果属性值是一个字符串,则会给this.refs增加这样的一个成员,成员值就是当前的DOM元素
- 如果属性值是一个函数,则会把函数执行,把当前DOM元素传递给这个函数「x->DOM元素」,而在函数执行的内部,我们一般都会把DOM元素直接挂在到实例的某个属性上
- 如果属性值是一个REF对象,则会把DOM元素赋值给对象的current属性
4.其他
获取函数组组件的ref 需要结合 forwardRef + useImperativeHandle