詳解 React Native

蘆葦科技App技術團隊發表於2019-03-02

React Native,是一個混合移動應用開發框架,本文根據 C. Enrique Ortiz 的文章 Dissecting React Native 同時結合自己開發中的一些理解編寫

React Native 是目前流行的跨平臺移動應用開發框架之一。本文概述了 React Native,以及作者在開發 React Native 應用的最佳實踐經驗雜談。

在開發移動應用時,我們可以選擇使用原生,純基於 Web 或使用混合方法(即使用原生和基於 Web 的技術的組合)。可以使用 Apache Cordova(或 Adobe PhoneGap)等開源平臺編寫 HTML,CSS 和 JavaScript 的跨平臺混合應用。這些應用在原生 Web 檢視(瀏覽器)中執行,所以看著就像 Web 應用,不過可以使用 JavaScript 和一些 Apache Cordova 外掛呼叫原生功能和 API。所以可以使用類似於 Web 應用而非原生應用的使用者介面上架到 Apple Store 或 Google Play 的應用。或者,可以使用 C#,XAML 和 CSS 使用 Xamarin 來編寫原生應用。對於用 HTML5,CSS 和 JavaScript 編寫的非原生移動 Web 應用,還可以配套使用 jQuery Mobile 等框架。

React Native 採用不同的方法進行混合移動應用開發。React,React Native 採用 JavaScript 代替生成原生 UI 元件的方式來構建基於 Web 的互動介面,因此會有更豐富的 UI 體驗效果,同時也能夠很好地呼叫底層框架的UI使用。 React Native 已經成為一種流行的移動開發技術,它提供了一個使用 JavaScript 構建原生跨平臺移動應用的強大框架,在需要時,我們也可以使用 Objective-C,Swift或 Java 來編寫原生程式碼來橋接。

使用 React Native 有利有弊。從積極的方面來說,React Native 已經成為受支援的開源社群的熱門,可以使用一組技術(如 JSX,React Native 元件和 JavaScript)為 iOS 和 Android 構建移動應用。這反過來有助於在釋出新版本時使 iOS 和 Android 應用保持同步。但是,React Native 仍在不斷髮展,當要在當前不需要 React Native 提供的功能(例如如何進行UI導航或使用地圖)時決定使用哪些庫時,可能會造成混淆。此外,根據應用的複雜程度,可能需要編寫區別於平臺的程式碼來解決移動平臺的差異。對於複雜的應用,可能要編寫自定義元件或深入瞭解 iOS 和 Android(例如,出於效能原因或將 React Native 新增到現有原生應用時所需的但不支援的UI元件)。

配置 React Native 環境

React Native 的核心語言是 JavaScript,特別是 ECMAScript 6(ES6)。因此,需要熟悉ES6的一些最新功能。此外,在開始開發第一個 React Native 應用之前,需要安裝必備的工具,比如 IDE,JavaScript 庫等。另外,還需要了解核心的 React Native API。

ECMAScript 6 功能

ES6 引入了許多改進,其中一些值得一提,因為它們在最新的 React Native 框架中已經被用到:

  • 變數和作用域:關鍵字 var,let 和 const 表示變數作用域,其中 var 是函式作用域,let 是塊作用域,const 也是塊作用域。 const 變數是常量,但在 JavaScript 中對於物件和陣列來說,它是可變的。直譯器將變數宣告提升或移動到var 宣告的作用域頂部,而 let 和 const 宣告不會被掛起。
  • 箭頭函式:一種新型別的函式,它允許更緊湊,更簡潔的表示法,並且具有靜態或詞彙的定義(它不會在詞法上圍繞它)。可以看以下這些示例。

沒有指定引數的箭頭函式:

() => { ... }
複製程式碼

帶一個引數,注意省略了的括號:

x => { ... }
複製程式碼

指定幾個引數:

(x, y) => {* 
...
}
複製程式碼

引數定義和箭頭函式的箭頭必須在同一行中。

  • Promises:Promises 是非同步程式設計的替代API,它提供了優於傳統回撥(如連結和並行執行)的優勢。
  function myAsyncFunc() {
    return new Promise(
    function (resolve, reject) {
          :
    resolve(result);
           :
           if (error) {
                reject(error);
           }
    });
  }
複製程式碼

比較以前的非同步函式:

myAsyncFunc().then(result =\> { ··· }).catch(error =\> { ··· });
複製程式碼

這只是新功能的一個示例,但還有許多其他功能。關於ES6的一個很好的線上參考是 Axel Rauschmayer 的 Exploring ES6

接下來,我們來看看如何設定開發平臺和環境。

設定環境和工程

目前來看,開發 iOS 原生應用只能用 macOS 系統。由於大多數移動應用都會涉及到 Android 和 iOS兩端,因此應該考慮使用 Mac 來開發 React Native 移動應用。

在建立專案之前,你需要安裝一堆東西,主要包括:

  • Android Studio和Xcode工具:安裝最新版本以構建和釋出應用。對於 Android 開發,確保為要執行的Android API 版本配置正確的模擬器。

  • JavaScript IDE或文字編輯器:不需要使用 Xcode 或 Android Studio 來編寫 JavaScript 程式碼,真正需要的只是一個文字編輯器。比如使用 Atom(由 GitHub 開發),Sublime Text 或喜歡的任何優秀文字編輯器,筆者目前用的是 VSC。

  • JavaScript 或 React Native 包:安裝一些工具來管理 React Native 軟體包以及三方庫,包括:

    • 從 nodejs.org 下載 node.js:為了讓JavaScript 執行時允許訪問npm,這是一個由 node.js 專案建立的便捷工具,可用於管理開源軟體包。確保下載 node.js 的最新 LTS(長期支援)版本。此下載中還包括一個名 為 Metro bundler 的開發伺服器,它在除錯時能夠提供實時更新渲染。
    • 下載 create-react-native-app:可以使用此工具開始開發。它可以建立一個基礎專案。使用 npm 來下載:

    sudo npm install -g create-react-native-app

    • 下載常用的第三方庫。 React Nativ 提供了很多基礎元件,與原生 API 使用相比會更加簡單。常規的移動應用使用可能包括有相機,管理狀態,並具有導航,地圖,圖示和核取方塊。這都都可以從 React Native 社群獲取對用元件:

      • react-native-navigation
      • react-native-vector-icons
      • redux
      • React-native-maps

      此外,可以到React Native Elements獲取需要的其他UI元件元素。在開發一個完整功能的 app 時,需要用到上面提到的 UI 元件。用這個方法來安裝以前的 packages :

      npm install _package-name_ --save

    • React Native Debugger:這是一個非常棒的偵錯程式,同時它還是一個獨立的應用,裡面能用到的工具包括 React Inspector 和 Redux DevTools。配置好環境後可以連線到應用(注意:一次只能調適一個應用),可以很方便的實時檢視 React Native 應用的狀態,調適必備工具,如圖所示:

詳解 React Native

現在,來建立一個簡單應用並執行一下:

1.建立工程。用以下語句來建立,指定建立的工程路徑,預設建立在當前路徑下:

create-react-native-app [APPNAME]

cd [APPNAME]

複製程式碼

注意:這一步建立了一個沒有進行任何配置的 React Native app 模板。在 GitHub 專案中閱讀有關 create-react-native-app 的更多資訊。

安裝完成後,可以使用 npm 執行以下命令來執行應用:

  • npm start 執行 app
  • npm run ios:跟 npm start 相似,這是用來執行 iOS 應用
  • npm run android:執行 Android 應用,如果需要執行模擬器,需要提前開啟模擬器
  • npm run eject:從當前的 create-react-native 模式中彈出 app,這樣就可以完全控制應用構建過程(筆者沒用過)

2.啟動 app。通過執行 npm run android 和 npm run ios 在模擬器上執行應用。這將在開發模式下啟動應用。同時還會啟動 Metro bundler,可以在修改程式碼後實時更新應用(在除錯移動應用時這很棒)。

如果執行成功,會有這樣的提示:

詳解 React Native

React Native API 概述

我們簡單快速瀏覽一下 React Native API ,從 React 轉變過來的 React Native ,它繼承了 JSX,state,props 和元件生命週期的概念。然後,它通過提供對原生UI元件和功能的支援來擴充套件 React。這是通過匯入 React 和 React Native 功能來實現的,如清單1所示:

清單1.從 React 和 React Native 匯入

import React, { Component } from **'react'**

import { View, ScrollView, StyleSheet } from **'react-native**'


複製程式碼

匯入後,我們就可以訪問 React Native 對應的元件,這些元件支援我們所需的許多常用UI元件:

  • 基礎UI元件:如 View,Text,Image,TextInput,ScrollView 和 StyleSheet。此外,還提供了UI控制元件,如Button,Picker,Slider 和 Switch。
  • 列表:FlatList(用於呈現可滾動列表)和 SectionList(類似於 FlatList 但用於分段列表)的列表
  • Android 專用元件:如 Android BackHandler,DatePickerAndroid,ToastAndroid。
  • iOS 專用元件:如 AlertIOS,ImagePickerIOS,NavitatorIOS,TabBarIOS。
  • 通用元件:可以在 Components and APIs 上查詢所有的公共元件。

還有一點需要注意,React Native 還提供了很多原來不支援的功能。比如使用地圖,甚至是圖示和核取方塊。雖然底層作業系統支援,但 React Native 還是需要安裝第三方庫。也就是說,React Native(包括開發工具)所支援的任何複雜或不受支援的內容都要求我們編寫其他程式碼或使用第三方庫來承載。

使用 React Native 構建應用

常規的的 React Native 應用由許多元件組成,如圖所示:

詳解 React Native

在一個應用容器中,每個介面會包含一個或多個 View(和樣式),介面跳轉導航,狀態管理和資料模型。還有本地儲存和網路請求功能。最後但並非最不重要的是,有很多第三方庫可以擴充套件應用的功能。

以下介紹部分圖中內容:

Screens: Views and styles

應用容器中包含多個 screen 和其他元件的集合。應用本身由許多 screen 組成,每個 screen 包含多個檢視,如按鈕,文字輸入和列表。View 是構建使用者介面的最基本元件,對映到 iOS 和 Android 的原生 View 元件。

清單2是一個介面示例,包含一個列表,使用 ScrollView 實現,以及一個封裝在 Checkbox UI 元件中的任務列表:

# components/TaskList/TaskList.js

import React, { Component } from 'react'
import { View, ScrollView, StyleSheet } from 'react-native'
import { CheckBox } from 'react-native-elements'

import styles from './TaskListStyle;

export default class TaskList extends Component {

    renderTaskItem = (item, idx) => {

        return (
            <View style={styles.row}>
                <Checkbox
                    checked = {item.checked}
                    title = {item.text}
                    onPress = { () => onPressItem(item, idx) }
                />

            </View>
        );
    }

  render() {
    const {list} = this.props
    return (
        <ScrollView style={styles.container}>
            {list.map( this.renderTaskItem )}
        </ScrollView>
    )
  }
}
複製程式碼

程式碼中列表元件需要匯入各種依賴項,包括 React Native 中未找到的第三方Checkbox UI元件,可以從 React Native Elements下載該元件。

在清單2中,UI元件和樣式是分開的。我們可以建立更高階UI元件並與樣式隔離開。我們把所有元件放到一個子目錄中,每個元件都有其 component.js 和 style.js 檔案,如清單3所示:

./Components
+-TaskList
  +-TaskList.js
  +-TaskListStyle.js
複製程式碼

或者,使用更通用的方法:

+-TaskList
  +-index.js
  +-style.js
複製程式碼

清單4顯示了一個樣式檔案的示例:

# components/TaskList/TaskListStyle.js

import { StyleSheet } from 'react-native';

// Define the styles
export default StyleSheet.create({
    container: {
        flex: 1,
        paddingLeft: 20,
      },

      row: {
        flex: 1,
        flexDirection: 'row',
        alignItems: 'flex-start',        
      },
});
複製程式碼

樣式需要使用 StyleSheet 元件建立,它類似於 CSS 樣式表。可以檢視 individual UI component 獲取更多樣式屬性。

自適應佈局

React Native 提供了一個 Dimensions 元件來檢測螢幕尺寸:

import { Dimensions } from 'react-native';
var {height, width} = Dimensions.get('window');
複製程式碼

Dimensions 可根據需要實時調整介面UI;例如,我們可以根據需要的應用樣式,在方向更改時調整UI,包括改變橫向與縱向方向的文字輸入欄位的位置和長度。

另外我們還可能需要處理另一種情況的UI適配。比如需要實現 Androi d或 iOS 特有的行為的情況。包括與兩端系統相關的差異,例如UI控制元件的呈現及使用。要解決此設計問題,我們需要使用不同的邏輯或樣式,或者在更復雜的情況下,實現自定義元件。因此 React Native 提供了不同的方法來檢測系統,以便可以以程式設計方式決定要執行的操作:使用 Platform 元件甚至使用特定於平臺的副檔名。

使用Platform 元件檢測系統會返回“ios”或“android”。例如:

import {Platform} from 'react-native';


if (Platform.OS === 'ios') {
     
}

if (Platform.OS === 'android') {
      
}
複製程式碼

Platform 還提供 Platform.select 和 Platform.Version,分別用於檢測平臺和平臺版本。有關更多資訊,請參閱平臺文件。應該僅對較小的更改使用 Platform.O S和 Platform.select。否則,我們將使用大量硬編碼的 if 或 switch 語句,這會使寫的程式碼更難以遵循。

如果要編寫更復雜的程式碼或元件,則應使用特定於平臺的副檔名。在這種方法中,使用每個平臺檔案拆分程式碼。也就是說,如果我們的程式碼不能跨平臺公用,比如我們的任務列表,那麼將編寫兩個檔案,一個用於 iOS,一個用於Android,然後讓 React Native 選擇對應的檔案:

./Components
+-TaskList
  +-TaskList.io.js
  +-TaskList.android.js
  +-TaskListStyle.ios.js
  +-TaskListStyle.android.js
複製程式碼

然後,讓React Native選擇正確的檔案:

const TaskList = require('./Components/TaskList/TaskList);
複製程式碼

本文未介紹 iOS 和 Android 之間究竟有什麼不同,但要注意的事項包括渲染和常規元件或UI控制元件使用佈局。現在,如果事實證明 Android 與 iOS 應用的佈局設計在主要方面有所不同,我們可以分別使用 index.ios.js 和 index.android.js 檔案而不是 index.js 來控制主應用。這就可以完全控制不同平臺的應用佈局和流程。為了最大限度地減少重複程式碼,需要將其重新打包為可重用的程式。

導航

React Native 中的導航是一個具有挑戰性的領域,因為 React Native 提供的導航要麼不夠強大,要麼僅針對 iOS。相反,有大量社群專案試圖解決跨平臺導航問題,其中一些專案比其他專案更受歡迎。這裡只是重點闡述當下流行的 React Native Navigation。

React Native Navigation 提供了一套非常完整的 API 來註冊介面元件,包括含有 tab 的應用,以及啟動單個介面應用。同時它還提供了其他 API 來管理模態跳轉和導航跳轉,以及用於處理堆疊,處理按鈕,可見性以及自定義導航器本身的低階 API。

安裝最新穩定版本的 react-native-navigation(執行 npm install react-native-navigation --save)並按照其網站上的 Android 和 iOS 安裝說明進行操作

另外必須通過呼叫 registerComponent()將唯一名稱的 Navigator 註冊到所有 screen 元件。註冊後,就能執行包含 Tab 的應用或單個 screen 應用,如清單8所示:

import { Navigation } from "react-native-navigation";
import MainScreen from "./src/screens/MainScreen/MainScreen";
:

// register the MainScreen component
Navigation.registerComponent(
  'MainScreen',
  () => 'MainScreen'
);

:
:

// Start the (single screen)App
export default () => Navigation.startSingleScreenApp({
  screen: {
    screen: "MainScreen",
    title: "Main Screen"
  }
});
複製程式碼

如果是基於 tab 的使用,則需要建立更多的 screen ,因為必須指明不同的 tab 及其關聯的 screen ,tab 詳細資訊和樣式,例如:

// Start the (tab-based) App
export default () => Navigation.startTabBasedApp({
    tabs: [
        {
            screen: "screen unique name",
            label: "label",
            title: "title",
            icon: icons[0],
            navigatorButtons: {
                leftButtons: [
                    {
                        icon: icons[2],
                        title: "title",
                        id: "unique id"
                    }
                ]
            }
        },
        {
            screen: "screen unique name",
            label: "label",
            title: "title",
            icon: icons[1],
            navigatorButtons: {
                leftButtons: [
                    {
                        icon: icons[2],
                        title: "title",
                        id: "unique id"
                    }
                ]
            }
        }
    ],
    tabsStyle: {
        tabBarSelectedButtonColor: "black"
    },
    drawer: {
        left: {
            screen: "drawer screen unique name"
        }
    },
    appStyle: {
        tabBarSelectedButtonColor: "red"
    },
});
複製程式碼

可以根據需要通過呼叫相應的功能在單個和基於 tab 的 screen 之間切換。另外,請注意前一個示例中抽屜的使用。可以為單個 screen 應用和基於 tab 的應用定義抽屜側面選單。

state 管理

在 React(和 React Native)中,props 和 state 用於控制元件。 props(properties)是用於在建立元件時自定義元件的引數。例如,Button 元件為許多屬性提供支援;在清單10中,title 和 onPress 是 props。

<Button
  onPress={onPressHandler}
  title="Learn More"
  :
/>
複製程式碼

屬性由 React 自身設定(由父元件設定),並在元件的整個生命週期內保持固定。 State 是應用內的特殊資料,可根據需要進行更改以驅動元件行為,比如重新整理資料。state 應該在建構函式中初始化,並且只能通過呼叫 setState 來更改。清單11顯示了 state 的用法:

class MyComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {isActive: true};
  }

  toggleActiveState() {
      this.setState(previousState => {
          return { isActive: !previousState.isActive };
      });
  }

  render() {
      :
  }

}
複製程式碼

清單11是一個簡單 state 管理的示例,但如果應用對於許多 screen 元件和元件間狀態管理更復雜,則應該利用 Redux。 Redux 是一種更先進的 state 管理方法,它提供了一個實現 state 管理及相關操作和處理程式的框架。但是使用 Redux 會增加應用的複雜性,因此應該考慮僅在真正需要它時使用它(即,對於較大的複雜應用)。

持久化儲存

React Native 使用 AsyncStorage 為持久化儲存提供支援,AsyncStorage 是一個易於使用的基於鍵值的非同步(持久)儲存系統,對整個應用而言是全域性的。它很簡單,因為它不需要過多的設定。在 iOS 上,它表現為一系列字典或者是檔案。在Android 上,它基於 SQLite 或 RocksDB:

import {AsyncStorage} from 'react-native'
:

const USERNAMES_KEY = 'Usernames'

const user = {
    name: 'John',
    last: 'Smith',
    username: 'jsmith'
}

// Storing the item
AsyncStorage.setItem(USERNAMES_KEY, JSON.stringify(user))

:

// Get item promise to retrieve the item
AsyncStorage.getItem(USERNAMES_KEY).then((item) => {
    const userItem = JSON.parse(item)
})

:
複製程式碼

但是 AsyncStorage 有許多限制,例如效能低下,沒有索引,也沒有加密。讓我再說一遍:它沒有加密。如果要儲存大量資料,效能和加密是應用的重要因素,則應考慮使用 Realm React Native 等替代方案。 Realm React Native 是跨平臺的,效能更好,支援加密,甚至還帶有版本的 ListView(具有與 React Native ListView 相同的 API 簽名),它在 Realm 之上進行了優化,大大改善了對本地儲存的訪問。 Realm 使用 static schema 的途徑來提高效能。(可以看一下筆者的這篇文章來學習 Realm 的使用,傳送門->移動資料庫 Realm 在 React-Native 的使用詳解 ):

class Usernames {
    static schema = {
        name: 'User',
        properties: {
            name: 'string',
            last: 'string',
            username: 'string',
        }
    }
}

let realm = new Realm({schema: [Usernames]});

realm.write(() => {
    realm.create('Usernames', { name: 'John', last: "Smit", username : "jsmith" });
});
複製程式碼

網路

移動應用通常是需要網路連線的應用。移動應用連線到網路以進行身份​​驗證。 React Native 提供不同的網路 API:

  • 最初的 Ajax(非同步JavaScript + XML)XMLHttpRequest API
  • WebSockets,通過TCP 連線提供全雙工通訊通道,也叫做雙向平等對話
  • Fetch API,該系列的最新成員,類似於 XMLHttpRequest,但具有其他功能。

清單14使用 fetch API 從伺服器獲取使用者資料的 json 檔案:

{
  "users": [
    { "name": "John Smith", "username": "jsmith"},
    { "name": "Matt People", "username": "mpeople"},
    { "name": "Graciela Lopez", "username": "glopez"},
    { "name": "Jeff Bezos", "username": "jbezos"},
  ]
}

--

import React from 'react';
import { View, Text, FlatList, ActivityIndicator} from 'react-native';

export default class FetchUsernames extends React.Component {

  constructor(props){
    super(props);
  }

  // dispatch the fetch once the component mounts
  componentDidMount(){
    return fetch('https://...')
      .then((response) => response.json())
      .then((responseJson) => {
this.setState({
          dataSource: responseJson.users,
        }, function(){
        });
      })
      .catch((error) => {
        console.error(error);
      });
  }

  // render the component, populating the FlatList from the 
  // state.dataSource that was in turn populated from the JSON
  render(){
    return(
      <View>
        <FlatList
          data={this.state.dataSource}
          renderItem={({item}) => 
               <Text>{item.name}, {item.username}</Text>}
          keyExtractor={(item, index) => index}
        />
      </View>
    );
  }
}

複製程式碼

關於 fetch API 和網路的一些注意事項:

  • 請注意清單14中 Promise 的使用。即使響應是 HTTP 404 或 500,fetch 也不會失敗(拒絕)。換句話說,它只會拒絕網路故障。檢測非網路故障的方法是檢查 status 是否正常返回。
  • 預設情況下,iOS 會阻止未使用 SSL 加密的請求。解決此問題的唯一方法是新增 App Transport Security
  • 可以使用 NetInfo 來檢測或檢索網路資訊。它可以檢測有網或無網路狀態,連線型別(Wi-Fi,蜂窩)和有效連線型別(2G,3G和4G),不過對於兩個端來說這個還有一些區別。

結論

本文僅是對 React Native 提供了一個高層次的觀點看法。 React Native 是一個很複雜的話題。如果要深究,則需要更詳細地研究本文所涵蓋的每個領域。

React Native 是一個充滿活力且不斷髮展的框架和社群。但 React Native 並不像原生應用開發那樣成熟。所以必須為Android,iOS 和 React Native 的開發保持穩定的環境,包括所有相關的開發工具。對 於React Native 中不存在的元件,可以 Google 搜尋,就可以找到所需內容,請記住所選的第三方庫可能會更改或維護可能會停止或被放棄,這是一個要考慮的主要風險。

React Native 並不適合所有人或每個專案。這需要考慮建立和維護 React Native 程式碼,工具,開發環境和技能集的時間,精力和成本,而不是僅僅關注原生程式碼。去年也有大型開發組織的例子,例如 Airbnb,它們在 React Native 上投入了大量的精力和投入,但最終決定迴歸純粹的原生開發。

作者介紹

  • 韋譽翔:廣州蘆葦科技 APP 團隊 iOS 開發工程師

內推資訊

  • 我們正在招募小夥伴,有興趣的小夥伴可以把簡歷發到 app@talkmoney.cn,備註:來自掘金社群
  • 詳情可以戳這裡--> 廣州蘆葦資訊科技

相關文章