usehook-ts 라이브러리에 대해 알아보자

Jeong·2023년 9월 8일
0

React Hooks

목록 보기
5/5
post-thumbnail

키워드

  • usehooks-ts
    • useBoolean
    • useEffectOnce
    • useFetch
    • useInterval
    • useEventListener
    • useLocalStorage
    • useDarkMode
  • swr
  • react-query

최종 목표

간단한 서버를 만들고, custom hook 을 사용하여 서버에서 데이터를 받아올 수 있다.

현재 목표

usehook-ts 라이브러리에 대해 알아보자.

usehooks-ts 란?

타입스크립트로 만들었지만 자바스크립트에서도 사용 가능하다.

모든 Hook 에 대해 어떻게 만들었는지 나와있어서 Hook 을 만드는데 많은 영감을 얻을 수 있다.

usehooks-ts 설치하기

npm i usehooks-ts

useBoolean 이란?

참/거짓을 다룰 땐 toogle 같이 의도가 명확한 함수를 쓰는 게 좋다.

이전 포스트 에서 작성했던 TimerControl 에 써보자.

setPlaying(!playing) 보다 toogle 혹은 tooglePlaying 이 의도가 명확하다.

export default function TimerControl() {
    // const [playing, setPlaying] = useState(false);
	const {value: playing, toogle: tooglePlaying} = useBoolean(); // 추가
   	
  	// const handleClick = () => {
    //	setPlaying(!playing);
    // };
  
  	return (
    	<div>
        	{playing ? (
          		<Timer />
    		) : (
        		<p>Stop</p>
        	)}
        	<button type="button" onClick={tooglePlaying}>
        		Toggle
        	</button>
        </div>
    );
}

useEffectOnce 이란?

의존성 배열을 빈 배열로 넣어서 한 번만 실행하는 Effect 를 잡아줄 때가 많은데, useEffectOnce 를 쓰면 더 명확히 드러난다.

로직은 똑같지만 이름을 명확히 했을 때 혜택을 무시할 수 없다.

이전 포스트 에서 작성했던 useFetchProducts 에 써보자.

const [products, setProducts] = useState<Product[]>([]);

useEffectOnce(() => {
	const fetchProducts = async () => {
		const url = 'http://localhost:3000/products';
		const response = await fetch(url);
		const data = await response.json();
		setProducts(data.products);
	};

	fetchProducts();
}); // 의존성 배열이 필요없다.

useFetch 란?

정말 간단히 쓸 때 좋다.

export default function useFetchProducts() {
  const url = 'http://localhost:3000/products';

  //	const response = await fetch(url);
  //	const data = await response.json();
  const { data } = useFetch(url);

  if (!data) { return []; }
  return data.products;
}

use-http 란?

몇 가지 기능이 더 있는 useFetch 라이브러리가 따로 있다. (아샬님은 잘 안씀)

만약 조금 더 복잡해도 괜찮다면, 캐시 이슈를 고려한 좋은 대안이 있다. 👇

SWR 을 사용하면 컴포넌트는 주기적으로 데이터가 바뀌었는지 확인한다. 예를 들어, 게시판이면 게시물이 올라왔는지 확인한다.

React-Query 는 이보다 더 복잡한 것을 다룰 때 사용한다.

useInterval 이란? (강추)

setInterval 은 상태가 물려있으면 대부분 문제가 발생한다. setInterval 을 사용할 일이 있으면 무조건 useInterval 을 쓰자.

setInterval 의 문제는?

React 에서 setInterval 등을 쓸 때는 주의해야 부분이 있다.

아래 코드를 보자.

우리가 예상하는 결과는 1초에 count 가 1씩 증가 이다. 하지만 정상적으로 동작하지 않는다. 이유는 2가지이다.

첫 번째는 매 렌더링마다 1초에 count 가 1씩 증가 를 하는 새로운 타이머가 생긴다. 타이머가 여러 개 생기는 것이다.

두 번째는 타이머가 제대로 일을 하지 않는다. 첫 번째 타이머를 예로 들면 1초에 count 가 1씩 증가 가 아닌 1초에 (count 값) 0 에 1이 증가 를 수행한다. Closure 로 인해서 count 값이 0 으로 바인딩 되었기 때문이다. 그래서 결과를 보면 숫자가 오르락 내리락 한다. 모든 타이머가 사라지지 않고 남아있기 때문이다.

const [count, setCount] = useState(0);

setInterval(() => {
	setCount(count + 1);
}, 1000);

return <h1>{count}</h1>;

예를 들면 다음과 같다.

첫 번째 렌더링: count 값이 0으로 고정된다. 그래서 수행하는 0+1 이다. setState(1). 타이머는 남아있다.
→ 두 번째 렌더링: count 값이 1로 고정된다. 그래서 수행하는 일은 1+1 이다. setState(2). 타이머는 남아있다.
→ ...

setInterval 을 해결하는 방법은?

그럼 어떻게 해결할 수 있을까?

리액트 컴포넌트에서 가장 중요한 점은 입력을 받아서 출력을 만드는 것이다. Props, Staet 모두 입력으로 볼 수 있다. 입력은 바깥의 일이다. 내부의 일이 아니다. 그래서 입력을 바꾸는 일도 Side Effect 이다.
리액트에서는 Side Effect 를 분리하는 일이 되게 중요하다. 왜냐하면 렌더링이 끊임없이 일어날 수 있는 구조이기 때문이다. 출력을 만드는 직접적인 일이 아니면 Side Effect 분리해야 한다.

그래서 위 코드를 useEffect 로 빼보자. 이렇게 했을 때 새로운 타이머를 만들면 그전 타이머는 치운다.

const [count, setCount] = useState(0);

useEffect(() => {
  const id = setInterval(() => {
      setCount(count + 1);
  }, 1000);
  return () => clearInterval(id);
})

return <h1>{count}</h1>;

하지만 이렇게 바꿔도 문제는 있다. 여전히 클로저를 고려해야 한다.

setInterval 이 세팅한 함수는 딱 한 번만 실행하고 변하지 않는데, 그 안에 setCount 인자의 count 값은 클로저에 인한 값이라 그 당시에 count 값을 갖는다.

그래서 이를 해결하려고 아래처럼 수정해보자.

const [count, setCount] = useState(0);

useEffect(() => {
  const id = setInterval(() => {
      setCount(value => value + 1);
  }, 1000);
  return () => clearInterval(id);
}, [])

return <h1>{count}</h1>;

하지만 이렇게 바꿔도 또 문제는 있다.

만약 count 가 state 가 아니라 prop 이라면 문제가 발생한다. 최신 prop 에 값을 사용하려면 setInterval 에 넘기는 함수가 매번 갱신돼야 한다.

결론은 이래저래 고려해야 할 사항이 많다. 그래서 useInterval 을 사용한다. 코드량도 줄고 클로저 등 신경 쓸 필요가 없다. 당시 렌더링 되는 그 시점의 상태에 대해서만 코드를 짤 수 있게 해준다.

const [count, setCount] = useState(0);

useInterval(() => {
  setCount(count + 1);
}, 1000)

return <h1>{count}</h1>;

useInterval 같은 게 절차적 프로그래밍이 아닌 선언적 프로그래밍의 특징을 갖는다고 한다. 이전에 무엇을 했는지 신경 쓸 필요가 없으며 다음에 무엇을 할지 신경 쓸 필요가 없다. 즉, 이전 렌더링과 다음 렌더링을 신경 쓸 필요가 없다. 현재 렌더링만 신경쓴다.

이것은 함수의 특징과도 같다. 내부적으로 선언한 변수는 다 초기화 해서 다시 실행시키기 때문에 이전에 어떤 값을 가졌는지 신경 쓰지 않는다.

선언적 프로그래밍은 그 시점에 필요한 입력만 주면 그 시점에 결과가 나온다. 절차적일 수 밖에 없었던 setInterval 을 useInterval 을 통해서 선언적으로 사용했다.

useEventListenr 란? (추천)

모든 종류의 이벤트를 확인할 수 있다.

특히 dispatchEvent로 전달되는 커스텀 이벤트에 반응하기 좋다고 한다.

useLocalStorage 란? (추천)

LocalStorage 에 객체를 넣는 경우가 많다. 그때 stringfy 와 parse 를 자동으로 해준다.

그리고 이보다 중요한 특징은 이벤트를 통해(dispatchEvent + useEventListener) 다른 컴포넌트와 동기화할 수 있다는 것이다.

예를 들어 다크 테마로 변경 됐을 때, 변경 사실을 어디로 알리지 않아도 된다. 알아서 처리한다.

useDarkMode 란? (추천)

다크 테마 여부를 알 수 있다.

아하! 포인트

useInterval 에 대해서는 useEffect 완벽 가이드 문서와 리액트 공식 문서를 읽어보면서 이해해야겠다.

useEventListener 을 쓰면 완벽히 분리할 수 있다고 하시는데 이 부분을 아직 이해 못하겠다.

useLocalStorage 도 쓰면서 이해해야겠다.

custom hook 을 만들 때 usehooks-ts 을 많이 참고해야겠다.

profile
성장중입니다 🔥 / 나무위키처럼 끊임없이 글이 수정됩니다!

0개의 댓글