Skip to content

Commit 09b4202

Browse files
committed
refactor(reactivity): adjust APIs
BREAKING CHANGE: Reactivity APIs adjustments: - `readonly` is now non-tracking if called on plain objects. `lock` and `unlock` have been removed. A `readonly` proxy can no longer be directly mutated. However, it can still wrap an already reactive object and track changes to the source reactive object. - `isReactive` now only returns true for proxies created by `reactive`, or a `readonly` proxy that wraps a `reactive` proxy. - A new utility `isProxy` is introduced, which returns true for both reactive or readonly proxies. - `markNonReactive` has been renamed to `markRaw`.
1 parent 11654a6 commit 09b4202

File tree

11 files changed

+139
-125
lines changed

11 files changed

+139
-125
lines changed

packages/reactivity/__tests__/effect.spec.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
TrackOpTypes,
77
TriggerOpTypes,
88
DebuggerEvent,
9-
markNonReactive,
9+
markRaw,
1010
ref
1111
} from '../src/index'
1212
import { ITERATE_KEY } from '../src/effect'
@@ -732,9 +732,9 @@ describe('reactivity/effect', () => {
732732
expect(dummy).toBe(3)
733733
})
734734

735-
it('markNonReactive', () => {
735+
it('markRaw', () => {
736736
const obj = reactive({
737-
foo: markNonReactive({
737+
foo: markRaw({
738738
prop: 0
739739
})
740740
})

packages/reactivity/__tests__/reactive.spec.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {
33
reactive,
44
isReactive,
55
toRaw,
6-
markNonReactive,
6+
markRaw,
77
shallowReactive
88
} from '../src/reactive'
99
import { mockWarn } from '@vue/shared'
@@ -146,10 +146,10 @@ describe('reactivity/reactive', () => {
146146
expect(reactive(d)).toBe(d)
147147
})
148148

149-
test('markNonReactive', () => {
149+
test('markRaw', () => {
150150
const obj = reactive({
151151
foo: { a: 1 },
152-
bar: markNonReactive({ b: 2 })
152+
bar: markRaw({ b: 2 })
153153
})
154154
expect(isReactive(obj.foo)).toBe(true)
155155
expect(isReactive(obj.bar)).toBe(false)

packages/reactivity/__tests__/readonly.spec.ts

+92-84
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ import {
44
toRaw,
55
isReactive,
66
isReadonly,
7-
markNonReactive,
7+
markRaw,
88
effect,
99
ref,
10-
shallowReadonly
10+
shallowReadonly,
11+
isProxy
1112
} from '../src'
1213
import { mockWarn } from '@vue/shared'
1314

@@ -22,22 +23,23 @@ describe('reactivity/readonly', () => {
2223
describe('Object', () => {
2324
it('should make nested values readonly', () => {
2425
const original = { foo: 1, bar: { baz: 2 } }
25-
const observed = readonly(original)
26-
expect(observed).not.toBe(original)
27-
expect(isReactive(observed)).toBe(true)
28-
expect(isReadonly(observed)).toBe(true)
26+
const wrapped = readonly(original)
27+
expect(wrapped).not.toBe(original)
28+
expect(isProxy(wrapped)).toBe(true)
29+
expect(isReactive(wrapped)).toBe(false)
30+
expect(isReadonly(wrapped)).toBe(true)
2931
expect(isReactive(original)).toBe(false)
3032
expect(isReadonly(original)).toBe(false)
31-
expect(isReactive(observed.bar)).toBe(true)
32-
expect(isReadonly(observed.bar)).toBe(true)
33+
expect(isReactive(wrapped.bar)).toBe(false)
34+
expect(isReadonly(wrapped.bar)).toBe(true)
3335
expect(isReactive(original.bar)).toBe(false)
3436
expect(isReadonly(original.bar)).toBe(false)
3537
// get
36-
expect(observed.foo).toBe(1)
38+
expect(wrapped.foo).toBe(1)
3739
// has
38-
expect('foo' in observed).toBe(true)
40+
expect('foo' in wrapped).toBe(true)
3941
// ownKeys
40-
expect(Object.keys(observed)).toEqual(['foo', 'bar'])
42+
expect(Object.keys(wrapped)).toEqual(['foo', 'bar'])
4143
})
4244

4345
it('should not allow mutation', () => {
@@ -49,54 +51,54 @@ describe('reactivity/readonly', () => {
4951
},
5052
[qux]: 3
5153
}
52-
const observed: Writable<typeof original> = readonly(original)
54+
const wrapped: Writable<typeof original> = readonly(original)
5355

54-
observed.foo = 2
55-
expect(observed.foo).toBe(1)
56+
wrapped.foo = 2
57+
expect(wrapped.foo).toBe(1)
5658
expect(
5759
`Set operation on key "foo" failed: target is readonly.`
5860
).toHaveBeenWarnedLast()
5961

60-
observed.bar.baz = 3
61-
expect(observed.bar.baz).toBe(2)
62+
wrapped.bar.baz = 3
63+
expect(wrapped.bar.baz).toBe(2)
6264
expect(
6365
`Set operation on key "baz" failed: target is readonly.`
6466
).toHaveBeenWarnedLast()
6567

66-
observed[qux] = 4
67-
expect(observed[qux]).toBe(3)
68+
wrapped[qux] = 4
69+
expect(wrapped[qux]).toBe(3)
6870
expect(
6971
`Set operation on key "Symbol(qux)" failed: target is readonly.`
7072
).toHaveBeenWarnedLast()
7173

72-
delete observed.foo
73-
expect(observed.foo).toBe(1)
74+
delete wrapped.foo
75+
expect(wrapped.foo).toBe(1)
7476
expect(
7577
`Delete operation on key "foo" failed: target is readonly.`
7678
).toHaveBeenWarnedLast()
7779

78-
delete observed.bar.baz
79-
expect(observed.bar.baz).toBe(2)
80+
delete wrapped.bar.baz
81+
expect(wrapped.bar.baz).toBe(2)
8082
expect(
8183
`Delete operation on key "baz" failed: target is readonly.`
8284
).toHaveBeenWarnedLast()
8385

84-
delete observed[qux]
85-
expect(observed[qux]).toBe(3)
86+
delete wrapped[qux]
87+
expect(wrapped[qux]).toBe(3)
8688
expect(
8789
`Delete operation on key "Symbol(qux)" failed: target is readonly.`
8890
).toHaveBeenWarnedLast()
8991
})
9092

9193
it('should not trigger effects', () => {
92-
const observed: any = readonly({ a: 1 })
94+
const wrapped: any = readonly({ a: 1 })
9395
let dummy
9496
effect(() => {
95-
dummy = observed.a
97+
dummy = wrapped.a
9698
})
9799
expect(dummy).toBe(1)
98-
observed.a = 2
99-
expect(observed.a).toBe(1)
100+
wrapped.a = 2
101+
expect(wrapped.a).toBe(1)
100102
expect(dummy).toBe(1)
101103
expect(`target is readonly`).toHaveBeenWarned()
102104
})
@@ -105,65 +107,66 @@ describe('reactivity/readonly', () => {
105107
describe('Array', () => {
106108
it('should make nested values readonly', () => {
107109
const original = [{ foo: 1 }]
108-
const observed = readonly(original)
109-
expect(observed).not.toBe(original)
110-
expect(isReactive(observed)).toBe(true)
111-
expect(isReadonly(observed)).toBe(true)
110+
const wrapped = readonly(original)
111+
expect(wrapped).not.toBe(original)
112+
expect(isProxy(wrapped)).toBe(true)
113+
expect(isReactive(wrapped)).toBe(false)
114+
expect(isReadonly(wrapped)).toBe(true)
112115
expect(isReactive(original)).toBe(false)
113116
expect(isReadonly(original)).toBe(false)
114-
expect(isReactive(observed[0])).toBe(true)
115-
expect(isReadonly(observed[0])).toBe(true)
117+
expect(isReactive(wrapped[0])).toBe(false)
118+
expect(isReadonly(wrapped[0])).toBe(true)
116119
expect(isReactive(original[0])).toBe(false)
117120
expect(isReadonly(original[0])).toBe(false)
118121
// get
119-
expect(observed[0].foo).toBe(1)
122+
expect(wrapped[0].foo).toBe(1)
120123
// has
121-
expect(0 in observed).toBe(true)
124+
expect(0 in wrapped).toBe(true)
122125
// ownKeys
123-
expect(Object.keys(observed)).toEqual(['0'])
126+
expect(Object.keys(wrapped)).toEqual(['0'])
124127
})
125128

126129
it('should not allow mutation', () => {
127-
const observed: any = readonly([{ foo: 1 }])
128-
observed[0] = 1
129-
expect(observed[0]).not.toBe(1)
130+
const wrapped: any = readonly([{ foo: 1 }])
131+
wrapped[0] = 1
132+
expect(wrapped[0]).not.toBe(1)
130133
expect(
131134
`Set operation on key "0" failed: target is readonly.`
132135
).toHaveBeenWarned()
133-
observed[0].foo = 2
134-
expect(observed[0].foo).toBe(1)
136+
wrapped[0].foo = 2
137+
expect(wrapped[0].foo).toBe(1)
135138
expect(
136139
`Set operation on key "foo" failed: target is readonly.`
137140
).toHaveBeenWarned()
138141

139142
// should block length mutation
140-
observed.length = 0
141-
expect(observed.length).toBe(1)
142-
expect(observed[0].foo).toBe(1)
143+
wrapped.length = 0
144+
expect(wrapped.length).toBe(1)
145+
expect(wrapped[0].foo).toBe(1)
143146
expect(
144147
`Set operation on key "length" failed: target is readonly.`
145148
).toHaveBeenWarned()
146149

147150
// mutation methods invoke set/length internally and thus are blocked as well
148-
observed.push(2)
149-
expect(observed.length).toBe(1)
151+
wrapped.push(2)
152+
expect(wrapped.length).toBe(1)
150153
// push triggers two warnings on [1] and .length
151154
expect(`target is readonly.`).toHaveBeenWarnedTimes(5)
152155
})
153156

154157
it('should not trigger effects', () => {
155-
const observed: any = readonly([{ a: 1 }])
158+
const wrapped: any = readonly([{ a: 1 }])
156159
let dummy
157160
effect(() => {
158-
dummy = observed[0].a
161+
dummy = wrapped[0].a
159162
})
160163
expect(dummy).toBe(1)
161-
observed[0].a = 2
162-
expect(observed[0].a).toBe(1)
164+
wrapped[0].a = 2
165+
expect(wrapped[0].a).toBe(1)
163166
expect(dummy).toBe(1)
164167
expect(`target is readonly`).toHaveBeenWarnedTimes(1)
165-
observed[0] = { a: 2 }
166-
expect(observed[0].a).toBe(1)
168+
wrapped[0] = { a: 2 }
169+
expect(wrapped[0].a).toBe(1)
167170
expect(dummy).toBe(1)
168171
expect(`target is readonly`).toHaveBeenWarnedTimes(2)
169172
})
@@ -176,14 +179,15 @@ describe('reactivity/readonly', () => {
176179
const key1 = {}
177180
const key2 = {}
178181
const original = new Collection([[key1, {}], [key2, {}]])
179-
const observed = readonly(original)
180-
expect(observed).not.toBe(original)
181-
expect(isReactive(observed)).toBe(true)
182-
expect(isReadonly(observed)).toBe(true)
182+
const wrapped = readonly(original)
183+
expect(wrapped).not.toBe(original)
184+
expect(isProxy(wrapped)).toBe(true)
185+
expect(isReactive(wrapped)).toBe(false)
186+
expect(isReadonly(wrapped)).toBe(true)
183187
expect(isReactive(original)).toBe(false)
184188
expect(isReadonly(original)).toBe(false)
185-
expect(isReactive(observed.get(key1))).toBe(true)
186-
expect(isReadonly(observed.get(key1))).toBe(true)
189+
expect(isReactive(wrapped.get(key1))).toBe(false)
190+
expect(isReadonly(wrapped.get(key1))).toBe(true)
187191
expect(isReactive(original.get(key1))).toBe(false)
188192
expect(isReadonly(original.get(key1))).toBe(false)
189193
})
@@ -209,15 +213,15 @@ describe('reactivity/readonly', () => {
209213
const key1 = {}
210214
const key2 = {}
211215
const original = new Collection([[key1, {}], [key2, {}]])
212-
const observed: any = readonly(original)
213-
for (const [key, value] of observed) {
216+
const wrapped: any = readonly(original)
217+
for (const [key, value] of wrapped) {
214218
expect(isReadonly(key)).toBe(true)
215219
expect(isReadonly(value)).toBe(true)
216220
}
217-
observed.forEach((value: any) => {
221+
wrapped.forEach((value: any) => {
218222
expect(isReadonly(value)).toBe(true)
219223
})
220-
for (const value of observed.values()) {
224+
for (const value of wrapped.values()) {
221225
expect(isReadonly(value)).toBe(true)
222226
}
223227
})
@@ -232,13 +236,14 @@ describe('reactivity/readonly', () => {
232236
const key1 = {}
233237
const key2 = {}
234238
const original = new Collection([key1, key2])
235-
const observed = readonly(original)
236-
expect(observed).not.toBe(original)
237-
expect(isReactive(observed)).toBe(true)
238-
expect(isReadonly(observed)).toBe(true)
239+
const wrapped = readonly(original)
240+
expect(wrapped).not.toBe(original)
241+
expect(isProxy(wrapped)).toBe(true)
242+
expect(isReactive(wrapped)).toBe(false)
243+
expect(isReadonly(wrapped)).toBe(true)
239244
expect(isReactive(original)).toBe(false)
240245
expect(isReadonly(original)).toBe(false)
241-
expect(observed.has(reactive(key1))).toBe(true)
246+
expect(wrapped.has(reactive(key1))).toBe(true)
242247
expect(original.has(reactive(key1))).toBe(false)
243248
})
244249

@@ -261,17 +266,17 @@ describe('reactivity/readonly', () => {
261266
if (Collection === Set) {
262267
test('should retrieve readonly values on iteration', () => {
263268
const original = new Collection([{}, {}])
264-
const observed: any = readonly(original)
265-
for (const value of observed) {
269+
const wrapped: any = readonly(original)
270+
for (const value of wrapped) {
266271
expect(isReadonly(value)).toBe(true)
267272
}
268-
observed.forEach((value: any) => {
273+
wrapped.forEach((value: any) => {
269274
expect(isReadonly(value)).toBe(true)
270275
})
271-
for (const value of observed.values()) {
276+
for (const value of wrapped.values()) {
272277
expect(isReadonly(value)).toBe(true)
273278
}
274-
for (const [v1, v2] of observed.entries()) {
279+
for (const [v1, v2] of wrapped.entries()) {
275280
expect(isReadonly(v1)).toBe(true)
276281
expect(isReadonly(v2)).toBe(true)
277282
}
@@ -299,6 +304,9 @@ describe('reactivity/readonly', () => {
299304
test('readonly should track and trigger if wrapping reactive original', () => {
300305
const a = reactive({ n: 1 })
301306
const b = readonly(a)
307+
// should return true since it's wrapping a reactive source
308+
expect(isReactive(b)).toBe(true)
309+
302310
let dummy
303311
effect(() => {
304312
dummy = b.n
@@ -309,26 +317,26 @@ describe('reactivity/readonly', () => {
309317
expect(dummy).toBe(2)
310318
})
311319

312-
test('observing already observed value should return same Proxy', () => {
320+
test('wrapping already wrapped value should return same Proxy', () => {
313321
const original = { foo: 1 }
314-
const observed = readonly(original)
315-
const observed2 = readonly(observed)
316-
expect(observed2).toBe(observed)
322+
const wrapped = readonly(original)
323+
const wrapped2 = readonly(wrapped)
324+
expect(wrapped2).toBe(wrapped)
317325
})
318326

319-
test('observing the same value multiple times should return same Proxy', () => {
327+
test('wrapping the same value multiple times should return same Proxy', () => {
320328
const original = { foo: 1 }
321-
const observed = readonly(original)
322-
const observed2 = readonly(original)
323-
expect(observed2).toBe(observed)
329+
const wrapped = readonly(original)
330+
const wrapped2 = readonly(original)
331+
expect(wrapped2).toBe(wrapped)
324332
})
325333

326-
test('markNonReactive', () => {
334+
test('markRaw', () => {
327335
const obj = readonly({
328336
foo: { a: 1 },
329-
bar: markNonReactive({ b: 2 })
337+
bar: markRaw({ b: 2 })
330338
})
331-
expect(isReactive(obj.foo)).toBe(true)
339+
expect(isReadonly(obj.foo)).toBe(true)
332340
expect(isReactive(obj.bar)).toBe(false)
333341
})
334342

0 commit comments

Comments
 (0)