Миграция с v7 на v8
🤖 Промпты для миграции через MCP
Чтобы AI не пропускал компоненты и правильно сопоставлял примеры с кодом, добавьте в системный промпт или задание пользователя следующие формулировки.
Системный промпт (общая стратегия)
Скопируйте в системный промпт или в инструкции для ассистента:
При миграции кода на VKUI v8:
1. **Сначала** вызови list_migration_targets и получи полный список целей миграции.
2. **Для каждой цели** из списка проверь: встречается ли в коде пользователя этот компонент/хук или связанные с ним паттерны (имена компонентов, пропсы из примеров).
3. **Для каждой найденной цели** вызови get_migration_target с точным именем из списка (например Alert, ActionSheet, Snackbar — регистр и формулировка как в списке).
4. **Применяй замены по смыслу**: в ответе приходит before и after. Твоя задача — найти в коде пользователя фрагменты, которые соответствуют сути примера (тот же компонент, те же пропсы/паттерны), и заменить их на вариант из after. Не ищи точное совпадение строки — ищи логическое соответствие (например, везде, где у Alert, Snackbar или ActionSheet есть onClose, заменить на onClosed).
5. **Не пропускай** компоненты только потому, что пример в before выглядит иначе (другая переменная, другой колбэк). Если суть совпадает — примени миграцию.Задание пользователя (краткое)
Вариант формулировки запроса к ассистенту:
Проверь этот код на миграцию VKUI v8. Действуй так: вызови list_migration_targets, затем для каждого компонента из списка, который есть в моём коде, вызови get_migration_target и примени все рекомендации (замени фрагменты по полям before → after). Не пропускай компоненты: сверяй по именам и пропсам, а не по точному тексту примера.Задание для одного файла
Более детальный запрос с акцентом на конкретные компоненты:
В этом файле найди все использования компонентов и хуков из списка миграции v8 (получи список через list_migration_targets). Для каждого такого использования запроси get_migration_target и примени замены: сопоставь код с примером «до» по смыслу (компонент, пропсы) и замени на вариант «после». Особенно проверь: Alert, Snackbar, ActionSheet (onClose → onClosed), Input, Search, Textarea, Checkbox, Switch, Radio (getRef → slotProps), ModalRoot, PopoutWrapper, RichCell, DateInput и остальные из списка.Напоминание про сопоставление
Если ассистент слишком строго сравнивает код с примером, добавьте в контекст:
Примеры миграции показывают типичный код «до» и «после». В реальном коде может быть другая переменная (например onClose={handleClose} вместо onClose={() => setPopout(null)}), другой формат — это нормально. Заменяй по смыслу: тот же проп, тот же компонент → применить изменение из after (например везде onClose → onClosed у Alert/Snackbar/ActionSheet).🌐 Поддерживаемые браузеры
Изменения минимальных версий браузеров:
// .browserslistrc
- Chrome >= 63
+ Chrome >= 84
- Safari >= 12
+ Safari >= 15
- Firefox >= 55
+ Firefox >= 115
- Edge >= 79
+ Edge >= 109
- Opera >= 50
+ Opera >= 90
- iOS >= 12
+ iOS >= 15
- ChromeAndroid >= 63
+ ChromeAndroid >= 84
- Samsung >= 8.2
+ Samsung >= 14
+ FirefoxAndroid >= 115
+ Android >= 84🗑️ Удалённые типы и функции
withModalRootContext
Удалена функция withModalRootContext из ModalRoot.
VisuallyHidden: baseClassName
Удалено свойство baseClassName у компонента VisuallyHidden. Используйте стандартное свойство className.
🧩 Компоненты
ActionSheet
- Свойство
onCloseпереименовано вonClosed. - Добавлена поддержка
slotProps.iosCloseItemдля упрощения локализации кнопки “Отмена” на iOS.
Кнопка отмены на iOS рендерится отдельно от списка действий (вне ActionSheetItem). Теперь свойства можно передавать напрямую в ActionSheetDefaultIosCloseItem через slotProps.iosCloseItem без обёртки.
Миграция
<ActionSheet
- onClose={() => {}}
+ onClosed={() => {}}
title="Заголовок"
>
<ActionSheetItem>Действие</ActionSheetItem>
</ActionSheet>Пример использования slotProps.iosCloseItem для локализации:
<ActionSheet
onClosed={() => setPopout(null)}
slotProps={{
iosCloseItem: {
children: i18n("cancel"),
},
}}
>
<ActionSheetItem onClick={handleAction}>Действие</ActionSheetItem>
</ActionSheet>Alert
Свойство onClose переименовано в onClosed.
Миграция
<Alert
title="Подтвердите действие"
description="Вы уверены?"
- onClose={() => setPopout(null)}
+ onClosed={() => setPopout(null)}
/>Snackbar
Свойство onClose переименовано в onClosed.
Миграция
<Snackbar
- onClose={() => setSnackbar(null)}
+ onClosed={() => setSnackbar(null)}
>
Сообщение
</Snackbar>PopoutWrapper
Свойство fixed удалено. Вместо него используйте свойство strategy.
Миграция
<PopoutWrapper
- fixed
/>
<PopoutWrapper
- fixed={true}
/>
<PopoutWrapper
- fixed={false}
+ strategy="none"
/>Textarea
Внутренние отступы синхронизированы с другими элементами форм. Компонент стал компактнее.
RichCell
Изменена структура компонента и уменьшена минимальная высота.
Структурные изменения:
- Свойство
afterтеперь всегда рендерится справа от компонента (вне основного контента), независимо от значенияafterAlign. - Добавлены новые свойства
metaиsubmetaдля отображения текста справа от основного контента внутри блока. - Если вы использовали
afterсafterAlign="start", замените его наmetaиsubmeta.
Изменение минимальной высоты:
| Режим | v7 | v8 |
|---|---|---|
| regular | 64px | 48px |
| compact | 60px | 44px |
Миграция
Если вы использовали after с afterAlign="start" (или не указывали afterAlign), замените на meta и submeta:
<RichCell
before={<Avatar size={48} />}
overTitle="онлайн"
subtitle="Санкт-Петербург"
- after="Текст"
- afterCaption="Подтекст"
- afterAlign="start"
+ meta="Текст"
+ submeta="Подтекст"
>
Имя пользователя
</RichCell>Если вы использовали after с afterAlign="center" или afterAlign="end", поведение остаётся прежним:
<RichCell
before={<Avatar size={48} />}
subtitle="Санкт-Петербург"
after={<Icon28Like />}
afterAlign="center"
>
Имя пользователя
</RichCell>Banner
Свойство imageTheme теперь по умолчанию имеет значение "auto" вместо "dark". При "auto" тема изображения определяется автоматически по цветовой схеме приложения.
Миграция
Если вам нужно сохранить прежнее поведение, укажите imageTheme="dark":
<Banner
mode="image"
+ imageTheme="dark"
background={<div style={{ backgroundImage: 'url(...)' }} />}
>
Контент
</Banner>ContentBadge
Изменены цвета при appearance="neutral":
mode=“primary”:
- Было: текст
--color_text_primary, фон--color_background_secondary - Стало: текст
--color_text_contrast, фон--color_icon_secondary
mode=“secondary”:
- Было: текст
--color_text_subhead, фон--color_background_secondary - Стало: текст
--color_text_primary, фон--color_background_secondary_alpha
DateInput, DateRangeInput, CustomSelect
Свойство accessible теперь по умолчанию имеет значение true.
Calendar, CalendarRange
Изменён тип свойства onChange:
- onChange?: (value?: Date) => void
+ onChange?: (value: Date) => voidПроверка значения на undefined больше не требуется.
Миграция
<Calendar
- onChange={(value: Date | undefined) => {
- if (value) {
- setValue(value);
- }
- }}
+ onChange={(value: Date) => setValue(value)}
/>DateInput, DateRangeInput
Изменён тип свойства onChange:
- onChange?: (value?: Date) => void
+ onChange?: (value: Date | null) => voidЗначение null означает, что поле очищено. undefined не используется — оно зарезервировано для неконтролируемого состояния компонента.
📦 Изменение передачи свойств (restProps)
В ряде компонентов изменилось поведение передачи дополнительных свойств (restProps). Теперь restProps применяются к корневому элементу, а для внутренних элементов используется slotProps.
Изменения в обработке событий
Раньше onClick и другие события обрабатывались на внутренних элементах — event.currentTarget указывал на них. В v8 обработчики из restProps применяются к корневому элементу, а события всплывают от внутренних.
Это влияет и на типизацию колбэков в TypeScript: тип события выводится из элемента, на который навешан обработчик. Например, если onClick вешается на корневой <label>, тип будет React.MouseEvent<HTMLLabelElement>, а не React.MouseEvent<HTMLInputElement>, как раньше.
Чтобы сохранить прежнее поведение и ожидаемые типы, перенесите обработчики в соответствующий slotProps:
// Обработчик вешается на корневой элемент
<Switch onClick={(event) => {
// event.currentTarget — корневой <label>
}} />
// Обработчик вешается на скрытый <input>
<Switch
slotProps={{
input: {
onClick: (event) => {
// event.currentTarget — внутренний <input>, как в v7
},
},
}}
/>Аналогично для других компонентов: используйте slotProps.input, slotProps.textarea, slotProps.content и другие слоты для работы с внутренними элементами.
Input, Search
Свойства getRef, data-* и aria-* атрибуты теперь нужно передавать через slotProps.input.
Миграция
<Input
- getRef={inputRef}
- data-testid="my-input"
- aria-label="Введите текст"
+ slotProps={{
+ input: {
+ getRootRef: inputRef,
+ "data-testid": "my-input",
+ "aria-label": "Введите текст"
+ }
+ }}
/>Textarea, WriteBar
Свойства getRef, data-* и aria-* атрибуты теперь нужно передавать через slotProps.textarea.
Миграция
<Textarea
- getRef={textareaRef}
- data-testid="my-textarea"
+ slotProps={{
+ textarea: {
+ getRootRef: textareaRef,
+ "data-testid": "my-textarea"
+ }
+ }}
/>Switch, Checkbox, Radio
Свойства getRef, data-*, aria-* атрибуты и специфичные для input свойства
(кроме checked, disabled, readOnly, required, name, value) теперь нужно
передавать через slotProps.input.
Миграция
<Checkbox
- getRef={checkboxRef}
- data-testid="my-checkbox"
- aria-describedby="hint"
+ slotProps={{
+ input: {
+ getRootRef: checkboxRef,
+ "data-testid": "my-checkbox",
+ "aria-describedby": "hint"
+ }
+ }}
>
Согласен
</Checkbox>
<Switch
- getRef={switchRef}
- data-testid="my-switch"
+ slotProps={{
+ input: {
+ getRootRef: switchRef,
+ "data-testid": "my-switch"
+ }
+ }}
/>
<Radio
- getRef={radioRef}
+ slotProps={{
+ input: {
+ getRootRef: radioRef
+ }
+ }}
>
Вариант
</Radio>File
Свойства getRef, data-* и aria-* атрибуты теперь нужно передавать через slotProps.input.
Миграция
<File
- getRef={fileRef}
- data-testid="file-input"
+ slotProps={{
+ input: {
+ getRootRef: fileRef,
+ "data-testid": "file-input"
+ }
+ }}
>
Выбрать файл
</File>CustomSelect, NativeSelect
Свойства restProps теперь применяются к корневому элементу, а не к внутреннему input.
Для передачи свойств во внутренний input используйте slotProps.input.
Миграция
<CustomSelect
- data-testid="select"
- aria-label="Выберите значение"
+ slotProps={{
+ input: {
+ "data-testid": "select",
+ "aria-label": "Выберите значение"
+ }
+ }}
options={options}
value={value}
onChange={onChange}
/>ChipsSelect, ChipsInput
Свойства для внутреннего input теперь передаются через slotProps.input.
Миграция
<ChipsSelect
- getRef={inputRef}
- data-testid="chips-select"
- aria-describedby="hint"
+ slotProps={{
+ input: {
+ getRootRef: inputRef,
+ "data-testid": "chips-select",
+ "aria-describedby": "hint"
+ }
+ }}
value={value}
onChange={onChange}
/>SplitLayout
Свойства restProps теперь применяются к корневому элементу, а не к внутреннему контейнеру content.
Для передачи свойств в контейнер используйте slotProps.content.
Миграция
<SplitLayout
- className="my-layout"
- data-testid="layout"
+ slotProps={{
+ content: {
+ className: "my-layout",
+ "data-testid": "layout"
+ }
+ }}
header={<PanelHeader />}
>
<SplitCol>Контент</SplitCol>
</SplitLayout>⚠️ Устаревшие API (deprecated)
Следующие API помечены как устаревшие и будут удалены в будущих версиях.
AdaptivityProvider
Свойства sizeX и sizeY теперь @deprecated: используйте density вместо sizeY, viewWidth={ViewWidth.MOBILE} вместо sizeX="compact" и viewWidth={ViewWidth.SMALL_TABLET} вместо sizeX="regular".
Пример миграции
- <AdaptivityProvider sizeX="compact">
+ <AdaptivityProvider viewWidth={ViewWidth.MOBILE}>
- <AdaptivityProvider sizeX="regular">
+ <AdaptivityProvider viewWidth={ViewWidth.SMALL_TABLET}>
- <AdaptivityProvider sizeY="compact">
+ <AdaptivityProvider density="compact">
- <AdaptivityProvider sizeY="regular">
+ <AdaptivityProvider density="regular">
- <AdaptivityProvider sizeX="compact" sizeY="compact">
+ <AdaptivityProvider viewWidth={ViewWidth.MOBILE} density="compact">
- <AdaptivityProvider sizeX="regular" sizeY="regular">
+ <AdaptivityProvider viewWidth={ViewWidth.SMALL_TABLET} density="regular">
- <AppRootProvider sizeX="regular" sizeY="compact">
+ <AppRootProvider viewWidth={ViewWidth.SMALL_TABLET} density="compact">
- <AppRootProvider sizeX="compact" sizeY="regular">
+ <AppRootProvider viewWidth={ViewWidth.MOBILE} density="regular">ModalRoot
Компонент ModalRoot помечен как @deprecated. Для императивного управления модальными окнами используйте хук useModalManager.
Миграция
- const [activeModal, setActiveModal] = useState(null);
-
- <ModalRoot activeModal={activeModal} onClose={() => setActiveModal(null)}>
- <ModalPage id="modal" onClose={() => setActiveModal(null)}>
- Контент
- </ModalPage>
- </ModalRoot>
+ const [modalApi, modalHolder] = useModalManager();
+
+ // Открытие модального окна
+ modalApi.openModalPage({
+ children: <div>Контент</div>,
+ });
+
+ // В JSX
+ {modalHolder}FixedLayout
Компонент FixedLayout помечен как @deprecated и будет удалён в v10. Используйте Box со свойствами position="sticky" и insetBlockStart/insetBlockEnd.
Миграция
- <FixedLayout vertical="top">
+ <Box position="sticky" insetBlockStart={0}>
Content
- </FixedLayout>
+ </Box>
- <FixedLayout vertical="bottom">
+ <Box position="sticky" insetBlockEnd={0}>
Content
- </FixedLayout>
+ </Box>Flex.Item
Подкомпонент Flex.Item помечен как @deprecated. Используйте стандартные CSS-свойства или свойства компонента Box для управления дочерними элементами.
RichCell: afterCaption
Свойство afterCaption помечено как @deprecated. Используйте вместо него свойство submeta.
Миграция
<RichCell
- afterCaption="Подтекст"
+ submeta="Подтекст"
>
Контент
</RichCell>ActionSheetItem: mode=“cancel” и isCancelItem
Свойства mode="cancel" и isCancelItem отмечены как устаревшие.
Вместо ActionSheetItem с mode="cancel" или isCancelItem в качестве iosCloseItem используйте:
slotProps.iosCloseItemвActionSheetдля передачи свойств в кнопку отмены;- или свойство
iosCloseItemс компонентомActionSheetDefaultIosCloseItemдля полной кастомизации.
Миграция
<ActionSheet
onClosed={() => setPopout(null)}
title="Выберите действие"
- iosCloseItem={
- <ActionSheetItem mode="cancel" onClick={() => setPopout(null)}>
- Отмена
- </ActionSheetItem>
- }
+ slotProps={{
+ iosCloseItem: {
+ children: "Отмена",
+ onClick: () => setPopout(null)
+ }
+ }}
+>
+ <ActionSheetItem onClick={handleAction}>Действие</ActionSheetItem>
</ActionSheet>Альтернативный вариант с использованием ActionSheetDefaultIosCloseItem напрямую:
<ActionSheet
onClosed={() => setPopout(null)}
title="Выберите действие"
- iosCloseItem={
- <ActionSheetItem mode="cancel" onClick={() => setPopout(null)}>
- Отмена
- </ActionSheetItem>
- }
+ iosCloseItem={
+ <ActionSheetDefaultIosCloseItem onClick={() => setPopout(null)}>
+ Отмена
+ </ActionSheetDefaultIosCloseItem>
+ }
>
<ActionSheetItem onClick={handleAction}>Действие</ActionSheetItem>
</ActionSheet>DateInput, DateRangeInput, CustomSelect: accessible
Свойство accessible помечено как @deprecated, так как теперь оно имеет значение true по умолчанию.