【react框架】在类式组件中,setState时尽量不要修改原数据的原因,还有不要直接修改state(知识相关shouldComponentUpdate、PureComponent)

前言

这是一个很重要的知识点,如果不加以注意,很有可能会出现bug,别问我为什么知道哈哈。

个人觉得这里是react设计的不太好的地方,不过应该也是有原因的,我们只需要记住setState时尽量不要修改原数据就好,下面来看看原因。

为什么setState时尽量不要修改原数据

首先,什么是修改原数据,如下:

let data = this.state.data
this.setState({data: data.push(1)}) // 这样修改了data原数据
this.setState({data: data.contact([1])}) // 这样就没有修改原数据,返回的是一个新的变量

两者使用的都能正常修改到数据,并且视图能正常更新,原因如下。

  • 只要是setState了(无论视图中是否使用到),父组件一定会重新执行render,然后底层经历diff算法后,重新渲染需要更新的部分视图。
  • 因为父组件走了render,所以子组件也更新了,走componentDidUpdate钩子,并且执行render(即使是毫无变化的子组件)。

小问题:不知道为什么我尝试的时候发现,在子组件中,render先执行,然后才执行componentDidUpdate。

既然两种方法都能正常更新视图,那为什么网上都说setState时尽量不要修改原数据?


分析

其实原因就是当使用了shouldComponentUpdate钩子函数时就会出现视图更新的问题了。

shouldComponentUpdate

react提供了shouldComponentUpdate这个钩子让我们去控制无论怎么setState,都执行render的行为。

shouldComponentUpdate这个函数我们不写底层是默认返回true的。和上面说的一样,无论如何,只要父组件传入的数据有变动,哪怕是重新赋值,子组件都会走更新的生命钩子并且执行render。

shouldComponentUpdate(nextProps, nextState) {
  return true
}

我们可以在这个函数中利用两个入参nextProps, nextState加上判断,满足什么条件返回true就执行render,满足什么条件返回false就不执行render。

例如,父组件通过新旧的state对比去看要不要执行render,子组件可以通过新旧的props对比去看要不要执行render。

所以,当一个子组件很复杂时,无意义的重新执行render再进行diff算法,开销太大,我们一般会在子组件中重新自定义这个函数:

shouldComponentUpdate(nextProps, nextState) {
  if (this.props.data === nextProps.data) { return false } // 如果是重新赋值的方式就不更新视图
  return true
}

问题来了,这和setState时尽量不要修改原数据有什么关系呢?

与setState的关系

我看网上说的大概意思是:

当我们在父组件setState选择了修改原数据方式,在父组件shouldComponentUpdate中,做了对比某个state新旧值,会发现新旧都是一样的,这时候自己写的代码就会返回false,不更新父子组件。

我们来写个例子看看,首先父组件:

export default class App extends Component {
  state = {
    a: [1],
  };
  shouldComponentUpdate(nextp, nexts) {
    console.log("shouldComponentUpdate", nexts, this.state);
    return true;
  }
  changeState = () => {
    let { a } = this.state;
    // this.setState({ a: a.concat([2]) });
    this.setState({ a: a.push(2) });
  };
  render() {
    let { a } = this.state;
    console.log('父组件走render');
    return (
      <div onClick={this.changeState}>
        {a}
        <Son a={a}></Son>
      </div>
    );
  }
}

然后子组件:

class Son extends Component {
  state = {};
  shouldComponentUpdate(nextp, nexts) {
    console.log("子组件shouldComponentUpdate", nextp);
    return true;
  }
  render() {
    console.log('子组件走render');
    return <div>我是子组件</div>;
  }
}

当我们使用不修改原数据的方式时,nexts传入的就是正常最新的修改值{a:[1,2]}

当我们使用修改原数据的方式时,视图同样会更新,且子组件同样会走shouldComponentUpdate和render。但是!shouldComponentUpdate的nexts传入的为{a:2},a直接变成2了。这就很难受了,如果我们在shouldComponentUpdate中写了判断就容易出错。

那为什么使用原数据修改的方式a会变成2呢?我发现:

let a = [1]; console.log(a.push(1)) // 控制台上打出来的就是2,其实返回的是数组的长度!

所以使用修改原数据的方式并不是这样子写的,“正确”写法应该是:

a.push(2)
this.setState({ a }); // 不能直接在这里push

这时候shouldComponentUpdate的nexts和this.state就会一样是最新的[1,2]了,那么我们在里面写判断的话,nexts.a和this.state.a就会是一样的,返回false,组件都不更新!

shouldComponentUpdate(nextp, nexts) {
  console.log("shouldComponentUpdate", nexts, this.state);
  if (nexts.a !== this.state.a) {
      return true
  }
  return false;
}

并且你就算不写判断,都返回true,父组件虽然会走render,但它的视图不会更新!!

实践出真知。这下都明白了。

虽然不用shouldComponentUpdate,修改了原数据的写法也无伤大雅,但是还是要严格要求自己,因为可变和不可变的写法积累也是一个重要基础。


不要直接修改state

超级重要!

如果想用state里的引用类型变量去做一些改变原值的处理,那么一定要进行深拷贝!

不好的例子:

let { userList } = this.state
// 类似改变原值的操作
userList[0].name = 'a' 
// ... 

PureComponent纯组件

如果每次都要在每个组件手动shouldComponentUpdate去优化更新机制太过麻烦,所以react提供了PureComponent:

import React, { PureComponent } from 'react' // 就是把PureComponent代替掉Component

export default class App extends PureComponent {
     ...
}

原理就是react在底层机制已经通过shouldComponentUpdate去帮我们处理好(比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false),子孙组件自动看需不需要更新。

注意点

第一:父组件更新state的时候,需要传入的是一个新的变量,因为react会对传入的变量与原来的state里的数据做比较,如果是同一个引用堆内存的地址,就不会更新所有组件(说白了也是不要修改原数据的方式去更新state):

state = { a: 1 }
...
fn = () => {
	state = this.state
	state.a = 2
	this.setState(state) // 不会更新render
}

fn = () => {
	state = this.state
	state.a = 2
	this.setState({...state}) // 才会更新render
}

第二:这个组件在底层做比较的时候,只会比较一维,多维的数据还是会更新的。

总结

我认为PureComponent组件只适合写小UI组件,且必须满足以下条件:

  • UI纯粹、数据简单、场景固定
  • 不再包含子组件

如果一上来就考虑做成PureComponent,日后拓展必然会换掉。


题外话

在函数式组件的写法中,如果还是采用修改原数据的方式去写,会偶然遇到视图不更新的问题。具体原因我还没去研究,所以还是那句话最好严格要求自己,使用一维拷贝或者深拷贝的方式去修改state。

peace~

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值