React Native的熱更新

Hot Reloading

React Native(下文簡稱RN)致力于提供最好的開發(fā)體驗,亮點之一就是極大縮短了修改文件到頁面刷新的時間。在文件修改之后,頁面可以直接獲取到這一變動并更新邏輯,這一技術(shù)被稱為Hot Reloading。
RN這一技術(shù)的實現(xiàn)依賴以下三種特性:

  • 使用JavaScript作為開發(fā)語言,規(guī)避了長時間編譯的問題。
  • 使用Packager的工具將es6/flow/jsx文件轉(zhuǎn)為Virtual Dom可以理解的普遍JS文件。Packager以服務(wù)器的形式將中間狀態(tài)保存在內(nèi)存中,這一處理使得對快速更新變動提供了強效支持,并且使用多內(nèi)核處理。
  • 使用一個稱為Live Reload的特性在項目保存后刷新。

通過以上特性,開發(fā)瓶頸由編譯時間變?yōu)槿绾伪3諥PP的state。

Hot Reloading實現(xiàn)

Hot Reloading的設(shè)計是在文件發(fā)生變化時,實時去刷新應(yīng)用的狀態(tài)。Hot Reloading是在Hot Module Replacement特性的基礎(chǔ)上實現(xiàn)的,簡稱HMR(熱組件替換)。這個特性由Webpack首次提出,現(xiàn)在應(yīng)用在RN Packager。

HMR包含了JS組件改變后的最新代碼,在HMR Runtime收到時,會將舊的代碼替換為新的:


HMR structure

下面通過代碼示例進行講解:

// log.js
function log(message) {
  const time = require('./time');
  console.log(`[${time()}] ${message}`);
}

module.exports = log;
// time.js
function time() {
  return new Date().getTime();
}

module.exports = time;

可以看到在log.js內(nèi)依賴了time。
在應(yīng)用打包時,RN會在組件系統(tǒng)內(nèi)通過__d方法注冊每個組件。對于以上示例來說,在眾多的__d定義中,log的定義如下:

__d('log', function() {
  ... // module's code
});

RN將每個組件的代碼通過匿名函數(shù)的方式封裝,類似于我們知道的工廠方法。組件系統(tǒng)的運行時會追蹤每個組件的工廠方法。
組件在被獲取后會進行緩存,緩存與否的處理方式是不一樣的。
未緩存:在time的代碼發(fā)生改變時,Packager會將time的最新代碼發(fā)送給runtime。

no cacke

存在緩存:先清除緩存 -> 替換time -> 在log使用time時重新建立緩存

has been required

HMR API

RN中通過引入hot對象的方式擴展了組件系統(tǒng)。hot對象提供了accept方法用于接受組件被修改后的回調(diào)方法。
下面這個示例,是說明了HMR中accept的用法,并沒有使用React Hot Loader,沒有React Transform等其他RN Hot Reloading的特性。HMR并不會改變React組件的語法,HMR只是一個用于處理幾個步驟的優(yōu)秀框架:獲取更新,將更新更新的模塊注入到腳本,調(diào)用回調(diào)。


var React = require('react')
var ReactDOM = require('react-dom')

// Render the root component normally
var rootEl = document.getElementById('root')
ReactDOM.render(<App />, rootEl)

// Are we in development mode?
if (module.hot) {
  // Whenever a new version of App.js is available
  module.hot.accept('./App', function () {
    // Require the new version and render it instead
    var NextApp = require('./App')
    ReactDOM.render(<NextApp />, rootEl)
  })
}

WebPack設(shè)計HMR的內(nèi)部細節(jié)

上面我們說過,React Native的Hot Reloading是在HMR的基礎(chǔ)上實現(xiàn)的,而HMR是WebPack的重要特性,我們下面分析下,HRM的實現(xiàn)細節(jié)。

對于應(yīng)用來說更新步驟如下:

  1. 應(yīng)用使用HRM runtime去檢測更新。
  2. runtime異步下載更新,通知應(yīng)用。
  3. 告知runtime去應(yīng)用更新
  4. 同步去應(yīng)用更新

對于編譯器的更新步驟如下:

  1. 更新manifest。
  2. 更新chunks,chunks是WebPack用于代碼分離的工具之一。

manifest內(nèi)包含新的編譯哈希和已更新的chunks列表。

對于Runtime的更新步驟:
這里我們先說一下Runtime:對于manifest來講,runtime主要是指在瀏覽器運行時,webpack 用來連接模塊化的應(yīng)用程序的所有代碼。runtime 包含在模塊交互時連接模塊所需的加載和解析邏輯。包括瀏覽器中的已加載模塊的連接,以及懶加載模塊的執(zhí)行邏輯。
對于組件系統(tǒng)運行時,runtime提供了兩個方法:checkapply。
check會發(fā)起HTTP請求來更新manifest,如果請求失敗,則更新失敗。請求成功后會對比新的chunks和已經(jīng)加載的chunks列表。當(dāng)所有更新的chunks下載完成并且可以應(yīng)用時,runtime會切換到ready狀態(tài)。

apply方法會將所有更新的組件標記為不可用。對于不可用組件,需要handler去處理,如果沒有處理,會逐級向上尋找,知道找到handler或者程序入口,如果直至入口都沒有處理,就會退出程序。

HMR Runtime

如果需要更新的組件,已經(jīng)被緩存了,就不能單純的只是完成替換,需要先接觸緩存的綁定關(guān)系。清除依賴關(guān)系的方式是遞歸進行的。


clear recursively

對于已經(jīng)緩存的組件,會查找依賴關(guān)系,并逐級解除。例如上圖,log的上級有MovieScreen和MovieSearch,MovieScreen沒有緩存log,所以遞歸結(jié)束。MovieSearch依賴log,而MovieRouter依賴MovieSearch,他們之間的緩存也需要解除。
為了遍歷依賴樹,會創(chuàng)建一個逆序依賴文件,如下:

{
  modules: [
    {
      name: 'time',
      code: /* time's new code */
    }
  ],
  inverseDependencies: {
    MovieRouter: [],
    MovieScreen: ['MovieRouter'],
    MovieSearch: ['MovieRouter'],
    log: ['MovieScreen', 'MovieSearch'],
    time: ['log'],
  }
}

React 組件

React組件使用Hot Reloading機制要更復(fù)雜些,因為不能單純的替換代碼,這樣會導(dǎo)致DOM和組件的狀態(tài)丟失。
因為模塊被重新計算了,內(nèi)部組件的ID和過去的不同,對于React而言,你想要重新渲染一個全心的組件,所以React會卸載過去組件。所以,如上所說,React會摧毀組件的DOM和本地state。
解決這種問題有幾種方法:

將state存于外部

類似于Redux這種數(shù)據(jù)流管理框架,每個組件并不管理自己的狀態(tài),而是整個APP公用一個state,每個組件獲取這個state內(nèi)的數(shù)據(jù),這樣在更新組件時,就不需要擔(dān)心組件更新的問題。

保存DOM和本地state

為了解決DOM和本地狀態(tài)被銷毀的問題,有兩種不同的做法:

  1. 找到一種方法,將React實例從DOM和狀態(tài)中分離出來,只更新這個實例,完成后將其“粘回”DOM和state。
  2. 使用代理組件類型。這樣對于React來說,類型沒有改變,不需要重新去卸載和裝載,內(nèi)部的實際實現(xiàn),會根據(jù)熱更新發(fā)生變化。

失敗方案:React實例從DOM和狀態(tài)中分離
第一種方法聽上去更好,但是目前React并沒有提供可以分離/聚合React實例和DOM、還有運行的生命周期鉤子。哪怕我們可以使用React的私有API,這個方案也未必可行。
例如React組件可能會訂閱一些生命周期方法(componentDidMount等),即使我們可以在不摧毀DOM和狀態(tài)的情況下靜默替換舊的實例,但是因為過去的實例沒有取消對生命周期的訂閱,新的實例也無法訂閱成功。

成功方案:Proxy Component代理組件
Proxy Component就是使用在React Hot Loader和React Transform中的方案。這種方案會改變代碼的語法,但是目前在React中的應(yīng)用還算成功。
Proxy就是一個類,內(nèi)部封裝了關(guān)于顯示和狀態(tài)的實現(xiàn)。
React Hot Loader將通過module.exports內(nèi)的React組件通過代理進行封裝,輸出封裝后的代理類。
可以這么理解:當(dāng)調(diào)用<App>render<NavBar>時,實際是在render<NavbarProxy>。

轉(zhuǎn)換機制:在轉(zhuǎn)換時為每個React組件創(chuàng)建代理,代理在組件真正的生命周期中持有它們的狀態(tài)和其他代理方法。

React Component hot reload by proxy

除了創(chuàng)建代理組件,轉(zhuǎn)換還定義了accept方法,用于對組件進行強制刷新。這樣組件的熱更新就不會丟失任何狀態(tài)了。

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,990評論 2 374