文章目录
介绍
Vue的单文件组件,即*vue
文件,简称SFC
是一种特殊的文件格式,是我们能够将一个Vue组件的模板(<template></template>
)、逻辑(<script></script>
)、样式(<style></style>
)封装在单个文件中。
- 选项式:
<script>
export default {
data() {
return {
greeting: 'Hello World!'
}
}
}
</script>
<template>
<p class="greeting">{{ greeting }}</p>
</template>
<style>
.greeting {
color: red;
font-weight: bold;
}
</style>
- 组合式:
<script setup>
import { ref } from 'vue'
const greeting = ref('Hello World!')
</script>
<template>
<p class="greeting">{{ greeting }}</p>
</template>
<style>
.greeting {
color: red;
font-weight: bold;
}
</style>
单文件组件SFC的优点
使用单文件组件必须使用构建工具
,但是有如下优点:
- 使用熟悉的 HTML、CSS 和 JavaScript 语法编写模块化的组件
- 预编译模板,避免运行时的编译开销
- 热更新 (HMR)
- 拥有组件作用域的 CSS功能 —— scoped
- 在使用组合式 API 时语法更简单
- 通过
交叉分析模板
和逻辑代码能进行更多编译时优化 - 让本来就强相关的关注点自然内聚 —— 指的是使用
html、css、js
在一个文件中编写。
单文件组件的工作方式
Vue SFC 是一个框架指定的文件格式,因此必须交由 @vue/compiler-sfc
编译为标准的 JavaScript 和 CSS
。
- 一个编译后的 SFC 是一个标准的 JavaScript(ES) 模块这也意味着在构建配置正确的前提下,你可以像导入其他 ES 模块一样导入 SFC:
import MyComponent from './MyComponent.vue'
export default {
components: {
MyComponent
}
}
- SFC 中的
<style>
标签一般会在开发时
注入成原生的<style>
标签以支持热更新,而生产环境
下它们会被抽取、合并成单独的 CSS
文件。
在实际开发中我们直接使用胡扯哥哥SFC编译器的构建工具来编辑SFC:Vite, VueCLI(基于 webpack)
单文件组件的语法定义
基本语法
一个Vue
单文件组件在语法上式兼容HTML的。每一个 *.vue 文件都由三种顶层语言块构成:<template>、<script> 和 <style>
,以及一些其他的自定义块
:
<template>
<div class="example">{{ msg }}</div>
</template>
<script>
export default {
data() {
return {
msg: 'Hello world!'
}
}
}
</script>
<style>
.example {
color: red;
}
</style>
<custom1>
自定义块,可能是组件的文档。
</custom1>
-
<template>
一个单文件组件最多包含一个顶层的<template>
块。
语法包裹的内容将会被提取传递給@vue/compiler-dom
,预编译为JS渲染函数,并附在导出的组件上作为其render
项。 -
<script>
每个.vue
文件最多可以包含一个<script>
块,使用<script setup>
的情况除外。
在该标签中默认导出Vue的组件选择对象,可以是一个对象字面量
或是defineComponent函数
(是用于定义组件的一个函数,可以提供类型推导)的返回值。
这个脚本代码块将作为ES模块执行 -
<script setup>
每个.vue
文件最多可以包含一个<script setup>
。
这个脚本块将被预处理为组件的setup()
函数。 -
<style>
每个.vue
文件可以包含多个<style>
标签。
一个<style>
标签可以使用scoped 或 module
属性来帮助封装当前组件的样式。使用了不同封装模式的多个<style>
标签可以被混合入同一个组件。 -
自定义块
在一个 *.vue 文件中可以为任何项目特定需求使用额外的自定义块。如,一个用作写文档的<docs>
块。
自定义块的处理需要依赖工具链:根据不同的构建工具需要进行不同的处理。
– 如果式vite需要使用一个自定义Vite插件
将自定义块转换成可执行的JavaScript代码。import vue from '@vitejs/plugin-vue' import yaml from 'js-yaml' const vueI18nPlugin = { name: 'vue-i18n', transform(code, id) { // 如果 .vue 文件没有 i18n 块,则返回 if (!/vue&type=i18n/.test(id)) { return } // 解析 yaml if (/\.ya?ml$/.test(id)) { code = JSON.stringify(yaml.load(code.trim())) } // 将值挂载到组件实例的 i18n 属性上 return `export default Comp => { Comp.i18n = ${code} }` }, } export default { plugins: [vue(), vueI18nPlugin], }
之后就可以使用
<i18n>
自定义块了– 如果使用 Vue CLI 或只是 webpack,需要使用一个
loader
来配置如何转换匹配到的自定义块。loader是一个配置项,值一般是一个文件的路径里面的逻辑是如何处理该自定义块
匹配自定义块<docs>
:
wepback.config.jsmodule.exports = { module: { rules: [ { resourceQuery: /blockType=docs/, // 自定义块的名称 loader: require.resolve('./docs-loader.js') } ] } }
./docs-loader.js
module.exports = function (source, map) { this.callback( null, `export default function (Component) { Component.options.__docs = ${ JSON.stringify(source) } }`, map ) }
自动名称推到
在以下情况当文件组件会自动推导组件名称
- 开发警告中需要格式化组件名时
- DevTools中观察组件时
- 递归组件自引用时。如一个名为
FooBar.vue
的组件可以在模板中通过<FooBar/>
引用自己(递归组件肯定要有终止的条件判断,否则会死循环)。
预处理器
代码块上可以使用lang
属性来声明预处理器语言,lang可以在任何块上使用。
<script>
标签中使用TypeScript
语法编写代码。
<script lang="ts">
// use TypeScript
</script>
- 在
<style>
标签中使用sass,表示以sass语法编写css
<style lang="scss">
$primary-color: #333;
body {
color: $primary-color;
}
</style>
- 在
<template>
上使用Pug
<template lang="pug">
p {{ msg }}
</template>
<style lang="scss">
$primary-color: #333;
body {
color: $primary-color;
}
</style>
pug
Pug(之前被称为Jade)是一个高性能、功能丰富、运行在Node.js上的模板引擎。
Pug 提供了一种简洁的语法来编写 HTML。 例如,不需要写闭合标签,元素的嵌套由缩进表示,允许我们在模板中插入 JavaScript 变量和运行 JavaScript 代码等等。
pug代码:
doctype html
html
head
title= pageTitle
body
h1 Hello, world!
翻译成html
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
</head>
<body>
<h1>Hello, world!</h1>
</body>
</html>
关于预处理器的可选项
预处理器的可选项和使用的工具链有关,不同的工具链支持不同的预处理器。
- vite:css预处理器支持.scss, .sass, .less, .styl 和 .stylus
- vueCLI : .sass, .less 和 .stylus
- webpack + vue-loader : .sass, .less, .scss, .stylus, postcss
src导入
如果你更喜欢将 *.vue 组件分散到多个文件中,可以为一个语块使用 src 这个 attribute 来导入一个外部文件:
<template src="./template.html"></template>
<style src="./style.css"></style>
<script src="./script.js"></script>
- 也可以从npm依赖中导入
<!-- 从所安装的 "todomvc-app-css" npm 包中导入一个文件 -->
<style src="todomvc-app-css/index.css" />
单文件组件的CSS功能
组件作用域-scoped
当<style>
标签带有scoped
属性的时候它的css
只会影响当前组件
的元素。和Shadow DOM
中的样式封装类似。
实现原理
实现只在当前组件中起样式依赖的是PostCSS实现的。
PostCSS的作用是将如下内容
<style scoped>
.example {
color: red;
}
</style>
<template>
<div class="example">hi</div>
</template>
转换成
<style>
.example[data-v-f3f3eg9] {
color: red;
}
</style>
<template>
<div class="example" data-v-f3f3eg9>hi</div>
</template>
实现样式隔离。
子组件的根元素
使用scoped
之后父组件的样式将不会渗透到子组件中。不过子组件的根节点
会同时被父组件的作用域样式和子组件的作用域样式影响,并且父组件的样式优先级高于子组件。
这样设计是为了让父组件可以从布局样式出发调整期子组件根元素的样式。
深度选择器deep
处于 scoped
样式中的选择器如果想要影响到子组件
,可以使用 :deep()
这个伪类。
原理:
<style scoped>
.a :deep(.b) {
/* ... */
}
</style>
会被编译成:
.a[data-v-f3f3eg9] .b {
/* ... */
}
插槽选择器
默认情况下子组件的作用域样式时不会影响到<slot/>
渲染出来的内容的,因为他们被认为是父组件所持有并传递进来的。使用slotted
伪类以明确地将插槽内容作为选择器地目标。
<style scoped>
// 将插槽中的div的字体样式设置为红色
:slotted(div) {
color: red;
}
</style>
全局选择器
<style>
标签不添加scoped
时定义的样式时全局样式,如果使用了scoped
定义的样式是局部作用域样式,那如何在scoped
的局部作用域中定义全局样式呢,两种办法:
- 法一:新建一个
<style></style>
标签
<style>
/* 全局样式 */
</style>
<style scoped>
/* 局部样式 */
</style>
- 法二:使用
:global
伪类实现
<style scoped>
:global(.red) {
color: red;
}
</style>
作用域样式须知
在作用域样式(scoped
)中类选择器比标签选择器更节省性能:
由于浏览器对不同的CSS样式渲染的方式不同,使用标签结合属性选择器
的渲染速度比类结合属性选择器
的渲染渲染速度慢很多,所以在作用域样式(scoped
)中类选择器比标签选择器更节省性能。
CSS Modules
CSSModules是一个 CSS 文件
,默认情况下,所有类名称和动画名称都在本地范围内。所有 URL (url(...)) 和 @imports
均采用模块请求格式(./xxx 和 …/xxx 表示相对,xxx 和 xxx/yyy 表示在模块文件夹中,即在 node_modules
中)。
在vue中一个<style module>
会被编译成CSS Modules
:
- 将生成的CSS class作为
$style
对象暴露给组件,在组件模板中使用$style
引用样式。 - 并且得到的样式将被哈希化,所以也实现了将css样式仅作用于当前组件的效果实现
样式隔离
。
<template>
<p :class="$style.red">This should be red</p>
</template>
<style module>
.red {
color: red;
}
</style>
自定义modules的注入名称
通过给modules属性赋值来自定义注入的class对象的属性名。
<template>
<p :class="classes.red">red</p>
</template>
<style module="classes">
.red {
color: red;
}
</style>
和组合式API一起使用
可以通过useCssModule
API在setup()
和<script setup>
中访问注入的class。对于使用了自定义注入名称的<style module>
块,该名称需要作为useCssModule
的第一个参数。
import { useCssModule } from 'vue'
// 在 setup() 作用域中...
// 默认情况下, 返回 <style module> 的 class
useCssModule()
// 具名情况下, 返回 <style module="classes"> 的 class
useCssModule('classes')
CSS的v-bind()
单文件组件的 <style>
标签支持使用 v-bind()
CSS 函数将 CSS 的值链接到动态的组件状态:
<script setup>
const theme = {
color: 'red'
}
</script>
<template>
<p>hello</p>
</template>
<style scoped>
p {
color: v-bind('theme.color');
}
</style>
实际的值会被编译成哈希化的 CSS 自定义属性,因此 CSS 本身仍然是静态的。
补充: SPA和SFC
-
SPA:单页面应用程序。是一种设计模式,是一种广泛应用于现代Web开发的架构模式。
- 它旨在通过在用户的浏览器中加载单个HTML页面并在该页面上动态更新内容和视图,而不是传统的每次用户交互都重新加载整个页面的方式。SPA可以显著改善用户体验,因为它们通常更快、更流畅,感觉更像是本地桌面应用。
- 是一种设计模式并不是Vue独有的,React、Angular同样支持SPA
-
SFC:单文件组件,是一种编写文件的方式用于实现组件化开发。
- 单文件是指允许开发者在一个.vue文件中定义一个完整的组件,包括模板(Template)、脚本(Script)和样式(Style)部分,这样的组件可以独立开发和复用。
- 是由Vue最先提出来的概念。
两者的关系:
在构建SPA时,往往会大量使用组件化开发来组织和管理UI结构及业务逻辑,而Vue.js的SFC则极大地便利了SPA中的组件化开发。SPA架构天然适合采用组件化的方式来组织界面,而SFC作为Vue.js中的一个重要特性,是构建SPA时不可或缺的工具之一。所以在基于Vue.js的SPA项目中,SFC是非常常见的实践。不过,SFC并非SPA特有的,它也可以用于非SPA类型的Vue.js项目或者其他需要组件化开发的场景。