지난 글에서 React.memo( ) 를 사용해도 계속해서 컴포넌트가 재랜더링 될 때에 대해서 해결책을 이야기 해보려고 한다. ( 클로저가 무엇인지 알아야 이해하기 쉽다.)
제목에도 적혀있듯이 바로 useCallback() 을 사용하는 것이다!
useCallback( ) 이란?
- 컴포넌트 실행 전반에 걸쳐 함수를 저장(메모라이제이션)할 수 있게 해주는 훅
- 리액트에게 내가 어떤 함수를 저장할 것이고, 해당 함수를 재 생성할 필요가 없다고 알려줄 수 있다.
- 쉽게 이해하자면 함수가 저장된 메모리 위치 값을 통해 해당 주소에 저장된 함수를 꺼내 비교할 수 있게 해주는 것 같다. ( 원시타입과 참조타입의 차이를 생각해보자 )
사용하는 방법은 다음과 같다.
이전 글의 <app.js> 참고
const toggleBtnHandler = useCallback(() => {
if (allowToggle) {
setActiveBtn((preveactiveBtn) => !preveactiveBtn);
}
}, [allowToggle]);
useCallback() 의 첫번째 인자로 함수를 전달하면, 그 함수를 저장하고
app() 컴포넌트가 다시 실행되면 useCallback()이 리액트가 저장한 함수를 찾아서 같은 함수 객체를 재사용한다.
+) js의 함수는 클로저이기 때문에 두번째 인자로 의존성을 설정해주지 않으면 처음 저장한 상태 값 그대로 굳어버린다. 그래서 함수 재생성이 필요할 때에는 두번째 인자 배열에 종속성을 추가해줘야한다.
< app.js >
import React, { useCallback, useState } from "react";
import "./App.css";
import Button from "./components/UI/Button/Button";
import Demooutput from "./components/Demo/DemoOutput";
function App() {
const [activeBtn, setActiveBtn] = useState(false);
const [allowToggle, setAllowToggle] = useState(false);
// 버튼을 누를때마다 App컴포넌트 전체가 다시 랜더링됨
console.log("APP RUNNING");
// useCallback 덕분에 toggleBtnHandler가 메모리 안에서 항상 같은 객체임을 보증받음
// js의 함수는 클로저 이기때문에 의존성에 값을 추가 안해주면 false인 채로 굳어버린다
const toggleBtnHandler = useCallback(() => {
if (allowToggle) {
setActiveBtn((preveactiveBtn) => !preveactiveBtn);
}
}, [allowToggle]);
const allowToggleHandler = () => {
setAllowToggle(true);
};
return (
<div className="app">
<h1>Hi there!</h1>
<Demooutput show={activeBtn}></Demooutput>
<Button onClick={toggleBtnHandler}>Show What she Saying</Button>
<Button onClick={allowToggleHandler}>Allow Toggling</Button>
</div>
);
}
export default App;
<Demooutput.js>
import React from "react";
// props로 받은 값에 따라 조건부로 랜더링 할 컴포넌트
const Demooutput = (props) => {
console.log("Demooutput 실행");
return <p>{props.show ? "This is new!" : ""}</p>;
};
export default React.memo(Demooutput);
<Button.js> - 디자인한 버튼을 재사용하기 위해 컴포넌트로 커스텀 한 것
import React from "react";
import classes from "./Button.module.css";
const Button = (props) => {
console.log("BUTTON RUNNING");
return (
<button
type={props.type || "button"}
className={`${classes.button} ${props.className}`}
onClick={props.onClick}
disabled={props.disabled}
>
{props.children}
</button>
);
};
export default React.memo(Button);
그리고 이제 확인을 해보자
이런 화면인데 allow Toggling을 눌러서 토글버튼을 활성화 하게 해줘야 숨겨놓은 문장을 볼 수 있게 했다.
코드를 보면 알겠지만 Show What she saying 버튼은 allowToggle 값이 true여야 작동한다.
useCallback()에 의존성으로 allowToggle 을 추가해준 이유는 만약 설정해주지 않았다면, JS의 함수가 클로저이기 때문에 초기값으로 설정해준 false가 들어간 채로 함수가 굳어질 것이기 때문이다.토글허락 버튼을 눌러주면 show라고 적힌 버튼이 작동해야 하기 때문이다.
그래서 allowToggle 의 값이 false -> true로 바뀌게 되면 리액트가 useCallback() 으로 저장해둔 함수가 있는 곳으로 가서 비교를 한다음 변경사항이 있다면 변경한 후 저장을 해준다.
useMemo() 란?
- useCallback()과 유사하지만 메모라이제이션 해주는 것이 함수가 아닌 값이다.
- 컴포넌트 실행 전반에 걸쳐 값을 저장(메모라이제이션)할 수 있게 해주는 훅
나는 처음에 useMemo()를 보고 왜 useState같은 훅이 있는데 값을 저장하는 기능이 있는 훅이 왜 또 있을까? 하는 의문이 들었었다.
그럼 useMemo()는 언제쓸까?
가정을 해보자, 부모 컴포넌트가 재 평가가 된다면 자식 컴포넌트도 마찬가지로 재평가가 된다.
그런데 자식 컴포넌트에 props로 배열을 전달하고 자식 컴포넌트에서 그 배열을 정렬하는 .sort()같이 연산에 많은 비용을 써야 하는 기능을 사용한다면?
props로 전달하는 배열이 매번 재 생성될 것이고 그럼 자식 컴포넌트에 있는 .sort()가 매번!! 다시!! 실행될 것이다.
그래서 자식 컴포넌트에서 연산을 한 결과 값을 useMemo()로 저장해 놓고 변경사항이 있을 때만 sort() 를 실행하게 해주는 것이다.
<예시>
import React, { useMemo } from "react";
const DemoList = (props) => {
const { items } = props;
const sortedList = useMemo(() => {
return items.sort((a, b) => a - b);
}, [items]);
return (
<div>
<h2>{props.title}</h2>
<ul>
{sortedList.map((item) => {
<li key={item}>{item}</li>;
})}
</ul>
</div>
);
};
요렇게! 그리고 props로 넘겨주는 배열이 객체이기 때문에 매번 재 생성되는 것을 잊으면 안된다.
그래서 부모 컴포넌트에서 넘겨주는 배열도 useMemo()를 사용해 재생성되지 않도록 해주는 걸 잊지말자!
'React & React-native' 카테고리의 다른 글
[ React ] React.memo() / 컴포넌트 재평가와 리랜더링(re-rendering)은 같지않다. / 함수 재평가 막기 (0) | 2023.06.28 |
---|---|
[ React ] react 생명주기란? - 생명주기 테스트 코드 (0) | 2023.06.26 |
[ React ] react 생명주기란? - 생명주기 함수들 (0) | 2023.06.26 |
<google-map-api> 어플에 지도 띄우기 (0) | 2022.12.16 |
< React-native > / expo-camera보다 가벼운 expo-imagepicker / 핸드폰 카메라 권한 설정하기 / 앨범에 있는 사진 올리기/ 권한 설정 (0) | 2022.12.15 |