React Native
React Native
是一个可以使用JavaScript
和React
来开发原生应用的框架,由facebook
开发,于2015年开源。
使用React Native
可以让我们完全使用JavaScript
来开发移动APP,其使用React
,所以可以通过声明式的组件来构建复杂的界面。
使用React Native
开发的APP,不是web app
,不是hybird app
,而是真正的原生应用,其展示的界面与使用OC或者JAVA编写的界面完全一样。React Native
通过JavaScrpit
来调用基本的系统方法来构建界面以构建APP。
React Native
可以热加载(Hot Reload) ,提高开发体验。热加载指RN服务器监听文件,如果文件修改,则会重新编译JS代码,而APP端只要重新刷新页面就可以看到修改。
React Native
可以流畅的接入原生开发的组件和模块,以扩展更多的功能。
React Native
的口号是 :
Learn once,write anywhere
React Native
没有说一份代码运行在多个平台,而是说,可以用在不同平台。所以我们还是要正式不同平台之间的差距,要对三端兼容的方案和思想保持谨慎的态度。
开发环境
本文档面向对象是了解React
的iOS开发人员😱
homebrew
mac上的包管理器 :
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
在Max OS X 10.11(El Capitan)版本中,homebrew在安装软件时可能会碰到/usr/local目录不可写的权限问题。可以使用下面的命令修复:
sudo chown -R `whoami` /usr/local
Node, Watchman
使用homebrew
安装:
brew install node
brew install watchman
watchman
是一个facebook
用来监听文件修改的工具,安装它以提高性能。
npm要设置使用淘宝的npm
镜像:
npm config set registry https://registry.npm.taobao.org --global
npm config set disturl https://npm.taobao.org/dist --global
React Native CLI
rn CLI 用于初始化项目以及打包。使用npm安装:
npm install -g react-native-cli
VSCode
开发react native
,一般我们使用VSCode
这个编辑器,下载后,安装插件React Native Tools
。
hello world
通过react native cli
创建一个项目 :
react-native init AwesomeProject
然后运行项目:
cd AwesomeProject
react-native run-ios
这时,我们来观察项目的结构:
__tests__
目录下是测试相关文件。
android
和ios
都是原生代码。
app.json
内容如下:
{
"name": "AwesomeProject",
"displayName": "AwesomeProject"
}
用来控制生成的原生代码的项目名和展示名。 修改后,使用react-native eject
命令来重新创建原生项目。
index.android.js
和index.ios.js
: 是页面文件,这里有两份文件,一个用于iOS,一个用于android
。
node_modules
是npm安装的依赖.
package.json
是npm 的依赖控制文件,在开发RN时,当我们想要引入一些好用的第三方组件时,会在这里添加依赖。
index.ios.js
我们查看index.ios.js
文件,以了解一个简单RN页面的样例代码:
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View
} from 'react-native';
export default class Awesome extends Component {
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>
Welcome to React Native!
</Text>
<Text style={styles.instructions}>
To get started, edit index.ios.js
</Text>
<Text style={styles.instructions}>
Press Cmd+R to reload,{'\n'}
Cmd+D or shake for dev menu
</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
});
AppRegistry.registerComponent('Awesome', () => Awesome);
这里我们发现,React Native
项目真的与React
项目的结构一样,写法一样。(本篇文档面向对象为 了解React
的开发者,所以请先了解React)上述代码中,我们看到React native
与React
中不同的地方:
AppRegistry
, 需要通过AppRegistry.registerComponent
来注册组件,注册的组件会成为根节点,而原生代码中选择访问一个RN项目中的某个根节点。 注册后,组件成为了Controller
。Text
和View
:react native
中使用的所有组件都是react native
提供的组件,没有web组件。StyleSheet
:React native
中的样式与web中的样式基本相同,但是会有一些区别:React native
中的样式名是驼峰式的, 如border-left-width
,在RN
中为borderLeftWidth
- 值类型不同,
React Native
中的很多值都是字符串类型,如color: '#333333'
. - 支持的样式有限,且有些会与浏览器中表现不同。
所以,在使用
React Native
时,样式不要以为与css中一样,还是要看官方文档,以了解React Native
样式,官方文档中,将样式分为三类: ViewStylePropTypes,TextStylePropTypes,ImageStylePropTypes
总结
这一节中,我们查看了一下react native
创建的项目,大致了解react native
的逻辑, 使用react native
开发原生项目,与使用react
开发web项目的区别,就在于使用的组件不同,和样式上稍微有些不同。
接下来介绍React Native
中的特殊之处。
平台区分
react-native
倾向于区别对待iOS和Android
,我们可以通过两种方式来区分平台。
使用 Platform
这个react native
内置module 用来识别当前APP运行的平台。我们可以如下进行判断 :
import { Platform, StyleSheet } from 'react-native';
const styles = StyleSheet.create({
height: (Platform.OS === 'ios') ? 200 : 100,
});
也可以使用Pllatform
的select
API :
import { Platform, StyleSheet } from 'react-native';
const styles = StyleSheet.create({
container: {
flex: 1,
...Platform.select({
ios: {
backgroundColor: 'red',
},
android: {
backgroundColor: 'blue',
},
}),
},
});
可以使用select
来为组件选择不同实现 :
const Component = Platform.select({
ios: () => require('ComponentIOS'),
android: () => require('ComponentAndroid'),
})();
使用Version
来判断Android
版本 :
if (Platform.Version === 25) {
console.log('Running on Nougat!');
}
通过文件后缀名
可以通过文件后缀名实现不同平台不同实现 ,如我们在项目中有两个文件 :
BigButton.ios.js
BigButton.android.js
当我们引入该组件时 :
const BigButton = require('./BigButton');
React Native
会主动的根据平台选择正确的文件。
图片处理
React Native
处理图片时,要考虑网络图片和本地图片的处理,本地图片要处理路径问题。
Image
这个组件,必须要设置图片大小,因为react native
认为,如果下载图片完成后,再根据图片大小去布局,会很影响用户体验。
打包图片
跟随js代码一起打包的图片,引入一张图片 :
<Image source={require('./icon.png')} />
require
函数会到该js所在路径寻找文件,如果文件有两个icon.ios.png
和icon.android.png
,函数能够正确地为不同的平台选择不同的文件。
在iOS中,@2x
和 @3x
图片也能够根据屏幕分辨率进行区分。
require
函数寻找本地图片,是静态的,其实现是在打包时找到图片真正的路径,并将一些图片信息一起记录下来。 所以require
不能动态加载图片 :
var icon = this.props.active ? 'my-icon-active' : 'my-icon-inactive';
<Image source={require('./' + icon + '.png')} />
var icon = this.props.active ? require('./my-icon-active.png') : require('./my-icon-inactive.png');
<Image source={icon} />
APP中图片
在hybird app
中,我们可以在react native
代码中使用APP中的图片(iOS中是放在asset资源中的图片,Android中是 drawable
文件夹下的图片) :
//<Image source= style= />
网络图片
// GOOD
// <Image source=
// style= />
样式中的颜色
颜色值是一个字符串,但是也支持使用rgb
和rgba
两个函数创建,有以下常见写法:
'#f0f' (#rgb)
'#ff00ff' (#rrggbb)
'rgb(255, 0, 255)'
'rgba(255, 255, 255, 1.0)'
'#f0ff'
(#rgba)'#ff00ff00'
(#rrggbbaa)'transparent'
, 同css中命名颜色。
Timer
计时函数有 :
- setTimeout, clearTimeout : 设置一段时间后执行任务
- setInterval, clearInterval : 设置每隔一段时间执行一次
- setImmediate, clearImmediate : 在一次
JavaScript
执行完成后,发送调用给Native
之前,执行回调。 - requestAnimationFrame, cancelAnimationFrame :每帧刷新时执行一次
####InteractionManager
使用InteractionManager.runAfterInteractions
,以在动画和交互完成后,执行耗时操作。
TimerMixin
React Native
中很多致命错误都是由于定时器 , 经常会出现组件已经被卸载,但计时器仍然在运行。所以React Native
提供TimerMixin
。安装 :
npm i react-timer-mixin --save
由于使用es6语法,所以在使用Mixin
时,我们要引入react-mixin
这个库 ,然后 :
import reactMixin from 'react-mixin';
import TimerMixin from 'react-timer-mixin';
@reactMixin.decorate(TimerMixin)
class Root extends Component {
componentDidMount: function() {
this.setTimeout(
() => { console.log('I do not leak!'); },
500
);
}
}
而要支持注解语法,我们要安装decorators
插件 :
npm install babel-plugin-transform-decorators-legacy --save
在.babelrc
中声明:
{
"presets": ["react-native"],
"plugins": ["transform-decorators-legacy"]
}
VSCode
会对注解提示警告 , 我们需要项目根目录下创建 tsconfig.json
文件:
{
"compilerOptions": {
"experimentalDecorators": true,
"allowJs": true
}
}
然后重新加载页面就不会有警告了。
Debug
当React Native
设置为调试模式时(在RCTDefines.h
文件中定义),我们可以呼出开发菜单 ,真机上进行摇晃,而模拟器上使用快捷键⌘D
:
automatic reloading
自动刷新设置后,每次修改js代码时,app都会自动刷新。
调试状态下错误与警告
错误是红色全屏弹框, 可以通过console.error()
创建自己的错误
警告是黄色弹框,会显示在界面底部,可以进行隐藏。 通过console.warn()
来创建自己的警告。
调试模式
点击第二个选项Debug JS Remotely
, 会在服务器端通过http://localhost:8081/debugger-ui
打开一个chrome
页面,使用快捷键 ⌘⌥J
打开调试页面:
在React Native
正常运行时,使用的引擎都是JavaScriptCore
,而在调试模式下,JS运行环境是调试器Chrome
的V8引擎,而通过WebSocket
与APP进行交互。
react developer tools
安装开发工具 :
npm install -g react-devtools
然后运行开发工具
react-devtools
会打开一个页面 ,在这个页面中,我们可以查看UI的布局以及各种状态值。
Show Inspector
使用Inspector
来查看页面层级与布局信息。
Perf Monitor
使用perf Monitor
来查看页面性能。
将react-native 嵌入原生iOS应用中
react-native
通过npm将库导入到原生应用中。首先,在我们的项目文件的上一层目录(即xcodeproj
文件的上一层目录中),添加一个文件package.json
,并添加描述 :
{
"name": "MyReactNativeApp",
"version": "0.0.1",
"private": true,
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start"
}
}
然后在目录下执行命令,以安装React Native
:
npm install --save react react-native
要在APP中支持React Native
,必须引入相关依赖到原生代码中,而iOS开发中,引入依赖一般使用CocoaPods
,所以我们在CocoaPods
下添加依赖 :
target 'RNTest' do
pod 'React', :path => '../node_modules/react-native', :subspecs => [
'Core',
'DevSupport', # Include this to enable In-App Devmenu if RN >= 0.43
'RCTText',
'RCTNetwork',
'RCTWebSocket', # needed for debugging
# Add any other subspecs you want to use in your project
]
# Explicitly include Yoga if you are using RN >= 0.42.0
pod "Yoga", :path => "../node_modules/react-native/ReactCommon/yoga"
end
然后执行pod install
来安装依赖。
个人看法
这里,我们可以看到,Podfile
中设置依赖,从npm放置依赖的文件夹node_modules
中获取React
原生依赖。 这样做,很明显会让纯iOS开发者很不爽, 为什么我在开发iOS代码时,要将你npm
的依赖内容放到原生代码的项目中,为什么要去使用npm
。一个莫名其妙的package.json
文件放到项目中,会使仓库变得很乱,强加的npm
会影响其他iOS开发者。
而react-native
可能是这样考虑的 ,react-native
的代码使用javascript
编写,第三方组件是通过npm来管理,且有一些第三方组件可能会有js代码和原生代码两个部分。react-native
从自己开发的角度来考虑,肯定是希望完全通过npm来管理所有依赖,避免出现需要在npm和cocoapods
同时管理依赖的情况 。
但是它的这种做法,对于大型APP肯定是无法接受的。原生APP中有大量的原生代码和很多纯粹的iOS开发者,React Native
只是应该像H5一样嵌入APP
这个容器中,而不是去影响APP的开发。所以我们会将React Native
构建一个私有版本,进行一些自己的需求定制,再构建一个纯CocoaPods
的依赖(react native
就版本也是单独的一个pod依赖,放在主干仓库中,后来为了进行优化而废弃)。而APP安装了依赖后,就拥有了加载React Native
项目的能力。
原生接入
安装好依赖之后,我们就要找一个入口,来测试react native
了。我们可以直接在Appdelegate
中添加以下代码 :
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSString *url = @"http://localhost:8081/index.ios.bundle?platform=ios";
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:[NSURL URLWithString:url]
moduleName:@"Awesome"
initialProperties:nil
launchOptions:launchOptions];
rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [UIViewController new];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
return YES;
}
这里在初始化APP时,以RCTRootView
创建一个界面,并加载url
指向的react native
页面。
服务器端有一个index.ios.js
文件,执行命令npm start
启动服务器。 然后在浏览器中可以查看服务状态,使用http://localhost:8081/index.ios.js
访问的是js原文件,使用http://localhost:8081/index.ios.bundle
访问的是打包好的文件,而platform
是必须的参数。
iOS中RCTRootView
是RN代码执行的容器,如同html
运行在UIWebView
中一样。初始RCTRootView
时传入三个重要参数 :
BundleURL
是.bundle
文件的URL,也就是入口文件打包后的路径。moduleName
是模块名,是一个bundle
文件中的模块名。与代码中AppRegistry.registerComponent
函数注册的名称相对应。Properties
是参数,会作为props
传递给模块名对应的组件。
这样我们就成功将react native
嵌入到我们的APP中了,运行以测试效果。
设置出错处理
必须设置。
RCTSetFatalHandler(^(NSError *error) {
NSLog(@"React Native Error : %@",error);
});