组件是 vue.js最强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互引用。
vue组件间的传值方式多种多样,并不局限于父子传值、事件传值这些。
这一组选项需要一起使用,允许一个祖先组件向其所有的后代组件注入一个依赖,不论组件层级有多深,并在其上下游关系成立的时间里始终生效。
// provide 选项应该是一个对象或返回一个对象的函数
// inject 选项应该是:一个字符串数组,或一个对象,对象的 key 是本地的绑定名
// 父级组件提供 'foo'
var Provider = {
provide: {
foo: 'bar'
},
// ...
}
// 子组件注入 'foo' (数组形式)
var Child = {
inject: ['foo'],
created () {
console.log(this.foo) // => "bar"
}
// ...
}
或 (对象形式)
var Child = {
inject: {
foo: {
from: 'bar', // 可选
default: 'self defined content' // 默认值
}
},
created () {
console.log(this.foo) // => "bar"
}
// ...
}
需要注意的是: Vue 2.2.1 或更高版本中,inject注入的值会在 props 和 data 初始化之前得到
// 使用一个注入的值作为数据(data)的入口 或者 属性(props)的默认值
const Child = {
inject: ['foo'],
data () {
return {
bar: this.foo // 输出bar的值与foo相同
}
}
}
const Child = {
inject: ['foo'],
props: {
bar: {
default () {
return this.foo
}
}
}
}
-----------------------------------------------------------------------------------------------
// 注入可以通过设置默认值使其变成可选项
const Child = {
inject: {
foo: { // 注意: 此处key值必须是父组件中provide的属性的key
from: 'bar', // 属性是在可用的注入内容中搜索用的 key (字符串或 Symbol), data或者props中的可搜索值
default: 'foo' // 属性是降级情况下使用的 value, 默认值为 ‘foo’
}
}
}
// 与 prop 的默认值类似
const Child = {
inject: {
foo: {
from: 'bar',
default: () => [1, 2, 3] // 默认值为引用类型时,需要使用一个工厂方法返回对象
}
}
}
简介
Vue2.2.0新增API,这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。一言而蔽之:祖先组件中通过provider来提供变量,然后在子孙组件中通过inject来注入变量。
provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。
案例
假设有两个组件: A.vue 和 B.vue,B 是 A 的子组件
A.vue
export default {
provide: {
name: '浪里行舟'
}
}
B.vue
export default {
inject: ['name'],
mounted () {
console.log(this.name); // 浪里行舟
}
}
可以看到,在 A.vue 里,我们设置了一个 provide: name,值为 浪里行舟,它的作用就是将 name 这个变量提供给它的所有子组件。而在 B.vue 中,通过 inject 注入了从 A 组件中提供的 name 变量,那么在组件 B 中,就可以直接通过 this.name 访问这个变量了,它的值也是 浪里行舟。这就是 provide / inject API 最核心的用法。
需要注意的是:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的----vue官方文档
所以,上面 A.vue 的 name 如果改变了,B.vue 的 this.name 是不会改变的,仍然是 浪里行舟。
provide与inject 怎么实现数据响应式
一般来说,有两种办法:
我们来看个例子:孙组件D、E和F获取A组件传递过来的color值,并能实现数据响应式变化,即A组件的color变化后,组件D、E、F不会跟着变(核心代码如下:)
A 组件
<div>
<h1>A 组件h1>
<button @click="() => changeColor()">改变colorbutton>
<ChildrenB />
<ChildrenC />
div>
......
data() {
return {
color: "blue"
};
},
// provide() {
// return {
// theme: {
// color: this.color //这种方式绑定的数据并不是可响应的
// } // 即A组件的color变化后,组件D、E、F不会跟着变
// };
// },
provide() {
return {
theme: this//方法一:提供祖先组件的实例
};
},
methods: {
changeColor(color) {
if (color) {
this.color = color;
} else {
this.color = this.color === "blue" ? "red" : "blue";
}
}
}
// 方法二:使用2.6最新API Vue.observable 优化响应式 provide
// provide() {
// this.theme = Vue.observable({
// color: "blue"
// });
// return {
// theme: this.theme
// };
// },
// methods: {
// changeColor(color) {
// if (color) {
// this.theme.color = color;
// } else {
// this.theme.color = this.theme.color === "blue" ? "red" : "blue";
// }
// }
// }
F 组件
<template functional>
<div class="border2">
<h3 :style="{ color: injections.theme.color }">F 组件h3>
div>
template>
<script>
export default {
inject: {
theme: {
//函数式组件取值不一样
default: () => ({})
}
}
};
script>
虽说provide 和 inject 主要为高阶插件/组件库提供用例,但如果你能在业务中熟练运用,可以达到事半功倍的效果!
// 创建组件
Vue.component('props-demo-advanced', {
props: {
age: {
type: Number,
default: 0
}
}
})
// 父组件中注册和使用组件,并传值
<props-demo-advanced :age="age"> </props-demo-advanced>
props/$emit
父组件A通过props的方式向子组件B传递,B to A 通过在 B 组件中 $emit, A 组件中 v-on 的方式实现。
案例 : 父组件向子组件传值
下例说明父组件如何向子组件传递值:在子组件Users.vue中如何获取父组件App.vue中的数据 users:["Henry","Bucky","Emily"]
App.vue父组件
<template>
<div id="app">
<users v-bind:users="users">users>//前者自定义名称便于子组件调用,后者要传递数据名
div>
template>
<script>
import Users from "./components/Users"
export default {
name: 'App',
data(){
return{
users:["Henry","Bucky","Emily"]
}
},
components:{
"users":Users
}
}
users子组件
<template>
<div class="hello">
<ul>
<li v-for="user in users">{{user}}li>//遍历传递过来的值,然后呈现到页面
ul>
div>
template>
<script>
export default {
name: 'HelloWorld',
props:{
users:{ //这个就是父组件中子标签自定义名字
type:Array,
required:true
}
}
}
script>
总结:父组件通过props向下传递数据给子组件。注:组件中的数据共有三种形式:data、props、computed
// 子组件Child, 负载payload可选
this.$emit('eventName', payload)
// 父组件 Parent
<Parent @evnetName="sayHi"></Parent>
案例 : 子组件向父组件传值(通过事件形式)
下例说明子组件如何向父组件传递值:当点击“Vue.js Demo”后,子组件向父组件传递值,文字由原来的“传递的是一个值”变成“子向父组件传值”,实现子组件向父组件值的传递。
子组件
<template>
<header>
<h1 @click="changeTitle">{{title}}h1>//绑定一个点击事件
header>
template>
<script>
export default {
name: 'app-header',
data() {
return {
title:"Vue.js Demo"
}
},
methods:{
changeTitle() {
this.$emit("titleChanged","子向父组件传值");//自定义事件 传递值“子向父组件传值”
}
}
}
script>
父组件
<template>
<div id="app">
<app-header v-on:titleChanged="updateTitle" >app-header>//与子组件titleChanged自定义事件保持一致
// updateTitle($event)接受传递过来的文字
<h2>{{title}}h2>
div>
template>
<script>
import Header from "./components/Header"
export default {
name: 'App',
data(){
return{
title:"传递的是一个值"
}
},
methods:{
updateTitle(e){ //声明这个函数
this.title = e;
}
},
components:{
"app-header":Header,
}
}
script>
总结:子组件通过events给父组件发送消息,实际上就是子组件把自己的数据发送到父组件。
$emit
/$on
这种方法通过一个空的Vue实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级。当我们的项目比较大时,可以选择更好的状态管理解决方案vuex。
1.具体实现方式:
var Event=new Vue();
Event.$emit(事件名,数据);
Event.$on(事件名,data => {});
案例 :
假设兄弟组件有三个,分别是A、B、C组件,C组件如何获取A或者B组件的数据
<div id="itany">
<my-a>my-a>
<my-b>my-b>
<my-c>my-c>
div>
<template id="a">
<div>
<h3>A组件:{{name}}h3>
<button @click="send">将数据发送给C组件button>
div>
template>
<template id="b">
<div>
<h3>B组件:{{age}}h3>
<button @click="send">将数组发送给C组件button>
div>
template>
<template id="c">
<div>
<h3>C组件:{{name}},{{age}}h3>
div>
template>
<script>
var Event = new Vue();//定义一个空的Vue实例
var A = {
template: '#a',
data() {
return {
name: 'tom'
}
},
methods: {
send() {
Event.$emit('data-a', this.name);
}
}
}
var B = {
template: '#b',
data() {
return {
age: 20
}
},
methods: {
send() {
Event.$emit('data-b', this.age);
}
}
}
var C = {
template: '#c',
data() {
return {
name: '',
age: ""
}
},
mounted() {//在模板编译完成后执行
Event.$on('data-a',name => {
this.name = name;//箭头函数内部不会产生新的this,这边如果不用=>,this指代Event
})
Event.$on('data-b',age => {
this.age = age;
})
}
}
var vm = new Vue({
el: '#itany',
components: {
'my-a': A,
'my-b': B,
'my-c': C
}
});
script>
$on 监听了自定义事件 data-a和data-b,因为有时不确定何时会触发事件,一般会在 mounted 或 created 钩子中来监听。
进行事件监听和数据传递。同时vuex也是基于这个原理实现的
// 三步使用
// 1. 创建
window.$bus = new Vue()
// 2. 注册事件
window.$bus.$on('user_task_change', (payload) => {
console.log('事件触发')
})
// 3. 触发
window.$bus.$emit('user_task_change', payload)
1.简要介绍Vuex原理
Vuex实现了一个单向数据流,在全局拥有一个State存放数据,当组件要更改State中的数据时,必须通过Mutation进行,Mutation同时提供了订阅者模式供外部插件调用获取State数据的更新。而当所有异步操作(常见于调用后端接口异步获取更新数据)或批量的同步操作需要走Action,但Action也是无法直接修改State的,还是需要通过Mutation来修改State的数据。最后,根据State的变化,渲染到视图上。
2.简要介绍各模块在流程中的功能:
3.Vuex与localStorage
vuex 是 vue 的状态管理器,存储的数据是响应式的。但是并不会保存起来,刷新之后就回到了初始状态,具体做法应该在vuex里数据改变的时候把数据拷贝一份保存到localStorage里面,刷新之后,如果localStorage里有保存的数据,取出来再替换store里的state。
let defaultCity = "上海"
try { // 用户关闭了本地存储功能,此时在外层加个try...catch
if (!defaultCity){
defaultCity = JSON.parse(window.localStorage.getItem('defaultCity'))
}
}catch(e){}
export default new Vuex.Store({
state: {
city: defaultCity
},
mutations: {
changeCity(state, city) {
state.city = city
try {
window.localStorage.setItem('defaultCity', JSON.stringify(state.city));
// 数据改变的时候把数据拷贝一份保存到localStorage里面
} catch (e) {}
}
}
})
这里需要注意的是:由于vuex里,我们保存的状态,都是数组,而localStorage只支持字符串,所以需要用JSON转换:
JSON.stringify(state.subscribeList); // array -> string
JSON.parse(window.localStorage.getItem("subscribeList")); // string -> array
其中vuex相关的三个文件counts.js、 index.js、 operate.js,内容如下:
index.js
import Vue from 'vue'
import Vuex from 'vuex'
import counter from './counter.js'
import operate from './operate.js'
Vue.use(Vuex)
const state = {
name: 'zhaoyh'
}
const getters = {
getName (state) {
return state.name
}
}
const mutations = {
changeName (state, payload) {
state.name = `${state.name} ${payload}`
}
}
const actions = {
changeNameAsync (context, payload) {
return new Promise((resolve, reject) => {
setTimeout(() => {
context.commit('changeName', payload)
}, 1000)
})
}
}
const store = new Vuex.Store({
state,
getters,
mutations,
actions,
modules: {
counter,
operate
}
})
export default store
counter.js
// 模块内方法调用本模块内的方法和数据
const state = {
counterName: 'module > counter > zhaoyh'
}
const getters = {
// state, getters: 本模块内的state, getters
// rootState, rootGetters: 根模块/根节点内的state, getters
// rootState, rootGetters: 同时也包括各个模块中的state 和 getters
getCounterName (state, getters, rootState, rootGetters) {
// rootState.name
// rootState.counter.counterName
// rootState.operate.operateName
console.log(rootState)
// rootGetters.getName,
// rootGetters['counter/getCounterName']
// rootGetters['operate/getOperateName']
console.log(rootGetters)
return state.counterName
}
}
const mutations = {
changeCounterName (state, payload) {
state.counterName = `${state.counterName} ${payload}`
}
}
const actions = {
// context 与 store 实例具有相同方法和属性 ------ important!!!
// context 包括: dispatch, commit, state, getters, rootState, rootGetters
changeCounterNameAsync (context, payload) {
return new Promise((resolve, reject) => {
setTimeout(() => {context.commit('changeCounterName', payload)}, 1000)
})
}
}
export default {
// 注意此处是namespaced,而不是namespace,
// 写错的话程序不会报错,vuex静默无法正常执行
namespaced: true,
state,
getters,
mutations,
actions
}
operate.js
// 模块内方法调用和获取本模块之外的方法和数据
const state = {
operateName: 'module > operate > zhaoyh'
}
// 如果你希望使用全局 state 和 getter
// rootState 和 rootGetter 会作为第三和第四参数传入
// 也会通过 context 对象的属性传入 action
const getters = {
// state, getters: 本模块内的state, getters
// rootState, rootGetters: 根模块/根节点内的state, getters, 包括各个模块中的state 和 getters
getOperateName (state, getters, rootState, rootGetters) {
return state.operateName
}
}
const mutations = {
operateChangeCounterName (state, payload) {
state.counterName = `${state.counterName} ${payload}`
}
}
// 如果你希望使用全局 state 和 getter,
// 也会通过 context 对象的属性传入 action
const actions = {
// context 与 store 实例具有相同方法和属性---- !!important!!!
// context 包括:
// dispatch, commit, state, getters, rootState, rootGetters
operateChangeCounterNameAsync (context, payload) {
return new Promise((resolve, reject) => {
setTimeout(() => {
/*
* 若需要在全局命名空间内分发 action 或提交 mutation,
* 将 {root: true} 作为第三参数传给 dispatch 或 commit 即可
*/
context.commit('counter/changeCounterName', payload, { root: true })
// 或 context.dispatch('counter/changeCounterNameAsync', null, { root: true })
}, 1000)
})
}
}
export default {
// 注意此处是namespaced,而不是namespace,
// 写错的话程序不会报错,vuex静默无法正常执行
namespaced: true,
state,
getters,
mutations,
actions
}
vuex属性、方法的引用方式:
common-operate.vue
<template>
<div>
<h2>{{title}}h2>
<ul>
<li>name: {{name}}li>
<li>getName: {{getName}}li>
<li><button @click="changeName('lastName')">Mutation操作button>li>
<li><button @click="changeNameAsync('lastName')">Action操作button>li>
ul>
div>
template>
<script>
import { mapState, mapGetters, mapActions, mapMutations } from 'vuex'
export default {
data () {
return {
title: 'vuex常规操作1'
}
},
computed: {
...mapState(['name']),
...mapGetters(['getName'])
},
methods: {
...mapMutations(['changeName']),
...mapActions(['changeNameAsync'])
}
}
script>
<style scoped>
ul, li{
padding: 0;
margin: 0;
padding: 8px 15px;
}
style>
common-operate2.vue
<template>
<div>
<h2>{{title}}h2>
<ul>
<li>name: {{name}}li>
<li>getName: {{getName}}li>
<li><button @click="changeName('lastName')">Mutation操作button>li>
<li><button @click="changeNameAsync('lastName')">Action操作button>li>
ul>
div>
template>
<script>
import { mapState, mapGetters } from 'vuex'
export default {
data () {
return {
title: 'vuex常规操作2'
}
},
computed: {
...mapState(['name']),
...mapGetters(['getName'])
},
methods: {
// mutation
changeName () {
this.$store.commit('changeName', 'lastName')
},
// actions
changeNameAsync () {
this.$store.dispatch('changeNameAsync', 'lastName')
}
}
}
script>
<style scoped>
ul, li{
padding: 0;
margin: 0;
padding: 8px 15px;
}
style>
module-operate.vue
<template>
<div>
<h2>{{title}}h2>
<ul>
<li>name: {{counterName}}li>
<li>getName: {{getCounterName}}li>
<li><button @click="changeName('lastName')">Mutation操作button>li>
<li><button @click="changeNameAsync('lastName')">Action操作button>li>
ul>
div>
template>
<script>
import { mapState, mapGetters } from 'vuex'
export default {
data () {
return {
title: '模块的基本操作方法'
}
},
computed: {
...mapState('counter', ['counterName']),
...mapGetters('counter', ['getCounterName'])
},
methods: {
// mutation
changeName () {
this.$store.commit('counter/changeCounterName', 'lastName')
},
// actions
changeNameAsync () {
this.$store.dispatch('counter/changeCounterNameAsync', 'lastName')
}
}
}
script>
<style scoped>
ul, li{
padding: 0;
margin: 0;
padding: 8px 15px;
}
style>
module-operate2.vue
<template>
<div>
<h2>{{title}}h2>
<ul>
<li>name: {{counterName}}li>
<li>getName: {{getCounterName}}li>
<li><button @click="changeCounterName('lastName')">Mutation操作button>li>
<li><button @click="changeCounterNameAsync('lastName')">Action操作button>li>
<li><button @click="rename('rename')">Action操作button>li>
ul>
div>
template>
<script>
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
export default {
data () {
return {
title: '模块内方法调用本模块内内的方法和数据'
}
},
computed: {
...mapState('counter', ['counterName']),
...mapGetters('counter', ['getCounterName'])
},
methods: {
...mapMutations('counter', ['changeCounterName']),
...mapActions('counter', ['changeCounterNameAsync']),
// 多模块方法引入的方法名重命名
...mapActions('counter', {
rename: 'changeCounterNameAsync'
}),
otherMethods () {
console.log('继续添加其他方法。')
}
}
}
script>
<style scoped>
ul, li{
padding: 0;
margin: 0;
padding: 8px 15px;
}
style>
module-operate3.vue
<template>
<div>
<h2>{{title}}h2>
<ul>
<li>name: {{counterName}}li>
<li>getName: {{getCounterName}}li>
<li><button @click="operateChangeCounterNameAsync('operate lastName')">Action操作button>li>
ul>
div>
template>
<script>
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
export default {
data () {
return {
title: '模块内方法调用和获取本模块之外的方法和数据'
}
},
computed: {
...mapState('counter', ['counterName']),
...mapGetters('counter', ['getCounterName'])
},
methods: {
...mapMutations('operate', ['operateChangeCounterName']),
...mapActions('operate', ['operateChangeCounterNameAsync']),
otherMethods () {
console.log('继续添加其他方法。')
}
}
}
script>
<style scoped>
ul, li{
padding: 0;
margin: 0;
padding: 8px 15px;
}
style>
$refs 获取对应组件实例,如果是原生dom,那么直接获取的是该dom
$parent / $children 该属性只针对vue组件,获取父/子组件实例
注: 节制地使用 $parent 和 $children - 它们的主要目的是作为访问组件的应急方法。更推荐用 props 和 events 实现父子组件通信
<template>
<div class="hello">
<ipc ref="ipcRef">ipc>
div>
template>
<script>
import ipc from './ipc'
export default {
name: 'HelloWorld',
data () {
return {
parentVal: 'parent content'
}
},
mounted () {
console.log(this.$refs.ipcRef.$data.child1) // "child1 content"
console.log(this.$children[0].$data.child2) // "child2 content"
},
components: {
ipc
}
}
script>
<template>
<div>
div>
template>
<script>
export default {
props: {
},
data () {
return {
child1: 'child1 content',
child2: 'child2 content'
}
},
mounted () {
console.log(this.$parent.parentVal) // "parent content"
}
}
script>
ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
$parent / $children:访问父 / 子实例
需要注意的是:这两种都是直接得到组件实例,使用后可以直接调用组件的方法或访问数据。我们先来看个用 ref来访问组件的例子:
component-a 子组件
export default {
data () {
return {
title: 'Vue.js'
}
},
methods: {
sayHello () {
window.alert('Hello');
}
}
}
父组件
<template>
<component-a ref="comA">component-a>
template>
<script>
export default {
mounted () {
const comA = this.$refs.comA;
console.log(comA.title); // Vue.js
comA.sayHello(); // 弹窗
}
}
script>
不过,这两种方法的弊端是,无法在跨级或兄弟间通信。
parent.vue
<component-a>component-a>
<component-b>component-b>
<component-b>component-b>
我们想在 component-a 中,访问到引用它的页面中(这里就是 parent.vue)的两个 component-b 组件,那这种情况下,就得配置额外的插件或工具了,比如 Vuex 和 Bus 的解决方案。
多级组件嵌套需要传递数据时,通常使用的方法是通过vuex。但如果仅仅是传递数据,而不做中间处理,使用 vuex 处理,未免有点大材小用。为此Vue2.4 版本提供了另一种方法----$attrs
/$listeners
$attrs
:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件。通常配合 inheritAttrs 选项一起使用。
$listeners
:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件
1) 包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)
2) 当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外)
可以通过v-bind="$attrs" 将所有父作用域的绑定 (class、style、 ref 除外) 传入内部组件
注: 在创建高级别的组件时非常有用
根组件HelloWorld.vue 中引入 ipc.vue
<ipc
koa="ipcRef"
name="go"
id="id"
ref="ref"
style="border: 1px solid red;"
class="className"
>
ipc>
ipc.vue 中引入 ipcChild.vue
<template>
<div>
<ipcChild v-bind="$attrs" selfDefine="selfDefine">ipcChild>
div>
template>
<script>
import ipcChild from './ipcChild'
export default {
components: {
ipcChild
},
mounted () {
console.log(this.$attrs) // {id: "id", name: "go", koa: "ipcRef"}
}
}
script>
// ipcChild.vue中打印接收到的$attrs
<script>
export default {
created () {
console.log(this.$attrs) // "{"selfDefine":"selfDefine","koa":"ipcRef","name":"go","id":"id"}"
}
}
script>
包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器
通过 v-on="$listeners" 将父作用域的时间监听器传入内部组件
A、B、C三个组件依次嵌套, B嵌套在A中,C嵌套在B中。 借助 B 组件的中转,从上到下props依次传递,从下至上,$
emit事件的传递,达到跨级组件通信的效果。$
attrs以及$
listeners 的出现解决的的问题,B 组件在其中传递props以及事件的过程中,不必在写多余的代码,仅仅是将 $
attrs以及$
listeners 向上或者向下传递即可。
跨级通信的案例:
index.vue
<template>
<div>
<h2>浪里行舟h2>
<child-com1
:foo="foo"
:boo="boo"
:coo="coo"
:doo="doo"
title="前端工匠"
>child-com1>
div>
template>
<script>
const childCom1 = () => import("./childCom1.vue");
export default {
components: { childCom1 },
data() {
return {
foo: "Javascript",
boo: "Html",
coo: "CSS",
doo: "Vue"
};
}
};
script>
childCom1.vue
<template class="border">
<div>
<p>foo: {{ foo }}p>
<p>childCom1的$attrs: {{ $attrs }}p>
<child-com2 v-bind="$attrs">child-com2>
div>
template>
<script>
const childCom2 = () => import("./childCom2.vue");
export default {
components: {
childCom2
},
inheritAttrs: false, // 可以关闭自动挂载到组件根元素上的没有在props声明的属性
props: {
foo: String // foo作为props属性绑定
},
created() {
console.log(this.$attrs); // { "boo": "Html", "coo": "CSS", "doo": "Vue", "title": "前端工匠" }
}
};
script>
childCom2.vue
<template>
<div class="border">
<p>boo: {{ boo }}p>
<p>childCom2: {{ $attrs }}p>
<child-com3 v-bind="$attrs">child-com3>
div>
template>
<script>
const childCom3 = () => import("./childCom3.vue");
export default {
components: {
childCom3
},
inheritAttrs: false,
props: {
boo: String
},
created() {
console.log(this.$attrs); // { "coo": "CSS", "doo": "Vue", "title": "前端工匠" }
}
};
script>
childCom3.vue
<template>
<div class="border">
<p>childCom3: {{ $attrs }}p>
div>
template>
<script>
export default {
props: {
coo: String,
title: String
}
};
script>
$attrs
表示没有继承数据的对象,格式为{属性名:属性值}
。Vue2.4提供了$attrs
, $listeners
来传递数据与事件,跨级组件之间的通讯变得更简单。
简单来说:$attrs
与$listeners
是两个对象,$attrs
里存放的是父组件中绑定的非 Props 属性,$listeners
里存放的是父组件中绑定的非原生事件。
mixin (混入) (谨慎使用全局混入, 如有必要可以设计插件并引入使用)
参见官方文档
常见使用场景可以分为三类:
父子通信:
父向子传递数据是通过 props
,子向父是通过 events($emit
);通过父链 / 子链也可以通信($parent / $children)
;ref
也可以访问组件实例;provide / inject API
;$attrs/$listeners
兄弟通信:
Bus
;Vuex
跨级通信:
Bus
;Vuex
;provide
/ inject API
、$attrs
/$listeners