Это почти то же самое, картинка немного клоунская, а также можно посмотреть чужие более полные картинки.@mxin
предисловие
давно нет связиVueТеперь, когда я смотрел прямую трансляцию Йоды несколько дней назад, я говорил о看源码Некоторые из представлений предназначены для лучшего начала работы.vueИли хотите изучить внутреннюю структуру мышления?
Внутренний интерфейс: интервью, интервью спросят.
В большой среде кажется, что пока вы являетесь разработчиком, вы должны изучить исходный код, будь вы стажером, новичком или старым фронтендером с многолетним опытом.
Если вы застопоритесь и не будете следовать броску, внезапное давление сокрушит вас, и вам может быть трудно выжить в броске, даже если вы правы.
Если вам интересно, вы можете прочитатьоползни @Nuggetsнаписал боссШкала самооценки тревоги программиста.
Кажется, я слишком много наговорил не по теме, вместо того, чтобы жаловаться, лучше успокоиться и учиться вместе.ReactivityНекоторые основные принципы этого, я полагаю, что после прочтения статьи выvue 3Реагирование на данные имеет более глубокое понимание.
И почему выбирают
ReactivityДля модуля это связано с тем, что его степень связи низкая, и онvue3.0Один из основных модулей, стоимость исполнения очень высока.
Основы
Прежде чем начать, если вы не понимаетеES6некоторые, которые появляются高阶api,Такие как,Proxy, Reflect, WeakMap, WeakSet,Map, SetИ так далее, вы можете прочитать главу ресурса самостоятельно, и лучше всего сначала понять необходимые очки знаний и просмотреть ее снова.
Proxy
существует@vue/reactivityсередина,ProxyЭто краеугольный камень всего планирования.
пройти черезProxyпрокси-объект, который можно использовать вget, setВ методе выполняются следующие действия, например依赖收集,effect,track, triggerОперации и т. д. здесь подробно описываться не будут и будут подробно описаны позже.
Если есть ученики, которые не могут ждать, плюс талант и ум,
ES6Если у вас есть определенная основа, вы можете сразу перейти к основной главе для просмотра и размышлений.
Давайте напишем простойProxy. в этомhandleCallbackнаписано наset, getДля перехвата данных мониторинга текущего изменения значения атрибута используются два метода. Сначала код:
const user = {
name: 'wangly19',
age: 22,
description: '一名掉头发微乎其微的前端小哥。'
}
const userProxy = new Proxy(user, {
get(target, key) {
console.log(`userProxy: 当前获取key为${key}`)
if (target.hasOwnProperty(key)) return target[key]
return {
}
},
set(target, key, value) {
console.log(`userProxy: 当前设置值key为${key}, value为${value}`)
let isWriteSuccess = false
if (target.hasOwnProperty(key)) {
target[key] = value
isWriteSuccess = true
}
return isWriteSuccess
}
})
console.log('myNaame', userProxy.name)
userProxy.age = 23
Когда мы назначаем, изменяем и печатаем значение, мы запускаем текущийsetа такжеgetметод.
Это очень важно, о некоторых других свойствах и способах использования я не буду здесь вдаваться в подробности.
Reflect
ReflectНе класс, а встроенный объект. Об этом должен знать каждый, а не прямо实例化(new)используя, его функция сравнивает иProxyизhandlesЭто немного похоже, но к этому добавлено гораздо большеObjectМетоды.
Здесь мы не углубляемся
ReflectЕсли вы хотите понять функции студентов, соответствующий адрес можно найти в следующих ресурсах для обучения. В этой главе представленыReflectОбъект безопасной эксплуатации.
Следующее правильноuserнекоторые из объектов修改За примером операции вы можете обратиться к нему, который может быть использован в будущем.
const user = {
name: 'wangly19',
age: 22,
description: '一名掉头发微乎其微的前端小哥。'
}
console.log('change age before' , Reflect.get(user, 'age'))
const hasChange = Reflect.set(user, 'age', 23)
console.log('set user age is done? ', hasChange ? 'yes' : 'no')
console.log('change age after' , Reflect.get(user, 'age'))
const hasDelete = Reflect.deleteProperty(user, 'age')
console.log('delete user age is done?', hasDelete ? 'yes' : 'none')
console.log('delete age after' , Reflect.get(user, 'age'))
Принципы
Когда вы усвоите некоторые из предыдущих знаний, пора начинать@vue/reactivityГлава анализа исходного кода завершена. Далее начнем с простой идеи по реализации базовогоreactivity, когда вы поймете его основные принципы, вы@vue/reactivityиз依赖收集(track)а также触发更新(trigger),так же как副作用(effect)Какая именно работа.
reactive
reactiveдаvue3используется для создания引用类型изapi.
const user = reactive({
name: 'wangly19',
age: 22,
description: '一名掉头发微乎其微的前端小哥。'
})
Затем загляните внутрь функции,reactiveЧто именно делает метод?
Внутренне выполняетtargetРешение только для чтения, если вы пройдетеtargetЕсли это прокси только для чтения, он вернется напрямую. для нормальногоreactiveзатем он возвращаетсяcreateReactiveObjectзначение метода.
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
return target
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap
)
}
createReactiveObject
существуетcreateReactiveObject, что делается дляtargetдобавить одинproxyиграет роль. Это его ядро,reactiveНаконец-то получилproxyпрокси, ссылкаProxyПростой пример главы может быть известенreactiveКак это работает, так что давайте посмотримcreateReactiveObjectсделал что-то.
Сначала определите токtargetТип , если он не соответствует требованиям, сразу выдается предупреждение и возвращается исходное значение.
if (!isObject(target)) {
if (__DEV__) {
console.warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
Во-вторых, оценивается, был ли текущий объект проксирован и не доступен ли он только для чтения, затем он сам является прокси-объектом, тогда нет необходимости снова проксировать, и он напрямую возвращается как возвращаемое значение, чтобы избежать повторного проксирования.
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
Для этих кодов суждений их не очень сложно прочитать, обратите внимание наif ()В состоянии суда посмотрите, какое действие оно совершает. а такжеcreateReactiveObjectСамое главное, что нужно сделать, это создатьtargetизproxy, и поместите его вMapзаписано в.
И интереснее, где входящийtargetназывается разнымproxy handle. Тогда давайте посмотримhandlesЧто вы делали в .
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
proxyMap.set(target, proxy)
return proxy
тип ручек
В типе объекта укажитеObjectа такжеArrayа такжеMap,Set, WeakMap,WeakSetдифференцированный. они называют разныеProxy Handle.
-
baseHandlers.ts:Object&Arrayбудет вызывать под этим файломmutableHandlersобъект какProxy Handle. -
collectionHandlers.ts:Map,Set,WeakMap,WeakSetбудет вызывать под этим файломmutableCollectionHandlersобъект какProxy Handle.
/**
* 对象类型判断
* @lineNumber 41
*/
function targetTypeMap(rawType: string) {
switch (rawType) {
case 'Object':
case 'Array':
return TargetType.COMMON
case 'Map':
case 'Set':
case 'WeakMap':
case 'WeakSet':
return TargetType.COLLECTION
default:
return TargetType.INVALID
}
}
Будет вnew Proxyвернулся в соответствии сtargetTypeсудить.
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
Из-за ограниченного места ниже приведены только примеры.
mutableHandlersв качестве ориентира для анализа. когда понялmutableHandlersпосле дляcollectionHandlersПросто вопрос времени.
Proxy Handle
Как было сказано выше, по разнымTypeназывай разныеhandle, тогда давайте посмотримmutableHandlersЧто именно сделал.
В основе все мы знаемProxyможет получить объект конфигурации, который мы демонстрируемgetа такжеsetметод собственности. а такжеmutableHandlersвещи одного и того же значения, отдельно определенные внутреннеget, set, deleteProperty, has, oneKeysДождитесь нескольких параметров атрибута, если вы не знаете, что это значит, вы можете посмотреть на этоProxy Mdn. Здесь нужно понять被监听的数据пока это происходит增查删改После этого большая часть из них попадет в соответствующий канал поступления.
Здесь мы используем простойget, setдля простого примера моделирования.
function createGetter () {
return (target, key, receiver) => {
const result = Reflect.get(target, key, receiver)
track(target, key)
return result
}
}
const get = /*#__PURE__*/ createGetter()
function createSetter () {
return (target, key, value, receiver) => {
const oldValue = target[key]
const result = Reflect.set(target, key, value, receiver)
if (result && oldValue != value) {
trigger(target, key)
}
return result
}
}
существуетgetвыполнитtrackсбор зависимостей, в то время какsetсрабатывает, когдаtriggerспусковой механизм. существуетvue3,а такжеtriggerа такжеtrackслова все в насeffect.tsуказано в нем, то взгляните на следующий依赖收集а также响应触发Что-то сделал.
Effect
Весь модуль эффектов разделен на три части:
-
effect: Функция побочного эффекта -
teack: коллекция зависимостей, вproxyданные проксиgetкогда звонят -
trigger: реакция триггера, вproxyВызывается при изменении данных прокси.
effect
Давайте посмотрим на примерeffectиспользовать и понимать, что его главный аргумент — это функция. Внутри функция поможет вам выполнить регистрацию некоторых побочных эффектов и оценку функций.
effect(() => {
proxy.user = 1
})
приди и посмотриvueизeffectЧто ты сделал?
Здесь сначала определите текущие параметрыfnэтоeffect, если да, тоrawХранится вfnсделать замену. затем сделай это сноваcreateReactiveEffectгенерировать.
export function effect<T = any>(
fn: () => T,
options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect<T> {
if (isEffect(fn)) {
fn = fn.raw
}
const effect = createReactiveEffect(fn, options)
if (!options.lazy) {
effect()
}
return effect
}
существуетcreateReactiveEffectбудем ли мыeffectзатолкнуть вeffectStackПоместите операцию стека в , а затем используйтеactiveEffectполучить доступ к выполняемому в данный моментeffect, который будет выполняться после выполнения出栈. заменить заодноactiveEffectдля новой вершины стека.
пока вeffectбудет срабатывать во время выполненияproxy handleпотомtrackа такжеtriggerДве ключевые функции.
function createReactiveEffect<T = any>(
fn: () => T,
options: ReactiveEffectOptions
): ReactiveEffect<T> {
const effect = function reactiveEffect(): unknown {
if (!effect.active) {
return options.scheduler ? undefined : fn()
}
if (!effectStack.includes(effect)) {
cleanup(effect)
try {
enableTracking()
effectStack.push(effect)
activeEffect = effect
return fn()
} finally {
effectStack.pop()
resetTracking()
activeEffect = effectStack[effectStack.length - 1]
}
}
} as ReactiveEffect
effect.id = uid++
effect.allowRecurse = !!options.allowRecurse
effect._isEffect = true
effect.active = true
effect.raw = fn
effect.deps = []
effect.options = options
return effect
}
посмотреть короткую версиюeffect, помимо большей части кодового багажа, следующий код не намного понятнее.
function effect(eff) {
try {
effectStack.push(eff)
activeEffect = eff
return eff(...argsument)
} finally {
effectStack.pop()
activeEffect = effectStack[effectStack.length - 1]
}
}
трек (коллекция зависимостей)
существуетtrack, будет выполнен известный сбор зависимостей, а текущийactiveEffectдобавить вdepВнутри, и говоря об этом типе отношений. Это будет отношение один-ко-многим-ко-многим.
Это также очень ясно из кода.Прежде всего, у нас будет общийtargetMapэтоWeakMap,keyдаtarget(代理的对象), valueЯвляетсяMap, назови этоdepsMap, который используется для управления текущимtargetкаждый изkeyизdepsТакже известна как зависимость от побочных эффектов, также известная какdepend. существуетvue3черезSetприходи и уходи.
Первый шаг — опираться на текущийtargetПолучатьtargetMapсерединаdepsMap, если он не существует, продолжайтеtargetMap.set(target, (depsMap = new Map()))объявление инициализации, за которым следуетdepsMapВозьмите текущийkeyизdeps, если не найдено, также используйтеdepsMap.set(key, (dep = new Set()))Сделайте объявление инициализации и, наконец, установите текущийactiveEffectзатолкнуть вdeps, для сбора зависимостей.
-
- существует
targetMapнайти вtarget
- существует
-
- существует
depsMapнайти вkey
- существует
-
- Буду
activeEffectСохранитьdepв.
- Буду
В этом случае будет сформирован шаблон структуры «один-ко-многим-ко-многим», в котором хранятся всеproxyЗахваченные зависимости.
function track(target: object, type: TrackOpTypes, key: unknown) {
if (!shouldTrack || activeEffect === undefined) {
return
}
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
if (!dep.has(activeEffect)) {
dep.add(activeEffect)
activeEffect.deps.push(dep)
if (__DEV__ && activeEffect.options.onTrack) {
activeEffect.options.onTrack({
effect: activeEffect,
target,
type,
key
})
}
}
}
триггер (ответ на триггер)
существуетtriggerКогда это происходит, то фактически запускает выполнение текущей зависимости ответа.
Во-первых, вам нужно получить текущийkeyпо всем каналамdeps, так что вы увидите, что естьeffectsа такжеaddФункция, которую он делает, очень проста, заключается в том, чтобы судить о текущем входящемdepsMapНужно ли добавлять недвижимостьeffectsВнутри вот такое состояниеeffectне может быть текущимactiveEffectа такжеeffect.allowRecurse, чтобы гарантировать, что текущийset keyЗависимости выполняются.
const effects = new Set<ReactiveEffect>()
const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
if (effect !== activeEffect || effect.allowRecurse) {
effects.add(effect)
}
})
}
}
Следующие хорошо известные сценарии позволяют судить о некоторых текущих входящих изменениях.triggerбудет доставлен вTriggerOpTypesповедение, а затем выполнитьaddметод, который позволит квалифицироватьeffectдобавить вeffectsиди среди, здесь@vue/reactivityСделайте много данных, чтобы изменить поведение, напримерlengthРазнообразие.
Затем по разнымTriggerOpTypesпровестиdepsMapДанные вынимаются и, наконец, вводятсяeffects. затем черезrunметод преобразует текущийeffectвыполнить, черезeffects.forEach(run)выполнить.
if (type === TriggerOpTypes.CLEAR) {
// collection being cleared
// trigger all effects for target
depsMap.forEach(add)
} else if (key === 'length' && isArray(target)) {
depsMap.forEach((dep, key) => {
if (key === 'length' || key >= (newValue as number)) {
add(dep)
}
})
} else {
// schedule runs for SET | ADD | DELETE
if (key !== void 0) {
add(depsMap.get(key))
}
// also run for iteration key on ADD | DELETE | Map.SET
switch (type) {
case TriggerOpTypes.ADD:
if (!isArray(target)) {
add(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
add(depsMap.get(MAP_KEY_ITERATE_KEY))
}
} else if (isIntegerKey(key)) {
// new index added to array -> length changes
add(depsMap.get('length'))
}
break
case TriggerOpTypes.DELETE:
if (!isArray(target)) {
add(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
add(depsMap.get(MAP_KEY_ITERATE_KEY))
}
}
break
case TriggerOpTypes.SET:
if (isMap(target)) {
add(depsMap.get(ITERATE_KEY))
}
break
}
}
а такжеrunЧто ты опять сделал?
Во-первых, определить текущийeffectсерединаoptionsЕсть лиscheduler, если есть, используйтеscheduleЧтобы обработать выполнение, в противном случае напрямую выполнить его напрямуюeffect().
if (effect.options.scheduler) {
effect.options.scheduler(effect)
} else {
effect()
}
Чтобы немного сократить, чтобы посмотреть на логику обработки, она на самом деле изtargetMapПереписка в Китаеkeyзависимость.
const depsMap = targetMap.get(target)
if (!depsMap) {
return
}
const dep = depsMap.get(key)
if (dep) {
dep.forEach((effect) => {
effect()
})
}
Ref
Как мы все знаем,refдаvue3Объявление реактивных данных для общих типов. и получитьrefнужно передать значениеref.valueспособ получения, многие думают, чтоrefпростойreactiveНо это не так.
В исходном кодеrefв конце концов позвонитеcreateRefметод, который внутренне возвращаетRefImplпример. это сProxyразница в том,refСбор зависимостей и триггеры ответа находятся вgetter/setterСреди них это может относиться к фигуреdemoформа, адрес ссылкиgettter/setter.
export function ref<T extends object>(value: T): ToRef<T>
export function ref<T>(value: T): Ref<UnwrapRef<T>>
export function ref<T = any>(): Ref<T | undefined>
export function ref(value?: unknown) {
return createRef(value)
}
function createRef(rawValue: unknown, shallow = false) {
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
как показано на рисунке,vueсуществуетgetterнейтральныйproxy中的getтот же звонокtrackСоберите зависимости, вsetterв ходе выполнения_valueВызывается после изменения значенияtriggerвызывать.
class RefImpl<T> {
private _value: T
public readonly __v_isRef = true
constructor(private _rawValue: T, public readonly _shallow = false) {
this._value = _shallow ? _rawValue : convert(_rawValue)
}
get value() {
track(toRaw(this), TrackOpTypes.GET, 'value')
return this._value
}
set value(newVal) {
if (hasChanged(toRaw(newVal), this._rawValue)) {
this._rawValue = newVal
this._value = this._shallow ? newVal : convert(newVal)
trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal)
}
}
}
Тогда вы должны знать:
-
proxy handleдаreactiveпринцип, иrefПринципgetter/setter. - существует
getназывается, когдаtrack,setназывается, когдаtrigger -
effectЭто ядро ответа данных.
Computed
computedКак правило, есть два распространенных способа использования: один — это передача объекта с внутреннимsetа такжеgetметод, который принадлежитComputedOptionsформа.
export function computed<T>(getter: ComputedGetter<T>): ComputedRef<T>
export function computed<T>(
options: WritableComputedOptions<T>
): WritableComputedRef<T>
export function computed<T>(
getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>
)
А внутри будетgetter / setterдве переменные для сохранения.
когдаgetterOrOptionsКогда это функция, она будет назначена иgetter.
когдаgetterOrOptionsКогда это объект, он будетsetа такжеgetназначить наsetter,getter.
который затем создается как параметрComputedRefImplкласс и вернуть его как возвращаемое значение.
let getter: ComputedGetter<T>
let setter: ComputedSetter<T>
if (isFunction(getterOrOptions)) {
getter = getterOrOptions
setter = __DEV__
? () => {
console.warn('Write operation failed: computed value is readonly')
}
: NOOP
} else {
getter = getterOrOptions.get
setter = getterOrOptions.set
}
return new ComputedRefImpl(
getter,
setter,
isFunction(getterOrOptions) || !getterOrOptions.set
) as any
ТакComputedRefImplчто ты сделал?
Исходный код вычисляемого свойства, на самом деле большая его часть зависит от лицевой стороныeffectнекоторое понимание.
Во-первых, мы все знаем, чтоeffectможет пройти函数с одним对象options.
здесь будетgetterпередается как параметр функции, т.е.副作用, пока вoptionsнастроен вlazyа такжеscheduler.
lazyвыражатьeffectвыполняется не сразу иschedulerвtriggerопределит, прошли ли выscheduler, который выполняется после передачиschedulerметод.
пока вcomputed schedulerСреди них нынешний_dirtyЭтоfalse, если да, то будет_dirtyУстановить какtrueи выполнитьtriggerтриггерный ответ.
class ComputedRefImpl<T> {
private _value!: T
private _dirty = true
public readonly effect: ReactiveEffect<T>
public readonly __v_isRef = true;
public readonly [ReactiveFlags.IS_READONLY]: boolean
constructor(
getter: ComputedGetter<T>,
private readonly _setter: ComputedSetter<T>,
isReadonly: boolean
) {
this.effect = effect(getter, {
lazy: true,
scheduler: () => {
if (!this._dirty) {
this._dirty = true
trigger(toRaw(this), TriggerOpTypes.SET, 'value')
}
}
})
this[ReactiveFlags.IS_READONLY] = isReadonly
}
пока вgetter/setterЦентральная встреча_valueвыполнять разные операции.
Первый вget value, судить о текущем._dirtyЭтоtrue, если это так, выполните кешированныйeffectи сохранить возвращенный результат в_valueи выполнитьtrackВыполните сбор зависимостей.
Во-вторых, вset value, затем позвоните_setterспособ сброса значения.
get value() {
// the computed ref may get wrapped by other proxies e.g. readonly() #3376
const self = toRaw(this)
if (self._dirty) {
self._value = this.effect()
self._dirty = false
}
track(self, TrackOpTypes.GET, 'value')
return self._value
}
set value(newValue: T) {
this._setter(newValue)
}
ссылка на ресурс
Ниже приведены некоторые справочные ресурсы, заинтересованные друзья могут взглянуть
- WeakMap серии ES6
- Прокси и отражение
- Vue Mastery
- Vue Docs
- React представляет @vue/reactivity Vue3 для реализации адаптивного управления состоянием.
Суммировать
если вы используетеvueНастоятельно рекомендую себяdebugПрочитав эту статью, она определенно поможет вам в написании кода.vue3Полным ходом уже есть команды, работающие над производственной средой для разработки проекта, и экология сообщества потихоньку развивается.
@vue/reactivityСложность чтения невелика, есть много качественных туториалов, если есть определенная рабочая база и знание кода, можно шаг за шагом разобраться.
Лично мне не нужно разбираться в нем досконально и понимать значение каждой строки кода, а нужно понимать его основные идеи, изучать концепции фреймворка и некоторые идеи разработчиков фреймворка по написанию кода. Это все знания, которые можно позаимствовать и усвоить как свои собственные.
для того, кто пошел в
ReactДля фронтенда под экосистему читайтеVueИсходный код на самом деле больше для обогащения ваших знаний в размышлениях, а не для чтения его для интервью. Так же, как ваше одобрение не для экзаменов, а для получения знаний. В нынешних условиях это делать сложно, лучше успокоиться и сконцентрироваться на понимании какого-то знания, чем заучивать несколько сутр лицом к лицу.