前言
react开发实践中,router是必不可少的一个库,之前对该库的使用是一知半解,所以手写一次,加深对该库的理解,学习原理和设计思想。
react-router-dom
核心库由router-dom实现,其中主要有以下几种组件。
BrowserRouter
一级组件,用于包裹router下的各类子组件,主要管理以何种方式进行跳转
import { Component } from "react";
import { createBrowserHistory } from "history";
import Router from "./Router";
class BrowserRouter extends Component {
constructor(props) {
super(props);
this.history = createBrowserHistory(); // 创建跳转函数(使用插件库,磨平浏览器差异)
}
render() {
const { children } = this.props;
return <Router history={this.history} children={children} />
}
}
export default BrowserRouter;
Router
路由的父组件,管理各类通用逻辑
import React, { Component } from "react";
import RouterContext from "./RouterContext";
// 路由中间层,处理通用逻辑和数据
class Router extends Component {
static computeRootMatch(pathname) { // 默认path,当未传入时赋值。
return { path: "/", url: "/", params: {}, isExact: pathname === "/" };
}
constructor(props) {
super(props);
// console.log("props.history", props.history.location);
// 获取父级传入的location信息
this.state = {
location: props.history.location
}
// 监听路由变化,通知对应path的子组件更新
this.unlisten = props.history.listen(({ location }) => {
this.setState({ location })
})
}
componentWillUnmount() {
this.unlisten();
}
render() {
const { history, children } = this.props;
// console.log("history", RouterContext.Provider);
// 决定以何种方式跳转的history进行传递(browser, hash, memory)
return (
<RouterContext.Provider
value={{ history, location: this.state.location, match: Router.computeRootMatch(this.state.location.pathname) }}
>
{ children }
</RouterContext.Provider>
)
}
}
export default Router;
Route
真正的路由组件,接收用户传入的组件,匹配相应路径进行展示。
import React, { Component } from "react";
import RouterContext from "./RouterContext";
import matchPath from "./matchPath";
class Route extends Component {
render() {
// 类组件中可以使用 Consumer 获取跨层级数据传递
return (
<RouterContext.Consumer>
{(context) => {
const { location } = context;
const { path, children, component, render } = this.props;
const match = path ? matchPath(path, location.pathname) : context.match;
// route props 由真正的组件使用
const props = {
...context,
match,
location
}
// 匹配 按优先级进行返回 children(函数) > component(组件) > render(函数)> null
// 不匹配 按优先级进行返回 children > null
// 源码中使用连续三元进行匹配,不是很利于学习观看,所以这里拆分成了 if else
const createElement = () => {
if (match) {
if (children) {
if (typeof children === "function") return children(props);
else return children;
} else if (component) {
return React.createElement(component, props);
} else if (render) {
return render(props);
} else return null;
} else {
// 在react-router设计中,不匹配模式下,如果有children传入则直接返回children。
return typeof children === "function" ? children(props) : null;
}
}
// 这里再使用Provider传递一层props,用于自定义hooks获取最近的props,
return <RouterContext.Provider value={props}>
{createElement()}
</RouterContext.Provider>
}}
</RouterContext.Consumer>
)
}
}
export default Route;
Switch
返回多个路由子组件中,第一个被匹配的子组件。(独占路由)
import React, { Component } from "react";
import matchPath from "./matchPath";
import RouterContext from "./RouterContext";
// 在所有子路由组件中,返回第一个匹配的。
class Switch extends Component {
render() {
return <RouterContext.Consumer>
{(context) => {
const { location } = context
let match; // 记录是否匹配
let element; // 记录匹配的元素
// console.log('this.props.children', this.props.children);
React.Children.forEach(this.props.children, child => {
if (!match && React.isValidElement(child)) {
element = child;
match = child.props.path ? matchPath(child.props.path, location.pathname) : context.match;
}
})
// 如果匹配成功 返回对应组件即可
return match ? React.cloneElement(element, { computedMatch: match }) : null ;
}}
</RouterContext.Consumer>
}
}
export default Switch;
Link
跳转组件,点击进入新页面(对应的path页面)
import { useContext } from "react";
import RouterContext from "./RouterContext";
// 创建link,本质就是a标签的跳转
function Link({ to, children, ...rest }) {
const context = useContext(RouterContext); // 获取跨层级数据传递
const handleClick = (e) => {
e.preventDefault(); // 禁用默认跳转事件
context.history.push(to); // 使用自有跳转方式进行路由跳转
}
return <a href={to} onClick={handleClick} { ...rest }>{children}</a>
}
export default Link;
Redirect
重定向,当匹配当前组件时,跳转你想重定向的path组件
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, push = false } = this.props;
return (
<LifeCycle
// 重定向跳转页面
onMount={() => {
push ? history.push(to) : history.replace(to);
}}
/>
);
}}
</RouterContext.Consumer>
);
}
}
class LifeCycle extends Component {
componentDidMount() {
if (this.props.onMount) {
this.props.onMount.call(this, this);
}
}
render() {
return null;
}
}
结语
以上便是react-router的核心组件,还有withRouter,hooks,等没有一一写入,可前往github进行完整查看。