прелюдия
Прежде чем понять функцию рендеринга vue, вам необходимо понять общий процесс Vue (как показано выше).
Из приведенного выше рисунка вы должны понять, как работает компонент Vue.
- Шаблоны компилируются для создания деревьев AST.
- Дерево AST генерирует функцию рендеринга Vue.
- Функция рендеринга объединяет данные для создания дерева vNode (Virtual DOM Node).
- Diff и Patch создали новый интерфейс пользовательского интерфейса после создания (рендеринг реального DOM)
На этой диаграмме нам нужно понять следующие понятия:
- Шаблон, шаблон Vue — это чистый HTML, основанный на синтаксисе шаблона Vue, который может легко обрабатывать взаимосвязь между данными и интерфейсом пользовательского интерфейса.
- AST, сокращение от Abstract Syntax Tree, Vue анализирует шаблоны HTML в AST и выполняет некоторую оптимизированную обработку разметки в AST для извлечения самого большого статического дерева, так что Virtual DOM напрямую пропускает следующие Diff
- функция рендеринга, функция рендеринга используется для создания виртуального DOM. Vue рекомендует использовать шаблоны для создания нашего приложения, в базовой реализации Vue в конечном итоге скомпилирует шаблоны в функции рендеринга. Поэтому, если мы хотим получить лучший контроль, вы можете написать функция рендеринга напрямую.(фокус)
- Виртуальный DOM, виртуальный DOM
- Наблюдатель, каждый компонент Vue имеет соответствующий
watcher, он будет в компонентеrenderСоберите данные, от которых зависит компонент, и, когда зависимость будет обновлена, запустите компонент для повторного рендеринга, Vue автоматически оптимизирует и обновит DOM, который необходимо обновить.
На картинке вышеrenderВ качестве разделительной линии можно использовать функцию:
-
renderЛевую часть функции можно назватьвремя компиляции, преобразовать плату Vue в функцию рендеринга -
renderСправа от функции находится среда выполнения Vue, которая в основном генерирует функцию рендеринга в дерево Virtual DOM, а также Diff и Patch.
render
Vue рекомендует использовать шаблоны для создания вашего HTML в подавляющем большинстве случаев. Однако в некоторых сценариях вам действительно нужны все возможности программирования JavaScript. Затем вы можете использовать функции рендеринга, которые ближе к компилятору, чем шаблоны.
Пример отрисовки заголовка
Например, один из официальных сайтовПример отрисовки заголовка
Актуальную реализацию можно посмотреть, здесь подробно описываться не будет.Вот код реализации шаблона и реализация функции рендеринга:
Реализация одного файла .vue
<template>
<h1 v-if="level === 1">
<slot></slot>
</h1>
<h2 v-else-if="level === 2">
<slot></slot>
</h2>
<h3 v-else-if="level === 3">
<slot></slot>
</h3>
<h4 v-else-if="level === 4">
<slot></slot>
</h4>
<h5 v-else-if="level === 5">
<slot></slot>
</h5>
<h6 v-else-if="level === 6">
<slot></slot>
</h6>
</template>
<script>
export default {
name: 'anchored-heading',
props: {
level: {
type: Number,
required: true
}
}
}
</script>
Реализация функции рендеринга
Vue.component('anchored-heading', {
render: function (createElement) {
return createElement(
'h' + this.level, // tag name 标签名称
this.$slots.default // 子组件中的阵列
)
},
props: {
level: {
type: Number,
required: true
}
}
})
Это более лаконично?
Node & tree & Virtual DOM
После определенного понимания основных концепций и функций рендеринга Vue нам также необходимо понять некоторые принципы работы браузера.renderФункции важны, например, следующий HTML-код:
<div>
<h1>My title</h1>
Some text content
<!-- TODO: Add tagline -->
</div>
Когда браузер считывает эти коды, он создаетДерево узлов DOMЧтобы отслеживать, если бы вы рисовали генеалогическое древо для отслеживания развития членов семьи, дерево узлов HTML DOM могло бы выглядеть так:
каждыйэлемента такжеСловоявляется узлом, даже комментарий является узлом. Узел является частью страницы, как и в генеалогическом дереве, у каждого узла могут быть дочерние элементы.
Эффективное обновление всех узлов может быть трудным, но не волнуйтесь, Vue сделает это за вас автоматически, вам просто нужно сообщить Vue, что такое HTML на странице?
Может быть шаблоном HTML, например:
<h1>{{title}}</h1>
Также может быть функцией рендеринга:
render(h){
return h('h1', this.title)
}
В обоих случаях, еслиtitleПри изменении значения Vue будет автоматически обновлять страницу.
виртуальный DOM
После того, как компилятор Vue скомпилирует шаблоны, он скомпилирует эти шаблоны в функцию рендеринга (render), а при вызове функции рендеринга (render) вернет виртуальное DOM-дерево.
Когда мы получим виртуальное DOM-дерево, то перенесем его вФункция патча, он будет отвечать за преобразование этих виртуальных DOM в реальные DOM. Во время этого процесса собственная отзывчивая система Vue будет определять источники данных, на которые она полагается в процессе рендеринга. Точно воспринимать изменения в источнике данных, чтобы повторно отображать, когда После повторного рендеринга будет сгенерировано новое дерево, и, сравнив новое дерево со старым деревом, вы можете получить окончательную необходимость изменить реальную DOM Точку изменения и, наконец, реализовать изменение с помощью функции Patch. .
Короче говоря, в базовой реализации Vue Vue компилирует шаблоны в виртуальные функции рендеринга DOM. В сочетании с собственной системой ответов Vue Vue может интеллектуально рассчитать минимальную стоимость повторного рендеринга компонентов и применить их к операциям DOM при изменении состояния.
Vue поддерживает нас черезdataПараметр передает объект JavaScript в качестве данных компонента, Vue будет проходить через свойство объекта данных, использоватьObject.definePropertyметод устанавливает объект описания, черезgett/setterфункция для перехвата чтения и модификации этого свойства.
Vue создает слойWatcherслой, который записывает свойства как зависимости во время рендеринга компонента, когда зависимостиsetterПри вызове уведомитWatcherПересчитайте, чтобы его связанные компоненты были обновлены.
Для виртуального DOM, если вы хотите узнать о нем больше, вы можете прочитатьVue принцип анализа виртуального DOM
В ходе предыдущего исследования мы изначально узнали, что Vue отслеживает изменения в реальном DOM, создавая ** виртуальный DOM». Например.
return createElement('h1', this.title)
createElement, которыйcreateNodeDescription, возвращает виртуальный узел (Virtual Node), обычно сокращенно «VNode». Virtual DOM — это общий термин для всего дерева VNode, установленного деревом компонентов Vue.
Все дерево VNode, установленное деревом компонентов Vue, уникально и не может повторяться.Например, следующая функция рендеринга недействительна.
render(createElement) {
const vP = createElement('p', 'hello james')
return createElement('div', [
// error, 有重复的vNode
vP, vP
])
}
Если вам нужно много повторяющихся компонентов/элементов, вы можете использовать фабричные функции, например:
render(createElement){
return createElement('div', Array.apply(null, {length: 20}).map(() => {
return createElement('p', 'hi james')
}))
}
Механизм рендеринга Vue
На следующем рисунке показана блок-схема рендеринга компонента при независимом построении:
Будут задействованы две концепции Vue:
- Автономная сборка, включая компилятор шаблонов, процесс рендеринга: строка HTML => функция рендеринга => vNode => реальный DOM
- Сборка среды выполнения, не включает компилятор шаблонов, процесс рендеринга: функция рендеринга => vNode => реальный DOM
Пакеты, созданные во время выполнения, будут иметь на один компилятор шаблонов меньше, чем автономные сборки (и, следовательно, будут работать быстрее).$mountфункционально разные и$mountЭтот метод является отправной точкой всего процесса рендеринга, который иллюстрируется следующей блок-схемой:
Как видно из изображения выше, в процессе рендеринга предоставляется три шаблона:
- пользовательская функция рендеринга
- template
- el
Оба могут отображать страницу, что соответствует трем методам написания, когда мы используем Vue.
Эти три режима в конечном итоге должны получитьrenderфункция.
Для обычной разработки использование template и el будет более дружелюбным и простым для понимания, но менее гибким. Функция рендеринга способна на более сложную логику и обладает высокой гибкостью, но ее относительно плохо понимают пользователи.
пользовательская функция рендеринга
Vue.component('anchored-heading', {
render(createElement) {
return createElement (
'h' + this.level,
this.$slots.default
)
},
props: {
level: {
type: Number,
required: true
}
}
})
написание шаблона
const app = new Vue({
template: `<div>{{ msg }}</div>`,
data () {
return {
msg: 'Hello Vue.js!'
}
}
})
эль правописание
let app = new Vue({
el: '#app',
data () {
return {
msg: 'Hello Vue!'
}
}
})
Понимание и использование функции рендеринга
createElement
В использованииrenderфункция,createElementявляется обязательным для освоения.
параметр createElement
createElementМожет принимать несколько параметров
1-й параметр:{String | Object | Function }, должно пройти
Первый параметр является обязательным параметром, который может быть строкой.String, так же может бытьObjectобъект или функцияFunction
// String
Vue.component('custom-element', {
render(createElement) {
return createElement('div', 'hello world!')
}
})
// Object
Vue.component('custom-element', {
render(createElement) {
return createElement({
template: `<div>hello world!</div>`
})
}
})
// Function
Vue.component('custom-element', {
render(createElement) {
const elFn = () => { template: `<div>hello world!</div>` }
return createElement(elFn())
}
})
Приведенный выше код эквивалентен:
<template>
<div>hello world!</>
</template>
<script>
export default {
name: 'custom-element'
}
</script>
2-й параметр:{ Object }, необязательный
createElemenВторой параметр является необязательным параметром, этот параметр является объектом, например:
Vue.component('custom-element', {
render(createElement) {
const self = this;
return createElement('div', {
'class': {
foo: true,
bar: false
},
style: {
color: 'red',
fontSize: '18px'
},
attrs: {
...self.attrs,
id: 'id-demo'
},
on: {
...self.$listeners,
click: (e) => {console.log(e)}
},
domProps: {
innerHTML: 'hello world!'
},
staticClass: 'wrapper'
})
}
})
Эквивалентно:
<template>
<div :id="id" class="wrapper" :class="{'foo': true, 'bar': false}" :style="{color: 'red', fontSize: '18px'}" v-bind="$attrs" v-on="$listeners" @click="(e) => console.log(e)"> hello world! </div>
</template>
<script>
export default {
name: 'custom-element',
data(){
return {
id: 'id-demo'
}
}
}
</script>
<style>
.wrapper{
display: block;
width: 100%;
}
</style>
3-й параметр:{ String | Array }, необязательный
createElementТретий параметр является необязательным, вы можете передать егоStringилиArray, например:
Vue.component('custom-element', {
render (createElement) {
var self = this
return createElement(
'div',
{
class: {
title: true
},
style: {
border: '1px solid',
padding: '10px'
}
},
[
createElement('h1', 'Hello Vue!'),
createElement('p', 'Hello world!')
]
)
}
})
Эквивалентно:
<template>
<div :class="{'title': true}" :style="{border: '1px solid', padding: '10px'}">
<h1>Hello Vue!</h1>
<p>Hello world!</p>
</div>
</template>
<script>
export default {
name: 'custom-element',
data(){
return {
id: 'id-demo'
}
}
}
</script>
Используйте шаблон и визуализацию для создания компонентов с одинаковым эффектом
шаблонный метод
<template>
<div id="wrapper" :class="{show: show}" @click="clickHandler">
Hello Vue!
</div>
</template>
<script>
export default {
name: 'custom-element',
data(){
return {
show: true
}
},
methods: {
clickHandler(){
console.log('you had click me!');
}
}
}
</script>
метод визуализации
Vue.component('custom-element', {
data () {
return {
show: true
}
},
methods: {
clickHandler: function(){
console.log('you had click me!');
}
},
render: function (createElement) {
return createElement('div', {
class: {
show: this.show
},
attrs: {
id: 'wrapper'
},
on: {
click: this.handleClick
}
}, 'Hello Vue!')
}
})
процесс парсинга createElement
Блок-схема синтаксического анализа createElement (выдержка из:сегмент fault.com/ah/119000000…)
createElementИсходный код ядра процесса парсинга (требует определенных знаний JS, выдержки из:сегмент fault.com/ah/119000000…)
const SIMPLE_NORMALIZE = 1
const ALWAYS_NORMALIZE = 2
function createElement (context, tag, data, children, normalizationType, alwaysNormalize) {
// 兼容不传data的情况
if (Array.isArray(data) || isPrimitive(data)) {
normalizationType = children
children = data
data = undefined
}
// 如果alwaysNormalize是true
// 那么normalizationType应该设置为常量ALWAYS_NORMALIZE的值
if (alwaysNormalize) normalizationType = ALWAYS_NORMALIZE
// 调用_createElement创建虚拟节点
return _createElement(context, tag, data, children, normalizationType)
}
function _createElement (context, tag, data, children, normalizationType) {
/**
* 如果存在data.__ob__,说明data是被Observer观察的数据
* 不能用作虚拟节点的data
* 需要抛出警告,并返回一个空节点
*
* 被监控的data不能被用作vnode渲染的数据的原因是:
* data在vnode渲染过程中可能会被改变,这样会触发监控,导致不符合预期的操作
*/
if (data && data.__ob__) {
process.env.NODE_ENV !== 'production' && warn(
`Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +
'Always create fresh vnode data objects in each render!',
context
)
return createEmptyVNode()
}
// 当组件的is属性被设置为一个falsy的值
// Vue将不会知道要把这个组件渲染成什么
// 所以渲染一个空节点
if (!tag) {
return createEmptyVNode()
}
// 作用域插槽
if (Array.isArray(children) && typeof children[0] === 'function') {
data = data || {}
data.scopedSlots = { default: children[0] }
children.length = 0
}
// 根据normalizationType的值,选择不同的处理方法
if (normalizationType === ALWAYS_NORMALIZE) {
children = normalizeChildren(children)
} else if (normalizationType === SIMPLE_NORMALIZE) {
children = simpleNormalizeChildren(children)
}
let vnode, ns
// 如果标签名是字符串类型
if (typeof tag === 'string') {
let Ctor
// 获取标签名的命名空间
ns = config.getTagNamespace(tag)
// 判断是否为保留标签
if (config.isReservedTag(tag)) {
// 如果是保留标签,就创建一个这样的vnode
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
)
// 如果不是保留标签,那么我们将尝试从vm的components上查找是否有这个标签的定义
} else if ((Ctor = resolveAsset(context.$options, 'components', tag))) {
// 如果找到了这个标签的定义,就以此创建虚拟组件节点
vnode = createComponent(Ctor, data, context, children, tag)
} else {
// 兜底方案,正常创建一个vnode
vnode = new VNode(
tag, data, children,
undefined, undefined, context
)
}
// 当tag不是字符串的时候,我们认为tag是组件的构造类
// 所以直接创建
} else {
vnode = createComponent(tag, data, context, children)
}
// 如果有vnode
if (vnode) {
// 如果有namespace,就应用下namespace,然后返回vnode
if (ns) applyNS(vnode, ns)
return vnode
// 否则,返回一个空节点
} else {
return createEmptyVNode()
}
}
}
Используйте функцию рендеринга вместо функции шаблона
При использовании шаблона Vue мы можем гибко использовать его в шаблоне.v-if,v-for,v-modelа также<slot>等模板语法。 но вrenderВ функции нет специального API. Если вы используете их в рендеринге, вам нужно использовать собственный JavaScript для этого.
v-if & v-for
<ul v-if="items.length">
<li v-for="item in items">{{ item }}</li>
</ul>
<p v-else>No items found.</p>
реализация функции рендеринга
Vue.component('item-list',{
props: ['items'],
render (createElement) {
if (this.items.length) {
return createElement('ul', this.items.map((item) => {
return createElement('item')
}))
} else {
return createElement('p', 'No items found.')
}
}
})
v-model
<template>
<el-input :name="name" @input="val => name = val"></el-input>
</template>
<script>
export default {
name: 'app',
data(){
return {
name: 'hello vue.js'
}
}
}
</script>
реализация функции рендеринга
Vue.component('app', {
data(){
return {
name: 'hello vue.js'
}
},
render: function (createElement) {
var self = this
return createElement('el-input', {
domProps: {
value: self.name
},
on: {
input: function (event) {
self.$emit('input', event.target.value)
}
}
})
},
props: {
name: String
}
})
slot
В Vue это можно сделать с помощью:
-
this.$slotsПолучите статическое содержимое в списке VNodes.
render(h){
return h('div', this.$slots.default)
}
Эквивалентно:
<template>
<div>
<slot> </slot>
</div>
</template>
В Vue это можно сделать с помощью:
-
this.$scopedSlotsПолучите слот с ограниченной областью действия, который можно использовать в качестве функции, возвращающей VNodes.
props: ['message'],
render (createElement) {
// `<div><slot :text="message"></slot></div>`
return createElement('div', [
this.$scopedSlots.default({
text: this.message
})
])
}
Чтобы передать слоты с областью действия дочерним компонентам с помощью функций рендеринга, используйте поле scopedSlots в данных VNode:
<div id="app">
<custom-ele></custom-ele>
</div>
Vue.component('custom-ele', {
render: function (createElement) {
return createElement('div', [
createElement('child', {
scopedSlots: {
default: function (props) {
return [
createElement('span', 'From Parent Component'),
createElement('span', props.text)
]
}
}
})
])
}
})
Vue.component('child', {
render: function (createElement) {
return createElement('strong', this.$scopedSlots.default({
text: 'This is Child Component'
}))
}
})
let app = new Vue({
el: '#app'
}
JSX
Если вы привыкли писатьtemplate, затем используйтеrenderфункция писать, вы обязательно почувствуетеочень неудобно, особенно при работе со сложными компонентами. Но наше использование JSX во Vue возвращает нас к синтаксису, более похожему на шаблон.
import View from './View.vue'
new Vue({
el: '#demo',
render (h) {
return (
<View level={1}>
<span>Hello</span> world!
</View>
)
}
}
установить ч как
createElementПсевдоним является распространенным соглашением в экосистеме Vue и фактически требуется для JSX.Если h выходит за рамки области видимости, это вызовет ошибку в приложении.
Суммировать
В рендеринге Vue основными ключевыми шагами являются:
-
new Vue, выполнить инициализацию - устанавливать
$mount, путем настройкиrenderметод,template,elи т. д. для созданияrenderфункция рендеринга - пройти через
WatcherМониторинг изменений данных - Когда данные меняются,
renderВыполнение функции генерирует объект VNode - пройти через
patchметод, сравнивает старые и новые объекты VNode черезDOM DiffАлгоритмы добавления/изменения/удаления реальных элементов DOM
До сих пор весьnew VueПроцесс рендеринга завершен.