vue 雙向綁定(v-model 雙向綁定、.sync 雙向綁定、.sync 傳對象)


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)。

源碼

點擊查看源碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM