xgrn-navigation

rn容器-路由模块

Usage no npm install needed!

<script type="module">
  import xgrnNavigation from 'https://cdn.skypack.dev/xgrn-navigation';
</script>

README

XGNavigation

RN导航框架

功能

原生页面与js页面导航模块

安装

私有库操作

nrm ls

如果提示nrm命令不存在,如下操作

npm install -g nrm

然后增加私有库地址并使用:


nrm add xgnpm  http://172.16.2.71:4873/
nrm use xgnpm
npm install

安装依赖

$ yarn add xgrn-navigation
$ react-native link xgrn-navigation

库升级

$ npm update xgrn-navigation  

项目配置

Android

  1. 修改你的MainApplication改为继承自NavigationApplication:
public class MainApplication extends NavigationApplication implements ReactApplication {
    ...
}

并实现NavigationApplication的抽象方法:

@Override
public boolean isDebug() {
  return BuildConfig.DEBUG;
}
  1. 修改你的MainActivity改为继承自SplashActivity:
public class MainActivity extends SplashActivity {

    /**
     * Returns the name of the main component registered from JavaScript.
     * This is used to schedule rendering of the component.
     */
    @Override
    protected String getMainComponentName() {
        return "";
    }
}

iOS

AppDelegate.m中


#import <XGNavigationController.h>
#import <XGRNBridgeManager.h>

/*------------------------------分割线--------------------------------*/

#pragma mark - 创建应用窗口
- (void)createWindowWithOptions:(NSDictionary *)launchOptions{
  
  // 获取js包的路径
  NSURL *jsCodeLocation;
#ifdef DEBUG
  jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
#else
  jsCodeLocation = [[TbbUpdateSDK standardSDK] getLocalBundleUrlAtReleasePackage];
#endif
  
  // 配置通用Bridge
  [[XGRNBridgeManager standardManager] loadBridgeWithBundleURL:jsCodeLocation andModuleName:@"jutong" launchOptions:launchOptions];
  
  // 初始化rootViewController
  self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  self.window.backgroundColor = [UIColor whiteColor];
  self.window.rootViewController = [XGNavigationController createWithLaunchOptions:launchOptions];
  
}


ios原生页面跳转js页面

#import <XGNavigation/XGNavigation.h>
/*------------------------------分割线--------------------------------*/

// 弱引用VC
__weak typeof(self) weakSelf = self;
JSCallBackBlock callBack = ^(NSDictionary * params){
    NSLog(@"%@---%@",params,weakSelf);
  };
[XGNavigation push:@{
                       @"pageType": @"JSPage",
                       @"pagePath": @"classify/GoodsSearchPage",
                       @"params": @{
                              @"onSubmitCallBack": callBack,
                           }
                       }
         andAnimated:YES];

JS页面代码规范示例

为了自动将应用路由信息标准格式化传递给电商配置中台。
项目中JS页面编写时应当遵循并定义如下参数规范:

页面props参数规范
static propTypes = {
    id: PropTypes.string.isRequired,
    name: PropTypes.string,
};

页面props参数描述
static propTypesDesc = {
    id: '商品id',
    name: '商品名称',
};

电商配置中台 和 是否需要登录 配置参数
static xgNavigationBarOptions = {
    needLogin: true,                //进入该页面是否需要登录
    showOnCMS: true,                //是否允许CMS配置中台配置跳转
    cmsPageTitle: '商品搜索页面',     //CMS展示的页面名称
    pageName: 'GoodsSearchPage',    //路由子path
};
当且仅当showOnCMS为true时,该页面才会加入路由上传表中,方可同步到CMS上。

用法

  1. 按要求写好文件层级,便于navigation识别你的js文件路径

  2. 修改你的入口App.js文件.仅用于根据配置生成一些的JSView,页面是大家在js中写的page。

//导入XGNavigation
import XGNavigation from 'xgrn-navigation';

export default class App extends Component<{}> {

    render() {
        //查询是否匹配路由
        const page = XGNavigation.renderPageByConfig(this.props);
        if (page && page !== 404) {
            return page;
        } else if (page == 404) {
            return (
                <View style={styles.container}>
                    <Text style={styles.welcome} onPress={this._onPress}>
                        {`App not found 404`}
                    </Text>
                </View>
            );
        }
        return (<LoadingView/>);
    }
}

3.路由跳转

XGNavigation会自动为你的每个子页面添加key为navigation的props属性,因此在你的页面中这样使用:

const {navigation} = this.props;
navigation.push({
    pagePath: home/HomePage
})

API介绍

1.pageType

作用:获取支持的页面类型

返回值:

return {
            JSPage: 'JSPage',               // JS页面
            NativePage: 'NativePage',       // 原声页面
            NativeTabPage: 'NativeTabPage', // 原声tab页面
        };


2.transitionsConfig

作用:获取支持的转场动画类型

返回值:

return {
            FromRight: 'FromRight',  // 从右到左
            FromBottom: 'FromBottom',// 从下到上
        };

3.appRoutes

作用:app注册的所有JS路由栈

返回值:

{}

5.initializeRoutes(routeConfig)

作用:初始化项目js页面路由信息

使用示例:@param routeConfig

XGNavigation.initializeRoutes([
    home,       //首页
    classify,   //分类
    shopping,   //购物车
    mine,       //我的
    debug,      //调试工具
    XGAuth      //登录、注册
]);

6.match(pathPath)

作用:根据pathPath匹配到页面组件

参数
@param pathPath 页面路由
返回值
组件Component或null

7.renderPageByConfig(initParams)

作用:根据初始化参数返回页面

参数
@param initParams
返回值
@return {*} 返回值可能为一个组件,也可能为404,如果initParams不存在pagePath返回null

8.resetRoot(pageConfig, animated)

作用:重置整个路由栈

@param pageConfig
    .pageType               String.页面类型:默认是JSPage,目前仅支持(JSPage,NativePage,NativeTabPage)
    .transitionsConfig      String.转场动画配置:默认是FromBottom,目前仅支持(FromRight,FromBottom,FromTop)
    .pagePath               String.页面路径(当pageType为JSPage,NativePage时需要需要指定页面初始化参数,代表js中页面的文件路径、或ios中viewcontroller的class名称、或android中active的class名称)
                    pagePath支持Object{ios: 'DemoViewController',android:'DemoActivety'}
    .params                 Object.页面初始化参数,可为空(当pageType为JSPage,NativePage时需要需要指定页面初始化参数)
    .tabConfigs             Tab配置(当且仅当pageType===NativeTabPage)
    .selectedIndex          默认选中的tab栏(可选)
    .tabCount               多少个tab(__ANDROID__)
    .tintColor              选中的Icon和title的颜色,可选项
    .unselectedItemTintColor 未选中Icon和title的颜色,可选项
    .barTintColor           tabbar的颜色,可选项
    .barTranslucent: false, tabbar半透明选项,barTintColor有值则该选项无效,效果不透明 (__IOS__)
    .pages                  Array<itemParams>.tab每一个item的配置
        .pageType               数据类型同上(此时不在支持NativeTabPage)
        .pagePath               数据类型同上
        .params                 数据类型同上
        .selectTabBarIcon       tab选中icon
        .unSelectTabBarIcon     tab非选中的icon
        .tabBarTitle            tab对应的title
@param animated 是否带动画

9.addPushPageInterceptor(pushInterceptor)

作用:添加页面push跳转拦截器函数(目前仅支持js调用时,能进行拦截)

使用示例:
@param targetPageComponent 待跳转页面Component
@param actionPush          继续执行跳转动作函数
XGNavigation.addPushPageInterceptor((targetPageComponent,actionPush)=>{
    const {needLogin} = targetPageComponent.xgNavigationBarOptions || {};
    if (needLogin && !userMobx.phone){
        setTimeout(()=>{
            actionPush();
        },1000);
        return false;
    }
    return true;
});

10.getRouteStack()

作用:获取整个路由栈信息

返回为一个route数组:

[{
    pagePath: 'TMMarketTabPage',
    pageType: 'NativeTabPage',
    uniqueId: 'xxxxx',          //唯一id
    index: 0,
    tabs: [{...}, {...},数据结构同下]
},
{
    pagePath: 'TMMarketTabPage',
    pageType: 'JSPage',
    uniqueId: 'xxxxx',
    index: 1,
},
{
    pagePath: 'TMMarketTabPage',
    pageType: 'NativePage',
    uniqueId: 'xxxxx',
    index: 2,
}]

10.push(pageConfig, animated)

作用:push进入下一个页面(原生或者ios),一般情况下只需要填下pagePath,剩下的参数会自动补全

@param pageConfig Object Required
         .pageType          String.页面类型:默认是JSPage,目前仅支持(JSPage,NativePage,NativeTabPage)
         .transitionsConfig String.转场动画配置:默认是FromBottom,目前仅支持(FromRight,FromBottom,FromTop)
         .pagePath          String.页面路径(当pageType为JSPage,NativePage时需要需要指定页面初始化参数,代表js中页面的文件路径、或ios中viewcontroller的class名称、或android中active的class名称)
             pagePath支持Object{ios: 'DemoViewController',android:'DemoActivety'}
         .params            Object.页面初始化参数,可为空(当pageType为JSPage,NativePage时需要需要指定页面初始化参数)
         .tabConfigs        Tab配置(当且仅当pageType===NativeTabPage)
            .selectedIndex      默认选中的tab栏(可选)
            .tabCount           多少个tab(__ANDROID__)
            .tintColor          选中的Icon和title的颜色,可选项
            .unselectedItemTintColor 未选中Icon和title的颜色,可选项
            .barTintColor       tabbar的颜色,可选项
            .barTranslucent: false, // tabbar半透明选项,barTintColor有值则该选项无效,效果不透明 (__IOS__)
            .pages       Array<itemParams>.tab每一个item的配置
                 .pageType           数据类型同上(此时不在支持NativeTabPage)
                 .pagePath           数据类型同上
                 .params             数据类型同上
                 .selectTabBarIcon   tab选中icon
                 .unSelectTabBarIcon tab非选中的icon
                 .tabBarTitle        tab对应的title

11.replaceStackTopRoute(pageConfig,animated)

作用:替换栈顶页面

@param pageConfig    新页面配置(具体内容同push函数的pageConfig)
@param animated      是否需要动画

12.pop(animated)

作用:返回上一级页面

@param animated 是否带动画

13.popToRoute(route, animated)

作用:返回到指定页面,每个子页面可使用this.props.route获取自己的route。

@param animated 是否带动画(ANDROID默认动画,不支持animated参数)

14.popToRoot(animated)

作用:返回到路由栈底

@param animated 是否带动画(ANDROID默认动画,不支持animated参数)

15.setTabBarSelectedIndex(route, newIndex)

作用:设置tab页面选中的index

@param route     当前tab子页面的route
@param newIndex  需要选中的下标
注意:如果当前route对应的页面并不存在于Tab容器里,则不执行任何效果

16.isChildTab(route)

作用:判断页面是否处于tab容器中

@param route 当前页面的route
@return {Promise.<bool>} 是否为tab容器里的子页面

17.showLaunchImageOnWindow()

作用:主动添加LaunchImage在window上进行占位显示((如果当前splash已经在展示中,则doNothing)

18.removeLaunchImageFromWindow()

作用:主动从window上移除LaunchImage((如果LaunchImage不存在,则doNothing)

19.allowPopGestureRecognizer(route)

作用:开启关闭侧滑手势(仅支持IOS)

@param route             路由
@param allowPopGesture   侧滑手势是否打开
如果route为tabbar上面的一个子页面,则allowPopGesture对tabbar生效

原生和JS页面跳转,携带函数问题和回到函数问题。

this.props.navigation.push({
    pageType: this.props.navigation.pageType.NativePage,
    pagePath: {//原生页面的文件名
        ios: 'WebController',
        android: 'com.xg.jsbridge.WebViewFragment',
    },
    params: {
        url: 'https://www.baidu.com',
        jumpCallBack: (obj)=>{
            console.warn(obj)
        }
    },
});

弹框。

首先要全局初始化注入组件。

// 初始化弹框(仅限即支持原声调用、又支持JS调用)
XGNavigation.initializeAlert([
    AcquireCouponsAlert
]);
组件内部要书写,这样方便通过字符串的alertPath映射到组件
static alertPath = 'AcquireCouponsAlert';
放置到当期ViewController的View上,或当前frament上,(ios可以侧滑退出)
this.props.navigation.alert({
            alertPath: 'AcquireCouponsAlert',//这个弹框组件所书写的static alertPath
            bindRoute: this.props.route,
            params:{
                //组件props的属性
                spuIdList: this.spuList,
                type: 'ShopCar',
                pickerNewCoupons: ()=>{
                    //用户在优惠券弹框内选择了新的弹框,刷新优惠券提示语(静默,不loading)
                    this._getCouponTipInfo(this.totalAmount);
                }
            }
});
放置到当期NavigationController的View上或window上,或当前Activity上,(ios 不可以侧滑退出) 
this.props.navigation.alert({
            alertPath: 'AcquireCouponsAlert',//这个弹框组件所书写的static alertPath
            bindRoute: this.props.route,
            params:{
                  //组件props的属性
                  spuIdList: this.spuList,
                  type: 'ShopCar',
                  pickerNewCoupons: ()=>{
                        //用户在优惠券弹框内选择了新的弹框,刷新优惠券提示语(静默,不loading)
                        this._getCouponTipInfo(this.totalAmount);
                  }
            }
});

导航条配置与页面基础状态管理

为了方便业务层能够快速构建页面,toast/alert/loading能力,不用太多的关注页面加载中、加载失败、加载成功的切换, 或单个组件的加载状态(加载中、加载失败、加载成功),以及导航条最基础的配置。
在初始化基础路由配置以后:

XGNavigation.initializeJSRoutes([
    home,       //首页v1.1.1
    coupons,    //优惠券
    productList,//商品列表
    shopping,   //购物车
    mine,       //我的
    debug,      //调试工具
    cmsPage,    //CMS页面
    XGAuth,     //登录、注册
    poi,        //poi地址搜索
]);

框架内部会对每一个路由页面的Component进行重写,在Component的原型链中添加toast/alert/loading能力。 具体可以浏览PageDecorator文件源码。

  /* -------------toast-------------  */
  target.prototype.xg_toastShow = function (title, params) {
    if (!this.xg_toast) {
      return;
    }
    this.xg_toast.showToast(title, params || {});
  };
  target.prototype.xg_toastDismiss = function (callBack) {
    if (!this.xg_toast) return;
    this.xg_toast.dismiss(typeof callBack === 'function' ? callBack : null);
  };
  target.prototype.xg_isToastShow = function () {
    if (!this.xg_toast) return false;
    return this.xg_toast.isToastShow();
  };


  /* -------------loading----------  */
  target.prototype.xg_loadingShow = function (msg, params) {
    if (!this.xg_loadingHub) return;
    this.xg_toastDismiss();
    this.xg_loadingHub.loadingShow(msg, params || {});
  };
  target.prototype.xg_loadingDismiss = function (callBack) {
    if (!this.xg_loadingHub) return;
    this.xg_loadingHub.dismiss(typeof callBack === 'function' ? callBack : null);
  };
  target.prototype.xg_isLoadingShow = function () {
    if (!this.xg_loadingHub) return false;
    return this.xg_loadingHub.isLoadingShow();
  };


  /* -------------alert----------  */
  target.prototype.xg_alertShow = function (params) {
    this.xg_alertView && this.xg_alertView.alertShow(params);
  };
  target.prototype.xg_alertDismiss = function (callBack) {
    this.xg_alertView &&  this.xg_alertView.dismiss(callBack);
  };
  target.prototype.xg_isAlertShow = function () {
    if (!this.xg_alertView) return false;
    return this.xg_alertView.isAlertShow();
  };



  /* -------------XGNavigatorBar-------------  */
  target.prototype.xg_NavigationBarHiddenLeftItem = function (hidden, callBack) {
    //隐藏左边item
    if (!this.xg_navigatorBar) return;
    this.xg_navigatorBar.hiddenLeftItem(hidden, callBack);
  };
  target.prototype.xg_NavigationBarHiddenRightItem = function (hidden, callBack) {
    //隐藏右边item
    if (!this.xg_navigatorBar) return;
    this.xg_navigatorBar.hiddenRightItem(hidden, callBack);
  };
  target.prototype.xg_NavigationBarResetTitle = function (newTitle, callBack) {
    //更换title
    if (!this.xg_navigatorBar) return;
    this.xg_navigatorBar.changeTitle(newTitle, callBack);
  };

这样这些组件就有了基础的动态设置导航和loading/alert/toast能力。 其实就是底层重写了组件本身的render函数

  const targetRender = ComponentClass.prototype.render;
  ComponentClass.prototype.render = function () {

    const {renderByPageState} = xgNavigationBarOptions;

    let controlParams = null;

    if (renderByPageState) {
      try {
        controlParams = this.xg_getPageStateOptions();
      } catch (error) {
        console.warn('when you set true, you need implementation xg_getPageStateOptions function to return your page state');
        controlParams = {};
      }
    }

    const that = this;

    return (<View style={styles.container}>

      <XGNavigatorBar {...xgNavigationBarOptions}
        leftPressed={()=> (this.xg_NavigationBarLeftPressed || xgNavigationBarDefaultLeftPressed).call(this)}
        rightPressed={()=>(this.xg_NavigationBarRightPressed || xgNavigationBarDefaultRightPressed).call(this)}
        ref={(bar)=>{this.xg_navigatorBar = bar;}}/>

      {
        renderByPageState ? renderViewByLoadingState(controlParams, () => {
          return targetRender.call(that);
        }) : targetRender.call(this)
      }

      <AlertView ref={(alertView) => {this.xg_alertView = alertView;}}/>

      <ToastView ref={(toast) => {this.xg_toast = toast;}}/>

      <LoadingHub ref={(loadingHub) => {this.xg_loadingHub = loadingHub;}}/>

    </View>);
  };

对于页面组件,要求大家书写static xgNavigationBarOptions配置。用于配置基础导航条,路由信息等。。。

如果需要设置页面状态管理

在xgNavigationBarOptions配置中renderByPageState设置为true 组件需要实现xg_getPageStateOptions函数,返回当前想要渲染的页面内容。 例如: 具体可以参考PageState文件实现

/**
     * 返回当前页面状态,应该有的状态
     * @return object 页面当前应该展示的状态
     */
    xg_getPageStateOptions = ()=>{
        return {
            loadingState: this.state.loadingState,
            emptyProps: {
                description: `暂无可用的优惠券`,
                isScrollViewContainer: true,
                isRefresh: this.state.refreshing,
                source: EmptyImg,
                onRefresh: this._onRefresh,
            },
            netFailedProps: {
                netFailedInfo: this.state.error,
                callback: () => {
                    this.setState({
                        loadingState: PageLoadingState.loading
                    },()=>{
                        this._loadDataFirstPage();
                    });
                }
            }
        };
    };