前端 - Vue中的AJAX、VueX、路由
目录Vue文档
- AJAX
- 配置代理
- 方式一
- 方式二
- 配置代理
- 插槽
- 默认插槽
- 具名插槽
- 作用域插槽
- Vuex
- 环境安装
- 举例
- mapState与mapGetters
- mapActions与mapMutations
- Vuex模块化
- 路由
- 路由简介
- SPA应用
- 路由的理解
- 路由的基本使用
- 嵌套路由
- query参数
- 命名路由
- params参数
- props配置
- 第一种:对象写法
- 第二种:布尔值写法
- 第三种:函数写法
- replace属性
- 编程式路由导航
- 缓存路由组件
- 两个新的生命周期
- 路由守卫
- 全局前置路由守卫
- 全局后置路由守卫
- 独享路由守卫
- 组件内路由守卫
- history模式与hash模式
- 路由简介
- UI组件库
- 移动端
- PC端
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:
需要配置代理服务器,端口号和客户端相同:
/* 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
插槽
默认插槽
分类
- 00001
- 00002
- 00003
- 00004
默认插槽,当使用者没有传递具体结构时会出现
具名插槽
多个插槽需要起名:
分类
- 00001
- 00002
- 00003
- 00004
baidu插槽,当使用者没有传递具体结构时会出现
bing插槽,当使用者没有传递具体结构时会出现
当插槽内有多个结构,但是又不希望使用 简写,将 父级的插槽无法访问子级数据,通过下面的方法传输: Vuex官方文档 专门在Vue中实现集中式状态管理的一个Vue插件,对Vue应用中多个组件的共享状态进行集中式的管理,也是一种组件间通信方式。 使用场景: 使用Vue2时,需要安装Vuex 3.x,输入下面的命令 新建 修改 在 使用 上面的代码相当于: 于是在模板直接使用该属性: 使用 这里要注意,由于不写参数默认传入 这时,使用 也可以使用数组写法。 VueX模块化官方文档 目的:让代码更好维护,数据分类更明确 Vue对应vue-router的3.x版本: 一条路由规则就是一组 注意: 下面是一个基本路由的例子,首先是两个路由组件: 接下来是 路由可以多级嵌套,并且可以携带query参数。 二级路由组件: 二级路由组件: 路由规则配置,修改 修改 在上面的二级路由 消息内容:{{ $route.query.context }} 可以简化路由跳转的路径, 修改 在二级路由组件: 修改 二级路由传递params参数: 注意:携带params参数,如果 最后在三级路由 消息内容:{{ $route.params.context }} 给三级路由配置props: 第一种写法:值为对象,该对象的key-value以props形式传给 修改 在三级路由 消息内容:{{ $route.params.context }} {{a}}, {{b}} 第二种写法为布尔值类型,如果为 修改 在三级路由 消息内容:{{ context }} 第三种写法为函数写法,可以指定query或者params,函数返回对象的key-value值以props形式传给MessageDetail组件。 修改 可以用对象解构简写: 作用:控制路由跳转时操作浏览器记录的方式,浏览器默认有两种方式写入历史记录: 不使用 另外, 注意:以上的方式都存在于 之前讲到过,路由组件在切换时,默认被销毁,需要时重新挂载。 给 注意:如果不写 路由组件所独有的两个生命周期钩子: 想象以下场景: 在暴露 上面的写法的缺点在于,对于每个希望进行权限验证的目标路径,都需要进行一次判断,可以通过给路由规则增加 注意: 通过全局后置路由守卫,配合 为路由组件配置独享的路由守卫: 注意:独享路由守卫没有后置! 之前的路由默认是hash模式,例如 可以修改 可以发现,URL变为标签的v-slot属性:
v-slot:改为#:
Baidu
baidu
作用域插槽
Vuex
环境安装
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进行操作:
当前和为:{{ $store.state.sum }}
当前和的10倍为:{{ $store.getters.bigSum }}
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;
}
},
};
当前和为:{{ mySum }}
当前和的10倍为:{{ bigSum }}
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,所以需要在模板中写明参数:
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'}) // 注意开启命名空间
路由
路由简介
npm i vue-router@3
SPA应用
vue-router是插件,专门用来实现SPA应用:
路由的理解
key-value的映射关系,一般key指的是请求路径,
value是组件,当浏览器路径改变时,对应的组件展示相应的页面内容value是函数,服务器收到请求时,根据请求路径找到对应的函数来处理请求,返回相应数据路由的基本使用
pages/文件夹,一般组件存放在components/文件夹$route属性,里面存储着自己的路由信息$router,通过路由组件访问
About!!!!
Home!!!!
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中:
嵌套路由
pages/News.vue:
pages/Message.vue,并且携带query参数,传给三级路由:
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:
Home!!!!!
query参数
pages/Message.vue中,将query参数传给了三级路由,三级路由/pages/MessageDetail.vue需要接收参数并展示:
消息编号:{{ $route.query.title }}
命名路由
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:params参数
router/index.js:children: [ // 配置三级路由
{
name: 'myDetail', // 命名路由
path: 'detail/:title/:context', // 必须声明params参数
component: MessageDetail
}
]
to属性采取对象写法,则必须命名路由,并且写name属性,而不能写path属性。/pages/MessageDetail.vue接收params参数:消息编号:{{ $route.params.title }}
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:
消息编号:{{ $route.params.title }}
第二种:布尔值写法
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:
消息编号:{{ title }}
第三种:函数写法
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:替换当前记录编程式路由导航
标签,完成路由跳转。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中,使用标签,让不展示的路由组件保持挂载,不被销毁:
include属性,则News组件和Message组件都会被缓存,但我们希望的是只缓存有标签的News组件。两个新的生命周期
activated:路由组件被激活时触发deactivated:路由组件失活时触发export default {
name: "News",
// data() {...},
activated() {
console.log('activated');
// this.timer = setInterval(() => {...}, 100);
},
deactivated() {
console.log('deactivated');
// clearInterval(this.timmer); // 清空定时器
},
};
News组件中由于有标签,所以使用标签缓存,但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模式
http://localhost:8080/#/home/message:
#之后的都是hash值,不会带给服务器mode,变为history模式:/* router/index.js */
// ...
export default new VueRouter({
mode: 'history',
// routes: [...]
});
http://localhost:8080/home/message。hash模式:
#history模式:
#,直接访问服务器,相当于请求服务器对应路径下的资源npm安装connect-history-api-fallbackUI组件库
移动端
PC端