本篇內(nèi)容主要講解“小程序能不能用react”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“小程序能不能用react”吧!
創(chuàng)新互聯(lián)公司:成立與2013年為各行業(yè)開(kāi)拓出企業(yè)自己的“網(wǎng)站建設(shè)”服務(wù),為上千余家公司企業(yè)提供了專(zhuān)業(yè)的成都網(wǎng)站設(shè)計(jì)、成都網(wǎng)站制作、網(wǎng)頁(yè)設(shè)計(jì)和網(wǎng)站推廣服務(wù), 按需制作網(wǎng)站由設(shè)計(jì)師親自精心設(shè)計(jì),設(shè)計(jì)的效果完全按照客戶(hù)的要求,并適當(dāng)?shù)奶岢龊侠淼慕ㄗh,擁有的視覺(jué)效果,策劃師分析客戶(hù)的同行競(jìng)爭(zhēng)對(duì)手,根據(jù)客戶(hù)的實(shí)際情況給出合理的網(wǎng)站構(gòu)架,制作客戶(hù)同行業(yè)具有領(lǐng)先地位的。
小程序能用react,其使用方法:1、基于“react-reconciler”實(shí)現(xiàn)一個(gè)渲染器,生成一個(gè)DSL;2、創(chuàng)建一個(gè)小程序組件,去解析和渲染DSL;3、安裝npm,并執(zhí)行開(kāi)發(fā)者工具中的構(gòu)建npm;4、在自己的頁(yè)面中引入包,再利用api即可完成開(kāi)發(fā)。
小程序能用react嗎?
能。
在微信小程序中直接運(yùn)行React組件
在研究跨端開(kāi)發(fā)時(shí),我的一個(gè)重要目標(biāo),是可以讓react組件跑在微信小程序中。在這個(gè)過(guò)程中,我探索了微信小程序的架構(gòu),并且引發(fā)了很多思考。而作為跨端開(kāi)發(fā),實(shí)際上很難做到 write once,run anywhere,因?yàn)槊總€(gè)平臺(tái)所提供的能力是不一樣的,例如微信小程序提供了原生的能力,例如調(diào)起攝像頭或其他需要原生環(huán)境支持的能力,在微信小程序中開(kāi)發(fā)雖然也是在webview中開(kāi)展,但是,卻需要一些原生的思維。所以,要做到 write once 就必須有一些限制,這些限制注定了我們無(wú)法完全利用小程序的能力,僅僅只用到一些布局的能力而已。所以,奉勸各位,在做跨端開(kāi)發(fā)時(shí),要有個(gè)心理準(zhǔn)備。但如果跳出跨端開(kāi)發(fā),我現(xiàn)在只開(kāi)發(fā)小程序,那我能否用我熟悉的react來(lái)開(kāi)發(fā)呢?甚至,能否用我開(kāi)發(fā)的nautil框架來(lái)開(kāi)發(fā)呢?答案是可以的,本文將帶你一步一步實(shí)現(xiàn)自己的react小程序開(kāi)發(fā)之路,幫助你在某些特定的場(chǎng)景下,完成react項(xiàng)目往小程序遷移的目標(biāo)。
小程序運(yùn)行React的方案對(duì)比
目前業(yè)界能夠比較好支持小程序(沒(méi)有特別注明的情況下,小程序特指微信小程序)運(yùn)行React組件的,有3套方案,分別是京東凹凸實(shí)驗(yàn)室的taro,螞蟻金服某團(tuán)隊(duì)(未找到具體團(tuán)隊(duì)名)的remax,微信某團(tuán)隊(duì)的kbone。
Taro
編譯,新版本也基于運(yùn)行時(shí)
解析為wxml+js
老牌,不斷發(fā)展,全平臺(tái)支持,持續(xù)迭代
Remax
運(yùn)行時(shí),帶編譯宏
基于reconciler
最優(yōu)雅,增量更新
不夠成熟,后續(xù)發(fā)展未知
Kbone
運(yùn)行時(shí),依賴(lài)webpack
自己實(shí)現(xiàn)一套DOM API
可兼容vue,甚至任意基于DOM渲染的框架
性能問(wèn)題(全量檢查),幾乎停更
3套方案各有不同,而且在各自的思路上都是獨(dú)樹(shù)一幟。就我個(gè)人而言,如果不考慮跨端開(kāi)發(fā),自己實(shí)現(xiàn)一套DOM API這種方案是非常有價(jià)值的,因?yàn)镈OM接口是HTML標(biāo)準(zhǔn),你不需要自己去發(fā)明一套標(biāo)準(zhǔn)出來(lái),而一旦實(shí)現(xiàn)了DOM API,那么所以其他基于DOM實(shí)現(xiàn)的應(yīng)用理論上都支持在這上面跑。但是,它的不足就是你每換一個(gè)平臺(tái),就要針對(duì)這個(gè)平臺(tái)去實(shí)現(xiàn)一套DOM API,這個(gè)成本是非常大的,因?yàn)镈OM接口標(biāo)準(zhǔn)極其龐大,實(shí)現(xiàn)的時(shí)候也很容易出bug。在我看來(lái),最優(yōu)雅的實(shí)現(xiàn)還是Remax的那種思路,基于react-reconciler做一個(gè)渲染器,這個(gè)渲染器將react組件實(shí)例抽象為一個(gè)統(tǒng)一的DSL,在不同的平臺(tái)上,去解析渲染這個(gè)DSL。
但是remax迭代更新之后,它開(kāi)始強(qiáng)依賴(lài)自己的編譯工具,這直接導(dǎo)致我放棄在項(xiàng)目中使用它。因?yàn)閷?duì)于我們自己的項(xiàng)目而言,我們其實(shí)有可能不需要它的全部,我們只是使用react來(lái)完成我們整個(gè)小程序中的某些部分(比如有些已經(jīng)用react寫(xiě)好的h6我們想要渲染到小程序,其他部分我們還是在原來(lái)的項(xiàng)目中跑)。如果對(duì)它的編譯工具有依賴(lài),我們就不得不把整個(gè)項(xiàng)目遷移到它的編譯工具,那我還不如直接使用taro這個(gè)老牌比較穩(wěn)定的工具。
整體實(shí)現(xiàn)思路
經(jīng)過(guò)一番研究之后,我決定采用remax的思路,也就是基于react-reconciler實(shí)現(xiàn)一個(gè)渲染器,生成一個(gè)DSL,再創(chuàng)建一個(gè)小程序組件,去解析和渲染這個(gè)DSL。在完成實(shí)現(xiàn)之后,我把所有這些邏輯構(gòu)建為最終產(chǎn)物,并以npm的形式發(fā)布產(chǎn)物,對(duì)于小程序開(kāi)發(fā)者而言,只需要npm安裝之后,執(zhí)行開(kāi)發(fā)者工具中的構(gòu)建npm即可,之后在自己的頁(yè)面中引入這個(gè)包,利用api即可完成開(kāi)發(fā),而不在需要使用另外的編譯工具。
這一方案的最大好處是,對(duì)編譯工具的弱(無(wú))依賴(lài),這樣就可以讓我們的這套方案可以在任意的項(xiàng)目中去跑,而不需要額外引入編譯工具切換工具棧。另外,因?yàn)閞econciler的部分已經(jīng)打包進(jìn)npm包了,所以它是一個(gè)可以獨(dú)立運(yùn)行的模塊,所以,你甚至可以在mpvue等vue風(fēng)格或小程序原生風(fēng)格項(xiàng)目中使用這個(gè)npm包來(lái)渲染react的組件。
微信小程序中運(yùn)行react組件的思路
如上圖所示,我們將一個(gè)react組件通過(guò)基于react-reconciler的渲染器,創(chuàng)建了一個(gè)DSL的純對(duì)象(包含回調(diào)函數(shù)),我們?cè)趐age的js文件中,通過(guò)this.setData把這個(gè)對(duì)象發(fā)送給渲染線程,在wxml中使用了我們提供的一個(gè)自引用嵌套的組件對(duì)DSL進(jìn)行渲染。這里需要注意一個(gè)點(diǎn),react-reconciler會(huì)在組件更新的時(shí)候,觸發(fā)對(duì)應(yīng)的鉤子,此時(shí),會(huì)再次生成新的DSL,并再次通過(guò)this.setData發(fā)送渲染。所以,這個(gè)渲染器和單純使用createElement的結(jié)果是不同的,渲染器支持hooks等react內(nèi)置的功能。
接下來(lái),我將對(duì)其中的具體細(xì)節(jié)進(jìn)行講解,以讓你盡可能自己可以手寫(xiě)出本文所闡述的代碼,以讓你在自己的項(xiàng)目中可以實(shí)現(xiàn)本文一致的效果。你可以克隆這個(gè)倉(cāng)庫(kù)到本地,運(yùn)行效果看看,研究它的整個(gè)實(shí)現(xiàn)過(guò)程。
將react組件渲染為純JS對(duì)象
react的渲染器本質(zhì)上是一個(gè)基于react調(diào)度系統(tǒng)的副作用執(zhí)行器,副作用的結(jié)果在web環(huán)境下就是DOM的操作,在native環(huán)境下就是調(diào)用渲染引擎光柵化圖形,在art環(huán)境下就是調(diào)用聲卡播放聲音,而在我們這次的計(jì)劃中,我們需要渲染器生成一個(gè)純js對(duì)象,以方便交給小程序在小程序的兩個(gè)線程之間作為消息體進(jìn)行傳遞,并基于這個(gè)對(duì)象在小程序中渲染界面。
有同學(xué)對(duì)我發(fā)出疑問(wèn):jsx編譯之后React.createElement的執(zhí)行結(jié)果不就是純JS的對(duì)象么?這里需要了解react的本質(zhì)。react的組件,實(shí)際上為react提供了一套描述系統(tǒng),它描述了react所表達(dá)的具體對(duì)象的結(jié)構(gòu)。但是,這個(gè)描述是抽象的,只有當(dāng)你把它實(shí)例化,運(yùn)行起來(lái)時(shí),它才有意義。我們?cè)诮M件中所做的描述,可不單單只有jsx的部分,它還包括業(yè)務(wù)和程序?qū)用娴倪壿?。比如很多?chǎng)景下,我們需要根據(jù)組件狀態(tài)來(lái)決定返回那一部分jsx,從而渲染不同的界面。而這部分內(nèi)容,需要依賴(lài)一個(gè)環(huán)境來(lái)執(zhí)行,也就是react渲染器。
在以前,我們只能模擬react-dom,按照它的運(yùn)行邏輯,自己手寫(xiě)一個(gè)渲染器。而現(xiàn)在,react把它的調(diào)度器專(zhuān)門(mén)做了一個(gè)庫(kù),react-reconciler,幫助開(kāi)發(fā)者快速接入react的調(diào)度系統(tǒng),從而可以構(gòu)建自己的渲染器。這里有一個(gè)視頻(自備梯子),介紹了react-reconciler的基本用法和使用效果。
import Reconciler from 'react-reconciler'
const container = {}
const HostConfig = {
// ... 極其復(fù)雜的一個(gè)配置
}
const reconcilerInstance = Reconciler(HostConfig)
let rootContainerInstance = null
export function render(element, { mounted, updated, created }) {
if (!rootContainerInstance) {
rootContainerInstance = reconcilerInstance.createContainer(container, false, false)
}
return reconcilerInstance.updateContainer(element, rootContainerInstance, null, () => {
notify = { mounted, updated, created }
created && created(container)
mounted(container.data)
})
}
上面代碼中,沒(méi)有給出的HostConfig的具體內(nèi)容是關(guān)鍵,它用于配制一個(gè)Reconciler,從代碼的角度,它就是一個(gè)鉤子函數(shù)的集合,我們需要在每個(gè)鉤子函數(shù)內(nèi)部寫(xiě)一些副作用來(lái)操作container,你可以看到,在不同的時(shí)刻,我們傳入的created, mounted, updated會(huì)被調(diào)用,而它們接收被操作過(guò)的container,從而讓我們獲得這個(gè)js對(duì)象(container上還有一些函數(shù),但我們可以不用理會(huì),因?yàn)閠his.setData會(huì)自動(dòng)清除這些函數(shù))。
由于這一配置內(nèi)容太過(guò)復(fù)雜,要講解清楚需要花費(fèi)比較大的篇幅,所以我直接把源碼地址貼在這里,你可以通過(guò)閱讀源碼來(lái)了解它都有哪些配置項(xiàng),并且你可以把這部分代碼拆分出來(lái)后,運(yùn)行一個(gè)自己的組件,通過(guò)console.log來(lái)觀察它們被調(diào)用的時(shí)機(jī)以及順序。
總而言之,這些接口都是知識(shí)層面的,不是什么復(fù)雜的邏輯,了解每一個(gè)配置項(xiàng)的作用和執(zhí)行時(shí)機(jī)之后,你就能寫(xiě)出自己的渲染器。理論上,它沒(méi)有什么難度。
基于react-reconciler,我在react運(yùn)行時(shí)的每一個(gè)環(huán)節(jié)都做了一些副作用操作,這些副作用的本質(zhì),就是修改一個(gè)純js對(duì)象,當(dāng)react被運(yùn)行起來(lái)時(shí),它會(huì)經(jīng)歷一個(gè)生命周期,這在我的一個(gè)視頻中有講到react的生命周期的具體過(guò)程。你也可以關(guān)注我的個(gè)人微信公眾號(hào) wwwtangshuangnet 和我討論相關(guān)的問(wèn)題。在每一個(gè)生命周期節(jié)點(diǎn)上,調(diào)度器就會(huì)執(zhí)行一個(gè)副作用,即修改我提供的那個(gè)純js對(duì)象。
我提供了兩個(gè)方法,用于在小程序的渲染器中,獲得生成好的js對(duì)象。得到這個(gè)js對(duì)象之后,就可以調(diào)用小程序的this.setData,把這個(gè)對(duì)象發(fā)送到渲染線程進(jìn)行渲染。
利用react渲染器得到的純對(duì)象上存在一些函數(shù),調(diào)用這些函數(shù)會(huì)觸發(fā)它們對(duì)應(yīng)的邏輯(比如調(diào)用setState觸發(fā)hooks狀態(tài)更新),從而觸發(fā)調(diào)度器中的鉤子函數(shù)執(zhí)行,container對(duì)象再次被修改,updated被再次調(diào)用,this.setData被再次執(zhí)行,這樣,就實(shí)現(xiàn)了真正的react運(yùn)行時(shí)在小程序中的植入。
嵌套遞歸自引用組件
渲染線程接收到this.setData發(fā)送過(guò)來(lái)的js對(duì)象后,如何將這個(gè)對(duì)象作為布局的信息,渲染到界面上呢?由于小程序的特殊架構(gòu),它為了安全起見(jiàn),渲染線程中無(wú)法執(zhí)行可操作界面的腳本,所有的渲染,都得依靠模板語(yǔ)法和少量的wxs腳本。所以,要怎么做呢?
小程序提供了自定義組件的功能,在app.json或?qū)?yīng)的page.json中,通過(guò)usingComponents來(lái)指定一個(gè)路徑,從而可以在wxml中使用這個(gè)組件。而有趣的地方在于,組件本身也可以在組件自己的component.json中使用usingComponents這個(gè)配置,而這個(gè)配置的內(nèi)容,可以直接指向自己,例如,我在自己的組件中,這樣自引用:
// dynamic.json
{
"usingComponents": {
"dynamic": "./dynamic"
}
}
自己引用自己作為組件之后,在其wxml中,我們就可以使用組件自己去渲染子級(jí)數(shù)據(jù),即一種嵌套遞歸的形式進(jìn)行渲染。
我規(guī)定了一種特別的數(shù)據(jù)結(jié)構(gòu),大致如下:
{
type: 'view',
props: {
class: 'shadow-component',
bindtap: (e) => { ... },
},
children: [
{
type: 'view',
props: {},
children: [
...
],
},
],
}
模板中,通過(guò)對(duì)type的判斷,選擇不同的模板代碼進(jìn)行渲染。
在wxml中把所有組件通過(guò)這種形式枚舉出來(lái)之后,這個(gè)組件就能按照上述的數(shù)據(jù)結(jié)構(gòu)遞歸渲染出整個(gè)結(jié)構(gòu)。
當(dāng)然,這里還需要處理一些細(xì)節(jié),例如響應(yīng)data的變化,事件響應(yīng)函數(shù)等,你可以通過(guò)源碼了解具體要怎么處理。另外,微信小程序this.setData限制在1M以?xún)?nèi),我雖然還沒(méi)有嘗試過(guò)很大的數(shù)據(jù),但是,這個(gè)限制肯定在將來(lái)是一個(gè)風(fēng)險(xiǎn)點(diǎn),我現(xiàn)在還沒(méi)有解決,還在思考應(yīng)該怎么最小化更新粒度。
不支持直接JSX的變通方法
小程序的編譯,沒(méi)有辦法自己配置支持新語(yǔ)法,所以如果我們?cè)谛〕绦虼a中使用jsx,就必須先走一遍自己的編譯邏輯。有兩種解決辦法,一種是不使用jsx語(yǔ)法,而是使用hyperscript標(biāo)記語(yǔ)法,比如:
import { createElement as h } from 'react'
function Some() {
return h(
'view',
{ class: 'some-component' },
h(
'view',
{ class: 'sub-view' },
'一段文字',
),
'一段文字',
)
}
這樣的寫(xiě)法顯然沒(méi)有直接寫(xiě)jsx來(lái)的方便,但是閱讀上沒(méi)有什么障礙,且不需要將jsx編譯的過(guò)程。
另一種辦法是走一遍編譯,在小程序的頁(yè)面目錄下,創(chuàng)建一個(gè)頁(yè)面同名的.jsx文件,再利用bebel將它編譯為.js文件。但是這樣的話(huà),你需要在發(fā)布小程序的時(shí)候,忽略掉所有的.jsx文件。另外,還有一個(gè)坑是,小程序的編譯不提供process.env,所以編譯react的結(jié)果用的時(shí)候會(huì)報(bào)錯(cuò)。解決辦法是把react的cjs/react.production.min.js作為react的入口文件,通過(guò)小程序的構(gòu)建npm的相關(guān)配置邏輯,指定react構(gòu)建的文件。
到此,相信大家對(duì)“小程序能不能用react”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢(xún),關(guān)注我們,繼續(xù)學(xué)習(xí)!