vue mixins(混入)遇到的坑以及原理分析

背景

mixins的一般用法

就我自己個人而言,mixins一般用的比較多的就是定義一個混入對象,然后在組件里或者新建的vue對象里使用mixins,用法簡單明了厅目。基本上和官網(wǎng)用法一樣法严,這里以官網(wǎng)示例為例:

// 定義一個混入對象
var myMixin = {
  created: function () {
    this.hello()
  },
  methods: {
    hello: function () {
      console.log('hello from mixin!')
    }
  }
}

// 定義一個使用混入對象的組件
var Component = Vue.extend({
  mixins: [myMixin]
})

var component = new Component() // => "hello from mixin!"
VM1740:8 hello from mixin!

全局使用mixins

直到最近想寫一個通用業(yè)務(wù)(埋點)损敷,希望能在公共文件引入,不需要改動其他文件的前提下深啤,比如統(tǒng)計進(jìn)入頁面拗馒、離開頁面的時間,各個生命周期的時間等等墓塌。很不湊巧瘟忱,我用了全局mixins,才發(fā)現(xiàn)原來所有的vue實例都會被統(tǒng)計到苫幢,不僅僅是當(dāng)前頁面的vue實例访诱,而是當(dāng)前頁面所有用到的子組件都會被統(tǒng)計到。而全局mixins使用的警告韩肝,官網(wǎng)早已經(jīng)告知過触菜,果然什么都要自己試試才會印象深刻,之前也看到過這段文字警告:

也可以全局注冊混入對象哀峻。
但是注意使用涡相!
 一旦使用全局混入對象,將會影響到 所有 之后創(chuàng)建的 Vue 實例剩蟀。
使用恰當(dāng)時催蝗,則可以為自定義對象注入處理邏輯。

還是古人有智慧育特,“紙上得來終覺淺丙号,絕知此事要躬行”!

mixin原理

這就引起了我的好奇缰冤,全局的mixins為什么會影響到所有的子組件犬缨?為此特地下載了最新的vue 源碼,2.6.8版本棉浸,查看了下源碼怀薛,才發(fā)現(xiàn)有部分語法沒見過,原來是flow的語法檢查迷郑,嗯枝恋,很抱歉用了這么久的2.x 版本的vue竟然不知道這個。
mixins文件代碼很少嗡害,如下:

import { mergeOptions } from '../util/index'

export function initMixin (Vue: GlobalAPI) {
  Vue.mixin = function (mixin: Object) {
    this.options = mergeOptions(this.options, mixin)
    return this
  }
}

可以看出鼓择,其實就是把當(dāng)前Vue實例的options和傳入的mixin合并,再返回就漾。真正的實現(xiàn)是靠mergeOptions函數(shù)實現(xiàn)的呐能。

mergeOptions函數(shù)實現(xiàn)

export function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  if (process.env.NODE_ENV !== 'production') {
    checkComponents(child)
  }
  if (typeof child === 'function') {
    child = child.options
  }
  normalizeProps(child, vm)
  normalizeInject(child, vm)
  normalizeDirectives(child)
  //上面的代碼可以略過不看,主要是檢查各種格式的
  if (!child._base) {
    if (child.extends) {
      parent = mergeOptions(parent, child.extends, vm)
    }
    if (child.mixins) {
      //處理mixins
      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) {
    //這里parent是this.options
    mergeField(key)
  }
  for (key in child) {
    //這里child是mixins抑堡,同時檢查parent中是否已經(jīng)有key即是否合并過
    if (!hasOwn(parent, key)) {
      mergeField(key)
    }
  }
  function mergeField (key) {
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}

上面的注釋已經(jīng)標(biāo)明摆出,首先我們要明確這個函數(shù)傳進(jìn)去的兩個參數(shù)分別是this.options 和 mixin,而mergeOptions函數(shù)則實現(xiàn)了遞歸遍歷this.options首妖,然后執(zhí)行mergeField偎漫,返回最終合并的this.options。到這里基本上就是mixin的所有實現(xiàn)了有缆。但是mergeField函數(shù)看似簡單象踊,實際上是很重要的温亲,我還是很好奇這個函數(shù)的實現(xiàn)的。

mergeField函數(shù)實現(xiàn)

const strats = config.optionMergeStrategies
//默認(rèn)的合并策略
const defaultStrat = function (parentVal: any, childVal: any): any {
  return childVal === undefined
    ? parentVal
    : childVal
}
function mergeField (key) {
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }

于是我又在代碼里找了找杯矩,發(fā)現(xiàn)strats這個對象有很多屬性栈虚,如下:

strats.el = strats.propsData = function (parent, child, vm, key) {
strats.data = function (
strats.watch = function (
strats.props =
strats.methods =
strats.inject =
strats.computed = function (
.....
.....

這個時候就很清晰了,一般我們執(zhí)行mergeField 里的key基本上就是上面strats的屬性了史隆,用的最多的可能就是data魂务、methods、props了泌射。所以如果我們在mixins中用到了data粘姜,其本質(zhì)上就是合并當(dāng)前vue實例對象里的data和我們傳進(jìn)去的mixin里的data,其他屬性也是一樣的熔酷,只是合并策略還需深入研究孤紧。

不得不感嘆,vue官網(wǎng)真的沒有任何廢話拒秘,官網(wǎng)其實早已給出了選項合并策略坛芽。

  • 當(dāng)組件和混入對象含有同名選項時,這些選項將以恰當(dāng)?shù)姆绞交旌弦砜佟1热缌瑪?shù)據(jù)對象在內(nèi)部會進(jìn)行遞歸合并,在和組件的數(shù)據(jù)發(fā)生沖突時以組件數(shù)據(jù)優(yōu)先阴颖。
  • 值為對象的選項活喊,例如 methods, components 和 directives,將被混合為同一個對象量愧。兩個對象鍵名沖突時钾菊,取組件對象的鍵值對。

而回到代碼層面上偎肃,如果是key為data的話煞烫,代碼實現(xiàn)如下:

strats.data = function (
  parentVal: any,
  childVal: any,
  vm?: Component
): ?Function {
  if (!vm) {
    if (childVal && typeof childVal !== 'function') {
      process.env.NODE_ENV !== 'production' && warn(
        'The "data" option should be a function ' +
        'that returns a per-instance value in component ' +
        'definitions.',
        vm
      )
      return parentVal
    }
    return mergeDataOrFn(parentVal, childVal)
  }
  return mergeDataOrFn(parentVal, childVal, vm)
}

如果key是 methods, components 和 directives

strats.props =
strats.methods =
strats.inject =
strats.computed = function (
  parentVal: ?Object,
  childVal: ?Object,
  vm?: Component,
  key: string
): ?Object {
  if (childVal && process.env.NODE_ENV !== 'production') {
    assertObjectType(key, childVal, vm)
  }
  if (!parentVal) return childVal
  const ret = Object.create(null)
  extend(ret, parentVal)
  if (childVal) extend(ret, childVal)
  return ret
}

組件局部注冊的原理

其實到這里,我依然不知道為什么全局mixins會影響到所有的子組件累颂,直到我發(fā)現(xiàn)了這段代碼:

function initAssetRegisters (Vue) {
    /**
     * Create asset registration methods.
     */
    ASSET_TYPES.forEach(function (type) {
      Vue[type] = function (
        id,
        definition
      ) {
        if (!definition) {
          return this.options[type + 's'][id]
        } else {
          /* istanbul ignore if */
          if (type === 'component') {
            validateComponentName(id);
          }
          if (type === 'component' && isPlainObject(definition)) {
            definition.name = definition.name || id;
            definition = this.options._base.extend(definition);
          }
          if (type === 'directive' && typeof definition === 'function') {
            definition = { bind: definition, update: definition };
          }
          this.options[type + 's'][id] = definition;
          return definition
        }
      };
    });
  }

直到看到這段代碼滞详,終于明白了為何會影響所有子組件了,局部注冊組件紊馏,本質(zhì)上其實是Vue.extends().而extend里面最終也會執(zhí)行mergeOptions()函數(shù)料饥。至此,終于明白了全局mixins的影響以及實現(xiàn)原理朱监。不過岸啡,extend就不再在這里多聊了,等我下次把extend看明白了赫编,再總結(jié)吧巡蘸。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末奋隶,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子悦荒,更是在濱河造成了極大的恐慌唯欣,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件逾冬,死亡現(xiàn)場離奇詭異黍聂,居然都是意外死亡躺苦,警方通過查閱死者的電腦和手機身腻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來匹厘,“玉大人嘀趟,你說我怎么就攤上這事∮希” “怎么了她按?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長炕柔。 經(jīng)常有香客問我酌泰,道長,這世上最難降的妖魔是什么匕累? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任陵刹,我火速辦了婚禮,結(jié)果婚禮上欢嘿,老公的妹妹穿的比我還像新娘衰琐。我一直安慰自己,他們只是感情好炼蹦,可當(dāng)我...
    茶點故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布羡宙。 她就那樣靜靜地躺著,像睡著了一般掐隐。 火紅的嫁衣襯著肌膚如雪狗热。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天虑省,我揣著相機與錄音斗搞,去河邊找鬼。 笑死慷妙,一個胖子當(dāng)著我的面吹牛僻焚,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播膝擂,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼虑啤,長吁一口氣:“原來是場噩夢啊……” “哼隙弛!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起狞山,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤全闷,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后萍启,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體总珠,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年勘纯,在試婚紗的時候發(fā)現(xiàn)自己被綠了局服。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡驳遵,死狀恐怖淫奔,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情堤结,我是刑警寧澤唆迁,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布,位于F島的核電站竞穷,受9級特大地震影響唐责,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜瘾带,卻給世界環(huán)境...
    茶點故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一鼠哥、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧月弛,春花似錦肴盏、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至厉萝,卻和暖如春恍飘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背谴垫。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工章母, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人翩剪。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓乳怎,卻偏偏與公主長得像,于是被迫代替她去往敵國和親前弯。 傳聞我的和親對象是個殘疾皇子蚪缀,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,728評論 2 351

推薦閱讀更多精彩內(nèi)容