react 强大的功能-通过生命周期方法对资源进行SSR异步获取

qq24tv8q  于 2022-10-28  发布在  React
关注(0)|答案(6)|浏览(162)

在进行SSR时,很难获取动态的资源,虽然您一直在尝试引入相当复杂的suspension API,但我不得不为我的用例提供一个更简单的解决方案。
通过react-dom简单地添加一个仅在SSR期间执行的生命周期方法,即async(使整个renderToString)方法真正异步;它允许更新初始状态,非常简单,而且可以工作(我已经测试过了)。
让我举一个例子来进一步说明这一点,假设我们有一个名为ResourceFetcher的组件,它执行它所声明的任务并获取资源,现在让我们假设我们的react开发人员只是像这样使用它。

<ResourceFetcher resource="data.json">
  {(data) => (
    <ResourceFetcher resource={data.secondaryResource}>
       {(secondaryData) => (
          <div>HERE I DISPLAY THE DATA</div>
       )}
    </ResourceFetcher>
  )}
</ResourceFetcher>

想象一下,如果使用SSR来设置这个,那将是一场噩梦,特别是当资源获取器可以任意使用时,我们将不得不复制逻辑来收集数据,当我们知道我们在正确的URL上时,数据将被呈现;这可能非常困难,必须对每个页面都执行,并且混合使用路由、动态路径、影响所需内容的查询字符串,服务器端逻辑变得难以应付。
现在,让我们想象一下,我们现在有一个<ResolverContext>,它位于所有应用程序之上,它为资源获取器提供了一个示例,指定如何使用资源获取器进行解析,它还包含一个资源获取器可以使用的缓存。
因此,resouce fetcher在constructor上执行的操作是,它将尝试该高速缓存中加载一个值,而在componentDidMount上,如果未加载该值(因为在缓存中找不到该值),它将运行一个获取请求。
现在假设我们有一个名为beforeServerSideRender的方法,它是异步的,只在服务器端呈现之前执行,它准备状态。在这种情况下,在构造函数上,我们的缓存确实是空的,但随后beforeServerSideRender触发,我们可以“解析”我们需要的这些资源。在服务器端的情况下,解析器不是获取,它只是从磁盘读取;然后,服务器可以具有收集器的形式,并且设置变量X1 M6 N1 X,使得客户机可以在上下文中设置其初始高速缓存。
其结果是,你有一个非常容易设置SSR,可以与基本上任何东西,不需要任何配置,一旦你的环境是拨号,我设法创建了非常丰富的动态加载SSR页面与该方法非常少的模糊,我不需要使我需要的每页列表,它只是在飞行中完成.
我所做的如下:

react-dom/src/server/ReactDOMNodeStreamRenderer.js

-  _read(size) {
+  async _read(size) {

-      this.push(this.partialRenderer.read(size));
+      this.push(await this.partialRenderer.read(size));

react-dom/src/server/ReactDOMStringRenderer.js

-export function renderToString(element, options?: ServerOptions) {
+export async function renderToString(element, options?: ServerOptions) {

-    const markup = renderer.read(Infinity);
+    const markup = await renderer.read(Infinity);

-export function renderToStaticMarkup(element, options?: ServerOptions) {
+export async function renderToStaticMarkup(element, options?: ServerOptions) {

-    const markup = renderer.read(Infinity);
+    const markup = await renderer.read(Infinity);

react-dom/src/server/ReactPartialRenderer.js

-function resolve(
+async function resolve(

-    processChild(element, Component);
+    await processChild(element, Component);

-  function processChild(element, Component) {
+  async function processChild(element, Component) {

+
+    if (inst.beforeServerSideRender) {
+      await inst.beforeServerSideRender();
+    }
     child = inst.render();

-  read(bytes: number): string | null {
+  async read(bytes: number): string | null {

-          outBuffer += this.render(child, frame.context, frame.domNamespace);
+          outBuffer += await this.render(child, frame.context, frame.domNamespace);

-  render(
+  async render(

-      ({child: nextChild, context} = resolve(child, context, this.threadID));
+      ({child: nextChild, context} = await resolve(child, context, this.threadID));

就这样,我给了SSR超能力,我不再需要列表来提前指定我需要什么资源,我可以在react渲染的时候得到它;如果他们有生命周期功能,它不会影响服务器端以外的任何东西,但一切都正常工作。
不仅如此,使用这种方法,如果SSR由于某种原因崩溃,它仍然工作!......我只会得到一个空缓存的默认版本,因为收集失败,资源将在挂载时提取;只是初始页面将是,没有适当的SSR,但没有模糊,它仍然工作;所以它是抗碰撞。
开发模式似乎有一个问题,它工作,但不需要太大的压力,因为它不能重叠渲染在异步模式,但工作在生产建设。这就是为什么我提出这个问题,而不是一个公关。
只是简单的生命周期功能增加了太多的功能,这是一个非常简单的变化,我认为它应该实现,特别是因为我们没有悬念,有些人不喜欢悬念的想法;这种解决方案极其简单,但却行之有效。
https://www.npmjs.com/package/@onzag/react-dom的最大值

3z6pesqy

3z6pesqy1#

如果这可行的话,也许我们可以使用当前可用的生命周期方法。如果方法请求了一些数据并需要重新渲染,那么它们将返回true。SSR可以这样工作:
1.将首先调用componentDidMount方法(在呈现之前),而不是beforeServerSideRender
1.如果方法调用是用true解析,则意味着它获取了一些数据,调用了一些setState,并将根据新状态再次更新如果方法返回false,则继续执行步骤4

  1. componentDidUpdate方法将在一个循环中被调用,条件是它的返回值--继续执行步骤2。
    1.当方法返回false时,不再需要“rerender”,因此我们可以进行最终渲染-实际上是调用inst.render()并完成。
    所以代码可能是这样的:
const needUpdate = inst.componentDidMount && await inst.componentDidMount();
if(inst.componentDidUpdate)
{
    while(await inst.componentDidUpdate(/* not sure how to get prevProps, prevState*/));
}
child = inst.render();

它可能会很好地解决SSR的一个大问题。

bsxbgnwa

bsxbgnwa2#

@onzag,你觉得怎么样?你会尝试一下吗?我没有编辑react这么大的项目代码的经验。

xzlaal3s

xzlaal3s3#

@mariusrak很抱歉这么晚才回复,我不认为componentDidMount或DidUpdate方法更好,因为这意味着它必须再次重新渲染,如果有什么getDerivedStateFromProps会更适合,但getDerived不是异步的,可以在任何上下文中运行。
你想知道你要从哪里得到这些道具,好吧,这正是从渲染本身,在一天结束时传递这些道具,所以当更新状态时,这些道具将不得不被传递,这是一个后勤噩梦,在这种情况下,对React域是一个巨大的变化,而一个单独的函数,是一个小变化。
修改did mount实际上可能会破坏现有的项目,getDerived也是一样,而新的生命周期函数不会影响任何东西,旧项目仍然可以很好地工作,无论它们在做什么,新项目可以使用该函数,它是100%向后兼容的。
它有一个很大的不同的思考方式,你必须做的事情使用这个新的功能,但它的工作;这是对React本身一个简单更改,我已经测试过它,并在我自己的项目上运行过。
不过,我的建议有两个问题。
1.没有钩子,例如useServerSideRender,事实上,我不认为它会与钩子一起工作,就像你没有一个钩子用于getDerived。
1.在开发构建中,如果对服务器进行压力测试,有时会由于响应开发调试的工作方式而无法渲染,使用异步方法时,渲染将变为异步。
1.流渲染似乎不起作用。
但好处是巨大的,而且这种工作方式极其简单; Suspension API相当复杂。

piah890a

piah890a4#

@mariusrak很抱歉这么晚才回复,我不认为componentDidMount或DidUpdate方法更好,因为这意味着它必须再次重新渲染,如果有什么getDerivedStateFromProps会更适合,但getDerived不是异步的,可以在任何上下文中运行。
render应该是纯函数。它不应该改变任何状态,也不应该有任何影响。因此,在服务器端,组件是否被呈现并不重要。这就是为什么我建议在一个循环中只调用componentDidUpdate。在不需要调用更多的componentDidUpdate之后,可以发生redner。因此,不需要重新呈现。(可能会有一些边缘情况也需要解决,比如调用父级提供的方法,但我认为这是可能解决的)。
你想知道你要从哪里得到这些道具,好吧,这正是从渲染本身,在一天结束时传递这些道具,所以当更新状态时,这些道具将不得不被传递,这是一个后勤噩梦,在这种情况下,对React域是一个巨大的变化,而一个单独的函数,是一个小变化。
是的,但是一旦所有的道具都准备好了,它们就被传递给孩子们。然后他们可以在孩子们的DidUpdate中被拾取。还有,状态--状态不能在渲染中被改变。状态在DidMountDidUpdate或一些事件中被改变。在服务器上,没有DOM事件,所以只有数据获取事件可以发生,并且它们可以被 Package 在async/await中。我不清楚你在哪里看到了后勤上的噩梦。但这可能是因为我对渲染过程的知识不足。
修改did mount实际上可能会破坏现有的项目,getDerived也是一样,而新的生命周期函数不会影响任何东西,旧项目仍然可以很好地工作,无论它们在做什么,新项目可以使用该函数,它是100%向后兼容的。
目前可以使DidMoutDidUpdate异步,并且它们同步或异步对任何东西都没有影响。因此没有变化。此外,目前这些方法不返回任何东西。并且它们仍然不需要返回任何东西以保持兼容性。但是如果它们返回,它将控制SSR。
目前,对于客户端浏览器代码,这两个函数被广泛使用。它们提供了某种逻辑和有用性。这种逻辑应该保留在SSR上。添加一个新的方法,这就是为什么我建议在客户端和服务器端使用几乎相同的API。对于'beforeServerSideRender·,我们必须用两种方法来获取数据。根据我的建议,我们可以只在一个项目中进行数据获取。因此,它不仅可以增强同构性并提供向后兼容性,而且还允许对旧项目进行SSR。可能需要一些小的修改。
它有一个很大的不同的思考方式,你必须做的事情使用这个新的功能,但它的工作;这是对React本身一个简单更改,我已经测试过它,并在我自己的项目上运行过。
使用已经介绍过的方法会减少这种“不同的思维”,我相信它也会起作用。
不过,我的建议有两个问题。

1. There is no hook eg. `useServerSideRender` in fact I don't think it would work with a hook at all, just like you don't have a hook for getDerived.

我不知道钩子是如何实现的,但我相信我们可以像我建议的那样修改DidUpdate/·DidMount。

2. On development builds if you stress test a server it sometimes fail to render due to the way react development debugging works, with an async method the render becomes async.

这个问题是否只与dev. debugging有关?看起来像是fiber引擎的问题,这可能是任何异步性的巨大问题。

3. Stream rendering does not seem to work.

我担心这将是不可能的,使服务器端渲染的React应用程序的完整流,因为它应该能够更新“上层”组件(如 Helm )。但除此之外,我认为没有问题,流与您的建议。
但好处是巨大的,而且这种工作方式极其简单; Suspension API相当复杂。
我看到了它的好处,我很想在产品中看到它。我也相信使用当前的API可以提供更多的好处,所以我们可以尝试一下。

t1qtbnec

t1qtbnec5#

让我先说一些人可能会从did mount返回一些东西;不管是不是错的,这就意味着有可能会有突破
想象一下这段代码:

<ResourceFetcher resource="data.json">
  {(data) => (
    data ? <ResourceFetcher resource={data.secondaryResource}>
       {(secondaryData) => (
          secondaryData ? <div>{secondaryData}</div> : null
       )}
    </ResourceFetcher> : null
  )}
</ResourceFetcher>

在标准形式下是这样的。
1.调用ResourceFetcher构造函数
1.会呼叫Render。
1.数据为null,因此返回null
1.可以提取以树结尾的HTML。
我提议的形式。
1.调用ResourceFetcher构造函数
1.在调用ServerSideRender并设置状态之前。
1.数据,因此它会建立第二个ResourceFetcher节点。
1.调用ResourceFetcher构造函数
1.在调用ServerSideRender并设置状态之前。
1.指定secondaryData,以便它创建div节点。
1.创建div并填充数据。
1.可以提取以树结尾的HTML。
现在,您指定的形式更像这样
1.调用ResourceFetcher构造函数
1.会呼叫Render。
1.数据为null,因此返回null
1.调用DidMount。
1.所有的母体交互作用都需要被指定,比如影响母体的副作用。
1.父母需要重新检查的副作用期间,做了孩子的坐骑,我们需要遍历树向后。

  1. shouldComponentUpdate需要触发。
    1.需要再次执行getDerived。
  2. render需要再次执行的次数与async didmount为每个setState指定的次数相同。
    ......事实上,这个过程是如此漫长,如此复杂,如此错综复杂,我无法继续下去。
    按照这种速度,有一种方法可以完成这项工作,那就是“React”本身,所以您也可以使用DOM本身,例如,使用puppeteer来执行SSR,执行代码,然后获取结果DOM;你能做到的。
    我给出的建议非常简单,它就像一个异步构造函数,只在SSR中执行,并导致非对称执行,您真的需要注意以确保结果是相同的;对于响应端,这是一个超级简单的更改,但用于SSR可能会比较棘手,因为您确实需要使用上下文才能使其工作,并了解如何使用上下文,因此需要设置来执行此SSR,但一旦您拨入设置,您的应用基本上会自行SSR,而不存在其他依赖项。
eeq64g8w

eeq64g8w6#

现在,您指定的形式更像这样

1. Constructor for ResourceFetcher gets called.

2. Render gets called.

为什么?不需要在服务器端调用render。我们为什么要调用它?我写了一个代码,在那里生命周期方法被调用,直到所有的都准备好了才进行render。所以不,render不会被调用。
...

5. All the parent interactions need to be specified, say side effects that affect the parents.

如果这不可能,那么SSR的体验仍然比目前更好,因为目前没有可用的生命周期方法。根据您的建议,这也是不可能的/可用的。根据我的建议,所有进一步的步骤也是不正确的。

6. Parents need to be rechecked for side effects during the did mount of the children, we need to traverse the tree backwards.

7. shouldComponentUpdate needs to trigger.

8. getDerived needs to be executed again.

9. render needs to be executed again as many times as the async didmount specifies with each setState.

按照这种速度,有一种方法可以完成这项工作,那就是“React”本身,所以您也可以使用DOM本身,例如,使用puppeteer来执行SSR,执行代码,然后获取结果DOM;你能做到的。
这不是那么简单的。你需要知道什么时候停止执行并抓取DOM。这就是为什么应该直接在react core中实现它。即使它需要更多的逻辑来处理像向后更新父对象这样的情况,使用react core仍然比自定义处理这个when-to-stop逻辑更容易和干净。(我刚刚实现了它,所以我知道它并不好)
React知道某个渲染器何时开始工作,使用async/await时,它会一直等待,直到所有问题都解决,每个渲染都完成。
我的提议很简单...
简单,但需要重写/复制每个要进行SSR的组件的逻辑。这违反了DRY和KISS原则

相关问题