我正在尝试使用新的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);
});
答案 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)
Suspense
和lazy
的Fallback组件动画@Akrom Sprinter在快速加载时间的情况下提供了一个很好的解决方案,因为它可以隐藏回退微调器并避免总体延迟。这是OP请求的更复杂动画的扩展:
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}}在这里使用)。
这是可能的,但是需要包装。在卸载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返回三个值:
hasImportFinished
(boolean
)→如果true
,Fallback
可以开始淡出动画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以更好地查看加载状态。