Planification de la mise en œuvre d'un système réactif et d'un observateur

Contexte

Compte tenu du fait que vue3 n'a pas encore été officiellement publié, et certaines des idées d'implémentation dans vue2 sont toujours très précieuses pour référence, donc cette explication de principe est toujours vue2, j'espère vous éclairer ~

Implémentation d'un système réactif

Vue.js est un framework MVVM. Le modèle de données n'est que des objets JavaScript ordinaires, mais lorsque ces objets sont manipulés, ils peuvent affecter les vues correspondantes. Son implémentation principale est le "système réactif".

En vue 2.0, il est basé sur l'  Object.definePropertyimplémentation de "responsive system". Vue3 est basé sur la  Proxy/Reflect réalisation, une analyse détaillée de vue3 aura le temps d'écrire, cet article concerne la réalisation de vue2.

Implique principalement des attributs:

  • énumérable, si la propriété peut être énumérée, valeur par défaut false.

  • configurable, que la propriété puisse être modifiée ou supprimée, la valeur par défaut est false.

  • get, la méthode pour obtenir l'attribut.

  • set, la méthode de définition des propriétés.

Le principe de base de la réactivité est de traiter les données d'options dans le constructeur Vue. En d'autres termes, lorsque l'instance de vue est initialisée, chaque propriété de données, accessoires et autres objets est définie une fois par Object.defineProperty. Lorsque les données sont définies, effectuez certaines opérations pour modifier la vue correspondante.

class Vue {
    /* Vue构造类 */
    constructor(options) {
        this._data = options.data;
        observer(this._data);
    }
}
function observer (value) {
    if (!value || (typeof value !== 'object')) {
        return;
    }
    
    Object.keys(value).forEach((key) => {
        defineReactive(value, key, value[key]);
    });
}
function defineReactive (obj, key, val) {
    Object.defineProperty(obj, key, {
        enumerable: true,       /* 属性可枚举 */
        configurable: true,     /* 属性可被修改或删除 */
        get: function reactiveGetter () {
            return val;
        },
        set: function reactiveSetter (newVal) {
            if (newVal === val) return;
            cb(newVal);
        }
    });
}

Dans les applications pratiques, divers systèmes sont extrêmement compliqués. Supposons que nous ayons maintenant un objet global, nous pouvons l'utiliser dans plusieurs objets Vue pour l'affichage. Ou peut-être que les données écrites dans les données ne sont pas appliquées à la vue, et la mise à jour de la vue à ce stade est superflue. Cela dépend du processus de collecte.

Collection dépendante

La collecte dite dépendante consiste à collecter l'endroit où une donnée est utilisée. Lorsque ces données changent, elle avertit chaque lieu d'effectuer l'opération correspondante. Le mode de base "abonné" dans VUE est le suivant:

exportdefaultclass Dep {
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;

  constructor () {
    this.id = uid++
    this.subs = []
  }

  addSub (sub: Watcher) {
    this.subs.push(sub)
  }

  removeSub (sub: Watcher) {
    remove(this.subs, sub)
  }
 //依赖收集,有需要才添加订阅
  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }

  notify () {
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

Avec les abonnés, regardons la mise en œuvre de Watcher. Le code source Watcher est plus logique, et le modèle simplifié est le suivant

class Watcher{
    constructor(vm,expOrFn,cb,options){
        //传进来的对象 例如Vue
        this.vm = vm
        //在Vue中cb是更新视图的核心,调用diff并更新视图的过程
        this.cb = cb
        //收集Deps,用于移除监听
        this.newDeps = []
        this.getter = expOrFn
        //设置Dep.target的值,依赖收集时的watcher对象
        this.value =this.get()
    }

    get(){
        //设置Dep.target值,用以依赖收集
        pushTarget(this)
        const vm = this.vm
        let value = this.getter.call(vm, vm)
        return value
    }

    //添加依赖
      addDep (dep) {
          // 这里简单处理,在Vue中做了重复筛选,即依赖只收集一次,不重复收集依赖
        this.newDeps.push(dep)
        dep.addSub(this)
      }

      //更新
      update () {
        this.run()
    }

    //更新视图
    run(){
        //这里只做简单的console.log 处理,在Vue中会调用diff过程从而更新视图
        console.log(`这里会去执行Vue的diff相关方法,进而更新数据`)
    }
}

Définir une logique détaillée réactive

exportfunction defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  const dep = new Dep()

  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }

  let childOb = !shallow && observe(val)
  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
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify()
    }
  })
}

所以响应式原理就是,我们通过递归遍历,把vue实例中data里面定义的数据,用defineReactive(Object.defineProperty)重新定义。每个数据内新建一个Dep实例,闭包中包含了这个 Dep 类的实例,用来收集 Watcher 对象。在对象被「读」的时候,会触发 reactiveGetter 函数把当前的 Watcher 对象(存放在 Dep.target 中)收集到 Dep 类中去。之后如果当该对象被「写」的时候,则会触发 reactiveSetter 方法,通知 Dep 类调用 notify 来触发所有 Watcher 对象的 update 方法更新对应视图。

La naissance de Watcher

Dans Vue, il y a 4 situations qui généreront Watcher:

  1. L'observateur sur l'objet instance Vue, observe les données racine et restitue le composant en cas de changement updateComponent = () => {vm._update (vm._render (), hydrating)} vm._watcher = new Watcher (vm, updateComponent, noop)

  2. L'observateur créé par l'utilisateur avec l'attribut watch dans l'objet vue

  3. L'attribut calculé créé par l'utilisateur dans l'objet vue est essentiellement un observateur

  4. Observateur créé par l'utilisateur à l'aide de vm. $ Watch

Wathcer augmentera ou diminuera et peut être ajouté lors du rendu. Par conséquent, il doit y avoir un horaire pour planifier l'observateur. Certains codes principaux sont les suivants:

 queue.sort((a, b) => a.id - b.id)

  // do not cache length because more watchers might be pushed
  // as we run existing watchers
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    if (watcher.before) {
      watcher.before()
    }
    id = watcher.id
    has[id] = null
    watcher.run()
    // in dev build, check and stop circular updates.
    if (process.env.NODE_ENV !== 'production' && has[id] != null) {
      circular[id] = (circular[id] || 0) + 1
      if (circular[id] > MAX_UPDATE_COUNT) {
        warn(
          'You may have an infinite update loop ' + (
            watcher.user
              ? `in watcher with expression "${watcher.expression}"`
              : `in a component render function.`
          ),
          watcher.vm
        )
        break
      }
    }
  }

Le rôle de l'annexe:

  1. Déduplication, chaque observateur a un identifiant unique. Tout d'abord, si l'ID est déjà dans la file d'attente, sautez-le, il n'est pas nécessaire de répéter l'exécution. Si l'ID n'est pas dans la file d'attente, cela dépend si la file d'attente est en cours d'exécution. S'il n'est pas en cours d'exécution, la file d'attente est exécutée dans la tranche de temps suivante, de sorte que la file d'attente est toujours exécutée de manière asynchrone.

  2. Le tri est effectué dans l'ordre d'analyse et de rendu, c'est-à-dire que Watcher est exécuté en premier. L'ID dans Watcher augmente automatiquement, l'ID créé en premier est plus petit que l'ID créé plus tard. Il y aura donc les règles suivantes:

2.1 Les composants peuvent être imbriqués et l'analyse doit d'abord analyser le composant parent, puis le composant enfant. L'ID du composant parent est donc plus petit que le composant enfant.

2.2 Le Watcher créé par l'utilisateur sera analysé avant celui créé lors du rendu. Par conséquent, l'ID de l'observateur créé par l'utilisateur est plus petit que celui créé lors du rendu.

  1. Supprimer l'observateur. Si l'observateur d'un composant est dans la file d'attente et que son composant parent est supprimé, cet observateur doit également être supprimé à ce moment.

  2. Pendant l'exécution de la file d'attente, une circulaire d'objet est stockée, qui contient le nombre d'exécutions de chaque observateur. Si un observateur s'exécute plus que le nombre de fois défini par MAX_UPDATE_COUNT, il est considéré comme une boucle infinie et ne sera pas exécuté. La valeur par défaut est 100 fois.

总之,调用的作用就是管理 Watcher。

Supplément

Comment utiliser Object.defineProperty pour redéfinir l'objet tableau dans VUE2? Pourquoi la vue ne change-t-elle pas de manière réactive lorsque nous modifions directement un élément dans les données (arr [3] = 4).

La réponse est que la réactivité de la baie n'est pas complète, et VUE n'a réécrit que des méthodes limitées. La logique de réécriture est la suivante:

const arrayProto = Array.prototype
exportconst arrayMethods = Object.create(arrayProto)

const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

/**
 * Intercept mutating methods and emit events
 */
methodsToPatch.forEach(function (method) {
  // cache original method
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator (...args) {
    const result = original.apply(this, args)
    const ob = this.__ob__
    let inserted
    switch (method) {
      case'push':
      case'unshift':
        inserted = args
        break
      case'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    // notify change
    ob.dep.notify()
    return result
  })
})
Publié 117 articles originaux · 69 éloges · 10 000+ vues

Je suppose que tu aimes

Origine blog.csdn.net/zsd0819qwq/article/details/105356741
conseillé
Classement