资讯动态

Vue3实战:打造useLazyData Hook,优雅实现数据懒加载与状态管理

发布时间:2026/10/13 13:53:47 来源:尧图企业网站定制
做 Vue3 开发这两年我发现自己日常碰到的不是那些花哨的组件动画反而是一遍又一遍的“数据什么时候加载”问题。列表页首屏要做懒加载Tab 切换要按需请求搜索按钮点了才拉数据滚动到底部继续分页……这些需求归拢到一起就是今天想聊的 useLazyData Hook一个在 Vue3 组合式 API 体系里把数据请求做成“默认不执行、按需触发、状态齐全”的自定义 Hook。它解决的远不只是“晚点发请求”这一件事而是把 loading、data、error、重新执行、重置这一整套状态管理收拢到一个函数里。适合正在写后台管理系统、移动端 H5、商城列表页或者对组合式函数设计感兴趣的同学参考代码可以直接抄回去按需改。1. 懒加载这件事为什么需要重新设计1.1 首屏性能与请求时机懒加载到底懒的是什么不少人一提“懒加载”就想到图片懒加载或者路由组件拆包。但在业务数据层面懒加载的意义是另一回事把请求动作从“组件创建”延后到“用户真正需要看到数据的那个瞬间”。这个延后带来两个直接收益第一是首屏请求变少接口压力和数据流量都会被压缩第二是用户感知变快因为每一个请求都对应一个明确的用户意图页面不会出现一堆无用的 loading 转圈。我用一个生活例子解释点外卖和囤菜的区别。如果你不确定家里今天要不要做饭一股脑把所有菜都买好放冰箱那大概率会有菜放坏。懒加载的逻辑是——用户开始点菜了你再把对应食材拿出来处理。放到前端场景里一个后台管理系统的详情页可能有基础信息、操作日志、统计数据三块内容用户大多数时候只是来看基础信息的一进页面就把三个接口全部打过去既浪费服务器资源又让首屏耗时被无关请求拖慢。如果把后两块做成懒加载等用户切到对应 Tab 或者滚动到那个区域时再拉数据整体体验会好很多。不过懒加载的目标说到底不是“完全不发请求”而是“让请求时机跟随用户行为”。如果只做到“组件挂载时不发请求”然后在某个事件里手动调一下接口那还只是最原始的懒加载。真正值得设计成一个 Hook 的是像 loading 状态、错误处理、竞态保护、防抖、重置这一整套围绕“延迟执行”展开的状态逻辑。这也是 useLazyData 比普通“写法改造”更有价值的地方——它把散落在每个页面里的请求状态管理统一成了同一个模型。1.2 常规写法的三个坑先说我在真实项目里看到最多、也踩过最多的三种写法。第一种是onMounted里一把梭。无论组件在哪种场景下被创建都在挂载后立刻把数据拉回来。这种写法在单体页面里问题不大但一旦进入 Tab 页、抽屉、弹窗这类“组件创建了但用户可能根本不会看”的场景就非常浪费。尤其弹窗类组件有些管理系统在打开弹窗的瞬间就把里面的全部接口发了一遍用户关掉再打开又是一遍请求量直接翻倍。第二种是用v-if加一个 created flag 包着子组件通过“子组件挂载时才请求”间接实现懒加载。这个思路本身没错但问题在于状态分散。父组件要维护 flag子组件要维护自己的 loading 和 error一旦切换逻辑变复杂比如“首次进入默认加载第二次切换要重新请求”控制权在两个组件之间来回传递代码很快就看不懂了。第三种是在事件回调里手动管理状态。比如点击查询按钮时自己写isLoading.value true然后 try catch 里请求、赋值、报错。这种写法最直接也是问题最隐蔽的每个按钮都要复制一遍同样的逻辑防抖和竞态处理很难做到统一。同一个页面里一个接口有竞态 bug另一个接口没有排查起来格外难受。这三条路我都走过。说实话它们都能跑但都只解决“某个页面”的问题没有解决“一类页面”的问题。1.3 一个 Hook 解决什么useLazyData 想做的是把下面这几件事固定在同一个模具里数据状态data、loading、error 全部收纳组件里不用再写散装 ref执行时机默认不执行由 execute 方法或依赖变化触发生命周期管理执行时自动处理 loading 开始和结束组件卸载后不再赋值可组合能力支持与其他逻辑分页、路由守卫、IntersectionObserver组合。说得更直接一点如果一个页面的数据请求需要满足“某些条件满足时才发”“失败后能重试”“切走再切回要重新拉”“请求结果要防竞态”那么它就是 useLazyData 的目标场景。这类场景在后台管理系统里几乎占了一半以上所以沉淀这样一个 Hook 的性价比很高——写一次长期复用。2. useLazyData 的 API 设计先定接口再写实现2.1 从 useAsyncData、useFetch 里借鉴的设计取舍写这个 Hook 之前我参考了 Nuxt 的 useAsyncData、VueUse 的 useAsyncState以及社区里很火的 alova 与 swrv。它们有一个共同点都在返回 data、loading、error 三件套都提供手动触发执行的方法。差异主要在于“执行时机”的默认值useFetch 默认自动执行VueUse 的 useAsyncState 也默认立即执行而 useLazyData 的定位是“数据请求默认不执行”。这个“默认不执行”就是“懒”字的来源。它意味着组件本身没有副作用副作用完全由开发者指定的触发条件来引爆。在 API 设计上这带来两个调整第一个是 immediate 参数默认 false但允许调用方改成 true退化成普通请求 Hook第二个是返回值里的 execute用于手动触发请求并且必须支持重复调用。也就是说useLazyData 不是替代 useFetch而是它的一种更受控的变体。另一个取舍是关于依赖触发的。我最初设计时想学习 watch 的用法给 Hook 传一个 watchSource 参数内部自动监听依赖变化并重新请求。但后来放弃了因为自动监听一旦结合防抖和竞态心智负担会明显上升很多人会搞不清楚请求到底是被谁触发的。最终我选择让 useLazyData 保持“执行器”形态外部想监听依赖变化自己调用 execute 即可。这样 Hook 的职责单一组合起来也更灵活。2.2 参数与返回值的完整约定useLazyData 的接口我最终定成了这样参数类型说明requestFn() PromiseT核心请求函数必须返回 PromiseoptionsUseLazyDataOptions可选配置包含 immediate、debounce、onSuccess、onError 等返回值类型说明dataRefT | null请求成功后的数据loadingRefboolean是否正在请求中errorRefError | null请求失败时的错误对象execute(...args) PromiseT手动触发请求返回当次结果reset() void清空数据、错误恢复初始状态isFirstLoadRefboolean是否还没有发过请求用于骨架屏判断这里有几个细节值得展开。第一data 的初始值是 null 而不是 undefined这样模板里判断“是否有数据”会更直观。第二loading 不包含“初始未执行”的状态因为懒加载场景里“还没请求”和“正在请求”是两种不同的 UI 状态前者适合显示占位提示后者适合显示转圈所以单独给了 isFirstLoad。第三execute 返回 Promise 是刻意为之调用方可以用 await 拿到当次结果做额外操作比如请求成功后自动关闭弹窗。2.3 为什么返回 ref 而不是 reactive这是我实际开发中被问得最多的问题。使用 reactive 看起来更简洁可以在模板里直接写 data.list但实际上有坑reactive 对象在解构时会丢失响应性。如果使用者在组件里写了const { data, execute } useLazyData(...)reactive 解构出来的 data 就变成了普通对象后续赋值不会触发视图更新而且报错不明显很难排查。返回 ref 就没有这个困扰。ref 在模板中会被自动解包在逻辑里又保持了引用语义解构后依然能维持响应性。另外一个原因是类型推导更稳定ReturnType 可以直接推出 RefT | null、Ref 使用方拿到手就知道怎么用。虽然 ref 在代码里要写 .value稍显啰嗦但在一个需要经常被组合、被解构的 Hook 中这一点啰嗦换来的稳定性是值得的。3. 核心实现把 useLazyData 从零写出来3.1 状态初始化与执行开关直接上核心代码。这个版本我做了简化保留了最关键的执行与状态逻辑方便阅读。import { ref, readonly } from vue interface UseLazyDataOptionsT { immediate?: boolean debounce?: number onSuccess?: (data: T) void onError?: (error: Error) void } export function useLazyDataT any( requestFn: (...args: any[]) PromiseT, options: UseLazyDataOptionsT {} ) { const data refT | null(null) const loading ref(false) const error refError | null(null) const isFirstLoad ref(true) let timer: ReturnTypetypeof setTimeout | null null async function run(args: any[]): PromiseT { loading.value true error.value null isFirstLoad.value false try { const result await requestFn(...args) data.value result options.onSuccess?.(result) return result } catch (e) { error.value e instanceof Error ? e : new Error(String(e)) options.onError?.(error.value) throw error.value } finally { loading.value false } } async function execute(...args: any[]): PromiseT { if (timer) clearTimeout(timer) if (options.debounce) { return new Promise((resolve, reject) { timer setTimeout(() { run(args).then(resolve).catch(reject) }, options.debounce) }) } return run(args) } function reset() { data.value null error.value null isFirstLoad.value true loading.value false } if (options.immediate) { execute() } return { data: readonly(data), loading: readonly(loading), error: readonly(error), isFirstLoad: readonly(isFirstLoad), execute, reset } }代码里我用了 readonly 包裹返回的 ref目的是防止外部直接篡改状态只能通过 execute 和 reset 修改。这个约束在多人协作的团队里特别好用能有效避免“某个人为了偷懒直接改 data.value”的情况。在一些项目里我看到直接把 data 返回成普通 ref结果页面里出现data.value.push(...)这种操作时间长了状态流就乱了。3.2 竞态处理最后一次请求说了算懒加载场景最容易遇到竞态问题。拿搜索框举例用户输入关键词后快速按下回车第一次请求还在路上第二次请求已经发出去了。如果第一次请求比第二次晚返回页面就会出现旧结果覆盖新结果的诡异 bug。我在真实项目里遇到过好几次排查起来特别痛苦因为表现不稳定时好时坏。解决竞态的通用思路是给每次请求编号只有最后一次请求的结果允许写入let requestSeq 0 async function run(args: any[]) { const seq requestSeq loading.value true error.value null isFirstLoad.value false try { const result await requestFn(...args) if (seq requestSeq) { data.value result onSuccess?.(result) } return result } catch (e) { if (seq requestSeq) { error.value e instanceof Error ? e : new Error(String(e)) } throw e } finally { if (seq requestSeq) { loading.value false } } }这里的核心是 requestSeq 递增计数器。每次 execute 都会使计数器加一返回结果时只有拿到最新计数的请求才有权限写状态。如果中途被更新的请求覆盖旧请求即使返回了也会被丢弃。这个方案比 AbortController 更轻量兼容性更好不用考虑浏览器对 fetch 中断的支持差异。当然如果接口是基于 fetch 的也可以配合 AbortController 直接把网络层请求取消掉减少带宽浪费两种方式不冲突可以同时用。3.3 防抖与重复触发保护懒加载 Hook 里的防抖要分场景讨论。搜索联想这种高频输入场景防抖时间给到 300ms 到 500ms 比较合适而按钮点击触发的查询防抖时间可以更短甚至不需要防抖但要加上“重复点击保护”——即 loading 状态下再次点击不再发起新请求。我这里给 execute 加了一个简单的防抖实现核心是 setTimeout 延迟执行。不过有一点需要提醒防抖只应该作用于“用户高频触发”的场景如果请求本身有严格的实时性要求防抖反而会带来延迟。比如详情页切换 Tab 时用户连续切多个 Tab我们希望最后一次切换立刻生效用防抖反而会让响应变慢。所以 options.debounce 应该设计成可选而不是默认开启。重复触发保护则在 execute 入口处判断async function execute(...args: any[]) { if (loading.value !options.allowConcurrent) { return data.value as T } // ... }当 allowConcurrent 为 false默认值时如果当前已有请求在飞新的 execute 调用会被忽略避免同一接口被连续点击按钮打出去十几个请求。这个开关在分页“加载更多”场景特别重要滚动到底部如果同时触发多次 execute没有这层保护页面上会出现重复数据。注意防抖和 loading 保护同时存在时loading判断要在防抖之前否则防抖排队期间 loading 还是 false会放行多个请求。3.4 stale data 策略与手动重置懒加载有个容易被忽略的体验问题重新触发请求时旧的 data 要不要保留很多场景里用户点完查询按钮在等待结果的这一两秒里应该让页面上继续显示旧数据而不是先清空成 null 导致页面闪烁。这个策略叫 stale data过期数据展示。useLazyData 默认保留了旧 data除非显式调用 reset 才会清空这样切 Tab、点查询的体验会顺滑很多。reset 的典型使用场景是退出登录、切换账号、进入编辑页前清理缓存。例如用户 A 的数据展示在页面上用户 B 登录后直接进入同一个页面如果不 resetB 会先看到 A 的数据这个隐患可大可小。所以我的建议是在包含敏感信息的页面里路由切换离开时主动 reset 一次或者干脆给 data 加一个“用户维度缓存 key”按用户隔离数据。4. 接入真实业务四种高频场景直接抄4.1 Tab 页签切换时惰性加载先看最常见的场景。管理后台的详情页经常有“基础信息 / 操作日志 / 关联订单”这种 Tab 结构如果一进页面就把所有 Tab 的数据都拉回来首屏请求会多出不少。用 useLazyData 改造后每个 Tab 的数据都变成独立的懒加载单元只有第一次切到该 Tab 时才触发请求。script setup langts import { watch, ref } from vue import { useLazyData } from /hooks/useLazyData const activeTab ref(base) const logData useLazyData(() fetchLogs(activeTab.value)) watch(activeTab, (val) { if (val logs) { logData.execute() } }) /script template div classtabs button clickactiveTab base基础信息/button button clickactiveTab logs操作日志/button /div div v-ifactiveTab logs p v-iflogData.isFirstLoad切换后才会请求日志数据/p p v-else-iflogData.loading正在加载.../p div v-else{{ logData.data }}/div /div /template这里我刻意把触发逻辑放在调用方而不是 Hook 内部因为 Tab 切换的时机跟业务高度相关有的场景只在第二次切到时请求有的场景每次切回都刷新这些差异 Hook 管不了交给调用方反而最灵活。实际项目中如果 Tab 非常多可以再封装一个 useTabLazyData把“首次切换才触发”的逻辑收敛成一个小工具但底层还是复用 useLazyData。4.2 搜索表单里“点查询才发请求”搜索列表页是懒加载 Hook 的另一个主战场。这类页面的共同点是初始化时通常要自动加载一次全部数据之后用户填完筛选条件点击查询用新条件重新请求。useLazyData 的 immediate 参数这时候就派上用场了。const params reactive({ keyword: , status: }) const listData useLazyData( () fetchList({ ...params }), { immediate: true, debounce: 300, onSuccess: (res) { total.value res.total } } ) function handleSearch() { listData.execute() }这里有个我反复提醒自己的点搜索参数不要写在 requestFn 外部然后用全局变量读取一定要把参数作为函数闭包的一部分传给 Hook。这样每次 execute 被调用时requestFn 都能拿到最新的参数快照。如果参数是异步获取的execute 也支持传参可以在 handleSearch 里把参数直接传给 execute 覆盖闭包里的旧值。具体用哪种看团队的代码风格但保持一致性很重要否则排查参数错乱时要花很多时间。4.3 上拉加载更多分页与追加模式列表页的“加载更多”其实是 useLazyData 的一个增强场景。普通 useLazyData 每次 execute 都会覆盖 data但分页需要的是“追加数据”。我的做法是在 Hook 之外包一层组合逻辑不污染 useLazyData 本身保持它的职责单一。const page ref(1) const list refItem[]([]) const finished ref(false) const pageData useLazyData( () fetchPage({ page: page.value, size: 20 }), { allowConcurrent: false } ) async function loadMore() { if (pageData.loading.value || finished.value) return const res await pageData.execute() if (res.list.length 0) { finished.value true return } list.value.push(...res.list) page.value }这里的关键是list 是页面自己的响应式状态useLazyData 只负责管理“当前这一页的请求状态”。这样解耦之后分页逻辑、排序逻辑、条件筛选逻辑都可以各管各的不会缠在一起。我见过有人把分页也塞进通用 Hook结果功能越加越多最终变成了一个谁也不敢动的巨无霸这种教训不值得重复。4.4 进入视口才加载IntersectionObserver 配合还有一种懒加载是“内容区域进入视口后才请求”常见于首页的信息流、大屏可视化的数据面板。它的核心是配合 IntersectionObserver 观察组件是否可见一旦可见就触发 execute。const targetRef refHTMLElement() const chartData useLazyData(() fetchChartData()) onMounted(() { const observer new IntersectionObserver((entries) { entries.forEach((entry) { if (entry.isIntersecting chartData.isFirstLoad.value) { chartData.execute() observer.disconnect() } }) }) if (targetRef.value) observer.observe(targetRef.value) })这样做的价值在于页面滚动到图表区域附近才开始拉接口如果用户根本没滚到那个位置请求就不会发出。实时数据这块要注意一点如果需要轮询或定时刷新记得在组件卸载时清理定时器和 observer不然会留下内存泄漏。懒加载 Hook 本身在卸载后不会自动停掉飞行中的请求所以我一般会在 onBeforeUnmount 里用一个释放标记把请求结果丢弃避免“死组件”继续更新状态。5. 常见问题与排查技巧实录5.1 数据有了但页面不更新这个问题在懒加载场景下出现率极高根因往往是响应性丢失。常见情况是把 Hook 返回的 ref 塞进 reactive 对象或者用响应式解构工具过度解包导致 data 变成一个普通值。我曾经在新项目里用 Vue 3.5 的响应式解构特性封装过一次结果列表赋值后页面纹丝不动排查了半天才发现是解构把 ref 的响应性拆掉了。排查方法很简单在赋值前后打一次console.log(data)把值打印出来看看是不是带 .value 的 RefImpl 对象。如果输出的是普通对象或值说明响应性已经在某个环节被拆掉。我的建议是不要过度依赖自动解包模板里直接用没问题但逻辑代码里尽量显式 .value并且避免把 Hook 返回的 ref 再嵌套进另一个 reactive 容器。5.2 快速连续操作时竞态覆盖我遇到过一个真实事故筛选区有“价格从高到低”和“价格从低到高”两个按钮用户快速点了两下结果列表按照错误的顺序渲染出来。原因就是第一次请求比第二次后返回把应该新的排序结果覆盖了。这类问题不是偶发只要用户操作够快必然出现。解决思路前面已经给出requestSeq 递增计数只有最新请求有写入权限。这类 bug 最怕的不是修而是复现时机不稳定所以我的建议是直接在 Hook 层面强制加入竞态保护而不是每次在业务代码里单独处理。如果你还在用没有竞态保护的请求封装强烈建议升级一下思路把请求序号作为核心机制内置进去。5.3 组件卸载后 loading 不结束懒加载场景里经常出现组件销毁但请求还在空中的情况。如果在请求完成后执行了赋值操作而组件已经卸载虽然 Vue 3 在大多数情况下不会抛错但状态更新仍然可能造成意料之外的副作用比如某个全局 store 被误改或者触发不必要的 watcher。我的做法是在 useLazyData 的 run 方法里加一个内部 token在组件卸载时通过一个dispose方法让 token 失效。如果你不想在 Hook 里自动注册生命周期也可以像竞态处理那样在调用方维护一个释放标记卸载时置为 true请求返回后检查标记再决定要不要写状态。无论如何不要让已经“死掉”的组件再去改状态这是前端状态管理的底线。5.4 依赖变化后旧数据闪烁懒加载通常会保留旧 data但如果依赖条件变了比如从查看“订单 A”切到“订单 B”旧的订单 A 数据在订单 B 加载完成之前一直显示会造成一种“页面内容与当前上下文不符”的错觉。这种情况下我建议手动判断是否要清旧数据。useLazyData 的 reset 可以清空数据但清空会让页面出现空白 loading 态体验也不一定更好。折中方案是给 data 加一个来源标记比如 sourceId切换时比较来源不同则显示骨架屏来源相同则保留旧数据实现无缝刷新。这个逻辑放在业务层处理Hook 本身不掺和。5.5 常见问题速查表现象可能原因解决办法数据赋值后页面不更新响应式丢失data 被解包成普通值返回 ref不放进 reactive 容器快速操作后结果顺序错乱请求竞态旧请求覆盖新结果requestSeq 递增计数丢弃过期结果组件卸载后仍在更新状态飞行中请求完成时组件已销毁卸载时递增 seq 或调用 dispose切换上下文后短暂显示旧数据stale data 策略保留旧值结合 sourceId 判断是否清空点击按钮重复发起多个请求没有 loading 保护execute 入口判断 loading 并返回旧 data请求失败后页面一直转圈错误分支忘记关闭 loading确保 finally 中统一关闭 loading这个表我整理自真实项目里的排查记录每一条都是踩过坑才写进去的。建议你收藏一份放到团队 wiki 里新人遇到类似问题可以先查表再动手少走很多弯路。6. 进阶玩法让 Hook 长出生态6.1 与 usePagination 组合成全套列表方案useLazyData 设计得足够收敛所以能很自然地和其他 Hook 组合。我在内部项目里把它和自定义的 usePagination 配合做出了一个既支持懒加载又支持分页的列表模板。export function useLazyPaginationT( fetchFn: (page: number) Promise{ list: T[]; total: number } ) { const page ref(1) const pageSize ref(20) const total ref(0) const { data, loading, error, execute, reset } useLazyData( () fetchFn(page.value), { onSuccess: (res) { total.value res.total } } ) const list computed(() data.value?.list ?? []) function loadMore() { if (loading.value) return page.value execute() } function reload() { page.value 1 return execute() } return { list, page, pageSize, total, loading, error, loadMore, reload, reset } }组合的核心思想是useLazyData 管理单次请求的状态usePagination 管理分页的页码和总数二者各司其职。只要每个 Hook 的职责边界清楚组合起来几乎不需要额外胶水代码。这也是我在团队里反复强调的原则先设计可组合的原子 Hook再写业务导向的复合 Hook而不是一开始就写一个什么都能干的大神类。6.2 泛型约束与完整类型推导useLazyData 的另一个价值是类型安全。通过泛型 T请求结果的类型可以从 requestFn 的返回类型自动推导出来使用方无需手动标注。对 TypeScript 项目来说这意味着重构接口字段时所有引用点会在编译期报错而不是运行时才发现字段名写错。interface User { id: number name: string } const { data } useLazyDataUser[](() fetchUsers()) // data 的类型被推导为 RefUser[] | null为了让类型推导更好用我在 Hook 的 execute 返回类型上做了一点设计execute 返回 Promise 所以调用方可以用 await result 拿到类型明确的数据。这样即使不用 data.value也能安全地访问请求结果。建议你在自己的项目里给 useLazyData 加上完整的泛型签名这会在后续维护时大幅减少类型相关的返工。6.3 定位对比与 alova、VueUse、swrv 的边界有些同学会问社区不是已经有 alova、VueUse 的 useAsyncState、swrv 这些方案了吗为什么还要自己写一个我的看法是这类库解决的是“请求状态管理”的大而全方案而 useLazyData 解决的是“懒加载时机控制”这个小而专的问题。两者不是替代关系而是可以共存。方案默认执行时机主打特性适合场景VueUse useAsyncState立即执行轻量、功能聚焦简单请求状态alova可配置多模式请求、缓存、状态管理复杂请求体系swrv立即执行SWR 策略缓存优先、后台刷新数据强一致性场景useLazyData默认不执行时机控制、竞态保护、组合灵活Tab 懒加载、按需查询、分页如果想快速跑通业务直接用 VueUse 和 alova 都没问题。但如果你像我一样项目里有大量“部分数据必须延迟加载”的需求并且希望请求状态完全受控那自己沉淀一个极简的 useLazyData 反而比引入大库更清晰。毕竟这个 Hook 只有不到一百行代码任何团队成员花一个小时就能完全读懂。6.4 实战体会与适用边界最后说点实在的使用体会。我自己把这套 Hook 用在公司后台管理系统和移动端 H5 里接近一年最大的感受是懒加载不是银弹它最适合“数据区域独立、用户触发明确、请求可延后”的场景比如 Tab 内容、抽屉详情、滚动加载。但如果你的页面本身数据量不大或者用户一进来就必须看到全部内容那强行懒加载只会增加代码复杂度没有任何收益。还有一个边界值得注意useLazyData 默认不执行意味着如果某些页面忘了触发 execute页面会一直停留在“未加载”状态。为了规避这个问题我在团队里立了一个约定凡是使用 useLazyData 的页面必须在模板里体现 isFirstLoad 状态至少要有一个“点击加载”或空白占位提示避免用户以为页面坏了。这个约定看起来很笨但实际运行下来它帮我减少了好几次线上“白屏”投诉。再分享一个小技巧给 useLazyData 的 requestFn 传参时尽量用函数工厂而不是组件实例里的 this。比如const factory (id: number) () fetchDetail(id)这样每个实例持有一份独立闭包组件之间互不干扰配合 keep-alive 缓存时也不会出现参数串号。这个小细节在维护老项目时帮了我不少忙每次排查参数错乱时都会想起这个约定。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑