客户端路由没有JavaScript
#javascript #网络开发人员 #性能 #solidjs

自从我写了一篇有关SolidJS技术创新的文章以来已经有一段时间了。自从我们使用流SSR上添加服务器上的悬念已经两年了。甚至更长的时间回到我们首次引入悬疑以进行数据获取和并发渲染的时间。

虽然React引入了这些概念,但将它们实施为细粒度的反应系统是另一种野兽。需要一点想象力和完全不同的解决方案来避免散布。

,这与我们最近一直在进行的探索类似。 Solid从React Server ComponentsMarkoAstro等岛屿解决方案等岛屿解决方案中得到了启发,这是Partial Hydration的第一步。 (底部的比较


实心

Image description

自从释放固体1.0以来,我一直被淹没了。在避开开放问题和试图查看更多盒子以获取收养的盒子之间,我肯定会感到散布稀薄。一切都指向SSR元框架的需求,甚至在1.0版本之前就开始了这项工作。

社区加紧帮助。但是最终,为了将Beta击出门,我将成为阻止者。尼基尔·萨拉夫(Nikhil Saraf),从来没有一个人坐着,最近被介绍给阿比亚奇(Fresh),想看看他是否不能仅仅将岛屿添加到Solidstart中。

想要将事情专注于发行,我同意,但告诉他时间盒,因为第二天我需要他的帮助。第二天,他向我展示了一个演示,他不仅添加了岛屿,还重新创建了新的体验,而且还增加了客户端的路线。


意外岛屿

Image description

现在演示很粗糙,但令人印象深刻。他拿走了我的一个黑客演示,并重新完成了递归岛。什么是递归岛..那是您在岛上投射岛屿的时候:

function MyServerComponent(props) {
  return <>{ props.data && 
    <MyClientIsland>
      <MyServerComponent data={props.data.childData} />
    </MyClientIsland>
  }</>
}

你为什么要这个?好吧,有一个规则,您不能在其中导入和使用服务器组件。原因是您不希望客户能够将状态传递给他们。为什么?好吧,如果客户端可以将状态传递给他们,那么他们需要能够更新,并且由于这个想法是不要将此JavaScript发送到浏览器,这是行不通的。幸运的是,props.children很好地强加了这个边界。 (假设您禁止在岛边界范围内传递渲染功能/渲染道具)。

function MyClientIsland() {
  const [state, setState] = createSignal();

  // can't pass props to the children
  return <div>{props.children}</div>
}

他如何在如此短的时间内进行这个演示?好吧,这是偶然的。 Solid的水合作用与DOM中实例化的模板相匹配。他们看起来像这样:

<div data-hk="0-0-1-0-2" />

每个模板都会增加计数,每个嵌套组件添加了另一个数字。这对于我们的单通道水合至关重要。毕竟,所有JSX都可以随时以任何顺序和悬疑边界创建。

但是,在给定的深度下,所有ID将在同一订单客户端或服务器中分配。

function Component() {
  const anotherDiv = <div data-hk="1" /> 
  return <div data-hk="2">{anotherDiv}</div>
}

// output
<div data-hk="2">
  <div data-hk="1" />
</div>

此外,我还添加了一个<NoHydration>组件来抑制这些ID,以便我们可以跳过头部中的链接和样式表等水合资产。仅在服务器上运行并且不需要在浏览器中运行的东西。

又是无关的,与Astro进行了固体整合,我添加了一种机制来设置水合根的前缀,以防止这些ID重复对无关岛屿的重复。

我从来没有想到我们可以将自己的ID作为前缀喂食。而且,由于它只会在最后一个附加,因此我们可以从页面上的任何点开始补充服务器的实体页面。使用<NoHydration>,我们可以在任何时候停止补充水分,将孩子隔离为仅服务器。


混合路由

Image description

对于岛屿和部分水合的所有好处,要不运送所有JavaScript,您就不需要在浏览器中需要该代码。当您需要客户渲染页面时,您需要所有代码才能渲染下一页。

尽管Turbo这样的技术已被用来获取和替换HTML而不完全重新加载页面,但人们注意到这经常感到笨拙。

,但我们有一个想法,我们可以采用嵌套路由,只取代HTML部分。早在三月份,瑞安·特克斯(Ryan Turnquist)(固体路由器的共同创造者)制作了this demo。虽然视觉演示不多,但证明我们只能使用JavaScript的1.3kb具有这种功能。

诀窍是,通过点击事件的事件委派,我们可以触发客户端路由器而无需补充页面。从那里,我们可以使用Ajax请求下一页并传递上一页,并且服务器可以从路由定义中知道该页面所需的渲染所需的嵌套部分。使用返回的HTML客户端路由器可以在内容中交换。


完成图片

原始演示很粗糙,但它表现出了很多希望。它仍然存在仅用于服务器内容的双重数据问题,这是我们需要在核心中解决的问题。因此,我们添加了何时在页面的仅服务器部分创建固体资源时。我们知道,如果触发数据获取的原因只能在服务器上发生,则无需全部序列化。岛屿已经序列化了他们的道具。

我们还借此机会创建了一种通过hydrate调用传递反应性上下文的机制,使上下文可以在服务器内容分开的岛屿之间的浏览器中工作。

有了那些,我们已经准备好递归黑客评论演示:

但是我们缺少一件事。交换HTML非常适合新导航,但是何时需要刷新页面的一部分呢?您不想失去客户端状态,输入焦点等... Nikhil管理了这样做的版本。但是最终,我们最终使用了micromorph Nate Moore(Astro)写的光DOM DIFF。

这样,我们将Taste Movie App Demo移植到了JS Glory的13KB中。 (感谢Addy Osmani的轻柔轻描淡写,尼基尔,大卫和扎实社区的几位成员:Dev-RB,Muhammad Zaki,Paulo Riccuiti等)。

搜索页面尤其是在不失去客户端状态的情况下展示重新加载。当您键入时,即使需要更新整个嵌套面板,输入也不会失去焦点。

Solid Movies Demo
以及Github

只是让您了解这有多荒谬。这是在两个电影列表页面之间导航的总javascript,然后在https://tastejs.com/movies/的客户端路由的各种框架中导航到电影。

注意:只有实体演示使用服务器渲染的部分,因此它有点不平等。但是关键是要强调大小的差异。其他框架正在开发类似的解决方案,例如Next中的RSC和Qwik的容器,但是这些是今天可用的演示。


结论

我们以这种方式构建的应用程序越多,我对技术的兴奋就越兴奋。在各个方面,感觉就像一个单一页应用程序,但它却小得多。老实说,每次打开网络标签时,我都会感到惊讶。

我们仍在努力将其从实验性和巩固API中移出。而且在服务器渲染方面有更多的优化空间,但是我们认为这里有一种新的架构的所有材料。那很酷。