组件 v-model

v-model 可以在组件上使用以实现双向绑定

1. v-model双向绑定

1.1 HTML表单元素v-model双向绑定

  • 原生的表单元素 使用v-model双向绑定

    <input v-model="searchText" />
    
  • 模板编译器会对上面的 v-model 进行如下更冗长的等价展开

    • 将组件实例上的searchText绑定给input的value属性
    • 监听input元素的输入事件,触发对应的监听函数执行,监听函数从输入事件中获取到输入的值,将输入的值更新给组件实例上的searchText,然后由于修改了响应式数据,引发重新渲染。
    <input
      :value="searchText"
      @input="searchText = $event.target.value"
    />
    
示例

在这里插入图片描述

MyInput.vue
<template>

    {{ searchText }}
    <input type="text" v-model="searchText"> <br/>
    
    <!-- 将被展开为如下形式(以下,同上面等价) -->
    <!-- <input type="text" :modelValue="searchText" @input="searchText = $event.target.value"> -->
    
</template>

<script>

export default {

    name: 'MyInput',
    
    data() {
        return {
            searchText:''
        }
    }

}

</script>

<style lang="scss"></style>

1.2 自定义组件实现v-model

  • 可以在一个自定义组件上使用v-model
    <custom-input v-model="searchText"/>
    
  • 当在一个自定义组件上使用v-model时,v-model 会被展开为如下的形式
    • 将父组件实例上的searchText绑定给自定义组件的modelValue属性
    • 在父组件中,监听自定义组件的update:modelValue事件,当自定义组件使用$emit触发update:modelValue事件时(并携带了事件参数),对应的监听函数将执行,并且将事件参数的值更新给父组件实例上的searchText,由于修改了响应式数据,从而引发重新渲染(这个可以验证出来)
    <CustomInput
      :modelValue="searchText"
      @update:modelValue="newValue => searchText = newValue"
    />
    
示例

在这里插入图片描述

MyInput.vue
  • 父组件MyInput将自身的searchTex属性,通过v-model绑定给了自定义组件CustomInput组件。这实际上等价于做了2件事情:
    • 将父组件的searchText属性以props的形式传递给了CustomInput组件的modelValue的prop,
    • 并且给CustomInput组件绑定了对update:modelValue的事件监听,将事件触发时传递过来的事件参数,更新给了父组件MyInput的searchText属性,由于修改了响应式数据,从而引发重新渲染(这个可以验证出来)
<template>

    {{ searchText }}
    
    <custom-input v-model="searchText"/>
    <!-- 将被展开为如下形式(以下,同上面等价) -->
    <!--<custom-input :modelValue="searchText" @update:modelValue="newVal => searchText = newVal"/>-->
    
</template>

<script>

import CustomInput from './CustomInput.vue';

export default {

    name: 'MyInput',
    
    components:{
        CustomInput
    },
    
    data() {
        return {
            searchText:''
        }
    }

}

</script>

<style lang="scss"></style>
CustomInput.vue

子组件中仍然需要监听input事件,通过$emit触发update:modelValue事件,并且,将输入的值作为事件参数传递给事件监听函数

<template>

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

<script>

export default {
    name: 'CustomInput',
    data() {
        return {
        }
    },
    props: ['modelValue']
}

</script>

<style lang="scss"></style>
示例2(验证重新渲染)

在这里插入图片描述

  • 输入3次2,然后出现了如图的效果
  • 每次输入2的时候,子组件通过监听input事件,使用$emit触发update:modelValue事件,并且在输入的值后拼接上1作为事件参数,监听函数则把事件参数更新给了searchText,由于响应式数据发生了变化,所以重新渲染,所以输入框中,又是添加上了1,这证明了,修改了响应式数据后,子组件由于使用了prop,而得到了更新
MyInput.vue
<template>
    {{ searchText }}
    <custom-input v-model="searchText"/>

</template>

<script>
import CustomInput from './CustomInput.vue';
export default {
    name: 'MyInput',
    components:{
        CustomInput
    },
    data() {
        return {
            searchText:''
        }
    }

}

</script>

<style lang="scss"></style>
CustomInput.vue
<template>
    <input type="text" :value="modelValue" @input="$emit('update:modelValue', $event.target.value + 1)">
    {{ modelValue }}
</template>

<script>

export default {
    name: 'CustomInput',
    data() {
        return {
        }
    },
    props: ['modelValue'],
    updated() {
        console.log('updated...');
    }
}

</script>

<style lang="scss"></style>

1.3 使用computed属性实现v-model双向绑定

  • 另一种在组件内实现 v-model 的方式是使用一个可写的,同时具有 getter 和 setter 的 computed 属性。
    • get 方法需返回 modelValue prop,而 set 方法需触发相应的事件:
示例1

在这里插入图片描述

MyInput.vue
<template>

    {{ searchText }}
    
    <custom-input v-model="searchText"/>
     <!-- 将被展开为如下形式(以下,同上面等价) -->
    <!--<custom-input :modelValue="searchText" @update:modelValue="newVal => searchText = newVal"/>-->

</template>

<script>
import CustomInput from './CustomInput.vue';
export default {
    name: 'MyInput',
    components:{
        CustomInput
    },
    data() {
        return {
            searchText:''
        }
    }

}

</script>

<style lang="scss"></style>
CustomInput.vue
  • 子组件CustomInput通过props接收modelValue属性,并将计算属性绑定给了input(计算属性的值来源于getter,即传入的modelValue这个prop)
  • 监听input元素的输入事件,并给计算属性赋值,触发计算属性的setter,在这个setter方法中,触发了update:modelValue事件,并将输入的值传递了过去,并更新给了searchText,然后引发重新渲染。
<template>
    <input type="text" :value="value" @input="value = $event.target.value">
    {{ modelValue }}
</template>

<script>

export default {
    name: 'CustomInput',
    data() {
        return {
        }
    },
    props: ['modelValue'],
    computed: {
        value: {
            get() {
                return this.modelValue
            },
            set(val) {
                this.$emit('update:modelValue', val)
            }
        }
    },
}

</script>

<style lang="scss"></style>
示例2
Test.vue
<template>
   
    <!-- 3. 使用computed属性实现v-model双向绑定 -->
    父组件中: {{ value1 }}<br/>
    子组件中: <custom-input2 v-model="value1"/>

    

</template>

<script lang="ts" setup>
    import { ref,reactive } from 'vue'
   
    import CustomInput2 from '@/views/test/CustomInput2.vue'
    
    const value1 = ref("zzhua")
   
</script>

<style lang="scss">

</style>
CustomInput2.vue
<template>
    <!-- <input v-model="value" /> --> 
    <!-- 以上等价于下方 -->
    <input :value="value" @input="value = $event.target.value"/> 
    {{ modelValue }} - {{ value }}
    <!-- 过程分析: 
            1. 父组件通过v-model指令, 将值通过props的方式传递给了子组件定义的modelValue
            2. 子组件中使用计算属性value, 读此属性返回modelValue, 修改此属性将触发 update:modelValue事件
            3. 当子组件初始化时, 模板被渲染, 拿到父组件传过来的modelValue, 
               模板中用到计算属性value的地方,计算属性value的get将被触发,于是返回modeValue的值
            4. 当修改子组件input框中的内容时, 将会把修改后的内容赋值给计算属性value, 
               计算属性value的set将会触发,将会把修改后的内容交给父组件的update:modelValue事件处理函数,
               从而子组件又开始更新渲染模板,于是跟第三步是一样的
    -->
</template>

<script lang="ts" setup>
import { computed } from 'vue'

const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])

const value = computed({
  get() {
    return props.modelValue
  },
  set(value) {
    emit('update:modelValue', value)
  }
})
</script>

3. v-model的参数

  • 默认情况下,v-model 在组件上都是使用 modelValue 作为 prop,并以 update:modelValue 作为对应的事件。

  • 我们可以通过给 v-model 指定一个参数来更改这些名字,如下:

    <MyComponent v-model:title="bookTitle" />
    
  • 在这个例子中,子组件应声明一个 title 的prop,并通过触发 update:title 事件更新父组件值:

    <!-- MyComponent.vue -->
    <script>
    export default {
      props: ['title'],
      emits: ['update:title']
    }
    </script>
    
    <template>
      <input
        type="text"
        :value="title"
        @input="$emit('update:title', $event.target.value)"
      />
    </template>
    
  • 可以在单个组件实例上,创建多个v-model双向绑定,组件上的每一个 v-model 都会同步不同的 prop,而无需额外的选项:

    <UserName
      v-model:first-name="first"
      v-model:last-name="last"
    />
    
    <!-- UserName组件 -->
    <script>
    export default {
      props: {
        firstName: String,
        lastName: String
      },
      emits: ['update:firstName', 'update:lastName']
    }
    </script>
    
    <template>
      <input
        type="text"
        :value="firstName"
        @input="$emit('update:firstName', $event.target.value)"
      />
      <input
        type="text"
        :value="lastName"
        @input="$emit('update:lastName', $event.target.value)"
      />
    </template>
    

示例

Test.vue
<template>


    <!-- 4. v-model:attr 自定义v-model对应的prop参数名 -->
    <!-- 默认情况下,v-model 在组件上都是使用 modelValue 作为 prop,
         并以 update:modelValue 作为对应的事件。
         我们可以通过给 v-model 指定一个参数来更改这些名字 
    -->
    <CustomInput3 v-model:title="bookTitle" />
   
    <!-- 这等价于如下 -->
	<!--<CustomInput3 :title="bookTitle" @update:title="newVal => bookTitle = newVal"/>-->
	
</template>

<script lang="ts" setup>

    import { ref,reactive } from 'vue'
  
    import CustomInput3 from '@/views/test/CustomInput3.vue';
   
    const bookTitle = ref("bookTitle")
 
</script>

<style lang="scss"></style>
CustomInput3
<template>

    <input type="text" :value="title" @input="$emit('update:title', $event.target.value)" />
    
    {{ title }}
    
</template>

<script setup>
    defineProps(['title'])
    defineEmits(['update:title'])
</script>

5. v-model支持修饰符

在学习输入绑定时,我们知道了 v-model 有一些内置的修饰符,例如 .trim,.number 和 .lazy。在某些场景下,你可能想要一个自定义组件的 v-model 支持自定义的修饰符。

自定义一个v-model修饰符

  • 创建一个自定义的修饰符 capitalize,它会自动将 v-model 绑定输入的字符串值第一个字母转为大写

    <MyComponent v-model.capitalize="myText" />
    
  • 组件的 v-model 上所添加的修饰符,可以通过 modelModifiers prop 在组件内访问到。在下面的组件中,我们声明了 modelModifiers 这个 prop,它的默认值是一个空对象:

    • 注意这里组件的 modelModifiers prop 包含了 capitalize 且其值为 true,因为它在模板中的 v-model 绑定 v-model.capitalize=“myText” 上被使用了。
    • 有了这个 prop,我们就可以检查 modelModifiers 对象的键,并编写一个处理函数来改变抛出的值。在下面的代码里,我们就是在每次 元素触发 input 事件时将值的首字母大写:
    <script>
    
    	export default {
    	
    	  props: {
    	  
    	    modelValue: String,
    	    
    	    modelModifiers: {
    	      default: () => ({})
    	    }
    	    
    	  },
    	  
    	  emits: ['update:modelValue'],
    	  
    	  created() {
    	    console.log(this.modelModifiers) // { capitalize: true }
    	  }
    	}
    </script>
    
    <template>
    
      <input
        type="text"
        
        :value="modelValue"
        
        @input="$emit('update:modelValue', $event.target.value)"
      />
      
    </template>
    

v-model参数 + 修饰符

对于又有参数又有修饰符的 v-model 绑定,生成的 prop 名将是 arg + “Modifiers”。

<MyComponent v-model:title.capitalize="myText">

相应的声明应该是:

export default {
  props: ['title', 'titleModifiers'],
  emits: ['update:title'],
  created() {
    console.log(this.titleModifiers) // { capitalize: true }
  }
}

示例

Test.vue
<template>

    <!-- 5. v-model的修饰符 -->
    <MyComponent v-model.capitalize="myText" />

</template>

<script lang="ts" setup>

    import { ref,reactive } from 'vue'
    
    import MyComponent from '@/views/test/MyComponent.vue';
   
    const myText = ref("myText")


</script>

<style lang="scss">

</style>
MyComponent.vue
<template>
    <input
        type="text"
        :value="modelValue"
        @input="handleInput"
    />
    {{ modelValue }}
</template>

<script lang="ts" setup>
    const props = defineProps({
        modelValue: String,
        modelModifiers: { default: () => ({}) } /* 这个prop可以拿到修饰符 */
    })
    console.log(props.modelModifiers) // { capitalize: true }

    const emit = defineEmits(['update:modelValue'])

    // 处理input事件
    function handleInput(e) {
        let value = e.target.value
        // 根据是否存在修饰符, 作进一步处理
        if(props.modelModifiers.capitalize) {
            value = value.charAt(0).toUpperCase() + value.slice(1)
        }
        emit('update:modelValue', value)
    }

</script>

<style lang="scss">

</style>
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐