채널톡 아이콘

인프런 커뮤니티 질문&답변

kwon님의 프로필 이미지

작성한 질문수 3

한 입 크기로 잘라 먹는 리액트(React.js) : 기초부터 실전까지

10.4) useCallback과 함수 재생성 방지

Editor 컴포넌트 리렌더링

해결된 질문

작성

·

50

0

아래 질문들을 보던 중 Editor 부분이 리렌더링 된다는 글을 보고 제 화면도 확인한 결과 리렌더링 된 걸 확인해서 Editor.jsx 또한 아래와 같이 memo메서드를 감싸줬더니 리렌더링 되지 않는 것을 확인했습니다.

import "./Editor.css";
import { useState, useRef, memo } from "react";

const Editor = ({ onCreate }) => {
  const [content, setContent] = useState("");
  const contentRef = useRef();

  const onChangeContent = (e) => {
    setContent(e.target.value);
  };

  const onKeyDown = (e) => {
    if (e.keyCode === 13) {
      onSubmit();
    }
  };

  const onSubmit = () => {
    if (content.trim() === "") {
      contentRef.current.focus();
      return;
    }
    onCreate(content.trim());
    setContent("");
  };

  return (
    <div className="Editor">
      <input
        ref={contentRef}
        value={content}
        onKeyDown={onKeyDown}
        onChange={onChangeContent}
        placeholder="새로운 Todo..."
      />
      <button onClick={onSubmit}>추가</button>
    </div>
  );
};

export default memo(Editor);

 

Editor.jsx의 props가 onCreate 메서드인데 이를 App.jsx에서 useCallback 처리를 해줘서

그냥 export default memo(Editor); 을 해줘도 리렌더링 방지가 가능했던 것인지 궁금해서 질문 남깁니다!

 

App.jsx 코드입니다!

import "./App.css";
import { useState, useRef, useReducer, useCallback } from "react";
import Header from "./components/Header";
import Editor from "./components/Editor";
import List from "./components/List";

const mockData = [
  {
    id: 0,
    isDone: false,
    content: "운동하기",
    date: new Date().getTime(),
  },
  {
    id: 1,
    isDone: false,
    content: "풋살하기",
    date: new Date().getTime(),
  },
  {
    id: 2,
    isDone: false,
    content: "게임하기",
    date: new Date().getTime(),
  },
];

function reducer(state, action) {
  switch (action.type) {
    case "CREATE":
      return [action.data, ...state];
    case "UPDATE":
      return state.map((item) =>
        item.id === action.targetId
          ? { ...item, isDone: !item.isDone }
          : item
      );
    case "DELETE":
      return state.filter((item) => item.id !== action.targetId);
    default:
      return state;
  }
}

function App() {
  const [todos, dispatch] = useReducer(reducer, mockData);
  const idRef = useRef(3);

  const onCreate = useCallback((content) => {
    dispatch({
      type: "CREATE",
      data: {
        id: idRef.current++,
        isDone: false,
        content: content,
        date: new Date().getTime(),
      },
    });
  }, []);

  const onUpdate = useCallback((targetId) => {
    dispatch({
      type: "UPDATE",
      targetId: targetId,
    });
  }, []);

  const onDelete = useCallback((targetId) => {
    dispatch({
      type: "DELETE",
      targetId: targetId,
    });
  }, []);

  return (
    <div className="App">
      <Header />
      <Editor onCreate={onCreate} />
      <List todos={todos} onUpdate={onUpdate} onDelete={onDelete} />
    </div>
  );
}

export default App;

답변 2

1

이정환 Winterlood님의 프로필 이미지
이정환 Winterlood
지식공유자

안녕하세요 이정환입니다.

네 맞습니다. 상황을 구체적으로 설명드리면 다음과 같습니다.

  • Editor 컴포넌트는 아무런 최적화도 되어 있지 않은 상황이기에 부모 컴포넌트인 App 컴포넌트가 리렌더링 되면 동시에 리렌더링 됩니다.

  • 위 이야기는 바꿔 말하면 새로운 Todo가 추가되거나, 기존 Todo가 수정되어 App 컴포넌트의 State가 변경될 경우 Editor 컴포넌트도 리렌더링 된다는 말 입니다.

  • 이때 Editor 컴포넌트에 React.memo를 적용하면 더 이상 App 컴포넌트의 리렌더링에 Editor가 동시에 리렌더링 되지 않습니다. 그 이유는 다음과 같습니다.

    • React.memo는 Prosp로 제공받는 값이 변경되지 않으면 컴포넌트를 리렌더링 하지 않도록 최적화합니다.

    • 현재 Editor 컴포넌트가 제공받는 onCreate Props는 App 컴포넌트에서 이미 useCallback을 이용해 재생성되지 않도록 최적화 해 두었습니다.

    • 결론적으로 App 컴포넌트의 State가 수정되어도 Editor 컴포넌트에게 제공되는 Props의 값은 변경되지 않습니다.

그런데 여기서 한가지 주의해야 할 점은 바로 과도한 최적화는 오히려 독이 될 수도 있다는 사실입니다.

Editor 컴포넌트는 아주 단순한 구성으로 이루어져 있습니다. <input>과 <button>만 존재하죠, 그리고 보통 이렇게 단순하고 가벼운 컴포넌트는 최적화 대상으로 삼지 않습니다.

최적화는 강의에서 말씀드렸듯 꼭 필요한 부분에만 적절히 적용하는걸 권장해드리고 있기 때문에 요런 점도 함께 고려해보시면 좋을 것 같습니다 😃

kwon님의 프로필 이미지
kwon
질문자

감사합니다 강사님:)

0

인프런 AI 인턴님의 프로필 이미지

안녕하세요, 인프런 AI 인턴입니다. kwon님이 질문하신 것처럼 React.memo를 사용하면 컴포넌트가 동일한 props를 받을 경우 리렌더링을 방지할 수 있습니다. memo 함수는 해당 컴포넌트가 받는 props가 변경되지 않는 한, 다시 렌더링하지 않도록 해주기 때문에 컴포넌트의 성능을 최적화하는 데 효과적입니다.

App 컴포넌트의 onCreate 함수를 useCallback을 사용하여 메모이제이션하셨습니다. useCallback은 함수의 의존성 배열이 변경되지 않는 한, 같은 함수 객체를 반환하므로, Editor 컴포넌트에 주입되는 onCreate prop 또한 동일한 참조를 유지하게 됩니다. 이렇게 함으로써 memo로 감싸진 Editor 컴포넌트는 onCreate prop이 변경되지 않았다고 판단하여 불필요한 리렌더링을 피할 수 있습니다.

이와 관련된 추가 설명 또는 사례를 참고하려면, 다음의 유사한 질문과 답변을 확인해볼 수 있습니다:

  1. 일기 추가 시 DiaryEditor 리렌더링 질문

    • 설명: React 컴포넌트가 불필요하게 리렌더링 되는 상황을 방지하는 방법에 대해 설명하고 있습니다. 여기에서도 React.memouseCallback의 조합으로 최적화를 시도했습니다.
  2. Checkbox를 누르는데 왜 Editor가 리렌더링되나요?

    • 설명: 특정 컴포넌트가 리렌더링되는 이유를 분석하고 이를 최적화하는 방법에 대해 논의하고 있습니다. React.memo와 prop 변경에 대한 부분이 주로 다뤄집니다.

위 답변들을 통해 리렌더링 최적화에 대한 다양한 접근 방식과 해결 방법을 참고하실 수 있습니다.

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.