4

多多少少有些不开心的事, 觉得精力没有被投入在重点上
创业公司遇到问题变成盲人摸象也许正常吧
不过最近这段时间因为服务端的策略调整, 我开始做一些服务端渲染
主要的站点是简聊的登录页面, 整体从 Jade 切换到了 React
https://account.jianliao.com/signin
以及做了一些整体项目结构统一的工作, 或者说一些思考

我估计这些问题已经被考虑过很多次, 特别是对于发展较快的那些公司
因为富交互的应用和较大的网站的需求, 很容易导向这个结果
而为了保证交互效果以及产量, 提出相应的方案是自然的一个结果
Teambition 主站的同学之前有讲过 ejs 共用代码的方案
我大致记得是后端渲染, 前端编译, 还重新实现了路由, 大致的
简聊(https://jianliao.com)这里的尝试晚了半年一年, 层次也不深, 用 React 难度也低一些

前后端渲染大略

这些天 Coding 在好多地方刷了几篇服务端渲染的广告, 学习目的推荐看下
http://segmentfault.com/a/1190000004120539
http://segmentfault.com/a/1190000004094442

简聊没有继续跟进 Redux 和 JSX 的方案, 实际上细节处理很不一样
整体的思路当然差不太多, Yahoo 的 fluxible 当时演示的方案已经成熟
我整理一下大体思路吧, 也许用得到, 特别是中间一些坑
大致有这么几点, 在开发之前就需要考虑清楚的

  • 前后端共用的渲染模块

  • 前后端共用的数据层实现

  • 前后端共用的路由方案

  • 前后端共用的类库

简聊技术栈当中已有的模块, 能够在服务端共用的:

  • 渲染模块: React 内置功能, 轻松实现

  • 数据层实现: actions-recorder 是很简单的封装

  • 路由方案: router-view 功能少, 容易自由组合

  • 共用类库: 通过 typeof window 强制判断控制加载

前后端渲染, 实际上总体的思路还是前端单页面渲染的套路
单页面, 会先创建好 Model, 然后根据 Model 和 Router 渲染 View
渲染在 React 当中就简化成为初次渲染, 以及后续数据和操作的更新
而服务端渲染, 仅仅是把初次渲染放在服务端进行, 后边照常
因此, 虽说是共用代码, 但实际上只是支持单页面在服务端做初次渲染

渲染模块

共用渲染代码本来是最难的, 然而 React 出现以后几乎不是个事情
只是渲染的性能让 React 的实用性打了折扣
不过 0.14 之后有新的模块在尝试, 号称性能提升明显
整体思路就是把渲染过程转化为 Node 常用的 Stream
https://github.com/aickin/react-dom-stream
我没有实际用, 不过这个方案需要手动封装 HTML 的 <head> 部分
方案的代码是 fork 了 react-dom 官方实现做的, 看起来挺长
我还是等等事件提供更好的方案吧

数据层实现

数据层需要做的主要是定义一个渲染页面所需的 initialStore
这个 initialStore 可以用于服务端的初次渲染, 也可以用于客户端
简聊网页版用的 actions-recorder 在服务端渲染时几乎每做什么
整个 initialStore 直接被输出为 JSON 写在 HTML head 当中

<script>window._initialStore= (JSON.stringify(store))</script>

前端代码初始化时读取其中的数据, 重新用 actions-recorder 初始化一遍

一般服务端写 initialStore 之前会做一些操作
比如说服务端能拿到的 cookie 或者其他的一些配置
或者是后面要说的路由信息, 因为简聊的路由是等效 JSON 表示的
服务端是代替客户端做了一些初始化工作, 否则前端初始化也是类似的代码

路由方案

router-view 包含两部分代码, 一部分是解析路由, 另一部分是渲染和监听
当然, 初次之外, 还有接口可以定义路由规则, 我挺懒
如果有兴趣看看内部实现和实例也就算了, 很短的代码
https://github.com/teambition/router-view
页面初次渲染的过程, 大致就是解析出路由, 渲染对应页面就完了
路由解析在后端做, 在前端做, 只是获取 path 的 API 不同而已

这里倒是有一点要注意, 服务端渲染有特别的地方, 就是初次操作
比如说, 打开一个页面, 会自动触发一个操作, 比如验证某些数据
单纯按照单页面的思路, 渲染时不应有操作, 那么, 操作应该是更早发出
也就是在之前用户发有操作, 到服务器渲染页面, 这中间
这种问题在前端做, 总架构考虑, 就该是认为是用户打开浏览器的行为上发出
总之尽量避免认为是 didMount 的时候发出这样一个行为了

路由另外一点是隔离 JavaScript/CSS 代码的作用
当然, 其实还是通过单页面应用的套路来实现的, 甚至 Webpack 打包也是
就是说通过 Component 的分割, 将 JavaScript/CSS 限制在每个页面里
我们早期一些代码用的是原始的 DOM 操作, 就不容易管理
比如说打包到一起, 万一 CSS 或者 JavaScript 不应该却触发了怎么办?
我们没有积累强大的后端渲染静态资源管理方案, 因而这点需要避免...

共用类库

最主要的问题就是第三方类库可能影响在 Node 中加载代码
其实初次渲染很可能用不到很多代码, 只要加载不报错就好了
用的办法简单粗暴, 就是强制判断, 然后控制 require 的执行

typeof window isnt undefined

我记得这个还是以前 AirBnB 放出的幻灯片里看到用的
实际遇到会是很琐碎的情况, 甚至导致代码都有些难看
但是谁让 JavaScript 设计时看不到这么远的适应场景呢

另外有个办法, 是用 Webpack 的打包方式, 自动把 Node 模块过滤掉
这个办法我是刚学会, 具体看网上的例子:
https://webpack.github.io/docs/configuration.html#node
http://stackoverflow.com/a/34033159/883571
大致说来就是定义一些模块, 告诉 Webpack 用什么方案处理
可以 mock, 可以引用... 细节我还不清楚, 但值得挖掘

我实践中遇到最坑的一件事, 莫过于代码当中存在 event loop
简聊有段代码打包后几万行, 中间有时间循环, 根本不知道在哪
后来猜想是 setInterval 有问题, 就重置了变量通过报错定位出来
这种 IO 代码毕竟不如 pure function 好控制, 能隔离尽量隔离

预想复杂场景

前后端渲染主要的好处, 就是做了单页面, 又保证首屏渲染体验
如果仅仅是服务端渲染加 jQuery, 组件化会很头疼
特别是实现比较复杂的功能, 还要迁就初次渲染额外处理, 真的不轻松
然而用了 React 很多工作就这么省掉了

简聊目前的场景, 就是第一次加载是服务端渲染, 然后前端加载
之后点击切换路由, 就完全是 HTML5 路由切换, 完全是单页面套路
说简单除了页面少, 另一点是因为数据层几乎没有内容
就是说, 从服务端在 HTML head 写 JSON 传导前端初始化, 几乎没有数据
仅仅是读取的一些配置信息而已, 所以不涉及数据库操作

复杂的情况, 从目前对简聊主站应用的情况做的设想
应用在初始化时, 会先装备好查询到首屏需要的所有数据
数据拼装完成, 然后才能开始渲染, 当然到这一步就很简单了
难度在于怎么查询好需要的数据? 而且, 是一套代码, 不是前后端各一套
那么要求有更好的抽象机制能做数据查询的事情, 有点难度
一方面是 polyfill 两边的 ajax API, 另一方面是数据逻辑抽象

我感觉跟着 React 和 GraphQL 的思路, 已经触及一些重要问题了
数据和界面之间, 怎么做好隔离, 然而又怎样设计界面对数据的依赖?
界面自身的组合如何抽象, 数据自身的组合又如何抽象?
将来要梳理的问题还是会有很多


题叶
17.3k 声望2.6k 粉丝

Calcit 语言作者