android ios 混合编程,React Native与原生(Android、iOS)混编,三端痛点解析

在做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 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 (

);

}

}

三、路由问题

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 (

返回

);

};

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();

}

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值