import HashRouter from './HashRouter';
import Route from './Route';
import Link from './Link';
Import Switch from './Switch';
import Redirect from './Redirect';
import NavLink from './NavLink';
import WithRouter from './WithRouter';
import Prompt from './Prompt';
import BrowserRouter from './BrowserRouter';
export {
HashRouter,
Route,
Link,
Swicth,
Redirect,
NavLink,
WithRouter,
Prompt,
BrowserRouter
}
複製代碼
HashRouter
事實上HashRouter只是個容器,並不具備DOM結構,他的職責就是渲染他的子組件,和想下層組件傳遞數據
exp: location
。css
<!--建立並導出一個HashRouter文件-->
import React from 'react';
import RouterContext from './RouterContext';
export default class HashRouter extends React.Component {
state = {
location : {
// 去掉#號,hash是帶有#
pathname: window.location.hash.slice(1)
}
}
componentDidMount(){
// 監聽haschange事件,當以觸發hashchange事件就是去改變當前的狀態以後再同步hash的值
window.addEventListener('hashchange', () = > {
this.setState({
...this.state.location,
pathname: window.location.hash.slice(1) || '/',
state: this.locationState
})
});
window.location.hash = window.location.hash || '/';
}
render() {
let self = this;
let history = {
location: this.state.location,
push(to) {
if(typeof to === 'object') { // 有可能用戶傳遞的是個包含路徑和狀態的對象
let {pathname, state} = to;
that.locationState = state;
window.location.hash = pathname;
} else { //傳遞的字符串
window.location.hash = to
}
}
block(prompt){
history.prompt = prompt
}
unblock() {
history.prompt = null;
}
}
let routerValue = {
location: that.state.location,
history
}
return (
<RouterContext.Provider value= {routerValue}> {this.props.chilren} </RouterContext.Provider> ) } } 複製代碼
這裏咱們會實現Route路由組件,他不少時候表明的是一條路由規則。hashRouter中的state、location、pathname是經過上下文傳遞出去的。vue
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
// component是route組件裏面傳過來的那個component屬性值也就是該條路由所對應的組件,這裏我將它重命名爲RouteComponent。
// path是該路由組件上傳過來的path路徑用來作匹配的
// excat 表明的是該條路由上的路徑是否是精確匹配和`path-to-regexp`的參數對應。
// 組件的渲染有三種方式分別是傳入一個render函數進行渲染、傳入一個children並且改children屬性是一個函數執行便可實現渲染、最後就是直接按照匹配的組件進行渲染。
render() {
let { path='/', component: RouteComponent, excat= false, render, children } = this.props;
let path = typeof path === 'object' ? path.pathname : path;
let pathname = this.context.location.pathname;
let paramNames = [];
let regexp = pathToRegexp(path, paramNames, {end: exact});
paramNames = paramNames.map(p=>p.name);
let matched = pathname.match(regexp);
let routeProps = {
location: this.context.location,
history: this.context.history
};
if(matched) {
let [url, ...values] = matched;
let params = values.reduce((memo, cur, index) => {
memo[paramNames[index]] = cur;
return memo;
}, {})
let match - {
url,
path,
isExact: pathname === url,
params
}
routerProps.match = match;
if(RouteComponent) {
return <RouteCompoponent {...routerProps}/> } else if(render) { return render(routerProps); } else if(children) { children(routerProps); } else { return null; } } else { if(children) { return children(routerProps); } else { return null; } } } } // match 屬性的存在與否很大程度上和這個組件是不是路由渲染出來的正相關 // isExact 表示的是是否爲精確匹配,同時也是表明了pathname和url是否徹底相同 複製代碼
// 不少人會問上面那個爲何有個文件不知道在哪,那麼接下來我就來實現一下RouterContext文件
// 這個其實賊簡單,不信你看看就知道了
import React from 'react';
export default const context = React.createContext();
// 要不要太簡單了一點
複製代碼
<!--這裏面我以爲仍是用函數組件來實現比較好,畢竟都已經functional programming-->
import React from 'react';
import RouterContext from 'RouterContext';
//注意千萬不要寫href而且賦值爲具體路徑,由於他會繞過路由的攔截走a標籤的屬性
export default function Link(props) {
return (
<RouterContext.Consumer> { routerValue => { <a {...props} onClick ={ () => { routerValue.history.push(props.to) } } > { prpos.children } </a> } } </RouterContext.Consumer> ) } 複製代碼
vue
的router-view
很像。import React, {useContext} from 'react';
import RouterContext from 'RouterContext';
import pathToRegexp from 'path-to-regexp';
// 這裏我又引入了一個新東西useContext是的他就是傳說中的hooks之一了。
// useContext是獲取上下文對象的第三種方式
// static contextType (類中使用)
// Consumer(函數中使用)
// 還能夠ReactHooks useContext獲取上下文對像
export default function (props) {
let routerContext = useContext(RouterContext);
let children = props.children;
children = Array.isArray(children) ? children : [children];
let pathname = routerContext.location.pathname;// 從上下文中取出當前的路徑
for(let i = 0; i < children.length; i++) {
// 千萬記住這裏的child是React元素或者虛擬DOM並非什麼組件
// 至關因而`React.createElement(Route,{exact,path,component})`的結果,
// `{type:Route,props:{exact,path,component}}`
let child = children[i];
let { path = '/', component, exact = false} = child.props;
<!--這裏面就用到了路由處理的正則庫不明白的能夠看看我以前的一篇介紹path-to-regexp的文章--> let regexp = pathToRegexp(path, [], {end: exact}); <!--用當前的路徑去匹配--> let matched = pathname.match(regexp); <!--匹配到了直接渲染子組件--> if(matched) { return child; } } <!--沒有匹配的子組件則return null--> return null } 複製代碼
import React, {useContext} from 'react';
import RouterContext from './RouterContext';
export default function (props) {
let routerContext = useContext(RouterContext);
if(!props.from || props.from === routerContext.location.pathname) {
routerContext.history.push(props.to);
}
return null;
}
複製代碼
.acitve {
color: #425658;
background: #eeeddd;
}
複製代碼
import React from 'react';
import './NavLink.css'
import { Route, Redirect, Link } from './index';
export default function (props) {
let { to, exact, children } = props;
return (
<Route path={to} exact={exacr} children={ routerProps => { <Link className={ routerProps.match? 'active' : '' to={to} }> {children} </Link> } }> </Route>
)
}
複製代碼
import React, {useContext} from 'react';
import RouterContext from './RouterContext';
export default function (props) {
let routerContext = useContext(RouterContext);
let {bool, msg} = props;
if(bool) {
routerContext.history.block(msg);
} else {
routerContext.history.unblock()
}
return null
}
複製代碼
<!--1、withContext.js-->
import React from 'react';
import { Route } './index';
export default function (OldComponent) {
reutnr props => {
<Route>
render={
routerProps =><OldComponent {...props} {...routerProps}/> } </Route>
}
}
export default function (OldComponent) {
return (
<RouterContext.Consumer> { contextValue => ( <div> <OldComponent/> </div> ) } </RouterContext.Consumer> ) } 複製代碼
import React, { useState, useEffect } from 'react';
import RouterContext from "./RouterContext.js";
export default function BrowserRouter(props) {
let [currentState, setCurrentState] = useState({ location: { pathname: window.location.pathname } });
useEffect(() => {
window.onpushstate = (state, pathname) => {
setCurrentState({
location: {
...currentState.location,
pathname,
state
}
});
}
window.onpopstate = (event) => {
setCurrentState({
location: {
...currentState.location,
pathname: window.location.pathname,
state: event.state
}
});
}
}, []);
const globalHistory = window.history;
let history = {
location: currentState.location,
push(to) {
if (history.prompt) {
let target = typeof to === 'string' ? { pathname: to } : to;
let yes = window.confirm(history.prompt(target));
if (!yes) return;
}
if (typeof to === 'object') {//傳的是一個對象 {pathname,state}
let { pathname, state } = to;
globalHistory.pushState(state, null, pathname);
} else {//就是個字符串
globalHistory.pushState(null, null, to);
}
},
block(prompt) {
history.prompt = prompt;
},
unblock() {
history.prompt = null;
}
}
let routerValue = {
location: currentState.location,
history
}
return (
<RouterContext.Provider value={routerValue}> {props.children} </RouterContext.Provider> ) } 複製代碼