Redux와 React Hooks를 활용하여 토스트 구현

"저장에 성공했습니다"이거나 "쿠폰 적응에 실패했습니다"이거나
사용자에게 뭔가 내용을 일시적으로 전달하고 싶을 때가 있다고 생각합니다.

그러한 때에 사용되는 통지의 UI의 일종인 토스트를 구현해 갑니다.
이미지로서는 이런 것입니다



토스트 요구 사항



아래의 요건을 충족하는 토스트를 만들어 갑니다.
  • 하나씩 순서대로 표시됩니다
  • 표시되고 조금 지나면 자동으로 사라진다
  • 모든 구성 요소에서 새 토스트를 추가 할 수 있습니다
  • 디자인은 노력하지 않습니다

  • 구현



    1. 토스트 정보를 보관할 장소를 준비합니다.



    어디서나 추가하고 참조할 수 있도록 Redux의 Store에 가져가십시오.
    Store 자체의 설정에 대해서는 생략합니다.
    // toast.ts
    import { Reducer, AnyAction } from 'redux'
    import { isType, actionCreatorFactory } from 'typescript-fsa'
    import { useSelector } from 'react-redux'
    
    export type Toast = {
      message: string
    }
    type Toasts = Toast[]
    
    // action
    const actionCreator = actionCreatorFactory('TOAST')
    export const Push = actionCreator<{ toast: Toast }>('PUSH')
    export const Shift = actionCreator('SHIFT')
    
    // reducer
    const initialState: Toasts = []
    export const reducer: Reducer<Toasts> = (
      state: Toasts = initialState,
      action: AnyAction,
    ) => {
      if (isType(action, Push)) {
        const { toast } = action.payload
        return state.concat([toast])
      }
    
      if (isType(action, Shift)) {
        return state.slice(1)
      }
    
      return state
    }
    export default reducer
    
    // selector
    export const GetToasts = (): Toasts => useSelector(
      (state: { toasts: Toasts }) => state.toasts,
    )
    

    State는 파일에 정의되어 있습니다.
    프로젝트에 맞게 로드하면 좋다고 생각합니다.

    2. 토스트를 표시하는 구성 요소 준비



    이 컴퍼넌트에서는 표시 상태를 관리하기 위해서 useState토스트의 변화를 모니터링하기 위해 useEffect와 같은 React Hooks 기능을 사용합니다.

    토스트의 정보는 Store에 저장하고 selector도 준비되어 있기 때문에
    어디서나 쉽게 호출할 수 있습니다.
    // ToastContainer.tsx
    import React, { useState, useEffect } from 'react'
    import { useDispatch } from 'react-redux'
    
    import { GetToasts, Shift } from 'toast.ts'
    import style from 'style.scss'
    
    const timeout = async (ms: number) => (
      new Promise((resolve: () => void): void => {
        setTimeout(() => resolve(), ms)
      })
    )
    
    const ToastContainer: React.FC = () => {
      const toasts = GetToasts()
      const [visible, setVisible] = useState(false)
      const dispatch = useDispatch()
    
      useEffect(() => {
        if (toasts.length === 0 || visible) {
          return
        }
    
        const showToast = async () => {
          setVisible(true)
          await timeout(3000)
          setVisible(false)
          await timeout(500)
          dispatch(Shift())
        }
    
        showToast()
      }, [toasts])
    
      return (
        <div className={style.toastContainer}>
          <div className={`${style.toast} ${visible && style.visible}`}>
            {toasts.length > 0 && toasts[0].message}
          </div>
        </div>
      )
    }
    
    export default ToastContainer
    

    토스트의 표시 흐름은 다음과 같습니다.
  • toasts 가 늘어나면 감시하고 있다 useEffect 가 발화
  • 토스트가 보이도록 visibletrue로 변경
  • 잠시 후 visiblefalse로 변경하고 숨기기
  • dispatch(Shift()) 에서 첫 번째 toasts 제거
  • toasts 가 업데이트되기 때문에 useEffect 가 다시 발화
  • 아직 toasts 있다면 2로 돌아갑니다

  • 그리고는 적절하게 CSS를 맞추면 괜찮습니다.
    // style.scss
    .toast {
      width: 320px;
      padding: 16px 0;
      color: #fff;
      text-align: center;
      background-color: red;
      opacity: 0;
      transition: opacity .6s;
    
      &.show {
        opacity: 1;
        transition: opacity .2s;
      }
    }
    

    3. 토스트 추가



    Redux에서 토스트를 관리하기 때문에 어디에서 추가해도 괜찮습니다.
    예를 들어 뭔가 API를 부르면 이렇게 토스트를 추가합니다.
    save()
      .then(() => dispatch(Push({ toast: { message: '保存しました' } })))
      .catch(() => dispatch(Push({ toast: { message: '保存に失敗しました' } })))
    

    토스트의 내용에 따라 색을 바꾸거나 하고 싶은 경우는 type 등도 갖게 합시다.

    요약



    밸리데이션이나 표시의 방법에 관해서는 실장하고 싶은 사양에 맞추어 적절히 변경해 주세요.
    React Hooks를 사용하면 번잡했던 처리가 깔끔하게 쓸 수 있으므로 즐겁습니다.

    참고


  • Redux: htps : // Rez X. js. 오 rg
  • React Hooks: htps : // 그럼. Rea ctjs. 오 rg / 두 cs / 호오 ks - t t. HTML
  • 좋은 웹페이지 즐겨찾기