vue3【详解】单文件组件 SFC(含SFC的优点、缺点、使用场景、原理、使用预处理器、<script setup>语法详解、资源拆分)
SFC 概述
将一个组件的逻辑 (JavaScript),模板 (HTML) 和样式 (CSS) 封装在同一个.vue
文件里,即单文件组件( Single-File Components,缩写为 SFC)。
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<button @click="count++">Count is: {{ count }}</button>
</template>
<style scoped>
button {
font-weight: bold;
}
</style>
每一个 *.vue
文件都由三种顶层语言块构成:<template>
、<script>
和 <style>
-
最多可以包含一个顶层
<template>
块,其包裹的内容将会被提取、传递给@vue/compiler-dom
,预编译为 JavaScript 渲染函数,并附在导出的组件上作为其 render 选项。 -
最多可以包含一个
<script>
块。(使用<script setup>
的情况除外) -
最多可以包含一个
<script setup>
。(不包括一般的<script>
) , 这个脚本块将被预处理为组件的 setup() 函数,这意味着它将为每一个组件实例都执行。<script setup>
中的顶层绑定都将自动暴露给模板。 -
可以包含多个
<style>
标签,可以使用 scoped 或 module attribute 来帮助封装当前组件的样式。使用了不同封装模式的多个<style>
标签可以被混合入同一个组件。 -
语言块内的注释,使用各自语言对应的注释语法
// js 的注释
<!-- html 的注释 -->
/* css 的注释 */
-
语言块外的注释,使用 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
<script lang="ts">
// ts 代码
</script>
template 使用 Pug
<template lang="pug">
p {{ msg }}
</template>
style 使用 Sass
<style lang="scss">
$primary-color: #333;
body {
color: $primary-color;
}
</style>
<script setup>
SFC 中使用组合式 API 默认会推荐使用<script setup>
语法糖,相比于普通的 <script>
,它具有更多优势:
- 更少的样板内容,更简洁的代码。
- 能够使用纯 TypeScript 声明 props 和自定义事件。
- 更好的运行时性能 (其模板会被编译成同一作用域内的渲染函数,避免了渲染上下文代理对象)。
- 更好的 IDE 类型推导性能 (减少了语言服务器从代码中抽取类型的工作)。
运行机制
- 普通的
<script>
只在组件被首次引入的时候执行一次 <script setup>
中的代码会被编译成组件 setup() 函数的内容,在每次组件实例被创建的时候执行。
顶层声明在模板中可直接使用
<script setup>
// 导入的函数
import { capitalize } from './helpers'
// 导入的组件(强烈建议使用 PascalCase 格式)
import MyComponent from './MyComponent.vue'
// 变量
const msg = 'Hello!'
// 函数
function log() {
console.log(msg)
}
</script>
<template>
<button @click="log">{{ msg }}</button>
<div>{{ capitalize('hello') }}</div>
<MyComponent />
</template>
递归组件
名为 FooBar.vue 的组件可以在其模板中用 <FooBar/>
引用它自己。
这种方式相比于导入的组件优先级更低。
如果有具名的导入和组件自身推导的名字冲突了,可以为导入的组件添加别名:
import { FooBar as FooBarChild } from './components'
从单文件中导入多个组件
将组件嵌套在对象属性中,使用带 . 的组件标签在模板中使用。
<script setup>
import * as Form from './form-components'
</script>
<template>
<Form.Input>
<Form.Label>label</Form.Label>
</Form.Input>
</template>
使用自定义指令
自定义指令必须遵循 vNameOfDirective
命名规范(自定义指令名的首字母必须是 v
,后续跟首字母大写的自定义指令名称 )
<script setup>
const vMyDirective = {
beforeMount: (el) => {
// 在元素上做些操作
}
}
</script>
<template>
<h1 v-my-directive>This is a Heading</h1>
</template>
导入的自定义指令,可以通过重命名来使其符合命名规范:
<script setup>
import { myDirective as vMyDirective } from './MyDirective.js'
</script>
defineProps
无需导入,可直接使用,参数与 props 选项相同
<script setup>
const props = defineProps({
foo: String,
bar?: Number
})
</script>
TS 中的写法
const props = defineProps<{
foo: string,
bar?: number
}>()
添加默认值需使用 withDefaults
export interface Props {
msg?: string
labels?: string[]
}
const props = withDefaults(defineProps<Props>(), {
msg: 'hello',
labels: () => ['one', 'two']
})
defineEmits
无需导入,可直接使用,参数与 emits 选项相同
const emit = defineEmits(['change', 'delete'])
TS 中的写法
const emit = defineEmits<{
(e: 'change', id: number): void
(e: 'update', value: string): void
}>()
// 3.3+:另一种更简洁的语法
const emit = defineEmits<{
change: [id: number] // 具名元组语法
update: [value: string]
}>()
defineModel
vue3.4 新增
无需导入,可直接使用,用来声明一个双向绑定 prop,详细用法见
https://cn.vuejs.org/guide/components/v-model.html
解构 defineModel() 的返回值可以获取 v-model 指令使用的修饰符
const [modelValue, modelModifiers] = defineModel()
// 对应 v-model.trim
if (modelModifiers.trim) {
// ...
}
通过 get 和 set 转换器选项可以在同步回父组件时对其值进行转换
const [modelValue, modelModifiers] = defineModel({
// get() 省略了,因为这里不需要它
set(value) {
// 如果使用了 .trim 修饰符,则返回裁剪过后的值
if (modelModifiers.trim) {
return value.trim()
}
// 否则,原样返回
return value
}
})
TS 中
const modelValue = defineModel<string>()
// ^? Ref<string | undefined>
// 用带有选项的默认 model,设置 required 去掉了可能的 undefined 值
const modelValue = defineModel<string>({ required: true })
// ^? Ref<string>
const [modelValue, modifiers] = defineModel<string, "trim" | "uppercase">()
// ^? Record<'trim' | 'uppercase', true | undefined>
defineExpose
使用 <script setup>
的组件默认是关闭的(通过模板引用或者 $parent 链无法获取到组件中的绑定,如声明的变量,导入的函数等)
需通过 defineExpose 来显式指定对外暴露的属性:
<script setup>
import { ref } from 'vue'
const a = 1
const b = ref(2)
defineExpose({
a,
b
})
</script>
此时,父组件通过模板引用的方式获取到的当前组件的实例为 { a: number, b: number }
defineOptions
Vue 3.3 新增
用于声明组件选项,避免使用单独的 <script>
块
<script setup>
defineOptions({
inheritAttrs: false,
customOptions: {
/* ... */
}
})
</script>
选项中无法访问 <script setup>
中不是字面常数的局部变量。
defineSlots
Vue 3.3 新增
用于为 IDE 提供插槽名称和 props 类型检查的类型提示。
<script setup lang="ts">
const slots = defineSlots<{
default(props: { msg: string }): any
}>()
</script>
defineSlots() 只接受类型参数,没有运行时参数。类型参数应该是一个类型字面量,其中属性键是插槽名称,值类型是插槽函数。函数的第一个参数是插槽期望接收的 props,其类型将用于模板中的插槽 props。返回类型目前被忽略,可以是 any,但我们将来可能会利用它来检查插槽内容。
它还返回 slots 对象,该对象等同于在 setup 上下文中暴露或由 useSlots() 返回的 slots 对象。
与普通的 <script>
一起使用
仅在有下述需求时使用:
- 声明模块的具名导出 (named exports)。
- 运行只需要在模块作用域执行一次的副作用,或是创建单例对象。
<script>
// 普通 <script>,在模块作用域下执行 (仅一次)
runSideEffectOnce()
</script>
<script setup>
// 在 setup() 作用域中执行 (对每个实例皆如此)
</script>
在顶层可以直接使用 await
因为代码会被编译成 async setup()
<script setup>
const post = await fetch(`/api/post/1`).then((r) => r.json())
</script>
获取组件实例
通过 getCurrentInstance 获取
资源拆分 src
如果不喜欢单文件组件这样的形式,可以按下方代码拆分单独的 HTML、JavaScript 和 CSS 文件
<template src="./template.html"></template>
<style src="./style.css"></style>
<script src="./script.js"></script>
由于模块执行语义的差异,<script setup>
中的代码依赖单文件组件的上下文。当将其移动到外部的 .js 或者 .ts 文件中的时候,对于开发者和工具来说都会感到混乱。因此,<script setup>
内的 JS 代码无法使用 src 拆分。
原文地址:https://blog.csdn.net/weixin_41192489/article/details/140466279
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!