反应悬念/懒惰延迟?

vem*_*und 8 javascript reactjs

我正在尝试使用新的React Lazy和Suspense来创建一个后备加载组件.这很好用,但后备只显示几毫秒.有没有办法添加额外的延迟或最短时间,所以我可以在渲染下一个组件之前显示此组件的动画?

现在懒惰导入

const Home = lazy(() => import("./home"));
const Products = lazy(() => import("./home/products"));
Run Code Online (Sandbox Code Playgroud)

等待组件:

function WaitingComponent(Component) {

    return props => (
      <Suspense fallback={<Loading />}>
            <Component {...props} />
      </Suspense>
    );
}
Run Code Online (Sandbox Code Playgroud)

我可以这样做吗?

const Home = lazy(() => {
  setTimeout(import("./home"), 300);
});
Run Code Online (Sandbox Code Playgroud)

Est*_*ask 19

lazy函数应该{ default: ... }返回由import()具有默认导出的模块返回的对象的promise .setTimeout没有回复承诺,也不能像那样使用.任意承诺可以:

const Home = lazy(() => {
  return new Promise(resolve => {
    setTimeout(() => resolve(import("./home")), 300);
  });
});
Run Code Online (Sandbox Code Playgroud)

如果目标是提供最小延迟,这不是一个好的选择,因为这会导致额外的延迟.

最小延迟是:

const Home = lazy(() => {
  return Promise.all([
    import("./home"),
    new Promise(resolve => setTimeout(resolve, 300))
  ])
  .then(([moduleExports]) => moduleExports);
});
Run Code Online (Sandbox Code Playgroud)

  • @AdiAzarya 它会工作,但不会按要求工作。延迟将是*加载时间加上额外的 300 毫秒*,而不是*加载时间或至少 300 毫秒*。 (9认同)
  • 另一种方法可能是使用 Promise.all([import(...), delayPromise]) ,它并行执行导入和延迟,也更容易理解。 (4认同)

小智 13

正如 loopmode 所提到的,组件回退应该有一个超时。

import React, { useState, useEffect } from 'react'

const DelayedFallback = () => {
  const [show, setShow] = useState(false)
  useEffect(() => {
    let timeout = setTimeout(() => setShow(true), 300)
    return () => {
      clearTimeout(timeout)
    }
  }, [])

  return (
    <>
      {show && <h3>Loading ...</h3>}
    </>
  )
}
export default DelayedFallback
Run Code Online (Sandbox Code Playgroud)

然后只需导入该组件并将其用作后备。

<Suspense fallback={<DelayedFallback />}>
       <LazyComponent  />
</Suspense>
Run Code Online (Sandbox Code Playgroud)


for*_*d04 9

Suspense使用和 的后备组件动画lazy

\n\n

@Akrom Sprinter 在加载时间过快的情况下有一个很好的解决方案,因为它隐藏了后备微调器并避免了整体延迟。这是 OP 请求的更复杂动画的扩展:

\n\n

1.简单变体:淡入+延迟显示

\n\n

\r\n
\r\n
const App = () => {\r\n  const [isEnabled, setEnabled] = React.useState(false);\r\n  return (\r\n    <div>\r\n      <button onClick={() => setEnabled(b => !b)}>Toggle Component</button>\r\n      <React.Suspense fallback={<Fallback />}>\r\n        {isEnabled && <Home />}\r\n      </React.Suspense>\r\n    </div>\r\n  );\r\n};\r\n\r\nconst Fallback = () => {\r\n  const containerRef = React.useRef();\r\n  return (\r\n    <p ref={containerRef} className="fallback-fadein">\r\n      <i className="fa fa-spinner spin" style={{ fontSize: "64px" }} />\r\n    </p>\r\n  );\r\n};\r\n\r\n/*\r\n Technical helpers\r\n*/\r\n\r\nconst Home = React.lazy(() => fakeDelay(2000)(import_("./routes/Home")));\r\n\r\n// import_ is just a stub for the stack snippet; use dynamic import in real code.\r\nfunction import_(path) {\r\n  return Promise.resolve({ default: () => <p>Hello Home!</p> });\r\n}\r\n\r\n// add some async delay for illustration purposes\r\nfunction fakeDelay(ms) {\r\n  return promise =>\r\n    promise.then(\r\n      data =>\r\n        new Promise(resolve => {\r\n          setTimeout(() => resolve(data), ms);\r\n        })\r\n    );\r\n}\r\n\r\nReactDOM.render(<App />, document.getElementById("root"));
Run Code Online (Sandbox Code Playgroud)\r\n
/* Delay showing spinner first, then gradually let it fade in. */\r\n.fallback-fadein {\r\n  visibility: hidden;\r\n  animation: fadein 1.5s;\r\n  animation-fill-mode: forwards;\r\n  animation-delay: 0.5s; /* no spinner flickering for fast load times */\r\n}\r\n\r\n@keyframes fadein {\r\n  from {\r\n    visibility: visible;\r\n    opacity: 0;\r\n  }\r\n  to {\r\n    visibility: visible;\r\n    opacity: 1;\r\n  }\r\n}\r\n\r\n.spin {\r\n  animation: spin 2s infinite linear;\r\n}\r\n\r\n@keyframes spin {\r\n  0% {\r\n    transform: rotate(0deg);\r\n  }\r\n  100% {\r\n    transform: rotate(359deg);\r\n  }\r\n}
Run Code Online (Sandbox Code Playgroud)\r\n
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>\r\n<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>\r\n<link\r\n  rel="stylesheet"\r\n  href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"\r\n/>\r\n<div id="root"></div>
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n\n

您只需向组件添加一些@keyframes动画Fallback,并通过setTimeout状态标志或纯 CSS(animation-fill-mode-delay在此处使用)延迟其显示。

\n\n

2.复杂变体:淡入淡出+延迟显示

\n\n

这是可能的,但需要一个包装器。在卸载组件之前,我们没有直接的 API 来等待Suspense淡出动画。Fallback

\n\n

让我们创建一个自定义useSuspenseAnimationHook,将承诺延迟足够React.lazy长的时间,以便我们的结束动画完全可见:

\n\n
// inside useSuspenseAnimation\nconst DeferredHomeComp = React.lazy(() => Promise.all([\n    import("./routes/Home"), \n    deferred.promise // resolve this promise, when Fallback animation is complete\n  ]).then(([imp]) => imp)\n)\n
Run Code Online (Sandbox Code Playgroud)\n\n

\r\n
\r\n
// inside useSuspenseAnimation\nconst DeferredHomeComp = React.lazy(() => Promise.all([\n    import("./routes/Home"), \n    deferred.promise // resolve this promise, when Fallback animation is complete\n  ]).then(([imp]) => imp)\n)\n
Run Code Online (Sandbox Code Playgroud)\r\n
const App = () => {\r\n  const { DeferredComponent, ...fallbackProps } = useSuspenseAnimation(\r\n    "./routes/Home"\r\n  );\r\n  const [isEnabled, setEnabled] = React.useState(false);\r\n  return (\r\n    <div>\r\n      <button onClick={() => setEnabled(b => !b)}>Toggle Component</button>\r\n      <React.Suspense fallback={<Fallback {...fallbackProps} />}>\r\n        {isEnabled && <DeferredComponent />}\r\n      </React.Suspense>\r\n    </div>\r\n  );\r\n};\r\n\r\nconst Fallback = ({ hasImportFinished, enableComponent }) => {\r\n  const ref = React.useRef();\r\n  React.useEffect(() => {\r\n    const current = ref.current;\r\n    current.addEventListener("animationend", handleAnimationEnd);\r\n    return () => {\r\n      current.removeEventListener("animationend", handleAnimationEnd);\r\n    };\r\n\r\n    function handleAnimationEnd(ev) {\r\n      if (ev.animationName === "fadeout") {\r\n        enableComponent();\r\n      }\r\n    }\r\n  }, [enableComponent]);\r\n\r\n  const classes = hasImportFinished ? "fallback-fadeout" : "fallback-fadein";\r\n\r\n  return (\r\n    <p ref={ref} className={classes}>\r\n      <i className="fa fa-spinner spin" style={{ fontSize: "64px" }} />\r\n    </p>\r\n  );\r\n};\r\n\r\n/* \r\nPossible State transitions: LAZY -> IMPORT_FINISHED -> ENABLED\r\n- LAZY: React suspense hasn\'t been triggered yet.\r\n- IMPORT_FINISHED: dynamic import has completed, now we can trigger animations.\r\n- ENABLED: Deferred component will now be displayed \r\n*/\r\nfunction useSuspenseAnimation(path) {\r\n  const [state, setState] = React.useState(init);\r\n\r\n  const enableComponent = React.useCallback(() => {\r\n    if (state.status === "IMPORT_FINISHED") {\r\n      setState(prev => ({ ...prev, status: "ENABLED" }));\r\n      state.deferred.resolve();\r\n    }\r\n  }, [state]);\r\n\r\n  return {\r\n    hasImportFinished: state.status === "IMPORT_FINISHED",\r\n    DeferredComponent: state.DeferredComponent,\r\n    enableComponent\r\n  };\r\n\r\n  function init() {\r\n    const deferred = deferPromise();\r\n    // component object reference  is kept stable, since it\'s stored in state.\r\n    const DeferredComponent = React.lazy(() =>\r\n      Promise.all([\r\n        // again some fake delay for illustration\r\n        fakeDelay(2000)(import_(path)).then(imp => {\r\n          // triggers re-render, so containing component can react\r\n          setState(prev => ({ ...prev, status: "IMPORT_FINISHED" }));\r\n          return imp;\r\n        }),\r\n        deferred.promise\r\n      ]).then(([imp]) => imp)\r\n    );\r\n\r\n    return {\r\n      status: "LAZY",\r\n      DeferredComponent,\r\n      deferred\r\n    };\r\n  }\r\n}\r\n\r\n/*\r\ntechnical helpers\r\n*/\r\n\r\n// import_ is just a stub for the stack snippet; use dynamic import in real code.\r\nfunction import_(path) {\r\n  return Promise.resolve({ default: () => <p>Hello Home!</p> });\r\n}\r\n\r\n// add some async delay for illustration purposes\r\nfunction fakeDelay(ms) {\r\n  return promise =>\r\n    promise.then(\r\n      data =>\r\n        new Promise(resolve => {\r\n          setTimeout(() => resolve(data), ms);\r\n        })\r\n    );\r\n}\r\n\r\nfunction deferPromise() {\r\n  let resolve;\r\n  const promise = new Promise(_resolve => {\r\n    resolve = _resolve;\r\n  });\r\n  return { resolve, promise };\r\n}\r\n\r\nReactDOM.render(<App />, document.getElementById("root"));
Run Code Online (Sandbox Code Playgroud)\r\n
/* Delay showing spinner first, then gradually let it fade in. */\r\n.fallback-fadein {\r\n  visibility: hidden;\r\n  animation: fadein 1.5s;\r\n  animation-fill-mode: forwards;\r\n  animation-delay: 0.5s; /* no spinner flickering for fast load times */\r\n}\r\n\r\n@keyframes fadein {\r\n  from {\r\n    visibility: visible;\r\n    opacity: 0;\r\n  }\r\n  to {\r\n    visibility: visible;\r\n    opacity: 1;\r\n  }\r\n}\r\n\r\n.fallback-fadeout {\r\n  animation: fadeout 1s;\r\n  animation-fill-mode: forwards;\r\n}\r\n\r\n@keyframes fadeout {\r\n  from {\r\n    opacity: 1;\r\n  }\r\n  to {\r\n    opacity: 0;\r\n  }\r\n}\r\n\r\n.spin {\r\n  animation: spin 2s infinite linear;\r\n}\r\n\r\n@keyframes spin {\r\n  0% {\r\n    transform: rotate(0deg);\r\n  }\r\n  100% {\r\n    transform: rotate(359deg);\r\n  }\r\n}
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n\n

复杂变体的要点

\n\n

1.) useSuspenseAnimationHook 返回三个值:

\n\n
    \n
  • hasImportFinished( boolean) \xe2\x86\x92 如果true,则Fallback可以开始其淡出动画
  • \n
  • enableComponent(回调)\xe2\x86\x92 调用它来卸载Fallback在动画完成时调用它来 unmount 。
  • \n
  • DeferredComponent\xe2\x86\x92 动态加载的扩展惰性组件import
  • \n
\n\n

2.) 听animationendDOM 事件,以便我们知道动画何时结束。

\n