Skip to content

Commit 416845a

Browse files
posvaantfu
andauthored
fix(ssr): set() twice lose reactivity (#821)
Co-authored-by: Anthony Fu <[email protected]>
1 parent 92b7eb1 commit 416845a

File tree

3 files changed

+38
-8
lines changed

3 files changed

+38
-8
lines changed

src/reactivity/reactive.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,8 @@ export function observe<T>(obj: T): T {
136136
* Mock __ob__ for object recursively
137137
*/
138138
export function mockReactivityDeep(obj: any, seen = new Set()) {
139-
if (seen.has(obj)) return
139+
if (seen.has(obj) || hasOwn(obj, '__ob__') || !Object.isExtensible(obj))
140+
return
140141

141142
def(obj, '__ob__', mockObserver(obj))
142143
seen.add(obj)

src/reactivity/set.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,33 @@ export function set<T>(target: AnyObject, key: any, val: T): T {
2424
`Cannot set reactive property on undefined, null, or primitive value: ${target}`
2525
)
2626
}
27+
28+
const ob = target.__ob__
29+
30+
function ssrMockReactivity() {
31+
// in SSR, there is no __ob__. Mock for reactivity check
32+
if (ob && isObject(val) && !hasOwn(val, '__ob__')) {
33+
mockReactivityDeep(val)
34+
}
35+
}
36+
2737
if (isArray(target)) {
2838
if (isValidArrayIndex(key)) {
2939
target.length = Math.max(target.length, key)
3040
target.splice(key, 1, val)
41+
ssrMockReactivity()
3142
return val
3243
} else if (key === 'length' && (val as any) !== target.length) {
3344
target.length = val as any
34-
;(target as any).__ob__?.dep.notify()
45+
ob?.dep.notify()
3546
return val
3647
}
3748
}
3849
if (key in target && !(key in Object.prototype)) {
3950
target[key] = val
51+
ssrMockReactivity()
4052
return val
4153
}
42-
const ob = target.__ob__
4354
if (target._isVue || (ob && ob.vmCount)) {
4455
__DEV__ &&
4556
warn(
@@ -48,18 +59,16 @@ export function set<T>(target: AnyObject, key: any, val: T): T {
4859
)
4960
return val
5061
}
62+
5163
if (!ob) {
5264
target[key] = val
5365
return val
5466
}
67+
5568
defineReactive(ob.value, key, val)
5669
// IMPORTANT: define access control before trigger watcher
5770
defineAccessControl(target, key, val)
58-
59-
// in SSR, there is no __ob__. Mock for reactivity check
60-
if (isObject(target[key]) && !hasOwn(target[key], '__ob__')) {
61-
mockReactivityDeep(target[key])
62-
}
71+
ssrMockReactivity()
6372

6473
ob.dep.notify()
6574
return val

test/ssr/ssrReactive.spec.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,26 @@ describe('SSR Reactive', () => {
5757
expect(isRaw(state)).toBe(false)
5858
})
5959

60+
it('should work on objects sets with set()', () => {
61+
const state = ref<any>({})
62+
63+
set(state.value, 'a', {})
64+
expect(isReactive(state.value.a)).toBe(true)
65+
66+
set(state.value, 'a', {})
67+
expect(isReactive(state.value.a)).toBe(true)
68+
})
69+
70+
it('should work on arrays sets with set()', () => {
71+
const state = ref<any>([])
72+
73+
set(state.value, 1, {})
74+
expect(isReactive(state.value[1])).toBe(true)
75+
76+
set(state.value, 1, {})
77+
expect(isReactive(state.value[1])).toBe(true)
78+
})
79+
6080
// #550
6181
it('props should work with set', async (done) => {
6282
let props: any

0 commit comments

Comments
 (0)