diff --git a/packages/runtime-core/src/directives.ts b/packages/runtime-core/src/directives.ts index b92e4b93ea5..c909e9a2edd 100644 --- a/packages/runtime-core/src/directives.ts +++ b/packages/runtime-core/src/directives.ts @@ -41,6 +41,7 @@ export type SSRDirectiveHook = ( ) => Data | undefined export interface ObjectDirective { + created?: DirectiveHook beforeMount?: DirectiveHook mounted?: DirectiveHook beforeUpdate?: DirectiveHook, V> diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 6497e88edcc..f35435a1c69 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -720,6 +720,9 @@ function baseCreateRenderer( ) } + if (dirs) { + invokeDirectiveHook(vnode, null, parentComponent, 'created') + } // props if (props) { for (const key in props) { @@ -741,10 +744,6 @@ function baseCreateRenderer( invokeVNodeHook(vnodeHook, parentComponent, vnode) } } - if (dirs) { - invokeDirectiveHook(vnode, null, parentComponent, 'beforeMount') - } - // scopeId if (scopeId) { hostSetScopeId(el, scopeId) @@ -756,6 +755,9 @@ function baseCreateRenderer( hostSetScopeId(el, treeOwnerId + '-s') } } + if (dirs) { + invokeDirectiveHook(vnode, null, parentComponent, 'beforeMount') + } // #1583 For inside suspense + suspense not resolved case, enter hook should call when suspense resolved // #1689 For inside suspense + suspense resolved case, just call it const needCallTransitionHooks = diff --git a/packages/runtime-dom/__tests__/directives/vModel.spec.ts b/packages/runtime-dom/__tests__/directives/vModel.spec.ts index 7fd6bc1b91a..c8b3531ffb9 100644 --- a/packages/runtime-dom/__tests__/directives/vModel.spec.ts +++ b/packages/runtime-dom/__tests__/directives/vModel.spec.ts @@ -29,6 +29,7 @@ beforeEach(() => { describe('vModel', () => { it('should work with text input', async () => { + const manualListener = jest.fn() const component = defineComponent({ data() { return { value: null } @@ -37,7 +38,10 @@ describe('vModel', () => { return [ withVModel( h('input', { - 'onUpdate:modelValue': setValue.bind(this) + 'onUpdate:modelValue': setValue.bind(this), + onInput: () => { + manualListener(data.value) + } }), this.value ) @@ -54,6 +58,8 @@ describe('vModel', () => { triggerEvent('input', input) await nextTick() expect(data.value).toEqual('foo') + // #1931 + expect(manualListener).toHaveBeenCalledWith('foo') data.value = 'bar' await nextTick() diff --git a/packages/runtime-dom/src/directives/vModel.ts b/packages/runtime-dom/src/directives/vModel.ts index 512f7686783..0516569f1a2 100644 --- a/packages/runtime-dom/src/directives/vModel.ts +++ b/packages/runtime-dom/src/directives/vModel.ts @@ -46,7 +46,7 @@ type ModelDirective = ObjectDirective export const vModelText: ModelDirective< HTMLInputElement | HTMLTextAreaElement > = { - beforeMount(el, { value, modifiers: { lazy, trim, number } }, vnode) { + created(el, { value, modifiers: { lazy, trim, number } }, vnode) { el.value = value == null ? '' : value el._assign = getModelAssigner(vnode) const castToNumber = number || el.type === 'number' @@ -90,7 +90,7 @@ export const vModelText: ModelDirective< } export const vModelCheckbox: ModelDirective = { - beforeMount(el, binding, vnode) { + created(el, binding, vnode) { setChecked(el, binding, vnode) el._assign = getModelAssigner(vnode) addEventListener(el, 'change', () => { @@ -135,7 +135,7 @@ function setChecked( } export const vModelRadio: ModelDirective = { - beforeMount(el, { value }, vnode) { + created(el, { value }, vnode) { el.checked = looseEqual(value, vnode.props!.value) el._assign = getModelAssigner(vnode) addEventListener(el, 'change', () => { @@ -151,16 +151,19 @@ export const vModelRadio: ModelDirective = { } export const vModelSelect: ModelDirective = { - // use mounted & updated because relies on its children + //