我试图理解以下代码为什么导致无限循环,从而使页面完全无响应。
function lazy(importTaskFactory) {
const f = function (props) {
console.log("enter");
debugger;
const [value, setValue] = useState(null);
const memoizedImportTask = useMemo(importTaskFactory, [importTaskFactory]);
useEffect(() => {
console.log("useEffect");
debugger;
memoizedImportTask
.then(m => {
console.log("settled");
debugger;
setValue(m);
})
.catch(err => {
console.error(f.displayName, err);
});
}, [memoizedImportTask]);
if (value) {
if (!value.default) {
throw new Error(`${importTaskFactory.toString()} - default export is required`);
}
return React.createElement(value.default, props);
}
console.log("throw");
debugger;
throw memoizedImportTask; // Suspense API
console.log("throw memoizedImportTask!", memoizedImportTask);
return null;
};
f.displayName = `lazy(${importTaskFactory.toString()})`;
return f;
}
此代码完成的唯一操作是记录enter
,throw
,enter
,throw
,enter
,throw
等。 useEffect
挂钩永远不会被调用,因此永远不会设置该值。尽管承诺已经解决(解决),但这仍然是可行的。为什么在这种特殊情况下反应以某种同步方式运行?
答案 0 :(得分:2)
我想出了问题的答案。尽管我喜欢React.js钩子编写组件的样式,但它们确实表现出与类组件有些不同的行为。
如果我们跳过useEffect
钩子并将所有内容移到useMemo
钩子中(这样我们就可以确定在诺言履行时我们确实设置了值)。它实际上什么也没做。这是因为当组件抛出异常时,它的挂钩被重置,这使得useState
挂钩无用!另外,在渲染时抛出错误的组件不会调用其useEffect
钩子,可能是出于相同的原因(该组件从未提交)。
因此,即使该方法能够解决承诺,在设置值和兑现承诺之间也会失去价值。但是,我们可以通过自己实现状态和记忆,而不是一开始就承诺(稍后会详细介绍),来解决这些限制。
export interface LazyComponent<P = {}> {
getComponent(): React.ComponentType<P> | undefined
importComponent(): Promise<React.ComponentType<P>>
}
type LazilyInstantiatedComponentModule<P> = {
default: React.ComponentType<P>
}
export function lazy<P = {}>(
importTaskFactory: () => Promise<LazilyInstantiatedComponentModule<P>>,
fallback?: React.ReactElement
): React.FunctionComponent<P> & LazyComponent<P> {
if (importTaskFactory === undefined) {
throw new TypeError("importTaskFactory is undefined")
}
let cached: LazilyInstantiatedComponentModule<P> | undefined
let importTask: Promise<LazilyInstantiatedComponentModule<P>> | undefined
function importComponent() {
if (!importTask) {
importTask = importTaskFactory().then(m => {
if (!(typeof m === "object" && typeof m.default === "function")) {
throw new Error(
`${importTaskFactory.toString()} - missing default export`
)
}
return (cached = m)
})
}
return importTask
}
function lazy(props: P) {
const [value, setValue] = useState(cached)
useEffect(() => {
if (!value) {
importComponent().then(setValue)
}
return undefined
})
if (value) {
return React.createElement(value.default, props)
}
return fallback || null
}
lazy.getComponent = () => (cached ? cached.default : undefined)
lazy.importComponent = () => importComponent().then(m => m.default)
lazy.displayName = `lazy(${importTaskFactory.toString()})`
return lazy
}
之所以可行,是因为我们的lazy
加载程序是一个高阶组件。您可能想知道为什么甚至在有lazy
的情况下完全实现React.lazy
的原因,但这与React.lazy
需要一个Suspense
组件的事实有关。 ReactDOMServer
不支持。
此处给出的答案反映了我自React 15以来如何解决此限制。公共API getComponent
和importComponent
可用于确保在您设置cached
值之前达到渲染。从而避免任何闪烁(即呈现后备或null
)。