跳到主要内容

如何设计组件库?✅

实现 Tooltip 组件?

// 使用示例
// React
<Tooltip title={<>提示内容</>}><span>悬停查看</span></Tooltip>

// Vue
<Tooltip title="提示内容"><span>悬停查看</span></Tooltip>
答案

组件结构

  • 触发器(trigger) 接受 hover/focus 事件,作为定位参考元素
  • 提示浮层(tooltip) 非模态信息浮层,绝对定位并随视口避让
  • 可访问性(a11y) role=tooltiparia-describedby 关联描述

核心功能及原理

功能要点Vue要点React要点
显隐控制mouseenter/leavefocus/blur 切换同左
位置计算使用 getBoundingClientRect 放置并处理溢出同左
无障碍触发器 aria-describedby、浮层 role=tooltip同左
<template>
  <div style="padding:24px">
    <Tooltip title="提示内容"><span style="border-bottom:1px dashed #999;cursor:help">悬停查看</span></Tooltip>
  </div>
</template>

<script setup lang="ts">
  import Tooltip from './Tooltip.vue'
</script>

实现支持 API 调用的 Modal 组件?

// API 消费
const modal = Modal.open({
title: () => <h1>标题</h1>,
content: () => <p>内容</p>,
onClose: () => { console.log('Modal closed'); },
onConfirm: () => { console.log('Modal confirmed'); },
onCancel: () => { console.log('Modal canceled'); }
})
// 关闭弹窗
modal.close()
答案

组件结构

Modal 组件涉及的核心模块包括

  • 遮罩(mask) , 用于阻止背景交互,采用 fixed 定位全屏覆盖遮罩
  • 关闭按钮(close) 触发关闭操作
  • 标题(title) , 显示对话框标题
  • 内容区(content) , 包含标题、内容与操作按钮
  • 底部操作区(footer) , 包含确认与取消按钮

API 调用需要结合渲染函数动态挂载,返回值暴露销毁方法。框架层面设计的核心方法包括

核心功能原理

功能要点Vue 要点React 要点
组件挂载和卸载通过 createApp() 返回的 app 实例暴露的 app.mount(container) 挂载、app.unmount() 销毁组件实例, 或者通过 render(vnode,container) 挂载、render(null,container) 销毁组件实例通过 ReactDOM.createRootroot.render、root.unmount 动态挂载和销毁组件实例
自定义title和content通过具名插槽实现渲染属性实现
事件回调通过 emit 触发通过 props 传递回调函数
<template>
  <button @click="open">API 弹窗</button>
</template>

<script setup lang="ts">
  import * as ModalApi from "./ModalApi.tsx";
  import { h } from "vue";

  function open() {
    const modal = ModalApi.open({
      title: () => h("h1", "标题"),
      content: () => h("p", "内容"),
      onClose: () => console.log("Modal closed"),
      onConfirm: () => console.log("Modal confirmed"),
      onCancel: () => console.log("Modal canceled"),
    });
  }
</script>

实现 Message/Notification 组件?

// API 消费
// React
import * as Message from './MessageApi'
Message.info('信息提示')
Message.success(() => <b>成功</b>)
const m = Message.error('错误', 3000)
m.close()

// Vue
import * as Message from './MessageApi'
Message.warning('警告')
答案

组件结构

  • 容器(container) 单例挂载点,承载消息队列并负责定位与层级
  • 消息项(item) 展示类型与内容,支持关闭按钮与自动计时销毁
  • 队列与合并(queue) 控制并发显示与相同类型合并,避免刷屏

核心功能及原理

功能要点Vue要点React要点
容器挂载/销毁render(h(Teleport), host) 挂载,render(null, host) 卸载createRoot(host).render() 渲染,root.unmount() 卸载
内容传入插槽/函数返回VNode渲染属性/函数返回Node
自动关闭setTimeout(() => emit('close'))useEffect(setTimeout(onClose))
返回句柄返回{ close }手动关闭返回{ close }手动关闭
<template>
  <div style="display:flex;gap:8px;">
    <button @click="() => Message.info('信息提示')">Info</button>
    <button @click="() => Message.success('成功提示')">Success</button>
    <button @click="() => Message.warning('警告提示')">Warning</button>
    <button @click="() => Message.error('错误提示')">Error</button>
  </div>
</template>

<script setup lang="ts">
  import * as Message from './MessageApi.tsx'
</script>

实现 Form 组件,支持联动校验?

// 使用示例
// React
const form = useForm({ name: '', mail: '' }, { name: [required()], mail: [required(), email()] })
<form onSubmit={async e => { e.preventDefault(); const ok = await form.validate(); }}>{/* ... */}</form>

// Vue
const { values, errors, setValue, validate } = useForm({ name: '', mail: '' }, { name: [required()], mail: [required(), email()] })
答案

组件结构

  • 字段存储(values/errors) 保存各字段值与错误状态,支撑校验与联动展示
  • 校验器(validator) 定义同步/异步规则,按触发时机执行并返回错误文案
  • 触发时机(trigger) 输入/失焦/提交触发单字段或整体验证
  • 依赖联动(deps) 校验器读取其他字段值以实现跨字段约束
  • 可访问性(a11y) 通过 aria-invalidrole=alert 提示错误状态

核心功能及原理

功能要点Vue要点React要点
字段注册/设置值useForm().setValue(name, v) 更新响应式 valuesuseForm().setValue(name, v) 触发状态更新
校验器与触发validateField/validate 支持异步校验validateField/validate 支持异步校验
错误聚合与可达错误集合驱动 aria-invalid/提示错误集合驱动 aria-invalid/提示
依赖联动校验器可读取 values 其他字段同左
<template>
  <form @submit.prevent="onSubmit">
    <div>
      <label>姓名</label>
      <input :aria-invalid="!!errors.name" :value="values.name || ''" @input="e => setValue('name', e.target.value)" />
      <span v-if="errors.name" role="alert">{{ errors.name }}</span>
    </div>
    <div>
      <label>邮箱</label>
      <input :aria-invalid="!!errors.mail" :value="values.mail || ''" @input="e => setValue('mail', e.target.value)" />
      <span v-if="errors.mail" role="alert">{{ errors.mail }}</span>
    </div>
    <button type="submit">提交</button>
  </form>
</template>

<script setup lang="ts">
  import { useForm, required, email } from './useForm'
  const { values, errors, setValue, validate } = useForm({ name: '', mail: '' }, { name: [required()], mail: [required(), email()] })
  function onSubmit(){ validate().then(ok => console.log('valid:', ok, values, errors)) }
</script>

实现 Select/Autocomplete?

// 使用示例
// React
<Select options={[{ value:'1', label:'选项 1' }]} value={value} onChange={setValue} />

// Vue
<Select :options="options" v-model="value" />
答案

组件结构

  • 触发器(trigger) 展示当前选择或占位文案,并控制下拉开合
  • 下拉面板(dropdown) 承载搜索输入与选项列表,支持虚拟滚动
  • 选项项(option) 展示文本与禁用态,支持高亮与键盘聚焦

核心功能及原理

功能要点Vue要点React要点
打开/关闭与聚焦open 状态与 nextTick(focus)同左
搜索与筛选v-model 绑定关键字过滤同左
键盘导航上下移动 activeIndex、Enter 提交同左
虚拟滚动长列表用虚拟列表优化同左
<template>
  <div>
    <Select :options="options" v-model="value" />
    <div style="margin-top:8px">当前值:{{ value || '未选择' }}</div>
  </div>
</template>

<script setup lang="ts">
  import { ref } from 'vue'
  import Select from './Select.vue'
  const options = Array.from({ length: 50 }, (_, i) => ({ value: String(i+1), label: `选项 ${i+1}` }))
  const value = ref<string|undefined>()
</script>

实现 Cascader ?

// 使用示例
// React
<Cascader options={options} value={value} onChange={setValue} loadData={loadData} />

// Vue
<Cascader :options="options" v-model="value" :loadData="loadData" />
答案

组件结构

  • 触发器(trigger) 展示选中路径或占位文案,控制级联面板开合
  • 级联面板(panel) 按列展示各层级选项,支持滚动与多列并排
  • 选项(option) 展示文本与末级标识(isLeaf),支持禁用与选中高亮
  • 懒加载(loadData) 选择到非叶子节点时按需异步加载子节点

核心功能及原理

功能要点Vue要点React要点
路径回显根据 modelValue 自顶向下解析 label 路径根据 value 解析各层级 label 拼接
选择与关闭末级或空子节点即提交并关闭同左
懒加载loadData(selectedPath) 返回 children 后续渲染同左
可访问性role=tree/group 分列语义同左
<template>
  <div>
    <Cascader :options="options" v-model="value" :loadData="loadData" />
    <div style="margin-top:8px">选中路径:{{ (value||[]).join(' / ') || '未选择' }}</div>
  </div>
</template>

<script setup lang="ts">
  import { ref } from 'vue'
  import Cascader from './Cascader.vue'
  interface CascaderOption { value: string; label: string; isLeaf?: boolean; children?: CascaderOption[] }
  const options = ref<CascaderOption[]>([
    { value: 'zj', label: '浙江', children:[ { value:'hz', label:'杭州', children:[ { value:'xh', label:'西湖', isLeaf:true }, { value:'yl', label:'余杭', isLeaf:true } ] }, { value:'nb', label:'宁波', isLeaf:true } ] },
    { value: 'js', label: '江苏', isLeaf:false }
  ])
  const value = ref<string[] | undefined>()
  async function loadData (path: CascaderOption[]){
    const last = path[path.length-1]
    await new Promise(r=>setTimeout(r,300))
    if (last.value==='js') return [ { value:'nj', label:'南京', isLeaf:true }, { value:'sz', label:'苏州', isLeaf:true } ]
    return []
  }
</script>

实现树组件?

// 使用示例
// React
<Tree data={data} defaultExpandedKeys={[ 'root' ]} onSelect={(k)=>console.log(k)} />

// Vue
<Tree :data="data" :defaultExpandedKeys="['root']" v-model:selectedKey="selected" />
答案

组件结构

  • 节点(node) 展示标题与选中态,支持禁用与半选(可扩展)
  • 层级容器(level/group) 分层渲染 ul/li,为屏幕阅读器提供语义
  • 展开控制(expand/collapse) 切换子树可见性,保持状态并支持默认展开

核心功能及原理

功能要点Vue要点React要点
受控/非受控v-model:selectedKey 受控选择;内部状态管理selected + onSelect 受控;useState 非受控
展开/收起expanded Set 管理展开键集合同左
可访问性role=tree、子 role=group、项 role=treeitem同左
虚拟化大量节点可替换为虚拟列表渲染同左
<template>
  <div>
    <Tree :data="data" :defaultExpandedKeys="['zj','hz']" v-model:selectedKey="selected" />
    <div style="margin-top:8px">选中:{{ selected || '无' }}</div>
  </div>
</template>

<script setup lang="ts">
  import { ref } from 'vue'
  import Tree from './Tree.vue'
  const data = [
    { key: 'zj', title: '浙江', children: [
      { key: 'hz', title: '杭州', children: [ { key: 'xh', title: '西湖' }, { key: 'yl', title: '余杭' } ] },
      { key: 'nb', title: '宁波' }
    ]},
    { key: 'js', title: '江苏', children: [ { key: 'nj', title: '南京' }, { key: 'sz', title: '苏州' } ] }
  ]
  const selected = ref<string | undefined>()
</script>

实现 Table 组件?

// 使用示例
// React
<Table columns={[{key:'name',title:'姓名'},{key:'age',title:'年龄'}]} data={[{name:'张三',age:22}]} />

// Vue
<Table :columns="columns" :data="data" />
答案

组件结构

  • 滚动容器(scroll container) 提供横纵向滚动并承载表格
  • 表头(header) 粘性吸顶,保持列标题可见
  • 固定列(sticky column) 首列/末列粘性定位,提升对齐与可读性
  • 表体(body) 渲染数据行与单元格,支持行/列对齐与高亮

核心功能及原理

功能要点Vue要点React要点
粘性表头thead th{ position: sticky; top: 0 }同左
固定首列首列添加 position: sticky; left: 0同左
滚动同步容器 overflow: auto 承载滚动同左
虚拟滚动大量数据可替换为虚拟列表渲染同左
<template>
  <Table :columns="columns" :data="data" />
</template>

<script setup lang="ts">
  import Table from './Table.vue'
  const columns = [
    { key: 'name', title: '姓名', width: 140 },
    { key: 'age', title: '年龄', width: 80 },
    { key: 'city', title: '城市', width: 160 },
    { key: 'job', title: '职业', width: 200 }
  ]
  const data = Array.from({ length: 30 }, (_, i) => ({ name: `用户 ${i+1}`, age: 20 + (i%10), city: '杭州', job: '前端工程师' }))
</script>

实现分页组件 Pagination?

// 使用示例
// React
<Pagination total={200} pageSize={10} current={current} onChange={setCurrent} />

// Vue
<Pagination :total="200" :pageSize="10" v-model="current" />
答案

组件结构

  • 容器(container) 提供分页导航语义 role=navigation 与对齐布局
  • 页码(page) 可点击的页码按钮,当前页使用 aria-current=page
  • 前后按钮(prev/next) 快速翻页并处理边界禁用
  • 省略(ellipsis) 在大量页码时收束显示范围

核心功能及原理

功能要点Vue要点React要点
受控/非受控v-model 当前页;内部计算总页数current + onChange 受控;内部计算总页数
页码生成左右窗口 + 首尾 + 省略同左
可访问性aria-current、按钮 aria-label同左
<template>
  <div>
    <Pagination :total="200" :pageSize="10" v-model="current" />
    <div style="margin-top:8px">当前页:{{ current }}</div>
  </div>
</template>

<script setup lang="ts">
  import { ref } from 'vue'
  import Pagination from './Pagination.vue'
  const current = ref(1)
</script>

实现日期选择器组件支持范围选择?

// 使用示例
// React
<DatePicker value={value} onChange={setValue} min="2020-01-01" max="2030-12-31" />

// Vue
<DatePicker v-model="value" min="2020-01-01" max="2030-12-31" />
答案

组件结构

  • 输入(input) 使用原生 type=date 或自定义面板触发与展示
  • 面板(panel) 选择单日或范围,受最小/最大日期限制
  • 格式化/解析(format/parse) 用户输入与内部值之间的转换
  • 本地化(i18n) 月份/周起始/区域格式配置

核心功能及原理

功能要点Vue要点React要点
受控/非受控v-model 绑定值value + onChange 受控
范围限制min/max 或自定义禁用逻辑同左
本地化基于浏览器或自定义库格式化同左
<template>
  <div>
    <DatePicker v-model="value" min="2020-01-01" max="2030-12-31" />
    <div style="margin-top:8px">当前日期:{{ value || '未选择' }}</div>
  </div>
</template>

<script setup lang="ts">
  import { ref } from 'vue'
  import DatePicker from './DatePicker.vue'
  const value = ref('')
</script>

实现 Upload 组件?

// 使用示例
// React
<Upload chunkSize={128*1024} concurrency={3} />

// Vue
<Upload :chunkSize="128*1024" :concurrency="3" />
答案

组件结构

  • 选择器(selector) 选择文件列表,触发上传流程
  • 任务队列(queue) 分片并发调度与状态管理(uploading/done/error/canceled)
  • 分片(chunk) 将大文件切片,逐片上传与重试(示例为模拟)
  • 进度(progress) 聚合分片进度,渲染进度条与状态

核心功能及原理

功能要点Vue要点React要点
分片策略chunkSize 切片同左
并发控制开启 concurrency 个 worker 轮询同左
取消响应取消标记,停止后续分片同左
<template>
  <Upload :chunkSize="128*1024" :concurrency="3" />
</template>

<script setup lang="ts">
  import Upload from './Upload.vue'
</script>

实现 Tabs 组件?

// 使用示例
// React
<Tabs items={[{key:'a',label:'A',content:<div>A</div>}]} />

// Vue
<Tabs :items="[{key:'a',label:'A',content:'A'}]" />
答案

组件结构

  • 标签列表(tablist) 容器使用 role=tablist,承载可切换的标签
  • 标签(tab) 可聚焦与激活,roving tabindex 管理焦点
  • 面板(tabpanel) 展示当前激活标签对应内容

核心功能及原理

功能要点Vue要点React要点
焦点管理左右/Home/End 键移动焦点与激活同左
语义role=tablist/tab/tabpanelaria-selected同左
保活/懒渲染按需渲染或保留面板状态同左
<template>
  <Tabs :items="items" />
</template>

<script setup lang="ts">
  import Tabs from './Tabs.vue'
  const items = [
    { key: 'a', label: '选项A', content: 'A 内容' },
    { key: 'b', label: '选项B', content: 'B 内容' },
    { key: 'c', label: '选项C', content: 'C 内容' }
  ]
</script>

实现 Popover/Popconfirm ?

// 使用示例
// React
<Popover content={<div>内容</div>}>触发</Popover>

// Vue
<Popover content="内容">触发</Popover>
答案

组件结构

  • 触发器(trigger) 控制浮层显隐,提供定位参照
  • 浮层(popover) 基于 getBoundingClientRect + transform/absolute 定位
  • 翻转与避让(flip/overflow) 根据视口与滚动容器边界调整位置

核心功能及原理

功能要点Vue要点React要点
位置计算onMounted/watch 读取触发器/浮层尺寸定位useLayoutEffect 读取尺寸并设置位置
滚动/窗口变化监听 scroll/resize 重算位置同左
无障碍role=dialog 非模态、可由触发器描述同左
<template>
  <div style="padding:24px">
    <Popover content="这是一段 Popover 内容">打开 Popover</Popover>
  </div>
</template>

<script setup lang="ts">
  import Popover from './Popover.vue'
</script>

实现吸顶组件 Affix ?

// 使用示例
// React
<Affix offsetTop={0}><button>吸顶按钮</button></Affix>

// Vue
<Affix :offsetTop="0"><button>吸顶按钮</button></Affix>
答案

组件结构

  • 占位容器(placeholder) 计算元素位置与宽度,避免布局抖动
  • 固定元素(fixed element) 到达阈值后使用 position:fixed 固定
  • 监听器(observer) 监听滚动/尺寸变化,实时重算位置

核心功能及原理

功能要点Vue要点React要点
阈值判断getBoundingClientRect().top <= offsetTop同左
定位计算设置 top/left/width 保持视觉位置同左
性能节流/被动监听 scroll同左
<template>
  <div style="height:600px;padding-top:200px">
    <Affix :offsetTop="0"><button class="affix-btn">吸顶按钮</button></Affix>
    <div style="height:1000px" />
  </div>
</template>

<script setup lang="ts">
  import Affix from './Affix.vue'
</script>

实现骨架图组件 Skeleton?

// 使用示例
// React
<Skeleton loading={loading}><Content/></Skeleton>

// Vue
<Skeleton :loading="loading"><Content/></Skeleton>
答案

组件结构

  • 占位骨架(skeleton) 预估布局形态,使用动画过渡避免闪烁
  • 内容(content) 加载完成后替换骨架,保持高度稳定
  • 可访问性(a11y) 使用 aria-busy 指示加载状态

核心功能及原理

功能要点Vue要点React要点
显示切换v-if loading/slot 切换loading ? skeleton : children
动画CSS 渐变骨架动画同左
可访问性aria-busy 表示忙碌同左
<template>
  <Skeleton :loading="loading"><div>内容已加载</div></Skeleton>
</template>

<script setup lang="ts">
  import { ref, onMounted } from 'vue'
  import Skeleton from './Skeleton.vue'
  const loading = ref(true)
  onMounted(()=>{ setTimeout(()=> loading.value=false, 1000) })
</script>

虚拟列表?

// 使用示例(支持动态高度)
// React
<VirtualList
count={1000}
height={300}
estimatedItemHeight={36}
renderItem={(i) => (
<div><b>#{i+1}</b> {"内容 ".repeat((i % 5) + 1)}</div>
)}
/>

// Vue
<VirtualList
:count="1000"
:height="300"
:estimatedItemHeight="36"
:renderItem="i => `#${i+1} ` + '内容 '.repeat((i % 5) + 1)"
/>
答案

组件结构

  • 滚动容器(container) 外层滚动容器,内部保持占位总高
  • 占位(placeholder) 设置总高度,避免滚动条抖动
  • 窗口(window) 依据滚动位置计算起止索引,按需渲染

核心功能及原理

功能要点Vue要点React要点
动态高度ResizeObserver 测量并缓存各项高度,触发重算同左
偏移与总高前缀和 offsets 计算总高与 translateY(offset)同左
二分查找基于 offsets+heightsscrollTop→start/end同左
预渲染缓冲overscanPx 前后缓冲减少滚动抖动同左
<template>
  <VirtualList
    :count="1000"
    :height="300"
    :estimatedItemHeight="36"
    :renderItem="i => `#${i+1} ` + '内容 '.repeat((i % 5) + 1)"
  />
</template>

<script setup lang="ts">
  import VirtualList from './VirtualList.vue'
</script>

实现类似 Swiper 的幻灯片组件?

// 使用示例
// React
<Carousel items={[<div>1</div>,<div>2</div>,<div>3</div>]} autoplay interval={1500} width={320} height={160} />

// Vue
<Carousel :items="['1','2','3']" :autoplay="true" :interval="1500" :width="320" :height="160" />
答案

组件结构

  • 轨道(track) 横向排列面板,translateX 控制位移
  • 面板(slide) 固定宽高,承载内容
  • 控制(control) 前进/后退与自动轮播定时器

核心功能及原理

功能要点Vue要点React要点
位移index*width 计算偏移同左
自动轮播onMounted/setInterval 控制useEffect 定时控制
可访问性按钮 aria-label同左
<template>
  <Carousel :items="['1','2','3']" :autoplay="true" :interval="1500" :width="320" :height="160" />
</template>

<script setup lang="ts">
  import Carousel from './Carousel.vue'
</script>

实现节点连线效果?

// 使用示例
// React
<NodeConnector />

// Vue
<NodeConnector />
答案

组件结构

  • 节点(node) 可拖动的端点,支持鼠标拖拽移动
  • 连线(line) 使用 SVG <line> 实时连接两端
  • 坐标计算(coord) 从容器坐标系换算到 SVG 坐标

核心功能及原理

功能要点Vue要点React要点
拖拽记录拖拽目标,mousemove 更新坐标同左
重绘响应式/状态驱动更新 <line>同左
扩展可拓展多连线/命中检测同左
<template>
  <NodeConnector />
</template>

<script setup lang="ts">
  import NodeConnector from './NodeConnector.vue'
</script>

实现电梯导航?

答案

组件结构

  • 目录(nav) 固定位置的导航列表,展示各章节标题
  • 内容容器(content) 可滚动内容区,包含各个带 id 的章节
  • 观察者(observer) 使用 IntersectionObserver 同步激活项

核心功能及原理

功能要点Vue要点React要点
滚动定位scrollIntoView({behavior:'smooth'})同左
激活同步IntersectionObserver 设置 active同左
可访问性为当前项设置 aria-current同左
<template>
  <Elevator :sections="sections" />
</template>

<script setup lang="ts">
  import Elevator from './Elevator.vue'
  const sections = [ { id:'s1', title:'一' }, { id:'s2', title:'二' }, { id:'s3', title:'三' } ]
</script>

实现长文本溢出自动折叠?

答案

组件结构

  • 文本容器(container) 控制 max-height 与溢出隐藏,提供平滑过渡
  • 切换(control) 按钮切换展开/收起状态
  • 可访问性(a11y) 文本区域与按钮的语义与状态提示

核心功能及原理

功能要点Vue要点React要点
收起高度collapsedHeight 控制 max-height同左
交互v-model/内部状态切换useState 切换 open 状态
可访问性结合文本描述与按钮文案同左
<template>
  <LongText :text="text" />
</template>

<script setup lang="ts">
  import LongText from './LongText.vue'
  const text = '这是一段可能很长的文本,初始显示收起,点击展开查看更多内容。'.repeat(10)
</script>

document.getElementById('toggleButton').addEventListener('click', function () {
const textContainer = document.getElementById('textContainer')
const button = document.getElementById('toggleButton')

// 检查当前是展开还是收起状态
if (button.textContent === '展开') {
// 修改文本容器的最大高度以显示全部文本
textContainer.style.maxHeight = 'none'
button.textContent = '收起'
} else {
// 重新设置最大高度以隐藏文本
textContainer.style.maxHeight = '60px' // 与CSS中定义的相同
button.textContent = '展开'
}
})

这只是实现长文本溢出展开/收起的一种基本方法。根据具体需求,这个示例可以进一步扩展或修改,比如添加动画效果使展开/收起操作更平滑,或者根据文本长度动态决定是否显示“展开/收起”按钮等。

还有其他方法可以实现这一功能,包括使用纯 CSS 的技巧(虽然可能不那么灵活),或者利用现成的 JavaScript 库和框架来简化实现过程。

面试官视角:

可追问多行省略方案(-webkit-line-clamp)、展开后回流影响、SSR 初始态一致性。

延伸阅读: