
Наблюдатель на основе прокси
1 Что такое прокси
Прокси-объекты используются для определения пользовательского поведения для основных операций (таких как поиск свойств, присвоение, перечисление, вызовы функций и т. д.).
Прокси — это новая функция ES6.Чтобы воздействовать на цель, он в основном перехватывает определенные действия целевого объекта (такие как поиск атрибутов, присвоение, перечисление, вызов функции и т. д.) через метод перехвата в объекте-обработчике.
/* target: 目标对象,待要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。 */
/* handler: 一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 proxy 的行为。 */
const proxy = new Proxy(target, handler);
2 Зачем использовать прокси, плюсы и минусы перехода на прокси
** 3.0 принесет реализацию наблюдателя на основе прокси, которая обеспечивает полный спектр адаптивных возможностей, охватывающих язык (JavaScript — аннотация), устраняя некоторые ограничения текущей серии Vue 2 на основе Object.defineProperty, эти ограничения включают: 1. Мониторинг добавления и удаления атрибутов 2. Мониторинг модификации массивов на основе индексов и мониторинг модификации .length 3. Поддержка Map, Set, WeakMap и WeakSet;
Для vue2.0Object.definePropertyКак реализация реактивного принципа, но он будет иметь свои ограничения, такие какНе может отслеживать модификацию массива на основе индексов и не поддерживает такие дефекты, как Map, Set, WeakMap и WeakSet., поэтому прокси используется для решения этих проблем, что также означает, что vue3.0 откажется от совместимости с браузерами более ранних версий (совместимые версии выше ie11).
3 Базовое использование объекта hander в прокси
Ловец, используемый отзывчивостью vue3.0 (будет выделен далее)
handler.has() -> в оператореловец.(используется vue3.0) handler.get() -> свойство читатьЛовец действий.(используется vue3.0) handler.set() -> настройки свойств* Ловец действий.(используется vue3.0) handler.deleteProperty() -> удалить операторловец.(используется vue3.0) handler.ownKeys() -> Метод Object.getOwnPropertyNames и метод Object.getOwnPropertySymbolsловец.(используется vue3.0)
vue3.0 отзывчивый неиспользуемый ловец (заинтересованные студенты могут изучить его)
handler.getPrototypeOf() -> Object.getPrototypeOfловец метода.handler.setPrototypeOf() -> Object.setPrototypeOfловец метода.handler.isExtensible() -> Object.isExtensibleловец метода.handler.preventExtensions() -> Object.preventExtensionsловец метода.handler.getOwnPropertyDescriptor() -> Object.getOwnPropertyDescriptorловец метода.handler.defineProperty() -> Object.definePropertyловец метода.handler.apply() -> операция вызова функцииловец.handler.construct() -> новый операторловец.
① есть ловушка
has(target, propKey)
цель: целевой объект
propKey: имя перехватываемого свойства.
Функция: перехватить операцию определения того, содержит ли целевой объект свойство propKey.
Операция перехвата:propKey in proxy; не содержит цикла for...in
Соответствует отражению:Reflect.has(target, propKey)
🌰 Пример:
const handler = {
has(target, propKey){
/*
* 做你的操作
*/
return propKey in target
}
}
const proxy = new Proxy(target, handler)
② поймать ловца
get(target, propKey, receiver)
цель: целевой объект
propKey: имя перехватываемого свойства.
получатель: экземпляр прокси
Возвращает: возвращает свойство чтения
Роль: Перехватывать чтение свойств объекта
Операция перехвата: proxy[propKey] или оператор точки
Соответствует отражению:Reflect.get(target, propertyKey[, receiver])
🌰 Пример:
const handler = {
get: function(obj, prop) {
return prop in obj ? obj[prop] : '没有此水果';
}
}
const foot = new Proxy({}, handler)
foot.apple = '苹果'
foot.banana = '香蕉';
console.log(foot.apple, foot.banana); /* 苹果 香蕉 */
console.log('pig' in foot, foot.pig); /* false 没有此水果 */
Особый случай
const person = {};
Object.defineProperty(person, 'age', {
value: 18,
writable: false,
configurable: false
})
const proxPerson = new Proxy(person, {
get(target,propKey) {
return 20
//应该return 18;不能返回其他值,否则报错
}
})
console.log( proxPerson.age ) /* 会报错 */
③ установить ловушку
set(target,propKey, value,receiver)
цель: целевой объект
propKey: имя перехватываемого свойства.
value: новое установленное значение атрибута
получатель: экземпляр прокси
Возврат: вернуть true в строгом режиме, операция выполнена успешно, в противном случае она завершается сбоем и сообщается об ошибке
Роль: перехватить операцию присвоения атрибутов объекта.
Операция перехвата: proxy[propkey] = значение
Соответствует отражению:Reflect.set(obj, prop, value, receiver)
let validator = {
set: function(obj, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value)) { /* 如果年龄不是整数 */
throw new TypeError('The age is not an integer')
}
if (value > 200) { /* 超出正常的年龄范围 */
throw new RangeError('The age seems invalid')
}
}
obj[prop] = value
// 表示成功
return true
}
}
let person = new Proxy({}, validator)
person.age = 100
console.log(person.age) // 100
person.age = 'young' // 抛出异常: Uncaught TypeError: The age is not an integer
person.age = 300 // 抛出异常: Uncaught RangeError: The age seems invalid
Когда свойство объекта для записи имеет значение false, свойство не может быть изменено в перехватчике.
const person = {};
Object.defineProperty(person, 'age', {
value: 18,
writable: false,
configurable: true,
});
const handler = {
set: function(obj, prop, value, receiver) {
return Reflect.set(...arguments);
},
};
const proxy = new Proxy(person, handler);
proxy.age = 20;
console.log(person) // {age: 18} 说明修改失败
④ ловушка удаления свойства
deleteProperty(target, propKey)
цель: целевой объект
propKey: имя перехватываемого свойства.
Возврат: вернуть true только в строгом режиме, в противном случае сообщить об ошибке
Функция: Перехватить операцию удаления свойства propKey целевого объекта
Операция перехвата: удалить прокси[propKey]
Соответствует отражению:Reflect.delete(obj, prop)
var foot = { apple: '苹果' , banana:'香蕉' }
var proxy = new Proxy(foot, {
deleteProperty(target, prop) {
console.log('当前删除水果 :',target[prop])
return delete target[prop]
}
});
delete proxy.apple
console.log(foot)
/*
运行结果:
'当前删除水果 : 苹果'
{ banana:'香蕉' }
*/
Особый случай: когда свойство является ненастраиваемым, его нельзя удалить.
var foot = { apple: '苹果' }
Object.defineProperty(foot, 'banana', {
value: '香蕉',
configurable: false
})
var proxy = new Proxy(foot, {
deleteProperty(target, prop) {
return delete target[prop];
}
})
delete proxy.banana /* 没有效果 */
console.log(foot)
⑤Ловец собственных ключей
ownKeys(target)
цель: целевой объект
Возврат: массив (элементы массива должны быть символами или символами, другие типы сообщают об ошибках)
Роль: Перехватить операцию получения значения ключа
Операция перехвата:
1 Object.getOwnPropertyNames(proxy)
2 Object.getOwnPropertySymbols(proxy)
3 Object.keys(proxy)
4 for...in...петля
Соответствует отражению:Reflect.ownKeys()
var obj = { a: 10, [Symbol.for('foo')]: 2 };
Object.defineProperty(obj, 'c', {
value: 3,
enumerable: false
})
var p = new Proxy(obj, {
ownKeys(target) {
return [...Reflect.ownKeys(target), 'b', Symbol.for('bar')]
}
})
const keys = Object.keys(p) // ['a']
// 自动过滤掉Symbol/非自身/不可遍历的属性
/* 和Object.keys()过滤性质一样,只返回target本身的可遍历属性 */
for(let prop in p) {
console.log('prop-',prop) /* prop-a */
}
/* 只返回拦截器返回的非Symbol的属性,不管是不是target上的属性 */
const ownNames = Object.getOwnPropertyNames(p) /* ['a', 'c', 'b'] */
/* 只返回拦截器返回的Symbol的属性,不管是不是target上的属性*/
const ownSymbols = Object.getOwnPropertySymbols(p)// [Symbol(foo), Symbol(bar)]
/*返回拦截器返回的所有值*/
const ownKeys = Reflect.ownKeys(p)
// ['a','c',Symbol(foo),'b',Symbol(bar)]
Два Vue3.0 как построить отзывчивый
Есть два способа создать отзывчивость в vue3.0: Во-первых, это использование reactive в API-интерфейсе композиции для непосредственного создания отзывчивости.С появлением API-интерфейса композиции мы можем напрямую использовать функцию setup() в файле .vue для обработки большей части предыдущей логики, что означает, что мы не обязательно должны быть в файле .vue. В export default{ } объявляются жизненный цикл, функция data(){}, watch{}, вычисляемый{} и т. д. Вместо этого мы используем реактивный vue3.0 наблюдайте за жизненным циклом API в функции настройки, чтобы добиться того же эффекта. Таким образом, как и в случае с реактивными хуками, повышается частота повторного использования кода, а логика становится сильнее.
Во-вторых, использовать традиционную форму data(){ return{} }, vue3.0 не отказывается от поддержки записи vue2.0, но полностью совместим с записью vue2.0, обеспечиваяapplyOptionsДля обработки компонентов vue в виде параметров. Тем не менее, данные, просмотр, вычисление и другая логика обработки в параметрах по-прежнему обрабатываются API в составе API.
1 composition-api reactive
Reactive эквивалентен текущему API Vue.observable().Функция после реактивной обработки может стать отзывчивыми данными, аналогично возвращаемому значению функции данных, обработанной vue в опции api.
Давайте попробуем это с демонстрацией todoList.
const { reactive , onMounted } = Vue
setup(){
const state = reactive({
count:0,
todoList:[]
})
/* 生命周期mounted */
onMounted(() => {
console.log('mounted')
})
/* 增加count数量 */
function add(){
state.count++
}
/* 减少count数量 */
function del(){
state.count--
}
/* 添加代办事项 */
function addTodo(id,title,content){
state.todoList.push({
id,
title,
content,
done:false
})
}
/* 完成代办事项 */
function complete(id){
for(let i = 0; i< state.todoList.length; i++){
const currentTodo = state.todoList[i]
if(id === currentTodo.id){
state.todoList[i] = {
...currentTodo,
done:true
}
break
}
}
}
return {
state,
add,
del,
addTodo,
complete
}
}
2 options data
Нет разницы между options и vue2.0
export default {
data(){
return{
count:0,
todoList:[]
}
},
mounted(){
console.log('mounted')
}
methods:{
add(){
this.count++
},
del(){
this.count--
},
addTodo(id,title,content){
this.todoList.push({
id,
title,
content,
done:false
})
},
complete(id){
for(let i = 0; i< this.todoList.length; i++){
const currentTodo = this.todoList[i]
if(id === currentTodo.id){
this.todoList[i] = {
...currentTodo,
done:true
}
break
}
}
}
}
}
Три принципа реагирования
Различные типы реактивных
Vue3.0 может вводить различные методы API в соответствии с бизнес-требованиями. нужно здесь
① реактивный
Создайте реактивный реактив и верните прокси-объект.Этот реактив может выполнять глубокую рекурсию, то есть если раскрытое значение атрибута окажетсятип ссылкиот и поЦитировать, также будет использовать реактивныйРекурсивная обработка. И свойства могут быть изменены.
② мелкореактивный
Создайте отзывчивый smallReactive и верните прокси-объект. Отличие от реактивного состоит в том, что устанавливается только один уровень отзывчивости, то есть если обнаруживается свойство расширения.тип ссылкини будетрекурсия.
③ только для чтения
Объект, обработанный возвращенным прокси, может быть обработан рекурсивно, но свойства доступны только для чтения и не могут быть изменены. Может использоваться в качестве реквизита для передачи дочерним компонентам.
④ мелкоеТолько для чтения
Возвращает обработанный прокси-объект, но построение реактивных свойств доступно только для чтения, не расширяет ссылки и не рекурсивно преобразует, что можно использовать для создания прокси-объектов реквизита для компонентов с отслеживанием состояния.
Объекты хранения и прокси
Мы упоминали выше. Объект, обработанный и возвращенный Reactive, является прокси-объектом.Если есть много компонентов или если компонент реактивен много раз, будет много пар прокси-объектов и их исходных объектов. Чтобы установить отношения между прокси-объектом и исходным объектом, vue3.0 использует WeakMap для хранения этих объектных отношений. WeakMaps поддерживает слабую ссылку на объект, на который ссылается имя ключа, то есть эта ссылка не учитывается механизмом сборки мусора. Как только другие ссылки на указанный объект очищаются, механизм сборки мусора освобождает память, занятую объектом. То есть, как только он больше не нужен, объект имя-ключ и соответствующая пара ключ-значение в WeakMap автоматически исчезнут, и нет необходимости вручную удалять ссылку.
const rawToReactive = new WeakMap<any, any>()
const reactiveToRaw = new WeakMap<any, any>()
const rawToReadonly = new WeakMap<any, any>() /* 只读的 */
const readonlyToRaw = new WeakMap<any, any>() /* 只读的 */
Vue3.0 использует только чтение, чтобы указать, можно ли изменить объект, перехваченный перехватчиком, что соответствует сценарию одностороннего потока данных, когда предыдущие реквизиты не могут быть изменены. Давайте сосредоточимся на отношениях хранения следующих четырех слабых карт.
rawToReactive
пара ключ-значение: { [targetObject] : наблюдается }
target (ключ): значение целевого объекта (здесь можно понимать какreactiveпервый параметр . ) наблюдаемый (значение): прокси-объект после прокси-прокси.
reactiveToRawreactiveToRaw хранит пары ключ-значение, противоположные rawToReactive. пара ключ-значение { [наблюдается] : targetObject }
rawToReadonly
пара ключ-значение: {[цель]: наблюдается}
цель (ключ): целевой объект. obsered (значение): прокси-объект со свойствами только для чтения после прокси-сервера.
readonlyToRawСостояние хранения прямо противоположно rawToReadonly.
Анализ реактивного входа
Далее мы сосредоточимся на реактивных.
реактивная ({ ... объект }) запись
/* TODO: */
export function reactive(target: object) {
if (readonlyToRaw.has(target)) {
return target
}
return createReactiveObject(
target, /* 目标对象 */
rawToReactive, /* { [targetObject] : obseved } */
reactiveToRaw, /* { [obseved] : targetObject } */
mutableHandlers, /* 处理 基本数据类型 和 引用数据类型 */
mutableCollectionHandlers /* 用于处理 Set, Map, WeakMap, WeakSet 类型 */
)
}
reactiveФункция функции состоит в том, чтобы сгенерировать прокси через метод createReactiveObject, и для разных типов данных даны разные методы обработки.
createReactiveObject
Упомянутый ранее createReactiveObject, давайте посмотрим, что произошло с createReactiveObject.
const collectionTypes = new Set<Function>([Set, Map, WeakMap, WeakSet])
function createReactiveObject(
target: unknown,
toProxy: WeakMap<any, any>,
toRaw: WeakMap<any, any>,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>
) {
/* 判断目标对象是否被effect */
/* observed 为经过 new Proxy代理的函数 */
let observed = toProxy.get(target) /* { [target] : obseved } */
if (observed !== void 0) { /* 如果目标对象已经被响应式处理,那么直接返回proxy的observed对象 */
return observed
}
if (toRaw.has(target)) { /* { [observed] : target } */
return target
}
/* 如果目标对象是 Set, Map, WeakMap, WeakSet 类型,那么 hander函数是 collectionHandlers 否侧目标函数是baseHandlers */
const handlers = collectionTypes.has(target.constructor)
? collectionHandlers
: baseHandlers
/* TODO: 创建响应式对象 */
observed = new Proxy(target, handlers)
/* target 和 observed 建立关联 */
toProxy.set(target, observed)
toRaw.set(observed, target)
/* 返回observed对象 */
return observed
}
Общий процесс создания прокси-объекта с помощью приведенного выше исходного кода выглядит следующим образом: ① Сначала определите, был ли целевой объект проксирован ответом прокси, и если да, верните объект напрямую. ② Затем выберите, следует ли использовать тип данных, определяя, является ли целевой объект [Set, Map, WeakMap, WeakSet]collectionHandlers, ещеbaseHandlers-> — это mutableHandlers, переданные реактивнымОбъект Hander в качестве прокси. ③ Наконец, создайте наблюдаемый с помощью нового прокси-сервера, а затем сохраните целевую и наблюдаемую пары ключ-значение с помощью rawToReactive reactiveToRaw.
Грубая блок-схема:

Четыре объекта-перехватчика baseHandlers -> mutableHandlers
Ранее мы представили, что baseHandlers — это объект mutableHandlers, который передается при вызове реактивного метода createReactiveObject. Давайте сначала посмотрим на объект mutableHandlers
mutableHandlers
Область применения перехватчика
export const mutableHandlers: ProxyHandler<object> = {
get,
set,
deleteProperty,
has,
ownKeys
}
Vue3.0 использует вышеуказанные перехватчики. Мы представили основное использование этих перехватчиков в предыдущем разделе. Сначала мы рассмотрим несколько основных перехватчиков.
① получить, перехватить свойства чтения данных, включая синтаксис target.dot и target[]
②set, перехватывает сохраненные атрибуты данных.
③Перехваты оператора удаления DeleteProperty.
vue2.0объектыудалить операторПерехват атрибутов.
Пример 🌰:
delete object.a
не может быть проконтролировано.
vue3.0через проксиdeletePropertyможет быть перехваченудалить оператор, что означает, что отзывчивый vue3.0 может прослушивать операцию удаления атрибута.
④has, выполнить перехват атрибутов оператора in.
vue2.0объектыв оператореПерехват атрибутов.
пример
a in object
должен решить вышеуказанную проблему. Это означает, что vue3.0 можетв оператореперехват.
⑤собственные ключиObject.keys(proxy) ,for...in...loop Object.getOwnPropertySymbols(proxy),Object.getOwnPropertyNames(proxy)перехватчик
пример
Object.keys(object)
Это показывает, что vue3.0 может перехватывать вышеуказанные методы.
Стадия инициализации пяти компонентов
Если мы хотим понять весь реактивный принцип. Тогда инициализация компонента, реактивная обработка данных API-интерфейса во время процесса инициализации и сбор зависимостей атрибута данных на этапе компиляции неотделимы друг от друга. Vue3.0 предоставляет полную отзывчивую систему от инициализации до процесса рендеринга, обновления компонентов и уничтожения компонентов.Нам сложно объяснить вещи с одной точки зрения, поэтому мы формально говорим о том, как объекты-перехватчики собирают зависимости, прежде чем отправлять обновление , давайте посмотрим, что делает эффект.
1 эффект -> новый наблюдатель рендеринга
Vue3.0 заменяет наблюдателя vue2.0 хуком побочного эффекта. Все мы знаем, что в vue2.0 есть наблюдатель за рендерингом, который отвечает за повторный рендеринг представлений после изменения данных. Vue3.0 использует эффект вместо наблюдателя для достижения того же эффекта.
Давайте сначала кратко представим процесс mountComponent, а в следующих статьях мы подробно рассмотрим фазу монтирования.
1 mountComponent инициализирует mountComponent
// 初始化组件
const mountComponent: MountComponentFn = (
initialVNode,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
) => {
/* 第一步: 创建component 实例 */
const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(
initialVNode,
parentComponent,
parentSuspense
))
/* 第二步 : TODO:初始化 初始化组件,建立proxy , 根据字符窜模版得到 */
setupComponent(instance)
/* 第三步:建立一个渲染effect,执行effect */
setupRenderEffect(
instance, // 组件实例
initialVNode, //vnode
container, // 容器元素
anchor,
parentSuspense,
isSVG,
optimized
)
}
Вышеупомянутая основная часть всего mountComponent разделена на три шага, здесь мы расскажем, что делает каждый шаг:① Шаг 1: Создайте экземпляр компонента. ② Шаг 2: Инициализируйте компонент, создайте прокси и получите функцию рендеринга в соответствии с шаблоном персонажа. Обработка функции хука жизненного цикла и т. д. ③ Шаг 3: Создайте эффект рендеринга и выполните его.
Из приведенного выше метода мы видим, что вsetupComponentРеактивный объект построен, но еще неИнициализировать зависимости коллекции.
2 setupRenderEffect построить эффект рендеринга
const setupRenderEffect: SetupRenderEffectFn = (
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
) => {
/* 创建一个渲染 effect */
instance.update = effect(function componentEffect() {
//...省去的内容后面会讲到
},{ scheduler: queueJob })
}
Чтобы все могли более четко понять принцип отзывчивости, я оставлю только часть кода, связанную с принципом отзывчивости.
Роль setupRenderEffect
① Создайте эффект и назначьте его методу обновления экземпляра компонента для использования в качестве представления обновления рендеринга. ② componentEffect передается как функция обратного вызова в качестве первого параметра.
3 Что делает эффект
export function effect<T = any>(
fn: () => T,
options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect<T> {
const effect = createReactiveEffect(fn, options)
/* 如果不是懒加载 立即执行 effect函数 */
if (!options.lazy) {
effect()
}
return effect
}
Эффект следующий
① Сначала позвоните. создатьReactiveEffect ② Если это не отложенная загрузка, немедленно выполните функцию ReactiveEffect, созданную createReactiveEffect.
4 ReactiveEffect
function createReactiveEffect<T = any>(
fn: (...args: any[]) => T, /**回调函数 */
options: ReactiveEffectOptions
): ReactiveEffect<T> {
const effect = function reactiveEffect(...args: unknown[]): unknown {
try {
enableTracking()
effectStack.push(effect) //往effect数组中里放入当前 effect
activeEffect = effect //TODO: effect 赋值给当前的 activeEffect
return fn(...args) //TODO: fn 为effect传进来 componentEffect
} finally {
effectStack.pop() //完成依赖收集后从effect数组删掉这个 effect
resetTracking()
/* 将activeEffect还原到之前的effect */
activeEffect = effectStack[effectStack.length - 1]
}
} as ReactiveEffect
/* 配置一下初始化参数 */
effect.id = uid++
effect._isEffect = true
effect.active = true
effect.raw = fn
effect.deps = [] /* TODO:用于收集相关依赖 */
effect.options = options
return effect
}
createReactiveEffect
createReactiveEffectОсновная функция состоит в том, чтобы настроить некоторые параметры инициализации, а затем обернуть fn, переданный ранее,Важным моментом является назначение текущего эффекта на activeEffect, что очень важно и имеет прямое отношение к коллекции зависимостей
Здесь есть сомнение,
①Зачем использовать массив effectStack для хранения этого эффекта
Суммировать
Мы резюмируем фазу адаптивной инициализации здесь
① setupComponent создает компонент, вызывает API-интерфейс состава, обрабатывает параметры (обеспечивает отзывчивость) и получает объект Observer.
② Создайте эффект рендеринга, который обертывает реальный метод рендеринга componentEffect и добавляет некоторые свойства инициализации эффекта.
③ Затем немедленно выполните эффект, а затем назначьте текущий эффект рендеринга для activeEffect.
Наконец, мы используем картинку, чтобы объяснить весь процесс.

6 Коллекция зависимостей, что делает get?
1 Вернуться к методу get в mutableHandlers
1 Различные типы получения
/* 深度get */
const get = /*#__PURE__*/ createGetter()
/* 浅get */
const shallowGet = /*#__PURE__*/ createGetter(false, true)
/* 只读的get */
const readonlyGet = /*#__PURE__*/ createGetter(true)
/* 只读的浅get */
const shallowReadonlyGet = /*#__PURE__*/ createGetter(true, true)
Из вышеизложенного мы можем знать, что для четырех различных методов реагирования на здания, упомянутых выше, существует четыре различных метода получения, и следующее является взаимно-однозначным соответствием.
reactive ---------> get
shallowReactive --------> shallowGet
readonly ----------> readonlyGet
shallowReadonly ---------------> shallowReadonlyGet
Все четыре метода вызывают метод createGetter, но конфигурация параметров отличается.Мы используем здесь первый метод get в качестве эталона, а затем изучаем createGetter.
createGetter
function createGetter(isReadonly = false, shallow = false) {
return function get(target: object, key: string | symbol, receiver: object) {
const res = Reflect.get(target, key, receiver)
/* 浅逻辑 */
if (shallow) {
!isReadonly && track(target, TrackOpTypes.GET, key)
return res
}
/* 数据绑定 */
!isReadonly && track(target, TrackOpTypes.GET, key)
return isObject(res)
? isReadonly
?
/* 只读属性 */
readonly(res)
/* */
: reactive(res)
: res
}
}
Это основной процесс createGetter,специальный тип данныха такжеrefДавайте пока проигнорируем это. Здесь используются некоторые оценки процесса.Давайте воспользуемся блок-схемой, чтобы проиллюстрировать, что в основном делает эта функция?

Мы можем заключить, что:Во времена vue2.0. Reactive глубоко рекурсивна во время инициализациино
В отличие от vue2.0, даже при глубокой отзывчивости мы можем запустить следующий уровень глубокой отзывчивости только после получения предыдущего уровня get.Например
setup(){
const state = reactive({ a:{ b:{} } })
return {
state
}
}
Во время инициализации только первый уровень a устанавливает отзывчивость, а b не устанавливает отзывчивость, и когда мы используем state.a, мы фактически выполняем отзывчивую обработку b, то есть мы получили доступ к верхнему уровню. -level, атрибут следующего поколения действительно установит отзывчивость
Преимущество этого в том, что,1 Нет необходимости рекурсивно обрабатывать объекты во время инициализации, что приводит к ненужной нагрузке на производительность. *2 Есть несколько неиспользуемых состояний, поэтому здесь нет необходимости в глубокой реактивной обработке.
2 трек-> сборщик зависимостей
Давайте сначала посмотрим на источник трека:
что делает трек
/* target 对象本身 ,key属性值 type 为 'GET' */
export function track(target: object, type: TrackOpTypes, key: unknown) {
/* 当打印或者获取属性的时候 console.log(this.a) 是没有activeEffect的 当前返回值为0 */
let depsMap = targetMap.get(target)
if (!depsMap) {
/* target -map-> depsMap */
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
/* key : dep dep观察者 */
depsMap.set(key, (dep = new Set()))
}
/* 当前activeEffect */
if (!dep.has(activeEffect)) {
/* dep添加 activeEffect */
dep.add(activeEffect)
/* 每个 activeEffect的deps 存放当前的dep */
activeEffect.deps.push(dep)
}
}
В основном он вводит два понятияtargetMapа такжеdepsMap
targetMapпрокси пары ключ-значение: depsMap proxy : Объект наблюдателя после реактивного прокси. depsMap : для хранения карты карты, зависящей от dep.
depsMapПара ключ-значение: ключ : deps key — это имя свойства, к которому в данный момент обращается get, deps содержит установленный тип данных эффекта.
Мы знаем, что функция отслеживания примерно такова: сначала, в соответствии с прокси-объектом, получить depsMap, в котором хранятся отложения, затем получить соответствующее отложение с помощью ключа имени доступного атрибута, а затем сохранить текущий активированный эффект в текущем отложении для собирать зависимости.
основная функция1 Найдите DEP, соответствующий текущему прокси-серверу и ключу. ②dep устанавливает связь с текущим активным эффектом и собирает зависимости.
Для простоты понимания,targetMapа такжеdepsMapОтношения, мы используем пример, чтобы проиллюстрировать: пример: родительский компонент А
<div id="app" >
<span>{{ state.a }}</span>
<span>{{ state.b }}</span>
<div>
<script>
const { createApp, reactive } = Vue
/* 子组件 */
const Children ={
template="<div> <span>{{ state.c }}</span> </div>",
setup(){
const state = reactive({
c:1
})
return {
state
}
}
}
/* 父组件 */
createApp({
component:{
Children
}
setup(){
const state = reactive({
a:1,
b:2
})
return {
state
}
}
})mount('#app')
</script>
Мы используем диаграмму, чтобы представить вышеуказанное отношение:

Как срабатывает функция эффекта рендеринга
Как мы уже говорили, создайте рендеринг RenereFeffect, а затем назначьте значение ActiveFectefect, и, наконец, выполнить RendereFeft. Как выполнить коллекцию зависимости в течение этого периода, давайте посмотрим на то, что сделано в функции обновления, давайте вернемся до логики компонента
function componentEffect() {
if (!instance.isMounted) {
let vnodeHook: VNodeHook | null | undefined
const { el, props } = initialVNode
const { bm, m, a, parent } = instance
/* TODO: 触发instance.render函数,形成树结构 */
const subTree = (instance.subTree = renderComponentRoot(instance))
if (bm) {
//触发 beforeMount声明周期钩子
invokeArrayFns(bm)
}
patch(
null,
subTree,
container,
anchor,
instance,
parentSuspense,
isSVG
)
/* 触发声明周期 mounted钩子 */
if (m) {
queuePostRenderEffect(m, parentSuspense)
}
instance.isMounted = true
} else {
// 更新组件逻辑
// ......
}
}
Код здесь будет примерно формировать древовидную структуру через метод renderComponentRoot.Здесь следует отметить, что в методе setupComponent исходного mountComponent было скомпилировано содержимое шаблона шаблона, state.a, state.b и другие абстрактные синтаксические деревья. через метод компиляции compile. , на этом этапе будет запущена окончательная возвращенная функция рендеринга. В функции рендеринга синтаксис точки выражения state.a state.b в шаблоне будет заменен реальным атрибутом в данных. время выполняется реальный сбор зависимостей, который запускает метод get. Следующим шагом является запуск жизненного цикла beforeMount, а затем повторное пропатчивание всей древовидной структуры.
Сводка процесса сбора зависимостей
① Сначала выполните renderEffect, назначьте его для activeEffect, вызовите метод renderComponentRoot, а затем запустите функцию рендеринга.
② В соответствии с функцией рендеринга проанализируйте выражение шаблона после компиляции и обработки синтаксического дерева, получите доступ к атрибуту реальных данных и инициируйте получение.
③ Метод get сначала проходит через различные реактивы и собирает зависимости с помощью метода track.
④ Метод track находит соответствующий dep через текущую цель прокси-объекта и ключ имени доступного атрибута.
⑤ Свяжите dep с текущим активным эффектом. Вставьте activeEffect в массив dep (в это время dep уже содержит эффект рендеринга текущего компонента, что является основной причиной отзывчивости). выполнить его последовательно.
Наконец, мы используем блок-схему, чтобы выразить процесс сбора зависимостей.
Семь наборов обновлений дистрибутива
Далее устанавливаем часть логики.
const set = /*#__PURE__*/ createSetter()
/* 浅逻辑 */
const shallowSet = /*#__PURE__*/ createSetter(true)
Набор также делится на две логики, набор и мелкий набор. Оба метода генерируются createSetter. Здесь мы в основном анализируем набор.
createSetter создает набор
function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
const oldValue = (target as any)[key]
/* shallowSet逻辑 */
const hadKey = hasOwn(target, key)
const result = Reflect.set(target, key, value, receiver)
/* 判断当前对象,和存在reactiveToRaw 里面是否相等 */
if (target === toRaw(receiver)) {
if (!hadKey) { /* 新建属性 */
/* TriggerOpTypes.ADD -> add */
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
/* 改变原有属性 */
/* TriggerOpTypes.SET -> set */
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}
}
Процесс createSetter примерно такой
① Сначала оцените, равен ли текущий прокси-объект прокси-объекту, хранящемуся в reactiveToRaw через toRaw. ② Определите, есть ли у цели текущий ключ.Если он существует, измените атрибут и выполните триггер(цель, TriggerOpTypes.SET, ключ, значение, старое значение). ③ Если текущий ключ не существует, это означает, что назначается новый атрибут и выполняется триггер(цель, TriggerOpTypes.ADD, ключ, значение).
trigger
/* 根据value值的改变,从effect和computer拿出对应的callback ,然后依次执行 */
export function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
/* 获取depssMap */
const depsMap = targetMap.get(target)
/* 没有经过依赖收集的 ,直接返回 */
if (!depsMap) {
return
}
const effects = new Set<ReactiveEffect>() /* effect钩子队列 */
const computedRunners = new Set<ReactiveEffect>() /* 计算属性队列 */
const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
if (effect !== activeEffect || !shouldTrack) {
if (effect.options.computed) { /* 处理computed逻辑 */
computedRunners.add(effect) /* 储存对应的dep */
} else {
effects.add(effect) /* 储存对应的dep */
}
}
})
}
}
add(depsMap.get(key))
const run = (effect: ReactiveEffect) => {
if (effect.options.scheduler) { /* 放进 scheduler 调度*/
effect.options.scheduler(effect)
} else {
effect() /* 不存在调度情况,直接执行effect */
}
}
//TODO: 必须首先运行计算属性的更新,以便计算的getter
//在任何依赖于它们的正常更新effect运行之前,都可能失效。
computedRunners.forEach(run) /* 依次执行computedRunners 回调*/
effects.forEach(run) /* 依次执行 effect 回调( TODO: 里面包括渲染effect )*/
}
Мы сохраняем основную логику триггера здесь
① Сначала в targetMap найдите соответствующую depsMap согласно текущему прокси. ② Найдите соответствующие deps в depsMap в соответствии с ключом, а затем разделите соответствующую функцию обратного вызова эффекта и вычисленную функцию обратного вызова с помощью метода добавления. ③ Выполняйте callback-функции в очереди ComputedRunners и Effects по очереди.
В частности:
На данный момент очередь эффектов включает в себя вышеупомянутый renderEffect, отвечающий за рендеринг, а также эффект, установленный через API эффектов, и эффект, сформированный через часы. Здесь мы рассматриваем только эффекты рендеринга. Что касается последней ситуации, я поделюсь ею с вами в следующей статье.
Мы используем блок-схему, чтобы проиллюстрировать установленный процесс.

Восьмое резюме
Резюмируя, всю привязку данных по установлению отзывчивости можно условно разделить на три этапа.
1 Фаза инициализации: Фаза инициализации формирует соответствующийproxyобъекта, а затем сформировать эффект, отвечающий за рендеринг.
2 фаза сбора зависимостей get: инициируйте get путем анализа шаблона, замените реальный атрибут данных, а затем передайтеstackметод, который через прокси-объект и ключ формирует соответствующие deps, а в deps сохраняет эффект, отвечающий за отрисовку. (В этом процессе есть и другие эффекты, такие как watchEffect, хранящийся в deps).
3 установить фазу обновления отправки: когда мы меняем свойство this[key] = значение, первый проходtriggerметод, найдите соответствующие отложения через прокси-объект и ключ, затем классифицируйте отложения на calculatedRunners и эффекты, а затем выполните их последовательно, если это необходиморасписаниеДа, поместите это прямо в планировщик.
Микроканал общественного концерна регулярно сканирует кодовый номер, чтобы делиться техническими статьями