欢迎光临
我们一直在努力

二、Vue3 核心基础:API 对比、Setup 与响应式详解

文章目录

    • 一、OptionsAPI与CompositionAPI
      • 1. 核心概念对比
        • Options API (选项式 API)
        • Composition API (组合式 API)
      • 2. 核心区别:代码组织方式
        • Options API 的痛点:碎片化
        • Composition API 的优势:高内聚
      • 3. 代码直观对比
        • 方式 A:Options API 风格
        • 方式 B:Composition API 风格 (Vue 3 推荐的 `<script setup>`)
    • 二、setup
      • 1. 原始的 `setup()` 函数(为什么麻烦?)
      • 2. `setup` 语法糖(消除 return)
      • 扩展:“给组件起名字”
    • 三、响应式数据
      • 1. 什么是 ref?
      • 2. 什么是 reactive?
      • 3. ref vs reactive 大比拼(核心区别)
      • 4. 为什么 reactive 容易让人踩雷?
        • 直接赋值导致响应式“断开”
        • 解构赋值丢失响应式
      • 5. 最佳实践:我该选哪一个?
    • 四、toRefs,toRef

一、OptionsAPI与CompositionAPI

在 Vue 的发展历程中,Options API(选项式 API)和 Composition API(组合式 API)是两种最核心的组件编写风格。Vue 2 时代主要是 Options API 的天下,而 Vue 3 引入了 Composition API 并将其推为主流。

用一句话总结它们的区别:Options API 是“按代码特性分堆”,而 Composition API 是“按业务逻辑分堆”。

1. 核心概念对比

Options API (选项式 API)

Options API 就像是给你一套固定的模版。你需要把代码填进特定的“选项”(Options)里:

  • data 放响应式数据

  • methods 放函数方法

  • computed 放计算属性

  • watch 放侦听器

Composition API (组合式 API)

Composition API 则给你了绝对的自由。它不再限制你代码的位置,而是提供了一系列核心方法(如 ref, reactive, computed, onMounted 等),让你直接在 setup 函数(或 <script setup>)中自由组合它们。

2. 核心区别:代码组织方式

为了更直观地理解,我们可以通过“高亮代码块”的分布来看看它们在处理复杂业务时的差异:

Options API 的痛点:碎片化

假设一个组件同时包含“用户管理”和“商品列表”两个业务逻辑:

  • 在 Options API 中,用户管理的变量在 data,方法在 methods;商品列表的变量也在 data,方法也在 methods。

  • 结果: 你的视线必须在 data、methods、computed 之间来回上下“反复横跳”,一个业务的代码被拆得七零八落。当组件达到几百行时,维护起来非常痛苦。

Composition API 的优势:高内聚
  • 在 Composition API 中,你可以把“用户管理”的所有数据、方法、生命周期写在一起;把“商品列表”的所有代码写在一起。

  • 你甚至可以把整个业务逻辑抽离成一个独立的 useUser.js 文件(即自定义 Hook),在任何组件里直接引入。

3. 代码直观对比

我们来实现同一个功能:一个简单的计数器,加上一个改变标题的功能。

方式 A:Options API 风格

<script>
export default {
data() {
return {
count: 0,
title: 'Hello Vue'
};
},
computed: {
doubleCount() {
return this.count * 2;
}
},
methods: {
increment() {
this.count++;
},
changeTitle() {
this.title = 'Title Changed!';
}
}
};
</script>

方式 B:Composition API 风格 (Vue 3 推荐的 <script setup>)

<script setup>
import { ref, computed } from 'vue';

// 逻辑一:计数器业务(高内聚在一起)
const count = ref(0);
const doubleCount = computed(() => count.value * 2);
function increment() {
count.value++;
}

// 逻辑二:标题业务(高内聚在一起)
const title = ref('Hello Vue');
function changeTitle() {
title.value = 'Title Changed!';
}
</script>

二、setup

1. 原始的 setup() 函数(为什么麻烦?)

<script lang="ts">
import { defineComponent, ref } from 'vue'

// 使用 defineComponent 定义组件
export default defineComponent({
// setup 函数是组合式API的入口
setup() {
// 1. 定义响应式变量
let name = ref('张三')
let age = ref(18)
let tel = '13888888888' // 非响应式

// 2. 定义方法
function changeName() {
name.value = 'zhang-san'
}
function changeAge() {
age.value += 1
}
function showTel() {
alert(tel)
}

// 3. 必须 return 出去,模板才能使用
return {
name,
age,
tel,
changeName,
changeAge,
showTel
}
}
})
</script>

<template>
<div class="person">
<h2>姓名:{{ name }}</h2>
<h2>年龄:{{ age }}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">年龄+1</button>
</div></template>

在 Vue 3 刚出来的时候,setup( )函数有两个致命的痛点:

  • 必须手动 return: 在 setup 里面定义了变量和方法,最后必须写一句 return { name, age, … }。如果不写,<template> 模板就拿不到,页面就会报错。

  • 此时还不是响应式: 就像你代码里注释写的,这时候用 let name = '张三',点击按钮虽然改变了变量,但页面死活不转。

  • 因为每次都要写 return 太痛苦了,所以官方后来推出了语法糖。

    2. setup 语法糖(消除 return)

    写法:直接在 <script> 标签上加一个 setup 单词。

    有了它,刚才那一大堆复杂的代码,直接缩减成了这样:

    <script setup lang="ts">
    // 1. 变量和方法直接定义,不需要写 export default {}
    // 2. 也不需要写 setup() 函数的外壳
    // 3. 配合 ref 让它变成真正的响应式

    import { ref } from 'vue'

    let name = ref('张三')
    let age = ref(18)
    let tel = '13888888888' // 电话不需要改,写成非响应式

    function changeName() {
    name.value = 'zhang-san' // 真正的响应式修改
    }
    function changeAge() {
    age.value += 1
    }
    function showTel() {
    alert(tel)
    }

    // 注意:这里绝对不需要写 return !!!模板自动就能用。
    </script>

    <template>
    <div class="person">
    <h2>姓名:{{name}}</h2>
    <h2>年龄:{{age}}</h2>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">年龄+1</button>
    </div>
    </template>

    扩展:“给组件起名字”

    现在已经不需要安装第三方插件了!Vue 3.3+ 官方已经原生支持了一个叫 defineOptions 的宏命令。标准做法是这样:

    <script setup lang="ts">
    // 官方自带的起名神器,不需要装任何插件
    defineOptions({ name: 'Person' })
    </script>

    • 绝大多数时候:可以完全忘记组件命名这件事,放心地交给文件名自动推导。

    • 只有在需要递归、需要 KeepAlive 精准控制、或者用 Vue Devtools 调试在成百上千个组件里找瞎眼时:defineOptions({ name: 'xxx' }) 才用得到。

    三、响应式数据

    Vue3 中的 ref 和 reactive 是组合式 API(Composition API)中最核心的两个响应式数据核心工具。很多刚从 Vue2 转过来的同学容易搞混它们。

    其实很简单,我们可以把它们理解为“单值包装盒”和“对象代理器”。下面为你详细拆解。

    1. 什么是 ref?

    ref 主要用来定义基本数据类型(如 String, Number, Boolean, null 等)的响应式数据,但它其实也可以传对象。

    • 本质:它把你的值包裹在一个对象的 value 属性里。

    • 特点:

      • 在 JS/TS 中修改或读取它,必须加上 .value。

      • 在 Template(模板)中读取它,Vue 会自动解包,不需要写 .value。

    <script setup>
    import { ref } from 'vue'

    // 定义响应式数据
    const count = ref(0)
    const name = ref('Vue3')

    function increment() {
    count.value++ // JS中必须用 .value
    }
    </script>

    <template>
    <button @click="increment">点击了 {{ count }} 次</button>
    <p>你好,{{ name }}</p>
    </template>

    2. 什么是 reactive?

    reactive 只能用来定义复杂数据类型(如 Object, Array, Map, Set),不能传基本类型。

    • 本质:它是基于底层 Proxy 实现的,直接对目标对象进行深度响应式代理。

    • 特点:

      • 无论在 JS 还是 Template 中,都不需要 .value,直接通过属性访问。

      • 致命痛点:不能直接替换整个对象,也不能直接解构,否则会失去响应式。

    <script setup>
    import { reactive } from 'vue'

    // 定义对象
    const state = reactive({
    count: 0,
    user: { name: '张三' } // 深度响应式
    })

    function increment() {
    state.count++ // 直接修改,不需要 .value
    }

    // 错误示范 1:直接赋值一个新对象(会导致响应式丢失)
    function reset() {
    // state = { count: 0, user: { name: '张三' } } // 这会打破 Proxy 的代理!
    }

    // 错误示范 2:解构赋值
    // const { count } = state // 此时 count 变成了一个普通数字,不再是响应式的
    </script>

    <template>
    <button @click="increment">点击了 {{ state.count }} 次</button>
    </template>

    3. ref vs reactive 大比拼(核心区别)

    为了让你一目了然,我们用一张表格来对比它们的应用场景和特性:

    特性 / 维度refreactive
    推荐数据类型 基本类型 (Number, String 等) 以及数组 复杂的对象 (Object)
    底层实现 RefImpl 类的 get/set 拦截(若传对象,内部也会调 reactive) 基于 ES6 的 Proxy 进行拦截
    JS中访问 必须加 .value 不需要 .value,直接 obj.key
    Template中访问 自动解包,不需要 .value 直接 obj.key
    重新赋值(覆盖) 支持。例:state.value = {} 依然是响应式的 不支持直接覆盖整个对象,会丢失响应式
    解构赋值 不支持(但可以通过 toRefs 转化后解构) 不支持(解构后会变成普通变量,失去响应式)

    4. 为什么 reactive 容易让人踩雷?

    很多新手喜欢用 reactive,因为觉得“不用写 .value 太爽了”。但 reactive 有两个非常容易让人抓狂的限制:

    直接赋值导致响应式“断开”

    如果你从后端请求了一个新对象,想赋值给 reactive:

    let userInfo = reactive({ name: 'Tom', age: 18 })
    // 后来从后端拿到了新数据
    userInfo = { name: 'Jerry', age: 20 } // 这样写,页面绝对不会更新!userInfo 变成了普通对象。

    正确解法:

  • 改用 ref(推荐):const userInfo = ref({ name: 'Tom' }); userInfo.value = { name: 'Jerry' };

  • 或者不改引用,只改属性:Object.assign(userInfo, newSubObj)

  • 解构赋值丢失响应式

    const pos = reactive({ x: 0, y: 0 })
    const { x, y } = pos // 此时 x 和 y 只是普通的数字 0,你修改它们,页面不会变。

    正确解法:

    使用 toRefs 帮它套一层 ref:

    import { reactive, toRefs } from 'vue'
    const pos = reactive({ x: 0, y: 0 })
    const { x, y } = toRefs(pos) // 现在 x 和 y 是 ref 了,在 JS 中使用需要 x.value

    5. 最佳实践:我该选哪一个?

    目前前端社区更推荐“一把梭”优先使用 ref。

    • 官方推荐/社区主流:优先使用 ref。无论是数字、字符串还是对象、数组,通通可以用 ref。虽然写 .value 稍微有点繁琐,但它绝对不会因为赋值或解构导致响应式丢失,心智负担极低。

    • 何时用 reactive:只有当一组数据关系非常紧密,比如表单的数据绑定(const loginForm = reactive({ username: '', password: '' })),且你确定不会去整体替换这个表单对象时,用 reactive 会让代码看起来更干净。

    四、toRefs,toRef

    既然解构 reactive 会导致响应式丢失,那如果我们非要解构、或者想把对象的某个属性单独传给子组件,该怎么办?这就需要 toRef 和 toRefs。

    <script setup>
    import { reactive, toRef, toRefs } from 'vue'

    const form = reactive({
    title: '服务器故障',
    priority: 'high',
    count: 1
    })

    // 1. toRef:把对象里的【单个属性】提取出来,变成一个 ref 对象
    const priorityRef = toRef(form, 'priority')

    // 2. toRefs:把对象里的【所有属性】批量转成 ref,然后可以安全地解构(最常用)
    const { title, count } = toRefs(form)

    const modify = () => {
    // 此时它们全都是真正的 ref 了,修改它们,原 form 对象里的数据也会同步改变!
    title.value = 'MySQL 死锁故障'
    priorityRef.value = 'critical'
    }
    </script>

    <template>
    <p>工单:{{ title }} —— 紧急度:{{ priorityRef }}</p>
    <button @click="modify">修改</button>
    </template>

    • 底层逻辑:toRef(s) 并没有创建任何新的响应式对象,它只是做了一层桥接(指向代理)。它创建了一个临时的 ref 对象,这个 ref 内部的 .value 读写操作,本质上是被拦截重定向到了原 reactive 对象的对应 key 上。
    赞(0)
    未经允许不得转载:171主机测评 » 二、Vue3 核心基础:API 对比、Setup 与响应式详解
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址