В React 18 был представлен новый API —startTransition
Также есть два новых крючка -useTransition
иuseDeferredValue
, они по существу неотделимы от понятияtransition
.
Изучая эту главу, вы получите следующее:
-
Transition
Первоначальный замысел, какую проблему он решил. -
startTransition
использование и принципы. -
useTranstion
использование и принципы. -
useDeferredValue
использование и принципы.
что вызваноtransition
Английский перевод'переход', то переход здесь относится к эффекту перехода данных, показывающих переход с нуля в обновлении. использоватьReactWgПредложение в startTransition описывает startTransition .
startTransition сохраняет отзывчивость страницы при обновлении большого экрана, этот API может помечать обновления React как особый тип обновления.
transitions
, с этим конкретным обновлением React смог сохранить визуальную обратную связь и отзывчивость браузера.
Только из вышеуказанной парыstartTransition
описание, нам сложно понять, какую проблему решает этот новый API. Но это не беда, позвольте мне постепенно разобрать, что делает этот апи и сценарии его применения.
Две переходные миссии
1 Рождение перехода
Почему появляется Переход? Переход по сути решает проблему параллелизма рендеринга.В описании startTransition в React 18 много раз упоминалась ситуация «большого экрана».Под большим экраном здесь подразумевается не просто размер, а большой объем данных.Сценарии с много узлов элементов DOM, таких как визуализация данных на большом экране, в этом сценарии изменения, вызванные одним обновлением, могут быть огромными, поэтому частые обновления и частые вызовы для выполнения js-транзакций требуют от браузера выполнения большого объема работы по рендерингу. , поэтому пользователь чувствует себя застрявшим.
Переход в основном используется для некоторых менее срочных обновлений.До React 18 все задачи обновления считались срочными задачами.В React 18 он родилсяconcurrent Mode
Режим, в этом режиме рендеринг может быть прерван, а задачи с низким приоритетом могут позволить задачам с высоким приоритетом сначала обновить рендеринг. Можно сказать, что React 18 способствует хорошему пользовательскому опыту. отconcurrent Mode
прибытьsusponse
сноваstartTransition
Несомненно, все это вращается вокруг лучшего пользовательского опыта.
startTransition зависит отconcurrent Mode
Рендеринг в параллельном режиме. То есть в React 18 используетсяstartTransition
, то нужно сначала включить concurrent mode, то есть нужно пройтиcreateRoot
Создать рут. Давайте сначала посмотрим на разницу между созданием Root в двух режимах.
традиционная унаследованная модель
import ReactDOM from 'react-dom'
/* 通过 ReactDOM.render */
ReactDOM.render(
<App />,
document.getElementById('app')
)
v18 параллельный режим параллельный режим
import ReactDOM from 'react-dom'
/* 通过 createRoot 创建 root */
const root = ReactDOM.createRoot(document.getElementById('app'))
/* 调用 root 的 render 方法 */
root.render(<App/>)
Условия использования startTransition описаны выше, далее обсудим, для каких сценариев используется startTransition. Как упоминалось ранее, React 18 определил задачи обновления с разными приоритетами и почему существуют задачи с разными приоритетами. В мире нет дороги, и когда ходит больше людей, она становится дорогой, и то же самое верно для генерации приоритетов, В мире React приоритета нет.
Если в обновлении одни и те же задачи, то приоритета задач нет вообще, и достаточно обрабатывать задачи пакетами, но на деле все обстоит иначе. Возьмем очень распространенный сценарий:просто есть одинinput
форма. И есть список большого количества данных, с помощью формы ввода, поиска и фильтрации данных списка. В этом случае существует несколько одновременных задач обновления. соответственно
- Первый: форма ввода должна получать статус в режиме реального времени, поэтому она контролируется, поэтому для обновления содержимого ввода необходимо запустить задачу обновления.
- Второе: меняется входной контент, фильтрация списка, повторная отрисовка списка — тоже задача.
Для первого типа обновления при вводе ожидается, что визуальные изменения появятся немедленно.Если вводимый контент задерживается и отображается при вводе, это даст пользователю крайне плохое визуальное впечатление. Второй тип обновления заключается в фильтрации данных в списке и отображении списка в соответствии с содержанием данных.Этот тип обновления имеет более низкий приоритет, чем предыдущий. Затем, если пользователь предпочитает изменить состояние поля ввода во время процесса поиска ввода, то при нормальных обстоятельствах событие onChange привязывается к вводу, чтобы инициировать два вышеуказанных типа обновлений.
const handleChange=(e)=>{
/* 改变搜索条件 */
setInputValue(e.target.value)
/* 改变搜索过滤后列表状态 */
setSearchQuery(e.target.value)
}
написано выше, тоsetInputValue
иsetSearchQuery
Результирующее обновление является обновлением того же приоритета. И сказал ранее,Приоритет обновления изменения состояния поля ввода выше, чем приоритет обновления списка., в это время на сцену выходит наш главный герой. использоватьstartTransition
Различайте два обновления.
const handleChange=()=>{
/* 高优先级任务 —— 改变搜索条件 */
setInputValue(e.target.value)
/* 低优先级任务 —— 改变搜索过滤后列表状态 */
startTransition(()=>{
setSearchQuery(e.target.value)
})
}
- Как и выше, менее срочная задача обновления setSearchQuery изолируется через startTransition. Как это работает в реальной ситуации? Давайте проверим это.
2 сценария моделирования
Далее мы моделируем описанный выше сценарий. Процесс примерно такой:
- Есть поле поиска и список из 10 000 элементов данных, каждый из которых имеет одну и ту же копию.
- Изменение ввода изменяет содержимое ввода в реальном времени (первое обновление), а затем выделяет то же значение поиска в списке (второе обновление).
- Управление одной кнопкой Нормальный режим |
transition
модель.
/* 模拟数据 */
const mockDataArray = new Array(10000).fill(1)
/* 高量显示内容 */
function ShowText({ query }){
const text = 'asdfghjk'
let children
if(text.indexOf(query) > 0 ){
/* 找到匹配的关键词 */
const arr = text.split(query)
children = <div>{arr[0]}<span style={{ color:'pink' }} >{query}</span>{arr[1]} </div>
}else{
children = <div>{text}</div>
}
return <div>{children}</div>
}
/* 列表数据 */
function List ({ query }){
console.log('List渲染')
return <div>
{
mockDataArray.map((item,index)=><div key={index} >
<ShowText query={query} />
</div>)
}
</div>
}
/* memo 做优化处理 */
const NewList = memo(List)
-
List
Компонент рендерит 10 000ShowText
компоненты. В компоненте ShowText динамическое выделение будет осуществляться посредством входящего запроса. - потому что каждое изменение
query
Выполнит 10000 повторных обновлений рендеринга, а также отобразит выделенное содержимое запроса, поэтому удовлетворитеПараллельный рендерингместо действия.
Следующим шагом является написание компонента приложения.
export default function App(){
const [ value ,setInputValue ] = React.useState('')
const [ isTransition , setTransion ] = React.useState(false)
const [ query ,setSearchQuery ] = React.useState('')
const handleChange = (e) => {
/* 高优先级任务 —— 改变搜索条件 */
setInputValue(e.target.value)
if(isTransition){ /* transition 模式 */
React.startTransition(()=>{
/* 低优先级任务 —— 改变搜索过滤后列表状态 */
setSearchQuery(e.target.value)
})
}else{ /* 不加优化,传统模式 */
setSearchQuery(e.target.value)
}
}
return <div>
<button onClick={()=>setTransion(!isTransition)} >{isTransition ? 'transition' : 'normal'} </button>
<input onChange={handleChange}
placeholder="输入搜索内容"
value={value}
/>
<NewList query={query} />
</div>
}
Давайте посмотрим, что делает приложение.
- Сначала обработайте событие onchange через событие handleChange.
-
button
кнопка для переключенияtransition(установить приоритет) иnormal(нормальный режим). Далее стоит стать свидетелем волшебного момента.
Эффекты в обычном режиме:
- Хорошо видно, что в обычном режиме вводимый контент и представление контента ненормально застревают, что создает очень плохое взаимодействие с пользователем.
Эффект в переходном режиме:
- После обработки большого количества одновременных задач через startTransition хорошо видно, что ввод будет отрисовываться нормально, а задача обновления списка будет отставать, но пользовательский опыт значительно улучшился.
общий эффект:
- Приходите испытать немного магии startTransition.
Суммировать:Из вышеизложенного мы можем интуитивно увидеть, что startTransition играет ключевую роль в обработке задач перехода и оптимизации взаимодействия с пользователем.
3 Почему бы не установить время ожидания
Вышеупомянутая проблема может бытьsetSearchQuery
Обновление упаковано вsetTimeout
Внутри вроде следующее.
const handleChange=()=>{
/* 高优先级任务 —— 改变搜索条件 */
setInputValue(e.target.value)
/* 把 setSearchQuery 通过延时器包裹 */
setTimeout(()=>{
setSearchQuery(e.target.value)
},0)
}
- Здесь через setTimeout обновление ставится внутрь setTimeout, тогда мы все знаем, что setTimeout — это задача-задержка, она не будет блокировать нормальную отрисовку браузера, и браузер установит setTimeout в следующий простой. Итак, как это работает? Давайте взглянем:
- Как вы можете видеть выше, setTimeout действительно может улучшить состояние ввода, но поскольку сам setTimeout также является задачей макроса, а каждый триггер onchange также является задачей макроса, setTimeout также повлияет на интерактивность страницы.
Подводя итог, преимущества и отличия startTransition по сравнению с setTimeout:
-
С одной стороны: логика обработки startTransition и setTimeout имееточень важное отличие, setTimeout — это асинхронно отложенное выполнение, а функция обратного вызова startTransition выполняется синхронно. Любое обновление в startTransition будет помечено
transition
, React будет оценивать этот флаг при обновлении, чтобы решить, следует ли завершить обновление. Таким образом, Transition можно понимать как обновление раньше, чем setTimeout. Но при этом необходимо обеспечить нормальный отклик UI.На устройстве с хорошей производительностью задержка между двумя обновлениями перехода будет небольшой, а на медленном устройстве задержка будет большая, но это не повлияет на реакцию пользовательского интерфейса. -
С другой стороны, только в приведенном выше примере вы можете видеть, что для параллельных сценариев рендеринга setTimeout по-прежнему будет замораживать страницу. Потому что по истечении таймаута будут выполняться и задачи setTimeout, а они тоже макрозадачи, взаимодействующие с пользователем, поэтому взаимодействие со страницей все равно будет заблокировано. Так
transition
Другое дело, в режиме conCurrent,startTransition
Можно прервать рендеринг, поэтому это не приведет к зависанию страницы.React позволяет выполнять эти задачи во время простоя браузера, поэтому при вводе вышеуказанного входного содержимого startTransition отдает приоритет обновлению входного значения. , а затем рендеринг списка .
4 Почему бы не дросселировать и не трясти
Тогда давайте подумаем над другим вопросом, а почему нет троттлинга и антишейка. Во-первых, могут ли троттлинг и антишейк решить проблему заикания? Ответ да, до этого нет апи типа перехода, можно только пройтиСтабилизаторидросселированиеБороться с этим, а потом бороться с антишейком.
const SetSearchQueryDebounce = useMemo(()=> debounce((value)=> setSearchQuery(value),1000) ,[])
const handleChange = (e) => {
setInputValue(e.target.value)
/* 通过防抖处理后的 setSearchQuery 函数。 */
SetSearchQueryDebounce(e.target.value)
}
- SetSearchQuery будет обрабатывать анти-дрожание, как указано выше. Потом смотрим на эффект.
Из вышеизложенного можно интуитивно почувствовать, что после обработки против сотрясения ввод в основном не затрагивается. Но есть проблема, что время задержки смены вида списка становится больше. затем переход иТроттлинг и защита от тряскиСущественная разница заключается в следующем:
-
С одной стороны, стабилизация образа дроссельной заслонки также по существу, по существу, устанавливается, но контролирует частоту выполнения, то содержимое можно найти через печать, принцип - сделать рендеринг раз. Переходы и сравнивали его, не уменьшали количество рендеринга.
-
С другой стороны, троттлинг и анти-тряска должны быть эффективно освоены.
Delay Time
Время задержки. Если время слишком велико, это даст людям ощущение задержки рендеринга. Если время слишком мало, это вызовет предыдущую проблему, аналогичную setTimeout(fn,0). И startTransition не нужно так много учитывать.
5 Влияние на производительность компьютера
Эффект перехода более заметен на медленных компьютерах, давайте посмотримReal world example
Обратите внимание на скорость слайдера
- Производительность обработки на устройствах выше и быстрее. Не используйте startTransition .
- Производительность обработки на устройствах выше и быстрее. Используйте начальный переход.
- На медленных устройствах с низкой производительностью обработки startTransition не используется.
- На медленных устройствах с низкой производительностью обработки используйте startTransition.
Три функции перехода
Теперь, когда было сказано об изначальном намерении перехода, давайте посмотрим на введение функции перехода.
1 Что такое перегрузка.
Обновление состояния, как правило, будет разделено на две категории:
- Обновление первого срочного задания. Например, некоторые взаимодействия с пользователем, клавиши, клики, набор текста и так далее.
- Вторая категория — задача обновления перехода. Например, переходы пользовательского интерфейса из одного представления в другое.
2 Что такое startTransition
уже использовано вышеstartTransition
Я полагаю, что многие учащиеся уже знают, как использовать startTransition для выполнения избыточных задач.
startTransition(scope)
- scope — это callback-функция, и задачи обновления в ней будут помечены какЗадача обновления перехода, задача обновления перехода будет понижена до приоритета обновления в параллельной сцене рендеринга, и обновление будет прервано.
использовать
startTransition(()=>{
/* 更新任务 */
setSearchQuery(value)
})
3 Что такое useTranstion
Выше введен startTransition, а также упоминается задача перехода.По сути, задача перехода имеет переходный период.В этот период текущая задача по сути прерывается.Так что же делать в переходный период,или подскажите пользователю когда переносить задачу.pending
состояние, когдаpending
Статус завершен.
Для решения этой проблемы React предоставляет хук с состоянием isPending — useTransition. Выполнение useTransition возвращает массив. Массив имеет два значения состояния:
- Первый — это флаг при переходе в состояние — isPending.
- Второй — это метод, который можно понимать как описанный выше startTransition. Задачу обновления внутри можно превратить в задачу перехода.
import { useTransition } from 'react'
/* 使用 */
const [ isPending , startTransition ] = useTransition ()
Затем, когда задача находится в состоянии наведения,isPending
заtrue
, который может отображаться как пользовательский интерфейс, которого ждет пользователь. Например:
{ isPending && < Spinner / > }
useTranstion на практике
Далее, давайте попрактикуемся с useTranstion или повторно используем приведенную выше демонстрацию. Модификация приведенного выше демо.
export default function App(){
const [ value ,setInputValue ] = React.useState('')
const [ query ,setSearchQuery ] = React.useState('')
const [ isPending , startTransition ] = React.useTransition()
const handleChange = (e) => {
setInputValue(e.target.value)
startTransition(()=>{
setSearchQuery(e.target.value)
})
}
return <div>
{isPending && <span>isTransiton</span>}
<input onChange={handleChange}
placeholder="输入搜索内容"
value={value}
/>
<NewList query={query} />
</div>
}
- как указано выше
useTransition
,isPending
Представляет переходное состояние, когда в переходном состоянии отображаетсяisTransiton
намекать.
Давайте посмотрим на эффект:
Видно, что состояние во время перехода фиксируется точно.
4 Что такое useDeferredValue
В приведенном выше сценарии мы обнаружили, что запрос по сути также является значением, но обновление запроса отстает от обновления значения. Что ж, React 18 обеспечиваетuseDeferredValue
Можно сделать вывод запаздывания состояния. Эффект реализации useDeferredValue также похож наtranstion
, при выполнении срочной задачи получается новое состояние, и это новое состояние называется DeferredValue.
Каковы сходства и различия между useDeferredValue и useTransition выше?
Тот же пункт:
- useDeferredValue по существу совпадает с внутренней реализацией, а useTransition помечен как задача обновления перехода.
разница:
-
useTransition превращает задачу обновления внутри startTransition в задачу перехода
transtion
, а useDeferredValue — получить новое значение из исходного значения через задачу перехода, и это значение используется в качестве состояния задержки.Один — обрабатывать часть логики, другой — создавать новое состояние. - Еще одно отличие useDeferredValue в том, что эта задача по сути выполняется внутри useEffect, а внутренняя логика useEffect выполняется асинхронно, поэтому она в определенной степени отстает
useTransition
.useDeferredValue
=useEffect
+transtion
Затем вернитесь к демо, кажется, что запрос DeferredValue становится более подходящим для реальной ситуации, тогда модифицируйте демо.
export default function App(){
const [ value ,setInputValue ] = React.useState('')
const query = React.useDeferredValue(value)
const handleChange = (e) => {
setInputValue(e.target.value)
}
return <div>
<button>useDeferredValue</button>
<input onChange={handleChange}
placeholder="输入搜索内容"
value={value}
/>
<NewList query={query} />
</div>
}
- Как вы можете видеть выше, запрос — это значение, сгенерированное useDeferredValue.
Эффект:
Четыре принципа
Далее идет принципиальная ссылка, от startTransition к useTranstion и к useDeferredValue. Принцип очень прост по своей природе.
1 startTransition
Давайте сначала посмотрим, как реализован самый простой startTransition.
react/src/ReactStartTransition.js -> startTransition
export function startTransition(scope) {
const prevTransition = ReactCurrentBatchConfig.transition;
/* 通过设置状态 */
ReactCurrentBatchConfig.transition = 1;
try {
/* 执行更新 */
scope();
} finally {
/* 恢复状态 */
ReactCurrentBatchConfig.transition = prevTransition;
}
}
-
startTransition
Принцип очень прост, немного похож на логику пакетной обработки batchUpdate в React v17. Это путем установки переключателя, и переключательtransition = 1
, а затем выполнить обновление, задачи обновления в нем получатtranstion
логотип. -
Далее он будет обработан отдельно в одновременном режиме
transtion
тип обновления.
Его принципиальная схема показана ниже.
2 useTranstion
следующий взглядuseTranstion
внутренняя реализация.
react-reconciler/src/ReactFiberHooks.new.js -> useTranstion
function mountTransition(){
const [isPending, setPending] = mountState(false);
const start = (callback)=>{
setPending(true);
const prevTransition = ReactCurrentBatchConfig.transition;
ReactCurrentBatchConfig.transition = 1;
try {
setPending(false);
callback();
} finally {
ReactCurrentBatchConfig.transition = prevTransition;
}
}
return [isPending, start];
}
Этот код не является исходным кодом. Я комбинирую и сжимаю содержимое в исходном коде.
- Как видно из вышеизложенного, useTranstion, по сути,
useState
+startTransition
. - Изменение статуса PENDING через USSTATE. Во время выполнения MountTransition триггер дважды
setPending
, однажды вtransition = 1
до, один раз после. будет обновляться нормально один разsetPending(true)
, когда-то будет действовать какtransition
Обновление переходной миссииsetPending(false);
, поэтому время перехода можно точно зафиксировать.
Его принципиальная схема показана ниже.
3 useDeferredValue
Наконец, давайте посмотрим наuseDeferredValue
Принцип внутренней реализации.
react-reconciler/src/ReactFiberHooks.new.js -> useTranstion
function updateDeferredValue(value){
const [prevValue, setValue] = updateState(value);
updateEffect(() => {
const prevTransition = ReactCurrentBatchConfig.transition;
ReactCurrentBatchConfig.transition = 1;
try {
setValue(value);
} finally {
ReactCurrentBatchConfig.transition = prevTransition;
}
}, [value]);
return prevValue;
}
Поток обработки useDeferredValue выглядит следующим образом.
- Как вы можете видеть из приведенного выше, useDeferredValue по существу
useDeferredValue
=useState
+useEffect
+transition
- Передавая значение useDeferredValue, useDeferredValue сохраняет состояние через useState.
- Затем в useEffect проходит
transition
Режим обновления значения. Это гарантирует, что DefredValue отстает от обновлений государства и удовлетворяетtransition
Принцип обновления перехода.
Его принципиальная схема показана ниже.
Четыре резюме
Знания, рассматриваемые в этой главе, следующие:
-
Transition
Первоначальный замысел, какую проблему он решил. -
startTransition
использование и принципы. -
useTranstion
использование и принципы. -
useDeferredValue
использование и принципы.