Drawer Menu

定番の Drawer Menu です。Menu は将来的に拡張される代表的なものなので、拡張性を初めから考慮します。今回は Custom Hooks を2つ定義しています。小項目を格納し開閉する「useDrillDown」と、Menu 全体の開閉を扱う「useDrawerMenu」です。
code: [github](https://github.com/takefumi-yoshii/react-hooks-ogiri/tree/master/apps/drawer-menu) / `$ yarn 1206`

|![1206.jpg](https://qiita-image-store.s3.amazonaws.com/0/59071/90680c70-c3f7-2a19-1d0d-3eab23ab31cf.jpeg)|![1206-1.jpg](https://qiita-image-store.s3.amazonaws.com/0/59071/f02af5e2-0929-8201-5a2f-ca51f41034c5.jpeg)|
|---|---|


# 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](https://qiita-image-store.s3.amazonaws.com/0/59071/1643b310-3b7f-9dce-d2e8-1f38a02a885b.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}
    />
  )
}

```