1. v-modelImplement custom component bidirectional binding

V-model is a syntactic sugar, and writing v-Model will not work if the component is not defined according to the corresponding specification. Again, similar to v-on:click can be shortened to @click, v-model is shorthand for two expressions put together. So keep that in mind, and I’m going to explain it.

1.1 inputTwo-way binding

Child component myinput.vue:

<template>
    <div>The input<input :value="value" @input="input"/>
    </div>
</template>

<script>
    export default {
        name: "MyInput".props: {
            value: {type: [String.Number]}},methods: {
            input(e) {
                this.$emit('input', e.target.value)
            }
        }
    }
</script>
Copy the code

Used in parent component app.vue:

<template>
    <div id="app">
        <my-input v-model="haha" />
        <div>{{haha}}</div>
    </div>
</template>

<script>
    import MyInput from '@/components/MyInput'
    export default {
        name: 'App',
        components: { MyInput },
        data() {
            return {
                haha: '66666',
            }
        }
    }
</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>
Copy the code

is a syntactically equivalent to:

<my-input
    :value="haha"
    @input="onInput"
/>

onInput(e) {
    this.haha = e
}
Copy the code

V-model has the same effect. It saves a lot of code by not having to manually assign values to HAHA and defining one less method. The effect is quite obvious.

The value in the MyInput component cannot be called anything else, and the method thrown by $emit can only be called input. Otherwise, the v-model syntax will not work. Otherwise, the $event argument is omitted from the $event.target.value method to obtain the input value.

1.2. checkboxTwo-way binding

The value it binds to is not called value, and the event it triggers is not called input.

MyCheckBox subcomponent code:

<template> <div> <input type="checkbox" :checked="checked" @change="change"/> </div> </template> <script> export default  { name: "MyCheckBox", model: { prop: 'checked', event: 'zhuangbi' }, props: { checked: Boolean }, methods: { change(e) { this.$emit('zhuangbi', e.target.checked) } } } </script>Copy the code

Used in parent component app.vue:

<template>
    <div id="app">
        <my-check-box v-model="changeV"></my-check-box>
        <div>{{changeV}}</div>
    </div>
</template>

<script>
    import MyCheckBox from '@/components/MyCheckBox'
    export default {
        name: 'App',
        components: { MyCheckBox },
        data() {
            return {
                changeV: true,
            }
        }
    }
</script>
Copy the code

Except that the binding value becomes checked, the @input becomes @change, and the argument passed to the parent component is E.target.checked

Note: in most cases, the event emitted by $emit is “change”. I write “zhuangbi”, as long as it has the same value as the event attribute in the model module.

Also, outside props are necessary.

2. .syncTo achieve bidirectional binding

If you need to bind a prop bidirectionally, you can use the.sync syntax sugar. As an example of a usage scenario, someone else’s CheckBox component needs to make some style changes or function combinations to be reused, which requires another bidirectional binding of the V-Model values.


With $emit, the parent component listens for events and does assignments. Now with.sync you can do it all in one sentence.

Subcomponent DiyCheckBox code:

<template> <div> <my-check-box v-model="diyCheck" @change="dChange"/> </div> </template> <script> import MyCheckBox from  './MyCheckBox' export default { name: "DiyCheckBox", components: {MyCheckBox}, props: { diyCheck: Boolean, test: String }, methods: { dChange(e) { this.$emit('update:diyCheck', e) } } } </script>Copy the code

Used in parent component app.vue:

<template>
    <div id="app">
        <diy-check-box :diyCheck.sync="dCheck" />
        <div>{{dCheck}}</div>
    </div>
</template>

<script>
    import DiyCheckBox from '@/components/DiyCheckBox'

    export default {
        name: 'App',
        components: { DiyCheckBox },
        data() {
            return {
                dCheck: true,
            }
        }
    }
</script>
Copy the code

: diycheck. sync=”dCheck”

:diyCheck="dCheck"
@update:diyCheck="dCheck = $event"
Copy the code

Syntactic sugar is obvious and greatly simplifies code.

The above code does what you want, but the console has a warning:

[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "diyCheck"

found in

---> <DiyCheckBox> at src/components/DiyCheckBox.vue
       <App>
         <Root>
Copy the code

Avoid changing props directly, use data or computed instead. So let’s optimize it and write it this way:

<template> <div> <my-check-box v-model="comDiyCheck"/> </div> </template> <script> import MyCheckBox from './MyCheckBox'  export default { name: "DiyCheckBox", components: {MyCheckBox}, props: { diyCheck: Boolean, test: String }, computed: { comDiyCheck: { get() { return this.diyCheck }, set(e) { this.$emit('update:diyCheck', e) } } } } </script>Copy the code

$emit = MyCheckBox (); $emit = MyCheckBox (); $emit = MyCheckBox (); $emit = mycheck (); The parent component still has the.sync binding and the code is unchanged.

.sync can not be used to perform bidirectional binding of checkboxes, and can be used to implement bidirectional binding of props.

.syncPass the whole object

If there are many props properties that need to be bidirectionally bound, the tag is long, like this:

<coverage-charge
    v-for="(item, index) in chargingPiles"
    :key="index + 'index'"
    :code.sync="item.code"
    :address.sync="item.address"
    :addressType.sync="item.addressType"
    :kind.sync="item.kind"
    :yearLimitType.sync="item.yearLimitType"
  >
</coverage-charge>
Copy the code

The official document says you can abbreviate it like this:

<text-document v-bind.sync="doc"></text-document>
<! -- our example is: -->
<coverage-charge
    v-for="(item, index) in chargingPiles"
    :key="index + 'index'"
    v-bind.sync='item'
  >
</coverage-charge>
Copy the code

Officials also said:

This passes each property (such as title) in the doc object as a separate prop, and then adds a separate V-on listener for updates.

All five attributes of item are passed to the child component via prop, and I can add different computed attributes to the child component:

<script> export default { name: 'CoverageCharge', props: {}, computed: { code: { get() { return this.code }, set(val) { this.$emit('update:code', val) } }, address: { get() { return this.address }, set(val) { this.$emit('update:address', val) } } ... }} </script>Copy the code

This. Code and this. Address are both undefined, so we need to define the props for the subcomponent.

 props: [ 'code', 'address', 'addressType', 'kind', 'yearLimitType' ]
Copy the code

That’s it..sync is powerful and has a lot of uses.

If there are too many things in the props, you can also define an object and pass in the parent component via v-bind or v-model:

2.1.1 v-bindway

<! -- subcomponent --> props: {zb: {
        type: Object.default: () = >{}}},computed: {
    code: {
      get() {
        return this.zb.code
      },
      set(val) {
        this.$emit('update:code', val)
      }
    },
    address: {
      get() {
        return this.zb.address
      },
      set(val) {
        this.$emit('update:address', val)
      }
    }
}
Copy the code
<! <coverage-charge v-for="(item, index) in chargingPiles" :key="index + 'index'" v-bind.sync='item' :zb='item' > </coverage-charge>Copy the code

2.2.2 v-modelway

<! -- subcomponent --> export default {model: {prop: 'zb', event: 'update'}, props: {zb: {type: Object, default: () => {} } }, computed: { code: { get() { return this.zb.code }, set(val) { this.$emit('update:code', val) } }, address: { get() { return this.zb.address }, set(val) { this.$emit('update:address', val) } } } }Copy the code
<! <coverage-charge v-for="(item, index) in chargingPiles" :key="index + 'index'" v-bind.sync='item' v-model='item' > </coverage-charge>Copy the code

Note that the event value in model is update.

3. v-modeland.syncTo compare

The purpose of the first example in Section 2 above is to wrap the V-Model in another layer, which can be done using the V-Model method as in Section 1. Here only write the main point, not paste the source code:

  • definemodelModule:model: {prop: 'checked', event: 'zhuangbi' }
  • propsDefined in thecheckedattribute
  • computedDefined in theMyChecked: {get(){return this.checked}, set(val) {this.$emit('zhuangbi', val)}}
  • DiyCheckBoxComponents used in:<my-check-box v-model="MyChecked"/>
  • Used in the parent component:<diy-check-box v-model=suibian" />.suibianIs the parent componentdataVariables defined in

Sync is used for bidirectional binding of one props (one tag cannot have more than one V-model), and. Sync is used for bidirectional binding of one props (one tag can have more than one :xxx.sync=yyy).

The source code

Click to view source code