如何解决昂贵的自定义钩子? [英] How to work around expensive custom hooks?

查看:44
本文介绍了如何解决昂贵的自定义钩子?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

众所周知,规则是:

<块引用>

仅在顶层调用挂钩.不要在循环、条件或嵌套函数中调用 Hook.

所以我的问题是如何使用和设计昂贵的定制挂钩?

给定这个钩子:

const useExpensiveHook = () =>{//一些使用其他内置钩子的代码...const 值 = computeExpensiveValue();//还有一些代码....返回值;}

如果该规则不存在,我的客户端代码将是:

const myComponent = ({isSuperFeatureEnabled}) =>{让价值;如果(isSuperFeatureEnabled){值 = useExpensiveHook();}返回<div>{value}</div>;}

我想出的解决方案是让钩子知道它应该退出,就像这样,使用标志:

const useExpensiveHook = ({enabled}) =>{//一些使用其他内置钩子的代码...让价值;如果(启用){值 = 计算费用值();}别的 {值 = 未定义;}//还有一些代码....返回值;};

和客户端代码:

const myComponent = ({isSuperFeatureEnabled}) =>{const value = useExpensiveHook({enabled : isSuperFeatureEnabled});返回<div>{value}</div>;}

它正在将标志传递给昂贵的钩子,这是处理条件钩子的正确方法吗?还有哪些选择?

解决方案

在原始示例中,昂贵的是钩子初始值,而不是钩子本身,computeExpensiveValue 可以有条件地调用:

const [value, setValue] = useState(enabled ? computeExpensiveValue() : undefined);

在当前列出的例子中,useExpensiveHook 不是一个钩子而是一些函数;它不使用 React 钩子.

引用规则的目的是让内置钩子无条件地被调用,因为钩子的状态是由它们被调用的顺序决定的:

if (flipCoin())var [foo] = useState('foo');var [bar] = useState('bar');

如果 useState('foo') 在下一个组件渲染时没有被调用,useState('bar') 将成为第一个 useState> 钩子被调用并被认为是 foo 状态,而第二个 useState 缺失,这种不一致会在渲染器中触发错误.

如果保证保留钩子调用的顺序,使用条件是可以接受的,但这在实践中很少可行.即使有像 if (process.env.NODE_ENV === 'development') 这样看似恒定的条件,它也可能在运行时的某些情况下发生变化并导致难以调试的问题.>

正确:

useEffect(() => {如果(变化条件)计算昂贵的价值();});

不正确:

if (variingCondition)useEffect(() => {计算昂贵的价值();});

此规则仅适用于内置钩子和直接或间接调用它们的函数(所谓的自定义钩子).只要 computeExpensiveValue 在内部不使用内置钩子,就可以有条件地调用它,如正确"示例所示.

如果组件需要根据 prop 标志有条件地应用第三方钩子,应该通过将其限制为初始 prop 值来保证条件不会随时间变化:

const Component = ({cost, optionalValue }) =>{const isExpensive = useMemo(() => 昂贵的,[]);如果(是昂贵的)optionalValue = useExpensiveHook();返回 ...}

这样<组件昂贵={flipCoin()}/>不会破坏规则,只会滥用组件.

既然在使用<组件昂贵/>时就应该知道是否需要昂贵的钩子,所以更简洁的方法是将此功能组合在高阶组件中并使用不同的组件取决于需要哪一个:

const withExpensive = Comp =>道具 =>{const optionalValue = useExpensiveHook();return <Comp optionalValue={optionalValue} ...props/>;}const 组件 = ({ optionalValue }) =>{返回 ...}const ExpensiveComponent = withExpensive(Component);

As we know, the rule is:

Only Call Hooks at the Top Level. Don’t call Hooks inside loops, conditions, or nested functions.

So my questions is how to use and design a custom hook that is expensive?

Given this hook:

const useExpensiveHook = () => {
    // some code that uses other built-in hooks...
    const value = computeExpensiveValue();
    // some more code....
    return value;
}

If that rule did not exist my client code would be:

const myComponent = ({isSuperFeatureEnabled}) => {
   let value;
   if (isSuperFeatureEnabled){
      value = useExpensiveHook();
   }

   return <div>{value}</div>;
}

The solution I came up with is to let the hook know that it should bail out, like this, using a flag:

const useExpensiveHook = ({enabled}) => {
    // some code that uses other built-in hooks...
    let value;
      if(enabled) {
          value = computeExpensiveValue();
      }
      else {
          value = undefined;
      }
    // some more code....
    return value;
};

and the client code:

const myComponent = ({isSuperFeatureEnabled}) => {
   const value = useExpensiveHook({enabled : isSuperFeatureEnabled});
   return <div>{value}</div>;
}

It is passing a flag to expensive hooks the right way to handle conditional hooks? What are the other options?

解决方案

In original example it is hook initial value that is expensive, not a hook itself, computeExpensiveValue can be conditionally called:

const [value, setValue] = useState(enabled ? computeExpensiveValue() : undefined);

In currently listed example useExpensiveHook is not a hook but some function; it doesn't use React hooks.

The purpose of quoted rule is to make built-in hooks called unconditionally because the state of hooks is determined by the order in which they are called:

if (flipCoin())
  var [foo] = useState('foo');

var [bar] = useState('bar');

In case useState('foo') isn't called on next component render, useState('bar') becomes the first useState hook to be called and considered foo state, while second useState is missing, this inconsistency triggers an error in a renderer.

If it were guaranteed that the order of hook calls is preserved, it would be acceptable to use conditions but this is rarely feasible in practice. Even if there's seemingly constant condition like if (process.env.NODE_ENV === 'development'), it could change under some circumstances at runtime and result in said problems that are hard to debug.

Correct:

useEffect(() => {
  if (varyingCondition)
    computeExpensiveValue();
});

Incorrect:

if (varyingCondition)
  useEffect(() => {
    computeExpensiveValue();
  });

This rule applies only to built-in hooks and functions that call them directly or indirectly (so-called custom hooks). As long as computeExpensiveValue doesn't use built-in hooks internally, it can be conditionally called, as 'correct' example shows.

In case a component needs to conditionally apply third-party hook depending on prop flag, it should be guaranteed that the condition won't change with time by restricting it to be initial prop value:

const Component = ({ expensive, optionalValue }) => {
  const isExpensive = useMemo(() => expensive, []);
  if (isExpensive)
    optionalValue = useExpensiveHook();
  return ...
}

This way <Component expensive={flipCoin()} /> won't break the rule but just misuse the component.

Since it should be known if expensive hook is needed at the time when <Component expensive/> is used, a cleaner way is to compose this functionality in higher-order component and use different components depending on which one is needed:

const withExpensive = Comp => props => {
  const optionalValue = useExpensiveHook();
  return <Comp optionalValue={optionalValue} ...props />;
}

const Component = ({ optionalValue }) => {
  return ...
}

const ExpensiveComponent = withExpensive(Component);

这篇关于如何解决昂贵的自定义钩子?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆