React Native的开发思路是经过组合各类组件来组织整个App,在大部分状况下经过组合View、Image等几个基础的组件,能够很是方便的实现各类复杂的跨平台组件,不过在须要原生功能支持、对性能有要求的状况下仍是须要进行必定的原生的开发,合理的组件实现方式能够下降使用和跨平台的成本。javascript
(底层实现分析参见:React-Native 渲染实现分析,本文仅讨论组件开发方法)html
RN的组件开发有几种方式,JS组件、Native功能组件、NativeUI组件。JS组件是仅使用React Native自带的组件进行组合实现的组件,优点是跨平台方便,但受限于RN实现的效果,一些复杂需求没法实现。而Native组件要强大许多,不过须要考虑平台差别,提供统一的接口难度要大一些。java
JS组件是指仅靠RN自己自带的组件开发的组件,是RN下最基础的开发方式,大部分的组件都是JS组件。最大的好处是能够低成本的跨平台。JS组件的开发体验跟React(Web)一致,区别在基础标签不同,以及样式定义方式不同。react
一个例子,无网络提示组件:json
(例子语言Typescript)react-native
// 组件的属性定义 interface PropsDefine { // 组件宽度 width: number // 组件高度 height: number // 点击刷新按钮回调,可选 onClickRefresh?: () => void } export class NoNetwork extends React.Component<PropsDefine, {}> { // 组件无状态,定义为空:{} // 组件的默认属性定义,单例,实例间共享 static defaultProps = { onClickRefresh: () => { } } constructor(props: PropsDefine) { super(props) } 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组件也定义了组件的生命周期:promise
getDefaultProps
getInitialState
componentWillMount
render
componentDidMount
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
render
componentDidUpdate
componentWillUnmount
适用场景:
性能要求不高、不须要复杂端上能力的组件。网络
那么在须要端上能力、或性能要求高的时候怎么办呢?这就须要开发Native组件了,Native组件又能够简单的分为功能性组件和UI组件两种。app
通常来讲,Native的组件开发分为两部分,一部分是Native代码,一部分是与之配合的JS代码(JS端代码开发相似JS组件开发,开放接口,屏蔽实现细节、平台差别),这两部分共同构成一个Native组件。ide
功能性组件相似服务开发,以模块的形式提供功能接口(单例),并暴露给JS端。
以 网络信息模块(NetInfo) 为例:
模块的JS端代码:
const RCTNetInfo = NativeModules.NetInfo; // 已Promise的形式主动调用Native模块 RCTNetInfo.getCurrentConnectivity().then(resp => resp.network_info); const NetInfoEventEmitter = new NativeEventEmitter(RCTNetInfo); // 注册Native模块的事件 NetInfoEventEmitter.addListener( 'networkStatusDidChange', (appStateData) => { handler(appStateData.network_info); } );
模块的Native端代码:
Android端:
// 定义暴露的方法,以Promise的形式返回结果 @ReactMethod public void getCurrentConnectivity(Promise promise) { promise.resolve(createConnectivityEventMap()); } // 发送网络状态变动事件 private void sendConnectivityChangedEvent() { getReactApplicationContext().getJSModule(RCTDeviceEventEmitter.class) .emit("networkStatusDidChange", createConnectivityEventMap()); } // 建立网络状态变动事件数据 private WritableMap createConnectivityEventMap() { WritableMap event = new WritableNativeMap(); event.putString("network_info", mConnectivity); return event; }
iOS端:
// 定义暴露的方法,以Promise的形式返回结果 RCT_EXPORT_METHOD(getCurrentConnectivity:(RCTPromiseResolveBlock)resolve reject:(__unused RCTPromiseRejectBlock)reject) { resolve(@{@"network_info": _status ?: RCTReachabilityStateUnknown}); } // 发送网络状态变动事件 [self sendEventWithName:@"networkStatusDidChange" body:@{@"network_info": status}];
*数据类型:
UI组件要复杂一些,须要定义ViewManager和具体View的实现,ViewManager用来建立/管理View实例,是View实例和React之间沟通的桥梁。
Android端ViewManager可继承自SimpleViewManager、ViewGroupManager
以图片组件Image为例:
组件的JS端代码:
// 引用Native组件 const RCTImageView = requireNativeComponent('RCTImageView', Image); // 组件代码 <RCTImageView {...this.props} style={style} resizeMode={resizeMode} tintColor={tintColor} source={sources} />
定义组件名称:
Android端:
// 定义组件名称 public static final String REACT_CLASS = "RCTImageView"; @Override public String getName() { return REACT_CLASS; } // 建立组件实例 @Override public ReactImageView createViewInstance(ThemedReactContext context) { return new ReactImageView( context, getDraweeControllerBuilder(), getCallerContext()); }
iOS端:
RCT_EXPORT_MODULE() // RCT_EXPORT_MODULE的宏定义 #define RCT_EXPORT_MODULE(js_name) \ RCT_EXTERN void RCTRegisterModule(Class); \ + (NSString *)moduleName { return @#js_name; } \ + (void)load { RCTRegisterModule(self); } // Implemented by RCT_EXPORT_MODULE + (NSString *)moduleName; - (UIView *)view { return [[RCTImageView alloc] initWithBridge:self.bridge]; }
JS端传递属性的形式是:
<RCTImageView {...this.props} // 属性 style={style} resizeMode={resizeMode} tintColor={tintColor} source={sources} />
Android端:
@ReactProp(name = "src") public void setSource(ReactImageView view, @Nullable ReadableArray sources) { view.setSource(sources); }
iOS端:
RCT_EXPORT_VIEW_PROPERTY(blurRadius, CGFloat) RCT_REMAP_VIEW_PROPERTY(defaultSource, defaultImage, UIImage) RCT_CUSTOM_VIEW_PROPERTY(tintColor, UIColor, RCTImageView) { view.tintColor = [RCTConvert UIColor:json] ?: defaultView.tintColor; view.renderingMode = json ? UIImageRenderingModeAlwaysTemplate : defaultView.renderingMode; }
在JS端能够经过UIManager调用UI组件的方法:
UIManager.dispatchViewManagerCommand( findNodeHandle(this), // 找到与NativeUI组件对应的JS组件实例 UIManager.[UI组件名].Commands.[方法], [] // 参数 )
Android端:
// 定义命令号 @Override public @Nullable Map<String, Integer> getCommandsMap() { return MapBuilder.of("focusTextInput", FOCUS_TEXT_INPUT, "blurTextInput", BLUR_TEXT_INPUT); } // 处理命令 public void receiveCommand(T root, int commandId, @Nullable ReadableArray args) { // 根据命令号处理,root为对应View的实例 switch(commandId) { } }
iOS端:
RCT_EXPORT_METHOD(start:(nonnull NSNumber *)reactTag data:data) { [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, TBNAnimationView *> *viewRegistry) { // 找到目标View实例 TBNAnimationView *view = viewRegistry[reactTag]; if (![view isKindOfClass:[TBNAnimationView class]]) { RCTLogError(@"Invalid view returned from registry, expecting TBNAnimationView, got: %@", view); } else { // 调用View的方法 [view start:data]; } }]; }
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端使用的DOM位置上。
(底层实现分析参见:React-Native 渲染实现分析)