vue - 以deifne开头的 API

在 Vue 3 中,这些以 define 开头的 API 按实现机制、核心用途可清晰分为编译器宏(Compiler Macros)工具函数(Utility Functions)组件定义函数三大类。以下在分类基础上,为每个 API 提供功能解析、使用场景、代码示例及注意事项,兼顾深度与实用性。


一、编译器宏(Compiler Macros)

核心特征

  • 仅在 <script setup> 中生效,无需手动导入,Vue 编译器自动识别并在编译阶段处理;
  • 本质是语法糖,编译时被替换为具体的运行时代码;
  • 专为简化 <script setup> 中的组件声明设计。

1. defineProps()

核心能力
声明组件的属性(Props),接收父组件传递的数据,支持类型校验、必填性和默认值配置。

代码示例

示例 1:运行时声明(非 TS 项目)

<template>
  <div>标题:{{ title }},数量:{{ count }}</div>
</template>

<script setup>
// 声明 props 并设置校验规则
const props = defineProps({
  title: {
    type: String,
    required: true,
    default: '默认标题'
  },
  count: {
    type: Number,
    default: 0,
    validator: (value) => value >= 0 // 自定义校验:值必须非负
  }
});
</script>

示例 2:TypeScript 类型声明 + 默认值

<template>
  <div>标题:{{ title }},数量:{{ count }}</div>
</template>

<script setup lang="ts">
// 定义类型接口
interface Props {
  title: string;
  count?: number;
}

// 配合 withDefaults 设置默认值
const props = withDefaults(defineProps<Props>(), {
  count: 0
});
</script>

关键细节

  • props 是只读的响应式对象,直接修改会报警告;
  • 解构 props 时需用 toRefs(props) 保留响应式。

常用场景
父组件向子组件传递数据,如列表组件接收数据源、按钮组件接收样式类型等。

2. defineEmits()

核心能力
声明组件的自定义事件,用于子组件向父组件传递数据或触发逻辑,支持事件参数校验。

代码示例

示例 1:运行时声明 + 事件校验

<template>
  <button @click="handleClick">触发事件</button>
</template>

<script setup>
// 声明事件并添加校验
const emit = defineEmits({
  change: (value) => {
    if (value.length > 0) return true;
    console.error('值不能为空');
    return false;
  },
  delete: (id) => id > 0
});

// 触发事件
const handleClick = () => {
  emit('change', '新值');
  emit('delete', 1);
};
</script>

示例 2:TypeScript 类型声明

<template>
  <input @input="handleInput" />
</template>

<script setup lang="ts">
// 用类型签名声明事件
const emit = defineEmits<{
  (e: 'input', value: string): void;
  (e: 'blur', id: number): void;
}>();

const handleInput = (e) => {
  emit('input', e.target.value);
};
</script>

关键细节

  • 触发事件的参数会被包装为 e.detail(兼容原生事件);
  • 事件校验返回 false 时,事件会被阻止触发。

常用场景
子组件通知父组件状态变化,如输入框值变化、列表项删除等。

3. defineModel() 3.4+

核心能力
Vue 3.4+ 新增,简化自定义组件的 v-model 双向绑定,替代传统的 props + emit 写法。

代码示例

示例 1:基础用法(默认 v-model)

image

父组件

<template>
  <div>
    <ChildOldComponent v-model="message" /> old: {{ message }}
    <ChildNewComponent v-model="message" /> new: {{ message }}
  </div>
</template>

<script setup>
  import ChildOldComponent from './components/defineModelOld.vue';  
  import ChildNewComponent from './components/defineModelNew.vue';  
  import { ref } from 'vue';
  const message = ref('');
</script>

2个子组件

3.4 写法:defineModelNew.vue

<script setup>
const model = defineModel() // 默认对应 `modelValue` prop
</script>

<template>
  <input v-model="model" />
</template>

3.4 以前的写法: defineModelOld.vue 3.4

<script setup>
const prop = defineProps({
  modelValue: {
    type: String,
    default: ''
  }
})
const emit = defineEmits(['update:modelValue'])

function updateValue(event) {
  emit('update:modelValue', event.target.value)
}
</script>

<template>
  <input type="text" :value="modelValue" @input="updateValue" />
</template>

示例 2:多 v-model + 修饰符处理

子组件

<template>
  <input v-model="name" placeholder="姓名" />
  <input v-model="age" placeholder="年龄" />
</template>

<script setup>
// 自定义名称的 v-model
const [name, nameModifiers] = defineModel('name', {
  set(value) {
    // 检查修饰符并处理
    if (nameModifiers.capitalize) {
      return value.charAt(0).toUpperCase() + value.slice(1);
    }
    return value
  }
})

const age = defineModel('age');
</script>

父组件

<template>
	<!--capitalize修饰符-->
  <Child v-model:name.capitalize="userName" v-model:age="userAge" /> {{ userName }} - {{ userAge }}
</template>

<script setup>
import { ref } from 'vue';
import Child from './Child.vue';
const username = ref('');
const userAge = ref(0);
</script>

关键细节

  • 底层自动声明 modelValue prop 和 update:modelValue emit;
  • 返回的是可写的响应式引用,修改时自动触发 emit。

常用场景
自定义输入框、开关、滑块等需要双向绑定的组件。

4. defineOptions() 3.3+

核心能力
Vue 3.3+ 新增,在 <script setup> 中直接声明组件的元选项(如名称、属性继承等)。

defineOptions 看起来好像没太大作用,因为它确实不影响功能,主要影响开发体验。让我用更直白的方式解释:

代码示例

<template>
  <div>{{ msg }}</div>
</template>

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

// 定义组件选项
defineOptions({
  name: 'MyComponent', // 组件名(用于调试、递归组件)
  inheritAttrs: false, // 关闭属性继承
  emits: ['change']
});

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

关键细节

  • 可设置 nameinheritAttrsprops 等选项;
  • 不建议在其中写业务逻辑(如 methods、watch),应在 setup 中实现。

常用场景
为组件命名、关闭属性继承、声明递归组件等。

场景1:你肯定遇到过这个问题——调试时组件名字都是 "Anonymous"

<script setup>
defineOptions({
  name: 'MyCounter'  // 👈 现在 Vue DevTools 里显示为 <MyCounter>
})

const count = ref(0)
</script>

image

场景2:插件或库需要组件配置

有些第三方库或插件会读取组件选项:

<script setup>
defineOptions({
  // 某些 UI 库需要这个来识别组件类型
  componentType: 'form-item',
  // 某些路由库需要的配置
  keepAlive: true
})
</script>

场景3:控制属性继承

<script setup>
// 不想要父组件传下来的属性自动加到根元素上
defineOptions({
  inheritAttrs: false  // 👈 阻止自动继承
})

// 然后你可以手动决定属性放哪
const attrs = useAttrs()
</script>

<template>
  <div class="wrapper">
    <!-- 手动控制属性绑定到哪里 -->
    <input v-bind="attrs" />
  </div>
</template>

案例1:递归组件(必须有 name)

<script setup>
import { computed } from "vue";

defineOptions({
  name: "TreeItem", // 👈 必须!否则无法递归调用自己
});

const props = defineProps(["item"]);

// 递归调用自身
const hasChildren = computed(() => props.item.children?.length > 0);
</script>

<template>
  <li>
    {{ props.item.name }}
    <ul v-if="hasChildren" style="margin-left: 20px">
      <!-- 这里要递归调用自己 -->
      <TreeItem
        v-for="child in props.item.children"
        :key="child.id"
        :item="child"
      />
    </ul>
  </li>
</template>

5. defineExpose()

核心能力
<script setup> 中显式暴露组件内部的属性/方法,供父组件通过 ref 访问。

代码示例

子组件

<template>
  <div>计数:{{ count }}</div>
</template>

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

const count = ref(0);
const increment = () => count.value++;

// 暴露内部属性和方法
defineExpose({
  count,
  increment
});
</script>

父组件

<template>
  <Child ref="childRef" />
  <button @click="handleClick">调用子组件方法</button>
</template>

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

const childRef = ref(null);
const handleClick = () => {
  childRef.value.increment(); // 调用子组件方法
  console.log(childRef.value.count); // 访问子组件属性
};
</script>

关键细节

  • <script setup> 中内部变量默认私有,仅暴露的内容可被父组件访问;
  • 暴露的响应式数据保持响应式特性。

常用场景
父组件需要直接操作子组件内部状态或方法的场景。

6. defineSlots()

核心能力
Vue 3.3+ 新增,在 TypeScript 中声明插槽的类型,提升插槽的类型推导能力。

代码示例

<template>
  <div>
    <slot :item="item" />
    <slot name="header" />
  </div>
</template>

<script setup lang="ts">
// 声明插槽类型
defineSlots<{
  default: (props: { item: string }) => void; // 默认插槽的作用域类型
  header: () => void; // 具名插槽无参数
}>();

const item = ref('测试内容');
</script>

关键细节

  • 仅用于 TypeScript 类型推导,不影响运行时逻辑;
  • 让 IDE 能为插槽提供准确的参数提示。

常用场景
TS 项目中开发带作用域插槽的组件(如列表、表格组件)。


二、工具函数(Utility Functions)

核心特征

  • 手动导入后使用,非编译器自动识别;
  • 运行时执行,用于实现特定功能;
  • 可在任意地方使用(不限于 <script setup>)。

1. defineCustomElement()

核心能力
将 Vue 组件编译为原生 Web Components(自定义元素),实现跨框架复用。

代码示例

步骤 1:创建 Vue 组件

<!-- MyElement.vue -->
<template>
  <div>
    <h2>{{ title }}</h2>
    <button @click="handleClick">点击</button>
  </div>
</template>

<script setup>
const props = defineProps({
  title: { type: String, default: '默认标题' }
});

const emit = defineEmits(['my-click']);

const handleClick = () => {
  emit('my-click', Date.now());
};
</script>

步骤 2:编译为自定义元素并注册

// main.js
import { defineCustomElement } from 'vue';
import MyElement from './MyElement.vue';

// 转换为自定义元素构造函数
const MyElementCE = defineCustomElement(MyElement);

// 注册为原生自定义元素(名称需含连字符)
customElements.define('my-element', MyElementCE);

步骤 3:在纯 HTML 中使用

<!DOCTYPE html>
<html>
<body>
  <my-element title="Hello Web Components"></my-element>
  <script src="./dist/my-element.js"></script>
  <script>
    // 监听自定义事件
    document.querySelector('my-element').addEventListener('my-click', (e) => {
      console.log('点击事件:', e.detail);
    });
  </script>
</body>
</html>

关键细节

  • 支持大部分 Vue 特性,但不支持跨 Shadow DOM 的组件通信;
  • 自定义元素名称必须包含连字符(符合 W3C 规范)。

常用场景
跨框架复用组件(如 React/Angular 项目中使用 Vue 组件)。

2. defineAsyncComponent()

核心能力
定义异步懒加载组件,减少初始打包体积,提升应用加载速度。

代码示例

示例 1:基础用法

<template>
  <AsyncComponent />
</template>

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

// 异步加载组件
const AsyncComponent = defineAsyncComponent(() => import('./AsyncComponent.vue'));
</script>

示例 2:完整配置(加载状态 + 错误处理)

<template>
  <AsyncComponent />
</template>

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

const AsyncComponent = defineAsyncComponent({
  loader: () => import('./AsyncComponent.vue'),
  loadingComponent: () => import('./Loading.vue'), // 加载中组件
  errorComponent: () => import('./Error.vue'), // 加载失败组件
  delay: 200, // 延迟 200ms 显示加载组件
  timeout: 3000, // 超时 3 秒触发错误组件
  onError(error, retry, fail, attempts) {
    // 重试 3 次
    if (attempts < 3) retry();
    else fail();
  }
});
</script>

示例 3:路由懒加载(结合 Vue Router)

// router/index.js
import { createRouter, createWebHistory } from 'vue-router';
import { defineAsyncComponent } from 'vue';

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/async',
      component: defineAsyncComponent(() => import('./AsyncPage.vue'))
    }
  ]
});

export default router;

关键细节

  • 加载函数需返回 Promise(import() 天然返回 Promise);
  • 可配合 Suspense 组件更优雅地处理加载状态。

常用场景
路由懒加载、大型组件(弹窗、图表)的按需加载。


三、组件定义函数

核心特征

  • 需手动导入使用;
  • 兼具类型推导和组件定义的作用;
  • 是 Vue 3 中定义组件的标准方式之一。

defineComponent()

核心能力
定义 Vue 组件,增强 TypeScript 类型推导,支持选项式和组合式 API。

代码示例

示例 1:选项式 API 用法

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

export default defineComponent({
  name: 'App',
  props: {
    title: String
  },
  data() {
    return {
      count: 0
    };
  },
  methods: {
    increment() {
      this.count++;
    }
  }
});
</script>

示例 2:组合式 API 用法(TS)

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

export default defineComponent(() => {
  const count = ref(0);
  const increment = () => count.value++;

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

关键细节

  • 在 TS 中能自动推导 props、emit 等类型;
  • 是根组件定义的首选方式。

常用场景
TS 项目中定义组件、选项式 API 项目中定义组件、创建根组件。


四、分类总结与使用建议

分类 包含的 API 核心特征
编译器宏 defineProps()、defineEmits()、defineModel()、defineOptions()、defineExpose()、defineSlots() 无需导入、仅在 <script setup> 中生效、编译阶段处理
工具函数 defineCustomElement()、defineAsyncComponent() 需手动导入、运行时执行、实现特定功能
组件定义函数 defineComponent 需手动导入、支持类型推导、定义组件

使用建议

  1. 优先用编译器宏:在 <script setup> 中,用 defineProps/defineEmits/defineModel 简化开发,提升效率;
  2. 工具函数按需用:跨框架复用用 defineCustomElement,性能优化用 defineAsyncComponent
  3. TS 必用 defineComponent:获得完善的类型提示,减少类型错误;
  4. 关注版本兼容:如 defineModel 需 Vue 3.4+,defineOptions 需 Vue 3.3+。
posted @ 2025-12-16 11:45  【唐】三三  阅读(31)  评论(0)    收藏  举报