koa 实现 react-view 原理
在之前我们有过一篇『React 同构实践与思考』的专栏文章,给读者实践了用 React 怎么实现同构。今天,其实讲的是在实现同构过程中看到过,可能非常容易被忽视更小的一个点 —— React View。 React View每一个 BS 架构的框架都会涉及到 View 层的展现,Koa 也不例外。我们在做 View 层的时候有两种做法,一种是做成插件形式,对于 View 来说就是模板引擎,另一种是做成中件间的形式。 再说到 React,常常有人说它是增强版的模板引擎。这种说法即对也不对。 从表象来看的确,React 可以替换变量,有条件判断,有循环判断,JSX 语法让渲染过程和 HTML 没什么两样,毕竟说到底 React 就是 JavaScript,而 React 所推崇的无状态函数,也彻彻底底把 React 变成了像是模板的样子。 从内在来看,React 它还是 JavaScript,它可以方便地做模块化管理,有内部状态,有自己的数据流。它可以做一部分 Controller,或者说,可以完全承担 Controller 的工作。 但是在服务端,我们需要模板是为了作 HTML 的同步请求,因此说地简单一些就只需要渲染成 HTML 的功能就可以了。当然,特殊的一点是,之所以让 React 作模板就是可以让服务端跑到客户端的渲染逻辑,并解决单页应用常常诟病的加载后白屏的问题。 言归正传,现在我们就带着 React View 怎么实现这个问题来解读源码。 React-View 源码解读配置配置是设计的源头之一,一切源码都可以从配置入手研究。 var defaultOptions = { doctype: '<!DOCTYPE html>',beautify: false,cache: process.env.NODE_ENV === 'production',extname: 'jsx',writeResp: true,views: path.join(__dirname,'views'),internals: false }; 如果我们用过像 handlebars 或是 jade View,我们看到 React View 的配置与其它 View 的配置有几点不同。doctype、internals 这些配置都是其它模板引擎不会有的。 模板常用的配置应该是什么呢?
渲染标准的渲染过程其实非常的简单。对于 React 来说就是读取目录下的文件,像前端加载一样,require 那个文件。最后利用 ReactDOMServer 中的方法来渲染。 var render = internals ? ReactDOMServer.renderToString : ReactDOMServer.renderToStaticMarkup; ... var markup = options.doctype || ''; try { var component = require(filepath); // Transpiled ES6 may export components as { default: Component } component = component.default || component; markup += render(React.createElement(component,locals)); } catch (err) { err.code = 'REACT'; throw err; } if (options.beautify) { // NOTE: This will screw up some things where whitespace is important,and be // subtly different than prod. markup = beautifyHTML(markup); } var writeResp = locals.writeResp === false ? false : (locals.writeResp || options.writeResp); if (writeResp) { this.type = 'html'; this.body = markup; } return markup 这里我们截取最关键的片段,正如我们预估的渲染过程一样。但我们看到,从流程上看有四个细节: 设置 doctype 的目的 在一般模板中我们很少看到将 doctype 放在配置中配置,但因为 React 的特殊性,让我们不得不这么做。原因很简单,React 渲染 React 组件
在 美化 HTML
绑定到上下文 最后一步,尽管有一个开关控制,但我们看到最后是把内容绑定到 Cache我们从一开始就看到了配置中就有 cache 配置,这个 cache 是不是我们所想呢?我们来看下源代码: // match function for cache clean var match = createMatchFunction(options.views); ... if (!options.cache) { cleanCache(match); } 这里的 cache 指的是模板缓存么。事实上不完全是,我们来看一下 cleanCache 方法就明白了: function cleanCache(match) { Object.keys(require.cache).forEach(function(module) { if (match(require.cache[module].filename)) { delete require.cache[module]; } }); } 因为我们读取 React 文件用的是 在这里的确我们全局缓存了 React 模板文件,但这个文件是编译前的文件。而我们需要缓存的是编译后的文件,也就是说 在这里我们想想怎么去实现,方便起见,我们可以新增一个 lru-cache,用它的好处是 lru 封装了很多关于 cache 时效与容量的开关。 var LRU = require("lru-cache"); var cache = LRU(this.options.cacheOptions); ... if (options.cache && cache.get(filepath)) { markup = cache.get(filepath); } else { var markup = options.doctype || ''; try { var component = require(filepath); } else { // Transpiled ES6 may export components as { default: Component } component = component.default || component; markup += render(React.createElement(component,locals)); } } catch (err) { err.code = 'REACT'; throw err; } // beautify ... if (options.cache) { cache.set(filepath,markup); } } 当然,我们现在这种情形下都需要清除 Babel我想很多开发者在写 React 组件的时候用的是 ES6 Class 来写的,而且会用到很多 ES6/ES7 的方法,不巧的是 Node 还不支持有些高级特性。因此就引到了一个话题,服务端怎么引用 babel? 在业务有 babel-node 这类解决方案,但这毕竟是一个实验性的 Node,我们不会拿生产环境去冒险。 在 koa/react-view 中间件内,有一段说明,它建议开发者在使用的时候加入 babel-register 作实时编译。关于这个问题,当然也可以写在中间件内,在加载模板前引入。随着 Node 对 ES6 方法支持的完善,也许有一天也用不到了。 总结其实,实现 View 非常简单,我们也从一些维度看到了设计一个 xx-view 的一般方法。在具体实现的时候,我们可以用一些更好的方法去做,比如用类来抽象 View,用 Promise 来描述过程。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |