跳到主要內容

29 篇標註「工程」的文章

檢視所有標籤

React Native 每月快訊 #1

·6 分鐘閱讀
Tomislav Tenodi
Shoutem 產品經理

Shoutem,我們很幸運能夠從 React Native 的最初期就開始使用它。我們決定從第一天起就成為這個令人驚嘆的社群的一份子。很快地,我們意識到幾乎不可能跟上社群成長和改進的步伐。這就是為什麼我們決定組織每月會議,讓所有主要的 React Native 貢獻者可以簡要介紹他們的工作和計畫。

每月會議

我們在 2017 年 6 月 14 日舉行了第一次每月會議。React Native Monthly 的任務很簡單明瞭:改善 React Native 社群。展示團隊的工作有助於促進團隊之間線下的協作。

團隊

在第一次會議上,有 8 個團隊加入我們

我們希望有更多核心貢獻者加入即將到來的會議!

筆記

由於團隊的計畫可能引起更廣泛受眾的興趣,我們將在此處(React Native 部落格)分享這些計畫。所以,以下是這些計畫

Airbnb

  • 計畫將一些 A11y(無障礙功能)API 新增到 ViewAccessibilityInfo 原生模組。
  • 將研究在 Android 上為原生模組新增一些 API,以允許指定它們運行的執行緒。
  • 一直在研究潛在的初始化效能改進。
  • 一直在研究一些更複雜的捆綁策略,以用於「unbundle」之上。

Callstack

  • 正在研究透過使用 Detox 進行 E2E 測試來改進發布流程。提取請求應很快就會登陸。
  • 他們一直在處理的 Blob 提取請求已合併,後續的提取請求即將推出。
  • 在內部專案中增加 Haul 的採用率,以查看其與 Metro Bundler 相比的效能。正在與 webpack 團隊合作,以實現更好的多執行緒效能。
  • 在內部,他們實作了更好的基礎架構來管理開源專案。計畫在未來幾週內推出更多內容。
  • React Native Europe 會議即將到來,目前還沒有什麼有趣的內容,但歡迎大家參加!
  • 暫時退出了 react-navigation 一段時間,以研究替代方案(尤其是原生導航)。

Expo

  • 正在努力使在 Snack 中安裝 npm 模組成為可能,這對於程式庫將範例新增到文件中將非常有用。
  • 正在與 KrzysztofSoftware Mansion 的其他人合作,進行 Android 上的 JSC 更新和手勢處理程式庫。
  • Adam Miskiewicz 正在將他的重點轉移到 react-navigation
  • Create React Native App 已在文件中的入門指南中。Expo 希望鼓勵程式庫作者清楚地說明他們的程式庫是否適用於 CRNA,如果適用,請說明如何設定。

Facebook

  • React Native 的打包器現在是 Metro Bundler,在一個獨立的儲存庫中。倫敦的 Metro Bundler 團隊很高興能滿足社群的需求、提高額外用例(超出 React Native 範圍)的模組化,並提高對問題和 PR 的回應速度。
  • 在未來幾個月,React Native 團隊將致力於改進原始組件的 API。預期在版面配置怪癖、無障礙功能和 Flow 類型方面有所改進。
  • React Native 團隊也計畫在今年透過重構以完全支援 Windows 和 macOS 等第三方平台來改進核心模組化。

GeekyAnts

  • 該團隊正在開發一個 UI/UX 設計應用程式(代號:Builder),該應用程式直接與 .js 檔案一起使用。目前,它僅支援 React Native。它類似於 Adobe XD 和 Sketch。
  • 該團隊正在努力工作,以便您可以將現有的 React Native 應用程式載入到編輯器中、進行變更(視覺上,作為設計師)並將變更直接儲存到 JS 檔案中。
  • 人們正試圖彌合設計師和開發人員之間的差距,並將他們帶到同一個儲存庫中。
  • 此外,NativeBase 最近達到了 5,000 個 GitHub 星星。

Microsoft

  • CodePush 現在已整合到 Mobile Center 中。這是提供與發布、分析和其他服務更整合體驗的第一步。請參閱他們的公告此處
  • VS Code 在偵錯方面存在錯誤,他們目前正在修復該錯誤,並將推出新的組建。
  • 正在研究用於整合測試的 Detox,並查看 JSC Context 以取得與崩潰報告一起的變數。

Shoutem

  • 使其更容易使用 React Native 社群的工具來處理 Shoutem 應用程式。您將能夠使用所有 React Native 命令來運行在 Shoutem 上建立的應用程式。
  • 正在研究 React Native 的效能分析工具。他們在設定方面遇到了很多問題,他們將寫下他們在此過程中發現的一些見解。
  • Shoutem 正在努力使將 React Native 與現有的原生應用程式整合變得更容易。他們將記錄他們在公司內部開發的概念,以便獲得社群的回饋。

Wix

  • 在內部努力採用 Detox,以將 Wix 應用程式的重要部分轉移到「零手動 QA」。因此,Detox 正在被數十名開發人員在生產環境中大量使用,並且正在快速成熟。
  • 致力於為 Metro Bundler 新增支援,以便在組建期間覆寫任何檔案副檔名。它不僅支援「ios」和「android」,還將支援任何自訂副檔名,例如「e2e」或「detox」。計畫將其用於 E2E 模擬。已經有一個名為 react-native-repackager 的程式庫,目前正在處理 PR。
  • 正在研究效能測試的自動化。這是一個名為 DetoxInstruments 的新儲存庫。您可以查看一下,它正在開源開發中。
  • 正在與來自 KPN 的貢獻者合作開發適用於 Android 的 Detox,並支援真實裝置。
  • 正在考慮將「Detox 作為平台」,以允許建置其他需要自動化模擬器/裝置的工具。一個範例是適用於 React Native 的 Storybook 或 Ram 關於整合測試的想法。

下一次會議

會議將每四周舉行一次。下一次會議定於 2017 年 7 月 12 日舉行。由於我們才剛開始舉行此會議,我們想知道這些筆記對 React Native 社群有何益處。如果您對我們應在後續會議中涵蓋哪些內容或我們應如何改進會議的輸出有任何建議,請隨時在 Twitter 上 ping 我。

React Native 中更佳的清單檢視

·6 分鐘閱讀
Spencer Ahrens
Facebook 軟體工程師

在我們於社群群組中發布 預告公告 後,你們中的許多人已經開始試用我們的一些新清單組件,但我們今天正式宣布它們!不再有 ListViewDataSource、過時的列、被忽略的錯誤或過多的記憶體消耗 - 使用最新的 React Native 2017 年 3 月發布候選版本 (0.43-rc.1),您可以從新的組件套件中選擇最適合您用例的組件,它們開箱即用地具有出色的效能和功能集

<FlatList>

這是用於簡單、高效能清單的工作組件。提供資料陣列和 renderItem 函式,您就可以開始使用了

<FlatList
data={[{title: 'Title Text', key: 'item1'}, ...]}
renderItem={({item}) => <ListItem title={item.title} />}
/>

<SectionList>

如果您想要呈現一組分成邏輯區段的資料,可能帶有區段標題(例如,在字母順序的通訊錄中),並且可能具有異質資料和呈現方式(例如,帶有一些按鈕的個人資料檢視,後跟一個撰寫器,然後是一個照片網格,然後是一個朋友網格,最後是一個故事清單),這是最佳選擇。

<SectionList
renderItem={({item}) => <ListItem title={item.title} />}
renderSectionHeader={({section}) => <H1 title={section.key} />}
sections={[ // homogeneous rendering between sections
{data: [...], key: ...},
{data: [...], key: ...},
{data: [...], key: ...},
]}
/>

<SectionList
sections={[ // heterogeneous rendering between sections
{data: [...], key: ..., renderItem: ...},
{data: [...], key: ..., renderItem: ...},
{data: [...], key: ..., renderItem: ...},
]}
/>

<VirtualizedList>

幕後實作具有更彈性的 API。如果您的資料不在純陣列中(例如,不可變清單),則特別方便。

功能

清單在許多情境中使用,因此我們在新組件中加入了完整的功能,以開箱即用地處理大多數用例

  • 捲動載入 (onEndReached)。
  • 下拉刷新 (onRefresh / refreshing)。
  • 可配置 可見性 (VPV) 回呼 (onViewableItemsChanged / viewabilityConfig)。
  • 水平模式 (horizontal)。
  • 智慧型項目和區段分隔符號。
  • 多欄支援 (numColumns)
  • scrollToEndscrollToIndexscrollToItem
  • 更佳的 Flow 類型。

一些注意事項

  • 當內容捲動出渲染視窗時,項目子樹的內部狀態不會保留。請確保您的所有資料都擷取在項目資料或 Flux、Redux 或 Relay 等外部儲存區中。

  • 這些組件基於 PureComponent,這表示如果 props 保持淺層相等,它們將不會重新渲染。請確保您的 renderItem 函式直接依賴的所有內容都作為在更新後不為 === 的 prop 傳遞,否則您的 UI 可能不會在變更時更新。這包括 data prop 和父組件狀態。例如

    <FlatList
    data={this.state.data}
    renderItem={({item}) => (
    <MyItem
    item={item}
    onPress={() =>
    this.setState(oldState => ({
    selected: {
    // New instance breaks `===`
    ...oldState.selected, // copy old data
    [item.key]: !oldState.selected[item.key], // toggle
    },
    }))
    }
    selected={
    !!this.state.selected[item.key] // renderItem depends on state
    }
    />
    )}
    selected={
    // Can be any prop that doesn't collide with existing props
    this.state.selected // A change to selected should re-render FlatList
    }
    />
  • 為了限制記憶體並實現流暢捲動,內容會異步地在螢幕外渲染。這表示捲動速度可能快於填充率,並暫時看到空白內容。這是一個可以調整以滿足每個應用程式需求的權衡,我們正在幕後努力改進它。

  • 預設情況下,這些新清單會在每個項目上尋找 key prop,並將其用於 React key。或者,您可以提供自訂的 keyExtractor prop。

效能

除了簡化 API 之外,新的清單組件還具有顯著的效能增強功能,主要功能是幾乎恆定的記憶體使用量,適用於任何數量的列。這是透過「虛擬化」渲染視窗外部的元素來完成的,方法是將它們從組件層次結構中完全卸載,並從 react 組件中回收 JS 記憶體,以及從陰影樹和 UI 檢視中回收原生記憶體。這有一個缺點,即內部組件狀態將不會保留,因此請確保您在組件本身之外追蹤任何重要的狀態,例如在 Relay 或 Redux 或 Flux 儲存區中。

限制渲染視窗也減少了 React 和原生平台需要完成的工作量,例如來自檢視遍歷。即使您正在渲染一百萬個元素中的最後一個,使用這些新清單也無需遍歷所有這些元素即可進行渲染。您甚至可以使用 scrollToIndex 跳到中間,而無需過度渲染。

我們還在排程方面進行了一些改進,這應該有助於應用程式的回應能力。渲染視窗邊緣的項目以較低的優先順序且不頻繁地渲染,在任何活動的手勢或動畫或其他互動完成後。

進階使用

ListView 不同,渲染視窗中的所有項目會在任何 props 變更時重新渲染。通常這很好,因為視窗化將項目數量減少到恆定數量,但如果您的項目在複雜方面,您應確保遵循 React 效能的最佳實務,並在您的組件中使用 React.PureComponent 和/或 shouldComponentUpdate,以限制遞迴子樹的重新渲染。

如果您可以在不渲染列的情況下計算列的高度,則可以透過提供 getItemLayout prop 來改善使用者體驗。這使得使用例如 scrollToIndex 捲動到特定項目更加流暢,並且將改善捲動指示器 UI,因為可以在不渲染內容的情況下確定內容的高度。

如果您有替代資料類型(例如,不可變清單),則 <VirtualizedList> 是最佳選擇。它採用 getItem prop,可讓您傳回任何給定索引的項目資料,並具有更寬鬆的 flow 類型。

如果您有不尋常的用例,還可以調整許多參數。例如,您可以使用 windowSize 來權衡記憶體使用量與使用者體驗、使用 maxToRenderPerBatch 來調整填充率與回應能力、使用 onEndReachedThreshold 來控制捲動載入發生的時間等等。

未來工作

  • 遷移現有介面(最終棄用 ListView)。
  • 更多功能,因為我們看到/聽到需求(請告訴我們!)。
  • 黏性區段標題支援。
  • 更多效能最佳化。
  • 支援具有狀態的功能項目組件。

idx:存在性函式

·2 分鐘閱讀
Timothy Yung
Facebook 工程經理

在 Facebook,我們經常需要存取使用 GraphQL 擷取的資料結構中深層巢狀的值。在存取這些深層巢狀值的過程中,一個或多個中間欄位通常是可為 Null 的。這些中間欄位可能因各種原因而為 Null,從隱私權檢查失敗到 Null 碰巧是表示非致命錯誤的最靈活方式。

不幸的是,存取這些深層巢狀的值目前既繁瑣又冗長。

props.user &&
props.user.friends &&
props.user.friends[0] &&
props.user.friends[0].friends;

有一個 ECMAScript 提案,旨在引入存在性運算符,這將使此操作方便得多。但在該提案最終確定之前,我們需要一個解決方案,可以提高我們的生活品質、維護現有的語言語意,並鼓勵使用 Flow 的類型安全。

我們提出了一個存在性函式,我們稱之為 idx

idx(props, _ => _.user.friends[0].friends);

此程式碼片段中的調用行為與上述程式碼片段中的布林運算式類似,只是重複次數顯著減少。idx 函式正好接受兩個引數

  • 任何值,通常是您想要存取巢狀值的物件或陣列。
  • 一個函式,它接收第一個引數並存取其上的巢狀值。

理論上,idx 函式將嘗試捕捉因存取 Null 或未定義的屬性而導致的錯誤。如果捕捉到此類錯誤,它將傳回 Null 或未定義。(您可以查看 idx.js 中可能如何實作此功能。)

實際上,嘗試捕捉每個巢狀屬性存取速度很慢,並且區分特定種類的 TypeErrors 很脆弱。為了應對這些缺點,我們建立了一個 Babel 外掛程式,將上述 idx 調用轉換為以下運算式

props.user == null
? props.user
: props.user.friends == null
? props.user.friends
: props.user.friends[0] == null
? props.user.friends[0]
: props.user.friends[0].friends;

最後,我們為 idx 新增了一個自訂 Flow 類型宣告,允許在第二個引數中進行的遍歷得到正確的類型檢查,同時允許在可為 Null 的屬性上進行巢狀存取。

該函式、Babel 外掛程式和 Flow 宣告現在在 GitHub 上提供。它們透過安裝 idxbabel-plugin-idx npm 套件,並將「idx」新增到您的 .babelrc 檔案中的外掛程式清單中來使用。

介紹 Create React Native App

·2 分鐘閱讀
Adam Perry
Expo 軟體工程師

今天我們宣布 Create React Native App:一個全新的工具,可讓開始使用 React Native 專案變得更加容易!它在設計上深受 Create React App 的啟發,並且是 FacebookExpo(前身為 Exponent)之間協作的產物。

許多開發人員在安裝和設定 React Native 當前的原生組建依賴項方面遇到困難,尤其是對於 Android。使用 Create React Native App,無需使用 Xcode 或 Android Studio,並且您可以使用 Linux 或 Windows 為您的 iOS 裝置進行開發。這是透過使用 Expo 應用程式完成的,該應用程式載入並運行以純 JavaScript 撰寫的 CRNA 專案,而無需編譯任何原生程式碼。

嘗試建立一個新專案(如果您已安裝 yarn,請替換為合適的 yarn 命令)

$ npm i -g create-react-native-app
$ create-react-native-app my-project
$ cd my-project
$ npm start

這將啟動 React Native 打包器並列印 QR 碼。在 Expo 應用程式中開啟它以載入您的 JavaScript。對 console.log 的呼叫會轉發到您的終端機。您可以使用任何標準 React Native API 以及 Expo SDK

原生程式碼呢?

許多 React Native 專案都有需要編譯的 Java 或 Objective-C/Swift 依賴項。Expo 應用程式確實包含用於相機、視訊、聯絡人等的 API,並捆綁了流行的程式庫,例如 Airbnb 的 react-native-mapsFacebook 驗證。但是,如果您需要 Expo 未捆綁的原生程式碼依賴項,那麼您可能需要為其建立自己的組建配置。就像 Create React App 一樣,CRNA 支援「彈出」。

您可以運行 npm run eject 來取得與 react-native init 將產生的專案非常相似的專案。那時,您將需要 Xcode 和/或 Android Studio,就像您從 react-native init 開始一樣,使用 react-native link 新增程式庫將會有效,並且您將完全控制原生程式碼編譯過程。

問題?回饋?

Create React Native App 現在已足夠穩定,可以廣泛使用,這表示我們非常渴望聽到您使用它的體驗!您可以在 Twitter 上找到我,或在 GitHub 儲存庫上開啟問題。非常歡迎提取請求!

將 Native Driver 用於 Animated

·7 分鐘閱讀
Janic Duplessis
App & Flow 軟體工程師

在過去一年中,我們一直致力於改進使用 Animated 程式庫的動畫效能。動畫對於創造美麗的使用者體驗非常重要,但也可能很難做好。我們希望讓開發人員可以輕鬆地建立高效能動畫,而無需擔心他們的一些程式碼會導致動畫延遲。

這是什麼?

Animated API 在設計時考慮到一個非常重要的限制,即它是可序列化的。這表示我們可以將動畫的所有內容發送到原生端,甚至在動畫開始之前,並允許原生程式碼在 UI 執行緒上執行動畫,而無需在每個影格上都通過橋接器。這非常有用,因為一旦動畫開始,JS 執行緒可能會被封鎖,而動畫仍將流暢運行。實際上,這種情況可能會經常發生,因為使用者程式碼在 JS 執行緒上運行,並且 React 渲染也可能會長時間鎖定 JS。

一點歷史...

這個專案大約在一年前開始,當時 Expo 在 Android 上建構了 li.st 應用程式。Krzysztof Magiera 受聘在 Android 上建構初始實作。結果運作良好,而 li.st 成為第一個使用 Animated 驅動原生動畫發佈的應用程式。幾個月後,Brandon Withrow 在 iOS 上建構了初始實作。在那之後,Ryan Gomba 和我共同致力於新增遺失的功能,例如支援 Animated.event,以及修正在生產應用程式中使用時發現的錯誤。這真是一個社群共同努力的成果,我要感謝所有參與者以及贊助大部分開發工作的 Expo。現在 React Native 中的 Touchable 組件以及新發布的 React Navigation 函式庫中的導航動畫都使用了它。

它是如何運作的?

首先,讓我們看看目前使用 Animated 和 JS 驅動程式時動畫是如何運作的。當使用 Animated 時,你會宣告一個節點圖,表示你想要執行的動畫,然後使用驅動程式使用預定義的曲線更新 Animated 值。你也可以透過將 Animated 值連接到使用 Animated.eventView 事件來更新 Animated 值。

以下是動畫步驟及其發生位置的細分

  • JS:動畫驅動程式使用 requestAnimationFrame 在每個影格上執行,並根據動畫曲線計算的新值來更新它驅動的值。
  • JS:中介值會被計算並傳遞到附加到 View 的屬性節點。
  • JS:View 會使用 setNativeProps 更新。
  • JS 到原生橋接器。
  • 原生:UIViewandroid.View 會被更新。

如你所見,大部分工作都發生在 JS 執行緒上。如果它被封鎖,動畫將會跳過影格。它也需要在每個影格上通過 JS 到原生橋接器來更新原生視圖。

原生驅動程式的作用是將所有這些步驟移至原生端。由於 Animated 會產生動畫節點圖,因此它可以在動畫開始時序列化並僅發送一次到原生端,從而無需回調到 JS 執行緒;原生程式碼可以負責在每個影格上直接在 UI 執行緒上更新視圖。

以下是如何序列化動畫值和插值節點的範例(不是確切的實作,僅為範例)。

建立原生值節點,這是將被動畫化的值

NativeAnimatedModule.createNode({
id: 1,
type: 'value',
initialValue: 0,
});

建立原生插值節點,這告訴原生驅動程式如何插值

NativeAnimatedModule.createNode({
id: 2,
type: 'interpolation',
inputRange: [0, 10],
outputRange: [10, 0],
extrapolate: 'clamp',
});

建立原生屬性節點,這告訴原生驅動程式它附加到的視圖上的哪個屬性

NativeAnimatedModule.createNode({
id: 3,
type: 'props',
properties: ['style.opacity'],
});

將節點連接在一起

NativeAnimatedModule.connectNodes(1, 2);
NativeAnimatedModule.connectNodes(2, 3);

將屬性節點連接到視圖

NativeAnimatedModule.connectToView(3, ReactNative.findNodeHandle(viewRef));

有了這些,原生動畫模組就擁有更新原生視圖所需的所有資訊,而無需前往 JS 計算任何值。

剩下的就是透過指定我們想要的動畫曲線類型以及要更新的動畫值來實際開始動畫。定時動畫也可以透過在 JS 中預先計算動畫的每個影格來簡化,以縮小原生實作的規模。

NativeAnimatedModule.startAnimation({
type: 'timing',
frames: [0, 0.1, 0.2, 0.4, 0.65, ...],
animatedValueId: 1,
});

現在這是動畫運行時發生的情況的細分

  • 原生:原生動畫驅動程式使用 CADisplayLinkandroid.view.Choreographer 在每個影格上執行,並根據動畫曲線計算的新值來更新它驅動的值。
  • 原生:中介值會被計算並傳遞到附加到原生視圖的屬性節點。
  • 原生:UIViewandroid.View 會被更新。

如你所見,不再有 JS 執行緒和橋接器,這意味著更快的動畫!🎉🎉

我如何在我的應用程式中使用它?

對於一般動畫,答案很簡單,只需在啟動動畫時將 useNativeDriver: true 新增到動畫配置中即可。

之前

Animated.timing(this.state.animatedValue, {
toValue: 1,
duration: 500,
}).start();

之後

Animated.timing(this.state.animatedValue, {
toValue: 1,
duration: 500,
useNativeDriver: true, // <-- Add this
}).start();

Animated 值僅與一個驅動程式相容,因此如果你在使用原生驅動程式啟動值上的動畫時,請確保該值上的每個動畫也使用原生驅動程式。

它也適用於 Animated.event,如果你有一個必須跟隨滾動位置的動畫,這非常有用,因為如果沒有原生驅動程式,由於 React Native 的異步性質,它總是會比手勢落後一個影格。

之前

<ScrollView
scrollEventThrottle={16}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: this.state.animatedValue } } }]
)}
>
{content}
</ScrollView>

之後

<Animated.ScrollView // <-- Use the Animated ScrollView wrapper
scrollEventThrottle={1} // <-- Use 1 here to make sure no events are ever missed
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: this.state.animatedValue } } }],
{ useNativeDriver: true } // <-- Add this
)}
>
{content}
</Animated.ScrollView>

注意事項

目前並非所有你可以使用 Animated 執行的操作都在原生動畫中受到支援。主要的限制是你只能動畫非佈局屬性,例如 transformopacity 將會運作,但 Flexbox 和位置屬性則不會。另一個限制是使用 Animated.event,它僅適用於直接事件,而不適用於冒泡事件。這表示它不適用於 PanResponder,但適用於 ScrollView#onScroll 之類的東西。

原生動畫也已成為 React Native 的一部分相當長一段時間,但從未被記錄下來,因為它被認為是實驗性的。因此,如果你想使用此功能,請確保你正在使用 React Native 的最新版本(0.40+)。

資源

有關動畫的更多資訊,我建議觀看 Christopher Chedeau演講

如果你想深入了解動畫以及將動畫卸載到原生端如何改善使用者體驗,還有 Krzysztof Magiera演講

React Native 應用程式的由右至左版面配置支援

·7 分鐘閱讀
Mengjue (Mandy) Wang
Facebook 軟體工程實習生

在應用程式商店發布應用程式後,國際化是進一步擴展你的受眾範圍的下一步。全球有 20 多個國家和無數人使用由右至左 (RTL) 的語言。因此,讓你的應用程式支援 RTL 對他們來說是必要的。

我們很高興宣布 React Native 已得到改進以支援 RTL 版面配置。這現在已在今天的 react-native master 分支中提供,並將在下一個 RC 版本中提供:v0.33.0-rc

這涉及變更 css-layout、RN 使用的核心佈局引擎和 RN 核心實作,以及特定的 OSS JS 組件以支援 RTL。

為了在生產環境中實戰測試 RTL 支援,最新版本的 Facebook 廣告管理員應用程式(第一個跨平台 100% RN 應用程式)現在以阿拉伯語和希伯來語提供,並為 iOSAndroid 提供 RTL 版面配置。以下是在這些 RTL 語言中的外觀

RN 中 RTL 支援的概述變更

css-layout 已經有一個用於佈局的 startend 概念。在由左至右 (LTR) 的佈局中,start 表示 left,而 end 表示 right。但在 RTL 中,start 表示 right,而 end 表示 left。這表示我們可以讓 RN 依賴 startend 計算來計算正確的佈局,其中包括 positionpaddingmargin

此外,css-layout 已經使每個組件的方向繼承自其父組件。這表示,我們只需要將根組件的方向設定為 RTL,整個應用程式就會翻轉。

下圖描述了高階的變更

這些包括

透過此更新,當你允許你的應用程式使用 RTL 版面配置時

  • 每個組件的版面配置都會水平翻轉
  • 如果你使用支援 RTL 的 OSS 組件,則某些手勢和動畫將自動具有 RTL 版面配置
  • 可能需要最少的額外努力才能使你的應用程式完全支援 RTL

使應用程式支援 RTL

  1. 為了支援 RTL,你應該首先將 RTL 語言包新增到你的應用程式。

  2. 透過在原生程式碼的開頭呼叫 allowRTL() 函數來允許你的應用程式使用 RTL 版面配置。我們提供此工具僅在你的應用程式準備就緒時才應用於 RTL 版面配置。這是一個範例

    iOS

    // in AppDelegate.m
    [[RCTI18nUtil sharedInstance] allowRTL:YES];

    Android

    // in MainActivity.java
    I18nUtil sharedI18nUtilInstance = I18nUtil.getInstance();
    sharedI18nUtilInstance.allowRTL(context, true);
  3. 對於 Android,你需要在 AndroidManifest.xml 檔案中的 <application> 元素中新增 android:supportsRtl="true"

現在,當你重新編譯你的應用程式並將裝置語言變更為 RTL 語言(例如阿拉伯語或希伯來語)時,你的應用程式版面配置應該會自動變更為 RTL。

編寫支援 RTL 的組件

一般來說,大多數組件已經支援 RTL,例如

  • 由左至右版面配置
  • 由右至左版面配置

但是,有幾種情況需要注意,你需要 I18nManager。在 I18nManager 中,有一個常數 isRTL 用於判斷應用程式的版面配置是否為 RTL,以便你可以根據版面配置進行必要的變更。

具有方向性意義的圖示

如果你的組件有圖示或圖像,它們在 LTR 和 RTL 版面配置中會以相同的方式顯示,因為 RN 不會翻轉你的來源圖像。因此,你應該根據版面配置樣式翻轉它們。

  • 由左至右版面配置
  • 由右至左版面配置

以下是根據方向翻轉圖示的兩種方法

  • transform 樣式新增到圖像組件

    <Image
    source={...}
    style={{transform: [{scaleX: I18nManager.isRTL ? -1 : 1}]}}
    />
  • 或者,根據方向變更圖像來源

    let imageSource = require('./back.png');
    if (I18nManager.isRTL) {
    imageSource = require('./forward.png');
    }
    return <Image source={imageSource} />;

手勢和動畫

在 Android 和 iOS 開發中,當你變更為 RTL 版面配置時,手勢和動畫與 LTR 版面配置相反。目前,在 RN 中,手勢和動畫在 RN 核心程式碼層級上不受支援,而是在組件層級上受支援。好消息是,其中一些組件今天已經支援 RTL,例如 SwipeableRowNavigationExperimental。但是,其他具有手勢的組件將需要手動支援 RTL。

一個說明手勢 RTL 支援的好例子是 SwipeableRow

手勢範例
// SwipeableRow.js
_isSwipingExcessivelyRightFromClosedPosition(gestureState: Object): boolean {
// ...
const gestureStateDx = IS_RTL ? -gestureState.dx : gestureState.dx;
return (
this._isSwipingRightFromClosed(gestureState) &&
gestureStateDx > RIGHT_SWIPE_THRESHOLD
);
},
動畫範例
// SwipeableRow.js
_animateBounceBack(duration: number): void {
// ...
const swipeBounceBackDistance = IS_RTL ?
-RIGHT_SWIPE_BOUNCE_BACK_DISTANCE :
RIGHT_SWIPE_BOUNCE_BACK_DISTANCE;
this._animateTo(
-swipeBounceBackDistance,
duration,
this._animateToClosedPositionDuringBounce,
);
},

維護支援 RTL 的應用程式

即使在最初發布與 RTL 相容的應用程式之後,你可能仍然需要迭代新功能。為了提高開發效率,I18nManager 提供了 forceRTL() 函數,用於更快速地進行 RTL 測試,而無需變更測試裝置語言。你可能想在你的應用程式中為此提供一個簡單的開關。這是 RNTester 中 RTL 範例的範例

<RNTesterBlock title={'Quickly Test RTL Layout'}>
<View style={styles.flexDirectionRow}>
<Text style={styles.switchRowTextView}>forceRTL</Text>
<View style={styles.switchRowSwitchView}>
<Switch
onValueChange={this._onDirectionChange}
style={styles.rightAlignStyle}
value={this.state.isRTL}
/>
</View>
</View>
</RNTesterBlock>;

_onDirectionChange = () => {
I18nManager.forceRTL(!this.state.isRTL);
this.setState({isRTL: !this.state.isRTL});
Alert.alert(
'Reload this page',
'Please reload this page to change the UI direction! ' +
'All examples in this app will be affected. ' +
'Check them out to see what they look like in RTL layout.',
);
};

在開發新功能時,你可以輕鬆切換此按鈕並重新載入應用程式以查看 RTL 版面配置。好處是你無需變更語言設定即可進行測試,但是某些文字對齊方式不會變更,如下一節所述。因此,在發布之前,始終最好在 RTL 語言中測試你的應用程式。

限制和未來計畫

RTL 支援應涵蓋你的應用程式中的大多數 UX;但是,目前有一些限制

  • 文字對齊行為在 Android 和 iOS 中有所不同
    • 在 iOS 中,預設文字對齊方式取決於活動語言包,它們始終在同一側。在 Android 中,預設文字對齊方式取決於文字內容的語言,即英文將靠左對齊,而阿拉伯文將靠右對齊。
    • 理論上,這應該在跨平台之間保持一致,但是當使用應用程式時,有些人可能更喜歡一種行為而不是另一種行為。可能需要更多使用者體驗研究來找出文字對齊的最佳實務。
  • 沒有「真正」的左/右

    如前所述,我們將 JS 端的 left/right 樣式映射到 start/end,RTL 版面配置中程式碼中的所有 left 在螢幕上都變成「right」,而程式碼中的 right 在螢幕上都變成「left」。這很方便,因為你不需要對你的產品程式碼進行太多變更,但這表示無法在程式碼中指定「真正左」或「真正右」。在未來,允許組件控制其方向而與語言無關可能是必要的。

  • 使手勢和動畫的 RTL 支援對開發人員更友善

    目前,仍然需要一些程式設計工作才能使手勢和動畫與 RTL 相容。在未來,理想的情況是找到一種方法,使手勢和動畫的 RTL 支援對開發人員更友善。

試試看!

查看 RNTester 中的 RTLExample,以更了解 RTL 支援,並讓我們知道它對你來說如何運作!

最後,感謝你的閱讀!我們希望 React Native 的 RTL 支援可以幫助你為國際受眾擴展你的應用程式!

深入探討 React Native 效能

·2 分鐘閱讀
Pieter De Baets
Facebook 軟體工程師

React Native 允許你使用 React 和 Relay 的宣告式程式設計模型,以 JavaScript 建構 Android 和 iOS 應用程式。這使得程式碼更簡潔、更易於理解;快速迭代而無需編譯週期;以及跨多個平台輕鬆共享程式碼。你可以更快地發布產品,並專注於真正重要的細節,使你的應用程式看起來和感覺都很棒。最佳化效能是其中的重要組成部分。這是我們如何使 React Native 應用程式啟動速度提高一倍的故事。

為何要趕時間?

對於運行速度更快的應用程式,內容載入速度很快,這表示人們有更多時間與之互動,而流暢的動畫使應用程式使用起來更加愉快。在新興市場中,2011 年級別的手機2G 網路上佔大多數,對效能的關注可以決定應用程式是否可用。

自從在 iOSAndroid 上發布 React Native 以來,我們一直在改善列表視圖滾動效能、記憶體效率、UI 響應速度和應用程式啟動時間。啟動設定了應用程式的第一印象,並對框架的所有部分施加壓力,因此它是最值得且最具挑戰性的問題。

這是摘錄。在 Facebook Code 上閱讀帖子的其餘部分。

介紹 Hot Reloading

·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 更新包含的不僅僅是我們要變更的模組的程式碼,因為替換它不足以讓運行時接收變更。問題在於模組系統可能已經快取了我們要更新的模組的導出。例如,假設你有一個由這兩個模組組成的應用程式

// 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
});

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

假設你啟動你的應用程式並需要 log。此時,logtime 的工廠函數都尚未執行,因此沒有快取任何導出。然後,使用者修改 timeMM/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 作為頂層需求

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

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

module.exports = log;

當需要 log 時,運行時會快取其導出和 time 的導出。(步驟 1)。然後,當 time 被修改時,HMR 過程不能在替換 time 的程式碼後簡單地完成。如果這樣做,當 log 被執行時,它將使用 time 的快取副本(舊程式碼)執行。

為了讓 log 接收 time 的變更,我們需要清除其快取的導出,因為它所依賴的模組之一已被熱交換(步驟 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 更新是不夠的,因為使用正在熱交換的模組的模組可能已經執行,並且其導入已快取。例如,假設電影應用程式範例的依賴樹有一個頂層 MovieRouter,它依賴於 MovieSearchMovieScreen 視圖,而這些視圖又依賴於先前範例中的 logtime 模組

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

為了遍歷依賴樹,運行時會在 HMR 更新時從 Packager 接收反向依賴樹。對於此範例,運行時將接收如下所示的 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 儲存區

若要在 Redux 儲存區上啟用熱重載,你只需要使用 HMR API,類似於你在使用 webpack 的 Web 專案中所做的操作

// 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 更新。

結論

如果您有興趣協助改善熱重載 (hot reloading),我鼓勵您閱讀 Dan Abramov 關於熱重載未來發展的文章 並做出貢獻。例如,Johny Days 將 使其能夠與多個連接的用戶端協同運作。我們仰賴各位來維護和改進這項功能。

藉由 React Native,我們有機會重新思考建構應用程式的方式,使其成為絕佳的開發者體驗。熱重載只是拼圖中的一塊,我們還能進行哪些瘋狂的 hack 來使其變得更好呢?

讓 React Native 應用程式更易於存取

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

隨著近期在網頁上推出 React 以及在行動裝置上推出 React Native,我們為開發者提供了一個新的前端框架來建構產品。建構穩健產品的一個關鍵面向是確保任何人都能使用它,包括有視力障礙或其他身心障礙的人。React 和 React Native 的 Accessibility API 使您能夠讓任何 React 驅動的體驗,都能被可能使用輔助科技的人使用,例如供盲人和視覺障礙人士使用的螢幕閱讀器。

在這篇文章中,我們將聚焦於 React Native 應用程式。我們設計的 React Accessibility API,使其外觀和感覺與 Android 和 iOS API 相似。如果您之前曾為 Android、iOS 或網頁開發過無障礙應用程式,您應該會對 React AX API 的框架和命名感到熟悉。例如,您可以將 UI 元素設為無障礙(因此對輔助科技公開),並使用 accessibilityLabel 為該元素提供字串描述。

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

讓我們透過檢視 Facebook 自身以 React 驅動的產品之一:廣告管理員應用程式,來逐步了解 React AX API 更深入的應用。

這是一段節錄。請在 Facebook Code 上閱讀文章的其餘部分。