v-model转发(父子组件间数据双向绑定)

最近在做一个项目,项目里有一个一个需求是要有一个富文本编辑器,我在试了几个后选择了vue-quill-editor这个编辑器,在选定编辑器后,按照习惯,我决定把这个编辑器再封装一层,整成一个单独的组件,但是根据这个组件的使用方式,有一个地方需要做一些特别的处理,我们先看看在官方文档中组件的使用方式

<template>
  <quill-editor
    ref="myQuillEditor"
    v-model="content"
    :options="editorOption"
  />
</template>
export default {
  data () {
    return {
      content: '<h2>I am Example</h2>',
      editorOption: {
        // Some Quill options...
      }
    }
  }
}

我们可以看到,quill编辑器组件可以通过v-model完成值的双向绑定,而我们要在这之上再进行一层封装,封装成我们自己的编辑器组件,然后其他组件也可以在我们自己的编辑器组件上使用v-model来进行双向绑定。

在说下去之前,我们要先了解一下v-model的原理,v-model是一个语法糖,一个v-bind和监听事件(默认是input事件)的语法糖,我们看看下面两段代码

<template>
  <input type="text" v-model="msg">
</template>

<script>
export default {
  data () {
    return {
      msg: ""
    }
  }
}
</script>
<template>
  <input type="text" :value="msg" @input="msg = $event.target.value">
</template>
<script>
export default {
  data () {
    return {
      msg: ""
    }
  }
}
</script>

这两种写法是等效的,所以v-model只是语法糖,我们只要用v-bind绑定值,再监听一个事件来刷新值就能达到相同的效果,而vue有一个专门的model语法,可以完成自定义的v-model,直接上代码
Parent.vue

<template>
    <div class="wrapper ParentView">
        parent-msg : {{msg}}
        <Child v-model="msg"/>
    </div>
</template>

<script lang="ts">
    import {Component, Emit, Inject, Model, Prop, Provide, Vue, Watch} from 'vue-property-decorator'
    import Child from "@/test/Child.vue";

    @Component({
        components: {
            Child
        }
    })
    export default class Parent extends Vue {

        msg : string = "";

    }
</script>

Child.vue

<template>
    <div class="wrapper ChildView">
        child-content : {{content}}
    </div>

</template>

<script lang="ts">
    import {Component, Emit, Inject, Model, Prop, Provide, Vue, Watch} from 'vue-property-decorator'

    @Component({
        components: {}
    })
    export default class Child extends Vue {

        // 这个event是v-model会监听的事件名
        @Model("event", {
            type : String
        })
        readonly content !: string;

        mounted() {
            setTimeout(() => {
                this.$emit("event", "ruaQAQ");
            }, 1000)
        }
    }
</script>

刚刚挂载时:
file
1s后
file

这样,组件间的v-model完成了,但是我们要做的是v-model的转发,所以我们在子组件里在加个input框,用于模拟富文本编辑器

<template>
    <div class="wrapper ChildView">
        child-content : {{content}}
+        <input v-model="inputContent"></input>
    </div>

</template>

<script lang="ts">
    import {Component, Emit, Inject, Model, Prop, Provide, Vue, Watch} from 'vue-property-decorator'

    @Component({
        components: {}
    })
    export default class Child extends Vue {

        @Model("event", {
            type : String
        })
        readonly content !: string;

+        inputContent : string = "";

+        @Watch("inputContent")
+        watchInputContent(newVal : string) {
+            this.$emit("event", newVal);
+        }

        mounted() {
            setTimeout(() => {
                this.$emit("event", "ruaQAQ");
            }, 3000)
        }

    }
</script>

我们加了一个inputContent用于双向绑定child组件里的input,然后监听inputContent的变化,当inputContent发生变化时,立刻提交event来更新content的值,这样就完成了v-model的转发,这个方式在封装许多组件时都会用上,比如如果你要再次封装一些已经封装好的组件库。

另外,我们还可以使用另外一种办法完成类似的功能,那就是用.sync,话不多说,直接上代码

<!-- parent.vue -->
<template>
    <div class="wrapper ParentView">
        parentMsg : {{parentMsg}}
        <Child :msg.sync="parentMsg"/>
    </div>
</template>

<script lang="ts">
    import {Component, Emit, Inject, Model, Prop, Provide, Vue, Watch} from 'vue-property-decorator'
    import Child from "@/test/Child.vue";

    @Component({
        components: {
            Child
        }
    })
    export default class Parent extends Vue {

        parentMsg : string = "这是父组件传入的msg";

    }
</script>
<!-- children.vue -->
<template>
    <div class="wrapper ChildView">
        msg : {{msg}}
    </div>

</template>

<script lang="ts">
    import {Component, Emit, Inject, Model, Prop, Provide, Vue, Watch} from 'vue-property-decorator'

    @Component({
        components: {}
    })
    export default class Child extends Vue {

        mounted() {
            setTimeout(() => {
                this.$emit("update:msg", "这是变化后的msg")
            }, 3000);
        }

        @Prop()
        msg ?: string
    }
</script>

然后就会这样
file
3秒后
file

这样我们就可以愉快的在子组件里修改父组件的值了,只要父组件设置一个.sync就可以了,是不是非常简单~

那我们来看看它的原理,实际上.sync也是语法糖,以下代码是等同的

<div
    :msg="message"
    @update:msg="message = $event"
></div>
<div
    :msg.sync="message"
></div>

所以这种方式也是一个语法糖而已,只是很普通的事件封装。

今天的学习就到这里啦,我们下次再见

点赞

发表评论

昵称和uid可以选填一个,填邮箱必填(留言回复后将会发邮件给你)
tips:输入uid可以快速获得你的昵称和头像

Title - Artist
0:00