ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 1-8 React Hooks - useCallback
    React 숙련주차 2023. 5. 2. 14:13

    useCallback
    인자로 들어오는 함수 자체를 캐싱(메모이제이션)

    useCallback의 필요성
    Box1이 만약 count를 초기화 해 주는 코드라고 가정해보자.

    먼저 initCount라는 count초기화 함수를 생성한다.
    (단순하게 이 initCount를 Box1파일로 가서 초기화버튼태그안에 다짜고짜 onClick={initCount}라고만 하면
    연결이 안된다. 왜냐? 부모컴-자식컴 관계이기때문에 children과 props를 연결해줘야 동작이 된다!)
    그리고 아래 children태그인 Box1태그 안에 initCount={initCount} 지정해놓고,

    import React from "react"
    import {useState} from "react"
    import Box1 from "./components/Box1";
    import Box2 from "./components/Box2";
    import Box3 from "./components/Box3";
    
    function App() {
      //App컴-렌더링
      console.log('App컴포넌트가 렌더링되었습니다!')
      const [count, setCount] = useState(0);
    
      const onPlusButtonClickHandler = () => {
        setCount(count + 1);
      };
    
      const onMinusButtonClickHandler = () => {
        setCount(count - 1);
      };
    
      //count를 초기화해주는 함수
      const initCount = () => {
        setCount(0);
      }
    
      return (
        <>
        <h3>카운트예제입니다!</h3>
        <p>현재 카운트 : {count}</p>
        <button onClick={onPlusButtonClickHandler}>+</button>
        <button onClick={onMinusButtonClickHandler}>-</button>
        {/* Box1~3컴-렌더링 */}
        <div style={{
          display: 'flex',
          marginTop: "10px",
        }}>
          <Box1 initCount={initCount}/>
          <Box2 />
          <Box3 />
        </div>
        </>
      );
    }
    
    export default App;



    Box1.jsx파일에서 만든 초기화 버튼 태그안에 onClick={initCount}가 아닌
    Box1함수의 (인풋)자리에 {initCount}를 넣고 아래 버튼태그에 함수onClick={()=>{initCount();}}를 넣는다.

    import React from 'react'
    
    const style = {
        width: '100px',
        height: '100px',
        backgroundColor: '#01c49f',
        color: 'white'
    }
    
    function Box1({initCount}) {
        console.log("Box1컴포넌트가 렌더링 되었습니다!");
      return (
        <div style={style}>
          <button onClick={()=>{
            initCount();
        }}>초기화</button></div>
      )
    }
    
    export default React.memo(Box1);


    이렇게 만들고 페이지에서 +-버튼을 누르고 초기화버튼을 누르면 0으로 초기화가 되는것을 볼 수 있다.
    하지만 분명 전 시간에 Box1~3파일들은 export default React.memo(Box1);의 형태로 메모이제이션을 했었기 때문에
    초기화 버튼을 누른 해당 컴포넌트도 리렌더링 기록이 찍히지 않을거라는 짐작을 하기마련인데
    (새로고침하면 그냥 처음부터 냅다 초기화버튼을 누르면 당연히 리렌더링이 될것이 없으므로 아무 기록이 뜨지 않는다.)
    +-버튼을 눌렀을때 Box1의 기록이 같이 뜨게된다(+-버튼누르고 초기화버튼 눌렀을때도 마찬가지).

    이유는 함수형 컴포넌트이기 때문이라고 한다.
    App.jsx에서 Function키워드로 이루어진 함수를 이용한 App컴포넌트인데
    initCount도 마찬가지로 App컴포넌트가 리렌더링이 될때 다시 만들어졌다.
    또한 Box1파일에서도 다시 만들어진 그 initCount를 (props)로 받았고 그걸 onClick부분에서 return initCount()했기때문에
    물려받는 입장인 Box1컴포넌트는 props가 바뀌었다고 인식하고 리렌더링 기록을 찍는다.

    다시 알아보자.
    여기서 유심히 살펴볼점은 App컴포넌트안의 initCount함수 자체는 바뀐게 없다.
    그런데 왜 Box1이 props로 내려온 값 initCount()이 바뀌었다고 판단했을까?
    여기서 이제 React입문주차에서 배웠던 '원시데이터가 아닌것들(객체함수배열)은 불변성이 없다'는 것을 다시 떠올릴 필요가 있다(자바스크립트에서의 함수는 어떻게 보면 객체의 한 종류라고한다).
    부모컴포넌트(함수)가 리렌더링되면서 함수가 저장된 주소값이 바뀌면(불변성이 없다), 자식컴포넌트인 initCount의 함수 또한 새로운 주소를 받게된다.
    그래서 아래 export default React.memo(Box1);을 해도 props가 바뀐것을 Box1이 인식하기때문에 리렌더링 기록이 같이 찍히는 것이다.

    그러므로 결국 useCallback이라는 방법을 써서
    인풋으로 들어오는({initCount}) 함수 자체를 캐싱해서 함수의 불변성을 유지해주도록 바꿔줘야한다.

    initCount함수 자체를 메모이제이션을 해보자(그리고 특정 조건이 아니면 아예 변경이 되지 않도록 만든다).

    useCallback으로 함수를 감싼다(feat. []의존성배열)
    useEffect처럼 useCallback에서도 dependency array가 사용되는 이유는
    처음에 저장했던 콜백함수가 갱신되어야하면 이 []안에 그 해당 state를 넣어야하기 때문이다.

      const initCount = useCallback(() => {
        setCount(0);
      }, []);

     

    이렇게 하고 저장을 하면 더이상 부모컴포넌트함수의 리렌더링에 관여를 받지 않는다. 

    작동되는 원리를 알아보자.

    initCount의 함수자체를 useCallback으로 감싸 캐싱해놓았으므로
    초기화 버튼은 별다른 상황[]이 없는한 함수의 저장주소는 그냥 이상태로 남는다.
    그래서 처음 렌더링 되었을 때 export default React.memo(Box1);한 기록말고는
    initCount함수는 App함수의 props변화에 관여하지 않게된다.

    initCount에다가 별다른 상황[]인 count를 추가해보자.
    의존성배열안에 아무것도 없었을때는 useCallback으로 감싼 initCount함수자체의 캐시메모리상태는
    setCount(0)이다. 그래서 console.log에 ${count}값도 0이 된다.

      const initCount = useCallback(() => {
        console.log(`${count}에서 0으로 초기화됨`)
        setCount(0);
      }, []);

     

    의존성배열안에 [count]를 추가하면

      const initCount = useCallback(() => {
        console.log(`${count}에서 0으로 초기화됨`)
        setCount(0);
      }, [count]);

    띠용 결국 count값이  바뀔때 같이 count값을 받아서 렌더링도 같이 되었다.

    차이를 비교하자면

    useCallback 사용 전: App컴포넌트 변화 자체를 받음

    useCallback 사용 후: App컴포넌트 변화 자체를 받지 않음 My way

    useCallback+[count] 사용 후: App컴포넌트의 count변화를 받음

    'React 숙련주차' 카테고리의 다른 글

    1-10 Lifecycle  (0) 2023.05.03
    1-9 React Hooks - useMemo  (0) 2023.05.03
    1-7 React Hooks - React.memo  (1) 2023.05.02
    1-6 React Hooks - useContext  (0) 2023.04.30
    1-5 React Hooks - useRef  (0) 2023.04.30
Designed by Tistory.