1. 程式人生 > >RN與原生互動(一)——基本頁面跳轉

RN與原生互動(一)——基本頁面跳轉

React Native(以下簡稱RN)開發app過程中大部分都可以在JS端完成,但是也有一些功能是需要原生端來完成的。這時RN與原生端就不可避免的需要進行互動,比如頁面跳轉和資料傳遞。本篇文章主要以RN——原生、原生——RN——原生為例來講解如何進行基本的頁面跳轉操作。

關於頁面跳轉,我寫了三個demo。這裡發下地址:

RNAddNativeRNPushToNative差不多,都是RN頁面跳轉到原生的例項,區別是一個RNAddNative使用了react-native-navigationRNPushToNative使用了react-navigation。在試過兩種導航庫之後,我更傾向於使用react-native-navigation

,誰用誰知道。

NativeJumpToRN是原生頁面跳轉到RN,再從RN跳轉到原生的示例。

在實際開發中,我認為RN頁面(JS寫的)和原生頁面(iOS和Android寫的)之間最好還是做到分離開來,通用頁面還是用RN實現,各自需要定製化的、功能複雜的頁面還是原生寫。儘量不要搞RN和原生元件內嵌,畢竟寫UI元件的話是相對比較簡單的事,沒必要在原生頁面內嵌一個RN頁面作為元件,或者RN頁面內嵌一個原生元件。將兩者分離開,有需要就直接頁面間進行跳轉,這樣也方便處理。

RN——Native

如果你還沒有建立原生專案,那直接react-native init建立一個RN專案,android和iOS的工程會自動給你建立好。android和iOS的工程都是可以拿來獨立開發的,不會受RN的影響。如果已經有原生專案了,請自行搜尋如何整合react native到現有原生專案,為了精簡篇幅這裡不多做贅述。

RNPushToNative為例先來說明一下如何從RN跳轉到原生頁面。從RN跳轉到原生,其實是在原生端建立Module類通過橋接的方式匯出到JS端供JS程式碼呼叫原生端程式碼來實現的。

Android

Android分三步:

1. 定義Module類,繼承ReactContextBaseJavaModule

在Module類中,重寫getName方法宣告Module類名稱,建立我們自己的方法用來做頁面跳轉。

2. 定義Package類,實現介面ReactPackage

Package類需要實現createNativeModulescreateViewManagers兩個方法。在createNativeModules

中初始化Module類並新增到集合

3. 定義Application類,繼承android的Application,並實現ReactApplication介面。

在getPackages方法中初始化Package類,並新增到集合。

RN專案建立的時候Application類就有了,不需要再建立。如果是整合到現有原生專案,就需要手動修改Application類。

Module類核心程式碼:

public class OpenNativeModule extends ReactContextBaseJavaModule {

    private ReactContext mReactContext;

    public OpenNativeModule(ReactApplicationContext context) {
        super(context);
        this.mReactContext = context;
    }

    @Override
    public String getName() {
        return "OpenNativeModule";
    }

    @ReactMethod
    public void openNativeVC() {
        Intent intent = new Intent();
        intent.setClass(mReactContext, SettingsActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        mReactContext.startActivity(intent);
    }
}

Package類核心程式碼:

public class TestReactPackage implements ReactPackage {

    @Override
    public List<NativeModule> createNativeModules(
            ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        modules.add(new OpenNativeModule(reactContext));
        return modules;
    }

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }
}

Application類核心程式碼:

@Override
    protected List<ReactPackage> getPackages() {
      return Arrays.<ReactPackage>asList(
          new MainReactPackage(),
          new VectorIconsPackage(),
          new TestReactPackage()
      );
    }

iOS

iOS端比Android就簡單多了,只需要建立一個Module類,實現RCTBridgeModule協議就行了。

OpenNativeModule.h程式碼:

#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>

@interface OpenNativeModule : NSObject<RCTBridgeModule>

@end

OpenNativeModule.m程式碼:

#import "OpenNativeModule.h"
#import "AppDelegate.h"
#import "NativeViewController.h"

@implementation OpenNativeModule

RCT_EXPORT_MODULE();

RCT_EXPORT_METHOD(openNativeVC) {
  dispatch_async(dispatch_get_main_queue(), ^{
    AppDelegate *delegate = (AppDelegate *)([UIApplication sharedApplication].delegate);
    UINavigationController *rootNav = delegate.navController;
    NativeViewController *nativeVC = [[NativeViewController alloc] init];
    [rootNav pushViewController:nativeVC animated:YES];
  });
}

@end

最後在RN端呼叫:

import React from 'react';
import {View, Text, Button, NativeModules} from 'react-native';

var nativeModule = NativeModules.OpenNativeModule;

export default class HomeScreen extends React.Component {
  
  render() {
    return (
      <View>
        <Text>首頁</Text>
        <Button title={'跳轉到原生頁面'} onPress={() => {
          this.jumpToNativeView();
        }}/>
      </View>
    )
  }
  
  jumpToNativeView() {
    nativeModule.openNativeVC();
  }
}

Native——RN——Native

原生跳轉到RN再由RN跳轉到原生頁面,這個示例可以參考NativeJumpToRN。RN到原生的跳轉與上述原理相同,不同的是原生到RN的跳轉。這個demo中我只是將原生專案啟動後的初始頁面由RN頁面改為了原生頁面,而原本的RN頁面我將它內嵌在一個原生頁面當中了。

對於Android來說,原本的啟動頁面是MainActivity, MainActivity就對應著RN頁面。 我建立了一個LaunchActivity作為啟動頁面,在AndroidManifest.xml中將其設定為啟動頁。在LaunchActivity中新增一個button,設定點選跳轉到MainActivity就完成了原生頁面到RN的跳轉,是不是很簡單?

public class LaunchActivity extends AppCompatActivity {

    private Button button;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_launch);

        button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(LaunchActivity.this, MainActivity.class);
                startActivity(intent);
            }
        });
    }
}

iOS端也一樣,RN頁面的初始化是在AppDelegate中完成的,這裡我們將window的rootViewController改為原生的UIViewControllerAppDelegate核心程式碼:

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  _navController = [[UINavigationController alloc] initWithRootViewController:[[HomeViewController alloc] init]];
  self.window.rootViewController = _navController;
  [self.window makeKeyAndVisible];
  return YES;
}

@end

HomeViewController中建立一個button,點選button跳轉到RNViewController

@implementation HomeViewController

- (void)viewDidLoad {
  [super viewDidLoad];
  self.title = @"這是iOS原生頁面";
  self.view.backgroundColor = [UIColor brownColor];
  
  UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(100, 100, 200, 50)];
  [button setTitle:@"點選跳轉到RN頁面" forState:UIControlStateNormal];
  [button addTarget:self action:@selector(onClickButton) forControlEvents:UIControlEventTouchUpInside];
  
  [self.view addSubview:button];
}

- (void)onClickButton {
  RNViewController *vc = [[RNViewController alloc] init];
  [self.navigationController pushViewController:vc animated:YES];
}

@end

AppDelegate中RN頁面的初始化操作放到RNViewController中來:

@implementation RNViewController

- (void)viewDidLoad {
  [super viewDidLoad];
  self.title = @"這是RN頁面";
  
  NSURL *jsCodeLocation;
  
  jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
  
  RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
                                                      moduleName:@"NativeJumpToRN"
                                               initialProperties:nil
                                                   launchOptions:nil];
  rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
  
  self.view = rootView;
}

這樣就完成了原生到RN的跳轉。