Примечание. Код мог измениться на момент написания. Когда вы читаете статью, код, скорее всего, изменится.Если есть какое-либо несоответствие, будут серьезные несоответствия в реализации или понимании исходного кода.Добро пожаловать, чтобы указать, большое спасибо.
10 мая, в национальный праздник, маленький правый бог-мужчина выпустил код альфа-версии vue@3.0.0. Все равно делать мне нечего, я тоже недавно изучаю TypeScript, просто посмотри на код мужского бога и выучи его.
Из входного файла packages/vue/index он очень узкий в начале, 7 строк кода. Ищу несколько файлов, пока runtime-core вдруг просветлел. Комментарий построчно, API такой же. Забудьте, я уже не могу его редактировать, короче, код начинает становиться все больше и больше. Я чувствую, что смотреть Национальный день определенно невозможно, поэтому выберите основную реализацию принципа двусторонней привязки, который всегда задают другим во время интервью.
Всем следует знать, что Vue3 использует Proxy вместо defineProperty для реализации ответного обновления данных, как этого добиться? Откройте каталог файлов с исходным кодом, и вы сразу увидите, что ядро лежит в пакетах/реактивности.
Reactivity
Нажмите на его Readme, и с помощью перевода Google мы можем понять, что это примерно означает:
Этот пакет будет встроен в средство визуализации vue (@vue/runtime-dom). Однако его также можно опубликовать отдельно и на него могут ссылаться третьи стороны (не полагаясь на vue). Тем не менее, не используйте его вслепую, если ваш рендерер открыт для пользователей фреймворка, он может уже иметь встроенный механизм ответа, который полностью состоит из двух наборов нашей реактивности, не обязательно совместимых (говоря, что это вы, react-dom) .
Что касается его API, давайте сначала посмотрим на исходный код или типы. Примечание: кроме
Map
,WeakMap
,Set
andWeakSet
Кроме того, некоторые встроенные объекты недоступны для наблюдения (например:Date
,RegExp
Ждать).
Ну, на основании одного только Readme невозможно точно знать, на что это похоже. Ведь это альфа версия. Потом еще его слушаем и прямо шлепаем исходники.
Почисти исходный код, выгляди сбитым с толку
Из входного файла реактивности выясняется, что он предоставляет API только в 6 файлах. Они есть:ref
,reactive
,computed
,effect
,lock
,operations
. вlock
иoperations
очень простой,lock
Внутри файла есть два метода для управления переменной блокировки переключателя:operations
Внутри это перечисление типов операций с данными.
Таким образом, центр реактивностиref
,reactive
,computed
,effect
Эти четыре файла, но эти четыре файла не так просты. Мне потребовалось много времени, чтобы пролистать его от начала до конца, и я обнаружил, что знаю каждую букву; я также знал каждое слово с помощью Google; в основном все выражения, мой полусырой уровень TypeScript также может понять. Однако, когда они образуют функцию, я немного смущен...ref
цитируетсяreactive
,reactive
цитируется сноваref
, плюс странная операция внутри функции, и вы запутаетесь, если дважды ее обойдете.
Я подытожил, большая причина в том, что я не знаю, для чего нужны эти ключевые API. Я не понимаю исходный код и не понимаю смысла API. Мы знаем, что одно квадратное уравнение не может быть решено.
тогда что нам делать? На самом деле там другое уравнение, то есть один тест. Чтение из одиночного теста — отличный способ прочитать исходный код. Вы можете не только быстро узнать значение и использование API, но также узнать множество граничных условий. В процессе чтения я также подумаю о том, как реализовать его, если он мой, и я смогу углубить свое понимание и изучение исходного кода в будущем.
Начните с одного теста
Поскольку у меня есть немного исходного кода, я могу примерно знать порядок чтения. Конечно, исходя из количества строк кода, мы также можем оценить примерный порядок. Здесь я приведу вывод непосредственно, рекомендуемый порядок чтения: реактивный -> ссылка -> эффект -> вычисляемый -> только для чтения -> коллекции
Reactive
reactive
Отзывчивый, как следует из названия, означаетreactive
Данные — это реагирующие данные, и название предполагает, что они являются ядром этой библиотеки. Итак, давайте посмотрим, какие у него есть возможности.
Первый одиночный тест:
test('Object', () => {
const original = { foo: 1 }
const observed = reactive(original)
expect(observed).not.toBe(original)
expect(isReactive(observed)).toBe(true)
expect(isReactive(original)).toBe(false)
// get
expect(observed.foo).toBe(1)
// has
expect('foo' in observed).toBe(true)
// ownKeys
expect(Object.keys(observed)).toEqual(['foo'])
})
Это ни на что не похоже, простоreactive
Передача объекта вернет новый объект.Два объекта имеют одинаковый тип, данные выглядят одинаково, но ссылки разные. Тогда мы сразу поняли, что это, должно быть, было использованоProxy! Ядро адаптивной системы vue@3.
Тогда давайте посмотримreactive
заявление:
инструкцияreactive
Принимает только данные объекта и возвращаетUnwrapNestedRefs
Тип данных, но что это такое, я не знаю, я расскажу об этом позже.
Второй одиночный тест:
test('Array', () => {
const original: any[] = [{ foo: 1 }]
const observed = reactive(original)
expect(observed).not.toBe(original)
expect(isReactive(observed)).toBe(true)
expect(isReactive(original)).toBe(false)
expect(isReactive(observed[0])).toBe(true)
// get
expect(observed[0].foo).toBe(1)
// has
expect(0 in observed).toBe(true)
// ownKeys
expect(Object.keys(observed)).toEqual(['0'])
})
reactive
После получения массива (массив, естественно, является объектом), возвращаемый новый массив не равен исходному массиву, но данные непротиворечивы. Показатели соответствуют показателям испытуемых в Тесте 1. Однако этот единственный тест не рассматривает вложенность, я добавлю
test('Array', () => {
const original: any[] = [{ foo: 1, a: { b: { c: 1 } }, arr: [{ d: {} }] }]
const observed = reactive(original)
expect(observed).not.toBe(original)
expect(isReactive(observed)).toBe(true)
expect(isReactive(original)).toBe(false)
expect(isReactive(observed[0])).toBe(true)
// observed.a.b 是reactive
expect(isReactive(observed[0].a.b)).toBe(true)
// observed[0].arr[0].d 是reactive
expect(isReactive(observed[0].arr[0].d)).toBe(true)
// get
expect(observed[0].foo).toBe(1)
// has
expect(0 in observed).toBe(true)
// ownKeys
expect(Object.keys(observed)).toEqual(['0'])
})
Указывает, что возвращаемые новые данные, пока значение атрибута остается объектом, остаютсяisReactive
.
Третий одиночный тест, о котором и говорить нечего, четвертый одиночный тест, проверка вложенных объектов, был рассмотрен в моем дополнении ко второму одиночному тесту.
Пятый одиночный тест:
test('observed value should proxy mutations to original (Object)', () => {
const original: any = { foo: 1 }
const observed = reactive(original)
// set
observed.bar = 1
expect(observed.bar).toBe(1)
expect(original.bar).toBe(1)
// delete
delete observed.foo
expect('foo' in observed).toBe(false)
expect('foo' in original).toBe(false)
})
**
В этом единственном тесте мы наконец видим "Отзывчивый".пройти черезreactive
Любые операции записи/удаления данных ответа, возвращаемых после выполнения, можно синхронно синхронизировать с исходными данными. Что, если верно обратное и исходные данные изменены напрямую?
test('observed value should proxy mutations to original (Object)', () => {
let original: any = { foo: 1 }
const observed = reactive(original)
// set
original.bar = 1
expect(observed.bar).toBe(1)
expect(original.bar).toBe(1)
// delete
delete original.foo
expect('foo' in observed).toBe(false)
expect('foo' in original).toBe(false)
})
Мы обнаружили, что последние данные также можно получить путем прямого изменения исходных данных и реагирования на данные.
Шестой одиночный тест
test('observed value should proxy mutations to original (Array)', () => {
const original: any[] = [{ foo: 1 }, { bar: 2 }]
const observed = reactive(original)
// set
const value = { baz: 3 }
const reactiveValue = reactive(value)
observed[0] = value
expect(observed[0]).toBe(reactiveValue)
expect(original[0]).toBe(value)
// delete
delete observed[0]
expect(observed[0]).toBeUndefined()
expect(original[0]).toBeUndefined()
// mutating methods
observed.push(value)
expect(observed[2]).toBe(reactiveValue)
expect(original[2]).toBe(value)
})
Шестое одиночное испытание оказалось пройденнымProxy
Одно из больших преимуществ реализации реактивных данных: возможность перехватить все изменения данных в массиве. Помните, в vue@2 вам нужно вручную установить массив? В vue@3 нам, наконец, не нужно делать какие-то странные операции и спокойно обновлять массив.
Седьмой одиночный тест:
test('setting a property with an unobserved value should wrap with reactive', () => {
const observed: any = reactive({})
const raw = {}
observed.foo = raw
expect(observed.foo).not.toBe(raw)
expect(isReactive(observed.foo)).toBe(true)
})
Собираюсь снова постучать по доске, это черезProxy
Два больших преимущества реализации реактивных данных. В vue@2 ключ должен быть объявлен в ответных данных в начале.Если это значение свойства не существует в начале, сначала необходимо установить значение по умолчанию. Благодаря текущему техническому решению значения атрибутов отзывчивых данных vue@3 могут быть добавлены или удалены в любое время.
Восьмой и девятый одиночный тест
test('observing already observed value should return same Proxy', () => {
const original = { foo: 1 }
const observed = reactive(original)
const observed2 = reactive(observed)
expect(observed2).toBe(observed)
})
test('observing the same value multiple times should return same Proxy', () => {
const original = { foo: 1 }
const observed = reactive(original)
const observed2 = reactive(original)
expect(observed2).toBe(observed)
})
Эти два одиночных теста показывают, что для одних и тех же необработанных данных выполняется несколько выполнений.reactive
или вложенное выполнениеreactive
, возвращаемые результаты — это все те же соответствующие данные. инструкцияreactive
Кэш поддерживается в файле, исходные данные используются в качестве ключа, а данные ответа используются в качестве значения.Если ключ уже имеет значение, значение будет возвращено напрямую. Те, у кого есть основа OK js, должны знать, что черезWeakMap
Этот результат может быть достигнут.
Десятый одиночный тест
test('unwrap', () => {
const original = { foo: 1 }
const observed = reactive(original)
expect(toRaw(observed)).toBe(original)
expect(toRaw(original)).toBe(original)
})
Благодаря этому единственному тесту мы узналиtoRaw
Это API, вы можете получить исходные данные через данные ответа. Это объясняетreactive
Файл также должен поддерживать другойWeakMap
Сделайте обратное отображение.
Одиннадцатый одиночный тест, не вставляйте код, в этом единственном тесте перечислены типы данных, которые нельзя использовать в качестве данных ответа, то есть пять основных типов данных JS +Symbol
(После самостоятельного тестирования функция тоже не поддерживает). А для некоторых встроенных специальных типов, таких какPromise
,RegExp
,Date
, эти три типа данных передаются вreactive
Об ошибках не сообщается, и исходные данные возвращаются напрямую.
последний одиночный тест
test('markNonReactive', () => {
const obj = reactive({
foo: { a: 1 },
bar: markNonReactive({ b: 2 })
})
expect(isReactive(obj.foo)).toBe(true)
expect(isReactive(obj.bar)).toBe(false)
})
Вот ссылка на api-markNonReactive
, данные объекта, обернутые этим API, не станут ответными данными. Этот API следует реже использовать в реальном бизнесе, и его можно использовать для некоторых специальных оптимизаций производительности.
Прочитав одиночный тест, мыreactive
При определенном понимании: он может принять объект или массив и вернуть новые данные ответа. Данные ответа совпадают с исходными данными, и любая операция на любой стороне может быть синхронизирована с другой стороной.
Но это... не похоже на большое дело. Но судя по производительности одиночного теста, он основан на Proxy, и была сделана некоторая обработка границ и вложенности. Тогда это приводит к очень важному вопросу: ** Как vue@3 уведомляет представление об обновлении? Другими словами, когда данные ответа изменяются, как он уведомляет своего потребителя о необходимости что-то сделать? **Это поведение должно быть инкапсулировано в различных обработчиках, таких как set/get of Proxy. Но я еще не знаю, поэтому я могу только продолжать смотреть свысока на другие одиночные тесты.
С самого начала мы знали, чтоreactive
Возвращаемое значениеUnwrapNestedRefs
Тип, который на первый взгляд особенныйRef
Типа, тогда продолжим смотретьref
. (На самом деле, этот UnwrapNestedRefs предназначен для получения общего типа вложенных ссылок. Помните, что Unwrap — это глагол, который немного сбивает с толку и будет объяснен позже, когда мы будем говорить о разборе исходного кода)
Ref
Затем посмотрите на первый модульный тест ref:
it('should hold a value', () => {
const a = ref(1)
expect(a.value).toBe(1)
a.value = 2
expect(a.value).toBe(2)
})
Тогда давайте посмотримref
Объявление функции, передавая любые данные, может возвращатьRef
данные.
иRef
Неверный тип значения данныхreactive
Тип возвращаемого значения функции. Толькоreactive
Дженерики должны наследоваться от объектов (в js этоreactive
Параметр должен быть объектом), иRef
Данные не ограничены. Это,Ref
тип основан наReactive
Специальный тип данных, который поддерживает другие типы данных в дополнение к объекту.
Вернувшись в одиночный тест, мы видим, что прохождениеref
Функция является числом и также может возвращатьRef
объект, значение которого является числовым значением, переданным в момент времени, иМодификация разрешенаэто значение.
Посмотрите на второй одиночный тест:
it('should be reactive', () => {
const a = ref(1)
let dummy
effect(() => {
dummy = a.value
})
expect(dummy).toBe(1)
a.value = 2
expect(dummy).toBe(2)
})
Этот одиночный тест более информативен, а вдруг их большеeffect
концепция. Что бы это ни было, эффекту все равно передается функция, а внутри нее выполняется операция присваивания, котораяref
Значение (a.value) результата, возвращаемого функцией, присваивается фиктивному. Затем эта функция будет выполняться один раз по умолчанию, делая фиктивную 1. Когда a.value изменяется, функция эффекта будет выполняться повторно, делая фиктивным последнее значение.
Это,Если вы передадите метод эффекту, он будет выполнен один раз и будет выполняться повторно всякий раз, когда данные ссылки, от которых он зависит, изменяются.. Это разблокирует чтение доreactive
Сомнения: когда данные ответа меняются, как уведомить своих пользователей? Очевидно, через эффект. в любое времяreactive
Когда данные изменяются, запускается зависящий от них метод эффекта.
Я чувствую, что этого нетрудно добиться, поэтому, если бы это был я, это нужно было бы сделать так:
- Во-первых, вам нужно поддерживать двухмерную Карту эффектов;
- В направлении
effect
Функция передает функцию ответа; - Эта функция ответа будет выполнена один раз немедленно.Если на данные ответа ссылаются внутренне, поскольку эти данные были перехвачены с помощью установки/получения через прокси, зависимости этой функции могут быть соответственно собраны, и двумерная карта эффектов может быть обновлено.
- Когда какие-либо последующие данные ссылки изменяются (набор триггера), проверьте двухмерную карту, найдите соответствующий эффект и инициируйте их выполнение.
Но есть одна беда,ref
Функции также поддерживают необъектные данные, в то время как прокси поддерживает только объекты. Итак, в этой библиотекеreactivity
Для необъектных данных будет осуществляться слой объектной упаковки, а затем значение будет получено через .value.
Посмотрите на третий одиночный тест:
it('should make nested properties reactive', () => {
const a = ref({
count: 1
})
let dummy
effect(() => {
dummy = a.value.count
})
expect(dummy).toBe(1)
a.value.count = 2
expect(dummy).toBe(2)
})
Исходные данные, переданные функции ref, становятся объектом, и операции с его прокси-данными также запускают выполнение эффекта. После прочтения сначала возникло любопытство:
- Что, если бы он был вложен на один уровень дальше?
- Поскольку исходные данные являются объектом, если я изменю исходные данные напрямую, будут ли они синхронизированы с прокси-данными?
- Будет ли изменение исходных данных напрямую вызывать эффект?
Поэтому я предполагаю, что 1. он может быть вложенным, 2. он будет синхронизирован и 3. он не вызовет эффекта. Тест порядка был преобразован в:
it('should make nested properties reactive', () => {
const origin = {
count: 1,
b: {
count: 1
}
}
const a = ref(origin)
// 声明两个变量,dummy跟踪a.value.count,dummyB跟踪a.value.b.count
let dummy, dummyB
effect(() => {
dummy = a.value.count
})
effect(() => {
dummyB = a.value.b.count
})
expect(dummy).toBe(1)
// 修改代理数据的第一层数据
a.value.count = 2
expect(dummy).toBe(2)
// 修改代理对象的嵌套数据
expect(dummyB).toBe(1)
a.value.b.count = 2
expect(dummyB).toBe(2)
// 修改原始数据的第一层数据
origin.count = 10
expect(a.value.count).toBe(10)
expect(dummy).toBe(2)
// 修改原始数据的嵌套数据
origin.b.count = 10
expect(a.value.b.count).toBe(10)
expect(dummyB).toBe(2)
})
Результат такой, как я ожидал (на самом деле, я сначала попробовал, просто чтобы написать статью гладко, как я ожидал):
- Независимо от того, как вложен объект, изменение прокси-данных может вызвать эффект, зависящий от него.
- При изменении исходных данных прокси-данные могут быть синхронизированы при получении новых данных, но эффект, зависящий от его прокси-данных, не сработает.
Таким образом, мы можем сделать вывод: **за ****Ref**
**Обновление данных вызовет выполнение эффектов, которые от него зависят. **ТотReactive
Как насчет данных? Давайте продолжим чтение.
Четвертый одиночный тест
it('should work like a normal property when nested in a reactive object', () => {
const a = ref(1)
const obj = reactive({
a,
b: {
c: a,
d: [a]
}
})
let dummy1
let dummy2
let dummy3
effect(() => {
dummy1 = obj.a
dummy2 = obj.b.c
dummy3 = obj.b.d[0]
})
expect(dummy1).toBe(1)
expect(dummy2).toBe(1)
expect(dummy3).toBe(1)
a.value++
expect(dummy1).toBe(2)
expect(dummy2).toBe(2)
expect(dummy3).toBe(2)
obj.a++
expect(dummy1).toBe(3)
expect(dummy2).toBe(3)
expect(dummy3).toBe(3)
})
Наконец-то представлен четвертый одиночный тестreactive
. доreactive
В одиночном тесте пройдены все простые объекты. Здесь некоторые значения свойств в переданном объектеRef
данные. и после такого использования этиRef
Данные больше не нужно оценивать с помощью .value, даже внутренне вложенныеRef
Данные также не требуются. Используя вывод типа TS, мы можем это ясно увидеть.
В этот момент мы действительно можем понятьreactive
Почему возвращаемый типUnwrapNestedRefs<T>
. Из-за дженериковT
может бытьRef<T>
, так что этот тип возврата на самом деле означает: распаковать вложенныйRef
дженерикиT
. В частности, если ** передаетсяreactive
функция одинRef
data, тип данных, возвращаемый после выполнения функции,Ref
Тип данных необработанных данных данных. ** Этот человек, который не имел большого контакта с TS, не должен этого понимать, я подробнее остановлюсь на этом позже при анализе исходного кода.
Кроме того, этот единственный тест разрешил наши сомнения в предыдущем одиночном тесте,Исправлять Reactive
Данные также вызовут обновление эффекта.
Пятый одиночный тест
it('should unwrap nested values in types', () => {
const a = {
b: ref(0)
}
const c = ref(a)
expect(typeof (c.value.b + 1)).toBe('number')
})
Пятый одиночный тест интересен, мы обнаружили, что вложенныеRef
Для значения данных вам нужно использовать .value только в начале, а внутренним прокси-данным не нужно повторно вызывать .value. Объясните, что в предыдущем отдельном тесте дляreactive
Вложенность передачи функцийRef
Данные могут быть развернуты с помощьюreactive
Функция на самом деле не имеет значения, этоRef
Возможности самих данных. На самом деле, в соответствии с типом ТС и выводом типа мы также можем видеть:
Что, если я установлю больше слоев, например этот:
const a = {
b: ref(0),
d: {
b: ref(0),
d: ref({
b: 0,
d: {
b: ref(0)
}
})
}
}
const c = ref(a)
В любом случае, это просто набор наборов, набор наборов и набор наборов, согласно выводу типа TS, мы обнаружили, что в этой ситуации нет проблем, пока достаточно первого .value.
Однако этой возможности не хватало в первой версии, выпущенной Xiaoyou 5 октября, и она не могла вывести данные, вложенные глубже 9 уровней. этоcommitРешив эту проблему, студенты, интересующиеся выводом типа TS, могут взглянуть.
Шестой одиночный тест
test('isRef', () => {
expect(isRef(ref(1))).toBe(true)
expect(isRef(computed(() => 1))).toBe(true)
expect(isRef(0)).toBe(false)
// an object that looks like a ref isn't necessarily a ref
expect(isRef({ value: 0 })).toBe(false)
})
Об этом отдельном тесте сказать особо нечего, но есть некоторая полезная информация,computed
Хотя мы еще не коснулись его, мы знаем, что его возвращаемый результат также является ref данными.Другими словами, если эффект является зависимостьюcomputed
возвращает данные, то при их изменении эффект тоже будет выполняться.
последний одиночный тест
test('toRefs', () => {
const a = reactive({
x: 1,
y: 2
})
const { x, y } = toRefs(a)
expect(isRef(x)).toBe(true)
expect(isRef(y)).toBe(true)
expect(x.value).toBe(1)
expect(y.value).toBe(2)
// source -> proxy
a.x = 2
a.y = 3
expect(x.value).toBe(2)
expect(y.value).toBe(3)
// proxy -> source
x.value = 3
y.value = 4
expect(a.x).toBe(3)
expect(a.y).toBe(4)
// reactivity
let dummyX, dummyY
effect(() => {
dummyX = x.value
dummyY = y.value
})
expect(dummyX).toBe(x.value)
expect(dummyY).toBe(y.value)
// mutating source should trigger effect using the proxy refs
a.x = 4
a.y = 5
expect(dummyX).toBe(4)
expect(dummyY).toBe(5)
})
Этот единственный тест предназначен дляtoRefs
этого апи. По единому измерению,toRefs
иref
Разница в том,ref
превратит входящие данные вRef
тип, покаtoRefs
Требуется, чтобы входящие данные были объектом, а затем данные первого слоя этого объекта преобразуются вRef
тип. Я не знаю, для чего это можно использовать, просто знаю, какой эффект.
Пока что, прочитав единственный тест ref, вы можете примерно почувствовать, что наиболее важным назначением ref является,Реализовать захват необъектных данных. Другими словами, кажется, что другого особого применения нет. На самом деле в тестовом файле эффекта на данный момент только тестreactive
Данные запускают метод эффекта.
Тогда давайте посмотрим на тестовый файл эффекта.
Effect
effect
Поведение на самом деле из приведенного выше тестового файла мы уже можем понять. Главное — следить за изменениями в ответных данных и запускать выполнение функции монитора. Описание простое, ноeffect
Существует множество отдельных измерений для , с 39 вариантами использования, более 600 строк кода и множеством соображений пограничного случая. Что касается эффектов, я не буду перечислять их по одному. Позвольте мне сначала прочитать его для вас, затем обобщить его в несколько небольших пунктов, непосредственно обобщить ключевые выводы и при необходимости вставить соответствующий тестовый код.
базовая способность
- Метод, переданный эффекту, будет выполнен сразу один раз.. (Если второй параметр не проходит { lazy: true }, что зависит от исходного кода, одиночный тест не проходит, и заинтересованные студенты могут перейти на PR).
-
reactive
Он может наблюдать за изменениями данных в цепочке прототипов и контролироваться функцией эффекта, а также может наследовать средства доступа к свойствам (get/set) в цепочке прототипов.
it('should observe properties on the prototype chain', () => {
let dummy
const counter = reactive({ num: 0 })
const parentCounter = reactive({ num: 2 })
Object.setPrototypeOf(counter, parentCounter)
effect(() => (dummy = counter.num))
expect(dummy).toBe(0)
delete counter.num
expect(dummy).toBe(2)
parentCounter.num = 4
expect(dummy).toBe(4)
counter.num = 3
expect(dummy).toBe(3)
})
- Любая операция чтения любых данных ответа может быть выполнена в ответ, а любая операция записи любых данных ответа может быть отслежена. пока не:
- Ключевым значением обновленных данных является некоторое встроенное специальное значение Symbol, такое как
Symbol.isConcatSpreadable
(в основном не задействованы в повседневном использовании) - Несмотря на то, что операция записи выполнена, данные не изменились, и функция прослушивателя не будет запущена.
- Ключевым значением обновленных данных является некоторое встроенное специальное значение Symbol, такое как
it('should not observe set operations without a value change', () => {
let hasDummy, getDummy
const obj = reactive({ prop: 'value' })
const getSpy = jest.fn(() => (getDummy = obj.prop))
const hasSpy = jest.fn(() => (hasDummy = 'prop' in obj))
effect(getSpy)
effect(hasSpy)
expect(getDummy).toBe('value')
expect(hasDummy).toBe(true)
obj.prop = 'value'
expect(getSpy).toHaveBeenCalledTimes(1)
expect(hasSpy).toHaveBeenCalledTimes(1)
expect(getDummy).toBe('value')
expect(hasDummy).toBe(true)
})
- Операции с необработанными данными данных ответа не будут запускать функцию прослушивателя.
- Функция слушателя может вводить другую функцию слушателя.
- Каждый раз, когда эффект выполняется, возвращается новая функция слушателя, даже если передается одна и та же функция.
it('should return a new reactive version of the function', () => {
function greet() {
return 'Hello World'
}
const effect1 = effect(greet)
const effect2 = effect(greet)
expect(typeof effect1).toBe('function')
expect(typeof effect2).toBe('function')
expect(effect1).not.toBe(greet)
expect(effect1).not.toBe(effect2)
})
- в состоянии пройти
stop
api, завершите функцию прослушивателя, чтобы продолжить прослушивание. (Я чувствую, что могу добавить большеstart
,Заинтересованные студенты могут упомянуть PR для Сяою)
it('stop', () => {
let dummy
const obj = reactive({ prop: 1 })
const runner = effect(() => {
dummy = obj.prop
})
obj.prop = 2
expect(dummy).toBe(2)
stop(runner)
obj.prop = 3
expect(dummy).toBe(2)
// stopped effect should still be manually callable
runner()
expect(dummy).toBe(3)
})
специальная логика
- ** может избежать бесконечных циклов, вызванных неявной рекурсией,Если данные ответа изменены внутри функции прослушивателя или несколько функций прослушивателя влияют друг на друга. Но это не предотвратит явную рекурсию, такую как вызов самого себя цикла функции слушателя.
it('should avoid implicit infinite recursive loops with itself', () => {
const counter = reactive({ num: 0 })
const counterSpy = jest.fn(() => counter.num++)
effect(counterSpy)
expect(counter.num).toBe(1)
expect(counterSpy).toHaveBeenCalledTimes(1)
counter.num = 4
expect(counter.num).toBe(5)
expect(counterSpy).toHaveBeenCalledTimes(2)
})
it('should allow explicitly recursive raw function loops', () => {
const counter = reactive({ num: 0 })
const numSpy = jest.fn(() => {
counter.num++
if (counter.num < 10) {
numSpy()
}
})
effect(numSpy)
expect(counter.num).toEqual(10)
expect(numSpy).toHaveBeenCalledTimes(10)
})
- **Если зависимости внутри эффекта имеют логические ответвления, функция слушателя будет повторно обновлять зависимости после каждого выполнения. **Следующим образом: когда
obj.run
заfalse
час,conditionalSpy
После повторного выполнения зависимости мониторинга обновляются, и последующиеobj.prop
Как изменить, функция прослушивателя больше не будет выполняться.
it('should not be triggered by mutating a property, which is used in an inactive branch', () => {
let dummy
const obj = reactive({ prop: 'value', run: true })
const conditionalSpy = jest.fn(() => {
dummy = obj.run ? obj.prop : 'other'
})
effect(conditionalSpy)
expect(dummy).toBe('value')
expect(conditionalSpy).toHaveBeenCalledTimes(1)
obj.run = false
expect(dummy).toBe('other')
expect(conditionalSpy).toHaveBeenCalledTimes(2)
obj.prop = 'value2'
expect(dummy).toBe('other')
expect(conditionalSpy).toHaveBeenCalledTimes(2)
})
ReactiveEffectOptions
effect
также может принимать второй параметрReactiveEffectOptions
, параметры следующие:
export interface ReactiveEffectOptions {
lazy?: boolean
computed?: boolean
scheduler?: (run: Function) => void
onTrack?: (event: DebuggerEvent) => void
onTrigger?: (event: DebuggerEvent) => void
onStop?: () => void
}
- lazy: расчет с задержкой, при значении true входящий эффект не будет выполняться немедленно.
-
computed: В одиночном тесте это не отражено, не знаю для чего он используется, может это связано с названием.
computed
Неважно, отпусти. - scheduler: функция планировщика, принятый входной параметр run - это функция, переданная эффекту, если передан планировщик, через него может быть вызвана функция слушателя.
-
onStop:пройти через
stop
Событие запускается, когда функция прослушивателя завершается. - onTrack:Только для отладки. Запускается во время сбора зависимостей (фаза получения).
- onTrigger:Только для отладки. Запускается перед выполнением функции прослушивателя после запуска обновления.
effect
Несмотря на то, что логики много, основные концепции все равно легко понять, нужно обратить внимание на некоторые специальные внутренние оптимизации, в будущем нужно сосредоточиться на чтении исходного кода. Следующийcomputed
Мы коснулись, но еще не читали.
Computed
Вычисляемые свойства. Этот одноклассник, написавший vue, должен знать, что это значит. мы смотрим наreactivity
Как именно в .
первый одиночный тест
it('should return updated value', () => {
const value = reactive<{ foo?: number }>({})
const cValue = computed(() => value.foo)
expect(cValue.value).toBe(undefined)
value.foo = 1
expect(cValue.value).toBe(1)
})
В направленииcomputed
Передайте функцию получения, функция внутренне зависит отReactive
Данные после выполнения функции возвращают объект вычисления, значение которого является возвращаемым значением функции. когда это зависитReactive
При изменении данных расчетные данные могут синхронизироваться, как если быRef
Ах. по фактуref
Мы уже знаем в тестовом файле, что возвращаемый результат вычислений также является своего родаRef
данные.
Проверьте тип TS, конечно жеComputedRef
унаследовано отRef
,по сравнению сRef
Добавлено свойство эффекта только для чтения, типReactiveEffect
. Как вы можете догадаться, значение атрибута эффекта здесь должно быть тем, что мы передаем вcomputed
Расчетная функция , а затемeffect
Результат, возвращаемый после выполнения функции. Кроме того, егоvalue
доступен только для чтения, заявивcomputed
Значение возвращаемого результата доступно только для чтения.
второй одиночный тест
it('should compute lazily', () => {
const value = reactive<{ foo?: number }>({})
const getter = jest.fn(() => value.foo)
const cValue = computed(getter)
// lazy
expect(getter).not.toHaveBeenCalled()
expect(cValue.value).toBe(undefined)
expect(getter).toHaveBeenCalledTimes(1)
// should not compute again
cValue.value
expect(getter).toHaveBeenCalledTimes(1)
// should not compute until needed
value.foo = 1
expect(getter).toHaveBeenCalledTimes(1)
// now it should compute
expect(cValue.value).toBe(1)
expect(getter).toHaveBeenCalledTimes(2)
// should not compute again
cValue.value
expect(getter).toHaveBeenCalledTimes(2)
})
Этот единственный тест говорит намcomputed
Множество функций:
- отличается от
effect
,В направленииcomputed
прошедшийgetter
Функция не будет выполняться немедленно, она будет выполняться только тогда, когда данные будут фактически использованы. - Не каждый раз, когда значение нужно вызывать снова
getter
функция, иgetter
Он не будет запускаться повторно при изменении данных, от которых зависит функция, и будет запускаться только при повторном использовании данных расчета после изменения зависимых данных.getter
функция.
В первом отдельном тесте мы предполагаемComputedRef
Свойство эффекта , передаетсяeffect
передача методаgetter
Функция прослушивателя, сгенерированная функцией. Но когда effect
В одном тесте, как только зависимые данные изменятся, функция слушателя будет выполнена немедленно, как и здесь.computed
производительность непостоянна. В этом должно быть что-то хитрое!
в предыдущем разделеEffect
Наконец, мы находимeffect
Второй параметр функции — это элемент конфигурации, и одна из конфигураций называется вычисляемой, которая не рассматривается в отдельном тесте. Предполагается, что этот элемент конфигурации реализует здесь отложенный расчет вычисляемых данных.
третий одиночный тест
it('should trigger effect', () => {
const value = reactive<{ foo?: number }>({})
const cValue = computed(() => value.foo)
let dummy
effect(() => {
dummy = cValue.value
})
expect(dummy).toBe(undefined)
value.foo = 1
expect(dummy).toBe(1)
})
Этот единственный тест доказывает, что мыRef
Гипотезы, представленные в главе 1:Если есть эффект, который зависитcomputed
возвращает данные, то при их изменении эффект тоже будет выполняться.
что еслиcomputed
Хотя возвращаемые данные не изменились, но изменились его зависимые данные? Это вызоветeffect
Что насчет казни? Я думаю, еслиcomputed
Если значение не изменилось, это не приведет к повторному выполнению функции прослушивателя, поэтому измените тест порядка:
it('should trigger effect', () => {
const value = reactive<{ foo?: number }>({})
const cValue = computed(() => value.foo ? true : false)
let dummy
const reactiveEffect = jest.fn(() => {
dummy = cValue.value
})
effect(reactiveEffect)
expect(dummy).toBe(false)
expect(reactiveEffect).toHaveBeenCalledTimes(1)
value.foo = 1
expect(dummy).toBe(true)
expect(reactiveEffect).toHaveBeenCalledTimes(2)
value.foo = 2
expect(dummy).toBe(true)
expect(reactiveEffect).toHaveBeenCalledTimes(2)
})
Потом я понял, что ошибался.reactiveEffect
зависит отcValue
,cValue
зависит отvalue
,если толькоvalue
измениться, несмотря ни на чтоcValue
Если есть какие-либо изменения, он будет запущен повторноreactiveEffect
. Я чувствую, что здесь можно оптимизировать, и заинтересованные студенты могут пойти в PR.
Четвертый одиночный тест
it('should work when chained', () => {
const value = reactive({ foo: 0 })
const c1 = computed(() => value.foo)
const c2 = computed(() => c1.value + 1)
expect(c2.value).toBe(1)
expect(c1.value).toBe(0)
value.foo++
expect(c2.value).toBe(2)
expect(c1.value).toBe(1)
})
Этот единственный тест показываетcomputed
изgetter
Функция может зависеть от другойcomputed
данные.
Пятый и шестой одиночные тесты относятся к использованию меняющихся цветов.computed
. Передаваемая концепция: использованиеcomputed
Данные аналогичны использованию обычных данных ответа и могут корректно инициировать выполнение функции мониторинга.
Седьмой одиночный тест
it('should no longer update when stopped', () => {
const value = reactive<{ foo?: number }>({})
const cValue = computed(() => value.foo)
let dummy
effect(() => {
dummy = cValue.value
})
expect(dummy).toBe(undefined)
value.foo = 1
expect(dummy).toBe(1)
stop(cValue.effect)
value.foo = 2
expect(dummy).toBe(1)
})
Этот единственный тест представляетstop
Этот апи черезstop(cValue.effect)
Прервано обновление ответа для этих вычисляемых данных.
Два последних одиночных теста
it('should support setter', () => {
const n = ref(1)
const plusOne = computed({
get: () => n.value + 1,
set: val => {
n.value = val - 1
}
})
expect(plusOne.value).toBe(2)
n.value++
expect(plusOne.value).toBe(3)
plusOne.value = 0
expect(n.value).toBe(-1)
})
it('should trigger effect w/ setter', () => {
const n = ref(1)
const plusOne = computed({
get: () => n.value + 1,
set: val => {
n.value = val - 1
}
})
let dummy
effect(() => {
dummy = n.value
})
expect(dummy).toBe(1)
plusOne.value = 0
expect(dummy).toBe(-1)
})
Эти два отдельных теста более важны. прежде чем мыcomputed
просто пройтиgetter
функцию, и ееvalue
Доступен только для чтения и не может напрямую изменять возвращаемое значение. Дайте нам знать здесь,computed
Вы также можете передать объект, содержащий методы get/set. получить этоgetter
функции, это легче понять.setter
Входные параметры, полученные функцией, назначаютсяcomptued
значение Значение данных. Таким образом, в приведенном выше случае использованияplusOne.value = 0
, так чтоn.value = 0 - 1
, запустить сноваdummy
становится -1.
Пока мы почти закончилиreactivity
Понятие системы, остальныеreadonly
иcollections
.readonly
Есть много одиночных тестовых файлов, но на самом деле концепция очень проста, т.е.reactive
Версия только для чтения.collections
Одиночный тест должен покрытьMap
,Set
,WeakMap
,WeakSet
ответ обновлен, и это не должно быть большой проблемой, если вы пока не посмотрите его.
Суммировать
После разбора у нас должно быть четкое представление об основных внутренних API.Подведем итоги и повторим:
reactive: основной метод этой библиотеки, передающий примитивные данные объектного типа и возвращающий прокси-данные через Proxy. При этом любые операции чтения или записи исходных данных перехватываются. Затем, когда данные прокси изменяются, эффект функции слушателя, который зависит от них, может быть запущен.
ref: это файл, который больше всего влияет на чтение кода (легко спутать связь между ним и реактивным, когда вы смотрите на код), но чтобы действительно понять его, вам нужно внимательно прочитать код. Рекомендуется оставить его в покое, прежде чем разъяснять другую логику.... когда ее не существует. Просто знайте, что самая важная роль этого файла — предоставить наборRef
тип.
effect: принимает функцию и возвращает новую функцию слушателяreactiveEffect
. Если функция прослушивателя внутренне полагается на реактивные данные, функция прослушивателя будет запущена при изменении данных.
computed: Вычислить данные, принять функцию получения или объект, содержащий поведение получения/установки, и вернуть реактивные данные. Он также запускает reactiveEffect, если он изменяется.
Наконец, я нарисовал общую картину, чтобы облегчить обзор памяти.
Но эта картинка, я не могу гарантировать, что она правильная, потому что я не закончил исходный код. На этой неделе я уделю немного времени написанию настоящего анализа исходного кода.
Автор этой статьи: Ant Insurance - Experience Technology Group - A Xiang
Адрес Наггетс:Сян Сюэчан