React TS父组件状态变更致列表子组件重复挂载问题求解
React + TypeScript组件状态重置问题解决方案
问题场景
在React(TypeScript)项目中,父组件App包含列表组件ComponentList,后者渲染多个Number子组件。当点击Update按钮修改父组件的x状态(未改动列表数据)时,已修改过颜色的Number组件会重置为初始颜色,内部状态丢失。
代码复现
App.tsx
import { useState } from 'react' import Number from './Number' import './App.css' function App() { const [list, setList] = useState(["1", "2", "3", "4"]); const [x, setX] = useState({}); function ComponentList(){ return(<> {list.map(i => <Number num={i} key={i}/>) } </>) } return ( <div className="App"> <ComponentList/> <div onClick={()=>{ setX({}); }}>Update</div> </div> ) } export default App
Number.tsx
import React, {useState} from 'react' type props = { num: string } function Number({num}:props) { const [color, setColor] = useState("rgb(0, 0, 0)"); return ( <div style={{color: color}} onClick={()=>{ setColor("rgb(255, 255, 255)"); }}> {num} </div> ) } export default Number
问题根源
核心原因是ComponentList被定义为App组件内部的函数组件:每次App因状态变更(比如x更新)重新渲染时,ComponentList函数都会被重新创建。React在对比组件类型时,会将新创建的ComponentList视为完全不同的组件类型,因此会卸载旧的ComponentList及其所有子组件(Number),再挂载新的实例,导致Number的内部color状态被重置。
解决方法
以下两种方案均可解决问题,且保留列表组件的结构:
方案1:将ComponentList提取为独立组件
把ComponentList移到App组件外部,作为独立组件接收list作为props。这样ComponentList的引用不会随App渲染而变化,React会识别为同一组件类型,不会触发子组件的卸载/重新挂载:
import { useState } from 'react' import Number from './Number' import './App.css' // 提取为独立组件 const ComponentList = ({ list }: { list: string[] }) => { return ( <> {list.map(i => <Number num={i} key={i} />)} </> ) } function App() { const [list, setList] = useState(["1", "2", "3", "4"]); const [x, setX] = useState({}); return ( <div className="App"> <ComponentList list={list} /> <div onClick={()=> setX({})}>Update</div> </div> ) } export default App
方案2:将ComponentList改为普通渲染函数
如果需要保留在App内部定义,可将ComponentList改为普通函数,在App的return中直接调用(而非作为组件标签使用)。这样React会直接渲染函数返回的元素,不会将其视为独立组件,避免卸载/重新挂载:
import { useState } from 'react' import Number from './Number' import './App.css' function App() { const [list, setList] = useState(["1", "2", "3", "4"]); const [x, setX] = useState({}); // 改为普通渲染函数 const renderComponentList = () => { return(<> {list.map(i => <Number num={i} key={i}/>) } </>) } return ( <div className="App"> {renderComponentList()} {/* 直接调用函数 */} <div onClick={()=> setX({})}>Update</div> </div> ) } export default App
额外优化(可选)
为了避免Number组件在父组件渲染时不必要的重渲染,可使用React.memo包裹Number组件:
// Number.tsx import React, { useState, memo } from 'react' type props = { num: string } const Number = memo(({num}:props) => { const [color, setColor] = useState("rgb(0, 0, 0)"); return ( <div style={{color: color}} onClick={()=>{ setColor("rgb(255, 255, 255)"); }}> {num} </div> ) }) export default Number
内容的提问来源于stack exchange,提问作者Max W.
相关产品推荐
相关产品推荐

