菜单

盼了什么?_?破屏而来的尴尬!

2018年11月15日 - CSS/CSS3

降低首屏时间,“直出”是独什么概念?

2015/12/31 · HTML5 · 2
评论 ·
首屏

初稿出处: VaJoy Larn   

早几年前端还处于刀耕火种、JQuery独树一帜的一时,前后端代码的耦合度很高,一个web页面文件的代码可能是这样的:

图片 1

图片 2

当时意味后端的工程师往往得承受一部分窜HTML、编写脚本的工作,而前端开发者为得询问页面及在的服务端代码含义。

偶然某处页面逻辑的改,鉴于代码的混搭,可能都非确定相应要后端还是前者来改变(即使他们都能处理)。

图片 3

前端框架热潮

产生句俗话说的好——“人呀,要是擅于开口‘关我屁事’和‘关你屁事’这俩句,可以节约人生遭遇之多数时日”。

乘势这有限年为 angular
牵头带来从的各种前端MV*框架的流行,后端可以毋须再为静态页面耗费心思,只需要全身心开发数据接口供前端采用即可。得益于斯,前后端终于得以欣慰地互相道一望“关我屁事”或“关你屁事”了。

因 avalon
为条例,前端只需要在页面加载时发送个ajax请求取得数据绑定到vm,然后开view层渲染即可:

var vm = avalon.define({ $id: “wrap”, list: [] });
fetch(‘data/list.php’) //向后端接口发出请求 .then(res => res.json())
.then(json => { vm.list = json; //数据注入vm avalon.scan();
//渲染view层 });

1
2
3
4
5
6
7
8
9
10
11
var vm = avalon.define({
    $id: "wrap",
    list: []
});
 
fetch(‘data/list.php’)   //向后端接口发出请求
    .then(res => res.json())
    .then(json => {
        vm.list = json; //数据注入vm
        avalon.scan();  //渲染view层
    });

静态页面的代码也由前端一手掌握,原本服务端的代码换成了 avalaon
的专用属性与插值表达式:

ul ms-controller=”wrap”> li ms-repeat=”list”>{el.name}li>
ul>

1
2
3
ul ms-controller="wrap">
    li ms-repeat="list">{el.name}li>
ul>

内外端代码隔离的款式大大升级了品种之可维护性和开支效率,已经化为同种web开发之主流模式。它解放了后端程序员的手,也以再多之控制权转移给前端人员(当然前端也为此用多学学一些框架知识)。

图片 4

弊端

上下端隔离的模式则给出带来了福利,但相比水乳交融的固有模式,页面首屏的数额要以加载的时光向服务端发去请求才能够得到,多了要等候之年华(RTT)。

即时意味着用户访问页面的时候,这段“等待后端返回数据”的时延会处在白屏状态,如果用户网速差,那么就段首屏等候时间会是杀糟糕的感受。

理所当然拉到数码后,还得做 view
层渲染(客户端引擎的拍卖或者快的,忽小渲染之时光),这同时凭让框架本身,即框架而先行让下充斥下来才会处理这些视图渲染操作。那么好武器,一个
angular.min.js 就达成了 120
多KB,用着渣信号的用户得几近等上一两秒来下充斥她。

如此这般看来,单纯前后端隔离的样式是首屏时间比较丰富之题目,除非未来平均网速达到上G/s,不然都是匪佳之心得。

除此以外利用前端框架的页面吗不便利SEO,其实应当说非便宜国内这些渣搜索引擎的SEO,谷歌早已能打内存中错过抓捕数据(客户端渲染后的DOM数据)。

so 怎么收拾?相信广大冤家猜到了——用 node 来助阵。

图片 5

直出与同构

简直来说白了实际上就是是“服务端渲染并出口”,跟起初我们提及的光景端水乳交融之支出模式为主相仿,只是后端语言我们换成了
node 。

09年开班冒头的 node
现在变成了当红炸子鸡,包含阿里、腾讯在内的各大商店还普遍地把 node
用到品种上,前后端整而也同一,如果 node
的表征适用于您的档次,那么何乐而不呢呢。

咱于马上边也提及了一个“同构”的概念,即上下端(这里的“后端”指的是直出端,数据接口不必然由node开发)运同样套代码方案,方便维护。

此时此刻 node 在服务端有着广大主流或非主流的框架,包括
express、koa、thinkjs 等,能够比快上亲手,利用各种中间件好开展高效开发。

此外如 ejs、jade
这样的渲染模板能让咱轻松地将首屏内容(数据要渲染好之DOM树)注入页面被。

诸如此类用户访问到的即使是一度包含首屏内容之页面,大大降低了守候时间,提升了体会。

图片 6

示例

每当这里我们盖 koa + ejs + React
的服务端渲染为例,来探视一个简短的“直起”方案是怎贯彻之。该示例为堪在我的github内外充满及。

花色之目录结构如下:

+—data //模拟数据接口,放了一个.json文件 +—dist
//文件构建后(gulp/webpack)存放处 | +—css | | +—common | | —page
| +—js | | +—component | | —page | —views | +—common | —home
+—modules //一些自行封装的通用业务模块 +—routes //路由安排 —src
//未构建的文书夹 +—css | +—common | +—component | —page +—js |
+—component //React组件 | —page //页面入口文件 —views //ejs模板
+—common —home

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
+—data   //模拟数据接口,放了一个.json文件
+—dist  //文件构建后(gulp/webpack)存放处
|   +—css
|   |   +—common
|   |   —page
|   +—js
|   |   +—component
|   |   —page
|   —views
|       +—common
|       —home
+—modules  //一些自行封装的通用业务模块
+—routes  //路由配置
—src  //未构建的文件夹
    +—css
    |   +—common
    |   +—component
    |   —page
    +—js
    |   +—component //React组件
    |   —page //页面入口文件
    —views  //ejs模板
        +—common
        —home

1. node 端 jsx 解析处理

node 端是休见面协调辨认 React 的 jsx
语法的,故我们要在类型文件被引入 node-jsx ,即使现行得以设置 babel-cli
(并累加预设)采用 babel-node 命令替代
node,但后者用起来总会产生问题,故暂还是采纳 node-jsx 方案:

//app.js require(‘node-jsx’).install({ //让node端能解析jsx extension:
‘.js’ }); var fs = require(‘fs’), koa = require(‘koa’), compress =
require(‘koa-compress’), render = require(‘koa-ejs’), mime =
require(‘mime-types’), r_home = require(‘./routes/home’), limit =
require(‘koa-better-ratelimit’), getData = require(‘./modules/getData’);
var app = koa(); app.use(limit({ duration: 1000*10 , max: 500,
accessLimited : “您的呼吁太过频繁,请稍后重试”}) ); app.use(compress({
threshold: 50, flush: require(‘zlib’).Z_SYNC_FLUSH })); render(app, {
//ejs渲染配置 root: ‘./dist/views’, layout: false , viewExt: ‘ejs’,
cache: false, debug: true }); getData(app); //首页路由 r_home(app);
app.use(function*(next){ var p = this.path; this.type = mime.lookup(p);
this.body = fs.createReadStream(‘.’+p); }); app.listen(3300);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//app.js
require(‘node-jsx’).install({  //让node端能解析jsx
    extension: ‘.js’
});
 
var fs = require(‘fs’),
    koa = require(‘koa’),
    compress = require(‘koa-compress’),
    render = require(‘koa-ejs’),
    mime = require(‘mime-types’),
    r_home = require(‘./routes/home’),
    limit = require(‘koa-better-ratelimit’),
    getData = require(‘./modules/getData’);
 
var app = koa();
 
app.use(limit({ duration: 1000*10 ,
    max: 500, accessLimited : "您的请求太过频繁,请稍后重试"})
);
app.use(compress({
    threshold: 50,
    flush: require(‘zlib’).Z_SYNC_FLUSH
}));
 
render(app, {  //ejs渲染配置
    root: ‘./dist/views’,
    layout: false ,
    viewExt: ‘ejs’,
    cache: false,
    debug: true
});
 
getData(app);
 
//首页路由
r_home(app);
 
app.use(function*(next){
    var p = this.path;
    this.type = mime.lookup(p);
    this.body = fs.createReadStream(‘.’+p);
});
 
app.listen(3300);

2. 首页路由(’./routes/home’)配置

var router = require(‘koa-router’), getHost =
require(‘../modules/getHost’), apiRouter = new router(); var React =
require(‘react/lib/ReactElement’), ReactDOMServer =
require(‘react-dom/server’); var List =
React.createFactory(require(‘../dist/js/component/List’));
module.exports = function (app) { var data =
this.getDataSync(‘../data/names.json’), //取首屏数据 json =
JSON.parse(data); var lis = json.map(function(item, i){ return (
<li>{item.name}</li> ) }), props = {color: ‘red’};
apiRouter.get(‘/’, function *() { //首页 yield
this.render(‘home/index’, { title: “serverRender”, syncData: { names:
json, //将取到之首屏数据注入ejs模板 props: props }, reactHtml:
ReactDOMServer.renderToString(List(props, lis)), dirpath: getHost(this)
}); }); app.use(apiRouter.routes()); };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
var router = require(‘koa-router’),
    getHost = require(‘../modules/getHost’),
    apiRouter = new router();
 
var React = require(‘react/lib/ReactElement’),
    ReactDOMServer = require(‘react-dom/server’);
var List = React.createFactory(require(‘../dist/js/component/List’));
 
module.exports = function (app) {
 
    var data = this.getDataSync(‘../data/names.json’),  //取首屏数据
        json = JSON.parse(data);
 
    var lis = json.map(function(item, i){
       return (
           <li>{item.name}</li>
       )
    }),
        props = {color: ‘red’};
 
    apiRouter.get(‘/’, function *() {  //首页
        yield this.render(‘home/index’, {
            title: "serverRender",
            syncData: {
                names: json,  //将取到的首屏数据注入ejs模板
                props: props
            },
            reactHtml:  ReactDOMServer.renderToString(List(props, lis)),
            dirpath: getHost(this)
        });
    });
 
    app.use(apiRouter.routes());
 
};

只顾这里我们使用了 ReactDOMServer.renderToString 来渲染 React 组件为纯
HTML 字符串,注意 List(props, lis) ,我们还传播了 props 和 children。

那于 ejs 模板被的利用为:

div class=”wrap” id=”wrap”>-reactHtml%>div>

1
div class="wrap" id="wrap">-reactHtml%>div>

尽管这么简单地得了劳务端渲染之处理,但还有一样高居问题,如果组件中绑定了风波,客户端不见面感知。

故在客户端我们为欲更开同样软跟服务端一致的渲染操作,鉴于服务端生成的DOM会给起上
data-react-id 标志,故在客户端渲染之口舌,react
会通过该标志位的对待来避免冗余的render,并绑定上相应的波。

立即也是我们将所要注入组件中的多寡(syncData)传入 ejs
的来由,我们将拿它们作为客户端的一个全局变量来使用,方便客户端挂载组件的时段用上:

ejs上渐直起多少:

script> syncData = JSON.parse(”); script>

1
2
3
  script>
    syncData = JSON.parse(”);
  script>

页面入口文件(js/page/home.js)挂载组件:

import React from ‘react’; import ReactDOM from ‘react-dom’; var List =
require(‘../component/List’); var lis =
syncData.names.map(function(item, i){ return (
<li>{item.name}</li> ) }); ReactDOM.render( <List
{…syncData.props}> {lis} </List>,
document.getElementById(‘wrap’) );

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React from ‘react’;
import ReactDOM from ‘react-dom’;
var List = require(‘../component/List’);
 
var lis = syncData.names.map(function(item, i){  
    return (
        <li>{item.name}</li>
    )
});
ReactDOM.render(
    <List {…syncData.props}>
        {lis}
    </List>,
    document.getElementById(‘wrap’)
);

3. 辅助工具

为玩鲜,在一些模块里写了 es2015 的语法,然后使用 babel
来做转换处理,在 gulp 和 webpack 中还产生利用到,具体而参照其的部署。

除此以外由于服务端对 es2015 的风味支持非完,配合 babel-core/register
或者下 babel-node
命令还留存兼容问题,故对所有需要以服务端引入到的模块(比如React组件),在koa运行前先行开gulp处理转为es5(这些构建模块仅以服务端会因此到,客户端走webpack直接引用未移模块即可)。

ejs文件中样式或脚本的内联处理自己使用了和睦支付的
gulp-embed
,有趣味的朋友可以打同样玩耍。

4. issue

说实话 React
的劳务端渲染处理一体化开发是从来不问题的,就是出体验不敷好,主要由要各个面针对
es2015 支持不完了导致的。

尽管当服务端运行前,我们以gulp中采用babel对相关模块进行更换,但比如 export
default XXX

这样的语法转换后还是无法给服务端支持,只能降级写吗 module.exports =
XXX
。但这么形容,在其它模块就无法 import XXX from ‘X’ 了(改为
require(‘X’)代替)
,总的无好受。只能希望后续 node(其实应当说V8)
再迭代一些本会再好地支持 es2015 的特点。

另外如 React 组件涉及列表项,常规我们会添加 key
的props特性来提升渲染效率,但纵然前后端传入相同之key值,最终 React
渲染出来的 key 值是匪平等的,会促成客户端挂载组件时再也做相同次等渲染处理。

对此这点我个人建议是,如果是静态的列表,那么统一且非加 key
,如果是动态的,那么就算加吧,客户端再渲染一不折不扣感觉吧从未多可怜点从。(或者您生出更好方案要留言哈~)

5. 其它

偶然服务端引入的模块里面,有些东西是特要以客户端采用及的,我们坐这示例中之零部件component/List也例,里面的体裁文件

require(‘css/component/List’);

1
require(‘css/component/List’);

匪该以服务端执行的早晚以及,但出于同构,前后端用的同样仿东西,这个怎么化解吗?其实生好惩治,通过
window
对象来判断即可(只要没有啊中间件为你在服务端也加以了window接口)

var isNode = typeof window === ‘undefined’; if(!isNode){
require(‘css/component/List’); }

1
2
3
4
5
var isNode = typeof window === ‘undefined’;
 
if(!isNode){
    require(‘css/component/List’);
}

然请小心,这里自己透过 webpack
把组件的体也打包上了客户端的页面入口文件,其实不服帖。因为经直出,页面在应的下就早已把组件的DOM树都先出示出来了,但这个时刻是还没取到样式的(样式打包到进口下部论了),需要相当及进口脚本加载的早晚才会见到对的体裁,这个过程会来一个眨眼的过程,是种植不爽快的体验。

故而走直来之讲话,建议把首屏的样式抽离出来内联到头部去。

1 赞 2 收藏 2
评论

图片 7

1拘禁(fang)到(da)了大百姓的无知,以及普法的贫乏。
2情以及具象的要紧冲突,完全不能够因此巧合来覆盖,农村妇女一年之后之内认识了由县及政治文化骨干的企业主,车子想拦截就拦截获。想不叫警卫查出来就能免叫翻出来。
3胜似行扭曲法律程序,人家说之不可开交了解,事情解决途径很清楚,权责很显然。人家无受而的案跟吸纳不了凡少拨事。
4究竟是啊支撑了它们底10年告状?如果是控诉官员,他们得手下了,如果是以他前夫的言辞?应该是匪是告离婚案件还要告他前夫人身攻击污蔑什么的吧!或者是未是潘金莲这词话提出来自我就莫名其妙!!!提出来的早晚尴尬到顶!!想证明什么?批判渣男?直男癌?还是嘲讽历史及之潘金莲???招谁挑起谁了?揪着潘金莲是梗不放开?潘金莲说到底都是一代的结局,锅不欠一个口背!!!而且李雪莲说真除了她前夫的那句话,和潘金莲没有其他关系!!浓浓的标题党浓浓的腐臭味!
5主意成就非常得意,可是情节浪费就是这样的景色画面配乐!传统乐器得到了深好之扶情节辣鸡的作用!
6末段,浪费了自家宝贵的休息时间,被拖延在去押的,没悟出是这种矫情做作垃圾,如果是千篇一律开始青春忧伤疼痛的录像,根本就是非会见产生希。

相关文章

标签:

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图