setup 是 Vue 3 的新特性,也是组合式 API(Composition API)的核心。它提供了一种全新的方式设计组件的逻辑,便于复用同时也增加代码可读性。
1. 理解 setup
1、组合式 API 的入口
setup 是在组件实例创建之前执行的,因此无法访问 this,也没有生命周期钩子函数。可以使用 setup 定义组件的逻辑、状态和方法。
<script>
export default {
setup(props, context) {
console.log('this: ', this); // undefined
}
}
</script>
2、函数式组件的支持
setup 让 Vue 组件变得更像一个函数式组件,在 setup 内部可以使用组合式 API 声明响应式数据、计算属性、监听器等。它替代了传统的选项式 API 中的 data、computed、methods 等,让逻辑关注点更加集中和模块化。
举个 🌰
<template>
<div>
<p>num1: {{ num1 }}</p>
<p>num2: {{ num2 }}</p>
<p>sum: {{ sum }}</p>
<button @click="calculateSum" class="btn">计算</button>
</div>
</template>
传统写法:
export default {
name: 'Calculator',
data() {
return {
num1: 10,
num2: 20,
sum: 0,
};
},
computed: {
// 也可以将 sum 声明为一个计算属性而不是方法
// sum() {
// return this.num1 + this.num2;
// }
},
// 使用 methods 选项来声明组件方法
methods: {
calculateSum() {
this.sum = parseFloat(this.num1) + parseFloat(this.num2);
}
},
};
setup 写法:
import { computed, ref } from 'vue'
export default {
name: 'Calculator',
setup() {
const num1 = ref(10)
const num2 = ref(20)
const sum = ref(0);
// const sum = computed(() => num1 + num2);
const calculateSum = () => {
sum.value = num1.value + num2.value
}
return {
num1,
num2,
sum,
calculateSum
}
}
}
3、增强逻辑复用
使用组合函数(Composition Function)可以将 setup 中的逻辑,抽离到独立的函数或组件中,便于复用。
举个 🌰
<template>
<div>
<h3>count: {{ count }}</h3>
<button @click="increment" style="margin-top: 13px; padding: 4px 6px">increment</button>
</div>
</template>
<script>
import { ref } from 'vue'
function useCounter() {
const count = ref(0)
const increment = () => {
count.value++
}
return {
count,
increment
}
}
export default {
setup() {
const { count, increment } = useCounter()
return { count, increment }
}
}
</script>
使用 useCounter 的方式,不仅可以复用,还可以保持代码的简洁和模块化。
2. 使用 setup
1、基本语法
setup 函数接收两个参数:props 和 context。props 是传入组件的属性,context 包含了 attrs、slots、emit 等对象。
export default {
setup(props, context) {
console.log('props: ', props);
console.log('context: ', context);
}
}
举个 🌰
<template>
<div>
<h2>count: {{ count }}</h2>
<h2>doubleCount: {{ doubleCount }}</h2>
<button @click="increment" style="margin-top: 13px; padding: 4px 6px">Increment</button>
</div>
</template>
<script>
import { ref, computed } from 'vue'
export default {
setup(props, context) {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
const increment = () => {
count.value++;
console.log('count: ', count.value);
}
return {
count,
doubleCount,
increment
}
}
}
</script>
在这个代码中,count 是一个响应式数据,doubleCount 是一个计算属性,increment 是一个方法。这些都是通过 setup 函数声明,并通过返回值暴露给模板使用。
2、访问 props 和 context
- props 是响应式对象,可以在 setup 中直接使用。
- context 包含了 attrs(非 props 的属性)、slots(插槽)、emit(事件触发)等对象。
举个 🌰
<!-- 子组件 -->
<template>
<div>
<p>当前计数: {{ count }}</p>
<button @click="increment" style="margin: 10px 0; padding: 4px 6px">增加计数</button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
export default defineComponent({
name: 'Counter',
props: {
initialCount: {
type: Number,
required: true, // 必填
},
},
setup(props, { emit }) {
const count = ref(props.initialCount);
const increment = () => {
count.value++;
// 触发一个名为 'update:count' 的事件,传递当前的计数值
emit('update:count', count.value);
};
// 将响应式数据和方法返回,以便在模板中使用
return {
count,
increment,
};
},
})
</script>
<!-- 父组件 -->
<template>
<div>
<Counter :initialCount="5" @update:count="handleCountUpdate" />
<p>父组件中的计数: {{ parentCount }}</p>
<Calculator />
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import Counter from './components/Counter.vue';
export default defineComponent({
components: {
Counter,
},
setup() {
const parentCount = ref(0);
const handleCountUpdate = (newCount: number) => {
parentCount.value = newCount;
};
return {
parentCount,
handleCountUpdate,
};
},
});
</script>
3. 对比 Vue 2 好处
1、更好的逻辑组织
在 Vue 2 中,组件的逻辑代码往往分散在多个选项(如 data、methods、computed、watch 等)中。对于复杂组件而言,不利于逻辑组织和后期维护。
在 Vue 3 中,setup 函数让所有的逻辑集中在一起,便于模块化和维护。
2、更强的类型支持和推断
在 Vue 2 中,类型推断主要依赖于外部工具(如 TypeScript),而在选项式 API (Option API)中,类型推断往往不够直观,特别是对于 data 返回的对象。
在 Vue 3 中,setup 函数提供了更好的 TypeScript 支持。因为 setup 是一个普通的JS 函数,所有的类型信息都可以直接使用函数签名定义,并且 Vue 3 内置了类型推断支持,使得类型检查更加精确和友好。
3、更简洁的代码
在 Vue 2 中,必须通过 this 访问组件的状态和方法,这增加了代码的复杂性,并且 this 的上下文在某些情况下可能不够清晰。
在 Vue 3 中,setup 函数中不再使用 this,直接通过变量引用即可访问状态和方法。这不仅使代码更简洁,还避免了 this 绑定的问题。
4、更容易的逻辑复用
在 Vue 2 中,逻辑复用主要通过混入(mixins)和高阶组件(HOC)实现。混入容易导致命名冲突和代码难以追踪,而高阶组件也会增加组件嵌套的复杂度。
在 Vue 3 的组合式 API 和 setup 函数让逻辑复用变得更加直观。开发者可以编写自定义的组合函数来复用逻辑。
4. 注意事项
1、不能使用 this
在 setup 中,组件实例还未创建,因此无法使用 this(undefined)。所有组件的状态和方法都需要通过返回值暴露。
2、生命周期钩子的使用
虽然 setup 中没有直接提供生命周期钩子,但可以通过 onMounted、onUpdated 等组合式 API 来注册生命周期钩子。
import { onMounted } from 'vue'
export default defineComponent({
setup() {
onMounted(() => {
console.log('Component mounted')
})
}
})
3、setup 的返回值
setup 的返回值决定了组件模版中可以访问的内容。如果返回一个对象,那该对象的属性将直接暴露给模版使用。
4、setup 中的异步操作
虽然 setup 支持异步操作,但如果需要在组件渲染之前完成某些异步任务,最好不要直接使用 async setup,而是在 setup 中手动处理异步逻辑。
export default {
setup() {
const data = ref(null)
const fetchData = async () => {
data.value = await fetchSomeData()
}
fetchData()
return { data }
}
}
5、与 Vuex 和 Router 的结合
可以直接在 setup 中使用 useStore 和 useRouter 钩子来访问全局状态和路由。
import { useStore } from 'vuex';
import { useRouter } from 'vue-router';
export default defineComponent({
setup() {
const store = useStore(); // 获取 Vuex store 实例
const router = useRouter(); // 获取 Vue Router 实例
},
});
下文介绍,Vue 3 中 <script setup> 的使用和特点。