美文网首页
React Native与原生(Android、iOS)混编,三

React Native与原生(Android、iOS)混编,三

作者: 干吧_7973 | 来源:发表于2019-11-09 22:09 被阅读0次

    在做RN混编项目的时候或者面试的时候经常会遇到一些问题,总结起来有以下几种:

    1、过多的注册RN组件( AppRegistry.registerComponent() );
    2、从原生跳转指定的RN页面及传值问题;
    3、路由处理:原生 -> React Native -> 原生 -> React Native,多次操作后的进栈出栈问题。
    

    、解决问题1需要使用 React-Navigation 这个库,然后创建一个 RootScreen.js 作为RN页入口,且这个页面不显示UI元素,每次进入RN页面都需要经过的页面进行转发。

    1、先注册RootScreen页面:

    Root: {screen: RootScreen}
    

    2、设置RootScreen页面为初始化页面

    const AppNavigator = createStackNavigator(
        StackNavigator,
        {
           initialRouteName: "Root", // 默认显示界面
           mode: 'card',
           headerMode: 'screen',
           defaultNavigationOptions:{
               gesturesEnabled: true
           },
           transitionConfig: () => ({
               //push动画(右进右出)
               screenInterpolator: StackViewStyleInterpolator.forHorizontal,
           })
        }
    );
    
    export const AppContainer = createAppContainer(AppNavigator);
    

    3、在RootScreen页面进行跳转,可以看到这里使用了重置路由的方法StackActions.reset(),防止返回时能回到这个RootScreen页面。通过this.props.navigation.dispatch()跳转页面,然后在打开的页面中按照正常取值的方法this.props.navigation.getParam()取出对应的值即可。

    其中两个参数:(1)RouteInfo.routeName;(2)RouteInfo.routeParams在后面会说到。

    export default class RootScreen extends Component {
    
      constructor(props) {
        super(props);
        const RouteName = RouteInfo.routeName;
        const RouteParams = RouteInfo.routeParams;
        this._push(RouteName, RouteParams);
      }
    
      /**
       * 通过重置路由方式实现初始化不同的页面
       * @param routeName 在StackNavigator中注册的页面
       * @param params 如 {user_id: 21, money: 100}
       * @private
       */
      _push = (routeName: string, params?: NavigationParams) => {
        const resetAction = StackActions.reset({
          index: 0,
          actions: [NavigationActions.navigate({routeName, params})]
        });
        this.props.navigation.dispatch(resetAction)
      };
    
      render() {
        return null;
      }
    }
    

    、从原生进入RN页面传值,通过源码看到Android是以Bundle传进去,就是这个initialProperties

    /** {@see #startReactApplication(ReactInstanceManager, String, android.os.Bundle, String)} */
    public void startReactApplication(
          ReactInstanceManager reactInstanceManager,
          String moduleName,
          @Nullable Bundle initialProperties) {
        startReactApplication(reactInstanceManager, moduleName, initialProperties, null);
      }
    

    iOS的源码也有一个initialProperties,且是个字典对象。

    /**
     * - Designated initializer -
     */
    - (instancetype)initWithBridge:(RCTBridge *)bridge
                        moduleName:(NSString *)moduleName
                 initialProperties:(nullable NSDictionary *)initialProperties NS_DESIGNATED_INITIALIZER;
    

    好了,知道了关键点就好做了。先给Android创建一个RNRouteInfo类,用来存放要进入的RN页面,以及要传进去的参数。其中:
    1、NativeRouteInfo这个就是给RN的属性名称,RN根据这个属性取出传进去的值;
    2、routeName是要打开的页面,这个值就在React-Navigation中注册的页面,比如前面Root: {screen: RootScreen}中的Root;
    3、routeParams就是要给routeName页面的参数。

    到这里就可以解释前面问题一中第3点提到的两个参数(1)RouteInfo.routeName;(2)RouteInfo.routeParams

    public class RNRouteInfo {
    
        public static final String NATIVE_ROUTE_INFO = "NativeRouteInfo";
    
        private String routeName;
        private ArrayMap routeParams;
    
        public String getRouteName() {
            return routeName;
        }
    
        public void setRouteName(String routeName) {
            this.routeName = routeName;
        }
    
        public ArrayMap getRouteParams() {
            return routeParams;
        }
    
        public void setRouteParams(ArrayMap routeParams) {
            this.routeParams = routeParams;
        }
    
        public Bundle getBundle(){
            Bundle bundle = new Bundle();
            //把对象转成json字符串传给RN
            bundle.putString(RNRouteInfo.NATIVE_ROUTE_INFO, new Gson().toJson(this));
            return bundle;
        }
    }
    

    所以要打开某个页面,就像这样就行了

    RNRouteInfo route = new RNRouteInfo();
    route.setRouteName("TestOne");
    ArrayMap<String, Object> map = new ArrayMap<>();
    map.put("initTitle", "从Android首页过来");
    route.setRouteParams(map);
    startActivity(RNActivity.class, route.getBundle());
    

    而iOS这边也是需要创建一个类RNRouteInfo.m,可以看到这边也定义了三个相同的属性名称

    #import "RNRouteInfo.h"
    
    @implementation RNRouteInfo
    
    - (void)setRouteName:(NSString*)name {
      routeName = name;
    }
    
    - (void)setRouteParams:(NSDictionary*)params {
      routeParams = params;
    }
    
    - (NSDictionary *)toNSDictionary{
      NSDictionary *dic;
      if (routeParams == nil) {
        dic = @{@"NativeRouteInfo":@{
                     @"routeName":routeName
                  }
               };
      }else{
        dic = @{@"NativeRouteInfo":@{
                     @"routeName":routeName,
                     @"routeParams": routeParams
                  }
               };
      }
    
     return dic;
    }
    
    @end
    

    使用起来也很简单

    RNViewController *vc = [[RNViewController alloc] init];
    //初始化RN路由信息
    RNRouteInfo *info = [[RNRouteInfo alloc] init];
    //设置要进入的RN页面
    [info setRouteName:@"TestOne"];
    //设置要传入的参数
    NSDictionary * params = @{@"initTitle": @"从iOS首页过来"};
    [info setRouteParams:params];
    vc.rnRouteInfo = info.toNSDictionary;
      
    AppDelegate *app = (AppDelegate *)[[UIApplication sharedApplication] delegate];
    [app.nav pushViewController:vc animated:YES];
    

    接下来就是重点了,RN页面接受传过来的值。RN要取到从原生传过来的值只能在AppRegistry.registerComponent()中注册的组件中拿到,而在这里是注册了一个App的组件,所以取值是this.props.NativeRouteInfo。这里的构造函数中不只判断NativeRouteInfo属性是否定义,还多了一层判断,这是因为iOS传进来的值可以是json对象,而Android传进来的只能是基本数据类型,所以这里要转成json对象。

    到这里,再回去看RootScreen.js,整个模块的封装就穿起来。

    export default class App extends Component {
    
      constructor(props) {
        super(props);
        if(this.props.NativeRouteInfo){
          if (typeof this.props.NativeRouteInfo === 'object'){//ios
            global.RouteInfo = this.props.NativeRouteInfo
          }else {//android
            global.RouteInfo = JSON.parse(this.props.NativeRouteInfo);
          }
        }
      }
    
      render() {
        return (
            <AppContainer/>
        );
      }
    }
    

    、路由问题
    app中习惯了右进右出的转场效果,所以在Android中定义入栈动画
    overridePendingTransition(R.anim.slide_in_right, 0);
    而iOS中使用UINavigationController pushViewController来进行。

    而出栈动画需要原生定义都定义CommonModule,且实现以下两个方法:
    1、定义Android的出栈方法finish()

      @ReactMethod
      public void finish(){
        if (getCurrentActivity() != null){
            getCurrentActivity().finish();
            getCurrentActivity().overridePendingTransition(0, R.anim.slide_out_right);
        }
    }
    

    2、定义iOS的出栈方法finish()

    RCT_EXPORT_METHOD(finish){
    
      dispatch_async(dispatch_get_main_queue(), ^{
        AppDelegate *app = (AppDelegate *)[[UIApplication sharedApplication] delegate];
        [app.nav popViewControllerAnimated:YES];
      });
      
    }
    

    RN的的出栈方法是:先定义一个基类BaseScreen,所有页面都应该继承这个基类来实现业务需求。

    export default class BaseScreen extends React.Component {
    
      constructor(props) {
          super(props);
          this._didFocusSubscription = props.navigation.addListener('didFocus', payload =>
          BackHandler.addEventListener('hardwareBackPress', this._onBackButtonPressAndroid),
        );
      }
    
      componentDidMount() {
        this._willBlurSubscription = this.props.navigation.addListener('willBlur', payload =>
          BackHandler.removeEventListener('hardwareBackPress', this._onBackButtonPressAndroid),
        );
      }
    
      componentWillUnmount() {
        this._didFocusSubscription && this._didFocusSubscription.remove();
        this._willBlurSubscription && this._willBlurSubscription.remove();
      }
    
      _onBackButtonPressAndroid = () => {
        this.navLeftClick();
        return true;//拦截返回按钮默认事件
      };
    
      renderNavLeftView = () => {
        return (
          <TouchableOpacity activeOpacity={1} onPress={this.navLeftClick}>
            <Text>返回</Text>
          </TouchableOpacity>
        );
      };
    
      navLeftClick = () => {
        if (!this.props.navigation.goBack()) {
          CommonModule.finish();
        } else {
          this.props.navigation.goBack();
        }
      };
    
      ......
    
    

    通过判断this.props.navigation.goBack()是否能返回,如果不能,表示RN的路由栈已经到底了,此时应该关闭当前页面(Activity、Controller),否则正常执行RN的出栈方法goBack()

    if (!this.props.navigation.goBack()) {
        CommonModule.finish();
      } else {
        this.props.navigation.goBack();
    }
    

    封装完之后就可以愉快的跳转页面了,不需要改动代码了

    源码:https://github.com/1280103995/RN-Android-iOS

    相关文章

      网友评论

          本文标题:React Native与原生(Android、iOS)混编,三

          本文链接:https://www.haomeiwen.com/subject/pobnbctx.html