bis*_*ack 3 javascript reactjs next.js react-suspense
通常,当我返回依赖于获取的数据的组件(我使用 nextjs 13)时,我有条件地渲染元素以确保值可用:
表组件:
export const Table = ({ ...props }) => {
const [tableEvents, setTableEvents] = useState(null);
useEffect(() => {
fetchAndSetTableEvents();
}, []);
async function fetchAndSetTableEvents() {
const fetchedEvents = await fetch(* url and params here *)
if (fetchedEvents && fetchedEvents) {
setTableEvents(fetchedEvents);
} else {
setTableEvents(null);
}
}
return (
<React.Fragment>
<div>
{tableEvents ? tableEvents[0].title : null}
</div>
</React.Fragment>
)
};
Run Code Online (Sandbox Code Playgroud)
如果我尝试使用Suspense从父组件加载 TableComponent ,它会加载但不会在加载之前显示回退:
<Suspense fallback={<div>Loading Message...</div>}>
<TableComponent />
</Suspense>
Run Code Online (Sandbox Code Playgroud)
但是,如果我删除TableComponent中的条件渲染并仅指定变量,则在尝试加载组件时回退会正确显示:
return (
<React.Fragment>
<div>
{tableEvents[0].title}
</div>
</React.Fragment>
)
Run Code Online (Sandbox Code Playgroud)
但它最终无法将组件加载为tableEvents最初为空,并且每次获取都会有所不同,因此它无法具有可预测的键。
React文档展示了一个像这样的简单示例。
有条件地返回渲染也会返回组件,但无法显示悬念回退
if (tableEvents) {
return (
<React.Fragment>
<div>
{tableEvents[0].event_title}
</div>
</React.Fragment>
)
}
Run Code Online (Sandbox Code Playgroud)
如何获取和返回组件中的值(可能存在也可能不存在),这些值满足加载时显示的 Suspense 回退条件。我假设它以一种我正在阻止但找不到解决方法的方式依赖于 Promise。
She*_*aff 11
要Suspense被触发,其中一个子级必须throw是Promise. 此功能更针对库开发人员,但您仍然可以尝试自己实现一些功能。
基本思想很简单,这是伪代码
function ComponentWithLoad() {
const promise = fetch('/url') // create a promise
if (promise.pending) { // as long as it's not resolved
throw promise // throw the promise
}
// otherwise, promise is resolved, it's a normal component
return (
<p>{promise.data}</p>
)
}
Run Code Online (Sandbox Code Playgroud)
当Suspense抛出边界时Promise,它将等待它,并在承诺解决时重新渲染组件。就这样。
但现在我们有两个问题:
fetch实际上会创建一个新的承诺,它将再次抛出,我们将永远循环......解决这两个问题的方法是找到一种方法将 Promise 存储在边界之外Suspense(并且很可能完全存储在 React 之外)。
async首先,让我们为任何 Promise 编写一个包装器,以允许我们获取其状态(待处理、已解决、已拒绝)或其已解决的数据。
const promises = new WeakMap()
function wrapPromise(promise) {
const meta = promises.get(promise) || {}
// for any new promise
if (!meta.status) {
meta.status = 'pending' // set it as pending
promise.then((data) => { // when resolved, store the data
meta.status = 'resolved'
meta.data = data
})
promise.catch((error) => { // when rejected store the error
meta.status = 'rejected'
meta.error = error
})
promises.set(promise, meta)
}
if (meta.status === 'pending') { // if still pending, throw promise to Suspense
throw promise
}
if (meta.status === 'error') { // if error, throw error to ErrorBoundary
throw new Error(meta.error)
}
return meta.data // otherwise, return resolved data
}
Run Code Online (Sandbox Code Playgroud)
通过在每次渲染时调用此函数,我们将能够获取 Promise 的数据,而无需任何async. 然后 ReactSuspense的工作就是在需要时重新渲染。这就是它的作用。
Promise那么我们只需要把我们的承诺存储在边界之外Suspense。最简单的例子是在父级中声明它,但理想的解决方案(避免在父级本身重新渲染时创建新的 Promise)是将其存储在 React 本身之外。
export default function App() {
// create a promise *outside* of the Suspense boundary
const promise = fetch('/url').then(r => r.json())
// Suspense will try to render its children, if rendering throws a promise, it'll try again when that promise resolves
return (
<Suspense fallback={<div>Loading...</div>}>
{/* we pass the promise to our suspended component so it's always the same `Promise` every time it re-renders */}
<ComponentWithLoad promise={promise} />
</Suspense>
)
}
function ComponentWithLoad({promise}) {
// using the wrapper we declared above, it will
// - throw a Promise if it's still pending
// - return synchronously the result of our promise otherwise
const data = wrapPromise(promise)
// we now have access to our fetched data without ever using `async`
return <p>{data}</p>
}
Run Code Online (Sandbox Code Playgroud)
WeakMap在 a 和有关此承诺的一些元数据(状态、返回的数据等)之间进行映射非常理想promise,因为一旦其promise本身在任何地方都没有被引用,元数据就可用于垃圾收集Suspense边界的任何组件都会抛出一个承诺),但在每次“尝试”渲染后,它将被 React 卸载。这意味着您不能使用 auseState或 auseRef来保留承诺或其状态。Map在端点之间建立一个端点并Promise获取该端点一样简单,并且只会随着重新获取、缓存控制、标头、请求参数而变得复杂……这就是为什么我的示例只创建一个简单的承诺一次。使用时Suspense,Suspense 节点内的任何树都不会被渲染,但其中任何树仍会抛出 Promise。如果您需要同时渲染某些内容,这就是fallbackprop 的用途。
它确实需要我们改变对组件细分的思考方式