【React】状态更新避坑:基本类型与引用类型对父子组件通信的影响

之前有个业务,父组件调用api,子组件需要根据 api 的一个 code 显示警告,input 可以为空,但是有内容就需要 api 校验,警告的内容也是后台给的(每次都是一样的内容),这个时候就需要父传子,尽管父组件内容无变化,但子组件仍需做出响应。

在 React 父组件向子组件传值时,setCodeMsg({ errMsg: resp.msg })(传对象)和 setCodeMsg(resp.msg)(传字符串)两种写法的区别,以及为什么传对象时无论值是否变化子组件都能检测到更新,而传字符串却不行。

核心原因与代码示例解析

1. React 状态更新与重渲染的底层逻辑

React 判断是否需要重新渲染组件的核心依据是:状态 / 属性的引用是否发生变化 (对于引用类型)或值是否发生变化(对于基本类型)。

  • 基本类型(字符串、数字、布尔等):比较的是「值本身」,只有值发生改变时,React 才会认为状态更新,进而触发子组件重渲染。
  • 引用类型(对象、数组、函数等):比较的是「内存地址(引用)」,哪怕对象内部的值完全没变,只要重新创建了一个新对象(引用变了),React 就会认为状态更新,触发子组件重渲染。

2. 代码示例还原你的场景

父组件(核心代码)
javascript 复制代码
import { useState } from 'react';
import Child from './Child';

function Parent() {
  // 场景1:状态是对象类型
  const [checkMsgObj, setCheckMsgObj] = useState({ errMsg: '' });
  // 场景2:状态是字符串类型
  const [checkMsgStr, setCheckMsgStr] = useState('');

  // 模拟接口请求后更新状态
  const handleCheck = () => {
    const resp = { msg: '验证码无效' }; // 模拟接口返回值

    // 方式1:更新对象类型状态(每次都是新对象)
    setCodeMsgObj({ errMsg: resp.msg }); 
    // 方式2:更新字符串类型状态(值不变时引用也不变)
    setCodeMsgStr(resp.msg); 
  };

  return (
    <div>
      <button onClick={handleCheck}>触发更新</button>
      {/* 子组件接收对象类型属性 */}
      <Child msg={checkMsgObj} />
      {/* 子组件接收字符串类型属性 */}
      <Child msg={checkMsgStr} />
    </div>
  );
}

export default Parent;

子组件(核心代码)

javascript 复制代码
function Child({ msg }) {
  // 打印日志,观察是否重渲染
  console.log('子组件渲染了', msg);
  return <div>{typeof msg === 'object' ? msg.errMsg : msg}</div>;
}

export default Child;

为什么setState({})就可以生成新对象?
点击查看https://blog.csdn.net/L_xxxxxxx/article/details/157429206?spm=1001.2014.3001.5501

3. 两种写法的具体差异

|----------------------------------|-----------|--------------------------|-------------------------------------------|
| 写法 | 类型 | 每次更新的本质 | 子组件是否触发渲染 |
| setCodeMsg({ errMsg: resp.msg }) | 对象(引用类型) | 每次创建新的对象(内存地址变了) | 无论 resp.msg 值是否变化,子组件都会检测到属性更新并渲染 |
| setCodeMsg(resp.msg) | 字符串(基本类型) | 仅当 resp.msg 值变化时,状态才更新 | 若 resp.msg 值不变(比如连续两次都是「验证码无效」),子组件不会渲染 |

举个具体例子:

  • 第一次点击按钮,resp.msg = '验证码无效',两种写法都会让子组件渲染;
  • 第二次点击按钮,resp.msg 还是 ' 验证码无效 ':
    • 传对象的写法:因为创建了新对象 { errMsg: '验证码无效' },引用变了,子组件重新渲染;
    • 传字符串的写法:字符串值没变化,React 认为状态未更新,子组件不渲染。

总结

  1. React 对状态 / 属性的更新判断规则:基本类型比「值」,引用类型比「内存地址(引用)」;
  2. setCheckInviteCodeMsg({ errMsg: resp.msg }) 每次生成新对象,引用必变,因此子组件总能检测到更新;
  3. setCheckInviteCodeMsg(resp.msg) 仅当字符串值变化时,子组件才会更新,值不变则无感知。

补充建议

  • 若希望子组件仅在「实际值变化」时渲染(即使传对象),可使用 React.memo 包裹子组件,并配合 useMemo 缓存对象:
javascript 复制代码
// 父组件优化
const memoizedMsg = useMemo(() => ({ errMsg: resp.msg }), [resp.msg]);
setCodeMsg(memoizedMsg);

// 子组件优化
const Child = React.memo(({ msg }) => { /* 子组件逻辑 */ });
  • 优先根据实际业务场景选择状态类型:仅需传递单个字符串时,用基本类型更高效;需传递多字段时,用对象并配合缓存优化。
相关推荐
雨季6664 小时前
构建 OpenHarmony 简易文字行数统计器:用字符串分割实现纯文本结构感知
开发语言·前端·javascript·flutter·ui·dart
雨季6664 小时前
Flutter 三端应用实战:OpenHarmony 简易倒序文本查看器开发指南
开发语言·javascript·flutter·ui
小北方城市网4 小时前
Redis 分布式锁高可用实现:从原理到生产级落地
java·前端·javascript·spring boot·redis·分布式·wpf
console.log('npc')4 小时前
vue2 使用高德接口查询天气
前端·vue.js
2401_892000524 小时前
Flutter for OpenHarmony 猫咪管家App实战 - 添加支出实现
前端·javascript·flutter
天马37984 小时前
Canvas 倾斜矩形绘制波浪效果
开发语言·前端·javascript
天天向上10244 小时前
vue3 实现el-table 部分行不让勾选
前端·javascript·vue.js
qx094 小时前
esm模块与commonjs模块相互调用的方法
开发语言·前端·javascript
摘星编程5 小时前
在OpenHarmony上用React Native:SectionList吸顶分组标题
javascript·react native·react.js
摘星编程5 小时前
React Native鸿蒙版:StackNavigation页面返回拦截
react native·react.js·harmonyos