文章内容输出来源:拉勾教育前端高薪训练营
history路由模式配置
- history需要服务器支持
- 单页应用中,服务端不存在http://www.testurl.com/login这样的的地址,会返回找不到该页面
- 在服务端应该除了静态资源外都返回单页应用的index.html
Node.js处理history模式
const path = require('path')
// 导入处理history模式的模块
const history = require('connect-history-api-fallback')
// 导入express
const express = require('express')
const app = express()
// 注册处理history模式中间件
app.use(history())
// 处理静态资源的中间件,网站根目录 ../web
app.use(express.static(path.join(__dirname, '../web')))
// 开启服务器
app.listen(3000, () => {
console.log('服务器开启,localhost:3000')
})
nginx处理history模式
nginx目录不能有中文字符
nginx.conf的location中添加配置
try_files $uri $uri/ /index.html
启动nginx
# 启动
$ start nginx
# 重启
$ nginx -s reload
# 停止
$ nginx -s stop
vue-router
vue-router中Hash模式工作原理
- url中#后面的内容作为路径地址
- 监听hashchange事件,记录当前路由地址
- 根据当前路由地址找到对应组件重新渲染
vue-router中History模式工作原理
- 通过history.pushState()方法改变地址栏
- 监听popstate事件,记录改变后的地址(调用pushState和replaceState不会触发该事件,点击浏览器前进后退或history的back和forword时触发)
- 根据当前路由地址找到对应组件重新渲染
模拟开发一个vue-router类
params参数
- options:路由规则
- mode:路由模式
- data:响应式对象,记录当前路由的信息
- routeMap:记录路由地址和组件的对应关系
方法
- Constructor(options): VueRouter
- _install(Vue): void
- setRouterData(): Vue.observable 根据路由模式返回对应的,在window.location中找到相应的参数设置current值
- init(): void 初始化方法
- initEvent(): void 注册popState事件,监听浏览器历史变化
- initRouteMap(): void 初始化routeMap
- initComponents(Vue): void 创建router-link和router-view
install静态方法
- 判断当前插件是够已经被安装
- 把Vue构造函数记录到全局变量
- 把创建Vue实例时候传入的router对象注入到Vue实例上
let _Vue = null
export default class VueRouter {
static install (Vue) {
// 判断当前插件是够已经被安装
if (VueRouter.install.installed) return
VueRouter.install.installed = true
// 把Vue构造函数记录到全局变量
_Vue = Vue
// 把创建Vue实例时候传入的router对象注入到Vue实例上,注入的时候要用混入,直接等的时候this是指向VueRouter类
_Vue.mixin({
beforeCreate () {
// 判断this.$options.router,组件没有此属性
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router
}
},
})
}
}
构造函数
let _Vue = null
export default class VueRouter {
static install (Vue) {
// 判断当前插件是够已经被安装
if (VueRouter.install.installed) return
VueRouter.install.installed = true
// 把Vue构造函数记录到全局变量
_Vue = Vue
// 把创建Vue实例时候传入的router对象注入到Vue实例上
_Vue.mixin({
beforeCreate () {
// 判断this.$options.router,组件没有此属性
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router
}
},
})
}
constructor (options) {
this.options = options
this.mode = options.mode
// 判断mode的值是否正确
if (this.mode !== 'history' && this.mode !== 'hash') throw new Error('vueRouter mode must be hash or history')
this.routeMap = {}
// 根据mode的值设置对应的current参数
this.data = this.setRouterData(this.mode)
}
setRouterData (mode) {
const { pathname, hash } = window.location
return _Vue.observable({
current: mode === 'history' ? pathname : hash.substr(1)
})
}
}
initRouteMap
遍历所有的路由规则,把路由规则解析成键值对的形式存储到routeMap中
let _Vue = null
export default class VueRouter {
static install (Vue) {
// 判断当前插件是够已经被安装
if (VueRouter.install.installed) return
VueRouter.install.installed = true
// 把Vue构造函数记录到全局变量
_Vue = Vue
// 把创建Vue实例时候传入的router对象注入到Vue实例上
_Vue.mixin({
beforeCreate () {
// 判断this.$options.router,组件没有此属性
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router
}
},
})
}
constructor (options) {
this.options = options
this.mode = options.mode
// 判断mode的值是否正确
if (this.mode !== 'history' && this.mode !== 'hash') throw new Error('vueRouter mode must be hash or history')
this.routeMap = {}
// 根据mode的值设置对应的current参数
this.data = this.setRouterData(this.mode)
}
setRouterData (mode) {
const { pathname, hash } = window.location
return _Vue.observable({
current: mode === 'history' ? pathname : hash.substr(1)
})
}
initRouteMap () {
// 遍历所有的路由规则,把路由规则解析成键值对的形式存储到routeMap中
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component
})
}
}
initComponents
生成router-link和router-view
先说明下vue的构建版本
- 运行时版:不支持template模板,需要打包的时候提前编译
- 完整版:包含运行时和编译器,体积比运行时版大10kb左右,程序运行的时候把模板转换成render函数
let _Vue = null
export default class VueRouter {
static install (Vue) {
// 判断当前插件是够已经被安装
if (VueRouter.install.installed) return
VueRouter.install.installed = true
// 把Vue构造函数记录到全局变量
_Vue = Vue
// 把创建Vue实例时候传入的router对象注入到Vue实例上
_Vue.mixin({
beforeCreate () {
// 判断this.$options.router,组件没有此属性
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router
this.$options.router.init(_Vue)
}
},
})
}
constructor (options) {
this.options = options
this.mode = options.mode
// 判断mode的值是否正确
if (this.mode !== 'history' && this.mode !== 'hash') throw new Error('vueRouter mode must be hash or history')
this.routeMap = {}
// 根据mode的值设置对应的current参数
this.data = this.setRouterData(this.mode)
}
setRouterData (mode) {
const { pathname, hash } = window.location
return _Vue.observable({
current: mode === 'history' ? pathname : hash.substr(1)
})
}
init (Vue) {
this.initRouteMap()
this.initComponents()
}
initRouteMap () {
// 遍历所有的路由规则,把路由规则解析成键值对的形式存储到routeMap中
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component
})
}
initComponents (Vue) {
const self = this
const { mode } = self
Vue.component('router-link', {
props: {
to: String,
},
// 此处用template时,如果Vue时运行时版将会报错,因为运行时版没有编译器,无法编译模板
// template: '<a :href="to"><slot></slot></a>'
// 使用render函数代替template
render (h) {
return h('a', {
attrs: {
href: mode === 'history' ? this.to : `#${this.to}`,
},
on: {
click: this.handleClick,
},
}, [this.$slots.default])
},
methods: {
handleClick (e) {
if (mode === 'history') {
history.pushState({}, '', this.to)
e.preventDefault()
} else {
window.location.hash = `#${this.to}`
}
this.$router.data.current = this.to
},
},
})
const self = this
Vue.component('router-view', {
render (h) {
const component = self.routeMap[self.data.current]
return h(component)
},
})
}
}
解决Vue.component时template无法编译
- 在vue.config.js中配置runtimeCompiler: true,就可以在 Vue 组件中使用 template 选项了,但是这会让你的应用额外增加 10kb 左右
- 使用render函数代替template
render (h) {
return h('a', {
attrs: {
href: mode === 'history' ? this.to : `#${this.to}`,
},
on: {
click: this.handleClick,
},
}, [this.$slots.default])
}
initEvent实现,代码模拟完成
let _Vue = null
export default class VueRouter {
static install (Vue) {
// 判断当前插件是够已经被安装
if (VueRouter.install.installed) return
VueRouter.install.installed = true
// 把Vue构造函数记录到全局变量
_Vue = Vue
// 把创建Vue实例时候传入的router对象注入到Vue实例上
_Vue.mixin({
beforeCreate () {
// 判断this.$options.router,组件没有此属性
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router
this.$options.router.init(_Vue)
}
},
})
}
constructor (options) {
this.options = options
this.mode = options.mode
// 判断mode的值是否正确
if (this.mode !== 'history' && this.mode !== 'hash') throw new Error('vueRouter mode must be hash or history')
this.routeMap = {}
// 根据mode的值设置对应的current参数
this.data = this.setRouterData(this.mode)
}
setRouterData (mode) {
const { pathname, hash } = window.location
return _Vue.observable({
current: mode === 'history' ? pathname : hash.substr(1)
})
}
init (Vue) {
this.initRouteMap()
this.initComponents(Vue)
this.initEvent()
}
initRouteMap () {
// 遍历所有的路由规则,把路由规则解析成键值对的形式存储到routeMap中
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component
})
}
initComponents (Vue) {
const self = this
const { mode } = self
Vue.component('router-link', {
props: {
to: String,
},
// 此处用template时,如果Vue时运行时版将会报错,因为运行时版没有编译器,无法编译模板
// template: '<a :href="to"><slot></slot></a>'
// 使用render函数代替template
render (h) {
return h('a', {
attrs: {
href: mode === 'history' ? this.to : `#${this.to}`,
},
on: {
click: this.handleClick,
},
}, [this.$slots.default])
},
methods: {
handleClick (e) {
if (mode === 'history') {
history.pushState({}, '', this.to)
e.preventDefault()
} else {
window.location.hash = `#${this.to}`
}
this.$router.data.current = this.to
},
},
})
Vue.component('router-view', {
render (h) {
const component = self.routeMap[self.data.current]
return h(component)
},
})
}
initEvent () {
const { mode } = this
const eventName = mode === 'history' ? 'popstate' : 'hashchange'
window.addEventListener(eventName, () => {
const { pathname, hash } = window.location
const currentPath = mode === 'history' ? pathname : hash.substr(1)
this.data.current = currentPath
})
}
}