故事的开始:
我的上一个项目尝试使用了react16.0进行开发,并全线使用hooks-api,开发时由于没有注意react组件渲染机制,导致项目的性能大大低于预期。开发一时爽,完成后才发现对于react的hooks只是机械性的使用,未能得其精髓,在又一次的react16.0的学习中,看到了对于React.memo这个方法,知道了这个API是对组件渲染优化至关重要的点。
情景再现:
请看以下代码
//react
const Child = (props) => {
console.log('child update')
return (
<div>
{props.text}
</div>
)
}
const App = () => {
const [text, setText] = useState('我是文本');
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>{count}</button>
<Child text={text}/>
</div>
)
}
为什么我每点击一次按钮,控制台就回打印出'child update'?
当页面初始化完成之后,在手动点击button按钮时,请问控制台输出什么??? 答案是:每当我点击一次button,count更改后,控制台会打印出 'child update',意味着child组件重新渲染了,那么为什么count的变动会影响child组件。如果是vue用户,可能不会有这样的疑惑,因为在vue下,这种情况必不可能发生,例如:
//child
<template>
<div>{{text}}</div>
</template>
export default {
props: {
text: {
type: String,
default: ''
}
},
mounted() {
console.log('child mounted')
},
updated() {
console.log('child update')
}
}
// parent
<template>
<div>
<button @click='countAdd'>
{{count}}
</button>
<Child :text='text'/>
</div>
</template>
export default {
data() {
return {
count: 0,
text: 'child'
}
},
methods: {
countAdd() {
this.count ++;
}
}
mounted() {
console.log('child mounted')
},
updated() {
console.log('child update')
}
}
对于vue来说,这很好解释,child依赖parent传入的text,parent内count的变更,而text没有变动,所以child组件无需更新。那么问题回到最初,在react内父组件的state变更,没有依赖变更数据的子组件为什么会重新渲染。
数据的单向流动
对于vue来说,因为使用的是数据劫持的方式,所以可以很方便的知道哪里的数据进行了更新,因为知道数据改变的具体位置,那么组件的重新渲染是可控的,即在框架内部,vue自己就已经优化了组件渲染的时机,防止单一数据变动而引起的大规模的组件渲染,造成不必要的性能浪费,但是 React是单向数据流,数据主要从父节点传递到子节点(通过props)。 如果顶层(父级)的某个props改变了,React会重渲染所有的子节点,但是这样就会造成比较多的性能浪费,在react 16以下 class组件中 我们可以使用shouldComponentDidMount或者PureComponent来控制组件的渲染,但是在16.0以上函数式组件中,我们应该如何去优化组件渲染呢,标题来了:React.memo
React.memo
该api使得组件仅在它的 props 发生改变的时候进行重新渲染。通常来说,在组件树中 React 组件,只要有变化就会
走一遍渲染流程。但是通过React.memo(),我们可以仅仅让某些组件进行渲染。
所以对于上面重复渲染子组件的方案,仅仅需要小小改造一下就行
//react
import {memo} from 'react';
const Child = memo((props) => {
console.log('child update')
return (
<div>
{props.text}
</div>
)
})
改造完成,多次点击button控制台已经不会再像之前一样多次打印'child update'了,到此本次react组件优化结束了。
后话:
其实以上的问题,解决起来是很快速的,百度一下,1分钟搞定,但是在问题之下,有更多需要我们去积极了解的内在因素,业务代码千篇一律,但是内在的编码思想确实我们需要去深刻思考。