Vue Router
路由的两种方式
- Hash:
location.hash = 'about'
,形如file:///Users/.../202303311041.html#foo
- HTML5 History API:
history.pushState({}, '', "about")
(可返回)history.replaceState({}, '', about)
;不可返回history.go(num)
1.The simplest router
(hashchange + <component :is>
)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<a href="#foo">foo</a>
<a href="#bar">bar</a>
<component :is="cmp"></component>
</div>
<script>
const app = new Vue({
el: "#app",
components: {
Foo: {
template: "<div>foo</div>"
},
Bar: {
template: "<div>bar</div>"
}
},
data() {
return {
cmp: "bar"
};
}
});
window.addEventListener('hashchange', () => {
// app.cmp = window.location.hash === "#foo" ? "foo" : "bar";
app.cmp = window.location.hash.slice(1);
})
</script>
</body>
</html>
2.Extracting a route table
(hashchange + <component :is>
)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<a href="#foo">foo</a>
<a href="#bar">bar</a>
<a href="#fire">fire</a>
<component :is="curCmp"></component>
</div>
<script>
const Foo = { template: `<div>foo</div>` };
const Bar = { template: `<div>bar</div>` };
const Fire = { template: `<div>fire</div>` };
const NotFound = { template: `<div>not found</div>` };
const routeTable = {
foo: Foo,
bar: Bar,
fire: Fire
};
const app = new Vue({
el: "#app",
components: {
Foo, Bar, Fire, NotFound
},
data() {
return {
cmp: "bar"
}
},
computed: {
curCmp() {
return routeTable[this.cmp] || NotFound;
}
}
});
window.addEventListener('hashchange', () => {
// app.cmp = window.location.hash === "#foo" ? "foo" : "bar";
app.cmp = window.location.hash.slice(1);
})
</script>
</body>
</html>
(hashchange + render)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<!-- <a href="#foo">foo</a>
<a href="#bar">bar</a>
<a href="#fire">fire</a>
<component :is="curCmp"></component> -->
</div>
<script>
const Foo = { template: `<div>foo</div>` };
const Bar = { template: `<div>bar</div>` };
const Fire = { template: `<div>fire</div>` };
const NotFound = { template: `<div>not found</div>` };
const routeTable = {
foo: Foo,
bar: Bar,
fire: Fire
};
const app = new Vue({
el: "#app",
components: {
Foo, Bar, Fire, NotFound
},
data() {
return {
cmp: "bar"
}
},
render(h) {
return h('div', [
h('a', { attrs: {href: "#foo"} }, "foo"),
h('a', { attrs: {href: "#bar"} }, "bar"),
h('a', { attrs: {href: "#fire"} }, "fire"),
h(routeTable[this.cmp] || NotFound)
]);
}
});
window.addEventListener('hashchange', () => {
// app.cmp = window.location.hash === "#foo" ? "foo" : "bar";
app.cmp = window.location.hash.slice(1);
})
</script>
</body>
</html>
3.URL matching
(with ‘path-to-regexp’)
前面的例子仅仅是在顶级页面切换,情境简单。
路由形参:’/user/:username’
路由实参:’/user/123’
路由解析:{ username: ‘123’ }
第三方库path-to-regexp的使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="../JS/path-to-regexp.js"></script>
</head>
<body>
<div id="app">
</div>
<script>
/**
* 路由形参:'/user/:username'
* 路由实参:'/user/123'
* 路由解析:{ username: '123' }
**/
const keys = [];
const regex = pathToRegexp("/foo/:username", keys);
console.log(keys);
/**
* [
{
"name": "username",
"prefix": "/",
"delimiter": "/",
"optional": false,
"repeat": false,
"partial": false,
"pattern": "[^\\/]+?"
}
]
keys[0].name
**/
const result = regex.exec('/foo/123');
console.log(result); //["/foo/123","123"]
//result[0 + 1]
//路由解析:
// {
// keys[0].name: result[0 + 1],
// keys[1].name: result[1 + 1],
// ...
// keys[n].name: result[n + 1]
// }
const res1 = regex.exec('/bar/123');
console.log(res1); //null
const res2 = regex.exec('foo/123'); //缺少 '/',匹配不到
console.log(res2); //null
</script>
</body>
</html>
实现一个简单的router
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="../JS/path-to-regexp.js"></script>
</head>
<body>
<div id="app">
</div>
<script>
const Foo = {
props: ['id'],
template: `<div>foo with id: {{id}}</div>`
};
const Bar = { template: `<div>bar</div>` };
const NotFound = { template: `<div>not found</div>` };
const routeTable = {
'/foo/:id': Foo,
'/bar': Bar
};
//打表(预编译)
const compiledRoutes = []
Object.keys(routeTable).forEach(key => {
const dynamicSegments = []
const regex = pathToRegexp(key, dynamicSegments)
const component = routeTable[key]
compiledRoutes.push({
component,
regex,
dynamicSegments
})
})
//查表
console.log(compiledRoutes.length); //2
compiledRoutes.forEach(value => console.log(value));
/**
*
* compiledRoutes[0]:
*
* component: {
"props": [
"id"
],
"template": "<div>foo with id: {{id}}</div>"
}
dynamicSegments: [{
"name": "id",
"prefix": "/",
"delimiter": "/",
"optional": false,
"repeat": false,
"partial": false,
"pattern": "[^\\/]+?"
}],
regex: /^\/foo\/((?:[^\/]+?))(?:\/(?=$))?$/i
**/
/**
*
* compiledRoutes[1]:
*
* component: {
"template": "<div>bar</div>"
}
dynamicSegments: [],
regex: /^\/bar(?:\/(?=$))?$/i
**/
window.addEventListener('hashchange', () => {
app.url = window.location.hash.slice(1);
});
const app = new Vue({
el: "#app",
data() {
return {
// url: "bar"
url: window.location.hash.slice(1)
}
},
render(h) {
const path = '/' + this.url;//为了能匹配到
let componentToRender;
let props = {};
//solution
compiledRoutes.some(route => {
const match = route.regex.exec(path)
componentToRender = NotFound
if (match) {
componentToRender = route.component
route.dynamicSegments.forEach((segment, index) => {
props[segment.name] = match[index + 1]
/**
* props["id"] = match[1] = "456"
**/
})
return true
}
})
/**
*
**/
return h('div', [
h(componentToRender, { props }),
h('a', { attrs: { href: '#foo/123' } }, 'foo 123'),
' | ',
h('a', { attrs: { href: '#foo/456' } }, 'foo 456'),
' | ',
h('a', { attrs: { href: '#bar' } }, 'bar'),
' | ',
h('a', { attrs: { href: '#garbage' }}, 'garbage')
])
}
})
</script>
</body>
</html>