Skip to content

Commit 4f5b9b9

Browse files
committed
fix(fe): streamline IntersectionObserver setup by ensuring pending setups are completed before initialization
1 parent 7fcf142 commit 4f5b9b9

2 files changed

Lines changed: 59 additions & 4 deletions

File tree

fe/packages/render/__tests__/pending-setup-race.spec.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,59 @@ describe('竞态场景:IntersectionObserver 与子组件 created 的时序', (
277277
expect(elapsed).toBeLessThan(50)
278278
expect(observerCallbackLog[0].listenerRegisteredBeforeEmit).toBe(true)
279279
})
280+
281+
it('【关键】二次打开场景:addIntersectionObserver 到达时 pendingSetupCount 已为 0,但 DOM 出现后 setup 才开始', async () => {
282+
/**
283+
* 模拟二次打开的精确时序:
284+
* - 关闭弹框时 sliding-scale 销毁,_pendingSetupCount 早已归零
285+
* - 重新打开:addIntersectionObserver 的 invokeAPI 消息先到达 render 侧(同步分发)
286+
* - show 的 setData 经 queueMicrotask + $nextTick 后才触发 Vue re-render
287+
* - 所以 addIntersectionObserver 到达时,sliding-scale 的 setup 甚至还没开始
288+
* - _pendingSetupCount 此刻为 0,不能直接 resolve
289+
*
290+
* 正确做法:先等 DOM 出现(waitForElement),DOM 出现时 setup 已发 mC(count++),
291+
* 然后再 waitForPendingSetups,等 created 完成后才建立 observer
292+
*/
293+
294+
// 模拟"先等 DOM 出现(期间 setup 开始),再等 pending setups"的正确顺序
295+
let domAppeared = false
296+
let setupStarted = false
297+
298+
// step1: addIntersectionObserver 到达,此时 count=0(弹框已关再开)
299+
expect(tracker._pendingSetupCount).toBe(0)
300+
301+
// step2: 模拟 waitForElement 异步等待 DOM(此时 setup 还没开始)
302+
const domReadyPromise = new Promise((resolve) => {
303+
setTimeout(() => {
304+
// DOM 出现:同时触发 setup 开始(beginSetup)
305+
setupStarted = true
306+
tracker.beginSetup()
307+
domAppeared = true
308+
resolve()
309+
}, 5)
310+
})
311+
312+
// step3: 等 DOM 出现
313+
await domReadyPromise
314+
expect(domAppeared).toBe(true)
315+
expect(setupStarted).toBe(true)
316+
expect(tracker._pendingSetupCount).toBe(1) // DOM 出现后 count 变为 1
317+
318+
// step4: 现在 waitForPendingSetups,等 created 完成
319+
const waitPromise = tracker.waitForPendingSetups()
320+
321+
// step5: 模拟 service 侧 created 完成(注册 EventBus),然后 completeSetup
322+
await new Promise(resolve => setTimeout(resolve, 5))
323+
eventBus.once('needFeedBack', () => {})
324+
tracker.completeSetup()
325+
326+
await waitPromise
327+
328+
// step6: 此时建立 observer 并触发(EventBus 监听已注册)
329+
const listenerRegistered = eventBus.has('needFeedBack')
330+
expect(listenerRegistered).toBe(true)
331+
eventBus.emit('needFeedBack')
332+
})
280333
})
281334

282335
describe('边界情况', () => {

fe/packages/render/src/core/runtime.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -689,9 +689,7 @@ class Runtime {
689689
}
690690

691691
addIntersectionObserver(opts) {
692-
// 等待所有挂起的组件 setup(即等待 service 侧 created 完成)结束后再建立 observer,
693-
// 避免 IntersectionObserver 首次回调早于子组件生命周期钩子注册而导致事件丢失
694-
this._waitForPendingSetups().then(async () => {
692+
(async () => {
695693
const { bridgeId, params: { targetSelector, relativeInfo, moduleId, options, success } } = opts
696694

697695
const el = await this.waitForEl(this.instance.get(moduleId))
@@ -758,6 +756,10 @@ class Runtime {
758756
}
759757

760758
const targetEls = await this.waitForElement(el, targetSelector, options.observeAll ? 'querySelectorAll' : 'querySelector')
759+
// 目标 DOM 已出现(对应组件 setup 中 mC 已发出、_pendingSetupCount 已 ++),
760+
// 此时等待所有 pending setup 完成(service 侧 created/attached 执行完毕),
761+
// 确保 IntersectionObserver 首次回调到达 service 时相关生命周期钩子已注册
762+
await this._waitForPendingSetups()
761763
if (!targetEls) {
762764
console.error('[system]', '[render]', 'Failed to find target element for intersection observer')
763765
return
@@ -824,7 +826,7 @@ class Runtime {
824826
args: { observerId },
825827
},
826828
})
827-
})
829+
})()
828830
}
829831

830832
removeIntersectionObserver({ params: { observerId } }) {

0 commit comments

Comments
 (0)