2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

画面を上いっぱいまでスクロールし、引っ張る事で最新のデータを fetch する、アプリでお馴染みの機能です。
code: github / $ yarn 1214

1214.jpg 1214-1.jpg

usePullFetcher

今回の Custom Hooks 内訳です。

components/usePullFetcher.ts
function usePullFetcher() {
  const [state, update] = useState<State>(...)
  const handleTouchDown = useCallback(...)
  const handleTouchUp = useCallback(...)
  const handleTouchMove = useCallback(...)
  useEffect(...)
  useEffect(...)
  return {
    state,
    handleTouchDown,
    handleTouchUp,
    handleTouchMove
  }
}

触れた瞬間の処理

タッチフラグをオンにします。

components/usePullFetcher.ts
const handleTouchDown = useCallback(
  (event: TouchEvent<HTMLElement>) => {
    event.persist()
    update(_state => ({ ..._state, isTouching: true }))
  },
  []
)

指を動かしている時の処理

window.scrollY が 0px でない場合、early return します。「勢いよく引っ張った」判定をするため、前回同イベント発生時の座標を保持します。閾値が大きいほど「勢い」が必要になります。

components/usePullFetcher.ts
const handleTouchMove = useCallback(
  (event: TouchEvent<HTMLElement>) => {
    event.persist()
    update(_state => {
      if (!_state.isTop) return _state
      const y = event.touches[0].clientY - _state.startY
      const offsetY = y > 0 ? _state.threshold : 0
      const startY = event.touches[0].clientY
      return {
        ..._state,
        isTouching: true,
        startY,
        offsetY
      }
    })
  },
  []
)

離した瞬間の処理

軽く触れて離された場合、処理が走らない様にブロックします。

components/usePullFetcher.ts
const handleTouchUp = useCallback(
  (event: TouchEvent<HTMLElement>) => {
    event.persist()
    update(_state => {
      const offsetY =
        _state.offsetY > 0 ? _state.threshold : 0
      const fetched = offsetY === 0
      return {
        ..._state,
        fetched,
        isTouching: false,
        offsetY
      }
    })
  },
  []
)

useEffect

今回はサーバーを立てていないので簡易的な処理を施していますが、async function を useEffect で即時実行することで await する事が出来ます。

components/usePullFetcher.ts
useEffect(() => {
  const handleScroll = () => {
    update(_state => {
      const isTop = window.scrollY === 0
      return { ..._state, isTop }
    })
  }
  handleScroll()
  window.addEventListener('scroll', handleScroll)
  return () =>
    window.removeEventListener('scroll', handleScroll)
}, [])
useEffect(
  () => {
    ;(async () => {
      if (state.fetched) return
      try {
        await wait(400)
        const newItems = getMailItems(1)
        const items = [
          ...state.items,
          ...newItems.map(item => ({
            ...item,
            date: new Date(item.date)
          }))
        ].sort(
          (a, b) => b.date.getTime() - a.date.getTime()
        )
        update(_state => ({
          ..._state,
          fetched: true,
          offsetY: 0,
          items
        }))
      } catch (error) {
        update(_state => ({
          ..._state,
          fetched: true,
          offsetY: 0,
          error
        }))
      }
    })()
  },
  [state.fetched]
)

render props

Custom Hooks を利用するコンポーネントを用意、子コンポーネントに状態変化を伝搬させる時のみ render props します。

components/container.tsx
const View = (props: Props) => {
  const {
    state,
    handleTouchDown,
    handleTouchUp,
    handleTouchMove
  } = usePullFetcher()
  return (
    <div
      className={props.className}
      onTouchStart={handleTouchDown}
      onTouchEnd={handleTouchUp}
      onTouchCancel={handleTouchUp}
      onTouchMove={handleTouchMove}
    >
      {useMemo(() => props.render(state), [
        state.offsetY,
        state.fetched
      ])}
    </div>
  )
}
2
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
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?