有一段时间没有写东西了,因为最近项目开始尝试使用React Native(以下简称RN)来开发,所以这段时间一直在研究,目前为止开发的内容不多,所以使用过的东西也不算多,这里也只是做个简单的记录

这里我打算从以下几个方面来讲:

1.背景介绍

2.环境的配置

3.RN所需要知道的知识

4.RN与原生的交互

5.本地调试与本地打包调试

6.远程热更新

7.iOS和Android不同样式处理

8.踩坑记录

9.相关资料

0x00 背景介绍

RN是Facebook在React.js 2015大会上公布开源的,它是基于开源框架React.js来实现的,它支持了iOS和Android两大平台,解决开发者们编写重复代码的痛点,实现了所谓的跨平台开发,Learn Once , Write Anywhere,这是目前很多开发者所追求的,特别是一些独立开发者或者项目快速迭代的团队,可以尝试使用RN来开发,另外包括方便的npm管理,快速的调试等等

那么既然优点这么明显,为什么大部分的团队还是采用传统的iOS、Android开发呢,踩过坑的同学都知道,首先在支持上还做得不够完善,在使用组件时,RN原有提供的组件往往不能很好的支持,与原生组件多少存在着差异,而且在使用第三方组件时,又会因为长期不更新的原因,存在很多坑,对于新手来说,根本不知道坑在哪,完全无从下手。另外RN的性能也不能和原生的相提并论,特别是列表组件在渲染大量数据时,流畅性方面还是原生更加优越,而且并非所以代码iOS和Android都能公用,如果某个组件只支持某一个平台,那你必须分开编写代码,实际上还是存在重复代码,除此之外学习的成本以及团队RN推广等等原因都需要考量,但是我相信,跨平台开发始终是一个趋势,RN整个社区也在不断的发展,相信未来我们会实现真正意义上的跨平台开发~

0x01 环境配置

相对于Android的环境配置过程来说,iOS可以说是简单轻松…出现的问题要少很多

首先我们需要安装Homebrew

1
$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

然后安装nodewatchman(用于监测文件系统的变更)

1
2
brew install node
brew install watchman

RN的命令行工具react-native-cli

1
npm install -g react-native-cli

如果遇到权限问题,只要前面加个sudo即可

1
sudo npm install -g react-native-cli

yeah~that’s all~我在配置的过程中,基本没有报错,如果有出现配置问题的话,请自行Google一下,看看大家的解决方法

如果在原有iOS项目中集成的话,我们需要在package.json文件配置一下

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"name": "your project name",
"version": "0.0.1",
"private": true,
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start"
},
"dependencies": {
"react": "15.3.1",
"react-native": "0.33.0",
"react-native-swipeout": "^2.0.12"
}
}

并且Podfile里,导入需要的RN模块

1
2
3
4
5
6
7
8
pod 'React', :path => '../node_modules/react-native', :subspecs => [
'Core',
'RCTText',
'RCTImage',
'RCTNetwork',
'RCTWebSocket', # needed for debugging
# Add any other subspecs you want to use in your project
]

0x02 RN所需要知道的知识

RN的运行机制

在开始写代码之前,我们需要了解RN的运行机制是怎么样的,这样写起来思路会更加清晰

首先,程序需要有个入口,我们可以创建很多的组件,但是有且只有一个组件用来做为程序的入口,RN的入口则类似于iOS的main.m,在iOS里我们会在main函数里设置应用程序类的代理类

1
return UIApplicationMain(argc, argv, nil, NSStringFromClass([KDAppDelegate class]));

同样,RN里我们需要注册入口的名称,并且这个名称要和原生的初始化RN界面时的入口名称保持一致

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 引用navigation使用的组件
import React, { Component } from 'react';
import {
AppRegistry,
...
} from 'react-native';

// 创建navigation类
class navigation extends Component {
// set compnent
}

// 注册navigation为程序的入口
AppRegistry.registerComponent('navigation', () => navigation);

在iOS原生这边需要用到RN的地方,我们需要初始化它

1
2
3
4
5
6
7
8
NSURL *jsCodeLocation =
[[NSBundle mainBundle] URLForResource:@"bundle/index.ios" withExtension:@"jsbundle"];
// [NSURL URLWithString:@"http://172.17.9.188:8081/index.ios.bundle?platform=ios"];
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"navigation"
initialProperties:nil
launchOptions:nil];
self.view = rootView;

Tip:

1
jsCodeLocation 是RN资源加载的路径,我们有两种方式去加载,一种是加载本地的js文件及其他资源文件,一种是我们将其打包成bundle文件,前者的优势在于方便调试,后者是用来打包发布上线用

moduleName是对应于RN的入口名字,且这个是唯一的,那我们如果原生有多个入口需要初始化不同的RN界面,那该怎么办呢?这就用到了initialProperties,它是字典类型,我们可以将入口作为路由,在initialProperties里传入我们需要初始化的界面名称,入口获取到名称之后,渲染对应的界面即可

RN组件的生命周期

在RN里面,所谓的界面应该称作类或者组件更为合适

并且组件也有它的生命周期,和iOS里的viewWillAppearviewDidDisappear等等很像,下面生命周期内容取自于http://www.race604.com/react-native-component-lifecycle/

我们可以把组件生命周期大致分为三个阶段:

  • 第一阶段:是组件第一次绘制阶段,如图中的上面虚线框内,在这里完成了组件的加载和初始化;
  • 第二阶段:是组件在运行和交互阶段,如图中左下角虚线框,这个阶段组件可以处理用户交互,或者接收事件更新界面;
  • 第三阶段:是组件卸载消亡的阶段,如图中右下角的虚线框中,这里做一些组件的清理工作。

下面来详细介绍生命周期中的各回调函数。

getDefaultProps

在组件创建之前,会先调用 getDefaultProps(),这是全局调用一次,严格地来说,这不是组件的生命周期的一部分。在组件被创建并加载候,首先调用 getInitialState(),来初始化组件的状态。

componentWillMount

然后,准备加载组件,会调用 componentWillMount(),其原型如下:

1
void componentWillMount()

这个函数调用时机是在组件创建,并初始化了状态之后,在第一次绘制 render() 之前。可以在这里做一些业务初始化操作,也可以设置组件状态。这个函数在整个生命周期中只被调用一次。

componentDidMount

在组件第一次绘制之后,会调用 componentDidMount(),通知组件已经加载完成。函数原型如下:

1
void componentDidMount()

这个函数调用的时候,其虚拟 DOM 已经构建完成,你可以在这个函数开始获取其中的元素或者子组件了。需要注意的是,RN 框架是先调用子组件的 componentDidMount(),然后调用父组件的函数。从这个函数开始,就可以和 JS 其他框架交互了,例如设置计时 setTimeout 或者 setInterval,或者发起网络请求。这个函数也是只被调用一次。这个函数之后,就进入了稳定运行状态,等待事件触发。

componentWillReceiveProps

如果组件收到新的属性(props),就会调用 componentWillReceiveProps(),其原型如下:

1
2
3
void componentWillReceiveProps(  
object nextProps
)

输入参数 nextProps 是即将被设置的属性,旧的属性还是可以通过 this.props 来获取。在这个回调函数里面,你可以根据属性的变化,通过调用 this.setState() 来更新你的组件状态,这里调用更新状态是安全的,并不会触发额外的 render() 调用。如下:

1
2
3
4
5
componentWillReceiveProps: function(nextProps) {  
this.setState({
likesIncreasing: nextProps.likeCount > this.props.likeCount
});
}

shouldComponentUpdate

当组件接收到新的属性和状态改变的话,都会触发调用 shouldComponentUpdate(...),函数原型如下:

1
2
3
boolean shouldComponentUpdate(  
object nextProps, object nextState
)

输入参数 nextProps 和上面的 componentWillReceiveProps 函数一样,nextState 表示组件即将更新的状态值。这个函数的返回值决定是否需要更新组件,如果 true 表示需要更新,继续走后面的更新流程。否者,则不更新,直接进入等待状态。

默认情况下,这个函数永远返回 true 用来保证数据变化的时候 UI 能够同步更新。在大型项目中,你可以自己重载这个函数,通过检查变化前后属性和状态,来决定 UI 是否需要更新,能有效提高应用性能。

componentWillUpdate

如果组件状态或者属性改变,并且上面的 shouldComponentUpdate(...) 返回为 true,就会开始准更新组件,并调用 componentWillUpdate(),其函数原型如下:

1
2
3
void componentWillUpdate(  
object nextProps, object nextState
)

输入参数与 shouldComponentUpdate 一样,在这个回调中,可以做一些在更新界面之前要做的事情。需要特别注意的是,在这个函数里面,你就不能使用 this.setState 来修改状态。这个函数调用之后,就会把 nextPropsnextState 分别设置到 this.propsthis.state 中。紧接着这个函数,就会调用 render() 来更新界面了。

componentDidUpdate

调用了 render() 更新完成界面之后,会调用 componentDidUpdate() 来得到通知,其函数原型如下:

1
2
3
void componentDidUpdate(  
object prevProps, object prevState
)

因为到这里已经完成了属性和状态的更新了,此函数的输入参数变成了 prevPropsprevState

componentWillUnmount

当组件要被从界面上移除的时候,就会调用 componentWillUnmount(),其函数原型如下:

1
void componentWillUnmount()

在这个函数中,可以做一些组件相关的清理工作,例如取消计时器、网络请求等。

下表是生命周期函数的调用次数,以及能否使用 setSate():

生命周期 调用次数 能否使用 setSate()
getDefaultProps 1(全局调用一次)
getInitialState 1
componentWillMount 1
render >=1
componentDidMount 1
componentWillReceiveProps >=0
shouldComponentUpdate >=0
componentWillUpdate >=0
componentDidUpdate >=0
componentWillUnmount 1 0

RN的设计模式

目前设计模式也非常多,如Flux,Reflux,Redux,Relay,Marty,不过以上都不是很了解,可以参考ReactNative的组件架构设计学习了解一下,由于做客户端的同学接触的最多的是MVC,MVVM、MVCS等等,所以我觉得选用类似MVCS的模式可能更加适合新手的学习,比如写组件时,通常我们会创建一个组件,里面会包含数据的处理,页面的渲染,样式的设置,网络请求,当这些内容过多时,组件就会显得特别臃肿,所以我们需要将其拆分开为数据模型(Model),页面渲染,样式设置,网路请求(Service),这里的页面渲染和样式设置,不能算是称作为iOS里的Controller和View,应该跟前端一样,在html文件里面写布局,css文件里面写样式,感觉像是MVCS和前端的融合

0x03 RN与原生的交互

在写RN时不免会遇到与原生交互,下面我分JS调用原生、原生调用JS来讲

JS调用原生

在调用原生时,我们需要实现RCTBridgeModuleRCT_EXPORT_MODULE();

RCT_EXPORT_MODULE();则是一个宏定义,返回moduleName,并且调用+ load方法注册

1
2
3
4
#define RCT_EXPORT_MODULE(js_name) \
RCT_EXTERN void RCTRegisterModule(Class); \
+ (NSString *)moduleName { return @#js_name; } \
+ (void)load { RCTRegisterModule(self); }

例如我们增加一个bridge方法,获取版本号,getVersion为方法名,callback是原生回调给JS的内容

1
2
3
4
5
RCT_EXPORT_METHOD(getVersion : (RCTResponseSenderBlock)callback) {
NSString *version =
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
callback(@[[NSNull null], @[version]]);
}

然后返回方法的队列为主队列

1
2
3
- (dispatch_queue_t)methodQueue {
return dispatch_get_main_queue();
}

在JS文件里,我们可以定义一个全局变量

1
var ZanIntentModule = NativeModules.ZanIntentModule;

然后在使用的时候调用我们在原生时定义方法

1
2
3
4
ZanIntentModule.getVersion(
(callback) => {
// do some thing
})

原生调用JS

老版本的调用方式为,但是接口被标记为deprecated:__deprecated_msg("Subclass RCTEventEmitter instead");

1
[self.bridge.eventDispatcher sendAppEventWithName:kGiftReloadData body:nil];

新版本的调用方式为

1
2
3
ZanEventEmitter *emitter = [[ZanEventEmitter alloc] init];
emitter.bridge = self.bridge;
[emitter sendEventWithName:kGiftReloadData body:nil];

但是新版本坑的是,直接这样调用时bridge居然是nil,网上说用单例,但是也不行…所以我还是用老版本的调用方法,有哪个大神知道怎么用新版本接口调用的正确姿势,请留言交流哈

然后在实现RCTBridgeDelegate

1
2
3
4
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
return [[NSBundle mainBundle] URLForResource:@"bundle/index.ios" withExtension:@"jsbundle"];
// return [NSURL URLWithString:@"http://172.17.9.94:8081/index.ios.bundle?platform=ios"];
}

在对应的组件里,需要在componentWillMount增加监听

1
2
3
4
5
6
componentWillMount() {
this.eventEmitter = NativeAppEventEmitter.addListener(
'GiftReloadData',
() => this._reloadData()
);
}

对应的也需要移除掉监听

1
2
3
componentWillUnmount() {
subscription.remove();
}

然后原生发送action之后,会触发我们设定好的reloadData()方法

0x04 本地调试与打包调试

在编写的过程中,也需要进行调试,调试有两种方法:一种是本地调试,一种是打包调试

本地调试

我们在加载bundle时,需要替换成你的ip地址,端口号不要变

1
[NSURL URLWithString:@"http://172.17.9.94:8081/index.ios.bundle?platform=ios"]

如果你是在真机上调试,你需要开启HTTP代理,填写你的ip地址和端口号

在终端上,先进入到你的项目目录(与node_modules目录同级),然后开启服务

1
yzydeMacBook-Pro:shangjiaban-ios yzy$ npm start

你修改了某处之后,在模拟器上点击Shake Gesture或者快捷键,在真机上只要摇一摇就可以

在模拟器弹出框里选择Roload,这样就会重新加载你本地的JS文件

如果你想查看JS里面的log日志,你可以选择Start Remote JS Debugging,在chrome浏览器里就能看到输出的日志了

打包调试

另外一种就是打包调试,但是比较麻烦,首先我们要讲bundle加载方式改为

1
[[NSBundle mainBundle] URLForResource:@"bundle/index.ios" withExtension:@"jsbundle"];

然后在终端里面,输入

1
yzydeMacBook-Pro:shangjiaban-ios yzy$ react-native bundle --entry-file index.ios.js --platform ios --dev false --bundle-output ./xxx/bundle/index.ios.jsbundle --assets-dest ./xxx/bundle

--bundle-output ./xxx/bundle/index.ios.jsbundle指的是输出的bundle文件路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[20:54:43] <START> Building Dependency Graph
[20:54:43] <START> Crawling File System
[20:54:43] <START> find dependencies
[20:54:48] <END> Crawling File System (4712ms)
[20:54:48] <START> Building in-memory fs for JavaScript
[20:54:48] <END> Building in-memory fs for JavaScript (230ms)
[20:54:48] <START> Building in-memory fs for Assets
[20:54:48] <END> Building in-memory fs for Assets (154ms)
[20:54:48] <START> Building Haste Map
[20:54:48] <START> Building (deprecated) Asset Map
[20:54:48] <END> Building (deprecated) Asset Map (66ms)
[20:54:48] <END> Building Haste Map (154ms)
[20:54:48] <END> Building Dependency Graph (5261ms)
transformed 372/372 (100%)
[20:54:49] <END> find dependencies (6402ms)
bundle: start
bundle: finish
bundle: Writing bundle output to: ./Koudaitong/bundle/index.ios.jsbundle
bundle: Copying 5 asset files
bundle: Done writing bundle output
bundle: Done copying assets

当看到这样的信息的时候,说明已经打包成功了,再将生成的bundle文件夹以Create folder references形式加到工程里,然后就可以run了

Tip:

1
在真机调试时,需要在Edit Scheme里在Run模式里,将Build Configuration改为Release模式

0x05 远程热更新

这块网上的方案大同小异,因为目前我们还是采取本地打包加载的方式,还未上热更新,所以在这不好多做说明,等上了热更新之后,我再来补充~

2016年11月23日更新:

Tips:

1
该热更新方法取自于有赞技术团队官方博客里《React Native有赞初探》,欢迎关注我们技术团队的博客~

选型

经过调研和选型,最终选择了微软出品的 CodePush 作为 React Native 热部署方案。

CodePush 是提供给 React Native 开发者直接部署移动应用更新给用户设备的云服务。CodePush 作为一个中央仓库,开发者可以推送更新 (JS, HTML, CSS and images),应用可以从客户端 SDK 里面查询更新。CodePush 可以让应用有更多的可确定性,也可以让你直接接触用户群。在修复一些小问题和添加新特性的时候,不需要经过二进制打包,可以直接推送代码进行实时更新。

CodePush 可以进行实时的推送代码更新:

  • 直接对用户部署代码更新
  • 管理 AlphaBeta 和生产环境应用
  • 支持 JavaScript 文件与图片资源的更新
  • 暂不支持增量更新

CodePush 开源了 react-native 版本,react-native-code-push托管在GitHub上。

具体的教程和用法微软都在 Github上 做了详细说明,接下来简单地梳理一下从配置、编码、部署等具体流程。

(1) 安装 CodePush CLI

管理 CodePush 账号需要通过 NodeJS-based CLI。 只需要在终端输入 npm install -g code-push-cli ,就可以安装了。 安装完毕后,输入 code-push -v 查看版本,如看到版本代表成功。

(2) 创建一个 CodePush 账号 在终端输入 code-push register ,会打开如下注册页面让你选择授权账号。

授权通过之后,CodePush 会告诉你“access key”,复制此key到终端即可完成注册。

然后终端输入 code-push login 进行登陆,登陆成功后,你的session文件将会写在 /Users/你的用户名 /.code-push.config

(3) 在CodePush服务器注册app 为了让 CodePush 服务器知道你的app,我们需要向它注册app: 在终端输入 code-push app add 即可完成注册。

例如:

1
code-push app add shangjiaban-android

如果是iOS平台,命令为 code-push app add shangjiaban-iosAndroidiOS 必须要区分

还有很多 code-push app 相关的命令,参考如下:

1
2
3
4
5
6
7
8
9
10
11
$: code-push app help
Usage: code-push app <command>

命令:
add Add a new app to your account
remove Remove an app from your account
rm Remove an app from your account
rename Rename an existing app
list Lists the apps associated with your account
ls Lists the apps associated with your account
transfer Transfer the ownership of an app to another account

iOS配置

iOS平台上关于 CodePush 的配置和 Android 平台是类似的,可以参考上文的(1)(2)(3),iOS平台集成 CodePush 比较简单,官网提供了3种集成方式,这里重点介绍如何通过 cocoapods 来集成。

(1) 引入 CodePush

首先在Podfile文件中添加 CodePush,配置如下:

1
pod 'CodePush', :path => './node_modules/react-native-code-push'

然后执行 pod install 就可以了。

(2) 声明 bundle 文件来源

引入 CodePush 后还需要在代码中声明 bundle 的加载来源,之前是加载本地的bundle文件,现在需要调用 CodePush 提供的方法指定加载 Bundle 文件,代码如下:

1
2
3
4
5
6
7
8
#import "CodePush.h"
...
// 原来的bundle加载方法
jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];

// CodePush的bundle加载方法,这里的bundleURL默认加载的是main.jsbundle,
// 如果你的名称不一样,需要调用CodePush提供的其他方法来自定义。
jsCodeLocation = [CodePush bundleURL];

最后还需要在 Info.plist 中添加一个 keyCodePushDeploymentKey ,其value 就是 CodePush 提供的唯一 token 值。具体获取方法,可以通过如下命令获得

1
code-push deployment ls <appName> -k

RN配置

为了达到更好的体验效果,我们决定采用静默升级的策略,让用户无感知地体验热更新,也可以是具体的升级流程图如下:

如果要达成上述热部署效果,那么还需要在 JavaScript 文件中完成更新时机和更新策略的设置。

(1)在js中导入 CodePush 模块:

1
import codePush from 'react-native-code-push'

(2)在 componentDidMount 中调用 sync 方法,后台请求更新

1
codePush.sync()

如果是非强制并允许更新, CodePush 会在后台静默地将更新下载到本地,等待APP再一次启动或者加载 React Native 页面的时候更新应用。

如果更新是强制性的,更新文件下载好之后会立即进行更新。关于如何配置是否强制更新,会在下文发布更新处重点说明。

如果你期望更及时的获得更新,可以在每次APP从后台进入前台的时候去主动的检查更新:

1
2
3
4
5
6
AppState.addEventListener("change", (newState) => {  
newState === "active" && codePush.sync({
installMode:codePush.InstallMode.ON_NEXT_RESUME,
deploymentKey: DEPLOYMENT_KEY,
});
});

上述流程图提及的三种更新方式,就是通过 installMode 参数控制的,取值方式分别为:

  1. codePush.InstallMode.ON_NEXT_RESTART即下一次启动的时候安装更新
  2. codePush.InstallMode.ON_NEXT_RESUME即下一次切后台切换的时候安装更新
  3. codePush.InstallMode. IMMEDIATE立即下载安装更新

如果发布更新时 mandatory 参数为true,即强制更新,则上述设置都会无效,只有mandatory 参数为fasle时,设置才会有效。

打包并发布

(1) 打包js 发布更新之前,需要先把js打包成 bundle ,以下是Android的做法:

第一步: 在 Android 工程目录里面新增 release 文件: mkdir release ,对于iOS来说,目前 bundle 文件直接放在工程根目录下,所以无需这一步。 第二步: 运行命令打包

1
react-native bundle --platform 平台 --entry-file 启动文件 --bundle-output 打包js输出文件 --assets-dest 资源输出目录 --dev 是否调试。

例如:

Android

1
react-native bundle --platform android --entry-file index.android.js --bundle-output ./release/index.android.bundle --assets-dest ./release --dev false

iOS

1
react-native bundle --platform ios --entry-file index.ios.js --bundle-output ./Koudaitong/main.jsbundle --assets-dest ./Koudaitong --dev false

(2) 发布更新

打包 bundle 结束后,就可以通过 CodePush 发布更新了。在终端输入

1
2
code-push release <应用名称> <Bundles所在目录> <对应的应用版本> --deploymentName: 更新环境  
--description: 更新描述 --mandatory: 是否强制更新

例如:

Android

1
code-push release shangjiaban-android ./release 3.12.1 --description "update React Native" --mandatory true

iOS

1
code-push release shangjiaban-ios ./Koudaitong/main.jsbundle 3.12.0 --description "update React Native" --mandatory false

注意:

  1. CodePush 默认是更新 Staging 环境的,如果是 Staging ,则不需要填写deploymentName
  2. 如果有 mandatoryCode Push 会根据 mandatorytruefalse 来控制应用是否强制更新。默认情况下 mandatoryfalse 即不强制更新。
  3. 对应的应用版本 targetBinaryVersion 是指当前app的版本(对应 build.gradle 中设置的versionName “3.12.1”),也就是说此次更新的 js/images 对应的是app的那个版本。不要将其理解为这次js更新的版本。 如客户端版本是3.12.1,那么我们对3.12.1的客户端更新 js/imagestargetBinaryVersion 填的就是3.12.1。
  4. 对于对某个应用版本进行多次更新的情况, CodePush 会检查每次上传的 bundle ,如果在该版本下如3.12.1已经存在与这次上传完全一样的 bundle (对应一个版本有两个 bundlemd5 完全一样),那么 CodePush 会拒绝此次更新。

0x06 iOS和Android不同样式处理

最近看到FB的F8代码里面对于iOS和Android不同平台上样式的处理觉得挺不错的,由于系统原生控件样式设计风格的不一样,导致在写styles的时候会根据不同的platform来写,之前做法是定义不同的styles,然后判断platform去用,这样styles里面的代码会存在冗余,而且对styles的定义也不好

FB的做法是定义一个styles的基类,然后基类里解析平台信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export function create(styles: Object): {[name: string]: number} {
const platformStyles = {};
Object.keys(styles).forEach((name) => {
let {ios, android, ...style} = {...styles[name]};
if (ios && Platform.OS === 'ios') {
style = {...style, ...ios};
}
if (android && Platform.OS === 'android') {
style = {...style, ...android};
}
platformStyles[name] = style;
});
return StyleSheet.create(platformStyles);
}

解析完之后,在styles里面,会根据不同的platform取不同的样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
button: {
borderColor: 'transparent',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'transparent',
ios: {
height: HEIGHT,
paddingHorizontal: 20,
borderRadius: HEIGHT / 2,
borderWidth: 1,
},
android: {
paddingBottom: 6,
paddingHorizontal: 10,
borderBottomWidth: 3,
marginRight: 10,
},
},

0x07 踩坑记录

踩坑最多是应该是使用上的

1.RN系统的组件并不是所有都是共用的,比如segment支持iOS,不支持Android,Alert分为iOS和Android等等,所以还是要写重复的代码

2.ListView不支持iOS原生的滑动操作,需要使用第三方库,但是第三方库不能控制只编辑一个Cell

3.由于原先iOS和Android的代码仓库是分开的,所以接入RN时,JS文件也是跟着仓库走的,这样iOS和Android会存在重复代码,并且目前两个人分别接iOS和Android,写JS时,有时并不共享,容易代码写着写着就有差异了,偏离了Write Once , Run Anywhere的初衷

0x08 相关资料

React Native

React Native 中文网

汇集了各类react-native学习资源、开源App和组件

写给 iOS 开发者的 React Native 学习路线

江清清的技术专栏

React/React Native 的ES5 ES6写法对照表

React Native有赞初探