麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁(yè) > 課堂 > FAQ問(wèn)答 > 正文

降低首屏?xí)r間 “直出”是個(gè)什么概念-

2020-03-22 16:28:41
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友
  • 早幾年前端還處于刀耕火種、JQuery獨(dú)樹(shù)一幟的時(shí)代,前后端代碼的耦合度很高,一個(gè)web頁(yè)面文件的代碼可能是這樣的:

    這意味著后端的工程師往往得負(fù)責(zé)一部分修改HTML、編寫(xiě)腳本的工作,而前端開(kāi)發(fā)者也得了解頁(yè)面上存在的服務(wù)端代碼含義。

    有時(shí)候某處頁(yè)面邏輯的變動(dòng),鑒于代碼的混搭,可能都不確定應(yīng)該請(qǐng)后端還是前端來(lái)改動(dòng)(即使他們都能處理)。

    前端框架熱潮

    有句俗話(huà)說(shuō)的好——“人啊,要是擅于開(kāi)口‘關(guān)我屁事’和‘關(guān)你屁事’這倆句,可以節(jié)省人生中的大部分時(shí)間”。

    隨著這兩年被 angular 牽頭帶起的各種前端MV*框架的風(fēng)靡,后端可以毋須再于靜態(tài)頁(yè)面耗費(fèi)心思,只需要專(zhuān)心開(kāi)發(fā)數(shù)據(jù)接口供前端使用即可。得益于此,前后端終于可以安心地互相道一聲“關(guān)我屁事”或“關(guān)你屁事”了。

    以 avalon 為例,前端只需要在頁(yè)面加載時(shí)發(fā)送個(gè)ajax請(qǐng)求取得數(shù)據(jù)綁定到vm,然后做view層渲染即可:

    var vm = avalon.define({    $id: 'wrap',    list: []});fetch('data/list.php')   //向后端接口發(fā)出請(qǐng)求    .then(res => res.json())    .then(json => {        vm.list = json; //數(shù)據(jù)注入vm        avalon.scan();  //渲染view層    });

    靜態(tài)頁(yè)面的代碼也由前端一手掌握,原本服務(wù)端的代碼換成了 avalaon 的專(zhuān)用屬性與插值表達(dá)式:

    <ul ms-controller='wrap'>    <li ms-repeat='list'>{el.name}</li></ul>

    前后端代碼隔離的形式大大提升了項(xiàng)目的可維護(hù)性和開(kāi)發(fā)效率,已經(jīng)成為一種web開(kāi)發(fā)的主流模式。它解放了后端程序員的雙手,也將更多的控制權(quán)轉(zhuǎn)移給前端人員(當(dāng)然前端也因此需要多學(xué)習(xí)一些框架知識(shí))。

    弊端

    前后端隔離的模式雖然給開(kāi)發(fā)帶來(lái)了便利,但相比水乳交融的舊模式,頁(yè)面首屏的數(shù)據(jù)需要在加載的時(shí)候向服務(wù)端發(fā)去請(qǐng)求才能取得,多了請(qǐng)求等候的時(shí)間(RTT)。

    這意味著用戶(hù)訪(fǎng)問(wèn)頁(yè)面的時(shí)候,這段“等待后端返回?cái)?shù)據(jù)”的時(shí)延會(huì)處于白屏狀態(tài),如果用戶(hù)網(wǎng)速差,那么這段首屏等候時(shí)間會(huì)是很糟糕的體驗(yàn)。

    當(dāng)然拉到數(shù)據(jù)后,還得做 view 層渲染(客戶(hù)端引擎的處理還是很快的,忽略渲染的時(shí)間),這又依賴(lài)于框架本身,即框架要先被下載下來(lái)才能處理這些視圖渲染操作。那么好家伙,一個(gè) angular.min.js 就達(dá)到了 120 多KB,用著渣信號(hào)的用戶(hù)得多等上一兩秒來(lái)下載它。

    這么看來(lái),單純前后端隔離的形式存在首屏?xí)r間較長(zhǎng)的問(wèn)題,除非未來(lái)平均網(wǎng)速達(dá)到上G/s,不然都是不理想的體驗(yàn)。

    so 怎么辦?相信很多朋友猜到了——用 node 來(lái)助陣。

    直出和同構(gòu)

    直出說(shuō)白了其實(shí)就是“服務(wù)端渲染并輸出”,跟起初我們提及的前后端水乳交融的開(kāi)發(fā)模式基本類(lèi)似,只是后端語(yǔ)言我們換成了 node 。

    09年開(kāi)始冒頭的 node 現(xiàn)在成了當(dāng)紅炸子雞,包含阿里、騰訊在內(nèi)的各大公司都廣泛地把 node 用到項(xiàng)目上,前后端整而為一,如果 node 的特性適用于你的項(xiàng)目,那么何樂(lè)而不為呢。

    我們?cè)谶@邊也提及了一個(gè)“同構(gòu)”的概念,即前后端(這里的“后端”指的是直出端,數(shù)據(jù)接口不一定由node開(kāi)發(fā))使用同一套代碼方案,方便維護(hù)。

    當(dāng)前 node 在服務(wù)端有著許多主流抑或非主流的框架,包括 express、koa、thinkjs 等,能夠較快上手,利用各種中間件得以進(jìn)行敏捷開(kāi)發(fā)。

    另外諸如 ejs、jade 這樣的渲染模板能讓我們輕松地把首屏內(nèi)容(數(shù)據(jù)或渲染好的DOM樹(shù))注入頁(yè)面中。

    這樣用戶(hù)訪(fǎng)問(wèn)到的便是已經(jīng)帶有首屏內(nèi)容的頁(yè)面,大大降低了等候時(shí)間,提升了體驗(yàn)。

    示例

    在這里我們以 koa + ejs + React 的服務(wù)端渲染為例,來(lái)看看一個(gè)簡(jiǎn)單的“直出”方案是怎樣實(shí)現(xiàn)的。該示例也可以在我的github上下載到。

    項(xiàng)目的目錄結(jié)構(gòu)如下:

    +---data   //模擬數(shù)據(jù)接口,放了一個(gè).json文件+---dist  //文件構(gòu)建后(gulp/webpack)存放處|   +---css|   |   +---common|   |   ---page|   +---js|   |   +---component|   |   ---page|   ---views|       +---common|       ---home+---modules  //一些自行封裝的通用業(yè)務(wù)模塊+---routes  //路由配置---src  //未構(gòu)建的文件夾    +---css     |   +---common    |   +---component    |   ---page    +---js    |   +---component //React組件    |   ---page //頁(yè)面入口文件    ---views  //ejs模板        +---common        ---home

    1. node 端 jsx 解析處理

    node 端是不會(huì)自己識(shí)別 React 的jsx 語(yǔ)法的,故我們需要在項(xiàng)目文件中引入node-jsx,即使現(xiàn)在可以安裝babel-cli 后(并添加預(yù)設(shè))使用 babel-node 命令替代 node,但后者用起來(lái)總會(huì)出問(wèn)題,故暫時(shí)還是采納 node-jsx 方案:

    //app.jsrequire('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 : '您的請(qǐng)求太過(guò)頻繁,請(qǐng)稍后重試'}));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);//首頁(yè)路由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. 首頁(yè)路由('./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'),  //取首屏數(shù)據(jù)        json = JSON.parse(data);    var lis = json.map(function(item, i){       return (           <li>{item.name}</li>       )    }),        props = {color: 'red'};    apiRouter.get('/', function *() {  //首頁(yè)        yield this.render('home/index', {            title: 'serverRender',            syncData: {                names: json,  //將取到的首屏數(shù)據(jù)注入ejs模板                props: props            },            reactHtml:  ReactDOMServer.renderToString(List(props, lis)),            dirpath: getHost(this)        });    });    app.use(apiRouter.routes());};

    注意這里我們使用了ReactDOMServer.renderToString 來(lái)渲染 React 組件為純 HTML 字符串,注意 List(props, lis) ,我們還傳入了 props 和 children。

    其在 ejs 模板中的應(yīng)用為:

    <div html' target='_blank'>class='wrap' id='wrap'><%-reactHtml%></div>

    就這么簡(jiǎn)單地完成了服務(wù)端渲染的處理,但還有一處問(wèn)題,如果組件中綁定了事件,客戶(hù)端不會(huì)感知。

    所以在客戶(hù)端我們也需要再做一次與服務(wù)端一致的渲染操作,鑒于服務(wù)端生成的DOM會(huì)被打上 data-react-id 標(biāo)志,故在客戶(hù)端渲染的話(huà),react 會(huì)通過(guò)該標(biāo)志位的對(duì)比來(lái)避免冗余的render,并綁定上相應(yīng)的事件。

    這也是我們把所要注入組件中的數(shù)據(jù)(syncData)傳入 ejs 的原因,我們將把它作為客戶(hù)端的一個(gè)全局變量來(lái)使用,方便客戶(hù)端掛載組件的時(shí)候用上:

    ejs上注入直出數(shù)據(jù):

      <script>    syncData = JSON.parse('<%- JSON.stringify(syncData) %>');  </script>

    頁(yè)面入口文件(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'));

    3. 輔助工具

    為了玩鮮,在部分模塊里寫(xiě)了 es2015 的語(yǔ)法,然后使用 babel 來(lái)做轉(zhuǎn)換處理,在 gulp 和 webpack 中都有使用到,具體可參考它們的配置。

    另外鑒于服務(wù)端對(duì) es2015 的特性支持不完整,配合 babel-core/register 或者使用 babel-node 命令都存在兼容問(wèn)題,故針對(duì)所有需要在服務(wù)端引入到的模塊(比如React組件),在koa運(yùn)行前先做gulp處理轉(zhuǎn)為es5(這些構(gòu)建模塊僅在服務(wù)端會(huì)用到,客戶(hù)端走webpack直接引用未轉(zhuǎn)換模塊即可)。

    ejs文件中樣式或腳本的內(nèi)聯(lián)處理我使用了自己開(kāi)發(fā)的 gulp-embed ,有興趣的朋友可以玩一玩。

    4. issue

    說(shuō)實(shí)話(huà) React 的服務(wù)端渲染處理整體開(kāi)發(fā)是沒(méi)問(wèn)題的,就是開(kāi)發(fā)體驗(yàn)不夠好,主要原因還是各方面對(duì) es2015 支持不到位導(dǎo)致的。

    雖然在服務(wù)端運(yùn)行前,我們?cè)趃ulp中使用babel對(duì)相關(guān)模塊進(jìn)行轉(zhuǎn)換,但像export default XXX 這樣的語(yǔ)法轉(zhuǎn)換后還是無(wú)法被服務(wù)端支持,只能降級(jí)寫(xiě)為module.exports = XXX。但這么寫(xiě),在其它模塊就沒(méi)法 import XXX from 'X' 了(改為 require('X')代替),總之不爽快。只能期待后續(xù) node(其實(shí)應(yīng)該說(shuō)V8) 再迭代一些版本能更好地支持 es2015 的特性。

    另外如果 React 組件涉及列表項(xiàng),常規(guī)我們會(huì)加上 key 的props特性來(lái)提升渲染效率,但即使前后端傳入相同的key值,最終 React 渲染出來(lái)的 key 值是不一致的,會(huì)導(dǎo)致客戶(hù)端掛載組件時(shí)再做一次渲染處理。

    對(duì)于這點(diǎn)我個(gè)人建議是,如果是靜態(tài)的列表,那么統(tǒng)一都不加 key ,如果是動(dòng)態(tài)的,那么就加吧,客戶(hù)端再渲染一遍感覺(jué)也沒(méi)多大點(diǎn)事。(或者你有更好方案請(qǐng)留言哈~)

    5. 其它

    有時(shí)候服務(wù)端引入的模塊里面,有些東西是僅僅需要在客戶(hù)端使用到的,我們以這個(gè)示例中的組件 component/List 為例,里面的樣式文件

    require('css/component/List');

    不應(yīng)當(dāng)在服務(wù)端執(zhí)行的時(shí)候使用到,但鑒于同構(gòu),前后端用的一套東西,這個(gè)怎么解決呢?其實(shí)很好辦,通過(guò) window 對(duì)象來(lái)判斷即可(只要沒(méi)有什么中間件給你在服務(wù)端也加了window接口)

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

    不過(guò)請(qǐng)注意,這里我通過(guò) webpack 把組件的樣式也打包進(jìn)了客戶(hù)端的頁(yè)面入口文件,其實(shí)不妥當(dāng)。因?yàn)橥ㄟ^(guò)直出,頁(yè)面在響應(yīng)的時(shí)候就已經(jīng)把組件的DOM樹(shù)都先顯示出來(lái)了,但這個(gè)時(shí)候是還沒(méi)有取到樣式的(樣式打包到入口腳本了),需要等到入口腳本加載的時(shí)候才能看到正確的樣式,這個(gè)過(guò)程會(huì)有一個(gè)閃動(dòng)的過(guò)程,是種不舒服的體驗(yàn)。

    所以走直出的話(huà),建議把首屏的樣式抽離出來(lái)內(nèi)聯(lián)到頭部去。

    嘮嘮磕磕就說(shuō)了這么多,歡迎討論交流,共勉~

    PHP編程

    鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請(qǐng)第一時(shí)間聯(lián)系我們修改或刪除,多謝。

  • 發(fā)表評(píng)論 共有條評(píng)論
    用戶(hù)名: 密碼:
    驗(yàn)證碼: 匿名發(fā)表
    主站蜘蛛池模板: 9999精品 | 欧美精品一区二区久久 | 国产免费视频一区二区裸体 | 国产高潮国产高潮久久久91 | 欧美日韩免费一区 | 欧美精品亚洲人成在线观看 | 国产一级免费不卡 | 久久久久久久黄色片 | 精品国产精品久久 | 欧洲成人综合网 | 亚洲免费视频大全 | 久久综合艹 | 最新欧美精品一区二区三区 | 国产小视频在线观看 | 久久久久久免费免费 | 亚洲成人在线视频网站 | 成人免费视频视频在线观看 免费 | 欧美一级毛片欧美一级成人毛片 | 精品一区二区三区免费 | 一级在线观看 | 男人天堂新地址 | 中日韩乱码一二新区 | 特色一级黄色片 | av在线播放免费观看 | 亚洲一区 国产精品 | 日本欧美一区二区三区视频麻豆 | 精品一区二区在线播放 | av在线等| 黄色aaa视频 | 免费放黄网站在线播放 | 精品一区二区久久久久久按摩 | 182tv成人福利视频免费看 | 精品国产看高清国产毛片 | 午夜精品网站 | 精品国产精品久久 | 视频二区国产 | a视频网站 | 欧美18—19sex性护士中国 | 青青草最新网址 | 午夜精品久久久久久久99热浪潮 | 中文字幕国 |