위와 같은 앱이 있다.
포켓몬 이름의 버튼을 누르면 버튼 value에 해당하는 포켓몬 정보가 렌더 된다.
input의 입력이 없거나 초기 상태면 특정한 text를 리턴한다.
input에 포켓몬 이름을 입력 후 submit 버튼을 누르면 포켓몬 정보가 렌더 된다.
없는 이름을 누르면 에러 UI가 나온다.
데이터 패칭 중 loading UI를 띄운다.
이제 위의 요구 사항을 만족하는 모듈을 만들어보자.
빠르게 살펴보면 PokemonInfo 컴포넌트는 부모로부터 pokemonName을 받는다.
useAsync()
함수는 api 호출함수, status, dependency를 인자로 받아 내부 로직을 실행하여 state
를 리턴한다.
useReducer()
부분은 비동기 패칭 시 status 처리 관련이라 생략해도 되고 useEffect()
안의 내용을 보면 먼저 asyncCallback()
를 호출하고 promise 체인을 타면서 status를 업데이트 한다.
그리고 useAsync 함수는 state를 리턴한다.
일단, useAsync
의 useEffect()
의 디펜더시를 인자로 받고 있다(lint에 걸림)
이렇게 디펜더시에 [asyncCallback]
넣어준다.
이제 이렇게 되면 문제가 생기는데 매렌더 마다 asyncCallback()
을 재생성하고 useAsync()
는 디펜더시인 pokemonName
가 변경되지 않아도 asyncCallback()
를 호출이 되면서 무한 렌더가 된다. 그래서 useCallback()
으로 디펜더시가 변경될 때마다 렌더되도록 해야한다.
이렇게 useCallback()
사용해주면 무의미한 함수 생선을 막을 수 있다.
다음 문제점이 발생한다. 리액트 공문에 보면 useMemo()
에 이렇게 써있다.
그러니까, semantic guarantee란 말이 나오는데 뭔지 잘 모르겠고... useMemo, useCallback 같은 메모 함수를 사용할 때는 메모 함수 없이도 잘 동작하는 곳에 사용해라 라는 뜻인 것 같다.
위의 작성한 asyncCallback()
는 useCallback()
없이는 제대로 작동하지 않는다. useCallback()
은 내부 함수가 항상 안정적(stable)이 것이라 보증해 주지 않는다. 그러므로 asyncCallback()
는 잘못 작동할 수도 있다.
의존적이지 않은 안정적인 fetch 함수를 만들어보자
먼저, useAsync()
함수에 run()
함수를 추가한다. run()
함수는 useCallback()
을 통해 호출 시만 실행된다. useCallback()
의 의존성 배열이 비어도 되는 이유는 함수 내부에서 어느 것도 의존적인 것이 없기 떄문이다(= 안정적이다.).
PokemonInfo 컴포넌트에서 run()
을 통해 fetch를 하도록한다. 그리고 useEffect()
에 디펜던시에 run함수를 추가한다. 결국, run()
는 useAsync()
에서 왔기 때문에 run()
이 필요할 때만 변경된다고 보증할 수 있게 된다.
** 수정 : return 이 아님
가독성도 좋아진 것 같다????
무지성으로 useEffect 썼는데 이제 생각하고 잘 써야겠다..
참고 -
https://kentcdodds.com/blog/usememo-and-usecallback
https://dongurami0502.tistory.com/32