[내배캠] 프로덕트 디자인 트랙/[내배캠] TIL : Today I Learned

[TIL] D+54 / React입문 / React - state, 컴포넌트 및 랜더링, DOM과 Virtual DOM

rimmy 2025. 1. 6. 14:28

2025_1_6 월요일

+ 코딩 20일차

 

 

 

📝 Lastday I Did
React프로젝트, 컴포넌트와 JSX, props에 대해 배웠고
UX집중반에서 우리팀 문제정의 파트에서 5whys의 질문에 대한 대답에 주관이 섞이진 않았는지 검증이 필요하다는 피드백을 받았다.

 

 

💫 Today I Did

→ React입문주차의 state와 컴포넌트 및 랜더링, DOM과 Virtual DOM에 대해 배웠고,
UX집중반에서 '가설과 솔루션 도출 과정에서의 디자인씽킹'에 대한 발제가 있었다.🙆‍♂️

 

 

 

 


 

 

✅  배운점

 

 

React State

state란?
컴포넌트 내부에서 바뀔 수 있는 값을 의미

useState라는 함수(hook)를 이용

//     ⌌--------객체-------⌍
const [변수, 변수를 변경하는 함수] = useState("초기값");

[ 변수: "르순이",
  변수를 변결하는 함수: function() {}
]

 

 

 


 

 

불변성

불변성은 리액트의 state개념을 이해하기 위해 꼭 필요한 개념

메모리에 저장되는 원리와 밀접한 원리

 

불변성이란?
메모리에 있는 값을 변경할 수 없는 것

 

• 원시 데이터 → 불변성 ⭕️ (= 변경 불가)
 원시 데이터 아닌 것(객체, 배열, 함수) → 불변성 ❌ (= 변경 가능)

 

 

 

 

 

변경 가능한 방법(Mutable way)

numbers에 새로운 요소 '4'를 추가하여 불변성을 깨트린다.

let numbers = [1, 2, 3];
numbers.push(4); // 배열에 직접 요소를 추가
console.log(numbers); // [1, 2, 3, 4]

 

 

 

불변성을 깨뜨리는 것이 문제가 되는 이유

• 예측 불가능한 코드
불변성을 깨트리면 해당 데이터 구조를 다른 부분에서 참조하고 있을때,
예상하지 못한 방식으로 변경되어 버그가 발생할 수 있고,동작을 예측하기 어렵게 한다.

• 버그 추적의 어려움
원본 데이터가 여러 곳에서 변경될 수 있다면,
어떤 부분의 코드가 데이터를 변경했는지 추적하기 어려워 진다.(협업환경에서 문제발생 가능성 증가)

 

 

 

불변성을 유지하는 방법(Immutable way)

let numbers = [1, 2, 3];
let newNumbers = [...numbers, 4]; // 새 배열을 생성하여 기존 배열을 변경하지 않음
console.log(numbers); // [1, 2, 3]
console.log(newNumbers); // [1, 2, 3, 4]

 

 

 

🌟 리액트에서 불변성이 가지는 의의

리액트에서는 화면을 리렌더링 할지 말지 결정할 때 state의 변화를 확인한다.

state가 변했으면 리렌더링 하는 것이고, state가 변하지 않았으면 리렌더링을 하지 않는다.

 

state가 변했는지 변하지 않았는지 확인하는 방법이 state의 변화 전, 후의 메모리 주소를 비교한다.

그래서 만약 리액트에서 원시데이터가 아닌 데이터를 수정할 때 불변성을 지켜주지 않고,

직접 수정을 가하면 값은 바뀌지만 메모리주소는 변함이 없게 되는 것이다.

 

개발자가 값은 바꿨지만 리액트는 state가 변했다고 인지하지 못하게 된다.

그래서 결국 마땅히 일어나야 할 리렌더링이 일어나지 않게된다.

 

 

 


 

 

 

Component

→ 컴포넌트는 리액트의 핵심 빌딩 블록
리액트에서 개발할 모든 애플리케이션은 컴포넌트라는 조각으로 구성된다.
컴포넌트는 UI 구축 작업을 훨씬 쉽게 만들어준다.

 

  • UI 요소를 표현하는 최소한의 단위
  • 화면의 특정 부분이 어떻게 생길지 정하는 선언체

 

DOM(명령형 프로그래밍)

명령형으로 작성된 코드의 경우

예를 들어 Hello, World!를 출력하기 위해 컴퓨터가 수행하는 절차를 일일히 코드로 작성해주어야 합니다.

// Hello, World! 화면에 출력하기
// 순수 javaScript 명령형 코드
const root = document.getElementById('root'); 
const header = document.createElement('h1'); 
const headerContent = document.createTextNode(
	'Hello, World!'
);

header.appendChild(headerContent); 
root.appendChild(header);

 

 

 

리액트(선언형 프로그래밍)

React 코드의 경우 내가 UI을 선언하고 render 함수를 호출하면 React가 알아서 절차를 수행해 화면에 출력해준다

→ 화면에 어떻게 그려야할지는 React 내부에 잘 숨겨져 추상화되어 있다.

// React 코드 (선언적인)
const header = <h1>Hello World</h1>; // jsx
ReactDOM.render(header, document.getElementById('root'));

 

 

 

 

 

DOM을 직접 조작하여 명령형 프로그래밍 방식으로 작성하던 코드가 나쁘다는게 🙅‍♂️

예시와 같이 격리된 예제에서는 차라리 리액트와 같은 UI 라이브러리를 사용하지 않고 만드는게 더 빠르고 전체적인 번들 사이즈 측면에서도 더 효율적인 방법일수 있다. 그치만 더 복잡한 UI 시스템에서는 관리하기가 기하급수적으로 어려워집니다.

 

 

 

 

 

 


 

 

 

 

렌더링

→ 컴포넌트가 현재 props와 state의 상태에 기초하여 UI를 어떻게 구성할지 컴포넌트에게 요청하는 작업

 

 

예시)

 

UI 음식
컴포넌트 음식을 만드는 주방장
리액트 웨이터

 

1. 렌더링 일으키는 것은 (triggering)- UI를 주문하고 주방으로 전달하는 것
2. 렌더링한다는 것은 (rendering)- 주방에서 컴포넌트가 UI를 만들고 준비하는 것
3. 렌더링 결과는 실제 DOM에 커밋한다는 것은 (commit) - 리액트가 준비된 UI를 손님(브라우저) 테이블에 올려놓는 것

 

 

 

•  렌더링 트리거

→ 랜더링이 언제 일어나느냐?

  1. 첫 리액트 앱을 실행했을 때
  2. 현재 리액트 내부에 어떤 상태(state)에 변경됐을 때
    • 컴포넌트 내부 state가 변경되었을 때
    • 컴포넌트에 새로운 props가 들어올 때,
    • 상위 부모 컴포넌트에서 위에 두 이유로 렌더링이 발생했을 때

 

리액트 앱이 실행되고 첫 렌더링이 일어나면 리액트는

컴포넌트의 루트에서 시작하여 아래쪽으로 쭉 훑으며 컴포넌트가 반환하는 JSX 결과물을 DOM 요소에 반영

 

 

 

 

•  리렌더링

컴포넌트 상태에 변화가 생기면 리렌더링 발생

이때 여러 상태가 변경됐다면 리액트는 이를 큐 자료구조에 넣어 순서를 관리

 

 

예시) A 컴포넌트가 관리하는 state 30개 중 → 어떤 버튼이 눌렸을 때, 17개의 state가 변경

이를 '큐'라는 자료구조에 넣고, 이 순서를 관리한다.

(이때 17개가 변경되었다고 해서 화면을 17번 깜빡이면서 변경하는 것이 아닌, 한꺼번에 17개의 변경사항을 반영한다.)

 

 

  1. 주방 예시를 다시 들어보면 리렌더링은 음식점 손님이 첫 주문 이후에 갈증이 생겨 추가로 음료를 주문하거나 처음 받은 음식이 마음에 들지 않아 새로운 메뉴를 주문하는 것과 같다.
  2. 새로운 UI주문(리렌더링)이 일어나면 리액트가 변경된 내용을 주방에 있는 요리사인 컴포넌트에 전달하고 컴포넌트는 새로운 변경된 주문을 토대로 새로운 요리(UI)를 만든다.
  3. 새롭게 만들어진 요리(렌러딩 결과)는 리액트에 의해 다시 손님 테이블에 올려진다.(DOM에 반영 - commit phase).

 

 

 

 

 

 

•  브라우저 렌더링 (≠ 리액트 렌더링)

→ 브라우저 렌더링(=페인팅)렌더링이 완료되고 React가 DOM을 업데이트한 후 브라우저는 화면을 그리는 프로세스

 

 

 

 

 

 

 


 

 

 

 

 

DOM과 Virtual DOM

리액트(react.js)나, 뷰(Vue.js)는 가상돔(Virtual DOM)을 사용해서 원하는 화면을 브라우저에 그려준다.
자체적으로 상당히 효율적인 알고리즘을 사용해서 그려주기 때문에 그 속도가 어마어마 하다.
가상돔을 사용하지 않는다면, 일반 돔(DOM)을 사용하면 매우 비효율적이다.

 

 

• DOM (Document Object Model)

브라우저를 돌아다니다 보면, 수 많은 컴포넌트로 구성된 웹페이지들을 보게 된다.

페이지를 문서(document)라고 하구요. 페이지를 이루는 컴포넌트를 엘리먼트(element)라고 해요.

문서(document)를 오브젝트로 보는 모델 → DOM

 

 

→ DOM은 이 엘리먼트를 tree 형태(= DOM TREE)로 표현

트리의 요소 하나하나를 ‘노드’라고 부르구요. 각각의 ‘노드’는 해당 노드에 접근과 제어(DOM 조작)를 할 수 있는 API를 제공해

(API = DOM을 조작할 수 있는 방법. 단순히 HTML 요소에 접근해서 수정할 수 있는 함수 정도로 이해)

왼쪽 : 문서(document), 오른쪽 : tree형태(=DOM TREE)

 

 

대표적인 DOM API의 사용 예시

// id가 demo인 녀석을 찾아, 'Hello World!'를 대입해줘.
document.getElementById("demo").innerHTML = "Hello World!";

// p 태그들을 모두 가져와서 element 변수에 저장해줘
const element = document.getElementsByTagName("p");

// 클래스 이름이 intro인 모든 요소를 가져와서 x 변수에 저장해줘
const x = document.getElementsByClassName("intro");

 

 

 

 

• 가상DOM(Virtual DOM)

DOM과 비슷하지만, 실제로는 없는 가상의 DOM

 

→ 리액트는 가상DOM을 이용해서 실제DOM을 변경하는 작업을 상당히 효율적으로 수행

실제 DOM은 아니지만, 객체(object) 형태로 메모리에 저장되기 때문에 실제 DOM을 조작하는 것 보다 훨씬 더 빠르게 조작을 수행

 

 

가상DOM은 실제 DOM과 구조가 완벽히 동일한 복사본 형태

 

 

 

 

<DOM 조작 과정>

 

[STEP 1]

이 과정에서 리액트는 항상 [2가지 버전의 가상DOM]을 가지고 있다.

1. 화면이 갱신되기 전 구조가 담겨있는 가상DOM 객체
2. 화면 갱신 후 보여야 할 가상 DOM 객체

리액트는 state가 변경돼야만 리렌더링. 그 때, 바로 2번에 해당되는 가상 DOM을 만든다.

 

[STEP 2 : diffing]

state가 변경되면 2번에서 생성된 가상돔과 1번에서 이미 갖고있었던 가상돔을 비교해서

어느 부분(엘리먼트)에서 변화가 일어났는지를 상당히 빠르게 파악

 

[STEP 3 : 재조정(reconciliation)]

파악이 다 끝나면, 변경이 일어난 그 부분만 실제 DOM에 적용

적용시킬 때는, 한건 한건 적용시키는 것이 아니라, 변경사항을 모두 모아 한 번만 적용을 시킨다 (Batch Update 🔥)

 

 

 

Batch Update

리액트가 state를 batch 방법으로 update 한다는 것은 변경된 모든 엘리먼트를 한꺼번에 반영할 수 있는 방법을 의미

 

<클릭 한 번으로 화면에 있는 5개의 엘리먼트가 바뀌어야 한다면>

  • 실제 DOM : 5번의 화면 갱신 필요
  • 가상 DOM : Batch Update로 인해 단 한번만 갱신 필요

 

 

 

 

 가상DOM(Virtual DOM)

  1. 가상 DOM 생성 : React는 컴포넌트의 렌더 함수를 실행하여 가상 DOM을 새로 생성
  2. 차이 비교 (Diffing) : 새로운 가상 DOM과 이전 가상 DOM을 비교하여 실제 DOM을 업데이트해야 할 부분을 찾는다.
  3. 배치 업데이트 (Reconciliation) : 변경된 부분만을 효율적으로 실제 DOM에 반영

 

React가 실제 DOM을 업데이트하면, 이 변경 사항은 브라우저의 렌더링 엔진에 의해 화면에 반영

  1. 스타일 계산 : HTML과 CSS를 분석하여 각 요소의 스타일을 계산
  2. 레이아웃 : 각 요소의 위치와 크기를 계산
  3. 페인팅 : 레이아웃 단계에서 계산된 정보를 바탕으로 요소를 화면에 그린다.

 

 

 

 

 

 

 

 

 


 

📊 UX집중반

 

지난주 문제정의 파트에 이어 이번주는 '가설과 솔루션 도출 과정에서의 디자인씽킹'에 대한 발제가 있었다.

 

그전에 문제정의 파트에서 지난주 피드백에 대해 반영해보려고 했는데, 쉽지 않았다.

카카오 페이 자체에 문제가 정말 없어서 자꾸 근본적인 문제에 도달했던것 때문에 지난주에 정말 고생했었는데,

피드백을 반영해서 고쳐보려니까 자꾸만 다시 문제가 커지고 다시 돌아오고, 다시 커지고의 반복이였다....

그래서 어차피 시간적인 여유가 없기 때문에 아에 우리가 정의한 문제에서 완전히 벗어날 수 없으니, 문제 정의파트를 조금 손보더라도

정의한 문제가 틀을 벗어나지 않을 것이다.라는 팀원들과의 이야기를 나눈 후 일단은 솔루션 단계에 진입해서 하다가

마지막 장표제작할때쯤 다시 문제정의파트를 손보는게 더 방향성이 확실해서 그나마 수월할 것같아 그렇게 진행하기로했다

 

 


 

 

 

⭐️  느낀점

UX집중반 디자인 집중반이 너무 힘들다

카카오페이의 문제점은 아무리 찾아도 없고, 겨우겨우 찾은 문제를 연결점을 찾아 연결하는 것이 너무 어렵다.

그래서 진행중에 하나 탈이 나면 다시 겨우겨우 찾은 문제에서 또 벗어나서 또 우리가 해결할 수 없는 선을 넘어가 버리기 일쑤다.

그래서 자꾸 어떤 식으로 생각을 해야할지 모르겠고, 머리가 딱딱하게 굳어지는 것 같다.

이제 카카오 페이만 봐도 너무 머리가 아프다...🙄