Skip to content

useResponsive

作用

获取响应式信息

原理

是监听 resize 方法,判断与配置的每一种宽度,大于则为 true,否则为 false

源码

ts
import { useEffect, useState } from 'react'
import isBrowser from '../utils/isBrowser'

type Subscriber = () => void

// 订阅者集合
const subscribers = new Set<Subscriber>()

type ResponsiveConfig = Record<string, number>
type ResponsiveInfo = Record<string, boolean>

let info: ResponsiveInfo

// 默认配置
let responsiveConfig: ResponsiveConfig = {
  xs: 0,
  sm: 576,
  md: 768,
  lg: 992,
  xl: 1200,
}

// resize 事件回调
function handleResize() {
  const oldInfo = info
  calculate()
  if (oldInfo === info) return
  for (const subscriber of subscribers) {
    subscriber()
  }
}

// 是否监听 resize 事件的标识
let listening = false

// 计算当前响应式信息
function calculate() {
  const width = window.innerWidth
  const newInfo = {} as ResponsiveInfo
  let shouldUpdate = false
  for (const key of Object.keys(responsiveConfig)) {
    newInfo[key] = width >= responsiveConfig[key]
    if (newInfo[key] !== info[key]) {
      shouldUpdate = true
    }
  }
  if (shouldUpdate) {
    info = newInfo
  }
}

// 设置配置的方法
export function configResponsive(config: ResponsiveConfig) {
  responsiveConfig = config
  if (info) calculate()
}

export function useResponsive() {
  // 判断是否为浏览器环境,并且没有监听 resize 事件
  if (isBrowser && !listening) {
    info = {}
    calculate()
    // 监听 resize 事件
    window.addEventListener('resize', handleResize)
    // 标识已经监听
    listening = true
  }
  const [state, setState] = useState<ResponsiveInfo>(info)

  useEffect(() => {
    // 如果不是浏览器环境,则直接返回
    if (!isBrowser) return

    // 如果没有监听 resize 事件,则添加监听
    // In React 18's StrictMode, useEffect perform twice, resize listener is remove, so handleResize is never perform.
    // https://github.com/alibaba/hooks/issues/1910
    if (!listening) {
      window.addEventListener('resize', handleResize)
    }

    const subscriber = () => {
      setState(info)
    }

    // 添加订阅者
    subscribers.add(subscriber)

    return () => {
      // 移除订阅者
      subscribers.delete(subscriber)
      // 如果订阅者为空,则移除 resize 事件监听
      if (subscribers.size === 0) {
        window.removeEventListener('resize', handleResize)
        // 标识重置为false,表示没有监听
        listening = false
      }
    }
  }, [])

  return state
}

如有转载或 CV 的请标注本站原文地址