Skip to content

Commit

Permalink
feat(getCurrentInstance): Aligning with vue3 (#520)
Browse files Browse the repository at this point in the history
* feat(getCurrentInstance): Aligning with vue3
BREAKING CHANGE: The internal vm can be accessed with `getCurrentInstance().proxy`

```js
const vm = getCurrentInstance()

// becomes

const vm = getCurrentInstance().proxy
```

* chore: improve

* changes

* update tests

* chore: add tests

Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>
  • Loading branch information
pikax and antfu committed Dec 19, 2020
1 parent 27ff2f3 commit 1495a46
Show file tree
Hide file tree
Showing 14 changed files with 420 additions and 32 deletions.
2 changes: 1 addition & 1 deletion src/apis/computed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function computed<T>(options: Option<T>): WritableComputedRef<T>
export function computed<T>(
options: Option<T>['get'] | Option<T>
): ComputedRef<T> | WritableComputedRef<T> {
const vm = getCurrentInstance()
const vm = getCurrentInstance()?.proxy
let get: Option<T>['get'], set: Option<T>['set'] | undefined
if (typeof options === 'function') {
get = options
Expand Down
2 changes: 1 addition & 1 deletion src/apis/createElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ type CreateElement = Vue['$createElement']
let fallbackCreateElement: CreateElement

export const createElement = (function createElement(...args: any) {
const instance = getCurrentInstance()
const instance = getCurrentInstance()?.proxy
if (!instance) {
warn('`createElement()` has been called outside of render function.')
if (!fallbackCreateElement) {
Expand Down
2 changes: 1 addition & 1 deletion src/apis/inject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export function inject(
return defaultValue
}

const vm = getCurrentInstance()
const vm = getCurrentInstance()?.proxy
if (!vm) {
warn(`inject() can only be used inside setup() or functional components.`)
return
Expand Down
4 changes: 2 additions & 2 deletions src/apis/lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ function injectHookOption(

function wrapHookCall(vm: ComponentInstance, fn: Function) {
return (...args: any) => {
let preVm = getCurrentInstance()
let preVm = getCurrentInstance()?.proxy
setCurrentInstance(vm)
try {
return fn(...args)
} finally {
setCurrentInstance(preVm)
setCurrentInstance(preVm!)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/apis/warn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ import { warn as vueWarn } from '../utils'
* @param message warning message to be displayed
*/
export function warn(message: string) {
vueWarn(message, getCurrentInstance())
vueWarn(message, getCurrentInstance()?.proxy)
}
2 changes: 1 addition & 1 deletion src/apis/watch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ function getWatchEffectOption(options?: Partial<WatchOptions>): WatchOptions {
}

function getWatcherVM() {
let vm = getCurrentInstance()
let vm = getCurrentInstance()?.proxy
if (!vm) {
if (!fallbackVM) {
fallbackVM = defineComponentInstance(getVueConstructor())
Expand Down
170 changes: 164 additions & 6 deletions src/runtimeContext.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { VueConstructor } from 'vue'
import { ComponentInstance } from './component'
import { assert, hasOwn, warn } from './utils'
import type { VueConstructor, VNode } from 'vue'
import { ComponentInstance, Data } from './component'
import { assert, hasOwn, warn, proxy, UnionToIntersection } from './utils'

let vueDependency: VueConstructor | undefined = undefined

Expand Down Expand Up @@ -71,10 +71,168 @@ export function setVueConstructor(Vue: VueConstructor) {
})
}

export function getCurrentInstance(): ComponentInstance | null {
export function setCurrentInstance(vm: ComponentInstance | null) {
// currentInstance?.$scopedSlots
currentInstance = vm
}

export type Slot = (...args: any[]) => VNode[]

export type InternalSlots = {
[name: string]: Slot | undefined
}

export type ObjectEmitsOptions = Record<
string,
((...args: any[]) => any) | null
>
export type EmitsOptions = ObjectEmitsOptions | string[]

export type EmitFn<
Options = ObjectEmitsOptions,
Event extends keyof Options = keyof Options
> = Options extends Array<infer V>
? (event: V, ...args: any[]) => void
: {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function
? (event: string, ...args: any[]) => void
: UnionToIntersection<
{
[key in Event]: Options[key] extends (...args: infer Args) => any
? (event: key, ...args: Args) => void
: (event: key, ...args: any[]) => void
}[Event]
>

/**
* We expose a subset of properties on the internal instance as they are
* useful for advanced external libraries and tools.
*/
export declare interface ComponentInternalInstance {
uid: number
// type: ConcreteComponent
parent: ComponentInternalInstance | null
root: ComponentInternalInstance

//appContext: AppContext

/**
* Vnode representing this component in its parent's vdom tree
*/
vnode: VNode
/**
* Root vnode of this component's own vdom tree
*/
// subTree: VNode // does not exist in Vue 2

/**
* The reactive effect for rendering and patching the component. Callable.
*/
update: Function

data: Data
props: Data
attrs: Data
refs: Data
emit: EmitFn

slots: InternalSlots
emitted: Record<string, boolean> | null

proxy: ComponentInstance

isMounted: boolean
isUnmounted: boolean
isDeactivated: boolean
}

export function getCurrentVu2Instance() {
return currentInstance
}

export function setCurrentInstance(vm: ComponentInstance | null) {
currentInstance = vm
export function getCurrentInstance() {
if (currentInstance) {
return toVue3ComponentInstance(currentInstance)
}
return null
}

const instanceMapCache = new WeakMap<
ComponentInstance,
ComponentInternalInstance
>()

function toVue3ComponentInstance(
vue2Instance: ComponentInstance
): ComponentInternalInstance {
if (instanceMapCache.has(vue2Instance)) {
return instanceMapCache.get(vue2Instance)!
}

const instance: ComponentInternalInstance = ({
proxy: vue2Instance,
update: vue2Instance.$forceUpdate,
uid: vue2Instance._uid,

parent: null,
root: null as any,
} as unknown) as ComponentInternalInstance

// map vm.$props =
const instanceProps = [
'data',
'props',
'attrs',
'refs',
'emit',
'vnode',
'slots',
] as const

instanceProps.forEach((prop) => {
proxy(instance, prop, {
get() {
return (vue2Instance as any)[`$${prop}`]
},
})
})

proxy(instance, 'isMounted', {
get() {
// @ts-expect-error private api
return vue2Instance._isMounted
},
})

proxy(instance, 'isUnmounted', {
get() {
// @ts-expect-error private api
return vue2Instance._isDestroyed
},
})

proxy(instance, 'isDeactivated', {
get() {
// @ts-expect-error private api
return vue2Instance._inactive
},
})

proxy(instance, 'emitted', {
get() {
// @ts-expect-error private api
return vue2Instance._events
},
})

instanceMapCache.set(vue2Instance, instance)

if (vue2Instance.$parent) {
instance.parent = toVue3ComponentInstance(vue2Instance.$parent)
}

if (vue2Instance.$root) {
instance.root = toVue3ComponentInstance(vue2Instance.$root)
}

return instance
}
4 changes: 2 additions & 2 deletions src/utils/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ComponentInstance } from '../component'
import { getCurrentInstance, getVueConstructor } from '../runtimeContext'
import { warn } from './utils'

export function currentVMInFn(hook: string): ComponentInstance | null {
export function currentVMInFn(hook: string): ComponentInstance | undefined {
const vm = getCurrentInstance()
if (__DEV__ && !vm) {
warn(
Expand All @@ -12,7 +12,7 @@ export function currentVMInFn(hook: string): ComponentInstance | null {
`Lifecycle injection APIs can only be used during execution of setup().`
)
}
return vm
return vm?.proxy
}

export function defineComponentInstance<V extends Vue = Vue>(
Expand Down
1 change: 1 addition & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './utils'
export * from './helper'
export * from './typeutils'
4 changes: 2 additions & 2 deletions src/utils/instance.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ComponentInstance } from '../component'
import vmStateManager from './vmStateManager'
import { setCurrentInstance, getCurrentInstance } from '../runtimeContext'
import { setCurrentInstance, getCurrentVu2Instance } from '../runtimeContext'
import { Ref, isRef } from '../apis'
import { hasOwn, proxy, warn } from './utils'
import { createSlotProxy, resolveSlots } from './helper'
Expand Down Expand Up @@ -112,7 +112,7 @@ export function activateCurrentInstance(
fn: (vm_: ComponentInstance) => any,
onError?: (err: Error) => void
) {
let preVm = getCurrentInstance()
let preVm = getCurrentVu2Instance()
setCurrentInstance(vm)
try {
return fn(vm)
Expand Down
5 changes: 5 additions & 0 deletions src/utils/typeutils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export type UnionToIntersection<U> = (
U extends any ? (k: U) => void : never
) extends (k: infer I) => void
? I
: never
5 changes: 2 additions & 3 deletions test/setup.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const {
nextTick,
isReactive,
defineComponent,
onMounted
onMounted,
} = require('../src')
const { sleep } = require('./helpers/utils')

Expand Down Expand Up @@ -998,8 +998,7 @@ describe('setup', () => {
// await nextTick()
// expect(vm.$el.textContent).toBe('1')
// })



// #448
it('should not cause infinite loop', async () => {
const A = defineComponent({
Expand Down
Loading

0 comments on commit 1495a46

Please sign in to comment.