Frontend 18

React 핵심 개념: 복잡한 용어, 명확한 이해

React가 라이브러리인지 프레임워크인지, SPA와 Virtual DOM이 왜 게시판 만들기에 필요한지 용어가 겹쳐 보인다.

근거 · 교안 p85-p94

React 핵심 개념: 복잡한 용어, 명확한 이해 대표 이미지

1장: 루나: 명령형에서 선언형으로, React의 첫인상

솔라의 노트 한구석에는 짧은 문장이 적혀 있었다. 인터넷 강의에서 강사가 강조했던 말이었다.

"React는 UI를 컴포넌트 단위로 만들고 상태 변화에 따라 화면을 다시 그리는 라이브러리다."

솔라는 그 문장을 소리 내어 다시 읽어보았다. 아는 단어들의 조합인데도 뜻이 명확하게 잡히지 않았다. ‘라이브러리’라는 말은 얼마 전까지 ‘프레임워크’와 뭐가 다른지 찾아보느라 머리를 싸매게 했던 단어였다. ‘화면을 다시 그린다’는 표현은 또 어떤가. SPA니, Virtual DOM이니 하는 다른 용어들과 뒤섞여 머릿속을 둥둥 떠다녔다.

결국 솔라는 노트를 들고 거실에 있는 언니, 루나에게 다가갔다. 루나는 소파에 앉아 태블릿으로 무언가를 차분히 읽고 있었다.

“언니, 나 이것 좀 물어봐도 돼?”

루나가 고개를 들자, 솔라는 노트를 내밀며 말했다.

“React가 UI 라이브러리라는데, 이 설명이 너무 복잡하게 느껴져. 화면을 다시 그린다는 게 무슨 뜻인지, 컴포넌트는 또 뭔지… 그냥 간단한 게시판 하나 만들고 싶은데, 왜 이렇게 알아야 할 게 많은 것 같지?”

솔라의 목소리에는 약간의 짜증이 섞여 있었다. 마치 여러 개의 실타래가 엉켜버렸는데 어디서부터 풀어야 할지 막막한 심정이었다.

“어쩌면 React는 그냥 이것저것 다 해주는 만능 도구 같은 거고, 다른 용어들은 그냥 비슷비슷한 기능을 어렵게 부르는 말일 뿐일까?”

루나는 솔라의 노트를 잠시 들여다보더니, 태블릿 화면을 톡톡 두드려 무언가를 띄웠다. 단순한 게시판의 게시글 하나처럼 보이는 화면이었다. 거기에는 ‘좋아요 ❤️ 0’ 이라고 쓰인 작은 버튼이 있었다.

“솔라, 저기 ‘좋아요’ 숫자를 1로 바꿔야 한다고 생각해 봐. 네가 웹 브라우저에게 직접 명령을 내린다면, 어떤 순서로 지시하겠어?”

갑작스러운 질문이었지만, 솔라는 잠시 고민하더니 입을 열었다.

“음… 일단 사용자가 ‘좋아요’ 버튼을 누르는 걸 알아채야지. 그리고 ‘좋아요’ 숫자가 표시된 부분을 화면에서 찾아야 해. 아마 id="like-count" 같은 이름이 붙어 있겠지? 그런 다음, 그 부분의 내용물을 ‘0’에서 ‘1’로 바꿔치기하라고 명령할 거야.”

“바로 그거야.”

루나가 말했다.

“지금 너는 브라우저에게 ‘어떻게’ 할지를 하나부터 열까지 세세하게 지시했어. ‘이걸 찾고, 저걸 찾아서, 내용을 이걸로 바꿔.’ 마치 요리사에게 재료 손질법부터 불 조절까지 일일이 알려주는 것과 같아. 이런 방식을 **명령형(Imperative)**이라고 불러.”

루나는 말을 잠시 끊고, 솔라가 자신의 설명을 곱씹어볼 시간을 주었다. 명령을 내린다. 직접적이고 명확했지만, 왠지 번거롭게 느껴졌다.

“그럼 React는 다른가?” 솔라가 물었다.

“응. React는 다르게 생각하도록 우리를 유도해.”

루나는 손가락으로 ‘좋아요 ❤️ 0’ 부분을 가리켰다.

“이번엔 이렇게 생각해보는 거야. 우리는 ‘어떻게’ 바꿀지에 대해선 전혀 신경 쓰지 않아. 대신 우리가 원하는 최종 ‘결과물’의 모습, 즉 ‘상태’가 어때야 하는지만 선언하는 거지.”

“상태?”

“응. 지금 ‘좋아요 개수’라는 데이터는 0이야. 이걸 ‘좋아요 개수는 1이어야 한다’라고 선언하는 거야. 그럼 브라우저는 그 선언을 보고 알아서 화면을 ‘좋아요 ❤️ 1’로 바꿔주는 거지. 우리는 그 과정에 개입하지 않아. 단지 ‘좋아요 개수가 n개일 때, 화면은 이런 모습이어야 한다’라고 설계도만 그려주는 거야. 이게 바로 선언형(Declarative) 방식이야.”

솔라의 눈이 동그래졌다. 머릿속에 흩어져 있던 퍼즐 조각 하나가 제자리를 찾은 듯한 느낌이었다.

“아! 그럼 아까 그 문장에서 ‘상태 변화에 따라 화면을 다시 그린다’는 게 바로 그 뜻이었구나! 내가 일일이 ‘이 글자 지우고 저 글자 써!’라고 명령하는 게 아니라, 그냥 ‘좋아요 개수는 이제 1이야’라고 데이터만 선언하면, React가 그 변화를 알아채고 화면을 약속된 모습으로 바꿔준다는 거네.”

“맞아. 개발자는 UI가 어떻게 변해야 하는지의 ‘과정’이 아니라, 특정 데이터(상태)에서 UI가 어떤 모습이어야 하는지의 ‘결과’에만 집중하면 돼. 훨씬 간단하지.”

솔라는 고개를 끄덕였다. ‘화면을 다시 그린다’는 말이 더 이상 파괴적이고 비효율적인 작업으로 들리지 않았다. 오히려 똑똑하고 자동화된 과정처럼 느껴졌다. 명령형 방식이 수동으로 톱니바퀴를 하나하나 돌리는 것이라면, 선언형 방식은 원하는 숫자에 다이얼을 맞춰놓기만 하면 기계가 알아서 굴러가는 것과 같았다.

복잡하게 얽혀 있던 실타래 중 가장 바깥쪽 매듭 하나가 스르르 풀렸다. 하지만 솔라는 금세 새로운 궁금증에 사로잡혔다.

“선언형 방식이 편하다는 건 이제 알겠어. 그런데 ‘좋아요’ 버튼 하나는 정말 간단하잖아. 내가 만들고 싶은 게시판은 글 목록도 있고, 글마다 댓글도 여러 개 달리고, 작성자 정보도 보여야 하는데… 이렇게 복잡한 화면 전체를 어떻게 하나의 ‘상태’로 선언할 수 있지? 아까 그 정의에 있던 ‘컴포넌트 단위로 UI를 만든다’는 말이랑 관련 있는 건가?“

2장: 루나: UI 조립의 마법, 컴포넌트의 세계

솔라는 거실 바닥에 큰 스케치북을 펼쳐놓고 골머리를 앓고 있었다. 스케치북에는 연필로 그린 네모 상자들이 가득했다. ‘내가 만들 게시판!’이라고 맨 위에 적혀 있었지만, 그 아래는 복잡하기 짝이 없었다. 큰 네모 안에 여러 개의 가로로 긴 네모들이 줄지어 있었고, 각각의 긴 네모 안에는 또 작은 글씨와 버튼 모양의 그림이 그려져 있었다. ‘글 #1’, ‘글 #2’, ‘글 #3’…

어제 루나와 나눈 대화 덕분에 ‘선언형’이라는 개념은 조금 알 것 같았다. ‘좋아요 개수는 1이다’라고 선언하면 화면이 바뀐다는 아이디어는 분명 매력적이었다. 하지만 솔라가 지금 마주한 문제는 ‘좋아요’ 버튼 하나가 아니었다. 그녀는 글 #2의 ‘좋아요’ 버튼을 눌렀을 때 무슨 일이 벌어질지 상상하며 연필 끝으로 스케치북을 톡톡 건드렸다. 글 #2의 ‘좋아요’ 숫자는 5에서 6으로 바뀌어야 한다. 동시에 글 #1과 글 #3의 ‘좋아요’ 숫자는 그대로여야 한다. 댓글을 추가하면, 그 글 아래에만 새로운 댓글 상자가 생겨야 한다.

“이 전체 화면의 모습을 하나의 ‘상태’로 선언해야 한다면… 너무 복잡한데?”

결국 솔라는 혼잣말을 내뱉었다. 이 모든 글과 댓글, 좋아요 개수를 포함하는 거대한 설계도를 한 번에 관리해야 한다고 생각하니 머리가 아찔했다.

소파에서 이 모습을 지켜보던 루나가 조용히 다가와 솔라의 스케치북 옆에 앉았다. 루나는 복잡하게 얽힌 선들을 잠시 들여다보더니, 스케치북의 깨끗한 여백을 손가락으로 가리켰다.

“거기에 딱 하나만 그려봐. 게시글 목록에 있는, 가로로 긴 네모 하나만.”

솔라는 루나의 말에 따라 게시글 하나를 대표하는 상자를 새로 그렸다.

“자, 이제 그 안에 뭐가 들어있지?” 루나가 물었다.

“음… 작성자 이름, 글 제목, 그리고 ‘좋아요’ 버튼이랑 댓글 몇 개인지 보여주는 텍스트가 있어.”

솔라는 중얼거리며 상자 안에 요소들을 그려 넣었다. 루나는 그 모습을 보다가, 옆에 있던 작은 포스트잇 한 장을 가져와 솔라의 상자 위에 덧붙였다. 그리고는 그 위에 다시 상자를 그리고, 솔라가 말한 요소들을 글씨 대신 괄호로 묶어 적었다.

{작성자}, {제목} 좋아요 ❤️ {좋아요_개수} 댓글 {댓글_개수}개

“이 포스트잇이 게시글 하나를 만드는 ‘설계도’ 혹은 ‘틀’이라고 생각해봐. 일종의 빵틀 같은 거지.”

“빵틀?”

“응. 우리는 빵을 만들 때 빵 하나하나의 모양을 손으로 빚지 않아. 똑같은 모양의 빵을 여러 개 만들고 싶으면, 같은 빵틀에 반죽을 부으면 되잖아. 이 포스트잇이 바로 그런 빵틀이야. 우리는 이 틀을 **컴포넌트(Component)**라고 부를 수 있어.”

루나는 가방에서 다른 색 포스트잇 몇 장을 더 꺼내 솔라 앞에 놓았다.

“자, 여기 게시글 데이터가 세 개 있다고 상상해봐.”

루나는 포스트잇에 각각 다른 데이터를 간단히 적었다.

  • {작성자: 루나, 제목: 오늘 날씨 맑음, 좋아요_개수: 10, 댓글_개수: 2}
  • {작성자: 솔라, 제목: React 공부 시작!, 좋아요_개수: 5, 댓글_개수: 3}
  • {작성자: 루나, 제목: 저녁 메뉴 추천, 좋아요_개수: 22, 댓글_개수: 8}

“이제 아까 만든 ‘게시글 컴포넌트’라는 빵틀에 이 데이터들을 하나씩 넣는다고 생각해봐. 어떤 결과가 나올까?”

솔라는 잠시 눈을 감고 상상했다. 첫 번째 데이터 반죽을 빵틀에 부으면 ‘루나 / 오늘 날씨 맑음 / 좋아요 10 / 댓글 2’라고 쓰인 게시글 하나가 ‘구워져’ 나온다. 두 번째, 세 번째 데이터도 마찬가지. 그렇게 세 개의 서로 다른 내용을 가진, 하지만 똑같은 구조의 게시글이 만들어졌다.

그 순간, 솔라의 머릿속에서 무언가 ‘탁’ 하고 맞아 들어갔다.

“아! 그럼 내가 아까 고민했던 게 해결되네! 내가 만약 두 번째 게시글, 그러니까 내 글의 ‘좋아요’ 버튼을 누르면, 나는 이 거대한 스케치북 전체를 다시 그릴 필요가 없는 거구나!”

솔라는 흥분하며 말을 이었다.

“그냥 두 번째 게시글 컴포넌트에게만 ‘네가 가진 좋아요_개수 데이터를 5에서 6으로 바꿔!’라고 선언하면 되는 거야. 그러면 React가 그 두 번째 컴포넌트만 새로 ‘구워서’ 바꿔치기해주는 거네! 나머지 첫 번째랑 세 번째 컴포넌트는 데이터가 바뀐 게 없으니까 그냥 가만히 있고.”

“바로 그거야.” 루나가 조용히 미소 지었다. “각 컴포넌트는 자기만의 데이터를 가지고, 자기만의 모습을 책임져. 독립적이고, 재사용이 가능하지. 댓글도 마찬가지야. ‘댓글 컴포넌트’라는 빵틀을 하나 만들어두면, 어느 게시글에든 필요할 때마다 데이터만 쏙 넣어서 찍어낼 수 있는 거지.”

솔라는 고개를 끄덕이며 자신의 복잡했던 스케치북을 다시 내려다보았다. 이제는 그저 어지러운 낙서가 아니었다. 재사용 가능한 ‘게시글 컴포넌트’와 ‘댓글 컴포넌트’들의 조합으로 보이기 시작했다. ‘컴포넌트 단위로 UI를 만든다’는 말이 비로소 실감 나게 다가왔다. 복잡한 화면을 잘게 쪼개 관리하는 것, 그것이 바로 효율적인 ‘선언’의 비결이었던 것이다.

한숨 돌린 솔라는 이제 막 구워진 따끈따끈한 컴포넌트들을 머릿속에 그리며 만족스러운 미소를 지었다. 모든 게 명쾌해진 것 같았다. 그런데 문득, 한 가지 근본적인 의문이 떠올랐다.

“그런데 언니, 좀 이상해. 컴포넌트 하나만 다시 그려주는 건 좋은데… 보통 웹사이트에서 링크를 누르거나 뭔가를 제출하면, 화면 전체가 하얗게 변했다가 다시 로딩되잖아. 그런데 React로 만든 앱들은 마치 그런 깜빡임 없이, 원래 있던 페이지 안에서 스르륵 바뀌는 것 같단 말이지. 어떻게 페이지 전체를 새로 불러오지도 않고 일부만 그렇게 감쪽같이 바꿀 수 있는 거야?”

3장: 루나: 페이지 새로고침 없이, SPA의 동적 게시판

솔라는 노트북 앞에 앉아 미간을 찌푸렸다. 화면에는 두 개의 브라우저 탭이 열려 있었다. 하나는 오래된 디자인의 온라인 커뮤니티였고, 다른 하나는 최신 소셜 미디어 피드였다. 방금 전, 솔라는 두 사이트 모두에서 똑같은 행동을 해보았다. 오래된 커뮤니티에서는 ‘글쓰기’ 버튼을 눌러 짧은 글을 남기고 목록으로 돌아왔다. 그러자 화면 전체가 하얗게 번쩍이며 처음부터 페이지를 다시 불러왔고, 스크롤은 맨 위로 돌아가 버렸다. 방금 내가 쓴 글을 찾으려면 다시 스크롤을 내려야 했다.

반면, 소셜 미디어 피드에서는 친구의 게시물에 댓글을 달았다. ‘게시’ 버튼을 누르자, 화면은 깜빡임조차 없이 그대로였다. 그저 내가 방금 쓴 댓글이 스르륵, 원래 있던 댓글 목록의 맨 아래에 자연스럽게 추가될 뿐이었다. 전체 페이지가 새로 로딩되는 일도, 스크롤 위치가 바뀌는 불편함도 없었다. 마치 마법 같았다.

컴포넌트라는 ‘빵틀’ 덕분에 UI를 효율적으로 관리할 수 있다는 건 이해했다. 하지만 이 두 경험의 근본적인 차이는 그것만으로 설명되지 않았다. 어째서 한쪽은 페이지 전체를 갈아엎고, 다른 쪽은 필요한 부분만 감쪽같이 바꿔치기하는 걸까? 솔라의 머릿속에 어제 루나에게 던졌던 마지막 질문이 다시 맴돌았다.

“언니, 이것 좀 봐봐. 너무 다르지 않아?”

솔라가 노트북을 돌려 보여주자, 주방에서 물을 마시고 있던 루나가 다가와 화면을 들여다보았다. 루나는 두 개의 탭에서 일어나는 일을 잠자코 지켜보더니, 근처에 있던 메모지와 펜을 가져왔다.

“네가 방금 겪은 두 가지 경험을 여행에 비유해 볼까?”

루나는 메모지 위에 두 개의 커다란 네모를 그리고, 각각 ‘전통적인 식당’과 ‘현대적인 뷔페’라고 이름 붙였다.

“왼쪽, 오래된 커뮤니티가 ‘전통적인 식당’이야. 네가 메뉴를 주문할 때마다, 예를 들어 ‘게시판 목록 보여줘’, ‘글쓰기 페이지 보여줘’, ‘글 등록했으니 다시 목록 보여줘’ 라고 할 때마다, 주방(서버)에서는 완전히 새로운 상차림(페이지 전체)을 해서 내어주는 거야. 이전에 뭘 먹었는지는 상관없이, 항상 완전한 새 요리가 나오는 거지.”

루나는 ‘전통적인 식당’ 네모 안에 화살표를 그렸다. [손님: 요청] -> [주방: 완전한 새 HTML 페이지 제공] -> [손님 앞 테이블 갈아엎기].

“화면이 하얗게 번쩍이는 건, 기존 테이블을 치우고 새로운 상차림을 통째로 다시 까는 과정인 셈이야. 이런 방식을 MPA(Multi-Page Application), 여러 페이지로 구성된 애플리케이션이라고 불러. 각 페이지가 완전히 독립된 하나의 HTML 문서인 거지.”

솔라는 고개를 끄덕였다. ‘테이블 갈아엎기’라는 비유가 화면 깜빡임의 불쾌함을 정확히 설명해주었다.

“그럼 이쪽은?” 솔라가 소셜 미디어 탭을 가리켰다.

“그건 ‘현대적인 뷔페’야.”

루나는 ‘현대적인 뷔페’라고 적힌 오른쪽 네모를 가리켰다.

“뷔페에 처음 입장할 때, 일단 빈 접시, 수저, 컵 같은 기본 세팅(최소한의 뼈대 HTML)을 한 번 받아. 그 다음부터는 어떻게 하지? 먹고 싶은 음식이 생길 때마다 전체 테이블을 바꿔달라고 하나?”

“아니. 내가 직접 가서 원하는 음식만 내 접시에 담아오지.”

“바로 그거야. 이게 SPA(Single-Page Application), 즉 단일 페이지 애플리케이션의 작동 방식이야. 최초에 서버로부터 텅 빈 뼈대와 같은 페이지를 딱 한 번만 받아와. 그 후에는 사용자가 댓글을 달거나, ‘좋아요’를 누르는 등의 행동을 하면, 전체 페이지를 다시 달라고 서버에 요청하는 게 아니야.”

루나는 ‘현대적인 뷔페’ 네모 안에 다른 흐름의 화살표를 그렸다. [손님: ‘이 데이터만 더 줘’ 요청] -> [주방: 데이터 조각(JSON 등)만 제공] -> [손님: 내 접시 위 빈 곳에 새 음식 놓기].

“서버에게는 ‘방금 내가 쓴 댓글 데이터만 줘’라고 요청하는 거지. 그럼 서버는 페이지 전체가 아니라, 정말 필요한 그 데이터 조각만 보내줘. 그러면 우리 브라우저는 그 데이터 조각을 받아서, 미리 만들어둔 ‘댓글 컴포넌트’라는 빵틀에 넣어 새로운 댓글 UI를 ‘구운’ 다음, 페이지의 빈자리에 쏙 끼워 넣는 거야. 페이지의 다른 부분은 전혀 건드리지 않고.”

솔라의 눈이 커졌다. 흩어져 있던 개념들이 하나의 흐름으로 꿰어지는 순간이었다.

“아! 그래서 깜빡임이 없었던 거구나! 전체 테이블을 갈아엎는 게 아니라, 내 접시 위에 음식 하나만 더 얹는 거니까! React가 바로 그 ‘데이터를 받아서 컴포넌트를 구운 다음, 제자리에 끼워 넣는’ 역할을 하는 주방장이자 웨이터였던 거네!”

이제야 ‘상태 변화에 따라 화면을 다시 그린다’는 말의 전체 그림이 보였다. 컴포넌트 단위로 UI를 관리하고, SPA 구조 안에서 필요한 데이터만 받아와 바뀐 부분만 다시 그려주는 것. 이 모든 것이 합쳐져 우리가 요즘 웹에서 경험하는 부드럽고 역동적인 사용자 경험을 만들어내고 있었다. ‘게시판’처럼 사용자와의 상호작용이 계속 일어나고, 데이터가 실시간으로 변하는 곳에 왜 이런 방식이 필요한지 명확해졌다.

솔라는 잠시 만족감에 잠겼다. 하지만 이내 새로운 의문이 고개를 들었다. 똑똑한 웨이터의 움직임이 신기하긴 한데, 그 움직임의 세부 과정이 궁금해졌다.

“알겠어. 필요한 부분만 바꿔주는 건 정말 효율적이네. 그런데… 그 ‘제자리에 끼워 넣는다’는 건 정확히 어떻게 하는 거야? 아무리 일부라지만, 결국 브라우저 화면의 실제 모습을 바꾸는 작업이잖아. 그걸 너무 자주 하면 오히려 더 복잡하고 느려지는 거 아닐까? React는 어떻게 그렇게 빠르고 정확하게 바꿀 부분만 콕 집어내는 거지?”

4장: 루나: 눈에 보이지 않는 최적화, Virtual DOM의 비밀

솔라는 자신의 스케치북 위에 다시금 복잡한 나무 구조를 그리고 있었다. 지난번 루나가 설명해 준 컴포넌트 개념을 바탕으로, 게시판 페이지가 브라우저에 어떻게 표시될지를 구조화해 본 것이었다. 최상단에는 <div> 상자가 있고, 그 아래로 <ul> (게시글 목록), 그리고 여러 개의 <li> (게시글 아이템)가 자식처럼 뻗어 있었다. 각 <li> 안에는 또다시 작성자, 제목, ‘좋아요’ 버튼 컴포넌트가 가지처럼 매달려 있었다. 웹 개발 강의에서 본 ‘DOM 트리’라는 게 이런 모습일 거라고 짐작했다.

문제는 여기서부터였다. 솔라는 ‘게시글 #2’의 ‘좋아요’ 숫자를 5에서 6으로 바꾸는 상황을 시뮬레이션해보기로 했다. 그녀는 연필을 들고 ‘좋아요: 5’라고 쓰인 부분을 지우개로 힘주어 지웠다. 지우개 가루가 지저분하게 번졌다. 그 자리에 ‘좋아요: 6’이라고 꾹꾹 눌러썼다. 이번엔 게시글 #3에 새로운 댓글을 추가하는 상황을 상상했다. 해당 <li> 아래에 새로운 가지를 그리고, ‘댓글 컴포넌트’ 상자를 그려 넣었다. 스케치북은 금세 지우고 그린 자국으로 얼룩덜룩해졌다.

“으… 번거로워.”

솔라는 저도 모르게 중얼거렸다. SPA가 필요한 부분만 데이터를 받아와서 화면을 바꾼다는 건 이제 안다. 하지만 그 ‘바꾼다’는 행위 자체가 문제였다. 아무리 작은 부분이라도, 이 거대하고 빽빽한 나무 구조의 한 부분을 지우고, 새로 그리고, 혹시나 다른 부분에 영향을 주지 않는지 확인하는 건 상상만 해도 피곤한 일이었다.

“이렇게 진짜 화면을 직접 건드리는 건, 아무리 일부만 한다고 해도 자주 하면 오히려 더 복잡하고 느려지는 거 아닐까? React는 대체 어떻게 이렇게 빠르고 정확하게 바꿀 부분만 콕 집어내는 거지?”

그때, 솔라의 혼잣말을 들은 루나가 다가왔다. 루나는 솔라의 얼룩덜룩한 스케치북을 잠시 들여다보더니, 자신의 방에서 얇고 투명한 OHP 필름 한 장과 마커펜을 가져왔다.

“솔라, 네가 지금 그리고 있는 그 복잡한 나무가 바로 브라우저의 ‘실제 DOM(Document Object Model)’이라고 생각해보자. 아주 무겁고 정교하게 만들어진, 한 번 바꾸려면 큰 비용이 드는 진짜 ‘벽화’ 같은 거야.”

루나는 그렇게 말하며 투명한 OHP 필름을 솔라의 스케치북 그림 위에 조심스럽게 포개었다.

“React는 똑똑한 화가 같아서, 이 비싼 벽화에 바로 붓을 대지 않아. 대신, 이 투명한 종이 위에 먼저 예행연습을 하지.”

루나는 마커펜을 들고, 솔라의 스케치북 그림을 그대로 OHP 필름 위에 빠르게 베껴 그리기 시작했다. 훨씬 가볍고 단순한 선으로 그려진 복사본이 순식간에 만들어졌다.

“이게 바로 **가상 DOM(Virtual DOM)**이야. 실제 벽화의 복사본이자, 일종의 가벼운 설계도지. 자, 이제 아까처럼 ‘게시글 #2’의 ‘좋아요’ 숫자를 6으로 바꿔보자.”

루나는 OHP 필름 위에 그려진 ‘좋아요: 5’를 마커펜으로 찍- 긋고, 그 옆에 ‘6’이라고 썼다. 이어서 ‘게시글 #3’ 아래에 새로운 댓글 상자도 쓱쓱 그려 넣었다. 실제 스케치북과는 달리, OHP 필름 위에서는 수정이 아주 빠르고 깔끔했다.

“상태가 바뀔 때마다, React는 이렇게 가상 DOM이라는 투명 필름 위에 ‘바뀐 후의 모습’을 통째로 새로 그려. 진짜가 아니니까 몇 번을 다시 그려도 전혀 부담이 없지.”

루나는 변경이 끝난 OHP 필름을 들어 올렸다.

“그 다음이 핵심이야. React는 방금 새로 그린 이 필름(새로운 가상 DOM)과, 그 이전에 그렸던 필름(이전 가상 DOM)을 서로 비교해.”

루나는 말로 설명하는 대신, 변경이 가해진 OHP 필름을 원래의 스케치북 그림(실제 DOM) 위에 다시 겹쳐 보였다. 두 그림이 겹쳐지자, 다른 부분은 모두 똑같았지만 정확히 두 군데만 차이가 눈에 띄었다. ‘좋아요’ 숫자가 바뀐 부분과, 새로운 댓글이 추가된 부분.

“아!”

솔라의 입에서 짧은 탄성이 터져 나왔다.

“비교해서… 다른 부분만 찾아내는구나! 진짜 벽화 전체를 훑어보는 게 아니라, 가벼운 설계도 두 개를 비교해서 수정할 부분 목록을 만드는 거네!”

“바로 그거야.”

루나가 고개를 끄덕였다.

“React는 이 비교를 통해 ‘아, 게시글 #2의 텍스트가 5에서 6으로 바뀌었고, 게시글 #3의 자식으로 새로운 댓글이 하나 추가됐네’라는 최소한의 변경사항 목록을 얻어내. 그리고는 실제 DOM, 즉 진짜 벽화에게 찾아가서 그 두 군데만 정확히, 딱 한 번의 작업으로 수정하는 거야. 변경 사항이 100개라도 마찬가지야. 100번 고치는 게 아니라, 모든 변경 사항을 이 가상 세계에서 다 계산하고 최적화해서, 실제 세계에는 최종 결과만 한 번에 반영하는 거지.”

솔라는 무릎을 탁 쳤다. 이제 모든 게 이해되었다. Virtual DOM이 무조건 ‘빠르다’는 건 오해였다. 핵심은 ‘효율성’이었다. 비용이 비싼 실제 DOM 조작 횟수를 극단적으로 줄여주는 것. 마치 복잡한 수술을 하기 전에, 환자의 CT 스캔본(가상 DOM)을 보고 수술 계획을 완벽하게 시뮬레이션해서, 실제 수술(실제 DOM 조작) 시에는 최소한의 절개로 가장 빠르고 정확하게 끝내는 최고의 외과 의사 같았다.

선언형 프로그래밍, 컴포넌트, SPA, 그리고 가상 DOM까지. 얽혀 있던 실타래가 거의 다 풀린 느낌이었다. 각각의 개념들이 왜 필요한지, 어떤 문제를 해결하는지 명확해졌다. 솔라는 이제 자신이 만들 게시판을 훨씬 더 구체적이고 효율적인 구조로 머릿속에 그릴 수 있었다.

하지만 모든 조각을 손에 쥐었을 때, 새로운 질문이 떠올랐다. 이 멋진 조각들을 어떻게 맞춰야 하나의 완성품이 될까?

“이제 선언형, 컴포넌트, SPA, 가상 DOM… 각각의 역할은 알겠어. 정말 똑똑한 도구들이네. 그런데 이 조각들이 전부 합쳐져서 어떻게 하나의 완성된 그림이 되는 거지? 내가 만들려는 게시판이라는 큰 목표 아래에서, 이 개념들이 서로 어떻게 맞물려 돌아가는지 그 전체적인 연결고리가 궁금해.”

5장: 루나: 게시판을 넘어서, React 생태계의 완성

솔라의 방바닥은 지난 며칠간의 고민의 흔적들로 작은 전시회장처럼 변해 있었다. 중앙에는 ‘선언형’이라고 휘갈겨 쓴 메모가 놓여 있었다. 그 옆에는 ‘빵틀’이라는 별명이 붙은 ‘게시글’과 ‘댓글’ 컴포넌트 포스트잇들이 쌓여 있었다. 조금 떨어진 곳에는 ‘뷔페 접시’ 그림이 그려진 SPA의 원리가, 그리고 그 위에는 투명한 OHP 필름, 즉 가상 DOM이 올려져 있었다. 솔라는 이 개념 조각들을 바닥에 늘어놓고, 마치 사건 현장을 재구성하는 탐정처럼 그 사이를 오가며 중얼거리고 있었다.

그녀는 손가락으로 각 조각을 차례로 짚으며 자신이 만들 게시판의 댓글 추가 시나리오를 설명하려 애썼다. “사용자가 댓글을 쓰면… ‘상태’가 바뀌고… 아니, 먼저 SPA니까 서버랑 통신하고… 그러면 가상 DOM이… 아, 컴포넌트는 언제 등장하지?” 순서가 뒤죽박죽 엉키고, 각 도구의 역할이 서로 충돌하는 것만 같았다. 각각은 날카롭고 유용한 도구임이 분명했지만, 이것들을 한 번에 조립해 강력한 기계를 만드는 방법을 알 수 없었다. 마치 설명서 없이 복잡한 기계 부품들을 받은 듯한 막막함에, 솔라는 결국 바닥에 주저앉아 헝클어진 머리를 감싸 쥐었다.

그때, 방문에 기대어 조용히 지켜보던 루나가 방 안으로 들어왔다. 루나는 바닥에 흩어진 개념의 조각들을 잠시 내려다보더니, 한가운데를 가리켰다.

“솔라, 그 도구들을 어떻게 배열할지 고민하기보다, 아주 작은 정보 하나의 여정을 따라가 보자. 네가 게시판 댓글 창에 ‘안녕!’이라고 입력하고 ‘등록’ 버튼을 누르는, 바로 그 순간부터 시작하는 거야. 가장 먼저 무슨 일이 일어날까?”

솔라는 루나의 말에 고개를 들었다. 도구가 아닌, ‘사용자의 행동’에서부터 시작하라는 말이었다. 그녀는 눈을 감고 그 장면을 상상했다.

“내가 ‘등록’ 버튼을 클릭해.” 솔라가 입을 열었다. “그럼… 내 행동은 React에게 새로운 ‘선언’을 하는 거야. ‘이 게시글의 댓글 목록 데이터는 이제 “안녕!”이라는 새 항목을 포함해야 한다’라고. 화면을 어떻게 바꿀지 지시하는 게 아니라, 내가 원하는 최종 결과물의 상태를 선언하는 거지.”

솔라는 ‘선언형’이라고 쓰인 메모를 톡 건드렸다. 첫 번째 조각이 제자리를 찾았다.

“좋아. 그 다음은?”

“음, 내 앱은 SPA니까, 브라우저가 서버에게 ‘페이지 전체를 새로 주세요!’라고 요청하지 않아. 대신 ‘이 댓글 하나를 데이터베이스에 추가해줘’라는 아주 작은 요청만 보내겠지. 서버는 ‘알았어, 처리 완료!’라는 신호와 함께 내가 방금 쓴 댓글 데이터만 돌려줄 거야. 페이지는 깜빡이지 않고 그대로 있어.”

솔라는 ‘뷔페 접시’ 그림을 가리켰다. 이제 데이터는 서버와의 통신을 마치고 다시 브라우저로 돌아왔다.

“자, 이제 브라우저는 새로운 댓글 데이터를 손에 쥐고 있어. 여기서부터가 React의 진짜 무대야.” 솔라는 흥분하며 목소리를 높였다. “React는 댓글 목록의 상태가 바뀌었다는 걸 즉시 알아채. 그리고는 ‘댓글 컴포넌트’라는 빵틀을 가져와서, 방금 받은 ‘안녕!’이라는 데이터 반죽을 넣어 새로운 댓글 UI 조각을 ‘구워내는’ 거지.”

솔라의 손이 ‘빵틀’ 포스트잇 더미로 향했다. 이제 막 구워진 따끈따끈한 UI 조각이 손에 들린 것 같았다.

“하지만 바로 벽화에 그림을 그리진 않아!” 솔라는 외치듯 말했다. “먼저 투명 필름, 즉 가상 DOM 위에 새로운 댓글이 추가된 후의 완성된 화면 전체 모습을 빠르게 그려. 그리고 그 새로운 필름과, 변경 직전의 낡은 필름을 서로 겹쳐서 비교하는 거야. ‘Diffing’ 작업!”

마지막으로 솔라는 OHP 필름을 집어 들었다. 그녀의 눈앞에 두 개의 설계도가 겹쳐지며 다른 부분이 선명하게 드러나는 모습이 그려졌다.

“비교 결과, React는 ‘아, 다른 건 다 똑같고, 댓글 목록 맨 끝에 이 새로운 댓글 하나만 추가하면 되겠구나!’라는 사실을 정확히 알아내. 그리고 나서야 딱 한 번, 실제 DOM을 찾아가서 그 위치에 새로운 댓글 UI 조각을 쏙 집어넣는 거야. 최소한의 움직임으로, 가장 효율적인 업데이트를 하는 거지.”

숨 가쁘게 설명을 마친 솔라는 바닥에 흩어져 있던 조각들을 내려다보았다. 더 이상 혼란스러운 부품들이 아니었다. 사용자의 작은 행동 하나가 어떻게 선언, SPA, 컴포넌트, 가상 DOM이라는 잘 짜인 파이프라인을 타고 흘러, 화면의 부드러운 변화라는 결과물로 이어지는지 한눈에 보였다. 마치 개별 악기들의 소리만 듣다가, 지휘자의 손짓 아래 모든 소리가 하나의 교향곡으로 완성되는 순간을 목격한 기분이었다.

솔라는 자리에서 일어나 자신의 책상으로 향했다. 그리고 가장 처음에, 아무 의미도 모른 채 그저 받아 적었던 문장이 쓰인 노트를 펼쳤다.

"React는 UI를 컴포넌트 단위로 만들고 상태 변화에 따라 화면을 다시 그리는 라이브러리다."

솔라는 문장을 천천히, 한 단어 한 단어 곱씹으며 소리 내어 읽었다. 예전에는 그저 암호 같았던 문장이, 이제는 자신이 방금 겪은 모든 여정을 담고 있는 완벽한 요약문으로 보였다.

  • 라이브러리다: 내가 필요할 때 불러와 UI 갱신을 맡기는, 똑똑한 UI 전문 조수.
  • 컴포넌트 단위로: UI를 재사용 가능한 독립적인 부품(빵틀)으로 만들어 복잡함을 제어하고.
  • 상태 변화에 따라: ‘댓글 추가’ 같은 데이터의 변화가 생기면.
  • 화면을 다시 그리는: SPA 구조 안에서 가상 DOM을 통해 가장 효율적인 방식으로 화면을 갱신한다.

모든 조각이 맞아떨어졌다. 솔라는 노트를 덮고, 깨끗한 스케치북 페이지를 펼쳤다. 더 이상 막막하지 않았다. 그녀는 연필을 들고, 망설임 없이 자신이 만들 게시판의 전체 구조를 그리기 시작했다. 가장 큰 App 컴포넌트, 그 안에 자리 잡을 Board 컴포넌트, 그리고 Board를 채울 여러 개의 Post 컴포넌트와 그 아래에 달릴 CommentList 컴포넌트까지. 머릿속에 있던 복잡한 웹 페이지가 명확하고 질서 있는 설계도로 구체화되고 있었다. 이제 그녀는 무엇을 만들어야 할지 정확히 알고 있었다.