2024/05/30 memoization

YIS·2024년 5월 30일
post-thumbnail

memoization?

리액트에서는 리랜더링이 불필요하게 발생하는 경우를 줄여 성능 최적화를 하기 위해
useMemo와 useCallback이라는 훅을 사용
계산결과를 저장후 동일한 반복을 해야할 경우 저장된결과를 재사용하는 개념
컴포넌트를 캐싱한다 = memo
값을 캐싱한다 = useMemo
함수를 캐싱한다 = useCallback
다만 이것 또한 값을 저장하는 행위이기때문에 너무빈번한 사용도 금물.


memo

컴포넌트 자체를 메모이제이션하여
동일한 props가 전달될 때 불필요한 재렌더링을 방지

import { memo } from "react";
const MyComponent = memo((props) => {
  // 로직
});

useMemo

특정 계산의 결과를 메모이제이션하여
의존성 배열에 포함된 값들이 변경되지 않는 한 해당 계산을 다시 수행하지 않도록 함

import { useMemo } from "react";
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

useCallback

함수를 메모이제이션하여
의존성 배열에 포함된 값들이 변경되지 않는 한 해당 함수를 다시 생성하지 않도록 함

import { useCallback } from "react";
const memoizedCallback = useCallback(() => {
  doSomething(a, b);
}, [a, b]);



과제로 보는 사용예시

ex) memo

//App.jsx
import React, { useMemo } from "react";
import List from "./components/List";

const App = () => {
  const [input, setInput] = useState("");
  const [items, setItems] = useState(["Item 1", "Item 2", "Item 3"]);

  const handleInputChange = (event) => {
    setInput(event.target.value);
  };

  const addItem = () => {
    setItems((prevItems) => [...prevItems, input]);
    setInput("");
  };

  return (
    <div>
      <h1>Item List</h1>
      <input type="text" value={input} onChange={handleInputChange} />
      <button onClick={addItem}>Add Item</button>
      <List items={items} />
    </div>
  );
};

export default App;

인풋에 값을 입력할때마다 handleInputChange가 입력필드값을 계속 업뎃하고
버튼을 클릭시 addItem 함수가 실행, items의 값을 펼친후
input으로 들어온 값을 합침.
그 변경된 items을 List로 내려주고 items을 map으로 순환후
출력하는 로직임


//List.jsx
import { memo } from "react";

const List = memo(({ items }) => {
  console.log("List component rendered");
  return (
    <ul>
      {items.map((item, index) => (
        <li key={index}>{item}</li>
      ))}
    </ul>
  );
});

export default List;

input이 제어컴포넌트로 연결되있어서 인풋값이 입력될때마다 리랜더링이 일어나고있음
그결과 자식컴포넌트인 List.jsx도 같이 리랜더링됨.
그래서 불필요한 리랜더링을 줄이기위해 items가 변경 될때만
List.jsx컴포넌트가 랜더링 될수 있게 memo를 사용.


ex) useMemo

//App.jsx
import React, { useState, useMemo } from "react";
import List from "./components/List";

const App = () => {
  const [input, setInput] = useState("");
  const [items, setItems] = useState(["Item 1", "Item 2", "Item 3"]);

  const handleInputChange = (event) => {
    setInput(event.target.value);
  };

  const addItem = () => {
    setItems((prevItems) => [...prevItems, input]);
    setInput("");
  };

  const filteredItems = useMemo(() => {
    return items.filter((item) => item.toLocaleLowerCase().includes("item"));
  }, [items]);

  return (
    <div>
      <h1>Item List</h1>
      <input type="text" value={input} onChange={handleInputChange} />
      <button onClick={addItem}>Add Item</button>
      <List items={filteredItems} />
    </div>
  );
};

export default App;
//List.jsx
import { memo } from "react";

const List = ({ items }) => {
  console.log("List component rendered");
  return (
    <ul>
      {items.map((item, index) => (
        <li key={index}>{item}</li>
      ))}
    </ul>
  );
};

export default memo(List);

memo예시에서 fliteredItem함수가 추가되고 List에 내려주는 값이 fliteredItem로만 바뀜.
fliteredItem에서 useMemo가 없다면 똑같이 인풋값에 값이 입력될때마다
리랜더링이 되고 그때마다 List.jsx도 리랜더링됨.

값을 필터링하고 넘겨주는 filteredItems에 useMemo를 사용
의존성배열에 items를 지정,
items을 순회하면서 필터링하는 로직을 캐싱후
의존성배열에 할당한 items가 변경될때만 다시 계산되고
의존성배열이 변경되지않으면 캐싱했던 값을 재사용하면서 리랜더링을 막음.

ex) useCallback

//App.jsx
import { useEffect, useState, useCallback } from "react";
import List from "./components/List";

const App = () => {
  const [input, setInput] = useState("");
  const [items, setItems] = useState(["Item 1", "Item 2", "Item 3"]);

  const handleInputChange = (event) => {
    setInput(event.target.value);
  };

  const addItem = useCallback(() => {
    setItems((prevItems) => [...prevItems, input]);
  }, [input]);

  useEffect(() => {
    console.log("Add Item 버튼 클릭 시에는 로그가 찍히지 않아야 합니다!");
  }, [addItem]);

  return (
    <div>
      <h1>Item List</h1>
      <input type="text" value={input} onChange={handleInputChange} />
      <button onClick={addItem}>Add Item</button>
      <List items={items} />
    </div>
  );
};

export default App;
//List.jsx
import { memo } from "react";

const List = ({ items }) => {
  console.log("List component rendered");
  return (
    <ul>
      {items.map((item, index) => (
        <li key={index}>{item}</li>
      ))}
    </ul>
  );
};

export default memo(List);

위의 useMemo 예시에서 filteredItem 대신 useEffect 가 추가됨.
useEffect도 마찬가지로 의존성배열(addItem)이 변경될때만 안에 로직을 실행함.

버튼클릭을 할 때마다 addItem함수가 실행되고
setItems상태업뎃을 통해 계속 새로운 배열을 반환함.
useCallback을 사용해서 의존성배열에 input값을 넣음으로써
input값이 변할때만 addItem 함수가 새로 생성되고, 그렇지않으면
이전에 생성된 함수를 사용.
addItem 함수가 변경되지 않는 한, useEffect 훅이 실행되지 않기 때문에
콘솔값이 찍히지않음.

profile
엉덩이가 무거운 사람

0개의 댓글