VueJs 指令双向绑定

VueJs directive two way binding

我创建了一个自定义指令来处理 VueJs 中的 select2。当我将 select 绑定到我的视图模型中的数据 属性 时,下面的代码有效,该数据不是数据中对象的属性。

类似于 this.userId 但如果它绑定到类似于 this.user.id 的东西,它不会更新我的视图模型数据对象中的值。

Vue.directive('selected', {    
    bind: function (el, binding, vnode) {    
        var key = binding.expression;    
        var select = $(el);    

        select.select2();    
        vnode.context.$data[binding.expression] = select.val();    

        select.on('change', function () {    
            vnode.context.$data[binding.expression] = select.val();    
        });    
    },    
    update: function (el, binding, newVnode, oldVnode) {    
        var select = $(el);    
        select.val(binding.value).trigger('change');    
    }    
});

<select v-selected="userEditor.Id">
   <option v-for="user in users" v-bind:value="user.id" >
       {{ user.fullName}}
   </option>
</select>

相关fiddle: https://jsfiddle.net/raime910/rHm4e/4/

当你使用一级$data的-属性时,它直接通过[]-brackets

访问$data对象

但是您想将嵌套对象的路径传递给 selected 指令,因此您应该这样做:

// source: 
function deepSet(obj, value, path) {
    var i;
    path = path.split('.');
    for (i = 0; i < path.length - 1; i++)
        obj = obj[path[i]];

    obj[path[i]] = value;
}

Vue.directive('selected', {    
bind: function (el, binding, vnode) {    
    var select = $(el);    

    select.select2();    
    deepSet(vnode.context.$data, select.val(), binding.expression);    

    select.on('change', function () {    
        deepSet(vnode.context.$data, select.val(), binding.expression);
    });    
},    
update: function (el, binding, newVnode, oldVnode) {    
    var select = $(el);    
    select.val(binding.value).trigger('change');    
}    
});

<select v-selected="userEditor.Id">
<option v-for="user in users" v-bind:value="user.id" >
   {{ user.fullName}}
</option>
</select>

描述:

假设我们有两个$data的道具:valOrObjectWithoutNestingobjLvl1

data: function(){
  return{
    valOrObjectWithoutNesting: 'let it be some string',
    objLvl1:{
      objLvl2:{
        objLvl3:{
          objField: 'primitive string'
        }
      }
    }
  }
}

第 1 级 $data 的变体-属性:

<select v-selected="valOrObjectWithoutNesting">

// Now this code:
vnode.context.$data[binding.expression] = select.val();
// Equals to: 
vnode.context.$data['valOrObjectWithoutNesting'] = select.val();

第 4 级 $data 的变体-属性:

<select v-selected="objLvl1.objLvl2.objLvl3.objField">

// Now this code:
vnode.context.$data[binding.expression] = select.val();
// Equals to: 
vnode.context.$data['objLvl1.objLvl2.objLvl3.objField'] = select.val(); // error here

所以我上面代码中的 deepSet 函数 "converting" $data['objLvl1.objLvl2.objLvl3.objField']$data['objLvl1']['objLvl2']['objLvl3']['objField'].

如你所见,正如我在对你的问题的评论中提到的,当你想让 select2-wrapper 更可定制时,指令方式比单独的组件方式复杂得多。在组件中,您可以根据需要传递尽可能多的配置道具和事件订阅,您可以避免像 vnode.context.$data[binding.expression] 这样的边突变,并且您的代码将变得更容易理解和更简单以获得进一步的支持。

自定义指令非常好,除了使用 inserted 钩子而不是 bind。改编自 Vue Wrapper Component Example.

要绑定到一个对象 属性,最简单的方法是将它包装在计算的 setter Computed Setter 中并绑定到它。

请注意,'deep setting' 似乎不起作用。问题是变化检测之一,计算 setter 克服了这个问题。 (注意 on('change' 函数是 jQuery 而不是 Vue。)

console.clear()

Vue.directive('selected', {
  inserted: function (el, binding, vnode) {
    var select = $(el);
    select
      .select2()
      .val(binding.value)
      .trigger('change')
      .on('change', function () {
        if (vnode.context[binding.expression]) {
          vnode.context[binding.expression] = select.val();     
        }
      })
    },
});

var vm = new Vue({
  el: '#my-app',
  computed: {
    selectedValue: {
      get: function() { return this.myObj.type },
      set: function (value) { this.myObj.type = value }
    }
  },
  data: {
    selectedVal: 0,
    myObj: { type: 3 },
    opts: [{
      id: 1,
      text: 'Test 1'
    }, {
      id: 2,
      text: 'Test 2'
    }, {
      id: 3,
      text: 'Test 3'
    }]
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.5/css/select2.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.5/js/select2.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.4/vue.js"></script>

<div id="my-app">
  <div>
    <label for="example">Test dropdown list ({{ myObj.type }})</label>
  </div>
  <div>
    <select id="example" style="width: 300px" v-selected="selectedValue">
      <option v-for="(opt,index) in opts" :value="opt.id" :key="index">
        {{ opt.text }}
      </option>
    </select>
  </div>
</div>