Are*_*yan 5 javascript lazy-loading reactjs webpack server-side-rendering
我有一个项目,仅出于一个原因使用react-router v3。原因是需要使用数据预取的服务器端渲染,最方便的方法是将集中式路由配置保持在objector 中array并循环匹配元素以从服务器端的 API 获取数据。稍后的数据将与响应 HTML 一起传递给客户端,并存储在 JSON 格式字符串的变量中。
应用程序也使用代码拆分,但是通过babel-plugin-transform-ensure-ignore在服务器端使用,我可以直接获取组件而不是延迟加载,并且本机import方法将仅在客户端使用。
尽管如此,上述结构不适用于react-router v5,因为它有点困难,因为我不能使用@loadable/components,正如react-router官方文档所建议的那样。根据我的观察,@loadable/components只是在服务器端生成 HTML,而不是给我实现fetch负责服务器端逻辑的方法的组件。
所以想请教一下webpack + react-router v5 + ssr + data prefetch + redux + code splitting的好结构
我看到它非常复杂并且没有通用的解决方案,但是我可能是错的。
任何方向或建议表示赞赏。
我从未尝试过@loadable/components,但我使用代码分割的自定义实现做了类似的事情(SSR + 代码分割 + 数据预取),我相信你应该改变你的数据预取方法。
如果我没猜错的话,你的问题是你试图干预正常的 React 渲染过程,提前推断出渲染中将使用哪些组件,从而应该预取哪些数据。这种干预/推导并不是 React API 的一部分,尽管我看到不同的人使用一些未记录的内部 React 东西来实现它,但从长远来看,它都很脆弱,并且容易出现像你这样的问题。
我相信,更好的防弹方法是将 SSR 作为一些正常的渲染过程来执行,在每个过程中收集要预取的数据列表,获取它们,然后从头开始重复渲染并更新状态。我正在努力想出一个清晰的解释,但让我尝试举这样的例子。
比如说,应用程序树中某处的组件<A>依赖于异步获取的数据,这些数据应该存储在some.pathRedux 存储中。考虑一下:
context,或者使用 React 的 Context API 创建一个单独的上下文)。ReactDOMServer.renderToString(..).<A>,无论是否进行代码分割,如果一切设置正确,该组件都可以访问 Redux 存储和 SSR 上下文。因此,如果<A>看到当前渲染发生在服务器上,并且没有预取到some.pathRedux 存储的数据,<A>则会将“加载这些数据的请求”保存到 SSR 上下文中,并渲染一些占位符(或任何有意义的渲染内容)无需预取这些数据)。我所说的“请求加载这些数据”的意思是,<A>实际上可以触发一个异步函数来获取数据,并将相应的数据承诺推送到上下文中的专用数组。ReactDOMServer.renderToString(..),您将拥有:呈现的 HTML 标记的当前版本,以及 SSR 上下文对象中收集的数据获取承诺数组。您可以在此处执行以下操作之一:
您应该看到,如果实现正确,它将与依赖异步数据的任意数量的不同组件一起很好地工作,无论它们是否嵌套,以及您如何准确地实现代码分割、路由等。重复渲染会产生一些开销通过,但我相信这是可以接受的。
一个小代码示例,基于我使用的代码片段:
SSR循环(原始代码):
const ssrContext = {
// That's the initial content of "Global State". I use a custom library
// to manage it with Context API; but similar stuff can be done with Redux.
state: {},
};
let markup;
const ssrStart = Date.now();
for (let round = 0; round < options.maxSsrRounds; ++round) {
// These resets are not in my original code, as they are done in my global
// state management library.
ssrContext.dirty = false;
ssrContext.pending = [];
markup = ReactDOM.renderToString((
// With Redux, you'll have Redux store provider here.
<GlobalStateProvider
initialState={ssrContext.state}
ssrContext={ssrContext}
>
<StaticRouter
context={ssrContext}
location={req.url}
>
<App />
</StaticRouter>
</GlobalStateProvider>
));
if (!ssrContext.dirty) break;
const timeout = options.ssrTimeout + ssrStart - Date.now();
const ok = timeout > 0 && await Promise.race([
Promise.allSettled(ssrContext.pending),
time.timer(timeout).then(() => false),
]);
if (!ok) break;
// Here you should take data resolved by "ssrContext.pending" promises,
// and place it into the correct paths of "ssrContext.state", before going
// to the next SSR iteration. In my case, my global state management library
// takes care of it, so I don't have to do it explicitly here.
}
// Here "ssrContext.state" should contain the Redux store content to send to
// the client side, and "markup" is the corresponding rendered HTML.
Run Code Online (Sandbox Code Playgroud)
依赖于异步数据的组件内部的逻辑有点像这样:
function Component() {
// Try to get necessary async from Redux store.
const data = useSelector(..);
// react-router does not provide a hook for accessing the context,
// and in my case I am getting it via my <GlobalStateProvider>, but
// one way or another it should not be a problem to get it.
const ssrContext = useSsrContext();
// No necessary data in Redux store.
if (!data) {
// We are at server.
if (ssrContext) {
ssrContext.dirty = true;
ssrContext.pending.push(
// A promise which resolves to the data we need here.
);
// We are at client-side.
} else {
// Dispatch an action to load data into Redux store,
// as appropriate for your setup.
}
}
return data ? (
// Return the complete component render, which requires "data"
// for rendering.
) : (
// Return an appropriate placeholder (e.g. a "loading" indicator).
);
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
206 次 |
| 最近记录: |