Введение
Давайте поговорим об этом сегодняReact
Принцип событий, в этой статье я стараюсь использовать популярный и лаконичный способ изложитьReact
Система событий говорит четко.
что мы сказалиreact
версия16.13.1
, v17
Позжеreact
Будут соответствующие изменения в системе событий, которые будут упомянуты во второй половине статьи.
Старые правила, в официальном объясненииreact
Прежде давайте подумаем над этими вопросами (Если бы я был интервьюером, как бы вы ответили?):
- 1 Событие, которое мы написали, связано
dom
На, если нет, то куда он привязан? - 2 Почему наши события не могут быть привязаны к компонентам?
- 3 Почему наши события связаны вручную
this
(Не случай стрелочных функций) - 4 Почему бы не использовать
return false
предотвратить поведение событий по умолчанию? - 5
react
как пройтиdom
элемент, найдите соответствующийfiber
объект? - 6
onClick
Связано ли оно во время фазы образования пузырей? ТакonClickCapture
Связано ли оно на этапе захвата событий?
основные концепции знаний
выяснение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
форму следующим образом:
в итоге превратился вfiber
Форма объекта следующая:
fiber
на объектеmemoizedProps
а такжеpendingProps
Сохранили наше мероприятие.
Что такое синтетическое событие?
Как мы видели на предыдущем шаге, мы объявляем место, где сохраняются события. Но действительно ли событие зарегистрировано? Давайте посмотрим дальше:
Давайте посмотрим на текущий элемент<button>
Есть ли какая-либо привязка к этому прослушивателю событий?
событие привязано к кнопке
Мы видим, что ,button
С ним связаны два события, одноdocument
прослушиватель событий включен, другойbutton
, но обработчик событияhandle
, не нашhanderClick
событие, ноnoop
.
noop
Что тогда? Посмотрим дальше.
оказалосьnoop
Он указывает на пустую функцию.
Затем мы видимdocument
связанное событие
можно увидеть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
события, привязанные к элементам
Затем мы смотрим наdocument
событие, связанное с
мы находим, что мы даем<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
тогда мы идем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,
}
SimpleEventPlugin
Etc. — это подключаемый модуль, который обрабатывает каждую функцию события, такую как событие клика, вы найдете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'
}
Структурная схема выглядит следующим образом:
На втором этапе, после согласования дочерних узлов, 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];
}
посмотри в отладчике гугла
Отношения между двумя
устаревшая система обработки событий и пакетные обновления
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, это так просто, идеально!
триггер события
Некоторым учащимся может быть любопытно, как выглядит исходный объект события 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
:
Теперь, когда очередь выполнения события и объект-источник события сформированы, следующий шаг — последний.триггер события. Вы заметили функцию выше?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
Выполните очередь событий, если она будет обнаружена, чтобы предотвратить всплытие, выйти из цикла и, наконец, сбросить источник события, поместить его обратно в пул событий и завершить весь процесс.
Пять о системе событий версии react v17
Общие изменения в React v17 не очень большие, но и изменения в системе событий не маленькие.Прежде всего, многие из вышеперечисленных функций исполнения больше не существуют в версии v17. Позвольте мне кратко описать ревизию системы событий v17.
1 Событие, связывающее единство на контейнере, Reactom.runder (приложение, контейнер); и не в документе, это преимущество способствует микросхожению, передний конец, передний конец фронтальной системы Micro может иметь несколько приложений, если она продолжается взять все связанныеdocument
, то могут быть проблемы с несколькими приложениями.
2 Выровняйте нативные события браузера
React 17
Наконец, поддерживается собственная поддержка событий захвата в соответствии с собственными стандартами браузера. в то же времяonScroll
События больше не всплывают.onFocus
а такжеonBlur
использовать роднойfocusin
,focusout
синтез.
React 17
Отмените пул событий, вышеперечисленное решеноsetTimeout
печать, не найденоe.target
Эта проблема.
Шесть резюме
Эта статья изсинтез событий,привязка события,триггер событияПринципы системы событий React подробно представлены в трех аспектах. Я надеюсь, что вы сможете узнать больше о системе событий React v16 из этой статьи. Если у вас есть какие-либо вопросы или недостатки, я надеюсь, что вы можете указать на них в области комментариев. .
Напоследок подарите розы и оставьте в руках стойкий аромат.Друзья, которые чувствуют, что что-то приобрели, могут подарить авторуНравится, следуйОдна волна, обновляйте фронтальные суперхардкорные статьи одну за другой.
Предварительное раскрытие: раскрытие будет опубликовано позжеreact
Статьи о системах планирования. Заинтересованные студенты, пожалуйста, обратите внимание на общедоступный номерСовместное использование внешнего интерфейсаОбновите внешний жесткий текст в первый раз.
Предыдущая статья о реакции
Продвинутая серия React
-
В статье "React Advanced" разбирается принцип работы хуков реакции
880+
👍 -
Восемь предложений по оптимизации для разработчиков React в конце года «React Advanced»
950+
👍 -
В статье «React Advanced» подробно рассматриваются компоненты React High-Order Components (HOC).
353+
👍