# react-router
# 1、router原理
web前端,路由位URL与UI的映射关系,改变URL不引起页面刷新。
# 1、hash路由
特点
- url 中带有一个#符号,但是#只是浏览器端/客户端的状态,不会传递给服务端
- hash 值的更改,不会导致页面的刷新
- hash 值的更改,会在浏览器的访问历史中添加一条记录。所以我们才可以通过浏览器的返回、前进按钮来控制 hash 的切换
修改:location.hash='#aaa'
监测:hashchange事件
# 2、history路由
特点
- url无#,美观,服务器可接收到路径和参数变化,需服务器适配
- 基于浏览器的history对象实现,主要为history.pushState 和 history.replaceState来进行路由控制。通过这两个方法,可以实现改变 url 且不向服务器发送请求
修改
- 点击后退/前进触发
popstate
事件,监听进行页面更新 - 调用history.pushState或history.replaceState触发相应的函数后,在后面手动添加回调更新页面
- 点击后退/前进触发
监测:无监测事件
# 3、非浏览器模式
在非浏览器环境,使用抽象路由实现导航的记录功能
- react-router的 memoryHistory
- vue-router 的 abstract 封装
# 2、vue-router
# 1、使用
import Vue from 'vue'
import VueRouter from 'vue-router'
import App from "./App.vue"
import Home from "./Home.vue"
//全局挂载router相关实例
Vue.use(VueRouter)
const router = new VueRouter({
mode: 'history',
routes: [
{path: '/app', component: App},
{
path: '/home',
component: Home,
children: [
{
path: 'home-sub',
//异步加载,独立拆包
component: () => import(/* webpackChunkName: "home-sub" */ "@/HomeSub.vue"),
}
]
},
]
})
new Vue({
router
}).$mount("#app");
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//正则表达式匹配参数
const reg = /([^#&?]+)=([^#&?]+)/g;
const url = "wangjxk.top/#weriw?we=12&jjj=eee"
const obj = {}
url.replace(reg, (_, c1, c2) => {
obj[c1] = c2;
})
console.log(obj); //{we: '12', jjj: 'eee'}
2
3
4
5
6
7
8
# 2、源码分析
# 1、use
- 用法:Vue.use(plugin),参数{Object | Function} plugin
- 作用:安装Vue.js插件,如果插件是一个对象,必须提供install方法。如果插件是一个函数,被作为install方法。调用install方法时,会将Vue作为参数传入。install方法被同一个插件多次调用时,插件也只会被安装一次。
- 本质:调用插件install方法,将插件推入数组保存
Vue.use = function (plugin) {
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []));
if(installedPlugins.indexOf(plugin) > -1){
return this
}
const args = toArray(arguments, 1)
args.unshift(this)
if(typeof plugin.install === 'function'){
plugin.install.apply(plugin, args);
} else if (typeof plugin === 'function'){
plugin.apply(null, args)
}
installedPlugins.push(plugin)
return this
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 2、router
- install
//vue-router中install.js
//1、原型挂载$router、$route对象
//2、全局注册<router-view>、<router-link>组件
import View from './components/view'
import Link from './components/link'
export let _Vue
export function install (Vue) {
if (install.installed && _Vue === Vue) return
install.installed = true
_Vue = Vue
const isDef = v => v !== undefined
const registerInstance = (vm, callVal) => {
let i = vm.$options._parentVnode
if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
i(vm, callVal)
}
}
Vue.mixin({
beforeCreate () {
if (isDef(this.$options.router)) {
this._routerRoot = this
this._router = this.$options.router
this._router.init(this)
Vue.util.defineReactive(this, '_route', this._router.history.current)
} else {
this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
}
registerInstance(this, this)
},
destroyed () {
registerInstance(this)
}
})
//挂载$router对象
Object.defineProperty(Vue.prototype, '$router', {
get () { return this._routerRoot._router }
})
//挂载$route对象
Object.defineProperty(Vue.prototype, '$route', {
get () { return this._routerRoot._route }
})
//全局注册组件,<router-view>、<router-link>可全局使用
Vue.component('RouterView', View)
Vue.component('RouterLink', Link)
const strats = Vue.config.optionMergeStrategies
// use the same hook merging strategy for route hooks
strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
- 构造函数
//index.js, vue-router构造器
export default class VueRouter {
//...
//路由守卫钩子相关定义
beforeEach(){/* */}
//路由方法定义
push(){/* */},
replace(){/* */},
//构造函数
constructor (options: RouterOptions = {}) {
this.app = null
this.apps = []
this.options = options
this.beforeHooks = []
this.resolveHooks = []
this.afterHooks = []
this.matcher = createMatcher(options.routes || [], this)
let mode = options.mode || 'hash'
this.fallback =
mode === 'history' && !supportsPushState && options.fallback !== false
if (this.fallback) {
mode = 'hash'
}
//非浏览器,默认abstract模式
if (!inBrowser) {
mode = 'abstract'
}
this.mode = mode
//不同模式,封装了不同的对象
switch (mode) {
case 'history':
this.history = new HTML5History(this, options.base)
break
case 'hash':
this.history = new HashHistory(this, options.base, this.fallback)
break
case 'abstract':
this.history = new AbstractHistory(this, options.base)
break
default:
if (process.env.NODE_ENV !== 'production') {
assert(false, `invalid mode: ${mode}`)
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//HTML5History对象
//html5history:封装了go、push等方法,transitionTo代码在History里
export class HTML5History extends History {
go (n: number) {
window.history.go(n)
}
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
const { current: fromRoute } = this
this.transitionTo(location, route => {
//内部调用history.pushState({ key: setStateKey(genStateKey()) }, '', url)
pushState(cleanPath(this.base + route.fullPath))
handleScroll(this.router, route, fromRoute, false)
onComplete && onComplete(route)
}, onAbort)
}
replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
const { current: fromRoute } = this
this.transitionTo(location, route => {
//内部调用history.replaceState(stateCopy, '', url)
replaceState(cleanPath(this.base + route.fullPath))
handleScroll(this.router, route, fromRoute, false)
onComplete && onComplete(route)
}, onAbort)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 3、react-router
# 1、安装
React Router已被拆分成三个包:react-router,react-router-dom和react-router-native
- react-router包提供核心的路由组件与函数,不建议直接安装使用
- react-router-dom和react-router-native提供运行环境(浏览器与react-native)所需的特定组件,但是他们都暴露出react-router中暴露的对象与方法(底层安装了react-router库)
# 2、使用和原理
不像 Vue 那样,将 router 的一些实例方法挂载到全局,组件可以通过 this.$router,this.$route 访问到,react-router 为组件注入的方法,使用函数式编程。
# 1、react-router使用
import React from 'react';
import ReactDOM from 'react-dom';
import App from "./App";
import Home from "./views/Home";
import Home1 from "./views/Home1";
import Home2 from "./views/Home2";
import About from "./views/About";
//路由配置
import {Router, Route} from 'react-router';
import {createBrowserHistory} from 'history';
const histroy = createBrowserHistory();
//访问: http://localHost:3000/home,展示Home组件
ReactDOM.render(
<Router history={history}>
<App>
<Route path="/home" component={Home}/>
<Route path="/about" component={props => {
return (
/* 子路由配置 */
<App1>
<Route path="/home1" component={Home1}/>
<Route path="/home2" component={Home2}/>
</App1>
)
}}/>
</App>
</Router>,
document.getElementById('root')
)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 2、react-router-dom使用
- Link:封装的专属a标签,用于路由跳转,类似于router-link
- to:字符串或者位置对象(包含路径名,search,hash和state的组合)
- Switch:匹配多个路由选择其一组件,从前往后,匹配成功,将route克隆返回
- Route:路由组件,类似于router-view
- path:字符串,描述路由匹配的路径名类型
- component:一个React组件。当带有component 参数的的路由匹配时,路由将返回一个新元素,其类型是一个React component (使用React.createElement创建)
- render:一个返回React元素的函数,它将在path匹配时被调用。这与component类似,应用于内联渲染和更多参数传递
- children:一个返回React元素的函数,无论路由的路径是否与当前位置相匹配,都将始终被渲染
- exact属性:指定 path 是否需要绝对匹配才会跳转
- BrowserRouter:histroy路由组件
- HashRouter:hash路由组件
- withRouter:高阶函数,使用后可使用注入的context对象上的属性
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import Home from './views/Home'
import About from './views/About'
const NoMatch = () => <>404</>
import {Route, BrowserRouter, Switch} from 'react-router-dom'
//switch和route组件可分散在任何组件中,下例子为根组件使用
const extraProps = { color: 'red' }
ReactDOM.render(
<BrowserRouter>
<App>
<Switch>
<Route path="/home" component={Home}/>
<Route path="/about" component={About}/>
<Route path='/page' render={(props) => (
<Page {...props} data={extraProps}/>
)}/>
{/* props.match指通过路径匹配获取到的参数信息,包含如下信息:
1. params – (object)从对应于路径的动态段的URL解析的键/值对
2. isExact – (boolean)true如果整个URL匹配(没有尾随字符)
3. path – (string)用于匹配的路径模式,作用于构建嵌套的
4. url – (string)URL的匹配部分,作用于构建嵌套的
*/}
<Route path='/page' children={(props) => (
props.match
? <Page {...props}/>
: <EmptyPage {...props}/>
)}/>
<Route path="*" component={NoMatch}/>
</Switch>
</App>
</BrowserRouter>,
document.getElementById('root')
)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 1、switch代码分析
代码在react-router/modules/Switch.js中
/**
* The public API for rendering the first <Route> that matches.
*/
class Switch extends React.Component {
render() {
return (
{/* 使用RouterContext.Consumer包裹,可使用注入的context */}
<RouterContext.Consumer>
{context => {
invariant(context, "You should not use <Switch> outside a <Router>");
const location = this.props.location || context.location;
let element, match;
// React.Children.forEach为react遍历this.props.children的方法
// switch子元素的遍历,即Route组件遍历
React.Children.forEach(this.props.children, child => {
if (match == null && React.isValidElement(child)) {
element = child;
const path = child.props.path || child.props.from;
match = path
? matchPath(location.pathname, { ...child.props, path })
: context.match;
}
});
return match //匹配到则返回,保证唯一,克隆的新元素,注入了location等对象参数
? React.cloneElement(element, { location, computedMatch: match })
: null;
}}
</RouterContext.Consumer>
);
}
}
//...
export default Switch;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 2、Route代码分析
组件通过Route组件包装后,添加了location、history等对象参数props
调试技巧:在chrome中,source栏目中使用ctrl+p查找文件Home.js,在函数中添加断点,可查看props添加如下对象:history、location、match、staticContext,可直接使用。
export default function Home({location, history}){
return (
<div onClick={() => history.push({
pathname: '/player',
state: {age: 18}
})}>
Home
</div>
)
}
2
3
4
5
6
7
8
9
10
原理解析:通过Routers组件通过context注入使用对象,Route组件中通过props注入创建组件
//react-router/modules/RouterContext.js
//react-router/modules/createNameContext.js
import createContext from "mini-create-react-context";
const createNamedContext = name => {
const context = createContext();
context.displayName = name;
return context;
};
export default createNamedContext;
//react-router/modules/Routers.js
import HistoryContext from "./HistoryContext.js";
import RouterContext from "./RouterContext.js";
class Router extends React.Component {
//通过Provider注入history、location、match、staticContext对象
render() {
return (
<RouterContext.Provider
value={{
history: this.props.history,
location: this.state.location,
match: Router.computeRootMatch(this.state.location.pathname),
staticContext: this.props.staticContext
}}
>
<HistoryContext.Provider
children={this.props.children || null}
value={this.props.history}
/>
</RouterContext.Provider>
);
}
}
//react-router/modules/Route.js
//The public API for matching a single path and rendering.
class Route extends React.Component {
render() {
return (
<RouterContext.Consumer>
{context => {
invariant(context, "You should not use <Route> outside a <Router>");
const location = this.props.location || context.location;
const match = this.props.computedMatch
? this.props.computedMatch // <Switch> already computed the match for us
: this.props.path
? matchPath(location.pathname, this.props)
: context.match;
const props = { ...context, location, match };
let { children, component, render } = this.props;
// Preact uses an empty array as children by
// default, so use null if that's the case.
if (Array.isArray(children) && isEmptyChildren(children)) {
children = null;
}
//解析顺序:children、component、render
return (
<RouterContext.Provider value={props}>
{props.match
? children
? typeof children === "function"
? __DEV__
? evalChildrenDev(children, props, this.props.path)
: children(props) //将props以入参形式注入
: children
: component
? React.createElement(component, props)
: render
? render(props)
: null
: typeof children === "function"
? __DEV__
? evalChildrenDev(children, props, this.props.path)
: children(props)
: null}
</RouterContext.Provider>
);
}}
</RouterContext.Consumer>
);
}
}
export default Route;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# 3、withRouter原理及使用
想让业务组件即非Route组件包裹的组件使用context对象上的内容,可使用withRouter高阶函数包裹
//使用通用语法实现
import {__RouterContext} from 'react-router'
function App(){
return (
<__RouterContext.Consumer>
{
/* 此时props已包含context传递的属性 */
props => {
return <div className = "App">App</div>
}
}
</__RouterContext.Consumer>
)
}
//通常使用高阶组件实现
import {__RouterContext} from 'react-router'
function App({children}){
return (
<div className = "App">App</div>
)
}
function Ctx(Component){
return props => <__RouterContext.Consumer>
{
/* context为router注入的对象 */
context => <Component {...context} {...props}/>
}
</__RouterContext.Consumer>
}
export default ctx(App);
//使用默认withRouter方法,使用原context方法
import {withRouter} from 'react-router'
function App(){
return (
<div className = "App">
App
</div>
)
}
export default withRouter(App);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 3、路由守卫
react-router-dom使用Prompt和getUserConfirmation函数实现路由守卫
//Home组件
import React from 'react'
import {Prompt} from 'react-router'
export default function Home({location, history}){
const [message, setMessage] = useState("")
return (
<>
<div onClick={()=> history.push({
pathname: '/about',
state: {state: 12}
})}>
Home
{/* 确定继续跳转路由,取消不跳转 */}
<Prompt message={'是否跳转'} when={!!message}/>
</div>
<span onClick={()=> setMessage(Math.random())}>修改了{message}</span>
</>
)
}
//App.js组件
//使用子定义函数处理实现路由守卫,定义后,不会弹alert框
//getUserConfirmation函数使用在history.js中
const getUserConfirmation = (msg, callback) => {
console.log(msg) //Home中Prompt的message值
callback(true); //跳转路由
}
ReactDOM.render(
<BrowserRouter getUserConfirmation={getUserConfirmation}>
<App>
<Switch>
<Route path="/home" component={Home}/>
<Route path="/about" component={About}/>
<Route path="*" component={NoMatch}/>
</Switch>
</App>
</BrowserRouter>,
document.getElementById('root')
)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# 4、v6版本
v6相关组件和属性:BrowserRouter, HashRouter, Link, MemoryRouter, NavLink, Navigate, Outlet, Route, Router, Routes, UNSAFE_LocationContext, UNSAFE_NavigationContext, UNSAFE_RouteContext, createRoutesFromChildren, createSearchParams, generatePath, matchPath, matchRoutes, renderMatches, resolvePath, unstable_HistoryRouter, useHref, useInRouterContext, useLinkClickHandler, useLocation, useMatch, useNavigate, useNavigationType, useOutlet, useOutletContext, useParams, useResolvedPath, useRoutes, useSearchParams
- 废弃
Switch
组件,由Routes
代替(使用了智能匹配路径算法) - 废弃
Redirect
组件,由Navigate
代替 - 废弃
useHistory
方法,由useNavigate
代替 Route
组件移除原有component
及render
属性,统一通过element
属性传递:<Route element={<Home />}>
Route
组件支持嵌套写法(v3 版本用法回归)Route
组件的path
规则变更,不再支持类正则写法- 消除了
v5
版本中带后斜杠的路径时,Link
组件的跳转模糊的问题 Link
组件支持自动携带当前父路由,以及相对路径写法../home
- 新增
useRoutes
方法,代替之前的react-router-config
写法,同样支持嵌套 - 其他一些 API 名称变更