предисловие
Сегодня мы подошли к необычной продвинутой статье React, в этой статье мы рассмотрим некоторыенеобычныйФеномен, проанализируйте причины и найдите результаты с помощью процесса обнаружения, чтобы понять React, войти в мир React и раскрыть завесу React, я убежден, что,Более глубокое понимание может привести к лучшему использованию.
Я признаю, что это имя может быть чем-то вроде вечеринки, захватывающей заголовки. Вдохновение пришло из колонки CCTV под названием «В науку», когда я был ребенком. В ней каждый день рассказывалось о различных сверхъестественных сверхъестественных явлениях. Проблема в том, что сейчас это смешно что я думаю об этом. Но природа «духовных» феноменов Реакта, которую я представил сегодня, не педиатрическая, и каждый феномен раскрывается послеРеагировать на рабочий механизма такжепринцип конструкции. (Реагирующая версия, о которой мы говорим,16.13.1
)
Итак, без лишних слов, мои великие сыщики, вы готовы? Давайте начнем сегодняшнее раскрытие.
Случай 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>
}
}
Эффект следующий
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
для завершения обновления. Но когда он хочет изменить входное значение, происходят неожиданные вещи.
Ошибка консоли, как показано выше.Cannot read property 'value' of null
то естьe.target
дляnull
. источник событияtarget
Как сказать нет?
След подсказок
Получив это дело, мы сначала исследуем проблему, затем мы сначалаhanderChange
прямая печатьe.target
,следующим образом:
Кажется, что первое расследование неhanderChange
, то переходим кsetTimeout
нашел в печати:
Конечно же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 из пула событий, если нет, создайте его, затем назначьте источник события, подождите, пока событие не будет выполнено, сбросите источник события и поместите его обратно в пул событий для повторного использования.
Используйте блок-схему для представления:
Случай 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
и загружены в частную библиотеку, в основном для унифицированного управления элементами управления формами. Содержание ошибки следующее:
Проверяйте один за другим
Мы проверяем проблему одну за другой в соответствии с содержанием ошибки, о которой сообщает 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
Изменения вызывают визуализацию компонента. Но реальность такова:
Эффект нажатия кнопки:
раскопал
Почему это происходит? Давай проверим еще разIndex
компоненты, найденныеIndex
компонент имеетchangeType
, так это причина? Разберем, в первую очередь обновление состояния происходит в родительском компонентеHome
начальство,Home
Каждое обновление компонента будет генерировать новыйchangeName
,такIndex
изPureComponent
каждый разповерхностное сравнение,Обнаружитьprops
серединаchangeName
Он не равен каждый раз, поэтому он обновляется, что дает нам интуитивное ощущение, что он недействителен.
Итак, как решить эту проблему,React hooks
предоставлено вuseCallback
, даprops
Входящая функция обратного вызова кэшируется, давайте изменим ееHome
код.
const changeName = React.useCallback((name) => {
setType(name)
},[])
Эффект:
Это решило проблему вообще, используя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
, который оказывается чистым компонентом.
Далее это точкаshallowEqual
,кprops
Например, давайте посмотрим.
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 обновляет одно и то же состояние, и компонент функции выполняется дважды
получил отчет
Этот вопрос на самом деле очень болтается. Возможно, вы не замечали его в обычное время. Мое внимание привлек вопрос, заданный диггером из Наггетс. Вопрос заключается в следующем:
Прежде всего, я очень благодарен этому внимательному копателю за сообщение о случае.Принцип 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 />
}
}
Как и в приведенной выше демонстрации, три кнопки, мы ожидаем непрерывного нажатия каждой кнопки, компонент будет отображаться только один раз, поэтому мы начинаем эксперимент:
Эффект:
Конечно, мы прошли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
указывать друг на друга. Это то, с чем мы знакомыдвойная буферизация.
Инициализировать печать
Изображение эффекта:
После инициализации первого рендера давайте посмотрим на эти состояния на дереве волокон.
Первый результат печати выглядит следующим образом:
-
fiber
ВверхmemoizedState
серединаbaseState = 0
то есть инициализацияuseState
ценность . -
fiber
Вверхalternate
дляnull
. -
Index
на компонентеnumber
равно 0.
Процесс инициализации: во-первых, для первой инициализации компонента он будет согласован и визуализирован для формирования дерева волокон (мыназывается деревом А). дерево Аalternate
собственностьnull
.
сначала нажмите setNumber (1)
Мы щелкнули в первый раз и обнаружили, что компонент отрендерился, а затем напечатали результат следующим образом:
- на дереве А
memoizedState
середина **baseState = 0
. - на дереве А
alternate
указать на другойfiber
(Мы называем это деревом здесь). -
Index
на компонентеnumber
1.
Далее печатаем на дереве BmemoizedState
В результате мы нашли, что дерево BmemoizedState
ВверхbaseState = 1
.
Вывод состоит в том, что обновленное состояние находится в дереве B, а baseState в дереве A по-прежнему равно 0.
Давайте смело предположим процесс обновления: когда рендер обновляется в первый раз, потому что в дереве А нетalternate
, поэтому скопируйте дерево A напрямую какworkInProgress
(здесь мы называем этодерево Б) Все обновления производятся в текущем дереве B, поэтому baseState будет обновляться до 1, а затем использовать текущийдерево Брендерить. После окончания дерево A и дерево B проходятalternate
указывать друг на друга. дерево B в качестве следующей операцииcurrent
Дерево.
второй щелчок setNumber(1)
Вторая печать, компонент тоже рендерится, а потом мы печатаем объект волокна, эффект такой:
- на волокнистом объекте
memoizedState
серединаbaseState
Обновлено до 1.
Затем мы печатаемalternate
серединаbaseState
Также обновился до 1.
После второго щелчка оба дерева и дерева B обновляются до последних базовых = 1
Во-первых, давайте проанализируем процесс: когда мы нажимаем второй раз, состояние в дереве A не обновляется до последнего состояния, и компонент снова обновляется. Далее текущее дерево (дерево б) будетalternate
указывает на дерево A как на новыйworkInProgress
После обновления baseState в дереве A наконец-то обновляется до 1, что объясняет, почему оба вышеуказанных baseState равны 1. Затем завершается рендеринг компонента. Дерево A используется как новое текущее дерево.
В нашей второй печати фактически печатается дерево B после чередования, а дерево A и дерево B поочередно используются в качестве последнего состояния для рендеринга.workInProgress
Дерево и последнее состояние кеша для следующего рендераcurrent
Дерево.
Третий щелчок (три говорят больше)
Тогда компонент третьего клика не отображается, это хорошее объяснение, третий клик в предыдущем дереве BbaseState = 1
а такжеsetNumber(1)
Если они равны, логика возврата выполняется напрямую.
Разгадать тайну (что мы узнали)
-
Деревья с двойной буферизацией: для React
workInProgress
дерево (дерево, построенное в памяти) иcurrent
(дерево рендеринга) для реализации логики обновления. Файберы, напечатанные нашим console.log, вот-вот окажутся в памяти.workInProgress
волокнистое дерево. Один из двойных кешей строится в памяти, при следующем рендеринге дерево кеша напрямую используется как следующее дерево рендеринга, а предыдущее дерево рендеринга используется как дерево кеша, что может предотвратить потерю обновления состояния всего лишь одно дерево, снова ускорьсяdom
Замена и обновление узлов. -
Механизм обновления: при обновлении сначала будет получено текущее дерево.
alternate
как текущийworkInProgress
, после рендерингаworkInProgress
дерево становитсяcurrent
Дерево. Мы используем приведенные выше дерево A и дерево B, а также сохраненную модель baseState, чтобы более наглядно объяснить механизм обновления.
Мы используем блок-схему, чтобы описать весь процесс.
Дело раскрыто, и благодаря этому легко упускаемому из виду делу мы узнали о двойной буферизации и механизме обновления.
Случай 6: useEffect изменяет элементы DOM, вызывая странные вспышки
любопытное совпадение
Сяо Мин (псевдоним) столкнулся со странным явлением мигания Домов при динамическом монтировании компонентов.Давайте сначала рассмотрим это явление.
Явление мигания:
Код:
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, могут обратить внимание. В настоящее время включены следующие статьи:
-
Статья «React advanced» объясняет принцип системы событий реагирования.
244+
👍 -
В статье "React Advanced" разбирается принцип работы хуков реакции
946+
👍 -
Восемь предложений по оптимизации для разработчиков React в конце года «React Advanced»
978+
👍 -
В статье «React Advanced» подробно рассматриваются компоненты React High-Order Components (HOC).
368+
👍
2 Я написал буклет для углубленного систематического изучения React
Чтобы каждый мог систематически изучать React и продвигать его, автор недавно написал буклет «Руководство по расширенной практике React».Базовый Расширенный,Оптимизация и расширенные статьи,Расширенные принципы,Продвинутая экология,Продвинутая практика, пять направлений, чтобы подробно обсудить руководство по использованию React и введение в принципы.
-
существуетБазовый РасширенныйЗдесь мы заново поймем состояние, свойства, ссылку, контекст и другие модули в реакции и подробно объясним их основное использование и игровой процесс высокого уровня.
-
существуетОптимизация и расширенные статьиВ этом уроке мы расскажем о настройке производительности React и подробной обработке, чтобы React можно было написать более элегантно.
-
существуетРасширенные принципыВ этой статье я подробно расскажу о принципах нескольких основных модулей React и решу проблемы принципов React в разовых интервью.
-
существуетПродвинутая экологияЗдесь мы рассмотрим использование ключевой экологии React и проанализируем внутренний механизм работы с принципиальной точки зрения.
-
существуетПродвинутая практикаЗдесь первые несколько модулей будут соединены последовательно для интенсивной практики.
Что касается того, почему буклет называется Advanced Practice Guide, потому что, объясняя расширенный игровой процесс, он также содержит множество небольших демонстраций для практики. В интервью также есть несколько сессий вопросов и ответов, чтобы читатели выделялись из интервью.
На данный момент в буклете завершено больше всего глав.Базовый Расширенный, другие главы, я думаю, что скоро встречусь с вами, и заинтересованные студенты могут подписаться на меня! Каждая последующая статья будет раскрывать последний статус буклета.
Напоследок подарите розы и оставьте в руках стойкий аромат.Друзья, которые чувствуют, что что-то приобрели, могут подарить авторуНравится, следуйОдна волна, обновляйте фронтальные суперхардкорные статьи одну за другой.