国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統 > iOS > 正文

React Native 封裝原生UI組件(iOS)

2019-11-06 09:57:52
字體:
來源:轉載
供稿:網友

      原生開發,發展到今天已經非常成熟完善,已有組件成千上萬,極大的提高了開發效率。而React Native 在Facebook的React.js conf 2015上提出,至今一年多,組件數目肯定沒得和原生的相比。因此,在使用React Native開發App的過程中,我們可能需要調用RN沒有實現的原生視圖組件或第三方組件。甚至,我們可以把本地模塊構造成一個React Native組件,提供給別人使用。

本文的demo基于SDCycleScrollView,即banner,因為想不到什么好的例子,所以就把在做的項目用到的SDCycleScrollView封裝下,直接給js調用。

SDCycleScrollView為github開源的無限循環自動圖片輪播器。地址為:https://github.com/gsdios/SDCycleScrollView里面會用SDWebImage,如果項目已用到SDWebImage,則建議直接把SDCycleScrollView相關代碼拉進項目就OK了。

一、對原生視圖進行進一步封裝

參考其他人對原生視圖的封裝,大多都會新建一個視圖,繼承(或者子視圖包含)原生視圖,里面可能含有事件的調用(這里簡單demo,就沒用到)。#import "UIView+React.h",對原生視圖進行擴展(這里有個重要的屬性reactTag,后面會用到,作為區分用途)。

TestScrollView.h

#import "SDCycleScrollView.h"#import "RCTComponent.h"#import "UIView+React.h"@interface TestScrollView : SDCycleScrollView@PRoperty (nonatomic, copy) RCTBubblingEventBlock onClickBanner;@end

在封裝的UIView中聲明RCTBubblingEventBlock或RCTBubblingEventBlock類型的block屬性,才可以被當做事件導出。(新的事件導出方式,后面會用到哦)注意:聲明block屬性名稱要以on開頭(不確定為什么,在不做其它配置的情況下,只有on開頭能成功)

TestScrollView.m

#import "TestScrollView.h"@implementation TestScrollView/** * 挺多封裝原生的第三方組件都會這么寫,這里還沒研究透徹,就沒按著去實現- (instancetype)initWithBridge:(RCTBridge *)bridge { if ((self = [super initWithFrame:CGRectZero])) { _eventDispatcher = bridge.eventDispatcher; _bridge = bridge; ...... } return self;} */@end

二、創建RCTViewManager子類來創建和管理原生視圖

原生視圖都需要被一個RCTViewManager的子類來創建和管理。這些管理器在功能上有些類似“視圖控制器”,但它們本質上都是單例 - React Native只會為每個管理器創建一個實例。它們創建原生的視圖并提供給RCTUIManager,RCTUIManager則會反過來委托它們在需要的時候去設置和更新視圖的屬性。RCTViewManager還會代理視圖的所有委托,并給javaScript發回對應的事件。

提供原生視圖步驟如下:

首先創建一個子類 —— 命名規范為“視圖名稱+Manager”. 視圖名稱可以加上自己的前綴,這里最好避免使用RCT前綴,除非你想給官方pull request添加RCT_EXPORT_MODULE()標記宏 —— 讓模塊接口暴露給Javascript實現-(UIView *)view方法 —— 創建并返回組件視圖封裝屬性及傳遞事件

下面先貼出完整的代碼,然后會對屬性和事件進行進一步的解說。

TestScrollViewManager.h

#import "RCTViewManager.h"@interface TestScrollViewManager : RCTViewManager@end

TestScrollViewManager.m

#import "TestScrollViewManager.h"#import "TestScrollView.h" //第三方組件的頭文件#import "RCTBridge.h" //進行通信的頭文件#import "RCTEventDispatcher.h" //事件派發,不導入會引起Xcode警告@interface TestScrollViewManager() <SDCycleScrollViewDelegate>@end@implementation TestScrollViewManager// 標記宏(必要)RCT_EXPORT_MODULE()// 事件的導出,onClickBanner對應view中擴展的屬性RCT_EXPORT_VIEW_PROPERTY(onClickBanner, RCTBubblingEventBlock)// 通過宏RCT_EXPORT_VIEW_PROPERTY完成屬性的映射和導出RCT_EXPORT_VIEW_PROPERTY(autoScrollTimeInterval, CGFloat);RCT_EXPORT_VIEW_PROPERTY(imageURLStringsGroup, NSArray);RCT_EXPORT_VIEW_PROPERTY(autoScroll, BOOL);- (UIView *)view{ // 實際組件的具體大小位置由js控制 TestScrollView *testScrollView = [TestScrollView cycleScrollViewWithFrame:CGRectZero delegate:self placeholderImage:nil]; // 初始化時將delegate指向了self testScrollView.pageControlStyle = SDCycleScrollViewPageContolStyleClassic; testScrollView.pageControlAliment = SDCycleScrollViewPageContolAlimentCenter; return testScrollView;}/** * 當事件導出用到 sendInputEventWithName 的方式時,會用到- (NSArray *) customDirectEventTypes { return @[@"onClickBanner"];} */#pragma mark SDCycleScrollViewDelegate/** * banner點擊 */- (void)cycleScrollView:(TestScrollView *)cycleScrollView didSelectItemAtIndex:(NSInteger)index{// 這也是導出事件的方式,不過好像是舊方法了,會有警告// [self.bridge.eventDispatcher sendInputEventWithName:@"onClickBanner"// body:@{@"target": cycleScrollView.reactTag,// @"value": [NSNumber numberWithInteger:index+1]// }]; if (!cycleScrollView.onClickBanner) { return; } NSLog(@"oc did click %li", [cycleScrollView.reactTag integerValue]); // 導出事件 cycleScrollView.onClickBanner(@{@"target": cycleScrollView.reactTag, @"value": [NSNumber numberWithInteger:index+1]});}// 導出枚舉常量,給js定義樣式用- (NSDictionary *)constantsToExport{ return @{ @"SDCycleScrollViewPageContolAliment": @{ @"right": @(SDCycleScrollViewPageContolAlimentRight), @"center": @(SDCycleScrollViewPageContolAlimentCenter) } };}// 因為這個類繼承RCTViewManager,實現RCTBridgeModule,因此可以使用原生模塊所有特性// 這個方法暫時沒用到RCT_EXPORT_METHOD(testResetTime:(RCTResponseSenderBlock)callback) { callback(@[@(234)]);}@end

屬性

RCT_EXPORT_VIEW_PROPERTY(autoScrollTimeInterval, CGFloat);

通過宏RCT_EXPORT_VIEW_PROPERTY完成屬性的映射和導出。CGFloat為autoScrollTimeInterval的OC數據類型,轉化成js則對應number。

React Native用RCTConvert來在JavaScript和原生代碼之間完成類型轉換。支持的默認轉換類型(部分)如下:

string (NSString)number (NSInteger, float, double, CGFloat, NSNumber)boolean (BOOL, NSNumber)array (NSArray) 包含本列表中任意類型map (NSDictionary) 包含string類型的鍵和本列表中任意類型的值

如果轉換無法完成,會產生一個“紅屏”的報錯提示,這樣你就能立即知道代碼中出現了問題。如果一切進展順利,上面這個宏就已經包含了導出屬性的全部實現。

ps:更復雜的類型轉換,則涉及到MKCoordinateRegion類型,本文沒做應用,具體可參考官方文檔例子。

事件

js和原生之間需要有事件的交互,例如,在原生實現的代理或者點擊事件,js也需要實時獲取到此類事件時,就需要利用事件進行交互。事件的實現方式有以下兩種:

通過sendInputEventWithName實現1) 實現customDirectEventTypes,返回自定義的事件名數組(on開頭才有效)

- (NSArray *) customDirectEventTypes { return @[@"onClickBanner"];}

2) sendInputEventWithName實現事件調用(reactTag用于實例的區分)

[self.bridge.eventDispatcher sendInputEventWithName:@"onClickBanner" body:@{@"target": cycleScrollView.reactTag, @"value": [NSNumber numberWithInteger:index+1] }];

通過RCTBubblingEventBlock實現1) 在封裝的View中添加RCTBubblingEventBlock的block屬性(on開頭才有效)

@property (nonatomic, copy) RCTBubblingEventBlock onClickBanner;

2) 在Manager類中通過宏RCT_EXPORT_VIEW_PROPERTY完成Block屬性的映射和導出

RCT_EXPORT_VIEW_PROPERTY(onClickBanner, RCTBubblingEventBlock)

3) 實現事件調用(reactTag用于實例的區分)

cycleScrollView.onClickBanner(@{@"target": cycleScrollView.reactTag, @"value": [NSNumber numberWithInteger:index+1]});

通過上面兩種方式封裝好的事件,在js中可以直接利用同名函數調用即可(后面會展示)。不過關于事件這塊,挺多都沒完全弄懂,希望有大神引導引導,比如為什么只能定義on開頭、如何自定義、如何事件數據源的回調等等。。。

樣式

因為我們所有的視圖都是UIView的子類,大部分的樣式屬性應該直接就可以生效。有些屬性定義,需要用到枚舉,則可以利用通過原生傳遞來的常數方式來實現,具體實現如下:

// 導出枚舉常量,給js定義樣式用- (NSDictionary *)constantsToExport{ return @{ @"SDCycleScrollViewPageContolAliment": @{ @"right": @(SDCycleScrollViewPageContolAlimentRight), @"center": @(SDCycleScrollViewPageContolAlimentCenter) } };}

在js中調用則如下:

// 首先獲取到常量var TestScrollViewConsts = require('react-native').UIManager.TestScrollView.Constants;// 調用<TestScrollView style={styles.container} pageControlAliment = {TestScrollViewConsts.SDCycleScrollViewPageContolAliment.right} />

ps: 一部分組件會希望使用自己定義的默認樣式,例如UIDatePicker希望自己的大小是固定的。比如大小用原生默認大小,這個例子具體可以參考官方文檔的樣式模塊。

三、在JS中進行調用

在js中調用,可以有兩種方式,一為直接作為擴展React組件調用,二為新建一個組件封裝好,再進行調用。下文用第二種方式,官方推薦,邏輯比較清晰。

1.先倒入原生組件,新建TestScrollView.js文件,在里面對TestScrollView導入,進行屬性類型聲明等。具體代碼和解釋如下:

TestScrollView.js

// TestScrollView.jsimport React, { Component, PropTypes } from 'react';import { requireNativeComponent } from 'react-native';// requireNativeComponent 自動把這個組件提供給 "RCTScrollView"var RCTScrollView = requireNativeComponent('TestScrollView', TestScrollView);export default class TestScrollView extends Component { render() { return <RCTScrollView {...this.props} />; }}TestScrollView.propTypes = { /** * 屬性類型,其實不寫也可以,js會自動轉換類型 */ autoScrollTimeInterval: PropTypes.number, imageURLStringsGroup: PropTypes.array, autoScroll: PropTypes.bool, onClickBanner: PropTypes.func};module.exports = TestScrollView;

2.在index.ios.js中進行調用

index.ios.js

var TestScrollView = require('./TestScrollView');// requireNativeComponent 自動把這個組件提供給 "TestScrollView"// 如果不新建TestScrollView.js對原生組件封裝聲明,則直接用這句導入即可// var TestScrollView = requireNativeComponent('TestScrollView', null);// 導入常量var TestScrollViewConsts = require('react-native').UIManager.TestScrollView.Constants;var bannerImgs = [ 'http://upload-images.jianshu.io/upload_images/2321678-ba5bf97ec3462662.png?imageMogr2/auto-orient/strip%7CimageView2/2', 'http://upload-images.jianshu.io/upload_images/1487291-2aec9e634117c24b.jpeg?imageMogr2/auto-orient/strip%7CimageView2/2/w/480/q/100', 'http://f.hiphotos.baidu.com/zhidao/pic/item/e7cd7b899e510fb37a4f2df3db33c895d1430c7b.jpg'];class NativeUIModule extends Component { constructor(props){ super(props); this.state={ bannerNum:0 } } render() { return ( <ScrollView style = {{marginTop:64}}> <View> <TestScrollView style={styles.container} autoScrollTimeInterval = {2} imageURLStringsGroup = {bannerImgs} pageControlAliment = {TestScrollViewConsts.SDCycleScrollViewPageContolAliment.right} onClickBanner={(e) => { console.log('test' + e.nativeEvent.value); this.setState({bannerNum:e.nativeEvent.value}); }} /> <Text style={{fontSize: 15, margin: 10, textAlign:'center'}}> 點擊banner -> {this.state.bannerNum} </Text> </View> </ScrollView> ); }}// 實際組件的具體大小位置由js控制const styles = StyleSheet.create({ container:{ padding:30, borderColor:'#e7e7e7', marginTop:10, height:200, },});AppRegistry.registerComponent('NativeTest2', () => NativeUIModule);

若使用第一種方式,即使用下面語句進行組件的引用:

var TestScrollView = requireNativeComponent('TestScrollView', null);

則會存在的這樣的問題:雖然很方便簡單,但這樣并不能很好的說明這個組件的用法——用戶要想知道我們的組件有哪些屬性可以用,以及可以取什么樣的值,他不得不一路翻到Objective-C的代碼。要解決這個問題,我們可以創建一個封裝組件,并且通過PropTypes來說明這個組件的接口。

注意:我們現在把requireNativeComponent的第二個參數從null變成了用于封裝的組件TestScrollView。這使得React Native的底層框架可以檢查原生屬性和包裝類的屬性是否一致,來減少出現問題的可能。

關于屬性、事件的調用,則是如下直接調用:

<TestScrollView style={styles.container} autoScrollTimeInterval = {2} imageURLStringsGroup = {bannerImgs} pageControlAliment = {TestScrollViewConsts.SDCycleScrollViewPageContolAliment.right} onClickBanner={(e) => { console.log('test' + e.nativeEvent.value); this.setState({bannerNum:e.nativeEvent.value}); }}/>

關于事件,需要注意的是,事件事件默認傳遞的是字典數據類型,即json,在js中調用需要利用e.nativeEvent才能將字典取出,在具體調用里面的值。(這里也還未研究透徹、需要指導)

React Native 與原生代碼之間混合互相調用。經典項目如下:

鏈接: https://pan.baidu.com/s/1kVwRnYB 密碼: vuwz


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 富宁县| 鄂温| 鹿邑县| 常熟市| 瑞昌市| 澎湖县| 六安市| 和田县| 邢台市| 沭阳县| 宁蒗| 定襄县| 台山市| 安化县| 萨嘎县| 湾仔区| 庆城县| 长沙市| 那坡县| 抚州市| 和政县| 罗江县| 盘锦市| 凌云县| 三河市| 明溪县| 石林| 景泰县| 张家口市| 分宜县| 正蓝旗| 和田县| 桐柏县| 含山县| 类乌齐县| 康定县| 清苑县| 六安市| 江源县| 六安市| 教育|