红茶的个人站点

  • 首页
  • 专栏
  • 开发工具
  • 其它
  • 隐私政策
Awalon
Talk is cheap,show me the code.
  1. 首页
  2. 前端学习笔记
  3. 正文

Vue3 学习笔记 2:组合式 API

2025年8月31日 10点热度 0人点赞 0条评论

组合式和选项式

Vue2 使用的是选项式(Options)API,Vue3 推荐使用组合式(Composition)API。

  • 关于组合式和选项式 API 的区别以及优缺点可以观看视频。

  • 可以在 Vue3 中使用组合式 API 编写代码,但不推荐。

setup

Vue3 提供一个setup方法,可以在其中以组合式的风格定义变量和方法。

比如一个选项式的 vue 组件:

<template>
    <div class="person">
        <h2>姓名:{{ name }}</h2>
        <h2>年龄:{{ age }}</h2>
        <button @click="showPhone">查看手机号</button>
        <button @click="changeName">修改姓名</button>
        <button @click="changeAge">修改年龄</button>
    </div>
</template>
​
<script lang="ts">
export default {
    name: 'Person',
    data() {
        return {
            name: '张三',
            age: 18,
            phone: 123213123,
        }
    },
    methods: {
        showPhone() {
            alert(this.phone)
        },
        changeAge() {
            this.age += 1
        },
        changeName() {
            this.name = '王五'
        }
    }
}
</script>

使用setup方法改写:

<script lang="ts">
import { ref } from 'vue'
export default {
    name: 'Person',
    setup() {
        const name = ref('张三')
        const age = ref(18)
        const phone = 123213123
        const showPhone = () => {
            alert(phone)
        }
        const changeName = () => {
            name.value = '王五'
        }
        const changeAge = () => {
            age.value += 1
        }
        return { name, age, phone, showPhone, changeName, changeAge }
    }
}
</script>

这里的 name 和 age 需要在页面显示,且随着内容变化页面视图也要改变,因此是响应式数据,所以要使用ref方法进行定义。

setup方法中定义的变量和方法要在模版中使用,需要返回一个对象,并在对象中以属性形式添加。

比较特殊的是,除了通过setup方法的返回值暴露变量和方法给模版,还可以直接返回一个渲染函数:

<script lang="ts">
import { ref } from 'vue'
export default {
    name: 'Person',
    setup() {
        // ...
        return ()=>{
            return "Hello"
        }
    }
}
</script>

此时模版内容不会被渲染,而直接加载setup返回值中的内容到页面。

setup方法是一个生命周期钩子,其加载顺序要优先于beforeCreate:

<script lang="ts">
export default {
    name: 'Person',
    beforeCreate(){
        console.log('beforeCreate')
    },
    setup() {
        console.log('setup')
        return ()=>{
            return "Hello"
        }
    }
}
</script>

在这个示例中,先输出setup。

我们现在已经知道,Vue2 的选项式 API 中,变量和方法定义在data和methods中,而 Vue3 的组合式 API 中,变量和方法定义在 setup 中,一般而言我们只会选择其中一种风格进行编码,特殊的是,两者是可以共存的:

<script lang="ts">
import { ref } from 'vue'
export default {
    name: 'Person',
    data() {
        return {
            msg: 'hello world'
        }
    },
    methods: {
        sayHello() {
            console.log('hello')
        },
    },
    setup() {
        // ...
    }
}
</script>

特别的,可以在 data 或 methods 中访问 setup 定义的变量或方法:

<script lang="ts">
import { ref } from 'vue'
export default {
    name: 'Person',
    data() {
        return {
            msg: 'hello world',
            anotherName: this.name
        }
    },
    // ...
    setup() {
        const name = ref('张三')
        // ...
        return { name, age, phone, showPhone, changeName, changeAge }
    }
}
</script>

因为就像前面说的,setup钩子先加载,所以这里data是可以通过this引用获取到setup中定义的变量的。

反之是不可行的,不仅是因为生命周期钩子加载顺序的问题,setup方法中压根就没有 this 引用:

setup() {
    console.log('setup',this)
    // ...
}

这里会输出setup undefined,也就是说在setup中 this 引用不可用,从语法上就杜绝了这种可能性。

语法糖

在setup中编写组合式 API 显得很累赘,因此 Vue 提供一个 setup 语法糖,让代码看上去更优雅。如果用语法糖改写之前的示例:

<script lang="ts">
export default {
    name: 'Person123',
}
</script>
​
<script lang="ts" setup>
import { ref } from 'vue'
const name = ref('张三')
const age = ref(18)
const phone = 123213123
const showPhone = () => {
    alert(phone)
}
const changeName = () => {
    name.value = '王五'
}
const changeAge = () => {
    age.value += 1
}
</script>

这里用有setup属性的script标签取代setup方法,在其中定义变量和方法,并且无需显式返回。

需要注意的是,如果有需要,你依然要使用一个额外的普通script标签以向外暴露组件名称。且export default语句不能合并到setup语法糖中,会报错。

可以借助一个插件解决这个问题。

先安装插件:

npm i vite-plugin-vue-setup-extend -D

修改vite配置文件vite.config.ts,添加插件:

import vueSetupExtend from 'vite-plugin-vue-setup-extend'

// https://vite.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    vueDevTools(),
    vueSetupExtend(),
  ],
  // ...
})

重启项目,在语法糖标签中使用name属性定义组件名称:

<script lang="ts" name="Person456" setup>
// ...
</script>

可以使用浏览器插件vue devtools查看子组件名称是否被修改:

image-20250823170042162

响应式数据

在 Vue2 的选项式 API 中,通过data方法返回的数据都是响应式的:

<script lang="ts">
export default {
    name: 'Person',
    data() {
        return {
            name: '张三',
            age: 18,
            phone: 123213123,
        }
    },
    // ...
}
</script>

其值改变后页面的视图也会随之改变:

<h2>姓名:{{ name }}</h2>
<h2>年龄:{{ age }}</h2>

在 Vue3 的组合式 API 中,需要使用特殊的函数定义响应式数据。

首先,可以用ref定义基础类型的响应式数据:

<script lang="ts" name="Person456" setup>
import { ref } from 'vue'
const name = ref('张三')
const age = ref(18)
const phone = 123213123
const showPhone = () => {
    alert(phone)
}
const changeName = () => {
    name.value = '王五'
}
const changeAge = () => {
    age.value += 1
}
</script>

在使用这些响应式数据时,需要访问其value属性,比如xxx.value,而不是直接使用。

如果通过控制台打印:

console.log(name)

image-20250824092417807

会发现是一个RefImpt对象,用户关心的值保存在value属性中。

特别的,在模版中使用ref定义的响应式数据时,不需要使用xxx.value:

<h2>姓名:{{ name }}</h2>
<h2>年龄:{{ age }}</h2>

可以使用reactive定义对象类型的响应式数据:

<script lang="ts" name="Person456" setup>
import { reactive } from 'vue'
const person = reactive({
    name: '张三',
    age: 18,
    phone: '123213123'
})
const showPhone = () => {
    alert(person.phone)
}
const changeName = () => {
    person.name = '王五'
}
const changeAge = () => {
    person.age += 1
}
console.log(person)
</script>

在控制台打印其类型可以看到:

image-20250824093352337

这是一个代理对象。

使用reactive定义的响应式数据不需要使用value属性,但需要注意的是,如果你要替换整个相应式数据对象,而不是仅仅修改其中某个属性,不能采用如下做法:

let person = reactive({
    name: '张三',
    age: 18,
    phone: '123213123'
})
person = {
    name: '李四',
    age: 20,
    phone: '55555555'
}

因为这样会导致 person 对象从一个响应式数据变成普通对象,相应的,数据改变后模版中的视图也就不会发生变化。

当然,你也可以用一个新的 reactive 定义的响应式数据替换原有的:

let person = reactive({
    name: '张三',
    age: 18,
    phone: '123213123'
})
person = reactive({
    name: '李四',
    age: 20,
    phone: '55555555'
})

但这并不常见,且可能导致一些新的问题,比如:

<script lang="ts" name="Person456" setup>
import { reactive } from 'vue'
let person = reactive({
    name: '张三',
    age: 18,
    phone: '123213123'
})
const showPhone = () => {
    alert(person.phone)
}
const changeName = () => {
    person.name = '王五'
}
const changeAge = () => {
    person.age += 1
}
const changePerson = () => {
    person = reactive({ name: '李四', age: 20, phone: '5555555' })
}
console.log(person)
</script>

这会产生一个奇怪的现象,changeName和changeAge在页面加载后都可以正常工作,但如果changePerson被触发,changeAge和changeName就失效了。

为了避免响应式数据被意外改写,通常我们会将其定义为常量(const),因此直接修改属性更为常见:

<script lang="ts" name="Person456" setup>
import { reactive } from 'vue'
const person = reactive({
    name: '张三',
    age: 18,
    phone: '123213123'
})
// ...
const changePerson = () => {
    person.name = '李四'
    person.age = 20
    person.phone = '5555555'
}
</script>

这样做有其局限性,如果一个对象属性过多,或者其要被替换的值来自于一个已有对象(比如通过接口获取到的),这样就很繁琐且不现实。

可以使用Object.assign简化对目标对象属性的赋值操作:

const changePerson = () => {
    Object.assign(person, { name: '王五', age: 20, phone: '5555555' })
}

ref不仅可以将基本类型包装成响应式数据,也可以将对象类型包装成响应式数据:

const person = ref({
    name: '张三',
    age: 18,
    phone: '123213123'
})

和包装基本类型时类似,其类型为RefImpl,对象数据保存在value属性中,是一个代理对象:

image-20250825084601241

同样的,修改属性值的时候需要通过value属性:

const showPhone = () => {
    alert(person.value.phone)
}
const changeName = () => {
    person.value.name = '王五'
}
const changeAge = () => {
    person.value.age += 1
}

与reactive不同,替换被包装的对象不需要借助Object.assign,直接对value属性赋值即可:

const changePerson = () => {
    person.value = { name: '王五', age: 20, phone: '5555555' }
}

总结,对于基本类型,只能使用ref将其转换为响应式数据,对于对象,可以使用ref或reactive,但一般而言,为了避免出现大量的.value,对于属性较多的对象,比如表单绑定的数据对象(formData),推荐使用reactive。

此外,有一个小技巧,如果你觉得使用ref时频繁需要.value很不方便,可以借助插件来简化:

image-20250825085458921

勾选上面的插件设置后,在编辑器中使用ref定义的响应式数据时会自动添加.value。

ref 属性

通常我们会通过在 HTML 标签行添加 id 属性,并通过原生的document.getElementById方法获取对应的 DOM 节点:

<button id="btnShowPhone" @click="showPhone">查看手机号</button>
<script lang="ts" name="Person456" setup>
import { ref,onMounted } from 'vue'
const btnChangeName = ref()
onMounted(()=>{
    const btnShowPhone = document.getElementById('btnShowPhone')
    console.log('btnShowPhone',btnShowPhone)
})

注意,在 Vue 中,document.getElementById必须在onMounted生命周期钩子中调用,这样才能确保 DOM 树已经完成加载。否则获取到的可能是null。

这样做有一些缺点,比如前端项目过于庞大(根组件加载了很多子组件),就可能出现 id 被重复定义的问题。

Vue 提供了一个更简洁的做法,使用ref属性定义标签:

<button ref="btnChangeName" @click="changeName">修改姓名</button>

通过ref方法获取 DOM 节点对应的响应式数据:

<script lang="ts" name="Person456" setup>
import { ref,onMounted } from 'vue'
const btnChangeName = ref()
onMounted(()=>{
    console.log('btnChangeName',btnChangeName.value)
})

同样需要注意的是,虽然通过ref方法获取响应式数据可以在setup中定义,但是通过响应式数据的.value属性获取 DOM 节点的内容同样需要在生命周期钩子onMounted中执行,否则可能获取到的是undefined。

toRefs

看一个示例:

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

<script lang="ts" name="Person456" setup>
import { reactive } from 'vue'
const person = reactive({
    name: '张三',
    age: 18
})
const changeName = () => {
    person.name += '~'
}
const changeAge = () => {
    person.age += 1
}
</script>

如果对reactive定义的响应式对象进行结构赋值:

<script lang="ts" name="Person456" setup>
import { reactive } from 'vue'
const person = reactive({
    name: '张三',
    age: 18
})
let {name,age} = person
const changeName = () => {
    name += '~'
}
const changeAge = () => {
    age += 1
}
</script>

获取到的name和age是普通变量,因此按钮点击后页面视图不会发生变化。

可以通过toRefs方法将响应式对象中的每个属性都转化为响应式数据,这样再结构赋值后获取到的就是响应式数据,视图会随着数据的改变而改变:

<script lang="ts" name="Person456" setup>
import { reactive,toRefs } from 'vue'
const person = reactive({
    name: '张三',
    age: 18
})
let {name,age} = toRefs(person)

如果打印console.log(toRefs(person)),就会发现:

{name: ObjectRefImpl, age: ObjectRefImpl}

此外,比较特别的是,通过这样方式获取到的响应式数据,与原始响应式数据是有关联的,比如:

const changeName = () => {
    name.value += '~'
    console.log(name.value, person.name)
}

输出:

张三~ 张三~
张三~~ 张三~~
张三~~~ 张三~~~

如果你只需要获取响应式对象中的某个属性的响应式数据,可以使用toRef方法:

import { reactive,toRef } from 'vue'
const person = reactive({
    name: '张三',
    age: 18
})
let name = toRef(person, 'name')
let age  = toRef(person, 'age')

本文的完整示例可以从这里获取。

The End.

参考资料

  • 尚硅谷Vue3入门到实战

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: reactive ref vue
最后更新:2025年8月31日

魔芋红茶

加一点PHP,加一点Go,加一点Python......

点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

COPYRIGHT © 2021 icexmoon.cn. ALL RIGHTS RESERVED.
本网站由提供CDN加速/云存储服务

Theme Kratos Made By Seaton Jiang

宁ICP备2021001508号

宁公网安备64040202000141号