面试题答案
一键面试避免不必要重渲染的策略
-
使用 React.memo:
- 原理:React.memo 是一个高阶组件,它通过浅比较 props 来决定组件是否需要重新渲染。如果 props 没有变化,组件将复用之前的渲染结果,从而避免不必要的重渲染。
- 应用于列表项组件:假设我们有一个
ListItem
组件来展示列表中的每一项数据。
const ListItem = React.memo(({ item }) => { return <div>{item.text}</div>; });
- 这样,只有当
ListItem
的item
prop 发生变化时,该组件才会重新渲染。如果item
是一个对象,浅比较只会检查对象的引用是否改变,所以要确保传递给ListItem
的item
引用变化是有意义的,否则可能出现数据更新但组件未重渲染的情况。
-
useCallback 和 useMemo:
- useCallback:
- 原理:
useCallback
用于缓存函数,返回一个 memoized 回调函数。它接收两个参数,第一个是要缓存的函数,第二个是依赖数组。只有当依赖数组中的值发生变化时,才会返回新的函数。 - 应用场景:在列表组件中,如果有传递给子组件(如
ListItem
)的回调函数,使用useCallback
可以避免这些回调函数在每次渲染时都重新创建,从而防止因函数引用变化导致子组件不必要的重渲染。例如,假设ListItem
有一个点击删除按钮的功能:
import React, { useCallback } from'react'; const List = ({ list, onDelete }) => { const handleDelete = useCallback((index) => { onDelete(index); }, [onDelete]); return ( <ul> {list.map((item, index) => ( <ListItem key={index} item={item} onDelete={handleDelete} /> ))} </ul> ); };
- 原理:
- useMemo:
- 原理:
useMemo
用于缓存计算结果,它接收两个参数,第一个是一个函数,该函数返回要缓存的值,第二个是依赖数组。只有当依赖数组中的值发生变化时,才会重新计算并返回新的值。 - 应用场景:如果列表组件中有一些需要计算的属性,并且计算成本较高,可以使用
useMemo
来缓存计算结果,避免每次渲染都重新计算。例如,计算列表中所有项的总和:
import React, { useMemo } from'react'; const List = ({ list }) => { const total = useMemo(() => { return list.reduce((acc, item) => acc + item.value, 0); }, [list]); return ( <div> <p>Total: {total}</p> <ul> {list.map((item, index) => ( <ListItem key={index} item={item} /> ))} </ul> </div> ); };
- 原理:
- useCallback:
-
虚拟列表:
- 原理:当列表数据量非常大时,一次性渲染所有列表项会导致性能问题。虚拟列表只渲染当前视口可见的列表项,当用户滚动时,动态地加载和卸载可见区域外的列表项。
- 实现方式:可以使用第三方库如
react - virtualized
或react - window
来实现虚拟列表。例如,使用react - virtualized
的List
组件:
import React from'react'; import { List } from'react - virtualized'; const rowRenderer = ({ index, key, style, data }) => { const item = data[index]; return ( <div key={key} style={style}> {item.text} </div> ); }; const MyList = ({ list }) => { return ( <List height={400} rowCount={list.length} rowHeight={50} rowRenderer={rowRenderer.bind(null, { data: list })} width={300} /> ); };
- 这样,无论列表数据量多大,始终只渲染视口内的列表项,大大提高了性能。
-
减少状态提升:
- 原理:如果列表项的状态只影响该项本身,尽量将状态放在该项组件内部,而不是提升到父列表组件。这样可以避免因父组件状态变化导致整个列表重新渲染。
- 示例:如果
ListItem
有一个展开/收起的功能,该状态应该在ListItem
组件内部管理,而不是在父List
组件管理。
const ListItem = React.memo(({ item }) => { const [isExpanded, setIsExpanded] = React.useState(false); return ( <div> <button onClick={() => setIsExpanded(!isExpanded)}> {isExpanded? 'Collapse' : 'Expand'} </button> {isExpanded && <div>{item.extraInfo}</div>} </div> ); });