Guia
Essenciais
- Instalação
- Introdução
- A Instância Vue
- Sintaxe de Templates
- Dados Computados e Observadores
- Interligações de Classe e Estilo
- Renderização Condicional
- Renderização de Listas
- Manipulação de Eventos
- Interligações em Formulários
- Básico sobre Componentes
Componentes em Detalhes
- Registro de Componentes
- Propriedades
- Eventos Personalizados
- Slots
- Dinâmicos & Assíncronos
- Lidando com Casos Extremos
Transições & Animações
- Transições de Visibilidade e Listas
- Transições de Estado
Reuso & Composição
- Mixins
- Diretivas Personalizadas
- Funções de Renderização & JSX
- Plugins
- Filtros
Ferramentas
- Componentes Single-File
- Testes Unitários
- Testing
- Suporte ao TypeScript
- Publicando em Produção
Escalonando
- Roteamento
- Gerenciamento de Estado
- Renderizando no Lado do Servidor
- Segurança
Internamente
- Reatividade em Profundidade
Migração
- Migração do Vue 1.x
- Migração do Vue Router 0.7.x
- Migração do Vuex 0.6.x para 1.0
Diversos
- Comparação com Outros Frameworks
- Junte-se à Comunidade Vue.js!
- Conheça a Equipe
Você está navegando a documentação da v2.x e anterior. Para a v3.x, clique aqui.
Funções de Renderização & JSX
Introdução
Vue recomenda que templates sejam utilizados para construir seu HTML na grande maioria dos casos. Haverá situações, no entanto, em que você irá realmente precisar de todo o poder de programação do JavaScript. É quando você pode usar a função render
, uma alternativa aos templates mais próxima do compilador.
Vamos mergulhar em um exemplo simples onde a função render
seria prática. Digamos que você quer gerar cabeçalhos (headings) com âncoras:
<h1>
<a name="hello-world" href="#hello-world">
Olá Mundo!
</a>
</h1>
Para o código HTML acima, você decide que quer esta interface para o componente:
<anchored-heading :level="1">Olá Mundo!</anchored-heading>
Quando você começar a criar o componente que gera um cabeçalho de acordo com a propriedade level
, rapidamente chegará a algo assim:
<script type="text/x-template" id="anchored-heading-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>
</script>
Vue.component('anchored-heading', {
template: '#anchored-heading-template',
props: {
level: {
type: Number,
required: true
}
}
})
Esse template não está passando uma boa sensação. Não é apenas extenso, ele inclusive duplica <slot></slot>
para cada nível de cabeçalho, e teríamos que fazer o mesmo ao incluirmos um elemento <a>
.
Enquanto templates funcionam muito bem para a maioria dos componentes, está claro que este caso é uma exceção. Então vamos tentar reescrevê-lo usando uma função render
:
Vue.component('anchored-heading', {
render: function (createElement) {
return createElement(
'h' + this.level, // nome do elemento (tag)
this.$slots.default // vetor de elementos filhos
)
},
props: {
level: {
type: Number,
required: true
}
}
})
Muito mais simples! Mais ou menos. O código é menor, mas requer maior familiaridade com as propriedades de uma instância Vue. Neste caso, você precisa saber que quando você inclui elementos filho em seu componente, sem especificar uma diretiva v-slot
, como o Olá Mundo!
dentro de anchored-heading
, esses elementos estão acessíveis na instância através de $slots.default
. Se você ainda não leu, é altamente recomendado que leia a seção da API de propriedades da instância antes de se aprofundar em funções render
.
Nós, Árvores e DOM Virtual
Antes de nos aprofundarmos nas funções de renderização, é importante saber um pouco sobre como os navegadores trabalham. Considere este HTML como exemplo:
<div>
<h1>My title</h1>
Some text content
<!-- TODO: Add tagline -->
</div>
Quando um navegador lê esse código, ele compila uma árvore de “nós DOM” para ajudar a manter o controle sobre todas as coisas, assim como você poderia querer construir uma árvore genealógica para manter o controle sobre a extensão de sua família.
A árvore de nós DOM para o HTML acima aparentaria isso:
Cada elemento é um nó. Cada pedaço de texto é um nó. Até mesmo comentários são nós! Um nó é apenas um pedaço da página. E assim como na árvore genealógica, cada nó tem filhos (ou seja, cada pedaço pode conter outros pedaços dentro).
Atualizar todos esses nós eficientemente pode ser difícil, mas por sorte você nunca precisará fazer isso manualmente. Em vez disso, você diz ao Vue qual HTML quer na página, através de um template:
<h1>{{ blogTitle }}</h1>
Ou uma função de renderização:
render: function (createElement) {
return createElement('h1', this.blogTitle)
}
E em ambos os casos, Vue automaticamente mantém a página atualizada, mesmo se blogTitle
mudar.
DOM Virtual
Vue realiza isto través da construção de um DOM Virtual para manter o controle das mudanças que ele precisa aplicar no DOM real. Dê uma olhada mais de perto nesta linha:
return createElement('h1', this.blogTitle)
O que a função createElement
retornará? Não é exatamente um elemento DOM real. Poderíamos chamar mais precisamente de createNodeDescription
, uma vez que conterá a descrição para o Vue do tipo de nó a renderizar na página, incluindo as descrições de quaisquer nós filhos. Nós chamamos esta descrição de “nó virtual”, usualmente abreviado em inglês como VNode. “DOM Virtual” é como nós chamamos a árvore de VNodes completa, construída através da árvore de componentes Vue.
Parâmetros para createElement
A próxima coisa com a qual você precisa se familiarizar é como utilizar os recursos disponíveis em templates na função createElement
. Estes são os parâmetros que createElement
aceita:
// @returns {VNode}
createElement(
// {String | Object | Function}
// Um nome de elemento (tag) HTML, um objeto com opções de componente, ou async
// ou uma função retornando um dentre os anteriores. Requerido.
'div',
// {Object}
// Um objeto de dados (chave: valor) correspondendo aos atributos
// que você usaria em template. Opcional.
{
// (veja detalhes na próxima seção, abaixo)
},
// {String | Array}
// VNodes filhos, construídos com `createElement()`,
// ou apenas Strings para 'VNodes textuais'. Opcional.
[
'Algum texto vem primeiro.',
createElement('h1', 'Um título')
createElement(MyComponent, {
props: {
someProp: 'foobar'
}
})
]
)
O Objeto de Dados em Detalhes
Importante observar: assim como v-bind:class
e v-bind:style
têm tratamento especial em templates, eles têm suas respectivas propriedades no primeiro nível de um objeto de dados VNode.
{
// Mesma API que `v-bind:class`, aceitando tanto
// uma String, Object, ou Array de Strings e Objects.
class: {
foo: true,
bar: false
},
// Mesma API que `v-bind:style`, aceitando tanto
// uma String, Object, ou Array de Strings e Objects.
style: {
color: 'red',
fontSize: '14px'
},
// Atributos HTML comuns
attrs: {
id: 'foo'
},
// Propriedades (props) de componente
props: {
myProp: 'bar'
},
// Propriedades DOM
domProps: {
innerHTML: 'baz'
},
// Manipuladores de eventos são declarados dentro de
// `on`, porém modificadores como em `v-on:keyup.enter`
// não são suportados. Você terá que verificar
// manualmente o keyCode do evento no código.
on: {
click: this.clickHandler
},
// Somente para componentes. Permite a escuta a
// eventos nativos, ao invés de eventos emitidos
// pelo componente através de `vm.$emit`.
nativeOn: {
click: this.nativeClickHandler
},
// Diretivas personalizadas. Veja que o bind de
// `oldValue` não pode ser setado, já que o Vue
// mantém o rastreamento para você
directives: [
{
name: 'my-custom-directive',
value: '2',
expression: '1 + 1',
arg: 'foo',
modifiers: {
bar: true
}
}
],
// Slot na forma de
// { name: props => VNode | Array<VNode> }
scopedSlots: {
default: props => createElement('span', props.text)
},
// O nome do slot, se este componente é
// filho de outro componente
slot: 'name-of-slot'
// Outras propriedades especiais de primeiro nível
key: 'myKey',
ref: 'myRef',
// If you are applying the same ref name to multiple
// elements in the render function. This will make `$refs.myRef` become an
// array
refInFor: true
}
Exemplo Completo
Com este conhecimento, nós agora podemos finalizar o componente que começamos:
var getChildrenTextContent = function (children) {
return children.map(function (node) {
return node.children
? getChildrenTextContent(node.children)
: node.text
}).join('')
}
Vue.component('anchored-heading', {
render: function (createElement) {
// criar id em kebab-case
var headingId = getChildrenTextContent(this.$slots.default)
.toLowerCase()
.replace(/\W+/g, '-')
.replace(/(^-|-$)/g, '')
return createElement(
'h' + this.level,
[
createElement('a', {
attrs: {
name: headingId,
href: '#' + headingId
}
}, this.$slots.default)
]
)
},
props: {
nivel: {
type: Number,
required: true
}
}
})
Restrições
VNodes Têm Que Ser Únicos
Todos os VNodes na árvore do componente têm que ser únicos. Isso significa que a seguinte função render
é inválida:
render: function (createElement) {
var myParagraphVNode = createElement('p', 'oi')
return createElement('div', [
// Oh não! - VNodes em duplicidade!
myParagraphVNode, myParagraphVNode
])
}
Se você realmente quiser duplicar o mesmo elemento ou componente várias vezes, você pode fazê-lo com uma função factory. Por exemplo, a seguinte função render
contém uma forma perfeitamente válida de renderizar 20 parágrafos idênticos:
render: function (createElement) {
return createElement('div',
Array.apply(null, { length: 20 }).map(function () {
return createElement('p', 'oi')
})
)
}
Substituindo templates por Código JavaScript
v-if
e v-for
Sempre que algo possa ser facilmente realizado em código JavaScript tradicional, as funções de renderização não oferecerão alternativas específicas. Por exemplo, em templates usando v-if
e v-for
:
<ul v-if="items.length">
<li v-for="item in items">{{ item.name }}</li>
</ul>
<p v-else>Nenhum item encontrado.</p>
Isto poderia ser reescrito com if
/else
e map
do JavaScript, em uma função render
:
props: ['items'],
render: function (createElement) {
if (this.items.length) {
return createElement('ul', this.items.map(function (item) {
return createElement('li', item.name)
}))
} else {
return createElement('p', 'Nenhum item encontrado.')
}
}
v-model
Não existe contrapartida direta de v-model
em funções de renderização - você terá que implementar a lógica por si próprio:
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)
}
}
})
}
Este é o custo de ir para um nível mais baixo, mas também oferece muito mais controle sobre os detalhes da interação em comparação ao v-model
.
Eventos e Modificadores
Para modificadores de eventos .passive
, .capture
e .once
, Vue oferece prefixos que podem ser usados em conjunto com on
:
Modificador | Prefixo |
---|---|
.passive |
& |
.capture |
! |
.once |
~ |
.capture.once ou.once.capture |
~! |
Por exemplo:
on: {
'!click': this.doThisInCapturingMode,
'~keyup': this.doThisOnce,
'~!mouseover': this.doThisOnceInCapturingMode
}
Para todos os outros eventos e modificadores de teclado, nenhum prefixo proprietário é necessário, pois você pode usar métodos do objeto event
no manipulador:
Modificador | Equivalente no Código |
---|---|
.stop |
event.stopPropagation() |
.prevent |
event.preventDefault() |
.self |
if (event.target !== event.currentTarget) return |
Teclas:.enter , .13 |
if (event.keyCode !== 13) return (troque 13 por outro key code para outros modificadores) |
Especiais:.ctrl , .alt , .shift , .meta |
if (!event.ctrlKey) return (troque ctrlKey por altKey , shiftKey , ou metaKey ) |
Aqui está um exemplo com todos estes modificadores em conjunto:
on: {
keyup: function (event) {
// Aborta se o elemento emitindo o evento não é
// o elemento no qual o evento está vinculado
if (event.target !== event.currentTarget) return
// Aborta se a tecla acionada não foi o Enter (13)
// e a tecla Shift não foi pressionada ao mesmo tempo
if (!event.shiftKey || event.keyCode !== 13) return
// Para a propagação do evento
event.stopPropagation()
// Previne a execução padrão do keyup para o elemento
event.preventDefault()
// ...
}
}
Slots
Você pode acessar conteúdo estáticos de slots como Arrays de VNodes a partir de this.$slots
:
render: function (createElement) {
// `<div><slot></slot></div>`
return createElement('div', this.$slots.default)
}
E acessar slots com escopo como funções que retornar VNodes a partir de this.$scopedSlots
:
props: ['message'],
render: function (createElement) {
// `<div><slot :text="message"></slot></div>`
return createElement('div', [
this.$scopedSlots.default({
text: this.message
})
])
}
Para passar slots a um componente filho usando funções de renderização, use o campo scopedSlots
nos dados do VNode:
render: function (createElement) {
// `<div><child v-slot="props"><span>{{ props.text }}</span></child></div>`
return createElement('div', [
createElement('child', {
// passe `scopedSlots` no objeto de dados
// na forma de { name: props => VNode | Array<VNode> }
scopedSlots: {
default: function (props) {
return createElement('span', props.text)
}
}
})
])
}
JSX
Se você estiver escrevendo muitas funções render
, pode se tornar cansativo e passível de erros escrever muitas linhas de código como essas:
createElement(
'anchored-heading', {
props: {
nivel: 1
}
}, [
createElement('span', 'Alô'),
' Mundo!'
]
)
Especialmente quando a versão usando template é tão simples em comparação:
<anchored-heading :nivel="1">
<span>Alô</span> Mundo!
</anchored-heading>
Por isso há um plugin para Babel destinado à utilização de JSX com o Vue, nos trazendo de volta a uma sintaxe mais semelhante à utilizada em templates:
import AnchoredHeading from './AnchoredHeading.vue'
new Vue({
el: '#demo',
render: function (h) {
return (
<AnchoredHeading nivel={1}>
<span>Alô</span> Mundo!
</AnchoredHeading>
)
}
})
Apelidar createElement
como h
é uma convenção comum que você verá na comunidade Vue e é necessária para o uso de JSX. Começando com a versão 3.4.0 do plugin Babel para Vue, nós automaticamente injetamos const h = this.$createElement
em qualquer método e getter (não funções ou arrow functions), declarado na sintaxe do ES2015 que possui JSX, então você pode descartar o parâmetro (h)
. Com as versões anteriores do plug-in, seu aplicativo lançaria um erro se h
não estivesse disponível no escopo.
Para mais informações sobre como JSX é mapeado para JavaScript, veja a documentação de utilização.
Componentes Funcionais
O componente de cabeçalho com âncoras que criamos anteriormente é relativamente simples. Ele não gerencia nenhum estado próprio, não observa nenhum estado passado para si, e não tem nenhum método ligado ao ciclo de vida da instância. Realmente, é apenas uma função com algumas propriedades.
Em casos como este, nós podemos marcar componentes como functional
, o que significa que eles são stateless (sem estado), ou seja, sem data
) e são instanceless (sem instância, ou seja, sem o contexto this
). Um componente funcional tem este formato:
Vue.component('meu-componente', {
functional: true,
// Para compensar a inexistência de uma instância,
// é providenciado um segundo argumento: "context".
render: function (createElement, context) {
// ...
},
// Props são opcionais
props: {
// ...
}
})
Nota: em versões anteriores à 2.3.0, a opção
props
é obrigatória se você deseja aceitar propriedades em um componente funcional. No 2.3.0+ você pode omitirprops
e todos os atributos encontrados no nó do componente serão extraídos como propriedades.
A referência será um
HTMLElement
quando utilizado com componentes funcionais, pois eles são stateless e instanceless.
No 2.5.0+, se você está usando Componentes Single-File, componentes funcionais baseados em templates podem ser declarados com:
<template functional>
</template>
Tudo que o componente funcional necessita é passado através de context
, o qual é um objeto contendo:
props
: Um objeto com as propriedadeschildren
: Um Array de elementos VNode filhosslots
: Uma função retornando um objeto slotsscopedSlots
: (2.6.0+) Um objeto que expõe os slots com escopo no passado. Também expõe slots normais como funções.data
: Todo o objetodata
passado ao componente como segundo argumento decreateElement
parent
: Uma referência ao componente pailisteners
: (2.3.0+) Um objeto contendo escutadores de eventos registradas pelo pai. É um atalho paradata.on
injections
: (2.3.0+) Se estiver usando a opçãoinject
, aqui estarão as injeções resolvidas
Após acrescentar functional: true
, adaptar a função render
do nosso componente de cabeçalho com âncoras iria requerer acrescentar o parâmetro context
, atualizar this.$slots.default
para context.children
e então atualizar this.level
para context.props.level
.
Como componentes funcionais são apenas funções, eles são muito mais leves para renderizar.
Eles também são muito úteis como componentes encapsuladores. Por exemplo, quando você precisa:
- Programaticamente escolher um entre vários outros componentes para delegar
- Manipular children, props, ou data antes de passá-los a componentes filhos
Aqui está um exemplo de um componente smart-list
que delega para componentes mais específicos, dependendo das propriedades (props) passadas a ele:
var EmptyList = { /* ... */ }
var TableList = { /* ... */ }
var OrderedList = { /* ... */ }
var UnorderedList = { /* ... */ }
Vue.component('smart-list', {
functional: true,
props: {
items: {
type: Array,
required: true
},
isOrdered: Boolean
},
render: function (createElement, context) {
function appropriateListComponent () {
var items = context.props.items
if (items.length === 0) return EmptyList
if (typeof items[0] === 'object') return TableList
if (context.props.isOrdered) return OrderedList
return UnorderedList
}
return createElement(
appropriateListComponent(),
context.data,
context.children
)
}
})
Passando Atributos e Eventos para Elementos/Componentes Filhos
Em componentes normais, atributos não definidos como propriedades são automaticamente adicionados ao elemento raiz do componente, substituindo ou mesclando de forma inteligente qualquer atributo existente com o mesmo nome.
Componentes funcionais, todavia, requerem que você defina explicitamente esse comportamento:
Vue.component('my-functional-button', {
functional: true,
render: function (createElement, context) {
// Passe, de forma transparente, qualquer atributo, escutas de evento, filhos, etc.
return createElement('button', context.data, context.children)
}
})
Passando context.data
como segundo argumento para createElement
, nós passamos adiante quaisquer atributos ou escutas de eventos usando no my-functional-button
. É tão transparente, de fato, que os eventos não precisam sequer o modificar .native
.
Se você estiver usando componentes funcionais baseados em templates, também terá de manualmente adicionar atributos e escutas a eventos. Uma vez que você tem acesso ao conteúdo individual do contexto, podemos usar data.attrs
para passar adiante quaisquer atributos HTML e listeners
(apelido para data.on
) para passar adiante quaisquer escutas a eventos.
<template functional>
<button
class="btn btn-primary"
v-bind="data.attrs"
v-on="listeners"
>
<slot/>
</button>
</template>
slots()
vs children
Você pode se perguntar por que nós precisamos de ambos - slots()
e children
. Não seria slots().default
o mesmo que children
? Em alguns casos, sim - mas o que aconteceria se você tivesse um componente funcional contendo os seguintes elementos filhos?
<meu-componente-funcional>
<p v-slot:foo>
primeiro
</p>
<p>segundo</p>
</meu-componente-funcional>
Para este componente, children
lhe fornecerá ambos os parágrafos, enquanto slots().default
lhe fornecerá apenas o segundo, e slots().foo
lhe fornecerá apenas o primeiro. Tendo tanto children
quanto slots()
lhe permite escolher se este componente precisa saber sobre os slots ou talvez delegar tal responsabilidade para outro componente passando adiante children
.
Compilação de Templates
Você pode estar interessado em saber que templates do Vue são compilados para funções render
. Este é um detalhe de implementação que você não necessita saber, mas se você quiser ver como templates específicos ficam quando compilados, pode ser interessante. Abaixo uma pequena demonstração usando Vue.compile
para compilar ao vivo uma String de template: