React Native與傳統的HybirdApp最大區別就是拋開WebView,使用JSC+原生組件的方式進行渲染,那麼整個App啓動/渲染流程又是怎樣的呢?javascript
首先從組件的角度來看下RN的啓動流程:(Android爲例)java
setupReactContext
初始化React上下文,調用JS端AppRegistry.runApplication(key,params)
,key爲模塊/組件名稱,參數包含rootTag、initialPropsrenderApplication
渲染整個應用renderApplication
函數中會執行:react
ReactNative.render( <AppContainer> <RootComponent {...initialProps} rootTag={rootTag} /> </AppContainer>, rootTag );
其中ReactNative
是在React庫中定義的,AppContainer
是一個JS組件,使用View包裹了根組件,開發時工具Inspector
、YellowBox
都是在這個組件中加載,RootComponent
是傳入的根組件。react-native
JS端註冊組件:(在第2步執行JSBundle時)網絡
AppRegistry.registerComponent('TiebaNext', rootComponent);
*僅在JS端處理,記錄在一個Map中。數據結構
Android端定義啓動組件,Activity中,繼承ReactActivity:(在第1步時調用)異步
@Override protected String getMainComponentName() { return "TiebaNext"; }
iOS端定義啓動組件:ide
self.rctRootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation moduleName:@"TiebaNext" initialProperties:nil launchOptions:nil];
簡單說就是Native初始化 -> 加載JS,JS端註冊組件 -> 端上調用JS端run方法,傳入入口組件名稱 -> JS端啓動渲染流程。函數
React的渲染都是以組件爲單位,上面已經分析了,啓動的最後階段就是JS端開始渲染根組件。首先咱們先看下React的組件是怎麼編寫的,以及他的生命週期:(熟悉React可略過)工具
一個例子,無網絡提示組件:
(例子語言Typescript)
// 組件的屬性定義 interface PropsDefine { // 組件寬度 width: number // 組件高度 height: number // 點擊刷新按鈕回調,可選 onClickRefresh?: () => void } export class NoNetwork extends React.Component<PropsDefine, {}> { // 組件無狀態,定義爲空:{} // 組件的默認屬性定義,單例,實例間共享 static defaultProps = { onClickRefresh: () => { } } render() { let {width, height} = this.props return ( <View style={[Styles.panel, { width: width, height: height, }]}> <View style={Styles.picBlock}> <Image source={Styles.picUrl}/> </View> <View style={Styles.textBlock}> <Text style={Styles.text}>你的網絡好像不給力</Text> <Text style={Styles.text}>點擊按鈕刷新</Text> </View> <TouchableOpacity style={Styles.button} onPress={this.props.onClickRefresh}> <Text style={Styles.buttonText}>刷新</Text> </TouchableOpacity> </View> ) } }
跟端上組件開發同樣,React組件也定義了組件的生命週期:
getDefaultProps
getInitialState
componentWillMount
render
componentDidMount
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
render
componentDidUpdate
componentWillUnmount
那麼這個組件究竟是怎麼用原生組件渲染的呢?首先咱們先來看看最主要的render作了什麼。jsx不太直觀,咱們先翻譯一下render:
render() { let { width, height } = this.props; return (React.createElement(View, { style: [Styles.panel, { width: width, height: height, }] }, React.createElement(View, { style: Styles.picBlock }, React.createElement(Image, { source: Styles.picUrl })), React.createElement(View, { style: Styles.textBlock }, React.createElement(Text, { style: Styles.text }, "\u4F60\u7684\u7F51\u7EDC\u597D\u50CF\u4E0D\u7ED9\u529B"), React.createElement(Text, { style: Styles.text }, "\u70B9\u51FB\u6309\u94AE\u5237\u65B0")), React.createElement(TouchableOpacity, { style: Styles.button, onPress: this.props.onClickRefresh }, React.createElement(Text, { style: Styles.buttonText }, "\u5237\u65B0")))); }
這下清晰多了吧?
React.createElement
的方法簽名:
ReactElement.createElement = function (type, config, children){ ... }
ReactNative的UI組件經過requireNativeComponent
-> createReactNativeComponentClass
-> ReactNativeBaseComponent下mountComponent
的調用關係,最終在mountComponent
中調用UIManager
組件建立View:UIManager.createView(tag, this.viewConfig.uiViewClassName, nativeTopRootTag, updatePayload);
,在Native端,UIManager調用對應組件類型的ViewManager(單例,管理類)建立實例。
*UIManager
是一個NativeModule,待下面分析
接下來咱們來詳細分析下原生組件的實現方法,以Image組件爲例:
iOS和Android實現有必定差別,首先是Image組件JS端代碼,都須要requireNativeComponent
加載原生組件:
const RCTImageView = requireNativeComponent('RCTImageView', Image);
Image的JS端實際上也是一個React JS組件,他也有render,返回的是:(iOS)
<RCTImageView {...this.props} style={style} resizeMode={resizeMode} tintColor={tintColor} source={sources} />
由於業務邏輯是寫在JS端的,建立出了Native組件就須要進行控制,天然就涉及到屬性傳遞、方法調用、事件回調這3個需求。
JS端組件跟Native真正實現的組件主要涉及三件事:
屬性同步很簡單,其實是在組件從新render的時候調用ReactNativeBaseComponent
下receiveComponent
-> UIManager.updateView
完成的。
兩種方法,一種是調用NativeModules
(後面有簡單分析),若是想直接調用一個具體View的方法,那就須要使用UIManager模塊:
Android端UIManager中的定義:
@ReactMethod public void dispatchViewManagerCommand(int reactTag, int commandId, ReadableArray commandArgs) { mUIImplementation.dispatchViewManagerCommand(reactTag, commandId, commandArgs); }
iOS端UIManager中的定義:
RCT_EXPORT_METHOD(dispatchViewManagerCommand:(nonnull NSNumber *)reactTag commandID:(NSInteger)commandID commandArgs:(NSArray<id> *)commandArgs) { RCTShadowView *shadowView = _shadowViewRegistry[reactTag]; RCTComponentData *componentData = _componentDataByName[shadowView.viewName]; Class managerClass = componentData.managerClass; RCTModuleData *moduleData = [_bridge moduleDataForName:RCTBridgeModuleNameForClass(managerClass)]; id<RCTBridgeMethod> method = moduleData.methods[commandID]; NSArray *args = [@[reactTag] arrayByAddingObjectsFromArray:commandArgs]; [method invokeWithBridge:_bridge module:componentData.manager arguments:args]; }
這個方法是從端上映射到JS的,因此在JS端能夠這樣調用:
UIManager.dispatchViewManagerCommand( findNodeHandle(this), // 找到與NativeUI組件對應的JS組件實例 UIManager.[UI組件名].Commands.[方法], [] // 參數 )
findNodeHandle
方法是在React中定義,能夠找到組件實例的reactTag
(執行在JS端),UIManager能夠把調用命令分發到Native端對應的組件類型的ViewManager,再經過ViewManager調用View組件實例的對應方法。
Android端使用的是相似JS端調用Native的方式,使用了事件機制,不過事件的接收者是從JS端映射過來的,React下ReactNativeEventEmitter.receiveEvent(tag, topLevelType, nativeEventParam)
,因此須要先實現一個Event:(Switch的onValueChange事件)
class ReactSwitchEvent extends Event<ReactSwitchEvent> { public static final String EVENT_NAME = "topChange"; // topChange會被映射成onChange,具體映射關係參見 UIManagerModuleConstants.java public ReactSwitchEvent(int viewId, boolean isChecked) { super(viewId); mIsChecked = isChecked; } public boolean getIsChecked() { return mIsChecked; } @Override public String getEventName() { return EVENT_NAME; } @Override public short getCoalescingKey() { // All switch events for a given view can be coalesced. return 0; } @Override public void dispatch(RCTEventEmitter rctEventEmitter) { rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData()); } private WritableMap serializeEventData() { WritableMap eventData = Arguments.createMap(); eventData.putInt("target", getViewTag()); eventData.putBoolean("value", getIsChecked()); return eventData; } }
而後在ViewManager或View中進行事件派發:
ReactContext reactContext = (ReactContext) buttonView.getContext(); reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent( new ReactSwitchEvent( buttonView.getId(), isChecked));
iOS端實現有所區別,iOS端將JS函數直接映射到Native,因此能夠直接調用(可屢次調用):(View爲RCTSwitch)
// ViewManager中聲明事件爲RCTBubblingEventBlock或RCTDirectEventBlock RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock); // View中聲明 @property (nonatomic, copy) RCTBubblingEventBlock onChange; // view實例化時監聽onChange - (void)onChange:(RCTSwitch *)sender { if (sender.wasOn != sender.on) { if (sender.onChange) { sender.onChange(@{ @"value": @(sender.on) }); } sender.wasOn = sender.on; } }
這樣就能夠從JS端建立NativeUI組件了,能夠看到UI組件的Native和JS端是經過reactTag進行的關聯,經過UIManager模塊,在Native端的DOM和React的DOM進行同步操做,保持結構一致。
模塊數據結構,JS端可訪問:
UIManager.[UI組件名].[Constants(靜態值)/Commands(命令/方法)]
從端上映射的方法:(部分)
createView(int tag, String className, int rootViewTag, ReadableMap props)
updateView(int tag, String className, ReadableMap props)
manageChildren(int viewTag, Array moveFrom, Array moveTo, Array addChildTags, Array addAtIndices, Array removeFrom)
measure(int reactTag, Callback callback)
measureInWindow(int reactTag, Callback callback)
dispatchViewManagerCommand(int reactTag, int commandId, ReadableArray commandArgs)
這個模塊是NativeModule方式定義的,在RN的JS端啓動時,端上會經過JSC把收集到的模塊信息(名稱)打到JS端全局變量global.__fbBatchedBridgeConfig
中,並採用延遲加載策略:設置NativeModules.[模塊名]
的getter,延遲經過JSC讀取模塊詳細信息(方法、命令號等信息)。在調用的時候會放到MessageQueue
的隊列裏,批量提交,兩次批量提交限制的最小間隔爲5ms。
關於React Native通信更詳盡的分析參見:React Native通信原理