父传子
props
父组件
子组件
- props只能是父组件向子组件进行传值,props使得父子组件之间形成了一个单向下行绑定。子组件的数据会随着父组件不断更新。
props 可以显示定义一个或一个以上的数据,对于接收的数据,可以是各种数据类型,同样也可以传递一个函数。
provide / inject
provide / inject是Vue提供的两个钩子,和data、methods是同级的。并且provide的书写形式和data一样。● provide 钩子用来发送数据或方法● inject钩子用来接收数据或方法(接收传递的数据,类似peops)
- 就是 provide 可以在祖先组件中指定我们想要提供给后代组件的数据或方法,而在任何后代组件中,我们都可以使用 inject 来接收 provide 提供的数据或方法。
例子:
// 父级组件提供 'foo'
<template>
<div>
<div>{{foo}}</div>
<son></son>
</div>
</template>
<script>
import Son from "./Son";
export default {
name: "parent",
components: { Son },
provide() {
return {
foo: this.foo
};
},
data() {
return {
foo: "测试",
};
},
mounted() {
console.log(this.foo)
},
};
</script>
//子级组件,不接收
<template>
<grandSon></grandSon>
</template>
<script>
import grandSon from "./grandSon";
export default {
name: "son",
components: { grandSon },
};
</script>
//孙级组件,接收foo
<template>
<div>{{foo}}</div>
</template>
<script>
export default {
name: "grandSon",
inject: ["foo"],
mounted() {
console.log(this.foo)
},
};
</script>
在这里我们可以发现孙组件越过子组件接收了父组件注入的数据,我们可以理解为爷爷越过爸爸偷偷给孙子买了辣条,这是一组最简单的用法,当层级继续增加时,仍可通过这种方式由父组件直接跨域多个层级向后代组件注入数据。
有一点需要特别注意的是,实际上我们可以将当前组件inject获取的数据直接赋值给它本身的data或props,不过官网提示我们,这是在Vue2.2.1版本才实现的功能,在这之前,必须先进行props和data数据的初始化。
数据的响应更新
在尝试中我们发现,由于Vue的单向流关系,实际上如果在parent中改变了初始传入的foo的值以后,grandSon并不会得到改变后的值,也就是在这个时候,父孙组件的数据出现了不一致的情况,我们肯定是希望拿到的数据是一致的,怎么来解决这个问题呢,官网还有一句提示为我们提供了解释。
提示:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的。 也就是指,我们需要人为的将这组数据关系变成可响应的,哦,我们之前的foo是一个字符串,基本数据类型是不具有响应特性的,那么,我们可能需要传递一个对象。
// 改造一下父级组件提供 'foo'
<template>
<div>
<div>{{foo}}</div>
<son></son>
</div>
</template>
<script>
import Son from "./Son";
export default {
name: "parent",
components: { Son },
provide() {
return {
foo: this.foo
};
},
data() {
return {
foo: {
foo: "测试"
},,
};
},
mounted() {
console.log(this.foo)
},
};
</script>
在这个栗子中,改造了一下父组件提供的数据,测试发现,foo变成了一组可响应的数据。经过尝试我发现在这种情况下如果在孙组件改变inject中foo的值,也会响应的更新到父组件中,当然为了保护单向数据流机制,最佳实践还是不要在子组件里更改inject。
默认值设置
在 2.5.0+ 的注入可以通过设置默认值使其变成可选项
<script>
export default {
name: "grandSon",
inject: {
foo: {
from: 'bar',
default: () => [1, 2, 3]
}
},
mounted() {
console.log(this.foo)
},
};
</script>:
在2.5.0 版本之后的版本中可以为inject提供默认值了,如果它需要从一个不同名字的 property 注入,则使用 from 来表示其源 property,与 prop 的默认值类似,需要对非原始值使用一个工厂方法。
缺点
provide/inject 的缺点还是非常明显的:
- 当多个后代组件同时依赖同一个父组件提供数据时,只要任一组件对数据进行了修改,所有依赖的组件都会受到影响,实际上是增加了耦合度。
- 任意层级访问使数据追踪变的比较困难,你并不能准确的定位到是哪一个层级对数据进行了改变,当数据出现问题时,尤其是多人协作时,可能会大大增加问题定位的损耗。
优点
在进行组件库或高级插件开发时,provide/inject 仍然是一个不错的选择。ref/$refs
ref: 这个属性用在子组件上,它的引用就指向了子组件的实例。可以通过实例来访问组件的数据和方法。
子传父
自定义事件($emit)
子组件
this.$emit('refresh')
父组件
<template>
<!-- 打标签 -->
<add-labels ref="addLabels" @refresh="refresh"></add-labels>
</template>
<script>
export default {
methods:{
// 刷新数据
refresh() {
this.getList()
},
}
}
</script>
父子通信
$parent / $children
$parent/$children的常用场景:封装嵌套组件时,直接使用长辈或者子孙组件的方法,该方法并不改变数据,常常结合$broadcast/$dispatch使用。
- 组件本身没有父组件和子组件,而是在运行时,每个组件实例的位置,决定其父组件和子组件
- 每个组件实例有且只有一个父组件(顶级组件实例没有哈)
- 但每个组件实例可能没有子组件,可能有一个,也可能有多个,所以一般children是数组,没有的时候是空数组
- js 运行时,若添加或者删除组件实例,那些发生位置变化的组件实例们,父组件和子组件也会发生变化。
写一个页面组件,里面放些组件,打印下$parent/$children
<template lang="pug">
//- 页面组件
div
list-item
list-item
</template>
<script>
import ListItem from "@/components/ListItem";
export default {
name: "List",
components: { ListItem },
mounted() {
console.log("页面的$parent", this.$parent);
console.log("页面的$children", this.$children);
}
};
</script>
其实还能看到,渲染的时候先子组件的mounted,然后再是自己的mounted(这与生命周期有关).
要是想在子组件里获取父组件的元素之类的,必须使用nextTick。
$broadcast/$dispatch
在使用element-ui时,如下:
<template lang="pug">
el-form
el-form-item
el-input
</template>
假设el-input想要执行el-form上的方法,就会这样this.$parent.$parent.methodXx(),更多层级可能更复杂,于是$dispatch就诞生了。(vuex中使用的dispatch和这个类似)
// main.js
// 向上某个组件,派发事件
Vue.prototype.$dispatch = function(eventName, componentName, ...args) {
let parent = this.$parent;
while (parent) {
// 只有是特定组件,才会触发事件。而不会一直往上,一直触发
const isSpecialComponent = parent.$options.name === componentName;
if (isSpecialComponent) {
// 触发了,就终止循环
parent.$emit(eventName, ...args);
return;
}
parent = parent.$parent;
}
};
这样在el-input里想要触发el-form里的方法,this.$dispatch(‘changeSort’,’el-form’,{isAsc:true})
同理,假设el-form想要执行el-input上的方法,就会这样this.$children[0].$children[0].methodXx(),更多层级可能更复杂,于是$broadcast就诞生了,使用的时候this.$broadcast(‘changeValue’,’el-input’,’hello’)。
注意,$children 是数组,所以当只有一个子组件时,使用[0]获取。当有多个子组件时,它并不保证顺序,也不是响应式的。
// 向下通知某个组件,触发事件
Vue.prototype.$broadcast = function(eventName, componentName, ...args) {
// 这里children是所有子组件,是子组件不是后代组件哈
let children = this.$children;
broadcast(children);
// 这里注意,抽离新的方法递归,而不是递归$broadcast
function broadcast(children) {
for (let i = 0; i < children.length; i++) {
let child = children[i];
const isSpecialComponent = child.$options.name === componentName;
if (isSpecialComponent) {
// 触发了,就终止循环
child.$emit(eventName, ...args);
return;
}
// 没触发的话,就看下有没有子组件,接着递归
child.$children.length &&
child.$broadcast(eventName, componentName, ...args);
}
}
};
$attrs / $listeners(属性/方法)
$attrs的使用
官方定义:
包含了父作用域中不作为prop 被识别 (且获取) 的特性绑定 (class和 style除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class和 style除外),并且可以通过 v-bind=”$attrs” 传入内部组件——在创建高级别的组件时非常有用。
(好抽象一脸懵逼。。。)
$attrs只代表的是那些没有被声明为props的属性,如果某个prop被子组件中声明了(就是这个属性已经在子组件的props中了),在子组件中的$attr会把声明的prop剔除。
个人理解: 一个组件在父组件中被引用,$attrs就是组件标签上的静态属性值(attr)和动态属性值(:attr)的对象集合,这个集合不包含class, style和事件属性
// 父组件
<child-com class="com" name="attr" :foo="foo" :boo="boo" :coo="coo" :doo="doo" @click="test"></child-com>
...
data() {
return {
foo: 'Hello World!',
boo: 'Hello Javascript!',
coo: 'Hello Vue',
doo: 'Last'
}
},
...
// child-com.vue
export default {
name: 'childCom',
components: {
childCom2
},
created() {
console.log(this.$attrs) // {name: "attr", foo: "Hello World!", boo: "Hello Javascript!", coo: "Hello Vue", doo: "Last"}
}
}
如果子组件声明了$prop,$attrs中与$props相同的属性会被移除
// child-com.vue
export default {
name: 'childCom',
props: ['foo'], // foo被声明
components: {
childCom2
},
created() {
console.log(this.$attrs) // {name: "attr", boo: "Hello Javascript!", coo: "Hello Vue", doo: "Last"}
}
}
如果childCom里的子组件还用到foo,可以继续将foo传给子组件
<template>
<div>
<p>foo: {{foo}} </p>
<child-com2 :foo="foo" v-bind="$attrs"></child-com2>
</div>
</template>
<script>
const childCom2 = () => import('./childCom2.vue')
export default {
name: 'childCom1',
props: ['foo'], // foo作为props属性绑定
inheritAttrs: false,
components: {
childCom2
},
created() {
console.log(this.$attrs) // {name: "attr", boo: "Hello Javascript!", coo: "Hello Vue", doo: "Last"}
}
}
</script>
//childCom2.vue
created() {
console.log(this.$attrs) // {foo: "Hello World!", name: "attr", boo: "Hello Javascript!", coo: "Hello Vue", doo: "Last"}
}
inheritAttrs要设置成false还是true(默认)看下图就知道,(inheritAttrs为true时允许动态或静态属性显示,为false时即使你给组件设置了属性也不会显示)
与$props比较
$props必须在组件中注册了props才能用拿到值,所以在嵌套层级比较深的组件中$attrs拿值更加便捷
$listeners的使用
包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过v-on=”$listeners”传入内部组件。
归纳起来也是两点:
$listeners是组件的内置属性,它的值是父组件(不含.native修饰器的) v-on事件监听器。
组件可以通 过在自己的子组件上使用v-on=”$listeners”,进一步把值传给自己的子组件。如果子组件已经绑定$listener中同名的监听器,则两个监听器函数会以冒泡的方式先后执行。
父组件:
<template>
<div class="test">
<child v-bind="{name, sex, age}" v-on="{changeName,changeAge}"></child>
</div>
</template>
<script>
import child from './child'
export default {
data() {
return {
name: '张三',
sex: '男',
age: 11,
}
},
components: {
child
},
methods: {
changeName(name) {
this.name = name
},
changeAge(age) {
this.age = age
}
}
}
</script>
子组件child.vue
<template>
<div class="child">
child组件的$attrs {{$attrs}}
<child-child v-bind="$attrs" v-on="$listeners" @showAttrs="showAttrs"></child-child>
</div>
</template>
<script>
import childChild from './child-child'
export default {
name: "child",
props: ['name'],
inheritAttrs: false,
created() {
console.log('child', this.$listeners)
},
components: {
childChild
},
methods: {
showAttrs() {
console.log(this.$attrs)
}
}
}
</script>
孙子组件:child-child.vue
<template>
<div class="child-child">
child-child组件的$attrs {{$attrs}}
</div>
</template>
<script>
export default {
name: "child-child",
inheritAttrs: false,
created() {
console.log('child-child',this.$listeners)
}
}
</script>
全局事件总线
- 一种组件间通信的方式,适用于任意组件间通信。
安装全局事件总线:
new Vue({
......
beforeCreate() {
Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
},
......
})
使用事件总线:
1.接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。
methods(){
demo(data){......}
}
......
mounted() {
this.$bus.$on('xxxx',this.demo)
}
2.提供数据:this.$bus.$emit(‘xxxx’,数据)
- 最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。
例子 : ```javascript //引入Vue import Vue from ‘vue’ //引入App import App from ‘./App.vue’ //关闭Vue的生产提示 Vue.config.productionTip = falsebeforeDestroy() {
this.$bus.$off('hello')
},
// 安装全局事件总线 Vue.prototype.$bus = new Vue()
//创建vm new Vue({ el:’#app’, render: h => h(App), })
两个兄弟组件 :<br />**发送的数据的组件:**
```javascript
<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: { // 点击事件,把学生们传递到兄弟组件School
sendStudentName(){
this.$bus.$emit('hello',this.name)
}
},
}
</script>
接受数据的组件
<template>
<div class="school">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
</div>
</template>
<script>
export default {
name:'School',
data() {
return {
name:'尚硅谷',
address:'北京',
}
},
mounted() { //接受数据
this.$bus.$on('hello',(data)=>{
console.log('我是School组件,收到了数据',data)
})
},
beforeDestroy() { // 销毁调全局事件总线
this.$bus.$off('hello')
},
}
</script>
vuex
核心流程及主要功能
● Vue Components 是 vue 组件,组件会触发(dispatch)一些事件或动作,也就是图中的 Actions;
● 在组件中发出的动作,肯定是想获取或者改变数据的,但是在 vuex 中,数据是集中管理的,不能直接去更改数据,所以会把这个动作提交(Commit)到 Mutations 中;
● 然后 Mutations 就去改变(Mutate)State 中的数据;
● 当 State 中的数据被改变之后,就会重新渲染(Render)到 Vue Components 中去,组件展示更新后的数据,完成一个流程。
各模块在核心流程中的主要功能
● Vue Components∶ Vue组件。HTML页面上,负责接收用户操作等交互行为,执行dispatch方法触发对应action进行回应。
● dispatch∶操作行为触发方法,是唯一能执行action的方法。
● actions∶ 操作行为处理模块。负责处理Vue Components接收到的所有交互行为。包含同步/异步操作,支持多个同名方法,按照注册的顺序依次触发。向后台API请求的操作就在这个模块中进行,包括触发其他action以及提交mutation的操作。该模块提供了Promise的封装,以支持action的链式触发。
● commit∶状态改变提交操作方法。对mutation进行提交,是唯一能执行mutation的方法。
● mutations∶状态改变操作方法。是Vuex修改state的唯一推荐方法,其他修改方式在严格模式下将会报错。该方法只能进行同步操作,且方法名只能全局唯一。
● state∶ 页面状态管理容器对象。集中存储Vuecomponents中data对象的零散数据,全局唯一,以进行统一的状态管理。页面显示所需的数据从该对象中进行读取,并进行状态更新。
● getters∶ state对象读取方法。
总结
Vuex 实现了一个单向数据流,在全局拥有一个 State 存放数据,当组件要更改 State 中的数据时,必须通过 Mutation 提交修改信息, Mutation 同时提供了订阅者模式供外部插件调用获取 State 数据的更新。而当所有异步操作(常见于调用后端接口异步获取更新数据)或批量的同步操作需要走 Action ,但 Action 也是无法直接修改 State 的,还是需要通过Mutation 来修改State的数据。最后,根据 State 的变化,渲染到视图上。