目录
composition API vs options API
composition API vs options API
-
vue2 采用的就是
options API
(1) 优点:
易于学习和使用
, 每个代码有着明确的位置 (例如: 数据放 data 中, 方法放 methods 中)(2) 缺点: 完成相同功能的代码不能放在一起, 在大项目中尤为明显。可维护性不好
-
vue3 新增的就是
composition API
(1) compositionAPI 是基于 逻辑功能 组织代码的, 一个功能相关的代码可以放到一起
(2) 即使项目大了, 功能多了,也能快速定位相关功能的代码,大大的提升了代码的
可维护性
-
vue3 推荐使用 composition API, 也保留了options API
即就算不用 composition API, 用 vue2 的写法也完全兼容!!
体验 composition API
需求:
-
显示隐藏图片
-
计数器功能
options API 版本
<template>
<button @click="toggle">显示隐藏图片</button>
<img v-show="show" alt="Vue logo" src="./assets/vue.svg" />
<hr />
计数器:{{ count }} <button @click="add">+1</button>
</template>
<script>
export default {
data() {
return {
show: true,
count: 0
}
},
methods: {
toggle() {
this.show = !this.show
},
add() {
this.count++
}
}
}
</script>
composition API 版本
<template>
<button @click="toggle">显示隐藏图片</button>
<img v-show="show" alt="Vue logo" src="./assets/vue.svg" />
<hr />
计数器:{{ count }} <button @click="add">+1</button>
</template>
<script>
// ref 就是一个组合式API,用于数据响应式
import { ref } from 'vue';
export default {
setup () {
// 显示隐藏图片
const show = ref(true)
const toggle = () => {
show.value = !show.value
}
// 计数器
const count = ref(0)
const add = () => {
count.value++
}
return { show, toggle, count, add }
}
}
</script>
小结:
optionsAPI
-
优点: 易于学习和使用, 每个代码有着明确的位置
-
缺点: 完成相同功能的代码不能放在一起, 在大项目中尤为明显。可维护性不好
compositionAPI
-
基于 逻辑功能 组织代码
-
可复用性和可维护性都更好!
setup 函数
composition api 的使用, 需要配置一个 setup 函数
-
setup 函数是一个新的组件选项, 作为组件中 composition API 的起点
-
setup 比 beforeCreate 执行时机还要早,此时 vue 的实例还没有创建,所以setup 中不能使用 this, this 指向 undefined
-
在模版中需要使用的数据和函数,需要在
setup
返回
<template>
<h1 @click="sayHi">{{msg}}</h1>
</template>
<script>
export default {
setup () {
console.log('setup')
console.log(this)
// 定义数据和函数
const msg = 'hi vue3'
const sayHi = () => {
console.log(msg)
}
return { msg, say }
},
beforeCreate() {
console.log('beforeCreate')
console.log(this)
}
}
</script>
reactive 函数
setup 中定义的数据,默认情况不是响应式的,需要用 reactive 函数,将数据变成响应式的
作用:可以将一个复杂类型的数据,转换成响应式数据
<template>
<div>{{ user.name }}</div>
<div>{{ user.age }}</div>
<button @click="changeAge">改年龄</button>
</template>
<script>
import { reactive } from 'vue'
export default {
setup () {
const user = reactive({
name: 'zs',
age: 18
})
const changeAge = () => {
user.age++
console.log(user.age)
}
return {
user,
changeAge
}
}
}
</script>
ref 函数
reactive 处理的数据,必须是复杂类型,如果是简单类型无法处理成响应式,所以有 ref 函数!
作用:可以将一个简单类型或复杂类型的数据,转换成响应式数据。
<template>
<div>{{ count }}</div>
<button @click="add">+1</button>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
console.log(count)
console.log(count.value)
const add = () => {
count.value++
}
return {
count,
add
}
}
}
</script>
ref 和 reactive 的最佳使用方式:
-
明确的对象,明确的属性,用 reactive,其他用 ref
-
从vue3.2之后,更推荐使用 ref(ref底层性能做了提升 => 260%)
// 1. 明确的对象和属性
const form = reactive({
username: '',
password: ''
})
// 2. 不明确的对象和属性
const list = ref([])
const res = await axios.get('/list')
list.value = res.data
script setup 语法
script setup 是在单文件组件 (SFC) 中,使用组合式 API 的编译时语法糖。相比于普通的 script 语法更加简洁
要使用这个语法,需要将 setup
attribute 添加到 <script>
代码块上:
<script setup></script>
所有定义的变量,函数和 import 导入的内容都可以直接在模板中直接使用
<template>
<div>{{ count }}</div>
<button @click="add">+1</button>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
const add = () => {
count.value++
}
</script>
计算属性 computed 函数
computed 函数调用时,要接收一个处理函数,处理函数中,需要返回计算属性的值。
<template>
<div>今年的年龄 <input type="number" v-model="age" /></div>
<div>明年的年龄 {{ nextAge }}</div>
</template>
<script setup>
import { computed, ref } from 'vue'
const age = ref(18)
const nextAge = computed(() => {
return age.value + 1
})
</script>
监听器 watch 函数
watch 监听, 接收三个参数
1. 参数1: 监视的数据
2. 参数2: 回调函数
3. 参数3: 额外的配置
<template>
<div>{{ count }} <button @click="count++">+1</button></div>
<div>
{{ user }}
<button @click="changeUser">修改用户信息</button>
<button @click="changeName">修改用户的年龄</button>
<button @click="changeAge">修改用户的姓名</button>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
// 1. 监听数据基础用法
const count = ref(0)
watch(count, (newValue, oldValue) => {
console.log(newValue, oldValue)
})
// 2. 监听复杂类型数据
const user = ref({
name: 'zs',
age: 18
})
const changeUser = () => {
user.value = {
name: 'ls',
age: 19
}
}
const changeName = () => {
user.value.name = 'ls'
}
watch(user, (newValue) => {
console.log('user变化了', newValue)
}, {
// 深度监听:当监听复杂数据类型的某个属性的变化,需要深度监听
deep: true,
// 让监听器立即执行一次
immediate: true
})
// 3. 监听对象的某个属性的变化
const changeAge = () => {
user.value.age = 19
}
watch(
() => user.value.age,
(newValue) => {
console.log(newValue)
}
)
</script>
生命周期
生命周期函数 vue3 中的生命周期函数, 需要在 setup 中调用。
vue2 和 vue3 的生命周期对比:
-
beforeCreate 和 created 在 setup 中不需要,原来在这两个生命周期中做的事,直接写到setup函数中。
-
在原来的名字前加一个 on
-
beforeDestroyed 变为 onBeforeUnmount,destroyed 变为 onUnmounted
选项式API下的生命周期函数使用 | 组合式API下的生命周期函数使用 |
---|---|
beforeCreate | 不需要(直接写到setup函数中) |
created | 不需要(直接写到setup函数中) |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeDestroyed | onBeforeUnmount |
destroyed | onUnmounted |
activated | onActivated |
deactivated | onDeactivated |
<script setup>
import { onMounted } from "vue"
// 生命周期函数:组件渲染完毕
onMounted(()=>{
console.log('onMounted触发了')
})
onMounted(()=>{
console.log('onMounted也触发了')
})
</script>
<template>
<div>生命周期函数</div>
</template>
模板中 ref 的使用
联想之前的 ref 和 $refs, 获取 DOM 元素 或 组件
获取 DOM
-
创建 ref =>
const h1Ref = ref(null)
-
模板中建立关联 =>
<h1 ref="h1Ref">钩子函数-----123</h1>
-
使用 =>
hRef.value
来获取 DOM
<script setup>
import { ref, onMounted } from 'vue'
const h1Ref = ref(null)
onMounted(() => {
console.log(h1Ref.value)
})
</script>
<template>
<h1 ref="h1Ref">我是标题</h1>
</template>
获取组件实例
<script setup>
import { ref } from 'vue'
import MyForm from './components/MyForm.vue'
const myFormRef = ref(null)
const fn = () => {
console.log(myFormRef.value.count)
myFormRef.value.validate()
}
</script>
<template>
<MyForm ref="myFormRef"></MyForm>
<button @click="fn">获取子 组件属性和方法</button>
</template>
需要使用 defineExpose 暴露属性或方法
<script setup>
import { ref } from 'vue'
const count = ref(0)
const validate = () => {
console.log('表单校验方法')
}
// 暴露属性或方法给外部组件使用
defineExpose({
count,
validate
})
</script>
<template>
<h3>我是 Form 组件</h3>
</template>
组件通讯 - 父传子
目标:能够实现组件通讯中的父传子组件通讯
步骤:
-
父组件提供数据
-
父组件将数据传递给子组件
-
子组件通过 defineProps 进行接收
核心代码:
父组件
<script setup>
import { ref } from 'vue'
// 在 setup 语法中,组件导入之后就能够直接使用,不需要使用 components 进行局部注册
import Child from './components/Child.vue'
const car = ref('宝马')
</script>
<template>
<div style="border: 3px solid #ccc;margin: 10px;">
<h1>我是父组件</h1>
<Child :car="car"></Child>
</div>
</template>
子组件
<script setup>
// defineProps: 接收父组件传递的数据
const props = defineProps({
car: {
type: String,
required: true
// default: '奔驰'
}
})
// 如果使用 defineProps 接收数据,这个数据只能在模板中渲染,如果想要在 script 中使用 props 属性,应该接收返回值。
console.log(props.car)
</script>
<template>
<div style="border: 3px solid #ccc;margin: 10px;">
<h3>我是子组件</h3>
<div>{{ car }}</div>
</div>
</template>
组件通讯 - 子传父
目标:能够实现组件通讯中的子传父
步骤:
-
父组件通过自定义事件的方式在子组件上注册事件
-
父组件提供自定义事件的处理函数,并接收子组件传递的数据
-
子组件通过 defineEmits 获取 emit 对象(因为没有 this)
-
子组件通过 emit 触发事件,并且传递数据
核心代码
父组件
<script setup>
import { ref } from 'vue'
import Child from './components/Child.vue'
const car = ref('宝马')
const changeCar = (newCar) => {
car.value = newCar
}
</script>
<Child :car="car" @changeCar="changeCar"></Child>
子组件
<script setup>
defineProps({
car: {
type: String,
required: true
// default: '奔驰'
}
})
const emit = defineEmits(['changeCar'])
const change = () => {
emit('changeCar', '玛莎拉蒂')
}
</script>
<template>
<div style="border: 3px solid #ccc;margin: 10px;">
<h3>我是子组件</h3>
<div>{{ car }}</div>
<button @click="change">换车</button>
</div>
</template>
依赖注入 - provide 和 inject
依赖注入:可以非常方便的实现跨级的组件通信
父组件利用 provide 提供数据
<script setup>
import { provide, ref } from 'vue'
import Child from './components/Child.vue'
const car = ref('宝马')
provide('car', car)
</script>
<template>
<div style="border: 3px solid #ccc;margin: 10px;">
<h1>我是父组件</h1>
<Child></Child>
</div>
</template>
子孙后代组件,都可以拿到这个数据)
<script setup>
import { inject } from 'vue'
const car = inject('car')
</script>
<template>
<div style="border: 3px solid #ccc;margin: 10px;">
<h3>我是子组件 -- {{ car }}</h3>
</div>
</template>
如果希望子传父, 可以 provide 传递一个方法
父组件
<script setup>
import { provide, ref } from 'vue'
import Child from './components/Child.vue'
const car = ref('宝马')
const changeCar = (newCar) => {
car.value = newCar
}
provide('car', car)
provide('changeCar', changeCar)
</script>
子组件
<script setup>
import { inject } from 'vue'
const car = inject('car')
const changeCar = inject('changeCar')
</script>
<template>
<div style="border: 3px solid #ccc;margin: 10px;">
<h3>我是子组件 -- {{ car }}</h3>
<button @click="changeCar('兰博基尼')">换车</button>
</div>
</template>
保持响应式 - toRefs 函数
如果对一个响应式数据, 进行解构, 会丢失它的响应式特性!
原因: vue3 底层是对 对象 进行监听劫持,reactive/ref 的响应式功能是赋值给对象的, 如果给对象解构, 会让数据丢失响应式的能力
toRefs 作用: 对一个 响应式对象 的所有内部属性, 都做响应式处理, 保证解构出的数据也是响应式的
<script setup>
import { ref, toRefs } from 'vue'
const user = ref({
name: 'zs',
age: 18
})
// 直接解构,会丢失响应式
const { name, age } = user.value
// 解构之前使用 toRefs 处理要解构的数据
const { name, age } = toRefs(user.value)
</script>
<template>
<div>{{ name }}</div>
<div>{{ age }}</div>
<button @click="name = 'ls'">改名</button>
<button @click="age++">改年龄</button>
</template>