一、 新建脚手架并配置好文件夹
create-react-app my-router
二、建立context.js
import React from "react";
// 目的是给底下的子组件透传路由的信息
export default React.createContext()
三、封装BrowserRouter
import React, {Component} from 'react'
import Context from './context'
export default class BrowserRouter extends Component{
constructor(props) {
super(props)
// 定义路由状态对象
this.state = {
location: {
// 获取当前路由路径
pathname: window.location.pathname || "/",
// 定义路由参数
query: undefined
},
match: {
}
}
}
componentWillMount() {
// 监测路由的前进后退
window.addEventListener('popstate', () => {
this.setState({
// 更改最新location
location: {
pathname: window.location.pathname,
match: this.state.match,
history: {
push: (to) => {
// 根据当前to去匹配不同的路由 实现路由切换
if(typeof to === 'object') {
let { pathname, query } = to
this.setState({
location: {
query,
pathname
}
})
// 进行路由跳转
window.history.pushState({}, {}, pathname)
}else {
// 如果是字符串
this.setState({
location: {
pathname: to
}
})
// 进行路由跳转
window.history.pushState({}, {}, to)
}
}
}
}
})
})
}
render() {
// 保存当前路由信息
const currentRoute = {
// 更改最新location
location: this.state.location,
match: this.state.match,
history: {
push: (to) => {
// 根据当前to去匹配不同的路由 实现路由切换
if(typeof to === 'object') {
let { pathname, query } = to
this.setState({
location: {
query,
pathname
}
})
window.history.pushState({}, {}, pathname)
}else {
// 如果是字符串
this.setState({
location: {
pathname: to
}
})
window.history.pushState({}, {}, to)
}
}
}
}
return (
// 通过Context把数据透传到子组件
<Context.Provider value={currentRoute}>{this.props.children}</Context.Provider>
)
}
}
四、安装path-to-regexp包
安装他的目的是匹配route中props属性的path, 简化了写正则的步骤
npm i path-to-regexp
五、封装Route组件
import React, {Component} from "react";
import Context from './context'
import { pathToRegexp, match } from "path-to-regexp"
export default class Route extends Component{
// 将context定义到类上
static contextType = context
render() {
const currentRoutePath = this.context.location.pathname // 从上下文context中获取当前路由
const { path, component: Component, exact=false } = this.props // 获取Route组件props的路由
const paramsRegexp = match(path, {end: exact}) // 获取params的表达式
const matchResult = paramsRegexp(currentRoutePath)
console.log(`路由匹配结果`, matchResult)
this.context.match.params = matchResult.params
const props = {
...this.context
}
const pathRegexp = pathToRegexp(path, [], {end: exact}) // 生成路径匹配表达式
// 如果当前路径与传入的props的路径匹配, 那么渲染对应的组件, 并传递给对应的路由数据
if(pathRegexp.test(currentRoutePath)) {
return (<Component {...props}></Component>)
}
// 没有匹配到什么都不渲染
return null
}
}
六、将封装的路由组件在router文件夹暴露出去
index.js
import BrowserRouter from "./BrowserRouter";
import Route from "./Route";
// 将定义的路由组件暴露出去
export {
BrowserRouter,
Route
}
七、在pages文件夹,定义三个要渲染的组件
// Page1
export default function Page1() {
return <div>Page1</div>
}
// Page2
export default function Page2() {
return <div>Page2</div>
}
// Page3
export default function Page3() {
return <div>Page3</div>
}
八、在router.js中使用路由
import Page1 from "./pages/page1";
import Page2 from "./pages/page2";
import Page3 from "./pages/page3";
import React, { Component } from "react";
import { Route, BrowserRouter } from './router'
class Router extends Component {
constructor(props) {
super(props)
this.state = {
}
}
render() {
return (
<BrowserRouter>
<Route exact path="/" component={Page1} />
<Route exact path="/page2" component={Page1} />
<Route exact path="/page3" component={Page1} />
</BrowserRouter>
)
}
}
九、在App中引入router
import Router from "./router";
export default function App() {
return <Router></Router>
}
我们可以看到根路径 /匹配的是Page1
/page2 匹配的是Page2 说明我们大功告成了🎇
下面我再来封装个Link组件 用于点击跳转
十、封装Link组件
在routerDom文件夹中新建Link.css Link.js
Link.css
.link {
text-decoration: none;
color: orange;
cursor: pointer;
}
Link.js
import React, {Component} from "react";
import context from "./context";
import './Link.css'
export default class Link extends Component {
static contextType = context
render() {
// 获取要跳转的信息, to可以是对象也可以是字符串
let {to} = this.props
// 点击通过context的hittory的push方法进行跳转
return <a className="link" href="" onClick={() => this.context.history.push(to)}>{this.props.children}</a>
}
}
十、在pages组件引入Link跳转组件
// Page1
import Link from "../routerDom/Link"
export default function Page1() {
return <div>
Page1
<Link to="/page2">跳转到page2</Link>
</div>
}
点击跳转则跳转到了page2
十一、总结
React-router是通过context全局上下文来把路由参数(location)传递给他的子组件的。在BrowserRouter中 通过history路由的popstate监听路由的前进后退, 把最新的路径(window.location.pathname)传给Route组件, Route进行路径的匹配 展示对应的组件, Link组件通过history的push方法,调用window.history.pushState进行路由的跳转