跳到主要内容

script setup 语法

问题

Vue 3 的 <script setup> 是什么?它有什么优势?

答案

<script setup> 是 Vue 3.2 引入的编译时语法糖,是在单文件组件(SFC)中使用 Composition API 的推荐方式。它让代码更简洁,并提供更好的性能和 TypeScript 支持。

基本对比

<script>
import { ref, computed } from 'vue';
import ChildComponent from './Child.vue';

export default {
components: {
ChildComponent
},
props: {
title: String
},
emits: ['update'],
setup(props, { emit }) {
const count = ref(0);
const double = computed(() => count.value * 2);

function increment() {
count.value++;
emit('update', count.value);
}

return {
count,
double,
increment
};
}
};
</script>

核心优势

优势说明
更简洁无需 return,顶层变量自动暴露给模板
组件自动注册import 的组件可直接在模板中使用
更好的性能编译优化,减少运行时开销
更好的 IDE 支持更准确的类型推断
更好的代码压缩变量名可以被压缩

defineProps - 声明 Props

<script setup lang="ts">
// 方式 1:运行时声明
const props = defineProps({
title: {
type: String,
required: true
},
count: {
type: Number,
default: 0
}
});

// 方式 2:类型声明(推荐)
interface Props {
title: string;
count?: number;
items?: string[];
}

const props = defineProps<Props>();

// 方式 3:带默认值的类型声明
const props = withDefaults(defineProps<Props>(), {
count: 0,
items: () => []
});

// 使用 props
console.log(props.title);
</script>
注意

definePropsdefineEmits编译器宏,不需要导入,在编译时会被处理。

defineEmits - 声明事件

<script setup lang="ts">
// 方式 1:运行时声明
const emit = defineEmits(['update', 'delete']);

// 方式 2:类型声明(推荐)
const emit = defineEmits<{
update: [id: number, value: string];
delete: [id: number];
}>();

// 方式 3:对象语法(带验证)
const emit = defineEmits({
// 返回 true 表示验证通过
update: (id: number, value: string) => {
return id > 0;
}
});

// 使用
emit('update', 1, 'new value');
</script>

defineExpose - 暴露组件接口

<script setup> 组件默认是封闭的,需要显式暴露给父组件:

<!-- Child.vue -->
<script setup lang="ts">
import { ref } from 'vue';

const count = ref(0);

function reset() {
count.value = 0;
}

// 显式暴露
defineExpose({
count,
reset
});
</script>
<!-- Parent.vue -->
<script setup lang="ts">
import { ref } from 'vue';
import Child from './Child.vue';

const childRef = ref<InstanceType<typeof Child>>();

function handleClick() {
console.log(childRef.value?.count);
childRef.value?.reset();
}
</script>

<template>
<Child ref="childRef" />
</template>

defineOptions - 组件选项

<script setup lang="ts">
// 定义组件名、继承属性等
defineOptions({
name: 'MyComponent',
inheritAttrs: false
});
</script>

defineModel - 双向绑定(Vue 3.4+)

<!-- 子组件 -->
<script setup lang="ts">
// Vue 3.4+ 新增
const modelValue = defineModel<string>();

// 带默认值
const count = defineModel('count', { default: 0 });

// 使用
modelValue.value = 'new value'; // 自动触发 update:modelValue
</script>

<template>
<input v-model="modelValue" />
</template>
<!-- 父组件 -->
<template>
<Child v-model="value" v-model:count="count" />
</template>

defineSlots - 类型化插槽(Vue 3.3+)

<script setup lang="ts">
const slots = defineSlots<{
default(props: { item: string }): any;
header(props: { title: string }): any;
}>();
</script>

<template>
<div>
<header>
<slot name="header" title="Hello" />
</header>
<main>
<slot :item="currentItem" />
</main>
</div>
</template>

使用顶层 await

<script setup> 支持顶层 await,组件会自动变成异步组件:

<script setup>
// 顶层 await
const data = await fetch('/api/data').then(r => r.json());
</script>

<template>
<div>{{ data }}</div>
</template>
<!-- 父组件需要配合 Suspense -->
<template>
<Suspense>
<AsyncComponent />
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
</template>

与普通 script 配合使用

<!-- 可以同时使用两个 script -->
<script>
// 普通 script:用于声明只需执行一次的逻辑
export const staticValue = 'constant';

// 或者导出 Options API 兼容的选项
export default {
name: 'MyComponent',
inheritAttrs: false
};
</script>

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

const count = ref(0);
</script>

编译优化

<script setup> 在编译时会进行优化:

<!-- 源代码 -->
<script setup>
import { ref } from 'vue';

const msg = ref('Hello');
</script>

<template>
<div>{{ msg }}</div>
</template>
// 编译后(简化)
import { ref, toDisplayString } from 'vue';

const __sfc__ = {
setup() {
const msg = ref('Hello');
return { msg };
}
};

function render(_ctx) {
// 直接访问 setup 返回值,无需通过 _ctx
return h('div', toDisplayString(_ctx.msg));
}

常见面试问题

Q1: script setup 和普通 setup 有什么区别?

答案

特性script setup普通 setup()
语法顶层代码即 setup需要在 setup() 函数内
return自动暴露需要手动 return
组件注册自动注册需要 components 选项
props/emits使用编译器宏通过参数获取
性能更好(编译优化)一般
TypeScript更好的类型推断需要 defineComponent

Q2: defineProps 是什么?为什么不需要 import?

答案

defineProps编译器宏(Compiler Macro),不是运行时函数:

// 编译前
const props = defineProps<{ msg: string }>();

// 编译后
const props = __props; // 直接引用组件的 props

特点:

  1. 无需导入:编译时被处理,不存在于运行时
  2. 类型安全:支持 TypeScript 泛型语法
  3. 编译优化:生成更高效的代码

类似的编译器宏还有:defineEmitsdefineExposedefineOptionsdefineSlotsdefineModelwithDefaults

Q3: 如何在 script setup 中定义组件名?

答案

<!-- 方式 1:使用 defineOptions(推荐) -->
<script setup>
defineOptions({
name: 'MyComponent'
});
</script>

<!-- 方式 2:使用额外的 script 块 -->
<script>
export default {
name: 'MyComponent'
};
</script>

<script setup>
// ...
</script>

<!-- 方式 3:通过文件名推断 -->
<!-- 文件名 MyComponent.vue 会自动推断为 MyComponent -->

Q4: script setup 组件如何被父组件访问?

答案

<script setup> 组件默认不暴露任何内容,需要使用 defineExpose

<!-- Child.vue -->
<script setup>
import { ref } from 'vue';

const count = ref(0);
const privateData = ref('private'); // 不暴露

function publicMethod() {
console.log('public');
}

// 只暴露需要的内容
defineExpose({
count,
publicMethod
});
</script>
<!-- Parent.vue -->
<script setup>
import { ref, onMounted } from 'vue';
import Child from './Child.vue';

const childRef = ref();

onMounted(() => {
console.log(childRef.value.count); // ✅ 0
console.log(childRef.value.privateData); // ❌ undefined
childRef.value.publicMethod(); // ✅
});
</script>

<template>
<Child ref="childRef" />
</template>

Q5: 如何在 script setup 中使用 TypeScript?

答案

<script setup lang="ts">
import { ref, computed } from 'vue';

// 1. ref 类型推断
const count = ref(0); // Ref<number>
const message = ref<string | null>(null); // 显式指定

// 2. Props 类型
interface Props {
title: string;
list?: number[];
}

const props = withDefaults(defineProps<Props>(), {
list: () => []
});

// 3. Emits 类型
const emit = defineEmits<{
change: [id: number];
update: [value: string];
}>();

// 4. ref 模板引用类型
import type { ComponentPublicInstance } from 'vue';

const inputRef = ref<HTMLInputElement | null>(null);
const childRef = ref<InstanceType<typeof ChildComponent> | null>(null);

// 5. computed 类型
const double = computed<number>(() => count.value * 2);
</script>

相关链接