Vue的双向绑定以及组件的自定义事件

孤街浪徒 提交于 2020-02-09 20:43:17

什么是双向绑定

所谓的双向绑定是指数据发生变化时,视图会同步发生变化,而当视图发生变化时,数据也会同步变化。

Vue中怎么实现双向绑定

在Vue中,我们通过v-model来创建双向绑定。

我们继续用todolist和todoitme组件来示例双向绑定

  1. 在App.vue的data中增加一个message。
  data(){
    return{
      message:"hello world",
      list: [
              {
                title: "新课程1",
                del: false
              },
              {
                title: "新课程2",
                del: true
              },
              {
                title: "新课程3",
                del: false
              }
            ]
    };
  },
  1. 在App.vue的template中通过模板语法增加对应的message展示。
    {{message}}
  1. 创建一个表单控件,通过v-model实现message的双向绑定。
<input type="text" v-model="message"/>

全部代码如下

<template>
  <div id="app">
    <input type="text" v-model="message"/>
    {{message}}
    <todolist>
      <todoitem v-on:delete="handleDelete" v-for="item in list" :key="item.title" data-wen="wen" :title="item.title" :del="item.del">
        <template v-slot:pretext="{val}">
          <label>前置文字{{val}}</label>
        </template>
      </todoitem>
    </todolist>
  </div>
</template>

<script>
import todolist from './components/todo-list.vue'
import todoitem from './components/todo-item.vue'

export default {
  name: 'App',
  components: {
    todolist,
    todoitem
  },
  data(){
    return{
      message:"hello world",
      list: [
              {
                title: "新课程1",
                del: false
              },
              {
                title: "新课程2",
                del: true
              },
              {
                title: "新课程3",
                del: false
              }
            ]
    };
  },
  methods: {
    handleDelete(vtitle){
      console.log("删除工程!",vtitle)
    }
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

效果如下:在表单中修改message,不论是脚本中的message变量还是动态绑定的message模板显示都会同步变化。

Vue双向绑定的本质

按照Vue官网介绍,Vue的双向绑定是一种语法糖。本质上是

负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。

它其实还是一种单向的数据流操作。

它的还原写法即本质是value属性和input事件的组合,代码如下:(单单相对于input的text控件来说):

<input type="text" :value="message" @input="handleChange"/>
handleChange(e){
      this.message = e.target.value;
    },

完整代码如下:

<template>
  <div id="app">
    <input type="text" v-model="message"/>
    <input type="text" :value="message" @input="handleChange"/>
    {{message}}
    <todolist>
      <todoitem v-on:delete="handleDelete" v-for="item in list" :key="item.title" data-wen="wen" :title="item.title" :del="item.del">
        <template v-slot:pretext="{val}">
          <label>前置文字{{val}}</label>
        </template>
      </todoitem>
    </todolist>
  </div>
</template>

<script>
import todolist from './components/todo-list.vue'
import todoitem from './components/todo-item.vue'

export default {
  name: 'App',
  components: {
    todolist,
    todoitem
  },
  data(){
    return{
      message:"hello world",
      list: [
              {
                title: "新课程1",
                del: false
              },
              {
                title: "新课程2",
                del: true
              },
              {
                title: "新课程3",
                del: false
              }
            ]
    };
  },
  methods: {
    handleChange(e){
      this.message = e.target.value;
    },
    handleDelete(vtitle){
      console.log("删除工程!",vtitle)
    }
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

为什么说上述的原始写法只针对于input 的text呢?
vue官网解释如下:

v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件:

  • text 和 textarea 元素使用 value 属性和 input 事件;
  • checkbox 和 radio 使用 checked 属性和 change 事件;
  • select 字段将 value 作为 prop 并将 change 作为事件。
  • .....

如何为组件添加自定义事件绑定

原生的双向绑定可以大大简化我们的编码,那么如何为我们自己封装的组件添加自定义的双向绑定呢?

为了实现这一点,我们需要为我们的组件增加一个model对象,并在model对象中告知vue框架本次双向绑定对应的属性和事件。

如下示例则是告知vue底层该组件监听checked属性的change事件。

model: {
    prop: 'checked',
    event: 'change'
  },

我们尝试修改todoitem的template和data,增加选择控件和选择后的文本。

  1. 第一步为组件增加model对象
    model: {
        prop: 'itemCheck',
        event: 'change'
    },
  1. 第二步在组件的props中注册itemCheck属性
    props: {
          title: String,
          itemCheck:{
            type: Boolean,
            default: false
          },
          del: {
            type: Boolean,
            default: false
          }
        },
  1. 第三步修改template,增加checkbox和选中的状态描述。需要注意的是:需要使用完全实现v-model的本质上的属性绑定和事件编码。
<template>
    <li>
        <input type="checkbox" :name="vrandom" :key="vrandom" :checked="itemCheck" @change="$emit('change', $event.target.checked)" />
        <slot name="pretext" :val="vrandom"></slot>
        <span class="redsapn" v-if="!del">{{title}}</span>
        <span v-else style="text-decoration:line-through">{{title}}</span>
        <button v-show="!del" @click="handleClick">删除</button>
        <slot name="suftext">默认尾部</slot>
    </li>
</template>
  1. 最后我们在APP.vue中的template中使用todoitem时就可以通过v-model来实现todoitem组件的双向绑定了。
<template>
  <div id="app">
    <input type="text" v-model="message"/>
    <input type="text" :value="message" @input="handleChange"/>
    {{message}}
    <todolist>
      <todoitem v-on:delete="handleDelete" v-model="Checkedmsg" v-for="item in list" :key="item.title" data-wen="wen" :title="item.title" :del="item.del">
        <template v-slot:pretext="{val}">
          <label>前置文字{{val}}</label>
        </template>
      </todoitem>
    </todolist>
  </div>
</template>

完整代码如下:

//App.vue
<template>
  <div id="app">
    <input type="text" v-model="message"/>
    <input type="text" :value="message" @input="handleChange"/>
    {{message}}
    <todolist>
      <todoitem v-on:delete="handleDelete" v-model="Checkedmsg" v-for="item in list" :key="item.title" data-wen="wen" :title="item.title" :del="item.del">
        <template v-slot:pretext="{val}">
          <label>前置文字{{val}}</label>
        </template>
      </todoitem>
    </todolist>
  </div>
</template>

<script>
import todolist from './components/todo-list.vue'
import todoitem from './components/todo-item.vue'

export default {
  name: 'App',
  components: {
    todolist,
    todoitem
  },
  data(){
    return{
      message:"hello world",
      Checkedmsg:false,
      list: [
              {
                title: "新课程1",
                del: false
              },
              {
                title: "新课程2",
                del: true
              },
              {
                title: "新课程3",
                del: false
              }
            ]
    };
  },
  methods: {
    handleChange(e){
      this.message = e.target.value;
    },
    handleDelete(vtitle){
      console.log("删除工程!",vtitle)
    }
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>
//todo-item.vue
<template>
    <li>
        <input type="checkbox" :name="vrandom" :key="vrandom" :checked="itemCheck" @change="$emit('change', $event.target.checked)" />
        <slot name="pretext" :val="vrandom"></slot>
        <span class="redsapn" v-if="!del">{{title}}</span>
        <span v-else style="text-decoration:line-through">{{title}}</span>
        <button v-show="!del" @click="handleClick">删除</button>
        <slot name="suftext">默认尾部</slot>
    </li>
</template>
<script>
export default {
    model: {
        prop: 'itemCheck',
        event: 'change'
    },
    props: {
          title: String,
          itemCheck:Boolean,
          del: {
            type: Boolean,
            default: false
          }
        },
    data: function() {
          return {
              vrandom:Math.random()
          };
        },
    methods: {
            handleClick(){
                console.log("点击删除按钮!");
                this.$emit('delete',this.title);
            }
        }
}
</script>
<style scoped>
.redsapn{color: red}
</style>
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!