Студенты, понимающие React, должны быть правыsetState
функции слишком знакомы,setState
Он также часто используется в качестве вопроса на собеседовании, чтобы проверить знакомство соискателей работы с React.
Я тоже закидываю сюда вопрос, читатели могут подумать над ответом на этот вопрос, прежде чем читать статью.
Каждый раз устанавливайте одно и то же значение для состояния компонента React, например.
setState({count: 1})
. Отрисовываются ли когда-либо компоненты React? Если да, то почему? Если нет, то почему?
Репродукция сцены
В ответ на вышеуказанные проблемы сначала выполняется простая проверка воспроизведения.
Как показано на рисунке, компонент приложения имеет кнопку настройки.Каждый раз, когда нажимается кнопка настройки, для состояния текущего компонента будет установлено одно и то же значение.{count: 1}
, когда компонент отрисовывается, количество отрисовок автоматически увеличивается на 1. Код выглядит следующим образом:
Компонент приложения
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
// 全局变量,用于记录组件渲染次数
let renderTimes = 0;
class App extends Component {
constructor(props) {
super(props);
this.state = {
count: 1
};
}
handleClick = () => {
this.setState({ count: 1 });
};
render() {
renderTimes += 1;
return (
<div>
<h3>场景复现:</h3>
<p>每次点击“设置”按钮,当前组件的状态都会被设置成相同的数值。</p>
<p>当前组件的状态: {this.state.count}</p>
<p>
当前组件发生渲染的次数:
<span style={{ color: 'red' }}>{renderTimes}</span>
</p>
<div>
<button onClick={this.handleClick}>设置</button>
</div>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
Фактический результат проверки показан ниже.Каждый раз, когда нажимается кнопка настройки, компонент приложения будет повторно отображаться.
2. Оптимизация производительности
Итак, как уменьшить повторный рендеринг компонентов приложения? доОптимизация производительности React. Говоря о компонентах PureComponent и мемо-компонентахВ этой статье подробноPureComponent
Внутренний механизм реализации доступен здесьPureComponent
компонентов для уменьшения дублирования рендеринга.
Фактические результаты проверки показаны ниже, а оптимизированный компонент приложения больше не генерирует повторную визуализацию.
Но есть проблема с деталями, о которой вы, возможно, не задумывались в своей обычной работе:
использовать
PureComponent
Компоненты могут уменьшить повторный рендеринг компонентов приложения, значит ли это, что состояние компонентов приложения не изменилось? То есть является ли ссылочный адрес последним адресом?
Без лишних слов, давайте протестируем и проверим эту проблему.Код выглядит следующим образом:
Компоненты приложения
import React, { PureComponent } from 'react';
import ReactDOM from 'react-dom';
// 全局变量,用于记录组件渲染次数
let renderTimes = 0;
// 全局变量,记录组件的上次状态
let lastState = null;
class App extends PureComponent {
constructor(props) {
super(props);
this.state = {
count: 1
};
lastState = this.state; // 初始化,地址保持一致
}
handleClick = () => {
console.log(`当前组件状态是否是上一次状态:${this.state === lastState}`);
this.setState({ count: 1 });
// 更新上一次状态
lastState = this.state;
};
render() {
renderTimes += 1;
return (
<div>
<h3>场景复现:</h3>
<p>每次点击“设置”按钮,当前组件的状态都会被设置成相同的数值。</p>
<p>当前组件的状态: {this.state.count}</p>
<p>
当前组件发生渲染的次数:
<span style={{ color: 'red' }}>{renderTimes}</span>
</p>
<div>
<button onClick={this.handleClick}>设置</button>
</div>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
В компоненте APP мы передаем глобальную переменнуюlastState
для записи последнего состояния компонента. Когда кнопка настройки нажата, она будет сравнивать, равно ли текущее состояние компонента предыдущему состоянию, то есть совпадает ли адрес ссылки?
В окне консоли мы обнаруживаем, что хотяPureComponent
Компонент уменьшает повторную отрисовку компонента App, но адрес ссылки состояния компонента App изменился Почему это?
Далее мы ответим на эти два вопроса и поговорим об исходном коде React V16.9.0.setState
механизм обновления состояния. В процессе интерпретации для лучшего понимания исходного кода часть исходного кода будет удалена.
В-третьих, механизм обновления состояния setState.
В процессе интерпретации исходного кода была организована функцияsetState
Блок-схема взаимосвязи вызовов выглядит следующим образом:
Как видно из рисунка выше, функцияsetState
Отношения вызова в основном делятся на следующие две части:
- Добавьте состояние для обновления в очередь обновлений;
- Создает запланированное задание. Задача планирования проходит через очередь обновлений и вычисляет конечное состояние для обновления, обновляет его до экземпляра компонента, а затем завершает операцию рендеринга компонента.
Ниже приводится подробное описание этих двух частей вместе с исходным кодом.
3.1 Войдите в очередь обновлений
3.1.1 Определение функции setState
Взято изReactBaseClasses.js
документ.
Component.prototype.setState = function(partialState, callback) {
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
функцияsetState
содержит два параметраpartialState
а такжеcallback
,вpartialState
Указывает частичное состояние, которое необходимо обновить,callback
Это функция обратного вызова после обновления статуса.
3.1.2 определение функции enqueueSetState
Взято изReactFiberClassComponent.js
документ.
enqueueSetState(inst, payload, callback) {
const fiber = getInstance(inst);
const currentTime = requestCurrentTime();
const suspenseConfig = requestCurrentSuspenseConfig();
const expirationTime = computeExpirationForFiber(
currentTime,
fiber,
suspenseConfig,
);
// 创建一个update对象
const update = createUpdate(expirationTime, suspenseConfig);
// payload存放的是要更新的状态,即partialState
update.payload = payload;
// 如果定义了callback,则将callback挂载在update对象上
if (callback !== undefined && callback !== null) {
update.callback = callback;
}
// ...省略...
// 将update对象添加至更新队列中
enqueueUpdate(fiber, update);
// 添加调度任务
scheduleWork(fiber, expirationTime);
},
функцияenqueueSetState
создастupdate
объект и обновит состояниеpartialState
, функция обратного вызова после обновления статусаcallback
и срок действия рендераexpirationTime
и т.д. будут смонтированы на этом объекте. затемupdate
Объект добавляется в очередь обновлений, и создается запланированная задача.
Если компонент вызывается несколько раз перед рендерингомsetState
, будет несколькоupdate
Объекты будут добавлены в очередь обновлений по очереди, а также будет создано несколько задач планирования.
3.1.3 определение функции createUpdate
Взято изReactUpdateQueue.js
документ.
export function createUpdate(
expirationTime: ExpirationTime,
suspenseConfig: null | SuspenseConfig,
): Update<*> {
let update: Update<*> = {
expirationTime,
suspenseConfig,
// 添加TAG标识,表示当前操作是UpdateState,后续会用到。
tag: UpdateState,
payload: null,
callback: null,
next: null,
nextEffect: null,
};
return update;
}
функцияcreateUpdate
создастupdate
объект для хранения обновленного состоянияpartialState
, функция обратного вызова после обновления статусаcallback
и срок действия рендераexpirationTime
.
3.2 Механизм обновления состояния setState
Как видно из рисунка выше, каждый вызовsetState
Каждая функция создает запланированную задачу. Затем после серии вызовов функций функция в конечном итоге будет вызванаupdateClassComponent
.
Красная область на рисунке включает в себя множество точек знаний, которые имеют мало общего с механизмом обновления состояния, который мы хотим обсудить.На этот раз это не является предметом нашего обсуждения, поэтому мы сначала пропускаем его и ждем последующего исследования (копания). .
Кратко расскажем о том, как шаг за шагом обновляется состояние экземпляра компонента.
3.2.1 Функция getstatefromupdate
Взято изReactUpdateQueue.js
документ.
function getStateFromUpdate<State>(
workInProgress: Fiber,
queue: UpdateQueue<State>,
update: Update<State>,
prevState: State,
nextProps: any,
instance: any,
): any {
switch (update.tag) {
// ....省略 ....
// 见3.3节内容,调用setState会创建update对象,其属性tag当时被标记为UpdateState
case UpdateState: {
// payload 存放的是要更新的状态state
const payload = update.payload;
let partialState;
// 获取要更新的状态
if (typeof payload === 'function') {
partialState = payload.call(instance, prevState, nextProps);
} else {
partialState = payload;
}
// partialState 为null 或者 undefined,则视为未操作,返回上次状态
if (partialState === null || partialState === undefined) {
return prevState;
}
// 注意:此处通过Object.assign生成一个全新的状态state, state的引用地址发生了变化。
return Object.assign({}, prevState, partialState);
}
// .... 省略 ....
}
return prevState;
}
getStateFromUpdate
Основная функция функции — хранить в объекте обновленияupdate
ВверхpartialState
с последнимprevState
Объединяйте объекты для создания нового состояния.
Уведомление:
-
Object.assign
Первый параметр — пустой объект, что означает, что адрес ссылки нового объекта состояния изменился. -
Object.assign
Делается поверхностная копия, а не глубокая копия.
3.2.2 функция processUpdateQueue
Взято изReactUpdateQueue.js
документ.
export function processUpdateQueue<State>(
workInProgress: Fiber,
queue: UpdateQueue<State>,
props: any,
instance: any,
renderExpirationTime: ExpirationTime,
): void {
// ...省略...
// 获取上次状态prevState
let newBaseState = queue.baseState;
/**
* 若在render之前多次调用了setState,则会产生多个update对象。这些update对象会以链表的形式存在queue中。
* 现在对这个更新队列进行依次遍历,并计算出最终要更新的状态state。
*/
let update = queue.firstUpdate;
let resultState = newBaseState;
while (update !== null) {
// ...省略...
/**
* resultState作为参数prevState传入getStateFromUpdate,然后getStateFromUpdate会合并生成
* 新的状态再次赋值给resultState。完成整个循环遍历,resultState即为最终要更新的state。
*/
resultState = getStateFromUpdate(
workInProgress,
queue,
update,
resultState,
props,
instance,
);
// ...省略...
// 遍历下一个update对象
update = update.next;
}
// ...省略...
// 将处理后的resultState更新到workInProgess上
workInProgress.memoizedState = resultState;
}
Перед визуализацией компонента React мы обычно вызываем его несколько раз.setState
, каждый раз, когда вы звонитеsetState
сгенерирует объект обновления. Эти объекты обновления будут храниться в очереди очереди в виде связанного списка.processUpdateQueue
Функция будет проходить очередь по очереди, и каждый обход будет проходить предыдущий.prevState
с объектом обновленияpartialState
Слияние, когда все обходы завершены, можно рассчитать конечное состояние, которое нужно обновить, и в это время оно будет сохранено в workInProgress.memoizedState
характеристики.
3.2.3 функция updateClassInstance
Взято изReactFiberClassComponent.js
документ.
function updateClassInstance(
current: Fiber,
workInProgress: Fiber,
ctor: any,
newProps: any,
renderExpirationTime: ExpirationTime,
): boolean {
// 获取当前实例
const instance = workInProgress.stateNode;
// ...省略...
const oldState = workInProgress.memoizedState;
let newState = (instance.state = oldState);
let updateQueue = workInProgress.updateQueue;
// 如果更新队列不为空,则处理更新队列,并将最终要更新的state赋值给newState
if (updateQueue !== null) {
processUpdateQueue(
workInProgress,
updateQueue,
newProps,
instance,
renderExpirationTime,
);
newState = workInProgress.memoizedState;
}
// ...省略...
/**
* shouldUpdate用于标识组件是否要进行渲染,其值取决于组件的shouldComponentUpdate生命周期执行结果,
* 亦或者PureComponent的浅比较的返回结果。
*/
const shouldUpdate = checkShouldComponentUpdate(
workInProgress,
ctor,
oldProps,
newProps,
oldState,
newState,
nextContext,
);
if (shouldUpdate) {
// 如果需要更新,则执行相应的生命周期函数
if (typeof instance.UNSAFE_componentWillUpdate === 'function' ||
typeof instance.componentWillUpdate === 'function') {
startPhaseTimer(workInProgress, 'componentWillUpdate');
if (typeof instance.componentWillUpdate === 'function') {
instance.componentWillUpdate(newProps, newState, nextContext);
}
if (typeof instance.UNSAFE_componentWillUpdate === 'function') {
instance.UNSAFE_componentWillUpdate(newProps, newState, nextContext);
}
stopPhaseTimer();
}
// ...省略...
}
// ...省略...
/**
* 不管shouldUpdate的值是true还是false,都会更新当前组件实例的props和state的值,
* 即组件实例的state和props的引用地址发生变化。也就是说即使我们采用PureComponent来减少无用渲染,
* 但并不代表该组件的state或者props的引用地址没有发生变化!!!
*/
instance.props = newProps;
instance.state = newState;
return shouldUpdate;
}
Как видно из приведенного выше кода,updateClassInstance
Функция в основном реализует следующие функции:
- Пройдите очередь обновлений, сгенерируйте новое состояние и обновите его до состояния экземпляра компонента;
- Возвращает идентификатор того, следует ли обновлять
shouldUpdate
, результат операции этого значения зависит отshouldComponentUpdate
Результат выполнения функции жизненного цикла илиPureComponent
Неглубокие результаты сравнения ; - если
shouldUpdate
ценностьtrue
, затем выполните соответствующую функцию жизненного циклаcomponentWillUpdate
;
На этом этапе обратите особое внимание на следующие моменты:
- Меняется состояние экземпляра компонента, то есть меняется адрес ссылки;
- даже если
PureComponent
илиshouldComponentUpdate
Чтобы уменьшить бесполезный рендеринг, но адрес ссылки реквизита или состояние экземпляра компонента все равно меняется.
Здесь интерпретируется код, предположительно у каждого есть ответы на два упомянутых ранее вопроса.
3.2.4 функция updateClassComponent
function updateClassComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
nextProps,
renderExpirationTime: ExpirationTime,
) {
// 获取组件实例
const instance = workInProgress.stateNode;
// ...省略...
let shouldUpdate;
/**
* 1. 完成组件实例的state、props的更新;
* 2. componentWillUpdate、shouldComponentUpdate生命周期函数执行完毕;
* 3. 获取是否要进行更新的标识shouldUpdate;
*/
shouldUpdate = updateClassInstance(
current,
workInProgress,
Component,
nextProps,
renderExpirationTime,
);
/**
* 1. 如果shouldUpdate值为false,则退出渲染;
* 2. 执行render函数
*/
const nextUnitOfWork = finishClassComponent(
current,
workInProgress,
Component,
shouldUpdate,
hasContext,
renderExpirationTime,
);
// 返回下一个任务单元
return nextUnitOfWork;
}
Как видно из приведенного выше кода,updateClassComponent
Функция в основном реализует следующие функции:
- Завершите обновление состояния и реквизита экземпляра компонента;
- воплощать в жизнь
componentWillUpdate
,shouldComponentUpdate
и другие функции жизненного цикла; - Завершите визуализацию экземпляра компонента;
- Возвращает следующий ожидающий модуль задачи;
4. Резюме
После интерпретации кода в предыдущей главе я считаю, что каждый должен понимать функциюsetState
Должно быть новое понимание. На два вопроса, упомянутых выше, должны быть свои ответы. Здесь я кратко резюмирую:
каждый раз, когда вызывается функцияsetState
, react добавит состояние для обновления в очередь обновлений и сгенерирует задачу планирования. Запланированные задачи выполняют две функции во время выполнения:
- Пройдите очередь обновления, вычислите новое состояние состояния и обновите его до экземпляра компонента;
- Согласно идентификации
shouldUpdate
чтобы решить, следует ли повторно визуализировать экземпляр компонента, и определитьshouldUpdate
значение зависит отPureComponent
Результат поверхностного сравнения компонентов или функция жизненного циклаshouldComponentUpdate
Результаты;
использоватьPureComponent
Компонент может уменьшить повторную визуализацию экземпляра компонента, но состояние экземпляра компонента получает новое состояние, поэтому адрес ссылки изменился.
Статья пока пишется здесь, если вы считаете, что пост в блоге неплох, то, пожалуйста, поставьте ему палец вверх.
разное: