前端 - Vue中的AJAX、VueX、路由
AJAX
配置代理
方式一
服务器:
/* server1.js */
const express = require('express')
const app = express()
app.use((request,response,next)=>{
console.log('有人请求服务器1了');
next()
})
app.get('/students',(request,response)=>{
const students = [
{id:'001',name:'tom',age:18},
{id:'002',name:'jerry',age:19},
{id:'003',name:'tony',age:120},
]
response.send(students)
})
app.listen(9000,(err)=>{
if(!err) console.log('服务器1启动成功了,请求学生信息地址为:http://localhost:9000/students');
})
客户端MyTest.vue:
<template>
<div>
<button @click="sendAjax">发送AJAX请求</button>
</div>
</template>
<script>
import axios from 'axios'; // 引入axios
export default {
methods: {
sendAjax() {
axios.get('http://localhost:8080/students').then(
response => {
console.log('请求成功', response.data);
},
error => {
console.log('请求失败', error.message);
}
);
}
},
}
</script>
需要配置代理服务器,端口号和客户端相同:
/* vue.config.js */
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
// ...
devServer: {
proxy: 'http://localhost:9000'
}
})
如果在MyTest.vue直接请求http://localhost:9000/students,会出现跨域请求错误:
Access to XMLHttpRequest at 'http://localhost:9000/students' from origin 'http://localhost:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
点击按钮成功请求,输出以下信息:
请求成功 (3) [{…}, {…}, {…}]
方式二
方式一的两个缺点:
- 只能配置一个代理,即只能给
http://localhost:9000转发(AJAX请求) - 如果本地有同名文件,则不会走代理,例如存在一个
public/students文件,则方式一不会走代理,而是直接拿到本地的文件
sendAjax() {
axios.get("http://localhost:8080/api/students").then(
(response) => {
console.log("请求成功", response.data);
},
(error) => {
console.log("请求失败", error.message);
}
);
},
devServer: {
proxy: {
'/api': { // 匹配所有以/api开头的请求路径
target: 'http://localhost:9000', // 目标端口
ws: true, // 用于支持websocket
changeOrigin: true,
pathRewrite: {'^/api': ''}
},
'/foo': {
target: 'http://localhost:9001',
pathRewrite: {'^/foo': ''}
}
}
}
以/api为例:
- 当
changeOrigin设置为false,服务器收到的请求头中的host为localhost:8080;设置为true时则为localhost:9000,默认为ture pathRewrite的意思是将请求中的/api变为空串,即http://localhost:8080/api/students变成http://localhost:8080/students
插槽
默认插槽
<!-- App.vue -->
<div class="container">
<Category>
<a href="https://www.baidu.com">Baidu</a>
</Category>
<Category />
<Category />
</div>
<!-- Category.vue -->
<div class="category">
<h3>分类</h3>
<ul>
<li>00001</li>
<li>00002</li>
<li>00003</li>
<li>00004</li>
</ul>
<slot>默认插槽,当使用者没有传递具体结构时会出现</slot>
</div>
具名插槽
多个插槽需要起名:
<!-- App.vue -->
<div class="container">
<Category>
<a slot="baidu" href="https://www.baidu.com">Baidu</a>
<a slot="bing" href="https://cn.bing.com">Bing</a>
</Category>
<Category>
<a slot="bing" href="https://cn.bing.com">Bing</a>
</Category>
</div>
<!-- Category.vue -->
<div class="category">
<h3>分类</h3>
<ul>
<li>00001</li>
<li>00002</li>
<li>00003</li>
<li>00004</li>
</ul>
<slot name="baidu">baidu插槽,当使用者没有传递具体结构时会出现</slot>
<slot name="bing">bing插槽,当使用者没有传递具体结构时会出现</slot>
</div>
当插槽内有多个结构,但是又不希望使用<div>包裹破坏结构时,可以使用<template>标签的v-slot属性:
<!-- App.vue -->
<div class="container">
<Category>
<template v-slot:baidu>
<a href="https://www.baidu.com">Baidu</a>
<h1>baidu</h1>
</template>
<a slot="bing" href="https://cn.bing.com">Bing</a>
</Category>
</div>
简写,将v-slot:改为#:
<template #baidu>
<a href="https://www.baidu.com">Baidu</a>
<h1>baidu</h1>
</template>
作用域插槽
父级的插槽无法访问子级数据,通过下面的方法传输:
<!-- Category.vue 通过v-bind绑定数据 -->
<slot :users="users"></slot>
<!-- App.vue 接收数据 -->
<Category>
<template v-slot:default="slotProps">
<ul>
<li v-for="(u, index) of slotProps.users" :key="index">
{{u}}
</li>
</ul>
</template>
</Category>
Vuex
专门在Vue中实现集中式状态管理的一个Vue插件,对Vue应用中多个组件的共享状态进行集中式的管理,也是一种组件间通信方式。
使用场景:
- 多个组件依赖于同一状态
- 来自不同组件的行为需要变更同一状态
环境安装
使用Vue2时,需要安装Vuex 3.x,输入下面的命令
npm i vuex@3
新建src/store/index.js文件:
/* src/store/index.js 创建store */
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex); // 使用插件
const actions = {}; // 用于响应组件中的动作
const mutations = {}; // 用于操作数据(state)
const state = {}; // 用于存储数据
// 创建并暴露store
export default new Vuex.Store({
actions,
mutations,
state
});
修改main.js,引入store对象:
/* main.js */
import Vue from 'vue'
import App from './App.vue'
import store from './store';
Vue.config.productionTip = false
new Vue({
render: h => h(App),
// beforeCreate() {
// Vue.prototype.$bus = this;
// }
store, // store对象
}).$mount('#app');
举例
getters:用于加工state的数据, 实现类似计算属性的功能
/* src/store/index.js */
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex); // 使用插件
const actions = {
// 当前和是奇数再加
increaseOdd(context, value) {
if (context.state.sum % 2) {
context.commit('INCREASE', value);
}
},
// 等2s后再加
increaseWait(context, value) {
setTimeout(() => {
context.commit('INCREASE', value);
}, 2000);
},
}; // 用于响应组件中的动作
const mutations = {
INCREASE(state, value) {
state.sum += value;
},
}; // 用于操作数据(state)
const state = {
sum: 0
}; // 用于存储数据
const getters ={
bigSum(state) {
return state.sum * 10;
}
} // 用于加工state的数据, 实现类似计算属性的功能
// 创建并暴露store
export default new Vuex.Store({
actions,
mutations,
state,
getters
});
在Counter.vue中对$store进行操作:
<template>
<div>
<h1>当前和为:{{ $store.state.sum }}</h1>
<h2>当前和的10倍为:{{ $store.getters.bigSum }}</h2>
<select v-model.number="n">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="add">+</button>
<button @click="subtract">-</button>
<button @click="addOdd">sum为奇数才加</button>
<button @click="addWait">等2s再加</button>
</div>
</template>
<script>
export default {
data() {
return {
n: 1,
};
},
methods: {
add() {
/* 如果没有复杂的业务逻辑, 直接commit, 跳过action */
this.$store.commit("INCREASE", this.n);
},
subtract() {
this.$store.commit("INCREASE", -this.n);
},
addOdd() {
this.$store.dispatch("increaseOdd", this.n);
},
addWait() {
this.$store.dispatch("increaseWait", this.n);
},
},
};
</script>
<style scoped>
button {
margin-left: 5px;
}
</style>
mapState与mapGetters
使用mapState与mapGetters简化模板,防止在模板里写类似{{$store.state.sum}}等复杂的属性。
/* Count.vue */
import { mapState } from "vuex";
export default {
// data() {...},
// methods: {...},
computed: {
/* 借助mapState生成计算属性 */
// 对象写法
...mapState({ mySum: 'sum' }), // 通过rest展开对象(不能简写, 因为'sum'必须是字符串, 不能是变量)
// 更简单的数组写法(在模板中需要写{{sum}})
// ...mapState(['sum']),
/* 借助mapGetters生成计算属性 */
...mapGetters(['bigSum']),
},
};
上面的代码相当于:
/* Count.vue */
export default {
// data() {...},
// methods: {...},
computed: {
mySum() {
return this.$store.state.sum;
},
bigSum() {
return this.$store.getters.bigSum;
}
},
};
于是在模板直接使用该属性:
<h1>当前和为:{{ mySum }}</h1>
<h2>当前和的10倍为:{{ bigSum }}</h2>
mapActions与mapMutations
使用mapActions与mapMutations简化方法的书写:
import { mapState, mapGetters, mapActions, mapMutations } from "vuex";
export default {
// data() {...},
methods: {
/* 借助mapMutations生成方法 */
...mapMutations({add: 'INCREASE', subtract: 'INCREASE'}),
/* 借助mapActions生成方法 */
...mapActions({addOdd: 'increaseOdd', addWait: 'increaseWait'}),
},
// computed: {...},
};
这里要注意,由于不写参数默认传入$event,所以需要在模板中写明参数:
<button @click="add(n)">+</button>
<button @click="subtract(-n)">-</button>
<button @click="addOdd(n)">sum为奇数才加</button>
<button @click="addWait(n)">等2s再加</button>
这时,使用mapActions与mapMutations帮我们生成了方法:
export default {
// ...
methods: {
add(value) {
this.$store.commit("INCREASE", value);
},
subtract(value) {
this.$store.commit("INCREASE", value);
},
addOdd(value) {
this.$store.dispatch("increaseOdd", value);
},
addWait(value) {
this.$store.dispatch("increaseWait", value);
},
}
};
也可以使用数组写法。
Vuex模块化
目的:让代码更好维护,数据分类更明确
- 修改
store.js,注意开启命名空间
const countAbout = {
namespces: true, // 开启命名空间
state: {},
mutations: {},
actions: {},
getters: {}
}
const personAbout = {
namespces: true, // 开启命名空间
state: {},
mutations: {},
actions: {},
getters: {}
}
export default new Vuex.Store({
modules: {
countAbout,
personAbout
}
});
- 组件读取
state数据:
// 方式一:直接读
this.$store.state.personAbout.list
// 方式二:借助mapState
...mapState('countAbout', ['sum']) // 注意开启命名空间
- 组件读取
getters数据:
// 方式一:直接读
this.$store.state['personAbout/list']
// 方式二:借助mapGetters
...mapGetters('countAbout', ['bigSum']) // 注意开启命名空间
- 组件调用
dispatch:
// 方式一:直接读
this.$store.dispatch('personAbout/setList', p)
// 方式二:借助mapActions
...mapActions('countAbout', {myAdd: 'add'}) // 注意开启命名空间
- 组件调用
commit:
// 方式一:直接读
this.$store.commit('personAbout/ADD_PERSON', p)
// 方式二:借助mapMutations
...mapMutations('countAbout', {mySub: 'SUB'}) // 注意开启命名空间
路由
路由简介
Vue对应vue-router的3.x版本:
npm i vue-router@3
SPA应用
vue-router是插件,专门用来实现SPA应用:
- 单页Web应用 (Single Page Web application)
- 整个应用只有一个完整的页面
- 点击页面的导航链接不会刷新页面,只会局部更新
- 数据请求通过ajax获取
路由的理解
一条路由规则就是一组key-value的映射关系,一般key指的是请求路径,
- 前端路由:
value是组件,当浏览器路径改变时,对应的组件展示相应的页面内容 - 后端路由:
value是函数,服务器收到请求时,根据请求路径找到对应的函数来处理请求,返回相应数据
路由的基本使用
注意:
- 路由组件通常存放在
pages/文件夹,一般组件存放在components/文件夹 - 切换时,路由组件默认被销毁,需要时重新挂载
- 每个路由组件都有单独的
$route属性,里面存储着自己的路由信息 - 整个应用只有一个
$router,通过路由组件访问
下面是一个基本路由的例子,首先是两个路由组件:
<!-- src/pages/About.vue -->
<div>
<h1>About!!!!</h1>
</div>
<!-- src/pages/Home.vue -->
<div>
<h1>Home!!!!</h1>
</div>
接下来是router配置:
/* src/router/index.js */
import VueRouter from 'vue-router';
import About from '../pages/About.vue';
import Home from '../pages/Home.vue';
export default new VueRouter({
routes: [ // 两条路由规则
{
path: '/about',
component: About
},
{
path: '/home',
component: Home
}
]
});
main.js配置:
/* src/main.js */
import VueRouter from 'vue-router';
import router from './router'
Vue.config.productionTip = false;
Vue.use(VueRouter); // 使用插件
new Vue({
render: h => h(App),
router,
}).$mount('#app');
App.vue中:
<template>
<!-- App.vue -->
<div>
<Banner />
<div class="row">
<div class="col-xs-2 col-xs-offset-2">
<div class="list-group">
<!-- 原来使用<a>标签实现页面跳转 -->
<!-- <a class="list-group-item" href="./about.html">About</a>
<a class="list-group-item active" href="./home.html">Home</a> -->
<!-- 使用<router-link>标签, href改为to属性 -->
<router-link class="list-group-item" active-class="active" to="/about"
>About</router-link
>
<router-link class="list-group-item" active-class="active" to="/home"
>Home</router-link
>
</div>
</div>
<div class="col-xs-6">
<div class="panel">
<div class="panel-body">
<!-- 指定组件的呈现位置 -->
<router-view></router-view>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import Banner from "./components/Banner.vue";
export default {
name: "App",
components: { // 不需要注册路由组件
Banner,
},
};
</script>
注意:<router-link>标签最终会转化为<a>标签。
嵌套路由
路由可以多级嵌套,并且可以携带query参数。
二级路由组件:pages/News.vue:
<template>
<ul>
<li v-for="(news, index) of newsList" :key="index">
{{ news }}
</li>
</ul>
</template>
<script>
export default {
name: "News",
data() {
return {
newsList: ["news001", "news002", "news003"],
};
},
};
</script>
二级路由组件:pages/Message.vue,并且携带query参数,传给三级路由:
<template>
<div>
<ul>
<li v-for="msg of messageList" :key="msg.id">
<!-- 携带query参数, to的字符串写法, 注意模板字符串 -->
<!-- <router-link
:to="`/home/message/detail?title=${msg.title}&context=${msg.context}`"
>{{ msg.title }}</router-link
> -->
<!-- 携带query参数, to的对象写法, 更推荐 -->
<router-link
:to="{
path: '/home/message/detail',
query: { title: msg.title, context: msg.context },
}"
>
{{ msg.title }}
</router-link>
</li>
</ul>
<hr />
<router-view></router-view>
</div>
</template>
<script>
export default {
name: "Message",
data() {
return {
messageList: [
{ id: "001", title: "message001", context: "0000000001" },
{ id: "002", title: "message002", context: "0000000002" },
{ id: "003", title: "message003", context: "0000000003" },
],
};
},
};
</script>
路由规则配置,修改router/index.js,通过childeren属性配置子路由:
// ...
import Message from '../pages/Message.vue';
import News from '../pages/News.vue';
import MessageDetail from '../pages/MessageDetail.vue';
export default new VueRouter({
routes: [
{
path: '/about',
component: About
},
{
path: '/home',
component: Home,
children: [ // 配置二级路由
{
path: 'message', // 二级路由地址不要加'/'
component: Message,
children: [ // 配置三级路由
{
path: 'detail',
component: MessageDetail
}
]
},
{
path: 'news',
component: News
},
]
},
]
});
修改pages/Home.vue:
<template>
<div>
<h1>Home!!!!!</h1>
<ul class="nav nav-tabs">
<li>
<!-- 二级路由 -->
<router-link
class="list-group-item"
active-class="active"
to="/home/news"
>News</router-link
>
</li>
<li>
<router-link
class="list-group-item"
active-class="active"
to="/home/message"
>Message</router-link
>
</li>
</ul>
<!-- 指定组件的呈现位置 -->
<router-view></router-view>
</div>
</template>
<script>
export default {
name: "Home",
};
</script>
query参数
在上面的二级路由pages/Message.vue中,将query参数传给了三级路由,三级路由/pages/MessageDetail.vue需要接收参数并展示:
<template>
<div>
<h3>消息编号:{{ $route.query.title }}</h3>
<p>消息内容:{{ $route.query.context }}</p>
</div>
</template>
<script>
export default {
name: "MessageDetail",
};
</script>
命名路由
可以简化路由跳转的路径,to属性必须以对象形式写。
修改router/index.js,给三级路由增加name属性:
import VueRouter from 'vue-router';
// ...
export default new VueRouter({
routes: [
// {...},
{
// ...
children: [ // 配置二级路由
{
// ...
children: [ // 配置三级路由
{
name: 'myDetail', // 命名路由
path: 'detail',
component: MessageDetail
}
]
},
// {...},
]
},
]
});
在二级路由组件:pages/Message.vue中不写path: '/home/message/detail',直接写name:
<router-link
:to="{
name: 'myDetail',
query: { title: msg.title, context: msg.context },
}"
>
{{ msg.title }}
</router-link>
params参数
修改router/index.js:
children: [ // 配置三级路由
{
name: 'myDetail', // 命名路由
path: 'detail/:title/:context', // 必须声明params参数
component: MessageDetail
}
]
二级路由传递params参数:
<!-- 携带params参数 -->
<!-- <router-link :to="`/home/message/detail/${msg.title}/${msg.context}`">
{{msg.title}}
</router-link> -->
<router-link
:to="{
name: 'myDetail',
params: { title: msg.title, context: msg.context },
}"
>
{{ msg.title }}
</router-link>
注意:携带params参数,如果to属性采取对象写法,则必须命名路由,并且写name属性,而不能写path属性。
最后在三级路由/pages/MessageDetail.vue接收params参数:
<div>
<!-- <h3>消息编号:{{ $route.query.title }}</h3>
<p>消息内容:{{ $route.query.context }}</p> -->
<h3>消息编号:{{ $route.params.title }}</h3>
<p>消息内容:{{ $route.params.context }}</p>
</div>
props配置
给三级路由配置props:
第一种:对象写法
第一种写法:值为对象,该对象的key-value以props形式传给MessageDetail组件
修改router/index.js:
children: [ // 配置三级路由
{
name: 'myDetail', // 命名路由
path: 'detail/:title/:context', // 必须声明params参数
component: MessageDetail,
// props第一种写法, 该对象的key-value以props形式传给MessageDetail组件
props: {a: 1, b: 'hello'},
}
]
在三级路由/pages/MessageDetail.vue使用props:
<template>
<div>
<h3>消息编号:{{ $route.params.title }}</h3>
<p>消息内容:{{ $route.params.context }}</p>
<p>{{a}}, {{b}}</p>
</div>
</template>
<script>
export default {
name: "MessageDetail",
props: ['a', 'b']
};
</script>
第二种:布尔值写法
第二种写法为布尔值类型,如果为true, 表示所有params参数以props形式传给MessageDetail组件。
修改router/index.js:
children: [ // 配置三级路由
{
name: 'myDetail', // 命名路由
path: 'detail/:title/:context', // 必须声明params参数
component: MessageDetail,
// props第二种写法, 如果为true, 表示所有params参数以props形式传给MessageDetail组件
props: true
}
]
在三级路由/pages/MessageDetail.vue使用props:
<template>
<div>
<!-- <h3>消息编号:{{ $route.params.title }}</h3>
<p>消息内容:{{ $route.params.context }}</p> -->
<!-- <p>{{a}}, {{b}}</p> -->
<h3>消息编号:{{ title }}</h3>
<p>消息内容:{{ context }}</p>
</div>
</template>
<script>
export default {
name: "MessageDetail",
props: ['title', 'context']
};
</script>
第三种:函数写法
第三种写法为函数写法,可以指定query或者params,函数返回对象的key-value值以props形式传给MessageDetail组件。
修改router/index.js:
children: [ // 配置三级路由
{
name: 'myDetail', // 命名路由
path: 'detail/',
component: MessageDetail,
// props第三种写法, 函数返回对象的key-value值以props形式传给MessageDetail组件
props($route) {
return {
title: $route.query.title,
context: $route.query.context
};
}
}
]
可以用对象解构简写:
children: [ // 配置三级路由
{
// ...
props({query: {title, context}}) {
return {title, context};
}
}
]
replace属性
作用:控制路由跳转时操作浏览器记录的方式,浏览器默认有两种方式写入历史记录:
push:追加记录replace:替换当前记录
<router-link replace ...> ... </router-link>
编程式路由导航
不使用<router-link>标签,完成路由跳转。
pushShow(msg) {
/* 参数就是to属性中要写的对象 */
this.$router.push({
name: "myDetail",
query: { title: msg.title, context: msg.context },
});
},
replaceShow(msg) {
this.$router.replace({
name: "myDetail",
query: { title: msg.title, context: msg.context },
});
},
另外,$router中的也有能实现类似BOM中页面跳转的API:
this.$router.forward(); // 前进
this.$router.back(); // 后退
this.$router.go(3); // 前进/后退指定页数
注意:以上的方式都存在于$router的原型对象。
缓存路由组件
之前讲到过,路由组件在切换时,默认被销毁,需要时重新挂载。
给News.vue添加一个输入框,当我们在News组件和Message组件之间来回切换时,不希望输入框中的内容被清空,可以在调用上一级路由组件Home中,使用<keep-alive>标签,让不展示的路由组件保持挂载,不被销毁:
<!-- News是组件名 -->
<keep-alive include="News">
<router-view></router-view>
</keep-alive>
<!-- 缓存多个组件 -->
<keep-alive :include="['News', 'Message']">
<router-view></router-view>
</keep-alive>
注意:如果不写include属性,则News组件和Message组件都会被缓存,但我们希望的是只缓存有<input>标签的News组件。
两个新的生命周期
路由组件所独有的两个生命周期钩子:
activated:路由组件被激活时触发deactivated:路由组件失活时触发
export default {
name: "News",
// data() {...},
activated() {
console.log('activated');
// this.timer = setInterval(() => {...}, 100);
},
deactivated() {
console.log('deactivated');
// clearInterval(this.timmer); // 清空定时器
},
};
想象以下场景:News组件中由于有<input>标签,所以使用<keep-alive>标签缓存,但News组件中还包含着一个计时器,由于没有被销毁,定时器一直没有被清空,于是就需要上面的两个生命周期钩子。
路由守卫
全局前置路由守卫
在暴露router对象之前,进行一些操作:
/* router/index.js */
// ...
// const router = new VueRouter({...});
/* 全局前置路由守卫: 初始化前/路由发生切换时调用 */
router.beforeEach((to, from, next) => {
// 通过name或者path来判断目标路径
if (to.name === 'myNews' || to.path === '/home/message') {
// 进行身份验证操作
if (localStorage.getItem('age') >= 18) {
next(); // 调用next表示允许前往
}
else {
alert('年龄未满18岁, 无权限查看!');
}
}
else {
next(); // 调用next表示允许前往
}
});
export default router;
上面的写法的缺点在于,对于每个希望进行权限验证的目标路径,都需要进行一次判断,可以通过给路由规则增加meta属性来解决:
children: [ // 配置二级路由
// {...},
{
name: 'myNews',
path: 'news',
component: News,
meta: {isAuth: true}, // 需要授权
},
]
router.beforeEach((to, from, next) => {
// 判断目标路径是否需要授权
if(to.meta.isAuth){
// 进行身份验证操作...
}
else { next(); }
});
export default router;
注意:meta属性允许我们将任意信息附加到路由上。
全局后置路由守卫
通过全局后置路由守卫,配合meta属性,实现点击页面,更改文档标题的功能:
// meta: {isAuth: true} // 首先在meta数据添加标题
/* 全局后置路由守卫: 初始化前/路由发生切换之后调用 */
router.afterEach((to, from) => {
document.title = to.meta.title || 'Title';
});
独享路由守卫
为路由组件配置独享的路由守卫:
children: [ // 配置二级路由
// {...},
{
name: 'myNews',
path: 'news',
component: News,
meta: {isAuth: true}, // 需要授权
beforeEnter(to, from, next) {
// 逻辑与beforeEach完全相同
}
},
]
注意:独享路由守卫没有后置!
组件内路由守卫
/* News.vue */
export default {
name: "News",
// ...
/* 通过路由规则, 进入该组件时被调用 */
beforeRouteEnter(to, from, next) {
// 逻辑与beforeEach完全相同
},
/* 通过路由规则, 离开该组件时被调用 */
beforeRouteLeave(to, from, next) {
// 逻辑类似与beforeEach, 离开当前组件, 前往另一个组件的权限验证
},
};
history模式与hash模式
之前的路由默认是hash模式,例如http://localhost:8080/#/home/message:
- 对于URL来说,
#之后的都是hash值,不会带给服务器
可以修改mode,变为history模式:
/* router/index.js */
// ...
export default new VueRouter({
mode: 'history',
// routes: [...]
});
可以发现,URL变为http://localhost:8080/home/message。
hash模式:
- 地址中有
# - 兼容性更好
- 如果将地址通过手机的第三方app分享,如果app校验严格,则地址会被标记为不合法
history模式:
- 地址干净
- 兼容性较差,因为没有
#,直接访问服务器,相当于请求服务器对应路径下的资源 - 应用部署后需要配合后端人员,解决404问题,例如使用
npm安装connect-history-api-fallback

浙公网安备 33010602011771号