useLoopCallback -- 在循环内创建的组件的 useCallback 钩子 [英] useLoopCallback -- useCallback hook for components created inside a loop

查看:37
本文介绍了useLoopCallback -- 在循环内创建的组件的 useCallback 钩子的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想开始讨论创建回调的推荐方法,这些回调接收来自在循环内创建的组件的参数.

例如,如果我要填充将具有删除"选项的项目列表按钮,我想要onDeleteItem"回调以知道要删除的项目的索引.所以是这样的:

 const onDeleteItem = useCallback(index => () => {setList(list.slice(0, index).concat(list.slice(index + 1)));}, [列表]);返回 (<div>{list.map((item, index) =><div><span>{item}</span><按钮类型=按钮"onClick={onDeleteItem(index)}>Delete</button>

)}

);

但这样做的问题是 onDeleteItem 总是会向 onClick 处理程序返回一个新函数,导致按钮重新呈现,即使列表没有改变.所以它违背了 useCallback 的目的.

我想出了我自己的钩子,我称之为useLoopCallback,它通过记住主回调以及循环参数映射到他们自己的回调来解决这个问题:

import React, {useCallback, useMemo} from react";导出函数 useLoopCallback(代码,依赖项){const callback = useCallback(code, dependencies);const loopCallbacks = useMemo(() => ({map: new Map(), callback}), [callback]);返回 useCallback(loopParam => {让 loopCallback = loopCallbacks.map.get(loopParam);如果(!loopCallback){loopCallback = (...otherParams) =>loopCallbacks.callback(loopParam, ...otherParams);loopCallbacks.map.set(loopParam, loopCallback);}返回循环回调;}, [打回来]);}

所以现在上面的处理程序看起来像这样:

 const onDeleteItem = useLoopCallback(index => {setList(list.slice(0, index).concat(list.slice(index + 1)));}, [列表]);

这很好用,但现在我想知道这个额外的逻辑是真的让事情变得更快还是只是增加了不必要的开销.任何人都可以提供一些见解吗?

上面的替代方法是将列表项包装在它们自己的组件中.所以是这样的:

function ListItem({key, item, onDeleteItem}) {const onDelete = useCallback(() => {onDeleteItem(key);}, [onDeleteItem, key]);返回 (<div><span>{item}</span><按钮类型=按钮"onClick={onDelete}>Delete</button>

);}导出默认函数 List(...) {...const onDeleteItem = useCallback(index => {setList(list.slice(0, index).concat(list.slice(index + 1)));}, [列表]);返回 (<div>{list.map((item, index) =><ListItem key={index} item={item} onDeleteItem={onDeleteItem}/>)}

);}

解决方案

性能优化总是要付出代价的.有时这个成本低于要优化的操作,有时更高.useCallback 是一个非常类似于useMemo 的钩子,其实你可以把它看作是useMemo 的特化,只能在函数中使用.例如,下面的语句是等价的

const callback = value =>值 * 2const memoizedCb = useCallback(callback, [])const memoizedWithUseMemo = useMemo(() => callback, [])

所以现在关于 useCallback 的每个断言都可以应用于 useMemo.

memoization 的要点是保留旧值的副本,以便在我们获得相同的依赖项时返回,这在您拥有 昂贵的 东西时会很棒计算.看看下面的代码

const Component = ({ items }) =>{const array = items.map(x => x*2)}

在每次render 时,const array 将作为 items 中执行的 map 的结果被创建.因此,您可能会很想执行以下操作

const Component = ({ items }) =>{const array = useMemo(() => items.map(x => x*2), [items])}

现在 items.map(x => x*2) 只会在 items 改变时执行,但值得吗?最简洁的答案是不.通过这样做获得的性能是微不足道的,有时使用 memoization 比每次渲染时只执行函数的成本更高.两个钩子(useCallbackuseMemo)在两个不同的用例中都很有用:

当您需要确保引用类型不会因为浅比较

失败而触发重新渲染时

像这样

const serializedValue = {item: props.item.map(x => ({...x, override: x ? y : z}))}

现在您有理由记忆操作并在每次props.item更改时懒惰地检索serializedValue:

const serializedValue = useMemo(() => ({item: props.item.map(x => ({...x, override: x ? y : z}))}),[道具.项目])

任何其他用例几乎总是值得再次重新计算所有值,React 它非常有效,附加渲染几乎不会导致性能问题.请记住,有时您优化代码的努力可能会适得其反,生成大量额外/不必要的代码,这不会产生太多好处(有时只会导致更多问题).

I'd like to start a discussion on the recommended approach for creating callbacks that take in a parameter from a component created inside a loop.

For example, if I'm populating a list of items that will have a "Delete" button, I want the "onDeleteItem" callback to know the index of the item to delete. So something like this:

  const onDeleteItem = useCallback(index => () => {
    setList(list.slice(0, index).concat(list.slice(index + 1)));
  }, [list]);

  return (
    <div>
      {list.map((item, index) =>
        <div>
          <span>{item}</span>
          <button type="button" onClick={onDeleteItem(index)}>Delete</button>
        </div>
      )}
    </div>
  ); 

But the problem with this is that onDeleteItem will always return a new function to the onClick handler, causing the button to be re-rendered, even when the list hasn't changed. So it defeats the purpose of useCallback.

I came up with my own hook, which I called useLoopCallback, that solves the problem by memoizing the main callback along with a Map of loop params to their own callback:

import React, {useCallback, useMemo} from "react";

export function useLoopCallback(code, dependencies) {
  const callback = useCallback(code, dependencies);
  const loopCallbacks = useMemo(() => ({map: new Map(), callback}), [callback]);

  return useCallback(loopParam => {
    let loopCallback = loopCallbacks.map.get(loopParam);
    if (!loopCallback) {
      loopCallback = (...otherParams) => loopCallbacks.callback(loopParam, ...otherParams);
      loopCallbacks.map.set(loopParam, loopCallback);
    }
    return loopCallback;
  }, [callback]);
}

So now the above handler looks like this:

  const onDeleteItem = useLoopCallback(index => {
    setList(list.slice(0, index).concat(list.slice(index + 1)));
  }, [list]);

This works fine but now I'm wondering if this extra logic is really making things faster or just adding unnecessary overhead. Can anyone please provide some insight?

EDIT: An alternative to the above is to wrap the list items inside their own component. So something like this:

function ListItem({key, item, onDeleteItem}) {
  const onDelete = useCallback(() => {
    onDeleteItem(key);
  }, [onDeleteItem, key]);

  return (
    <div>
      <span>{item}</span>
      <button type="button" onClick={onDelete}>Delete</button>
    </div>
  );
}

export default function List(...) {
  ...

  const onDeleteItem = useCallback(index => {
    setList(list.slice(0, index).concat(list.slice(index + 1)));
  }, [list]);

  return (
    <div>
      {list.map((item, index) =>
        <ListItem key={index} item={item} onDeleteItem={onDeleteItem} />
      )}
    </div>
  ); 
}

解决方案

Performance optimizations always come with a cost. Sometimes this cost is lower than the operation to be optimized, sometimes is higher. useCallback it's a hook very similar to useMemo, actually you can think of it as a specialization of useMemo that can only be used in functions. For example, the bellow statements are equivalents

const callback = value => value * 2

const memoizedCb = useCallback(callback, [])
const memoizedWithUseMemo = useMemo(() => callback, [])

So for now on every assertion about useCallback can be applied to useMemo.

The gist of memoization is to keep copies of old values to return in the event we get the same dependencies, this can be great when you have something that is expensive to compute. Take a look at the following code

const Component = ({ items }) =>{
    const array = items.map(x => x*2)
}

Uppon every render the const array will be created as a result of a map performed in items. So you can feel tempted to do the following

const Component = ({ items }) =>{
    const array = useMemo(() => items.map(x => x*2), [items])
}

Now items.map(x => x*2) will only be executed when items change, but is it worth? The short answer is no. The performance gained by doing this is trivial and sometimes will be more expensive to use memoization than just execute the function each render. Both hooks(useCallback and useMemo) are useful in two distinct use cases:

When you need to ensure that a reference type will not trigger a re render just for failing a shallow comparison

Something like this

const serializedValue = {item: props.item.map(x => ({...x, override: x ? y : z}))}

Now you have a reason to memoized the operation and lazily retrieve the serializedValue everytime props.item changes:

const serializedValue = useMemo(() => ({item: props.item.map(x => ({...x, override: x ? y : z}))}), [props.item])

Any other use case is almost always worth to just re compute all values again, React it's pretty efficient and aditional renders almost never cause performance issues. Keep in mind that sometimes your efforts to optimize your code can go the other way and generate a lot of extra/unecessary code, that won't generate so much benefits (sometimes will only cause more problems).

这篇关于useLoopCallback -- 在循环内创建的组件的 useCallback 钩子的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
其他开发最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆