Przeglądaj źródła

feat: support sync modifier for v-bind="object" (#5943)

close #5937
wenlu.wang 9 lat temu
rodzic
commit
3965e5053a

+ 1 - 1
flow/component.js

@@ -120,7 +120,7 @@ declare interface Component {
   // renderSlot
   _t: (name: string, fallback: ?Array<VNode>, props: ?Object) => ?Array<VNode>;
   // apply v-bind object
-  _b: (data: any, value: any, asProp?: boolean) => VNodeData;
+  _b: (data: any, tag: string, value: any, asProp: boolean, isSync?: boolean) => VNodeData;
   // check custom keyCode
   _k: (eventKeyCode: number, key: string, builtInAlias: number | Array<number> | void) => boolean;
   // resolve scoped slots

+ 4 - 2
src/compiler/directives/bind.js

@@ -2,8 +2,10 @@
 
 export default function bind (el: ASTElement, dir: ASTDirective) {
   el.wrapData = (code: string) => {
-    return `_b(${code},'${el.tag}',${dir.value}${
-      dir.modifiers && dir.modifiers.prop ? ',true' : ''
+    return `_b(${code},'${el.tag}',${dir.value},${
+      dir.modifiers && dir.modifiers.prop ? 'true' : 'false'
+    }${
+      dir.modifiers && dir.modifiers.sync ? ',true' : ''
     })`
   }
 }

+ 9 - 1
src/core/instance/render-helpers/bind-object-props.js

@@ -16,7 +16,8 @@ export function bindObjectProps (
   data: any,
   tag: string,
   value: any,
-  asProp?: boolean
+  asProp: boolean,
+  isSync?: boolean
 ): VNodeData {
   if (value) {
     if (!isObject(value)) {
@@ -44,6 +45,13 @@ export function bindObjectProps (
         }
         if (!(key in hash)) {
           hash[key] = value[key]
+
+          if (isSync) {
+            const on = data.on || (data.on = {})
+            on[`update:${key}`] = function ($event) {
+              value[key] = $event
+            }
+          }
         }
       }
     }

+ 30 - 0
test/unit/features/directives/bind.spec.js

@@ -187,6 +187,36 @@ describe('Directive v-bind', () => {
     }).then(done)
   })
 
+  it('.sync modifier with bind object', done => {
+    const vm = new Vue({
+      template: `<test v-bind.sync="test"/>`,
+      data: {
+        test: {
+          fooBar: 1
+        }
+      },
+      components: {
+        test: {
+          props: ['fooBar'],
+          template: `<div @click="handleUpdate">{{ fooBar }}</div>`,
+          methods: {
+            handleUpdate () {
+              this.$emit('update:fooBar', 2)
+            }
+          }
+        }
+      }
+    }).$mount()
+    expect(vm.$el.textContent).toBe('1')
+    triggerEvent(vm.$el, 'click')
+    waitForUpdate(() => {
+      expect(vm.$el.textContent).toBe('2')
+      vm.test.fooBar = 3
+    }).then(() => {
+      expect(vm.$el.textContent).toBe('3')
+    }).then(done)
+  })
+
   it('bind object with overwrite', done => {
     const vm = new Vue({
       template: '<input v-bind="test" id="foo" :class="test.value">',

+ 15 - 1
test/unit/modules/compiler/codegen.spec.js

@@ -126,7 +126,21 @@ describe('codegen', () => {
   it('generate v-bind directive', () => {
     assertCodegen(
       '<p v-bind="test"></p>',
-      `with(this){return _c('p',_b({},'p',test))}`
+      `with(this){return _c('p',_b({},'p',test,false))}`
+    )
+  })
+
+  it('generate v-bind with prop directive', () => {
+    assertCodegen(
+      '<p v-bind.prop="test"></p>',
+      `with(this){return _c('p',_b({},'p',test,true))}`
+    )
+  })
+
+  it('generate v-bind directive with sync modifier', () => {
+    assertCodegen(
+      '<p v-bind.sync="test"></p>',
+      `with(this){return _c('p',_b({},'p',test,false,true))}`
     )
   })