Skip to content

Commit

Permalink
fix(watch): align behavior with vue-next(doWatch). (#710)
Browse files Browse the repository at this point in the history
  • Loading branch information
ygj6 committed May 27, 2021
1 parent ea3ad5c commit fcf8bc3
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 4 deletions.
58 changes: 54 additions & 4 deletions src/apis/watch.ts
@@ -1,6 +1,17 @@
import { ComponentInstance } from '../component'
import { Ref, isRef, isReactive } from '../reactivity'
import { assert, logError, noopFn, warn, isFunction } from '../utils'
import {
assert,
logError,
noopFn,
warn,
isFunction,
isObject,
isArray,
isPlainObject,
isSet,
isMap,
} from '../utils'
import { defineComponentInstance } from '../utils/helper'
import { getCurrentInstance, getVueConstructor } from '../runtimeContext'
import {
Expand Down Expand Up @@ -272,13 +283,29 @@ function createWatcher(
let deep = options.deep

let getter: () => any
if (Array.isArray(source)) {
getter = () => source.map((s) => (isRef(s) ? s.value : s()))
} else if (isRef(source)) {
if (isRef(source)) {
getter = () => source.value
} else if (isReactive(source)) {
getter = () => source
deep = true
} else if (isArray(source)) {
getter = () =>
source.map((s) => {
if (isRef(s)) {
return s.value
} else if (isReactive(s)) {
return traverse(s)
} else if (isFunction(s)) {
return s()
} else {
warn(
`Invalid watch source: ${JSON.stringify(s)}.
A watch source can only be a getter/effect function, a ref, a reactive object, or an array of these types.`,
vm
)
return noopFn
}
})
} else if (isFunction(source)) {
getter = source as () => any
} else {
Expand Down Expand Up @@ -405,3 +432,26 @@ export function watch<T = any>(

return createWatcher(vm, source, callback, opts)
}

function traverse(value: unknown, seen: Set<unknown> = new Set()) {
if (!isObject(value) || seen.has(value)) {
return value
}
seen.add(value)
if (isRef(value)) {
traverse(value.value, seen)
} else if (isArray(value)) {
for (let i = 0; i < value.length; i++) {
traverse(value[i], seen)
}
} else if (isSet(value) || isMap(value)) {
value.forEach((v: any) => {
traverse(v, seen)
})
} else if (isPlainObject(value)) {
for (const key in value) {
traverse((value as any)[key], seen)
}
}
return value
}
11 changes: 11 additions & 0 deletions src/utils/utils.ts
Expand Up @@ -60,6 +60,17 @@ export function isArray<T>(x: unknown): x is T[] {
return Array.isArray(x)
}

export const objectToString = Object.prototype.toString

export const toTypeString = (value: unknown): string =>
objectToString.call(value)

export const isMap = (val: unknown): val is Map<any, any> =>
toTypeString(val) === '[object Map]'

export const isSet = (val: unknown): val is Set<any> =>
toTypeString(val) === '[object Set]'

export function isValidArrayIndex(val: any): boolean {
const n = parseFloat(String(val))
return n >= 0 && Math.floor(n) === n && isFinite(val)
Expand Down

0 comments on commit fcf8bc3

Please sign in to comment.