Принцип отзывчивости и реализация расширенной серии Vue (1)

внешний интерфейс GitHub Vue.js

Обновление: Спасибо за вашу поддержку.Недавно был выброшен официальный сайт блога, чтобы каждый мог его систематически читать.В будущем будет больше контента и больше оптимизации.Нажмите здесь, чтобы просмотреть

------ Далее идет текст ------

Продвинутая серия Vue резюмируется следующим образом, добро пожаловать на чтение.

Принцип отзывчивости и реализация расширенной серии Vue (1)

Принцип и реализация плагина Vue advanced series (2)

Принцип и реализация функции рендеринга расширенной серии Vue (3)

Что такое реактивная реактивность

Реактивность указывает, как динамически изменять всю систему после изменения состояния, а в реальных сценариях приложений проекта, то есть как данные динамически изменяют Dom.

нужно

Теперь есть требование, есть две переменные a и b, причем b всегда в 10 раз больше a, как это сделать?

Простая попытка 1:

let a = 3;
let b = a * 10;
console.log(b); // 30

На первый взгляд кажется, что оно удовлетворяет требованиям, но значение b в это время фиксировано, как бы ни изменялось a, b не будет меняться вместе с ним. То есть b не поддерживает синхронизацию данных с a. b изменится только в том случае, если значение b будет переопределено после изменения a.

a = 4;
console.log(a); // 4
console.log(b); // 30
b = a * 10;
console.log(b); // 40

Простая попытка 2:

Определите отношение между a и b в функции, затем выполните эту функцию после изменения a, и значение b изменится. Псевдокод выглядит следующим образом.

onAChanged(() => {
    b = a * 10;
})

Таким образом, теперь возникает вопрос, как достичьonAChangedфункция, которая автоматически выполняется при измененииonAChanged, см. продолжение.

Объедините слой просмотра

Теперь объедините a, b и страницы просмотра, где a соответствует данным, а b соответствует странице. Бизнес-сценарий очень прост: после изменения данных а измените страницу б.

<span class="cell b"></span>

document
    .querySelector('.cell.b')
    .textContent = state.a * 10

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

<span class="cell b"></span>

onStateChanged(() => {
    document
        .querySelector(‘.cell.b’)
        .textContent = state.a * 10
})

После повторного абстрагирования это выглядит так.

<span class="cell b">
    {{ state.a * 10 }}
</span>

onStateChanged(() => {
    view = render(state)
})

view = render(state)является высокоуровневой абстракцией для рендеринга всех страниц. Здесь не рассматриваетсяview = render(state), потому что он должен включать в себя ряд технических деталей, таких как структура DOM и ее реализация. Что здесь нужно, так этоonStateChangedреализация.

выполнить

Это достигается за счетObject.definePropertyсерединаgetterа такжеsetterметод. Обратитесь к ссылке ниже для конкретного использования.

Object.defineProperty MDN

должен быть в курсеgetа такжеsetФункции — это дескрипторы доступа,valueа такжеwritableФункции — это дескрипторы данных. Дескриптор должен быть одной из этих двух форм, но они не могут сосуществовать, иначе возникнет исключение.

Пример 1: Реализацияconvert()функция

Требования следующие:

  • 1. Входящий объектobjкак параметр
  • 2. ИспользуйтеObject.definePropertyПреобразование всех свойств объекта
  • 3. Преобразованный объект сохраняет исходное поведение, но журналы выводятся во время операций получения или установки.

Пример:

const obj = { foo: 123 }
convert(obj)


obj.foo // 输出 getting key "foo": 123
obj.foo = 234 // 输出 setting key "foo" to 234
obj.foo // 输出 getting key "foo": 234

в пониманииObject.definePropertyсерединаgetterа такжеsetterПосле использования метода, изменивgetа такжеsetфункция может быть реализованаonAChangedа такжеonStateChanged.

выполнить:

function convert (obj) {

  // 迭代对象的所有属性
  // 并使用Object.defineProperty()转换成getter/setters
  Object.keys(obj).forEach(key => {
  
    // 保存原始值
    let internalValue = obj[key]
    
    Object.defineProperty(obj, key, {
      get () {
        console.log(`getting key "${key}": ${internalValue}`)
        return internalValue
      },
      set (newValue) {
        console.log(`setting key "${key}" to: ${newValue}`)
        internalValue = newValue
      }
    })
  })
}

Пример 2: РеализацияDepДобрый

Требования следующие:

  • 1. СоздайтеDepкласс с двумя методами:dependа такжеnotify
  • 2. Создайтеautorunфункция, переходящая вupdateфункция как параметр
  • 3. Вupdateвызов функцииdep.depend(), который явно зависит отDepпример
  • 4. Звонокdep.notify()вызыватьupdateповторный запуск функции

Пример:

const dep = new Dep()

autorun(() => {
  dep.depend()
  console.log('updated')
})
// 注册订阅者,输出 updated

dep.notify()
// 通知改变,输出 updated

Сначала нужно определитьautorunфункционировать, получатьupdateфункционировать как параметр. потому что звонюautorunкогдаDepРегистрируйте абонентов и звонитеdep.notify()повторно выполнитьupdateфункция, поэтомуDepдолжен иметьupdateЗдесь используются ссылки, переменныеactiveUpdateВыражается как функция пакета обновления.

Код реализации выглядит следующим образом.

let activeUpdate = null 

function autorun (update) {
  const wrappedUpdate = () => {
    activeUpdate = wrappedUpdate    // 引用赋值给activeUpdate
    update()                        // 调用update,即调用内部的dep.depend
    activeUpdate = null             // 绑定成功之后清除引用
  }
  wrappedUpdate()                   // 调用
}

wrappedUpdateСуть в замыкании,updateможно получить внутри функцииactiveUpdateпеременная, то жеdep.depend()Также доступен внутриactiveUpdateпеременная, поэтомуDepРеализация очень проста.

Код реализации выглядит следующим образом.

class Dep {

  // 初始化
  constructor () {          
    this.subscribers = new Set()
  }

  // 订阅update函数列表
  depend () {
    if (activeUpdate) {     
      this.subscribers.add(activeUpdate)
    }
  }

  // 所有update函数重新运行
  notify () {              
    this.subscribers.forEach(sub => sub())
  }
}

Объединение двух вышеуказанных частей является полной реализацией.

Пример 3: Реализация реактивной системы

Требования следующие:

  • 1. Объединив два приведенных выше примера,convert()переименовать в наблюдателяobserve()
  • 2,observe()Преобразует свойства объекта в реактивные, каждому преобразованному свойству присваиваетсяDepэкземпляр, который отслеживает подпискиupdateсписок функций, а при вызовеsetterвызвать их повторный запуск
  • 3.autorun()перениматьupdateфункционировать как параметр, а вupdateВыполняется повторно при изменении свойств подписки на функцию.

Пример:

const state = {
  count: 0
}

observe(state)

autorun(() => {
  console.log(state.count)
})
// 输出 count is: 0

state.count++
// 输出 count is: 1

После объединения примера 1 и примера 2 вышеуказанные требования могут быть достигнуты,observeизменено вobjОдновременное присвоение свойствDepэкземпляр и вgetЗарегистрируйте подписчиков вsetУведомлять об изменениях.autorunФункция остается неизменной. Реализация выглядит следующим образом:

class Dep {

  // 初始化
  constructor () {          
    this.subscribers = new Set()
  }

  // 订阅update函数列表
  depend () {
    if (activeUpdate) {     
      this.subscribers.add(activeUpdate)
    }
  }

  // 所有update函数重新运行
  notify () {              
    this.subscribers.forEach(sub => sub())
  }
}

function observe (obj) {

  // 迭代对象的所有属性
  // 并使用Object.defineProperty()转换成getter/setters
  Object.keys(obj).forEach(key => {
    let internalValue = obj[key]

    // 每个属性分配一个Dep实例
    const dep = new Dep()

    Object.defineProperty(obj, key, {
    
      // getter负责注册订阅者
      get () {
        dep.depend()
        return internalValue
      },

      // setter负责通知改变
      set (newVal) {
        const changed = internalValue !== newVal
        internalValue = newVal
        
        // 触发后重新计算
        if (changed) {
          dep.notify()
        }
      }
    })
  })
  return obj
}

let activeUpdate = null

function autorun (update) {

  // 包裹update函数到"wrappedUpdate"函数中,
  // "wrappedUpdate"函数执行时注册和注销自身
  const wrappedUpdate = () => {
    activeUpdate = wrappedUpdate
    update()
    activeUpdate = null
  }
  wrappedUpdate()
}

В сочетании с блок-схемой в документе Vue это становится еще понятнее.

image

Работа выполнена! ! !

Содержание этой статьи взято из платного видео автора VUE Youda.

общаться с

Моя ссылка на Github выглядит следующим образом, добро пожаловать в Star

GitHub.com/100 миллионов акционеров/Нет...

Я деревянный Ян И, старший инженер по интерфейсу NetEase, и мое интервью было сосредоточено на том, чтобы запечатлеть тяжелую и трудную неделю переднего плана. Позвольте мне ввести вас в мир продвинутого фронтенда, продвинутых в пути, подбадривайте друг друга!