Перейти к содержимому

Миграция с v7 на v8

Чтобы 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 из ModalRoot.


Удалено свойство baseClassName у компонента VisuallyHidden. Используйте стандартное свойство className.


  • Свойство 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>

Свойство onClose переименовано в onClosed.

Миграция
<Alert
  title="Подтвердите действие"
  description="Вы уверены?"
- onClose={() => setPopout(null)}
+ onClosed={() => setPopout(null)}
/>

Свойство onClose переименовано в onClosed.

Миграция
<Snackbar
- onClose={() => setSnackbar(null)}
+ onClosed={() => setSnackbar(null)}
>
  Сообщение
</Snackbar>

Свойство fixed удалено. Вместо него используйте свойство strategy.

Миграция
<PopoutWrapper
- fixed
/>
 
<PopoutWrapper
- fixed={true}
/>
 
<PopoutWrapper
- fixed={false}
+ strategy="none"
/>

Внутренние отступы синхронизированы с другими элементами форм. Компонент стал компактнее.


Изменена структура компонента и уменьшена минимальная высота.

Структурные изменения:

  • Свойство after теперь всегда рендерится справа от компонента (вне основного контента), независимо от значения afterAlign.
  • Добавлены новые свойства meta и submeta для отображения текста справа от основного контента внутри блока.
  • Если вы использовали after с afterAlign="start", замените его на meta и submeta.

Изменение минимальной высоты:

Режимv7v8
regular64px48px
compact60px44px
Миграция

Если вы использовали 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>

Свойство imageTheme теперь по умолчанию имеет значение "auto" вместо "dark". При "auto" тема изображения определяется автоматически по цветовой схеме приложения.

Миграция

Если вам нужно сохранить прежнее поведение, укажите imageTheme="dark":

<Banner
  mode="image"
+ imageTheme="dark"
  background={<div style={{ backgroundImage: 'url(...)' }} />}
>
  Контент
</Banner>

Изменены цвета при 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

Свойство accessible теперь по умолчанию имеет значение true.


Изменён тип свойства onChange:

- onChange?: (value?: Date) => void
+ onChange?: (value: Date) => void

Проверка значения на undefined больше не требуется.

Миграция
<Calendar
- onChange={(value: Date | undefined) => {
-   if (value) {
-     setValue(value);
-   }
- }}
+ onChange={(value: Date) => setValue(value)}
/>

Изменён тип свойства onChange:

- onChange?: (value?: Date) => void
+ onChange?: (value: Date | null) => void

Значение null означает, что поле очищено. undefined не используется — оно зарезервировано для неконтролируемого состояния компонента.


В ряде компонентов изменилось поведение передачи дополнительных свойств (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 и другие слоты для работы с внутренними элементами.

Свойства getRef, data-* и aria-* атрибуты теперь нужно передавать через slotProps.input.

Миграция
<Input
- getRef={inputRef}
- data-testid="my-input"
- aria-label="Введите текст"
+ slotProps={{
+   input: {
+     getRootRef: inputRef,
+     "data-testid": "my-input",
+     "aria-label": "Введите текст"
+   }
+ }}
/>

Свойства getRef, data-* и aria-* атрибуты теперь нужно передавать через slotProps.textarea.

Миграция
<Textarea
- getRef={textareaRef}
- data-testid="my-textarea"
+ slotProps={{
+   textarea: {
+     getRootRef: textareaRef,
+     "data-testid": "my-textarea"
+   }
+ }}
/>

Свойства 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>

Свойства getRef, data-* и aria-* атрибуты теперь нужно передавать через slotProps.input.

Миграция
<File
- getRef={fileRef}
- data-testid="file-input"
+ slotProps={{
+   input: {
+     getRootRef: fileRef,
+     "data-testid": "file-input"
+   }
+ }}
>
  Выбрать файл
</File>

Свойства restProps теперь применяются к корневому элементу, а не к внутреннему input. Для передачи свойств во внутренний input используйте slotProps.input.

Миграция
<CustomSelect
- data-testid="select"
- aria-label="Выберите значение"
+ slotProps={{
+   input: {
+     "data-testid": "select",
+     "aria-label": "Выберите значение"
+   }
+ }}
  options={options}
  value={value}
  onChange={onChange}
/>

Свойства для внутреннего 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}
/>

Свойства 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 помечены как устаревшие и будут удалены в будущих версиях.

Свойства 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 помечен как @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 помечен как @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 помечен как @deprecated. Используйте стандартные CSS-свойства или свойства компонента Box для управления дочерними элементами.


Свойство afterCaption помечено как @deprecated. Используйте вместо него свойство submeta.

Миграция
<RichCell
- afterCaption="Подтекст"
+ submeta="Подтекст"
>
  Контент
</RichCell>

Свойства 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>

Свойство accessible помечено как @deprecated, так как теперь оно имеет значение true по умолчанию.