在进行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的最大值
6条答案
按热度按时间3z6pesqy1#
如果这可行的话,也许我们可以使用当前可用的生命周期方法。如果方法请求了一些数据并需要重新渲染,那么它们将返回true。SSR可以这样工作:
1.将首先调用
componentDidMount
方法(在呈现之前),而不是beforeServerSideRender
。1.如果方法调用是用
true
解析,则意味着它获取了一些数据,调用了一些setState
,并将根据新状态再次更新如果方法返回false,则继续执行步骤4componentDidUpdate
方法将在一个循环中被调用,条件是它的返回值--继续执行步骤2。1.当方法返回false时,不再需要“rerender”,因此我们可以进行最终渲染-实际上是调用
inst.render()
并完成。所以代码可能是这样的:
它可能会很好地解决SSR的一个大问题。
bsxbgnwa2#
@onzag,你觉得怎么样?你会尝试一下吗?我没有编辑react这么大的项目代码的经验。
xzlaal3s3#
@mariusrak很抱歉这么晚才回复,我不认为componentDidMount或DidUpdate方法更好,因为这意味着它必须再次重新渲染,如果有什么
getDerivedStateFromProps
会更适合,但getDerived
不是异步的,可以在任何上下文中运行。你想知道你要从哪里得到这些道具,好吧,这正是从渲染本身,在一天结束时传递这些道具,所以当更新状态时,这些道具将不得不被传递,这是一个后勤噩梦,在这种情况下,对React域是一个巨大的变化,而一个单独的函数,是一个小变化。
修改did mount实际上可能会破坏现有的项目,getDerived也是一样,而新的生命周期函数不会影响任何东西,旧项目仍然可以很好地工作,无论它们在做什么,新项目可以使用该函数,它是100%向后兼容的。
它有一个很大的不同的思考方式,你必须做的事情使用这个新的功能,但它的工作;这是对React本身一个简单更改,我已经测试过它,并在我自己的项目上运行过。
不过,我的建议有两个问题。
1.没有钩子,例如
useServerSideRender
,事实上,我不认为它会与钩子一起工作,就像你没有一个钩子用于getDerived。1.在开发构建中,如果对服务器进行压力测试,有时会由于响应开发调试的工作方式而无法渲染,使用异步方法时,渲染将变为异步。
1.流渲染似乎不起作用。
但好处是巨大的,而且这种工作方式极其简单; Suspension API相当复杂。
piah890a4#
@mariusrak很抱歉这么晚才回复,我不认为componentDidMount或DidUpdate方法更好,因为这意味着它必须再次重新渲染,如果有什么
getDerivedStateFromProps
会更适合,但getDerived
不是异步的,可以在任何上下文中运行。render
应该是纯函数。它不应该改变任何状态,也不应该有任何影响。因此,在服务器端,组件是否被呈现并不重要。这就是为什么我建议在一个循环中只调用componentDidUpdate
。在不需要调用更多的componentDidUpdate
之后,可以发生redner。因此,不需要重新呈现。(可能会有一些边缘情况也需要解决,比如调用父级提供的方法,但我认为这是可能解决的)。你想知道你要从哪里得到这些道具,好吧,这正是从渲染本身,在一天结束时传递这些道具,所以当更新状态时,这些道具将不得不被传递,这是一个后勤噩梦,在这种情况下,对React域是一个巨大的变化,而一个单独的函数,是一个小变化。
是的,但是一旦所有的道具都准备好了,它们就被传递给孩子们。然后他们可以在孩子们的
DidUpdate
中被拾取。还有,状态--状态不能在渲染中被改变。状态在DidMount
,DidUpdate
或一些事件中被改变。在服务器上,没有DOM事件,所以只有数据获取事件可以发生,并且它们可以被 Package 在async/await中。我不清楚你在哪里看到了后勤上的噩梦。但这可能是因为我对渲染过程的知识不足。修改did mount实际上可能会破坏现有的项目,getDerived也是一样,而新的生命周期函数不会影响任何东西,旧项目仍然可以很好地工作,无论它们在做什么,新项目可以使用该函数,它是100%向后兼容的。
目前可以使
DidMout
和DidUpdate
异步,并且它们同步或异步对任何东西都没有影响。因此没有变化。此外,目前这些方法不返回任何东西。并且它们仍然不需要返回任何东西以保持兼容性。但是如果它们返回,它将控制SSR。目前,对于客户端浏览器代码,这两个函数被广泛使用。它们提供了某种逻辑和有用性。这种逻辑应该保留在SSR上。添加一个新的方法,这就是为什么我建议在客户端和服务器端使用几乎相同的API。对于'beforeServerSideRender·,我们必须用两种方法来获取数据。根据我的建议,我们可以只在一个项目中进行数据获取。因此,它不仅可以增强同构性并提供向后兼容性,而且还允许对旧项目进行SSR。可能需要一些小的修改。
它有一个很大的不同的思考方式,你必须做的事情使用这个新的功能,但它的工作;这是对React本身一个简单更改,我已经测试过它,并在我自己的项目上运行过。
使用已经介绍过的方法会减少这种“不同的思维”,我相信它也会起作用。
不过,我的建议有两个问题。
我不知道钩子是如何实现的,但我相信我们可以像我建议的那样修改
DidUpdate
/·DidMount。这个问题是否只与dev. debugging有关?看起来像是fiber引擎的问题,这可能是任何异步性的巨大问题。
我担心这将是不可能的,使服务器端渲染的React应用程序的完整流,因为它应该能够更新“上层”组件(如 Helm )。但除此之外,我认为没有问题,流与您的建议。
但好处是巨大的,而且这种工作方式极其简单; Suspension API相当复杂。
我看到了它的好处,我很想在产品中看到它。我也相信使用当前的API可以提供更多的好处,所以我们可以尝试一下。
t1qtbnec5#
让我先说一些人可能会从did mount返回一些东西;不管是不是错的,这就意味着有可能会有突破
想象一下这段代码:
在标准形式下是这样的。
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.需要再次执行getDerived。
......事实上,这个过程是如此漫长,如此复杂,如此错综复杂,我无法继续下去。
按照这种速度,有一种方法可以完成这项工作,那就是“React”本身,所以您也可以使用DOM本身,例如,使用puppeteer来执行SSR,执行代码,然后获取结果DOM;你能做到的。
我给出的建议非常简单,它就像一个异步构造函数,只在SSR中执行,并导致非对称执行,您真的需要注意以确保结果是相同的;对于响应端,这是一个超级简单的更改,但用于SSR可能会比较棘手,因为您确实需要使用上下文才能使其工作,并了解如何使用上下文,因此需要设置来执行此SSR,但一旦您拨入设置,您的应用基本上会自行SSR,而不存在其他依赖项。
eeq64g8w6#
现在,您指定的形式更像这样
为什么?不需要在服务器端调用render。我们为什么要调用它?我写了一个代码,在那里生命周期方法被调用,直到所有的都准备好了才进行render。所以不,render不会被调用。
...
如果这不可能,那么SSR的体验仍然比目前更好,因为目前没有可用的生命周期方法。根据您的建议,这也是不可能的/可用的。根据我的建议,所有进一步的步骤也是不正确的。
按照这种速度,有一种方法可以完成这项工作,那就是“React”本身,所以您也可以使用DOM本身,例如,使用puppeteer来执行SSR,执行代码,然后获取结果DOM;你能做到的。
这不是那么简单的。你需要知道什么时候停止执行并抓取DOM。这就是为什么应该直接在react core中实现它。即使它需要更多的逻辑来处理像向后更新父对象这样的情况,使用react core仍然比自定义处理这个when-to-stop逻辑更容易和干净。(我刚刚实现了它,所以我知道它并不好)
React知道某个渲染器何时开始工作,使用async/await时,它会一直等待,直到所有问题都解决,每个渲染都完成。
我的提议很简单...
简单,但需要重写/复制每个要进行SSR的组件的逻辑。这违反了DRY和KISS原则