refactor: adjust reactivity structure

This commit is contained in:
Evan You 2019-06-12 00:03:50 +08:00
parent 471899af8b
commit bfe6987323
4 changed files with 164 additions and 168 deletions

View File

@ -1,5 +1,4 @@
import { value } from '../src/value'
import { effect, state } from '../src/index'
import { value, effect, state } from '../src/index'
describe('observer/value', () => {
it('should hold a value', () => {

View File

@ -1,5 +1,6 @@
import { OperationTypes } from './operations'
import { Dep, targetMap } from './state'
import { EMPTY_OBJ } from '@vue/shared'
export interface ReactiveEffect {
(): any
@ -38,7 +39,31 @@ export const activeReactiveEffectStack: ReactiveEffect[] = []
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,
options: ReactiveEffectOptions
): 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
if (deps.length) {
for (let i = 0; i < deps.length; i++) {

View File

@ -1,164 +1,20 @@
import { isObject, EMPTY_OBJ } from '@vue/shared'
import { mutableHandlers, immutableHandlers } from './baseHandlers'
import {
mutableCollectionHandlers,
immutableCollectionHandlers
} from './collectionHandlers'
import {
targetMap,
observedToRaw,
rawToObserved,
immutableToRaw,
rawToImmutable,
immutableValues,
nonReactiveValues
export { value, isValue, Value, UnwrapValue } from './value'
export {
state,
isState,
immutableState,
isImmutableState,
toRaw,
markImmutable,
markNonReactive
} from './state'
import {
createReactiveEffect,
cleanup,
export { computed, ComputedValue } from './computed'
export {
effect,
stop,
ReactiveEffect,
ReactiveEffectOptions,
DebuggerEvent
} 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 { value, isValue, Value, UnwrapValue } from './value'
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
}
export { OperationTypes } from './operations'

View File

@ -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'
// 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()
// WeakMaps that store {raw <-> observed} pairs.
export const rawToObserved: WeakMap<any, any> = new WeakMap()
export const observedToRaw: WeakMap<any, any> = new WeakMap()
export const rawToImmutable: WeakMap<any, any> = new WeakMap()
export const immutableToRaw: WeakMap<any, any> = new WeakMap()
const rawToObserved: WeakMap<any, any> = new WeakMap()
const observedToRaw: WeakMap<any, any> = new WeakMap()
const rawToImmutable: WeakMap<any, any> = new WeakMap()
const immutableToRaw: WeakMap<any, any> = new WeakMap()
// WeakSets for values that are marked immutable or non-reactive during
// observable creation.
export const immutableValues: WeakSet<any> = new WeakSet()
export const nonReactiveValues: WeakSet<any> = new WeakSet()
const immutableValues: 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
}