使用函数作为子组件模式时,如何用useCallback包裹renderItem
我创建了一个FirstChild组件,通过函数作为子组件的模式提供close方法,代码如下:
const Parent = () => { return <FirstChild> {({ close }) => <FlatList renderItem={(item) => { /* 使用 'close' 和 'item' */ }} />} </FirstChild> }
现在想把传递给renderItem的闭包用useCallback包裹,但遇到无法访问close方法的问题,尝试了三种方式都存在问题:
第一种尝试(Hook使用位置错误)
const Parent = () => { return <FirstChild> {({ close }) => { // 问题:不能在这里使用Hook! const renderItem = useCallback((item) => /* 使用 'close' 和 'item' */, []); return <FlatList renderItem={renderItem} />} </FirstChild> }
问题:Hook只能在组件顶层调用,不能在render props的回调函数内部使用。
第二种尝试(无法访问close)
const Parent = () => { // 问题:close 未定义 const renderItem = useCallback((item) => /* 使用 'close' 和 'item' */, []); return <FirstChild> {({ close }) => <FlatList renderItem={renderItem} /> </FirstChild> }
问题:close是FirstChild通过render props传递的,在Parent组件顶层无法访问。
第三种尝试(仍会创建新闭包)
const Parent = () => { // 问题:缓存的函数不对,renderItem仍会创建新闭包,Parent重渲染时FlatList也会跟着重渲染 const getRenderItemFunction = useCallback( (close) => (item) => /* 使用 'close' 和 'item' */, [] ); return <FirstChild> {({ close }) => <FlatList renderItem={(item) => getRenderItemFunction(close)(item)} /> </FirstChild> }
问题:传给FlatList的renderItem是每次渲染都重新创建的匿名函数,导致FlatList认为props变化而重渲染。
方法1:拆分出独立子组件
把依赖close的部分拆成单独组件,这样就能在组件顶层合法使用useCallback:
// 拆分出的子组件 const ListComponent = ({ close }) => { const renderItem = useCallback((item) => { // 在这里使用close和item }, [close]); return <FlatList renderItem={renderItem} />; }; // 父组件 const Parent = () => { return <FirstChild> {({ close }) => <ListComponent close={close} />} </FirstChild> }
如果close是稳定的函数,可以给ListComponent加上React.memo,避免Parent重渲染时触发不必要的更新:
const ListComponent = React.memo(({ close }) => { const renderItem = useCallback((item) => { // 使用close和item }, [close]); return <FlatList renderItem={renderItem} />; });
方法2:用useRef保存close,配合useCallback
如果不想拆分组件,可以用useRef存储最新的close引用,这样renderItem的依赖数组不需要包含close,同时能访问到最新的close:
const Parent = () => { const closeRef = useRef(null); const renderItem = useCallback((item) => { // 使用 closeRef.current 代替 close if (closeRef.current) { // 执行close相关逻辑 } }, []); return <FirstChild> {({ close }) => { // 每次render时更新ref的current值 closeRef.current = close; return <FlatList renderItem={renderItem} />; }} </FirstChild> }
这种方式要注意:如果close是会频繁变化的函数,需确保closeRef.current始终是最新值,同时renderItem只会创建一次,避免FlatList不必要重渲染。
方法3:优化第三种尝试的写法
第三种尝试的问题是传给renderItem的是匿名函数,我们可以直接把getRenderItemFunction(close)赋值给renderItem,只要close不变,这个函数引用就不会变:
const Parent = () => { const getRenderItemFunction = useCallback( (close) => (item) => { // 使用close和item }, [] ); return <FirstChild> {({ close }) => { // 直接把返回的函数赋值给renderItem,只要close不变,函数引用就不变 const renderItem = getRenderItemFunction(close); return <FlatList renderItem={renderItem} />; }} </FirstChild> }
这里getRenderItemFunction是缓存的,当close稳定时(比如FirstChild内部用useCallback包裹过close),renderItem的引用不会变化,FlatList就不会因为renderItem变化而重渲染。
内容的提问来源于stack exchange,提问作者Ted van Gageldonk

