React Router
再 v3
版本以前 是有 onEnter
鉤子函數的,也支持靜態路由配置;,但到了 v4
版本後鉤子函數被移除,官方說是爲了將此提供給開發者,由開發者自由發揮。既然如此咱們就只能本身實現,目前網上有不少版本,大多都是差很少的,這裏作一個總結並深化一下。同時提供鉤子函數或者vue中叫路由守衛和靜態化路由配置。html
鉤子函數實現比較簡單,只須要包裝一下官方的路由便可實現,這裏咱們容許鉤子支持
Promise
異步,就須要一個異步組件;代碼以下
class AsyncBeforeEnter extends React.PureComponent { constructor(props) { super(props) this.state = { // 注意:此處component和render不會同時使用,同Route中component和render,render方法優先級要高 // 目標組件 同 <Route componet> Component: null, // 目標組件render 方法 同 <Route render> 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 to={next} from={props.location.pathname} />) 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 {...nextProps} {...{ from: props.location.pathname }} />) return } this.completed(() => <Route {...nextProps} />) 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 {...this.getExtraProps()} /> : null } }
Route
將其命名爲
PrivateRoute
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
代替官方的
示例:vue
<PrivateRoute path="/" component={Example} beforeEnter={(props) => check(props) }/> <PrivateRoute path="/user" component={User} beforeEnter={(props) => check(props) }/>
靜態化路由配置官方頁給出了方案,見: react-router-config,本文的靜態路由配置也是參考了該實現,並重寫了其中的實現,加入鉤子函數
基本的靜態路由表以下
// 頂級兩個路由 // 一個登陸 // 其餘須要受權後放回 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 } ]
renderRoutes
// renderRoutes export default (routes, switchProps = {}, extraProps = {}) => { return routes && routes.length > 0 ? ( <Switch {...switchProps}> { 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 ) ) }} /> )) } </Switch> ) : null }
使用就能夠調用 renderRoutes
方法 , 該實例摘自官方示例:react
const Root = ({ route }) => ( <div> <h1>Root</h1> {/* child routes won't render without this */} {renderRoutes(route.routes)} </div> ); const Home = ({ route }) => ( <div> <h2>Home</h2> </div> ); const Child = ({ route }) => ( <div> <h2>Child</h2> {/* child routes won't render without this */} {renderRoutes(route.routes, { someProp: "these extra props are optional" })} </div> ); const GrandChild = ({ someProp }) => ( <div> <h3>Grand Child</h3> <div>{someProp}</div> </div> ); ReactDOM.render( <BrowserRouter> {/* kick it all off with the root route */} {renderRoutes(routes)} </BrowserRouter>, document.getElementById("root") );
vue-router
裏面的 route-view
功能通過以上的處理,基本的鉤子函數和靜態路由就算配置完成了;功能雖然完成了,但總感受使用上有點麻煩;確實,有沒有相似 vue-router
中的 route-view
這種的一步到位的呢?好的,安排。。。
這裏須要用到 React context
在 v16
之前這是不推薦的,不過如今已經成熟了,能夠大膽的用了;若是不知道怎麼用和什麼原理能夠 去這裏 補一下知識
git
這裏還有一個很關鍵的地方,看圖劃重點:
github
能夠重複使用,內部的值會覆蓋外層的值,這樣咱們就能夠多層路由嵌套了;web
context
import React from 'react' const RouteContext = React.createContext([]) // devtool 中使用 RouteContext.displayName = 'RouteViewContext' export const RouteProvider = RouteContext.Provider export const RouteConsumer = RouteContext.Consumer
RouteView
import { RouteConsumer } from './context' import renderRoutes from './renderRoutes' //RouteView export default () => { return ( <RouteConsumer> {/* 使用靜態路由渲染 */} {/* ruotes 由RouteProvider 提供 */} {routes => renderRoutes(routes)} </RouteConsumer> ) }
renderRoutes
, 使其可以渲染下級路由import { RouteProvider } from './context' // renderRoutes export default (routes, switchProps = {}, extraProps = {}) => { return routes && routes.length > 0 ? ( <Switch {...switchProps}> { 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 value={route.routes}> <AsyncBeforeEnter beforeEnter={beforeEnter} route={nextProps} {...props} {...extraProps} /> </RouteProvider> ) } return ( <RouteProvider value={route.routes}> { route.render && typeof route.render ? ( route.render({ ...props, ...extraProps, route: nextProps }) ) : ( route.component ? ( <route.component route={nextProps} {...props} {...extraProps} /> ) : null ) } </RouteProvider> ) }} /> )) } </Switch> ) : null }
import { RouteProvider, RouteView } from '../router' // 靜態路由 const routes = [ // 略。。。 ] class App extends React.PureComponent { // 略。。。 render() { return ( // 略。。。 // 提供頂層路由便可 // 下級路由 renderRoutes 處理 <RouteProvider value={routes}> <RouteView /> </RouteProvider> // 略。。。 ) } } export default App
class Example extends React.PureComponent { // 略。。。 render() { // 此處便不須要再提供routes了 // 在 renderRoutes 已經由 RouteProvider 提供了 return ( <div> Example <RouteView /> </div> ) } } export default Example
經過以上努力,咱們就具有了靜態路由、鉤子函數、相似 vue-router
中 router-view
;
最終的努力的結果:vue-router
原文出處:React Router v4 & v5 攔截器(鉤子)、靜態路由、route-view 實現api