使用动态导入加载React钩子?

时间:2020-07-28 02:55:18

标签: javascript reactjs webpack react-hooks

我正在使用一些初始渲染不需要的第三方React钩子库。例如。 react-use-gesturereact-springreact-hook-form。它们都提供交互性,可以等到呈现UI后再进行。我想在渲染组件后使用Webpack的代码拆分(即import())动态加载这些代码。

但是,由于它本质上是React不支持的条件性钩子,所以我无法将其存根。

我能想到的2种解决方案是:

  1. 以某种方式将钩子提取到组件中并使用合成
  2. 在钩子加载后强制React重建组件

这两种解决方案似乎都是很棘手的,未来的工程师很可能会将其弄乱。有更好的解决方案吗?

2 个答案:

答案 0 :(得分:4)

正如您所说的,使用延迟加载的钩子有两种方法:

  1. 在父组件中加载库,有条件时使用库有条件地渲染组件

类似的东西

let lib
const loadLib = () => {...}

const Component = () => {
  const {...hooks} = lib
  ...
}

const Parent = () => {
  const [loaded, setLoaded] = useState(false)
  useEffect(() => loadComponent().then(() => setLoaded(true)), [])
  return loaded && <Component/>
}

对于每个库来说,这种方法确实有点麻烦,而且需要大量手动工作

  1. 开始使用钩子加载组件,失败,在加载钩子时重建组件

可以在 React.Suspense

的帮助下进行简化
<Suspense fallback={"Loading..."}>
  <ComponentWithLazyHook/>
</Suspense>

挂起类似于错误边界,如下所示:

  1. 组件在渲染过程中(通过 React.lazy 或手动)抛出Promise
  2. 悬而未决的承诺并呈现后备状态
  3. 承诺解决
  4. 暂停重新渲染该组件

Suspense for Data Fetching从实验阶段成熟时,这种方式可能会变得越来越流行。

但是出于我们一次加载一个库并可能缓存结果的目的,简单的数据获取实现可以解决问题

const cache = {}
const errorsCache = {}
// <Suspense> catches the thrown promise
// and rerenders children when promise resolves
export const useSuspense = (importPromise, cacheKey) => {
  const cachedModule = cache[cacheKey]
  // already loaded previously
  if (cachedModule) return cachedModule

  //prevents import() loop on failed imports
  if (errorsCache[cacheKey]) throw errorsCache[cacheKey]

  // gets caught by Suspense
  throw importPromise
    .then((mod) => (cache[cacheKey] = mod))
    .catch((err) => {
      errorsCache[cacheKey] = err
    })
};

const SuspendedComp = () => {
  const { useForm } = useSuspense(import("react-hook-form"), "react-hook-form")
  const { register, handleSubmit, watch, errors } = useForm()
  ...
}

...

<Suspense fallback={null}>
  <SuspendedComp/>
</Suspense>

您可以看到示例实现here

编辑:

在我用codesandbox编写示例时,完全摆脱了依赖关系解析与webpack本地解析不同的行为。

Webpack import() can't handle completely dynamic paths类似于import(importPath)。它必须在静态某处具有import('react-hook-form'),才能在构建时创建块。

因此,我们必须自己编写import('react-hook-form'),并提供importPath = 'react-hook-form'用作缓存键。

我将codesanbox示例更新为适用于webpack的示例,可以在here

中找到旧示例,该示例无法在本地使用

答案 1 :(得分:0)

您是否考虑过对钩子进行拔钩?我们使用了类似于异步加载大型lib的方法,但是它不是一个钩子,所以是YMMV。

// init with stub
let _useDrag = () => undefined;

// load the actual implementation asynchronously
import('react-use-gesture').then(({useDrag}) => _useDrag = useDrag);

export asyncUseDrag = (cb) => _useDrag(cb)