React suspense/lazy delay?

后端 未结 5 1666
醉话见心
醉话见心 2020-12-05 18:13

I am trying to use the new React Lazy and Suspense to create a fallback loading component. This works great, but the fallback is showing only a few ms. Is there a way to add

5条回答
  •  情深已故
    2020-12-05 18:50

    Fallback component animations with Suspense and lazy

    @Akrom Sprinter has a good solution in case of fast load times, as it hides the fallback spinner and avoids overall delay. Here is an extension for more complex animations requested by OP:

    1. Simple variant: fade-in + delayed display

    const App = () => {
      const [isEnabled, setEnabled] = React.useState(false);
      return (
        
    }> {isEnabled && }
    ); }; const Fallback = () => { const containerRef = React.useRef(); return (

    ); }; /* 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: () =>

    Hello Home!

    }); } // add some async delay for illustration purposes function fakeDelay(ms) { return promise => promise.then( data => new Promise(resolve => { setTimeout(() => resolve(data), ms); }) ); } ReactDOM.render(, 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);
      }
    }
    
    
    
    

    You just add some @keyframes animations to Fallback component, and delay its display either by setTimeout and a state flag, or by pure CSS (animation-fill-mode and -delay used here).

    2. Complex variant: fade-in and out + delayed display

    This is possible, but needs a wrapper. We don't have a direct API for Suspense to wait for a fade out animation, before the Fallback component is unmounted.

    Let's create a custom useSuspenseAnimation Hook, that delays the promise given to React.lazy long enough, so that our ending animation is fully visible:

    // 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 (
        
    }> {isEnabled && }
    ); }; 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 (

    ); }; /* 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: () =>

    Hello Home!

    }); } // 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(, 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);
      }
    }
    
    
    
    

    Key points for complex variant

    1.) useSuspenseAnimation Hook returns three values:

    • hasImportFinished (boolean) → if true, Fallback can start its fade out animation
    • enableComponent (callback) → invoke it to unmount Fallback, when animation is done.
    • DeferredComponent → extended lazy Component loaded by dynamic import

    2.) Listen to the animationend DOM event, so we know when animation has ended.

提交回复
热议问题