SFC 概述
将一个组件的逻辑 (JavaScript),模板 (HTML) 和样式 (CSS) 封装在同一个.vue
文件里,即单文件组件( Single-File Components,缩写为 SFC)。
每一个 *.vue
文件都由三种顶层语言块构成:<template>
、<script>
和 <style>
- 最多可以包含一个顶层
<template>
块,其包裹的内容将会被提取、传递给@vue/compiler-dom
,预编译为 JavaScript 渲染函数,并附在导出的组件上作为其 render 选项。 - 最多可以包含一个
<script>
块。(使用<script setup>
的情况除外) - 最多可以包含一个
<script setup>
。(不包括一般的<script>
) , 这个脚本块将被预处理为组件的 setup() 函数,这意味着它将为每一个组件实例都执行。<script setup>
中的顶层绑定都将自动暴露给模板。 - 可以包含多个
<style>
标签,可以使用 scoped 或 module attribute 来帮助封装当前组件的样式。使用了不同封装模式的多个<style>
标签可以被混合入同一个组件。 - 语言块内的注释,使用各自语言对应的注释语法
- 语言块外的注释,使用 HTML 的注释语法
SFC 的优点
- 使用熟悉的 HTML、CSS 和 JavaScript 语法编写模块化的组件
- 让本来就强相关的关注点自然内聚
- 预编译模板,避免运行时的编译开销
- 组件作用域的 CSS
- 在使用组合式 API 时语法更简单
- 通过交叉分析模板和逻辑代码能进行更多编译时优化
- 更好的 IDE 支持,提供自动补全和对模板中表达式的类型检查
- 开箱即用的模块热更新 (HMR) 支持
SFC 的缺点
必须使用构建工具
SFC 的使用场景
- 单页面应用 (SPA)
- 静态站点生成 (SSG)
- 任何值得引入构建步骤以获得更好的开发体验 (DX) 的项目
SFC 的原理
SFC 会在打包构建过程中,通过@vue/compiler-sfc
编译为标准的 JavaScript 和 CSS。
- 开发阶段:
<style>
标签会注入成原生的<style>
标签以支持热更新 - 生产环境:
<style>
标签会被抽取、合并成单独的 CSS 文件
使用预处理器
需在语言块标签的 lang 属性中声明预处理器
script 使用 TypeScript
template 使用 Pug
style 使用 Sass
<script setup>
SFC 中使用组合式 API 默认会推荐使用<script setup>
语法糖,相比于普通的 <script>
,它具有更多优势:
- 更少的样板内容,更简洁的代码。
- 能够使用纯 TypeScript 声明 props 和自定义事件。
- 更好的运行时性能 (其模板会被编译成同一作用域内的渲染函数,避免了渲染上下文代理对象)。
- 更好的 IDE 类型推导性能 (减少了语言服务器从代码中抽取类型的工作)。
运行机制
- 普通的
<script>
只在组件被首次引入的时候执行一次 -
<script setup>
中的代码会被编译成组件 setup() 函数的内容,在每次组件实例被创建的时候执行。
顶层声明在模板中可直接使用
递归组件
名为 FooBar.vue 的组件可以在其模板中用 <FooBar/>
引用它自己。
这种方式相比于导入的组件优先级更低。
如果有具名的导入和组件自身推导的名字冲突了,可以为导入的组件添加别名:
从单文件中导入多个组件
将组件嵌套在对象属性中,使用带 . 的组件标签在模板中使用。
使用自定义指令
自定义指令必须遵循 vNameOfDirective
命名规范(自定义指令名的首字母必须是 v
,后续跟首字母大写的自定义指令名称 )
导入的自定义指令,可以通过重命名来使其符合命名规范:
defineProps
无需导入,可直接使用,参数与 props 选项相同
TS 中的写法
添加默认值需使用 withDefaults
defineEmits
无需导入,可直接使用,参数与 emits 选项相同
TS 中的写法
defineModel
vue3.4 新增
无需导入,可直接使用,用来声明一个双向绑定 prop,详细用法见
https://cn.vuejs.org/guide/components/v-model.html
解构 defineModel() 的返回值可以获取 v-model 指令使用的修饰符
通过 get 和 set 转换器选项可以在同步回父组件时对其值进行转换
TS 中
defineExpose
使用 <script setup>
的组件默认是关闭的(通过模板引用或者 $parent 链无法获取到组件中的绑定,如声明的变量,导入的函数等)
需通过 defineExpose 来显式指定对外暴露的属性:
此时,父组件通过模板引用的方式获取到的当前组件的实例为 { a: number, b: number }
defineOptions
Vue 3.3 新增
用于声明组件选项,避免使用单独的 <script>
块
选项中无法访问 <script setup>
中不是字面常数的局部变量。
defineSlots
Vue 3.3 新增
用于为 IDE 提供插槽名称和 props 类型检查的类型提示。
defineSlots() 只接受类型参数,没有运行时参数。类型参数应该是一个类型字面量,其中属性键是插槽名称,值类型是插槽函数。函数的第一个参数是插槽期望接收的 props,其类型将用于模板中的插槽 props。返回类型目前被忽略,可以是 any,但我们将来可能会利用它来检查插槽内容。
它还返回 slots 对象,该对象等同于在 setup 上下文中暴露或由 useSlots() 返回的 slots 对象。
与普通的 <script>
一起使用
仅在有下述需求时使用:
- 声明模块的具名导出 (named exports)。
- 运行只需要在模块作用域执行一次的副作用,或是创建单例对象。
在顶层可以直接使用 await
因为代码会被编译成 async setup()
获取组件实例
通过 getCurrentInstance 获取
资源拆分 src
如果不喜欢单文件组件这样的形式,可以按下方代码拆分单独的 HTML、JavaScript 和 CSS 文件
由于模块执行语义的差异,<script setup>
中的代码依赖单文件组件的上下文。当将其移动到外部的 .js 或者 .ts 文件中的时候,对于开发者和工具来说都会感到混乱。因此,<script setup>
内的 JS 代码无法使用 src 拆分。