代码之家  ›  专栏  ›  技术社区  ›  flyingbee

useNameProps”是有条件调用的。在每个组件渲染中必须以完全相同的顺序调用React Hooks

  •  0
  • flyingbee  · 技术社区  · 2 年前

    我有一个React组件,它经常给我错误:useNameProps“是有条件调用的。在每个组件渲染中,React Hooks必须以完全相同的顺序调用。

    迷你repro的完整代码如下:

    import * as React from 'react';
    
    function useNameProps(firstName: string, lastName: string): NameProps {
        return {
            name: firstName + lastName,
        };
    }
    
    function showShowName() {
        return false;
    }
    
    interface NameProps {
        name: string;
    }
    
    function Name(props: NameProps) {
        return <h1>Hello, {'hello'}</h1>;
    }
    
    export default function ComposeBottom() {
        return <div>{showShowName() && <Name {...useNameProps('Frank', 'Lee')} />}</div>;
    }
    

    我的想法是避免在showShowName()返回false时调用useNameProps,因为useNameProp在我的生产代码中非常昂贵,所以我必须让它有条件地运行,但它给了我上面提到的错误。我想知道如何用最佳实践来解决这个问题。

    提前感谢!

    2 回复  |  直到 2 年前
        1
  •  1
  •   Nicholas Tower    2 年前

    钩子必须总是以完全相同的顺序被调用完全相同的次数。这是必要的,因为例如,如果您多次调用 useState ,react必须依赖于调用的顺序来确定应该从的哪个调用返回哪个状态 使用状态 . See rules of hooks .

    至于如何解决这个问题,有几种可能性:

    1. 在您的示例中,您的自定义钩子实际上并不调用任何其他钩子。如果真正的代码也是如此,那么你就会得到一个错误的警报。lint规则使用命名约定来识别什么是钩子,什么不是钩子,函数以 use 被假定为钩子。要解决此问题,请重命名该函数。如, function getNameProps( 。但既然我知道你省略了代码,我想可能是吧 需要成为一个钩子,所以这个解决方案不适合你。

    2. 如果它不贵,那么每次都打电话给它。你说的 昂贵,所以这个解决方案不适合您,但我包括它,因为它是这个错误消息的最常见的解决方案(因为大多数钩子都是轻量级的)

    export default function ComposeBottom() {
      const nameProps = useNameProps('Frank', 'Lee');
      return <div>{showShowName() && <Name {...nameProps} />}</div>;
    }
    
    1. 修改 useNameProps 所以你可以把一些东西传给它,告诉它不要做昂贵的计算。例如,也许路过 null 可以告诉它什么都不做。您仍然需要调用任何钩子相同的次数,但您可以跳过其余的。
    function useNameProps(options: UseNameOptions | null): NameProps | null {
      // If there are any hooks you are calling, they must be before you bail out
      const [foo, setFoo] = useState('bar')
      useEffect(() => {
        //...
      }, [])
    
      if (options === null) {
        return null;
      } 
      // else, do expensive stuff
    }
    
    export default function ComposeBottom() {
      const nameProps = useNameProps('Frank', 'Lee');
      return <div>{showShowName() && <Name {...nameProps} />}</div>;
    }
    
    1. 移动的用法 useNameProps 到子组件中,并且在不需要时不要渲染该组件。
    export default function ComposeBottom() {
        return <div>{showShowName() && <Child />}</div>;
    }
    
    function Child() {
      return <Name {...useNameProps('Frank', 'Lee')} />
    }
    
        2
  •  1
  •   CertainPerformance    2 年前

    一种可能的方法是将逻辑移入 setShowName 进入您的 useNameProps hook-然后让现在的单个hook有条件地返回,比如, null 如果之前的逻辑 setShowName 没有实现。

    function useNameProps(firstName: string, lastName: string): NameProps | null {
        if (/* insert logic here */) {
            return null;
        }
        // beginning of expensive logic
        return {
            name: firstName + lastName,
        };
    }
    
    export default function ComposeBottom() {
        const nameProps = useNameProps('Frank', 'Lee');
        return <div>{nameProps && <Name {...nameProps} />}</div>;
    }