共计 7085 个字符,预计需要花费 18 分钟才能阅读完成。
简略版本的 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>
)
}
}