vue3入门- script setup详解下

defineProps() 和 defineEmits()

为了在声明 propsemits 选项时获得完整的类型推导支持,我们可以使用 definePropsdefineEmits API,它们将自动地在 <script setup> 中可用:

html 复制代码
<template>
  <div>
    <h1>{{ props.foo }}</h1>
    <button @click="handleChange">Change</button>
    <button @click="handleDelete">Delete</button>
  </div>
</template>

<script setup>
const props = defineProps({
  foo: String
})

const emit = defineEmits(['change', 'delete'])

function handleChange() {
  emit('change', props.foo)
}

function handleDelete() {
  emit('delete')
}
</script>
  • definePropsdefineEmits 都是只能在 <script setup> 中使用的编译器宏。他们不需要导入,且会随着 <script setup> 的处理过程一同被编译掉。
  • defineProps 接收与 props 选项相同的值,defineEmits 接收与 emits 选项相同的值。
  • definePropsdefineEmits 在选项传入后,会提供恰当的类型推导。

针对TS类型的 props/emit 声明

propsemit 也可以通过给 definePropsdefineEmits 传递纯TS类型参数的方式来声明:

typescript 复制代码
const props = defineProps<{
  foo: string
  bar?: number
}>()

const emit = defineEmits<{
  (e: 'change', id: number): void
  (e: 'update', value: string, status: boolean): void
}>()

// 3.3+:另一种更简洁的语法
const emit = defineEmits<{
  change: [id: number] // 具名元组语法
  update: [value: string, status: boolean]
}>()
  • definePropsdefineEmits 要么使用运行时声明 ,要么使用类型声明。同时使用两种声明方式会导致编译报错。

    • 示例 1: 使用运行时声明

      js 复制代码
      const props = defineProps({
        foo: String,
        bar: Number
      })
      
      const emit = defineEmits(['change', 'delete'])
    • 示例 2: 使用类型声明

      js 复制代码
      const props = defineProps<{
        foo: string
        bar?: number
      }>()
      
      const emit = defineEmits<{
        (e: 'change', id: number): void
        (e: 'delete'): void
      }>()
    • 错误示例: 同时使用运行时声明和类型声明

      js 复制代码
      // ❌ 会导致编译报错
      const props = defineProps<{
        foo: string
      }>({
        bar: Number
      })
      typescript 复制代码
      // ❌ 会导致编译报错
      const emit = defineEmits<{
        (e: 'change', id: number): void
      }>(['delete'])
  • 使用类型声明的时候,静态分析会自动生成等效的运行时声明,从而在避免双重声明的前提下确保正确的运行时行为。

    • 在开发模式下,编译器会试着从类型来推导对应的运行时验证。例如这里从 foo: string 类型中推断出 foo: String。如果类型是对导入类型的引用,这里的推导结果会是 foo: null (与 any 类型相等),因为编译器没有外部文件的信息。
    • 在生产模式下,编译器会生成数组格式的声明来减少打包体积 (这里的 props 会被编译成 ['foo', 'bar'])。

在 Vue 3.2 及以下版本中,defineProps() 的泛型类型参数只能使用类型字面量或本地接口的引用:

typescript 复制代码
interface Props {
  foo: string
  bar?: number
}

const props = defineProps<Props>()

在 Vue 3.3 及以上版本中,可以在类型参数的位置引用导入的类型或有限的复杂类型:

typescript 复制代码
import { SomeType } from './types'

const props = defineProps<SomeType>()

然而,仍然无法使用需要实际类型分析的复杂类型,例如条件类型:

typescript 复制代码
// ❌ 不支持
type ConditionalProps<T> = T extends string ? { foo: T } : { bar: T }

const props = defineProps<ConditionalProps<number>>()

但可以在单个 prop 的类型上使用条件类型:

typescript 复制代码
// ✅ 支持
const props = defineProps<{
  foo: string | number
  bar: string extends 'test' ? number : boolean
}>()

响应式 Props 解构

在 Vue 3.5 及以上版本中,从 defineProps 返回值解构出的变量是响应式的。当在同一个 <script setup> 块中的代码访问从 defineProps 解构出的变量时,Vue 的编译器会自动在前面添加 props.

js 复制代码
const { foo } = defineProps(['foo'])

watchEffect(() => {
  // 在 3.5 之前仅运行一次
  // 在 3.5+ 版本中会在 "foo" prop 改变时重新运行
  console.log(foo)
})

以上编译成以下等效内容:

js 复制代码
const props = defineProps(['foo'])

watchEffect(() => {
  // `foo` 由编译器转换为 `props.foo`
  console.log(props.foo)
})

此外,你可以使用 JavaScript 原生的默认值语法声明 props 的默认值。这在使用基于类型的 props 声明时特别有用。

js 复制代码
interface Props {
  msg?: string
  labels?: string[]
}

const { msg = 'hello', labels = ['one', 'two'] } = defineProps<Props>()

使用类型声明时的默认 props 值

在 3.5 及以上版本中,当使用响应式 Props 解构时,可以自然地声明默认值。但在 3.4 及以下版本中,默认情况下并未启用响应式 Props 解构。为了用基于类型声明的方式声明 props 的默认值,需要使用 withDefaults 编译器宏:

js 复制代码
interface Props {
  msg?: string
  labels?: string[]
}

const props = withDefaults(defineProps<Props>(), {
  msg: 'hello',
  labels: () => ['one', 'two']
})

上面代码会被编译为等价的运行时 propsdefault 选项。此外,withDefaults 辅助函数提供了对默认值的类型检查,并确保返回的 props 的类型删除了已声明默认值的属性的可选标志。

在使用 withDefaults 时,如果默认值是可变引用类型(如数组或对象),应将其封装在函数中,以避免意外修改和外部副作用。以下是一个示例:

js 复制代码
interface Props {
  items?: string[]
  config?: {
    theme: string
    layout: string
  }
}

const props = withDefaults(defineProps<Props>(), {
  items: () => ['item1', 'item2', 'item3'], // 使用函数返回数组
  config: () => ({ theme: 'dark', layout: 'grid' }) // 使用函数返回对象
})

在上述代码中,itemsconfig 的默认值是通过函数返回的。这确保了每个组件实例都能获得独立的默认值副本,而不会因为共享同一个引用而导致意外的修改。

  • 错误示例

如果直接使用对象或数组作为默认值,可能会导致所有组件实例共享同一个引用,从而引发意外行为:

js 复制代码
const props = withDefaults(defineProps<Props>(), {
  items: ['item1', 'item2', 'item3'], // ❌ 直接使用数组
  config: { theme: 'dark', layout: 'grid' } // ❌ 直接使用对象
})

在这种情况下,修改一个组件实例的 itemsconfig 会影响其他实例的值,这是不符合预期的。

  • 使用默认值解构

当使用默认值解构时,不需要封装在函数中,因为解构操作会为每个实例创建独立的值:

js 复制代码
const { items = ['item1', 'item2', 'item3'], config = { theme: 'dark', layout: 'grid' } } = defineProps<Props>()

这种方式同样可以确保每个组件实例获得自己的默认值副本。

defineModel()

这个宏可以用来声明一个双向绑定 prop,通过父组件的 v-model 来使用。组件 v-model 指南中也讨论了示例用法。

在底层,这个宏声明了一个 model prop 和一个相应的值更新事件 。如果第一个参数是一个字符串字面量,它将被用作 prop 名称;否则,prop 名称将默认为 modelValue。在这两种情况下,你都可以再传递一个额外的对象,它可以包含 prop 的选项和 model ref 的值转换选项。

js 复制代码
// 声明 "modelValue" prop,由父组件通过 v-model 使用
const model = defineModel()
// 或者:声明带选项的 "modelValue" prop
const model = defineModel({ type: String })

// 在被修改时,触发 "update:modelValue" 事件
model.value = "hello"

// 声明 "count" prop,由父组件通过 v-model:count 使用
const count = defineModel("count")
// 或者:声明带选项的 "count" prop
const count = defineModel("count", { type: Number, default: 0 })

function inc() {
  // 在被修改时,触发 "update:count" 事件
  count.value++
}

在实际项目中,子组件可以用 defineModel 实现双向绑定:

// 子组件 CInput.vue

html 复制代码
<template>
  <input v-model="modelValue" placeholder="输入内容" />
</template>

<script setup lang="ts">
const modelValue = defineModel<string>({ default: '' })
</script>

// 父组件

html 复制代码
<template>
  <c-input v-model="inputval" />
  <div>{{ inputval }}</div>
</template>
<script setup>
import { ref } from 'vue';
import CInput from './components/CInput.vue';
const inputval = ref('')
</script>

WARNING

如果为 defineModel prop 设置了一个 default 值且父组件没有为该 prop 提供任何值,会导致父组件与子组件之间不同步。在下面的示例中,父组件的 myRef 是 undefined,而子组件的 model 是 1:

js 复制代码
// 子组件:
const model = defineModel({ default: 1 })

// 父组件
const myRef = ref()
js 复制代码
<Child v-model="myRef"></Child>

解决方法:在父组件初始化时为 v-model 绑定的变量设置默认值,使其与子组件的默认值保持一致。

js 复制代码
// 父组件
const myRef = ref(1) // 初始化为与子组件默认值一致

修饰符和转换器

为了获取 v-model 指令使用的修饰符,我们可以像这样解构 defineModel() 的返回值:

// 父组件

html 复制代码
<c-input v-model.trim="inputval" />

// 子组件

js 复制代码
const [modelValue, modelModifiers] = defineModel()

// 对应 v-model.trim
if (modelModifiers.trim) {
  // ...
}

当存在修饰符时,我们可能需要在读取或将其同步回父组件时对其值进行转换。我们可以通过使用 getset 转换器选项来实现这一点:

// 子组件

js 复制代码
const [modelValue, modelModifiers] = defineModel({
  // get() 省略了,因为这里不需要它
  set(value) {
    // 如果使用了 .trim 修饰符,则返回裁剪过后的值
    if (modelModifiers.trim) {
      return value.trim()
    }
    // 否则,原样返回
    return value
  }
})

在 TypeScript 中使用

definePropsdefineEmits 一样,defineModel 也可以接收类型参数来指定 model 值和修饰符的类型:

  1. defineModel<string>()
typescript 复制代码
const modelValue = defineModel<string>()
// ^? Ref<string | undefined>
  • defineModel<string>() 定义了一个 v-model,其类型为 string
  • 返回值是一个 Ref<string | undefined>,表示 modelValue 是一个响应式引用,可能是 string 类型,也可能是 undefined
  • 默认情况下,v-model 的值是可选的,因此会包含 undefined
  1. defineModel<string>({ required: true })
typescript 复制代码
const modelValue = defineModel<string>({ required: true })
// ^? Ref<string>
  • 这里通过传递选项 { required: true },将 v-model 的值设置为必填。
  • 这意味着 modelValue 不再可能是 undefined,它的类型变为 Ref<string>
  • 这种用法适合需要确保 v-model 始终有值的场景。
  1. defineModel<string, "trim" | "uppercase">()
typescript 复制代码
const [modelValue, modifiers] = defineModel<string, "trim" | "uppercase">()
// ^? Record<'trim' | 'uppercase', true | undefined>
  • 这里定义了一个带有修饰符的 v-model,例如 v-model.trimv-model.uppercase
  • 返回值是一个数组:
    1. modelValueRef<string | undefined>,表示响应式的模型值。
    2. modifiers 是一个对象,类型为 Record<'trim' | 'uppercase', true | undefined>,表示修饰符的状态。
      • 如果某个修饰符被使用(如 v-model.trim),对应的值为 true
      • 如果未使用,则为 undefined

Record 语法

Record 是 TypeScript 中的一个实用类型(Utility Type),用于构造一个对象类型,其键和值的类型都可以被明确指定。

Record 通常用于:

  1. 定义固定键值对的对象类型。
  2. 动态生成对象类型,避免手动重复定义。
typescript 复制代码
Record<Keys, Type>
  • Keys: 对象的键的类型,通常是字符串字面量类型或联合类型。
  • Type: 对象的值的类型。
typescript 复制代码
Record<'trim' | 'uppercase', true | undefined>

这段代码的含义是:创建一个对象类型,该对象的键是 trimuppercase,值是 trueundefined

等价于:

typescript 复制代码
{
  trim: true | undefined;
  uppercase: true | undefined;
}
typescript 复制代码
const options: Record<'trim' | 'uppercase', true | undefined> = {
  trim: true,
  uppercase: undefined,
};

在这个例子中,options 是一个对象,必须包含 trimuppercase,并且它们的值只能是 trueundefined

defineExpose()

使用 <script setup> 的组件是默认关闭 的------即通过模板引用或者 $parent 链获取到的组件的公开实例,不会暴露任何在 <script setup> 中声明的绑定。

可以通过 defineExpose 编译器宏来显式指定在 <script setup> 组件中要暴露出去的属性:

js 复制代码
<script setup>
import { ref } from 'vue'

const a = 1
const b = ref(2)

defineExpose({
  a,
  b
})
</script>

当父组件通过模板引用的方式获取到当前组件的实例,获取到的实例会像这样 { a: number, b: number } (ref 会和在普通实例中一样被自动解包)

以下是一个关于如何使用 defineExpose 的实例讲解,展示如何在 <script setup> 中显式暴露属性,以便外部组件可以访问这些属性。

示例代码

  • 父组件 (ParentComponent.vue)

    html 复制代码
    <template>
      <ChildComponent ref="childRef" />
      <button @click="callChildMethod">调用子组件方法</button>
    </template>
    
    <script setup>
    import ChildComponent from './ChildComponent.vue';
    
    const childRef = useTemplateRef('childRef');
    
    function callChildMethod() {
      if (childRef.value) {
        childRef.value.exposedMethod(); // 调用子组件暴露的方法
      }
    }
    </script>
  • 子组件 (ChildComponent.vue)

    vue 复制代码
    <template>
      <div>子组件内容</div>
    </template>
    
    <script setup>
    import { defineExpose } from 'vue';
    
    function exposedMethod() {
      console.log('子组件方法被调用');
    }
    
    // 使用 defineExpose 显式暴露方法
    defineExpose({
      exposedMethod,
    });
    </script>

defineSlots()

defineSlots 是 Vue 3 <script setup> 的编译宏,用于类型化插槽,让 TypeScript 能够对插槽名称和插槽 props 进行类型检查和智能提示。它只在编译阶段生效,不会影响运行时。

  • 类型安全:确保插槽名称和 props 类型正确,减少运行时错误。
  • IDE 提示:在编辑器中获得插槽相关的自动补全和类型提示。

基本语法如下

typescript 复制代码
<script setup lang="ts">
const slots = defineSlots<{
  default(props: { msg: string }): any
}>()
// slots.default({ msg: 'hello' }) // 类型检查
</script>
  • 泛型参数是一个对象,键为插槽名称,值为函数类型。
  • 函数的参数是插槽 props 的类型,返回值类型通常用 any

多插槽示例

假设有一个组件支持多个插槽:

typescript 复制代码
<script setup lang="ts">
const slots = defineSlots<{
  header(props: { title: string }): any
  default(props: { msg: string }): any
  footer(props: { count: number }): any
}>()
</script>
  • header 插槽要求 title 为字符串。
  • default 插槽要求 msg 为字符串。
  • footer 插槽要求 count 为数字。

父组件如何传递插槽

html 复制代码
<template>
  <Child class="child-style" expand>
    <template #header="{ title }">
      <div>header部分描述:{{ title }}</div>
    </template>
    <template #default="{ count }">
      <p>default部分描述:{{ count }}</p>
    </template>
    <template #footer="{ msg }">
      <div>footer部分描述:{{ msg }}</div>
    </template>
  </Child>
</template>
<script setup>
import Child from './components/Child.vue';
</script>
  • 父组件传递的插槽参数会被类型检查,确保类型正确。

子组件如何传值给父组件

html 复制代码
<template>
    <slot name="header" headerDesc="顶部部分描述"></slot>
    <slot defaultDesc="插槽描述">默认插槽描述</slot>
    <slot name="footer" footerDesc="底部部分描述"></slot>
</template>
<script setup lang="ts">
const slots = defineSlots<{
  header(props: { headerDesc: string }):any
  footer( props: { footerDesc: string } ): any
  default( props: { defaultDesc: string } ): any
}>()
</script>

类型推断和 IDE 提示

<script setup> 中,使用 slots.header({ title: 'Hello' }) 时,IDE 会自动提示 title 必须是字符串,传递错误类型会报错。

可选插槽和返回值类型

你可以将插槽定义为可选:

typescript 复制代码
<script setup lang="ts">
const slots = defineSlots<{
  header?(props: { title: string }): any
  default(props: { msg: string }): any
}>()
</script>
  • header? 表示 header 插槽是可选的。

返回值类型可以更具体:

typescript 复制代码
<script setup lang="ts">
const slots = defineSlots<{
  default(props: { msg: string }): VNode[]
}>()
</script>

useSlots() 和 useAttrs()

<script setup> 中使用 slotsattrs 的情况相对较少,因为可以直接通过模板中的 $slots$attrs 访问它们。然而,在某些特定场景下,仍然可以使用 useSlotsuseAttrs 辅助函数来获取插槽和属性。

以下是一个实际使用 useSlotsuseAttrs 的示例:

  • 子组件 (ChildComponent.vue)

    html 复制代码
    <template>
      <div v-bind="attrs">
        <slot name="header" />
        <p>子组件内容</p>
        <slot />
      </div>
    </template>
    
    <script setup>
    import { useSlots, useAttrs } from 'vue'
    
    const slots = useSlots()
    const attrs = useAttrs()
    
    // 检查是否提供了名为 "header" 的插槽
    if (!slots.header) {
      console.warn('未提供 header 插槽')
    }
    </script>
  • 父组件 (ParentComponent.vue)

    html 复制代码
    <template>
      <ChildComponent class="custom-class">
        <template #header>
          <h1>这是标题插槽内容</h1>
        </template>
        <p>这是默认插槽内容</p>
      </ChildComponent>
    </template>
    
    <script setup>
    import ChildComponent from './ChildComponent.vue'
    </script>

说明:

  1. useSlots:

    • 在子组件中使用 useSlots 检查是否提供了特定的插槽(如 header)。
    • 如果未提供插槽,输出警告信息。
  2. useAttrs:

    • 使用 useAttrs 获取父组件传递的属性(如 class="custom-class")。
    • 使用 v-bind="attrs" 将这些属性绑定到子组件的根元素。
  3. 父组件:

    • 通过 #header 提供了一个具名插槽。
    • 默认插槽内容直接放置在 <ChildComponent> 标签内。

运行结果:

  • 子组件会渲染标题插槽内容、默认插槽内容,并应用父组件传递的 class 属性。
  • 如果父组件未提供 header 插槽,子组件会在控制台输出警告信息。

泛型

可以使用 <script> 标签上的 generic 属性声明泛型类型参数:

js 复制代码
<script setup lang="ts" generic="T">
defineProps<{
  items: T[]
  selected: T
}>()
</script>

generic 的值与 TypeScript 中位于 <...> 之间的参数列表完全相同。例如,你可以使用多个参数,extends 约束,默认类型和引用导入的类型:

js 复制代码
<script
  setup
  lang="ts"
  generic="T extends string | number, U extends Item"
>
import type { Item } from './types'
defineProps<{
  id: T
  list: U[]
}>()
</script>

为了在 ref 中使用泛型组件的引用,你需要使用 vue-component-type-helpers 库,因为 InstanceType 在这种场景下不起作用。

typescript 复制代码
<script
  setup
  lang="ts"
>
import componentWithoutGenerics from '../component-without-generics.vue'; // 一个没有泛型的普通组件。
import genericComponent from '../generic-component.vue'; // 一个带有泛型的组件。

import type { ComponentExposed } from 'vue-component-type-helpers';

// 适用于没有泛型的组件
ref<InstanceType<typeof componentWithoutGenerics>>();

// 适用于有泛型的组件
ref<ComponentExposed<typeof genericComponent>>();

这段代码展示了如何在 Vue 3 的 <script setup> 中使用 TypeScript 来处理组件的引用,尤其是泛型组件的引用。以下是逐步的详细解释:

  1. 导入类型工具
typescript 复制代码
import type { ComponentExposed } from 'vue-component-type-helpers';
  • vue-component-type-helpers 库中导入了 ComponentExposed 类型工具。
  • ComponentExposed 是一个辅助类型,用于提取组件的暴露类型(即组件实例的类型),特别适用于泛型组件。
  1. 处理没有泛型的组件引用
typescript 复制代码
ref<InstanceType<typeof componentWithoutGenerics>>();
  • ref 是 Vue 的响应式 API,用于创建一个响应式引用。
  • InstanceType<typeof componentWithoutGenerics>
    • InstanceType 是 TypeScript 的内置工具类型,用于获取构造函数的实例类型。
    • typeof componentWithoutGenerics 获取组件的类型。
    • 结合起来,InstanceType<typeof componentWithoutGenerics> 表示 componentWithoutGenerics 的实例类型。
  • 适用于没有泛型的普通组件。
  1. 处理带有泛型的组件引用
typescript 复制代码
ref<ComponentExposed<typeof genericComponent>>();
  • ref<ComponentExposed<typeof genericComponent>>
    • ComponentExposed 是从 vue-component-type-helpers 导入的类型工具。
    • typeof genericComponent 获取组件的类型。
    • ComponentExposed<typeof genericComponent> 提取了 genericComponent 的暴露类型。
  • 适用于带有泛型的组件,因为 InstanceType 无法正确处理泛型组件的类型。

总结:

  1. 普通组件 :可以直接使用 InstanceType 获取实例类型。
  2. 泛型组件 :需要借助 vue-component-type-helpers 提供的 ComponentExposed 类型工具,因为 InstanceType 无法正确处理泛型。

这种方式确保了在使用组件引用时,能够获得正确的类型推断和静态检查,提升了代码的安全性和可维护性。

为了更好地理解没有泛型的普通组件和带有泛型的组件的使用场景,以下是具体的示例讲解:

  • 没有泛型的普通组件

假设我们有一个普通的 Vue 组件 ButtonComponent.vue,它不使用任何泛型:

html 复制代码
<!-- filepath: ButtonComponent.vue -->
<template>
  <button>{{ label }}</button>
</template>

<script setup lang="ts">
defineProps<{
  label: string
}>();
</script>

<script setup> 中,我们可以通过 refInstanceType 来引用该组件的实例:

typescript 复制代码
<script setup lang="ts">
import ButtonComponent from './ButtonComponent.vue';

const buttonRef = ref<InstanceType<typeof ButtonComponent>>();
</script>

这里的 InstanceType<typeof ButtonComponent> 提供了 ButtonComponent 的实例类型,适用于没有泛型的普通组件。


  • 带有泛型的组件

假设我们有一个带有泛型的 Vue 组件 GenericList.vue,它接受一个泛型 T 来定义列表项的类型:

html 复制代码
<!-- filepath: GenericList.vue -->
<template>
  <ul>
    <li v-for="(item, index) in items" :key="index">{{ item }}</li>
  </ul>
</template>

<script setup lang="ts" generic="T">
defineProps<{
  items: T[]
}>();
</script>

<script setup> 中,我们需要使用 vue-component-type-helpers 提供的 ComponentExposed 来正确引用该组件的实例:

typescript 复制代码
<script setup lang="ts">
import GenericList from './GenericList.vue';
import type { ComponentExposed } from 'vue-component-type-helpers';

const listRef = ref<ComponentExposed<typeof GenericList>>();
</script>

这里的 ComponentExposed<typeof GenericList> 提取了 GenericList 的暴露类型,适用于带有泛型的组件,因为 InstanceType 无法正确处理泛型。

通过这些示例,我们可以清楚地看到如何在 Vue 3 的 <script setup> 中使用 TypeScript 来处理组件的引用,尤其是泛型组件的引用。

在 Vue 3.4 及以上版本,推荐使用 useTemplateRef 进行子组件引用,而不是直接用 refuseTemplateRef 能更好地处理类型推断,尤其是泛型组件。

typescript 复制代码
<script setup lang="ts">
import componentWithoutGenerics from '../component-without-generics.vue'; // 没有泛型的组件
import genericComponent from '../generic-component.vue'; // 带泛型的组件
import type { ComponentExposed } from 'vue-component-type-helpers';

// 没有泛型的组件引用
const normalRef = useTemplateRef<InstanceType<typeof componentWithoutGenerics>>('normalRef');

// 带泛型的组件引用
const genericRef = useTemplateRef<ComponentExposed<typeof genericComponent>>('genericRef');
</script>

这样可以确保在 <script setup> 中获得正确的类型推断和 IDE 智能提示,无论是普通组件还是泛型组件。

限制

以下是一个关于 <script setup> 的实例讲解,帮助你理解其用法和限制。

假设我们有一个 Vue 3 组件,使用 <script setup> 来定义逻辑和模板。

html 复制代码
<template>
  <div>
    <h1>{{ message }}</h1>
    <button @click="increment">点击次数: {{ count }}</button>
  </div>
</template>

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

// 定义响应式数据
const message = 'Hello, Vue 3!';
const count = ref(0);

// 定义方法
const increment = () => {
  count.value++;
};
</script>

限制说明:

  1. 不能与 src 属性一起使用

    如果你尝试将 <script setup> 的逻辑提取到外部文件并通过 src 引入,会导致上下文丢失。例如:

    vue 复制代码
    <script setup src="./logic.ts"></script>

    这种写法是不支持的,因为 <script setup> 的代码依赖于单文件组件的上下文。

  2. 不支持 DOM 内根组件模板 <script setup> 不支持直接在 DOM 内使用根组件模板。例如:

    html 复制代码
    <div id="app">
      <MyComponent />
    </div>

    如果 MyComponent 使用了 <script setup>,它必须通过 Vue 的 createApp 挂载,而不能直接在 DOM 内使用。

相关推荐
Momo__1 小时前
VueUse createReusableTemplate —— 单文件组件内的模板复用神器
前端·vue.js
程序员小富1 小时前
我开源了一个开发者专属的智能 JSON 工具,得到了媳妇高度认可
前端·vue.js·后端
小小小小宇1 小时前
程序员如何给 LLM 装工具以及看懂推理过程
前端
写代码的皮筏艇1 小时前
React中的forwardRef
前端·react.js·面试
Flynt1 小时前
装上TypeScript 7.0 RC之后,最让我意外不是10倍提速
typescript·visual studio code
疯狂SQL1 小时前
手写高性能在线 JSON 工具|Web Worker 工程化打包 + 语法自动修复 + 多语言代码生成实战
typescript·json·next.js·web worker·前端性能优化·esbuild·源码实战
槑有老呆1 小时前
花三个月工资请了个 AI 程序员,结果它连青岛啤酒股价都查不了
前端
风骏时光牛马1 小时前
Verilog开发常见问题汇总解析
前端
子兮曰1 小时前
AI Coding Method Map:一张图看懂 AI 编程的完整链路
前端·人工智能·后端