import React, {useState, startTransition, useEffect, memo, useCallback} from "react";
export default function StartTransitionDemo() {
const [inputValue, setInputValue] = useState('')
const [filterItems, setFilterItems] = useState([])
const [items, setItems] = useState([])
useEffect(() => {
const tempItems = []
for (let i = 0; i < 5000; i++) {
tempItems.push(`Item ${i}`)
}
setItems(tempItems)
setFilterItems(tempItems)
}, [])
const handleInputChange = useCallback((e) => {
setInputValue(e.target.value)
startTransition(() => {
setFilterItems(items.filter(item => item.includes(e.target.value)))
})
}, [items])
return <>
<ShowInput inputValue={inputValue} handleInputChange={handleInputChange} />
<ShowList items={filterItems} />
</>
}
const ShowInput = memo(({inputValue, handleInputChange}) => {
return <>
<input type="text" value={inputValue} onChange={handleInputChange} />
</>
})
const ShowList = memo(({items}) => {
return <>
<ul>
{
items.map((item, index) => {
return <li key={index}>{item}</li>
})
}
</ul>
</>
})
组件包含的功能
- 页面加载后,显示一个包含 5000 个项目的列表。
- 用户可以在上方的输入框中输入文本。
- 下方的列表会根据输入的文本进行实时筛选,只显示包含该文本的项目。
优化一: 使用startTransition触发React的并发特性
items.filter(...) 是一个潜在的慢操作,因为它需要遍历 5000 个项目。
startTransition 将这个慢操作包裹起来,告诉 React:“这个更新不紧急,你可以稍后处理,甚至在处理过程中如果有了新的更新,可以中断当前这次并重新开始。”
- 效果:React 会优先保证输入框的流畅响应,然后在后台(并发地)计算新的
filterItems。这避免了因为列表筛选而阻塞主线程,从而防止了输入框的卡顿。
优化二: 使用 memo 和 useCallback 避免不必要的子组件重渲染
-
useCallback(handleInputChange, [items]) :
- 它缓存了
handleInputChange 函数。因为依赖项 [items] 在组件生命周期内不变,所以 handleInputChange 函数的引用也永远不会变。
- 目的:当
StartTransitionDemo 组件因为 inputValue 或 filterItems 状态变化而重新渲染时,传递给子组件 ShowInput 的 handleInputChange prop 始终是同一个函数,而不是每次都创建一个新的。
-
memo(ShowInput) 和 memo(ShowList) :
memo 是一个高阶组件,它会对组件的 props 进行浅比较。只有当 props 发生变化时,组件才会重新渲染。
ShowInput: 结合 useCallback,如果父组件因为 filterItems 变化而重渲染,ShowInput 不会重渲染,因为它依赖的 inputValue 和 handleInputChange 都没变。
ShowList: 这是最关键的优化之一。ShowList 只依赖 items prop (也就是 filterItems 状态)。只有当筛选结果真正改变时,这个庞大的列表才会重新渲染。在用户快速输入的过程中,如果两次输入的筛选结果相同,或者在 startTransition 过渡期间,它都不会发生不必要的渲染。