¿Alguna vez te has preguntado cómo la comunicación entre componentes hijos y padres en Vue puede ser simplificada y eficaz? Aquí es donde entran en juego los EMITS. A través de los EMITS, Vue proporciona una manera de enviar eventos personalizados desde un componente hijo a uno padre. Es decir, puedes definir eventos propios de un componente que se activan bajo ciertas condiciones específicas. Aunque podrían implementarse como props, usar EMITS ofrece ventajas significativas al clarificar y estructurar mejor el código.
¿Cómo implementar un EMIT en Vue?
Imagina que tienes una barra de búsqueda que debe comunicar cambios al componente padre. Para lograr que funcione sin problemas, seguirás estos pasos:
Define Props: Primero, necesitas las propiedades. Por ejemplo, en un componente game layout, solicita los juegos a través de defineProps.
Referencia de Datos Originales: Siempre es útil tener una copia original de los datos a manipular, especialmente para gestionar las mutaciones de una lista como juegos.
const gamesView =ref(state.games);
EMIT personalizado: Declara un EMIT, como setGameView, que permitirá modificar desde el componente padre.
const emit =defineEmits(['setGameView']);
Usa el EMIT en la acción correspondiente: Cuando la acción se dispara (por ejemplo, una búsqueda), usa el EMIT para enviar datos al componente padre.
emit('setGameView', filteredGames);
¿Cómo mejorar la funcionalidad de la barra de búsqueda?
El objetivo es lograr que tu barra de búsqueda interactúe perfectamente con la lista de games, ajustando el comportamiento según la entrada del usuario.
Función de Búsqueda: Implementa una función que filtre los games basado en una entrada.
Implementando características como filtros y eventos, pueden surgir varios inconvenientes. Aquí algunos consejos para gestionar estos desafíos:
Chequea las Reglas de Mutación: Las props no deben ser mutadas directamente ya que Vue prohibe esto en aras de mantener la unidireccionalidad de los datos.
Casting de Variables: Asegúrate de que las variables sean interpretadas correctamente, ya que un tipo incorrecto puede resultar en errores de tipo no manejados.
Retos y mejoras sugeridas
La implementación de un EMIT básico es solo el comienzo. Aquí algunas ideas para refinar y extender tu proyecto:
Implementar Observadores (Watchers): Para automatizar el restablecimiento de la vista completa de games, podrías usar un watcher que escuche cambios en el input de búsqueda.
Añadir un Botón de Reseteo: Considera incluir un botón que solo aparezca cuando se haya realizado una búsqueda, permitiendo al usuario restablecer la lista a su estado original con un clic.
Simplificación Algorítmica: Mejorar el algoritmo de búsqueda para buscar de manera más eficiente y sin problemas de lógica.
Experimenta y personaliza estos elementos; la práctica te acercará a soluciones más óptimas y robustas. Tu aplicación será cada vez más dinámica, lo que hará que tanto tú como los usuarios tengan una experiencia enriquecedora. Si te encuentras con dudas durante este proceso, recuerda que el conocimiento se profundiza cuando se enfrenta con retos y errores. ¡Sigue con entusiasmo tu camino en Vue!
Me parece que el ejemplo es algo confuso, pudo haber sido algo mas simple.
no entendi bien esta clase...
x2
Yo igual, deberian pensar en estructurar esta clase, un bloque de 22 minutos donde no se explica el porque las cosas si no que el profesor de dedicho a echar codigo nada mas.
yo implemente la busqueda utilizando computed, para que cuando el usuario ingrese texto ya realice la busqueda
Yo pense en lo mismo pero lo implemente diferente, como ya tenemos la referencia al valor del input usando el v-model en el componente padre (GameLayout) no es necesario que hagas el emit ya que estan conectados el ref searchInput con el valor del input del componente hijo SharedSearch
<script setup>import{ ref, computed }from'vue'// import IconSearch from '../Icons/IconSearch.vue'const model =defineModel()const isActive =ref(false)const searchClasses =computed(()=>({'search--active': isActive.value}))</script><template><div class="search":class="searchClasses"><input
v-model="model"class="search__input" type="text" placeholder="Buscar" @focus="isActive = true" @blur="isActive = false"/><!--<button class="search__submit" type="submit" v-bind="$attrs"><IconSearch/></button>--></div></template>```Te ahorras el submit del form y los eventos extra
Como tienes el ref del searchInput aplicas un watch que este mirando si el valor cambia y aplicas el filtro 
```js
<script setup>import{ ref, watch }from'vue'importSharedSearchfrom'../Shared/SharedSearch.vue'const emit =defineEmits(['setGameView'])const{ title, games }=defineProps({title:{type:String,default:'Recent games',},games:{type:Array,required:true,},})const searchInput =ref('')watch(searchInput,(newValue)=>{if(newValue.length===0){emit('setGameView', games)return}const filteredGames = games.filter((game)=> game.title.toLowerCase().includes(newValue.toLowerCase()),)emit('setGameView', filteredGames)})</script><template><section><h3>{{ title }}</h3><div class="game-layout"><SharedSearch v-model="searchInput"class="my-class" id="search-form"/><slot /></div></section></template>
De ese modo podría ser interesante, sin embargo, en los casos en donde la búsqueda depende de una petición a un servicio o "back-end", podría generar una sobrecarga de peticiones, puesto que cada vez que se hace input, se realizaría una solicitud.
Pero con el evento limitas a que esa petición se realice solo cuando el usuario está seguro de realizar el filtrado.
Una solución sencilla para implementar el filtrado es simplemente encadenar los eventos y aplicar el filtrado en el lugar correcto:
1. Una vez que el usuario da clic en buscar o pulsa 'Enter', SharedSearch emite el evento 'search'.
2. Su componente padre, GameLayout, escucha el evento. Pero en lugar de aplicar el filtrado, directamente emite el evento 'setGameView' pero enviando con éste el valor de searchInput.
3. El abuelo, App, escuchará el evento y es aquí donde sí se debe aplicar el filtrado sobre el arreglo original de los videojuegos, y el arreglo resultante será almacenado en gamesView.value.
El problema con el enfoque anterior era que estábamos aplicando los filtros sobre un arreglo de juegos previamente ya filtrado. Entonces, si escribíamos 'Halo' y luego hacíamos clic en buscar, obteníamos Halo, pero ahora Halo era el único elemento dentro del arreglo gamesView.value. Por consiguiente, si inmediatamente buscábamos 'Death', no obtendríamos Death Stranding, sino una página en blanco porque ahora el nuevo arreglo gamesView estaba vacío. Y, si volvías a buscar 'Halo', otra vez obtendrías una página en blanco porque esta vez habrías aplicado un filtro sobre un arreglo vacío.
Es muy importante entender por qué sucedía este bug: nos permite darnos cuenta de por qué mantener el arreglo original y también un arreglo adicional que sea el resultado de los filtros es esencial para obtener los resultados esperados.
Hola Chicos, esta son algunas de mis mejroas en las sugerencias del profesor para la mejor UX en la SearchBar para cmapos de busqueda.
./src/App.vue<script setup>
import LayoutHero from './components/Layout/LayoutHero.vue';
import GameCard from './components/Games/GameCard.vue';
import GameLayout from './components/Games/GameLayout.vue';
import { onMounted ,reactive, ref, computed, watch } from 'vue';
// Cuando el campo se vacía, el computed 'filteredGames' automáticamente
// vuelve a la lista completa. Aquí no necesitamos hacer nada más.
console.log('Término de búsqueda vacío. La vista se ha reseteado automáticamente.');
}
});
const isSearching = computed(() => {
return String(searchTerm.value).trim() !== '';
});
const handleReset = () => {
searchTerm.value = '';
}
</script>
<script setup>importLayoutHerofrom'./components/Layout/LayoutHero.vue';importGameCardfrom'./components/Games/GameCard.vue';importGameLayoutfrom'./components/Games/GameLayout.vue';import{ onMounted ,reactive, ref, computed, watch }from'vue';constAPI_URL='https://gamestreamapi.herokuapp.com/api/games';// 1. Estado para datos y cargaconst rxState =reactive({error:null,isLoading:false,data:[]// Datos originales de la API});// 2. Estado para el término de búsqueda (nueva)const searchTerm =ref('');constfetchData=async()=>{try{ rxState.isLoading=true;const response =awaitfetch(API_URL);constGameData=await response.json(); rxState.data=GameData;}catch(error){console.error('Error fetching data:', error); rxState.error= error;}finally{ rxState.isLoading=false;}};onMounted(()=>{fetchData();});// 3. Propiedad Computada: Lógica de filtrado centralizadaconst filteredGames =computed(()=>{const term =String(searchTerm.value).toLocaleLowerCase().trim();const allGames = rxState.data;// Si no hay término de búsqueda, devuelve todos los juegosif(!term){return allGames;}// Filtra los juegosconst results = allGames.filter((game)=>{const gameTitle =String(game.title||'').toLocaleLowerCase();return gameTitle.includes(term);});return results;});// 4. Manejador del evento de búsquedaconsthandleSearch=(newSearchTerm)=>{ searchTerm.value= newSearchTerm;};watch(searchTerm,(newValue)=>{if(String(newValue).trim()===''){// Cuando el campo se vacía, el computed 'filteredGames' automáticamente// vuelve a la lista completa. ...console.log('Término de búsqueda vacío. La vista se ha reseteado automáticamente.');}});const isSearching =computed(()=>{returnString(searchTerm.value).trim()!=='';});consthandleReset=()=>{ searchTerm.value='';}</script>
Este es <templates>
<template><LayoutHero/><GameLayout:games="filteredGames" @search="handleSearch" @reset="handleReset":isSearching="isSearching"><template #title><h3 style="text-align: center; margin-top: 1rem;">Welcome to GameStream</h3><p style="text-align: center; margin-bottom: 1rem;">Your ultimate destination for streaming and discovering the latest and greatest games.</p></template><div v-if="rxState.isLoading"class="status-message"><p>LoadingGames fro mAPI...PleaseWait.</p></div><div v-else-if="rxState.error"class="status-message error"><p>¡Error al cargar los datos!</p><p>Detalle:{{ rxState.error.message||'Error desconocido'}}</p></div><template v-else><GameCard v-for="game in filteredGames":key="game.title":game="game"/><p v-if="!isSearching && filteredGames.length === 0" style="text-align: center;">There is no Games...</p><p v-else-if="isSearching && filteredGames.length === 0" style="text-align: center;">No se encontraron juegos para {{ searchTerm }}.</p></template></GameLayout></template><style scoped>main {padding: 2rem;}</style>