<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          跨端技術的本質是什么?現(xiàn)狀如何?

          共 9775字,需瀏覽 20分鐘

           ·

          2022-03-08 09:28

          來自團隊 匡凌熙 同學的分享

          零、何為跨端

          write once, run everywhere

          一次編寫,四處運行就是跨端的真諦。因為前端當下需要處理的場景實在是太多了:androidios、pc、小程序,甚至智能手表、車載電視等,當某幾個場景非常相似的時候,我們希望能夠用最少的開發(fā)成本來達到最好的效果,而不是每個端都需要一套單獨的人力來進行維護,所以跨端技術就誕生了。

          那么在跨端方案百花齊放的今天,比如現(xiàn)在最為人們所熟知的react nativeflutter、electron等,他們之間有沒有什么共同的特點,而我們又是否能夠找到其中的本質,就是今天這篇文章想講述的問題。

          一、主流跨端實現(xiàn)方案

          1.1 h5 hybrid 方案

          其實,瀏覽器本就是一個跨端實現(xiàn)方案,因為你只需要輸入網(wǎng)址,就能在任何端的瀏覽器上打開你的網(wǎng)頁。那么,如果我們把瀏覽器嵌入 app 中,再將地址欄等內(nèi)容隱藏掉,是不是就能將我們的網(wǎng)頁嵌入原生 app 了。而這個嵌入 app 的瀏覽器,我們把它稱之為 webview ,所以只要某個端支持 webview ,那么它就能使用這種方案跨端。

          同時這也是開發(fā)成本最小的一種方案,因為這實際上就是在寫前端界面,和我們開發(fā)普通的網(wǎng)頁并沒有太大區(qū)別。

          1.2 框架層+原生渲染

          典型的代表是 react-native,它的開發(fā)語言選擇了 js,使用的語法和 react 完全一致,其實也可以說它就是 react,這就是我們的框架層。而不同于一般 react 應用,它需要借助原生的能力來進行渲染,組件最終都會被渲染為原生組件,這可以給用戶帶來比較好的體驗。

          1.3 框架層+自渲染引擎

          這種方案和上面的區(qū)別就是,它并沒有直接借用原生能力去渲染組件,而是利用了更底層的渲染能力,自己去渲染組件。這種方式顯然鏈路會比上述方案的鏈路跟短,那么性能也就會更好,同時在保證多端渲染一致性上也會比上一種方案更加可靠。這類框架的典型例子就是 flutter 。

          1.4 另類跨端

          眾所周知,在最近幾年有一個東西變得非?;鸨盒〕绦?,現(xiàn)在許多大廠都有一套自己的小程序實現(xiàn),但相互之間還是有不小差異的,通??梢越柚?taroremax 這類框架實現(xiàn)一套代碼,多端運行的效果,這也算是一種另類的跨端,它的實現(xiàn)方式還是比較有意思的,我們后面會展開細講。

          二、react-native 實現(xiàn)

          2.1 rn的三個線程

          rn 包含三個線程:

          • native thread:主要負責原生渲染和調(diào)用原生能力;
          • js thread:JS 線程用于解釋和執(zhí)行我們的js代碼。在大多數(shù)情況下,react native 使用的js引擎是JSC(JavaScriptCore) ,在使用 chrome 調(diào)試時,所有的 js 代碼都運行在 chrome中,并且通過 websocket與原生代碼通信。此時的運行環(huán)境是v8。

          • shadow thread:要渲染到界面上一個很重要的步驟就是布局,我們需要知道每個組件應該渲染到什么位置,這個過程就是通過yoga去實現(xiàn)的,這是一個基于flexbox的跨平臺布局引擎。shadow thread 會維護一個 shadow tree來計算我們的各個組件在 native 頁面的實際布局,然后通過 bridge 通知native thread 渲染 ui

          2.2 初始化流程

          1. native 啟動一個原生界面,比如android會起一個新的activity來承載rn,并做一些初始化的操作。
          2. 加載 js 引擎,運行 js 代碼,此時的流程和 react 的啟動流程就非常相似了,我們先簡單觀察調(diào)用棧,

          是不是看見了一些非常熟悉的函數(shù)名,在上一講的基本原理中已經(jīng)提到過了,這里我們就不再贅述。同時再看一下FiberNode的結構,也和react的保持一致,只不過我們在js層是無法拿到真實結點的,所以stateNode只是一個代號。

          1. js 線程通知shadow thread。在react中,走到createInstance以后我們就可以直接調(diào)用createElement來創(chuàng)建真實結點了,但是在rn中我們沒辦法做到這一步,所以我們會通知native層讓它來幫助我們創(chuàng)建一個對應的真實結點。
          1. shadow thread 計算布局,通知native Thread 創(chuàng)建原生組件。
          2. native 在界面上渲染原生組件,呈現(xiàn)給用戶。

          2.3 更新流程

          比如某個時候,用戶點擊了屏幕上的一個按鈕觸發(fā)了一個點擊事件,此時界面需要進行相應的更新操作。

          1. native 獲取到了點擊事件,傳給了js thread
          2. js thread根據(jù) react 代碼進行相應的處理,比如處理 onClick 函數(shù),觸發(fā)了 setState。
          3. react 的更新流程一樣,觸發(fā)了 setState 之后會進行 diff,找到需要更新的結點
          4. 通知 shadow thread
          5. shadow thread 計算布局之后通知 native thread 進行真正的渲染。

          2.4 特點

          我們上述說的通知,都是通過 bridge 實現(xiàn)的,bridge本身是用實現(xiàn)C++的,就像一座橋一樣,將各個模塊關聯(lián)起來,整個通信是一個「異步」的過程。這樣做好處就是各自之間不會有阻塞關系,比如 不會native thread因為js thread而阻塞渲染,給用戶良好的體驗。但是這種「異步」也存在一個比較明顯的問題:因為通信過程花費的時間比較長,所以在一些時效性要求較高場景上體驗較差。

          比如長列表快速滾動的時候或者需要做一些跟手的動畫,整個過程是這樣的:

          1. native thread監(jiān)聽到了滾動事件,發(fā)送消息通知js thread
          2. js thread 處理滾動事件,如果需要修改 state 需要經(jīng)過一層js diff,拿到最終需要更新的結點
          3. js thread 通知 shadow thread
          4. shadow thread 通知 native 渲染

          當用戶操作過快的時候,就會導致界面來不及更新,進而導致在快速滑動的時候會出現(xiàn)白屏、卡頓的現(xiàn)象。

          2.5 優(yōu)化

          我們很容易看出,這是由rn的架構引出的問題,其實小程序的架構也會有這個問題,所以在rn和小程序上出現(xiàn)一些需要頻繁通信的場景時,就會導致頁面非常差,流暢度降低。那么如果想解決這個問題,勢必要從架構上去進行修改。

          三、從rn看本質

          那么既然我們知道了rn是如何實現(xiàn)的跨端,那么我們就可以來探究一下它本質上是在干什么。首先,跨端可以分為「邏輯跨端」「渲染跨端」。

          「邏輯跨端」通常通過 vm來實現(xiàn),例如利用 v8 引擎,我們就能在各個平臺上運行我們的 js 代碼,實現(xiàn)「邏輯跨端」。

          那么第二個問題就是「渲染跨端」,我們把業(yè)務代碼的實現(xiàn)抽象為開發(fā)層,比如 react-native 中我們寫的 react 代碼就屬于開發(fā)層,再把具體要渲染的端稱為渲染層。作為開發(fā)層來說,我一定知道我想要的ui長什么樣,但是我沒有能力去渲染到界面上,所以當我聲明了一個組件之后,我們需要考慮的問題是如何把我想要什么告訴渲染層。

          就像這樣的關系,那么我們最直觀的方式肯定是我能夠實現(xiàn)一種通信方式,在開發(fā)層將消息通知到各個系統(tǒng),再由各個系統(tǒng)自己去調(diào)用對應的 api 來實現(xiàn)最終的渲染。

          function?render()?{
          ????if(A)?{
          ????????message.sendA('render',?{?type:?'View'?})
          ????}
          ????
          ????
          ????if(B)?{
          ????????message.sendB('render',?{?type:?'View'?})
          ????}
          ????
          ????
          ????if(C)?{
          ????????message.sendC('render',?{?type:?'View'?})
          ????}
          }

          比如這樣,我就能通過判斷平臺來通知對應的端去渲染View組件。這一部分的工作就是跨端框架需要幫助我們做的,它可以把這一步放到 JS 層,也可以把這一步放到c++ 層。我們應該把這部分工作盡量往底層放,也就是我們可以對各個平臺的 api 進行一層封裝,上層只負責調(diào)用封裝的 api,再由這一層封裝層去調(diào)用真正的 api。因為這樣可以復用更多的邏輯,否則像上文中我們在 JS 層去發(fā)送消息給不同的平臺,我們就需要在A\B\C三個平臺寫三個不同方法去渲染組件。

          但是,歸根結底就是,一定有一個地方是通過判斷不同平臺來調(diào)用具體實現(xiàn),也就是下面這樣

          有一個地方會對系統(tǒng)進行判斷,再通過某種通信方式通知到對應的端,最后執(zhí)行真正的方法。其實,所有跨端相關操作其實都在做上圖中的這些事情。所有的跨端也可以總結為下面這句話:

          「我知道我想要什么,但是我沒有能力去渲染,我要通知有能力渲染的人來幫助我渲染」

          比如hybrid跨端方案中,webview其實就充當了橋接層的角色,createElement,appendChildapi就是給我們封裝好的跨平臺api,底層最終調(diào)用到了什么地方,又是如何渲染到界面上的細節(jié)都被屏蔽掉了。所以我們利用這些api就能很輕松的實現(xiàn)跨端開發(fā),寫一個網(wǎng)頁,只要能夠加載 webview 的地方,我們的代碼就能跑在這個上面。

          又比如flutter的方案通過研發(fā)一個自渲染的引擎來實現(xiàn)跨端,這種思路是不是相當于另外一個瀏覽器?但是不同的點在于 flutter 是一個非常新的東西,而 webview 需要遵循大量的 w3c 規(guī)范和背負一堆歷史包袱。flutter 并沒有什么歷史包袱,所以它能夠從架構,設計的方面去做的更好更快,能夠做更多的事情。

          四、跨端目前有什么問題

          4.1 一致性

          對于跨端來說,如何屏蔽好各端的細節(jié)至關重要,比如針對某個端特有的api如何處理,如何保證渲染細節(jié)上各個端始終保持一致。如果一個跨端框架能夠讓開發(fā)者的代碼里面不出現(xiàn) isIos、isAndroid的字眼,或者是為了兼容各種奇怪的渲染而產(chǎn)生的非常詭異的hack方式。那我認為它絕對是一個真正成功的框架。

          但是按我經(jīng)驗而言,先后寫過的 h5、rn、小程序,他們都沒有真正做到這一點,所以項目里面會出現(xiàn)為了解決不同端不一致問題而出現(xiàn)的各種奇奇怪怪的代碼。而這個問題其實也是非常難解決的,因為各端的差異還是比較大的,所以說很難去完全屏蔽這些細節(jié)。

          比如說h5中磨人的垂直居中問題,我相信只要開發(fā)過移動端頁面的都會遇見,就不用我多說了。

          4.2 為什么出現(xiàn)了這么多框架

          為什么大家其實本質上都是在干一件事情,卻出現(xiàn)了這么多的解決方案?其實大家都覺得某些框架沒能很好的解決某個問題,所以想自己去造一套。其中可能很多開發(fā)者最關心的就是性能問題,比如:

          • rn因為架構上的原因導致某些場景性能差,所以它就想辦法從架構上去進行修改。
          • flutter直接自己搞了一套渲染引擎,同時選用支持AOTdart作為開發(fā)語言。

          但是其實我們在選擇框架的時候性能并不是唯一因素,開發(fā)體驗、框架生態(tài)這些也都是關鍵因素,我個人感受是,目前rn的生態(tài)還是比其他的要好,所以在開發(fā)過程中你想要的東西基本都有。

          五、小程序跨端

          ok,說了這么多,對于跨端部分的內(nèi)容其實我想說的已經(jīng)說的差不多了,還記得上文提到的 Taro、Uni-app 一類跨小程序方案么。為什么說它是另類的跨端,因為它其實并沒有實際跨端,只是為了解決各個小程序語法之間不兼容的問題。但是它又確實是一個跨端解決方案,因為它符合 「write once, run everything?!?/strong>

          下面我們先來了解下小程序的背景。

          5.1 什么是小程序

          小程序是各個app廠商對外開放的一種能力。通過廠商提供的框架,就能在他們的app中運行自己的小程序,借助各大app的流量來開展自己的業(yè)務。同時作為廠商如果能吸引到更多的人加入到開發(fā)者大軍中來,也能給app帶來給多的流量,這可以看作一個雙贏的業(yè)務。那么最終呈現(xiàn)在app中的頁面是以什么方式進行渲染的呢?其實還是通過webview,但是會嵌入一些原生的組件在里面以提供更好的用戶體驗,比如video組件其實并不是h5 video,而是native video。

          5.2 什么是小程序跨端

          那么到了這里,我們就可以來談一談關于小程序跨端的東西了。關于小程序跨端,核心并不是真正意義上的跨端,雖然小程序也做到了跨端,例如一份代碼其實是可以跑在androidIos上的,但是實際上這和hybrid跨端十分相似。

          在這里我想說的其實是,市面上現(xiàn)在有非常多的小程序:字節(jié)小程序、百度小程序、微信小程序、支付寶小程序等等等等。雖然他們的dsl十分相似,但是終歸還是有所不同,那么就意味著如果我想在多個app上去開展我的業(yè)務,我是否需要維護多套十分相似的代碼?我又能否通過一套代碼能夠跑在各種小程序上?

          5.3 怎么做

          想通過一套代碼跑在多個小程序上,和想通過一套代碼跑在多個端,這兩件事到底是不是一件事呢?我們再回到這張圖

          這些平臺是否可以對應上不同的小程序?

          再回到那句話:「我知道我想要什么,但是我沒有能力去渲染,我要通知有能力渲染的人來幫助我渲染?!?/strong>

          現(xiàn)在來理一下我們的需求:

          • 小程序的語法不好用,我希望用 react 開發(fā);
          • 我希望盡可能低的成本讓小程序跑在多個平臺上。

          那么從這句話來看:「我」代表了什么,「有能力渲染的人」又代表了什么?

          第二個很容易對應上,「有能力渲染的人」就是小程序本身,只有它才能幫助我們把內(nèi)容真正渲染到界面上。

          「我」又是什么呢?其實這個「我」可以是很多東西,不過這里我們的需求是想用react進行開發(fā),所以我們回想一下第一講中react的核心流程,當它拿到vdom的時候,是不是就已經(jīng)知道【我想要什么】了?所以我們把react拿到vdom之前的流程搬過來,這樣就能獲取到「我知道我想要什么」的信息,但是「我沒有能力去渲染」,因為這不是web,沒有dom api,所以我需要通知小程序來幫助我渲染,我還可以根據(jù)不同的端來通知不同的小程序幫助我渲染。

          所以整個流程就是下面這樣的:

          前面三個流程都在我們的js層,也就是開發(fā)層,我們寫的代碼經(jīng)歷一遍完整的 react 流程之后,會將最后的結果給到各個小程序,然后再走小程序自己的內(nèi)部流程,將其真正的渲染到界面上。

          采用這種做法的典型例子有remax、taro3,他們宣稱用真正的react去開發(fā)小程序,其實并沒有錯,因為真的是把react的整套東西都搬了過來,和react并無差異。我們用taro寫一個非常簡單的例子來看一下:

          import?{?Component?}?from?'react'
          import?{?View,?Text,?Button?}?from?'@tarojs/components'
          import?'./index.css'


          export?default?class?Index?extends?Component?{

          ??state?=?{
          ????random:?Math.random()
          ??}


          ??componentWillMount?()?{?}


          ??componentDidMount?()?{?}


          ??componentWillUnmount?()?{?}


          ??componentDidShow?()?{?}


          ??componentDidHide?()?{?}


          ??handleClick?=?()?=>?{
          ????debugger;
          ????console.log("Math.random()",?Math.random());
          ????this.setState({random:?Math.random()})
          ??}


          ??render?()?{
          ????return?(
          ??????<View?className='index'>
          ????????<Text>Hello?world!?{this.state.random}Text>

          ????????<Button?onClick={this.handleClick}>clickButton>
          ??????View>
          ????)
          ??}
          }

          這是一個用taro寫的組件,把它編譯到字節(jié)小程序之后是這樣的效果:

          根據(jù)我們之前的分析,在最后生成的文件中,一定包含了一個「小程序渲染器」。它接受的data就是整個ui結構,然后通過小程序的渲染能力渲染到界面上,我們?nèi)?code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 148, 247);background: rgba(59, 170, 250, 0.1);padding-right: 2px;padding-left: 2px;border-radius: 2px;height: 21px;line-height: 22px;">dist文件中找一下,就能找到一個base.ttml的文件,里面的內(nèi)容是這樣的

          <template?name="taro_tmpl">
          ??<block?tt:for="{{root.cn}}"?tt:key="uid">
          ????<template?is="tmpl_0_container"?data="{{i:item}}"?/>
          ??block>
          template>

          <template?name="tmpl_0_catch-view">
          ??<view?hover-class="{{i.hoverClass===undefined?'none':i.hoverClass}}"?hover-stop-propagation="{{i.hoverStopPropagation===undefined?false:i.hoverStopPropagation}}"?hover-start-time="{{i.hoverStartTime===undefined?50:i.hoverStartTime}}"?hover-stay-time="{{i.hoverStayTime===undefined?400:i.hoverStayTime}}"?animation="{{i.animation}}"?bindtouchstart="eh"?bindtouchend="eh"?bindtouchcancel="eh"?bindlongpress="eh"?bindanimationstart="eh"?bindanimationiteration="eh"?bindanimationend="eh"?bindtransitionend="eh"?style="{{i.st}}"?class="{{i.cl}}"?bindtap="eh"?catchtouchmove="eh"??id="{{i.uid}}">
          ????<block?tt:for="{{i.cn}}"?tt:key="uid">
          ??????<template?is="tmpl_0_container"?data="{{i:item}}"?/>
          ????block>
          ??view>
          template>

          <template?name="tmpl_0_static-view">
          ??<view?hover-class="{{i.hoverClass===undefined?'none':i.hoverClass}}"?hover-stop-propagation="{{i.hoverStopPropagation===undefined?false:i.hoverStopPropagation}}"?hover-start-time="{{i.hoverStartTime===undefined?50:i.hoverStartTime}}"?hover-stay-time="{{i.hoverStayTime===undefined?400:i.hoverStayTime}}"?animation="{{i.animation}}"?style="{{i.st}}"?class="{{i.cl}}"??id="{{i.uid}}">
          ????<block?tt:for="{{i.cn}}"?tt:key="uid">
          ??????<template?is="tmpl_0_container"?data="{{i:item}}"?/>
          ????block>
          ??view>
          template>

          <template?name="tmpl_0_pure-view">
          ??<view?style="{{i.st}}"?class="{{i.cl}}"??id="{{i.uid}}">
          ????<block?tt:for="{{i.cn}}"?tt:key="uid">
          ??????<template?is="tmpl_0_container"?data="{{i:item}}"?/>
          ????block>
          ??view>
          template>

          <template?name="tmpl_0_view">
          ??<view?hover-class="{{i.hoverClass===undefined?'none':i.hoverClass}}"?hover-stop-propagation="{{i.hoverStopPropagation===undefined?false:i.hoverStopPropagation}}"?hover-start-time="{{i.hoverStartTime===undefined?50:i.hoverStartTime}}"?hover-stay-time="{{i.hoverStayTime===undefined?400:i.hoverStayTime}}"?animation="{{i.animation}}"?bindtouchstart="eh"?bindtouchmove="eh"?bindtouchend="eh"?bindtouchcancel="eh"?bindlongpress="eh"?bindanimationstart="eh"?bindanimationiteration="eh"?bindanimationend="eh"?bindtransitionend="eh"?style="{{i.st}}"?class="{{i.cl}}"?bindtap="eh"??id="{{i.uid}}">
          ????<block?tt:for="{{i.cn}}"?tt:key="uid">
          ??????<template?is="tmpl_0_container"?data="{{i:item}}"?/>
          ????block>
          ??view>
          template>

          <template?name="tmpl_0_static-text">
          ??<text?selectable="{{i.selectable===undefined?false:i.selectable}}"?space="{{i.space}}"?decode="{{i.decode===undefined?false:i.decode}}"?style="{{i.st}}"?class="{{i.cl}}"??id="{{i.uid}}">
          ????<block?tt:for="{{i.cn}}"?tt:key="uid">
          ??????<template?is="tmpl_0_container"?data="{{i:item}}"?/>
          ????block>
          ??text>
          template>

          <template?name="tmpl_0_text">
          ??<text?selectable="{{i.selectable===undefined?false:i.selectable}}"?space="{{i.space}}"?decode="{{i.decode===undefined?false:i.decode}}"?style="{{i.st}}"?class="{{i.cl}}"?bindtap="eh"??id="{{i.uid}}">
          ????<block?tt:for="{{i.cn}}"?tt:key="uid">
          ??????<template?is="tmpl_0_container"?data="{{i:item}}"?/>
          ????block>
          ??text>
          template>

          <template?name="tmpl_0_button">
          ??<button?size="{{i.size===undefined?'default':i.size}}"?type="{{i.type}}"?plain="{{i.plain===undefined?false:i.plain}}"?disabled="{{i.disabled}}"?loading="{{i.loading===undefined?false:i.loading}}"?form-type="{{i.formType}}"?open-type="{{i.openType}}"?hover-class="{{i.hoverClass===undefined?'button-hover':i.hoverClass}}"?hover-stop-propagation="{{i.hoverStopPropagation===undefined?false:i.hoverStopPropagation}}"?hover-start-time="{{i.hoverStartTime===undefined?20:i.hoverStartTime}}"?hover-stay-time="{{i.hoverStayTime===undefined?70:i.hoverStayTime}}"?name="{{i.name}}"?bindtouchstart="eh"?bindtouchmove="eh"?bindtouchend="eh"?bindtouchcancel="eh"?bindlongpress="eh"?bindgetphonenumber="eh"?data-channel="{{i.dataChannel}}"?style="{{i.st}}"?class="{{i.cl}}"?bindtap="eh"??id="{{i.uid}}">
          ????<block?tt:for="{{i.cn}}"?tt:key="uid">
          ??????<template?is="tmpl_0_container"?data="{{i:item}}"?/>
          ????block>
          ??button>
          template>

          <template?name="tmpl_0_scroll-view">
          ??<scroll-view?scroll-x="{{i.scrollX===undefined?false:i.scrollX}}"?scroll-y="{{i.scrollY===undefined?false:i.scrollY}}"?upper-threshold="{{i.upperThreshold===undefined?50:i.upperThreshold}}"?lower-threshold="{{i.lowerThreshold===undefined?50:i.lowerThreshold}}"?scroll-top="{{i.scrollTop}}"?scroll-left="{{i.scrollLeft}}"?scroll-into-view="{{i.scrollIntoView}}"?scroll-with-animation="{{i.scrollWithAnimation===undefined?false:i.scrollWithAnimation}}"?enable-back-to-top="{{i.enableBackToTop===undefined?false:i.enableBackToTop}}"?bindscrolltoupper="eh"?bindscrolltolower="eh"?bindscroll="eh"?bindtouchstart="eh"?bindtouchmove="eh"?bindtouchend="eh"?bindtouchcancel="eh"?bindlongpress="eh"?bindanimationstart="eh"?bindanimationiteration="eh"?bindanimationend="eh"?bindtransitionend="eh"?style="{{i.st}}"?class="{{i.cl}}"?bindtap="eh"??id="{{i.uid}}">
          ????<block?tt:for="{{i.cn}}"?tt:key="uid">
          ??????<template?is="tmpl_0_container"?data="{{i:item}}"?/>
          ????block>
          ??scroll-view>
          template>

          <template?name="tmpl_0_static-image">
          ??<image?src="{{i.src}}"?mode="{{i.mode===undefined?'scaleToFill':i.mode}}"?lazy-load="{{i.lazyLoad===undefined?false:i.lazyLoad}}"?style="{{i.st}}"?class="{{i.cl}}"??id="{{i.uid}}">
          ????<block?tt:for="{{i.cn}}"?tt:key="uid">
          ??????<template?is="tmpl_0_container"?data="{{i:item}}"?/>
          ????block>
          ??image>
          template>

          <template?name="tmpl_0_image">
          ??<image?src="{{i.src}}"?mode="{{i.mode===undefined?'scaleToFill':i.mode}}"?lazy-load="{{i.lazyLoad===undefined?false:i.lazyLoad}}"?binderror="eh"?bindload="eh"?bindtouchstart="eh"?bindtouchmove="eh"?bindtouchend="eh"?bindtouchcancel="eh"?bindlongpress="eh"?style="{{i.st}}"?class="{{i.cl}}"?bindtap="eh"??id="{{i.uid}}">
          ????<block?tt:for="{{i.cn}}"?tt:key="uid">
          ??????<template?is="tmpl_0_container"?data="{{i:item}}"?/>
          ????block>
          ??image>
          template>

          <template?name="tmpl_0_#text"?data="{{i:i}}">
          ??<block>{{i.v}}block>
          template>

          <template?name="tmpl_0_container">
          ??<template?is="{{'tmpl_0_'?+?i.nn}}"?data="{{i:i}}"?/>
          template>

          從名字可以看出,這是用于渲染各種組件的template,所以當我們拿到react傳遞過來的data時,將其傳給template,template就能根據(jù)對應的組件名采用不同的模版進行渲染。隨后再用一個for循環(huán)將其子組件進行遞歸渲染,完成整個頁面的渲染。這個就可以理解為我們針對不同端寫的不同渲染器,如果我們編譯到wx小程序,這里面的內(nèi)容是會不同的。

          總之,「在」**react**「對其處理完之后,會把數(shù)據(jù)」**setData**「傳遞給「小程序」,小程序再用之前寫好的各種」**template**「將其渲染到頁面上?!?/strong>

          下面這張圖就是經(jīng)過react處理之后,能夠拿到頁面的數(shù)據(jù),將其傳遞給小程序之后,就能遞歸渲染出來。

          那么這樣的架構有什么問題呢,可以很明顯的看到會走兩遍diff,為什么會走兩遍diff呢?因為在react層為了獲取到我想要什么這個信息,我們必須走一遍diff,這樣才能將最后得到的data交給小程序。

          而交給小程序之后,小程序對于之前的流程是無感知的,所以它為了得到需要更新什么這個信息,也需要過一遍diff,或者通過一些其他的方式來拿到這個信息(并沒有深入了解過小程序的渲染流程,所以不確定是否是通過diff拿到的),所以這一整套流程就會走兩遍diff

          為什么我們不能將兩次diff合并為一次?因為小程序的渲染對開發(fā)者而言就是個黑盒,我們不能干擾到其內(nèi)部流程。如果我們能夠直接對接小程序的渲染sdk,那么其實根本沒必要走兩遍diff,因為前置的 reactdiff我們已經(jīng)能夠知道需要更新什么內(nèi)容。

          這個問題的本質和普通意義上的跨端框架沒有太大的區(qū)別,開發(fā)層也就是 react 知道自己需要什么東西,但是它沒有能力去渲染到界面上,所以需要通過小程序充當渲染層來渲染到真正的界面上。這種開發(fā)方式有一種用 react 去寫 vue 的意思,但是為什么會出現(xiàn)這種詭異的開發(fā)方式,如果這個 vue 做的足夠好的話,誰又想去這樣折騰?

          5.4 組件的嵌套

          其實還有一個小問題,wxtemplate是無法支持遞歸調(diào)用的,也就導致了我們想用template遞歸渲染data內(nèi)容是無法實現(xiàn)的,那么這個問題要如何解決呢..我們看一下上面的代碼在wx小程序中編譯出來的結果:

          我們可以看到各種template之間多了0、1、2、3這種標號..就是為了解決無法遞歸調(diào)用的問題,提前多造幾個名字不同功能相同的template,不就能跨過遞歸調(diào)用的限制了么...

          六、另一種粗暴的跨端

          上述的這些跨端都是通過某種架構方式去實現(xiàn)的,那如果我們粗暴一點的想,我能不能直接把一套代碼通過編譯的方式去編譯到不同的平臺。比如我把js代碼編譯成java代碼、object-c代碼,其實,個人感覺也不是不行,但是因為這些的差異實在太大,所以在寫js代碼的時候,可能需要非常強的約束性、規(guī)范性,把開發(fā)者限制在某個區(qū)域內(nèi),才能很好的編譯過去。也就是說,從jsjava其實是一個自由度高到自由度低的一個過程,肯定是無法完全一一對應上的,并且由于開發(fā)方式、語法完全不一樣,所以想通過編譯的方式將js編譯到iosandroid上去還是比較難的,但是對于小程序來說,嘗試把jsx編譯到template似乎是一個可行的方案,實際上,taro1/2 都是這么干的。不過從jsxtemplate也是一個自由度從高到低的一個過程,所以是沒辦法絕對完美地將把所有語法都編譯到template...

          這里可以給大家分享一個很有意思的例子,最近很火的 SolidJS 框架也支持用 JSX 寫代碼,但是它完全沒有react這么重的runtime,因為它的JSX最終會被編譯成一些原生的操作...我們看一個簡單的例子:https://playground.solidjs.com/

          react 語境下,我們在input框里面輸入內(nèi)容的時候,上面的文案應該跟著改變,但是實際上并沒有。這是因為這個東西最后被編譯完之后是一些原生的操作,它其實只會運行一遍,最后你觸發(fā)的各種click并不會導致函數(shù)重新運行,而是直接通過原生操作操作到對應的DOM上來修改視圖,也就導致了上面問題的產(chǎn)生。

          其實我覺得這樣挺反人類的,雖然是JSX的語法,但是卻缺少了最核心的東西:函數(shù)式的思維。(還不如寫template)。

          七、virtual dom

          7.1 對于跨端的意義

          提到跨端,可能很多人第一個想到的東西就是 virtual dom,因為它是對于ui的抽象,脫離了平臺,所以可能很多人會覺得virtual dom和跨平臺已經(jīng)是綁定在一起的東西了。但是其實個人感覺并不是。

          首先我們回想一下,我們之前說到的跨平臺的本質是什么?開發(fā)層知道自己想要什么,然后告訴渲染層自己想要什么,就這么簡單。那對于react-native來說,是通過virtual dom來判斷自己需要更新什么結點的嗎?其實并不是,單靠一個virtual dom還不足以獲取到這個信息,必須還要加上diff,所以是virtual dom+diff獲取到了自己想要什么的信息,再通過通信的方式告訴native去更新真正的結點。

          所以virtual dom在這個里面只扮演了一個獲取方法的角色,是通過virtual dom+diff這個方法拿到了我們想要的東西。換言之,我們也可以通過其他的方法來拿到我們想要什么。比如之前分享的san框架,這是一個沒有virtual dom的框架,但是它為什么能夠跨平臺,我們先不管它內(nèi)部是如何實現(xiàn)的,但是在更新階段,如果它在某個時刻調(diào)用了 createElement,那么它一定是知道了:自己想要什么。對應上跨端的內(nèi)容,這個時候就能通過某種手段去告訴native,渲染某個東西。

          「所以,當我們通過其他手段獲取到了:我們想要什么這個信息之后,就能通知」**native**「去渲染真正的內(nèi)容。」

          7.2 virtual dom的優(yōu)勢

          那么vdom的優(yōu)勢在于什么地方?我認為主要是下面兩個:

          1. 開創(chuàng)jsx新時代,函數(shù)式編程思想
          2. 強大的表達力。能夠使用template獲取更多優(yōu)化信息,又能夠支持 jsx

          首先,jsx 簡直開創(chuàng)了一個新時代,讓我們能夠以函數(shù)式編程思想去寫ui,之前誰能想到一個切圖仔還能用這樣的方式去寫ui。

          其次,我們知道,vue雖然是使用的template作為dsl,但是實際上我們也是可以寫jsx的,jsx所提供的靈活能力是template無法比擬的。而之所以能夠同時支持templatejsx其實就是因為vdom的存在,如果vue不引入vdom,是沒辦法說去支持jsx的語法的,或者說,是沒辦法去支持真正的jsx。

          八、結語

          還是那句話,跨端就是:「我知道我想要什么,但是我沒有能力去渲染,我要通知有能力渲染的人來幫助我渲染。」

          他們的本質都非常簡單,但是細節(jié)卻非常難處理,同時對于目前市面上的多種跨端框架,也需要大家根據(jù)自己的項目去權衡利弊選擇一個最有方案,畢竟目前沒有一個框架能完全吊打所有其他框架,適合自己的才是最好的。

          瀏覽 56
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                    <th id="afajh"><progress id="afajh"></progress></th>
                    蜜芽AV无码 | 国产一级黄色A片在线观看 | 欧美一区二区 | 人人靠人人操 | 国产操逼视频。 |