El Sistema de Eventos Sintéticos en React
React implementa internamente un sistema sofisticado para el registro, almacenamiento,分发 y reutilización de eventos. Esta implementación propia está construida sobre el sistema de eventos del DOM nativo del navegador, aportando optimizaciones significativas que reducen el consumo de memoria, simplifican la lógica de manejo de eventos y resuelven problemas de compatibilidad con navegadores como Internet Explorer.
Descripción General
El mecanismo de eventos sintéticos de React, conocido como SyntheticEvent, constituye una capa de abstracción desarrollada internamente por el framework. Este sistema actúa como un wrapper multiplataforma para los eventos nativos del navegador, proporcionando compatibilidad completa con todas las plataformas y manteniendo una interfaz coherente que incluye métodos como stopPropagation() y preventDefault().
Es fundamental comprender que los eventos sintéticos difieren de los eventos nativos del navegador y no se mapean directamente a ellos. Por esta razón, se recomienda utilizar exclusivamente el sistema de eventos definido en React en lugar de agregar detectores de eventos manualmente mediante addEventListener. La mezcla de ambos enfoques puede generar conflictos, ya que los eventos nativos que implementan阻止 de propagación pueden interferir con la ejecución de los eventos sintéticos de React.
Cuando sea estrictamente necesario acceder al evento nativo, React proporciona esta posibilidad a través de la propiedad nativeEvent del objeto SyntheticEvent recibido en los manejadores de eventos.
Características Principales del Sistema de Eventos
El sistema de eventos de React presenta las siguientes particularidades distintivas que lo diferencian del manejo tradicional de eventos del DOM:
Centralización del registro: Todos los eventos de React se registran en el objeto document del DOM, no en los elementos específicos de los componentes. Esta estrategia de optimización reduce significativamente el consumo de memoria al evitar la creación de múltiples detectores de eventos individuales.
Propagación personalizada: React implementa su propio mecanismo de propagación de eventos que difiere del comportamiento nativo del DOM. Los objetos de evento de React no pueden mezclarse directamente con los eventos nativos del navegador.
Ejecución mediante cola: El sistema procesa los eventos desde el componente que los dispara, subiendo a través de la jerarquía de componentes padres, ejecutando secuencialmente todas las funciones callback definidas en el JSX de cada componente.
Gestión de memoria mediante pool: Para optimizar el rendimiento, React utiliza un sistema de pool de objetos para gestionar la creación y destrucción de eventos sintéticos, minimizando la generación de basura y la asignación de memoria para nuevos objetos.
Estructura del Objeto SyntheticEvent
Cada instancia de SyntheticEvent contiene las siguientes propiedades que proporcionan información sobre el evento ocurrido:
boolean bubbles
boolean cancelable
DOMEventTarget currentTarget
boolean defaultPrevented
number eventPhase
boolean isTrusted
DOMEvent nativeEvent
void preventDefault()
boolean isDefaultPrevented()
void stopPropagation()
boolean isPropagationStopped()
void persist()
DOMEventTarget target
number timeStamp
string type
Catálogo de Eventos Sintéticos Soportados
Los siguientes eventos pueden utilizarse directamente en los componentes React. Por defecto, estos manejadores se ejecutan durante la fase de burbujeo. Para registrar eventos durante la fase de captura, se debe añadir la palabra "Capture" al nombre del evento, por ejemplo, onClickCapture para capturar eventos de clic durante la fase de captura.
<!-- Eventos de Portapapeles -->
onCopy onCut onPaste
<!-- Eventos de Composición -->
onCompositionEnd onCompositionStart onCompositionUpdate
<!-- Eventos de Teclado -->
onKeyDown onKeyPress onKeyUp
<!-- Eventos de Foco -->
onFocus onBlur
<!-- Eventos de Formulario -->
onChange onInput onInvalid onReset onSubmit
<!-- Eventos Genéricos -->
onError onLoad
<!-- Eventos de Ratón -->
onClick onContextMenu onDoubleClick onDrag onDragEnd onDragEnter onDragExit
onDragLeave onDragOver onDragStart onDrop onMouseDown onMouseEnter onMouseLeave
onMouseMove onMouseOut onMouseOver onMouseUp
<!-- Eventos de Puntero -->
onPointerDown onPointerMove onPointerUp onPointerCancel onGotPointerCapture
onLostPointerCapture onPointerEnter onPointerLeave onPointerOver onPointerOut
<!-- Eventos de Selección -->
onSelect
<!-- Eventos Táctiles -->
onTouchCancel onTouchEnd onTouchMove onTouchStart
<!-- Eventos de Interfaz -->
onScroll
<!-- Eventos de Rueda -->
onWheel
<!-- Eventos de Medios -->
onAbort onCanPlay onCanPlayThrough onDurationChange onEmptied onEncrypted
onEnded onError onLoadedData onLoadedMetadata onLoadStart onPause onPlay
onPlaying onProgress onRateChange onSeeked onSeeking onStalled onSuspend
onTimeUpdate onVolumeChange onWaiting
<!-- Eventos de Imagen -->
onLoad onError
<!-- Eventos de Animación -->
onAnimationStart onAnimationEnd onAnimationIteration
<!-- Eventos de Transición -->
onTransitionEnd
<!-- Eventos Diversos -->
onToggle
Ejemplo Práctico
El siguiente ejemplo demuestra la interacción entre eventos nativos del DOM y eventos de React. Se puede observar cómo un evento nativo que detiene la propagación afecta la ejecución del evento de React correspondiente.
<html>
<head>
<meta charset="UTF-8" />
<title>React</title>
</head>
<body>
<div id="root"></div>
</body>
<script src="https://unpkg.zhimg.com/react@17/umd/react.development.js"></script>
<script src="https://unpkg.zhimg.com/react-dom@17/umd/react-dom.development.js"></script>
<script src="https://unpkg.zhimg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
class ComponenteEventos extends React.PureComponent {
componentDidMount(){
document.getElementById("btn-mixto").addEventListener("click", (e) => {
console.log("Evento nativo ejecutado", "listenerOriginal");
console.log("¿event es instancia de Event?:", e instanceof Event);
e.stopPropagation();
});
}
gestormixto = (e) => {
console.log("Evento React ejecutado", "gestormixto");
console.log("¿event es instancia de Event?:", e instanceof Event);
}
gestorSimple = (e) => {
console.log("Evento React ejecutado", "gestorSimple");
console.log("¿event es instancia de Event?:", e instanceof Event);
}
render() {
return (
<div className="contenedor">
<button id="btn-simple" onClick={this.gestorSimple}>Solo React</button>
<button id="btn-mixto" onClick={this.gestormixto}>Nativo + React</button>
</div>
)
}
}
var vm = ReactDOM.render(
<>
<ComponenteEventos />
</>,
document.getElementById("root")
);
</script>
</html>
Arquitectura del Sistema de Eventos
El funcionamiento interno del sistema de eventos de React comprende varias fases claramente diferenciadas. Durante la fase de montaje, el sistema almacena los eventos en estructuras de datos dedicadas mediante el mecanismo de listenerBank. Cuando ocurre un evento, el document ejecuta dispatchEvent, identifica el nodo más profundo donde ocurrió el evento y comienza un recorrido ascendente hacia los elementos padres, recopilando todos los callbacks asociados en una cola de eventos denominada eventQueue. Posteriormente, el sistema construye el objeto de evento correspondiente según el tipo de evento y ejecuta secuencialmente todos los listeners almacenados en la cola.
Analizando el código fuente de React, específicamente en el commit 4ab6305 con la etiqueta React16.10.2, podemos observar la arquitectura del sistema de eventos. Es importante destacar que en React 17 se realizaron cambios significativos: los eventos ya no se delegan en el objeto document, sino que se registran en el contenedor DOM específico de cada componente.
/**
* Resumen del manejo de eventos en ReactBrowserEventEmitter:
*
* - La delegación de nivel superior se utiliza para capturar la mayoría
* de los eventos nativos del navegador. Esto ocurre únicamente en el
* hilo principal y es responsabilidad de ReactDOMEventListener,
* que puede inyectarse para soportar fuentes de eventos modificables.
*
* - Normalizamos y desduplicamos eventos para resolver peculiaridades
* de los navegadores. Esto puede realizarse en un hilo secundario.
*
* - Reenviamos estos eventos nativos (con el tipo de nivel superior
* asociado utilizado para capturarlos) a EventPluginHub, que a su
* vez consultará a los plugins si desean extraer eventos sintéticos.
*
* - EventPluginHub procesará cada evento anotándolos con "dispatches",
* una secuencia de listeners y IDs relacionados con ese evento.
*
* - Finalmente, EventPluginHub despacha los eventos.
*/
/**
* Arquitectura del sistema de eventos de React:
*
* +------------+ .
* | DOM | .
* +------------+ .
* | .
* v .
* +------------+ .
* | ReactEvent | .
* | Listener | .
* +------------+ . +-----------+
* | . +--------+|SimpleEvent|
* | . | |Plugin |
* +-----|------+ . v +-----------+
* | | | . +--------------+ +------------+
* | +-----------.--->|EventPluginHub| | Event |
* | | . | | +-----------+ | Propagators|
* | ReactEvent | . | | |TapEvent | |------------|
* | Emitter | . | |<---+|Plugin | |other plugin|
* | | . | | +-----------+ | utilities |
* | +-----------.--->| | +------------+
* | | | . +--------------+
* +-----|------+ . ^ +-----------+
* | . | |Enter/Leave|
* + . +-------+|Plugin |
* +-------------+ . +-----------+
* | aplicación | .
* |-------------| .
* | | .
* | | .
* +-------------+ .
* .
*/
El procesamiento de eventos en React sigue un flujo estructurado: el código JSX se compila mediante Babel, se crean los elementos virtuales y se procesan las props de los componentes para extraer el tipo de evento y la función callback correspondiente. A partir de este punto, el sistema atraviesa las fases de registro, almacenamiento, síntesis,分发 y ejecución del evento.
Delegación de nivel superior: Captura los eventos nativos del navegador mediante ReactEventListener, que al ser inyectable permite soportar fuentes de eventos configurables. Este proceso se ejecuta en el hilo principal.
Normalización de eventos: React normaliza y elimina duplicados para manejar las inconsistencias entre navegadores, proceso que puede realizarse en un hilo secundario.
Procesamiento por plugins: Los eventos nativos se envían a EventPluginHub, que consulta a los plugins disponibles para determinar si pueden extraer eventos sintéticos relevantes.
Anotación de eventos: EventPluginHub procesa cada evento agregándole "dispatches", que son secuencias de listeners e identificadores relacionados con ese evento en particular.
Despacho final: Los eventos procesados son despachados para su ejecución.
Fase de Registro de Eventos
El proceso de registro comienza en la función setInitialDOMProperties(), donde se verifica si el nombre del evento existe en la lista registrationNameModules. Esta lista contiene todos los eventos que pueden ser registrados legalmente en el sistema de React.
// ReactDOMComponent.js
function setInitialDOMProperties(
etiqueta: string,
elementoDom: Element,
contenedorRaiz: Element | Document,
propiedadesSiguientes: Object,
esComponentePersonalizado: boolean,
): void {
for (const claveProp in propiedadesSiguientes) {
if (!propiedadesSiguientes.hasOwnProperty(claveProp)) {
continue;
}
const valorProp = propiedadesSiguientes[claveProp];
if (claveProp === ESTILO) {
if (__DEV__) {
if (valorProp) {
Object.freeze(valorProp);
}
}
establecerEstilos(elementoDom, valorProp);
} else if(/* ... */){
// ...
} else if (modulosRegistro.hasOwnProperty(claveProp)) {
if (valorProp != null) {
if (__DEV__ && typeof valorProp !== 'function') {
advertenciarListenerInvalido(claveProp, valorProp);
}
asegurarEscucha(contenedorRaiz, claveProp);
}
} else if (valorProp != null) {
establecerPropiedad(elementoDom, claveProp, valorProp, esComponentePersonalizado);
}
}
}
Cuando el nombre del evento es válido y corresponde a una función, se invoca el método asegurarEscucha(). Este método determina si el contenedorRaiz es un documento o un Fragment; si no lo es, obtiene el documento raíz mediante la propiedad ownerDocument. La mayoría de los eventos se registran en el documento principal, y posteriormente se llama al método escuchar() para completar el registro.
// ReactDOMComponent.js
function asegurarEscucha(
contenedorRaiz: Element | Node,
nombreRegistro: string,
): void {
const esDocumentoOFragmento =
contenedorRaiz.nodeType === NODO_DOCUMENTO ||
contenedorRaiz.nodeType === NODO_FRAGMENTO_DOCUMENTO;
const documento = esDocumentoOFragmento
? contenedorRaiz
: contenedorRaiz.ownerDocument;
escuchar(nombreRegistro, documento);
}
El método escuchar() utiliza el concepto de registrationNameDependencies, que permite a React vincular múltiples eventos simultáneamente para lograr un comportamiento unificado. Además, ciertos eventos como onBlur, onFocus y onScroll se registran mediante trapCapturedEvent debido a que no tienen comportamiento de burbujeo, mientras que eventos como invalid, submit y reset se vinculan directamente al elemento DOM específico.
// ReactBrowserEventEmitter.js
export function escuchar(
nombreRegistro: string,
monteEn: Document | Element | Node,
): void {
const conjuntoEscucha = obtenerConjuntoDeEscucha(monteEn);
const dependencias = dependenciasRegistro[nombreRegistro];
for (let i = 0; i < dependencias.length; i++) {
const dependencia = dependencias[i];
escucharNivelSuperior(dependencia, monteEn, conjuntoEscucha);
}
}
export function escucharNivelSuperior(
tipoNivelSuperior: DOMTopLevelEventType,
monteEn: Document | Element | Node,
conjuntoEscucha: Set<DOMTopLevelEventType | string>,
): void {
if (!conjuntoEscucha.has(tipoNivelSuperior)) {
switch (tipoNivelSuperior) {
case TOP_SCROLL:
capturarEvento(TOP_SCROLL, monteEn);
break;
case TOP_FOCUS:
case TOP_BLUR:
capturarEvento(TOP_FOCUS, monteEn);
capturarEvento(TOP_BLUR, monteEn);
conjuntoEscucha.add(TOP_BLUR);
conjuntoEscucha.add(TOP_FOCUS);
break;
case TOP_CANCEL:
case TOP_CLOSE:
if (eventoSoportado(obtenerNombreEventoCrudo(tipoNivelSuperior))) {
capturarEvento(tipoNivelSuperior, monteEn);
}
break;
case TOP_INVALID:
case TOP_SUBMIT:
case TOP_RESET:
break;
default:
const esEventoMedia = tiposEventosMedia.indexOf(tipoNivelSuperior) !== -1;
if (!esEventoMedia) {
burbujearEvento(tipoNivelSuperior, monteEn);
}
break;
}
conjuntoEscucha.add(tipoNivelSuperior);
}
}
El siguiente paso es la vinculación efectiva del evento. Tomando como ejemplo burbujearEvento(), podemos observar que internamente llama al método trapEventForPluginEventSystem.
// ReactDOMEventListener.js
export function burbujearEvento(
tipoNivelSuperior: DOMTopLevelEventType,
elemento: Document | Element | Node,
): void {
trapEventForPluginEventSystem(elemento, tipoNivelSuperior, false);
}
El sistema de eventos de React clasifica los eventos en tres categorías con prioridades diferenciadas:
DiscreteEvent (Eventos Discretos): Incluyen blur, focus, click, submit y touchStart. Estos eventos se disparan de forma aislada y no continua.
UserBlockingEvent (Eventos Bloqueantes): Comprenden touchMove, mouseMove, scroll, drag y dragOver, entre otros. Estos eventos pueden bloquear la interacción del usuario si no se procesan rápidamente.
ContinuousEvent (Eventos Continuos): Como load, error, loadStart, abort y animationEnd. Tienen la prioridad más alta y se ejecutan de forma síncrona e inmediata, sin interrupciones.
Esta clasificación de eventos se integra con la arquitectura de Fiber de React, donde las tareas se organizan en cinco niveles de prioridad. La correspondencia entre ambos sistemas es la siguiente:
Immediate: Tareas síncronas que se ejecutan inmediatamente sin posibilidad de interrupción. Corresponde a ContinuousEvent.
UserBlocking: Tareas resultantes de interacción del usuario que requieren retroalimentación inmediata. Corresponde a DiscreteEvent y UserBlockingEvent.
Normal: Tareas que no requieren retroalimentación inmediata, como solicitudes de red.
Low: Tareas que pueden postergarse pero eventualmente deben ejecutarse, como análisis de notificaciones.
Idle: Tareas definidas como innecesarias que pueden omitirse.
Continuando con trapEventForPluginEventSystem, aunque los tres tipos de eventos tienen funciones de activación diferentes, eventualmente todos confluyen en la función común dispatchEvent aunque requieren procesamiento previo específico según su tipo.
// ReactDOMEventListener.js
function trapEventForPluginEventSystem(
elemento: Document | Element | Node,
tipoNivelSuperior: DOMTopLevelEventType,
captura: boolean,
): void {
let listener;
switch (obtenerPrioridadEvento(tipoNivelSuperior)) {
case DiscreteEvent:
listener = despacharEventoDiscreto.bind(
null,
tipoNivelSuperior,
SISTEMA_EVENTOS_PLUGIN,
);
break;
case UserBlockingEvent:
listener = despacharActualizacionBloqueante.bind(
null,
tipoNivelSuperior,
SISTEMA_EVENTOS_PLUGIN,
);
break;
case ContinuousEvent:
default:
listener = despacharEvento.bind(null, tipoNivelSuperior, SISTEMA_EVENTOS_PLUGIN);
break;
}
const nombreEventoCrudo = obtenerNombreEventoCrudo(tipoNivelSuperior);
if (captura) {
agregarListenerCaptura(elemento, nombreEventoCrudo, listener);
} else {
agregarListenerBurbuja(elemento, nombreEventoCrudo, listener);
}
}
El registro final de eventos se realiza directamente en el objeto document del navegador.
// EventListener.js
export function agregarListenerBurbuja(
elemento: Document | Element | Node,
tipoEvento: string,
listener: Function,
): void {
elemento.addEventListener(tipoEvento, listener, false);
}
export function agregarListenerCaptura(
elemento: Document | Element | Node,
tipoEvento: string,
listener: Function,
): void {
elemento.addEventListener(tipoEvento, listener, true);
}
export function agregarListenerCapturaConFlagPasivo(
elemento: Document | Element | Node,
tipoEvento: string,
listener: Function,
pasivo: boolean,
): void {
elemento.addEventListener(tipoEvento, listener, {
capture: true,
pasivo,
});
}
Almacenamiento de Eventos
Volviendo al método escucharNivelSuperior, el conjunto de eventos registrados se almacena utilizando una estructura de datos basada en WeakMap o Map dependiendo de la disponibilidad del navegador. Esta estructura utiliza el nodo DOM como clave y un objeto Set que contiene los eventos asociados como valor. El objeto que gestiona esta colección se denomina EventPluginHub. El uso de WeakMap es preferible porque mantiene referencias débiles a las claves, evitando problemas de memory leaks cuando los nodos DOM ya no están en uso.
// ReactBrowserEventEmitter.js
const PosibleWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
const conjuntosEscuchaElementos:
| WeakMap
| Map<
Document | Element | Node,
Set<DOMTopLevelEventType | string>,
> = new PosibleWeakMap();
export function obtenerConjuntoDeEscucha(
elemento: Document | Element | Node,
): Set<DOMTopLevelEventType | string> {
let conjuntoEscucha = conjuntosEscuchaElementos.get(elemento);
if (conjuntoEscucha === undefined) {
conjuntoEscucha = new Set();
conjuntosEscuchaElementos.set(elemento, conjuntoEscucha);
}
return conjuntoEscucha;
}
Síntesis de Eventos
El método manejarNivelSuperior constituye el punto de entrada para el procesamiento de eventos. Su función principal es almacenar en caché los elementos ancestros para evitar errores cuando el evento se dispara y los elementos ya no son accesibles. Posteriormente, el flujo continúa hacia el método ejecutarEventosPluginEnLote.
// ReactDOMEventListener.js
function manejarNivelSuperior(instanciaBookKeeping: BookKeepingInstance) {
let instanciaObjetivo = instanciaBookKeeping.objetivoInst;
let ancestro = instanciaObjetivo;
do {
if (!ancestro) {
const ancestros = instanciaBookKeeping.ancestros;
((ancestros: any): Array<Fiber | null>).push(ancestro);
break;
}
const raiz = encontrarNodoRaiz(ancestro);
if (!raiz) {
break;
}
const etiqueta = ancestro.etiqueta;
if (etiqueta === ComponenteHost || etiqueta === TextoHost) {
instanciaBookKeeping.ancestros.push(ancestro);
}
ancestro = obtenerInstanciaMasCercana(raiz);
} while (ancestro);
for (let i = 0; i < instanciaBookKeeping.ancestros.length; i++) {
instanciaObjetivo = instanciaBookKeeping.ancestros[i];
const objetivoEvento = obtenerObjetivoEvento(instanciaBookKeeping.eventoNativo);
const tipoNivelSuperior = ((instanciaBookKeeping.tipoNivelSuperior: any): DOMTopLevelEventType);
const eventoNativo = ((instanciaBookKeeping.eventoNativo: any): AnyNativeEvent);
ejecutarEventosPluginEnLote(
tipoNivelSuperior,
instanciaObjetivo,
eventoNativo,
objetivoEvento,
instanciaBookKeeping.flagsSistemaEventos,
);
}
}
En el método ejecutarEventosPluginEnLote, la función extraerEventosPlugin sintetiza los eventos mediante diferentes plugins, mientras que ejecutarEventosEnLote gestiona la activación de dichos eventos.
// EventPluginHub.js
export function ejecutarEventosPluginEnLote(
tipoNivelSuperior: TopLevelType,
instanciaObjetivo: null | Fiber,
eventoNativo: AnyNativeEvent,
objetivoEventoNativo: EventTarget,
flagsSistemaEventos: EventSystemFlags,
) {
const eventos = extraerEventosPlugin(
tipoNivelSuperior,
instanciaObjetivo,
eventoNativo,
objetivoEventoNativo,
flagsSistemaEventos,
);
ejecutarEventosEnLote(eventos);
}
La función extraerEventosPlugin itera a través de todos los plugins disponibles, invocando su método extraerEventos para sintetizar los eventos correspondientse. Si un plugin es aplicable para el tipo de evento recibido, devuelve los eventos sintetizados; de lo contrario, retorna null. Los plugins predeterminados incluyen: SimpleEventPlugin, EnterLeaveEventPlugin, ChangeEventPlugin, SelectEventPlugin y BeforeInputEventPlugin.
// EventPluginHub.js
function extraerEventosPlugin(
tipoNivelSuperior: TopLevelType,
instanciaObjetivo: null | Fiber,
eventoNativo: AnyNativeEvent,
objetivoEventoNativo: EventTarget,
flagsSistemaEventos: EventSystemFlags,
): Array<ReactSyntheticEvent> | ReactSyntheticEvent | null {
let eventos = null;
for (let i = 0; i < plugins.length; i++) {
const pluginPosible: PluginModule<AnyNativeEvent> = plugins[i];
if (pluginPosible) {
const eventosExtraidos = pluginPosible.extraerEventos(
tipoNivelSuperior,
instanciaObjetivo,
eventoNativo,
objetivoEventoNativo,
flagsSistemaEventos,
);
if (eventosExtraidos) {
eventos = acumularEn(eventos, eventosExtraidos);
}
}
}
return eventos;
}
Cada tipo de evento tiene una clase base diferente para los eventos sintéticos, y la generación de eventos se realiza mediante EventConstructor.getPooled. La función acumularDispatchesDosFases se encarga de obtener los callbacks de los eventos, utilizando internamente el método obtenerListener.
Para prevenir problemas de rendimiento derivados de la creación y liberación frecuente de objetos de evento, React implementa un pool de eventos que gestiona estos objetos. En React 17, este mecanismo de pool fue eliminado. Cuando un evento se ha procesado completamente, sus propiedades quedan disponibles para su reutilización, lo que implica que el objeto SyntheticEvent se resetea y no puede accederse posteriormente. Si se necesita сохранить los datos del evento, existen dos alternativas: invocar e.persist() para indicar a React que no recicle el objeto (en React 17 esta llamada sigue funcionando pero no tiene efecto), o acceder directamente a e.nativeEvent ya que este mantiene una referencia persistente.
###分发 de Eventos
La fase dedistribución de eventos consiste en recorrer la jerarquía desde el elemento actual hasta los componentes padres, recopilando todos los eventos asociados y almacenándolos en la cola event._dispatchListeners para su posterior ejecución.
// EventPropagators.js
function acumularDispatchesDireccionales(inst, fase, evento) {
if (__DEV__) {
warningWithoutStack(inst, 'El objeto de despacho no debe ser null');
}
const listener = listenerEnFase(inst, evento, fase);
if (listener) {
evento._dispatchListeners = acumularEn(
evento._dispatchListeners,
listener,
);
evento._dispatchInstances = acumularEn(evento._dispatchInstances, inst);
}
}
Ejecución de Eventos
La ejecución de la cola de eventos utiliza el método ejecutarEventosEnLote, que itera sobre los eventos y ejecuta ejecutarDispatchesEnOrden. El调度 real se realiza mediante ejecutarDispatch, y la invocación de los callbacks se efectúa a través de invocarCallbackProtegidoYCapturarPrimerError.
// EventBatching.js
export function ejecutarEventosEnLote(
eventos: Array<ReactSyntheticEvent> | ReactSyntheticEvent | null,
) {
if (eventos !== null) {
colaEventos = acumularEn(colaEventos, eventos);
}
const procesandoColaEventos = colaEventos;
colaEventos = null;
if (!procesandoColaEventos) {
return;
}
forEachAcumulado(procesandoColaEventos, ejecutarDispatchesYLiberarNivelSuperior);
invariant(
!colaEventos,
'processEventQueue(): Se encolaron eventos adicionales mientras se procesaba la cola. Esta funcionalidad aún no está implementada.',
);
rethrowCaughtError();
}
// EventPluginUtils.js
export function ejecutarDispatchesEnOrden(evento) {
const dispatchListeners = evento._dispatchListeners;
const dispatchInstances = evento._dispatchInstances;
if (__DEV__) {
validarDispatchesEvento(evento);
}
if (Array.isArray(dispatchListeners)) {
for (let i = 0; i < dispatchListeners.length; i++) {
if (evento.isPropagationStopped()) {
break;
}
ejecutarDispatch(evento, dispatchListeners[i], dispatchInstances[i]);
}
} else if (dispatchListeners) {
ejecutarDispatch(evento, dispatchListeners, dispatchInstances);
}
evento._dispatchListeners = null;
evento._dispatchInstances = null;
}
// EventPluginUtils.js
export function ejecutarDispatch(evento, listener, inst) {
const tipo = evento.type || 'evento-desconocido';
evento.currentTarget = obtenerNodoDesdeInstancia(inst);
invocarCallbackProtegidoYCapturarPrimerError(tipo, listener, undefined, evento);
evento.currentTarget = null;
}
// ReactErrorUtils.js
export function invocarCallbackProtegidoYCapturarPrimerError<
A,
B,
C,
D,
E,
F,
Contexto,
>(
nombre: string | null,
funcion: (a: A, b: B, c: C, d: D, e: E, f: F) => void,
contexto: Contexto,
a: A,
b: B,
c: C,
d: D,
e: E,
f: F,
): void {
invocarCallbackProtegido.apply(this, arguments);
if (hayError) {
const error = limpiarErrorCapturado();
if (!hayRethrowError) {
hayRethrowError = true;
errorRethrow = error;
}
}
}