单文件组件的基础结构与语法
Vue单文件组件(SFC)以.vue为后缀,核心由template(模板)、script(逻辑)、style(样式)三个部分组成,直观实现”HTML+JS+CSS”的组件化封装。
 
1. 基础结构示例
一个最简单的SFC(Button.vue)长这样:
<template>
  <!-- 模板:组件的HTML结构,仅允许一个根元素 -->
  <button class="custom-btn" @click="handleClick">
    {{ label }}
  </button>
</template>
<script>
// 逻辑:组件的JS代码,需导出Vue组件对象
export default {
  name: 'CustomButton', // 组件名(建议大驼峰)
  props: {
    label: { type: String, required: true } // 父组件传递的属性
  },
  methods: {
    handleClick() {
      this.$emit('btn-click') // 触发父组件事件
    }
  }
}
</script>
<style scoped>
/* 样式:组件的CSS,scoped表示样式仅作用于当前组件 */
.custom-btn {
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
  background: #42b983;
  color: #fff;
  cursor: pointer;
}
</style>
2. 关键语法说明
| 部分 | 核心规则 | 
|---|---|
| template | 必须包含唯一根元素(Vue3允许fragment,但仍建议保持单根以避免样式问题) | 
| script | 需通过 export default导出组件配置,Vue3支持script setup语法糖(更简洁) | 
| style | scoped属性实现样式隔离,避免污染全局;支持lang="scss"等预处理器 | 
组件通信的核心方法
组件通信是SFC的核心能力,常用场景为父→子和子→父传递数据/事件。
1. 父→子:Props传递
父组件通过props向子组件传值,子组件需声明接收的属性类型与默认值:
<!-- 父组件:Parent.vue -->
<template>
  <CustomButton label="点击我" /> <!-- 传递label属性 -->
</template>
<!-- 子组件:CustomButton.vue -->
<script>
export default {
  props: {
    label: {
      type: String, // 属性类型
      required: true, // 是否必传
      default: '默认按钮' // 可选默认值(仅在非required时生效)
    }
  }
}
</script>
2. 子→父:Emit触发事件
子组件通过$emit触发父组件定义的事件,可携带参数:
<!-- 子组件:CustomButton.vue -->
<script>
export default {
  methods: {
    handleClick() {
      this.$emit('btn-click', '我是子组件传递的参数') // 触发事件并传参
    }
  }
}
</script>
<!-- 父组件:Parent.vue -->
<template>
  <CustomButton 
    label="点击我" 
    @btn-click="handleBtnClick" <!-- 监听子组件事件 -->
  />
</template>
<script>
export default {
  methods: {
    handleBtnClick(param) {
      console.log('子组件触发了点击:', param) // 输出:子组件触发了点击:我是子组件传递的参数
    }
  }
}
</script>
script setup语法糖:Vue3的效率神器
Vue3推出的script setup语法糖,彻底简化了SFC的逻辑代码——无需写export default,自动注册props/emit/components,代码量减少50%以上!
1. 基础用法示例
<template>
  <button @click="handleClick">{{ label }}</button>
</template>
<script setup>
// 1. 导入Vue API(无需注册,直接使用)
import { defineProps, defineEmits } from 'vue'
// 2. 定义Props(替代原props选项)
const props = defineProps({
  label: { type: String, required: true }
})
// 3. 定义Emit(替代原emits选项)
const emit = defineEmits(['btn-click']) // 声明要触发的事件名
// 4. 定义方法(直接写,无需methods选项)
const handleClick = () => {
  emit('btn-click') // 触发事件
}
</script>
2. 自动注册组件
script setup中导入的组件会自动注册,无需写components选项:
<script setup>
import CustomButton from './Button.vue' // 导入即注册
</script>
<template>
  <CustomButton label="自动注册的组件" /> <!-- 直接使用 -->
</template>
样式封装与穿透技巧
scoped属性让样式仅作用于当前组件,但有时需要修改子组件或第三方组件的样式,这时候需要”样式穿透”。
1. 基础样式隔离
<style scoped>
/* 仅作用于当前组件的.button类 */
.button {
  color: #42b983;
}
</style>
2. 样式穿透方法
| 场景 | 语法(Vue3) | 说明 | 
|---|---|---|
| 修改子组件样式 | :deep(.child-class) | 穿透到子组件的 .child-class | 
| 修改第三方组件样式 | :deep(.el-button) | 例如修改Element Plus的按钮样式 | 
| 全局样式 | 新增无scoped的style | <style>/* 全局样式 */</style> | 
示例:修改子组件样式
<!-- 父组件:Parent.vue -->
<style scoped>
/* 修改子组件CustomButton的背景色 */
:deep(.custom-btn) {
  background: #ff5722;
}
</style>
实战技巧:提升开发效率
1. 复用性:封装公共组件
将高频使用的UI元素封装为公共组件(如分页器、弹窗),能大幅减少重复代码。例如封装Pagination.vue:
<template>
  <div class="pagination">
    <button @click="prevPage" :disabled="currentPage === 1">上一页</button>
    <span>{{ currentPage }} / {{ totalPages }}</span>
    <button @click="nextPage" :disabled="currentPage === totalPages">下一页</button>
  </div>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue'
const props = defineProps({
  currentPage: { type: Number, default: 1 },
  totalPages: { type: Number, required: true }
})
const emit = defineEmits(['page-change'])
const prevPage = () => {
  if (props.currentPage > 1) {
    emit('page-change', props.currentPage - 1)
  }
}
const nextPage = () => {
  if (props.currentPage < props.totalPages) {
    emit('page-change', props.currentPage + 1)
  }
}
</script>
2. 性能优化:异步组件
对于大型组件(如报表、地图),使用异步加载减少初始bundle体积:
<script setup>
// 懒加载组件(仅在需要时加载)
const LargeReport = defineAsyncComponent(() => import('./LargeReport.vue'))
</script>
<template>
  <div v-if="showReport">
    <LargeReport />
  </div>
</template>
3. 动态切换:Component标签
用<component :is="componentName">实现组件动态切换(如Tab栏):
<template>
  <div class="tabs">
    <button @click="currentTab = 'Home'">首页</button>
    <button @click="currentTab = 'About'">关于我们</button>
    <component :is="currentTab" /> <!-- 动态渲染组件 -->
  </div>
</template>
<script setup>
import { ref } from 'vue'
import Home from './Home.vue'
import About from './About.vue'
const currentTab = ref('Home') // 初始显示Home组件
</script>
常见问题与避坑指南
1. 组件名大小写
Vue建议组件名使用大驼峰(如CustomButton),模板中使用短横线(如<custom-button>),避免大小写错误导致组件无法渲染。
2. script setup的陷阱
- 不能与export default同时使用(语法糖已替代);
- 变量/函数需显式导出(若需在模板外使用):
<script setup> import { ref } from 'vue' const count = ref(0) export const increment = () => count.value++ // 导出给父组件使用 </script>
3. 样式穿透失效
若:deep()不生效,检查:
– 是否使用了正确的Vue版本(Vue3需用:deep(),Vue2用::v-deep);
– 是否在scoped样式中使用穿透;
– 第三方组件是否有多层嵌套(需增加穿透层级)。
工具支持:提升开发体验
- IDE插件:推荐Volar(替代Vetur,更适配Vue3),支持SFC语法高亮、类型提示;
- 代码检查:配置ESLint规则vue/script-setup-uses-vars,避免变量未使用的错误;
- 预处理器:通过style lang="scss"启用SCSS,需安装sass依赖。
原创文章,作者:,如若转载,请注明出处:https://zube.cn/archives/195
