React-Native 组件开发方法
前言
React Native的开发思路是通过组合各种组件来组织整个App,在大部分情况下通过组合View、Image等几个基础的组件,可以非常方便的实现各种复杂的跨平台组件,不过在需要原生功能支持、对性能有要求的情况下还是需要进行一定的原生的开发,合理的组件实现方式可以降低使用和跨平台的成本。
(底层实现分析参见:React-Native 渲染实现分析,本文仅讨论组件开发方法)
React Native组件开发
RN的组件开发有几种方式,JS组件、Native功能组件、NativeUI组件。JS组件是仅使用React Native自带的组件进行组合实现的组件,优势是跨平台方便,但受限于RN实现的效果,一些复杂需求无法实现。而Native组件要强大许多,不过需要考虑平台差异,提供统一的接口难度要大一些。
JS组件(熟悉React可略过)
JS组件是指仅靠RN本身自带的组件开发的组件,是RN下最基础的开发方式,大部分的组件都是JS组件。最大的好处是可以低成本的跨平台。JS组件的开发体验跟React(Web)一致,区别在基础标签不一样,以及样式定义方式不一样。
一个例子,无网络提示组件:

(例子语言Typescript)
// 组件的属性定义
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组件也定义了组件的生命周期:
实例化
getDefaultProps
组件类型首次实例化时初始化默认props属性,多实例共享getInitialState
实例化时初始化默认state属性componentWillMount
在渲染之前触发一次render
渲染函数,返回DOM结构componentDidMount
在渲染之后触发一次
有需要重新渲染(props变更或者setState改变state时)
componentWillReceiveProps
组件接收到新的props时调用,并将其作为参数nextProps使用,可在此更改组件stateshouldComponentUpdate
判断是否需要更新组件(在首次渲染期间或者调用了forceUpdate方法后,该方法不会被调用)componentWillUpdate
更新渲染前调用render
渲染函数,返回DOM结构componentDidUpdate
更新渲染后调用
销毁
componentWillUnmount
组件移除之前调用
适用场景:
性能要求不高、不需要复杂端上能力的组件。
Native组件
那么在需要端上能力、或性能要求高的时候怎么办呢?这就需要开发Native组件了,Native组件又可以简单的分为功能性组件和UI组件两种。
一般来说,Native的组件开发分为两部分,一部分是Native代码,一部分是与之配合的JS代码(JS端代码开发类似JS组件开发,开放接口,屏蔽实现细节、平台差异),这两部分共同构成一个Native组件。
Native功能组件
功能性组件类似服务开发,以模块的形式提供功能接口(单例),并暴露给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}];
*数据类型:
- iOS下因为有id类型,所以限制不大,跟JS对象一致
- Android下需要手动读取对象值
- Null
- Boolean
- Number
- String
- Map
- Array
NativeUI组件
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}
/>
定义组件名称:
ViewManager:
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];
}
}];
}
Native事件回调JS端:
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;
}
}
View:
实现你想实现的,他会出现在JS端使用的DOM位置上。
(底层实现分析参见:React-Native 渲染实现分析)
React-Native 组件开发方法的更多相关文章
- 《React Native 精解与实战》书籍连载「Android 平台与 React Native 混合开发」
此文是我的出版书籍<React Native 精解与实战>连载分享,此书由机械工业出版社出版,书中详解了 React Native 框架底层原理.React Native 组件布局.组件与 ...
- 《React Native 精解与实战》书籍连载「iOS 平台与 React Native 混合开发」
此文是我的出版书籍<React Native 精解与实战>连载分享,此书由机械工业出版社出版,书中详解了 React Native 框架底层原理.React Native 组件布局.组件与 ...
- taro之React Native 端开发研究
初步结论:如果想把 React Native 集成到现有的原生项目中,不能使用taro的React Native 端开发功能(目前来说不能实现,以后再观察). RN开发有2种模式: 1.一是原生A ...
- React Native移动开发实战-5-Android平台的调试技巧
Android平台的调试和其他平台的调试也很类似,例如:在Android Studio打开的工程中,打开源码MainActivity.java,然后,将鼠标移至代码编辑区的左侧后,单击鼠标即可添加断点 ...
- React Native移动开发实战-3-实现页面间的数据传递
React Native使用props来实现页面间数据传递和通信.在React Native中,有两种方式可以存储和传递数据:props(属性)以及state(状态),其中: props通常是在父组件 ...
- <React Native移动开发实战>-1-React Native的JSX解决方案
JSX并不是一门新的开发语言,而是Facebook提出的语法方案:一种可以在JavaScript代码中直接书写HTML标签的语法糖,所以,JSX本质上还是JavaScript语言. 小知识:语法糖(S ...
- beeshell —— 开源的 React Native 组件库
介绍 beeshell 是一个 React Native 应用的基础组件库,基于 0.53.3 版本,提供一整套开箱即用的高质量组件,包含 JavaScript(以下简称 JS)组件和复合组件(包含 ...
- Android React Native组件的生命周期及回调函数
熟悉android的童鞋应该都清楚,android是有生命周期的,其很多组件也是有生命周期.今天小编和大家分享的React Native组件的生命周期,还不了解的童鞋,赶紧来围观吧 在android开 ...
- React Native组件的结构和生命周期
React Native组件的结构和生命周期 一.组件的结构 1.导入引用 可以理解为C++编程中的头文件. 导入引用包括导入react native定义的组件.API,以及自定义的组件. 1.1 导 ...
随机推荐
- Win10 IIS本地部署MVC网站时不能运行?
异常处理汇总-服 务 器 http://www.cnblogs.com/dunitian/p/4522983.html 部署后出现这个错误: 打开文件目录后发现是可以看见目录的,静态页面也是可以打开的 ...
- PHP-解析验证码类--学习笔记
1.开始 在 网上看到使用PHP写的ValidateCode生成验证码码类,感觉不错,特拿来分析学习一下. 2.类图 3.验证码类部分代码 3.1 定义变量 //随机因子 private $char ...
- Http请求
HTTP报文是面向文本的,报文中的每一个字段都是一些ASCII码串,各个字段的长度是不确定的.HTTP有两类报文:请求报文和响应报文. 请求报文 一个HTTP请求报文由请求行(request line ...
- Oracle数据库该如何着手优化一个SQL
这是个终极问题,因为优化本身的复杂性实在是难以总结的,很多时候优化的方法并不是用到了什么高深莫测的技术,而只是一个思想意识层面的差异,而这些都很可能连带导致性能表现上的巨大差异. 所以有时候我们应该先 ...
- MongoDB系列(一):简介及安装
什么是MongoDB MongoDB 是由C++语言编写的,是一个基于分布式文件存储的开源数据库系统. 在高负载的情况下,添加更多的节点,可以保证服务器性能. MongoDB 旨在为应用提供可扩展的高 ...
- 由js apply与call方法想到的js数据类型(原始类型和引用类型)
原文地址:由js apply与call方法想到的js数据类型(原始类型和引用类型) js的call方法与apply方法的区别在于第二个参数的不同,他们都有2个参数,第一个为对象(即需要用对象a继承b, ...
- 说一说python的牛比与不爽
本人写了10年php了.今年开始改写python了.不是说php有什么不好,php在自己的势力范围内还是很牛比的.只是我已经不能满足于php那两亩地了. 习惯了脚本,所以很自然就过度到python了. ...
- swift 中关于open ,public ,fileprivate,private ,internal,修饰的说明
关于 swift 中的open ,public ,fileprivate,private, internal的区别 以下按照修饰关键字的访问约束范围 从约束的限定范围大到小的排序进行说明 open,p ...
- asp.net core 实战之 redis 负载均衡和"高可用"实现
1.概述 分布式系统缓存已经变得不可或缺,本文主要阐述如何实现redis主从复制集群的负载均衡,以及 redis的"高可用"实现, 呵呵双引号的"高可用"并不是 ...
- VS2010 release编译下进行调试,“当前不会命中任何断点,还没有为文档加载”问题解决方案
在release模式下调试程序,经常出现"当前不会命中任何断点,还没有为文档加载"的问题,可尝试以下方法: 1. 属性 → 配置属性 → C/C++ → 常规 → 调试信息格式:选 ...