跳到主要內容

介紹熱重載

·9 分鐘閱讀
Martín Bigio
Instagram 軟體工程師

React Native 的目標是為您提供最佳的開發者體驗。其中很重要的一部分是從您儲存檔案到能夠看到變更之間所需的時間。我們的目標是讓這個回饋迴圈保持在 1 秒以下,即使您的應用程式不斷成長。

我們透過三個主要功能接近這個理想

  • 使用 JavaScript 作為語言,因為它沒有冗長的編譯週期時間。
  • 實作一個名為 Packager 的工具,將 es6/flow/jsx 檔案轉換為 VM 可以理解的普通 JavaScript。它被設計為一個伺服器,將中間狀態保存在記憶體中,以實現快速的增量變更,並使用多核心。
  • 建構一個名為 Live Reload 的功能,在儲存時重新載入應用程式。

在這一點上,開發人員的瓶頸不再是重新載入應用程式所需的時間,而是遺失應用程式的狀態。常見的場景是開發一個距離啟動畫面多個螢幕的功能。每次重新載入時,您都必須一次又一次地點擊相同的路徑才能回到您的功能,使週期長達數秒。

熱重載

熱重載背後的想法是保持應用程式運行,並在運行時注入您編輯的檔案的新版本。這樣,您就不會遺失任何狀態,這在您調整 UI 時尤其有用。

一圖勝千言。查看 Live Reload(目前)和 Hot Reload(新的)之間的差異。

如果您仔細觀察,您會注意到可以從紅色錯誤框中恢復,並且您也可以開始導入以前不存在的模組,而無需執行完整重新載入。

警告: 因為 JavaScript 是一種非常有狀態的語言,所以熱重載無法完美實作。在實務中,我們發現目前的設定在大量常見用例中運作良好,並且在出現問題時始終可以使用完整重新載入。

熱重載從 0.22 版本開始提供,您可以啟用它

  • 開啟開發人員選單
  • 點擊「啟用熱重載」

簡而言之的實作

現在我們已經了解了為什麼我們需要它以及如何使用它,有趣的部分開始了:它實際上是如何運作的。

熱重載是建立在一個功能之上 熱模組替換,或 HMR。它最初由 webpack 引入,我們在 React Native Packager 內部實作了它。HMR 使 Packager 監視檔案變更並將 HMR 更新發送到應用程式中包含的輕量級 HMR 運行時。

簡而言之,HMR 更新包含已變更的 JS 模組的新程式碼。當運行時接收到它們時,它會將舊模組的程式碼替換為新的程式碼

HMR 更新包含的不僅僅是我們要變更的模組的程式碼,因為僅替換它不足以讓運行時接收到變更。問題在於模組系統可能已經快取了我們要更新的模組的 *exports*。例如,假設您有一個由這兩個模組組成的應用程式

// 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`,印出提供的訊息,包括模組 `time` 提供的目前日期。

當應用程式被捆綁時,React Native 使用 `__d` 函數在模組系統上註冊每個模組。對於這個應用程式,在許多 `__d` 定義中,將會有一個用於 `log` 的定義

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

這個調用將每個模組的程式碼包裝成一個匿名函數,我們通常將其稱為工廠函數。模組系統運行時追蹤每個模組的工廠函數、它是否已經被執行,以及此類執行的結果(exports)。當需要一個模組時,模組系統要么提供已經快取的 exports,要么首次執行模組的工廠函數並儲存結果。

假設您啟動您的應用程式並需要 `log`。在這一點上,`log` 和 `time` 的工廠函數都尚未執行,因此沒有快取任何 exports。然後,使用者修改 `time` 以返回 `MM/DD` 格式的日期

// time.js
function bar() {
const date = new Date();
return `${date.getMonth() + 1}/${date.getDate()}`;
}

module.exports = bar;

Packager 會將 time 的新程式碼發送到運行時(步驟 1),當 `log` 最終被需要時,導出的函數被執行時,它將使用 `time` 的變更來執行(步驟 2)

現在假設 `log` 的程式碼需要 `time` 作為頂層 require

const time = require('./time'); // top level require

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

module.exports = log;

當需要 `log` 時,運行時將快取其 exports 和 `time` 的 exports。(步驟 1)。然後,當 `time` 被修改時,HMR 流程不能僅在替換 `time` 的程式碼後就完成。如果這樣做,當執行 `log` 時,它將使用 `time` 的快取副本(舊程式碼)來執行。

為了讓 `log` 接收 `time` 的變更,我們需要清除其快取的 exports,因為它所依賴的模組之一被熱替換了(步驟 3)。最後,當再次需要 `log` 時,它的工廠函數將被執行,需要 `time` 並取得它的新程式碼。

HMR API

React Native 中的 HMR 透過引入 `hot` 物件來擴展模組系統。這個 API 基於 webpack 的 API。`hot` 物件公開一個名為 `accept` 的函數,它允許您定義一個回呼函數,該函數將在模組需要被熱替換時執行。例如,如果我們將 `time` 的程式碼更改如下,每次我們儲存 time 時,我們都會在控制台中看到「time changed」

// time.js
function time() {
... // new code
}

module.hot.accept(() => {
console.log('time changed');
});

module.exports = time;

請注意,只有在極少數情況下您才需要手動使用此 API。對於最常見的用例,熱重載應該可以開箱即用。

HMR 運行時

正如我們之前所見,有時僅接受 HMR 更新是不夠的,因為使用正在被熱替換的模組的模組可能已經被執行並且其 imports 已被快取。例如,假設電影應用程式範例的依賴樹有一個頂層 `MovieRouter`,它依賴於 `MovieSearch` 和 `MovieScreen` 視圖,而這些視圖又依賴於先前範例中的 `log` 和 `time` 模組

如果使用者訪問了電影的搜尋視圖,但沒有訪問另一個視圖,則除了 `MovieScreen` 之外的所有模組都會快取 exports。如果對模組 `time` 進行了更改,則運行時將必須清除 `log` 的 exports,以便它接收 `time` 的變更。這個過程不會在那裡結束:運行時將遞迴地重複這個過程,直到所有父模組都被接受。因此,它將抓取依賴於 `log` 的模組並嘗試接受它們。對於 `MovieScreen`,它可以跳過,因為它尚未被需要。對於 `MovieSearch`,它將必須清除其 exports 並遞迴地處理其父模組。最後,它將對 `MovieRouter` 執行相同的操作並在那裡結束,因為沒有模組依賴於它。

為了遍歷依賴樹,運行時從 Packager 接收 HMR 更新上的反向依賴樹。對於這個例子,運行時將收到像這樣的一個 JSON 物件

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

React 組件

React 組件更難與熱重載一起使用。問題在於我們不能簡單地用新的程式碼替換舊的程式碼,因為我們會遺失組件的狀態。對於 React Web 應用程式,Dan Abramov 實作了一個 Babel 轉換,它使用 webpack 的 HMR API 來解決這個問題。簡而言之,他的解決方案的工作原理是在 *轉換時* 為每個 React 組件建立一個代理。代理保存組件的狀態,並將生命週期方法委派給實際的組件,這些組件是我們熱重載的組件

除了建立代理組件之外,轉換還定義了 `accept` 函數,其中包含一段程式碼,以強制 React 重新渲染組件。這樣,我們就可以熱重載渲染程式碼,而不會遺失應用程式的任何狀態。

React Native 附帶的預設 轉換器 使用 `babel-preset-react-native`,它被 配置 為以與在使用 webpack 的 React Web 專案中使用 `react-transform` 相同的方式使用它。

Redux Store

要在 Redux store 上啟用熱重載,您只需要像在使用 webpack 的 Web 專案中所做的那樣使用 HMR API

// configureStore.js
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import reducer from '../reducers';

export default function configureStore(initialState) {
const store = createStore(
reducer,
initialState,
applyMiddleware(thunk),
);

if (module.hot) {
module.hot.accept(() => {
const nextRootReducer = require('../reducers/index').default;
store.replaceReducer(nextRootReducer);
});
}

return store;
};

當您變更 reducer 時,用於接受該 reducer 的程式碼將被發送到客戶端。然後客戶端將意識到 reducer 不知道如何接受自身,因此它將尋找所有引用它的模組並嘗試接受它們。最終,流程將到達單一 store,`configureStore` 模組,它將接受 HMR 更新。

結論

如果您有興趣協助改進熱重載,我鼓勵您閱讀 Dan Abramov 關於熱重載未來 的文章並做出貢獻。例如,Johny Days 將要 使其與多個連接的客戶端一起工作。我們依靠大家來維護和改進這個功能。

有了 React Native,我們有機會重新思考我們建構應用程式的方式,以使其成為出色的開發者體驗。熱重載只是拼圖的一塊,我們還可以做哪些瘋狂的 hack 來使其更好?

使 React Native 應用程式可訪問

·2 分鐘閱讀
Georgiy Kassabli
Facebook 軟體工程師

隨著最近在 Web 上推出 React 和在行動裝置上推出 React Native,我們為開發人員提供了一個新的前端框架來建構產品。建構穩健產品的一個關鍵方面是確保任何人都可以使用它,包括有視力障礙或其他殘疾的人。React 和 React Native 的無障礙 API 使您能夠讓任何由 React 驅動的體驗都可以被可能使用輔助技術(例如針對盲人和視障人士的螢幕閱讀器)的人使用。

在這篇文章中,我們將重點關注 React Native 應用程式。我們設計的 React 無障礙 API 在外觀和感覺上都與 Android 和 iOS API 相似。如果您之前為 Android、iOS 或 Web 開發過無障礙應用程式,您應該會對 React AX API 的框架和術語感到自在。例如,您可以使 UI 元素 *accessible*(因此暴露於輔助技術),並使用 *accessibilityLabel* 為元素提供字串描述

<View accessible={true} accessibilityLabel=”This is simple view”>

讓我們透過查看 Facebook 自己的 React 驅動產品之一:廣告管理員應用程式,來逐步了解 React AX API 的稍微更複雜的應用。

這是一段摘錄。閱讀完整文章請見 Facebook Code

React Native for Android:我們如何建構第一個跨平台 React Native 應用程式

·1 分鐘閱讀
Facebook 軟體工程師

今年稍早,我們介紹了 React Native for iOS。React Native 將開發人員在 Web 上使用 React 的習慣用法(宣告式、自我包含的 UI 組件和快速開發週期)帶到行動平台,同時保留了原生應用程式的速度、保真度和感覺。今天,我們很高興發布 React Native for Android。

在 Facebook,我們已經在生產環境中使用 React Native 一年多了。大約一年前,我們的團隊著手開發廣告管理員應用程式。我們的目標是創建一個新的應用程式,讓數百萬在 Facebook 上投放廣告的人可以管理他們的帳戶並隨時隨地創建新的廣告。它最終不僅成為 Facebook 的第一個完全 React Native 應用程式,而且也是第一個跨平台應用程式。在這篇文章中,我們想與您分享我們如何建構這個應用程式、React Native 如何使我們能夠更快地行動以及我們學到的教訓。

這是一段摘錄。閱讀完整文章請見 Facebook Code

React Native:將現代 Web 技術帶到行動裝置

·2 分鐘閱讀
Tom Occhino
Facebook 工程經理

我們在兩年前向世界介紹了 React,從那時起,它在 Facebook 內外都取得了令人印象深刻的成長。今天,即使沒有人被迫使用它,Facebook 的新 Web 專案通常也以某種形式使用 React 建構,並且它正在業界被廣泛採用。工程師每天都選擇使用 React,因為它使他們能夠花更多時間專注於他們的產品,並減少與框架的鬥爭。然而,直到我們使用 React 建構了一段時間後,我們才開始了解是什麼使它如此強大。

React 強迫我們將應用程式分解為離散的組件,每個組件代表一個單一視圖。這些組件使我們可以更輕鬆地迭代我們的產品,因為我們不需要將整個系統記在腦海中才能對其一部分進行變更。但更重要的是,React 使用宣告式 API 包裝了 DOM 的可變、命令式 API,這提高了抽象層次並簡化了程式設計模型。我們發現,當我們使用 React 建構時,我們的程式碼更加可預測。這種可預測性使我們可以更有信心地更快地迭代,並且我們的應用程式也因此更加可靠。此外,當我們的應用程式使用 React 建構時,不僅更容易擴展它們,而且我們發現擴展我們團隊的規模本身也更容易。

結合 Web 的快速迭代週期,我們已經能夠使用 React 建構一些很棒的產品,包括 Facebook.com 的許多組件。此外,我們還在 React 之上使用 JavaScript 建構了令人驚嘆的框架,例如 Relay,這使我們能夠大規模地大大簡化我們的數據獲取。當然,Web 只是故事的一部分。Facebook 還擁有廣泛使用的 Android 和 iOS 應用程式,它們建立在不相交的專有技術堆疊之上。不得不在多個平台上建構我們的應用程式使我們的工程組織分裂,但這只是使原生行動應用程式開發變得困難的原因之一。

這是一段摘錄。閱讀完整文章請見 Facebook Code