摘要:今天,其實(shí)講的是在實(shí)現(xiàn)同構(gòu)過程中看到過,可能非常容易被忽視更小的一個(gè)點(diǎn)。每一個(gè)架構(gòu)的框架都會涉及到層的展現(xiàn),也不例外。這種說法即對也不對。總結(jié)其實(shí),實(shí)現(xiàn)非常簡單,我們也從一些維度看到了設(shè)計(jì)一個(gè)的一般方法。
在之前我們有過一篇『React 同構(gòu)實(shí)踐與思考』的專欄文章,給讀者實(shí)踐了用 React 怎么實(shí)現(xiàn)同構(gòu)。今天,其實(shí)講的是在實(shí)現(xiàn)同構(gòu)過程中看到過,可能非常容易被忽視更小的一個(gè)點(diǎn) —— React View。
React View每一個(gè) BS 架構(gòu)的框架都會涉及到 View 層的展現(xiàn),Koa 也不例外。我們在做 View 層的時(shí)候有兩種做法,一種是做成插件形式,對于 View 來說就是模板引擎,另一種是做成中件間的形式。
再說到 React,常常有人說它是增強(qiáng)版的模板引擎。這種說法即對也不對。
從表象來看的確,React 可以替換變量,有條件判斷,有循環(huán)判斷,JSX 語法讓渲染過程和 HTML 沒什么兩樣,畢竟說到底 React 就是 JavaScript,而 React 所推崇的無狀態(tài)函數(shù),也徹徹底底把 React 變成了像是模板的樣子。
從內(nèi)在來看,React 它還是 JavaScript,它可以方便地做模塊化管理,有內(nèi)部狀態(tài),有自己的數(shù)據(jù)流。它可以做一部分 Controller,或者說,可以完全承擔(dān) Controller 的工作。
但是在服務(wù)端,我們需要模板是為了作 HTML 的同步請求,因此說地簡單一些就只需要渲染成 HTML 的功能就可以了。當(dāng)然,特殊的一點(diǎn)是,之所以讓 React 作模板就是可以讓服務(wù)端跑到客戶端的渲染邏輯,并解決單頁應(yīng)用常常詬病的加載后白屏的問題。
言歸正傳,現(xiàn)在我們就帶著 React View 怎么實(shí)現(xiàn)這個(gè)問題來解讀源碼。
React-View 源碼解讀 配置配置是設(shè)計(jì)的源頭之一,一切源碼都可以從配置入手研究。
var defaultOptions = { doctype: "", 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 的配置有幾點(diǎn)不同。doctype、internals 這些配置都是其它模板引擎不會有的。
模板常用的配置應(yīng)該是什么呢?
viewPath,在上述配置指的是 view,就是 View 的目錄在哪里,這是每一個(gè)模板插件或中間件都需要去配的。
extname,后綴名是什么,一般來說模板引擎都有自己獨(dú)有的后綴,當(dāng)然不排除可以有喜好選擇的情況。比如對 React 而言,就可以寫成是 .jsx 或 .js 兩種不同的形式。
cache,我想一般模板引擎都會帶 cache 功能,因?yàn)槟0宓慕馕鍪切枰馁M(fèi)資源的,而模板本身的改動的頻度是非常低的。每當(dāng)發(fā)布的時(shí)候,我們?nèi)ニ⑿乱淮文0寮纯?。但上述配置中?cache 并不是指這個(gè),我們等讀源碼時(shí)再來看。
渲染標(biāo)準(zhǔn)的渲染過程其實(shí)非常的簡單。對于 React 來說就是讀取目錄下的文件,像前端加載一樣,require 那個(gè)文件。最后利用 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
這里我們截取最關(guān)鍵的片段,正如我們預(yù)估的渲染過程一樣。但我們看到,從流程上看有四個(gè)細(xì)節(jié):
設(shè)置 doctype 的目的
在一般模板中我們很少看到將 doctype 放在配置中配置,但因?yàn)?React 的特殊性,讓我們不得不這么做。原因很簡單,React render 方法返回時(shí)一定需要一個(gè)包裹的元素,比如 div,ul,甚至 html,因此,我們需要手動去加 doctype。
渲染 React 組件
renderToString 和 renderToStaticMarkup 都是 "react-dom/server" 下的方法,與 render 不同,render 方法需要指定具體渲染到 DOM 上的節(jié)點(diǎn),但那兩個(gè)方法都只返回一段 HTML 字符串。這一點(diǎn)讓 React 成為模板語言而存在。它們兩個(gè)方法的區(qū)別在于:
renderToString 方法渲染的時(shí)候帶有 data-reactid 屬性,意味著可以做 server render,React 在前端會認(rèn)識服務(wù)端渲染的內(nèi)容,不會重新渲染 DOM 節(jié)點(diǎn),開始執(zhí)行 componentDidMount 繼續(xù)執(zhí)行后續(xù)生命周期。
renderToStaticMarkup 方法渲染時(shí)沒有 data-reactid,把 React 當(dāng)做是純模板來使用,這個(gè)時(shí)候只渲染 body 外的框架是比較合適的。
在 render 方法里,我們看到 React.createElement 方法。是因?yàn)樵诜?wù)端 render 方法沒有 babel 編譯,因此寫的其實(shí)是
美化 HTML
options.beautify 配置了我們是否要美化 HTML,默認(rèn)時(shí)是關(guān)閉的。任何需要編譯的模板引擎一般都會有類似的配置。在 Reat 中,因?yàn)?render 后的代碼是一連串的字符串,返回到前臺的時(shí)候都是無法閱讀的代碼。在有必要時(shí),我們可以開啟這個(gè)配置。
綁定到上下文
最后一步,盡管有一個(gè)開關(guān)控制,但我們看到最后是把內(nèi)容綁定到 this.body 下的。 這里省略了整個(gè)實(shí)現(xiàn)過程是在 app.context.render 方法下,即是重寫了 app.context 下的 render 方法,用于渲染 React。如果說 app.context.render 方法是 function*,那么我們的 react-view,就會變?yōu)橹虚g件。
Cache我們從一開始就看到了配置中就有 cache 配置,這個(gè) cache 是不是我們所想呢?我們來看下源代碼:
// match function for cache clean var match = createMatchFunction(options.views); ... if (!options.cache) { cleanCache(match); }
這里的 cache 指的是模板緩存么。事實(shí)上不完全是,我們來看一下 cleanCache 方法就明白了:
function cleanCache(match) { Object.keys(require.cache).forEach(function(module) { if (match(require.cache[module].filename)) { delete require.cache[module]; } }); }
因?yàn)槲覀冏x取 React 文件用的是 require 方法,而在 Node 中 require 方法是有緩存的,Node 在每個(gè)第一次 Load Module 時(shí)就會將該 Module 緩存,存入全局的 _cache 中,在一般情況下我們當(dāng)然需要這么做。但在模板加載這個(gè)情景下就不同了。
在這里的確我們?nèi)志彺媪?React 模板文件,但這個(gè)文件是編譯前的文件。而我們需要緩存的是編譯后的文件,也就是說 markup 是我們需要緩存的值。
在這里我們想想怎么去實(shí)現(xiàn),方便起見,我們可以新增一個(gè) lru-cache,用它的好處是 lru 封裝了很多關(guān)于 cache 時(shí)效與容量的開關(guān)。
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); } }
當(dāng)然,我們現(xiàn)在這種情形下都需要清除 require 的 cache。
Babel我想很多開發(fā)者在寫 React 組件的時(shí)候用的是 ES6 Class 來寫的,而且會用到很多 ES6/ES7 的方法,不巧的是 Node 還不支持有些高級特性。因此就引到了一個(gè)話題,服務(wù)端怎么引用 babel?
在業(yè)務(wù)有 babel-node 這類解決方案,但這畢竟是一個(gè)實(shí)驗(yàn)性的 Node,我們不會拿生產(chǎn)環(huán)境去冒險(xiǎn)。
在 koa/react-view 中間件內(nèi),有一段說明,它建議開發(fā)者在使用的時(shí)候加入 babel-register 作實(shí)時(shí)編譯。關(guān)于這個(gè)問題,當(dāng)然也可以寫在中間件內(nèi),在加載模板前引入。隨著 Node 對 ES6 方法支持的完善,也許有一天也用不到了。
總結(jié)其實(shí),實(shí)現(xiàn) View 非常簡單,我們也從一些維度看到了設(shè)計(jì)一個(gè) xx-view 的一般方法。在具體實(shí)現(xiàn)的時(shí)候,我們可以用一些更好的方法去做,比如用類來抽象 View,用 Promise 來描述過程。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/79567.html
摘要:后面會利用這個(gè)框架來做實(shí)踐。接下來就是我們要繼續(xù)探討的同構(gòu)同構(gòu)數(shù)據(jù)處理的探討我們都知道,瀏覽器端獲取數(shù)據(jù)需要發(fā)起請求,實(shí)際上發(fā)起的請求就是對應(yīng)服務(wù)端一個(gè)路由控制器。是有生命周期的,官方給我們指出的綁定,應(yīng)該在里來進(jìn)行。 眾所周知,目前的 WEB 應(yīng)用,用戶體驗(yàn)要求越來越高,WEB 交互變得越來越豐富!前端可以做的事越來越多,去年 Node 引領(lǐng)了前后端分層的浪潮,而 React 的出現(xiàn)...
摘要:前言什么這是一篇源碼解讀文章那一定很枯燥不看。通過利用函數(shù),幫你丟棄回調(diào)函數(shù),并有力地增強(qiáng)錯(cuò)誤處理。并沒有捆綁任何中間件,而是提供了一套優(yōu)雅的方法,幫助您快速而愉快地編寫服務(wù)端應(yīng)用程序。 showImg(https://segmentfault.com/img/bVNQYf?w=1020&h=790); 前言 什么?這是一篇源碼解讀文章 ? 那一定很枯燥!不看。 我把 Koa 的核心實(shí)...
摘要:實(shí)現(xiàn)的四大模塊上文簡述了源碼的大體框架結(jié)構(gòu),接下來我們來實(shí)現(xiàn)一個(gè)的框架,筆者認(rèn)為理解和實(shí)現(xiàn)一個(gè)框架需要實(shí)現(xiàn)四個(gè)大模塊,分別是封裝創(chuàng)建類構(gòu)造函數(shù)構(gòu)造對象中間件機(jī)制和剝洋蔥模型的實(shí)現(xiàn)錯(cuò)誤捕獲和錯(cuò)誤處理下面我們就逐一分析和實(shí)現(xiàn)。 什么是koa框架? ? ? ? ?koa是一個(gè)基于node實(shí)現(xiàn)的一個(gè)新的web框架,它是由express框架的原班人馬打造的。它的特點(diǎn)是優(yōu)雅、簡潔、表達(dá)力強(qiáng)、自由度...
摘要:實(shí)現(xiàn)的四大模塊上文簡述了源碼的大體框架結(jié)構(gòu),接下來我們來實(shí)現(xiàn)一個(gè)的框架,筆者認(rèn)為理解和實(shí)現(xiàn)一個(gè)框架需要實(shí)現(xiàn)四個(gè)大模塊,分別是封裝創(chuàng)建類構(gòu)造函數(shù)構(gòu)造對象中間件機(jī)制和剝洋蔥模型的實(shí)現(xiàn)錯(cuò)誤捕獲和錯(cuò)誤處理下面我們就逐一分析和實(shí)現(xiàn)。 什么是koa框架? ? ? ? ?koa是一個(gè)基于node實(shí)現(xiàn)的一個(gè)新的web框架,它是由express框架的原班人馬打造的。它的特點(diǎn)是優(yōu)雅、簡潔、表達(dá)力強(qiáng)、自由度...
閱讀 2919·2021-10-19 10:09
閱讀 3140·2021-10-09 09:41
閱讀 3391·2021-09-26 09:47
閱讀 2702·2019-08-30 15:56
閱讀 604·2019-08-29 17:04
閱讀 993·2019-08-26 11:58
閱讀 2515·2019-08-26 11:51
閱讀 3369·2019-08-26 11:29