본문 바로가기
더조은컴퓨터아카데미 리액트 주말반

리액트 주말반 세번째 수업

by 오렌지코딩 2025. 7. 19.

 

function App() {
  const [mode, setMode] = useState("WELCOME");
  const [id, setId] = useState(null);
  const [nextId, setNextId] = useState(4);
  const [topics, setTopics] = useState([
    { id: 1, title: "html", body: "html is ..." },
    { id: 2, title: "css", body: "css is ..." },
    { id: 3, title: "javascript", body: "javascript is ..." },
  ]);

  let content = null;

  if (mode === "WELCOME") {
    content = <Article title="Welcome" body="Hello, Web"></Article>;
  } else if (mode === "READ") {
    let title,
      body = null;
    for (let i = 0; i < topics.length; i++) {
      console.log(topics[i].id, id);
      if (topics[i].id === id) {
        title = topics[i].title;
        body = topics[i].body;
      }
    }
    content = <Article title={title} body={body}></Article>;
  } else if (mode === "CREATE") {
    content = (
      <Create
        onCreate={(_title, _body) => {
          const newTopic = { id: nextId, title: _title, body: _body };

          const newTopics = [...topics];

          newTopics.push(newTopic);
          //alert(newTopics[0].title);
          setTopics(newTopics);
          setMode("READ");
          setId(nextId);
          setNextId(nextId + 1);
        }}
      ></Create>
    );
  }

  return (
    <div className="App">
      <Header
        title="WEB"
        onChangeMode={() => {
          setMode("WELCOME");
        }}
      ></Header>
      <Nav
        topics={topics}
        onChangeMode={(_id) => {
          setMode("READ");
          setId(_id);
        }}
      ></Nav>
      {content}

      <a
        href="/create"
        onClick={(event) => {
          event.preventDefault();
          setMode("CREATE");
        }}
      >
        Create
      </a>
    </div>
  );
}

 

const newTopics = [...topics];

[...topics] 이렇게 사용하는 이유가 무엇일까??

 

const newTopics = [...topics];에서 [...]는 **전개 연산자 (spread operator)**입니다.

이걸 사용하는 이유는 기존 상태인 topics 배열을 직접 수정하지 않고, 복사본을 만들어서 수정하기 위해서입니다.

 

왜 직접 수정하면 안 될까?

React에서는 상태(state)를 직접 변경하면 컴포넌트가 제대로 리렌더링되지 않을 수 있어. 예를 들어:

topics.push(newTopic); // ❌ 직접 상태 변경
setTopics(topics); // 이건 React가 변경을 감지 못할 수도 있음

위처럼 원본을 직접 바꾸면, React는 “상태가 변하지 않았네?” 하고 넘어갈 수 있어.

 

그래서 복사해서 쓰는 방식이 필요한 거야

const newTopics = [...topics]; // ✅ 새로운 배열을 복사해서 만들고
newTopics.push(newTopic);      // 새 항목 추가
setTopics(newTopics);          // setTopics로 새로운 배열을 상태로 설정

이렇게 하면 React가 “아, 상태가 진짜 바뀌었구나!” 하고 인식하고 리렌더링해줘.

 

요약

  • [...]는 배열을 **얕은 복사(shallow copy)**해서 기존 상태를 보존하면서 새로운 배열을 만드는 방법
  • React에서 상태는 절대 직접 수정하지 말고, 항상 복사 → 수정 → setState 패턴으로 처리해야 해

 

 

그렇다면 왜 리액트는 상태를 직접변경하면 리렌더링이 되지 않을 수 있는걸까??

React가 상태를 직접 변경하면 리렌더링되지 않을 수 있는 이유는 React의 렌더링 방식불변성(immutability) 원칙에 있어.

 

🔄 1. React는 상태 변경을 비교해서 감지해

React는 컴포넌트가 setState() (혹은 useState의 setter)로 상태가 변경되었는지를 판단할 때, 단순히 **"참조가 바뀌었는지"**를 기준으로 판단해.

const [topics, setTopics] = useState([...]);

// ❌ 이 경우 React는 topics가 '같다'고 판단
topics.push(newTopic);
setTopics(topics);  // 원래 참조 그대로니까 바뀐 줄 모름

JS 객체(배열 포함)는 메모리 주소(참조)를 비교해서 "같은 객체인지" 판단해.

 

✅ 2. 불변성을 지키면 참조가 달라져서 React가 "아! 바뀌었구나!" 하고 알아차림

const newTopics = [...topics]; // 참조가 바뀜
newTopics.push(newTopic);
setTopics(newTopics); // ✅ React가 바뀐 걸 감지함!

이렇게 새로운 객체를 만들어 넘기면, React는 setState 이후 상태가 "달라졌다"고 인식하고 해당 컴포넌트를 리렌더링해줘.

 

📌 요약

항목직접 변경복사 후 변경
참조 동일
React 리렌더링 ❌ 안 될 수 있음 ✅ 제대로 동작함
 

🔁 React는 왜 이렇게 작동할까?

  • React는 성능을 위해 **얕은 비교(shallow comparison)**를 사용함
  • 깊은 비교(deep equality)는 느림 → 모든 값을 비교하면 앱이 느려져
  • 그래서 객체 참조가 바뀌었는지만 보고 변경 여부를 판단함

 

🔧 참고: useState 내부에서 무슨 일이 일어날까?

if (Object.is(oldState, newState)) {
  // 상태가 같다고 보고 리렌더링 생략
}

 

 

topics와 newTopics의 메모리 주소가 서로 다르다는걸 그림을 사용해서 살펴보아요.

topics와 newTopics는 **서로 다른 메모리 주소를 가지는 객체(배열)**입니디ㅏ.
그걸 이해하기 위해 메모리 구조를 간단한 그림으로 표현해봅시다.

 

📦 상황: 상태를 직접 수정하는 경우

const [topics, setTopics] = useState([{ id: 1, title: "html" }]);

topics.push({ id: 2, title: "css" }); // ❌ 직접 변경
setTopics(topics); // 같은 참조를 다시 넣음

메모리 구조 (직접 수정)

topics  ─────┐
             ▼
         [ { id: 1, title: "html" },
           { id: 2, title: "css" } ]  ← 메모리 주소: 0x1234

 

  • topics는 주소 0x1234를 가리킴
  • setTopics(topics)도 여전히 같은 주소 0x1234
  • React는 주소가 같으니까 "아~ 상태가 안 바뀌었네" 하고 리렌더링 안 함

 

✅ 상황: 복사해서 새로운 상태로 설정하는 경우

 

topics     ─────┐                     newTopics ─────┐
                ▼                                   ▼
         [ { id: 1, title: "html" } ]       [ { id: 1, title: "html" },
               ↑                                  { id: 2, title: "css" } ]
      주소: 0x1234                           주소: 0x5678

 

  • topics: 주소 0x1234
  • newTopics: 주소 0x5678 (새 배열)
  • setTopics(newTopics) → React는 주소가 바뀌었으니 "상태 변경됨!"이라고 판단하고 리렌더링함

 

🧠 정리

비교 대상참조가 같음?React 리렌더링?
setTopics(topics) ✅ 같음 ❌ 안 함
setTopics([...topics]) ❌ 다름 ✅ 함

 

그림처럼 주소가 달라야 React가 "변했네!" 하고 감지 할 수 있습니다.
이게 바로 **불변성(immutability)**을 유지해야 하는 핵심 이유입니다.

 

 

 

출처: 생활코딩 리액트프로그래밍, chatgpt