目录
0前言
vue3官网:Vue.js - 渐进式 JavaScript 框架 | Vue.js
请多阅读官方文档
黑马程序员视频地址:140-vue3-为什么要学vue3_哔哩哔哩_bilibili
8.3、8.4以及11.2若有错误请指正,感谢!!!
1 使用create-vue创建项目
1.1 确认node版本
node -v
版本要在v18.3.0及以上(随着create-vue版本迭代,可能需要更高版本的node)
官网:Node.js — Download Node.js®,更多细节见 node.js安装-CSDN博客
1.2 执行如下命令,这一指令将会安装并执行 create-vue
npm init vue@latest
2 项目配置
2.1 更改vscode插件
禁用 vetur ,改为 volar(即Vue - Official)
3 setup选项
3.1 setup写法
完整写法:(不推荐)
<script>
export default {
setup(){
const message = 'this is message'
const logMessage = ()=>{
console.log(message)
}
// 必须return才可以
return {
message,
logMessage
}
}
}
</script>
语法糖:(推荐)
<script setup>
const message = 'this is message'
const logMessage = ()=>{
console.log(message)
}
</script>
3.2 执行时机
在beforeCreate钩子之前执行,非常早,以至于没有this指向组件实例
4 响应式数据
setup内声明的数据不是响应式的,可以使用reactive与ref函数来改成响应式的
4.1 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>
4.2 ref函数
注意:既支持对象类型的数据,也支持简单类型的数据
机理:接收简单类型或者对象类型的数据传入并返回一个响应式的对象
<script setup>
// 导入
import { ref } from 'vue'
// 执行函数 传入参数 变量接收
const count = ref(0)
const setCount = ()=>{
// 修改数据更新视图必须加上.value
count.value++
}
</script>
<template>
<button @click="setCount">{{count}}</button>
</template>
注意点:
1. 在 script 中,要通过 变量名.value 访问数据(简单类型与对象类型同理,简单类型是把数据传给响应式对象的value属性中,而对象数据类型会把整个对象传入响应式对象的value属性中)
2. 在 template 中,可以直接直接使用变量名访问数据
4.3 对比
1. 都是用来生成响应式数据
2. 不同点
1. reactive不能处理简单类型的数据
2. ref参数类型支持更好,但是必须通过.value做访问修改
3. ref函数内部的实现依赖于reactive函数
3. 在实际工作中的推荐
1. 推荐使用ref函数,减少记忆负担,小兔鲜项目都使用ref
5 computed 计算属性
示例:
<script setup>
// 导入
import {ref, computed } from 'vue'
// 原始数据
const count = ref(0)
// 计算属性
const doubleCount = computed(()=>count.value + 2)
console.log(doubleCount.value)
// 原始数据
const list = ref([1,2,3,4,5,6,7,8])
// 计算属性list
const filterList = computed(() => {
return list.value.filter(item => item > 2)
})
</script>
<template>
<div>原数组:{{ list }}</div>
<div>新数组:{{ filterList }}</div>
</template>
注意:
1.避免直接修改计算属性中的值,如果真要修改,请见文档使用方法:响应式 API:核心 | Vue.js
2. 避免在计算属性中使用dom操作或异步请求
示例:
创建一个只读的计算属性 ref:
const count = ref(1)
const plusOne = computed(() => count.value + 1)
console.log(plusOne.value) // 2
plusOne.value++ // 错误
创建一个可写的计算属性 ref:
const count = ref(1)
const plusOne = computed({
get: () => count.value + 1,
set: (val) => {
count.value = val - 1
}
})
plusOne.value = 1
console.log(count.value) // 0
6 watch 侦听数据变化
6.1 侦听单个数据
<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>
6.2 侦听多个数据
<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>
6.3 额外配置
6.3.1 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>
6.3.2 deep
通过watch监听的ref对象默认是浅层侦听的,直接修改嵌套的对象属性不会触发回调执行,需要开启deep
<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,只要把state改写成getters的形式 () => state.value,即可
watch(state, callback)
:这种方式直接将ref
对象作为监听目标。Vue 会对ref
对象本身的引用变化进行监听。只有当整个ref
对象被替换成一个新的ref
时,监听器才会触发。watch(() => state, callback)
:这里传入的是一个返回ref
对象的箭头函数。本质上和直接传入state
类似,同样是监听ref
对象本身的引用变化。watch(() => state.value, callback)
:此方式传入的箭头函数返回ref
对象的value
属性值。所以它监听的是ref
对象所存储的实际数据的变化,只要value
属性值发生改变,监听器就会触发。
示例见 6.4(6.4本质上是把state换成了 () => state.value.count的getters写法)
6.4 侦听对象数据的某一个属性
开启deep,会侦查整个对象数据,任意属性值都会触发回调函数,如果只想要侦听某个属性,则需要下面的写法:
const state = ref({ count: 0 })
watch(
() => state.value.count,
(newvalue, oldvalue) => {
/* ... */
}
)
如果要侦听某两个或多个属性,则可以通过一下两种方法:
1.使用数组
const state = ref({ count: 0, name: 'initial' })
watch(
[
() => state.value.count, // 第一个属性
() => state.value.name // 第二个属性
],
([newCount, newName], [oldCount, oldName]) => {
// 新值数组 [newCount, newName]
// 旧值数组 [oldCount, oldName]
console.log('count 变化:', newCount, 'old:', oldCount)
console.log('name 变化:', newName, 'old:', oldName)
}
)
2. 分别编写独立的侦听器
const state = ref({
count1: 0,
count2: 1,
count3: 2
})
watch(
() => state.value.count1,
(newvalue, oldvalue) => {
/* ... */
}
)
watch(
() => state.value.count2,
(newvalue, oldvalue) => {
/* ... */
}
)
7 生命周期函数
7.1 选项式API与组合式API对比
vue3中的选项式api由distoryed变成了unmounted
7.2 生命周期函数的使用
-
导入生命周期函数
-
执行生命周期函数,传入回调
<scirpt setup>
import { onMounted } from 'vue'
onMounted(()=>{
// 自定义逻辑
})
</script>
7.3 执行多次
生命周期函数执行多次的时候,会按照顺序依次执行
<scirpt setup>
import { onMounted } from 'vue'
onMounted(()=>{
// 自定义逻辑
})
onMounted(()=>{
// 自定义逻辑
})
</script>
8 组件通信 - 父子
8.1 父传子
第一步:在父组件中给子组件绑定值
<script setup>
// 父组件
// 导入子组件
import sonCom from './components/son-com.vue';
// 导入 ref 函数
import { ref } from 'vue'
// 定义 money 变量
const money = ref(100)
</script>
<template>
<div>
我是父组件
<sonCom :money="money"></sonCom>
</div>
</template>
<style scoped>
</style>
第二步:在子组件中编译器宏生成 props 并接收数据
<script setup>
// 子组件
// 编译器宏生成 props 并接收数据
const props = defineProps({
money: Number
})
// 打印money
console.log(props.money)
</script>
<template>
<div class="son">
我是子组件 - {{ money }}
</div>
</template>
<style scoped>
.son{
border: 1px solid black;
width: 300px;
height: 300px;
}
</style>
8.2 子传父
第一步:子组件编译器宏生成 emit 函数,并触发事件,传值
<script setup>
// 子组件
const props = defineProps({
money: Number
})
console.log(props)
// ————————————————————————————————————————
// 1、编译器宏生成 emit
const emit = defineEmits(['spendMoney'])
// 2、触发事件,传数据
const buy = () => {
emit('spendMoney', 10)
}
</script>
<template>
<div class="son">
我是子组件 - {{ money }}
<button @click="buy">花钱</button>
</div>
</template>
<style scoped>
.son{
border: 1px solid black;
width: 300px;
height: 300px;
}
</style>
第二步:父组件监听事件,触发函数
<script setup>
// 父组件
import sonCom from './components/son-com.vue';
import { ref } from 'vue'
const money = ref(100)
// ——————————————————————————————————————
// 定义方法
const spendMoney = (num) => {
money.value -= num
}
</script>
<template>
<div>
我是父组件
<!--监听 spendMoney 事件,并触发相应方法-->
<sonCom :money="money" @spendMoney="spendMoney"></sonCom>
</div>
</template>
<style scoped>
</style>
8.3 v-model
用 v-model 简化父组件中的写法
缺点:属性值与监听事件固定,解决方法见8.4
vue2中的v-model本质上是:value和@input
而vue3变成了:modelValue和@update:modelValue
写法如下:
<!--app.vue-->
<script setup>
import model from './components/model.vue'
import { ref } from 'vue'
const num = ref(1)
</script>
<template>
<div>
我是父组件
</div>
<model v-model="num"></model>
</template>
<!--model.vue-->
<script setup>
defineOptions({
name: 'modelPage'
})
defineProps({
modelValue: Number
})
const emit = defineEmits(['@update:modelVale'])
</script>
<template>
<div>
从父组件获取的数据 - {{ modelValue }}
<button @click="emit('update:modelValue', modelValue + 1)">
子组件按钮 - 加1
</button>
</div>
</template>
在父组件中,v-model等效于
<model :modelValue="num" @update:modelValue="num = $event" />
<!--$event为子组件传过来的值-->
可以发现,默认监听事件会执行赋值操作
如果不想要只做赋值操作,可以再在父组件中单独写监听update:modelValue事件,如
<!--app.vue-->
<script setup>
import model from './components/model.vue'
import { ref } from 'vue'
const num = ref(1)
const changeNum = (newNum) => { // newNum即子组件传过来的修改过的值
// 可以先执行其他操作
num.value = newNum // 这里才真正把父组件中的值进行修改
}
</script>
<template>
<div>
我是父组件
</div>
<model v-model="num" @update:modelValue="changeNum"></model>
</template>
8.4 v-model:自定义属性名
在vue3中,可以直接通过写v-model:属性名进行设置自定义属性名,如
<!--app.vue-->
<script setup>
import model from './components/model.vue'
import { ref } from 'vue'
const num = ref(1)
const changeNum = (newNum) => {
num = newNum
}
</script>
<template>
<div>
我是父组件
</div>
<model v-model:number="num" @update:number="changeNum"></model>
</template>
<!--model.vue-->
<script setup>
defineOptions({
name: 'modelPage'
})
defineProps({
number: Number
})
const emit = defineEmits(['@update:number'])
</script>
<template>
<div>
从父组件获取的数据 - {{ number }}
<button @click="emit('update:number', number + 1)">
子组件按钮 - 加1
</button>
</div>
</template>
9 模板引用
9.1 获取元素
第一步:创建一个空 ref 对象
import { ref } from 'vue'
const getEle = ref(null)
第二步:给元素绑定 ref 属性
<span ref="getEle">{{ num }}</span>
注意:ref 的值要与 ref 对象变量名同名
9.2 获取组件
与获取元素一样
第一步:创建一个空 ref 对象
import { ref } from 'vue'
const getCom = ref(null)
第二步:给组件绑定 ref 属性
<son ref="getCom"></son>
注意: <script setup>语法糖下组件内部的属性与方法是不开放给父组件访问的,需要在组件内使用defineExpose编译宏,如:
<script setup>
import {ref} from 'vue'
const num = ref(1)
defineExpose({
num
})
</script>
10 跨层级通信
10.1 向后代传值
第一步:父组件通过 provide 提供数据
<script setup>
import { provide } from 'vue'
import sonOne from '@/components/sonOne.vue'
const a = 1
provide('num', a)
</script>
传响应式数据也一样
第二步:后代组件通过 inject 获取数据
<script setup>
import { inject } from 'vue'
const num = inject('num')
</script>
10.2 后代修改值(向后代传函数方法)
遵循谁的数据谁维护,所以传方法下去,让其调用父组件的方法来修改父组件的数据
步骤一:父组件通过 provide传函数方法
<script setup>
import { provide } from 'vue'
import sonOne from '@/components/sonOne.vue'
const sayHi = () => {
console.log('你好')
}
provide('key', sayHi)
</script>
第二步:后代组件通过 inject 获取函数方法
// sonTwo.vue
<script setup>
import { inject } from 'vue'
const sayHi = inject('key')
</script>
步骤三:调用函数方法
<!--sonTwo.vue-->
<template>
<div> 我是子组件2 </div>
<button @click="sayHi">sayHi</button>
</template>
11 新特性
11.1 defineOptions
由于用了setup语法糖,没有地方配置name等属性,所以官方提供了这个方法
可以用 defineOptions 定义任意的选项, props, emits, expose, slots 除外(因为这些可以使用 defineXXX 来做到)
<script setup>
defineOptions({
name: 'indexPage'
// ...
})
</script>
11.2 defineModel
8.3、8.4中的方法还是过于麻烦,可以使用 defineModel 简化子组件中的代码
8.3 默认写法 (:modelValue 与 @update:modelValue):
父组件不用改内容:
<!--app.vue-->
<script setup>
import model from './components/model.vue'
import { ref } from 'vue'
const num = ref(1)
const changeNum = (newNum) => { // newNum即子组件传过来的修改过的值
num.value = newNum // 这里才真正把父组件中的值进行修改
}
</script>
<template>
<div>
我是父组件
</div>
<model v-model="num" @update:modelValue="changeNum"></model>
</template>
子组件代码如下:
发现不用写defineProps与defineEmits,因为defineModel都会帮我们处理
<!--model.vue-->
<script setup>
defineOptions({
name: 'modelPage'
})
const num = defineModel() // 默认会找modelValue,这里的num是随便命名的
console.log(num.value)
</script>
<template>
<div>
从父组件获取的数据 - {{ num }}
<button @click="num++"> <!--这里并不会真正修改父组件中的值,而是将修改后的值传给父组件-->
子组件按钮 - 加1
</button>
</div>
</template>
定义的num只要被修改,就会触发父组件中的监听函数
8.4 自定义写法:同理,只需要在defineModel括号中额外传属性名称即可
更多参数见官网:<script setup> | Vue.js
<!--app.vue-->
<script setup>
import model from './components/model.vue'
import { ref } from 'vue'
const num = ref(1)
const changeNum = (newNum) => {
num = newNum
}
</script>
<template>
<div>
我是父组件
</div>
<model v-model:number="num" @update:number="changeNum"></model>
</template>
<!--model.vue-->
<script setup>
const num1 = defineModel("number") // 与num绑定
</script>
<template>
<div>
从父组件获取的数据 - {{ num1 }}
<button @click="num1++"> <!--也可以在script中修改,但是修改值要写成num1.value-->
子组件按钮 - 加1
</button>
</div>
</template>