Преглед изворни кода

feat(v-model): craete non-existent properties as reactive

close #5932
Evan You пре 8 година
родитељ
комит
e1da0d585c

+ 34 - 19
src/compiler/directives/model.js

@@ -37,42 +37,57 @@ export function genAssignmentCode (
   value: string,
   assignment: string
 ): string {
-  const modelRs = parseModel(value)
-  if (modelRs.idx === null) {
+  const res = parseModel(value)
+  if (res.key === null) {
     return `${value}=${assignment}`
   } else {
-    return `$set(${modelRs.exp}, ${modelRs.idx}, ${assignment})`
+    return `$set(${res.exp}, ${res.key}, ${assignment})`
   }
 }
 
 /**
- * parse directive model to do the array update transform. a[idx] = val => $$a.splice($$idx, 1, val)
+ * Parse a v-model expression into a base path and a final key segment.
+ * Handles both dot-path and possible square brackets.
  *
- * for loop possible cases:
+ * Possible cases:
  *
  * - test
- * - test[idx]
- * - test[test1[idx]]
- * - test["a"][idx]
- * - xxx.test[a[a].test1[idx]]
- * - test.xxx.a["asa"][test1[idx]]
+ * - test[key]
+ * - test[test1[key]]
+ * - test["a"][key]
+ * - xxx.test[a[a].test1[key]]
+ * - test.xxx.a["asa"][test1[key]]
  *
  */
 
 let len, str, chr, index, expressionPos, expressionEndPos
 
-export function parseModel (val: string): Object {
-  str = val
-  len = str.length
-  index = expressionPos = expressionEndPos = 0
+type ModelParseResult = {
+  exp: string,
+  key: string | null
+}
+
+export function parseModel (val: string): ModelParseResult {
+  len = val.length
 
   if (val.indexOf('[') < 0 || val.lastIndexOf(']') < len - 1) {
-    return {
-      exp: val,
-      idx: null
+    index = val.lastIndexOf('.')
+    if (index > -1) {
+      return {
+        exp: val.slice(0, index),
+        key: '"' + val.slice(index + 1) + '"'
+      }
+    } else {
+      return {
+        exp: val,
+        key: null
+      }
     }
   }
 
+  str = val
+  index = expressionPos = expressionEndPos = 0
+
   while (!eof()) {
     chr = next()
     /* istanbul ignore if */
@@ -84,8 +99,8 @@ export function parseModel (val: string): Object {
   }
 
   return {
-    exp: val.substring(0, expressionPos),
-    idx: val.substring(expressionPos + 1, expressionEndPos)
+    exp: val.slice(0, expressionPos),
+    key: val.slice(expressionPos + 1, expressionEndPos)
   }
 }
 

+ 14 - 8
test/unit/features/directives/model-parse.spec.js

@@ -1,33 +1,39 @@
 import { parseModel } from 'compiler/directives/model'
 
 describe('model expression parser', () => {
+  it('parse single path', () => {
+    const res = parseModel('foo')
+    expect(res.exp).toBe('foo')
+    expect(res.key).toBe(null)
+  })
+
   it('parse object dot notation', () => {
     const res = parseModel('a.b.c')
-    expect(res.exp).toBe('a.b.c')
-    expect(res.idx).toBe(null)
+    expect(res.exp).toBe('a.b')
+    expect(res.key).toBe('"c"')
   })
 
   it('parse string in brackets', () => {
     const res = parseModel('a["b"][c]')
     expect(res.exp).toBe('a["b"]')
-    expect(res.idx).toBe('c')
+    expect(res.key).toBe('c')
   })
 
   it('parse brackets with object dot notation', () => {
     const res = parseModel('a["b"][c].xxx')
-    expect(res.exp).toBe('a["b"][c].xxx')
-    expect(res.idx).toBe(null)
+    expect(res.exp).toBe('a["b"][c]')
+    expect(res.key).toBe('"xxx"')
   })
 
   it('parse nested brackets', () => {
     const res = parseModel('a[i[c]]')
     expect(res.exp).toBe('a')
-    expect(res.idx).toBe('i[c]')
+    expect(res.key).toBe('i[c]')
   })
 
   it('combined', () => {
-    const res = parseModel('test.xxx.a["asa"][test1[idx]]')
+    const res = parseModel('test.xxx.a["asa"][test1[key]]')
     expect(res.exp).toBe('test.xxx.a["asa"]')
-    expect(res.idx).toBe('test1[idx]')
+    expect(res.key).toBe('test1[key]')
   })
 })

+ 21 - 0
test/unit/features/directives/model-text.spec.js

@@ -354,5 +354,26 @@ describe('Directive v-model text', () => {
         done()
       }, 16)
     })
+
+    it('should create and make reactive non-existent properties', done => {
+      const vm = new Vue({
+        data: {
+          foo: {}
+        },
+        template: '<input v-model="foo.bar">'
+      }).$mount()
+      expect(vm.$el.value).toBe('')
+
+      vm.$el.value = 'a'
+      triggerEvent(vm.$el, 'input')
+      expect(vm.foo.bar).toBe('a')
+      vm.foo.bar = 'b'
+      waitForUpdate(() => {
+        expect(vm.$el.value).toBe('b')
+        vm.foo = {}
+      }).then(() => {
+        expect(vm.$el.value).toBe('')
+      }).then(done)
+    })
   }
 })