vue自定義組件實現v-model(含vue3)


v-model原理

image

<input type="text" v-model="age">
<input type="text" v-bind="age" v-on:input="age = $event.target.value">

v-model的原理就是: v-bind 和 v-on的語法糖

vue2組件雙向綁定

第一種: v-bind(★)

原理: 子組件通過監聽父組件數據,子組件改變數據之后通知給父組件

錯誤寫法: 不可以直接修改props的值

image

正確寫法

父組件

// Users.vue 
<template>
  <div>
    <Son :ageValue="age" @changeInput="changeInput"/>
    <el-button @click="age = Math.floor(Math.random()*10)">添加</el-button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      age: ''
    }
  },
  methods: {
    changeInput(val) {
      this.age = val
    }
  }
}
</script>

子組件

// Son.vue
<template>
  <div>
    <input type="text" v-model="sonAge" @input="changeInput">
  </div>
</template>

<script>
export default {
  props: {
    ageValue: {
      typeof: String
    }
  },
  data() {
    return {
      sonAge: ''
    }
  },
  methods: {
    changeInput() {
      this.$emit('changeInput', this.sonAge)
    }
  },

  /*
   為什么要監聽:
   因為父組件傳遞過來屬性, 可能有默認值,
   子組件的input需要根據默認值回顯,或者別的地方需要
  */
  watch: {
    ageValue: {
      immediate: true, // 立即執行 :當刷新頁面時會立即執行一次handler函數
      handler(val) {
        this.sonAge = val
      }
    }
  }
}
</script>

第二種.sync修飾符(★★)

原理:
.sync:名字 是自己起的, 通過update:名字進行觸發對象的事件
update:是vue為我們約定好的名稱部分
image
父組件

// Users.vue
<template>
  <div>
    <Son :ageValue.sync="age" />
    <el-button @click="age = Math.floor(Math.random()*10)">添加</el-button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      age: ''
    }
  },
  methods: {
  }
}
</script>

子組件

// Son.vue
<template>
  <div>
    <input type="text" v-model="sonAge" @input="changeInput">
  </div>
</template>

<script>
export default {
  props: {
    ageValue: {
      typeof: String
    }
  },
  data() {
    return {
      sonAge: ''
    }
  },
  methods: {
    changeInput() {
      // this.$emit('changeInput', this.sonAge)
      // 這樣父組件內的值也同時被更改,省略了監聽事件這一步
      this.$emit('update:ageValue', this.sonAge)
    }
  },
  watch: {
    ageValue: {
      immediate: true, // 立即執行 :當刷新頁面時會立即執行一次handler函數
      handler(val) {
        this.sonAge = val
      }
    }
  }
}
</script>

第三種 v-model(★★★)

原理: 通過 model新屬性: 配置一個 props:接受的屬性, 和一個事件名
image

// Users.vue
<template>
  <div>
    <Son v-model="age" />
    <el-button @click="age = Math.floor(Math.random()*10)">添加</el-button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      age: ''
    }
  }
}
</script>

子組件

// Son.vue
<template>
  <div>
    <input type="text" v-model="sonAge" @input="changeInput">
  </div>
</template>

<script>
export default {
  props: {
    value: {
      typeof: String
    }
  },
  data() {
    return {
      sonAge: ''
    }
  },
  // 超級牛
  model: {
    prop: 'value',
    event: 'change'
  },
  methods: {
    changeInput() {
      this.$emit('change', this.sonAge)
    }
  },
  watch: {
    value: {
      immediate: true, // 立即執行 :當刷新頁面時會立即執行一次handler函數
      handler(val) {
        this.sonAge = val
      }
    }
  }
}
</script>

image

vue3組件雙向綁定

第一種(★★)

這里子組件input 用的是 :value 和 @input
父組件

// Users.vue
<template>
  <div class="user-wrap">
    <Son v-model="message" />
    <h1>{{ message }}</h1>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue'
import Son from './son.vue'
export default defineComponent({
  name: 'user',
  components: {
    Son
  },
  setup() {
    let message = ref('')
    return {
      message,
    }
  }
})
</script>

子組件

// Son.vue
<template>
  <div>
    <input type="text" :value="modelValue"
      @input="$emit('update:modelValue', $event.target.value)"
    />
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
  props: ['modelValue'],
  emits: ['update:modelValue'],
})
</script>

第二種: 通過computed計算屬性(★★★)

這里用的是 v-model ,精簡了 :vlaue 和@input
父組件

// Users.vue
<template>
  <div class="user-wrap">
  	<!-- 兩個方法等價 -->
    <!-- <Son :modelValue="message" @update:modelValue="message = $event" /> -->
    <Son v-model="message" />
    <h1>{{ message }}</h1>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue'
import Son from './son.vue'
export default defineComponent({
  name: 'user',
  components: {
    Son
  },
  setup() {
    let message = ref('')
    return {
      message,
    }
  }
})
</script>

子組件

// Son.vue
<template>
  <div>
    <!-- 兩個方法等價 -->
    <!-- <input type="text" :value="newValue" @input="newValue = $event.target.value" /> -->
    <input type="text" v-model="newValue" />
  </div>
</template>

<script lang="ts">
import { defineComponent, computed } from 'vue'
export default defineComponent({
  props: ['modelValue'],
  emits: ['update:modelValue'],
  setup(props, { emit }) {
    const newValue = computed({
      // 子組件v-model綁定 計算屬性, 一旦發生變化, 就會給父組件傳遞值
      get: () => props.modelValue,
      set: (nv) => {
        emit('update:modelValue', nv)
      }
    })
    return {
      newValue
    }
  }
})
</script>

第三種: 組件綁定多個v-model

父組件

// Users.vue
<template>
  <div class="user-wrap">
    <!-- 這里綁定兩個v-model -->
    <Son v-model="message" v-model:title="title" />
    <h1>message:{{ message }}</h1>
    <h1>title:{{ title }}</h1>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue'
import Son from './son.vue'
export default defineComponent({
  name: 'user',
  components: {
    Son
  },
  setup() {
    let message = ref('')
    let title = ref('')

    return {
      message,
      title,
    }
  }
})
</script>

子組件

// Son.vue
<template>
  <div>
    <!-- 兩個方法等價 -->
    <!-- <input type="text" :value="newValue" @input="newValue = $event.target.value" /> -->
    <input type="text" v-model="newValue" />
    -
    <input type="text" v-model="newTitle" />
  </div>
</template>

<script lang="ts">
import { defineComponent, computed } from 'vue'
export default defineComponent({
  props: {
    // v-model默認的名字
    modelValue: {
      type: String
    },
    title: {
      //這里可以直接使用 v-model:title ,:號后面的名字
      type: String
    }
  },
  emits: ['update:modelValue', 'update:title'],
  setup(props, { emit }) {
    const newValue = computed({
      get: () => props.modelValue,
      set: (nv) => {
        console.log(nv)
        emit('update:modelValue', nv)
      }
    })

    const newTitle = computed({
      get: () => props.title,
      set: (nv) => {
        emit('update:title', nv)
      }
    })

    return {
      newValue,
      newTitle
    }
  }
})
</script>

vue2 對比 vue3的 v-model區別

vue2在組件中這樣設置:

父組件

<ChildComponent v-model = "title">

子組件

export default {
  model: {
    prop: 'title', // v-model綁定的屬性名稱
    event: 'change' // v-model綁定的事件
  },
  props: {
    value: String, // value跟v-model無關
    title: { // title是跟v-model綁定的屬性
      type: String,
      default: 'Default title'
    }
  },
  methods: {
    handle() {
      // 這里的 change, 對應 event
      this.$emit('change', 'xxx')
    }
  }
}

vue3在組件中這樣設置

父組件

<!-- 兩個方法等價 -->
<Son v-model="message" />
<!-- <Son :modelValue="message" @update:modelValue="message = $event" /> -->

子組件

export default defineComponent({
  props: {
    modelValue: {
      type: String
    }
  },
  emits: ['update:modelValue'],
  setup(props, { emit }) {
    const newValue = computed({
      get: () => props.modelValue,
      set: (nv) => {
        console.log(nv)
        emit('update:modelValue', nv)
      }
    })

    return {
      newValue
    }
  }
})

總結:

vue2:

  1. v-model: 會把 value 用作 prop 且把 input 用作 event;
  2. 可以通過 .sync修飾符 指定傳遞名字
  3. 支持model: 可以指定v-model的 value屬性名 和 event事件名字

組件v-model原理:

<Son v-model="age" />
<Son :value="age"  @change="age = $event" />

vue3:

  1. v-model: 不在綁定 value 而是 modelValue, 接受方法也不再是 input 而是 update:modelValue
  2. 組件支持多個 v-model, 並且可以指定名字 v-model:名字

組件v-model原理:

<Son v-model:value="age" />
<Son :modelValue="age" @update:modelValue="age = $event" />


免責聲明!

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



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