React Native,是一个混合移动应用开发框架,本文根据 By 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 采用不同的方法进行混合移动应用开发。它不会生成原生 UI 组件,而是基于 React,React Native 是一个用于构建基于 Web 的交互界面的 JavaScript 库,因此会有更丰富的 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 应用的状态,调适必备工具,如图所示:
现在,来建立一个简单应用并运行一下:
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 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 应用由许多组件组成,如图所示:
在一个应用容器中,每个界面会包含一个或多个 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 上投入了大量的精力和投入,但最终决定回归纯粹的原生开发。