生命周期
组件的生命周期分成三个状态:
Mounting
装载已插入真实 DOM
Updating
正在被重新渲染
Unmounting
已移出真实 DOM
Mounting-装载
1.1 constructor
构造函数,在组件挂载之前调用一次。返回值将会作为 this.state 的初始值。
- 第一条语句必须是
super(props)
。 - constructor将在任意一个RN组件被加载之前优先调用,并且只会调用一次。
- 该函数最大的作用是定义该组件当中需要使用的状态机变量以及函数bind操作 。
constructor(props: props, context: any) {
super(props, context);
//初始化状态变量
this.state = {
name: '张三',
};
//函数绑定
this.login = this.login.bind(this);
}
1.2 UNSAFE_componentWillMount
准备加载组件,这个函数调用时机是在组件创建,并初始化了状态之后,在第一次绘制 render() 之前。可以在这里做一些业务初始化操作,也可以设置组件状态。这个函数在整个生命周期中只被调用一次。
- 函数整个过程中只执行一次。
- 在初始渲染前执行,即在render被调用之前调用。
- 子组件中同样拥有该方法,并会在父组件的
componentWillMount
函数之后
被调用。 - 该函数适合于需要在本地读取一些数据用于显示,那么在render执行前调用是一个很好的时机。
UNSAFE_componentWillMount(): void {
//加载本地数据
AsyncStorage.getItem('data')
.then((listData) => {
//TODO...
}).catch((error) => {
//TODO...
});
}
1.3 render
开始渲染
-
调用该方法,先对状态机变量与属性进行检查。
-
如果开发者不想渲染界面的话,可以在此处返回null或者false。
-
该方法适用于进行界面的JSX代码编写,因此不适合在此处对状态机变量进行修改或者访问服务器。
render() {
return (
<View>
<Text>
姓名:{this.props.content}
</Text>
</View>
);
}
1.4 componentDidMount
函数原型:void componentDidMount();
这个函数调用的时候,其虚拟 DOM 已经构建完成,可以在这个函数开始获取其中的元素或者子组件了。
从这个函数开始,就可以和 JS 其他框架交互了,例如设置计时 setTimeout 或者 setInterval,或者发起网络请求。这个函数也是只被调用一次。这个函数之后,就进入了稳定运行状态,等待事件触发。
- 函数整个过程只会调用一次。
- 在初始渲染完成之后调用,即在render被调用之后调用。
- 子组件中同样拥有该方法,并会在父组件的
componentDidMount
函数之前
被调用。 - 一般情况在这个方法中请求网络是一个不错的选择。
componentDidMount(): void {
//发起网络请求
fetch(url)
.then((response) => response.json())
.then((responseData) => {
//TODO...
})
.catch((error) => {
//TODO...
});
}
Updating-更新
2.1 UNSAFE_componentWillReceiveProps
函数原型:
UNSAFE_componentWillReceiveProps(nextProps);
当属性发生改变或接收到一个新的属性时候,该函数被调用。并接受一个输入参数,类型为Object,存放新的props,原先旧的props仍然可以通过this.props访问。
- 接受一个Object参数,存放新的props,旧的props仍然可通过this.props访问。
- 该函数在RN初次渲染时不会被调用。
- 如果在该函数当中对状态机变量进行了修改,RN不会立即渲染页面,而是会等待该方法执行完毕后一起渲染。
2.2 UNSAFE_componentWillUpdate
函数原型:
boolean UNSAFE_componentWillUpdate(nextProps,nextState);
组件是否需要更新,接受两个参数。根据返回的布尔值来决定是否需要对页面进行重新渲染,如果不进行渲染,那么该方法后续的componentWillUpdate与componentDidUpdate都不会被执行。
- 接受两个参数,根据返回值决定是否需要重新渲染。如果不进行渲染后续的
componentWillUpdate
和componentDidUpdate
都不会执行。 - 该函数默认会返回true。
- 可以在该函数中编写一些逻辑来判断渲染类型,来阻值一些没有必要的重新渲染,达到提升应用运行效率的目的。
2.3 componentWillUpdate
函数原型:
componentWillUpdate(object nextProps, object nextState)
,当shouldComponentUpdate
返回true后立即调用,
- 初始渲染完成之后,重新渲染前会调用这个函数.但是这个函数不能通过this.setSatte再次改变状态机变量的值。
- 该函数无返回值。
- 在该方法中,不应该对状态机变量进行修改。
2.4 componentDidUpdate
函数原型:
componentDidUpdate(object prevProps, object prevState)
,在组件的更新已经同步到 DOM 中之后立刻被调用。
- 该函数会在重新渲染render之后调用,参数是渲染前的props和state,传入上个方法必须的两个参数即可。
Unmounting-(卸载)
3.1 componentWillUnmount
该方法会在RN卸载之前调用,无参无返回值,在该方法中,需要对该组件当中申请或者订阅的某些资源与消息进行释放。
在该方法中执行任何必要的清理,比如无效的定时器,或者清除在 componentDidMount 中创建的 DOM 元素。
RN 变量
State变量
state或props任何一个变化都会引起render重新执行渲染。
state表示一个组件内部自身状态,只能在自身组件中存在。
由于state任何属性的改变都会导致UI重绘,而UI重绘会消耗系统资源,所以在封装可复用的组件时,尽量不用或少用state,而是通过props将数据传递到组件内部(props在组件内部是不可变的,不会导致UI重绘)。
Props变量
props是父组件中指定,传递给自组件的数据流,且一经指定,在被指定的组件的生命周期中不可更改。
在 React 中信息是单向的。我们维护着组件层次,在其中每个组件都仅依赖于它父组件和自己的状态。通过属性(props)我们将信息从上而下的从父组件传递到子元素。如果一个祖先组件需要自己子孙的状态,推荐的方法是传递一个回调函数给对应的子元素。
成员变量
在RN中如果使用状态机变量存储于UI无关的变量,会导致不必要的判断是否需要重新渲染,从而导致应用性能下降,正确的做法是保存在组件的成员变量中(在构造函数中定义成员变量是一个不错的做法,可保证成员变量有初始值)。
定义
//构造函数中定义成员变量
constructor(props) {
super(props);
//成员变量
this.inputContent = '';
}
使用
<TextInput
placeholder={'登陆账号 '}
onChangeText={(content) => {
//将输入内容赋值给当前的局部变量
this.inputContent = content;
}}
/>
<Button
title="点击2"
onPress={() => {
//输出局部变量
console.log('输入的账号为:' + this.inputContent);
}}
/>
静态变量
React Native 允许组件有静态变量、静态成员函数。它们的作用与 C++,Java 中的类静态变量、类静态成员函数基本一样。
定义
export default class RegisterComponent extends Component {
//定义类的静态成员变量
static loginName = '';
static loginPwd = '';
//定义类的静态成员函数
static login() {
console.log('loginName:' + this.loginName);
console.log('loginPwd:' + this.loginPwd);
}
}
使用
访问方式
类名.变量名
、类名.函数名
,不能以this
调用。
export default class LoginComponent extends Component {
render() {
return (
<View>
<TouchableOpacity
style={loginStyle.loginButtonTouchable}
activeOpactiv="0"
onPress={() => {
//直接类名.静态变量/静态函数名调用
RegisterComponent.loginName = 'zcmain';
RegisterComponent.loginPwd = '99999';
RegisterComponent.login();
}}>
</TouchableOpacity>
</View>
);
}
RN混合开发
1. RN调用原生方法
Android端实现
步骤
创建继承
ReactContextBaseJavaModule
的Module类。1.1 实现
getName
方法,返回一个字符串,该字符串是标记导出的原生模块,供RN端调用 (不要使用
ToastAndroid
作为导出名字,会与RN内置的冲突)。1.2 定义并实现使用
ReactMethod
注解的Java方法,且方法返回值只能是void
类型(RN跨语言访问是异 步的,所以RN想要返回值唯一办法是使用回掉函数或者发送事件
参见:《Android原生调用RN方法实现》。
创建实现
ReactPackage
接口的RN包管理器类,并将前一步创建的Module类注册到该包管理器的createNativeModules
方法中。将创建的RN包管理器类添加到Application中的
getPackages
方法中。
实现
-
原生创建一个类继承
ReactContextBaseJavaModule
类,实现getName
方法,用于提供给RN侧调用原生代码的接口名称。并使用@ReactMethod
注解定义给RN侧调用的函数/** *1.创建继承ReactContextBaseJavaModule的Module类 */ public class AndroidNativeModule extends ReactContextBaseJavaModule { ReactApplicationContext context; public AndroidNativeModule(@NonNull ReactApplicationContext reactContext) { super(reactContext); this.context = reactContext; } //1.1 实现getName方法,返回一个字符串(该字符串是标记导出的原生模块) @NonNull @Override public String getName() { return "AndroidNativeModule"; } /** *1.2 定义并实现使用ReactMethod注解的Java方法,且方法返回值只能是void类型。 */ //提供给RN调用的方法(无参) @ReactMethod public void callAndroidMethod() { Log.d("AndroidNativeModule","from the RN call..."); } //提供给RN调用的方法(有参) @ReactMethod public void callAndroidParamsMethod(String name, int age) { Log.d("AndroidNativeModule", "from the RN call name:" + name + "\tage:" + age); } }
-
原生创建一个类实现
ReactPackage
包管理器接口。实现该接口的createNativeModules
函数,将原生代码创建的AndroidNativeModule
添加到RN包管理器中。/** *2.创建类实现ReactPackage接口,实现createNativeModules方法将之前创建的 AndroidNativeModule * 添加到该包管理器中。 */ public class AndroidNativePackage implements ReactPackage { @NonNull @Override public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) { //将之前创建的 AndroidNativeModule 添加到该包管理器中 List<NativeModule> nativeModules = new ArrayList<>(); nativeModules.add(new AndroidNativeModule(reactContext)); return nativeModules; } @NonNull @Override public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) { //返回空集合 return Collections.emptyList(); } }
-
将原生创建的
AndroidNativePackage
包管理器实例添加到在Application中的getPackages
函数中。@Override protected List<ReactPackage> getPackages() { List<ReactPackage> packages = new PackageList(this).getPackages(); //将创建原生的包管理器添加到ReactPackage列表中 packages.add(new AndroidNativePackage()); return packages; }
-
RN侧调用
//1.导入NativeModules组件 import { NativeModules } from 'react-native'; //2.使用 NativeModules.原生导出模块.函数名称。 invokerNative() { //调用原生无参函数 NativeModules.AndroidNativeModule.callAndroidMethod(); //调用原生有参函数 NativeModules.AndroidNativeModule.callAndroidParamsMethod('Android',30); }
IOS端实现
步骤
创建头文件(
*.h
)引入RCTBridgeModule.h
头文件,来使用RCTBridgeModule
协议。创建实现文件(
*.m
):2.1 使用
RCT_EXPORT_MODULE(js_name)
宏,标记导出的原生模块。如果你不指定名称,默认 就会使用这个 Objective-C 类的名字。如果类名以 RCT 开头,则 JavaScript 端引入的模块名会自动
移除这个前缀。
2.2 使用
RCT_EXPORT_METHOD()
宏,导出实现的原生函数,且函数返回值只能是void
(RN跨语言访问是 异步的,所以RN想要返回值唯一办法是使用回掉函数或者发送事件
参见《IOS原生调用RN方法实现》。
PS:React Native 还定义了一个
RCT_REMAP_METHOD()
宏,它可以指定 JavaScript 方法名。所以当原生 端存在重载方法时,可以使用这个宏来避免在 JavaScript 端的名字冲突。
实现
-
创建原生头文件(.h)
//1.创建头文件 #import <Foundation/Foundation.h> //导入RCTBridgeModule.h文件 #import "RCTBridgeModule.h" @interface Hello : NSObject <RCTBridgeModule> @end
-
创建头文件实现文件(.m)
//2.创建实现文件 #import <Foundation/Foundation.h> #import "Hello.h" @implementation Hello //2.1 使用RCT_EXPORT_MODULE(js_name)宏,标记导出的原生模块。 RCT_EXPORT_MODULE(IOSNativeModule); /** *2.2 使用RCT_EXPORT_METHOD()宏,导出实现的原生函数,且函数返回值只能是void */ //导出供RN调用方法(无参) RCT_EXPORT_METHOD(callIosMethod){ NSLog(@"from the RN call..."); } //导出供RN调用方法(有参) RCT_EXPORT_METHOD(callIosParamsMethod:(NSString *)name age:(int)age) { NSLog(@"from the RN call name%@ age%d",name,age); } @end
-
RN侧实现调用
//导入 NativeModules 组件 import { NativeModules } from 'react-native'; //使用 NativeModules.原生导出模块.函数名 invokerNative() { //调用原生无参函数 NativeModules.IosNativeModule.callIosMethod(); //调用原生有参函数 NativeModules.IosNativeModule.callIosParamsMethod('IOS', 26); }
2. 原生调用RN方法
- 被动调用:原生模块还支持一种特殊的参数——回调函数。它提供了一个函数来把返回值传回给 JavaScript,即RN先调用原生函数,然后原生通过回掉函数返回数据至RN。后引申为Promise机制实现。
- 主动调用:原生模块主动向 JavaScript 发送事件通知。最好的方法是继承
RCTEventEmitter
,实现suppportEvents
方法并调用self sendEventWithName:
。
Android端实现
Androd端通过Promise机制被动发消息到RN,实现上与RN调用Android端方法实现相似,唯一不同点是如果桥接原生方法的最后一个参数为Promise
对象,则对应的JS方就会返回一个Promise对象。
步骤
相同步骤参考《RN调用Android原生方法实现》
不同点原生的Module中:
2.1 使用
ReactMethod
注解的Java方法,最后一个参数为Promise
对象。
实现
-
创建原生Module、包管理器、添加到Application中步骤略(参考:《RN调用Android原生方法实现》)。
-
原生Module中
ReactMethod
注解的方法,最后一个参数为Promise
对象//提供给RN调用的方法,且通过Promise对象返回数据到RN @ReactMethod public void callAndroidParamsMethodAcceptBackMsg(String fromRnMsg, Promise promise) { Log.d("AndroidNativeModule", "RN:" + fromRnMsg); String toRnMsg = "Android:Hello RN Nice to meet you! !"; if (!TextUtils.isEmpty(toRnMsg)) { //通过promise的resolve返回正常数据 promise.resolve("\n\n" + fromRnMsg + "\n" + toRnMsg); } else { //通过promise的reject返回异常 promise.reject("11", "msg cannot be empty!"); } }
-
RN测调用
//NativeModules.原生导出模块.原生注解方法名称 NativeModules.AndroidNativeModule .callAndroidParamsMethodAcceptBackMsg('RN:Hello Android!') .then((response) => { Alert.alert('Title', response); }).catch((error) => { Alert.alert('Title', error.message); });
主动调用(RCTEventEmitter事件
)
即使没有使用Promise机制被RN被动调用,原生模块也可以主动给RN发送事件通知。最简单办法是通过RCTDeviceEventEmitter
,这可以通过ReactContext
来获得对应的引用。然后调用其ReactContext.getJSMethod(RCTDeviceEventEmitter.class).emit(事件名称,data参数)
来发送数据即可。
注意:使用emit发送的消息数据需要包装成WritableMap对象,否则会出现异常
使用emit发送的消息数据需要包装成WritableMap对象,否则会出现异常
使用emit发送的消息数据需要包装成WritableMap对象,否则会出现异常
步骤
创建原生Module、包管理器、添加到Application步骤略(参考:《RN调用Android原生方法实现》)。
我们通常封装成一个工具类
1.1 内部持有一个ReactContext对象的引用。
1.2 实现原生向RN发送消息的方法
然后在原生创建的RN包管理器的
createNativeModules
方法中初始化该工具类。RN端配置:
4.1 导入
NativeEventEmitter
、NativeModules
组件4.2 通过
NativeModules.原生导出模块
实例化NativeEventEmitter
对象4.3 调用
NativeEventEmitter
的addlistener(事件名,回调函数)
来监听事件 (推荐在
UNSAFE_componentWillMount
中开启事件订阅监听)。4.4 别忘记最后取消订阅(推荐在
componentWillUnmount
中取消订阅)。
实现
-
创建原生Module、包管理器、添加到Application步骤略(参考:《RN调用Android原生方法实现》)。
-
创建通信工具类
//1.创建原生向RN主动发送消息工具类 public class DeviceEventEmitterUtil { //1.1内部持有ReactContext对象引用 private static ReactContext reactContext; public DeviceEventEmitterUtil(ReactContext reactContext) { this.reactContext = reactContext; } /** * 1.2原生向RN发送消息 * @param eventName 事件名称 * @param params 参数(注意包装成WritableMap对象) */ public static void sendMsgToRn(String eventName, WritableMap params) { reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) .emit(eventName, params); } }
模拟客户端调用该方法
void sendMsgToRn() { try { //模拟订单信息(需要包装成WritableMap对象) WritableMap orderParams = Arguments.createMap(); orderParams.putString("orderTime", "20191219154030"); orderParams.putString("orderAmount", "18888.09"); orderParams.putString("orderName", "MacBook Pro"); //发送提交订单消息到RN DeviceEventEmitterUtil.sendMsgToRn("order", orderParams); //模拟支付信息(需要包装成WritableMap对象) WritableMap payParams = Arguments.createMap(); payParams.putString("orderId", "90888878789"); //发送支付消息到RN DeviceEventEmitterUtil.sendMsgToRn("pay", payParams); } catch (Exception e) { Log.e("error", e.getMessage()); } }
-
在原生创建的包管理器的
createNativeModules
方法中实例化(注册)该工具类。
-
RN侧实现
//4.1 导入NativeEventEmitter、NativeModules组件 import { NativeModules, NativeEventEmitter, } from 'react-native'; //4.2 通过【NativeModules.原生导出模块】实例化NativeEventEmitter对象 const androidEventEmitter = new NativeEventEmitter( NativeModules.AndroidNativeModule); /** * 4.3 监听原生主动发送过来的消息 */ listenerNativeMsg() { //监听原生的order消息事件 this.subScriptOrder = androidEventEmitter.addListener('order', (data) => { Alert.alert('order:', JSON.stringify(data)); }); //监听原生的pay消息事件 this.subScriptPay = androidEventEmitter.addListener('pay', (data) => { Alert.alert('pay:', JSON.stringify(data)); }); } //4.4 开启事件订阅(建议在UNSAFE_componentWillMount中) UNSAFE_componentWillMount(): void { console.log('开启订阅'); this.listenerNativeMsg(); } //4.5取消事件订阅(建议在componentWillUnmount中) componentWillUnmount(): void { console.log('取消订阅'); this.subScriptOrder.remove(); this.subScriptPay.remove(); }
IOS端实现
IOS端通过Promise机制被动发送消息到RN,实现上与RN调用IOS端方法实现相似,唯一不同点是参数最后两个分别是Promise的RCTPromiseResolveBlock
和RCTPromiseRejectBlock
类型。
步骤
相同步骤参考《RN调用IOS原生方法》
不同点实现文件中:
2.1 使用
RCT_EXPORT_METHOD
宏,导出的函数最后两个参数为RCTPromiseResolveBlock
和
RCTPromiseRejectBlock
。
实现
-
创建头文件略(参考《RN调用IOS原生方法》)
-
创建头文件实现文件(.m)
//2.创建实现文件 #import <Foundation/Foundation.h> #import "Hello.h" @implementation Hello //使用RCT_EXPORT_MODULE(js_name)宏,标记导出的原生模块。 RCT_EXPORT_MODULE(IOSNativeModule); //2.1 导出供RN调用的方法(有参且原生通过Promise机制返回数据到RN) RCT_EXPORT_METHOD(callIosParamsMethodAcceptBackMsg:(NSString *)msg resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { NSString *toRnMsg = @"IOS:Hello RN Nice to meet you! !"; if (![msg isEqualToString:@""]) { NSString *response =[NSString stringWithFormat:@"\n\n%@\n%@",msg,toRnMsg]; resolve(response); } else { reject(@"warning", @"msg cannot be empty!", nil); } } @end
-
RN侧调用
//1.导入NativeModules组件 import {NativeModules} from 'react-native'; //2.使用NativeModules.原生导出模块.方法名 NativeModules.IosNativeModule .callIosParamsMethodAcceptBackMsg('RN:Hello IOS !') .then((response) => { Alert.alert('Title', response); }).catch((error) => { Alert.alert('Title', error.message); });
即使没有使用Promise机制被RN被动调用,原生模块也可以主动给RN发送事件通知,最好的办法是继承RCTEventEmitter
,实现supportEvents
方法并调用self sendEventWithName:
实现。
步骤
创建头文件(
.h
)继承RCTEventEmitter
(需要导入RCTEventEmitter.h
和RCTBridgeModule.h
)创建实现文件(
.m
):2.1 使用
RCT_EXPORT_MODULE(js_name)
宏,导出原生模块。2.2 实现
supportedEvents
方法并且返回发送事件的名称(后续原生通过该事件名称发送消息到RN, RN端通过该事件名称接收消息)。
2.3 原生调用
[self sendEventWithName:事件名称,body:]
来发送消息到RN端。RN端使用:
3.1 导入
NativeEventEmitter
和NativeModules
组件。3.2 使用解构赋值原生导出模块实例化出
NativeEventEmitter
对象。3.3 构造函数中声明事件订阅的局部变量。
3.4 使用
NativeEventEmitter
实例对象调用addListener(evnetName,listenerFunaction)
指定原 生
supportedEvents
函数中声明的事件名称,以及回掉函数即可。3.5 在需要地方进行事件订阅(推荐在
UNSAFE_componentWillMount
中进行订阅)。3.6 在需要地方取消订阅(
componentWillUnmount
中取消订阅)。
实现
-
创建头文件(
.h
)//1.创建头文件 #import <Foundation/Foundation.h> //导入RCTBridgeMOdule和RCTEventEmitter头文件 #import <React/RCTBridgeModule.h> #import <React/RCTEventEmitter.h> //继承RCTEventEmitter父类 @interface SendMsgToRn :RCTEventEmitter<RCTBridgeModule> @end
-
创建实现文件(.m)
//2.创建头文件 #import "SendMsgToRn.h" @implementation SendMsgToRn //2.1使用RCT_EXPORT_MODULE(js_name)宏导出模块 RCT_EXPORT_MODULE(IosNativeModule) //2.2实现RCTEventEmitter类中的supportedEvents方法,返回事件名称(可以定义多个事件名称) - (NSArray<NSString *> *)supportedEvents{ return @[@"order",@"pay"]; } //2.3原生主动向RN发送事件方法封装 -(void)sendMsgToRn:(NSString *)eventName eventData:(NSObject *)params{ NSLog(@"发送事件名称%@,发送事件参数%@",eventName,params); //最终调用父类(RCTEventEmitter)的sendEventWithName方法指定事件名称来发送消息 [self sendEventWithName:eventName body:params]; } @end
模拟客户端调用该类方法向RN发送消息
-(void)sendMsgToRn{ NSObject *order = @{@"orderTime":@"20191218125942", @"orderAmount":@"14888.88", @"orderName":@"MacBook Pro"}; //调用提交订单方法 [self sendMsgToRn:@"order" eventData:order]; //调用支付方法 NSObject *payInfo = @{@"orderId":[NSNumber numberWithInteger:9876563541]}; [self sendMsgToRn:@"pay" eventData:payInfo]; }
-
RN侧实现
//3.1 导入NativeModules、NativeEventEmitter组件 import { NativeModules, NativeEventEmitter, } from 'react-native'; //3.2 通过【NativeModules.原生导出模块】实例化NativeEventEmitter对象 const iosEventEmitter = new NativeEventEmitter(NativeModules.IosNativeModule); export default class MyCommonent extends Component{ //3.3 构造方法中定义局部订阅变量 constructor(props) { super(props); let subscribeOrder; let subscribePay; } //3.4 使用实例化后的NativeEventEmitter对象调用addListener方法(指定事件名称和回调函数) listenerNativeMsg(){ //监听原生的order事件 this.subscribeOrder = iosEventEmitter.addListener('order',(data)=>{ Alert.alert('order:', JSON.stringify(data)); }); //监听原生的pay事件 this.subscribePay = iosEventEmitter.addListener('pay',(data)=>{ Alert.alert('order:', JSON.stringify(data)); }); } //3.5 UNSAFE_componentWillMount中开启订阅 UNSAFE_componentWillMount(): void { console.log('开启订阅'); this.listenerNativeMsg(); } //3.6 别忘记在componentWillUnmount声明周期方法中取消订阅 componentWillUnmount(): void { console.log('取消订阅'); this.subscribeOrder.remove(); this.subscribePay.remove(); } }
RN组件创建方式
ES6方式创建
//ES6方式创建并导组件
export default class ComponentA extends Component{
render(){
return(
<Text>ES6方式创建组件</Text>
);
}
}
ES5方式创建(该方法最新版本已经废弃会报错)
- Es5创建的组件,其成员函数会自动绑定this,也就是说,在任何时候,我们通过this拿到的对象,都是指向当前的组件类的;
- 配置组件属性类型propTypes及其默认props属性defaultProps ,Es5创建的组件,其propTypes及其默认props属性defaultProps会作为组件实例的属性来进行配置,其中defaultProps是通过组件的getDefaultProps方法来获取的;
- 配置组件的初始状态state ,Es5创建的组件,其初始状态state是通过getInitialState方法来进行配置的
//ES5创建组件
//注意:React.createClass从0.48开始被删除,可以使用
//create-react-class 包中的 createReactClass 方法替代
var Hellocommpents=createReactClass({
render(){
return
<text style="{{fontSize:15,backgroundColor:'green'"> Hellocommpents</text>
}
} )
module.exports=Hellocommpents;
函数式创建
- 组件不会被实例化,整体渲染性能得到提升;
- 组件不能访问this对象;
- 组件无法访问生命周期的方法;
- 组件只能访问输入的props;
//函数方式定义组件并导出
function Hellocommpents() {
//注意结尾需要带 ";"
return <text style="{{fontSize:15,backgroundColor:'green'"> Hellocommpents</text>;
}
module.exports=Hellocommpents;
或者
//或者 函数方式定义组件并导出
const Hellocommpents = ({title,onClick})=>(
<View>
<Text>标题{title}</Text>
<Button
title={'确认'}
onPress={onClick}/>
</View>
)
//定义属性约束
Hellocommpents.propTypes = {
onClick: PropTypes.func.isRequired,
text: PropTypes.string.isRequired
}
//导出组件
export default Hellocommpents;
RN中PropTypes属性确认使用
什么是属性确认?
使用React-native创建的组件是可以复用的,所以我们封装的组件可以用在其他项目或给项目组其他人使用。但是别人可能对这个组件不熟悉,经常忘记使用某些属性,或者某些属性传递的数据类型有误。因此我们可以在开发React Native自定义组件时,可以通过PropTypes属性确认来声明这个组件需要哪些属性。这样,如果在调用这个自定义组件时没有提供相应的属性,则会在手机与调试工具中弹出警告信息,告知开发者该组件需要哪些属性。
简言之使用Prop-Types属性确认优点:
- 可以实现类型检查,当传入错误的属性值,会报警告,但是不会报错;
- 用PropTypes定义属性,外界使用的时候会有提示;
注意:
- 为了保证React Native代码高效运行,属性确认仅在开发环境中有效。也就是说,正式发布的App运行时是不会进行检查的。
- PropTypes必须要用static声明,否则无效果(仅在组件是通过ES6创建时候使用,如果通过函数创建则属性确认通过组件名称.propTypes={}来声明,具体可参考《RN通过函数创建组件》)
- PropTypes只能用于React框架的自定义组件,默认JS是没有的,因为它是React框架中的。
安装属性确认prop-types组件
- 安装prop-types
npm install --save prop-types
#或者
yarn add prop-types - 使用(需要使用的js文件中引入)
import PropTypes from ‘prop-types’;
属性确认prop-types中语法
-
要求属性是指定的JavaScript基本类型
属性名: PropTypes.array, //指定属性为数组类型 属性名: PropTypes.bool, //指定属性为布尔类型 属性名: PropTypes.func, //指定属性为函数类型 属性名: PropTypes.number, //指定属性为数值类型 属性名: PropTypes.object, //指定属性为Object类型 属性名: PropTypes.string, //指定属性为字符串类型
-
要求属性是可渲染的节点
属性名:PropTypes.node,
-
要求属性是某个React元素
属性名:PropTypes.element,
-
要求属性是某个指定类的实例
属性名:PropTypes.instanceOf(NameOfAClass),
-
要求属性取值为特定的几个值
属性名:PropTypes.oneOf(['value1','value2']),
-
要求属性为指定类型中的一个
属性名:PropTypes.oneOfType([ PropTypes.bool, PropTypes.number, PropTypes.instanceOf(NameOfAClass), ])
-
要求属性为指定类型的数组
属性名: PropTypes.arrayOf(PropTypes.number),
-
要求属性是一个有特定成员变量的对象
属性名: PropTypes.objectOf(PropTypes.number),
-
要求属性是一个指定构成方式的对象
属性名: PropTypes.shape({ color: PropTypes.string, fontSize: PropTypes.number, }),
-
属性可以是任意类型
属性名: PropTypes.any
PS:上述十种语法中都可以通过在后面加上isRequired
声明它是必需的。
属性名: PropTypes.array.isRequired,
属性名: PropTypes.any.isRequired,
属性名: PropTypes.instanceOf(NameOfAClass).isRequired,
给自定义属性设置初始值
- 如果想要给自定义属性添加默认初始值,需要使用defaultProps
- 注意:也是需要用static修饰
static defaultProps = {
name: 'scottDefault',
age: 12
}
示例
import React,{Component} from 'react';
import {...} from 'react-native';
//定义组件并导出
export default class CompontentA extends Component{
//定义常量
const NAV_BAR_HEIGHT_ANDROID=50; //Android的NavigationBar高度
const NAV_BAR_HEIGHT_IOS = 44; //iOs的NavigationBar高度
const STATUS_BAR_HEIGHT=20; //状态栏高度
const StatusBarShape={ //状态栏形状的约束,用来属性确认
backgroundColor:PropTypes.string,
barStyle:PropTypes.oneOf('default' , 'light-content' , 'dark-content'),
hidden:PropTypes.bool,
}
//定义属性约束使用 static 修饰
static propTypes = {
style: PropTypes.object, //NavigationBar样式约束
title: PropTypes.string, //标题约束:文本类标题
titleView:PropTypes.element, //标题的样式约束
hide:PropTypes.bool, //是否隐藏NavigationBar
leftButton:PropTypes.element, //NavigationBar左侧按钮
rightButton:PropTypes.element, //NavigationBar右侧按钮
staturBar: PropTypes.shape(StatusBarShape), //状态栏
};
//给组件设置默认值使用static修饰
static defaultProps ={
staturBar:{
barStyle: 'light-content',
hidden:false,
}
}
render(
//获取用户设置的状态栏的样式,用于下面的取出
let statusView = <View
style={[styles.staturBar , this.props.staturBar]}>
//取出用户设置的状态栏的样式
<StatusBar {...this.props.staturBar}/>
</View>
let titleView = this.props.titleView ?
this.props.titleView :
<Text style={styles.title}>{this.props.title}</Text>;
let contentView = <View style={styles.navBar}>
{this.props.leftButton}
<View style={styles.titleViewContainer}>
{statusView}
{titleView}
</View>
{this.props.rightButton}
</View>;
return(
<View style={styles.container}>
{contentView}
</View>
);
}
}
RN组件引用
构建完你的组件之后,你可能会想要去寻求一个办法,来直接调用你在
render()
返回的组件的实例的方法。在大部分情况下,这应该不是必须的,因为在响应式数据流中,你要输出一些数据,你应该在render()
中给子组件传递最新的属性。不过,在某些特殊情况下,直接操作组件实例的方法还是必要或者有利的。所以React提供了一个打破限制的办法,这就是refs
。refs
(reference,引用)在以下时候特别有用:当你需要直接操作一个组件渲染的DOM标记(譬如要调整它的绝对位置),或者在一个大型的非React应用中使用一个React组件,或者是把你已有的代码库在React中复用。
让我们来看看怎么获取一个ref
render() {
return(
//ES6箭头函数表示
<TextInput ref={(c)=>{this._input=c}}/>
);
}
//使用
componentDidMount() {
this._inputText.focus();
}
RN父子组件传值
例如:A组件是登陆页面,B组件为自定义的输入框属于A组件的一个子组件,那么我们在B组件中的输入值在父组件A中如何获取呢?可通过props方式通过回调函数将子组件的数据回传到父组件中。
实现原理:
- 父组件通过属性props传递一个回调函数到子组件中。
- 子组件的TextInput输入组件onTextChange函数中绑定父组件传递过来的回调函数即可。
//自定义子组件
import React, {Component} from 'react';
import {
View,
Text,
TextInput,
} from 'react-native';
export default class InPutComponent extends Component {
render() {
return (
<View style={style.rootView}>
<Text
style={{backgroundColor: '#999', textAlign: 'center', flex: 1}}>
{this.props.title}
</Text>
<TextInput
style={{flex: 3}}
placeholder={this.props.placeholder}
//onChangeText绑定父组件传递过来的回调函数,将数据返回父组件
onChangeText={(data)=>{this.props.onChangeTextCallBack(data)}}/>
</View>
);
}
}
//父组件
import React, {Component} from 'react';
import{View} from 'react-native';
import InPutText from '../../common/InPutComponent';
export default class LoginComponent extends Component {
render() {
return (
<View>
{/*父组件中使用子组件*/}
<InPutText
title='账号'
placeholder='请输入账号'
{/*通过props传递一个回调函数给子组件*/}
onChangeTextCallBack={(data,index) => {
console.log('index:' + index);
}}/>
</View>
}
这样当子组件输入框中的内容发生改变后会通过回调函数将数据返回到父组件的回调函数中。
扩展
ES6 Promise异步机制与ES7 async/await机制
Promise介绍
Promise是什么?
Promise是ES6中新增的特性。在以往 JavaScript 中,所有代码都是单线程的,也就是同步执行的。而 Promise
就为异步编程
提供了一种解决方案。
Promise 对象是由关键字 new 及其构造函数来创建的。
const promise = new Promise(((resolve, reject))=>{
// do something here ...
if (success) {
resolve(value); // fulfilled成功
} else {
reject(error); // rejected失败
}
});
由上述代码我们可知:
- 该构造函数接收两个函数作为参数,分别是
resolve
和reject
。 - 当异步操作执行成功后,会将异步操作结果作为参数传入
resolve
函数并执行,此时Promise
对象状态从pending
变为fulfilled
; - 失败则会将异步操作的错误作为参数传入
reject
函数并执行,此时Promise
对象状态从pending
变为rejected
;
接下来我们通过该对象的then
方法,分别指定resolved状态和rejected状态的回调函数
promise.then(function(value) {
// success
}, function(error) {
// failure
});
//或者
promise.then((value) => {
//success
}, (error)=> {
//failure
});
then
方法可以接收两个回调函数作为参数,第一个回调函数就是fulfilled
成功状态时调用;第二个回调函数就是rejected
失败时调用。这边的第二个参数是可选的,不一定要提供。
Promise 主要API方法
-
Promise.all(iterable)
-
参数
iterable 必须是一个可迭代对象,如 Array 或 String。 -
返回值
一个新的Promise
实例 -
Promise.all 的使用
如果传入的参数中存在不是
Promise
实例,则会先调用Promise.resolve
,将其转为Promise
实例,再进一步处理。var p1 = Promise.resolve(3); var p2 = 1337; //p2不是Promise对象,会通过Promise.resolve转换 var p3 = new Promise((resolve, reject) => { setTimeout(resolve, 100, 'foo'); }); Promise.all([p1, p2, p3]).then(values => { console.log(values); }); // [3, 1337, "foo"]
-
-
Promise.resolve(value)
-
参数
-
如果参数是一个
Promise
实例,则返回值是原封不动的返回该实例;//通过resolve指定一个字符串为参数,返回一个Promise实例 var original = Promise.resolve('我在第二行'); //通过resolve指定一个Promise实例为参数,会将original Promise实例原封不动的返回; var cast = Promise.resolve(original); //比较两个Promise是否是同一个实例 console.log('original === cast ? ' + (original === cast)); cast.then((value)=>{ console.log('value: ' + value); }); // "original === cast ? true" // "value: 我在第二行"
-
如果参数是普通数据:[String|Array|Object|Number],直接将传入参数当最终结果并返回一个新的
Promise
;//通过resolve指定Number返回Promise实例 let p = Promsie.resolve(123); //通过then方法指定回调函数 p.then((num)=>{ console.log(num); }); // 123
-
如果不指定参数,直接返回一个
resolved
状态的Promise
对象//通过resovle不指定参数返回一个Promise实例 let p = Promsie.resovle(); //通过then方法回调函数接受的是无 p.then(()=>{ // do something here... })
-
-
-
Promise.reject(reason)
-
参数:表示被拒绝的原因;
传入的参数会原封不动的作为 reject 函数的理由,并不会因为传入的参数 Promise 或者是 thenable 对象而有所不同;
-
返回值:一个含有
reason
的状态为rejected
的Promise
-
async/await介绍
async/await是什么?
async/await
是ES7提出的一种异步解决方案,相比较Promise
对象then 函数的嵌套Async/Await 可以让你轻松写出同步风格的代码同时又拥有异步机制,更加简洁,逻辑更加清晰。
async/await特性
- 自动将常规函数转换成Promise,
返回值也是一个Promise对象
; - 只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数;
- await只能在async函数内部使用,用在普通函数里就会报错;
- 在async函数里,无论是Promise reject的数据还是逻辑报错,都会被默默吞掉,所以
最好把await放入try{}catch{}中,catch能够捕捉到Promise对象rejected的数据或者抛出的异常
;
使用
- 注意
- await 表示在这里等待promise返回结果了,再继续执行。
await 后面跟着的应该是一个promise对象
(当然,其他返回值也没关系,只是会立即执行,不过那样就没有意义了…)
//定义一个使用async修饰的函数,返回时一个Promise对象
const fetchPostTest = async (url, params) => {
console.log('url:' + url);
console.log('params:' + JSON.stringify(params));
//模拟请求返回数据(await后面跟着一个Promise对象,否则setTimeout无效会立即返回)
return await new Promise((resolve, reject) => {
setTimeout(() => {
resolve('返回数据了');
}, 5000);
});
}