ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [리액트 알쓸신잡] Hook - 1 - useMemo VS useCallback
    프로그래밍/React 2023. 6. 22. 14:50
    useMemo(() => console.log(), [test])
    const memoizedCallback = useCallback(() => console.log(), [test])

    두 로직은 각각 useMemo와 useCallback을 사용해서 같은 결과를 나타내는 로직이다.


    useMemo

    컴포넌트 내부에서 발생하는 연산을 최적화 할 수 있다

    메모이제이션  값을 반환하는 함수이다.

    useMemo(() => fn, [deps])

    여기서 deps로 지정된 값이 변하게 된다면 첫번쨰 인자인 함수가 실행을 하고 그 함수의 반환 값을 반환 해준다.

    deps는 depencency(의존성)을 뜻하며 useMemo가 deps에 의존하고 있다는 것을 말한다.

    import React, { useState, useCallback, useMemo } from "react";
    
    export default function App() {
      const [ex, setEx] = useState(0);
      const [why, setWhy] = useState(0);
    
      // 버튼 클릭시 ex값이 출력된다.
      console.log(ex); 
      // const memo = useMemo(() => console.log(ex), [ex])
      // 위 로직은 useMemo를 활용한 내용이다. 
      return (
        <>
          <button onClick={() => setEx((curr) => (curr + 1))}>X</button>
          <button onClick={() => setWhy((curr2) => (curr2 + 1))}>Y</button>
        </>
      );
    }

     

    위 로직은 ex 값이든 why 값이든 state의 변화 할 때마다 console.log가 호출된다. 우리는 이러한 것을 원하는 것이 아니라 ex의 값이 변했을 때만 console.log를 출력하고 싶다 이럴때 memo를 이용해서 ex라는 변수를 등록함으로써 의존성이 있게끔 만들어 이 특정 변수가 변할 때만 useMemo에 등록함 함수만 실행하도록 하면된다

     

     


    useCallback

    앞서 보았던 useMemo는 메모이제이션된 값을 반환해서 참 직관적이었는데, 이 useCallback은 메모이제이션된 함수를 반환하는 특징을 가지고 있습니다.

    useCallback(fn, [deps])

    useCallback 또한 deps, 의존성이 있는 값이 변하면 fn에 등록한 함수를 반환하는 기능을 가지고 있습니다.

    useCallback이 함수를 반환하기 때문에 그 함수를 가지는 const 변수에 초기화하는 것이 일반적인 모양입니다. 

    useCallback을 사용하는 경우

    • 자식 컴포넌트에 props로 함수가 전달되는 경우
    • 외부에서 값을 가져오는 API를 호출하는 경우

    1. 자식컴포턴트에 prop로 함수가 전달 되는 경우

    먼저 함수는 값이 아닌 참조로 비교된다

    const functionOne = function() {
      return 5;
    };
    const functionTwo = function() {
      return 5;
    };
    // 서로의 참조가 다르기 때문에 false
    console.log(functionOne === functionTwo);

    동일한 값을 반환하지만 참조가 다르기 때문에 false가 나온다 (return 값이 같더라도 참조되는 곳이 다르다는 의미인가??)

    위와 같이 컴포넌트에서 특정 함수를 정의할 경우 각각의 함수들은 모두 고유한 함수가 됩니다.

    이런 고유한 함수가 생성될 때마다 부모를 통해 props에 함수를 전달받는 자식 컴포넌트에서는 props가 변경되었다고 생각하고 리렌더링이 발생한다.

    function App() {
      const [name, setName] = useState('');
      const onSave = () => {};
    
      return (
        <div className="App">
          <input
            type="text"
            value={name}
            onChange={(e) => setName(e.target.value)}
          />
          <Profile onSave={onSave} />
        </div>
      );
    }

    useCallback을 사용하지 않을 경우, name이 변경되어 리렌더링이 발생(state의 변경으로 인한 컴포넌트의 리랜더링)하면 onSave함수가 새로 만들어지고, Profile 컴포넌트의 props로 onSave함수가 새로 전달되게 됩니다.

    이때 Profile 컴포넌트에서 useMemo를 사용해도 이전 onSave와 이후 onSave가 같은 값을 반환하지만 참조가 다른 함수가 되어버리기 때문에 리렌더링이 일어나게 됩니다

     

    부모 컴포넌트만 수정하려고 했지만 연쇄적으로 하위 컴포넌트들 모두 렌더링이 일어나게 된다(props의 변경은 리렌더링임)

    import React, { useCallback, useState } from 'react';
    import Profile from './Profile';
    
    
    function App() {
      const [name, setName] = useState('');
      const onSave = useCallback(() => {
        console.log(name);
      }, [name]);
    
      return (
        <div className="App">
          <input
            type="text"
            value={name}
            onChange={(e) => setName(e.target.value)}
          />
          <Profile onSave={onSave} />
        </div>
      );
    }

    useCallback을 사용해서 onSave라는 함수를 재사용하는 것으로 자식 컴포넌트의 리렌더링을 방지할 수 있습니다!

     

     

     

    2. 외부에서 값을 가져와 호출하는 경우

    import React, { useState, useEffect } from "react";
    
    function Profile({ userId }) {
      const [user, setUser] = useState(null);
    
      const fetchUser = () =>
        fetch(`https://your-api.com/users/${userId}`)
          .then((response) => response.json())
          .then(({ user }) => user);
    
      useEffect(() => {
        fetchUser().then((user) => setUser(user));
      }, [fetchUser]);
    
      // ...
    }

    위의 코드는 fetchUser 함수가 변경될 때만 외부에서 api를 가져와 useEffect가 실행됩니다.

    사실 이 코드는 정상적인 코드가 아닌데요.

    앞서 말씀 드렸다시피 Profile이라는 컴포넌트가 리렌더링이 발생할 경우 fetchUser 함수에는 새로운 함수가 할당되게 됩니다. 그러면 useEffect()함수가 호출되어 user 상태값이 바뀌고, state 값이 바뀌었기 때문에 다시 리렌더링이 일어납니다.

    무한루프에 빠져버리게 되는 것이지요.

     

    이때 useCallback을 사용할 경우 fetchUser 함수의 참조값을 동일하게 유지시킬 수 있습니다.

    import React, { useState, useEffect } from "react";
    
    function Profile({ userId }) {
      const [user, setUser] = useState(null);
    
      const fetchUser = useCallback(
        () =>
          fetch(`https://your-api.com/users/${userId}`)
            .then((response) => response.json())
            .then(({ user }) => user),
        [userId]
      );
    
      useEffect(() => {
        fetchUser().then((user) => setUser(user));
      }, [fetchUser]);
    
      // ...
    }

    api의 옵션으로 사용되는 userId가 변동될 때만 fetchUser에 새로운 함수가 할당되도록 설정하고, 그것이 아니면 동일한 함수가 실행되게 되서 무한 루프에 빠지지 않도록 할 수 있습니다!

     

    Memo와 상당히 비슷하지만 주로 렌더링 성능을 최적화 할떄 사용한다. 리랜더링 시 불필요한 함수 생성을 막게 해준다 이것도 최적화의 방법 중의 하나로써 쓰면 좋다 useCallback으로 한번 감싸준 다음 []를 두번쨰 인자로 받는데 이 안에 값을 넣으면 그 값이 바뀌었을 때만 함수를 생성한다.

     

     

     

     

     

     

     

     

    참고

    https://react.vlpt.us/basic/18-useCallback.html

    댓글

Designed by Tistory.