组件✅
本主题包含对 Vue 组件概念的深入考察,涵盖组件的定义、类型、生命周期、通信策略等方面的内容。
Vue 的组件有哪些类型?
答案
组件的类型 Vue 下组件按照有无状态,加载方式分为
- 函数式组件 函数组件无任何状态就是一个纯函数,接受属性等配置,返回一个 UI 片段,函数组件不支持各种 hook
- Vue 2.x 中单文件组件通过定义 functional 属性,或者在配置中添加
functional: true
来申明为函数组件,注意该策略在 Vue 3.x 中不再支持。 - Vue 3.x 中函数式组件直接是一个 function
- Vue 2.x 中单文件组件通过定义 functional 属性,或者在配置中添加
- 状态组件(Stateless Component)Vue 3.x 中非函数组件都可以称为状态组件,状态组件支持各种 hook 和生命周期函数。可以采用选项式 API 或组合式 API 来定义。
- 异步组件 采用 defineAsyncComponent 来定义的组件,异步组件会在需要时才加载,异步组件可以是函数式组件或状态组件。
- 动态组件 通过
<component>
标签来定义的组件,动态组件会根据is
属性的值来加载不同的组件。动态组件可以是函数式组件或状态组件。
示例说明
组件生命周期?
答案
见官方生命周期钩子

核心钩子说明
选型式API | 组合API | 时机 | 备注 |
---|---|---|---|
beforeCreate | 采用 setup 模拟,注意 setup 在 beforeCreate 之前触发 | props 解析完成组件实例完成初始化 | 触发接口调用、副作用绑定等 |
created | 采用 setup 模拟,注意 setup 在 beforeCreate 之前触发 | 组件实例完成初始化,还未挂载 | 触发接口调用、副作用绑定等 |
beforeMount | onBeforeMount() | 组件挂载前,DOM 还未渲染 | 挂载前准备工作 |
mounted | onMounted() | 组件挂载后,DOM 已渲染 | DOM 操作 |
beforeUpdate | onBeforeUpdate | 组件数据更新,DOM 树更新前 | 保存之前dom状态,例如滚动回到之前 |
updated | onUpdated | 组件数据更新,DOM 树更新后 | - |
beforeUnmount | onBeforeUnmount | 组件卸载前 | 清理资源或事件 |
unmounted | onUnmounted | 组件卸载后 | 完成清理工作、性能或组件销毁打点 |
此外还支持如下钩子
选型式API | 组合API | 时机 | 备注 |
---|---|---|---|
errorCaptured | onErrorCaptured() | 捕获后代组件传递的错误时调用,注意无法捕获 setup 中的异步错误 | 错误监控与处理 |
renderTracked | onRenderTracked() | 响应式依赖被组件的渲染作用追踪后调用 | 调试依赖追踪,仅 dev 可用 |
renderTriggered | onRenderTriggered() | 响应式依赖被组件触发了重新渲染之后 | 调试渲染触发,仅 dev 可用 |
activated | onActivated() | 组件实例是 <KeepAlive> 缓存树的一部分,当组件被插入到 DOM 中时调用。 | 缓存组件激活逻辑 |
deactivated | onDeactivated() | 组件实例是 <KeepAlive> 缓存树的一部分,当组件从 DOM 中被移除时调用。 | 缓存组件停用逻辑 |
serverPrefetch | onServerPrefetch() | 当组件实例在服务器上被渲染之前要完成的异步函数。 | 服务端数据预取,仅 SSR 可用 |
示例说明
组件包含哪些核心选项?
答案
分类 | 选项 | 描述 |
---|---|---|
状态选项 | data | 定义组件的响应式数据。 |
props | 定义组件的外部属性。 | |
computed | 定义计算属性,基于其他响应式数据计算得出。 | |
methods | 定义组件的方法。 | |
watch | 监听响应式数据的变化并执行回调。 | |
emits | 定义组件可以抛出的事件。 | |
expose | 定义组件实例暴露的属性或方法。 | |
渲染选项 | template | 定义组件的模板。 |
render | 定义组件的渲染函数。 | |
compilerOptions | 配置模板编译选项。 | |
slots | 定义插槽内容。 | |
生命周期 | beforeCreate 、created 、beforeMount 、mounted | 组件的创建和挂载阶段的生命周期钩子。 |
beforeUpdate 、updated | 组件更新阶段的生命周期钩子。 | |
beforeUnmount 、unmounted | 组件卸载阶段的生命周期钩子。 | |
errorCaptured 、renderTracked 、renderTriggered | 错误捕获和渲染追踪相关的生命周期钩子。 | |
activated 、deactivated | <KeepAlive> 缓存组件的激活和停用钩子。 | |
serverPrefetch | 服务端渲染时的预取数据钩子,仅 SSR 可用。 | |
组合选项 | provide,inject | 提供数据给后代组件消费 |
mixins | 混入复用的组件选项,注意 mixin 是 merge 操作。 | |
extends | 继承另一个组件的选项。 | |
其他杂项 | name | 定义组件的名称。 |
inheritAttrs | 是否继承未被显式声明的属性。 | |
components | 注册局部组件。 | |
directives | 注册局部指令。 | |
组件实例 | $data 、$props 、$el 、$options | 组件实例的属性。 |
$parent 、$root | 父组件和根组件实例。 | |
$slots 、$refs | 插槽内容和子组件引用。 | |
$attrs | 未被声明的特性集合。 | |
$watch() 、$emit() 、$forceUpdate() 、$nextTick() | 实例方法,用于监听、触发事件、强制更新和延迟执行回调。 |
什么是单文件组件, 如何使用?
答案
- 单文件组件是 Vue.js 中一种用于组织组件的文件格式,它将组件的模板、脚本和样式封装在一个文件中,通常以
.vue
为扩展名。通过单文件组件,可以更方便地管理和维护组件的结构和样式。 - 单文件组件支持如下语言块
<template>
:定义组件的 HTML 模板,使用 Vue 的模板语法。做多只能包含一个<script>
:定义组件的 JavaScript 逻辑,使用 Vue 的选项式 API 或组合式 API。最多只能包含一个,如果使用<script setup>
则每种只能存在一个<style>
:定义组件的 CSS 样式,可以使用 scoped 属性来限制样式的作用范围,可以包含多个块- 自定义块, 比如
<docs>
等,自定义块可以通过 Vite 或 Webpack 的 loader 来处理自定义块,详见 官方说明
- 单文件中个语言块支持如下功能
- lang 配置不同语言块的预处理器,比如
<script lang="ts">
:使用 TypeScript 作为脚本语言<style lang="scss">
:使用 SCSS 作为样式语言<template lang="pug">
:使用 Pug 作为模板语言
- src 支持从外部导入文件,比如
<script src="./utils.js">
:导入外部 JavaScript 文件<style src="./styles.css">
:导入外部 CSS 文件<template src="./template.html">
:导入外部 HTML 模板<unit-test src="./unit-test.js">
自定义块也支持 source 导入
- lang 配置不同语言块的预处理器,比如
有用过 <script setup>
吗, 它和 option 方式有什么区别?
答案
<script setup>
是 Vue 3.x 中引入的一种新的语法糖,用于简化采用 Composition API 的组件编写方式。相比普通的 <script>
标签,优势如下
- 更简洁的语法:
<script setup>
省略了export default
和setup()
函数的定义,以及需要手动申明components
状态等逻辑,编写方式更加简单直观。 - 更好的类型推导:
<script setup>
提供了更好的 TypeScript 支持,自动推导 props 和 emits 的类型,减少了手动定义的工作量。 - 更好的性能
<script setup>
setup 中的内容会编译为同一作用域的函数,相比采用 options render 需要使用代理消费$data
等属性,性能更优,这也是为什么模版可以直接使用 setup 中定义的变量、函数、组件而无需显示绑定的原因。具体示例可参看 sfc playground 中编译的输出即可理解。 - setup 支持顶层异步 await, 这会被编译为
async setup
函数
setup 中提供了一系列工具方法,来简化类型等定义,具体如下
|方法|功能|
defineProps<T>()
|定义组件的 props,支持类型推导|
withDefaults(defineProps<T>(), defaultValues)
|定义组件的 props 的默认值|
defineEmits<T>()
|定义组件的抛出的事件,支持类型推导|
defineModel<T>()
|简化双向绑定的 prop 定义, 3.4+ 支持|
defineExpose()
|用来暴露组件的属性|
defineOptions()
|暴露组件其他配置,3.3+ 支持,解决之前需要单独定义 script 配置 name、inheritAttrs
等问题|
defineSlots<T>()
|定义插槽类型,和 useSlots
返回一致|
useSlots() 和 useAttrs()
|访问插槽内容和 attrs, 和 setupContext.slots 和 setUpContext.attrs 等价|
延伸阅读
- script setup 官方文档说明 script setup 的使用
- vue script setup rfc 详细介绍 script setup 的设计初衷
<style scope>
是怎么做的样式隔离的
答案
<style scoped>
是 Vue 单文件组件中的一个特性,用于限制样式的作用范围,使其仅应用于当前组件的元素。这样可以避免样式冲突和全局污染。
Vue 在编译带有 scoped
属性的 <style>
标签时,会按照以下步骤处理样式隔离:
- ID 属性生成:Vue 为每个带有
scoped
属性的组件生成一个唯一的作用域 ID, 绑定在组件对象__scopeId
属性上(如data-v-abcdef
)。 - 组件元素绑定 ID:组件模板的所有元素上都会添加一个属性
data-v-abcdef
。 - 选择器绑定:会在选择器末尾追加属性选择器,以确保样式仅应用于当前组件的元素。例如,如果 CSS 规则是
.button { color: red; }
,并且作用域 ID 是data-v-abcdef
,那么该规则会被转换成.button[data-v-abcdef] { color: red; }
。
可以参考 Vue SFC Playground 查看示例编译后的 JS 和 CSS 。
什么是函数组件,和 JSX、渲染函数有什么关系?
答案
-
函数组件 是一个纯函数,通常用于创建简单的、无状态的组件。由于组件无状态,所以没有生命周期钩子,也没有响应式数据。
-
JSX 是一种 JavaScript 语法扩展,它允许你在 JavaScript 中编写类似 HTML 的代码。JSX 语法通常与 React 一起使用,但 Vue 也支持 JSX。Vue 组件可以使用 JSX 来描述组件的结构和样式。JSX 语法会被 loader 转换为渲染函数。
-
渲染函数 动态创建组件的函数,内部通常采用
[h()](https://cn.vuejs.org/api/render-function.html#h)
函数,本质上模版编写的内容也会被转换为渲染函数。// 渲染函数完整参数如下
function h(
type: string | Component,
props?: object | null,
children?: Children | Slot | Slots
): VNode
总结函数组件用来定义无状态的组件。渲染函数,通过消费
h
手动接创建 vnode 节点,JSX 和单文件中的模版本质是简化渲染函数编写的语法糖。
延伸阅读
JSX 和采用单文件组件编写有哪些区别?
答案
- vue 模版指令需要转换为 jsx 表达式,自定义指令采用 withDirectives 进行包裹,此外 html 属性可以直接传递不需要转换为驼峰式命名,比如
class
、for
等
<div v-if="isShow">hello world</div>
<div v-for="item in list" :key="item.id">{{ item.name }}</div>
<div @click="handleClick">hello world</div>
<div @click.capture="handleCapture">hover capture</div>
<div>{isShow ? 'hello world' : ''}</div>
<div>{list.map(item => <div key={item.id}>{item.name}</div>)}</div>
<div onClick={handleClick}>hello world</div>
<div onClickCapture={handleCapture}>hover capture</div>
- 插槽采用 slots 替换
<!-- ChildComponent 定义插槽 -->
<div>
<slot></slot>
<slot name="namedSlot" :data="data"></slot>
</div>
<!-- 消费 ChildComponent -->
<template>
<ChildComponent>
<template #default>
<div>Default Slot Content</div>
</template>
<template #namedSlot="{ data }">
<div>Named Slot Content: {{ data }}</div>
</template>
</ChildComponent>
</template>
// ChildComponent 定义插槽
<div>
{slots.default()}
{slots.namedSlot({ data })}
</div>
// 消费 ChildComponent
<template>
<ChildComponent>
{{
default: () => <div>Default Slot Content</div>,
namedSlot: ({ data }) => <div>Named Slot Content: {data}</div>
}}
</ChildComponent>
</template>
- 内置组件不在是全局,需要导入后使用例如
KeepAlive
等, 需要导入import { KeepAlive } from 'vue'
- v-model 语法糖需要转换为 jsx 属性和时间注入
<template>
<input v-model="value" />
</template>
<template>
<input value={value} onInput={e => (value = e.target.value)} />
</template>
延伸阅读
- 渲染函数说明 官方文档说明 JSX 和采用 template 编写的区别
- babel-plugin-jsx vue jsx loader 说明
什么是异步组件,是如何实现的?
答案
-
异步组件 是为一个组件提供的包装器,来让被包装的组件可以进行懒加载。这通常用作减少构建后的 .js 文件大小的一种方式,通过将它们拆分为较小的块来按需加载。
-
如何使用 采用
defineAsyncComponent
配合构建工具实现一步组件加载import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() =>
import('./components/MyComponent.vue')
)此外
defineAsyncComponent
还支持加载中加载是被等控制const AsyncComp = defineAsyncComponent({
// 加载函数
loader: () => import('./Foo.vue'),
// 加载异步组件时使用的组件
loadingComponent: LoadingComponent,
// 展示加载组件前的延迟时间,默认为 200ms
delay: 200,
// 加载失败后展示的组件
errorComponent: ErrorComponent,
// 如果提供了一个 timeout 时间限制,并超时了
// 也会显示这里配置的报错组件,默认值是:Infinity
timeout: 3000
}) -
异步组件的实现原理 采用
import()
动态导入组件,返回一个 Promise 对象,Promise 对象解析后返回组件对象。Vue 3.x 中defineAsyncComponent
会在组件被加载时自动调用import()
函数来加载组件。
示例说明
组件通信策略和方法有哪些?
答案
通信方式 | 描述 | 适用场景 |
---|---|---|
props | 父组件通过 props 向子组件传递数据,子组件通过声明 props 接收数据。 | 父组件向子组件传递数据,单向数据流。 |
$emit | 子组件通过 $emit 触发自定义事件,父组件监听事件并接收数据。 | 子组件向父组件传递数据,常用于组件封装。 |
$attrs | 子组件通过 $attrs 获取父组件传递的未声明属性,Vue3 剔除了 $listners 属性 | 子组件需要获取父组件额外绑定的信息 |
Provide/Inject | 父组件通过 provide 提供数据,后代组件通过 inject 注入数据。 | 跨层级组件通信,常用于高阶组件或组件库。 |
Scoped Slots,expose | 子组件通过在 slot 上绑定属性,或者 expose 申明属性,父组件通过 v-slot 和直接引用子组件实例暴露的属性 | 表格等提供自定义修改能力的场景 |
$refs | 父组件通过 ref 获取子组件实例,直接调用子组件方法或访问属性。 | 父组件需要直接操作子组件实例时使用,注意避免直接修改子组件数据。 |
Event Bus | 使用一个空的 Vue 实例作为事件总线,通过 $emit 和 $on 实现任意组件之间的通信。 | 非父子关系组件之间的通信,适用于简单的全局事件管理。 |
Vuex,Pinia | 使用全局状态管理函数 | 复杂应用中需要共享状态时使用,适用于全局状态管理。 |
示例说明
Vue 有哪些内置组件?
答案
内置组件包括
组件 | 描述 |
---|---|
<Transition> | 为单个元素或组件提供动画过渡效果。 |
<TransitionGroup> | 为列表中的多个元素或组件提供动画过渡效果。 |
<KeepAlive> | 缓存动态切换的组件,保留其状态。 |
<Teleport> | 将插槽内容渲染到 DOM 的其他位置。 |
<Suspense> | 管理组件树中嵌套的异步依赖,加载时显示备用内容。 |
内置的特殊元素包括
元素 | 描述 |
---|---|
<slot> | 定义插槽,允许父组件向子组件传递内容。 |
template | 定义一个模板块,可以在组件中使用。 |
component | 动态组件,允许在运行时切换组件。 |
<component>、<slot>、<template>
具有类似组件的特性,也是模板语法的一部分。但它们并非真正的组件,在模板编译期间会被编译掉。因此,它们通常在模板中用小写字母书写。
示例说明
Teleport 组件功能?
答案
<teleport>
是 Vue 内置的一个特殊组件,用于将组件的模板内容渲染到指定的 DOM 节点,而不是在组件自身的位置渲染。<teleport>
允许将组件内容渲染到页面的任意位置,而不受组件层次结构的限制。这使得它非常适合用于模态框、通知、工具提示等需要脱离父组件 DOM 层次的场景。<teleport>
的核心功能包括- 通过
to
属性指定目标 DOM 节点,可以是一个 CSS 选择器或一个 DOM 元素。 - 通过
disabled
属性可以禁用<teleport>
的功能,使其在开发或调试时仍然在组件的原始位置渲染。 - Vue 3.5+ 后支持 defer 属性,延后挂载,注意延后的挂载点必须和 teleport 的挂载点一致, 类似同一个渲染周期后 mounted 后挂载
- 通过
- 注意事项
- 事件冒泡:
<teleport>
内部的事件会按照正常的 DOM 事件冒泡机制传播到目标位置的父元素中。确保在目标位置正确处理事件。 - 样式隔离:
<teleport>
的内容可能会受到目标位置样式的影响。使用 CSS 模块化或命名空间来避免样式冲突。 - 响应式数据:
<teleport>
内部的响应式数据仍然依赖于组件的上下文,确保数据在目标位置能正确更新。 - 目标元素存在性:确保目标 DOM 节点在
<teleport>
渲染时已经存在,否则内容可能无法正确挂载。
- 事件冒泡:
示例说明
延伸阅读
- teleport rfc
- teleport 组件 官方文档
KeepAlive 组件功能?
答案
<keep-alive>
是 Vue.js 提供的一个抽象组件,用于缓存动态组件,从而避免组件的重复销毁和重建,提高性能。<keep-alive>
是实现组件复用和性能优化的关键工具,适用于需要频繁切换的动态组件场景,如标签页、路由视图等。- 核心功能
- 组件缓存:被
<keep-alive>
包裹的组件会被缓存到内存中,而不是每次切换时销毁和重建。 - 激活与停用:
- 当组件被移出视图时,不会销毁,而是触发
deactivated
钩子。 - 当组件重新进入视图时,不会重新创建,而是触发
activated
钩子。
- 当组件被移出视图时,不会销毁,而是触发
- 缓存控制:
- 通过
include
和exclude
属性,指定需要缓存或排除缓存的组件。 - 通过
max
属性,限制缓存的组件数量,超出限制时会移除最久未使用的组件。
- 通过
- 组件缓存:被
- 注意事项
- 生命周期钩子:被缓存的组件不会触发
beforeUnmount
和unmounted
钩子,而是触发deactivated
和activated
钩子。 - 缓存清理:当缓存数量超过
max
时,最久未使用的组件会被移除。 - 动态切换:
include
和exclude
支持动态更新,但需要确保组件名称与name
属性一致。
- 生命周期钩子:被缓存的组件不会触发
示例说明
component 元素了解么,它是如何实现动态挂载的?
答案
<component>
是 Vue 提供的内置元素,用于动态渲染不同的组件。通过is
属性指定要渲染的组件,可以是组件名称的字符串或组件选项对象。它支持动态切换组件,适用于需要根据条件动态加载和渲染的场景。- 采用
is
属性来指定要渲染的组件,可以是字符串(组件名称)或组件选项对象。<component>
会根据is
的值动态加载和渲染对应的组件。- 当
is
的值是字符串时,Vue 会查找注册的组件名称,并渲染对应的组件实例。 - 当
is
的值是组件选项对象时,Vue 会直接渲染该组件。 <component>
还支持v-bind
指令来动态绑定组件的属性和事件,无法使用 v-model, 需要手动绑定
- 当
<component>
的核心是通过is
属性动态解析组件实例。Vue 在内部会根据is
的值查找对应的组件选项对象,并通过虚拟 DOM 渲染出对应的组件实例。组件切换时,Vue 会销毁旧组件实例并挂载新组件实例,确保状态隔离。
示例说明
讲解一下插槽的使用?
答案
- 插槽 是 Vue 提供的一种机制,用于在组件中定义占位符,以便父组件可以在这些占位符中插入内容。插槽允许组件的使用者在组件的特定位置插入自定义内容,从而实现更灵活的组件组合。
- 插槽的使用 主要分为两种类型:默认插槽 和 具名插槽。
- 默认插槽:在组件中定义一个
<slot>
元素,父组件可以在该位置插入内容。默认插槽可以有多个,但只有一个会被渲染。 - 具名插槽:在组件中定义多个
<slot name="xx">
元素,并为每个插槽指定一个名称。父组件可以通过v-slot:xx
指令或#xx
符号来指定要插入的内容。 - 条件插槽 结合
v-if
指令通过$slots.xx
来判断插槽是否存在来确定是否渲染 - 动态插槽 父组件支持
v-slot:[xx]
的模式基于变量动态渲染插槽元素 - 作用域插槽:子组件通过
<slot var1="data">
绑定变量,父组件通过<Children v-slot="{var1}">
的方式引用组件
- 默认插槽:在组件中定义一个
- 插槽的原理:Vue 在编译组件时,会将插槽内容解析为虚拟 DOM,并在渲染时将这些虚拟 DOM 插入到组件的指定位置。插槽的内容会被视为父组件的作用域,因此可以访问父组件的数据和方法。
示例说明
延伸阅读
- 插槽 官方文档讲解插槽
了解 Vue 组件的整个渲染流程么,如何处理挂载和更新的?
答案
整个关键流程
- 编译(compile) 输入为
.vue
的 SFC 文件,输出为组件的对象,其中template
片段被转换为render
函数 - 挂载 (mount) 首次挂载会直接基于
render
生成的 vdom ,转换为对应宿主环境对应的 dom tree - 补丁 (patch) 当组件的响应式数据发生变化时,会触发
update
,此时会比较 dom tree 上挂载的旧版vdom
节点和新的vdom
差异,触发增量更新。
详细参考下图
延伸阅读
- render mechanism 官方文档详细讲解渲染机制
- Vue.js 设计与实现 第三篇 渲染器, 详细说明了整个渲染流程
- render process 利用 deepwiki 详细解决 Vue 的源码执行流程
Vue3 是怎么实现 template 支持多个根节点的?
答案
在 Vue 组件编译时会识别出组件类型是否为 Fragment
, 识别出 Fragment
节点后,就会触发正常的节点渲染流程。
核心代码逻辑包括
Vue 的 DOM diff 算法是如何实现的?
答案
diff 算法的本质是比对两个虚拟 DOM 树的差异,以最小的成本更新真实 DOM 元素,整个 diff 基于如下核心原则
- 尽可能实现节点的复用,会通过 key 或者 ,type 来判断节点是否可以复用
- 如果不存在复用可能对于 type 不一样的类型直接挂载
基于上面原则,vue 目前采用的 diff 核心步骤包括
- 基于更新节点上的
._vnode
和新生成的vnode
节点,调用 patch 方法 - 会根据节点类型做比对,这里重点关注节点类型一样,子元素都是 children 的情况
- 会基于是否存在 key 做对比,这里重点关注有 key 的情况,核心处理步骤
- 预处理,先快速对比完收尾 key 相同的节点
- 预处理完成后会找出遍历一遍新节点,和旧节点,存储 newIndexToOldIndexMap 结构用来处理后续移位的判断
- 会基于 newIndexToOldIndexMap 计算最长递增子序列求出 increasingNewIndexSequence
- 基于 increasingNewIndexSequence 和 newIndexToOldIndexMap, 倒序遍历新节点,处理移位和跟新逻辑
延伸阅读
- Vue.js 设计与实现 第三篇 渲染器, 9/10/11 章节详细讲解了, 简单 diff、双端 diff、快速 diff 的实现原理
- patchKeyedChildren 源码中有key 的 diff 逻辑
SFC 是如何处理的
答案
- loader 预编译
- webpack 会使用 vue-loader 处理 SFC 文件。会通过追加
query
字符串的方式来处理不同的 loadertemplate
片段会被转换为import { render } from '/project/foo.vue?vue&type=template&id=xxxxxx'
转换为 render 函数script
会被转换为import script from '/project/foo.vue?vue&type=script'
, 后续基于配置的 lang ,经过 vue-loader 处理后在转换为后续 loader 处理style
会被转换为import '/project/foo.vue?vue&type=style&index=0&id=xxxxxx'
,会基于配置的 lang,经过 vue-loader 处理后在转换为后续 loader 处理- 自定义片段原理同上
- vite 会用 @vitejs/plugin-vue
- webpack 会使用 vue-loader 处理 SFC 文件。会通过追加
- 这里重点讲解
template
片段的处理,分为如下三个阶段- parser 通过
@vue/compiler-dom
输入 template 片段,输出模版 AST 语法树 - transform 处理 AST 语法树,转换为 render 函数的 AST 语法树
- generate 基于 render 函数的 AST 语法树,生成 render 函数的代码
- parser 通过
延伸阅读
- Vue.js 设计与实现 第五篇编译器,详细讲解了此流程
- vue loader 简要说明了 Vue loader 的工作原理
你是如何编写一个组件的?
答案
开放性问题,主要考察候选人对组件的理解和设计能力。笔者的经验如下
编写组件前,形成一致的风格约束。这里包括
- 规则约束 通过 lint 等工具规范组件的命名、属性、事件等编写规范
- 设计约束 通过统一的最佳实践来约束组件的设计和实现
在完成了上述约束的基础上,编写组件的步骤包括
- 确定组件的类型 判断组件是否需要管理自己的状态,明确组件是无状态的全渲染组件,还是有状态的逻辑组件,尽可能选择无状态组件最少知识原则,只暴露必要的接口
- 定义组件的输入输出 再确定组件的类型后,重点是明确组件的输入和输出,输入包括 props、插槽、事件等,输出包括事件、插槽等,这确定了组件的交互形态,这里的核心原则是
- 实现组件的逻辑
- UI 稿实现 按照功能粒度拆分 UI, 核心原则只有一条,单一职责,每个区块负责独立的功能
- 逻辑实现 通过
setup
函数实现组件的逻辑,核心原则是尽量使用组合式 API 来实现组件的逻辑 - 样式实现 css 有多种范式,遵循一种即可
- 测试组件 如果时间够的话,通过单元测试和集成测试来验证组件的功能和性能,确保组件在不同场景下的表现
延伸阅读
- Vue style 说明 Vue 组件的设计规范
- eslint vue guide
- angular style guide angular 的风格指南, 可以借鉴
- gitlab vue style gitlab 的 vue 规范说明