0

Tailwind UI的免费堆叠应用程序外壳有一个移动菜单,用户可以在小屏幕上显示:

    <!--
      Mobile menu, toggle classes based on menu state.

      Open: "block", closed: "hidden"
    -->
    <div class="hidden md:hidden">
       // mobile menu content
    </div>

在 React 中,我使用showMobileMenu状态变量实现了这一点:

    <div className={`${showMobileMenu ? 'block' : 'hidden'} md:hidden`}>
        // mobile menu content
    </div>

如果用户调整窗口大小(在菜单可见时),它会在 处消失medium并在重新调整回small. 在这种情况下,我希望在调整回small.

实现这一目标的最佳方法是什么?

我目前的解决方案是监听window resize事件并showMobileMenu=false随时设置window.innerWidth >= 768

我有点厌倦了由 JS 和 CSS 管理的响应行为,但也许这是典型的。只是想知道 React Tailwind 开发人员是否会以不同的方式处理这个问题。

4

1 回答 1

0

几天后,我采用了以下方法,以声明式和命令式的方式从 JS 处理断点更改。

基于react-responsive包,我实现了一个小型库,它提供断点上下文和钩子(以声明方式处理当前断点)并发布断点更改通知(以命令式处理断点更改)。

如果您有改进的建议,请告诉我。

//
// breakpoints.tsx
//
import { default as React, ReactNode } from 'react'
import { useMediaQuery } from 'react-responsive'

export enum Breakpoint {
  XS = 0,
  SM = 640,
  MD = 768,
  LG = 1024,
  XL = 1280,
  XXL = 1536,
}

const allBreakpoints = Object.values(Breakpoint)
  .filter((v) => typeof v === 'number')
  .sort((a, b) => (a as number) - (b as number)) as Breakpoint[]

function activeBreakpoint(matches: boolean[]): Breakpoint {
  const index = matches.lastIndexOf(true)
  return allBreakpoints[index]
}

export type BreakpointEvent = CustomEvent<{ breakpoint: Breakpoint }>

function postBreakpointChangeEvent(breakpoint: Breakpoint): void {
  window.dispatchEvent(
    new CustomEvent('breakpointChange', {
      detail: {
        breakpoint,
      },
    })
  )
}

const breakpointContext = React.createContext<Breakpoint>(Breakpoint.XS)

export function BreakpointProvider({ children }: { children: ReactNode }): JSX.Element {
  const matches = allBreakpoints.map((bp: number, index: number) => {
    // eslint-disable-next-line react-hooks/rules-of-hooks
    return useMediaQuery({ query: `(min-width: ${bp}px)` }, undefined, (macthes: boolean) => {
      const breakpoint = activeBreakpoint(matches)
      postBreakpointChangeEvent(breakpoint)
    })
  })
  const breakpoint = activeBreakpoint(matches)
  return <breakpointContext.Provider value={breakpoint}>{children}</breakpointContext.Provider>
}

export function useBreakpoint(): Breakpoint {
  return React.useContext(breakpointContext)
}
//
// index.tsx
//
ReactDOM.render(
  <React.StrictMode>
    <BreakpointProvider>
        <App />
    </BreakpointProvider>
  </React.StrictMode>,
  document.getElementById('root')
)
//
// handle breakpoint changes imperatively
//
  const handleBreakpointChange = (evt: Event) => {
    if ((evt as BreakpointEvent).detail.breakpoint >= Breakpoint.MD) {
      setMenuShowing(false)
    }
  }
  React.useEffect(() => {
    window.addEventListener('breakpointChange', handleBreakpointChange)
    return () => window.removeEventListener('breakpointChange', handleBreakpointChange)
  }, [])
//
// handle breakpoint changes declaratively. 
// re-renders each time the breakpoint changes
//
  const breakpoint = useBreakpoint()
  if (breakpoint >= Breakpoint.MD) {
     // render for medium and larger screens
  } else {
     // render for less than smaller screeens
  }
于 2021-02-03T16:43:44.317 回答