主题列表:juejin, github, smartblue, cyanosis, channing-cyan, fancy, hydrogen, condensed-night-purple, greenwillow, v-green, vue-pro, healer-readable, mk-cute, jzman, geek-black, awesome-green, qklhk-chocolate
贡献主题:https://github.com/xitu/juejin-markdown-themes
theme: jzman highlight:
前言
vue原理学习系列,本期为主要学习下前端路由,以及实现简单的vue哈希路由和动态路由。
vue原理学习系列往期文章:
- 系列(一):响应性
- 系列(二):插件plugin
- 系列(三):渲染函数(render funciton)和Virtual Dom
- 系列(四):状态管理
⚡「内容速览」⚡
- 前端router介绍
- 路由的实现原理
- 哈希路由
- 路由表
- 动态路由
前端router介绍
随着前端应用的业务需求和功能变得愈发复杂,单页应用(SPA)已经成为前端应用的一种主流模式,SPA就是通过前端路由来实现的。
前端路由最核心的特点在于:通过改变URL,在不重新请求页面的情况下,对视图进行更新。
因此实现前端路由我们需要考虑两个问题:
-
URL改变不引起页面刷新
-
对URL变化的监听
前端路由实现的2种主要方式:
-
hash 实现:
利用URL中hash(#)
-
history 实现:
利用History interface在HTML5中新增的方法
路由的实现原理
关于hash模式的路由和history模式的路由,vue:路由实现原理这篇文章可以说讲诉得十分详细。
我们对文章总结一下:
-
hash实现前端路由的原理
-
hash值是URL中#及后面连接它的一段字符串,通过
window.location.hash
获取。 -
hash用来指导浏览器动作,虽然出现在URL中,但不会被包括在http请求中,对服务器端来说没有作用的,因此hash的改变不会重新加载页面。
-
window对象可以监听hash改变时触发的
haschange
事件。 -
每一次改变window.localtion.hash,都会在浏览器访问历史中增加一个记录。
-
-
history实现前端路由的原理
-
HTML5的History interface提供了2个新的方法:pushState(),replaceState(),通过这两个api可以让我们对浏览器历史记录栈进行修改。
-
window.history.pushState(state, title, url)
-
window.history,replaceState(state, title, url)
-
state:与pushState()创建的新历史记录条目相关联;title:新历史记录的标题;url:新历史记录的url。
-
每当用户导航到新状态时,都会触发popstate事件。
-
pushState和replaceState共同的特点时当调用它们修改浏览器历史栈后,虽然当前url改变了,但浏览器不会刷新页面。
-
利用hash、history的特点,我们就可以实现前端路由的功能。
哈希路由
接下来我们模拟一个最简单的hash路由实现:
这里面关键点在于:
- 将当前URL的hash值在app(vue实例)中保存为
响应性状态
,让app响应hash值的变化 - 监听hash变化事件,改变app的hash值.
- 当app哈希值发生改变时,渲染不同的组件.
<div id="app">
<component :is="url"></component>
<a @click="routeTo('#foo')" href="#foo">foo</a>
<a @click="routeTo('#bar')" href="#bar">bar</a>
</div>
<script>
window.addEventListener('hashchange', () => {
app.url = window.location.hash.slice(1)
})
const app = new Vue({
el: '#app',
data: {
url: window.location.hash.slice(1)
},
components: {
foo: { template: `<div>foo</div>` },
bar: { template: `<div>bar</div>` },
},
methods: {
routeTo(route) {
// 改变hash值
window.location.hash = route
}
}
})
</script>
路由表
上一个小节中不同路由对于组件配置我们时硬编码写在实例中,而通常情况下我们需要一个可配置路由
,通过一个路由表来知道那个路径对应那个组件,因此这节我们在此前基础之上,抽取编写一个路由表
。
<div id="app">
<component :is="matchedComponent"></component>
<a @click="routeTo('#foo')" href="#foo">foo</a>
<a @click="routeTo('#bar')" href="#bar">bar</a>
</div>
<script>
const Foo = { template: `<div>foo</div>` }
const Bar = { template: `<div>bar</div>` }
const NotFound = { template: `<div>not found!</div>` }
// 路由表
const routeTable = {
foo: Foo,
bar: Bar
}
window.addEventListener('hashchange', () => {
app.url = window.location.hash.slice(1)
})
const app = new Vue({
el: '#app',
data: {
url: window.location.hash.slice(1)
},
computed: {
matchedComponent() {
// 返回路由表当前路径匹配的组件,如果没匹配到,则返回NotFound组件
return routeTable[this.url] || NotFound;
}
}
})
</script>
动态路由
在应用开发中,我们经常需要把某种模式匹配到的所有路由,全都映射到同个组件。
例如有一个 User 组件,对于所有ID不相同的用户,都使用这个组件来渲染(比如说把/user/xiaoshuai和/user/dashuai都映射到相同路由中),那么常用解决方式是使用“动态路径参数”(dynamic segment)
。
下面我们简单实现下动态路由:
// 利用path-to-regexp库用来分析如何匹配URL中的动态参数部分
<script src="./path-to-regexp.js"></script>
<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
}
// 预编译,遍历原始路由表,通过dynamicSegments来记录路径匹配规则是否包含动态参数
// 最后返回编译过的路由表
const compiledRoutes = [];
Object.keys(routeTable).forEach(key => {
const dynamicSegments = [];
const regex = pathToRegxp(key, dynamicSegments);
const component = routeTable[key];
// 编译的路由应该包括:1.显示的组件 2. 匹配路径的正则表达式 3. 动态的参数
compiledRoutes.push({
component,
regex,
dynamicSegments
})
})
window.addEventListener('hashchange', () => {
app.url = window.location.hash.slice(1)
})
const app = new Vue({
el: '#app',
data: {
url: window.location.hash.slice(1)
},
render(h) {
const path = '/' + this.url;
let componentToRender = NotFound;
let props = {};
compiledRoutes.some(route => {
const match = route.regex.exec(path);
if (match) {
componentToRender = route.component;
// 动态参数赋值
route.dynamicSegments.forEach((segment, index) => {
props[segment] = match[index + 1];
})
return true;
}
})
return h('div', [
h(componentToRender, { props }),
h('a', { attrs: { href: '#foo/123' }}, 'foo123'),
' | ',
h('a', { attrs: { href: '#foo/234' }}, 'foo456'),
' | ',
h('a', { attrs: { href: '#bar' }}, 'bar'),
' | ',
h('a', { attrs: { href: '#garbage' }}, 'garbage'),
])
}
})
</script>
以上都是基于个人的理解,欢迎大家提出意见。
自己前端博客:前端学习笔记
参考连接:
版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 作者: zs10 原文链接:https://juejin.im/post/6925063505460609037