LoginSignup
3
1

More than 5 years have passed since last update.

定番の 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}
    />
  )
}

3
1
0

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
  3. You can use dark theme
What you can do with signing up
3
1