Vue3 单文件中不同的组件

Vue3 单文件中不同的组件

在 Vue 3 的单文件组件(.vue 文件)中,组件可以从多个层面来理解。下面我将详细说明哪些部分算是组件,以及如何识别它们。

📁 组件定义的多个层次

1. 文件本身就是一个组件

这是最直观的理解:每个 .vue 文件本身就是一个 Vue 组件

vue 复制代码
<!-- UserCard.vue - 这个文件本身就是一个组件 -->
<template>
  <div class="user-card">
    <h3>{{ user.name }}</h3>
    <p>{{ user.email }}</p>
  </div>
</template>

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

const user = ref({
  name: '张三',
  email: 'zhangsan@example.com'
})
</script>

识别要点

  • 文件扩展名是 .vue
  • 包含标准的 Vue 组件结构(template/script/style)
  • 可以被其他组件导入和使用

2. 组件中的子组件

在单个 .vue 文件中,可以在 <template> 中使用的自定义标签都是组件。

示例1:使用全局注册的组件

vue 复制代码
<!-- ParentComponent.vue -->
<template>
  <div>
    <!-- 这些都是组件 -->
    <el-button>按钮组件</el-button>      <!-- Element Plus 组件 -->
    <el-input placeholder="输入框组件" /> <!-- Element Plus 组件 -->
    <router-view />                     <!-- Vue Router 组件 -->
  </div>
</template>

示例2:使用局部注册的组件

vue 复制代码
<!-- ParentComponent.vue -->
<template>
  <div>
    <!-- 这些都是子组件 -->
    <ChildComponentA />
    <ChildComponentB />
    <UserAvatar :user="currentUser" />
  </div>
</template>

<script setup>
// 导入的组件
import ChildComponentA from './ChildComponentA.vue'
import ChildComponentB from './ChildComponentB.vue'
import UserAvatar from './UserAvatar.vue'

// 在 <script setup> 中,导入的组件会自动在模板中可用
</script>

3. 动态组件

使用 Vue 的 <component> 标签配合 :is 属性也是组件。

vue 复制代码
<template>
  <div>
    <!-- 动态组件 -->
    <component :is="currentComponent" />
    
    <!-- 也可以是异步组件 -->
    <Suspense>
      <template #default>
        <AsyncComponent />
      </template>
      <template #fallback>
        <LoadingSpinner />
      </template>
    </Suspense>
  </div>
</template>

<script setup>
import { shallowRef, defineAsyncComponent } from 'vue'
import HomePage from './HomePage.vue'
import AboutPage from './AboutPage.vue'

// 动态组件
const currentComponent = shallowRef(HomePage)

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

4. 内置组件

Vue 3 提供的内置组件,虽然不写在文件中,但使用它们也算作组件。

vue 复制代码
<template>
  <!-- 内置组件 -->
  <Teleport to="body">
    <Modal />
  </Teleport>
  
  <KeepAlive>
    <component :is="currentComponent" />
  </KeepAlive>
  
  <Transition name="fade">
    <div v-if="show">过渡内容</div>
  </Transition>
</template>

5. 函数式组件

在同一个文件中定义的函数式组件。

vue 复制代码
<!-- FunctionalComponentExample.vue -->
<template>
  <div>
    <!-- 使用函数式组件 -->
    <DynamicHeading :level="1">标题1</DynamicHeading>
    <DynamicHeading :level="2">标题2</DynamicHeading>
  </div>
</template>

<script setup>
import { h } from 'vue'

// 函数式组件 - 这也是一个组件
const DynamicHeading = (props, { slots }) => {
  return h(`h${props.level}`, {}, slots.default())
}

// 定义 props
DynamicHeading.props = ['level']
</script>

6. 渲染函数组件

<script> 中使用渲染函数定义的组件。

vue 复制代码
<!-- RenderFunctionExample.vue -->
<script>
import { h, defineComponent } from 'vue'

// 渲染函数组件
const CustomButton = defineComponent({
  props: {
    type: {
      type: String,
      default: 'button'
    }
  },
  setup(props, { slots }) {
    return () => h(
      'button',
      {
        class: 'custom-button',
        type: props.type
      },
      slots.default()
    )
  }
})

export default defineComponent({
  components: {
    CustomButton
  }
})
</script>

<template>
  <CustomButton>点击我</CustomButton>
</template>

7. JSX 组件

如果在 Vue 3 中使用 JSX,那么在同一个文件中定义的 JSX 函数也是组件。

vue 复制代码
<!-- JsxComponentExample.vue -->
<script>
import { defineComponent } from 'vue'

// JSX 组件
const JsxButton = defineComponent({
  props: ['onClick'],
  setup(props, { slots }) {
    return () => (
      <button onClick={props.onClick} class="jsx-button">
        {slots.default()}
      </button>
    )
  }
})

export default defineComponent({
  components: {
    JsxButton
  }
})
</script>

🎯 实际项目示例分析

让我们通过一个完整的示例来识别其中所有的组件:

vue 复制代码
<!-- App.vue -->
<template>
  <div id="app">
    <!-- 1. 路由组件(动态组件) -->
    <router-view />
    
    <!-- 2. UI 框架组件 -->
    <el-button @click="showModal = true">打开弹窗</el-button>
    <el-dialog v-model="showModal" title="提示">
      <span>这是一个弹窗</span>
    </el-dialog>
    
    <!-- 3. 内置组件 -->
    <Teleport to="body">
      <!-- 4. 全局注册的组件 -->
      <GlobalNotification />
    </Teleport>
    
    <!-- 5. 局部注册的组件 -->
    <Header :title="appTitle" />
    <Sidebar :menus="menuItems" />
    <MainContent>
      <!-- 6. 插槽中的组件 -->
      <UserList :users="users" />
    </MainContent>
    <Footer />
    
    <!-- 7. 条件渲染的组件 -->
    <template v-if="user.isAdmin">
      <AdminPanel />
    </template>
    
    <!-- 8. 循环渲染的组件 -->
    <template v-for="item in featuredItems" :key="item.id">
      <FeaturedCard :item="item" />
    </template>
    
    <!-- 9. 动态组件 -->
    <component :is="currentTabComponent" />
  </div>
</template>

<script setup>
import { ref, shallowRef, computed, defineAsyncComponent } from 'vue'
import { useRouter } from 'vue-router'
import { ElButton, ElDialog } from 'element-plus'

// 10. 导入的组件
import Header from './components/Header.vue'
import Sidebar from './components/Sidebar.vue'
import MainContent from './components/MainContent.vue'
import Footer from './components/Footer.vue'
import UserList from './components/UserList.vue'

// 11. 异步组件
const AdminPanel = defineAsyncComponent(() => 
  import('./components/AdminPanel.vue')
)

// 12. 全局组件(已经在 main.js 中注册)
// GlobalNotification 已在全局注册

// 13. 函数式组件
const StatusBadge = (props, context) => {
  // 渲染函数返回虚拟节点
  return h('span', {
    class: `badge badge-${props.type}`,
    style: { backgroundColor: props.color }
  }, context.slots.default())
}
StatusBadge.props = ['type', 'color']

// 14. 动态组件定义
const tabs = {
  home: defineAsyncComponent(() => import('./views/Home.vue')),
  about: defineAsyncComponent(() => import('./views/About.vue')),
  contact: shallowRef({
    template: '<div>联系我们</div>'
  })
}
const currentTab = ref('home')
const currentTabComponent = computed(() => tabs[currentTab.value])

// 响应式数据
const showModal = ref(false)
const appTitle = ref('我的应用')
const menuItems = ref([/* ... */])
const users = ref([/* ... */])
const user = ref({ isAdmin: true })
const featuredItems = ref([/* ... */])
</script>

<style scoped>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
}
</style>

📊 组件分类总结

序号 组件类型 示例 定义位置 使用方式
1 文件组件 App.vue 本身 独立的 .vue 文件 被其他文件导入
2 子组件 Header, Sidebar 其他 .vue 文件 在模板中作为标签使用
3 UI 框架组件 el-button, el-dialog Element Plus 库 在模板中作为标签使用
4 内置组件 router-view, Teleport Vue 3 核心 在模板中作为标签使用
5 动态组件 <component :is="..."> 当前文件或导入 通过 :is 动态切换
6 异步组件 defineAsyncComponent() 通过函数定义 延迟加载
7 函数式组件 StatusBadge 函数 在当前文件定义 通过函数调用或标签
8 渲染函数组件 使用 h() 函数 <script> 通过渲染函数
9 全局组件 GlobalNotification main.js 注册 在任何地方使用
10 插槽组件 <UserList> 在插槽内 子组件 通过插槽传递

🔍 如何识别组件

1. 在模板中识别

vue 复制代码
<template>
  <!-- 这些都是组件 -->
  <ComponentName />          <!-- 自定义组件 -->
  <el-button />              <!-- UI 框架组件 -->
  <router-view />            <!-- 内置组件 -->
  <component :is="comp" />   <!-- 动态组件 -->
</template>

识别规则

  • 以大写字母开头的标签(PascalCase)通常是组件
  • 以短横线连接的标签(kebab-case)也可能是组件
  • 使用自闭合标签 <ComponentName /> 通常是组件
  • 使用非 HTML 标准标签的都是组件

2. 在 script 中识别

javascript 复制代码
// 这些都是组件的定义或导入
import UserCard from './UserCard.vue'           // 导入组件
const AsyncComp = defineAsyncComponent(...)     // 定义异步组件
const FunctionalComp = () => h('div', ...)      // 定义函数式组件
export default { components: { ... } }          // 注册组件

3. 在实际代码中识别

vue 复制代码
<template>
  <!-- 组件示例 -->
  <div>
    <!-- 1. 原生 HTML 元素 -->
    <div>这是 HTML 元素</div>
    <button>这是 HTML 按钮</button>
    
    <!-- 2. Vue 组件 -->
    <MyComponent />                    <!-- 自定义组件 -->
    <el-button>按钮</el-button>        <!-- UI 库组件 -->
    <router-link to="/">首页</router-link>  <!-- 路由组件 -->
    
    <!-- 3. 内置组件 -->
    <Transition>
      <div v-if="show">内容</div>
    </Transition>
    
    <!-- 4. 动态组件 -->
    <component :is="currentView" />
  </div>
</template>

🎯 快速识别技巧

技巧1:查看导入语句

javascript 复制代码
// 这些导入的都是组件
import Button from './Button.vue'           // 文件组件
import { ElButton } from 'element-plus'     // UI 库组件
import { RouterView } from 'vue-router'     // 内置组件

技巧2:查看组件注册

javascript 复制代码
// Options API
export default {
  components: {
    Button,      // 局部注册的组件
    ElButton,    // UI 组件
    RouterView   // 内置组件
  }
}

// Composition API (<script setup>)
// 导入的组件自动注册

技巧3:查看模板使用

vue 复制代码
<template>
  <!-- 组件特征 -->
  <!-- 1. 属性绑定 -->
  <Component :prop="value" @event="handler" />
  
  <!-- 2. 插槽使用 -->
  <Component>
    <template #header>标题</template>
    内容
  </Component>
  
  <!-- 3. 作用域插槽 -->
  <Component v-slot="{ item }">
    {{ item.name }}
  </Component>
</template>

📈 实际项目中的应用

项目结构示例

复制代码
src/
├── components/           # 可复用组件
│   ├── common/          # 通用组件
│   │   ├── Button.vue
│   │   ├── Input.vue
│   │   └── Modal.vue
│   └── layout/          # 布局组件
│       ├── Header.vue
│       ├── Sidebar.vue
│       └── Footer.vue
├── views/               # 页面组件
│   ├── Home.vue
│   ├── About.vue
│   └── User/
│       ├── List.vue
│       └── Detail.vue
└── App.vue             # 根组件

组件使用示例

vue 复制代码
<!-- Home.vue -->
<template>
  <div class="home">
    <!-- 布局组件 -->
    <AppHeader />
    <AppSidebar />
    
    <!-- 页面内容 -->
    <main class="content">
      <!-- 功能组件 -->
      <SearchBar v-model="searchText" />
      <UserList :users="filteredUsers" />
      
      <!-- UI 组件 -->
      <el-pagination
        :current-page="currentPage"
        :total="total"
        @current-change="handlePageChange"
      />
      
      <!-- 内置组件 -->
      <router-link to="/about">关于我们</router-link>
      
      <!-- 动态组件 -->
      <component :is="getComponentByType(user.type)" />
    </main>
    
    <!-- 布局组件 -->
    <AppFooter />
  </div>
</template>

🎯 总结

在 Vue 3 的单文件组件中,以下这些都算是组件

  1. 文件本身 :每个 .vue 文件就是一个组件
  2. 导入的子组件:从其他文件导入的 Vue 组件
  3. UI 框架组件:如 Element Plus、Ant Design Vue 等
  4. 内置组件 :Vue 3 提供的 <component><Transition>
  5. 路由组件 :Vue Router 提供的 <router-view><router-link>
  6. 动态组件 :通过 <component :is="..."> 动态渲染的组件
  7. 异步组件 :通过 defineAsyncComponent() 定义的组件
  8. 函数式组件:通过函数定义的渲染函数组件
  9. 全局组件:在应用级别注册的组件
  10. 渲染函数组件 :在 <script> 中使用 h() 函数定义的组件

核心要点

  • 组件是 Vue 应用的基本构建块
  • 组件可以被复用、组合和嵌套
  • 组件化是 Vue 的核心思想
  • 理解组件的各种形式有助于更好地组织代码

简单的识别方法

在 Vue 模板中,不是标准 HTML 标签 的元素,基本上都是组件。标准 HTML 标签包括:divspanpabuttoninput 等约 100 个元素。其他自定义标签都是组件。

通过理解这些概念,您就能准确地识别 Vue 3 单文件中的各种组件,并正确地使用它们来构建应用。

相关推荐
一字白首2 小时前
小程序组件化进阶:从复用到通信的完整指南DAY04
前端·小程序·apache
读忆2 小时前
你是否用过Tailwind CSS?你是在什么情况下使用的呢?
前端·css·经验分享·笔记·taiiwindcss
阿珊和她的猫2 小时前
探秘小程序:为何拿不到 DOM 相关 API
前端·小程序
FlyWIHTSKY2 小时前
Vue 3 onMounted 中控制同步与异步执行策略
前端·javascript·vue.js
PascalMing2 小时前
告别 Nginx!ASP.NET Core 实现多域名 Vue 静态服务与代理转发
vue.js·nginx·asp.net
蜗牛攻城狮2 小时前
【Vue3实战】El-Table实现“超过3行省略,悬停显示全文”的完美方案(附性能优化)
前端·vue.js·性能优化·element-plus
孙12~2 小时前
前端vue3+vite,后端SpringBoot+MySQL
前端·html·学习方法
隔壁小邓2 小时前
vue的组件化的理解之单独拆分的组件&组件的封装
前端·javascript·vue.js
Ivanqhz2 小时前
图着色寄存器分配算法(Graph Coloring)
开发语言·javascript·python·算法·蓝桥杯·rust