Исследование «React Advanced» раскрывает шесть видов «духовных» феноменов React.

JavaScript React.js
Исследование «React Advanced» раскрывает шесть видов «духовных» феноменов React.

предисловие

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

Я признаю, что это имя может быть чем-то вроде вечеринки, захватывающей заголовки. Вдохновение пришло из колонки CCTV под названием «В науку», когда я был ребенком. В ней каждый день рассказывалось о различных сверхъестественных сверхъестественных явлениях. Проблема в том, что сейчас это смешно что я думаю об этом. Но природа «духовных» феноменов Реакта, которую я представил сегодня, не педиатрическая, и каждый феномен раскрывается послеРеагировать на рабочий механизма такжепринцип конструкции. (Реагирующая версия, о которой мы говорим,16.13.1)

src=http___n.sinaimg.cn_sinacn_w640h360_20180113_9984-fyqrewh6822097.jpg&refer=http___n.sinaimg.jpg

Итак, без лишних слов, мои великие сыщики, вы готовы? Давайте начнем сегодняшнее раскрытие.

Случай 1: Компоненты постоянно монтируются необъяснимым образом

получил отчет

Бывший одноклассник столкнулся со странной ситуацией, он надеялся обновить компонент,componentDidUpdateДелайте то, что вы хотите сделать после выполнения, источник обновления компонента исходит из передачи родительского компонентаpropsизменять. Но родительский компонент меняетсяpropsобнаружил, что представление отображается, ноcomponentDidUpdateНикакой казни, что еще более странноcomponentDidMountвоплощать в жизнь. код показывает, как показано ниже:

// TODO: 重复挂载
class Index extends React.Component{
   componentDidMount(){
     console.log('组件初始化挂载')
   }
   componentDidUpdate(){
     console.log('组件更新')
     /* 想要做一些事情 */
   }
   render(){
      return <div>《React进阶实践指南》  👍 { this.props.number } +   </div>
   }
}

Эффект следующий

didupdate.gif

componentDidUpdateне исполнено,componentDidMountреализация, указывающая на то, что компонент принципиальноНет логики обновления, нопропал дубликат крепления.

Проверяйте один за другим

Подкомпоненты в недоумении, а причину мы никак не можем найти, приходится начинать с родительского компонента. Давайте посмотрим, как написан родительский компонент.

const BoxStyle = ({ children })=><div className='card' >{ children }</div>

export default function Home(){
   const [ number , setNumber ] = useState(0)
   const NewIndex = () => <BoxStyle><Index number={number}  /></BoxStyle>
   return <div>
      <NewIndex  />
      <button onClick={ ()=>setNumber(number+1) } >点赞</button>
   </div>
}

Нашел некоторые подсказки от родительского компонента. В родительском компоненте первый проходBoxStyleВ качестве компонента-контейнера добавьте стили и визуализируйте наши дочерние компоненты.Index, но каждый раз новый компонент формируется путем объединения компонентов контейнераNewIndex, настоящее креплениеNewIndex, правда открывается.

Меры предосторожности

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

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

Случай 2: Странное исчезновение источника событий e.target

чрезвычайные ситуации

Псевдоним (Сяо Мин) написал контролируемый компонент по прихоти темной и ветреной ночью. Написано следующее:

export default class EventDemo extends React.Component{
  constructor(props){
    super(props)
    this.state={
        value:''
    }
  }
  handerChange(e){
    setTimeout(()=>{
       this.setState({
         value:e.target.value
       })
    },0)
  }
  render(){
    return <div>
      <input placeholder="请输入用户名?" onChange={ this.handerChange.bind(this) }  />
    </div>
  }
}

inputценностьstateсерединаvalueКонтроль атрибутов, Сяо Мин хочет пройтиhanderChangeИзменятьvalueзначение, но он ожидаетsetTimeoutдля завершения обновления. Но когда он хочет изменить входное значение, происходят неожиданные вещи.

event.jpg

Ошибка консоли, как показано выше.Cannot read property 'value' of nullто естьe.targetдляnull. источник событияtargetКак сказать нет?

След подсказок

Получив это дело, мы сначала исследуем проблему, затем мы сначалаhanderChangeпрямая печатьe.target,следующим образом:

event1.jpg

Кажется, что первое расследование неhanderChange, то переходим кsetTimeoutнашел в печати:

event2.jpg

Конечно жеsetTimeoutпричина по которойsetTimeoutИсточник события в e.target по необъяснимым причинам отсутствует? Во-первых, источник события определенно не пропал необъяснимым образом. Нижний слой React, должно быть, выполнил дополнительную обработку источника события. Во-первых, мы знаем, что React используетсинтез событиймеханизм, то есть связываниеonChangeне настоящая границаchangeсобытие, связанное сяоминомhanderChangeИ это не настоящий обработчик событий. Это означает, что нижний слой React помогает нам работать с источниками событий. Все это может быть только подсказкой из исходного кода React. Изучив исходный код, я обнаружил очень подозрительную подсказку.

react-dom/src/events/DOMLegacyEventPluginSystem.js


function dispatchEventForLegacyPluginEventSystem(topLevelType,eventSystemFlags,nativeEvent,targetInst){
    const bookKeeping = getTopLevelCallbackBookKeeping(topLevelType,nativeEvent,targetInst,eventSystemFlags);
    batchedEventUpdates(handleTopLevel, bookKeeping);
}

dispatchEventForLegacyPluginEventSystemдаlegacyВ режиме все события должны проходить через основную функцию,batchedEventUpdatesЭто логика обработки пакетных обновлений, которая будет выполнять нашу реальную функцию обработки событий, о которой мы говорили в главе о принципах событий.nativeEventто естьДействительно нативный объект событияevent.targetInstто естьe.targetсоответствующийfiberобъект. мы вhanderChangeИсточник событий, полученный в нем, — это источник событий, синтезированный React, поэтому когда источник событий и как он синтезируется? Это может быть полезно при решении дел.

В статье, посвященной событию, мы познакомим вас с использованием в React механизма подключаемых модулей событий. Например, наше событие onClick соответствуетSimpleEventPlugin, то Сяомин пишетonChangeЕсть также специальныеChangeEventPluginПлагины событий, эти плагины играют решающую роль в синтезе нашего объекта источника событий e, поэтому давайте посмотримChangeEventPlugin.

react-dom/src/events/ChangeEventPlugin.js

const ChangeEventPlugin ={
   eventTypes: eventTypes,
   extractEvents:function(){
        const event = SyntheticEvent.getPooled(
            eventTypes.change,
            inst, // 组件实例
            nativeEvent, // 原生的事件源 e
            target,      // 原生的e.target
     );
     accumulateTwoPhaseListeners(event); // 这个函数按照冒泡捕获逻辑处理真正的事件函数,也就是  handerChange 事件
     return event; // 
   }   
}

Мы видим источник событий синтетических событийhanderChangeЕ в , этоSyntheticEvent.getPooledсозданный. Так что это ключ к раскрытию дела.

legacy-events/SyntheticEvent.js

SyntheticEvent.getPooled = function(){
    const EventConstructor = this; //  SyntheticEvent
    if (EventConstructor.eventPool.length) {
    const instance = EventConstructor.eventPool.pop();
    EventConstructor.call(instance,dispatchConfig,targetInst,nativeEvent,nativeInst,);
    return instance;
  }
  return new EventConstructor(dispatchConfig,targetInst,nativeEvent,nativeInst,);
}

Дополнительно: в главе, посвященной системе событий, мнение о пуле событий в статье является относительно поспешным и общим. Эта часть подробно дополняет настроение пула событий.

getPooledЭто приводит к реальной концепции объединения событий, которая делает две вещи:

  • Определите, есть ли в пуле событий свободный источник событий, и, если он есть, извлеките источник событий для повторного использования.
  • Если нет, пройдиnew SyntheticEventспособ создания нового объекта источника событий. ТакSyntheticEventИменно конструктор создает объект-источник события, давайте изучим его вместе.
const EventInterface = {
  type: null,
  target: null,
  currentTarget: function() {
    return null;
  },
  eventPhase: null,
  ...
};
function SyntheticEvent( dispatchConfig,targetInst,nativeEvent,nativeEventTarget){
  this.dispatchConfig = dispatchConfig; 
  this._targetInst = targetInst;    // 组件对应fiber。
  this.nativeEvent = nativeEvent;   // 原生事件源。
  this._dispatchListeners = null;   // 存放所有的事件监听器函数。
  for (const propName in Interface) {
      if (propName === 'target') {
        this.target = nativeEventTarget; // 我们真正打印的 target 是在这里
      } else {
        this[propName] = nativeEvent[propName];
      }
  }
}
SyntheticEvent.prototype.preventDefault = function (){ /* .... */ }     /* 组件浏览器默认行为 */
SyntheticEvent.prototype.stopPropagation = function () { /* .... */  }  /* 阻止事件冒泡 */

SyntheticEvent.prototype.destructor = function (){ /* 情况事件源对象*/
      for (const propName in Interface) {
           this[propName] = null
      }
    this.dispatchConfig = null;
    this._targetInst = null;
    this.nativeEvent = null;
}
const EVENT_POOL_SIZE = 10; /* 最大事件池数量 */
SyntheticEvent.eventPool = [] /* 绑定事件池 */
SyntheticEvent.release=function (){ /* 清空事件源对象,如果没有超过事件池上限,那么放回事件池 */
    const EventConstructor = this; 
    event.destructor();
    if (EventConstructor.eventPool.length < EVENT_POOL_SIZE) {
       EventConstructor.eventPool.push(event);
    }
}

После того, как я доработал этот кусок кода, правда постепенно обнаружилась, давайте сначала посмотримSyntheticEventЧто вы наделали:

  • Сначала назначьте некоторые инициализированные переменныеnativeEventЖдать. затем следуйтеEventInterfaceправило положитьсобственный источник событийсвойства, скопируйте копию вРеагировать на источник события. Затем одна важная вещь заключается в том, что e.target, который мы печатаем, — это this.target, который привязывается к реальному при инициализации источника события.e.target->nativeEventTarget

  • Затем источник событий React привязывает свое собственное блокирующее поведение по умолчанию.preventDefault, чтобы предотвратить пузырениеstopPropagationи другие методы. Но вот ключевой способdestructor,Эта функция обнуляет собственный исходный объект события React. Затем мы, наконец, нашли ответ, высокая вероятность исчезновения нашего источника событий e.target из-за этогоdestructor,destructorсуществуетreleaseзапускается в источнике события, а затем помещает источник события в пул событий, ожидая следующего повторного использования.

Теперь все копья указываютrelease,ТакreleaseКогда он был запущен?

legacy-events/SyntheticEvent.js

function executeDispatchesAndRelease(){
    event.constructor.release(event);
}

Когда система событий React выполнила все_dispatchListeners, это вызовет этот методexecuteDispatchesAndReleaseОсвободите текущий источник событий.

Правда раскрыта

Возвращаясь к проблеме, с которой столкнулся Сяомин, как мы упоминали выше, React в конечном итоге синхронно очистит источник событий, а затем поместит его в пул событий, потому чтоsetTimeoutОн выполняется асинхронно, при выполнении объект-источник события сбрасывается и выпускается в пул событий, поэтому мы печатаемe.target = null, До сих пор правда дела вышла на свет.

В этом случае мы понимаем некоторые концепции пула событий React:

  • Система событий React имеет уникальные синтетические события, собственный источник событий и логику обработки для некоторых особых случаев, таких как логика всплытия.
  • Чтобы предотвратить создание объектов-источников событий для каждого события и потерю производительности, React представилКонцепция пула событий, каждое пользовательское событие будет получать e из пула событий, если нет, создайте его, затем назначьте источник события, подождите, пока событие не будет выполнено, сбросите источник события и поместите его обратно в пул событий для повторного использования.

Используйте блок-схему для представления:

eventloop.jpg

Случай 3: истинная и ложная реакция

Место преступления

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

function Index({classes, onSubmit, isUpgrade}) {
   /* useFormQueryChange 是笔者写好的自定义hooks,并上传到私有库,主要是用于对表单控件的统一管理  */
  const {setFormItem, reset, formData} = useFormQueryChange()
  React.useEffect(() => {
    if (isUpgrade)  reset()
  }, [ isUpgrade ])
  return <form
    className={classes.bootstrapRoot}
    autoComplete='off'
  >
    <div className='btnbox' >
       { /* 这里是业务逻辑,已经省略 */ }
    </div>
  </form>
}

useFormQueryChangeЭто обычай, написанный авторомhooksи загружены в частную библиотеку, в основном для унифицированного управления элементами управления формами. Содержание ошибки следующее:

hooks.jpg

Проверяйте один за другим

Мы проверяем проблему одну за другой в соответствии с содержанием ошибки, о которой сообщает React:

  • Первая возможная причина ошибкиYou might have mismatching versions of React and the renderer (such as React DOM), значениеReactа такжеReact Dom Версия несовместима, из-за чего такая ситуация, но в нашем проектеReactа такжеReact Domобеv16.13.1, так что это подозрение исключено.

  • Вторая возможная причина ошибкиYou might be breaking the Rules of HooksЗначит вы нарушили правило Хуков, что тоже невозможно, т.к. в авторском коде нет брешиhoosрегулярное поведение. Так что подозрение исключено.

  • Третья возможная причина ошибки You might have more than one copy of React in the same appЭто означает, что в одном приложении может быть несколько React. В настоящее время все подозреваемые указывают на третью.Прежде всего, будут ли кастомные хуки, на которые мы ссылаемся, иметь внутри еще один React?

По приведенным выше советам я нашел соответствующие кастомные хукиnode_modulesКонечно, есть еще один React, этот假React(我们姑且称之为假React)搞的鬼。 мы вПринцип крючковКак упоминалось в статье,React HooksиспользоватьReactCurrentDispatcher.currentВо время инициализации компонента и фазы обновления компонента назначаются разные объекты ловушек, а после завершения обновления они назначаются разным объектам ловушек.ContextOnlyDispatcher, если будут вызваны хуки под этим объектом, будет выдано указанное выше сообщение об ошибке, значитЭта ошибка связана с нашим проектом, React, представленный контекстом выполнения, является React самого проекта, но пользовательские хуки ссылаются на поддельные хуки React.ContextOnlyDispatcher

Далее я вижу в библиотеке компонентовpackage.jsonсередина,

"dependencies": {
  "react": "^16.13.1",
  "react-dom": "^16.13.1"
},

Оказывается, React какdependenciesтак скачай кастомыHooksкогда, поставитьReactСкачал снова. Итак, как решить эту проблему. Для инкапсуляции библиотеки компонентов React нельзя использовать библиотеку хуков.dependencies, потому что он начнется с текущегоdependenciesЗагрузите в библиотеку пользовательских хуков для зависимостейnode_modulesсередина. Вместо этого вы должны использоватьpeerDependencies,использоватьpeerDependencies,настроитьhooksЕсли вы найдете соответствующие зависимости, вы перейдете к нашему проекту.node_modulesУзнайте, вы можете в корне решить эту проблему. Итак, мы меняем

"peerDependencies": {
    "react": ">=16.8",
    "react-dom": ">=16.8",
},

Он отлично решил эту проблему.

рассеять туман

Этот вопрос заставляет нас понять следующее:

  • Для некоторых библиотек хуков, библиотек компонентов и собственных зависимостей они уже существуют в проекте, поэтому используйтеpeerDependenciesутверждение.

  • В процессе разработки, скорее всего, будут использоваться разные версии одной и той же зависимости, например, в проекте представлена ​​версия А зависимости, а в библиотеке компонентов — версия В зависимости. Итак, как поступить в этой ситуации. существуетpackage.jsonДля решения этой проблемы в документации предусмотрен элемент конфигурации разрешения, вresolutionsОдна и та же версия импорта заблокирована, чтобы не вызывать проблем, вызванных наличием нескольких версий зависимостей проекта, как указано выше.

проектpackage.jsonнапиши вот так

{
  "resolutions": {
    "react": "16.13.1",
    "react-dom": "16.13.1"
  },
}

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

Случай 4: проблема сбоя функции PureComponent/memo

описание случая

при разработке в React, но мы хотели использоватьPureComponentЗаймитесь оптимизацией производительности и настройте отрисовку компонентов, но написав кусок кода, я обнаружил, чтоPureComponentФункция на самом деле не удалась, конкретный код выглядит следующим образом:


class Index extends React.PureComponent{
   render(){
     console.log('组件渲染')
     const { name , type } = this.props
     return <div>
       hello , my name is { name }
       let us learn { type }
     </div>
   }
}

export default function Home (){
   const [ number , setNumber  ] = React.useState(0)
   const [ type , setType ] = React.useState('react')
   const changeName = (name) => {
       setType(name)
   }
   return <div>
       <span>{ number }</span><br/>
       <button onClick={ ()=> setNumber(number + 1) } >change number</button>
       <Index type={type}  changeType={ changeName } name="alien"  />
   </div>
}

Мы ожидали:

  • Только для компонентов индексаpropsсерединаnameа такжеtypeИзменения вызывают визуализацию компонента. Но реальность такова:

Эффект нажатия кнопки:

purecomponent.gif

раскопал

Почему это происходит? Давай проверим еще разIndexкомпоненты, найденныеIndexкомпонент имеетchangeType, так это причина? Разберем, в первую очередь обновление состояния происходит в родительском компонентеHomeначальство,HomeКаждое обновление компонента будет генерировать новыйchangeName,такIndexизPureComponentкаждый разповерхностное сравнение,ОбнаружитьpropsсерединаchangeNameОн не равен каждый раз, поэтому он обновляется, что дает нам интуитивное ощущение, что он недействителен.

Итак, как решить эту проблему,React hooksпредоставлено вuseCallback, даpropsВходящая функция обратного вызова кэшируется, давайте изменим ееHomeкод.

const changeName = React.useCallback((name) => {
    setType(name)
},[])

Эффект:

pureComponent1.gif

Это решило проблему вообще, используяuseCallbackправильноchangeNameфункция кэшируется на каждомHomeкомпонент выполняется до тех пор, покаuseCallbackсерединаdepsбез изменений,changeNameПространство памяти также указывает на исходную функцию, поэтомуPureComponentПоверхностное сравнение покажет то же самоеchangeName, так что компонент не рендерится, пока дело в поломке.

идти дальше

При разработке с функциональными компонентами + классовыми компонентами, если вы используете React.memo React.PureComponentВ ожидании апи обратите внимание на способ привязки событий к этим компонентам, если это функциональный компонент, то вы хотите держать его постоянноВозможности управления рендерингом чистых компонентов, тогда используйтеuseCallback,useMemoДождитесь обработки API. Если это компонент класса, пожалуйста, не используйте стрелочные функции для привязки событий. Стрелочные функции также приведут к сбоям.

Неглубокое сравнение упомянуто вышеshallowEqual, то сосредоточимся на анализеPureComponentкакshallowEqual, Затем мы подробно рассмотримshallowEqualТайна. Потом идет обновление от тарифа такси.

react-reconciler/src/ReactFiberClassComponent.js

function updateClassInstance(){
    const shouldUpdate =
    checkHasForceUpdateAfterProcessing() ||
    checkShouldComponentUpdate(
      workInProgress,
      ctor,
      oldProps,
      newProps,
      oldState,
      newState,
      nextContext,
    );
    return shouldUpdate
}

я упрощаю здесьupdateClassInstance, который сохраняет толькоPureComponentчасть.updateClassInstanceЭта функция в основном используется для выполнения жизненного цикла, обновления состояния, определения повторного рендеринга компонента и возвратаshouldUpdateИспользуется для определения того, визуализируется ли текущий компонент класса.checkHasForceUpdateAfterProcessingПроверьте, является ли источник обновления источником forceUpdate , если даforceUpdateКомпоненты всегда будут обновляться.checkShouldComponentUpdateПроверьте, отображается ли компонент. Далее рассмотрим логику этой функции.

function checkShouldComponentUpdate(){
    /* 这里会执行类组件的生命周期 shouldComponentUpdate */
    const shouldUpdate = instance.shouldComponentUpdate(
      newProps,
      newState,
      nextContext,
    );
    /* 这里判断组件是否是 PureComponent 纯组件,如果是纯组件那么会调用 shallowEqual 浅比较  */
    if (ctor.prototype && ctor.prototype.isPureReactComponent) {
        return (
        !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
        );
    }
}

checkShouldComponentUpdateЕсть две важные роли:

  • Во-первых, если компонент класса имеет жизненный циклshouldComponentUpdate, выполнит жизненный циклshouldComponentUpdate, чтобы определить, визуализируется ли компонент.
  • Если обнаружено, что это чистый компонентPureComponent, будет мелким по сравнению с новым и старымpropsа такжеstateРавно ли оно, если да, то не обновлять компонент.isPureReactComponentчто мы используемPureComponent, который оказывается чистым компонентом.

Далее это точкаshallowEqualpropsНапример, давайте посмотрим.

shared/shallowEqual

function shallowEqual(objA: mixed, objB: mixed): boolean {
  if (is(objA, objB)) { // is可以 理解成  objA === objB 那么返回相等
    return true;
  }

  if (
    typeof objA !== 'object' ||
    objA === null ||
    typeof objB !== 'object' ||
    objB === null
  ) {
    return false;  
  } // 如果新老props有一个不为对象,或者不存在,那么直接返回false

  const keysA = Object.keys(objA); // 老props / 老state key组成的数组
  const keysB = Object.keys(objB); // 新props / 新state key组成的数组

  if (keysA.length !== keysB.length) { // 说明props增加或者减少,那么直接返回不想等
    return false;
  }

  for (let i = 0; i < keysA.length; i++) { // 遍历老的props ,发现新的props没有,或者新老props不同等,那么返回不更新组件。
    if (
      !hasOwnProperty.call(objB, keysA[i]) ||
      !is(objA[keysA[i]], objB[keysA[i]])
    ) {
      return false;
    }
  }

  return true; //默认返回相等
}

shallowEqualПроцесс такой,shallowEqualвернутьtrueто доказать равенство, то не обновлять компоненты; если вернутьfalseДокажите, что не хотите ждать, затем обновите компоненты.isВ настоящее время мы можем понимать это как ===

  • Первый шаг, напрямую перейдя === судя, равно ли оно, если поровну, то возвращаемtrue. Нормальная ситуация, пока звонокReact.createElementбудет воссозданprops,propsне равны.
  • Второй шаг, если новый и старыйpropsЕсли есть тот, который не является объектом или не существует, то возвращайтесь напрямуюfalse.
  • Третий шаг состоит в том, чтобы судить о старом и новом.props,keyКоличество сформированных массивов и т.п. ждать не хочется, пояснитеpropsЕсли есть увеличение или уменьшение, то вернитесь напрямуюfalse.
  • Четвертый шаг, пересечь старыйprops, открыть для себя новыеpropsЕму нет аналога, ни старого, ни новогоpropsне равно, то возвратfalse.
  • вернуть по умолчаниюtrue.

ЭтоshallowEqualЛогика и код по-прежнему очень просты. Заинтересованные студенты могут ознакомиться.

Случай 5: Usestate обновляет одно и то же состояние, и компонент функции выполняется дважды

получил отчет

Этот вопрос на самом деле очень болтается. Возможно, вы не замечали его в обычное время. Мое внимание привлек вопрос, заданный диггером из Наггетс. Вопрос заключается в следующем:

EBF05CF6-E088-4DE0-A0CD-57E04AB29BBF.jpg

Прежде всего, я очень благодарен этому внимательному копателю за сообщение о случае.Принцип React-хуковКак упоминалось выше, для функционального компонента метода компонента обновленияuseStateи компоненты классаsetStateЕсть определенная разница,useStateЕсли вы встретите одно и то же два раза в исходном кодеstate, предотвратит обновление компонента по умолчанию, но в компоненте классаsetStateЕсли не установленоPureComponent, дважды одно и то жеstateтакже будет обновляться.

Давайте рассмотримhooksКак запретить обновление компонентов.

react-reconciler/src/ReactFiberHooks.js -> dispatchAction

if (is(eagerState, currentState)) { 
     return
}
scheduleUpdateOnFiber(fiber, expirationTime); // 调度更新

Если вы судите последнееstate -> currentState, и на этот разstate -> eagerStateравны, то прямойreturnпредотвратить компонент отscheduleUpdateРасписание обновлений.Итак, мы хотим, если дваждыuseStateЗапустите одно и то же состояние, тогда компонент можно будет обновить только один раз, но так ли это на самом деле? .

изучение

Следуя подсказкам этого диггера, мы начали писатьdemoаутентификация.

const Index = () => {
  const [ number , setNumber  ] = useState(0)
  console.log('组件渲染',number)
  return <div className="page" >
    <div className="content" >
       <span>{ number }</span><br/>
       <button onClick={ () => setNumber(1) } >将number设置成1</button><br/>
       <button onClick={ () => setNumber(2) } >将number设置成2</button><br/>
       <button onClick={ () => setNumber(3) } >将number设置成3</button>
    </div>
  </div>
}
export default class Home extends React.Component{
  render(){
    return <Index />
  }
}

Как и в приведенной выше демонстрации, три кнопки, мы ожидаем непрерывного нажатия каждой кнопки, компонент будет отображаться только один раз, поэтому мы начинаем эксперимент:

Эффект:

demo1.gif

Конечно, мы прошлиsetNumberИзменятьnumber, каждый раз, когда кнопка непрерывно нажимается, компонент будет обновляться дважды.Согласно нашему обычному пониманию, каждый раз, когдаnumberОдно и то же значение будет отображаться только один раз, но почему оно выполняется дважды?

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

следовать лозе

Итак, как найти объект волокна, соответствующий функциональному компоненту, который следует за родителем функционального компонентаHomeНачато, потому что мы можем начать с компонентов классаHomeНайдите соответствующий объект волокна вchildуказатель для поиска функционального компонентаIndexсоответствующийfiber. Давайте продолжим и сделаем это, мы преобразуем приведенный выше код в следующий:

const Index = ({ consoleFiber }) => {
  const [ number , setNumber  ] = useState(0)
  useEffect(()=>{  
      console.log(number)
      consoleFiber() // 每次fiber更新后,打印 fiber 检测 fiber变化
  })
  return <div className="page" >
    <div className="content" >
       <span>{ number }</span><br/>
       <button onClick={ () => setNumber(1) } >将number设置成1</button><br/>
    </div>
  </div>
}
export default class Home extends React.Component{
  consoleChildrenFiber(){
     console.log(this._reactInternalFiber.child) /* 用来打印函数组件 Index 对应的fiber */
  }
  render(){
    return <Index consoleFiber={ this.consoleChildrenFiber.bind(this) }  />
  }
}

Мы фокусируемся на этих атрибутах волокна, которые очень полезны для решения задач.

  • Index fiberВверхmemoizedStateАтрибуты,react hooksКак упоминалось в основной статье, функциональные компоненты используютmemoizedStateсохранить всеhooksИнформация.
  • Index fiberВверхalternateАтрибуты
  • Index fiberВверхalternateатрибутmemoizedStateАтрибуты. Разве это не очень запутанно, я скоро покажу, что это такое.
  • Indexна компонентеuseStateсерединаnumber.

Сначала поговорим оalternateНа что указывает указатель?

Говоря оalternateотfiberКогда дело доходит до архитектурного дизайна, каждыйReactУзлы элементов, используйте два дерева волокон для сохранения состояния, одно дерево для сохранения текущего состояния, одно дерево для сохранения последнего состояния, дваfiberдля дереваalternateуказывать друг на друга. Это то, с чем мы знакомыдвойная буферизация.

Инициализировать печать

Изображение эффекта:

fiber1.jpg

После инициализации первого рендера давайте посмотрим на эти состояния на дереве волокон.

Первый результат печати выглядит следующим образом:

  • fiberВверхmemoizedStateсерединаbaseState = 0то есть инициализацияuseStateценность .
  • fiberВверхalternateдляnull.
  • Indexна компонентеnumberравно 0.

Процесс инициализации: во-первых, для первой инициализации компонента он будет согласован и визуализирован для формирования дерева волокон (мыназывается деревом А). дерево Аalternateсобственностьnull.

сначала нажмите setNumber (1)

Мы щелкнули в первый раз и обнаружили, что компонент отрендерился, а затем напечатали результат следующим образом:

fiber2.jpg

  • на дереве АmemoizedStateсередина **baseState = 0.
  • на дереве Аalternateуказать на другойfiber(Мы называем это деревом здесь).
  • Indexна компонентеnumber1.

Далее печатаем на дереве BmemoizedState

fiber3.jpg

В результате мы нашли, что дерево BmemoizedStateВверхbaseState = 1.

Вывод состоит в том, что обновленное состояние находится в дереве B, а baseState в дереве A по-прежнему равно 0.

Давайте смело предположим процесс обновления: когда рендер обновляется в первый раз, потому что в дереве А нетalternate, поэтому скопируйте дерево A напрямую какworkInProgress(здесь мы называем этодерево Б) Все обновления производятся в текущем дереве B, поэтому baseState будет обновляться до 1, а затем использовать текущийдерево Брендерить. После окончания дерево A и дерево B проходятalternateуказывать друг на друга. дерево B в качестве следующей операцииcurrentДерево.

второй щелчок setNumber(1)

Вторая печать, компонент тоже рендерится, а потом мы печатаем объект волокна, эффект такой:

fiber4.jpg

  • на волокнистом объектеmemoizedStateсерединаbaseStateОбновлено до 1.

Затем мы печатаемalternateсерединаbaseStateТакже обновился до 1.

fiber5.jpg

После второго щелчка оба дерева и дерева B обновляются до последних базовых = 1

Во-первых, давайте проанализируем процесс: когда мы нажимаем второй раз, состояние в дереве A не обновляется до последнего состояния, и компонент снова обновляется. Далее текущее дерево (дерево б) будетalternateуказывает на дерево A как на новыйworkInProgressПосле обновления baseState в дереве A наконец-то обновляется до 1, что объясняет, почему оба вышеуказанных baseState равны 1. Затем завершается рендеринг компонента. Дерево A используется как новое текущее дерево.

В нашей второй печати фактически печатается дерево B после чередования, а дерево A и дерево B поочередно используются в качестве последнего состояния для рендеринга.workInProgressДерево и последнее состояние кеша для следующего рендераcurrentДерево.

Третий щелчок (три говорят больше)

Тогда компонент третьего клика не отображается, это хорошее объяснение, третий клик в предыдущем дереве BbaseState = 1а такжеsetNumber(1)Если они равны, логика возврата выполняется напрямую.

Разгадать тайну (что мы узнали)

  • Деревья с двойной буферизацией: для ReactworkInProgressдерево (дерево, построенное в памяти) иcurrent(дерево рендеринга) для реализации логики обновления. Файберы, напечатанные нашим console.log, вот-вот окажутся в памяти.workInProgressволокнистое дерево. Один из двойных кешей строится в памяти, при следующем рендеринге дерево кеша напрямую используется как следующее дерево рендеринга, а предыдущее дерево рендеринга используется как дерево кеша, что может предотвратить потерю обновления состояния всего лишь одно дерево, снова ускорьсяdomЗамена и обновление узлов.

  • Механизм обновления: при обновлении сначала будет получено текущее дерево.alternateкак текущийworkInProgress, после рендерингаworkInProgressдерево становитсяcurrentДерево. Мы используем приведенные выше дерево A и дерево B, а также сохраненную модель baseState, чтобы более наглядно объяснить механизм обновления.

Мы используем блок-схему, чтобы описать весь процесс.

FFB125E7-6A34-4F44-BB6E-A11D598D0A01.jpg

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

Случай 6: useEffect изменяет элементы DOM, вызывая странные вспышки

любопытное совпадение

Сяо Мин (псевдоним) столкнулся со странным явлением мигания Домов при динамическом монтировании компонентов.Давайте сначала рассмотрим это явление.

Явление мигания:

effect.gif

Код:

function Index({ offset }){
    const card  = React.useRef(null)
    React.useEffect(()=>{
       card.current.style.left = offset
    },[])
    return <div className='box' >
        <div className='card custom' ref={card}   >《 React进阶实践指南 》</div>
    </div>
}

export default function Home({ offset = '300px' }){
   const [ isRender , setRender ] = React.useState(false)
   return <div>
       { isRender && <Index offset={offset}  /> }
       <button onClick={ ()=>setRender(true) } > 挂载</button>
   </div>
}
  • Для родительских компонентовisRenderдинамическая нагрузкаIndex, нажмите кнопку для управленияIndexоказывать.
  • существуетIndexпринимает динамические смещенияoffset. и манипулируяuseRefприобретенный роднойdomИзмените смещение напрямую, чтобы сделать смахивание скользящим. Но есть явление мигания, как показано выше, что очень недружелюбно, так почему же оно вызывает эту проблему?

понять глубже

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

useEffect ,useLayoutEffect , componentDidMountВремя выполненияcommitсценическое исполнение. Мы знаем, что у React естьeffectListхранить по-разномуeffect. потому чтоReactк разнымeffectЛогика выполнения и сроки разные. Давайте взглянемuseEffectКогда он определен, какой тип определенeffect.

react-reconciler/src/ReactFiberHooks.js

function mountEffect(create, deps){
  return mountEffectImpl(
    UpdateEffect | PassiveEffect, // PassiveEffect 
    HookPassive,
    create,
    deps,
  );
}

Информация для этой функции следующая:

  • useEffectбыть даннымPassiveEffectТипeffect.
  • Функция Сяо Мина для изменения исходного положения DOM:create.

ТакcreateКогда функция выполняется и как React ее обрабатываетPassiveEffectДа, это ключ к раскрытию дела. Запишите это и посмотрим, как React справится с этим.PassiveEffect.

react-reconciler/src/ReactFiberCommitWork.js

function commitBeforeMutationEffects() {
  while (nextEffect !== null) {
    if ((effectTag & Passive) !== NoEffect) {
      if (!rootDoesHavePassiveEffects) {
        rootDoesHavePassiveEffects = true;
        /*  异步调度 - PassiveEffect */
        scheduleCallback(NormalPriority, () => {
          flushPassiveEffects();
          return null;
        });
      }
    }
    nextEffect = nextEffect.nextEffect;
  }
}

существуетcommitBeforeMutationEffectsВ функции он будет отправлен асинхронноflushPassiveEffectsметод,flushPassiveEffectsметод, для хуков React будет выполнятьсяcommitPassiveHookEffects, который затем выполнитcommitHookEffectListMount.

function commitHookEffectListMount(){
     if (lastEffect !== null) {
          effect.destroy = create(); /* 执行useEffect中饿 */
     }
}

существуетcommitHookEffectListMountсередина,createбудет вызвана функция. мы даемdomПозиция, в которой добавлен элемент, вступит в силу.

Итак, вопрос в том, что делает асинхронное планирование? Асинхронное планирование React, чтобы предотвратить задержку рендеринга браузера при выполнении некоторых задач и задержку кадров, React использует асинхронное планирование для некоторых задач с низким приоритетом, то есть браузер может свободно выполнять эти задачи. , асинхронные задачи реализуются по-разному на разных платформах и в разных браузерах.setTimeoutТакой же.

дождливо и солнечно

Благодаря вышеизложенному мы нашлиuseEffectпервый параметрcreate, метод асинхронного вызова принят, тогда мигание легко понять,В первом процессе рендеринга компонента кнопки щелчка функциональный компонент выполняется первым.render,ПотомcommitЗамените настоящий узел dom, а затем отрисуется браузер. В этот момент браузер отрисовал один раз, а затем у браузера есть свободное время для выполнения асинхронных задач, поэтому он выполняетcreate, информация о положении элемента изменена, потому что элемент был нарисован в последний раз, а в это время изменена другая позиция, поэтому я чувствую эффект мигания, и дело было решено.,

Так как же нам решить явление мигания, котороеReact.useLayoutEffect,useLayoutEffectизcreateЭто выполняется синхронно, поэтому браузер рисует один раз и напрямую обновляет последнюю позицию.

  React.useLayoutEffect(()=>{
      card.current.style.left = offset
  },[])

Резюме + вне числа, вне числа, вне числа

Что мы узнали в этом разделе?

Эта статья объясняет дело с точки зрения решения дела и с точки зрения принципа.ReactКакие-то неожиданные явления, через эти явления мы узнали какие-то внутренние вещи React, резюмирую вышеописанные случаи,

  • Случай 1 — понимание рендеринга некоторых компонентов и деклараций времени ошибок компонентов
  • Случай второй — Дополнение к концепции фактического пула событий.
  • Случай 3 — ввести несколько версий некоторых библиотек компонентов.Reactмышление и решения.
  • Случай четвертый — обратите внимание на отдачуmemo / PureComponentСобытия привязки и способы их обработкиPureComponentлогика,shallowEqualпринцип.
  • Случай пятый — на самом деле правдаfiberОбъяснение деревьев с двойным кэшированием.
  • Случай шестой - даuseEffect createОбъяснение времени выполнения.

Внимание! !

Я хочу сказать вам две вещи здесь, конкретное содержание заключается в следующем:

1 Колонка расширенной серии React

Недавно вышла платформа NuggetsЦентр авторова такжеТехническая колонкаЖдем новых функций, действительно очень удобно использовать и опыт очень хороший.Я очень благодарен платформе Nuggets и надеюсь, что платформа Nuggets будет становиться все лучше и лучше. я положил мимоПродвинутая серия ReactСтатья размещенаРасширенная колонка React, Студенты, которые хотят продвинуть стек технологий React, могут обратить внимание. В настоящее время включены следующие статьи:

2 Я написал буклет для углубленного систематического изучения React

Чтобы каждый мог систематически изучать React и продвигать его, автор недавно написал буклет «Руководство по расширенной практике React».Базовый Расширенный,Оптимизация и расширенные статьи,Расширенные принципы,Продвинутая экология,Продвинутая практика, пять направлений, чтобы подробно обсудить руководство по использованию React и введение в принципы.

  • существуетБазовый РасширенныйЗдесь мы заново поймем состояние, свойства, ссылку, контекст и другие модули в реакции и подробно объясним их основное использование и игровой процесс высокого уровня.

  • существуетОптимизация и расширенные статьиВ этом уроке мы расскажем о настройке производительности React и подробной обработке, чтобы React можно было написать более элегантно.

  • существуетРасширенные принципыВ этой статье я подробно расскажу о принципах нескольких основных модулей React и решу проблемы принципов React в разовых интервью.

  • существуетПродвинутая экологияЗдесь мы рассмотрим использование ключевой экологии React и проанализируем внутренний механизм работы с принципиальной точки зрения.

  • существуетПродвинутая практикаЗдесь первые несколько модулей будут соединены последовательно для интенсивной практики.

Что касается того, почему буклет называется Advanced Practice Guide, потому что, объясняя расширенный игровой процесс, он также содержит множество небольших демонстраций для практики. В интервью также есть несколько сессий вопросов и ответов, чтобы читатели выделялись из интервью.

На данный момент в буклете завершено больше всего глав.Базовый Расширенный, другие главы, я думаю, что скоро встречусь с вами, и заинтересованные студенты могут подписаться на меня! Каждая последующая статья будет раскрывать последний статус буклета.

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