摘要:今天,其實講的是在實現同構過程中看到過,可能非常容易被忽視更小的一個點。每一個架構的框架都會涉及到層的展現,也不例外。這種說法即對也不對。總結其實,實現非常簡單,我們也從一些維度看到了設計一個的一般方法。
在之前我們有過一篇『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: "", 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 編譯,因此寫的其實是
美化 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 來描述過程。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/79567.html
摘要:后面會利用這個框架來做實踐。接下來就是我們要繼續探討的同構同構數據處理的探討我們都知道,瀏覽器端獲取數據需要發起請求,實際上發起的請求就是對應服務端一個路由控制器。是有生命周期的,官方給我們指出的綁定,應該在里來進行。 眾所周知,目前的 WEB 應用,用戶體驗要求越來越高,WEB 交互變得越來越豐富!前端可以做的事越來越多,去年 Node 引領了前后端分層的浪潮,而 React 的出現...
摘要:前言什么這是一篇源碼解讀文章那一定很枯燥不看。通過利用函數,幫你丟棄回調函數,并有力地增強錯誤處理。并沒有捆綁任何中間件,而是提供了一套優雅的方法,幫助您快速而愉快地編寫服務端應用程序。 showImg(https://segmentfault.com/img/bVNQYf?w=1020&h=790); 前言 什么?這是一篇源碼解讀文章 ? 那一定很枯燥!不看。 我把 Koa 的核心實...
摘要:實現的四大模塊上文簡述了源碼的大體框架結構,接下來我們來實現一個的框架,筆者認為理解和實現一個框架需要實現四個大模塊,分別是封裝創建類構造函數構造對象中間件機制和剝洋蔥模型的實現錯誤捕獲和錯誤處理下面我們就逐一分析和實現。 什么是koa框架? ? ? ? ?koa是一個基于node實現的一個新的web框架,它是由express框架的原班人馬打造的。它的特點是優雅、簡潔、表達力強、自由度...
摘要:實現的四大模塊上文簡述了源碼的大體框架結構,接下來我們來實現一個的框架,筆者認為理解和實現一個框架需要實現四個大模塊,分別是封裝創建類構造函數構造對象中間件機制和剝洋蔥模型的實現錯誤捕獲和錯誤處理下面我們就逐一分析和實現。 什么是koa框架? ? ? ? ?koa是一個基于node實現的一個新的web框架,它是由express框架的原班人馬打造的。它的特點是優雅、簡潔、表達力強、自由度...
閱讀 2912·2021-10-19 10:09
閱讀 3131·2021-10-09 09:41
閱讀 3378·2021-09-26 09:47
閱讀 2692·2019-08-30 15:56
閱讀 597·2019-08-29 17:04
閱讀 984·2019-08-26 11:58
閱讀 2509·2019-08-26 11:51
閱讀 3360·2019-08-26 11:29