简体   繁体   English

动态导入 React Hooks

[英]Dynamic import of react hooks

Can we dynamically import hooks based on the value passed to the component?我们可以根据传递给组件的值动态导入钩子吗?

For eg.例如。

App.js应用程序.js

<BaseComponent isActive />

BaseComponent.js基础组件.js

if(props.isActive) {
  // import useActive (custom Hook)
}

I donot want these(custom hook) files to be imported and increase the size of BaseComponent even when the props contain falsey values.我不希望导入这些(自定义挂钩)文件并增加 BaseComponent 的大小,即使道具包含虚假值也是如此。

You can dynamically import hooks as it is just a function (using require ), but you shouldn't because you can't use hooks inside conditions .您可以动态导入挂钩,因为它只是一个 function (使用require ),但您不应该因为您不能在 conditions 中使用挂钩

See Rules of Hooks钩子规则

Only call Hooks at the top level .在顶层调用 Hooks。 Don't call Hooks inside loops, conditions , or nested functions.不要在循环、条件或嵌套函数中调用 Hooks。

If you want conditionally use a hook, use the condition in its implementation ( look for example at skip option of useQuery hook from Apollo GraphQL Client ).如果您想有条件地使用钩子,请在其实现中使用条件( 例如查看 Apollo GraphQL 客户端的useQuery钩子的skip选项)。

const useActive = (isUsed) => {
  if (isUsed) {
    // logic
  }
}

You should extract the logic inside the useActive hook and dynamically import it instead of dynamically importing the hook since you should not call Hooks inside loops, conditions, or nested functions ., checkout Rules of Hooks :您应该提取useActive钩子内部的逻辑并动态导入它,而不是动态导入钩子,因为您不应该在循环、条件或嵌套函数中调用 Hooks 。检查钩子规则

Let's say your useActive hook was trying to update the document title (in real world, it has to be a big chunk of code that you would consider using dynamic import)假设你的useActive钩子试图更新文档标题(在现实世界中,它必须是你会考虑使用动态导入的一大块代码)

It might be implemented as below:它可能实现如下:

// useActive.js

import { useEffect } from "react";    
export function useActive() {
  useEffect(() => {
    document.title = "(Active) Hello World!";
  }, []);
}

And you tried to use it in the BaseComponent :您尝试在BaseComponent中使用它:

// BaseComponent.js

function BaseComponent({ isActive }) {
  if (isActive) { // <-- call Hooks inside conditions ❌
    import("./useActive").then(({ useActive }) => {
      useActive();
    });
  }
  return <p>Base</p>;
}

Here you violated the rule "don't call Hooks inside conditions" and will get an Invalid hook call.在这里,您违反了“不要在条件内调用 Hooks”的规则,并将收到Invalid hook call. error.错误。

So instead of dynamically import the hook, you can extract the logic inside the hook and dynamically import it:因此,您可以提取钩子内部的逻辑并动态导入它,而不是动态导入钩子:

// updateTitle.js

export function updateTitle() {
  document.title = "(Active) Hello World!"
}

And you do the isActive check inside the hook:然后你在钩子里面做isActive检查:

// BaseComponent.js

function BaseComponent({ isActive }) {
  useEffect(() => {
    if (!isActive) return;

    import("./updateTitle").then(({ updateTitle }) => {
      updateTitle();
    });
  }, [isActive]);

  return <p>Base</p>;
}

It works fine without violating any rules of hooks.它工作正常,不违反任何钩子规则。

I have attached a CodeSandbox for you to play around:我附上了一个 CodeSandbox 供你玩:

编辑 hardcore-wing-p3uvc

To whoever encountered it as well: You can't use Hooks inside dynamically imported components(however, apparently if you does not use hooks even the first example works):对于遇到它的人:您不能在动态导入的组件中使用 Hooks(但是,显然如果您不使用钩子,即使第一个示例也有效):

instead of:代替:

const useDynamicDemoImport = (name) => {
  const [comp, setComp] = useState(null);
  useEffect(() => {
    let resolvedComp = false;
    import(`@site/src/demos/${name}`)
      .then((m) => {
        if (!resolvedComp) {
          resolvedComp = true;
          setComp(m.default);
        }
      })
      .catch(console.error);
    return () => {
      resolvedComp = true;
    };
  }, []);
  return comp;
};

const DemoPreviewer: FC<DemoPreviewerProps> = (props) => {
  comp = useDynamicDemoImport(props.name);
  return (
    <Paper sx={{ position: "relative" }}>
      {comp}
    </Paper>
  );
};

export default DemoPreviewer


use React Lazy instead and render the component later改用 React Lazy 并稍后渲染组件

const useDynamicDemoImport = (name) => {
  const Comp = React.lazy(() => import(`@site/src/demos/${name}`));
  return comp;
};

const RootDemoPreviewer: FC<DemoPreviewerProps> = (props) => {
  console.log("RootDemoPreviewer");
  return (
    <React.Suspense fallback={<div>Loading...</div>}>
      <DemoPreviewer {...props} />
    </React.Suspense>
  );
};

const DemoPreviewer: FC<DemoPreviewerProps> = (props) => {
  const Comp = useDynamicDemoImport(props.name);
  return (
    <Paper sx={{ position: "relative" }}>
      <Comp />
    </Paper>
  );
};

export default RootDemoPreviewer

I agree with the other answers that you shouldn't do this.我同意你不应该这样做的其他答案。 But if you had to, you could create a Higher Order Component that fetches the hook and then passes it down as a prop to a wrapped component.但是,如果必须的话,您可以创建一个高阶组件来获取钩子,然后将其作为 prop 传递给包装组件。 By doing so the wrapped component can use the hook without breaking the rules of hooks, eg from the wrapped component's point of view, the reference to the hook never changes and it gets called everytime the wrapped component renders.通过这样做,被包装组件可以在不破坏钩子规则的情况下使用钩子,例如,从被包装组件的角度来看,对钩子的引用永远不会改变,并且每次被包装组件渲染时都会调用它。 Here is what the code would look like:代码如下所示:

export function withDynamicHook(hookName, importFunc, Component) {
    return (props) => {
        const [hook, setHook] = useState();

        useEffect(() => {
            importFunc().then((mod) => setHook(() => mod[hookName]));
        }, []);

        if (!hook) {
            return null;
        }

        const newProps = { ...props, [hookName]: hook };
        return <Component {...newProps} />;
    };
}

// example of a Component using it:
const MyComponent = ({useMyHook}) => {
    let something = useMyHook();
    console.log(something)
    return <div>myHook returned something, see the console to inspect it </div>
}
const MyComponentWithHook = withDynamicHook('useMyHook', () => import('module-containing-usemyhook'), MyComponent)


声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM