1. Введение
За это время я перечитал документацию vue и обнаружил, что есть еще много знаний, которые используются не так часто, или которые использовались просто, но не так ясно. Сегодня мы рассмотрим функцию рендеринга render, синтаксис jsx и использование слота slot.
2. Недостатки синтаксиса шаблонов
Учащиеся, знакомые с методом написания однофайловых компонентов vue, знают, что html-часть vue-файла состоит из<template></template>Composition, этот метод относительно прост в использовании и может удовлетворить потребности в большинстве случаев с инструкциями vue. Тем не менее, бывают случаи, когда синтаксис шаблона неудобен, например, необходимо разработать компонент, которому нужно выбрать отображаемый html-тег в соответствии со значением, переданным родительским компонентом. Рассмотрим пример:
// hLabel.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{
props: {
level:{
type: Number
}
}
}
</script>
Хотя вышеуказанный компонент может отображать соответствующий контент в соответствии со значением уровня<h1>,<h2>...теги, но есть также много избыточного кода, и по одному в теге заголовка каждого уровня<slot>Этикетка.
Чтобы решить эту проблему, нам нужно использовать функцию рендеринга в vue.render.
3. Рендер функции рендеринга
Давайте сначала посмотрим, как использовать функцию рендеринга для реализации компонентов, требуемых выше:
// hLabel.vue
<script>
export default {
props: {
level: {
type: Number,
}
},
render: function(createElement){
return createElement(
'h' + this.level, // 标签名称,根据父组件传入的level值确定
this.$slots.default // 子节点数组
)
}
}
</script>
Приведенный выше код очень лаконичен, и шаблон этикетки можно отобразить с помощью функции рендеринга. В то же время, если вам нужно передать оригинал<slot>Полученный контент, на этот раз для использования$slots.default, мы отдельно упомянем об использовании слота позже.
Vue предоставляет параметр createElement для функции рендеринга. Этот параметр также является методом функции. Он принимает определенные параметры и возвращает виртуальный DOM (Virtual Dom) VNode. В Vue мы обычно согласны с тем, что createElement можно сократить как h. Давайте посмотрим на использование createElement:
render: function(createElement){
// @returns {VNode}
createElement(
// {String | Object | Function}
// 一个 HTML 标签名、组件选项对象,或者
// resolve 了上述任何一种的一个 async 函数。必填项。
'div',
// {Object}
// 一个与模板中属性对应的数据对象。可选。
{
// 主要是html模板标签中的属性值的写法,下面单独介绍
},
// {String | Array}
// 子级虚拟节点 (VNodes),由 `createElement()` 构建而成,
// 也可以使用字符串来生成“文本虚拟节点”。可选。
[
'先写一些文字', // 如果是字符串,表示是生成标签中的内容
createElement('h1', '一则头条'), // createElement生成的新VNode
createElement(MyComponent, {
props: {
someProp: 'foobar'
}
})
]
)
}
Давайте посмотрим, как записываются атрибуты в шаблоне в функции createElement:
{
// 与 `v-bind:class` 的 API 相同,
// 接受一个字符串、对象或字符串和对象组成的数组
'class': {
foo: true,
bar: false
},
// 与 `v-bind:style` 的 API 相同,
// 接受一个字符串、对象,或对象组成的数组
style: {
color: 'red',
fontSize: '14px'
},
// 普通的 HTML 特性
attrs: {
id: 'foo'
},
// 组件 prop,这个属性是当createElement渲染的是一个组件时使用
props: {
myProp: 'bar'
},
// DOM 属性
domProps: {
innerHTML: 'baz'
},
// 事件监听器在 `on` 属性内,
// 但不再支持如 `v-on:keyup.enter` 这样的修饰器。
// 需要在处理函数中手动检查 keyCode。
on: {
click: this.clickHandler
},
// 仅用于组件,用于监听原生事件,而不是组件内部使用
// 组件内的原生事件触发时,使用`vm.$emit` 触发的事件。
nativeOn: {
click: this.nativeClickHandler
},
// 自定义指令。注意,你无法对 `binding` 中的 `oldValue`
// 赋值,因为 Vue 已经自动为你进行了同步。
directives: [
{
name: 'my-custom-directive',
value: '2',
expression: '1 + 1',
arg: 'foo',
modifiers: {
bar: true
}
}
],
// 作用域插槽的格式为
// { name: props => VNode | Array<VNode> }
scopedSlots: {
default: props => createElement('span', props.text)
},
// 如果组件是其它组件的子组件,需为插槽指定名称
slot: 'name-of-slot',
// 其它特殊顶层属性
key: 'myKey',
ref: 'myRef',
// 如果你在渲染函数中给多个元素都应用了相同的 ref 名,
// 那么 `$refs.myRef` 会变成一个数组。
refInFor: true
}
Из приведенного выше примера видно, что так же, как v-bind:class и v-bind:style специально обрабатываются в синтаксисе шаблона, они также имеют соответствующие поля верхнего уровня в объектах данных VNode. Этот объект также позволяет привязывать обычные свойства HTML, а также свойства DOM, такие как innerHTML (это переопределяет директиву v-html).
Я полагаю, что всем здесь покажется, что использование рендера слишком хлопотно, если нужно написать чуть более сложный html шаблон, то моя функция рендера будет написана насмерть, поэтому естественно приводит к использованию jsx.
4. JSX-синтаксис
Я считаю, что люди, которые написали React, определенно не будут незнакомы с этим синтаксисом. Благодаря поддержке плагина babel синтаксис jsx также можно использовать непосредственно в функции рендеринга vue. Если вы используете проект, созданный vue-cli 3.x, вам не нужна никакая конфигурация, просто используйте jsx напрямую.
// hLabel.vue
<script>
export default{
props: {
level: {
type: Number,
}
},
methods: {
clickHandler(){
},
nativeClickHandler(){
}
},
render:function(h) { // createElement约定可简写为h
let tag = `h${this.level}`
return (
<tag
key="key"
ref="ref"
id='title'
class={{'foo':true,, 'bar':false}}
style={{margin: '10px', color:'red'}}
onClick={this.clickHandler}
nativeOnClick={this.nativeClickHandler} // 监听组件内的原生事件
>{this.$slots.default}
</tag>
)
}
}
</script>
В приведенном выше примере приведен простой пример синтаксиса jsx и добавления атрибутов в теги. Однако, если мы используем функцию рендеринга, некоторые инструкции, поставляемые с Vue, больше не будут действовать, в том числеv-if,v-forа такжеv-model, Нам нужно реализовать себя.
5. Реализация инструкции vue в функции рендеринга
v-если и v-для:
<ul v-if="items.length">
<li v-for="item in items">{{ item.name }}</li>
</ul>
<p v-else>No items found.</p>
// 在渲染函数中需要使用 if/else 和 map 来重写
props: ['items'],
render: function (h) {
if (this.items.length) {
return h('ul', this.items.map(function (item) {
return h('li', item.name)
}))
} else {
return h('p', 'No items found.')
}
}
v-model:
props: ['value'],
render: function (createElement) {
var self = this
return createElement('input', {
domProps: {
value: self.value
},
on: {
input: function (event) {
self.$emit('input', event.target.value)
}
}
})
}
По сути, приведенный выше код — это принцип двухсторонней привязки инструкций v-model в vue, но v-model сделал совместимую обработку для разных элементов привязки. В то же время v-модель также может быть привязана к компоненту, конкретное использование можно щелкнутьПосмотреть здесь.
В то же время при привязке событий во Vue нельзя использовать модификаторы событий и клавиш, потому что эти модификаторы событий — синтаксический сахар, который Vue обрабатывает за нас. О том, как использовать модификатор события/ключа в функции рендеринга, относительно просто, вы можете перейти кофициальная документацияПроверить.
6. Функциональные компоненты
Если компонент, который нам нужен, проще, он не управляет никаким состоянием, не прослушивает переданное ему состояние и не имеет методов жизненного цикла. На самом деле это просто функция, которая принимает некоторые реквизиты. В таком сценарии мы можем пометить компонент как функциональный, что означает, что он не имеет состояния (нет реактивных данных) и не имеет экземпляра (нет этого контекста).
Функциональный компонент выглядит так:
<script>
export default{
functional: true, // 添加属性functional: true,表示该组件为函数式组件
// Props 是可选的
props: {
// ...
},
// 为了弥补缺少的实例
// 提供第二个参数作为上下文
render: function (createElement, context) {
// ...
},
}
</script>
В однофайловых компонентах версии 2.5.0 и выше функциональные компоненты на основе шаблонов могут быть объявлены следующим образом:
<template functional>
</template>
Поскольку функциональные компоненты не имеют состояния, этот контекст и такие атрибуты, как данные, отсутствуют, поэтому, если требуемые данные являются вторым параметром функции рендерингаcontextприобретенный:
- реквизит: объект, предоставляющий все реквизиты
- data: весь объект данных, переданный компоненту, переданный в компонент в качестве второго параметра createElement
- Children: массив дочерних узлов VNode
- parent: ссылка на родительский компонент
- слоты: функция, которая возвращает объект, содержащий все слоты
- scopedSlots: (2.6.0+) Объект, предоставляющий переданные слоты с областью видимости. Также предоставляет обычные слоты как функции.
- Слушатели: (2.3.0+) Объект, содержащий все родительские компоненты, зарегистрированные с текущим компонентом. Это псевдоним Data.on.
- инъекции: (2.3.0+) Если используется опция инъекции, этот объект содержит свойства, которые должны быть внедрены.
После перехода на функциональный компонент нам нужно изменить функцию рендеринга нашего компонента, добавить в него параметры контекста, и если естьthis.$slots.defaultизменить наcontext.children,Потомthis.levelизменить наcontext.props.levelЖдать.
7. Слот,
scopedSlots
Выше мы видели многократное использование слота в функции рендеринга. Итак, на этот раз давайте посмотрим, что такое слот.
Содержание слота:
Vue реализует набор API для распространения контента,<slot>Элемент действует как выход для переноса распределенного контента, то есть контент, заполненный компонентом, может быть отображен между слотами дочернего компонента.
<!-- 父组件 -->
<navigation-link url="/profile">
Your Profile
</navigation-link>
<!-- navigation-link组件 -->
<a
v-bind:href="url"
class="nav-link"
>
<slot></slot>
</a>
Когда компонент визуализируется,<slot></slot>будет заменен на «Ваш профиль».
Слот может содержать любой код шаблона, включая HTML или другие компоненты:
<navigation-link url="/profile">
<!-- 添加一个 Font Awesome 图标 -->
<span class="fa fa-user"></span>
Your Profile
</navigation-link>
<!-- 插槽内为组件 -->
<navigation-link url="/profile">
<!-- 添加一个图标的组件 -->
<font-awesome-icon name="user"></font-awesome-icon>
Your Profile
</navigation-link>
Объем компиляции:
Область компиляции относится к содержимому, записанному внутри ссылочного компонента, и содержимому внутри подкомпонента, и все, что можно получить, — это значение в его текущей области. Существует принцип, что все в родительском шаблоне компилируется в родительской области, все в дочернем шаблоне компилируется в дочерней области. ** В сочетании с кодом для просмотра:
<!-- 如果想在插槽中使用数据user -->
<!-- user必须是navigation-link组件坐在的作用域可以访问到的值 -->
<navigation-link url="/profile">
Logged in as {{ user.name }}
</navigation-link>
Ниже приведен пример недостижимой ошибки:
<!-- 这里是访问不到url的 -->
<!-- 因为当前的url值"/profile"是在navigation-link组件内部定义的 -->
<!-- 在navigation-link组件所在的作用域,是访问不到url的 -->
<navigation-link url="/profile">
Clicking here will send you to: {{ url }}
</navigation-link>
Резервный контент и именованные слоты:
Пример кода для непосредственного просмотра резервного содержимого:
<!-- 父组件 -->
<submit-button>
Save
</submit-button>
<!-- submit-button组件 -->
<button type="submit">
<slot>Submit</slot>
</button>
Резервное содержимое означает, что я также пишу содержимое между слотами в качестве резервного содержимого.Когда кнопка отправки используется в родительском компоненте и между ними есть содержимое, это значение будет отображаться первым. Если между кнопками отправки нет контента, будет отображаться запасной контент между слотами.
<!-- 父组件 -->
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template #footer>
<p>Here's some contact info</p>
</template>
</base-layout>
<!-- base-layout 组件 -->
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
Выше приведен пример использования именованных слотов. Если вам нужно указать содержимое для рендеринга нескольких слотов, вы можете добавить атрибут имени к слоту, а при предоставлении контента для слота вы можете использовать шаблон для переноса контента и написать спецификацию v-slot на шаблоне. , и используйте параметр Форма предоставляет имя слота для рендеринга на v-слоте. Таким образом, содержимое шаблона может быть отображено в слоте с указанным именем. v-slot: имя в шаблоне может быть сокращено до #name. Если в шаблоне не указано имя, используется имя по умолчанию.
Слоты с ограниченной областью действия:
Как мы упоминали выше, область действия слота ограничена, содержимое родительского шаблона может иметь доступ только к области действия родительского шаблона, а содержимое дочернего компонента может отображаться только в области дочернего элемента. Если я хочу использовать значение в дочернем компоненте в родительском шаблоне, как я могу это реализовать?На данный момент мне нужно использовать слот, а именно:
<!-- 错误示范 -->
<!-- 父组件想要使用current-user组件内的值user -->
<current-user>
{{ user.firstName }}
</current-user>
<!-- current-user组件 -->
<span>
<slot>{{ user.lastName }}</slot>
</span>
<!-- 正确示范 -->
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
</current-user>
<span>
<slot v-bind:user="user">
{{ user.lastName }}
</slot>
</span>
$slots:
API, используемый в vue для доступа к контенту, распространяемому слотом, эквивалентен API в шаблоне.<slot></slot>. Каждый именованный слот имеет свои собственные свойства (например: содержимое v-slot:foo будет найдено в vm.$slots.foo). Атрибут по умолчанию включает все узлы, не содержащиеся в именованном слоте, или содержимое v-slot:default.
$scopedSlots:
Используется для доступа к слотам с ограниченной областью действия, что может дать<slot>Область слота, предоставляющая значение. Для каждого слота, включая слот по умолчанию, этот объект содержит функцию, которая возвращает соответствующий VNode.
8. Резюме
Это то, что я представлю на этот раз.Хотя это относительно просто, но в нем задействованы основные принципы использования.Я надеюсь, что это будет полезно для всех в будущем развитии или интервью.
9. Справочные статьи
Официальная документация Vue: функция рендеринга и jsx
официальная документация vue: слоты
Использование синтаксиса jsx в vue
vue jsx не полностью указывает на север
Об авторе: Гун Ченгуан,люди и будущееИнженер по работе с большими данными.