反应悬念/延迟延迟?

时间:2019-01-12 11:07:52

标签: javascript reactjs

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

立即延迟导入

const Home = lazy(() => import("./home"));
const Products = lazy(() => import("./home/products"));

等待组件:

function WaitingComponent(Component) {

    return props => (
      <Suspense fallback={<Loading />}>
            <Component {...props} />
      </Suspense>
    );
}

我可以做这样的事情吗?

const Home = lazy(() => {
  setTimeout(import("./home"), 300);
});

6 个答案:

答案 0 :(得分:11)

const serialized = JSON.stringify(child); // Human inheritance gets lost, its a plain object now const child2 = Object.assign(Object.create(Human.prototype), JSON.parse(serialized)); 函数应该返回lazy对象的承诺,该承诺由具有默认导出功能的模块的{ default: ... }返回。 import()不返回承诺,因此不能这样使用。虽然任意承诺可以:

setTimeout

如果目标是提供最小延迟,则这不是一个好选择,因为这会导致附加延迟。

最小延迟是:

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

答案 1 :(得分:2)

具有Suspenselazy的Fallback组件动画

@Akrom Sprinter在快速加载时间的情况下提供了一个很好的解决方案,因为它可以隐藏回退微调器并避免总体延迟。这是OP请求的更复杂动画的扩展:

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

const App = () => {
  const [isEnabled, setEnabled] = React.useState(false);
  return (
    <div>
      <button onClick={() => setEnabled(b => !b)}>Toggle Component</button>
      <React.Suspense fallback={<Fallback />}>
        {isEnabled && <Home />}
      </React.Suspense>
    </div>
  );
};

const Fallback = () => {
  const containerRef = React.useRef();
  return (
    <p ref={containerRef} className="fallback-fadein">
      <i className="fa fa-spinner spin" style={{ fontSize: "64px" }} />
    </p>
  );
};

/*
 Technical helpers
*/

const Home = React.lazy(() => fakeDelay(2000)(import_("./routes/Home")));

// import_ is just a stub for the stack snippet; use dynamic import in real code.
function import_(path) {
  return Promise.resolve({ default: () => <p>Hello Home!</p> });
}

// add some async delay for illustration purposes
function fakeDelay(ms) {
  return promise =>
    promise.then(
      data =>
        new Promise(resolve => {
          setTimeout(() => resolve(data), ms);
        })
    );
}

ReactDOM.render(<App />, document.getElementById("root"));
/* Delay showing spinner first, then gradually let it fade in. */
.fallback-fadein {
  visibility: hidden;
  animation: fadein 1.5s;
  animation-fill-mode: forwards;
  animation-delay: 0.5s; /* no spinner flickering for fast load times */
}

@keyframes fadein {
  from {
    visibility: visible;
    opacity: 0;
  }
  to {
    visibility: visible;
    opacity: 1;
  }
}

.spin {
  animation: spin 2s infinite linear;
}

@keyframes spin {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(359deg);
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<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>
<link
  rel="stylesheet"
  href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
/>
<div id="root"></div>

您只需向@keyframes组件中添加一些Fallback动画,然后通过setTimeout和一个状态标志,或者通过纯CSS(animation-fill-mode和{{ 3}}在这里使用)。

2。复杂的变体:淡入和淡出 +延迟显示

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

让我们创建一个自定义的useSuspenseAnimation钩子,该钩子将对React.lazy的承诺延迟足够长的时间,以使我们的结尾动画完全可见:

// inside useSuspenseAnimation
const DeferredHomeComp = React.lazy(() => Promise.all([
    import("./routes/Home"), 
    deferred.promise // resolve this promise, when Fallback animation is complete
  ]).then(([imp]) => imp)
)

const App = () => {
  const { DeferredComponent, ...fallbackProps } = useSuspenseAnimation(
    "./routes/Home"
  );
  const [isEnabled, setEnabled] = React.useState(false);
  return (
    <div>
      <button onClick={() => setEnabled(b => !b)}>Toggle Component</button>
      <React.Suspense fallback={<Fallback {...fallbackProps} />}>
        {isEnabled && <DeferredComponent />}
      </React.Suspense>
    </div>
  );
};

const Fallback = ({ hasImportFinished, enableComponent }) => {
  const ref = React.useRef();
  React.useEffect(() => {
    const current = ref.current;
    current.addEventListener("animationend", handleAnimationEnd);
    return () => {
      current.removeEventListener("animationend", handleAnimationEnd);
    };

    function handleAnimationEnd(ev) {
      if (ev.animationName === "fadeout") {
        enableComponent();
      }
    }
  }, [enableComponent]);

  const classes = hasImportFinished ? "fallback-fadeout" : "fallback-fadein";

  return (
    <p ref={ref} className={classes}>
      <i className="fa fa-spinner spin" style={{ fontSize: "64px" }} />
    </p>
  );
};

/* 
Possible State transitions: LAZY -> IMPORT_FINISHED -> ENABLED
- LAZY: React suspense hasn't been triggered yet.
- IMPORT_FINISHED: dynamic import has completed, now we can trigger animations.
- ENABLED: Deferred component will now be displayed 
*/
function useSuspenseAnimation(path) {
  const [state, setState] = React.useState(init);

  const enableComponent = React.useCallback(() => {
    if (state.status === "IMPORT_FINISHED") {
      setState(prev => ({ ...prev, status: "ENABLED" }));
      state.deferred.resolve();
    }
  }, [state]);

  return {
    hasImportFinished: state.status === "IMPORT_FINISHED",
    DeferredComponent: state.DeferredComponent,
    enableComponent
  };

  function init() {
    const deferred = deferPromise();
    // component object reference  is kept stable, since it's stored in state.
    const DeferredComponent = React.lazy(() =>
      Promise.all([
        // again some fake delay for illustration
        fakeDelay(2000)(import_(path)).then(imp => {
          // triggers re-render, so containing component can react
          setState(prev => ({ ...prev, status: "IMPORT_FINISHED" }));
          return imp;
        }),
        deferred.promise
      ]).then(([imp]) => imp)
    );

    return {
      status: "LAZY",
      DeferredComponent,
      deferred
    };
  }
}

/*
technical helpers
*/

// import_ is just a stub for the stack snippet; use dynamic import in real code.
function import_(path) {
  return Promise.resolve({ default: () => <p>Hello Home!</p> });
}

// add some async delay for illustration purposes
function fakeDelay(ms) {
  return promise =>
    promise.then(
      data =>
        new Promise(resolve => {
          setTimeout(() => resolve(data), ms);
        })
    );
}

function deferPromise() {
  let resolve;
  const promise = new Promise(_resolve => {
    resolve = _resolve;
  });
  return { resolve, promise };
}

ReactDOM.render(<App />, document.getElementById("root"));
/* Delay showing spinner first, then gradually let it fade in. */
.fallback-fadein {
  visibility: hidden;
  animation: fadein 1.5s;
  animation-fill-mode: forwards;
  animation-delay: 0.5s; /* no spinner flickering for fast load times */
}

@keyframes fadein {
  from {
    visibility: visible;
    opacity: 0;
  }
  to {
    visibility: visible;
    opacity: 1;
  }
}

.fallback-fadeout {
  animation: fadeout 1s;
  animation-fill-mode: forwards;
}

@keyframes fadeout {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}

.spin {
  animation: spin 2s infinite linear;
}

@keyframes spin {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(359deg);
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<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>
<link
  rel="stylesheet"
  href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
/>
<div id="root"></div>

复杂变体的要点

1。)useSuspenseAnimation Hook返回三个值:

  • hasImportFinishedboolean)→如果trueFallback可以开始淡出动画
  • enableComponent(回调)→完成动画后,调用它以卸载Fallback
  • DeferredComponent→动态import加载的扩展惰性组件

2。)听-delay DOM事件,所以我们知道动画何时结束。

答案 2 :(得分:1)

如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

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

<Suspense fallback={<DelayedFallback />}>
       <LazyComponent  />
</Suspense>

答案 3 :(得分:0)

您应该创建一个本身具有超时和可见状态的后备组件。最初,您将visible设置为false。 安装后备组件时,应将setTimeout设置为打开可见状态标志。 请确保您的组件仍处于挂载状态,或者清除卸载组件时的超时。 最后,如果可见状态为false,请在后备组件中将其呈现为null(例如,仅阻止/半透明的叠加层,但不旋转/动画)

然后使用此类组件,例如<正在加载叠加/>,作为备用。

答案 4 :(得分:0)

此外,我在使用TypeScript和React时也遇到了类似的问题。 因此,我也必须尊重打字稿编译器,并且我继续采用了无限延迟且没有打字稿抱怨的方法。 没解决的承诺?

const LazyRoute = lazy(() => {
  return new Promise(resolve => () =>
    import(
      '../../abc'
    ).then(x => x e => null as never),
  );
});

答案 5 :(得分:-1)

在开发人员控制台中打开“网络”标签,您将能够限制网络速度。在chrome上,它将出现在最初设置为Online的下拉列表中。将其设置为慢速3G以更好地查看加载状态。