原文:Server Component 思想在应用中心的改良[1]
如果你还不懂什么是 Server Component,请一定要看:
1.React Server Components[2]2.精读《React Server Component》[3]3.什么是Server Component?
背景
应用中心是一个许多场景都常见的需求。近段时间,React Server Component 逐渐火了起来。虽然暂时没法上生产,但也给应用的实现方式带来了一种可能。本文面向加密应用的场景,对 Server Component 的实现做个简单的改造和适配。
与 React 的区别是什么
方向不同
React Server Component 可能更重在优化渲染的性能。带来的好处如:
•可以减少 bundle 体积•搭配传统 SSR 解决服务端 renderToString 造成的拖慢 QPS 问题•可以直接在 Node 环境中访问后端,提升开发舒适度,解决网络 IO 速度慢的问题
于是 React 在从一开始设计编写的方向,就是打算开发一套全新的开发模式。但此模式也暴露出一些弊端。如:无法很优雅地存储 Server Component 的状态,起码这在当前的开发模式中是无法忍受的(在未来也无法忍受)
下文这套方案的初衷方向,是为了组件加密执行。Server Component 的 bundle 并不落前端浏览器,于是也不会存在源码泄露问题,从源头上解决了泄密问题。对于一些开放平台的收费应用,此方向有很棒的效果。
所以在设计此方案时,考虑在 Node 侧使用沙箱选择了屏蔽一些 Node 的 API(具体看下文),像fs、http等。同时又开放了一些自定义的 API 在里面。这些都是为特殊场景做的努力
技术方案的差别
对于状态管理,React Server Component 的初衷是希望组件不要拥有自己的状态,于是 hook 功能失效。但考虑到开发便捷性,下文方案还是选择了对组件树中的 hook 状态做了单独剥离出来,同时有安全存取的方案。
简述
Server Component 协调渲染器
作用
主渲染器的主要作用,是将对应的组件通过 Reconciler (协调渲染器) 生成 DSL(这里也可以简单地理解为vdom一类的东西,但不映射任何dom元素),接着返回给前端进行递归渲染。
举个例子,我的组件代码是下图这样编写。那么在经过 Reconciler 之后,会变成对应的DSL。返回给前端,拿到 DSL 之后,才可以进行自定义的递归渲染。
上图为一套简单的完整实现流程。可以看到不同于Server Component的是,将hook状态树完全独立地剥离了出来,解决了 Server Component 组件状态的麻烦问题。下面就让我们一步步来解析其实现原理
第一步:初始化渲染
在我们定义好一个组件之后,我们就可以将其与状态树进行组合,交给渲染器。在这一步,我们的 effects 只有一个:initialize 初始化。
effects 的含义是事件记录。例如初始化事件,或者按钮的onclick事件。
在准备好后,就可以开始渲染了。
1. 生成fiber节点
我们将拿到的 effect,进行遍历组合执行,如果是 initialize 状态,第一步则将当前的组件转化为fiber节点。这里的fiber节点不仅存放了组件的类型、子组件fiber、key,还存放了当前组件hook的值。
2. 生成 DSL
在拿到fiber之后,简单地递归生成DSL。这一步没什么好说的,代码很简单
3. 生成状态树
当然,生成DSL之后,还要生成一颗独立的状态树存放组件的状态。这里的实现也很简单:从fiber当中递归拿到对应的hook值,根据组件的key存放在JS对象当中
到这一步,最后DSL的完整生成就结束了。这里贯穿全程的是一个 reconcilerState 实例。其作用是:
•存放当前批量 effect 的更新队列(如果有多个effect,可以存放在队列里面。甚至在执行effect的时候碰到新的 effect 产出)•标记更新队列开关•存放当前执行到的 fiber 节点
第二步:事件触发更新
这套技术方案与 Server Component 相比,最香的地方可能莫过于使用 hook 了。由于设计时独立出来一颗状态树,使得在组件中使用 hook 也存在可能。
1. 生成fiber节点驱动更新触发
这一步依然重复上面的 fiber 生成。但生成的同时,也会去检查当前的 effect 队列有没有匹配的事件。如果有,则触发事件:
2. 执行事件
如果事件当中,碰到了有状态更新,则会调用之前定义的 useState 的更新事件。hook更新函数则会扔到effect队列中一条 action
执行事件。
在原本的 event 事件(按钮点击事件)流程走完后,会获得一条 action 事件(状态更新事件),这就是 hook 的更新事件
3. action 更新
这一步的状态,和初始化渲染要执行的函数逻辑是一样的。同样是构建fiber节点,创建 DSL,创建状态树。最后结束返回 DSL 到前端。
一些小心思
1. 组件key的生成
这里使用的是深度 + 层级数字自增的key。同时为了避免生成有问题,搭配对应的babel插件进行编译时添加。
2. hook为什么是对象而不是链表
考虑到数据需要进行传递,链表的数据打印后层级会很深,不便于数据进行序列化和反序列化,于是还是选择了对象拍平的方式存储值。
Node Sandbox
在 React Server Component 中,提倡组件可以直接访问后端,甚至通过Node Api操作db。但在开放场景的情况下,此操作是非常高危的,需要屏蔽一些模块访问
于是选择基于开源的 isolated-vm[4] 做沙箱方案。isolated-vm 是基于c++ 编写的 Node 原生模块。安全,多线程,还可以控制内存大小和超时时间,甚至可以接入 Chrome Devtools 做调试。
但有一个缺点:不支持模块化。不过也有方案解决。我们可以将上面的方案通过构建工具打包为一个整体的 bundle,然后再交给 Sandbox 执行。
状态加密
对于hook的状态,这部分可以利用一些第三方加密服务进行存取。大体逻辑如下:
1.在服务端,将状态存储进加密服务中,返回密钥给前端2.前端再次发起事件请求,将密钥给后端3.后端带着密钥,将上一次状态从加密服务中取出。
缺点
目前来看,因为 Server Component 始终执行在 Node 端,所以概念上是无法操作 DOM 的。这也进一步受限了它的场景。不过可以通过在 Client Component 侧封装一些需要操作 DOM 的组件,例如拖动 Panel 等,在Server Component 定义,在前端渲染。
引用链接
[1]
Server Component 思想在应用中心的改良: https://github.com/Janlay884181317/blog/issues/3[2]
React Server Components: http://www.ayqy.net/blog/react-server-components/[3]
精读《React Server Component》: https://github.com/ascoders/weekly/blob/master/%E5%89%8D%E6%B2%BF%E6%8A%80%E6%9C%AF/193.%E7%B2%BE%E8%AF%BB%E3%80%8AReact%20Server%20Component%E3%80%8B.md[4]
isolated-vm: https://github.com/laverdet/isolated-vm
往期推荐
最后
欢迎加我微信,拉你进技术群,长期交流学习...
欢迎关注「前端Q」,认真学前端,做个专业的技术人...
点个在看支持我吧