Skip to content

Typescript Custom Hook

Junho Lee edited this page Dec 15, 2019 · 1 revision

Typescript + Custom Hook


useState

  • state 변수, 해당 변수를 변경할 수 있는 함수를 반환해줍니다.
  • useState()에 인자로 초기값을 넘겨줍니다.
import React, { useState } from 'react';

function Example() {
  // 새로운 state 변수를 선언하고, count라 부르겠습니다.
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

Custom Hook

  • 재사용가능한 로직을 묶어놓은 함수입니다.
  • 재사용가능한 로직?
    1. api 호출
    2. 응답이 오기를 기다림
    3. 데이터를 받음
    4. 화면에 렌더링

Hook 사용 규칙

Hook은 그냥 JavaScript 함수이지만, 두 가지 규칙을 준수해야 합니다.

  • 최상위(at the top level)에서만 Hook을 호출해야 합니다. 반복문, 조건문, 중첩된 함수 내에서 Hook을 실행하지 마세요.

  • React 함수 컴포넌트 내에서만 Hook을 호출해야 합니다. 일반 JavaScript 함수에서는 Hook을 호출해서는 안 됩니다.


const useGetFeed = <T>(initData:T):[
    data:T, // 데이터
    setData:React.Dispatch<T>, // 데이터를 변경하는 함수
    isLoading:boolean, // 현재 로딩중인지??
    isError:boolean, // 에러가 났는지??
    (url:string)=>void // url 을 변경시키는 함수
] => {
  // 초기값 세팅
  const [data, setData] = useState(initData); 
  const [url, setUrl] = useState('');
  const [isLoading, setIsLoading] = useState(false);
  const [isError, setIsError] = useState(false);

  useEffect(() => {
    const fetchData = async () => {
      setIsError(false); // 에러가 없어요
      setIsLoading(true); // 로딩중이에요
      try {
        const result = await axios(url); // 데이터 요청 기다림
        if (!result.data.success) { // 실패 응답 받음
          setIsLoading(false); // 로딩 끝났어요
        } else {
          // 성공 응답
          setData(result.data.data); // 데이터 세팅
          setIsLoading(false); // 로딩 끝났어요
        }
      } catch (e) {
        setIsError(true); // 에러가 났어요
      }
    };
    
    fetchData();
  }, [url]); // url이 변경될 때마다 위의 함수가 실행


  return [
    data,
    setData,
    isLoading,
    isError,
    setUrl,
  ];
};

<T> 는 뭔가요??

  • useGetFeed 은 재사용가능한 로직이지만 그때마다 사용하는 데이터의 타입은 다를 수 있습니다.
  • string, number 등 어떤 타입의 데이터가 필요하든지 사용 가능해야합니다. 그렇지 않으면 데이터 타입의 종류마다 hooks 를 작성해야하겠죠?
  • 물론 타입을 any로 주면 모든 데이터에 대응 가능하겠지만 그러면 타입스크립트를 사용하는 이유가 없기 때문에..
  • GenericType 을 이용하면 상황에 따라 다른 데이터를 다룰 수 있게 됩니다.
  • 밑의 예제처럼 IMusicData 타입을 갖는다라고 명시함과 동시에 호출할 수 있습니다.

  // hooks 는 이렇고
  const useGetFeed = <T>(initData:T):[
    data:T, // 데이터
    setData:React.Dispatch<T>, // 데이터를 변경하는 함수
    isLoading:boolean, // 현재 로딩중인지??
    isError:boolean, // 에러가 났는지??
    (url:string)=>void // url 을 변경시키는 함수
  ]
  
  ...
  // 타입은 이렇고
  interface IMusicData{
    id: string,
    content: IMusicContent[];
    comments: IComment[];
    createdAt: string,
    updatedAt: string,
  // 생략...
  }
  
  ...
  const [
    data,
    setData,
    isLoading,
    isError,
    setUrl
  ] = useGetFeed<IMusicData | null>(null); 
  // 사용할때는 이렇게 <IMusicData> 를 넣어줍니다.        
  const [] = useGetFeed<IWallpaperData | null>(null); 
  const [] = useGetFeed<IPhotoData | null>(null); 
  

받은 데이터들은 어떻게 활용할까요?

  • 먼저 데이터를 받아오기 위해서는 setUrl 함수를 사용합니다. setUrluseState 로 생성한 url 을 변경하는 함수입니다.
  • useGetFeed 에서 useEffecturl 이 변경될 때마다 실행되기 때문에 url 값에 api 주소를 넣으면 해당 주소에서 데이터를 받아오게 됩니다.
useEffect(() => {
  setUrl(`${API_SERVER}/feed/workmusic/${id}`); // api call
}, []);

  • isLoading은 이름 그대로 '현재 로딩중인지' 를 나타내는 값이기 때문에 다음과같이 true 일때 로딩 아이콘을 돌려주면 되겠습니다.
  • 그 외에도 isErrortrue 일때 다른페이지로 리다이렉트를 한다던지 필요하다면 추가적인 로직을 구현할 수 있습니다.
  <S.Progress>
    {isLoading && <CircularProgress color="inherit" />}
  </S.Progress>

  • data는 원하는 현태로 렌더링 해줍니다.
<S.FeedWrapper>
  {
    data.map(({
      _id,
      ownerId,
      url,
      creator,
      title,
      numOfComments,
      views,
    }) => (
      <WorksCard
        _id={_id}
        ownerId={ownerId}
        imgUrl={url}
        creator={creator}
        key={getShortId()}
        title={title}
        numOfComments={numOfComments}
        views={views}
      />
    ))
  }
</S.FeedWrapper>

참조

1. 그라운드 룰

2. 스크럼 hackmd link

3. 변경 내역

4. 스프린트

5. 기술공유

6. 팀 회고록

Clone this wiki locally