Vue3入门
认识 Vue3
目录
Vue2 选项式 API vs Vue3 组合式 API
- 之前都是 Vue2 选项式 API(Options API)
- 即有 data、methods、computed、watch 等选项,要编写一个功能,就把所需要编写的部分一个个分散到这几个选项中
- 一旦代码量多了之后,维护起来一一对应就很麻烦
- 功能代码发散,不便复用
- 之后就是 vue3 组合式 API(Composition API)
- 把一个功能调用到的代码都写在一起
- 集中式管理,便于维护
- 想要复用的功能代码集中,便于一个个封装为函数
Vue3 的优势
- vue3 的整个源码都用 TS 重写了(友好)
使用 create-vue 搭建 Vue3 项目
认识 create-vue
- create-vue 是 Vue 官方新的脚手架工具,底层切换到了 vite(下一代前端工具链),为开发提供极速响应
- 原来 vue2 是用的 vue-cli + webpack
- 现在 vue3:
- vue-cli ——> create-vue
- webpack ——> vite
使用 create-vue 创建项目
- 前置条件:已安装 16.0 或更高版本的 Node.js
- 执行如下命令,这一指令将会安装并执行 create-vue
npm init vue@latest
- 然后就会显示
Project name
、Add TypeScript
......- 可以全选 No(无碍),也可以
Add ESLint for code quality
时左右键选择 yes
- 可以全选 No(无碍),也可以
- 之后根据显示的
Done. Now run
后面的指示来打开(cd)、安装(npm install)、运行(npm run dev)
熟悉项目和关键文件
- vue3 插件也从 2 的 Vetur 换成了 3 的 volar(Vue Language Feature)
- VS 用到的
组合式 API — setup 选项
setup 选项的写法和执行时机
- 写法
<script>
export default {
setup(){
// 因为太早所以没有this.xxx的写法,即this是undefined
},
beforeCreate(){
}
}
</script>
- 执行时机:比 beforeCreate 钩子还早执行
setup 中写代码的特点
- 在 setup 函数中写的数据和方法需要在末尾以对象的方式 return,才能给模版使用
<script>
export default {
setup(){
const message = 'this is message'
const logMessage = ()=>{
console.log(message)
}
// 必须return才可以
return {
message,
logMessage
}
}
}
</script>
- 不管是事件还是数据等都要 return
- 这样感觉有些麻烦,所以有了 setup 语法糖:
<script setup>
语法糖
- script 标签添加 setup 标记,不需要再写导出语句,默认会添加导出语句
- 底层还是在表面看不到的地方帮助 return 了
<script setup>
const message = 'this is message'
const logMessage = ()=>{
console.log(message)
}
</script>
组合式 API — reactive 和 ref 函数
reactive
- 接受对象类型数据的参数传入并返回一个响应式的对象
<script setup>
// 导入
import { reactive } from 'vue'
// 执行函数 传入参数 变量接收
const state = reactive({
msg:'this is msg'
})
const setSate = ()=>{
// 修改数据更新视图
state.msg = 'this is new msg'
}
</script>
<template>
{{ state.msg }}
<button @click="setState">change msg</button>
</template>
- 要求必须是对象类型
ref
- 接收简单类型或者对象类型的数据传入并返回一个响应式的对象
<script setup>
// 导入
import { ref } from 'vue'
// 执行函数 传入参数 变量接收
const count = ref(0)
const setCount = ()=>{
// 修改数据更新视图必须加上.value
count.value++
}
</script>
<template>
<button @click="setCount">{{count}}</button>
</template>
- 打印出来可见其实是一个对象,之所以可以让简单类型也变成响应式的本质也就是:在原有传入数据的基础上外层包了一层对象
- 底层就是被包成了复杂类型的了
- 所以理应访问数据就要 .value 访问获取(如在 js 部分)
- 但是在 template 里不需要 .value 获取,因为已经帮助扒了一层
reactive 对比 ref
- 都是用来生成响应式数据
- 不同点
- reactive 不能处理简单类型的数据
- ref 参数类型支持更好,但是必须通过 .value 做访问修改
- ref 函数内部的实现依赖于 reactive 函数
- 在实际工作中推荐使用 ref 函数
组合式 API — computed
- 计算属性基本思想和 Vue2 保持一致,组合式 API 下的计算属性只是修改了 API 写法
<script setup>
// 导入
import {ref, computed } from 'vue'
// 原始数据
const count = ref(0)
// 计算属性,写法一
const doubleCount = computed(() => count.value * 2)
// 也可以写法二
const doubleCount = computed(() => {
return count.value * 2
})
// 原始数据
const list = ref([1,2,3,4,5,6,7,8])
// 计算属性list
const filterList = computed(item => item > 2)
</script>
组合式 API — watch
- 侦听一个或者多个数据的变化,数据变化时执行回调函数,俩个额外参数 immediate 控制立刻执行,deep 开启深度侦听
侦听单个数据
<script setup>
// 1. 导入watch
import { ref, watch } from 'vue'
const count = ref(0)
// 2. 调用watch 侦听变化
watch(count, (newValue, oldValue)=>{
console.log(`count发生了变化,老值为${oldValue},新值为${newValue}`)
})
</script>
侦听多个数据
- 侦听多个数据,第一个参数可以改写成数组的写法
<script setup>
// 1. 导入watch
import { ref, watch } from 'vue'
const count = ref(0)
const name = ref('cp')
// 2. 调用watch 侦听变化
watch([count, name], ([newCount, newName],[oldCount,oldName])=>{
console.log(`count或者name变化了,[newCount, newName],[oldCount,oldName]`)
})
</script>
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>
deep
- 通过 watch 监听的 ref 对象默认是浅层侦听的,直接修改嵌套的对象属性不会触发回调执行,需要开启 deep
- 即:不加 deep 的话,对象属性修改内部属性则不会引起监听的
- 除非是直接更改了对象的 .value = 新的对象才会被浅层侦听
<script setup>
// 1. 导入watch
import { ref, watch } from 'vue'
const state = ref({ count: 0 })
// 2. 监听对象state
watch(state, ()=>{
console.log('数据变化了')
})
const changeStateByCount = ()=>{
// 直接修改不会引发回调执行
state.value.count++
}
</script>
<script setup>
// 1. 导入watch
import { ref, watch } from 'vue'
const state = ref({ count: 0 })
// 2. 监听对象state 并开启deep
watch(state, ()=>{
console.log('数据变化了')
},{deep:true})
const changeStateByCount = ()=>{
// 此时修改可以触发回调
state.value.count++
}
</script>
不用 deep 只侦听某对象的指定的某个属性
const info = ref({
name: 'cp',
age: 18
})
watch(() => info.value.age, (newValue, oldValue) => console.log(newValue, oldValue)
)
- 只侦听 info 对象里的 age 属性
组合式 API — 生命周期函数
选项式对比组合式
- 左右替换
生命周期函数基本使用
- 导入生命周期函数
- 执行生命周期函数,传入回调
<scirpt setup>
// 这里的语句就是原来写 created 里的
import { onMounted } from 'vue'
onMounted(()=>{ // 这里就是原写 mounted 里的
// 自定义逻辑
})
</script>
执行多次
- 生命周期函数执行多次的时候,会按照顺序依次执行,不会冲突
<scirpt setup>
import { onMounted } from 'vue'
onMounted(()=>{
// 自定义逻辑1
})
onMounted(()=>{
// 自定义逻辑2
})
</script>
组合式 API — 父子通信
父传子
-
原来 vue 写 props 的地方就没有了,所以就采用下述 defineProps 的写法
- 实际上也是宏编译到了原来的地方
- 父也不用注册,引入子组件后直接使用即可
-
基本思想
- 父组件中给子组件绑定属性(添加属性的方式传值,下图例子因为是常量不是动态变量所以不用加冒号)
- 子组件内部通过 props 选项接收数据
- 传到子的获取后直接使用即可
- defineProps 原理:就是编译阶段的一个标识,实际编译器解析时,遇到后会进行编译转换
子传父
-
原来是用 this,但 setup 里没有,所以要用下图 defineEmits 的方式
-
基本思想
- 父组件中给子组件标签通过 @ 绑定事件
- 子组件内部通过 emit 方法触发事件
组合式 API — 模版引用
- 概念:通过 ref 标识获取真实的 dom 对象或者组件实例对象
基本使用
-
原是用 this.$refs. 来获取的,但 vue3 中没有 this 就得用下述方法
-
实现步骤:
- 调用 ref 函数生成一个 ref 对象
- 通过 ref 标识绑定 ref 对象到标签
-
只要同名即可绑定上,就可以用 h1Ref.value 访问到绑定的元素(必须渲染完成后才能拿到)
-
一定要注意:上图中若是在 const h1Ref 下一行直接编写
console.log(h1Ref.value)
打印结果是 null(因为还没渲染完)-
但是可以使用生命周期钩子
onMounted()
:onMounted(()=>{ console.log(h1Ref.value) // 就可以获取到 <h1>...</h1> })
-
-
同理也可以绑定获取组件
- 但是现在并不能拿到绑定了的组件内的数据事件等,想要获取就需要用下述方法
defineExpose
- 默认情况下在
<script setup>
语法糖下组件内部的属性和方法是不开放给父组件访问的,可以通过 defineExpose 编译宏指定哪些属性和方法容许访问- 说明:如下图指定的 testMessage 属性可以被访问到
- 即:把想要被父获取访问的都写到 defineExpose 中
组合式 API — provide 和 inject
作用和场景
- 顶层组件向任意的底层组件传递数据和方法,实现跨层组件通信
跨层传递普通数据
- 实现步骤
- 顶层组件通过
provide
函数提供数据 - 底层组件通过
inject
函数提供数据
- 顶层组件通过
跨层传递响应式数据
- 在调用 provide 函数时,第二个参数设置为 ref 对象的对象名
跨层传递方法
-
顶层组件可以向底层组件传递方法,底层组件调用方法修改顶层组件的数据(靠跨层级传递函数,控制权实际还是在父手中)
- 注意:底层不能直接修改顶层的数据
// 父中 const count = ref(10) provide('changeCount', (newCount) => { count.value = newCount }) // 子中 const changeCount = inject('changeCount') const 某事件中 =()=> { changeCount(1000) }
- 当事件触发时,就会返到顶层更改数据,底层获取时也就随之更改
Vue3.3 新特性 — defineOptions
-
有
<script setup>
之前,如果要定义 props,emits 可以轻而易举地添加一个与 setup 平级的属性- 但是用了
<script setup>
后,就没法这么干了 setup 属性已经没有了,自然无法添加与其平级的属性
- 但是用了
-
为了解决这一问题,引入了 defineProps 与 defineEmits 这两个宏
- 但这只解决了 props 与 emits 这两个属性。
-
如果我们要定义组件的 name 或其他自定义的属性,还是得回到最原始的用法 —— 再添加一个普通的
<script>
标签-
这样就会存在两个
<script>
标签:<script> export default { name: 'LoginIndex' } </script> <script setup> ...... </script>
-
-
所以在 Vue 3.3 中新引入了 defineOptions 宏。顾名思义,主要是用来定义 Options API 的选项
- 可以用 defineOptions 定义任意的选项,props、emits、expose、slots 除外(因为这些可以使用 defineXXX 来做到)
Vue3.3 新特性 — defineModel
-
此特性暂时还是实验特性,可考虑使用
-
在 Vue3 中,自定义组件上使用 v-model,相当于传递一个 modelValue 属性,同时触发 update:modelValue 事件
-
我们需要先在子里定义 props,再定义 emits
- 就需要 defineProps、defineEmits
- 其中有许多重复的代码
- 如果需要修改此值,还需要手动调用 emit 函数。
-
于是就有了 defineModel
- 甚至不需要写 emit 了
- 但若想生效需要配置 vite.config.js
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue({
script: {
defineModel: true // 这里要开启
}
}),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})