React输入搜索文本时结果列表重复渲染问题该如何解决?
问题根因
- 你将
Asset组件定义在了AssetListTool组件内部,每次父组件因为searchText更新重渲染时,Asset组件的引用都会被重新创建,你给它加的React.memo完全失效,导致所有图片项每次都跟着重渲染 onSearchTextChanged的useCallback错误添加了searchText作为依赖,每次输入都会生成新的回调引用,进一步加剧不必要的渲染- 所有状态都维护在父组件,输入状态更新时必然触发父组件整体重渲染,哪怕图片列表的依赖没有任何变化
最优解决方案
优先采用拆分组件的方案,完全可以解决你的问题,不会出现你担心的“子组件重渲染带动父组件重渲染”的问题:子组件自身的内部状态更新只会触发自身重渲染,只有子组件调用父组件传入的方法修改父组件状态时,才会触发父组件重渲染。
具体优化步骤:
- 抽离
SearchBar子组件,内部独立维护searchText输入状态,仅在用户回车/点击搜索按钮时,才调用父组件传入的onSearch回调提交搜索词 - 抽离
AssetList和Asset子组件,用React.memo包裹,仅当assets属性变化时才触发重渲染 - 修复原代码的闭包隐患,比如
useEffect中更新assets用函数式写法,修正useCallback的依赖
优化后完整代码:
import { memo, useState, useCallback, useEffect } from 'react' // 抽离到父组件外部的Asset项组件,memo生效 const Asset = memo(function ({ asset }) { return ( <div className="asset"> <img src={asset.thumbnail} alt={asset.title} /> </div> ) }) // 图片列表组件,memo包裹,仅assets变化时重渲染 const AssetList = memo(function ({ assets }) { if (!assets?.length) return null return ( <div> {assets.map(asset => <Asset key={asset.id} asset={asset} />)} </div> ) }) // 搜索栏组件,内部维护输入状态,输入时仅自身重渲染 function SearchBar({ onSearch }) { const [searchText, setSearchText] = useState('') const onSearchTextChanged = useCallback((e) => { setSearchText(e.target.value) }, []) const onSearchKeyPressed = useCallback((e) => { if (e.key === 'Enter') { handleSearch() } }, [searchText, onSearch]) const handleSearch = useCallback(() => { onSearch(searchText.trim()) }, [searchText, onSearch]) return ( <div> <div> <input onChange={onSearchTextChanged} onKeyPress={onSearchKeyPressed} value={searchText} type="text" /> </div> <div> <button onClick={handleSearch}> <Icon icon={faSearch}/> </button> </div> </div> ) } function AssetListTool () { const [assets, setAssets] = useState([]) const [searchTerm, setSearchTerm] = useState('') // 搜索回调用useCallback包,保证引用稳定,不会导致SearchBar不必要重渲染 const handleSearch = useCallback((keyword) => { setAssets([]) setSearchTerm(keyword) }, []) useEffect(() => { const params = { limit: 30, sort: 'title:desc', searchTerm } const matchingAssets = assetApi.getAllAssets({...params }) Promise.all([matchingAssets]).then(responses => { // 用函数式更新避免闭包问题,不需要依赖外部assets setAssets(prev => prev.concat(responses[0].items)) }) }, [searchTerm]) return ( <> <SearchBar onSearch={handleSearch} /> <AssetList assets={assets} /> </> ) } export default AssetListTool
优化效果
- 搜索框输入时,仅
SearchBar组件自身重渲染,父组件和图片列表完全不会触发重渲染 - 仅当搜索提交、
searchTerm变化触发新数据返回后,AssetList才会因为assets更新重渲染 - 列表中的
Asset项仅当自身asset属性变化时才会重渲染,不会跟着列表整体无差别重渲染
内容的提问来源于stack exchange,提问作者Pete
相关产品推荐
相关产品推荐

