useCallback

서성원·2024년 1월 24일
0

리액트

목록 보기
7/26
post-thumbnail

컴포넌트를 최적화해야 하는 이유

불필요한 연산을 막아 필요한 것만 렌더링 하기 위함

function App() {

  const [data, setData] = useState([]);

  const dataId = useRef(0);

  const getData = async()=>{
    const res = await fetch(
      'https://jsonplaceholder.typicode.com/comments'
      ).then((res)=>res.json());
    
      const initData = res.slice(0,20).map((it)=>{
        return {
          author : it.email,
          content : it.body,
          emotion : Math.floor(Math.random() * 5)+1,
          created_date : new Date().getTime(),
          id : dataId.current++
        }
      })
      setData(initData);
  };

  useEffect(()=>{
    getData(); 
  },[])

현재 내가 작성하고 있는 코드에서 예를 들어보겠다. DiaryEditor가 앱 컴포넌트의 하위태그로 설정되어 있는 상태다. 참고로 DiaryEditor 는 React.memo 로 감싸져 있다. 이 상태에서 마운트 되는 시점이 몇 번인지 확인하기 위해 useEffect 에 콘솔을 넣어 확인했다. 그 결과 콘솔에 두 번 출력되는 것을 볼 수 있었다. 왜 두 번 출력이 될까?

그 이유는 앱 컴포넌트에서 data state 초기값이 빈 배열이어서 한 번 렌더링 일어나고 DiaryEditor 또한 렌더링이 일어난다. 그리고 앱 컴포넌트 "setData(initData)" 부분에서 data state 가 한 번 더 바뀌게 되므로 총 두 번 렌더링 되는 것이다. 하지만 다시 생성되어도 앱 컴포넌트 안의 onCreate 함수는 변하지 않는다.

React.memo 에서 비원시 타입 자료의 비교는 기본적으로 얕은 비교가 일어나므로 onCreate 함수가 계속 생성된다. -> 어떻게 해결??

연산 결과 재사용하는 useMemo 를 사용하면 안 되는 이유

-> 함수가 아닌 값을 반환하므로 onCreate 함수 그대로 DiaryEditor 의 prop 으로 전달할 수 없음.

useCallback

*공식문서

값을 반환하는 게 아니라 콜백함수를 다시 반환하는 역할을 한다. 중요한 건 메모이제이션 된 콜백함수를 반환한다는 것이다. 두 번째 인자로 전달된 dependency array 안의 값이 변하지 않으면 첫 번째 인자로 전달한 콜백함수를 계속 재사용할 수 있도록 도와주는 리액트 훅.

const onCreate = useCallback((author,content,emotion)=>{
    const created_date = new Date().getTime();
    const newItem = {
      author,
      content,
      emotion,
      created_date,
      id : dataId.current
    }
    dataId.current += 1;
    setData([newItem, ...data])
  },[]);

onCreate 함수에 useCallback 을 적용시킨 모습이다. 이제는 일기 목록을 삭제해도 useEffect 안의 콘솔이 출력되지 않음을 확인할 수 있다.

problem : 일기 저장하면 원래 있던 목록들 다 사라지고 저장한 것만 남음

왜? useCallback 활용하면서 dependency array 에 아무것도 넣지 않아서.
onCreate 함수는 컴포넌트 마운트 시점에 한 번만 생성되므로 당시 data state 값이 빈 배열이므로 이런 현상이 발생한 것.

함수는 컴포넌트가 재생성될 때 다시 생성되는 이유가 있다.

-> 현재 state 값을 참조할 수 있어야하기 때문!!

하지만 onCreate 함수는 callback 안에 갇혀 빈배열로 전달했으므로 onCraete 함수가 알고 있는 data 값은 그대로 빈 배열이 된다. 그래서 일기를 추가하면 해당 일기만 newItem 으로 렌더링 되는 것이다.

그럼 어떻게 해야 할까?? 단순히 빈 배열에 data 를 전달해 원래 데이터들이 남아있게 할 수 있지만 여기서 딜레마가 발생한다. 우리는 useCallback 을 이용해 필요없는 렌더링을 막으려고 했지만 빈 배열에 data를 전달하면 결국 data state 가 변할 때마다 렌더링되는 것을 막을 수 없기 때문이다.

함수형 업데이트를 활용

setData((data)=>[newItem, ...data])

setData 에 함수를 전달하여 아이템을 추가한 data 를 리턴하도록 하면 useCallback 에서 빈 배열을 전달해도 항상 최신의 state 를 data인자로 참고할 수 있게 된다.

=> 이전에는 onCreate 함수가 data state 의 현재값을 참조할 수 없었기 때문에 아이템이 하나만 남았었는데 이제는 정상적으로 수행된다.

profile
FrontEnd Developer

0개의 댓글

관련 채용 정보