QX-AI初始化中...
暂无预设简介,请点击下方生成AI简介按钮。
过渡与动画
Vue 封装了过渡与动画效果
作用:在插入、更新、移除 Dom 元素时,自动在合适的时刻在过渡标签上添加或移除不同的类名,只需写动画和过渡效果并应用到对应时刻的类名上,而无需手动操作 Dom
过渡标签:
(1) <transition>
包裹需要动画效果的单个元素
(2) <transition-group>
包裹需要动画效果的多个元素,每个元素都需要配置 Key 属性
不同时刻样式类名:
(1) 控制元素进入的样式
- v-enter 进入的起点
- v-enter-active 进入过程中
- v-enter-to 进入的终点
(2) 控制元素离开的样式
- v-leave 离开的起点
- v-leave-active 离开过程中
- v-leave-to 离开的终点
过渡标签添加 name 属性来配置类名的前缀,默认是 v,如进入起点的类名是 .v-enter 或 <前缀>-enter
离开和进入:即元素的插入、更新、移除,通常配合 v-show、v-if 来控制
添加 appear
属性让动画在网页首次加载时、元素首次插入时也触发
元素飞入飞出案例
分别用动画属性 animate
和过渡属性 transition
配合不同的类名实现相同的动画效果
模板结构:
1 2 3 4 5 6 7 8
| <template> <div> <button @click="isShow=!isShow">显示隐藏</button> <transition name="hello" appear> <h1 v-show="isShow" class="title">你好</h1> </transition> </div> </template>
|
交互:
1 2 3 4 5 6 7 8
| export default { name: 'Test-', data(){ return{ isShow: true, } } }
|
使用动画属性 animate
配合 .v-enter-active 和 .v-leave-active
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| .title { background: rgb(114, 196, 219); }
.hello-enter-active{ animation: titleShow 1s; }
.hello-leave-active{ animation: titleShow 1s reverse; }
@keyframes titleShow{ from { transform: translateX(-100%); } to{ transform: translateX(0px); } }
|
过渡属性 transition
配合进入和离开的起点、终点的样式实现动画
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| .title { background: rgb(114, 196, 219); }
.hello-enter { transform: translateX(-100%); }
.hello-enter-to { transform: translateX(0); }
.hello-leave { transform: translateX(0); }
.hello-leave-to { transform: translateX(-100%); }
.hello-enter-active, .hello-leave-active { transition: 0.5s linear; }
|
第三方动画库
有许多优秀的动画库,引入即可使用,以 animate.css 为例
安装:npm install animate.css --save
使用第三方库时,通常要在过渡标签上通过几个属性来自定义样式类名的后缀,name属性设置前缀
(1) enter-active-class 属性:设置进入过程中的样式类名
(2) leave-active-class 属性:设置离开过程中的样式类名
使用:引入动画库后,在过渡标签上设置name前缀以使用该动画库,然后在animate.css文档中挑选动画效果,将进入动画的类名设置到 enter-active-class 属性中,离开动画的类名设置到 leave-active-class 属性中,通过设置样式类名后缀来使用具体的动画效果
案例:
1 2 3 4 5 6 7 8 9 10
| import 'animate.css' export default { name: 'Test-', data() { return { isShow: true, } } }
|
模板结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <template> <div> <button @click="isShow = !isShow">显示隐藏</button> <transition name="animate__animated animate__bounce" enter-active-class="animate__swing" leave-active-class="animate__backOutUp" appear > <h1 v-show="isShow" class="title" key="1">你好</h1> </transition> </div> </template>
|
CSS 样式,因为使用了动画库,就无需自己写动画了
1 2 3
| .title { background: rgb(114, 196, 219); }
|
ToDoList添加动画
给 ToDoList 案例中每个todo 的新增和删除添加动画
效果:
修改 ToDoList.vue 组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| <template> <div class="todo-list"> <ul> <transition-group name="todo"> <ListItem v-for="item in todos" :key="item.id" :todo="item"></ListItem> </transition-group> </ul> </div> </template>
<style>
.todo-enter, .todo-leave-to { opacity: 0; transform: translateX(-50%); }
.todo-enter-to, .todo-leave { opacity: 1; transform: translateX(0); } .todo-enter-active, .todo-leave-active { transition: all 0.3s; } </style>
|
Vue中发请求
在 Vue 中发请求通常用 Axios
安装: npm i axios
Axios 笔记: AJAX请求相关-Axios
Vue-cli配置代理
当后端没有配置 cors 时,本地开发环境请求后端API就会产生跨域问题
服务器之间通信没有跨域问题,可以在本地配置一个代理服务器去请求API
在 vue.config.js 中添加 devServer 配置项,以在开发环境下将 API 请求代理到 API 服务器
1 2 3 4 5 6 7
| const { defineConfig } = require('@vue/cli-service') module.exports = defineConfig({ transpileDependencies: true, devServer: { proxy: '<转发的url>' } })
|
配置代理后,将请求的 baseURL 改为本地ip+端口,如 120.0.0.1:8080,代理服务会将请求转发,自己去请求实际的API地址,然后返回数据
有些时候并不想所有发往自身获取资源的请求都被代理转发,实际想获取的是本地public文件夹下的资源,而不是远程API的
可以写完整版 devServer 配置,匹配路径去选择性代理
作用:可以配置多个代理,且可以灵活的控制发往本地的请求是否走代理转发
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const { defineConfig } = require('@vue/cli-service') module.exports = defineConfig({ transpileDependencies: true, devServer: { proxy: { '/api': { target: '<url>', ws: true, changeOrigin: true, pathRewrite: {'<正则匹配路径>', '<替换内容>'} }, '/foo': { target: '<other_url>' } } } })
|
还是让后端配cors好
获取所有账单
在 NodeJS 中写过一个记账本案例,里面写了一套完整的 API,刚好拿来用
KeepingBook-API文档
功能:点击登陆提示登陆中,登陆成功提示成功登陆,点击获取账单,提示加载中,请求成功后展示所有账单的列表
App.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| <template> <div> <button @click="login">登录获取token</button> <button @click="getData">获取所有账单</button> <div v-show="tip !== ''" class="tip">{{ tip }}</div> <ul> <li v-for="(item, index) in kpb" :key="item.id"> {{ index + 1 }}: {{ item.matter }} | {{ item.date }} | {{ item.type }} | {{ item.account }} </li> </ul> </div> </template>
<script> import axios from "axios"; axios.defaults.baseURL = 'https://kpb.qcqx.cn'; export default { name: "App", data() { return { token: "", kpb: "", tip: "欢迎,请登陆", } }, methods: { login() { this.kpb = ""; this.tip = "登陆中...."; axios({ url: '/api/login', method: 'post', data: { username: 'qx', password: '123' }, timeout: 5000 }).then(res => { console.log(res.data); this.token = res.data.data.token; this.tip = "登陆成功"; }).catch(err => { console.log(err); this.tip = "登陆失败"; }); }, getData() { this.kpb = ""; this.tip = "账单加载中...."; axios({ url: '/api', method: 'get', headers: { token: this.token }, timeout: 5000 }).then(res => { console.log(res.data); if (res.data.data === null) { this.tip = res.data.msg; return; } this.kpb = res.data.data; this.tip = ""; }).catch(err => { console.log(err); this.tip = "获取账单失败"; }); } } }; </script>
|
插槽
插槽是由子组件在模板的合适位置通过 <slot>
定义的
作用:让父组件可以向子组件定义的插槽处插入 html 结构
插槽的种类:
(1) 默认插槽:简单地将组件标签中所有html结构放到插槽处
(2) 具名插槽:通过 <slot>
上的 name 属性区分插槽,通过 v-slot:<插槽名>
将html插入到指定插槽
(3) 作用域插槽:用于数据在子组件中,且不将数据外传,根据数据生成的结构由父组件决定的情况
注意:
(1) 父组件中要插入插槽的html结构也是在父组件中完成模板解析的,可以访问父组件中的数据,而不能直接访问到自组件的数据
(2) 三种插槽可以混合使用
默认插槽
简单地将组件标签中所有html结构放到插槽处
父组件1 2 3 4 5
| <ListBox> <ul> <li v-for="(item, index) in arr" :key="index">{{ item }}</li> </ul> </ListBox>
|
具名插槽
通过 <slot>
上的 name 属性区分插槽,通过 v-slot:<插槽名>
将html插入到指定插槽
v-slot:
可以简写为 #
父组件1 2 3 4 5 6 7 8 9 10
| <ListBox> <template v-slot:list> <ul><li v-for="(item, index) in arr" :key="index">{{ item }}</li></ul> </template> <template #other> <h2>chuckle</h2> </template> </ListBox>
|
子组件1 2
| <slot name="list"></slot> <slot name="other"></slot>
|
作用域插槽
用于数据在子组件中,且不将数据外传,根据数据生成的结构由父组件决定的情况
子组件通过 <slot>
的标签属性传数据给父组件(只能在组件标签中使用)
父组件通过 #<插槽名>="obj"
的 obj 接收一个对象,对象中有传过来的属性名和属性值(数据)
父组件1 2 3 4 5 6 7 8 9 10 11
| <ListBox> <template #default="{arr}"> <ul><li v-for="(item, index) in arr" :key="index">{{ item }}</li></ul> </template> <template #other="obj"> <div>{{ obj.x }} | {{ obj.y }}</div> </template> </ListBox>
|
子组件1 2 3 4
| <slot :arr="arr"></slot>
<slot name="other" :x="x" :y="y"></slot>
|
Vuex
在实现组件间通信时,无论是全局事件总线还是消息订阅与发布,大量的声明事件或消息名,会让代码变得臃肿、难以维护
Vuex 是实现集中式状态(数据)管理的一个 Vue 插件,对 Vue 应用中多个组件的共享状态进行集中式的管理(读/写)
安装: npm install vuex --save
Vue2 只能使用 Vuex3
功能:将原本某一组件管理的数据拿出来给 Vuex 管理,所有组件都能看到 Vuex 中管理数据的仓库 Store,以及使用仓库所提供的,对数据读和写的方法
何时用 Vuex 管理状态(数据):
(1) 多个组件依赖于同一状态
(2) 来自不同组件的行为需要变更同一状态
Vuex工作原理
Vuex 工作原理图:
Vuex 有三个重要组成部分,这三个部分都在 Store 实例上,本质都是对象
- Actions:进行一些业务逻辑(异步)的操作,如发送 AJAX 请求,不直接对数据进行操作
- Mutations:对数据进行直接的维护,有许多对数据进行操作的自定义方法
- State:状态(数据),存放着多个组件共享的数据
Vuex的工作循环:
- 组件通过
dispatch()
调用 Actions 中的方法,触发一些业务逻辑
- Actions 中业务逻辑处理完后,在合适时机调用
commit()
触发 Mutations 中对数据进行直接操作的方法
- 然后 State 中数据响应式地变化,触发对应视图更新
注意:如果对数据的操作不带有更多的业务逻辑,组件也可以直接调用 commit()
方法
Store:由 Vuex.Store()
构造函数创建,提供了三个重要组成部分以及 dispatch()
、commit()
等各种API
搭建Vuex环境
新建一个JS文件,用于配置和创建 Store 实例,通常是 src/store/index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import Vue from 'vue'; import Vuex from 'vuex';
Vue.use(Vuex);
const actions = {} const mutations = {} const state = {}
export default new Vuex.Store({ actions, mutations, state });
|
然后在 new Vue()
时将 Store 实例作为配置项传入
目的:让 vm 和 vc 身上新增一个 $store 属性,让所有组件都能访问到数据仓库 store
main.js1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import Vue from "vue"; import App from "./App.vue";
import store from "./store";
Vue.config.productionTip = false;
new Vue({ el: "#app", store, render: h => h(App), })
|
纯Vue求和案例
App.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <template> <div> <CountNum></CountNum> </div> </template>
<script> import CountNum from "./components/CountNum.vue"; export default { name: "App", components: { CountNum, } }; </script>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| <template> <div> <h3>{{ sum }}</h3> <select v-model.number="num"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button @click="add">+</button> <button @click="reduce">-</button> <button @click="addOdd">当前总和是奇数再加</button> <button @click="addWait">延迟1s再加</button> </div> </template>
<script> export default { name: "CountNum", data() { return { sum: 0, num: 0, } }, methods: { add() { this.sum += this.num; }, reduce(){ this.sum -= this.num; }, addOdd(){ if(this.sum%2){ this.sum += this.num; } }, addWait(){ setTimeout(()=>{ this.sum += this.num; }, 1000) } }, } </script>
|
Vuex版求和案例
对纯 Vue 版进行修改,将 sum 交给 vuex 的 store 管理,将直接对数据的操作放到 Mutations 中,将带有业务逻辑的操作放到 Actions 中
一些总结与注意:
- 虽然
$store.state.xxx = xxx
直接修改数据,也能响应式更新视图,但这样做无法被 vuex-tools 所监视,所以不要直接修改数据
commit()
和 dispatch()
都只能传入两个参数,第一个是要触发的函数名,第二个是传入的数据
- actions 中的函数收到两个参数,第一个 context 指上下文,是一个 mini 的 store,有部分 store 上的属性和方法,第二个 value 是
dispatch()
传来的数据
- mutations 中的函数也收到两个参数,第一个 state 是 store 中的数据,第二个 value 是
commit()
传来的数据
CountNum.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| <template> <div> <h3>{{ $store.state.sum }}</h3> <select v-model.number="num"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button @click="add">+</button> <button @click="reduce">-</button> <button @click="addOdd">当前总和是奇数再加</button> <button @click="addWait">延迟1s再加</button> <button @click="$store.state.sum = 1">测试直接修改数据</button> </div> </template> <script> export default { name: "CountNum", data() { return { num: 0, } }, methods: { add() { this.$store.commit('add', this.num) }, reduce(){ this.$store.commit('reduce', this.num) }, addOdd(){ this.$store.dispatch('addOdd', this.num) }, addWait(){ this.$store.dispatch('addWait', this.num) } }, } </script>
|
/store/index.js1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| import Vue from 'vue'; import Vuex from 'vuex';
Vue.use(Vuex);
const actions = { addOdd(context, value) { if (context.state.sum % 2) { context.commit('add', value); } }, addWait(context, value) { setTimeout(() => { context.commit('add', value); }, 1000) } }
const mutations = { add(state, value) { state.sum += value; }, reduce(state, value) { state.sum -= value; }, }
const state = { sum: 0, }
export default new Vuex.Store({ actions, mutations, state });
|
getters配置项
除了三个重要组成部分必须作为 Store 的配置项之外,还有其它配置项
getters 和计算属性差不多,对数据进行加工后再返回,其中的函数收到一个参数 state
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const getters = { bigSum(state){ return state.sum * 10; } } export default new Vuex.Store({ actions, mutations, state, getters });
|
通过 $store.getters
获取加工后的数据
1 2
| <h3>{{ $store.state.sum }}</h3> <h3>{{ $store.getters.bigSum }}</h3>
|
mapState和mapGetters
当一个组件需要获取多个状态时,总在模板中用 $store.state.xxx
获取状态,代码重复且冗余
即使是手动写成计算属性,也要重复去 return $store.state.xxx
mapState()
和 mapGetters()
可以帮我们生成状态的计算属性,以此简洁地使用状态
需要引入后才能使用:
1
| import { mapState, mapGetters} from 'vuex'
|
使用注意:
- 传入一个对象,key-value 是
<生成的计算属性名>:<状态名或getter名>
- 返回值是包含多个计算属性的对象,需要用
...
对象展开运算符,将其与组件的计算属性对象合并
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import { mapState, mapGetters} from 'vuex' export default { computed: { test(){ return 'test' }, ...mapState({ sum: 'sum' }), ...mapGetters({ bigSum: 'bigSum' }) }, }
|
当要生成的计算属性名和状态名或getter名一样时,可以使用数组简写形式,传入数组,元素是状态名或getter名
mapActions和mapMutations
在 methods 中也写了很多看起来重复的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| add() { this.$store.commit('add', this.num) },
reduce(){ this.$store.commit('reduce', this.num) },
addOdd(){ this.$store.dispatch('addOdd', this.num) },
addWait(){ this.$store.dispatch('addWait', this.num) }
|
mapActions()
和 mapMutations()
能够生成调用 commit 和 dispatch 方法的方法
用法和 mapState 和 mapGetters 差不多
1 2 3 4 5 6 7
| methods: { ...mapMutations(['add', 'reduce']), ...mapActions({ addOdd: 'addOdd', addWait: 'addWait' }), },
|
生成的方法长这样,需要传入一个value
1 2 3
| add(value){ this.$store.commit('add', value) }
|
所以调用时就需要手动传入 value
1 2 3 4 5
| <button @click="add(num)">+</button> <button @click="reduce(num)">-</button> <button @click="addOdd(num)">当前总和是奇数再加</button> <button @click="addWait(num)">延迟1s再加</button>
|
Vuex模块化
不同的功能会用到不同的数据,当很多实现不同功能的数据都写在一个 index.js 中或一个配置项中时,会导致命名冲突且代码不易维护
作用:让代码更好维护,让多种数据分类更加明确
将不同数据的 store 配置项单独拿出,每个模块的函数获取的数据或上下文都是模块自己的,无需再指定,即模块的局部状态
/store/count.js1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| export default { namespaced: true,
actions: { addOdd(context, value) { if (context.state.sum % 2) { context.commit('add', value); } }, addWait(context, value) { setTimeout(() => { context.commit('add', value); }, 1000) } },
mutations: { add(state, value) { state.sum += value; }, reduce(state, value) { state.sum -= value; }, },
state: { sum: 0, },
getters: { bigSum(state) { return state.sum * 10; } } }
|
通过 Store()
的 modules 配置项使用模块
/store/index.js1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import Vue from 'vue'; import Vuex from 'vuex';
Vue.use(Vuex);
import count from "./count.js";
export default new Vuex.Store({ modules: { count, } });
|
调整 mapState()
、mapGetters()
等的参数,指定是触发哪个模块的 actions 或 mutations
1 2 3 4 5 6 7 8
| computed: { ...mapState('count',['sum']), ...mapGetters('count',['bigSum']) }, methods: { ...mapMutations('count',['add', 'reduce']), ...mapActions('count',['addOdd', 'addWait']), },
|
总结:
(1) 命名空间:默认情况下,模块内部的 action、mutation 和 getter 注册在全局命名空间,这样使得多个模块能够对同一个 mutation 或 action 作出响应。如果希望模块具有更高的封装度和复用性,可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。
(2) 开启命名空间后:
- 组件中读取 state 数据
1 2 3 4
| this.$store.count.sum
...mapState('count',['sum'])
|
- 组件中读取 getters 数据
1 2 3 4
| this.$store.getters['count/bigSum']
...mapGetters('count',['bigSum'])
|
- 组件中调用 dispatch
1 2 3 4
| this.$store.dispatch('count/addOdd', value)
...mapActions('count',['addOdd', 'addWait']),
|
- 组件中调用 commit
1 2 3 4
| this.$store.commit('count/add', value)
...mapMutations('count',['add', 'reduce']),
|