react爬坑之PureComponent下setstate不触发render中状态变化的问题与相关知识。

本文讨论了在React中PureComponent下setState不触发render的情况,分析了浅比较与深复制的原理,以及如何通过解构赋值、JSON深拷贝等方式解决这个问题。同时,解释了PureComponent与Component在shouldComponentUpdate上的区别,并建议在性能优化时谨慎使用PureComponent。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

react爬坑之PureComponent下setstate不触发render中状态变化的问题。

在学习react时出现情况: 明明改变了值, 但是就是不触发render。

下面贴代码

import React, { PureComponent } from 'react';
import CommentInput from '@/components/CommentInput';
import CommentList  from '@/components/CommentList';
import styles from './CommentApp.less';

class CommentApp extends PureComponent {
  
  constructor () {
    super();
    this.state = {
      comments:[]
    }
  }

  componentWillMount () {
    this._loadComments()
  }

  _loadComments () {
    let comments = localStorage.getItem('comments');
    if(comments){
      comments = JSON.parse(comments)
      this.setState({comments:comments})
    }
  }

  _saveComments (comments) {
    localStorage.setItem('comments',JSON.stringify(comments));
  }


  handleSubmitComment = (comment) =>{
   
    const {comments} = this.state;

    if(!comment) return;
    if(!comment.username) return alert('请输入用户名');
    if(!comment.content) return  alert('请输入评论内容');

    let temp=[...comments,comment];
    this.setState({ comments:temp,});
    this._saveComments(temp);

  };

  handleDeleteComment = (index) => {
    //console.log("delete");
    const tmp = this.state.comments ;
    tmp.splice(index,1) ;
    console.log("comments:",tmp);
    this.setState({
      //comments:[...tmp]
      comments:tmp
    });
    this._saveComments(tmp);
  }


  render() {

    const {comments} = this.state;
    console.log("test:",comments);

    return (
      <div className={styles.wrapper}>
         <CommentInput 
            onSubmit={this.handleSubmitComment}
         />
         <CommentList 
            comments={comments}  
            onDeleteComment={this.handleDeleteComment}         
         />
      </div>
    )
  }
}

export default CommentApp;

handleDeleteComment这个函数的目的是想通过点击删除后 页面会自动显示删除后的列表。在打印tmp发现值是已经打印出来的 但是render中的console.log并没有触发。把

comments:tmp

改成

comments:[...tmp]

后问题解决。

但是用Component代替PureComponent 时,
使用 comments:tmp这种写法又是ok的。所以这里涉及的知识点应该是Component与PureComponent的不同点 以及 深复制与浅复制的一些基础知识点。

  • es6解构赋值的相关知识
    • 对象的解构赋值

对象的解构赋值用于从一个对象取值,相当于将所有可遍历的、但尚未被读取的属性,分配到指定的对象上面。所有的键和它们的值,都会拷贝到新对象上面。
解构赋值的拷贝是浅拷贝,即如果一个键的值是复合类型的值(数组、对象、函数)、那么解构赋值拷贝的是这个值的引用,而不是这个值的副本。

类似 const { id, value } = this.state 其实就是一次浅复制, 我们通常用于在渲染的时候提供一些变化的值,而不需要我们在render中对某个变量进行其他操作。所以在明确只是浅复制的情况下,尽量使用const定义变量,因为const定义的变量不能赋值或更改,这样就避免了后面不小心改变了该变量而引起的问题了。浅复制的好处就是可以有效的节约内存地址,避免不必要的内存浪费。

在ES6中可以利用 … 对对象解构并且对对象中需要深复制的变量进行拷贝。

let obj = {a:1,b:2};

let newObj = {...obj,a:2};//对a深复制
newObj.b = 3;//对b浅复制

console.log(obj);
//{a:1,b:3}
console.log(newObj);
//{a:2,b:3}

在reducer中经常会用到这种方式来更新我们的state,从而达到重新渲染的目的。改变state需要用到 setState() 方法,setState() 方法属于深复制

this.state = {
    value: { a: 1 }
}
const { value } = this.state;//浅复制
value.a = 2;
console.log(this.state.value.a);//输出2,但dom不更新
this.setState({ value });//dom更新


这里 value.a = 2 虽然已经改变了state中value的值,但由于是浅复制,新旧value指向的是同一块内存地址,组件更新时(state,props改变)默认只比较新旧对象的内存地址是否一致,如果一致则不更新。同理,如果在reducer中,直接对当前的state进行修改并返回props,相应的调用该props的组件不会更新渲染。

利用 JSON 深拷贝
JSON.parse(JSON.stringify(obj));

  • Purecomponent
  • PureComponent真正起作用的,只是在一些纯展示组件上

参考文章:https://juejin.im/entry/5934c9bc570c35005b556e1a
https://segmentfault.com/a/1190000014979065

当组件更新时,如果组件的 props 和 state 都没发生改变,render 方法就不会触发,省去 Virtual DOM 的生成和比对过程,达到提升性能的目的。

Component和PureComponent有一个不同点
除了为你提供了一个具有浅比较的shouldComponentUpdate方法,PureComponent和Component基本上完全相同。当props或者state改变时,PureComponent将对props和state进行浅比较。另一方面,Component不会比较当前和下个状态的props和state。因此,每当shouldComponentUpdate被调用时,组件默认的会重新渲染。

在React Component的生命周期中,有一个shouldComponentUpdate方法。这个方法默认返回值是true。

这意味着就算没有改变组件的props或者state,也会导致组件的重绘。这就经常导致组件因为不相关数据的改变导致重绘。

React创建了PureComponent组件创建了默认的shouldComponentUpdate行为。这个默认的shouldComponentUpdate行为会一一比较props和state中所有的属性,只有当其中任意一项发生改变是,才会进行重绘。

需要注意的是,PureComponent使用浅比较判断组件是否需要重绘

因此,下面对数据的修改并不会导致重绘(假设Table也是PureComponent)

  options.push(new Option())
  options.splice(0, 1)
  options[i].name = "Hello"

这些例子都是在原对象上进行修改,由于浅比较是比较指针的异同,所以会认为不需要进行重绘。

为了避免出现这些问题,推荐使用immutable.js。immutable.js会在每次对原对象进行添加,删除,修改使返回新的对象实例。任何对数据的修改都会导致数据指针的变化。

  • 总结:在对性能要求没有非常强烈的情况下 可以只用Component就可以了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值