Skip to content

Commit 1133e94

Browse files
committed
feat(fe): add deepEqual function for deep comparison and integrate it into runtime and service components
1 parent de7c8b8 commit 1133e94

6 files changed

Lines changed: 82 additions & 20 deletions

File tree

fe/packages/common/src/core/utils.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,23 @@ export function isNil(value) {
1212
return value === null || value === undefined
1313
}
1414

15+
export function deepEqual(a, b) {
16+
if (a === b) {
17+
return true
18+
}
19+
if (typeof a !== 'object' || typeof b !== 'object' || a == null || b == null) {
20+
return false
21+
}
22+
23+
const keysA = Object.keys(a)
24+
const keysB = Object.keys(b)
25+
if (keysA.length !== keysB.length) {
26+
return false
27+
}
28+
29+
return keysA.every(key => deepEqual(a[key], b[key]))
30+
}
31+
1532
export function get(data, path) {
1633
return _get(data, path)
1734
}

fe/packages/common/src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export {
99
animationToStyle,
1010
camelCaseToUnderscore,
1111
cloneDeep,
12+
deepEqual,
1213
get,
1314
getDataAttributes,
1415
isAndroid,

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

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getDataAttributes, set, uuid } from '@dimina/common'
1+
import { deepEqual, getDataAttributes, set, uuid } from '@dimina/common'
22
import { Components, deepToRaw, triggerEvent } from '@dimina/components'
33
import {
44
createApp,
@@ -429,21 +429,33 @@ class Runtime {
429429
let skipInitialPropsNotify = true
430430

431431
watch(
432-
props,
433-
(newProps) => {
432+
() => deepToRaw(props),
433+
(newProps, oldProps = {}) => {
434434
Object.assign(data, newProps)
435435
if (skipInitialPropsNotify) {
436436
skipInitialPropsNotify = false
437437
return
438438
}
439+
440+
const changedProps = Object.entries(newProps).reduce((acc, [key, value]) => {
441+
if (!deepEqual(value, oldProps[key])) {
442+
acc[key] = value
443+
}
444+
return acc
445+
}, {})
446+
447+
if (Object.keys(changedProps).length === 0) {
448+
return
449+
}
450+
439451
message.send({
440452
type: 't',
441453
target: 'service',
442454
body: {
443455
bridgeId,
444456
moduleId,
445457
methodName: 'tO', // triggerObserver
446-
event: deepToRaw(newProps),
458+
event: changedProps,
447459
},
448460
})
449461
},

fe/packages/service/__tests__/component-observer-order.spec.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,50 @@ describe('Component.tO observer ordering', () => {
8181
])
8282
})
8383

84+
it('runs later changed props before earlier props in reverse batch order', () => {
85+
const calls = []
86+
const instance = {
87+
data: {
88+
defaultDate: 1,
89+
type: 'single',
90+
currentDate: null,
91+
},
92+
__pendingSyncedProps__: {},
93+
__info__: {
94+
observers: {},
95+
properties: {
96+
defaultDate: {
97+
observer: 'observeDefaultDate',
98+
},
99+
type: {
100+
observer: 'observeType',
101+
},
102+
},
103+
},
104+
observeDefaultDate: vi.fn(function observeDefaultDate(val) {
105+
this.data.currentDate = val
106+
calls.push(`defaultDate:${this.data.currentDate}`)
107+
}),
108+
observeType: vi.fn(function observeType() {
109+
if (this.data.type === 'multiple') {
110+
this.data.currentDate = [this.data.defaultDate]
111+
}
112+
calls.push(`type:${Array.isArray(this.data.currentDate) ? 'array' : typeof this.data.currentDate}`)
113+
}),
114+
}
115+
116+
Component.prototype.tO.call(instance, {
117+
defaultDate: 1,
118+
type: 'multiple',
119+
})
120+
121+
expect(calls).toEqual([
122+
'type:array',
123+
'defaultDate:1',
124+
])
125+
expect(instance.data.currentDate).toBe(1)
126+
})
127+
84128
it('skips duplicate observer execution but keeps data in sync for identical render replay after parent sync', () => {
85129
const observeShow = vi.fn()
86130
const instance = {

fe/packages/service/src/core/utils.js

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { get, isFunction, isNil } from '@dimina/common'
1+
import { deepEqual, get, isFunction, isNil } from '@dimina/common'
22

33
const queue = []
44
let isFlushing = false
@@ -21,19 +21,7 @@ function flushQueue() {
2121
}
2222
}
2323

24-
export function deepEqual(a, b) {
25-
if (a === b)
26-
return true // 引用相同
27-
if (typeof a !== 'object' || typeof b !== 'object' || a == null || b == null)
28-
return false
29-
30-
const keysA = Object.keys(a)
31-
const keysB = Object.keys(b)
32-
if (keysA.length !== keysB.length)
33-
return false
34-
35-
return keysA.every(key => deepEqual(a[key], b[key]))
36-
}
24+
export { deepEqual }
3725
/**
3826
* 将 computed 的 key 提前写入 data,确保渲染层初始化时能正确追踪这些 key 的响应式依赖。
3927
*

fe/packages/service/src/instance/component/component.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -534,7 +534,7 @@ export class Component {
534534
}
535535
}
536536

537-
propertyObserversToExecute.reverse().forEach(run => run())
537+
propertyObserversToExecute.forEach(run => run())
538538
}
539539

540540
async #invokeInitLifecycle() {
@@ -608,7 +608,7 @@ export class Component {
608608
observersToExecute.forEach(run => run())
609609
invokeBehaviorObservers(this, Object.keys(nextData), Object.fromEntries(Object.entries(nextData).map(([prop]) => [prop, undefined])))
610610

611-
// 同批次 props 更新时,优先让后写入的属性观察器完成派生状态计算
611+
// 同批次 props 更新时,让后写入的属性观察器覆盖前者的派生状态
612612
propertyObserversToExecute.reverse().forEach(run => run())
613613
}
614614

0 commit comments

Comments
 (0)