第一部分:Vue 3 基础入门

第6集:单文件组件(SFC)揭秘

Vue 3的单文件组件(Single-File Component,简称SFC)是Vue开发的核心特性之一。它将组件的模板、逻辑和样式封装在一个文件中,提供了一种高效、组织良好的组件开发方式。在本集中,我们将深入了解SFC的结构、特性和最佳实践。

6.1 单文件组件的基本结构

一个Vue单文件组件通常包含三个部分:

  1. <template>:组件的模板,定义组件的HTML结构
  2. <script><script setup>:组件的逻辑,定义组件的数据、方法、生命周期等
  3. <style>:组件的样式,定义组件的外观

6.2 <template> 部分

作用:定义组件的HTML结构,包含组件的视图模板。

特点

  • 每个SFC只能有一个<template>标签
  • 支持Vue的模板语法,如插值、指令、条件渲染、列表渲染等
  • 模板中的根元素可以是多个(Vue 3支持Fragment)
  • 支持使用v-bind:绑定属性
  • 支持使用v-on@绑定事件

示例

<template>
  <div class="greeting">
    <h1>{{ message }}</h1>
    <button @click="increment">点击计数</button>
    <p>计数: {{ count }}</p>
  </div>
  <div class="info">
    <p>这是另一个根元素</p>
  </div>
</template>

6.3 &lt;script&gt; 部分

作用:定义组件的逻辑,包括数据、方法、生命周期钩子等。

Vue 3支持两种脚本语法:

6.3.1 传统选项式API

<script>
export default {
  name: 'Greeting',
  data() {
    return {
      message: 'Hello Vue 3!',
      count: 0
    }
  },
  methods: {
    increment() {
      this.count++
    }
  },
  mounted() {
    console.log('组件已挂载')
  }
}
</script>

6.3.2 组合式API (&lt;script setup&gt;)

&lt;script setup&gt;是Vue 3.2+推荐的语法,提供了更简洁、更高效的开发体验。

特点

  • 自动注册组件,无需export default
  • 直接导入和使用组件,无需注册
  • 自动解析顶层变量和函数,可在模板中直接使用
  • 更好的TypeScript支持
  • 更小的打包体积

示例

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

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

// 定义方法
function increment() {
  count.value++
}

// 生命周期钩子
onMounted(() => {
  console.log('组件已挂载')
})
</script>

6.4 &lt;style&gt; 部分

作用:定义组件的样式,包括CSS、SCSS、Less等。

特点

  • 支持多种CSS预处理器,如SCSS、Less、Stylus等
  • 支持作用域样式(使用scoped属性)
  • 支持CSS Modules(使用module属性)
  • 可以有多个&lt;style&gt;标签,用于不同的样式需求

6.4.1 全局样式

<style>
/* 全局样式,会影响所有组件 */
body {
  font-family: Arial, sans-serif;
}
</style>

6.4.2 作用域样式 (scoped)

使用scoped属性可以使样式只作用于当前组件,避免样式冲突。

<style scoped>
/* 只作用于当前组件的样式 */
.greeting {
  background-color: #f0f0f0;
  padding: 20px;
  border-radius: 8px;
}

button {
  background-color: #42b883;
  color: white;
  border: none;
  padding: 8px 16px;
  border-radius: 4px;
  cursor: pointer;
}
</style>

6.4.3 CSS Modules (module)

使用module属性可以将CSS作为模块导入,避免样式名冲突。

<style module>
/* CSS Modules */
.container {
  background-color: #f0f0f0;
  padding: 20px;
}

.title {
  color: #35495e;
  font-size: 24px;
}
</style>

<template>
  <div :class="$style.container">
    <h1 :class="$style.title">{{ message }}</h1>
  </div>
</template>

6.4.4 使用CSS预处理器

需要先安装相应的预处理器依赖,然后在&lt;style&gt;标签中指定语言。

安装SCSS依赖

npm install -D sass

使用SCSS

<style scoped lang="scss">
$primary-color: #42b883;
$secondary-color: #35495e;

.greeting {
  background-color: lighten($primary-color, 20%);
  padding: 20px;
  border-radius: 8px;
  
  h1 {
    color: $secondary-color;
    margin-bottom: 10px;
  }
  
  button {
    background-color: $primary-color;
    color: white;
    border: none;
    padding: 8px 16px;
    border-radius: 4px;
    cursor: pointer;
    
    &:hover {
      background-color: darken($primary-color, 10%);
    }
  }
}
</style>

6.5 单文件组件的高级特性

6.5.1 组件导入和使用

&lt;script setup&gt;中,可以直接导入和使用组件,无需注册。

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

const count = ref(0)
</script>

<template>
  <HelloWorld msg="Hello from Parent" />
  <p>Count: {{ count }}</p>
</template>

6.5.2 Props定义

使用defineProps函数定义组件的props。

<script setup>
const props = defineProps({
  msg: {
    type: String,
    default: 'Hello'
  },
  count: {
    type: Number,
    required: true
  }
})
</script>

<template>
  <div>
    <p>{{ msg }}</p>
    <p>Count: {{ count }}</p>
  </div>
</template>

6.5.3 自定义事件

使用defineEmits函数定义组件可以触发的事件。

<script setup>
const emit = defineEmits(['increment', 'decrement'])

function handleIncrement() {
  emit('increment')
}

function handleDecrement() {
  emit('decrement', 2) // 可以传递参数
}
</script>

<template>
  <div>
    <button @click="handleIncrement">+</button>
    <button @click="handleDecrement">-</button>
  </div>
</template>

6.5.4 计算属性

使用computed函数定义计算属性。

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

const count = ref(0)
const doubleCount = computed(() => count.value * 2)
const isEven = computed(() => count.value % 2 === 0)
</script>

<template>
  <div>
    <p>Count: {{ count }}</p>
    <p>Double Count: {{ doubleCount }}</p>
    <p>Is Even: {{ isEven ? 'Yes' : 'No' }}</p>
    <button @click="count++">Increment</button>
  </div>
</template>

6.5.5 侦听器

使用watchwatchEffect函数定义侦听器。

<script setup>
import { ref, watch, watchEffect } from 'vue'

const count = ref(0)
const message = ref('')

// 监听单个值
watch(count, (newValue, oldValue) => {
  console.log(`Count changed from ${oldValue} to ${newValue}`)
})

// 监听多个值
watch([count, message], ([newCount, newMessage], [oldCount, oldMessage]) => {
  console.log('Count or message changed')
})

// 自动跟踪依赖
watchEffect(() => {
  console.log(`Count: ${count.value}, Message: ${message.value}`)
})
</script>

6.6 单文件组件的最佳实践

  1. 组件命名

    • 组件名使用PascalCase,如HelloWorld.vue
    • 文件名与组件名保持一致
    • 避免使用Vue保留字作为组件名
  2. 组件职责

    • 每个组件只负责一个功能
    • 组件大小适中,避免过大的组件
    • 复杂组件可以拆分为多个子组件
  3. 样式管理

    • 优先使用scoped样式,避免样式冲突
    • 复杂样式可以使用CSS预处理器
    • 全局样式放在专门的文件中
  4. 脚本语法

    • 优先使用&lt;script setup&gt;语法
    • 合理使用组合式API,按逻辑组织代码
    • 避免在模板中写复杂的表达式
  5. 性能优化

    • 使用v-memo优化频繁渲染的列表
    • 使用v-once优化静态内容
    • 合理使用computed缓存计算结果
    • 避免在模板中使用复杂的计算
  6. 代码组织

    • 导入语句按字母顺序排列
    • 先导入Vue核心API,再导入组件,最后导入工具函数
    • 响应式数据、计算属性、方法、生命周期钩子按逻辑顺序组织

6.7 单文件组件的编译过程

Vue单文件组件需要经过编译才能在浏览器中运行。编译过程包括:

  1. 模板编译:将模板转换为渲染函数
  2. 脚本编译:处理脚本,包括TypeScript转译、代码优化等
  3. 样式编译:处理样式,包括预处理器转译、CSS Modules处理等
  4. 合并输出:将编译后的模板、脚本和样式合并为最终的JavaScript模块

在开发环境中,Vite会实时编译SFC;在生产环境中,Vite会将SFC编译为优化后的JavaScript文件。

本集小结

在本集中,我们深入了解了Vue 3单文件组件的结构、特性和最佳实践:

  • 基本结构&lt;template&gt;&lt;script&gt;/&lt;script setup&gt;&lt;style&gt;
  • 模板部分:支持Vue模板语法,Vue 3支持多个根元素
  • 脚本部分:支持传统选项式API和组合式API
  • 样式部分:支持全局样式、作用域样式和CSS Modules
  • 高级特性:组件导入、Props定义、自定义事件、计算属性、侦听器等
  • 最佳实践:组件命名、职责划分、样式管理、性能优化等

单文件组件是Vue开发的核心,掌握好SFC的使用可以提高开发效率和代码质量。在下一集中,我们将学习Vue 3的模板语法基础,包括插值和指令。

« 上一篇 项目结构深度解析 下一篇 » 模板语法基础:插值与指令