]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(directives): introduce `created` custom directive hook and ensure
authorEvan You <yyx990803@gmail.com>
Mon, 24 Aug 2020 21:12:16 +0000 (17:12 -0400)
committerEvan You <yyx990803@gmail.com>
Mon, 24 Aug 2020 21:12:16 +0000 (17:12 -0400)
`v-model` event listener fire before template/props listeners

fix #1931

packages/runtime-core/src/directives.ts
packages/runtime-core/src/renderer.ts
packages/runtime-dom/__tests__/directives/vModel.spec.ts
packages/runtime-dom/src/directives/vModel.ts

index b92e4b93ea575d09c671db06139e1eafc0eee9f6..c909e9a2eddffeb0d21467d63a6e6b62581c054d 100644 (file)
@@ -41,6 +41,7 @@ export type SSRDirectiveHook = (
 ) => Data | undefined
 
 export interface ObjectDirective<T = any, V = any> {
+  created?: DirectiveHook<T, null, V>
   beforeMount?: DirectiveHook<T, null, V>
   mounted?: DirectiveHook<T, null, V>
   beforeUpdate?: DirectiveHook<T, VNode<any, T>, V>
index 6497e88edcce259f081c89a7e0df2f2b36d965cf..f35435a1c6912ee46d2d10d67f8ef93b76046b8f 100644 (file)
@@ -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 =
index 7fd6bc1b91aceace9dfb597d52d839319378b5e0..c8b3531ffb92ee3e6b147704fb4f9b2d93af191e 100644 (file)
@@ -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()
index 512f76867836e619b8a6ba7519bb640295b947f8..0516569f1a22b4b3ec1a26f9f8fafeac1229ad2b 100644 (file)
@@ -46,7 +46,7 @@ type ModelDirective<T> = ObjectDirective<T & { _assign: AssignerFn }>
 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<HTMLInputElement> = {
-  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<HTMLInputElement> = {
-  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<HTMLInputElement> = {
 }
 
 export const vModelSelect: ModelDirective<HTMLSelectElement> = {
-  // use mounted & updated because <select> relies on its children <option>s.
-  mounted(el, { value }, vnode) {
-    setSelected(el, value)
-    el._assign = getModelAssigner(vnode)
+  created(el, binding, vnode) {
     addEventListener(el, 'change', () => {
       const selectedVal = Array.prototype.filter
         .call(el.options, (o: HTMLOptionElement) => o.selected)
         .map(getValue)
       el._assign(el.multiple ? selectedVal : selectedVal[0])
     })
+    el._assign = getModelAssigner(vnode)
+  },
+  // set value in mounted & updated because <select> relies on its children
+  // <option>s.
+  mounted(el, { value }) {
+    setSelected(el, value)
   },
   beforeUpdate(el, _binding, vnode) {
     el._assign = getModelAssigner(vnode)
@@ -214,8 +217,8 @@ function getCheckboxValue(
 export const vModelDynamic: ObjectDirective<
   HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
 > = {
-  beforeMount(el, binding, vnode) {
-    callModelHook(el, binding, vnode, null, 'beforeMount')
+  created(el, binding, vnode) {
+    callModelHook(el, binding, vnode, null, 'created')
   },
   mounted(el, binding, vnode) {
     callModelHook(el, binding, vnode, null, 'mounted')
@@ -233,7 +236,7 @@ function callModelHook(
   binding: DirectiveBinding,
   vnode: VNode,
   prevVNode: VNode | null,
-  hook: 'beforeMount' | 'mounted' | 'beforeUpdate' | 'updated'
+  hook: keyof ObjectDirective
 ) {
   let modelToUse: ObjectDirective
   switch (el.tagName) {
@@ -244,7 +247,7 @@ function callModelHook(
       modelToUse = vModelText
       break
     default:
-      switch (el.type) {
+      switch (vnode.props && vnode.props.type) {
         case 'checkbox':
           modelToUse = vModelCheckbox
           break