From 416845aa0798a222a94931770baa98c43b361435 Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Tue, 5 Oct 2021 02:47:20 +0200 Subject: [PATCH] fix(ssr): `set()` twice lose reactivity (#821) Co-authored-by: Anthony Fu --- src/reactivity/reactive.ts | 3 ++- src/reactivity/set.ts | 23 ++++++++++++++++------- test/ssr/ssrReactive.spec.ts | 20 ++++++++++++++++++++ 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/src/reactivity/reactive.ts b/src/reactivity/reactive.ts index f7ddc8fa..386cb299 100644 --- a/src/reactivity/reactive.ts +++ b/src/reactivity/reactive.ts @@ -136,7 +136,8 @@ export function observe(obj: T): T { * Mock __ob__ for object recursively */ export function mockReactivityDeep(obj: any, seen = new Set()) { - if (seen.has(obj)) return + if (seen.has(obj) || hasOwn(obj, '__ob__') || !Object.isExtensible(obj)) + return def(obj, '__ob__', mockObserver(obj)) seen.add(obj) diff --git a/src/reactivity/set.ts b/src/reactivity/set.ts index 2270c9ec..01a55f6a 100644 --- a/src/reactivity/set.ts +++ b/src/reactivity/set.ts @@ -24,22 +24,33 @@ export function set(target: AnyObject, key: any, val: T): T { `Cannot set reactive property on undefined, null, or primitive value: ${target}` ) } + + const ob = target.__ob__ + + function ssrMockReactivity() { + // in SSR, there is no __ob__. Mock for reactivity check + if (ob && isObject(val) && !hasOwn(val, '__ob__')) { + mockReactivityDeep(val) + } + } + if (isArray(target)) { if (isValidArrayIndex(key)) { target.length = Math.max(target.length, key) target.splice(key, 1, val) + ssrMockReactivity() return val } else if (key === 'length' && (val as any) !== target.length) { target.length = val as any - ;(target as any).__ob__?.dep.notify() + ob?.dep.notify() return val } } if (key in target && !(key in Object.prototype)) { target[key] = val + ssrMockReactivity() return val } - const ob = target.__ob__ if (target._isVue || (ob && ob.vmCount)) { __DEV__ && warn( @@ -48,18 +59,16 @@ export function set(target: AnyObject, key: any, val: T): T { ) return val } + if (!ob) { target[key] = val return val } + defineReactive(ob.value, key, val) // IMPORTANT: define access control before trigger watcher defineAccessControl(target, key, val) - - // in SSR, there is no __ob__. Mock for reactivity check - if (isObject(target[key]) && !hasOwn(target[key], '__ob__')) { - mockReactivityDeep(target[key]) - } + ssrMockReactivity() ob.dep.notify() return val diff --git a/test/ssr/ssrReactive.spec.ts b/test/ssr/ssrReactive.spec.ts index 432244be..c5fa4d6e 100644 --- a/test/ssr/ssrReactive.spec.ts +++ b/test/ssr/ssrReactive.spec.ts @@ -57,6 +57,26 @@ describe('SSR Reactive', () => { expect(isRaw(state)).toBe(false) }) + it('should work on objects sets with set()', () => { + const state = ref({}) + + set(state.value, 'a', {}) + expect(isReactive(state.value.a)).toBe(true) + + set(state.value, 'a', {}) + expect(isReactive(state.value.a)).toBe(true) + }) + + it('should work on arrays sets with set()', () => { + const state = ref([]) + + set(state.value, 1, {}) + expect(isReactive(state.value[1])).toBe(true) + + set(state.value, 1, {}) + expect(isReactive(state.value[1])).toBe(true) + }) + // #550 it('props should work with set', async (done) => { let props: any