Skip to content

Latest commit

 

History

History
1836 lines (1456 loc) · 57 KB

README_CN.md

File metadata and controls

1836 lines (1456 loc) · 57 KB

react-view-router

一个路由配置化的路由管理组件,支持各种路由事件拦截、重定向操作。

安装

npm install react-view-router --save
# or
yarn add react-view-router

基本用法

示例

/// router.js
import ReactViewRouter from 'react-view-router';
import routes from './routes';
const router = new ReactViewRouter({
  basename: '',     // app的basename,若页面路由是在/app/下面,则base的值应该为"/app/"
  mode: 'hash',    // 路由类型 browser|memory|hash, default:hash
  routes: routes   // 路由配置数组,也可以使用router.use方法来初始化
});
   
export default router;
/// routes.js
import { normalizeRoutes, lazyImport } from 'react-view-router';
import Home from './home';
import Login from './login';

const routes = normalizeRoutes([
  {
    path: '/home',
    component: Home
    // 子路由
    children: [
      // 路径重定向
      { path: '/', redirect: 'main' },
      // 默认(索引)路径
      // { path: '/', index: 'main' },
      {
        path: 'main',
        // 懒加载
        component: lazyImport(() => import(/* webpackChunkName: "home" */ './home/main')),
        // 路由元信息
        meta: { needLogin: true },
        children: [
          // 重定向
          { path: '/', redirect: to => ({ path: 'some', query: { aa: 1, bb: 2 } }) },
          {
            path: 'some',
            components: {
              default: lazyImport(() => import(/* webpackChunkName: "home" */ './home/main/some')),
              footer: lazyImport(() => import(/* webpackChunkName: "home" */ './home/main/some/footer.js')),
            }
            // 路由守卫:
            beforeEnter(to, from, next) { next(); }
            beforeLeave(to, from, next) { next(); }
            beforeUpdate(to, from) {}
            afterLeave(to, from) {}
          }
        ]
      }
    ]
  },
  {
    path: '/login',
    component: Login
  }
])
/// app.js
import React from 'react';
import { RouterView } from 'react-view-router';
import router from './router';

router.beforeEach((to, from, next) => {
  if (to) {
    console.log(
      '%croute changed',
      'background-color:#ccc;color:green;font-weight:bold;font-size:14px;',
      to.url, to.query, to.meta, to.redirectedFrom
    );
    return next();
  }
});

function App() {

  const filter = routes => routes.filter(r => r.meta.visible !== false);

  return (
    <div>
      <h1>App</h1>
      {
        // 跟RouterView需要router参数 
      }
      <RouterView 
        router={router}
        fallback={<div>loading</div>}
      />
    </div>
  );
}
/// home/index.js
import React from 'react';
import { RouterView } from 'react-view-router';

export default function HomeIndex() {
  return (
    <div>
      <h1>HomeIndex</h1>
      <RouterView />
    </div>
  );
}
/// home/main/index.js
import React from 'react';
import { RouterView } from 'react-view-router';

export default function HomeMainIndex() {
  return (
    <div>
      <h1>HomeMainIndex</h1>
      <RouterView />
      <RouterView name="footer" />
    </div>
  );
}

命名视图

/// home/main/some/index.js
import React from 'react';
import { withRouteGuards } from 'react-view-router';
import store from 'store';

class HomeMainSomeIndex extends React.Component {
  constructor(props) {
    super(props);
    this.state = { text: 'text1' };
  }

  refresh = () => {
    this.setState({ text: 'text1 refreshed' })
  }

  render() {
    let { text } = this.state;
    return (
      <div>
        <h1>HomeMainSomeIndex</h1>
        { text }
      </div>
    );
  }
}

export default withRouteGuards(HomeMainSomeIndex, {
  beforeRouteEnter(to, from, next) {
    if (!store.logined) next('/login');
    else next(vm => vm.refresh());
  },
  beforeRouteLeave(to, from, next) {
    // 可以在这里确认是否离开
    next();
  },
  beforeRouteUpdate(to, from) {

  },
  afterRouteLeave(to, from) {

  },
});
/// home/main/some/footer.js
import React from 'react';

export default function HomeMainSomeFooter() {
  return (
    <div>
      <h1>HomeMainSomeFooter</h1>
    </div>
  )
}
/// login/index.js
import React from 'react';
import store from './store';
import router from './router';

export default function LoginIndex() {
  const doLogin = () => {
    store.logined = true;
    router.push({
      path: '/home',
      query: { aa: 1 }
    }, () => {
      console.log('router.push is complete!');
    }, () => {
      console.log('router.push is abort!');
    });
  };

  return (
    <div>
      <h1>LoginIndex</h1>
      <button onClick={doLogin}>Login</button>
    </div>
  );
}

APIs

Route 配置选项

  • name 路由名称,相当于别名,路由名称在一个router的所有路由配置中应该唯一,可以通过它调整路由,如:router.push('[aRouteName]/somepath')name中可以有以下字符:A-z.\-_#@$%^&*():|?<>=+
  • path URL字符串。
  • component 路由对应的组件。
  • components 路由对应的组件,可用于命名RouterViewcomponent对应于components.default
  • exact 是否严格匹配location.pathname
  • redirect 路由将会重定向到一个新地址,可以是字符串、对象或函数。
  • index 默认路由名称,可以是字符串或函数。
  • children 嵌套的子路由配置信息。
  • meta 路由的自定义元信息, 参见: 路由元信息
  • metaComputed 路由配置中的meta信息(已完成计算)。当meta中某个值为函数时,则将会将其当做RouteMetaFunction函数执行结果当做值。
  • defaultProps 值为对象,格式: { aa: 1, bb: '2', cc: true }, 渲染路由组件时添加额外的pros参数。
  • props boolean或对象,格式: { aa: Number, bb: String, cc: Boolean }, 将路由的params参数中指定的属性当做props传递给路由组件,是下面的paramsProps的别名。
  • paramsProps boolean或对象,格式: { aa: Number, bb: String, cc: Boolean }, 将路由的params参数中指定的属性当做props传递给路由组件。
  • queryProps boolean或对象,格式:{ aa: Number, bb: String, cc: Boolean }, 将路由的query参数中指定的属性当做props传递给路由组件。
  • guards 路由守卫, 指beforeEnterbeforeLeavebeforeUpdateafterLeave,参见:Per-Route Guard
  • isComplete boolean,路由是否完成拦截处理。在路由跳转过程中,经常需要重定向处理,对于最终未完成跳转的路由对象RouteisComplete将为false

RouterView的Props

  • name 用于命名视图, 参见vue-router说明
  • filter 函数: function (routes: Array) { return [] },可以用它过滤匹配的路由
  • fallback 可以是一个函数:function ({ parentRoute, currentRoute, inited, resolving, depth }) { return <div /> }, 或者React Component Element,比如:<Loading>loading</Loading>

RouterLink的Props

RouterLink组件是一个类似react-router中的Link组件,它可以实现路由调整,以及根据是否匹配当前路由从而高亮自己。

使用示例:

import React from 'react';
import { RouterLink } from 'react-view-router';

export default function HomeIndex() {
  return (
    <div>
      <RouterLink tag="a" to={{ path: '/login' }}>登录</RouterLink>
      <RouterLink tag="a" to="/admin" replace>设置</RouterLink>
    </div>
  );
}

router: ReactViewRouter

RouterLink关联的ReactViewRouter实例,当RouterLink在跟RouterView同级或之上时,该属性为必须;否则RouterLink给根据React组件层级关系自动寻找关联的ReactViewRouter实例。

to: RouteLocation|string

表示目标路由的链接。当被点击后,内部会立刻把to的值传到router.push()router.replace(),所以这个值可以是一个字符串或者是描述目标位置的对象。对象格式为:

{
  // 跳转路由地址
  path?: string,
  // 查询参数
  query?: Partial<any>,
  // 动态参数
  params?: Partial<any>,
  // 是否在当前路由上追加
  append?: boolean,
  // 是否是绝对地址
  absolute?: boolean | 'hash' | 'browser' | 'memory',
  // 跳转前的回退步数,整数表示前进,负数表示后退
  delta?: number,
  // 如果要跳转的路由在历史路由栈中已经有时,则进行回退,而不是push/replace,该参数跳转了一系列路由后会首页,又不想产生新的路由历史时会比较有用
  backIfVisited?: boolean,
  // 如果该参数为true,则在调用`push`、`replace`方法时,如果当前`router`未完成准备(`!router.isPrepared`)并且处在拦截器处理中(`beforeEach`、`beforeRouterEnter`、`beforeRouterLeave`)时则不会解决拦截器的处理,而是改为设置`pendingRoute`;
  pendingIfNotPrepared?: boolean,
}

replace: boolean

设置replace属性的话,当点击时,会调用router.replace()而不是router.push(),于是导航后不会留下history记录。

append:boolean

设置append属性后,则在当前 (相对) 路径前添加基路径。例如,我们从/a导航到一个相对路径b,如果没有配置append,则路径为/b,如果配了,则为/a/b。该属性和跳转对象中的append等效

tag: string|React.ElementType

有时候想要RouterLink渲染成某种标签,例如<li>。 于是我们使用tag属性指定何种标签,同样它还是会监听点击,触发导航。

activeClass: string = 'router-link-active'

RouterLink匹配当前路由激活时添加的class类名;

exact: boolean

是否精确匹配时才激活RouterLink组件。“是否激活”默认类名的依据是包含匹配。 举个例子,如果当前的路径是/a开头的,那么RouterLink也会被设置CSS类名。按照这个规则,每个路由都会激活<RouterLink to="/" />!想要链接使用“精确匹配模式”,则使用exact属性:

exactActiveClass: string = 'exact-active-class'

配置当链接被精确匹配的时候应该激活的class

event: 'click'|'mouse-enter'|'mouse-leave'等React支持的组件事件名 = 'click'

配置触发跳转事件的事件名,可以是任意React支持的组件事件。

onRouteChange: (route: Route) => void

路由发生变更时的事件

onRouteActive: (route: Route) => void

RouterLink组件匹配当前路由时触发

onRouteInactive: (route: Route) => void

RouterLink组件从匹配状态变为非匹配状态时触发

ReactViewRouter的options选项

options选项用在创建ReactViewRouter实例中,或者usestart方法参数上。它支持下面几个参数:

  • name: string 你可以为该ReactViewRouter提供一个名称,方便识别

  • basename: string 该路径组件的基路径

  • mode: 'hash'|'memory'|'browser'|History - 路由模式

  • hashType: 'slash'|'noslash'modehash时,配置hash路径是否以/开头

  • pathname: string - 在memory路由模式时使用,用于指定memory路由的初始路由地址

  • history: History - ReactViewRouterhistory对象,不传时ReactViewRouter将内部创建它,该属性一般用在路由为memory模式,通过将其的router.history传递给子应用的 ReactViewRouter, 这样子应用的memory路由可以实现和父应用的memory路由联动。

  • routes: ConfigRoute[] - 所有的路由配置列表

  • queryProps: { [key: string]: (val: string) => any; } url参数类型转换对象

  • manual: boolean 是否手动模式,表示在new ReactViewRouter()时不自动调用start方法。这时需要你手动调用start方法来启用路由监听后,ReactViewRouter实例才真正开始工作

  • rememberInitialRoute: boolean 是否记住initialRoute。当为true时,在刷新浏览器页面时,initialRoute不再是记录的当前url中的路由信息,而是从sessionStroage中检索到的第一次的路由信息。该功能主要是为了解决刷新页面导致初始化url参数丢失的问题

  • holdInitialQueryProps: boolean - 是否在调用pushreplace等路由跳转方法时,把初始路由(initialRoute)的query合并到跳转路由的query中。该参数在一些需要一直保持url参数的系统中将会比较有效;

ReactViewRouter属性

  • currentRoute 当前匹配的路由信息:
{
  // 当前路由的操作类型,有:POP、PUSH、REPLACE
  action: String,
  // 当前路由的路径
  url: String,
  // 当前路由的路径,和url的区别是path可能是:/home/dd/:id这种,url会将id转换为具体数值
  path: String,
  // 全路径,包括路径和查询条件
  fullPath: String,
  // 是否严格匹配location.pathname
  isExact: Boolean,
  // 一个数组,包含当前路由陪陪的所有嵌套路由信息
  matched: [
    route1,
    // route2:
    {
      // 来自路由配置中的path,和subpath的区别是:path转换为了绝对路径,subpath等于原值
      path: String,
      // 来自路由配置中的path
      subpath: String,
      // 路由配置中的meta信息
      meta: Object,
      // 路由配置中的meta信息(已完成计算)
      metaComputed: Object,
      // 路由的状态信息,可以通过router.replaceState(state: State, matchedRoute?: MatchedRoute)来更新指定matchedRoute的state
      state: Object,
      // 路由配置的中的redirect
      redirect: String|Object|Function,
      // 原始的路由配置对象
      config,
      // 匹配该路由的组件实例
      componentInstances: {
        default: React.Component,
        /* others: React.Component */
      }
      // 匹配该路由的RouterView实例
      viewInstances: {
        default: RouterView
      }
    }
    ..., 
    routeN
  ],
  // 解析url得来的一个键值对像
  params: Object,
  // 解析url中?后面的查询参数得来的键值对像。例如:/foo?user=1, 则currentRoute.query.user == 1. 如果没有查询参数,这它是一个空对象。
  query: Object,
  // 当前路由的元信息,等同于matched[matched.length - 1].meta
  meta: Object,
  // 路由配置中的meta信息(已完成计算)
  metaComputed: Object,
  // 当前路由的state信息,等同于matched[matched.length - 1].state
  state: Object,
  // 指示该路由是否是从哪个路由重定向过来的
  redirectedFrom: Object,
  // 来自`router.push`, `router.replace`, `router.redirect`路由的取消回调函数
  onAbort: Function,
  // 来自`router.push`, `router.replace`, `router.redirect`路由的完成回调函数
  onComplete: Function,
}

示例

参见: Route Object Properties

ReactViewRouter 实例属性

  • id - 路由实例id,数字类型,用于标识一个路由实例的唯一性;

  • name - 路由实例名称,在创建路由实例时,可以传递一个name参数为该路由实例取个名字以方便识别;

  • currentRoute - 匹配当前url的当前路由信息。

  • initialRoute - 当实例创建时的初始路由对象。

  • prevRoute - 前一个路由信息。

  • mode - 路由模式,值有: hashbrowsermemory

  • basename - 当前路由实例的路由前缀,当不为空时始终以/结尾;

  • basenameNoSlash - 当前路由实例的路由前缀,当不为空时始终不以/结尾;

  • parent - 当前路由实例的父路由实例(如果有的话);

  • top - 当前路由实例的最顶层路由实例,当等于自己时,标识自己就是最顶层的路由实例;

  • children - 当前路由实例的子路由实例列表;

  • viewRoot - 和当前路由实例关联的跟RouterView

  • stacks: { index: number, pathname: string, search: string, timestamp: number }[] - 路由堆栈,里面包含了当前跳转过的路由信息,通过它可以查看当前页面已经跳转过哪些路由,可以通过 go 方法前进/回退到该路由,或者通过getMatched获取该路由匹配的所有路由配置。示例:

    let stack = router.stacks.find(v => v.pathname.startsWith('/home'));
    if (stack) {
      let matched = router.getMatched(stack);
      // 如果是主页,就回退到该页
      if (matched.some(r => r.meta.isHome)) router.go(stack);
    }
    • isRunning - router实例是否在运行状态,当为false时将不会响应路由事件;

    • isRunning - router实例是否在运行状态,当为false时将不会响应路由事件;

    • isPrepared - 当前路由是否已准备好 - 根路由currentRoute已经生成完成;

    • isHistoryCreater - 是否是内部的history的创建者;

    • isBrowserMode - 是否是browser路由模式;

    • isHashMode - 是否是hash路由模式;

    • isMemoryMode - 是否是memory路由模式;

ReactViewRouter 实例方法

const router = new ReactViewRouter({ ... })

router.beforeEach((to, from, next) => {
  // ...
})

当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中。

每个守卫方法接收三个参数:

  • to: Route: 即将要进入的目标 路由对象

  • from: Route: 当前导航正要离开的路由

  • next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。

    • next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。

    • next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。

    • next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: true之类的选项。

    • next(error): 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。

确保 next 函数在任何给定的导航守卫中都被严格调用一次。它可以出现多于一次,但是只能在所有的逻辑路径都不重叠的情况下,否则钩子永远都不会被解析或报错。

** 这里有一个在用户未能验证身份时重定向到 /login 的示例:

// BAD
router.beforeEach((to, from, next) => {
  if (to.path !== '/login' && !isAuthenticated) next({ name: 'Login' })
  // 如果用户未能验证身份,则 `next` 会被调用两次
  next()
})
// GOOD
router.beforeEach((to, from, next) => {
  if (to.path !== '/login' && !isAuthenticated) next({ path: '/login' })
  else next()
})

注意:在beforeEach中,调用router.pushrouter.replacerouter.redirect等同于调用next方法。即下面2中写法作用是相同的:

function checkLogin(next) {
  if (to.path !== '/login' && !isAuthenticated) {
    next({ path: '/login' });
    return false;
  }
  return true;
}

router.beforeEach((to, from, next) => {
  if (!checkLogin(next)) return;
  next()
})
function checkLogin() {
  if (to.path !== '/login' && !isAuthenticated) {
    router.push({ path: '/login' });
    return false;
  }
  return true;
}

router.beforeEach((to, from, next) => {
  if (!checkLogin()) return;
  next()
})

beforeResolve 全局解析守卫

你可以用 router.beforeResolve 注册一个全局守卫。这和 router.beforeEach 类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。

你也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身:

router.afterEach((to, from) => {
  // ...
})

pushreplacegobackforward redirect编程式的导航

这些方法的参数可以是一个字符串路径,或者一个描述地址的对象。例如:

// 字符串,相对路径
router.push('home')
// 字符串,绝对路径
router.push('/home')

// 对象
router.replace({ path: '/home' })

// 从上一层路由开始跳转
router.push({ path: '../home' });

// 带动态参数
router.push({ path: '/user', params: { userId: '123' }})

// 带查询参数,变成 /register?plan=private
router.push({ path: '/register', query: { plan: 'private' }})

// 当router的basename不为空时,如果想跳转时忽略basename,则可以添加个absolute参数
router.push({ path: '/register', absolute: true, query: { plan: 'private' }})

// 通过路由名称跳转
router.push({ path: '[a-route-name]/register', })

// 返回前一个路由
router.go(-1);

// 等同于router.replace,只是路由对象里会多一个isRedirect: true的标志
router.redirect({ path: '/home' })

// 先回退到两步之前,在跳转到home路由
router.push({ path: '/home', delta: -2 });

// 跳转到外部地址,等同于location.href = ''
router.push('http://www.baidu.com')

可选择在router.pushrouter.replace 中提供 onCompleteonAbort 回调作为第二个和第三个参数。这些回调将会在导航成功完成 (在所有的异步钩子被解析之后) 或终止 (导航到相同的路由、或在当前导航完成之前导航到另一个不同的路由) 的时候进行相应的调用。可以省略第二个和第三个参数,此时如果支持Promiserouter.pushrouter.replace 将返回一个 Promise

router.pushrouter.replacerouter.redirect的第一个参数对象格式可以支持以下参数:

{
  // 调整的路径,可以是完整的url,也可以是`/`开头的绝对路径,或者'./'、'../'这样的相对路径
  path?: string,
  // 查询参数,即url中?后面的参数部分
  query?: Partial<any>,
  // 当path非完整url,并且不是以'/'、'./'、'../'开头时,path会识别成替换当前路径的最后一节子路径。当append为true是,则表示在当前路径后面追加。
  append?: boolean,
  // 当router的basename不为空时,除了完整url,都会在最终跳转路径之前添加上basename,如果不想让router添加,可以设置absolute为true
  // absolute也可以是取'hash' | 'browser' | 'memory'值,router默认将父路由的模式与自己相同,但当router为memory模式时,是无法推算副路由的模式的,这时可以通过absolute设置父路由模式方便router识别要跳转的方式
  absolute?: boolean|string,
  // 表示跳转该路径前,先前进/回退`delta`步路由历史后再跳转
  delta?: number,
  // 当path为'./'、'../'或其他相对路径时的参考路由,在ReactViewLike组件中,可以通过this.$matchedRoute取到
  route?: ConfigRoute,
}

parseQuerystringifyQuery

内部使用的url的query解析/字符串化方法,可以通过new ReactViewRouter({ parseQuery: parseQueryMethod, stringifyQuery: stringifyQueryMethod });来复写。

注:默认的解析器会将query中的truefalsenullundefinedJSON对象/数组解析成对应类型,而非字符串。

createRoute(to: string | object, options: { from?: object, action?: string, matchedProvider?: object })

string{ path: string, query: {} }类型的路由信息根据转换成和router.currentRoute一样的格式,并且已经解析好matched,你可以通过它来获取该地址匹配的路由配置信息。

nameToPath(name: string, options: { absolute?: boolean }|RouteHistoryLocation = {}) => string

将某个路由名称转换为对应的路径,可以通过该方法判断某个路由名称是否存在。options.absolute表示是否是绝对路径,如果是,则也将会从父路由的中寻找路由名称。

resolveRouteName(fn: RouteResolveNameFn) => () => void

注册一个路由名称解析函数,当nameToPath无法从路由配置中找到对应的路由名称时,会调研这些解析函数解析。返回值是一个回调函数,用于注销该解析函数。

函数签名RouteResolveNameFn格式为:

  (name: string, options: { absolute?: boolean }|RouteHistoryLocation, router: ReactViewRouter) => string|null|void

当解析函数返回值不为nullundefined时,表示找到对应的路径,返回值将会当做该路由名称的路径返回

  • 注:如果options.absolutetrue,请自行处理它的basename

getMatched(to: string | object, from?: object, parent?: object)

根据string{ path: string, query: {} }类型的路由信息获取它能匹配的所有路由配置,你可以通过匹配的路由配置中取得的信息做一些事情。下面是一段该方法在实际场景中的应用:

```js
/**
 * 根据路由获取当前菜单路径,例如:个税申报>>申报总览,用于在线咨询入参
 * @param {Object} route 当前路由
 */
getCurrentMenuPosition(route) {
  let position = '';
  route = route || router.currentRoute;
  if (!route.query.redirect) return position;
  const rs = router.getMatched(route.query.redirect).filter((r) => r.meta.title);
  if (rs.length) {
    position = rs.reduce((p, c) => {
      let title = c.meta.title;
      return (p ? `${p}>>${title}` : title);
    }, '');
  }
  return position;
},
```

replaceState(newState: Partial<any>, matchedRoute?: MatchedRoute)

更新当前路由(currentRoute)或者指定matchedRoutestate,这样当从其他页面回退到该路由时,将可以通过router.currentRoute.staterouter.currentRoute.matched[N].state取到当时设置的state值。

import React, { useEffect } from 'react';
import router from '@/router';

function Test() {
  const setRouteState = () => {
    router.replaceState({ haha: 1, a: '1' });
  }

  useEffect(() => {
    console.log(router.currentRoute.state);
  }, []);

  return <div>
    <Button onClick={setRouteState}>设置State</Button>
  </div>;
}

replaceQuery(keyOrObj: string, value?: any)

更新当前路由(currentRoute)的query中的指定参数。如果routermodebrowser|hash,同时也会更新url上对应的参数;

import React, { useEffect } from 'react';
import router from '@/router';

function Test() {
  const updateQueryTest = () => {
    router.replaceQuery('test', 1);
  }

  const updateQueryObj = () => {
    router.replaceQuery({
      test: 1,
      aaa: 2
    });
  }

  return <div>
    <Button onClick={updateQueryTest}>更新query中的test</Button>
    <Button onClick={updateQueryObj}>更新query中多个参数</Button>
  </div>;
}

plugin(plugin: ReactViewRoutePlugin): () => void

注册一个ReactViewRouter插件,该插件可以用于监听路由中的各种事件,并做一些处理。如:

const uninstall = router.plugin({
  name: 'a-router-plugin',
  onRouteChange(route, router) {
    console.log('route changed', route);
  })
});

...sometimes...
uninstall();

注意:插件名如果相同将被认作同一个插件,注册该插件时,会先将原先同名的插件卸载掉再注册

插件支持的方法有:

type onRouteChangeEvent = (route: Route, prevRoute: Route, router: ReactViewRouter, prevRes?: any) => void;
type onRouteMetaChangeEvent = (newVal: any, oldVal: any, route: ConfigRoute, router: ReactViewRouter, prevRes?: any) => void;

interface ReactViewRoutePlugin {
  // 插件名称
  name: string;
  // 插件的装载方法,在调用`router.plugin()`方法中会被调用
  install?(router: any): void;
  // 插件卸载方法,在调用`router.plugin()`注册插件时发现同名插件时会uninstall该插件,再注册。或者是调用`router.plugin()`返回的卸载方法时也会调用uninstall
  uninstall?(router: any): void;

  // 当启动路由监听时调用
  onStart?(router: ReactViewRouter, routerOptions: ReactViewRouterOptions, isInit: boolean|undefined, prevRes?: any): void;
  // 当停止路由监听时调用
  onStop?(router: ReactViewRouter, isInit: boolean|undefined, prevRes?: any): void;

  // 当router.routes发生改变时(use设置routes、调用addRoutes添加路由)被触发,当routes === originRoutes时表示在在原有routes里新增了路由;
  onRoutesChange?(
    routes: ConfigRouteArray,
    originRoutes: ConfigRouteArray,
    parent?: ConfigRoute|null,
    parentChildren?: ConfigRouteArray,
    prevRes?: any
  ): void;

  // 当调用router.push、router.replace、router.redirect方法时被触发,你可以在该事件里中断默认的路由跳转行为,改为你自己的操作;
  onRouteGo?(
    to: RouteHistoryLocation,
    onComplete: (res: any, _to: Route|null) => void,
    onAbort: (res: any, _to: Route|null) => void,
    isReplace: boolean,
    prevRes?: any
  ): void|boolean;

  // 路由进入的守卫事件
  onRouteEnterNext?(route: MatchedRoute, ci: React.Component, prevRes?: any): void;
  // 路由离开的守卫事件
  onRouteLeaveNext?(route: MatchedRoute, ci: React.Component, prevRes?: any): void;
  // 当路由发生改变,正在解析、判断路由是否可以跳转的过程前后被调用
  onRouteing?(next: (ok: boolean|onRouteingNextCallback|Route) => void, prevRes?: any): void;
  // 当路由发生改变时被调用
  onRouteChange?: onRouteChangeEvent;
  // 当路由meta发生改变时被调用
  onRouteMetaChange?: onRouteMetaChangeEvent;
  // 当解析异步路由懒加载拿到真实的路由组件时被调用
  onLazyResolveComponent?(
    nc: React.ComponentType|React.ForwardRefExoticComponent<any>,
    route: ConfigRoute,
    prevRes?: any
  ): React.ComponentType | undefined;
  // 当向router中注册routes时,router初次遍历routes中没有route时被调用
  onWalkRoute?(route: ConfigRoute, routeIndex: number, routes: ConfigRouteArray, prevRes?: any): void;

  onGetRouteComponentGurads?(
    interceptors: RouteGuardInterceptor[],
    route: ConfigRoute,
    component: any,
    componentKey: string,
    guardName: string,
     options: {
      router: ReactViewRouter,
      onBindInstance?: OnBindInstance,
      onGetLazyResovle?: OnGetLazyResovle,
      toResovle: RouteComponentToResolveFn,
      getGuard: (obj: any, guardName: string) => any,
      replaceInterceptors: (newInterceptors: any[], interceptors: RouteGuardInterceptor[], index: number) => any[]
    },
    prevRes?: any
  ): void|boolean;

  // 当路由调整被终止时被调用
  onRouteAbort?(to: Route, reason?: any, prevRes?: any): void;

  onViewContainer?(container: ReactViewContainer|undefined, options: {
    routes: MatchedRoute[],
    route: MatchedRoute,
    depth: number,
    router: ReactViewRouter,
    view: RouterViewComponent,
  }, prevRes?: ReactViewContainer): ReactViewContainer|void;
}

install ReactVueLike插件装载方法. 参见: ReactVueLike

import vuelike from '@/vuelike';
import ReactViewRouter from 'react-view-router';

const router = new ReactViewRouter();

vuelike.use(router);

导出的其他方法

  • withRouteGuards 路由组件的守卫方法:
/**
 * 路由组件的守卫注册方法
 * @param {Object} component - 路由组件
 * @param {Object} guards - 守卫方法
 * @param {Class} [componentClass] - 路由组件的类,如果component是被高阶组件包装过的话,可以通过该参数来指明正确的路由组件
 * @return {RouteComponentGuards} - 可以被当做`React.forwardRef`来使用的路由组件
 **/
function withRouteGuards(component, guards = {}, componentClass?) {}

示例:

import React from 'react';
import { RouterView, withRouteGuards } from 'react-view-router';

function HomeIndex() {
  return (
    <div>
      <h1>HomeIndex</h1>
      <RouterView />
    </div>
  );
}

export default withRouteGuards(HomeIndex, {
  beforeRouteEnter(to, from, next) {
    console.log('HomeIndex beforeRouteEnter', to, from);
    next();
  },
  beforeRouteLeave(to, from, next) {
    // confirm leave prompt
    console.log('HomeIndex beforeRouteLeave', this, to, from);
    next();
  },
  beforeRouteUpdate(to, from) {
    console.log('HomeIndex beforeRouteUpdate', to, from);
  },
  beforeRouteResolve(to, from) {
    console.log('HomeIndex beforeRouteResolve', to, from);
  },
  afterRouteLeave(to, from) {
    console.log('HomeIndex afterRouteLeave', to, from);
  },
});
  • lazyImport 路由组件懒加载方法:
type LazyImportMethod<P = any> = (route: ConfigRoute, key: string, router: ReactViewRouter, options: Partial<any>) => P | Promise<P>;

/**
 * 路由组件懒加载方法
 * @param {LazyImportMethod} importMethod - webpack的import懒加载方法, 示例: () => import('@/components/some-component')
 * @return {RouteLazy} - 返回值可以作为路由配置中的component/components值,来实现懒加载
 **/
function lazyImport(importMethod: LazyImportMethod, options?: Partial<any>): RouteLazy;
  • normalizeRoutes 正规化路由配置:
/**
 * 正规化路由配置
 * @param {UserConfigRoute[]} routes - 为正规化的路由配置
 * @param {ConfigRoute} [parent] - 如果提供,则routes将会被当做它的子路由配置信息
 * @return {ConfigRouteArray} - 正规化后的路由配置
 **/
function normalizeRoutes(routes: UserConfigRoute[], parent?: ConfigRoute): ConfigRouteArray;
  • normalizeLocation 正规化路由字符串或对象为统一格式的路由对象:
/**
 * 正规化路由字符串或对象为统一格式的路由对象
 * @param {Object|string} to - 待正规化的地址对象
 * @param {Object} [options] - 其他选项
 * @param {Object} [options.route] 地址对象的父路由,如果提供的话,路由中的相对路径将会基于它来解析
 * @return {Object} - 正规化后的路由对象: { path: string, pathname: string, search: string, query: Object, ...custom props } 
 **/
function normalizeLocation(to, options: { route? } = {}) {}
  • isLocation判断 v是否是一个路由对象
/**
 * 判断 `v`是否是一个路由对象
 * @param {Object} v - 待判定的对象
 * @return {boolean}
 **/
function isLocation(v) {}
  • matchPath 只是重新导出,参见: matchPath

  • createRouterLink - 创建一个基于某个routerrouter-link组件。你可以在创建router的同时,创建一个该组件:

    import ReactViewRouter, { createRouterLink } from 'react-view-router';
    
    const router = new ReactViewRouter();
      
    const RouterLink = createRouterLink(router);
    
    export {
      RouterLink
    }
    
    export default router;
  • createRouteGuardsRef 创建路由守卫引用

    该方法是在使用者不想使用useRouteGuardsRef,而是直接使用useImperativeHandle时,通过该方法创建一个和useRouteGuardsRef等效的兼容实例。

    示例:

    import React, { useImperativeHandle } from 'react';
    import { createRouteGuardsRef } from 'react-view-router';
    import modals from '~/utils/modals';
    
    const Test = React.forwardRef((props, ref) => {
      const inputRef = useRef();
    
      useRouteGuardsRef(
        ref, 
        () => createRouteGuardsRef({
          async beforeRouteLeave(to, from, next) {
            console.log('Test beforeRouteLeave', to, from);
            try {
              await modals.confirm({ content: '确定要离开吗?' });
              next();
            } catch (ex) {
              next(false);
            }
          },
          beforeRouteUpdate(to, from) {
            console.log('Test beforeRouteUpdate', to, from);
          },
    
          focus: () => {
            inputRef.current.focus();
          },
        })
      );
    
      return (
        return <input ref={inputRef} ... />;
      );
    });
    
    export default Test;
  • readRouteMeta 读取ConfigRoute中meta的某个值,当值为函数时,会将其当做

    function (route: ConfigRoute, routes: ConfigRoute[], props: Partial<any>): any

回调进行调用,并将其返回值返回;

/**
 * @param {ConfigRoute} route - 要读取meta的ConfigRoute
 * @param {string} key - 要读取meta的参数名
 * @param {props: Partial<any>} props - 将作为第三个参数传递给meta的value函数
 * @return {any}
 **/
function readRouteMeta(route: ConfigRoute, key: string = '', props: {
  router?: ReactViewRouter|null,
  [key: string]: any
} = {}): any

HOCS/HOOKS

react-view-router也支持HOC/HOOKS

HOCS

withRouter

获取router实例,函数签名如下:

/**
 * @param {React.ComponentType} Comp - 待封装的组件 
 * @param {object} [options] - 选项 
 * @param {boolean} [options.withRoute] - 是否也获取当前route对象
 * @return {React.ComponentType}
 **/
const withRouter = (Comp: React.ComponentType, { withRoute: boolean = false }) => React.ComponentType

withRoute

获取route对象,函数签名如下:

/**
 * @param {React.ComponentType} Comp - 待封装的组件 
 * @param {object} [options] - 选项 
 * @param {boolean} [options.withRouter] - 是否也获取当前router实例
 * @return {React.ComponentType}
 **/
const withRoute = (Comp: React.ComponentType, { withRouter: boolean = false }) => React.ComponentType

withMatchedRoute

获取当前层级匹配的matchedRoute对象,函数签名如下:

/**
 * @param {React.ComponentType} Comp - 待封装的组件 
 * @param {object} [options] - 选项 
 * @param {boolean} [options.withMatchedRouteIndex] - 是否也获取当前层级索引
 * @return {React.ComponentType}
 **/
const withMatchedRoute = (Comp: React.ComponentType, { withMatchedRouteIndex: boolean = false }) => React.ComponentType

withMatchedRouteIndex

获取当前层级获取当前层级索引,函数签名如下:

/**
 * @param {React.ComponentType} Comp - 待封装的组件 
 * @param {object} [options] - 选项 
 * @param {boolean} [options.withMatchedRoute] - 是否也获取当前层级匹配的matchedRoute对象
 * @return {React.ComponentType}
 **/
const withMatchedRouteIndex = (Comp: React.ComponentType, { withMatchedRoute: boolean = false }) => React.ComponentType

withRouterView

获取当前层级的RouteView实例,函数签名如下:

/**
 * @param {React.ComponentType} Comp - 待封装的组件 
 * @param {object} [options] - 选项
 * @return {React.ComponentType}
 **/
const withRouterView = (Comp: React.ComponentType, {}) => React.ComponentType

HOOKS

useRouter

获取router实例,函数签名如下:

/**
 * @return {ReactViewRouter|null}
 **/
const useRouter = (defaultRouter?: ReactViewRouter|null) => ReactViewRouter|null

useRoute

获取route对象,函数签名如下:

/**
 * @param {ReactViewRouter} [defaultRouter] 默认的路由管理组件,当不传时它会根据上下文来寻找
 * @param {object} [options]  匹配选项
 * @param {boolean} [options.watch]  是否监听路由变化
 * @param {number|boolean = false} [options.delay]  路由发生变化时是否异步通知路由变更
 * @param {boolean = true} [options.ignoreSamePath]  路由发生变化前后的路由fullPath相同,则不触发组件重新渲染
 * @return {Route}
 **/
const useRoute = (
  defaultRouter?: ReactViewRouter|null, 
  options?: { 
    watch?: boolean,
    delay?: boolean|number,
    ignoreSamePath?: boolean
  }
) => Route

useRouteMeta

获取当前路由层级的路由元信息,你可以读取其中信息,并对它进行修改。

/**
 * @return {Route}
 **/
const useRouteMeta = (key: string|string[]): [Partial<any>, (key: string|string[], value: any) => void]

示例:

import { useRouteMeta } from 'react-view-router';

function Test() {
  const [title, setTitle] = useRouteMeta('title');

  return <>
    <button
      onClick={() => setTitle('标题被修改')}
    >修改标题</button>
    <div>{title}</div>
  </>
}

useRouteMetaChanged

注册一个路由元信息变更事件,该方法在通过路由元信息生成数据的组件中会比较有用

/**
 * @return {Route}
 **/
const useRouteMetaChanged = (router: ReactViewRouter, onChange: onRouteMetaChangeEvent, depMetaKeys: string[] = [])

示例:

import React, { useState } from 'react';
import { useRouter, useMatchedRoute, useRouteMetaChanged } from 'react-view-router';

function Test() {
  const router = useRouter();
  const matchedRoute = useMatchedRoute();
  const [title, setTitle] = useRouteMeta('title');
  
  useRouteMetaChanged(router, (newVal, oldVal, route) => {
    console.log('title changed');
  }, ['title']);

  return <>
    <button
      onClick={() => setRouteMeta('title', '标题被修改')}
    >修改标题</button>
    <div>{title}</div>
  </>
}

useMatchedRoute

获取当前层级匹配的matchedRoute对象,函数签名如下:

/**
 * @param {ReactViewRouter} [defaultRouter] 默认的路由管理组件,当不传时它会根据上下文来寻找
 * @param {object} [options]  匹配选项
 * @param {number = 0} [options.matchedOffset]  匹配的matchedRoute的(向前)偏移量
 * @param {string} [commonPageName] 公共路由的meta名称,如果commonPageName不为空并且currentRoute的meta中该名称的属性值为true,并且currentRoute.query.redirect不为空,则将会从currentRoute.query.redirect中解析matchedRoute
 * @param {boolean = true} [options.watch]  是否监听路由变化
 * @param {number|boolean = false} [options.delay]  路由发生变化时是否异步通知路由变更
 * @param {boolean = true} [options.ignoreSamePath]  路由发生变化前后的路由fullPath相同,则不触发组件重新渲染
 * @return {MatchRoute}
 **/
const useMatchedRoute = (
  defaultRouter?: ReactViewRouter|null, 
  options?: { 
    matchedOffset?: number, 
    watch?: boolean,
    delay?: boolean|number,
    ignoreSamePath?: boolean 
  }
) => MatchedRoute

useMatchedRouteIndex

获取当前层级获取当前层级索引,函数签名如下:

/**
 * @param {ReactViewRouter} [defaultRouter] 默认的路由管理组件,当不传时它会根据上下文来寻找
 * @param {number} [matchedOffset]  匹配的matchedRoute的(向前)偏移量,默认值为0
 * @return {number}
 **/
const useMatchedRouteIndex = (matchedOffset?: number = 0) => number

useRouterView

获取当前层级的RouteView实例,函数签名如下:

/**
 * @return {RouterView}
 **/
const useRouterView = () => RouterView

useRouteGuardsRef

该方法封装了useImperativeHandle hooks,你可以通过该方法注册路由守卫。

/**
 * @param {Ref} ref 通过React.forwardRef获取的ref
 * @param {T|(() => T)} guards 路由守卫对象,支持beforeRouteLeave、beforeRouteResolve、afterRouteLeave、beforeRouteUpdate守卫
 * @param {DependencyList} deps 传给useImperativeHandle的deps
 * @return {void} 
 **/
function useRouteGuardsRef<T extends RouteGuardsInfo>(
  ref: Ref<T>|undefined,
  guards: T|(() => T),
  deps: DependencyList = []
): void

示例:

import React from 'react';
import { useRouteGuardsRef } from 'react-view-router';
import modals from '~/utils/modals';

const Test = React.forwardRef((props, ref) => {
  useRouteGuardsRef(ref, {
    async beforeRouteLeave(to, from, next) {
      console.log('Test beforeRouteLeave', to, from);
      try {
        await modals.confirm({ content: '确定要离开吗?' });
        next();
      } catch (ex) {
        next(false);
      }
    },
    beforeRouteUpdate(to, from) {
      console.log('Test beforeRouteUpdate', to, from);
    },
  });

  return (
    <div className="">
      something
    </div>
  );
});

export default Test;

注意:不要将useRouteGuardsRefuseImperativeHandle在一个函数组件中混用。

useRouteChanged

为指定的router注册一个路由变更回调事件,函数签名如下:

/**
 * @return {RouterView}
 **/
const useRouteChanged = (router: ReactViewRouter, onChange: onRouteChangeEvent) => void

示例:

import { useRouter, useRouteChanged } from 'react-view-router';

const router = useRouter();
useRouteChanged(router, (route) => {
  const path = route.matched[0] && route.matched[0].subpath;
  if (path !== activeTab && tabs.some((tab) => tab.key === path)) {
    setActiveTab(path);
  }
});

useViewActivate

当当前路由组件启用了keepAlive后,并且在再次进入时会会触发激活事件;

interface KeepAliveEventObject {
  type: keyof RouterViewEvents,
  router: ReactViewRouter,
  source: RouterView,
  target: MatchedRoute,
  to: MatchedRoute|null,
  from: MatchedRoute|null,
}

type KeepAliveChangeEvent = (event: KeepAliveEventObject) => void;
/**
 * @return {RouterView}
 **/
const useViewActivate = (onEvent: KeepAliveChangeEvent) => void

示例:

import { useViewActivate } from 'react-view-router';

useViewActivate((event) => {
  // dosomethine
});

useViewDeactivate

当当前路由组件启用了keepAlive后,等离开当前路由时会触发失活事件;

interface KeepAliveEventObject {
  type: keyof RouterViewEvents,
  router: ReactViewRouter,
  source: RouterView,
  target: MatchedRoute,
  to: MatchedRoute|null,
  from: MatchedRoute|null,
}

type KeepAliveChangeEvent = (event: KeepAliveEventObject) => void;
/**
 * @return {RouterView}
 **/
const useViewDeactivate = (onEvent: KeepAliveChangeEvent) => void

示例:

import { useViewDeactivate } from 'react-view-router';

useViewDeactivate((event) => {
  // dosomethine
});

useRouteTitle

遍历路由配置,检索出组件当前所在层级的路由配置中meta.titlemeta.visible过滤出来的信息,其中meta.titlemeta.visible可以是一个函数。你可以根据该信息来创建菜单、标签页等。函数签名如下:

// `meta.title`、`meta.visible`可以是boolean值,也可以下面这样的函数
type RouteMetaFunction<T = boolean> = (route: ConfigRoute, routes: ConfigRouteArray, props: {
  router?: ReactViewRouter|null,
  level?: number,
  maxLevel?: number,
  refresh?: () => void
  [key:string]: any
}) => T;

type RouteTitleInfo = {
  title: string;
  path: string;
  meta: Partial<any>;
  route: ConfigRoute;
  children?: RouteTitleInfo[];
};

type filterCallback = (r: ConfigRoute, routes: ConfigRouteArray, props: {
  router: ReactViewRouter
  level: number,
  maxLevel: number,
  refresh: () => void,
  title?: string,
  visible?: boolean
}) => boolean;

type RouteTitleProps = {
  // useRouteTitle的最大检索层级
  maxLevel?: number,
  // 对过滤出来的title信息进行自定义的过滤
  filter?: filterCallback,
  // 指定filter依赖的ConfigRoute中的meta参数名,以便它们更新时可以触发titles的更新
  filterMetas?: string[],
  /**
   * 是否是手动模式,当为true时,第一次调用useRouteTitle时不会主动检索ConfigRoute,直到你调用一次refreshTitles后,
   * 可以通过该参数实现异步展示菜单、标签栏等方案
   **/
  manual?: boolean,
  // 匹配的matchedRoute的(向前)偏移量,默认值为0
  matchedOffset?: number;
  // 公共页面meta的key值,默认为'commonPage'。即当某个匹配路由的meta中包含commonPage: true时,并且router.currentRoute.query.redirect不为空时,解析路由将会改为从router.currentRoute.query.redirect重新解析titles
  commonPageName?: string;
}

type RouteTitleResult = {
  titles: RouteTitleInfo[];
  setTitles: (titles: RouteTitleInfo[]) => void;

  currentPaths: string[];
  setCurrentPaths: (currentPaths: string[]) => void;

  refreshTitles: () => void;
}

/**
 * @param {RouteTitleProps} props 参数
 * @param {ReactViewRouter} defaultRouter 默认的路由实例
 * @param {DependencyList} deps 传给useRouteTitle的deps
 * @return {RouteTitleResult} 
 **/
function useRouteTitle(
  props: RouteTitleProps = {},
  defaultRouter?: ReactViewRouter,
  deps: any[] = []
): RouteTitleResult;

左侧菜单示例:

import React, { useMemo, useEffect } from 'react';
import {
 useRouter, useRouteTitle,
} from 'react-view-router';
import { observer } from 'mobx-react';
import { LeftMenu } from '@/ui';
import store from '@/store';

const MainLeft = observer(() => {
  const { companyId } = store.state.company;

  const router = useRouter();
  const {
    titles,
    currentPaths: currentMenus,
    refreshTitles,
  } = useRouteTitle({ 
    // 从当前组件所在的路由层级再向下2级开始查找匹配titles
    maxLevel: 2 
  });

  const menus = useMemo(() => {
    const getMenus = (list = []) => list.map((item) => {
      const { title, meta, path } = item;
      return {
        text: title,
        key: path,
        icon: meta.icon ? <i className={`iconfont ${meta.icon}`} /> : null,
        children: getMenus(item.children),
      };
    });
    return getMenus(titles);
  }, [titles]);

  useEffect(() => {
    if (titles.length) refreshTitles();
  }, [companyId]);

  const parentPaths = currentPaths.length > 1
    ? currentPaths.slice(0, currentPaths.length - 1)
    : currentPaths;
  const currentPath = currentPaths.length ? currentPaths[currentPaths.length - 1] : null;

  return (
    <LeftMenu
      onlyOpenCurrent
      defaultLock
      height="100%"
      data={menus}
      openKeys={parentPaths}
      selectedKeys={currentPath ? [currentPath] : null}
      onSelect={(path) => router.push(path)}
    />
  );
});

export default MainLeft;

标签栏示例:

import React from 'react';
import router from '@/history';
import { useRouter, RouterView, useRouteTitle } from 'react-view-router';
import { Tabs } from '@/ui';

const { TabPane } = Tabs;

function MainHeader(props) {
  const router = useRouter();
  const {
    titles,
    currentPaths: [currentPath],
  } = useRouteTitle({ maxLevel: 1 }, router);

  return (
    <div>
      <Tabs
        activeKey={currentPath}
        onChange={(path) => router.replace(path)}
      >
      {
        titles.map((tab) => (
          <TabPane
            tab={tab.title}
            key={tab.path}
          />
        ))
      }
      </Tabs>
      <RouterView />
   </div>
  );
}

export default MainHeader;

ReactViewLike集成

react-view-router可以作为ReactVueLike的插件来使用:

import vuelike from '@/vuelike';
import ReactViewRouter from 'react-view-router';

const router = new ReactViewRouter({
  basename: '',     // app的basename,若页面路由是在/app/下面,则base的值应该为"/app/"
  mode: 'hash', // 路由类型 browser|memory|hash, default:hash
  routes: []    // 路由配置数组,也可以使用router.use方法来初始化
});
   
vuelike.use(router);

这样既可将router注册为ReactVueLike的插件。注册后,将有以下功能:

组件级的路由事件

import React from 'react';
import ReactVueLike from 'react-vue-like';

class EmployeeManager extends ReactVueLike.Component {

  beforeRouteEnter(to, from, next) {
    next();
  }

  beforeRouteLeave(to, from, next) {
    next();
  }

  beforeRouteResolve(to, from) {

  }

  afterRouteLeave(to, from) {

  }

  beforeRouteUpdate(to, from) {

  }
 
  render() {
    return (
      <div className="employee-manager">  
      </div>
    );
  }
}

export default EmployeeManager;

注入对象

ReactVueLike.Component组件实例中,可以取到:

  • $router - 当前路由管理实例
  • $route - object, 当前路由信息,等同于router.currentRoute
  • $matchedRoute object, 当前匹配的子路由信息
  • $routeIndex number, 当前匹配的子路由的索引

三者的关系是:$route.matched[$routeIndex] === $matchedRoute

下面是一个“通过从路由元信息中读取tab标签页信息并渲染”的一个示例:

import React from 'react';
import ReactVueLike from 'react-vue-like';

class EmployeeManager extends ReactVueLike.Component {

  static computed = {
    active() {
      const matched = this.$route.query.redirect
        ? this.$router.getMatched(this.$route.query.redirect)
        : this.$route.matched;
      const match = matched[this.$routeIndex + 1];
      return (match && match.subpath) || 'roster';
    },
    tabs() {
      return this.$matchedRoute.config.children
        ? this.$matchedRoute.config.children.filter((r) => r.meta.title && !r.meta.hideTab).map((r) => ({
          key: r.subpath,
          label: r.meta.title,
          badge: Boolean(r.meta.badge)
        }))
        : [];
    },
  }

  handleTabChanged(v) {
    const path = `${this.$matchedRoute.path}/${v}`;
    if (this.$route.path === path) return;
    this.$router.replace(path);
  }

  render() {
    return (
      <div className="employee-manager">
        <ui-tabs activeKey={this.active} onChange={this.handleTabChanged}>
          { this.tabs.map((tab) => <ui-tabs-tab-pane tab={tab.label} key={tab.key} />)}
        </ui-tabs>
      </div>
    );
  }
}

export default EmployeeManager;

监听路由变化

通过ReactVueLike.Componentwatch功能可以监听路由变化来做一些事情。

示例:

import React from 'react';
import ReactVueLike from 'react-vue-like';

class MainSider extends ReactVueLike.Component {

  static watch = {
    $route(newVal, oldVal) {
      if (!newVal || !oldVal) return;
      // 路由变化时,更新左侧菜单信息
      if (newVal.fullPath !== oldVal.fullPath) this.updateList(newVal);
    },
  }

  static methods = {
    updateList(route) {
      ...
    },
  }
}

export default MainSider;

路由转场动效

你可以通过import RouterView from 'react-view-router/transition'引用支持路由跳转动效的RouterView来为页面添加简单的转场动效。目前支持fade|slide|carousel三种动效:

import React from 'react';
// import { RouterView } from 'react-view-router';
import RouterView from 'react-view-router/transition';
import router from '@/history';

function  App() {
  return (
    <div className="app-index">
      <RouterView
        router={router}
        transition="slide"
        // transitionPrefix="react-view-router-"
        // transitionZIndex={1000}
      />
    </div>
  );
}

export default App;

用法和普通的RouterView一样,只是多添加了transitiontransitionPrefixtransitionZIndexrouterView四个额外属性:

interface TransitionRouterViewProps extends RouterViewProps {
  transition?: TransitionName | {
    name: TransitionName,
    zIndex?: number,
    containerStyle?: React.HTMLAttributes<HTMLDivElement>,
    containerTag?: string | React.ComponentType | React.ForwardRefExoticComponent<any>
  };
  transitionPrefix?: string;
  transitionZIndex?: number;
  routerView?: RouterViewComponent
}
  • transition - 动效的名称,目前支持slidefade两种动效。它也可以是一个对象。
  • transitionPrefix - 动效的className前缀,默认为react-view-router-。如果你修改了该值,则动效的样式需要由你自己来提供;
  • transitionZIndex - 进场/离场页面的zIndex,默认为1000。若当前是slide动效,并且当前页面中存在zIndex大于1000的元素时,你可以通过设置transitionZIndex为更高的值来避免该元素穿透转场页面。

中台模块中路由处理

中台模块如果需要配置路由时,需要将它的路由添加到宿主项目的路由地址后面追加,react-view-router也支持这种模式的开发。

  1. 配置路由实例为手动模式:
import ReactViewRouter from 'react-view-router';
import routes from './routes';

const router = new ReactViewRouter({
  manual: true,
  routes
});

export default router;
  1. 在模块初始化/销毁时启动路由监听
import router from '@/history';

export default {
  bootstrap({ basename, routeMode }) {
    ...
    router.start({ basename, routeMode });
    ...
  },

  unmount() {
    ...
    router.stop();
    ...
  }
}

或者在组件中:

import React from 'react';
import { useManualRouter, RouterView } from 'react-view-router';
import router from '@/history';

/** 
 * 全局的路由拦截事件
 * @type {import('react-view-router').RouteBeforeGuardFn} 
 **/
function beforeEach(to, from, next) {
  next();
}

function App({ basename, routeMode }) {
  useManualRouter(router, { basename, routeMode });

  return <div>
    <RouterView router={router} beforeEach={beforeEach} />
  </div>
}

export default App;

NOTE

  1. react-view-router不依赖react-vue-like,可以独立使用

  2. 如果路由组件是Class Component (不是 Function Component), 则在beforeRouteUpdate,beforeRouteLeave,afterRouteLeave 组件的守卫函数都会绑定到 this变量,它指向当前组件实例;