{T}

概述

单文件组件(Single-File Component,简称 SFC)是 Vue.js 特有的一种组件定义方式,使用 .vue 作为文件扩展名。它将组件的模板、逻辑和样式封装在同一个文件中,实现了组件的高内聚和低耦合。

图表渲染中…

传统方式的局限性

在很多 Vue 项目中使用 Vue.component 来定义全局组件,紧接着用 new Vue({ el: '#container '}) 在每个页面内指定一个容器元素。

这种方式在很多中小规模的项目中运作的很好,在这些项目里 JavaScript 只被用来加强特定的视图。但当在更复杂的项目中,或者你的前端完全由 JavaScript 驱动的时候,下面这些缺点将变得非常明显:

问题描述影响
全局定义强制要求每个 component 中的命名不得重复大型项目命名冲突风险高
字符串模板缺乏语法高亮,多行 HTML 需要使用 \ 拼接代码可读性差,维护困难
不支持 CSSHTML 和 JavaScript 组件化时,CSS 明显被遗漏样式管理混乱
没有构建步骤限制只能使用 HTML 和 ES5 JavaScript无法使用预处理器提升开发效率

单文件组件的优势

文件扩展名为 .vue 的单文件组件为以上所有问题提供了解决方案:

基础示例

Vue SFC
<template>
  <p>{{ greeting }} World!</p>
</template>

<script>
  module.exports = {
    data: function () {
      return {
        greeting: "Hello"
      }
    }
  }
</script>

<style scoped>
  p {
    font-size: 2em;
    text-align: center;
  }
</style>

核心优势

特性说明
完整语法高亮编辑器可识别模板、脚本、样式区块,提供语法高亮
CommonJS 模块组件可导入导出,实现模块化开发
组件作用域 CSS通过 scoped 属性实现样式隔离
预处理器支持支持 Pug、Babel、TypeScript、SCSS、Stylus 等

预处理器支持

可以使用预处理器来构建简洁和功能更丰富的组件:

Vue SFC
<template lang="jade">
div
  p {{ greeting }} World!
  OtherComponent
</template>

<script>
  import OtherComponent from "./OtherComponent.vue"
  export default {
    components: {
      OtherComponent
    },
    data() {
      return {
        greeting: "Hello"
      }
    }
  }
</script>

<style lang="stylus" scoped>
  p
    font-size 2em
    text-align center
</style>

常用预处理器配置

区块预处理器lang 属性值
templatePuglang="pug"lang="jade"
scriptTypeScriptlang="ts"
scriptBabel默认支持,无需指定
styleSCSSlang="scss"
styleSASSlang="sass"
styleLesslang="less"
styleStyluslang="stylus"

关注点分离

关注点分离不等于文件类型分离。 在现代 UI 开发中,相比于把代码库分离成三个大的层次并将其相互交织起来,把它们划分为松散耦合的组件再将其组合起来更合理一些。

在一个组件里,其模板、逻辑和样式是内部耦合的,并且搭配在一起实际上使得组件更加内聚且更可维护。

文件分离方案

即便你不喜欢单文件组件,可以把 JavaScript、CSS 分离成独立的文件然后做到热重载和预编译:

html
<!-- my-component.vue -->
<template>
  <div>This will be pre-compiled</div>
</template>
<script src="./my-component.js"></script>
<style src="./my-component.css"></style>
javascript
// my-component.js
export default {
  data() {
    return {
      message: "Hello Vue!"
    }
  }
}
css
/* my-component.css */
div {
  color: #42b983;
}

vue-loader 配置

关于 vue-loader 的详细 webpack 配置(包括基础配置、VueLoaderPlugin、配置选项、资源处理、CSS Modules、热重载、预处理器配置、PostCSS 配置、scoped 样式等),请参阅本文件「vue-loader 详细配置」章节。

完整组件示例

以下是一个功能完整的单文件组件示例:

Vue SFC
<template>
  <div class="user-card">
    <img :src="user.avatar" :alt="user.name" class="avatar" />
    <div class="info">
      <h3>{{ user.name }}</h3>
      <p>{{ user.email }}</p>
      <button @click="handleClick">查看详情</button>
    </div>
  </div>
</template>

<script>
  export default {
    name: "UserCard",
    props: {
      user: {
        type: Object,
        required: true,
        validator(value) {
          return value.name && value.email
        }
      }
    },
    methods: {
      handleClick() {
        this.$emit("view-detail", this.user)
      }
    }
  }
</script>

<style scoped>
  .user-card {
    display: flex;
    align-items: center;
    padding: 16px;
    border: 1px solid #e0e0e0;
    border-radius: 8px;
  }

  .avatar {
    width: 60px;
    height: 60px;
    border-radius: 50%;
    margin-right: 16px;
  }

  .info h3 {
    margin: 0 0 8px 0;
    color: #333;
  }

  .info p {
    margin: 0 0 12px 0;
    color: #666;
  }

  button {
    padding: 8px 16px;
    background-color: #42b983;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
  }

  button:hover {
    background-color: #3aa876;
  }
</style>

常见问题解答

1. 单文件组件如何实现样式隔离?

使用 scoped 属性,vue-loader 会为组件内的 CSS 添加唯一属性选择器:

Vue SFC
<style scoped>
  .example {
    color: red;
  }
</style>

编译后:

css
.example[data-v-f3f3eg9] {
  color: red;
}

2. 如何在单文件组件中使用 TypeScript?

<script> 标签添加 lang="ts" 属性:

Vue SFC
<script lang="ts">
  import { Vue, Component, Prop } from "vue-property-decorator"

  @Component
  export default class MyComponent extends Vue {
    @Prop({ type: String, required: true })
    message!: string

    count: number = 0

    increment(): void {
      this.count++
    }
  }
</script>

3. 单文件组件可以继承吗?

可以,使用 extends 选项或 mixins:

Vue SFC
<script>
  import BaseButton from "./BaseButton.vue"

  export default {
    extends: BaseButton,
    data() {
      return {
        // 覆盖或扩展父组件数据
      }
    }
  }
</script>

4. 如何处理全局样式和组件样式?

Vue SFC
<style>
  /* 全局样式,无 scoped */
  .global-class {
    color: blue;
  }
</style>

<style scoped>
  /* 组件作用域样式 */
  .local-class {
    color: red;
  }
</style>

5. 单文件组件如何实现按需加载?

使用动态导入语法:

javascript
const AsyncComponent = () => import("./AsyncComponent.vue")

export default {
  components: {
    AsyncComponent
  }
}

SFC 编译管线

图表渲染中…

构建工具推荐

Vue CLI

CLI 会为你搞定大多数工具的配置问题,同时也支持细粒度自定义配置项

bash
# 安装 Vue CLI
npm install -g @vue/cli

# 创建项目
vue create my-project

# 运行开发服务器
npm run serve

手动配置 webpack

有时你会想从零搭建你自己的构建工具,这时你需要通过 Vue Loader 手动配置 webpack。关于学习更多 webpack 的内容,请查阅其官方文档Webpack Academy

相关资源