Vue3 + TypeScript 组件模式:从能用到可维护

痛点 项目初期组件写得很快,半年后常见症状是: props 越加越多 事件命名混乱 业务逻辑散在模板与组件里 要解决这些问题,关键是建立稳定的组件边界。 用 defineProps 和 defineEmits 明确契约 const props = defineProps<{ modelValue: string disabled?: boolean }>() const emit = defineEmits<{ (e: "update:modelValue", val: string): void (e: "submit"): void }>() 契约清楚后,重构成本会明显降低。 分离容器组件与展示组件 容器组件负责数据获取、状态管理 展示组件只关心渲染和交互事件 这能避免“万能组件”不断膨胀。 组合式函数复用逻辑 export function usePagination() { const page = ref(1) const pageSize = ref(20) const setPage = (v: number) => (page.value = v) return { page, pageSize, setPage } } 把重复逻辑提炼到 composable,比 mixin 更直观也更可控。 结语 Vue3 + TS 的上限很高,但前提是组件契约、职责边界、逻辑复用这三件事先做好。代码会更稳,也更容易协作。

2026年4月22日 · 1 分钟 · BvBeJ

Vue3 性能优化:从响应式细节到页面加载

背景 前端性能这件事,经常有两个极端: 一种是完全不管,页面卡了再说 另一种是上来就讲虚拟列表、SSR、代码分割,结果项目里真正拖慢页面的点根本不在那 Vue3 本身已经做了不少优化,但框架快,不代表业务代码就一定快。 真正影响体验的,通常还是这些问题: 不必要的响应式开销 大列表重复渲染 首屏加载资源过大 watch 写得太随意,副作用失控 这篇文章只聊实战里最常见、最值回票价的优化点。 先判断瓶颈在哪 优化前先确认问题类型。通常分三类: 首屏慢 JS 包太大、资源太多、接口太慢。 交互卡 某个状态变更引起大面积重渲染。 长列表卡 DOM 数量过多,滚动和 patch 开销都很高。 这三类问题的解决手段完全不同。不要把“页面卡”都归因到 Vue 响应式。 不要把所有东西都塞进 reactive 很多项目里常见这种写法: const state = reactive({ tableData: [], chartInstance: null, editor: null, wsConnection: null, filters: { keyword: '', status: 'all', }, }) 看起来统一,实际上问题不少。 像图表实例、编辑器对象、WebSocket 连接这种第三方对象,本来就不是拿来做细粒度响应式追踪的。把它们塞进深层响应式对象里,只会增加代理成本,还可能带来奇怪副作用。 更合理的拆法是: import { reactive, shallowRef, markRaw } from 'vue' const filters = reactive({ keyword: '', status: 'all', }) const tableData = shallowRef<User[]>([]) const chartInstance = shallowRef<any>(null) const editor = shallowRef<any>(null) function initChart(el: HTMLDivElement) { chartInstance.value = markRaw(createChart(el)) } 这里的思路很明确: 业务表单状态,用 reactive 大数组、外部实例,用 shallowRef 不希望被代理的对象,用 markRaw 这不是“写法偏好”,而是直接影响更新成本。 ...

2026年4月16日 · 2 分钟 · BvBeJ

Vue3 Composition API 实战经验

背景 Vue3 发布两年多了,从 Options API 迁移到 Composition API 的项目也有了不少。这里总结一些实战经验。 为什么需要 Composition API Options API 的问题:逻辑关注点分散在一个组件的各个选项里(data、methods、computed、watch…)。 // Options API - 逻辑分散 export default { data() { return { count: 0 } }, methods: { increment() { this.count++ } }, computed: { doubled() { return this.count * 2 } }, watch: { count(newVal) { console.log('count changed:', newVal) } } } <!-- Composition API - 逻辑内聚 --> <script setup> import { ref, computed, watch } from 'vue' const count = ref(0) const doubled = computed(() => count.value * 2) const increment = () => count.value++ watch(count, (newVal) => { console.log('count changed:', newVal) }) </script> 实用技巧 1. ref vs reactive 该用哪个? // primitive types (String, Number, Boolean) -> ref const name = ref('BvBeJ') const age = ref(18) // objects/arrays -> reactive const user = reactive({ name: 'BvBeJ', skills: ['Go', 'Rust', 'C++'] }) // 或者对象也用 ref,通过 .value 访问 const user = ref({ name: 'BvBeJ' }) user.value.name = 'New Name' // 需要 .value 我的习惯: 简单类型用 ref,复杂对象用 reactive。TypeScript 类型推导更清晰。 ...

2026年4月3日 · 2 分钟 · BvBeJ