Самоучитель JL # 25

Серия [Истории самообучения JL]

[Net Ninja] JS Regular Expressions
[Net Ninja] Vue JS2 (Part1, Part2, Part3)
[Net Ninja] Vuex
[Net Ninja] Python3 (Part1, Part2, Part3)
[Net Ninja] Django (Part1, Part2, Part3)
[Net Ninja] Sass (Part1, Part2)
[Sean Larkin] Webpack4 (Part1, Part2, Part3, Part4)
[Sarah Drasner] VueJS (Part1, Part2, Part3, Part4, 
                       Part5, Part6, Part7)
[Evan You] Advanced VueJS (Part1(current), Part2, Part3,
                           Part4, Part5, Part6, Part7)

🌲 Это первая часть моего обзора Расширенные возможности Vue.js с нуля Эвана Ю на FrontendMasters.

[1. Reactivity]
    1-1. Introducing Reactivity
    1-2. Challenge 1: Getters & Setters
    1-3. Challenge 2: Dependency Tracker
    1-4. Challenge 3: Mini Observer

[1–1. Введение в реактивность]

Как Vue отслеживает изменения? = Как изменение состояния отражается на изменениях в DOM

Начнем с написания очень простой программы. У нас есть переменная a, имеющая значение 3. Ваш начальник дает вам требование: значение переменной b всегда должно быть в 10 раз больше значения a.

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

›Все хорошо.

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

›Позже вас попросят изменить a на 4. Теперь все не синхронизировано, потому что вы забыли обновить b! (‘b’ не обновляется автоматически, поскольку он носит процедурный характер. Он не поддерживает синхронизацию отношений.)

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

> Фиксированный. Но теперь у нас есть повторение и потенциальный источник ошибок, когда нам нужно изменить a.

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

›Значение ячейки B1 декларативно выражается формулой, поэтому оно автоматически обновляется при изменении ячейки A1.

Как это выразить в терминах программирования?

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

›Чтобы выполнить требования нашей программы как можно проще, предположим, что мы каким-то образом реализуем эту волшебную функцию: onAChanged. Вы можете думать об этом как о наблюдателе, обратном вызове события, подписке - не имеет значения. Сейчас важно то, что b всегда будет выполнять требование, что оно в 10 раз превышает a - до тех пор, пока мы правильно реализуем эту функцию.

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

<span class="cell b1"></span>
document
  .querySelector(‘.cell.b1’)
  .textContent = state.a * 10

›Мы хотим, чтобы ячейка« b1 »всегда синхронизировалась. Как и в нашем первоначальном императивном коде, мы обязательно обращаемся к DOM и обновляем textContent ячейки.

<span class="cell b1"></span>
onStateChanged(() => {
  document
    .querySelector(‘.cell.b1’)
    .textContent = state.a * 10
})

›Чтобы сделать его декларативным, просто оберните его в нашу волшебную функцию!

<span class="cell b1">
  {{ state.a * 10 }}
</span>
onStateChanged(() => {
  view = render(state)
})

Путем абстрагирования мы, по сути, создали библиотеку Vue.

  • Пойдя еще дальше, давайте превратим разметку в шаблоны. Шаблоны обычно компилируются в функции рендеринга, поэтому наш код JavaScript можно упростить.
  • Шаблон (или функции рендеринга JSX) - это конструкция, которая позволяет нам объявить связь между состоянием и представлением. Другими словами, шаблон - это эквивалент формулы в электронной таблице.
onStateChanged(() => {
  view = render(state)
})

Это внутреннее представление «view = render(state)» является абстракцией очень высокого уровня того, как работают все системы рендеринга Vue.

Это присвоение view можно интерпретировать по-разному в зависимости от того, как вы на это смотрите. В контексте виртуального DOM мы можем рассматривать view как новое виртуальное дерево DOM, но в более общем плане давайте просто пропустим виртуальный DOM и представим его как применение побочного эффекта мутации DOM.

Честно говоря, это только половина картины: есть также вопрос, как декларативно сопоставить вводимые пользователем данные с изменениями состояния, верно? С точки зрения цикла, это половина, которая сопоставляет намерение пользователя с состоянием. Это область, в которой Rx действительно хорош, но, учитывая длину этого выступления, Эван Ю сосредоточился на state -> view части.

Теперь возникает вопрос: «Как приложение узнает, когда повторно выполнить эту функцию обновления?»

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

Давайте разберемся! Вот чрезвычайно надуманная версия того, как это может работать:

let update, state
const onStateChanged = _update => {
  update = _update
}

const setState = newState => {
  state = newState
  update()
}

›Вместо того, чтобы позволять пользователям произвольно манипулировать состоянием, мы требуем, чтобы они всегда вызывали функцию для управления состоянием. Функция называется
«setState».

… И вуаля, мы реализовали React! (хотя и чрезвычайно упрощенная версия, просто чтобы донести смысл)

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

setState({ a: 5 })

React заставляет вас запускать изменения состояния вашего setState.

onStateChanged(() => {
  view = render(state)
})
state.a = 5

›Мы можем напрямую управлять состоянием без вызова setState. В AngularJS он использует грязную проверку, которая перехватывает такие события, как щелчки, для выполнения цикла дайджеста и проверки всех вещей, чтобы увидеть, не изменилось ли что-то. В VueJS он преобразует объекты состояния в реактивные. Используя ES5 object.defineProperty API, Vue преобразует все эти свойства в геттеры и сеттеры. В случае state.a Vue преобразует a в геттер и сеттер.

autorun(() => {
  console.log(state.count)
})

›Если мы переименуем функцию onStateChanged в функцию autorun, это, по сути, основная форма систем отслеживания зависимостей, обычно используемых в Knockout.js, Meteor Tracker. , Vue.js и MobX (шаблон управления состоянием для React).

[1–2. Задача 1. Получатели и сеттеры]

ИНСТРУКЦИЯ:

Реализуйте convert функцию, которая:

  • Принимает Object в качестве аргумента
  • Преобразует свойства объекта на месте в _20 _ / _ 21_ с помощью Object.defineProperty
  • Преобразованный объект должен сохранять исходное поведение, но в то же время протоколировать все _23 _ / _ 24_ операции.

ОЖИДАЛ:

let realValue
Object.defineProperty(obj, 'foo', {
  get(){
    return 'bar'
  },
  set(newValue){
  
  }
})
const obj = { foo: 123 }
convert(obj)
obj.foo // should log: 'getting key "foo": 123'
obj.foo = 234 // should log: 'setting key "foo" to 234'
obj.foo // should log: 'getting key "foo": 234'

ШАБЛОН:

<script>
function convert(obj){
  // Implement this!
</script>

РЕШЕНИЕ:

[1–3. Challenge 2: Dependency Tracker]

ИНСТРУКЦИЯ:

Задача состоит в том, чтобы связать экземпляр зависимости с вычислением.

  • Создайте класс Dep двумя методами: depend и notify.

›› Свяжите вычисление с зависимостью. вычисление следует рассматривать как подписчик зависимости.

  • Создайте autorun функцию, которая принимает функцию обновления (вычисление).

›› Когда вы входите в функцию, все становится особенным. Мы находимся в реактивной зоне.

  • Внутри функции обновления вы можете явно зависеть от экземпляра Dep, вызвав dep.depend()

›› Находясь внутри реактивной зоны, вы можете зарегистрировать зависимости.

  • Позже вы можете снова запустить функцию обновления, вызвав dep.notify().

ОЖИДАЛ:

const dep = new Dep()
autorun(() => {
  dep.depend()
  console.log('updated')
})
// should log: "updated"
dep.notify()
// should log: "updated"

ШАБЛОН:

<script>
// a class representing a dependency
// exposing it on window is necessary for testing
window.Dep = class Dep {
  // Implement this!
}
function autorun (update) {
  // Implement this!
}
</script>

ШАБЛОН С НАМЕКАМИ:

  • JavaScript однопоточный. В любой момент времени может выполняться только одна функция. Таким образом, создание функции отмечает себя как функцию, которая выполняется в данный момент. И тогда мы можем в любой момент узнать, выполняется ли эта функция в данный момент (= находимся ли мы внутри этой функции).
  • Каждый раз, когда мы вызываем wrappedUpdate(), выполняется код внутри функции. Код гарантированно сможет получить доступ к коду вне функции через глобальную переменную activeUpdate. Итак, наш класс зависимостей может иметь доступ к activeUpdate.
  • Объект Set позволяет хранить уникальные значения любого типа, будь то примитивные значения или ссылки на объекты.

РЕШЕНИЕ:

  • Мы зарегистрировали wrappedUpdate() как активное обновление. Итак, когда наша зависимость изменяется и функция обновления вызывается снова, мы снова вызываем wrappedUpdate(). Поэтому наш трюк с отслеживанием зависимостей по-прежнему будет работать в будущих итерациях. Итак, он продолжает собирать зависимости.

›› Это важно, потому что в некоторых случаях наша функция обновления может содержать условные обозначения. Итак, когда одна переменная истинна, функция может собирать определенные зависимости. И когда другая переменная переворачивается, она может собирать другие зависимости. Итак, наша система сбора зависимостей должна динамически повторно балансировать зависимости, чтобы всегда обеспечивать актуальность списка зависимостей.

››› Хотя наш трекер зависимостей работает для нашего тестового примера, он пропускает некоторые крайние случаи. (например, как вы будете работать с массивами? Как насчет недавно добавленных свойств?)

[1–4. Challenge 3: Mini Observer]

ИНСТРУКЦИЯ:

Пришло время объединить две предыдущие задачи. Объедините две предыдущие функции, переименовав convert() (из задачи 1) в observe() и сохранив autorun() (из задачи 2):

  • observe() преобразует свойства в полученном объекте и делает их реактивными. Для каждого свойства преобразованного ему назначается экземпляр Dep, который отслеживает список подписывающихся функций update и запускает их повторный запуск, когда его установщик вызывается.
  • autorun() принимает функцию update и повторно запускает ее, когда свойства, на которые подписывается функция update, были изменены. Считается, что функция update «подписывается» на свойство, если она полагается на это свойство во время оценки.

ИНСТРУКЦИЯ (ДРУГИМИ СЛОВАМИ):

  • Используйте Dep класс и autorun() из Задания 2
  • Скопируйте и измените convert() из Задачи 1, чтобы подключить dep.depend(), чтобы dep.notify() звонил в getters и setters.
  • Когда autorun() и convert() (переименованные в observe()) соединяются, мы можем получить доступ к свойству, которое собирает зависимости, вызвав dep.depend(). Изменение свойства заставляет dep.notify() инициировать изменения.

ОЖИДАЛ:

const state = {
  count: 0
}
observe(state)
autorun(() => {
  console.log(state.count)
  // should call dep.depend() "getter"
})
// should immediately log "count is: 0"
state.count++
// should log "count is: 1"
// should call dep.notify() "setter"

ШАБЛОН:

<script>
function observe (obj) {
  // Implement this!
}
function autorun (update) {
  // Implement this!
}
</script>

РЕШЕНИЕ:

Инструктор класса Эван Ю является создателем и руководителем проекта Vue.js.

Его можно найти на его личном веб-сайте, GitHub, LinkedIn, Twitter, Medium (Evan You) и Frontend Masters.

Спасибо за чтение! 💕 Если вам понравился этот пост в блоге, пожалуйста, хлопайте в ладоши👏