普通视图

发现新文章,点击刷新页面。
昨天以前首页

第二章 虎牢关前初试Composition,吕布持v-model搦战

2025年8月16日 11:28

上回说到桃园三英,以reactive义旗初燃响应之火。未及旬日,消息传遍十八路诸侯:董卓挟旧框架jQuery,虎踞虎牢关,其义子吕布手持方天画戟,戟上双锋刻着“v-model”二字,号称“双向绑定第一猛将”,凡与之交锋者,模板与数据顷刻错乱,士卒(开发者)叫苦不迭。诸侯震惧,聚于酸枣大营,共议破敌之策。

玄德谓关、张曰:“吕布之势,在于旧双向绑定蛮横:一处改值,处处牵连,调试如坠五里雾。吾等新得Vue 3利器,可趁此关试其锋芒。”于是三人随公孙瓒军,星夜抵虎牢关下。

翌日黎明,鼓角齐鸣。吕布匹马出阵,戟指诸侯大喝:“谁敢与我斗绑定!”诸侯阵中,一将应声而出,乃江东孙坚,旧用Angular.js,被吕布一戟挑落马下,数据流当场断链。诸侯失色。

玄德回顾云长:“二弟,汝可出战?”云长丹凤眼微睁,提刀而出,却非青龙偃月,而是一柄新铸长刀,名曰<script setup>。刀背暗藏三大新纹:

  1. 纹一:ref()——化普通值为响应利刃;
  2. 纹二:computed()——凝衍生数据为刀罡;
  3. 纹三:watchEffect()——布追踪暗劲,敌一动则我即知。

云长横刀立马,朗声道:“吕布小儿,敢接我Composition刀法否?”

吕布大笑,挥戟直取。云长举刀迎敌,只见刀光闪处,代码如诗:

<!-- 虎牢关·关云长挑战牌.vue -->
<script setup lang="ts">
import { ref, computed, watchEffect } from 'vue'

const luBuHP = ref(100)          // 吕布血条
const guanYuATK = ref(30)        // 关羽攻击
const isCritical = computed(() => luBuHP.value < 50)
watchEffect(() => {
  if (isCritical.value) console.warn('吕布进入红血,狂暴模式开启!')
})
</script>

<template>
  <div>
    <p>吕布剩余血量:{{ luBuHP }}</p>
    <button @click="luBuHP -= guanYuATK">青龙斩</button>
  </div>
</template>

刀戟相交,吕布顿觉旧法迟钝:每次v-model改动,需层层触发$digest,而云长刀法轻灵,仅在必要处精准更新,DOM重绘大减。三十合后,吕布戟法渐乱。

翼德见云长占上风,大吼一声,挺矛跃马而出。其矛名Teleport,一矛刺出,竟将“

”元素瞬息移至关外战场中央,避开层层嵌套之DOM重围,令吕布措手不及。

玄德亦催马上前,袖中飞出一面小旗,旗上书“Suspense”。旗展处,诸侯军阵忽现异步大军:先遣async setup()轻骑诱敌,随后<Suspense>大军稳压阵脚,待数据从远域(API)归来,三军齐发,吕布旧部顿时崩溃。

吕布见势不妙,拨马欲走。云长刀锋一转,大喝:“留下v-model!”刀落处,吕布戟上“双向绑定”四字应声而碎,化作漫天光屑。诸侯军乘势掩杀,虎牢关大门轰然洞开。

战后,酸枣大营庆功。曹操把盏谓玄德曰:“今日之战,方知Vue 3新特性之利:
<script setup>简军书(样板代码减半);
ref & reactive分兵权(原始与对象各得其所);
computedwatchEffect如伏兵暗哨,料敌先机;
Teleport奇兵突袭,解定位之困;
Suspense则整饬异步之乱,令军容肃整。

有此五利,何愁旧框架不破?”

玄德谦逊答曰:“皆赖众志。然董卓未灭,jQuery余孽犹在,吾等当整军向西,直驱长安。”

众人齐应。夜色下,营火映照着一面新旗——旗上赫然是Vue 3的Logo,而下方绣着一行小字:

“Composition API · 破釜沉舟”

(第二章完)

——下回《凤仪亭密谋自定义ref,貂蝉夜探shallowReactive》

第一章 桃园灯火初燃,响应义旗始揭

2025年8月16日 11:24

却说中平元年,黄巾大乱,页面失序,交互崩坏。时有涿郡涿县义士刘备字玄德,胸怀仁道,常叹 DOM 操作之繁;关羽字云长,力能扛鼎,恨 reflow 之劳形;张飞字翼德,声若巨雷,苦 repaint 之伤神。三人心念苍生,俱欲收拾旧山河,重整乾坤之渲染。

是夜,桃园深处,月色如银。三人焚香再拜,誓以 Vue 3 为号,举响应式义旗。刘备执defineReactive为剑,关羽握ref作刀,张飞抡reactive成矛。金兰结义,共立宏愿:
“自此以后,凡我兄弟,同写单文件组件,同守 Composition API,同赴前端沙场,生死与共,不可背弃!”

誓毕,刘备展卷,出一物示二人,乃《setup()》秘策一卷。卷首云:
“夫响应之道,先立 state,后衍 effect;state 者,民生之本,effect 者,治世之干。”
关羽、张飞拜受,顿首再拜。于是三英于桃园之中,点燃第一簇数据之火——

// 桃园结义·state.ts
import { reactive } from 'vue'

export const peachGarden = reactive({
  brothers: ['刘备', '关羽', '张飞'],
  oath: '上报国家,下安黎庶,同生共死,永不背义'
})

火光照处,页面微颤,旧日静态之 HTML 忽生涟漪。张飞惊曰:“异哉!我但改一字,视图即随动,莫非天命?”
刘备笑曰:“非天命也,Proxy 之力耳。凡入reactive者,皆录于 WeakMap,牵一发而动全身,此即‘响应’二字真谛!”

关羽抚髯而思:“既有响应之兵,尚缺调度之帅。来日当筑effect营寨,使数据之兵随帅旗而行,无令散乱。”

三人言谈未尽,忽闻远处鼓角之声——黄巾残党jQuery余孽,正聚众欲复辟直接操作 DOM 之旧制。刘备拔剑而起:“兄弟,随我出村,初试响应锋芒!”

于是桃园灯火未灭,三骑已扬尘而去。前端乱世,自此拉开序幕。后人有诗赞曰:

桃园一火照前端,
Proxy 初开响应天。
自此 DOM 随令转,
三分代码见真源。

(第一章完)

——下回《虎牢关前初试Composition,吕布持双向绑定搦战》

🔥10 个被忽视的 Vue3 API 开发利器,用过 5 个才算真正入门

2025年8月15日 16:26

Vue3你真的会用吗?这些 API 开发利器,你用过几个
——10 个被忽视却真香的生产力技巧与场景案例

如果你已经能够熟练地把 ref/reactivewatch/computeddefineProps/defineEmits 倒背如流,那么恭喜你,Vue3 的“入门课”已经通关。但 Vue3 在源码层埋了不少小而美的“隐藏关卡”,它们往往能在关键时刻把代码体积、性能、可维护性同时拉高一个档次。下面 10 个技巧,全部来自社区实战沉淀,每一个都附带真实业务场景 + 代码片段,看完直接就能搬进项目。


1. 巨型表格秒开:shallowRef + markRaw

痛点
后台系统一次性拉 5k 条数据,前端还要做排序/过滤,页面直接卡成 PPT。

方案
只读展示层数据用 shallowRef 包起来,再把不会变动的配置markRaw 标记,Vue 会跳过深层 Proxy 创建,首屏渲染时间直接腰斩。

import { shallowRef, markRaw } from 'vue'

const tableData = shallowRef([])
const columns   = markRaw([           // 列配置完全静态
  { key: 'name', title: '姓名' },
  { key: 'age',  title: '年龄' }
])

// 接口回来直接替换引用即可
api.getList().then(res => tableData.value = res)

实测 5k 条 20 字段数据,FPS 从 12 提升到 45。


2. 跨层级通信不再层层透传:provide + inject + 稳定 key

痛点
Form → FormItem → Input 三级组件,rules、modelValue、校验方法都要逐级 props/emits,写吐了。

方案
父级 provide('formCtx', {...}) 一次性把整包逻辑扔下去;子级用 inject 读取。
技巧:把动态数据包在一个 readonly(reactive({...})) 里,防止子组件误改,又能保证引用稳定、不触发多余更新。

// Form.vue
provide('formCtx', readonly(reactive({
  model: formModel,
  rules: formRules,
  validate
})))

3. 弹窗/抽屉永远挂载到 body:Teleport

痛点
position: fixed 遇到父级 transform 直接失效;z-index 战争更是灾难。

方案
把弹窗内容直接 teleport 到 body 末尾,告别样式副作用。

<Teleport to="body">
  <Modal v-if="visible" />
</Teleport>

4. 异步白屏终结者:Suspense

场景
路由懒加载、图表组件、Markdown 渲染器,首次加载总闪一下白屏。

方案
Suspense 把“加载中”和“真正组件”分离,骨架屏/Loading 丝滑切换。

<Suspense>
  <template #default><AsyncChart /></template>
  <template #fallback><Skeleton /></template>
</Suspense>

5. Hooks 内存零泄漏:effectScope + onScopeDispose

痛点
公共 Hook 里 watch / onMounted / addEventListener 一大堆,组件卸载时漏清一个就内存泄漏。

方案
effectScope 把同一业务域的副作用打包,组件销毁时一句 scope.stop() 一键清理。

import { effectScope, onScopeDispose } from 'vue'

export function useMouse() {
  const scope = effectScope()
  const pos   = reactive({ x: 0, y: 0 })

  scope.run(() => {
    const update = (e: MouseEvent) => { pos.x = e.clientX; pos.y = e.clientY }
    window.addEventListener('mousemove', update)
    onScopeDispose(() => window.removeEventListener('mousemove', update))
  })

  onScopeDispose(() => scope.stop())
  return pos
}

不管页面里 useMouse() 调用多少次,都只挂一个 mousemove,性能 & 内存双保险。


6. 巨型列表滑动不卡顿:v-memo

痛点
v-for 渲染 1k+ 行图文卡片,每次筛选都要全量 diff。

方案
v-memo="[item.id, item.title]" 让 Vue 仅当依赖变化时才重新渲染当前行,其余直接复用 DOM。
实测 1k 条滚动帧率从 18 提到 55。


7. 秒杀倒计时秒级更新:computed 缓存 + 懒执行

场景
活动页 10 个倒计时卡片,每秒更新一次,但只更新“剩余秒数”文本。

方案
把“剩余时间”做成 computed,依赖是 Date.now(),再用 setInterval 触发引用变更。
由于 computed 自带缓存,只有真正需要刷新的卡片才会重新计算,其余直接命中缓存。


8. 全局配置防手滑:readonly

场景
团队协作,总有新人直接改 config.apiBaseURL,导致线上 404。

方案
暴露出去的配置统一 readonly,写保护运行时报错,从源头杜绝。

export const config = readonly(reactive({
  apiBaseURL: 'https://prod.api.com',
  timeout: 8000
}))

9. 模板更干净:Fragment + 无根组件

痛点
为了包一层 div 导致 CSS 布局崩坏(flex/grid 直接多一个层级)。

方案
Vue3 支持 Fragment,组件可以返回多个根节点。

<template>
  <h2>{{ title }}</h2>
  <p>{{ content }}</p>
</template>

10. 一行实现防抖指令:Custom Directives

场景
搜索框、按钮防重复点击,每次复制粘贴防抖函数太啰嗦。

方案
封装成指令,模板里一行搞定。

app.directive('debounce', {
  mounted(el, binding) {
    let timer: any
    el.addEventListener(binding.arg || 'click', () => {
      clearTimeout(timer)
      timer = setTimeout(binding.value, binding.modifiers?.wait || 300)
    })
  }
})
<button v-debounce:click.wait="500" @click="submit">提交</button>

小结:一张脑图带走

场景痛点 冷门利器 一句话记忆
巨型数据渲染 shallowRef / markRaw 只追踪引用,跳过深层 Proxy
跨层级通信 provide/inject + readonly 父传子整包逻辑,子改就报错
弹窗样式地狱 Teleport 直接挂到 body,层级永远最上
异步组件白屏 Suspense 骨架屏 & 组件无缝切换
Hooks 内存泄漏 effectScope 副作用打包,一键 stop
长列表卡顿 v-memo 行级缓存,只 diff 变更行
全局配置防误改 readonly 写保护,运行时报错
模板多余根节点 Fragment 不想包 div 就不包
防抖/节流到处复制 自定义指令 模板级一行声明,逻辑集中

把这篇文章收藏起来,下次 Code Review 的时候,看看你又能从团队代码里“薅”出几个优化点吧!

❌
❌