前言
- 单向绑定:就是把
Model绑定到View,当我们用JavaScript代码更新Model时,View就会自动更新。因此,我们不需要进行额外的DOM操作,只需要进行Modal的操作就可以实现试图的联动更新 - 双向绑定:在单向绑定的基础上,用户更新了
View,Model的数据自动被更新了,这种情况就是双向绑定。所以,当我们用JavaScript代码更新Model时,View就会自动更新,反之,如果用户更新了View,Model的数据也自动被更新了。
一、单向绑定
实现思路:
- 所有数据只有一份
- 一旦数据变化,就去更新页面(只有data->DOM,没有DOM->data)
- 若用户在页面上做了更新,就需要用户手动收集
1.1 插值语法:v-text
-
参数类型:
String -
示例:
<span v-text="msg"></span> <!-- 等同于 --> <span>{{msg}}</span>
1.2 属性绑定:v-bind
-
参考:
-
参数类型:any - 不限制数据类型,基础类型或复杂类型都可以
-
作用:动态地绑定一个或多个 attribute,或一个组件 prop 到表达式。
-
修饰符:
-
.prop
v-bind 默认绑定到 DOM 节点的 attribute 上,使用 .prop 修饰符后,会绑定到 property
-
作用/用途:
1)通过自定义属性存储变量,避免暴露数据
2)防止污染 HTML 结构
-
-
.camel
-
支持版本:2.1.0+
-
作用/用途:
1)将命名变为驼峰命名法
-
-
.sync
-
支持版本:2.3.0+
-
描述:Vue的组语法
-
作用/用途:
1)子组件直接更新父组件的值
-
示例:
<!-- 父组件 --> <template> <div> <ChildCompontent :groupNames.sync="groupNames" /> <div> </template> <!-- 子组件 --> <template> <div> <button @click="updated">点击</button> <div> </template> export default { props: { groupNames: String, }, methods: { updated() { this.$emit("update:groupNames", "张三,李四,王麻子") }, }, }
-
-
-
示例:
<!-- 绑定一个 attribute --> <img v-bind:src="imageSrc"> <!-- 等同于 --> <img :src="imageSrc">
二、双向绑定
2.1 双向绑定的原理
我们都知道Vue是数据双向绑定的框架,双向绑定由三个重要部分构成:
- 数据层(Model):应用的数据及业务逻辑
- 视图层(View):应用的展示效果,各类UI组件
- 业务逻辑层(ViewModel):框架封装的核心,它负责将数据与视图关联起来
而上面的这个分层的架构方案,可以用一个专业术语进行称呼MVVM
这里的控制层的核心功能便是“数据双向绑定”。自然,我们只需弄懂它是什么,便可以进一步了解数据绑定的原理。
2.2 理解ViewModel
- 主要职责:
- 数据变化后更新视图
- 视图变化后更新数据
- 主要组成:
- 监听器(Observer):对所有数据的属性进行监听
- 解析器(Compiler):对每个元素节点的指令进行扫描跟解析,根据指令模板替换数据,以及绑定相应的更新函数
2.3 双向绑定的流程
1)new Vue()首先执行初始化,对data执行响应化处理,这个过程发生在 监听器(Observer) 中;
2)同时对模板执行编译(Compile),找到其中动态绑定的数据,从data中获取并初始化视图,这个过程发生在 解析器(Compiler) 中;
3)同时定义一个更新函数和Watcher,将来对应数据变化时Watcher会调用更新函数;
4)由于data的某个key在一个视图中可能出现多次,所以每个key都需要一个管家Dep来管理多个Watcher
5)将来data中数据一旦发生变化,会首先找到对应的Dep,通知所有Watcher执行更新函数。
流程图如下:
2.4 双向绑定的方法:v-model
2.4.1 v-model的介绍和用法
- 作用/用途:实现表单元素的数据双向绑定
- 用法:
-
v-model在原生元素上的用法:<input v-model="searchText" />在代码背后,模板编译器会对
v-model进行更冗长的等价展开。因此上面的代码其实等价于下面这段代码:<input :value="searchText" @input="searchText = $event.target.value" /> -
自定义组件多个参数的双向绑定
-
vue2:
在vue2中,一个标签有且只有一个
v-model,因此只能实现一个数据的双向绑定,而vue2.3+中新增了一个修饰符.sync,可以让我们在自定义组件中对多个参数进行双向绑定。// FatherComponent.vue <template> <CustomInput v-model:firstName="firstName" :lastName.sync="lastName" /> </template> <script> import CustomInput from "./customInput"; export default { name: 'FatherComponent', components: { CustomInput, }, watch: { firstName(val) { console.log("firstName:::", val); }, lastName(val) { console.log("lastName:::", val); }, }, data() { return { firstName: "张三", lastName: "李四" } }, } </script>// customInput.vue <template> <div> <input placeholder="Basic usage" v-model="inputValue1" @input="handleInput1" /> <input placeholder="Basic usage" v-model="inputValue2" @input="handleInput2" /> </div> </template> <script> export default { name: "customInput", props: { firstName: String, lastName: String, }, model: { prop: 'firstName', event: 'input', }, data() { return { inputValue1: this.firstName, inputValue2: this.lastName, }; }, methods: { handleInput1(e) { this.$emit("input", e.target.value); }, handleInput2(e) { this.$emit('update:lastName', e.target.value); } }, } </script> -
vue3:多个
v-model绑定 - vue3
vue3中的v-model像是提取vue2中的.sync和v-model的优势结合,使用起来更加便捷。
我们可以在单个组件实例上创建多个
v-model双向绑定组件上的每一个
v-model都会同步不同的prop,而无需额外的选项:<UserName v-model:first-name="first" v-model:last-name="last" /><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> <script> export default { data() { return { childValue: "", } }, props: { firstName: String, lastName: String }, } </script> -
-
2.4.2 v-model 修饰符
v-model有一些内置的修饰符,例如.trim,.number和.lazy。当然,我们也是可以自定义修饰符的。
-
内置修饰符
.trim():默认自动去除用户输入内容中两端的空格<input v-model.trim="msg" />.number:将输入内容自动转换为数字。- 如果该值无法被
parseFloat()处理,那么将返回原始值。 number修饰符会在输入框有type="number"时自动启用。- 是在输入框失去焦点时,将非数字删除
<input v-model.number="age" />- 如果该值无法被
.lazy:改变v-moel的语法糖,将更新数据时机放置于change事件后。默认情况是,v-model会在每次input事件后更新事件<!-- 在 "change" 事件后同步更新而不是 "input" --> <input v-model.lazy="msg" />
-
自定义修饰符
【特别注意】:自定义修饰符,Vue3中支持!!!Vue2不支持!!!
<MyComponent v-model.capitalize="myText" />组件的
v-model上所添加的修饰符,可以通过modelModifiersprop 在组件内访问到。在下面的组件中,我们声明了modelModifiers这个 prop,它的默认值是一个空对象:<script setup> const props = defineProps({ modelValue: String, modelModifiers: { default: () => ({}) } }) defineEmits(['update:modelValue']) console.log(props.modelModifiers) // { capitalize: true } </script> <template> <input type="text" :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" /> </template>注意这里组件的
modelModifiersprop 包含了capitalize且其值为true,因为它在模板中的v-model绑定v-model.capitalize="myText"上被使用了。有了这个 prop,我们就可以检查
modelModifiers对象的键,并编写一个处理函数来改变抛出的值。在下面的代码里,我们就是在每次<input />元素触发input事件时将值的首字母大写:<script setup> const props = defineProps({ modelValue: String, modelModifiers: { default: () => ({}) } }) const emit = defineEmits(['update:modelValue']) function emitValue(e) { let value = e.target.value if (props.modelModifiers.capitalize) { value = value.charAt(0).toUpperCase() + value.slice(1) } emit('update:modelValue', value) } </script> <template> <input type="text" :value="modelValue" @input="emitValue" /> </template>参数的 v-model 修饰符: 对于又有参数又有修饰符的 v-model 绑定,生成的 prop 名将是
arg + "Modifiers"。举例来说:<UserName v-model:first-name.capitalize="first" v-model:last-name.uppercase="last" /><script setup> const props = defineProps({ firstName: String, lastName: String, firstNameModifiers: { default: () => ({}) }, lastNameModifiers: { default: () => ({}) } }) defineEmits(['update:firstName', 'update:lastName']) console.log(props.firstNameModifiers) // { capitalize: true } console.log(props.lastNameModifiers) // { uppercase: true} </script>
2.4.3 自定义组件的 v-model
-
-
给自定义组件绑定一个
v-model:// faterComponent.vue <template> <div id="app"> <CustomInput v-model:modelValue="modelValue" /> </div> </template> <script> import CustomInput from './customInput.vue' export default { name: 'app', components: { CustomInput }, data() { return { modelValue: "" } }, } </script> // customInput.vue <template> <input placeholder="Basic usage" v-model="inputValue" @input="handleInput" /> </template> <script> export default { props: { modelValue: String, }, // 声明v-model的相关属性信息 model: { prop: 'modelValue', // 声明哪个数据是v-model使用的 event: 'input', // 声明哪个事件可以触发v-model这个语法糖 }, data() { return { inputValue: this.modelValue, }; }, methods: { handleInput(e) { this.$emit("input", e.target.value); } }, } </script> -
给自定义组件绑定多个
v-model: 在vue2中,一个标签有且只有一个v-model,因此只能实现一个数据的双向绑定,而vue2.3+中新增了一个修饰符.sync,可以让我们在自定义组件中对多个参数进行双向绑定。// FatherComponent.vue <template> <CustomInput v-model:firstName="firstName" :lastName.sync="lastName" /> </template> <script> import CustomInput from "./customInput"; export default { name: 'FatherComponent', components: { CustomInput, }, watch: { firstName(val) { console.log("firstName:::", val); }, lastName(val) { console.log("lastName:::", val); }, }, data() { return { firstName: "张三", lastName: "李四" } }, } </script>// customInput.vue <template> <div> <input placeholder="Basic usage" v-model="inputValue1" @input="handleInput1" /> <input placeholder="Basic usage" v-model="inputValue2" @input="handleInput2" /> </div> </template> <script> export default { name: "customInput", props: { firstName: String, lastName: String, }, model: { prop: 'firstName', event: 'input', }, data() { return { inputValue1: this.firstName, inputValue2: this.lastName, }; }, methods: { handleInput1(e) { this.$emit("input", e.target.value); }, handleInput2(e) { this.$emit('update:lastName', e.target.value); } }, } </script>
-
-
Vue3:多个
v-model绑定 - vue3-
给自定义组件绑定一个
v-model:<CustomInput v-model="searchText" /><!-- CustomInput.vue --> <script setup> defineProps(['modelValue']) defineEmits(['update:modelValue']) </script> <template> <input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" /> </template> -
给自定义组件绑定多个
v-model:vue3中的v-model像是提取vue2中的.sync和v-model的优势结合,使用起来更加便捷。
我们可以在单个组件实例上创建多个
v-model双向绑定组件上的每一个
v-model都会同步不同的prop,而无需额外的选项:<UserName v-model:first-name="first" v-model:last-name="last" /><script setup> defineProps({ firstName: String, lastName: String }) defineEmits(['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>
-