React?跨端動態(tài)化核心技術實例分析
一 前言
React 在跨端動態(tài)化方向上有著得天獨厚的優(yōu)勢,這和 React 本身的設計是密不可分的,目前市面上有很多成熟的方案,比如:
1 React Native 是一個經(jīng)典動態(tài)化方案。React Native 邏輯層是由 JS 處理的,而渲染是由 Native 完成的,這使得 React Native 能夠保持 React 開發(fā)特性,同樣又有原生渲染的性能。
2 Taro React 也是一個不錯的跨端解決方案,Taro 3 由重編譯,輕運行時,變成了輕編譯,重運行時,這使得 React 運行時的代碼能夠真正的運行到由 Taro 構建的 web 或者小程序應用中。
3 目前很多大廠自己也有一套以 React 為 DSL 的動態(tài)化框架,它們有一個共性就是,保持 JSX 靈活的語法特性,不僅如此,還可以保持 React 的活性,也就是說,同時保持了和 React 一樣的 api 設計和語法規(guī)范。
如上是 React 在跨端領域落地的方案,為什么 React 在跨端領域這么受歡迎呢?接下來我們具體展開說說。
二 React 技術核心優(yōu)勢分析
1 數(shù)據(jù)驅動模型
React 和 Redux 的數(shù)據(jù)通信架構模型都和 Flux 架構類似,我們先來看看 Flux ,F(xiàn)lux 是 Facebook 提出的一種前端應用架構模式,它本身并不是一個 UI 框架,而是一種以單向數(shù)據(jù)流為核心思想的設計理念。
在 Flux 思想中有三個組成部分,那就是 dispatcher,store,和 view。下面來看一下三者的職責。
- dispatcher: 其中更改數(shù)據(jù),分發(fā)事件,就是由 dispatcher 來實現(xiàn)的。
- store : store 為數(shù)據(jù)層,負責保存數(shù)據(jù),并且相應事件,更新數(shù)據(jù)源。
- view :view 層可以訂閱更新,當數(shù)據(jù)發(fā)生更新的時候,負責通知視圖重新渲染 UI。
在 React 應用中,setState 為更改視圖的工具,state 為數(shù)據(jù)層,view 為視圖層,如果想要更改視圖,那么通過 setState 改變 state ,在重新渲染組件得到新的 element ,接下來交給瀏覽器渲染就可以了。
這種數(shù)據(jù)驅動模式一定程度上,并不受到平臺的影響,或者說受到平臺影響較小,因為在整個數(shù)據(jù)流過程中,從 setState 的觸發(fā),到 state 改變,再到組件 render 得到 element,接下來形成新的虛擬 DOM ,都是在 js 層完成的,而最后涉及到渲染繪制的時候,在通過不同的平臺做差異化處理就可以了,比如在 web 交給瀏覽器去繪制,在移動端交給 Native 去繪制。
2 從 JSX 到虛擬 DOM
JSX 的靈活性也是 React 在跨端領域備受歡迎的原因之一,JSX 會被 babel 編譯成 React element 對象形式,也就是當我們在組件中寫了一個頁面結構,但是本質上就是一個對象結構。比如我們在頁面中這么寫:
/* 子組件 */ function Children(){ return <div>子組件</div> } /* 父組件 */ function Index(){ const element = <div> <p> hello,React </p> <Children /> </div> console.log(element,'element') return element }
我們先來看看經(jīng)過 babel 和 createElement 處理后,會變成什么樣子:
通過上面可以看到當 Index 渲染的時候,會把當前組件視圖層所有元素轉換成 element 對象,最終形成 element 對象結構如下所示:
相比 template 模板形式,JSX 的優(yōu)勢非常明顯:
第一個就是通過 JSX 方式,一些設計模式會變得非常靈活,比如組合模式,render props 模式,這些模式的本質就是更靈活的組裝 element 對象。在 template 模式中,組合模式需要通過 slot 插槽來實現(xiàn),如果多個 slot 嵌套的,會讓 template 結構變得非常復雜,難以理解。
第二個原因就是 JSX 語法本質上就是 JS ,所以對于寫法非常靈活,包括在視圖層寫判斷,循環(huán),抽象狀態(tài)等,而 template 一般都會有寫法的限制,比如 vue 中,必須遵循 vue 的模版邏輯,在微信小程序中亦是如此。
第三個就是 JSX 對于數(shù)據(jù)的預處理能力非常好,開發(fā)者可以在 render 函數(shù)中,對于數(shù)據(jù)進行格式化處理,比如一些來源于父組件的 props ,把數(shù)據(jù)處理成視圖層需要的結構,再進行渲染。在 vue 可能會用過濾器或者計算屬性解決這個問題。但是在小程序中會變得很棘手,有一些數(shù)據(jù)的處理,比如依賴小程序通過監(jiān)聽器的方式,對數(shù)據(jù)進行處理,然后通過 setData 的方式,將 props 中的數(shù)據(jù),映射到 data 中,這無疑是一種性能上的浪費。
在 web 領域,因為驅動視圖都是由 js 來完成的,所以少了很多通信成本,但是在跨端領域就不是這樣子了,跨端無論是 webview 渲染還是 native 渲染都有一定的通信成本。
webview 渲染得益于微信小程序那套通信模型,一些數(shù)據(jù)需要通過橋的方式通信,而且在這其中,可能還需要將數(shù)據(jù)進行序列化之后傳遞,這其中的每個環(huán)節(jié)都需要通信成本,在微信小程序文檔中,也對 setData 的頻和量都有嚴格的限定,也就是不建議頻繁使用 setData ,同樣也不建議將所有數(shù)據(jù)都 setData ,setData 只更新相關視圖的部分。
Native 渲染,以 RN 為例子,通信模型下需要 C++ 做中間層,一方面與 Native 進行通信,另外一方面與 JS 進行通信,所以也會有一定的通信成本,通過渲染形成樹結構,也是要通過通信方式傳遞給 Native ,Native 是根據(jù)這個進行渲染繪制的。
還有一點就是 template 需要由獨立的模版解析器去解析,再轉化成虛擬 DOM ,這樣就多了一道工序。
JSX 和 template 相比流程:
如上說到了 JSX 的優(yōu)勢,我們再看一下 element 轉化成 fiber 的流程。fiber 架構是 React 的核心,fiber 上保存了當前節(jié)點的信息,fiber 樹能夠直接反映出整個 React 應用的原貌,跨端應用也可以利用 React fiber 這一點,在跨端應用中,也可以存活 fiber 樹,不過在瀏覽器端,fiber 上保存了真實 DOM 信息,但是在跨端應用中,就可以保存其他有關視圖元素的信息。
element 轉化成 fiber 后:
Taro 3 中可以選擇運行時的 Vue 和 React 做基礎開發(fā)框架,當選擇 React 開發(fā)小程序后,整個應用還是能夠正常運行 React 應用,保持 React 的活性,這是為什么呢?
雖然微信小程序是采用 webview 的方式,但是對于原生 DOM 的操作,小程序并沒有給開發(fā)者開口子,也就是說小程序里如果想要使用 React 框架,就不能使用 DOM 的相關操作,也就不能直接操作 DOM 元素,既然不能操作 DOM ,那么 React fiber 如何處理的呢?
原來在 Taro React 中,會改變 reconciler 中涉及到 DOM 操作的部分,我們來看看部分改動:
taro-react/src/reconciler.ts
import Reconciler, { HostConfig } from 'react-reconciler' /* 引入 taro 中兼容的 document 對象 */ import { document, TaroElement, TaroText } from '@tarojs/runtime' const hostConfig = { /* 創(chuàng)建元素 */ createInstance (type) { return document.createElement(type) }, /* 創(chuàng)建文本 */ createTextInstance (text) { return document.createTextNode(text) }, /* 插入元素 */ appendChild (parent, child) { parent.appendChild(child) }, ... }
這個是 taro 對 react-reconciler 的改動,在 react-reconciler 中,HostConfig 里面包含了所有有關真實 DOM 的操作,taro reconciler 會通過向 tarojs/runtime 引入 document 的方式,來劫持原生 DOM 中的 document,然后注入兼容好的方法,這樣的話,當 fiber 操作 DOM 的時候,本質上是使用 Taro 提供了方法。
接下來 taro 就可以通過遞歸的方式插入自己偽造的 DOM 元素樹,這樣就可以在小程序中正常運行 React 框架了。
3 獨立事件系統(tǒng)
React 做跨端還有一個好處,就是 React 有一套獨立的事件系統(tǒng),在我之前的文章中,也講到過 React 事件系統(tǒng)的原理。React 事件系統(tǒng)的設計,能夠把原生 DOM 元素和事件執(zhí)行函數(shù)隔離開來,統(tǒng)一管理事件,這樣事件的觸發(fā),由 DOM 層面變成了 JS 層面。為 React 做跨平臺兼容提供了技術支撐。
上面說到了 React 的事件系統(tǒng)做的很好的一點就是,事件并非直接綁定在原生的 DOM 上,而是由 React 的事件系統(tǒng)統(tǒng)一控制。這樣在跨端應用中,就可以有不同的平臺獨立處理這些事件。
三 React 能為跨端動態(tài)化做些什么?
React 到底了跨端動態(tài)化做了些什么呢? 我們來從內(nèi)到外來分析一下:
1 React 語法做 DSL
React 對跨端領域的貢獻。第一種就是以 React 作為 DSL 的跨端方案,對于 DSL 可能有些同學比較陌生,什么是 DSL 。
DSL 的全稱是動態(tài)腳本語言(Dynamic Script Language),它是對腳本語言的一種擴展。
這種方案保留了 React 的語法,比如說 JSX 和 React 完全一致,包括有渲染函數(shù) render,觸發(fā)更新的方法 setState 等,但是不具有 React 的活性,也就是形似神不似,其內(nèi)部并不是由 React 系統(tǒng)驅動,這種方案,最后會被編譯成 JS 文件,接下來只要由 JS 引擎解析 JS,再由端上進行繪制就可以了。
以 React 作為 DSL 的應用,有一個好處就是不需要預編譯處理。
比如微信小程序有自己 wxml,但是 wxml 只是一個模板,最后想要被 JS 識別,就必須進行預編譯,編譯成 JS 能夠識別的抽象語法樹形式。
還有一個優(yōu)點是 React DSL 一般都采用 css in js 作為樣式處理方法,所以也不需要用專門的解釋器去解析 css 樣式。
但是這種方案也有一些弊端,就是 React 的一些新特性或者新功能,可能無法正常使用,比如說 hooks,Suspense 等。
2 保留 React 運行時
還有一種就是類似 Taro 的解決方案,用 React 做為跨端方案,不僅僅是神似,而且還是形似,也就是 React 完全應用于運行時,這依賴于 React 框架一些良好的特性,比如 react-reconciler 對 DOM 方法的隔離 hostConfig,或者獨立的事件系統(tǒng)等。
但是也需要對跨端做一些兼容處理,比如像 Taro react 中對 reconciler 的兼容。
這種方式因為是 React 運行時,所以可以用 React 提供的 API,也能使用 React 的特性。
3 React 新領域延伸
還有就是像 React Native ,本質上是官方 React 在跨端領域的解決方案。react 在 web 端用的是 ReactDOM.render 或者 createRoot 創(chuàng)建整個應用,在 RN 中則是用的是 AppRegistry.registerComponent 。
React Native 像是 web 的功能,在 Native 端重新實現(xiàn)一遍。
四 總結
本文介紹了 React 做跨端動態(tài)化的一些優(yōu)勢分析,列舉出一些經(jīng)典通過 React 實現(xiàn)跨端動態(tài)化的案例,淺析了原理,感興趣的同學可以嘗試一下用 RN ,Taro 等技術從零到一開發(fā)一個簡單的移動端應用,相信會有很多收獲。
以上就是React 跨端動態(tài)化核心技術實例分析的詳細內(nèi)容,更多關于React 跨端動態(tài)化的資料請關注腳本之家其它相關文章!
相關文章
React Native實現(xiàn)簡單的登錄功能(推薦)
這篇文章主要介紹了React Native實現(xiàn)登錄功能的相關資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2016-09-09