2021-03-07 React原理解析04
理解协调
React中的数据结构
Flags
注:这里的flags都是二进制,这个和React中用到的位运算有关。首先我们要知道位运算只能用于整数,并且是直接对二进制位进行计算,直接处理每一个比特位,是非常底层的运算,运算速度极快;其次,一个节点可能有多个任务,所以要有组合运算,没有唯一性
ReactWorkTag
export const FunctionComponent = 0;
export const ClassComponent = 1;
export const IndeterminateComponent = 2;
export const HostRoot = 3;
...
...
Update & UpdateQueue
tag的标记不同类型,如执行forceUpdate的时候,tag值就是2
这里的payload是参数,比如setState更新时候,payload就是partialState,render的时候,payload就是第一个参数,即element
ExecutionContext
React执行栈中,所处于几种环境的值,所对应的全局变量是executionContext
React合成事件系统
React为了实现跨平台兼容性,对于事件处理有自己的一套代码
React中有自己的事件系统模式,即通常被称为React合成事件。之所以采用这种自己定义的合成事件,一方面是为了抹平差异性,使得React开发者不需要自己再去关注浏览器事件兼容性问题,另一方面是为了统一管理事件,提高性能,这主要提现在React内部实现事件委托,并且记录当前事件发生的状态上
事件委托,也就是我们通常提到的事件代理机制,这种机制不会把时间处理函数直接绑定在真实的节点上,而是把所有的事件绑定到结构的最外层,使用一个统一的事件监听和处理函数。当组件加载或卸载时,只是在这个统一的事件监听器上插入或删除一些对象;当事件放生时,首先被这个统一的事件监听器处理,然后在映射表里找到真正的事件处理函数并调用。这样做简化了事件处理和回收机制,效率也有很大提升
记录当前事件发生的状态,即记录事件执⾏的上下⽂,这便于React来处理不同事件的优先级,达到谁优先级⾼先处理谁的⽬的,这⾥也就实现了React的增量渲染思想,可以预防掉帧,同时达到⻚⾯更顺滑的⽬的,提升⽤户体验
setState
setState()
会对一个组件的state对象安排一次更新。当state改变了,该组件就会重新渲染。class组件的特点,就是拥有特殊状态并且可以通过setState更新状态并重新渲染视图,是React中最重要的api
1、setState异步
setState在原生事件和定时器setTimeout中是同步的,在合成事件和生命周期中是异步的
2、为什么React不同步更新this.state
在开始重新渲染之前,React会有意的进行“等待”,直到所有组件的事件处理函数内调用的setState()完成之后。这样可以通过避免不必要的重新渲染来提升性能
3、为什么setState是异步的
这里的异步指的是多个state会合成到一起进行批量更新
常见的组件优化技术
核心:只渲染需要被渲染的,只计算需要被计算的量
定制组件的shouldComponentUpdate钩子
import { Component } from "react";
class CommentListPage extends Component {
constructor(props) {
super(props);
this.state = {
commentList: []
};
}
componentDidMount() {
this.timer = setInterval(() => {
this.setState({
commentList: [
{
id: 0,
author: "小明",
body: "这是小明写的文章"
},
{
id: 1,
author: "小红",
body: "这是小红写的文章"
}
]
});
}, 1000);
}
componentWillUnmount() {
if (this.timer) {
clearInterval(this.timer);
}
}
render() {
// console.log("render");
const { commentList } = this.state;
return (
<div>
<h3>CommentListPage</h3>
{commentList.map(item => (
<Comment key={item.id} data={item} />
))}
</div>
);
}
}
class Comment extends Component {
shouldComponentUpdate(nextProps, nextState) {
const { author, body } = this.props.data;
const { author: newAuthor, body: newBody } = nextProps.data;
if (author === newAuthor && body === newBody) {
return false; // 如果不执行这里,将会多次render
}
return true;
}
render() {
console.log("commentList render");
const { author, body } = this.props.data;
return (
<div className="border">
<p>{author}</p>
<p>{body}</p>
</div>
);
}
}
export default CommentListPage;
PureComponent
定制了shouldComponentUpdate后的Component
import { PureComponent } from "react";
class PureComponentPage extends PureComponent {
constructor(props) {
super(props);
this.state = {
counter: 0
};
}
setCounter = () => {
this.setState({ counter: 100 });
};
render() {
const { counter } = this.state;
console.log("render");
return (
<div>
<h3>PureComponentPage</h3>
<div onClick={this.setCounter}>counter: {counter}</div>
</div>
);
}
}
export default PureComponentPage;
注意:缺点是必须使用Class组件,而且是浅比较
React.memo
React.memo
为高阶组件。它与React.PureComponent
非常相似。但它适用于函数组件,不适用于class组件
如果你的函数组件在给定相同props的情况下渲染相同的结果,那么你可以通过将其包装在React.memo中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味这在这种情况下,React将跳过渲染组件操作并直接复用最近一次渲染的结果
默认情况下其只会对复杂对象做浅层对比,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现
import React, { Component } from "react";
class ReactMemoPage extends Component {
constructor(props) {
super(props);
this.state = {
date: new Date(),
count: 0
};
}
render() {
const { count, date } = this.state;
console.log("render", count);
return (
<div>
<h3>ReactMemoPage</h3>
<button onClick={() => this.setState({ count: 1 })}>
click add {count}
</button>
<button onClick={() => this.setState({ date: new Date() })}>
click reset {date.toLocaleTimeString()}
</button>
<MemoCounter count={{ count }} />
</div>
);
}
}
const MemoCounter = React.memo(
props => {
console.log("MemoCounter");
return <div>MemoCounter-{props.count.count}</div>;
},
(prevProps, nextProps) => {
return prevProps.count.count === nextProps.count.count;
}
);
export default ReactMemoPage;
useMemo
把“创建”函数和依赖项数组作为参数传入useMemo
,它仅会在某个依赖项改变时才会重新计算memorized值。这种优化有助于避免在每次渲染时都进行高开销的计算
import { useMemo, useState } from "react";
function UseMemoPage(props) {
const [count, setCount] = useState(0);
const [value, setValue] = useState("");
// useMemo返回一个缓存值
const expensive = useMemo(() => {
console.log("compute");
let sum = 0;
for (let i = 0; i < count; i++) {
sum += i;
}
return sum;
}, [count]);
return (
<div>
<h3>UseMemoPage</h3>
<p>expensive: {expensive}</p>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>add</button>
<input value={value} onChange={e => setValue(e.target.value)} />
</div>
);
}
export default UseMemoPage;
useCallback
把内联回调函数及依赖项数组作为参数传入useCallback
,它将返回该回调函数的memorized版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如shouldComponentUpdate)的子组件时,它将非常有用
import React, { useState, useCallback, PureComponent } from "react";
function UseCallbackPage(props) {
const [count, setCount] = useState(0);
// useCallback返回一个缓存过的函数
const addClick = useCallback(() => {
let sum = 0;
for (let i = 0; i < count; i++) {
sum += i;
}
return sum;
}, [count]);
const [value, setValue] = useState("");
return (
<div>
<h3>UseCallbackPage</h3>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>add</button>
<input value={value} onChange={event => setValue(event.target.value)} />
<Child addClick={addClick} />
</div>
);
}
class Child extends PureComponent {
render() {
console.log("child render");
const { addClick } = this.props;
return (
<div>
<h3>Child</h3>
<button onClick={() => console.log(addClick())}>add</button>
</div>
);
}
}
export default UseCallbackPage;
useCallback(fn, deps)
相当于useMemo(()=>fn, deps)
注意:依赖项数组不会作为参数传给“创建”函数。虽然从概念上来说它表现为:所有“创建”函数中引用的值都应该出现在依赖项数组中。未来编译器会更加智能,届时自动创建数组将成为可能