Почему данные, определенные в Vue.mixin, доступны по всему миру

внешний интерфейс Linux Vue.js Прием на работу

0. Фон

В настоящее время в бизнесе доктора Лайлака я буду отвечать за проект WebApp, основанный на корзине семейства Vue.

Всегда было что-то, чего я не совсем понимаю: в теге шаблона каждого компонента он будет использоватьdataReadyдля рендеринга управления. Например, как показано ниже, страница отображается после выполнения запроса.

## 模板部分
<template>
  <div class="wrap"
       v-if="dataReady">
  </div>
</template>

## Script部分

  async created() {
    await this.makeSomeRequest();
    this.dataReady = true;
  },

Но на самом деле я не определил его в опции данных компонентаdataReadyАтрибуты.

Итак, я проверил входной файлmain.js, есть такая фраза

  Vue.mixin({
    data() {
      return {
        dataReady: false
      };
    }
    // 以下省略
  });

Почему переменная, определенная глобально, доступна в каждом компоненте? Как Vue это делает?

Итак, просмотрев кучу информации и исходников, есть немного ответа.

1. Предварительное знание

Поскольку часть необходимых знаний сложно объяснить, я привожу их прямо в виде заключения:

  • Vue — это конструктор, черезnew VueКорневой экземпляр создан
  • Все однофайловые компоненты являются подклассами, расширенными Vue.extend.
  • Каждый тег шаблона в теге родительского компонента или компонент, отображаемый в функции рендеринга, является экземпляром соответствующего подкласса.

2. Начните с Vue.mixin

Исходный код выглядит так:

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

Это очень просто, достаточно развернуть параметры текущего объекта контекста и входящие параметры.

Так что делать что-то на самом делеmergeOptionsЭта функция, которая расширяет параметры статических свойств класса Vue.

тогда посмотримmergeOptions, что именно он сделал.

3. Слияние опций с mergeOptions в классе Vue

оказатьсяmergeOptionsИсходный код, запомните его.

export function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  // 中间好长一串代码,都跳过不看,暂时和data属性没关系。
  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
}

этоmergeOptionsфункция, по сути, просто проходитoptionsНа объекте пройдите его собственные свойства для выполненияmergeFieldFunction, затем верните новый Options.

Тогда проблема становится:mergeFieldЧто именно он сделал? Посмотрим на его код.

// 找到合并策略函数
const strat = strats[key] || defaultStrat

// 执行合并策略函数
options[key] = strat(parent[key], child[key], vm, key)

Теперь вспомните,

  • что такое родитель? - в данном случае Vue.options
  • что такое ребенок? Да, это объект параметра, передаваемый при использовании метода миксина.
  • Так в чем ключ? -- это ключ к свойству родительского или дочернего объекта.

Что ж, можно подтвердить, что дочерний объект должен содержать атрибут, ключом которого являются данные.

Хорошо, давайте узнаем, что это такоеstrats.data.

strats.data = function (
  // parentVal,在这个例子里,是Vue自身的options选项上的data属性,有可能不存在
  parentVal: any,
  
  // childVal,在这个例子里,是mixin方法传入的选项对象中的data属性
  childVal: any,
  vm?: Component
): ?Function {

  // 回想一下Vue.mixin的代码,会发现vm为空
  if (!vm) {
    if (childVal && typeof childVal !== 'function') {
      // 这个错误眼熟吗?想想如果你刚才.mixin的时候,传入的data如果不是函数,是不是就报错了?
      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
    }
    
    // 这条语句的返回值,将会在mergeField函数中,作为options.data的值。
    return mergeDataOrFn(parentVal, childVal)
  }
  // 在这个例子里,下面这行不会执行,为什么?自己想想。
  return mergeDataOrFn(parentVal, childVal, vm)
}

Хорошо, давайте посмотрим еще раз,mergeDataOrFn, что именно.

export function mergeDataOrFn (
  parentVal: any,
  childVal: any,
  vm?: Component
): ?Function {
  if (!vm) {
    // childVal是刚刚mixin方法的参数中的data属性,一个函数
    if (!childVal) {
      return parentVal
    }
    // parentVal是Vue.options.data属性,然鹅Vue属性并没有自带的data属性
    if (!parentVal) {
      return childVal
    }
    // 下边也不用看了,到这里就返回了。
  } else {
    // 这里不用看先,反正你也没有传递vm参数嘛
  }
}

Итак, это последнее слово?

Vue.options.data = function data(){
    return {
        dataReady: false
    }
}

4. Из класса Vue -> подкласс

Другими словами, атрибут data был добавлен в Vue.options только сейчас, почему однофайловые компоненты Vue, то есть подклассы, также могут использоваться в их экземплярах?

это оVue.extendфункция, она используется для расширения подклассов.Обычно однофайловые компоненты SFC, которые мы пишем, на самом деле являются подклассами класса Vue.

  Vue.extend = function (extendOptions: Object): Function {
    const Super = this
    
    // 你不用关心中间还有一些代码

    const Sub = function VueComponent (options) {
      this._init(options)
    }
    
    // 继承
    Sub.prototype = Object.create(Super.prototype)
    Sub.prototype.constructor = Sub
    Sub.cid = cid++
    
    // 注意这里也执行了options函数,做了选项合并工作。
    Sub.options = mergeOptions(
      Super.options,
      extendOptions
    )
    
    // 你不用关心中间还有一些代码

    
    // 把子类返回出去了。
    return Sub;
}
  • что такое расширенные опции?

На самом деле это то, что мы пишем в однофайловом компоненте, это может выглядеть так

export default {
    // 当然,也可能没有data函数
    data(){
        return{
            id: 0
        }
    },
    methods: {
        handleClick(){
            
        }
    }
}
  • Что такое Super.options?

В нашем проекте его нетVue -> Parent -> ChildТакие отношения множественного наследования, поэтому можно считать, чтоSuper.options, как упоминалось ранееVue.options!

запомнить? После выполнения Vue.mixin,Vue.optionsЕсть атрибут данных.

5. mergeOptions, когда класс Vue -> подкласс

Увидимся

Sub.options = mergeOptions(
  Super.options,
  extendOptions
)

мы снова вернулисьmergeOptionsфункция.

export function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  // 省略上面一些检查和规范化
  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
}

Как и раньше, он все равно вернет параметры и передаст ихSub.options.

Свойство options.data по-прежнему будетstrats.dataФункция стратегии выполняется один раз, но на этот раз процесс может быть другим.

Уведомление,parentValдаVue.options.data, в то время как childVal может бытьdataфункция, также может быть пустой. Почему? спросить фронтextendOptionsАх, какие параметры он передает.

strats.data = function (
  parentVal: any,
  childVal: any,
  vm?: Component
): ?Function {
  if (!vm) {
    if (childVal && typeof childVal !== 'function') {
        // 省略
    }
    // 没问题,还是执行这一句。
    return mergeDataOrFn(parentVal, childVal)
  }

  return mergeDataOrFn(parentVal, childVal, vm)
}

Мы видим, что процесс в основном тот же, или выполнениеreturn mergeDataOrFn(parentVal, childVal).

давайте посмотрим на этоmergeDataOrFn.

Сначала предположим, что childVal пуст.

export function mergeDataOrFn (
  parentVal: any,
  childVal: any,
  vm?: Component
): ?Function {
  if (!vm) {
    // 到这里就返回了
    if (!childVal) {
      return parentVal
    }
  } else {
    // 省略
  }
}

так что еслиextendOptionsЕсли атрибут данных (функция) не передан, то он будет использовать parentVal, т.е.Vue.options.data.

Следовательно, это можно понимать просто как

Sub.options.data = Vue.options.data = function data(){
    return {
        dataReady: false
    }
}

если этоextendOptionsПередать функцию данных? Мы можем продолжить поиск в функции mergeDataOrFn

    return function mergedDataFn () {
      return mergeData(
        typeof childVal === 'function' ? childVal.call(this, this) : childVal,
        typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal
      )
    }

То, что возвращается, является функцией.Учитывая, что и childVal, и parentVal здесь являются функциями, мы можем упростить код

// 现在假设子类的data选项长这样
function subData(){
        return{
            id: 0
        }
}

function vueData(){
    return {
        dataReady: false
    }
}

// Sub得到了什么?

Sub.options.data = function data(){
    return mergeData(
        subData.call(this, this),
        vueData.call(this, this)
    )
}

Пожалуйста, подумайте, что это такое, и скажите в конце.

Когда подкласс создается один раз,Sub.options.dataбудет казнен. Таким образом, вы получите результат этой формы.

return mergeData({ id: 0 }, { dataReady: false })

Принцип конкретного слияния данных также очень прост: обход ключа + глубокое слияние, и если ключ имеет такое же имя, то перезапись производиться не будет. Проверьте этоmergeDataЭта функция хороша, это не тема этой статьи.

Как выполнить создание экземпляра, как выполнитьdataЕсли вам интересно, вы можете узнать об этом самостоятельно.Кратко, это связано с тремя функциями:

  • Vue.prototype._init
  • initState
  • initData

7. Эпилог

Теперь вы понимаете, почему в каждом компоненте будетdataReady: falseВсе же?

На самом деле, если резюмировать это одним предложением, это так: функция данных в классе Vue (я называю ее parentDataFn) будет объединена с функцией данных подкласса (я называю ее childDataFn), чтобы получить новую функцию, которая будет использоваться в подклассе.Он выполняется во время преобразования, и parentDataFn и childDataFn выполняются одновременно, и возвращается объединенный объект данных.

Кстати, только что

Sub.options.data = function mergedDataFn(){
    return mergeData(
        subData.call(this, this),
        vueData.call(this, this)
    )
}

Здесь это пример подразделения.

8. Заключение

Честно говоря, я обычно писал небольшую статью после того, как закончил свою работу, чтобы лучше понять то, что я узнал, например:

Но все они очень простые «записи навыков» или «базовые исследования».

На этот раз я впервые пытаюсь понять сложную систему, такую ​​​​как исходный код Vue, Я беспокоюсь, что многие места могут ввести людей в заблуждение, поэтому я хотел бы поблагодарить следующие ссылки:

Если есть что-то еще, что не совсем правильно, пожалуйста, дайте больше комментариев.

Наконец-то набирается команда по работе с доктором Кловом.

Знакомство с командой здесь.

Если у вас есть какие-либо намерения или вопросы по поводу найма, вы можете написать автору в личном сообщении на Zhihu.

Автор: Инженер-конструктор отдела сиреневого сада@Kevin Wong