インタビュアー: Vue の mixin についての理解を教えてください。アプリケーション シナリオは何ですか?
1.ミックスインとは何ですか
Mixin
メソッド実装を提供するオブジェクト指向プログラミング言語のクラス。mixin
他のクラスは、そのクラスのサブクラスでなくても、そのクラスのメソッドにアクセスできます。
Mixin
クラスは通常、機能モジュールとして使用され、機能が必要なときに「混合」されます。これにより、コードの再利用が容易になり、多重継承の複雑さが回避されます。
Vue のミックスイン
まずは公式の定義を見てみましょう
mixin
(ミックスイン) は、コンポーネント内で再利用可能な機能を配布するための非常に柔軟な方法を提供しますVue
。
本質は実際にはオブジェクトでありjs
、コンポーネント内の関数オプション ( data
、components
、methods
、created
などcomputed
)を含めることができます。
mixins
共通関数をオブジェクトとしてオプションに渡すだけでよく、コンポーネントがmixins
そのオブジェクトを使用するときに、すべてのmixins
オブジェクトのオプションがコンポーネント自体のオプションに混合されます。
ローカルでもグローバルでも融合Vue
できる
部分的なミックスイン
コンポーネントとプロパティmixin
を使用してオブジェクトを定義するoptions
data
methods
var myMixin = {
created: function () {
this.hello()
},
methods: {
hello: function () {
console.log('hello from mixin!')
}
}
}
コンポーネントはプロパティを通じてオブジェクトmixins
を呼び出しますmixin
Vue.component('componentA',{
mixins: [myMixin]
})
このコンポーネントが使用されると、mixin
内部でメソッドが混合され、created
ライフフックが自動的に実行され、hello
メソッドが実行されます。
グローバルミックスイン
Vue.mixin()
グローバルミックスインを作成することで
Vue.mixin({
created: function () {
console.log("全局混入")
}
})
グローバル ミックスインを使用する場合は、すべてのコンポーネント インスタンス (サードパーティ コンポーネントを含む) に影響するため、特別な注意が必要です。
PS: グローバル ミックスインはプラグインの作成によく使用されます
予防:
コンポーネントにオブジェクトと同じオプションがある場合、再帰的マージが実行されるmixin
とコンポーネントのオプションが上書きされます。mixin
ただし、同じオプションがライフサイクルフックの場合は、配列にマージされ、フックが最初に実行されmixin
、次にコンポーネントのフックが実行されます。
2. 利用シーン
日常の開発では、異なるコンポーネントで使用する必要がある同じまたは類似のコードに遭遇することがよくありますが、これらのコードの機能は比較的独立しています。
Vue
このとき、mixin
関数を通じて同じまたは類似のコードを提案できます。
例えば
内部的に表示を制御するためにmodal
使用されるポップアップ ウィンドウ コンポーネントを定義します。isShowing
const Modal = {
template: '#modal',
data() {
return {
isShowing: false
}
},
methods: {
toggleShow() {
this.isShowing = !this.isShowing;
}
}
}
プロンプト ボックスを定義しますtooltip
。これは内部的にisShowing
制御されます。
const Tooltip = {
template: '#tooltip',
data() {
return {
isShowing: false
}
},
methods: {
toggleShow() {
this.isShowing = !this.isShowing;
}
}
}
上記 2 つのコンポーネントを観察すると、2 つのロジックは同じであり、コード制御の表示も同じであることがわかります。これはこの時点でmixin
役に立ちます。
まず共通コードを抽出し、mixin
const toggle = {
data() {
return {
isShowing: false
}
},
methods: {
toggleShow() {
this.isShowing = !this.isShowing;
}
}
}
2 つのコンポーネントを使用するには、導入するだけで済みます。mixin
const Modal = {
template: '#modal',
mixins: [toggle]
};
const Tooltip = {
template: '#tooltip',
mixins: [toggle]
}
Mixin
上記の小さな例を通して、いくつかの再利用可能な関数をカプセル化することが非常に興味深く、便利で、実用的であることがわかります。
3. ソースコード分析
Vue.mixin
から始める
ソースの場所: /src/core/global-api/mixin.js
export function initMixin (Vue: GlobalAPI) {
Vue.mixin = function (mixin: Object) {
this.options = mergeOptions(this.options, mixin)
return this
}
}
merOptions
主にメソッドを呼び出す
ソースの場所: /src/core/util/options.js
export function mergeOptions (
parent: Object,
child: Object,
vm?: Component
): Object {
if (child.mixins) {
// 判断有没有mixin 也就是mixin里面挂mixin的情况 有的话递归进行合并
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm)
}
}
const options = {
}
let key
for (key in parent) {
mergeField(key) // 先遍历parent的key 调对应的strats[XXX]方法进行合并
}
for (key in child) {
if (!hasOwn(parent, key)) {
// 如果parent已经处理过某个key 就不处理了
mergeField(key) // 处理child中的key 也就parent中没有处理过的key
}
}
function mergeField (key) {
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key) // 根据不同类型的options调用strats中不同的方法进行合并
}
return options
}
上記のソース コードから、次の点がわかります。
- 優先再帰処理
mixins
- まず merge をトラバースし
parent
、マージするメソッドをkey
呼び出して、それを変数に保存します。mergeField
options
- 再度トラバースし
child
、マージしてマージしてparent
、key
マージmergeField
するメソッドを呼び出し、変数に保存します。options
- 関数によって
mergeField
結合される
以下に、Vue
マージ戦略のいくつかのタイプを示します。
- 交換
- 組み合わせた
- キューの種類
- オーバーレイタイプ
交換
交換タイプにはprops
、methods
、inject
、が組み込まれています。computed
strats.props =
strats.methods =
strats.inject =
strats.computed = function (
parentVal: ?Object,
childVal: ?Object,
vm?: Component,
key: string
): ?Object {
if (!parentVal) return childVal // 如果parentVal没有值,直接返回childVal
const ret = Object.create(null) // 创建一个第三方对象 ret
extend(ret, parentVal) // extend方法实际是把parentVal的属性复制到ret中
if (childVal) extend(ret, childVal) // 把childVal的属性复制到ret中
return ret
}
strats.provide = mergeDataOrFn
同じ名前のprops
、methods
、inject
は、computed
後のものに置き換えられます。
組み合わせた
マージ タイプには次のものがあります。data
strats.data = function(parentVal, childVal, vm) {
return mergeDataOrFn(
parentVal, childVal, vm
)
};
function mergeDataOrFn(parentVal, childVal, vm) {
return function mergedInstanceDataFn() {
var childData = childVal.call(vm, vm) // 执行data挂的函数得到对象
var parentData = parentVal.call(vm, vm)
if (childData) {
return mergeData(childData, parentData) // 将2个对象进行合并
} else {
return parentData // 如果没有childData 直接返回parentData
}
}
}
function mergeData(to, from) {
if (!from) return to
var key, toVal, fromVal;
var keys = Object.keys(from);
for (var i = 0; i < keys.length; i++) {
key = keys[i];
toVal = to[key];
fromVal = from[key];
// 如果不存在这个属性,就重新设置
if (!to.hasOwnProperty(key)) {
set(to, key, fromVal);
}
// 存在相同属性,合并对象
else if (typeof toVal =="object" && typeof fromVal =="object") {
mergeData(toVal, fromVal);
}
}
return to
}
mergeData
この関数は、マージされるデータのすべての属性を調べて、さまざまな状況に応じてマージします。
- ターゲット データ オブジェクトに現在の属性が含まれていない場合は、
set
マージするメソッドを呼び出します (実際には、set メソッドはマージと再割り当てのメソッドです)。 - ターゲット データ オブジェクトに現在の属性が含まれており、現在の値が純粋なオブジェクトである場合、現在のオブジェクトの値を再帰的にマージします。これは、オブジェクトが新しい属性を持たないようにするために行われます。
列
キューに入れられたマージは次のとおりです: すべてのライフタイムとwatch
function mergeHook (
parentVal: ?Array<Function>,
childVal: ?Function | ?Array<Function>
): ?Array<Function> {
return childVal
? parentVal
? parentVal.concat(childVal)
: Array.isArray(childVal)
? childVal
: [childVal]
: parentVal
}
LIFECYCLE_HOOKS.forEach(hook => {
strats[hook] = mergeHook
})
// watch
strats.watch = function (
parentVal,
childVal,
vm,
key
) {
// work around Firefox's Object.prototype.watch...
if (parentVal === nativeWatch) {
parentVal = undefined; }
if (childVal === nativeWatch) {
childVal = undefined; }
/* istanbul ignore if */
if (!childVal) {
return Object.create(parentVal || null) }
{
assertObjectType(key, childVal, vm);
}
if (!parentVal) {
return childVal }
var ret = {
};
extend(ret, parentVal);
for (var key$1 in childVal) {
var parent = ret[key$1];
var child = childVal[key$1];
if (parent && !Array.isArray(parent)) {
parent = [parent];
}
ret[key$1] = parent
? parent.concat(child)
: Array.isArray(child) ? child : [child];
}
return ret
};
ライフサイクルフックはwatch
配列にマージされ、正の順序で走査されて実行されます。
オーバーレイタイプ
重複するマージは次のとおりです: component
、directives
、filters
strats.components=
strats.directives=
strats.filters = function mergeAssets(
parentVal, childVal, vm, key
) {
var res = Object.create(parentVal || null);
if (childVal) {
for (var key in childVal) {
res[key] = childVal[key];
}
}
return res
}
重ね合わせタイプは、主にプロトタイプ チェーンのレイヤーごとの重ね合わせによって行われます。
まとめ:
- 置換戦略には
props
、、、、、methods
が含まれますinject
。computed
これらは、古いパラメータを同じ名前の新しいパラメータに置き換えます。 - マージ戦略は、メソッド
data
によってset
マージおよび再割り当てを行うことです。 - キュータイプの戦略にはライフサイクル関数があり、
watch
その原理は関数を配列に格納し、それらを順番に走査して順次実行することです。 - 重ね合わせタイプには
component
、directives
、 、filters
、および プロトタイプ チェーンを通じてレイヤーごとに重ね合わされます。
参考文献
- https://zhuanlan.zhihu.com/p/31018570
- https://juejin.cn/post/6844904015495446536#Heading-1
- https://juejin.cn/post/6844903846775357453