實現簡易版 React Route
我們都知道 React中,一切皆組件。Router的使用就是引入一個個組件,非常方便。常見的路由組件有這些:
- Link 路由鏈接
- Route 基本路由
- Switch 獨佔路由
- Redirect 重定向組件
- PrivateRoute 導航守衛
- BrowserRouter 在Route基礎上添加了一些API方法
上面組件用法不多介紹了,參考react-router文檔 很容易上手。來看看如何實現它們:
Link
組件返回一個 a標籤,href路徑爲props中的 to 屬性,爲a標籤添加一個點擊事件,跳轉到對應組件
Switch
獨佔路由,即拿到Route數組,一旦匹配直接渲染,不往下繼續匹配
同時,Switch組件有location屬性,可指定path,無論瀏覽器path如何改變,總會顯示指定path對應組件
import React, {Component} from "react";
import {RouterContext} from "./RouterContext";
import matchPath from "./matchPath";
export default class Switch extends Component {
render() {
return (
<RouterContext.Consumer>
{context => {
// 找出渲染的,第一個符合匹配的元素,存在element
// const {location} = context;
// 優先用props上的location
const location = this.props.location || context.location;
let element,
match = null;
let {children} = this.props;
// 此處直接用for循環亦可,不過要考慮children非數組情況
React.Children.forEach(children, child => {
if (match === null && React.isValidElement(child)) {
element = child;
const path = child.props.path;
match = path
? matchPath(location.pathname, {
...child.props,
path
})
: context.match;
}
});
return match
? React.cloneElement(element, {
location,
computedMatch: match
})
: null;
}}
</RouterContext.Consumer>
);
}
}
Redirect
重定向組件,跳轉自然會用到 history.push
import React, {Component} from "react";
import {RouterContext} from "./RouterContext";
export default class Redirect extends Component {
render() {
return (
<RouterContext.Consumer>
{context => {
const {history} = context;
const {to} = this.props;
// history.push(to)
return <LifeCycle onMount={() => history.push(to)} />;
}}
</RouterContext.Consumer>
);
}
}
// 考慮到渲染的生命週期,在Mount周後執行
class LifeCycle extends Component {
componentDidMount() {
if (this.props.onMount) {
this.props.onMount();
}
}
render() {
return null;
}
}
PrivateRoute
導航守衛,通常用於權限校驗或判斷登錄。
以判斷登錄狀態爲例,結合Redirect組件,已登錄,跳轉到to對應的path組件,未登錄則重定向到登錄頁
// 此處的connect方法是 react-redux中的方法,在react-redux一節提到過
export default connect(
// mapStateToProps
({user}) => ({isLogin: user.isLogin})
)(
class PrivateRoute extends Component {
render() {
const {isLogin, path, component} = this.props;
if (isLogin) {
// 登錄
return <Route path={path} component={component} />;
} else {
// 去登錄,跳轉登錄頁面
return <Redirect to={{pathname: "/login", state: {redirect: path}}} />;
}
}
}
);
BrowserRouter
BrowserRouter暴露出三個屬性:
- history 即history庫提供的 createBrowserHistory方法產生的對象
- location 當前路由地址
- match 是否匹配
import React, {Component} from "react";
import {createBrowserHistory} from "history";
import {RouterContext} from "./RouterContext";
export default class BrowserRouter extends Component {
static computeRootMatch(pathname) {
return {
path: "/",
url: "/",
params: {},
isExact: pathname === "/"
};
}
constructor(props) {
super(props);
this.history = createBrowserHistory();
this.state = {
location: this.history.location
};
this.unlisten = this.history.listen(location => {
this.setState({location});
});
}
componentWillUnmount() {
if (this.unlisten) {
this.unlisten();
}
}
render() {
return (
<RouterContext.Provider
value={{
history: this.history,
location: this.state.location,
match: BrowserRouter.computeRootMatch(this.state.location.pathname)
}}>
{this.props.children}
</RouterContext.Provider>
);
}
}
Route
Route相比其他幾個組件會複雜些,掌握以下要點:
- props 除了人爲添加的 children/component/render, path 之外,還有BrowserRouter傳到context中的 match, location, history
- 渲染的優先級 Children > Component > render
- return 時先看 match結果,
- 結果爲true,則按優先級渲染;
- 結果爲false,則判斷是否有children,有則渲染,沒有爲null
- 外面包一層Provide, 將需要傳遞的屬性如: context對象,location以及 match結果傳遞出去(可能跨層級,所以使用context方式傳遞)
import React, {Component, Children} from "react";
import {RouterContext} from "./RouterContext";
import matchPath from "./matchPath";
export default class Route extends Component {
render() {
return (
<RouterContext.Consumer>
{context => {
const {path, computedMatch, children, component, render} = this.props;
const location = this.props.location || context.location;
const match = computedMatch
? computedMatch
: path
? matchPath(location.pathname, this.props)
: context.match;
const props = {
...context,
location,
match
};
// match 匹配 children是function或者是節點
// 非match 不匹配 children是function
return (
<RouterContext.Provider value={props}>
{match
? children
? typeof children === "function"
? children(props)
: children
: component
? // ? React.cloneElement(element, props)
React.createElement(component, props)
: render
? render(props)
: null
: typeof children === "function"
? children(props)
: null}
</RouterContext.Provider>
);
// return match ? React.createElement(component, this.props) : null;
}}
</RouterContext.Consumer>
);
}
}
爲了看的更清楚,當訪問首頁 / 時傳遞的props截圖如下: