目录
composition API vs options API
体验 composition API
setup 函数
reactive 函数
ref 函数
script setup 语法
计算属性 computed 函数
监听器 watch 函数
生命周期
模板中 ref 的使用
组件通讯 - 父传子
组件通讯 - 子传父
依赖注入 - provide 和 inject
保持响应式 - toRefs 函数
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>