Интервьюер ByteDance: Расскажите, пожалуйста, как работает vuex (дело в нескольких строчках кода)

Vue.js

Я не знаю, почему статьи «Наггетса» недавно популярны с «Bytedance интервьюеру» как начало, и мне смущается, чтобы сказать, что я посетил наггетс без волны. 23333.

Это действительно сезон интервью, так что давайте поговорим об этомVuexисходный код. Прочитав его, вы обнаружите, что принцип реализации Vue и Vuex в основном состоит всего из нескольких строк кода.

Двусторонняя привязка Vue

сказатьVuexДвусторонняя привязка должна начинаться сVueДвусторонняя привязка начинается с

VueГоворят, что большинство двусторонних креплений очень детализированы, здесь есть тонкий момент из-за фокуса илиVuex

Из исходного кода Vue, двусторонняя привязка Vue в основном делает две вещи

  1. захват данных
  2. добавить наблюдателя

Реализация захвата данных: (упрощен исходный код)

// 老版本通过 Object.defineProperty 递归可以实现

// src/core/observer/index.js
Object.defineProperty(obj, key, {
  enumerable: true,
  configurable: true,
  get: function reactiveGetter () {
    const value = getter ? getter.call(obj) : val
    if (Dep.target) {
      dep.depend()
      if (childOb) {
        childOb.dep.depend()
      }
      if (Array.isArray(value)) {
        dependArray(value)
      }
    }
    return value
  },
  set: function reactiveSetter (newVal) {
    const value = getter ? getter.call(obj) : val
    if (newVal === value || (newVal !== newVal && value !== value)) {
      return
    }
    if (setter) {
      setter.call(obj, newVal)
    } else {
      val = newVal
    }
    childOb = !shallow && observe(newVal)
    dep.notify()
  }
})

Это не что иное, как захват методов get и set объекта. В методе get прокси-свойства, когдаdep.Targetбудет вызываться, когда он существуетdep.depend(),

Фокус: 2 строки кода

  1. Object.defineProperty
  2. dep.depend()
// 最新版可以通过 Proxy 实现
Proxy(data, {
  get(target, key) {
    return target[key];
  },
  set(target, key, value) {
    let val = Reflect.set(target, key, value);
      _that.$dep[key].forEach(item => item.update());
    return val;
  }
})

Как видно из приведенного выше кода, это не что иное, как захват методов get и set объекта. Самая важная часть помимо захвата данныхDepа такжеWatcher, который на самом деле является шаблоном наблюдателя. Реализуйте следующий шаблон Vue Observer с максимально простым кодом.

Реализация режима наблюдателя: (сокращение исходного кода)

    // 观察者
    class Dep {
        constructor() {
            this.subs = []
        }
        
        addSub(sub) {
            this.subs.push(sub)
        }
        
        depend() {
            if (Dep.target) { 
                Dep.target.addDep(this);
            }
        }
        
        notify() {
            this.subs.forEach(sub => sub.update())
        }
    }
    
    // 被观察者
    class Watcher {
        constructor(vm, expOrFn) {
            this.vm = vm;
            this.getter = expOrFn;
            this.value;
        }

        get() {
            Dep.target = this;
            
            var vm = this.vm;
            var value = this.getter.call(vm, vm);
            return value;
        }

        evaluate() {
            this.value = this.get();
        }

        addDep(dep) {
            dep.addSub(this);
        }
        
        update() {
            console.log('更新, value:', this.value)
        }
    }
    
    // 观察者实例
    var dep = new Dep();
    
    //  被观察者实例
    var watcher = new Watcher({x: 1}, (val) => val);
    watcher.evaluate();
    
    // 观察者监听被观察对象
    dep.depend()
    
    dep.notify()

Фокус: 3 вещи

  1. пройти черезwatcher.evaluate()присвоить экземпляр самого себяDep.target
  2. передачаdep.depend()Вставьте экземпляр dep и экземпляр наблюдателя в dep.subs
  3. При перехвате данных, когда вызывается метод set захваченного объекта, вызываются все dep.subswatcher.update()

Впредь. Двусторонняя привязка завершена.

плагин vuex

С учетом вышеизложенного в качестве предзнаменования мы можем легко объяснить принцип работы vuex.

Vuex — это просто плагин для Vue. Vuex можно использовать только на vue, потому что он сильно зависит от двусторонней привязки Vue и системы плагинов.

Код внедрения Vuex относительно прост, назовем егоapplyMixinметод, текущая версия фактически вызываетVue.mixin, во всех компонентахbeforeCreateЖизненный цикл внедряет настройкиthis.$storeтакой объект.

// src/store.js
export function install (_Vue) {
  if (Vue && _Vue === Vue) {
    return
  }
  Vue = _Vue
  applyMixin(Vue)
}
// src/mixins.js
export default function (Vue) {
  const version = Number(Vue.version.split('.')[0])

  if (version >= 2) {
    Vue.mixin({ beforeCreate: vuexInit })
  } else {
    const _init = Vue.prototype._init
    Vue.prototype._init = function (options = {}) {
      options.init = options.init
        ? [vuexInit].concat(options.init)
        : vuexInit
      _init.call(this, options)
    }
  }

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

Фокус: 1 строка кодаVue.mixin

ТакVuex.StoreКак это достигается?

// src/store.js
constructor (options = {}) {
  const {
    plugins = [],
    strict = false
  } = options

  // store internal state
  this._committing = false
  this._actions = Object.create(null)
  this._actionSubscribers = []
  this._mutations = Object.create(null)
  this._wrappedGetters = Object.create(null)
  this._modules = new ModuleCollection(options)
  this._modulesNamespaceMap = Object.create(null)
  this._subscribers = []
  this._watcherVM = new Vue()

  const store = this
  const { dispatch, commit } = this
  this.dispatch = function boundDispatch (type, payload) {
    return dispatch.call(store, type, payload)
}
  this.commit = function boundCommit (type, payload, options) {
    return commit.call(store, type, payload, options)
}

  // strict mode
  this.strict = strict

  const state = this._modules.root.state

  // init root module.

  // this also recursively registers all sub-modules
  // and collects all module getters inside this._wrappedGetters
  installModule(this, state, [], this._modules.root)

  resetStoreVM(this, state)

  // apply plugins
  plugins.forEach(plugin => plugin(this))

}

Фокус: На самом деле, большая часть приведенного выше кода не требует внимания.- -. По сути, дело в одной строчке кодаresetStoreVM(this, state).

ТакresetStoreVMЧто внутри?

// src/store.js
function resetStoreVM (store, state, hot) {
  Vue.config.silent = true
  store._vm = new Vue({
    data: {
      $$state: state
    },
    computed
  })
}

Фокус: еще одна строка кода:new Vue. Через собственную двустороннюю привязку Via, а затем внедрить в

Вы думали, что это конец? Нет-нет-нет, а что, если вы вызовете данные магазина через это во Vue?

// 当获取state时,返回以双向绑定的$$sate
var prototypeAccessors$1 = { state: { configurable: true } };

prototypeAccessors$1.state.get = function () {
  return this._vm._data.$$state
};

// 将state定义在原型中
Object.defineProperties( Store.prototype, prototypeAccessors$1 );

На самом деле получитьthis._vm._data.?stateВот и все.

Суммировать

Двусторонняя привязка Vuex достигается вызовом нового Vue, затем внедрением его в жизненный цикл компонента Vue через Vue.mixin, а затем помещением данных в компонент путем захвата state.get.

наконец

  1. Нелегко любить оригинал
  2. Добро пожаловать, чтобы обратить внимание на общедоступную учетную запись «Продвинутый курс по интерфейсу», чтобы серьезно изучить интерфейс и продвигаться вместе.