如何设计组件库?✅
实现 Tooltip 组件?
// 使用示例
// React
<Tooltip title={<>提示内容</>}><span>悬停查看</span></Tooltip>
// Vue
<Tooltip title="提示内容"><span>悬停查看</span></Tooltip>
答案
组件结构
- 触发器(trigger) 接受 hover/focus 事件,作为定位参考元素
- 提示浮层(tooltip) 非模态信息浮层,绝对定位并随视口避让
- 可访问性(a11y)
role=tooltip
与aria-describedby
关联描述
核心功能及原理
功能要点 | Vue要点 | React要点 |
---|---|---|
显隐控制 | mouseenter/leave 、focus/blur 切换 | 同左 |
位置计算 | 使用 getBoundingClientRect 放置并处理溢出 | 同左 |
无障碍 | 触发器 aria-describedby 、浮层 role=tooltip | 同左 |
- vue
- react
- html
实现支持 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.createRoot 或 root.render、root.unmount 动态挂载和销毁组件实例 |
自定义title和content | 通过具名插槽实现 | 渲染属性实现 |
事件回调 | 通过 emit 触发 | 通过 props 传递回调函数 |
- vue
- react
- html
实现 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 } 手动关闭 |
- vue
- react
- html
实现 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-invalid
与role=alert
提示错误状态
核心功能及原理
功能要点 | Vue要点 | React要点 |
---|---|---|
字段注册/设置值 | useForm().setValue(name, v) 更新响应式 values | useForm().setValue(name, v) 触发状态更新 |
校验器与触发 | validateField/validate 支持异步校验 | validateField/validate 支持异步校验 |
错误聚合与可达 | 错误集合驱动 aria-invalid /提示 | 错误集合驱动 aria-invalid /提示 |
依赖联动 | 校验器可读取 values 其他字段 | 同左 |
- vue
- react
- html
实现 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 提交 | 同左 |
虚拟滚动 | 长列表用虚拟列表优化 | 同左 |
- vue
- react
- html
实现 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 分列语义 | 同左 |
- vue
- react
- html
实现树组件?
// 使用示例
// 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 | 同左 |
虚拟化 | 大量节点可替换为虚拟列表渲染 | 同左 |
- vue
- react
- html
实现 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 承载滚动 | 同左 |
虚拟滚动 | 大量数据可替换为虚拟列表渲染 | 同左 |
- vue
- react
- html
实现分页组件 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 | 同左 |
- vue
- react
- html
实现日期选择器组件支持范围选择?
// 使用示例
// 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 或自定义禁用逻辑 | 同左 |
本地化 | 基于浏览器或自定义库格式化 | 同左 |
- vue
- react
- html
实现 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 轮询 | 同左 |
取消 | 响应取消标记,停止后续分片 | 同左 |
- vue
- react
- html
实现 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/tabpanel 与 aria-selected | 同左 |
保活/懒渲染 | 按需渲染或保留面板状态 | 同左 |
- vue
- react
- html
实现 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 非模态、可由触发器描述 | 同左 |
- vue
- react
- html
实现吸顶组件 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 | 同左 |
- vue
- react
- html
实现骨架图组件 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 表示忙碌 | 同左 |
- vue
- react
- html
虚拟列表?
// 使用示例(支持动态高度)
// 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+heights 将 scrollTop→start/end | 同左 |
预渲染缓冲 | overscanPx 前后缓冲减少滚动抖动 | 同左 |
- vue
- react
- html
实现类似 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 | 同左 |
- vue
- react
- html
实现节点连线效果?
// 使用示例
// React
<NodeConnector />
// Vue
<NodeConnector />
答案
组件结构
- 节点(node) 可拖动的端点,支持鼠标拖拽移动
- 连线(line) 使用 SVG
<line>
实时连接两端 - 坐标计算(coord) 从容器坐标系换算到 SVG 坐标
核心功能及原理
功能要点 | Vue要点 | React要点 |
---|---|---|
拖拽 | 记录拖拽目标,mousemove 更新坐标 | 同左 |
重绘 | 响应式/状态驱动更新 <line> | 同左 |
扩展 | 可拓展多连线/命中检测 | 同左 |
- vue
- react
- html
实现电梯导航?
答案
组件结构
- 目录(nav) 固定位置的导航列表,展示各章节标题
- 内容容器(content) 可滚动内容区,包含各个带
id
的章节 - 观察者(observer) 使用 IntersectionObserver 同步激活项
核心功能及原理
功能要点 | Vue要点 | React要点 |
---|---|---|
滚动定位 | scrollIntoView({behavior:'smooth'}) | 同左 |
激活同步 | IntersectionObserver 设置 active | 同左 |
可访问性 | 为当前项设置 aria-current | 同左 |
- vue
- react
- html
实现长文本溢出自动折叠?
答案
组件结构
- 文本容器(container) 控制
max-height
与溢出隐藏,提供平滑过渡 - 切换(control) 按钮切换展开/收起状态
- 可访问性(a11y) 文本区域与按钮的语义与状态提示
核心功能及原理
功能要点 | Vue要点 | React要点 |
---|---|---|
收起高度 | collapsedHeight 控制 max-height | 同左 |
交互 | v-model /内部状态切换 | useState 切换 open 状态 |
可访问性 | 结合文本描述与按钮文案 | 同左 |
- vue
- react
- html
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 初始态一致性。
延伸阅读: