之前有个业务,父组件调用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 认为状态未更新,子组件不渲染。
- 传对象的写法:因为创建了新对象
总结
- React 对状态 / 属性的更新判断规则:基本类型比「值」,引用类型比「内存地址(引用)」;
setCheckInviteCodeMsg({ errMsg: resp.msg })每次生成新对象,引用必变,因此子组件总能检测到更新;setCheckInviteCodeMsg(resp.msg)仅当字符串值变化时,子组件才会更新,值不变则无感知。
补充建议
- 若希望子组件仅在「实际值变化」时渲染(即使传对象),可使用
React.memo包裹子组件,并配合useMemo缓存对象:
javascript
// 父组件优化
const memoizedMsg = useMemo(() => ({ errMsg: resp.msg }), [resp.msg]);
setCodeMsg(memoizedMsg);
// 子组件优化
const Child = React.memo(({ msg }) => { /* 子组件逻辑 */ });
- 优先根据实际业务场景选择状态类型:仅需传递单个字符串时,用基本类型更高效;需传递多字段时,用对象并配合缓存优化。