Рекомендуемые ресурсы для чтения
Адрес хостинга исходного кода vue.js
Адрес инструмента статической проверки потока
Виртуальная библиотека DOM с открытым исходным кодом
[Чтение исходного кода vue] Что делает импорт Vue из 'vue'?
предисловие
Основная идея Vue.js — управление данными. Иными словами, представление создается данными. Наша модификация представления не будет напрямую манипулировать DOM, а будет изменять данные. Когда взаимодействие сложное, только забота об изменении данных сделает логику кода очень понятной, потому что DOM становится отображением данных, вся наша логика заключается в изменении данных, не касаясь DOM, такой код Очень прост в обслуживании.
В Vue.js мы можем использовать краткий синтаксис шаблона для декларативного рендеринга данных в DOM:
<div id="app">
{{ msg }}
</div>
var app = new Vue({
el: '#app',
data: {
msg: 'Hello world!'
}
})
Страница результатов покажетHello world!
. Это знания, которые вы знаете, когда начинаете работать с vue.js. Итак, теперь мы должны спросить, что сделал исходный код vue.js, чтобы шаблон и данные наконец можно было отобразить в DOM? ? ?
отnew Vue()
Начинать
При написании проекта vue он будет находиться в файле входа проекта.main.js
Создайте экземпляр vue в файле.
следующим образом:
var app = new Vue({
el: '#app',
data: {
msg: 'Hello world!'
},
})
Из итогового вывода предыдущей статьи видно, чтоVue — это класс, реализованный с помощью функции.. Исходный код выглядит следующим образом:src/core/instance/index.js
середина
// _init 方法所在的位置
import { initMixin } from './init'
// Vue就是一个用 Function 实现的类,所以才通过 new Vue 去实例化它。
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
когда мы в проектеnew Vue({})
Когда объект передается, это фактически выполнение вышеуказанного метода, и переданные параметрыoptions
, а потом позвонилthis._init(options)
метод. Метод находится вsrc/core/instance/init.js
в файле. код показывает, как показано ниже:
import { initState } from './state'
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// 定义了uid
vm._uid = uid++
let startTag, endTag
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
startTag = `vue-perf-start:${vm._uid}`
endTag = `vue-perf-end:${vm._uid}`
mark(startTag)
}
vm._isVue = true
// 合并options
if (options && options._isComponent) {
initInternalComponent(vm, options)
} else {
// 这里将传入的options全部合并在$options上。
// 因此我们可以通过$el访问到 vue 项目中new Vue 中的el
// 通过$options.data 访问到 vue 项目中new Vue 中的data
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// 初始化函数
vm._self = vm
initLifecycle(vm) // 生命周期函数
initEvents(vm) // 初始化事件链
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
vm._name = formatComponentName(vm, false)
mark(endTag)
measure(`vue ${vm._name} init`, startTag, endTag)
}
// 判断当前的$options.el是否有el 也就是说是否传入挂载的DOM对象
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
Это видно из приведенного выше кодаthis._init(options)
В основном комбинированная конфигурация, жизненный цикл инициализации, центр событий инициализации, рендеринг инициализации, данные инициализации, реквизиты, вычисления, наблюдатель и т. д. Важная часть ничего не делает в коде.
Затем давайте возьмем одну из функций в качестве примера для анализа: взятьinitState(vm)
Например:
Почему данные, определенные в data, могут быть доступны в функции ловушки?
В проекте vue, когда данные определены, доступ к свойствам, определенным в данных, можно получить в функции ловушки компонента или в функции методов. почему это? ?
var app = new Vue({
el: '#app',
data:(){
return{
msg: 'Hello world!'
}
},
mounted(){
console.log(this.msg) // logs 'Hello world!'
},
Анализ исходного кода: вы можете увидетьthis._init(options)
метод, в разделе функции инициализации естьinitState(vm)
функция. Этот метод действительно./state.js
Среда: Конкретный код выглядит следующим образом:
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
// 如果定义了 props 就初始化props;
if (opts.props) initProps(vm, opts.props)
// 如果定义了methods 就初始化methods;
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
// 如果定义了data,就初始化data;(要分析的内容从这里开始)
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
существуетinitState
Решение в методе: если данные определены, инициализируйте данные, продолжайте смотреть на функцию для инициализации данных:initData(vm)
. код показывает, как показано ниже:
function initData (vm: Component) {
/*
这个data 就是 我们vue 项目中定义的data。也就是上面例子中的
data(){
return {
msg: 'Hello world!'
}
}
*/
let data = vm.$options.data
// 拿到data 后,做了判断,判断它是不是一个function
data = vm._data = typeof data === 'function'
? getData(data, vm) // 如果是 执行了getData()方法 ,这个方法就是返回data
: data || {}
// 如果不是一个对象则在开发环境报出一个警告
if (!isPlainObject(data)) {
data = {}
process.env.NODE_ENV !== 'production' && warn(
'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
)
}
// 拿到data 定义的属性
const keys = Object.keys(data)
// 拿到props
const props = vm.$options.props
// 拿到 methods
const methods = vm.$options.methods
let i = keys.length
// 做了一个循环对比,如果在data 上定义的属性,就不能在props与methods在定义该属性。因为不管是data里定义的,在props里定义的,还是在medthods里定义的,最终都挂载在vm实例上了。见proxy(vm, `_data`, key)
while (i--) {
const key = keys[i]
if (process.env.NODE_ENV !== 'production') {
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
)
}
}
if (props && hasOwn(props, key)) {
process.env.NODE_ENV !== 'production' && warn(
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else if (!isReserved(key)) {
proxy(vm, `_data`, key) // 代理 定义了Getter 和 Setter
}
}
// observe data
observe(data, true /* asRootData */)
}
// proxy 代理
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
}
export function proxy (target: Object, sourceKey: string, key: string) {
// 通过对象 sharedPropertyDefinition 定义了Getter 和 Setter
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
// 当访问vm.key 的时候其实访问的是 vm[sourceKey][key]
// 以上述开始的问题,当访问this.msg 实际是访问 this._data.msg
}
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val
}
// 对vm 的 key 做了一次Getter 和 Setter
Object.defineProperty(target, key, sharedPropertyDefinition)
}
Подводя итог: инициализация данных на самом деле./state.js
в файле. воплощать в жизньinitState()
метод, который определяет, что если данные определены, инициализирует данные.
Если данные являются функцией, выполнитеgetData()
методreturn data.call(vm, vm)
. Затем выполните циклическое сравнение атрибутов, определенных в данных на виртуальной машине, реквизитах на виртуальной машине и атрибутах в методах на виртуальной машине.Если атрибуты определены для данных, атрибуты не могут быть определены в реквизитах и методы. Потому что независимо от того, определен ли он в данных, в свойствах или в методах, он в конечном итоге монтируется на экземпляре виртуальной машины. См. прокси(vm,_data
, ключ).
Затем привяжите методы Getter и Setter к свойствам виртуальной машины с помощью прокси-метода. Вернемся к предыдущему вопросу: при доступе к this.msg фактически осуществляется доступ к vm._data.msg. Следовательно, данные, определенные в data, действительно могут быть доступны в функции ловушки.
Я должен еще раз сказать, что логика инициализации Vue написана очень четко, и различные функциональные логики разделены на несколько отдельных функций для выполнения, так что логика основной линии ясна с первого взгляда.Такие идеи программирования очень достойны внимания. ссылка и обучение.
Вы можете самостоятельно добавить другое содержимое для инициализации, далее см. раздел «Монтирование ВМ». В конце инициализации обнаруживается, что при наличии атрибута el он вызываетсяvm.$mount
Методы Mount VM, монтируемая цель - сделать шаблон в конечный DOM, затем следующий запрос процесса Vue Bar Mount
Реализация монтирования экземпляра Vue
Во Vue мы переходим$mount
Метод экземпляра для монтирования vm. Далее мы рассмотрим реализацию$mount('#app')
Что делал исходный код? ? ?
new Vue({
render: h => h(App),
}).$mount('#app')
$mount
Методы определены в нескольких файлах, таких какsrc/platform/web/entry-runtime-with-compiler.js
,src/platform/web/runtime/index.js
,src/platform/weex/runtime/index.js
. потому что$mount
Реализация этого метода связана с платформой и методом построения.
Просто выберите версию компилятора$mount
Проанализируйте его, адрес файла находится по адресуsrc/platform/web/entry-runtime-with-compiler.js
, код показан ниже:
// 获取vue 原型上的 $mount 方法, 存在变量 mount 上。
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
// query 定义在 './util/index'文件中
// 调用原生的DOM api querySelector() 方法。最后将el转化为一个DOM 对象。
el = el && query(el)
...
return mount.call(this, el, hydrating)
}
Читая код, мы видим, что код сначала получает прототип vue на$mount
метод, сохраните его в переменной mount, а затем переопределите метод. Этот метод обрабатывает входящий el, который может быть строкой или объектом DOM. а потом позвонилquery()
метод, который находится в./util/index
в файле. В основном вызывайте собственный метод DOM API querySelector(). Наконец, преобразуйте el в объект DOM и верните его. Выше размещена только основная часть кода.
Исходный код также оценивает el, чтобы определить, является ли входящий el body или html , если это так, в среде разработки будет сообщено предупреждение. Vue нельзя напрямую смонтировать в body и html, потому что он будет перезаписан, и будет сообщено об ошибке, когда будет перезаписан весь документ html или body.
Исходный код также получает $options, чтобы определить, следует ли определять метод рендеринга. Если метод рендеринга не определен, строка el или шаблона в конечном итоге будет скомпилирована вrender()
функция.
наконецreturn mount.call(this, el, hydrating)
. Крепление здесь на прототипе vue$mount
метод. в файле./runtime/index
. код показывает, как показано ниже:
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
Параметр el представляет смонтированный элемент, который может быть строкой или объектом DOM. Если это строка, она будет вызываться в среде браузера.query()
метод в объект DOM. Второй параметр связан с рендерингом на стороне сервера, в среде браузера нам не нужно передавать второй параметр. Вызывается при последнем возвратеmountComponent()
метод. Метод определен вsrc/core/instance/lifecycle.js
, код показан ниже:
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
...
let updateComponent
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
updateComponent = () => {
const name = vm._name
const id = vm._uid
const startTag = `vue-perf-start:${id}`
const endTag = `vue-perf-end:${id}`
mark(startTag)
const vnode = vm._render()
mark(endTag)
measure(`vue ${name} render`, startTag, endTag)
mark(startTag)
vm._update(vnode, hydrating)
mark(endTag)
measure(`vue ${name} patch`, startTag, endTag)
}
} else {
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
}
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
hydrating = false
// manually mounted instance, call mounted on self
// mounted is called for render-created child components in its inserted hook
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
Читая код, мы видим, что этот метод сначала создает экземпляр рендеринга.Watcher
, в его callback-функции будет вызыватьсяupdateComponent
метод, который вызывается в этом методеvm._render()
Метод сначала создает виртуальный узел DOM и, наконец, вызываетvm._update
Обновите ДОМ.
Установите, когда он будет окончательно признан корневым узломvm._isMounted
дляtrue
, Указывает, что этот экземпляр смонтирован и выполняется одновременноmounted
функция крючка.vm.$vnode
Представляет родительский виртуальный узел экземпляра Vue, поэтому, если он имеет значение Null, это означает, что в настоящее время он является корневым экземпляром Vue.
Такvm._render()
Как создать виртуальные узлы DOM?
_render()
Визуализировать виртуальные узлы DOM
В Vue 2.0 рендеринг всех компонентов Vue в конечном итоге потребуетrender()
. Вью_render()
Это закрытый метод экземпляра, который используется для рендеринга экземпляра в виртуальный узел DOM. Его определениеsrc/core/instance/render.js
В файле код такой:
Vue.prototype._render = function (): VNode {
const vm: Component = this
const { render, _parentVnode } = vm.$options
...
let vnode
try {
currentRenderingInstance = vm
vnode = render.call(vm._renderProxy, vm.$createElement)
}
}
Приведенный выше код получает функцию рендеринга из $options экземпляра vue. пройти черезcall()
называется_renderProxy
а такжеcreateElement()
метод, давайте изучимcreateElement()
метод.
createElement()
createElement()
вinitRender()
середина. следующим образом:
// 该函数是在 _init() 过程中执行 initRender()
// 见 './init.js' 文件中的 initRender(vm) 传入vm。就执行到下面的方法。
export function initRender (vm: Component) {
// 被编译后生成的render函数
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
// 手写render函数 创建 vnode 的方法。
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
}
initRender()
выполняется во время _initinitRender()
Видеть./init.js
в файлеinitRender(vm)
Пройти в вм.
В фактической разработке проекта vue пример рукописной функции рендеринга выглядит следующим образом:
new Vue({
render(createElement){
return createElement('div',{
style:{color:'red'}
},this.msg)
},
data(){
return{
msg:"hello world"
}
}
}).$mount('#app')
Поскольку написанная вручную функция рендеринга экономит процесс компиляции шаблона в функцию рендеринга, производительность выше.
см. далее_renderProxy
метод:
_renderProxy
_renderProxy
метод, который также выполняется в процессе инициализации. см. документацию./init.js
, код выглядит следующим образом:
import { initProxy } from './proxy'
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
Если текущая среда является рабочей средой, назначьте виртуальную машину непосредственноvm._renderProxy
;
Если текущая среда является средой разработки, выполнитеinitProxy()
.
Функция находится в./proxy.js
В файле код такой:
initProxy = function initProxy (vm) {
// 判断浏览器是否支持 proxy 。
if (hasProxy) {
// determine which proxy handler to use
const options = vm.$options
const handlers = options.render && options.render._withStripped
? getHandler
: hasHandler
vm._renderProxy = new Proxy(vm, handlers)
} else {
vm._renderProxy = vm
}
}
Сначала определите, поддерживает ли браузерproxy
. Это новое дополнение к ES6. Оно используется для настройки уровня «перехвата» перед целевым объектом. Доступ к объекту из внешнего мира должен сначала пройти этот уровень перехвата. Поэтому он предоставляет механизм фильтрации и перехватить доступ к внешнему миру.
Если браузер не поддерживаетproxy
, затем назначьте vm непосредственноvm._renderProxy
;
если браузер это поддерживаетproxy
, затем выполнитеnew Proxy()
.
В итоге:vm._render
выполняетсяcreateElement
метод и возвращает виртуальный узел DOM. Так что же такое виртуальный DOM? ? ?
виртуальный DOM
Прежде чем исследовать виртуальный DOM vue, я рекомендуюВиртуальная библиотека DOM с открытым исходным кодом. Если у вас есть время, заинтересованные друзья могут пойти и узнать больше об этом. Это функция для представления уровня представления приложения. view.js реализует виртуальный DOM, используя его. Это значительно повышает производительность программы. Далее давайте посмотрим, как это делает vue.js.
vnode определяется вsrc/core/vdom/vnode.js
файл следующим образом:
export default class VNode {
tag: string | void;
data: VNodeData | void;
children: ?Array<VNode>;
text: string | void;
elm: Node | void;
...
}
Виртуальный DOM — это объект js, который представляет собой абстрактное описание реального DOM, такое как имя тега, данные, имя дочернего узла и т. д. Поскольку виртуальный DOM используется только для отображения реального DOM, он не содержит методов для управления DOM. Поэтому он легче и проще. Поскольку виртуальный DOM создаетсяcreateElement
метод, как эта часть достигается? ? ?
createElement
Эксплойт Vue.jscreateElement
Метод создает узел DOM, который определен вsrc/core/vdom/create-elemenet.js
В файле код такой:
export function createElement (
context: Component, // vm 实例
tag: any, // 标签
data: any, // 数据
children: any,// 子节点 可以构造DOM 树
normalizationType: any,
alwaysNormalize: boolean
): VNode | Array<VNode> {
// 对参数不一致的处理
if (Array.isArray(data) || isPrimitive(data)) {
normalizationType = children
children = data
data = undefined
}
if (isTrue(alwaysNormalize)) {
normalizationType = ALWAYS_NORMALIZE
}
// 处理好参数,则调用 _createElement() 去真正的创建节点。
return _createElement(context, tag, data, children, normalizationType)
}
createElement
метод правильный_createElement
Инкапсуляция метода, позволяющая сделать входящие параметры более гибкими.После обработки этих параметров вызывается функция, которая собственно и создает DOM-узел_createElement
, код показан ниже:
export function _createElement (
context: Component,
tag?: string | Class<Component> | Function | Object,
data?: VNodeData,
children?: any,
normalizationType?: number
): VNode | Array<VNode> {
...
if (normalizationType === ALWAYS_NORMALIZE) {
children = normalizeChildren(children)
} else if (normalizationType === SIMPLE_NORMALIZE) {
children = simpleNormalizeChildren(children)
}
...
}
_createElement
Метод предоставляет 5 следующих параметров:
-
context
Представляет контекст узла DOM, который имеет тип Component; -
tag
Представляет метку, которая может быть строкой или компонентом; -
data
Представляет данные на узле DOM, это тип VNodeData, который можно использовать вflow/vnode.js
найти его определение в ; -
children
Представляет дочерний узел текущего узла DOM любого типа, который затем необходимо нормализовать как стандартный массив VNode; -
normalizationType
Указывает тип спецификации дочернего узла, а методы для разных типов различаются. В основном это относится к тому, является ли функция рендеринга скомпилированной или написанной от руки.
Процесс функции createElement немного подробнее, в этой статье речь пойдет о нормализации дочерних элементов и создании VNode.
нормализация детей
Виртуальный DOM (Virtual DOM) на самом деле представляет собой древовидную структуру, каждый узел DOM может иметь несколько дочерних узлов, и эти дочерние узлы также должны быть типа VNode.
_createElement
4-й параметр полученchildren
являются произвольными типами, поэтому нам нужно нормализовать их как типы VNode.
это основано наnormalizationType
отличается, звонитnormalizeChildren(children)
а такжеsimpleNormalizeChildren(children)
методы, они определены вsrc/core/vdom/helpers/normalzie-children.js
В файле код такой:
// render 函数是编译生成的时候调用
// 拍平数组为一维数组
export function simpleNormalizeChildren (children: any) {
for (let i = 0; i < children.length; i++) {
if (Array.isArray(children[i])) {
return Array.prototype.concat.apply([], children)
}
}
return children
}
// 返回一维数组
export function normalizeChildren (children: any): ?Array<VNode> {
return isPrimitive(children)
? [createTextVNode(children)]
: Array.isArray(children)
? normalizeArrayChildren(children)
: undefined
}
simpleNormalizeChildren
Метод Вызов функции рендеринга сцены генерируется компилятором. Однако, когда дочерний узел является компонентом, когда функциональный компонент возвращает массив вместо корня, он пройдетArray.prototype.concat
Методchildren
Массив выравнивается так, что его глубина составляет всего один уровень.
normalizeChildren
Существует два типа сценариев вызова метода, один сценарий представляет собой написанную от руки функцию рендеринга, когдаchildren
Когда есть только один узел, Vue.js позволяет пользователям размещатьchildren
Написан как примитивный тип для создания одного простого текстового узла, который будет вызыватьcreateTextVNode
Создает узел DOM для текстового узла; другой сценарий — при компиляцииslot
,v-for
Когда ситуация будет иметь вложенные массивы, вызовыnormalizeArrayChildren
метод, код выглядит следующим образом:
function normalizeArrayChildren (children: any, nestedIndex?: string): Array<VNode> {
const res = []
let i, c, lastIndex, last
for (i = 0; i < children.length; i++) {
c = children[i]
if (isUndef(c) || typeof c === 'boolean') continue
lastIndex = res.length - 1
last = res[lastIndex]
// nested
if (Array.isArray(c)) {
if (c.length > 0) {
c = normalizeArrayChildren(c, `${nestedIndex || ''}_${i}`)
// merge adjacent text nodes
if (isTextNode(c[0]) && isTextNode(last)) {
res[lastIndex] = createTextVNode(last.text + (c[0]: any).text)
c.shift()
}
res.push.apply(res, c)
}
} else if (isPrimitive(c)) {
if (isTextNode(last)) {
res[lastIndex] = createTextVNode(last.text + c)
} else if (c !== '') {
res.push(createTextVNode(c))
}
} else {
// 如果两个节点都为文本节点,则合并他们。
if (isTextNode(c) && isTextNode(last)) {
res[lastIndex] = createTextVNode(last.text + c.text)
} else {
if (isTrue(children._isVList) &&
isDef(c.tag) &&
isUndef(c.key) &&
isDef(nestedIndex)) {
c.key = `__vlist${nestedIndex}_${i}__`
}
res.push(c)
}
}
}
return res
}
normalizeArrayChildren
Принимает 2 аргумента.
-
children
Указывает дочерний узел, который необходимо нормализовать; -
nestedIndex
представляет вложенный индекс; потому что синглchild
Может быть типом массива.normalizeArrayChildren
в основном траверсchildren
, получить один узелc
, тогда правильноc
Тип суждения, если это тип массива, то рекурсивно вызыватьnormalizeArrayChildren
; если это базовый тип, передатьcreateTextVNode
метод преобразуется в тип VNode, иначе это уже тип VNode, еслиchildren
является списком, и список вложен, то в соответствии сnestedIndex
обновить егоkey
.
В процессе обхода три случая обрабатываются следующим образом: если есть два последовательныхtext
узлы, объединит их в одинtext
узел.
В этот момент дети стали массивом типа VNode. Это нормализация детей.
Создание виртуальных узлов DOM
назадcreateElement
функция, нормализованнаяchildren
После этого следующим шагом является создание экземпляра DOM, код выглядит следующим образом:
let vnode, ns
if (typeof tag === 'string') {
let Ctor
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
if (config.isReservedTag(tag)) {
// platform built-in elements
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
)
} else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
// component
vnode = createComponent(Ctor, data, context, children, tag)
} else {
// 不认识的节点的处理
vnode = new VNode(
tag, data, children,
undefined, undefined, context
)
}
} else {
// direct component options / constructor
vnode = createComponent(tag, data, context, children)
}
Прямо здесьtag
судить, еслиstring
тип, затем рассудите, что если это какой-то встроенный узел, создайте общий VNode напрямую, если это зарегистрированное имя компонента, то передайтеcreateComponent
Создайте VNode типа component, в противном случае создайте VNode с неизвестной меткой. еслиtag
ЯвляетсяComponent
типа, звоните напрямуюcreateComponent
Создайте узел VNode типа Component.
В этот момент,createElement
Метод создает экземпляр виртуального дерева DOM, который используется для описания реального дерева DOM, так как же отобразить его как реальное дерево DOM? ? ? На самом деле этоvm._update
Завершенный.
update отображает виртуальный DOM в реальный DOM
_update
Метод заключается в том, как преобразовать виртуальный DOM в реальный DOM. Эта часть кода находится вsrc/core/instance/lifecycle.js
В файле код такой:
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
const vm: Component = this
const prevEl = vm.$el
const prevVnode = vm._vnode
const restoreActiveInstance = setActiveInstance(vm)
vm._vnode = vnode
if (!prevVnode) {
// 数据的首次渲染时候执行
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
}
...
}
Читая код, мы видим, что при первом отображении данных вызовvm.__patch__()
Метод, он получил четыре параметра, в сочетании с процессом разработки нашего фактического проекта vue.vm.$el
Это объект DOM с идентификатором app, то есть:<div id="app"></div>
;vnode
Соответствует возвращаемому значению вызова функции рендеринга;hydrating
В случае несерверного рендеринга этоfalse
,removeOnly
является ложным.
vm.__patch__
Определение метода отличается на разных платформах.Определение веб-платформы находится вsrc/platforms/web/runtime/index.js
, код выглядит следующим образом:
// 是否在浏览器环境
Vue.prototype.__patch__ = inBrowser ? patch : noop
На веб-платформе рендеринг на стороне сервера также влияет на этот метод. Поскольку при рендеринге на стороне сервера нет реальной среды DOM браузера, поэтому нет необходимости преобразовывать VNode в DOM, поэтому это пустая функция, а при рендеринге на стороне браузера она указывает на метод patch, который определен вsrc/platforms/web/runtime/patch.js
В файле код такой:
import * as nodeOps from 'web/runtime/node-ops'
import { createPatchFunction } from 'core/vdom/patch'
import baseModules from 'core/vdom/modules/index'
import platformModules from 'web/runtime/modules/index'
const modules = platformModules.concat(baseModules)
export const patch: Function = createPatchFunction({ nodeOps, modules })
Прочтите код, чтобы узнатьcreatePatchFunction
Возвращаемое значение метода передается в объект, где
-
nodeOps
Инкапсулирует ряд методов манипулирования DOM; -
modules
Определяет реализацию функции ловушки модуля;createPatchFunction
Метод определен вsrc/core/vdom/patch.js
В файле код такой:
const hooks = ['create', 'activate', 'update', 'remove', 'destroy']
export function createPatchFunction (backend) {
let i, j
const cbs = {}
const { modules, nodeOps } = backend
for (i = 0; i < hooks.length; ++i) {
cbs[hooks[i]] = []
for (j = 0; j < modules.length; ++j) {
if (isDef(modules[j][hooks[i]])) {
cbs[hooks[i]].push(modules[j][hooks[i]])
}
}
}
// ...
// 定义了一些辅助函数
// 当调用 vm.__dispatch__时,其实就是调用下面的 patch 方法
// 这块应用了函数柯理化的技巧
return function patch (oldVnode, vnode, hydrating, removeOnly) {
// ...
return vnode.elm
}
}
createPatchFunction
Ряд вспомогательных методов определен внутри и, наконец, возвращаетpatch
метод, этот метод назначаетсяvm._update
Вызов в функцииvm.__patch__
. Другими словамиvm.__dispatch__
, на самом деле звонитpatch (oldVnode, vnode, hydrating, removeOnly)
метод, этот блок на самом деле является применением метода курирования функций.
patch
Метод получает 4 параметра, а именно:
-
oldVnode
Представляет старый узел VNode, который также может не существовать или быть объектом DOM; -
vnode
Представляет узел VNode, возвращенный после выполнения _render; -
hydrating
Указывает, является ли это рендеринг сервера; -
removeOnly
Это для переходной группы.
анализироватьpatch
метод, потому что входящийoldVnode
на самом деле является DOM-контейнером, поэтомуisRealElement
верно, то звонитеemptyNodeAt
метод положитьoldVnode
Преобразуется в виртуальный узел DOM (объект js), а затем вызываетсяcreateElm
метод. код показывает, как показано ниже:
if (isRealElement) {
oldVnode = emptyNodeAt(oldVnode)
}
function createElm (
vnode,
insertedVnodeQueue,
parentElm,
refElm,
nested,
ownerArray,
index
) {
if (isDef(vnode.elm) && isDef(ownerArray)) {
vnode = ownerArray[index] = cloneVNode(vnode)
}
vnode.isRootInsert = !nested // for transition enter check
const data = vnode.data
const children = vnode.children
const tag = vnode.tag
// 接下来判断 vnode 是否包含 tag,
// 如果包含,先对tag的合法性在非生产环境下做校验,看是否是一个合法标签;
// 然后再去调用平台 DOM 的操作去创建一个占位符元素。
if (isDef(tag)) {
if (process.env.NODE_ENV !== 'production') {
if (data && data.pre) {
creatingElmInVPre++
}
if (isUnknownElement(vnode, creatingElmInVPre)) {
warn(
'Unknown custom element: <' + tag + '> - did you ' +
'register the component correctly? For recursive components, ' +
'make sure to provide the "name" option.',
vnode.context
)
}
}
// 调用 createChildren 方法去创建子元素:
vnode.elm = vnode.ns
? nodeOps.createElementNS(vnode.ns, tag)
: nodeOps.createElement(tag, vnode)
setScope(vnode)
/* istanbul ignore if */
if (__WEEX__) {
// ...
} else {
// 调用 createChildren 方法去创建子元素
// 用 createChildren 方法遍历子虚拟节点,递归调用 createElm
// 在遍历过程中会把 vnode.elm 作为父容器的 DOM 节点占位符传入。
createChildren(vnode, children, insertedVnodeQueue)
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue)
}
insert(parentElm, vnode.elm, refElm)
}
if (process.env.NODE_ENV !== 'production' && data && data.pre) {
creatingElmInVPre--
}
} else if (isTrue(vnode.isComment)) {
vnode.elm = nodeOps.createComment(vnode.text)
insert(parentElm, vnode.elm, refElm)
} else {
vnode.elm = nodeOps.createTextNode(vnode.text)
insert(parentElm, vnode.elm, refElm)
}
}
createElm
Цель метода — создать настоящий DOM из виртуального узла и вставить его в родительский узел. Определите, содержит ли vnode тег. Если да, сначала проверьте действительность тега в непроизводственной среде, чтобы убедиться, что это допустимый тег, а затем вызовите операцию DOM платформы, чтобы создать элемент-заполнитель. тогда позвониcreateChildren
способ создания дочерних элементов,createChildren
Код метода следующий:
createChildren(vnode, children, insertedVnodeQueue)
function createChildren (vnode, children, insertedVnodeQueue) {
if (Array.isArray(children)) {
if (process.env.NODE_ENV !== 'production') {
checkDuplicateKeys(children)
}
for (let i = 0; i < children.length; ++i) {
createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i)
}
} else if (isPrimitive(vnode.text)) {
nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(String(vnode.text)))
}
}
createChildren
Метод обходит дочерние виртуальные узлы и рекурсивно вызываетcreateElm
, в процессе обхода vnode.elm будет передан в качестве заполнителя узла DOM родительского контейнера. тогда позвониinvokeCreateHooks
метод выполняет все хуки создания и помещает vnode вinsertedVnodeQueue
середина. последний звонокinsert
Метод вставляет DOM в родительский узел, так как он вызывается рекурсивно, дочерний элемент будет вызываться первымinsert
, поэтому порядок вставки всего узла дерева vnode сначала дочерний, а затем родительский.insert
метод определен вsrc/core/vdom/patch.js
В файле код такой:
insert(parentElm, vnode.elm, refElm)
function insert (parent, elm, ref) {
if (isDef(parent)) {
if (isDef(ref)) {
if (ref.parentNode === parent) {
nodeOps.insertBefore(parent, elm, ref)
}
} else {
nodeOps.appendChild(parent, elm)
}
}
}
Прочтите код, чтобы увидеть,insert
Метод вызывает некоторые вспомогательные методы для вставки дочернего узла в родительский узел (фактически это вызов API нативного DOM для работы с DOM), эти вспомогательные методы определены вsrc/platforms/web/runtime/node-ops.js
в файле. На этом этапе узел DOM, динамически созданный Vue, завершен. эмм~~ Оглядываясь назад на эту картинку.
конец
Недавно я серьезно посмотрю на исходный код vue.js. [Читать исходный код vue] будет обновляться в соответствии с серией. Делясь собственными знаниями, я также надеюсь общаться с большим количеством сверстников, вот и все.
первый раз:[Чтение исходного кода vue] Что делает импорт Vue из 'vue'?
Вторая статья:[Читать исходный код vue] Узнайте, как шаблоны и данные отображаются в DOM?【В настоящее время читаю】