react项目,解决object类型状态改变但不及时触发渲染的问题
发布于 作者:苏南大叔 来源:程序如此灵动~本文描述的问题依然是react
项目,无论是父组件还是子组件,只有当项目内的state
变量或者传入组件的props
发生改变的时候,才应该触发组件的渲染。那么,上一篇文章里面,使用memo
解决了多次渲染的问题,那么本文中则致力于解决相反的问题,即变量发生改变了,却并不会触发渲染的问题。据说本文所描述现象的根本原因是react
渲染使用浅比较的原因所致,复杂类型就会被检测为没有变化。
大家好,这里是苏南大叔的程序如此灵动博客,这里记录苏南大叔和计算机代码的故事。本文描述触发渲染的变量特殊类型的问题,对于js
编程语言来说,这些特殊类型的变量都是object
类型。测试环境:create-react-app@5.0.1
,react@18.2.0
,react-dom@18.2.0
,node@16.14.2
。
现象描述
本文用例是基于如下文章链接的:
默认情况下来说,状态变量的改变,就会引起组件的渲染。还是以上面的例子为例,仅仅换个变量类型。react
项目是基于函数式组件的。父组件有三个状态值(在react tools
里面称之为hook
值),作为本文的关键因素。分别是:
变量 | 数据 | 说明 | 渲染 |
---|---|---|---|
data1 | 一个普通类型的数据 | 整形、字符串 | 会引起渲染 |
data2 | 一个数组类型的数据 | js 会认为数组是个object | 不一定会引起渲染 |
data3 | 一个object 类型的数据 | 就是大家想的那个object 类型 | 不一定会引起渲染 |
这里有关array
会被判断为object
的事情,请参考文章:
作为一个父组件的state
值(子组件的props
值),修改的时候,按理说是一定会引起渲染动作的。但是object
(包括array
),修改其数据的时候,因为并不会引发变量内存地址的变化,所以并不会引发渲染。如果触发了内存地址的变化(比如一个新的变量地址)就会触发渲染动作。因此,分别有两种方式对data2
和data3
进行修改。
变量 | 数据 | 修改方式 | 渲染 |
---|---|---|---|
data2 | 一个数组类型的数据 | arr.push(xxx) | 不会引起渲染 |
data2 | 一个数组类型的数据 | [...arr,xxx] | 会引起渲染 |
data3 | 一个object 类型的数据 | obj.prop = xxx | 不会引起渲染 |
data3 | 一个object 类型的数据 | {...obj,...{prop:xxx}} | 会引起渲染 |
测试代码
测试代码,以父组件触发渲染为例,进行演示。对应的数据,以props
的形式传入子组件的时候,存在一致的是否渲染的关系。
app.js
代码:
import './App.css';
import React, { useState } from 'react';
function App() {
// 基本状态值三个
const [data1, set_data1] = useState(0)
const [data2, set_data2] = useState(["sunan"])
const [data3, set_data3] = useState({ "name": "苏南大叔" })
// 改变状态的办法,有两个对照组
function _set_data2() {
data2.push(data1 + ",传统方式");
set_data2(data2);
}
function __set_data2() {
//set_data2([...data2, data1 + ",三个点方式"]);
set_data2(["苏南大叔,定义了一个新数组"]);
}
function _set_data3() {
data3.name = data3.name + ",传统方式" + data1;
set_data3(data3);
}
function __set_data3() {
// set_data3({ ...data3, ...{ "name": data3.name + ",三个点方式" + data1 } });
set_data3({"name":"苏南大叔,定义了一个新对象"});
}
// 组件调用
return (
<>
{
console.log("重新渲染了", Date.now())
}
<div className="App">
<table>
<tbody>
<tr>
<td>
<button onClick={() => set_data1((val) => val + 1)}>data1永远会触发渲染,可以点此看渲染效果</button>
</td>
<td>{data1}</td>
</tr>
<tr>
<td>
<button onClick={_set_data2.bind()}>data2传统方式修改数据,不渲染</button>
<button onClick={__set_data2.bind()}>data2三个点方式改数据,会渲染</button>
</td>
<td>{data2}</td>
</tr>
<tr>
<td>
<button onClick={_set_data3.bind()}>data3传统方式修改数据,不渲染</button>
<button onClick={__set_data3.bind()}>data3三个点方式改数据,会渲染</button>
</td>
<td>{data3.name}</td>
</tr>
</tbody>
</table>
</div>
</>
);
}
export default App;
对于代码里面的useState
的说明,请参考:
执行效果
界面上共三组按钮,
- 第一组(个)是用于改变状态变量
data1
的,因为改变的是个普通的类型的值。所以常规更改即可触发渲染。 - 第二组是用于改变状态数组变量
data2
的,传统的.push()
方式,并不能触发渲染。而改用...
方式则触发了渲染。 - 第三组是用于改变状态数组变量
data3
的,传统的obj.xxx
方式,并不能触发渲染。而改用...
方式则触发了渲染。
没有触发渲染的按钮动作,也实际上修改了变量的值,只不过没有被展示出来而已。
不能被触发渲染的动作
实际上改变了值,但是就是不会被触发渲染,因为内置地址没有发生改变。
function _set_data2() {
data2.push(data1 + ",传统方式");
set_data2(data2);
}
function _set_data3() {
data3.name = data3.name + ",传统方式" + data1;
set_data3(data3);
}
这两个动作的共同特点,就是直接修改的原object
的值。(array
在js
类型识别的时候,也是object
)
被触发渲染的动作
下面的函数动作,触发了实际的渲染动作。
function __set_data2() {
set_data2([...data2, data1 + ",三个点方式"]);
}
function __set_data3() {
set_data3({ ...data3, ...{ "name": data3.name + ",三个点方式" + data1 } });
}
当然,也不是说只有三个点才会触发渲染,只是这种三个点的方式【表象】,实际上创建了新的变量【重点】,改变了内存地址。参考文章:
比如下面这种普通的重新定义变量的方式,也是可以触发渲染的。
function __set_data2() {
set_data2(["苏南大叔,定义了一个新数组"]);
}
function __set_data3() {
set_data3({"name":"苏南大叔,定义了一个新对象"});
}
相关链接
- https://newsn.net/say/react-strict-mode.html
- https://newsn.net/say/js-array-merge.html
- https://newsn.net/say/js-dot-dot-dot.html
总结
更多react
经验文章,请点击下面的链接:
本博客不欢迎:各种镜像采集行为。请尊重原创文章内容,转载请保留作者链接。