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