01-组件
编写组件
Vue 2.x
<!-- MyComponent.vue -->
<template>
<div>{{ message }}</div>
</template>
<script>
export default {
name: 'MyComponent',
props: {
// 定义props
propMessage: {
type: String,
required: true
}
},
data() {
return {
message: this.propMessage
};
}
};
</script>
在父级页面中使用组件
<!-- ParentComponent.vue -->
<template>
<div>
<!-- 使用子组件 -->
<my-component :prop-message="parentMessage"></my-component>
</div>
</template>
<script>
import MyComponent from './MyComponent.vue';
export default {
name: 'ParentComponent',
components: {
// 注册子组件
MyComponent
},
data() {
return {
parentMessage: 'Hello from Parent'
};
}
};
</script>
Vue 3.x
组件 - 父子组件通信
Vue 3.x 相对于 Vue 2.x 在许多方面都有所改进,包括性能、API 设计以及组件通信等方面。尽管基本的父子组件通信原理在 Vue 3.x 中仍然保持不变,但有一些新的特性和最佳实践可以帮助你更有效地进行组件通信。以下是对 Vue 3.x 中父子组件通信的详细介绍:
1. props 向下传递数据
父组件:
<template>
<div>
<ChildComponent :my-prop="parentData" />
</div>
</template>
<script>
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
setup() {
const parentData = ref('Data from parent');
return { parentData };
}
}
</script>
子组件 (ChildComponent.vue
):
<template>
<div>{{ myProp }}</div>
</template>
<script>
export default {
props: {
myProp: {
type: String,
required: true
}
}
}
</script>
在 Vue 3.x 中,<script setup>
语法糖提供了一种更加简洁和直观的方式来编写组件。它允许你在组件的 <script>
标签内部直接使用 setup
函数内的响应式状态、计算属性、方法等,而无需显式返回它们。这有助于减少模板与逻辑之间的样板代码,使组件代码更加清晰和易于维护。
<template>
<div>{{ myProp }}</div>
</template>
<script setup>
import { ref, defineProps } from 'vue';
// 定义props
const props = defineProps({
myProp: {
type: String,
required: true
}
});
// 使用Composition API
const myProp = ref(props.myProp);
</script>
在 <script setup>
中,无需显式注册子组件。当导入一个组件时,Vue 3 会自动处理组件的注册,所以可以直接在模板中使用 <MyComponent />
。此外,<script setup>
中定义的所有响应式状态和方法都会自动暴露给模板,因此可以直接在模板中引用 parentMessage
。
<template>
<div>
<ChildComponent :my-prop="parentData" />
</div>
</template>
<script setup>
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue';
// 定义响应式状态
const parentData = ref('Hello from Parent in Vue 3');
// 由于使用了 <script setup>,无需显式注册子组件,只需导入即可
// Vue 3 会自动处理组件的注册
</script>
2. 事件向上传递数据
子组件可以通过 $emit
方法触发事件来向父组件发送消息或数据。父组件监听这些事件并处理相应的逻辑。
子组件 (ChildComponent.vue
):
<template>
<button @click="notifyParent">{{ title }}</button>
</template>
<script>
export default {
props: {
title: {
type: String,
required: true,
default: 'Notify Parent' // 如果未传递 title,则使用默认值
}
},
setup(props, { emit }) {
const notifyParent = () => {
emit('childEvent', 'Data from child');
};
return { notifyParent, title }; // 返回 title 以便在模板中使用
}
}
</script>
父组件:
<template>
<div>
<h1>Parent Component</h1>
<ChildComponent :title="parentTitle" @childEvent="handleChildEvent" />
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
data() {
return {
parentTitle: 'Click Me' // 父组件定义的 title 数据
};
},
methods: {
handleChildEvent(data) {
console.log('Child sent data:', data); // 输出: Child sent data: Data from child
}
}
}
</script>
在 Vue 3 的 Composition API 中,setup
函数的第二个参数是一个上下文对象,其中包含了 emit
函数,用于触发事件。这使得在 setup
函数内部可以方便地使用 emit
。
3. 使用 provide
和 inject
进行依赖注入
在 Vue 3 的 Composition API 中,provide
和 inject
是一对用于实现依赖注入的 API。依赖注入是一种设计模式,它允许我们在组件树中的任意位置提供或注入依赖项,而无需显式地在每个组件间传递 props。
provide
provide
函数允许父组件向其所有子组件提供一个值或方法。这个值或方法可以被任何子组件(无论层级多深)通过 inject
来访问。provide
通常在 setup
函数中调用。
import { provide } from 'vue';
export default {
setup() {
const theme = 'dark';
provide('theme', theme);
}
}
在上面的例子中,父组件通过 provide
提供了一个名为 theme
的值 'dark'
。
inject
inject
函数允许组件从它的父级或祖先组件中接收通过 provide
提供的值或方法。inject
也可以在 setup
函数中调用。
import { inject } from 'vue';
export default {
setup() {
const theme = inject('theme', 'light'); // 如果找不到 'theme',则使用默认值 'light'
return { theme };
}
}
在这个例子中,子组件使用 inject
来尝试接收 theme
的值。如果父组件或祖先组件通过 provide
提供了这个值,那么子组件就能接收到它。如果没有找到 theme
,inject
会使用提供的默认值 'light'
。
何时使用 provide
和 inject
provide
和 inject
通常在以下场景中非常有用:
- 跨越多层级的通信:当需要在多层嵌套的组件间传递数据时,使用
provide
和inject
比逐层传递 props 更加方便。 - 插件化或库的开发:当开发一个可重用的库或插件时,
provide
和inject
可以用来提供 API 或状态管理功能,使得使用该库或插件的组件能够轻松地访问这些功能。 - 高级组件模式:在复杂的组件结构中,如高阶组件(HOC)或渲染函数(render functions)中,依赖注入可以帮助实现更加灵活和可维护的代码结构。
注:虽然 provide
和 inject
提供了跨层级通信的能力,但过度使用它们可能会导致代码难以理解和维护。因此,在大多数情况下,优先考虑使用 props 和 events 进行父子组件间的通信,而将 provide
和 inject
作为特殊场景下的解决方案。
4. 使用 Vuex
进行状态管理
Vuex
在 Vue 3 中仍然是一个强大的状态管理库,适用于复杂的 Vue 应用。通过Vuex
可以在一个集中的地方管理应用的状态,并通过 this.$store
或在 Composition API 中使用 useStore
钩子来访问和修改状态。
中央事件总线 bus
在 Vue.js 中,中央事件总线是一个简单的实现,用于在组件之间非父子关系的情况下进行通信。当组件树变得复杂,并且组件之间需要共享数据或触发事件时,中央事件总线可以作为一个桥梁来连接这些组件。
以下是如何在 Vue 中创建和使用中央事件总线的简单示例:
1.创建事件总线
首先,创建一个新的 Vue 实例作为事件总线:
// 在 main.js 或其他全局文件中
import Vue from 'vue';
export const EventBus = new Vue();
2.在组件中触发事件
然后,在任何组件中,使用 $emit
方法来触发一个事件:
// 在某个组件中
import { EventBus } from './main.js';
export default {
methods: {
someMethod() {
EventBus.$emit('some-event', { someData: 'someValue' });
}
}
}
3.在另一个组件中监听事件
在另一个组件中,使用 $on
方法来监听这个事件:
// 在另一个组件中
import { EventBus } from './main.js';
export default {
created() {
EventBus.$on('some-event', (data) => {
console.log(data); // 输出:{ someData: 'someValue' }
});
},
beforeDestroy() {
// 组件销毁前,移除事件监听器,避免内存泄漏
EventBus.$off('some-event');
}
}
4.在不需要时清理事件监听器
重要的一点是,在组件不再需要监听事件时(通常在组件的 beforeDestroy
或 destroyed
生命周期钩子中),应该使用 $off
方法来清理事件监听器,以避免潜在的内存泄漏。
尽管中央事件总线在某些情况下可能很有用,但它也有其局限性。例如,它可能导致代码难以维护和理解,特别是当事件和监听器分散在多个组件中时。因此更倾向于使用更现代、更结构化的通信方法,如 Vuex
(用于状态管理)或 provide/inject
(用于父子组件之间的数据传递)。
在 Vue.js 中,ref
是一个特殊的属性,它用于为 DOM 元素或子组件注册引用信息。这样,你可以直接访问这些元素或组件实例,并在 JavaScript 中进行操作。Vue 2.x 和 Vue 3.x 在 ref
的使用上有一些差异,主要是由于 Vue 3.x 引入了 Composition API 和一些内部机制的改进。
ref
Vue 2.x 中的 ref
<template>
<div ref="myDiv">Hello World</div>
<ChildComponent ref="childComponent" />
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
mounted() {
console.log(this.$refs.myDiv); // 输出 DOM 元素
console.log(this.$refs.childComponent); // 输出子组件实例
}
}
</script>
Vue 3.x 中的 ref
在 Vue 3.x 中,由于引入了 Composition API,ref
的使用方式发生了显著变化。现在ref
是一个函数,可以在 setup
函数中使用它来创建响应式数据。
使用 ref
创建响应式数据
<template>
<div>{{ count }}</div>
<button @click="increment">Increment</button>
<div ref="myDiv">Hello World</div>
</template>
<script>
import { ref, onMounted } from 'vue';
export default {
setup() {
const count = ref(0);
const increment = () => {
count.value++;
};
const myDiv = ref(null);
onMounted(() => {
console.log(myDiv.value); // 输出 DOM 元素
});
return { count, increment, myDiv };
},
};
</script>
使用 ref
创建的响应式数据被返回并在模板中使用时,不需要在模板中加 .value
来访问它的值。Vue 会自动处理这部分。但在 JavaScript 代码中(例如在 setup
函数内部或计算属性中),需要通过 .value
来访问或修改它的值。
是否可以通过ref
进行元素的修改
在 Vue.js 中,虽然 ref
可以用来获取 DOM 元素或子组件的引用,但直接修改这些元素并不是明智的做法
- 响应式问题:直接修改 DOM 元素不会触发 Vue 的响应式更新。如果修改依赖于 Vue 的状态,并且这些状态变化应该反映在视图中,那么应该使用 Vue 的数据和方法来更新状态,而不是直接操作 DOM。
- 生命周期问题:确保在正确的生命周期钩子中访问和操作 DOM 元素。在 Vue 2 中,这通常是在
mounted
钩子中;在 Vue 3 中,可以使用onMounted
。 - 可维护性:直接操作 DOM 可能会使代码更难理解和维护。尽量避免在 Vue 组件中直接操作 DOM,除非绝对必要。
props能否修改
props是单向数据流,意味着父组件可以通过props将数据传递给子组件,但子组件不能直接修改props中的数据。这是因为props的主要目的是让父组件能够控制子组件的状态和行为,如果允许子组件修改props,那么父组件对子组件的控制力将会大大减弱,可能导致状态管理的混乱。
然而,如果子组件确实需要根据某些条件改变某些值,并希望这些变化能够反映到父组件中,那么可以通过以下几种方式实现:
- 使用事件触发机制:子组件可以通过
$emit()
方法触发父组件中定义的事件,并传递新的数据。父组件在接收到事件后,可以调用一个方法来更新它自己的状态,并通过props
将新的数据传递给子组件。这样,子组件虽然不能直接修改props
,但可以通过触发事件来通知父组件更新数据。 - 使用计算属性或方法:在子组件中,可以根据
props
的值计算出新的值,或者通过方法来处理props
的值。这样,虽然props
本身没有被修改,但子组件可以根据props
的值得到需要的结果。 - 使用.sync修饰符:在Vue 2.x中,可以使用
.sync
修饰符来创建一个“双向绑定”的prop
。这实际上是一种语法糖,它允许子组件触发一个更新事件来修改父组件中的prop
值。这仍然是通过事件触发机制实现的,而不是直接修改props
。
Vue2.x中使用.sync示例
在 Vue 2.x 中,.sync
修饰符提供了一种简洁的方式来实现父子组件之间的“双向绑定”效果,尽管它并不是真正的双向绑定(Vue 的 props 是单向的)。.sync
修饰符允许子组件通过触发一个特定的事件来修改父组件中的 prop 值。
以下是一个 .sync
修饰符的示例来说明其使用方法:
父组件
<template>
<div>
<child-component :value.sync="parentValue"></child-component>
<p>Parent value: {{ parentValue }}</p>
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
data() {
return {
parentValue: 'Initial Value'
};
}
}
</script>
子组件
<template>
<div>
<button @click="updateValue">Update Parent Value</button>
</div>
</template>
<script>
export default {
props: ['value'],
methods: {
updateValue() {
// 使用 $emit 触发一个 'update:value' 事件,并传递新的值
this.$emit('update:value', 'New Value from Child');
}
}
}
</script>
父组件通过 .sync
修饰符将 parentValue
作为 prop
传递给子组件。子组件接收到这个 prop
并显示一个按钮。当按钮被点击时,updateValue
方法会被调用,该方法使用 $emit
触发一个 update:value
事件,并传递一个新的值 'New Value from Child'
。
由于父组件使用了.sync
修饰符,当子组件触发 update:value
事件时,Vue 会自动将父组件的 parentValue
更新为子组件传递的新值。因此,父组件中的 parentValue
会被更新,并且父组件的模板中显示的值也会相应地改变。
.sync
修饰符在 Vue 3 中已经被移除。在 Vue 3 中通过 v-model
或者自定义事件和 props 来实现类似的功能。对于需要“双向绑定”的场景,使用 v-model
,而对于其他场景,则可以直接通过 $emit
和 props 进行数据交互。
Vue3.x中使用v-model示例
子组件
<template>
<div>
<input type="text" :value="name" @input="updateName">
<input type="number" :value="age" @input="updateAge">
</div>
</template>
<script>
export default {
props: {
name: {
type: String,
default: ''
},
age: {
type: Number,
default: 0
}
},
emits: ['update:name', 'update:age'],
methods: {
updateName(event) {
this.$emit('update:name', event.target.value);
},
updateAge(event) {
this.$emit('update:age', parseInt(event.target.value, 10));
}
}
}
</script>
在这个子组件中,定义了两个props
:name
和age
,分别用于接收父组件传递的名字和年龄值。同时,定义了两个emits
事件:update:name
和update:age
,用于向父组件发送更新后的值。每个input
元素都绑定到相应的prop
上,并监听input
事件来触发对应的更新方法。
父组件
<template>
<div>
<child-component
v-model:name="parentName"
v-model:age="parentAge"
></child-component>
<p>Parent name: {{ parentName }}</p>
<p>Parent age: {{ parentAge }}</p>
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
data() {
return {
parentName: 'John Doe',
parentAge: 30
};
}
}
</script>
在父组件中,使用v-model:name
和v-model:age
来分别绑定子组件的name
和age
属性到父组件的parentName
和parentAge
数据属性上。这样,当子组件中的input
元素的值发生变化时,它会触发相应的update
事件,并更新父组件中的对应数据属性。同样地,如果父组件中的parentName
或parentAge
发生变化,它们也会自动更新到子组件的相应prop
中。
通过为v-model
指定不同的属性名,可以在自定义组件中实现多个参数的双向绑定。这种方法比使用.sync
修饰符更加清晰和灵活,并且符合Vue 3的设计哲学。