1.父子间通信
源码位置:src/core/instance/events.js
父->子:通过 props
子-> 父:$on、$emit (发布订阅)
父组件:
<template>
<div id="app">
<users :users="users"></users>//子组件
</div>
</template>
export default {
data(){
return{
users:["one","two","three"]
}
}
}
子组件:
<template>
<div>
<ul>
<li v-for="user in users">{{user}}</li>// 显示父组件传递的值
</ul>
</div>
</template>
export default {
props:{
users:{ // 需要在子组件中props中写上父组件自定义的变量名
type:Array
}
}
}
// 子组件
<template>
<div>
<button @click="changeVal">传值</button>
</div>
</template>
export default {
data() {
return {
dataVal: "Vue.js Demo"
}
},
methods:{
changeVal() {
this.$emit("getVal",this.dataVal); //自定义事件 传递值“子向父组件传值”
}
}
}
// 父组件
<template>
<div id="app">
// 子组件
<users @getVal="updateVal" ></users>// 与子组件中getVal自定义事件保持一致
</div>
</template>
export default {
data(){
return{
dataVal1:""
}
},
methods:{
updateVal(e){ //声明这个函数
this.dataVal1= e; // 接收值
}
}
}
2.获取父子组件实例的方式:
(1)$parent、$children
源码位置:src/core/instance/lifecycle.js
组件在初始化的时候会初始一个$parent、$children,这样就可以拿到父实例和子实例
使用:在父组件中通过:this.$children访问子组件中的方法和数据
在子组件中通过:this.$parent访问父组件中的方法和数据
(2)ref
源码位置:src/core/vdom/modules/ref.js
如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
这两种都是直接得到组件实例,使用后可以直接调用组件的方法或访问数据。这两种方法的弊端是,无法在跨级或兄弟间通信。
使用:在子组件上写上ref值,通过this.$refs.定义的ref值访问子组件中的方法和数据
3.在父组件中提供数据子组件进行消费 Provide、inject 插件
源码位置:src/core/instance/inject.js
export function initProvide (vm: Component) {
const provide = vm.$options.provide // 将数据放在了vm.$options
if (provide) {
vm._provided = typeof provide === 'function' // 如果是函数就调用一下
? provide.call(vm)
: provide
}// 最后会把提供的属性放到当前实例的_provided上
}
// 不停的找父组件
export function initInjections (vm: Component) {
const result = resolveInject(vm.$options.inject, vm)// 找父组件有没有_provided属性
if (result) {
// 如果有,就把这个属性定义到它自己的身上,而且是一个响应式的,只要父组件的数据一变,就会更新子组件
toggleObserving(false)
Object.keys(result).forEach(key => {
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
defineReactive(vm, key, result[key], () => {
warn(
`Avoid mutating an injected value directly since the changes will be ` +
`overwritten whenever the provided component re-renders. ` +
`injection being mutated: "${key}"`,
vm
)
})
} else {
defineReactive(vm, key, result[key])
}
})
toggleObserving(true)
}
}
// 遍历寻找上面是否有_provided属性,如果找到了就将这个结果return
export function resolveInject (inject: any, vm: Component): ?Object {
if (inject) {
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
if (key === '__ob__') continue
const provideKey = inject[key].from
let source = vm
while (source) {
if (source._provided && hasOwn(source._provided, provideKey)) {
result[key] = source._provided[provideKey]
break
}
source = source.$parent
}
}
return result
}
}
在父组件中提供好数据,在子组件中注入依赖,这样就可以在子组件中直接修改父组件的实例的属性
使用:
(1)父组件中provide:
// 父组件
(1)这样写可以传递属性,但是不会响应
provide() {
return {
theme: {
color: this.color //这种方式绑定的数据并不是可响应的
} // 即父组件的color发生变化,子组件不会变化
};
}
(2)可以直接将整个实例赋值过去
provide() {
return {
theme: this //提供父组件的实例
};
}
(3)使用Vue.observable 优化响应式 provide
provide() {
this.theme = Vue.observable({
color: "blue"
})
return {
theme: this.theme
}
}
(2)子组件中inject
// 子组件:
(1)数组接收
inject: ['tenme']
取值:this.tenme.color
(2)对象 函数
inject: {
tenme: {
default: () => ({})
}
}
函数式组件取值:injections.theme.color
inject: {
tenme1: {
from: 'tenme'
default: ''
}
}
this.tenme1.color
4.Event Bus 实现跨组件通信
使用的就是$on 和$emit,绑定事件和触发事件必须在同一个实例上,所以用一个全局的来通信
(1)方法一:全局挂载
// main.js 在项目中的 main.js 初始化 Bus :
Vue.prototype.$Bus = new Vue()
// 发送事件
this.$Bus.$emit("user", {
name: 'wthreesix',
age: 17
})
// 接收事件
this.$Bus.$on("user", ({name, age}) => {
console.log(name, age)
})
(2)方法二:单独建立一个bus.js文件
import Vue from 'vue'
export const Bus = new Vue()
在使用的组件中引入
import { Bus} from "../bus.js"
// 发送事件
Bus.$emit("user", {
name: 'wthreesix',
age: 17
})
// 接收事件
Bus.$on("user", ({name, age}) => {
console.log(name, age)
})
移除事件监听者
Bus.$off() 移除所有事件频道, 注意不需要添加任何参数 。
Bus.$off('user') 移除指定的事件
5.$attrs/$listeners
(1)$attrs:包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class
和 style
除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class
和 style
除外),并且可以通过 v-bind=
"$attrs" 传入内部组件——在创建高级别的组件时非常有用。(理解为:继承所有的父组件属性,除了prop传递的属性、class 和 style )
(2)$listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件——在创建更高层次的组件时非常有用。(理解为:$listeners它是一个对象,里面包含了作用在这个组件上的所有监听器,你就可以配合 v-on="$listeners"
将所有的事件监听器指向这个组件的某个特定的子元素)
(3)inheritAttrs:默认情况下父作用域的不被认作 props 的特性绑定 (attribute bindings) 将会“回退”且作为普通的 HTML 特性应用在子组件的根元素上。当撰写包裹一个目标元素或另一个组件的组件时,这可能不会总是符合预期行为。通过设置 inheritAttrs 到 false,这些默认行为将会被去掉。而通过 (同样是 2.4 新增的) 实例属性 $attrs 可以让这些特性生效,且可以通过 v-bind 显性的绑定到非根元素上。
简单理解inheritAttrs:用来设置禁用继承属性,当设置inheritAttrs: true(默认)时,子组件的顶层标签元素中会渲染出父组件传递过来的属性,当设置inheritAttrs: false时,子组件的顶层标签元素中不会渲染出父组件传递过来的属性,不管inheritAttrs为true或者false,子组件中都能通过$attrs属性或者props获取到父组件中传递过来的属性。
使用:
组件childCom:
<template>
<div>
<h2>前端:</h2>
<child-com1
:css="css"
:html="html"
:js="js"
@childVal="getChildVal"
></child-com1>
</div>
</template>
<script>
const childCom1 = () => import("./childCom1.vue")
export default {
components: { childCom1 },
data() {
return {
css: "css",
html: "Html",
js: "js"
};
},
methods: {
getChildVal(val) {
console.log('childVal:', val)
}
}
}
</script>
组件childCom1:
<template>
<div class="border">
<p>css: {{ css }}</p>
<p>childCom1的$attrs: {{ $attrs }}</p>
<child-com2 v-bind="$attrs" v-on="$listeners"></child-com2>
// 在子组件上绑定$listeners是,在该组件里就能够拿到父组件中绑定的非原生事件,通过$emit触发
</div>
</template>
<script>
const childCom2 = () => import("./childCom2.vue");
export default {
components: {
childCom2
},
inheritAttrs: false, // 可以关闭自动挂载到组件根元素上的属性,除在props声明的
props: {
css: String
},
created() {
console.log(this.$attrs); // {html: "Html", js: "js"}
}
};
</script>
组件childCom2:
<template>
<div class="border">
<p @click="sendVal">html: {{ html }}</p>
<p>childCom2: {{ $attrs }}</p>
</div>
</template>
<script>
export default {
inheritAttrs: true,
props: {
html: String
},
created() {
console.log(this.$attrs);//{js: "js"}
},
methods: {
sendVal() {
this.$emit('childVal', '子子组件的值') // 触发父组件中的方法
}
}
};
</script>
页面显示:
inheritAttrs属性false和true的dom渲染区别:false时子组件上不渲染属性,true会将属性渲染在子组件元素上。
6.Vuex状态管理实现通信
将数据都存放在一个容器里,大家一起去共享。
Vuex 实现了一个单向数据流,在全局拥有一个 State 存放数据,当组件要更改 State 中的数据时,必须通过 Mutation 进行,Mutation 同时提供了订阅者模式供外部插件调用获取 State 数据的更新。而当所有异步操作(常见于调用后端接口异步获取更新数据)或批量的同步操作需要走 Action,但 Action 也是无法直接修改 State 的,还是需要通过 Mutation 来修改 State 的数据。最后,根据 State 的变化,渲染到视图上。
vuex 是 vue 的状态管理器,存储的数据是响应式的。但是并不会保存起来,刷新之后就回到了初始状态,具体做法应该在 vuex 里数据改变的时候把数据拷贝一份保存到 localStorage 里面,刷新之后,如果 localStorage 里有保存的数据,取出来再替换 store 里的 state。或者npm安装vuex-persistedstate:
在store中写:
plugins: [
persistedstate({
storage: window.sessionStorage,
})
]
注:
1.组件中的数据共有三种形式:data、props、computed
2.vue组件是单向数据流