vue-router有两种工作模式,一种是hash
,一种是history
,下面我会分别介绍这两种模式的工作原理。
hash路由模式的原理
首先你要了解hash的特点:
- hash值是
url
中#
后面的值,可以通过location.hash
来直接修改值。 - hash值不会被包括在HTTP请求中,例:现有url ->
http:gitee.com/lw #1
,刷新浏览器时,其中http:gitee.com/lw
会作为请求路径发送请求。这也就是为什么hash模式的项目部署后不会出现刷新页面后404的问题。 - 每一次hash值的改变,都会在浏览器的访问历史中增加一条记录。
- 改变hash值可以触发
hashchange
事件。
底层是如何来通过路由来更新页面的:
下面只是大概的意思,想要深入了解可以去看源码。
现有路由配置:
const routes=[
{
path: '/login',
name: 'login',
component: () => import( '../views/login')
},
{
path: '/register',
name: 'register',
component: () => import( '../views/register')
},
{
path: '/updatepsd',
name: 'updatepsd',
component: () => import( '../views/updatepsd')
},
{
path: '/updateinfo',
name: 'updateinfo',
component: () => import( '../views/updateinfo')
},
]
底层解析路由配置
const pages={}
for(let p of routes){
pages[p.path]=p.component
}
底层通过监听hash值的变化来渲染对应页面
// 监听hash变化,根据hash渲染对应组件。这里的render是渲染组件的方法
window.onhashchange=()=>{
const path=location.hash.split('#')[1]
render(pages[path])
}
底层跳转路由的方法
function push(url){
window.location.hash = url
}
// hash值的每一次变化都会在浏览器产生历史记录,因此我们也可以切换历史记录来改变hash值
function go(n){
history.go(n)
}
history路由模式的原理
首先你要了解history的特点:
- 可以通过
history
上面的这些方法:back()、forward()、go()
和pushState()、replaceState()
来读取历史记录并完成跳转。 - 修改浏览器历史记录后会触发
popstate
事件。 - url的变化不会立即发送请求,这就暂时满足单页面应用更新视图但不重新请求页面的需求,但是当我们刷新浏览器时,浏览器就会去发送请求,这也就造成了history模式的项目部署后,刷新页面时会出现404的问题,但是你知道为什么在开发时没有这样的问题吗?此时vue是这样做的,在
dev
环境下,webpack
开启了一个内置服务器,当我们刷新浏览器时,会向这个内置服务器发送请求来获取当前路径对应的组件资源,因为在dev
模式下发送请求优先会走这个内置的服务器,而内置服务器中有所有配置过的前端路由路径对应的接口,而在build
环境下并没有这个内置服务器,使用的是我们部署的服务器,这就是为什么build
环境下,使用history模式会出现404
问题。
底层是如何来通过路由来更新页面的:
下面只是大概的意思,想要深入了解可以去看源码。
现有路由配置:
const routes=[
{
path: '/login',
name: 'login',
component: () => import( '../views/login')
},
{
path: '/register',
name: 'register',
component: () => import( '../views/register')
},
{
path: '/updatepsd',
name: 'updatepsd',
component: () => import( '../views/updatepsd')
},
{
path: '/updateinfo',
name: 'updateinfo',
component: () => import( '../views/updateinfo')
},
]
底层解析路由配置
const pages={}
for(let p of routes){
pages[p.path]=p.component
}
底层通过监听‘history的变化来渲染对应页面
// popstate只能监听到 back、forward、go方法引起的url变化。render是渲染组件的方法
window.onpopstate=()=>{
render(pages[location.pathname])
}
底层跳转路由的方法
function push(url){
history.pushState({info:'xxx'},'url',url)
render(pages[location.pathname])
}
function replace(url){
history.replaceState({info:'xxx'},'url',url)
render(pages[location.pathname])
}
function go(n){
history.go(n)
}
证明dev下发送的请求会优先走内置服务器
说一下配置代理服务器加/api
前缀的作用吧,可以控制请求是否优先走代理服务器,还可以配置多台代理服务器。
现在以以下示例来证明:
现有以这种方式在project/vue.config.js
中配置代理服务器
module.exports={
devServer:{
proxy:'http://localhost:5000'
}
}
5000端口服务器有文件夹public
,该目录下有文件a.html
,内容如下:
<h1>5000 server/a.html</h1>
还有文件b.html
,内容如下:
<h1>5000 server/b.html</h1>
并在5000端口服务器开放了public
目录的静态资源。开放了public的静态资源可以理解为:为public目录下的所有文件配置了get
请求的接口,请求路径为文件名。
server.use(express.static(path.join(__dirname,'public')))
vue项目下有文件夹public
,对vue或者react
比较了解的小伙伴应该知道该文件夹下的静态资源是可以直接使用的,而其他目录下的静态资源需要以模块的方式引入才能使用。知道为什么吗?因为dev
开启的内置服务器做了一件事情,对,和上面5000端口服务器一样,开放了public
目录下的静态资源。然后回归正题,该目录下也有文件a.html
,内容如下:
<h1>project/a.html</h1>
为了验证请求优先走内置服务器,我们在组件内发送一下请求便可以知道
mounted(){
axios({
url:'/a.html',
})
.then((res)=>{console.log(res.data)})
.catch((err)=>{console.log(err)})
}
axios({
url:'/b.html',
})
.then((res)=>{console.log(res.data)})
.catch((err)=>{console.log(err)})
}
}
发送请求后,打印结果为:
<h1>project/a.html</h1>
<h1>server/b.html</h1>
为什么说配置代理服务器加api
前缀可以控制请求优先走代理服务器呢,因为我们知道项目的public
目录下没有api
的文件夹或文件,如果有,我们可以换一个前缀。
在dev下,webpack如何根据前端路由创建接口
现有路由配置:
const routes=[
{
path: '/login',
name: 'login',
component: () => import( '../views/login')
},
{
path: '/register',
name: 'register',
component: () => import( '../views/register')
},
{
path: '/updatepsd',
name: 'updatepsd',
component: () => import( '../views/updatepsd')
},
{
path: '/updateinfo',
name: 'updateinfo',
component: () => import( '../views/updateinfo')
},
]
在内置服务器内,根据该配置来生成接口
const express=require("express")
const server=express()
for(let p of routes){
server.get(p.path,(req,res)=>{
res.send(parseToHtml(p.component))
})
}
部署在express服务器中的项目如何解决刷新404的问题
安装三方中间件connect-history-api-fallback
yarn add connect-history-api-fallback
在服务器中使用中间件。
server.use(require('connect-history-api-fallback')())
中间件原理:把所有非ajax
的请求过滤掉。就是当我们发送非ajax
请求时,相当于没有做这么一件事情。什么是非ajax
请求呢,对ajax
不是很了解的小伙伴可能不知道,就是以页面刷新的方式发送的请求。因为ajax是一种在页面不刷新的情况下获取服务器响应资源的技术。
使用时的注意事项:
1.中间件的使用要放在部署应用资源的前面。如果放在后面就无法过滤掉项目刷新时的请求了。
server.use(require('connect-history-api-fallback')())
server.use(express.static(require("path").join(__dirname,'build')))
2.用于非ajax请求的接口应该放在该中间件的上面。因为放在该中间件下面会导致接口无效。
server.get('/page1',(req,res)=>{
requier('fs').readFile('/views/page1.html',(err,data)=>{
res.end(data)
})
})
server.use(require('connect-history-api-fallback')())