Corrección del error de transmisión de valor incremental en componentes Vue

El objetivo era tranmsitir un valor después de un inicio de sesión exitoso para determinar si el usuario inició sesión con un teléfono o correo electrónico, y así vincular la cuenta correspondiente. Sin embargo, al pasar el valor del campo de entrada, se encontró un problema: la primera vez se transmitían 0 veces, la segunda 1 vez, la tercera 2 veces, y así sucesivamente.

Después de consultarlo, se determinó la causa: los oyentes de eventos no se eliminan cuando la página se destruye, lo que requiere una cancelación manual. A continuación se muestra el código inicial problemático y su corrección.

Código Inicial del Componente de Inicio de Sesión (Problemático)

<template>
  <div>
    <el-form class="login-form" :rules="reglas" ref="formulario" :model="credenciales">
      <el-form-item prop="usuario">
        <el-input
          v-model="credenciales.usuario"
          placeholder="Ingrese su correo o teléfono"
          type="text"
        />
      </el-form-item>
      <el-form-item prop="contrasena">
        <el-input
          v-model="credenciales.contrasena"
          type="password"
          placeholder="Ingrese su contraseña"
          show-password
        />
      </el-form-item>
      <el-button
        :loading="cargando"
        type="primary"
        @click="procesarSesion()"
      >Iniciar Sesión</el-button>
    </el-form>
  </div>
</template>

<script>
import configuracion from '@/config'
import { validarCorreo, validarTelefono } from './validaciones'

export default {
  data() {
    return {
      cargando: false,
      credenciales: {
        usuario: '',
        contrasena: ''
      }
    };
  },
  destroyed() {
    // Esto se ejecuta siempre, causando transmisiones no deseadas
    this.$bus.$emit("datosUsuario", this.credenciales.usuario);
  },
  methods: {
    procesarSesion() {
      this.cargando = true;
      this.$axios.post(`${configuracion.API_URL}/iniciar-sesion`, {
        identificador: this.credenciales.usuario,
        clave: this.credenciales.contrasena
      }).then(respuesta => {
        if (respuesta.data.estado === 200) {
          this.$message.success("Sesión iniciada");
          setTimeout(() => {
            this.$router.push("/perfil/vinculacion");
          }, 1000);
        }
      }).catch(() => {
        this.$message.error("Error en el servidor");
      }).finally(() => {
        this.cargando = false;
      });
    }
  }
};
</script>

Código del Componente Receptor (Problemático)

<template>
  <div class="contenedor-vinculacion">
    <router-view />
  </div>
</template>

<script>
export default {
  data() {
    return {
      valorVinculacion: ""
    }
  },
  created() {
    // El oyente se registra pero nunca se elimina formalmente
    this.$bus.$on("datosUsuario", valor => {
      this.valorVinculacion = valor;
    });
  }
  // No hay lógica de limpieza en el ciclo de vida
}
</script>

Diagnóstico y Corrección

El problema radicaba en dos aspectos. Primero, el evento datosUsuario se emitía en el hook destroyed del componente de login, independientemente del éxito de la operación. Segundo, el componente receptor nunca llamaba a $off para desuscribirse, provocando que los oyentes se acumularan con cada montaje del componente de login.

La solución implicó dos cambios clave: restringir la emisión del evento únicamente a una autenticación exitosa y gestionar explícitamente el ciclo de vida del oyente.

Solución Final - Componente de Inicio de Sesión (Corregido)

<template>
  <!-- El template permanece igual -->
</template>

<script>
import configuracion from '@/config'
import bus from '@/config/eventBus'
import { validarCorreo, validarTelefono } from './validaciones'

export default {
  data() {
    return {
      cargando: false,
      credenciales: {
        usuario: '',
        contrasena: ''
      },
      usuarioExitoso: '' // Almacena el usuario solo si el login fue exitoso
    };
  },
  destroyed() {
    // Emite el evento únicamente si existe un usuario validado
    if (this.usuarioExitoso) {
      bus.$emit("sesionExitosa", this.usuarioExitoso);
    }
  },
  methods: {
    procesarSesion() {
      this.cargando = true;
      this.$axios.post(`${configuracion.API_URL}/iniciar-sesion`, {
        identificador: this.credenciales.usuario,
        clave: this.credenciales.contrasena
      }).then(respuesta => {
        if (respuesta.data.estado === 200) {
          this.usuarioExitoso = this.credenciales.usuario; // Guarda el valor en caso de éxito
          this.$message.success("Sesión iniciada");
          setTimeout(() => {
            this.$router.push("/perfil/vinculacion");
          }, 1000);
        } else {
          this.usuarioExitoso = ''; // Limpia si hay otro estado de error
          this.$message.error(respuesta.data.mensaje);
        }
      }).catch(() => {
        this.usuarioExitoso = '';
        this.$message.error("Error en el servidor");
      }).finally(() => {
        this.cargando = false;
      });
    }
  }
};
</script>

Solución Final - Componente Receptor (Corregido)

<template>
  <div class="contenedor-vinculacion">
    <router-view />
  </div>
</template>

<script>
import bus from '@/config/eventBus'

export default {
  data() {
    return {
      valorVinculacion: ""
    }
  },
  created() {
    // Escucha el evento con un nombre específico
    this.oyenteSesion = (valor) => {
      console.log("Datos recibidos:", valor);
      this.valorVinculacion = valor;
      // Lógica adicional para determinar tipo (email/teléfono)
    };
    bus.$on("sesionExitosa", this.oyenteSesion);
  },
  destroyed() {
    // Elimina explícitamente el oyente al destruir el componente
    if (this.oyenteSesion) {
      bus.$off("sesionExitosa", this.oyenteSesion);
    }
  }
}
</script>

Almacenando la función oyente en una propiedad de la instancia (this.oyenteSesion) y utilizando $off en el hook destroyed, se garantiza que cada instancia del componente receptor maneje un único oyente limpio. Combinado con la condición de emisión basada en el éxito, el flujo de datos se vuelve predecible y correcto.

Etiquetas: vue.js EventBus ComponentCommunication VueLifecycle bugfix

Publicado el 6-8 20:35