-
1-8 React Hooks - useCallbackReact 숙련주차 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