프레임워크/React
[React] useMemo
은돌1113
2024. 3. 29. 15:48
🔗 출처
🖥️ useMemo의 기능
⚠️ useMemo는 결과값을 기억해두는 React Hook이기 때문에 빈번하게 사용할 경우 성능에 안좋은 영향을 줄 수 있습니다.
- 1️⃣ 복잡한 계산을 저장해 놓는 용도
- 아래 코드를 예시로 들자면
🙅♂️ 렌더링 될 때마다 expensiveCalculate 함수가 재실행되기 때문에 복잡한 작업을 수행해야 하는 경우 불필요한 함수가 매번 재실행되는 것이기 때문에 성능 저하 문제를 초래할 수 있습니다.
즉, 복잡한 작업을 할수록 비싼 값을 내야 한다.import React, { useState } from "react"; const expensiveCalculate = (numbers) => { console.log("calculate sum...."); return numbers.reduce((acc, cur) => acc + cur, 0); }; export default function Home() { const [numbers, setNumbers] = useState([]); const [addNumber, setAddNumber] = useState(""); const sum = () => expensiveCalculate(numbers); const handlerAddNumber = () => { setNumbers((prev) => [...prev, parseInt(addNumber)]); setAddNumber(""); }; return ( <div> <input type="text" value={addNumber} onChange={(e) => setAddNumber(e.target.value)} /> <button onClick={handlerAddNumber}>add number</button> <p>{numbers.join(", ")}</p> <p>sum : {sum}</p> </div> ); }
🙆♂️ 이 경우 useMemo를 사용하여 numbers state의 값(의존성 배열)이 변할 때만 콜백함수를 실행할 수 있도록 개선할 수 있습니다.
const sum = useMemo(() => expensiveCalculate(numbers), [numbers]);
⚠️ useMemo는 값을 저장하는 함수이기 때문에 위 코드처럼 expensiceCalculate(numbers)를 useMemo로 감싸주었습니다. 함수를 저장하고 싶을 때는 useCallback를 사용할 수 있습니다.
- 아래 코드를 예시로 들자면
- 2️⃣ 참조 동일성 유지
- 참조 동일성 유지를 설명하기 전에 React.memo에 대한 설명이 필요할 것 같습니다.
두 개의 부모, 자식 관계의 컴포넌트가 있다고 했을 때 부모 컴포넌트에는 todo와 something이라는 state가 있고, 자식 컴포넌트에서는 todo만 props로 전달받은 상태입니다.
🧐 자식 컴포넌트에 있는 "I am child"라는 console은 todo state의 값이 변하면 같이 렌더링 될까요?
(todo가 rprops로 연결되어 있는 상태)
🧐 자식 컴포넌트에 있는 "I am child"라는 console은 something state의 값이 변하면 같이 렌더링 될까요?
(아무것도 연결되어 있지 않은 상태)
🤓 정답은더보기부모 컴포넌트에서 렌더링이 일어나면 자식 컴포넌트도 모두 렌더링이 된다.입니다!
그렇기 때문에 자식 컴포넌트에서 props로 넘겨주는 todo state에 변화가 생겼을 때만 자식 컴포넌트를 렌더링 시켜주고 싶을 때 React.memo를 사용할 수 있습니다.
React.memo를 사용하면 컴포넌트를 통쨰로 기억하게 할 수 있습니다. (props로 전달받은 값이 변경되지 않는 이상 렌더링 되지 않습니다.)
- React.memo 사용 전
더보기
부모 컴포넌트
import React, { useState } from "react"; import Detail from "./Detail"; export default function Home() { const [todo, setTodo] = useState(0); const [something, setSomething] = useState(0); const changeTodo = () => { console.log("running todo..."); setTodo(Math.random() * 100); }; const changeSomething = () => { console.log("running something..."); setSomething((prev) => prev + 1); }; return ( <div> <h1>todo : {todo}</h1> <h1>something : {something}</h1> <button onClick={changeTodo}>change Todo value</button> <button onClick={changeSomething}>change Something value</button> <Detail todo={todo} /> </div> ); }
자식 컴포넌트
import React from "react"; function Detail({ todo }) { console.log("I am child"); return <div>{todo}</div>; } export default Detail;
- React.memo사용 후
더보기
부모 컴포넌트
import React, { useState } from "react"; import Detail from "./Detail"; export default function Home() { const [todo, setTodo] = useState(0); const [something, setSomething] = useState(0); const changeTodo = () => { console.log("running todo..."); setTodo(Math.random() * 100); }; const changeSomething = () => { console.log("running something..."); setSomething((prev) => prev + 1); }; return ( <div> <h1>todo : {todo}</h1> <h1>something : {something}</h1> <button onClick={changeTodo}>change Todo value</button> <button onClick={changeSomething}>change Something value</button> <Detail todo={todo} /> </div> ); }
자식 컴포넌트
import React from "react"; function Detail({ todo }) { console.log("I am child"); return <div>{todo}</div>; } export default React.memo(Detail);
⚠️ React.memo 사용 시 주의할 점은 아래처럼 객체 형태에 customTodo를 Child 컴포넌트에 props로 넘겨주는 경우 React.memo의 기능이 정상적으로 작동하지 않을 수 있습니다. 왜냐하면 React.memo는 원시형 타입(string, number, boolean 등 온전한 값 자체)은 이해할 수 있지만, 참조형 타입(객체, 배열처럼 다양한 타입의 여러 개의 값이 하나로 묶여 저장되어 있는 주소를 참고하는 형태)은 주소값의 변화를 감지하고 있기 때문에 관련 없는 state의 변화에도 주소값이 변화되었다고 인식하여 자식 컴포넌트가 렌더링 되는 것입니다.
- React.memo 사용 전
- 👉 그렇기 때문에 useMemo가 참조값 동일성 유지를 해줄 수 있습니다.
아래처럼 주소값을 갖는 customTodo를 useMemo로 감싸주면 todo 값이 변할 때만 커스텀 된 값이 return 되기 때문에 참조값을 동일하게 유지시킬 수 있습니다!
const customTodo = useMemo(() => { return { changedTodo: todo + "개", name: "jJames" }; }, [todo]);
- 참조 동일성 유지를 설명하기 전에 React.memo에 대한 설명이 필요할 것 같습니다.