Skip to content

Commit 1495a46

Browse files
pikaxantfu
andauthored
feat(getCurrentInstance): Aligning with vue3 (#520)
* 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 <[email protected]>
1 parent 27ff2f3 commit 1495a46

File tree

14 files changed

+420
-32
lines changed

14 files changed

+420
-32
lines changed

src/apis/computed.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export function computed<T>(options: Option<T>): WritableComputedRef<T>
2626
export function computed<T>(
2727
options: Option<T>['get'] | Option<T>
2828
): ComputedRef<T> | WritableComputedRef<T> {
29-
const vm = getCurrentInstance()
29+
const vm = getCurrentInstance()?.proxy
3030
let get: Option<T>['get'], set: Option<T>['set'] | undefined
3131
if (typeof options === 'function') {
3232
get = options

src/apis/createElement.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ type CreateElement = Vue['$createElement']
88
let fallbackCreateElement: CreateElement
99

1010
export const createElement = (function createElement(...args: any) {
11-
const instance = getCurrentInstance()
11+
const instance = getCurrentInstance()?.proxy
1212
if (!instance) {
1313
warn('`createElement()` has been called outside of render function.')
1414
if (!fallbackCreateElement) {

src/apis/inject.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export function inject(
5252
return defaultValue
5353
}
5454

55-
const vm = getCurrentInstance()
55+
const vm = getCurrentInstance()?.proxy
5656
if (!vm) {
5757
warn(`inject() can only be used inside setup() or functional components.`)
5858
return

src/apis/lifecycle.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,12 @@ function injectHookOption(
3030

3131
function wrapHookCall(vm: ComponentInstance, fn: Function) {
3232
return (...args: any) => {
33-
let preVm = getCurrentInstance()
33+
let preVm = getCurrentInstance()?.proxy
3434
setCurrentInstance(vm)
3535
try {
3636
return fn(...args)
3737
} finally {
38-
setCurrentInstance(preVm)
38+
setCurrentInstance(preVm!)
3939
}
4040
}
4141
}

src/apis/warn.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ import { warn as vueWarn } from '../utils'
88
* @param message warning message to be displayed
99
*/
1010
export function warn(message: string) {
11-
vueWarn(message, getCurrentInstance())
11+
vueWarn(message, getCurrentInstance()?.proxy)
1212
}

src/apis/watch.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ function getWatchEffectOption(options?: Partial<WatchOptions>): WatchOptions {
101101
}
102102

103103
function getWatcherVM() {
104-
let vm = getCurrentInstance()
104+
let vm = getCurrentInstance()?.proxy
105105
if (!vm) {
106106
if (!fallbackVM) {
107107
fallbackVM = defineComponentInstance(getVueConstructor())

src/runtimeContext.ts

Lines changed: 164 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import type { VueConstructor } from 'vue'
2-
import { ComponentInstance } from './component'
3-
import { assert, hasOwn, warn } from './utils'
1+
import type { VueConstructor, VNode } from 'vue'
2+
import { ComponentInstance, Data } from './component'
3+
import { assert, hasOwn, warn, proxy, UnionToIntersection } from './utils'
44

55
let vueDependency: VueConstructor | undefined = undefined
66

@@ -71,10 +71,168 @@ export function setVueConstructor(Vue: VueConstructor) {
7171
})
7272
}
7373

74-
export function getCurrentInstance(): ComponentInstance | null {
74+
export function setCurrentInstance(vm: ComponentInstance | null) {
75+
// currentInstance?.$scopedSlots
76+
currentInstance = vm
77+
}
78+
79+
export type Slot = (...args: any[]) => VNode[]
80+
81+
export type InternalSlots = {
82+
[name: string]: Slot | undefined
83+
}
84+
85+
export type ObjectEmitsOptions = Record<
86+
string,
87+
((...args: any[]) => any) | null
88+
>
89+
export type EmitsOptions = ObjectEmitsOptions | string[]
90+
91+
export type EmitFn<
92+
Options = ObjectEmitsOptions,
93+
Event extends keyof Options = keyof Options
94+
> = Options extends Array<infer V>
95+
? (event: V, ...args: any[]) => void
96+
: {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function
97+
? (event: string, ...args: any[]) => void
98+
: UnionToIntersection<
99+
{
100+
[key in Event]: Options[key] extends (...args: infer Args) => any
101+
? (event: key, ...args: Args) => void
102+
: (event: key, ...args: any[]) => void
103+
}[Event]
104+
>
105+
106+
/**
107+
* We expose a subset of properties on the internal instance as they are
108+
* useful for advanced external libraries and tools.
109+
*/
110+
export declare interface ComponentInternalInstance {
111+
uid: number
112+
// type: ConcreteComponent
113+
parent: ComponentInternalInstance | null
114+
root: ComponentInternalInstance
115+
116+
//appContext: AppContext
117+
118+
/**
119+
* Vnode representing this component in its parent's vdom tree
120+
*/
121+
vnode: VNode
122+
/**
123+
* Root vnode of this component's own vdom tree
124+
*/
125+
// subTree: VNode // does not exist in Vue 2
126+
127+
/**
128+
* The reactive effect for rendering and patching the component. Callable.
129+
*/
130+
update: Function
131+
132+
data: Data
133+
props: Data
134+
attrs: Data
135+
refs: Data
136+
emit: EmitFn
137+
138+
slots: InternalSlots
139+
emitted: Record<string, boolean> | null
140+
141+
proxy: ComponentInstance
142+
143+
isMounted: boolean
144+
isUnmounted: boolean
145+
isDeactivated: boolean
146+
}
147+
148+
export function getCurrentVu2Instance() {
75149
return currentInstance
76150
}
77151

78-
export function setCurrentInstance(vm: ComponentInstance | null) {
79-
currentInstance = vm
152+
export function getCurrentInstance() {
153+
if (currentInstance) {
154+
return toVue3ComponentInstance(currentInstance)
155+
}
156+
return null
157+
}
158+
159+
const instanceMapCache = new WeakMap<
160+
ComponentInstance,
161+
ComponentInternalInstance
162+
>()
163+
164+
function toVue3ComponentInstance(
165+
vue2Instance: ComponentInstance
166+
): ComponentInternalInstance {
167+
if (instanceMapCache.has(vue2Instance)) {
168+
return instanceMapCache.get(vue2Instance)!
169+
}
170+
171+
const instance: ComponentInternalInstance = ({
172+
proxy: vue2Instance,
173+
update: vue2Instance.$forceUpdate,
174+
uid: vue2Instance._uid,
175+
176+
parent: null,
177+
root: null as any,
178+
} as unknown) as ComponentInternalInstance
179+
180+
// map vm.$props =
181+
const instanceProps = [
182+
'data',
183+
'props',
184+
'attrs',
185+
'refs',
186+
'emit',
187+
'vnode',
188+
'slots',
189+
] as const
190+
191+
instanceProps.forEach((prop) => {
192+
proxy(instance, prop, {
193+
get() {
194+
return (vue2Instance as any)[`$${prop}`]
195+
},
196+
})
197+
})
198+
199+
proxy(instance, 'isMounted', {
200+
get() {
201+
// @ts-expect-error private api
202+
return vue2Instance._isMounted
203+
},
204+
})
205+
206+
proxy(instance, 'isUnmounted', {
207+
get() {
208+
// @ts-expect-error private api
209+
return vue2Instance._isDestroyed
210+
},
211+
})
212+
213+
proxy(instance, 'isDeactivated', {
214+
get() {
215+
// @ts-expect-error private api
216+
return vue2Instance._inactive
217+
},
218+
})
219+
220+
proxy(instance, 'emitted', {
221+
get() {
222+
// @ts-expect-error private api
223+
return vue2Instance._events
224+
},
225+
})
226+
227+
instanceMapCache.set(vue2Instance, instance)
228+
229+
if (vue2Instance.$parent) {
230+
instance.parent = toVue3ComponentInstance(vue2Instance.$parent)
231+
}
232+
233+
if (vue2Instance.$root) {
234+
instance.root = toVue3ComponentInstance(vue2Instance.$root)
235+
}
236+
237+
return instance
80238
}

src/utils/helper.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { ComponentInstance } from '../component'
33
import { getCurrentInstance, getVueConstructor } from '../runtimeContext'
44
import { warn } from './utils'
55

6-
export function currentVMInFn(hook: string): ComponentInstance | null {
6+
export function currentVMInFn(hook: string): ComponentInstance | undefined {
77
const vm = getCurrentInstance()
88
if (__DEV__ && !vm) {
99
warn(
@@ -12,7 +12,7 @@ export function currentVMInFn(hook: string): ComponentInstance | null {
1212
`Lifecycle injection APIs can only be used during execution of setup().`
1313
)
1414
}
15-
return vm
15+
return vm?.proxy
1616
}
1717

1818
export function defineComponentInstance<V extends Vue = Vue>(

src/utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './utils'
22
export * from './helper'
3+
export * from './typeutils'

src/utils/instance.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ComponentInstance } from '../component'
22
import vmStateManager from './vmStateManager'
3-
import { setCurrentInstance, getCurrentInstance } from '../runtimeContext'
3+
import { setCurrentInstance, getCurrentVu2Instance } from '../runtimeContext'
44
import { Ref, isRef } from '../apis'
55
import { hasOwn, proxy, warn } from './utils'
66
import { createSlotProxy, resolveSlots } from './helper'
@@ -112,7 +112,7 @@ export function activateCurrentInstance(
112112
fn: (vm_: ComponentInstance) => any,
113113
onError?: (err: Error) => void
114114
) {
115-
let preVm = getCurrentInstance()
115+
let preVm = getCurrentVu2Instance()
116116
setCurrentInstance(vm)
117117
try {
118118
return fn(vm)

src/utils/typeutils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export type UnionToIntersection<U> = (
2+
U extends any ? (k: U) => void : never
3+
) extends (k: infer I) => void
4+
? I
5+
: never

test/setup.spec.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const {
1212
nextTick,
1313
isReactive,
1414
defineComponent,
15-
onMounted
15+
onMounted,
1616
} = require('../src')
1717
const { sleep } = require('./helpers/utils')
1818

@@ -998,8 +998,7 @@ describe('setup', () => {
998998
// await nextTick()
999999
// expect(vm.$el.textContent).toBe('1')
10001000
// })
1001-
1002-
1001+
10031002
// #448
10041003
it('should not cause infinite loop', async () => {
10051004
const A = defineComponent({

0 commit comments

Comments
 (0)