VK Mini Apps
VK Mini Apps ↗ — кроссплатформенные мини-приложения в экосистеме ВКонтакте ↗. VKUI изначально задумывался как набор компонентов для таких приложений. Со временем библиотека вышла за эти рамки, но по-прежнему отлично подходит для мини-приложений. Ниже — рекомендации по интеграции.
Конфигурация VKUI
Сначала устанавливаем библиотеки:
npm i --save @vkontakte/vk-bridge @vkontakte/vk-bridge-react
Далее подписываемся на изменения из VK Bridge и передаём их в ConfigProvider из VKUI. Используем готовые хуки из @vkontakte/vk-bridge-react ↗.
import * as React from 'react';
import { createRoot } from 'react-dom/client';
import vkBridge, { parseURLSearchParamsForGetLaunchParams } from '@vkontakte/vk-bridge';
import { useAppearance, useInsets, useAdaptivity } from '@vkontakte/vk-bridge-react';
import { Platform, ConfigProvider, AdaptivityProvider, AppRoot } from '@vkontakte/vkui';
import { transformVKBridgeAdaptivity } from './helpers/transformVKBridgeAdaptivity';
// Инициализируем VK Mini App
vkBridge.send('VKWebAppInit');
const App = () => {
const vkBridgeColorScheme = useAppearance() || undefined; // Вместо undefined можно задать значение по умолчанию
const vkBridgeInsets = useInsets() || undefined; // Вместо undefined можно задать значение по умолчанию
const vkBridgeAdaptivityProps = transformVKBridgeAdaptivity(useAdaptivity()); // Конвертируем значения из VK Bridge в параметры AdaptivityProvider
const { vk_platform } = parseURLSearchParamsForGetLaunchParams(window.location.search); // [опционально] Платформа может передаваться через URL (см. https://dev.vk.com/mini-apps/development/launch-params#vk_platform)
return (
<ConfigProvider
colorScheme={vkBridgeColorScheme}
platform={vk_platform === 'desktop_web' ? 'vkcom' : undefined}
isWebView={vkBridge.isWebView()}
hasCustomPanelHeaderAfter={true} // Резервируем правую часть PanelHeader под кнопки управления VK Mini Apps. Через параметр customPanelHeaderAfterMinWidth можно регулировать ширину этой области (по умолчанию, используется 90)
>
<AdaptivityProvider {...vkBridgeAdaptivityProps}>
{/* Для VK Mini Apps рекомендуем использовать mode="full" (выставлен по умолчанию, для примера указан явно) */}
<AppRoot mode="full" safeAreaInsets={vkBridgeInsets}>
{/* Ваше приложение */}
</AppRoot>
</AdaptivityProvider>
</ConfigProvider>
);
};
const container = document.getElementById('root');
const root = createRoot(container); // createRoot(container!), если используется TypeScript
root.render(<App />);import {
type AdaptivityProps,
getViewWidthByViewportWidth,
getViewHeightByViewportHeight,
ViewWidth,
} from '@vkontakte/vkui';
import type { UseAdaptivity } from '@vkontakte/vk-bridge-react';
/**
* Требуется конвертировать данные из VK Bridge в те, что принимает AdaptivityProvider из VKUI.
*/
export const transformVKBridgeAdaptivity = ({
type,
viewportWidth,
viewportHeight,
}: UseAdaptivity): AdaptivityProps => {
switch (type) {
case 'adaptive':
return {
viewWidth: getViewWidthByViewportWidth(viewportWidth),
viewHeight: getViewHeightByViewportHeight(viewportHeight),
};
case 'force_mobile':
case 'force_mobile_compact':
return {
viewWidth: ViewWidth.MOBILE,
density: type === 'force_mobile_compact' ? 'compact' : 'regular',
};
default:
return {};
}
};Навигация
View
На стартовой странице мини-приложения нужно включить свайпбек нативного клиента, чтобы пользователь мог выйти из мини-приложения. Для этого вызывайте определённые методы VK Bridge в зависимости от типа мини-приложения:
- В стандартном мини-приложении ВКонтакте при переходах отправляйте
VKWebAppSetSwipeSettings↗ сhistory: trueна первой панели иhistory: falseна остальных. - Во внутреннем мини-приложении ВКонтакте отправляйте
VKWebAppEnableSwipeBack↗ при переходе на первый экран иVKWebAppDisableSwipeBack↗ — на любой другой.
import vkBridge from '@vkontakte/vk-bridge';
const SomeViews = () => {
const [history, setHistory] = useState(['main']);
const activePanel = history[history.length - 1];
const isFirst = history.length === 1;
const go = React.useCallback((panel) => setHistory((prevHistory) => [...prevHistory, panel]), []);
const goBack = React.useCallback(() => setHistory((prevHistory) => prevHistory.slice(0, -1)), []);
const handleProfileClick = () => go('profile');
const handleMainClick = () => go('main');
React.useEffect(() => {
// Для стандартных мини-приложений делайте так:
vkBridge.send('VKWebAppSetSwipeSettings', { history: isFirst });
// Для внутренних мини-приложений делайте так:
vkBridge.send(isFirst ? 'VKWebAppEnableSwipeBack' : 'VKWebAppDisableSwipeBack');
}, [isFirst]);
return (
<View history={history} activePanel={activePanel} onSwipeBack={goBack}>
<Panel id="main">
<div onClick={handleProfileClick}>Main</div>
</Panel>
<Panel id="profile">
<div onClick={handleMainClick}>Profile</div>
</Panel>
</View>
);
};PanelHeader
Мини-приложению доступна почти вся площадь экрана, поэтому для корректной навигации используйте PanelHeader на каждом экране. Он должен содержать название приложения и значок Назад (см. PanelHeaderBack) там, где он нужен.
Виброотклик (Taptic Engine)
Для лучшего пользовательского опыта в VK Bridge есть события вызова вибрации на устройствах, которые её поддерживают.
@vkontakte/vk-bridge-react ↗ предоставляет функцию runTapticImpactOccurred, которая отправляет событие VKWebAppTapticImpactOccurred ↗ и заранее проверяет, что вибрация доступна на устройстве.
Виброотклик удобно использовать, например, в onRefresh у PullToRefresh.
import * as React from 'react';
import { runTapticImpactOccurred } from '@vkontakte/vk-bridge-react';
const Users = () => {
const [users, setUsers] = React.useState([
{ id: 1, name: 'Placeholder', avatarUrl: 'https://placehold.co/100' },
]);
const [fetching, setFetching] = React.useState(false);
const onRefresh = React.useCallback(() => {
setFetching(true);
// Вызываем виброотклик
runTapticImpactOccurred('light');
}, []);
return (
<View activePanel="users">
<Panel id="users">
<PanelHeader>Пользователи</PanelHeader>
<PullToRefresh onRefresh={onRefresh} isFetching={fetching}>
<Group>
<List>
{users.map(({ id, name, avatarUrl }, i) => {
return (
<Cell key={i} before={<Avatar src={avatarUrl} />}>
{name}
</Cell>
);
})}
</List>
</Group>
</PullToRefresh>
</Panel>
</View>
);
};