React Router v4 & v5 攔截器(鉤子)、靜態路由、route-view 實現

前提

React Router 再 v3 版本之前 是有 onEnter 鉤子函數的,也支持靜態路由配置;,但到了 v4 版本後鉤子函數被移除,官方說是為了將此提供給開發者,由開發者自由發揮。既然如此我們就只能自己實現,目前網上有很多版本,大多都是差不多的,這裡做一個總結並深化一下。同時提供鉤子函數或者vue中叫路由守衛和靜態化路由配置。

鉤子函數實現

鉤子函數實現比較簡單,只需要包裝一下官方的路由即可實現,這裡我們允許鉤子支持 Promise 異步,就需要一個異步組件;代碼如下

異步組件代碼

<code>class AsyncBeforeEnter extends React.PureComponent {
constructor(props) {
super(props)
this.state = {
// 注意:此處component和render不會同時使用,同Route中component和render,render方法優先級要高
// 目標組件 同 <route>
Component: null,
// 目標組件render 方法 同 <route>
render: null,
// 錯誤信息
error: null,
// 標記異步是否完成
completed: false
}
}
componentDidMount() {
const { beforeEnter, ...props } = this.props
// beforeEnter 鉤子函數
const enter = beforeEnter({ ...props })
if (isPromise(enter)) {
// 判斷是否是Promise
enter
.then(next => {
this.handleAfterEnter(next)
})
.catch(error => {
console.error(error)
this.setState({ error })
})
} else {
this.handleAfterEnter(enter)

}
}
handleAfterEnter(next) {
// 結果處理
const { route = {}, ...props } = this.props
// 如果結果是null 或者undefined 或者 true : 不做任何處理直接渲染組件
if (next === null || next === undefined || next === true) {
this.completed(route.component, route.render)
return
}
// 返回false:阻止組件的渲染
if (next === false) {
this.completed(null)
return
}

// 返回 string : 跳轉的路由,類似http中302狀態碼
// 這裡使用 React Router 的 Redirect 做跳轉
if (typeof next === 'string') {
this.completed(null, () => <redirect>)
return
}
// 返回React 組件
if (typeof next === 'function' || React.isValidElement(next)) {
this.completed(null, () => next)
return
}

// 返回 Object: 如果有 redirect=true 的屬性,做跳轉
// 否則使用 Route 組件渲染
if (isPlainObject(next)) {
const { redirect, ...nextProps } = next
if (redirect === true) {
this.completed(null, () => <redirect>)
return
}
this.completed(() => <route>)
return
}
warn(`"${props.location.pathname} => beforeEnter"
hook return values error. expected null? undefined? true? React.Component? HTMLElement? Route props?
route props detail to see

https://reacttraining.com/react-router/web/api/Route
https://reacttraining.com/react-router/web/api/Redirect`
)
// 例外情況 阻止組件的渲染
this.completed(null)
}
/**
* 完成後改變state渲染組件:
* @param component
* @param render
*/
completed(component, render) {
this.setState({ Component: component, render, completed: true, error: null })
}

getExtraProps() {
// 去掉鉤子函數,獲取其他props
const { loading: Loading, beforeEnter, ...props } = this.props
return { ...props }
}
render() {
const { Component, render, error, completed } = this.state
if (!completed) {
// 未完成
return null
}
// 已完成
if (render && typeof render === 'function') {
return render(this.getExtraProps())
}
return Component ? <component> : null
}
}/<route>/<route>/<code>

帶有鉤子函數的 Route

將其命名為 PrivateRoute

<code>export default (route) => (
<route> path={route.path}
exact={route.exact}
strict={route.strict}
location={route.location}
sensitive={route.sensitive}
children={route.children}

render={props => {
// beforeEnter
const { beforeEnter, ...nextProps } = route
// 如果有鉤子函數,執行帶有異步組件
if (route.beforeEnter && typeof route.beforeEnter === 'function') {
return (
<asyncbeforeenter> beforeEnter={beforeEnter}
route={nextProps}
{...props}
{...extraProps}
/>
)

}
// 直接渲染
return (
route.render && typeof route.render ?
(
route.render({ ...props, ...extraProps, route: nextProps })
) : (
route.component ? (
<route.component> route={nextProps}
{...props}
{...extraProps}
/>
) : null
)
)
}}
/>
)/<route.component>/<asyncbeforeenter>/<route>/<code>

使用的時候就可以用該 Route 代替官方的示例:

<code><privateroute> check(props) }/>
<privateroute> check(props) }/>/<privateroute>/<privateroute>/<code>

靜態化路由配置,並支持鉤子函數

靜態化路由配置官方頁給出了方案,見:react-router-config,本文的靜態路由配置也是參考了該實現,並重寫了其中的實現,加入鉤子函數

靜態路由配置

基本的靜態路由表如下

<code>
// 頂級兩個路由
// 一個登錄
// 其他需要授權後放回
export default [
{
path: '/example',
key: 'example',
component: Example,
beforeEnter(props) {
if (auth(props.localtion.pathname)) {
return true
}
return '/login'
},
// 子路由
routes: [
{
path: '/example1',
key: 'example1',
component: Example1,
}
]
},
{
path: '/login',
key: 'login',
component: Login
}
]/<code>

改寫鉤子函數使其能渲染靜態路由表=>renderRoutes

<code>
// renderRoutes
export default (routes, switchProps = {}, extraProps = {}) => {
return routes && routes.length > 0 ? (
<switch>
{
routes.map((route, i) => (

<route> key={route.key || i}
path={route.path}
exact={route.exact}
strict={route.strict}
location={route.location}
sensitive={route.sensitive}
children={route.children}
render={props => {
// beforeEnter
const { beforeEnter, ...nextProps } = route
if (route.beforeEnter && typeof route.beforeEnter === 'function') {
return (
<asyncbeforeenter> beforeEnter={beforeEnter}
route={nextProps}
{...props}
{...extraProps}
/>
)

}
return (
route.render && typeof route.render ?
(
route.render({ ...props, ...extraProps, route: nextProps })
) : (
route.component ? (
<route.component> route={nextProps}
{...props}
{...extraProps}
/>
) : null
)
)
}}
/>
))
}
/<route.component>/<asyncbeforeenter>/<route>/<switch>
) : null
}/<code>

使用就可以調用 renderRoutes 方法 , 該實例摘自官方示例:

<code>const Root = ({ route }) => (

Root


{/* child routes won't render without this */}
{renderRoutes(route.routes)}

);

const Home = ({ route }) => (

Home



);

const Child = ({ route }) => (

Child


{/* child routes won't render without this */}
{renderRoutes(route.routes, { someProp: "these extra props are optional" })}

);

const GrandChild = ({ someProp }) => (

Grand Child


{someProp}


);

ReactDOM.render(
<browserrouter>
{/* kick it all off with the root route */}
{renderRoutes(routes)}
/<browserrouter>,
document.getElementById("root")
);/<code>

實現類似 vue-router 裡面的 route-view 功能

經過以上的處理,基本的鉤子函數和靜態路由就算配置完成了;功能雖然完成了,但總感覺使用上有點麻煩;確實,有沒有類似 vue-router 中的 route-view 這種的一步到位的呢?好的,安排。。。這裡需要用到 React context 在 v16 以前這是不推薦的,不過現在已經成熟了,可以大膽的用了;如果不知道怎麼用和什麼原理可以 去這裡 補一下知識

React Router v4 & v5 攔截器(鉤子)、靜態路由、route-view 實現

這裡還有一個很關鍵的地方,看圖劃重點:

React Router v4 & v5 攔截器(鉤子)、靜態路由、route-view 實現

可以重複使用,內部的值會覆蓋外層的值,這樣我們就可以多層路由嵌套了;

創建 context

<code>import React from 'react'

const RouteContext = React.createContext([])
// devtool 中使用
RouteContext.displayName = 'RouteViewContext'

export const RouteProvider = RouteContext.Provider
export const RouteConsumer = RouteContext.Consumer/<code>

創建 RouteView

<code>import { RouteConsumer } from './context'
import renderRoutes from './renderRoutes'

//RouteView
export default () => {
return (
<routeconsumer>
{/* 使用靜態路由渲染 */}
{/* ruotes 由RouteProvider 提供 */}
{routes => renderRoutes(routes)}
/<routeconsumer>
)
}/<code>

再次改寫 renderRoutes, 使其能夠渲染下級路由

<code>import { RouteProvider } from './context'

// renderRoutes
export default (routes, switchProps = {}, extraProps = {}) => {
return routes && routes.length > 0 ? (
<switch>
{
routes.map((route, i) => (
<route> key={route.key || i}

path={route.path}
exact={route.exact}
strict={route.strict}
location={route.location}
sensitive={route.sensitive}
children={route.children}
render={props => {
checkProps(props)
// beforeEnter
const { beforeEnter, ...nextProps } = route

// RouteProvider 提供下級路由所需的數據
if (route.beforeEnter && typeof route.beforeEnter === 'function') {
return (
<routeprovider>
<asyncbeforeenter> beforeEnter={beforeEnter}
route={nextProps}
{...props}
{...extraProps}
/>
/<asyncbeforeenter>/<routeprovider>
)

}
return (
<routeprovider>
{
route.render && typeof route.render ?
(
route.render({ ...props, ...extraProps, route: nextProps })
) : (
route.component ? (
<route.component> route={nextProps}
{...props}
{...extraProps}
/>
) : null
)
}
/<route.component>/<routeprovider>
)
}}
/>
))
}
/<route>/<switch>
) : null
}/<code>

使用

在入口js添加代碼如下

<code>import { RouteProvider, RouteView } from '../router'

// 靜態路由
const routes = [
// 略。。。
]
class App extends React.PureComponent {
// 略。。。
render() {

return (
// 略。。。
// 提供頂層路由即可
// 下級路由 renderRoutes 處理
<routeprovider>
<routeview>
/<routeprovider>
// 略。。。
)
}
}
export default App/<code>

二級路由使用

<code>class Example extends React.PureComponent {
// 略。。。
render() {
// 此處便不需要再提供routes了
// 在 renderRoutes 已經由 RouteProvider 提供了
return (

Example
<routeview>

)
}

}
export default Example/<code>

通過以上努力,我們就具備了靜態路由、鉤子函數、類似 vue-router 中 router-view;最終的努力的結果:

  • 方便鑑權
  • 路由方便管理
  • 多層級路由一個組件即可實現渲染

原文出處:React Router v4 & v5 攔截器(鉤子)、靜態路由、route-view 實現

參考文章

  • https://reacttraining.com/react-router/web/guides/quick-start
  • https://github.com/ReactTraining/react-router/tree/master/packages/react-router-config
  • https://github.com/mirrorjs/mirror/issues/78
  • https://stackoverflow.com/questions/50928349/react-router-4-how-to-wait-for-a-promise-to-resolve-inside-render-method-of-ro
  • https://juejin.im/post/5d2d32a9f265da1b8b2b90c7
  • https://zh-hans.reactjs.org/docs/context.html


分享到:


相關文章: