vue 双向绑定(v-model 双向绑定、.sync 双向绑定、.sync 传对象)

·  阅读 326

1. v-model实现自定义组件双向绑定

v-model其实是个语法糖,如果没按照相应的规范定义组件,直接写v-model是不会生效的。再说一遍,类似于v-on:click可以简写成@clickv-model是两个表达式合在一起的简写。记住这个,下面具体说明。

1.1 input双向绑定

子组件MyInput.vue

<template>
    <div>输入
        <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>
复制代码

父组件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>
复制代码

<my-input v-model="haha" />是一个语法糖(简写),它相当于:

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

onInput(e) {
    this.haha = e
}
复制代码

两者效果一样,v-model减少了很多代码,不用你手动把传过来的值赋值给haha了,少定义了一个方法。作用还是挺明显的。

MyInput组件中的value不可以叫别的名字,$emit中抛出的方法也只能叫input ,否则都不可以用v-model语法糖,用了也没啥效果。
另外,获取 input 输入值的的方式是:$event.target.value,写成方法的话,$event实参省略了。

1.2. checkbox双向绑定

到了checkbox这,又有点不一样,首先,它绑定的值不叫value,触发事件也不叫input,具体看代码。

子组件MyCheckBox代码:

<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>
复制代码

父组件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>
复制代码

除了绑定的value变成了checked@input变成了@change,传到父组件的参数是e.target.checked

==注意:== 绝大多数例子中,$emit中抛出的事件都是change,我这里写的是zhuangbi,其实只要和model模块中event属性值一样即可

此外,外面的props也不可少。

2. .sync方式实现双向绑定

如果需要对一个prop进行双向绑定,可以使用.sync语法糖。举一个使用场景的例子:别人封装好的 CheckBox 组件,需要做一些样式修改或者功能组合再使用,这就需要对 v-model 的值再来一次双向绑定。拿上面的 MyCheckBox 来说,<my-check-box v-model="checked"/>,给这个checked传值可以用 props,但想把checked的值传给父组件并赋值给props的值,就有点麻烦,需要定义一个方法,使用$emit,父组件监听事件并作赋值操作。现在用.sync可以一句搞定。

子组件DiyCheckBox代码:

<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>
复制代码

父组件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>
复制代码

:diyCheck.sync="dCheck"这句代码相当于:

:diyCheck="dCheck"
@update:diyCheck="dCheck = $event"
复制代码

语法糖作用很明显,大大简化了代码。

上面代码可以实现想要的功能,只是控制台会有一个警告:

[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>
复制代码

避免直接改变props,使用data or computed代替。所以优化一下,换个写法:

<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>
复制代码

使用计算属性后,@change事件都可以不要了,get()就是获取props传值,set(e)MyCheckBoxv-model值改变了会触发的方法,这里面做法是直接把改变后的值通过$emit方式发出去。父组件中仍然通过.sync绑定,代码没有变化。

.sync可不光能用来做checkbox的双向绑定,涉及到props双向绑定的场景都可以用sync实现。

.sync传整个对象

如果有许多props属性需要做双向绑定操作,标签写起来就很长,像这样:

<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>
复制代码

官方文档说可以简写成这样:

<text-document v-bind.sync="doc"></text-document>
<!--对应我们的例子就是:-->
<coverage-charge
    v-for="(item, index) in chargingPiles"
    :key="index + 'index'"
    v-bind.sync='item'
  >
</coverage-charge>
复制代码

官方还说:

这样会把 doc 对象中的每一个 property (如 title) 都作为一个独立的 prop 传进去,然后各自添加用于更新的 v-on 监听器。

按照这个说法,item 的 5 个属性,都会通过 prop 传到子组件,我再在子组件中添加不同的computed属性即可:

<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>
复制代码

真这样写,会发现,this.codethis.address这些都是undefined,这里需要在子组件的props中再定义一遍属性名才行,可以省略各属性的typedefault值:

 props: [ 'code', 'address', 'addressType', 'kind', 'yearLimitType' ]
复制代码

这样就可以了,.sync功能强大,用的地方也挺多的。

props里面东西太多的话,也可以统一定义成一个对象,然后父组件通过v-bind或者v-model传进来:

2.1.1 v-bind 方式

<!--子组件-->
 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)
      }
    }
}
复制代码
<!--父组件-->
<coverage-charge
    v-for="(item, index) in chargingPiles"
    :key="index + 'index'"
    v-bind.sync='item'
    :zb='item'
  >
</coverage-charge>
复制代码

2.2.2 v-model方式

<!--子组件-->
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)
          }
        }
    }
}
复制代码
<!--父组件-->
<coverage-charge
    v-for="(item, index) in chargingPiles"
    :key="index + 'index'"
    v-bind.sync='item'
    v-model='item'
  >
</coverage-charge>
复制代码

注意modelevent值是update

3. v-model.sync比较

上面第 2 节第一个例子目的就是把v-model再包一层,照着第 1 节,用v-model的方法也能做到。这里只写主要点,不贴源码了:

  • 定义model模块:model: {prop: 'checked', event: 'zhuangbi' }
  • props中定义checked属性
  • computed中定义MyChecked: { get(){ return this.checked},set(val) { this.$emit('zhuangbi', val) } }
  • DiyCheckBox组件中使用:<my-check-box v-model="MyChecked"/>
  • 父组件中使用:<diy-check-box v-model=suibian" />suibian是父组件data中定义的变量

可见,v-model适用于双向绑定单个props(一个标签中不能有多个v-model),.sync适用于双向绑定一到多个props(一个标签中允许使用多个:xxx.sync=yyy)。

源码

点击查看源码

分类:
前端
标签:
分类:
前端
标签: