VueJs 指令双向绑定

Posted

技术标签:

【中文标题】VueJs 指令双向绑定【英文标题】:VueJs directive two way binding 【发布时间】:2018-04-22 09:11:24 【问题描述】:

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

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

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>

相关小提琴: https://jsfiddle.net/raime910/rHm4e/4/

【问题讨论】:

你能分享一下这个的现场演示吗? 你为什么使用指令而不是自定义 Vue 组件?恕我直言,组件方式匹配更简单——你不需要使用这个 hack vnode.context.$data,你也可以使用 createdmounted 钩子等。几周前,我在 Vue 中为一些 jquery 插件实现了包装器:我第一次使用指令方式 - 很快它变得太复杂了,所以我重构以分离 vue-component,现在它很简单并且工作正常。 对了,你看到这个原生的 vue-component-library:sagalbot.github.io/vue-select 了吗? - 我将它用于我的项目。它已经是 vue-component,所以不需要包装器。 另外,请提及这一刻:您使用v-for 表示没有:key 属性的对象:&lt;option v-for="user in users" v-bind:value="user.id" &gt;。应该是这样的:&lt;option v-for="user in users" v-bind:value="user.id" :key="user.id"&gt;(请参阅此文档:vuejs.org/v2/guide/list.html#key)。简而言之——如果你正在迭代对象,而不是原始类型——你可能会遇到 Vue 反应性的意外错误。 (我花了一整天的时间来找出我的项目中同样的遗漏)。 您可以使用 HTML select 元素并设置 is 属性以使其成为 select2 组件。 vuejs.org/v2/api/#is 【参考方案1】:

当您使用一级$data's-property 时,它直接通过[]-brackets 访问$data 对象

但是你想将嵌套对象的路径传递给selected-directive,所以你应该这样做:

// source: https://***.com/a/6842900/8311719
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 的props:valOrObjectWithoutNestingobjLvl1

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

具有 1 级 $data's-property 的变体:

<select v-selected="valOrObjectWithoutNesting">

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

具有第 4 级 $data's-property 的变体:

<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 函数将$data['objLvl1.objLvl2.objLvl3.objField']“转换”为$data['objLvl1']['objLvl2']['objLvl3']['objField']

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

【讨论】:

【参考方案2】:

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

要绑定到对象属性,最简单的方法是将其包装在计算设置器Computed 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>

【讨论】:

以上是关于VueJs 指令双向绑定的主要内容,如果未能解决你的问题,请参考以下文章

面试问题:Vuejs如何实现双向绑定

Vuejs——入门(单向绑定双向绑定列表渲染响应函数)

VueJS双向数据绑定实现

解决vuejs 创建数据后设置对象的属性实现不了双向绑定问题

解决vuejs 创建数据后设置对象的属性实现不了双向绑定问题

JS学习笔记——AngularJS 1.x双向数据绑定机制