【React】PureComponent 和 Component 的区别

前言

在 React 中,PureComponent 和 Component 都是用于创建组件的基类,但它们有一个主要的区别:PureComponent 会给类组件默认加一个shouldComponentUpdate周期函数。在此周期函数中,它对props 和 state (新老的属性/状态)会做一个浅比较只比较第一层状态主要指的是内存地址是否发生变化。如果经过浅比较,发现属性和状态并没有变化,则返回fasle,也就是不继续更新组件,有变化才会去更新。

一、shouldComponentUpdate 使用

 shouldComponentUpdate(nextProps, nextState) {
        let { props, state } = this;
        // props/state:修改之前的属性状态
        // nextProps/nextState:将要修改的属性状态
        return !shallowEqual(props, nextProps) || !shallowEqual(state, nextState);
        // return true 说明需要更新,false 说明不需要更新
    } 

二、浅比较

在这里插入图片描述
上图是一个浅比较的逻辑。

这里我们实现一个浅比较的逻辑,主要是比较props
state。首先需要一个判断是否为对象的方法。

  • 判断传过来的参数不为null,且参数类型为object或者function
const isObject = function isObject(obj){
     return obj !== null && /^(object|function)$/.test(typeof obj);
}

比较两个对象的属性和状态

  • 判断传过的对象是否为对象
  • 判断对象是否相等
  • 接下来就开始判断两个对象的key、value
    • 判断两个对象的长度是否相等(通过Reflect.ownkeys(obj)获取key)
    • 循环其中一个对象的key集合,判断该对象的key是否在另一个对象里,如果在,则判断两个对象相同keyvalue 值是否相等

// 对象浅比较的方法
const shallowEqual = function shallowEqual(objA, objB) {
    if (!isObject(objA) || !isObject(objB)) return false;
    if (objA === objB) return true;
    // 先比较成员的数量
    let keysA = Reflect.ownKeys(objA),
        keysB = Reflect.ownKeys(objB);
    if (keysA.length !== keysB.length) return false;
    // 数量一致,再逐一比较内部的成员「只比较第一级:浅比较」
    for (let i = 0; i < keysA.length; i++) {
        let key = keysA[i];
        // 如果一个对象中有这个成员,一个对象中没有;或者,都有这个成员,但是成员值不一样;都应该被判定为不相同!!
        if (!objB.hasOwnProperty(key) || !Object.is(objA[key], objB[key])) {
            return false;
        }
    }
    // 以上都处理完,发现没有不相同的成员,则认为两个对象是相等的
    return true;
};

三、浅比较组件内使用

在使用React.PureComponent时候,组件内会默认加一个shouldComponentUpdate周期函数实现浅比较的功能,但是使用React.Component 要想使用shouldComponentUpdate周期函数的功能,就需要手动添加shouldComponentUpdate,且手动判断属性和状态是否改变。

class Demo extends React.Component {
    state = {
        arr: [10, 20, 30] //0x001
    };
    render() {
        let { arr } = this.state; //arr->0x001
        return <div>
            {arr.map((item, index) => {
                return <span key={index} style={{
                    display: 'inline-block',
                    width: 100,
                    height: 100,
                    background: 'pink',
                    marginRight: 10
                }}>
                    {item}
                </span>;
            })}

            <br />

            <button onClick={() => {
                arr.push(40); //给0x001堆中新增一个40
                // this.setState({ arr }); //最新修改的转态地址,还是0x001「状态地址没有改」 
                // console.log(this.state.arr); //[10,20,30,40]
                /* 
                // 无法更新的
                */

                // this.forceUpdate(); //跳过默认加的shouldComponentUpdate,直接更新
                this.setState({
                    arr: [...arr] //我们是让arr状态值改为一个新的数组「堆地址」
                })
            }}>新增SPAN</button>
        </div >;
    }

    shouldComponentUpdate(nextProps, nextState) {
        let { props, state } = this;
        return !shallowEqual(props, nextProps) || !shallowEqual(state, nextState);
    }
}

修改arr的值,并使用setState修改arr的状态,发现页面的状态并没有变化,这是因为,状态变了,但是arr还是之前的状态地址。

  <button onClick={() => {
                arr.push(40); //给0x001堆中新增一个40
                this.setState({ arr }); //最新修改的转态地址,还是0x001「状态地址没有改」 
  }}>新增SPAN</button>

此时想要页面状态改变则有两种方式:

(1)方式一:使用this.forceUpdate(),直接跳过shouldComponentUpdate周期,所以页面一定会更改状态并渲染


            <button onClick={() => {
               arr.push(40); //给0x001堆中新增一个40
               this.setState({ arr }); //最新修改的转态地址,还是0x001「状态地址没有改」 
               this.forceUpdate(); //跳过默认加的shouldComponentUpdate,直接更新
            }}>新增SPAN</button>

(2)方式二:修改arr的状态地址

使用this.setState({ arr: [...arr] }),此时arr的引用地址就不是之前的引用地址,是一个全新的数组,则objA === objB就为false

       <button onClick={() => {
              arr.push(40); //给0x001堆中新增一个40
              this.setState({ arr }); //最新修改的转态地址,还是0x001「状态地址没有改」 
              this.setState({
                   arr: [...arr] //我们是让arr状态值改为一个新的数组「堆地址」
                })
        }}>新增SPAN</button>

四、拓展

1、为什么使用Reflect.ownKeys(obj)获取对象的属性,而不是用Object.keys()?

特性Object.keys()Reflect.ownKeys()
返回的键类型仅普通属性的键(字符串)普通属性键 + 符号属性键(字符串和符号)
返回的属性类型仅可枚举属性包含所有自有属性(可枚举与不可枚举属性)
是否支持符号属性
适用场景处理普通属性,只关心普通属性或只关心可枚举属性全面访问对象所有自有属性(包括符号属性和不可枚举属性),用于更底层的操作或元编程场景

Reflect.ownKeys(obj) 是一种比 Object.keys(obj) 更通用的方式来获取对象的所有键名,包括普通属性和Symbol 属性。

Object.keys(obj) 只能返回对象的普通属性(即非符号属性)。它会遍历对象的自有可枚举属性,并返回一个数组。不可枚举属性(比如通过 Object.defineProperty 设置为不可枚举的属性)和符号属性都不会被返回。

示例:

const obj = {
  normalKey: 'value',
  [Symbol('symbolKey')]: 'symbolValue'
};

console.log(Object.keys(obj));  // 输出: ["normalKey"]

在上述代码中,Object.keys(obj) 只返回了 normalKey,没有返回符号属性。

Reflect.ownKeys(obj) 是一个更通用的方式,它会返回对象的所有自有键名,无论是普通属性还是符号属性。返回的结果是一个包含字符串和符号的数组。

示例:

const obj = {
  normalKey: 'value',
  [Symbol('symbolKey')]: 'symbolValue'
};

console.log(Reflect.ownKeys(obj));  // 输出: ["normalKey", Symbol(symbolKey)]

在这个示例中,Reflect.ownKeys(obj) 返回了 normalKey 和符号属性 Symbol(symbolKey),这使得它比 Object.keys(obj) 更为强大和灵活。

示例:结合 Reflect.ownKeys() 使用:

const obj = {
  normalKey: 'value'
};

Object.defineProperty(obj, 'hiddenKey', {
  value: 'hiddenValue',
  enumerable: false
});

const sym = Symbol('symbolKey');
obj[sym] = 'symbolValue';

console.log(Object.keys(obj));  // 输出: ["normalKey"]
console.log(Reflect.ownKeys(obj));  // 输出: ["normalKey", "hiddenKey", Symbol(symbolKey)]

在这个示例中,Object.keys(obj) 只返回了 normalKey,而 Reflect.ownKeys(obj) 返回了所有自有键,包括不可枚举的 hiddenKey 和符号属性 Symbol(symbolKey)

2、为什么使用hasOwnProperty()获取对象的属性,而不是用in?

objB.hasOwnProperty(key) 是 JavaScript 中用于判断对象 objB 是否具有某个自有属性 key 的方法。

hasOwnProperty()Object 的一个原型方法,属于所有对象实例。它用于检查给定的属性是否是对象自身的直接属性,而不是从原型链继承来的属性。

语法

obj.hasOwnProperty(key)

示例 1:检查自有属性

const objB = {
  name: 'Alice',
  age: 25
};

console.log(objB.hasOwnProperty('name'));  // 输出: true
console.log(objB.hasOwnProperty('age'));   // 输出: true
console.log(objB.hasOwnProperty('gender')); // 输出: false

在这个例子中,objBnameage 这两个自有属性,因此 hasOwnProperty('name')hasOwnProperty('age') 返回 true。而 gender 并没有在 objB 上定义,所以返回 false

示例 2:检查继承的属性

const person = {
  name: 'Alice'
};

const employee = Object.create(person);
employee.age = 25;

console.log(employee.hasOwnProperty('age'));   // 输出: true
console.log(employee.hasOwnProperty('name'));  // 输出: false

employee 对象通过 Object.create(person) 创建,继承了 person 的属性。因此,employee.hasOwnProperty('name') 返回 false,因为 name 是从原型链继承来的,而 ageemployee 自己定义的属性,所以返回 true

in 操作符的区别

in 操作符会检查对象及其原型链上是否存在指定的属性,而 hasOwnProperty() 只检查对象自身的属性,不会查找原型链上的属性。

示例 3:区别

const person = {
  name: 'Alice'
};

const employee = Object.create(person);
employee.age = 25;

console.log('age' in employee);           // 输出: true
console.log('name' in employee);          // 输出: true
console.log(employee.hasOwnProperty('age')); // 输出: true
console.log(employee.hasOwnProperty('name')); // 输出: false
  • 'age' in employee 会返回 true,因为 employee 对象自身有 age 属性。
  • 'name' in employee 会返回 true,因为 name 是从原型 person 继承的。
  • employee.hasOwnProperty('age') 返回 true,因为 ageemployee 自有的属性。
  • employee.hasOwnProperty('name') 返回 false,因为 name 是从原型链继承来的。

使用 hasOwnProperty 时的注意点

  • 性能hasOwnProperty() 只检查自有属性,性能上优于检查属性是否存在于对象及其原型链中(例如,使用 in 操作符或 for...in 循环)。
  • 避免属性名冲突:如果对象本身有一个名为 hasOwnProperty 的属性,可能会导致调用方法时发生冲突。因此,使用 hasOwnProperty 时可以通过 Object.prototype.hasOwnProperty.call() 来确保方法的正确调用:

示例 4:避免属性冲突

const objB = {
  hasOwnProperty: 'some value',
  name: 'Alice'
};

console.log(objB.hasOwnProperty('name'));  // 输出: TypeError: objB.hasOwnProperty is not a function

// 使用 Object.prototype.hasOwnProperty 来避免冲突
console.log(Object.prototype.hasOwnProperty.call(objB, 'name'));  // 输出: true

3、为什么使用Object.is(objA,objB)

Object.is(objA[key], objB[key]) 是 JavaScript 中用于比较两个值是否严格相等的方法,类似于 ===(严格相等操作符),但在某些特殊情况下行为有所不同。Object.is 方法比较的是两个值是否相同,返回 true 表示相等,返回 false 表示不相等。

Object.is=== 在大多数情况下行为相同,但是它们有两个显著的不同点:

  1. NaN:在 === 中,NaN 不等于 NaN,但是在 Object.is 中,NaN 等于 NaN
  2. +0-0:在 === 中,+0-0 被认为是相等的,但是在 Object.is 中,+0-0 被认为是不同的。

示例 1:NaN 比较

console.log(Object.is(NaN, NaN));  // 输出: true
console.log(NaN === NaN);          // 输出: false

在这个例子中,Object.is(NaN, NaN) 返回 true,而 NaN === NaN 返回 false。这是 Object.is=== 的区别之一。

示例 2:+0-0 比较

console.log(Object.is(+0, -0));  // 输出: false
console.log(+0 === -0);          // 输出: true

在这个例子中,Object.is(+0, -0) 返回 false,而 +0 === -0 返回 true。这是由于 Object.is 会区分 +0-0

使用场景

Object.is 适用于需要精确比较两个值是否完全相同,特别是在以下情况下:

  • 比较 NaN 是否相等。
  • 比较 +0-0 是否相等。
  • 比较对象或数组的引用是否相同。

示例 3:在比较对象属性时使用 Object.is

const objA = { key: 42 };
const objB = { key: 42 };

console.log(Object.is(objA.key, objB.key));  // 输出: true,因为值相等

示例 4:比较引用类型(对象、数组)

const arrA = [1, 2, 3];
const arrB = [1, 2, 3];

console.log(Object.is(arrA, arrB));  // 输出: false,因为它们是不同的引用

const arrC = arrA;
console.log(Object.is(arrA, arrC));  // 输出: true,因为它们引用的是同一个数组

四、使用===判断对象是否相等

if (objA === objB) return true; 的含义是判断 objAobjB 是否是相等的,并在它们相等时返回 true。这里使用的是严格相等运算符 ===

  1. === 运算符:

    • 严格相等=== 是严格相等运算符,它会同时比较值和类型。如果两个操作数的类型不同,返回 false
    • 对于引用类型(例如对象、数组、函数等),=== 比较的是内存地址,即两个变量是否引用同一个对象。
  2. 对象的比较:

    • 如果 objAobjB 都是对象类型(包括数组、函数等),=== 会检查它们是否指向同一个对象。
    • 如果它们指向同一个内存地址(即是同一个对象),则返回 true
    • 如果它们是不同的对象,虽然对象的内容可能相同,但它们的内存地址不同,因此返回 false

示例:

let objA = { name: 'Alice' };
let objB = { name: 'Alice' };
let objC = objA;

console.log(objA === objB); // false, 因为它们是不同的对象,虽然内容相同
console.log(objA === objC); // true, 因为 objC 是 objA 的引用,指向同一个对象

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/959436.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AI赋能医疗:智慧医疗系统源码与互联网医院APP的核心技术剖析

本篇文章&#xff0c;笔者将深入剖析智慧医疗系统的源码架构以及互联网医院APP背后的核心技术&#xff0c;探讨其在医疗行业中的应用价值。 一、智慧医疗系统的核心架构 智慧医疗系统是一个高度集成的信息化平台&#xff0c;主要涵盖数据采集、智能分析、决策支持、远程医疗等…

HTML-新浪新闻-实现标题-样式1

用css进行样式控制 css引入方式&#xff1a; --行内样式&#xff1a;写在标签的style属性中&#xff08;不推荐&#xff09; --内嵌样式&#xff1a;写在style标签中&#xff08;可以写在页面任何位置&#xff0c;但通常约定写在head标签中&#xff09; --外联样式&#xf…

【学习笔记】深度学习网络-深度前馈网络(MLP)

作者选择了由 Ian Goodfellow、Yoshua Bengio 和 Aaron Courville 三位大佬撰写的《Deep Learning》(人工智能领域的经典教程&#xff0c;深度学习领域研究生必读教材),开始深度学习领域学习&#xff0c;深入全面的理解深度学习的理论知识。 在之前的文章中介绍了深度学习中用…

如何在IDEA社区版Service面板中管理springboot项目

1、开启service仪表盘 2、在service仪表盘中&#xff0c;添加启动类配置项&#xff0c;专业版是SpringBoot 、社区版是application。 3、控制台彩色日志输出 右键启动类配置项&#xff0c;添加虚拟机参数 -Dspring.output.ansi.enabledALWAYS

如何在data.table中处理缺失值

&#x1f4ca;&#x1f4bb;【R语言进阶】轻松搞定缺失值&#xff0c;让数据清洗更高效&#xff01; &#x1f44b; 大家好呀&#xff01;今天我要和大家分享一个超实用的R语言技巧——如何在data.table中处理缺失值&#xff0c;并且提供了一个自定义函数calculate_missing_va…

.NET9增强OpenAPI规范,不再内置swagger

ASP.NETCore in .NET 9.0 OpenAPI官方文档ASP.NET Core API 应用中的 OpenAPI 支持概述 | Microsoft Learnhttps://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/openapi/overview?viewaspnetcore-9.0https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/ope…

【redis初阶】redis客户端

目录 一、基本介绍 二、认识RESP&#xff08;redis自定的应用层协议名称&#xff09; 三、访问github的技巧 四、安装redisplusplus 4.1 安装 hiredis** 4.2 下载 redis-plus-plus 源码 4.3 编译/安装 redis-plus-plus 五、编写运行helloworld 六、redis命令演示 6.1 通用命令的…

蓝桥杯3518 三国游戏 | 排序

题目传送门 这题的思路很巧妙&#xff0c;需要算出每个事件给三国带来的净贡献&#xff08;即本国士兵量减其他两国士兵量&#xff09;并对其排序&#xff0c;根据贪心的原理累加贡献量直到累加结果不大于0。最后对三国的胜利的最大事件数排序取最值即可。 n int(input()) a …

基于vue框架的的信用社业务管理系统设计与实现4gnx5(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;用户,用户销户,用户存款,用户取款,用户转账,理财类型,投资理财,理财订单,金属类别,贵金属,金属订单,产品分类,保险产品,保险订单 开题报告内容 基于Vue框架的信用社业务管理系统设计与实现开题报告 一、研究背景与意义 随着金融科技的…

自然语言处理——从原理、经典模型到应用

1. 概述 自然语言处理&#xff08;Natural Language Processing&#xff0c;NLP&#xff09;是一门借助计算机技术研究人类语言的科学&#xff0c;是人工智能领域的一个分支&#xff0c;旨在让计算机理解、生成和处理人类语言。其核心任务是将非结构化的自然语言转换为机器可以…

微信小程序1.1 微信小程序介绍

1.1 微信小程序介绍 内容提要 1.1 什么是微信小程序 1.2 微信小程序的功能 1.3 微信小程序使用场景 1.4 微信小程序能取代App吗 1.5 微信小程序的发展历程 1.6微信小程序带来的机会

【已解决】OSS配置问题

OSS SDK快速入门_对象存储(OSS)-阿里云帮助中心 阿里官方的SDK使用方法还得配置环境变量access Key、access Secret &#xff0c;我没有配置&#xff0c;仅把access Key和access Secret写到了yml文件读取&#xff0c;结果上传图片时还是出现下面的问题。 [ ERROR ] [ com.s…

SVN客户端使用手册

目录 一、简介 二、SVN的安装与卸载 1. 安装&#xff08;公司内部一般会提供安装包和汉化包&#xff0c;直接到公司内部网盘下载即可&#xff0c;如果找不到可以看下面的教程&#xff09; 2. 查看SVN版本 ​编辑 3. SVN卸载 三、SVN的基本操作 1. 检出 2. 清除认证数据 3. 提交…

Oracle迁移DM数据库

Oracle迁移DM数据库 1 数据准备 2 DTS工具操作步骤 2.1 创建工程 打开DTS迁移工具&#xff0c;点击新建工程&#xff0c;填写好工程信息&#xff0c;如图&#xff1a; 2.2 新建迁移任务 右击迁移>选择新建迁移>填写迁移名称>勾选启用&#xff0c;然后确认下一步…

正则表达式以及Qt中的使用

目录 一、正则表达式 1、基本匹配&#xff1a; 2、元字符&#xff1a; 2.1 .运算符&#xff1a; 2.2 字符集&#xff1a; 2.3 重复次数&#xff1a; 2.4 量词{} 2.5 特征标群() 2.6 或运算符 2.7 \反斜线转码特殊字符 2.8 锚点 3、简写字符 4、零宽度断言 4.1 正…

PyQt6医疗多模态大语言模型(MLLM)实用系统框架构建初探(上.文章部分)

一、引言 1.1 研究背景与意义 在数字化时代,医疗行业正经历着深刻的变革,智能化技术的应用为其带来了前所未有的发展机遇。随着医疗数据的指数级增长,传统的医疗诊断和治疗方式逐渐难以满足现代医疗的需求。据统计,全球医疗数据量预计每年以 48% 的速度增长,到 2025 年将…

Java进阶(一)

目录 一.Java注解 什么是注解&#xff1f; 内置注解 元注解 二.对象克隆 什么是对象克隆? 为什么用到对象克隆 三.浅克隆深克隆 一.Java注解 什么是注解&#xff1f; java中注解(Annotation)又称java标注&#xff0c;是一种特殊的注释。 可以添加在包&#xff0c;类&…

Pyecharts之特殊图表的独特展示

在数据可视化的世界里&#xff0c;除了常见的柱状图、折线图、饼图等&#xff0c;还有一些特殊的图表可以为我们带来独特的展示效果&#xff0c;帮助我们以更有趣、更直观的方式呈现数据。Pyecharts 为我们提供了多种特殊图表的绘制功能&#xff0c;本文将介绍象形图、水球图和…

VSCode下EIDE插件开发STM32

VSCode下STM32开发环境搭建 本STM32教程使用vscode的EIDE插件的开发环境&#xff0c;完全免费&#xff0c;有管理代码文件的界面&#xff0c;不需要其它IDE。 视频教程见本人的 VSCodeEIDE开发STM32 安装EIDE插件 Embedded IDE 嵌入式IDE 这个插件可以帮我们管理代码文件&am…

TLF35584 基本介绍

1 概述 1&#xff09;多电压电源芯片&#xff0c;包含6路输出电压。 LDO_Com&#xff1a;低降后调节器 5V/200mA 通信电源。LDO_C &#xff1a;低降后调节器 5V/600mA (TLF35584xxVS1)/3.3 V/600mA (TLF35584xxVS2) uC电源。Volt_Ref &#xff1a;参考电压5.0 V /- 1%/150mA …