mirror of
https://gitee.com/vuejs/vue.git
synced 2024-11-29 18:47:39 +08:00
feat(setup): support listeners on setup context + useListeners()
helper
These are added because Vue 2 does not include listeners in `context.attrs` so there is no way to access the equivalent of `this.$listeners` in `setup()`.
This commit is contained in:
parent
135d07442a
commit
adf3ac8adc
@ -18,7 +18,7 @@ import {
|
|||||||
invokeWithErrorHandling
|
invokeWithErrorHandling
|
||||||
} from '../util/index'
|
} from '../util/index'
|
||||||
import { currentInstance, setCurrentInstance } from 'v3/currentInstance'
|
import { currentInstance, setCurrentInstance } from 'v3/currentInstance'
|
||||||
import { syncSetupAttrs } from 'v3/apiSetup'
|
import { syncSetupProxy } from 'v3/apiSetup'
|
||||||
|
|
||||||
export let activeInstance: any = null
|
export let activeInstance: any = null
|
||||||
export let isUpdatingChildComponent: boolean = false
|
export let isUpdatingChildComponent: boolean = false
|
||||||
@ -288,11 +288,12 @@ export function updateChildComponent(
|
|||||||
// force update if attrs are accessed and has changed since it may be
|
// force update if attrs are accessed and has changed since it may be
|
||||||
// passed to a child component.
|
// passed to a child component.
|
||||||
if (
|
if (
|
||||||
syncSetupAttrs(
|
syncSetupProxy(
|
||||||
vm._attrsProxy,
|
vm._attrsProxy,
|
||||||
attrs,
|
attrs,
|
||||||
(prevVNode.data && prevVNode.data.attrs) || emptyObject,
|
(prevVNode.data && prevVNode.data.attrs) || emptyObject,
|
||||||
vm
|
vm,
|
||||||
|
'$attrs'
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
needsForceUpdate = true
|
needsForceUpdate = true
|
||||||
@ -300,7 +301,20 @@ export function updateChildComponent(
|
|||||||
}
|
}
|
||||||
vm.$attrs = attrs
|
vm.$attrs = attrs
|
||||||
|
|
||||||
vm.$listeners = listeners || emptyObject
|
// update listeners
|
||||||
|
listeners = listeners || emptyObject
|
||||||
|
const prevListeners = vm.$options._parentListeners
|
||||||
|
if (vm._listenersProxy) {
|
||||||
|
syncSetupProxy(
|
||||||
|
vm._listenersProxy,
|
||||||
|
listeners,
|
||||||
|
prevListeners || emptyObject,
|
||||||
|
vm,
|
||||||
|
'$listeners'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
vm.$listeners = vm.$options._parentListeners = listeners
|
||||||
|
updateComponentListeners(vm, listeners, prevListeners)
|
||||||
|
|
||||||
// update props
|
// update props
|
||||||
if (propsData && vm.$options.props) {
|
if (propsData && vm.$options.props) {
|
||||||
@ -317,12 +331,6 @@ export function updateChildComponent(
|
|||||||
vm.$options.propsData = propsData
|
vm.$options.propsData = propsData
|
||||||
}
|
}
|
||||||
|
|
||||||
// update listeners
|
|
||||||
listeners = listeners || emptyObject
|
|
||||||
const oldListeners = vm.$options._parentListeners
|
|
||||||
vm.$options._parentListeners = listeners
|
|
||||||
updateComponentListeners(vm, listeners, oldListeners)
|
|
||||||
|
|
||||||
// resolve slots + force update if has children
|
// resolve slots + force update if has children
|
||||||
if (needsForceUpdate) {
|
if (needsForceUpdate) {
|
||||||
vm.$slots = resolveSlots(renderChildren, parentVnode.context)
|
vm.$slots = resolveSlots(renderChildren, parentVnode.context)
|
||||||
|
@ -111,6 +111,7 @@ export declare class Component {
|
|||||||
_setupProxy?: Record<string, any>
|
_setupProxy?: Record<string, any>
|
||||||
_setupContext?: SetupContext
|
_setupContext?: SetupContext
|
||||||
_attrsProxy?: Record<string, any>
|
_attrsProxy?: Record<string, any>
|
||||||
|
_listenersProxy?: Record<string, Function | Function[]>
|
||||||
_slotsProxy?: Record<string, () => VNode[]>
|
_slotsProxy?: Record<string, () => VNode[]>
|
||||||
_preWatchers?: Watcher[]
|
_preWatchers?: Watcher[]
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ import { proxyWithRefUnwrap } from './reactivity/ref'
|
|||||||
*/
|
*/
|
||||||
export interface SetupContext {
|
export interface SetupContext {
|
||||||
attrs: Record<string, any>
|
attrs: Record<string, any>
|
||||||
|
listeners: Record<string, Function | Function[]>
|
||||||
slots: Record<string, () => VNode[]>
|
slots: Record<string, () => VNode[]>
|
||||||
emit: (event: string, ...args: any[]) => any
|
emit: (event: string, ...args: any[]) => any
|
||||||
expose: (exposed: Record<string, any>) => void
|
expose: (exposed: Record<string, any>) => void
|
||||||
@ -87,7 +88,19 @@ function createSetupContext(vm: Component): SetupContext {
|
|||||||
let exposeCalled = false
|
let exposeCalled = false
|
||||||
return {
|
return {
|
||||||
get attrs() {
|
get attrs() {
|
||||||
return initAttrsProxy(vm)
|
if (!vm._attrsProxy) {
|
||||||
|
const proxy = (vm._attrsProxy = {})
|
||||||
|
def(proxy, '_v_attr_proxy', true)
|
||||||
|
syncSetupProxy(proxy, vm.$attrs, emptyObject, vm, '$attrs')
|
||||||
|
}
|
||||||
|
return vm._attrsProxy
|
||||||
|
},
|
||||||
|
get listeners() {
|
||||||
|
if (!vm._listenersProxy) {
|
||||||
|
const proxy = (vm._listenersProxy = {})
|
||||||
|
syncSetupProxy(proxy, vm.$listeners, emptyObject, vm, '$listeners')
|
||||||
|
}
|
||||||
|
return vm._listenersProxy
|
||||||
},
|
},
|
||||||
get slots() {
|
get slots() {
|
||||||
return initSlotsProxy(vm)
|
return initSlotsProxy(vm)
|
||||||
@ -109,26 +122,18 @@ function createSetupContext(vm: Component): SetupContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function initAttrsProxy(vm: Component) {
|
export function syncSetupProxy(
|
||||||
if (!vm._attrsProxy) {
|
|
||||||
const proxy = (vm._attrsProxy = {})
|
|
||||||
def(proxy, '_v_attr_proxy', true)
|
|
||||||
syncSetupAttrs(proxy, vm.$attrs, emptyObject, vm)
|
|
||||||
}
|
|
||||||
return vm._attrsProxy
|
|
||||||
}
|
|
||||||
|
|
||||||
export function syncSetupAttrs(
|
|
||||||
to: any,
|
to: any,
|
||||||
from: any,
|
from: any,
|
||||||
prev: any,
|
prev: any,
|
||||||
instance: Component
|
instance: Component,
|
||||||
|
type: string
|
||||||
) {
|
) {
|
||||||
let changed = false
|
let changed = false
|
||||||
for (const key in from) {
|
for (const key in from) {
|
||||||
if (!(key in to)) {
|
if (!(key in to)) {
|
||||||
changed = true
|
changed = true
|
||||||
defineProxyAttr(to, key, instance)
|
defineProxyAttr(to, key, instance, type)
|
||||||
} else if (from[key] !== prev[key]) {
|
} else if (from[key] !== prev[key]) {
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
@ -142,12 +147,17 @@ export function syncSetupAttrs(
|
|||||||
return changed
|
return changed
|
||||||
}
|
}
|
||||||
|
|
||||||
function defineProxyAttr(proxy: any, key: string, instance: Component) {
|
function defineProxyAttr(
|
||||||
|
proxy: any,
|
||||||
|
key: string,
|
||||||
|
instance: Component,
|
||||||
|
type: string
|
||||||
|
) {
|
||||||
Object.defineProperty(proxy, key, {
|
Object.defineProperty(proxy, key, {
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
configurable: true,
|
configurable: true,
|
||||||
get() {
|
get() {
|
||||||
return instance.$attrs[key]
|
return instance[type][key]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -171,19 +181,23 @@ export function syncSetupSlots(to: any, from: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal use manual type def
|
* @internal use manual type def because it relies on legacy VNode types
|
||||||
*/
|
*/
|
||||||
export function useSlots(): SetupContext['slots'] {
|
export function useSlots(): SetupContext['slots'] {
|
||||||
return getContext().slots
|
return getContext().slots
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal use manual type def
|
|
||||||
*/
|
|
||||||
export function useAttrs(): SetupContext['attrs'] {
|
export function useAttrs(): SetupContext['attrs'] {
|
||||||
return getContext().attrs
|
return getContext().attrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vue 2 only
|
||||||
|
*/
|
||||||
|
export function useListeners(): SetupContext['listeners'] {
|
||||||
|
return getContext().listeners
|
||||||
|
}
|
||||||
|
|
||||||
function getContext(): SetupContext {
|
function getContext(): SetupContext {
|
||||||
if (__DEV__ && !currentInstance) {
|
if (__DEV__ && !currentInstance) {
|
||||||
warn(`useContext() called without active instance.`)
|
warn(`useContext() called without active instance.`)
|
||||||
|
@ -77,7 +77,7 @@ export { provide, inject, InjectionKey } from './apiInject'
|
|||||||
|
|
||||||
export { h } from './h'
|
export { h } from './h'
|
||||||
export { getCurrentInstance } from './currentInstance'
|
export { getCurrentInstance } from './currentInstance'
|
||||||
export { useSlots, useAttrs, mergeDefaults } from './apiSetup'
|
export { useSlots, useAttrs, useListeners, mergeDefaults } from './apiSetup'
|
||||||
export { nextTick } from 'core/util/next-tick'
|
export { nextTick } from 'core/util/next-tick'
|
||||||
export { set, del } from 'core/observer'
|
export { set, del } from 'core/observer'
|
||||||
|
|
||||||
|
@ -297,4 +297,27 @@ describe('api: setup context', () => {
|
|||||||
await nextTick()
|
await nextTick()
|
||||||
expect(spy).toHaveBeenCalledTimes(1)
|
expect(spy).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('context.listeners', async () => {
|
||||||
|
let _listeners
|
||||||
|
const Child = {
|
||||||
|
setup(_, { listeners }) {
|
||||||
|
_listeners = listeners
|
||||||
|
return () => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Parent = {
|
||||||
|
data: () => ({ log: () => 1 }),
|
||||||
|
template: `<Child @foo="log" />`,
|
||||||
|
components: { Child }
|
||||||
|
}
|
||||||
|
|
||||||
|
const vm = new Vue(Parent).$mount()
|
||||||
|
|
||||||
|
expect(_listeners.foo()).toBe(1)
|
||||||
|
vm.log = () => 2
|
||||||
|
await nextTick()
|
||||||
|
expect(_listeners.foo()).toBe(2)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
2
types/v3-manual-apis.d.ts
vendored
2
types/v3-manual-apis.d.ts
vendored
@ -5,6 +5,4 @@ export function getCurrentInstance(): { proxy: Vue } | null
|
|||||||
|
|
||||||
export const h: CreateElement
|
export const h: CreateElement
|
||||||
|
|
||||||
export function useAttrs(): SetupContext['attrs']
|
|
||||||
|
|
||||||
export function useSlots(): SetupContext['slots']
|
export function useSlots(): SetupContext['slots']
|
||||||
|
4
types/v3-setup-context.d.ts
vendored
4
types/v3-setup-context.d.ts
vendored
@ -31,6 +31,10 @@ export type EmitFn<
|
|||||||
|
|
||||||
export interface SetupContext<E extends EmitsOptions = {}> {
|
export interface SetupContext<E extends EmitsOptions = {}> {
|
||||||
attrs: Data
|
attrs: Data
|
||||||
|
/**
|
||||||
|
* Equivalent of `this.$listeners`, which is Vue 2 only.
|
||||||
|
*/
|
||||||
|
listeners: Record<string, Function | Function[]>
|
||||||
slots: Slots
|
slots: Slots
|
||||||
emit: EmitFn<E>
|
emit: EmitFn<E>
|
||||||
expose(exposed?: Record<string, any>): void
|
expose(exposed?: Record<string, any>): void
|
||||||
|
Loading…
Reference in New Issue
Block a user