【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 }) => { /* 子组件逻辑 */ });
  • 优先根据实际业务场景选择状态类型:仅需传递单个字符串时,用基本类型更高效;需传递多字段时,用对象并配合缓存优化。
相关推荐
saber_andlibert27 分钟前
TCMalloc底层实现
java·前端·网络
逍遥德27 分钟前
如何学编程之01.理论篇.如何通过阅读代码来提高自己的编程能力?
前端·后端·程序人生·重构·软件构建·代码规范
冻感糕人~42 分钟前
【珍藏必备】ReAct框架实战指南:从零开始构建AI智能体,让大模型学会思考与行动
java·前端·人工智能·react.js·大模型·就业·大模型学习
程序员agions1 小时前
2026年,“配置工程师“终于死绝了
前端·程序人生
alice--小文子1 小时前
cursor-mcp工具使用
java·服务器·前端
晚霞的不甘1 小时前
揭秘 CANN 内存管理:如何让大模型在小设备上“轻装上阵”?
前端·数据库·经验分享·flutter·3d
小迷糊的学习记录1 小时前
0.1 + 0.2 不等于 0.3
前端·javascript·面试
空&白2 小时前
vue暗黑模式
javascript·vue.js
梦帮科技2 小时前
Node.js配置生成器CLI工具开发实战
前端·人工智能·windows·前端框架·node.js·json
VT.馒头2 小时前
【力扣】2695. 包装数组
前端·javascript·算法·leetcode·职场和发展·typescript