React Native
Learn once, write anywhere.
React Native為前端開發(fā)工程師開發(fā)Native應用提供了一種能力。保障開發(fā)效率,同時兼顧平臺的性能日丹。
React
相比于傳統(tǒng)的DOM的優(yōu)勢
- VirtualDOM 高效diff算法(O(n^3) -> O(n)),實現(xiàn)局部刷新
- 對外暴露Component良哲,js & css 統(tǒng)一管理
相比于傳統(tǒng)WebView H5優(yōu)勢
- 不需要兼容各個WebView了
- 效率會更高焊刹,它最終會調用native組件本地的渲染。
Get Started
參考官網(wǎng)或者中文網(wǎng)的Get Started,里面介紹了如何配置環(huán)境新症,以及通過簡單的命令很快就能run出一個簡單的示例工程步氏,然后在index.ios.js
或者index.android.js
里簡單改吧改吧就能開啟React Native開發(fā)之旅了。
NPM(Yarn)
npm 全稱是 Node Package Manager徒爹,是隨同NodeJS一起安裝的包管理工具荚醒,包括包(模塊)的下載芋类,安裝, 和發(fā)布界阁,以及管理侯繁。Yarn是Facebook提供的npm的替代工具,一般我們通過一個package.json
文件做配置泡躯。當我們執(zhí)行npm init
命令的時候贮竟,會自動提示我們輸入以下信息,得到package.json
较剃。
{
"name": "ls",
"version": "1.0.0",
"description": "this is a test",
"main": "index.js",
"dependencies": {},
"devDependencies": {},
"scripts": {
"test": "test"
},
"repository": {
"type": "git",
"url": "\"\""
},
"keywords": [
"123"
],
"author": "sl",
"license": "UNLICENSED"
}
對應我們在React Native中的配置,舉例
{
"name": "RN",
"version": "0.0.1",
"private": true,
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start"
},
"dependencies": {
"react": "15.4.2",
"react-native": "0.44.0",
"react-native-linear-gradient": "^2.0.0"
}
}
當我們執(zhí)行npm start
命令的時候坝锰,其實執(zhí)行的就是node node_modules/react-native/local-cli/cli.js start
,其中cli
是Command line interface
的縮寫,在local-cli
里相當豐富的命令重付。
構建Android應用
1顷级、新建一個普通的Adroid工程,以ReactApplication
為例确垫。
2弓颈、在工程目錄里配置package.json
,如上配置即可删掀,執(zhí)行npm install
翔冀,安裝npm組件和下載react native 包。
3披泪、在Project的build.gradle配置,上步會把react-native的相關代碼打成arr纤子,jar,pom等格式放到/node_modules/react-native/android目錄下款票,新版React Native只通過npm發(fā)布控硼,所以,需要在jcenter外配置本地倉庫艾少,獲取依賴包卡乾。
repositories {
jcenter()
maven {
// All of React Native (JS, Android binaries) is installed from npm
url "$projectDir/../node_modules/react-native/android"
}
}
4、在主module里配置
compile "com.facebook.react:react-native:0.44.0"
5缚够、 工程目錄下幔妨,新建index.android.js
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
Image
} from 'react-native';
class App extends Component {
render() {
return (
<View style={styles.container}>
<Text style={styles.note}>Welcome to React Native</Text>
<Image style={styles.sss} source={{uri:'https://facebook.github.io/react/img/logo_og.png'}}></Image>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
note: {
fontSize: 20
},
sss: {
width : 200,
height : 100
},
});
AppRegistry.registerComponent('react-native-module', () => App);
在React Native中,JS端對外暴露的是一個個Component
(組件)谍椅,AppRegistry
模塊是React Native應用運行JS的入口误堡,所有的根組件都可以registerComponent
來注冊,這里是App
,react-native-module
是對外(Native)暴露的組件的名稱雏吭。
6锁施、自定義ReactInstanceManager
可以理解成React的超級管家,我們可以進行各種配置。 然后內部會做一系列的初始化工作:包括index.android.bundle
的加載沾谜,js和native module的注冊等等膊毁,在調試模式下會生成DevSupportManager
,所有調試相關的如:調試彈窗(RedBox)基跑,與本地server端的通信婚温,bundle的更新與加載都由它統(tǒng)一管理。
@Override
protected ReactInstanceManager createReactInstanceManager() {
ReactInstanceManager reactInstanceManager = ReactInstanceManager.builder().setApplication(getApplication())
.setBundleAssetName("index.android.bundle")
.setJSMainModuleName("index.android")
.setJSBundleFile("XXX")
.addPackage(new MainReactPackage())
.addPackage(new ReactCellPackage())
.setUseDeveloperSupport(true)
.setInitialLifecycleState(LifecycleState.RESUMED)
.setUIImplementationProvider(new MyUIImplementationProvider())
.build();
return reactInstanceManager;
簡單來說index.android.bundle
就是對React Native里JS端代碼的打包媳否。
1) 一般release打包會放到assets目錄下栅螟,然后它的資源文件也會放到assets目錄下。通過設置BundleAssetName就可以利用AssetManager讀取bundle篱竭,看到XX工程里的bundle接近1MB力图。這也是我們后面可以優(yōu)化的點。
- bundle包的拆分掺逼,按需加載吃媒,優(yōu)化頁面的加載時間,攜程專門有篇文章是講bundle的拆分的吕喘。
- 提前加載bundle
- 動態(tài)下發(fā)
2)通過設置JSBundleFile赘那,可以讀取本地的bundle,RN的熱更新就可以通過這個下發(fā)氯质,然后Native重新加載一下即可募舟。
3)ReactCellPackage就是我們自定義的Module和組件。
7闻察、加載React布局
1)繼承ReactActiviy
public class MyReactActivity extends ReactActivity {
@Override
protected String getMainComponentName() {
return "react-native-module";
}
}
ReactActivity是FB為我們提供的基礎類拱礁,內部會在根布局里設置成ReactRootView(實際上就是一個FrameLayout),并做一些RN的初始化工作辕漂。JS端的Component最終會被加載成為ReactRootView的子布局呢灶。
- 自定義ReactRootView加載
ReactRootView可以設置成根布局或者頁面的一個子View。startReactApplication
方法首次就會異步做一些初始化工作钮热,ReactActivity里也是調用這個方法填抬。
public abstract class BaseActivity extends AppCompatActivity implements DefaultHardwareBackBtnHandler{
private ReactRootView mReactRootView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mReactRootView = new ReactRootView(this);
mReactRootView.startReactApplication(SSApplication.inst().getReactNativeHost().getReactInstanceManager(), getRNComponentName(), null);
setContentView(mReactRootView);
}
protected abstract String getRNComponentName();
@Override
protected void onResume() {
super.onResume();
SSApplication.inst().getReactNativeHost().getReactInstanceManager().onHostResume(this, this);
}
@Override
protected void onPause() {
SSApplication.inst().getReactNativeHost().getReactInstanceManager().onHostPause();
super.onPause();
}
@Override
protected void onDestroy() {
SSApplication.inst().getReactNativeHost().getReactInstanceManager().onHostDestroy(this);
SSApplication.inst().getReactNativeHost().getReactInstanceManager().detachRootView(mReactRootView);
super.onDestroy();
}
@Override
public void onBackPressed() {
super.onBackPressed();
SSApplication.inst().getReactNativeHost().getReactInstanceManager().onBackPressed();
}
@Override
public void invokeDefaultOnBackPressed() {
super.onBackPressed();
}
}
8、自定義ReactPackage
public class ReactCellPackage implements ReactPackage {
//需要注冊的自定義的NativeModule列表
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
//JS Module 接口隧期,需要在JS中做對等實現(xiàn),并打包的bundle中赘娄。
@Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
//自定義View的ViewManager列表
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(new ReactCellViewManager()
, new MyReactImageViewManager()
, new MyReactTextViewManager()
, new RealRecyclerItemViewManager()
, new RealRecyclerViewManager());
}
}
至此我們就可以開始玩React Native了仆潮。
React Native 通信
這張圖比較省略,Java和JS之間還有一個Bridge(C/C++層)遣臼,簡單理解來說性置,Java和JS各自維護了一份對等的映射config,比如Java傳入A就可以調用js config表里揍堰,A對應的模塊鹏浅。反之亦然嗅义。這個配置是在初始化React Native 上下文的時候生成的。
初始化流程
1)生成ReactApplicationContext實例.
2)初始化3個線程隐砸,UI線程之碗,NativeModule線程,JSThread,以及 jsExecutor季希。
3)處理各類Package褪那,解析并在Java側維護了各個JavaScriptModule和NativeModule表
- initBridge,將NativeModule表下發(fā)給C/C++層
- runJSBundle 加載JSBundle
Java -> JS
可以這樣理解式塌,JavaScriptModule就是JS暴露給Java的調用接口博敬。當Java調用JS方法的時候:
- 先找到對應的JS Module,并填充相應的參數(shù)峰尝。
2)Proxy通過jni調用生成JS Module的實例
3)jni調用 實例偏窝,方法,參數(shù)作為產(chǎn)生 下發(fā)的到Bridge層
4)Bridge層 執(zhí)行對應的 JS 方法
JS -> JAVA
- JS Module的方法調用時武学,會把module囚枪,methond,params放入到MessageQueue里
2)當message的消息間隔大于5ms時劳淆,觸發(fā)Queue的情況操作链沼。
3)Bridge層 通過JNI找到 對應的NativeModule
4)NativeModule執(zhí)行相應的method.
View(UIManagerModule)
JS端觸發(fā)view刷新等都會走到UIManagerModule中,這里會將這些“消息”封裝成一個一個Operation沛鸵,由UIViewOperationQueue統(tǒng)一調度括勺。這個就回到在UI Thread里了,后續(xù)處理的就是Native本地的渲染了曲掰。
痛點
性能
React Native相比于Native性能還是偏弱疾捍。包括渲染效率,加載延時栏妖,以及View的復用等等乱豆,大廠們紛紛都在對React Native 做特定場景的優(yōu)化。比如是listview吊趾。目前我們也在做listview的優(yōu)化宛裕。
穩(wěn)定性
1、crash
2论泛、國產(chǎn)手機的兼容性
AnyWay
React Native 確實為Native 開發(fā)提供一種思路揩尸,也許后面性能不再是瓶頸,那么未來是光明的屁奏。眼下岩榆,我們還是要多踩坑。
參考
React 源碼剖析系列 - 不可思議的 react diff
React Native Android 從學車到補胎和成功發(fā)車經(jīng)歷
React Native Android 源碼框架淺析
其實沒那么復雜!探究react-native通信機制