принцип отзывчивости vue3.0

Vue.js
принцип отзывчивости vue3.0

написать впереди

В настоящее время реактивная система Vue использует геттеры и сеттеры Object.defineProperty. Однако Vue 3 будет использовать прокси-сервер ES2015 в качестве механизма наблюдения. Это удаляет ранее существовавшее предупреждение, удваивает скорость и экономит половину накладных расходов памяти. В то же время используйте новый Composition API, лучшее повторное использование логики, вывод типов и более высокую производительность.

недостатки 2.0

Используя рекурсию для захвата данных, многоуровневая вложенность потребляет много памяти, а производительность невысока. Только предустановленные данные могут быть перехвачены, а непосредственно добавленные данные не могут быть перехвачены. Вы можете передать vue.set(xxx). Операция с массивом может быть только 7 методами внутреннего захвата, и прямое изменение индекса не может вызвать ответ.

3.0 улучшения

  1. Логическая комбинация и мультиплексирование
  2. Тип удержания: одна из основных точек VUE3.0 - использовать рефакторинг TS для достижения шелковистой поддержки для TS. API на основе функций, естественно, дружелюбна для вычета.
  3. Размер упаковки: каждая функция может быть импортирована отдельно как именованный экспорт ES, что удобно для встряхивания дерева; во-вторых, все имена функций и переменные внутри функции настройки могут быть сжаты, что повышает эффективность сжатия.

Реализация реактивных и эффектных

Использование некоторых базовых API 3.0 здесь подробно описываться не будет, сегодня мы сосредоточимся на реализации реактивного и эффектного, то есть на том, как реализовать реактивный принцип ядра 3.0 и как собирать зависимости.Composition Api

reactive

/**
* 这是第一步,实现数据的劫持
*/
function reactive(target) {
  return createReactiveObject(target)
}

function createReactiveObject(target) {
  if(!isObject(target)) {
    return target
  }
  // 说明已经代理过了
  const proxyed = toProxy.get(target)
  if (proxyed) {
    return proxyed
  }
  // 防止反复代理
  // reactive(proxy) reactive(proxy)
  if (toRow.has(target)) {
    return target
  }

  const handles = {
    get(target, key, receiver) {
      let result = Reflect.get(target, key, receiver)
      // 如果是多层次的对象的,我们需要递归代理
      return isObject(result) ? reactive(result) : result
    },
    set(target, key, value, receiver) {
      let oldValue = target[key]
      // 我们不知道设置是否成功,所以要做一个反射,来告诉我们是否成功
      let flag = Reflect.set(target, key, value, receiver)
      return flag
    },
    // 删除的同上
    deleteProperty() {
    }
  }

  const observe = new Proxy(target, handles)
  return observe
}

Приведенный выше код очень прост, мы рекурсивно перехватываем наши данные, мы используемReflectОтражение, если вы используете target[key] = value для присвоения значения непосредственно в наборе, будет сообщено об ошибке. Используйте Reflect, чтобы вернуть, успешно ли выполнен набор. Здесь нужно решить несколько вопросов:

  1. Проксирование одного и того же объекта несколько раз
let name = {a:123}
reactive(name)
reactive(name)
reactive(name)
  1. Проксируемый объект проксируется несколько раз
let name = {a:123}
let proxy = reactive(name)
reactive(proxy)
reactive(proxy)

Чтобы решить две вышеупомянутые проблемы, в исходном коде используются два WeakMaps для создания связи таблицы сопоставления. Поэтому мы добавляем следующий код к приведенному выше коду. Тот же WeakMap есть и в es6, студенты, которые не знают, могут пойти и посмотреть.WeakMap

const toProxy = new WeakMap() // 用来放 当前对象:代理过的对象
const toRow = new WeakMap() // 用来放 代理过的对象: 当前对象


// 说明已经代理过了
const proxyed = toProxy.get(target)
if (proxyed) {
  return proxyed
}
// 防止反复代理
// reactive(proxy) reactive(proxy)
if (toRow.has(target)) {
  return target
}
// 对已经代理过的,进行保存
toProxy.set(target, observe)
toRow.set(observe, target)

сбор эффектов и зависимостей

Реализуем принцип отзывчивости, в vue3.0 очень полезным API является эффект, он будет сначала выполняться, а потом будут меняться зависимые данные и автоматически будет выполняться входящая функция, что равносильно вычислению. (Вот как я это понимаю)


// 栈数组,先进后出
/**
 * 依赖收集 (数据: [effect])
 * 每个数据对应它的依赖,数据一变执行方法
 */
const activeEffectStacks = []

/**
 * 建立依赖关系
 * 存放依赖关系的数据结构
 * targetMap : (WeakMap): {
 *   target: (Map) {
 *     key: (Set) [effect,effect]
 *   }
 * }
 */
const targetMap = new WeakMap()
function track(target, key) {
  let effect = activeEffectStacks[activeEffectStacks.length - 1]
  if (effect) {
    let depsMap = targetMap.get(target)

    if (!depsMap) {
      depsMap = new Map()
      targetMap.set(target, depsMap)
    }

    let deps = depsMap.get(key)
    if (!deps) {
      deps = new Set()
      depsMap.set(key, deps)
    }
    if (!deps.has(effect)) {
      deps.add(effect)
    }
  }
}

/**
 * 第二步,实现数据的响应式
 * 数据变通知依赖的数据更新
 * 
 * 副作用,先会执行一次,当数据变话的时候在执行一次
 * 这里面设计到一个依赖收集的东西,源码里面用一个栈(数组[])来做的
 * 
 */
function effect(fn) {
  const effectFun = createReactiveEffect(fn)
  effectFun()
}
function createReactiveEffect(fn) {
  const effect = function() {
    run(effect, fn)
  }
  return effect
}

function run(effect, fn) {
  try {
    // 栈里面已经拿到数据了以后,清掉保证数据量
    // try 保证fn执行报错时,一定能将栈清除
    activeEffectStacks.push(effect)
    fn()
  } finally{
    activeEffectStacks.pop(effect)
  }
}

// 这里增加依赖收集
get(target, key, receiver) {
	let result = Reflect.get(target, key, receiver)
	// 进行依赖收集
	/**
	 * 这里很巧妙,在第一次调用effect的时候,一定能触发一次target的get方法的
	 * 此时我们将依赖的关系建立
	 */
	track(target, key)
	// 如果是多层次的对象的,我们需要递归代理
	return isObject(result) ? reactive(result) : result
},

Здесь мы в основном рассмотрим взаимосвязь структуры данных этой зависимости коллекции. Используйте слабую карту, чтобы поместить [цель, карта], Map[key, Set], Set[effect], чтобы мы могли установить цель, ключ, зависимость эффекта, и каждый раз, когда target[key] меняется, мы могли выполнять соответствующий цикл эффекта.

Специальная обработка массивов

Мы знаем, что в версии 2.0 мы можем вызывать только определенные 7 методов для массивов, чтобы сделать данные реактивными. Но в 3.0 с Proxy мы можем напрямую следить за изменениями массива.

/**
 * 这里是数组的一个处理,如果push[1,2] => [1,2,3]
 * 这里会触发两次的set,一次是下标2的set,一次是length的set
 * 但是length的set的触发在这里是无意义的,length的修改并不需要是响应式的
 * oldValue !== value 可以规避length的修改带来的影响
 */
if (!isOwnProperty(target, key)) {
  console.log('设置新的属性')
// 修改属性
} else if (oldValue !== value) {
  console.log('修改原有的属性')
}

триггер зависит от триггера

/**
 * 依赖的触发
 */
function trigger(target, type, key) {
  // 这里先不做type的区分
  const depsMap = targetMap.get(target)
  if (depsMap) {
    const deps = depsMap.get(key)
    if (deps) {
      deps.forEach(effect => {
        effect()
      })
    }
  }
}
// 我们在get的时候触发依赖
if (!isOwnProperty(target, key)) {
   trigger(target, 'add', key)
   console.log('设置新的属性')
 // 修改属性
 } else if (oldValue !== value) {
   trigger(target, 'set', key)
   console.log('修改原有的属性')
 }

Таким образом, у нас есть полный захват данных, сбор зависимостей и запуск зависимостей в основном завершены.полный код