Статья «React advanced» объясняет принцип системы событий реагирования.

JavaScript React.js
Статья «React advanced» объясняет принцип системы событий реагирования.

Введение

Давайте поговорим об этом сегодняReactПринцип событий, в этой статье я стараюсь использовать популярный и лаконичный способ изложитьReactСистема событий говорит четко.

что мы сказалиreactверсия16.13.1 , v17ПозжеreactБудут соответствующие изменения в системе событий, которые будут упомянуты во второй половине статьи.

Старые правила, в официальном объясненииreactПрежде давайте подумаем над этими вопросами (Если бы я был интервьюером, как бы вы ответили?):

  • 1 Событие, которое мы написали, связаноdomНа, если нет, то куда он привязан?
  • 2 Почему наши события не могут быть привязаны к компонентам?
  • 3 Почему наши события связаны вручнуюthis(Не случай стрелочных функций)
  • 4 Почему бы не использоватьreturn false предотвратить поведение событий по умолчанию?
  • 5 reactкак пройтиdomэлемент, найдите соответствующийfiberобъект?
  • 6 onClickСвязано ли оно во время фазы образования пузырей? ТакonClickCaptureСвязано ли оно на этапе захвата событий?

B7836791-2C40-48BA-83BF-835E0BD87B55.jpg

основные концепции знаний

выяснениеreactПеред событием мы должны выяснить несколько концепций, потому что только поняв эти концепции, мы сможем лучше понять этап запуска события.reactОбработайте характер события.

Какими будут события, которые мы пишем в JSX?

Давайте сначала напишем абзац с событием кликаreact JSXсинтаксис, посмотрите, как это выглядит в итоге?

class Index extends React.Component{
    handerClick= (value) => console.log(value) 
    render(){
        return <div>
            <button onClick={ this.handerClick } > 按钮点击 </button>
        </div>
    }
}

проходить черезbabelПеревести вReact.createElementформу следующим образом:

babel.jpg

в итоге превратился вfiberФорма объекта следующая:

fiber.jpg

fiberна объектеmemoizedPropsа такжеpendingPropsСохранили наше мероприятие.

Что такое синтетическое событие?

Как мы видели на предыдущем шаге, мы объявляем место, где сохраняются события. Но действительно ли событие зарегистрировано? Давайте посмотрим дальше:

Давайте посмотрим на текущий элемент<button>Есть ли какая-либо привязка к этому прослушивателю событий?

button_event.jpg

событие привязано к кнопке

Мы видим, что ,buttonС ним связаны два события, одноdocumentпрослушиватель событий включен, другойbutton, но обработчик событияhandle, не нашhanderClickсобытие, ноnoop.

noopЧто тогда? Посмотрим дальше.

оказалосьnoopОн указывает на пустую функцию.

noop.jpg

Затем мы видимdocumentсвязанное событие

document.jpg

можно увидетьclickсобытие связано сdocumentвверх.

Дальше будем делать дела 😂😂😂, вdemoдобавить один в проектinputполе ввода и привязатьonChangeмероприятие. Открой глаза, чтобы увидеть, что будет дальше?

class Index extends React.Component{
    componentDidMount(){
        console.log(this)
    }
    handerClick= (value) => console.log(value) 
    handerChange=(value) => console.log(value)
    render(){
        return <div style={{ marginTop:'50px' }} >
            <button onClick={ this.handerClick } > 按钮点击 </button>
            <input  placeholder="请输入内容" onChange={ this.handerChange }  />
        </div>
    }
}

Давайте сначала посмотримinput domсобытия, привязанные к элементам

22BEC470-233A-4C50-9C47-D21D343C055D.jpg

Затем мы смотрим наdocumentсобытие, связанное с

8E1D3BDB-ACFB-4E49-A5FF-CF990C47A60E.jpg

мы находим, что мы даем<input>границаonChange, и не связан напрямую сinput, но равномерно связан сdocumentдальше, то мыonChangeобрабатываются как многие прослушиватели событий, такие какblur , change , input , keydown , keyupЖдать.

Подводя итог, мы можем сделать вывод, что:

  • мы вjsxСобытия связаны в (демоhanderClick,handerChange), вообще не прописан к реальномуdomначальство. связан сdocumentединое управление.

  • настоящийdomВверхclickСобытия обрабатываются отдельно иreactНижний слой заменяется пустой функцией.

  • мы вreactОбязательные события, такие какonChange,существуетdocument, ему может соответствовать несколько событий.

  • reactНе сначала, привяжите все события вdocument, но принимает привязку по запросу, например обнаружениеonClickсобытие, перейти к привязкеdocument clickмероприятие.

Так что жеreactКак насчет синтеза событий?

существуетreactв случае, если мы связываемonClickт. д., не нативные события, а синтезированные нативными событиямиReactсобытия, такие какclickСобытия синтезируются какonClickмероприятие. Напримерblur , change , input , keydown , keyupдр., синтезированные какonChange.

ТакreactКак насчет использования этой модели синтеза событий?

С одной стороны, привязать событие вdocumentУнифицированное управление для предотвращения прямой привязки многих событий к нативномуdomна элементе. вызвать неконтролируемые ситуации

с другой стороны,ReactЧтобы создать полноценную инфраструктуру браузера, для достижения этой цели необходимо предоставить систему событий, согласованную во всех браузерах, чтобы сгладить различия между разными браузерами.

В следующей статье мы познакомимreactКак сделать синтез событий.

Объект тега волокна, соответствующий элементу dom

мы знаемreactКак сохранить нашу функцию события и причинно-следственную связь синтеза событий. Далее я хочу, чтобы вы запомнили типfiberОбъект, потому что он будет использоваться позже, что очень полезно для последующего понимания.

Давайте сначала посмотрим на фрагмент кода:

<div> 
  <div> hello , my name is alien </div>
</div>

Смотреть <div> hello , my name is alien </div>соответствующийfiberТипы. тег = 5

B7514E02-E542-4BDC-8CF3-F198D094A9D3.jpgтогда мы идемreactНашел такой в ​​исходникахfiberТипы.

/react-reconciler/src/ReactWorkTagsq.js

export const HostComponent = 5; // 元素节点

ок, поставимHostComponentа такжеHostTextЗапишите 📝 вниз. Теперь вернемся к основной теме, давайте посмотримreactМеханизм синтеза событий.

Инициализация двух событий - синтез событий, подключаемый механизм

Далее давайте посмотримreactТак синтезируются события. Сначала мы знаем свыше, что,reactне все события сразусвязывать, но если окажется, что проектonClick, перед привязкойclickсобытие, обнаруженноеonChangeсобытие, перед привязкойblur , change , input , keydown , keyupЖдать. Итак, чтобы прояснить принцип, автор разделил принцип события на три части:

  • 1 reactкак синтезируются события.
  • 2 reactКак связаны события.
  • 3 reactСобытия запускают процессы.

Синтез событий — плагин событий

1 Основные понятия

Давайте сначала рассмотрим несколько постоянных соотношений, которые нам нужно понять.reactПринцип события полезен. В объяснении анализа я также расскажу о том, откуда берутся эти объекты и что они делают.

①имяToPlugins

Первая концепция:namesToPluginsУстановить имя события -> сопоставление плагинов модуля событий,namesToPluginsОкончательный вид выглядит следующим образом:

const namesToPlugins = {
    SimpleEventPlugin,
    EnterLeaveEventPlugin,
    ChangeEventPlugin,
    SelectEventPlugin,
    BeforeInputEventPlugin,
}

SimpleEventPluginEtc. — это подключаемый модуль, который обрабатывает каждую функцию события, такую ​​как событие клика, вы найдетеSimpleEventPluginсоответствующую функцию обработки. Давайте сначала запишем его, а что он делает, мы поговорим об этом позже.

②плагины

plugins, этот объект представляет собой список всех плагинов, зарегистрированных выше, инициализированных пустыми.

const  plugins = [LegacySimpleEventPlugin, LegacyEnterLeaveEventPlugin, ...];

③registrationNameModules

registrationNameModulesСвязь между событиями, синтезируемыми React, и соответствующими плагинами событий записывается вReactв процессеpropsКогда событие находится в середине события, оно найдет соответствующий подключаемый модуль события в соответствии с разными именами события, а затем единообразно привяжет его вdocumentначальство. Для событий, которые не появились, они не будут привязаны, о чем мы поговорим далее.registrationNameModulesОбщий вид выглядит следующим образом.

{
    onBlur: SimpleEventPlugin,
    onClick: SimpleEventPlugin,
    onClickCapture: SimpleEventPlugin,
    onChange: ChangeEventPlugin,
    onChangeCapture: ChangeEventPlugin,
    onMouseEnter: EnterLeaveEventPlugin,
    onMouseLeave: EnterLeaveEventPlugin,
    ...
}

④Плагин событий

Итак, сначала мы должны выяснить,SimpleEventPlugin,EnterLeaveEventPluginЧто представляет собой каждый плагин? мы принимаемSimpleEventPluginНапример, как это выглядит?

const SimpleEventPlugin = {
    eventTypes:{ 
        'click':{ /* 处理点击事件  */
            phasedRegistrationNames:{
                bubbled: 'onClick',       // 对应的事件冒泡 - onClick 
                captured:'onClickCapture' //对应事件捕获阶段 - onClickCapture
            },
            dependencies: ['click'], //事件依赖
            ...
        },
        'blur':{ /* 处理失去焦点事件 */ },
        ...
    }
    extractEvents:function(topLevelType,targetInst,){ /* eventTypes 里面的事件对应的统一事件处理函数,接下来会重点讲到 */ }
}

Сначала подключаемый модуль события представляет собой объект с двумя свойствами, первоеextractEventsВ качестве функции унифицированного обработчика событий втораяeventTypesэто объект, который сохраняет собственное имя события и соответствующие элементы конфигурацииdispatchConfigкартографические отношения. Поскольку события v16React единообразно связаны вdocumentВыше React использует уникальные имена событий, такие какonClickа такжеonClickCapture, чтобы указать, выполняется ли функция, которую мы связываем, в фазе всплывающего события или в фазе захвата события.

⑤ регистрацияИмяЗависимости

registrationNameDependenciesИспользуется для записи, синтеза таких событий, какonClickи родные событияclickПереписка. НапримерonChangeвести перепискуchange , input , keydown , keyupмероприятие.

{
    onBlur: ['blur'],
    onClick: ['click'],
    onClickCapture: ['click'],
    onChange: ['blur', 'change', 'click', 'focus', 'input', 'keydown', 'keyup', 'selectionchange'],
    onMouseEnter: ['mouseout', 'mouseover'],
    onMouseLeave: ['mouseout', 'mouseover'],
    ...
}

2 Инициализация события

Для синтеза событийv16.13.1ВерсияreactПринят метод первоначальной регистрации.

react-dom/src/client/ReactDOMClientInjection.js

/* 第一步:注册事件:  */
injectEventPluginsByName({
    SimpleEventPlugin: SimpleEventPlugin,
    EnterLeaveEventPlugin: EnterLeaveEventPlugin,
    ChangeEventPlugin: ChangeEventPlugin,
    SelectEventPlugin: SelectEventPlugin,
    BeforeInputEventPlugin: BeforeInputEventPlugin,
});

injectEventPluginsByNameКаково конкретное использование этой функции, это вreactНижний слой реализован по умолчанию. Давайте упростим эту функцию и посмотрим, что она делает.

legacy-event/EventPluginRegistry.js

/* 注册事件插件 */
export function injectEventPluginsByName(injectedNamesToPlugins){
     for (const pluginName in injectedNamesToPlugins) {
         namesToPlugins[pluginName] = injectedNamesToPlugins[pluginName]
     }
     recomputePluginOrdering()
}

injectEventPluginsByNameСделать это просто, сформируйте вышеnamesToPlugins, затем выполнитеrecomputePluginOrdering, Давайте посмотрим наrecomputePluginOrderingЧто ты сделал, чтобы написать?

const eventPluginOrder = [ 'SimpleEventPlugin' , 'EnterLeaveEventPlugin','ChangeEventPlugin','SelectEventPlugin' , 'BeforeInputEventPlugin' ]

function recomputePluginOrdering(){
    for (const pluginName in namesToPlugins) {
        /* 找到对应的事件处理插件,比如 SimpleEventPlugin  */
        const pluginModule = namesToPlugins[pluginName];
        const pluginIndex = eventPluginOrder.indexOf(pluginName);
        /* 填充 plugins 数组  */
        plugins[pluginIndex] = pluginModule;
        const publishedEvents = pluginModule.eventTypes;
    for (const eventName in publishedEvents) {
       // publishedEvents[eventName] -> eventConfig , pluginModule -> 事件插件 , eventName -> 事件名称
        publishEventForPlugin(publishedEvents[eventName],pluginModule,eventName,)
    } 
    }


}

recomputePluginOrdering, роль очень ясна, образуя упомянутую вышеpluginsМножество. Тогда естьключевая функцияpublishEventForPlugin.

/*
  dispatchConfig -> 原生事件对应配置项 { phasedRegistrationNames :{  冒泡 捕获  } ,   }
  pluginModule -> 事件插件 比如SimpleEventPlugin  
  eventName -> 原生事件名称。
*/
function publishEventForPlugin (dispatchConfig,pluginModule,eventName){
    eventNameDispatchConfigs[eventName] = dispatchConfig;
    /* 事件 */
    const phasedRegistrationNames = dispatchConfig.phasedRegistrationNames;
    if (phasedRegistrationNames) {
    for (const phaseName in phasedRegistrationNames) {
        if (phasedRegistrationNames.hasOwnProperty(phaseName)) {
            // phasedRegistrationName React事件名 比如 onClick / onClickCapture
            const phasedRegistrationName = phasedRegistrationNames[phaseName];
            // 填充形成 registrationNameModules React 合成事件 -> React 处理事件插件映射关系
            registrationNameModules[phasedRegistrationName] = pluginModule;
            // 填充形成 registrationNameDependencies React 合成事件 -> 原生事件 映射关系
            registrationNameDependencies[phasedRegistrationName] = pluginModule.eventTypes[eventName].dependencies;
        }
    }
    return true;
    }
}

publishEventForPluginфункция для формирования вышеуказанногоregistrationNameModulesа такжеregistrationNameDependenciesОтображение отношений в объектах.

3 Резюме синтеза событий

Теперь, когда вся фаза инициализации завершена, позвольте мне подытожить, что сделал синтез событий инициализации. На этом этапе в основном формируются упомянутые выше важные объекты, строится соответствующая связь между исходным синтетическим событием React и нативным событием, а также связь между синтетическим событием и соответствующим плагином обработки событий. Далее следует фаза привязки событий.

Привязка трех событий — начиная с события клика

процесс привязки событий

Если мы напишем событие щелчка в таком компоненте,ReactКак с этим бороться шаг за шагом.

1 diffProperties обрабатывает синтетические события React

<div>
  <button onClick={ this.handerClick }  className="button" >点击</button>
</div>

Первый шаг, сначала через приведенное выше объяснение, мы привязываемся к волокну типа hostComponent (например, к элементу кнопки выше), будетbuttonна соответствующем волокне, сmemoizedPropsа такжеpendingPropsСохранение формы.

button 对应 fiber
memoizedProps = {
   onClick:function handerClick(){},
   className:'button'
}

Структурная схема выглядит следующим образом:

58E6A4AF-1902-42BC-9D11-B47234037E01.jpg

На втором этапе, после согласования дочерних узлов, React входит в стадию сравнения.HostComponent(элемент dom) тип волокна, будет использовать функцию diff propsdiffPropertiesобрабатывается отдельно.

react-dom/src/client/ReactDOMComponent.js

function diffProperties(){
    /* 判断当前的 propKey 是不是 React合成事件 */
    if(registrationNameModules.hasOwnProperty(propKey)){
         /* 这里多个函数简化了,如果是合成事件, 传入成事件名称 onClick ,向document注册事件  */
         legacyListenToEvent(registrationName, document);
    }
}

diffPropertiesфункционировать вdiff propsЕсли обнаружено, что это синтетическое событие (onClick) позвонюlegacyListenToEventфункция. Зарегистрируйте прослушиватели событий.

2 прослушиватель событий регистрации legacyListenToEvent

react-dom/src/events/DOMLegacyEventPluginSystem.js

//  registrationName -> onClick 事件
//  mountAt -> document or container
function legacyListenToEvent(registrationName,mountAt){
   const dependencies = registrationNameDependencies[registrationName]; // 根据 onClick 获取  onClick 依赖的事件数组 [ 'click' ]。
    for (let i = 0; i < dependencies.length; i++) {
    const dependency = dependencies[i];
    //这个经过多个函数简化,如果是 click 基础事件,会走 legacyTrapBubbledEvent ,而且都是按照冒泡处理
     legacyTrapBubbledEvent(dependency, mountAt);
  }
}

legacyTrapBubbleEvent — это функция, которая выполняет реальное событие dom, которое будет привязано к legacyTrapBubbleEvent (обработка пузырьком).

function legacyTrapBubbledEvent(topLevelType,element){
   addTrappedEventListener(element,topLevelType,PLUGIN_EVENT_SYSTEM,false)
}

Шаг 3: вlegacyListenToEventфункция, сначала найтиReactКоллекция собственных событий, соответствующих синтетическим событиям, например onClick -> ['click'] , onChange -> [blur , change , input , keydown , keyup], затем перебирает массив зависимостей, связывает события,Это объясняет, почему мы привязали только один элемент к элементу в начальной демонстрации.onChangeсобытие, в результате которогоdocumentПричина, по которой в этой функции много прослушивателей событий, заключается в том, что она обрабатывается в этой функции.

Мы уже показали, что React использует привязку событий,ReactдляclickТакие базовые события будут обрабатываться в соответствии с событиями на этапе всплытия событий по умолчанию.Однако это не является абсолютным, например обработка некоторых событий, а некоторые специальные события обрабатываются в соответствии с захватом событий.

case TOP_SCROLL: {                                // scroll 事件
    legacyTrapCapturedEvent(TOP_SCROLL, mountAt); // legacyTrapCapturedEvent 事件捕获处理。
    break;
}
case TOP_FOCUS: // focus 事件
case TOP_BLUR:  // blur 事件
legacyTrapCapturedEvent(TOP_FOCUS, mountAt);
legacyTrapCapturedEvent(TOP_BLUR, mountAt);
break;

3 Привяжите dispatchEvent для отслеживания событий

как указано вышеscrollмероприятие,focusмероприятие ,blurСобытия и т. д. по умолчанию обрабатываются в соответствии с логикой захвата событий. Следующий шаг — самый важный и решающий. Как React связывает события сdocument? Что такое функция обработчика событий? Все проблемы указывают на вышеперечисленноеaddTrappedEventListener, давайте приоткроем завесу над ним.

/*
  targetContainer -> document
  topLevelType ->  click
  capture = false
*/
function addTrappedEventListener(targetContainer,topLevelType,eventSystemFlags,capture){
   const listener = dispatchEvent.bind(null,topLevelType,eventSystemFlags,targetContainer) 
   if(capture){
       // 事件捕获阶段处理函数。
   }else{
       /* TODO: 重要, 这里进行真正的事件绑定。*/
      targetContainer.addEventListener(topLevelType,listener,false) // document.addEventListener('click',listener,false)
   }
}

Шаг 4: Хотя содержание этой функции невелико, оно очень важно.Во-первых, привяжите нашу унифицированную функцию обработки событий.dispatchEvent, привязать несколько параметров по умолчанию, тип событияtopLevelTypeв демоclickи связанный контейнерdoucment.Затем привязка реального события, добавление прослушивателей событийaddEventListener.Фаза привязки события завершена.

4 Краткое описание процесса привязки событий

Давайте подведем итоги фазы привязки событий.

  • ① В React при различии реквизитов волокна типа элемента DOM, если обнаружено, что это событие синтеза React, напримерonClick, которые будут обрабатываться отдельно в соответствии с логикой системы событий.
  • ② В соответствии с синтетическим типом события React найдите соответствующий собственный тип события, а затем вызовите, чтобы оценить собственный тип события.Большинство событий обрабатываются в соответствии с логикой всплытия, а некоторые события обрабатываются в соответствии с логикой захвата (например,scrollмероприятие).
  • ③ Вызовите addTrappedEventListener для привязки реального события, которое привязано вdocumentначальство,dispatchEventЭто унифицированный обработчик событий.
  • Стоит отметить одну вещь: только вышеупомянутые специальные события, такие какscorll,focus,blurи т. д. происходит на этапе захвата события, все остальное происходит на этапе всплытия события, либоonClickещеonClickCaptureпроисходит в стадии бульканья, что касается того, как сам React обрабатывает логику захвата. Мы доберемся до этого дальше.

Четыре триггера события — событие щелчка, вreactЧто происходит с базовой системой?

<div>
  <button onClick={ this.handerClick }  className="button" >点击</button>
</div>

Или приведенный выше фрагмент кода, когда кнопка нажата, вReactЧто происходит внизу? Далее, позвольте мне вместе исследовать тайну запуска события.

Обработчик триггера события dispatchEvent

Как мы упоминали на этапе привязки событий, когда регистрируются события React, единый прослушивательdispatchEvent, то есть когда мыПосле нажатия кнопки появляется первыйdispatchEventфункция,потому чтоdispatchEventПервые три параметра были привязаны, поэтому реальный объект источника событияevent, который по умолчанию привязан как четвертый параметр.

react-dom/src/events/ReactDOMEventListener.js

function dispatchEvent(topLevelType,eventSystemFlags,targetContainer,nativeEvent){
    /* 尝试调度事件 */
    const blockedOn = attemptToDispatchEvent( topLevelType,eventSystemFlags, targetContainer, nativeEvent);
}
/*
topLevelType -> click
eventSystemFlags -> 1
targetContainer -> document
nativeEvent -> 原生事件的 event 对象
*/
function attemptToDispatchEvent(topLevelType,eventSystemFlags,targetContainer,nativeEvent){
    /* 获取原生事件 e.target */
    const nativeEventTarget = getEventTarget(nativeEvent)
    /* 获取当前事件,最近的dom类型fiber ,我们 demo中 button 按钮对应的 fiber */
    let targetInst = getClosestInstanceFromNode(nativeEventTarget); 
    /* 重要:进入legacy模式的事件处理系统 */
    dispatchEventForLegacyPluginEventSystem(topLevelType,eventSystemFlags,nativeEvent,targetInst,);
    return null;
}

Главное, что нужно сделать на этом этапе:

  • ① Во-первых, в соответствии с реальным объектом-источником события найдитеe.targetнастоящийdomэлемент.
  • ② Тогда согласноdomэлемент, найдите соответствующийfiberобъектtargetInst, в нашемdemoв, найтиbuttonсоответствующий кнопкеfiber.
  • ③ Затем войдитеlegacyСистема обработки событий шаблона, то есть шаблон React, который мы сейчас используем,legacyВ этом режиме начинает действовать принцип пакетного обновления.

Здесь есть небольшая проблема,Reactкак пройти роднойdomэлемент, найдите соответствующийfiberчто о?то естьgetClosestInstanceFromNodeКаков принцип?

Ответ первыйgetClosestInstanceFromNodeможет найти текущий входящийdomсоответствующий тип ближайшего элементаfiberобъект.Reactв инициализации реальныйdom, использовать случайныйkey internalInstanceKeyуказатель указывает на текущийdomсоответствующийfiberобъект,fiberобъектstateNodeуказывает на текущийdomэлемент.

// 声明随机key
var internalInstanceKey = '__reactInternalInstance$' + randomKey;

// 使用随机key 
function getClosestInstanceFromNode(targetNode){
  // targetNode -dom  targetInst -> 与之对应的fiber对象
  var targetInst = targetNode[internalInstanceKey];
}

посмотри в отладчике гугла

fiber_dom.jpg

Отношения между двумя

dom_fiber.jpg

устаревшая система обработки событий и пакетные обновления

react-dom/src/events/DOMLegacyEventPluginSystem.js

/* topLevelType - click事件 | eventSystemFlags = 1 | nativeEvent = 事件源对象  | targetInst = 元素对应的fiber对象  */
function dispatchEventForLegacyPluginEventSystem(topLevelType,eventSystemFlags,nativeEvent,targetInst){
    /* 从React 事件池中取出一个,将 topLevelType ,targetInst 等属性赋予给事件  */
    const bookKeeping = getTopLevelCallbackBookKeeping(topLevelType,nativeEvent,targetInst,eventSystemFlags);
    try { /* 执行批量更新 handleTopLevel 为事件处理的主要函数 */
    batchedEventUpdates(handleTopLevel, bookKeeping);
  } finally {
    /* 释放事件池 */  
    releaseTopLevelCallbackBookKeeping(bookKeeping);
  }
}

Что касается пула событий v16, мы поговорим об этом сначала.batchedEventUpdatesОсновная функция пакетного обновления. Давайте сначала посмотримbatchedEventUpdates

react-dom/src/events/ReactDOMUpdateBatching.js

export function batchedEventUpdates(fn,a){
    isBatchingEventUpdates = true;
    try{
       fn(a) // handleTopLevel(bookKeeping)
    }finally{
        isBatchingEventUpdates = false
    }
}

Пакетное обновление упрощено, как описано выше.Из вышеизложенного мы видим, что React передает переключательisBatchingEventUpdatesчтобы контролировать, включены ли пакетные обновления.fn(a), который вызывается по событиюhandleTopLevel(bookKeeping), Поскольку js является однопоточным, мы действительно пишем обработчики событий в компонентах, таких как демонстрационныеhanderClickФактическая реализацияhandleTopLevel(bookKeeping)казнен в. так что если мыhanderClickвнутренний триггерsetState, тогда вы можете прочитать isBatchingEventUpdates = trueВот почему синтетические события React имеют пакетные обновления. Например, мы пишем

state={number:0}
handerClick = () =>{
    this.setState({number: this.state.number + 1   })
    console.log(this.state.number) //0
    this.setState({number: this.state.number + 1   })
    console.log(this.state.number) //0
    setTimeout(()=>{
        this.setState({number: this.state.number + 1   })
        console.log(this.state.number) //2
        this.setState({number: this.state.number + 1   })
        console.log(this.state.number)// 3
    })
}

Как показано выше, первыйsetStateи второйsetStateВыполняется в условиях пакетного обновления, поэтому печать не будет последним значением, но если оноsetTimeout, так как eventLoop выполняется в следующем цикле событий, в это время выполняется batchedEventUpdatesisBatchingEventUpdates = false, поэтому пакетное обновление прерывается, и у нас есть прямой доступ к последнему измененному значению.

Далее у нас есть два неразобранных момента:

  • Одним из них является концепция пула событий React.
  • Во-вторых, последняя подсказка - выполнитьhandleTopLevel(bookKeeping),ТакhandleTopLevelЧто ты сделал, чтобы написать.

Выполнить функцию плагина события

Вся система событий упомянута выше и, наконец, указывает на функциюhandleTopLevel(bookKeeping)ТакhandleTopLevelЧто именно он сделал?

// 流程简化后
// topLevelType - click  
// targetInst - button Fiber
// nativeEvent
function handleTopLevel(bookKeeping){
    const { topLevelType,targetInst,nativeEvent,eventTarget, eventSystemFlags} = bookKeeping
    for(let i=0; i < plugins.length;i++ ){
        const possiblePlugin = plugins[i];
        /* 找到对应的事件插件,形成对应的合成event,形成事件执行队列  */
        const  extractedEvents = possiblePlugin.extractEvents(topLevelType,targetInst,nativeEvent,eventTarget,eventSystemFlags)  
    }
    if (extractedEvents) {
        events = accumulateInto(events, extractedEvents);
    }
    /* 执行事件处理函数 */
    runEventsInBatch(events);
}

Я упростил весь процесс и оставил только основной процесс,handleTopLevelОкончательная логика обработки заключается в выполнении функции обработки в том, что мы называем подключаемым модулем обработки событий (SimpleEventPlugin).extractEvents, например, событие клика onClick в нашей демонстрации, наконец, переходит кSimpleEventPluginсерединаextractEventsфункция, так почему React делает это?Мы знаем, что наш React использует синтез событий, унифицированную привязку событий, а обработчик событий (handerClick), который мы пишем в компоненте, не является реальной функцией выполненияdispatchAciton, то мыhanderClickобъект событияevent, также синтезируется и обрабатывается только React, который инкапсулирует отдельно, напримерstopPropagationа такжеpreventDefaultи так далее,Преимущество этого в том, что нам не нужно решать проблемы совместимости отдельно для разных браузеров, и мы оставляем React решать их единообразно внизу.

ExtractEvents формирует событие объекта события и очередь обработчика событий

Вот в чем дело! Вот в чем дело! Вот в чем дело!, ExtractEvents можно использовать как основную функцию всей системы событий, давайте вернемся к исходнойdemo, Если мы пишем так, то четыре функции обратного вызова, затем нажмите кнопку, как обрабатываются четыре события. Во-первых, если вы нажмете кнопку, вы получитеextractEventsфункцию, проверьте эту функцию.

legacy-events/SyntheticEvent.js

const  SimpleEventPlugin = {
    extractEvents:function(topLevelType,targetInst,nativeEvent,nativeEventTarget){
        const dispatchConfig = topLevelEventsToDispatchConfig.get(topLevelType);
        if (!dispatchConfig) {
            return null;
        }
        switch(topLevelType){
            default:
            EventConstructor = SyntheticEvent;
            break;
        }
        /* 产生事件源对象 */
        const event = EventConstructor.getPooled(dispatchConfig,targetInst,nativeEvent,nativeEventTarget)
        const phasedRegistrationNames = event.dispatchConfig.phasedRegistrationNames;
        const dispatchListeners = [];
        const {bubbled, captured} = phasedRegistrationNames; /* onClick / onClickCapture */
        const dispatchInstances = [];
        /* 从事件源开始逐渐向上,查找dom元素类型HostComponent对应的fiber ,收集上面的React合成事件,onClick / onClickCapture  */
         while (instance !== null) {
              const {stateNode, tag} = instance;
              if (tag === HostComponent && stateNode !== null) { /* DOM 元素 */
                   const currentTarget = stateNode;
                   if (captured !== null) { /* 事件捕获 */
                        /* 在事件捕获阶段,真正的事件处理函数 */
                        const captureListener = getListener(instance, captured);
                        if (captureListener != null) {
                        /* 对应发生在事件捕获阶段的处理函数,逻辑是将执行函数unshift添加到队列的最前面 */
                            dispatchListeners.unshift(captureListener);
                            dispatchInstances.unshift(instance);
                            dispatchCurrentTargets.unshift(currentTarget);
                        }
                    }
                    if (bubbled !== null) { /* 事件冒泡 */
                        /* 事件冒泡阶段,真正的事件处理函数,逻辑是将执行函数push到执行队列的最后面 */
                        const bubbleListener = getListener(instance, bubbled);
                        if (bubbleListener != null) {
                            dispatchListeners.push(bubbleListener);
                            dispatchInstances.push(instance);
                            dispatchCurrentTargets.push(currentTarget);
                        }
                    }
              }
              instance = instance.return;
         }
          if (dispatchListeners.length > 0) {
              /* 将函数执行队列,挂到事件对象event上 */
            event._dispatchListeners = dispatchListeners;
            event._dispatchInstances = dispatchInstances;
            event._dispatchCurrentTargets = dispatchCurrentTargets;
         }
        return event
    }
}

Ядро системы плагинов событийextractEventsГлавное, что нужно сделать:

  • ① Сформируйте сначалаReactСпецифический для события синтетический исходный объект события, этот объект, содержит информацию обо всем событии. Будет передан в качестве параметра реальному обработчику события (handerClick).
  • ② Затем объявите очередь выполнения события в соответствии с冒泡а также捕获Логика, начиная с источника события постепенно, чтобы найти соответствующий тип HostComponent HostComponent WhostComponent Fiber DOM, собранный вышеReactСинтетические события, такие какonClick / onClickCapture, для событий в фазе пузыря (onClick),Будуpushв конец очереди выполнения для событий в фазе захвата (onClickCapture),БудуunShiftв начало очереди выполнения.
  • ③ Наконец, сохраните очередь выполнения события в исходный объект события React. Ожидает исполнения.

Например, следующим образом

handerClick = () => console.log(1)
handerClick1 = () => console.log(2)
handerClick2 = () => console.log(3) 
handerClick3= () => console.log(4)
render(){
    return <div onClick={ this.handerClick2 } onClickCapture={this.handerClick3}  > 
        <button onClick={ this.handerClick }  onClickCapture={ this.handerClick1  }  className="button" >点击</button>
    </div>
}

напечатать // 4 2 1 3

Увидев это, мы должны понять, почему указанные выше функции печатаются по порядку.buttonСоответствующее волокно, впервые встретившеесяonClickCapture,БудуhanderClick1поместите его в начало массива, а затем поместитеonClickвести перепискуhanderClickпомещается в конец массива, результирующая структура имеет вид[ handerClick1 , handerClick ], а затем двигайтесь вверх, встречаяdivВ соответствии с волокном,onClickCaptureсоответствующийhanderClick3помещается перед массивом,onClickсоответствующийhanderClick2После массива образовалась структура[ handerClick3,handerClick1 , handerClick,handerClick2 ], поэтому порядок выполнения // 4 2 1 3, это так просто, идеально!

FDEBA681-2E03-420B-A838-5907439837A9.jpg

триггер события

Некоторым учащимся может быть любопытно, как выглядит исходный объект события React.SyntheticEventДавайте рассмотрим пример:

legacy-events/SyntheticEvent.js/

function SyntheticEvent( dispatchConfig,targetInst,nativeEvent,nativeEventTarget){
  this.dispatchConfig = dispatchConfig;
  this._targetInst = targetInst;
  this.nativeEvent = nativeEvent;
  this._dispatchListeners = null;
  this._dispatchInstances = null;
  this._dispatchCurrentTargets = null;
  this.isPropagationStopped = () => false; /* 初始化,返回为false  */

}
SyntheticEvent.prototype={
    stopPropagation(){ this.isPropagationStopped = () => true;  }, /* React单独处理,阻止事件冒泡函数 */
    preventDefault(){ },  /* React单独处理,阻止事件捕获函数  */
    ...
}

существуетhanderClickпечатать вe :

B9180401-93FF-4EF0-A2FB-C2FA43B29550.jpg

Теперь, когда очередь выполнения события и объект-источник события сформированы, следующий шаг — последний.триггер события. Вы заметили функцию выше?runEventsInBatch, здесь запускаются все функции привязки событий. Давайте взглянем.

legacy-events/EventBatching.js

function runEventsInBatch(){
    const dispatchListeners = event._dispatchListeners;
    const dispatchInstances = event._dispatchInstances;
    if (Array.isArray(dispatchListeners)) {
    for (let i = 0; i < dispatchListeners.length; i++) {
      if (event.isPropagationStopped()) { /* 判断是否已经阻止事件冒泡 */
        break;
      }
      
      dispatchListeners[i](event)
    }
  }
  /* 执行完函数,置空两字段 */
  event._dispatchListeners = null;
  event._dispatchInstances = null;
}

dispatchListeners[i](event)заключается в выполнении нашей функции обработчика событий, такой какhanderClick, отсюда мы знаем,В обработчике событий возврат false не препятствует поведению браузера по умолчанию..

handerClick(){ //并不能阻止浏览器默认行为。
    return false
}

Это должно быть изменено на это:

handerClick(e){
    e.preventDefault()
}

С другой стороны, чтобы предотвратить всплывание, React использует isPropagationStopped, чтобы определить, было ли предотвращено всплытие события. Если мы находимся в очереди выполнения событийной функции, то в определенной функции вызовемe.stopPropagation(), будет присвоеноisPropagationStopped=()=>true, при повторном выполненииe.isPropagationStopped()вернусьtrue, следующий обработчик события не будет выполнен.

Другие концепции — пул событий

 handerClick = (e) => {
    console.log(e.target) // button 
    setTimeout(()=>{
        console.log(e.target) // null
    },0)
}

Для обработчика события щелчка печатайте в обычном контексте выполнения функции.e.targetэто указывает наdomэлемент, но вsetTimeoutпечатать вnull, если это не система событий React, два принта должны быть одинаковыми, но почему два принта разные?Поскольку React принимает концепцию пула событий, каждый раз, когда мы используем объект источника события, после выполнения функции события его можно передать черезreleaseTopLevelCallbackBookKeepingи другие методы выпуска объекта-источника события в пул событий. Преимущество этого заключается в том, что не нужно каждый раз создавать объект-источник события. Мы можем извлечь объект-источник события из пула событий для повторного использования. После того, как обработчик события выполнено, событие будет выпущено.Отправьте источник в пул событий, очистите свойства, этоsetTimeoutраспечатать почемуnullпричина.

Сводка триггеров событий

Я резюмирую, что я делаю на этапе запуска события:

  • ① Сначала через унифицированную функцию обработки событийdispatchEvent, чтобы выполнить пакетное обновление batchUpdate.

  • ② Затем запустите подключаемый модуль обработки, соответствующий событию.extractEvents, Синтезировать объект источника события.Каждый раз, когда React будет запускаться из источника события, проходить по волокну типа hostComponent, то есть типа dom, чтобы определить, есть ли в реквизитах текущее событие, например, onClick, и, наконец, сформировать очередь выполнения событий. React использует эту очередь для имитации процесса захвата событий -> источника событий -> всплытия событий.

  • ③ Наконец-то пройденоrunEventsInBatchВыполните очередь событий, если она будет обнаружена, чтобы предотвратить всплытие, выйти из цикла и, наконец, сбросить источник события, поместить его обратно в пул событий и завершить весь процесс.

evnent_click.jpg

Пять о системе событий версии react v17

Общие изменения в React v17 не очень большие, но и изменения в системе событий не маленькие.Прежде всего, многие из вышеперечисленных функций исполнения больше не существуют в версии v17. Позвольте мне кратко описать ревизию системы событий v17.

1 Событие, связывающее единство на контейнере, Reactom.runder (приложение, контейнер); и не в документе, это преимущество способствует микросхожению, передний конец, передний конец фронтальной системы Micro может иметь несколько приложений, если она продолжается взять все связанныеdocument, то могут быть проблемы с несколькими приложениями.

react_17_delegation.png

2 Выровняйте нативные события браузера

React 17 Наконец, поддерживается собственная поддержка событий захвата в соответствии с собственными стандартами браузера. в то же времяonScrollСобытия больше не всплывают.onFocusа такжеonBlurиспользовать роднойfocusin,focusoutсинтез.

React 17 Отмените пул событий, вышеперечисленное решеноsetTimeoutпечать, не найденоe.targetЭта проблема.

Шесть резюме

Эта статья изсинтез событий,привязка события,триггер событияПринципы системы событий React подробно представлены в трех аспектах. Я надеюсь, что вы сможете узнать больше о системе событий React v16 из этой статьи. Если у вас есть какие-либо вопросы или недостатки, я надеюсь, что вы можете указать на них в области комментариев. .

Напоследок подарите розы и оставьте в руках стойкий аромат.Друзья, которые чувствуют, что что-то приобрели, могут подарить авторуНравится, следуйОдна волна, обновляйте фронтальные суперхардкорные статьи одну за другой.

Предварительное раскрытие: раскрытие будет опубликовано позжеreactСтатьи о системах планирования. Заинтересованные студенты, пожалуйста, обратите внимание на общедоступный номерСовместное использование внешнего интерфейсаОбновите внешний жесткий текст в первый раз.

Предыдущая статья о реакции

Продвинутая серия React

Справочная документация