Sử dụng v-model trên component lồng nhau

2618

Cách viết dùng v-model để tự đồng bộ giá trị khi lồng các component

Bạn có component nhận vào prop, muốn sử dụng v-model để nó tự cập nhập giá trị khi có thay đổi

// Address.vue
<template>
    <div>
        <input name="street" v-model="street">
        <input name="city" v-model="city">
        <input name="state" v-model="state">
        <input name="zip" v-model="zip">
    </div>
</template>
<script>
    export default {
        props: ['street', 'city', 'state', 'zip']
    }
</script>

Bạn truyền nó vào như thế này, với hy vọng mọi thứ chạy ngon lành

// Form.vue
<template>
    <form>
        <input name="name" v-model="name">
        <input name="email" v-model="email">
        <mailing-address
            :street="address.street"
            :city="address.city"
            :state="address.state"
            :zip="address.zip"
        />
    </form>
</template>
<script>
    import MailingAddress from './Address.vue';
    export default {
        components: { MailingAddress },
        data() {
            return {
                address: {
                    street: '',
                    city: '',
                    state: '',
                    zip: ''
                }
            }
        }
    }
</script>

Nhưng không 😭 nó sẽ thông báo trong console, “Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. “

Về nguyên tắc, chúng ta ko được thay đổi giá trị của prop, nếu không lúc re-render nó sẽ ko còn đúng nữa

Để nó chạy ngon lành, chúng ta không dùng prop. Khi sử dụng v-model nó làm cho chúng ta 2 việc, bind giá trị vào biến value, gắn handle cho sự kiện v-on:input. Túm lại chúng ta ko cần dùng prop làm gì cả, chỉ việc dùng lùng value bên trong component

// Address.vue
<template>
    <div>
        <input name="street" v-model="value.street">
        <input name="city" v-model="value.city">
        <input name="state" v-model="value.state">
        <input name="zip" v-model="value.zip">
    </div>
</template>
<script>
    export default {
        props: {
            value: {
                type: Object,
                required: true
            }
        },
        watch: {
            value() {
                this.$emit('input', this.value);
            }
        }
    }
</script>

Khi sử dụng

<mailing-address v-model="address" />

Nếu nó thêm một cấp nữa thì sao? Ví dụ bên trong Address.vue chúng ta nhét thêm một component cháu nội của Form nữa

// Address.vue
<template>
    <div>
        ...
        <state v-model="value.state" />
</div>
</template>
<script>
    import State from "./State";
    export default {
        components: { State },
        props: {
            value: {
                type: Object,
                required: true
            }
        },
        watch: {
            value() {
                this.$emit('input', this.value);
            }
        }
    }
</script>

State component

<template>
    <select v-model="value">
        <option v-for="(state, abbreviation) in states"
                :value="abbreviation"
                v-html="state"
        ></option>
    </select>
</template>
<script type="text/babel">
    export default {
        props: {
            value: {
                type: String,
                required: true
            }
        },
        data() {
            return {
                states: {
                    NY: 'New York',
                    WI: 'Wisconsin'
                    // + rest of the states
                }
            }
        }
    }
</script>

Nó sẽ tiếp tục chửi bới chúng ta, vì chúng ta đi thay đổi prop nữa rồi, chúng ta cần đưa nó về computed

// State.vue
<template>
    <select v-model="localState">
        <option v-for="(state, abbreviation) in states"
                :value="abbreviation"
                v-html="state"
        ></option>
    </select>
</template>
<script type="text/babel">
    export default {
        props: {
            value: {
                type: String,
                required: true
            }
        },
        data() {
            return {
                states: {
                    NY: 'New York',
                    WI: 'Wisconsin'
                    // + rest of the states
                }
            }
        },
        computed: {
            localState: {
                get() {return this.value},
                set(localState) { this.$emit('input', localState)}
            }
        }
    }
</script>

Cách này giống như chúng ta dùng controlled component trong React

TopDev via Vuilaptrinh