Skip to content
This repository has been archived by the owner on May 8, 2023. It is now read-only.

Latest commit

 

History

History
201 lines (154 loc) · 10.5 KB

06-view-render-introduction.md

File metadata and controls

201 lines (154 loc) · 10.5 KB

Рендеринг Представлений - Введение

Эта статья - часть серсии читая исходный код Vue.

В этой части мы разберём:

  • Как найти функции ренедеринга
  • Устройство рендера представлений

Ищем функции рендеринга

Итак - мы разобрались в процессе инцициализации и обновления данных. Теперь перейдём к рендерингу представлений и посмотрим, как Vue превращает наши данные в узлы DOM дерева.

Замечание: слово "рендер" относится ко всему процессу рендеринга представлений, тогда как форматированные render() или _render() указывает на имена функций.

Для начала, нам нужно найти функции, относящиеся к процессу ренедринга.

В пролой части мы разобрали, что mountComponent() будет использовать _update() и _render() для обновления представлений. Давайте поищем по проекту слово _update.

Vue.prototype._update = function(vnode: VNode, hydrating?: boolean) {
  const vm: Component = this;
  if (vm._isMounted) {
    callHook(vm, 'beforeUpdate');
  }
  const prevEl = vm.$el;
  const prevVnode = vm._vnode;
  const prevActiveInstance = activeInstance;
  activeInstance = vm;
  vm._vnode = vnode;
  // Vue.prototype.__patch__ создаётся в точке входа
  // Зависит от того, что используется для рендеринга
  if (!prevVnode) {
    // Первоначальный рендеринг
    vm.$el = vm.__patch__(
      vm.$el,
      vnode,
      hydrating,
      false /* removeOnly */,
      vm.$options._parentElm,
      vm.$options._refElm,
    );
  } else {
    // обновления
    vm.$el = vm.__patch__(prevVnode, vnode);
  }
  activeInstance = prevActiveInstance;
  // обновляет ссылку  __vue__
  if (prevEl) {
    prevEl.__vue__ = null;
  }
  if (vm.$el) {
    vm.$el.__vue__ = vm;
  }
  // Если родительский комонент -  HOC, заодно обновим его  $el
  if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
    vm.$parent.$el = vm.$el;
  }
  // Хук обновления вызывается планировщиком, чтобы гарантировать
  // Что дочерние элементы были обновлены по хуку родительского компонента
};

_update() использует __patch__() для обсчёта того, какие части нужно обновить и манипуляций с соответствующим DOM. Про __pathch__() поговорим попозже.

Вернёмся к mountComponent(), наша следующая цель - _render().

Мы видели, что _render() определён в core/instance/render.js, давайте снова на него посмотрим..

Vue.prototype._render = function (): VNode {
  const vm: Component = this
  const {
    render,  // <-- ВАЖНО
    staticRenderFns,
    _parentVnode
  } = vm.$options // <-- ВАЖНО

  if (vm._isMounted) {
    // Клонирует ноды слота при ререндере
    for (const key in vm.$slots) {
      vm.$slots[key] = cloneVNodes(vm.$slots[key])
    }
  }

  vm.$scopedSlots = (_parentVnode && _parentVnode.data.scopedSlots) || emptyObject

  if (staticRenderFns && !vm._staticTrees) {
    vm._staticTrees = []
  }
  // Задаёт родительский vnode. Это даёт рендер-функции доступ к данным
  // из узла-заглушки
  vm.$vnode = _parentVnode
  // собственно рендер
  let vnode
  try {
    vnode = render.call(vm._renderProxy, vm.$createElement) // <-- ВАЖНО
  } catch (e) {
  ...

Внутри _render(), вызывается render() , который извлечён из vm.$options.

После поиска $option по проекту, находим:

vm.$options = mergeOptions(
  resolveConstructorOptions(vm.constructor),
  options || {},
  vm,
);

options - это параметр, передаваемый при вызове new Vue({...}), так что render() должен приходить из vm.constructor. vm это экземпляр Vue, так что попробуем поискать options.render, может удастся найти где он задается..

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

Двойной клик.

Код:

...
const { render, staticRenderFns } = compileToFunctions(template, {
  shouldDecodeNewlines,
  delimiters: options.delimiters
}, this)
options.render = render
options.staticRenderFns = staticRenderFns
...

Круто, это обёртка над $mount(), которая вызывает compileToFunctions() с вашим шаблоном (template), получает render и staticRenderFns как позвращаемое значение и сохраняет их в options.render / options.staticRenderFns.

Продолжаем, compileToFunctions() появляется из ./compiler/index.js:

const { compile, compileToFunctions } = createCompiler(baseOptions);

export { compile, compileToFunctions };

createCompiler() comes from compiler/index.js:

// `createCompilerCreator` позволяет создавать компиляторы, использующие альтернативные
// парсеры/оптимизаторы/кодогенераторы, например компиляторы, оптимизарованные под серверный рендеринг (SSR)
// Пока мы используем реализацию по-умолчанию - мы просто экспортируем стандартный компилятор
export const createCompiler = createCompilerCreator(function baseCompile(
  template: string,
  options: CompilerOptions,
): CompiledResult {
  const ast = parse(template.trim(), options);
  optimize(ast, options);
  const code = generate(ast, options);
  return {
    ast,
    render: code.render,
    staticRenderFns: code.staticRenderFns,
  };
});

createCompilerCreator(), хорошее название! Это функция высшего порядка, которая создаёт функцию createCompiler(), которая используется для создания компилятора. Позже наш шаблон будет преобразован этим компилятором.

Прочитав исходный код createCompilerCreator(), мы увидим, что это просто обёртка. Основная фукнция - baseCompile(), которая использует parse(), optimise() и generate() чтобы сделать всю работу.

Проясним:

  • сначала выбираем ваш парсер, оптимизатор и кодогенератов, чтобы создать главную фукнцию-компилятор (или используете стандартную)
  • дальше пробрасываем фукнцию-компилятор в createCompilerCreator(), которая вернёт фукнцию используемую для создания компилятора, поэтому такое название -createCompiler()
  • дальше вызываем createComiler() с параметрами и получаем собственно компилятор
  • наконец, используем компилятор для обработки шаблона

Цель такого сложного процесса - вынести наружу компилятор и параметры. Если взглянуть на core compiler function (функию компиляции) and options (опции), как на два параметра для createCompiler(), можно заметить, что это очень похоже на каррирование. Два параметра приходят в разное время, так что в createCompilerCreator() приходится создавать и возвращать новую функцию, для сохранения core compiler function (та самая createCompiler()). Когда будут переданы options(опции) - createCompiler() скомбинирует параметры и создаст финальный компилятор.

Мы нашли все функции, относящиеся к процессу рендеринга _update(), __patch__(), _render(), createCompiler(), parse(), optimize(), generate(). Давайте разберём их взаимодействие.

Структура ренедеринга

Процесс ренедеринга начинается, когда вызывается mountComponent(). Он вызывает _render() для компиляции шаблона в render и staticRenderFns, после чего передаёт их в _update() для вычисления операций и применяет их к DOM.

Следующий шаг

Следующие две статьи будут о compiler и patch - вы увидите, как спроектирован VDom и как быстро просчитывать изменения.

Читайте продолжение : Рендеринг Представлений - Компилятор.

Практика

vnode = render.call(vm._renderProxy, vm.$createElement);

Эта строчка расположена в _render(), попробуйте разобраться что такое vm._renderProxy и для чего он нужен.