render阶段作用是什么?
首先来思考一个问题,组件在一次更新中,类组件执行 render ,执行函数组件 renderWithHooks ( renderWithHook 内部执行 React 函数组件本身),他们的作用是什么呢? 他们真实渲染了 DOM 了吗?显然不是,真实 DOM 是在 commit 阶段挂载的,之前章节打印过 render 后的内容。
那么render的作用是根据一次更新中产生的新状态值,通过 React.createElement ,替换成新的状态,得到新的 React element 对象,新的 element 对象上,保存了最新状态值。 createElement 会产生一个全新的props。到此 render 函数使命完成了。
接下来,React 会调和由 render 函数产生 chidlren,将子代 element 变成 fiber(这个过程如果存在 alternate,会复用 alternate 进行克隆,如果没有 alternate ,那么将创建一个),将 props 变成 pendingProps ,至此当前组件更新完毕。然后如果 children 是组件,会继续重复上一步,直到全部 fiber 调和完毕。完成 render 阶段。
React 几种控制 render 方法
React 提供了几种控制 render 的方式。我这里会介绍原理和使用。说到对render 的控制,究其本质,主要有以下两种方式:
第一种就是从父组件直接隔断子组件的渲染,经典的就是 memo,缓存 element 对象。 第二种就是组件从自身来控制是否 render ,比如:PureComponent ,shouldComponentUpdate 。
1.缓存React.element对象
function Children ({ number }) {
console.log('子组件渲染')
return <div>let us learn React! {number}</div>
}
class Index extends React.Component {
constructor (props) {
super(props)
this.state = {
numberA: 0,
numberB: 0
}
this.component = <Children number={this.state.numberA }/>
}
controllComponentRender = () => {
const { props } = this.component
if (props.number !== this.state.numberA) {
return this.component = React.cloneElement(this.component, {number: this.state.numberA})
}
return this.component
}
render () {
return <div>
{/* <Children number={this.state.numberA}/> */}
{this.controllComponentRender()}
<button onClick={() => this.setState({
numberA: this.state.numberA + 1
})}>改变numberA</button>
<button onClick={() => this.setState({
numberB: this.state.numberB + 1
})}>改变numberB</button>
</div>
}
}
2.缓存
PureComponent:浅比较props和state是否相等
class Children extends React.PureComponent {
state = {
name: 'alien',
age: 18,
obj: {
number: 1
}
}
changeObjNumber = () => {
const { obj } = this.state
obj.number++
this.setState({obj})
}
render () {
console.log('组件渲染')
return <div>
<div>组件本身改变state</div>
<button onClick={() => this.setState({name: 'alien'})}>state相同情况</button>
<button onClick={() => this.setState({age: this.state.age + 1})}>state不同情况</button>
<button onClick={this.changeObjNumber}>state为引用类型时候</button>
</div>
}
}
function Index () {
const [numberA, setNumberA] = React.useState(0)
const [numberB, setNumberB] = React.useState(0)
return <div>
<div>父组件改变props</div>
<button onClick={() => setNumberA(numberA + 1)}>改变numberA</button>
<button onClick={() => setNumberB(numberB + 1)}>改变numberB</button>
<Children number={numberA} />
</div>
}
- 对于 props ,PureComponent 会浅比较 props 是否发生改变,再决定是否渲染组件,所以只有点击 numberA 才会促使组件重新渲染。
- 对于 state ,如上也会浅比较处理,当上述触发 ‘ state 相同情况’ 按钮时,组件没有渲染。
- 浅比较只会比较基础数据类型,对于引用类型,比如 demo 中 state 的 obj ,单纯的改变 obj 下属性是不会促使组件更新的,因为浅比较两次 obj 还是指向同一个内存空间,想要解决这个问题也容易,浅拷贝就可以解决,将如上 changeObjNumber 这么修改。这样就是重新创建了一个 obj ,所以浅比较会不相等,组件就会更新了。
useMemo:
function Children ({ number }) {
console.log('子组件渲染')
return <div>let us learn React! {number}</div>
}
function Index () {
const [numberA, setNumberA] = React.useState(0)
const [numberB, setNumberB] = React.useState(0)
const Child = React.useMemo(() => <Children number={numberA}/>, [numberA])
return <div>
{Child}
<button onClick={() => setNumberA(numberA + 1)}>改变numberA</button>
<button onClick={() => setNumberB(numberB + 1)}>改变numberB</button>
</div>
}
useMemo 是一个用于优化性能的 React Hook,它的主要作用是避免在每次渲染时都进行复杂的计算和重新创建对象。通过记住上一次的计算结果,只有在依赖项发生变化时才重新计算,从而提高性能。
useMemo 接受两个参数: 一个函数,这个函数返回需要记住的值。 一个依赖项数组,当数组中的依赖项发生变化时,才会重新计算函数的返回值。
export default function (){
const callback = React.useCallback(function handerCallback(){},[])
return <Index callback={callback} />
}
useCallback 接受二个参数,第一个参数就是需要缓存的函数,第二个参数为deps, deps 中依赖项改变返回新的函数。如上处理之后,就能从根本上解决 PureComponent 失效问题。
useCallback 和 useMemo 有什么区别?
答:useCallback 第一个参数就是缓存的内容,useMemo 需要执行第一个函数,返回值为缓存的内容,比起 useCallback , useMemo 更像是缓存了一段逻辑,或者说执行这段逻辑获取的结果。那么对于缓存 element 用 useCallback 可以吗,答案是当然可以了。
useCallback 和 useMemo 都是 React 的 Hooks,用于优化性能,它们的主要区别在于用途和返回值:
用途:
useCallback 主要用于避免在每次渲染时都重新创建函数。它会在依赖项发生变化时才重新创建新的函数,从而提高性能。 useMemo 主要用于避免在每次渲染时都进行复杂的计算和重新创建对象。它会在依赖项发生变化时才重新计算函数的返回值,从而提高性能。
返回值:
useCallback 返回的是一个记住的函数。当依赖项发生变化时,它会返回一个新的函数。 useMemo 返回的是一个记住的计算结果。当依赖项发生变化时,它会返回重新计算的结果。
,useCallback 用于优化函数,而 useMemo 用于优化计算结果。在实际使用中,可以根据需要选择合适的 Hook 进行性能优化。 需要注意的是,虽然 useCallback 和 useMemo 都可以帮助我们优化性能,但并不是所有情况下都需要使用它们。在一些简单的计算、函数或不会频繁触发重新渲染的情况下,使用这两个 Hooks 反而可能带来额外的开销。因此,在使用 useCallback 和 useMemo 时,需要根据具体情况进行权衡
shouldComponentUpdate:
class Children extends React.Component {
state = {
stateNumA: 0,
stateNumB: 0
}
shouldComponentUpdate (newProp, newState, newContext) {
if (newProp.propsNumA !== this.props.propsNumA || newState.stateNumA !== this.state.stateNumA) {
return true
}
return false
}
render () {
console.log('组件渲染')
return <div>
<div>组件本身改变state</div>
<button onClick={() => this.setState({stateNumA: this.state.stateNumA + 1})}>改变state中numA</button>
<button onClick={() => this.setState({stateNumB: this.state.stateNumB + 1})}>改变state中numB</button>
</div>
}
}
function Index () {
const [numberA, setNumberA] = React.useState(0)
const [numberB, setNumberB] = React.useState(0)
return <div>
<div>父组件改变props</div>
<button onClick={() => setNumberA(numberA + 1)}>改变numberA</button>
<button onClick={() => setNumberB(numberB + 1)}>改变numberB</button>
<Children propsNumA={numberA} propsNumB={numberB}/>
</div>
}
shouldComponentUpdate 可以根据传入的新的 props 和 state ,或者 newContext 来确定是否更新组件,如上面例子🌰,只有当 props 中 propsNumA 属性和 state 中 stateNumA 改变的时候,组件才渲染。但是有一种情况就是如果子组件的 props 是引用数据类型,比如 object ,还是不能直观比较是否相等。那么如果想有对比新老属性相等,怎么对比呢,而且很多情况下,组件中数据可能来源于服务端交互,对于属性结构是未知的。
immutable.js 可以解决此问题,immutable.js 不可变的状态,对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象。鉴于这个功能,所以可以把需要对比的 props 或者 state 数据变成 Immutable 对象,通过对比 Immutable 是否相等,来证明状态是否改变,从而确定是否更新组件。
对于 shouldComponentUpdate 生命周期篇章和上面都有提及,它的执行是在 checkShouldComponentUpdate,会执行此生命周期。
React.memo
function TextMemo (props) {
console.log('子组件渲染')
return <div>hello, world</div>
}
const controllIsRender = (pre, next) => {
return (pre.number === next.number) || (pre.number !== next.number && next.number > 5)
}
const NewTextMemo = React.memo(TextMemo, controllIsRender)
class Index extends React.Component {
constructor (props) {
super(props)
this.state = {
number: 1,
num: 1
}
}
render () {
const { num, number } = this.state
return <div>
<div>
改变num: 当前值: {num}
<button onClick={() => this.setState({num: num+1})}>num++</button>
<button onClick={() => this.setState({num: num - 1})}>num--</button>
</div>
<div>
改变number: 当前值: {number}
<button onClick={() => this.setState({number: number+1})}>number++</button>
<button onClick={() => this.setState({number: number - 1})}>number--</button>
</div>
<NewTextMemo num={num} number={number} />
</div>
}
}
React.memo(Component,compare) React.memo 可作为一种容器化的控制渲染方案,可以对比 props 变化,来决定是否渲染组件,首先先来看一下 memo 的基本用法。React.memo 接受两个参数,第一个参数 Component 原始组件本身,第二个参数 compare 是一个函数,可以根据一次更新中 props 是否相同决定原始组件是否重新渲染。
memo的几个特点是:
React.memo: 第二个参数 返回 true 组件不渲染 , 返回 false 组件重新渲染。和 shouldComponentUpdate 相反,shouldComponentUpdate : 返回 true 组件渲染 , 返回 false 组件不渲染。 memo 当二个参数 compare 不存在时,会用浅比较原则处理 props ,相当于仅比较 props 版本的 pureComponent 。 memo 同样适合类组件和函数组件。
memo 主要逻辑是:
通过 memo 第二个参数,判断是否执行更新,如果没有那么第二个参数,那么以浅比较 props 为 diff 规则。如果相等,当前 fiber 完成工作,停止向下调和节点,所以被包裹的组件即将不更新。 memo 可以理解为包了一层的高阶组件,它的阻断更新机制,是通过控制下一级 children ,也就是 memo 包装的组件,是否继续调和渲染,来达到目的的。