实现简易版 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截图如下: