LoginSignup
1

More than 3 years have passed since last update.

posted at

updated at

Organization

Drawer Menu

定番の Drawer Menu です。Menu は将来的に拡張される代表的なものなので、拡張性を初めから考慮します。今回は Custom Hooks を2つ定義しています。小項目を格納し開閉する「useDrillDown」と、Menu 全体の開閉を扱う「useDrawerMenu」です。
code: github / $ yarn 1206

1206.jpg 1206-1.jpg

useDrillDown

小項目を格納し開閉する Custom Hooks です。まずは、ここで利用されている Hooks API を見てみます。

components/aside/menu/list/drillDown/useDrillDown.ts
const useDrillDown = (props: Props) => {
  const [state, update] = useState(...)
  const options = useMemo(...)
  const duration = useMemo(...)
  const transitionDuration = useMemo(...)
  const style = useMemo(...)
  const handleOpen = useCallback(...)
  useEffect(...) // 要素高さ取得 effect
  return {
    style,
    handleOpen
  }
}

image.png

格納される項目は数も高さもバラバラだったりすることが常です。高さを切り替える開閉アニメーションの為に、次のことをしています。

  • まずはマウント時に内包要素を出しっ放しに
  • この瞬間、高さ合計を取得保持
  • その後すぐに閉じる

この保持した高さを 0px と toggle することで、高さ不確定要素でも開閉アニメーションを実現します。各小項目の高さが決まっていれば、もっと単純になります。

components/aside/menu/list/drillDown/useDrillDown.ts
useEffect(
  () => {
    if (props.ref.current === null) return
    const defaultHeight =
      state.defaultHeight === 'auto' // 初期マウントは 'auto'
        ? props.ref.current.clientHeight
        : state.defaultHeight
    const height = state.opened ? defaultHeight : '0px'
    update(_state => ({
      ..._state,
      height,
      defaultHeight
    }))
  },
  [state.opened]
)

memoize chain

useMemo により memoize された値も、memoize input array に含むことができます。下記の通り、options.transitionDuration が変化すると、style まで一連で値が変化。あとは必要になる style のみを公開します。

components/aside/menu/list/drillDown/useDrillDown.ts
const duration = useMemo(
  () => `${options.transitionDuration / 1000}s`,
  [options.transitionDuration]
) // options.transitionDuration に反応

const transitionDuration = useMemo(
  () =>
    state.defaultHeight !== 'auto' ? duration : '0s',
  [state.defaultHeight, duration]
) // memoize された上の duration に反応

const style = useMemo(
  () => ({
    height: state.height,
    transitionDuration
  }),
  [state.height, transitionDuration]
) // memoize された上の transitionDuration に反応

useDrawerMenu

Menu の横サイズ指定を Optional Injection で受け付けます。Menu は非表示時に画面の左側に格納されている状態で、開閉の状態に応じて styleを算出します。

components/aside/useDrawerMenu.ts
const useDrawerMenu = (props: Props) => {
  const [opened, toggleOpened] = useState(...)
  const options = useMemo(...)
  const nodeStyle = useMemo(...)
  const containerStyle = useMemo(...)
  const handleToggleOpen = useCallback(...)
  return {
    opened,
    nodeStyle,
    containerStyle,
    handleToggleOpen
  }
}

State Bubbling による視覚効果の追加

Drawer Menu (aside要素) の開閉後に、main 要素に動きを与えています。ここでも、先日と同様の State Bubbling を利用しています。たびたびの反面教師ですが、Drawer Menu の状態が他の要素の関心ごととして更に拡張される折には、リファクタするべき箇所です。

components/index.tsx
export default () => {
  const [opened, toggleOpened] = useState(false)
  const mainStyle = useMemo(
    () => ({
      opacity: opened ? 0 : 1,
      transform: opened
        ? `translateX(10px)`
        : `translateX(0)`
    }),
    [opened]
  )
  return (
    <StyledView
      opend={opened}
      toggleOpened={toggleOpened}
      mainStyle={mainStyle}
    />
  )
}

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
1