Code前端首页关于Code前端联系我们

Vue3选项式API基础咋理解?

terry 8小时前 阅读数 9 #Vue
文章标签 Vue3;选项式API

很多刚接触Vue3的同学,一上来就被选项式API和组合式API搞晕:明明Vue2一直用选项式,Vue3里这玩意儿还能用不?和组合式比到底咋选?开发时要注意啥?今天就唠明白Vue3选项式API的那些事儿,从基础到实战,帮你理清思路~

选项式API是Vue3保留的经典写法,和Vue2一脉相承,在Vue3里,你依然能像Vue2那样,把组件逻辑拆成datamethodscomputedwatch这些“选项”来组织,比如写个简单的计数器组件:

<template>
  <button @click="increment">{{ count }}</button>
</template>
<script>
export default {
  data() {
    return { count: 0 }
  },
  methods: {
    increment() {
      this.count++
    }
  }
}
</script>

这里data返回响应式数据,methods放方法,this指向组件实例——和Vue2的逻辑完全打通,Vue3保留选项式API,一是照顾老项目迁移(直接升级Vue3时,代码改动少);二是给习惯“按功能分块写代码”的开发者安全感——不用重构整个项目,也能享受Vue3的性能优化(比如响应式系统重构、Tree-shaking支持)。

不过Vue3选项式里的API有小变化:比如生命周期钩子,Vue2的beforeDestroy改成beforeUnmountdestroyed改成unmounted;自定义指令的钩子也有调整(bindbeforeMountinsertedmounted等),这些细节看官方文档就能快速摸清~

选项式和组合式API核心区别是啥?

这两种API的设计思路天差地别,从代码组织、逻辑复用,到响应式原理、TypeScript支持,处处都有差异,咱逐个掰扯:

代码组织逻辑

选项式是“按功能分类”:把数据(data)、方法(methods)、计算属性(computed)等,分别丢进不同选项里,好处是结构清晰,新手看一眼就知道“数据在data,方法在methods”;但组件逻辑复杂时,同一个功能的代码可能分散在多个选项里(比如一个表单验证,数据在data、方法在methods、侦听器在watch),来回跳着看代码特累。

组合式API是“按逻辑聚合”:用setup函数把相关逻辑打包,比如一个用户信息模块,把数据、修改数据的方法、侦听器全塞一块:

<script setup>
import { ref, watch } from 'vue'
const user = ref({ name: '张三' })
const updateName = () => { user.value.name = '李四' }
watch(user, (newVal) => { console.log('用户变化:', newVal) })
</script>

这样“用户模块”的逻辑全在setup里扎堆,维护时不用满文件找代码,适合复杂组件。

逻辑复用方式

选项式靠mixins复用逻辑:比如把表单验证逻辑写在mixin里,多个组件引入,但问题一堆:不同mixin里的datamethods重名会冲突;组件里分不清哪个逻辑来自哪个mixin,调试时像“黑盒”。

组合式用Composables(组合函数):把复用逻辑封装成函数,比如useFormValidation,组件里按需引入,逻辑来源清晰,也没命名冲突风险,举个例子:

// useFormValidation.js
import { ref, computed } from 'vue'
export function useFormValidation() {
  const formData = ref({ username: '', password: '' })
  const isFormValid = computed(() => {
    return formData.value.username && formData.value.password
  })
  return { formData, isFormValid }
}
// 组件里使用
<script setup>
import { useFormValidation } from './useFormValidation.js'
const { formData, isFormValid } = useFormValidation()
</script>

每个组合函数的逻辑独立,复用起来丝滑多了~

响应式原理差异

选项式里,data返回的对象会被Vue自动加上响应式“魔法”,直接通过this.xxx就能访问/修改,Vue能自动追踪依赖,但要注意:直接改数组索引(比如this.list[0] = 1)、给对象新增属性(this.obj.newKey = 'val'),Vue检测不到,得用this.$set(Vue2写法,Vue3选项式里也支持)。

组合式API里,得手动用ref(基本类型)、reactive(对象/数组)来声明响应式数据,修改时要通过.valueref)或者直接改(reactive对象,但深层响应式要注意)。

<!-- 组合式 -->
<script setup>
import { ref } from 'vue'
const count = ref(0)
const increment = () => { count.value++ } // 必须写 .value
</script>
<!-- 选项式 -->
<script>
export default {
  data() { return { count: 0 } },
  methods: { increment() { this.count++ } } // 直接 this.xxx
}
</script>

TypeScript支持友好度

选项式API在TS里写类型,得给propsdata做额外声明,

<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
  props: { { type: String, required: true }
  },
  data() {
    return {
      count: 0 as number // 得手动断言类型
    }
  },
  methods: {
    increment() { /* ... */ }
  }
})
</script>

组合式API用definePropsdefineEmits能直接推断类型,refreactive也能通过泛型自动推导,对TS更友好。

<script setup lang="ts">
const props = defineProps<{ title: string }>()
const count = ref(0) // 自动推断 number 类型
</script>

所以如果项目重度用TS,组合式写起来更顺手~

选项式API开发常见场景咋处理?

日常开发逃不过组件通信、生命周期、计算属性这些,选项式里咋玩?一个个说:

组件通信:父→子、子→父、跨层级

  • 父传子(props:和Vue2一样,在props选项声明接收的参数,类型、默认值都能配:

    <script>
    export default {
      props: {
        list: {
          type: Array,
          default: () => []
        },
        title: String
      }
    }
    </script>
  • 子传父(emits:在emits选项声明事件,然后this.$emit('事件名', 数据)

    <script>
    export default {
      emits: ['item-click'],
      methods: {
        handleClick(item) {
          this.$emit('item-click', item)
        }
      }
    }
    </script>
  • 跨层级(provide/inject:祖先组件provide数据,后代组件inject接收:

    祖先组件:

    <script>
    export default {
      provide() {
        return {
          theme: 'dark'
        }
      }
    }
    </script>

    后代组件:

    <script>
    export default {
      inject: ['theme'],
      mounted() {
        console.log('当前主题:', this.theme)
      }
    }
    </script>

生命周期钩子咋用?

选项式里的生命周期和Vue2名字大部分一样,只是Vue3把“销毁阶段”改成“卸载阶段”了:

Vue2生命周期 Vue3选项式生命周期 触发时机
beforeCreate beforeCreate 组件创建前
created created 组件创建后
beforeMount beforeMount 挂载到DOM前
mounted mounted 挂载到DOM后
beforeUpdate beforeUpdate 数据更新,DOM更新前
updated updated 数据更新,DOM更新后
beforeDestroy beforeUnmount 组件卸载前
destroyed unmounted 组件卸载后

用的时候直接在选项里写钩子函数:

<script>
export default {
  mounted() {
    console.log('组件挂载完成,能操作DOM啦~')
  },
  beforeUnmount() {
    console.log('组件要卸载了,清理定时器、事件监听之类的~')
  }
}
</script>

计算属性(computed)和侦听器(watch

  • computed:处理依赖数据的“自动计算”,比如购物车总价:

    <script>
    export default {
      data() { 
        return { 
          goods: [{ price: 10, num: 2 }, { price: 20, num: 1 }] 
        } 
      },
      computed: {
        totalPrice() {
          return this.goods.reduce((sum, item) => sum + item.price * item.num, 0)
        }
      }
    }
    </script>

    依赖的goods变化时,totalPrice会自动更新~

  • watch:监听数据变化做副作用操作,比如监听搜索关键词发请求:

    <script>
    export default {
      data() { return { searchKey: '' } },
      watch: {
        searchKey(newVal, oldVal) {
          if (newVal) {
            // 发请求获取搜索结果
          }
        }
      }
    }
    </script>

    还能深度监听对象/数组:

    watch: {
      'user.info'(newVal) { // 监听 user.info 子属性
        console.log('用户信息变化:', newVal)
      },
      goods: {
        handler(newVal) { /* ... */ },
        deep: true // 深度监听数组/对象内部变化
      }
    }

自定义指令(directives

选项式里通过directives选项注册自定义指令,比如做个“自动聚焦”指令:

<template>
  <input v-focus />
</template>
<script>
export default {
  directives: {
    focus: {
      mounted(el) { // 指令绑定的元素挂载后触发
        el.focus()
      }
    }
  }
}
</script>

指令的钩子函数和生命周期对应,比如beforeMount(元素挂载前)、mounted(元素挂载后)等,和Vue3的指令钩子规则一致~

新手用选项式容易踩哪些坑?

选项式看似和Vue2差不多,但Vue3里有些细节没注意,容易掉坑里,这些“雷区”得避开:

this指向丢了!

在选项式里,methods里的函数默认绑定组件实例的this,但如果用箭头函数this就不是组件实例了!

<script>
export default {
  data() { return { count: 0 } },
  methods: {
    increment: () => { 
      this.count++ // 这里this是window(非严格模式)或undefined(严格模式),直接报错!
    }
  }
}
</script>

所以methods里必须用普通函数,别用箭头函数!

响应式数据更新不生效

选项式里data的响应式是Vue自动处理的,但直接修改数组索引、给对象加新属性,Vue检测不到变化!

<script>
export default {
  data() { 
    return { 
      list: [1,2,3], 
      user: { name: '张三' } 
    } 
  },
  methods: {
    wrongUpdate() {
      this.list[0] = 10 // Vue检测不到,页面不更新
      this.user.age = 18 // user原本没age属性,新增的属性不是响应式的,页面也不更新
    }
  }
}
</script>

解决办法:

  • 数组更新用pushsplice等变异方法,或者替换整个数组(比如this.list = [...this.list.slice(0,0), 10, ...this.list.slice(1)]);
  • 对象新增属性用this.$set(this.user, 'age', 18)(Vue3选项式里也支持this.$set,和Vue2一样)。

mixins带来的“隐形Bug”

mixins复用逻辑时,容易出现命名冲突(比如两个mixin都有handleClick方法,组件里会覆盖),而且逻辑来源不透明(组件里分不清哪个方法来自哪个mixin)。

// mixin1.js
export default {
  methods: { handleClick() { console.log('mixin1的点击') } }
}
// mixin2.js
export default {
  methods: { handleClick() { console.log('mixin2的点击') } }
}
// 组件里同时引入
<script>
import mixin1 from './mixin1.js'
import mixin2 from './mixin2.js'
export default {
  mixins: [mixin1, mixin2],
  methods: {
    // 这里handleClick会被mixin2覆盖,自己写的也会被覆盖,逻辑乱成粥
  }
}
</script>

所以除非维护老项目,尽量少用mixins,改用组合式的Composables,逻辑清晰不冲突~

TypeScript里类型声明麻烦

选项式在TS中给dataprops写类型,得手动断言或用defineComponent配合类型接口,比组合式的自动推导麻烦,比如给data加类型:

<script lang="ts">
import { defineComponent } from 'vue'
interface User { name: string; age: number }
export default defineComponent({
  data() {
    return {
      user: { name: '张三', age: 18 } as User // 得手动as断言,否则TS推断成any
    }
  }
})
</script>

如果是组合式,用ref<User>({ name: '张三', age: 18 })就能自动推导类型,省心多了~

啥时候选选项式而非组合式?

不是所有场景都适合组合式,这几种情况选选项式更舒服:

维护Vue2迁移过来的老项目

团队要把Vue2项目升级到Vue3,用选项式API能最大限度保留原有代码结构,只需要处理少数 breaking changes(比如生命周期改名、指令钩子变化),升级成本低到飞起~

团队成员对选项式更熟悉

如果团队里大多是“Vue2老玩家”,突然切换组合式API,学习成本高,代码review也费劲,先用选项式过渡,等大家熟悉Vue3后再逐步迁组合式,团队协作更丝滑~

小型组件,逻辑简单到不需要“聚合”

比如一个纯展示的按钮组件、静态列表组件,逻辑就几个propsmethods,用选项式分块写更直观,没必要搞复杂的setup函数。

快速做原型开发

想快速搭个Demo验证想法,选项式不用学refreactive这些新API,直接按Vue2的习惯写,效率拉满~

Vue3选项式API是Vue2的“继承者”,适合老项目迁移、团队习惯、简单组件开发;组合式API是“革新者”,适合复杂逻辑聚合、TS友好、高复用场景,没有绝对的好坏,只有合不合适~要是你现在维护老项目,或者团队还没准备好换写法,选项式完全能打;要是新项目、复杂逻辑、重度TS,组合式更香~下次写代码前,先想清楚需求和团队情况,选对API,开发效率直接起飞!

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门