单文件组件的基础结构与语法
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