Skip to content

Commit

Permalink
feat: implement effectScope api (#762)
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed Jul 16, 2021
1 parent 16e831b commit fcadec2
Show file tree
Hide file tree
Showing 6 changed files with 399 additions and 5 deletions.
5 changes: 3 additions & 2 deletions src/apis/computed.ts
@@ -1,11 +1,12 @@
import { getVueConstructor, getCurrentInstance } from '../runtimeContext'
import { getVueConstructor } from '../runtimeContext'
import { createRef, Ref } from '../reactivity'
import {
warn,
noopFn,
defineComponentInstance,
getVueInternalClasses,
} from '../utils'
import { getCurrentScopeVM } from './effectScope'

export interface ComputedRef<T = any> extends WritableComputedRef<T> {
readonly value: T
Expand All @@ -31,7 +32,7 @@ export function computed<T>(
export function computed<T>(
getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>
): ComputedRef<T> | WritableComputedRef<T> {
const vm = getCurrentInstance()?.proxy
const vm = getCurrentScopeVM()
let getter: ComputedGetter<T>
let setter: ComputedSetter<T> | undefined

Expand Down
105 changes: 105 additions & 0 deletions src/apis/effectScope.ts
@@ -0,0 +1,105 @@
import {
getCurrentInstance,
getVueConstructor,
withCurrentInstanceTrackingDisabled,
} from '../runtimeContext'
import { defineComponentInstance } from '../utils'
import { warn } from './warn'

let activeEffectScope: EffectScope | undefined
const effectScopeStack: EffectScope[] = []

export class EffectScope {
active = true
effects: EffectScope[] = []
cleanups: (() => void)[] = []

/**
* @internal
**/
vm: Vue

constructor(detached = false) {
let vm: Vue = undefined!
withCurrentInstanceTrackingDisabled(() => {
vm = defineComponentInstance(getVueConstructor())
})
this.vm = vm
if (!detached) {
recordEffectScope(this)
}
}

run<T>(fn: () => T): T | undefined {
if (this.active) {
try {
this.on()
return fn()
} finally {
this.off()
}
} else if (__DEV__) {
warn(`cannot run an inactive effect scope.`)
}
return
}

on() {
if (this.active) {
effectScopeStack.push(this)
activeEffectScope = this
}
}

off() {
if (this.active) {
effectScopeStack.pop()
activeEffectScope = effectScopeStack[effectScopeStack.length - 1]
}
}

stop() {
if (this.active) {
this.vm.$destroy()
this.effects.forEach((e) => e.stop())
this.cleanups.forEach((cleanup) => cleanup())
this.active = false
}
}
}

export function recordEffectScope(
effect: EffectScope,
scope?: EffectScope | null
) {
scope = scope || activeEffectScope
if (scope && scope.active) {
scope.effects.push(effect)
}
}

export function effectScope(detached?: boolean) {
return new EffectScope(detached)
}

export function getCurrentScope() {
return activeEffectScope
}

export function onScopeDispose(fn: () => void) {
if (activeEffectScope) {
activeEffectScope.cleanups.push(fn)
} else if (__DEV__) {
warn(
`onDispose() is called when there is no active effect scope ` +
` to be associated with.`
)
}
}

/**
* @internal
**/
export function getCurrentScopeVM() {
return getCurrentScope()?.vm || getCurrentInstance()?.proxy
}
19 changes: 18 additions & 1 deletion src/apis/index.ts
@@ -1,5 +1,16 @@
export * from '../reactivity'
export * from './lifecycle'
export {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
onErrorCaptured,
onActivated,
onDeactivated,
onServerPrefetch,
} from './lifecycle'
export * from './watch'
export * from './computed'
export * from './inject'
Expand All @@ -8,3 +19,9 @@ export { App, createApp } from './createApp'
export { nextTick } from './nextTick'
export { createElement as h } from './createElement'
export { warn } from './warn'
export {
effectScope,
EffectScope,
getCurrentScope,
onScopeDispose,
} from './effectScope'
5 changes: 3 additions & 2 deletions src/apis/watch.ts
Expand Up @@ -13,12 +13,13 @@ import {
isMap,
} from '../utils'
import { defineComponentInstance } from '../utils/helper'
import { getCurrentInstance, getVueConstructor } from '../runtimeContext'
import { getVueConstructor } from '../runtimeContext'
import {
WatcherPreFlushQueueKey,
WatcherPostFlushQueueKey,
} from '../utils/symbols'
import { ComputedRef } from './computed'
import { getCurrentScopeVM } from './effectScope'

export type WatchEffect = (onInvalidate: InvalidateCbRegistrator) => void

Expand Down Expand Up @@ -110,7 +111,7 @@ function getWatchEffectOption(options?: Partial<WatchOptions>): WatchOptions {
}

function getWatcherVM() {
let vm = getCurrentInstance()?.proxy
let vm = getCurrentScopeVM()
if (!vm) {
if (!fallbackVM) {
fallbackVM = defineComponentInstance(getVueConstructor())
Expand Down
16 changes: 16 additions & 0 deletions src/runtimeContext.ts
Expand Up @@ -21,6 +21,7 @@ try {

let vueConstructor: VueConstructor | null = null
let currentInstance: ComponentInstance | null = null
let currentInstanceTracking = true

const PluginInstalledFlag = '__composition_api_installed__'

Expand Down Expand Up @@ -71,7 +72,22 @@ export function setVueConstructor(Vue: VueConstructor) {
})
}

/**
* For `effectScope` to create instance without populate the current instance
* @internal
**/
export function withCurrentInstanceTrackingDisabled(fn: () => void) {
const prev = currentInstanceTracking
currentInstanceTracking = false
try {
fn()
} finally {
currentInstanceTracking = prev
}
}

export function setCurrentInstance(vm: ComponentInstance | null) {
if (!currentInstanceTracking) return
// currentInstance?.$scopedSlots
currentInstance = vm
}
Expand Down

0 comments on commit fcadec2

Please sign in to comment.