Vue.js


¿Qué es Vue.js?

Vue.js es un framework progresivo de JavaScript para construir interfaces de usuario. Creado por Evan You, Vue se caracteriza por ser fácil de aprender, flexible y escalable, permitiendo adoptarlo gradualmente en proyectos existentes o usarlo para aplicaciones completas.

¿Para qué sirve Vue.js?

Vue.js es ideal para:

  • Interfaces de usuario interactivas y dinámicas.
  • Single Page Applications (SPAs) modernas.
  • Aplicaciones web progresivas (PWAs).
  • Dashboards y paneles de administración.
  • E-commerce y aplicaciones empresariales.
  • Migración gradual desde jQuery o JavaScript vanilla.

¿Cómo funciona?

Vue utiliza un sistema reactivo que actualiza automáticamente la interfaz cuando cambian los datos. Imagina Vue como un asistente inteligente que observa tus datos y actualiza la pantalla instantáneamente cuando algo cambia, sin que tengas que decirle específicamente qué hacer.

Ejemplo: Componente básico en Vue

Vue combina HTML, CSS y JavaScript en componentes de una sola archivo (SFC):

<!-- TodoApp.vue -->
<template>
  <div class="todo-app">
    <h1>{{ title }}</h1>

    <form @submit.prevent="addTodo">
      <input v-model="newTodo" placeholder="¿Qué necesitas hacer?" required />
      <button type="submit">Agregar</button>
    </form>

    <ul class="todo-list">
      <li v-for="todo in filteredTodos" :key="todo.id" :class="{ completed: todo.completed }">
        <input type="checkbox" v-model="todo.completed" />
        <span>{{ todo.text }}</span>
        <button @click="removeTodo(todo.id)">❌</button>
      </li>
    </ul>

    <div class="filters">
      <button
        v-for="filter in filters"
        :key="filter"
        @click="currentFilter = filter"
        :class="{ active: currentFilter === filter }"
      >
        {{ filter }}
      </button>
    </div>

    <p>{{ remainingTodos }} tareas pendientes</p>
  </div>
</template>

<script>
export default {
  name: 'TodoApp',
  data() {
    return {
      title: 'Mi Lista de Tareas',
      newTodo: '',
      currentFilter: 'Todas',
      filters: ['Todas', 'Pendientes', 'Completadas'],
      todos: [
        { id: 1, text: 'Aprender Vue.js', completed: false },
        { id: 2, text: 'Construir una app', completed: true },
      ],
    };
  },
  computed: {
    filteredTodos() {
      switch (this.currentFilter) {
        case 'Pendientes':
          return this.todos.filter((todo) => !todo.completed);
        case 'Completadas':
          return this.todos.filter((todo) => todo.completed);
        default:
          return this.todos;
      }
    },
    remainingTodos() {
      return this.todos.filter((todo) => !todo.completed).length;
    },
  },
  methods: {
    addTodo() {
      if (this.newTodo.trim()) {
        this.todos.push({
          id: Date.now(),
          text: this.newTodo.trim(),
          completed: false,
        });
        this.newTodo = '';
      }
    },
    removeTodo(id) {
      this.todos = this.todos.filter((todo) => todo.id !== id);
    },
  },
};
</script>

<style scoped>
.todo-app {
  max-width: 500px;
  margin: 0 auto;
  padding: 20px;
}

.todo-list {
  list-style: none;
  padding: 0;
}

.todo-list li {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px;
  border-bottom: 1px solid #eee;
}

.completed span {
  text-decoration: line-through;
  opacity: 0.6;
}

.filters button {
  margin: 5px;
  padding: 5px 10px;
  border: 1px solid #ddd;
  background: white;
  cursor: pointer;
}

.filters button.active {
  background: #42b883;
  color: white;
}
</style>

Composition API (Vue 3)

Vue 3 introdujo la Composition API para mejor organización del código:

<template>
  <div>
    <h2>Contador: {{ count }}</h2>
    <button @click="increment">+1</button>
    <button @click="decrement">-1</button>
    <button @click="reset">Reset</button>

    <h3>Usuarios</h3>
    <div v-if="loading">Cargando...</div>
    <ul v-else>
      <li v-for="user in users" :key="user.id">{{ user.name }} - {{ user.email }}</li>
    </ul>
  </div>
</template>

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

// Estado reactivo
const count = ref(0);
const users = ref([]);
const loading = ref(true);

// Computed properties
const isEven = computed(() => count.value % 2 === 0);

// Métodos
const increment = () => count.value++;
const decrement = () => count.value--;
const reset = () => (count.value = 0);

// Ciclo de vida
onMounted(async () => {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/users');
    users.value = await response.json();
  } catch (error) {
    console.error('Error cargando usuarios:', error);
  } finally {
    loading.value = false;
  }
});
</script>

Directivas de Vue

Vue incluye directivas poderosas para manipular el DOM:

<template>
  <div>
    <!-- Renderizado condicional -->
    <p v-if="isLoggedIn">¡Bienvenido, {{ username }}!</p>
    <p v-else>Por favor, inicia sesión</p>

    <!-- Mostrar/ocultar (mantiene en DOM) -->
    <div v-show="showDetails">Detalles del usuario...</div>

    <!-- Loops -->
    <ul>
      <li v-for="(item, index) in items" :key="item.id">{{ index + 1 }}. {{ item.name }}</li>
    </ul>

    <!-- Binding de atributos -->
    <img :src="imageUrl" :alt="imageAlt" />
    <button :disabled="isLoading">{{ buttonText }}</button>

    <!-- Clases dinámicas -->
    <div
      :class="{
        active: isActive,
        disabled: isDisabled,
        'has-error': hasError,
      }"
    >
      Estado dinámico
    </div>

    <!-- Estilos dinámicos -->
    <div
      :style="{
        color: textColor,
        fontSize: fontSize + 'px',
        backgroundColor: bgColor,
      }"
    >
      Estilos dinámicos
    </div>

    <!-- Event listeners -->
    <button @click="handleClick">Clic simple</button>
    <button @click.prevent="handleSubmit">Prevenir default</button>
    <input @keyup.enter="handleEnter" />

    <!-- Two-way binding -->
    <input v-model="message" placeholder="Escribe algo..." />
    <p>Mensaje: {{ message }}</p>
  </div>
</template>

Comunicación entre componentes

Padre a Hijo (Props):

<!-- ParentComponent.vue -->
<template>
  <ChildComponent :title="pageTitle" :user="currentUser" :is-admin="userIsAdmin" />
</template>

<!-- ChildComponent.vue -->
<script setup>
const props = defineProps({
  title: String,
  user: Object,
  isAdmin: {
    type: Boolean,
    default: false,
  },
});
</script>

Hijo a Padre (Events):

<!-- ChildComponent.vue -->
<template>
  <button @click="notifyParent">Enviar al padre</button>
</template>

<script setup>
const emit = defineEmits(['user-action', 'data-updated']);

const notifyParent = () => {
  emit('user-action', {
    action: 'button-click',
    timestamp: Date.now(),
  });
};
</script>

<!-- ParentComponent.vue -->
<template>
  <ChildComponent @user-action="handleUserAction" />
</template>

Estado global con Pinia

Para aplicaciones más grandes, Vue usa Pinia para gestión de estado:

// stores/user.js
import { defineStore } from 'pinia';

export const useUserStore = defineStore('user', {
  state: () => ({
    currentUser: null,
    isAuthenticated: false,
    preferences: {
      theme: 'light',
      language: 'es',
    },
  }),

  getters: {
    fullName: (state) => {
      return state.currentUser
        ? `${state.currentUser.firstName} ${state.currentUser.lastName}`
        : 'Invitado';
    },
    isAdmin: (state) => {
      return state.currentUser?.role === 'admin';
    },
  },

  actions: {
    async login(credentials) {
      try {
        const response = await api.login(credentials);
        this.currentUser = response.user;
        this.isAuthenticated = true;
      } catch (error) {
        throw new Error('Error al iniciar sesión');
      }
    },

    logout() {
      this.currentUser = null;
      this.isAuthenticated = false;
    },

    updatePreferences(newPreferences) {
      this.preferences = { ...this.preferences, ...newPreferences };
    },
  },
});
<!-- En un componente -->
<template>
  <div>
    <h1>Hola, {{ userStore.fullName }}</h1>
    <button v-if="!userStore.isAuthenticated" @click="showLogin">Iniciar Sesión</button>
    <button v-else @click="userStore.logout()">Cerrar Sesión</button>
  </div>
</template>

<script setup>
import { useUserStore } from '@/stores/user';

const userStore = useUserStore();
</script>

Características principales

  • Reactive System: Actualización automática de la UI cuando cambian los datos.
  • Component-Based: Arquitectura basada en componentes reutilizables.
  • Single File Components: HTML, CSS y JS en un solo archivo.
  • Virtual DOM: Renderizado eficiente con reconciliación mínima.
  • Progressive: Se puede adoptar gradualmente en proyectos existentes.
  • Flexible: Múltiples formas de escribir componentes (Options API, Composition API).

Ecosistema Vue

// Principales librerías del ecosistema
const vueEcosystem = {
  // Routing
  'Vue Router': 'Enrutamiento para SPAs',

  // Estado global
  Pinia: 'Gestión de estado (reemplaza Vuex)',

  // Build tools
  Vite: 'Build tool ultra-rápido',
  'Vue CLI': 'Herramienta de línea de comandos',

  // UI Libraries
  Vuetify: 'Material Design components',
  Quasar: 'Framework completo con muchos componentes',
  'Element Plus': 'Componentes para aplicaciones empresariales',

  // Testing
  'Vue Test Utils': 'Utilidades para testing de componentes',

  // Meta-frameworks
  'Nuxt.js': 'Framework full-stack para Vue',
};

¿Cuándo usar Vue.js?

  • Proyectos nuevos que necesitan desarrollo rápido.
  • Migración gradual desde jQuery o JavaScript vanilla.
  • Equipos mixtos con diferentes niveles de experiencia.
  • Aplicaciones empresariales que requieren mantenibilidad.
  • Prototipado rápido y MVPs.
  • Cuando necesitas curva de aprendizaje suave.

Comparación con otros frameworks

CaracterísticaVue.jsReactAngular
Curva de aprendizajeSuaveMediaPronunciada
Tamaño de bundlePequeñoPequeñoGrande
PerformanceExcelenteExcelenteMuy buena
EcosistemaCompletoExtensoCompleto
Adopción empresarialAltaMuy altaMuy alta
FlexibilidadAltaAltaMedia
TypeScriptSoporte nativoExcelenteNativo

Ventajas de Vue.js

✅ Fácil de aprender: Sintaxis intuitiva y documentación excelente. ✅ Flexible: Múltiples formas de organizar y escribir código. ✅ Performance: Virtual DOM optimizado y reactivity eficiente. ✅ Ecosistema maduro: Librerías oficiales y comunidad activa. ✅ Progressive: Se puede integrar gradualmente. ✅ Single File Components: Organización clara y mantenible.

Configuración básica

// main.js
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import { createPinia } from 'pinia';

const app = createApp(App);

app.use(router);
app.use(createPinia());

app.mount('#app');

Conclusión

Vue.js es el framework perfecto para desarrolladores que buscan productividad sin sacrificar flexibilidad. Su filosofía progresiva y su curva de aprendizaje suave lo hacen ideal tanto para equipos experimentados como para quienes están empezando con frameworks modernos.

Con su sistema reactivo intuitivo, componentes bien organizados y ecosistema maduro, Vue.js te permite construir desde interfaces simples hasta aplicaciones empresariales complejas, manteniendo siempre el código limpio y mantenible.