Vue 脚手架笔记
第一章:Vue CLI
一、认识 Vue CLI
1. 是什么?
Vue CLI 是 Vue.js 的官方命令行工具,它提供了一种快速搭建 Vue.js 项目的方法。Vue CLI 提供了一套完整的工具链,包括项目脚手架、热重载、代码压缩、单元测试、端到端测试等。
2. 如何使用
要使用 Vue CLI,首先需要在系统上安装 Node.js 和 npm,然后通过 npm 安装 Vue CLI:
npm install -g @vue/cli然后,就可以使用 vue create 命令创建一个新的 Vue.js 项目:
vue create my-project二、脚手架文件结构
├── node_modules
├── public
│ ├── favicon.ico: 页签图标
│ └── index.html: 主页面
├── src
│ ├── assets: 存放静态资源
│ │ └── logo.png
│ │── component: 存放组件
│ │ └── HelloWorld.vue
│ │── App.vue: 汇总所有组件
│ │── main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件
├── package.json: 应用包配置文件
├── package-lock.json:包版本控制文件
├── README.md: 应用描述文件三、关于不同版本的 Vue
vue.js 与 vue.runtime.xxx.js 的区别:
① vue.js 是完整版的 Vue,包含:核心功能 + 模板解析器。
② vue.runtime.xxx.js 是运行版的 Vue,只包含:核心功能;没有模板解析器。
因为 vue.runtime.xxx.js 没有模板解析器,所以不能使用 template 这个配置项,需要使用 render 函数接收到的 createElement 函数去指定具体内容。
四、vue.config.js 配置文件
1. 是什么
使用 vue inspect > output.js 仅仅可以查看到 Vue 脚手架的默认配置。想修改默认配置怎么办?
vue.config.js 是一个可选的配置文件,如果项目的 (和 package.json 同级的) 根目录中存在这个文件,那么它会被 @vue/cli-service 自动加载。也可以使用 package.json 中的 vue 字段。
module.exports = {
pages: {
index: {
// page 的入口
entry: 'src/index/main.js',
// 模板来源
template: 'public/index.html',
// 在 dist/index.html 的输出
filename: 'index.html',
// 当使用 title 选项时,
// template 中的 title 标签需要是 <title><%= htmlWebpackPlugin.options.title %></title>
title: 'Index Page',
// 在这个页面中包含的块,默认情况下会包含
// 提取出来的通用 chunk 和 vendor chunk
// 决定了哪些 JavaScript chunk 会被包含到生成的 HTML 文件中
chunks: ['chunk-vendors', 'chunk-common', 'index']
},
// 当使用只有入口的字符串格式时,
// 模板会被推导为 `public/subpage.html`
// 并且如果找不到的话,就回退到 `public/index.html`。
// 输出文件名会被推导为 `subpage.html`。
subpage: 'src/subpage/main.js'
}
}详情见:配置参考
2. devServer.proxy
Vue 脚手架配置代理。
方法一
在 vue.config.js 中添加如下配置:
module.exports = {
devServer: {
proxy: 'http://localhost:5000'
}
}说明:
1)工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端资源)。
2)优点:配置简单,请求资源时直接发给前端(8080)即可。
缺点:不能配置多个代理,不能灵活的控制请求是否走代理。
方法二
编写 vue.config.js 配置具体代理规则:
module.exports = {
devServer: {
proxy: {
'/api1': { // 匹配所有以 '/api1'开头的请求路径
target: 'http://localhost:5000', // 代理目标的基础路径
changeOrigin: true,
pathRewrite: {'^/api1': ''}
},
'/api2': {// 匹配所有以 '/api2'开头的请求路径
target: 'http://localhost:5001', // 代理目标的基础路径
changeOrigin: true,
pathRewrite: {'^/api2': ''}
}
}
}
}
/*
changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
changeOrigin默认值为true
*/优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
缺点:配置略微繁琐,请求资源时必须加前缀。
第二章:深入组件
一、VC 常用属性与方法
1. ref 属性
1)被用来给元素或子组件注册引用信息(id 的替代者)。
2)应用在 html 标签上获取的是真实 DOM 元素,应用在组件标签上是组件实例对象(vc)。若想获取到子组件真实的解析后的 HTML 标签,需要在组件标签上添加 id 属性,在 Vue 中使用 JavaScript 原生方式获取。
3)使用方式
第一步:打标识 <h1 ref="xxx">.....</h1> 或 <School ref="xxx"></School>
第二步:获取 this.$refs.xxx
2. nextTick 方法
1)语法:this.$nextTick(回调函数)
2)作用:在下一次 DOM 更新结束后执行其指定的回调。
3)什么时候用:当改变数据后,要基于更新后的新 DOM 进行某些操作时,要在 nextTick 所指定的回调函数中执行。
二、组件间的通信
1. 父给子传
说明
父给子传信息,使用 props 配置项。
1)功能:让组件接收外部传过来的数据。
2)传递数据:<Demo name="xxx"/>
3)接收数据
第一种方式(只接收):props:['name']
第二种方式(限制类型):props:{name:String}
第三种方式(限制类型、限制必要性、指定默认值)
props:{
name:{
type:String, // 类型
required:true, // 必要性
default:'老王' // 默认值
}
}这样在接受数据的组件实例上就会有属性了。
备注:① props 是只读的,Vue 底层会监测你对 props 的修改,如果进行了修改,就会发出警告。若业务需求确实需要修改,那么请复制 props 的内容到 data 中一份,然后去修改 data 中的数据。② props 与 data 中的数据发生冲突,那么,优先使用 props 中的数据,且 Vue 会在控制台发出警告。
2. 子给父传
方法一:props + 回调
A 组件是 B 的父组件。现在 B 要给 A 传递数据,怎么办?
思路:在 A 组件编写一个函数,通过 props 传递给 B,然后在 B 合适的位置调用 A 组件传过来的函数,这样 A 就能拿到 B 的数据了。
TodoList 案例学到的技巧
组件化编码流程
(1). 拆分静态组件:组件要按照功能点拆分,命名不要与 html 元素冲突。
(2). 实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用。
1). 一个组件在用:放在组件自身即可。
2). 一些组件在用:放在他们共同的父组件上(状态提升)。
(3). 实现交互:从绑定事件开始。
props 适用于
(1). 父组件 ==> 子组件通信
(2). 子组件 ==> 父组件通信(要求父先给子一个函数)
使用 v-model 时要切记:v-model 绑定的值不能是 props 传过来的值,因为 props 是不可以修改的!
props 传过来的若是对象类型的值,修改对象中的属性时 Vue 不会报错,但不推荐这样做。
方法二:自定义事件
1)一种组件间通信的方式,适用于:子组件 ===> 父组件
2)使用场景:A 是父组件,B 是子组件,B 想给 A 传数据,那么就要在 A 中给 B 绑定自定义事件(事件的回调在 A 中)。
3)绑定自定义事件
第一种方式:在父组件中使用的子组件标签上添加
v-on:自定义事件名或者@自定义事件名,例如:<Demo @atXQ="test"/>或<Demo v-on:atXQ="test"/>(Vue 会把自定义事件名添加到当前子组件 VC 上)。然后在父组件的 methods 中编写一个函数,函数名为自定义事件名。最后在子组件合适位置调用this.$emit('自定义事件名',数据)。第二种方式:在父组件中使用的子组件标签上添加 ref 属性,这样在父组件 VC 的
emit('自定义事件名',数据)```。 javascript<Demo ref="xxx"/> ...... mounted(){ this.$refs.xxx.$on('atXQ',this.test) }若想让自定义事件只能触发一次,可以使用
once修饰符,或$once方法。触发自定义事件:
this.$emit('atXQ',数据)解绑自定义事件:
this.$off('atXQ')、this.$off(['atXQ','atCui'])、this.$off()解绑所有自定义事件。组件上也可以绑定原生 DOM 事件,需要使用
native修饰符。
注意:通过 this.$refs.xxx.$on('atXQ',回调) 绑定自定义事件时,回调要么配置在 methods 中,要么用箭头函数,否则 this 指向会出问题!
3. 任意组件间通信
[1] 全局事件总线
兄弟之间用总线,称为全局事件总线(Global Event Bus)。说白了,也是用的自定义事件。

1)一种组件间通信的方式,适用于任意组件间通信。
2)如何实现?
安装全局事件总线
javascriptnew Vue({ // ...... beforeCreate() { Vue.prototype.$bus = this // 安装全局事件总线,$bus就是当前应用的vm }, // ...... })使用事件总线
接收数据:A 组件想接收数据,则在 A 组件中给 $bus 绑定自定义事件,事件的回调留在 A 组件自身。
javascriptmethods(){ demo(data){......} } // ...... mounted() { this.$bus.$on('xxxx',this.demo) }提供数据:
this.$bus.$emit('xxxx',数据)
最好在 beforeDestroy 钩子中,用 $off 去解绑当前组件所用到的事件。
[2] 消息订阅与发布 (pubsub)
1)一种组件间通信的方式,适用于任意组件间通信。
2)使用步骤
安装 pubsub:
npm i pubsub-js引入:
import pubsub from 'pubsub-js'。引入的 pubsub 是一个对象,上面有很多实用方法。使用 pubsub 对象
接收数据:A 组件想接收数据,则在 A 组件中订阅消息,订阅的回调留在 A 组件自身。
javascriptmethods(){ demo(data){......} } // ...... mounted() { this.pid = pubsub.subscribe('xxx',this.demo) // 订阅消息 }提供数据:
pubsub.publish('xxx',数据)
最好在 beforeDestroy 钩子中,用
PubSub.unsubscribe(pid)去取消订阅。
三、插槽 Slots
1)作用:让父组件可以向子组件指定位置插入 html 结构,也是一种组件间通信的方式,适用于父组件 ==> 子组件。
2)分类:默认插槽、具名插槽、作用域插槽。
3)使用方式:
[1] 默认插槽
父组件中:
<Category>
<div>html结构1</div>
</Category>
子组件中:
<template>
<div>
<!-- 定义插槽 -->
<slot>插槽默认内容...</slot>
</div>
</template>[2] 具名插槽
v-slot:footer 只能写在 template 标签中,简写为 #。
父组件中:
<Category>
<template slot="center">
<div>html结构1</div>
</template>
<template v-slot:footer>
<div>html结构2</div>
</template>
<template #footer>
<div>html结构2</div>
</template>
</Category>
子组件中:
<template>
<div>
<!-- 定义插槽 -->
<slot name="center">插槽默认内容...</slot>
<slot name="footer">插槽默认内容...</slot>
</div>
</template>[3] 作用域插槽
① 理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。
② 具体编码:
games 数据在 Category 组件中,但使用数据所遍历出来的结构由 App 组件决定。
App 父组件中:
<Category>
<template scope="scopeData">
<!-- 生成的是ul列表 -->
<ul>
<li v-for="g in scopeData.games" :key="g">{{g}}</li>
</ul>
</template>
</Category>
<Category>
<template slot-scope="scopeData">
<!-- 生成的是h4标题 -->
<h4 v-for="g in scopeData.games" :key="g">{{g}}</h4>
</template>
</Category>
Category 子组件中:
<template>
<div>
<slot :games="games"></slot>
</div>
</template>
<script>
export default {
name:'Category',
props:['title'],
//数据在子组件自身
data() {
return {
games:['红色警戒','穿越火线','劲舞团','超级玛丽']
}
},
}
</script>四、Vuex
1. 初识 Vuex
1)概念
在 Vue 中实现集中式状态(数据)管理的一个 Vue 插件,对 vue 应用中多个组件的共享状态进行集中式的管理(读 / 写),也是一种组件间通信的方式,且适用于任意组件间通信。


vuex 存储的数据是非持久化的。
2)何时使用?
- 多个视图依赖于同一状态。
- 来自不同视图的行为需要变更同一状态。
总结:多个组件需要共享数据时。
3)工作原理

Vue Components 可以直接调用 Mutations;Actions、Mutations、State 都需要 Store 管理。
2. 快速入门
1)搭建 vuex 环境
说明
Vue2 中要用 Vuex3 版本;Vue3 中要用 Vuex4 版本。
① 创建文件:src/store/index.js
// 1. 引入
// 引入Vue核心库
import Vue from 'vue'
// 引入Vuex
import Vuex from 'vuex'
// 应用Vuex插件
Vue.use(Vuex)
// 2. 准备三个组件
// 2.1 准备actions对象——响应组件中用户的动作
//actions: 处理action,可以书写自己的业务逻辑,也可以处理异步
const actions = {}
// 2.2 准备mutations对象——修改state中的数据
// mutations: 修改state的唯一手段
const mutations = {}
// 2.3 准备state对象——保存具体的数据
// state: 仓库存储数据的地方
const state = {}
// 3. 创建并暴露store
export default new Vuex.Store({
actions,
mutations,
state
})② 在 main.js 中创建 vm 时传入 store 配置项
......
// 引入store
import store from './store'
......
// 创建vm
new Vue({
el:'#app',
render: h => h(App),
store
})这样就会在每个 vm 和 vc 上有 $store 属性了。
2)使用
① 初始化数据:配置 actions、配置 mutations。下面是在文件 src/store/index.js 中进行编写的。
// 引入Vue核心库
import Vue from 'vue'
// 引入Vuex
import Vuex from 'vuex'
// 引用Vuex
Vue.use(Vuex)
const actions = {
// 响应组件中加的动作
// context 是 miniStore; value 是调用 dispatch 方法传入的第二个参数
jia(context,value){
// console.log('actions中的jia被调用了',context,value)
context.commit('JIA',value)
},
}
const mutations = {
// 执行加
JIA(state,value){
// console.log('mutations中的JIA被调用了',state,value)
state.sum += value
}
}
// 初始化数据
const state = {
sum:0
}
// 创建并暴露store
export default new Vuex.Store({
actions,
mutations,
state,
})② 组件中使用
组件中读取 vuex 中的数据:$store.state.sum
组件中修改 vuex 中的数据:this.$store.dispatch('action中的方法名',数据) 或 this.$store.commit('mutations中的方法名',数据)
若没有网络请求或其他业务逻辑,组件中也可以越过 actions,即不写
dispatch,直接编写commit。
3. 基本使用
1)getters 的使用
[1] 概念:当 state 中的数据需要经过加工后再使用时,可以使用 getters 加工。
[2] 使用步骤
① 在 store.js 中追加 getters 配置。
// ......
// getters: 理解为计算属性,用于简化仓库数据,让组件获取仓库的数据更加方便
const getters = {
bigSum(state){
return state.sum * 10
}
}
// 创建并暴露store
export default new Vuex.Store({
// ......
getters
})② 组件中读取数据:$store.getters.bigSum
2)辅助函数
① mapState 方法:用于帮助我们映射 state 中的数据为计算属性。

import {mapState} from 'vuex'
// ......
computed: {
// 借助mapState生成计算属性:sum、school、subject(对象写法)
...mapState({sum:'sum',school:'school',subject:'subject'}),
// 借助mapState生成计算属性:sum、school、subject(数组写法)
...mapState(['sum','school','subject']),
},② mapGetters 方法:用于帮助我们映射 getters 中的数据为计算属性
import {mapGetters} from 'vuex'
......
computed: {
// 借助mapGetters生成计算属性:bigSum(对象写法)
...mapGetters({bigSum:'bigSum'}),
// 借助mapGetters生成计算属性:bigSum(数组写法)
...mapGetters(['bigSum'])
},③ mapActions 方法:用于帮助我们生成与 actions 对话的方法,即:包含 this.$store.dispatch(xxx) 的函数

import {mapActions} from 'vuex'
......
methods:{
// 靠mapActions生成:incrementOdd、incrementWait(对象形式)
...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
// 靠mapActions生成:incrementOdd、incrementWait(数组形式)
...mapActions(['jiaOdd','jiaWait'])
}自动生成的方法:
// ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
incrementOdd(value){
this.$store.commit(jiaOdd, value);
}
incrementWait(value){
this.$store.commit(jiaWait, value);
}④ mapMutations 方法:用于帮助我们生成与 mutations 对话的方法,即:包含 this.$store.commit(xxx) 的函数
import {mapMutations} from 'vuex'
......
methods:{
// 靠mapActions生成:increment、decrement(对象形式)
...mapMutations({increment:'JIA',decrement:'JIAN'}),
// 靠mapMutations生成:JIA、JIAN(对象形式)
...mapMutations(['JIA','JIAN']),
}mapActions 与 mapMutations 使用时,若需要传递参数,则要在模板中绑定事件时传递好参数,否则参数是事件对象。
3)模块化 + 命名空间
[1] 目的:让代码更好维护,让多种数据分类更加明确。
[2] 使用
① 修改 store.js
const countAbout = {
namespaced:true, // 开启命名空间
state:{x:1},
mutations: { ... },
actions: { ... },
getters: {
bigSum(state){
return state.sum * 10
}
}
}
const personAbout = {
namespaced:true, // 开启命名空间
state:{ ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
countAbout,
personAbout
}
})推荐把 countAbout 与 personAbout 抽取到单独的 JavaScript 文件中(例如:countAbout 抽取到 count.js 文件中;personAbout 抽取到 personAbout .js 文件中)。然后使用 import 导入。更加利于后期维护。
② 开启命名空间后,组件中读取 state 数据
// 方式一:自己直接读取
this.$store.state.personAbout.list
// 方式二:借助mapState读取
...mapState('countAbout',['sum','school','subject']),③ 开启命名空间后,组件中读取 getters 数据
// 方式一:自己直接读取
this.$store.getters['personAbout/firstPersonName']
// 方式二:借助mapGetters读取
...mapGetters('countAbout',['bigSum'])④ 开启命名空间后,组件中调用 dispatch
// 方式一:自己直接dispatch
this.$store.dispatch('personAbout/addPersonWang',person)
// 方式二:借助mapActions
...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})⑤ 开启命名空间后,组件中调用 commit
// 方式一:自己直接commit
this.$store.commit('personAbout/ADD_PERSON',person)
// 方式二:借助mapMutations
...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),五、pinia
1. 初识 pinia
1)是什么?
Pinia 是由 Eduardo San Martin Morote 创建的,他是 Vue.js 核心团队的成员,负责 Vue Router 的维护。而 Vuex 是由 Vue.js 的创始人尤雨溪创建的。Pinia 是作为 Vuex 的一个轻量级替代方案而创建的,它提供了更简单和灵活的状态管理。
起始于 2019 年 11 月左右的一次实验,其目的是设计一个拥有组合式 API 的 Vue 状态管理库。但不强制要求开发者使用组合式 API。
2)优势
mutation 已被弃用。
不再有可命名的模块。
不再有嵌套结构的模块。在 Pinia 中,所有的 store 都是扁平的。

API 的设计方式是尽可能地利用 TS 类型推理。
无需要动态添加 Store,它们默认都是动态的。
3)工作原理

2. 快速入门
1)安装
yarn add pinia
# 或者使用 npm
npm install pinia2)集成 pinia
src/main.js 文件添加 pinia。
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const pinia = createPinia()
const app = createApp(App)
app.use(pinia)
app.mount('#app')3. 核心概念
1)store
[1] 定义store
Option Store
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
getters: {
double: (state) => state.count * 2,
},
actions: {
increment() {
this.count++
},
},
})Setup Store
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})推荐使用 Option Store,因为如果使用 Setup Store,会有局限性。比如:操作 state 的重置方法 $reset() 在 Setup Store 中就没有,需要自己实现。
[2] 使用 Store
拿到特定的 store
<script setup>
import { useCounterStore } from '@/stores/counter'
const store = useCounterStore()
</script>获取数据
<script setup>
// ❌ 这将不起作用,因为它破坏了响应性
// 这就和直接解构 `props` 一样
const { doubleCount } = store // doubleCount 将始终是 0
setTimeout(() => {
store.increment()
}, 1000)
// ✅ 这样写是响应式的
const doubleValue = computed(() => store.doubleCount)
const { doubleCount } = toRefs(store)
const { doubleCount } = storeToRefs(store)
// 作为 action 的 increment 可以直接解构
const { increment } = store
</script>2)操作 store
访问 state
通过 store 实例访问 state,直接对其进行读写。
const store = useStore()
store.count++变更 state
允许用一个 state 的补丁对象在同一时间更改多个属性:
store.$patch({
count: store.count + 1,
age: 120,
name: 'DIO',
})重置 state
将 state 重置为初始值。
const store = useStore()
store.$reset()替换 state
可以通过将其 $state 属性设置为新对象来替换 Store 的整个状态:
// 这实际上并没有替换`$state`
store.$state = { count: 24 }3)Getter
[1] 访问当前 store 的 Getters
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
}),
getters: {
doubleCount: (state) => state.count * 2,
},
})然后就可以直接访问 store 实例上的 getter 了:
<script setup>
import { useCounterStore } from './counterStore'
const store = useCounterStore()
</script>
<template>
<p>Double count is {{ store.doubleCount }}</p>
</template>[2] Getters 中访问自己的其他 Getters
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
}),
getters: {
doubleCount: (state) => state.count * 2,
doubleCountPlusOne() {
return this.doubleCount + 1
},
},
})[3] 向 getter 传递参数
Getter 只是幕后的计算属性,所以不可以向它们传递任何参数。不过,可以从 getter 返回一个函数,该函数可以接受任意参数:
export const useUserListStore = defineStore('userList', {
getters: {
getUserById: (state) => {
return (userId) => state.users.find((user) => user.id === userId)
},
},
})并在组件中使用:
<script setup>
import { useUserListStore } from './store'
const userList = useUserListStore()
const { getUserById } = storeToRefs(userList) // 需要使用 getUserById.value 访问
</script>
<template>
<p>User 2: {{ getUserById(2) }}</p>
</template>[4] 访问其他 store 的 getter
想要使用另一个 store 的 getter 的话,那就直接在 getter 内使用就好:
import { useOtherStore } from './other-store'
export const useStore = defineStore('main', {
state: () => ({
// ...
}),
getters: {
otherGetter(state) {
const otherStore = useOtherStore()
return state.localData + otherStore.data
},
},
})4)Actions
[1] 定义
Action 相当于组件中的 method。它们可以通过 defineStore() 中的 actions 属性来定义,并且它们也是定义业务逻辑的完美选择。
export const useCounterStore = defineStore('main', {
state: () => ({
count: 0,
}),
actions: {
increment() {
this.count++
},
randomizeCounter() {
this.count = Math.round(100 * Math.random())
},
},
})[2] 使用
类似 getter,action 也可通过 this 访问整个 store 实例。
import { useAuthStore } from './auth-store'
export const useSettingsStore = defineStore('settings', {
state: () => ({
preferences: null,
// ...
}),
actions: {
async fetchUserPreferences() {
const auth = useAuthStore()
if (auth.isAuthenticated) {
this.preferences = await fetchPreferences()
} else {
throw new Error('User must be authenticated')
}
},
},
})不同的是,action 可以是异步的。
import { mande } from 'mande'
const api = mande('/api/users')
export const useUsers = defineStore('users', {
state: () => ({
userData: null,
// ...
}),
actions: {
async registerUser(login, password) {
try {
this.userData = await api.post({ login, password })
showTooltip(`Welcome back ${this.userData.name}!`)
} catch (error) {
showTooltip(error)
// 让表单组件显示错误
return error
}
},
},
})第三章:逻辑复用
一、mixin (混入)
1)功能:可以把多个组件共用的配置提取成一个混入对象。
2)使用方式
① 第一步:定义混合
在一个新的 js 文件中编写如下内容。
export const hunhe = {
data(){....},
methods:{....}
....
}② 第二步:使用混入
全局混入:Vue.mixin(xxx)
局部混入(Vue 的 vm 或 vc 配置对象中添加属性):mixins:[xxx]
如果 mixin 数据与组件或者 Vue 实例冲突,优先以自己的为准;如果 mixin 定义的生命周期与组件或者 Vue 实例冲突,那么先执行 mixin 定义的生命周期,在执行自己的生命周期。
二、插件
1)功能:用于增强 Vue。
2)本质:包含 install 方法的一个对象。install 的第一个参数是 Vue,第二个及其以后的参数是插件使用者传递的数据。
3)如何使用?
① 定义插件
在一个新的 js 文件中编写如下内容。
// vm和vc都可以用
export default {
install(Vue, ...args){
// vue帮你调用install方法
// 全局过滤器
Vue.filter(...);
// 全局指令
Vue.directive(...);
// 全局混入
Vue.mixin(...);
// 给vue原型上添加一个方法 vc/vm 都可以使用
Vue.prototype.hello = function (){
alert('hello')
}
}
}② 在 src/main.js 导入
import plugins from './plugins'③ 使用插件:Vue.use()
第四章:过渡 & 动画
一、transition 组件
1. 是什么
如果希望给单元素或者组件实现过渡动画,可以使用 transition 内置组件来完成动画。Vue 提供了 transition 的封装组件,在下列情形中,可以给任何元素和组件添加进入 / 离开过渡:
- 条件渲染 (v-if)、条件展示 (v-show)
- 动态组件
- 组件根节点
Transition 组件的原理
当插入或删除包含在 transition 组件中的元素时, Vue 将会做以下处理:
1)自动嗅探目标元素是否应用了 CSS 过渡或者动画 ,如果有,那么在恰当的时机添加 / 删除 CSS 类名。
2)如果 transition 组件提供了 JavaScript 钩子函数,这些钩子函数将在恰当的时机被调用。
3)如果没有找到 JavaScript 钩子并且也没有检测到 CSS 过渡 动画 DOM 插入、删除操作将会立即执行。
总结:在插入、更新或移除 DOM 元素时,在合适的时候给元素添加样式类名。

2. 使用
1)准备好样式
- 元素进入的样式:
- v-enter:进入的起点
- v-enter-active:进入过程中
- v-enter-to:进入的终点
- 元素离开的样式:
- v-leave:离开的起点
- v-leave-active:离开过程中
- v-leave-to:离开的终点
2)使用 <transition> 包裹要过度的元素,并配置 name 属性。
<transition name="hello">
<h1 v-show="isShow">你好啊!</h1>
</transition>备注:若有多个元素需要过度,则需要使用 <transition-group>,且每个元素都要指定 key 值。
第五章:路由
单页面应用程序(英文名:Single Page Application)简称 SPA,顾名思义,指的是一个 Web 网站中只有唯一的一个 HTML 页面,所有的功能与交互都在这唯一的一个页面内完成。这就需要用到路由。
一个路由(route)就是一组映射关系(key - value),多个路由需要路由器(router)进行管理。
前端路由:key 是路径,value 是组件。
一、基础
1. 快速入门
1)安装 vue-router
npm i vue-router
# vue2 项目,要指定版本安装
npm i vue-router@3.5.2说明
2022 / 07 以后,安装 vue-router 时,默认版本改为了 4,而 4 版本只能在 Vue3 中使用。只有 vue-router3 才能在 Vue2 使用。
2)引入与应用插件
src/main.js
// 1. 导入所需模块
import Vue from 'vue'
import VueRouter from 'vue-router'
// 2. 调用 Vue.use() 函数,将 VueRouter 安装为 Vue 的插件
Vue.use(VueRouter)3)编写 router 配置项
新建 src/router/index.js 文件,编写路由配置。
// 引入VueRouter
import VueRouter from 'vue-router'
// 引入Luyou组件
import About from '../components/About'
import Home from '../components/Home'
// 配置路由规则
const routes = [
{
path:'/about',
component:About
},
{
path:'/home',
component:Home
}
]
// 创建router实例对象,去管理一组一组的路由规则
const router = new VueRouter({
mode: 'history', // 或者是 hash
routes,
})
// 暴露router
export default router4)在 main.js 中创建 vm 时传入 router 配置项
......
// 引入store
import router from './router'
......
// 创建vm
new Vue({
el:'#app',
render: h => h(App),
router
})5)实现切换(active-class 可配置高亮样式)
<router-link active-class="active" to="/about">About</router-link>6)指定展示位置
<router-view></router-view>任何子路由都是在其父路由的组件中切换显示,不管是多少层的路由嵌套。因此,在其父路由上需要写上 <router-view /> 承载容器来存放子路由组件。
几个注意点
① 路由组件通常存放在 pages|views 文件夹,一般组件通常存放在 components 文件夹。
② 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
③ 每个组件都有自己的 $route 属性,里面存储着自己的路由信息。
④ 整个应用只有一个 router,可以通过组件的 $router 属性获取到。
2. 多级路由(多级路由)
1)配置路由规则,使用 children 配置项
routes:[
{
path:'/about',
component:About,
},
{
path:'/home',
component:Home,
children:[ // 通过children配置子级路由
{
path:'news', // 此处一定不要写:/news
component:News
},
{
path:'message',// 此处一定不要写:/message
component:Message
}
]
}
]2)跳转(要写完整路径)
<router-link to="/home/news">News</router-link>3. 命名路由
1)作用:可以简化路由的跳转。
2)如何使用
① 给路由命名
{
path:'/demo',
component:Demo,
children:[
{
path:'test',
component:Test,
children:[
{
name:'hello' // 给路由命名
path:'welcome',
component:Hello,
}
]
}
]
}② 简化跳转
<!--简化前,需要写完整的路径 -->
<router-link to="/demo/test/welcome">跳转</router-link>
<!--简化后,直接通过名字跳转 -->
<router-link :to="{name:'hello'}">跳转</router-link>
<!--简化写法配合传递参数 -->
<router-link
:to="{
name:'hello',
query:{
id:666,
title:'你好'
}
}"
>跳转</router-link><router-link> 的 replace 属性
① 作用:控制路由跳转时操作浏览器历史记录的模式。
② 浏览器的历史记录有两种写入方式:分别为 push 和 replace,push 是追加历史记录,replace 是替换当前记录。路由跳转时候默认为 push。
③ 如何开启 replace 模式:<router-link replace .......>News</router-link>
4. 其他
1)路由重定向
{
path: '/',
redirect: '/discover/tuijian'
}2)404 处理
// 方法一
{
path: '*',
component: () => import('@/views/NotFound.vue')
}
// 方法二
{
name: 'NotFound',
path: '/:pathMatch(.*)*',
component: () => import('@/views/NotFound.vue')
}3) 链接高亮(排它)
为什么超链接,不用 a 标签,而用 router-link 标签?
- 使用 router-link 也会解析为 a 标签
- 会给当前访问的超链接,自动加两个类名
- router-link-exact-active --- 精确匹配类名
- router-link-active --- 模糊匹配类名
如何更改默认给我们添加的类名?
const router = new VueRouter({
routes: [...],
linkActiveClass: "类名1",
linkExactActiveClass: "类名2"
})通过给上述两个类添加样式,实现导航链接的高亮效果。
二、进阶
1. 路由传参
1)query 参数
① 传递参数
<!-- 跳转并携带query参数,to的字符串写法 -->
<router-link to="/home/message/detail?id=666&title=你好">跳转</router-link>
<router-link :to="`/home/message/detail?id=${id}&title=${title}`">跳转</router-link>
<!-- 跳转并携带query参数,to的对象写法 -->
<router-link
:to="{
path:'/home/message/detail',
query:{
id:666,
title:'你好'
}
}"
>跳转</router-link>② 接收参数
$route.query.id
$route.query.title2)params 参数
① 配置路由,声明接收 params 参数
{
path:'/home',
component:Home,
children:[
{
path:'news',
component:News
},
{
component:Message,
children:[
{
name:'xiangqing',
path:'detail/:id/:title', // 使用占位符声明接收params参数
component:Detail
}
]
}
]
}
// 注: 在配置路由的时候,在占位的后面加上一个问号[params可以传递或者不传递]
// 比如: path: '/search/:keyword?',② 传递参数
<!-- 跳转并携带params参数,to的字符串写法 -->
<router-link :to="/home/message/detail/666/你好">跳转</router-link>
<router-link :to="`/home/message/detail/${m.id}/${m.title}`"></router-link>
<!-- 跳转并携带params参数,to的对象写法 -->
<router-link
:to="{
name:'xiangqing',
params:{
id:666,
title:'你好'
}
}"
>跳转</router-link>特别注意:路由携带 params 参数时,若使用 to 的对象写法,则不能使用 path 配置项,必须使用 name 配置!
③ 接收参数:
$route.params.id
$route.params.title3)props 配置
作用:让路由组件更方便的收到参数。
{
name:'xiangqing',
path:'detail/:id',
component:Detail,
//第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件
// props:{a:900}
//第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件
// props:true
//第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
props(route){
return {
id:route.query.id,
title:route.query.title
}
}
}面试题
路由传递参数(对象写法)path 是否可以结合 params 参数一起使用?
答: 路由跳转传参的时候,对象的写法可以是 name、path 形式,但是需要注意的是,path 这种写法不能与 params 参数一起使用。
如何指定 params 参数可传可不传?
如果路由要求传递params参数,但是你就不传递params参数,URL就会有问题
比如:配置路由的时候,占位了(params参数),但是路由跳转的时候就不传递
路径就会出现问题:
http://localhost:8080/#/?k=QWE
http://localhost:8080/#/search?k=QWE (正常情况下)
如何指定params参数可以传递、或者不传递,在配置路由的时候,在占位的后面加上一个问号[params可以传递或者不传递]params 参数可以传递也可以不传递,但是如果传递的是空串,如何解决?
使用 undefined 解决 params 参数可以传递、不传递(空的字符串)。
javascriptthis.$router.push({ name:'search', params:{ keyword:'' || undefined }, query:{ k:this.keyword.toUpperCase() } });路由组件能不能传递 props 数据?
可以总共有三种写法。
javascript// 布尔值写法: params props: true, // 对象写法: 额外的给路由组件传递一些props props: { a: 1, b: 2 }, // 函数写法: 可以params参数、query参数,通过props传递给路由组件 props: ($route) => { return { keyword: $route.params.keyword, k: $route.query.k } }
2. 编程式路由导航
1)作用:不借助 <router-link> 实现路由跳转,让路由跳转更加灵活。
2)API
// *************************************************************** 字符串
this.$router.push('路由路径')
this.$router.replace('路由路径')
// *************************************************************** 对象
// ********************************************* path
// 写法一:对象格式,用 path 进行跳转
this.$router.push({
path: '完整路由地址',
query: {
参数: '值',
参数: '值' // query参数会拼接成 /xxxxx?参数=值&参数=值
},
// params: {
// 参数: '值',
// 参数: '值'
// }, // 有问题,不用。参数不能拼接到url后面
})
// 写法二:对象格式,用 name 进行跳转
this.$router.push({
name: '路由的name值',
query: {
参数: '值',
参数: '值' // /xxxxx?参数=值&参数=值
},
params: {
参数: '值',
参数: '值'
}, // 参数会拼到url后面 /xxxxx/3/绿皮书
})
// ********************************************* replace
this.$router.replace({
name:'xiangqing',
params:{
id:xxx,
title:xxx
}
})
// *************************************************************** 前进后退
this.$router.forward() // 前进
this.$router.back() // 后退
this.$router.go() // 可前进也可后退 里面传递的是数字,正数前进几步,负数后退几步| 跳转类型 | query 参数(id=1&name=xx) | params 参数(/movie/1) |
|---|---|---|
| path | √ | × |
| name | √ | √ |
| 完整字符串 | - | - |
3. 缓存路由组件
1)作用:让不展示的路由组件保持挂载,不被销毁。
2)具体编码
<keep-alive include="News">
<router-view></router-view>
</keep-alive>
<keep-alive :include="['News','Message']">
<router-view></router-view>
</keep-alive>include 写的是组件名,不写 include 属性,将会缓存所有组件。
4. 两个新的生命周期钩子
1)作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
2)具体名字:
① activated 路由组件被激活时触发。
② deactivated 路由组件失活时触发。
5. 路由守卫
作用:对路由进行权限控制。
分类:全局守卫、独享守卫、组件内守卫
每个守卫方法接收三个参数:
to: Route:即将要进入的目标路由对象。from: Route:当前导航正要离开的路由。next: Function:一定要调用该方法来 resolve 这个钩子。执行效果依赖next方法的调用参数。next():进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。next(false):中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到from路由对应的地址。next('/')或者next({ path: '/' }):跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向next传递任意位置对象,且允许设置诸如replace: true、name: 'home'之类的选项以及任何用在router-link的toprop 或router.push中的选项。next(error):(2.4.0+) 如果传入next的参数是一个Error实例,则导航会被终止且该错误会被传递给router.onError()注册过的回调。
1)全局守卫
src/router/index.js
// 全局前置守卫:初始化时执行、每次路由切换前执行
router.beforeEach((to,from,next)=>{
console.log('beforeEach',to,from)
if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
if(localStorage.getItem('school') === 'hj44d45fh6*'){ //权限控制的具体规则
next() //放行
}else{
alert('暂无权限查看')
// next({name:'guanyu'})
}
}else{
next() //放行
}
})
// 全局后置守卫:初始化时执行、每次路由切换后执行
router.afterEach((to,from)=>{
console.log('afterEach',to,from)
if(to.meta.title){
document.title = to.meta.title // 修改网页的title
}else{
document.title = 'vue_test'
}
})2)独享守卫
只作用于某个特定的路由,这些守卫定义在路由配置对象上。只有前置独享守卫,无后置独享守卫。
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})可以把全局守卫的前置守卫代码放到独享守卫里面,如下:
beforeEnter(to,from,next){
console.log('beforeEnter',to,from)
if(to.meta.isAuth){ // 判断当前路由是否需要进行权限控制
if(localStorage.getItem('school') === 'hj44d45fh6*'){
next()
}else{
alert('暂无权限查看')
// next({name:'guanyu'})
}
}else{
next()
}
}3)组件内守卫
只作用于某个特定的组件内部,这些守卫可以在组件内使用。
// 进入守卫:通过路由规则,进入该组件时被调用
beforeRouteEnter (to, from, next) {
},
// 离开守卫:通过路由规则,离开该组件时被调用
beforeRouteLeave (to, from, next) {
}6. 路由器的两种工作模式
1)对于一个 url 来说,什么是 hash 值?—— # 及其后面的内容就是 hash 值。
2)hash 值不会包含在 HTTP 请求中,即:hash 值不会带给服务器。
3)hash 模式:
① 地址中永远带着 # 号,不美观。
② 若以后将地址通过第三方手机 app 分享,若 app 校验严格,则地址会被标记为不合法。
③ 兼容性较好。
4)history 模式:
① 地址干净,美观 。
② 兼容性和 hash 模式相比略差。
③ 应用部署上线时需要后端人员支持,解决刷新页面服务端 404 的问题。
注意:可以使用 connect-history-api-fallback 解决问题。