Funkce pro vykreslení & JSX
Vue doporučuje pro většinu případů tvorby aplikací používat šablony. Nicméně existují situace, kdy potřebujeme plnou programovou sílu JavaScriptu. A právě pro tyto případy můžeme použít funkci pro vykreslení.
Pokud je koncept virtuálního DOM a funkcí pro vykreslení nový, přečtěte si nejprve kapitolu Mechanismus vykreslování.
Základní použití
Vytváření VNodes
Vue poskytuje funkci h()
pro vytváření VNodes:
js
import { h } from 'vue'
const vnode = h(
'div', // typ
{ id: 'foo', class: 'bar' }, // vlastnosti
[
/* potomci */
]
)
h()
je zkratka pro hyperscript - což znamená „JavaScript, který produkuje HTML (hypertext markup language)“. Tento název je dědictvím konvencí sdílených mnoha implementacemi virtuálního DOM. Popisnější název by mohl být createVNode()
, ale kratší název pomáhá, když tuto funkci musíte v rámci funkce pro vykreslení volat mnohokrát.
Funkce h()
je navržena tak, aby byla velmi flexibilní:
js
// všechny parametry kromě typu jsou volitelné
h('div')
h('div', { id: 'foo' })
// Vue automaticky vybere správný způsob přiřazení hodnot z props objektu
// - zda jde o html atributy nebo vlastnosti (props) komponenty
h('div', { class: 'bar', innerHTML: 'hello' })
// mohou být přidány modifikátory vlastností, jako `.prop` a `.attr`
// s předponami `.` resp. `^`
h('div', { '.name': 'some-name', '^width': '100' })
// třída a styl mají stejnou podporu objektu / pole
// hodnot, kterou mají ve šablonách
h('div', { class: [foo, { bar }], style: { color: 'red' } })
// event listenery je třeba předávat jako onXxx
h('div', { onClick: () => {} })
// potomci mohou být řetězec
h('div', { id: 'foo' }, 'hello')
// objekt props lze vynechat, pokud není potřeba
h('div', 'hello')
h('div', [h('span', 'hello')])
// pole potomků může obsahovat směs VNodes a řetězců
h('div', ['hello', h('span', 'hello')])
Výsledný VNode má následující strukturu:
js
const vnode = h('div', { id: 'foo' }, [])
vnode.type // 'div'
vnode.props // { id: 'foo' }
vnode.children // []
vnode.key // null
Poznámka
Plné rozhraní VNode
obsahuje mnoho dalších interních vlastností, ale je silně doporučeno nespoléhat se na žádné vlastnosti kromě těchto uvedených. Tím se zabrání nechtěným problémům, pokud se interní vlastnosti změní.
Deklarace funkcí pro vykreslení
Při použití šablon s Composition API se k vystavení dat šabloně návratová hodnota setup()
hooku používá. Při použití funkcí pro vykreslení však můžeme přímo vrátit takovou funkci:
js
import { ref, h } from 'vue'
export default {
props: {
/* ... */
},
setup(props) {
const count = ref(1)
// vrátit funkci pro vykreslení
return () => h('div', props.msg + count.value)
}
}
Funkce pro vykreslení je deklarována uvnitř setup()
a má tak přirozený přístup k vlastnostem (props) a jakémukoli reaktivnímu stavu deklarovanému ve stejném scope.
Kromě vrácení jednoho VNode můžete také vrátit řetězce nebo pole:
js
export default {
setup() {
return () => 'Ahoj, Vue!'
}
}
js
import { h } from 'vue'
export default {
setup() {
// použít pole pro vrácení více root elementů
return () => [
h('div'),
h('div'),
h('div')
]
}
}
TIP
Ujistěte se, že vrátíte funkci místo přímého vrácení hodnot! Funkce setup()
je pro každou komponentu volána pouze jednou, zatímco vrácená funkce pro vykreslení bude volána vícekrát.
Pokud komponenta funkce pro vykreslení nepotřebuje žádný stav instance, může být pro stručnost deklarována také přímo jako funkce:
js
function Hello() {
return 'Ahoj, Vue!'
}
Ano, toto je platná Vue komponenta! Pro více podrobností o této syntaxi viz Funkční komponenty.
VNodes musí být jedinečné
Všechny VNodes ve stromu komponent musí být jedinečné. To znamená, že následující funkce pro vykreslení je neplatná:
js
function render() {
const p = h('p', 'ahoj')
return h('div', [
// Oops - duplicitní VNodes!
p,
p
])
}
Pokud opravdu chcete mnohokrát zduplikovat stejný element/komponentu, můžete to udělat pomocí tovární funkce. Například následující funkce pro vykreslení je naprosto platným způsobem vykreslování 20 identických odstavců:
js
function render() {
return h(
'div',
Array.from({ length: 20 }).map(() => {
return h('p', 'ahoj')
})
)
}
JSX / TSX
JSX je XML-like rozšíření pro JavaScript, které nám umožňuje psát kód takto:
jsx
const vnode = <div>ahoj</div>
V rámci JSX výrazů použijte pro vložení dynamických hodnot složené závorky:
jsx
const vnode = <div id={dynamicId}>ahoj, {userName}</div>
Jak create-vue
, tak Vue CLI umožňují vytvářet projekty s předkonfigurovanou podporou JSX. Pokud konfigurujete JSX manuálně, podívejte se prosím na podrobnosti do dokumentace @vue/babel-plugin-jsx
.
I když byl JSX původně představen v Reactu, ve skutečnosti nemá definovanou běhovou sémantiku a může být zkompilován do různých výstupů. Pokud jste již s JSX pracovali, mějte na paměti, že Vue JSX transformace se liší od React JSX transformace, takže React JSX transformaci nemůžete použít ve Vue aplikacích. Některé významné rozdíly oproti React JSX zahrnují:
- Můžete použít HTML atributy jako
class
afor
jako vlastnosti (props) - není třeba používatclassName
nebohtmlFor
. - Předávání potomků komponentám (tj. sloty) funguje jinak.
Definice typů ve Vue také poskytuje odvozování typů pro použití TSX. Při použití TSX se ujistěte, že ve vašem souboru tsconfig.json
je specifikováno "jsx": "preserve"
, aby TypeScript nechal JSX syntaxi nedotčenou pro zpracování Vue JSX transformace.
Odvozování JSX typů
Podobně jako transformace, JSX Vue také potřebuje odlišné definice typů.
Od verze 3.4 už Vue implicitně neregistruje globální JSX
namespace. Abyste řekli TypeScriptu, že má používat Vue JSX definice typů, přidejte do vašeho tsconfig.json
následující:
json
{
"compilerOptions": {
"jsx": "preserve",
"jsxImportSource": "vue"
// ...
}
}
Toto chování můžete také nastavit samostatně pouze pro vybrané soubory přidáním komentáře /* @jsxImportSource vue */
na začátek souboru.
Pokud existuje kód, který závisí na přítomnosti globálního jmenného prostoru JSX
, můžete zachovat přesně stejné chování jako před verzí 3.4 explicitním odkazováním na vue/jsx
, který registruje globální jmenný prostor JSX
.
Návody k funkcím pro vykreslení
Níže poskytneme několik návodů pro běžnou implementaci funkcí šablony pomocí jejich ekvivalentních funkcí pro vykreslení / JSX.
v-if
Šablona:
template
<div>
<div v-if="ok">ano</div>
<span v-else>ne</span>
</div>
Ekvivalentní funkce pro vykreslení / JSX:
js
h('div', [ok.value ? h('div', 'ano') : h('span', 'ne')])
jsx
<div>{ok.value ? <div>ano</div> : <span>ne</span>}</div>
v-for
Šablona:
template
<ul>
<li v-for="{ id, text } in items" :key="id">
{{ text }}
</li>
</ul>
Ekvivalentní funkce pro vykreslení / JSX:
js
h(
'ul',
// předpokládá se, že `items` je ref s hodnotou pole
items.value.map(({ id, text }) => {
return h('li', { key: id }, text)
})
)
jsx
<ul>
{items.value.map(({ id, text }) => {
return <li key={id}>{text}</li>
})}
</ul>
v-on
Vlastnosti (props) s názvy začínajícími na on
a následovanými velkým písmenem jsou považovány za event listenery. Například onClick
je ekvivalentem @click
ve šablonách.
js
h(
'button',
{
onClick(event) {
/* ... */
}
},
'Klikni na mě'
)
jsx
<button
onClick={(event) => {
/* ... */
}}
>
Klikni na mě
</button>
Modifikátory událostí
Modifikátory událostí .passive
, .capture
a .once
lze připojit za název události pomocí camelCase.
Například:
js
h('input', {
onClickCapture() {
/* listener v režimu capture */
},
onKeyupOnce() {
/* spustí se pouze jednou */
},
onMouseoverOnceCapture() {
/* once + capture */
}
})
jsx
<input
onClickCapture={() => {}}
onKeyupOnce={() => {}}
onMouseoverOnceCapture={() => {}}
/>
Pro ostatní modifikátory událostí a klávesové modifikátory lze použít pomocnou funkci withModifiers
:
js
import { withModifiers } from 'vue'
h('div', {
onClick: withModifiers(() => {}, ['self'])
})
jsx
<div onClick={withModifiers(() => {}, ['self'])} />
Komponenty
K vytvoření VNode pro komponentu by měl být první parametr předaný do h()
definice komponenty. To znamená, že při použití funkcí pro vykreslení není nutné komponenty registrovat - můžete je použít přímo:
js
import Foo from './Foo.vue'
import Bar from './Bar.jsx'
function render() {
return h('div', [h(Foo), h(Bar)])
}
jsx
function render() {
return (
<div>
<Foo />
<Bar />
</div>
)
}
Jak můžeme vidět, h
může pracovat s komponentami importovanými z jakéhokoli formátu souboru, pokud je to platná Vue komponenta.
Dynamické komponenty jsou s funkcemi pro vykreslení jednoduché:
js
import Foo from './Foo.vue'
import Bar from './Bar.jsx'
function render() {
return ok.value ? h(Foo) : h(Bar)
}
jsx
function render() {
return ok.value ? <Foo /> : <Bar />
}
Pokud je komponenta registrována podle jména a nelze ji importovat přímo (například je globálně registrována knihovnou), lze ji programově vyřešit pomocnou funkcí resolveComponent()
.
Vykreslování slotů
Ve funkcích pro vykreslení lze sloty (slots) získat z kontextu setup()
. Každý slot na objektu slots
je funkce, která vrací pole VNodes:
js
export default {
props: ['message'],
setup(props, { slots }) {
return () => [
// default slot:
// <div><slot /></div>
h('div', slots.default()),
// pojmenovaný slot:
// <div><slot name="footer" :text="message" /></div>
h(
'div',
slots.footer({
text: props.message
})
)
]
}
}
Ekvivalent v JSX:
jsx
// default
<div>{slots.default()}</div>
// pojmenovaný
<div>{slots.footer({ text: props.message })}</div>
Předávání slotů
Předávání potomků komponentám funguje trochu jinak než předávání potomků elementům. Místo pole musíme předat buď funkci slotu, nebo objekt funkcí slotů. Funkce slotů mohou vrátit cokoli, co může vrátit běžná funkce pro vykreslení - což bude vždy normalizováno na pole VNodes, když je přístupováno v dceřiné komponentě.
js
// jediný výchozí slot
h(MyComponent, () => 'ahoj')
// pojmenované sloty
// `null` je nutné použít, aby se
// objekt slotů nezaměňoval s props
h(MyComponent, null, {
default: () => 'výchozí slot',
foo: () => h('div', 'foo'),
bar: () => [h('span', 'jeden'), h('span', 'dva')]
})
JSX ekvivalent:
jsx
// výchozí
<MyComponent>{() => 'hello'}</MyComponent>
// pojmenované
<MyComponent>{{
default: () => 'výchozí slot',
foo: () => <div>foo</div>,
bar: () => [<span>jeden</span>, <span>dva</span>]
}}</MyComponent>
Předávání slotů jako funkcí umožňuje jejich „lazy“ volání v komponentě potomka. Díky tomu jsou závislosti slotu sledovány komponentou potomka místo rodičovské, což vede k přesnějším a efektivnějším aktualizacím.
Scoped sloty
Pro vykreslení scoped slotu v komponentě rodiče je slot předán potomkovi. Všimněte si, že slot má nyní parametr text
. Slot bude zavolán v komponentně potomka a její data budou předány nahoru do rodiče.
js
// komponenta rodiče
export default {
setup() {
return () => h(MyComp, null, {
default: ({ text }) => h('p', text)
})
}
}
Nezapomeňte předat null
, aby sloty nebyly vyhodnoceny jako props.
js
// komponenta potomka
export default {
setup(props, { slots }) {
const text = ref('ahoj')
return () => h('div', null, slots.default({ text: text.value }))
}
}
JSX ekvivalent:
jsx
<MyComponent>{{
default: ({ text }) => <p>{ text }</p>
}}</MyComponent>
Vestavěné komponenty
Vestavěné komponenty jako <KeepAlive>
, <Transition>
, <TransitionGroup>
, <Teleport>
a <Suspense>
musí být pro použití ve funkcích pro vykreslení importovány:
js
import { h, KeepAlive, Teleport, Transition, TransitionGroup } from 'vue'
export default {
setup () {
return () => h(Transition, { mode: 'out-in' }, /* ... */)
}
}
v-model
Direktiva v-model
je při kompilaci šablony rozšířena na vlastnosti modelValue
a onUpdate:modelValue
- tyto vlastnosti (props) musíme poskytnout sami:
js
export default {
props: ['modelValue'],
emits: ['update:modelValue'],
setup(props, { emit }) {
return () =>
h(SomeComponent, {
modelValue: props.modelValue,
'onUpdate:modelValue': (value) => emit('update:modelValue', value)
})
}
}
Vlastní direktivy
Vlastní direktivy lze na VNode aplikovat pomocí withDirectives
:
js
import { h, withDirectives } from 'vue'
// vlastní direktiva
const pin = {
mounted() { /* ... */ },
updated() { /* ... */ }
}
// <div v-pin:top.animate="200"></div>
const vnode = withDirectives(h('div'), [
[pin, 200, 'top', { animate: true }]
])
Pokud je direktiva registrována pod názvem a nelze ji importovat přímo, lze ji vyřešit pomocnou funkcí resolveDirective
.
Template Refs
S Composition API jsou template refs vytvářeny předáním samotného ref()
jako vlastnosti VNode:
js
import { h, ref } from 'vue'
export default {
setup() {
const divEl = ref()
// <div ref="divEl">
return () => h('div', { ref: divEl })
}
}
Funkční komponenty
Funkční (Functional) komponenty jsou alternativní formou komponent, které nemají vlastní stav. Chovají se jako čisté funkce: přijímají props a vrací VNodes. Jsou vykreslovány bez vytváření instance komponenty (tj. žádné this
) a bez běžných lifecycle hooks komponenty.
Pro vytvoření funkční komponenty používáme běžnou funkci místo objektu s možnostmi (options object). Tato funkce je vlastně render
funkcí pro komponentu.
Signatura funkční komponenty je stejná jako setup()
hook:
js
function MyComponent(props, { slots, emit, attrs }) {
// ...
}
Většina běžných konfiguračních možností pro komponenty není pro funkční komponenty dostupná. Nicméně je možné definovat props
a emits
přidáním těchto vlastností:
js
MyComponent.props = ['value']
MyComponent.emits = ['click']
Pokud není specifikována možnost props
, objekt props
předaný funkci bude obsahovat všechny atributy, stejně jako attrs
. Pokud není specifikována možnost props
, názvy props nebudou normalizovány na camelCase.
Pro funkční komponenty s explicitními props
fungují fallthrough atributy stejně jako u běžných komponent. Pro funkční komponenty, které své props
explicitně nezadávají, však budou ve výchozím stavu děděny pouze class
, style
a event listenery onXxx
z attrs
. V obou případech lze pomocí inheritAttrs
nastavit false
, aby se dědění atributů zakázalo:
js
MyComponent.inheritAttrs = false
Funkční komponenty mohou být registrovány a používány stejně jako běžné komponenty. Pokud předáte funkci jako první parametr do h()
, bude považována za funkční komponentu.
Typování funkčních komponent
Funkční komponenty mohou být typovány na základě toho, zda jsou pojmenované nebo anonymní. Rozšíření Vue - Official také podporuje kontrolu typů správně typovaných funkčních komponent při jejich použití v SFC šablonách.
Pojmenovaná funkční komponenta
tsx
import type { SetupContext } from 'vue'
type FComponentProps = {
message: string
}
type Events = {
sendMessage(message: string): void
}
function FComponent(
props: FComponentProps,
context: SetupContext<Events>
) {
return (
<button onClick={() => context.emit('sendMessage', props.message)}>
{props.message} {' '}
</button>
)
}
FComponent.props = {
message: {
type: String,
required: true
}
}
FComponent.emits = {
sendMessage: (value: unknown) => typeof value === 'string'
}
Anonymní funkční komponenta
tsx
import type { FunctionalComponent } from 'vue'
type FComponentProps = {
message: string
}
type Events = {
sendMessage(message: string): void
}
const FComponent: FunctionalComponent<FComponentProps, Events> = (
props,
context
) => {
return (
<button onClick={() => context.emit('sendMessage', props.message)}>
{props.message} {' '}
</button>
)
}
FComponent.props = {
message: {
type: String,
required: true
}
}
FComponent.emits = {
sendMessage: (value) => typeof value === 'string'
}