返回笔记首页

项目经验总结

主题配置

一、踩坑经验

坑 1:虚拟滚动的滚动条跳动问题

问题描述 实现动态高度虚拟滚动后,快速滚动时滚动条会跳动,体验很差。

问题原因

  1. 首次渲染时用预估高度计算总高度
  2. 测量真实高度后,总高度变化
  3. 滚动条位置相对总高度的比例变了,导致跳动
解决方案
javascript
// 记录滚动位置
const scrollTop = containerRef.value.scrollTop

// 更新高度
updatePositions()

// 计算新旧总高度的比例
const ratio = newTotalHeight / oldTotalHeight

// 恢复滚动位置
containerRef.value.scrollTop = scrollTop * ratio
关键点
  • 在更新高度前记录滚动位置
  • 按比例恢复滚动位置
  • 使用 requestAnimationFrame 确保 DOM 更新完成

教训 动态高度虚拟滚动一定要处理好高度变化时的滚动位置同步。


坑 2:主题切换时页面闪烁

问题描述 切换主题时,页面会先显示默认主题,然后闪一下变成目标主题。

问题原因

  1. HTML 加载时,CSS Variables 还没设置
  2. 浏览器用默认值渲染
  3. JavaScript 执行后才设置 CSS Variables
  4. 导致重绘,产生闪烁
解决方案
html
<!-- 在 HTML head 中内联脚本,优先执行 -->
<script>
(function() {
  const theme = localStorage.getItem('theme') || 'light'
  document.documentElement.setAttribute('data-theme', theme)

  // 如果是暗色主题,立即应用关键变量
  if (theme === 'dark') {
    const style = document.createElement('style')
    style.innerHTML = `
      :root {
        --bg-color: #141414;
        --text-color: #ffffffd9;
      }
    `
    document.head.appendChild(style)
  }
})()
</script>
关键点
  • 脚本内联在 HTML 中,优先于所有资源加载
  • 只设置关键变量,减少执行时间
  • 使用 localStorage 持久化主题选择

教训 首屏渲染的关键样式要内联,不能依赖异步加载的 JavaScript。


坑 3:Form 表单校验的时机问题

问题描述 用户输入时频繁触发校验,异步校验导致请求过多,体验差。

问题原因

  1. input 事件每次输入都触发
  2. 异步校验(如用户名唯一性)请求频繁
  3. 服务器压力大,用户体验差
解决方案
javascript
// 对异步校验做防抖
const asyncValidator = {
  validator: debounce(async (rule, value) => {
    if (!value) return Promise.resolve()

    const exists = await checkUsernameExists(value)
    if (exists) {
      return Promise.reject('用户名已存在')
    }
    return Promise.resolve()
  }, 500),  // 500ms 防抖
  trigger: 'change'
}

// 使用缓存避免重复校验
const validationCache = new Map()

async function validateWithCache(value) {
  if (validationCache.has(value)) {
    return validationCache.get(value)
  }

  const result = await doValidate(value)
  validationCache.set(value, result)
  return result
}
关键点
  • 异步校验必须防抖
  • 使用缓存避免重复请求
  • 区分 changeblur 触发时机

教训 表单校验要考虑性能,不能每次输入都发请求。


坑 4:Rollup 打包样式丢失

问题描述 按需引入组件后,样式没有加载,组件显示不正常。

问题原因

  1. 组件 .vue 文件中的 <style> 被编译成独立的 CSS 文件
  2. 按需引入只引入了 JS,没有引入 CSS
  3. 需要手动引入样式
解决方案
javascript
// 1. 配置 Rollup 提取样式
export default {
  plugins: [
    vue(),
    css({
      extract: true,  // 提取 CSS 到单独文件
      output: 'style.css'
    })
  ]
}

// 2. 为每个组件生成样式入口
// packages/components/button/style.js
import './style.css'

// 3. 配置 package.json 的 sideEffects
{
  "sideEffects": [
    "*.css",
    "*.less",
    "*/style.js"
  ]
}

// 4. 提供 Babel 插件自动引入
import { Button } from 'vue-ui'
// 自动转换为
import Button from 'vue-ui/es/button'
import 'vue-ui/es/button/style'
关键点
  • 样式和 JS 分离
  • 标记样式文件为副作用
  • 提供自动化工具

教训 组件库的样式加载方案要提前规划,不能事后补救。


坑 5:大量组件实例的内存泄漏

问题描述 Table 组件渲染大量数据后,内存持续增长,页面越来越卡。

问题原因

  1. ResizeObserver 没有正确销毁
  2. 事件监听器没有移除
  3. 响应式数据没有清理
解决方案
javascript
// 1. 正确使用 onUnmounted
onUnmounted(() => {
  // 断开 ResizeObserver
  if (resizeObserver) {
    resizeObserver.disconnect()
    resizeObserver = null
  }

  // 移除事件监听
  if (containerRef.value) {
    containerRef.value.removeEventListener('scroll', onScroll)
  }

  // 清理定时器
  if (rafId) {
    cancelAnimationFrame(rafId)
  }
})

// 2. 使用 WeakMap 存储临时数据
const tempCache = new WeakMap()  // 自动垃圾回收

// 3. 避免闭包引用大对象
// ❌ 不好
const data = ref(largeArray)
const computed = computed(() => {
  return data.value.map(item => /* 大量计算 */)
})

// ✅ 好
const computed = computed(() => {
  const data = props.data  // 直接用 props,不缓存
  return data.map(item => /* 大量计算 */)
})
关键点
  • 所有订阅都要取消订阅
  • 使用 WeakMap/WeakSet 避免内存泄漏
  • 避免闭包引用大对象

教训 组件销毁时一定要清理资源,否则会导致内存泄漏。


二、性能数据

1. 打包体积优化

优化项 优化前 优化后 提升
全量引入 800KB 280KB (gzip) 65%
按需引入 5 个组件 - 40KB (gzip) -
Tree Shaking 生效率 - 85% -

关键优化

  • ES Module + Rollup
  • 样式按需加载
  • 外部化 Vue

2. 虚拟滚动性能

数据量 传统渲染 虚拟滚动 提升
1,000 行 300ms 45ms 6.7x
10,000 行 3,000ms 80ms 37.5x
100,000 行 超时 150ms -

关键优化

  • DOM 数量从 10 万降到 25 个
  • 二分查找定位可视区域
  • RAF 节流滚动事件

3. 主题切换性能

指标 优化前 优化后 提升
切换耗时 500ms 100ms 5x
重绘次数 200+ 1 次 200x
是否闪烁 -

关键优化

  • View Transition API
  • CSS Variables 批量更新
  • 首屏内联关键样式

4. 表单校验性能

场景 优化前 优化后 提升
100 字段表单首次校验 800ms 200ms 4x
单字段异步校验请求 20 次/s 2 次/s 10x
内存占用 150MB 90MB 40%

关键优化

  • 异步校验防抖
  • 校验结果缓存
  • 按需校验策略

三、可吹的点(面试加分项)

1. 技术深度

虚拟滚动支持动态高度

  • 不是简单的固定高度虚拟滚动
  • 支持 10 万级数据流畅渲染
  • 用 ResizeObserver + 二分查找实现
  • 性能提升 37 倍

主题系统设计

  • CSS Variables + Less 混合方案
  • 运行时零成本切换
  • 支持多套品牌主题定制
  • 使用 View Transition API 平滑过渡

复杂表单方案

  • 支持字段联动、异步校验、动态表单
  • provide/inject 实现组件通信
  • 防抖 + 缓存优化性能

2. 工程化能力

Monorepo 架构

  • pnpm workspace 管理多包
  • changeset 自动化版本管理
  • 清晰的代码组织和职责划分

构建优化

  • Vite 开发,Rollup 打包
  • 支持 ESM/CJS/UMD 三种格式
  • Tree Shaking 生效率 85%
  • 打包体积减少 70%

质量保障

  • 单元测试覆盖率 85%+
  • Storybook 文档体系
  • ESLint + Prettier 规范
  • CI/CD 自动化流程

3. 业务价值

覆盖面广

  • 6 个业务系统接入
  • 40+ 个通用组件
  • 减少重复开发 60%

提效明显

  • 前端开发效率提升 40%
  • 需求交付周期缩短 30%
  • 组件 Bug 率降低 30%

成本节约

  • 节省人力成本 10 人月/年
  • 降低维护成本
  • 统一技术栈,降低学习成本

4. 团队影响

技术分享

  • 输出 3 场技术分享
  • 影响前端团队 30+ 人
  • 沉淀最佳实践文档

开源精神

  • 组件库 GitHub Star 200+
  • NPM 周下载 500+
  • 培养 5 名核心贡献者

技术标准

  • 成为团队技术标准
  • 新人上手时间从 2 周缩短至 3 天
  • 建立 Owner 机制

四、数据总结

核心指标

plain
📦 打包体积优化
  全量引入: 800KB → 280KB (gzip)
  按需引入: 120KB → 40KB (gzip)
  优化幅度: 70%

⚡ 性能提升
  虚拟滚动: 3s → 80ms (37.5x)
  主题切换: 500ms → 100ms (5x)
  表单校验: 800ms → 200ms (4x)

👥 业务覆盖
  接入系统: 6 个
  沉淀组件: 40+
  代码复用率: 60%

💰 成本节约
  人力成本: 10 人月/年
  开发效率: +40%
  Bug 率: -30%

五、面试话术示例

当面试官问"这个项目最大的挑战是什么?"

"最大的挑战是虚拟滚动支持动态高度。传统虚拟滚动要求固定高度,但我们的业务场景中,Table 的行高是动态的,比如文本换行、图片加载。

我的解决方案是:首次渲染时测量所有行的真实高度并缓存,然后用 ResizeObserver 监听高度变化实时更新。定位可视区域时用二分查找优化,把复杂度从 O(n) 降到 O(log n)。

最终效果是支持 10 万条数据流畅滚动,首屏渲染从 3 秒降到 80ms,内存占用减少 90%。这个技术点我花了两周时间攻克,也是整个项目最有技术含量的部分。"

当面试官问"这个项目给你带来了什么成长?"

"这个项目让我对前端工程化有了更深的理解。

第一,我学会了如何设计一个可扩展的架构。从 monorepo 管理、组件设计模式、到构建流程,都是从 0 到 1 亲手搭建的。

第二,我的性能优化能力提升了。虚拟滚动、主题切换、打包优化,每个点都深入研究,形成了自己的方法论。

第三,我的业务理解能力变强了。组件库不是闭门造车,要深入业务,理解痛点,才能设计出真正有用的组件。

最重要的是,这个项目让我意识到技术要服务业务。我们的组件库不是最炫的,但是最适合公司业务的,这才是它的价值所在。"


六、注意事项

1. 面试时的坑

不要说"抄袭 Element Plus" ✅ 强调"针对业务深度定制"

不要说"没什么难度" ✅ 突出"虚拟滚动"等技术难点

不要只讲技术不讲业务 ✅ 结合业务场景和价值

2. 数据真实性

  • 所有性能数据都要能解释清楚怎么测的
  • 业务覆盖数据要和简历其他部分一致
  • 如果数据是估算的,要说明是"约"

3. 回答技巧

  • 用 STAR 法则:情境、任务、行动、结果
  • 突出自己的贡献,不要都说"我们"
  • 准备 2-3 个深度技术点,详细讲解
  • 其他点简要说明,避免面试官追问不会的

4. 扩展知识

面试可能问到的相关知识:

  • Vue3 Composition API 原理
  • Vite 和 Webpack 的区别
  • Tree Shaking 原理
  • CSS Variables 浏览器兼容性
  • 虚拟 DOM diff 算法
  • 组件设计模式

建议提前准备这些知识点的回答。