React实战:使用startTransition、memo、useCallback优化筛选组件

33 阅读3分钟
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)
    }, [])

    // useCallback 在重新渲染之间缓存函数定义
    // 默认情况下,传给showInput的handleInputChange函数,每次渲染时都是一个新的函数
    // 使用useCallback包裹后,只有当items发生变化时,才会重新渲染handleInputChange函数
    const handleInputChange = useCallback((e) => {
        setInputValue(e.target.value)
        // startTransition 用于将更新标记为低优先级,允许其他高优先级更新先执行
        // 例如,用户输入时,我们希望先更新输入框的值,然后再更新筛选后的列表
        // 这样可以避免用户输入时,列表更新导致的闪烁问题
        startTransition(() => {
            setFilterItems(items.filter(item => item.includes(e.target.value)))
        })
    }, [items])

    return <>
        <ShowInput inputValue={inputValue} handleInputChange={handleInputChange} />
        <ShowList items={filterItems} />
    </>
}

// 父组件重新渲染时,默认会重新渲染子组件
// 子组件使用memo包裹后,只有当props发生变化时,才会重新渲染
// 被memo包裹的组件,称为被记忆化的组件
const ShowInput = memo(({inputValue, handleInputChange}) => {
    return <>
        <input type="text" value={inputValue} onChange={handleInputChange} />
    </>
})

// 只有当filterItems发生变化时,才会重新渲染showList组件
const ShowList = memo(({items}) => {
    return <>
        <ul>
            {
                items.map((item, index) => {
                    return <li key={index}>{item}</li>
                })
            }
        </ul>
    </>
})

组件包含的功能

  1. 页面加载后,显示一个包含 5000 个项目的列表。
  2. 用户可以在上方的输入框中输入文本。
  3. 下方的列表会根据输入的文本进行实时筛选,只显示包含该文本的项目。

优化一: 使用startTransition触发React的并发特性

  • items.filter(...) 是一个潜在的慢操作,因为它需要遍历 5000 个项目。
  • startTransition 将这个慢操作包裹起来,告诉 React:“这个更新不紧急,你可以稍后处理,甚至在处理过程中如果有了新的更新,可以中断当前这次并重新开始。”
  • 效果:React 会优先保证输入框的流畅响应,然后在后台(并发地)计算新的 filterItems。这避免了因为列表筛选而阻塞主线程,从而防止了输入框的卡顿。

优化二: 使用 memo 和 useCallback 避免不必要的子组件重渲染

  1. useCallback(handleInputChange, [items]) :

    • 它缓存了 handleInputChange 函数。因为依赖项 [items] 在组件生命周期内不变,所以 handleInputChange 函数的引用也永远不会变。
    • 目的:当 StartTransitionDemo 组件因为 inputValue 或 filterItems 状态变化而重新渲染时,传递给子组件 ShowInput 的 handleInputChange prop 始终是同一个函数,而不是每次都创建一个新的。
  2. memo(ShowInput) 和 memo(ShowList) :

    • memo 是一个高阶组件,它会对组件的 props 进行浅比较。只有当 props 发生变化时,组件才会重新渲染。
    • ShowInput: 结合 useCallback,如果父组件因为 filterItems 变化而重渲染,ShowInput 不会重渲染,因为它依赖的 inputValue 和 handleInputChange 都没变。
    • ShowList: 这是最关键的优化之一。ShowList 只依赖 items prop (也就是 filterItems 状态)。只有当筛选结果真正改变时,这个庞大的列表才会重新渲染。在用户快速输入的过程中,如果两次输入的筛选结果相同,或者在 startTransition 过渡期间,它都不会发生不必要的渲染。