vue双向绑定没法输入

http://jsrun.net/Sz3Kp/edit?m...

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <!-- import CSS -->
  <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
</head>
<body>
  <div id="app">
      {{formModel}}
    <el-input v-model="formModel.a"></el-input>
    <el-input v-model="formModel.b"></el-input>
  </div>
</body>
  <!-- import Vue before Element -->
  <script src="https://unpkg.com/vue/dist/vue.js"></script>
  <!-- import JavaScript -->
  <script src="https://unpkg.com/element-ui/lib/index.js"></script>
  <script>
    new Vue({
      el: '#app',
      data: function() {
        return { 
            formModel: {

            }
         }
      },
      created() {
          this.formModel = {}
          // 只写上面可以,但是一旦这样写就没法输入,或者说只能读取上一次的值
          // 既然vue官网说了这样写不会绑上getter setter那为何formModel里又有值
          this.formModel.a = 'a' // 这行注释了就能输入
          // 为啥formModel里啥都不写,直接v-model="formModel.x"又能直接被绑上呢?
      }
    })
  </script>
</html>
阅读 3.2k
4 个回答

为了了解为什么在created里面给formModel的a属值赋值以后,就导致a属性不具有响应式的效果以后我们得了解实际运行时组件的代码是怎样的

  <div>
      {{formModel}}
      <input v-model="formModel.a" />
      <input v-model="formModel.b" />
 </div>

我们将上面的代码用Vue.complie编译得到该组件的渲染函数

{
  render() {
    with (this) {
      return _c('div', [
        _v(_s(formModel) + '\r\n'),
        _c('input', {
          directives: [
            {
              name: 'model',
              rawName: 'v-model',
              value: formModel.a,
              expression: 'formModel.a',
            },
          ],
          domProps: { value: formModel.a },
          on: {
            input: function ($event) {
              if ($event.target.composing) return
              $set(formModel, 'a', $event.target.value)
            },
          },
        }),
        _c('input', {
          directives: [
            {
              name: 'model',
              rawName: 'v-model',
              value: formModel.b,
              expression: 'formModel.b',
            },
          ],
          domProps: { value: formModel.b },
          on: {
            input: function ($event) {
              if ($event.target.composing) return
              $set(formModel, 'b', $event.target.value)
            },
          },
        }),
      ])
    }
  },
}

上面的函数就是组件真正运行时的代码,现在我们可以去掉模板在页面中添加一下代码就可以将组件直接渲染出来了

new Vue({
  el: '#app',
  data: function () {
    return {
      formModel: {},
    }
  },
  render() {
    with (this) {
      return _c('div', [
        _v(_s(formModel) + '\r\n'),
        _c('input', {
          directives: [
            {
              name: 'model',
              rawName: 'v-model',
              value: formModel.a,
              expression: 'formModel.a',
            },
          ],
          domProps: { value: formModel.a },
          on: {
            input: function ($event) {
              if ($event.target.composing) return
              $set(formModel, 'a', $event.target.value)
            },
          },
        }),
        _c('input', {
          directives: [
            {
              name: 'model',
              rawName: 'v-model',
              value: formModel.b,
              expression: 'formModel.b',
            },
          ],
          domProps: { value: formModel.b },
          on: {
            input: function ($event) {
              if ($event.target.composing) return
              $set(formModel, 'b', $event.target.value)
            },
          },
        }),
      ])
    }
  },
  created() {
    // this.formModel = {}
    // 只写上面可以,但是一旦这样写就没法输入,或者说只能读取上一次的值
    // 既然vue官网说了这样写不会绑上getter setter那为何formModel里又有值
    this.formModel.a = 'a' // 这行注释了就能输入
    // 为啥formModel里啥都不写,直接v-model="formModel.x"又能直接被绑上呢?
  },
})

可以看到vue自动给input添加一个v-model指令

directives: [
  {
    name: 'model',
    rawName: 'v-model',
    value: formModel.a,
    expression: 'formModel.a',
  },
]

我们再看看vue的v-model指令都干了些什么

const directive = {
  inserted: function inserted(el, binding, vnode, oldVnode) {
    ...
    el.addEventListener('change', onCompositionEnd)
    ...
  }
  ...
}

function onCompositionEnd(e) {
  ...
  trigger(e.target, 'input')
  ...
}

function trigger(el, type) {
  var e = document.createEvent('HTMLEvents')
  e.initEvent(type, true, true)
  el.dispatchEvent(e)
}

vue的v-model指令为dom注册了onchange事件,并且dispatch了一个input事件,而和v-model指令一样,input的监听函数vue也会自动添加,这就能理解说为什么v-model是个语法糖了

on: {
    input: function ($event) {
      if ($event.target.composing) return
      $set(formModel, 'b', $event.target.value)
    },
  }

可以看到关键代码 $set(formModel, 'b', $event.target.value)到这里我们就知道即使不用手动给formModel $set属性,formModel在使用v-model时formModel上的属性也是响应式属性,因为vue会自己$set,
关键就在于如果在created方法里面如果提前给formModel设置一个非响应式属性比如this.formModel.a = 'a' 那么后续事件触发时$set看到已经存在该属性了,就不会帮formModel定义该响应式属性了,就导致该属性的响应式失效明明给他赋了值但没有触发视图更新

//input事件触发后调用set方法
function set(target, key, val) {
  //formModel.a属性已经存在直接赋值后返回,即使他不是一个响应式的属性
  if (key in target && !(key in Object.prototype)) {
    //formModel.a为普通属性给他赋值不会触发视图更新
    target[key] = val
    return val
  }
  defineReactive$$1(ob.value, key, val)
  return val
}

结论: 要么formModel上的属性一个都不声明,等事件触发时vue会自动声明,或者自己手动声明时用$set

this.$set(this.formModel,'a','a')
//
还有麻烦设置一下data的初始值
formModal:{
    a:'',
    b:''
}

你这样改写就好了

new Vue({
    el: '#app',
    data: function() {
      return { 
        formModel: {
          a:"",
        }
      }
    },
    created() {
      // this.formModel = {}
      this.formModel.a = 'a'
    }
})

具体原因呢,是因为你在 created 里重新给 formModel 赋值了一个新得空对象,然后直接增加属性 a
这个 a 属性不会被收集到侦测对象中,所以不会再每次 input 事件中去 $set 新值,只会维持初始的 "a" 值。

你可以在 <input> 上绑定一个 @input 事件,然后查看输出日志:
image.png

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏