【React Native】文件翻譯閱讀紀錄 - 指南(iOS) - Native 和 React Native之間的通信

by - 上午9:00

Facebook Open Source React Native
Native 和 React Native之間的通信

在“與現有應用程序集成指南”和“本機UI組件”指南中,我們將學習如何在本機組件中嵌入React Native,反之亦然。當我們混合使用Native和React Native組件時,我們最終會發現需要在這兩個世界之間進行通信。其他指南中已經提到了實現這一目標的一些方法。本文總結了可用的技術。

介紹


React Native 的靈感來自 React,因此信息流的基本思想是相似的。 React 中的流程是單向的。我們維護組件的層次結構,其中每個組件僅依賴於其父級和它自己的內部狀態。我們使用屬性執行此操作:數據以自上而下的方式從父級傳遞給其子級。如果祖先組件依賴於其後代的狀態,則應傳遞回調以供後代使用以更新祖先。

相同的概念適用於 React Native。只要我們在框架內構建我們的應用程序,我們就可以使用屬性和回調驅動我們的應用程序。但是,當我們混合使用 React Native 和本機組件時,我們需要一些特殊的跨語言機制來允許我們在它們之間傳遞信息。

屬性

屬性是跨組件通信的最簡單方式。因此,我們需要一種方法將屬性從本機傳遞到React Native,從React Native傳遞到本機。

將屬性從本機傳遞到React Native
為了在本機組件中嵌入React Native視圖,我們使用RCTRootView。 RCTRootView是一個包含React Native應用程序的UIView。它還提供本機端和託管應用程序之間的接口。

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:bridge
                                                 moduleName:@"ImageBrowserApp"
                                          initialProperties:props];
import React from 'react';
import {
  AppRegistry,
  View,
  Image
} from 'react-native';

class ImageBrowserApp extends React.Component {
  renderImage(imgURI) {
    return (
      <Image source={{uri: imgURI}} />
    );
  }
  render() {
    return (
      <View>
        {this.props.images.map(this.renderImage)}
      </View>
    );
  }
}

AppRegistry.registerComponent('AwesomeProject', () => ImageBrowserApp);
AppRegistry.registerComponent('AwesomeProject',()=> ImageBrowserApp);
RCTRootView還提供了一個讀寫屬性appProperties。設置appProperties後,將使用新屬性重新呈現React Native應用程序。僅當新更新的屬性與先前的屬性不同時才執行更新。
NSArray *imageList = @[@"http://foo.com/bar3.png",
                       @"http://foo.com/bar4.png"];

rootView.appProperties = @{@"images" : imageList};
可以隨時更新屬性。但是,必須在主線程上執行更新。你在任何線程上使用getter。

一次只能更新幾個屬性。我們建議您將其構建到自己的包裝器中。
注意:目前,在更新prop後,將不會調用頂級RN組件的JS函數componentWillReceiveProps和componentWillUpdateProps。但是,您可以在componentWillMount函數中訪問新的props。

將屬性從React Native傳遞到本機

本文詳細介紹了暴露本機組件屬性的問題。簡而言之,在自定義本機組件中使用RCT_CUSTOM_VIEW_PROPERTY宏導出屬性,然後在React Native中使用它們,就好像該組件是普通的React Native組件一樣。

屬性限制

跨語言屬性的主要缺點是它們不支持回調,這將允許我們處理自下而上的數據綁定。想像一下,由於JS操作,您希望從本機父視圖中刪除一個小RN視圖。道具沒有辦法做到這一點,因為信息需要自下而上。

雖然我們有一種跨語言的回調(這裡描述),但這些回調並不總是我們需要的。主要問題是它們不打算作為屬性傳遞。相反,這種機制允許我們從JS觸發本機操作,並在JS中處理該操作的結果。

跨語言交互的其他方式(事件和本機模塊)

如前一章所述,使用屬性有一些限制。有時屬性不足以推動我們的應用程序的邏輯,我們需要一個提供更多靈活性的解決方案。本章介紹React Native中可用的其他通信技術。它們可用於內部通信(在RN和RN中的本機層之間)以及外部通信(在RN與應用程序的“純本機”部分之間)。

React Native使您可以執行跨語言函數調用。您可以從JS執行自定義本機代碼,反之亦然。不幸的是,根據我們正在努力的方面,我們以不同的方式實現相同的目標。對於native - 我們使用事件機制來安排在JS中執行處理函數,而對於React Native,我們直接調用由本機模塊導出的方法。

從本機(事件)調用React Native函數

本文詳細介紹了事件。請注意,使用事件不能保證執行時間,因為事件是在單獨的線程上處理的。

事件很強大,因為它們允許我們更改React Native組件而無需引用它們。但是,使用它們時可能會遇到一些陷阱:

  • 由於事件可以從任何地方發送,因此可以在項目中引入意大利面風格的依賴項。
  • 事件共享命名空間,這意味著您可能會遇到一些名稱衝突。不會靜態檢測到衝突,這使得它們難以調試。
  • 如果您使用相同React Native組件的多個實例,並且希望將它們與事件的視角區分開來,則可能需要引入標識符並將它們與事件一起傳遞(您可以使用本機視圖的reactTag作為標識符) 。
我們在React Native中嵌入native時使用的常見模式是使本機組件的RCTViewManager成為視圖的委託,通過網橋將事件發送回JavaScript。這使相關的事件調用保持在一個地方。

從React Native調用本機函數(本機模塊)

本機模塊是JS中可用的Objective-C類。通常,每個JS橋都會創建一個每個模塊的實例。他們可以將任意函數和常量導出到React Native。本文詳細介紹了它們。

原生模塊是單例的事實限制了嵌入環境中的機制。假設我們在本機視圖中嵌入了React Native組件,並且我們想要更新本機父視圖。使用本機模塊機制,我們將導出一個函數,該函數不僅包含預期的參數,還包含父本機視圖的標識符。標識符將用於檢索對要更新的父視圖的引用。也就是說,我們需要在模塊中保持從標識符到本機視圖的映射。

儘管此解決方案很複雜,但它在RCTUIManager中使用,RCTUIManager是一個管理所有React Native視圖的內部React Native類。

本機模塊也可用於將現有本機庫公開給JS。地理位置庫是這個想法的活生生的例子。
警告:所有本機模塊共享相同的命名空間。創建新名稱時要注意名稱衝突。

佈局計算流程

在集成native和React Native時,我們還需要一種方法來整合兩個不同的佈局系統。本節介紹常見的佈局問題,並簡要介紹解決這些問題的機制。

React Native 中嵌入的本機組件的佈局

本文將介紹此案例。基本上,由於我們所有的本機反應視圖都是UIView的子類,因此大多數樣式和大小屬性都可以像開箱即用的那樣工作。

嵌入本機的React Native組件的佈局

使用固定大小的React Native內容

最簡單的方案是當我們有一個具有固定大小的React Native應用程序時,本機方面已知。特別是,全屏React Native視圖屬於這種情況。如果我們想要一個較小的根視圖,我們可以顯式設置RCTRootView的框架。
例如,要使RN app 200(邏輯)像素為高,並且託管視圖的寬度為寬,我們可以:
// SomeViewController.m

- (void)viewDidLoad
{
  [...]
  RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
                                                   moduleName:appName
                                            initialProperties:props];
  rootView.frame = CGRectMake(0, 0, self.view.width, 200);
  [self.view addSubview:rootView];
}
當我們有一個固定大小的根視圖時,我們需要尊重它在JS方面的界限。換句話說,我們需要確保React Native內容可以包含在固定大小的根視圖中。確保這一點的最簡單方法是使用flexbox佈局。如果使用絕對定位,並且React組件在根視圖的邊界外可見,則會與本機視圖重疊,從而導致某些功能出現意外行為。例如,'TouchableHighlight'不會突出顯示根視圖邊界之外的觸摸。

通過重新設置其frame屬性來動態更新根視圖的大小是完全正確的。 React Native將負責內容的佈局。

具有靈活大小的 React Native 內容

在某些情況下,我們想要呈現最初未知大小的內容。假設大小將在JS中動態定義。我們有兩個解決這個問題的方法。

  1. 您可以將React Native視圖包裝在ScrollView組件中。這可以保證您的內容始終可用,並且不會與本機視圖重疊。
  2. React Native允許您在JS中確定RN應用程序的大小,並將其提供給託管RCTRootView的所有者。然後,所有者負責重新佈置子視圖並保持UI一致。我們通過RCTRootView的靈活模式實現了這一目標。
RCTRootView 支持4種不同大小的靈活模式:
// RCTRootView.h

typedef NS_ENUM(NSInteger, RCTRootViewSizeFlexibility) {
  RCTRootViewSizeFlexibilityNone = 0,
  RCTRootViewSizeFlexibilityWidth,
  RCTRootViewSizeFlexibilityHeight,
  RCTRootViewSizeFlexibilityWidthAndHeight,
};
RCTRootViewSizeFlexibilityNone 是默認值,它使根視圖的大小固定(但仍可以使用setFrame更新:)。其他三種模式允許我們跟踪React Native內容的大小更新。例如,將模式設置為RCTRootViewSizeFlexibilityHeight將導致React Native測量內容的高度並將該信息傳遞回RCTRootView的委託。可以在委託中執行任意操作,包括設置根視圖的框架,以使內容適合。只有在內容大小發生變化時才會調用委託。
警告:在JS和native中創建一個靈活的維度會導致未定義的行為。例如 - 當您在託管RCTRootView上使用RCTRootViewSizeFlexibilityWidth時,不要使頂級React組件的寬度靈活(使用flexbox)。
我們來看一個例子。
// FlexibleSizeExampleView.m

- (instancetype)initWithFrame:(CGRect)frame
{
  [...]

  _rootView = [[RCTRootView alloc] initWithBridge:bridge
  moduleName:@"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.intrinsicContentSize;

  rootView.frame = newFrame;
} 
在示例中,我們有一個包含根視圖的FlexibleSizeExampleView視圖。我們創建根視圖,初始化它並設置委託。代表將處理大小更新。然後,我們將根視圖的大小靈活性設置為RCTRootViewSizeFlexibilityHeight,這意味著每次React Native內容更改其高度時都將調用rootViewDidChangeIntrinsicSize:方法。最後,我們設置根視圖的寬度和位置。請注意,我們也設置了高度,但它沒有效果,因為我們使高度RN依賴。

您可以在此處查看示例的完整源代碼 
可以動態更改根視圖的大小靈活性模式。更改根視圖的靈活性模式將安排佈局重新計算,並且一旦知道內容大小,將調用委託rootViewDidChangeIntrinsicSize:方法。
注意:React Native佈局計算在特殊線程上執行,而本機UI視圖更新在主線程上完成。這可能會導致本機和React Native之間的UI不一致。這是一個已知問題,我們的團隊正在努力同步來自不同來源的UI更新。
注意:在根視圖成為某些其他視圖的子視圖之前,React Native不會執行任何佈局計算。如果要隱藏React Native視圖直到其尺寸已知,請將根視圖添加為子視圖並使其最初隱藏(使用UIView的隱藏屬性)。然後在委託方法中更改其可見性。



You May Also Like

0 意見