You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React父组件与Memo子组件:表单值丢失问题排查与解决

问题分析

你的代码存在几个核心问题:

  1. Props名称不匹配:父组件给子组件传递的是form属性,但子组件接收的是initForm,导致子组件初始状态无法正确获取父组件的初始值。
  2. React.memo使用错误:比较函数返回() => true,意味着子组件永远不会重新渲染,即使props确实发生了需要同步的变化。
  3. 状态冗余且不同步:子组件和父组件各自维护一份表单状态,没有建立同步机制,导致状态不一致。
  4. 回调函数未缓存:父组件的change函数每次渲染都会重新创建,导致子组件的onChange props频繁变化,即使使用了memo也可能触发不必要的渲染或逻辑异常。

解决方案

我们需要实现单一状态源(父组件维护所有表单状态),子组件仅作为受控组件展示和触发更新,同时通过React.memo和useCallback实现父组件非相关属性变化时,子组件不更新。

修改后的完整代码

import React, { useState, useCallback } from "react";

// 子组件:作为受控组件,直接使用父组件传递的值,不维护自身state
const Child = ({ name, onChange }) => {
  const update = (e) => {
    onChange(e.target.value);
  };

  return (
    <>
      <input value={name ?? ""} onChange={update} />
      (Updated at : {new Date().toLocaleTimeString([], { timeStyle: "medium" })})
    </>
  );
};
// 用React.memo缓存子组件,仅当name或onChange变化时才重新渲染
const MemoChild = React.memo(Child);

function Parent() {
  const [form, setForm] = useState({
    name: "",
    childA: { name: null },
    childB: { name: null }
  });

  // 用useCallback缓存回调函数,避免每次渲染创建新函数
  const handleChildAChange = useCallback((value) => {
    setForm(prev => ({ ...prev, childA: { name: value } }));
  }, []);

  const handleChildBChange = useCallback((value) => {
    setForm(prev => ({ ...prev, childB: { name: value } }));
  }, []);

  const handleParentChange = (e) => {
    setForm(prev => ({ ...prev, name: e.target.value }));
  };

  return (
    <>
      <div>
        Parent Name : <input value={form.name} onChange={handleParentChange} />
        (Updated at : {new Date().toLocaleTimeString([], { timeStyle: "medium" })})
      </div>
      <div>
        ChildA Name : <MemoChild name={form.childA.name} onChange={handleChildAChange} />
      </div>
      <div>
        ChildB Name : <MemoChild name={form.childB.name} onChange={handleChildBChange} />
      </div>
    </>
  );
}

export default Parent;

关键修改点说明

  1. 统一状态管理:子组件不再维护自身state,完全依赖父组件传递的name属性,成为受控组件,避免状态冗余和不同步问题。
  2. 正确使用React.memo:移除自定义比较函数(默认会浅比较props),当子组件的name或onChange变化时才重新渲染;父组件修改自身name时,子组件的props未变化,因此不会重新渲染,符合你的需求。
  3. 缓存回调函数:用useCallback包裹子组件的回调函数,确保父组件渲染时,回调函数的引用不会变化,避免子组件因onChange props变化而触发不必要的渲染。
  4. 函数式更新state:使用setForm(prev => ...)的方式更新状态,确保获取到最新的state值,避免闭包陷阱。

如果你坚持子组件维护自身状态

如果你需要子组件独立维护状态(比如做本地临时缓存),可以通过useEffect同步父组件的初始值,同时正确配置React.memo:

const ChildA = ({ onChange, initValue }) => {
  const [name, setName] = useState(initValue);

  // 同步父组件传递的初始值变化
  useEffect(() => {
    setName(initValue);
  }, [initValue]);

  const update = (e) => {
    const newName = e.target.value;
    setName(newName);
    onChange('childA', newName);
  };

  return (
    <>
      <input value={name ?? ""} onChange={update} />
      (Updated at : {new Date().toLocaleTimeString([], { timeStyle: "medium" })})
    </>
  );
};

// 自定义memo比较函数:仅当initValue或onChange变化时才更新
const MemoChildA = React.memo(ChildA, (prevProps, nextProps) => {
  return prevProps.initValue === nextProps.initValue && prevProps.onChange === nextProps.onChange;
});

同时父组件需要传递正确的initValue,并缓存change函数:

const change = useCallback((key, value) => {
  setForm(prev => ({ ...prev, [key]: { name: value } }));
}, []);

// 调用子组件
<MemoChildA onChange={change} initValue={form.childA.name} />

内容的提问来源于stack exchange,提问作者stoomm

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 22:44:55