Vue3教程
1. 简介
🔍 深入探索Vue 3
想象一下,你正在用一台老旧的电脑玩最新的3D游戏大作。虽然游戏还能运行,但画面卡顿、加载缓慢,有时候甚至还会出现一些莫名其妙的小错误。这就是Vue 2在面对现代前端开发挑战时可能遇到的状况,而Vue 3就像是那台升级换代后的超级电脑,专为高效运行复杂应用而生。
不是Vue 2不好,而是Vue 3通过一系列创新和优化,为我们带来了更加优秀的开发体验和性能表现。随着前端技术的不断发展,选择Vue 3无疑是一个明智的决定。
虽然Vue3以其先进性吸引着众多开发者的目光,但在正式踏入这一新阶段之前,掌握Vue2作为基石,将是你快速且深入理解Vue3内容的宝贵财富,Vue3虽然带来了许多变革,但它也保留了Vue2中的许多优秀特性,并在此基础上进行了优化与扩展。
其实深入理解一项技术的最佳途径,莫过于直接查阅其官方文档,掌握有效查阅文档的能力,是每位合格程序员不可或缺的知识技能。
点击查阅官网进入学习 Vue.js
2. Vue2 选项式API vsVue3 组合式API
左为vue2,相对于vue2,vue3特点:代码量变少、分散式维护变成集中维护。
3. vue3优势
-
更容易维护
- 组合式API
- 更好的TypeScript支持
-
更快的速度
- 重写diff算法
- 模板编译优化
- 更高效的组件初始化
-
更小的体积
- 良好的TreeSnaking
- 按需引入
-
更优的数据响应式
- proxy
4. 搭建Vue3项目
4.1 create-vue
create-vue是Vue官方新的脚手架工具,底层切换到了 vite (下一代前端工具链),为开发提供极速响应
Vue2是在webpack构建工具基础上,使用脚手架Vue-CLI构建;
Vue3是在vite构建工具基础上,使用create-vue脚手架构建,
4.2 使用create-vue项目
需已安装16.0或更高版本的Node.js
node -v //查看版本号
(1)安装并执行 create-vue
在需要创建项目的目录终端下,执行如下命令:
npm init vue@latest
指令将会安装并执行 create-vue
(2)启动项目
npm run dev
注:若这里报’vite’ 不是内部或外部命令,也不是可运行的程序或批处理文件。
安装vite后启动项目
npm install vite --save-dev
4.3 vue3项目结构
main.js将创建实例进行了封装,vue2使用new()而vue3使用createApp(),createRouter(),createStore()创建实例,将实例进行封装,保证了每个实例的独立封闭性。
在组件文件里,将脚本script和模板template顺序进行调整,且在script上加上setup,表示允许在script中书写组合式API。
对于组件,vue3直接导入使用,相对于vue2的注册效率更高。
对于模板template不再要求唯一根元素。
插件的升级(vue2-vetur vue3-volar 插件提高了开发效率和代码的可读性,在vscode中, volar扩展已弃用。请改用Vue - Official扩展。
5. 组合式API
5.1 组合式API-setup
(1)语法规则
<script>
export default {
setup(){
},
beforeCreate(){
}
}
</script>
(2)语法特点
特点:setup在beforeCreate钩子之前执行;
注意:setup中的this指向组件实例,其指向undefined,原因是它的创建太早了(beforeCreate钩子之前执行)
使用:setup函数可以书写数据,函数,但需要在末尾以return的形式返回对象,才能给模板使用。
<script>
export default {
setup(){
// 数据
const message='我是vue3'
// 函数
const logMessage = ()=>{
console.log(message)
}
//return之后才能使用
return {
message,
logMessage
}
}
}
</script>
(3)语法糖
存在问题:每次都要return,太麻烦?
解决方法:使用语法糖:在script标签添加 setup标记,不需要再写导出语句,默认会添加导出语句。
5.2 组合式API-reactive和ref函数
(1)reactive函数
作用:接收对象类型数据的参数传入并返回一个响应式对象。
记住:接收的是对象类型数据
使用步骤:
<script setup>
// 导入
import {reactive} from 'vue'
// 传入对象参数
const state=reactive({
count:100
})
const addCount=()=>{
state.count++
}
</script>
<template>
<div>{{ state.count }}</div>
<button @click="addCount">+1</button>
</template>
(2)ref函数
作用:接收简单类型或者对象类型的数据传入并返回一个响应式的对象
本质:是在原有传入数据的基础上,外层包了一个对象,包为复杂对象。
底层:ref函数内部的实现依赖于reactive函数
<script setup>
// 导入
import { ref } from 'vue'
// 执行函数 传入参数 变量接收
const count = ref(0)
const setCount = ()=>{
// 修改数据更新视图必须加上.value
count.value++
}
</script>
<template>
<button @click="setCount">{{count}}</button>
</template>
注意:访问数据,如果是在脚本中访问数据,必须添加.value,在template中,不需要添加。
(3)reactive 对比 ref
- 两者都是用来生成响应式数据
- reactive不能处理简单类型的数据
- ref参数类型支持更好,但是必须通过.value做访问修改
- ref函数内部的实现依赖于reactive函数。
- 实际使用,推荐使用ref函数
5.3 组合式API-computed
(1)语法规则
<script setup>
// 导入
import {ref,computed} from 'vue'
// 执行函数 变量接收,在回调函数中return计算值
const computedState=computed(()=>{
return 基于响应式数据做计算之后的值
})
(2)示例
<script setup>
// 导入
import {ref,computed} from 'vue'
// 原始数据
const list=ref(
[1,2,3,4,5,6,7,8]
)
// 从list中 过滤出>2
const filterList = computed(()=>{
return list.value.filter(item=>item>2)
})
</script>
<template>
<div>{{ filterList }}</div>
<!-- [3,4,5,6,7,8] -->
</template>
5.4 组合式API-watch函数
作用:侦听一个或多个数据的变化,数据变化时执行回调函数
两个额外参数:immediate(立即执行)deep(深度侦听)
(1)侦听单个数据
<script setup>
// 导入watch
import { ref, watch } from 'vue'
const count=ref(0)
// 调用watch 侦听count的变化
watch(count,(newValue,oldValue)=>{
console.log(`count变化了,老值为${oldValue},新值为${newValue}`);
})
const addCount=()=>{count.value++}
</script>
<template>
<div></div>
<button @click="addCount">count+1</button>
</template>
结果:点击一次按钮,数据变化了,执行watch函数,故控制台输出:count变化了,老值为0,新值为1
(2)侦听多个数据
侦听多个数据,第一个参数可以改写成数组的写法。
<script setup>
// 导入watch
import { ref, watch } from 'vue'
const count=ref(0)
const name = ref('zs')
// 调用watch 侦听count的变化
watch([count, name], ([newCount, newName],[oldCount,oldName])=>{
console.log('count或者name变化了'+" "+[newCount,newName],[oldCount,oldName]);
})
const addCount=()=>{
count.value++
}
const setName=()=>{
name.value='ys'
}
</script>
<template>
<button @click="addCount">count+1</button>
<button @click="setName">修改name</button>
</template>
(3)immediate
在侦听器创建时立即触发回调,响应式数据变化之后继续执行回调。
<script setup>
// 1. 导入watch
import { ref, watch } from 'vue'
const count = ref(0)
// 2. 调用watch 侦听变化
watch(count, (newValue, oldValue)=>{
console.log(`count发生了变化,老值为${oldValue},新值为${newValue}`)
},{
immediate: true
})
</script>
(4)deep
通过watch监听的ref对象默认是浅层侦听的,直接修改嵌套的对象属性不会触发回调执行,需要开启deep.
const ref1=ref(简单类型) 可以直接进行监视
const ref2=ref(复杂类型)监视不到复杂类型内部数据的变化。
const userInfo=ref({
name:'zs',
age:19
})
const setUserInfo=()=>{
userInfo.value.name='ls'
}
// 复杂类型的侦听
watch(userInfo,()=>{
console.log('数据变化了');
})
</script>
<template>
<button @click="setUserInfo">修改复杂对象</button>
</template>
此时点击按钮,没有触发侦听,需要添加深度deep侦听
// 复杂类型的侦听
watch(userInfo,()=>{
console.log('数据变化了');
},{deep:true})
(5)精确侦听对象某个属性
可以在不开启deep的前提下,侦听对象具体的某个属性。如有对象userInfo里有age属性,只对age进行侦听
watch(()=>userInfo.value.age,()=>{//侦听回调函数})
5.5 组合式API-生命周期函数
选项式API | 组合式API |
---|---|
beforeCreate/created | setup |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeUnmount | onBeforeUnmount |
unmounted | onUnmounted |
(1)基本使用
<scirpt setup>
//导入函数
import { onMounted } from 'vue'
//执行生命周期函数,传入回调
onMounted(()=>{
// 自定义逻辑
})
</script>
生命周期函数可以执行多次,但会按照顺序依次执行。
<scirpt setup>
import { onMounted } from 'vue'
onMounted(()=>{
// 自定义逻辑
})
onMounted(()=>{
// 自定义逻辑
})
</script>
5.6 组合式API-组件通信
(1)父传子
- 使用步骤:
- 父组件给子组件绑定属性。
- 子组件内部通过props选项接收。
注意:子组件通过定义使用defineProps编译器宏‘接收传递过来的数据
defineProps原理:就是编译阶段的一个标识,实际编译器解析时,遇到后会进行编译转换,本质还是props。
(2)子传父
- 使用步骤:
- 父组件给子组件标签通过@绑定事件
- 子组件内部通过emit方法触发事件
5.7 组合式API-模板引用
模板引用:通过 ref标识 获取真实的 dom对象或者组件实例对象。
(1)基本使用
- 调用ref函数生成一个ref对象
- 通过ref标识绑定ref对象到标签
<script setup>
import { onMounted, ref } from "vue"
// 调用ref'函数声明对象
const h1Ref=ref(null)
onMounted(()=>{
console.log(h1Ref.value);//我是dom标签h1
})
</script>
<template>
<h1 ref="h1Ref">我是dom标签h1
</h1>
</template>
(2) defineExpose
默认情况下在 script setup语法糖下组件内部的属性和方法是不开放给父组件访问的,可以通过defineExpose编译宏指定哪些属性和方法容许访问
5.8 组合式API-provide和inject
顶层组件向任意的底层组件传递数据和方法,实现跨层组件通信
(1)跨层传递普通数据
- 使用步骤:
- 顶层组件通过provide提供数据
- 底层组件通过inject函数获取数据
顶层数据
provide('key',顶层组件中的数据)
底层数据
const message=inject('key')
(2)跨层传递响应式数据
在调用provide函数时,第二个参数设置为ref对象
顶层数据
provide('key',ref对象)
底层数据
const message=inject('key')
(3)跨层传递函数
注意:数据传递遵循谁的数据谁负责的原则
跨层传递函数->子孙后代组件传递可以修改数据的方法
顶层组件可以向底层组件传递方法,底层组件调用方法修改顶层组件的数据
//顶层组件提供函数
provide('changeCount',(newCount)=>{
count.value=newCount})
}
//底层使用
const changeCount=inject('changeCount')
//底层函数调用
const clickFn=()=>{
changeCount(1000)
}
6. Vue3.3 新特性
6.1 defineOptions
在引入setup 之前,如果要定义 props, emits 可以直接添加属性。
<script>
export default{
name:'',
props:'',
// ...
}
</script>
但添加了setup之后,无法设置平级属性。
所以在 Vue 3.3 中新引入了 defineOptions 宏。顾名思义,主要是用来定义 Options API 的选项。可以用 defineOptions 定义任意的选项, props, emits, expose, slots 除外(因为这些可以使用 defineXXX 来做到)
<script setup>
defineOptions({
name:'LoginPage',
inheritAttrs:false
//...更多自定义属性
})
defineProps({
count:Number,
//...
})
//...其他definexxx
</script>
6.2 defineModel
v-model 可以在组件上使用以实现双向绑定。
从 Vue 3.4 开始,推荐的实现方式是使用 defineModel() 宏:
App.vue
<script setup>
import Child from './Child.vue'
import { ref } from 'vue'
const msg = ref('Hello World!')
</script>
<template>
<h1>{{ msg }}</h1>
<Child v-model="msg" />
</template>
child.vue
<script setup>
const model = defineModel()
</script>
<template>
<span>My input</span> <input v-model="model">
</template>
7. Pinia-状态管理工具
Pinia是vuex最新状态管理工具,是vuex的替代品。
- Pinia优势:
- 通过了更加简单的API,(去掉了mutation);
- 提供符合组合式风格的API(和Vue3新语法统一);
- 去掉了modules的概念,每一个store都是一个独立的模块;
- 配合TypeScript更加友好,提供可靠的类型判断;
7.1 安装使用
(1)安装Pinia
yarn add pinia
或者使用 npm
npm install pinia
(2)创建实例
创建一个 pinia 实例 (根 store) 并将其传递给应用:
main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const pinia = createPinia()//创建pinia实例
const app = createApp(App)//创建根实例
app.use(pinia)
app.mount('#app')
7.2 核心概念
(1)定义store
Store 是用 defineStore() 定义的,它的第一个参数要求是一个独一无二的名字。
import { defineStore } from 'pinia'
// 你可以任意命名 `defineStore()` 的返回值,但最好使用 store 的名字,同时以 `use` 开头且以 `Store` 结尾。
// (比如 `useUserStore`,`useCartStore`,`useProductStore`)
// 第一个参数是你的应用中 Store 的唯一 ID。
export const useAlertsStore = defineStore('alerts', {
// 其他配置...
})
defineStore() 的第二个参数可接受两类值:Setup 函数或 Option 对象。
- Option对象
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
getters: {
double: (state) => state.count * 2,
},
actions: {
increment() {
this.count++
},
},
})
可以认为 state 是 store 的数据 (data),getters 是 store 的计算属性 (computed),而 actions 则是方法 (methods)。
- setup函数
与 Vue 组合式 API 的 setup 函数 相似,我们可以传入一个函数,该函数定义了一些响应式属性和方法,并且返回一个带有我们想暴露出去的属性和方法的对象。
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})
在 Setup Store 中:ref() 就是 state 属性,computed() 就是 getters,function() 就是 actions。
推荐使用SetUp函数
(2) getters实现
Pinia中的 getters 直接使用 computed函数 进行模拟, 组件中需要使用需要把 getters return出去
const doubleCount=computed(()=>{
return count.value*2
return {
doubleCount
}
})
(3)action异步实现
方式:异步action函数的写法和组件中获取异步数据的写法完全一致。
// 异步action
const getList=async()=>{
const res=await axios.get('xxx')
//向外暴露
return {
getList
}
}
(4)示例使用
新建store/counter.js
import {defineStore} from 'pinia'
import { ref } from 'vue'
// 1.定义store 参数:id,setup函数
export const userCountStore=defineStore('counter',()=>{
// 2.声明数据 state
const count=ref(0)
// 3.声明操作数据的方法 相对于action
function addCount(){
count.value++
}
// 可声明基于数据派生的计算属性getters
//Pinia中的 getters 直接使用 computed函数 进行模拟, 组件中需要使用需要把 getters return出去
const doubleCount=computed(()=>{
return count.value*2
})
// 声明数据2:
const msg=ref('hello Pinia')
// return暴露使用
return{
count,
msg,
addCount
}
})
在组件中使用
App.vue
<script setup>
// 使用pinia
import {userCountStore} from '@/store/counter'
const countStore=userCountStore()
console.log(countStore);//对象
</script>
<template>
<h1> 我是根组件
{{ countStore.count }}
</h1>
<div> {{ countStore.msg }}</div>
<button @click="countStore.addCount">+</button>
</template>
7.3 storeToRefs工具函数
定义了store。在组件中进行使用,如果使用解构来使用store里面的数据,那么会存在响应丢失的问题
//响应式丢失,视图不再更新
const{count,msg,doubleCount}=countStore
若想解构之后继续保持数据的响应式,借助storeToRefs工具函数
const{count,msg}=storeToRefs(countStore)
而对于store中的方法,可以直接解构
//作为action的increment可以直接解构使用
const {increment}=store
7.4 Pinia-持久化
之前对数据的持久化,是使用localstorage进行封装,现在pinia支持持久化插件,直接使用。
可查阅插件的官方文档:Pinia持久化插件
(1)基本使用
- 安装插件
npm i pinia-plugin-persistedstate
- 将插件添加到 pinia 实例上
main.js
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
- 创建 Store 时,将 persist 选项设置为 true。
组合式
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useStore = defineStore(
'main',
() => {
const someState = ref('你好 pinia')
return { someState }
},
{
persist: true,
},
)
选项式语法
import { defineStore } from 'pinia'
export const useStore = defineStore('main', {
state: () => {
return {
someState: '你好 pinia',
}
},
persist: true,
})
现在,Store 将使用默认持久化配置保存。
(2)持久化配置
插件使用默认持久化配置,也可以进行自定义。
-
插件的默认配置为:
- 使用 localStorage 进行存储
- store.$id 作为 storage 默认的 key
- 使用 JSON.stringify/JSON.parse 进行序列化/反序列化
- 整个 state 默认将被持久化
-
自定义配置
将一个对象传递给 Store 的 persist 属性来配置持久化。
import { defineStore } from 'pinia'
export const useStore = defineStore('main', {
state: () => ({
someState: '你好 pinia',
}),
persist: {
// 在这里进行自定义配置
key: 'my-custom-key',
storage: sessionStorage,
//...配置项查看官网说明
},
},
})
- 自定义配置项
可查阅官方文档说明:配置说明