1、函数式组件
函数式组件即通过调用一个方法返回一个组件,可以解决组件开发时嵌套过多组件代码臃肿问题。
render() {
return(
{this._renderSectionHeader()}
{this._renderSections()}
{this._renderSectionFooter()}
)
}
将一个组件进行拆分,拆分到多个函数里边,使代码简洁,直观,便于维护
2、获取组件frame
iOS开发中,一个view的frame可以用(x,y,width,height)来设定,这四个属性一旦确定,这个组件的在什么位置怎么显示就固定了。并且想要获取组件的x、y、width、height也很方便,比如view.frame.origin.x,view.frame.size.width。但是在React Native开发中如果想要使用组件当前状态的x、y、width、height就要用下边的回调的方式拿到。
_onLayout(event){
let {x,y,width,height} = e.nativeEvent.layout
...
}
3、gif图片使用
iOS端React Native加载gif图片使用source方式不起作用,换为require方式,使用相对路径加载gif图片即可。
var gifImage = require('./../assets/rn_snatch_order.gif')
4、组件的隐藏和显示
组件的显示和隐藏在iOS中非常简单,如下设置即可
// 方案一
view.hidden = YES; //YES or NO
// 方案二
view.height = 0
React Native中由于组件没有hidden属性,所以需要另觅他途,目前我常用的有两种方式。
4.1 设置state属性
设置state后当前组件刷新,根据state属性值来判断当前组件是否加载可隐藏组件,这是最常用的方法。如果不需要显示组件,将原来return的组件设置为return null
即可。
constructor(props){
super(props)
this.state = {
isShowCountDown:false
}
}
_renderHeaderActivityLabel() {
return(
{this.state.isShowCountDown ?
...
: null}
)
}
但是这种方法有弊端,就是在组件隐藏时相当于没有加载这个组件,如果你想要使用组件中的某个数据或属性,那么抱歉,你拿到的是undefined
。我在开发中就遇到了这种问题,于是寻求到了第二种方案。
4.2 设置height
iOS中可以通过设置view.height = 0
来使组件不显示,那么React Native可以可以呢,答案是肯定的。组件加载的时候,设置style = {{height:0}}
即可。不过要注意,仅仅这么做是不够的的,设置了组件的高度为0并不代表其子组件的高度也为0,如果子组件高度不为0,那么子组件还是会显示出来,我们可以通过设置overflow
属性来解决这个问题,设置后超出当前组件的所有子组件都会被隐藏掉。
viewStyle:{
height:0,
overflow:'hidden'
}
还有一个问题,现在设置了组件的高度为0,要显示的时候怎么办呢,在组件重新渲染的时候将组件设置为需要的高度即可,当然这个组件这个时候是一个固定高度。如果可变高度的,可使用上边第二条获取组件的layout,用一个属性记录下来,刷新的时候设置组件高度,代码如下:
constructor(props){
super(props)
this.height = 0;
}
_renderScrollerView (){
let normalStyle = {height:0,overflow:'hidden'}
let selectStyle = {height:this.height}
return(
{this._renderSections()}
)
}
_onLayout(event){
this.height = event.nativeEvent.layout.height
}
或者不用onLayout
,使用let selectStyle = {}
,不设置height,使用约束布局,使组件自适应高度也可以完成高度变化。
4.3 设置display
React Native 0.47版本开始支持display属性,可用于组件的显示和隐藏。代码如下:
var hiddenStyle = null;
if(hidden) hiddenStyle = {display:'none'}
5、Text控件
5.1 文本居中
Text控件的文本设置justifyContent:'center'
后只能水平居中,如果想要垂直居中,两个方法,第一个设置line-height
;第二个方法外边再包一层View组件,然后设置justifyContent:'center'
。
// 方法一
{text}
// 方法二
{text}
5.2 富文本
在iOS中如果想要实现下面的文本样式还是比较麻烦的,首先我需要知道特殊文本所在的位置和长度,然后针对这块区域去设置相关属性,而React Native使用文本Text嵌套的方式就能很方便的实现
{protectText}
{
this._loadWebView(protectUrl);
}}
>
{global.local == 'xx' ? text : ''}
6、组件间通信
6.1 父组件-->子组件
父组件以props的方式将属性传递给子组件,子组件通过this.props.xxx
拿到父组件属性值。这种情况适用于变量或者方法的传递。如果希望组件接收到的数据类型是有效的,可以通过PropTypes进行检查,此时如果传入的值类型不正确,在开发者模式下会给出warning提示,方便我们进行调试和修复问题。
static propTypes = {
onSelected:PropTypes.func,
name:PropTypes.string,
digitStyle: PropTypes.object,
timeToShow: PropTypes.array,
showSeparator: PropTypes.bool,
size: PropTypes.number
}
6.2 子组件-->父组件
① this.props
方式
这里可以使用6.1中的props方式传递,将function作为属性传递给子组件,子组件在适当时机通过this.props
回调function将值传递回父组件。
_renderHotBuyViewSelected() {
...
return(
{this.buy(token)}}
/>
)
}
buy(token){
...
}
最后在子组件中的适当时机,执行this.props.addCart(token)
即可将token传递回父组件使用。
② ref
方式
ref可以看做是组件被渲染后,指向该组件的一个引用,我们可以通过ref来获取到该组件的实例,然后就可以使用该组件的public属性和方法。
_renderProductSuit(){
return(
this._suitView = ref}
/>
)
}
如代码所示,我们通过当前组件的this._suitView
保存了ref所指向的ProductSuit
实例,后面我们就可以通过this._suitView
使用ProductSuit
组件的public属性和方法。
③ 通知
对于没有相互关联关系或者层级过深的组件间通讯可以考虑使用通知的方式来完成。
// 被通知组件
componentDidMount() {
this.closePorductService = DeviceEventEmitter.addListener('closePorductService',
() => {
this._hiddenAction();
}
);
}
componentWillUnmount() {
this.closePorductService.remove();
}
_hiddenAction() {
...
}
在需要发出通知的组件中的适当时机调用DeviceEventEmitter.emit('closePorductService')
发出通知,被通知组件在加载完毕后会接收到通知,执行相应的操作,在组件被卸载的时候记得要移除对应的通知。
7、组件生命周期
从图中可以看出,React Native的生命周期大致分为三部分:
- 实例化阶段:如图中最上面部分,是组件第一次绘制阶段,在这个阶段组件完成了初始化,渲染和加载。
- 运行阶段:如图中左下面部分,是组件的运行和交互阶段,在这个阶段组件完成了用户交互处理,接收事件,根据state重新渲染界面。
- 销毁阶段:如图中右下面部分,是组件的卸载销毁阶段,在这里做一些组件的清理工作,如取消计时器、网络请求等。
注意:
render()
方法中不要初始化或设置组件的state,因为它会导致当前组件重新渲染,并引发警告或者错误,如果有setState()需求,更好的方法是在componentDidMount()方法内部完成设置操作。
8、引入第三方库
目前React Native自身和一些第三方的库都是用了npm
或yarn
来管理和托管代码,这里我们以yarn
为例。
8.1 依赖库不包含原生代码
① 安装依赖库
yarn add xxx
② 链接依赖库
react-native link //链接所有依赖库
react-native link xxx //只连接某一个库时请使用这个
8.2 依赖库包含原生代码
一般的依赖库完成这些操作就可以通过import导入使用了,但是如果依赖库包含了原生代码实现,那么需要额外的操作把这些文件添加到你的应用,否则应用会在你使用这些库的时候产生报错。我们介绍两种方式,一种是手动链接(不建议使用,坑比较多,也比较麻烦,特别是集成了CocoaPods的项目);另一种是自动链接。
① 手动链接
这里不做过多介绍,如有兴趣,请参考React Native中文网相关介绍(https://reactnative.cn/docs/0.51/linking-libraries-ios/)。
② 自动链接
项目使用了CocoaPods就非常方便了,react-native link xxx
链接依赖库这一步就可以直接跳过了,然后参照依赖库的官方安装文档或者参考下面步骤来完成安装链接即可。
以当前MIStore项目使用的RN第三方库react-native-i18n为例,使用yarn安装依赖库
yarn add react-native-i18n
通过CocoaPods自动安装,将下面代码添加到Podfile文件,执行pod install
即可。
pod 'RNI18n', :path => '../node_modules/react-native-i18n'
目前几乎所有的第三方React Native库都支持Pod
的方式引入。
9、Modal组件遮挡原生页面问题
iOS原生View有着不同的优先级,优先级最高的显示在最前面,优先级低的则因为在后面而被遮挡。React Native页面的弹窗遮罩或蒙层如果想要遮挡native界面的导航栏,则需要使用Modal这种组件,Modal组件能够用来覆盖包含React Native根视图的原生视图(如UIViewController,Activity)。如图所示在Modal组件页面点击链接跳转的native页面也被覆盖遮住了。
既然Modal的优先级很高,以至于native页面都被遮盖住了,那么解决问题的思路就是能不能跳转Web页面的时候隐藏掉弹窗组件,关闭Web页面的时候再把弹窗组件显示出来呢,根据这个思路,设计了以下解决方案,最终解决了遮挡问题。
加载Web页面时发出通知,关闭当前弹窗组件,并且给native通信,给当前的Web页面增加标识protectOpenWebView
,后面关闭Web页面是再根据标识做相应的处理操作。
_loadWebView(url) {
if(url == null || url.length == 0 || url.indexOf('://') == -1) return;
nativeVC.push(url);
DeviceEventEmitter.emit('closePorductSelected');
nativeVC.rnOpenWeb('protectOpenWebView');
}
native的Web页面关闭时,native和React Native通信,发送携带protectOpenWebView
标识的信息,React Native收到后判断标识来展示对应的弹窗
componentDidMount() {
this.openSubscription = NativeAppEventEmitter.addListener('EventReminder',
(reminder) => {
let identify = reminder.identify;
if(identify && identify == 'protectOpenWebView') {
this._showActionSheet(ActionSheetType.ActionSheetTypeProductSelected);
}
nativeVC.rnOpenWeb('');//重置标识
}
);
this.closePorductSelected = DeviceEventEmitter.addListener('closePorductSelected',
() => {
this._hiddenAction();
}
);
}
componentWillUnmount() {
this.openSubscription.remove();
this.closePorductSelected.remove();
}
10、React Native获取native变量
10.1 回调
一般情况下,在加载React Native页面的时候可以通过传值的方式把需要的参数和值传递过来,但是有些初始化操作在每个js文件加载的时候就开始做了,这个时候是获取不到传递进来的参数值的,比如下面代码:
var regionCode = global.local; //执行后regionCode为null
if(regionCode == 'in') {
I18n.locale = 'en';
} else if(regionCode == 'hk') {
I18n.locale = 'zh_HK';
} else if(regionCode == 'tw') {
I18n.locale = 'zh_TW';
}
所以说务必使用同步方法在拿到native的国家code后直接给I18n.locale赋值,因此这里使用了回调的方式,在加载React Native代码的时候去和native通信,拿到国家code后通过回调方式传回来直接赋值。
React Native代码
nativeModule.getAppRegionCode((regionCode)=>{
if(regionCode == 'in') {
I18n.locale = 'en';
} else if(regionCode == 'hk') {
I18n.locale = 'zh_HK';
} else if(regionCode == 'tw') {
I18n.locale = 'zh_TW';
}
})
native代码
RCT_EXPORT_METHOD(getAppRegionCode:(RCTResponseSenderBlock)callBack){
if(callBack){
callBack(@[[MIAppContext sharedInstance].currentRegion.regionCode]);
}
}
10.2 导出常量
Native模块可以在运行时向React Native导出立即可用的常量,这是非常有用的一个功能,可以不用回调的方式来及时拿到所要使用的数据,避免了这些数据通过bridge来回传递。
- (NSDictionary *)constantsToExport {
return @{@"regionCode": [MIAppContext sharedInstance].currentRegion.regionCode};
}
在React Native模块里边直接使用下面代码即可拿到数据赋值使用:
I18n.locale = nativeVC. regionCode;
不过要注意的是在初始化React Native模块时常量才会被导入,如果运行时改变了constantsToExport
代码块的常量值,它不会影响React Native模块的常量值。如果想要改变React Native模块的导入常量的值,只能重新加载React Native模块,再次导入常量值使用。
参考
React Native 中文网
React Native 生命周期函数详解