React Native开发指南-在原生和React Native间通信
通過植入原生應用和原生UI組件兩篇文檔,我們學習了React Native和原生組件的互相整合。在整合的過程中,我們會需要在兩個世界間互相通信。有些方法已經在其他的指南中提到了,這篇文章總結了所有可行的技術。
簡介
React Native是從React中得到的靈感,因此基本的信息流是類似的。在React中信息是單向的。我們維護了組件層次,在其中每個組件都僅依賴于它父母和自己的狀態。通過屬性(properties)我們將信息從上而下的從父母傳遞到子元素。如果一個祖先組件需要自己子孫的狀態,推薦的方法是傳遞一個回調函數給對應的子元素。
React Native也運用了相同的概念。只要我們完全在框架內構建應用,就可以通過屬性和回調函數來調動整個應用。但是,當我們混合React Native和原生組件時,我們需要一些特殊的,跨語言的機制來傳遞信息。
屬性
屬性是最簡單的跨組件通信。因此我們需要一個方法從原生組件傳遞屬性到React Native或者從React Native到原生組件。
從原生組件傳遞屬性到React Native
我們使用RCTRootView將React Natvie視圖封裝到原生組件中。RCTRootView是一個UIView容器,承載著React Native應用。同時它也提供了一個聯通原生端和被托管端的接口。
通過RCTRootView的初始化函數你可以將任意屬性傳遞給React Native應用。參數initialProperties必須是NSDictionary的一個實例。這一字典參數會在內部被轉化為一個可供JS組件調用的JSON對象。
NSArray *imageList = @[@"http://foo.com/bar1.png",@"http://foo.com/bar2.png"];NSDictionary *props = @{@"images" : imageList};RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridgemoduleName:@"ImageBrowserApp"initialProperties:props]; 'use strict';var React = require('react-native');var {View,Image } = React;class ImageBrowserApp extends React.Component {renderImage: function(imgURI) {return (<Image source={{uri: imgURI}} />);},render() {return (<View>{this.props.images.map(this.renderImage)}</View>);} }React.AppRegistry.registerComponent('ImageBrowserApp', () => ImageBrowserApp);RCTRootView同樣提供了一個可讀寫的屬性appProperties。在appProperties設置之后,React Native應用將會根據新的屬性重新渲染。當然,只有在新屬性和之前的屬性有區別時更新才會被觸發。
NSArray *imageList = @[@"http://foo.com/bar3.png",@"http://foo.com/bar4.png"]; rootView.appProperties = @{@"images" : imageList}; 你可以隨時更新屬性,但是更新必須在主線程中進行,讀取則可以在任何線程中進行。
更新屬性時并不能做到只更新一部分屬性。我們建議你自己封裝一個函數來構造屬性。
注意:目前,最頂層的RN組件(即registerComponent方法中調用的那個)的componentWillReceiveProps和componentWillUpdateProps方法在屬性更新后不會觸發。但是,你可以通過componentWillMount訪問新的屬性值。
從React Native傳遞屬性到原生組件
這篇文檔詳細討論了暴露原生組件屬性的問題。簡而言之,在你自定義的原生組件中通過RCT_CUSTOM_VIEW_PROPERTY宏導出屬性,就可以直接在React Native中使用,就好像它們是普通的React Native組件一樣。
屬性的限制
跨語言屬性的主要缺點是不支持回調方法,因而無法實現自下而上的數據綁定。設想你有一個小的RN視圖,當一個JS動作觸發時你想從原生的父視圖中移除它。此時你會發現根本做不到,因為信息需要自下而上進行傳遞。
雖然我們有跨語言回調(參閱這里,但是這些回調函數并不總能滿足需求。最主要的問題是它們并不是被設計來當作屬性進行傳遞。這一機制的本意是允許我們從JS觸發一個原生動作,然后用JS處理那個動作的處理結果。
其他的跨語言交互(事件和原生模塊)
如上一章所說,使用屬性總會有一些限制。有時候屬性并不足以滿足應用邏輯,因此我們需要更靈活的解決辦法。這一章描述了其他的在React Native中可用的通信方法。他們可以用來內部通信(在JS和RN的原生層之間),也可以用作外部通信(在RN和純原生部分之間)。
React Native允許使用跨語言的函數調用。你可以在JS中調用原生代碼,也可以在原生代碼中調用JS。在不同端需要用不同的方法來實現相同的目的。在原生代碼中我們使用事件機制來調度JS中的處理函數,而在React Native中我們直接使用原生模塊導出的方法。
從原生代碼調用React Natvie函數(事件)
事件的詳細用法在這篇文章中進行了討論。注意使用事件無法確保執行的時間,因為事件的處理函數是在單獨的線程中執行。
事件很強大,它可以不需要引用直接修改React Native組件。但是,當你使用時要注意下面這些陷阱:
- 由于事件可以從各種地方產生,它們可能導致混亂的依賴。
- 事件共享相同的命名空間,因此你可能遇到名字沖突。沖突不會在編寫代碼時被探測到,因此很難排錯。
- 如果你使用了同一個React Native組件的多個引用,然后想在事件中區分它們,name你很可能需要在事件中同時傳遞一些標識(你可以使用原生視圖中的reactTag作為標識)。
在React Native中嵌入原生組件時,通常的做法是用原生組件的RCTViewManager作為視圖的代理,通過bridge向JS發送事件。這樣可以集中在一處調用相關的事件。
從React Native中調用原生方法(原生模塊)
原生模塊是JS中也可以使用的Objective-C類。一般來說這樣的每一個模塊的實例都是在每一次通過JS bridge通信時創建的。他們可以導出任意的函數和常量給React Native。相關細節可以參閱這篇文章。
事實上原生模塊的單實例模式限制了嵌入。假設我們有一個React Native組件被嵌入了一個原生視圖,并且我們希望更新原生的父視圖。使用原生模塊機制,我們可以導出一個函數,不僅要接收預設參數,還要接收父視圖的標識。這個標識將會用來獲得父視圖的引用以更新父視圖。那樣的話,我們需要維持模塊中標識到原生模塊的映射。 雖然這個解決辦法很復雜,它仍被用在了管理所有React Native視圖的RCTUIManager類中,
原生模塊同樣可以暴露已有的原生庫給JS,地理定位庫就是一個現成的例子。
警告:所有原生模塊共享同一個命名空間。創建新模塊時注意命名沖突。
布局計算流
當集成原生模塊和React Natvie時,我們同樣需要一個能協同不同的布局系統的辦法。這一章節討論了常見的布局問題,并且提供了解決機制的簡單說明。
在React Native中嵌入一個原生組件
這個情況在這篇文章中進行了討論?;旧?#xff0c;由于所有的原生視圖都是UIView的子集,大多數類型和尺寸屬性將和你期望的一樣可以使用。
在原生中嵌入一個React Native組件
固定大小的React Native內容
最簡單的情況是一個對于原生端已知的,固定大小的React Native應用,尤其是一個全屏的React Native視圖。如果我們需要一個小一點的根視圖,我們可以明確的設置RCTRootView的frame。 比如說,創建一個200像素高,宿主視圖那樣寬的RN app,我們可以這樣做:
// SomeViewController.m- (void)viewDidLoad {[...]RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridgemoduleName:appNameinitialProperties:props];rootView.frame = CGMakeRect(0, 0, self.view.width, 200);[self.view addSubview:rootView]; }當我們創建了一個固定大小的根視圖,則需要在JS中遵守它的邊界。換句話說,我們需要確保React Native內容能夠在固定的大小中放下。最簡單的辦法是使用flexbox布局。如果你使用絕對定位,并且React組件在根視圖邊界外可見,則React Native組件將會和原生視圖重疊,導致某些不符合期望的行為。比如說,當你點擊根視圖邊界之外的區域TouchableHighlight將不會高亮。 通過重新設置frame的屬性來動態更新根視圖的大小是完全可行的。React Native將會關注內容布局的變化。
彈性大小的React Native
有時候我們需要渲染一些不知道大小的內容。假設尺寸將會在JS中動態指定。我們有兩個解決辦法。
- 你可以將React Native視圖包裹在ScrollView中。這樣可以保證你的內容總是可以訪問,并且不會和原生視圖重疊。
- React Native允許你在JS中決定RN應用的尺寸,并且將它傳遞給宿主視圖RCTRootView。然后宿主視圖將重新布局子視圖,保證UI統一。我們通過RCTRootView的彈性模式來達到目的。
RCTRootView支持4種不同的彈性模式:
// RCTRootView.htypedef NS_ENUM(NSInteger, RCTRootViewSizeFlexibility) {RCTRootViewSizeFlexibilityNone = 0,RCTRootViewSizeFlexibilityWidth,RCTRootViewSizeFlexibilityHeight,RCTRootViewSizeFlexibilityWidthAndHeight, };默認值是RCTRootViewSizeFlexibilityNone,表示使用固定大小的根視圖(仍然可以通過setFrame更改)。其他三種模式可以跟蹤React Native尺寸的變化。比如說,設置模式為RCTRootViewSizeFlexibilityHeight,React Native將會測量內容的高度然后傳遞回RCTRootView的代理。代理可以執行任意的行為,包括設置根視圖的frame以使內容尺寸相匹配。 代理僅僅在內容的尺寸發生變化時才進行調用。
注意:在JS和原生中都設置彈性尺寸可能導致不確定的行為。比如--不要在設置RCTRootView為RCTRootViewSizeFlexibilityWidth時同時指定最頂層的RN組件寬度可變(使用Flexbox)。
看一個例子。
// FlexibleSizeExampleView.m- (instancetype)initWithFrame:(CGRect)frame {[...]_rootView = [[RCTRootView alloc] initWithBridge:bridgemoduleName:@"FlexibilityExampleApp"initialProperties:@{}];_rootView.delegate = self;_rootView.sizeFlexibility = RCTRootViewSizeFlexibilityHeight;_rootView.frame = CGRectMake(0, 0, self.frame.size.width, 0); }#pragma mark - RCTRootViewDelegate - (void)rootViewDidChangeIntrinsicSize:(RCTRootView *)rootView {CGRect newFrame = rootView.frame;newFrame.size = rootView.intrinsicSize;rootView.frame = newFrame; }在例子中我們使用一個FlexibleSizeExampleView視圖來包含根視圖。我們創建了根視圖,初始化并且設置了代理。代理將會處理尺寸更新。然后,我們設置根視圖的彈性尺寸為RCTRootViewSizeFlexibilityHeight,意味著rootViewDidChangeIntrinsicSize:方法將會在每次React Native內容高度變化時進行調用。最后,我們設置根視圖的寬度和位置。注意我們也設置了高度,但是并沒有效果,因為我們已經將高度設置為根據RN內容進行彈性變化了。
你可以在這里查看完整的例子源代碼。
動態改變根視圖的彈性模式是可行的。改變根視圖的彈性模式將會導致布局的重新計算,并且在重新量出內容尺寸時會調用rootViewDidChangeIntrinsicSize方法。
注意:React Native布局是通過一個特殊的線程進行計算,而原生UI視圖是通過主線程更新。這可能導致短暫的原生端和React Native端的不一致。這是一個已知的問題,我們的團隊已經在著手解決不同源的UI同步更新。 注意:除非根視圖成為其他視圖的子視圖,否則React Native不會進行任何的布局計算。如果你想在還沒有獲得React Native視圖的尺寸之前先隱藏視圖,請將根視圖添加為子視圖并且在初始化的時候進行隱藏(使用UIView的hidden屬性),然后在代理方法中改變它的可見性。
本文轉自React Native中文網:http://reactnative.cn/docs/0.20/communication-ios.html#content總結
以上是生活随笔為你收集整理的React Native开发指南-在原生和React Native间通信的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: pc版android sd卡,告别瓶颈:
- 下一篇: mysql多数据源_egg-mysql配