asp网站部署 iis7,网站首页弹出图片,凡科做的网站好吗,中山精品网站建设咨询event事件监听 1 #xff09;概述
在 react-dom 代码初始化的时候#xff0c;去注入了平台相关的事件插件接下去在react的更新过程绑定了事件的操作#xff0c;在执行到 completeWork 的时候对于 HostComponent 会一开始就先去执行了 finalizeInitialChildren 这个方法位置…event事件监听 1 概述
在 react-dom 代码初始化的时候去注入了平台相关的事件插件接下去在react的更新过程绑定了事件的操作在执行到 completeWork 的时候对于 HostComponent 会一开始就先去执行了 finalizeInitialChildren 这个方法位置在 packages/react-reconciler/src/ReactFiberCompleteWork.js#L642
2 源码
定位到 packages/react-dom/src/client/ReactDOMHostConfig.js#L212
找到 finalizeInitialChildren
export function finalizeInitialChildren(domElement: Instance,type: string,props: Props,rootContainerInstance: Container,hostContext: HostContext,
): boolean {setInitialProperties(domElement, type, props, rootContainerInstance);return shouldAutoFocusHostComponent(type, props);
}定位到 packages/react-dom/src/client/ReactDOMComponent.js#L447
找到 setInitialProperties
export function setInitialProperties(domElement: Element,tag: string,rawProps: Object,rootContainerElement: Element | Document,
): void {const isCustomComponentTag isCustomComponent(tag, rawProps);if (__DEV__) {validatePropertiesInDevelopment(tag, rawProps);if (isCustomComponentTag !didWarnShadyDOM (domElement: any).shadyRoot) {warning(false,%s is using shady DOM. Using shady DOM with React can cause things to break subtly.,getCurrentFiberOwnerNameInDevOrNull() || A component,);didWarnShadyDOM true;}}// TODO: Make sure that we check isMounted before firing any of these events.let props: Object;switch (tag) {case iframe:case object:trapBubbledEvent(TOP_LOAD, domElement);props rawProps;break;case video:case audio:// Create listener for each media eventfor (let i 0; i mediaEventTypes.length; i) {trapBubbledEvent(mediaEventTypes[i], domElement);}props rawProps;break;case source:trapBubbledEvent(TOP_ERROR, domElement);props rawProps;break;case img:case image:case link:trapBubbledEvent(TOP_ERROR, domElement);trapBubbledEvent(TOP_LOAD, domElement);props rawProps;break;case form:trapBubbledEvent(TOP_RESET, domElement);trapBubbledEvent(TOP_SUBMIT, domElement);props rawProps;break;case details:trapBubbledEvent(TOP_TOGGLE, domElement);props rawProps;break;case input:ReactDOMInput.initWrapperState(domElement, rawProps);props ReactDOMInput.getHostProps(domElement, rawProps);trapBubbledEvent(TOP_INVALID, domElement);// For controlled components we always need to ensure were listening// to onChange. Even if there is no listener.ensureListeningTo(rootContainerElement, onChange);break;case option:ReactDOMOption.validateProps(domElement, rawProps);props ReactDOMOption.getHostProps(domElement, rawProps);break;case select:ReactDOMSelect.initWrapperState(domElement, rawProps);props ReactDOMSelect.getHostProps(domElement, rawProps);trapBubbledEvent(TOP_INVALID, domElement);// For controlled components we always need to ensure were listening// to onChange. Even if there is no listener.ensureListeningTo(rootContainerElement, onChange);break;case textarea:ReactDOMTextarea.initWrapperState(domElement, rawProps);props ReactDOMTextarea.getHostProps(domElement, rawProps);trapBubbledEvent(TOP_INVALID, domElement);// For controlled components we always need to ensure were listening// to onChange. Even if there is no listener.ensureListeningTo(rootContainerElement, onChange);break;default:props rawProps;}assertValidProps(tag, props);setInitialDOMProperties(tag,domElement,rootContainerElement,props,isCustomComponentTag,);switch (tag) {case input:// TODO: Make sure we check if this is still unmounted or do any clean// up necessary since we never stop tracking anymore.inputValueTracking.track((domElement: any));ReactDOMInput.postMountWrapper(domElement, rawProps, false);break;case textarea:// TODO: Make sure we check if this is still unmounted or do any clean// up necessary since we never stop tracking anymore.inputValueTracking.track((domElement: any));ReactDOMTextarea.postMountWrapper(domElement, rawProps);break;case option:ReactDOMOption.postMountWrapper(domElement, rawProps);break;case select:ReactDOMSelect.postMountWrapper(domElement, rawProps);break;default:if (typeof props.onClick function) {// TODO: This cast may not be sound for SVG, MathML or custom elements.trapClickOnNonInteractiveElement(((domElement: any): HTMLElement));}break;}
}对于 iframe, object, video, audio, source 这些多媒体节点的初始化绑定是通过 trapBubbledEvent 来实现的后续执行到 setInitialDOMProperties, 在这个方法内部function setInitialDOMProperties(tag: string,domElement: Element,rootContainerElement: Element | Document,nextProps: Object,isCustomComponentTag: boolean,
): void {for (const propKey in nextProps) {if (!nextProps.hasOwnProperty(propKey)) {continue;}const nextProp nextProps[propKey];if (propKey STYLE) {// ... 跳过很多代码} else if (propKey DANGEROUSLY_SET_INNER_HTML) {// ... 跳过很多代码} else if (propKey AUTOFOCUS) {// We polyfill it separately on the client during commit.// We could have excluded it in the property list instead of// adding a special case here, but then it wouldnt be emitted// on server rendering (but we *do* want to emit it in SSR).// 注意这里, propKey 是 dom 节点内的 props的配置如果这个配置在 registrationNameModules 这里// registrationNameModules 是通过每一个插件里面每一个 eventTypes 里面// 它对应的有 phasedRegistrationNames 的情况下比如说 onChange, onChangeCapture, 它都是作为它的一个key而存在的// 也就是说我们如果在这个 props 上面写了 onChange onClick 这些事件相关的props的话// 就会符合这个条件的判断符合这个条件判断之后, 它会调用一个方法叫做 ensureListeningTo} else if (registrationNameModules.hasOwnProperty(propKey)) {if (nextProp ! null) {if (__DEV__ typeof nextProp ! function) {warnForInvalidEventListener(propKey, nextProp);}ensureListeningTo(rootContainerElement, propKey); // rootContainerElement 是 fiberRoot 对应的 container }} else if (nextProp ! null) {// ... 跳过很多代码}}
}进入 ensureListeningTofunction ensureListeningTo(rootContainerElement, registrationName) {const isDocumentOrFragment rootContainerElement.nodeType DOCUMENT_NODE ||rootContainerElement.nodeType DOCUMENT_FRAGMENT_NODE;// 如果它是一个document或者fragment那么它就等于 rootContainerElement// 如果它不是它就等于它的 rootContainerElement.ownerDocument// 这个是用来最终要去把事件绑定在哪个地方的// 可以确定的是在 react 当中大部分可冒泡的事件都是通过事件代理的形式来进行一个绑定的// 也就是说不是每一个节点都会绑定自己的事件// 因为每个节点绑定自己的事件肯定是性能比较低下的一个操作而且有可能会导致内存溢出这种情况const doc isDocumentOrFragment? rootContainerElement: rootContainerElement.ownerDocument;// 调用这个方法listenTo(registrationName, doc);
}进入 listenTo// packages/react-dom/src/events/ReactBrowserEventEmitter.js#L126
export function listenTo(registrationName: string,mountAt: Document | Element,
) {// 注意这里const isListening getListeningForDocument(mountAt);const dependencies registrationNameDependencies[registrationName];// 遍历依赖for (let i 0; i dependencies.length; i) {const dependency dependencies[i];// 没有这些依赖则对 dependency 进行事件监听处理if (!(isListening.hasOwnProperty(dependency) isListening[dependency])) {switch (dependency) {case TOP_SCROLL:// 这个方法监听的是 捕获阶段的事件trapCapturedEvent(TOP_SCROLL, mountAt);break;case TOP_FOCUS:case TOP_BLUR:trapCapturedEvent(TOP_FOCUS, mountAt);trapCapturedEvent(TOP_BLUR, mountAt);// We set the flag for a single dependency later in this function,// but this ensures we mark both as attached rather than just one.isListening[TOP_BLUR] true;isListening[TOP_FOCUS] true;break;case TOP_CANCEL:case TOP_CLOSE:if (isEventSupported(getRawEventName(dependency))) {trapCapturedEvent(dependency, mountAt);}break;case TOP_INVALID:case TOP_SUBMIT:case TOP_RESET:// We listen to them on the target DOM elements.// Some of them bubble so we dont want them to fire twice.break;// 对于其他大部分的事件处理 用冒泡处理default:// By default, listen on the top level to all non-media events.// Media events dont bubble so adding the listener wouldnt do anything.const isMediaEvent mediaEventTypes.indexOf(dependency) ! -1; // 注意这里排除了 mediaEventTypes因为一开始就已经对一些 媒体事件处理了if (!isMediaEvent) {trapBubbledEvent(dependency, mountAt); // 这是对常规事件的处理 冒泡}break;}isListening[dependency] true;}}
}进入 getListeningForDocument const alreadyListeningTo {};
let reactTopListenersCounter 0;
// 这个属性就是用来挂载 container 节点上面去记录这个节点监听了哪些事件的
// 用这种方式判断是因为 可能不存在这个属性如果没有则需要初始化属性
const topListenersIDKey _reactListenersID ( Math.random()).slice(2);
function getListeningForDocument(mountAt: any) {// In IE8, mountAt is a host object and doesnt have hasOwnProperty// directly.if (!Object.prototype.hasOwnProperty.call(mountAt, topListenersIDKey)) {mountAt[topListenersIDKey] reactTopListenersCounter; // 这里初始化属性alreadyListeningTo[mountAt[topListenersIDKey]] {};}// 那如果已经有了我们就返回这个对象, 用来记录这个dom节点它是否监听了哪些事件的return alreadyListeningTo[mountAt[topListenersIDKey]];
}对于 mediaEventTypes 和媒体相关的事件 // packages/react-dom/src/events/DOMTopLevelEventTypes.js#L155
export const mediaEventTypes [TOP_ABORT,TOP_CAN_PLAY,TOP_CAN_PLAY_THROUGH,TOP_DURATION_CHANGE,TOP_EMPTIED,TOP_ENCRYPTED,TOP_ENDED,TOP_ERROR,TOP_LOADED_DATA,TOP_LOADED_METADATA,TOP_LOAD_START,TOP_PAUSE,TOP_PLAY,TOP_PLAYING,TOP_PROGRESS,TOP_RATE_CHANGE,TOP_SEEKED,TOP_SEEKING,TOP_STALLED,TOP_SUSPEND,TOP_TIME_UPDATE,TOP_VOLUME_CHANGE,TOP_WAITING,
];进入 trapCapturedEvent export function trapCapturedEvent(topLevelType: DOMTopLevelEventType,element: Document | Element,
) {if (!element) {return null;}// 注意这里根据是否是 Interactive 类型的事件调用的不同的回调最终赋值给 dispatchconst dispatch isInteractiveTopLevelEventType(topLevelType)? dispatchInteractiveEvent: dispatchEvent;addEventCaptureListener(element,getRawEventName(topLevelType),// Check if interactive and wrap in interactiveUpdatesdispatch.bind(null, topLevelType),);
}进入 isInteractiveTopLevelEventTypeconst SimpleEventPlugin: PluginModuleMouseEvent {isInteractiveTopLevelEventType: (topLevelType: TopLevelType) boolean,
} {eventTypes: eventTypes,// 注意这里的 topLevelEventsToDispatchConfig 一开始是一个空的对象// 在调用 addEventTypeNameToConfig 时候加入的// 这个方法是检测 isInteractiveTopLevelEventType(topLevelType: TopLevelType): boolean {const config topLevelEventsToDispatchConfig[topLevelType];return config ! undefined config.isInteractive true;},// ... 跳过其他
}进入 addEventTypeNameToConfigfunction addEventTypeNameToConfig([topEvent, event]: EventTuple,isInteractive: boolean,
) {const capitalizedEvent event[0].toUpperCase() event.slice(1);const onEvent on capitalizedEvent;// 注意这个数据结构const type {phasedRegistrationNames: {bubbled: onEvent,captured: onEvent Capture,},dependencies: [topEvent],isInteractive, // 注意这里的标识};eventTypes[event] type;topLevelEventsToDispatchConfig[topEvent] type; // 这里进行注入
}关于这里的 isInteractive 标识的来源interactiveEventTypeNames.forEach(eventTuple {addEventTypeNameToConfig(eventTuple, true);
});
nonInteractiveEventTypeNames.forEach(eventTuple {addEventTypeNameToConfig(eventTuple, false);
});其中 interactiveEventTypeNames 参考 packages/react-dom/src/events/SimpleEventPlugin.js#L59 其中 nonInteractiveEventTypeNames 参考 packages/react-dom/src/events/SimpleEventPlugin.js#L95 上面两个数组对应 dom 原生的事件, 它们的区别是什么呢 这些事件去调用了设置的事件回调之后里面如果有 setState那么创建了update去计算的 expirationTime 会有 interactive 和 nonInteractive 的区分它们的区别在 expirationTimeinteractive的会比较的小也就是说它的优先级会比较的高它需要优先被执行因为它是一个用户交互相关的事件希望是用户比如说点了一个按钮之后立马可以得到反馈, 因为它需要被优先执行的 进入 addEventCaptureListener// 两者区别是第三个参数bubble 是 false, capture 是 true
export function addEventBubbleListener(element: Document | Element,eventType: string,listener: Function,
): void {element.addEventListener(eventType, listener, false);
}// 注意这里
export function addEventCaptureListener(element: Document | Element,eventType: string,listener: Function,
): void {element.addEventListener(eventType, listener, true); // 主要是绑定 dom 原生事件
}同样对于 trapBubbledEvent 也同上类似这里不再赘述