大家在用react的时候,应该都知道react的state更新会由父到子逐级更新的事情,那么就会有这样一个问题,当要渲染的页面元素较多,极端情况下,特别是大数据展示的时候,由顶层一点点逐级渲染,性能消耗太大。打个具体点的比方,有A,B,C三个组件,A是B,C的父组件,当应用场景是,B的某个动作需要C来相应的时候,传统的做法如下。
class A extends React.Component {
constructor(props) {
super(props)
this.state = {
X: "",
}
}
onChangeX=(v)=>{
this.setState({X:v})
}
render() {
return <div>
<B onChangeX={this.onChangeX}/>
<C X={this.state.X}/>
</div>
}
}
class B extends React.Component {
render() {
return <div onClick={this.props.onChangeX}>
{sample}
</div>
}
}
class C extends React.Component {
render() {
return <div>
{this.props.X}
</div>
}
}
在这种代码的实现下,由B触发的事件必须经过父组件A所缓存的状态才能更新到C,如果A本身Render的性能消耗大的话,比如A本身有很多的逻辑计算 或者A下面还有其它组件D,E,F,G,画面渲染就非常耗时。那么有没有什么办法让B触发,绕过父组件A只更新目标C呢。
本人这里有两个方案可供选择。
第一,走redux
让触发点B不直接调用A的内部方法,而是发送redux更新用的Action。同时在被渲染的C组件外层包裹一个直接接收redux数据的container,数据由redux传给C。关于redux和container就暂时不写入代码了(偷个懒)
class A extends React.Component {
constructor(props) {
super(props)
this.state = {
}
}
render() {
return <div>
<B/>
<C/>
</div>
}
}
class B extends React.Component {
render() {
return <div onClick={this.props.onChangeXAction}>
{sample}
</div>
}
}
class C extends React.Component {
render() {
return <div>
{this.props.X}
</div>
}
}
这个方法的好处是,条理清晰,代码好读。坏处是副作用太大,首先是当组件存在于一个比较大的应用的时候,redux的负担过重,影响反应时间。其次是要想达到不触发父组件A的目的,A本身就不能跟redux关联,否则,redux的状态动了,在没有特殊控制的情况下,A也会被触发更新。
第二个方法,把被渲染组件C的this传给A,然后缓存下来。当被触发的时候调用C自身的setState方法更新。代码如下:
class A extends React.Component {
constructor(props) {
super(props)
this.state = {
// 组件C的对象缓存
cInst: "",
}
}
setCInst=(inst)=>{
// 给缓存赋值
this.setState({cInst:inst})
}
onChangeX=(v)=>{
//由C自身对象的setState方法进行render
this.cInst.setState({X:v})
}
render() {
return <div>
<B onChangeX={this.onChangeX}/>
<C setCInst={this.setCInst}/>
</div>
}
}
class B extends React.Component {
render() {
return <div onClick={this.props.onChangeX}>
{sample}
</div>
}
}
class C extends React.Component {
constructor(props) {
super(props)
this.state = {
X: "",
}
}
componentDidMount() {
this.props.setCInst(this)
}
render() {
return <div>
{this.state.X}
</div>
}
}
这样一来,当B开始触发的时候,因为没有调用A的setstate方法,A就不会重新渲染,从而达到提高性能的目的。当然,这种方法也有缺点,C组件的缓存是在componentDidMount生命周期上挂载的,那么在写父组件的时候就要特别小心。比如本人在另一篇文章中【getDerivedStateFromProps 如何区分状态更新来源】
闫松:react -- getDerivedStateFromProps 如何区分状态更新来源zhuanlan.zhihu.com之中写到的方法,用随机标志位来判断区分跟新元,就不适用上面的代码。原因是,当给子组件C加上key=随机标志位 之后。每次A在渲染的时候,都会生成新的key。当新key生成的时候,react认为以前的子组件已经被抛弃了。会生成新的子组件,这时候新的子组件会重新执行componentDidMount生命周期,而建立缓存的时候,父组件又会重新render。这样就会出现死循环。所以,当用以上方法时,切记不要加key或者加固定key,以免出现问题。