1. 当界面要学会"看人下菜碟"

身为前端开发者,你是不是经常被设计师的连环call搞到崩溃?"这个按钮在小屏要消失!"、"平板显示两列就好"、"PC端间距得放大",这些看似简单的要求背后,藏着现代Web开发的核心能力——响应式设计。

在React生态圈里,我们走过了传统的CSS媒体查询,经历了CSS-in-JS的奇妙旅程,如今终于迎来了useMediaQuery这个救世主。本文将带你体验这场响应式设计的进化之旅,用真实案例教你如何优雅地应对各种设备尺寸的挑战。

2. 响应式设计的必修基础课

2.1 媒体查询的老黄历

传统CSS媒体查询就像给不同尺寸的设备写情书,我们来看个经典案例:

/* 基础移动端样式 */
.container {
  padding: 10px;
}

/* 平板电脑的爱心便签 */
@media (min-width: 768px) {
  .container {
    padding: 20px;
  }
}

/* PC端的豪华套餐 */
@media (min-width: 1024px) {
  .container {
    padding: 30px;
    max-width: 1200px;
  }
}

这种方案简单直接,但遇到动态交互就变得笨手笨脚。比如我们想根据屏幕尺寸渲染不同组件时,CSS媒体查询就力不从心了。

2.2 CSS-in-JS的华丽变身

以Emotion为例,我们可以把媒体查询玩出花:

/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react'

const Box = () => (
  <div
    css={css`
      padding: 10px;
      @media (min-width: 768px) {
        padding: 20px;
      }
      @media (min-width: 1024px) {
        padding: 30px;
      }
    `}
  >
    自适应盒子
  </div>
)

这种写法虽然更灵活,但还是绕不开CSS原生媒体查询的局限——无法在JavaScript逻辑中实时响应屏幕变化。

3. useMediaQuery的智能管家模式

3.1 初识hook的魔力

让我们请出今天的主角——useMediaQuery。以最流行的react-responsive库为例:

import { useMediaQuery } from 'react-responsive'

const ResponsiveComponent = () => {
  const isMobile = useMediaQuery({ maxWidth: 767 })
  const isTablet = useMediaQuery({ minWidth: 768, maxWidth: 1023 })
  const isDesktop = useMediaQuery({ minWidth: 1024 })

  return (
    <div>
      {isMobile && <MobileMenu />}
      {isTablet && <TabletLayout />}
      {isDesktop && <DesktopDashboard />}
    </div>
  )
}

看到没?现在屏幕尺寸直接变成了可编程的JavaScript变量!这种声明式写法让组件逻辑清晰得像白开水。

3.2 高阶用法大揭秘

更复杂的场景怎么玩?来看个电商商品列表案例:

import { useMediaQuery } from 'react-responsive'

const ProductGrid = () => {
  // 精确匹配设备类型
  const isRetina = useMediaQuery({ 
    resolution: '2dppx' 
  })
  
  // 动态计算列数
  const columns = useMediaQuery({ minWidth: 1280 }) ? 4 
    : useMediaQuery({ minWidth: 768 }) ? 3 
    : 2

  // 横竖屏检测
  const isLandscape = useMediaQuery({ orientation: 'landscape' })

  return (
    <div className={`grid columns-${columns} ${isRetina ? 'retina' : ''}`}>
      {products.map(product => (
        <ProductCard 
          key={product.id} 
          compact={!isLandscape}
        />
      ))}
    </div>
  )
}

这种逻辑的魔法在于:当屏幕尺寸变化时,React会自动触发重新渲染,你甚至不需要手动监听resize事件!

4. 关键技术大起底

4.1 服务端渲染的暗礁

使用useMediaQuery时要注意服务端和客户端的渲染差异。试看这个错误示范:

const BuggyComponent = () => {
  const isMobile = useMediaQuery({ maxWidth: 767 })
  // 服务端渲染时isMobile默认是false
  return <div>{isMobile ? 'Mobile' : 'Desktop'}</div>
}

正确姿势应该加上默认值:

const SafeComponent = () => {
  const isMobile = useMediaQuery(
    { maxWidth: 767 },
    { deviceWidth: 1280 } // 服务端默认桌面尺寸
  )
  return <div>{isMobile ? 'Mobile' : 'Desktop'}</div>
}

4.2 性能优化的独孤九剑

多个媒体查询使用时要注意渲染优化:

const OptimizedComponent = () => {
  // 使用同一个媒体查询实例
  const isDesktop = useMediaQuery({ minWidth: 1024 })
  
  // Bad: 每个条件单独查询
  // const showSidebar = useMediaQuery({ minWidth: 768 })
  // const showBanner = useMediaQuery({ minWidth: 768 })

  // Good: 派生状态
  const showSidebar = isDesktop
  const showBanner = isDesktop

  return (
    <>
      {showSidebar && <Sidebar />}
      {showBanner && <PromotionBanner />}
    </>
  )
}

5. 技术选型的大比武

5.1 适用场景大全

  • 媒体查询:简单样式调整、基础布局变更
  • CSS-in-JS:需要动态样式的复杂组件
  • useMediaQuery:涉及渲染逻辑、条件加载、数据过滤等场景

5.2 方案优缺点PK表

方案 优点 缺点
CSS媒体查询 原生支持、性能优异 逻辑表达能力弱
CSS-in-JS 组件化样式、动态能力强 学习成本高
useMediaQuery 完美融入React生命周期 需处理SSR问题

6. 避坑指南红宝书

  1. Zombie组件问题:在页面跳转时注意清理事件监听
  2. 查询条件冲突:避免出现minWidth: 768maxWidth: 768同时存在
  3. 批量更新策略:多个状态变更合并处理
  4. TypeScript类型:自定义hook时要完善类型定义
  5. 移动优先原则:始终先写移动端样式再扩展

7. 未来趋势望远镜

新一代响应式API正在崛起,比如Container Queries终于能让组件自主响应容器尺寸。结合useMediaQuery可以创造更智能的布局系统:

const FutureComponent = () => {
  const containerRef = useRef(null)
  const isWideContainer = useContainerQuery(containerRef, { minWidth: 600 })

  return (
    <div ref={containerRef}>
      {isWideContainer ? <WideLayout /> : <CompactLayout />}
    </div>
  )
}

8. 总结:响应式设计的新纪元

从媒体查询到useMediaQuery的进化,反映了前端开发从"样式驱动"到"逻辑驱动"的转变。新的hook方案不仅保留了媒体查询的核心能力,更重要的是打开了状态驱动的响应式编程新世界。

但记住,技术选择永远要看具体场景。下次当设计师要求"这个列表在小屏显示卡片,大屏显示表格"时,你大可以微微一笑:"用useMediaQuery三分钟搞定!"