BEF 렌더링 문제 (props 함수 + 메모제이션)

이명진·2026년 4월 22일

TIL

목록 보기
23/27

BEF 에서 리액트 렌더링 렌더링 문제를 요즘 풀고있는데 내가 생각한것보다 달라서
많이 배우고 있다. 문제형식은 콘솔이 어떻게 찍히는지 제출하면 되는 문제이다.

해당 문제

import * as React from 'react'
import { memo, useState} from 'react'
import { createRoot } from 'react-dom/client'
import { screen, fireEvent } from '@testing-library/dom'

function _A({ onClick }) {
  console.log('A')
  return <button onClick={onClick} data-testid="button">click me</button>
}

const A = memo(_A)

function App() {
  console.log('App')
  const [state, setState] = useState(0)
  return <div>
    {state}
    <A onClick={() => {setState(state => state + 1)}}/>
  </div>
}

const root = createRoot(document.getElementById('root'));
root.render(<App/>);

// click the button
;(async function() {
  const button = await screen.findByTestId('button')
  fireEvent.click(button)
})()

문제를 읽었을때
맨 처음에는 렌더링 이 되어 야 하니 App과 A가 차례로 찍히고

const A = memo(_A) 를 사용하기 때문에

메모제이션이 되고 바뀐게 없기 때문에

App만 렌더링 되는줄알았는데

답은 A도 한번 더 돈다 였다.

내가 예상했던 답
“App”
“A”
“App”

하지만 최종 답은 아래와 같다.
“App”
“A”
“App”
“A”

이유는 메모를 사용하면 바뀐게 없다면 리렌더링을 하지 않는게 맞지만
_A 컴포넌트에서는 props로 onClick 함수를 받게 되는데
App 함수에서 리랜더링 되면서 props로 넘겨주는 onClick함수가 같은 내용이지만 참조값이 변경되면서 새로운 함수라고 여기게 되어 _A 가 리렌더링 되는 것이었다.

이 이슈를 해소하기 위해서는 Props로 넘겨주는 함수를 메모제이션 하면 되는 해결 방법이 있다.

function _A({ onClick }) {
 console.log('A');
 return <button onClick={onClick} data-testid="button">click me</button>;
}

const A = memo(_A);

export default function App() {
 console.log('App');
 const [state, setState] = useState(0);

 const handleClick = useCallback(() => {
   setState((state) => state + 1);
 }, []);

 return (
   <div>
     {state}
     <A onClick={handleClick} />
   </div>
 );
}

이렇게 된다면 handleClick 함수가 메모제이션되어서 리렌더링 되더라도 변경되지 않아서 _A 컴포넌트는 리렌더링 되지 않을 것이다.

이와 관련하여서 한 문제 풀이 사용자가 잘 정리해둔 내용이 있어서 여기에 첨부해둔다. (mKHpzQp 라는 사용자가 쓴 내용이다)

React uses shallow comparison to detect changes in props and state

Shallow comparison refers to the process of comparing the first layer of properties of two objects to determine if they are equal. In other words, it looks at the top-level properties of both objects without considering nested properties or structures within them.

This explains why "every time App is rendered, onClick corresponds to a new reference value for the function".

When App is rendered, onClick is given a new reference value because the function is redefined within the component during each render cycle. This results in a new function reference being created, even if the function's contents remain the same.

Since a shallow comparison only checks the top-level properties and their references, it would consider the onClick functions in consecutive renders as different, even though their actual behavior is unchanged.

해석 본

얕은 비교(Shallow comparison)는 두 객체가 같은지 판단하기 위해 객체의 가장 바깥쪽(첫 번째 단계)의 속성들만 비교하는 과정을 말한다. 다시 말해, 객체 내부에 있는 중첩된 구조나 깊은 속성들은 고려하지 않고, 최상위 속성들만 확인한다.

이것은 “App이 렌더링될 때마다 onClick이 새로운 함수 참조값을 갖게 되는 이유”를 설명해준다.

App이 렌더링될 때마다 컴포넌트 내부에서 해당 함수가 다시 정의되기 때문에 onClick은 새로운 참조값을 가지게 된다. 즉, 함수의 내용은 동일하더라도 매 렌더링마다 새로운 함수 객체가 생성되기 때문에 새로운 참조가 만들어진다.

얕은 비교는 최상위 속성과 그 참조값만 비교하기 때문에, 실제 동작은 변하지 않았더라도 연속된 렌더링에서의 onClick 함수들을 서로 다른 것으로 판단하게 된다.

얕은 비교.. 객체에서 얕은 복사, 깊은 복사를 사용하면서 많이 들은 내용인데 이게 렌더링에도 적용된다는 사실에 매우 놀랍고 신기 하다.

알것 같으면서도 어려운 리액트 렌더링을 문제를 풀면서 공부하고 있는데 많이 어렵다..

profile
프론트엔드 개발자 초보에서 고수까지!

0개의 댓글