Skip to content

Commit 22f7d96

Browse files
authoredAug 2, 2024··
feat(watch): support passing number to deep option to control the watch depth (#9572)
1 parent 321d807 commit 22f7d96

File tree

2 files changed

+153
-15
lines changed

2 files changed

+153
-15
lines changed
 

‎packages/runtime-core/__tests__/apiWatch.spec.ts

+141
Original file line numberDiff line numberDiff line change
@@ -1532,6 +1532,147 @@ describe('api: watch', () => {
15321532
expect(spy2).toHaveBeenCalledTimes(1)
15331533
})
15341534

1535+
it('watching reactive depth', async () => {
1536+
const state = reactive({
1537+
a: {
1538+
b: {
1539+
c: {
1540+
d: {
1541+
e: 1,
1542+
},
1543+
},
1544+
},
1545+
},
1546+
})
1547+
1548+
const cb = vi.fn()
1549+
1550+
watch(state, cb, { deep: 2 })
1551+
1552+
state.a.b = { c: { d: { e: 2 } } }
1553+
await nextTick()
1554+
expect(cb).toHaveBeenCalledTimes(1)
1555+
1556+
state.a.b.c = { d: { e: 3 } }
1557+
1558+
await nextTick()
1559+
expect(cb).toHaveBeenCalledTimes(1)
1560+
1561+
state.a.b = { c: { d: { e: 4 } } }
1562+
1563+
await nextTick()
1564+
expect(cb).toHaveBeenCalledTimes(2)
1565+
})
1566+
1567+
it('watching ref depth', async () => {
1568+
const state = ref({
1569+
a: {
1570+
b: 2,
1571+
},
1572+
})
1573+
1574+
const cb = vi.fn()
1575+
1576+
watch(state, cb, { deep: 1 })
1577+
1578+
state.value.a.b = 3
1579+
await nextTick()
1580+
expect(cb).toHaveBeenCalledTimes(0)
1581+
1582+
state.value.a = { b: 3 }
1583+
await nextTick()
1584+
expect(cb).toHaveBeenCalledTimes(1)
1585+
})
1586+
1587+
it('watching array depth', async () => {
1588+
const arr = ref([
1589+
{
1590+
a: {
1591+
b: 2,
1592+
},
1593+
},
1594+
{
1595+
a: {
1596+
b: 3,
1597+
},
1598+
},
1599+
])
1600+
const cb = vi.fn()
1601+
watch(arr, cb, { deep: 2 })
1602+
1603+
arr.value[0].a.b = 3
1604+
await nextTick()
1605+
expect(cb).toHaveBeenCalledTimes(0)
1606+
1607+
arr.value[0].a = { b: 3 }
1608+
await nextTick()
1609+
expect(cb).toHaveBeenCalledTimes(1)
1610+
1611+
arr.value[1].a = { b: 4 }
1612+
await nextTick()
1613+
expect(cb).toHaveBeenCalledTimes(2)
1614+
1615+
arr.value.push({ a: { b: 5 } })
1616+
await nextTick()
1617+
expect(cb).toHaveBeenCalledTimes(3)
1618+
1619+
arr.value.pop()
1620+
await nextTick()
1621+
expect(cb).toHaveBeenCalledTimes(4)
1622+
})
1623+
1624+
it('shallowReactive', async () => {
1625+
const state = shallowReactive({
1626+
msg: ref('hello'),
1627+
foo: {
1628+
a: ref(1),
1629+
b: 2,
1630+
},
1631+
bar: 'bar',
1632+
})
1633+
1634+
const spy = vi.fn()
1635+
1636+
watch(state, spy)
1637+
1638+
state.msg.value = 'hi'
1639+
await nextTick()
1640+
expect(spy).not.toHaveBeenCalled()
1641+
1642+
state.bar = 'bar2'
1643+
await nextTick()
1644+
expect(spy).toHaveBeenCalledTimes(1)
1645+
1646+
state.foo.a.value++
1647+
state.foo.b++
1648+
await nextTick()
1649+
expect(spy).toHaveBeenCalledTimes(1)
1650+
1651+
state.bar = 'bar3'
1652+
await nextTick()
1653+
expect(spy).toHaveBeenCalledTimes(2)
1654+
})
1655+
it('watching reactive with deep: false', async () => {
1656+
const state = reactive({
1657+
foo: {
1658+
a: 2,
1659+
},
1660+
bar: 'bar',
1661+
})
1662+
1663+
const spy = vi.fn()
1664+
1665+
watch(state, spy, { deep: false })
1666+
1667+
state.foo.a++
1668+
await nextTick()
1669+
expect(spy).toHaveBeenCalledTimes(0)
1670+
1671+
state.bar = 'bar2'
1672+
await nextTick()
1673+
expect(spy).toHaveBeenCalledTimes(1)
1674+
})
1675+
15351676
test("effect should be removed from scope's effects after it is stopped", () => {
15361677
const num = ref(0)
15371678
let unwatch: () => void

‎packages/runtime-core/src/apiWatch.ts

+12-15
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export interface WatchOptionsBase extends DebuggerOptions {
7373

7474
export interface WatchOptions<Immediate = boolean> extends WatchOptionsBase {
7575
immediate?: Immediate
76-
deep?: boolean
76+
deep?: boolean | number
7777
once?: boolean
7878
}
7979

@@ -189,14 +189,6 @@ function doWatch(
189189
}
190190
}
191191

192-
// TODO remove in 3.5
193-
if (__DEV__ && deep !== void 0 && typeof deep === 'number') {
194-
warn(
195-
`watch() "deep" option with number value will be used as watch depth in future versions. ` +
196-
`Please use a boolean instead to avoid potential breakage.`,
197-
)
198-
}
199-
200192
if (__DEV__ && !cb) {
201193
if (immediate !== undefined) {
202194
warn(
@@ -228,11 +220,15 @@ function doWatch(
228220
}
229221

230222
const instance = currentInstance
231-
const reactiveGetter = (source: object) =>
232-
deep === true
233-
? source // traverse will happen in wrapped getter below
234-
: // for deep: false, only traverse root-level properties
235-
traverse(source, deep === false ? 1 : undefined)
223+
const reactiveGetter = (source: object) => {
224+
// traverse will happen in wrapped getter below
225+
if (deep) return source
226+
// for `deep: false | 0` or shallow reactive, only traverse root-level properties
227+
if (isShallow(source) || deep === false || deep === 0)
228+
return traverse(source, 1)
229+
// for `deep: undefined` on a reactive object, deeply traverse all properties
230+
return traverse(source)
231+
}
236232

237233
let getter: () => any
238234
let forceTrigger = false
@@ -300,7 +296,8 @@ function doWatch(
300296

301297
if (cb && deep) {
302298
const baseGetter = getter
303-
getter = () => traverse(baseGetter())
299+
const depth = deep === true ? Infinity : deep
300+
getter = () => traverse(baseGetter(), depth)
304301
}
305302

306303
let cleanup: (() => void) | undefined

0 commit comments

Comments
 (0)
Please sign in to comment.