React-Native
React-Native 目前是一个不错的多平台开发和快速开发的解决方案,它解决了很多开发上的痛点,并且作为 facebook 大厂出品,维护上有保障,因此学习 RN 是有好处的。当然,它目前还存在着很多的问题,但是都可以通过一些方案来规避和解决。RN 目前来说对 iOS 开发者比较友好,而 Android 开发环境则比较难以配置。
HelloWorld
学什么都得先来个 HelloWorld,RN 官方文档对 HelloWorld 已经讲的很详细了。
> brew install node
> brew install watchman
> npm install -g react-native-cli
> react-native init HelloWorld
然后就自动创建了对应的 iOS 和 Android 工程,笔者自己使用的是 node4-lts 版本,watchman 用于监视文件是否改变,不过笔者记得以前版本 watchman 是通过 npm 安装的,并且不是强制要求。可能现在版本改动了,不装就不能运行。
iOS
{
"name": "HelloWorld",
"version": "0.0.1",
"private": true,
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start"
},
"dependencies": {
"react": "15.3.2",
"react-native": "0.34.1"
}
}
这是 package.json 文件内容,里面依赖了 react 和 react-native。打开 iOS 工程,里面基本和普通的 iOS 工程差不多,Libraries 目录包括了 node_modules 文件夹下的依赖工程,React.xcodeproj
工程内含两段脚本,一个是 RCTJSCProfiler
分析,一个是启动本地 node 服务器。AppDelegate.m 如下
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSURL *jsCodeLocation;
jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil];
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"HelloWorld"
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;
}
主要的区别就是使用 RCTRootView 作为 RN 渲染视图添加到控制器上。并且指定了 bundleURL 和 moduleName,看过源码的应该知道,bundleURL 就是本地或者远程 jsbundle 路径,而 moduleName 就是 js 代码中注册的模块名,jsBundleURLForBundleRoot:fallbackResource
则是返回本地或者远程的 jsbundle 路径,这里就不细讲了,只要点开源码一看就懂。
Android
Android 环境配置比 iOS 麻烦多了,因为 iOS URL 等配置都是明文放在 AppDelegate 代码中的,哪里创建了 RootView 都很清楚,而安卓就比较麻烦,MainApplication
实现了 ReactApplication
接口,主要是生成了 mReactNativeHost
成员变量。mReactNativeHost
持有 ReactInstanceManager
类变量,初始化代码如下
ReactInstanceManager.Builder builder = ReactInstanceManager.builder()
.setApplication(mApplication)
.setJSMainModuleName(getJSMainModuleName())
.setUseDeveloperSupport(getUseDeveloperSupport())
.setRedBoxHandler(getRedBoxHandler())
.setUIImplementationProvider(getUIImplementationProvider())
.setInitialLifecycleState(LifecycleState.BEFORE_CREATE);
这里可以看到指定了 jsMainModuleName 和是否显示开发者菜单的参数,再来看 MainActivity,它继承了 ReactActivity,只重写了一个方法
/**
* Returns the name of the main component registered from JavaScript.
* This is used to schedule rendering of the component.
*/
@Override
protected String getMainComponentName() {
return "HelloWorld";
}
再来看它的父类 ReactActivity,里面有一个成员变量 ReactActivityDelegate,它接受两个参数初始化
new ReactActivityDelegate(this, getMainComponentName());
这就变相的将组件名传入了 mDelegate。再进入 ReactActivityDelegate
,就能找到
private @Nullable ReactRootView mReactRootView;
这就是 RootView,然后它的初始化如下
protected void loadApp(String appKey) {
if (mReactRootView != null) {
throw new IllegalStateException("Cannot loadApp while app is already running.");
}
mReactRootView = createRootView();
mReactRootView.startReactApplication(
getReactNativeHost().getReactInstanceManager(),
appKey,
getLaunchOptions());
getPlainActivity().setContentView(mReactRootView);
}
这里的初始化实际上就是对应了 iOS 中 rootView 的初始化,只不过 Android 是 startReactApplication 而不是通过构造函数传递参数的方式。并且,主要的参数都是通过 ReactInstanceManager 来存储管理,appKey 实际上就是组件名,等同于 iOS 中的 moduleName
,而 ReactInstanceManager
初始化中的 setJSMainModuleName
才是 jsbundle 名称。Android 甚至没有在代码中指定 remote server jsbundle 路径的地方,目前只能进入 app 然后打开 dev setting
来修改远程服务器地址,当然,也可能是笔者没有找到,如果有找到的朋友请在评论中告诉我。而且 ReactInstanceManager
初始化后面还有一小段代码
String jsBundleFile = getJSBundleFile();
if (jsBundleFile != null) {
builder.setJSBundleFile(jsBundleFile);
} else {
builder.setBundleAssetName(Assertions.assertNotNull(getBundleAssetName()));
}
发现了没,iOS 下远程 jsbundle 和本地 jsbundle 是通过一个方法统一处理的,而 Android 则需要两个参数维护。iOS 下 RN 只提供了 RootView,开发者只需要处理好 RootView 暴露的方法就行了,更加简单,而 Android 环境下则是提供了 ReactActivity 类来做封装,并且将参数拆成了好几个类,非常的麻烦。
Integration With Existing Apps
iOS
手动 iOS 整合步骤如下:
package.json
创建带有 subspecs 的
Podfile
用于引入所需要的第三方库,这需要 CocoaPods,虽然也可以手动 link 第三方库,但是目前笔者推荐使用 CocoaPodsindex.ios.js
RCTRootView
当然,还有其他步骤,比如 ATS 的问题,不过一个合格的开发者应当都能完成,看上面这些步骤,你会发现实际上在 iOS 下,手动整合和 HelloWorld 基本是一致的。
Android
手动整合步骤如下
package.json
增加
com.facebook.react:react-native:+
和maven {}
到 build.gradleindex.android.js
ReactRootView
是否发现了,在 Android 环境下,HelloWorld 工程和手动整合指南是不一样的,最主要的区别在于 HelloWorld 工程是继承 ReactActivity 来产生 Activity 的,而手动整合则是可以随便做,甚至可以在 Fragment 上面创建 RootView。
总结
就目前而言,RN 在 iOS 下无论是表现还是编码难度,都优于 Android 环境,Android 官方甚至没有一个很好的启动 App 方式,只能 react-native run-android
。而且在性能方面,iOS 经过良好的编码和优化,是能够达到原生代码的水平的,而 Android 则有不少问题,但是 RN 虽然有这样那样的问题,它对整个跨平台开发是有很大贡献的,让开发者有机会多端开发,能涉足其他平台,而且相信未来这些问题都是能解决的。