# <script setup> do SFC
<script setup> é um açúcar sintático em tempo de compilação para usar API de Composição dentro de Componentes Single-File (SFCs). É a sintaxe recomendada se você estiver usando SFCs e API de Composição. Ele fornece uma série de vantagens sobre a sintaxe normal do <script>:
- Código mais sucinto com menos boilerplate
- Capacidade de declarar props e eventos emitidos usando TypeScript puro
- Melhor desempenho em tempo de execução (o template é compilado em uma função de renderização no mesmo escopo, sem um proxy intermediário)
- Melhor desempenho de inferência de tipo na IDE (menos trabalho para o servidor de linguagem extrair tipos de código)
# Sintaxe Básica
Para ativar a sintaxe, adicione o atributo setup ao bloco <script>:
<script setup>
console.log('olá script setup')
</script>
2
3
O código dentro é compilado como o conteúdo da função setup() do componente. Isso significa que, diferentemente do <script> normal, que é executado apenas uma vez quando o componente é importado pela primeira vez, o código dentro do <script setup> executará toda vez que uma instância do componente for criada.
# Vínculos de Nível Superior são Expostos ao Template
Ao usar <script setup>, quaisquer vínculos de nível superior (incluindo variáveis, declarações de função e importações) declaradas dentro de <script setup> podem ser usadas diretamente no template:
<script setup>
// variável
const msg = 'Olá!'
// funções
function log() {
console.log(msg)
}
</script>
<template>
<div @click="log">{{ msg }}</div>
</template>
2
3
4
5
6
7
8
9
10
11
12
13
As importações são expostas da mesma forma. Isso significa que você pode usar diretamente uma função auxiliar importada em expressões de template sem ter que expô-la através da opção methods:
<script setup>
import { capitalize } from './helpers'
</script>
<template>
<div>{{ capitalize('olá') }}</div>
</template>
2
3
4
5
6
7
# Reatividade
O estado reativo precisa ser criado explicitamente usando APIs de Reatividade. Semelhante aos valores retornados de uma função setup(), refs são automaticamente desempacotados quando referenciados em templates:
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<button @click="count++">{{ count }}</button>
</template>
2
3
4
5
6
7
8
9
# Usando Componentes
Os valores no escopo de <script setup> também podem ser usados diretamente como nomes de tags de componentes personalizados:
<script setup>
import MyComponent from './MyComponent.vue'
</script>
<template>
<MyComponent />
</template>
2
3
4
5
6
7
Pense em MyComponent como sendo referenciado como uma variável. Se você usou JSX, o modelo mental é semelhante aqui. O equivalente kebab-case <my-component> também funciona no template - no entanto, as tags do componente PascalCase são fortemente recomendadas para consistência. Também ajuda a diferenciar dos elementos personalizados nativos.
# Componentes Dinâmicos
Como os componentes são referenciados como variáveis em vez de registrados em chaves string, você deve usar a vinculação dinâmica :is ao usar componentes dinâmicos dentro de <script setup>:
<script setup>
import Foo from './Foo.vue'
import Bar from './Bar.vue'
</script>
<template>
<component :is="Foo" />
<component :is="someCondition ? Foo : Bar" />
</template>
2
3
4
5
6
7
8
9
Observe como os componentes podem ser usados como variáveis em uma expressão ternária.
# Componentes Recursivos
Um SFC pode se referir implicitamente a si mesmo por meio de seu nome de arquivo. Por exemplo, um arquivo chamado FooBar.vue pode se referir a si mesmo como <FooBar/> em seu template.
Observe que isso tem prioridade menor do que os componentes importados. Se você tiver uma importação nomeada que conflite com o nome inferido do componente, você pode usar o alias da importação:
import { FooBar as FooBarChild } from './components'
# Componentes com Namespace
Você pode usar tags de componentes com pontos como <Foo.Bar> para se referir aos componentes aninhados nas propriedades do objeto. Isso é útil quando você importa vários componentes de um único arquivo:
<script setup>
import * as Form from './form-components'
</script>
<template>
<Form.Input>
<Form.Label>rótulo</Form.Label>
</Form.Input>
</template>
2
3
4
5
6
7
8
9
# Usando Diretivas Customizadas
As diretivas customizadas registradas globalmente funcionam como esperado, e as locais podem ser usadas diretamente no template, assim como explicamos acima para componentes.
Mas há uma restrição a ser observada: Você deve nomear as diretivas customizadas locais de acordo com o seguinte esquema: vNameOfDirective para que elas possam ser usadas diretamente no template.
<script setup>
const vMyDirective = {
beforeMount: (el) => {
// faz algo com o elemento
}
}
</script>
<template>
<h1 v-my-directive>Este é um Título</h1>
</template>
2
3
4
5
6
7
8
9
10
<script setup>
// importações também funcionam e podem ser nomeadas com o esquema necessário
import { myDirective as vMyDirective } from './MyDirective.js'
</script>
2
3
4
# defineProps e defineEmits
Para declarar props e emits em <script setup>, você deve usar as APIs defineProps e defineEmits, que fornecem suporte completo para inferência de tipos e estão automaticamente disponíveis dentro de <script setup>:
<script setup>
const props = defineProps({
foo: String
})
const emit = defineEmits(['change', 'delete'])
// código de configuração
</script>
2
3
4
5
6
7
8
definePropsedefineEmitssão macros de compilador utilizáveis apenas dentro de<script setup>. Eles não precisam ser importados e são compilados quando<script setup>é processado.definePropsaceita o mesmo valor que a opçãoprops, enquantodefineEmitsaceita o mesmo valor que a opçãoemits.definePropsedefineEmitsfornecem inferência de tipo adequada com base nas opções passadas.As opções passadas para
definePropsedefineEmitsserão içadas para fora do setup no escopo do módulo. Portanto, as opções não podem fazer referência a variáveis locais declaradas no escopo do setup. Fazer isso resultará em um erro de compilação. No entanto, pode fazer referência a vínculos importados, pois eles também estão no escopo do módulo.
Se você estiver usando TypeScript, também é possível declarar props e emits usando anotações de tipos puros.
# defineExpose
Componentes usando <script setup> são fechados por padrão - ou seja, a instância pública do componente, que é recuperada via refs de template ou correntes de $parent, não exporá nenhuma das vinculações declaradas dentro de <script setup>.
Para expor explicitamente as propriedades em um componente <script setup>, use o macro de compilação defineExpose:
<script setup>
import { ref } from 'vue'
const a = 1
const b = ref(2)
defineExpose({
a,
b
})
</script>
2
3
4
5
6
7
8
9
10
11
Quando um pai obtém uma instância deste componente via refs de template, a instância recuperada terá a forma { a: number, b: number } (refs são automaticamente desempacotadas como em instâncias normais).
# useSlots e useAttrs
O uso de slots e attrs dentro de <script setup> deve ser relativamente raro, já que você pode acessá-los diretamente como $slots e $attrs no template. No caso raro em que você precisar deles, use os auxiliares useSlots e useAttrs respectivamente:
<script setup>
import { useSlots, useAttrs } from 'vue'
const slots = useSlots()
const attrs = useAttrs()
</script>
2
3
4
5
6
useSlots e useAttrs são funções de tempo de execução reais que retornam o equivalente de setupContext.slots e setupContext.attrs. Eles também podem ser usados em funções de API de composição normal.
# Uso ao lado do <script> normal
<script setup> pode ser usado junto com o <script> normal. Um <script> normal pode ser necessário nos casos em que você precisa:
- Declarar opções que não podem ser expressas em
<script setup>, por exemploinheritAttrsou opções customizadas habilitadas por meio de plugins. - Declarar exportações nomeadas (incluindo tipos TypeScript).
- Executar efeitos colaterais ou crie objetos que devem ser executados apenas uma vez.
<script>
// normal <script>, executado no escopo do módulo (apenas uma vez)
runSideEffectOnce()
// declara opções adicionais
export default {
inheritAttrs: false,
customOptions: {}
}
</script>
<script setup>
// executado no escopo setup() (para cada instância)
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
Aviso
A função render não é suportada neste cenário. Por favor, use um <script> normal com a opção setup.
# await de nível superior
O await de nível superior pode ser usado dentro de <script setup>. O código resultante será compilado como async setup():
<script setup>
const post = await fetch(`/api/post/1`).then(r => r.json())
</script>
2
3
Além disso, a expressão aguardada será compilada automaticamente em um formato que preserva o contexto da instância do componente atual após o await.
Nota
async setup() deve ser usado em combinação com Suspense, que atualmente ainda é um recurso experimental. Planejamos finalizá-lo e documentá-lo em uma versão futura - mas se você estiver curioso agora, pode consultar seus testes (opens new window) para ver como funciona.
# Recursos somente do TypeScript
# Exportações de tipos adicionais
Como observado acima, para exportar tipos adicionais de um SFC, eles devem ser movidos para um bloco <script> adicional ao lado do bloco <script setup>.
Por exemplo
<script lang="ts">
export type SizeOptions = 'small' | 'medium' | 'large';
</script>
<script lang="ts" setup>
defineProps({
size: { type: String as PropType<SizeOptions> },
})
</script>
2
3
4
5
6
7
8
9
# Declarando somente o tipo de props/emits
Props e emits também podem ser declarados usando sintaxe de tipo puro passando um argumento de tipo literal para defineProps ou defineEmits:
const props = defineProps<{
foo: string
bar?: number
}>()
const emit = defineEmits<{
(e: 'change', id: number): void
(e: 'update', value: string): void
}>()
2
3
4
5
6
7
8
9
definePropsoudefineEmitssó podem usar declaração de tempo de execução OU declaração de tipo. Usar ambos ao mesmo tempo resultará em um erro de compilação.Ao usar a declaração de tipo, a declaração de tempo de execução equivalente é gerada automaticamente a partir da análise estática para eliminar a necessidade de declaração dupla e ainda garantir o comportamento correto em tempo de execução.
No modo dev, o compilador tentará inferir a validação em tempo de execução correspondente dos tipos. Por exemplo, aqui
foo: Stringé inferido do tipofoo: string. Se o tipo for uma referência a um tipo importado, o resultado inferido seráfoo: null(igual ao tipoany) já que o compilador não possui informações de arquivos externos.No modo de produção, o compilador gerará a declaração de formato de array para reduzir o tamanho do pacote (as props aqui serão compiladas em
['foo', 'bar'])O código emitido ainda é TypeScript com tipagem válida, podendo ser posteriormente processado por outras ferramentas.
A partir de agora, o argumento de declaração de tipo deve ser um dos seguintes para garantir a análise estática correta:
- Um literal de tipo
- Uma referência a uma interface ou um literal de tipo no mesmo arquivo
Atualmente, tipos complexos e importações de tipos de outros arquivos não são suportados. É teoricamente possível suportar importações de tipo no futuro.
# Valores padrão de props ao usar declaração de tipo
Uma desvantagem da declaração defineProps somente de tipo é que ela não tem uma maneira de fornecer valores padrão para as props. Para resolver este problema, uma macro do compilador withDefaults também é fornecida:
interface Props {
msg?: string
labels?: string[]
}
const props = withDefaults(defineProps<Props>(), {
msg: 'olá',
labels: () => ['um', 'dois']
})
2
3
4
5
6
7
8
9
Isso será compilado para as opções default de props equivalentes em tempo de execução. Além disso, o auxiliar withDefaults fornece verificações de tipo para os valores padrão e garante que o tipo props retornado tenha os sinalizadores opcionais removidos para propriedades que possuem valores padrão declarados.
# Restrição: Nenhuma Importação Src
Devido à diferença na semântica de execução do módulo, o código dentro de <script setup> depende do contexto de um SFC. Quando movido para arquivos externos .js ou .ts, pode causar confusão tanto para desenvolvedores quanto para ferramentas. Portanto, <script setup> não pode ser usado com o atributo src.