一、Vue 简介
1.1 MVVM
- MVVM 是Vue实现属数据驱动视图和双向数据绑定的核心原理。它把每个HTML 都拆分了三个部分:

在MVVM中:
- Model 代表当前页面渲染时所依赖的数据源
- View 表示当前页面所渲染的DOM 结构
- ViewMode 表示Vue 的实例,它时MVVM的实例
1.2 MVVM 的工作原理
1、 ViewModel 会监听数据源的变化,当数据源变化后会同步更新到View的视图中
2、随后ViewModel会监听View的DOM的变化,当View变化后会同步变化到Model的数据源中
1.2.3 Vue 3 的新特性
#值得注意的新特性
Vue 3 中一些需要关注的新功能包括:
- 组合式 API
- Teleport
- 片段
- 触发组件选项
- 来自
@vue/runtime-core的createRendererAPI,用于创建自定义渲染器 - 单文件组件组合式 API 语法糖 (``)
- 单文件组件状态驱动的 CSS 变量 (`` 中的
v-bind) - SFC `` 现在可以包含全局规则或只针对插槽内容的规则
- Suspense 实验性
#非兼容的变更
下面列出了从 2.x 开始的非兼容的变更:
#全局 API
#模板指令
- 组件上
v-model用法已更改,以替换v-bind.sync - 在同一元素上使用的
v-if和v-for优先级已更改 v-bind="object"现在排序敏感v-on:event.native修饰符已移除v-for中的ref不再注册 ref 数组
#组件
- 只能使用普通函数创建函数式组件
- [
functionalattribute 在单文件组件 (SFC) 的 ` - 异步组件现在需要使用
defineAsyncComponent方法来创建 - 组件事件现在需要在
emits选项中声明
#渲染函数
- 渲染函数 API 更改
$scopedSlotsproperty 已移除,所有插槽都通过$slots作为函数暴露$listeners被移除或整合到$attrs$attrs现在包含class和styleattribute
#自定义元素
#其他小改变
destroyed生命周期选项被重命名为unmountedbeforeDestroy生命周期选项被重命名为beforeUnmountdefaultprop 工厂函数不再可以访问this上下文- 自定义指令的 API 已更改为与组件生命周期一致,且
binding.expression已移除 data选项应始终被声明为一个函数- 来自 mixin 的
data选项现在为浅合并 - Attribute 强制策略已更改
- 一些过渡的 class 被重命名
- `` 不再默认渲染包裹元素
- 当侦听一个数组时,只有当数组被替换时,回调才会触发,如果需要在变更时触发,则必须指定
deep选项 - 没有特殊指令的标记 (
v-if/else-if/else、v-for或v-slot) 的<template>现在被视为普通元素,并将渲染为原生的<template>元素,而不是渲染其内部内容。 - 已挂载的应用不会取代它所挂载的元素
- 生命周期的
hook:事件前缀改为vnode-
#被移除的 API
keyCode作为v-on修饰符的支持- o n 、 on、on、off 和 $once 实例方法
- 过滤器 (filter)
- 内联模板 attribute
$children实例 propertypropsData选项$destroy实例方法。用户不应再手动管理单个 Vue 组件的生命周期。- 全局函数
set和delete以及实例方法$set和$delete。基于代理的变化检测已经不再需要它们了。
二、Vue 3 与 Vue 2 的区别
2.1 Vite 的基本使用
2.1.1 vite 项目的运行流程
在工程化的项目中,vue 要做的事情很单纯:通过 main.js 把 App.vue 渲染到 index.html 的指定区域中。
其中:
① App.vue 用来编写待渲染的模板结构
② index.html 中需要预留一个 el 区域
③ main.js 把 App.vue 渲染到了 index.html 所预留的区域中
//1、 从Vue 中按需导入 createApp 函数
//2、 createApp 创建 vue 的单页面应用程序实例
import { createApp } from 'vue'
// 导入带渲染的App组件
import App from './App.vue'
//3、 调用 createApp 函数,返回时“ 单页面应用的实例 ” 用常量 spa_app 进行接收,
// 同时把App组件作为参数传给 createApp 函数,表示渲染App 到index.html 页面
const spa_app = createApp(App)
//4、 调用 spa_app 实例的 mount 方法,用来指定 vue 实际要控制的区域
spa_app.mount("#app")
2.2 在 template 中定义根节点
在 vue 2.x 的版本中, 节点内的 DOM 结构仅支持单个根节点
但是,在 vue 3.x 的版本中, 中支持定义多个根节点:
<template>
<h1>第一个</h1>
<h2>第二个</h2>
</template>
2.3 解决组件样式冲突
为每个组件分配唯一的自定义属性,在编写组件样式时,通过属性选择器来控制样式的作用域,示例代码如下:
<template>
<h3 data-v--001> 卢波图</h3>
</template>
<style>
/*
通过中括号属性选择器,来防止组件之间的样式冲突,因为每个组件分配的自定义属性是唯一的
*/
h3[data-v--001]{
border:1px solid red;
}
</style>
后者在style 标签上加上scoped 属性,也可以自动生产唯一的自定义属性
2.3.1 / depp / 样式穿透
2.4 组件之间的数据共享
父节点可以通过provide方法,对子孙组件共享数据
provide(){
return { // 该函数里包含了向子孙组件共享的数据
color : this.color
}
}
子组件可以通过 inject数组 接受父级向下共享的数据
inject:['color']
<h1>{{color}}</h1>
父组件也可以通过computed 函数向下共享响应式数据
provied(){
return {
// 通过computed 函数,将数据包装为响应式的
color:computed(()=> this.color)
}
}
子组件使用 则要加上.value
inject:['color']
// 响应式数据必须加上.value
<h1>{{color.value}}</h1>
总结:组件之间可以使用的通信
- 父子关系
- 父 --> 子 属性绑定
- 子 --> 父 事件绑定
- 父 <–> 子 组件上的v-model
- 兄弟关系
- EventBus
- 后代关系
- provide & inject
- 全局数据共享
- vuex
三、 vue 3 配置 axios
在全局配置axios
// 在main.js
// 为 axios 配置请求的根路径
axios.defaults.baseURL = 'http://api.com'
// 将axios 挂载为app的全局自定义属性
app.config.globalProperties.$http = axios
解决跨域
cors ,服务器返回数据,携带一些特殊的响应头
jsonp 只能解决 get 跨域请求问题
代理服务器
- nginx
- vue-cli
vue-cli
//vue.config.js module.exports = { ..., //方式一 devServer: { proxy:'http://localhost:5000' } }重新启动脚手架
npm run serve- 方式一不足:1.只能配置一个代理,2.只有本地没有找到的资源才走代理
//vue.config.js module.exports = { ..., //方式二 devServer: { proxy:{ //请求前缀 '/api':{ //匹配所以以 'api1'开头的请求路径 target:'<url>', pathRewrite:{'^/api':''} ws: true, //用于支持websocket changeOrigin: true //用于控制请求头中的host值 }, '/foo':{ target:'<other_url>' , pathRewrite:{'^/foo':''} } } } }
vue 2.x 全局配置 axios
实际项目开发中,几乎每个组件中都会使用 axios 发起数据请求。此时会遇到如下两个问题:
- 每个组件中都需要导入
axios(代码臃肿) - 每次发请求都需要填写完整的请求路径(不利于后期的维护)
在 main.js 文件中进行配置:
// 配置请求根路径
axios.defaults.baseURL = 'http://api.com'
// 把 axios 挂载到 Vue 原型上
Vue.prototype.$http = aixos
优点:每个组件可以通过 this.$http.get 直接发起请求,无需再导入 axios ;若根路径发生改变,只需修改 axios.defaults.baseURL ,有利于代码维护。
缺点:无法实现 API 的复用。即多个组件需要对同一个接口发起请求,那么每个组件都需要重复书写 this.$http.get('/users') 类似的代码,造成冗余。(视频上的说法,个人认为真正的缺点是如果存在多个根路径,这种方式无法解决,所以才会有下面的改进方式。)
改进:对于每一个根路径,独立封装一个 request.js 模块,组件导入所需根路径对应的 axios 进行使用。
import axios from 'axios'
// 创建 axios 实例
const request = axios.create({
baseURL: 'http://api.taobao.com',
})
export default request
四、vue 3 配置路由
1、安装vue-router
npm i vue-router@next -s
2、 从vue-router导入方法
import { createRouter,createWebHashHistory } from 'vue-router'
3、 创建路由实例
// 1. 导入对应组件
const Report = () => import('../components/report/Report')// 路由懒加载导入
import Home from './componens/home' // 正常导入
// 2, 创建路由实例
const router = createRouter({
history: createWebHashHistory(),
routes:[
{ path :"/home",components: Home},
{ path :"/report" ,components:Report}
]
})
// 4. 向外暴露路由实例对象
export default router
4、 main.js 挂载路由模块
import router from './router'
app.use(router)
4.2 配置路由导航守卫
// 创建罗友实例对象
const router = createRouter({...})
// 全局前置守卫
router.beforeEach((to,from,next)=>{
//to 目标路由
// from 当前导航正要离开的路由对象
// next 函数,表示放行
// 如果访问登录页面直接放行
if(to.path === '/login') return next()
// 查看用户有没token有token放行没toke跳回登录页
const tokenStr = window.sessionStorage.token
if(!tokenStr) return next('/login')
next()
})
五、 Vuex 状态管理
1、安装axios
npm install vuex@next --save
2、使用vuex
import { createApp } from 'vue'
import { createStore } from 'vuex'
// 创建一个新的 store 实例
const store = createStore({
state () {
return {
count: 0
}
},
mutations: {
increment (state) {
state.count++
}
}
})
const app = createApp({ /* 根组件 */ })
// 将 store 实例作为插件安装
app.use(store)
Vuex
概述
何为 Vuex ?
Vuex 专门在 Vue 中实现集中式状态(数据)管理的一个 Vue 插件,对 Vue 应用中多个组件的共享状态进行集中式的管理,也是一种组件间通信的方式,适用于任意组件间通信
何时用 Vuex ?
- 多个组件依赖于同一状态
- 来自不同组件的行为需要变更同一状态
Vuex 工作原理图:

官方 Vuex 项目结构示例:
├── index.html
├── main.js
├── api
│ └── ... # 抽取出API请求
├── components
│ ├── App.vue
│ └── ...
└── store
├── index.js # 组装模块并导出 store 的地方
├── actions.js # 根级别的 action
├── mutations.js # 根级别的 mutation
└── modules
├── cart.js # 购物车模块
└── products.js # 产品模块
Vuex 核心概念
state
- Vuex 管理的状态对象
- 唯一的
actions
- 值为一个对象,包含多个响应用户动作的回调函数
- 通过
commit()触发 mutation 中函数的调用,间接更新 state - 可包含异步代码
mutations
- 值为一个对象,包含多个直接更新 state 的方法
- 不能写异步代码,只能单纯地操作 state
getters
- 值为一个对象,包含多个用于返回数据的函数
- 类似于计算属性,getters 返回的数据依赖于 state 的数据
modules
- 一个 module 是一个 store 的配置对象,与一个组件对应
搭建 Vuex 环境
安装 Vuex:npm install vuex --save
创建文件 src/store/index.js :
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,
})
main.js 配置 store :
import store from './store'
new Vue({
el: '#app',
store,
render: (h) => h(App),
})
基本使用
组件实例与 Actions 和 Mutations 对话:
- 若没有网络请求或其他业务逻辑,组件中也可以越过
actions,即不写dispatch,直接编写commit:
methods: {
increment() {
this.$store.commit('ADD', this.number)
},
incrementOdd() {
this.$store.dispatch('addOdd', this.number)
},
incrementAsync() {
this.$store.dispatch('addAsync', this.number)
}
}
定义 Actions 和 Mutations:
context是一个迷你版的store,可访问dispatch,commit方法和statemutations的动作类型一般用大写,与actions区分
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const actions = {
// context 是一个迷你版的 store
// 可访问 dispatch, commit 方法和 state
addOdd(context, value) {
if (context.state.sum % 2 !== 0) {
context.commit('ADD', value)
}
},
addAsync(context, value) {
setTimeout(() => {
context.commit('ADD', value)
})
},
}
const mutations = {
// mutations 的动作类型一般用大写,与 actions 区分
ADD(state, value) {
state.sum += value
},
}
const state = {
sum: 0,
}
export default new Vuex.Store({
actions,
mutations,
state,
})
组件访问 Vuex 的数据:
<p>{{ $store.state.sum }}</p>
getters 的使用
- 当
state中的数据需要经过加工后再使用时,可以使用getters加工 - 它不是必须的,当加工逻辑复杂且需要复用时,可以考虑使用
state与getters的关系有点像data和computed的关系- 组件读取:
$store.getters.bigSum
...
const getters = {
bigSum(state) {
return state.sum * 10
}
}
export default new Vuex.Store({
actions,
mutations,
state,
getters
})
四个 mapXxx 方法
mapState()
- 将
state状态映射为计算属性 - 对象写法:键为自取的计算属性名,值为对应的状态(必须为字符串)
- 数组写法:当键值同名,可直接写状态名(字符串)
- 函数返回一个对象:
{sum: f, price: f} - 注意对象的
...{}展开写法
import { mapState } from 'vuex'
computed: {
// 手动写法
sum() {
return this.$store.state.sum
},
price() {
return this.$store.state.price
},
// 对象写法
...mapState({sum: 'sum', price: 'price'}),
// 数组写法
...mapState(['sum', 'price'])
}
mapGetters
- 将
getters的数据映射为计算属性
import { mapGetters } from 'vuex'
computed: {
bigSum() {
return this.$store.getters.bigSum
},
double() {
return this.$store.getters.double
},
// 对象写法
...mapGetters({bigSum: 'bigSum', double: 'double'}),
// 数组写法
...mapGetters(['bigSum', 'double']),
}
mapActions
- 生成与
actions对话的函数,即包含$store.dispatch() mapActions生成的函数不会传入参数,需要在调用时手动传入数据,不传参默认传入$event- 数组写法要注意函数名和
actions动作类型同名,调用时勿写错
import { mapActions } from 'vuex'
methods: {
// 手动写法
incrementOdd() {
this.$store.dispatch('addOdd', this.number)
},
incrementAsync() {
this.$store.dispatch('addAsync', this.number)
},
// 对象写法
...mapActions({incrementOdd: 'addOdd', incrementAsync: 'addAsync'}),
// 数组写法
...mapActions(['addOdd', 'addAsync']),
}
<button @click="incrementOdd(number)">奇数+1</button>
mapMutations
- 生成与
mutations对话的函数,即包含$store.commit() - 同样注意传递参数,以及数组形式函数名的问题
import { mapMutations } from 'vuex'
methods: {
increment() {
this.$store.commit('ADD', this.number)
},
decrement() {
this.$store.commit('SUB', this.number)
},
// 对象写法
...mapMutations({increment: 'ADD', decrement: 'SUB'}),
// 数组写法
...mapMutations(['ADD', 'SUB']),
}
Vuex 模块化&命名空间
让代码更好维护,让多种数据分类更加明确,每一类数据及其相关操作对应一个 store 。
// src/store/index.js
const countAbout = {
// 开启命名空间
namespaced: true,
state: {
sum: 0
},
actions: {...},
mutations: {...},
getters: {
bigSum(state) {
return state.sum * 10
}
}
}
const personAbout = {
// 开启命名空间
namespaced: true,
state: {
personList: []
},
actions: {...},
mutations: {...},
getters: {...}
}
export default new Vuex.Store({
modules: {
countAbout,
personAbout
}
})
开启命名空间后,组件中读取 state 数据:
// 直接读取
this.$store.state.personAbout.personList
// mapState 读取
...mapState('countAbout',['sum','school']),
开启命名空间后,组件中读取 getters 数据:
// 直接读取
this.$store.getters['countAbout/bigSum']
// mapGetters 读取:
...mapGetters('countAbout',['bigSum'])
开启命名空间后,组件中调用 dispatch
// 直接 dispatch
this.$store.dispatch('countAbout/addODdd', this.number)
// 借助 mapActions
...mapActions('countAbout', {incrementOdd:'addOdd', incrementWait:'addAsync'})
开启命名空间后,组件中调用 commit
// 直接 commit
this.$store.commit('personAbout/ADD_PERSON', person)
// 借助 mapMutations
...mapMutations('countAbout',{increment:'ADD',decrement:'SUB'}),