mirror of https://github.com/vuejs/core.git
refactor: adjust reactivity structure
This commit is contained in:
parent
471899af8b
commit
bfe6987323
|
@ -1,5 +1,4 @@
|
||||||
import { value } from '../src/value'
|
import { value, effect, state } from '../src/index'
|
||||||
import { effect, state } from '../src/index'
|
|
||||||
|
|
||||||
describe('observer/value', () => {
|
describe('observer/value', () => {
|
||||||
it('should hold a value', () => {
|
it('should hold a value', () => {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { OperationTypes } from './operations'
|
import { OperationTypes } from './operations'
|
||||||
import { Dep, targetMap } from './state'
|
import { Dep, targetMap } from './state'
|
||||||
|
import { EMPTY_OBJ } from '@vue/shared'
|
||||||
|
|
||||||
export interface ReactiveEffect {
|
export interface ReactiveEffect {
|
||||||
(): any
|
(): any
|
||||||
|
@ -38,7 +39,31 @@ export const activeReactiveEffectStack: ReactiveEffect[] = []
|
||||||
|
|
||||||
export const ITERATE_KEY = Symbol('iterate')
|
export const ITERATE_KEY = Symbol('iterate')
|
||||||
|
|
||||||
export function createReactiveEffect(
|
export function effect(
|
||||||
|
fn: Function,
|
||||||
|
options: ReactiveEffectOptions = EMPTY_OBJ
|
||||||
|
): ReactiveEffect {
|
||||||
|
if ((fn as ReactiveEffect).isEffect) {
|
||||||
|
fn = (fn as ReactiveEffect).raw
|
||||||
|
}
|
||||||
|
const effect = createReactiveEffect(fn, options)
|
||||||
|
if (!options.lazy) {
|
||||||
|
effect()
|
||||||
|
}
|
||||||
|
return effect
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stop(effect: ReactiveEffect) {
|
||||||
|
if (effect.active) {
|
||||||
|
cleanup(effect)
|
||||||
|
if (effect.onStop) {
|
||||||
|
effect.onStop()
|
||||||
|
}
|
||||||
|
effect.active = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createReactiveEffect(
|
||||||
fn: Function,
|
fn: Function,
|
||||||
options: ReactiveEffectOptions
|
options: ReactiveEffectOptions
|
||||||
): ReactiveEffect {
|
): ReactiveEffect {
|
||||||
|
@ -72,7 +97,7 @@ function run(effect: ReactiveEffect, fn: Function, args: any[]): any {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function cleanup(effect: ReactiveEffect) {
|
function cleanup(effect: ReactiveEffect) {
|
||||||
const { deps } = effect
|
const { deps } = effect
|
||||||
if (deps.length) {
|
if (deps.length) {
|
||||||
for (let i = 0; i < deps.length; i++) {
|
for (let i = 0; i < deps.length; i++) {
|
||||||
|
|
|
@ -1,164 +1,20 @@
|
||||||
import { isObject, EMPTY_OBJ } from '@vue/shared'
|
export { value, isValue, Value, UnwrapValue } from './value'
|
||||||
import { mutableHandlers, immutableHandlers } from './baseHandlers'
|
export {
|
||||||
|
state,
|
||||||
import {
|
isState,
|
||||||
mutableCollectionHandlers,
|
immutableState,
|
||||||
immutableCollectionHandlers
|
isImmutableState,
|
||||||
} from './collectionHandlers'
|
toRaw,
|
||||||
|
markImmutable,
|
||||||
import {
|
markNonReactive
|
||||||
targetMap,
|
|
||||||
observedToRaw,
|
|
||||||
rawToObserved,
|
|
||||||
immutableToRaw,
|
|
||||||
rawToImmutable,
|
|
||||||
immutableValues,
|
|
||||||
nonReactiveValues
|
|
||||||
} from './state'
|
} from './state'
|
||||||
|
export { computed, ComputedValue } from './computed'
|
||||||
import {
|
export {
|
||||||
createReactiveEffect,
|
effect,
|
||||||
cleanup,
|
stop,
|
||||||
ReactiveEffect,
|
ReactiveEffect,
|
||||||
ReactiveEffectOptions,
|
ReactiveEffectOptions,
|
||||||
DebuggerEvent
|
DebuggerEvent
|
||||||
} from './effect'
|
} from './effect'
|
||||||
|
|
||||||
import { UnwrapValue } from './value'
|
|
||||||
|
|
||||||
export { ReactiveEffect, ReactiveEffectOptions, DebuggerEvent }
|
|
||||||
export { OperationTypes } from './operations'
|
|
||||||
export { computed, ComputedValue } from './computed'
|
|
||||||
export { lock, unlock } from './lock'
|
export { lock, unlock } from './lock'
|
||||||
export { value, isValue, Value, UnwrapValue } from './value'
|
export { OperationTypes } from './operations'
|
||||||
|
|
||||||
const collectionTypes: Set<any> = new Set([Set, Map, WeakMap, WeakSet])
|
|
||||||
const observableValueRE = /^\[object (?:Object|Array|Map|Set|WeakMap|WeakSet)\]$/
|
|
||||||
|
|
||||||
const canObserve = (value: any): boolean => {
|
|
||||||
return (
|
|
||||||
!value._isVue &&
|
|
||||||
!value._isVNode &&
|
|
||||||
observableValueRE.test(Object.prototype.toString.call(value)) &&
|
|
||||||
!nonReactiveValues.has(value)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ObservableFactory = <T>(target?: T) => UnwrapValue<T>
|
|
||||||
|
|
||||||
export const state = ((target: any = {}): any => {
|
|
||||||
// if trying to observe an immutable proxy, return the immutable version.
|
|
||||||
if (immutableToRaw.has(target)) {
|
|
||||||
return target
|
|
||||||
}
|
|
||||||
// target is explicitly marked as immutable by user
|
|
||||||
if (immutableValues.has(target)) {
|
|
||||||
return immutableState(target)
|
|
||||||
}
|
|
||||||
return createObservable(
|
|
||||||
target,
|
|
||||||
rawToObserved,
|
|
||||||
observedToRaw,
|
|
||||||
mutableHandlers,
|
|
||||||
mutableCollectionHandlers
|
|
||||||
)
|
|
||||||
}) as ObservableFactory
|
|
||||||
|
|
||||||
export const immutableState = ((target: any = {}): any => {
|
|
||||||
// value is a mutable observable, retrive its original and return
|
|
||||||
// a readonly version.
|
|
||||||
if (observedToRaw.has(target)) {
|
|
||||||
target = observedToRaw.get(target)
|
|
||||||
}
|
|
||||||
return createObservable(
|
|
||||||
target,
|
|
||||||
rawToImmutable,
|
|
||||||
immutableToRaw,
|
|
||||||
immutableHandlers,
|
|
||||||
immutableCollectionHandlers
|
|
||||||
)
|
|
||||||
}) as ObservableFactory
|
|
||||||
|
|
||||||
function createObservable(
|
|
||||||
target: any,
|
|
||||||
toProxy: WeakMap<any, any>,
|
|
||||||
toRaw: WeakMap<any, any>,
|
|
||||||
baseHandlers: ProxyHandler<any>,
|
|
||||||
collectionHandlers: ProxyHandler<any>
|
|
||||||
) {
|
|
||||||
if (!isObject(target)) {
|
|
||||||
if (__DEV__) {
|
|
||||||
console.warn(`value is not observable: ${String(target)}`)
|
|
||||||
}
|
|
||||||
return target
|
|
||||||
}
|
|
||||||
// target already has corresponding Proxy
|
|
||||||
let observed = toProxy.get(target)
|
|
||||||
if (observed !== void 0) {
|
|
||||||
return observed
|
|
||||||
}
|
|
||||||
// target is already a Proxy
|
|
||||||
if (toRaw.has(target)) {
|
|
||||||
return target
|
|
||||||
}
|
|
||||||
// only a whitelist of value types can be observed.
|
|
||||||
if (!canObserve(target)) {
|
|
||||||
return target
|
|
||||||
}
|
|
||||||
const handlers = collectionTypes.has(target.constructor)
|
|
||||||
? collectionHandlers
|
|
||||||
: baseHandlers
|
|
||||||
observed = new Proxy(target, handlers)
|
|
||||||
toProxy.set(target, observed)
|
|
||||||
toRaw.set(observed, target)
|
|
||||||
if (!targetMap.has(target)) {
|
|
||||||
targetMap.set(target, new Map())
|
|
||||||
}
|
|
||||||
return observed
|
|
||||||
}
|
|
||||||
|
|
||||||
export function effect(
|
|
||||||
fn: Function,
|
|
||||||
options: ReactiveEffectOptions = EMPTY_OBJ
|
|
||||||
): ReactiveEffect {
|
|
||||||
if ((fn as ReactiveEffect).isEffect) {
|
|
||||||
fn = (fn as ReactiveEffect).raw
|
|
||||||
}
|
|
||||||
const effect = createReactiveEffect(fn, options)
|
|
||||||
if (!options.lazy) {
|
|
||||||
effect()
|
|
||||||
}
|
|
||||||
return effect
|
|
||||||
}
|
|
||||||
|
|
||||||
export function stop(effect: ReactiveEffect) {
|
|
||||||
if (effect.active) {
|
|
||||||
cleanup(effect)
|
|
||||||
if (effect.onStop) {
|
|
||||||
effect.onStop()
|
|
||||||
}
|
|
||||||
effect.active = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isState(value: any): boolean {
|
|
||||||
return observedToRaw.has(value) || immutableToRaw.has(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isImmutableState(value: any): boolean {
|
|
||||||
return immutableToRaw.has(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function toRaw<T>(observed: T): T {
|
|
||||||
return observedToRaw.get(observed) || immutableToRaw.get(observed) || observed
|
|
||||||
}
|
|
||||||
|
|
||||||
export function markImmutable<T>(value: T): T {
|
|
||||||
immutableValues.add(value)
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
export function markNonReactive<T>(value: T): T {
|
|
||||||
nonReactiveValues.add(value)
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,3 +1,12 @@
|
||||||
|
import { isObject } from '@vue/shared'
|
||||||
|
import { mutableHandlers, immutableHandlers } from './baseHandlers'
|
||||||
|
|
||||||
|
import {
|
||||||
|
mutableCollectionHandlers,
|
||||||
|
immutableCollectionHandlers
|
||||||
|
} from './collectionHandlers'
|
||||||
|
|
||||||
|
import { UnwrapValue } from './value'
|
||||||
import { ReactiveEffect } from './effect'
|
import { ReactiveEffect } from './effect'
|
||||||
|
|
||||||
// The main WeakMap that stores {target -> key -> dep} connections.
|
// The main WeakMap that stores {target -> key -> dep} connections.
|
||||||
|
@ -9,12 +18,119 @@ export type KeyToDepMap = Map<string | symbol, Dep>
|
||||||
export const targetMap: WeakMap<any, KeyToDepMap> = new WeakMap()
|
export const targetMap: WeakMap<any, KeyToDepMap> = new WeakMap()
|
||||||
|
|
||||||
// WeakMaps that store {raw <-> observed} pairs.
|
// WeakMaps that store {raw <-> observed} pairs.
|
||||||
export const rawToObserved: WeakMap<any, any> = new WeakMap()
|
const rawToObserved: WeakMap<any, any> = new WeakMap()
|
||||||
export const observedToRaw: WeakMap<any, any> = new WeakMap()
|
const observedToRaw: WeakMap<any, any> = new WeakMap()
|
||||||
export const rawToImmutable: WeakMap<any, any> = new WeakMap()
|
const rawToImmutable: WeakMap<any, any> = new WeakMap()
|
||||||
export const immutableToRaw: WeakMap<any, any> = new WeakMap()
|
const immutableToRaw: WeakMap<any, any> = new WeakMap()
|
||||||
|
|
||||||
// WeakSets for values that are marked immutable or non-reactive during
|
// WeakSets for values that are marked immutable or non-reactive during
|
||||||
// observable creation.
|
// observable creation.
|
||||||
export const immutableValues: WeakSet<any> = new WeakSet()
|
const immutableValues: WeakSet<any> = new WeakSet()
|
||||||
export const nonReactiveValues: WeakSet<any> = new WeakSet()
|
const nonReactiveValues: WeakSet<any> = new WeakSet()
|
||||||
|
|
||||||
|
const collectionTypes: Set<any> = new Set([Set, Map, WeakMap, WeakSet])
|
||||||
|
const observableValueRE = /^\[object (?:Object|Array|Map|Set|WeakMap|WeakSet)\]$/
|
||||||
|
|
||||||
|
const canObserve = (value: any): boolean => {
|
||||||
|
return (
|
||||||
|
!value._isVue &&
|
||||||
|
!value._isVNode &&
|
||||||
|
observableValueRE.test(Object.prototype.toString.call(value)) &&
|
||||||
|
!nonReactiveValues.has(value)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ObservableFactory = <T>(target?: T) => UnwrapValue<T>
|
||||||
|
|
||||||
|
export const state = ((target: any = {}): any => {
|
||||||
|
// if trying to observe an immutable proxy, return the immutable version.
|
||||||
|
if (immutableToRaw.has(target)) {
|
||||||
|
return target
|
||||||
|
}
|
||||||
|
// target is explicitly marked as immutable by user
|
||||||
|
if (immutableValues.has(target)) {
|
||||||
|
return immutableState(target)
|
||||||
|
}
|
||||||
|
return createObservable(
|
||||||
|
target,
|
||||||
|
rawToObserved,
|
||||||
|
observedToRaw,
|
||||||
|
mutableHandlers,
|
||||||
|
mutableCollectionHandlers
|
||||||
|
)
|
||||||
|
}) as ObservableFactory
|
||||||
|
|
||||||
|
export const immutableState = ((target: any = {}): any => {
|
||||||
|
// value is a mutable observable, retrive its original and return
|
||||||
|
// a readonly version.
|
||||||
|
if (observedToRaw.has(target)) {
|
||||||
|
target = observedToRaw.get(target)
|
||||||
|
}
|
||||||
|
return createObservable(
|
||||||
|
target,
|
||||||
|
rawToImmutable,
|
||||||
|
immutableToRaw,
|
||||||
|
immutableHandlers,
|
||||||
|
immutableCollectionHandlers
|
||||||
|
)
|
||||||
|
}) as ObservableFactory
|
||||||
|
|
||||||
|
function createObservable(
|
||||||
|
target: any,
|
||||||
|
toProxy: WeakMap<any, any>,
|
||||||
|
toRaw: WeakMap<any, any>,
|
||||||
|
baseHandlers: ProxyHandler<any>,
|
||||||
|
collectionHandlers: ProxyHandler<any>
|
||||||
|
) {
|
||||||
|
if (!isObject(target)) {
|
||||||
|
if (__DEV__) {
|
||||||
|
console.warn(`value is not observable: ${String(target)}`)
|
||||||
|
}
|
||||||
|
return target
|
||||||
|
}
|
||||||
|
// target already has corresponding Proxy
|
||||||
|
let observed = toProxy.get(target)
|
||||||
|
if (observed !== void 0) {
|
||||||
|
return observed
|
||||||
|
}
|
||||||
|
// target is already a Proxy
|
||||||
|
if (toRaw.has(target)) {
|
||||||
|
return target
|
||||||
|
}
|
||||||
|
// only a whitelist of value types can be observed.
|
||||||
|
if (!canObserve(target)) {
|
||||||
|
return target
|
||||||
|
}
|
||||||
|
const handlers = collectionTypes.has(target.constructor)
|
||||||
|
? collectionHandlers
|
||||||
|
: baseHandlers
|
||||||
|
observed = new Proxy(target, handlers)
|
||||||
|
toProxy.set(target, observed)
|
||||||
|
toRaw.set(observed, target)
|
||||||
|
if (!targetMap.has(target)) {
|
||||||
|
targetMap.set(target, new Map())
|
||||||
|
}
|
||||||
|
return observed
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isState(value: any): boolean {
|
||||||
|
return observedToRaw.has(value) || immutableToRaw.has(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isImmutableState(value: any): boolean {
|
||||||
|
return immutableToRaw.has(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toRaw<T>(observed: T): T {
|
||||||
|
return observedToRaw.get(observed) || immutableToRaw.get(observed) || observed
|
||||||
|
}
|
||||||
|
|
||||||
|
export function markImmutable<T>(value: T): T {
|
||||||
|
immutableValues.add(value)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
export function markNonReactive<T>(value: T): T {
|
||||||
|
nonReactiveValues.add(value)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue