Vue3数据双向绑定
# Vue 3 数据双向绑定详解
Vue 是一个响应式框架,数据和视图之间的绑定是其核心功能之一。双向绑定就是让数据和视图在变化时保持同步,它使得开发者可以高效地管理和更新用户界面的状态。本文将详细探讨 Vue 2 和 Vue 3 中的数据双向绑定,包括 Options API 和 Composition API 的使用方法,以及 Vue 3 中的一些新特性。
# 1. Vue 2 语法的双向绑定
在 Vue 2 中,数据双向绑定通过 v-model 指令实现。它允许我们绑定数据到表单元素(如 <input>)的 value 属性,同时也能监听用户输入事件,将数据的变化反映到视图中。
以下是 Vue 2 中双向绑定的示例:
<template>
<div>
姓名:<input v-model="userName" /> {{ userName }} <br />
薪资:<input type="number" v-model="salary" /> <button @click="addSalary">薪资加1000</button> {{ salary }}
</div>
</template>
<script lang="ts">
export default {
data() {
return {
userName: "王一",
salary: 15000,
};
},
methods: {
addSalary() {
this.salary += 1000;
},
},
};
</script>
<style scoped></style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
v-model绑定userName和salary到输入框,任何对输入框的修改都会直接更新数据。- 双向绑定通过监听输入框的变化,自动更新数据,同时视图中的数据也会同步更新。
# 2. Options API 和 Composition API
Vue 2 使用的是 Options API,它通过 data、methods、computed 等配置项来组织代码。Vue 3 在保持兼容的基础上,新增了 Composition API,提供了更加灵活的组件编写方式。
# 2.1 Options API
在 Vue 2 中,我们用 data 来声明数据,用 methods 来处理逻辑,数据和视图之间的绑定通过 v-model 来完成。这种方式结构清晰,适合小型项目,但在复杂项目中,逻辑和数据容易耦合,维护起来比较困难。
示例:
<template>
<div>
姓名:<input v-model="userName" /> {{ userName }} <br />
薪资:<input type="number" v-model="salary" /> {{ salary }}
</div>
</template>
<script lang="ts">
export default {
data() {
return {
userName: "王一",
salary: 15000,
};
},
methods: {
addSalary() {
this.salary += 1000;
},
},
};
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 2.2 Composition API
Vue 3 引入了 Composition API,它通过 setup 函数使得数据和逻辑能够在函数中进行组织,避免了传统 Options API 中逻辑过于分散的问题。在 setup 中,我们声明响应式数据和方法,并通过 return 将它们暴露到模板中。
示例:
<template>
<div>
姓名:<input v-model="userName" /> {{ userName }} <br />
薪资:<input type="number" v-model="salary" /> {{ salary }}
</div>
</template>
<script lang="ts">
export default {
setup() {
let userName = "王一";
let salary = 15000;
function addSalary() {
salary += 1000;
console.log("salary = " + salary);
}
return { userName, salary, addSalary };
},
};
</script>
<style scoped></style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
在 Composition API 中,所有的数据和方法都可以被集中管理,组件的逻辑更加灵活,可重用性更高。
# 3. Vue 3 中的数据双向绑定
Vue 3 中的双向绑定机制有了进一步的改进,尤其是在 ref 和 reactive 的使用上,提供了更强大的响应式数据管理能力。
# 3.1 ref 定义基础类型响应式数据
ref 用于声明基础类型的响应式数据,如字符串、数字等。使用 ref 时,它返回的是一个包装对象,实际的数据存储在 value 属性中。为了让数据响应式,必须通过 .value 来访问和修改数据。
import { ref } from 'vue';
let userName = ref("王一"); // 使用ref声明响应式数据
userName.value = "李四"; // 修改数据时使用.value
2
3
4
在模板中,Vue 会自动解包 ref 对象,因此直接使用变量即可。
<template>
<div>
姓名:<input v-model="userName" /> {{ userName }} <br />
</div>
</template>
2
3
4
5
# 3.2 reactive 定义对象型响应式数据
reactive 用于声明对象型的响应式数据。它会返回一个 Proxy 对象,Vue 会自动将对象的每个属性变为响应式。
import { reactive } from 'vue';
let salaryInfo = reactive({ userName: "王一", salary: 15000 });
2
3
对于对象数据,可以直接操作对象的属性,而无需使用 .value:
salaryInfo.userName = "李四";
salaryInfo.salary += 1000;
2
# 3.3 ref 对比 reactive
ref适用于基础数据类型(如字符串、数字等)的响应式声明。reactive适用于对象型数据,能自动使对象的属性变为响应式。
对于基础类型的数据,我们使用 ref;对于对象或数组,我们通常使用 reactive。不过,ref 也可以用来声明对象型数据,这时 Vue 会在内部使用 reactive 来处理。
let salaryInfo = ref({ userName: "王一", salary: 15000 });
salaryInfo.value.userName = "李四"; // 需要通过 .value 来访问
2
# 3.4 标签的 ref 属性
在 Vue 中,ref 既可以用于声明响应式数据,也可以用于访问 DOM 元素。通过 ref 我们可以在模板中获取某个 DOM 元素的引用。
例如:
<template>
<input ref="nameInput" />
<button @click="focusInput">聚焦输入框</button>
</template>
<script setup lang="ts">
import { ref } from 'vue';
let nameInput = ref(null);
function focusInput() {
nameInput.value.focus(); // 访问 DOM 元素并聚焦
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
在上述例子中,ref="nameInput" 会将 <input> 元素与 nameInput 变量绑定。通过 nameInput.value 可以访问到该 DOM 元素。
# 4. 父子组件通信
Vue 3 中的父子组件通信机制通过 props、事件、v-model 和 ref 提供了丰富的数据交互方式。父组件与子组件之间的数据流动和事件传递对于构建复杂交互式应用至关重要。
# 4.1 从父到子的数据传递
# 4.11 使用 props 传递数据
在 Vue 中,父组件通过 props 向子组件传递数据。Vue 3 使用 Composition API 来定义 props,使得数据的传递更加清晰和可维护。父组件通过指定 prop 将数据传递给子组件,子组件则通过访问 props 来接收数据。
父组件 (Parent.vue)
<template>
<Child :title="parentTitle" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import Child from './Child.vue'
const parentTitle = ref('Hello, Vue 3!')
</script>
2
3
4
5
6
7
8
9
10
子组件 (Child.vue)
<script setup lang="ts">
import { defineProps } from 'vue'
const props = defineProps({
title: String
})
</script>
<template>
<h1>{{ props.title }}</h1>
</template>
2
3
4
5
6
7
8
9
10
11
在这个例子中,父组件通过 title prop 将 parentTitle 传递给子组件,子组件使用 props.title 来接收并渲染这个数据。
# 4.12 defineProps 与 props 的对比
- 在 Options API 中,
props是在组件选项中定义的:
<script>
export default {
props: ['title']
}
</script>
2
3
4
5
- 在 Composition API 中,使用
defineProps定义:
<script setup>
import { defineProps } from 'vue'
defineProps({
title: String
})
</script>
2
3
4
5
6
Composition API 的优势在于逻辑更加集中和可复用,特别是当组件的功能较复杂时,defineProps 提供了更灵活的数据接收方式。
# 4.2 从子到父的数据传递
# 4.21 事件发射 (emit) 机制
子组件向父组件传递数据时,Vue 使用事件机制。子组件通过 $emit 或 defineEmits 发射事件,父组件通过 @ 或 v-on 监听这些事件。当事件被触发时,父组件会执行相应的回调函数。
子组件 (Child.vue)
<script setup lang="ts">
import { defineEmits } from 'vue'
const emits = defineEmits(['increment'])
function handleClick() {
emits('increment')
}
</script>
<template>
<button @click="handleClick">Increment</button>
</template>
2
3
4
5
6
7
8
9
10
11
12
13
父组件 (Parent.vue)
<template>
<Child @increment="handleIncrement" />
<p>Count: {{ count }}</p>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import Child from './Child.vue'
const count = ref(0)
function handleIncrement() {
count.value++
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
在上面的示例中,子组件通过 defineEmits 定义 increment 事件,并在按钮点击时发射该事件。父组件通过 @increment="handleIncrement" 监听事件并更新 count 的值。
# 4.22 defineEmits 与 $emit 的对比
- 在 Options API 中,子组件通过
$emit发射事件:
<script>
export default {
methods: {
handleClick() {
this.$emit('increment')
}
}
}
</script>
2
3
4
5
6
7
8
9
- 在 Composition API 中,使用
defineEmits来定义事件并发射:
<script setup>
import { defineEmits } from 'vue'
const emit = defineEmits(['increment'])
function handleClick() {
emit('increment')
}
</script>
2
3
4
5
6
7
8
9
Composition API 提供了更清晰的语法,尤其是在处理多个事件时。
# 4.3 双向绑定:v-model 的实现
v-model 是 Vue 中常用的双向数据绑定机制。它允许父子组件之间同步数据。当子组件的输入发生变化时,父组件的数据会自动更新,反之亦然。
# 4.31 v-model 的基本使用
在 Vue 3 中,v-model 默认绑定到 modelValue prop 和 update:modelValue 事件。通过自定义组件中的 modelValue 和 update:modelValue,我们可以实现父子组件之间的双向绑定。
子组件 (InputField.vue)
<script setup lang="ts">
import { defineProps, defineEmits } from 'vue'
const props = defineProps({
modelValue: String
})
const emits = defineEmits(['update:modelValue'])
function handleInput(event) {
emits('update:modelValue', event.target.value)
}
</script>
<template>
<input :value="props.modelValue" @input="handleInput" />
</template>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
父组件 (Parent.vue)
<template>
<InputField v-model="myValue" />
<p>My Value: {{ myValue }}</p>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import InputField from './InputField.vue'
const myValue = ref('Initial Value')
</script>
2
3
4
5
6
7
8
9
10
11
在这里,父组件通过 v-model 将 myValue 传递给子组件。子组件使用 modelValue prop 获取该值,并通过 update:modelValue 事件将输入框的值传回父组件,实现双向绑定。
# 4.32 v-model 的扩展用法
Vue 3 中,v-model 支持多个绑定。我们可以通过 modelValue 和自定义的事件名称实现多个双向绑定。比如,我们可以将 v-model 用于多个输入字段或自定义组件,并为每个 v-model 定义不同的事件。
<template>
<MyComponent v-model:title="title" v-model:description="description" />
</template>
<script setup>
import { ref } from 'vue'
const title = ref('My Title')
const description = ref('My Description')
</script>
2
3
4
5
6
7
8
9
10
在 MyComponent 组件中,v-model:title 会自动绑定到 modelValue 和 update:title,而 v-model:description 会绑定到 modelValue 和 update:description。
# 4.4 ref 在父子组件通信中的作用
除了 props 和事件机制,ref 也在父子组件通信中扮演着重要角色。通过 ref,父组件可以直接访问子组件的实例,从而调用子组件的方法或访问子组件的属性。这使得父组件与子组件之间的交互更加灵活,尤其是在需要直接操作子组件时。
# 4.41 父组件访问子组件
在 Vue 中,父组件可以通过 ref 获取子组件的引用,并直接调用子组件的方法或访问其属性。这对于需要父组件直接操作子组件的场景非常有用。
子组件 (Child.vue)
<script setup lang="ts">
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
function reset() {
count.value = 0
}
</script>
<template>
<div>Current count: {{ count }}</div>
</template>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
父组件 (Parent.vue)
<template>
<Child ref="childRef" />
<button @click="handleIncrement">Increment from Parent</button>
<button @click="handleReset">Reset from Parent</button>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import Child from './Child.vue'
const childRef = ref(null)
function handleIncrement() {
childRef.value.increment()
}
function handleReset() {
childRef.value.reset()
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
在这个例子中,父组件通过 ref 获取了子组件的引用 childRef,并通过调用 childRef.value.increment() 和 childRef.value.reset() 来操作子组件的方法。
# 4.42 ref 用于自定义数据
除了访问子组件实例外,ref 还可以用来引用普通的数据(例如 DOM 元素或响应式数据),以便在父组件中进行直接操作。
<template>
<div ref="myDiv">This is a div element</div>
<button @click="changeDivText">Change Text</button>
</template>
<script setup>
import { ref } from 'vue'
const myDiv = ref(null)
function changeDivText() {
myDiv.value.textContent = 'Text changed by parent!'
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
在此例中,ref 用于获取 DOM 元素引用,父组件可以直接修改其内容。通过这种方式,ref 可以作为一种简单的手段,让父组件与子组件共享数据或进行交互。