文章目录
1. 组件使用(引入(全局|局部)组件)
1. 全局引入
- 在main.js文件中引入并注册
import ChildrenComponent from '@/views/components/ChildrenComponent '
Vue.component('ChildrenComponent ',ChildrenComponent )// 第一个参数 全局组件的名字(字符串类型),第二个参数:引入的组件名(一般都与组件名保持一致)
之后就可以全局使用组件了
2. 局部引入
- 在父组件中引入
import ChildrenComponent from '@/views/components/ChildrenComponent'
export default {
components: {
ChildrenComponent,
},
}
之后就可以在父组件中使用组件了
3. 页面使用
<ChildrenComponent ></ChildrenComponent >
<!-- 或 -->
<children-component></children-component>
2. props的写法
1. 字符串数组形式
```
props: ['title', 'likes', 'isPublished', 'commentIds', 'author']
```
2. 指定prop值类型
```
props: {
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object,
callback: Function,
contactsPromise: Promise // or any other constructor (其他构造函数)
}
```
3. 指定 prop 的验证要求
- 当 prop 验证失败的时候,(开发环境构建版本的) Vue 将会产生一个控制台的警告。
```
props: {
// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
propA: Number,
// 多个可能的类型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required: true
},
// 带有默认值的数字
propD: {
type: Number,
default: 100
},
// 带有默认值的对象
propE: {
type: Object,
// 对象或数组默认值必须从一个工厂函数获取
default: function () {
return { message: 'hello' }
}
},
// 数组类型
propE2: {
type: Array,
// 对象或数组默认值必须从一个工厂函数获取
default: function () {
return []
}
},
// 自定义验证函数
propF: {
validator: function (value) {
// 这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
},
```
- 注意: prop 会在一个组件实例创建之前进行验证,所以实例的 property (如 data、computed 等) 在 default 或 validator 函数中是不可用的。
4.备注(sync修饰符,native修饰符)
-
一般情况下,子组件不能直接修改从父组件接收的属性值,否则会报错,如果子组件需要接收值后处理再使用,可以将接收的值赋值给子组件本身的属性,如data中的属性或计算属性。
-
如果希望子组件prop父组件中的值改变时,将变化同步到父组件中,可使用事件监听或**.sync修饰符**,sync修饰符是一个语法糖,本质上等同于事件监听的方法
-
父组件
<h1>父组件title值:{{ title }}</h1> <ChildrenComponent :title.sync="title"></ChildrenComponent >
-
子组件
<template> <div class="ChildrenComponent "> <h1>子组件</h1> <input type="text" v-model="childTitle" /> </div> </template> <script> export default { props: ["title"], data() { return {}; }, computed: { childTitle: { get() { return this.title; }, set(val) { this.$emit("update:title", val);//更新父组件中的title }, }, } }; </script>
-
效果:当子组件中input内容改变时,父组件中的title会同步改变
-
-
在一个组件的根元素上直接监听一个原生事件,需要使用native修饰符
div id="app"> <!-- 直接绑定原生事件无效 --> <!-- <child @click="show"></child> --> <!-- 添加native修饰符 --> <child @click.native="show"></child> </div>
3. 父子组件通信
1.父->子组件传递数据的注意事项
- 每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值
- 不应该在一个子组件内部改变 prop。如果这样做了,Vue会在浏览器的控制台中发出警告。 可以先把该值赋给子组件自己的变量,然后去更改复制后的变量
- 如果你传进来的是个对象,同时你又需要在子组件中操作传进来的这个数据,那么在父组件中的这个数据也会改变,因为你传递的只是个引用, 你只能对对象做深拷贝创建一个副本才能继续操作
2. 父子组件相互传递数据,子组件触发父组件方法
<template>
<div>
<h1>父组件</h1>
//使用子组件
<child-component :message="message" @test-event="handleEvent"></child-component>
</div>
</template>
<script>
// 引用子组件
import ChildComponent from './ChildComponent.vue';
export default {
components: {
// 声明子组件
ChildComponent
},
data() {
return {
message: '这是父组件的数据'
}
},
methods: {
handleEvent(data) {
console.log('父组件接收到子组件触发的事件,数据为:', data);
}
},
}
</script>
- 代码中在父组件中定义了一个名为 message 的数据,并通过 :message=“message” 的方式将其传递给子组件。
- 父组件可以通过自定义事件的方式向子组件传递事件。子组件可以通过 $emit 方法触发父组件定义的事件。
- 代码中在父组件中通过 @test-event=“handleEvent” 的方式监听子组件触发的 test-event 事件,并定义一个名为 handleEvent 的方法来处理事件。
<template>
<div>
<h2>子组件</h2>
<p>{{ message }}</p>
<button @click="emitEvent">点击触发事件</button>
</div>
</template>
<script>
export default {
props: ['message'],
methods: {
emitEvent() {
this.$emit('test-event', '这是子组件传递的数据');
}
}
}
</script>
- 在子组件中,可以通过 props: [‘message’] 的方式声明一个名为 message 的属性,用于接收父组件传递的数据。
- 在子组件中,可以通过 this.$emit(‘test-event’, ‘这是子组件传递的数据’) 的方式触发父组件定义的 test-event 事件,并传递一些数据给父组件。
3. 使用 $refs (父组件访问子组件的数据和方法)
- 使用时需在调用子组件时给子组件定义一个 ref 名
//父组件:
<ChildComponent ref="childComponent "></ChildComponent >
<button @click="getChildData">点击获取子组件数据</button>
getChildData: function () {
let child = this.$refs.childComponent //获取子组件实例
console.log(child.value);//访问子组件属性
child.childFn() //调用子组件的childFn()方法
},
- 注意:
- $refs 只会在组件渲染完成之后生效,并且它们不是响应式的。这仅作为一个用于直接操作子组件的“逃生舱”——你应该避免在模板或计算属性中访问 $refs。
- 由于ref 需要在dom渲染完成后才会有,在使用的时候确保dom已经渲染完成。比如在生命周期 mounted(){} 钩子中调用,或者在 this.$nextTick(()=>{}) 中调用。
4.使用$children:(父组件访问子组件的数据和方法)
- 获取到一个包含所有子组件(不包含孙子组件)的 VueComponent 对象数组,可以直接拿到子组件中所有数据和方法等。
- c h i l d r e n 和 children和 children和parent 并不保证顺序,也不是响应式的,只能拿到一级父组件和子组件。
// Parent.vue
export default{
mounted(){
this.$children[0].someMethod() // 调用第一个子组件的方法
this.$children[0].name // 获取第一个子组件中的属性
}
}
5. 使用$parent(子组件访问父组件的数据和方法)
子组件:
<button @click="getParentData">点击获取父组件数据</button>
getParentData(){
let parent = this.$parent //获取父组件实例
console.log(parent.parentValue) //访问父组件属性
parent.parentFn() //调用父组件的方法parentFn()
}
6.使用v-model进行双向绑定数据传递
- 在父组件中,我们定义一个变量作为数据源,并将其传递给子组件。在子组件中,我们接受这个数据源,并将其绑定到一个输入框上。当用户在输入框中输入内容时,这个变量将会被更新,同时也会更新父组件中的数据。
<!-- 父组件 -->
<template>
<div>
<h1>父组件</h1>
<p>父组件的数据:{{data}}</p>
<ChildComponent v-mode:value="data"></ChildComponent>
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
data() {
return {
data: ''
};
}
};
</script>
<!-- 子组件 -->
<template>
<div>
<h2>子组件</h2>
<input type="text" v-model:value="value">
<p>子组件的数据:{{value}}</p>
</div>
</template>
<script>
export default {
props: ['value']
};
</script>
-
在这个例子中,我们在父组件中使用了ChildComponent,并通过v-model指令将data传递给了子组件。在子组件中,我们用v-model指令将value绑定到了一个输入框上,同时展示了当前的value值。当用户在输入框中输入内容时,value将会被更新,同时也会更新父组件中的data。
-
这是v-model的魔力所在:父组件和子组件之间的数据变化是双向的。无论是从父组件到子组件,还是从子组件到父组件,数据都将保持同步。
4.兄弟组件通信
- 思路: 把兄弟组件共享的数据定义在父组件,A组件通过子传父的方式,向父组件传值,父组件再通过父向子的方式,向B组件传值。
5. 全局事件总线的使用
- EventBus 是中央(全局)事件总线,不管是父子组件,兄弟组件,跨层级组件等都可以使用它完成通信操作。
- 声明一个全局Vue实例变量 EventBus , 把所有的通信数据,事件监听都存储到这个变量上;
- 类似于 Vuex。但这种方式只适用于极小的项目;
- 原理就是利用on和emit 并实例化一个全局 vue 实现数据共享;
- 可以实现平级,嵌套组件传值,但是对应的事件名eventTarget必须是全局唯一的;
1. 方法一 直接挂载到全局
-
main.js中安装事件总线
//引入Vue import Vue from 'vue' //引入App import App from './App.vue' //关闭Vue的生产提示 Vue.config.productionTip = false //创建vm new Vue({ el:'#app', render: h => h(App), beforeCreate() { Vue.prototype.$bus = this //安装全局事件总线 }, })
-
发送数据 (组件A)
<template> <div class="student"> <h2>学生姓名:{{name}}</h2> <h2>学生性别:{{sex}}</h2> <button @click="sendStudentName">把学生名给School组件</button> </div> </template> <script> export default { name:'Student', data() { return { name:'张三', sex:'男', } }, mounted() { // console.log('Student',this.x) }, methods: { sendStudentName(){ this.$bus.$emit('hello',this.name) } }, } </script> <style lang="less" scoped> .student{ background-color: pink; padding: 5px; margin-top: 30px; } </style>
-
接收数据(组件B)
<template> <div class="school"> <h2>学校名称:{{name}}</h2> <h2>学校地址:{{address}}</h2> </div> </template> <script> export default { name:'School', data() { return { name:'测试', address:'测试地址', } }, mounted() { // console.log('School',this) this.$bus.$on('hello',(data)=>{ console.log('我是School组件,收到了数据',data) }) }, beforeDestroy() { this.$bus.$off('hello') }, } </script> <style scoped> .school{ background-color: skyblue; padding: 5px; } </style>
-
总结(4个步骤)
-
main.js中安装全局事件总线
-
组件A中用this.$ bus.$emit(‘事件名’,val)发送要传的数据
-
组件B中用this.$ bus.$on(‘事件名’,callback)监听(接收)A传过来的数据
-
组件B中this.$ bus.$off(‘事件名’)移除事件监听(销毁全局事件总线)
-
2. 方法二 抽离成一个单独的 js 文件 Bus.js ,然后在需要的地方引入
- 创建一个event_bus.js 文件
import Vue from 'vue' export const EventBus = new Vue()
- 具体使用
// 在需要向外部发送自定义事件的组件内 <template> <button @click="handlerClick">按钮</button> </template> import { EventBus } from '@/event/event_bus.js' export default{ methods:{ handlerClick(){ // 自定义事件名 sendMsg EventBus.$emit("sendMsg", "这是要向外部发送的数据") } } } // 在需要接收外部事件的组件内 import { EventBus } from '@/event/event_bus.js' export default{ mounted(){ // 监听事件的触发 EventBus.$on("sendMsg", data => { console.log("这是接收到的数据:", data) }) }, beforeDestroy(){ // 取消监听 EventBus.$off("sendMsg") } }
6. $ attrs和$listener 实现步骤
- 主要用于孙组件获取父组件的属性和方法。
- 多级组件嵌套需要传递数据时,通常使用的方法是通过vuex。如果仅仅是传递数据,而不做中间处理,使用 vuex 处理,这就有点大材小用了。所以就有了 $attrs / $listeners ,通常配合 inheritAttrs 一起使用。
- nheritAttrs:默认值为 true。
- inheritAttrs:true 继承除props之外的所有属性;inheritAttrs:false 只继承class属性
- 默认情况下父作用域的不被认作 props 的 attribute 绑定 (attribute bindings) 将会“回退”且作为普通的 HTML attribute 应用在子组件的根元素上。当撰写包裹一个目标元素或另一个组件的组件时,这可能不会总是符合预期行为。通过设置 inheritAttrs 到 false,这些默认行为将会被去掉。而通过 (同样是 2.4 新增的) 实例 property $attrs 可以让这些 attribute 生效,且可以通过 v-bind 显性的绑定到非根元素上。
父组件的代码:
<template>
<div>
<child-component :foo="foo" :bar="bar"></child-component >
</div>
</template>
<script>
import ChildComponent from "../components/ChildComponent /ChildComponent .vue";
export default {
components: {
ChildComponent ,
},
data() {
return {
foo: "foo",
bar: "bar",
};
},
};
</script>
子组件的代码:
<template>
<div>
<p>foo:{{ foo }}</p>
</div>
</template>
<script>
export default {
props: ["foo"],
};
</script>
1.$attrs
$ attrs:包含了父作用域中不被认为 (且不预期为) props 的特性绑定 (class 和 style 除外),并且可以通过 v-bind=”$attrs” 传入内部组件。当一个组件没有声明任何 props 时,它包含所有父作用域的绑定 (class 和 style 除外)。
2.$listener
$ listeners:包含了父作用域中的 (不含 .native 修饰符) v-on 事件监听器。它可以通过 v-on=”$listeners” 传入内部组件。它是一个对象,里面包含了作用在这个组件上的所有事件监听器,相当于子组件继承了父组件的事件。
父组件:
<template>
<child :name="name" :age="age" :infoObj="infoObj" @updateInfo="updateInfo" @delInfo="delInfo" />
</template>
<script>
import Child from '../components/child.vue'
export default {
name: 'father',
components: { Child },
data () {
return {
name: 'mary',
age: 25,
infoObj: {
from: '杭州',
job: 'IT',
hobby: ['reading', 'writing', 'hopping']
}
}
},
methods: {
updateInfo() {
console.log('update info');
},
delInfo() {
console.log('delete info');
}
}
}
</script>
子组件:
<template>
<grand-son :height="height" :weight="weight" @addInfo="addInfo" v-bind="$attrs" v-on="$listeners" />
// 通过 $listeners 将父作用域中的事件,传入 grandSon 组件,使其可以获取到 father 中的事件
</template>
<script>
import GrandSon from '../components/grandSon.vue'
export default {
name: 'child',
components: { GrandSon },
props: ['name'],
data() {
return {
height: '160cm',
weight: '50kg'
};
},
created() {
console.log(this.$attrs);
// 结果:age, infoObj, 因为父组件共传来name, age, infoObj三个值,由于name被 props接收了,所以只有age, infoObj属性
console.log(this.$listeners); // updateInfo: f, delInfo: f
},
methods: {
addInfo () {
console.log('add info')
}
}
}
</script>
孙子组件:
<template>
<div>
{{ $attrs }} --- {{ $listeners }}
<div>
</template>
<script>
export default {
... ...
props: ['weight'],
created() {
console.log(this.$attrs); // age, infoObj, height
console.log(this.$listeners) // updateInfo: f, delInfo: f, addInfo: f
this.$emit('updateInfo') // 可以触发 father 组件中的updateInfo函数
}
}
</script>
7. provide / inject
1.背景
-
通常,当我们需要从父组件向子组件传递数据时,我们使用 props。想象一下这样的结构:有一些深度嵌套的组件,而深层的子组件只需要父组件的部分内容。在这种情况下,如果仍然将 prop 沿着组件链逐级传递下去,可能会很麻烦。
-
对于这种情况,我们可以使用一对 provide 和 inject。无论组件层次结构有多深,父组件都可以作为其所有子组件的依赖提供者。这个特性有两个部分:父组件有一个 provide 选项来提供数据,子组件有一个 inject 选项来开始使用这些数据。
2.provide
- 允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。
3.inject
- 获取祖先组件提供的依赖,并注入到当前组件中使用。形式可以是一个字符串数组,也可以为一个对象。
4.类型
provide:Object | () => Object
inject:Array | { [key: string]: string | Symbol | Object }
5.注意事项
- provide是在祖先组件中提供注入一个依赖,这个依赖可以是一个变量,可以是一个函数方法。那么,显然易得的是,当祖先组件失活销毁(如:祖先组件销毁,路由改变等)时,那么后代子孙组件自然而然无法获取祖先组件提供的依赖。
6. 例子
-
假设有一个组件A,A组件引入B组件(A为B的父组件) ,B组件引入C组件(B为C的父组件),即A为C的祖先组件,此时二者可以使用provide / inject进行通信。
-
A与C使用provide / inject方式进行通信
A使用provide
<template> <div> <B></B> </div> </template> <script> import B from "./B.vue"; export default { name: "A", components: { B }, provide:{ name:this.name }, data(){ return { name:"mary" } }, }; </script>
C使用inject
<template> <div> <span>{{name}}</span> </div> </template> <script> export default { name: "C", inject:["name"] }; </script>
我们希望当name改变时,对应的C中的name也要相应改变,但是使用以上方式时,C中的name并未随着改变,此时需要我们进一步处理,即处理响应性。
处理响应性
- A使用provide,此时传入的应是一个响应式对象(如以下的obj)
<template> <div> <B></B> </div> </template> <script> import B from "./B.vue"; export default { name: "A", components: { B }, provide(){ return { obj:this.obj //传入一个响应式对象 } }, data(){ return { obj:{ name:"tom" } } }, methods:{ changeName(){ this.obj.name = "mary" } } }; </script>
- C使用inject
<template> <div> <span>{{obj.name}}</span> </div> </template> <script> export default { name: "C", inject:["obj"] //接收响应式对象 }; </script>
-
此时A中的name改变,C中的值也会相应跟着变化。
以上为A向C传数据,如果C向A传数据(或者说C需要改变A中的数据),该如何做?
我们这里不让C直接改变A中的数据,而是将A改变数据的方法通过provide传给C,C执行该方法,触发改变A中的数据。A使用provide传入一个方法
<template> <div> <span>{{obj.name}}</span> <B></B> </div> </template> <script> import B from "./B.vue"; export default { name: "A", components: { B }, provide(){ return { changeVal:this.changeName //传入一个方法 } }, data(){ return { obj:{ name:"mary" } } }, methods:{ changeName(val){ //C中触发该方法执行,此时变成"lion" this.obj.name = val } } }; </script>
C使用inject
<template> <div> <span @click="changeName">点击改变A组件数据</span> </div> </template> <script> export default { name: "C", inject:["changeVal"], //接收一个方法 methods:{ changeName(){ this.changeVal("tom") //执行此方法,改变A中的数据 } } }; </script>
8. 消息订阅与发布(pubsub)
-
一种组件间通信的方式,适用于任意组件间通信。
-
使用步骤:
-
安装pubsub:
npm i pubsub-js
-
引入:
import pubsub from 'pubsub-js'
-
-
提供数据:
pubsub.publish('xxx',数据)
```c <template> <div class="student"> <h2>学生姓名:{{name}}</h2> <h2>学生性别:{{sex}}</h2> <button @click="sendStudentName">把学生名给School组件</button> </div> </template> <script> import pubsub from 'pubsub-js' export default { name:'Student', data() { return { name:'张三', sex:'男', } }, mounted() { // console.log('Student',this.x) }, methods: { sendStudentName(){ pubsub.publish('hello',666) } }, } </script> <style lang="less" scoped> .student{ background-color: pink; padding: 5px; margin-top: 30px; } </style> ```
-
接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。
<template> <div class="school"> <h2>学校名称:{{name}}</h2> <h2>学校地址:{{address}}</h2> </div> </template> <script> import pubsub from 'pubsub-js' export default { name:'School', data() { return { name:'测试', address:'测试地址', } }, mounted() { this.pubId = pubsub.subscribe('hello',(msgName,data)=>{ console.log(this) // console.log('有人发布了hello消息,hello消息的回调执行了',msgName,data) }) }, beforeDestroy() { pubsub.unsubscribe(this.pubId) }, } </script> <style scoped> .school{ background-color: skyblue; padding: 5px; } </style>
-
最好在beforeDestroy钩子中,用
PubSub.unsubscribe(pid)
去取消订阅。