引言
簡要總結於 掘金小冊->Vue組件精講
作者:Aresn iView作者
組件通信不管是在業務組件開發還是UI組件開發基本都是不可避免的,實際項目開發中一般會選擇引入vuex,這裏關於vuex不做過多介紹,不瞭解的可直接去 vuex官網 查看。這裏主要使用vue自身的一些東西來達到組件通信的目的。
provide / inject
provide / inject
是 Vue.js 2.2.0 版本後新增的 API,在文檔中這樣介紹 :
這對選項需要一起使用,以允許一個祖先組件向其所有子孫後代注入一個依賴,不論組件層次有多深,並在起上下游關係成立的時間裏始終生效。如果你熟悉 React,這與 React 的上下文特性很相似。
並且文檔中有如下提示:
provide 和 inject 主要爲高階插件/組件庫提供用例。並不推薦直接用於應用程序代碼中。
下面簡要描述使用的Demo
// A.vue
export default {
provide: {
name: 'Aresn'
}
}
// B.vue
export default {
inject: ['name'],
mounted () {
console.log(this.name); // Aresn
}
}
需要注意的是:
provide 和 inject 綁定並不是可響應的。這是刻意爲之的。然而,如果你傳入了一個可監聽的對象(傳入一個對象,對象爲引用傳遞),那麼其對象的屬性還是可響應的。
派發與廣播——自行實現 dispatch 和 broadcast 方法
$on 與 $emit
$on 與 $emit
常用於父子組件的通信,在vue1.x
中跨組件通信 $dispatch 和 $broadcast
也是常用的api,在vue2.x中被廢棄了。
$dispatch 和 $broadcast ,前者用於向上級派發事件,只要是它的父級(一級或多級以上),都可以在組件內通過 $on (或 events,2.x 已廢棄)監聽到,後者相反,是由上級向下級廣播事件的。
廢棄原因如下:
因爲基於組件樹結構的事件流方式有時讓人難以理解,並且在組件結構擴展的過程中會變得越來越脆弱。
自行實現 dispatch 和 broadcast 方法
前提需要爲每一個組件添加name
屬性的標識,方便遞歸與遍歷查找。
function broadcast(componentName, eventName, params) {
this.$children.forEach(child => {
const name = child.$options.name;
if (name === componentName) {
child.$emit.apply(child, [eventName].concat(params));
} else {
broadcast.apply(child, [componentName, eventName].concat([params]));
}
});
}
export default {
methods: {
dispatch(componentName, eventName, params) {
let parent = this.$parent || this.$root;
let name = parent.$options.name;
while (parent && (!name || name !== componentName)) {
parent = parent.$parent;
if (parent) {
name = parent.$options.name;
}
}
if (parent) {
parent.$emit.apply(parent, [eventName].concat(params));
}
},
broadcast(componentName, eventName, params) {
broadcast.call(this, componentName, eventName, params);
}
}
};
上面兩個方法,大家可自己測試!
找到任意組件實例——findComponents 系列方法
provide / inject 和 dispatch / broadcast。它們有各自的使用場景和侷限,比如前者多用於子組件獲取父組件的狀態,後者常用於父子跨級組件間通過自定義事件通信。
findComponents 系列方法最終都是返回組件的實例,進而可以讀取或調用該組件的數據和方法。
它適用於以下場景:
- 由一個組件,向上找到最近的指定組件;
- 由一個組件,向上找到所有的指定組件;
- 由一個組件,向下找到最近的指定組件;
- 由一個組件,向下找到所有指定的組件;
- 由一個組件,找到指定組件的兄弟組件。
5 個不同的場景,對應 5 個不同的函數,實現原理也大同小異。
下面爲具體方法實現:
// 由一個組件,向上找到最近的指定組件
function findComponentUpward (context, componentName) {
let parent = context.$parent;
let name = parent.$options.name;
while (parent && (!name || [componentName].indexOf(name) < 0)) {
parent = parent.$parent;
if (parent) name = parent.$options.name;
}
return parent;
}
export { findComponentUpward };
// 由一個組件,向上找到所有的指定組件
function findComponentsUpward (context, componentName) {
let parents = [];
const parent = context.$parent;
if (parent) {
if (parent.$options.name === componentName) parents.push(parent);
return parents.concat(findComponentsUpward(parent, componentName));
} else {
return [];
}
}
export { findComponentsUpward };
// 由一個組件,向下找到最近的指定組件
function findComponentDownward (context, componentName) {
const childrens = context.$children;
let children = null;
if (childrens.length) {
for (const child of childrens) {
const name = child.$options.name;
if (name === componentName) {
children = child;
break;
} else {
children = findComponentDownward(child, componentName);
if (children) break;
}
}
}
return children;
}
export { findComponentDownward };
// 由一個組件,向下找到所有指定的組件
function findComponentsDownward (context, componentName) {
return context.$children.reduce((components, child) => {
if (child.$options.name === componentName) components.push(child);
const foundChilds = findComponentsDownward(child, componentName);
return components.concat(foundChilds);
}, []);
}
export { findComponentsDownward };
// 由一個組件,找到指定組件的兄弟組件
function findBrothersComponents (context, componentName, exceptMe = true) {
let res = context.$parent.$children.filter(item => {
return item.$options.name === componentName;
});
let index = res.findIndex(item => item._uid === context._uid);
if (exceptMe) res.splice(index, 1);
return res;
}
export { findBrothersComponents };
function typeOf(obj) {
const toString = Object.prototype.toString;
const map = {
'[object Boolean]' : 'boolean',
'[object Number]' : 'number',
'[object String]' : 'string',
'[object Function]' : 'function',
'[object Array]' : 'array',
'[object Date]' : 'date',
'[object RegExp]' : 'regExp',
'[object Undefined]': 'undefined',
'[object Null]' : 'null',
'[object Object]' : 'object'
};
return map[toString.call(obj)];
}
// deepCopy
function deepCopy(data) {
const t = typeOf(data);
let o;
if (t === 'array') {
o = [];
} else if ( t === 'object') {
o = {};
} else {
return data;
}
if (t === 'array') {
for (let i = 0; i < data.length; i++) {
o.push(deepCopy(data[i]));
}
} else if ( t === 'object') {
for (let i in data) {
o[i] = deepCopy(data[i]);
}
}
return o;
}
export {deepCopy};
具體使用大家可自行測試!