Предыдущая статья представилаSuspense
, то эта статья расскажет о его хорошем партнереuseTransition
. Если вы поклонник React, эти две статьи нельзя пропустить.
Мы знаем, что React претерпел потрясающую внутреннюю оптимизацию, а некоторые компактные новые API также были предоставлены извне.Эти API в основном используются для оптимизации взаимодействия с пользователем.. React официально использует очень длинный документ«Параллельные шаблоны пользовательского интерфейса»Специально представить мотивацию и создание этого аспекта, главным героем которого являетсяuseTransition
.
Статьи по Теме
- Это, пожалуй, самый распространенный способ открыть React Fiber (разрезка по времени)🔥Приходи первым
- Ранний доступ к React Concurrent Mode, часть 1: Приостановите мирЧасть 1
План этой статьи
- Каков сценарий применения?
- дебюты useTransition
- Предварительное исследование принципа использования Transition
- А как насчет использованияDeferedValue ?
- Суммировать
- использованная литература
React использует’Параллельная вселенная'Для сравнения useTransition API. какие?
Это будет лучше понятно, если использовать ветку Git в качестве аналогии, как показано на рисунке ниже, React может просматривать текущее представление (которое можно рассматривать какMaster
) в веткеFork
из новой ветки (также называемойPending
), обновить эту новую ветку, аMaster
Оставаясь отзывчивыми и обновляемыми, эти две ветки подобны «параллельным вселенным», где они не мешают друг другу. когдаPending
Ветка готова «сделана», затем объединена (зафиксирована) вMaster
филиал.
useTransition
Как временной туннель, позволяющий компонентам войти в параллельную вселенную и ждать в этой параллельной вселенной.异步状态
(асинхронный запрос, задержка, что угодно) готов. Конечно, компоненты нельзя бесконечно ожидать в параллельных вселенных,useTranstion
Вы можете настроить период тайм-аута.异步状态
Не готовы также будут вынуждены вернуться в реальный мир. Вернувшись в реальный мир, React немедленно объединит изменения компонента Pengding и представит их пользователю.
Таким образом, вы можете думать о компонентах React как о трех состояниях в параллельном режиме:
- Normal- Компоненты в нормальном состоянии
- Suspense- Компонент приостановлен из-за асинхронного состояния
- Pending- Компоненты для входа в параллельные вселенные. Соответственно, есть также ожидающие «изменения состояния», эти изменения не сразу отправляются в пользовательский интерфейс React, а кэшируются, ожидая готовности Suspense или истечения времени ожидания.
Возможно, вы еще не совсем поняли это, ничего страшного, продолжайте читать.
Каков сценарий применения?
Какая польза от «параллельных вселенных»? Мы не говорим о вещах на уровне кода или архитектуры. сингл отUI
Погляди:В некоторых сценариях взаимодействия с пользовательским интерфейсом мы не хотим сразу применять изменения к странице..
🔴Например, если вы переключаетесь с одной страницы на другую, загрузка новой страницы может занять некоторое время. На самом деле, мы предпочитаем оставаться на предыдущей странице некоторое время и сохранять реакцию на некоторые операции. Например, мы можем отменить, или выполнить другие операции, и дать я вижу пустую страницу без ничего или значок состояния загрузки в режиме ожидания, и я чувствую, что я делаю ненужное ожидание.
Такой сценарий взаимодействия на самом деле очень распространен.Прямым примером является браузер:
Притворись, что я могу позволить себе AirPods
И наш часто используемый Github:
Известный зарубежный сайт знакомствНапример, я хочу нажать, чтобы купитьAirPods
, браузер останется на предыдущей странице до тех пор, пока не будет получен ответ на запрос следующей страницы или пока не истечет время ожидания. Кроме того, браузер подскажет статус запроса через индикатор загрузки в адресной строке. Такой дизайн взаимодействия намного лучше, чем переключение и показ пустой страницы: страница может оставаться отзывчивой для пользователя, а запрос можно отменить в любой момент и остаться на исходной странице.
Конечно, есть и другой сценарий взаимодействия при переключении Tab, и мы надеемся, что он будет переключен сразу, иначе пользователю будет казаться, что нажатие не работает.
«Параллельные вселенные» с еще одним преимуществом:🔴Мы предполагаем, что в большинстве случаев запрос данных очень быстрый, в этом случае нет необходимости отображать состояние загрузки, из-за чего страница будет мерцать и трястись. На самом деле через короткую задержку можно уменьшить частоту отображения состояния загрузки.
Кроме того,🔴useTransition также можно использовать для переноса низкоприоритетных обновлений.. В текущей ситуации React не хочет раскрывать слишком много низкоуровневых деталей режима Concurrent. Используйте useTransition только в том случае, если вы хотите запланировать обновления с низким приоритетом.
дебюты useTransition
Как показано выше, сначала мы определяем различные состояния страницы, как описано в официальной документации React.В нем упоминается, что загрузка страницы состоит из следующих трех этапов.:
① Переход
Относится к стадии, когда страница не готова, ожидая загрузки важных данных. В соответствии с различными стратегиями отображения страница может находиться в следующих двух состояниях:
-
⚛️Отступил. Немедленно переключайте страницы, показывая большой индикатор загрузки или пустую страницу. Что означает «вырождение»?Согласно React, страница, которая изначально имела контент, а теперь не имеет контента, является своего рода вырождением или исторической «регрессией».
-
⚛️ В ожидании. Это
useTransition
Достигаемое состояние, т. е. оставаться на текущей странице и поддерживать отклик текущей страницы. существуетКлючевые данные готовывходитьSkeleton
(экран скелета) или подождите, пока тайм-аут не вернется кReceded
государство.
② Стадия загрузки (Загрузка)
Относится关键数据
Все готово для отображения скелета или рамки страницы. Этот этап имеет статус:
- ⚛️ Скелет. Ключевые данные были загружены, и на странице показана рамка основного тела.
③ этап готовности (Готово).
Это означает, что страница полностью загружена. Этот этап имеет статус:
- ⚛️ЗавершитьСтраница полностью отображается
В традиционном React, когда мы меняем состояние, чтобы перейти на новый экран, мы получаем🔴Receded
-> Skeleton
-> Complete
дорожка. достичь до🔴Pending
-> Skeleton
-> Complete
Этот путь загрузки является более сложным.useTransition
может изменить эту ситуацию.
Далее просто смоделируйте переключение страниц, сначала посмотрите, как оно загружается по умолчанию:
function A() {
return <div className="letter">A</div>;
}
function B() {
// ⚛️ 延迟加载2s,模拟异步数据请求
delay("B", 2000);
return <div className="letter">B</div>;
}
function C() {
// ⚛️ 延迟加载4s,模拟异步数据请求
delay("C", 4000);
return <div className="letter">C</div>;
}
// 页面1
function Page1() {
return <A />;
}
// 页面2
function Page2() {
return (
<>
<B />
<Suspense fallback={<div>Loading... C</div>}>
<C />
</Suspense>
</>
);
}
function App() {
const [showPage2, setShowPage2] = useState(false);
// 点击切换到页面2
const handleClick = () => setShowPage2(true)
return (
<div className="App">
<div>
<button onClick={handleClick}>切换</button>
</div>
<div className="page">
<Suspense fallback={<div>Loading ...</div>}>
{!showPage2 ? <Page1 /> : <Page2 />}
</Suspense>
</div>
</div>
);
}
Взгляните на беговой эффект:
После нажатия на тумблер мы сразу увидим большоеLoading...
, то B загружается через 2 с, а C загружается через 2 с. Этот процессReceded
-> Skeleton
-> Complete
Теперь, пожалуйста, используйте Transition здесь 🎉, просто модифицируя приведенный выше код:
// ⚛️ 导入 useTransition
import React, { Suspense, useState, useTransition } from "react";
function App() {
const [showPage2, setShowPage2] = useState(false);
// ⚛️ useTransition 接收一个超时时间,返回一个startTransition 函数,以及一个 pending
const [startTransition, pending] = useTransition({ timeoutMs: 10000 });
const handleClick = () =>
// ⚛️ 将可能触发 Suspense 挂起的状态变更包裹在 startTransition 中
startTransition(() => {
setShowPage2(true);
});
return (
<div className="App">
<div>
<button onClick={handleClick}>切换</button>
{/* ⚛️ pending 表示处于待定状态, 你可以进行一些轻微的提示 */}
{pending && <span>切换中...</span>}
</div>
<div className="page">
<Suspense fallback={<div>Loading ...</div>}>
{!showPage2 ? <Page1 /> : <Page2 />}
</Suspense>
</div>
</div>
);
}
API useTransition Hook относительно прост и состоит из 4 ключевых моментов:
-
timeoutMs
, указывает период ожидания переключения (самый длинный в параллельной вселенной), useTransition будет удерживать React на текущей странице до тех пор, пока он не будет активирован. Suspence не будет готов или не истечет время ожидания. -
startTransition
, переносите изменения состояния, которые могут вызвать переключение страниц (строго говоря, вызвать зависание приостановки) вstartTransition
Затем фактически startTransition предоставляет «обновленный контекст». Мы углубимся в детали в следующем разделе -
pending
, что указывает на то, что он находится в состоянии ожидания. Мы можем использовать это значение состояния, чтобы дать пользователю соответствующее приглашение. -
Suspense
, useTransition должен взаимодействовать с Suspense для реализации переходного состояния, то естьstartTransition
Обновления должны вызвать приостановку приостановки.
Взгляните на реальный эффект от операции!
можно найти в этомCodeSandboxПроверьте беговой эффект
Эффект точно такой же, как у «первого изображения» в начале этого раздела: React останется на текущей странице,pending
Становится истинным, тогда B готов первым, и тут же переключается интерфейс. Весь процесс соответствуетPending
-> Skeleton
-> Complete
маршрут из.
startTransition
середина变更
после запускаSuspense
, Реакция будет变更
Отмеченный в состоянии «Ожидание», React отложит «фиксацию» этих изменений. такНа самом деле никакой параллельной вселенной, о которой говорилось в начале, нет, такой высокой и волшебной, React просто задержал подачу этих изменений. Все, что мы видим в интерфейсе, — это старое или незавершенное состояние, предварительный рендеринг React в фоновом режиме..
Обратите внимание, что React пока не отправлял эти изменения. Это не означает, что React «застрял». Компоненты в состоянии «Ожидание» также получат ответ пользователя и внесут новые изменения состояния. Обновление нового состояния также может перезаписать или завершиться. Состояние ожидания.
Подытожим условия входа и выхода из состояния ожидания:
-
Введите ожиданиеГосударство в первую очередь должно быть
状态变更
завернут вstartTransition
, и эти обновления приводят к зависанию приостановки - Выход в ожиданииСуществует три варианта статуса: ① Приостановка готова; ② Тайм-аут; ③ Перезаписано или прекращено новым обновлением статуса.
Предварительное исследование принципа использования Transition
В этом разделе мы подробно изучаем useTransition, но вместо того, чтобы выбрасывать исходный код, мы рассматриваем его как черный ящик и углубляем ваше понимание useTransition с помощью нескольких экспериментов.
Предшественником useTransition былwithSuspenseConfig
, SebmarkbageОдин упоминается в мае этого годаPRпредставил его.
А именно, он просто хочет настроить Suspense. Мы также можем проверить это с помощью последнего исходного кода. Работа useTransition «кажется» очень простой:
function updateTransition(
config: SuspenseConfig | void | null,
): [(() => void) => void, boolean] {
const [isPending, setPending] = updateState(false); // 相当于useState
const startTransition = updateCallback( // 相当于useCallback
callback => {
setPending(true); // 设置 pending 为 true
// 以低优先级调度执行
Scheduler.unstable_next(() => {
// ⚛️ 设置suspenseConfig
const previousConfig = ReactCurrentBatchConfig.suspense;
ReactCurrentBatchConfig.suspense = config === undefined ? null : config;
try {
// 还原 pending
setPending(false);
// 执行你的回调
callback();
} finally {
// ⚛️ 还原suspenseConfig
ReactCurrentBatchConfig.suspense = previousConfig;
}
});
},
[config, isPending],
);
return [startTransition, isPending];
}
Это кажется очень распространенным, в чем смысл? Sebmarkbage также упомянул некоторую информацию в вышеупомянутом PR.
-
startTransition устанавливает ожидание в значение true, как только начинает выполняться. затем используйте
unstable_next
выполнить обратный вызов,нестабильность_некст может понизить приоритет обновлений. То есть «изменение», инициированное в обратном вызове нестабильного_следующего, будет иметь более низкий приоритет и уступит место высокоприоритетному обновлению, или, когда текущая транзакция будет занята, оно будет запланировано для применения в следующем период простоя, но может быть применен и немедленно. -
Суть в том
ReactCurrentBatchConfig.suspense
конфигурация, которая будет настраивать период ожидания приостановки.Это указывает на то, что изменения, вызванные этим интервалом, связаны с этимsuspenseConfig
, эти изменения будут вычисляться самостоятельно в соответствии с suspenseConfigexpiredTime
(Можно рассматривать как «приоритет»). Назовем эти изменения, связанные с suspenseConfig, какPending 变更
. -
Pending 变更
Инициированные повторные рендеры (Render) также будут связаны сsuspenseConfig
. Если приостановка срабатывает во время рендеринга, тоPending 变更
Коммиты будут отложены, они будут кэшироваться в памяти и не будут принудительно отображаться в пользовательском интерфейсе до тех пор, пока не истечет время приостановки, не будет готова или не будет перезаписана другими обновлениями. -
Pending 变更
Он только задерживается и подается, но это не повлияет на согласованность конечных данных и представления. React будет повторно отображать в памяти, просто не отправляя его в пользовательский интерфейс.
Внутренняя реализация React настолько сложна, что мне дорого копаться в ней или выражать ее словами. Итак, по-другому, чтобы понять его поведение экспериментальным (черным ящиком) способом:
Эти экспериментальные коды находятся в этомCodeSandboxсередина
1️⃣ Используйте startTransition для запуска задач с низким приоритетом
Этот эксперимент в основном используется для проверкиunstable_next
, это отменит приоритет обновления. С помощью следующих экспериментов мы будем наблюдать:startTransition
Изменения пакета будут обновлены чуть позже, когда задача будет занята, но финальное состояние останется прежним.
Экспериментальный код:
export default function App() {
const [count, setCount] = useState(0);
const [tick, setTick] = useState(0);
const [startTransition, pending] = useTransition({ timeoutMs: 10000 });
const handleClick = () => {
// ⚛️ 同步更新
setCount(count + 1);
startTransition(() => {
// ⚛️ 低优先级更新 tick
setTick(t => t + 1);
});
};
return (
<div className="App">
<h1>Hello useTransition</h1>
<div>
<button onClick={handleClick}>ADD + 1</button>
{pending && <span>pending</span>}
</div>
<div>Count: {count}</div>
{/* ⚛️ 这是一个复杂的组件,渲染需要一点时间,模拟繁忙的情况 */}
<ComplexComponent value={tick} />
</div>
);
}
Результаты эксперимента следующие:
В случае непрерывных щелчковComplexComponent
Обновления будут значительно отставать, потому что изменения тиков задерживаются и объединяются, но в итоге их результаты одинаковы.
2️⃣ Обновление startTransition вызывает приостановку
export default function App() {
const [count, setCount] = useState(0);
const [tick, setTick] = useState(0);
const [startTransition, pending] = useTransition({ timeoutMs: 10000 });
const handleClick = () => {
startTransition(() => {
setCount(c => c + 1);
setTick(c => c + 1);
});
};
return (
<div className="App">
<h1>Hello useTransition {tick}</h1>
<div>
<button onClick={handleClick}>ADD + 1</button>
{pending && <span className="pending">pending</span>}
</div>
<Tick />
<SuspenseBoundary id={count} />
</div>
);
}
const SuspenseBoundary = ({ id }) => {
return (
<Suspense fallback="Loading...">
{/* 这里会抛出一个Promise异常,3s 后 resolved */}
<ComponentThatThrowPromise id={id} />
</Suspense>
);
};
// Tick 组件每秒递增一次
const Tick = ({ duration = 1000 }) => {
const [tick, setTick] = useState(0);
useEffect(() => {
const t = setInterval(() => {
setTick(tick => tick + 1);
}, duration);
return () => clearInterval(t);
}, [duration]);
return <div className="tick">tick: {tick}</div>;
};
Когда мы нажимаем кнопку, счетчик и тик увеличиваются, и счетчик передается в SuspenseBoundary, который запускает Suspense.
Из приведенных выше результатов мы можем узнать, что в startTransition внесено изменение (с помощью suspenseConfig), и соответствующий повторный рендеринг запускает приостановку, поэтому он переходит в состояние ожидания, и их результаты рендеринга не будут «отправлены» немедленно, а страница останется в исходном состоянии. .
Кроме того, вы обнаружите, что тик компонента App будет «остановлен», как SuspenseBoundary (см. тик после Hello Transition), потому что изменение тика также связано с suspenseConfig.
С другой стороны, компонент Tick увеличивается каждую секунду и не блокируется.
Это означает, что после запуска Suspense любые изменения, связанные с SuspenseConfig, будут «приостановлены».
3️⃣ Вывести обновление галочки из области startTransition
На основании 2️⃣ setTick упоминается вне рамок startTransition:
export default function App() {
const [count, setCount] = useState(0);
const [tick, setTick] = useState(0);
const [startTransition, pending] = useTransition({ timeoutMs: 10000 });
console.log("App rendering with", count, tick, pending);
const handleClick = () => {
setTick(c => c + 1);
startTransition(() => {
setCount(c => c + 1);
});
};
const handleAddTick = () => setTick(c => c + 1);
useEffect(() => {
console.log("App committed with", count, tick, pending);
});
return (
<div className="App">
<h1>Hello useTransition {tick}</h1>
<div>
<button onClick={handleClick}>ADD + 1</button>
<button onClick={handleAddTick}>Tick + 1</button>
{pending && <span className="pending">pending</span>}
</div>
<Tick />
<SuspenseBoundary id={count} />
</div>
);
}
Теперь галочка будет обновляться немедленно, а SuspenseBoundary все еще находится в ожидании.
Откроем консоль и посмотрим вывод:
App rendering with 1 2 true # pending 被设置为true, count 这是时候是 1, 而 tick 是 2
App rendering with 1 2 true
read 1
App committed with 1 2 true # 进入Pending 状态之前的一次提交,我们在这里开始展示 pending 指示符
# 下面 Tick 更新了三次(3s)
# 我们注意到,每一次 React 都会重新渲染一下 App 组件,即 'ping' 一下处于 Pending 状态的组件, 检查一下是否‘就绪’(没有触发Suspense)
# 如果还触发 Suspense, 说明还要继续等待,这些重新渲染的结果不会被提交
App rendering with 2 2 false # ping, 这里count变成了2,且 pending 变成了 false
App rendering with 2 2 false # 但是 React 在内存中渲染它们,我们看不到
read 2
Tick rendering with 76 # Tick 重新渲染
Tick rendering with 76
Tick committed with 76 # 提交 Tick 更新,刷新到界面上
App rendering with 2 2 false # ping 还是没有就绪,继续 pending
App rendering with 2 2 false
read 2
Tick rendering with 77
Tick rendering with 77
Tick committed with 77
App rendering with 2 2 false # ping
App rendering with 2 2 false
read 2
Tick rendering with 78
Tick rendering with 78
Tick committed with 78
App rendering with 2 2 false # ping
App rendering with 2 2 false
read 2
# Ok, Promise 已经就绪了,这时候再一次重新渲染 App
# 这次没有触发 Suspense,React 会马上提交用户界面
App rendering with 2 2 false
App rendering with 2 2 false
read 2
App committed with 2 2 false
Из приведенного выше журнала мы можем четко понять поведение обновления компонента Pending.
4️⃣ Вложенная приостановка
На основе 3️⃣ переписать SuspenseBoundary на DoubleSuspenseBoundary, где Suspense будет вложен для загрузки более трудоемкого ресурса:
const DoubleSuspenseBoundary = ({ id }) => {
return (
<Suspense fallback={<div>Loading...</div>}>
{/* 需要加载 2s */}
<ComponentThatThrowPromise id={id} timeout={2000} />
<Suspense fallback={<div>Loading second...</div>}>
{/* 需要加载 4s */}
<ComponentThatThrowPromise id={id + "second"} timeout={4000} />
</Suspense>
</Suspense>
)
}
Проверьте эффект:
Во-первых, обратите внимание на первое крепление,Приостановка не запускает отложенную фиксацию при первом монтировании, поэтому мы сначала видимLoading...
, то первыйComponentThatThrowPromise
После загрузки отображатьComponentThatThrowPromise id: 0
иLoading second...
, и, наконец, полностью загружены.
Затем нажимаем кнопку, в это время DoubleSuspenseBoundary будет стоять на месте, ждем 5с (то есть второйComponentThatThrowPromise
загружен) перед отправкой.
Идеальный эффект такой же, как и у первого монтирования: переключиться, когда первый ComponentThatThrowPromise будет готов, не дожидаясь загрузки второго.
Чувствуете себя немного не так? Я долго думал об этом здесь, на официальном документеConcurrent UI Patterns (Experimental) - Wrap Lazy Features in <Suspense>сказал, второйComponentThatThrowPromise
уже вложены вSuspense
Теоретически он не должен блокировать коммиты.
Вернемся к первому предложению в начале: 'Приостановка не запускает отложенную фиксацию при первом монтировании'. Давайте попробуем еще раз, установим ключ на DoubleSuspenseBoundary, чтобы заставить его уничтожить и воссоздать:
export default function App() {
// .....
return (
<div className="App">
<h1>Hello useTransition {tick}</h1>
<div>
<button onClick={handleClick}>ADD + 1</button>
{pending && <span className="pending">pending</span>}
</div>
<Tick />
{/* ⚛️ 这里添加key,强制重新销毁创建 */}
<DoubleSuspenseBoundary id={count} key={count} />
</div>
)
}
Попробуйте эффект:
Мы обнаружили, что каждый щелчокLoading...
, состояние Pending пропало!Потому что каждый разcount
увеличение,DoubleSuspenseBoundary
будет воссоздан без запуска отложенной фиксации.
Основываясь на этом принципе, мы можем изменитьDoubleSuspenseBoundary
, на этот раз мы даем только вложенныеSuspense
Добавьте ключи, чтобы они воссоздали неблокирующее состояние ожидания.
const DoubleSuspenseBoundary = ({ id }) => {
return (
<Suspense fallback={<div>Loading...</div>}>
<ComponentThatThrowPromise id={id} timeout={2000} />
{/* ⚛️ 我们不希望这个 Suspense 阻塞 pending 状态, 给它加个key, 让它强制重新创建 */}
<Suspense key={id} fallback={<div>Loading second...</div>}>
<ComponentThatThrowPromise id={id + "second"} timeout={4000} />
</Suspense>
</Suspense>
);
};
окончательный эффект
Это работа!
5️⃣ Можно ли использовать с Mobx и Redux?
Я тоже не знаю, проверьте:
mport React, { useTransition, useEffect } from "react";
import { createStore } from "redux";
import { Provider, useSelector, useDispatch } from "react-redux";
import SuspenseBoundary from "./SuspenseBoundary";
import Tick from "./Tick";
const initialState = { count: 0, tick: 0 };
const ADD_TICK = "ADD_TICK";
const ADD_COUNT = "ADD_COUNT";
const store = createStore((state = initialState, action) => {
const copy = { ...state };
if (action.type === ADD_TICK) {
copy.tick++;
} else {
copy.count++;
}
return copy
});
export const Page = () => {
const { count, tick } = useSelector(({ tick, count }) => ({ tick, count }));
const dispatch = useDispatch();
const [startTransition, pending] = useTransition({ timeoutMs: 10000 });
const addTick = () => dispatch({ type: ADD_TICK });
const addCount = () => dispatch({ type: ADD_COUNT });
const handleClick = () => {
addTick();
startTransition(() => {
console.log("Start transition with count: ", count);
addCount();
console.log("End transition");
});
};
console.log(`App rendering with count(${count}) pendig(${pending})`);
useEffect(() => {
console.log("committed with", count, tick, pending);
});
return (
<div className="App">
<h1>Hello useTransition {tick}</h1>
<div>
<button onClick={handleClick}>ADD + 1</button>
{pending && <span className="pending">pending</span>}
</div>
<Tick />
<SuspenseBoundary id={count} />
</div>
);
};
export default () => {
return (
<Provider store={store}>
<Page />
</Provider>
);
};
Давайте посмотрим на эффект операции:
В чем проблема? Весь интерфейсPending
, весь интерфейс не простоApp
Это поддерево, и Тик тоже никуда не делся. Открываем консоль и видим предупреждение:
Warning: Page triggered a user-blocking update that suspended.
The fix is to split the update into multiple parts: a user-blocking update to provide immediate feedback, and another update that triggers the bulk of the changes.
Refer to the documentation for useTransition to learn how to implement this pattern.
Давайте посмотрим, как в настоящее время обновляются Hooks API Rudux и Mobx.По сути, все они используют механизм подписки для принудительного обновления после запуска события., основная структура выглядит следующим образом:
function useSomeOutsideStore() {
// 获取外部 store
const store = getOutsideStore()
const [, forceUpdate] = useReducer(s => s + 1, 0)
// ⚛️ 订阅外部数据源
useEffect(() => {
const disposer = store.subscribe(() => {
// ⚛️ 强制更新
forceUpdate()
))
return disposer
}, [store])
// ...
}
То есть мыstartTransition
Когда состояние Redux обновляется в , событие принимается синхронно, а затем вызовforceUpdate
.forceUpdate
Это состояние, которое фактически изменяется в контексте suspenseConfig..
Посмотрим еще раз на лог консоли:
Start transition with count 0
End transition
App rendering with count(1) pendig(true) # 这里出问题了 🔴, 你可以和实验 3️⃣ 中的日志对比一下
App rendering with count(1) pendig(true) # 实验 3️⃣ 中这里的 count 是 0,而这里的count是1,说明没有 defer!
read 1
Warning: App triggered a user-blocking update that suspended.
The fix is to split the update into multiple parts: a user-blocking update to provide immediate feedback, and another update that triggers the bulk of the changes.
Refer to the documentation for useTransition to learn how to implement this pattern.
Журнал может в основном определить проблему, счетчик не обновляется с задержкой, поэтому «синхронизация» запускает приостановку, что также является причиной предупреждения React. Поскольку useTransition все еще находится на экспериментальной стадии,Если приостановка не вызвана обновлением состояния в контексте startTransition , поведение по-прежнему не определено..
Но окончательное поведение немного метафизично, оно приведет к тому, что все приложение будет «ожидающим», и все обновления состояния не будут зафиксированы. Я тоже сильно запутался в этом, и сил нет вникать в это, остается только ждать последующее официальное обновление, а читатели тоже могут об этом подумать.
Следовательно, он временно не рекомендуется размещать состояния, которые запускают неизведенцию в Redux или MOBX.
Наконец, повторюсь,useTransition
входитьPending
Статус должен соответствовать следующим условиям:
- Для обновлений лучше использовать собственный механизм состояния React, такой как Hooks или setState, в настоящее время не используйте Mobx и Redux.
- Эти обновления вызывают приостановку.
- обновление должно быть в
startTransition
эти обновления будут связаны сsuspenseConfig
- Из повторных рендеров, вызванных этими обновлениями, по крайней мере один из них должен сработать.
Suspense
- это
Suspense
Не первое крепление
А как насчет использованияDeferedValue ?
Если вы понимаете вышеизложенное, тоuseDeferedValue
Это просто, это просто оболочка вокруг useTransition :
function useDeferredValue<T>(
value: T,
config: TimeoutConfig | void | null,
): T {
const [prevValue, setValue] = useState(value);
const [startTransition] = useTransition(config)
// ⚛️ useDeferredValue 只不过是监听 value 的变化,
// 然后在 startTransition 中更新它。从而实现延迟更新的效果
useEffect(
() => {
startTransition(() => {
setValue(value);
})
},
[value, config],
);
return prevValue;
}
useDeferredValue
Просто слушайте с useEffectvalue
изменения, затем обновите их в startTransition . Чтобы добиться эффекта отложенного обновления. Как упоминалось в эксперименте 1️⃣ выше, React понизит приоритет обновлений в startTransition, что означает, что они будут отложены, когда транзакции заняты.
Суммировать
В начале мы представили сценарий использования useTransition и позволили странице реализоватьPending
-> Skeleton
-> Complete
Путь обновления пользователя может оставаться на текущей странице при переключении страниц, чтобы страница оставалась отзывчивой. Это намного удобнее, чем показывать бесполезную пустую страницу или состояние загрузки.
Конечно, при вышеприведенных предположениях загрузка данных происходит очень медленно.Если загрузка данных происходит очень быстро, с помощью механизма useTransition мы можем запретить пользователю видеть состояние загрузки, что позволяет избежать дрожания страницы и мерцания, которое выглядит так: нет процесса загрузки.
Затем мы кратко познакомили с принципом работы и условиями использования Transition. Если обновление состояния в startTransition вызывает приостановку, соответствующий компонент перейдет в состояние ожидания. В состоянии Pending изменения, заданные в startTransition, будут отложены для фиксации. Состояние ожидания будет продолжаться до тех пор, пока приостановка не будет готова или не истечет время ожидания.
useTransition необходимо использовать в сочетании с Suspense для выполнения магии. Существует также вариант использования, когда мы можем размещать обновления с низким приоритетом в startTransition. Например, если обновление стоит дорого, вы можете поместить его в startTransition.Эти обновления уступят приоритетным задачам, а React задержит или объединит более сложное обновление, чтобы страница оставалась отзывчивой.
Ок, введение режима Concurrent подходит к концу, это информация из первых рук на китайском языке. Написание этих статей занимает большую часть моего свободного времени, если вам нравятся мои статьи, пожалуйста, дайте мне больше лайков и отзывов.