Skip to content

Commit 3178504

Browse files
committed
refactor(reactivity): make readonly non-tracking
1 parent 09b44e0 commit 3178504

File tree

11 files changed

+110
-250
lines changed

11 files changed

+110
-250
lines changed

packages/reactivity/__tests__/readonly.spec.ts

+15-114
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ import {
55
isReactive,
66
isReadonly,
77
markNonReactive,
8-
lock,
9-
unlock,
108
effect,
119
ref,
1210
shallowReadonly
@@ -90,22 +88,7 @@ describe('reactivity/readonly', () => {
9088
).toHaveBeenWarnedLast()
9189
})
9290

93-
it('should allow mutation when unlocked', () => {
94-
const observed: any = readonly({ foo: 1, bar: { baz: 2 } })
95-
unlock()
96-
observed.prop = 2
97-
observed.bar.qux = 3
98-
delete observed.bar.baz
99-
delete observed.foo
100-
lock()
101-
expect(observed.prop).toBe(2)
102-
expect(observed.foo).toBeUndefined()
103-
expect(observed.bar.qux).toBe(3)
104-
expect('baz' in observed.bar).toBe(false)
105-
expect(`target is readonly`).not.toHaveBeenWarned()
106-
})
107-
108-
it('should not trigger effects when locked', () => {
91+
it('should not trigger effects', () => {
10992
const observed: any = readonly({ a: 1 })
11093
let dummy
11194
effect(() => {
@@ -117,20 +100,6 @@ describe('reactivity/readonly', () => {
117100
expect(dummy).toBe(1)
118101
expect(`target is readonly`).toHaveBeenWarned()
119102
})
120-
121-
it('should trigger effects when unlocked', () => {
122-
const observed: any = readonly({ a: 1 })
123-
let dummy
124-
effect(() => {
125-
dummy = observed.a
126-
})
127-
expect(dummy).toBe(1)
128-
unlock()
129-
observed.a = 2
130-
lock()
131-
expect(observed.a).toBe(2)
132-
expect(dummy).toBe(2)
133-
})
134103
})
135104

136105
describe('Array', () => {
@@ -182,23 +151,7 @@ describe('reactivity/readonly', () => {
182151
expect(`target is readonly.`).toHaveBeenWarnedTimes(5)
183152
})
184153

185-
it('should allow mutation when unlocked', () => {
186-
const observed: any = readonly([{ foo: 1, bar: { baz: 2 } }])
187-
unlock()
188-
observed[1] = 2
189-
observed.push(3)
190-
observed[0].foo = 2
191-
observed[0].bar.baz = 3
192-
lock()
193-
expect(observed.length).toBe(3)
194-
expect(observed[1]).toBe(2)
195-
expect(observed[2]).toBe(3)
196-
expect(observed[0].foo).toBe(2)
197-
expect(observed[0].bar.baz).toBe(3)
198-
expect(`target is readonly`).not.toHaveBeenWarned()
199-
})
200-
201-
it('should not trigger effects when locked', () => {
154+
it('should not trigger effects', () => {
202155
const observed: any = readonly([{ a: 1 }])
203156
let dummy
204157
effect(() => {
@@ -214,30 +167,6 @@ describe('reactivity/readonly', () => {
214167
expect(dummy).toBe(1)
215168
expect(`target is readonly`).toHaveBeenWarnedTimes(2)
216169
})
217-
218-
it('should trigger effects when unlocked', () => {
219-
const observed: any = readonly([{ a: 1 }])
220-
let dummy
221-
effect(() => {
222-
dummy = observed[0].a
223-
})
224-
expect(dummy).toBe(1)
225-
226-
unlock()
227-
228-
observed[0].a = 2
229-
expect(observed[0].a).toBe(2)
230-
expect(dummy).toBe(2)
231-
232-
observed[0] = { a: 3 }
233-
expect(observed[0].a).toBe(3)
234-
expect(dummy).toBe(3)
235-
236-
observed.unshift({ a: 4 })
237-
expect(observed[0].a).toBe(4)
238-
expect(dummy).toBe(4)
239-
lock()
240-
})
241170
})
242171

243172
const maps = [Map, WeakMap]
@@ -275,23 +204,6 @@ describe('reactivity/readonly', () => {
275204
).toHaveBeenWarned()
276205
})
277206

278-
test('should allow mutation & trigger effect when unlocked', () => {
279-
const map = readonly(new Collection())
280-
const isWeak = Collection === WeakMap
281-
const key = {}
282-
let dummy
283-
effect(() => {
284-
dummy = map.get(key) + (isWeak ? 0 : map.size)
285-
})
286-
expect(dummy).toBeNaN()
287-
unlock()
288-
map.set(key, 1)
289-
lock()
290-
expect(dummy).toBe(isWeak ? 1 : 2)
291-
expect(map.get(key)).toBe(1)
292-
expect(`target is readonly`).not.toHaveBeenWarned()
293-
})
294-
295207
if (Collection === Map) {
296208
test('should retrieve readonly values on iteration', () => {
297209
const key1 = {}
@@ -346,22 +258,6 @@ describe('reactivity/readonly', () => {
346258
).toHaveBeenWarned()
347259
})
348260

349-
test('should allow mutation & trigger effect when unlocked', () => {
350-
const set = readonly(new Collection())
351-
const key = {}
352-
let dummy
353-
effect(() => {
354-
dummy = set.has(key)
355-
})
356-
expect(dummy).toBe(false)
357-
unlock()
358-
set.add(key)
359-
lock()
360-
expect(dummy).toBe(true)
361-
expect(set.has(key)).toBe(true)
362-
expect(`target is readonly`).not.toHaveBeenWarned()
363-
})
364-
365261
if (Collection === Set) {
366262
test('should retrieve readonly values on iteration', () => {
367263
const original = new Collection([{}, {}])
@@ -400,6 +296,19 @@ describe('reactivity/readonly', () => {
400296
expect(toRaw(a)).toBe(toRaw(b))
401297
})
402298

299+
test('readonly should track and trigger if wrapping reactive original', () => {
300+
const a = reactive({ n: 1 })
301+
const b = readonly(a)
302+
let dummy
303+
effect(() => {
304+
dummy = b.n
305+
})
306+
expect(dummy).toBe(1)
307+
a.n++
308+
expect(b.n).toBe(2)
309+
expect(dummy).toBe(2)
310+
})
311+
403312
test('observing already observed value should return same Proxy', () => {
404313
const original = { foo: 1 }
405314
const observed = readonly(original)
@@ -458,13 +367,5 @@ describe('reactivity/readonly', () => {
458367
`Set operation on key "foo" failed: target is readonly.`
459368
).not.toHaveBeenWarned()
460369
})
461-
462-
test('should keep reactive properties reactive', () => {
463-
const props: any = shallowReadonly({ n: reactive({ foo: 1 }) })
464-
unlock()
465-
props.n = reactive({ foo: 2 })
466-
lock()
467-
expect(isReactive(props.n)).toBe(true)
468-
})
469370
})
470371
})

packages/reactivity/src/baseHandlers.ts

+36-46
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { reactive, readonly, toRaw } from './reactive'
22
import { TrackOpTypes, TriggerOpTypes } from './operations'
33
import { track, trigger, ITERATE_KEY } from './effect'
4-
import { LOCKED } from './lock'
54
import { isObject, hasOwn, isSymbol, hasChanged, isArray } from '@vue/shared'
65
import { isRef } from './ref'
76

@@ -12,7 +11,7 @@ const builtInSymbols = new Set(
1211
)
1312

1413
const get = /*#__PURE__*/ createGetter()
15-
const shallowReactiveGet = /*#__PURE__*/ createGetter(false, true)
14+
const shallowGet = /*#__PURE__*/ createGetter(false, true)
1615
const readonlyGet = /*#__PURE__*/ createGetter(true)
1716
const shallowReadonlyGet = /*#__PURE__*/ createGetter(true, true)
1817

@@ -41,57 +40,47 @@ function createGetter(isReadonly = false, shallow = false) {
4140
return Reflect.get(arrayInstrumentations, key, receiver)
4241
}
4342
const res = Reflect.get(target, key, receiver)
43+
4444
if (isSymbol(key) && builtInSymbols.has(key)) {
4545
return res
4646
}
47+
4748
if (shallow) {
48-
track(target, TrackOpTypes.GET, key)
49-
// TODO strict mode that returns a shallow-readonly version of the value
49+
!isReadonly && track(target, TrackOpTypes.GET, key)
5050
return res
5151
}
52+
5253
if (isRef(res)) {
5354
if (targetIsArray) {
54-
track(target, TrackOpTypes.GET, key)
55+
!isReadonly && track(target, TrackOpTypes.GET, key)
5556
return res
5657
} else {
5758
// ref unwrapping, only for Objects, not for Arrays.
5859
return res.value
5960
}
60-
} else {
61-
track(target, TrackOpTypes.GET, key)
62-
return isObject(res)
63-
? isReadonly
64-
? // need to lazy access readonly and reactive here to avoid
65-
// circular dependency
66-
readonly(res)
67-
: reactive(res)
68-
: res
6961
}
62+
63+
!isReadonly && track(target, TrackOpTypes.GET, key)
64+
return isObject(res)
65+
? isReadonly
66+
? // need to lazy access readonly and reactive here to avoid
67+
// circular dependency
68+
readonly(res)
69+
: reactive(res)
70+
: res
7071
}
7172
}
7273

7374
const set = /*#__PURE__*/ createSetter()
74-
const shallowReactiveSet = /*#__PURE__*/ createSetter(false, true)
75-
const readonlySet = /*#__PURE__*/ createSetter(true)
76-
const shallowReadonlySet = /*#__PURE__*/ createSetter(true, true)
75+
const shallowSet = /*#__PURE__*/ createSetter(true)
7776

78-
function createSetter(isReadonly = false, shallow = false) {
77+
function createSetter(shallow = false) {
7978
return function set(
8079
target: object,
8180
key: string | symbol,
8281
value: unknown,
8382
receiver: object
8483
): boolean {
85-
if (isReadonly && LOCKED) {
86-
if (__DEV__) {
87-
console.warn(
88-
`Set operation on key "${String(key)}" failed: target is readonly.`,
89-
target
90-
)
91-
}
92-
return true
93-
}
94-
9584
const oldValue = (target as any)[key]
9685
if (!shallow) {
9786
value = toRaw(value)
@@ -148,37 +137,38 @@ export const mutableHandlers: ProxyHandler<object> = {
148137

149138
export const readonlyHandlers: ProxyHandler<object> = {
150139
get: readonlyGet,
151-
set: readonlySet,
152140
has,
153141
ownKeys,
154-
deleteProperty(target: object, key: string | symbol): boolean {
155-
if (LOCKED) {
156-
if (__DEV__) {
157-
console.warn(
158-
`Delete operation on key "${String(
159-
key
160-
)}" failed: target is readonly.`,
161-
target
162-
)
163-
}
164-
return true
165-
} else {
166-
return deleteProperty(target, key)
142+
set(target, key) {
143+
if (__DEV__) {
144+
console.warn(
145+
`Set operation on key "${String(key)}" failed: target is readonly.`,
146+
target
147+
)
148+
}
149+
return true
150+
},
151+
deleteProperty(target, key) {
152+
if (__DEV__) {
153+
console.warn(
154+
`Delete operation on key "${String(key)}" failed: target is readonly.`,
155+
target
156+
)
167157
}
158+
return true
168159
}
169160
}
170161

171162
export const shallowReactiveHandlers: ProxyHandler<object> = {
172163
...mutableHandlers,
173-
get: shallowReactiveGet,
174-
set: shallowReactiveSet
164+
get: shallowGet,
165+
set: shallowSet
175166
}
176167

177168
// Props handlers are special in the sense that it should not unwrap top-level
178169
// refs (in order to allow refs to be explicitly passed down), but should
179170
// retain the reactivity of the normal readonly object.
180171
export const shallowReadonlyHandlers: ProxyHandler<object> = {
181172
...readonlyHandlers,
182-
get: shallowReadonlyGet,
183-
set: shallowReadonlySet
173+
get: shallowReadonlyGet
184174
}

0 commit comments

Comments
 (0)