Анализ исходного кода адаптивной системы Vue3 — ссылки на статьи

Vue.js

предисловие

Чтение этой статьи требует определенной основы TypeScript, и требования не высоки, просто прочитайте документацию TS один раз.

Почему мы читаем исходный код? Не более чем 1: обучение; 2: лучшее использование этой библиотеки. Если вы просто хотите иметь общее представление о принципе, вам не нужно тратить время на чтение исходного кода, вы можете разобраться с помощью нескольких слов и картинок, которых в Интернете должно быть много. Поэтому процесс чтения исходного кода должен заключаться в глубоком понимании вещей, которые непонятны, что должно занимать очень много времени.

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

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

предисловие

Как упоминалось в предыдущей статье, ref — это файл, который больше всего влияет на чтение исходного кода. Но если вы не поймете это первым, у вас только больше закружится голова, глядя на других. Позвольте мне сначала помочь вам прояснить логику и концепцию исх.

Поскольку vue@3 еще не был официально выпущен (2019/10/9), никто не знаком с его использованием. Хотя предыдущая статья многое познакомила, остается еще много вопросов. Перед чтением этой статьи, если у вас есть время, рекомендуется прочитать официальное введение Vue в Composition API:

  1. Vue Composition API
  2. Ref Vs Reactive

Прочитав введение в Composition API, вы лучше поймете эту библиотеку, что облегчит понимание исходного кода.

refа такжеreactiveЭто ядро ​​всего исходного кода, и с помощью этих двух методов создаются адаптивные данные. полностью понятьreactivity, вы должны съесть эти два в первую очередь.

Ref

Самая важная роль ref — предоставить наборRefТип, давайте сначала посмотрим, что это за тип данных. (Для лучшего объяснения я изменю порядок объявления интерфейсов, типов, функций и т. д. в исходном коде и добавлю некоторые комментарии для облегчения чтения)

// 生成一个唯一key,开发环境下增加描述符 'refSymbol'
export const refSymbol = Symbol(__DEV__ ? 'refSymbol' : undefined)

// 声明Ref接口
export interface Ref<T = any> {
  // 用此唯一key,来做Ref接口的一个描述符,让isRef函数做类型判断
  [refSymbol]: true
  // value值,存放真正的数据的地方。关于UnwrapNestedRefs这个类型,我后续单独解释
  value: UnwrapNestedRefs<T>
}

// 判断是否是Ref数据的方法
// 对于is关键词,若不熟悉,见:http://www.typescriptlang.org/docs/handbook/advanced-types.html#using-type-predicates
export function isRef(v: any): v is Ref {
  return v ? v[refSymbol] === true : false
}

// 见下文解释
export type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRef<T>

чтобы понятьUnwrapNestedRefsа такжеUnwrapRef, вы должны сначала понятьinfer. Если вы не знали этого раньше, пожалуйста, прочитайте это первымСвязанная документация. После прочтения документа рекомендуется зайти в google, чтобы посмотреть некоторые кейсы для углубления впечатления.

Теперь мы предполагаем, что вы понимаетеinferконцепции, но и понимать ее ежедневное использование. Посмотрите еще раз на исходный код:


// 不应该继续递归的引用数据类型
type BailTypes =
  | Function
  | Map<any, any>
  | Set<any>
  | WeakMap<any, any>
  | WeakSet<any>

// 递归地获取嵌套数据的类型
// Recursively unwraps nested value bindings.
export type UnwrapRef<T> = {
  // 如果是ref类型,继续解套
  ref: T extends Ref<infer V> ? UnwrapRef<V> : T
  // 如果是数组,循环解套
  array: T extends Array<infer V> ? Array<UnwrapRef<V>> : T
  // 如果是对象,遍历解套
  object: { [K in keyof T]: UnwrapRef<T[K]> }
  // 否则,停止解套
  stop: T
}[T extends Ref
  ? 'ref'
  : T extends Array<any>
    ? 'array'
    : T extends BailTypes
      ? 'stop' // bail out on types that shouldn't be unwrapped
      : T extends object ? 'object' : 'stop']

// 声明类型别名:UnwrapNestedRefs
// 它是这样的类型:如果该类型已经继承于Ref,则不需要解套,否则可能是嵌套的ref,走递归解套
export type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRef<T>

Если вы все еще запутались, рекомендуется посмотреть на введение infer в будущем. Здесь мы выбрасываем результат напрямую:

Refтакая структура данных: она имеет ключSymbolАтрибут типа идентифицирует make, имеет свойствоvalueиспользуется для хранения данных. Эти данные могут быть любого типа,Только нельзя вкладыватьRefтип типа.В частности, нельзяArray<Ref>или вот так{ [key]: Ref }. Но странно, что этоRef<Ref>Опять это возможно. Я не знаю почему, поэтому я смело упомянулPR...

(В самом делеRef<Ref>Это не идеально В ночь на 2019.10.10 мой PR был слит. Когда у всех возникают сомнения, тоже смело можно упоминать о пиаре, а может и сольют....)

Кроме того, Map, Set, WeakMap и WeakSet также не поддерживают распаковку. иллюстрироватьRefЗначение данных также может бытьMap<Ref>такой тип данных.

Говоря о ссылке, из прошлой статьи мы узнали, чтоRefТип данных — это своего рода отзывчивые данные. Затем смотрим на его конкретную реализацию:

// 从@vue/shared中引入,判断一个数据是否为对象
// Record<any, any>代表了任意类型key,任意类型value的类型
// 为什么不是 val is object 呢?可以看下这个回答:https://stackoverflow.com/questions/52245366/in-typescript-is-there-a-difference-between-types-object-and-recordany-any
export const isObject = (val: any): val is Record<any, any> =>
  val !== null && typeof val === 'object'

// 如果传递的值是个对象(包含数组/Map/Set/WeakMap/WeakSet),则使用reactive执行,否则返回原数据
// 从上篇文章知道,这个reactive就是将我们的数据转成响应式数据
const convert = (val: any): any => (isObject(val) ? reactive(val) : val)

export function ref<T>(raw: T): Ref<T> {
  // 转化数据
  raw = convert(raw)
  const v = {
    [refSymbol]: true,
    get value() {
      // track的代码在effect中,暂时不看,能猜到此处就是监听函数收集依赖的方法。
      track(v, OperationTypes.GET, '')
      // 返回刚刚被转化后的数据
      return raw
    },
    set value(newVal) {
      // 将设置的值,转化为响应式数据,赋值给raw
      raw = convert(newVal)
      // trigger也暂时不看,能猜到此处就是触发监听函数执行的方法
      trigger(v, OperationTypes.SET, '')
    }
  }
  return v as Ref<T>
}

Сложнее всего понять этоrefфункция. Мы видим, что здесь тоже определено get/set, но нетProxyсопутствующие операции. Из предыдущей информации мы знаемreactiveМожно создать адаптивные данные, но параметры должны быть объектами. ноrefКогда входной параметр является объектом, он также долженreactiveсделать трансформацию. ЭтоrefКакова цель этой функции? Зачем тебе это?

В начале статьи я опубликовал это официальное введениеRef vs Reactive, что уже было сказано очень ясно.

However, the problem with going reactive-only is that the consumer of a composition function must keep the reference to the returned object at all times in order to retain reactivity. The object cannot be destructured or spread:

Для базовых типов данных при передаче функции или уничтожении объекта ссылка на исходные данные будет потеряна, другими словами, мы не можем сделать базовый тип данных или деконструируемую переменную (если ее значение также является базовым тип данных), становятся реактивными данными.

// 我们是永远没办法让`a`或`x`这样的基本数据成为响应式的数据的,Proxy也无法劫持基本数据。
const a = 1;
const { x: 1 } = { x: 1 }

Но иногда мы просто хотим, чтобы число, строка были реактивными, или просто хотим использовать деструктурирование. тогда что нам делать? Только путем создания объекта, то есть в исходном кодеRefданные, затем сохраните исходные данные вRefсвойстваvalueСреди них его ссылка возвращается пользователю. Поскольку это объект, созданный нами, нет необходимости его использовать.ProxyСнова стань агентом, захвати это напрямуюvalueПолучить/установить можно, этоrefфункция сRefПроисхождение типа.

Но только поrefВсе еще не может решить проблему деструктуризации объекта, он просто сохраняет основные данные в объекте.valueдля достижения оперативности данных. Для деструктуризации объекта требуется еще одна функция:toRefs.

export function toRefs<T extends object>(
  object: T
): { [K in keyof T]: Ref<T[K]> } {
  const ret: any = {}
  // 遍历对象的所有key,将其值转化为Ref数据
  for (const key in object) {
    ret[key] = toProxyRef(object, key)
  }
  return ret
}
function toProxyRef<T extends object, K extends keyof T>(
  object: T,
  key: K
): Ref<T[K]> {
  const v = {
    [refSymbol]: true,
    get value() {
      // 注意,这里没用到track
      return object[key]
    },
    set value(newVal) {
      // 注意,这里没用到trigger
      object[key] = newVal
    }
  }
  return v as Ref<T[K]>
}

Обходя объект, преобразуйте каждое значение свойства вRefданные, деконструированные таким образомRefdata, естественно, поддерживает ссылку на ответные данные. Но есть одно замечание в исходном коде,toRefsУпомянутая функцияtoProxyRefвместоref, он не вводится в get/settrackа такжеtrigger, то есть кtoRefsПередача обычного объекта не вернет ответные данные. ** Должен пройтиreactiveОбъект, возвращаемый выполнением, может иметь ответный эффект. Я чувствую, что этот момент можно оптимизировать, и я не знаю, почему Сяою делает это в настоящее время. Поскольку это будет касатьсяtrackа такжеtrigger, а эти два на момент написания статьи не изучались, так что упоминать о PR у меня нет смелости.

Здесь мы будемrefИсходный код прочитан.

Начнем следующую главуreactive, который является ядром, из которого формируются различныеapiЗапустите настоящую конкатенацию.


Автор этой статьи: Ant Insurance - Experience Technology Group - A Xiang

Адрес Наггетс:Сян Сюэчан