如何创建从 vue.js 实例到自定义原生 Web 组件的双向绑定?

Posted

技术标签:

【中文标题】如何创建从 vue.js 实例到自定义原生 Web 组件的双向绑定?【英文标题】:How to create bidirectional binding from vue.js instance to custom native web component? 【发布时间】:2018-10-07 08:05:43 【问题描述】:

以下是自定义 Web 组件 my-input 的示例。我想将自定义输入组件的 value 属性绑定到 vue 实例的 email 属性。 (该示例可能需要 Chrome 支持自定义 Web 组件。)

=>我必须如何调整我的 Web 组件示例才能使绑定正常工作?

如果我将 my-input 替换为普通的 input 标签,则绑定有效。因此,我对 vue.js 部分的语法似乎很好。

https://jsfiddle.net/j5f9edjt/

 new Vue(
  el: '#app',
  template: '#app-template',
  data: 
  //email data is blank initially
    email: ''
  
)
<script type="text/javascript" src="https://unpkg.com/vue@2.2.4"></script>

<script>
  class MyInput extends htmlElement 

		static get observedAttributes() 
        return ['value'];
    
    
    constructor()
       super();
       this.wrappedInput=undefined;
    

    connectedCallback()
        var self=this;
        if(!self.wrappedInput)
            var wrappedInput = document.createElement('input');
            wrappedInput.type='text';
            wrappedInput.onchange = ()=>this.wrappedInputChanged();
            self.appendChild(wrappedInput);
            self.wrappedInput = wrappedInput;
        
    
    
    attributeChangedCallback(attr, oldValue, newValue) 
      if(attr==='value')
          console.log('attribute changed ' + newValue);
          if(this.wrappedInput)
      	      this.wrappedInput.value= newValue; 
             
                                                 
       
    
    
    wrappedInputChanged()
      console.log('wrapepd input changed')
    	var newValue = this.wrappedInput.value;
      this.value = newValue;
    
    
     get value() 
        console.log('get value')
				return this.getAttribute('value');
		

	 set value(newValue) 	
    	this.setAttribute('value',newValue);     
      console.log('set value ' + newValue);
		

  
  window.customElements.define('my-input', MyInput);

</script>

<div id="app"></div>

<template id="app-template">
  <div>
  <my-input v-model="email"></my-input>
    <h1>
       You entered email
    </h1>
  </div>
  
</template>

我尝试发送一个额外的输入事件,但没有帮助:

     var myInput = new CustomEvent("input", 
        
          detail: 
            message: "Hello World!",
            type: 'text',
          ,
          bubbles: true,
          cancelable: true
        
      );
      this.dispatchEvent(myInput);

我在哪里可以找到 v-model 指令的源代码以了解它的作用?

相关问题:

How to target custom element (native web component) in vue.js?

【问题讨论】:

【参考方案1】:

要使v-model 工作,您需要为您的网络组件制作一个包装器组件。包装器将符合requirements for using v-model with a component。

或者,您可以将v-model 分解为两部分:设置value 属性和处理input 事件。就v-model 而言,Vue 似乎并未将 Web 组件识别为原生元素。

class MyInput extends HTMLElement 

  static get observedAttributes() 
    return ['value'];
  

  constructor() 
    super();
    this.wrappedInput = undefined;
  

  connectedCallback() 
    var self = this;
    if (!self.wrappedInput) 
      var wrappedInput = document.createElement('input');
      wrappedInput.type = 'text';
      wrappedInput.onchange = () => this.wrappedInputChanged();
      self.appendChild(wrappedInput);
      self.wrappedInput = wrappedInput;
    
  

  attributeChangedCallback(attr, oldValue, newValue) 
    if (attr === 'value') 
      console.log('attribute changed ' + newValue);
      if (this.wrappedInput) 
        this.wrappedInput.value = newValue;
      
    
  

  wrappedInputChanged() 
    var newValue = this.wrappedInput.value;
    this.value = newValue;
  

  get value() 
    console.log('get value')
    return this.getAttribute('value');
  

  set value(newValue) 
    this.setAttribute('value', newValue);
    console.log('set value ' + newValue);
  



window.customElements.define('my-input', MyInput);

new Vue(
  el: '#app',
  template: '#app-template',
  data: 
    //email data is blank initially
    email: ''
  ,
  methods: 
    handleInput(event) 
      this.email = event.target.value;
    
  ,
  components: 
    wrappedMyInput: 
      template: '#wmi-template',
      props: ['value'],
      methods: 
        emitInput(event) 
          this.$emit('input', event.target.value);
        
      
    
  
)
<script type="text/javascript" src="https://unpkg.com/vue@2.2.4"></script>

<div id="app"></div>

<template id="app-template">
  <div>
    <my-input :value="email" @input="handleInput"></my-input>
    <h1>
       You entered email
    </h1>
    <wrapped-my-input v-model="email"></wrapped-my-input>
    <h1>
       You entered email
    </h1>
  </div>
</template>

<template id="wmi-template">
  <my-input :value="value" @input="emitInput"></my-input>
</template>

【讨论】:

在 vue 实例中没有额外方法定义的变体是 也许可以创建一个自定义指令作为它的简写,例如v-mymodel="电子邮件"。我首选的解决方案仍然是以某种方式调整 Web 组件以直接使用 v-model。也许为此目的发送的特殊事件可以解决问题,但我没有让它发挥作用。【参考方案2】:

v-model 指令似乎检查标签的类型并单独处理它们:

https://github.com/vuejs/vue/blob/dev/src/platforms/web/compiler/directives/model.js

如果没有额外的包装器,我没有让 v-model 为我的自定义组件工作。 (我放弃了了解我的案例在 model.js 中是如何处理的。)

作为 Roy J 建议的“分解绑定”的替代方法。

<my-input :value="email" @input="email = $event.target.value"></my-input>

我创建了一个自定义指令v-property。它适用于 my-inputinput(在我的原始示例中使用;没有尝试所有可能的情况):

<my-input v-property="email"></my-input>
<input v-property="email"></my-input>

--

Vue.directive('property',  
  bind: function (el, binding, vnode) 
    var viewModel = vnode.context;
    var propertyName = binding.expression;
    el.addEventListener('input', (event)=> 
      var oldValue = viewModel[propertyName];
      var newValue = event.target.value;
      if(newValue != oldValue)
        viewModel[propertyName] = newValue;        
           
    );    

    viewModel.$watch(propertyName, ()=>
      var oldValue = el.value;
      var newValue = viewModel[propertyName];
      if(newValue != oldValue)
        el.value = newValue;        
       
    );

  
);

https://jsfiddle.net/7dqppqo2/

【讨论】:

以上是关于如何创建从 vue.js 实例到自定义原生 Web 组件的双向绑定?的主要内容,如果未能解决你的问题,请参考以下文章

vue.js基础(继)

如何在vue.js中定位自定义元素(本机Web组件)?

如何从弹出视图控制器获取数据到自定义表格视图单元格?

如何从 index.html 向 Vue.js 3 实例发送消息?

如何解决 Vue.js 中的“属性或方法未在实例上定义但在渲染期间引用..”?

如何将 JSP 页面包含到自定义标记中