Merge remote-tracking branch 'github/master' into changing_unwrap_ref

This commit is contained in:
pikax
2020-04-15 15:54:26 +01:00
23 changed files with 361 additions and 318 deletions

View File

@@ -1,7 +1,6 @@
import { reactive, readonly, toRaw } from './reactive'
import { TrackOpTypes, TriggerOpTypes } from './operations'
import { track, trigger, ITERATE_KEY } from './effect'
import { LOCKED } from './lock'
import { isObject, hasOwn, isSymbol, hasChanged, isArray } from '@vue/shared'
import { isRef } from './ref'
@@ -12,7 +11,7 @@ const builtInSymbols = new Set(
)
const get = /*#__PURE__*/ createGetter()
const shallowReactiveGet = /*#__PURE__*/ createGetter(false, true)
const shallowGet = /*#__PURE__*/ createGetter(false, true)
const readonlyGet = /*#__PURE__*/ createGetter(true)
const shallowReadonlyGet = /*#__PURE__*/ createGetter(true, true)
@@ -36,23 +35,32 @@ const arrayInstrumentations: Record<string, Function> = {}
function createGetter(isReadonly = false, shallow = false) {
return function get(target: object, key: string | symbol, receiver: object) {
if (isArray(target) && hasOwn(arrayInstrumentations, key)) {
const targetIsArray = isArray(target)
if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}
const res = Reflect.get(target, key, receiver)
if (isSymbol(key) && builtInSymbols.has(key)) {
return res
}
if (shallow) {
track(target, TrackOpTypes.GET, key)
// TODO strict mode that returns a shallow-readonly version of the value
!isReadonly && track(target, TrackOpTypes.GET, key)
return res
}
// ref unwrapping, only for Objects, not for Arrays.
if (isRef(res) && !isArray(target)) {
return res.value
if (isRef(res)) {
if (targetIsArray) {
!isReadonly && track(target, TrackOpTypes.GET, key)
return res
} else {
// ref unwrapping, only for Objects, not for Arrays.
return res.value
}
}
track(target, TrackOpTypes.GET, key)
!isReadonly && track(target, TrackOpTypes.GET, key)
return isObject(res)
? isReadonly
? // need to lazy access readonly and reactive here to avoid
@@ -64,27 +72,15 @@ function createGetter(isReadonly = false, shallow = false) {
}
const set = /*#__PURE__*/ createSetter()
const shallowReactiveSet = /*#__PURE__*/ createSetter(false, true)
const readonlySet = /*#__PURE__*/ createSetter(true)
const shallowReadonlySet = /*#__PURE__*/ createSetter(true, true)
const shallowSet = /*#__PURE__*/ createSetter(true)
function createSetter(isReadonly = false, shallow = false) {
function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
if (isReadonly && LOCKED) {
if (__DEV__) {
console.warn(
`Set operation on key "${String(key)}" failed: target is readonly.`,
target
)
}
return true
}
const oldValue = (target as any)[key]
if (!shallow) {
value = toRaw(value)
@@ -141,30 +137,32 @@ export const mutableHandlers: ProxyHandler<object> = {
export const readonlyHandlers: ProxyHandler<object> = {
get: readonlyGet,
set: readonlySet,
has,
ownKeys,
deleteProperty(target: object, key: string | symbol): boolean {
if (LOCKED) {
if (__DEV__) {
console.warn(
`Delete operation on key "${String(
key
)}" failed: target is readonly.`,
target
)
}
return true
} else {
return deleteProperty(target, key)
set(target, key) {
if (__DEV__) {
console.warn(
`Set operation on key "${String(key)}" failed: target is readonly.`,
target
)
}
return true
},
deleteProperty(target, key) {
if (__DEV__) {
console.warn(
`Delete operation on key "${String(key)}" failed: target is readonly.`,
target
)
}
return true
}
}
export const shallowReactiveHandlers: ProxyHandler<object> = {
...mutableHandlers,
get: shallowReactiveGet,
set: shallowReactiveSet
get: shallowGet,
set: shallowSet
}
// Props handlers are special in the sense that it should not unwrap top-level
@@ -172,6 +170,5 @@ export const shallowReactiveHandlers: ProxyHandler<object> = {
// retain the reactivity of the normal readonly object.
export const shallowReadonlyHandlers: ProxyHandler<object> = {
...readonlyHandlers,
get: shallowReadonlyGet,
set: shallowReadonlySet
get: shallowReadonlyGet
}

View File

@@ -1,7 +1,6 @@
import { toRaw, reactive, readonly } from './reactive'
import { track, trigger, ITERATE_KEY, MAP_KEY_ITERATE_KEY } from './effect'
import { TrackOpTypes, TriggerOpTypes } from './operations'
import { LOCKED } from './lock'
import {
isObject,
capitalize,
@@ -142,7 +141,7 @@ function createForEach(isReadonly: boolean) {
const observed = this
const target = toRaw(observed)
const wrap = isReadonly ? toReadonly : toReactive
track(target, TrackOpTypes.ITERATE, ITERATE_KEY)
!isReadonly && track(target, TrackOpTypes.ITERATE, ITERATE_KEY)
// important: create sure the callback is
// 1. invoked with the reactive map as `this` and 3rd arg
// 2. the value received should be a corresponding reactive/readonly.
@@ -161,11 +160,12 @@ function createIterableMethod(method: string | symbol, isReadonly: boolean) {
const isKeyOnly = method === 'keys' && isMap
const innerIterator = getProto(target)[method].apply(target, args)
const wrap = isReadonly ? toReadonly : toReactive
track(
target,
TrackOpTypes.ITERATE,
isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY
)
!isReadonly &&
track(
target,
TrackOpTypes.ITERATE,
isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY
)
// return a wrapped iterator which returns observed versions of the
// values emitted from the real iterator
return {
@@ -187,23 +187,16 @@ function createIterableMethod(method: string | symbol, isReadonly: boolean) {
}
}
function createReadonlyMethod(
method: Function,
type: TriggerOpTypes
): Function {
function createReadonlyMethod(type: TriggerOpTypes): Function {
return function(this: CollectionTypes, ...args: unknown[]) {
if (LOCKED) {
if (__DEV__) {
const key = args[0] ? `on key "${args[0]}" ` : ``
console.warn(
`${capitalize(type)} operation ${key}failed: target is readonly.`,
toRaw(this)
)
}
return type === TriggerOpTypes.DELETE ? false : this
} else {
return method.apply(this, args)
if (__DEV__) {
const key = args[0] ? `on key "${args[0]}" ` : ``
console.warn(
`${capitalize(type)} operation ${key}failed: target is readonly.`,
toRaw(this)
)
}
return type === TriggerOpTypes.DELETE ? false : this
}
}
@@ -230,10 +223,10 @@ const readonlyInstrumentations: Record<string, Function> = {
return size((this as unknown) as IterableCollections)
},
has,
add: createReadonlyMethod(add, TriggerOpTypes.ADD),
set: createReadonlyMethod(set, TriggerOpTypes.SET),
delete: createReadonlyMethod(deleteEntry, TriggerOpTypes.DELETE),
clear: createReadonlyMethod(clear, TriggerOpTypes.CLEAR),
add: createReadonlyMethod(TriggerOpTypes.ADD),
set: createReadonlyMethod(TriggerOpTypes.SET),
delete: createReadonlyMethod(TriggerOpTypes.DELETE),
clear: createReadonlyMethod(TriggerOpTypes.CLEAR),
forEach: createForEach(true)
}

View File

@@ -12,6 +12,7 @@ const targetMap = new WeakMap<any, KeyToDepMap>()
export interface ReactiveEffect<T = any> {
(...args: any[]): T
_isEffect: true
id: number
active: boolean
raw: () => T
deps: Array<Dep>
@@ -21,7 +22,7 @@ export interface ReactiveEffect<T = any> {
export interface ReactiveEffectOptions {
lazy?: boolean
computed?: boolean
scheduler?: (job: () => void) => void
scheduler?: (job: ReactiveEffect) => void
onTrack?: (event: DebuggerEvent) => void
onTrigger?: (event: DebuggerEvent) => void
onStop?: () => void
@@ -74,6 +75,8 @@ export function stop(effect: ReactiveEffect) {
}
}
let uid = 0
function createReactiveEffect<T = any>(
fn: (...args: any[]) => T,
options: ReactiveEffectOptions
@@ -96,6 +99,7 @@ function createReactiveEffect<T = any>(
}
}
} as ReactiveEffect
effect.id = uid++
effect._isEffect = true
effect.active = true
effect.raw = fn

View File

@@ -1,4 +1,14 @@
export { ref, unref, shallowRef, isRef, toRefs, Ref, UnwrapRef } from './ref'
export {
ref,
unref,
shallowRef,
isRef,
toRef,
toRefs,
customRef,
Ref,
UnwrapRef
} from './ref'
export {
reactive,
isReactive,
@@ -7,7 +17,6 @@ export {
isReadonly,
shallowReadonly,
toRaw,
markReadonly,
markNonReactive
} from './reactive'
export {
@@ -31,5 +40,4 @@ export {
ReactiveEffectOptions,
DebuggerEvent
} from './effect'
export { lock, unlock } from './lock'
export { TrackOpTypes, TriggerOpTypes } from './operations'

View File

@@ -1,10 +0,0 @@
// global immutability lock
export let LOCKED = true
export function lock() {
LOCKED = true
}
export function unlock() {
LOCKED = false
}

View File

@@ -2,14 +2,14 @@ import { isObject, toRawType } from '@vue/shared'
import {
mutableHandlers,
readonlyHandlers,
shallowReadonlyHandlers,
shallowReactiveHandlers
shallowReactiveHandlers,
shallowReadonlyHandlers
} from './baseHandlers'
import {
mutableCollectionHandlers,
readonlyCollectionHandlers
} from './collectionHandlers'
import { UnwrapRef, Ref, isRef } from './ref'
import { UnwrapRef, Ref } from './ref'
import { makeMap } from '@vue/shared'
// WeakMaps that store {raw <-> observed} pairs.
@@ -20,7 +20,6 @@ const readonlyToRaw = new WeakMap<any, any>()
// WeakSets for values that are marked readonly or non-reactive during
// observable creation.
const readonlyValues = new WeakSet<any>()
const nonReactiveValues = new WeakSet<any>()
const collectionTypes = new Set<Function>([Set, Map, WeakMap, WeakSet])
@@ -47,13 +46,6 @@ export function reactive(target: object) {
if (readonlyToRaw.has(target)) {
return target
}
// target is explicitly marked as readonly by user
if (readonlyValues.has(target)) {
return readonly(target)
}
if (isRef(target)) {
return target
}
return createReactiveObject(
target,
rawToReactive,
@@ -63,14 +55,22 @@ export function reactive(target: object) {
)
}
// Return a reactive-copy of the original object, where only the root level
// properties are reactive, and does NOT unwrap refs nor recursively convert
// returned properties.
export function shallowReactive<T extends object>(target: T): T {
return createReactiveObject(
target,
rawToReactive,
reactiveToRaw,
shallowReactiveHandlers,
mutableCollectionHandlers
)
}
export function readonly<T extends object>(
target: T
): Readonly<UnwrapNestedRefs<T>> {
// value is a mutable observable, retrieve its original and return
// a readonly version.
if (reactiveToRaw.has(target)) {
target = reactiveToRaw.get(target)
}
return createReactiveObject(
target,
rawToReadonly,
@@ -96,19 +96,6 @@ export function shallowReadonly<T extends object>(
)
}
// Return a reactive-copy of the original object, where only the root level
// properties are reactive, and does NOT unwrap refs nor recursively convert
// returned properties.
export function shallowReactive<T extends object>(target: T): T {
return createReactiveObject(
target,
rawToReactive,
reactiveToRaw,
shallowReactiveHandlers,
mutableCollectionHandlers
)
}
function createReactiveObject(
target: unknown,
toProxy: WeakMap<any, any>,
@@ -153,12 +140,8 @@ export function isReadonly(value: unknown): boolean {
}
export function toRaw<T>(observed: T): T {
return reactiveToRaw.get(observed) || readonlyToRaw.get(observed) || observed
}
export function markReadonly<T>(value: T): T {
readonlyValues.add(value)
return value
observed = readonlyToRaw.get(observed) || observed
return reactiveToRaw.get(observed) || observed
}
export function markNonReactive<T extends object>(value: T): T {

View File

@@ -70,6 +70,31 @@ export function unref<T>(ref: T): T extends Ref<infer V> ? V : T {
return isRef(ref) ? (ref.value as any) : ref
}
export type CustomRefFactory<T> = (
track: () => void,
trigger: () => void
) => {
get: () => T
set: (value: T) => void
}
export function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {
const { get, set } = factory(
() => track(r, TrackOpTypes.GET, 'value'),
() => trigger(r, TriggerOpTypes.SET, 'value')
)
const r = {
_isRef: true,
get value() {
return get()
},
set value(v) {
set(v)
}
}
return r as any
}
export function toRefs<T extends object>(
object: T
): { [K in keyof T]: Ref<T[K]> } {
@@ -78,12 +103,12 @@ export function toRefs<T extends object>(
}
const ret: any = {}
for (const key in object) {
ret[key] = toProxyRef(object, key)
ret[key] = toRef(object, key)
}
return ret
}
function toProxyRef<T extends object, K extends keyof T>(
export function toRef<T extends object, K extends keyof T>(
object: T,
key: K
): Ref<T[K]> {