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

React组件切换后数据丢失:已用React.memo仍无效

问题:切换React标签页后表单数据丢失

我有两个React组件:BucketTabs与BucketForms。切换标签页时,下方展示对应的表单。但每次从一个标签页切换到另一个,再切回原标签页时,BucketForms中的内容会丢失——这些数据本该存在组件的state里。我已经给BucketForms加了React.memo,但数据还是会丢,请问问题根源是什么?该怎么解决?

我的代码

BucketTabs组件

import { BucketForms } from '~components/BucketForms/BuckForms'

export const BucketTabs: React.FC = () => {
 

  const items = useMemo<ContentTabsItem[]>((): ContentTabsItem[] => {
    return [
      {
        title: '1',
        renderContent: () => <BucketForms key="1" bucketCategory="1" />,
      },
      {
        title: '2',
        renderContent: () => <BucketForms key="2" bucketCategory="2" />,
      },
    ]
  }, [])

 

  return (
    <div className="row">
      <div className="col">
        <ContentTabs items={tabs} kind="tabs" />
      </div>
    </div>
  )
}

BucketForms组件

function PropsAreEqual(prev, next) {
  const result = prev.bucketCategory === next.bucketCategory;
  return result;
}

interface IData {
  portfolioValue?: number
  }
export const BucketForms: React.FC<IProps> = React.memo(props => {
  const { bucketCategory } = props
  const [data, setData] = useState<IData>({
  })

  
  const view = ({
    portfolioValue,
  }: IData) => {
    return (
      <>
        <div className="row portfolio">
          <FormNumericInput
            key="input-portfolio-value"
            name="portfolioValue"
            required
            value={portfolioValue}
          />
        </div>
      </>
    )
  }


  return (
  <Form
      onChange={e => {
        setData({ ...data, ...e, })
      }}
    >
      {view(data)}
   </Form>
  )
}, PropsAreEqual)

问题根源

React.memo的作用是阻止组件在props未变化时的不必要重渲染,但它解决不了组件卸载导致的state丢失问题。你的场景里,ContentTabs组件在切换标签时,会把非激活标签对应的BucketForms组件从DOM中移除——这会触发组件卸载,组件内部的state会被完全销毁。当你切回原标签时,会重新创建一个全新的BucketForms实例,state自然回到初始值,之前输入的数据也就没了。


解决方案

1. 将状态提升到父组件(推荐)

把BucketForms的state移到父组件BucketTabs中统一管理,通过props把数据和更新方法传递给子组件。这样即使子组件被卸载,数据也会保存在父组件的state里,切回标签时能直接恢复。

修改后的BucketTabs:

import { BucketForms } from '~components/BucketForms/BuckForms'

// 引入IData类型(需要和BucketForms中的定义一致)
interface IData {
  portfolioValue?: number
}

export const BucketTabs: React.FC = () => {
  // 用对象存储不同标签的表单数据,key对应bucketCategory
  const [tabData, setTabData] = useState<Record<string, IData>>({
    '1': {},
    '2': {}
  })

  // 统一处理不同标签的数据更新
  const updateTabData = (bucketCategory: string, newData: IData) => {
    setTabData(prev => ({
      ...prev,
      [bucketCategory]: newData
    }))
  }

  const items = useMemo<ContentTabsItem[]>((): ContentTabsItem[] => {
    return [
      {
        title: '1',
        renderContent: () => (
          <BucketForms 
            key="1" 
            bucketCategory="1" 
            data={tabData['1']}
            onChange={(newData) => updateTabData('1', newData)}
          />
        ),
      },
      {
        title: '2',
        renderContent: () => (
          <BucketForms 
            key="2" 
            bucketCategory="2" 
            data={tabData['2']}
            onChange={(newData) => updateTabData('2', newData)}
          />
        ),
      },
    ]
  }, [tabData])

  return (
    <div className="row">
      <div className="col">
        <ContentTabs items={items} kind="tabs" />
      </div>
    </div>
  )
}

修改后的BucketForms:

function PropsAreEqual(prev, next) {
  // 对比bucketCategory和data,确保props不变时不重渲染
  return prev.bucketCategory === next.bucketCategory && JSON.stringify(prev.data) === JSON.stringify(next.data);
}

interface IData {
  portfolioValue?: number
}

interface IProps {
  bucketCategory: string;
  data: IData;
  onChange: (data: IData) => void;
}

export const BucketForms: React.FC<IProps> = React.memo((props) => {
  const { bucketCategory, data, onChange } = props

  const view = ({ portfolioValue }: IData) => {
    return (
      <>
        <div className="row portfolio">
          <FormNumericInput
            key="input-portfolio-value"
            name="portfolioValue"
            required
            value={portfolioValue}
          />
        </div>
      </>
    )
  }

  return (
    <Form
      onChange={e => {
        onChange({ ...data, ...e })
      }}
    >
      {view(data)}
    </Form>
  )
}, PropsAreEqual)

2. 让ContentTabs保留所有标签的组件实例

如果你的ContentTabs组件支持,可以配置为不卸载非激活标签的内容,而是通过CSS隐藏(比如display: none)。这样所有BucketForms组件都会一直存在于DOM中,state不会被销毁。

如果是自定义的ContentTabs组件,修改渲染逻辑:不要用条件渲染(比如{activeTab === '1' && <BucketForms />}),而是渲染所有标签内容,通过类名控制显示和隐藏:

// 示例:自定义ContentTabs的渲染逻辑
return (
  <div>
    {/* 标签头 */}
    <div className="tabs-header">
      {items.map(item => (
        <button 
          key={item.title}
          className={activeKey === item.key ? 'active' : ''}
          onClick={() => setActiveKey(item.key)}
        >
          {item.title}
        </button>
      ))}
    </div>
    {/* 标签内容:全部渲染,用CSS控制显示 */}
    <div className="tabs-content">
      {items.map(item => (
        <div 
          key={item.key}
          className={activeKey === item.key ? 'tab-content active' : 'tab-content hidden'}
        >
          {item.renderContent()}
        </div>
      ))}
    </div>
  </div>
)

对应的CSS:

.tab-content.hidden {
  display: none;
}
.tab-content.active {
  display: block;
}

3. 用本地存储持久化数据

如果不需要把数据存在父组件,也可以把表单数据存在localStorage或sessionStorage中,组件初始化时从本地存储读取恢复数据。这种方式即使刷新页面,数据也不会丢失。

修改后的BucketForms:

function PropsAreEqual(prev, next) {
  const result = prev.bucketCategory === next.bucketCategory;
  return result;
}

interface IData {
  portfolioValue?: number
}

export const BucketForms: React.FC<IProps> = React.memo((props) => {
  const { bucketCategory } = props
  // 初始化时从localStorage读取对应标签的数据
  const [data, setData] = useState<IData>(() => {
    const savedData = localStorage.getItem(`bucket-form-${bucketCategory}`);
    return savedData ? JSON.parse(savedData) : {};
  })

  // 数据变化时同步保存到localStorage
  useEffect(() => {
    localStorage.setItem(`bucket-form-${bucketCategory}`, JSON.stringify(data));
  }, [data, bucketCategory])
  
  const view = ({ portfolioValue }: IData) => {
    return (
      <>
        <div className="row portfolio">
          <FormNumericInput
            key="input-portfolio-value"
            name="portfolioValue"
            required
            value={portfolioValue}
          />
        </div>
      </>
    )
  }

  return (
    <Form
      onChange={e => {
        setData({ ...data, ...e })
      }}
    >
      {view(data)}
    </Form>
  )
}, PropsAreEqual)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 04:24:26