定番の Drawer Menu です。Menu は将来的に拡張される代表的なものなので、拡張性を初めから考慮します。今回は Custom Hooks を2つ定義しています。小項目を格納し開閉する「useDrillDown」と、Menu 全体の開閉を扱う「useDrawerMenu」です。
code: github / $ yarn 1206
useDrillDown
小項目を格納し開閉する Custom Hooks です。まずは、ここで利用されている Hooks API を見てみます。
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
}
}
格納される項目は数も高さもバラバラだったりすることが常です。高さを切り替える開閉アニメーションの為に、次のことをしています。
- まずはマウント時に内包要素を出しっ放しに
- この瞬間、高さ合計を取得保持
- その後すぐに閉じる
この保持した高さを 0px と toggle することで、高さ不確定要素でも開閉アニメーションを実現します。各小項目の高さが決まっていれば、もっと単純になります。
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 のみを公開します。
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を算出します。
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 の状態が他の要素の関心ごととして更に拡張される折には、リファクタするべき箇所です。
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}
/>
)
}