最近在做一个项目,项目里有一个一个需求是要有一个富文本编辑器,我在试了几个后选择了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>
刚刚挂载时:
1s后
这样,组件间的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>
然后就会这样
3秒后
这样我们就可以愉快的在子组件里修改父组件的值了,只要父组件设置一个.sync就可以了,是不是非常简单~
那我们来看看它的原理,实际上.sync也是语法糖,以下代码是等同的
<div
:msg="message"
@update:msg="message = $event"
></div>
<div
:msg.sync="message"
></div>
所以这种方式也是一个语法糖而已,只是很普通的事件封装。
今天的学习就到这里啦,我们下次再见