在上一篇中我们实现了BrowserRoute,这篇我们继续实现HashRouter。
本文的核心功能:
HashRouter
Route
Link
MenuLink
Switch
Redirect
import React from "react";
import ReactDOM from "react-dom";
import {HashRouter as Router, Route} from ‘react-router-dom‘ //路由库
import Home from ‘./components/Home‘
import User from ‘./components/User‘
import Profile from ‘./components/Profile‘
ReactDOM.render(
<Router>
<Route exact path="/" component={Home}></Route>
<Route path="/user" component={User}></Route>
<Route path="/profile" component={Profile}></Route>
</Router>,document.getElementById(‘root‘)
)
HashRouter的实现如下:
import React, {useState} from ‘react‘
import RouterContext from ‘./RouterContext‘
/**
* HashRouter只是一个容器,并没有DOM结构,它渲染的就是它的子组件,并向下层传递location
*/
export default class HashRouter extends React.Component {
state = {
location: {
pathname: window.location.hash.slice(1), // #/user
state:window.history.state
}
}
//组件挂载完成之后,根据hash改变pathname的值
componentDidMount(){
window.addEventListener(‘hashchange‘,event =>{
this.setState({
...this.state,
location: {
...this.state.location,
pathname:window.location.hash.slice(1)
}
})
})
window.location.hash = window.location.hash || ‘/‘ //如果没有hash值就给一个默认值
}
render() { //渲染子组件 如果子组件里面嵌套着二级三级路由需要通过上下文Context取
let routerValue = {
location: this.state.location
}
return (
// this.props.children
<RouterContext.Provider value={routerValue}>
{this.props.children}
</RouterContext.Provider>
)
}
}
因为HashRouter渲染的是它的子组件,那么子组件里面有可能嵌套着二级三级路由,这个时候就需要上下文Context来读取嵌套的值,需要创建一个Context
import React from ‘react‘
//创建一个上下文
let context = React.createContext()
export default context
在上面的HashRouter里面引入
文章开始的例子中我们说过,route代表一条路由规则,path代表此规则的路径, component代表要渲染的组件,如果说通过Context传下来的路径location.pathname与当前属性中的路径path相匹配就进行渲染。
import React from ‘react‘
import RouterContext from ‘./RouterContext‘
import pathToRegexp from ‘path-to-regexp‘
export default class Route extends React.Component{
static contextType = RouterContext //拿到的就是 this.context.location.pathname
render() {
let {path, component: RouteComeponent, exact} = this.props
let pathname = this.context.location.pathname; //拿到地址栏路径
let paramName = []
let regexp = pathToRegexp(path, paramName, {end:exact})
//进行匹配
if(regexp.test(pathname)) {
//渲染
return <RouteComeponent></RouteComeponent>
}else {
return null
}
}
}
点击某个链接跳转到指定页面。实例:
import React from "react";
import ReactDOM from "react-dom";
import {HashRouter as Router, Route,Link} from ‘react-router-dom‘ //路由库
import Home from ‘./components/Home‘
import User from ‘./components/User‘
import Profile from ‘./components/Profile‘
/**
* Router是路由容器
* Route是路由规则,一个Route代表一个路由规则
* path 代表路径 component代表要渲染的组件
*/
ReactDOM.render(
<Router>
<Link to="/">home</Link>
<Link to="/user">user</Link>
<Link to="/profile">profile</Link>
</Router>,document.getElementById(‘root‘)
)
渲染结构:
可以看到它的渲染结构就是一个a链接,href就是属性to对应的值,所以可以这么实现link方法:
import React from ‘react‘
function Link (props) {
return (
<a href="{`#${props.to}`}">{props.children}</a>
)
}
但是这种方法只适用于hash路由,如果不是hash路由,就需要通过上下文拿到一个history对象的一个push方法,实现路径的跳转。
import React from ‘react‘
import RouterContext from ‘./RouterContext‘
function Link (props) {
return (
<RouterContext.Consumer>
{
routerValue => (
<a href="{`#${props.to}`}" onClick={() => routerValue.history.push(props.to)}>{props.children}</a>
)
}
</RouterContext.Consumer>
)
}
同时需要修改HashRouter传过来的实参
修改前:
修改后:
接下来实现动态路由和二级路由
switch是为了解决route
的唯一渲染,保证路由只渲染一个路径。
如果配置了<Switch>
<Router history={history}>
<Switch>
<Route path=‘/home‘ render={()=>(<div>首页</div>)}/>
<Route path=‘/home‘ component={()=>(<div>首页</div>)}/>
</Switch>
</Router>
如果没有配置:
<Router history={history}>
<Route path=‘/home‘ render={()=>(<div>首页</div>)}/>
<Route path=‘/home‘ render={()=>(<div>首页</div>)}/>
</Router>
实现Switch:
import React, { useContext } from ‘react‘
import RouterContext from ‘./RouterContext‘
import pathToRegexp from ‘path-to-regexp‘
/**
* switch 的作用是负责子组件的匹配,只会渲染第一个匹配上的子组件
* useContext 获取上下文对象的一种方式
* @param {*} props
*/
export default function (props) {
let routerContext = useContext(RouterContext) //拿到上下文中传过来的location
let children = props.children
children = Array.isArray(children) ? children : [children] //判断是否是数组,如果不是就包装成数组
let pathname = routerContext.location.pathname
//对子组件进行匹配
for(let i = 0; i < children.length; i++) {
let child = children[i] //child是一个react元素它的返回值是一个虚拟dom {type:Route,props:{exact,path,component}}
let {path=‘/‘,component,exact=false} = child.props //取出对应的属性
let regexp = pathToRegexp(path, [], {end: exact})
let matched = pathname.match(regexp)
//若匹配进行渲染
if(matched) {
return child
}
}
//若不匹配就返回null
return null
}
拿到上下文中传过来的location,然后取出pathname。再对它的子组件进行遍历,如果子组件的path属性和当前上下文中传过来的pathname属性相匹配就进行渲染,若不匹配就返回null。
重定向,当所有都不匹配的时候会重定向到新的页面,就是改变path值驱动页面重新渲染。
例子:
import React from "react";
import ReactDOM from "react-dom";
import { HashRouter as Router, Route, Link, Switch, Redirect } from "react-router-dom"; //路由库
import Home from "./components/Home";
import User from "./components/User";
import Profile from "./components/Profile";
/**
* Router是路由容器
* Route是路由规则,一个Route代表一个路由规则
* path 代表路径 component代表要渲染的组件
*/
ReactDOM.render(
<Router>
<Link to="/">home</Link>
<Link to="/user">user</Link>
<Link to="/profile">profile</Link>
<Switch>
<Route exact path="/" component={Home}></Route>
<Route path="/user" component={User}></Route>
<Route path="/user" component={User}></Route>
<Route path="/profile" component={Profile}></Route>
<Redirect to="/"></Redirect>
<Redirect from="/home" to="/"></Redirect>
</Switch>
</Router>,
document.getElementById("root")
);
第一个Redireact是当location.pathname与上面所有的path属性不相等的时候会重定向到path=‘/‘的页面
第二个Redireact是当location.pathname为/home的时候,重定向到path=‘/‘的页面
实现Redireact:
import React, { useContext } from ‘react‘
import RouterContext from ‘./RouterContext‘
export default function (props) {
let routerContext = useContext(RouterContext)
//当Redirect元素的props.from属性和当前location.pathname属性相等时或者from属性不存在时就直接跳转到to
if(!props.from || props.from === routerContext.location.pathname) {
routerContext.history.push(props.to)
}
return null
}
。。。明天更新。。。
推荐博文:
https://segmentfault.com/a/1190000014313428
https://www.cnblogs.com/sunLemon/p/9020153.html
原文:https://www.cnblogs.com/lyt0207/p/12734944.html