在之前我们有过一篇『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 这些设置都是别的模板引擎不会有的。
模板经常运用的设置应当是什么呢?
viewPath,在上述设置指的是 view,就是 View 的目次在那里,这是每一个模板插件或中间件都须要去配的。
extname,后缀名是什么,平常来讲模板引擎都有自身独占的后缀,固然不消除能够有喜欢挑选的状况。比方对 React 而言,就能够写成是
.jsx
或.js
两种差别的情势。cache,我想平常模板引擎都邑带 cache 功用,由于模板的剖析是须要消耗资本的,而模板自身的修改的频度是异常低的。每当宣布的时刻,我们去革新一次模板即可。但上述设置中的 cache 并非指这个,我们等读源码时再来看。
衬着
规范的衬着历程实在异常的简朴。关于 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 render
要领返回时肯定须要一个包裹的元素,比方 div,ul,以至 html,因而,我们须要手动去加 doctype。
衬着 React 组件
renderToString
和 renderToStaticMarkup
都是 ‘react-dom/server’ 下的要领,与 render
差别,render
要领须要指定详细衬着到 DOM 上的节点,但那两个要领都只返回一段 HTML 字符串。这一点让 React 成为模板言语而存在。它们两个要领的区分在于:
renderToString
要领衬着的时刻带有data-reactid
属性,意味着能够做 server render,React 在前端会熟悉服务端衬着的内容,不会从新衬着 DOM 节点,最先实行componentDidMount
继承实行后续生命周期。renderToStaticMarkup
要领衬着时没有data-reactid
,把 React 当作是纯模板来运用,这个时刻只衬着 body 外的框架是比较适宜的。
在 render
要领里,我们看到 React.createElement
要领。是由于在服务端 render
要领没有 babel 编译,因而写的实际上是 <component {...locals} />
编译后的代码。
美化 HTML
options.beautify
设置了我们是不是要美化 HTML,默许时是封闭的。任何须要编译的模板引擎平常都邑有相似的设置。在 Reat 中,由于 render
后的代码是一连串的字符串,返回到前台的时刻都是没法浏览的代码。在有必要时,我们能够开启这个设置。
绑定到上下文
末了一步,只管有一个开关掌握,但我们看到末了是把内容绑定到 this.body
下的。 这里省略了全部完成历程是在 app.context.render
要领下,等于重写了 app.context
下的 render
要领,用于衬着 React。假如说 app.context.render
要领是 function*
,那末我们的 react-view,就会变成中间件。
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 文件用的是 require
要领,而在 Node 中 require 要领是有缓存的,Node 在每一个第一次 Load Module 时就会将该 Module 缓存,存入全局的 _cache 中,在平常状况下我们固然须要这么做。但在模板加载这个情况下就差别了。
在这里确实我们全局缓存了 React 模板文件,但这个文件是编译前的文件。而我们须要缓存的是编译后的文件,也就是说 markup
是我们须要缓存的值。
在这里我们想一想怎样去完成,轻易起见,我们能够新增一个 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);
}
}
固然,我们如今这类情况下都须要消灭 require
的 cache。
Babel
我想许多开发者在写 React 组件的时刻用的是 ES6 Class 来写的,而且会用到许多 ES6/ES7 的要领,不巧的是 Node 还不支撑有些高等特征。因而就引到了一个话题,服务端怎样援用 babel?
在营业有 babel-node 这类处理方案,但这毕竟是一个实验性的 Node,我们不会拿临盆环境去冒险。
在 koa/react-view 中间件内,有一段申明,它发起开发者在运用的时刻到场 babel-register 作及时编译。关于这个题目,固然也能够写在中间件内,在加载模板前引入。跟着 Node 对 ES6 要领支撑的完美,或许有一天也用不到了。
总结
实在,完成 View 异常简朴,我们也从一些维度看到了设想一个 xx-view 的平常要领。在详细完成的时刻,我们能够用一些更好的要领去做,比方用类来笼统 View,用 Promise 来形貌历程。