路由是跟据不同的url地址展示不同的页面
后端路由
服务端解析url地址,发送数据,但是每一次切换都需要重新刷新页面,对用户体验不好
前端路由
用户在点击切换路由的时候不需要重新刷新页面,路由由前端维护,分为hash路由和history路由,带#号的为hash路由,hash路由兼容性比较好
history路由
H5新增了window.history.pushstate 和window.history.replaceState
hash路由
hashchange事件来监听url hash值的改变
react-router
架构
Route 拿到context 中的location 和history 给 component, 然后根据路径 path 和pathname 生出一个match 也传给component
实现
HashRouter
新建一个文件夹,react-router-dom, 在index.js 文件中导出 HashRouter,
export { default as HashRouter } from './HashRouter';
export { default as BrowserRouter } from './BrowserRouter'
复制代码
然后来写HashRouter.js 文件
实际上引用的还是Router组件,通过createHashHistory 创建hash的history对象,传给Router
// HashRouter.js
import { Router } from '../react-router'
import { createHashHistory } from 'history'
const HashRouter = (props) => {
// 创建一个history对象,创建一个history对象,但是是使用hash实现
let hashHistory = createHashHistory()
return (
<Router history={hashHistory}>
{props.children}
</Router>
)
}
export default HashRouter
复制代码
新建一个文件夹,react-router
在Router传值给Route的时候,一共传了三个对象,history,location, match, 通过context的方法传递给下面的Route。在这里我们写一个RouterContext.js文件,用于所有组件都可以引用到这个context
// RouterContext.js
import React from 'react'
const RouterContext = React.createContext();
export default RouterContext;
复制代码
然后来实现Router
// Router.js
import React, {useState} from 'react'
import RouterContext from './RouterContext'
const Router = (props) => {
const { history } = props; // 可能是hashHistory 可能是 browserHistory
const [location, setLocation] = useState(history.location)
// 监听历史对象中的路径变化,当路径发生变化后执行回调函数
// 返回一个location 参数就是最新的对象
history.listen(location => setLocation(location))
// 里面有三个值
const value = {
history, // 这个里面的location和外面的是一样的
// location 代表当前路径 url 地址
// location: {pathname:'', state: undefined, search: '', hash:"", }
// match: {
// isExact: false,
// path: '/',
// url:'/'
// }
location,
}
return (
<RouterContext.Provider value = {value}>
{props.children}
</RouterContext.Provider>
)
}
export default Router
复制代码
最后我们来实现Route进行渲染不同的组件
这里判断传给route的path路径和location.pathname(当前地址中的url地址)匹不匹配,匹配就渲染组件
// Route.js
import React, {useContext} from 'react'
import RouterContext from './RouterContext'
const Route = (props) => {
const { history, location, match } = useContext(RouterContext);
const { path, component: RouterComponent, exact } = props;
let renderElement = null;
// 将history和location传给组件
let renderProps = {
history,
location
}
if (path === location.pathname) {
renderElement = < RouterComponent {...renderProps} />
}
return renderElement
}
export default Route;
复制代码
最后我们来实现 createHashHistory 这个方法, 用来创建出的 hash路由下面的 history 对象
在history文件夹下面新建一个createHashHistory.js 文件 用来实现history
// createHashHistory.js
const createHashHistory = () => {
let action;
let listenerList = [];
let historyStack = [];
let historyIndex = -1;
let state;
// 用于监听到location改变后修改location
const listen = (handleLocation) => {
// hadleLocation是一个改变location的函数
listenerList.push(handleLocation)
return () => {
const index = listenerList.indexOf(handleLocation)
listenerList.splice(index, 1);
}
}
const handelHashChange = () => {
// 当前的路径
let pathname = window.location.hash.slice(1);
// 将新的action和pathname赋值给histor.action
Object.assign(history,{
action,
location: {pathname, state}
})
if (!action || action === 'PUSH') {
historyStack[++historyIndex] = history.location
} else if (action === 'REPLACE') {
historyStack[historyIndex] = history.location
}
listenerList.forEach(handleLocation => handleLocation(history.location))
}
window.addEventListener('hashchange', handelHashChange)
const push = (newPath, newState) => {
action = 'PUSH';
let pathname;
if (typeof newPath === 'object') {
state = newPath.state;
pathname = newPath.pathname;
} else {
pathname = newPath;
state = newState;
}
window.location.hash = pathname;
}
const replace = (newPath, nextState) => {
action = 'REPLACE'
let pathname;
if (typeof newPath === 'object') {
state = newPath.state;
pathname = newPath.pathname
} else {
pathname = newPath;
state = nextState
}
window.location.hash = pathname;
}
const go = (n) => {
action = 'POP'
historyIndex += n;
let newLocation = historyStack[historyIndex];
state = newLocation.state;
window.location.hash = newLocation.pathname;
}
const goBack = () => {
go(-1)
}
const goForward = () => {
go(1)
}
// 自己创建的一个对象,类似于window.history,用于hash路由,两者没有关系
let history = {
listen, //监听
action,
// 给location一个默认值
location: {pathname:'/', state:undefined},
go,
goBack,
goForward,
push,
replace,
}
// 当url输入的地址和当前地址是一样的时候,相当于刷新页面,这个时候是不会触发到hashchange事件的因为hash值没有变,所以需要判断一下,手动调用一下这个事件
if (window.location.hash) {
handelHashChange()
} else {
window.location.hash = "/"
}
// 返回自己创建的history对象
return history
}
export default createHashHistory;
复制代码
现在我们已经简单实现了,来看一下效果吧