Vue
刀刀
3/20/2025
0 字
0 分钟
组件
概念
组件就是可复用的 vue
文件( Vue
实例, 封装标签, 样式和 JS
代码)。
组件化 :封装的思想,把页面上 可重用的部分
封装为 组件
,从而方便项目的 开发 和 维护
一个页面, 可以拆分成一个个组件,一个组件就是一个整体, 每个组件可以有自己独立的 结构 样式 和 行为( html
, css
和 js
)
使用
创建组件,封装代码
引入组件,注册组件
全局注册:
main.js
中vue.compnent(自定义组件名, 组件对象)
。js// 目标: 全局注册 (一处定义到处使用) // 1. 创建组件 - 文件名.vue // 2. 引入组件 import Pannel from './components/Pannel' // 3. 全局 - 注册组件 Vue.component("PannelG", Pannel)
局部注册:某个
vue
文件中,components:{ 自定义组件名: 组件对象 }
。jsimport Pannel from './components/Pannel_1' export default { // 3. 局部 - 注册组件 components: { PannelL: Pannel } }
使用组件
把注册的组件名当成标签名使用在结构中即可。
scoped
作用
scpoed
添加到 style
标签上的属性;作用:使得样式只作用于当前文件的标签上。
组件通信
因为每个组件的变量和值都是独立的。
父: 使用其他组件的vue文件。父组件中,通过标签属性的方式将数据传递给子组件。
子: 被引入的组件(嵌入)。子组件中,props
定义变量,用于接收父组件传递过来的参数。
父传子
关键代码:
props
步骤:
在文件夹
components
下创建子组件Product.vue
。html<template> <div class="my-product"> <h3>标题: {{ title }}</h3> <p>价格: {{ price }}元</p> <p>{{ intro }}</p> </div> </template>
组件内在
props
定义变量, 用于接收外部传入的值。vue<script> export default { props: ['title', 'price', 'intro'] } </script>
App.vue
中引入注册组件, 使用时, 传入具体数据给组件显示。vue<template> <div> <Product title="好吃的口水鸡" price="50" intro="开业大酬宾, 全场8折"></Product> <Product title="好可爱的可爱多" price="20" intro="老板不在家, 全场1折"></Product> <Product title="好贵的北京烤鸭" price="290" :intro="str"></Product> </div> </template> <script> // 引入组件 import Product from './components/MyProduct' export default { data(){ return { str: "好贵啊, 快来啊, 好吃" } }, // 3. 注册组件 components: { Product // Product: Product // key和value变量名同名 - 简写 } } </script>
整体步骤概况如下图所示。
父向子-配合循环
把数据循环分别传入给组件内显示。每次循环,变量和组件互不影响。
子组件代码:
<template>
<div class="my-product">
<h3>标题: {{ title }}</h3>
<p>价格: {{ price }}</p>
<p>{{ intro }}</p>
</div>
</template>
<script>
export default {
props: ["title", "price", "intro"],
};
</script>
父组件代码
<template>
<div>
<myproduct
v-for="item in list"
:key="item.id"
:title="item.proname"
:price="item.proprice"
:intro="item.info"
></myproduct>
</div>
</template>
<script>
import myproduct from "./components/myProduct.vue";
export default {
data() {
return {
list: [
{
id: 1,
proname: "超级好吃的棒棒糖",
proprice: 18.8,
info: "开业大酬宾, 全场8折",
},
{
id: 2,
proname: "超级好吃的大鸡腿",
proprice: 34.2,
info: "好吃不腻, 快来买啊",
},
{
id: 3,
proname: "超级无敌的冰激凌",
proprice: 14.2,
info: "炎热的夏天, 来个冰激凌了",
},
],
};
},
components: { myproduct },
};
</script>
父向子-单向数据流
从父到子的数据流向,叫单向数据流 ,在 vue
中需要遵循单向数据流原则。
父组件的数据发生了改变,子组件会自动跟着变。
子组件不能直接修改父组件传递过来的
props
数据。注意:
关于子组件不能直接修改父组件传递过来的
props
数据中,指的是不能修改其引用的地址(因为是数组类型的数据,属于复杂数据类型),比如用map
方法或者filter
方法修改了原数组返回了新数组并覆盖了原数据,此时就是改变了引用地址。jsthis.arr = this.arr.filter((item) => item.id != id);
但是如果仅仅改变了数组内的值,其地址没有发生变化,则可以让子组件改变
props
内的数据。如splice
、push
等方法。jslet index = this.arr.findIndex((item) => item.id == id); this.arr.splice(index, 1);
原因:
子组件修改了不通知父组件,会造成数据不一致。因此
vue
规定,props
是只读的。
父组件传给子组件的是一个对象,子组件修改对象的属性,是不会报错的,对象是引用类型, 互相更新
总结: 所以
props
变量本身是不能重新赋值的。
子传父
子组件中:在恰当时机(一般是触发某个数据时候)通过 $emit
自定义事件,把值传递给父组件。
父组件中:使用子组件标签上绑定自定义事件来接收数据。
步骤:
子:
js@自定义事件名="methods函数" methods函数(){ this.$emit(自定义名称,索引或id,数值) }
- 参数1:自定义的名称,可随便更换。
- 参数2:由父传子获得的索引或
id
。 - 参数3:变量或数值。
父:
js自定义名称="自定义函数名" 自定义函数名(参数1,参数2){}
- 参数1:接收子组件传递过来的索引或
id
。 - 参数2:接收子组件传递过来的数值。
- 参数1:接收子组件传递过来的索引或
整体流程如下所示:
子组件:
html<div class="my-product"> <h3>标题: {{ title }}</h3> <p>价格: {{ price }}</p> <p>{{ intro }}</p> <button @click="kanFn">砍价</button> </div> </template> <script> export default { props: ["title", "price", "intro", "index"], methods: { kanFn() { this.$emit("subprice", this.index, 2); }, }, }; </script>
父组件
html<template> <div> <h1>hello world</h1> <aa></aa> <bb></bb> <myproduct v-for="(item, index) in list" :key="item.id" :title="item.proname" :price="item.proprice" :intro="item.info" :index="index" @subprice="fn" ></myproduct> </div> </template> <script> import myproduct from "./components/myProduct.vue"; export default { data() { return { list: [ { id: 1, proname: "超级好吃的棒棒糖", proprice: 18.8, info: "开业大酬宾, 全场8折", }, { id: 2, proname: "超级好吃的大鸡腿", proprice: 34.2, info: "好吃不腻, 快来买啊", }, { id: 3, proname: "超级无敌的冰激凌", proprice: 14.2, info: "炎热的夏天, 来个冰激凌了", }, ], }; }, methods: { // 这里的参数是由子组件中的this.$emit中第二第三个参数传来的 fn(index, price) { this.list[index].proprice = this.list[index].proprice - price; }, }, components: { myproduct }, }; </script>
总结: 父自定义事件和方法, 等待子组件触发事件给方法传值。
跨组件通信
作用:让非父子的组件能够使用通用的组件进行通讯:事件总线(event-bus
)。
核心语法: 在src文件夹下创建一个 EventBus
的文件夹,里面放一个 index.js
文件,用于定义事件总线 bus
对象。
import Vue from 'vue'
// 导出空白vue对象
export default new Vue()
创建一个子组件 a.vue
。
引入事件总线 /EventBus/index.js
,为其绑定事件,触发事件后用 $emit()
方法传递参数。
- 参数1:自定义方法名称。
- 参数2:变量。
<template>
<div>
<h1>我是a</h1>
<button @click="give">点我</button>
</div>
</template>
<script>
import eventBus from "../EventBus/index.js";
export default {
data() {
return {
msg: "我是a",
};
},
methods: {
give() {
eventBus.$emit("sendata", this.msg);
},
},
};
</script>
创建一个子组件 b.vue
。
引入事件总线 /EventBus/index.js
,创建好 b.vue
组件后立即监听,使用的方法是创建一个 created
函数,在其之内用 $on
方法来监听自定义事件方法,返回回调函数。
<template>
<div>
<h1>我是b</h1>
</div>
</template>
<script>
import eventBus from "../EventBus/index.js";
export default {
created() {
eventBus.$on("sendata", (val) => {
console.log(val);
});
},
};
</script>
在 App.vue
组件中引入两个子组件。
<template>
<div>
<aa></aa>
<bb></bb>
</div>
</template>
<script>
import aa from "./components/a.vue";
import bb from "./components/b.vue";
export default {
components: { aa, bb },
};
</script>
整体流程如下所示。
总结: 空的Vue对象, 只负责on注册事件, emit触发事件, 一定要确保$on先执行
拓展
原生事件绑定到组件
<base_input @input.navite="inputFn"></base_input>
如上有一个 base_input
的子组件,想要使用原生 input
框自带的输入事件,可使用 .navite
修饰符。但是当该子组件根节点不是 input
输入框而是 label
(如下),则加了修饰符也不会生效。
<label>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
>
</label>
因此官方特地提供了一个 $listeners
,它是一个对象,里面包含了作用在这个组件上的所有监听器。例如:
{
focus: function (event) { /* ... */ }
input: function (value) { /* ... */ },
}
有了这个 $listeners
,可以配合 v-on="$listeners"
将所有的事件监听器指向这个组件的某个特定的子元素。对于类似 <input>
的你希望它也可以配合 v-model
工作的组件来说,为这些监听器创建一个类似下述 inputListeners
的计算属性通常是非常有用的:
Vue.component('base-input', {
inheritAttrs: false,
props: ['label', 'value'],
computed: {
inputListeners: function () {
var vm = this
// `Object.assign` 将所有的对象合并为一个新对象
return Object.assign({},
// 我们从父级添加所有的监听器
this.$listeners,
// 然后我们添加自定义监听器,
// 或覆写一些监听器的行为
{
// 这里确保组件配合 `v-model` 的工作
input: function (event) {
vm.$emit('input', event.target.value)
}
}
)
}
},
template: `
<label>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
v-on="inputListeners"
>
</label>
`
})
现在 <base-input>
组件是一个完全透明的包裹器了,也就是说它可以完全像一个普通的 <input>
元素一样使用了:所有跟它相同的 attribute 和监听器都可以工作,不必再使用 .native
监听器。