Tipando Eventos
Receta
Tipifica correctamente manejadores de eventos de React para clics, envíos de formularios, cambios de entrada, eventos de teclado y callbacks de eventos personalizados. Usa los tipos de eventos sintéticos de React para seguridad de tipos completa.
Ejemplo Funcional
// Evento de clic
function handleClick(event: React.MouseEvent<HTMLButtonElement>) {
console.log("Botón clickeado en", event.clientX, event.clientY);
}
// Evento de envío de formulario
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
const formData = new FormData(event.currentTarget);
const email = formData.get("email") as string;
console.log("Enviado:", email);
}
// Evento de cambio de entrada
function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
console.log("Valor:", event.target.value);
}
// Evento de teclado
function handleKeyDown(event: React.KeyboardEvent<HTMLInputElement>) {
if (event.key === "Enter") {
console.log("Intro presionado");
}
}// Componente de formulario completo
function LoginForm() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
console.log({ email, password });
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Escape") setEmail("");
}}
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button type="submit">Iniciar Sesión</button>
</form>
);
}Análisis Profundo
Cómo Funciona
- React envuelve eventos DOM nativos en objetos
SyntheticEvent. TypeScript proporciona tipos de eventos genéricos comoReact.MouseEvent<T>dondeTes el tipo del elemento HTML. event.targetse tipifica comoEventTarget, que es amplio. Usaevent.currentTargetpara el elemento al que se adjunta el manejador -- está tipificado como el parámetro genérico (HTMLButtonElement,HTMLInputElement, etc.).- Al escribir manejadores inline (
onChange={(e) => ...}), TypeScript infiere el tipo de evento automáticamente desde el atributo JSX. - Al extraer manejadores a funciones nombradas, debes anotar explícitamente el parámetro de evento.
Variaciones
Select y textarea:
function handleSelectChange(event: React.ChangeEvent<HTMLSelectElement>) {
console.log("Seleccionado:", event.target.value);
}
function handleTextareaChange(event: React.ChangeEvent<HTMLTextAreaElement>) {
console.log("Texto:", event.target.value);
}Eventos de arrastre:
function handleDragStart(event: React.DragEvent<HTMLDivElement>) {
event.dataTransfer.setData("text/plain", "dragged");
}Eventos de enfoque:
function handleFocus(event: React.FocusEvent<HTMLInputElement>) {
event.currentTarget.select();
}Props de callback de evento personalizado:
type SearchBarProps = {
onSearch: (query: string) => void;
onClear?: () => void;
};
function SearchBar({ onSearch, onClear }: SearchBarProps) {
const [query, setQuery] = useState("");
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
onSearch(query);
};
return (
<form onSubmit={handleSubmit}>
<input value={query} onChange={(e) => setQuery(e.target.value)} />
<button type="submit">Buscar</button>
{onClear && <button type="button" onClick={onClear}>Limpiar</button>}
</form>
);
}Notas de TypeScript
- Tipos de eventos comunes:
React.MouseEvent,React.ChangeEvent,React.FormEvent,React.KeyboardEvent,React.FocusEvent,React.DragEvent,React.TouchEvent,React.WheelEvent. - El parámetro genérico determina el tipo de
currentTarget. Por ejemplo,React.MouseEvent<HTMLAnchorElement>le da acurrentTargetel tipoHTMLAnchorElement. React.SyntheticEventes el tipo base para todos los eventos de React. Úsalo cuando no te importa el tipo de evento específico.
Trampas
event.targetvsevent.currentTarget:targetes el elemento que desencadenó el evento (podría ser un hijo), mientras quecurrentTargetes el elemento al que se adjunta el manejador. TypeScript tipificacurrentTargetmás precisamente.- Usar
event.target.valueen un evento de envío de formulario causará un error porqueEventTargetno tiene una propiedadvalue. Cástéalo o usacurrentTarget. - Los eventos sintéticos de React se agrupan en React 16 y anteriores. En React 17+, el pooling de eventos se ha eliminado, por lo que puedes acceder de forma segura a las propiedades de eventos de forma asincrónica.
- Olvidar
event.preventDefault()en el envío del formulario causa una recarga de página completa.
Alternativas
| Enfoque | Ventajas | Desventajas |
|---|---|---|
| Funciones flecha inline | TypeScript infiere el tipo de evento automáticamente | Crea una nueva función en cada renderizado |
| Funciones manejadoras nombradas | Reutilizables, probables, sin re-creación | Debe anotar manualmente el tipo de evento |
Tipo React.EventHandler<E> | Alias de tipo de manejador conciso | Menos común, ligeramente más difícil de leer |
Prop de callback (p. ej., onSearch: (q: string) => void) | Desacopla el padre de eventos DOM | El padre no tiene acceso al evento sin procesar |
Manejador envuelto useCallback | Referencia estable para hijos memoizados | Boilerplate adicional |
Preguntas Frecuentes
¿Cuál es la diferencia entre event.target y event.currentTarget?
event.targetes el elemento que desencadenó el evento (podría ser un elemento hijo).event.currentTargetes el elemento al que se adjunta el manejador.- TypeScript tipifica
currentTargetmás precisamente según el parámetro genérico.
¿Por qué TypeScript infiere tipos de eventos automáticamente para manejadores inline pero no para funciones nombradas?
- Los manejadores inline como
onChange={(e) => ...}obtienen su tipo del contexto del atributo JSX. - Las funciones nombradas existen fuera de ese contexto, por lo que TypeScript no puede inferir el tipo de evento.
- Debes anotar el parámetro de evento explícitamente:
function handleChange(e: React.ChangeEvent<HTMLInputElement>).
¿Cuáles son los tipos de eventos de React más comunes?
React.MouseEvent<T>-- clics, movimiento del ratón.React.ChangeEvent<T>-- cambios en entrada, select, textarea.React.FormEvent<T>-- envíos de formularios.React.KeyboardEvent<T>-- pulsaciones de teclas.React.FocusEvent<T>-- enfoque y desenfoque.
Trampa: ¿Por qué acceder a event.target.value en un evento de envío de formulario causa un error de TypeScript?
event.targetse tipifica comoEventTarget, que no tiene una propiedadvalue.- Usa
event.currentTarget(tipificado como el elemento del formulario) o castaevent.target. - Para datos de formulario, prefiere
new FormData(event.currentTarget).
¿Cómo tipificas una prop de callback de evento personalizado?
type SearchBarProps = {
onSearch: (query: string) => void;
onClear?: () => void;
};- Expone un callback específico del dominio (p. ej.,
onSearch(query)) en lugar del evento DOM sin procesar. - Esto desacopla el padre de la implementación DOM del hijo.
¿Qué controla el parámetro genérico en React.MouseEvent<HTMLButtonElement>?
- Determina el tipo de
event.currentTarget. React.MouseEvent<HTMLButtonElement>significa quecurrentTargetse tipifica comoHTMLButtonElement.- Usa el tipo de elemento HTML que coincida con el elemento al que se adjunta el manejador.
Trampa: ¿Qué sucede si olvidas event.preventDefault() en el envío del formulario?
- El navegador realiza una recarga de página completa (comportamiento de envío de formulario predeterminado).
- Tu estado SPA se pierde y la lógica del manejador del formulario puede no completarse.
- Siempre llama
event.preventDefault()en la parte superior de los manejadores de envío de formularios.
¿Cuándo debes usar React.SyntheticEvent como el tipo de evento?
- Cuando no te importa el tipo de evento específico (ratón, teclado, etc.).
- Es el tipo base para todos los eventos de React.
- Prefiere tipos específicos cuando necesitas acceso a propiedades específicas del evento (p. ej.,
clientX,key).
¿Cómo tipificas un manejador para un elemento select?
function handleSelectChange(event: React.ChangeEvent<HTMLSelectElement>) {
console.log("Seleccionado:", event.target.value);
}- Usa
React.ChangeEvent<HTMLSelectElement>-- noHTMLInputElement. - La propiedad
valuees el valor de cadena de la opción seleccionada.
¿Sigue siendo un problema el pooling de eventos en React 17+?
- No. React 17 eliminó por completo el pooling de eventos.
- En React 16 y anteriores, los eventos sintéticos se reciclaban, y acceder a las propiedades de forma asincrónica requería
event.persist(). - En React 17+, puedes acceder de forma segura a las propiedades de eventos en callbacks asincrónico sin
persist().