Skip to content

Интеграция с фреймворками

Интеграция с Vue 3

Vue composables включены в основной бандл и активируются, когда Vue установлен как peer-зависимость.

Регистрация плагина

ts
import { createApp } from 'vue'
import { ResponsivePlugin } from 'responsive-media'

const app = createApp(App)

app.use(ResponsivePlugin, {
  sm: [{ type: 'max-width', value: 767 }],
  lg: [{ type: 'min-width', value: 1024 }],
})

app.mount('#app')

useResponsive<T>(): объект реактивного состояния

Возвращает Vue-реактивное responsive-состояние. Реактивно в шаблонах и computed-свойствах.

vue
<script setup lang="ts">
import { useResponsive } from 'responsive-media'

type MyState = { sm: boolean; lg: boolean }
const state = useResponsive<MyState>()
</script>

<template>
  <MobileNav v-if="state.sm" />
  <DesktopNav v-else />
</template>

useBreakpoints(): упорядоченные хелперы

Возвращает реактивные упорядоченные хелперы брейкпоинтов. Все методы реагируют на изменения вьюпорта в шаблонах.

vue
<script setup>
import { useBreakpoints } from 'responsive-media'

const { current, isAbove, isBelow, between } = useBreakpoints()
</script>

<template>
  <span>Current: {{ current }}</span>
  <DesktopNav v-if="isAbove('sm')" />
  <MobileNav v-else />
  <TabletOnly v-if="between('sm', 'lg')" />
</template>

current — это ComputedRef<string | null>. isAbove, isBelow, between — обычные функции, реактивные благодаря тому, что читают из Vue-реактивного состояния.

useMediaQuery(query): одиночный сырой запрос

Возвращает Ref<boolean> для сырой строки CSS media query. Автоматически убирается при onUnmounted.

vue
<script setup>
import { useMediaQuery } from 'responsive-media'

const isDark = useMediaQuery('(prefers-color-scheme: dark)')
const canHover = useMediaQuery('(hover: hover)')
</script>

<template>
  <DarkTheme v-if="isDark" />
</template>

useContainerState(elementRef, config, options?): контейнерные запросы

Отслеживает размеры элемента и возвращает объект реактивного состояния. Автоматически настраивает и убирает ResizeObserver через watchEffect.

vue
<script setup>
import { useTemplateRef } from 'vue'
import { useContainerState } from 'responsive-media'

const cardRef = useTemplateRef('card')
const cardState = useContainerState(cardRef, {
  compact: [{ type: 'max-width', value: 300 }],
  wide: [{ type: 'min-width', value: 600 }],
})
</script>

<template>
  <div ref="card">
    <CompactLayout v-if="cardState.compact" />
    <WideLayout v-else-if="cardState.wide" />
    <DefaultLayout v-else />
  </div>
</template>

Интеграция с React 19+

Импортируйте из responsive-media/react.

ts
import {
  useResponsive,
  useBreakpoints,
  useMediaQuery,
  useContainerState,
} from 'responsive-media/react'

useResponsive<T>(): реактивное состояние

Возвращает текущее responsive-состояние. Ререндерится только при изменении состояния. Внутри использует useSyncExternalStore.

tsx
import { useResponsive } from 'responsive-media/react'

type MyState = { sm: boolean; lg: boolean }

function App() {
  const { sm, lg } = useResponsive<MyState>()
  return sm ? <MobileNav /> : <DesktopNav />
}

useBreakpoints(): упорядоченные хелперы

Возвращает упорядоченные хелперы брейкпоинтов. Ререндерится при изменении responsive-состояния.

tsx
import { useBreakpoints } from 'responsive-media/react'

function Nav() {
  const { current, isAbove, isBelow, between } = useBreakpoints()
  return (
    <>
      <span>Current: {current}</span>
      {isAbove('sm') ? <DesktopNav /> : <MobileNav />}
      {between('sm', 'lg') && <TabletBanner />}
    </>
  )
}

В отличие от Vue, current — обычная string | null (не ref). Ререндеры запускаются через useSyncExternalStore.

useMediaQuery(query): одиночный сырой запрос

Возвращает boolean, отслеживающий сырую строку CSS media query. SSR-безопасно (возвращает false на сервере).

tsx
import { useMediaQuery } from 'responsive-media/react'

function ThemeToggle() {
  const isDark = useMediaQuery('(prefers-color-scheme: dark)')
  const canHover = useMediaQuery('(hover: hover)')
  return <button className={isDark ? 'dark' : 'light'}>Toggle</button>
}

useContainerState(ref, config, options?): контейнерные запросы

Отслеживает размеры элемента и возвращает объект состояния. Настраивает и убирает ResizeObserver через useEffect.

tsx
import { useRef } from 'react'
import { useContainerState } from 'responsive-media/react'

function Card() {
  const ref = useRef<HTMLDivElement>(null)
  const { compact, wide } = useContainerState(ref, {
    compact: [{ type: 'max-width', value: 300 }],
    wide: [{ type: 'min-width', value: 600 }],
  })

  return (
    <div ref={ref}>{compact ? <CompactLayout /> : wide ? <WideLayout /> : <DefaultLayout />}</div>
  )
}

config и options считаются статичными после монтирования. Оборачивайте в useMemo, если они меняются.

Хелперы TypeScript

ConfigToState<T>

Выводит тип булева состояния из объекта конфига:

ts
import type { ConfigToState, MediaQueryConfig } from 'responsive-media'

const config = {
  sm: [{ type: 'min-width', value: 640 }],
  lg: [{ type: 'min-width', value: 1024 }],
} satisfies Record<string, MediaQueryConfig>

type MyState = ConfigToState<typeof config>
// → { sm: boolean; lg: boolean }

const { sm, lg } = responsiveState.getState<MyState>()

Обобщённый useResponsive<T>()

Оба адаптера, Vue и React, принимают обобщённый параметр типа для сужения возвращаемого состояния:

ts
type AppState = { mobile: boolean; tablet: boolean; desktop: boolean }
const state = useResponsive<AppState>()

SSR и гидратация

Все API SSR-безопасны — они проверяют доступность window, matchMedia и ResizeObserver перед использованием и откатываются на false на сервере.

СценарийПоведение
typeof window === 'undefined'Все слушатели пропускают настройку; proxy / getState() возвращают false для всех ключей
useCookie на сервере (Nuxt)Неприменимо — используйте useCookie из @vueuse/core или встроенные средства Nuxt
React SSRuseMediaQuery возвращает false; useResponsive возвращает серверный снапшот
Несовпадение при гидратацииВызовите hydrate() с серверным снапшотом перед первым рендером
ts
// Сервер: сериализуем ожидаемое начальное состояние
const initialState = { mobile: false, tablet: false, desktop: true }

// Клиент: гидратируем перед первым рендером
import { responsiveState } from 'responsive-media'
responsiveState.hydrate(initialState)