Vue - 组件

本文详细介绍了Vue2.x和Vue3.x中组件的编写、父子组件间的通信方式,包括props传递、事件驱动、provide/inject依赖注入以及Vuex状态管理。同时对比了两者在ref和v-model的使用差异,强调了单向数据流原则和CompositionAPI的优化。
摘要由CSDN通过智能技术生成

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. 使用 provideinject 进行依赖注入

在 Vue 3 的 Composition API 中,provideinject 是一对用于实现依赖注入的 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 提供了这个值,那么子组件就能接收到它。如果没有找到 themeinject 会使用提供的默认值 'light'

何时使用 provideinject

provideinject 通常在以下场景中非常有用:

  1. 跨越多层级的通信:当需要在多层嵌套的组件间传递数据时,使用 provideinject 比逐层传递 props 更加方便。
  2. 插件化或库的开发:当开发一个可重用的库或插件时,provideinject 可以用来提供 API 或状态管理功能,使得使用该库或插件的组件能够轻松地访问这些功能。
  3. 高级组件模式:在复杂的组件结构中,如高阶组件(HOC)或渲染函数(render functions)中,依赖注入可以帮助实现更加灵活和可维护的代码结构。

:虽然 provideinject 提供了跨层级通信的能力,但过度使用它们可能会导致代码难以理解和维护。因此,在大多数情况下,优先考虑使用 props 和 events 进行父子组件间的通信,而将 provideinject 作为特殊场景下的解决方案。

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.在不需要时清理事件监听器

重要的一点是,在组件不再需要监听事件时(通常在组件的 beforeDestroydestroyed 生命周期钩子中),应该使用 $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 元素或子组件的引用,但直接修改这些元素并不是明智的做法

  1. 响应式问题:直接修改 DOM 元素不会触发 Vue 的响应式更新。如果修改依赖于 Vue 的状态,并且这些状态变化应该反映在视图中,那么应该使用 Vue 的数据和方法来更新状态,而不是直接操作 DOM。
  2. 生命周期问题:确保在正确的生命周期钩子中访问和操作 DOM 元素。在 Vue 2 中,这通常是在 mounted 钩子中;在 Vue 3 中,可以使用 onMounted
  3. 可维护性:直接操作 DOM 可能会使代码更难理解和维护。尽量避免在 Vue 组件中直接操作 DOM,除非绝对必要。
props能否修改

props是单向数据流,意味着父组件可以通过props将数据传递给子组件,但子组件不能直接修改props中的数据。这是因为props的主要目的是让父组件能够控制子组件的状态和行为,如果允许子组件修改props,那么父组件对子组件的控制力将会大大减弱,可能导致状态管理的混乱。

然而,如果子组件确实需要根据某些条件改变某些值,并希望这些变化能够反映到父组件中,那么可以通过以下几种方式实现:

  1. 使用事件触发机制:子组件可以通过$emit()方法触发父组件中定义的事件,并传递新的数据。父组件在接收到事件后,可以调用一个方法来更新它自己的状态,并通过props将新的数据传递给子组件。这样,子组件虽然不能直接修改props,但可以通过触发事件来通知父组件更新数据。
  2. 使用计算属性或方法:在子组件中,可以根据props的值计算出新的值,或者通过方法来处理props的值。这样,虽然props本身没有被修改,但子组件可以根据props的值得到需要的结果。
  3. 使用.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>

在这个子组件中,定义了两个propsnameage,分别用于接收父组件传递的名字和年龄值。同时,定义了两个emits事件:update:nameupdate: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:namev-model:age来分别绑定子组件的nameage属性到父组件的parentNameparentAge数据属性上。这样,当子组件中的input元素的值发生变化时,它会触发相应的update事件,并更新父组件中的对应数据属性。同样地,如果父组件中的parentNameparentAge发生变化,它们也会自动更新到子组件的相应prop中。

通过为v-model指定不同的属性名,可以在自定义组件中实现多个参数的双向绑定。这种方法比使用.sync修饰符更加清晰和灵活,并且符合Vue 3的设计哲学。

  • 28
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值