<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【Vuejs】887- 深入淺出 Vue Mixin

          共 18129字,需瀏覽 37分鐘

           ·

          2021-03-04 08:58

          作者:flyyang 

          https://flyyang.me/2019/01/24/vue-mixin/


          mixin, 意為混入。

          比如去買冰激凌,我先要一點奶油的,再來點香草的。我就可以吃一個奶油香草的冰激凌。如果再加點草莓,我可以同時吃三個口味的冰激凌。

          代碼表示

          假設(shè)把你已有的奶油味的稱為 base,把要添加的味道稱為 mixins。用 js 偽代碼可以這么來寫:

          const base = {
            hasCreamFlavor() {
              return true;
            }
          }
          const mixins = {
            hasVanillaFlavor() {
              return true;
            },
            hasStrawberryFlavor() {
              return true;
           }
          }

          function mergeStrategies(base, mixins{
            return Object.assign({}, base, mixins);
          }
          // newBase 就擁有了三種口味。
          const newBase = mergeStrategies(base, mixins);

          注意一下這個 mergeStrategies

          合并策略可以你想要的形式,也就是說你可以自定義自己的策略,這是其一。另外要解決沖突的問題。上面是通過 Object.assign 來實現(xiàn)的,那么 mixins 內(nèi)的方法會覆蓋base 內(nèi)的內(nèi)容。如果這不是你期望的結(jié)果,可以調(diào)換 mixin 和 base 的位置。

          組合大于繼承 && DRY

          想象一下上面的例子用繼承如何實現(xiàn)?由于 js 是單繼承語言,只能一層層繼承。寫起來很繁瑣。這里就體現(xiàn)了 mixin 的好處。符合組合大于繼承的原則。

          mixin 內(nèi)通常是提取了公用功能的代碼。而不是每一個地方都寫一遍。符合 DRY 原則。

          什么是 vue mixin

          vue mixin 是針對組件間功能共享來做的。可以對組件的任意部分(生命周期, data等)進行mixin,但不同的 mixin 之后的合并策略不同。在源碼分析部分會介紹細節(jié)。

          組件級 mixin

          假設(shè)兩個功能組件 model 和 tooltip ,他們都有一個顯示和關(guān)閉的 toggle 動作:

          //modal
          const Modal = {
            template'#modal',
            data() {
              return {
                isShowingfalse
              }
            },
            methods: {
              toggleShow() {
                this.isShowing = !this.isShowing;
              }
            }
          }

          //tooltip
          const Tooltip = {
            template'#tooltip',
            data() {
              return {
                isShowingfalse
              }
            },
            methods: {
              toggleShow() {
                this.isShowing = !this.isShowing;
              }
            }
          }

          可以用 mixin 這么寫:

          const toggleMixin = {
            data() {
              return {
                isShowingfalse
              }
            },
            methods: {
              toggleShow() {
                this.isShowing = !this.isShowing;
              }
            }
          }

          const Modal = {
            template'#modal',
            mixins: [toggleMixin]
          };

          const Tooltip = {
            template'#tooltip',
            mixins: [toggleMixin],
          };

          全局 mixin

          全局 mixin 會作用到每一個 vue 實例上。所以使用的時候要慎重。通常會用 plugin 來顯示的聲明用到了那些 mixin。

          比如 vuex。我們都知道它在每一個實例上擴展了一個 store。那么他是如何實現(xiàn)的呢?

          src/mixin.js 內(nèi)

          export default function (Vue{
            const version = Number(Vue.version.split('.')[0])

            if (version >= 2) {
              Vue.mixin({ beforeCreate: vuexInit })
            } else {
              // override init and inject vuex init procedure
              // for 1.x backwards compatibility.
              const _init = Vue.prototype._init
              Vue.prototype._init = function (options = {}{
                options.init = options.init
                  ? [vuexInit].concat(options.init)
                  : vuexInit
                _init.call(this, options)
              }
            }
            /**
             * Vuex init hook, injected into each instances init hooks list.
             */


            function vuexInit () {
              const options = this.$options
              // store injection
              if (options.store) {
                this.$store = typeof options.store === 'function'
                  ? options.store()
                  : options.store
              } else if (options.parent && options.parent.$store) {
                this.$store = options.parent.$store
              }
            }
          }

          我們看到 在 Vue 2.0 以上版本,通過 Vue.mixin({ beforeCreate: vuexInit })實現(xiàn)了在每一個實例的 beforeCreate 生命周期調(diào)用vuexInit 方法。

          而 vuexInit 方法則是:在跟節(jié)點我們會直接把store 注入,在其他節(jié)點則拿父級節(jié)點的 store,這樣this.$store 永遠是你在根節(jié)點注入的那個store。

          vue mixin 源碼實現(xiàn)

          在 Vuex 的例子中,我們通過 Vue.mixin({ beforeCreate: vuexInit }) 實現(xiàn)對實例的 $store 擴展。

          全局 mixin 注冊

          我們先看一下 mixin 是如何掛載到原型上的。

          src/core/index.js 中:

          import Vue from './instance/index'
          import { initGlobalAPI } from './global-api/index'

          initGlobalAPI(Vue)

          export default Vue

          我們發(fā)現(xiàn)有一個 initGlobalAPI。在 src/global-api/index 中:

          /* @flow */

          import config from '../config'
          import { initUse } from './use'
          import { initMixin } from './mixin'
          import { initExtend } from './extend'
          import { initAssetRegisters } from './assets'
          import { set, del } from '../observer/index'
          import { ASSET_TYPES } from 'shared/constants'
          import builtInComponents from '../components/index'

          import {
            warn,
            extend,
            nextTick,
            mergeOptions,
            defineReactive
          from '../util/index'

          export function initGlobalAPI (Vue: GlobalAPI{
            // config
            const configDef = {}
            configDef.get = () => config
            if (process.env.NODE_ENV !== 'production') {
              configDef.set = () => {
                warn(
                  'Do not replace the Vue.config object, set individual fields instead.'
                )
              }
            }
            Object.defineProperty(Vue, 'config', configDef)

            // exposed util methods.
            // NOTE: these are not considered part of the public API - avoid relying on
            // them unless you are aware of the risk.
            Vue.util = {
              warn,
              extend,
              mergeOptions,
              defineReactive
            }

            Vue.set = set
            Vue.delete = del
            Vue.nextTick = nextTick

            Vue.options = Object.create(null)
            ASSET_TYPES.forEach(type => {
              Vue.options[type + 's'] = Object.create(null)
            })

            // this is used to identify the "base" constructor to extend all plain-object
            // components with in Weex's multi-instance scenarios.
            Vue.options._base = Vue

            extend(Vue.options.components, builtInComponents)

            initUse(Vue)
            initMixin(Vue)
            initExtend(Vue)
            initAssetRegisters(Vue)
          }

          所有全局的方法都在這里注冊。我們關(guān)注 initMixin 方法,定義在 src/core/global-api/mixin.js:

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

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

          至此我們發(fā)現(xiàn)了 Vue 如何掛載全局 mixin。

          mixin 合并策略

          vuex 通過 beforeCreate Hook 實現(xiàn)為所有 vm 添加 $store 實例。讓我們先把 hook 的事情放一邊。看一看 beforeCreate 如何實現(xiàn)。

          src/core/instance/init.js 中:

          export function initMixin (Vue: Class<Component>{
            Vue.prototype._init = function (options?: Object{
              // remove unrelated code
              initLifecycle(vm)
              initEvents(vm)
              initRender(vm)
              callHook(vm, 'beforeCreate')
              initInjections(vm) // resolve injections before data/props
              initState(vm)
              initProvide(vm) // resolve provide after data/props
              callHook(vm, 'created')

              // remove unrelated code
              if (vm.$options.el) {
                vm.$mount(vm.$options.el)
              }
            }
          }

          我們可以看到在 initRender 完成后,會調(diào)用 callHook(vm, 'beforeCreate')。而 init 實在 vue 實例化會執(zhí)行的。

          src/core/instance/lifecycle.js 中:

          export function callHook (vm: Component, hook: string{
            // #7573 disable dep collection when invoking lifecycle hooks
            pushTarget()
            const handlers = vm.$options[hook]
            if (handlers) {
              for (let i = 0, j = handlers.length; i < j; i++) {
                try {
                  handlers[i].call(vm)
                } catch (e) {
                  handleError(e, vm, `${hook} hook`)
                }
              }
            }
            if (vm._hasHookEvent) {
              vm.$emit('hook:' + hook)
            }
            popTarget()
          }

          在對 beforeCreate 執(zhí)行 callHook 過程中,會先從 vue 實例的 options 中取出所有掛載的 handlers。然后循環(huán)調(diào)用 call 方法執(zhí)行所有的 hook:

          handlers[i].call(vm)

          由此我們可以了解到全局的 hook mixin 會和要 mixin 的組件合并 hook,最后生成一個數(shù)組。

          回頭再看:

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

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

          this.options 默認是 vue 內(nèi)置的一些 option:

          image

          mixin 就是你要混入的對象。我們來看一看 mergeOptions。定義在 src/core/util/options.js:

          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)
            const extendsFrom = child.extends
            if (extendsFrom) {
              parent = mergeOptions(parent, extendsFrom, vm)
            }
            if (child.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) {
              mergeField(key)
            }
            for (key in child) {
              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
          }

          忽略不相干代碼我們直接跳到:

            for (key in child) {
              if (!hasOwn(parent, key)) {
                mergeField(key)
              }
            }
            function mergeField (key{
              const strat = strats[key] || defaultStrat
              options[key] = strat(parent[key], child[key], vm, key)
            }

          此時 child 為 { beforeCreate: vuexInit }。走入到 mergeField 流程。mergeField 先取合并策略。

          const strat = strats[key] || defaultStrat,相當(dāng)于取 strats['beforeCreate'] 的合并策略。定義在通文件的上方:

          /**
           * Hooks and props are merged as arrays.
           */

          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
          })

          // src/shared/constants.js

          export const LIFECYCLE_HOOKS = [
            'beforeCreate',
            'created',
            'beforeMount',
            'mounted',
            'beforeUpdate',
            'updated',
            'beforeDestroy',
            'destroyed',
            'activated',
            'deactivated',
            'errorCaptured'
          ]

          在  mergeHook 中的合并策略是把所有的 hook 生成一個函數(shù)數(shù)組。其他相關(guān)策略可以在options 文件中查找(如果是對象,組件本身的會覆蓋上層,data 會執(zhí)行結(jié)果,返回再merge,hook則生成數(shù)組)。

          mixin 早于實例化

          mergeOptions 會多次調(diào)用,正如其注釋說描述的那樣:

          /**
           * Merge two option objects into a new one.
           * Core utility used in both instantiation and inheritance.
           */

          上面介紹了全局 mixin 的流程,我們來看下 實例化部分的流程。在 src/core/instance/init.js 中:

          export function initMixin (Vue: Class<Component>{
            Vue.prototype._init = function (options?: Object{
              if (options && options._isComponent) {
                // optimize internal component instantiation
                // since dynamic options merging is pretty slow, and none of the
                // internal component options needs special treatment.
                initInternalComponent(vm, options)
              } else {
                vm.$options = mergeOptions(
                  resolveConstructorOptions(vm.constructor),
                  options || {},
                  vm
                )
              }
              // expose real self
              vm._self = vm
              initLifecycle(vm)
              initEvents(vm)
              initRender(vm)
              callHook(vm, 'beforeCreate')
              initInjections(vm) // resolve injections before data/props
              initState(vm)
              initProvide(vm) // resolve provide after data/props
              callHook(vm, 'created')
              if (vm.$options.el) {
                vm.$mount(vm.$options.el)
              }
            }
          }

          由于 全局 mixin 通常放在最上方。所以一個 vue 實例,通常是內(nèi)置的 options + 全局 mixin 的 options +用戶自定義options,加上合并策略生成最終的 options.

          那么對于 hook 來說是[mixinHook, userHook]。mixin 的hook 函數(shù)優(yōu)先于用戶自定義的 hook 執(zhí)行。

          local mixin

          在 組件中書寫 mixin 過程中:

          const Tooltip = {
            template'#tooltip',
            mixins: [toggleMixin],
          };

          在 mergeOptions 的過程中有下面一段代碼:

            if (child.mixins) {
              for (let i = 0, l = child.mixins.length; i < l; i++) {
                parent = mergeOptions(parent, child.mixins[i], vm)
              }
            }

          當(dāng) tooltip 實例化時,會將對應(yīng)的參數(shù) merge 到實例中。

          定制合并策略

          Vue.config.optionMergeStrategies.myOption = function (toVal, fromVal{
            // return mergedVal
          }

          以上。

          參考

          • http://techsith.com/mixins-in-javascript/
          • https://vuejs.org/v2/guide/mixins.html
          • https://css-tricks.com/using-mixins-vue-js/

          1. JavaScript 重溫系列(22篇全)
          2. ECMAScript 重溫系列(10篇全)
          3. JavaScript設(shè)計模式 重溫系列(9篇全)
          4. 正則 / 框架 / 算法等 重溫系列(16篇全)
          5. Webpack4 入門(上)|| Webpack4 入門(下)
          6. MobX 入門(上) ||  MobX 入門(下)
          7. 100+篇原創(chuàng)系列匯總

          回復(fù)“加群”與大佬們一起交流學(xué)習(xí)~

          點擊“閱讀原文”查看 100+ 篇原創(chuàng)文章

          瀏覽 74
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美A片免费在线观看 | 亚洲AV无码乱码国产精品蜜芽 | 激情久久一区 | 亚洲国产永久精品成人麻豆 | 強姧伦久久久久久久 |