Migrando do Vue 2 para o Vue 3: Diferenças Práticas no Desenvolvimento de Componentes

Estrutura do Template e Fragments

No Vue 3, a introdução dos Fragments permite que um componente possua múltiplos nós raiz, eliminando a necessidade de envolver todo o conteúdo em uma div extra. Embora esta seja uma mudança estrutural importante, a sintaxe básica do template permanece bastante semelhante entre as versões. Nos exemplos a seguir, utilizaremos um nó raiz único para focar nas diferenças de lógica e gerenciamento de estado.

Gerenciamento de Estado e Dados

A distinção fundamental entre as duas versões é a mudança da Options API (Vue 2) para a Composition API (Vue 3). Na Options API, os dados são declarados dentro de um objeto retornado pela função data(). Na Composition API, utilizamos a função setup() e a API de reatividade para criar estados.

Abordagem Vue 2 (Options API):

export default {
  props: {
    formTitle: String
  },
  data() {
    return {
      taskName: '',
      taskPriority: 'low'
    }
  }
}

Abordagem Vue 3 (Composition API):

É necessário importar reactive para agrupar as variáveis de estado e retorná-las através do setup().

import { reactive } from 'vue';

export default {
  props: {
    formTitle: String
  },
  setup() {
    const state = reactive({
      taskName: '',
      taskPriority: 'low'
    });

    return { state };
  }
}

Definição de Métodos

Na Options API, as funções são agrupadas na propriedade methods. Na Composition API, elas são declaradas como funções normais dentro do setup() e expostas via return.

Vue 2:

methods: {
  submitTask() {
    // Lógica de envio
  }
}

Vue 3:

setup() {
  // ... estado reativo

  const submitTask = () => {
    // Lógica de envio
  };

  return { state, submitTask };
}

Ciclo de Vida dos Componentes

Os hooks de ciclo de vida no Vue 2 são declarados como opções no nível raiz do componente. No Vue 3, eles devem ser importados explicitamente e chamados dentro da função setup().

Vue 2:

mounted() {
  console.log('Componente montado com sucesso');
}

Vue 3:

import { onMounted } from 'vue';

setup() {
  onMounted(() => {
    console.log('Componente montado com sucesso');
  });
}

Propriedades Computadas

As propriedades computadas seguem a mesma lógica de importação sob demanda no Vue 3, otimizando o tamanho final do pacote através de tree-shaking.

Vue 2:

computed: {
  upperCaseTask() {
    return this.taskName.toUpperCase();
  }
}

Vue 3:

import { reactive, computed } from 'vue';

setup() {
  const state = reactive({
    taskName: '',
    taskPriority: 'low',
    upperCaseTask: computed(() => state.taskName.toUpperCase())
  });

  return { state };
}

Recepção de Props e Emissão de Eventos

No Vue 2, a instância do componente é acessada via this, permitindo a leitura de props e a emissão de eventos com this.$emit. No Vue 3, o contexto this não está disponível no setup(). Em vez disso, as props são recebidas como o primeiro argumento e o contexto (que contém emit) como o segundo.

Vue 2:

mounted() {
  console.log(this.formTitle);
},
methods: {
  submitTask() {
    this.$emit('task-added', { name: this.taskName });
  }
}

Vue 3:

setup(props, { emit }) {
  onMounted(() => {
    console.log(props.formTitle);
  });

  const submitTask = () => {
    emit('task-added', { name: state.taskName });
  };
  
  return { submitTask };
}

Implementação Completa: Vue 2 vs Vue 3

Abaixo está a comparação completa de um componente de formulário de tarefas utilizando ambas as abordagens.

Componente Vue 2

<template>
  <div class="task-form">
    <h2>{{ formTitle }}</h2>
    <input type="text" v-model="taskName" placeholder="Nome da Tarefa" />
    <select v-model="taskPriority">
      <option value="low">Baixa</option>
      <option value="high">Alta</option>
    </select>
    <button @click="submitTask">Adicionar</button>
    <p>Resumo: {{ upperCaseTask }} - {{ taskPriority }}</p>
  </div>
</template>

<script>
export default {
  props: {
    formTitle: String
  },
  data() {
    return {
      taskName: '',
      taskPriority: 'low'
    };
  },
  mounted() {
    console.log('Título do formulário: ' + this.formTitle);
  },
  computed: {
    upperCaseTask() {
      return this.taskName.toUpperCase();
    }
  },
  methods: {
    submitTask() {
      this.$emit('task-added', {
        name: this.taskName,
        priority: this.taskPriority
      });
    }
  }
};
</script>

Componente Vue 3

<template>
  <div class="task-form">
    <h2>{{ formTitle }}</h2>
    <input type="text" v-model="state.taskName" placeholder="Nome da Tarefa" />
    <select v-model="state.taskPriority">
      <option value="low">Baixa</option>
      <option value="high">Alta</option>
    </select>
    <button @click="submitTask">Adicionar</button>
    <p>Resumo: {{ state.upperCaseTask }} - {{ state.taskPriority }}</p>
  </div>
</template>

<script>
import { reactive, onMounted, computed } from 'vue';

export default {
  props: {
    formTitle: String
  },
  setup(props, { emit }) {
    const state = reactive({
      taskName: '',
      taskPriority: 'low',
      upperCaseTask: computed(() => state.taskName.toUpperCase())
    });

    onMounted(() => {
      console.log('Título do formulário: ' + props.formTitle);
    });

    const submitTask = () => {
      emit('task-added', {
        name: state.taskName,
        priority: state.taskPriority
      });
    };

    return {
      state,
      submitTask
    };
  }
};
</script>

Tags: vue2 vue3 composition-api options-api reactivity-api

Publicado em 7-14 09:36