目录
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 的写法也完全兼容!!
需求:
显示隐藏图片
计数器功能
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
基于 逻辑功能 组织代码
可复用性和可维护性都更好!
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>
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>
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 是在单文件组件 (SFC) 中,使用组合式 API 的编译时语法糖。相比于普通的 script 语法更加简洁
要使用这个语法,需要将 setup
attribute 添加到 代码块上:
<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 函数调用时,要接收一个处理函数,处理函数中,需要返回计算属性的值。
- <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 监听, 接收三个参数
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 和 $refs, 获取 DOM 元素 或 组件
获取 DOM
创建 ref => const h1Ref = ref(null)
模板中建立关联 => 钩子函数-----123
使用 => 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 提供数据
- <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>
如果对一个响应式数据, 进行解构, 会丢失它的响应式特性!
原因: 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>