简单版本的React-Router
Posted aren945
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了简单版本的React-Router相关的知识,希望对你有一定的参考价值。
简单版本的React-Router
React-Router需要掌握的核心知识点是Context
React的上下文
用法:
import React from \'react\';
// Provider 供应者
// Consumer 消费者
let {Provider, Consumer} = React.createContext();
export {
Provider,
Consumer
}
Provider
组件是数据的提供者,会将value
属性提供给Consumer
消费者组件。
Provider组件的用法
import React from \'react\';
export default class HashRouter extends React.Component {
render() {
return (
<Provider value={{a:1}}>
this.props.children
</Provider>
)
}
}
Consumer组件的用法
Consumer
组件的内容是一个方法,方法接受一个参数,即是Provider
提供的数据。
import React from \'react\';
export default class Route extends React.Component {
render() {
<Consumer>
{state => {
// 这里的state就是Provider提供的数据
console.log(state);
}}
</Consumer>
}
}
路由实现思想
创建一个context.js
的文件创建一个Context
类,并导出Provider
以及Consumer
组件,创建一个HashRouter
的组件,目的是 通过hashchange
的事件来监听location.hash
的变化。并用于包裹Switch
和Route
以及Redirect
组件,像子组件提供数据。Switch
组件的目的是为了让 路径匹配只匹配到一个,Route
组件用于定义路由。Redirect
组件是在未匹配到路由的时候,进行重定向操作。使用Link
组件来进行导航,Link
组件内通过Consumer
来消费HashRouter
传递过来的数据,调用HashRouter
通过Provider
上下文提供一个push
方法来改变HashRouter
的state
属性达到重新渲染的目的。
组件分析
react-router-dom/context.js
context.js
这个文件就是创建一个上下文环境,用以在HashRouter
组件中通过Provider
来提供属性,然后在其他组件中通过Consumer
来消费所提供的数据。代码如下:
import React from \'react\';
// Provider 供应者
// Consumer 消费者
let {Provider, Consumer} = React.createContext();
export {
Provider,
Consumer
}
react-router-dom/index.js
在ECMASCRIPT6 MODULE
中,使用import
导入模块的时候,会默认去找导入目录下面的index.js
。所以可以用来导出组件。
import HashRouter from \'./HashRouter\';
import Route from \'./Route\';
import Link from \'./Link\';
import Redirect from \'./Redirect\';
import Switch from \'./Switch\';
export {
HashRouter, Route, Link, Redirect, Switch
}
react-router-dom/HashRouter.js
HashRouter
组件作为最外层的根组件,用来处理路由变化,监听到hash
变化改变自身的state
,然后重新渲染组件。在HashRouter
实例化后就会监听hashchange
事件,在hash
改变后改变state
。HashRouter
组件里面使用Provider
来包裹This.props.children
,这样子子组件就可以访问到通过Provider
提供的上下文了。在HashRouter
组件里通过Provider
组件来提供两个属性,location
和history
属性。location
属性记录的是当前的window.location
对象,也是state
里面的location
字段;history
字段提供一个push
方法,push
方法用于设置window.location.hash
的值来实现路由跳转的功能。代码如下:
import React from \'react\';
import {Provider} from \'./context\';
class HashRouter extends React.Component {
constructor(props) {
super(props);
this.state = {
location: {
pathname: window.location.hash.slice(1) || \'/\'
},
}
}
componentDidMount() {
// 默认hash没有时候跳转到/
window.location.hash = window.location.hash || \'/\';
// 监听hash值变化,重新设置状态
window.addEventListener(\'hashchange\', ()=> {
this.setState({
location: {
...this.state.location,
pathname: window.location.hash.slice(1) || \'/\'
}
})
})
}
render() {
const value = {
location: this.state.location,
history: {
push(to) {
window.location.hash = to;
}
}
}
return(
<Provider value={value}>
{this.props.children}
</Provider>
)
}
}
export default HashRouter;
react-router-dom/Switch.js
当存在Redirect
组件时,任何路由都将跳转到其配置的路径去,为了解决这个问题,创建了一个一个Switch
组件, Switch
会遍历所有的this.props.children
,将child
的props
上的path
字段用来跟从Consumer
组件获取到的location
里面的pathname
进行正则匹配,若匹配成功,则返回该组件。代码如下:
import React from \'react\';
import {Consumer} from "./context";
import pathToRegexp from "path-to-regexp";
/**
* 只匹配一个
*/
export default class Switch extends React.Component {
render() {
return (
<Consumer>
{
state => {
let pathname = state.location.pathname;
let children = this.props.children;
for (let i =0; i < children.length; i++) {
const child = children[i];
// 可能没有path属性,比如Redirect组件
let path = child.props.path || \'\';
const reg = pathToRegexp(path, [], {end: false});
// 匹配成功
if (reg.test(pathname)) {
return child;
}
}
}
}
</Consumer>
)
}
}
react-router-dom/Route.js
Route
组件是用于声明路由,接受三个props
属性:
path
: 这个字段指定路由的路径component
: 这个字段用于指定路由所对应的组件exact
: 是否严格匹配,严格匹配是指路径以path
结尾。例如:可以匹配到/a
但是匹配不到/a/b
, 如果是非严格匹配的话则可以匹配到。
Route
组件通过自身的props
获取到path
属性,根据path
字段来使用pathToRegexp
库来创建一个正则来匹配从Consumer
消费到的location.pathname
属性,如果匹配到了,就返回props
字段里面的component
所声明的组件。pathToRegexp库接受一个路径、一个数组和其他配置,可以声明一个数组传递进去,用于存储路由携带的动态参数,例如
/a/:id/:name,使用
pathToRegexp`库的话,传递的数组参数会得到一个如下的值:
[{
name: \'id\',
}, {
name: \'name\'
}]
可以使用数组的map
方法遍历一下数组就可以通过name
属性获取到传入参数的键的一个数组,此时再使用pathname
字段用match
方法去匹配通过pathToRegexp
库所生成的正则,就可以获取到动态参数的值。使用reduce
方法遍历存放参数建的数组,由此来拼装出一个参数对象。最后将从Consumer
获取的数据以及拼装好的params
参数数据通过props
传递给Component
。代码如下:
import React from \'react\';
import pathToRegexp from "path-to-regexp";
import {Consumer} from \'./context\';
class Route extends React.Component {
constructor(props) {
super(props);
this.state = {}
}
render() {
return (
<Consumer>
{(state) => {
// path是Route中配置的。
const {path, component: Component, exact = false} = this.props;
// pathName是location中的
let pathname = state.location.pathname;
// 优化方案, 根据path实现一个正则
let keys = []
let reg = pathToRegexp(path, keys, {end: exact});
keys = keys.map(item => item.name); // 键
let res = pathname.match(reg);
let [url, ...values] = res || []; // 值
// if (pathname === path) {
let props = {
location: state.location,
history: state.history,
match: {
params: keys.reduce((obj, current, idx) => {
obj[current] = values[idx];
return obj;
}, {})
}
}
if (res) {
return <Component {...props}/>
}
return null
}}
</Consumer>
);
}
}
export default Route;
react-router-dom/Link.js
Link
组件用去路由导航。Link
接受一个to
的props
属性,用于声明跳转到哪个路径,Link
同理可以通过Consumer
来消费到从HashRouter
传递过来的数据,其中就包含history.push
这个方法,在组件上绑定一个点击事件,点击后调用history.push
这个方法就实现了设置window.location.hash
的功能,由此出发hashchange
事件,然后修改了HashRouter
的state
,重新渲染组件。代码如下:
import React from \'react\';
import {Consumer} from "./context";
export default class Link extends React.Component {
render(){
return (
<Consumer>
{
state => {
return <button onClick = {
(e) => {
e.preventDefault();
state.history.push(this.props.to)
}
}>{this.props.children}</button>
}
}
</Consumer>
)
}
}
react-router-dom/Redirect.js
Redirect
是一个重定向组件,当前面的Route
组件没有找到匹配的路由的时候,会命中Redirect
组件进行重定向,本质就是通过Consumer
来调用HashRouter
里面的history.push
方法。代码如下:
import React from \'react\';
import {Consumer} from "./context";
export default class Redirect extends React.Component {
render() {
return (
<Consumer>
{state => {
// 重定向就是匹配不到后直接跳转到redirect中的to路径
state.history.push(this.props.to);
return null;
}}
</Consumer>
)
}
}
demo例子
App.jsx
<div className="App">
<Router>
<>
<div>
<Link to={"/home"}>主页</Link>
<Link to={"/profile"}>配置</Link>
<Link to={"/user"}>用户</Link>
</div>
<div>
{/*
exact 严格匹配
路由/home/123不会加载/home的组件
*/}
<Switch>
<Route path="/home" exact={true} component={Home} />
<Route path="/home/123" component={Home12} />
<Route path="/profile" component={Profile} />
<Route path="/user" component={User} />
<Redirect to="/home" />
</Switch>
</div>
</>
</Router>
</div>
二级路由
User.js
import React from \'react\';
import {Link, Route} from "../react-router-dom";
import UserAdd from "./UserAdd";
import UserList from "./UserList";
import UserDetail from "./UserDetail";
class User extends React.Component {
constructor(props) {
super(props);
this.state = { }
}
render() {
return (
<div style={{
display: "flex",
}}>
<nav>
<li><Link to={"/user/add"}>添加用户</Link></li>
<li><Link to={"/user/list"}>用户列表</Link></li>
</nav>
<div style={{
flexShrink: 0
}}>
<Route path={"/user/add"} component={UserAdd} />
<Route path={"/user/list"} component={UserList} />
<Route path={"/user/detail/:id"} exact={true} component={UserDetail} />
</div>
</div>
);
}
}
export default User;
手动调用方法实现路由跳转
UserAdd.js
import React from \'react\';
export default class UserAdd extends React.Component {
constructor(props){
super(props);
this.nameInput = React.createRef();
}
handleSubmit = (e) => {
e.preventDefault();
console.log(this.nameInput.current.value);
this.props.history.push(\'/user/list\');
}
render() {
console.log(this.props);
return (
<div>
<form onSubmit={this.handleSubmit}>
<input ref={this.nameInput} name={"name"} placeholder={"请输入姓名"} type={"text"}/>
<button type={"submit"}>提交</button>
</form>
</div>
)
}
}
获取参数
UserDetail.js
import React from \'react\';
export default class UserDetail extends React.Component {
render() {
console.log(this.props )
return (
<div>
this is detail component {this.props.match.params.id}
</div>
)
}
}
以上是关于简单版本的React-Router的主要内容,如果未能解决你的问题,请参考以下文章