简略版本的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> ) }}