<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>코딩크리처</title>
    <link>https://codecreature.tistory.com/</link>
    <description>신 기술이 항상 나오는 이 시대에 기초의 중요성은 더욱 중요해졌습니다.

항상 이에 적응하기 위해 기초를 쌓으려 배움을 기록하는 장소입니다.</description>
    <language>ko</language>
    <pubDate>Thu, 11 Jun 2026 09:46:28 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>코딩크리처</managingEditor>
    <image>
      <title>코딩크리처</title>
      <url>https://tistory1.daumcdn.net/tistory/6142901/attach/eaf30c1affbe45adb0179071560647bc</url>
      <link>https://codecreature.tistory.com</link>
    </image>
    <item>
      <title>플랫폼 개발 엔지니어로서 출근하기 전, 자아성찰과 나아갈 길에 대한 고찰</title>
      <link>https://codecreature.tistory.com/248</link>
      <description>&lt;h2&gt;제목 : 플랫폼 개발 엔지니어로서 출근하기 전, 자아성찰과 나아갈 길에 대한 고찰&lt;/h2&gt;
&lt;h3&gt;부제목 : 인생에서 하나의 챕터를 끝내고, 다음 챕터로 나아가기 위한 글&lt;/h3&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;이 글을 작성하는 이유&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;개발자, 즉, 엔지니어를 꿈꾸는 이들은 굉장히 많다.&lt;/p&gt;
&lt;p&gt;이들은 각기 다른 이유로 개발직을 꿈꾼다.&lt;/p&gt;
&lt;p&gt;누구는 &amp;quot;좋은 회사&amp;quot; 에 가기 위해,&lt;/p&gt;
&lt;p&gt;누구는 &amp;quot;좋은 연봉&amp;quot; 을 바라기에,&lt;/p&gt;
&lt;p&gt;누구는 컴퓨터 그 자체를 좋아하기에, (이건 나에 해당한다.)&lt;/p&gt;
&lt;p&gt;누구는 부트캠프에 다녀왔기에, 개발자를 그저 꿈꿀 수도 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;나는 다양한 목표를 가지고 개발직군을 꿈꾸는 이들에게, 내 이야기를 들려주고자 이 글을 작성한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;기초와 경험은 이제 그만, 실무를 해야 한다.&lt;/h2&gt;
&lt;p&gt;나는 2 주간 약 &lt;code&gt;120&lt;/code&gt; 개 정도의 이력서를 넣었으며,&lt;/p&gt;
&lt;p&gt;그 중 &lt;code&gt;12&lt;/code&gt; 번의 면접 제의를 받았다. (이 글을 작성하는 현재까지.)&lt;/p&gt;
&lt;p&gt;현재 차갑다 못해 남극으로 변해버린 채용 시장에서 그래도 꽤 괜찮은 타율을 낸 것 같다고 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러면 이건 오롯이 나의 경험과 능력으로 인해 수많은 면접 제의를 받을 수 있었는가?&lt;/p&gt;
&lt;p&gt;절반은 맞고, 절반은 틀리다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;내가 만든 고정관념과 회피성향을 마주했다.&lt;/h3&gt;
&lt;p&gt;나는 현재까지 총 230 편 이상의 블로그 IT 글을 작성하고, 나만의 IoC 프레임워크를 만들기도 했으며,&lt;/p&gt;
&lt;p&gt;팀 프로젝트에서 &lt;code&gt;Backend &amp;amp; Infra &amp;amp; CI/CD&lt;/code&gt; 역할을 맡았다.&lt;/p&gt;
&lt;p&gt;위의 두 문장은 개발/엔지니어 직군에서 나를 표현 할 때, 단순하게 표현 할 수 있는 문장이다.&lt;/p&gt;
&lt;p&gt;물론 프로젝트를 단순하게 1-2 개 한 것은 아니고, 다양한 도메인의 프로젝트를 진행했다.&lt;/p&gt;
&lt;p&gt;쿠팡 API 를 분석하여 &amp;quot;보안취약점&amp;quot; 을 보고하고, 3000 달러 (세후 308 정도 나옴) 받은 적도 있다.&lt;/p&gt;
&lt;p&gt;이 글을 읽는 분들은 어쩌다 들어오셨을 확률이 매우 높은데,&lt;/p&gt;
&lt;p&gt;저는 이러한 글을 작성했었습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/247&quot;&gt;Spring IoC 컨테이너를 내 방식으로 직접 만들기 (Java 의 메타데이터를 극한으로 다루는 법)&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;--&amp;gt; 나만의 프레임워크 엔진 만들기&lt;/li&gt;
&lt;li&gt;--&amp;gt; 스스로 Convention 을 만들어 보며, 사용자의 편의성을 체득하기&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/228&quot;&gt;index.html 에서 시작하는 점진적인 리액트 프로젝트 적용 방법에 대해서&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;--&amp;gt; Legacy 웹 구조를 현대 구조로 변환하려는 현업 종사자들을 위해 작성한 글&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/227&quot;&gt;React 런타임 코드 해부 노트 : Fiber 와 Scheduler 를 따라간 21일&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;--&amp;gt; 너무 방대하고 복잡한 오픈소스 분석하는 법을 공유하는 글&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/211&quot;&gt;React 를 다시 공부하기 전, 스스로에 대한 고찰 및 비판&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;--&amp;gt; 나 스스로, 그리고 개발자들을 위해 작성한 고찰&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/201&quot;&gt;node.js 로 멀티 스레드 구현하기 (Worker)&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;--&amp;gt; &lt;code&gt;Node.js&lt;/code&gt; 서버가 단일 스레드 기반 이벤트 루프 구조를 가지기에 가지는 단점을 극복하고자 작성한 글&lt;/li&gt;
&lt;li&gt;--&amp;gt; 비동기 처리는 요청을 빠르게 받아들이지만, 단일 스레드가 모든 Context 를 가지기에, &lt;br/&gt; 복잡한 계산이 서버에서 실행 될 경우, 요청 I/O 가 느려질 수 있음을 알고 작성 한 글&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/233&quot;&gt;C 그리고 fgets 라인 입력만으로 tokenizer 메서드 제작하기 (하드코어)&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;--&amp;gt; 나 자신의 알고리즘 포텐셜을 높이고, 메모리 구조와 로직을 Low 에서 다루고자 노력했던 글&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/category&quot;&gt;블로그 글 전체 보기&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;사람들은 자신만의 세상을 만들 수 있으며, 고정관념을 가지고, 스스로의 길을 개척 할 수 있는 권리가 있다.&lt;/p&gt;
&lt;p&gt;특히나 나는 수많은 글을 제작 할 때, 단순 &amp;quot;정보 전달&amp;quot; 에 그치지 않고, 나의 생각을 나누었다.&lt;/p&gt;
&lt;p&gt;따라서 구글 검색 엔진이 나의 글 점수를 높게 쳐 주어, 7,000 이상의 조회를 달성했다고 생각한다.&lt;/p&gt;
&lt;p&gt;그리고 약 2 년 동안 정말 꾸준하게 블로그 글을 작성했기 때문이었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나 나는 오랜 독학으로 인해 실제 세상을 깨닫지 못하고, 스스로의 세상에 갇혀 있었다.&lt;/p&gt;
&lt;p&gt;나는 내가 만든 새장에서 나올 생각을 못했지만, 이미 엔지니어로서 일하고 있던 친구의 도움으로&lt;/p&gt;
&lt;p&gt;실제 세상을 마주하고, 내가 땅바닥에 버렸던 모든 기회비용을 마주했다.&lt;/p&gt;
&lt;p&gt;이를 스스로 마주한다는 건, 매우 어려운 일 중 하나에 속했다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;면접 제의를 받았을 때, 이를 오롯이 나의 경험과 능력으로 볼 수 있는가?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;라는 질문을 던진다면, 나는 &lt;strong&gt;절대 아니다&lt;/strong&gt; 라고 말할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;완전한 준비 상태는 존재하지 않는다. 그러나 미리 준비된 사람이 압도적으로 유리하다&lt;/h3&gt;
&lt;p&gt;고심해서 생각난 문장인데, 나는 3 년 동안의 공부와 프로젝트를 진행하면서 이 생각을 하지 못했다.&lt;/p&gt;
&lt;p&gt;다행인 것은, 꾸준히 작성한 나의 블로그는 &amp;quot;나는 준비되었다&amp;quot; 라는 것을 &amp;quot;정량적으로&amp;quot; 보여주는 증거였다.&lt;/p&gt;
&lt;p&gt;우리가 생각해야 하는 것은, &amp;quot;어느 정도가 준비 상태인가?&amp;quot; 를 정의하는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;신입 개발자이자, 엔지니어로 변모하기 위해, 우리는 얼마나 준비해야 하는지 모른다.&lt;/p&gt;
&lt;p&gt;그 전에, &lt;strong&gt;&amp;quot;도대체 무엇을 준비해야 하는 지 조차도 알기 힘든 것&amp;quot;&lt;/strong&gt; 이 바로 &amp;quot;Develop&amp;quot; 이라고 생각한다.&lt;/p&gt;
&lt;p&gt;AI 가 많이 발전 한 것은 사실이다. 나는 Cursor Pro 를 사용하여 복잡한 형태의 아키텍쳐를 구성해 보기도 했다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;처음엔 경악과 감탄이었고, 15 분 이후부터는 의문으로 변했다.&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;AI 는 인간의 컨벤션을 가져와 자신만의 Convention 으로 인간이 코드로 개입 할 여지를 주지 않는다.&lt;/li&gt;
&lt;li&gt;쓸데없는 행동(명령어 직접 입력) 으로, 정해진 Token 수를 과도하게 낭비한다.&lt;/li&gt;
&lt;li&gt;Architecture 가 복잡 해 질 수록, AI 사용은 비례하지 않고, 기하학적으로 상승한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;위의 3 가지는 내가 최신 버전의 Cursor Agent 를 사용하면서 알게 된 것이다.&lt;/p&gt;
&lt;p&gt;나는 모든 과정을 보고, 최종 오케스트레이션은 엔지니어가 관리해야 한다는 것을 알게 되었지만,&lt;/p&gt;
&lt;p&gt;이 글을 읽는 여러분들은 어떤 생각을 하실 지 모르겠다.&lt;/p&gt;
&lt;p&gt;개발의 분야는 단순히 취업 시장에서 나뉘는 것 보다, 보지 못하는 영역이 훨씬 더 많기 때문에,&lt;/p&gt;
&lt;p&gt;각자 이 특징을 보고 자신은 어떻게 성장해야 할 지 정해야 한다고 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;스스로 정의한 &amp;quot;준비&amp;quot; 가 되었다고 생각하면, 자신이 내세울 수 있는 &amp;quot;강점&amp;quot;이 무엇인지 생각해야만 한다.&lt;/p&gt;
&lt;p&gt;이는 비단 신입 뿐만 아니라, 실무를 하고 있는 사람에게도 해당 될 수 밖에 없다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;AI 시대에서 가장 중요 해 진 것은, 아이러니하게 &amp;quot;인간의 본질&amp;quot; 이었다.&lt;/h2&gt;
&lt;p&gt;인간의 본질을 정의 해 보자.&lt;/p&gt;
&lt;p&gt;철학, 영혼, 생명, 죽음, 혹은 우주적인 엔트로피 상승 요인... 이런 추상적인 영역을 제외하고,&lt;/p&gt;
&lt;p&gt;개발, 그리고 엔지니어로서 갖추어야 할 &amp;quot;인간의 본질&amp;quot; 에 대해서 이야기하고자 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;내가 AI 보다 개발을 잘 할까?&lt;/h3&gt;
&lt;p&gt;솔직히 말해보자. 단순 코드 구현, 알고리즘, API 엔드포인트, JWT 보안 구성, 세션, 등등..&lt;/p&gt;
&lt;p&gt;이들은 모두 AI 에게 점령되었다. 이는 Web 과 App 분야에서도 마찬가지이다.&lt;/p&gt;
&lt;p&gt;심지어 특정 아키텍쳐를 배워야 하는 &amp;quot;결제 시스템&amp;quot; 조차, AI 가 가장 구현을 잘하는 영역이다.&lt;/p&gt;
&lt;p&gt;AI 보다 코드를 빠르게 만드는 사람은 그냥 없다고 봐도 무방하다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;생산성 자체로 보았을 때, AI 를 이길 수 없다. 이용하는 시각을 가져야 한다.&lt;/h3&gt;
&lt;p&gt;2022 년 부터 시작된 AI Boom 부터 현재 2026년 까지의 행보를 다시 보자.&lt;/p&gt;
&lt;p&gt;처음에는 똑똑한 단순 Chat 이었다. 코드를 작성할 수 있지만, 할루시네이션이 심했다.&lt;/p&gt;
&lt;p&gt;Version 이 업그레이드 되어가며 복잡한 &amp;quot;규칙&amp;quot; 과 &amp;quot;법&amp;quot; 그리고 &amp;quot;구현&amp;quot; 을 매우 잘하게 되었다.&lt;/p&gt;
&lt;p&gt;이 시기 미국에서는 살벌한 전문직 해고와, 절반 이상의 개발직군 해고가 동시에 이루어지기 시작했다.&lt;/p&gt;
&lt;p&gt;버전이 업그레이드 될 때 마다 똑똑해 지는 것이 보일 정도이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;조금 시각을 바꿔서 보자. 그 당시 완벽하지 않던 AI 때문에 과연 대량 해고가 일어났을까?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;내가 만약에 대형 기업을 이끄는 수장이었다면, 그 수많은 사람들의 노고를 세심하게 알 수 없다.&lt;/p&gt;
&lt;p&gt;즉, 정량적 업무와 기여 수치를 만들고, 지정한 수치에 이르지 못한 자를 모두 잘랐다고 볼 수 있다.&lt;/p&gt;
&lt;p&gt;한국과 다르게 미국은 자르는게 훨씬 쉽다. &lt;/p&gt;
&lt;p&gt;즉, &amp;quot;회사&amp;quot; 는, 기존의 구현 업무를 진행하던 사람들을 &amp;quot;해고하고&amp;quot;,&lt;/p&gt;
&lt;p&gt;AI 의 발전과 사용으로 얼마나 인력을 대체할 수 있는지 Testing 한 것이다.&lt;/p&gt;
&lt;p&gt;그게 비록 특정 업무의 마비가 일어나더라도, 가장 비싼 투자인 &amp;quot;인력&amp;quot; 자체를 삭감할 수 있었기 때문이다.&lt;/p&gt;
&lt;p&gt;문제는, 회사 자체가 이러한 과정 속에 AI 의 활용에 익숙해져 굳이 사람들을 뽑을 이유가 많이 없어진 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;지금 사회는 단순하게 AI 하나만 이용하지 않는다.&lt;/p&gt;
&lt;p&gt;AI Codex, MCP, Agent 와 같은 개념이 등장하며, 목적을 위한 &amp;quot;판단&amp;quot; 까지도 동시에 수행하려 한다.&lt;/p&gt;
&lt;p&gt;머리를 잘 사용하고, 돈을 많이 주어야 했던 White-Color 직군들이, &lt;/p&gt;
&lt;p&gt;회사 입장에서는 AI 로 교체하고 싶으며, 심지어는 AI 가 더 잘하기도 한다.&lt;/p&gt;
&lt;p&gt;회사 입장에서는 인건비가 가장 큰 &amp;quot;지출&amp;quot; 일 테니, 군침을 흘릴 수 밖에 없다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;다시 돌아와서,&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;결국 우리가 개발/엔지니어 를 하고자 함은, 원하는 일을 하고자 함이다.&lt;/p&gt;
&lt;p&gt;그리고 AI 가 가장 잘 하는 것이 바로 &amp;quot;개발&amp;quot; 이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;우리는 어떻게 사회에서 살아남아야 하는가?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이는 단순히 &amp;quot;개인&amp;quot;에게 할당된 문제가 아니라, &amp;quot;회사&amp;quot; 그 자체에도 해당되는 문제다.&lt;/p&gt;
&lt;p&gt;공기업, 공공기관으로부터 단순 구현 수주를 받는 회사는 서서히 저물 수 밖에 없다.&lt;/p&gt;
&lt;p&gt;정말 수많은 IT 기업들이 무너졌다. 이를 보고 IT 기업들은 살아남기 위해 변했다.&lt;/p&gt;
&lt;p&gt;이 급격한 AI 시대 변화에 살아남기 위해 가장 노력한 주체가 바로 기업이다.&lt;/p&gt;
&lt;p&gt;기업의 변화를 보면, 우리가 어떻게 AI 사회에서 생산성 있는 사람으로서 변모할 수 있는지 보여준다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;그건 정말 간단하게도, &amp;quot;AI&amp;quot; 를 어떻게든 사용하고 이용하는 것이다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;즉, 인간의 적응 능력으로, &amp;quot;뛰어난 AI&amp;quot; 를 사용하여 더 높은 생산성을 끌어올리는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;AI 를 어떻게 이용해야 하나?&lt;/h2&gt;
&lt;p&gt;내가 질문한 이 문장의 본질은, 단순히 &amp;quot;AI 를 사용하세요&amp;quot; 가 절대로 아니다.&lt;/p&gt;
&lt;p&gt;정말로, AI 를 어떻게 바라봐야 하냐는 의미이다.&lt;/p&gt;
&lt;p&gt;우리 모두는 AI 가 너무 급격하게 성장하고 변화하기에, 단순히 원하는 정보 추출과 사용 그 자체에만 몰두해 있다.&lt;/p&gt;
&lt;p&gt;나는 최근에 Spring IoC/DI 컨테이너를 직접 만들어 가벼운 프레임워크를 만들었는데,&lt;/p&gt;
&lt;p&gt;그 과정에서 생각난 발상이 존재했다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;AI 를 사용하는 것이 아니라, AI 에게 &amp;quot;도구 혹은 정보&amp;quot; 를 제공해라.&lt;/h3&gt;
&lt;p&gt;대부분의 IT 기업들은, 개발 인력을 통한 서비스 구현과 제공에 초점을 두었다.&lt;/p&gt;
&lt;p&gt;그러나, 이러한 생산성을 AI 에게 빼앗긴 기업들은 선택을 해야 했다.&lt;/p&gt;
&lt;p&gt;대부분의 기업은 감히 엄두도 못 할 정도의 기술력을 가진 것은 아니기 때문이었다.&lt;/p&gt;
&lt;p&gt;그래서 현재 일반적인 IT 기업들은, &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;다른 기업에게 AI 도입 컨설팅&lt;/li&gt;
&lt;li&gt;AI 를 이용한 빠른 구현 &amp;amp; 인건비 감소 (개발자를 자르며) --&amp;gt; 절대 지속적이지 않음.&lt;/li&gt;
&lt;li&gt;AI 자체 제작 --&amp;gt; 극소수&lt;/li&gt;
&lt;li&gt;AI Agent 제공 --&amp;gt; 국제기업 AI 와 오픈소스 AI 를 결합한 맞춤형 &amp;quot;로컬 AI&amp;quot; 제작 회사&lt;/li&gt;
&lt;li&gt;접근하기 어려운 정보를 부여받아 서비스를 제공하는 회사 (당연히 AI 사용)&lt;/li&gt;
&lt;li&gt;단순 서비스 중개 뿐만 아니라, 하드웨어로 진출하는 회사 (당연히 AI 사용)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;일단 크게 생각나는 대로 적어보았다. &lt;/p&gt;
&lt;p&gt;공고를 300 개 정도 보았는데, 기업의 성장 방향성이 위와 같이 변하고 있다고 판단했기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;AI 를 음식에 비유 해 보자. - 고추가루와 고추장의 탄생&lt;/h3&gt;
&lt;p&gt;고추가루와 고추장은 우리나라 음식에 빠지지 않으며, 엄청난 감칠맛과 매운맛을 더해주는 재료다.&lt;/p&gt;
&lt;p&gt;여기서 나는 고추가루와 고추장을 AI, 그리고 AI Agent 에 비유 할 것이다.&lt;/p&gt;
&lt;p&gt;상황을 가정 해 보자.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;2021 년 까지, 고추가루는 기존의 재료를 대체 할 만큼 훌륭하지는 않았다고 가정하자.&lt;/li&gt;
&lt;li&gt;사람들은 음식을 먹을 때, 대부분 고추가루가 존재하지 않는 음식을 먹었다고 가정하자.&lt;/li&gt;
&lt;li&gt;사람들은 매운 것을 원하기에, 만들기 힘든 캡사이신을 조금씩 넣어 먹었다고 가정해 보자.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;그런데, 2022년에&lt;/p&gt;
&lt;p&gt;매운맛, 감칠맛을 너무나 쉽게 더해주고, &amp;quot;어떠한 음식&amp;quot; 에도 궁합이 잘 맞는 재료,&lt;/p&gt;
&lt;p&gt;&amp;quot;고추가루&amp;quot;(AI) 가 등장했다.&lt;/p&gt;
&lt;p&gt;사람들은 기존에 음식(서비스)에 고추가루가 들어갔을 때, 대부분의 상황에서 맛이 더 좋아 진 것이다.&lt;/p&gt;
&lt;p&gt;음식 제조에 일가견이 있는 사람들은, 모두 고추가루를 융합하여 고객에게 판매하기 시작했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그렇게 고추가루로 인해 모든 음식이 상향 평준화 되었을 때, 고추가루를 이용한 융합 재료, &lt;/p&gt;
&lt;p&gt;2023-2024 년 즈음, &amp;quot;고추장&amp;quot;(AI Codex, AI Agent) 이 등장했다.&lt;/p&gt;
&lt;p&gt;이미 음식(서비스)이 상향 평준화 된 상황에서, 음식을 제조하는 모든 회사들은 이 기회를 놓칠 수 없었다.&lt;/p&gt;
&lt;p&gt;따라서, 자신들의 모든 기존 음식 제품에, &amp;quot;고추장&amp;quot; 을 섞어서 만들어 판매하게 된 것이다.&lt;/p&gt;
&lt;p&gt;그리고 현재로 돌아와서, 우리는 모두 저렴한 가격에 &amp;quot;고추장&amp;quot; 을 구매해서, 밥에 비벼먹고 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;우리가 음식을 제공하는 사람이라고 생각 해 보자.&lt;/p&gt;
&lt;p&gt;우리는 살아남기 위해서 &amp;quot;어떤 음식&amp;quot;(프로그램) 을 만들어야 하는가?&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;여기서 확실히 해야 할 것은, &amp;quot;고추가루&amp;quot; 와 &amp;quot;고추장&amp;quot; 만 있으면 맵기만 하다는 것이다.&lt;/p&gt;
&lt;p&gt;그래서 우리는 계란, 콩나물, 참기름, 혹은 참치캔을 따서 재료들을 밥과 같이 비빈다.&lt;/p&gt;
&lt;p&gt;여기서 &amp;quot;계란&amp;quot;, &amp;quot;콩나물&amp;quot;, &amp;quot;참기름&amp;quot;, &amp;quot;참치캔&amp;quot; 은 비유인데,&lt;/p&gt;
&lt;p&gt;각 개인이나, 회사가 가진 인력과 능력, 정보에 해당 할 수 있다.&lt;/p&gt;
&lt;p&gt;각자에게 맞는 재료들이 무언인지를 깊게 생각 해 보아야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;변하는 세상에 흔들리지 말고, 자신만의 어려운 길을 걸어야 한다.&lt;/h2&gt;
&lt;p&gt;자신만의 세상에 갇히라는 것이 아니다.&lt;/p&gt;
&lt;p&gt;바로 위에서 설명한 &amp;quot;계란&amp;quot;, &amp;quot;참기름&amp;quot;, ... 과 같이,&lt;/p&gt;
&lt;p&gt;AI 와는 궤를 달리하는 능력이 필요하다.&lt;/p&gt;
&lt;p&gt;이 글을 읽는 여러분이, AI 에게 대체된 능력을 가지고 있더라도 상관없다.&lt;/p&gt;
&lt;p&gt;단순한 게시판 서비스를 만들더라도, 그 과정에서 겪은 나만의 트러블슈팅(해결과정) 은, &lt;/p&gt;
&lt;p&gt;AI 가 함부로 대체 할 수 있는 능력이 아니다.&lt;/p&gt;
&lt;p&gt;이러한 생각으로, 나는 누구도 관심을 기울이지 않았던 &lt;/p&gt;
&lt;p&gt;Spring IoC/DI Container 프레임워크를, 나만의 시각, 그리고 방식으로 만든 것이다.&lt;/p&gt;
&lt;p&gt;그 과정을 겪으며, 개발자와 엔지니어들도 보지도 듣지도 못한 기능을 직접 제작 및 사용하며,&lt;/p&gt;
&lt;p&gt;나만의 계란과 참기름을 만들어가는 것이다.&lt;/p&gt;
&lt;p&gt;그리고 나만의 재료는 AI 와 결합하여 특별한 서비스를 만들어 낼 수도 있는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;우리는 여태까지 특정 능력을 고도화시켰는데, AI 가 대체해버렸다.&lt;/h3&gt;
&lt;p&gt;이 소단원을 따로 넣은 이유는, AI 에 부정적인 시각을 가짐을 말하는 것이 아니다.&lt;/p&gt;
&lt;p&gt;누구라도 자신이 여태껏 키웠던 능력을 5 년도 안되는 시간 안에 빼았겼다면, 매우 불쾌할 수 밖에 없다.&lt;/p&gt;
&lt;p&gt;예술의 영역이었던 그림, 음악, 편집은 AI 가 가져간 것이 사실이다.&lt;/p&gt;
&lt;p&gt;나는 누군가 어렵게 제작한 프로그램이 있다면, 시간만 주면 나는 만들 수 있다고 자부할 수 있다. 끈질기기 때문이다.&lt;/p&gt;
&lt;p&gt;그러나 유튜브에서 &lt;strong&gt;&amp;quot;심통봇&amp;quot;&lt;/strong&gt; 이라는 유튜브 채널을 보았다.&lt;/p&gt;
&lt;p&gt;이 채널은 뮤직비디오를 AI Agent 와 같이 협업하여 만드는 채널인데, 영상의 결과물이 비록 AI 같더라도,&lt;/p&gt;
&lt;p&gt;AI 양산형 콘텐츠를 생산하는 채널과는 격을 달리하는 콘텐츠를 생산했다.&lt;/p&gt;
&lt;p&gt;나는 어떤 프로그램이던 시간만 준다면 반드시 해낼 수 있다고 생각하는 사람인데,&lt;/p&gt;
&lt;p&gt;거의 유일하게 이 사람은 어떤 방식과 시각으로 이러한 컨텐츠를 생산하는 건지 감이 잡히지 않았다.&lt;/p&gt;
&lt;p&gt;즉, 사람인 우리는 개인이 가진 고유의 특성이 있다.&lt;/p&gt;
&lt;p&gt;이것은 성격이 될 수도, 인맥이 될 수도, 정보가 될 수도, 아이디어가 될 수도 있다.&lt;/p&gt;
&lt;p&gt;우리가 가져야 할 시각은, &amp;quot;너가 나를 대체했어.&amp;quot; 가 아니다.&lt;/p&gt;
&lt;p&gt;&amp;quot;너를 통해 내가 이득을 보겠다.&amp;quot; 라는 시각을 가져야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;기존의 프로그래머들은 특정 라이브러리, 프레임워크, 코드 작성에 익숙하다.&lt;/p&gt;
&lt;p&gt;그렇다면, 우리는 일종의 &amp;quot;감독&amp;quot; 으로서 AI 가 코드를 단순히 출력한 것인지,&lt;/p&gt;
&lt;p&gt;책임 분리와 유지보수성을 유지했는지, 인간의 개입을 위해 우리가 어떤 라이브러리를 사용하여 테스팅하라고 명령할지&lt;/p&gt;
&lt;p&gt;생각해야 한다.&lt;/p&gt;
&lt;p&gt;그리고 이러한 능력을 가지기 위해서는, 어떤 언어와 도메인, 혹은 지식이던, 굉장히 깊게 팔 필요가 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;즉, 우리는 밑바닥의 기초 지식을 알기에 비즈니스 로직과 데이터베이스를 연결 할 수 있었다. &lt;/p&gt;
&lt;p&gt;그리고 에러를 처리 할 수 있었다.&lt;/p&gt;
&lt;p&gt;그러나 AI 를 생각 해 보자. 당연히 어떤 기초 지식이던 물어보면 정확하게 알려주겠지만,&lt;/p&gt;
&lt;p&gt;비즈니스 로직과 복잡한 플랫폼 관리를 만들기 위해, AI 는 딱히 밑바닥 지식을 고려하지는 않는다.&lt;/p&gt;
&lt;p&gt;그저 자가 테스트를 통해 에러가 일어나면, 그 때 가서 오랜 분석을 거친다.&lt;/p&gt;
&lt;p&gt;Cursor 를 통해 간단한 React + Spring + Redis + Docker 프로그램을 제작했을 때,&lt;/p&gt;
&lt;p&gt;우리가 흔히 말하는 Maven Central 의존성 라이브러리 중 E2E 테스팅 라이브러리를 가져오지 않고,&lt;/p&gt;
&lt;p&gt;비효율적으로 &lt;code&gt;curl&lt;/code&gt; 명령어와 &lt;code&gt;body&lt;/code&gt; 내용문을 직접 집어넣어 테스팅하는 작업을 연속으로 틀리고,&lt;/p&gt;
&lt;p&gt;수십 번의 행동으로 토큰을 낭비하는 과정을 보면서, 나는 엔지니어가 가져야 할 시각을 비로소 보게 되었다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;너가 취업 되었으니까, 맘 편하게 이런 글을 작성하는 거 아니야?&lt;/h2&gt;
&lt;p&gt;세상은 더욱 비정해졌으며, 개인주의적으로 변했다.&lt;/p&gt;
&lt;p&gt;직관적으로 Money(돈), Wealth(부) 는 사회가 발전하며 점점 더 양극화 되어가고 있다.&lt;/p&gt;
&lt;p&gt;그리고 우리가 가질 수 있던 스스로의 인력, &lt;/p&gt;
&lt;p&gt;즉 사회에서의 생산성 조차도 기존의 실력자들이 가로채 갈 수 있는 것이 사실이다.&lt;/p&gt;
&lt;p&gt;소셜 미디어로 사람들의 자존감조차도 깎였는데, &lt;/p&gt;
&lt;p&gt;심지어는 사회에 미칠 수 있는 나의 영향조차도 없어졌다고 생각할 수 있기 때문이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;솔직히 말해서, 나 또한 그랬다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;나는 C 언어로, 제한된 라이브러리를 사용하여 C 자체의 내장 라이브러리를 다루지 않고 직접 제작 해 봤으며,&lt;/p&gt;
&lt;p&gt;이를 통해 저 수준의 메모리 조작과 나만의 타입 생성을 경험했다.&lt;/p&gt;
&lt;p&gt;나는 Java 를 통해 IoC/DI 프레임워크를 제작 해 보았으며, &lt;/p&gt;
&lt;p&gt;이 과정에서 Spring 의 오픈소스 코드는 쳐다도 보지 않고, 기능만 보고 다른 시각으로 제작했다.&lt;/p&gt;
&lt;p&gt;나는 쿠팡에게 공개 API 취약점을 보고하여 3,000 달러의 포상금을 얻기도 했다.&lt;/p&gt;
&lt;p&gt;나는 React 라는 거대한 라이브러리 오픈소스의 코드를 뜯어 일부 순환 사이클을 분석했다.&lt;/p&gt;
&lt;p&gt;등등.. 말하고 싶은 것이 너무 많다.&lt;/p&gt;
&lt;p&gt;사람들은 자신들이 내세울 능력이 있으며, 그것을 사회에 알릴 수 있다. 그건 권리이다.&lt;/p&gt;
&lt;p&gt;그러나, 지금의 상황을 정확히 요약 해 보자면,&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AI 로 인력을 대부분 교체 할 수 있는거 아니야?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;였다가,&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AI 와 인력을 합쳐야 생산성이 극대화되는구나&lt;/strong&gt; &lt;/p&gt;
&lt;p&gt;로 바뀌었다고 생각한다.&lt;/p&gt;
&lt;p&gt;지금은 혼돈의 시기이다. 어떤 능력이 대체될지, 무엇을 요구할지, 그것은 알 수 없다.&lt;/p&gt;
&lt;p&gt;따라서, 기업들도 사람을 뽑아야 하는데, 그 기준이 명확하지 않고 추상적으로 변했다는 것이다.&lt;/p&gt;
&lt;p&gt;나는 웹 페이지의 구조와 비즈니스 로직에 대한 이해, 의존성의 원리, 그리고 아키텍쳐의 존재 이유를 알고 있다.&lt;/p&gt;
&lt;p&gt;10년차 시니어 수준의 지식은 아니더라도, 아키텍쳐가 미치는 영향이 무엇인지는 알고 있는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이제는 회사에 작성된 특정 프로그램의 이해도를 따지는 것이 아니라,&lt;/p&gt;
&lt;p&gt;어짜피 해당 프로그램은 AI 를 이용하여 빠르게 유지보수 및 기능 추가를 도입 할 수 있으므로,&lt;/p&gt;
&lt;p&gt;전체적인 이해도를 가진 신입을 뽑는 것을 추구한다는 걸 알 수 있었다.&lt;/p&gt;
&lt;p&gt;왜냐면, 면접제의 온 회사들의 업무가 전부 달랐고, 언어, 프레임워크, 웹, 프론트, 아키텍트, .. 다양했다.&lt;/p&gt;
&lt;p&gt;심지어는 PHP, OracleDB 를 사용하는 회사에서도 면접제의가 왔기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이 회사들이 나를 잘못 부른 것이 아니다. 미래에 어떤 프로그램과 플랫폼을 구축할 지 모르는데,&lt;/p&gt;
&lt;p&gt;빠르게 변화하는 세상에 적응 할 줄 아는 신입을 원하는 것이다.&lt;/p&gt;
&lt;p&gt;이것은 단순히 AI 를 이용하여 프로그램을 뽑아내는 사람 뿐만 아니라,&lt;/p&gt;
&lt;p&gt;CS 기초 지식에 매우 튼튼한 사람에게도 유리한 조건이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;각자 자신만의 능력에 대한 상대적인 기준점이 있을 것이고, 다르다는 것을 알 수 있다.&lt;/p&gt;
&lt;p&gt;그러나, 이제는 확실하게 정해야 한다.&lt;/p&gt;
&lt;p&gt;현재까지 자신이 세워 온 업적이, AI 세상에서, 어떤 강점을 가질 수 있는지 정하고, 키워야 한다.&lt;/p&gt;
&lt;p&gt;그리고 공통적으로, AI 사용은 필수이다. (어떤 업무던.)&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;나에게 해당되는 것.&lt;/h2&gt;
&lt;p&gt;이 글은 단순히 취준생들(특히 개발자) 혹은 회사(IT) 들을 위해 작성하는 글이 아니다.&lt;/p&gt;
&lt;p&gt;나 스스로가 뼈저리게 이 세상을 마주해야 하기 때문이다.&lt;/p&gt;
&lt;p&gt;내가 플랫폼 구축을 완성하고 큰 기여를 준다 한들,&lt;/p&gt;
&lt;p&gt;AI 발전이 훨씬 빨라져서 내가 미리 정리했던 &lt;code&gt;.md&lt;/code&gt; 파일들을 읽고, 순식간에 대체 할 수도 있다.&lt;/p&gt;
&lt;p&gt;그러나, 절대로 없어질 수 없고, 대체할 수 없는 것이 있다.&lt;/p&gt;
&lt;p&gt;바로 플랫폼을 구축하기 위해 경험하게 될 나의 AI 협업 과정이다.&lt;/p&gt;
&lt;p&gt;현재는 이러한 AI 협업 과정을 정확하게 정의할 수 없어 &amp;quot;바이브코딩&amp;quot; 이라고 부르기도 하지만,&lt;/p&gt;
&lt;p&gt;이제 몇 년 지나지 않아 AI 와 협업하는 전문 엔지니어를 따로 부르는 언어가 생길 것이다.&lt;/p&gt;
&lt;p&gt;아직 나는 치열한 아키텍쳐 구현에 해당하는 실무를 겪지 않았다. 나는 내 미래를 알 수 없다.&lt;/p&gt;
&lt;p&gt;그러나 하나는 알고 있다. 이 모든 게 헛되지 않는다는 것을.&lt;/p&gt;
&lt;p&gt;비록 원하는 길이 나오지 않더라도, 항상 인생은 다른 길을 제시했던 것을 이전부터 많이 체험했기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;마지막으로 하고 싶은 말.&lt;/h2&gt;
&lt;p&gt;최근에 유튜브에 영화 &amp;quot;혜옥이&amp;quot; 라는 요약 영상이 떴다.&lt;/p&gt;
&lt;p&gt;굉장히 흥미있게 봤는데, &lt;/p&gt;
&lt;p&gt;고려대를 나온 여주인공이 부모님의 등살과 스스로의 선택으로 행정고시 5급을 준비하며,&lt;/p&gt;
&lt;p&gt;매년 동일한 실패를 겪으며 마주하는 &amp;quot;매몰비용&amp;quot; 에 대한 영화이다.&lt;/p&gt;
&lt;p&gt;붙지 않으면 그 동안의 청춘과 미래가 모두 &amp;quot;매몰비용&amp;quot; 으로서, 회수될 수 없다는 의미가 아니다.&lt;/p&gt;
&lt;p&gt;사람은, 쉽게 변하지 않는다. 그러나, 극단적인 상황에서 변한다.&lt;/p&gt;
&lt;p&gt;이 극단적인 상황은, 주변인과 주변 상황에 따라 벌어질 수도 있지만, &lt;/p&gt;
&lt;p&gt;누구에게나 벌어질 수 있는 상황에서, 스스로를 갉아먹는 상태로 전환시킬 수 있다.&lt;/p&gt;
&lt;p&gt;나는 대부분의 취준생이 이 상황에 놓여있다고 생각한다.&lt;/p&gt;
&lt;p&gt;내가 말하고 싶은 것은 극단적인 상황이라는 것은 스스로 결정하는 것이라고 생각한다.&lt;/p&gt;
&lt;p&gt;별 볼일 없는 일이 나 자신에게는 극단적일 수 있고,&lt;/p&gt;
&lt;p&gt;너무나 심각한 상황이 나에게는 별 볼일 없는 일이 될 수 있다.&lt;/p&gt;
&lt;p&gt;여러분에게 드리고 싶은 말씀은, 스스로의 가치를 놓치지 말고, 스스로를 인정하시고 받아들였으면 좋겠습니다.&lt;/p&gt;
&lt;p&gt;그렇다고 자만감을 가지진 마십시오. 자만감과 자신감은 한 끝 차이일 수 있겠으나,&lt;/p&gt;
&lt;p&gt;이 차이는 스스로가 자만감인지, 자신감인지 생각 하는 과정에서 나뉜다고 생각합니다.&lt;/p&gt;
&lt;p&gt;스스로 만든 세상을 부수고, 현실을 알게 된다는 게 얼마나 힘든지 알고 있습니다.&lt;/p&gt;
&lt;p&gt;제 글을 통해 여러분들이 응원을 받고, 스스로를 다시 돌아보셨으면 좋겠습니다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;-그저 수많은 사람 중 하나 일 뿐일 사람. 코딩크리처 블로그 운영자 올림.-&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;




&lt;br/&gt;

&lt;hr&gt;</description>
      <category>Ai</category>
      <category>ai agent</category>
      <category>AI Codex</category>
      <category>개발자</category>
      <category>대비</category>
      <category>엔지니어</category>
      <category>인공지능</category>
      <category>취업준비</category>
      <category>코더</category>
      <category>프로그래머</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/248</guid>
      <comments>https://codecreature.tistory.com/248#entry248comment</comments>
      <pubDate>Fri, 1 May 2026 19:11:30 +0900</pubDate>
    </item>
    <item>
      <title>Spring IoC 컨테이너를 내 방식으로 직접 만들기 (Java 에서 메타데이터를 극한으로 다루기)</title>
      <link>https://codecreature.tistory.com/247</link>
      <description>&lt;h2&gt;제목 : Spring Container, 다른 관점으로 &amp;quot;직접 제작 해보기&amp;quot;&lt;/h2&gt;
&lt;h3&gt;부제목 : 오로지 Java 와 &amp;quot;내장 API&amp;quot; 만 사용해서 만들기&lt;/h3&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;모든 글을 마무리 하고 나서 작성하는 문단&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Java 라는 언어를 전제로, 어떠한 언어든지 상관없는 &amp;quot;메타데이터&amp;quot; 라는 주제로 가벼운 프레임워크를 제작했습니다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;초급&lt;/strong&gt; 수준의 입문자라면, 언어의 문법과 콜스택 메모리, 힙 메모리에 대한 이해도가 필요하기 때문에,&lt;/p&gt;
&lt;p&gt;이 페이지를 &lt;strong&gt;&amp;quot;북마크&amp;quot;&lt;/strong&gt; 하신 후 천천~히 읽으시는 것을 매우 추천합니다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;중급 ~ 그 이상&lt;/strong&gt; 수준의 Java 및 컴퓨팅 이해도를 가지신 분이시라면,&lt;/p&gt;
&lt;p&gt;다른 시각에서 만들게 된 Light Framework 를 이해하게 만들기 위해 최대한 풀어서 설명했습니다.&lt;/p&gt;
&lt;p&gt;이 때문에 글이 길어지기도 했습니다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;깃허브 링크 주소&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://github.com/damhyeong/Custom-Spring-Container&quot;&gt;Custom-Container Framework&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;후반에 나올 내용이 이해되지 않을 때, 코드와 함께 본다면 이해가 쉬울 수 있습니다.&lt;/p&gt;
&lt;p&gt;그러나, &amp;quot;코드를 먼저 보는 것&amp;quot; 은 추천하지 않습니다.&lt;/p&gt;
&lt;p&gt;대부분의 어려운 기능에 Comment 를 달아놓았기 때문에, 이해가 되실 겁니다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;이 글을 작성하는 이유&lt;/h2&gt;
&lt;p&gt;단순히 특정 하나의 언어만을 사용하여 알고리즘 논리를 단련하거나,&lt;/p&gt;
&lt;p&gt;내가 원하는 형태의 조그마한 정보 덩어리(객체) 를 만들 수 있다.&lt;/p&gt;
&lt;p&gt;어떠한 요건 하에서라도, 조그마한 로직을 구현 할 때,&lt;/p&gt;
&lt;p&gt;우리는 프로그램이 실행되고 나서 종료되기까지의 데이터 생명주기를 어떤 형태던 작성하게 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;아마 이 글을 읽는 분이라면, &lt;strong&gt;Framework&lt;/strong&gt;, 라는 단어를 거의 무조건 알 것이라고 생각한다.&lt;/p&gt;
&lt;p&gt;프레임워크란 무엇인가? 정의를 내려본다면,&lt;/p&gt;
&lt;p&gt;프레임워크는 해당 프로그램, 혹은 코드 템플릿 그대로 완성되어 있다.&lt;/p&gt;
&lt;p&gt;그러나, 이 프레임워크를 사용하는 개발자 혹은 사용자의 시각에서는&lt;/p&gt;
&lt;p&gt;당연히 이 프로그램은 원하는 목적에 알맞게 완성되어 있지 않다.&lt;/p&gt;
&lt;p&gt;개발자, 사용자가 원하는 다양한 기능을 탑재하되, 이미 흐름(Flow) 는 완성되어 있다.&lt;/p&gt;
&lt;p&gt;그리고 대체로 프레임워크의 내부에 작성한 특정 논리가 사용자가 생각 한 대로 움직이지 않고,&lt;/p&gt;
&lt;p&gt;사용하는 프레임워크가 내부에 &amp;quot;비즈니스 로직&amp;quot; 이라는 개념으로 장착한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TB

Spring(&amp;quot;Spring - 디폴트 세팅부터 실행까지 이미 완성&amp;quot;)

My-Info(&amp;quot;내 비즈니스 로직 - 내가 원하는 목적을 이루기 위해 코드를 삽입&amp;quot;)

Custom-Spring(&amp;quot;Spring-커스텀화 &amp;lt;br/&amp;gt; 내 목적에 알맞은 Spring 이 됨.&amp;quot;)

Spring --&amp;gt; Custom-Spring
My-Info --&amp;gt; Custom-Spring&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이러한 기능 덕분에 사용자는 요청과 응답을 위한 데이터 처리 과정에만 집중 할 수 있다는 점이&lt;/p&gt;
&lt;p&gt;프레임워크 라고 말할 수 있다고 생각한다.&lt;/p&gt;
&lt;p&gt;(React 는 스스로 라이브러리라고 말하던데, 이 전략 덕분에 수많은 웹 프레임워크에서 사용하는듯.)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;세상에는 수많은 프레임워크가 존재한다.&lt;/p&gt;
&lt;p&gt;그러나, 대표적인 프레임워크 중 하나는 무엇인가? 하면 Spring 이라고 당당히 말할 수 있다.&lt;/p&gt;
&lt;p&gt;여기서 우리에게 스스로 질문을 던져보자. Java 를 잘한다는 가정 하에.&lt;/p&gt;
&lt;p&gt;&amp;quot;왜? 우리는 Spring 을 사용하는가?&amp;quot;&lt;/p&gt;
&lt;p&gt;Spring 은 너무 유명하고 많이 사용되니까, 또한 정답이라고 생각한다.&lt;/p&gt;
&lt;p&gt;취업에도 유리하고, 역사가 깊어 개발 리소스 참조하기에 정말 좋다.&lt;/p&gt;
&lt;p&gt;그러나, 사회적인 요인을 차단하고, Spring 을 사용하는 이유는 무엇인가를 생각 해 보자.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Spring 은 초 고도화 된 메타프로그래밍의 결과물이다.&lt;/h2&gt;
&lt;p&gt;나는 Spring 이 현대 프로그래밍 언어에 적용된 Meta 정보를 극한으로 재구성하여&lt;/p&gt;
&lt;p&gt;개발 생산성을 올려준 프레임워크라고 생각한다.&lt;/p&gt;
&lt;p&gt;나는 Spring 을 제외하고 제대로 사용한 프레임워크가 NestJS 이다.&lt;/p&gt;
&lt;p&gt;NestJS 공식문서에서는 Spring 을 잘 언급하지 않고, Vue 를 계승했다고 하는데,&lt;/p&gt;
&lt;p&gt;대부분의 로직이 Spring 과 매우매우 유사하다.&lt;/p&gt;
&lt;p&gt;(그렇다고 Java를 사용하는 상황에서 NestJS 로 건너갈 이유는 거의 없다고 생각한다. 굳이??)&lt;/p&gt;
&lt;p&gt;(만약 비즈니스 논리보다 웹페이지 로직에 더 치중되어 있다면, 사용할 수도 있다곤 생각한다.)&lt;/p&gt;
&lt;p&gt;하여튼, NestJS 는 TS 에서 제공하는 메타프로그래밍 기능을 이용하여,&lt;/p&gt;
&lt;p&gt;효과적으로 JS 로 트랜스파일링 하는 프레임워크이다.&lt;/p&gt;
&lt;p&gt;처음에는 구현된 인터페이스와 클래스를 역으로 추적하다가 메타프로그래밍을 인식하게 되었고,&lt;/p&gt;
&lt;p&gt;JavaScript 에서는 Decorator 라는 기능을 본격적으로 분석하고 글을 작성하여&lt;/p&gt;
&lt;p&gt;( 글 --&amp;gt; &lt;b&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/195&quot;&gt;타입스크립트와 데코레이터 - TypeScript With Decorator&lt;/a&gt;&lt;/b&gt; )&lt;/p&gt;
&lt;p&gt;어떤 방식으로 프레임워크가 메타 프로그래밍을 수행하는지 알게 되었다.&lt;/p&gt;
&lt;p&gt;이는 단순 메타프로그래밍 면에서만 보자면 Spring 과 큰 차이는 없다. (성능 차이는 크다..)&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;메타프로그래밍이란?&lt;/h3&gt;
&lt;p&gt;먼저 사전적 정의를 알아보자.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;자기 자신 혹은 다른 컴퓨터 프로그램을 데이터로 취급하며 프로그램을 작성,수정하는 것을 말한다.&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;메타프로그래밍이라는 단어에는 언어의 범주를 넘어선 의미가 존재하는데,&lt;/p&gt;
&lt;p&gt;이러한 의미를 제외하고, 일반적인 프로그래밍 언어의 메타프로그래밍이라는 의미에 집중하겠다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;정말 저수준의 언어, Assembly, C 와 같은 언어를 제외하고,&lt;/p&gt;
&lt;p&gt;현대적 언어들은 메타프로그래밍을 위한 대부분의 기능을 지원한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;예를 들어 우리가 작성한 Java 클래스, 메서드, 변수, enum, ... 등등이 있다고 가정 해 보자.&lt;/p&gt;
&lt;p&gt;그런데, 우리가 Spring 프레임워크에서 코드를 작성 할 때,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;main&lt;/code&gt; 기능을 하는 코드에 &amp;quot;코드로 직접&amp;quot; 선언 한 적이 있나?&lt;/p&gt;
&lt;p&gt;그런데 어떻게 프로그램 실행 후, 내가 작성한 로직이 실행 될 수 있는 걸까?&lt;/p&gt;
&lt;p&gt;Spring 은 자동적으로 애너테이션과 그 내부에 작성된 정보를 인식하여 재구성한다.&lt;/p&gt;
&lt;p&gt;어떻게 이런 일이 가능할까?&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;클래스, 메서드, 변수 모두 메타데이터로 치환한다.&lt;/h3&gt;
&lt;p&gt;Spring 은 우리가 애너테이션으로 선언한 코드들을 모두 메타데이터로 치환한다.&lt;/p&gt;
&lt;p&gt;클래스 이름은 무엇인지, 어떤 메서드를 가지고 있는지, 어떤 생성자를 가지는지, 등등.&lt;/p&gt;
&lt;p&gt;메서드의 경우라면, 메서드 이름과 내부 인자, 그리고 인자의 타입과 그 수, 반환 타입을 인식한다.&lt;/p&gt;
&lt;p&gt;변수 또한 마찬가지이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;즉 이 현상을 요약 해 보자면,&lt;/p&gt;
&lt;p&gt;우리가 Spring 이 시작되는 &lt;code&gt;main&lt;/code&gt; 급에 되는 실행 코드에 굳이&lt;/p&gt;
&lt;p&gt;따로 작성한 코드를 주입하지 않더라도, 선언한 애너테이션에 의해 인식되어&lt;/p&gt;
&lt;p&gt;Spring 이라는 완성된 프레임워크에 개발자의 코드를 스스로 &amp;quot;주입&amp;quot;(Injection) 한다.&lt;/p&gt;
&lt;p&gt;물론 이 &amp;quot;주입&amp;quot; 과정이 모두 같지는 않다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;공통점 : 프로그램의 흐름을 직접 조작하지 않으며, 맡긴다.&lt;/li&gt;
&lt;li&gt;차이점 : 프로그램 외부, 내부, 혹은 과정 등등 변경하는 역할들이 다르다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;어떤 차이가 존재하는지 예를 들어 보자.&lt;/p&gt;
&lt;p&gt;예시 1. &lt;code&gt;@Component&lt;/code&gt;, &lt;code&gt;@Service&lt;/code&gt;, &lt;code&gt;@Controller&lt;/code&gt;, 등등&lt;/p&gt;
&lt;p&gt;Spring 을 찍어서 먹어 본 사람이라도 위의 몇 가지는 분명히 본 적이 있을 것이다.&lt;/p&gt;
&lt;p&gt;(생명주기 Scope 가 선언되지 않았다는 가정.)&lt;/p&gt;
&lt;p&gt;일반적인 상황으로 가정했을 때, 위의 Annotation 들은&lt;/p&gt;
&lt;p&gt;현재 Spring Project 내에서, &amp;quot;단 하나의 객체&amp;quot; 만이 존재함을 보장한다.&lt;/p&gt;
&lt;p&gt;이는 Singleton 패턴과 큰 연관이 존재한다.&lt;/p&gt;
&lt;p&gt;Spring 에는 Spring Container 가 존재한다. 그리고, 여기에 유일 객체들이 저장되어 있다.&lt;/p&gt;
&lt;p&gt;그리고 이들은 단순히 요청 데이터를 어떻게 받아들이고 변형하는지,&lt;/p&gt;
&lt;p&gt;그리고 그 데이터에 따라 어떻게 응답할지를 고려한다. ==&amp;gt; 비즈니스 로직&lt;/p&gt;
&lt;p&gt;프로젝트는 매우 크고 복잡할 수 있다. 그렇다면 필요한 모든 로직 클래스를 생성해야 할까?&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;만약에 유일 객체가 아닌 요청 때 마다 새로 원하는 객체를 생성한다면,&lt;/p&gt;
&lt;p&gt;복잡도에 따라 최소 수십배의 컴퓨팅 리소스를 잡아먹을 것이다.&lt;/p&gt;
&lt;p&gt;따라서, 이들을 유일 객체로 생성 한 뒤 Spring Container 에 저장하고,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@Autowired&lt;/code&gt; 된 생성자 메서드나 변수에 접근하여&lt;/p&gt;
&lt;p&gt;클래스에 필요한 객체를 &amp;quot;생성하지 않고&amp;quot;, &amp;quot;클래스 주소를 등록&amp;quot; 해 준다.&lt;/p&gt;
&lt;p&gt;위의 방식으로 리소스를 매우 효율적으로 절약 해 준다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;예시 2. &lt;code&gt;@Transactional&lt;/code&gt;, &lt;code&gt;@Entity&lt;/code&gt;, ..&lt;/p&gt;
&lt;p&gt;일단 위의 2 가지 DB 와 관련된 Annotation 이 가장 차별점을 두기에 적합하다고 판단했다.&lt;/p&gt;
&lt;p&gt;예시 1 의 애너테이션들은 모두 Spring Container, Singleton, Autowired 와 관련되어 있다.&lt;/p&gt;
&lt;p&gt;즉, 프로그램 시작과 더불어 컴퓨팅 리소스 최적화와 의존성에 초점이 맞춰져 있다면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@Transactional&lt;/code&gt;, &lt;code&gt;@Entity&lt;/code&gt; 이 둘은 다른 영역을 담당한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;@Transactional&lt;/code&gt; 은, 데이터베이스에 요청을 날렸을 때,&lt;/p&gt;
&lt;p&gt;그 에러에 대해 어떻게 반응 할 것인지를 사용자가 직접 선언하는 것이다.&lt;/p&gt;
&lt;p&gt;데이터베이스에 특정 쿼리를 날렸을 때, 오류가 나면, 이것을 rollback 할 것인지, commit 할 것인지.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@Entity&lt;/code&gt; 는 Spring 과 연결된 데이터베이스와 데이터를 요청 및 응답함에 있어&lt;/p&gt;
&lt;p&gt;&amp;quot;이 형태로 주거니 받거니 하겠다&amp;quot; 를 선언하는 애너테이션이다.&lt;/p&gt;
&lt;p&gt;내부에는 Primary Key 선언, 컬럼 형태, 속성 및 이름을 지정하는 애너테이션이 또 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@Entity&lt;/code&gt; 에 속하는 클래스는 내부의 모든 정보를 메타정보로 치환당하는 입장으로서,&lt;/p&gt;
&lt;p&gt;데이터베이스와 원활하게 소통하는 일종의 정해진 템플릿 데이터가 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;헷갈림을 방지하기 위한 요약을 하자면,&lt;/h3&gt;
&lt;p&gt;극도로 저 수준의 언어를 제외하고 대부분의 경우 저마다의 메타프로그래밍 지원 내장 문법과 기능이 존재하며,&lt;/p&gt;
&lt;p&gt;Spring 프레임워크의 경우, 이 메타프로그래밍 기법들을 극한으로 사용한 프로그램 템플릿이라는 것이다.&lt;/p&gt;
&lt;p&gt;Spring 은 우리가 따로 원활한 실행을 위한 생명주기를 작성 할 필요가 없다. 이미 완성되어 있다.&lt;/p&gt;
&lt;p&gt;우리가 원하는 목적을 위해, 이 Spring 이라는 프레임워크에 비즈니스 논리(우리가 작성한 코드) 를&lt;/p&gt;
&lt;p&gt;꽂아넣어 목적을 완성하는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;또한, 위에서 예시로 든 메타프로그래밍이란, 내장된 언어의 기능과 Spring 프레임워크가&lt;/p&gt;
&lt;p&gt;내부에 기입된 Annotation 들을(&lt;code&gt;@&lt;/code&gt;) 인식하여,&lt;/p&gt;
&lt;p&gt;클래스, 메서드, 변수 등등 가릴 것 없이 내부의 정보들을 &amp;quot;메타데이터&amp;quot; 의 형태로 분석한다.&lt;/p&gt;
&lt;p&gt;그리고 최적화 된 형태로 나의 코드를 Spring 프레임워크의 흐름에 넣어주는 것이다.&lt;/p&gt;
&lt;p&gt;이것이 바로 우리가 Spring 의 &lt;code&gt;main&lt;/code&gt; 메서드 급 되는 장소에&lt;/p&gt;
&lt;p&gt;우리가 만든 Component, Controller, Service 등등의 클래스를 따로 선언하지 않고도,&lt;/p&gt;
&lt;p&gt;애너테이션 만으로도 Spring 이 흩어져 있는 코드들을 인식하여 Spring Containerized 하는&lt;/p&gt;
&lt;p&gt;결과가 나오는 것이다.&lt;/p&gt;
&lt;p&gt;그리고, 메타프로그래밍은 예시로 들어준 기능을 제외하고 너무나도 많은 활용 사례가 존재한다.&lt;/p&gt;
&lt;p&gt;AOP, Proxy, Intercept, Request Scoping, Isolatated, 등등..&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Java 로 메타프로그래밍을 구현 해 보자.&lt;/h2&gt;
&lt;p&gt;이제 Java 로 메타프로그래밍을 수행하기 위해 먼저 2 시간정도의 조사를 실행하며 익혔는데,&lt;/p&gt;
&lt;p&gt;&amp;quot;이거 제대로 설명 할 수 있을까?&amp;quot; 라는 생각이 들 정도로 복잡하다.&lt;/p&gt;
&lt;p&gt;물론 끝내 작성하겠지만, 어느 정도의 선에서 마무리해야 하는지 결정해야 할 문제인 것 같다.&lt;/p&gt;
&lt;p&gt;기능이 워낙 많아, Psuedo 코드로는 표현하면 안될 것 같고,&lt;/p&gt;
&lt;p&gt;기능이 정확히 동작함으로 나의 설명이 정확함을 인증해야 한다.&lt;/p&gt;
&lt;p&gt;따라서, 직접 작성한 예시 코드와 더불어 결과를 같이 보여줄 생각이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;그리고 이 글은 내가 보여주는 코드 중 역대급 어려운 코드가 될 것 같다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;만약에 이 글을 촘촘히 읽고 계신 분이 계시다면, 저 또한 같이 공부하는 입장이니,&lt;/p&gt;
&lt;p&gt;천천히 읽어주시면 될 것 같습니다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;반드시 알고 넘어가야 할 개념&lt;/h2&gt;
&lt;p&gt;결국 앞에서 알게 될 모든 개념은 Spring 템플릿이 수행 해 주던 일부의 기능을&lt;/p&gt;
&lt;p&gt;우리가 직접 제작하는 것이라서, 단순히 코드를 작성하고 넘기기에는 너무 중요하다고 판단이 되었다.&lt;/p&gt;
&lt;p&gt;확실한 건, Spring 프레임워크는 개발 도구로서, 결국에는 JVM 과 Java (타 언어가 될 수도 있겠지만)&lt;/p&gt;
&lt;p&gt;이 두 가지에 핵심을 두고 있다는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;1. package - 패키지란?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;(패키지들은 유일성을 가져야 한다.)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;우리가 테스팅용으로 따로 덩그러니 &lt;code&gt;xxx.java&lt;/code&gt; 해당 파일만 생성하지 않고&lt;/p&gt;
&lt;p&gt;대부분의 경우 Java 프로젝트에는 특정 패키지 이름이 들어가게 된다.&lt;/p&gt;
&lt;p&gt;예를 들어, &lt;code&gt;com.example.&amp;lt;원하는 마지막 패키지 이름&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;이러한 패키지를 우리는 Spring Initializer 사이트에서 생성한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;나는 그러한 생각을 했었는데, 도대체 왜, 간단히 Root(&lt;code&gt;.&lt;/code&gt;) 나, &lt;code&gt;test&lt;/code&gt; 라는 간단한 패키지명으로&lt;/p&gt;
&lt;p&gt;생성하지 않고,&lt;/p&gt;
&lt;p&gt;Convention(관례)적으로 &lt;code&gt;com.xxx.xxx...&lt;/code&gt; 으로 시작을 정하는지가 매우 궁금했다.&lt;/p&gt;
&lt;p&gt;(물론 혼자만의 프로젝트를 생성한다면, 아주 간단한 패키지명이 가능하다.)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;결과적으로 복잡한 패키지명을 가지는 이유는, Maven Central(메이븐 의존성 라이브러리 저장소)&lt;/p&gt;
&lt;p&gt;에 특정 단체나 회사가 만든 공식 라이브러리를 올릴 때, 시작 패키지명과 제작 단체의 공식 사이트와 연계되어 있다.&lt;/p&gt;
&lt;p&gt;이를 이용하여 Maven Central 에 존재하는 라이브러리들은 &amp;quot;패키지명&amp;quot; 을 따른다면,&lt;/p&gt;
&lt;p&gt;이들은 각자 자동적으로 &amp;quot;유일한&amp;quot; 패키지명을 가지게 된다.&lt;/p&gt;
&lt;p&gt;따라서 누군가 라이브러리(의존성) 을 다운로드 했을 때, 패키지명이 중첩 될 확률은 제로에 수렴한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;(클래스를 인식하기 위해서는 패키지 이름이 필요하다.)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;만약에 어떤 의존 파일이 없이 덩그러니 하나의 자바 파일이 있고,&lt;/p&gt;
&lt;p&gt;여기서 본인 스스로의 클래스를 인식하고자 한다면, Root Package(&lt;code&gt;.&lt;/code&gt;) 를 인식해야 한다.&lt;/p&gt;
&lt;p&gt;애초에 패키지를 지정한 적이 없기 때문이다.&lt;/p&gt;
&lt;p&gt;그리고 패키지의 이름은 디렉토리 계층과 동일하다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;com.codecreature.test&lt;/code&gt; 라는 패키지명을 가진 &lt;code&gt;Testing&lt;/code&gt; 자바 파일이 존재한다고 가정하자.&lt;/p&gt;
&lt;p&gt;그렇다면, 위의 상황은 다음과 같이 귀결된다.&lt;/p&gt;
&lt;p&gt;파일을 인식하기 시작하는 시작점 + &lt;code&gt;com/codecreature/test/Testing.java&lt;/code&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;보통 Spring 의 경우, &lt;code&gt;&amp;lt;Root&amp;gt;src/main/java&lt;/code&gt; 부터 지정된 패키지명, &lt;code&gt;com.codecreature.test&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;를 인식하며, 이 곳에 보통 &lt;code&gt;@SpringBootApplication&lt;/code&gt; 이라는 애너테이션이 붙은 클래스가 존재한다.&lt;/p&gt;
&lt;p&gt;이 곳에서부터 Spring 은 하위 패키지를 탐색한다. (필요시 다중 선언도 가능)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나 Spring 이 아닌 나만의 프로젝트를 Default Package 로 선언하게 된다면,&lt;/p&gt;
&lt;p&gt;나중에 목적 분리를 위해 패키지를 선언했을 경우, Default Package 에 존재하는 기능들을&lt;/p&gt;
&lt;p&gt;&lt;code&gt;import ...&lt;/code&gt; 할 수 없다는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;2. ClassLoader 란?&lt;/h3&gt;
&lt;p&gt;우리가 사실상 Spring 에 비즈니스 논리를 쉽게 적용할 수 있는 기능은 바로,&lt;/p&gt;
&lt;p&gt;이 클래스 로더(ClassLoader) 덕분이라고 볼 수 있다.&lt;/p&gt;
&lt;p&gt;Java 에서 클래스 로더가 하는 역할은 우리가 만들어 놓은 객체들을 가져오는 역할을 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;어떻게 가져올까?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;위에서 &lt;code&gt;package&lt;/code&gt; 에 대한 정보를 길게 나열했던 이유는, 바로 이 ClassLoader 의&lt;/p&gt;
&lt;p&gt;&amp;quot;객체 인식 방법&amp;quot;이 우리가 직관적으로 이해할 수 있는 것과 많이 다르고,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;package&lt;/code&gt; 로서 인식하기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;예를 들어, &lt;code&gt;C&lt;/code&gt; 나, &lt;code&gt;JavaScript&lt;/code&gt; 의 경우,&lt;/p&gt;
&lt;p&gt;우리는 직관적인 디렉토리 접근법을 통해,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;C&lt;/code&gt; : &lt;code&gt;#include &amp;quot;..../xx.h&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;JS&lt;/code&gt; : &lt;code&gt;import &amp;quot;..../xxx.js&amp;quot;&lt;/code&gt; or &lt;code&gt;import &amp;quot;..../xxx&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;위와 같은 방식으로, 상대 경로를 통해 기능을 병합하거나, 불러 올 수 있다.&lt;/p&gt;
&lt;p&gt;그러나, Java 는 다르다.&lt;/p&gt;
&lt;p&gt;우리가 디렉토리와 &amp;quot;패키지&amp;quot; 경로는 동일하다고 했지만,&lt;/p&gt;
&lt;p&gt;불러오는 Root 는 정해져 있다. --&amp;gt; Java 와 JVM 의 특성 (&lt;code&gt;ClassPath&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;따라서, 상대 경로로 불러오는 것이 아니라, 프로젝트에서 Root 로 정해진 디렉토리로부터,&lt;/p&gt;
&lt;p&gt;해당 Root(&lt;code&gt;ClassPath&lt;/code&gt;) 로부터 뻗어있는 디렉토리, 즉, 패키지를 통해 인식한다는 것이다.&lt;/p&gt;
&lt;p&gt;(여기서 말하는 패키지란, 프로젝트 내부의 패키지만을 의미한다.)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그렇다면, 우리는 먼저 자주 사용하던 Java 의 &lt;code&gt;import&lt;/code&gt; 를 떠올려야 한다.&lt;/p&gt;
&lt;p&gt;만약에 &lt;code&gt;com.damsoon.func&lt;/code&gt; 에 &lt;code&gt;FuncClass.java or .class&lt;/code&gt; 가 있고,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;com.damsoon.test&lt;/code&gt; 에 &lt;code&gt;TestClass.java or .class&lt;/code&gt; 가 있다고 가정 해 보자.&lt;/p&gt;
&lt;p&gt;(&lt;code&gt;.java&lt;/code&gt; 는 가독성을 위함이며, 실제 런타임의 경우 &lt;code&gt;.class&lt;/code&gt;가 정확합니다.)&lt;/p&gt;
&lt;br/&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;import com.damsoon.func.FuncClass&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;import com.damsoon.test.TestClass&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이러한 형식으로 2 클래스를 불러오게 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;먼저 파일의 구조를 살펴보자&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;# tree 명령어는 따로 설치해야 합니다. macOS 의 경우 brew 패키지 매니저 추천
➜  com tree
.
└── damsoon
    ├── Main.java
    ├── func
    │   └── FuncClass.java
    └── test
        └── TestClass.java

4 directories, 3 files&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;FuncClass.java&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package com.damsoon.func;

public class FuncClass {
    public FuncClass() {
        System.out.println(&amp;quot;생성자 실행 : &amp;quot; + getClass().getSimpleName());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;TestClass.java&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package com.damsoon.test;

public class TestClass {
    public TestClass () {
        System.out.println(&amp;quot;생성자 실행 : &amp;quot; + getClass().getSimpleName());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Main.java&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package com.damsoon;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Main {
    // 추가되어야 할 Exception 이 너무 많아 Exception 으로 작성.
    public static void main(String[] args) throws Exception {
        // 현재 스레드의 클래스로더를 가져온다.
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

        // class 라는 단어는 &amp;quot;예약어&amp;quot; 이므로, clazz 라는 이름으로 메타데이터임을 알린다.
        Class&amp;lt;?&amp;gt; funcClazz = classLoader.loadClass(&amp;quot;com.damsoon.func.FuncClass&amp;quot;);
        Class&amp;lt;?&amp;gt; testClazz = classLoader.loadClass(&amp;quot;com.damsoon.test.TestClass&amp;quot;);

        // FuncClass, TestClass 의 기본 생성자를 가져온다.
        Constructor&amp;lt;?&amp;gt; funcConstructor = funcClazz.getDeclaredConstructor();
        Constructor&amp;lt;?&amp;gt; testConstructor = testClazz.getDeclaredConstructor();

        // new FuncInstance(); 와 동일한 동작.
        Object funcInstance = funcConstructor.newInstance();
        // new TestConstructor(); 와 동일한 동작.
        Object testInstance = testConstructor.newInstance();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Command&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;# com 디렉토리를 가진 위치에서 이를 실행한다.
# -d 옵션을 통해 .class 출력 디렉토리를 설정하지 않음.
# 따라서, javac 는 .java 와 .class 를 동일 장소에 배치하는 상황이 된다.
$ javac com/damsoon/*.java com/damsoon/func/*.java com/damsoon/test/*.java&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  test-1 tree
.
├── com
│   └── damsoon
│       ├── Main.class
│       ├── Main.java
│       ├── func
│       │   ├── FuncClass.class
│       │   └── FuncClass.java
│       └── test
│           ├── TestClass.class
│           └── TestClass.java&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;위의 명령어, &lt;code&gt;javac&lt;/code&gt; 와 더불어 적힌 모든 패키지 경로들은&lt;/p&gt;
&lt;p&gt;해당 패키지 구조(폴더구조) 를 유지하며, 각 계층의 파일을 &lt;code&gt;xxx.class&lt;/code&gt; 로 만든다.&lt;/p&gt;
&lt;p&gt;위의 구조는 &lt;code&gt;.java&lt;/code&gt;, &lt;code&gt;.class&lt;/code&gt; 가 공존하는 일종의 &amp;quot;테스팅 결과물&amp;quot;임을 절대로 잊으면 안된다.&lt;/p&gt;
&lt;p&gt;즉, 폴더(패키지) 구조를 편하게 재사용하려고 이렇게 만든 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;실행 방법&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;javac&lt;/code&gt; 명령어로 각 파일을 &lt;code&gt;.class&lt;/code&gt; 파일들로 &amp;quot;컴파일&amp;quot; 했다.&lt;/p&gt;
&lt;p&gt;&amp;quot;실행&amp;quot; 을 위해서는 &lt;code&gt;java ..&lt;/code&gt; 로 실행하는데,&lt;/p&gt;
&lt;p&gt;위에서 말했듯, JVM 은 ClassPath 가 필요하다.&lt;/p&gt;
&lt;p&gt;ClassPath 란, &lt;code&gt;com.damsoon...&lt;/code&gt; 이 패키지 구조, 즉 폴더 구조가 안착한&lt;/p&gt;
&lt;p&gt;디렉토리의 경로를 의미한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  test-1 tree
.
├── com
│   └── damsoon
│       ├── Main.class
│       ├── Main.java
│       ├── func
│       │   ├── FuncClass.class
│       │   └── FuncClass.java
│       └── test
│           ├── TestClass.class
│           └── TestClass.java&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;com.damsoon...&lt;/code&gt; 패키지가 안착한 디렉토리는, 바로 &lt;code&gt;test-1&lt;/code&gt; 이다.&lt;/p&gt;
&lt;p&gt;우리가 경로 명령어를 사용할 때, 절대 경로와 상대 경로를 지정하듯,&lt;/p&gt;
&lt;p&gt;2 가지의 실행 방식이 존재한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;# 윈도우의 경우 &amp;#39;${pwd}&amp;#39; 로 작성해야 합니다.
➜  test-1 java -cp $(pwd) com.damsoon.Main
생성자 실행 : FuncClass
생성자 실행 : TestClass&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  test-1 java -cp . com.damsoon.Main
생성자 실행 : FuncClass
생성자 실행 : TestClass&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;사실 &lt;code&gt;java ./com/damsoon/Main.java&lt;/code&gt; 를 해도 동일한 결과가 나온다.&lt;/p&gt;
&lt;p&gt;이를 따로 소개하지는 않았는데, JVM 추가 기능으로서, &lt;code&gt;.java&lt;/code&gt; 파일을 즉시 &lt;code&gt;.class&lt;/code&gt; 로 바꾸고,&lt;/p&gt;
&lt;p&gt;그 결과물을 남기지 않으며, 추가되지 않은 패키지 정보들을 &amp;quot;알아서&amp;quot; 처리해 주는 마법같은 기능이기 때문이다.&lt;/p&gt;
&lt;p&gt;그러나, 마법같은 기능에는 더 많은 리소스가 필요하기 마련이다.&lt;/p&gt;
&lt;p&gt;이미 &lt;code&gt;.class&lt;/code&gt; 파일로 컴파일 된 상황과, 항상 &lt;code&gt;.java&lt;/code&gt; 파일을 캐싱하는 것은 크나큰 차이가 존재한다.&lt;/p&gt;
&lt;p&gt;따라서,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;컴파일&lt;/li&gt;
&lt;li&gt;실행&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 2 가지 단계로 나누어서 실행된다는 것을 잊지 않는 것이 좋다. (IDE 가 대신 수행 해 준다.)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;다시 돌아와서,&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;javac &amp;lt;컴파일할 위치1&amp;gt; &amp;lt;컴파일 위치2&amp;gt; &amp;lt;...&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;위의 명령어로 &lt;strong&gt;Compile&lt;/strong&gt; 을 수행하여 계층을 유지하는 &lt;code&gt;.class&lt;/code&gt; 파일들을 생성했으며,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;java -cp . com.damsoon.Main&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;위의 명령어로 &amp;quot;현재 위치까지가 ClassPath&amp;quot; 이며, 해당 위치의 &lt;code&gt;com.damsoon.Main&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;파일에서 JVM 가동하겠다는 명령을 내렸다.&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;#&lt;strong&gt;단, 여기서 무조건 고쳐야 할 명령어가 있다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;위의 명령어들은 잘 수행되었지만, &lt;code&gt;.java&lt;/code&gt;, &lt;code&gt;.class&lt;/code&gt; 파일이 혼재하는 상황이다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  test-1 tree
.
├── com
│   └── damsoon
│       ├── Main.class
│       ├── Main.java
│       ├── func
│       │   ├── FuncClass.class
│       │   └── FuncClass.java
│       └── test
│           ├── TestClass.class
│           └── TestClass.java&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;클라우드나, 사설 서버에 올린다고 가정 할 때,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.java&lt;/code&gt; 를 사용하지 않는다.(대부분의 경우)&lt;/p&gt;
&lt;p&gt;JVM 은 &lt;code&gt;.class&lt;/code&gt; 를 load 하여 사용하기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;따라서, 우리는 컴파일 시, 따로 생성할 디렉토리를 지정 해 주어야 한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;나의 경우,&lt;/strong&gt; &lt;code&gt;result/bin&lt;/code&gt; 이라는 예시에 계층을 유지한 &lt;code&gt;.class&lt;/code&gt; 파일을 생성해 보겠다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ javac -d  \ # shell 사용 시, 커맨드가 길다면 &amp;quot;\&amp;quot; 를 사용하는 습관을 길들여 보자.
./result/bin \ # 보기에도 편하고, 명령어 실패 시 어디가 틀렸는지 확인하기 좋다.
com/damsoon/*.java com/damsoon/test/*.java com/damsoon/func/*.java&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  test-1 tree
.
├── com # .java 가 존재하는 위치
│   └── damsoon
│       ├── ...
├── result
│   └── bin # .class 파일들이 존재하는 위치
│       └── com
│           └── damsoon
│               ├── Main.class
│               ├── func
│               │   └── FuncClass.class
│               └── test
│                   └── TestClass.class&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 결과를 보면,&lt;/p&gt;
&lt;p&gt;내가 결과물 디렉토리로 선정한 &lt;code&gt;result/bin&lt;/code&gt; 을 생성하고,&lt;/p&gt;
&lt;p&gt;그 하위에 &amp;quot;패키지 계층&amp;quot; 과 동일하게 형성된 &lt;code&gt;.class&lt;/code&gt; 파일들을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;그리고, 이 위치에서 &lt;code&gt;java ...&lt;/code&gt; 를 사용하는 것이다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;# 현재 생성된 result/bin 에 들어와 있는 상황
➜  bin java -cp . com.damsoon.Main
생성자 실행 : FuncClass
생성자 실행 : TestClass&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;혹은&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;# 생성된 디렉토리에 가지 않고,
# 해당 디렉토리를 ClassPath 로 설정하여 내부 class 파일을 실행한다.
➜  test-1 java -cp ./result/bin com.damsoon.Main
생성자 실행 : FuncClass
생성자 실행 : TestClass&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;즉, 컴파일 결과물 &lt;code&gt;.class&lt;/code&gt; 들은, 내가 &lt;code&gt;javac -d&lt;/code&gt; 로 설정한 &lt;code&gt;&amp;lt;root&amp;gt;/result/bin&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;에 계층적으로 저장된다.&lt;/p&gt;
&lt;p&gt;그리고 해당 디렉토리를 중심으로 ClassPath 를 구성하고, 실행하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;(기존에 혼재하던 .java, .class 중에서 .class 만 지우자.)&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;위에서 나는 이러한 주제들을 다루었다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;메타프로그래밍의 의미&lt;/li&gt;
&lt;li&gt;Java 에게 있어 Package Name 의 의미와 중요성&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ClassLoader&lt;/code&gt; 를 가져와서 사용하는 방법 그리고, &lt;code&gt;ClassLoader&lt;/code&gt; 의 클래스 인식 방법&lt;/li&gt;
&lt;li&gt;컴파일 방법과 실행 방법&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이제 위의 기반 지식들로 충분히 Reflection API 를 설명 할 수 있으리라 믿는다.&lt;/p&gt;
&lt;p&gt;(또한 독자 분들도 이해하시리라 믿고, 모르면 왼쪽 프로파일의 &lt;code&gt;Email&lt;/code&gt; 로 질문 보내주세요)&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;3. Reflection - 리플렉션&lt;/h3&gt;
&lt;p&gt;클래스의 메타데이터와 함께 세트로 Spring 에서 활동하는 매우 중요한 개념이다.&lt;/p&gt;
&lt;p&gt;이 글의 초반에 나는&lt;/p&gt;
&lt;p&gt;&amp;quot;우리는 비즈니스 논리를 main 급의 메서드에 직접 선언 한 적이 없다&amp;quot; 고 말했다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;즉, 누군가는 우리의 비즈니스 논리를 직접 넣어주었다는 이야기이다.&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;그 누군가가 바로 Reflection API 이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;Reflection API 를 누구한테 설명하느냐에 따라서 그 예시가 달라질 수 있다고 생각하는데,&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;만약에 JavaScript 를 사용 해 본 분이라면,&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;생성자를 갈아끼우거나, 메서드, 또다른 내부 객체, 변수 조작 등등..&lt;/p&gt;
&lt;p&gt;엄청난 자유도를 보이는데, 마치 언어 자체의 규칙이 어디까지인가 신기 할 정도이다.&lt;/p&gt;
&lt;p&gt;물론, JS 에도 한계가 존재하며, 문법적 설탕(Synthetic Sugar)이 매우 많기 때문에,&lt;/p&gt;
&lt;p&gt;TypeScript 로 이를 완화할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;당연히, Java 는 JS 보다 타입에 있어 비교할 수 없는 엄격함을 보인다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;우리가 처음 Java 를 사용할 때를 기억 해 보자&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;클래스를 직접 지정하고,&lt;/p&gt;
&lt;p&gt;어떤 클래스를 확장 혹은 구현하며,&lt;/p&gt;
&lt;p&gt;이 클래스는 어떤 변수, 메서드, 생성자를 가지며....&lt;/p&gt;
&lt;p&gt;이들을 직접 컴파일 되기 전, 개발하면서 작성한다.&lt;/p&gt;
&lt;p&gt;그런데, Reflection API 는,&lt;/p&gt;
&lt;p&gt;런타임 시, 위의 로직을 수행하므로, 거꾸로 생각하면 된다.&lt;/p&gt;
&lt;p&gt;&amp;quot;내가 직접&amp;quot; 조작하는 것이 아니라, &lt;strong&gt;&amp;quot;프로그램이&amp;quot; 조작한다고 생각해야 한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;그러나, JS 만큼의 자유도는 아니라는 것을 반드시 기억해야 한다.&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;위에서 보았던 코드 예제를 바꾸어 보자.&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;(여기서 &lt;code&gt;TestClass&lt;/code&gt; 는 &lt;code&gt;FuncClass&lt;/code&gt; 를 의존성으로 필요로 한다는 가정을 하겠습니다.)&lt;/p&gt;
&lt;br/&gt;

&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package com.damsoon.test;

public class TestClass {
    private FuncClass funcClass;

    // TestClass 는 FuncClass 를 의존성으로 삼기 때문에 필요합니다.
    public TestClass (FuncClass funcClass) {
        System.out.println(&amp;quot;생성자 실행 : &amp;quot; + getClass().getSimpleName());
        // TestClass 는 FuncClass 의존성이 존재함.
        this.funcClass = funcClass;
    }

    // getClass() 는 this.getClass() 와 동일합니다.
    public String toString() {
        return &amp;quot;[Class] : &amp;quot; + getClass().getSimpleName();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package com.damsoon.func;

public class FuncClass {
    public FuncClass() {
        System.out.println(&amp;quot;생성자 실행 : &amp;quot; + getClass().getSimpleName());
    }

    public String toString() {
        return &amp;quot;[Class] : &amp;quot; + getClass().getSimpleName();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;TestClass&lt;/code&gt; &amp;amp;&amp;amp; &lt;code&gt;FuncClass&lt;/code&gt; :&lt;/p&gt;
&lt;p&gt;클래스 확인용 &lt;code&gt;toString()&lt;/code&gt; 을 &lt;code&gt;@Override&lt;/code&gt; 한 것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;toString()&lt;/code&gt; 실행 시, 실행된 해당 클래스의 패키지 클래스 이름이 아닌, 단순 클래스가 나온다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, &lt;code&gt;TestClass&lt;/code&gt; 는 &lt;code&gt;FuncClass&lt;/code&gt; 타입 변수를 내부에 두고,&lt;/p&gt;
&lt;p&gt;생성자에서 &lt;code&gt;FuncClass&lt;/code&gt; 타입 인자를 받고 있다.&lt;/p&gt;
&lt;p&gt;즉, &lt;code&gt;TestClass&lt;/code&gt; 는 &lt;code&gt;FuncClass&lt;/code&gt; 의존성이 존재한다.&lt;/p&gt;
&lt;br/&gt;

&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package com.damsoon;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class Main {
    // 추가되어야 할 Exception 이 너무 많아 Exception 으로 작성.
    public static void main(String[] args) throws Exception {
        // 현재 스레드의 클래스로더를 가져온다.
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

        // 공식문서에서 clazz 를 사용하는 것을 보면, 클래스 메타데이터는 보통 이렇게 선언하는 것이 Convention 이라는 것을 말한다.
        Class&amp;lt;?&amp;gt; funcClazz = classLoader.loadClass(&amp;quot;com.damsoon.func.FuncClass&amp;quot;);
        Class&amp;lt;?&amp;gt; testClazz = classLoader.loadClass(&amp;quot;com.damsoon.test.TestClass&amp;quot;);

        // 인자가 없는 FuncClass 생성자 가져오기
        Constructor&amp;lt;?&amp;gt; funcConstructor = funcClazz.getDeclaredConstructor();
        // 인자가 1 개인 TestClass 생성자 가져오기 - 인자 없으면 오류나요
        Constructor&amp;lt;?&amp;gt; testConstructor = testClazz.getDeclaredConstructor(funcClazz);

        // 각 클래스의 생성자를 이용하여 인스턴스를 생성 및 할당.
        Object funcInstance = funcConstructor.newInstance();
        Object testInstance = testConstructor.newInstance(funcInstance);

        // 각 클래스에 선언되어 있는 public 메서드, &amp;quot;toString&amp;quot; 을 가져오기
        // 인자가 있다면, 이름 뒤에 인자들을 int.class, String.class 식으로 나열해야 합니다.
        // ex - funcClazz.getMethod(&amp;quot;something&amp;quot;, int.class, String.class)
        Method funcToString = funcClazz.getMethod(&amp;quot;toString&amp;quot;);
        Method testToString = testClazz.getMethod(&amp;quot;toString&amp;quot;);

        // 추출된 메서드는 각 &amp;quot;인스턴스 별&amp;quot; 로 실행할 수 있습니다. - invoke
        // 인스턴스가 중심이 아닌, &amp;quot;메서드&amp;quot; 가 중심이라는 것을 인지해야 합니다.
        // 그리고 getMethod 와 동일하게, 인자가 있다면, int.class, String.class 식으로 인자를 나열해야 합니다.
        String funcString = (String)funcToString.invoke(funcInstance);
        String testString = (String)testToString.invoke(testInstance);

        System.out.println(funcString);
        System.out.println(testString);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Main.java&lt;/code&gt; :&lt;/p&gt;
&lt;p&gt;&lt;code&gt;main&lt;/code&gt; 에서 도드라지는 변화는, 단순히 인스턴스를 생성하는 것 뿐만 아니라,&lt;/p&gt;
&lt;p&gt;각 클래스에 요청할 &lt;code&gt;toString&lt;/code&gt; 이라는 문자열 치환을 Reflection 이 수행하고 있다는 걸&lt;/p&gt;
&lt;p&gt;볼 수 있다.&lt;/p&gt;
&lt;p&gt;즉, 내가 직접 클래스에 요청을 넣는 것이 아니라, 결국 프로그램이 스스로 메서드를 실행하는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Method&lt;/code&gt; 는 클래스 메타데이터 자체에서 뽑아낼 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;quot;클래스&amp;quot; 는 하나이지만, 클래스에서 만들어진 &amp;quot;인스턴스&amp;quot; 는 수없이 많다.&lt;/p&gt;
&lt;p&gt;우리는 Reflection API 에서, &lt;code&gt;Method&lt;/code&gt; 에서 사용 가능한 &lt;code&gt;invoke&lt;/code&gt; 라는 메서드를 통해&lt;/p&gt;
&lt;p&gt;인스턴스와 인자 타입을 순서대로 넣어 실행하고, 그 결과를 받을 수 있다.&lt;/p&gt;
&lt;p&gt;물론, &lt;code&gt;toString()&lt;/code&gt; 형태로 인자가 없기에 넣지 않았다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;필요한 모든 패키지 경로를 적지 않고, 원하는 모든 .java 파일을 컴파일하는 방법&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;# 쉘의 Recursive 표식인 ** 를 이용하여 com/damsoon 및 하위에 존재하는 모든 디렉토리의
# .java 파일을 찾아 컴파일한다.
➜  test-1 javac -d result/bin $(find com -name &amp;quot;*.java&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;# 결과물이 출력된 result/bin 을 ClassPath 로 지정.
# 결과물의 com.damsoon.Main 클래스를 기점으로 실행
➜  test-1 java -cp ./result/bin com.damsoon.Main

생성자 실행 : FuncClass # FuncClass 생성자 실행
생성자 실행 : TestClass # TestClass 생성자 실행
[Class] : FuncClass # FuncClass-&amp;gt;toString()
[Class] : TestClass # TestClass-&amp;gt;toString()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;보다시피, Reflection API 를 이용하여 내가 제어하지 않고,&lt;/p&gt;
&lt;p&gt;(물론 아직까지는 내가 제어한 것과 비슷하지만)&lt;/p&gt;
&lt;p&gt;프로그램이 직접 클래스를 생성하고, 클래스의 메서드를 실행하는 것을 볼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;위의 예제에서 해결이 되지 않은 치명적인 약점 - 순환 참조&lt;/h3&gt;
&lt;p&gt;위의 예제에서는 내가 순환 참조 해결을 애너테이션으로 이어서 해결하는 예제를 보이기 위해&lt;/p&gt;
&lt;p&gt;단방향 의존성 참조, &lt;code&gt;TestClass&lt;/code&gt; 가 &lt;code&gt;FuncClass&lt;/code&gt; 를 의존하는 경우로 만들었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그렇다면, 만약에 &lt;code&gt;TestClass&lt;/code&gt;, &lt;code&gt;FuncClass&lt;/code&gt; 가 생성자 단에서 서로를 요구한다면 어떨까?&lt;/p&gt;
&lt;p&gt;&lt;code&gt;TestClass&lt;/code&gt; 를 먼저 생성하던, &lt;code&gt;FuncClass&lt;/code&gt; 를 먼저 생성하던, 모두 치명 에러를 던진다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart LR

TestClass &amp;lt;-- 생성 시 서로를 필요로 함 --&amp;gt; FuncClass&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 얘기를 하는 이유는,&lt;/p&gt;
&lt;p&gt;우리가 Spring 프레임워크에서 사용하는 수많은 비즈니스 Component들은,&lt;/p&gt;
&lt;p&gt;결국 서로를 참조하는 &amp;quot;순환 참조&amp;quot; 패턴이 생각보다 자주 발견되기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;부트캠프에서 한달 프로젝트를 NestJS 로 수행하던 과정에서,&lt;/p&gt;
&lt;p&gt;기능을 빠르게 먼저 만들어야겠다는 생각에 &lt;code&gt;Service&lt;/code&gt; 컴포넌트를 가끔 순환참조로 만들어버렸다.&lt;/p&gt;
&lt;p&gt;이는 나중에 컴포넌트들끼리의 결합이 &amp;quot;조금이라도&amp;quot; 더 복잡해 지는 순간,&lt;/p&gt;
&lt;p&gt;매우 큰 후회로 다가오게 된다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TB

A-Service

B-Service

C-Service

D-Service

B-Service --B 는 C 를 필요로 한다.--&amp;gt; C-Service
B-Service --B 는 D 를 필요로 한다.--&amp;gt; D-Service

D-Service --D 는 A 를 필요로 한다.--&amp;gt; A-Service

A-Service --A 는 B 를 필요로 한다.--&amp;gt; B-Service&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;A&lt;/code&gt; 와 &lt;code&gt;B&lt;/code&gt; 가 단순히 서로를 참조하는 형태 뿐만 아니라,&lt;/p&gt;
&lt;p&gt;위의 예시처럼 Logic 구조에서 멀어보이는 객체가 &amp;quot;순환 참조(Circular reference)&amp;quot;&lt;/p&gt;
&lt;p&gt;를 일으킬 수가 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;물론, 이를 미리 예방하기 위해 &lt;code&gt;interface&lt;/code&gt; (추상화) 가 존재하지만,&lt;/p&gt;
&lt;p&gt;이걸 어떻게 예방하는지 &amp;quot;직접 제작&amp;quot; 하는 것도 매우 좋은 습관이라고 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;애너테이션&lt;code&gt;@&lt;/code&gt; 없이 순수 코드로 해결 할 수 있지만,&lt;/p&gt;
&lt;p&gt;아직 Reflection 과 Annotation 간의 관계를 명확히 해소하지 않았으므로,&lt;/p&gt;
&lt;p&gt;여차저차 Annotation 을 사용하며 Reflection 을 조합하여 순환 참조를 해결하는 방식으로&lt;/p&gt;
&lt;p&gt;공부 할 것이다.&lt;/p&gt;
&lt;p&gt;(왜냐면 간단한 Container 로직이 들어가기 때문에, 어마무시한 코드가 작성될 예정이다.)&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;너무 많은 개념과 예제가 나와서, 요약정리를 하고 넘어가겠습니다.&lt;/h3&gt;
&lt;p&gt;Java 로 메타프로그래밍을 구현하기 위해, 수많은 기반 지식을 풀고 해석했다.&lt;/p&gt;
&lt;p&gt;이 과정은 절대 순탄치 않았으며, 여타 다른 프로그램과는 다른 JVM 과 Java 의 특성을&lt;/p&gt;
&lt;p&gt;먼저 알기 위해 여러 개념들을 풀어서 설명했다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;요약 이후, Spring Container 를 모방한 로직 + Annotation&lt;/strong&gt; 이 시작된다.&lt;/p&gt;
&lt;p&gt;즉, 순환 참조를 완전한 자동화 Reflection 기법으로 풀 예정이기 때문에,&lt;/p&gt;
&lt;p&gt;위의 개념들을 반드시 다시 잡고 가야 했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;1. package&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;여타 다른 언어들과는 다르게, Java 에서 사용되는 모든 라이브러리, 클래스, 메서드&lt;/p&gt;
&lt;p&gt;등등을 사용하기 위해서는, &lt;code&gt;Package Name&lt;/code&gt; 을 중심으로 Load 한다.&lt;/p&gt;
&lt;p&gt;JVM 에서 클래스나 기능을 가져오는데 &amp;quot;상대 경로&amp;quot; 는 어떠한 의미도 가질 수 없다.&lt;/p&gt;
&lt;p&gt;JVM 은 &amp;quot;정확하게 정해진&amp;quot; &lt;code&gt;Class Path&lt;/code&gt; 에서 파생된 디렉토리 구조(package) 를 통해,&lt;/p&gt;
&lt;p&gt;클래스를 가져올 수 있다.&lt;/p&gt;
&lt;p&gt;이는 Java 에 내장된 기본 유틸리티들도 피해갈 수 없다.&lt;/p&gt;
&lt;p&gt;내장 유틸리티의 Class Path 를 정하는 것은,&lt;/p&gt;
&lt;p&gt;우리가 다운로드 한 jdk-xx 가 존재하는 경로를 통해 유틸리티를 가져오는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;2. ClassLoader&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;스프링의 컨테이너를 만드는 핵심 중의 핵심 기능이라고 볼 수 있다.&lt;/p&gt;
&lt;p&gt;Java 에서의 메타프로그래밍 수행을 위해 필수불가결한 장치이다.&lt;/p&gt;
&lt;p&gt;클래스(인스턴스x) 가 고유하게 가지는 &amp;quot;모든&amp;quot; 변수, 생성자, 메서드 들을 뽑아낼 수 있다.&lt;/p&gt;
&lt;p&gt;private 이던, final 이던 상관없다. 전부 뽑아낼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;단, 뽑아냈다고 해서 &amp;quot;수정&amp;quot; 할 수 있는 것이 아니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;JavaScript 를 생각해 본다면, 객체 내부에 변수던 메서드이던 객체이던,&lt;/p&gt;
&lt;p&gt;마음대로 조작하고 변경할 수 있지만,&lt;/p&gt;
&lt;p&gt;Java 의 객체에서 생성된 인스턴스의 정보를 곧바로 변경할 수 없다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;이는 정보의 변환 권한이 &amp;quot;객체&amp;quot;가 아닌, &amp;quot;내부의 정보&amp;quot; 만이 스스로를 변화시킬 수 있기 때문이다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;우리가 아는 여타 객체 변환과는 다르게, Java 에서의 객체 정보 변환 시스템은 이해가 필요하다.&lt;/p&gt;
&lt;p&gt;이를 가능하게 해 주는 것이 바로, Reflection API 이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;3. Reflection API&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ClassLoader&lt;/code&gt; 로 뽑아낸 클래스의 데이터와 더불어,&lt;/p&gt;
&lt;p&gt;이들을 조회하고 명확한 데이터 클래스로 만들어 낼 수 있는 것이 바로&lt;/p&gt;
&lt;p&gt;이 리플렉션 API 덕분이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;Reflection API 는 이렇게 이해하는게 편하다고 생각하는데,&lt;/p&gt;
&lt;p&gt;일반적으로 객체의 행동을 보통 개발자가 관리한다.&lt;/p&gt;
&lt;p&gt;(C 수준의 객체 관리 말고.)&lt;/p&gt;
&lt;p&gt;우리는 &lt;code&gt;Test test = new Test();&lt;/code&gt; 이러한 간단한 문구로 원하는 타입의 객체를 생성한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;test.all()&lt;/code&gt; 과 같은 문법을 작성하여 메서드를 실행시킨다.&lt;/p&gt;
&lt;p&gt;그러나, 위의 문구는 Dynamic(동적) 이지 않다. Static(정적) 이다.&lt;/p&gt;
&lt;p&gt;내가 말하고자 하는 것은, 객체의 탄생과 그 활동을 &amp;quot;개발자가 직접&amp;quot; 조작한다는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그렇다면, Reflection API 는 어떨까?&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ClassLoader&lt;/code&gt; 로 뽑아낸 데이터들은 모든 정보의 추출이 가능하다고 했다.&lt;/p&gt;
&lt;p&gt;즉,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;객체를 생성하는 &amp;quot;생성자&amp;quot; == &lt;code&gt;Constructor&lt;/code&gt; 를 뽑아낼 수 있으며,&lt;/li&gt;
&lt;li&gt;객체 내부의 변수인 &amp;quot;필드&amp;quot; == &lt;code&gt;Field&lt;/code&gt; 를 뽑아낼 수 있으며,&lt;/li&gt;
&lt;li&gt;객체 내부의 함수인 &amp;quot;메서드&amp;quot; == &lt;code&gt;Method&lt;/code&gt; 를 뽑아낼 수 있다.&lt;/li&gt;
&lt;li&gt;객체가 상속한 클래스의 정보를 추출할 수 있다. == &lt;code&gt;Class&amp;lt;?&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;객체가 구현한 인터페이스의 정보를 추출 할 수 있다. == &lt;code&gt;Class&amp;lt;?&amp;gt;[]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;이 객체의 패키지 이름을 추출 할 수 있다. == &lt;code&gt;String&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;등등..&lt;/li&gt;
&lt;li&gt;위의 보안 수준이 어떻든 간에 상관없다. 모두 뽑아낼 수 있다.&lt;/li&gt;
&lt;li&gt;But!!, 객체 내부 변수가 &lt;code&gt;private final&lt;/code&gt; 로 되어있을 경우, &lt;br/&gt; 내장 Reflection 으로 수정할 수 없다. (Java 의 &lt;code&gt;fianl&lt;/code&gt; 은 미리 지정되거나, &amp;quot;생성자&amp;quot; 에서만 지정되고 변경 X)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;위의 정보를 추출하여,&lt;/p&gt;
&lt;p&gt;어떠한 클래스가 올지 모른다. 어떠한 메서드가 실행될 지도 모른다.&lt;/p&gt;
&lt;p&gt;그러나, Reflection API 를 통해 다양한 클래스들을 프로그램 자체적으로 관리하도록 만들 수 있다.&lt;/p&gt;
&lt;p&gt;이 기법을 극한까지 몰아서 사용하는 프레임워크가 바로, &lt;strong&gt;Spring&lt;/strong&gt; 인 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;위에서 보인 &lt;code&gt;Constructor&lt;/code&gt;, &lt;code&gt;Field&lt;/code&gt;, &lt;code&gt;Method&lt;/code&gt; 와 같은 클래스 타입들은&lt;/p&gt;
&lt;p&gt;Reflection API 에서 제공한다.&lt;/p&gt;
&lt;p&gt;그리고, Java 코드로 위의 클래스 타입에서 직접 실행한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;즉, 우리가 직접 객체를 생성하고, 변수를 설정하고, 메서드를 실행하는 것이 아니라,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;생성자 가져오기 :&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Constructor objConstructor = clazz.getDelaredConstructor()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ul&gt;
&lt;li&gt;인스턴스 생성 :&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Object obj = new Object()&lt;/code&gt; =&amp;gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Object obj = objConstructor.newInstance()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ul&gt;
&lt;li&gt;변수 조작 :&lt;/li&gt;
&lt;li&gt;&lt;code&gt;obj.name = &amp;quot;newName&amp;quot;&lt;/code&gt; ==&amp;gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Field nameField = clazz.getDeclaredField(&amp;quot;name&amp;quot;)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nameField.set(obj, &amp;quot;newName&amp;quot;)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ul&gt;
&lt;li&gt;메서드 실행 및 조작 :&lt;/li&gt;
&lt;li&gt;&lt;code&gt;String result = obj.getName()&lt;/code&gt; ==&amp;gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Method method = clazz.getMethod(&amp;quot;getName&amp;quot;);&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;String result = method.invoke(obj);&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;위의 예시 리스트들은 모두&lt;/p&gt;
&lt;p&gt;&lt;code&gt;obj&lt;/code&gt; 내부 생성자와 메서드에 인자가 필요하지 않다는 가정 하에 작성되었습니다.&lt;/p&gt;
&lt;p&gt;인자가 존재하는 경우,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Constructor objConstructor = clazz.getConstructor(String.class);&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;objConstructor.newInstance(&amp;quot;myName&amp;quot;);&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Method method = clazz.getMethod(&amp;quot;setName&amp;quot;, String.class);&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;와 같은 형식으로 인자를 나열하게 됩니다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Annotation - 애너테이션 &amp;#39;@&amp;#39;&lt;/h2&gt;
&lt;p&gt;드디어 우리가 Spring Framework 에서 사용하던 Annotation, &lt;code&gt;@&lt;/code&gt; 를 제대로 배우게 된다.&lt;/p&gt;
&lt;p&gt;Annotation 은 코드의 Logic 속에서, 단순히 하나의 기능만 담당하지 않는다.&lt;/p&gt;
&lt;p&gt;Proxy, AOP, DI, 등등 뿐만 아니라,&lt;/p&gt;
&lt;p&gt;특정 클래스를 상속받은 클래스가 &lt;code&gt;@Override&lt;/code&gt; 표시를 남겨&lt;/p&gt;
&lt;p&gt;조상 클래스의 메서드를 &amp;quot;재구성&amp;quot; 했다는 것을 명시적으로 남길 수 있다.&lt;/p&gt;
&lt;p&gt;뿐만 아니라, &lt;code&gt;@Deprecated&lt;/code&gt; 를 통해 해당 클래스, 혹은 메서드, 변수가&lt;/p&gt;
&lt;p&gt;미래에 있어 지원과 연결성이 끊길 예정이라는 걸 명시 할 수도 있다. (되도록 쓰지 말라는 말)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;위의 2 개의 애너테이션을 포함하여, &lt;code&gt;@SuppressWarnings&lt;/code&gt; 라는 애너테이션 까지,&lt;/p&gt;
&lt;p&gt;내장되어 있는 애너테이션이다. (이건 특정 경고를 꺼 주는 애너테이션)&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Custom Annotation 이 핵심이다.&lt;/h3&gt;
&lt;p&gt;위에 명시한 3 개의 내장 애너테이션은 JVM 이 클래스를 load 하고, execute 하는데&lt;/p&gt;
&lt;p&gt;어떠한 영향도 미치지 않는다. 즉, 해당 코드를 보는 개발자에게 알려주기 위한 용도인 것이다.&lt;/p&gt;
&lt;p&gt;Java 에는 개발자가 직접 본인만의 Annotation 을 만들 수 있는 기능이 존재한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;커스텀 애너테이션은 주로 &amp;quot;메타데이터&amp;quot; 에 집중되어 있다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;위에서 우리는 &amp;quot;메타데이터&amp;quot; 를 다루었다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;생성자 정보 및 생성자 추출&lt;/li&gt;
&lt;li&gt;변수 정보 및 변수 Field 추출&lt;/li&gt;
&lt;li&gt;메서드 정보 및 메서드 Method 추출&lt;/li&gt;
&lt;li&gt;추출된 Field, Method 를 &amp;quot;내가 아닌&amp;quot;, &amp;quot;프로그램&amp;quot; 이 관리하도록 바꿈.&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;p&gt;애너테이션은 &lt;code&gt;ClassLoader&lt;/code&gt;, &lt;code&gt;Reflection&lt;/code&gt; 과 더불어 사용되는 핵심 문법이며,&lt;/p&gt;
&lt;p&gt;자바 파일에서 &lt;code&gt;.class&lt;/code&gt; 파일로 컴파일 되고, 자바 가상머신, JVM 에 포함되어 동작한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;우리가 다루는 Annotation 또한 인터페이스, 즉 &amp;quot;클래스 메타데이터&amp;quot; 로서 인식된다.&lt;/p&gt;
&lt;p&gt;그러나, 일반적인 &lt;code&gt;class&lt;/code&gt; 와는 다른 문법을 보인다.&lt;/p&gt;
&lt;p&gt;따라서 Annotation 이 가지는 고유의 특성과, 사용법을 배워야만 한다.&lt;/p&gt;
&lt;h4&gt;1. Annotation 사용할 때&lt;/h4&gt;
&lt;p&gt;애너테이션은 다양한 형식으로 선언 될 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Spring&lt;/code&gt; 을 자주 사용 해 보았다면, 인자가 있거나 없는 애너테이션이나, 중첩되는 애너테이션을 보았을 것이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@Entity&lt;/code&gt; : 괄호 없이 사용될 수 있다. - (물론 설정을 원한다면 인자를 넣을 수 있다.)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@SuppressWarnings(value = &amp;quot;unchecked&amp;quot;)&lt;/code&gt; : 직접 인자 &lt;code&gt;value&lt;/code&gt; 라고 선언한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@SuppressWarnings(&amp;quot;unchecked&amp;quot;)&lt;/code&gt; : 인자가 하나이므로 단순하게 값만 넘겨준다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Author(name = &amp;quot;kong&amp;quot;)&lt;/code&gt; : 이 밑에 &lt;code&gt;@Author(name = &amp;quot;gong&amp;quot;)&lt;/code&gt; 으로 중첩할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;뿐만 아니라, 많이 보지 못했던 형태도 가능하다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@Interned&lt;/code&gt; : &lt;code&gt;new @Interned MyObject();&lt;/code&gt; ==&amp;gt; 인스턴스 생성 시&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@NonNull&lt;/code&gt; : &lt;code&gt;(@NonNull String) str&lt;/code&gt; ==&amp;gt; 타입 캐스팅 시&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Readonly&lt;/code&gt; : &lt;code&gt;@Readonly List&amp;lt;...&amp;gt;&lt;/code&gt; ==&amp;gt; 저장된 리스트는 &amp;quot;읽기만 가능&amp;quot; 하게&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Critical&lt;/code&gt; : &lt;code&gt;...() throws @Critical ..Exception { ... }&lt;/code&gt; &lt;br/&gt; ==&amp;gt; 이 에러 발생 시 에러 위험도를 &amp;quot;치명&amp;quot; 으로 올린다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2. Custom Annotation 선언 기초&lt;/h4&gt;
&lt;p&gt;우리는 이미 스프링에서 선언된 애너테이션들을 보면, 맨 위에 다양하게 붙어있는&lt;/p&gt;
&lt;p&gt;또 다른 애너테이션들을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@Retention&lt;/code&gt;, &lt;code&gt;@Documented&lt;/code&gt;, &lt;code&gt;@Target&lt;/code&gt;, .. 등등이 존재한다.&lt;/p&gt;
&lt;p&gt;커스텀 애너테이션에 붙은 이러한 애너테이션들은, 커스텀 애너테이션에 부가적인 기능을 제공한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;완벽하게 커스텀화 된 애너테이션을 선언 해 보자면,&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// Custom 저자 애너테이션 제작
@interface AuthorAnnotation {
    String name();
    String comment() default &amp;quot;No Comment&amp;quot;;
    int currVersion();
    String[] metaDatas() default {};
}

// Testing 클래스에 애너테이션 메타데이터 &amp;quot;주입&amp;quot;
@AuthorAnnotation(
        name = &amp;quot;Gong Damhyeong&amp;quot;,
        comment = &amp;quot;첫 커스텀 애너테이션 제작&amp;quot;,
        currVersion = 1
)
class Testing {
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;가장 기초적인 형태의 Custom Annotation 을 보면, 이질감이 들 수 밖에 없을 것이다.&lt;/p&gt;
&lt;p&gt;우리는 &amp;quot;저자 정보&amp;quot; 를 저장하는 아주 간단한 애너테이션을 만들었다.&lt;/p&gt;
&lt;p&gt;문법을 보면, &lt;code&gt;@interface&lt;/code&gt; 를 선언함으로서 이는 애너테이션이라고 선언한다.&lt;/p&gt;
&lt;p&gt;그런데, 분명 값을 인자로 받는데, &lt;code&gt;String name()&lt;/code&gt; 과 같이,&lt;/p&gt;
&lt;p&gt;변수가 아니라 메서드 반환 형식으로 선언되어 있다.&lt;/p&gt;
&lt;p&gt;이는 애너테이션이 특징으로, &lt;code&gt;Interface&lt;/code&gt; 구조가 기본이다. 따라서 변수를 가지지 않는데,&lt;/p&gt;
&lt;p&gt;JVM 이 애너테이션을 &lt;code&gt;Proxy&lt;/code&gt; 구조로 클래스로 재생성하여 메타데이터로 주입하는 것이다.&lt;/p&gt;
&lt;p&gt;또한, 배열 선언과 디폴트 형식이 존재한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;위에서 선언한 애너테이션의 값 주입으로 생성된 객체는,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// JVM 이 이런 식으로 프록시 객체를 생성하여 애너테이션으로 주입한다고 생각하면 됩니다.
public class AuthorAnnotationProxy extends AuthorAnnotation {
    @Override
    public String name() {
        return &amp;quot;Gong Damhyeong&amp;quot;;
    }
    @Override
    public String comment() {
        return &amp;quot;첫 커스텀 애너테이션 제작&amp;quot;;
    }
    @Override
    public int currVerion() {
        return 1;
    }
    @Override
    public String[] metaDatas() {
        return {};
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;정적 객체로 재탄생하게 된다.&lt;/p&gt;
&lt;p&gt;그리고 자신만의 Custom Annotation 을 제작하고 꼭 같이 작성해야 한다고 판단되는 애너테이션은,&lt;/p&gt;
&lt;p&gt;바로 &lt;code&gt;@Documented&lt;/code&gt; 라고 생각한다.&lt;/p&gt;
&lt;p&gt;많은 에디터 프로그램에서 Java 코드의 정보를 &lt;code&gt;javadoc&lt;/code&gt; 으로 펼쳐 본다.&lt;/p&gt;
&lt;p&gt;따라서, &lt;code&gt;@Documented&lt;/code&gt; 를 우리가 만든 애너테이션 위에 붙여주면, 에디터 사양이 펼쳐 보여 준다.&lt;/p&gt;
&lt;p&gt;또한, Java 가 내장 기능으로 제공하는 다양한 애너테이션들이 존재하는데,&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;이들은 애너테이션을 제작하는 데 가장 많이 사용되고, 꿀팁도 주므로 꼭 보는 것을 추천한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://docs.oracle.com/javase/tutorial/java/annotations/predefined.html&quot;&gt;Oracle - Predefined 애너테이션&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Spring Framework 가 제작한 애너테이션들은 내장 애너테이션을 사용하여 구현되었다.&lt;/p&gt;
&lt;p&gt;그래도 영어 문서이기 때문에, 곤란한 분들을 위해 요약을 하자면,&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; &lt;code&gt;@Deprecated&lt;/code&gt; :&lt;/p&gt;
&lt;p&gt;더이상 사용되지 않거나, 곧 지원이 중단되는 요소에 덧붙여 사용한다.&lt;/p&gt;
&lt;p&gt;만약 이 요소를 사용한다면, 에디터 프로그램이 &amp;quot;경고&amp;quot; 를 생성한다.&lt;/p&gt;
&lt;p&gt;또한 Javadoc 코멘트에도 deprecated 를 넣어놓는다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class Test {
    @Deprecated // 이런 식으로 작성된다. - 이 기능은 지원이 곧 중단 혹은 사용하지 말아라.
    public static void plus(int a, int b) {
        return a + b;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;2.&lt;/strong&gt; &lt;code&gt;@Override&lt;/code&gt; :&lt;/p&gt;
&lt;p&gt;Java 사용자라면 대부분 알고 있는 애너테이션이다.&lt;/p&gt;
&lt;p&gt;굳이 사용하지 않더라도, 상속받은 메서드를 재작성하더라도, 딱히 에러는 존재하지 않는다.&lt;/p&gt;
&lt;p&gt;그러나, 이 애너테이션은 &amp;quot;Convention&amp;quot; 에서 지대한 영향을 미친다.&lt;/p&gt;
&lt;p&gt;상속받은 슈퍼클래스의 메서드를 재작성 한다는 것은, 어떤 상황에서는&lt;/p&gt;
&lt;p&gt;수백 ~ 수천의 메서드 중 하나를 재작성 하는 작업일 수도 있다.&lt;/p&gt;
&lt;p&gt;만약에 그저 &lt;code&gt;@Override&lt;/code&gt; 없이 작성한다면, 재작성 된 이 메서드가 우리가 아는 &amp;quot;그 메서드 이름&amp;quot;&lt;/p&gt;
&lt;p&gt;인지 체크해야한다.&lt;/p&gt;
&lt;p&gt;그러나, &lt;code&gt;@Override&lt;/code&gt; 를 사용하여 작성하면, 메서드 이름이 틀렸는지 확인시켜준다.&lt;/p&gt;
&lt;p&gt;이는 매우 복잡한 프로그램에서 오류를 예방하는 데 큰 도움을 줄 수 있다는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;3.&lt;/strong&gt; &lt;code&gt;@SuppressWarnings&lt;/code&gt; :&lt;/p&gt;
&lt;p&gt;자바에서는 특정 에러나, 경고를 맞닥들이게 된다.&lt;/p&gt;
&lt;p&gt;지원되지 않는 저수준 기능을 Java 로 구현하기 위해 직접 코드를 작성하다 보면,&lt;/p&gt;
&lt;p&gt;논리적으로는 맞지만 Java 컴파일 시 통과시켜주지 않는 경우가 발생한다.&lt;/p&gt;
&lt;p&gt;이 때, &lt;code&gt;@SuppressWarnings(&amp;quot;해당 에러&amp;quot;)&lt;/code&gt; 를 선언하여,&lt;/p&gt;
&lt;p&gt;컴파일러가 동작할 수 있게 만든다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;4.&lt;/strong&gt; &lt;code&gt;@SafeVarargs&lt;/code&gt; :&lt;/p&gt;
&lt;p&gt;메서드나 생성자에서 적용된다.&lt;/p&gt;
&lt;p&gt;이 애너테이션은 가변 타입이 들어 올 때, 발생하는 &lt;code&gt;unchecked&lt;/code&gt; 에러를 없앤다.&lt;/p&gt;
&lt;p&gt;즉, 이 곳에 선언된 가변 타입은 힙의 메모리가 &amp;quot;오염&amp;quot; 되지 않는다는 것을 스스로 보증하는 것이다.&lt;/p&gt;
&lt;p&gt;주로 제네릭에서 많이 사용된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;5.&lt;/strong&gt; &lt;code&gt;@FunctionalInterface&lt;/code&gt; :&lt;/p&gt;
&lt;p&gt;Java SE 8 부터 도입된 애너테이션인데, 람다식과 연결되어 있다.&lt;/p&gt;
&lt;p&gt;수많은 인터페이스들이 &amp;quot;여러 메서드&amp;quot; 를 추상적으로 구현하고 있지만,&lt;/p&gt;
&lt;p&gt;또 다른 수많은 인터페이스들은 &amp;quot;하나의 기능&amp;quot; 만들 추상 구현하고 있는 경우가 있다.&lt;/p&gt;
&lt;p&gt;예를 들어서, 계산기 인터페이스가 있는데, &lt;code&gt;int calculate(int a, int b);&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@FunctionalInterface
public interface Calculation {
    // 미구현 메서드 1개 --&amp;gt; calculate
    int calculate(int a, int b);

    // static 이나, default 는 개수에 영향을 받지 않는다.
    // 이미 구현되어 있으므로.
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;하나의 추상 메서드만 선언되어 있고, 내부 구현이 없다.&lt;/p&gt;
&lt;p&gt;그렇다면, 계산기 인터페이스를 사용하는 곳에선,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public static void main(String[] args) {
    // 구현되지 않은 단 하나의 메서드를 구현하는 방식으로 생성한다.
    Calculation calc = (x, y) -&amp;gt; x - y;

    // 혹은 (x, y) -&amp;gt; x + y; 도 가능하다.
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;결국 위의 코드는, 추상화 인터페이스를 람다식으로 선언하여, 우리가 원하는 방식으로 구동하도록&lt;/p&gt;
&lt;p&gt;&amp;quot;형식을 지정&amp;quot; 한 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;이제부터는 Annotation 위에 붙는 애너테이션을 소개합니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;6.&lt;/strong&gt; &lt;code&gt;@Retention&lt;/code&gt; :&lt;/p&gt;
&lt;p&gt;Retention 은 영어 말 그대로 &amp;quot;보유&amp;quot; 라는 뜻인데, 이는 Compiler, JVM 과 큰 연관이 있다.&lt;/p&gt;
&lt;p&gt;Retention 은 &amp;quot;유지&amp;quot; 라는 의미로, 크게 3 개로 나눌 수 있다. (특수 옵션도 존재함)&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;RetentionPolicy.SOURCE&lt;/code&gt; : 소스 코드 작성 시에만&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RetentionPolicy.CLASS&lt;/code&gt; : 컴파일러가 작동할 때 까지만&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RetentionPolicy.RUNTIME&lt;/code&gt; : JVM 작동시 같이 사용되며, runtime 라이브러리와 조화할 수 있음.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;즉, 작성된 이 Annotation 이 &amp;quot;어디까지 보유&amp;quot; 되냐고 보면 된다.&lt;/p&gt;
&lt;p&gt;우리는 위에서 다양한 목적과, 형태의 Annotation 을 보았다.&lt;/p&gt;
&lt;p&gt;개발자에게 코드 수준에서 개발 생산성을 도와주는&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Deprecated&lt;/code&gt;, &lt;code&gt;Documented&lt;/code&gt;, &lt;code&gt;Override&lt;/code&gt; 어노테이션도 존재하고,(&lt;code&gt;SOURCE&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@NonNull&lt;/code&gt; 이라는 애너테이션도 존재하고,(&lt;code&gt;CLASS&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;Spring 프레임워크에서 사용되는 친숙한 &lt;code&gt;@Component&lt;/code&gt;, &lt;code&gt;@Controller&lt;/code&gt;, 등등..&lt;/p&gt;
&lt;p&gt;이들이 &lt;code&gt;RUNTIME&lt;/code&gt; 이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;SOURCE&lt;/code&gt;, &lt;code&gt;RUNTIME&lt;/code&gt; 은 추상적이더라도 명확히 이해되나,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CLASS&lt;/code&gt; 리텐션은 조금 이해가 되지 않는다.&lt;/p&gt;
&lt;p&gt;그래서 AI 에게 이것에 대해 물어보았는데, 주로 IDE, Linter(어떤 에디터에서도 작동) 가&lt;/p&gt;
&lt;p&gt;해당 애너테이션에 대한 개발 정보를 참조하기 위함이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;또한 공식 문서에서는 각 단계별로 엄격한 유지 정책이 적용되는 것 처럼 보이지만&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;우리가 사용하는 Lombok 같은 도구들이 SOURCE 의 형상을 하고&lt;/p&gt;
&lt;p&gt;Compiler 단계에서 난입하여 AST(추상 구문 트리) 를 바이너리 형식으로 직접 바꾼다는 사실을 알게 되었다..&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;7.&lt;/strong&gt; &lt;code&gt;@Documented&lt;/code&gt; :&lt;/p&gt;
&lt;p&gt;에디터들 중에서 Javadoc 을 적극 사용하는 프로그램들이 많다.&lt;/p&gt;
&lt;p&gt;우리가 이 애너테이션을 위에 덧붙여 주면, 해당 프로그램들이 커스텀 된 애너테이션에 대해&lt;/p&gt;
&lt;p&gt;문서적으로 표현 해 준다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;8.&lt;/strong&gt; &lt;code&gt;@Target&lt;/code&gt; :&lt;/p&gt;
&lt;p&gt;이건 간단하게, &amp;quot;어떤 코드를 목표로 하냐&amp;quot; 이냐.&lt;/p&gt;
&lt;p&gt;공식문서의 내용을 참고로 하여 펼쳐볼 건데,&lt;/p&gt;
&lt;p&gt;이를 통해 Java 가 크게 코드를 어떤 카테고리들로 분리하고 있는지 볼 수 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ElementType.ANNOTATION_TYPE&lt;/code&gt; : 이건 애너테이션을 목표로 한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ElementType.CONSTRUCTOR&lt;/code&gt; : 생성자를 목표로 한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ElementType.FIELD&lt;/code&gt; : 클래스 내부의 변수를 목표로 한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ElementType.LOCAL_VARIABLE&lt;/code&gt; : 메서드 내부 구현 시 콜스택에 잠깐 나타났다 사라지는 지역 변수를 목표로 한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ElementType.METHOD&lt;/code&gt; : 클래스 내부의 메서드를 목표로 한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ElementType.PACKAGE&lt;/code&gt; : &amp;quot;패키지 선언&amp;quot; 을 목표로 한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ElementType.PARAMETER&lt;/code&gt; : 메서드 내부의 인자(args) 를 목표로 한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ElementType.TYPE&lt;/code&gt; : 어떠한 타입이라도(내장타입 및 클래스 전부) 목표로 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;9.&lt;/strong&gt; &lt;code&gt;@Inherited&lt;/code&gt; :&lt;/p&gt;
&lt;p&gt;이는 커스텀 중인 이 애너테이션이 특정 Super Class(상위 애너테이션) 에게 상속받음을 알린다.&lt;/p&gt;
&lt;p&gt;예를 들어, 상속받은 특정 클래스는 상위 클래스의 구현된 메서드를 &amp;quot;모두 구현하지 않을 수도 있다.&amp;quot;&lt;/p&gt;
&lt;p&gt;애너테이션도 동일하므로, 유저가 이 애너테이션을 통해 무언가를 실행하려 할 때, 없을 수도 있다.&lt;/p&gt;
&lt;p&gt;이 때 &lt;code&gt;@Inherited&lt;/code&gt; 에 작성된 슈퍼 클래스(애너테이션) 을 참조하겠다는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;10.&lt;/strong&gt; &lt;code&gt;@Repeatable&lt;/code&gt; :&lt;/p&gt;
&lt;p&gt;작성중인 현재 애너테이션이 작성된 장소에 여러번 다시 작성될 수 있음을 알린다.&lt;/p&gt;
&lt;p&gt;예를 들어, 클래스 작성자가 2명이라면, &lt;code&gt;@AuthorAnnotation&lt;/code&gt; 을 2 번 작성할 수 있게 만드는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;만들게 될 애너테이션의 기능&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Container 에 들어갈 클래스를 표식하기&lt;/li&gt;
&lt;li&gt;Proxy 기능 추가하기&lt;/li&gt;
&lt;li&gt;순환 참조 해결하기&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;p&gt;솔직히 이 글을 작성하면서 짧게 카테고리를 잡아 8 개의 글로 나눌 수 있는 분량을,&lt;/p&gt;
&lt;p&gt;하나의 Article 로 작성함으로 인해서 글이 복잡해 졌다고 생각한다.&lt;/p&gt;
&lt;p&gt;그러나, 핵심적인 기술인 Spring Container 를 모방하는 것 자체가 난이도가 높으므로,&lt;/p&gt;
&lt;p&gt;이 정도 복잡함을 감수하고 작성해야겠다고 판단했었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;Annotation 을 배우느라 조금 길이 잘못 빠진 느낌이 있지만,&lt;/p&gt;
&lt;p&gt;앞으로 작성하게 될 코드의 구조를 알고자 하시는 독자분이 계시다면, 일일히 파악하기 위해&lt;/p&gt;
&lt;p&gt;꼭 짚고 넘어가야 한다고 생각했다.&lt;/p&gt;
&lt;h3&gt;복잡한 명령어를 shell script 로 압축하자.&lt;/h3&gt;
&lt;p&gt;나는 위에서 예제를 만들고, 컴파일 및 실행 할 때,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;javac -d result/bin $(find com -name &amp;quot;*.java&amp;quot;)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;java -cp ./result/bin com.damsoon.Main&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이렇게 com.damsoon.Main 을 통해 JVM 을 실행했다.&lt;/p&gt;
&lt;p&gt;이에 대한 명령어를 하나로 압축 할 것이다.&lt;/p&gt;
&lt;p&gt;딱히 Shell Script (&lt;code&gt;bash&lt;/code&gt;) 에 대해서 관심이 없다면,&lt;/p&gt;
&lt;p&gt;복사 붙여넣기 해도 된다. (단, 이 파일은 Project Root 에 위치해야 함)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;#!/bin/bash

echo &amp;quot;컴파일 &amp;amp; 실행 구문 시작&amp;quot;

javac -d result/bin $(find com -name &amp;quot;*.java&amp;quot;)

java -cp ./result/bin com.damsoon.Main

echo &amp;quot;컴파일 &amp;amp; 실행 구문 종료&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 파일을 만들어도 바로 실행 할 수 없다.&lt;/p&gt;
&lt;p&gt;이에 대해서 실행 권한을 부여 해 준다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;➜  test-1 ls -al
total 24
...
# 현재 실행 권한 &amp;#39;x&amp;#39; 가 없는 것을 볼 수 있음
-rw-r--r--@ 1 gongdamhyeong  staff  177 Dec 19 01:44 compile-and-execute.sh
...

$ chmod +x ./&amp;lt;위에 만든 파일명&amp;gt;.sh

➜  test-1 ls -al
total 24
...
# 어떤 사람이던 실행할 수 있음.
# 단, 특정 단계의 사람(보안)만 실행하길 원한다면,
# chmod u+x ... 로 실행하면 된다.
-rwxr-xr-x@ 1 gongdamhyeong  staff  177 Dec 19 01:47 compile-and-execute.sh
...&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그리고 나서, 실행을 해 보면,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;➜  test-1 ./compile-and-execute.sh
컴파일 &amp;amp; 실행 구문 시작
생성자 실행 : FuncClass
생성자 실행 : TestClass
[Class] : FuncClass
[Class] : TestClass
컴파일 &amp;amp; 실행 구문 종료&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;정상적으로 실행 되는 것을 볼 수 있다.&lt;/p&gt;
&lt;h2&gt;애너테이션 요약하고 넘어가기&lt;/h2&gt;
&lt;p&gt;이미 이 글은 수많은 개념과 예제를 작성했기 때문에, 애너테이션과 다른 개념을 최소한으로 엮으려고 한다.&lt;/p&gt;
&lt;p&gt;여기까지 읽으신 분이 있으시다면, 감사를 표합니다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;우리가 애너테이션을 배우기 전에 다룬 것을 한 문장으로 &amp;quot;압축&amp;quot; 해 본다면,&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;Metadata 를 다루고 객체를 조작하는 방법&amp;quot;&lt;/strong&gt; 이다.&lt;/p&gt;
&lt;p&gt;이 주제가 매우 어려운 이유는,&lt;/p&gt;
&lt;p&gt;코드의 메타데이터라는 개념이 매우 추상적이고 활용성이 넓기 때문이다.&lt;/p&gt;
&lt;p&gt;단순히 모든 타입을 어우를 뿐만 아니라, 프로젝트의 위치와 정보까지도 포함한다.&lt;/p&gt;
&lt;p&gt;또한, 객체를 조작하는 개체는 &amp;quot;나 자신&amp;quot; 이 아니라, &amp;quot;프로그램&amp;quot; 이기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이 메타데이터를 다루는 과정이 매우 복잡한 가운데,&lt;/p&gt;
&lt;p&gt;아예 객체, 메서드, 타입, 인수(args) 자체에 메타데이터를 꽂아버리는 기능이 있다.&lt;/p&gt;
&lt;p&gt;그게 바로 Annotation(&lt;code&gt;@&lt;/code&gt;) 이다.&lt;/p&gt;
&lt;p&gt;새로운 개념을 배우면서 오히려 애너테이션이 어렵다고 느낄 수 있겠으나,&lt;/p&gt;
&lt;p&gt;난잡해 질 수 있는 메타데이터를 정리 한 것이 &lt;code&gt;Annotation&lt;/code&gt; 이라고 볼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Annotation 의 다양화&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;프로그래밍 고대 시절의 Java 가 아닌, 현대적 Java 를 접하고 사용하는 우리는,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@&lt;/code&gt; 표식을 밥먹듯이 본다.&lt;/p&gt;
&lt;p&gt;Annotation 은 하나의 역할로 묶을 수 없을 만큼 다양한 영역에서 활동하게 된다.&lt;/p&gt;
&lt;p&gt;그러나, 확실하게 나눌 수 있는 것은 &lt;code&gt;Code&lt;/code&gt;, &lt;code&gt;Compiler&lt;/code&gt;, &lt;code&gt;JVM&lt;/code&gt; 이다.&lt;/p&gt;
&lt;p&gt;애너테이션의 &lt;code&gt;Retention&lt;/code&gt; 부가 기능을 통해,&lt;/p&gt;
&lt;p&gt;이 애너테이션이 어디서 활동할지, 어디까지 유지될 지 선택할 수 있는 것이다.&lt;/p&gt;
&lt;p&gt;또한 Java SE 8 이후로 &lt;code&gt;FunctionalInterface&lt;/code&gt; 애너테이션이생성되며,&lt;/p&gt;
&lt;p&gt;Java 의 애너테이션 사용은 부흥을 맞았다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;내장 애너테이션이 아닌 커스텀 애너테이션을 직접 제작하여 내가 원하는 기능을 제작할 수 있는데,&lt;/p&gt;
&lt;p&gt;이 과정에서 민낯의 애너테이션을 제작했다.&lt;/p&gt;
&lt;p&gt;이를 통해, 애너테이션의 민낯은 정확히 &amp;quot;메타데이터&amp;quot; 를 향한다는 것을 알 수 있다.&lt;/p&gt;
&lt;p&gt;또한, &lt;code&gt;ClassLoader&lt;/code&gt; 는 인식하는 객체 뿐만 아니라, 붙어있는 애너테이션들을 분석하는 데에도 탁월하다.&lt;/p&gt;
&lt;p&gt;이러한 기능을 통해 Spring Container 는 개발자들의 수많은 요구사항에 유연하게 대처할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Spring Container 를 만들기 전, 구조화 하자.&lt;/h2&gt;
&lt;p&gt;먼저 만들게 될 프로그램의 기능의 요구사항을 미리 만들어 놓아야 한다.&lt;/p&gt;
&lt;p&gt;(Spring Container 의 모든 기능을 구현하지 않으므로.)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;내가 만들 컨테이너는, 단일 객체, 의존성 해결에 초점이 맞춰져 있다.&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;주의사항 - CGLIB 가 아닙니다.&lt;/h3&gt;
&lt;p&gt;조금 먼 위쪽에서 &lt;code&gt;ClassLoader&lt;/code&gt; 예시를 들 때, 순환 참조를 피하기 위해&lt;/p&gt;
&lt;p&gt;양방향 참조를 피했다.&lt;/p&gt;
&lt;p&gt;이를 해결하는 방법으로는,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;모든 객체의 Proxy 객체를 만들어서 주입한다. &lt;br/&gt; (쉽게 해결되나 성능적 이슈 - 비효율 리소스 사용)&lt;/li&gt;
&lt;li&gt;바이트코드 수준을 수정하는 라이브러리를 사용한다. (EX - &lt;code&gt;CGLIB&lt;/code&gt; = Spring 이 사용)&lt;/li&gt;
&lt;li&gt;모든 객체의 생성자 실행 단계에서 필요한 의존성을 &lt;code&gt;null&lt;/code&gt; 로 주입한 뒤, 내가 해결한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;내가 상상한 방식&lt;/li&gt;
&lt;li&gt;외부 라이브러리에 의존하지 않고, 최대한 Java 로 해결하기 위함&lt;/li&gt;
&lt;li&gt;당연히 &lt;code&gt;2번&lt;/code&gt; 보다는 시작이 느리며, 오류의 위험성이 존재함.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;Spring Framework 는 당연히 &amp;quot;속도&amp;quot;, &amp;quot;유지보수&amp;quot;, &amp;quot;에러 관리&amp;quot; 면에서 &lt;code&gt;CGLIB&lt;/code&gt; 외부 라이브러리가 압도적으로 효율적이기 때문에 사용할 것이다.&lt;/p&gt;
&lt;p&gt;그러나, 나는 Java 가 가진 내장 기능 및 Reflection API 와 Logic 을 최대한 활용하여&lt;/p&gt;
&lt;p&gt;Container 를 다른 형태로 구현 할 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Spring Container 기능 요구사항&lt;/h3&gt;
&lt;p&gt;단일 객체, 객체 의존성 해결, 순환참조 예방 등등..&lt;/p&gt;
&lt;p&gt;이것이 Spring Container 의 목적이다.&lt;/p&gt;
&lt;p&gt;여기서 &lt;strong&gt;&amp;quot;단일 객체&amp;quot;&lt;/strong&gt; 란, 실행되고 있는 프로그램에서&lt;/p&gt;
&lt;p&gt;내가 선언한 객체가 &amp;quot;단 하나&amp;quot; 만 존재하며, 다른 장소의 여러 객체가&lt;/p&gt;
&lt;p&gt;생성될 때 마다 즉시 생성하지 않고, &lt;strong&gt;&amp;quot;주소만 참조&amp;quot;&lt;/strong&gt; 하는 형태이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;생각 해 보면 순환 참조 뿐만 아니라,&lt;/p&gt;
&lt;p&gt;의존성에 문제가 없는 수십 개의 객체들을 컨테이너에 담을 때도&lt;/p&gt;
&lt;p&gt;다양한 문제가 제기된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;예를 들어 보자.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TB

First(&amp;quot;First-Object&amp;quot;)

Second(&amp;quot;Second-Object&amp;quot;)

Third(&amp;quot;Third-Object&amp;quot;)

First -- 의존(depend) --&amp;gt; Second

Second -- 의존(depend) --&amp;gt; Third
&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;단순하게 그대로, 1 번째는 2 번째를 요구하고, 2 번째는 3 번쨰를 요구한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;위의 3 개의 객체는 모두 Spring Container 에 들어가야 한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;위의 3 개의 객체는 ClassLoader 가 어떤 순서로 가져올 지 모른다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;최선은 3 번째부터 1 번째까지 역순으로 Load 하는 것이겠지만,&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&amp;quot;수십 ~ 수천&amp;quot; 개의 컴포넌트가 원하는 순서대로 Load 할 리는 없다고 생각해야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;그러면, 어떻게 인스턴스를 생성해야 하는건가?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;1 번째, 2 번째는 모두 의존성을 가지고 있다.&lt;/p&gt;
&lt;p&gt;&amp;quot;의존성을 가진 객체는 의존성 해결 후, 완성된 인스턴스가 되어야만 한다.&amp;quot;&lt;/p&gt;
&lt;p&gt;즉, 이 말은 &lt;code&gt;ClassLoader&lt;/code&gt; 가 이 2 개중 하나를 먼저 로드하면, 완성된 인스턴스를 생성 할 수가 없다는 의미이다.&lt;/p&gt;
&lt;p&gt;그래서 떠올린 방식이 있다.&lt;/p&gt;
&lt;p&gt;{&amp;quot;의존성이 필요한 생성자에 &lt;code&gt;null&lt;/code&gt; 을 먼저 넣어 완성 된 것 처럼 만들자&amp;quot;}&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이게 무슨 의미냐면, 모든 객체들은 Spring Container 에 있는 인스턴스 주소를 가져와서&lt;/p&gt;
&lt;p&gt;단일 객체들을 완성해야 하는데, 어떤 생성자를 먼저 실행할 지 모른다.&lt;/p&gt;
&lt;p&gt;이는 대부분의 경우 의존성 문제로 인해 프로그램이 에러를 뱉으므로,&lt;/p&gt;
&lt;p&gt;&amp;quot;일단 Spring Container 를 채운 이후, 의존성은 프로그램 로직으로 해결&amp;quot; 하는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;null 을 넣어 인스턴스를 생성한 이후의 Spring Container&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TB

subgraph Container[&amp;quot;Spring-Container&amp;quot;]
    direction TB
    First(&amp;quot;First-Object&amp;quot;)
    Second(&amp;quot;Second-Object&amp;quot;)
    Third(&amp;quot;Third-Object&amp;quot;)

    First --Second 라고 착각--&amp;gt; null1(&amp;quot;Field - null&amp;quot;)
    Second --Third 라고 착각--&amp;gt; null2(&amp;quot;Field - null&amp;quot;)
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 그래프를 보면 이러한 결과를 도출할 수 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;프로그램은 의존성이 전부 확보 되었다고 생각하나, 논리적으로 전혀 그렇지 않다.&lt;/li&gt;
&lt;li&gt;Spring Container 는 일단 &amp;quot;모든 객체 인스턴스&amp;quot;를 생성했다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;위에서 일부로 힌트로 &lt;code&gt;Field&lt;/code&gt; 를 넣어두었다.&lt;/p&gt;
&lt;p&gt;위에서 &lt;code&gt;Constructor&lt;/code&gt;, &lt;code&gt;Method&lt;/code&gt;, &lt;code&gt;Field&lt;/code&gt; 와 같은 Reflection API 를 다루었다.&lt;/p&gt;
&lt;p&gt;여기서 &lt;code&gt;Field&lt;/code&gt; 는 인스턴스와 더불어, 원하는 값으로 재변경 할 수 있다.&lt;/p&gt;
&lt;p&gt;(단, &lt;code&gt;private final xxxx..&lt;/code&gt; 식으로 &lt;code&gt;final&lt;/code&gt; 이 붙은 값은 변경 할 수 없다.)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;위에서 &lt;strong&gt;Spring Container&lt;/strong&gt; 생성은 되었다.&lt;/p&gt;
&lt;p&gt;이제 의존성을 해결해야 한다. (앞으로 만들게 될 Logic 으로 직접.)&lt;/p&gt;
&lt;p&gt;컨테이너에 들어와야 할 객체들을 인식 할 때, 완성된 객체가 있다면 바로 주입하면 된다.&lt;/p&gt;
&lt;p&gt;그러나 대부분의 경우는 의존성이 바로 해결되지 않으므로,&lt;/p&gt;
&lt;p&gt;직접 로직을 작성해 보았다.&lt;/p&gt;
&lt;h3&gt;빠진 설명 &amp;amp;&amp;amp; 다른 방식 채택으로 인한 문제점&lt;/h3&gt;
&lt;p&gt;기능을 완성하고 나서 글을 점검하고 있는데,&lt;/p&gt;
&lt;p&gt;앞으로 나올 Java 의 내장 타입과 더불어 Logic 을 이해하기 위해&lt;/p&gt;
&lt;p&gt;글을 덧붙여야 한다고 판단했다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;Spring 의 객체 컨테이너 방식&lt;/h4&gt;
&lt;p&gt;Spring 은 외부 라이브러리 &lt;code&gt;CGLIB&lt;/code&gt; 를 이용하여 객체를 완성한다.&lt;/p&gt;
&lt;p&gt;만약에 클래스 &lt;code&gt;Compo1&lt;/code&gt; 이 클래스 &lt;code&gt;Compo2&lt;/code&gt; 를 의존성으로 원하여,&lt;/p&gt;
&lt;p&gt;생성자 인자에 넣어두고, &lt;code&gt;AutoWired&lt;/code&gt; 를 표식 해 놨다고 가정 해 보자.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그렇다면, Spring 은 &lt;code&gt;Compo2&lt;/code&gt; 가 &amp;quot;완전한 객체&amp;quot; 가 될 때 까지,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Compo2&lt;/code&gt; 가 원하는 의존성을 찾고, 또 필요한 의존성 객체가 완성 될 때 까지 찾고 완성하고..&lt;/p&gt;
&lt;p&gt;따라서 &lt;code&gt;Compo2&lt;/code&gt; 가 완전한 의존성이 되었을 때, &lt;code&gt;Compo1&lt;/code&gt; 의 &lt;code&gt;Compo2&lt;/code&gt; 의존성이 드디어&lt;/p&gt;
&lt;p&gt;내부에 넣어지는 방식이다. 의존성 방식이 &lt;code&gt;DFS&lt;/code&gt; 알고리즘에 해당한다고 보면 된다.&lt;/p&gt;
&lt;p&gt;Spring 은 특정 객체가 원하는 객체가 DFS 방식으로 의존성을 완료하지 못했을 때,&lt;/p&gt;
&lt;p&gt;(대부분은 순환 참조 문제겠지만,) 에러를 낸다.&lt;/p&gt;
&lt;p&gt;이 때문에, 순환참조가 발생 가능한 구조에서 &lt;code&gt;@Lazy&lt;/code&gt; 표식을 의존성에 넣어두지 않으면,&lt;/p&gt;
&lt;p&gt;Spring 은 서버의 시작을 &amp;quot;거절&amp;quot; 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;뿐만 아니라, Spring 은 &lt;code&gt;CGLIB&lt;/code&gt; 를 사용하는데,&lt;/p&gt;
&lt;p&gt;이 라이브러리는 JVM 이 실질적으로 실행하는 &lt;code&gt;.class&lt;/code&gt; 파일, 즉 바이너리 파일을,&lt;/p&gt;
&lt;p&gt;강제로 수정하여 원하는 코드나 기능을 추가하는 라이브러리이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;무엇 때문에 &amp;quot;강제로 바이너리 파일을 수정하냐?&amp;quot; 할 수 있는데, Java 자체에 제약이 많다.&lt;/p&gt;
&lt;p&gt;가장 대표적인 예시로, &lt;code&gt;Lombok&lt;/code&gt; 라이브러리에서 &lt;code&gt;@Getter&lt;/code&gt;, &lt;code&gt;@Setter&lt;/code&gt; 와 같은 기능은&lt;/p&gt;
&lt;p&gt;컴파일 시 컴파일러가 객체에 &lt;code&gt;Method&lt;/code&gt; 를 넣어주는 것이 아니다.&lt;/p&gt;
&lt;p&gt;정확히 컴파일 될 때, 바이너리 파일에 접근하여 해당 객체 &lt;code&gt;.class&lt;/code&gt; 파일에 접근하여&lt;/p&gt;
&lt;p&gt;해당 필드 변수명과 결합하여 &lt;code&gt;getName()&lt;/code&gt; 과 같은 메서드와 반환문 자체를 수정한다.&lt;/p&gt;
&lt;p&gt;게다가 AOP 기능, 혹은 내부적인 Proxy 를 만들 때도 매우매우 유용하다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;내가 만들려는 컨테이너의 방식&lt;/h4&gt;
&lt;p&gt;나는 &amp;quot;순수 Java 와 내장 기능&amp;quot;만을 사용하여 컨테이너를 제작한다는 &amp;quot;제약&amp;quot; 을 가지고 시작한다.&lt;/p&gt;
&lt;p&gt;물론 Spring 과 비슷하게 현재 객체가 필요로 하는 의존성이 완성되도록 DFS 방식을 만들 수 있었지만,&lt;/p&gt;
&lt;p&gt;프레임워크에서 개발 시 발생하는 순환 참조에 대한 제약이 강력하다고 생각했다.&lt;/p&gt;
&lt;p&gt;당연하게도, 순환 참조를 예방하기 위해 interface 사용을 권장 할 뿐만 아니라,&lt;/p&gt;
&lt;p&gt;너무 꼬여서 스파게티가 될 경우 &lt;code&gt;@Lazy&lt;/code&gt; 를 접두어로 붙여 의존성을 풀 수도 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나 나는 이런 생각을 했다.&lt;/p&gt;
&lt;p&gt;&amp;quot;컴포넌트를 선언하고, 서로만을 원하는 순환 참조가 발생하더라도, 그게 꼭 오류가 되어야 할까?&amp;quot;&lt;/p&gt;
&lt;p&gt;이는 어떠한 Architecture 를 구상하더라도, 스파게티 코드를 생성할 수도 있는 위험한 발상이다.&lt;/p&gt;
&lt;p&gt;그러나, 오히려 인스턴스 식품처럼 구조를 빠르고 간편하게 생성 할 수 있는 방식이기도 하다.&lt;/p&gt;
&lt;p&gt;(단 객체의 &amp;quot;생성자&amp;quot; 에서 의존성을 필드 변수에만 주입해야 한다. 다른 로직을 넣으면 안된다.)&lt;/p&gt;
&lt;p&gt;따라서 &amp;quot;순환 참조(Circular Dependency)&amp;quot; 가 된 의존성들을 경고하고,&lt;/p&gt;
&lt;p&gt;결국 모든 의존성을 해결 해 주는 것이 어떨까? 생각했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그 방식을 해결 해 줄 수 있는 방식이 &amp;quot;선 컨테이너 완성 후 의존성 해결&amp;quot; 이다.&lt;/p&gt;
&lt;p&gt;물론, 컨테이너는 정말 완료되지 않았다. 가짜 의존성으로 가득 찬 컨테이너는 완성되지 않았다.&lt;/p&gt;
&lt;p&gt;그러나, 클래스 메타데이터를 모두 읽으며 필요한 의존성들을 &amp;quot;기록&amp;quot; 해 둔 뒤,&lt;/p&gt;
&lt;p&gt;의존성을 해소 해 주는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이 방식은 &amp;quot;선 의존성 해결 후 컨테이너 완성&amp;quot; 이라는 로직을 가진 Spring 과는&lt;/p&gt;
&lt;p&gt;완전히 방식이 정반대 인 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;하지만 순수 Java 및 내장 기능만 운용한다는 제약은 프레임워크에서도&lt;/p&gt;
&lt;p&gt;사용할 개발자에게조차 제약을 만들 수 밖에 없도록 만들었다.&lt;/p&gt;
&lt;p&gt;가장 중요한 제약은 바로&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;quot;필드변수, 생성자 인자 변수명 동일&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;프록시 적용 시 적용될 메서드에 대한 인터페이스 작성 및 적용&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이다.&lt;/p&gt;
&lt;p&gt;이는 언어의 제약과 더불어 로직의 한계였는데, 나는 ByteCode File(&lt;code&gt;.class&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;자체를 수정하지 않기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;Java 에서 프록시를 적용하는 방법은 내장 API 를 사용하면 되는데,&lt;/p&gt;
&lt;p&gt;이 기능이 범용성 있게 사용하기 매우 어려우며,&lt;/p&gt;
&lt;p&gt;가장 중요한 것은 프록시 적용 객체는 &amp;quot;기존 객체와 완전히 다르다&amp;quot;.&lt;/p&gt;
&lt;p&gt;기존 객체와, 이 객체에 프록시가 적용된 새로운 객체의 공통점은,&lt;/p&gt;
&lt;p&gt;Interface 단 1 가지밖에 없다.&lt;/p&gt;
&lt;p&gt;만약,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;TestComponent&lt;/code&gt; 에 특정 Proxy 를 적용하여 나온 결과는 &lt;code&gt;TestComponent&lt;/code&gt; 가 아니라,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Proxy$$6&lt;/code&gt; 이라는 것이다.&lt;/p&gt;
&lt;p&gt;이로 인해 곤욕을 겪었지만, 커스텀 Annotation 을 제작하여 해결 한 상태이다.&lt;/p&gt;
&lt;p&gt;앞으로 제기될 여러 가지 문제로 인해,&lt;/p&gt;
&lt;p&gt;생성자에서 인자로 주어진 여러 개의 의존성과, 객체의 필드 변수의 이름은 동등해야 한다.&lt;/p&gt;
&lt;p&gt;이 제약이 강력하게 주어진다. 틀리면 에러를 던지고 프로그램이 종료되도록 설계했다.&lt;/p&gt;
&lt;p&gt;아직 이해되지 않겠지만, 나는 생성자 인자로 &lt;code&gt;null&lt;/code&gt; 값을 던지고 이후에 필드 변수로 값을 주입하여&lt;/p&gt;
&lt;p&gt;의존성을 해결하기 때문에, 이에 대한 연결성을 확보하기 위해 &amp;quot;변수명&amp;quot; 이라는 제약이 생겼다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;나머지 이야기는 앞으로 나올 예제와 설명으로 인지하게 된다.&lt;/p&gt;
&lt;p&gt;그리고 &amp;quot;객체 의존성 해결&amp;quot; 이라는 부분은 생각보다 어렵지만,&lt;/p&gt;
&lt;p&gt;그 만큼 Reflection API 로 해결 할 수 있는 방안이 많다.&lt;/p&gt;
&lt;p&gt;내 방식은 절대로 또 다른 정답이 될 수 없다. 그저 &amp;quot;다른 방식&amp;quot; 일 뿐이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;다시 돌아와서 의존성 해결은 어떤 방식으로 진행하게 되나?&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;@Component&lt;/code&gt; 역할을 하는 &lt;code&gt;@MyComponent&lt;/code&gt; 애너테이션을 가진&lt;/p&gt;
&lt;p&gt;모든 객체 메타데이터 &lt;code&gt;Class&amp;lt;?&amp;gt;&lt;/code&gt; 타입의 정보를 추출하여 순회한다.&lt;/p&gt;
&lt;p&gt;그리고 객체 메타데이터는 Convention(관례) 적으로 &lt;code&gt;clazz&lt;/code&gt; 라고 표현한다.&lt;/p&gt;
&lt;br/&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;HashMap&lt;/code&gt;, &lt;code&gt;ArrayList&lt;/code&gt; 등등 자료구조를 사용하게 된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;com.damsoon.component.xxx&lt;/code&gt; 를 원하는 &lt;code&gt;(Field, Object)&lt;/code&gt; 들을 저장해 둔다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;code&gt;HashMap&amp;lt;String, ArrayList&amp;lt;해당 의존성 대기 자료구조&amp;gt;&amp;gt;&lt;/code&gt; 로 의존성 대기 논리를 만든다.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;아마 위 자료구조에는 (객체 변수)&lt;code&gt;Field&lt;/code&gt;, 인스턴스(&lt;code&gt;Object&lt;/code&gt;) 가 들어갈 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;인식한 클래스 메타데이터의 생성자를 추출한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;clazz.getConstructors(); ==&amp;gt; Constructor[]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;생성자의 인수를 추출한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Method.getParameters(); ==&amp;gt; Parameter[]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;생성자 인수(args) 에 선언된 클래스 패키지 이름을 추출하여 의존성으로 등록한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Method&lt;/code&gt; 의 인자들은 &lt;code&gt;Parameter&lt;/code&gt; 이라는 내부적인 API 자료구조로 존재한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Parameter[]&lt;/code&gt; 에서 각 파라미터의 정의 &lt;code&gt;Component1 com1&lt;/code&gt; 가 있을 경우,&lt;/li&gt;
&lt;li&gt;이 &amp;quot;객체&amp;quot; 는 &lt;code&gt;com.damsoon.component.Component1&lt;/code&gt; 이라는 의존성을 필요로 한다.&lt;/li&gt;
&lt;li&gt;이를 &lt;code&gt;HashMap&amp;lt;String, ArrayList&amp;lt;의존성 대기 자료구조&amp;gt;&amp;gt;&lt;/code&gt; 에 넣는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ol start=&quot;6&quot;&gt;
&lt;li&gt;인자에 &lt;code&gt;null&lt;/code&gt; 주입 후, 인스턴스를 생성한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;가짜 의존성 &lt;code&gt;null&lt;/code&gt; 을 인자로 넣어 의존성이 완성 된 것 처럼 만든다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ol start=&quot;7&quot;&gt;
&lt;li&gt;생성자 인자 이름과 동일한 &lt;code&gt;Field&lt;/code&gt; 를 추출하고, 인스턴스를 기준으로 필요한 의존성을 Map 에 저장한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;생성자 인자(Test test) == 필드변수(private Test test)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Reflection 에서 &lt;code&gt;객체.변수 = 새로운 값&lt;/code&gt; 으로 설정할 수 없다.&lt;/li&gt;
&lt;li&gt;Java 자체의 엄격한 규칙으로 인해, 값 변경의 &amp;quot;권한&amp;quot; 은 객체 내부의 자료구조들이 가진다.&lt;/li&gt;
&lt;li&gt;즉, &lt;code&gt;Constructor&lt;/code&gt;, &lt;code&gt;Method&lt;/code&gt;, &lt;code&gt;Field&lt;/code&gt;, 등등이 권한을 가진다.&lt;/li&gt;
&lt;li&gt;따라서 의존성 대기 리스트를 등록 할 때, &lt;code&gt;Field(필드), Object(미완성 인스턴스)&lt;/code&gt; 쌍을 이룬다.&lt;/li&gt;
&lt;li&gt;EX - &lt;code&gt;field.set(Object, 새로운 field 값 - 타입 혹은 인터페이스 일치)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ol start=&quot;8&quot;&gt;
&lt;li&gt;생성자 단에서 요구한 의존성이 여러 개 일 수 있다는 것을 명심해야 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;우리가 추출한 &lt;code&gt;Constructor&lt;/code&gt; 에서 필요한 인자들(&lt;code&gt;Parameter&lt;/code&gt;) 는 여러 개 일 것이다.&lt;/li&gt;
&lt;li&gt;객체가 원하는 의존성은 10 개일 수도 있다. ==&amp;gt; 10 개의 의존성 파라미터가 존재한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;9&quot;&gt;
&lt;li&gt;시각적으로 보면, &amp;quot;패키지 클래스&amp;quot; 의존성이 필요한 인스턴스들이 &amp;quot;대기줄을 선다&amp;quot; 라는 느낌&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;패키지 의존성은 &lt;code&gt;com.damsoon.component.xxx..&lt;/code&gt; 문자열을 의미한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HashMap&amp;lt;String, List&amp;lt;의존성 대기 자료구조&amp;gt;&amp;gt;&lt;/code&gt; 는 &lt;br/&gt; &lt;code&gt;&amp;quot;..damsoon.component1&amp;quot;&lt;/code&gt; 을 원하는 &amp;quot;필드-객체&amp;quot; 쌍의 배열을 의미한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;Annotation 을 표식한 모든 클래스의 인스턴스를 컨테이너에 집어넣는 상황에서,&lt;/p&gt;
&lt;p&gt;모든 인스턴스가 컨테이너에 등록되었을 때 의존성을 풀기 시작할 패키지 클래스들도 필요하다.&lt;/p&gt;
&lt;p&gt;즉, 내가 위에서 &lt;code&gt;HashMap&amp;lt;String, ArrayList&amp;lt;ResolveWait&amp;gt;&amp;gt;&lt;/code&gt; 라는 의존성 대기 맵을&lt;/p&gt;
&lt;p&gt;만든 것과 동일하게,&lt;/p&gt;
&lt;p&gt;컨테이너가 컴포넌트들을 순회하며 &amp;quot;의존성이 필요없거나, 해결되어 있는 경우,&amp;quot;&lt;/p&gt;
&lt;p&gt;이들을 따로 넣어둘 &lt;code&gt;Queue&lt;/code&gt; 가 필요하다.&lt;/p&gt;
&lt;p&gt;즉, &lt;code&gt;Queue&amp;lt;객체(&amp;quot;패키지 이름&amp;quot;, Object - 인스턴스))&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;모든 컴포넌트들을 순회하며 필요한 의존성을 등록한 뒤,&lt;/p&gt;
&lt;p&gt;(&lt;code&gt;HashMap&amp;lt;String, List&amp;lt;의존성 대기 맵&amp;gt;&amp;gt;&lt;/code&gt;) ==&amp;gt; 의존성 대기 Map&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Queue&lt;/code&gt; 에서 먼저 완성되어 있던 객체를 꺼내,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Object instance = Queue.poll(); ==&amp;gt; 진짜 완성된 객체 인스턴스&lt;/code&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;의존성 대기 맵 &lt;code&gt;HashMap&lt;/code&gt; 에서 &amp;quot;패키지 이름&amp;quot; 을 기준으로 &lt;code&gt;ArrayList&lt;/code&gt; 를 뽑는다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;String 인스턴스 패키지 이름 = instance.getClass().getName();&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;(실제로는 &lt;code&gt;Queue&amp;lt;CompeteObject&amp;gt;&lt;/code&gt; 라는 자료구조로, 인스턴스의 패키지 이름이 동봉되어 있다)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;String 인스턴스 패키지 이름 = completeObject.getFullName();&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;이 때, 패키지 이름은 &lt;code&gt;com.damsoon.component.Component1&lt;/code&gt; 와 비슷하다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;따라서 완성된 인스턴스를 뽑았다면, &lt;code&gt;com.damsoon.component.Component1&lt;/code&gt; 일 것이라고 가정하자.&lt;/p&gt;
&lt;p&gt;그렇다면 위의 패키지 객체, 즉 인스턴스를 원하는 &amp;quot;의존성 대기 리스트&amp;quot; 가 존재 할 것이다.&lt;/p&gt;
&lt;p&gt;컴포넌트1 을 원하는 다양한 객체들이 존재 할 것이다.&lt;/p&gt;
&lt;p&gt;컴포넌트1 이 완성되었으므로, 이를 원하는 대기 인스턴스들의 필드 변수에 컴포넌트1 을 꽂아주어야 한다.&lt;/p&gt;
&lt;p&gt;의존성 대기 맵에서 &lt;code&gt;field&lt;/code&gt;, &lt;code&gt;instance&lt;/code&gt; 리스트를 가져온다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;`List&amp;lt;의존성 대기&amp;gt; = 의존성map.get(&amp;quot;com.damsoon.component.Component1&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 &lt;code&gt;List&lt;/code&gt; 는, &lt;code&gt;Component1&lt;/code&gt; 을 받기 위해 대기중인 &lt;code&gt;Field-Object&lt;/code&gt; 의 쌍들이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;List&lt;/code&gt; 를 순회하며 &lt;code&gt;field.set(대기 instance, 완성 instance)&lt;/code&gt; 로 실행한다.&lt;/p&gt;
&lt;p&gt;이로서 &lt;code&gt;Component1&lt;/code&gt; 을 원하는 모든 대기 의존성들은 &lt;code&gt;Component1&lt;/code&gt; 에 대해서&lt;/p&gt;
&lt;p&gt;의존성을 해소했다.&lt;/p&gt;
&lt;p&gt;(이로서 대기 의존성 객체는 모든 의존성을 해소 했을 수도, 아직 해소를 다 하지 못했을 수도 있다.)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그리고 순회한 &lt;code&gt;com.damsoon.component.Component1&lt;/code&gt; 를&lt;/p&gt;
&lt;p&gt;&lt;code&gt;HashMap&lt;/code&gt; 에서 &lt;code&gt;remove&lt;/code&gt; 한다. (Component1 은 전부 해소되었기 때문이다.)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;즉, 위의 과정은 HashMap 에서&lt;/p&gt;
&lt;p&gt;Component1 객체를 필요로 하는 대기 의존성 List 를 뽑아 순며하며&lt;/p&gt;
&lt;p&gt;Component1 을 원하는 모든 객체가 Component1 을 &amp;quot;해소&amp;quot; 했다.&lt;/p&gt;
&lt;p&gt;그렇다면, 원래 &amp;quot;대기 의존성&amp;quot; 이던 객체가, &lt;code&gt;Component2&lt;/code&gt; 라고 가정하자.&lt;/p&gt;
&lt;p&gt;즉,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 예시
package com.damsoon.component;

@MyComponent
// Component2 는 패키지명으로, &amp;quot;com.damsoon.component.Component2&amp;quot; 이다.
public class Component2 {
    // Component1 은 패키지명으로, &amp;quot;com.damsoon.component.Component1&amp;quot; 이다.
    private Component1 component1;

    @MyAutowired
    // 인자의 타입 패키지명은 위와 동일하다.
    public Component2(Component1 component1) {
        this.component1 = component1;
        // 여기서 타 컴포넌트의 로직이나 변수를 참조하면 안된다.
        // Spring 이나 내가 만드는 프로그램이나 동일하다.
    }
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위에 작성한 &lt;code&gt;Component2&lt;/code&gt; 는 원래 &amp;quot;의존성 대기 맵&amp;quot; 에 등록되어 있었다.&lt;/p&gt;
&lt;p&gt;그런데, 위의 코드를 보자.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Component1&lt;/code&gt; 이 해소되었다면, 위의 객체는 여전히 대기해야 할 인스턴스일까?&lt;/p&gt;
&lt;p&gt;당연히 아니다. ==&amp;gt; 이를 &lt;code&gt;Queue&lt;/code&gt; 에 넣어준다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Queue&lt;/code&gt; 에서 하나씩 완성된 인스턴스를 뽑아 의존성을 넣어 줄 때 마다,&lt;/p&gt;
&lt;p&gt;의존성 대기 인스턴스는 &amp;quot;완성된 인스턴스&amp;quot; 로 전환되며 &lt;code&gt;Queue&lt;/code&gt; 에 새로이 넣어 주는 것이다.&lt;/p&gt;
&lt;p&gt;이러한 방식으로 &lt;code&gt;Queue&lt;/code&gt; 가 Empty 될 때 까지 반복한다.&lt;/p&gt;
&lt;p&gt;EX - &lt;code&gt;while(!completeQueue.isEmpty()) { ... }&lt;/code&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;복잡한 로직을 정리하는 좋은 방식 중 하나는 시각화 하는 것이다.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TB

Spring-Container[&amp;quot;모든 객체의 메타데이터를 순회하며 &amp;lt;br/&amp;gt; 의존성 존재 -&amp;gt; 의존성 대기 Map &amp;lt;br/&amp;gt; 의존성 미존재 -&amp;gt; 완성 인스턴스 Queue&amp;quot;]

HashMap(&amp;quot;HashMap - 미완성 컴포넌트가 등록됨&amp;quot;)

Queue(&amp;quot;Queue - 완성된 컴포넌트들이 등록됨&amp;quot;)

Spring-Container --&amp;gt; HashMap

Spring-Container --&amp;gt; Queue

Queue1(&amp;quot;완성 컴포넌트 의존성을 필요로 하는 다른 컴포넌트들에게 주입한다.&amp;quot;)

Queue -.-&amp;gt; Queue1

HashMap1(&amp;quot;완성된 컴포넌트 패키지 이름으로 ArrayList 를 추출하여 의존성을 해결 해 준다.&amp;quot;)

HashMap -.-&amp;gt; HashMap1

Queue1 --&amp;gt; HashMap1

Complete1(&amp;quot;의존성이 해소된 컴포넌트는 Queue 로 이동한다.&amp;quot;)

HashMap1 --&amp;gt; Complete1

Complete1 --&amp;gt; Queue

Complete1 ----&amp;gt; Complete2(&amp;quot;Queue 가 비워 질 때 까지 반복한다.&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기서 중요한 것은,&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;컴포넌트의 의존성이 해소되었다는 것을 어떻게 아는가?&amp;quot;&lt;/strong&gt; 라는 것이었다.&lt;/p&gt;
&lt;p&gt;즉, 의존성이 해소되었다는 것을 알기 위해서는 Tracker 를 만들어야 한다.&lt;/p&gt;
&lt;p&gt;어려운 구조는 아니고, 이러한 기능의 자료구조 Class 를 만들면 된다.&lt;/p&gt;
&lt;p&gt;이것 또한 &lt;code&gt;HashMap&lt;/code&gt; 구조로 만들면 되는데,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Map&amp;lt;Object, AtomicInteger&amp;gt;&lt;/code&gt; 로 등록하면 된다.&lt;/p&gt;
&lt;p&gt;(&lt;code&gt;AtomicInteger&lt;/code&gt; 인 이유는, &lt;code&gt;Integer&lt;/code&gt; 는 불변성을 가져 값의 변경이 어렵기 때문.)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;또한,&lt;/strong&gt; 왜 &lt;code&gt;SPRING&lt;/code&gt; 이 &lt;code&gt;@Lazy&lt;/code&gt; 를 사용하는지도 깨닫게 되었다.&lt;/p&gt;
&lt;p&gt;우리가 생각하는 상황의 순환참조는 주로 하나의 &amp;quot;원&amp;quot; 형태로 이루어 질 것이다.&lt;/p&gt;
&lt;p&gt;위에서 내가 만들었던 그래프 예제를 다시 가져와 보면,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TB

A-Class

B-Class

C-Class

D-Class

B-Class --B 는 C 를 필요로 한다.--&amp;gt; C-Class
B-Class --B 는 D 를 필요로 한다.--&amp;gt; D-Class

D-Class --D 는 A 를 필요로 한다.--&amp;gt; A-Class

A-Class --A 는 B 를 필요로 한다.--&amp;gt; B-Class&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 예제에서 &lt;code&gt;C-Class&lt;/code&gt; 는 &lt;strong&gt;의존 객체가 없으므로, 완성된 인스턴스로 간주한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;그렇다면, 여기서 발견할 수 있는 원형 형태의 순환 참조는&lt;/p&gt;
&lt;p&gt;&lt;code&gt;A&lt;/code&gt; --&amp;gt; &lt;code&gt;B&lt;/code&gt; --&amp;gt; &lt;code&gt;D&lt;/code&gt; --&amp;gt; &lt;code&gt;A&lt;/code&gt; 형태이다.&lt;/p&gt;
&lt;p&gt;단순한 원 형태이므로, &lt;code&gt;A&lt;/code&gt;, &lt;code&gt;B&lt;/code&gt;, &lt;code&gt;D&lt;/code&gt; 중 하나만 &lt;strong&gt;Proxy&lt;/strong&gt; 구조로 변형해도 해소가 가능하다.&lt;/p&gt;
&lt;p&gt;그러나, 여기서 누구 하나라도 생성자 메서드에서 &amp;quot;의존성을 등록하는 것&amp;quot; 뿐만 이 아니라,&lt;/p&gt;
&lt;p&gt;&amp;quot;의존성의 변수 혹은 메서드 참조&amp;quot; 시 무조건 Error 가 난다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;순환참조는 애초에 논리적 오류이다.&lt;/h3&gt;
&lt;p&gt;예를 들어서 이런 Java 코드가 있다고 가정 해 보자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;class A {
    public B b;
    public A (B b) {
        this.b = b; // 이건 해결 가능 -&amp;gt; 가짜 의존성 주입으로
        b.init(); // 이건 Spring 으로도 해결 불가능
    }
    public void init() {
        System.out.println(b.toString());
        // ...
    }
}

class B {
    public A a;
    public B (A a) {
        this.a = a; // 이건 해결 가능 -&amp;gt; 가짜 의존성 주입
        a.init(); // Spring 으로도 해결 불가능
    }
    public void init() {
        System.out.println(a.toString());
        // ...
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;내가 예제로 작성한 위의 코드는 순환 참조의 완벽한 예시이다.&lt;/p&gt;
&lt;p&gt;그냥 각각의 클래스 &lt;code&gt;Field&lt;/code&gt; 에 인스턴스를 넣는 것도 &lt;code&gt;Reflection API&lt;/code&gt; 덕분인데,&lt;/p&gt;
&lt;p&gt;심지어는 아직 제대로 생성되지도 않은 객체를 서로 실행하게 된다.&lt;/p&gt;
&lt;p&gt;그러니, 서로가 서로를 생성하는 과정에서 Code CallStack 이 쌓여&lt;/p&gt;
&lt;p&gt;&lt;code&gt;StackOverFlowError&lt;/code&gt; 에러가 나거나,&lt;/p&gt;
&lt;p&gt;혹은 객체의 의존성 임시 완성을 위해 가짜 의존성 &lt;code&gt;null&lt;/code&gt; 이 존재하여&lt;/p&gt;
&lt;p&gt;&lt;code&gt;NullPointerException&lt;/code&gt; 에러가 난다.&lt;/p&gt;
&lt;p&gt;Spring 은 &lt;code&gt;Bean&lt;/code&gt; 관련 오류로 출력 될 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;만약에, 순환 참조 상태에서 서로를 초기화하는 &lt;code&gt;init()&lt;/code&gt; 역할의 메서드를 넣고 싶다면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@PostConstruct&lt;/code&gt; 라는 애너테이션과 Method 를 작성하여 &amp;quot;따로 분리&amp;quot; 하면 된다.&lt;/p&gt;
&lt;p&gt;(&lt;strong&gt;Spring&lt;/strong&gt; 을 사용한다는 가정 하에. - 기본 사양에서는 안됩니다.)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;위에서 보인 &amp;quot;생성자&amp;quot; 에서 서로의 메서드를 사용하는 것을 빼고,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;A &amp;lt;--&amp;gt; B&lt;/code&gt; 의 단순한 상호참조 관계에서조차,&lt;/p&gt;
&lt;p&gt;의존성 완료 &lt;code&gt;Queue&lt;/code&gt; 에 들어갈 수가 없다. (A 와 B 가 서로 요구할 경우)&lt;/p&gt;
&lt;p&gt;그렇다면, A, B 는 영원히 의존성이 해결되지 않는다.&lt;/p&gt;
&lt;p&gt;이 때는 강제로 뜯어서 해결하는 수 밖에 없는 것이다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TB

step-1(&amp;quot;A 한테 미완성 B 를 넣어준다.&amp;quot;);
step-2(&amp;quot;A 는 자신이 완성되었다고 생각한다.&amp;quot;);
step-3(&amp;quot;A 는 완성 인스턴스 Queue 로 들어간다.&amp;quot;);
step-4(&amp;quot;Queue 에서 A 를 뽑는다.&amp;quot;);
step-5(&amp;quot;A 를 필요로 했던 B 가 의존성 해결이 된다.&amp;quot;);
step-6(&amp;quot;이로서 A 와 B 모두 실질적으로 의존성이 해결 되었다&amp;quot;);

step-1 --&amp;gt; step-2 --&amp;gt; step-3 --&amp;gt; step-4 --&amp;gt; step-5 --&amp;gt; step-6&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 과정은 마치 심장을 수술하는 과정처럼 매우 촘촘하게 Logic 이 구성되어야 한다.&lt;/p&gt;
&lt;p&gt;이 과정에서 사소한 에러라도 일어나면 프로그램은 박살이 나기 때문이다..&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Annotation 요구사항&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Container&lt;/strong&gt; 는 유일 객체로 제작하며 순환 참조가 발생하지 않도록 만들어 주어야 한다.&lt;/p&gt;
&lt;p&gt;그렇다면, Annotation 은 어떠한 역할을 수행하게 될까?&lt;/p&gt;
&lt;p&gt;익숙 한 대로, 혹은 얘기 한 대로, Spring Container 에 넣을 것이라는 표식을 새기는 용도이거나,&lt;/p&gt;
&lt;p&gt;IDE 가 프레임워크를 잘 사용하도록 유도해 주는 장치가 되기도 한다.&lt;/p&gt;
&lt;p&gt;Annotation 은 Java 의 &lt;code&gt;interface&lt;/code&gt; 형태 자체는 아니지만, 거의 동일한 속성을 지닌다.&lt;/p&gt;
&lt;p&gt;따라서 Annotation &lt;code&gt;@interface&lt;/code&gt; 선언시, 메타데이터로서 가질 일종의 변수나 객체를 선언 할 수 있다.&lt;/p&gt;
&lt;p&gt;즉, &lt;code&gt;ClassLoader&lt;/code&gt; 는 메타데이터를 읽는 과정에서 우리가 평범히 아는&lt;/p&gt;
&lt;p&gt;&lt;code&gt;class&lt;/code&gt;, &lt;code&gt;interface&lt;/code&gt;, &lt;code&gt;Method&lt;/code&gt;, &lt;code&gt;Field&lt;/code&gt;(변수), &lt;code&gt;Constructor&lt;/code&gt; 뿐만 아니라,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@interface&lt;/code&gt; 로 선언된 Annotation 의 내부 정보까지 읽을 수 있다.&lt;/p&gt;
&lt;p&gt;(&lt;code&gt;interface&lt;/code&gt; 와 &lt;code&gt;Annotation&lt;/code&gt; 전부 &lt;code&gt;Class&amp;lt;?&amp;gt;&lt;/code&gt; 타입이다.)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;클래스 로더는 &amp;quot;클래스&amp;quot;, &amp;quot;메서드&amp;quot;, &amp;quot;생성자&amp;quot;, &amp;quot;필드&amp;quot; 등에 붙은 Annotation 을 읽고,&lt;/p&gt;
&lt;p&gt;나는 클래스 로더가 가져온 정보를 토대로 각각 다른 행동을 수행하도록 만든다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이미 먼 길을 와서 까먹었을 확률이 매우 높지만, 위에서 &lt;code&gt;ClassLoader&lt;/code&gt; 를 이용하여&lt;/p&gt;
&lt;p&gt;&lt;code&gt;FuncClass&lt;/code&gt;, &lt;code&gt;TestClass&lt;/code&gt; 메타데이터를 가져오고, 이를 &amp;quot;내가 직접&amp;quot; 정해서 실행하는 것이 아니라,&lt;/p&gt;
&lt;p&gt;&amp;quot;내가 정한 규칙 혹은 로직에 따라 JVM 이 판단해서 실행&amp;quot; 하는 로직을 작성했었다.&lt;/p&gt;
&lt;p&gt;그렇다. 메타프로그래밍이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;Annotation 은 타입에 따라 다른 행동을 하는 지침 역할을 할 뿐만 아니라,&lt;/p&gt;
&lt;p&gt;내부에 개발자가 미리 선언 해 놓은 메타데이터에 따라 다른 과정을 거칠 수도 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ClassLoader&lt;/code&gt; 와 &lt;code&gt;Annotation&lt;/code&gt; 은 찰떡궁합이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;&amp;quot;Spring Container 제작과 Annotation 의 역할&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이미 위 파트에서 Spring Container 를 어떻게 제작 할 건지, 의존성은 어떻게 해결 할 것인지 작성했다.&lt;/p&gt;
&lt;p&gt;Annotation 은, 어떤 class 가 Container 에 들어가는지, 해당 클래스 인스턴스가&lt;/p&gt;
&lt;p&gt;&amp;quot;나중에&amp;quot; 의존성이 해결되어도 되는지(순환참조 - Circulation Reference) &lt;code&gt;@Lazy&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;이에 대한 메타데이터를 코드 작성 시 개발자가 선언하도록 만들 것이다.&lt;/p&gt;
&lt;p&gt;(그래도 스파게티 코드는 지양하도록 만드는게 옳다고 생각합니다.)&lt;/p&gt;
&lt;p&gt;그러나, 여기서 &lt;code&gt;Proxy&lt;/code&gt; 적용법에 대해서 생각 해 보아야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;나는 Proxy Class 제작을 JS 로 전문 제작한 적이 있어 저 ~ 중급 수준의 언어에서도&lt;/p&gt;
&lt;p&gt;이와 비슷하게 만들 수 있지 않을까? 생각했다.&lt;/p&gt;
&lt;p&gt;다행히 Java 에서 자체적으로 Reflection API 중 &lt;code&gt;Proxy&lt;/code&gt; 가 존재한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;여기서 Java Reflection API 중 Proxy 객체 이론을 설명하면 몇백줄이 더 늘 것 같아서,&lt;/p&gt;
&lt;p&gt;조금 이따가 보여질 코드에서 이를 펼쳐보려 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Annotation 이 보여질 형태는 다음과 비슷할 것이다.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@MyComponent
public class ... { ... }

// or

@MyComponent
public class ... {
    public ... (@MyLazy ...) {
        ...
    }
}

// or

@MyComponent
@MyProxy(ExecutionTime.class)
public class ... {...}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이러한 방식으로 만들 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;Java 의 내장 Proxy 사용법은 조금 배워야 하기 때문에,&lt;/p&gt;
&lt;p&gt;먼저&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Spring Container 클래스 제작&lt;/li&gt;
&lt;li&gt;Annotation 제작&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HashMap&lt;/code&gt;, &lt;code&gt;Queue&lt;/code&gt;, &lt;code&gt;Tracker&lt;/code&gt;(custom) 자료구조를 이용한 실제 의존성 해결 논리 제작&lt;/li&gt;
&lt;li&gt;Proxy 전용 Annotation 제작&lt;/li&gt;
&lt;li&gt;Proxy API 를 전담하는 클래스 제작&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이러한 과정으로 흘러 갈 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;코드 구현 - 한 단계씩 구현&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;➜  test-1 tree -L 3
. (test-1)
├── com
│   └── damsoon
│       ├── Main.java
│       ├── annotation
│       ├── func # --&amp;gt; 삭제하기
│       └── test # --&amp;gt; 삭제하기
├── compile-and-execute.sh
├── result
│   └── bin
│       └── com
└── sources.txt

9 directories, 4 files&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이전에 사용할 기능을 테스트하기 위해 만들었던 이 프로젝트 폴더를 그대로 활용 할 것이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;com.damsoon.Main&lt;/code&gt; 에서 로직이 시작 할 것이며,&lt;/p&gt;
&lt;p&gt;하위 패키지 폴더에 여러 유틸성 도구와 자료구조가 작성될 것이다.&lt;/p&gt;
&lt;p&gt;먼저 위의 &lt;code&gt;com.damsoon.xx&lt;/code&gt; 중에, &lt;code&gt;Main.java&lt;/code&gt;, &lt;code&gt;annotation&lt;/code&gt; 패키지 폴더 빼고&lt;/p&gt;
&lt;p&gt;나머지 하위 패키지 폴더를 삭제한다.&lt;/p&gt;
&lt;p&gt;그리고, Container 가 생성될 &lt;code&gt;container&lt;/code&gt; 패키지 폴더를 생성하고,&lt;/p&gt;
&lt;p&gt;이 패키지 폴더에 &lt;code&gt;CustomContainer&lt;/code&gt; 클래스를 제작할 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;먼저, 단일 인스턴스 객체를 저장하게 될 &lt;strong&gt;Container&lt;/strong&gt; 를 만들어 보자.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;컨테이너는 애너테이션으로 작성된 모든 클래스의 &amp;quot;생성자&amp;quot;(&lt;code&gt;Constructor&lt;/code&gt;) 를 보관한다.&lt;/li&gt;
&lt;li&gt;생성자와 인스턴스는 모두 &lt;code&gt;HashMap&lt;/code&gt; 으로 보관한다. (각 자료는 따로 보관)&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;h3&gt;Container Class&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;com.damsoon.container.CustomContainer.java&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package com.damsoon.container;

import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class CustomContainer {
    private Map&amp;lt;String, Constructor&amp;gt; constructorMap;
    private Map&amp;lt;String, Object&amp;gt; instanceMap;

    public CustomContainer (){
        constructorMap = new HashMap&amp;lt;&amp;gt;();
        instanceMap = new HashMap&amp;lt;&amp;gt;();
    }

    public Map&amp;lt;String, Constructor&amp;gt; getConstructorSet() {
        return this.constructorMap;
    }
    public boolean addConstructor(String fullPackageName, Constructor constructor) {
        Object alreadyContain = this.constructorMap.get(fullPackageName);

        if(alreadyContain != null) {
            System.out.println(
                    getClass().getSimpleName()
                            + &amp;quot; - [Constructor] : &amp;quot;
                            + constructor.getName()
                            + &amp;quot; 이 이미 존재합니다.&amp;quot;
            );
        }

        this.constructorMap.put(fullPackageName, constructor);

        return alreadyContain == null ? true : false;
    }

    public Map&amp;lt;String, Object&amp;gt; getInstanceSet() {
        return this.instanceMap;
    }

    public boolean addInstance(String fullPackageName, Object object) {
        Object alreadyContain = this.instanceMap.get(fullPackageName);

        if(alreadyContain != null) {
            System.out.println(
                    getClass().getSimpleName()
                            + &amp;quot; - [Instance] : &amp;quot;
                            + object.getClass().getSimpleName()
                            + &amp;quot; 이 이미 존재합니다.&amp;quot;
            );
        }

        this.instanceMap.put(fullPackageName, object);

        return alreadyContain == null ? true : false;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;간단히 생성자로 내부 자료구를 먼저 생성하고,&lt;/p&gt;
&lt;p&gt;&amp;quot;가져가는 것은 쉽되, 추가하는 것은 메서드로&amp;quot; 접근하도록 만들었다. (기능적으로 분리하기 위함)&lt;/p&gt;
&lt;p&gt;그리고 &lt;code&gt;HashMap&lt;/code&gt; 특성상 중복 추가도 에러가 나지는 않지만,&lt;/p&gt;
&lt;p&gt;특정 부분에서 놓친 알고리즘이 무한 반복 할 수도 있기 때문에,&lt;/p&gt;
&lt;p&gt;이미 존재 할 경우 경고 출력을 하도록 만들었다.&lt;/p&gt;
&lt;p&gt;(Container 는 정말 &amp;quot;담는 것만&amp;quot; 목적으로 하도록 만들었습니다.)&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;ResearchPackage Class&lt;/h3&gt;
&lt;p&gt;단순히 &lt;code&gt;Main&lt;/code&gt; 에서 시작하고, 하위에 패키지 경로, 파일들이 존재한다고&lt;/p&gt;
&lt;p&gt;마법처럼 인식시켜주지 않는다.&lt;/p&gt;
&lt;p&gt;즉, &lt;code&gt;File I/O&lt;/code&gt; 기능과, &lt;code&gt;Reflection API&lt;/code&gt; 를 이용하여&lt;/p&gt;
&lt;p&gt;재귀적으로 하위 파일을 탐색하며, 이들을 메타데이터화 시켜 저장해야 한다.&lt;/p&gt;
&lt;p&gt;즉, 파일을 찾으면, &lt;code&gt;Class&amp;lt;?&amp;gt;&lt;/code&gt; 형태로 변환시켜 &lt;code&gt;List&lt;/code&gt; 에 저장한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;여기서 알고리즘 중 &lt;strong&gt;BFS&lt;/strong&gt;, &lt;strong&gt;DFS&lt;/strong&gt; 방식을 선택하여 재귀 탐색을 실행하면 되는데,&lt;/p&gt;
&lt;p&gt;나는 &lt;strong&gt;DFS&lt;/strong&gt; (Stack) 방식을 이용하여 탐색을 할 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;실행되는 위치는 &lt;code&gt;com.damsoon.Main.class&lt;/code&gt; 이다.&lt;/p&gt;
&lt;p&gt;즉, &lt;code&gt;Main&lt;/code&gt; 클래스에서 실행되어 현재 위치, 그리고 재귀적인 패키지 위치와 더불어&lt;/p&gt;
&lt;p&gt;모든 &lt;code&gt;.class&lt;/code&gt; 파일을 탐색하여 &lt;code&gt;@Container&lt;/code&gt; 표식이 새겨진 모든 클래스들을 찾는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;솔직히 말하자면, 탐색 과정이 생각보다 복잡합니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;문제는 &lt;code&gt;Directory&lt;/code&gt; 가 따로 있는 것은 아니고, &lt;code&gt;File&lt;/code&gt; 이라는 클래스에 &amp;quot;디렉토리&amp;quot; 또한 들어있다.&lt;/p&gt;
&lt;p&gt;여기서 단순히 클래스를 로드하는 것 뿐만이 아닌, &lt;code&gt;URL&lt;/code&gt; 이라는 객체를 추출하는 역할 또한&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ClassLoader&lt;/code&gt; 가 수행하는 것을 볼 수 있을 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;ResearchPackage&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package com.damsoon.util;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

public class ResearchPackage {
    // 클래스 메타데이터 배열 저장.
    List&amp;lt;Class&amp;lt;?&amp;gt;&amp;gt; clazzList = new ArrayList&amp;lt;&amp;gt;();

    // Main 클래스를 품는 패키지 이름 (com.damsoon)
    String rootPackage;

    // 시작 루트 패키지 기입 강제
    public ResearchPackage(String rootPackage) {
        this.rootPackage = rootPackage;
    }

    // 로직 완료 후 추출
    public List&amp;lt;Class&amp;lt;?&amp;gt;&amp;gt; getClazzList() {
        return this.clazzList;
    }

    // 외부에서 시작 제어
    public void startScan() throws IOException, ClassNotFoundException {
        // 시작 루트 패키지 미기입시 시작하지 않는다. (생성자 단 에서 설정 강제)
        if(rootPackage == null) {
            System.out.println(&amp;quot;시작 패키지를 정하지 않았습니다.&amp;quot;);
            return;
        }

        // 파일 경로 --&amp;gt; URL --&amp;gt; File --&amp;gt; 디렉토리 or 클래스 파일
        // 이로 인해 기존 루트 패키지 이름을 &amp;quot;com.damsoon&amp;quot; --&amp;gt; &amp;quot;com/damsoon&amp;quot; 으로 변경
        String path = this.rootPackage.replace(&amp;#39;.&amp;#39;, &amp;#39;/&amp;#39;);

        // 파일, 패키지 리소스를 추출할 ClassLoader 가져오기
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        // 현재 패키지 실제 경로를 기준으로 파일, 패키지, JAR 파일을 모두 URL 로 추출한다. (JAR 은 현재 없다 가정)
        Enumeration&amp;lt;URL&amp;gt; urlResources = loader.getResources(path);

        // &amp;quot;시작 할 때만&amp;quot; URL iter 을 기준으로 파일로 치환하여 재귀한다.
        while(urlResources.hasMoreElements()) {
            URL rootURL = urlResources.nextElement();

            // URL 경로를 기반으로 File 객체를 생성. --&amp;gt; File 로 디렉토리인지, 파일인지 구별 및 클래스 메타데이터 추출이 가능하다.
            File rootFile = new File(rootURL.getFile());

            if(rootFile.isDirectory()) {
                scan(rootFile, this.rootPackage);
            }
        }
    }

    private void scan(File file, String packageName) throws ClassNotFoundException, IOException {
        System.out.println(&amp;quot;scan start&amp;quot;);

        // 하나의 파일(.class) 이건, 디렉토리이건 동일한 File[] 정보를 가져온다.
        File[] tmpFiles = file.listFiles();

        // 파일이 없으면 null 이 된다.
        if(tmpFiles == null) {
            return;
        }

        // 하나 혹은 그 이상의 파일 혹은 디렉토리들
        for (File eachFile : tmpFiles) {

            // 현재 파일은 &amp;quot;디렉토리&amp;quot; 일 때,
            if(eachFile.isDirectory()) {
                // File 리소스와 패키지 이름 + 현재 파일 이름을 재귀로 넘긴다.
                scan(eachFile, packageName + &amp;quot;.&amp;quot; + eachFile.getName());
            } else if(eachFile.getName().endsWith(&amp;quot;.class&amp;quot;)) { // 만약 디렉토리가 아닌 .class 파일이라면

                // 맨 뒤의 protocol 은 없애주고 등록한다. (패키지명으로)
                String className = packageName
                        + &amp;#39;.&amp;#39;
                        + eachFile.getName().replace(&amp;quot;.class&amp;quot;, &amp;quot;&amp;quot;);

                // classList 배열에 등록
                System.out.println(className);
                this.clazzList.add(Class.forName(className));
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 &lt;code&gt;ResearchPackage&lt;/code&gt; 클래스는,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Main&lt;/code&gt; 을 감싸는 패키지 이름으로부터 시작하여,&lt;/p&gt;
&lt;p&gt;DFS 방식으로 재귀적으로 탐색한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Main.class&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package com.damsoon;

import com.damsoon.util.ResearchPackage;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;

public class Main {
    public static void main(String[] args) throws Exception {

        // &amp;quot;com.damsoon&amp;quot; 문자열이 들어가게 된다.
        ResearchPackage testResearch = new ResearchPackage(Main.class.getPackageName());

        // &amp;quot;com.damsoon&amp;quot;
        System.out.println(Main.class.getPackageName());

        testResearch.startScan();

        List&amp;lt;Class&amp;lt;?&amp;gt;&amp;gt; clazzList = testResearch.getClazzList();

        System.out.println(&amp;quot;메타데이터 확인 절차 시작&amp;quot;);

        for(int i = 0; i &amp;lt; clazzList.size(); i++) {
            Class&amp;lt;?&amp;gt; tmpClazz = clazzList.get(i);

            String tmpPackageName = tmpClazz.getPackageName();
            String tmpFileName = tmpClazz.getSimpleName();


            System.out.println(tmpPackageName + &amp;quot; -- &amp;quot; + tmpFileName);
        }
    }

}

&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 컴파일 전 삭제된 구조나 파일이 존재한다면, 없애야 하기 때문.
➜  test-1 rm -rf result


# Shell Script 실행
➜  test-1 ./compile-and-execute.sh
컴파일 &amp;amp; 실행 구문 시작
com.damsoon # Main -&amp;gt; ResearchPackage 클래스에 전달될 rootPackage 문자열.
scan start # ResearchPackage.scan() 재귀 시작
scan start # .... 재귀 시작
com.damsoon.util.ResearchPackage # 해당 클래스 파일 찾음 -&amp;gt; 메타데이터 리스트 추가
scan start # ... 재귀 시작
com.damsoon.container.CustomContainer # 해당 클래스 파일 찾음 -&amp;gt; 메타데이터 리스트 추가
com.damsoon.Main # 해당 클래스 파일 찾음 -&amp;gt; 메타데이터 리스트 추가

메타데이터 확인 절차 시작 # ResearchPackage.getClazzList() 이후. - Main 에서 실행
com.damsoon.util -- ResearchPackage
com.damsoon.container -- CustomContainer
com.damsoon -- Main
컴파일 &amp;amp; 실행 구문 종료

# 컴파일 이후의 모습
➜  result tree
.
└── bin
    └── com
        └── damsoon
            ├── Main.class
            ├── container
            │   └── CustomContainer.class
            └── util
                └── ResearchPackage.class

6 directories, 3 files&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;현재는 모든 &lt;code&gt;.class&lt;/code&gt; 파일을 읽어서 배열에 넣는 상황인데,&lt;/p&gt;
&lt;p&gt;곧 만들게 될 애너테이션과 더불어 &lt;code&gt;@MyComponent&lt;/code&gt; 가 붙은 객체들만 가져오게 수정할 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;본격적으로 의존성 해결 전문 클래스를 만들기 전 &amp;quot;요약&amp;quot;&lt;/h2&gt;
&lt;p&gt;커스텀 Spring Container 를 만들기 위해, 수많은 선 지식들을 작성했으며,&lt;/p&gt;
&lt;p&gt;이를 구현하기 위한 요구사항, 그리고 전반적인 로직을 구현했다.&lt;/p&gt;
&lt;p&gt;유일 인스턴스(단일 객체), 의존성 해결, 순환 참조 해결&lt;/p&gt;
&lt;p&gt;이 3 가지를 염두한 컨테이너를 제작하는 중이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나 &lt;strong&gt;&amp;quot;의존성 해결&amp;quot;&lt;/strong&gt; 부분에서 &amp;quot;어떤 방법론&amp;quot; 을 사용할 것인지 결정해야 했다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;모든 객체를 프록시 객체로 만들고 서로 참조하면 된다.&lt;/li&gt;
&lt;li&gt;Spring 도 사용하는 &lt;code&gt;CGLIB&lt;/code&gt; 라이브러리를 사용하면 된다.&lt;/li&gt;
&lt;li&gt;의존성 해결 부분을 나만의 로직으로 직접 해결한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;나는 포텐셜을 높이기 위해 과감히 &amp;quot;3번&amp;quot; 을 선택했다.&lt;/p&gt;
&lt;p&gt;당연히 이 길은 꽃밭은 아니고 가시밭길과 레고지뢰가 가득한 길이었다...&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;먼저 간단히, &lt;code&gt;CustomContainer&lt;/code&gt; 클래스 구현을 통해,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;클래스의 &lt;code&gt;Constructor&lt;/code&gt; 을 보관하는 Map&lt;/li&gt;
&lt;li&gt;클래스의 인스턴스, &lt;code&gt;Object&lt;/code&gt; 를 보관하는 Map&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;을 가지고 있다.&lt;/p&gt;
&lt;p&gt;두 맵의 key 는 &lt;code&gt;패키지명 + 파일&lt;/code&gt; 이 붙어있는 문자열 형태이다.&lt;/p&gt;
&lt;p&gt;(EX - &lt;code&gt;&amp;quot;com.damsoon.component.Component1&amp;quot;&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;추후, 단일 객체의 클래스 메타데이터를 뽑아서 사용하기 위한 형태이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CustomContainer&lt;/code&gt; 는 일단 아주 단순한 형태의 Spring Container 형태가 될 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;ResearchPackage&lt;/code&gt; 클래스는 전달된 부모 패키지 경로부터 시작하여&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.class&lt;/code&gt; 파일들을 읽어 메타데이터로 전환한다.&lt;/p&gt;
&lt;p&gt;우리가 작성하는 코드는 &lt;code&gt;.java&lt;/code&gt; 지만, 실제 작동시 &lt;code&gt;.class&lt;/code&gt; 라는 것을 명심해야 한다.&lt;/p&gt;
&lt;p&gt;파일 탐색은 DFS 재귀 메서드 형식으로 제작되었다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;다만 현재는 &amp;quot;모든&amp;quot; 클래스 파일을 로드하는 중이며&lt;/strong&gt;,&lt;/p&gt;
&lt;p&gt;Annotation 인터페이스도 제작된다면, 애너테이션을 읽고 특정 컴포넌트만 읽게 수정 될 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;ResolveDependency&lt;/code&gt;(의존성 해소) 클래스를 제작하는 데 중요하는 것은&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Class&amp;lt;?&amp;gt;&lt;/code&gt; 배열을 받고 의존성을 해소하는 것 뿐만 아니라, 순환 참조를 예방해야 한다는 것이다.&lt;/p&gt;
&lt;p&gt;순환 참조 이후 생성자에서 서로를 실행하는 것은 &amp;quot;애초에 완벽하게 틀린 Logic&amp;quot; 에 해당한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;문법으로서 틀리진 않지만, 논리적으로 틀리다&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A &amp;lt;--&amp;gt; B 라는 상황은 틀리진 않다.&lt;/p&gt;
&lt;p&gt;그러나, A 와 B. 서로의 컴포넌트가 생성되기 위하여 서로를 필요로 하는 상황은&lt;/p&gt;
&lt;p&gt;논리적으로 절대로 성립될 수 없다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;그러나, A 와 B 서로를 사용한다는 것 자체가 틀리진 않다.&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;중요한 것은 &amp;quot;생성자&amp;quot;(&lt;code&gt;Constructor&lt;/code&gt;) 부분에서 서로를 실행해서는 안된다는 것이다.&lt;/p&gt;
&lt;p&gt;간단하게 설명한다면,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;빛이 있기에 어둠이 존재한다.&lt;/li&gt;
&lt;li&gt;어둠이 있기에 빛이 존재한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;두 개념 중 어떤 것이 맞을까?&lt;/p&gt;
&lt;p&gt;논리적으로 2 가지 모두 성립할 수 없게 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Annotation Interface 선언하기&lt;/h3&gt;
&lt;p&gt;스프링의 완성도가 높고 간편하기 때문에 Annotation 이 &amp;quot;특정한 행동&amp;quot; 을 한다고 착각하기 쉽지만,&lt;/p&gt;
&lt;p&gt;실제로는 이 애너테이션을 읽고 클래스 객체를 load 하는 프로그램이 &amp;quot;특정한 행동을 한다&amp;quot;.&lt;/p&gt;
&lt;p&gt;Annotation 을 통해 총 3 개의 기능을 적용 할 것인데,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;컨테이너에 들어갈 컴포넌트 표시&lt;/li&gt;
&lt;li&gt;순환 참조됨을 표시&lt;/li&gt;
&lt;li&gt;어떤 클래스를 Proxy 로 사용 할 것인지 &amp;quot;객체 메타데이터&amp;quot; 형태로 전달 할 것이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;여기서 &lt;code&gt;1&lt;/code&gt; 번은 &lt;code&gt;MyComponent&lt;/code&gt; 라는 이름을 가지게 되고,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;2&lt;/code&gt; 번은 &lt;code&gt;MyLazy&lt;/code&gt; 라는 이름으로 참조 객체에 선언하게 된다.&lt;/p&gt;
&lt;p&gt;단, 생성자의 &amp;quot;인자&amp;quot; 에 붙일 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;3&lt;/code&gt; 번은 &lt;code&gt;MyProxy&lt;/code&gt; 라는 애너테이션으로 만들 것이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;MyProxy&lt;/code&gt; 는 객체 위에 선언되나,&lt;/p&gt;
&lt;p&gt;타겟 클래스 내부의 &amp;quot;모든 메서드&amp;quot; 에 대해서 적용한다.&lt;/p&gt;
&lt;br/&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;MyComponent&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package com.damsoon.annotation;

import java.lang.annotation.*;

@Documented
// JVM 구동 시 클래스를 읽어 메타데이터로 만들어야 하므로.
@Retention(RetentionPolicy.RUNTIME)
// 어떠한 종류의 클래스에도 적용할 수 있어야 하므로.
@Target(ElementType.TYPE)
public @interface MyComponent {
    // 스프링 컨테이너의 객체 중 &amp;quot;유일 객체&amp;quot; 임을 표식하므로, 따로 값을 넣진 않는다.
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;code&gt;MyLazy&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package com.damsoon.annotation;

import java.lang.annotation.*;

@Documented
// JVM 구동 이후 의존성을 해소하므로.
@Retention(RetentionPolicy.RUNTIME)
// 순환 참조라는 것을 생성자의 인자 단계에서 표식하기 위함
@Target(ElementType.PARAMETER)
public @interface MyLazy {
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;&lt;code&gt;MyProxy&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package com.damsoon.annotation;

import java.lang.annotation.*;

@Documented
// JVM 에서 인스턴스 생성 이후 Proxy 를 적용 해 주어야 하기 때문.
@Retention(RetentionPolicy.RUNTIME)
// 객체를 타겟으로 하되, 해당 객체들의 Method 를 타겟으로 Proxy 를 행할 것이다.
@Target(ElementType.TYPE)
public @interface MyProxy {
    // Proxy 역할을 할 메서드를 가지고 있는 클래스 메타데이터를 가져야 한다.
    Class&amp;lt;?&amp;gt; handler();
    Class&amp;lt;?&amp;gt; targetInterface();
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;&lt;code&gt;MyProxies&lt;/code&gt; : 단일 컴포넌트에 여러 AOP, 즉 Proxy 가 적용 될 경우.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package com.damsoon.annotation;

import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyProxies {
    Class&amp;lt;?&amp;gt;[] proxies();
    Class&amp;lt;?&amp;gt; targetInterface();
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;위의 4 애너테이션은 전부 JVM 런타임 중, 클래스 메타데이터로 치환되어 load 되어야 하기 때문에,&lt;/p&gt;
&lt;p&gt;전부 &lt;code&gt;RetentionPolicy.RUNTIME&lt;/code&gt; 을 가지게 된다. (Retention : &amp;quot;유지&amp;quot; 라는 의미)&lt;/p&gt;
&lt;p&gt;이제 이 애너테이션 메타데이터를 내가 만든 클래스 중 어떤 단계에서 검증할 것인지 정해야 한다.&lt;/p&gt;
&lt;p&gt;그 단계를 바로 &lt;code&gt;ResolveDependency&lt;/code&gt; Class 에서 진행 할 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이 클래스를 제작하기 전에, 먼저 애너테이션에 대한 정보가 올바르게 추출 될 수 있는지&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ResearchPackage&lt;/code&gt; 클래스에서 확인을 해 보자.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Annotation 메타데이터 논리의 검증&lt;/h3&gt;
&lt;p&gt;먼저, &lt;code&gt;ResearchPackage&lt;/code&gt; 는 &lt;code&gt;Main.class&lt;/code&gt; 를 포함하는 패키지에서&lt;/p&gt;
&lt;p&gt;재귀적으로 하위 패키지를 뒤지며 &amp;quot;모든 클래스&amp;quot; 를 가지고 온다.&lt;/p&gt;
&lt;p&gt;즉, 아직 &lt;code&gt;@MyComponent&lt;/code&gt; 와 같은 애너테이션이 붙은 클래스 메타데이터를 가져오는 것이 아니라,&lt;/p&gt;
&lt;p&gt;일단 모든 클래스 메타데이터를 가져오고 있는 상황이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이러한 상황에서 &lt;code&gt;ResearchPackage&lt;/code&gt; 메서드를 통해&lt;/p&gt;
&lt;p&gt;&amp;quot;애너테이션이 없는&amp;quot; 클래스와, &amp;quot;애너테이션이 있는&amp;quot; 클래스의 메타데이터는 어떻게 다른지&lt;/p&gt;
&lt;p&gt;이를 확인 해 보고 넘어가는 것이 매우 중요하다고 판단했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;현재 디렉토리의 현황은 이러하다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  com tree
.
└── damsoon
    ├── Main.java
    ├── annotation # 애너테이션 구현 패키지
    │   ├── MyComponent.java
    │   ├── MyLazy.java
    │   └── MyProxy.java
    │   └── MyProxies.java
    ├── component # 애너테이션 정보를 테스팅 할 컴포넌트들
    │   ├── Component1.java
    │   ├── Component2.java
    │   └── Component3.java
    ├── container
    │   └── CustomContainer.java
    ├── proxy # @MyProxy 를 테스팅 하기 위한 클래스 - 아무 정보 없음
    │   └── ProxyTest.java
    └── util
        ├── ResearchPackage.java
        └── ResolveDependency.java

7 directories, 12 files&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ProxyTest&lt;/code&gt; 는 클래스만 생성 해 놓고, 내부에는 어떠한 장치도 마련하지 않았다. (아직은)&lt;/p&gt;
&lt;p&gt;우선, &lt;code&gt;component&lt;/code&gt; 패키지에 생성 한 테스팅용 컴포넌트 3 개를 예시로 마련했다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// Component1
package com.damsoon.component;

import com.damsoon.annotation.MyComponent;

@MyComponent
public class Component1 {
    Component2 component2;

    // 순환 참조 상태. Component2 에서 @MyLazy 선언을 해 준 상태.
    public Component1 (Component2 component2) {
        this.component2 = component2;
    }
}

// Component2
package com.damsoon.component;

import com.damsoon.annotation.MyComponent;
import com.damsoon.annotation.MyLazy;

@MyComponent
public class Component2 {
    Component1 component1;

    public Component2 (@MyLazy Component1 component1) {
        this.component1 = component1;
    }
}


// Component3
package com.damsoon.component;

import com.damsoon.annotation.MyAutowired;
import com.damsoon.annotation.MyComponent;
import com.damsoon.annotation.MyProxy;
import com.damsoon.proxy.ExecutionTime;

// MyProxy 에 메타데이터로 전달되며,
// 해당 인터페이스에 존재하는 Method 에만 Proxy 를 적용한다.
// 즉, &amp;quot;testProxy()&amp;quot; 메서드만 Proxy 가 적용된다.
interface Component3 {
    public void testProxy();
}

// 여러 애너테이션이 붙어 있는 상황을 연출
@MyComponent
// 예시로 이 객체에 적용될 Proxy 로직과, 타겟 인터페이스를 메타데이터로 정한다.
@MyProxy(handler = ExecutionTime.class, targetInterface = Component3.class)
// implements Component3 무조건 적용.
public class Component3Impl implements Component3 {
    Component4 component4;

    @MyAutowired
    public Component3Impl(Component4 component4) {
        this.component4 = component4;
    }

    public void testProxy() {
        System.out.println(&amp;quot;Method in Component3&amp;quot;);
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;그리고, 모든 클래스 메타데이터를 load 하는 &lt;code&gt;ResearchPackage&lt;/code&gt; 클래스에서&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.class&lt;/code&gt; 파일을 Load 할 때 마다, 해당 클래스가 &amp;quot;어떤 애너테이션을 가지고 있는지&amp;quot;&lt;/p&gt;
&lt;p&gt;확인하는 콘솔 출력 Logic 을 넣어보자.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ResearchPackage&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package com.damsoon.util;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;


public class ResearchPackage {
    // 클래스 메타데이터 배열 저장.
    List&amp;lt;Class&amp;lt;?&amp;gt;&amp;gt; clazzList = new ArrayList&amp;lt;&amp;gt;();

    //...

    private void scan(File file, String packageName) throws ClassNotFoundException, IOException {
        System.out.println(&amp;quot;scan start&amp;quot;);

        // ...

        for (File eachFile : tmpFiles) {

            // 현재 파일은 &amp;quot;디렉토리&amp;quot; 일 때,
            if(eachFile.isDirectory()) {
                scan(eachFile, packageName + &amp;quot;.&amp;quot; + eachFile.getName());
            } else if(eachFile.getName().endsWith(&amp;quot;.class&amp;quot;)) {
                Class&amp;lt;?&amp;gt; clazzData = Class.forName(className);


                //
                // 클래스 메타데이터에서 애너테이션 정보를 꺼내기
                //
                Annotation[] annotations = clazzData.getAnnotations();

                System.out.println(clazzData.getName() + &amp;quot; : &amp;quot; + Arrays.toString(annotations));

                this.clazzList.add(clazzData);
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  test-1 rm -rf result
➜  test-1 ./compile-and-execute.sh
컴파일 &amp;amp; 실행 구문 시작
...
com.damsoon.proxy.ProxyTest : [] # 이 클래스는 애너테이션이 붙어있지 않음.
...
com.damsoon.util.ResolveDependency : [] # 마찬가지
com.damsoon.util.ResearchPackage : [] # 애너테이션이 없다면 &amp;quot;빈 배열&amp;quot; 이다. (null 아님)
...
# &amp;quot;@com.damsoon.annotation.MyComponent()&amp;quot; 로 출력된다. - 1 개
com.damsoon.component.Component2 : [@com.damsoon.annotation.MyComponent()]
# &amp;quot;객체&amp;quot; 에 적용된 커스텀 애너테이션이 2 개이다. - 2 개
com.damsoon.component.Component3 : [@com.damsoon.annotation.MyComponent(), @com.damsoon.annotation.MyProxy(doProxy=com.damsoon.proxy.ProxyTest.class)]

# 객체에 적용된 커스텀 애너테이션이 1 개 이다. - 1 개
com.damsoon.component.Component1 : [@com.damsoon.annotation.MyComponent()]
...

## 여기서부턴 애너테이션을 구성하는 &amp;quot;기본 내장 애너테이션&amp;quot; 을 추출한다.
com.damsoon.annotation.MyLazy : [@java.lang.annotation.Documented(), @java.lang.annotation.Retention(RUNTIME), @java.lang.annotation.Target({PARAMETER})]
com.damsoon.annotation.MyComponent : [@java.lang.annotation.Documented(), @java.lang.annotation.Retention(RUNTIME), @java.lang.annotation.Target({TYPE})]
com.damsoon.annotation.MyProxy : [@java.lang.annotation.Documented(), @java.lang.annotation.Retention(RUNTIME), @java.lang.annotation.Target({TYPE})]
## 애너테이션 끝.
...

## 밑의 2 개의 클래스도 애너테이션이 붙지 않음.
com.damsoon.container.CustomContainer : []
com.damsoon.Main : []

메타데이터 확인 절차 시작 # Main 에서 실행 및 검증.
com.damsoon.proxy -- ExecutionTime
com.damsoon.util -- ResolveDependency
com.damsoon.util -- ResearchPackage
com.damsoon.component -- Component2
com.damsoon.component -- Component3
com.damsoon.component -- Component1
com.damsoon.annotation -- MyLazy
com.damsoon.annotation -- MyComponent
com.damsoon.annotation -- MyProxy
com.damsoon.annotation -- MyProxies
com.damsoon.container -- CustomContainer
com.damsoon -- Main
컴파일 &amp;amp; 실행 구문 종료&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;파일의 재구성과 신기능 도입에 대한 검증이 길다고 생각할 수 있겠지만,&lt;/p&gt;
&lt;p&gt;&amp;quot;애너테이션 추출 및 분석&amp;quot; 단계를 넘어가서 곧바로 &amp;quot;사용&amp;quot; 단계에 진입하면,&lt;/p&gt;
&lt;p&gt;추후 일어날 수 있는 에러를 감당하지 못할 수 있다. (Technical Dept.)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;확실히 애너테이션을 잘 적용하고, 추출도 하고 있는 것을 볼 수 있다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;현재는 &amp;quot;모든 클래스 메타데이터&amp;quot; 추출 과정을 &lt;code&gt;ResearchPackage&lt;/code&gt; 클래스에서 진행하고 있는데,&lt;/p&gt;
&lt;p&gt;우리는 클래스 중 &lt;code&gt;@&lt;/code&gt;(Annotation) 이 적용된 클래스를 따로 추출해야 한다.&lt;/p&gt;
&lt;p&gt;이에 대한 기능을 &lt;code&gt;ResolveDependency&lt;/code&gt; 클래스에서 진행 할 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;ResolveDependency Class&lt;/h2&gt;
&lt;p&gt;위에 작성한 &lt;code&gt;CustomContainer&lt;/code&gt;, &lt;code&gt;ResearchPackage&lt;/code&gt; 클래스는&lt;/p&gt;
&lt;p&gt;각각&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CustomContainer&lt;/code&gt; : 클래스 자체의 &amp;quot;생성자&amp;quot;, 그리고 &amp;quot;메타데이터&amp;quot; 들을 보관한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ResearchPackage&lt;/code&gt; : 직접 루트 패키지 경로에서부터 탐색하여 모든 클래스 메타데이터를 추출한다. &lt;br/&gt; 이후 &lt;code&gt;CustomContainer&lt;/code&gt; 에 전달할 예정이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;그렇다면, &lt;code&gt;ResolveDependency&lt;/code&gt; 클래스는 어떤 역할을 하게 될까?&lt;/p&gt;
&lt;p&gt;물론, &lt;code&gt;Main&lt;/code&gt; 에서 조작하게 되겠지만,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;ResearchPackage&lt;/code&gt; 에서 추출된 &lt;code&gt;Class&amp;lt;?&amp;gt;&lt;/code&gt; 메타데이터 리스트를 대상으로 애너테이션을 분석한다.&lt;/li&gt;
&lt;li&gt;추후 작성될 &lt;code&gt;MyComponent&lt;/code&gt;, &lt;code&gt;MyLazy&lt;/code&gt;, &lt;code&gt;MyProxy&lt;/code&gt; 에 대해 &lt;br/&gt; 어떤 로직을 추가할 지 내부에 구현한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CustomContainer&lt;/code&gt; 에 &lt;code&gt;Constructor&lt;/code&gt;, &lt;code&gt;Object&lt;/code&gt;(instance) 를 등록한다.&lt;/li&gt;
&lt;li&gt;위의 과정에서 의존성을 해결한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;p&gt;마지막에 &amp;quot;의존성을 해결한다&amp;quot; 고 뭉퉁그려서 이야기했지만,&lt;/p&gt;
&lt;p&gt;위에서 미리 언질했기 때문인데,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;모든 생성자에 &amp;quot;일단&amp;quot; &lt;code&gt;null&lt;/code&gt; 인자를 주입하여 인스턴스부터 생성 해 놓는다.&lt;/li&gt;
&lt;li&gt;그 다음 인스턴스가 요구했던 모든 인자(값)를 다시 주입하여 완성한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 방식은 &amp;quot;생성자 내부&amp;quot; 에서 의존하는 객체의 컴포넌트 메서드를 호출하지만 않으면&lt;/p&gt;
&lt;p&gt;순환 참조를 신경 쓸 필요가 없다.&lt;/p&gt;
&lt;p&gt;짧게 말해 순환 참조에 대한 에러 바운더리가 굉장히 넓어진다는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;ResolveDependency 의 막중한 책임&lt;/h3&gt;
&lt;p&gt;&amp;quot;의존성 해결&amp;quot; 이라는 부분은 단순히 하나의 클래스로 만들 만한 쉬운 문제는 아니다.&lt;/p&gt;
&lt;p&gt;여기서 &amp;quot;알고리즘 역량&amp;quot; 이 굉장히 중요한 이유가 드러나는데,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;책임의 분리&lt;/li&gt;
&lt;li&gt;재귀&lt;/li&gt;
&lt;li&gt;주어진 데이터를 통한 프로그램의 자동화&lt;/li&gt;
&lt;li&gt;등등..&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;복잡한 개념이 한데 뭉쳐 &lt;code&gt;ResolveDependency&lt;/code&gt; 를 구성하기 때문이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;또한 메타데이터 프레임워크를 만드는 것은 이번이 처음이기에 더 어려운 것도 있다.&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;객체가 원하는 의존성을 어떻게 주입 할 것인가?&lt;/h3&gt;
&lt;p&gt;나는 Spring 과는 정 반대의 과정을 선택하기로 결정했었다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Spring : (선 의존성 완성 후 컨테이너)&lt;/li&gt;
&lt;li&gt;나 : (선 컨테이너 완성 후 의존성 주입)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Spring 의 방식&lt;/h4&gt;
&lt;p&gt;스프링은 사용자의 코드에 최대한 제약을 두지 않고, 편리하게 사용하도록&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CGLIB&lt;/code&gt; 라는 외부 라이브러리를 사용했다.&lt;/p&gt;
&lt;p&gt;이 라이브러리는 사용자가 선언한 Annotation 에 따라,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.class&lt;/code&gt; 파일로 컴파일 하고 나서, 필요한 로직을 ByteCode 로 수정, 혹은 삽입한다.&lt;/p&gt;
&lt;p&gt;이 덕분에 Java 에서 요구하는 언어적 제약에서 벗어나 매우 유연한 프레임워크가 되었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;또한, 순환 참조를 허용하는 방식은 &lt;code&gt;@Lazy&lt;/code&gt; 로 허용되기에,&lt;/p&gt;
&lt;p&gt;이를 사용하지 않고 순환 참조를 선언한다면, 에러를 뱉으며 그대로 프로그램이 종료된다.&lt;/p&gt;
&lt;p&gt;이는 어떻게 보면 개발자에게 올바른 코드의 방향성을 제공하기도 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;스프링이 순환 참조를 허용하지 못하는 이유에는 또 다른 이유도 있는데,&lt;/p&gt;
&lt;p&gt;스프링은 한 컴포넌트의 &amp;quot;생성자 인자&amp;quot; 를 전부 읽는데, 이 의존성 해결 방식이&lt;/p&gt;
&lt;p&gt;완전한 DFS 이며, 한 의존성을 완성하기 위해 수많은 재귀 속에서 의존성이 완성되지 못했다면,&lt;/p&gt;
&lt;p&gt;그 하나의 의존성으로 인해 프로그램은 실행되지 않는다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;EX - 간단한 코드를 보기&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// ...

@Component
public class Component1 {
    private Component2 component2;
    private Component3 component3;

    public Component1(Component2 component2, Component3 component3) {
        this.component2 = component2;
        this.component3 = component3;
    }
    // ...
}

@Component
public class Component2 {}

@Component
public class Component3 {
    private Component4 component4;

    public Component3 (Component4 component4) {
        this.component4;
    }
    // ...
}

@Component
public class Component4 {
    private Component1 component1;
    public Component4 (Component1 component1) {
        this.component1 = component1;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;스프링에서 위와 같은 컴포넌트가 존재한다고 가정한다.&lt;/p&gt;
&lt;p&gt;Spring 이 &lt;code&gt;Component1&lt;/code&gt; 메타데이터를 읽으면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Componenent2&lt;/code&gt;, &lt;code&gt;Component3&lt;/code&gt; 의존성을 원한다고 인식 할 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그런데, 여기서 문제가 생긴다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Component2&lt;/code&gt; 에 대한 인스턴스를 생성했는데,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Component3&lt;/code&gt; 에 대한 의존성을 완성하기 위해,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Component3&lt;/code&gt; 메타데이터를 읽고, 생성자를 실행한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Component3&lt;/code&gt; 를 완성하기 위해 &lt;code&gt;Component4&lt;/code&gt; 메타데이터를 읽고, 생성자를 실행한다.&lt;/p&gt;
&lt;p&gt;그런데, &lt;code&gt;Component4&lt;/code&gt; 는 &lt;code&gt;Component1&lt;/code&gt; 을 필요로 한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;결국 도돌이표(순환참조)&amp;quot;&lt;/strong&gt; 가 되는 것이다.&lt;/p&gt;
&lt;p&gt;위의 과정처럼 DFS 방식으로 끝까지 메서드가 의존성을 완수하기 위해 추적했지만,&lt;/p&gt;
&lt;p&gt;결국 도돌이표이자, &amp;quot;완성되지 못하는&amp;quot; &lt;code&gt;Component1&lt;/code&gt; 을 보니,&lt;/p&gt;
&lt;p&gt;Spring 은 &lt;code&gt;Component1&lt;/code&gt; 을 완성하지 못하고 프로그램을 종료한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;즉, Spring 이 의존성을 해결하는 방식은,&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;하나의 컴포넌트 메타데이터를 발견했을 때,&lt;/p&gt;
&lt;p&gt;이를 &amp;quot;그 자리에서 모두 해결한다&amp;quot; 는 방식인 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;내가 만든 의존성 해결의 방식&lt;/h4&gt;
&lt;p&gt;나는 그러한 생각이 들었다. &lt;strong&gt;&amp;quot;없는 컴포넌트를 개발자가 부르지는 않는다.&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;그러니까, 어쨌든 순환참조가 발생할 구조더라도, 코드 컴파일러 수준에서&lt;/p&gt;
&lt;p&gt;에러가 날 수준의 로직이 아니라면, 순환 참조는 스파게티 코드가 되더라도 문제가 되지는 않는다는&lt;/p&gt;
&lt;p&gt;그런 생각이 들었다. (물론, 이러한 프로젝트 Architecture 가 너무 복잡해지지만.)&lt;/p&gt;
&lt;p&gt;따라서, 나의 방식은 순환참조를 허용하되, 이에 대한 경고는 하고 넘어가는 방식을 만들기로 결정했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;위에서 그런 말을 간단히 했었다.&lt;/p&gt;
&lt;p&gt;&amp;quot;빛이 있기에 어둠이 있는가? 어둠이 있기에 빛이 있는가?&amp;quot;&lt;/p&gt;
&lt;p&gt;이는 내가 생각한 순환참조를 의미할 수 있는 가장 알맞는 말이라고 생각한다.&lt;/p&gt;
&lt;p&gt;나는 &amp;quot;꼼수&amp;quot; 를 썼다.&lt;/p&gt;
&lt;p&gt;결국에 둘 다 존재해야 하는데, 서로가 서로의 존재가 없어서 생성 될 수 없다면,&lt;/p&gt;
&lt;p&gt;밑의 방식 중 하나를 선택하면 된다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;quot;빛&amp;quot; 에게 &amp;quot;가짜 어둠&amp;quot; 을 선사한다.&lt;/li&gt;
&lt;li&gt;&amp;quot;어둠&amp;quot; 에게 &amp;quot;가짜 빛&amp;quot; 을 선사한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;둘 중 하나만 이루어져도 순환 참조는 해결된다.&lt;/p&gt;
&lt;p&gt;(위의 방식은 &lt;code&gt;Spring&lt;/code&gt; 에서 &lt;code&gt;Lazy&lt;/code&gt; 를 적용하는 방식이기도 하다.)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&amp;quot;빛&amp;quot; 그리고 &amp;quot;어둠&amp;quot; 에 관한 상황은 프로젝트를 구성하는 데에 자주 발생할 수 있는 문제이다.&lt;/p&gt;
&lt;p&gt;그래서, 나는 &lt;strong&gt;&amp;quot;모든 존재가 탄생하는 데에 필요한 재료를 가짜로 준다.&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;그렇다면, 나의 프로젝트를 구성하는 데 필요한 모든 &amp;quot;존재&amp;quot; 들은,&lt;/p&gt;
&lt;p&gt;자신들이 &amp;quot;완성되었다.&amp;quot; 라고 &amp;quot;속는다.&amp;quot;&lt;/p&gt;
&lt;p&gt;(여기서 말하는 재료는 &lt;code&gt;null&lt;/code&gt; 값을 의미한다.)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;모든 컴포넌트들은 속았지만, 결국 모두 완성되었다.&lt;/p&gt;
&lt;p&gt;따라서, 컴포넌트들을 순회하며, 필요한 의존성 데이터를 읽고,&lt;/p&gt;
&lt;p&gt;컴포넌트에 의존성들을 모두 꽂아주면, &amp;quot;완성된 객체&amp;quot; 가 된다.&lt;/p&gt;
&lt;p&gt;의존성을 해결하기 위해 필요한 나의 창의성이었다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;나의 방식은 창의적이지만, 그만큼 위험하다.&lt;/h4&gt;
&lt;p&gt;의존적인 컴포넌트가 &amp;quot;완성&amp;quot; 되어야지만 채울 수 있는 방식은&lt;/p&gt;
&lt;p&gt;결국 Spring 이나 나의 방식이나 동일하다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;가장 중요한 것은, 개발자가 자신이 &amp;quot;스파게티 코드&amp;quot; 를 작성했는지 꼭 알아야 한다는 것이다.&lt;/p&gt;
&lt;p&gt;그것을 알려주지 못한다면, 실제로 나의 프로그램을 사용한 모든 사용자들은,&lt;/p&gt;
&lt;p&gt;필연적으로 스파게티 코드를 짜게 된다. 이것이 순환참조 구조인지, 등등..&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;결국 프레임워크라는 프로그램의 &amp;quot;본질&amp;quot; 은, 개발자의 개발을 편리하게 만들어 줄 뿐만 아니라,&lt;/p&gt;
&lt;p&gt;&amp;quot;개발 방향의 의도&amp;quot; 까지도 고려해야 한다는 것이다.&lt;/p&gt;
&lt;p&gt;순환 참조가 발생한다는 것은 컴포넌트 의존성 아키텍쳐를 바꿔야 한다는 의미이다.&lt;/p&gt;
&lt;p&gt;개발자의 실력 이슈, 혹은 어쩔 수 없는 상황에서 사용 할 때, 어떤 컴포넌트들이 결국&lt;/p&gt;
&lt;p&gt;정상적으로 의존성을 해결하지 않았다는 것을 알려주어야 한다는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그리고, 모드를 선택하여 개발자가 스파게티 코드를 허용할지, 금지할지 결정해야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;프록시 적용의 문제점&lt;/h3&gt;
&lt;p&gt;Java 의 프록시 적용에 대해서 알게 된다면,&lt;/p&gt;
&lt;p&gt;왜 Spring 이 &lt;code&gt;CGLIB&lt;/code&gt; 라는 라이브러리를 이용하는지 알게 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;물론, Spring 을 사용 할 때, &lt;code&gt;interface&lt;/code&gt; 가 매우 권장된다.&lt;/p&gt;
&lt;p&gt;그러한 구조를 가지고 있기도 하고,&lt;/p&gt;
&lt;p&gt;심지어는 단순히 &amp;quot;컴포넌트 클래스&amp;quot; 자체에 &lt;code&gt;@Proxy(...)&lt;/code&gt; 와 같은 방식으로&lt;/p&gt;
&lt;p&gt;간단히 클래스 인스턴스에 프록시를 적용할 수 있다는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;원래 Java 의 프록시는 Spring 처럼 쉽게 적용 할 수 없다.&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;프록시를 적용하기 위해서는,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;InvocationHandler&lt;/code&gt; 인터페이스를 적용한 AOP 클래스(유일 객체 아님)&lt;/p&gt;
&lt;p&gt;그리고 기능을 적용할 컴포넌트 클래스 객체를 &amp;quot;위의 AOP 클래스&amp;quot; 의 생성자로 등록.(주소 등록)&lt;/p&gt;
&lt;p&gt;이 컴포넌트 클래스의 &amp;quot;어떤 메서드&amp;quot; 를 프록시 적용 할 것인지 인터페이스 메서드로 선언&lt;/p&gt;
&lt;p&gt;그리고 &lt;code&gt;Proxy&lt;/code&gt; 유틸 객체 내장 메서드에 인자로,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;컴포넌트 메타데이터를 가져온 스레드의 &amp;quot;클래스로더&amp;quot;&lt;/li&gt;
&lt;li&gt;컴포넌트 인스턴스에 적용될 메서드들을 담은 &amp;quot;인터페이스들&amp;quot;(&lt;code&gt;Class&amp;lt;?&amp;gt;[]&lt;/code&gt; 타입)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;InvocationHandler&lt;/code&gt; 를 적용한 AOP 클래스&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;가 들어간다.&lt;/p&gt;
&lt;p&gt;복잡하고 불편하지만, 기능의 분리는 준수한 메서드이다.&lt;/p&gt;
&lt;p&gt;그러나, Spring 을 생각해보자.&lt;/p&gt;
&lt;p&gt;단순히 Component Class 위에다, 애너테이션 &lt;code&gt;@&lt;/code&gt; 하나로 AOP 기능이나, 프록시를 적용할 수 있다.&lt;/p&gt;
&lt;p&gt;왜 그럴까?&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CGLIB&lt;/code&gt; 의 바이트코드 수정 기술 때문이다.&lt;/p&gt;
&lt;p&gt;Spring 에서는 &lt;code&gt;@Aspect&lt;/code&gt; 라는 애너테이션과 함께 작성된 &lt;code&gt;Bean&lt;/code&gt; 은,&lt;/p&gt;
&lt;p&gt;특정 문법을 통해 &amp;quot;지정한 패키지를 포함한 모든 컴포넌트&amp;quot; 에 지정된 AOP 를 실행한다.&lt;/p&gt;
&lt;p&gt;그리고 단순히 컴포넌트만 존재하던 &lt;code&gt;.java&lt;/code&gt; 파일이 있고, 이것이 AOP 에 적용되었다 가정하면,&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;원래 파일 :&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Component
public class Component1 {

    @ProxyTarget // 이러한 AOP 클래스(&amp;quot;ProxyTarget&amp;quot;)가 있다고 &amp;quot;가정&amp;quot; 해보자.
    public void test_1() {
        ...
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이러한 아주 간단한 컴포넌트가 있는데, AOP Bean 의 타겟에 걸린 것이다.&lt;/p&gt;
&lt;p&gt;그렇다면, 컴파일 이후, JVM 에서 메모리에 load 까지 된다면, 이러한 형태가 된다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class Component1$$CGLIB.....$$31412 extends Component1 {
    .....

    @Override
    public void test_1() {
        // AOP 로직
        super.test_1();
        // AOP 로직
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 정보는 &lt;code&gt;xxx.xxx.Component1&lt;/code&gt; 의 정보로 &amp;quot;의존성 주입&amp;quot; 한다.&lt;/p&gt;
&lt;p&gt;이는 &lt;code&gt;CGLIB&lt;/code&gt; 로 가능한 작업이다. (기본 기능으로는 어림도 없고, 바이트코드를 만져야 가능함.)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;언뜻 보기에, 결과물이 굉장히 복잡해 보이고, 왜 저렇게까지 해야 하는지 모를 것이다.&lt;/p&gt;
&lt;p&gt;하지만, 이는 Spring 개발자들이 최대한 Proxy, AOP 기능을 &amp;quot;쉽게&amp;quot; 넣기 위해 노력한 결과물이다.&lt;/p&gt;
&lt;p&gt;생각해보자. 만약 스프링이 이러한 기능을 만들어 두지 않았다면,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;InvocationHandler&lt;/code&gt; 를 구현한 빈, 혹은 날 것의 클래스를 &amp;quot;직접 구현&amp;quot;&lt;/li&gt;
&lt;li&gt;적용될 메서드의 &amp;quot;이름&amp;quot; 을 전부 AOP 클래스에 작성 및 Logic 구현&lt;/li&gt;
&lt;li&gt;적용될 컴포넌트의 메서드들을 정하기 위해, &amp;quot;타겟 인터페이스&amp;quot; 작성&lt;/li&gt;
&lt;li&gt;프록시 객체의 생성을 위해 위에서 언급한 3 가지의 인자를 전부 가져오기.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이걸 수작업으로 진행해야 한다. 이 과정도 Java 내장 Proxy 기능 덕분에 &amp;quot;축소&amp;quot; 된 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;Spring 이 &lt;code&gt;@Aspect&lt;/code&gt; 라는 기능을 만들어 특정 패키지, 혹은 단일 클래스에 적용할 수 있도록&lt;/p&gt;
&lt;p&gt;문자열로 정하도록 만들어 두었지만, 편리함이 느껴지며 동시에 괴기함까지 느껴진다.&lt;/p&gt;
&lt;p&gt;편의성을 위해 우리가 선언하는 과정과, 프록시가 실제로 적용되는 과정에서 괴리감이 발생하기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나 이는 사용자의 편의성을 위함이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;스프링은 왜 프록시 적용 결과를 괴기하게 만들었을까?&lt;/h4&gt;
&lt;p&gt;Spring 에서 프록시가 적용된 컴포넌트 코드와, 컴파일 &amp;quot;직후&amp;quot; 의 컴포넌트 코드는&lt;/p&gt;
&lt;p&gt;사실 거의 동일하다.(애너테이션이 유지됨 - &lt;code&gt;RetentionPolicy.RUNTIME&lt;/code&gt; 덕분)&lt;/p&gt;
&lt;p&gt;그러나, 나는 위의 예시에서 &amp;quot;JVM 런타임 이후&amp;quot; 까지 컴파일된 &lt;code&gt;.class&lt;/code&gt; 의 예시를 보여주었다.&lt;/p&gt;
&lt;p&gt;개인적인 의견으로 처음 보았을 때, 억지스럽게 적용한다고 판단했다.&lt;/p&gt;
&lt;p&gt;그리고 Java 의 &lt;code&gt;Proxy&lt;/code&gt; 에 대해서 배우고 이해했을 때, Spring 의 의도를 정확히 파악했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;위에서 아주 짧게 언급 한 적이 있는데,&lt;/p&gt;
&lt;p&gt;프록시가 적용될 &lt;strong&gt;Target 인스턴스&lt;/strong&gt; 와,&lt;/p&gt;
&lt;p&gt;프록시가 적용&amp;quot;된&amp;quot; &lt;strong&gt;Result 인스턴스&lt;/strong&gt; 는,&lt;/p&gt;
&lt;p&gt;&amp;quot;단 하나&amp;quot; 의 공통점을 제외하고 모든 점에서 차이점이 존재한다.&lt;/p&gt;
&lt;p&gt;이를 나열 해 보겠다. (프로젝트를 하면서 알게 된 점들)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;공통점&lt;ul&gt;
&lt;li&gt;적용 인터페이스가 동일하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;차이점&lt;ul&gt;
&lt;li&gt;원본 인스턴스에 선언된 &amp;quot;필드 변수들&amp;quot; 이 없다.&lt;/li&gt;
&lt;li&gt;인터페이스에 포함되지 않은 메서드는 프록시 객체에 존재하지 않는다.&lt;/li&gt;
&lt;li&gt;인스턴스 원본 주소와 &amp;quot;완전히&amp;quot; 다르다. (그냥 다른 객체이다)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;위와 같은 차이점이 있지만, &amp;quot;적용 interface&amp;quot; 가 동일하다는 점이 있다.&lt;/p&gt;
&lt;p&gt;이 덕분에 나는 &amp;quot;순수 Java 와 내장 LIB 사용&amp;quot; 이라는 제약에도 불구하고&lt;/p&gt;
&lt;p&gt;프록시를 적용 할 수 있었다. (이후에 API 사용법을 보게 됩니다. 지금 작성하면 어지러울 수도..)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, Spring 은 다른 방식을 채택했다.&lt;/p&gt;
&lt;p&gt;일단 Spring 에서는 딱히 인터페이스를 작성하지 않아도 적용이 된다.&lt;/p&gt;
&lt;p&gt;이게 어떻게 가능했을까??&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;주소가 아무리 다르고, 변수가 없고... 그렇더라도,&lt;/p&gt;
&lt;p&gt;원본 객체와 프록시 객체가 동일할 수 있는 조건은, 바로 &lt;strong&gt;&amp;quot;Type&amp;quot;&lt;/strong&gt; 이다.&lt;/p&gt;
&lt;p&gt;잠시 생각 해 보자. &amp;quot;다른 주소를 가지지만 Type&amp;quot; 을 동일시 할 수 있는 방법은?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;동일한 Interface 를 구현한다. --&amp;gt; 이건 순수 Java 방식&lt;/li&gt;
&lt;li&gt;타겟 Instance 를 &amp;quot;계승&amp;quot;(extends) 한 객체 --&amp;gt; Spring 의 CGLIB 방식&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;바로 2 번 방식을 사용하여 Type 을 맞춘 것이다.&lt;/p&gt;
&lt;p&gt;그게 비록 순수 Java 스펙에 맞춰지지 않고, 바이트코드를 RUNTIME 중에 변경하더라도,&lt;/p&gt;
&lt;p&gt;프레임워크 사용자가 거의 유일하게 객체 단에서 애너테이션을 선언하는 것 만으로도,&lt;/p&gt;
&lt;p&gt;프록시를 적용할 수 있는 방식이기 때문이다.&lt;/p&gt;
&lt;p&gt;그러나 여기서 순수 객체 이름이 괴기한 이유는, 내부에서 &amp;quot;고유&amp;quot;(Unique) 한 이름을 가져야 하기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;p&gt;너무 많은 설명과 비유로 혼란을 조장한 것 처럼 보이지만,&lt;/p&gt;
&lt;p&gt;앞으로 나올 코드를 처음 민낯으로 보는 것이 더 혼란을 조장할 것 같아서,&lt;/p&gt;
&lt;p&gt;글 자체로서 설명하는 것을 양해 부탁 드립니다.&lt;/p&gt;
&lt;hr&gt;
&lt;br/&gt;

&lt;h3&gt;프로젝트에 적용할 Custom Annotation 들&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Documented
// JVM 구동 시 클래스를 읽어 메타데이터로 만들어야 하므로.
@Retention(RetentionPolicy.RUNTIME)
// 어떠한 종류의 클래스에도 적용할 수 있어야 하므로.
@Target(ElementType.TYPE)
public @interface MyComponent {
    // 스프링 컨테이너의 객체 중 &amp;quot;유일 객체&amp;quot; 임을 표식하므로, 따로 값을 넣진 않는다.
}

// ---

@Documented
@Retention(RetentionPolicy.RUNTIME)
// 컴포넌트의 필드 변수, 혹은 의존성 지정 생성자에 선언한다.
@Target({ElementType.FIELD, ElementType.CONSTRUCTOR})
public @interface MyAutowired {
}

// ---

@Documented
// JVM 구동 이후 의존성을 해소하므로.
@Retention(RetentionPolicy.RUNTIME)
// 순환 참조라는 것을 생성자의 인자 단계에서 표식하기 위함
@Target(ElementType.PARAMETER)
public @interface MyLazy {
}

// ---

@Documented
// JVM 에서 인스턴스 생성 이후 Proxy 를 적용 해 주어야 하기 때문.
@Retention(RetentionPolicy.RUNTIME)
// 객체를 타겟으로 하되, 해당 객체들의 Method 를 타겟으로 Proxy 를 행할 것이다.
@Target(ElementType.TYPE)
public @interface MyProxy {
    // InvocationHandler 가 구현된
    Class&amp;lt;? extends InvocationHandler&amp;gt; proxy();
    Class&amp;lt;?&amp;gt; targetInterface();
}

// ---

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyProxies {
    Class&amp;lt;? extends InvocationHandler&amp;gt;[] proxies();
    Class&amp;lt;?&amp;gt; targetInterface();
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Custom Annotation 적용 예시 컴포넌트들&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;
@MyComponent
public class Component1 {
    Component2 component2;

    // 순환 참조 상태. 그러나 Component2 에서 @MyLazy 선언을 해 준 상태.
    @MyAutowired
    public Component1 (Component2 component2) {
        this.component2 = component2;
    }
}

// ---

@MyComponent
public class Component2 {
    Component1 component1;
    // 필드 변수로 &amp;quot;MyAutowired&amp;quot; 한 상태.
    @MyAutowired
    Component3 component3;

    @MyAutowired
    // @MyLazy 선언은 완벽한 순환참조 상태에서,
    // 개발자 스스로가 이를 인지하고 있음을 나타내는 신호이다.
    public Component2 (@MyLazy Component1 component1) {
        this.component1 = component1;
    }
}

// ---

// Component3Impl 컴포넌트에서 AOP, proxy 가 적용되야 할 &amp;quot;메서드&amp;quot; 를 선언한다.
interface Component3 {
    public void testProxy();
}

@MyComponent
// proxy
// --&amp;gt; 이 컴포넌트 메서드들에 적용하려고 하는 기능.
// --&amp;gt; InvocationHandler 를 implements 한 클래스이다.
@MyProxy(proxy = ExecutionTime.class, targetInterface = Component3.class)
// implements Component3 는 &amp;quot;필수&amp;quot; 이다. targetInterface 선언 일치해야 한다.
public class Component3Impl implements Component3 {
    Component4 component4;

    @MyAutowired
    public Component3Impl(Component4 component4) {
        this.component4 = component4;
    }

    public void testProxy() {
        System.out.println(&amp;quot;Method in Component3&amp;quot;);
    }
}

// ---

@MyComponent
public class Component4 {
    Component3 component3;

    @MyAutowired
    public Component4 (@MyLazy Component3 component3) {
        this.component3 = component3;
    }
}

// ---

// Component5, 6 서로는 &amp;quot;완벽한 순환참조&amp;quot; 형태를 이루고 있다.
// --&amp;gt; @MyLazy 를 선언하지도 않았기 때문이다.

@MyComponent
public class Component5 {
    Component6 component6;

    @MyAutowired
    public Component5(Component6 component6) {
        this.component6 = component6;
    }
}

// ---

@MyComponent
public class Component6 {
    Component5 component5;
    public Component6(Component5 component5) {
        this.component5 = component5;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 애너테이션 모두 순수 Java 로 만들어졌다.&lt;/p&gt;
&lt;p&gt;갑작스럽게 애너테이션을 보여주는 이유는, &lt;code&gt;ResolveDependency&lt;/code&gt; 클래스가&lt;/p&gt;
&lt;p&gt;애너테이션을 &amp;quot;감지&amp;quot; 하고 &amp;quot;재구성&amp;quot; 하기 때문이다.&lt;/p&gt;
&lt;p&gt;클래스 메타데이터는 &amp;quot;&lt;code&gt;Annotation&lt;/code&gt;&amp;quot; 메타데이터를 포함한다.&lt;/p&gt;
&lt;p&gt;즉, 위의 모든 annotation 들은, 이런 의미이다.&lt;/p&gt;
&lt;p&gt;&amp;quot;이 컴포넌트는, 이러한 성질을 가지고, 적절한 변환을 해 주어야 한다.&amp;quot;&lt;/p&gt;
&lt;p&gt;표식으로서 사용된다. 그리고 이는 Spring 또한 마찬가지이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@MyComponent&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;만들어질 프로그램에서 사용될 컴포넌트에 선언한다.&lt;/li&gt;
&lt;li&gt;지정된 패키지 스캔 위치에서 &amp;quot;모든 클래스&amp;quot; 를 읽는데, 여기서 한번 필터링한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@MyAutowired&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;컴포넌트 내부의 필드 변수들, 혹은 지정 생성자에 선언한다.&lt;/li&gt;
&lt;li&gt;클래스 자체의 생성자는 여러 개 일 수도 있다. or Default.&lt;/li&gt;
&lt;li&gt;사용자는 여러 생성자를 선언하고 다른 의존성이 필요 할 때 다른 생성자에 기입할 수 있다.&lt;/li&gt;
&lt;li&gt;또한 필드 변수에 단일로 붙여 의존성을 넣을 수 있다.&lt;/li&gt;
&lt;li&gt;여러 필드에 선언할 수 있으나, 여러 생성자에 선언할 수 없게 만들어 놨다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@MyLazy&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;컴포넌트의 생성자 인자, 혹은 필드 변수에 선언된다.&lt;/li&gt;
&lt;li&gt;개발자가 스스로 선언한 구조가 &amp;quot;순환참조&amp;quot; 임을 자각하고 있다는 신호이기도 하다.&lt;/li&gt;
&lt;li&gt;Lazy 의존성을 해소하는 로직을 따로 구성했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@MyProxy&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;해당 컴포넌트에 Proxy 기능을 적용하기 위한 애너테이션이다.&lt;/li&gt;
&lt;li&gt;인자로 &lt;code&gt;InvocationHandler&lt;/code&gt; 를 구현한 AOP 로직 클래스,&lt;/li&gt;
&lt;li&gt;그리고 이 컴포넌트에서 &amp;quot;어떤 메서드&amp;quot; 에 프록시를 적용할 것인지 지정한다.(interface)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@MyProxies&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;해당 컴포넌트에 다중 Proxy 기능을 적용하게 되면 사용하는 애너테이션이다.&lt;/li&gt;
&lt;li&gt;인자로 &lt;code&gt;InvocationHandler&lt;/code&gt; 를 구현한 클래스 배열(&lt;code&gt;Class&amp;lt;?&amp;gt;[]&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;그리고 위와 동일하게 interface 로 지정한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;위의 애너테이션과 클래스 자체의 메타데이터를 분석하여,&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;싱글톤 인스턴스 컨테이너를 완성하는 것&lt;/strong&gt;이 목표라고 할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;생성자 (Constructor) 와 필드 변수&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class ResolveDependency {
    // String : 인스턴스들이 기다리는 클래스 정보의 문자열
    // WaitingField : 인스턴스-필드 쌍 배열을 의미하며, 대기중인 리스트를 의미한다.
    Map&amp;lt;String, List&amp;lt;WaitingField&amp;gt;&amp;gt; dependencyMap;

    // String : 인스턴스들이 기다리는 클래스 정보의 문자열
    // WaitingField : 위와 같으나, &amp;quot;@MyLazy&amp;quot; 선언으로 의존성 해결을 한 경우에 한함.
    Map&amp;lt;String, List&amp;lt;WaitingField&amp;gt;&amp;gt; lazyMap;

    // 의존성이 완료된 인스턴스는 이 &amp;quot;큐&amp;quot;에 들어가고, poll 되며 컨테이너에 넣는다.
    // CompleteObject :
    // --&amp;gt; 의존 리스트를 해소하기 위해 만들어 둔 자료구조.(문자열, 인스턴스, 클래스 데이터)
    // --&amp;gt; 완성 인스턴스는 자기를 기다리는 필드-인스턴스 리스트들을 해소 해 줘야 한다.
    Queue&amp;lt;CompleteObject&amp;gt; completeQueue;

    // Object : &amp;quot;미완성 인스턴스&amp;quot; 를 의미한다.
    // AtomicInteger : 이 인스턴스가 완성되기까지 필요한 수를 의미한다.
    // &amp;quot;Integer&amp;quot; 는 불변성을 지녀 AtomicInteger 로 선언하였다.
    Map&amp;lt;Object, AtomicInteger&amp;gt; dependencyTracker;

    // String : 완성된 인스턴스의 등록 패키지 이름. -&amp;gt; 위와 의미가 동일함.
    // Object :
    //  --&amp;gt; 일반 인스턴스는 String key 와 패키지 이름이 같으며,
    //  --&amp;gt; 프록시 인스턴스의 경우 String key 와 패키지 이름이 같지 않다.
    Map&amp;lt;String, Object&amp;gt; singletonContainer;

    // 생성자로부터 건네받은 &amp;quot;모든&amp;quot; 클래스 메타데이터를 의미한다.
    List&amp;lt;Class&amp;lt;?&amp;gt;&amp;gt; clazzList;

    // 이 프로세스에서, 순환 참조를 허용 할 것인지, 엄격하게 실행 할 것인지 설정한다.
    boolean isAllowCircular;

    // 필요한 데이터와 자료구조 세팅.
    public ResolveDependency(List&amp;lt;Class&amp;lt;?&amp;gt;&amp;gt; clazzList, boolean allowCircularDependency) {
        this.clazzList = clazzList;
        this.isAllowCircular = allowCircularDependency;
        this.dependencyMap = new HashMap&amp;lt;&amp;gt;();
        this.completeQueue = new LinkedList&amp;lt;&amp;gt;();
        this.dependencyTracker = new HashMap&amp;lt;&amp;gt;();
        this.lazyMap = new HashMap&amp;lt;&amp;gt;();
        this.singletonContainer = new HashMap&amp;lt;&amp;gt;();
    }
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ResolveDependency&lt;/code&gt; 는 클래스의 메타데이터를 읽고,&lt;/p&gt;
&lt;p&gt;내부에 적용된 애너테이션에 대한 적용, 및 &amp;quot;의존성 해결&amp;quot; 을 목표로 한다.&lt;/p&gt;
&lt;p&gt;따라서 생성자는 인자로 &lt;code&gt;Class&amp;lt;?&amp;gt;[] clazzArrs&lt;/code&gt; 배열 하나를 받을 것이다.&lt;/p&gt;
&lt;p&gt;즉, &lt;code&gt;ResearchPackage&lt;/code&gt; 에서 탐색된 클래스 메타데이터를 &lt;code&gt;ResolveDependency&lt;/code&gt; 로 옮긴다.&lt;/p&gt;
&lt;p&gt;또한 &lt;code&gt;isAllowCircular&lt;/code&gt; 불린 값으로, 순환 참조를 허용 할 것인지, 아닌지 정한다.&lt;/p&gt;
&lt;p&gt;이는 &lt;code&gt;@MyLazy&lt;/code&gt; 를 선언하지도 않은 &amp;quot;완전한 순환참조&amp;quot; 를 의미한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;수많은 자료구조들이 위에 떠 있는데, 이는 프로그램을 구축하면서&lt;/p&gt;
&lt;p&gt;필수적인 &amp;quot;의존성 해결&amp;quot; 뿐만 아니라, 몇 가지 유틸적인 기능을 넣기 위함이다.&lt;/p&gt;
&lt;p&gt;솔직히, &amp;quot;순환참조도 상관없는 의존성 컨테이너 제작&amp;quot; 을 선택한다면,&lt;/p&gt;
&lt;p&gt;5 개의 자료구조는 사라져도 된다.&lt;/p&gt;
&lt;p&gt;그러나 최소한 사용자에게 있어 올바른 아키텍쳐의 구성을 유도하도록 만드는 것이 올바르다고 판단했다.&lt;/p&gt;
&lt;h3&gt;Proxy API 와 적용 메서드들&lt;/h3&gt;
&lt;p&gt;Java Proxy API 는 Java 라는 언어 자체의 엄격함과 그로부터 나온 한계로 인해&lt;/p&gt;
&lt;p&gt;공식 문서에서 배우면서 의아한 경우가 많았다.&lt;/p&gt;
&lt;p&gt;그러나 직접 여러 상황에서의 Proxy API 를 적용하다 보니, 어떤 흐름으로 이해해야 하는지&lt;/p&gt;
&lt;p&gt;알게 되었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;컴퓨터 로직에서의 Proxy 란, 원본을 거치기 전 특정한 Logic 을 수행해 주는 존재라고 할 수 있다.&lt;/p&gt;
&lt;p&gt;프로그래밍에서의 Proxy 란, 원본 객체를 거치기 전 그리고 후, 특정한 Logic 을 수행하는 존재이다.&lt;/p&gt;
&lt;p&gt;어떤 의미에서 Proxy 란, Interceptor(가로채기) 라는 개념과 매우 비슷하기도 하다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;Spring 에서 AOP, Proxy 를 적용하는 과정은 다음과 같다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;code&gt;@Aspect&lt;/code&gt; 애너테이션과 스키마(Schema) 를 통한 일괄 적용&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;사실, Spring 을 제대로 배우지 않고 Container 를 제작해서 잘 모른다..&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이를 알기 위해 Spring Documentation 을 읽어봤는데,&lt;/p&gt;
&lt;p&gt;Spring 은 &amp;quot;책임의 분리&amp;quot; 를 완벽하게 해내기 위해 또 다른 시스템을 도입했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;내가 만들게 되는 Logic 과&lt;/p&gt;
&lt;p&gt;즉, 옛날 시스템, EJB(Enterprise Java Bean) 의 Proxy 적용 과정은 매우 흡사하다.&lt;/p&gt;
&lt;p&gt;&amp;quot;그러나&amp;quot;&lt;/p&gt;
&lt;p&gt;Spring 은 EJB 의 프록시 적용 과정에서,&lt;/p&gt;
&lt;p&gt;개발자들이 &amp;quot;메서드 실행/종료 체크&amp;quot; 와 같은 간단한 AOP 적용조차,&lt;/p&gt;
&lt;p&gt;클래스와 인터페이스, 그리고 위에서 질리도록 말한 &lt;code&gt;InvocationHandler&lt;/code&gt; 의 구현이&lt;/p&gt;
&lt;p&gt;뒤따를 수 밖에 없다는 것을 인지했을 것이다.&lt;/p&gt;
&lt;p&gt;&amp;quot;배우면 되지 않나?&amp;quot; 라고 생각하면 되지만, Spring 의 개발자들은 더 넓은 시야를 가졌던 것 같다.&lt;/p&gt;
&lt;p&gt;내가 설명하는 것은 완벽히 동일하지 않지만,&lt;/p&gt;
&lt;p&gt;Spring 은,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;AOP 로직을 만든다.&lt;/li&gt;
&lt;li&gt;AOP 클래스 Bean 내부에 &amp;quot;어떤 패키지를 기준으로 전부 적용할 것인지&amp;quot; 정한다.&lt;/li&gt;
&lt;li&gt;이 때, AOP 로직(Proxy 로직) 이 적용되는 컴포넌트는 &amp;quot;이를 알지 못한다.&amp;quot;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;위의 과정은 &amp;quot;책임의 분리&amp;quot; 자체를 성실히 수행한 결과이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나 일반적인 Proxy 적용과 Spring 의 AOP 적용은 둘 다 치명적인 문제를 가진다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;바로, &amp;quot;final&amp;quot; 은 어떻게 할 수가 없다는 것이다.&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;Proxy 과정에서 Spring 조차 어찌 할 수 없는 final&lt;/h4&gt;
&lt;p&gt;내가 만든 프로그램과 Spring 조차도, Proxy 에 final 값을 수정할 수가 없다.&lt;/p&gt;
&lt;p&gt;Java 라는 언어와, 비슷한 저 수준의 언어를 이해해야 하는데,&lt;/p&gt;
&lt;p&gt;원래 &lt;code&gt;final&lt;/code&gt; 이라는 예약어는 &amp;quot;생성자에서조차 정할 수 없는 자료구조&amp;quot; 이다.&lt;/p&gt;
&lt;p&gt;그냥 코드에 값 자체를 &amp;quot;지정&amp;quot; 해야 한다.&lt;/p&gt;
&lt;p&gt;그런데, Java 는 &lt;code&gt;final&lt;/code&gt; 을 도입 할 때,&lt;/p&gt;
&lt;p&gt;JVM 런타임 시 이 값을 변경 할 수 있는 단 하나의 단계만을 남겨두었다.&lt;/p&gt;
&lt;p&gt;그게 바로 생성자, (&lt;code&gt;Constructor&lt;/code&gt;) 부분이다.&lt;/p&gt;
&lt;p&gt;이 부분을 제외한 단계에서는 변경 할 방법이 없다.&lt;/p&gt;
&lt;p&gt;Spring 은 Proxy(AOP) 를 제작 할 때,&lt;/p&gt;
&lt;p&gt;기본 컴포넌트의 &amp;quot;자식&amp;quot;(extends) 화 컴포넌트를 만들어서 적용한다.&lt;/p&gt;
&lt;p&gt;그러나, &lt;code&gt;final&lt;/code&gt; 은 이를 접근할 수 없다는 문제가 존재한다.&lt;/p&gt;
&lt;p&gt;클래스, 메서드에 &lt;code&gt;final&lt;/code&gt; 을 붙이면, 상속이 불가능해지기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;Proxy API 사용법&lt;/h4&gt;
&lt;p&gt;단순 Java 언어를 사용해 본 사람들 중에서도, Proxy 를 사용 해 본 사람은 적을 것이다.&lt;/p&gt;
&lt;p&gt;알고리즘 문제를 푸는 데에서 굳이 프록시를 적용 할 일은 없기도 하고,&lt;/p&gt;
&lt;p&gt;딱히 간단한 CRUD API 를 만드는 과정에서도 Proxy API 를 건들 일이 없다.&lt;/p&gt;
&lt;p&gt;차라리 AI 를 시켜서 Spring 에서 제작한 Custom Proxy 로직을 채택 할 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, 나는 순수 Java 와 내장 API 만을 사용하여 컨테이너를 만들겠다고 선언했다.&lt;/p&gt;
&lt;p&gt;앞으로 선언될 컴포넌트에 적용될 Proxy 를 &amp;quot;이해하기 위해서&amp;quot;는,&lt;/p&gt;
&lt;p&gt;Java 의 내장 Proxy API 를 이해해야 한다.&lt;/p&gt;
&lt;p&gt;간단히 내가 만든 Java 예제와 함께 알아보자.&lt;/p&gt;
&lt;br/&gt;

&lt;ol&gt;
&lt;li&gt;먼저 간단하게 적용할 클래스와, 인터페이스를 만들자.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;interface Component {
    public void testMethod();
}

// 무조건 Component 를 구현했다고 코드로 작성해야 한다.
public class ComponentImpl implements Component{
    private Integer number;
    public ComponentImpl(int number) {
        this.number = number;
    }

    public void testMethod() {
        System.out.println(&amp;quot;컴포넌트의 숫자 변수는 : &amp;quot; + number + &amp;quot; 입니다.&amp;quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;자, 위와 같은 &lt;code&gt;interface&lt;/code&gt;, &lt;code&gt;class&lt;/code&gt; 를 선언하자.&lt;/p&gt;
&lt;p&gt;그리고, PROXY 역할을 수행할 클래스 (&lt;code&gt;InvocationHandler&lt;/code&gt; 구현체) 를 만든다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class TestProxy implements InvocationHandler {
    // Proxy 에 감싸질 &amp;quot;원본 인스턴스 객체&amp;quot; 를 넣게 된다.
    private final Object target;

    // 인자로 원본 인스턴스를 받는다. --&amp;gt; 필수적인 템플릿
    public TestProxy(Object target) {
        this.target = target;
    }

    // InvocationHandler 의 메서드 구현체.
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(&amp;quot;Proxy 내부 --&amp;gt; 원본 메서드 실행 이전&amp;quot;);

        Object result = method.invoke(target, args);

        System.out.println(&amp;quot;Proxy 내부 --&amp;gt; 원본 메서드 실행 이후&amp;quot;);

        return result;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 &lt;code&gt;InvocationHandler&lt;/code&gt; 구현체인 &lt;code&gt;TestProxy&lt;/code&gt; 는,&lt;/p&gt;
&lt;p&gt;메서드인 &lt;code&gt;invoke&lt;/code&gt; 에서, 원본 객체 메서드 실행 직/후 Logic 을 만들 수 있다.&lt;/p&gt;
&lt;p&gt;여기서 메서드 실행 시간을 측정 할 수도 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그리고 위의 선언들을 이용하여 진정한 Proxy 객체를 만들어 보자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;
public class Main {
    public static void main(String[] args) {
        // 원본 객체를 생성한다.
        ComponentImpl originInstance = new ComponentImpl(3);

        // 원본 인스턴스의 객체 데이터를 뽑는다.
        Class&amp;lt;?&amp;gt; clazz = originInstance.getClass();
        // = ComponentImpl.class 와 동일.

        // InvocationHandler 클래스의 인스턴스가 필요하다.
        InvocationHandler handler = new TestProxy(originInstance);

        // 프록시 API 를 이용하여 프록시 객체를 만들어 낸다.
        Component proxyInstance = (Component) Proxy.newProxyInstance(
            // 이 클래스 메타데이터를 가져온 &amp;quot;클래스 로더&amp;quot; 객체를 가져온다.
            clazz.getClassLoader(),
            // 객체에서 proxy 처리될 메서드를 지정하는 행위와 동일하다.
            // 인스턴스에서 추출한 인터페이스 리스트를 그대로 넣는 것 또한 가능하다.
            new Class&amp;lt;?&amp;gt;[] {Component.class},
            // 어떤 proxy 기능을 넣을건지 지정한 InvocationHandler 구현체를 넣는다.
            handler
        );

        // proxy 객체에서 &amp;quot;지정된 proxy 메서드&amp;quot; 를 실행 할 수 있다.
        proxyInstance.testMethod();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 과정을 통해 Java 언어에서의 일반적인 Proxy Instance 생성 과정을 알아보았다.&lt;/p&gt;
&lt;p&gt;그러나, 메타 프로그래밍에서의 Proxy Instance 생성 코드는 굉장히 불친절할 것이다.&lt;/p&gt;
&lt;p&gt;위의 예시에서는 직접적으로 &amp;quot;Type&amp;quot; 이 지정되었지만,&lt;/p&gt;
&lt;p&gt;아쉽게도 프로그램에서는&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Object&lt;/code&gt;(인스턴스), &lt;code&gt;Class&amp;lt;?&amp;gt;&lt;/code&gt; (클래스 메타데이터, 인터페이스, 애너테이션 등등)&lt;/p&gt;
&lt;p&gt;이렇게 표현된다.&lt;/p&gt;
&lt;p&gt;따라서 프로그램을 구축하면서, 해당 &lt;code&gt;Object&lt;/code&gt;, &lt;code&gt;Class&amp;lt;?&amp;gt;&lt;/code&gt; 타입의 변수들이&lt;/p&gt;
&lt;p&gt;어떠한 값을 담고 있는지 Naming Convention 을 지정하는 것이 좋다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;Proxy API 적용 메서드들&lt;/h4&gt;
&lt;p&gt;나는 &lt;code&gt;Proxy&lt;/code&gt; 관련된 Custom Annotation 을 2 개 만들었다. (위에서 작성한 대로.)&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;MyProxy&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MyProxies&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Annotation 의 코드 지원으로 &lt;code&gt;@Repeatable&lt;/code&gt; 을 넣고,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;MyProxy&lt;/code&gt; 를 여러 번 작성할 수 있었는데,&lt;/p&gt;
&lt;p&gt;단일 프록시와 다중 프록시의 적용은 다른 역할을 수행한다고 생각하고 이를 나누었다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;MyProxy&lt;/code&gt; 와 &lt;code&gt;MyProxies&lt;/code&gt; 는 동일하게 컴포넌트에 적용될 &lt;code&gt;interface&lt;/code&gt; 를 하나 가지지만,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;MyProxy&lt;/code&gt; 는 &lt;code&gt;InvocationHandler&lt;/code&gt; 클래스 하나, &lt;code&gt;Class&amp;lt;?&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;MyProxies&lt;/code&gt; 는 &lt;code&gt;InvocationHandler&lt;/code&gt; 배열을 가진다. &lt;code&gt;Class&amp;lt;?&amp;gt;[]&lt;/code&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;나는 의존성 해결 Logic 을 먼저 작성하고 난 뒤, Proxy 메서드를 작성했는데,&lt;/p&gt;
&lt;p&gt;&amp;quot;너무 많은 분기점이 Clean Code&amp;quot; 를 방해하고 있었다.&amp;quot;&lt;/p&gt;
&lt;p&gt;각각의 애너테이션들은 Main Logic 에서 제 각각의 역할을 수행한다.&lt;/p&gt;
&lt;p&gt;문제는, &lt;code&gt;MyProxy&lt;/code&gt;, &lt;code&gt;MyProxies&lt;/code&gt; 를 유사하게 다루도록 만들어야 했다.&lt;/p&gt;
&lt;p&gt;그리고 추후 Spring 의 &lt;code&gt;@Aspect&lt;/code&gt; 와 비슷한 로직을 만들지도 몰라서,&lt;/p&gt;
&lt;p&gt;애너테이션은 각기 다르나, 로직은 하나의 줄기로 흐르도록 만들어야 했다.&lt;/p&gt;
&lt;br/&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TB

MyProxy(&amp;quot;MyProxy&amp;quot;)
MyProxies(&amp;quot;MyProxies&amp;quot;)

proxyProcess(&amp;quot;proxyProcess - 메서드&amp;quot;)

insertProxies(&amp;quot;insertProxies - 메서드&amp;quot;)

insertProxy(&amp;quot;insertProxy - 메서드&amp;quot;)

MyProxy --&amp;gt; proxyProcess

MyProxies --&amp;gt; proxyProcess

proxyProcess --&amp;gt; insertProxies

insertProxies --MyProxy 일 경우, 한 번 실행--&amp;gt; insertProxy

insertProxies --MyProxies 일 경우, 여러 번 실행---&amp;gt; insertProxy&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;
/**
* 프록시 적용 로직에서 사용되는 Custom Type
*/
package com.damsoon.util.type;

public class ProxyInfo {
    public Class&amp;lt;?&amp;gt; handler;
    public Class&amp;lt;?&amp;gt; targetInterface;

    public ProxyInfo(Class&amp;lt;?&amp;gt; handler, Class&amp;lt;?&amp;gt; targetInterface) {
        this.handler = handler;
        this.targetInterface = targetInterface;
    }
}


// --- ResolveDependency 클래스 메서드 중 일부

/**
     * 컴포넌트의 MyProxy, MyProxies 둘 중 하나에 표기되어 있는 정보를 토대로 &amp;quot;새로운 프록시&amp;quot; 객체를 만들어 낸다.
     *
     * @param proxy 컴포넌트에 단일 프록시 기능만 들어가 있을 경우, null 이 아니다.
     * @param proxies 컴포넌트에 다중 프록시 기능이 들어가 있을 경우, null 이 아니다.
     *                이 둘 중 하나는 null 이다.
     * @param instance 프록시가 적용된 타겟 인스턴스이다.
     * @param clazz 프록시가 적용되는 클래스 메타데이터 정보이다. 프록시 적용을 위해 모든 인터페이스 정보를 읽기 위함이다.
     * @return 프록시가 적용된 객체(&amp;lt;code&amp;gt;Object&amp;lt;/code&amp;gt;) 를 반환한다. 그러나, 기존 instance 와 묶여진 target Interface 외 어떠한 연결점도 없다.
     * 따라서 &amp;lt;code&amp;gt;completeQueue&amp;lt;/code&amp;gt; 에 들어가기 전에만 사용된다.
     */
    public Object proxyProcess(MyProxy proxy, MyProxies proxies, Object instance, Class&amp;lt;?&amp;gt; clazz) {
        // 단일이던, 다중이던 하나의 자료구조 &amp;quot;List&amp;quot; 로 만든다.
        // 이는 Iterator 로 모두 처리하기 위함이다.
        List&amp;lt;ProxyInfo&amp;gt; proxyList = new ArrayList&amp;lt;&amp;gt;();

        // 단일 프록시와 다중 프록시의 로직을 하나로 묶기 위해 자료구조 ProxyInfo 적용
        // ProxyInfo 는 핸들러와 인터페이스 정보를 각각 하나씩 가짐.
        if(proxy != null) {
            proxyList.add(new ProxyInfo(proxy.proxy(), proxy.targetInterface()));
        } else { // proxies 가 있는 상황
            Class&amp;lt;?&amp;gt;[] proxyArr = proxies.proxies();
            Class&amp;lt;?&amp;gt; targetInterface = proxies.targetInterface();
            for(int i = 0; i &amp;lt; proxyArr.length; i++) {
                proxyList.add(new ProxyInfo(proxyArr[i], targetInterface));
            }
        }

        // proxy 적용
        Iterator&amp;lt;ProxyInfo&amp;gt; proxyIterator = proxyList.iterator();
        instance = this.insertProxies(proxyIterator, instance, clazz);

        return instance;
    }

    // 프록시 객체 생성에 필요한 3 가지가 인자로 들어가는데,
    // 1. InvacationHandler 를 구현한 객체 메타데이터
    // 2. 프록시가 적용될 객체의 &amp;#39;인스턴스&amp;#39;
    // 3. 프록시가 적용될 객체 클래스의 모든 정보. --&amp;gt; 구현된 인터페이스를 모두 추출하기 위함
    public Object insertProxy(Class&amp;lt;?&amp;gt; handlerInfo, Class&amp;lt;?&amp;gt; targetInterface, Object instance, Class&amp;lt;?&amp;gt; clazzInfo) {
        Constructor handlerConstructor = null;
        InvocationHandler handlerInstance = null;

        // handlerInfo 에서 생성자를 꺼내는 것은 &amp;quot;무조건&amp;quot; 성공해야 하는 작업이기 때문에,
        // 실패 시 null 이 아니라 Exception 이 된다.
        // 에러를 담당하기 위해 try, catch 문을 사용한다.
        try {
            handlerConstructor = handlerInfo.getConstructor(Object.class);
            handlerInstance = (InvocationHandler) handlerConstructor.newInstance(instance);
        } catch (ReflectiveOperationException e) {
            String ErrorText = ColorText.red(&amp;quot;[Error in Creating Proxy Instance] : \n&amp;quot;)
                    + ColorText.red(&amp;quot;--&amp;gt; &amp;quot; + handlerInfo.getName() + &amp;quot; - 핸들러 에러 발생&amp;quot;);
            throw new RuntimeException(e);
        }

        // 새로운 프록시 객체를 만들고, 원본 객체와 다른 주소를 반환한다.
        // 새로운 주소는 프록시 객체의 주소를 의미한다.
        Object proxyObject = Proxy.newProxyInstance(
                clazzInfo.getClassLoader(),
                new Class&amp;lt;?&amp;gt;[] {targetInterface},
                handlerInstance
        );

        return proxyObject;
    }

    public Object insertProxies(Iterator&amp;lt;ProxyInfo&amp;gt; iterator, Object instance, Class&amp;lt;?&amp;gt; clazz) {
        // MyProxy 라면 1 번만 순회하며, MyProxies 는 여러번 순회한다.
        while(iterator.hasNext()) {
            ProxyInfo tmpProxy = iterator.next();
            try {
                // 프록시가 적용 될 때 마다, 반환된 instance 객체의 주소는 다르다.
                instance = insertProxy(tmpProxy.handler, tmpProxy.targetInterface, instance, clazz);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        return instance;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;생각보다 프록시 적용을 위해 복잡한 로직을 작성했다고 생각했을 수도 있고,&lt;/p&gt;
&lt;p&gt;더 나은 방식이 존재할 것이다.&lt;/p&gt;
&lt;p&gt;이 코드 위에 보여주었던 Proxy Instance 의 생성과는 달리,&lt;/p&gt;
&lt;p&gt;내가 작성한 Proxy 적용의 코드가 더 복잡한 이유는,&lt;/p&gt;
&lt;p&gt;&amp;quot;우리는 개발자가 작성한 프록시의 정보를 모른다.&amp;quot; 라는 특수성 때문이다.&lt;/p&gt;
&lt;p&gt;프레임워크의 특성은 사용자가 규칙 내에서만 충족한다면, 어떻게 작성되든 상관없이 동작해야 한다.&lt;/p&gt;
&lt;p&gt;즉, 프로그램이 구동되고 메타데이터가 증축되는 그 순간부터, 모든 정보는&lt;/p&gt;
&lt;p&gt;&amp;quot;어떤 타입을 가졌는지 알지 못한다.&amp;quot;&lt;/p&gt;
&lt;p&gt;따라서 어떠한 타입이던지 Meta Data 에 속한다면 &lt;code&gt;Class&amp;lt;?&amp;gt;&lt;/code&gt; 를,&lt;/p&gt;
&lt;p&gt;어떠한 타입이던지 Instance 에 속한다면, &lt;code&gt;Object&lt;/code&gt; 타입으로 처리하게 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;의존성 해소를 위해서는 Type 의 구분이 무조건 필요하지 않나?&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;이 고민은 의존성 해결 로직을 작성하면서 끊임없이 생각했던 주제였다.&lt;/p&gt;
&lt;p&gt;나는 의존성 대기 맵, &lt;code&gt;Map&amp;lt;String, List&amp;lt;WaitingField&amp;gt;&amp;gt; dependencyMap&lt;/code&gt; 을 선언하여&lt;/p&gt;
&lt;p&gt;기다리고 있는 객체에 대한 정보를 &amp;quot;문자열&amp;quot; 로 지정했다.&lt;/p&gt;
&lt;p&gt;이 덕분에, 아무리 프록시가 적용되어 객체 주소와 실제 타입(EX - &lt;code&gt;Proxy7$&lt;/code&gt;) 이 다르다고 한들,&lt;/p&gt;
&lt;p&gt;문자열로 해당 의존성을 지정해 버리니, &lt;code&gt;com.damsoon.component.Component&lt;/code&gt; 와 같이 등록한다면,&lt;/p&gt;
&lt;p&gt;내부의 실제 객체는 &lt;code&gt;Proxy7$&lt;/code&gt; 이더라도, 해당 문자열에 해당하는 인스턴스가 생성되기를 기다리는 리스트에&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Proxy7$&lt;/code&gt; 을 넣어서 의존성을 해결 해 줄 수 있는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;위의 과정은 의존성 해결 로직과 함께 설명해야 하는데, 이해가 잘 되지 않는다면 그게 정상일 것이라고 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;의존성 해결 로직과 책임의 분리&lt;/h3&gt;
&lt;p&gt;나는 &amp;quot;순환참조&amp;quot; 구조도 받아들이는 프로그램을 제작한다고 말했었다.&lt;/p&gt;
&lt;p&gt;Spring 은 이미 &lt;code&gt;@Lazy&lt;/code&gt; 애너테이션 표식을 통해,&lt;/p&gt;
&lt;p&gt;클래스 내부의 필요 의존성을 나중으로 미룰 수 있는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나 나는 여기서 한 발짝 더 나아갔다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@Lazy&lt;/code&gt; 와 같은 표식(애너테이션) 도 추가하되(&lt;code&gt;@MyLazy&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;이 표식조차도 갖춰지지 않은 완벽한 순환참조 구조조차도 받아들이자.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;스프링은 객체를 인식하고, 필요 의존성을 완성하기 위해, 필요 의존성의 필요 의존성을 마무리해야&lt;/p&gt;
&lt;p&gt;객체 내부의 필요 의존성을 완성하여, 객체를 완성할 수 있는 것이다. (DFS) 흐름과 유사하다.&lt;/p&gt;
&lt;p&gt;그런데, 나의 방식은 완전히 달랐다.&lt;/p&gt;
&lt;p&gt;객체를 인식하는 순간, 해당 객체는 &amp;quot;의존성이 완성 된 것 처럼&amp;quot; 만들어 버린다.&lt;/p&gt;
&lt;p&gt;비록 완전한 인스턴스 객체가 아니라, 가짜 의존성이지만, 어쨌든 Instance 가 존재하는 것이다.&lt;/p&gt;
&lt;p&gt;이 과정을 &amp;quot;모든 컴포넌트&amp;quot; 에서 먼저 실행하고, 한 번만 순회하여 간단히 의존성을 완성 할 수도 있었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나 이 방식은 좋은 코드, 즉, 올바른 템플릿으로 유도하지 않고 스파게티 코드를 유발하는 프로그램이 될 것이라고 생각했다.&lt;/p&gt;
&lt;p&gt;이에 나는 의존성 해결 로직을 &amp;quot;3 단계&amp;quot; 로 나누었다.&lt;/p&gt;
&lt;br/&gt;

&lt;ol&gt;
&lt;li&gt;순환 참조나 여타 문제가 없는 구조의 컴포넌트들을 완성한다.&lt;/li&gt;
&lt;li&gt;Annotation 으로 인해 의존성 완성을 미룬 &lt;code&gt;@Lazy&lt;/code&gt; 의존성을 넣어주고 완성한다.&lt;/li&gt;
&lt;li&gt;완벽한 순환 참조를 이루는 비정상적인 컴포넌트 의존성을 해소 해 주되, &amp;quot;경고한다.&amp;quot;&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;p&gt;위의 과정을 수행하기 위해, 여러 자료구조를 생성했다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;HashMap&amp;lt;String, List&amp;lt;WaitingField&amp;gt;&amp;gt; dependencyMap&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;HashMap&amp;lt;String, List&amp;lt;WaitingField&amp;gt;&amp;gt; lazyMap&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Map&amp;lt;Object, AtomicInteger&amp;gt; dependencyTracker&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Queue&amp;lt;CompleteQueue&amp;gt; completeQueue&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Map&amp;lt;String, Object&amp;gt; singletonContainer&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;해당 패키지를 기다리는 (인스턴스-&lt;code&gt;Field&lt;/code&gt;) 쌍 배열이 대기하는 곳.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;String&lt;/code&gt; 에 해당하는 컴포넌트가 &amp;quot;완성&amp;quot; 되면, &lt;code&gt;WaitingField&lt;/code&gt; 들을 순회하며 의존성을 주입한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;code&gt;MyLazy&lt;/code&gt; 가 의존성에 선언되었을 때, 해당 패키지를 기다리는 (인스턴스-`Field) 쌍 배열이 대기하는 곳&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@MyLazy&lt;/code&gt; 가 붙은 의존성일 경우 이 Map 에 등록되며, 두 번째 의존성 해결 로직에서 해결한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;미완성 인스턴스가 생성되지마자, 해당 객체는 몇 개의 의존성을 가지는지 기록하는 곳&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;어떤 미완성 객체가 의존성을 하나 해결하면, 해당 객체의 수(&lt;code&gt;AtomicInteger&lt;/code&gt;) 가 1 줄어든다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0&lt;/code&gt; 이 되었을 경우, 이 자료구조에서 &lt;code&gt;delete&lt;/code&gt; 하며 &lt;code&gt;completeQueue&lt;/code&gt; 에 들어간다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;미완성 인스턴스가 &amp;quot;완성&amp;quot; 되었을 때, 들어가는 곳.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&amp;quot;의존성 완성&amp;quot; 된 컴포넌트는 자신을 원하는 장소에 주소를 주입하며, 프록시 처리를 위해 대기한다.(큐에서)&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;완성 인스턴스가 자신을 필요하는 장소에 주입하고, 프록시 적용 시 적용 이후 들어가는 완전체 컨테이너.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;더 이상 처리 할 것이 없는 완벽한 컴포넌트들이 들어가는 곳이다.&lt;/li&gt;
&lt;li&gt;스스로 수정 할 것은 없지만, 필요시 &amp;quot;참조&amp;quot; 될 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;h4&gt;의존성 해결 예시를 위한 컴포넌트 Example&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TB

subgraph Component1 [&amp;quot;Component1&amp;quot;]
    Depen-Com1-1(&amp;quot;의존 - Component2&amp;quot;)
end

subgraph Component2 [&amp;quot;Component2&amp;quot;]
    Depen-Com2-1(&amp;quot;의존 - {Lazy} Component1&amp;quot;)
    Depen-Com2-2(&amp;quot;의존 - Component3&amp;quot;)
end

subgraph Component3 [&amp;quot;Component3&amp;quot;]
    Depen-Com3-1(&amp;quot;의존성 없음&amp;quot;)
end

subgraph Component4 [&amp;quot;Component4&amp;quot;]
    Depen-Com4-1(&amp;quot;의존 - Component3&amp;quot;)
end

subgraph Component5 [&amp;quot;Component5&amp;quot;]
    Depen-Com5-1(&amp;quot;의존 - Component6&amp;quot;)
end

subgraph Component6 [&amp;quot;Component6&amp;quot;]
    Depen-Com6-1(&amp;quot;의존 - Component5&amp;quot;)
end

Component1 ~~~ Component2


Component3 ~~~ Component4


Component5 ~~~ Component6&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;앞으로 보여줄 3 단계의 의존성 해결 프로세스를 설명하기 위해 위와 같은 그래프를 제작했다.&lt;/p&gt;
&lt;p&gt;간단한 설명을 위해 &lt;code&gt;Proxy&lt;/code&gt; 표식은 넣지 않았으며,&lt;/p&gt;
&lt;p&gt;이를 통해&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;일반적인 컴포넌트 의존성 해결&lt;/li&gt;
&lt;li&gt;Lazy 의존성 해결&lt;/li&gt;
&lt;li&gt;완벽한 순환참조 고리에 갇힌 의존성 해결&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;프로세스를 설명 할 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;위의 컴포넌트들이 존재한다면, 각각의 자료구조에는 이러한 데이터가 저장된다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Queue&amp;lt;CompleteObject&amp;gt; completeQueue&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;완성된 &lt;code&gt;Component3&lt;/code&gt; 인스턴스&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Map&amp;lt;String, List&amp;lt;WaitingField&amp;gt;&amp;gt; dependencyMap&lt;/code&gt;&lt;ol&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;com.damsoon.component.Component2&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;[ {컴포넌트1-Field, 컴포넌트1-instance} ]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;com.damsoon.component.Component3&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;[ {컴포넌트2-Field, 컴포넌트2-instance}, {컴포넌트4-Field, 컴포넌트4-instance} ]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;com.damsoon.component.Component5&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;[ {컴포넌트6-Field, 컴포넌트6-instance} ]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;com.damsoon.component.Component6&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;[ {컴포넌트5-Field, 컴포넌트5-instance} ]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Map&amp;lt;String, List&amp;lt;WaitingField&amp;gt;&amp;gt; lazyMap&lt;/code&gt;&lt;ol&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;com.damsoon.component.Component1&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;[ {컴포넌트2-Field, 컴포넌트2-instance} ]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Map&amp;lt;Object, AtomicInteger&amp;gt; dependencyTracker&lt;/code&gt;&lt;ol&gt;
&lt;li&gt;&lt;code&gt;Component1 인스턴스&lt;/code&gt; -- &lt;code&gt;1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Component2 인스턴스&lt;/code&gt; -- &lt;code&gt;1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Component4 인스턴스&lt;/code&gt; -- &lt;code&gt;1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Component5 인스턴스&lt;/code&gt; -- &lt;code&gt;1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Component6 인스턴스&lt;/code&gt; -- &lt;code&gt;1&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Map&amp;lt;String, Object&amp;gt; singletonContainer&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;비어 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;-dependencyTracker 를 보면서 들 수 있는 의문.-&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Component2&lt;/code&gt; 를 보면, 가지고 있는 총 의존성은 2 개이다.&lt;/p&gt;
&lt;p&gt;그런데, &lt;code&gt;dependencyTracker&lt;/code&gt; 에서 참조하면, 의존성은 1 개이다.&lt;/p&gt;
&lt;p&gt;이는 &amp;quot;2단계 의존성 해소 로직&amp;quot; 에서 이루어질 행동 때문인데,&lt;/p&gt;
&lt;p&gt;일반 의존성과는 달리 해석하는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;의존성을 읽으며, &lt;code&gt;@MyLazy&lt;/code&gt; 표기된 의존성은 &amp;quot;의존성 수&amp;quot; 에 더해지지 않는다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;의존성 해결 1 단계 - 특수성을 지니지 않은 컴포넌트 의존성 해결&lt;/h4&gt;
&lt;p&gt;위에서 의존성 해결을 3 단계로 나누겠다고 선언했고, 이는 그 중 첫 번째 로직이다.&lt;/p&gt;
&lt;p&gt;일반적인 의존성을 해결하는 단계이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;우리가 눈여겨 봐야 할 &lt;code&gt;ResolveDependency&lt;/code&gt; 클래스의 변수 자료구조가 있다.&lt;/p&gt;
&lt;p&gt;바로,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Map&amp;lt;String, List&amp;lt;WaitingField&amp;gt;&amp;gt; dependencyMap&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Queue&amp;lt;CompleteObject&amp;gt; completeQueue&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Map&amp;lt;Object, AtomicInteger&amp;gt; dependencyTrakcer&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Map&amp;lt;String, Object&amp;gt; singletonContainer&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 3 개 이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이 단계가 시작하기 위해서는,&lt;/p&gt;
&lt;p&gt;어떠한 외부적인 요인으로 인해 &amp;quot;모든 의존성이 해소&amp;quot; 된 컴포넌트나,&lt;/p&gt;
&lt;p&gt;애초부터 &amp;quot;어떠한 의존성도 없는&amp;quot; 컴포넌트가 필요하다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;처음 모든 컴포넌트 메타데이터를 읽을 때, 위에 작성해 놓은 3 가지 자료구조에 모든 정보가 들어간다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Queue&lt;/code&gt; 자료구조에는 의존성이 없거나 모두 해소되어 &amp;quot;다른 컴포넌트의 의존성을 해소&amp;quot;하기위해 대기하는 장소이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;dependencyMap&lt;/code&gt; 은 컴포넌트의 의존성이 모두 해소되지 못하여 &lt;code&gt;instance-Field&lt;/code&gt; 쌍 배열로 대기한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;dependencyTracker&lt;/code&gt; 은 의존성이 모두 해소되지 못한 인스턴스가 현재 &amp;quot;몇 개의 의존성&amp;quot; 을 아직 필요로 하는지 기록한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;&amp;quot;위의 자료구조들로 의존성 해소를 시작 해 보자.&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;먼저, &lt;code&gt;Queue&lt;/code&gt; 에 적어도 하나의 완성 인스턴스가 존재한다고 가정한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;---
title : Logic 1 -
---
flowchart TB

subgraph Queue[&amp;quot;Queue&amp;quot;]
    Queue-Purpose(&amp;quot;의존성이 모두 해소된 인스턴스들이 들어간다.&amp;quot;)
end

subgraph DependencyMap[&amp;quot;dependencyMap&amp;quot;]
    DependencyMap-Purpose(&amp;quot;어떤 의존성을 기다리는지, &amp;lt;br/&amp;gt; (인스턴스-필드) 쌍 구조로 기다린다.&amp;quot;)
end

subgraph DependencyTracker[&amp;quot;dependencyTrakcer&amp;quot;]
    DependencyTracker-Purpose(&amp;quot;의존성 미해소 인스턴스가 현재 몇 개의 의존성을 필요로 하는지 표기한다.&amp;quot;)
end

subgraph SingletonContainer[&amp;quot;singletonContainer&amp;quot;]
    SingletonContainer-Purpose(&amp;quot;더 이상 수정될 필요가 없는 완성 인스턴스가 정착하는 마지막 장소&amp;quot;)
end

DependencyTracker ~~~ SingletonContainer

subgraph Logic1[&amp;quot;Logic 1 - 시작&amp;quot;]
    direction TB
    Logic1-1(&amp;quot;Queue 에서 poll 한다. &amp;lt;br/&amp;gt; (완성 인스턴스.)&amp;quot;)
    Logic1-2(&amp;quot;완성 인스턴스의 이름, String 을 추출한다.&amp;quot;)
    Logic1-3(&amp;quot;String 으로 dependencyMap.get 한다.&amp;quot;)
    Logic1-4(&amp;quot;추출된 WaitingField 리스트를 순회한다.&amp;quot;)

    Logic1-1 -.- Logic1-2
    Logic1-2 -.- Logic1-3
    Logic1-3 -.- Logic1-4
end

Queue --완성 인스턴스 하나를 건네준다--&amp;gt; Logic1

DependencyMap --검색된 이름으로 대기 리스트를 건네준다--&amp;gt; Logic1-3
&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TB

subgraph Queue[&amp;quot;Queue&amp;quot;]
    Queue-Purpose(&amp;quot;의존성이 모두 해소된 인스턴스들이 들어간다.&amp;quot;)
end

subgraph DependencyMap[&amp;quot;dependencyMap&amp;quot;]
    DependencyMap-Purpose(&amp;quot;어떤 의존성을 기다리는지, &amp;lt;br/&amp;gt; (인스턴스-필드) 쌍 구조로 기다린다.&amp;quot;)
end

subgraph DependencyTracker[&amp;quot;dependencyTrakcer&amp;quot;]
    DependencyTracker-Purpose(&amp;quot;의존성 미해소 인스턴스가 현재 몇 개의 의존성을 필요로 하는지 표기한다.&amp;quot;)
end

subgraph SingletonContainer[&amp;quot;singletonContainer&amp;quot;]
    SingletonContainer-Purpose(&amp;quot;더 이상 수정될 필요가 없는 완성 인스턴스가 정착하는 마지막 장소&amp;quot;)
end

DependencyMap ~~~ SingletonContainer

subgraph Logic2[&amp;quot;Logic 2 - 순회 내부&amp;quot;]
    direction TB
    Logic2-1(&amp;quot;인스턴스-Field 에 완성 인스턴스를 Field 에 넣어준다.&amp;quot;)
    Logic2-2(&amp;quot;field.set(인스턴스, 완성 인스턴스) 가 된다.&amp;quot;)
    Logic2-3(&amp;quot;만약 순회 중 인스턴스 의존성이 해소되었다면, Queue 에 넣는다.&amp;quot;)
    Logic2-4(&amp;quot;해소된 인스턴스는 dependencyTracker 에서 제거한다.&amp;quot;)

    Logic2-1 -.- Logic2-2
    Logic2-2 -.- Logic2-3
    Logic2-3 -.- Logic2-4
end

Logic2-3 &amp;lt;--현재 필요 의존성 수를 검색--&amp;gt; DependencyTracker
Logic2-3 --의존성이 모두 해소된 완성 인스턴스 추가--&amp;gt; Queue
Logic2-4 --해소 인스턴스는 DependencyTracker 에서 제거한다--&amp;gt; DependencyTracker
&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TB

subgraph Queue[&amp;quot;Queue&amp;quot;]
    Queue-Purpose(&amp;quot;의존성이 모두 해소된 인스턴스들이 들어간다.&amp;quot;)
end

subgraph DependencyMap[&amp;quot;dependencyMap&amp;quot;]
    DependencyMap-Purpose(&amp;quot;어떤 의존성을 기다리는지, &amp;lt;br/&amp;gt; (인스턴스-필드) 쌍 구조로 기다린다.&amp;quot;)
end

subgraph DependencyTracker[&amp;quot;dependencyTrakcer&amp;quot;]
    DependencyTracker-Purpose(&amp;quot;의존성 미해소 인스턴스가 현재 몇 개의 의존성을 필요로 하는지 표기한다.&amp;quot;)
end

subgraph SingletonContainer[&amp;quot;singletonContainer&amp;quot;]
    SingletonContainer-Purpose(&amp;quot;더 이상 수정될 필요가 없는 완성 인스턴스가 정착하는 마지막 장소&amp;quot;)
end

Queue ~~~ DependencyTracker

subgraph Logic3[&amp;quot;Logic 3 - 마무리&amp;quot;]
    direction TB
    Logic3-1(&amp;quot;Queue 에서 poll 했던 완성 인스턴스는 Container 로 들어간다.&amp;quot;);
    Logic3-2(&amp;quot;완성 인스턴스의 이름으로 등록된 의존성 대기줄을 삭제한다.&amp;quot;);
    Logic3-3(&amp;quot;Queue 가 빌 때 까지, Logic1 으로 되돌아간다.&amp;quot;)

    Logic3-1 -.- Logic3-2
    Logic3-2 -.- Logic3-3
end

Logic3-1 --수정이 필요없는 완성 인스턴스--&amp;gt; SingletonContainer
Logic3-2 --자신을 대기하는 의존성은 없다--&amp;gt; DependencyMap
&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;Logic1, 2, 3 를 전부 하나의 그래프에 넣으려고 했는데,&lt;/p&gt;
&lt;p&gt;너무 난잡하여 이를 나누어 표기했다.&lt;/p&gt;
&lt;p&gt;Logic 1 은 &amp;quot;완성 인스턴스&amp;quot; 를 기준으로 의존성 해소를 위한 준비 작업이며,&lt;/p&gt;
&lt;p&gt;Logic 2 는 추출된 &lt;code&gt;List&amp;lt;WaitingField&amp;gt;&lt;/code&gt; 를 순회할 때 내부의 로직이다.&lt;/p&gt;
&lt;p&gt;Logic 3 는 순회가 끝나고 &amp;quot;완성 인스턴스 후처리&amp;quot; 를 의미한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;&amp;quot;그러면 추후 의존성 해결 2 단계와 3 단계는 더 어려운가?&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&amp;quot;전혀 그렇지 않다.&amp;quot;&lt;/p&gt;
&lt;p&gt;2 단계와 3 단계는 전부 1 단계에서 만들어진 로직을 재활용하여 처리한다.&lt;/p&gt;
&lt;p&gt;표기된 순환 참조 의존성 해소와,&lt;/p&gt;
&lt;p&gt;완전한 순환 참조 의존성 해소는 엄연히 그 의미가 다르다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;의존성 해결 1단계를 이용한 컴포넌트 예시&lt;/h4&gt;
&lt;p&gt;간단히 표시 할 건데, 이를 이해하기 위한 설명이 필요하다.&lt;/p&gt;
&lt;p&gt;1 단계 의존성 해결은 &amp;quot;일반 의존성 해결&amp;quot; 을 의미한다.&lt;/p&gt;
&lt;p&gt;시작하기 위해서는 메타데이터를 읽으며 &amp;quot;의존성 해소가 필요 없는&amp;quot; 컴포넌트가 최소 1 개는 필요하다.&lt;/p&gt;
&lt;p&gt;내가 만든 컴포넌트 예시에, &lt;code&gt;Component3&lt;/code&gt; 는 의존성이 존재하지 않는다.&lt;/p&gt;
&lt;p&gt;그렇다면, &lt;code&gt;Component3&lt;/code&gt; 는 이미 &lt;code&gt;Queue&lt;/code&gt; 에 들어가 있다.&lt;/p&gt;
&lt;p&gt;1 단계의 시작은 &lt;code&gt;Queue.poll&lt;/code&gt; 로 시작하기 때문에, &lt;code&gt;Component3&lt;/code&gt; 를 기준으로,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Component3&lt;/code&gt; 를 의존성으로 삼은 컴포넌트들(&lt;code&gt;List&amp;lt;WaitingField&amp;gt;&lt;/code&gt;) 을 순회하게 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Queue(completeQueue)&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;add&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;poll&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Component3&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;1. Component3 로 의존성 해소&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;미리 &lt;code&gt;Queue&lt;/code&gt; 에 들어가 있던 Component3 를 기준으로, 의존성 해소를 시작하게 된다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Component3&lt;/code&gt; 를 원하는 컴포넌트는 &lt;code&gt;Component4&lt;/code&gt;, &lt;code&gt;Component2&lt;/code&gt; 이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Component4&lt;/code&gt;, &lt;code&gt;Component2&lt;/code&gt; 는 &lt;code&gt;Component3&lt;/code&gt; 의존성을 해결하면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Queue&lt;/code&gt; 에 들어간다.&lt;/p&gt;
&lt;p&gt;이 때의 결과는,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;Component3&lt;/code&gt; 인스턴스는 &lt;code&gt;singletonContainer&lt;/code&gt; 에 들어간다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Component2&lt;/code&gt;, &lt;code&gt;Component4&lt;/code&gt; 인스턴스는 &lt;code&gt;Queue&lt;/code&gt; 에 들어간다.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;이 2 개의 인스턴스는 &lt;code&gt;dependencyTracker&lt;/code&gt; 에서 제거한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;&lt;code&gt;Component3&lt;/code&gt; 는 &lt;code&gt;dependencyMap&lt;/code&gt; 에서 제거하고, &lt;code&gt;singletonContainer&lt;/code&gt; 로 들어간다.&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Queue(completeQueue)&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;add&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;poll&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Component4&lt;/td&gt;
&lt;td&gt;Component2&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;2. Component2 로 의존성 해소&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;컴포넌트3 를 해소하고, 컴포넌트 2, 4 가 현재 Queue 에 들어가 있다.&lt;/p&gt;
&lt;p&gt;그 다음으로 &lt;code&gt;Component2&lt;/code&gt; 이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;Component2&lt;/code&gt; 를 기다리는 인스턴스는 &lt;code&gt;Component1&lt;/code&gt; 하나뿐이다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;Component2&lt;/code&gt; 인스턴스는 &lt;code&gt;Component1&lt;/code&gt; 필드에 자신의 주소를 &amp;quot;주입&amp;quot; 하고 &lt;code&gt;singletonContainer&lt;/code&gt; 에 들어간다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Component1&lt;/code&gt; 인스턴스는 &lt;code&gt;Queue&lt;/code&gt; 에 들어간다.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Component1&lt;/code&gt; 은 &lt;code&gt;dependencyTracker&lt;/code&gt; 에서 제거한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;&lt;code&gt;Component2&lt;/code&gt; 는 &lt;code&gt;dependencyMap&lt;/code&gt; 에서 제거하고, &lt;code&gt;singletonContainer&lt;/code&gt; 로 들어간다.&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Queue(completeQueue)&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;add&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;poll&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Component1&lt;/td&gt;
&lt;td&gt;Component4&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;3. Component4 로 의존성 해소&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Component4&lt;/code&gt; 를 기다리는 인스턴스는 존재하지 않는다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;Component4&lt;/code&gt; 인스턴스는 자신을 기다리는 의존성이 등록되어 있지 않다.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;즉, &lt;code&gt;dependencyMap&lt;/code&gt; 에 &lt;code&gt;&amp;quot;com.damsoon.component.Component4&amp;quot;&lt;/code&gt; key 가 없다는 의미.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;code&gt;Component4&lt;/code&gt; 를 &lt;code&gt;dependencyTrakcer&lt;/code&gt; 에서 제거한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Component4&lt;/code&gt; 를 &lt;code&gt;singletonContainer&lt;/code&gt; 에 등록한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Queue(completeQueue)&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;add&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;poll&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Component1&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;p&gt;&lt;code&gt;Component1&lt;/code&gt; 을 기다리는 인스턴스는 &lt;code&gt;Component2&lt;/code&gt; 이지만, &lt;code&gt;@MyLazy&lt;/code&gt; 로 등록되어 있다.&lt;/p&gt;
&lt;p&gt;(이는 &amp;quot;일반 의존성&amp;quot; 이 아니므로, 2 단계 의존성 해결 프로세스에서 해결한다.)&lt;/p&gt;
&lt;p&gt;따라서, &lt;code&gt;Component1&lt;/code&gt; 을 기다리는 인스턴스는 존재하지 않는다. (의존성 해결 1단계에서 없다.)&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;Component1&lt;/code&gt; 인스턴스는 &lt;code&gt;dependencyMap&lt;/code&gt; 에 자신을 기다리는 의존성이 없다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Component1&lt;/code&gt; 을 &lt;code&gt;dependencyTracker&lt;/code&gt; 에서 제거한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Component1&lt;/code&gt; 을 &lt;code&gt;singletonContainer&lt;/code&gt; 에 등록한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Queue(completeQueue)&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;add&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;poll&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;비어있음&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;Queue&lt;/code&gt; 가 비어지면서, 의존성 해결 1 단계는 마무리가 되었다.&lt;/p&gt;
&lt;p&gt;그 다음 의존성 해결 2 단계를 시작하게 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;의존성 해결 2 단계 - @MyLazy 표기된 의존성 해결&lt;/h4&gt;
&lt;p&gt;애초에 완벽한 순환참조, 개발자조차 인지하지 못한 미해결 의존성 고리를 해결 할 수 있는 나의 프로그램에서&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@MyLazy&lt;/code&gt; 표기가 왜 필요한지 이해가 안 될 수 있다.&lt;/p&gt;
&lt;p&gt;그렇다면, 나는 2 단계, &lt;code&gt;@MyLazy&lt;/code&gt; 표기에 대한 의존성 해결 단계를 왜 넣었을까?&lt;/p&gt;
&lt;p&gt;이는 나의 프로그램이 &lt;strong&gt;&amp;quot;Framework&amp;quot;&lt;/strong&gt; 라는 정체성을 가진 순간부터 매우 중요한 의미를 가진다.&lt;/p&gt;
&lt;p&gt;프레임워크라는 것은, 사용자가 편리하게 프로그램을 만드는 것에 의의가 있다.&lt;/p&gt;
&lt;p&gt;그러나 모든 프레임워크는, &amp;quot;사용자가 올바른 아키텍쳐를 만들도록 유도&amp;quot; 하는 데 책임이 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그토록 편리한 프레임워크의 대명사인 Spring 조차도, &lt;code&gt;@Lazy&lt;/code&gt; 의 사용은 자제하도록 유도한다.&lt;/p&gt;
&lt;p&gt;그 이유는, &amp;quot;기술의 부채&amp;quot;, Technical Dept 에 가깝다.&lt;/p&gt;
&lt;p&gt;&amp;quot;컴포넌트&amp;quot; 라 함은, 결국 재활용성에 가깝다.&lt;/p&gt;
&lt;p&gt;하나의 컴포넌트는 다른 여러 컴포넌트에서 사용을 원할 수도,&lt;/p&gt;
&lt;p&gt;혹은 거꾸로 하나의 컴포넌트가 다른 여러 컴포넌트를 원할 수 있다.&lt;/p&gt;
&lt;p&gt;만약에 SingletonContainer 라는 개념이 존재하지 않는다면,&lt;/p&gt;
&lt;p&gt;하나의 컴포넌트는 원하는 여러 클래스를 &amp;quot;제작 및 보관&amp;quot; 하게 되며,&lt;/p&gt;
&lt;p&gt;하나의 클래스가 몇십개의 컴포넌트에서 &amp;quot;제작&amp;quot; 되는 것이 된다.&lt;/p&gt;
&lt;p&gt;이는 심각한 메모리 낭비에 가깝다.&lt;/p&gt;
&lt;p&gt;이렇기에 &amp;quot;유일성&amp;quot;(Unique) 을 지닌 &amp;quot;컴포넌트&amp;quot; 로직 개념을 만들어 사용하는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이러한 컴포넌트의 역할을 통해 메모리의 누수를 방지하지만,&lt;/p&gt;
&lt;p&gt;사용자가 스파게티 코드를 제작하게 된다면, 프레임워크가 지닌 특유의 장점은 사라지게 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이러한 의미에서, 컴포넌트 클래스 제작 시, &lt;code&gt;@Lazy&lt;/code&gt;, 나의 프로그램에서 &lt;code&gt;@MyLazy&lt;/code&gt; 를 사용하는 것은,&lt;/p&gt;
&lt;p&gt;제작한 컴포넌트의 의존성이 &amp;quot;순환 참조&amp;quot;(&lt;code&gt;Circular Dependency&lt;/code&gt;) 상황에 이르렀음을 인지한다는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;내가 하고 싶은 말은,&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;프레임워크가 지닌 역할 중, 사용자가 올바른 아키텍쳐를 만들도록 유도한다 라는 점을 고려하여&lt;/p&gt;
&lt;p&gt;2 단계, &lt;code&gt;@MyLazy&lt;/code&gt; 선언된 의존성을 따로 처리하는 2 단계 로직을 만들었다는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;2 단계 의존성 해결 예시를 위한 컴포넌트 &amp;quot;현황&amp;quot;&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TB

subgraph SingletonContainer [&amp;quot;SingletonContainer&amp;quot;]
    Component1
    Component2
    Component3
    Component4
end

subgraph Component5 [&amp;quot;Component5&amp;quot;]
    Depen-Com5-1(&amp;quot;의존 - Component6&amp;quot;)
end

subgraph Component6 [&amp;quot;Component6&amp;quot;]
    Depen-Com6-1(&amp;quot;의존 - Component5&amp;quot;)
end
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4&gt;의존성 해결 2 단계 - @MyLazy 표식을 지닌 의존성 해결&lt;/h4&gt;
&lt;p&gt;현재 &lt;code&gt;ResolveDependency&lt;/code&gt; 자료구조가 가진 데이터의 현황을 작성해 보겠다.&lt;/p&gt;
&lt;br/&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Queue&amp;lt;CompleteObject&amp;gt; completeQueue&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;비어 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Map&amp;lt;String, List&amp;lt;WaitingField&amp;gt;&amp;gt; dependencyMap&lt;/code&gt;&lt;ol&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;com.damsoon.component.Component5&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;[ {컴포넌트6-Field, 컴포넌트6-instance} ]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;com.damsoon.component.Component6&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;[ {컴포넌트5-Field, 컴포넌트5-instance} ]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Map&amp;lt;String, List&amp;lt;WaitingField&amp;gt;&amp;gt; lazyMap&lt;/code&gt;&lt;ol&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;com.damsoon.component.Component1&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;[ {컴포넌트2-Field, 컴포넌트2-instance} ]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Map&amp;lt;Object, AtomicInteger&amp;gt; dependencyTracker&lt;/code&gt;&lt;ol&gt;
&lt;li&gt;&lt;code&gt;Component5 인스턴스&lt;/code&gt; -- &lt;code&gt;1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Component6 인스턴스&lt;/code&gt; -- &lt;code&gt;1&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Map&amp;lt;String, Object&amp;gt; singletonContainer&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;com.damsoon.component.Component1&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;com.damsoon.component.Component2&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;com.damsoon.component.Component3&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;com.damsoon.component.Component4&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;위의 자료구조 현황을 통해 인지할 수 있는 사실들을 펼쳐보자.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@MyLazy&lt;/code&gt; 의존성을 해결하는 2 단계에서는, &lt;code&gt;Queue&lt;/code&gt; 자료구조를 사용하지 않는다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@MyLazy&lt;/code&gt; 표식은, 사용자가 스스로 순환 참조임을 인식함과 동시에,&lt;/p&gt;
&lt;p&gt;이 표식으로 인해 순환참조의 고리가 해결 될 것이라는 의미를 가지고 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;하지만, 이 표식으로도 완벽한 순환참조를 이룰 수 있다는 상황을 반드시 가정해야 한다.&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;Map&amp;lt;String, List&amp;lt;WaitingField&amp;gt;&amp;gt; dependencyMap&lt;/code&gt; 자료구조에서,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Component5&lt;/code&gt;, &lt;code&gt;Component6&lt;/code&gt; 서로는 &amp;quot;완벽한 순환참조&amp;quot; 고리를 이루고 있기에 여전히 남아있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그리고 &lt;code&gt;Map&amp;lt;String, List&amp;lt;WaitingField&amp;gt;&amp;gt; lazyMap&lt;/code&gt; 자료구조는&lt;/p&gt;
&lt;p&gt;사용자가 &lt;code&gt;@MyLazy&lt;/code&gt; 애너테이션 표기한 의존성 대기 리스트를 등록시키는 장소이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;의존성 해결 2 단계&lt;/strong&gt; 프로세스에서 이 맵을 중심으로 행동하게 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이러한 의미인데, Lazy 의존성으로도 해결을 못해? 그렇다면 이 의존성은 3 단계에서 해소한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;singletonContainer.get(&amp;quot;lazyMap key 문자열&amp;quot;)&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;의존성 해결을 미뤘으니 이제 컨테이너에 원하는 의존성 인스턴스가 있지?&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;code&gt;if(instance == null)&lt;/code&gt; ==&amp;gt; 아직도 완성되지 않았다면&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;이 분기에 해당한다면, 3 단계에서 해소한다.&lt;/li&gt;
&lt;li&gt;해당하지 않는다면, &lt;code&gt;List&amp;lt;WaitingField&amp;gt;&lt;/code&gt; 의존성 대기열을 해소하고, 제거한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;라는 의미이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;dependencyTracker&lt;/code&gt; 는 &amp;quot;3 단계 의존성 해소&amp;quot;에서 가장 중요한 역할을 맡게 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;의존성 해결 2단계를 이용한 컴포넌트 예시.&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TB

subgraph LazyMap [&amp;quot;LazyMap&amp;quot;]
    direction TB
    key1(&amp;quot;key : com.damsoon.component.Component1&amp;quot;)
    value1(&amp;quot;value : [ { Field(컴포넌트2) - instance(컴포넌트2) } ]&amp;quot;)

    key1 &amp;lt;--&amp;gt; value1
end

subgraph SingletonContainer [&amp;quot;SingletonContainer&amp;quot;]
    Component1
    Component2
    Component3
    Component4
end

subgraph Component5 [&amp;quot;Component5&amp;quot;]
    Depen-Com5-1(&amp;quot;의존 - Component6&amp;quot;)
end

subgraph Component6 [&amp;quot;Component6&amp;quot;]
    Depen-Com6-1(&amp;quot;의존 - Component5&amp;quot;)
end

Component5 ~~~ Component6&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;의존성 해결 2 단계&lt;/strong&gt; 에서는 &lt;code&gt;Queue - completeQueue&lt;/code&gt; 를 이용하지 않는다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;의존성 해결 1 단계&lt;/strong&gt; 는 &lt;code&gt;while(!completeQueue.isEmpty())&lt;/code&gt; 라는 조건이 있었다.&lt;/p&gt;
&lt;p&gt;즉, 정상적인 의존성 해결이 &amp;quot;완료&amp;quot; 될 때 까지는, 지속적으로 &lt;code&gt;completeQueue.poll&lt;/code&gt; 을 하여&lt;/p&gt;
&lt;p&gt;의존성을 해소하는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, &lt;strong&gt;의존성 해결 2 단계&lt;/strong&gt; 는 다르다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Component2&lt;/code&gt; 는, &lt;code&gt;@MyLazy Component1&lt;/code&gt; 을 선언했다.&lt;/p&gt;
&lt;p&gt;이 의미는, &lt;code&gt;Component2&lt;/code&gt; 에서 &lt;code&gt;Component1&lt;/code&gt; 이라는 의존성 해소를 미룬 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;심지어 &lt;code&gt;dependencyTracker&lt;/code&gt; 는 &lt;code&gt;Component2&lt;/code&gt; 를 가지고 있지 않다.&lt;/p&gt;
&lt;p&gt;왜냐면 &amp;quot;의존성이 완료되었다&amp;quot; 고 인지하기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;모든 의존성 1, 2, 3 단계 해소는 &amp;quot;순회 대상&amp;quot; 이 존재한다.&lt;/p&gt;
&lt;p&gt;그렇다면 의존성 해결 2 단계에서는 어떤 것이 &amp;quot;순회 대상&amp;quot; 이 될까?&lt;/p&gt;
&lt;p&gt;바로, &lt;code&gt;lazyMap.keySet().iterator()&lt;/code&gt; 가 된다. (&lt;code&gt;Iterator&amp;lt;WaitingField&amp;gt; iterator&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@MyLazy&lt;/code&gt; 선언된 의존성은 이미 &amp;quot;완성되어있다&amp;quot; 고 가정한다.&lt;/p&gt;
&lt;p&gt;(물론 완성 안 될 경우, 의존성 3 단계로 던지게 된다.)&lt;/p&gt;
&lt;p&gt;그렇다면, &lt;code&gt;lazyMap&lt;/code&gt; 에 선언된 모든 &lt;code&gt;Key&lt;/code&gt; 값을 가져와서,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;singletonContainer&lt;/code&gt; 를 조회하며 꺼내면 되기 때문이다.&lt;/p&gt;
&lt;p&gt;(말 그대로 완성된 인스턴스라고 판단하기 때문이다.)&lt;/p&gt;
&lt;br/&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Map&amp;lt;String, List&amp;lt;WaitingField&amp;gt;&amp;gt; lazyMap&lt;/code&gt;&lt;ol&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;com.damsoon.component.Component1&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;List [ {컴포넌트2-Field, 컴포넌트2-instance} ]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;우리가 순회하게 될 &lt;code&gt;lazyMap&lt;/code&gt; 의 &lt;code&gt;key&lt;/code&gt; 는 하나이다. (예시)&lt;/p&gt;
&lt;p&gt;따라서, &lt;code&gt;while(iterator.hasNext())&lt;/code&gt; 라는 순회문을 따라,&lt;/p&gt;
&lt;p&gt;한 번의 순회를 실행하게 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;Map&amp;lt;String, Object&amp;gt; singletonContainer&lt;/code&gt; 에서 &lt;code&gt;com.damsoon.component.Component1&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;을 조회한다.&lt;/p&gt;
&lt;p&gt;그렇다면, 컨테이너는 &lt;code&gt;Component1&lt;/code&gt; 을 반환한다.&lt;/p&gt;
&lt;p&gt;그리고 &lt;code&gt;List [ {컴포넌트2-Field, 컴포넌트2-instance} ]&lt;/code&gt; 이 List 에&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Component1&lt;/code&gt; 을 넣어준다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그리고 나서 &lt;code&gt;lazyMap&lt;/code&gt; 에서 &lt;code&gt;com.damsoon.component.Component1&lt;/code&gt; 을 제거한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이렇게 &lt;strong&gt;&amp;quot;의존성 해결 2 단계&amp;quot;&lt;/strong&gt; 는 완료된다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;의존성 해결 2 단계에서 문제가 발생 - Lazy 의존성으로도 해결이 안되는 상황&lt;/h4&gt;
&lt;p&gt;위에서 나는 &lt;code&gt;@MyLazy&lt;/code&gt; 는 개발자가 올바른 아키텍트를 만들도록 유도하며,&lt;/p&gt;
&lt;p&gt;Lazy 처리함으로서 원하는 의존성이 &amp;quot;완성 인스턴스&amp;quot; 가 될 때 까지 기다리고, 이를 드디어 가져오도록 만들었다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;그러나,&lt;/strong&gt; 만약에 사용자가 틀렸다면???&lt;/p&gt;
&lt;p&gt;즉, &lt;code&gt;@MyLazy&lt;/code&gt; 를 사용해도 순환 참조가 해결되지 않는 것이다.&lt;/p&gt;
&lt;p&gt;이 때, &lt;code&gt;while(iterator.hasNext()){ ... }&lt;/code&gt; 순회문에서 &lt;code&gt;continue&lt;/code&gt; 를 사용하여 건너뛴다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;lazyMap&lt;/code&gt; 에서 제거하지 않고 건너뛴다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이 의미는 &amp;quot;의존성 해결 3 단계&amp;quot; 에서 다시 &lt;code&gt;while(iterator.hasNext())&lt;/code&gt; 를 사용하겠다는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@MyLazy&lt;/code&gt; 표식으로 2단계에서 해소되지 못한 의존성은, 3 단계의 &amp;quot;마지막 로직&amp;quot; 에서 해소하게 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;의존성 해소 3단계 진입 시 컴포넌트 예시&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TB

subgraph LazyMap [&amp;quot;LazyMap&amp;quot;]
    empty2(&amp;quot;비어 있음&amp;quot;)
end

subgraph DependencyTracker [&amp;quot;DependencyTracker&amp;quot;]
    Com5(&amp;quot;컴포 5 인스턴스 - 1&amp;quot;)
    Com6(&amp;quot;컴포 6 인스턴스 - 1&amp;quot;)
end

subgraph SingletonContainer [&amp;quot;SingletonContainer&amp;quot;]
    Component1
    Component2
    Component3
    Component4
end

subgraph Component5 [&amp;quot;Component5&amp;quot;]
    Depen-Com5-1(&amp;quot;의존 - Component6&amp;quot;)
end

subgraph Component6 [&amp;quot;Component6&amp;quot;]
    Depen-Com6-1(&amp;quot;의존 - Component5&amp;quot;)
end

Component5 ~~~ Component6&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;내가 의존성 1, 2 단계를 자세히 나열하기 전, 예시로 들은 자료구조의 예시와 지금의 상황이 많이 다르다.&lt;/p&gt;
&lt;p&gt;즉, &lt;code&gt;Component1 ~ 4&lt;/code&gt; 까지의 모든 컴포넌트는 &amp;quot;완벽한 컴포넌트&amp;quot; 가 된 것이다.&lt;/p&gt;
&lt;p&gt;그러나 아직 &lt;code&gt;Component5&lt;/code&gt;, &lt;code&gt;Component6&lt;/code&gt; 는 &amp;quot;완벽한 순환참조&amp;quot; 로서, 해소되지 못했다.&lt;/p&gt;
&lt;p&gt;의존성 해결 1 단계, 2 단계는 각자 기준이 되는 순회문을 가졌다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1 단계 : &lt;code&gt;while(!completeQueue.isEmpty())&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;2 단계 : &lt;code&gt;while(lazyIter.hasNext())&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;그렇다면, 3 단계는 어떤 순회문을 가질까?&lt;/p&gt;
&lt;p&gt;바로 &lt;code&gt;Map&amp;lt;Object, AtomicInteger&amp;gt; dependencyTracker&lt;/code&gt; 자료구조의 &lt;code&gt;Iterator&lt;/code&gt; 를 사용한다.&lt;/p&gt;
&lt;p&gt;그런데, 마지막까지 &lt;code&gt;dependencyMap&lt;/code&gt; 에 남은 &lt;code&gt;Component5&lt;/code&gt;, &lt;code&gt;Component6&lt;/code&gt; 를&lt;/p&gt;
&lt;p&gt;전부 &lt;code&gt;Queue&amp;lt;CompleteObject&amp;gt; completeQueue&lt;/code&gt; 에 넣어놓고,&lt;/p&gt;
&lt;p&gt;의존성 1 단계에서 사용했던 &lt;code&gt;while(!completeQueue.isEmpty()) { ... }&lt;/code&gt; 를 다시 사용한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;아니, 남은 의존성을 모두 완성 인스턴스 취급 할 거면, 큐 말고 &amp;quot;순회&amp;quot; 만 하면 되지 않나?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;나 또한 이러한 의문 앞에서 고민했는데, 그냥 &lt;code&gt;List&amp;lt;CompleteObject&amp;gt;&lt;/code&gt; 지역적 변수를 만들고,&lt;/p&gt;
&lt;p&gt;여기에 모두 추가하고 순회하면 되는 일이 아닌가 고민했다.&lt;/p&gt;
&lt;p&gt;그러나, &lt;code&gt;dependencyMap&lt;/code&gt; 이라는 자료구조가 가진 역할이 매우 약화된다.&lt;/p&gt;
&lt;p&gt;결국 모든 의존성을 &amp;quot;억지로&amp;quot; 해소한다는 데에 다를 바는 없지만,&lt;/p&gt;
&lt;p&gt;&amp;quot;의존성 해소&amp;quot; 라는 과정은 억지스러워서는 아니된다.&lt;/p&gt;
&lt;p&gt;또한 &amp;quot;의존성 해소 1 단계&amp;quot; 의 로직을 재활용하여, 추후 코드를 줄일 생각을 하고 있기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;간단하게 보는 의존성 해소 3 단계&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;Map&amp;lt;Object, AtomicInteger&amp;gt; dependencyTracker&lt;/code&gt; 를 순회한다.&lt;/li&gt;
&lt;li&gt;순회하며 모두 &lt;code&gt;Queue&amp;lt;CompleteObject&amp;gt; completeQueue&lt;/code&gt; 에 넣는다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;while(!completeQueue.isEmpty())&lt;/code&gt; 를 통해 인스턴스를 &lt;code&gt;poll()&lt;/code&gt; 한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;poll()&lt;/code&gt; 된 인스턴스를 통해 &lt;code&gt;dependencyMap&lt;/code&gt; 을 모두 지운다.&lt;/li&gt;
&lt;li&gt;의존성 해소 2 단계(&lt;code&gt;@MyLazy&lt;/code&gt;) 로도 해소되지 못한 의존성을 해소한다.&lt;/li&gt;
&lt;li&gt;이 때 &lt;code&gt;lazyMap.keySet.iterator()&lt;/code&gt; 를 통해 다시 순회한다.&lt;/li&gt;
&lt;li&gt;마지막에서도 lazy 의존성이 해소될 수 없다면, Critical Error 로 프로그램을 종료한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;h4&gt;의존성 해소 3단계 - 마지막까지 해소되지 못한 모든 의존성 해소&lt;/h4&gt;
&lt;p&gt;나는 사용자가 인지하지 못한 &amp;quot;완벽한 순환참조&amp;quot; 형태를 소화 할 수 있도록 만들었지만,&lt;/p&gt;
&lt;p&gt;사용자가 이를 허용 할 것인지, 허용하지 않을 것인지 선택 할 수 있도록 만들었다.&lt;/p&gt;
&lt;p&gt;이 단계에 들어선다는 것은, 완벽한 순환참조 의존성 관계가 존재한다는 것이다.&lt;/p&gt;
&lt;p&gt;따라서, 노란색 경고로 &amp;quot;미해결 된 의존성&amp;quot; 리스트를 출력하고,&lt;/p&gt;
&lt;p&gt;사용자가 순환참조를 &amp;quot;허용&amp;quot; 한다면 3 단계 로직을 실행하고,&lt;/p&gt;
&lt;p&gt;순환참조를 &amp;quot;금지&amp;quot; 한다면, 3 단계 로직에 들어선 순간 &lt;code&gt;RuntimeError&lt;/code&gt; 를 던지고 종료한다.&lt;/p&gt;
&lt;p&gt;나는 사용자가 순환참조를 &amp;quot;허용&amp;quot; 했다는 가정 하에 예시를 들 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;현재 남아있는 의존성은 &lt;code&gt;Component5&lt;/code&gt;, &lt;code&gt;Component6&lt;/code&gt; 이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;dependencyMap&lt;/code&gt;, &lt;code&gt;dependecyTracker&lt;/code&gt; 2 개의 자료구조에 남아있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;첫 번째 : 모든 인스턴스를 Queue 에 넣는다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;의존성 해결 1 단계에서 사용했던 &lt;code&gt;Queue&amp;lt;CompleteQueue&amp;gt;&lt;/code&gt; 에 &lt;code&gt;Component5&lt;/code&gt;, &lt;code&gt;Component6&lt;/code&gt; 에 넣는다.&lt;/p&gt;
&lt;p&gt;그렇다면, Queue 는 이러한 형태가 된다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;add&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;poll&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Component6&lt;/td&gt;
&lt;td&gt;Component5&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;두 번째 : Queue 의 모든 인스턴스를 의존성 1 단계 해소 로직처럼 해결한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;다른 점은, 의존성 1 단계와 달리, &lt;code&gt;Queue&lt;/code&gt; 에 새로운 인스턴스가 &lt;code&gt;add&lt;/code&gt; 될 일은 없다는 것이다.&lt;/p&gt;
&lt;p&gt;단, 인스턴스 하나를 Queue 에서 꺼내서 &lt;code&gt;dependencyMap&lt;/code&gt; 과 연계되는 것은 동일하다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;List&amp;lt;WaitingField&amp;gt;&lt;/code&gt; 를 뽑아서, 의존성을 해소한다. 그리고, &lt;code&gt;dependencyMap&lt;/code&gt; 에서 제거한다.&lt;/p&gt;
&lt;p&gt;그리고 해당 인스턴스를 &lt;code&gt;singletonContainer&lt;/code&gt; 에 넣는다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;세 번째 : Map&amp;lt;String, List&lt;WaitingField&gt;&amp;gt; 를 다시 순회한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;의존성 해결 2 단계에서, 개발자조차 예상하지 못한 의존성을 마무리하는 단계이다.&lt;/p&gt;
&lt;p&gt;즉, &lt;code&gt;@MyLazy&lt;/code&gt; 애너테이션을 작성하여 스스로 순환 참조를 다스리고자 했으나,&lt;/p&gt;
&lt;p&gt;예상치 못한 구조로 인해 &amp;quot;완벽한 순환참조&amp;quot; 가 된 경우에 해당한다.&lt;/p&gt;
&lt;p&gt;즉, &lt;code&gt;lazyMap&lt;/code&gt; 의 &lt;code&gt;iterator&lt;/code&gt; 를 다시 돌린다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;만약에 이조차도 통하지 않는다면.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Critical Error 로서 에러문을 출력하고, 프로그램을 종료한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;모든 의존성 해결 단계에서 고려되는 Proxy 적용 타이밍 - 코드를 바로 보여주지 않은 이유.&lt;/h3&gt;
&lt;p&gt;내가 굳이 코드를 바로 보여주지 않고 &amp;quot;글&amp;quot; 로서 Logic 을 설명한 이유가 여기에 있다.&lt;/p&gt;
&lt;p&gt;Proxy API 를 완성된 인스턴스에 적용하는 과정이 난해하여,&lt;/p&gt;
&lt;p&gt;코드를 곧장 보게 된다면, 이해되지 않기 때문이다.&lt;/p&gt;
&lt;p&gt;그리고 Proxy API 적용으로 인해 코드 양이 늘어났을 뿐만 아니라,&lt;/p&gt;
&lt;p&gt;의존성 해결 1, 2, 3 단계에서 공통으로 사용되는 Logic 을 Method 로 분리시키는 과정이 존재하여&lt;/p&gt;
&lt;p&gt;코드를 본다 해도 &amp;quot;감으로 이해&amp;quot; 할 뿐이지, &amp;quot;완전한 이해&amp;quot; 는 불가능하기 때문이다.&lt;/p&gt;
&lt;p&gt;즉, &amp;quot;순수한 의존성 해결 로직&amp;quot; 에 초점이 맞춰지지 않기 때문이었다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;왜? 프록시 적용 타이밍 때문에 코드가 난해하다는 것인가?&lt;/h4&gt;
&lt;p&gt;만약에 여기까지 글을 읽었다면, 정말 대단한 분이라는 찬사를 날림과 동시에,&lt;/p&gt;
&lt;p&gt;이러한 질문을 드리고 싶다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;왜 완성된 인스턴스에만 Proxy API 를 적용하는 것인가?&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;답은, 원본 객체와, Proxy API 로 생성된 객체는, 완전히 다른 존재이기 때문이다.&lt;/p&gt;
&lt;p&gt;원본 객체에 변수 2 개가 있다고 가정하자.(&lt;code&gt;Object origin&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;그리고, 이 객체를 참조하여 탄생한 프록시 객체가 있다고 가정하자. (&lt;code&gt;Object proxyObj&lt;/code&gt;)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;origin&lt;/code&gt; 에서 &lt;code&gt;Field&lt;/code&gt; (필드 변수) 를 꺼내서 변경할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;proxyObj&lt;/code&gt; 에서는 &lt;code&gt;origin&lt;/code&gt; 이 가지고 있던 &lt;code&gt;Field&lt;/code&gt; 가 존재하지 않는다.&lt;/p&gt;
&lt;p&gt;따라서, &lt;code&gt;proxyObj&lt;/code&gt; 에서 &lt;code&gt;origin&lt;/code&gt; 의 &lt;code&gt;Field&lt;/code&gt; 를 변경 할 수 없다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이를 축약하자면, 프록시 객체가 되는 순간, 원본 객체의 Field 를 프록시 객체에서 가져올 수 없다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;나는 클래스 메타데이터를 다루면서, 가짜 의존성들(&lt;code&gt;null&lt;/code&gt;) 로만 가득찬 인스턴스에,&lt;/p&gt;
&lt;p&gt;곧장 Proxy API 를 적용하려고 했다.&lt;/p&gt;
&lt;p&gt;그러나 그것은 불가능했는데, 위에서 언급했듯 Proxy 객체가 되는 그 순간부터, 원본 객체의 &lt;code&gt;Field&lt;/code&gt; 를 직접 가져 올 수 없었다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Field&lt;/code&gt; 에 직접 객체 주소를 주입하여 의존성을 해소해야 하는데, Proxy 객체가 되는 그 순간부터,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Field&lt;/code&gt; 에 접근할 수가 없으니, 의존성이 해소가 되지 않는 것이다.&lt;/p&gt;
&lt;p&gt;이 사실에 나는 &lt;strong&gt;&amp;quot;인스턴스가 완전 해 지는 순간&amp;quot;&lt;/strong&gt; (&lt;code&gt;CompleteObject&lt;/code&gt; 되기 직전)&lt;/p&gt;
&lt;p&gt;문자열 Name Tag 는 (예시) &lt;code&gt;com.damsoon.component.Component3&lt;/code&gt; 이지만,&lt;/p&gt;
&lt;p&gt;실제 객체의 문자열 Name Tag (예시) 는 &lt;code&gt;xxx.Proxy6$&lt;/code&gt; 가 되는 것이다.&lt;/p&gt;
&lt;p&gt;여기서 &lt;code&gt;Component3&lt;/code&gt; 와 &lt;code&gt;Proxy6$&lt;/code&gt; 와의 공통점은, &lt;code&gt;interface&lt;/code&gt; 하나밖에 없다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, 반드시 알아야 할 Proxy 의 구조가 존재한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart BT

subgraph Proxy [&amp;quot;Proxy Object - Instance&amp;quot;]
    Origin(&amp;quot;Origin Object - Instance&amp;quot;)

end

Access1(&amp;quot;접근법 1 - &amp;lt;br/&amp;gt; Proxy 객체 Field 변경&amp;quot;)

Access2(&amp;quot;접근법 2 - &amp;lt;br/&amp;gt; Origin 객체 Field 변경&amp;quot;)

Access1 --Proxy 는 필드가 없으므로 변경 불가--&amp;gt; Proxy

Access2 --Origin 은 필드가 당연히 있으므로 변경 가능--&amp;gt; Origin&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;위에서 다룬 Proxy 와 Origin 인스턴스는, 같은 계층의 데이터로서 바라보았다.&lt;/p&gt;
&lt;p&gt;Proxy 과정을 겪은 객체는 더 이상 변경 할 수 없다.&lt;/p&gt;
&lt;p&gt;그러나, Origin 인스턴스를 저장한다면, Proxy 로 감싸져 있어도 변경할 수 있다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;이러한 사실 속에서 나는 의존성이 완전히 해소된 완벽한 인스턴스가 될 때,&lt;/p&gt;
&lt;p&gt;그제서야 &lt;code&gt;Proxy API&lt;/code&gt; 를 적용하게 된 것이다.&lt;/p&gt;
&lt;p&gt;의존성 해결 1, 2, 3 단계는 모두 &amp;quot;Proxy 적용 Logic&amp;quot; 이 포함되어 있어 클린 코드가 되지 않았다.&lt;/p&gt;
&lt;h4&gt;잠시만, 나는 바본가? 왜 이걸 생각을 못 했지?&lt;/h4&gt;
&lt;p&gt;내가 먼저 의존성 해결 로직을 작성하고,&lt;/p&gt;
&lt;p&gt;그 다음 Proxy 적용 로직을 작성하고 구성해서 그런 걸 지도 모르는데,&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;의존성이 모두 해결 된 다음&amp;quot;&lt;/strong&gt; 에야 원본 객체를 프록시 객체로 만들고 등록했다.&lt;/p&gt;
&lt;p&gt;그런데 나는 바로 위에서 그래프까지 보여가며, &lt;/p&gt;
&lt;p&gt;Proxy 객체가 되어도, Origin 객체에 대한 주소만 존재한다면, 수정이 가능하다고 했다.&lt;/p&gt;
&lt;p&gt;그렇다면, 일단 먼저 생성된 미완성 인스턴스에 Proxy 를 적용하여 Proxy 인스턴스를 만들고,&lt;/p&gt;
&lt;p&gt;원본 객체와 매핑되는 Proxy 인스턴스로 자료구조를 만들면 되는 게 아닌가?&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Map&amp;lt;Object, CompleteObject&amp;gt; proxyContainer&lt;/code&gt; 를 미리 만들고 사용한다면,&lt;/p&gt;
&lt;p&gt;40 줄 정도의 코드를 약 8 줄 정도로 줄일 수 있다는 것이다.&lt;/p&gt;
&lt;p&gt;반복되는 코드를 줄이고 싶었는데, 매우 잘 되었다고 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;의존성 해결 로직 Code&lt;/h3&gt;
&lt;h4&gt;의존성 데이터를 자료구조로 변환하기&lt;/h4&gt;
&lt;p&gt;각 인스턴스들의 의존성을 해결하기 위해서는,&lt;/p&gt;
&lt;p&gt;자료구조들의 초기화가 필요하다.&lt;/p&gt;
&lt;p&gt;모든 메타데이터를 읽으며, 적재 적소에 의도했던 대로 적층 한 뒤,&lt;/p&gt;
&lt;p&gt;&amp;quot;의존성 해소&amp;quot; 라는 단계에 들어설 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class ResolveDependency {
    // 처음에 순회하며 생성자에서 필요로 하는 의존성과 해당 인스턴스를 줄세운다.
        private Map&amp;lt;String, List&amp;lt;WaitingField&amp;gt;&amp;gt; dependencyMap;
        // 의존성이 없어진 완료 인스턴스는 큐에 넣고 뽑아서 &amp;quot;dependencyMap&amp;quot; 에 자신의 패키지에 해당하는 의존성을 해결해 준다.
        private Queue&amp;lt;CompleteObject&amp;gt; completeQueue;
        // 해당 클래스 인스턴스에 필요한 &amp;quot;의존성의 수&amp;quot; 를 저장한다. --&amp;gt; Detecting 용도
        // Integer 가 아닌 AtomicInteger 를 사용한 이유는, Map 의 put, get 을 한 번에 사용하는 비효율을 피하기 위함이다.
        private Map&amp;lt;Object, AtomicInteger&amp;gt; dependencyTracker;

        // @MyLazy 가 붙었을 경우를 상정한다.
        // 일반적인 의존성을 해소 한 후, 이를 해소한다.
        private Map&amp;lt;String, List&amp;lt;WaitingField&amp;gt;&amp;gt; lazyMap;
        // 의존성을 해소 해 준 &amp;quot;완성된 컴포넌트&amp;quot; 는 처리가 끝난 후 컨테이너에 저장한다.
        private Map&amp;lt;String, Object&amp;gt; singletonContainer;

        // key : 원본 객체, value : (String, Object)
        private Map&amp;lt;Object, CompleteObject&amp;gt; proxyContainer;

        List&amp;lt;Class&amp;lt;?&amp;gt;&amp;gt; clazzList;

        boolean isAllowCircular;

    // ...

    // 의존성 해결 부문의 Main 역할 메서드 
    public void resolveStart() {
        // 먼저 클래스 리스트를 순회한다.
        // 이 과정에서 프록시를 적용한다.

        // 모든 클래스 메타데이터를 대상으로 순회한다.
        Iterator&amp;lt;Class&amp;lt;?&amp;gt;&amp;gt; clazzIterator = this.clazzList.iterator();
        while(clazzIterator.hasNext()) {
            Class&amp;lt;?&amp;gt; clazz = clazzIterator.next();

            // MyComponent 선언된 애너테이션인지 확인.
            MyComponent isComponent = clazz.getDeclaredAnnotation(MyComponent.class);

            // 만약 컴포넌트가 아니라면 건너뛴다. (스캔 대상이 아니다.)
            if(isComponent == null) {
                continue;
            }

            // 클래스 메타데이터의 패키지 이름을 가져온다. (EX - &amp;quot;com.damsoon.component.Component1&amp;quot;)
            String keyName = clazz.getName();

            // MyAutowired 혹은 Default 생성자를 가져온다. --&amp;gt; Spring 과 동일한 규칙을 적용한다.
            Constructor&amp;lt;?&amp;gt; constructor = this.getRequireConstructor(clazz);
            // MyAutowired 된 객체의 Field 를 ParamMetadata 형태로 가공하여 반환한다. --&amp;gt; 추후 타입의 일치 때문.
            ParamMetadata[] dependencyFields = this.getRequireFields(clazz);

            // 객체 애너테이션, 생성자 파라미터 애너테이션 추출 완료.
            ClassMetadata clazzInfo = this.getDataInClass(clazz, constructor);

            // 지정된 생성자의 파라미터 수를 의미한다.
            int paramCount = constructor.getParameterCount();

            // &amp;quot;모든&amp;quot; 의존성의 수를 의미한다.
            // 지정된 생성자 뿐만 아니라, 객체의 필드에서 요구하는 의존성의 수도 더하여 판단한다.
            int dependencyCount = paramCount + dependencyFields.length;

            // 인자가 몇 개이던, &amp;quot;null&amp;quot; 로 의존성을 채운 &amp;quot;가짜 의존성&amp;quot; 으로 채운 인스턴스를 가져온다.
            Object instance = this.createInstance(paramCount, constructor, clazzInfo);

            // clazz 에 붙은 애너테이션을 Mapping 했다.
            // 단순히 &amp;quot;getDeclaredAnnotation&amp;quot; 으로 가져온다면, 여기서 @NonNull 에 대한 처리를 해야 했었다.
            // 따라서 &amp;quot;ClassMetadata&amp;quot; 생성자에서 이를 매핑화 하는 과정이 존재한다.
            Map&amp;lt;Class&amp;lt;? extends Annotation&amp;gt;, Annotation&amp;gt; clazzAnno = clazzInfo.getClazzAnnotationMap();

            // 미리 이 객체에 붙어있는 프록시 정보를 가져온다. --&amp;gt; 코드 단축화를 위함
            MyProxy proxy = (MyProxy) clazzAnno.get(MyProxy.class);
            MyProxies proxies = (MyProxies) clazzAnno.get(MyProxies.class);

            // 인스턴스가 생성되지마자, 프록시를 적용한다.
            // proxyInstance 는 프록시 유무에 따라 instance 와 같을 수도, 다를 수도 있다.
            Object proxyInstance = instance;
            if(proxy != null || proxies != null) {
                proxyInstance = this.proxyProcess(proxy, proxies, instance, clazz);
                keyName = proxy != null ? proxy.targetInterface().getName() : proxies.targetInterface().getName();
            }

            // 만약 프록시가 없다면, instance 주소 == proxyInstance 주소 이며, 둘은 동일한 인스턴스이다.
            // 만약 프록시가 있다면, instance 주소 != proxyInstance 주소이며, 둘은 다른 인스턴스이다.
            this.proxyContainer.put(instance, new CompleteObject(keyName, proxyInstance));

            // 위에서 추출된 instance 가 어떠한 의존성도 필요 없었을 경우, 곧바로 Queue 로 넣는다.(이미 완성된 객체)
            if(dependencyCount == 0) {
                // 등록되어야 할 패키지 문자열과 동시에, 프록시 or 원본 인스턴스를 넣어 완성한다.

                CompleteObject completeObject = this.proxyContainer.get(instance);
                this.completeQueue.add(completeObject);
            } else {

                // &amp;quot;생성자&amp;quot; 에 존재하는 인자의 데이터를 가져온다.
                ParamMetadata[] paramMetadatas = clazzInfo.getParamDataList();

                // &amp;quot;생성자&amp;quot; 의 의존성과, &amp;quot;클래스 필드&amp;quot; 의존성을 하나의 배열로 합친다.
                List&amp;lt;ParamMetadata&amp;gt; dependencyMetadataList = this.appendList(paramMetadatas, dependencyFields);
                ParamMetadata[] dependencyMetadatas = dependencyMetadataList.toArray(new ParamMetadata[0]);

                // 이 객체의 필요 의존성들을 등록한다.
                // registerDependency 내부에서 dependencyMap, lazyMap 에 데이터를 넣는다.
                // 반환값으로는 모든 의존성을 순회하면서, 감지된 &amp;quot;@MyLazy&amp;quot; 의 개수를 반환한다.
                int lazyCount = this.registerDependency(dependencyMetadatas, clazz, instance);

                // 일반 의존성의 수를 구하기 위해, &amp;quot;모든 의존성&amp;quot; - &amp;quot;lazy 의존성&amp;quot; 한다.
                dependencyCount -= lazyCount;

                // 만약 일반 의존성이 존재하지 않는다면,
                if(dependencyCount == 0) {
                    CompleteObject completeObject = this.proxyContainer.get(instance);

                    // 존재하는 의존성이 모두 Lazy 라면, 해당 의존성들은 모두 해결 된 것으로 보아야 한다.
                    this.completeQueue.add(completeObject);
                } else {
                    // 필요 의존성 수를 저장하는 맵에 등록한다.
                    // String key 가 아니라, Object 자체로 비교하기 때문에, proxy 를 고려하지 않아도 된다.
                    this.dependencyTracker.put(instance, new AtomicInteger(dependencyCount));
                }

            }
        }
    }
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h4&gt;코드보다 이해되는 그래프 예시&lt;/h4&gt;
&lt;p&gt;주석을 꼼꼼히 적었는데, 이해가 되지 않을 수 있다.&lt;/p&gt;
&lt;p&gt;어쨌던 본인이 작성한 코드가 아니라, 나만의 Convention 이 들어갔기 때문이다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;자료구조의 세분화&lt;/li&gt;
&lt;li&gt;분기문의 의도&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 2 개가 쟁정일 거라고 생각한다.&lt;/p&gt;
&lt;p&gt;먼저, 하나의 메타데이터가 &amp;quot;어떻게 다양한 자료구조로 (치환)&amp;quot; 되는지 흐름을 작성 해 보겠다.&lt;/p&gt;
&lt;p&gt;이를 보고 위의 코드를 보면 훨씬 이해가 쉬울 것이다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TB

subgraph Clazz [&amp;quot;변수명 - clazz&amp;quot;]
    Clazz-1(&amp;quot;단일 클래스 메타데이터&amp;quot;)
end

Clazz(&amp;quot;단일 클래스 메타데이터 - clazz&amp;quot;)

IsComponent[&amp;quot;@MyComponent 가 붙었는가?&amp;quot;]

Continue1(&amp;quot;건너뜀&amp;quot;)

Clazz --&amp;gt; IsComponent

IsComponent --붙지 않음--&amp;gt; Continue1

ToInstance(&amp;quot;인스턴스가 된다.&amp;quot;)

IsComponent --붙었음--&amp;gt; ToInstance

Instance(&amp;quot;인스턴스 와 클래스 메타데이터&amp;quot;)

ToInstance --&amp;gt; Instance

subgraph ProxyContainer [&amp;quot;ProxyContainer&amp;quot;]
    direction TB
    ProxyContainer-1(&amp;quot;구조 &amp;lt;br/&amp;gt; Map(Object, CompleteObject)&amp;quot;)
    ProxyContainer-2(&amp;quot;변수명 : proxyContainer&amp;quot;)
    ProxyContainer-1 ~~~ ProxyContainer-2
end

subgraph ProxiedObject [&amp;quot;@MyProxy or @MyProxies&amp;quot;]
    ProxiedObject-Key(&amp;quot;Key : 원본 인스턴스 주소&amp;quot;)
    ProxiedObject-Value(&amp;quot;Value : 프록시 인스턴스 정보&amp;quot;)
end

subgraph OriginObject [&amp;quot;프록시 적용 안된 인스턴스&amp;quot;]
    OriginObject-Key(&amp;quot;Key : 원본 인스턴스 주소&amp;quot;)
    OriginObject-Value(&amp;quot;Value : 원본 인스턴스 정보&amp;quot;)
end

Instance --IF 프록시 적용--&amp;gt; ProxiedObject
Instance --IF 프록시 미적용--&amp;gt; OriginObject

ProxiedObject --저장--&amp;gt; ProxyContainer 
OriginObject --저장--&amp;gt; ProxyContainer

DependencyCount{&amp;quot;모든 종류의 의존성 여부&amp;quot;}

DependencyCount1[&amp;quot;모든 의존성이 없다면&amp;quot;]
DependencyCount2[&amp;quot;어떠하든 의존성이 존재한다면&amp;quot;]

Instance ---&amp;gt; DependencyCount

DependencyCount --&amp;gt; DependencyCount1
DependencyCount --&amp;gt; DependencyCount2

subgraph CompleteQueue [&amp;quot;CompleteQueue&amp;quot;]
    CompleteQueue-1(&amp;quot;일반 의존성이 해결된 인스턴스가 들어간다.&amp;quot;)
    CompleteQueue-2(&amp;quot;Queue(CompleteObject)&amp;quot;)
end

DependencyCount1 --&amp;gt; CompleteQueue

RegisterDependency(&amp;quot;클래스에 존재하는 모든 의존성을 등록한다.&amp;quot;)

DependencyCount2 --&amp;gt; RegisterDependency

subgraph RegisterSector [&amp;quot;의존성 등록 자료구조들&amp;quot;]
    subgraph DependencyMap [&amp;quot;DependencyMap&amp;quot;]
        DependencyMap-1(&amp;quot;일반 의존성 대기맵&amp;quot;)
        DependencyMap-2(&amp;quot;Map(String, List(WaitingField))&amp;quot;)
    end

    subgraph LazyMap [&amp;quot;LazyMap&amp;quot;]
        LazyMap-1(&amp;quot;@MyLazy 의존성 대기맵&amp;quot;)
        LazyMap-2(&amp;quot;Map(String, List(WaitingField))&amp;quot;)
    end
end



RegisterDependency --일반 의존성일 경우--&amp;gt; DependencyMap 
RegisterDependency --Lazy 의존성일 경우--&amp;gt; LazyMap

DependencyCount3[&amp;quot;일반 의존성이 존재하지 않을 경우&amp;quot;]
DependencyCount4[&amp;quot;일반 의존성이 존재할 경우&amp;quot;]


Gather(&amp;quot;이후의 과정&amp;quot;)

RegisterSector --&amp;gt; Gather

Gather --&amp;gt; DependencyCount3
Gather --&amp;gt; DependencyCount4

subgraph CompleteQueue1 [&amp;quot;CompleteQueue&amp;quot;]
    CompleteQueue-3(&amp;quot;일반 의존성이 해결된 인스턴스가 들어간다.&amp;quot;)
    CompleteQueue-4(&amp;quot;Queue(CompleteObject)&amp;quot;)
end

subgraph DependencyTracker [&amp;quot;DependencyTracker&amp;quot;]
    DependencyTracker-1(&amp;quot;Key : 인스턴스, Value : 필요 의존성 수&amp;quot;)
    DependencyTracker-2(&amp;quot;각 인스턴스가 몇 개의 의존성을 필요로 하는지 기록한다.&amp;quot;)
end

DependencyCount3 --&amp;gt; CompleteQueue1
DependencyCount4 --&amp;gt; DependencyTracker
&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;이 흐름도를 보고, &amp;quot;한 번의 순회&amp;quot; 과정 속에서,&lt;/p&gt;
&lt;p&gt;어떻게 다양한 자료구조로 치환되고 저장되는지 &amp;quot;의도&amp;quot; 를 알 수 있을 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;여기서 &lt;code&gt;CompleteObject&lt;/code&gt; 자료구조, &lt;code&gt;registerDependency&lt;/code&gt; 메서드를 따로 만들어 두었는데,&lt;/p&gt;
&lt;p&gt;이는 책임의 분리, 클린 코드를 지향하기 위함이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CompleteObject&lt;/code&gt; : &lt;code&gt;String-이 인스턴스의 의존성 네임 태그&lt;/code&gt;-&lt;code&gt;Object-인스턴스 객체&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;registerDependency&lt;/code&gt; &lt;ul&gt;
&lt;li&gt;&lt;code&gt;DependencyMap&lt;/code&gt;, &lt;code&gt;LazyMap&lt;/code&gt; 자료구조에 의존성을 등록한다.&lt;/li&gt;
&lt;li&gt;반환값으로, 이 객체가 요구하는 &lt;code&gt;@MyLazy&lt;/code&gt;, &lt;code&gt;Lazy&lt;/code&gt; 의존성 개수를 가져온다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;h4&gt;의존성 해결 1 단계 (일반 의존성 해소하기) Code&lt;/h4&gt;
&lt;p&gt;그래도 본격적으로 코드를 보기 시작했으니, 의존성 해소 1 단계의 코드 또한 잘 이해 할 수 있으리라 생각한다.&lt;/p&gt;
&lt;p&gt;위에서는 코드로 표현하지 않고, &lt;strong&gt;&amp;quot;컴포넌트 예시&amp;quot;&lt;/strong&gt; 를 통해 1, 2, 3 단계를 묘사했다.&lt;/p&gt;
&lt;p&gt;이제 코드로 만나 볼 시간이다.&lt;/p&gt;
&lt;p&gt;위에서 예시로 보여주었던 &amp;quot;컴포넌트 예제&amp;quot; 를 생각하며 보면 좀 더 쉽게 이해 할 수 있다.&lt;/p&gt;
&lt;br/&gt; 

&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class ResolveDependency {
    // 자료구조들..

    // ...

    public void resolveStart() {

        // 위에서 실행했던 로직을 생략 - 그 직후이다.

        // &amp;quot;의존성 해소 1 단계&amp;quot;
        while(!this.completeQueue.isEmpty()) {
            // 완성되어 있는 인스턴스 객체 하나를 큐에서 꺼낸다.
            CompleteObject completeObject = this.completeQueue.poll();

            // 완성 인스턴스의 전체 이름 - key
            String fullName = completeObject.getFullName();
            // 완성 인스턴스. - 대기중인 필드들에 꽂아줄 예정 - (원본 혹은 프록시)
            Object instance = completeObject.getObject();

            // 이 인스턴스를 받기 위해 대기중인 (인스턴스-필드) 배열을 추출한다.
            // 일반 의존성에 해당한다.
            List&amp;lt;WaitingField&amp;gt; waitingFieldList = this.dependencyMap.get(fullName);

            // 일반 의존성으로서 이 인스턴스를 원하는 객체가 없다면, 바로 컨테이너에 넣는다.
            // 다음 순회문으로 건너뛴다.
            if(waitingFieldList == null) {
                this.singletonContainer.put(fullName, instance);
                continue;
            }

            // 여기부턴, dependencyMap(일반 의존성) 에서 자신을 기다리는 인스턴스가 존재 할 경우.

            // 일반 의존성 대기 &amp;quot;리스트&amp;quot; 에서 Iterator 를 따로 추출한다.
            Iterator&amp;lt;WaitingField&amp;gt; waitingIter = waitingFieldList.iterator();

            // 순회하며 의존성을 넣어준다.
            this.insertRealInstance(waitingIter, instance, DependencyMode.NORMAL);

            // fullName(com.damsoon.component.xxx) 를 필요로 하는 모든 인스턴스에
            // 의존성을 &amp;quot;실제로&amp;quot; 넣어줬으므로, 일반 의존성 대기 Map 에서 제거한다.
            this.dependencyMap.remove(fullName);

            // 이 의존성은 &amp;quot;모든 의존성&amp;quot; 을 만족시켜주었으므로, 진정한 컨테이너 자료구조에 들어간다.
            this.singletonContainer.put(fullName, instance);
        }

        // 의존성 해소 2 단계
        // 
        // 의존성 해소 3 단계
    }
    // ...
    // 가짜 의존성으로 생성된 인스턴스가 대기 맵을 통해 하나씩 실제 의존성을 가지게 된다.
    // 만약 모든 의존성이 갖춰진 인스턴스로 변모한다면, 완성 인스턴스 큐에 등록한다.
    private void insertRealInstance(Iterator&amp;lt;WaitingField&amp;gt; iterator, Object realInstance, DependencyMode mode) {
        while(iterator.hasNext()) {
            WaitingField waitingField = iterator.next();

            // 대기중인 인스턴스와 해당되는 필드를 쌍으로 저장하고 있는 형태이다.
            Field targetField = waitingField.getField();
            Object targetObject = waitingField.getObject();
            Class&amp;lt;?&amp;gt; targetClazz = waitingField.getClazz();
            String keyName = targetClazz.getName();

            // 이러한 변수를 &amp;quot;런타임&amp;quot; 중에 바꿔주기 위해서는, 엄격한 규칙으로 인해
            // &amp;quot;인스턴스&amp;quot; 가 중심이 아니라, &amp;quot;필드&amp;quot; 가 직접 주체가 되어 변경된다.
            targetField.setAccessible(true);

            // 필드 주체의 접근 상황에서 에러가 날 수 있으므로 따로 try-catch 문법으로 나눈다.
            try {
                targetField.set(targetObject, realInstance);
            } catch(Exception e) {
                System.out.println(
                        ColorText.red(
                        &amp;quot;[Field Access Error - IllegalAccess] : &amp;quot;
                                + &amp;quot;targetObject : &amp;quot; + keyName
                                + &amp;quot;, realInstance : &amp;quot; + realInstance.getClass().getName()
                        )
                );
                e.printStackTrace();
            }

            // 일반 의존성 해소(의존성 해소 1 단계) 에서만 실행된다.
            if(mode == DependencyMode.NORMAL) {
                // detectDependency 메서드를 통해 &amp;quot;타겟 인스턴스&amp;quot; 의 의존성이 해소되었는지 확인한다.
                // dependencyTracker 자료구조에서 targetObject 에 대한 의존성 수를 -1 하고, 의존성이 남아있는지 없는지 bool 값으로 반환한다.
                boolean isRemainDependency = this.detectDependency(targetObject);

                // 의존성이 모두 해소되었다면, 완성 인스턴스 큐에 새로 추가한다.
                if(!isRemainDependency) {
                    // 의존성 해소 인스턴스가 프록시 적용되어 있었다면, 해당 프록시 객체로 대신한다.
                    // 프록시 미적용이라면, 동일한 인스턴스가 추출된다.
                    CompleteObject completeObject = this.proxyContainer.get(targetObject);
                    this.completeQueue.add(completeObject);
                }
            }

        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;메타데이터를 분석하며 초기화 된 자료구조들을 이용하여 의존성을 해결하게 되는데,&lt;/p&gt;
&lt;p&gt;그 중, &lt;code&gt;insertRealInstance&lt;/code&gt; 라는 메서드를 여기서 보여준 이유가 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;의존성 해소 1, 2, 3 단계에서 모두 사용되는 메서드이다. - 재사용 메서드&lt;/li&gt;
&lt;li&gt;인자로 주는 &lt;code&gt;Enumeration&lt;/code&gt; 인 &lt;code&gt;DependencyMode&lt;/code&gt; 에 따라, 분기가 나뉜다.&lt;ul&gt;
&lt;li&gt;&lt;code&gt;DependencyMode.NORAML&lt;/code&gt;, &lt;code&gt;.LAZY&lt;/code&gt;, &lt;code&gt;.REST&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;클래스에서 &amp;quot;생성자 인자&amp;quot; 로 적어놓은 &amp;quot;인자 이름&amp;quot;과, &amp;quot;필드 변수 이름&amp;quot; 이 무조건 동일해야 한다.&lt;ul&gt;
&lt;li&gt;완벽한 순환참조도 해결하는 로직을 작성하는 과정에서 결정된 &amp;quot;어쩔 수 없는 제약&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;detectDependency&lt;/code&gt; 를 통해, 인스턴스의 의존성 개수를 수정하고, 해소되었는지 확인한다.&lt;ul&gt;
&lt;li&gt;&lt;code&gt;detectDependency&lt;/code&gt; 에서 &lt;code&gt;DependencyTracker&lt;/code&gt; 를 접근하고 수정한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;h4&gt;의존성 해소 2 단계 (@MyLazy 의존성 해소하기) Code&lt;/h4&gt;
&lt;p&gt;의존성 해소 1 단계를 통해서, &amp;quot;일반적인 경우의 의존성&amp;quot; 그래프는 모두 해소되어 &lt;/p&gt;
&lt;p&gt;&lt;code&gt;singletonContainer&lt;/code&gt; 로 들어 간 상황이다.&lt;/p&gt;
&lt;p&gt;우리는 여기서 &amp;quot;사용자가 &lt;code&gt;@MyLazy&lt;/code&gt; 를 의존성에 표기&amp;quot; 한 이유에 대해서 생각 해 보아야 한다.&lt;/p&gt;
&lt;p&gt;사용자가 이 애너테이션을 의존성에 표기했다는 것은,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;스스로 현재 구조가 순환참조임을 인지하고 있다.&lt;/li&gt;
&lt;li&gt;이 순환참조 구조를 깨뜨리기 위해서 &lt;code&gt;@MyLazy&lt;/code&gt; 를 사용한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이다.&lt;/p&gt;
&lt;p&gt;즉, &lt;code&gt;@MyLazy TestComponent testComponent&lt;/code&gt; 라는 &amp;quot;인자&amp;quot; 나 &amp;quot;필드 변수&amp;quot; 가 존재했다면,&lt;/p&gt;
&lt;p&gt;그건 개발자가 &lt;/p&gt;
&lt;p&gt;&amp;quot;&lt;code&gt;testComponent&lt;/code&gt; 만 &amp;#39;나중에&amp;#39; 가져오면, 순환 참조는 해결된다.&amp;quot; 라고 말한 것과 동일하다.&lt;/p&gt;
&lt;p&gt;당연하겠지만, 개발자도 사람이므로, 실수 할 경우를 상정해야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class ResolveDependency {
    // 자료구조들..

    // ...

    public void resolveStart() {

        // 위에서 실행했던 로직을 생략 - 그 직후이다.

        // &amp;quot;의존성 해소 1 단계&amp;quot;


        // 의존성 해소 2단계 - @MyLazy 의존성 해소
        // 만약 lazy 로 등록한 필요 의존성이 singletonContainer 에 존재하지 않는다면
        // 의존성 해소 3 단계 로 넘어간다.
        if(!this.lazyMap.isEmpty()) {
            this.resolveLazyDependency();
        }


        // 의존성 해소 3 단계
    }
    // ...

    /**
        * 의존성 해결 2 단계.
        * @MyLazy 표기된 의존성들을 해결하는 과정이다.
        *
        * @MyLazy 애너테이션을 사용자가 표기했다는 것은, 이를 사용함으로서 순환참조가 해결된다는 것이다.
        * 그 의미로, 의존성 해결 1 단계에서 @MyLazy 의존성 수를 &amp;quot;제거&amp;quot; 했었다.
        *
        * 의존성 해결 1 단계에서는 &amp;quot;완성된 객체&amp;quot; 를 Queue 에 넣고 poll 하면서 순회했다면,
        * 의존성 해결 2 단계에서는 lazyMap 에 등록된 &amp;quot;key&amp;quot; 가 컨테이너에 존재한다고 가정한다.
        * 따라서, lazyMap 키를 순회하며 컨테이너에서 객체를 가져온다.
        */
    private void resolveLazyDependency() {
        // 등록된 &amp;quot;모든&amp;quot; lazy 등록 의존성은 &amp;#39;일반 의존성&amp;#39; 이 해소 된 후에 진행한다.

        Iterator&amp;lt;String&amp;gt; lazyIter = this.lazyMap.keySet().iterator();

        while(lazyIter.hasNext()) {
            String lazyKey = lazyIter.next();

            // 완성 컨테이너에서 나중에 가져오도록 지정된 오브젝트가 완성 된 상태인지 확인한다.
            Object instance = this.singletonContainer.get(lazyKey);

            // 만약 의존성을 &amp;quot;@MyLazy&amp;quot; 지정해놨는데도 원하는 인스턴스가 완성되지 않았다면, 개발자가 의도하지 않은 오류에 해당한다.
            // 따라서 의존성 해결 3 단계(resolveCircularDependency) 로 넘기기에,
            // 이에 대한 경고를 출력한다.
            if(instance == null) {
                System.out.println(ColorText.red(&amp;quot;[Exception Lazy in Dependency] : &amp;quot;));
                System.out.println(ColorText.red(&amp;quot;--&amp;gt; 아직까지도 Lazy 처리된 의존성 인스턴스가 존재하지 않음.&amp;quot;));
                System.out.println(ColorText.red(&amp;quot;--&amp;gt; Dependency : &amp;quot; + lazyKey + &amp;quot; 객체가 아직도 완성되지 않음.&amp;quot;));
                System.out.println(ColorText.red(&amp;quot;--&amp;gt; 해당 의존성은 강제 의존성 해결 단계에서 해소됩니다.&amp;quot;));
                continue;
            }

            // 원하는 인스턴스를 기다리는 여러 Field 배열
            List&amp;lt;WaitingField&amp;gt; lazyList = this.lazyMap.get(lazyKey);

            // 이 의존성을 원하는 필드들 모두 의존성 해소.
            this.insertRealInstance(lazyList.iterator(), instance, DependencyMode.LAZY);

            // lazy 에서 의존 인스턴스를 제거한다.
            // this.lazyMap.remove(lazyKey); 문법의 Iterator 버전.
            lazyIter.remove();
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;생각보다 간단히 이해가 될 수도 있는데,&lt;/p&gt;
&lt;p&gt;&amp;quot;의존성 해결 1 단계&amp;quot; 는 &lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;의존성 해소&lt;/li&gt;
&lt;li&gt;순회하기 위한 완성 인스턴스 &lt;code&gt;Queue&lt;/code&gt; 에 추가&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이를 동시에 수행했다면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@MyLazy&lt;/code&gt; 의존성은 말 그대로 &amp;quot;완성되어 있는 인스턴스&amp;quot; 여야만 한다.&lt;/p&gt;
&lt;p&gt;따라서 이전에는 &lt;code&gt;Queue.poll()&lt;/code&gt; 을 통해 완성된 인스턴스를 꺼냈다면,&lt;/p&gt;
&lt;p&gt;이번에는 &lt;code&gt;lazyMap&lt;/code&gt; 에 존재하는 모든 문자열(&lt;code&gt;String&lt;/code&gt;) Key 를 통해&lt;/p&gt;
&lt;p&gt;&lt;code&gt;singletonContainer&lt;/code&gt; 에서 인스턴스를 찾는 것이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;null&lt;/code&gt; 이라면? : 개발자의 설계 실수에 가깝다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;null&lt;/code&gt; 이 아니라면? : 가져와서 &lt;code&gt;insertRealInstance&lt;/code&gt; 를 통해 의존성들을 해결한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;h4&gt;의존성 해결 3 단계 - (완벽한 순환참조 구조 해결)&lt;/h4&gt;
&lt;p&gt;대망의 마지막 단계에 도달했다.&lt;/p&gt;
&lt;p&gt;어떻게 보면 이 마지막 단계야말로, &lt;/p&gt;
&lt;p&gt;&amp;quot;완벽한 순환참조 구조&amp;quot; 까지도 받아들이겠다는 나의 프로그램 방향성을 보여주는 가장 중요한 대목이기도 하다.&lt;/p&gt;
&lt;br/&gt;

&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class ResolveDependency {
    // 자료구조들..

    // ...

    public void resolveStart() {

        // 메타데이터 분석 및 자료구조 안착 로직

        // &amp;quot;의존성 해소 1 단계&amp;quot;

        // 의존성 해소 2단계 - @MyLazy 의존성 해소

        // 의존성 해소 3단계 - 모든 순환참조 의존성 해소
        // 완벽한 순환참조가 발생할 경우 실행된다.
        // completeQueue 는 3 단계에서 재사용된다. --&amp;gt; dependencyTracker 의 객체를 전부 넣는다.
        // resolveCircularDependency 메서드에서 this.completeQueue 를 다시 순회하며 강제로 해소한다.
        if(!this.dependencyMap.isEmpty() || !this.lazyMap.isEmpty()) {
            this.resolveCircularDependency();
        }

        // 이 때 컨테이너는 완성된다.
    }
    // ...

    /**
    * 의존성 해결 3 단계.
    * 결국 의도적이지 않은 완벽한 순환참조, 혹은 &amp;quot;@MyLazy&amp;quot; 표기했음에도 순환참조로 이어지는 모든 의존성을 해결한다.
    * 만약 개발자가 &amp;quot;엄격 모드&amp;quot; 를 원한다면, 프로그램은 종료된다.
    * (만약 ResolveDependency 클래스 변수의 isAllowCircular 이 FALSE 라면.
    * 그러나 개발자가 순환참조 허용을 원한다면, 프로그램은 종료되지 않고 강제로 의존성을 허용한다. (Default == TRUE)
    */
    public void resolveCircularDependency() {
        // 사용자에게 의도적으로 &amp;quot;아직도 해소되지 않은&amp;quot; 모든 의존성을 보여준다.
        System.out.println(ColorText.red(&amp;quot;{UnResolved Dependency Detect!} --&amp;gt; &amp;quot;));

        // 일반 의존성으로서 해소되지 못한 순환참조들
        Iterator&amp;lt;String&amp;gt; normalIterator = this.dependencyMap.keySet().iterator();
        while(normalIterator.hasNext()) {
            String normalKey = normalIterator.next();
            System.out.println(ColorText.yellow(&amp;quot;UnResolve Dependency Key : &amp;quot; + normalKey));
        }

        // 사용자가 컴포넌트 의존성으로 &amp;quot;@MyLazy&amp;quot; 를 붙였는데도 불구하고, 순환참조 해결이 되지 않은 경우.
        Iterator&amp;lt;String&amp;gt; lazyIterator = this.lazyMap.keySet().iterator();
        while(lazyIterator.hasNext()) {
            String lazyKey = normalIterator.next();
            System.out.println(ColorText.yellow(&amp;quot;UnResolve Dependency Key : &amp;quot; + lazyKey));
            System.out.println(ColorText.yellow(&amp;quot;--&amp;gt; MyLazy 의존성&amp;quot;));
        }

        // 사용자가 만약 &amp;quot;엄격 모드&amp;quot; (isAllowCircular) 를 FALSE 로 해 놓았을 경우.
        if(!this.isAllowCircular) {
            throw new RuntimeException(&amp;quot;[Exception by Not Allow Circular Dependency]&amp;quot;);
        }

        // 일반 의존성 중, &amp;quot;완벽한 순환참조&amp;quot; 구조에 갇힌 모든 의존성 객체를 순회한다.
        Iterator&amp;lt;Object&amp;gt; restObjectIter = this.dependencyTracker.keySet().iterator();
        while(restObjectIter.hasNext()) {
            Object restObj = restObjectIter.next();

            // 이 객체와 매칭되어 있는 Proxy 객체, 혹은 자기 자신의 주소를 가져온다.
            CompleteObject completeObject = this.proxyContainer.get(restObj);

            // Dead Lock 구조에 갇혀버린 인스턴스들을 넣는 과정이다.
            this.completeQueue.add(completeObject);

            // 이 방식으로 제거해야 Iterator 가 제거된 정보와 &amp;quot;동기화&amp;quot; 된다. --&amp;gt; 제거 시 이 방식이 아니면 에러가 난다.
            restObjectIter.remove();
        }

        // 나머지 인스턴스들을 대상으로 &amp;quot;의존성 해결 1 단계&amp;quot; 와 유사한 로직을 구사한다.
        // 단, 해소되지 않은 모든 인스턴스를 모두 Queue 에 넣었기 때문에, 더 이상 Queue 에 들어갈 필요가 없다.
        while(!this.completeQueue.isEmpty()) {
            // 순환참조 인스턴스 하나를 뽑는다.
            CompleteObject completeObject = this.completeQueue.poll();

            // 해당 인스턴스와 패키지 이름을 추출한다.
            Object instance = completeObject.getObject();
            String fullName = completeObject.getFullName();

            // 자신을 기다리는 (필드-인스턴스) 쌍 배열을 추출한다.
            List&amp;lt;WaitingField&amp;gt; waitingFieldList = this.dependencyMap.get(completeObject.getFullName());

            // 만약 자신을 기다리는 의존성이 없다면, 최종적으로 &amp;quot;컨테이너&amp;quot; 에 들어가 완성된다.
            if(waitingFieldList == null) {
                this.singletonContainer.put(completeObject.getFullName(), completeObject.getObject());
                continue;
            }

            // Iterator 를 추출한다.
            Iterator&amp;lt;WaitingField&amp;gt; waitingIter = waitingFieldList.iterator();

            // 순회하며 의존성을 넣어준다.
            this.insertRealInstance(waitingIter, instance, DependencyMode.REST);

            // fullName(com.damsoon.component.xxx) 를 필요로 하는 모든 인스턴스에
            // 의존성을 &amp;quot;실제로&amp;quot; 넣어줬으므로, 대기 Map 에서 제거한다.
            this.dependencyMap.remove(fullName);

            // 이 의존성은 &amp;quot;모든 의존성&amp;quot; 을 만족시켜주었으므로, 진정한 컨테이너 자료구조에 들어간다.
            this.singletonContainer.put(fullName, instance);
        }

        // 마지막으로, 사용자가 &amp;quot;@MyLazy&amp;quot; 처리를 했음에도 순환참조에 갇힌 의존성들을 해소한다.
        Iterator&amp;lt;String&amp;gt; lastLazyIter = this.lazyMap.keySet().iterator();
        while(lastLazyIter.hasNext()) {
            // &amp;quot;@MyLazy&amp;quot; 로서 필요했던 패키지 클래스 이름을 가져온다.
            String keyName = lastLazyIter.next();

            // keyName 클래스 인스턴스를 원하는 배열(필드-인스턴스) 을 가져온다.
            List&amp;lt;WaitingField&amp;gt; fieldList = this.lazyMap.get(keyName);

            // 필요한 &amp;quot;모든 의존성&amp;quot; 은 이제 컨테이너에 들어가 있다.
            Object instance = this.singletonContainer.get(keyName);

            // 그럼에도 불구하고 인스턴스가 존재하지 않는다면, 이건 치명적인 에러다.
            // 여러 시나리오가 있을 수 있는데, 필요한 의존성 클래스에 &amp;quot;@MyComponent&amp;quot; 애너테이션을 달지 않았을 때 발생한다.
            // Dead Lock 을 뜯어서 모든 일반 의존성을 강제로 해소했기 때문이다.
            if(instance == null) {
                System.out.println(ColorText.red(&amp;quot;[CRITICAL ERROR OCCURR!!!] : 의존성 해결이 완전히 불가능한 객체가 존재합니다.&amp;quot;));
                System.out.println(ColorText.red(&amp;quot;--&amp;gt; 필요한 의존성 &amp;quot; + keyName + &amp;quot; 이 존재하지 않습니다.&amp;quot;));
                throw new RuntimeException();
            }

            // &amp;quot;@MyLazy&amp;quot; 로서 필요했던 의존성이 존재한다면, 기다리는 모든 의존성들을 해소 해 준다.
            Iterator&amp;lt;WaitingField&amp;gt; fieldIterator = fieldList.iterator();

            this.insertRealInstance(fieldIterator, instance, DependencyMode.LAZY);

            lastLazyIter.remove();
        }

        // Testing - 컨테이너에 들어간 &amp;quot;모든&amp;quot; 인스턴스들을 체크한다.
        Iterator&amp;lt;String&amp;gt; singletonIterator = this.singletonContainer.keySet().iterator();
        while(singletonIterator.hasNext()) {
            String keyName = singletonIterator.next();
            Object completeInstance = this.singletonContainer.get(keyName);
            System.out.println(keyName);
            System.out.println(&amp;quot;--&amp;gt; &amp;quot; + completeInstance.toString());
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;모든 의존성 구조에 상관없이 의존성을 해소 해 줄 수 있다는 것이 일종의 캐치프레이즈일 수는 있으나,&lt;/p&gt;
&lt;p&gt;사용자에게 이를 고지조차 하지 않는다면, 사실상 스파게티 코드를 독려하는 꼴이 될 수 있다고 생각한다.&lt;/p&gt;
&lt;p&gt;따라서, 현재 해소되지 못한 의존성들은 무엇이 있는지 알려주고,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;일반 의존성 Dead Lock 해제 및 의존성 해소&lt;/li&gt;
&lt;li&gt;마지막으로 개발자의 실수로 인해 해소되지 못한 &lt;code&gt;@MyLazy&lt;/code&gt; 의존성도 해소한다.&lt;/li&gt;
&lt;li&gt;컨테이너에 들어간 &amp;quot;모든&amp;quot; 컴포넌트를 출력한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;h4&gt;마지막 컴포넌트 테스팅.&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@MyComponent
public class Component1 {
    Component2 component2;

    // 순환 참조 상태. Component2 에서 @MyLazy 선언을 해 준 상태.
    @MyAutowired
    public Component1 (Component2 component2) {
        this.component2 = component2;
    }
}

// ---

@MyComponent
public class Component2 {
    Component1 component1;
    @MyAutowired
    Component3 component3;

    @MyAutowired
    public Component2 (@MyLazy Component1 component1) {
        this.component1 = component1;
    }
}

// ---

interface Component3 {
    public void testProxy();
}

@MyComponent
@MyProxy(proxy = ExecutionTime.class, targetInterface = Component3.class)
public class Component3Impl implements Component3 {
    Component4 component4;

    @MyAutowired
    public Component3Impl(Component4 component4) {
        this.component4 = component4;
    }

    public void testProxy() {
        System.out.println(&amp;quot;Method in Component3&amp;quot;);
    }
}

// ---

@MyComponent
public class Component4 {
    Component3 component3;

    @MyAutowired
    public Component4 (@MyLazy Component3 component3) {
        this.component3 = component3;
    }
}

// ---

@MyComponent
public class Component5 {
    Component6 component6;

    @MyAutowired
    public Component5(Component6 component6) {
        this.component6 = component6;
    }
}

// ---

@MyComponent
public class Component6 {
    Component5 component5;
    public Component6(Component5 component5) {
        this.component5 = component5;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Shell&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;# 기존 컴파일 결과를 지우고, 다시 생성하기 위함.
➜  test-1 git:(main) rm -rf result            

# 만들어 둔 shell 명령어 파일 실행
➜  test-1 git:(main) ✗ ./compile-and-execute.sh

컴파일 &amp;amp; 실행 구문 시작
com.damsoon # 메타데이터 읽는 시작점.

# 시작점으로부터 존재하는 &amp;quot;모든&amp;quot; 클래스 메타데이터 읽기
메타데이터 확인 절차 시작 
com.damsoon.proxy -- ExecutionTime
com.damsoon.util -- ResolveDependency
com.damsoon.util -- ResearchPackage
com.damsoon.util.type -- ParamMetadata
com.damsoon.util.type -- ClassMetadata
com.damsoon.util.type -- WaitingField
com.damsoon.util.type -- CompleteObject
com.damsoon.util.type -- ProxyInfo
com.damsoon.util -- DependencyMode
com.damsoon.util.console -- ColorText
com.damsoon.component -- Component3Impl
com.damsoon.component -- Component2
com.damsoon.component -- Component6
com.damsoon.component -- Component4
com.damsoon.component -- Component3
com.damsoon.component -- Component1
com.damsoon.component -- Component5
com.damsoon.annotation -- MyProxies
com.damsoon.annotation -- MyAutowired
com.damsoon.annotation -- MyLazy
com.damsoon.annotation -- MyComponent
com.damsoon.annotation -- MyProxy
com.damsoon.container -- CustomContainer
com.damsoon -- Main

# 의존성 해결 1 단계 실행 후, 컨테이너에 들어가 있는 인스턴스 목록
현재 싱글톤에 들어있는 완성 인스턴스 이름 : com.damsoon.component.Component3
현재 싱글톤에 들어있는 완성 인스턴스 이름 : com.damsoon.component.Component2
현재 싱글톤에 들어있는 완성 인스턴스 이름 : com.damsoon.component.Component1
현재 싱글톤에 들어있는 완성 인스턴스 이름 : com.damsoon.component.Component4

# 의존성 해결 2 단계 실행 중, Lazy 의존성이 무엇이었는지 출력.
lazyKey : com.damsoon.component.Component3
lazyKey : com.damsoon.component.Component1

# 의존성 해결 3 단계 - 미해결 순환참조 의존성 출력 후, 모든 의존성 해결.
{UnResolved Dependency Detect!} --&amp;gt; 
UnResolve Dependency Key : com.damsoon.component.Component6
UnResolve Dependency Key : com.damsoon.component.Component5

# 테스팅 과정에서 &amp;quot;toString&amp;quot; 을 모든 인스턴스에 행한 결과.
com.damsoon.component.Component3
proxy 실행됨
시작과 종료에 걸린 시간은 : 0ms, OR : 182334ns
--&amp;gt; com.damsoon.component.Component3Impl@3d646c37

com.damsoon.component.Component2
--&amp;gt; com.damsoon.component.Component2@5a10411

com.damsoon.component.Component1
--&amp;gt; com.damsoon.component.Component1@68de145

com.damsoon.component.Component6
--&amp;gt; com.damsoon.component.Component6@2ef1e4fa

com.damsoon.component.Component5
--&amp;gt; com.damsoon.component.Component5@27fa135a

com.damsoon.component.Component4
--&amp;gt; com.damsoon.component.Component4@306a30c7
컴파일 &amp;amp; 실행 구문 종료&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;ResolveDependency 클래스 파일의 &amp;quot;모든 코드&amp;quot;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;의존성 해결&amp;quot;&lt;/strong&gt; 에 초점을 맞추기 위해 &lt;/p&gt;
&lt;p&gt;이 목적을 보조하는 Method 들을 따로 펼쳐놓지 않았다. (펼치면 너무 난잡해져서..)&lt;/p&gt;
&lt;p&gt;모든 코드를 보고 이해를 원하는 분들을 위해, GitHub 주소를 드리는 것이 맞다고 생각했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://github.com/damhyeong/Custom-Spring-Container/blob/main/com/damsoon/util/ResolveDependency.java&quot;&gt;ResolveDependency 파일의 코드&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;직접 만든 Util 자료구조들도 있는데, 깃허브에서 상세하게 확인이 가능하다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;컨테이너의 로직을 전부 완성하였다.&lt;/h2&gt;
&lt;p&gt;이 글을 작성하게 된 계기는 생각보다 단순했다.&lt;/p&gt;
&lt;p&gt;인터넷 강의를 통해 &amp;quot;Spring&amp;quot; 을 배우고 있는데,&lt;/p&gt;
&lt;p&gt;정작 가장 중요해 보이는 &amp;quot;Spring Container&amp;quot; 에 대해서 뭉퉁그려 설명하고 넘어갔다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;&amp;quot;Spring Container 는 어려우니 간단히 설명하고 넘어가는게 맞지 않나?&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;당연히, Spring 강의에서 Spring Container 에 대해서 간단히 설명하고 넘어가는 것이 맞다.&lt;/p&gt;
&lt;p&gt;그러나, 나는 &amp;quot;내가 직접 컨테이너를 만들어 보겠다&amp;quot; 라는 생각이 뇌를 지배했다.&lt;/p&gt;
&lt;p&gt;이 편한 프레임을 만들게 해준 컨테이너를 만드는 것이 &amp;quot;생각보다 쉬워 보였기 때문&amp;quot; 이었다.&lt;/p&gt;
&lt;p&gt;결과적으로 보자면, 절대 쉽지 않았다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;이 주제로 글을 작성하는 사람이 없었다.&lt;/h3&gt;
&lt;p&gt;포기하지 않고 글을 완료하게 해 준 이유이기도 한데, &lt;/p&gt;
&lt;p&gt;나만의 Custom Container 를 만드는 사람이 없기도 하고,&lt;/p&gt;
&lt;p&gt;전부 컨테이너의 &amp;quot;동작 원리&amp;quot; 만을 설명하는 사람들이었다.&lt;/p&gt;
&lt;p&gt;사실 이 글의 수도 그리 많지는 않았다.&lt;/p&gt;
&lt;p&gt;그렇다면, &amp;quot;직접 컨테이너를 만드는 것&amp;quot; 은, &lt;strong&gt;더 특별하지 않을까?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;그리고 &lt;strong&gt;나의 성장&lt;/strong&gt; 에 지대한 영향을 끼칠 것이라고 생각했다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;마지막 요약 및 제약&lt;/h2&gt;
&lt;p&gt;직전 &amp;quot;요약&amp;quot; 부분에서 지금까지의 내용은 결국 &lt;code&gt;ResolveDependency&lt;/code&gt; 에 관한 내용이다.&lt;/p&gt;
&lt;p&gt;그리고 가장 중요한 부분 또한, &lt;code&gt;ResolveDependency&lt;/code&gt; 이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;&amp;quot;모든 것은 결국 Class 가 된다.&amp;quot;&lt;/h3&gt;
&lt;p&gt;특히 Java 에서 까먹지 말아야 할 중요한 포인트라고 생각한다.&lt;/p&gt;
&lt;p&gt;그리고 Metadata 를 활용하여 Java Class 를 런타임에서 분석할 생각을 한다면,&lt;/p&gt;
&lt;p&gt;이 문장은 더더욱 잊지 말아야 한다.&lt;/p&gt;
&lt;p&gt;모든 클래스 메타데이터를 분석하여 인스턴스화 한다는 것은,&lt;/p&gt;
&lt;p&gt;인스턴스화 하기 위한 클래스 메타데이터를 무조건 필요로 한다는 말이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;타 언어와 다르게, Java 는 JVM 의 &lt;code&gt;ClassPath&lt;/code&gt; 를 기준으로,&lt;/p&gt;
&lt;p&gt;패키지 이름으로 클래스 메타데이터를 탐색 할 수 있다.&lt;/p&gt;
&lt;p&gt;무작정 &lt;code&gt;../xxx.class&lt;/code&gt; 로 불러 올 수 없다.&lt;/p&gt;
&lt;p&gt;정확한 패키지 절대경로명, &lt;code&gt;com.damsoon.component.xxx&lt;/code&gt; 로 불러올 수 있다.&lt;/p&gt;
&lt;p&gt;이는 &amp;quot;클래스 파일&amp;quot; 에 대한 설명이고,&lt;/p&gt;
&lt;p&gt;만약에 파일 탐색 및 처리를 &amp;quot;문자열 단위&amp;quot; 로 한다면, 클래스 파일을 탐색하는 것은 아니므로,&lt;/p&gt;
&lt;p&gt;&amp;quot;상대경로&amp;quot; 를 사용 할 수 있을 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;프레임워크는 사용자의 컴포넌트, 혹은 유틸 &amp;quot;클래스명&amp;quot; 을 미리 알 수 없다.&lt;/p&gt;
&lt;p&gt;따라서, &lt;code&gt;Class&amp;lt;?&amp;gt;&lt;/code&gt; 와 같은 메타데이터 클래스로 분석할 수 밖에 없다.&lt;/p&gt;
&lt;p&gt;만약에 &lt;code&gt;getDeclaredAnnotation()&lt;/code&gt; 과 같은 내장 메서드를 이용하여 클래스를 추출한다면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Class&amp;lt;? extends Annotation&amp;gt;&lt;/code&gt; 과 같은 &amp;quot;Generic&amp;quot; 형태로 세분화 할 수 있다.&lt;/p&gt;
&lt;p&gt;모든 클래스 자체의 데이터는 &lt;code&gt;Class&amp;lt;?&amp;gt;&lt;/code&gt; 이며,&lt;/p&gt;
&lt;p&gt;모든 클래스에서 &amp;quot;탄생한 인스턴스&amp;quot; 는 &lt;code&gt;Object&lt;/code&gt; 를 띈다는 것을 기억해야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;이 글을 작성하면서 느낀 것.&lt;/h2&gt;
&lt;p&gt;이번 글은 내가 작성 한 글 중 &amp;quot;가장 어렵고 난해한&amp;quot; 글이다.&lt;/p&gt;
&lt;p&gt;앞으로 더 어려운 글을 적겠지만,&lt;/p&gt;
&lt;p&gt;적어도 &amp;quot;어떤 언어에서든,&amp;quot; 메타프로그래밍을 작성하기 위한 초안 정도는&lt;/p&gt;
&lt;p&gt;설명을 잘 해 놓았다고 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;Spring 이 잘 만들어진 프레임워크라서, 단순히 Spring 의 사용처를 잘 아는 것 만으로도 &lt;/p&gt;
&lt;p&gt;System Architecture 를 이해하고 적용 할 수 있지만,&lt;/p&gt;
&lt;p&gt;Spring 자체의 그 조그마한 보안 취약점도 허용하지 못하여,&lt;/p&gt;
&lt;p&gt;사내의 Framework 를 제작 할 수도 있다고 생각한다.&lt;/p&gt;
&lt;p&gt;이 때 나의 글이 &amp;quot;영감&amp;quot; (Idea) 를 줄 수 있지 않을까? 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이 글은 나의 성장이자 도전이었다.&lt;/p&gt;
&lt;p&gt;누군가 이 글을 읽고 도움이 되었다면, 나는 너무나도 만족한다.&lt;/p&gt;
&lt;p&gt;그리고 앞으로 작성하게 될 나의 글이 또다시 누군가에게 큰 도움이 되길 바란다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;특정 독자들을 유추할 수 없지만,&lt;/p&gt;
&lt;p&gt;요즘 블로그에 Perplexity (퍼플렉시티) 라는 AI 가 나의 글을 많이 참조하는 것을 발견한다.&lt;/p&gt;
&lt;p&gt;이 또한 누군가 나의 정보를 원하여, AI 가 가공하여 제공하는 것이라고 생각한다.&lt;/p&gt;
&lt;p&gt;과정만 다를 뿐, 누군가에게 큰 도움이 된다는 것은 다를 여지가 없다.&lt;/p&gt;
&lt;p&gt;앞으로도 좋을 글을 작성하며, 성장하고, &lt;/p&gt;
&lt;p&gt;내가 만든 프로그램이 나중에 많은 사람들에게 도움이 되기를 빈다.&lt;/p&gt;
&lt;br/&gt;

&lt;br/&gt;

&lt;br/&gt;

&lt;br/&gt;

&lt;hr&gt;
&lt;h2&gt;참조 사이트&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;위키백과 - 메타프로그래밍&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.wikipedia.org/wiki/%EB%A9%94%ED%83%80%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D&quot;&gt;https://ko.wikipedia.org/wiki/%EB%A9%94%ED%83%80%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Oracle Java Documentation- Examining Class Modifiers and Types&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://docs.oracle.com/javase/tutorial/reflect/class/classModifiers.html&quot;&gt;https://docs.oracle.com/javase/tutorial/reflect/class/classModifiers.html&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;개발자 블로그 - cp 옵션 와일드카드&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://blog.naver.com/cardano_null/222115578045&quot;&gt;https://blog.naver.com/cardano_null/222115578045&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;IBM 공식문서 - Java 클래스 경로&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.ibm.com/docs/ko/i/7.5.0?topic=usage-java-classpath&quot;&gt;https://www.ibm.com/docs/ko/i/7.5.0?topic=usage-java-classpath&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;위키백과 - 메타프로그래밍&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.wikipedia.org/wiki/%EB%A9%94%ED%83%80%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D&quot;&gt;https://ko.wikipedia.org/wiki/%EB%A9%94%ED%83%80%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Oracle - ClassLoader&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://docs.oracle.com/javase/8/docs/api/java/lang/ClassLoader.html&quot;&gt;https://docs.oracle.com/javase/8/docs/api/java/lang/ClassLoader.html&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Oracle - Class&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html&quot;&gt;https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;위키백과 - 자바 애너테이션&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.wikipedia.org/wiki/%EC%9E%90%EB%B0%94_%EC%95%A0%EB%84%88%ED%85%8C%EC%9D%B4%EC%85%98&quot;&gt;https://ko.wikipedia.org/wiki/%EC%9E%90%EB%B0%94_%EC%95%A0%EB%84%88%ED%85%8C%EC%9D%B4%EC%85%98&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Oracle Document - (Annotations)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://docs.oracle.com/javase/tutorial/java/annotations/&quot;&gt;https://docs.oracle.com/javase/tutorial/java/annotations/&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;</description>
      <category>Spring</category>
      <category>Annotation</category>
      <category>Container</category>
      <category>Custom Container</category>
      <category>Framework</category>
      <category>metadata</category>
      <category>Spring</category>
      <category>스프링 IoC</category>
      <category>애너테이션</category>
      <category>직접 제작</category>
      <category>커스텀 컨테이너</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/247</guid>
      <comments>https://codecreature.tistory.com/247#entry247comment</comments>
      <pubDate>Sun, 5 Apr 2026 00:00:14 +0900</pubDate>
    </item>
    <item>
      <title>도커 컨테이너 안의 MySQL 과 MySQL Workbench 는 어떻게 연결할까?</title>
      <link>https://codecreature.tistory.com/246</link>
      <description>&lt;h2&gt;제목 : 도커 안의 MySQL 과 MySQL Workbench 는 어떻게 연결할까?&lt;/h2&gt;
&lt;h3&gt;부제목 : 편하게 데이터베이스를 작업하는 방법&lt;/h3&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;이 글을 작성하는 이유&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;나는 주로 길 것이 예상되고 분석 혹은 공부가 반드시 뒤따르는 글을 작성하는 편이다.&lt;/p&gt;
&lt;p&gt;(공식문서 해석 글은 빼고)&lt;/p&gt;
&lt;p&gt;예를 들어 React 의 근간이 되는 Fiber Architecture 분석이나,&lt;/p&gt;
&lt;p&gt;모든 HTML 태그를 작성하고 예시를 &lt;code&gt;iframe&lt;/code&gt; 을 통해 직접적으로 보여주는 편인데,&lt;/p&gt;
&lt;p&gt;이는 글이 길고 좁고 깊은 도메인의 지식을 독자들이 따라 올 수 있게 만든다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;분명히 &amp;quot;누군가는&amp;quot; 이러한 글이나 내부 지식을 필요로 할 것이다.&lt;/p&gt;
&lt;p&gt;그러나 그 누군가가 상당수는 아닐 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;나는 NestJS 에서 Spring 으로 백엔드 도메인 전문성을 교체하고 있는 중인데,&lt;/p&gt;
&lt;p&gt;이를 위해 Udemy 에서 Spring 강의를 사서 듣고 있다. &lt;/p&gt;
&lt;p&gt;이 과정에서 로컬 컴퓨터에 MySQL 을 설치하고, MySQL Workbench 를 통해 &lt;/p&gt;
&lt;p&gt;쉽게 데이터베이스에 접근하고 변경 및 추가 삭제를 할 수 있게 만들어 주는데,&lt;/p&gt;
&lt;p&gt;내가 만약 Spring, MySQL 및 DB 를 처음 접하면 로컬로 프로그램을 실행하겠지만,&lt;/p&gt;
&lt;p&gt;나는 추후 클라우드 컴퓨팅을 통해 다양한 프로젝트들을 나열할 생각이기 때문에,&lt;/p&gt;
&lt;p&gt;이를 고려하여 Docker 를 통한 인프라 Isolation 구조로 데이터베이스를 구현하려 한다.&lt;/p&gt;
&lt;p&gt;쉽게 말해 도커 컨테이너에 데이터베이스를 구축하고, 나중에 필요 없어 질 때 컨테이너를 삭제 할 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;나와 같은 상황의 사람도 있을 것이고, 혹은 Docker 컨테이너로 고립된 프로세스에 &lt;/p&gt;
&lt;p&gt;굳이 터미널로 접근하는 것 보단, Workbench 를 통한 조작이 쉽고 간단하기 때문에&lt;/p&gt;
&lt;p&gt;이 글을 보는 사람도 존재 할 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;나는 공식문서를 통해 앞으로 표현될 코드와 그 원리는 이미 미리 글로 작성하며 공부했다.&lt;/p&gt;
&lt;p&gt;이해가 되지 않는다면, 밑의 링크를 통해 어떤 과정이 있었는지 보길 권합니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/244&quot;&gt;Docker 는 어떻게 운용되며 실행할까? (Dockerfile + CLI)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/245&quot;&gt;Docker Compose 는 무엇이고 어떻게 사용할까? - Spring &amp;amp; Nginx &amp;amp; MySQL&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;h2&gt;이 글을 볼 때 주의점&lt;/h2&gt;
&lt;p&gt;이번 글은 평소에 글을 작성할 때 항상 초점을 겨누는 &amp;quot;방법론&amp;quot; 에 대한 해체는 진행하지 않습니다.&lt;/p&gt;
&lt;p&gt;이 글을 작성하며 초점을 맞추게 되는 상황은 다음과 같습니다 :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;로컬 컴퓨터(macOS)&lt;/li&gt;
&lt;li&gt;Docker 환경 + Docker Compose 프로그램 사용&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docker-compose.yml&lt;/code&gt; 파일을 통한 쉬운 MySQL 인프라 실행&lt;/li&gt;
&lt;li&gt;격리된 데이터베이스 서버와 로컬에 설치된 MySQL WorkBench 연동&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;p&gt;여러분들에게 유용한 정보가 있길 빕니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Email : &lt;code&gt;rhdwhdals8765@gmail.com&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;h2&gt;Docker 로 격리된 MySQL 컨테이너 생성하기&lt;/h2&gt;
&lt;p&gt;보통 MySQL 서버를 실행 할 때, &lt;/p&gt;
&lt;p&gt;각 운영체제(Linux, Windows, MacOS) 에서 시동하는 방식이 다르며,&lt;/p&gt;
&lt;p&gt;사용하는 로컬 컴퓨터와 떨어지지 않는 방식으로 즉석 실행한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;주로 격리된 MySQL 컨테이너를 생성하는 이유는 위에서 설명한 나의 상황과 비슷하거나,&lt;/p&gt;
&lt;p&gt;곧 Cloud 컴퓨터에 Docker 격리 컨테이너를 적용하게 되는데,&lt;/p&gt;
&lt;p&gt;이를 로컬에서 테스팅 하기 위한 용도일 것이라고 추정한다.&lt;/p&gt;
&lt;p&gt;어떠한 상황이던, 로컬에서 Docker 격리 상황을 가정하여 Workbench 로 &lt;/p&gt;
&lt;p&gt;연결하고자 하는 것은 동일한다는 것을 기억하면 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;MySQL 컨테이너를 생성하는 2 가지 방식&lt;/h3&gt;
&lt;p&gt;도커로 MySQL 컨테이너를 생성하는 방식은 크게 2 가지가 있다.&lt;/p&gt;
&lt;p&gt;우선 도커가 설치된 로컬 컴퓨터라는 가정으로,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;docker run&lt;/code&gt; 이라는 명령어와 함께 수많은 옵션을 추가하는 방식이 있고,&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt; : &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ docker run -d \ 
&amp;gt; --name=custom-mysql \
&amp;gt; -e MYSQL_ROOT_PASSWORD=&amp;lt;root 유저 비밀번호&amp;gt; \
&amp;gt; -e MYSQL_DATABASE=test_scheme \
&amp;gt; -e MYSQL_USER=&amp;lt;보안을 위해 새로 생성한 일반 유저 이름&amp;gt; \
&amp;gt; -e MYSQL_PASSWORD=&amp;lt;위의 유저의 비밀번호&amp;gt; \
&amp;gt; -e TZ=Asiz/Seoul \
&amp;gt; -p 3306:3306 \
&amp;gt; -v &amp;quot;$(pwd)/data-db:/var/lib/mysql&amp;quot; \
&amp;gt; mysql:8.0 \
&amp;gt; --character-set-server=utf8mb4 \
&amp;gt; --collation-server=utf8mb4_unicode_ci
&amp;lt;생성된 CONTAINER ID&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;또 다른 방식으로는 Docker-Compose 를 따로 설치하여&lt;/p&gt;
&lt;p&gt;&lt;code&gt;docker&lt;/code&gt; 의 호환성 하위 프로그램으로 만드는 방식이 있다.&lt;/p&gt;
&lt;p&gt;이 때 &lt;code&gt;docker-compose.yml&lt;/code&gt; 이라는 이름으로 생성하여 &lt;/p&gt;
&lt;p&gt;&lt;code&gt;docker-compose&lt;/code&gt; or &lt;code&gt;docker compose&lt;/code&gt; 로 해당 파일을 실행 할 수 있는데,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Dockerfile&lt;/code&gt; 로 충족하기 힘든 여러 요소들을 &lt;code&gt;docker-compose.yml&lt;/code&gt; 파일을 통해&lt;/p&gt;
&lt;p&gt;기입하여 도커 디버깅 및 개발에 필요한 여러 단계를 자동화하는데 기여한다.&lt;/p&gt;
&lt;p&gt;또한 위에서 예시로 보여준 명령어를 굳이 띄웠다 내릴 때 마다 실행 할 필요가 없다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;도커와 도커 파일에 대한 더 많은 정보를 원한다면, 위에서 내가 보여준 2 개의 링크로 가면 된다.&lt;/p&gt;
&lt;p&gt;아마 이 글은 답변을 요하는 사람들이 주로 볼 것 같아서이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; 하위 디렉토리 &lt;code&gt;./data-db&lt;/code&gt; 생성하기&lt;/p&gt;
&lt;p&gt;이는 컨테이너 내부의 MySQL 프로그램에 대한 정보와 내 정보를 저장하기 위해&lt;/p&gt;
&lt;p&gt;로컬 디렉토리와 &amp;quot;Mount&amp;quot; 하기 위해 필요하다.&lt;/p&gt;
&lt;p&gt;한번 띄우고 바로 내릴 때, 모든 정보가 항상 사라져야 하는 것이 아니라면,&lt;/p&gt;
&lt;p&gt;데이터베이스는 특정 디렉토리가 항상 로컬과 연결되어 있는 것이 좋다.&lt;/p&gt;
&lt;p&gt;MySQL 에 대한 정보는 &lt;code&gt;/var/lib/mysql&lt;/code&gt; 이다.&lt;/p&gt;
&lt;p&gt;컨테이너 내부의 경로 &lt;code&gt;/var/lib/mysql&lt;/code&gt; 과 &lt;code&gt;./data-db&lt;/code&gt; 디렉토리를 연결(마운트) 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;2.&lt;/strong&gt; &lt;code&gt;docker-compose.yml&lt;/code&gt; 파일 생성&lt;/p&gt;
&lt;p&gt;생성된 야믈(&lt;code&gt;yaml&lt;/code&gt; or &lt;code&gt;yml&lt;/code&gt;) 파일에 밑의 내용을 기입한다.&lt;/p&gt;
&lt;p&gt;밑의 의미는 말하고 싶으나 글의 목적과 맞지 않아 생략한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;꼭 알아야 할 것은, 한국어는 프로그래밍과 엔지니어링의 중심 언어가 아니다.&lt;/p&gt;
&lt;p&gt;즉, 소수민족 취급을 받는 언어라고 생각해야 하기 때문에, 인코딩과 시간 설정이 핵심이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;MYSQL_&lt;/code&gt; 접두어가 붙은 장소는 이 글 독자의 마음대로 바꾸는 것이 맞으며,&lt;/p&gt;
&lt;p&gt;환경변수 중 &lt;code&gt;TZ&lt;/code&gt; 는 건드리면, 안되며, &lt;code&gt;volumes&lt;/code&gt;, &lt;code&gt;command&lt;/code&gt; 는 그대로 두어야 한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;version: &amp;#39;3.8&amp;#39;

services:
  db:
    image: mysql:8.0
    ports:
      - 3306:3306
    environment:
      - MYSQL_DATABASE=test_user
      - MYSQL_ROOT_PASSWORD=1324
      - MYSQL_USER=udemystudent
      - MYSQL_PASSWORD=1324
      - TZ=Asia/Seoul
    volumes:
      - ./db-info:/var/lib/mysql
    command:
      - --character-set-server=utf8mb4
      - --collation-server=utf8mb4_unicode_ci&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그리고 이 &lt;code&gt;docker-compose.yml&lt;/code&gt; 파일이 존재하는 터미널에서&lt;/p&gt;
&lt;p&gt;&lt;code&gt;docker compose up&lt;/code&gt; 을 입력한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  sqls docker compose up
WARN[0000] /Users/..../docker-compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion
[+] Running 2/2
 ✔ Network sqls_default  Created                                                 0.0s
 ✔ Container sqls-db-1   Created                                                 0.0s
Attaching to db-1
db-1  | 2025-11-26 22:21:12+09:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.0.44-1.el9 started.
...
...
db-1  | 2025-11-26T13:21:19.448526Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: &amp;#39;8.0.44&amp;#39;  socket: &amp;#39;/var/run/mysqld/mysqld.sock&amp;#39;  port: 3306  MySQL Community Server - GPL.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;만약에 위의 긴 내용이 터미널에 나오지 않고 &amp;quot;백그라운드&amp;quot; 로 실행되기를 원한다면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;docker compose up -d&lt;/code&gt; 를 입력하면 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그리고 꼭 확인해야 하는 것은, 특정 오류로 인해 컨테이너가 열리자마자 닫혔는지 꼭 확인해야 한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  ~ docker container ls
CONTAINER ID   IMAGE       COMMAND                   CREATED          STATUS          PORTS                                         NAMES
9b5d27c8c065   mysql:8.0   &amp;quot;docker-entrypoint.s…&amp;quot;   50 minutes ago   Up 50 minutes   0.0.0.0:3306-&amp;gt;3306/tcp, [::]:3306-&amp;gt;3306/tcp   sqls-db-1&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 내용에서 &lt;code&gt;STATUS&lt;/code&gt; 칸을 잘 보아야 하는데, &lt;code&gt;exited....&lt;/code&gt; 가 없고,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Up ...&lt;/code&gt; 가 있는것을 볼 수 있다. (잘 동작한다는 이야기)&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;상황 정리&lt;/h2&gt;
&lt;p&gt;현재 &lt;code&gt;docker&lt;/code&gt;, &lt;code&gt;docker-compose&lt;/code&gt; 프로그램을 통해 MySQL 격리 컨테이너를 띄웠다.&lt;/p&gt;
&lt;p&gt;이 컨테이너는 localhost Port 중에서, &lt;code&gt;3306&lt;/code&gt; 으로 요청을 넣을 수 있으며,&lt;/p&gt;
&lt;p&gt;컨테이너 내부에서는 요청이 들어왔을 때, &lt;code&gt;localhost:3306&lt;/code&gt; 으로 요청이 왔다고 인식한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;MySQL 컨테이너는 잘 동작 중이므로, 이제 MySQL WorkBench 를 사용 할 차례이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;MySQL Workbench 를 통해 연결 해 보자.&lt;/h2&gt;
&lt;p&gt;먼저 MySQL Workbench 를 키면, 이러한 화면을 볼 수 있다.&lt;/p&gt;
&lt;img src=&quot;https://github.com/damhyeong/Image-and-content/blob/main/blog-content/docker-mysql-with-workbench/01-screenshot.png?raw=true&quot;  alt=&quot;screen01&quot; /&gt;

&lt;p&gt;먼저 화면에서는 MySQL Connections 에 아무것도 없을 수도 있고,&lt;/p&gt;
&lt;p&gt;혹은 여러 개가 있을 수도 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그리고, 여기에서 내가 빨간색 처리 한 버튼을 눌러 다음과 같은 화면으로 이동한다.&lt;/p&gt;
&lt;img src=&quot;https://github.com/damhyeong/Image-and-content/blob/main/blog-content/docker-mysql-with-workbench/02-screenshot.png?raw=true&quot; alt=&quot;screen02&quot; /&gt;

&lt;img src=&quot;https://github.com/damhyeong/Image-and-content/blob/main/blog-content/docker-mysql-with-workbench/03-screenshot.png?raw=true&quot; alt=&quot;screen03&quot; /&gt;

&lt;p&gt;위의 화면은 &amp;quot;새로운 Connection&amp;quot; 을 만드는 것이다.&lt;/p&gt;
&lt;p&gt;나의 경우, Udemy 강의에서 생성한 프로젝트에 적용할 DB 이기 때문에,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Udemy_Connection&lt;/code&gt; 이라는 Connection Name 을 설정 할 것이다.&lt;/p&gt;
&lt;p&gt;그리고, Hostname, Port 는 바꿀 필요 없다.&lt;/p&gt;
&lt;p&gt;그리고 &lt;code&gt;docker-compose.yml&lt;/code&gt; 파일에서 우리는 &amp;quot;루트 유저&amp;quot; 외 &amp;quot;일반 유저&amp;quot; 또한 생성했다.&lt;/p&gt;
&lt;p&gt;원한다면 root 와 그 비밀번호로 연결해도 된다.&lt;/p&gt;
&lt;p&gt;Default Schema 는 연결하면서 생성할 DB, 최상위 스키마를 말하는 것이다.&lt;/p&gt;
&lt;p&gt;나는 이미 &lt;code&gt;test_user&lt;/code&gt; 이라는 DB 스키마를 설정하기도 했고,&lt;/p&gt;
&lt;p&gt;생성한다면 서버 내에서 생성하는 편이라서 넣지는 않았다.&lt;/p&gt;
&lt;br/&gt;

&lt;img src=&quot;https://github.com/damhyeong/Image-and-content/blob/main/blog-content/docker-mysql-with-workbench/04-screenshot.png?raw=true&quot; alt=&quot;screen04&quot; /&gt;

&lt;img src=&quot;https://github.com/damhyeong/Image-and-content/blob/main/blog-content/docker-mysql-with-workbench/05-screenshot.png?raw=true&quot; alt=&quot;screen05&quot; /&gt;

&lt;img src=&quot;https://github.com/damhyeong/Image-and-content/blob/main/blog-content/docker-mysql-with-workbench/06-screenshot.png?raw=true&quot; alt=&quot;screen06&quot; /&gt;

&lt;img src=&quot;https://github.com/damhyeong/Image-and-content/blob/main/blog-content/docker-mysql-with-workbench/07-screenshot.png?raw=true&quot; alt=&quot;screen07&quot; /&gt;

&lt;br/&gt;

&lt;p&gt;위에서 예시를 든 것 처럼 입장한다면, &lt;/p&gt;
&lt;p&gt;Docker Container 내부의 MySQL 을 마치 로컬에서 실행 한 것 처럼&lt;/p&gt;
&lt;p&gt;운용할 수 있게 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;보통 나는 데이터베이스를 구축 할 때,&lt;/p&gt;
&lt;p&gt;특정 프로젝트와 결합해야 하는 일이 생긴다면,&lt;/p&gt;
&lt;p&gt;NodeJS 도메인에서는 &lt;code&gt;TypeORM&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Spring 도메인에서는 &lt;code&gt;JPA &amp;amp; Hibernate&lt;/code&gt; 를 사용한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;만약에 옛날에 데이터베이스를 구축했다면,&lt;/p&gt;
&lt;p&gt;그건 주로 회사의 정보, 단체의 정보 및 등등을 넣어&lt;/p&gt;
&lt;p&gt;데이터베이스 내부에서 효과적으로 원하는 정보를 계산 및 추출하기 위함이다.&lt;/p&gt;
&lt;p&gt;그러나 요즘의 소프트웨어 자동화 기술은 정말 좋아서,&lt;/p&gt;
&lt;p&gt;코드로 작성한 엔티티 코드를 데코레이터 혹은 애노테이션으로 읽어서,&lt;/p&gt;
&lt;p&gt;이에 대한 메타데이터를 그대로 SQL 정보로 형성하여 데이터베이스에 명령한다.&lt;/p&gt;
&lt;p&gt;그러면 우리는 데이터베이스에서 손 하나 까딱하지 않고도 원하는 스키마와 테이블 및&lt;/p&gt;
&lt;p&gt;속성을 지정할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그럼에도 불구하고, ORM 이라는 기술이 완벽 한 것은 아니다.&lt;/p&gt;
&lt;p&gt;PK, FK 의 변경, 혹은 속성의 삭제와 같이 치명적인 명령은&lt;/p&gt;
&lt;p&gt;특수 옵션을 붙여야 하거나, 내부의 정보를 손상시키지 않기 위해&lt;/p&gt;
&lt;p&gt;직접 데이터베이스로 들어가서 &lt;code&gt;ALTER TABLE ...&lt;/code&gt; 해야 한다.&lt;/p&gt;
&lt;p&gt;나는 이러한 이유 때문에 직접 SQL 을 작성 할 일이 많았다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나 Docker 로 인해 격리화 된 상황에서도,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;TCP/IP&lt;/code&gt; 방식으로 쉽게 Workbench 와 연동 할 수 있다는 것은,&lt;/p&gt;
&lt;p&gt;이미 CLI 형식으로 접근 가능한 MySQL Containeried Server 에서,&lt;/p&gt;
&lt;p&gt;GUI 로 내부 정보를 추출 및 변형 삭제를 수행함이 가능하다는 의미이다.&lt;/p&gt;
&lt;p&gt;이는 개발 입장에서는 큰 차이가 존재한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;질문하고 싶으신 분이 계시다면,&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Email :&lt;/strong&gt; : &lt;code&gt;rhdwhdals8765@gmail.com&lt;/code&gt; &lt;/p&gt;
&lt;p&gt;으로 언제든 연락 주세요. 환영합니다&lt;/p&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;h2&gt;참조 사이트 목록&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;내 깃허브&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://github.com/damhyeong?tab=repositories&quot;&gt;https://github.com/damhyeong?tab=repositories&lt;/a&gt;&lt;/p&gt;</description>
      <category>잡다 지식</category>
      <category>Compose</category>
      <category>docker</category>
      <category>docker compose</category>
      <category>MySQL</category>
      <category>mysql workbench</category>
      <category>workbench</category>
      <category>workbench 연결</category>
      <category>yaml</category>
      <category>yml</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/246</guid>
      <comments>https://codecreature.tistory.com/246#entry246comment</comments>
      <pubDate>Thu, 27 Nov 2025 00:08:53 +0900</pubDate>
    </item>
    <item>
      <title>Docker Compose 는 무엇이고 어떻게 사용할까? - Spring &amp;amp; Nginx &amp;amp; MySQL</title>
      <link>https://codecreature.tistory.com/245</link>
      <description>&lt;h2&gt;제목 : Docker-Compose 란 무엇이고 어떻게 사용하는가? - With Spring &amp;amp; Nginx &amp;amp; MySQL&lt;/h2&gt;
&lt;h3&gt;부제목 : 도커를 명령어 없이 쉽게 사용하기 위한 방법&lt;/h3&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;이 글을 작성하는 이유&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;나는 NestJS 에서 Spring 으로 백엔드 도메인을 옮기는 상황이다.&lt;/p&gt;
&lt;p&gt;현재 Udemy 강의 사이트에서 외국 강사 분의 강의를 들으며 미약했던 Spring 의 기억을 끌어올리고 있다.&lt;/p&gt;
&lt;p&gt;이 강의는 데이터베이스를 MySQL, &amp;amp; Workbench 를 통한 쉬운 스키마 변경을 유도하고 있다.&lt;/p&gt;
&lt;p&gt;그러나, 나는 서버 운용 상황에서의 유연성을 고려하여 docker container 를 통해 &lt;/p&gt;
&lt;p&gt;데이터베이스 서버를 &lt;code&gt;3306&lt;/code&gt; 로컬 포트에 열고, 이를 연결 할 계획이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;NestJS 에서 Spring 으로 백엔드 도메인을 바꾼 이유는 이러하다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;단순 트렌드를 넘어서, Web &amp;amp; WAS 간의 개발 생산성을 비약적으로 끌어올리기 위해서라면,&lt;/p&gt;
&lt;p&gt;즉, 모든 영역에 JavaScript(with TypeScript) 를 사용하고자 한다면,&lt;/p&gt;
&lt;p&gt;NestJS 는 옳은 선택이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, 나는 프로그래머스 타입스크립트 풀스택 부트캠프를 수료했으며,&lt;/p&gt;
&lt;p&gt;이 과정에서 팀플 2개에서 백엔드 도메인을 맡고, NestJS 로 구현했다.&lt;/p&gt;
&lt;p&gt;타입스크립트와 방대한 오픈소스 의존성은 매혹적이었으나,&lt;/p&gt;
&lt;p&gt;부트캠프가 끝나고 NestJS 자체의 성능을 더 끌어올릴 수 있는 방안을 모색하면서,&lt;/p&gt;
&lt;p&gt;오히려 프로젝트에서 성능을 끌어올리기 위해 저수준 언어보다 개발시간이 더 들어간다는 것을 알게 되었다.&lt;/p&gt;
&lt;p&gt;혹은, 비정상적인 서버 Scaling 을 통해 성능을 해결해야 하는 상황이었다.&lt;/p&gt;
&lt;p&gt;나는 NestJS 가 가진 고유의 한계성을 깨기 위해 &lt;/p&gt;
&lt;p&gt;JavaScript의 기능을 분석하고 글을 작성했었다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/201&quot;&gt;node.js 로 멀티 스레드 구현하기 (Worker)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/202&quot;&gt;WebAssembly 와 Node.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/236&quot;&gt;JavaScript 개발자를 위한 스프링, NestJS 에 대해서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/242&quot;&gt;내가 Node.js 에서 Spring 으로 백엔드 영역을 변경하는 이유&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/198&quot;&gt;JavaScript, TypeScript 에서 진짜 Private 구현하기&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;조금의 이유를 덧붙이자면, Spring 은 리소스가 많이 필요하나(메모리), &lt;/p&gt;
&lt;p&gt;jvm 을 포기하고 바이너리 파일로 변경하여 70% 정도의 메모리를 절약할 수 있다.(GraalVM Image)&lt;/p&gt;
&lt;p&gt;그러나 Node.js 에서는 가능한 일이 아니다.&lt;/p&gt;
&lt;p&gt;물론, 웹 어셈블리를 통해서 모듈로 붙일 수는 있겠으나, 이는 Node.js 를 사용하는게 아니라,&lt;/p&gt;
&lt;p&gt;저 수준의 타 언어가 베이스가 되어야 하는 역설적인 상황이 벌어진다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;다시 돌아와서&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;나는 바로 이전의 글에서 &lt;code&gt;Dockerfile&lt;/code&gt; 에 대한 문법 기술과 더불어,&lt;/p&gt;
&lt;p&gt;이미지 생성과 컨테이너 생성 방식에 대해서 논했다.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/244&quot;&gt;Docker 는 어떻게 운용되며 실행할까? (Dockerfile + CLI)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;사실상 이 글의 다음편과 동일하다고 생각하고 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Docker Compose 란?&lt;/h2&gt;
&lt;p&gt;먼저 공식 문서에 나와있는 문구를 읊어보면,&lt;/p&gt;
&lt;p&gt;Docker Compose 는 다중 컨테이너를 정의하고 실행하는 &amp;quot;어플리케이션&amp;quot; 이라고 말한다.&lt;/p&gt;
&lt;p&gt;이를 진행하는 방법은 바로 YAML 설정 파일이다.&lt;/p&gt;
&lt;p&gt;여기서부터는 &amp;quot;설정 파일 조작&amp;quot; 을 통한 인프라 오케스트레이션이 본격적으로 적용된다고 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;직전의 파일에서 &lt;code&gt;Dockerfile&lt;/code&gt; 을 통해 &amp;quot;생성될 이미지&amp;quot; 를 빌드하는 데 초점을 맞췄다면,&lt;/p&gt;
&lt;p&gt;이번에는 &amp;quot;생성할 컨테이너&amp;quot; 를 만드는 데 초점을 맞춘다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Docker Compose 가 유용한 이유&lt;/h3&gt;
&lt;p&gt;docker 의 compose 기능이 자주 사용된다니까~ 라는 접근법으로 공부하게 되면,&lt;/p&gt;
&lt;p&gt;우리는 강의나 공식문서에 철저하게 휘둘릴 수 밖에 없다.&lt;/p&gt;
&lt;p&gt;내가 그렇게 몇년을 공부했고, 모든 것을 잊었기 때문이다.&lt;/p&gt;
&lt;p&gt;Docker Compose 를 다뤄야만 하는 이유를 아는 것이 중요하다고 판단된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;단일 프레임워크나, 단일 기능을 수행하는 프로젝트만을 샘플로 보고 싶다면,&lt;/p&gt;
&lt;p&gt;Docker Compose 는 그다지 큰 역할을 해 주지는 않는다.&lt;/p&gt;
&lt;p&gt;그냥 명령어로 해결하면 되기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;예를 들어서,&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;나는 Nginx + Spring 조합으로,&lt;/p&gt;
&lt;p&gt;컨테이너로 띄워진 Spring 이 직접적으로 요청을 받지 않고,&lt;/p&gt;
&lt;p&gt;Nginx 가 Reverse Proxy 기능을 수행하여 대신 요청을 받고, 응답하도록 만들고 싶다.&lt;/p&gt;
&lt;p&gt;그 이유는, 나중에 내가 클라우드 인프라에 적용하게 될,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SSL Termination - Nginx 가 인증을 수행함&lt;/li&gt;
&lt;li&gt;Load Balancing - Nginx 가 들어오는 요청을 복제된 서버들에 골고루 분산해줌&lt;/li&gt;
&lt;li&gt;경로 변환 - 서버 내에 들어있는 몇 가지 프로젝트에 요청이 들어 갈 때, 내부에서 경로를 변경함.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 말고도 Nginx 는 정적 파일을 매우 빠르게 반환해 준다는 장점이 있다.&lt;/p&gt;
&lt;p&gt;클라우드에서 제한된 컴퓨팅 리소스를 절제하는 중요한 인프라 중 하나이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;Docker Compose 가 &amp;quot;없을 때&amp;quot; 예시&lt;/h4&gt;
&lt;p&gt;우리는 이미지를 하나씩 커스텀하거나 만들어서 올려야 한다.&lt;/p&gt;
&lt;p&gt;이 때, Spring 서버의 이미지는 &amp;quot;이미 만들어져서 docker 에 저장된 상황&amp;quot; 이라고 가정한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TB

Client 

subgraph network[&amp;quot;docker network&amp;quot;]
    Nginx(&amp;quot;Nginx - 노출 포트 8080&amp;quot;)
    Spring(&amp;quot;Spring - spring1 이라는 이름으로 nginx 에게 연결됨&amp;quot;)
end

Client --&amp;gt; Nginx --&amp;gt; Spring 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;1. 도커 네트워크 생성&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;일종의 가상 망을 까는 행위인데, port 가 아닌 내부 IP 로 들어갈 수 있기 때문에,&lt;/p&gt;
&lt;p&gt;특정 도커 네트워크에 소속될 컨테이너가 노출 포트를 지정하지 않는 한,&lt;/p&gt;
&lt;p&gt;도커 네트워크에 소속된 컨테이너에 요청을 보낼 수 있는 방법은 없다.&lt;/p&gt;
&lt;p&gt;프로그램의 논리로 네트워크 차단망을 생성한다는 의미와 동일하게 보면 된다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;example&lt;/strong&gt; : &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;# test-spring-network 라는 bridge(default) 형태의 가상 망 생성
$ docker network create test-spring-network&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;2. 스프링 프로젝트 컨테이너화 및 옵션 입력&lt;/strong&gt; &lt;/p&gt;
&lt;p&gt;아주 간단한 스프링 프로젝트, (&lt;code&gt;/&lt;/code&gt;) 만 구현된 형태라고 생각하면 된다.&lt;/p&gt;
&lt;p&gt;이 프로젝트는 현재 이미지화 되어, &lt;code&gt;test-spring&lt;/code&gt; 이라는 &lt;code&gt;image name&lt;/code&gt; 를 가지고 있다.&lt;/p&gt;
&lt;p&gt;이제 이 이미지를 실행하며 컨테이너로 만들려고 하는데,&lt;/p&gt;
&lt;p&gt;문제는 옵션을 매우 잘 적어 주어야 한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;생성될 컨테이너의 &lt;code&gt;NAME&lt;/code&gt; --&amp;gt; nginx 가 &lt;code&gt;.conf&lt;/code&gt; 파일에서 쉽게 인식하기 위함&lt;/li&gt;
&lt;li&gt;생성될 컨테이너가 속할 도커 네트워크 선언 --&amp;gt; 직접 호출 API 서버가 아님.&lt;/li&gt;
&lt;li&gt;실행할 이미지 이름&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ docker run -d \
--name=spring1 \
--network=test-spring-network \
test-spring&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;만약에, 스프링 서버를 단일 컨테이너로 테스팅을 하고 싶었다면,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ docker run -d -p 8080:8080 test-spring&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이렇게 간단히 실행 할 수도 있다.&lt;/p&gt;
&lt;p&gt;그러나, 위에 port 정보가 없이, &lt;/p&gt;
&lt;p&gt;이 이미지 기반으로 실행될 &amp;quot;컨테이너의 이름&amp;quot; 그리고 &amp;quot;소속될 도커 네트워크 이름&amp;quot;&lt;/p&gt;
&lt;p&gt;으로 실행했으므로, 접근 할 수 없다. (물론 하려면 특정 IP 로 가능하긴 하겠지만.)&lt;/p&gt;
&lt;p&gt;따라서 실행된 컨테이너는 이러한 형태를 띈다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TB

Client(&amp;quot;local Client&amp;quot;)

subgraph test-spring-network[&amp;quot;test-spring-network&amp;quot;]
    spring1(&amp;quot;Container Name : spring1 &amp;lt;br/&amp;gt; 노출 PORT : 없음&amp;quot;)
end

Client -- 아직 호출 불가 --&amp;gt; test-spring-network&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;3. Nginx 컨테이너 실행 및 도커 네트워크의 노출 포트가 생성됨&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Nginx 또한 컨테이너로서 실행되며, 소속 네트워크를 지정한다.&lt;/p&gt;
&lt;p&gt;그 과정에서 옵션으로 노출 포트:인식 포트 를 지정하게 되는데,&lt;/p&gt;
&lt;p&gt;결과적으로 소속 네트워크 논리는 노출된 포트를 기점으로 Nginx 에 요청을 보낼 수 있게 해 준다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이 상황에서 예시로 들 것은, &lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;nginx&lt;/code&gt; 공식 이미지를 사용한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/etc/nginx/conf.d&lt;/code&gt; 라는 nginx 서버의 폴더에, 나의 &lt;code&gt;.conf&lt;/code&gt; 파일을 넣는다.&lt;/li&gt;
&lt;li&gt;위의 &lt;code&gt;.conf&lt;/code&gt; 파일은 나의 로컬 볼륨을 참조한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;그리고 넣게 될 conf 파일의 예제는 아주아주 간단하게 이렇게 생겼다고 가정한다.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nginx&quot;&gt;server {
  # 이 Nginx 는 80 포트로 수신하겠다
  listen 80;

  # 루트 uri 로 들어왔을 때 행동
  location / {
    return 200 &amp;quot;Nginx Root URL&amp;quot;;
  }

  # /api/v1/ uri 로 들어왔을 때, &amp;quot;spring1&amp;quot; 컨테이너의 8080 포트로 요청을 보내겠다.
  location /api/v1/ {
    proxy_pass http://spring1:8080/;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 nginx 설정 파일이 들어간다고 가정한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;docker 명령어 실행&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ docker run -d \
--name=test-nginx-network \
--network=test-spring-network \
-v &amp;quot;$(pwd)/custom.conf:/etc/nginx/conf.d/default.conf&amp;quot; \
-p 8080:80 nginx&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 명령을 통해 &amp;quot;공식 nginx 이미지&amp;quot; 를 기반으로,&lt;/p&gt;
&lt;p&gt;나의 커스텀 파일을 하나 넣어 &lt;code&gt;8080&lt;/code&gt; 포트로 spring 에 API 요청을 넣을 수 있게 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;현재 상황은,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TB

Client 

subgraph network[&amp;quot;docker network&amp;quot;]
    Nginx(&amp;quot;Nginx - 노출 포트 8080&amp;quot;)
    Spring(&amp;quot;Spring - spring1 이라는 이름으로 nginx 에게 연결됨&amp;quot;)
end

Client -- localhost:8080/api/v1/* --&amp;gt; Nginx

Nginx -- spring1:8080/* --&amp;gt; Spring &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위와 같은 형태가 구성된다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;Docker Compose 가 있을 때 예시&lt;/h4&gt;
&lt;p&gt;위에서 간단히, Nginx 와 Spring 을 하나의 도커 네트워크로 묶고,&lt;/p&gt;
&lt;p&gt;Nginx 가 Reverse Proxy 로 구성하는 데에 많은 명령과 그 옵션이 필요하다.&lt;/p&gt;
&lt;p&gt;심지어 위에서는 이미지가 &amp;quot;이미 만들어진&amp;quot; 경우를 상정 한 것이다.&lt;/p&gt;
&lt;p&gt;즉, &lt;code&gt;Dockerfile&lt;/code&gt; 내용은 뺀 상황이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;내가 말하고자 하는 것은 컨테이너를 띄우고, 볼륨이나 네트워크를 조정하고,&lt;/p&gt;
&lt;p&gt;재시작을 위해 기존 컨테이너를 정지 및 내려야 하는 불필요한 명령이 &lt;/p&gt;
&lt;p&gt;굉장히 번거로울 수 밖에 없다는 것을 의미한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;게다가, &lt;code&gt;Dockerfile&lt;/code&gt; 자체에서 사용할 수 없는 명령들이 존재한다.&lt;/p&gt;
&lt;p&gt;도커파일은&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;어떤 공식 이미지를 사용 할 것인지 - &lt;code&gt;FROM&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;어떤 디렉토리에서 작업 할 것인지 - &lt;code&gt;WORKDIR&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;어떤 로컬 파일을 내부에 복사 할 것인지 - &lt;code&gt;COPY&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;컨테이너 서버 내부에 어떤 환경 변수를 넣을 것인지 - &lt;code&gt;ENV&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;하나의 컨테이너가 필요한 어떤 의존성을 설치 할 것인지 - &lt;code&gt;RUN&lt;/code&gt;(명령어)&lt;/li&gt;
&lt;li&gt;컨테이너 실행 직후 어떤 명령어를 실행 할 것인지 - &lt;code&gt;CMD&lt;/code&gt;(명령어)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;위의 &lt;code&gt;Dockerfile&lt;/code&gt; 을 위한 예약어들은,&lt;/p&gt;
&lt;p&gt;&amp;quot;하나의 이미지를 만들기 위한&amp;quot; Layer 를 쌓는 것이다.&lt;/p&gt;
&lt;p&gt;이는 docker 라는 시스템 안의 인프라를 조정하기 위한 문법이 아니라는 것이다.&lt;/p&gt;
&lt;p&gt;따라서, Docker Compose 를 사용하지 않고, 위에서는&lt;/p&gt;
&lt;p&gt;각 컨테이너가 필요한 옵션들을 명령어로 직접 넣어주었다.&lt;/p&gt;
&lt;p&gt;이는 나중에 유지보수, 및 확장, 수정에 굉장히 불리하다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이러한 문제를 해결하고 하나의 파일로 일부 인프라 체계를 순식간에 구축 해 주는 것이,&lt;/p&gt;
&lt;p&gt;바로 &lt;strong&gt;Docker Compose&lt;/strong&gt; 이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;먼저, 내가 Docker Compose 를 매우 잘 다루는 것이 아니기 때문에,&lt;/p&gt;
&lt;p&gt;상정 할 것이 있다.&lt;/p&gt;
&lt;p&gt;Spring 프로젝트가 이미 빌드되어 &amp;quot;이미지화&amp;quot; 되어 있다고 가정한다.&lt;/p&gt;
&lt;p&gt;그리고 밑의 &lt;code&gt;yaml&lt;/code&gt; 코드는 Gemini 에게 compose 문법에 대해 질문하여 만들었다.&lt;/p&gt;
&lt;br/&gt;

&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;version : &amp;#39;3.8&amp;#39;

networks : 
  test-spring-network :
    # 이 네트워크는 이미 선언 해 놨으므로, driver : bridge 라고 선언하지 않는다.
    external : true

# 실행될 컨테이너들을 지정한다.
services : 
  # 실행될 스프링 컨테이너에 대한 메타데이터 지정
  spring-project :
    # 이미 빌드된 &amp;quot;test-spring&amp;quot; 을 기준으로 실행한다.
    image : test-spring 
    # 스프링 컨테이너의 이름은 &amp;quot;spring1&amp;quot; 으로 지정한다.
    container_name : spring1
    # 사용할 네트워크는 위의 네트워크에서 가져와 준 test-spring-network 를 이용한다.
    networks : 
      - test-spring-network

  # 실행될 nginx 컨테이너에 대한 메타데이터 지정
  nginx-reverse-proxy :
    # 위에서 &amp;quot;공식 nginx 이미지&amp;quot; 를 사용했기 때문에, 공식 이미지 최신을 가져오는 방식으로 지정했다. 
    # 만약에 이미 빌드된 nginx 를 사용한다면, 
    # image : &amp;lt;빌드한 nginx 이미지 이름&amp;gt; 으로 선언한다.
    image : nginx:latest
    container_name : test-nginx-network 
    # nginx 가 test-spring-network 컨테이너 중 유일하게 외부에 노출됨.
    ports :
      - &amp;quot;8080:80&amp;quot;
    # spring 프로젝트와 동일한 네트워크 사용
    networks : 
      - test-spring-network
    # 컨테이너 실행 시 참조 할 로컬 폴더와 추가 혹은 수정 및 덮을 파일 매칭
    volumes : 
      - ./custom.conf:/etc/nginx/conf.d/default.conf&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 파일은 &lt;code&gt;yml&lt;/code&gt; or &lt;code&gt;yaml&lt;/code&gt; 로 불리는 파일의 형태로 작성 한 건데,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;docker-compose&lt;/code&gt; 만의 예약어가 존재한다.&lt;/p&gt;
&lt;p&gt;하지만, 도커 자체의 내부 논리 인프라를 기억하거나 이해한다면,&lt;/p&gt;
&lt;p&gt;어렵지 않게 이해하고 작성 할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이 컴포즈(compose) 파일을 사용하기 전, 먼저 기존에 띄워진 컨테이너를 멈추고 삭제하자.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;docker compose 결과&lt;/h4&gt;
&lt;p&gt;나는 spring 프로젝트가 존재하는 root 에 따로 &lt;code&gt;custom.conf&lt;/code&gt; 파일을 위치시켜 놓았다.&lt;/p&gt;
&lt;p&gt;한번, 요청으로 nginx 와 spring 이 잘 동작하고 있는지 확인 해 보자 :&lt;/p&gt;
&lt;p&gt;Docker Container 상황 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  core-5 docker compose up
[+] Running 2/2
 ✔ Container test-nginx-network  Created                                                                                                                                      0.0s 
 ✔ Container spring1             Created                                                                                                                                      0.0s 
Attaching to spring1, test-nginx-network
test-nginx-network  | /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
....&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  ~ docker container ls --all
CONTAINER ID   IMAGE          COMMAND                   CREATED         STATUS         PORTS                                     NAMES
e2525cc48a28   test-spring    &amp;quot;java -jar app.jar&amp;quot;       7 minutes ago   Up 7 minutes                                             spring1
9a12e5e24654   nginx:latest   &amp;quot;/docker-entrypoint.…&amp;quot;   7 minutes ago   Up 7 minutes   0.0.0.0:8080-&amp;gt;80/tcp, [::]:8080-&amp;gt;80/tcp   test-nginx-network&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;# nginx 가 동작중인 것을 볼 수 있다.
# location / 해당
➜  ~ curl -X GET &amp;quot;http://localhost:8080&amp;quot;
nginx 게이트웨이는 동작중.

# reverse proxy 가 정상적으로 동작중인 것을 볼 수 있다.
# location /api/v1/ 해당
➜  ~ curl -X GET &amp;quot;http://localhost:8080/api/v1/&amp;quot;
헬로 월드를 도커 내부에서 실행 - 루트 url

# 뒤에 따라붙은 &amp;quot;/result&amp;quot; 가 spring 프로젝트에 잘 전달 된 것을 볼 수 있다.
# location /api/v1/ 에 해당
➜  ~ curl -X GET &amp;quot;http://localhost:8080/api/v1/result&amp;quot;
This is BeanResultSample&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h4&gt;docker compose 취소하는 2 가지 방법&lt;/h4&gt;
&lt;p&gt;첫 번쨰로, &lt;code&gt;Ctrl&lt;/code&gt; + &lt;code&gt;c&lt;/code&gt; 키보드를 눌러 취소하게 된다면,&lt;/p&gt;
&lt;p&gt;언제든 다시 빠르게 컨테이너를 실행 할 수 있게 &lt;code&gt;docker container stop ..&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;과 같은 상태로 존재한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, &lt;code&gt;docker compose up&lt;/code&gt; 를 실행한 현재 디렉토리 위치에서&lt;/p&gt;
&lt;p&gt;또 다른 터미널을 열어 &lt;code&gt;docker compose down&lt;/code&gt; 을 실행하면,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  core-5 docker compose down
[+] Running 2/2
 ✔ Container test-nginx-network  Removed                                                                                                                                      0.2s
 ✔ Container spring1             Removed&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  ~ docker container ls --all
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;깔끔하게 &lt;code&gt;docker compose up&lt;/code&gt; 으로 생겨난 2 개의 컨테이너가&lt;/p&gt;
&lt;p&gt;삭제 된 것을 볼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;이제 MySQL 만을 위한 compose 파일을 제작 해 보자.&lt;/h2&gt;
&lt;h3&gt;왜 MySQL 을 Compose 파일로 제작해야 할까?&lt;/h3&gt;
&lt;p&gt;수많은 공식 사이트들이 존재한다.&lt;/p&gt;
&lt;p&gt;Docker 의 공식 사이트거나,&lt;/p&gt;
&lt;p&gt;혹은 MySQL 커뮤니티의 공식 사이트거나,&lt;/p&gt;
&lt;p&gt;유명한 Medium 의 개발 블로그거나, 공신력이 있는 사람이거나, 등등..&lt;/p&gt;
&lt;p&gt;그러나, 각 조직이나 개인이 말하는 MySQL 실행에 대한 방법론이 조금씩 다르다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;특히, docker 공식 사이트의 경우 &lt;code&gt;docker run&lt;/code&gt; 을 통해 compose 파일 없이 생성하는 경우도 있다.&lt;/p&gt;
&lt;p&gt;특정 다른 공식 사이트의 경우, &lt;code&gt;docker-compose.yml&lt;/code&gt; 파일을 통해 MySQL 컨테이너를 실행한다.&lt;/p&gt;
&lt;p&gt;결과적으로 말하자면, &lt;code&gt;docker run&lt;/code&gt; 이나, &lt;code&gt;docker compose&lt;/code&gt; 나 틀린 방법들은 아니다.&lt;/p&gt;
&lt;p&gt;그러나, 운영체제와 상관없이 동일하고 편리한 배포 환경을 만들고 싶다면,&lt;/p&gt;
&lt;p&gt;철저하게 Docker Compose 를 추천한다.&lt;/p&gt;
&lt;p&gt;그리고 영어를 사용하는 사람이 Target 이 아니라면, 더더욱이 Compose 를 사용해야 한다.&lt;/p&gt;
&lt;p&gt;그 이유는, MySQL 서버에 적용해야 할 변수가 많아지기 때문이다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;MySQL 서버의 시간&lt;/li&gt;
&lt;li&gt;MySQL 서버의 문자열 인식&lt;/li&gt;
&lt;li&gt;MySQL 서버의 유저이름&lt;/li&gt;
&lt;li&gt;MySQL 서버의 비밀번호&lt;/li&gt;
&lt;li&gt;MySQL 서버의 데이터와 내 로컬 폴더와의 볼륨 마운트 (계속 생성되기 싫다면.)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;등등이 있을 수도 있는데, 이러한 정보를 단 하나의 파일로,&lt;/p&gt;
&lt;p&gt;한 번의 DB 서버를 컨테이너로 열 때 마다 명령어를 복잡하게 입력하지 않고,&lt;/p&gt;
&lt;p&gt;하나의 docker compose 파일로 실행 할 수 있게 만드는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;따로 &lt;code&gt;docker run&lt;/code&gt;, &lt;code&gt;docker start&lt;/code&gt;, &lt;code&gt;docker stop&lt;/code&gt;, &lt;code&gt;docker rm&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;등등의 명령어를 입력하여 실행하고, 끌 필요도 없기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그렇다고 위의 명령어가 중요하지 않고, 모든 것을 &lt;code&gt;docker compose&lt;/code&gt; 로 해결하라는 말은 아니다.&lt;/p&gt;
&lt;p&gt;상황에 따라 옵션과 명령이 많이 들어가는 경우, &lt;code&gt;compose&lt;/code&gt; 를 이용하여 해결 할 수 있다는 말이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;compose 없이 MySQL 컨테이너 다뤄보기&lt;/h3&gt;
&lt;p&gt;하나 더 말할 것이 있는데, 나는 MySQL 을 다룰 때, 데이터베이스를 한국패치 진행을 하지 않아,&lt;/p&gt;
&lt;p&gt;팀프로젝트 도중 모든 시간 기록과 한글 입력이 깨진 적이 존재한다.&lt;/p&gt;
&lt;p&gt;따라서, 이를 미리 적용하거나, 방지하는 수단은 필수적이다.&lt;/p&gt;
&lt;p&gt;한국어는 메인 언어가 아니다. 즉, 인코딩하는 입장에서 &lt;/p&gt;
&lt;p&gt;한국어는 소수민족 언어와 동일하게 취급한다고 생각하고 주의깊게 보아야 한다.&lt;/p&gt;
&lt;p&gt;그렇다면, 우리가 해야 할 행동은 무엇인가?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;컨테이너 내부 서버의 시간을 &amp;quot;서울&amp;quot; 로 맞추어야 한다.&lt;/li&gt;
&lt;li&gt;mysql 프로그램 내부에서 utf8 인코딩을 사용해야 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;위 2 개를 설정하지 않으면,&lt;/p&gt;
&lt;p&gt;아예 다시 DB 서버를 세팅하고 시작하거나,&lt;/p&gt;
&lt;p&gt;DB 프로그램에 입장하여 데이터베이스 Table 들에 인코딩을 설정하는 최악의 상황이 나올 수 있다.&lt;/p&gt;
&lt;p&gt;(심지어 입력된 데이터가 이미 훼손된 상황일 가능성이 높음)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;사용할 이미지는 &lt;code&gt;mysql:latest&lt;/code&gt; 이다.&lt;/p&gt;
&lt;p&gt;위에서는 도커의 내부 논리 인프라를 사용하는 과정을 복잡하게 다루었는데,&lt;/p&gt;
&lt;p&gt;이번에는 DB 는 왜 단순 &lt;code&gt;Dockerfile&lt;/code&gt; 이 아닌, &lt;code&gt;docker-compose.yml&lt;/code&gt; 파일이 사용되는지&lt;/p&gt;
&lt;p&gt;보여줄 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;시작 해 보겠다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;MySQL 은 보통 Dockerfile 로 빌드하지 않고, 곧바로 &lt;code&gt;docker run ...&lt;/code&gt; 을 사용한다.&lt;/p&gt;
&lt;p&gt;그 이유는, MySQL 은 빌드된 결과물이 필요하다기 보다는,&lt;/p&gt;
&lt;p&gt;하나의 서버, 혹은 컴퓨터의 프로그램으로서 실행되기 때문이다.&lt;/p&gt;
&lt;p&gt;Spring 을 실행하는 데 최적화 된 빌드 과정이 필요한 반면,&lt;/p&gt;
&lt;p&gt;MySQL 은 &amp;quot;이미 완성된 프로그램&amp;quot; 으로서, 우리가 빌드하는 과정이 필요하지 않다.&lt;/p&gt;
&lt;p&gt;단, docker 시스템에서 설정하기 어렵거나, 매우 디테일한 설정 변경이 필요할 경우,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Dockerfile&lt;/code&gt; 을 사용할 수 있는데, 이는 보통 &lt;code&gt;/docker-entrypoint-initdb.d/&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;경로에 &lt;code&gt;.sh&lt;/code&gt;(명령어 파일), &lt;code&gt;.sql&lt;/code&gt;(초기화 sql 명령), 등등과 같은 파일을 통해&lt;/p&gt;
&lt;p&gt;완성되어 있는 mysql 프로그램에 특수한 초기화를 수행하기 위해 사용한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;mysql:8,0&lt;/code&gt; 을 사용하는 경우를 상정한다 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;# -d 를 사용하는 이유는 현재 명령어 터미널에서 곧바로 컨테이너 세션 내부로 진입하지 않기 위함이다. (Detached - 떼어낸다 == 백그라운드 실행)
$ docker run -d \ 
&amp;gt; --name=custom-mysql # 컨테이너 이름은 &amp;quot;custom-mysql&amp;quot; 이다.
&amp;gt; -e MYSQL_ROOT_PASSWORD=1324 # 컨테이너에서 db 접근시 비밀번호 &amp;quot;1324&amp;quot;
# 정확히 말하면 DB 의 Scheme(스키마) 를 생성하는 것이다. (우리는 test_scheme 라는 이름으로 생성하겠다.)
&amp;gt; -e MYSQL_DATABASE=test_scheme
# Root 가 아닌, 유저 접근 권한으로 안전하게 아이디, 비밀번호를 생성한다.
&amp;gt; -e MYSQL_USER=test-user
&amp;gt; -e MYSQL_PASSWORD=test-pw
# 서버의 시간을 서울로 맞춘다 (정말 중요)
&amp;gt; -e TZ=Asiz/Seoul
# 외부 요청시 &amp;quot;3308&amp;quot; 포트로, 내부에서는 &amp;quot;3306&amp;quot; 으로 인식한다.
&amp;gt; -p 3308:3306 
# Linux/MacOS 문법으로, 터미널의 현 주소 + 하위 디렉토리와
# 컨테이너 서버 내부의 var/lib/mysql 을 마운트시켜서, 데이터베이스 정보를 로컬에 장착한다.
&amp;gt; -v &amp;quot;$(pwd)/data-db:/var/lib/mysql&amp;quot;
# mysql 8 버전을 사용.
&amp;gt; mysql:8.0 \
# 밑의 2 개는 &amp;quot;명령어 - (command)&amp;quot; 를 의미한다.
# 정확히 밑의 2 개가 서버에서 실행된다라는 의미보다, 필요한 mysql 프로그램 설정을
# docker 가 편하게 설정해 준다고 인식하면 된다.
&amp;gt; --character-set-server=utf8mb4 \
&amp;gt; --collation-server=utf8mb4_unicode_ci&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위에 주석이 많아서, 주석을 제거한다면&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ docker run -d \ 
&amp;gt; --name=custom-mysql \
&amp;gt; -e MYSQL_ROOT_PASSWORD=1324 \
&amp;gt; -e MYSQL_DATABASE=test_scheme \
&amp;gt; -e MYSQL_USER=test-user \
&amp;gt; -e MYSQL_PASSWORD=test-pw \
&amp;gt; -e TZ=Asiz/Seoul \
&amp;gt; -p 3308:3306 \
&amp;gt; -v &amp;quot;$(pwd)/data-db:/var/lib/mysql&amp;quot; \
&amp;gt; mysql:8.0 \
&amp;gt; --character-set-server=utf8mb4 \
&amp;gt; --collation-server=utf8mb4_unicode_ci
&amp;lt;생성된 CONTAINER ID&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;중요한 것 :&lt;/strong&gt; &lt;code&gt;../data-db&lt;/code&gt; 폴더 내부는 무조건 비어 있어야 한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;# 잘 살아있는 것을 볼 수 있음.
➜  ~ docker container ls --all
CONTAINER ID   IMAGE       COMMAND                   CREATED          STATUS          PORTS                                         NAMES
986eb2596fe9   mysql:8.0   &amp;quot;docker-entrypoint.s…&amp;quot;   13 seconds ago   Up 13 seconds   0.0.0.0:3308-&amp;gt;3306/tcp, [::]:3308-&amp;gt;3306/tcp   custom-mysql

# 현재 터미널과 컨테이너 서버를 연결하는 Interactive Terminal 선언
# 여기에서 &amp;quot;bin/bash&amp;quot; 내부 터미널 시스템을 이용한다.
➜  ~ docker exec -it custom-mysql bin/bash
# 곧바로 루트가 아닌 유저의 아이디와 비번으로 mysql 서버에 접속한다.
bash-5.1 mysql -u test-user -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.44 MySQL Community Server - GPL

Copyright (c) 2000, 2025, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type &amp;#39;help;&amp;#39; or &amp;#39;\h&amp;#39; for help. Type &amp;#39;\c&amp;#39; to clear the current input statement.

# mysql 프로그램에서 내가 선언했던 &amp;quot;test_scheme&amp;quot; 이 생성되었는지 확인한다.
mysql&amp;gt; show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| performance_schema |
| test_scheme        |
+--------------------+
3 rows in set (0.01 sec)

# mysql 의 터미널에서 퇴장
mysql&amp;gt; exit
Bye

# 현재 접속한 컨테이너의 터미널에서도 퇴장
bash-5.1 exit
exit

What&amp;#39;s next:
    Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug custom-mysql
    Learn more at https://docs.docker.com/go/debug-cli/

# 내 터미널로 돌아온다.
➜  ~&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 형태와 같이, MySQL 내부 서버 설정을 위해 무지막지한 옵션을 추가하게 된다.&lt;/p&gt;
&lt;p&gt;더 문제인 것은, MySQL 서버를 실행 해 봤더니 원하는 시나리오대로 되지 않거나,&lt;/p&gt;
&lt;p&gt;추가 설정이 필요할 경우, 이 컨테이너를 내리고, 또 다시 위의 명령어를 입력해야 한다.&lt;/p&gt;
&lt;p&gt;번거로움의 수준을 넘어 굳이 이렇게까지 해야하나? 의 수준으로 온다.&lt;/p&gt;
&lt;p&gt;이를 해결 해 주는 것이 바로 &lt;code&gt;docker-compose.yml&lt;/code&gt; 이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;compose 를 사용하여 MySQL 컨테이너 다루기&lt;/h3&gt;
&lt;p&gt;위에서는 한 방에 모든 것을 실행 한 것 처럼 보이지만,&lt;/p&gt;
&lt;p&gt;내가 선언한 명령어가 잘못 입력되어 컨테이너를 약 4 번 내리고 올렸다.&lt;/p&gt;
&lt;p&gt;정말 번거로운 작업일 수 밖에 없었는데, &lt;code&gt;docker-compose&lt;/code&gt; 를 사용하면 훨씬 나아진다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;docker-compose.yml&lt;/code&gt; --&amp;gt; 위치는 알아서.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;version: &amp;#39;3.8&amp;#39;

services:
  mysql-db:
    image: mysql:8.0
    environment:
      - MYSQL_ROOT_PASSWORD=1324
      - MYSQL_DATABASE=test_scheme
      - MYSQL_USER=test-user
      - MYSQL_PASSWORD=test-pw
      - TZ=Asia/Seoul
    ports:
      - 3308:3306
    volumes:
      - ./data-db:/var/lib/mysql
    command:
      - --character-set-server=utf8mb4
      - --collation-server=utf8mb4_unicode_ci&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 위치에 &lt;code&gt;data-db&lt;/code&gt; 폴더를 가지고 있어야 한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  db-compose docker compose up
WARN[0000] /Users/gongdamhyeong/docs-to-github/docker/example/db-compose/docker-compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion
[+] Running 1/1
 ✔ mysql-db Pulled                                                             3.9s
[+] Running 2/2
 ✔ Network db-compose_default       Created                                    0.0s
 ✔ Container db-compose-mysql-db-1  Creat...                                   0.0s
Attaching to mysql-db-1
mysql-db-1  | 2025-11-25 23:39:17+09:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.0.44-1.el9 started.
...
...
mysql-db-1  | 2025-11-25T14:39:24.804557Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: &amp;#39;8.0.44&amp;#39;  socket: &amp;#39;/var/run/mysqld/mysqld.sock&amp;#39;  port: 3306  MySQL Community Server - GPL.

➜  ~ docker container ls --all
CONTAINER ID   IMAGE       COMMAND                   CREATED         STATUS         PORTS                                         NAMES
927f0327d158   mysql:8.0   &amp;quot;docker-entrypoint.s…&amp;quot;   2 minutes ago   Up 2 minutes   0.0.0.0:3308-&amp;gt;3306/tcp, [::]:3308-&amp;gt;3306/tcp   db-compose-mysql-db-1&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기서 컨테이너를 내리고 싶다면, 현재 compose 세션이 실행되고 있는 터미널에서&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Ctrl&lt;/code&gt; + &lt;code&gt;c&lt;/code&gt; 를 누른 후 실행되고 있는 컨테이너를 &lt;code&gt;docker stop&lt;/code&gt; 하면 되고,&lt;/p&gt;
&lt;p&gt;해당 디렉토리에서 &lt;code&gt;docker compose down&lt;/code&gt; 을 입력하면 &lt;code&gt;docker rm ..&lt;/code&gt; 과 같이 된다.&lt;/p&gt;
&lt;p&gt;정말 좋은 것은, 이 모든 과정이 단순히&lt;/p&gt;
&lt;p&gt;&lt;code&gt;docker compose up&lt;/code&gt;, &lt;code&gt;docker compose down&lt;/code&gt; 으로 이루어진다는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;마무리 및 요약&lt;/h2&gt;
&lt;p&gt;Docker 라는 프로그램을 통해 생성되는 Container 라는 프로세스 단위는&lt;/p&gt;
&lt;p&gt;현재 인프라 오케스트레이션의 가장 중요한 엘리먼트가 되었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;컨테이너의 조율은 명령어로 가능하나, 실행하고자 하는 프로그램의 단계가 존재한다.&lt;/p&gt;
&lt;p&gt;크게 &amp;quot;빌드 전&amp;quot;, &amp;quot;이미지&amp;quot;, &amp;quot;컨테이너&amp;quot; 로 나눌 수 있다.&lt;/p&gt;
&lt;p&gt;문제는 여러 프로그램의 개발이나 프로덕션 시,&lt;/p&gt;
&lt;p&gt;발생하는 경고 및 에러에 대처하기 위해 개별 컨테이너나 이미지를 다시 빌드 및 실행한다는 것이다.&lt;/p&gt;
&lt;p&gt;이 과정에서는 위에 선보인 수많은 명령어들은 제일 많은 중간 과정이 생략되었는데,&lt;/p&gt;
&lt;p&gt;그 중간 과정은 바로 명령어가 실패하고 나서 컨테이너나 이미지를 삭제하고 다시 빌드하거나 실행하는 명령어이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;프로그램이 경고를 띄우거나 실패를 할 수 있으나, 이 과정에서 너무나 많은 시간 비용이 발생한다.&lt;/p&gt;
&lt;p&gt;이들에 대한 설정을 &lt;code&gt;YAML&lt;/code&gt; 파일 하나로 묶어 실행하고, 의존적 실행을 설정할 수 있다는 건&lt;/p&gt;
&lt;p&gt;정말 편리한 기능이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;특정 어플리케이션, &lt;code&gt;nginx&lt;/code&gt;, &lt;code&gt;spring&lt;/code&gt; 를 예로 들어본다면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;spring&lt;/code&gt; 은 DB 가 존재해야 프로그램이 실행되었을 때 DB 커넥션 풀을 성공적으로 형성 할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;nginx&lt;/code&gt; 는 스케일링 될 수 있는 &lt;code&gt;spring&lt;/code&gt; 의 서버 체크를 통해 에러 없이 실행된다.&lt;/p&gt;
&lt;p&gt;위의 과정은 의존적이며, 이를 &lt;code&gt;docker-compose&lt;/code&gt; 파일에서 작성할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;벌써 몇 개 되지도 않은 프로그램들을 &lt;code&gt;docker run&lt;/code&gt; 으로 실행한다고 가정했을 때에도 머리아프지만,&lt;/p&gt;
&lt;p&gt;프로덕션 수준의 프로그램들을 &lt;code&gt;docker run&lt;/code&gt; 으로 전부 띄우고 관리한다고 생각한다면 &lt;/p&gt;
&lt;p&gt;명령어의 수준과 그 양은 감히 비교도 할 수 없을 정도로 많아질 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;Docker Compose 는, 마치 Docker Swarm 이나, K8S 수준의 오케스트레이션을 하지는 않지만,&lt;/p&gt;
&lt;p&gt;인프라의 부분적인 구조에서 인프라 오케스트레이션을 쉽게 할 수 있게 해 준다는 것이다.&lt;/p&gt;
&lt;br/&gt;


&lt;br/&gt;

&lt;hr&gt;
&lt;h2&gt;참조 사이트&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Docker docs 공식 사이트 - Docker Compose&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://docs.docker.com/compose/&quot;&gt;https://docs.docker.com/compose/&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Docker docs 공식 사이트 - Use containerized databases&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://docs.docker.com/guides/databases/&quot;&gt;https://docs.docker.com/guides/databases/&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Docker docs 공식 사이트 - How Compose works&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://docs.docker.com/compose/intro/compose-application-model/&quot;&gt;https://docs.docker.com/compose/intro/compose-application-model/&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Docker docs 공식 사이트 - Networking using how network&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://docs.docker.com/engine/network/tutorials/host/&quot;&gt;https://docs.docker.com/engine/network/tutorials/host/&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;


&lt;br/&gt;</description>
      <category>잡다 지식</category>
      <category>Compose</category>
      <category>db</category>
      <category>docker</category>
      <category>docker compose</category>
      <category>MySQL</category>
      <category>NGINX</category>
      <category>Spring</category>
      <category>yaml</category>
      <category>yml</category>
      <category>데이터베이스</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/245</guid>
      <comments>https://codecreature.tistory.com/245#entry245comment</comments>
      <pubDate>Wed, 26 Nov 2025 00:34:14 +0900</pubDate>
    </item>
    <item>
      <title>Docker 는 어떻게 운용되며 실행할까? (Dockerfile + CLI)</title>
      <link>https://codecreature.tistory.com/244</link>
      <description>&lt;h2&gt;제목 : docker 는 어떻게 운용되며 실행할까? (Dockerfile + CLI)&lt;/h2&gt;
&lt;h3&gt;부제 : Spring 프로젝트와 Nginx 를 섞어보자&lt;/h3&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;이 글을 작성하는 이유&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;docker&lt;/code&gt; 는 Virtual Machine 의 선택된 가상 OS 가 무겁기 때문에&lt;/p&gt;
&lt;p&gt;&amp;quot;가벼운 컨테이너&amp;quot; 라는 캐치 프레이즈로 널리 쓰이고 있는 프로그램이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;Singleton 서버에서 마이크로서비스의 시대를 활짝 열어버린 주역이라고 할 수 있다.&lt;/p&gt;
&lt;p&gt;특히, 각 기능을 담당하는 특화 서버들 간의 통신 속도, &lt;/p&gt;
&lt;p&gt;무거운 Guest OS 를 서버마다 설치 할 필요 없이,&lt;/p&gt;
&lt;p&gt;Docker 만의 OS 수준 가상화를 통해 가벼운 개별 실행을 보장한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;나는 클라우드에 내가 만든 프로젝트 코드를 올려서, 실제 상호작용이 가능한 서버를 만들려고 한다.&lt;/p&gt;
&lt;p&gt;그런데, 여기서 제약이 생긴다. 나는 이미 AWS, GCP 에서 의도치 않은 큰 비용이 청구 된 적이 있으며,&lt;/p&gt;
&lt;p&gt;또한 이유를 참작 받아 탕감받았다. &lt;/p&gt;
&lt;p&gt;또한, 단순 1년 무료라는 이벤트성 웹 서비스를 사용하기에는&lt;/p&gt;
&lt;p&gt;생각보다 1년 무료는 그리 긴 시간이 아니다.&lt;/p&gt;
&lt;p&gt;따라서, 나는 Oracle 에서 제공하는 평생 무료의 제한된 Computing Resource 를 가지고,&lt;/p&gt;
&lt;p&gt;마치 귀중한 식재료를 얇게 발라먹듯 사용 할 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;위의 상황은 어떤 것이냐면,&lt;/p&gt;
&lt;p&gt;인스턴스 1 개에 1 개의 프로젝트를 넣을 수 있는 넉넉한 상황이 아니라,&lt;/p&gt;
&lt;p&gt;인스턴스 1 개에 허용될 수 있는 적당한 선에서 메모리를 아껴가며 &lt;/p&gt;
&lt;p&gt;프로젝트들을 책꽂이에 넣듯 사용해야 하는 상황이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;컴퓨팅 리소스가 아까운 지금의 상황에서, docker 에 대한 &lt;/p&gt;
&lt;p&gt;기초 이론적인 지식 뿐만 아니라, 실전적인 예제까지 봐 가며&lt;/p&gt;
&lt;p&gt;어떻게 한정된 리소스 속에서 캐싱, API 서버, 데이터베이스, 등등을&lt;/p&gt;
&lt;p&gt;격리시켜 메모리를 최적화 할 수 있는지 알아보려 한다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;따라서, 이번 글은 &lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Docker 의 간단한 기초 지식&lt;/li&gt;
&lt;li&gt;Docker 내부의 여러 프로세스의 역할 분석&lt;/li&gt;
&lt;li&gt;아주 간단한 Spring 서버를 Docker 로 띄우기&lt;/li&gt;
&lt;li&gt;nginx 컨테이너를 띄워 Spring 서버에 요청 가도록 만들기&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;를 한다.&lt;/p&gt;
&lt;p&gt;나머지 단계는 이후 글로 작성할 것인데,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cloud Server 에 Docker 설치 및 확인하기&lt;/li&gt;
&lt;li&gt;Cloud Server 의 docker container 로 Spring 띄우고 메모리 확인하기&lt;/li&gt;
&lt;li&gt;이를 위해 Github Actions 를 사용하기&lt;/li&gt;
&lt;li&gt;Cloud SSH Remote 를 Github Actions 서버에서 실행하기 &lt;/li&gt;
&lt;li&gt;등등 주제가 많다..&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cloud 부터 작성될 글의 양을 고려하여 포스팅을 나눌 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Docker 란 무엇인가?&lt;/h2&gt;
&lt;p&gt;위에서 도커에 대한 아주 간략한 정보에 이어, &lt;/p&gt;
&lt;p&gt;Docker 를 요약하자면, 현재 컴퓨터 세상에 존재하는 다양한 목적의 프로그램들을 &lt;/p&gt;
&lt;p&gt;개별 가상 컨테이너로 가볍게 분리 해 주는 프로그램이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;Infrastructure(인프라스트럭쳐) 라는 단어가 흔히 사용 될 정도로,&lt;/p&gt;
&lt;p&gt;개별 인프라들은 특정 서비스나 목적을 위해 계층을 이루거나, 서로를 의존한다.&lt;/p&gt;
&lt;p&gt;이제는 단일 프로젝트에서 Spring 만을 사용하는 것이 아니라,&lt;/p&gt;
&lt;p&gt;Redis, Kafka, RabbitMQ, Nginx, 등등을 사용한다.&lt;/p&gt;
&lt;p&gt;캐싱 뿐만 아니라, 요청 분산, 심지어는 데이터베이스도 가상화 시켜 데이터를 저장 할 수 있다.&lt;/p&gt;
&lt;p&gt;이러한 프로그램들을 물론 Local Server 에 직접 설치하여 Port 로 접근 할 수도 있겠지만,&lt;/p&gt;
&lt;p&gt;이는 추후 유지보수와 확장성 면에서 심각한 부작용을 보일 수 있다.&lt;/p&gt;
&lt;p&gt;바로, 어떠한 형태가 되었든 최신 버전으로의 이전이 굉장히 번거로우며,&lt;/p&gt;
&lt;p&gt;현재 사용 진행중인 컴퓨팅 리소스에 대한 확장과 축소 대응이 어렵다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;인프라를 추가하거나 제거함에 따라 추가적인 문제들이 정말 많을텐데,&lt;/p&gt;
&lt;p&gt;이러한 문제와 번거로움을 정해진 &amp;quot;설정 파일&amp;quot; 로 &amp;quot;자동화&amp;quot; 시킨 것이 Docker 이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;개인적으로 느꼈던 생각은,&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;도커는 인프라 계의 프레임워크라고 불러도 괜찮을 만큼,&lt;/p&gt;
&lt;p&gt;설정 파일을 통해 CI/CD 파이프라인을 해결 할 수 있다.&lt;/p&gt;
&lt;p&gt;프레임워크라고 불러도 괜찮을 만큼, 배워야 하는 것 또한 비슷하다고 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Docker 에 존재하는 다양한 형태의 이미지들&lt;/h2&gt;
&lt;p&gt;도커 레지스트리(Registry) 에는 내가 필요로 하는 이미지들이 대부분 존재하는데,&lt;/p&gt;
&lt;p&gt;예를 들면 우리가 직접 Nginx 를 컨테이너화 시키기 위해 &lt;/p&gt;
&lt;p&gt;Nginx 파일을 특정 폴더에 생성하고, 커스텀하고... 하는 것이 아니라&lt;/p&gt;
&lt;p&gt;이미 존재하는 Nginx 이미지를 docker image 로 다운로드 받고,&lt;/p&gt;
&lt;p&gt;Official Nginx 이미지를 컨테이너로 가동시킨 후,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;컨테이너 내부에 들어가서 직접 파일을 조정한다. OR&lt;/li&gt;
&lt;li&gt;내 로컬 컴퓨터에 컨테이너 서버와 특정 로컬 위치를 &amp;quot;Mount&amp;quot; 시킨 후 조정한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;가볍게 경량화 시킨 이미지 내부에 VIM 같은 에디터가 내장 될 리는 없으니,&lt;/p&gt;
&lt;p&gt;VI 에디터를 잘 사용한다면 1 번을 통해 조정해도 상관 없다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;이미 최적화 되어 있는 공식 이미지들&lt;/h3&gt;
&lt;p&gt;나중에 &lt;code&gt;Dockerfile&lt;/code&gt; 을 작성하면 알게 되겠지만,&lt;/p&gt;
&lt;p&gt;도커 컨테이너 실행에 최적화 되어 있는 이미지들이 준비중이다.&lt;/p&gt;
&lt;p&gt;데이터베이스와 같은 이미지 뿐만 아니라,&lt;/p&gt;
&lt;p&gt;NodeJS 기반 엔진이라면, &lt;code&gt;node&lt;/code&gt; 이미지를,&lt;/p&gt;
&lt;p&gt;Java 기반 엔진이라면, &lt;code&gt;java&lt;/code&gt; 이미지를 기반으로 생성한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;물론, C, C++ 와 같이 Binary 수준의 결과물만 나오는 프로그램과는 다르게,&lt;/p&gt;
&lt;p&gt;Node 즉, JavaScript 진영은 NodeJS 엔진을 통한 실행을 필요로 하며,&lt;/p&gt;
&lt;p&gt;Java 기반의 Spring 프레임워크 서버는 JRE 엔진이 존재하는 이미지를 필요로 한다.&lt;/p&gt;
&lt;p&gt;이를 우리가 항상 느끼지 않는 것은, 로컬 컴퓨터에 개발을 위한 편리한 프로그램들이 깔려 있는 것과 더불어,&lt;/p&gt;
&lt;p&gt;현대적인 에디터가 실행과 중단을 직접 도맡아 하는 경우가 많기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;수많은 공식 이미지들이 존재하는 것을 넘어, 도커는 Customize 이미지를 쉽게 만들 수 있게 지원한다.&lt;/p&gt;
&lt;p&gt;위에서 말했듯, Interactive Terminal 을 통한 제어와, Local Mount 를 통한 제어가 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;클라우드에서 더욱 빛을 발하는 image&lt;/h3&gt;
&lt;p&gt;도커 없이도 당연하게 제작한 프로젝트 실행이 가능하다.&lt;/p&gt;
&lt;p&gt;그러나, 이 프로젝트 실행을 위해 설정해야 할 환경 조작에 대한 번거로움을 고려 해 보자.&lt;/p&gt;
&lt;p&gt;사실 번거로운 수준을 넘어 설치 지옥에 빠질 가능성도 있다.&lt;/p&gt;
&lt;p&gt;Spring 을 위한 환경과 MySQL 만을 설치한다고 가정해도, 꽤 귀찮은 시나리오가 떠오를 정도이다.&lt;/p&gt;
&lt;p&gt;정말 바이너리 수준의 즉시 실행 가능한 프로그램 수준이 아니라면,&lt;/p&gt;
&lt;p&gt;프로그램 실행을 위한 기반을 무조건 마련해야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;우리가 로컬 컴퓨터에서 쉽게 프로젝트를 실행하고 관찰하고, 수정하는 것은&lt;/p&gt;
&lt;p&gt;위에서 말했듯 현대적인 에디터의 훌륭한 디버깅 기능 뿐만 아니라,&lt;/p&gt;
&lt;p&gt;GUI(Graphic User Interface) 덕분이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;만약에 가상화, (Docker, or VM) 을 하지 않는다면,&lt;/p&gt;
&lt;p&gt;우리가 필요로 하는 모든 기반 의존성 프로그램에 대한 공식 사이트에 들어가서,&lt;/p&gt;
&lt;p&gt;일일이 파일을 요청 다운로드(&lt;code&gt;curl&lt;/code&gt;) 해야 할 수도 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, Docker 를 사용한다면 그 이야기가 달라진다.&lt;/p&gt;
&lt;p&gt;Docker 는 우리가 필요로 하는 기반 프로그램, 혹은 응용 프로그램들이 &lt;/p&gt;
&lt;p&gt;전부 &amp;quot;이미지&amp;quot; 로 다운로드 할 수 있다.&lt;/p&gt;
&lt;p&gt;심지어는 내가 커스텀한 이미지를 다운로드 할 수도 있다.&lt;/p&gt;
&lt;p&gt;그렇다면, 우리는 다양한 인프라들을 사용함에 있어 OS 에 직접 설치 할 필요가 없으며,&lt;/p&gt;
&lt;p&gt;그저 불러와서 docker 엔진에게 컨테이너를 실행하라고 명령하면 될 뿐이다.&lt;/p&gt;
&lt;p&gt;특히 경량화 된 이미지를 사용한다는 점에서, 나에게는 도커가 더 매력적인 선택지이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;도커의 핵심 엘리먼트를 분석 해 보자. - 도커 아키텍쳐&lt;/h2&gt;
&lt;p&gt;나는 편한 프로그램에 대한 경계를 해 왔다.&lt;/p&gt;
&lt;p&gt;그 이유가, 몇 번 적용해 보았다고 해서 그 방식을 이해했다고 말할 수가 없었기 때문이다.&lt;/p&gt;
&lt;p&gt;결국 나는 Docker 를 알지만, 그 기반 원리와 구성 요소를 이해하지 못해 다시 공식문서를 보고 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;사실 도커를 사용한다는 것 자체가 편리한 프로그램을 사용한다는 점에서 모순적이지만,&lt;/p&gt;
&lt;p&gt;언제나 항상 기술적 타협점은 가지고 있어야 한다고 생각한다.&lt;/p&gt;
&lt;p&gt;개발자로서 컴퓨터 프로세스를 &amp;quot;직접 제어&amp;quot; 한다는 것은 큰 일을 해내는 것은 맞다.&lt;/p&gt;
&lt;p&gt;그러나, 그 직접 제어로 인해 개발 생산력이 떨어진다면, 그건 올바르지 않은 방향일 것이다.&lt;/p&gt;
&lt;p&gt;아집은 옳지 않다.&lt;/p&gt;
&lt;p&gt;따라서, 나는 Docker 를 통해 infra ochestration 의 추상적 기반을 마련하려고 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Docker Daemon - 도커 데몬&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;도커 데몬&lt;/strong&gt;이란, Docker 의 요소인 이미지, 컨테이너, 네트워크, 볼륨(저장소)&lt;/p&gt;
&lt;p&gt;이들을 관리하고 다루는 프로그램이다.&lt;/p&gt;
&lt;p&gt;도커 데몬은 도커가 실행중인 클라이언트 PC 의 프로세스라고 생각하면 된다.&lt;/p&gt;
&lt;p&gt;이후 설명할 Docker Client 에 의한 사용자 요청을 기다리고 있다.&lt;/p&gt;
&lt;p&gt;전체적으로 도커 데몬은 도커를 위해 생성된 다른 데몬을 다루기 위해 소통하는데,&lt;/p&gt;
&lt;p&gt;클라이언트 PC 에서 실행되는 Docker 프로세스들을 관리하는 핵심 프로세스(데몬) 이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Docker client - 도커 클라이언트&lt;/h3&gt;
&lt;p&gt;보통은 도커 데스크탑 버전을 PC 에 다운로드 받아서 직접 컨테이너, 이미지, 볼륨과 같은 요소들을&lt;/p&gt;
&lt;p&gt;GUI 입력 요소로 제어할 수 있지만, Docker client 는 명령어와 밀접하다.&lt;/p&gt;
&lt;p&gt;즉, 우리가 &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;docker run ...&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docker container list ..&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docker build -t ....&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이러한 &amp;quot;명령어&amp;quot; 들을 인식하고 &lt;code&gt;dockerd&lt;/code&gt; 에게 전달해 준다.&lt;/p&gt;
&lt;p&gt;즉, Docker 의 명령어 API 이다.&lt;/p&gt;
&lt;p&gt;물론 명령어를 통해 도커 클라이언트를 사용하고 싶다면,&lt;/p&gt;
&lt;p&gt;위에서 언급한 &lt;code&gt;docker daemon&lt;/code&gt; 이 필수적이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Docker Registries - 도커 레지스트리&lt;/h3&gt;
&lt;p&gt;우리가 사용할 Nginx 라던지, 데이터베이스, 등등&lt;/p&gt;
&lt;p&gt;원하는 이미지들은 &amp;quot;공식 이미지&amp;quot; 로 OpenSource 로 배포되어 있다.&lt;/p&gt;
&lt;p&gt;이들을 Docker Hub 에서 태그와 함께 쉽게 가져올 수 있는데,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;docker pull &amp;lt;원하는 이미지&amp;gt;:&amp;lt;원하는 버전&amp;gt;&lt;/code&gt;, &lt;code&gt;docker run ...&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;이러한 방식으로 쉽게 가져올 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;뿐만 아니라, 개인의 Docker Registry 주소도 존재하여,&lt;/p&gt;
&lt;p&gt;내가 만든 Custom Image 를 Docker Registry 에 저장하고,&lt;/p&gt;
&lt;p&gt;이를 클라우드 환경에서 끌어다 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;이는 매우 핵심적인 기능이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Docker File - 도커 파일&lt;/h3&gt;
&lt;p&gt;도커 파일은 우리가 만든 프로젝트를 기반으로 어떤 환경과 환경 변수,&lt;/p&gt;
&lt;p&gt;그리고 어떠한 외부 자원과 실행 명령어를 사용 할 것인지 기술하는 파일이다.&lt;/p&gt;
&lt;p&gt;파일 확장자나 이름 필요 없이 그냥 &lt;code&gt;Dockerfile&lt;/code&gt; 로 기술한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;물론, &lt;code&gt;Dockerfile-1&lt;/code&gt; 과 같은 이름으로 작성 한 뒤,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;docker build -t new-app -f ./Dockerfile-1&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;과 같은 방식으로 도커파일을 지정 할 수는 있다.&lt;/p&gt;
&lt;p&gt;그러나, 만약에 에디터의 하이라이팅 및 예시 기능을 사용하기 위해서는&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Dockerfile&lt;/code&gt; 이라는 정확한 파일을 기술해야 문법적 지원을 받을 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;따라서, 주로 도커 파일을 제작하는 Convention(관례) 는,&lt;/p&gt;
&lt;p&gt;정확한 목적을 가진 이름의 디렉토리를 만든 뒤, 해당 디렉토리에 &lt;code&gt;Dockerfile&lt;/code&gt; 을 생성한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;Dockerfile&lt;/code&gt; 만의 문법이 존재하며, &lt;/p&gt;
&lt;p&gt;특히 예약어의 경우 대문자로 이루어진다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;FROM&lt;/code&gt; : 어떤 이미지를 기반으로 컨테이너를 실행 할 것인지 지정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WORKDIR&lt;/code&gt; : 결국 컨테이너는 서버를 의미하므로, 이 컨테이너 서버의 &amp;quot;어디에서&amp;quot; 실행 할 것인지 지정.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;COPY&lt;/code&gt; : Docker 환경 속이 아닌 장소에서, 이 이미지의 특정 경로로 파일을 복사하기 위해 사용된다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RUN&lt;/code&gt; : 명령어를 실행하는데 사용되며, 의존성을 설치하거나, 필요 디렉토리를 만드는 중간 단계의 명령어를 의미한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ENV&lt;/code&gt; : 이미지가 컨테이너로 실행되면서 내부에서 사용될 &amp;quot;환경 변수&amp;quot; 를 의미한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ARG&lt;/code&gt; : 현재 &lt;code&gt;Dockerfile&lt;/code&gt; 에서 사용될 변수를 선언한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;EXPOSE&lt;/code&gt; : 제작하는 이 이미지가 어느 port 로 &amp;quot;노출&amp;quot; 될 것인지를 지정한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CMD&lt;/code&gt; : 컨테이너가 실행 된 뒤, 기본적으로 실행되야 할 필수 명령어를 넣는다. &lt;br/&gt; 이 예약어는 하나만 유효하며, 맨 마지막이 선택된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이에 대한 자세한 설명은 &lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://docs.docker.com/get-started/docker-concepts/building-images/writing-a-dockerfile/&quot;&gt;dockerdocs - Writing a Dockerfile&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;사이트에서 참조 할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;도커로 스프링 &amp;#39;로컬에서&amp;#39; 띄워보기&lt;/h2&gt;
&lt;p&gt;스프링 공식 사이트에서 친절하게 Docker 로 띄우는 법을 보여주고 있다.&lt;/p&gt;
&lt;p&gt;그런데, 여기서 &lt;code&gt;Gradle&lt;/code&gt;, &lt;code&gt;Maven&lt;/code&gt; 의 방식이 살짝 다르다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;예시 &lt;code&gt;Dockerfile&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-Dockerfile&quot;&gt;FROM openjdk:8-jdk-alpine
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar 
ENTRYPOINT [&amp;quot;java&amp;quot;, &amp;quot;-jar&amp;quot;, &amp;quot;/app.jar&amp;quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 예제에서 이상한 점을 느꼈다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;왜 &lt;code&gt;8&lt;/code&gt; 버전이지? 현재 spring init 은 최소가 Java &lt;code&gt;17&lt;/code&gt; 버전인데?&lt;/li&gt;
&lt;li&gt;왜 &lt;code&gt;jdk&lt;/code&gt; 이지? 컨테이너 내부에서 따로 develop 할 게 아닌데? &lt;code&gt;jre&lt;/code&gt; 가 들어가야 하지 않나?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;따라서, 이에 대한 부분을 AI (Gemini) 에게 물어보니, &lt;/p&gt;
&lt;p&gt;옛 공식자료이며, &amp;quot;돌아만 가게&amp;quot; 만들었기 때문에 최적화가 안되어 있다고 한다.&lt;/p&gt;
&lt;p&gt;따라서, 현재는 &lt;code&gt;eclipse-temurin:17-jre-focal&lt;/code&gt; 이라는 이미지에서 가져온다고 한다.&lt;/p&gt;
&lt;p&gt;따라서, 위에서 제시한 &amp;quot;공식문서 예제&amp;quot; 는 이렇게 바뀐다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-Dockerfile&quot;&gt;FROM eclipse-temurin:17-jre-focal 
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar 
ENTRYPOINT [&amp;quot;java&amp;quot;, &amp;quot;-jar&amp;quot;, &amp;quot;/app.jar&amp;quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;도커파일로 스프링 띄워보기&lt;/h3&gt;
&lt;p&gt;만약에 스프링을 &lt;code&gt;Gradle&lt;/code&gt; 로 띄운다면,&lt;/p&gt;
&lt;p&gt;위에서 제시한&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ARG JAR_FILE=target/*.jar&lt;/code&gt; 은 바뀌어야 한다.&lt;/p&gt;
&lt;p&gt;위의 경로는 &lt;code&gt;Maven&lt;/code&gt; 을 위한 경로이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Gradle 을 사용하여 빌드한다면&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-Dockerfile&quot;&gt;FROM eclipse-temurin:17-jre-focal
ARG JAR_FILE=build/libs/*.jar
COPY ${JAR_FILE} app.jar 
ENTRYPOINT [&amp;quot;java&amp;quot;, &amp;quot;-jar&amp;quot;, &amp;quot;/app.jar&amp;quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;로 작성하여, 굳이 &lt;code&gt;docker build ..&lt;/code&gt; 명령어 실행 시 &lt;/p&gt;
&lt;p&gt;긴 명령어로 실행하지 않도록 방지하자.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;현재 테스트의 기반이 될 폴더의 구조는 이러하다 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  core-5 tree -L 2   
.
├── HELP.md
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
    ├── main
    └── test

4 directories, 4 files&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;폴더 구조를 보면, 아직 Maven 빌드 폴더인 &lt;code&gt;./target&lt;/code&gt; 이 없는 것을 볼 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;# 내장된 mvnw 프로그램을 통해 Maven 전역 프로그램 설치 없이 메이븐 명령이 가능하다.
$ ./mvnw package 
...
...
테스팅
...
완료

$&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;현재 디렉토리&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  core-5 tree -L 2     
.
├── HELP.md
├── mvnw
├── mvnw.cmd
├── pom.xml
├── src
│   ├── main
│   └── test
└── target
    ├── classes
    ├── generated-sources
    ├── generated-test-sources
    ├── maven-archiver
    ├── maven-status
    ├── springcore.test-0.0.1-SNAPSHOT.jar # 이 파일을 사용하게 됩니다.
    ├── springcore.test-0.0.1-SNAPSHOT.jar.original
    ├── surefire-reports
    └── test-classes

12 directories, 6 files&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 디렉토리를 보면, Docker 이미지에 넣을 &lt;code&gt;springcore.test-0.0.1-SNAPSHOT.jar&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;이 &lt;code&gt;package&lt;/code&gt;을 통해(gradle 은 build) 생성되었음을 알 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이제 위에서 선언한 &lt;code&gt;Dockerfile&lt;/code&gt; 중 Maven 용을 프로젝트의 루트에 생성한다.&lt;/p&gt;
&lt;br/&gt;

&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;.
├── Dockerfile # 위에서 보였던 Dockerfile Maven 버전을 작성.
├── ...
├── src
│   ├── main
│   └── test
└── target
    ├── ...
    ├── springcore.test-0.0.1-SNAPSHOT.jar&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  core-5 docker build -t test-spring .     
[+] Building 0.9s (7/7) FINISHED                                                                                                                              docker:desktop-linux
 =&amp;gt; [internal] load build definition from Dockerfile                                                                                                                          0.0s

 ....

 =&amp;gt; =&amp;gt; naming to docker.io/library/test-spring                                                                                                                                0.0s

View build details: docker-desktop://dashboard/build/desktop-linux/desktop-linux/xgk2h5plnv5rs97goinmqamtm

What&amp;#39;s next:
    View a summary of image vulnerabilities and recommendations → docker scout quickview&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  core-5 docker image list
REPOSITORY                                TAG                                                                           IMAGE ID       CREATED         SIZE
test-spring                               latest                                                                        ef97619f4b21   3 minutes ago   280MB
hello-world                               latest                                                                        ca9905c726f0   3 months ago    5.2kB
... 등등 이전에 만들었던 나의 이미지들&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위에서 &lt;code&gt;test-spring&lt;/code&gt; 이라는 &lt;code&gt;TAG&lt;/code&gt; 로 Spring 프로젝트가 이미지화 된 것을 볼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  core-5 docker run -d -p 8080:8080 test-spring
d3a682bcb5cc963593cbf92f80ef334b22acccb47da49382040487aa515238b4&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-d&lt;/code&gt; : Detached 모드인데, &amp;quot;현재 터미널&amp;quot; 이 실행된 &lt;code&gt;test-spring&lt;/code&gt; 컨테이너로 곧바로 접속하지 않고, Container ID 만 출력하고 명령이 종료됨.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-p&lt;/code&gt; : &lt;code&gt;&amp;lt;호스트OS 에 노출될 PORT&amp;gt;:&amp;lt;컨테이너 내부에 연결될 PORT&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;스프링 프로젝트에서 따로 환경설정으로 PORT 를 바꾸지는 않았으므로, Default 인 &lt;code&gt;8080&lt;/code&gt; 으로 설정.&lt;/p&gt;
&lt;br/&gt;

&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  core-5 docker container list
CONTAINER ID   IMAGE         COMMAND                  CREATED          STATUS          PORTS                                         NAMES
d3a682bcb5cc   test-spring   &amp;quot;java -jar app.jar&amp;quot;      21 seconds ago   Up 21 seconds   0.0.0.0:8080-&amp;gt;8080/tcp, [::]:8080-&amp;gt;8080/tcp   stoic_faraday&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;현재 나의 컴퓨터 로컬 환경에서 &lt;code&gt;8080&lt;/code&gt; 으로 접근 할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  core-5 curl -X GET &amp;quot;http://localhost:8080&amp;quot;
헬로 월드를 도커 내부에서 실행 - 루트 url

➜  core-5 curl -X GET &amp;quot;http://localhost:8080/result&amp;quot;    
This is BeanResultSample&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;내부에는 JSON 반환체가 아닌, &lt;code&gt;text/plain&lt;/code&gt; 형태로 반환되고 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;스프링 컨테이너 정지하기&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  core-5 docker container stop d3a        
d3a&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;현재 실행되고 있는 도커 컨테이너 중, &lt;code&gt;d3a&lt;/code&gt; 를 종료했다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;d3a&lt;/code&gt; 는 무엇일까?&lt;/p&gt;
&lt;p&gt;&lt;code&gt;d3a&lt;/code&gt; 는 이미지가 &amp;quot;컨테이너화&amp;quot; 되면서 생기는 고유(Unique) 한 해싱 번호의 &amp;quot;전반부 일부&amp;quot; 이다.&lt;/p&gt;
&lt;p&gt;현재 가지고 있는 컨테이너가 &amp;quot;중복되지 않는 선에서&amp;quot; 구별될 수 있는 &lt;/p&gt;
&lt;p&gt;전반부 일부만 입력해도 종료된다.&lt;/p&gt;
&lt;br/&gt;

&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  core-5 docker container list --all
CONTAINER ID   IMAGE         COMMAND                  CREATED         STATUS                        PORTS                    NAMES
d3a682bcb5cc   test-spring   &amp;quot;java -jar app.jar&amp;quot;      7 minutes ago   Exited (143) 49 seconds ago                            stoic_faraday&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;일반적인 &lt;code&gt;docker ps&lt;/code&gt;, &lt;code&gt;docker container list&lt;/code&gt; 는,&lt;/p&gt;
&lt;p&gt;현재 &amp;quot;실행중인&amp;quot; 컨테이너 리스트를 나타 내 준다.&lt;/p&gt;
&lt;p&gt;그러나, 종료되었으나, 사라지지 않는 컨테이너(대기) 도 존재한다.&lt;/p&gt;
&lt;p&gt;우리가 &lt;code&gt;docker container rm ...&lt;/code&gt; 를 통해 컨테이너를 &amp;quot;삭제&amp;quot; 한 것은 아니고,&lt;/p&gt;
&lt;p&gt;단순히 정지시킨 것이기 때문에, &lt;code&gt;--all&lt;/code&gt; 이라는 옵션을 붙여서 볼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Nginx 컨테이너를 이용하여 Spring 서버와 리버스 프록시 체결하기&lt;/h2&gt;
&lt;p&gt;도커를 이용하여 격리된 컨테이너는 PORT(&lt;code&gt;8080&lt;/code&gt;) 과 함께 노출되어 접근이 가능하다.&lt;/p&gt;
&lt;p&gt;그러나, 프로덕션에 가까운 완성도를 보이기 위해서는, Reverse Proxy 형태를 만들어야 한다.&lt;/p&gt;
&lt;p&gt;왜 그럴까?&lt;/p&gt;
&lt;p&gt;하나의 서버에 단순한 하나의 프로젝트에 필요한 인프라를 간단히 넣는다면,&lt;/p&gt;
&lt;p&gt;컨테이너 당 하나의 PORT 를 할당하여 노출시킬 수도 있다.&lt;/p&gt;
&lt;p&gt;그러나 여기서 문제가 발생한다.&lt;/p&gt;
&lt;p&gt;나는 클라우드에서, &lt;code&gt;https&lt;/code&gt; 통신이 공식적으로 가능하도록 만들 것이다.&lt;/p&gt;
&lt;p&gt;정보를 제공하는 &amp;quot;서버&amp;quot;란, &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;정적 정보(페이지) 를 Serving 하는 Front-End 전용 서버나,&lt;/li&gt;
&lt;li&gt;동적 정보(비즈니스 데이터) 를 전달 해 주는 Back-End 전용 서버나&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;동일하게 &lt;code&gt;https&lt;/code&gt; 가 적용 되어야 한다.&lt;/p&gt;
&lt;p&gt;(그렇지 않다면 전달 데이터는 모두 평문으로 노출되기 때문입니다.)&lt;/p&gt;
&lt;p&gt;이 과정에서 https 연결을 위한 ssl 인증서, 도메인 이름을 증명할 공간이 필요하다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;내 상황에 Nginx 가 더더욱 필요 한 이유&lt;/h3&gt;
&lt;p&gt;나는 클라우드 서버 중 &amp;quot;평생 무료&amp;quot; 인스턴스를 제공하는 Oracle 클라우드 서버에서,&lt;/p&gt;
&lt;p&gt;한정된 컴퓨팅 리소스를 발라 먹듯이 촘촘히 프로젝트를 넣을 것이다.&lt;/p&gt;
&lt;p&gt;동일한 컴퓨터에 동일한 프레임워크를 사용하여 여러 프로젝트를 생성할 수도 있지만,&lt;/p&gt;
&lt;p&gt;그 목적들은 다를 것이다.&lt;/p&gt;
&lt;p&gt;그렇다면, 외부에서 오는 모든 요청들에 대한 ssl 인증서와 tcp 체결을,&lt;/p&gt;
&lt;p&gt;각 프로젝트에서 실행 할 것인가? 그건 코드를 너무 낭비하는 일이다.&lt;/p&gt;
&lt;p&gt;내가 http2.0 버전을 언제 적용할 지도 모르는 일이다.&lt;/p&gt;
&lt;p&gt;그렇다면 어떤 언어로 작성될 지도 모를 모든 API 프로젝트의 인증서 요구사항을 바꿔야한다는 말과 동일하다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;따라서, 외부(클라이언트)에서 내부(클라우드 인스턴스) 로 요청이 들어 올 때,&lt;/p&gt;
&lt;p&gt;모든 인증을 한번에 해결하고, nginx 가 다시 프로젝트들에 요청하는 방식으로 이루어져야 한다.&lt;/p&gt;
&lt;p&gt;또한, Nginx 는 들어온 경로들을 다시 변환하여 내부에 스스로 요청 할 수도 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;리버스 프록시는, 클라이언트로부터 들어온 요청들을 각각의 API 서버로 분리하고,&lt;/p&gt;
&lt;p&gt;혹은 위에서 말했듯 hyper text protocol secure (https) 를 하나의 장소에서 해결하기 위한&lt;/p&gt;
&lt;p&gt;특별한 해결사로 사용할 수도 있다.&lt;/p&gt;
&lt;p&gt;또한, 중요한 기능으로 외부에서 요청한 특정한 경로가 있다면,&lt;/p&gt;
&lt;p&gt;이를 로컬로 다시 요청을 보내기 직전 원하는 경로로 비틀 수도 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Client : &lt;code&gt;https://test-domain.com/api/v1/docker-spring&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Nginx 변환 : &lt;code&gt;https://localhost:8080/nginx-for-spring&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;외부에서 들어온 요청을 Nginx 가 받는데, 또 Nginx --&amp;gt; Nginx 형태가 될 수도 있는 이유는,&lt;/p&gt;
&lt;p&gt;Server Scaling 과정에서 여러개로 분산된 spring 컨테이너의 요청들을 관리하는,&lt;/p&gt;
&lt;p&gt;그러한 Nginx 가 존재 할 수도 있기 때문이다.&lt;/p&gt;
&lt;p&gt;단순하게 1 개의 Docker Container 로 관리한다면, &lt;code&gt;localhost:8080&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;이런 식으로 작성 할 수도 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;또한, Docker 에서 Nginx 내부 설정 파일을 따로 건들지 않고도,&lt;/p&gt;
&lt;p&gt;단순 명령어를 통해 &lt;code&gt;docker network&lt;/code&gt; 에 종속시켜 여러 컨테이너를 관리 할 수도 있다.&lt;/p&gt;
&lt;p&gt;그러나, 나는 결국 &lt;code&gt;nginx&lt;/code&gt; 를 Customize 된 Image 화 시켜 만들게 될 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;위의 내용을 요약 해 보자면,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TB

Client(&amp;quot;Client - 프론트엔드&amp;quot;)

subgraph Instance[&amp;quot;클라우드 인스턴스&amp;quot;]
    subgraph Nginx[&amp;quot;Nginx&amp;quot;]
        nginx-role1(&amp;quot;https 요청에 대한 ssl 인증과 tcp 연결 체결 &amp;lt;br/&amp;gt; 및 요청 분산&amp;quot;)
    end

    Nginx-Spring(&amp;quot;Nginx - Spring 이미지들에 대한 요청 분산용 &amp;lt;br/&amp;gt; Spring 이미지들에 대한 주소를 가짐&amp;quot;)

    subgraph Springs[&amp;quot;Spring Images&amp;quot;]
        Spring1(&amp;quot;Spring 복제1&amp;quot;)
        Spring2(&amp;quot;Spring 복제2&amp;quot;)
    end

    subgraph Other-Projects[&amp;quot;다른 프로젝트들&amp;quot;]
        another-project(&amp;quot;또 다른 용도의 API 프로젝트들..&amp;quot;)
    end

    Nginx --http 프로토콜 및 (경로 변환)--&amp;gt; Nginx-Spring
    Nginx --http 프로토콜 및 (경로 변환)--&amp;gt; Other-Projects
    Nginx-Spring --http--&amp;gt; Springs
end

Client --https 프로토콜--&amp;gt; Nginx&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;아직 내부에 DB 나 Redis 와 같은 캐싱 장치는 넣지 않았는데,&lt;/p&gt;
&lt;p&gt;이는 그래프가 너무 복잡해지고, 설명에 적합하지 않아 넣지 않았다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Nginx 를 통해 로컬에서 테스팅 해 보자 - with Docker Network&lt;/h3&gt;
&lt;p&gt;이 과정을 실행하기 전, Docker Network 에 대한 내용과,&lt;/p&gt;
&lt;p&gt;로드 밸런싱을 수행하기 위해 Docker 를 어떻게 활용되어야 하는지 보았다.&lt;/p&gt;
&lt;p&gt;만약에, Docker Network 를 생성하지 않고, 단순히 Nginx 라는 컨테이너를 실행하여,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;localhost:8080&lt;/code&gt; 와 같은 형식으로 요청을 보내도 된다.&lt;/p&gt;
&lt;p&gt;그러나, 이러한 경우, &amp;quot;API 접근 시각&amp;quot; 에서 바라보았을 때,&lt;/p&gt;
&lt;p&gt;목적에 따른 각기 다른 프로젝트들이 네트워크적으로 격리 될 수 없음을 확인했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;&amp;quot;그냥 구현하기만 하면 되는 것이 아닌가?&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;물론 이도 맞는 말이지만, 나는 한정된 리소스에서 복잡한 프로젝트 체계를 잡아야 한다.&lt;/p&gt;
&lt;p&gt;이 과정에서 핵심 기능 network 를 이해하지 못한다면, 추후 클라우드 네트워크 환경이&lt;/p&gt;
&lt;p&gt;스파게티처럼 엮여 있을 가능성이 90% 는 될 것이라 생각한다. (확장까지 고려했을 때.)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;내가 이제 수행할 과정에서 매우 많은 도커의 Command 가 사용 될 것이지만,&lt;/p&gt;
&lt;p&gt;이는 Docker-Compose 를 사용하지 않아 그렇다.&lt;/p&gt;
&lt;p&gt;나는 아직 Docker-Compose 를 사용하지 않고, docker 명령어를 이해하기 위해&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Dockerfile + docker command + container + network&lt;/code&gt; 형식으로 구성 할 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;현재 글에서 Docker network 역할 수준인 &lt;code&gt;bridge&lt;/code&gt;, &lt;code&gt;host&lt;/code&gt;, &lt;code&gt;none&lt;/code&gt;, .. 을 다루면&lt;/p&gt;
&lt;p&gt;난잡해 질 것 같다.&lt;/p&gt;
&lt;p&gt;지금 만들게 될 도커 네트워크는 &lt;code&gt;bridge&lt;/code&gt; 이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;1. 도커 가상 네트워크 생성&lt;/strong&gt; - Docker network create.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;# spring-test-network 라는 이름의 도커 네트워크 생성 - bridge
➜  ~ docker network create spring-test-network
6171b5277ed3a9b19ce3fc4bccb0b1fea8df9b6362877c6eb4b85566a8747fcd

# bridge, host, none 은 docker 의 기본 네트워크라서 삭제 불가
➜  ~ docker network ls 
NETWORK ID     NAME                  DRIVER    SCOPE
d7a1a41c2242   bridge                bridge    local
3508372af286   host                  host      local
a2587916247a   none                  null      local
6171b5277ed3   spring-test-network   bridge    local&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 목록을 보면, 내가 생성한 새로운 네트워크 &lt;code&gt;spring-test-network&lt;/code&gt; 가 보일 것이다.&lt;/p&gt;
&lt;p&gt;아직 어떤 컨테이너도 속하지는 않으며, 이 네트워크는 현재 오로지 IP 로만 접근이 가능하다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;2. 이미 만들어 둔 스프링 컨테이너 삭제&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;test-spring&lt;/code&gt; 은 현재 정지되었으며,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;docker run -d -p 8080:8080 test-spring&lt;/code&gt; 으로, 생성된 이미지를 기반으로&lt;/p&gt;
&lt;p&gt;2 개의 옵션이 적용 된 컨테이너로 탄생되었었다.&lt;/p&gt;
&lt;p&gt;그러나, 현재 스프링 서버는 로컬에 노출되어서는 안된다. Nginx 를 통해서 연결되어야 한다.&lt;/p&gt;
&lt;p&gt;따라서, 생성된 컨테이너를 지우고, 새로운 옵션들과 함께 &lt;code&gt;test-spring&lt;/code&gt; 을 실행한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;# 정지된 컨테이너도 포함하여 폴 수 있는 명령어 옵션 --all
➜  ~ docker container list --all
CONTAINER ID   IMAGE         COMMAND                   CREATED         STATUS                      PORTS                    NAMES
d3a682bcb5cc   test-spring   &amp;quot;java -jar app.jar&amp;quot;       22 hours ago    Exited (143) 22 hours ago                            stoic_faraday

# 컨테이너 ID &amp;quot;접두어&amp;quot; 일부를 이용하여 쉽게 삭제
➜  ~ docker container rm d3a
d3a

# Detached 모드는 유지하되, 컨테이너의 고유 이름과 네트워크가 선언된 컨테이너가 생성됨.
#
➜  ~ docker run -d --name=spring1 --network=spring-test-network test-spring
0695c883b5f00b5bfa934cfb0129e63eea1df1682e7ab5c2ea940fe302db52b9

# 랜덤한 이름 대신, 확실하게 NAMES 에 spring1 이 박힌 것을 볼 수 있다.
➜  ~ docker container ls
CONTAINER ID   IMAGE         COMMAND                   CREATED              STATUS              PORTS                    NAMES
0695c883b5f0   test-spring   &amp;quot;java -jar app.jar&amp;quot;       About a minute ago   Up About a minute                            spring1&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;--name&lt;/code&gt; 옵션은 왜 붙였냐면,&lt;/p&gt;
&lt;p&gt;이제 곧 Nginx 이미지를 이용하여 이름과 네트워크, 노출 포트를 설정하는데,&lt;/p&gt;
&lt;p&gt;추가적으로 &lt;code&gt;test-spring&lt;/code&gt; 이미지 기반 컨테이너에 &amp;quot;연결하기&amp;quot; 위해서,&lt;/p&gt;
&lt;p&gt;이 &lt;code&gt;NAMES&lt;/code&gt; 라는 항목이 꼭 필요하다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/etc/nginx/conf.d/default.conf&lt;/code&gt; 는 Nginx 에서 꼭 알아야 할 경로인데,&lt;/p&gt;
&lt;p&gt;이 경로에 적힌 &lt;code&gt;.conf&lt;/code&gt; 파일이, Nginx 가 받은 요청을 &amp;quot;어디로 분산시킬지&amp;quot; 결정하기 때문이다.&lt;/p&gt;
&lt;p&gt;즉, 간단히 말해 코드로 작성하지 않는 &amp;quot;설정 파일&amp;quot; 이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;공식 이미지와 함께 nginx 이미지 &amp;amp; 컨테이너 실행하기&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;현재 과정에서는 Nginx 에 대한 매우 디테일한 설정은 필요가 없다.&lt;/p&gt;
&lt;p&gt;내부 파일 중 &lt;code&gt;/etc/nginx/conf.d/default.conf&lt;/code&gt; 파일만 변경하면 되므로,&lt;/p&gt;
&lt;p&gt;Nginx 공식 이미지를 기반으로 곧바로 실행하면 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;(만약에 이 글을 실제로 실행하는 귀중한 분이 계시다면, 감사합니다.)&lt;/p&gt;
&lt;p&gt;(잠깐 참고용으로 사용할 &lt;code&gt;.conf&lt;/code&gt; 파일은 따로 예제 디렉토리를 생성하시길 권장드립니다.)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Nginx 문법 참고용 추천 사이트&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.docker.com/blog/how-to-use-the-official-nginx-docker-image/#:~:text=We%20configure%20the%20reverse%20proxy%20in&quot;&gt;nginx 문법 특정&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;spring-conf.conf&lt;/code&gt; 예제 파일:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nginx&quot;&gt;server {
  # 80 포트로 수신한다.
  listen 80;

  # nginx 가 동작하고 있음을 알기 위한 경로
  location / {
    return 200 &amp;quot;nginx 게이트웨이는 동작중.&amp;quot;;
  }

  # location 마지막에 &amp;#39;/&amp;#39; 가 붙었는데, 이게 붙지 않으면,
  # 예를 들어 &amp;quot;../api/v1/result&amp;quot; 라는 요청이 간다면,
  # &amp;quot;http://spring1:8080//result&amp;quot; 가 된다.
  # &amp;quot;//result&amp;quot; 는 프로젝트에 해당하는 경로가 없다. --&amp;gt; 404 반환
  location /api/v1/ {
    # uri 로 /api/v1/* 이 날라왔을 때, 밑의 경로로 치환한다.
    # 마지막에 &amp;#39;/&amp;#39; 를 꼭 붙여주어야 하는데, 그렇지 않으면
    # http://sprin1:8080/api/v1 으로 요청이 날라간다.
    # 현재 스프링 프로젝트는 &amp;#39;/api/v1&amp;#39; 이 아니라, &amp;#39;/&amp;#39; 에 매칭되어 있다.
    proxy_pass http://spring1:8080/;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;# &amp;#39;\&amp;#39; 를 통해 명령어를 깔끔하게 여러 줄로 입력 할 수 있음.
# 마지막 줄의 &amp;#39;nginx&amp;#39; 는 공식 nginx 도커 이미지를 가져와서 컨테이너로 구성 한 것을 의미합니다.
# 명령 중간에 또다른 명령어의 결과를 넣고 싶다면, &amp;quot;$(원하는 명령어)&amp;quot; 로 주입 할 수 있습니다.
➜  example docker run -d \
--name=nginx-spring-test \
--network=spring-test-network \
-v &amp;quot;$(pwd)/spring-conf.conf:/etc/nginx/conf.d/default.conf&amp;quot; \
-p 8080:80 nginx
95bc996905c7fa70ea9f03031bb4e11d19275c3a6614e691f44d334120a6d6e5

# 밑의 nginx 컨테이너가 성공적으로 띄워진 것을 볼 수 있습니다.
➜  ~ docker container ls --all
CONTAINER ID   IMAGE         COMMAND                   CREATED         STATUS         PORTS                                     NAMES
95bc996905c7   nginx         &amp;quot;/docker-entrypoint.…&amp;quot;   7 seconds ago   Up 6 seconds   0.0.0.0:8080-&amp;gt;80/tcp, [::]:8080-&amp;gt;80/tcp   nginx-spring-test
0695c883b5f0   test-spring   &amp;quot;java -jar app.jar&amp;quot;       2 hours ago     Up 2 hours                                               spring1&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Request &amp;amp;&amp;amp; Response&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;# 단순 루트 요청시 nginx location / 반응
➜  example curl -X GET &amp;quot;http://localhost:8080&amp;quot;
nginx 게이트웨이는 동작중.

# location &amp;quot;/api/v1/&amp;quot; 을 통해 스프링 &amp;#39;/&amp;#39; 경로에 접근
➜  example curl -X GET &amp;quot;http://localhost:8080/api/v1/&amp;quot;
헬로 월드를 도커 내부에서 실행 - 루트 url     

# /api/v1/result 요청을 통해 스프링 &amp;quot;/result&amp;quot; 경로에 접근
➜  example curl -X GET &amp;quot;http://localhost:8080/api/v1/result&amp;quot;
This is BeanResultSample&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;위의 상황을 그래프로 요약 해 보자&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;---
title : Local 컴퓨터 내의 흐름
---
flowchart TB

Client(&amp;quot;Client - Local 컴퓨터&amp;quot;)

subgraph Docker[&amp;quot;Docker&amp;quot;]
    subgraph D-Network[&amp;quot;도커 네트워크 - 격리 네트워크&amp;quot;]
        direction LR:

        subgraph Nginx[&amp;quot;Nginx - 8080 포트 노출&amp;quot;]
            direction LR
            route1(&amp;quot;경로 - &amp;#39;localhost:8080&amp;#39; &amp;lt;br/&amp;gt; nginx 가동 확인용&amp;quot;)
            route2(&amp;quot;경로 - &amp;#39;localhost:8080/api/v1/ &amp;lt;br/&amp;gt; spring 요청 전달용&amp;quot;)
            route1 ~~~ route2 
        end
        subgraph Spring[&amp;quot;Spring 단일 컨테이너&amp;quot;]
            direction LR
            route1-spring(&amp;quot;경로 - /&amp;quot;)
            route2-spring(&amp;quot;경로 - /result&amp;quot;)
            route1-spring ~~~ route2-spring
        end
        Nginx -- spring1:8080/result --&amp;gt; Spring
    end
end

Client --GET localhost:8080/api/v1/result--&amp;gt; Nginx&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 그래프를 보다 보면, 굳이? 할 수도 있다.&lt;/p&gt;
&lt;p&gt;로컬 Develop 상황에서는 굳이 복잡한 nginx 설정을 구비 할 필요가 없기 때문이다.&lt;/p&gt;
&lt;p&gt;그러나, 나는 클라우드 상황에 대비 할 것이기 때문에, 이러한 방향으로 구축을 해 보았다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;혹시 Nginx 에 대한 전반적인 설명과 문법이 궁금하다면,&lt;/p&gt;
&lt;p&gt;Nginx 설정 파일을 따로 판매하는 국내 업체에서 운영하는 사이트를 보면 되는데,&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://nginxstore.com/training/nginx-reverse-proxy-%EB%A1%9C-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0/&quot;&gt;Nginx Store - Nginx Reverse Proxy 구성하기&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;이 사이트를 참조하면 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;내가 GUI 를 적극 이용하지 않은 이유&lt;/h3&gt;
&lt;p&gt;나는 Graphical User Interface, 즉 macOS 나, Window 와 같은 환경에 설치하는&lt;/p&gt;
&lt;p&gt;Docker Desktop 의 편리한 기능을 이용하지 않고,&lt;/p&gt;
&lt;p&gt;CLI(Commond Line Interface) 를 이용하여 Docker 를 구축했다.&lt;/p&gt;
&lt;p&gt;그 이유는 생각보다 간단한데, GUI 는 편리 한 대신 사용할 수 있는 장소가 매우 한정적이며,&lt;/p&gt;
&lt;p&gt;CLI 는 어디서든 사용할 수 있기 때문이다.&lt;/p&gt;
&lt;p&gt;나는 클라우드에서 Docker 를 운용해야 하는데, &lt;/p&gt;
&lt;p&gt;당연히 GUI 는 존재하지 않는다. CLI 로만 운용해야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;아직 Docker 에 대한 설명이 부족합니다. - Need Docker-Compose&lt;/h3&gt;
&lt;p&gt;단순 &lt;code&gt;Dockerfile&lt;/code&gt; 수준과 명령어만을 이용하여 인프라를 구축한다면,&lt;/p&gt;
&lt;p&gt;나중에 네트워크와 더불어, 컨테이너 복제, 확장, 축소 등등의 기능이 매우 어려워 진다.&lt;/p&gt;
&lt;p&gt;Docker 의 산출물에서 특정 변화가 필요 할 경우, 위에서 선보인 예제와 비슷하게&lt;/p&gt;
&lt;p&gt;수많은 옵션을 따로 CLI 로 적어야 하기 때문이다.&lt;/p&gt;
&lt;p&gt;해당 이미지로 실행할 컨테이너가 &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;어떤 이름을 가질지&lt;/li&gt;
&lt;li&gt;어떤 Docker Network 에 속할지&lt;/li&gt;
&lt;li&gt;어떤 로컬 Volumn 을 참조할지&lt;/li&gt;
&lt;li&gt;서버는 몇 개로 확장할지&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이에 대한 내용을 명령어로 지속적으로 실행한다면, 번거로움이 따라올 수 밖에 없다.&lt;/p&gt;
&lt;p&gt;따라서, 이에 대한 내용의 후속편으로 &lt;code&gt;Docker-Compose&lt;/code&gt; 를 다루거나,&lt;/p&gt;
&lt;p&gt;도커의 이해에 필요한 또 다른 내용으로 작성을 하게 될 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;h2&gt;참조 사이트&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;IBM - Docker 란?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.ibm.com/kr-ko/think/topics/docker&quot;&gt;https://www.ibm.com/kr-ko/think/topics/docker&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;위키백과 - 도커(소프트웨어)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.wikipedia.org/wiki/%EB%8F%84%EC%BB%A4_(%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4)&quot;&gt;https://ko.wikipedia.org/wiki/%EB%8F%84%EC%BB%A4_(%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4)&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Spring 공식문서 - Spring Boot with Docker&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://spring.io/guides/gs/spring-boot-docker&quot;&gt;https://spring.io/guides/gs/spring-boot-docker&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;DockerDocs 공식문서 - Writing a Dockerfile&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://docs.docker.com/get-started/docker-concepts/building-images/writing-a-dockerfile/&quot;&gt;https://docs.docker.com/get-started/docker-concepts/building-images/writing-a-dockerfile/&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Docker Hub 레지스트리 - eclipse-temurin:17-jre-focal&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://hub.docker.com/layers/library/eclipse-temurin/17-jre-focal/images/sha256-4b65dc03c8f221944e4cf5d964660c5e443fd04e0007f9b63489ddb833fe5c7e&quot;&gt;https://hub.docker.com/layers/library/eclipse-temurin/17-jre-focal/images/sha256-4b65dc03c8f221944e4cf5d964660c5e443fd04e0007f9b63489ddb833fe5c7e&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Docker Hub 레지스트리 - nginx&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://hub.docker.com/_/nginx&quot;&gt;https://hub.docker.com/_/nginx&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Docker 공식문서 - How to Use the NGINX docker Official Image&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.docker.com/blog/how-to-use-the-official-nginx-docker-image/&quot;&gt;https://www.docker.com/blog/how-to-use-the-official-nginx-docker-image/&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Nginx Store(국내기업)- Nginx Reverse Proxy 구성하기&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://nginxstore.com/training/nginx-reverse-proxy-%EB%A1%9C-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0/&quot;&gt;https://nginxstore.com/training/nginx-reverse-proxy-%EB%A1%9C-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0/&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;</description>
      <category>Computer Science</category>
      <category>CLI</category>
      <category>docker</category>
      <category>docker network</category>
      <category>NGINX</category>
      <category>Spring</category>
      <category>기초</category>
      <category>도커</category>
      <category>도커 네트워크</category>
      <category>스프링</category>
      <category>컨테이너</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/244</guid>
      <comments>https://codecreature.tistory.com/244#entry244comment</comments>
      <pubDate>Tue, 18 Nov 2025 22:42:06 +0900</pubDate>
    </item>
    <item>
      <title>IoC, DI, DIP 개념을 Spring &amp;amp; Code 로 알아보자</title>
      <link>https://codecreature.tistory.com/243</link>
      <description>&lt;h2&gt;제목 : IoC, DI, DIP 들은 무엇인가? - Spring&lt;/h2&gt;
&lt;h3&gt;부제목 : 코드로 보는 제어의 역전&lt;/h3&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;혹시 궁금한 점이 있으시다면, 언제든지 질문 주시면 환영합니다&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Email : &lt;code&gt;rhdwhdals8765@gmail.com&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;이 글을 작성하는 이유란&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;제어의 역전이라는 단어는 다양한 프로그래밍 언어를 초월하여,&lt;/p&gt;
&lt;p&gt;Well Made 된 Back-End 프레임워크에 포함되어 있는 개념이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;제어의 역전이라는 의미는 우리가 작성하는 코드를 프로그램에서 관리하도록 만든다는 &lt;/p&gt;
&lt;p&gt;그러한 추상적인 개념 또한 포함하지만,&lt;/p&gt;
&lt;p&gt;정작 프로그래밍 언어를 많이 접하지 못하고, Spring 을 접했을 때는 이해하지 못했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;Spring, 즉, Spring Boot 는 매우 방대한 라이브러리가 포함된 프레임워크이다.&lt;/p&gt;
&lt;p&gt;내가 NodeJS 분야에서 NestJS 로 프로젝트를 2 번 실행했었다.&lt;/p&gt;
&lt;p&gt;그 당시에는 필요한 라이브러리를 따로 어디서 다운받아야 하는지 외워야 하는 상황이 있었는데,&lt;/p&gt;
&lt;p&gt;Spring 은 검증되지 않은 Third-Party 제품(프로그램)이 대부분 필요가 없으며,&lt;/p&gt;
&lt;p&gt;전문화 된 조직이 운영하는 검증된 라이브러리로 거의 모든 것을 해결 할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이는 전문화 된 조직이나 특정 단체가 &amp;quot;도메인&amp;quot;(Domain) 을 운영하고,&lt;/p&gt;
&lt;p&gt;이 도메인을 기반으로 Maven Registry 에 업로드하기 때문인데,&lt;/p&gt;
&lt;p&gt;이 때문에 NPM 과는 비교할 수 없는 오픈소스 업로드 난이도가 존재한다.&lt;/p&gt;
&lt;p&gt;그러나, 이러한 난이도 장벽이 존재하여 역설적으로 Maven 라이브러리의 신뢰도는 굉장히 높다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;조각조각 나뉘어져 있는 기능들을 어느 정도는 외워 의존성을 적용시켜야 하는 nodejs 와는 다르게,&lt;/p&gt;
&lt;p&gt;방대한 기능들을 Package 하여 다운로드 하는 것이 NodeJS 와 Java 프레임워크의 차이점이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;다시 주제로 돌아와서,&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;제어의 역전이라는 개념 자체에 집중 할 필요가 있다.&lt;/p&gt;
&lt;p&gt;제어의 역전이라는 것이 무엇일까?&lt;/p&gt;
&lt;p&gt;프로그래머가 된 우리들은 모두 Spring 및 백엔드 프레임워크를 사용하지 않더라도, &lt;/p&gt;
&lt;p&gt;&amp;quot;제어의 역전&amp;quot;(Invertion of Control) 을 수없이 경험했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;Spring 에서의 제어의 역전은,&lt;/p&gt;
&lt;p&gt;Spring 프로젝트를 실행하며 생성되는 &amp;quot;스프링 컨테이너&amp;quot; 에,&lt;/p&gt;
&lt;p&gt;우리가 사용할 비즈니스 로직이나, 내가 직접 만든 Bean or Component 를 위임하여&lt;/p&gt;
&lt;p&gt;대신 제어하도록 맡기는 행위를 의미한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;Spring 에서의 제어의 역전이 어떤 역할을 하는가? 한다면 여러 대답이 생각나기는 하는데,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Spring 사용자가 구축해야 할 비즈니스 논리에만 집중하도록 도와준다.&lt;/li&gt;
&lt;li&gt;작성된 비즈니스 논리(Stateless) 클래스가 재사용 될 수 있게 만들어 준다.&lt;ul&gt;
&lt;li&gt;그렇게 만들지 않으면 1 번의 요청마다 수없이 많은 객체를 재생성 하게 되는 대참사가 벌어진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;코드 자체에서도 굉장한 이점이 있는데, 즉, 관심사에 따라서 Logic 을 분리할 수 있다.&lt;ul&gt;
&lt;li&gt;추상화시키는 것이 왜 좋냐면, 우리가 어떤 DB 를 선택 할 줄 미리 알고 있을까?&lt;/li&gt;
&lt;li&gt;또한, 우리가 선택한 DB 를 영원히 사용하게 될까? - 중간에 바꿀 수도 있음.&lt;/li&gt;
&lt;li&gt;기능이 추가되거나 제거 될 때, 수십, 수백번의 수정을 미리 예방하는 중요한 개념&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;맨 처음 말했듯, 비즈니스 논리에만 집중할 수 있게, Annotation 을 통해 메타데이터 프로그래밍을 이루어 놨다.&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;p&gt;맨 처음 제어의 역전이 &amp;quot;주&amp;quot; 를 이루는 라이브러리 혹은 프레임워크를 사용한다면,&lt;/p&gt;
&lt;p&gt;이해하기가 매우 어려울 것이라고 판단하는데,&lt;/p&gt;
&lt;p&gt;가장 중요한 프로그래밍 이라는 개념을 배우기 위해 우리가 하는 초기 행위에 정답이 있다고 생각한다.&lt;/p&gt;
&lt;p&gt;어떠한 프로그래밍 언어를 선택해도, &amp;quot;우리가 제어하는&amp;quot; Logic 을 작성하기 때문이다.&lt;/p&gt;
&lt;p&gt;이건 당연한 공부 행위이다.&lt;/p&gt;
&lt;p&gt;역설적이게도, IoC 라는 개념 자체가 프로그램에 적용되면, 그 코드를 분해해서 이해하는 것은&lt;/p&gt;
&lt;p&gt;난이도가 정말 높은 행동이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;TypeScript 를 통해 배웠었던 이전의 IoC 기능&lt;/h2&gt;
&lt;p&gt;나는 JavaScript(ECMAScript) 에 적극적으로 도입된 &lt;code&gt;Decorator&lt;/code&gt; 기능을 통해,&lt;/p&gt;
&lt;p&gt;메타프로그래밍이 어떤 것인지, 어떻게 적용되는지 직접 글을 작성했었다.&lt;/p&gt;
&lt;p&gt;단순히 이해하고 넘기는 것이 아니라, 그나마 이해가 되도록 글로 만들어내는 것은,&lt;/p&gt;
&lt;p&gt;절대로 쉽지 않은 작업이라고 단언할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/195&quot;&gt;타입스크립트와 데코레이터&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;위의 글은 IoC(제어의 역전) 이라는 개념에 대해서 상세하게 다루지는 않고,&lt;/p&gt;
&lt;p&gt;TypeScript 의 Decorator 라는 기능은 무엇인지,&lt;/p&gt;
&lt;p&gt;TypeScript 가 어떻게 JavaScript 로 트랜스파일 하는지 알기 위해 작성한 글이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이 과정에서 TypeScript 의 Decorator(데코레이터) 나, &lt;/p&gt;
&lt;p&gt;Java 의 Annotation(애너테이션) 의 기능이 매우매우 흡사하다는 것을 알게 되었다.&lt;/p&gt;
&lt;p&gt;물론, 디테일하게 보면 기능적인 부분이 조금씩은 다르다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Spring 이 IoC 를 위한 컨테이너를 만드는 간단한 과정&lt;/h2&gt;
&lt;p&gt;Spring 은 개발자들이 비즈니스 논리, 및 관심사 분리 등등 에 집중하기 위해&lt;/p&gt;
&lt;p&gt;IoC, 즉, 개발자가 선언한 클래스를 담아두는 컨테이너를 생성한다.&lt;/p&gt;
&lt;p&gt;프로그래머들은 프로젝트에 사용될 논리를 Annotation(&lt;code&gt;@&lt;/code&gt;) 을 작성하여&lt;/p&gt;
&lt;p&gt;Spring 컨테이너가 인식 할 수 있도록 만들어 놓는데,&lt;/p&gt;
&lt;p&gt;흔하게 &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@Controller&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Service&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Repository&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;가 있으며,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@Configuration&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Bean&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Component&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Scope&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;등등 이 존재한다.&lt;/p&gt;
&lt;p&gt;만약에 Java 에서 제공하는 Default(기본 제공) 애너테이션을 알고 싶다면,&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://en.wikipedia.org/wiki/Java_annotation&quot;&gt;위키피디아 - Java Annotation&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;이곳을 참조하는 것을 추천한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;Spring 은 내가 위에서 따로 예시로 적어둔 Annotation 들을 읽고,&lt;/p&gt;
&lt;p&gt;우리가 만들어 둔 클래스, 혹은 메서드, 변수 단위의 &amp;quot;메타데이터&amp;quot; 를 읽는다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;즉, 애너테이션 선언을 통해 Spring 에게 내 코드에 대한 전반적인 정보를 제공하는 것이다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Spring 은 애너테이션과 함께 작성된 코드가 어떤 식으로 생성되어야 하는지,&lt;/p&gt;
&lt;p&gt;어떤 &amp;quot;시점&amp;quot; 에 생성되어야 하는지 분석하고,&lt;/p&gt;
&lt;p&gt;또한 &amp;quot;어떤 정보&amp;quot; 를 주입(DI - Dependency Injection) 해야 하는지 분석한다.&lt;/p&gt;
&lt;p&gt;그리고 Spring Container 는, &lt;code&gt;@Scope&lt;/code&gt; 를 통해 생명주기가 직접 주어지지 않은 이상,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Singleton&lt;/code&gt; 으로 분석한다.&lt;/p&gt;
&lt;p&gt;이 말은, 스프링 서버가 가동하고 나서, &amp;quot;꺼질 때 까지&amp;quot; 1 개로 공유한다는 의미이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;즉, 우리가 특정 기능을 사용하기 위해 Spring 프레임워크 안에서&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Component 
public class TestComponent {
    public String returnSomeText() {
        return &amp;quot;테스팅 컴포넌트를 사용했습니다.&amp;quot;;
    }
}

/** 파일이 분리되어 있거나, 혹은 함께 있거나. */

@RestController
public class Test1Controller {

    private final TestComponent component;

    @Autowired
    public Test1Controller (TestComponent component) {
        this.component = component;
    }

    @GetMapping(&amp;quot;/test1&amp;quot;)
    public testRoute() {
        return component.returnSomeText();
    }
}

/** 다른 파일에 분리된 컨트롤러라고 가정 */

@RestController
public class Test2Controller {

    private final TestComponent component;

    @Autowired
    public Test2Controller (TestComponent component) {
        this.component = component;
    }

    @GetMapping(&amp;quot;/test2&amp;quot;)
    public testRoute() {
        return component.returnSomeText();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;아직 인터페이스도 구축하지도 않은 아주 간단한 위의 코드가&lt;/p&gt;
&lt;p&gt;Spring 에서 작성되고, 실행되었다고 가정 해 보자.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;Spring 코드는 위의 코드를 가져와서 분석 할 때,&lt;/p&gt;
&lt;p&gt;각 Controller 클래스의 생성자를 실행 할 때,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;TestComponent&lt;/code&gt; 는 어떠한 형태로 주입될까?&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;Spring 은 실행 되면서,&lt;/p&gt;
&lt;p&gt;애너테이션이 붙은 클래스들을 단순한 메타데이터의 형태로 &amp;quot;저장되어 있다&amp;quot; 라는 가정으로,&lt;/p&gt;
&lt;p&gt;controller 클래스 내부에 &lt;code&gt;@Autowired&lt;/code&gt; 가 있는 것을 발견한다.&lt;/p&gt;
&lt;p&gt;해당 생성자의 인자 메타데이터를 가져오는데,&lt;/p&gt;
&lt;p&gt;어라? &lt;code&gt;TestComponent&lt;/code&gt; 구현체 or 클래스 타입을 필요로 한다.&lt;/p&gt;
&lt;p&gt;이 때, Spring Container 는 &lt;code&gt;TestComponent&lt;/code&gt; 를 검색한다.&lt;/p&gt;
&lt;p&gt;Spring 컨테이너는 &lt;code&gt;TestComponent&lt;/code&gt; 의 객체 주소를&lt;/p&gt;
&lt;p&gt;각 컨트롤러의 생성자에 &amp;quot;인자로&amp;quot; 넘겨 준다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;여기서 이해가 안 갈 수도 있는 부분&lt;/h3&gt;
&lt;p&gt;우리가 어떤 프로그래밍 언어를 사용하던, 인자를 넘겨준다는 행위는,&lt;/p&gt;
&lt;p&gt;직접적으로 코드를 작성하여 객체를 생성하거나, 메서드를 실행한다는 의미이다.&lt;/p&gt;
&lt;p&gt;그런데, Spring 에서는(특정 상황을 제외) &lt;/p&gt;
&lt;p&gt;우리가 Controller 클래스에 인자로 &lt;code&gt;TestComponent&lt;/code&gt; 를 전해줘야 할 것 같지만,&lt;/p&gt;
&lt;p&gt;우리는 그렇게 하지 않았다. 아니, 그런 적이 없다.&lt;/p&gt;
&lt;p&gt;우리가 한 것은, 단지 생성자, 혹은 클래스 의존성 주입 객체(DI) 인자에,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@Autowired&lt;/code&gt; 를 선언 해 준 것 뿐이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;Spring 에서는 마법이라도 부린 듯, 매우 간편하게 필요한 객체를 &amp;quot;보이지 않는 곳&amp;quot; 에서&lt;/p&gt;
&lt;p&gt;주입(DI - Dependency Injection) 한 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;Spring 은 자신만의 Container 에서 클래스들을 보관하고 있다.&lt;/p&gt;
&lt;p&gt;그리고, @Autowired` 를 보고, 선언된 메타데이터를 읽어&lt;/p&gt;
&lt;p&gt;이것이 &amp;quot;변수&amp;quot; 인지, &amp;quot;생성자&amp;quot; 인지 구분한다. (두 상황 모두 거의 비슷함)&lt;/p&gt;
&lt;p&gt;그리고 필요로 하는 Type(Class), or Interface 를 읽어&lt;/p&gt;
&lt;p&gt;Spring Container 가 보존하고 있는 &amp;quot;객체의 주소&amp;quot; 를 할당한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그렇게 되면, 선언된 2 개의 컨트롤러 객체는 &lt;/p&gt;
&lt;p&gt;따로따로 생성된 &lt;code&gt;TestComponent&lt;/code&gt; 를 할당받는 것이 아니라,&lt;/p&gt;
&lt;p&gt;이미 Spring Container 에 등록된 &lt;code&gt;TestComponent&lt;/code&gt; 를 &amp;quot;공유&amp;quot;하는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Autowired 가 그래서 의미하는 것은,&lt;/h3&gt;
&lt;p&gt;Spring Container 에 등록된 클래스 객체를,&lt;/p&gt;
&lt;p&gt;개발자가 &amp;quot;코드로 직접&amp;quot; 넣어주는 것이 아니라,&lt;/p&gt;
&lt;p&gt;Spring 이 이를 인식하여 자동으로 &amp;quot;의존성 주입&amp;quot; 을 해주는 것을 의미한다.&lt;/p&gt;
&lt;p&gt;Autowired, 즉, 자동으로 연결해 주는 것이다.&lt;/p&gt;
&lt;p&gt;두 컨트롤러에 요청을 넣는다면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;TestComponent&lt;/code&gt; 라는 1 개의 단일 객체가 사용된다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;매우 중요한 개념, DIP 란?&lt;/h2&gt;
&lt;p&gt;내가 Spring Container 가 어떻게 IoC, 및 DI 를 하는지에 대한 예시는 &lt;/p&gt;
&lt;p&gt;간략하게 보여주었다고 생각하지만, 아주 중요한 점을 간과한 예제이다.&lt;/p&gt;
&lt;p&gt;그건 바로 &lt;code&gt;DIP&lt;/code&gt; 라는 개념인데,&lt;/p&gt;
&lt;p&gt;Depencency Inversion Principle,&lt;/p&gt;
&lt;p&gt;즉, 의존 관계 역전 이라는 매우매우 중요한 기능이 빠져있다.&lt;/p&gt;
&lt;p&gt;이게 무엇일까?&lt;/p&gt;
&lt;p&gt;개념적으로 접근하자면, 인터페이스를 통해서&lt;/p&gt;
&lt;p&gt;명확한 의존관계가 설정되는 것을 &amp;quot;최대한 피하는&amp;quot; 로직이다.&lt;/p&gt;
&lt;p&gt;&amp;quot;의존 관계 역전.&amp;quot;&lt;/p&gt;
&lt;p&gt;이해하면 매우 간략하며 잘 설명된 단어라고 생각이 들지만,&lt;/p&gt;
&lt;p&gt;정확히 이해하지 않으면 뭔 소린지 알기 힘들다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;예를 들어, Spring 프로젝트와 함께 사용될 Database 가,&lt;/p&gt;
&lt;p&gt;개발 상황에서 SQLite, 프로덕션에서는 MySQL 이 사용된다고 가정 해 보자.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;(&lt;code&gt;@Repository&lt;/code&gt; 를 사용하면 헷갈릴 것 같아서 &lt;code&gt;@Component&lt;/code&gt; 로 대체하겠스빈다.)&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Component 
public class SQLite {
    public String whatDB() {
        return &amp;quot;현재 데이터베이스는 &amp;quot; + getClass.getSimpleName();
    }
}

@Component 
public class MySQL {
    public String whatDB() {
        return &amp;quot;현재 데이터베이스는 &amp;quot; + getClass.getSimpleName();
    }
}


/** 개발 중이라고 가정 */

@RestController
public class TestController {

    private final SQLite db;

    @Autowired
    public TestController (SQLite db) {
        this.db = db;
    }

    @GetMapping(&amp;quot;/current-db&amp;quot;)
    public currDB() {
        return db.whatDB();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;만약에 &lt;code&gt;.../current-db GET&lt;/code&gt; 요청을 넣는다면?&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;현재 데이터베이스는 SQLite&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;위의 코드를 Spring 에서 실행한다면, 오류는 없이 잘 실행 될 것이다.&lt;/p&gt;
&lt;p&gt;그러나, 이는 개발 생산성에 있어 중대한 오류를 낳는다.&lt;/p&gt;
&lt;p&gt;위 예시에서는 &amp;quot;개발 상황이니까&amp;quot;, &lt;code&gt;TestController&lt;/code&gt; 에서 &lt;code&gt;SQLite&lt;/code&gt; 를 직접 사용했다.&lt;/p&gt;
&lt;p&gt;따라서, 개발 DB 로 사용될 Database 를 사용하게 되는 것이다.&lt;/p&gt;
&lt;p&gt;그러나, 데이터베이스는 Spring 프로젝트 자체로 보았을 때,&lt;/p&gt;
&lt;p&gt;매우 특정된 상황에서만 사용되는 것이 아니라, 매우 광범위한 장소에서 사용된다.&lt;/p&gt;
&lt;p&gt;수십, 수백개의 파일이 &lt;code&gt;SQLite&lt;/code&gt; 라는 선언에 &amp;quot;의존&amp;quot; 하고 있는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;상상해보자&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;수십, 수백개의 서비스가 &amp;quot;개발&amp;quot; 중이라서 &lt;code&gt;SQLite&lt;/code&gt; 를 선언했다고 가정하고,&lt;/p&gt;
&lt;p&gt;이제 프로덕션으로 내놓기 위해 &lt;code&gt;MySQL&lt;/code&gt; 을 사용한다.&lt;/p&gt;
&lt;p&gt;이제, 이를 만든 개발자는 수십, 수백개의 파일에 걸쳐 &amp;quot;모든&amp;quot; 선언을 &lt;code&gt;MySQL&lt;/code&gt; 로 바꾸어야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;정말 상상하기 어려울 정도의 어지러운 상황이 펼쳐지는 건데,&lt;/p&gt;
&lt;p&gt;이러한 상황이 &amp;quot;왜?&amp;quot; 나오게 되었을까?&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이는 종속성 적인 시각으로 보지 않았을 때 나오게 되는 것인데,&lt;/p&gt;
&lt;p&gt;만약에 &amp;quot;사용하는 모듈&amp;quot; 로 보게 된다면,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TB

TestController(&amp;quot;TestController&amp;quot;)

TestController --&amp;gt; SQLite &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;로 해석 할 수 있다.&lt;/p&gt;
&lt;p&gt;즉, 상위 모듈이 하위 모듈을 참조하는 형태가 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, &lt;strong&gt;의존적 시각&lt;/strong&gt; 으로 바라보자 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TB
SQLite

com1(&amp;quot;컴포넌트1&amp;quot;)
com2(&amp;quot;컴포넌트2&amp;quot;)
com3(&amp;quot;컴포넌트3&amp;quot;)
com4(&amp;quot;컴포넌트4&amp;quot;)
com5(&amp;quot;등등 수십개&amp;quot;)

SQLite &amp;lt;--&amp;gt; com1 &amp;amp; com2 &amp;amp; com3 &amp;amp; com4 &amp;amp; com5&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;데이터베이스에 접근하는 컴포넌트가 &amp;quot;수십 개&amp;quot; 이며, 추상화(&lt;code&gt;interface&lt;/code&gt;) 를 사용하지 않았다면,&lt;/p&gt;
&lt;p&gt;우리가 &amp;quot;직접적으로&amp;quot; 선언한 모든 데이터베이스 객체를 &amp;quot;전부&amp;quot; &lt;code&gt;MySQL&lt;/code&gt; 로 바꿔야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;대참사를 막는 interface 의 중요성.&lt;/h3&gt;
&lt;p&gt;다시 언급하자면,&lt;/p&gt;
&lt;p&gt;DIP 는 상위 모듈이, 하위 모듈에 접근하여 &lt;/p&gt;
&lt;p&gt;특정 의존성을 형성되는 것을 &amp;quot;방지하는&amp;quot; 원칙이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;자주 사용되며, 특정 상황에 따라 변할수도 있는 &amp;quot;관심사&amp;quot;가 있다면,&lt;/p&gt;
&lt;p&gt;해당 영역을 어떻게든 &lt;code&gt;추상화&lt;/code&gt; 시켜야 한다는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;DIP 원칙을 지킨 코드로 변경 해 보자면,&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 추상화 레벨로 접근 할 수 있도록 인터페이스를 선언한다.
public interface MainDB() {
    public String whatDB();
}

@Component 
@Profile(&amp;quot;dev&amp;quot;)
public class SQLite implements MainDB {
    @Override
    public String whatDB() {
        return &amp;quot;현재 데이터베이스는 &amp;quot; + getClass.getSimpleName();
    }
}

@Component 
@Profile(&amp;quot;prod&amp;quot;)
public class MySQL implements MainDB {
    @Override
    public String whatDB() {
        return &amp;quot;현재 데이터베이스는 &amp;quot; + getClass.getSimpleName();
    }
}

@RestController
public class TestController {
    // 인터페이스 타입을 의존
    private final MainDB db;

    // 인터페이스로 인자를 받기
    @Autowired
    public TestController (MainDB db) {
        this.db = db;
    }

    @GetMapping(&amp;quot;/current-db&amp;quot;)
    public currDB() {
        return db.whatDB();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 코드의 변경점을 통해, 데이터베이스를 접근하기 위해서 &lt;code&gt;interface&lt;/code&gt; 를 사용한다.&lt;/p&gt;
&lt;p&gt;즉 추상화를 통해서 모듈 자체의 Level 을 맞춘 것이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@Profile&lt;/code&gt; 은 Spring 의 개발 혹은 제품 상태에 따라서&lt;/p&gt;
&lt;p&gt;&lt;code&gt;MainDB&lt;/code&gt; 가 선언되는 코드는 &amp;quot;이 상황에서&amp;quot; 해당 객체가 주입되도록 만든 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;데이터베이스에 접근하는 두 객체, &lt;code&gt;SQLite&lt;/code&gt;, &lt;code&gt;MySQL&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;두 객체 모두 &lt;code&gt;MainDB&lt;/code&gt; 라는 인터페이스를 구현했다.&lt;/p&gt;
&lt;p&gt;두 객체는 Spring Container 에게 Scanning 되는 과정에서 등록되며,&lt;/p&gt;
&lt;p&gt;서비스 객체에서 단순 &lt;code&gt;MainDB&lt;/code&gt; 를 조회하게 될 때,&lt;/p&gt;
&lt;p&gt;상황에 맞게 &amp;#39;알아서&amp;#39; 적절한 &lt;code&gt;MainDB&lt;/code&gt; -&amp;quot;구현체&amp;quot;- 를 전달 해 준다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;이를 그래프로 표현 해 보자면,&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TB;

SQLite
MySQL

MainDB(&amp;quot;MainDB - 인터페이스&amp;quot;)

SQLite &amp;amp; MySQL -.-&amp;gt; MainDB

com1(&amp;quot;컴포넌트1&amp;quot;)
com2(&amp;quot;컴포넌트2&amp;quot;)
com3(&amp;quot;컴포넌트3&amp;quot;)
com4(&amp;quot;등등 수십개&amp;quot;)

MainDB &amp;lt;--&amp;gt; com1 &amp;amp; com2 &amp;amp; com3 &amp;amp; com4&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 그래프대로 형성된다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;위의 그래프가 효과적으로 보여준 것 - 관심사의 분리&lt;/h3&gt;
&lt;p&gt;이제 DB 전담 &lt;code&gt;interface&lt;/code&gt;, 그리고 이를 구현한 DB &lt;code&gt;class&lt;/code&gt; 덕분에&lt;/p&gt;
&lt;p&gt;Spring 객체들은 데이터베이스에 접근하기 위해서 구체적으로 Class 명을 선언하지 않아도 된다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;그렇기 때문에,&lt;/strong&gt; 우리는 때에 맞춰 데이터베이스를 갈아끼우기만 하면 된다.&lt;/p&gt;
&lt;p&gt;기존 코드를 수정 할 필요가 없으며, 그저 코드 몇 줄로 끝나는 상황이 되었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;그렇다면, 이로 인해 효과적으로 드러난 점은 무엇일까?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;그건 바로, &amp;quot;관심사의 분리&amp;quot; 이다.&lt;/p&gt;
&lt;p&gt;나는 방금 &amp;quot;데이터베이스&amp;quot; 라는 관심사를 &lt;code&gt;interface&lt;/code&gt; 로 추상적으로 구분 한 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;백엔드라는 분야에서, 접근해야 할 기능과 infra 들은 수없이 많다.&lt;/p&gt;
&lt;p&gt;하나의 infra 가 몇 가지 기능을 모두 담당하다가,&lt;/p&gt;
&lt;p&gt;성능을 위해 또 다른 infra 에게 또 다른 기능을 위임할 수도 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;사용자의 이미지를 저장하는 과정에서,&lt;/p&gt;
&lt;p&gt;날 것의 데이터베이스의 &lt;code&gt;blob&lt;/code&gt; 형태를 이용하다가,&lt;/p&gt;
&lt;p&gt;AWS S3 를 사용하여 요청 형태로 저장하는 방식을 선택 할 수도 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;기존의 기능을 다른 기능으로 교체하는 행동은 매우 흔하다.&lt;/p&gt;
&lt;p&gt;또한, 이러한 기능을 서로 복잡하게 연계하여 사용하는 일은 비일비재하다.&lt;/p&gt;
&lt;p&gt;관심사를 추상화 시키지 않고 사용하게 된다면, &lt;/p&gt;
&lt;p&gt;서로 엮여있는 코드를 풀기 매우 힘들어지는데,&lt;/p&gt;
&lt;p&gt;이는 내가 이전에 부트캠프에서 팀 프로젝트를 수행했을 때,&lt;/p&gt;
&lt;p&gt;백엔드 담당을 맡으면서 느꼈었던 부분이었다. (NestJS 를 사용했었을 때 경험입니다)&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;요약하자면,&lt;/h2&gt;
&lt;h3&gt;IoC&lt;/h3&gt;
&lt;p&gt;&amp;quot;제어의 역전&amp;quot; 을 의미하며,&lt;/p&gt;
&lt;p&gt;Spring 도메인에서는 Spring Container 에 저장되는 singleton Scope 객체를 의미한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;singleton&lt;/code&gt; 은, 서버가 꺼질 때 까지 없어지지 않는 객체를 의미하며, 단일 객체이다.&lt;/p&gt;
&lt;p&gt;물론, &lt;code&gt;request&lt;/code&gt; 와 같은 요청 생명주기 기반의 객체도 Spring Container 에 담긴다.&lt;/p&gt;
&lt;p&gt;사용자는 사용되는 객체의 생명주기를 코드로 직접 작성하는 것이 아니라,&lt;/p&gt;
&lt;p&gt;그 객체들의 생명주기를 Spring Container 가 관리한다는 것이&lt;/p&gt;
&lt;p&gt;IoC 를 나타낼 수 있는 구체적인 나의 생각이 아닐까 판단된다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;DI&lt;/h2&gt;
&lt;p&gt;&amp;quot;의존성 주입&amp;quot; 을 의미한다.&lt;/p&gt;
&lt;p&gt;컨트롤러는 주로 서비스를 요구하며,&lt;/p&gt;
&lt;p&gt;서비스는 주로 &amp;quot;또다른 서비스&amp;quot; 나, DB 접근 객체를 요구한다.&lt;/p&gt;
&lt;p&gt;이 과정에서 객체에 선언되는 객체는 &lt;/p&gt;
&lt;p&gt;&lt;code&gt;@Autowired&lt;/code&gt; 를 선언하여 선언한 객체에 &amp;quot;주입&amp;quot; 한다.&lt;/p&gt;
&lt;p&gt;이러한 의존성 주입을 &amp;quot;Spring 이 대신&amp;quot; 한다는 것이 중점이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;DIP&lt;/h2&gt;
&lt;p&gt;&amp;quot;의존성 역전 원칙&amp;quot; 을 의미한다.&lt;/p&gt;
&lt;p&gt;유연함과 확장성을 지키기 위한 전략이며,&lt;/p&gt;
&lt;p&gt;코드의 수정 및 변경을 가장 많이 축소시켜주는 개념이다.&lt;/p&gt;
&lt;p&gt;컨트롤러, 서비스와 같은 대표적인 상위 레벨의 모듈이,&lt;/p&gt;
&lt;p&gt;아주 정확한 기능만을 담당하는 하위 레벨의 모듈에 의존하게 만들지 않으며,&lt;/p&gt;
&lt;p&gt;아주 정확한 기능(MySQL or SQLite or PostgreSQL) 들을 추상화시킨 인터페이스를 구현하여&lt;/p&gt;
&lt;p&gt;해당 기능을 요구하는 컴포넌트들이 &amp;quot;추상화 된 인터페이스&amp;quot; 를 호출하도록 만든다.&lt;/p&gt;
&lt;p&gt;따라서, 이는 소프트웨어에 유연성과 확장성을 부여한다.&lt;/p&gt;
&lt;p&gt;개인적으로, 변경 및 확장 될 수 있는 객체는&lt;/p&gt;
&lt;p&gt;대부분 인터페이스를 통한 구현이 필요하지 않을까 생각이 들 정도의 중요한 개념이라고 생각한다.&lt;/p&gt;
&lt;p&gt;추상화 된 인터페이스를 호출하게 만들어, 의존 레벨을 동등하게 만드는 것을 의미한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;현재 나는 Udemy 의 외국 Spring 강의를 듣고 있다.&lt;/p&gt;
&lt;p&gt;(Spring Boot 3, Spring 6 &amp;amp; Hibernate for Beginners) 라는 이름의 강의이다.&lt;/p&gt;
&lt;p&gt;이 분은 꽤 세심하게 기초를 닦아 올라 갈 수 있게 설명해 주신다.&lt;/p&gt;
&lt;p&gt;그러나, 강의를 들으며 따로 조사를 해야만 이해 할 수 있는 커다란 질문들을 모아&lt;/p&gt;
&lt;p&gt;미리 Markdown 파일로 &amp;quot;제목&amp;quot; 까지만 정해두고 쌓고 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;바로바로 작성하지 않는 이유는, 내가 70% 정도만 아는 상태에서&lt;/p&gt;
&lt;p&gt;그저 공부용으로 작성한 이 글이 누군가에게 95% 의 진실로 다가올 수 있기 때문이다.&lt;/p&gt;
&lt;p&gt;정보의 불일치는 개발자에게 치명적일 수 있다.&lt;/p&gt;
&lt;p&gt;이를 경계하여 Spring 에서의 IoC 도메인의 이해가 95 퍼센트가 되었을 때,&lt;/p&gt;
&lt;p&gt;이 글을 작성 한 것이다.&lt;/p&gt;
&lt;p&gt;누군가에게는 이 글이 도움이 되기를 빕니다.&lt;/p&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;h2&gt;참조 사이트&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;위키피디아 - Java Annotation&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://en.wikipedia.org/wiki/Java_annotation&quot;&gt;https://en.wikipedia.org/wiki/Java_annotation&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;</description>
      <category>Spring</category>
      <category>AOP</category>
      <category>DI</category>
      <category>dip</category>
      <category>ioc</category>
      <category>Spring</category>
      <category>Spring Container</category>
      <category>spring 추상화</category>
      <category>관심사</category>
      <category>분리</category>
      <category>코드 예제</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/243</guid>
      <comments>https://codecreature.tistory.com/243#entry243comment</comments>
      <pubDate>Sat, 15 Nov 2025 01:36:18 +0900</pubDate>
    </item>
    <item>
      <title>내가 Node.js 에서 Spring 으로 백엔드 영역을 변경하는 이유</title>
      <link>https://codecreature.tistory.com/242</link>
      <description>&lt;h2&gt;제목 : 내가 Node.js 에서 Spring 으로 백엔드 영역을 변경하려는 이유&lt;/h2&gt;
&lt;h3&gt;Node.js 환경의 한계점과 지식의 만류귀종&lt;/h3&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;내가 이 글을 작성하는 이유는,&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;나는 대학교에서 소프트웨어학으로 시작하여,&lt;/p&gt;
&lt;p&gt;C, C++, Java 순으로 언어를 배웠다.&lt;/p&gt;
&lt;p&gt;내가 컴퓨터 언어를 배운 것은 간단했었다.&lt;/p&gt;
&lt;p&gt;그 당시엔, &amp;quot;컴퓨터&amp;quot; 전공자들은 취업이 쉬웠다는 것.&lt;/p&gt;
&lt;p&gt;이러한 마인드로 어떻게 공부가 될까,&lt;/p&gt;
&lt;p&gt;당연히 신입생 시절 공부하지 않고 술마시러 가는 날이 잦았다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;학점을 망치고 1학년 2 학기 시작하기 전, 군대를 갔다 왔다.&lt;/p&gt;
&lt;p&gt;또한, 3학년 2 학기 시작하기 전, 나는 세상이 궁금하여 무작정 사업등록을 하고,&lt;/p&gt;
&lt;p&gt;남대문의 허름한 지하 사무실을 구해 모았던 종잣돈으로 모든 제품을 가리지 않고 팔았다.&lt;/p&gt;
&lt;p&gt;양말, 장갑, 모포, 체크무늬 잠옷, 마스크(코로나 당시), 등등,&lt;/p&gt;
&lt;p&gt;초짜라고 하기에도 부족할 정도로 초창기 시절에는 어떻게 제품을 판매하는지 몰라서,&lt;/p&gt;
&lt;p&gt;그저 여러 제품을 수입해서, 저렴한 값에 파는 &amp;quot;경험&amp;quot; 그 자체를 중심했었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;평일에 사무실을 가고, 주말에는 피자배달을 하는 생활을 이루었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그리고 1 년간의 짧은 경험 끝에, 내가 가지고 있는 고유의 전문 지식 없이는,&lt;/p&gt;
&lt;p&gt;그 어떤 상업적 행위도 지속적일 수 없다고 판단했다.&lt;/p&gt;
&lt;p&gt;이 때 당시에는 쿠팡 창고로 모든 제품을 보내 판매를 자동화 시켰었는데,&lt;/p&gt;
&lt;p&gt;이 덕분에 창고가 필요하지 않아 학교로 복학하겠다는 판단이 가능했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;다행히도, 내가 컴퓨터 공학과를 나온 덕이었을까,&lt;/p&gt;
&lt;p&gt;쿠팡의 비즈니스 모델 중 사업자를 대상으로 구독제 정보 프로그램을 파는 것을 눈여겨 보고 있었다.&lt;/p&gt;
&lt;p&gt;이는 복학하기 전, 내가 다시 컴퓨터에 대한 감각을 끌어올리기 위해 만들만한 클론 기능들이 들어있다고 판단했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;나는 제품화 된 프로덕션을 주 목적으로 하기 보다는, 먼저 결과를 볼 수 있는 데모를 빠르게 만들고 싶었다.&lt;/p&gt;
&lt;p&gt;이 과정에서 Node.js 엔진을 바탕으로 실행되는 ECMAScript(JavaScript) 를 사용하게 되었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;네트워크 자체의 I/O 과정이 매우 간단하고 편리하며, 확장 기능을 매우 간단하게 가져 올 수 있는&lt;/p&gt;
&lt;p&gt;NPM 의 생태계로 인해 나도 모를 정도로 빨려들어가고 있었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, Java 가 그저 간단히 중요하다는 것만 인지하여 Java 를 놓지는 않았다.&lt;/p&gt;
&lt;p&gt;일종의, 회사에서 Java 를 사용하니까, Java 를 사용한 프레임워크를 사용하여 백엔드를 작성한다는 느낌이었다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Java 에서 Node.js 를 본격적으로 사용했던 옛 경험&lt;/h2&gt;
&lt;p&gt;나에게 있어 부족한 점이 무엇인가를 그 때 생각했었는데,&lt;/p&gt;
&lt;p&gt;그건 바로 하나의 지식 도메인에서의 전문성이었다.&lt;/p&gt;
&lt;p&gt;나는 그 어떤 것도 전문화되지 않았었다.&lt;/p&gt;
&lt;p&gt;남들이 하지 못한 경험이 있고, 공부 하라면 공부 할 수 있으나, 빠르게 투입하기에는 어려워&lt;/p&gt;
&lt;p&gt;회사 입장에서 나를 바라보았을 때, 굳이? 싶은 느낌이 들 것이라고 판단했다.&lt;/p&gt;
&lt;p&gt;따라서, 나는 프로그래머스의 &amp;quot;TypeScript 와 함께하는 풀스택&amp;quot; 부트캠프를 듣게 되었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;확실히, 프로덕션을 빠르게 뽑아낼 수 있는 언어는 JavaScript 라고 생각한다.&lt;/p&gt;
&lt;p&gt;그러나, 이 Production 의 결과물의 완성도 자체만 놓고 보았을 때는, 각기 다를 것이라 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;NPM 계열의 방대한 의존성 라이브러리와 쉽게 참여 가능한 시스템은&lt;/p&gt;
&lt;p&gt;누군가 만들어 놓은 최적화 프레임워크, 혹은 라이브러리로 나의 트러블슈팅을 쉽게 해결 할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이를 웹에서, 또한 WAS(백엔드) 에서도 깊게 배웠다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;시선을 돌리지 않고, 끝까지 바라보니 보이는 한계&lt;/h3&gt;
&lt;p&gt;Node.js 는 매우 매력적인 엔진이며,&lt;/p&gt;
&lt;p&gt;또한 이와 깊게 연계된 NPM 에코시스템은 너무나 편리하다.&lt;/p&gt;
&lt;p&gt;부트캠프가 끝나고, 나는 지속적으로 작성하는 블로그 글을 작성하던 일상적인 나날이었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;나는 Node.js 의 싱글 스레드 (코드 콜스택 말하는 것) 기반의 엔진이라는 것에 큰 의구심을 품었다.&lt;/p&gt;
&lt;p&gt;다른 Low ~ Middle 수준의 언어들은 다중 스레드를 사용한다.&lt;/p&gt;
&lt;p&gt;그런데, Node.js 는 NestJS 라는 JS 프레임워크에서도 여러 OS 스레드를 사용하지 않았다.&lt;/p&gt;
&lt;p&gt;물론, Node.js 의 비동기 실행 방식과 더불어, 싱글 스레드로 인한 자원 비경쟁으로 인해,&lt;/p&gt;
&lt;p&gt;굳이 다중 스레드를 사용하지 않아도 빠르다는 것은 알고 있었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, 나는 여전히 궁금했다. 누군가는 Spring 에서 NestJS 로 옮기는 것이 트렌드라고 말하며,&lt;/p&gt;
&lt;p&gt;누군가는 Spring 이 여전히 우세라고 말했다.&lt;/p&gt;
&lt;p&gt;위의 의견들 또한 모두 수용 가능했다.&lt;/p&gt;
&lt;p&gt;Spring 이 우세하지만, NestJS 가 트렌디하니까 옮겨보는 개발자들도 많을 거라고 생각한다.&lt;/p&gt;
&lt;p&gt;그러나, 왜? NestJS 의 간결한 프로그램 생산에 비해 Spring 은 프레임워크 자체의 난이도를 가짐에도,&lt;/p&gt;
&lt;p&gt;그 지위가 무너지지 않는지 궁금했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그 이전에 나는, Node.js 기반에서 실행되므로, 기본 실행 자체가&lt;/p&gt;
&lt;p&gt;싱글 스레드라서 일어날 수 있는 성능 저하에 집중했다.&lt;/p&gt;
&lt;p&gt;따라서, 나는 백엔드, 혹은 웹 자체에 적용하여 JS 대신 실행할 수 있게,&lt;/p&gt;
&lt;p&gt;바이트코드로 즉시 실행되는 &lt;code&gt;wasm&lt;/code&gt; 을 분석하고, 이에 대한 글을 작성했었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/202&quot;&gt;WebAssembly 와 Node.js&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;이 글을 작성하면서, 나의 Node.js 에 대한 불신의 씨앗이 생겨났다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이 글을 작성한 직후, &amp;quot;방법론&amp;quot; 으로 이루어진 프로그램 세상에서 살면,&lt;/p&gt;
&lt;p&gt;그 트렌드에 휘둘릴 수 밖에 없다고 깨닫았다.&lt;/p&gt;
&lt;p&gt;직후, tsconfig, WebPack, CRA, React Fiber 와 같은 수많은 프로그램들에 대한 글을 작성하며, 프로그래밍 세상에서 나를 감싸고 있는 방법론을 구체화시켰다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/196&quot;&gt;tsconfig 옵션 의미 (with NestJS)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/214&quot;&gt;Webpack 이라는 번들러는 무엇인가?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/227&quot;&gt;React 런타임 코드 해부 노트 : Fiber 와 Scheduler 를 따라간 21 일&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/234&quot;&gt;create-react-app 은 레거시화 되었다. - 빠르게 변화하는 웹 진영 템플릿에 대해서.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;또한, 현대에 존재하는 대부분의 언어가 모티브로 가질 수 밖에 없는 언어,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;C&lt;/code&gt; 언어를 사용하여 백준 문제를 다시 풀었다.&lt;/p&gt;
&lt;p&gt;C 를 사용하는 과정에서조차, Memory alloc, free 그리고,&lt;/p&gt;
&lt;p&gt;문자열 입출력에서 한 글자라도 틀려선 안되는 &lt;code&gt;fget..&lt;/code&gt; 키워드로 모든 문제를 풀었으며,&lt;/p&gt;
&lt;p&gt;자료 구조와 정렬 및 함수 관심사 분리와 추상화를 전부 진행했다.&lt;/p&gt;
&lt;p&gt;3 줄에 끝낼 수 있는 문제를 전부 처음부터 작성하여 200 줄로 만들어 푼다는 것은,&lt;/p&gt;
&lt;p&gt;보통 힘든 일은 아니었다. 내가 문제에 사용할 모든 유틸리티를 제작하고, 기억했다.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/233&quot;&gt;C 그리고 fgets 라인 입력만으로 tokenizer 메서드 제작하기 (하드코어)&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Node.js 에서 Java 기반으로 돌아오는 과정&lt;/h2&gt;
&lt;p&gt;사실 &amp;quot;방법론&amp;quot; 기반으로 수많은 글을 작성했으며,&lt;/p&gt;
&lt;p&gt;이 글들은 모두 &amp;quot;방법론&amp;quot;, 즉, 나를 편리하게 만들어 주는 것은 나의 포텐셜을 낮추는 것이라고&lt;/p&gt;
&lt;p&gt;판단하고 작성 한 것과 다름이 없다는 것을 인지한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그렇다면, &lt;code&gt;C&lt;/code&gt; 언어에서조차, 그 방법론이 없을까?&lt;/p&gt;
&lt;p&gt;&lt;code&gt;C&lt;/code&gt; 에서조차, 방법론과 컨벤션이 존재한다.&lt;/p&gt;
&lt;p&gt;내가 사용했던 C 컴파일 명령어 &lt;code&gt;gcc&lt;/code&gt; 조차도, 누군가 만들어놓은 컨벤션적인 방법론이라고&lt;/p&gt;
&lt;p&gt;치부 할 수도 있는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;Node.js 의 너무 높은 추상화 레벨로 인해 &amp;quot;방법론&amp;quot; 을 경계하는 데 꽂혀있었는데,&lt;/p&gt;
&lt;p&gt;나는 C 를 다루는 동안,&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;이건 너무나 낮은 언어 레벨인 C 니까,&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;내부적으로 장착되어 있는 컨벤션과 도구에 집중하지 못했다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;결국 이중적으로 나는 방법론을 사용하며 방법론을 경계하는 사람이 되었다.&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Golang 과 Java 사이에서 갈등하다.&lt;/h3&gt;
&lt;p&gt;이번에 새롭게 프로젝트를 풀스택으로 만들되,&lt;/p&gt;
&lt;p&gt;웹은 JS 에 최적화 되어 있으므로,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vite 기반의 React&lt;/li&gt;
&lt;li&gt;Backend&lt;/li&gt;
&lt;li&gt;Redis - 데이터 캐싱용&lt;/li&gt;
&lt;li&gt;DB - PostgreSQL&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;을 사용하기로 결정했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;여기서 백엔드를 어떤 언어로 사용 할 지 Golang 과 Java 사이에서 심각하게 고민했다.&lt;/p&gt;
&lt;p&gt;golang 을 사용하며 마주하게 될 벽이 존재 할 것이다.&lt;/p&gt;
&lt;p&gt;이 과정에서 아직 존재하지 않은 오픈소스가 존재한다면, 내가 만들면 어떨까? 하는 생각도 들었다.&lt;/p&gt;
&lt;p&gt;그리고 Java 는 튼튼하고 검증된 Spring Boot 라는 심플한 프레임워크 패키지가 존재했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;Golang 은 메모리가 Spring 보다 메모리 사용량이 낮으며, 기계에 더 가깝다.&lt;/p&gt;
&lt;p&gt;Spring 은 그 자체로 검증되었으며, WAS 로서의 기능을 내가 제작할 필요가 없다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;더 고민되었던 것은, 나는 Oracle Cloud 를 사용할 것이라서,&lt;/p&gt;
&lt;p&gt;몇 개 없는 컴퓨팅 리소스 안에 내가 개발하게 될 프로그램들을&lt;/p&gt;
&lt;p&gt;컨테이너화 시켜 넣어 놓을 것이었는데,&lt;/p&gt;
&lt;p&gt;이는 정해진 크기의 책꽂이(메모리)에 프로그램을 끼워넣는 형태에 가까웠다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;따라서 golang 을 선택해야 하나? 생각했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, Spring 에서, GraalVM Image 라는 것을 통해,&lt;/p&gt;
&lt;p&gt;메모리 사용량을 절반 이하로 낮출 수 있는 방법을 찾았다.&lt;/p&gt;
&lt;p&gt;따라서, 내가 선택 한 것은 Java With Spring 이었다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Java 외에 다른 프레임워크도 많지 않나?&lt;/h2&gt;
&lt;p&gt;위에서 언급한 WASM, (Web Assembly Module) 을 다루면서,&lt;/p&gt;
&lt;p&gt;나는 Rust, Zig 와 같이 현대적이며, C 혹은 C++ 수준의 Low Level 을 다루는 언어들을 알게 되었다.&lt;/p&gt;
&lt;p&gt;이들의 홈페이지를 들어가서 문법만 본 것이 아니라,&lt;/p&gt;
&lt;p&gt;이들의 웹, 혹은 WAS 개발은 어떠한 형태로 진행되는지 살펴보기도 했었다.&lt;/p&gt;
&lt;p&gt;이 2 개의 언어는 메모리 할당과 해제가 굉장히 엄격하다는 공통점이 있지만,&lt;/p&gt;
&lt;p&gt;Rust 는 메모리의 주체와 빌림(Borrow), Zig 는 C 와 일관되며 공존하는 생태계를 보였다.&lt;/p&gt;
&lt;p&gt;Rust 또한, C, C++ 과 공존할 수 있는 기능이 존재하지만,&lt;/p&gt;
&lt;p&gt;그들의 생태계는 기존의 C, C++ 기능을 공격적으로 마치 &amp;quot;정복하듯이&amp;quot;&lt;/p&gt;
&lt;p&gt;변경한다는 점에서 공존을 택하진 않는다는 느낌을 받았다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;거의 C, C++ 수준의 이러한 현대적 언어들은, 이제 웹 개발도 가능한 라이브러리가 존재하며,&lt;/p&gt;
&lt;p&gt;또한 Spring 처럼 WAS 를 만들 수도 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, 여기서 매우 중요한 점이 존재한다.&lt;/p&gt;
&lt;p&gt;유지보수, 지속 가능성, 코드 가독성, 디버깅, 내장 기능, 성능 면에서&lt;/p&gt;
&lt;p&gt;면밀히 검토 해 보았을 때,&lt;/p&gt;
&lt;p&gt;Rust 와 Zig 를 사용하는 것 보다는, Golang 으로 만드는 것이 비교할 수 없을 정도로&lt;/p&gt;
&lt;p&gt;효율적이라는 것을 알게 되었다. (비교 벤치마킹 영상을 시청하며 알게됨. - 외국영상)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, 한국의 전자 시스템 정부는 거의? 혹은 모두? Java 를 기준으로 맞추어져 있다.&lt;/p&gt;
&lt;p&gt;물론, 이 시스템을 사용하거나, 요청하게 되는 API 가 있다면,&lt;/p&gt;
&lt;p&gt;그 형태에 맞춰 전달할 수 있다면 원하는 언어로 작성해도 된다.&lt;/p&gt;
&lt;p&gt;그러나, 만약 프로덕션 제작 과정에서 이러한 문제를 맞닥들인다면,&lt;/p&gt;
&lt;p&gt;그건 개발의 비용이 미친듯이 올라가는 이야기랑 동일하다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;물론, &amp;quot;공부&amp;quot; 혹은 &amp;quot;연구&amp;quot; 의 시각으로서 Java 이하의 저수준 프레임워크를 사용한다면,&lt;/p&gt;
&lt;p&gt;그건 좋은 시도라고 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이제 나는 만들게 될 프로젝트의 주제와 그 흐름을 생각해 두었으며,&lt;/p&gt;
&lt;p&gt;공부가 아닌, 프로덕션을 만들고 직접 볼 수 있게 만들어야 하기 때문에,&lt;/p&gt;
&lt;p&gt;Golang 이 아닌 Java 를 선택했다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Java 를 사용함에도 불구하고 유지 할 행동습관&lt;/h2&gt;
&lt;p&gt;여태까지 가져온 공부에 대한 습관, 혹은 시각을 전부 바꿀 필요는 없다고 생각한다.&lt;/p&gt;
&lt;p&gt;현재 나에게 필요한 일부 습관, 시각을 가져오고 비효율적인 것은 버릴 것이다.&lt;/p&gt;
&lt;p&gt;여기서 말하는 &amp;quot;비효율&amp;quot; 이란, &amp;quot;내가&amp;quot; 자주 맞닥들이게 될 것 같지는 않는 주제를&lt;/p&gt;
&lt;p&gt;글로 작성하지 않고, 공식 문서와 AI 를 통해 효율을 올릴 것이라는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이미 나에게 있어 공부와 함께 블로그에 올릴 글을 작성하는 것은 굳어진 행동 습관이다.&lt;/p&gt;
&lt;p&gt;이러한 좋은 습관을 굳이 바꿀 필요는 없다.&lt;/p&gt;
&lt;p&gt;그러나, 나쁜 습관이 하나 있는데,&lt;/p&gt;
&lt;p&gt;1, 0 이 모여 생성된 프로그램에, 모든 것들을 &amp;quot;방법론&amp;quot; 이라는 시각을 장착하면 안된다는 것이다.&lt;/p&gt;
&lt;p&gt;이는 나의 시각을 굉장히 둔화시키고, Flexible 하게 만들지 않는다.&lt;/p&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;br/&gt;</description>
      <category>잡다 지식</category>
      <category>회고</category>
      <category>회고록</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/242</guid>
      <comments>https://codecreature.tistory.com/242#entry242comment</comments>
      <pubDate>Wed, 5 Nov 2025 21:43:53 +0900</pubDate>
    </item>
    <item>
      <title>CSS Grid 레이아웃은 무엇이고, 어떻게 사용할까? (예제와 함께 알아보기)</title>
      <link>https://codecreature.tistory.com/240</link>
      <description>&lt;h2&gt;제목 : CSS grid 란 무엇이고, 어떻게 사용할까?&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;이 글을 작성하는 이유&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;아주 최근에 작성한 CSS 에 대한 글 몇 개와 연동하여&lt;/p&gt;
&lt;p&gt;알아야 할 지식으로 생각하여 CSS grid 에 대해서 다루게 되었다.&lt;/p&gt;
&lt;p&gt;이전 글은,&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/237&quot;&gt;미리 알아두면 좋았을 CSS 기초 및 응용 예제&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/238&quot;&gt;CSS 레이아웃, display 에 대해서 알아놓자&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/239&quot;&gt;예제와 함께 알아보는 CSS flex 와 Flexbox&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;위의 3 글이며,&lt;/p&gt;
&lt;p&gt;레이아웃의 핵심인 Flexbox 와 더불어, Grid 에 대해서도 알아보려고 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;혹시라도 CSS 에 대해서 잘 모르시는 분이 있다면,&lt;/h3&gt;
&lt;p&gt;위의 3 개의 글을 순차적으로 읽고 오시면 도움이 됩니다.&lt;/p&gt;
&lt;p&gt;그리고, 질문은 환영이기 때문에,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;mailto:rhdwhdals8765@gmail.com&quot;&gt;rhdwhdals8765@gmail.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;으로 메일을 보내주시면 감사하겠습니다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;내가 예제를 블로그에 만드는 방법&lt;/h2&gt;
&lt;p&gt;나의 블로그 템플릿은 &lt;code&gt;hELLO&lt;/code&gt; 라는 템플릿에서 나의 커스텀 CSS 를 덧붙인 형태이기 때문에,&lt;/p&gt;
&lt;p&gt;현재 블로그 스타일 시트의 영향을 받는다면, 날 것의 형태를 볼 수 없다.&lt;/p&gt;
&lt;p&gt;따라서, 내가 선택한 방식은 &lt;code&gt;iframe&lt;/code&gt; 태그를 이용하여 현재 페이지에 종속되어 있는 StyleSheet&lt;/p&gt;
&lt;p&gt;로부터 떼어놓는 방식이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;혹시라도 블로그를 작성 할 생각이 있다면, 내가 사용하는 방식은 추후 많은 도움이 될 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;예시 :&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;iframe
    style=&amp;quot;width:70%; max-height:20rem;&amp;quot;
    srcdoc=&amp;#39;
        &amp;lt;style&amp;gt;
            body {
                background : white;
            }
        &amp;lt;/style&amp;gt;
        &amp;lt;body&amp;gt;
        기본 상태는 이러합니당
        &amp;lt;/body&amp;gt;
    &amp;#39;
&amp;gt;
    &amp;lt;p&amp;gt;이 문구가 보인다면, iframe 이 막혀 CSS 예시를 볼 수 없습니다.
&amp;lt;/iframe&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 사이트 자체에서 &lt;code&gt;iframe&lt;/code&gt; 의 백그라운드는 투명에 속하기 때문에,&lt;/p&gt;
&lt;p&gt;강제로 하얀색으로 초기화 시키고 시작한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; 에 메타데이터로 스타일을 넣지 않아도,&lt;/p&gt;
&lt;p&gt;이러한 형식으로 분리된 HTML + CSS + JS 형식을 만들 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Grid 란 무엇인가?&lt;/h2&gt;
&lt;p&gt;개별 컴포넌트는 외부(부모, 형제) 컴포넌트가 자신을 어떻게 인식할지,&lt;/p&gt;
&lt;p&gt;내부에 포함된 컴포넌트는 &amp;quot;어떻게 정렬될지&amp;quot; 선택할 수 있다.&lt;/p&gt;
&lt;p&gt;이러한 정렬은 &lt;code&gt;display&lt;/code&gt; 라는 CSS 속성으로 결정된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;HTML 태그는 정말 다양한데, 리스트, 테이블, 단순 영역 등등&lt;/p&gt;
&lt;p&gt;고유의 HTML 태그들은 제 역할에 맡게 적당한 &lt;code&gt;display&lt;/code&gt; Default 값을 가지고 있다.&lt;/p&gt;
&lt;p&gt;우리가 다루는 &lt;code&gt;display&lt;/code&gt; 속성은, 주로 여러 컴포넌트를 담게 되는 오브젝트에 선언하게 된다.&lt;/p&gt;
&lt;p&gt;즉, 우리는 여러 컴포넌트를 담을 컨테이너가 하위 요소를 어떻게 정렬 할 지 선택 할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;div&lt;/code&gt;, &lt;code&gt;section&lt;/code&gt;, &lt;code&gt;article&lt;/code&gt;, 과 같은 영역 태그들은 &lt;code&gt;block&lt;/code&gt; 이라는 기본 값이 있으나,&lt;/p&gt;
&lt;p&gt;개발자들의 무한한 스타일링 기법을 충족시키기 위해,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;display : flex&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;display : grid&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;가 대표적으로 존재한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;Flex 란, 컨테이너가 내부 컴포넌트를 1차원 나열 형식으로 표현하고자 할 때 사용되며,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;flex-wrap : wrap&lt;/code&gt; 이라는 명시를 통해 다양한 너비의 디바이스에서&lt;/p&gt;
&lt;p&gt;크기에 따른 자동 줄넘김을 유도 할 수 있다.&lt;/p&gt;
&lt;p&gt;flex 에 대해서 더 자세한 것은,&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/239&quot;&gt;예제와 함께 알아보는 CSS flex 와 Flexbox&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;여기서 배우고 오면 이해가 될 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그렇다면, Grid 란 무엇일까?&lt;/p&gt;
&lt;p&gt;Grid 란, 내부 컴포넌트를 row 와 column 나열 형태로 표현하기 위해 존재한다.&lt;/p&gt;
&lt;p&gt;즉, 2차원이라고 말할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;row(행), column(열) 을 직접 선언하는 것이 언제 중요하냐고 할 수 있을 테지만,&lt;/p&gt;
&lt;p&gt;헤더, 사이드바, 아티클 등등 여러 요소가 순서대로 항상 배치되어야 할 때&lt;/p&gt;
&lt;p&gt;grid 를 사용하여 간단하게 해결 할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;grid 에 추가되는 다양한 속성들&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Grid 기본 속성 (Grid)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;grid-template-columns&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grid-auto-columns&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grid-template-rows&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grid-auto-rows&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Grid 영역 속성 (Grid Areas)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;grid-area&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grid-template-areas&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Grid 영역의 새로운 단위&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;fr&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Grid 에서 사용되는 함수&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;repeat()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;minmax()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fit-content()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Grid 의 아이템 배치&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;grid-column-start&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grid-column-end&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grid-row-start&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grid-row-end&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Grid 의 여백&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;grid-column-gap&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grid-row-gap&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grid gap&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Grid 의 단축어(짧은 표기) 속성&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;grid-column&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grid-row&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grid-template&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grid&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;위와 같이 Grid 디스플레이를 위해 다양하며 새로운 속성들이 기다리고 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Grid 에서 알아야 할 용어에 대한 설명&lt;/h2&gt;
&lt;p&gt;그리드에서부터는 위에서 언급한 새로운 특화 속성들을 자유로이 다루기 위해서,&lt;/p&gt;
&lt;p&gt;Term(사용될 용어) 를 간단하게 볼 필요가 있다.&lt;/p&gt;
&lt;p&gt;그래서 따로 이미지를 만들어 봤다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;Grid Line&lt;/h3&gt;
&lt;img style=&quot;width : 70%&quot; src=&quot;https://github.com/damhyeong/Image-and-content/blob/main/blog-content/display-of-grid/1-grid-line.png?raw=true&quot; alt=&quot;이미지가 나오지 않습니다.&quot;/&gt;

&lt;br/&gt;

&lt;p&gt;Grid Line 은 시작 지점을 나타내는 용도이다.&lt;/p&gt;
&lt;p&gt;시작 인덱스는 &lt;code&gt;0&lt;/code&gt; 이 아니라, &lt;code&gt;1&lt;/code&gt; 부터 시작한다.&lt;/p&gt;
&lt;p&gt;추후 사용될 이 용어와 의미는 Column, Row 둘 다 동일하게 적용된다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Grid Track&lt;/h3&gt;
&lt;img style=&quot;width : 70%&quot; src=&quot;https://github.com/damhyeong/Image-and-content/blob/main/blog-content/display-of-grid/2-grid-track.png?raw=true&quot; alt=&quot;이미지가 나오지 않습니다.&quot;/&gt;

&lt;br/&gt;

&lt;p&gt;행을 의미하는 &lt;code&gt;Row&lt;/code&gt; 로 작성되지 않고, 여기서는 &lt;code&gt;Track&lt;/code&gt; 이라고 불린다.&lt;/p&gt;
&lt;p&gt;왜 하나의 줄이라고 부르지 않고, 도대체 왜 &amp;quot;트랙&amp;quot; 이라고 부르는가? 해서 의미를 계속 살펴보았다.&lt;/p&gt;
&lt;p&gt;여기서 위의 &lt;strong&gt;Grid Line&lt;/strong&gt; 과 연관되어 나오는데,&lt;/p&gt;
&lt;p&gt;&amp;quot;행 트랙&amp;quot; 이란, 두 행(row) Line 사이에 있으며,&lt;/p&gt;
&lt;p&gt;&amp;quot;열 트랙&amp;quot; 이란, 두 열(column) Line 사이에 있다는 것이다.&lt;/p&gt;
&lt;p&gt;여기서나는 &lt;strong&gt;Line&lt;/strong&gt; 을 공간으로 바라보았는데,&lt;/p&gt;
&lt;p&gt;그게 아니고, 위에 설명된 진짜 &lt;strong&gt;&amp;quot;그어진 Grid Line&amp;quot;&lt;/strong&gt; 으로 바라보아야 한다.&lt;/p&gt;
&lt;p&gt;사실상 이 라인까지도 포함된 개념을 Row 혹은 Column 으로 보고,&lt;/p&gt;
&lt;p&gt;&amp;quot;그 사이의 공간&amp;quot; 을 Track 이라고 부르는 것이다.&lt;/p&gt;
&lt;br/&gt;


&lt;h3&gt;Grid Cell&lt;/h3&gt;
&lt;img style=&quot;width : 70%&quot; src=&quot;https://github.com/damhyeong/Image-and-content/blob/main/blog-content/display-of-grid/3-grid-cell.png?raw=true&quot; alt=&quot;이미지가 나오지 않습니다.&quot;/&gt;

&lt;br/&gt;

&lt;p&gt;셀은 간단하다. 위에서 말한 영역, 즉, Grid Line 사이의 영역을 Track 이라고 말했다면,&lt;/p&gt;
&lt;p&gt;이 Cell 은 이 자리의 &amp;quot;행 Track&amp;quot;, &amp;quot;열 Track&amp;quot; 의 교차 영역을 의미한다.&lt;/p&gt;
&lt;p&gt;이렇게 어렵게 설명하는 이유는, 간단히 말해 경계선을 포함시키지 않기 위함이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Grid Area&lt;/h3&gt;
&lt;img style=&quot;width : 70%&quot; src=&quot;https://github.com/damhyeong/Image-and-content/blob/main/blog-content/display-of-grid/4-grid-area.png?raw=true&quot; alt=&quot;이미지가 나오지 않습니다.&quot;/&gt;

&lt;br/&gt;

&lt;p&gt;위의 용어는 특정 엘리먼트, 혹은 위치에 배치된 엘리먼트가&lt;/p&gt;
&lt;p&gt;글 작성 시 줄넘김 하는 것 처럼 넘어가지 않고,&lt;/p&gt;
&lt;p&gt;해당 위치에서 정해진 위치까지 포함하도록 만든다.&lt;/p&gt;
&lt;br/&gt;


&lt;h3&gt;Grid Gap&lt;/h3&gt;
&lt;img style=&quot;width : 70%&quot; src=&quot;https://github.com/damhyeong/Image-and-content/blob/main/blog-content/display-of-grid/5-grid-gap.png?raw=true&quot; alt=&quot;이미지가 나오지 않습니다.&quot;/&gt;

&lt;br/&gt;

&lt;p&gt;내부에 채워질 각 엘리먼트들의 Margin 이라고도 볼 수 있다.&lt;/p&gt;
&lt;p&gt;각 엘리먼트가 얼마나 떨어져 있을지 선언한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;새로운 단위, fr&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;display : flex&lt;/code&gt; 환경에서는 굉장히 유용하게 사용되는 기능이 존재하는데,&lt;/p&gt;
&lt;p&gt;바로 비율로 각 컴포넌트가 얼마나 이 컨테이너를 차지 할 것인지를 지정하는 것이다.&lt;/p&gt;
&lt;p&gt;단축 키워드인 &lt;code&gt;flex&lt;/code&gt; 를 이용할 수 있는데,&lt;/p&gt;
&lt;p&gt;하위 엘리먼트에서 스스로 &lt;code&gt;flex : 1&lt;/code&gt;, &lt;code&gt;flex : 2&lt;/code&gt; 가 선언되었다면,&lt;/p&gt;
&lt;p&gt;각 엘리먼트는 &lt;code&gt;1/3&lt;/code&gt;, &lt;code&gt;2/3&lt;/code&gt; 의 공간을 차지 할 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이 &lt;code&gt;fr&lt;/code&gt; 이라는 단위는 나중에 CSS 의 &amp;quot;함수&amp;quot; 와 직접적으로 연관되어 있다.&lt;/p&gt;
&lt;p&gt;아직 현재는 &lt;code&gt;fr&lt;/code&gt; 이라는 단위가, 선언된 위치에 따라 해당 Column, or Row 가&lt;/p&gt;
&lt;p&gt;이 단위와 함께 비율적으로 커지거나 작아진다는 것이다.&lt;/p&gt;
&lt;p&gt;예를 들어, &lt;code&gt;grid-template-column : 1fr 1fr 1fr 2fr;&lt;/code&gt; 으로 선언 될 경우,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;하나의 트랙에 들어가는 순서, column(열) 에 따라 &lt;code&gt;1 : 1 : 1 : 2&lt;/code&gt; 비율로 너비가 나뉜다.&lt;/li&gt;
&lt;li&gt;엘리먼트가 6 개 일 경우, 2 줄로 편성되며, 배치된 열에 따라 위와 같은 비율의 너비를 가진다.&lt;/li&gt;
&lt;li&gt;엘리먼트가 그보다 작은 2 일경우, &lt;code&gt;1 : 1&lt;/code&gt; 로 계산되지 않으며, &lt;br/&gt; 배치되지 않은 &lt;code&gt;1fr 2fr&lt;/code&gt; 은 빈 칸으로 간주한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;위의 설명을 본다면, &lt;code&gt;fr&lt;/code&gt; 이라는 단위는 &amp;quot;비율&amp;quot; 에 해당한다는 것을 알 수 있다.&lt;/p&gt;
&lt;p&gt;그리고, 이 단위는 &lt;code&gt;px&lt;/code&gt;, &lt;code&gt;em&lt;/code&gt;, .. 등등 정확한 절대 단위와 같이 사용될 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;fr&lt;/code&gt; 은 이 절대적인 값들과 Gap 을 뺀 나머지 영역에서 &lt;code&gt;fr&lt;/code&gt; 비율로 나눈다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Grid 를 사용 해 보자.&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;display : flex&lt;/code&gt; 를 통해 Flexbox 를 본격적으로 여는 것 처럼,&lt;/p&gt;
&lt;p&gt;Grid 는 &lt;code&gt;display : grid&lt;/code&gt; 를 통해 2차원 엘리먼트 배치를 본격적으로 구성하게 해 준다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;grid-template-xxx (행, 열)&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;grid&lt;/code&gt; 는 행, 그리고 열 을 선언하는 특수? 전용 속성이 존재한다.&lt;/p&gt;
&lt;p&gt;바로,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;grid-template-columns&lt;/code&gt; : 열 설정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grid-template-rows&lt;/code&gt; : 행 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;여기서 다른 css 속성과는 확실히 다른 점이 존재하는데,&lt;/p&gt;
&lt;p&gt;바로 인자의 개수 제한이 없다시피 한다는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;인자의 개수 제한이 없다는 게 무슨 의미일까?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;CSS 에서 &lt;code&gt;Propertie : Value&lt;/code&gt; 선언 쌍을 이루는 것이 일반적이다.&lt;/p&gt;
&lt;p&gt;그러나, 몇몇 속성에서는 다중 값 선언이 허용된다.&lt;/p&gt;
&lt;p&gt;예를 들어,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;display : inline flex;&lt;/code&gt; : 블록이 아닌 인라인 형식, 그리고 Flexbox.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;border-width : 0 0 10px 0;&lt;/code&gt; : 아래에 10px 의 경계선이 있으며, 나머지는 경계가 안보임&lt;/li&gt;
&lt;li&gt;&lt;code&gt;flex : 2 0 auto&lt;/code&gt; : flex-item 에서 선언할 수 있는데, 이 아이템은 비율 수준 2 를 가져간다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;생각나는 다중 값 속성들은 일단, 이러하다.&lt;/p&gt;
&lt;p&gt;그리고, &lt;code&gt;grid-template-xxx&lt;/code&gt; 에 해당하는 이 2 가지 속성은,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;grid-template-??? : 100px 1fr 100px 10px 10px 10px ...&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;이러한 식으로 거의 무한하게 뻗어나갈 수 있다.&lt;/p&gt;
&lt;p&gt;그리고 위의 값이 선언된다면, 이러한 의미가 된다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;row or column&lt;/code&gt; 에서의 의미 (행이던 열이던 상관이 없음.)&lt;/p&gt;
&lt;p&gt;1 번째 인자 : 절대적인 100px 확보&lt;/p&gt;
&lt;p&gt;2 번째 인자 : 남는 공간에서 비율 1 을 가져간다.&lt;/p&gt;
&lt;p&gt;3 번째 인자 : 절대적인 10px 확보&lt;/p&gt;
&lt;p&gt;4 번째 인자 : 동일&lt;/p&gt;
&lt;p&gt;... 10px 이 지속적으로 이어진다고 가정.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;그렇다면, 행과 열 개수를 자유롭게 바꾸기 어렵지 않나?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;라고 생각을 했었는데, 그렇지 않다. 바로, CSS 문법의 &lt;strong&gt;&amp;quot;함수&amp;quot;&lt;/strong&gt; 가 존재하기 때문이다.&lt;/p&gt;
&lt;p&gt;함수를 사용하기까지는 아직 내가 적응이 된 것은 아니라서, Grid 의 기초를 다루며 천천히 진입 할 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;내가 만든 예제를 살펴보자.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.container {
    /* grid 선언 */
    display : grid;
    /* 이 컨테이너는 20 rem 의 너비를 고정적으로 가진다 */
    width : 20rem;
    margin : 1rem;
    border : 4px solid black;
    /* grid 컨테이너 내부의 요소들이 서로 간격을 10px 로 떨어진다 */
    gap : 10px;
    /* 내가 선언하는 &amp;#39;열&amp;#39; 이 가질 요소 */
    grid-template-columns : 50px 1fr 30%;
}
div {
    text-align : center;
    border : 2px solid gray;
    border-radius : 3px;
    box-shadow : 0 5px 5px 0 darkgray;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body&amp;gt;
    &amp;lt;button onclick=&amp;quot;plusElem()&amp;quot;&amp;gt;요소 추가하기&amp;lt;/button&amp;gt;
    &amp;lt;br/&amp;gt;
    &amp;lt;section class=&amp;quot;container&amp;quot; id=&amp;quot;container&amp;quot;&amp;gt;
        &amp;lt;div&amp;gt;
            1
        &amp;lt;/div&amp;gt;
    &amp;lt;/section&amp;gt;
    &amp;lt;script&amp;gt;
    const container = document.getElementById(&amp;quot;container&amp;quot;);
    let currNum = 2;
    function plusElem() {
      const newElem = document.createElement(&amp;quot;div&amp;quot;);
      newElem.textContent = currNum++;
      container.appendChild(newElem);
    }
    &amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Preview&lt;/strong&gt; :&lt;/p&gt;
&lt;iframe
    style=&quot;width : 70%; height : 20rem; min-width : 25rem;&quot;
    srcdoc='
        &lt;style&gt;
            body {
                background : white;
            }
            .container {
                /* grid 선언 */
                display : grid;
                /* 이 컨테이너는 20 rem 의 너비를 고정적으로 가진다 */
                width : 20rem;
                margin : 1rem;
                border : 4px solid black;
                /* grid 컨테이너 내부의 요소들이 서로 간격을 10px 로 떨어진다 */
                gap : 10px;
                /* 내가 선언하는 열 이 가질 요소 */
                grid-template-columns : 50px 1fr 30%;
            }
            div {
                text-align : center;
                border : 2px solid gray;
                border-radius : 3px;
                box-shadow : 0 5px 5px 0 darkgray;
            }
        &lt;/style&gt;
        &lt;body&gt;
            &lt;button onclick=&quot;plusElem()&quot;&gt;요소 추가하기&lt;/button&gt;
            &lt;br/&gt;
            &lt;section class=&quot;container&quot; id=&quot;container&quot;&gt;
                &lt;div&gt;
                    1
                &lt;/div&gt;
            &lt;/section&gt;
            &lt;script&gt;
            const container = document.getElementById(&quot;container&quot;);
            let currNum = 2;
            function plusElem() {
              const newElem = document.createElement(&quot;div&quot;);
              newElem.textContent = currNum++;
              container.appendChild(newElem);
            }
            &lt;/script&gt;
        &lt;/body&gt;
    '
&gt;
    &lt;p&gt;이 문구가 보인다면, 개인의 설정으로 인해 iframe 이 거부되었을 확률이 높습니다.&lt;/p&gt;
&lt;/iframe&gt;

&lt;p&gt;&lt;strong&gt;end&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;위의 예제에 아주 간단한 엘리먼트 추가 기능을 넣었는데,&lt;/p&gt;
&lt;p&gt;이는 엘리먼트가 하나의 Track 에서 포함 기준치를 넘어가면,&lt;/p&gt;
&lt;p&gt;어떻게 엘리먼트를 배치하는지 직관적으로 보여주기 위함이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;위에서 내가 선언한 Grid Template 을 보자.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;grid-template-columns : 50px 1fr 30%;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;첫 번째 요소는 고정적으로 50px 을 얻으며,&lt;/p&gt;
&lt;p&gt;세 번째 요소는 Grid Container 의 영역에 따라 30% 를 가진다.&lt;/p&gt;
&lt;p&gt;그리고 남은 Free Area 는,&lt;/p&gt;
&lt;p&gt;유일하게 이 단위를 사용하는 두 번째 요소에 남는 영역을 모두 몰아준다.&lt;/p&gt;
&lt;p&gt;따라서 위의 예제를 보면, 2 번째 요소가 제일 큰 것이다.&lt;/p&gt;
&lt;p&gt;만약에 위의 &lt;code&gt;1fr&lt;/code&gt; 을 &lt;code&gt;3fr&lt;/code&gt; 로 바꿔도, 같은 단위를 사용하는 &amp;quot;열&amp;quot; 이 없기 때문에,&lt;/p&gt;
&lt;p&gt;동일하게 너비를 가진다.&lt;/p&gt;
&lt;p&gt;만약에 &lt;code&gt;50px 1fr 30% 1fr&lt;/code&gt; 이라면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;50px&lt;/code&gt;, &lt;code&gt;30%&lt;/code&gt; 에 해당하는 너비를 뺀 결과를,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;1fr&lt;/code&gt;, &lt;code&gt;1fr&lt;/code&gt; 이 서로 비율로 1/2, 1/2 로 나눠 갖는다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;그렇다면, 반대로 grid-template-rows&lt;/strong&gt; 만 선언되면 어떻게 될까?&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.container {
    /* grid 선언 */
    display : grid;
    /* 이 컨테이너는 20 rem 의 높이를 고정적으로 가진다 */
    height : 20rem;
    margin : 1rem;
    border : 4px solid black;
    /* grid 컨테이너 내부의 요소들이 서로 간격을 10px 로 떨어진다 */
    gap : 10px;
    /* 내가 선언하는 행 이 가질 요소 */
    grid-template-rows : 50px 1fr 30%;
    grid-auto-flow : column;
}
div {
    text-align : center;
    border : 2px solid gray;
    border-radius : 3px;
    box-shadow : 0 5px 5px 0 darkgray;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- 위의 예제와 동일합니다. --&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;button onclick=&amp;quot;plusElem()&amp;quot;&amp;gt;요소 추가하기&amp;lt;/button&amp;gt;
    &amp;lt;br/&amp;gt;
    &amp;lt;section class=&amp;quot;container&amp;quot; id=&amp;quot;container&amp;quot;&amp;gt;
        &amp;lt;div&amp;gt;
            1
        &amp;lt;/div&amp;gt;
    &amp;lt;/section&amp;gt;
    &amp;lt;script&amp;gt;
    const container = document.getElementById(&amp;quot;container&amp;quot;);
    let currNum = 2;
    function plusElem() {
      const newElem = document.createElement(&amp;quot;div&amp;quot;);
      newElem.textContent = currNum++;
      container.appendChild(newElem);
    }
    &amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;iframe
    style=&quot;width : 70%; height : 35rem; min-width : 25rem;&quot;
    srcdoc='
        &lt;style&gt;
            body {
                background : white;
            }
            .container {
                /* grid 선언 */
                display : grid;
                /* 이 컨테이너는 20 rem 의 높이를 고정적으로 가진다 */
                height : 20rem;
                margin : 1rem;
                border : 4px solid black;
                /* grid 컨테이너 내부의 요소들이 서로 간격을 10px 로 떨어진다 */
                gap : 10px;
                /* 내가 선언하는 행 이 가질 요소 */
                grid-template-rows : 50px 1fr 30%;
            }
            div {
                text-align : center;
                border : 2px solid gray;
                border-radius : 3px;
                box-shadow : 0 5px 5px 0 darkgray;
            }
        &lt;/style&gt;
        &lt;body&gt;
            &lt;button onclick=&quot;plusElem()&quot;&gt;요소 추가하기&lt;/button&gt;
            &lt;br/&gt;
            &lt;section class=&quot;container&quot; id=&quot;container&quot;&gt;
                &lt;div&gt;
                    1
                &lt;/div&gt;
            &lt;/section&gt;
            &lt;script&gt;
            const container = document.getElementById(&quot;container&quot;);
            let currNum = 2;
            function plusElem() {
              const newElem = document.createElement(&quot;div&quot;);
              newElem.textContent = currNum++;
              container.appendChild(newElem);
            }
            &lt;/script&gt;
        &lt;/body&gt;
    '
&gt;
    &lt;p&gt;이 문구가 보인다면, 개인의 설정으로 인해 iframe 이 거부되었을 확률이 높습니다.&lt;/p&gt;
&lt;/iframe&gt;

&lt;br/&gt;

&lt;p&gt;안타깝게도, &lt;code&gt;grid-template-columns&lt;/code&gt; 를 단독으로 선언하여 다음 줄로 넘길 수 있었던 것에 비해,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;grid-template-rows&lt;/code&gt; 만을 단독으로 선언해서는, 원하는 바를 만들 수가 없다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;나는 굳이 &lt;code&gt;grid-template-columns&lt;/code&gt; 를 사용하고 싶지 않았다.&lt;/p&gt;
&lt;p&gt;그 이유가, 컬럼을 선언하게 되면, 내부의 컨텐츠가 컬럼에 따라 &amp;quot;미리 분리&amp;quot; 가 되어있기 때문이다.&lt;/p&gt;
&lt;p&gt;내가 원한 것은, &amp;quot;컨텐츠의 수에 따라 영역이 그때그때 나뉘는 것&amp;quot; 을 원했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이에 대한 답을 알기 위해 Gemini-2.5 를 사용했는데,&lt;/p&gt;
&lt;p&gt;위의 예제에서 CSS 에 &lt;code&gt;grid-auto-flow : column;&lt;/code&gt; 단 한 줄만 추가하면 되었다.&lt;/p&gt;
&lt;br/&gt;

&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.container {
    /* grid 선언 */
    display : grid;
    /* 이 컨테이너는 20 rem 의 높이를 고정적으로 가진다 */
    height : 20rem;
    margin : 1rem;
    border : 4px solid black;
    /* grid 컨테이너 내부의 요소들이 서로 간격을 10px 로 떨어진다 */
    gap : 10px;
    /* 내가 선언하는 행 이 가질 요소 */
    grid-template-rows : 50px 1fr 30%;
    grid-auto-flow : column;
}
div {
    text-align : center;
    border : 2px solid gray;
    border-radius : 3px;
    box-shadow : 0 5px 5px 0 darkgray;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;HTML 은 그대로&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Preview&lt;/strong&gt;&lt;/p&gt;
&lt;iframe
    style=&quot;width : 70%; height : 35rem; min-width : 25rem;&quot;
    srcdoc='
        &lt;style&gt;
            body {
                background : white;
            }
            .container {
                /* grid 선언 */
                display : grid;
                /* 이 컨테이너는 20 rem 의 높이를 고정적으로 가진다 */
                height : 20rem;
                margin : 1rem;
                border : 4px solid black;
                /* grid 컨테이너 내부의 요소들이 서로 간격을 10px 로 떨어진다 */
                gap : 10px;
                /* 내가 선언하는 행 이 가질 요소 */
                grid-template-rows : 50px 1fr 30%;
                grid-auto-flow : column;
            }
            div {
                text-align : center;
                border : 2px solid gray;
                border-radius : 3px;
                box-shadow : 0 5px 5px 0 darkgray;
            }
        &lt;/style&gt;
        &lt;body&gt;
            &lt;button onclick=&quot;plusElem()&quot;&gt;요소 추가하기&lt;/button&gt;
            &lt;br/&gt;
            &lt;section class=&quot;container&quot; id=&quot;container&quot;&gt;
                &lt;div&gt;
                    1
                &lt;/div&gt;
            &lt;/section&gt;
            &lt;script&gt;
            const container = document.getElementById(&quot;container&quot;);
            let currNum = 2;
            function plusElem() {
              const newElem = document.createElement(&quot;div&quot;);
              newElem.textContent = currNum++;
              container.appendChild(newElem);
            }
            &lt;/script&gt;
        &lt;/body&gt;
    '
&gt;
    &lt;p&gt;이 문구가 보인다면, 개인의 설정으로 인해 iframe 이 거부되었을 확률이 높습니다.&lt;/p&gt;
&lt;/iframe&gt;

&lt;p&gt;&lt;strong&gt;end&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;위의 예제를 만들고 나서, 원하던 형식으로 표시되는 것을 확인 할 수 있었다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Grid 컨테이너 트랙 고유 크기 조정 키워드&lt;/h2&gt;
&lt;p&gt;우리가 생각해 볼 수 있는 것은, 절대값 선언인 &lt;code&gt;50px&lt;/code&gt;, &lt;code&gt;2rem&lt;/code&gt;, &lt;code&gt;30%&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;를 제외하고,&lt;/p&gt;
&lt;p&gt;Grid 시스템에서 가장 많이 사용 되는 단위는 &lt;code&gt;fr&lt;/code&gt; 이라는 단위다.&lt;/p&gt;
&lt;p&gt;즉, 이는 free 의 약자로서, 남는 공간을 &lt;code&gt;fr&lt;/code&gt; 끼리 비율로 분배하는 효율적인 단위이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;여기서 생각 해 볼 수 있는 점은,&lt;/p&gt;
&lt;p&gt;위의 단위들에서 &lt;strong&gt;&amp;quot;컨텐츠에 따른 크기&amp;quot; 는 고려 대상이 아니라는 것이다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;즉, 절대 값이나, Grid 컨테이너에서 유연한 비율을 선언할 수 있는 &lt;code&gt;fr&lt;/code&gt; 단위 또한, 컨텐츠를 신경쓰지 않는다.&lt;/p&gt;
&lt;p&gt;따라서 여기서 &lt;code&gt;grid-template-columns or rows&lt;/code&gt; 에 선언할 수 있는 또 다른 키워드들이 존재하는데,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;auto&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;min-content&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;max-content&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fit-content(..)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;아직 본격적으로 &lt;strong&gt;CSS Function&lt;/strong&gt; 을 다룰 때가 아닌 것 같아서 밑의 아주 간단한 예시를 들자면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;grid-template-columns : auto min-content max-content fit-content(3rem);&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;이러한 방식으로 선언이 가능하다.&lt;/p&gt;
&lt;p&gt;만약에, 1 개의 트랙에 동일한 속성을 선언하고자 한다면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;grid-template-columns : repeat(4, 1fr);&lt;/code&gt; 와 같은 형식으로 선언이 가능하다.&lt;/p&gt;
&lt;p&gt;위의 &lt;code&gt;repeat&lt;/code&gt; 라는 함수는 동일한 인자를 결과적으로 펼쳐주는 역할을 하는데,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;4&lt;/code&gt; : 4 번 동일하게 반복 선언한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;1fr&lt;/code&gt; : 1fr 을.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt; : &lt;code&gt;grid-...-columns : 1fr 1fr 1fr 1fr;&lt;/code&gt; 이 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;다시 돌아와서, Grid Track 의 고유 크기 조정 키워드는&lt;/p&gt;
&lt;p&gt;위와 같은 &lt;code&gt;repeat&lt;/code&gt; 함수의 &amp;quot;인자&amp;quot; 로 건네주어 하위 컴포넌트의 크기를 다룰 수 있다.&lt;/p&gt;
&lt;p&gt;예를 들어보자.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;나는 총 7 개의 엘리먼트를 넣을 수 있게 만들 것이며,&lt;/li&gt;
&lt;li&gt;하나의 줄에 총 3 개가 들어 갈 수 있다.&lt;/li&gt;
&lt;li&gt;하위 컴포넌트들은 컨테이너의 선언에 따라 모두 같은 키워드로 조정된다.&lt;/li&gt;
&lt;li&gt;그러나, 각각의 컴포넌트들은 다른 컨텐츠 길이를 가진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;그리고, 각 단위의 정확한 의미를 알아보자.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;auto&lt;/code&gt; : 컨텐츠의 크기에 따라 공간을 분배하며, 컴포넌트들이 하나의 트랙을 전부 차지한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;min-content&lt;/code&gt; : 내부 컴포넌트에서 &amp;quot;가장 긴 단어&amp;quot; 만큼 너비를 가진다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;max-content&lt;/code&gt; : 내부 컴포넌트에서 줄넘김이 발생하지 않을 만큼 큰 너비를 가진다. &lt;br/&gt; 이 키워드는 오버플로우를 발생시킬 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fit-content(?)&lt;/code&gt; : &lt;code&gt;?&lt;/code&gt; 에 선언된 크기만큼 커질 수 있으며, 그 이상은 늘어나지 않는다. &lt;br/&gt; 트랙의 끝에 도달하면 컨텐츠 내부를 자동으로 줄넘김하므로, 유용한 기능이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/* 전역 변수로서 트랙 키워드를 동적으로 변화시키기 위해서 선언 */
/* 또한, CSS 내부에서 함수를 통해 전역적으로 가져올 수 있음. */
:root {
    --grid-track-keyword : auto;
}
.container {
    display : grid;
    /* 오버플로를 유발시키기 위한 적정한 길이로 판단 */
    max-width : 20rem;
    /* repeat, var 함수를 통해 앞으로 grid 를 구성하게 됨. */
    grid-template-columns : repeat(3, var(--grid-track-keyword));
    /* 1fr 이 아니라, auto 로 선언하여, 하얀 빈 칸이 생겨나지 않도록 조치. */
    grid-template-rows : repeat(3, auto);
    padding : 1rem;
    margin : 1rem;
    border : 2px solid dodgerblue;
}
div {
    text-align : center;
    padding : 1rem;
    border : 2px solid gray;
    border-radius : 3px;
    box-shadow : 0 5px 5px 0 darkgray;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body&amp;gt;
    각 개체의 컬럼 사이즈를 골라보자 :
    &amp;lt;br/&amp;gt;
    &amp;lt;select id=&amp;quot;grid-track-select&amp;quot;&amp;gt;
        &amp;lt;option value=&amp;quot;auto&amp;quot;&amp;gt;auto&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;min-content&amp;quot;&amp;gt;min-content&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;max-content&amp;quot;&amp;gt;max-content&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;fit-content(7rem)&amp;quot;&amp;gt;fit-content(7rem)&amp;lt;/option&amp;gt;
    &amp;lt;/select&amp;gt;
    &amp;lt;br/&amp;gt;
    &amp;lt;section id=&amp;quot;container&amp;quot; class=&amp;quot;container&amp;quot;&amp;gt;
        &amp;lt;div&amp;gt;
            컨테이너 1
        &amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
            컨테이너 2
        &amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
            컨테이너 3 인데, max-content 를 선택하면 오버플로가 납니다.
        &amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
            컨테이너 4
        &amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
            컨테이너 5
        &amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
            컨테이너 6
        &amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
            컨테이너 7
        &amp;lt;/div&amp;gt;
    &amp;lt;/section&amp;gt;
    &amp;lt;script&amp;gt;
    const select = document.getElementById(&amp;quot;grid-track-select&amp;quot;);
    const container = document.getElementById(&amp;quot;container&amp;quot;);
    const root = document.documentElement;
    select.addEventListener(&amp;quot;change&amp;quot;, function () {
      /* CSS 시트에서 :root 로 선언된 변수는 이와 같이 동적으로 변화시키거나 선언할 수 있음. */
      root.style.setProperty(&amp;quot;--grid-track-keyword&amp;quot;, select.value);
    });
    &amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Preview&lt;/strong&gt; :&lt;/p&gt;
&lt;iframe
    style=&quot;width : 70%; height : 35rem; min-width : 25rem;&quot;
    srcdoc='
        &lt;style&gt;
            body {
                background : white;
            }
            /* 전역 변수로서 트랙 키워드를 동적으로 변화시키기 위해서 선언 */
            /* 또한, CSS 내부에서 함수를 통해 전역적으로 가져올 수 있음. */
            :root {
                --grid-track-keyword : auto;
            }
            .container {
                display : grid;
                /* 오버플로를 유발시키기 위한 적정한 길이로 판단 */
                max-width : 20rem;
                /* repeat, var 함수를 통해 앞으로 grid 를 구성하게 됨. */
                grid-template-columns : repeat(3, var(--grid-track-keyword));
                /* 1fr 이 아니라, auto 로 선언하여, 하얀 빈 칸이 생겨나지 않도록 조치. */
                grid-template-rows : repeat(3, auto);
                padding : 1rem;
                margin : 1rem;
                border : 2px solid dodgerblue;
            }
            div {
                text-align : center;
                padding : 1rem;
                border : 2px solid gray;
                border-radius : 3px;
                box-shadow : 0 5px 5px 0 darkgray;
            }
        &lt;/style&gt;
        &lt;body&gt;
            각 개체의 컬럼 사이즈를 골라보자 :
            &lt;br/&gt;
            &lt;select id=&quot;grid-track-select&quot;&gt;
                &lt;option value=&quot;auto&quot;&gt;auto&lt;/option&gt;
                &lt;option value=&quot;min-content&quot;&gt;min-content&lt;/option&gt;
                &lt;option value=&quot;max-content&quot;&gt;max-content&lt;/option&gt;
                &lt;option value=&quot;fit-content(7rem)&quot;&gt;fit-content(7rem)&lt;/option&gt;
            &lt;/select&gt;
            &lt;br/&gt;
            &lt;section id=&quot;container&quot; class=&quot;container&quot;&gt;
                &lt;div&gt;
                    컨테이너 1
                &lt;/div&gt;
                &lt;div&gt;
                    컨테이너 2
                &lt;/div&gt;
                &lt;div&gt;
                    컨테이너 3 인데, max-content 를 선택하면 오버플로가 납니다.
                &lt;/div&gt;
                &lt;div&gt;
                    컨테이너 4
                &lt;/div&gt;
                &lt;div&gt;
                    컨테이너 5
                &lt;/div&gt;
                &lt;div&gt;
                    컨테이너 6
                &lt;/div&gt;
                &lt;div&gt;
                    컨테이너 7
                &lt;/div&gt;
            &lt;/section&gt;
            &lt;script&gt;
            const select = document.getElementById(&quot;grid-track-select&quot;);
            const container = document.getElementById(&quot;container&quot;);
            const root = document.documentElement;
            select.addEventListener(&quot;change&quot;, function () {
              /* CSS 시트에서 :root 로 선언된 변수는 이와 같이 동적으로 변화시키거나 선언할 수 있음. */
              root.style.setProperty(&quot;--grid-track-keyword&quot;, select.value);
            });
            &lt;/script&gt;
        &lt;/body&gt;
    '
&gt;
    &lt;p&gt;이 문구가 보인다면, 개인의 설정으로 인해 iframe 이 거부되었을 확률이 높습니다.&lt;/p&gt;
&lt;/iframe&gt;

&lt;p&gt;&lt;strong&gt;end&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;위에 만들어 놓은 예제를 통해 Grid 의 고유 크기 조정 keyword 들을 익히기를 바란다.&lt;/p&gt;
&lt;p&gt;위의 키워드에서 중요한 것은, 일방적으로 컨테이너가 선언하는 크기만을 선언 할 수 있는 것이 아니라,&lt;/p&gt;
&lt;p&gt;컨테이너 내부에 존재하는 컴포넌트들의 &lt;strong&gt;&amp;quot;컨텐츠 양&amp;quot;&lt;/strong&gt; 에 따라 조절할 수 있다는 것이 핵심이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;조금 더 직접적으로 알아보는 fr, 그리고 추가 함수들&lt;/h2&gt;
&lt;p&gt;이 글에서는 &lt;code&gt;fr&lt;/code&gt; 이라는 단위에 대해서 먼저 설명하긴 했지만,&lt;/p&gt;
&lt;p&gt;컨텐츠의 양에 따라 자동적으로 영역을 분배 해 주는 &lt;code&gt;auto&lt;/code&gt; 가 먼저 배울 내용이라고 생각된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;fr&lt;/code&gt; 단위에 대해서 본격적으로 들어가기 전에, 다시 정보를 짚고 넘어가자.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;fr&lt;/code&gt; 단위는 남는 grid 컨테이너에서 비율로 나눈다.&lt;/li&gt;
&lt;li&gt;컨테이너에 하나의 엘리먼트가 존재한다면, &lt;code&gt;1fr&lt;/code&gt;, &lt;code&gt;3fr&lt;/code&gt; 이던 동일한 크기를 가진다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fr&lt;/code&gt; 단위가 여러번 선언된다면, 해당 트랙 전체 크기에서 비율로 나눈다.&lt;/li&gt;
&lt;li&gt;그리드 템플릿 행, 열은 여러 번 인자를 선언하거나 전달하는 방식으로 구성되므로, &lt;br/&gt; &lt;code&gt;repeat&lt;/code&gt; 와 같은 함수를 자주 사용하게 된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;p&gt;먼저 예제로 하나의 예시를 보고 가자.&lt;/p&gt;
&lt;p&gt;나는 예제를 이렇게 만들었다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;row 방향이며, 총 3 개의 열을 가질 수 있다.&lt;/li&gt;
&lt;li&gt;2 번째 열의 크기만을 바꾸고, 나머지는 &lt;code&gt;1fr&lt;/code&gt; 로 동결이다.&lt;/li&gt;
&lt;li&gt;크기를 선택 해 가며, 비율의 감각을 키우고 넘어가자.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;물론, 나도 css 를 잘하는 사람이 절대 아니기 때문에, 예시를 만들면서 조금씩 감각을 키우고 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;:root {
    --select-free : 1fr;
}
.container {
    display : grid;
    grid-template-columns : 1fr var(--select-free) 1fr;
    width : 20rem;
    border : 2px solid dodgerblue;
    margin : 1rem;
    padding : 1rem;
    gap : 0.75rem;
}
div {
    text-align : center;
    border : 2px solid gray;
    border-radius : 3px;
    box-shadow : 0 5px 5px 0 darkgray;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body&amp;gt;
    두 번째 엘리먼트의 비율을 변화시켜봅시다.
    &amp;lt;br/&amp;gt;
    &amp;lt;select id=&amp;quot;select-fr&amp;quot;&amp;gt;
        &amp;lt;option value=&amp;quot;1fr&amp;quot;&amp;gt;1fr&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;2fr&amp;quot;&amp;gt;2fr&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;3fr&amp;quot;&amp;gt;3fr&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;4fr&amp;quot;&amp;gt;4fr&amp;lt;/option&amp;gt;
    &amp;lt;/select&amp;gt;
    &amp;lt;br/&amp;gt;
    &amp;lt;section class=&amp;quot;container&amp;quot;&amp;gt;
        &amp;lt;div&amp;gt;
            컨테이너 1
        &amp;lt;/div&amp;gt;
        &amp;lt;div style=&amp;quot;background: lightblue;&amp;quot;&amp;gt;
            컨테이너 2 - 변화
        &amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
            컨테이너 3
        &amp;lt;/div&amp;gt;
    &amp;lt;/section&amp;gt;
    &amp;lt;script&amp;gt;
    const select = document.getElementById(&amp;quot;select-fr&amp;quot;);
    const rootStyle = document.documentElement;
    select.addEventListener(&amp;quot;change&amp;quot;, function () {
      rootStyle.style.setProperty(&amp;quot;--select-free&amp;quot;, select.value);
    })
    &amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Preview&lt;/strong&gt; :&lt;/p&gt;
&lt;iframe
    style=&quot;width : 70%; height : 35rem; min-width : 25rem;&quot;
    srcdoc='
        &lt;style&gt;
            body {
                background : white;
            }
            :root {
                --select-free : 1fr;
            }
            .container {
                display : grid;
                grid-template-columns : 1fr var(--select-free) 1fr;
                width : 20rem;
                border : 2px solid dodgerblue;
                margin : 1rem;
                padding : 1rem;
                gap : 0.75rem;
            }
            div {
                text-align : center;
                border : 2px solid gray;
                border-radius : 3px;
                box-shadow : 0 5px 5px 0 darkgray;
            }
        &lt;/style&gt;
        &lt;body&gt;
            두 번째 엘리먼트의 비율을 변화시켜봅시다.
            &lt;br/&gt;
            &lt;select id=&quot;select-fr&quot;&gt;
                &lt;option value=&quot;1fr&quot;&gt;1fr&lt;/option&gt;
                &lt;option value=&quot;2fr&quot;&gt;2fr&lt;/option&gt;
                &lt;option value=&quot;3fr&quot;&gt;3fr&lt;/option&gt;
                &lt;option value=&quot;4fr&quot;&gt;4fr&lt;/option&gt;
            &lt;/select&gt;
            &lt;br/&gt;
            &lt;section class=&quot;container&quot;&gt;
                &lt;div&gt;
                    컨테이너 1
                &lt;/div&gt;
                &lt;div style=&quot;background: lightblue;&quot;&gt;
                    컨테이너 2 - 변화
                &lt;/div&gt;
                &lt;div&gt;
                    컨테이너 3
                &lt;/div&gt;
            &lt;/section&gt;
            &lt;script&gt;
            const select = document.getElementById(&quot;select-fr&quot;);
            const rootStyle = document.documentElement;
            select.addEventListener(&quot;change&quot;, function () {
              rootStyle.style.setProperty(&quot;--select-free&quot;, select.value);
            })
            &lt;/script&gt;
        &lt;/body&gt;
    '
&gt;
    &lt;p&gt;이 문구가 보인다면, 개인의 설정으로 인해 iframe 이 거부되었을 확률이 높습니다.&lt;/p&gt;
&lt;/iframe&gt;

&lt;p&gt;&lt;strong&gt;end&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;하나의 트랙 안에서, 선택한 2 번째의 엘리먼트가&lt;/p&gt;
&lt;p&gt;가지는 &lt;code&gt;fr&lt;/code&gt; 단위에 따라서 어떤 변화를 보이는지 볼 수 있을 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;fr&lt;/code&gt; 단위를 가지는 엘리먼트들은 남는 영역을 경쟁하므로,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;1fr&lt;/code&gt; --&amp;gt; &lt;code&gt;3fr&lt;/code&gt; 했을 때, &lt;code&gt;1fr&lt;/code&gt; 에 비해 3 배 커진 것이 아니라,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;(3/5)fr&lt;/code&gt; 의 크기를 가지게 되었다는 것이 중요하다.&lt;/p&gt;
&lt;p&gt;만약 &lt;code&gt;1fr&lt;/code&gt; 을 선택했다면, &lt;code&gt;(1/3)fr&lt;/code&gt; 의 크기를 가지게 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;새로운 함수 minmax 와 함수의 조합&lt;/h3&gt;
&lt;p&gt;grid 템플릿 선언에서는 인자의 중복 선언으로 개별 열, 혹은 행을 생성 할 수 있게 해 준다.&lt;/p&gt;
&lt;p&gt;그러나, 이러한 행동은 코드의 길이를 과도하게 늘릴 뿐만 아니라, 번거롭게 한다.&lt;/p&gt;
&lt;p&gt;따라서, 위의 예제에서 &lt;code&gt;repeat&lt;/code&gt; CSS Function 을 작성하며 간단하게 선언했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그리고 이번에는 새로운 함수, &lt;code&gt;minmax&lt;/code&gt; 를 알아 볼 시간이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;minmax 함수란 무엇인가?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이 함수는 우리가 여태까지&lt;/p&gt;
&lt;p&gt;&lt;code&gt;grid-template-columns or rows : 50px 1rem 30% auto min-content max-content fit..&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;이렇게 하나의 인자에 다양한 단위를 넣을 수 있던 것 처럼,&lt;/p&gt;
&lt;p&gt;이 인자 중 하나에 넣을 수 있는 함수값에 해당한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;minmax(1 번째 인자, 2 번째 인자)&lt;/code&gt; 의 형태를 띄게 되는데,&lt;/p&gt;
&lt;p&gt;1 번째 인자는 이 &amp;quot;열&amp;quot; 의 &amp;quot;최소 크기&amp;quot; 를 의미하며,&lt;/p&gt;
&lt;p&gt;2 번째 인자는 이 &amp;quot;열&amp;quot; 의 &amp;quot;최대 크기&amp;quot; 를 의미한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;여기서 다양한 조합이 나올 수 있게 되는데,&lt;/p&gt;
&lt;p&gt;단순한 50 ~ 100 px 로도 만들 수 있겠지만,&lt;/p&gt;
&lt;p&gt;이러한 조합이 가능하다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;minmax(auto, 1fr)&lt;/code&gt; : 최소 크기는 &amp;quot;컨텐츠에 따라&amp;quot; 맞추며, 최대 크기는 하나의 Grid Track 이다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;minmax(0, 1fr)&lt;/code&gt; : 최소 크기는 컨텐츠 여부에 상관없이 0 길이에 도달 가능하며, &lt;br/&gt; 최대 크기는 하나의 Grid Track 을 의미한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;repeat(12, minmax(0, 1fr))&lt;/code&gt; : &lt;br/&gt; 컨테이너의 요소 개수에 따라 분배하며, 트랙에 빈 공간은 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;요약하자면&lt;/strong&gt;,&lt;/p&gt;
&lt;p&gt;선언된 열, 혹은 행 은 인자에 따라 &amp;quot;최소&amp;quot; 혹은 &amp;quot;최대&amp;quot; 크기를 갖게 되는 함수라는 의미이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;repeat 인자로 사용될 수 있는 새로운 단어, &amp;quot;auto-fill&amp;quot; &amp;quot;auto-fit&amp;quot;&lt;/h3&gt;
&lt;p&gt;기존에 &lt;code&gt;repeat&lt;/code&gt; 라는 CSS 함수를 사용하기 위해서는,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;repeat(반복 횟수, &amp;quot;들어갈 문장&amp;quot;);&lt;/code&gt; 형식으로 사용했다.&lt;/p&gt;
&lt;p&gt;그런데, 위의 &lt;code&gt;auto-fill&lt;/code&gt;, &lt;code&gt;auto-fit&lt;/code&gt; 이라는 단어는 &amp;quot;반복 횟수&amp;quot; 인자에 들어 갈 수 있다.&lt;/p&gt;
&lt;p&gt;직관적으로 이해되지 않았는데,&lt;/p&gt;
&lt;p&gt;이 문장을 몇 번 인자로 넣을 것인지에 대한 대답이, &lt;code&gt;auto-fill&lt;/code&gt;, &lt;code&gt;auto-fit&lt;/code&gt; 이라는 것에&lt;/p&gt;
&lt;p&gt;의문을 가졌다.&lt;/p&gt;
&lt;p&gt;그리고 &lt;code&gt;repeat&lt;/code&gt; 안에 들어가는 &lt;code&gt;auto-???&lt;/code&gt; 선언은 무조건 하나의 함수가 더 조합되어야 하는데,&lt;/p&gt;
&lt;p&gt;바로 &lt;code&gt;minmax&lt;/code&gt; 이다.&lt;/p&gt;
&lt;p&gt;밑에서 예제로 &lt;code&gt;minmax&lt;/code&gt; 를 사용하지 않고 절대단위로 사용했는데, 변화가 없었다.&lt;/p&gt;
&lt;p&gt;즉, 이 두 속성, &lt;code&gt;auto-fill&lt;/code&gt;, &lt;code&gt;auto-fit&lt;/code&gt; 에 대한 이해가 부족했기 때문에&lt;/p&gt;
&lt;p&gt;변화를 직접 만들 수가 없었다.&lt;/p&gt;
&lt;p&gt;먼저, 설명과 예제를 보자.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;이 2 개의 공통점&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;둘 다, Grid 를 반응형 디자인으로 만들어 준다.&lt;/p&gt;
&lt;p&gt;즉, 명시적으로 &amp;quot;횟수&amp;quot; 를 선언하는 게 아니라,&lt;/p&gt;
&lt;p&gt;자연스러운 줄넘김을 구사할 수 있는 것이다.&lt;/p&gt;
&lt;p&gt;먼저 간단하게 짚고 넘어가자면,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;auto-fill&lt;/code&gt; : &lt;code&gt;repeat(auto-fill, minmax(200px, 1fr));&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;auto-fit&lt;/code&gt; : &lt;code&gt;repeat(auto-fit, minmax(200px, 1fr));&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이러한 방식으로 사용한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;auto-fill&lt;/code&gt; 은, 하나의 트랙이 오버플로 되지 않을 만큼, (padding, gap 더하면 오버플로 값이 나올 수 있으므로,)&lt;/p&gt;
&lt;p&gt;채우되, 만약에 Grid Track 에 남는 공간이 있다면,(오른쪽 공간) 이를 냅둔다.&lt;/p&gt;
&lt;p&gt;그러나,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;auto-fit&lt;/code&gt; 은, 하나의 트랙이 오버플로 되지 않을 만큼 추가해 주는 기능을 동일하게 가지지만,&lt;/p&gt;
&lt;p&gt;남는 공간을 분배하여 트랙이 꽉 차게 만든다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;왜 minmax&lt;/strong&gt; 를 사용해야 하는가?&lt;/p&gt;
&lt;p&gt;여기서 중요한 게, 두 선언 &lt;code&gt;auto-???&lt;/code&gt; 는 Grid Track 을 인식하고,&lt;/p&gt;
&lt;p&gt;&amp;quot;컨테이너의 너비&amp;quot; 는 고려 대상이 아니라는 것이다.&lt;/p&gt;
&lt;p&gt;아니 컨테이너의 너비가 결국 Grid Track 의 너비가 아닌가? 라고 생각했는데,&lt;/p&gt;
&lt;p&gt;하위 컴포넌트에서 Grid Track 의 최종 길이 (padding 과 gap 등등을 뺀 결과)&lt;/p&gt;
&lt;p&gt;를 인식하기 위해서는, &lt;code&gt;1fr&lt;/code&gt; 을 사용해야 한다는 것이다.&lt;/p&gt;
&lt;p&gt;이러한 이유로 인해, &lt;code&gt;auto-???&lt;/code&gt; 두 속성은 &lt;code&gt;minmax&lt;/code&gt; 함수와 결합되어 사용된다.&lt;/p&gt;
&lt;br/&gt;

&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;:root {
    --auto-var : auto-fill;
}
.container {
    display : grid;
    width : 23rem;
    grid-template-columns : repeat(var(--auto-var), minmax(5rem, 1fr));
    margin : 1.5rem;
    padding : 1.5rem;
    gap : 1rem;
    background-color : gray;
}
div {
    text-align : center;
    background : white;
    box-shadow : 0 0 0.5rem darkgray;
    border-radius : 0.25rem;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body&amp;gt;
    값을 바꿔가며 변화를 확인 해 보세요.
    &amp;lt;br/&amp;gt;
    &amp;lt;select id=&amp;quot;auto-select&amp;quot;&amp;gt;
        &amp;lt;option value=&amp;quot;auto-fill&amp;quot;&amp;gt;auto-fill&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;auto-fit&amp;quot;&amp;gt;auto-fit&amp;lt;/option&amp;gt;
    &amp;lt;/select&amp;gt;
    &amp;lt;br/&amp;gt;
    &amp;lt;section id=&amp;quot;container&amp;quot; class=&amp;quot;container&amp;quot;&amp;gt;
        &amp;lt;div&amp;gt;
            엘리먼트 1
        &amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
            엘리먼트 2
        &amp;lt;/div&amp;gt;
    &amp;lt;/section&amp;gt;
    &amp;lt;br/&amp;gt;
    &amp;lt;button onclick=&amp;quot;appending()&amp;quot;&amp;gt;엘리먼트 추가&amp;lt;/button&amp;gt;
    &amp;lt;br/&amp;gt;
    &amp;lt;button onclick=&amp;quot;initial()&amp;quot;&amp;gt;초기화&amp;lt;/button&amp;gt;
    &amp;lt;script&amp;gt;
    const select = document.getElementById(&amp;quot;auto-select&amp;quot;);
    const rootStyle = document.documentElement;
    const container = document.getElementById(&amp;quot;container&amp;quot;);
    select.addEventListener(&amp;quot;change&amp;quot;, function () {
      rootStyle.style.setProperty(&amp;quot;--auto-var&amp;quot;, select.value);
    })
    // 예제를 위한 추가 함수이므로, 위에 집중하셔도 됩니다.
    function appending() {
      const newElem = document.createElement(&amp;quot;div&amp;quot;);
      newElem.textContent = &amp;quot;새 엘리먼트&amp;quot;;
      container.appendChild(newElem);
    }
    function initial() {
      container.innerHTML = &amp;quot;&amp;lt;div&amp;gt;엘리먼트 1&amp;lt;/div&amp;gt;&amp;lt;div&amp;gt;엘리먼트 2&amp;lt;/div&amp;gt;&amp;quot;;
    }
    &amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Preview&lt;/strong&gt; :&lt;/p&gt;
&lt;iframe
    style=&quot;width : 70%; height : 25rem; min-width : 27rem;&quot;
    srcdoc='
        &lt;style&gt;
            body {
                background : white;
            }
            :root {
                --auto-var : auto-fill;
            }
            .container {
                display : grid;
                width : 23rem;
                grid-template-columns : repeat(var(--auto-var), minmax(5rem, 1fr));
                margin : 1.5rem;
                padding : 1.5rem;
                gap : 1rem;
                background-color : gray;
            }
            div {
                text-align : center;
                background : white;
                box-shadow : 0 0 0.5rem darkgray;
                border-radius : 0.25rem;
            }
        &lt;/style&gt;
        &lt;body&gt;
            값을 바꿔가며 변화를 확인 해 보세요.
            &lt;br/&gt;
            &lt;select id=&quot;auto-select&quot;&gt;
                &lt;option value=&quot;auto-fill&quot;&gt;auto-fill&lt;/option&gt;
                &lt;option value=&quot;auto-fit&quot;&gt;auto-fit&lt;/option&gt;
            &lt;/select&gt;
            &lt;br/&gt;
            &lt;section id=&quot;container&quot; class=&quot;container&quot;&gt;
                &lt;div&gt;
                    엘리먼트 1
                &lt;/div&gt;
                &lt;div&gt;
                    엘리먼트 2
                &lt;/div&gt;
            &lt;/section&gt;
            &lt;br/&gt;
            &lt;button onclick=&quot;appending()&quot;&gt;엘리먼트 추가&lt;/button&gt;
            &lt;br/&gt;
            &lt;button onclick=&quot;initial()&quot;&gt;초기화&lt;/button&gt;
            &lt;script&gt;
            const select = document.getElementById(&quot;auto-select&quot;);
            const rootStyle = document.documentElement;
            const container = document.getElementById(&quot;container&quot;);
            select.addEventListener(&quot;change&quot;, function () {
              rootStyle.style.setProperty(&quot;--auto-var&quot;, select.value);
            })
            function appending() {
              const newElem = document.createElement(&quot;div&quot;);
              newElem.textContent = &quot;새 엘리먼트&quot;;
              container.appendChild(newElem);
            }
            function initial() {
              container.innerHTML = &quot;&lt;div&gt;엘리먼트 1&lt;/div&gt;&lt;div&gt;엘리먼트 2&lt;/div&gt;&quot;;
            }
            &lt;/script&gt;
        &lt;/body&gt;
    '
&gt;
    &lt;p&gt;이 문구가 보인다면, 개인의 설정으로 인해 iframe 이 거부되었을 확률이 높습니다.&lt;/p&gt;
&lt;/iframe&gt;

&lt;p&gt;&lt;strong&gt;end&lt;/strong&gt; :&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;auto-fit&lt;/code&gt;, &lt;code&gt;auto-fill&lt;/code&gt; 은,&lt;/p&gt;
&lt;p&gt;하나의 줄에 엘리먼트를 나열하는 상태에서, 아직 컬럼을 넣을 수 있는 상태에서 차이를 보인다.&lt;/p&gt;
&lt;p&gt;즉, 이미 하나의 트랙이 채워진 상태에서는, &lt;code&gt;auto-fit&lt;/code&gt;, &lt;code&gt;auto-fill&lt;/code&gt; 은 동일하게 보인다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;자동 배치&lt;/h2&gt;
&lt;p&gt;지금까지 볼 수 있었던 배치는, 주로 왼쪽에서 오른쪽으로 향하는 배치 방식이었다.&lt;/p&gt;
&lt;p&gt;그런데, Grid 레이아웃에서는, 위아래 작성 후 오른쪽으로 줄넘김하는 것이 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;row&lt;/code&gt;, &lt;code&gt;column&lt;/code&gt; 두 개의 방향을 모두 만족하기 위해서는,&lt;/p&gt;
&lt;p&gt;들어갈 수 있는 최대 엘리먼트를 인자를 통해 지정 해 주어야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;grid-auto-flow&lt;/h3&gt;
&lt;p&gt;위의 기능을 가능하게 만들어 주는 것이 바로 이 속성이다.&lt;/p&gt;
&lt;p&gt;여기에 &lt;code&gt;row&lt;/code&gt;, &lt;code&gt;column&lt;/code&gt; 2 개의 값이 들어 갈 수 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;row&lt;/code&gt; : 여태까지 봤던 것 처럼 엘리먼트가 순서대로 배치됨.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;column&lt;/code&gt; : 수직으로 먼저 작성되는데, 위아래로 향하며, 수직이 채워지면 그 다음 열에서 시작한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;역시, 이러한 것도 예제를 통해 직접적으로 변화를 보는 것이 좋다고 생각한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;:root {
    --auto-flow : row;
}
.container {
    display : grid;
    width : 20rem;
    grid-template-columns : repeat(3, 1fr);
    grid-template-rows : repeat(3, 1fr);
    grid-auto-flow : var(--auto-flow);
    margin : 1.5rem;
    padding : 1.5rem;
    gap : 1rem;
    background-color : gray;
}
div {
    text-align : center;
    background : white;
    box-shadow : 0 0 0.5rem black;
    border-radius : 0.25rem;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body&amp;gt;
    &amp;lt;select id=&amp;quot;select-flow&amp;quot;&amp;gt;
        &amp;lt;option value=&amp;quot;row&amp;quot;&amp;gt;row&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;column&amp;quot;&amp;gt;column&amp;lt;/option&amp;gt;
    &amp;lt;/select&amp;gt;
    &amp;lt;br/&amp;gt;
    &amp;lt;section class=&amp;quot;container&amp;quot;&amp;gt;
        &amp;lt;div&amp;gt;
            엘리먼트 1
        &amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
            엘리먼트 2
        &amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
            엘리먼트 3
        &amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
            엘리먼트 4
        &amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
            엘리먼트 5
        &amp;lt;/div&amp;gt;
    &amp;lt;/section&amp;gt;
    &amp;lt;script&amp;gt;
    const select = document.getElementById(&amp;quot;select-flow&amp;quot;);
    const rootStyle = document.documentElement;
    select.addEventListener(&amp;quot;change&amp;quot;, function() {
      rootStyle.style.setProperty(&amp;quot;--auto-flow&amp;quot;, select.value);
    })
    &amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Preview&lt;/strong&gt; :&lt;/p&gt;
&lt;iframe
    style=&quot;width : 70%; height : 25rem; min-width : 27rem;&quot;
    srcdoc='
        &lt;style&gt;
            body {
                background : white;
            }
            :root {
                --auto-flow : row;
            }
            .container {
                display : grid;
                width : 20rem;
                grid-template-columns : repeat(3, 1fr);
                grid-template-rows : repeat(3, 1fr);
                grid-auto-flow : var(--auto-flow);
                margin : 1.5rem;
                padding : 1.5rem;
                gap : 1rem;
                background-color : gray;
            }
            div {
                text-align : center;
                background : white;
                box-shadow : 0 0 0.5rem black;
                border-radius : 0.25rem;
            }
        &lt;/style&gt;
        &lt;body&gt;
            &lt;select id=&quot;select-flow&quot;&gt;
                &lt;option value=&quot;row&quot;&gt;row&lt;/option&gt;
                &lt;option value=&quot;column&quot;&gt;column&lt;/option&gt;
            &lt;/select&gt;
            &lt;br/&gt;
            &lt;section class=&quot;container&quot;&gt;
                &lt;div&gt;
                    엘리먼트 1
                &lt;/div&gt;
                &lt;div&gt;
                    엘리먼트 2
                &lt;/div&gt;
                &lt;div&gt;
                    엘리먼트 3
                &lt;/div&gt;
                &lt;div&gt;
                    엘리먼트 4
                &lt;/div&gt;
                &lt;div&gt;
                    엘리먼트 5
                &lt;/div&gt;
            &lt;/section&gt;
            &lt;script&gt;
            const select = document.getElementById(&quot;select-flow&quot;);
            const rootStyle = document.documentElement;
            select.addEventListener(&quot;change&quot;, function() {
              rootStyle.style.setProperty(&quot;--auto-flow&quot;, select.value);
            })
            &lt;/script&gt;
        &lt;/body&gt;
    '
&gt;
    &lt;p&gt;이 문구가 보인다면, 개인의 설정으로 인해 iframe 이 거부되었을 확률이 높습니다.&lt;/p&gt;
&lt;/iframe&gt;

&lt;p&gt;&lt;strong&gt;end&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;지정, 자동 배치 그리고 트랙 늘리기&lt;/h2&gt;
&lt;p&gt;이제까지 우리는 Grid Container 가 선언된 엘리먼트가&lt;/p&gt;
&lt;p&gt;하위 엘리먼트를 어떻게 정렬하고, 조정하는지 보았다.&lt;/p&gt;
&lt;p&gt;지금부터 작성되는 것은, Grid Container 바로 하위에 존재하는 grid-item 들이 선언하는 항목이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;Flexbox 즉, &lt;code&gt;display : flex&lt;/code&gt; 가 선언되었을 때도 flex-item 들이 존재했는데,&lt;/p&gt;
&lt;p&gt;여기서도 &lt;code&gt;display : grid&lt;/code&gt; 가 선언되었을 때, grid-item 에 선언하는 속성들이 존재했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;컨테이너 하위에 존재하는 요소에 이러한 항목을 선언 할 수 있다.&lt;/p&gt;
&lt;p&gt;먼저 보고 세부 내용을 보도록 하자.&lt;/p&gt;
&lt;h3&gt;grid-item 에서 선언 할 수 있는 속성들&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;grid-column-start&lt;/code&gt; : 이 엘리먼트의 Grid Column Line 시작 지점&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grid-column-end&lt;/code&gt; : 이 엘리먼트의 Grid Column Line 끝 지점&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grid-row-start&lt;/code&gt; : 이 엘리먼트의 Grid Row Line 시작 지점&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grid-row-end&lt;/code&gt; : 이 엘리먼트의 Grid Row Line 끝 지점&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grid-column&lt;/code&gt; : Grid Column Line 의 시작과 끝을 모두 선언하는 단축 속성&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grid-row&lt;/code&gt; : Grid Row Line 의 시작과 끝을 모두 선언하는 단축 속성&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;만약에 같은 열, 혹은 행 에 대해서 시작과 끝이 모두 선언되었을 때,&lt;/p&gt;
&lt;p&gt;그 간격이 1 이상이라면, &lt;code&gt;span&lt;/code&gt;(확장) 을 의미한다.&lt;/p&gt;
&lt;p&gt;모든 속성에는 &lt;code&gt;span&lt;/code&gt; 이 인자로 들어갈 수 있다.&lt;/p&gt;
&lt;p&gt;EX - &lt;code&gt;span 2&lt;/code&gt; --&amp;gt; 선언된 속성에 따라 해당 방향으로 2 칸으로 확장.&lt;/p&gt;
&lt;p&gt;그리고 꼭 기억해야 할 게, 단축 속성인 &lt;code&gt;grid-column&lt;/code&gt;, &lt;code&gt;grid-row&lt;/code&gt; 는,&lt;/p&gt;
&lt;p&gt;시작과 끝을 &lt;code&gt;/&lt;/code&gt; 로 나눈다. 이는 나눗셈으로 생각해서는 안되고,&lt;/p&gt;
&lt;p&gt;시작과 끝을 나누는 델림(delim) 으로 해석해야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;한번, 간단하게 이 예제가 적용된 예시를 보자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;:root {
    --select-row : 1;
    --select-col : 1;
}
.container {
    display : grid;
    width : 20rem;
    grid-template-columns : repeat(5, 1fr);
    grid-template-rows : repeat(5, 1fr);
    margin : 1.5rem;
    padding : 1.5rem;
    gap : 1rem;
    background-color : gray;
}
div {
    text-align : center;
    background : white;
    box-shadow : 0 0 0.5rem black;
    border-radius : 0.25rem;
}
div.span-row {
    grid-column : auto / span var(--select-row);
}
div.span-col {
    grid-row : auto / span var(--select-col);
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body&amp;gt;
    grid-column 적용 엘리먼트의 크기를 조정 해 보세요.
    &amp;lt;br/&amp;gt;
    &amp;lt;select id=&amp;quot;select-row-span&amp;quot;&amp;gt;
        &amp;lt;option value=&amp;quot;1&amp;quot;&amp;gt;1&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;2&amp;quot;&amp;gt;2&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;3&amp;quot;&amp;gt;3&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;4&amp;quot;&amp;gt;4&amp;lt;/option&amp;gt;
    &amp;lt;/select&amp;gt;
    &amp;lt;br/&amp;gt;
    grid-row 적용 엘리먼트의 크기를 조정 해 보세요.
    &amp;lt;br/&amp;gt;
    &amp;lt;select id=&amp;quot;select-col-span&amp;quot;&amp;gt;
        &amp;lt;option value=&amp;quot;1&amp;quot;&amp;gt;1&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;2&amp;quot;&amp;gt;2&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;3&amp;quot;&amp;gt;3&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;4&amp;quot;&amp;gt;4&amp;lt;/option&amp;gt;
    &amp;lt;/select&amp;gt;
    &amp;lt;section class=&amp;quot;container&amp;quot;&amp;gt;
        &amp;lt;div&amp;gt;
            기본 엘리먼트
        &amp;lt;/div&amp;gt;
        &amp;lt;div class=&amp;quot;span-row&amp;quot;&amp;gt;
            grid-column 적용 엘리먼트
        &amp;lt;/div&amp;gt;
        &amp;lt;div class=&amp;quot;span-col&amp;quot;&amp;gt;
            grid-row 적용 엘리먼트
        &amp;lt;/div&amp;gt;
    &amp;lt;/section&amp;gt;
    &amp;lt;script&amp;gt;
    const selectRow = document.getElementById(&amp;quot;select-row-span&amp;quot;);
    const selectCol = document.getElementById(&amp;quot;select-col-span&amp;quot;);
    const rootElem = document.documentElement;
    function changeVal (event) {
      const id = event.target.id;
      if(id == &amp;quot;select-row-span&amp;quot;) {
        rootElem.style.setProperty(&amp;quot;--select-row&amp;quot;, event.target.value);
      } else {
        rootElem.style.setProperty(&amp;quot;--select-col&amp;quot;, event.target.value);
      }
    }
    selectRow.addEventListener(&amp;quot;change&amp;quot;, changeVal);
    selectCol.addEventListener(&amp;quot;change&amp;quot;, changeVal);
    &amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;preview&lt;/strong&gt; :&lt;/p&gt;
&lt;iframe
    style=&quot;width : 70%; height : 30rem; min-width : 27rem;&quot;
    srcdoc='
        &lt;style&gt;
            body {
                background : white;
            }
            :root {
                --select-row : 1;
                --select-col : 1;
            }
            .container {
                display : grid;
                width : 20rem;
                grid-template-columns : repeat(5, 1fr);
                grid-template-rows : repeat(5, 1fr);
                margin : 1.5rem;
                padding : 1.5rem;
                gap : 1rem;
                background-color : gray;
            }
            div {
                text-align : center;
                background : white;
                box-shadow : 0 0 0.5rem black;
                border-radius : 0.25rem;
            }
            div.span-row {
                grid-column : auto / span var(--select-row);
            }
            div.span-col {
                grid-row : auto / span var(--select-col);
            }
        &lt;/style&gt;
        &lt;body&gt;
            grid-column 적용 엘리먼트의 크기를 조정 해 보세요.
            &lt;br/&gt;
            &lt;select id=&quot;select-row-span&quot;&gt;
                &lt;option value=&quot;1&quot;&gt;1&lt;/option&gt;
                &lt;option value=&quot;2&quot;&gt;2&lt;/option&gt;
                &lt;option value=&quot;3&quot;&gt;3&lt;/option&gt;
                &lt;option value=&quot;4&quot;&gt;4&lt;/option&gt;
            &lt;/select&gt;
            &lt;br/&gt;
            grid-row 적용 엘리먼트의 크기를 조정 해 보세요.
            &lt;br/&gt;
            &lt;select id=&quot;select-col-span&quot;&gt;
                &lt;option value=&quot;1&quot;&gt;1&lt;/option&gt;
                &lt;option value=&quot;2&quot;&gt;2&lt;/option&gt;
                &lt;option value=&quot;3&quot;&gt;3&lt;/option&gt;
                &lt;option value=&quot;4&quot;&gt;4&lt;/option&gt;
            &lt;/select&gt;
            &lt;section class=&quot;container&quot;&gt;
                &lt;div&gt;
                    기본 엘리먼트
                &lt;/div&gt;
                &lt;div class=&quot;span-row&quot;&gt;
                    grid-column 적용 엘리먼트
                &lt;/div&gt;
                &lt;div class=&quot;span-col&quot;&gt;
                    grid-row 적용 엘리먼트
                &lt;/div&gt;
            &lt;/section&gt;
            &lt;script&gt;
            const selectRow = document.getElementById(&quot;select-row-span&quot;);
            const selectCol = document.getElementById(&quot;select-col-span&quot;);
            const rootElem = document.documentElement;
            function changeVal (event) {
              const id = event.target.id;
              if(id == &quot;select-row-span&quot;) {
                rootElem.style.setProperty(&quot;--select-row&quot;, event.target.value);
              } else {
                rootElem.style.setProperty(&quot;--select-col&quot;, event.target.value);
              }
            }
            selectRow.addEventListener(&quot;change&quot;, changeVal);
            selectCol.addEventListener(&quot;change&quot;, changeVal);
            &lt;/script&gt;
        &lt;/body&gt;
    '
&gt;
    &lt;p&gt;이 문구가 보인다면, 개인의 설정으로 인해 iframe 이 거부되었을 확률이 높습니다.&lt;/p&gt;
&lt;/iframe&gt;

&lt;p&gt;&lt;strong&gt;end&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;위에서 선언된 &lt;code&gt;grid-column&lt;/code&gt;, &lt;code&gt;grid-row&lt;/code&gt; 는, 복합 인자를 받는다.&lt;/p&gt;
&lt;p&gt;여기서 첫 번째 인자로 &lt;code&gt;auto&lt;/code&gt; 가 들어갔는데, 이 의미는&lt;/p&gt;
&lt;p&gt;&amp;quot;원래 하던 대로 행동&amp;quot; 하겠다는 것이다. 유동적으로 들어가겠다는 의미이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그리고 나는 끝을 넣지 않고, &lt;code&gt;span &amp;lt;숫자&amp;gt;&lt;/code&gt; 를 이용해서 엘리먼트를 넓히는 예제를 선택했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;시작과 끝을 지정하는 것을 하기 전에, 늘리는 예제를 먼저 배우는 것이 나중을 위해서 더 쉽겠다는 생각이 들었다.&lt;/p&gt;
&lt;p&gt;만약에 위의 예제를 단순한 끝과 마지막으로 표현했다면,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;grid-column&lt;/code&gt; : &lt;code&gt;1 / 3&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;grid-row&lt;/code&gt; : &lt;code&gt;2 / 4&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이러한 형태로 나타 낼 수도 있었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;즉, 위의 내용으로 알 수 있는 것은,&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;위에서 선언했던 grid-item 에서 사용 할 수 있는 속성들로,&lt;/p&gt;
&lt;p&gt;Grid Track 들을 &amp;quot;걸쳐서&amp;quot; 보여 줄 수 있다는 것이다.&lt;/p&gt;
&lt;p&gt;이는 Grid Area 이기도 하다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;각 방향에서의 시작과 끝을 마음대로 조정 해 보자&lt;/h3&gt;
&lt;p&gt;위에서는 단순한 열, 행 방향으로의 확장을 보았다.&lt;/p&gt;
&lt;p&gt;이번에는 5 x 5 크기의 컨테이너에서, 하나의 엘리먼트가 어떻게 확장될 수 있는지,&lt;/p&gt;
&lt;p&gt;그에 대해서 예제를 통해 알아보자.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;내가 의도하는 것은,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;시작 지점과 끝 지점을 직접 조정하면서 어떻게 늘려질 수 있는지 알아보기&lt;/li&gt;
&lt;li&gt;시작과 끝은 같거나, 역으로 성립되면 기본 크기인 1 을 가진다. - css 문법에 어긋나는 행동&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;:root {
    --select-row-start : 1;
    --select-col-start : 1;
    --select-row-end : 2;
    --select-col-end : 2;
}
.container {
    display : grid;
    width : 20rem;
    height : 20rem;
    grid-template-columns : repeat(5, 1fr);
    grid-template-rows : repeat(5, 1fr);
    margin : 1.5rem;
    padding : 1.5rem;
    gap : 1rem;
    background-color : gray;
}
div {
    text-align : center;
    background : white;
    box-shadow : 0 0 0.5rem black;
    border-radius : 0.25rem;
}
div.span-elem {
    grid-column-start : var(--select-col-start);
    grid-column-end : var(--select-col-end);
    grid-row-start : var(--select-row-start);
    grid-row-end : var(--select-row-end);
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body&amp;gt;
    grid-column-start : &amp;lt;select id=&amp;quot;select-col-start&amp;quot;&amp;gt;
        &amp;lt;option value=&amp;quot;1&amp;quot;&amp;gt;1&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;2&amp;quot;&amp;gt;2&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;3&amp;quot;&amp;gt;3&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;4&amp;quot;&amp;gt;4&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;5&amp;quot;&amp;gt;5&amp;lt;/option&amp;gt;
    &amp;lt;/select&amp;gt;&amp;lt;br/&amp;gt;
    grid-column-end : &amp;lt;select id=&amp;quot;select-col-end&amp;quot;&amp;gt;
        &amp;lt;option value=&amp;quot;2&amp;quot;&amp;gt;2&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;3&amp;quot;&amp;gt;3&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;4&amp;quot;&amp;gt;4&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;5&amp;quot;&amp;gt;5&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;6&amp;quot;&amp;gt;6&amp;lt;/option&amp;gt;
    &amp;lt;/select&amp;gt;&amp;lt;br/&amp;gt;
    &amp;lt;br/&amp;gt;
    grid-row-start : &amp;lt;select id=&amp;quot;select-row-start&amp;quot;&amp;gt;
        &amp;lt;option value=&amp;quot;1&amp;quot;&amp;gt;1&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;2&amp;quot;&amp;gt;2&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;3&amp;quot;&amp;gt;3&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;4&amp;quot;&amp;gt;4&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;5&amp;quot;&amp;gt;5&amp;lt;/option&amp;gt;
    &amp;lt;/select&amp;gt;&amp;lt;br/&amp;gt;
    grid-row-end : &amp;lt;select id=&amp;quot;select-row-end&amp;quot;&amp;gt;
        &amp;lt;option value=&amp;quot;2&amp;quot;&amp;gt;2&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;3&amp;quot;&amp;gt;3&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;4&amp;quot;&amp;gt;4&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;5&amp;quot;&amp;gt;5&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;6&amp;quot;&amp;gt;6&amp;lt;/option&amp;gt;
    &amp;lt;/select&amp;gt;
    &amp;lt;section class=&amp;quot;container&amp;quot;&amp;gt;
        &amp;lt;div class=&amp;quot;span-elem&amp;quot;&amp;gt;
            엘리먼트 증감 해 보기
        &amp;lt;/div&amp;gt;
    &amp;lt;/section&amp;gt;
    &amp;lt;script&amp;gt;
    const selectColStart = document.getElementById(&amp;quot;select-col-start&amp;quot;);
    const selectColEnd = document.getElementById(&amp;quot;select-col-end&amp;quot;);
    const selectRowStart = document.getElementById(&amp;quot;select-row-start&amp;quot;);
    const selectRowEnd = document.getElementById(&amp;quot;select-row-end&amp;quot;);
    const rootElem = document.documentElement;
    selectColStart.addEventListener(&amp;quot;change&amp;quot;, changeRange);
    selectColEnd.addEventListener(&amp;quot;change&amp;quot;, changeRange);
    selectRowStart.addEventListener(&amp;quot;change&amp;quot;, changeRange);
    selectRowEnd.addEventListener(&amp;quot;change&amp;quot;, changeRange);
    function changeRange(event) {
      rootElem.style.setProperty(`--${event.target.id}`, event.target.value);
    }
    &amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Preview&lt;/strong&gt; :&lt;/p&gt;
&lt;iframe
    style=&quot;width : 70%; height : 30rem; min-width : 27rem;&quot;
    srcdoc='
        &lt;style&gt;
            body {
                background : white;
            }
            :root {
                --select-row-start : 1;
                --select-col-start : 1;
                --select-row-end : 2;
                --select-col-end : 2;
            }
            .container {
                display : grid;
                width : 20rem;
                height : 20rem;
                grid-template-columns : repeat(5, 1fr);
                grid-template-rows : repeat(5, 1fr);
                margin : 1.5rem;
                padding : 1.5rem;
                gap : 1rem;
                background-color : gray;
            }
            div {
                text-align : center;
                background : white;
                box-shadow : 0 0 0.5rem black;
                border-radius : 0.25rem;
            }
            div.span-elem {
                grid-column-start : var(--select-col-start);
                grid-column-end : var(--select-col-end);
                grid-row-start : var(--select-row-start);
                grid-row-end : var(--select-row-end);
            }
        &lt;/style&gt;
        &lt;body&gt;
            grid-column-start : &lt;select id=&quot;select-col-start&quot;&gt;
                &lt;option value=&quot;1&quot;&gt;1&lt;/option&gt;
                &lt;option value=&quot;2&quot;&gt;2&lt;/option&gt;
                &lt;option value=&quot;3&quot;&gt;3&lt;/option&gt;
                &lt;option value=&quot;4&quot;&gt;4&lt;/option&gt;
                &lt;option value=&quot;5&quot;&gt;5&lt;/option&gt;
            &lt;/select&gt;&lt;br/&gt;
            grid-column-end : &lt;select id=&quot;select-col-end&quot;&gt;
                &lt;option value=&quot;2&quot;&gt;2&lt;/option&gt;
                &lt;option value=&quot;3&quot;&gt;3&lt;/option&gt;
                &lt;option value=&quot;4&quot;&gt;4&lt;/option&gt;
                &lt;option value=&quot;5&quot;&gt;5&lt;/option&gt;
                &lt;option value=&quot;6&quot;&gt;6&lt;/option&gt;
            &lt;/select&gt;&lt;br/&gt;
            &lt;br/&gt;
            grid-row-start : &lt;select id=&quot;select-row-start&quot;&gt;
                &lt;option value=&quot;1&quot;&gt;1&lt;/option&gt;
                &lt;option value=&quot;2&quot;&gt;2&lt;/option&gt;
                &lt;option value=&quot;3&quot;&gt;3&lt;/option&gt;
                &lt;option value=&quot;4&quot;&gt;4&lt;/option&gt;
                &lt;option value=&quot;5&quot;&gt;5&lt;/option&gt;
            &lt;/select&gt;&lt;br/&gt;
            grid-row-end : &lt;select id=&quot;select-row-end&quot;&gt;
                &lt;option value=&quot;2&quot;&gt;2&lt;/option&gt;
                &lt;option value=&quot;3&quot;&gt;3&lt;/option&gt;
                &lt;option value=&quot;4&quot;&gt;4&lt;/option&gt;
                &lt;option value=&quot;5&quot;&gt;5&lt;/option&gt;
                &lt;option value=&quot;6&quot;&gt;6&lt;/option&gt;
            &lt;/select&gt;
            &lt;section class=&quot;container&quot;&gt;
                &lt;div class=&quot;span-elem&quot;&gt;
                    엘리먼트 증감 해 보기
                &lt;/div&gt;
            &lt;/section&gt;
            &lt;script&gt;
            const selectColStart = document.getElementById(&quot;select-col-start&quot;);
            const selectColEnd = document.getElementById(&quot;select-col-end&quot;);
            const selectRowStart = document.getElementById(&quot;select-row-start&quot;);
            const selectRowEnd = document.getElementById(&quot;select-row-end&quot;);
            const rootElem = document.documentElement;
            selectColStart.addEventListener(&quot;change&quot;, changeRange);
            selectColEnd.addEventListener(&quot;change&quot;, changeRange);
            selectRowStart.addEventListener(&quot;change&quot;, changeRange);
            selectRowEnd.addEventListener(&quot;change&quot;, changeRange);
            function changeRange(event) {
              rootElem.style.setProperty(`--${event.target.id}`, event.target.value);
            }
            &lt;/script&gt;
        &lt;/body&gt;
    '
&gt;
    &lt;p&gt;이 문구가 보인다면, 개인의 설정으로 인해 iframe 이 거부되었을 확률이 높습니다.&lt;/p&gt;
&lt;/iframe&gt;

&lt;p&gt;&lt;strong&gt;end&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;위에서,&lt;/p&gt;
&lt;p&gt;각 시작과 끝을 &lt;code&gt;2&lt;/code&gt;, &lt;code&gt;5&lt;/code&gt; 로 설정하면,&lt;/p&gt;
&lt;p&gt;정 중앙에 엘리먼트를 위치하게 만들 수도 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;위에서 사용한&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;grid-column-start&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grid-column-end&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grid-row-start&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grid-row-end&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 4 개의 속성을 항상 같이 사용해야 하는 것은 아니다.&lt;/p&gt;
&lt;p&gt;해당 Grid-Item 이 행, 열 이 2 개의 상황에서,&lt;/p&gt;
&lt;p&gt;어떤 위치부터 시작해야 하는지, 필요한 속성을 빼서 사용하면 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;각각의 속성은 하나씩 선언될 수 있으며, &lt;code&gt;span&lt;/code&gt; 이라는 키워드와 함께 사용이 가능하다.&lt;/p&gt;
&lt;p&gt;예를 들어, &lt;code&gt;column&lt;/code&gt;, &lt;code&gt;row&lt;/code&gt; 던지,&lt;/p&gt;
&lt;p&gt;EX - &lt;code&gt;grid-column-end : span 2&lt;/code&gt; 라면,&lt;/p&gt;
&lt;p&gt;시작 부분에서 2 개의 트랙을 가지며,&lt;/p&gt;
&lt;p&gt;혹은 끝 부분에서 반대로 2 개의 트랙을 가진다는 의미가 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;grid-area 사용하기&lt;/h2&gt;
&lt;p&gt;바로 위의 방식, &lt;code&gt;grid-xxx-start or end&lt;/code&gt; 키워드로 트랙을 걸쳐 생성되는 엘리먼트를 생성할 수 있지만,&lt;/p&gt;
&lt;p&gt;헤더, 사이드바, 컨텐츠, 푸터 의 방식으로 직관적으로 나눌 수 있다.&lt;/p&gt;
&lt;p&gt;단, 직접적으로 문자열로 선언 해 주어야 한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;grid-area&lt;/code&gt; 는, grid-item 에 해당하는 엘리먼트에서 선언하는 속성이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;grid-template-areas 사용하기&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;grid-area&lt;/code&gt; 라는 직관적인 속성을 grid-item 에서 사용하기 위해서는,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;grid-template-areas&lt;/code&gt; 를 Grid Container 에서 선언 해 주어야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이게 어떻게 직관적으로 사용 가능한 grid container 가 되냐면,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.container {
    display : grid;
    width : 18rem;
    grid-template-columns : repeat(1, 3fr);
    grid-template-areas :
        &amp;quot;header header header&amp;quot;
        &amp;quot;sidebar content content&amp;quot;
        &amp;quot;sidebar footer footer&amp;quot;
}
.header {
    grid-area : header;
}
.sidebar {
    grid-area : sidebar;
}
.content {
    grid-area : content;
}
.footer {
    grid-area : footer;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;.container&lt;/code&gt; 의, &lt;code&gt;grid-template-area&lt;/code&gt; 를 살펴보자.&lt;/p&gt;
&lt;p&gt;보면, 문자열로 &amp;quot;직접&amp;quot; 영역을 지정해 놓은 것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;여기서 &lt;code&gt;grid-area : 위에서 선언한 나의 속성;&lt;/code&gt; 으로 쉽게 grid-area 를 만들 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;이 주제를 마무리하며&lt;/h2&gt;
&lt;p&gt;이번에는 Flexbox 에 이은, Grid 사용법을 알아보았다.&lt;/p&gt;
&lt;p&gt;만약에 Grid 레이아웃이 존재하지 않는다고 해도, 이 표현이 불가능한 것은 아니다.&lt;/p&gt;
&lt;p&gt;그러나, 2 차원 나열 레이아웃에서 엘리먼트의 영역 분할,&lt;/p&gt;
&lt;p&gt;혹은 컨텐츠에 따른 영역 분할을 쉽고 동적으로 분할하도록 도와준다는 점에서 호감이 갔다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Grid 에 대해서 더 이해하고 싶다면.&lt;/h3&gt;
&lt;p&gt;Grid 에 대한 깊은 이해를 하고 싶다면,&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://developer.mozilla.org/ko/docs/Glossary/Grid&quot;&gt;MDN - 그리드&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://css-tricks.com/snippets/css/complete-guide-grid/&quot;&gt;CSS-TRICKS - CSS Grid Layout Guide&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;위의 두 문서를 같이 본다면 추가적인 정보를 얻을 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;또한, Grid Layout 를 직접 작성하여 배우는 게임이 존재하는데,&lt;/p&gt;
&lt;p&gt;Grid Garden 이라는 게임이다.&lt;/p&gt;
&lt;p&gt;나는 예제를 전부 직접 작성하며 공식문서를 보고 익힌 것 보다 더 높을 수 밖에 없다.&lt;/p&gt;
&lt;p&gt;이 링크를 통해 끝까지 풀어 Grid 레이아웃을 익히는 것을 추천한다.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://cssgridgarden.com/#ko&quot;&gt;Grid Garden - 게임&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;h2&gt;참조 사이트&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;web.deb 사이트- 그리드&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://web.dev/learn/css/grid?hl=ko&quot;&gt;https://web.dev/learn/css/grid?hl=ko&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;MDN 문서 - Grid&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://developer.mozilla.org/ko/docs/Glossary/Grid&quot;&gt;https://developer.mozilla.org/ko/docs/Glossary/Grid&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Grid Garden - 게임(직감을 발달시키기에 굉장히 유용함)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://cssgridgarden.com/#ko&quot;&gt;https://cssgridgarden.com/#ko&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;MDN 문서 - Grid Layout&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://developer.mozilla.org/ko/docs/Web/CSS/CSS_grid_layout&quot;&gt;https://developer.mozilla.org/ko/docs/Web/CSS/CSS_grid_layout&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;CSS-TRICS - CSS Grid Layout Guide&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://css-tricks.com/snippets/css/complete-guide-grid/&quot;&gt;https://css-tricks.com/snippets/css/complete-guide-grid/&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;</description>
      <category>Web-Server/웹 지식</category>
      <category>CSS</category>
      <category>css grid</category>
      <category>CSS Layout</category>
      <category>Display</category>
      <category>display grid</category>
      <category>grid</category>
      <category>Grid Garden</category>
      <category>grid layout</category>
      <category>grid-template</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/240</guid>
      <comments>https://codecreature.tistory.com/240#entry240comment</comments>
      <pubDate>Mon, 3 Nov 2025 20:56:32 +0900</pubDate>
    </item>
    <item>
      <title>예제와 함께 알아보는 CSS flex 와 Flexbox</title>
      <link>https://codecreature.tistory.com/239</link>
      <description>&lt;h2&gt;제목 : 예제와 함께 알아보는 flex 와 Flexbox&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;이 글을 작성하는 이유&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;다양한 크기의 디바이스가 웹에 접속하는 시대이기 때문에,&lt;/p&gt;
&lt;p&gt;디바이스 크기에 따른 유연한 확장과 축소가 필수인 시대가 되었다.&lt;/p&gt;
&lt;p&gt;핸드폰부터 노트북, 커다란 모니터까지 호환이 가능한 스타일링을 추구하는 시대,&lt;/p&gt;
&lt;p&gt;즉, &amp;quot;반응형 웹 디자인&amp;quot; 이 필수적인 시대가 되었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;2000년대 초중반, HTML 태그를 통해 일방적인 정보의 소통 (Server --&amp;gt; Visitor)&lt;/p&gt;
&lt;p&gt;이 이루어지며, 우리가 하얀 바탕에 기본적인 태그를 작성했을 때의 스타일이 주를 이루었다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;table&lt;/code&gt;, &lt;code&gt;ul&lt;/code&gt;, &lt;code&gt;ol&lt;/code&gt;, &lt;code&gt;li&lt;/code&gt;, &lt;code&gt;iframe&lt;/code&gt; 과 같은 간단한 태그를 이용하여&lt;/p&gt;
&lt;p&gt;정보의 가독성을 확보하고자 하였다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그리고 시간이 흘러 2020년 중반이 되었다.&lt;/p&gt;
&lt;p&gt;제각각의 규칙을 가지고 있었던 사이트들이, 이제는 서로에게 약간의 변화가 있을 뿐,&lt;/p&gt;
&lt;p&gt;대부분의 경우 공통된 컨벤션을 따라 웹이 제작되고 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;다른 것이 있다면, 웹을 실행하는 디바이스 성능의 향상에 따라,&lt;/p&gt;
&lt;p&gt;웹을 제작하는 개발자의 입맛대로 기능적 컴포넌트의 Customization 이 가능해졌다는 것이다.&lt;/p&gt;
&lt;p&gt;이제는 엘리먼트가 각자 가지고 있는 &amp;quot;Box-Model&amp;quot; 을 스스로 스타일링하며,&lt;/p&gt;
&lt;p&gt;블럭 스타일 &lt;code&gt;display : block&lt;/code&gt; 에서 나타낼 수 있는 스타일의 한계를 풀어 준 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이 과정에서 가장 많이 사용된다고도 말할 수 있는 표시 형식은, &lt;code&gt;display : flex&lt;/code&gt; 이다.&lt;/p&gt;
&lt;p&gt;Horizontal, Vertical, 이 둘 중 한 가지 모드로,&lt;/p&gt;
&lt;p&gt;원하는 방향으로 출력 할 수 있으며,&lt;/p&gt;
&lt;p&gt;엘리먼트의 정렬 형식을 Direction 에 따라 커스텀 할 수 있다는 것이 크다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;뿐만 아니라, 모든 디바이스의 출력에 알맞게 표시하기 위한 가장 중요한 요소 중 하나이다.&lt;/p&gt;
&lt;p&gt;가로로 펼쳐진 옵션 8 개가 모니터 상에서 알맞게 표시되었을 때,&lt;/p&gt;
&lt;p&gt;핸드폰에서는 8 개의 옵션을 알맞는 형식으로 줄넘김 해 주는 옵션, &lt;code&gt;flex-wrap : true&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;와 같은 성질을 &lt;code&gt;display : flex&lt;/code&gt; 가 충족 해 주기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그리고 특히, 웹을 제작하며 가장 많이 사용하게 될 &lt;code&gt;Property : Value&lt;/code&gt; 중 하나인 &lt;code&gt;flex&lt;/code&gt; 를,&lt;/p&gt;
&lt;p&gt;제대로 공부하고 넘어가지 않는다면, 이는 기술적 부채로 이어질 것이 분명하다는 생각이 들었다.&lt;/p&gt;
&lt;p&gt;이를 예방하기 위해, Flex, 그리고 Flexbox 를 제대로 공부하고 기록한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;display 와 Flex 에 대해서 간단히 짚고 넘어가자.&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;display&lt;/code&gt; 라는 css 속성에는 정말 다양한 값이 들어 갈 수 있다.&lt;/p&gt;
&lt;p&gt;이 &lt;code&gt;display&lt;/code&gt; 라는 속성에 대해서 적어놓은 글이 있으니,&lt;/p&gt;
&lt;p&gt;만약에 &lt;code&gt;display&lt;/code&gt; 가 뭔지 정확히 모른다면,&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/238&quot;&gt;CSS 레이아웃, display 에 대해서 알아놓자&lt;/a&gt; 를 보고 오는 것이 좋다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;display&lt;/code&gt; 라는 값은 &amp;quot;이 엘리먼트의 출력 형식&amp;quot; 을 정해주는 것이다.&lt;/p&gt;
&lt;p&gt;이 &lt;code&gt;display&lt;/code&gt; 라는 것을 정확히 따져본다면, 아주 추상적으로 이런 것을 정한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;부모나 형제에게 있어, 자신은 어떤 형태로 인식되는가?&lt;/li&gt;
&lt;li&gt;자신에게 속한 자식 혹은 손자 엘리먼트들은 어떻게 표시되는가?&lt;/li&gt;
&lt;li&gt;브라우저 자체에게 있어 자신은 어떤 형태로 인식되는가?&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;p&gt;이를 정하는 것이 &lt;code&gt;display&lt;/code&gt; 라고 간략하게 말할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;만약에 &lt;code&gt;inline xxx&lt;/code&gt; or &lt;code&gt;inline-xxx&lt;/code&gt; 라고 붙여진 속성이라면,&lt;/p&gt;
&lt;p&gt;위의 계층에서 나를 바라보았을 때, &amp;quot;글자&amp;quot; 처럼 취급된다.&lt;/p&gt;
&lt;p&gt;만약에 그렇지 않고, &lt;code&gt;block&lt;/code&gt;, &lt;code&gt;flex&lt;/code&gt;, &lt;code&gt;grid&lt;/code&gt;, &lt;code&gt;table&lt;/code&gt;, .... 등등&lt;/p&gt;
&lt;p&gt;단순 Property 로 구성되었을 경우, 이는 상위 계층 혹은 동일 계층에서 있어&lt;/p&gt;
&lt;p&gt;인식되었을 때, &amp;quot;영역&amp;quot; 으로 인식된다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;만약에 inline-flex or inline flex 라면?&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;display : inline-flex&lt;/code&gt; or &lt;code&gt;inline flex&lt;/code&gt; 라고 정해졌을 경우,&lt;/p&gt;
&lt;p&gt;이 엘리먼트를 감싸는 개체는 이를 텍스트처럼 배치한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;display : flex&lt;/code&gt; 라면, 외부에서 바라보았을 때는 &lt;code&gt;display : block&lt;/code&gt; 처럼 영역을 가진다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;둘의 차이점은, 외부에서 바라보는 &amp;quot;배치의 차이점&amp;quot; 인 것이다.&lt;/p&gt;
&lt;p&gt;공통점은, 내부 엘리먼트를 &amp;quot;어떻게 배치 할 것인가?&amp;quot; 인 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;flex 에 대한 이론적인 기초&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;flex&lt;/code&gt; 라는 속성은, 내부의 엘리먼트를 1차원 배열 형태로 나열시키는 속성이다.&lt;/p&gt;
&lt;p&gt;단, 이 속성에 대한 기능적인 부분이 굉장히 세세하여 따로 분야가 나뉠 정도이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;기본적으로 &lt;code&gt;display : flex&lt;/code&gt; 만 선언 될 경우,&lt;/p&gt;
&lt;p&gt;내부 엘리먼트들은 Horizontal(수평) 방향으로 왼쪽에서 오른쪽으로 배치된다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;display : flex&lt;/code&gt; 가 선언되어야, &lt;code&gt;flex&lt;/code&gt; 와 직접 관련된 속성들이 효과를 발휘한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;또한, &lt;code&gt;display : flex&lt;/code&gt; 속성은, 직속 자식 엘리먼트들을 flex-item 이라는 관점으로 보게 만든다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;display : flex&lt;/code&gt; 가 선언된다 해도, 하위 요소의 &lt;code&gt;display&lt;/code&gt; 속성 값은 변하지 않는다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;본격적으로 시작하기 전에, 이 블로그에 사용될 CSS 예시의 형태&lt;/h2&gt;
&lt;p&gt;이를 작성하는 이유는, 이 글을 읽으시는 독자분들의 디바이스나 브라우저에 따라,&lt;/p&gt;
&lt;p&gt;혹은 설정한 커스텀 속성에 따라 &lt;code&gt;iframe&lt;/code&gt; 이 보이지 않을 수 있기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;iframe
    width=&amp;quot;70%&amp;quot;
    max-height=&amp;quot;20rem&amp;quot;
    srcdoc=&amp;#39;
        &amp;lt;style&amp;gt;
            body {
                background : white;
            }
        &amp;lt;/style&amp;gt;
        &amp;lt;body&amp;gt;
        기본 상태는 이러합니당
        &amp;lt;/body&amp;gt;
    &amp;#39;
&amp;gt;
    &amp;lt;p&amp;gt;이 문구가 보인다면, iframe 이 막혀 CSS 예시를 볼 수 없습니다.
&amp;lt;/iframe&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Preview&lt;/strong&gt; :&lt;/p&gt;
&lt;iframe
    width=&quot;70%&quot;
    max-height=&quot;20rem&quot;
    srcdoc='
        &lt;style&gt;
            body {
                background : white;
            }
        &lt;/style&gt;
        &lt;body&gt;
        기본 상태는 이러합니당
        &lt;/body&gt;
    '
&gt;
    &lt;p&gt;이 문구가 보인다면, iframe 이 막혀 CSS 예시를 볼 수 없습니다.
&lt;/iframe&gt;

&lt;p&gt;&lt;strong&gt;end&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;보통은 이 iframe 을 외부 리소스를 끌어오는 용도로 사용하고,&lt;/p&gt;
&lt;p&gt;나처럼 이렇게 사용하는 것은 일반적인 행동은 아니라는 것을 알아주었으면 좋겠다.&lt;/p&gt;
&lt;p&gt;보통은, &lt;code&gt;src : .....&lt;/code&gt; 로 페이지를 보여준다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;일단 잠깐 보고 시작하는 block 과 flex 의 차이&lt;/h2&gt;
&lt;hr&gt;
&lt;h3&gt;block 은 어떻게 표시될까?&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;block&lt;/code&gt; 은, 기본적으로 &amp;quot;하나의 영역에 하나의 줄&amp;quot; 규칙이 적용된다.&lt;/p&gt;
&lt;p&gt;따라서, &lt;code&gt;article&lt;/code&gt;, &lt;code&gt;div&lt;/code&gt;, &lt;code&gt;section&lt;/code&gt; 과 같은 기본 영역 구분자들이 하나의 줄에 하나씩 배치된다.&lt;/p&gt;
&lt;p&gt;어짜피 3 개 모두 같은 CSS 속성을 Default 로 가지고 있어서,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;div&lt;/code&gt; 내부의 &lt;code&gt;section&lt;/code&gt; 3 개가 어떻게 배치되는지 집중해서 보도록 하자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;div {
    /* Default 속성 */
    display : block;
}
section {
    border : 1px solid black;
    border-radius : 4px;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body&amp;gt;
    &amp;lt;div&amp;gt;
        &amp;lt;section&amp;gt;Section 1&amp;lt;/section&amp;gt;
        &amp;lt;section&amp;gt;Section 2&amp;lt;/section&amp;gt;
        &amp;lt;section&amp;gt;Section 3&amp;lt;/section&amp;gt;
        &amp;lt;section&amp;gt;Section 4&amp;lt;/section&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Preview&lt;/strong&gt; :&lt;/p&gt;
&lt;iframe
    width=&quot;70%&quot;
    max-height=&quot;20rem&quot;
    srcdoc='
        &lt;style&gt;
            body {
                background : white;
            }
            div {
                display : block;
            }
            section {
                border : 1px solid black;
                border-radius : 4px;
            }
        &lt;/style&gt;
        &lt;body&gt;
            &lt;div&gt;
                &lt;section&gt;Section 1&lt;/section&gt;
                &lt;section&gt;Section 2&lt;/section&gt;
                &lt;section&gt;Section 3&lt;/section&gt;
                &lt;section&gt;Section 4&lt;/section&gt;
            &lt;/div&gt;
        &lt;/body&gt;
    '
&gt;
    &lt;p&gt;이 문구가 보인다면, iframe 이 막혀 CSS 예시를 볼 수 없습니다.
&lt;/iframe&gt;

&lt;p&gt;&lt;strong&gt;end&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;위의 예제를 본다면, 기본 배치의 경우,&lt;/p&gt;
&lt;p&gt;하나의 블록에 하나의 Line 이 할당되는 것을 볼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이제, &lt;code&gt;div&lt;/code&gt; 가 &lt;code&gt;flex&lt;/code&gt; 가 되면 어떻게 될까?&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;flex 는 어떻게 표시될까?&lt;/h3&gt;
&lt;p&gt;위에서 말했듯, &lt;code&gt;flex&lt;/code&gt; 는 내부 엘리먼트들을 1차원 형태로 표현한다.&lt;/p&gt;
&lt;p&gt;위의 예시를 그대로, &lt;code&gt;flex&lt;/code&gt; 로만 변경한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;div {
    /* 자식 엘리먼트 flex 화 */
    display : flex;
}
section {
    border : 1px solid black;
    border-radius : 4px;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body&amp;gt;
    &amp;lt;div&amp;gt;
        &amp;lt;section&amp;gt;Section 1&amp;lt;/section&amp;gt;
        &amp;lt;section&amp;gt;Section 2&amp;lt;/section&amp;gt;
        &amp;lt;section&amp;gt;Section 3&amp;lt;/section&amp;gt;
        &amp;lt;section&amp;gt;Section 4&amp;lt;/section&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Preview&lt;/strong&gt; :&lt;/p&gt;
&lt;iframe
    width=&quot;70%&quot;
    max-height=&quot;20rem&quot;
    srcdoc='
        &lt;style&gt;
            body {
                background : white;
            }
            div {
                /* 자식 엘리먼트 flex 화 */
                display : flex;
            }
            section {
                border : 1px solid black;
                border-radius : 4px;
            }
        &lt;/style&gt;
        &lt;body&gt;
            &lt;div&gt;
                &lt;section&gt;Section 1&lt;/section&gt;
                &lt;section&gt;Section 2&lt;/section&gt;
                &lt;section&gt;Section 3&lt;/section&gt;
                &lt;section&gt;Section 4&lt;/section&gt;
            &lt;/div&gt;
        &lt;/body&gt;
    '
&gt;
    &lt;p&gt;이 문구가 보인다면, iframe 이 막혀 CSS 예시를 볼 수 없습니다.
&lt;/iframe&gt;

&lt;p&gt;&lt;strong&gt;End&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;위의 예제를 보면, 상위 요소인 &lt;code&gt;div&lt;/code&gt; 의 &lt;code&gt;display&lt;/code&gt; 가 &lt;code&gt;flex&lt;/code&gt; 가 됨에 따라,&lt;/p&gt;
&lt;p&gt;하위 요소인 &lt;code&gt;section&lt;/code&gt; 들이 &amp;quot;가로&amp;quot;(Horizontal or row) 로 정렬 된 것을 볼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;즉, &lt;code&gt;block&lt;/code&gt; 과 &lt;code&gt;flex&lt;/code&gt; 는 이러한 차이점을 가지고 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Flexbox 는 무엇일까?&lt;/h2&gt;
&lt;p&gt;web.dev 에서 Flexbox 에 대해서 알려주는 이론적인 문장은,&lt;/p&gt;
&lt;p&gt;&amp;quot;Flexbox 는 1차원 콘텐츠용으로 설계된 레이아웃 모델이다.&amp;quot;&lt;/p&gt;
&lt;p&gt;&amp;quot;크기가 다른 여러 항목을 가져와서, 해당 항목에 가장 적합한 레이아웃을 반환하는 데 탁월하다&amp;quot;&lt;/p&gt;
&lt;p&gt;라고 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;만약에 이 글을 실제로 읽고 있는 독자가 있다가 잠깐 보고 오기를 권장하는데,&lt;/p&gt;
&lt;p&gt;하이라이트 링크 주소는 : &lt;a target=&quot;_blank&quot; href=&quot;https://web.dev/learn/css/flexbox?hl=ko#%ED%94%8C%EB%A0%89%EC%8A%A4_%EB%A0%88%EC%9D%B4%EC%95%84%EC%9B%83%EC%9C%BC%EB%A1%9C_%EB%AC%B4%EC%97%87%EC%9D%84_%ED%95%A0_%EC%88%98_%EC%9E%88%EB%82%98%EC%9A%94&quot;&gt;플렉스 레이아웃으로 무엇을 할 수 있나요?&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;나는 &amp;quot;플렉스 박스란 무엇인가?&amp;quot; 를 보면서, 오히려 이런 생각이 들었다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;왜 플렉스 박스란 단어가 탄생했는가?&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;기본으로 설정되던 &lt;code&gt;display : block&lt;/code&gt; 과 달리,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;display : flex&lt;/code&gt; 를 선언하면, 내부의 엘리먼트들은 이 선언으로 인해&lt;/p&gt;
&lt;p&gt;시각적으로 정말 다양한 요청을 수행할 수 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Detail 한 정렬&lt;/li&gt;
&lt;li&gt;이 블록 내부의 컨텐츠가 너비를 넘어섰을 때&lt;/li&gt;
&lt;li&gt;작성 순서 (x, y 축에 대해 반대로 가능)&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;이것은 순전히 나의 생각이고 판단인데,&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;1차원 DOM 나열 형태에 있어,&lt;/p&gt;
&lt;p&gt;기본 &lt;code&gt;display&lt;/code&gt; 에 비해 풍부한 기능과 생태계로 인해,&lt;/p&gt;
&lt;p&gt;이를 직관적으로 설명하기 위해 &lt;strong&gt;Flexbox&lt;/strong&gt; 라는 단어를 사용하여 flex 를 설명하지 않나 싶다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;flex-direction 이란? - (작성 방향에 대해서)&lt;/h2&gt;
&lt;p&gt;flex 는 1차원 엘리먼트 나열 방향에 대해 다양한 선택지를 제공한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;row&lt;/code&gt; --&amp;gt; Default&lt;/li&gt;
&lt;li&gt;&lt;code&gt;row-reverse&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;column&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;column-reverse&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;한번, 예제로 알아보자.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;각 flex-direction 에 따른 예제.&lt;/h3&gt;
&lt;p&gt;한번 간단한 DOM 예제에, &lt;code&gt;flex-direction&lt;/code&gt; 만 추가되고 변형 된 상태를 각각 살펴보자.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;flex-direction : row&lt;/code&gt; 의 경우 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;div {
    display : flex;
    flex-direction : row;
}
/* 스타일 가독성을 위해 넣은 속성. */
section {
    margin : 10px;
    padding : 10px;
    border : 2px solid black;
    border-radius : 3px;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;공통 HTML&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body&amp;gt;
  &amp;lt;div&amp;gt;
      &amp;lt;section&amp;gt;섹션 1&amp;lt;/section&amp;gt;
      &amp;lt;section&amp;gt;섹션 2&amp;lt;/section&amp;gt;
      &amp;lt;section&amp;gt;섹션 3&amp;lt;/section&amp;gt;
      &amp;lt;section&amp;gt;섹션 4&amp;lt;/section&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Preview&lt;/strong&gt; :&lt;/p&gt;
&lt;iframe
    width=&quot;70%&quot;
    max-height=&quot;20rem&quot;
    srcdoc='
        &lt;style&gt;
            body {
                background : white;
            }
            div {
                display : flex;
                flex-direction : row;
            }
            /* 스타일 가독성을 위해 넣은 속성. */
            section {
                margin : 10px;
                padding : 10px;
                border : 2px solid black;
                border-radius : 3px;
            }
        &lt;/style&gt;
        &lt;body&gt;
          &lt;div&gt;
              &lt;section&gt;섹션 1&lt;/section&gt;
              &lt;section&gt;섹션 2&lt;/section&gt;
              &lt;section&gt;섹션 3&lt;/section&gt;
              &lt;section&gt;섹션 4&lt;/section&gt;
          &lt;/div&gt;
        &lt;/body&gt;
    '
&gt;
    &lt;p&gt;이 문구가 보인다면, iframe 이 막혀 CSS 예시를 볼 수 없습니다.
&lt;/iframe&gt;

&lt;p&gt;&lt;strong&gt;end&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;flex-direction : row-reverse&lt;/code&gt; 의 경우 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;div {
    display : flex;
    flex-direction : row-reverse;
}
/* 스타일 가독성을 위해 넣은 속성. */
section {
    margin : 10px;
    padding : 10px;
    border : 2px solid black;
    border-radius : 3px;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Preview&lt;/strong&gt; :&lt;/p&gt;
&lt;iframe
    width=&quot;70%&quot;
    max-height=&quot;20rem&quot;
    srcdoc='
        &lt;style&gt;
            body {
                background : white;
            }
            div {
                display : flex;
                flex-direction : row-reverse;
            }
            /* 스타일 가독성을 위해 넣은 속성. */
            section {
                margin : 10px;
                padding : 10px;
                border : 2px solid black;
                border-radius : 3px;
            }
        &lt;/style&gt;
        &lt;body&gt;
          &lt;div&gt;
              &lt;section&gt;섹션 1&lt;/section&gt;
              &lt;section&gt;섹션 2&lt;/section&gt;
              &lt;section&gt;섹션 3&lt;/section&gt;
              &lt;section&gt;섹션 4&lt;/section&gt;
          &lt;/div&gt;
        &lt;/body&gt;
    '
&gt;
    &lt;p&gt;이 문구가 보인다면, iframe 이 막혀 CSS 예시를 볼 수 없습니다.
&lt;/iframe&gt;

&lt;p&gt;&lt;strong&gt;end&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;flex-direction : column&lt;/code&gt; 의 경우 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;div {
    display : flex;
    flex-direction : column;
}
/* 스타일 가독성을 위해 넣은 속성. */
section {
    margin : 10px;
    padding : 10px;
    border : 2px solid black;
    border-radius : 3px;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Preview&lt;/strong&gt; :&lt;/p&gt;
&lt;iframe
    width=&quot;70%&quot;
    max-height=&quot;20rem&quot;
    srcdoc='
        &lt;style&gt;
            body {
                background : white;
            }
            div {
                display : flex;
                flex-direction : column;
            }
            /* 스타일 가독성을 위해 넣은 속성. */
            section {
                margin : 10px;
                padding : 10px;
                border : 2px solid black;
                border-radius : 3px;
            }
        &lt;/style&gt;
        &lt;body&gt;
          &lt;div&gt;
              &lt;section&gt;섹션 1&lt;/section&gt;
              &lt;section&gt;섹션 2&lt;/section&gt;
              &lt;section&gt;섹션 3&lt;/section&gt;
              &lt;section&gt;섹션 4&lt;/section&gt;
          &lt;/div&gt;
        &lt;/body&gt;
    '
&gt;
    &lt;p&gt;이 문구가 보인다면, iframe 이 막혀 CSS 예시를 볼 수 없습니다.
&lt;/iframe&gt;

&lt;p&gt;&lt;strong&gt;end&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;flex-direction : column-reverse&lt;/code&gt; 의 경우 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;div {
    display : flex;
    flex-direction : column-reverse;
}
/* 스타일 가독성을 위해 넣은 속성. */
section {
    margin : 10px;
    padding : 10px;
    border : 2px solid black;
    border-radius : 3px;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Preview&lt;/strong&gt; :&lt;/p&gt;
&lt;iframe
    width=&quot;70%&quot;
    max-height=&quot;20rem&quot;
    srcdoc='
        &lt;style&gt;
            body {
                background : white;
            }
            div {
                display : flex;
                flex-direction : column-reverse;
            }
            /* 스타일 가독성을 위해 넣은 속성. */
            section {
                margin : 10px;
                padding : 10px;
                border : 2px solid black;
                border-radius : 3px;
            }
        &lt;/style&gt;
        &lt;body&gt;
          &lt;div&gt;
              &lt;section&gt;섹션 1&lt;/section&gt;
              &lt;section&gt;섹션 2&lt;/section&gt;
              &lt;section&gt;섹션 3&lt;/section&gt;
              &lt;section&gt;섹션 4&lt;/section&gt;
          &lt;/div&gt;
        &lt;/body&gt;
    '
&gt;
    &lt;p&gt;이 문구가 보인다면, iframe 이 막혀 CSS 예시를 볼 수 없습니다.
&lt;/iframe&gt;

&lt;p&gt;&lt;strong&gt;end&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;flex-wrap 이란? - 오버플로를 막아주는 기능&lt;/h2&gt;
&lt;p&gt;나는 이 기능이 각종 디바이스의 크기에 따라 자신의 스타일 형식을 유지 해 주는&lt;/p&gt;
&lt;p&gt;중요한 기능이라고 생각한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;display : flex&lt;/code&gt; &amp;amp;&amp;amp; &lt;code&gt;flex-wrap : wrap&lt;/code&gt; 를 선언하면,&lt;/p&gt;
&lt;p&gt;1차원 나열 형태로 선언된 박스가, 상위 엘리먼트의 크기를 고려하여,&lt;/p&gt;
&lt;p&gt;내부 엘리먼트가 Overflow 되지 않도록 만들어 준다.&lt;/p&gt;
&lt;p&gt;상위 엘리먼트의 크기는 고정되어 있을 수도, 혹은 사용자의 디바이스에 따라 증감할 수도 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;flex-wrap : wrap&lt;/code&gt; 선언은, 반응형 웹 디자인에 가장 걸맞는 속성이지 않을까 생각된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;한번 예시를 보자.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;flex-wrap&lt;/code&gt; 이 없어 오버플로가 되는 상황 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;div {
    display : flex;
    /* Elem 을 담는 컨테이너 최대 너비를 300px 로 고정. */
    max-width : 300px;
    border : 3px solid dodgerblue;
    gap : 1rem;
}
section {
    /* 애매하게 2 개 까지는 오버플로가 발생하지 않게 설계 */
    width : 120px;
    border : 2px solid black;
    /* section 은 무조건 정해진 너비를 지킬 수 있게 설정 */
    /* 아니라면, 120px 너비를 유지하지 못하고, 좁은 div 에 맞춰 너비가 줄어든다. */
    flex-shrink : 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body&amp;gt;
    &amp;lt;div&amp;gt;
        &amp;lt;section&amp;gt;120px 크기의 섹션 1&amp;lt;/section&amp;gt;
        &amp;lt;section&amp;gt;120px 크기의 섹션 2&amp;lt;/section&amp;gt;
        &amp;lt;section&amp;gt;120px 크기의 섹션 3&amp;lt;/section&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Preview&lt;/strong&gt; :&lt;/p&gt;
&lt;iframe
    width=&quot;70%&quot;
    max-height=&quot;20rem&quot;
    srcdoc='
        &lt;style&gt;
            body {
                background : white;
            }
            div {
                display : flex;
                /* Elem 을 담는 컨테이너 최대 너비를 300px 로 고정. */
                max-width : 300px;
                border : 3px solid dodgerblue;
                gap : 1rem;
            }
            section {
                /* 애매하게 2 개 까지는 오버플로가 발생하지 않게 설계 */
                width : 120px;
                border : 2px solid black;
                flex-shrink : 0;
            }
        &lt;/style&gt;
        &lt;body&gt;
            &lt;div&gt;
                &lt;section&gt;120px 크기의 섹션 1&lt;/section&gt;
                &lt;section&gt;120px 크기의 섹션 2&lt;/section&gt;
                &lt;section&gt;120px 크기의 섹션 3&lt;/section&gt;
            &lt;/div&gt;
        &lt;/body&gt;
    '
&gt;
    &lt;p&gt;이 문구가 보인다면, iframe 이 막혀 CSS 예시를 볼 수 없습니다.
&lt;/iframe&gt;

&lt;p&gt;&lt;strong&gt;end&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;위의 예제를 보면, 고정된 &lt;code&gt;section&lt;/code&gt; 태그의 &lt;code&gt;120px&lt;/code&gt; 로 인해,&lt;/p&gt;
&lt;p&gt;자신을 감싸고 있는 &lt;code&gt;div&lt;/code&gt; 의 영역까지 넘어가는 것을 볼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;flex-wrap&lt;/code&gt; 이 선언되어 오버플로를 막는 상황 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;div {
    display : flex;
    flex-wrap : wrap;
    /* Elem 을 담는 컨테이너 최대 너비를 300px 로 고정. */
    max-width : 300px;
    border : 3px solid dodgerblue;
    gap : 1rem;
}
section {
    /* 애매하게 2 개 까지는 오버플로가 발생하지 않게 설계 */
    width : 120px;
    border : 2px solid black;
    /* section 은 무조건 정해진 너비를 지킬 수 있게 설정 */
    /* 아니라면, 120px 너비를 유지하지 못하고, 좁은 div 에 맞춰 너비가 줄어든다. */
    flex-shrink : 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Preview&lt;/strong&gt; :&lt;/p&gt;
&lt;iframe
    width=&quot;70%&quot;
    max-height=&quot;20rem&quot;
    srcdoc='
        &lt;style&gt;
            body {
                background : white;
            }
            div {
                display : flex;
                flex-wrap : wrap;
                /* Elem 을 담는 컨테이너 최대 너비를 300px 로 고정. */
                max-width : 300px;
                border : 3px solid dodgerblue;
                gap : 1rem;
            }
            section {
                /* 애매하게 2 개 까지는 오버플로가 발생하지 않게 설계 */
                width : 120px;
                border : 2px solid black;
                /* section 은 무조건 정해진 너비를 지킬 수 있게 설정 */
                /* 아니라면, 120px 너비를 유지하지 못하고, 좁은 div 에 맞춰 너비가 줄어든다. */
                flex-shrink : 0;
            }
        &lt;/style&gt;
        &lt;body&gt;
            &lt;div&gt;
                &lt;section&gt;120px 크기의 섹션 1&lt;/section&gt;
                &lt;section&gt;120px 크기의 섹션 2&lt;/section&gt;
                &lt;section&gt;120px 크기의 섹션 3&lt;/section&gt;
            &lt;/div&gt;
        &lt;/body&gt;
    '
&gt;
    &lt;p&gt;이 문구가 보인다면, iframe 이 막혀 CSS 예시를 볼 수 없습니다.
&lt;/iframe&gt;

&lt;p&gt;&lt;strong&gt;end&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이번에는 &lt;code&gt;flex-wrap : wrap&lt;/code&gt; 이 선언되어,&lt;/p&gt;
&lt;p&gt;상위 엘리먼트의 최대 크기인 &lt;code&gt;300px&lt;/code&gt; 을 오버플로 하지 않고,&lt;/p&gt;
&lt;p&gt;다음 줄로 줄넘김이 된 것을 볼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이번 &lt;strong&gt;flex-wrap&lt;/strong&gt; 예제에서 주의깊게 보아야 할 것은 무엇일까?&lt;/p&gt;
&lt;p&gt;나는 오히려 &amp;quot;Overflow 를 유발&amp;quot; 하기 위해 하위 자식인 &lt;code&gt;section&lt;/code&gt;에,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;flex-shrink : 0&lt;/code&gt; 을 선언하여, 고정된 width 120px 를 유지할 수 있게 만들었다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;flex&lt;/code&gt; 선언시 기본적으로 &lt;code&gt;flex-shrink : 1;&lt;/code&gt;  의 값을 가진다.&lt;/p&gt;
&lt;p&gt;따라서, 하위 값은 고유의 너비가 있음에도 불구하고,&lt;/p&gt;
&lt;p&gt;오버플로를 막기 위해 고유의 너비보다 더 작은 너비로 세팅된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;즉,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;flex-shrink : 1&lt;/code&gt; : 상위 요소에 따라 너비가 줄어들 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;flex-shrink : 0&lt;/code&gt; : 상위 요소의 크기가 어떻든 자신의 크기를 유지한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;h2&gt;flex 요소의 내부 공간 제어하기&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;display : flex&lt;/code&gt; 선언 시, 직속 항목들은 Flex 와 관련된 일련의 속성을 선언 할 수 있는데,&lt;/p&gt;
&lt;p&gt;이는 flex 에 의해 형성되는 공통 디자인에서 하위 요소가 가질 수 있는 개별 속성을 지정하는 것이다.&lt;/p&gt;
&lt;p&gt;바로 직전의 예시에서, 나는 Overflow 를 유발하기 위해,&lt;/p&gt;
&lt;p&gt;하위 항목인 &lt;code&gt;section&lt;/code&gt; 태그에 &lt;code&gt;flex-shrink : 0&lt;/code&gt; 을 선언했다.&lt;/p&gt;
&lt;p&gt;이 속성이 적용될 수 있는 이유는, 바로 위의 요소인 &lt;code&gt;div&lt;/code&gt; 가 &lt;code&gt;display : flex&lt;/code&gt; 선언했기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;div&lt;/code&gt; 태그에서 &lt;code&gt;display : flex&lt;/code&gt; 가 선언되었을 때,&lt;/p&gt;
&lt;p&gt;직속 항목들의 Flex 관련 요소 커스텀을 위해, &lt;code&gt;section&lt;/code&gt; 은 이 3 가지 항목을 조정 할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Default Value&lt;/strong&gt; :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;flex-grow : 0&lt;/code&gt; --&amp;gt; 상위 컨텐츠에 따라 해당 컨텐츠를 늘릴 수 있는가?&lt;/li&gt;
&lt;li&gt;&lt;code&gt;flex-shrink : 1&lt;/code&gt; --&amp;gt; 상위 컨텐츠에 따라 해당 컨텐츠를 줄일 수 있는가?&lt;/li&gt;
&lt;li&gt;&lt;code&gt;flex-basis : auto&lt;/code&gt; --&amp;gt; 컨텐츠의 비율의 중심이 될 요소는 어느정도인가?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;즉, &lt;code&gt;flex-grow&lt;/code&gt;, &lt;code&gt;flex-shrink&lt;/code&gt;, &lt;code&gt;flex-basis&lt;/code&gt; 를 조정할 수 있다.&lt;/p&gt;
&lt;p&gt;여기서 &lt;code&gt;flex-grow&lt;/code&gt;, &lt;code&gt;flex-shrink&lt;/code&gt; 는 서로 반대의 개념이지만, 동일한 단위를 사용하며,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;flex-basis&lt;/code&gt; 는 위의 2 개의 속성에서 &amp;quot;기준이 되는&amp;quot; 단위를 말한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;%&lt;/code&gt;, &lt;code&gt;px&lt;/code&gt;, &lt;code&gt;em&lt;/code&gt;, &lt;code&gt;rem&lt;/code&gt;, ... 등등이 사용되며,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;auto&lt;/code&gt; 로 표시 할 경우, 컨텐츠의 크기 자체를 기준으로 사용한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;flex-grow 란 무엇인가?&lt;/h3&gt;
&lt;p&gt;이는 &lt;code&gt;flex&lt;/code&gt; 가 선언된 엘리먼트의 직속 하위 요소, flex-item 에 사용할 수 있는 속성이다.&lt;/p&gt;
&lt;p&gt;-이 속성의 값이 &lt;code&gt;0&lt;/code&gt; 인 경우-&lt;/p&gt;
&lt;p&gt;이 flex-item 은 상위 컨텐츠의 크기가 변동해도 &amp;quot;증가 하지 않는다&amp;quot;.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;-이 속성의 값이 &lt;code&gt;1&lt;/code&gt; 인 경우-&lt;/p&gt;
&lt;p&gt;이 flex-item 은 상위 컨텐츠의 크기가 변동 할 시, &amp;quot;증가 할 수 있다.&amp;quot;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;-이 속성의 값이 &lt;strong&gt;1 보다 큰 경우&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이 상황은 &lt;code&gt;flex-basis : auto&lt;/code&gt; 라고 가정한다.&lt;/p&gt;
&lt;p&gt;이 경우, &lt;code&gt;flex&lt;/code&gt; 선언된 블록 내부에 직속 자식으로 여러개가 있는 상황에 주로 사용되는데,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1 번째 : &lt;code&gt;flex-grow : 1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;2 번째 : &lt;code&gt;flex-grow : 2&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;3 번째 : &lt;code&gt;flex-grow : 3&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;으로 선언되었을 경우,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;flex&lt;/code&gt; 선언된 블록의 컨텐츠 크기에 따라서,&lt;/p&gt;
&lt;p&gt;&amp;quot;1 : 2 : 3&amp;quot;&lt;/p&gt;
&lt;p&gt;크기로 나뉘어진 영역을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;만약에 모든 엘리먼트에서 &lt;code&gt;flex-grow : 1&lt;/code&gt; 이 선언되었을 경우,&lt;/p&gt;
&lt;p&gt;모든 엘리먼트는 &lt;code&gt;flex&lt;/code&gt; 선언된 영역의 크기를 정확히 동일하게 나누어 가진다.&lt;/p&gt;
&lt;p&gt;그러나, 여러 엘리먼트 중 &lt;code&gt;flex-grow : 2&lt;/code&gt; 인 요소가 존재한다면,&lt;/p&gt;
&lt;p&gt;해당 요소는 &lt;code&gt;flex&lt;/code&gt; 영역 중 남는 영역이 존재한다면,&lt;/p&gt;
&lt;p&gt;그 중 (&lt;code&gt;2 / 모든 flex-grow 합)&lt;/code&gt; + &lt;code&gt;flex-basis&lt;/code&gt; 가 최종 영역이 된다.&lt;/p&gt;
&lt;p&gt;어떻게 보면, &lt;code&gt;0&lt;/code&gt; 은 경쟁에 참여하지 않음으로 볼 수 있고,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;1&lt;/code&gt; 이상은 남는 영역을 가져가는 경쟁에 참여하는 엘리먼트라고 볼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;즉, &lt;code&gt;flex-grow&lt;/code&gt; 를 요약하자면,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;0&lt;/code&gt; : 이 컨텐츠는 상위 요소의 크기에 관계없이 이 크기를 유지 할 것이다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;1&lt;/code&gt; : 이 컨텐츠는 상위 요소의 크기에 맞게 커질 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;1 보다 클 경우&lt;/code&gt; : 주로 여러개의 엘리먼트가 있을 경우 사용되며, 비율로 사용된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;0&lt;/code&gt;, &lt;code&gt;1&lt;/code&gt; 의 값 또한 여러개의 엘리먼트가 존재하는 경우에서 사용될 수 있으며,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;flex-basis&lt;/code&gt; 와 조합되어 더 다양한 상황을 연출할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;flex-grow 예시&lt;/h3&gt;
&lt;p&gt;이전에 HTML 모든 태그 다루기 Series 에서 모든 것을 다루었다고 생각했는데,&lt;/p&gt;
&lt;p&gt;내용이 장편으로 6 편까지 다루는 과정에서 예시 보조로 사용할 수 있는 HTML 을 많이 까먹었다는 생각이 든다.&lt;/p&gt;
&lt;p&gt;이번에는 직간접적으로 체험할 수 있는 인터랙션 &lt;code&gt;input&lt;/code&gt; 을 넣을 것이다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;article {
    display : flex;
    width : 200px;
}

article div {
    flex-grow : 1;
    flex-shrink : 0;
    flex-basis : 50px;
    border : 2px solid black;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body&amp;gt;
    초기 이 영역의 총 크기는 &amp;lt;span&amp;gt;200px&amp;lt;/span&amp;gt; 입니다.
    &amp;lt;br/&amp;gt;
    &amp;lt;input type=&amp;quot;range&amp;quot; id=&amp;quot;width-range&amp;quot; min=&amp;quot;100&amp;quot; max=&amp;quot;400&amp;quot; value=&amp;quot;200&amp;quot; step=&amp;quot;50&amp;quot;/&amp;gt;&amp;lt;br/&amp;gt;
    현재 이 영역의 총 크기는 &amp;lt;span id=&amp;quot;width-num&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;px 입니다.
    &amp;lt;article id=&amp;quot;article-dom&amp;quot;&amp;gt;
        &amp;lt;div style=&amp;quot;background : dodgerblue;&amp;quot;&amp;gt;
            a
        &amp;lt;/div&amp;gt;
        &amp;lt;div style=&amp;quot;background : gray;&amp;quot;&amp;gt;
            b
        &amp;lt;/div&amp;gt;
        &amp;lt;div style=&amp;quot;background : green;&amp;quot;&amp;gt;
            c
        &amp;lt;/div&amp;gt;
        &amp;lt;div style=&amp;quot;background : lightblue;&amp;quot;&amp;gt;
            d
        &amp;lt;/div&amp;gt;
    &amp;lt;/article&amp;gt;
    &amp;lt;script&amp;gt;
    const lever = document.getElementById(&amp;quot;width-range&amp;quot;);
    const widthText = document.getElementById(&amp;quot;width-num&amp;quot;);
    const articleWidth = document.getElementById(&amp;quot;article-dom&amp;quot;);
    widthText.textContent = lever.value;
    lever.addEventListener(&amp;quot;input&amp;quot;, function() {
      widthText.textContent = lever.value;
      articleWidth.style.width = lever.value + &amp;quot;px&amp;quot;;
    })
    &amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Preview&lt;/strong&gt; :&lt;/p&gt;
&lt;iframe
    width=&quot;70%&quot;
    max-height=&quot;20rem&quot;
    srcdoc='
        &lt;style&gt;
            body {
                background : white;
            }
            article {
                display : flex;
                width : 200px;
            }
            article div {
                flex-grow : 1;
                flex-shrink : 0;
                flex-basis : 50px;
                border : 2px solid black;
            }
        &lt;/style&gt;
        &lt;body&gt;
            초기 이 영역의 총 크기는 &lt;span&gt;200px&lt;/span&gt; 입니다.
            &lt;br/&gt;
            &lt;input type=&quot;range&quot; id=&quot;width-range&quot; min=&quot;100&quot; max=&quot;400&quot; value=&quot;200&quot; step=&quot;50&quot;/&gt;&lt;br/&gt;
            현재 이 영역의 총 크기는 &lt;span id=&quot;width-num&quot;&gt;&lt;/span&gt;px 입니다.
            &lt;article id=&quot;article-dom&quot;&gt;
                &lt;div style=&quot;background : dodgerblue;&quot;&gt;
                    a
                &lt;/div&gt;
                &lt;div style=&quot;background : gray;&quot;&gt;
                    b
                &lt;/div&gt;
                &lt;div style=&quot;background : green;&quot;&gt;
                    c
                &lt;/div&gt;
                &lt;div style=&quot;background : lightblue;&quot;&gt;
                    d
                &lt;/div&gt;
            &lt;/article&gt;
            &lt;script&gt;
            const lever = document.getElementById(&quot;width-range&quot;);
            const widthText = document.getElementById(&quot;width-num&quot;);
            const articleWidth = document.getElementById(&quot;article-dom&quot;);
            widthText.textContent = lever.value;
            lever.addEventListener(&quot;input&quot;, function() {
              widthText.textContent = lever.value;
              articleWidth.style.width = lever.value + &quot;px&quot;;
            })
            &lt;/script&gt;
        &lt;/body&gt;
    '
&gt;
    &lt;p&gt;이 문구가 보인다면, iframe 이 막혀 CSS 예시를 볼 수 없습니다.
&lt;/iframe&gt;

&lt;p&gt;&lt;strong&gt;end&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;위의 예시에서 &amp;quot;의도한 바&amp;quot; 는,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;flow-grow : 1&lt;/code&gt; 선언과, &lt;code&gt;flow-shrink : 0&lt;/code&gt; 선언에 의해,&lt;/p&gt;
&lt;p&gt;컨텐츠가 본래보다 늘어났을 때는 대응하나,&lt;/p&gt;
&lt;p&gt;컨텐츠가 자신의 실제 크기보다 줄어들어야 할 상황에는 &amp;quot;줄어들지 않는&amp;quot; 상황을 연출했다.&lt;/p&gt;
&lt;p&gt;여기서 레버에 해당하는 HTML 태그를 찾느라 약간 헤메고 있었는데,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;input&lt;/code&gt; 태그의 &lt;code&gt;type=&amp;quot;range&amp;quot;&lt;/code&gt; 로 설정 할 수 있었다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;flex-shrink 란 무엇인가?&lt;/h3&gt;
&lt;p&gt;이 또한 &lt;code&gt;flex&lt;/code&gt; 가 선언된 엘리먼트의 직속 하위 요소, flex-item 에 사용 할 수 있는 속성이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;shrink&lt;/code&gt; 라는 용어는, &amp;quot;수축하다&amp;quot; 라는 의미를 담고 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;-이 속성의 값이 &lt;code&gt;0&lt;/code&gt;인 경우-&lt;/p&gt;
&lt;p&gt;해당 flex-item 은 상위 컨텐츠의 크기가 변동해도 &amp;quot;감소하지 않는다.&amp;quot;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;-이 속성의 값이 &lt;code&gt;1&lt;/code&gt;인 경우-&lt;/p&gt;
&lt;p&gt;해당 flex-item 은 상위 컨텐츠의 크기가 변동 할 시, &amp;quot;감소 할 수 있다.&amp;quot;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;-이 속성의 값이 &lt;code&gt;1 이상&lt;/code&gt; 인 경우-&lt;/p&gt;
&lt;p&gt;이전의 예시에서 &lt;code&gt;flex-grow&lt;/code&gt; 가 늘어 날 수록, 크기가 더 커질 수 있는 반면에,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;flex-shrink&lt;/code&gt; 는 늘어 날 수록, 크기가 줄어 들 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;article {
    display : flex;
    padding : 1rem;
    background : gray;
    width : 200px;
}

article div {
    flex-grow : 0;
    flex-shrink : 1;
    flex-basis : 50px;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body&amp;gt;
    처음 이 영역의 총 크기는 200px 입니다.
    &amp;lt;br/&amp;gt;
    &amp;lt;input type=&amp;quot;range&amp;quot; id=&amp;quot;width-lever&amp;quot; min=&amp;quot;100&amp;quot; max=&amp;quot;300&amp;quot; value=&amp;quot;200&amp;quot; step=&amp;quot;50&amp;quot;/&amp;gt;
    &amp;lt;br/&amp;gt;
    현재 이 영역의 총 크기는 &amp;lt;span id=&amp;quot;width-text&amp;quot;&amp;gt;&amp;lt;/span&amp;gt; px 입니다.
    &amp;lt;br/&amp;gt;
    &amp;lt;article id=&amp;quot;article-dom&amp;quot;&amp;gt;
        &amp;lt;div style=&amp;quot;background : dodgerblue;&amp;quot;&amp;gt;
            a
        &amp;lt;/div&amp;gt;
        &amp;lt;div style=&amp;quot;background : white;&amp;quot;&amp;gt;
            b
        &amp;lt;/div&amp;gt;
        &amp;lt;div style=&amp;quot;background : green;&amp;quot;&amp;gt;
            c
        &amp;lt;/div&amp;gt;
        &amp;lt;div style=&amp;quot;background : lightblue;&amp;quot;&amp;gt;
            d
        &amp;lt;/div&amp;gt;
    &amp;lt;/article&amp;gt;
    &amp;lt;script&amp;gt;
    const lever = document.getElementById(&amp;quot;width-lever&amp;quot;);
    const text = document.getElementById(&amp;quot;width-text&amp;quot;);
    text.textContent = lever.value;
    const article = document.getElementById(&amp;quot;article-dom&amp;quot;);
    lever.addEventListener(&amp;quot;input&amp;quot;, function () {
      text.textContent = lever.value;
      article.style.width = lever.value + &amp;quot;px&amp;quot;;
    })
    &amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Preview&lt;/strong&gt; :&lt;/p&gt;
&lt;iframe
    width=&quot;70%&quot;
    max-height=&quot;20rem&quot;
    srcdoc='
        &lt;style&gt;
            body {
                background : white;
            }
            article {
                display : flex;
                padding : 1rem;
                background : gray;
                width : 200px;
            }
            article div {
                flex-grow : 0;
                flex-shrink : 1;
                flex-basis : 50px;
            }
        &lt;/style&gt;
        &lt;body&gt;
            처음 이 영역의 총 크기는 200px 입니다.
            &lt;br/&gt;
            &lt;input type=&quot;range&quot; id=&quot;width-lever&quot; min=&quot;100&quot; max=&quot;300&quot; value=&quot;200&quot; step=&quot;50&quot;/&gt;
            &lt;br/&gt;
            현재 이 영역의 총 크기는 &lt;span id=&quot;width-text&quot;&gt;&lt;/span&gt; px 입니다.
            &lt;br/&gt;
            &lt;article id=&quot;article-dom&quot;&gt;
                &lt;div style=&quot;background : dodgerblue;&quot;&gt;
                    a
                &lt;/div&gt;
                &lt;div style=&quot;background : white;&quot;&gt;
                    b
                &lt;/div&gt;
                &lt;div style=&quot;background : green;&quot;&gt;
                    c
                &lt;/div&gt;
                &lt;div style=&quot;background : lightblue;&quot;&gt;
                    d
                &lt;/div&gt;
            &lt;/article&gt;
            &lt;script&gt;
            const lever = document.getElementById(&quot;width-lever&quot;);
            const text = document.getElementById(&quot;width-text&quot;);
            text.textContent = lever.value;
            const article = document.getElementById(&quot;article-dom&quot;);
            lever.addEventListener(&quot;input&quot;, function () {
              text.textContent = lever.value;
              article.style.width = lever.value + &quot;px&quot;;
            })
            &lt;/script&gt;
        &lt;/body&gt;
    '
&gt;
    &lt;p&gt;이 문구가 보인다면, iframe 이 막혀 CSS 예시를 볼 수 없습니다.
&lt;/iframe&gt;

&lt;p&gt;&lt;strong&gt;end&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;위의 레버를 한번 앞뒤로 움직여 보면 알겠지만,&lt;/p&gt;
&lt;p&gt;Flex 된 컨텐츠가 커진다고 하여, 하위 요소는 반응하지 않고, 자신의 스타일(너비) 를 유지한다.&lt;/p&gt;
&lt;p&gt;그러나, Flex 된 컨텐츠가 자신들을 온전히 담을 수 없을 때,&lt;/p&gt;
&lt;p&gt;각자 수축하여 컨텐츠에 들어가도록 맞추는 것을 볼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;위의 3 가지 속성을 단축하여 한 번에 선언하기 - flex&lt;/h3&gt;
&lt;p&gt;지금 설명하려는 것 또한, &lt;code&gt;display : flex&lt;/code&gt; 가 선언된 컨테이너 내부의,&lt;/p&gt;
&lt;p&gt;flex-item 에 해당하는 직속 DOM 에서 선언할 수 있는 속성이다.&lt;/p&gt;
&lt;p&gt;즉, &lt;code&gt;flex : 1&lt;/code&gt;, &lt;code&gt;flex : 1 1 auto&lt;/code&gt; 식으로 사용한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그런데, 단일 값이 아니라, 다중 값을 연결하여 사용하고 있는 것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;이는 각 순서가 가지는 일련의 값이 매칭되기 때문이다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;flex-grow&lt;/code&gt; --&amp;gt; 컨테이너의 크기가 남을 때 더 늘릴 것인가&lt;/li&gt;
&lt;li&gt;&lt;code&gt;flex-shrink&lt;/code&gt; --&amp;gt; 컨테이너의 크기가 작을 때 자신도 따라 줄일 것인가&lt;/li&gt;
&lt;li&gt;&lt;code&gt;flex-basis&lt;/code&gt; --&amp;gt; flex 에 의해 handle 될 때, 이 DOM 의 기준 너비를 정한다. (width)&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;p&gt;따라서, 만약에 flex-item 에서 &lt;code&gt;flex : 1 0 50px&lt;/code&gt; 로 선언했다면,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;flex-grow&lt;/code&gt; : &lt;code&gt;1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;flex-shrink&lt;/code&gt; : &lt;code&gt;0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;flex-basis&lt;/code&gt; : &lt;code&gt;50px&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;형식을 가진 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그런데, 보통 &lt;code&gt;flex-basis&lt;/code&gt; 의 기본 값은 보통 &lt;code&gt;auto&lt;/code&gt; 로 설정된다.&lt;/p&gt;
&lt;p&gt;그렇다면, &lt;code&gt;auto&lt;/code&gt; 의 너비, &lt;code&gt;flex-basis&lt;/code&gt; 는 얼마일까?&lt;/p&gt;
&lt;p&gt;정답은 바로 &lt;code&gt;0&lt;/code&gt; 이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;만약에 &lt;code&gt;auto&lt;/code&gt; == &lt;code&gt;0&lt;/code&gt; 으로 설정하게 된다면, &lt;code&gt;flex&lt;/code&gt; 와 관련된 속성으로,&lt;/p&gt;
&lt;p&gt;컨테이너 영역을 얼마나 차지 할 것인지, 마치 비율로 나눌 수 있다는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;예를 들어서, &lt;code&gt;1 : 3 : 2&lt;/code&gt; 의 비율을 가진 컨테이너를 표시하고 싶다고 가정하자.&lt;/p&gt;
&lt;p&gt;그렇다면, &lt;code&gt;flex&lt;/code&gt; 라는 아주 간단한 단축 기능의 속성을 통해 바로 표현 할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;article {
    display : flex;
    background : lightgray;
    border : 2px solid black;
    width : 180px;
}

article div:nth-of-type(1) {
    flex : 1;
    background : dodgerblue;
}
article div:nth-of-type(2) {
    flex : 3;
    background : pink;
}
article div:nth-of-type(3) {
    flex : 2;
    background : gray;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body&amp;gt;
    &amp;lt;input type=&amp;quot;range&amp;quot; id=&amp;quot;width-lever&amp;quot; min=&amp;quot;100&amp;quot; max=&amp;quot;500&amp;quot; value=&amp;quot;180&amp;quot; step=&amp;quot;20&amp;quot;&amp;gt; &amp;lt;br/&amp;gt;
    현재 컨테이너의 총 길이는, &amp;lt;span id=&amp;quot;width-text&amp;quot;&amp;gt;&amp;lt;/span&amp;gt; px 입니다.
    &amp;lt;article id=&amp;quot;article-dom&amp;quot;&amp;gt;
        &amp;lt;div&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/article&amp;gt;
    &amp;lt;script&amp;gt;
    function changeTexts() {
      let blocks = document.querySelectorAll(&amp;quot;article div&amp;quot;);
      for(let i = 0; i &amp;lt; blocks.length; i++) {
        const tempStyleObj = window.getComputedStyle(blocks[i]);
        blocks[i].innerText = tempStyleObj.width;
      }
    }
    changeTexts();
    const lever = document.getElementById(&amp;quot;width-lever&amp;quot;);
    const text = document.getElementById(&amp;quot;width-text&amp;quot;);
    text.textContent = lever.value;
    const article = document.getElementById(&amp;quot;article-dom&amp;quot;);
    lever.addEventListener(&amp;quot;input&amp;quot;, function () {
      text.textContent = lever.value;
      article.style.width = lever.value + &amp;quot;px&amp;quot;;
      changeTexts();
    })
    &amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Preview&lt;/strong&gt;&lt;/p&gt;
&lt;iframe
    width=&quot;70%&quot;
    max-height=&quot;20rem&quot;
    srcdoc='
        &lt;style&gt;
            body {
                background : white;
            }
            article {
                display : flex;
                background : lightgray;
                border : 2px solid black;
                width : 180px;
            }
            article div:nth-of-type(1) {
                flex : 1;
                background : dodgerblue;
            }
            article div:nth-of-type(2) {
                flex : 3;
                background : pink;
            }
            article div:nth-of-type(3) {
                flex : 2;
                background : gray;
            }
        &lt;/style&gt;
        &lt;body&gt;
            &lt;input type=&quot;range&quot; id=&quot;width-lever&quot; min=&quot;100&quot; max=&quot;500&quot; value=&quot;180&quot; step=&quot;20&quot;&gt; &lt;br/&gt;
            현재 컨테이너의 총 길이는, &lt;span id=&quot;width-text&quot;&gt;&lt;/span&gt; px 입니다.
            &lt;article id=&quot;article-dom&quot;&gt;
                &lt;div&gt;
                &lt;/div&gt;
                &lt;div&gt;
                &lt;/div&gt;
                &lt;div&gt;
                &lt;/div&gt;
            &lt;/article&gt;
            &lt;script&gt;
            function changeTexts() {
              let blocks = document.querySelectorAll(&quot;article div&quot;);
              for(let i = 0; i &lt; blocks.length; i++) {
                const tempStyleObj = window.getComputedStyle(blocks[i]);
                blocks[i].innerText = tempStyleObj.width;
              }
            }
            changeTexts();
            const lever = document.getElementById(&quot;width-lever&quot;);
            const text = document.getElementById(&quot;width-text&quot;);
            text.textContent = lever.value;
            const article = document.getElementById(&quot;article-dom&quot;);
            lever.addEventListener(&quot;input&quot;, function () {
              text.textContent = lever.value;
              article.style.width = lever.value + &quot;px&quot;;
              changeTexts();
            })
            &lt;/script&gt;
        &lt;/body&gt;
    '
&gt;
    &lt;p&gt;이 문구가 보인다면, iframe 이 막혀 CSS 예시를 볼 수 없습니다.
&lt;/iframe&gt;

&lt;p&gt;&lt;strong&gt;end&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;위의 예제와 같이, flex-item 에 해당하는 요소가 선언할 수 있는&lt;/p&gt;
&lt;p&gt;여러 개의 속성 중, 단축어의 기능을 하는 &lt;code&gt;flex&lt;/code&gt; 를 이용하여, 비율로 나누는 예제를 선보였다.&lt;/p&gt;
&lt;p&gt;즉, &lt;code&gt;flex&lt;/code&gt; 는 &lt;code&gt;flex-grow&lt;/code&gt;, &lt;code&gt;flex-shrink&lt;/code&gt;, &lt;code&gt;flex-basis&lt;/code&gt; 의 단축을 도와준다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Flexbox 의 꽃, 정렬에 대해서 알아보자.&lt;/h2&gt;
&lt;p&gt;만약에, CSS 스타일링을 해 본 사람이 있다면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;justify-content&lt;/code&gt;, &lt;code&gt;align-items&lt;/code&gt; 라는 속성에 대해서 들어 보거나, 작성 해 본적이 있을 것이다.&lt;/p&gt;
&lt;p&gt;이게 정확히 무엇을 의미하는지 알고 있는가?&lt;/p&gt;
&lt;p&gt;&amp;quot;사실 나도 모른다.&amp;quot;&lt;/p&gt;
&lt;p&gt;나는 이러한 속성을 작성 할 때, &amp;quot;중앙 정렬이 필요해&amp;quot; 생각하며,&lt;/p&gt;
&lt;p&gt;단순히 두 속성의 값을 변동 해 가며 맞춰본 기억이 있다.&lt;/p&gt;
&lt;p&gt;즉, 이에 대해 이해하고 있지도 않으면서, 비효율적으로 조합을 맞춰본 꼴이 된 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;드디어, Flexbox 의 정렬에 대한 정확한 이해와 사용을 시작 할 때가 된 것 같다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;그 전에 알아 두어야 할 &amp;quot;주축&amp;quot;, 과 &amp;quot;교차 축&amp;quot;&lt;/h3&gt;
&lt;p&gt;원활한 글 작성을 위해 먼저 참조자료들을 읽고 작성하는 편인데,&lt;/p&gt;
&lt;p&gt;Flexbox, 즉, &lt;code&gt;display : flex&lt;/code&gt; 라고 선언된 컨테이너는,&lt;/p&gt;
&lt;p&gt;4 가지의 &amp;quot;방향&amp;quot; 즉, Direction 을 가질 수 있다.&lt;/p&gt;
&lt;p&gt;이 4 가지의 방향에 따라서, 우리가 선언할 &amp;quot;정렬&amp;quot; 에 관한 성질들은 전부 달라 질 수 있다.&lt;/p&gt;
&lt;p&gt;정렬의 선언은 동일해도, 이 &amp;quot;주축&amp;quot;, &amp;quot;교차 축&amp;quot; 을 정하는 &lt;code&gt;flex-direction&lt;/code&gt; 에 따라&lt;/p&gt;
&lt;p&gt;스타일링은 현저히 달라질 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그 이유가, &lt;code&gt;flex-direction&lt;/code&gt; 은 주축의 방향과 교차 축의 방향을 정한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;justify-xxx&lt;/code&gt;, &lt;code&gt;align-xxx&lt;/code&gt; 은 각각 주축, 교차축의 정렬을 도맡는다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;주 축과 교차 축, 그리고 그 방향에 대해서 이해해 보자.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;flex-direction&lt;/code&gt; 은 기본적으로 &lt;code&gt;row&lt;/code&gt; 이다.&lt;/p&gt;
&lt;p&gt;즉, 주 축은 Left to Right, (수평) 이며, (Main Axis)&lt;/p&gt;
&lt;p&gt;교차 축은 Up to Down, (수직) 이다. (Cross Axis)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;flex-direction&lt;/code&gt; 이 &lt;code&gt;column&lt;/code&gt; 일 경우,&lt;/p&gt;
&lt;p&gt;주 축은 UP to Down, (수직) 이며, (Main Axis)&lt;/p&gt;
&lt;p&gt;교차 축은 Left to Right, (수평) 이다. (Cross Axis)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;여기서 확실히 해야 할 점이 몇 가지 존재한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;flex-direction&lt;/code&gt; 이 &lt;code&gt;row&lt;/code&gt;, 혹은 &lt;code&gt;row-reverse&lt;/code&gt; 일 경우,&lt;/p&gt;
&lt;p&gt;주 축은 여전히 수평이지만, 배치 방향은 반대이다.&lt;/p&gt;
&lt;p&gt;그러나, 교차 축인 수직에서는 배치 방향이 변하지 않는다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;flex-direction&lt;/code&gt; 이 &lt;code&gt;column&lt;/code&gt;, 혹은 &lt;code&gt;column-reverse&lt;/code&gt; 일 경우,&lt;/p&gt;
&lt;p&gt;주 축은 수직으로 동일하지만, 배치 방향은 서로 반대이다.&lt;/p&gt;
&lt;p&gt;그러나, 교차 축은 수평에서는 배치 방향이 변하지 않는다.&lt;/p&gt;
&lt;p&gt;즉, &lt;code&gt;flex-direction&lt;/code&gt; 의 값에 따라 여러 상황이 변할 수 있지만,&lt;/p&gt;
&lt;p&gt;기본적인 &amp;quot;교차 축&amp;quot; 은 변하지 않는다는 것이 중요하다.&lt;/p&gt;
&lt;p&gt;교차 축은 수평, 수직이 될 수 있으며, 항상 Left To Right, 혹은 Up to Down 이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이에 대해 각종 옵션을 선택 할 수 있는 예제를 만들어 보기로 결정했다.&lt;/p&gt;
&lt;p&gt;단, flex-direction 의 방향을 바꿔가며, 주축과 교차축을 이해하여&lt;/p&gt;
&lt;p&gt;여러 엘리먼트가 어떻게 작성되는지만 보도록 하자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;section {
    display : flex;
    flex-direction : row;

    width : 10rem;
    height : 10rem;

    border : 2px solid lightgray;
}

section div {
    text-align : center;
    width : 2rem;
    height : 2rem;
    padding : 0.25rem;
    &amp;amp;:nth-of-type(1) {
        background : dodgerblue;
    }
    &amp;amp;:nth-of-type(2) {
        background : lightgray;
    }
    &amp;amp;:nth-of-type(3) {
        background : green;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body&amp;gt;
    &amp;lt;select id=&amp;quot;direction-value&amp;quot;&amp;gt;
        &amp;lt;option value=&amp;quot;row&amp;quot;&amp;gt;row&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;row-reverse&amp;quot;&amp;gt;row-reverse&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;column&amp;quot;&amp;gt;column&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;column-reverse&amp;quot;&amp;gt;column-reverse&amp;lt;/option&amp;gt;
    &amp;lt;/select&amp;gt;
    &amp;lt;br/&amp;gt;
    현재 방향은 &amp;lt;span id=&amp;quot;direction-text&amp;quot;&amp;gt;row&amp;lt;/span&amp;gt; 입니다.
    &amp;lt;section id=&amp;quot;container&amp;quot;&amp;gt;
        &amp;lt;div&amp;gt;
            1
        &amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
            2
        &amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
            3
        &amp;lt;/div&amp;gt;
    &amp;lt;/section&amp;gt;
    &amp;lt;script&amp;gt;
    const select = document.getElementById(&amp;quot;direction-value&amp;quot;);
    const text = document.getElementById(&amp;quot;direction-text&amp;quot;);
    const container = document.getElementById(&amp;quot;container&amp;quot;);

    select.addEventListener(&amp;quot;change&amp;quot;, function () {
      container.style[&amp;quot;flex-direction&amp;quot;] = select.value;

      text.textContent = select.value;
    })
    &amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Preview&lt;/strong&gt; :&lt;/p&gt;
&lt;iframe
    style='width : 70%; height : 20rem;'
    srcdoc='
        &lt;style&gt;
            body {
                background : white;
            }
            section {
                display : flex;
                flex-direction : row;
                width : 10rem;
                height : 10rem;
                border : 2px solid lightgray;
            }
            section div {
                text-align : center;
                width : 2rem;
                height : 2rem;
                padding : 0.25rem;
                &amp;:nth-of-type(1) {
                    background : dodgerblue;
                }
                &amp;:nth-of-type(2) {
                    background : lightgray;
                }
                &amp;:nth-of-type(3) {
                    background : green;
                }
            }
        &lt;/style&gt;
        &lt;body&gt;
            &lt;select id=&quot;direction-value&quot;&gt;
                &lt;option value=&quot;row&quot;&gt;row&lt;/option&gt;
                &lt;option value=&quot;row-reverse&quot;&gt;row-reverse&lt;/option&gt;
                &lt;option value=&quot;column&quot;&gt;column&lt;/option&gt;
                &lt;option value=&quot;column-reverse&quot;&gt;column-reverse&lt;/option&gt;
            &lt;/select&gt;
            &lt;br/&gt;
            현재 방향은 &lt;span id=&quot;direction-text&quot;&gt;row&lt;/span&gt; 입니다.
            &lt;section id=&quot;container&quot;&gt;
                &lt;div&gt;
                    1
                &lt;/div&gt;
                &lt;div&gt;
                    2
                &lt;/div&gt;
                &lt;div&gt;
                    3
                &lt;/div&gt;
            &lt;/section&gt;
            &lt;script&gt;
            const select = document.getElementById(&quot;direction-value&quot;);
            const text = document.getElementById(&quot;direction-text&quot;);
            const container = document.getElementById(&quot;container&quot;);
            select.addEventListener(&quot;change&quot;, function () {
              container.style[&quot;flex-direction&quot;] = select.value;
              text.textContent = select.value;
            })
            &lt;/script&gt;
        &lt;/body&gt;
    '
&gt;
    &lt;p&gt;이 문구가 보인다면, iframe 이 막혀 CSS 예시를 볼 수 없습니다.
&lt;/iframe&gt;


&lt;p&gt;&lt;strong&gt;end&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;위의 예제를 보면, &lt;code&gt;flex-direction&lt;/code&gt; 의 변화로 인해,&lt;/p&gt;
&lt;p&gt;작성 방향이 달라지고 있다.&lt;/p&gt;
&lt;p&gt;그에 반해, 주축이 아닌, 결정된 교차 축의 방향은 변하지 않는 것을 볼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;justify 와 align 을 알아보자.&lt;/h3&gt;
&lt;p&gt;먼저 추상적으로 보자면,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;justify&lt;/code&gt; : 주 축을 관리&lt;/li&gt;
&lt;li&gt;&lt;code&gt;align&lt;/code&gt; : 교차 축을 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이러하다.&lt;/p&gt;
&lt;p&gt;위에서 언급했듯, &lt;code&gt;flex-direction&lt;/code&gt; 에 의해, &lt;code&gt;justify&lt;/code&gt;, &lt;code&gt;align&lt;/code&gt; 을 시각적으로 관리하는 것은 다르다.&lt;/p&gt;
&lt;p&gt;그러니까, 주 축의 시작점을 &lt;code&gt;flex-direction&lt;/code&gt;, &lt;code&gt;justify-..&lt;/code&gt; 로 금방 알아 낼 수 있다면,&lt;/p&gt;
&lt;p&gt;이 컨테이너가 엘리먼트를 &amp;quot;어떤 형식으로, 어떤 방향으로&amp;quot; 나열 할 것인지 알 수 있다는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;여기서 드디어 나의 또 다른 의문점을 해소 할 단계가 왔다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;의문점 리스트&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;justify-content&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;align-content&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;align-items&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;align-self&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 4 개이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;justify-content&lt;/code&gt; 는 &amp;quot;주 축&amp;quot;(Main Axis) 를 기점으로&lt;/p&gt;
&lt;p&gt;&amp;quot;앞, 중앙, 뒤 중 무엇부터 채울 것인가?&amp;quot; 를 의미하지만,&lt;/p&gt;
&lt;p&gt;상황에 따라 &lt;code&gt;align-items&lt;/code&gt;, 혹은 &lt;code&gt;align-content&lt;/code&gt; 를 덧붙여 사용했다.&lt;/p&gt;
&lt;p&gt;이는 도대체 무엇인가? 둘 다 컨텐츠를 의미하는 것 같은데, 하나는 아이템이고, 하나는 컨텐츠이다.&lt;/p&gt;
&lt;p&gt;위에서 제시한 4 가지의 의문점을 차차 소제목으로 소개하며 알아가는 시간을 가져보자.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;justify-content 를 알아보자&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;justify-content&lt;/code&gt; 는 &lt;code&gt;display : flex&lt;/code&gt; 와 같이 사용되며,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;flex-direction&lt;/code&gt; 에 따라 전혀 다른 시각적 효과를 줄 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이 속성은, 주 축인 Main Axis 방향의 공간에서, 내부 요소들을 어떻게 분배 할 것인지 결정한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;flex-direction : row;&lt;/code&gt; 가 기본 값이기 때문에,&lt;/p&gt;
&lt;p&gt;Default 에서는 수평선 영역에 어떻게 영역을 분배 할 것인지 결정한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;justify-content&lt;/code&gt; 는 밑과 같은 값으로 설정 할 수 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;flex-start&lt;/code&gt; : 시작부분에 배치&lt;/li&gt;
&lt;li&gt;&lt;code&gt;flex-end&lt;/code&gt; : 마지막 부분에 배치&lt;/li&gt;
&lt;li&gt;&lt;code&gt;center&lt;/code&gt; : 중앙에 모이도록 배치&lt;/li&gt;
&lt;li&gt;&lt;code&gt;space-between&lt;/code&gt; : 내부 요소들끼리 최대한 떨어지도록 배치&lt;/li&gt;
&lt;li&gt;&lt;code&gt;space-around&lt;/code&gt; : 컨테이너의 border 와, 내부 요소까지 합쳐 최대한 떨어지도록 배치&lt;/li&gt;
&lt;li&gt;&lt;code&gt;space-evenly&lt;/code&gt; : 규칙적으로 떨어지도록 배치&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;중요한 것은, 영역을 분배하는 것이기 때문에,&lt;/p&gt;
&lt;p&gt;내부 요소보다 더 많은 영역을 가지고 있어야 작동 여부를 확실히 볼 수 있다.&lt;/p&gt;
&lt;p&gt;기본 상태인 &lt;code&gt;flex-direction : row&lt;/code&gt; 의 예제를 살펴보자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.container {
    display : flex;
    flex-direction : row;
    width : 15rem;
    height : 15rem;
    background : lightgray;
}
.container div {
    width : 3rem;
    height : 3rem;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body&amp;gt;
    justify-content : &amp;lt;br/&amp;gt;
    &amp;lt;select id=&amp;quot;justify-value&amp;quot;&amp;gt;
        &amp;lt;option value=&amp;quot;flex-start&amp;quot;&amp;gt;flex-start&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;flex-end&amp;quot;&amp;gt;flex-end&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;center&amp;quot;&amp;gt;center&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;space-between&amp;quot;&amp;gt;space-between&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;space-around&amp;quot;&amp;gt;space-around&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;space-evenly&amp;quot;&amp;gt;space-evenly&amp;lt;/option&amp;gt;
    &amp;lt;/select&amp;gt;
    &amp;lt;br/&amp;gt;
    &amp;lt;section class=&amp;quot;container&amp;quot; id=&amp;quot;container&amp;quot;&amp;gt;
        &amp;lt;div style=&amp;quot;background : dodgerblue;&amp;quot;&amp;gt;
            1
        &amp;lt;/div&amp;gt;
        &amp;lt;div style=&amp;quot;background : green;&amp;quot;&amp;gt;
            2
        &amp;lt;/div&amp;gt;
        &amp;lt;div style=&amp;quot;background : pink;&amp;quot;&amp;gt;
            3
        &amp;lt;/div&amp;gt;
    &amp;lt;/section&amp;gt;
    &amp;lt;script&amp;gt;
    const justify = document.getElementById(&amp;quot;justify-value&amp;quot;);
    const container = document.getElementById(&amp;quot;container&amp;quot;);

    justify.addEventListener(&amp;quot;change&amp;quot;, function () {
      container.style[&amp;quot;justify-content&amp;quot;] = justify.value;
    });
    &amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Preview&lt;/strong&gt; :&lt;/p&gt;
&lt;iframe
    style='width : 70%; height : 20rem;'
    srcdoc='
        &lt;style&gt;
            body {
                background : white;
            }
            .container {
                display : flex;
                flex-direction : row;
                width : 15rem;
                height : 15rem;
                background : lightgray;
            }
            .container div {
                width : 3rem;
                height : 3rem;
            }
        &lt;/style&gt;
        &lt;body&gt;
            justify-content : &lt;br/&gt;
            &lt;select id=&quot;justify-value&quot;&gt;
                &lt;option value=&quot;flex-start&quot;&gt;flex-start&lt;/option&gt;
                &lt;option value=&quot;flex-end&quot;&gt;flex-end&lt;/option&gt;
                &lt;option value=&quot;center&quot;&gt;center&lt;/option&gt;
                &lt;option value=&quot;space-between&quot;&gt;space-between&lt;/option&gt;
                &lt;option value=&quot;space-around&quot;&gt;space-around&lt;/option&gt;
                &lt;option value=&quot;space-evenly&quot;&gt;space-evenly&lt;/option&gt;
            &lt;/select&gt;
            &lt;br/&gt;
            &lt;section class=&quot;container&quot; id=&quot;container&quot;&gt;
                &lt;div style=&quot;background : dodgerblue;&quot;&gt;
                    1
                &lt;/div&gt;
                &lt;div style=&quot;background : green;&quot;&gt;
                    2
                &lt;/div&gt;
                &lt;div style=&quot;background : pink;&quot;&gt;
                    3
                &lt;/div&gt;
            &lt;/section&gt;
            &lt;script&gt;
            const justify = document.getElementById(&quot;justify-value&quot;);
            const container = document.getElementById(&quot;container&quot;);
            justify.addEventListener(&quot;change&quot;, function () {
              container.style[&quot;justify-content&quot;] = justify.value;
            });
            &lt;/script&gt;
        &lt;/body&gt;
    '
&gt;
    &lt;p&gt;이 문구가 보인다면, iframe 이 막혀 CSS 예시를 볼 수 없습니다.
&lt;/iframe&gt;

&lt;p&gt;&lt;strong&gt;end&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;만약 flex-direction 이 column&lt;/strong&gt; 이라면,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.container {
    display : flex;
    flex-direction : column;
    width : 15rem;
    height : 15rem;
    background : lightgray;
}
.container div {
    width : 3rem;
    height : 3rem;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Preview&lt;/strong&gt; :&lt;/p&gt;
&lt;iframe
    style='width : 70%; height : 20rem;'
    srcdoc='
        &lt;style&gt;
            body {
                background : white;
            }
            .container {
                display : flex;
                flex-direction : column;
                width : 15rem;
                height : 15rem;
                background : lightgray;
            }
            .container div {
                width : 3rem;
                height : 3rem;
            }
        &lt;/style&gt;
        &lt;body&gt;
            justify-content : &lt;br/&gt;
            &lt;select id=&quot;justify-value&quot;&gt;
                &lt;option value=&quot;flex-start&quot;&gt;flex-start&lt;/option&gt;
                &lt;option value=&quot;flex-end&quot;&gt;flex-end&lt;/option&gt;
                &lt;option value=&quot;center&quot;&gt;center&lt;/option&gt;
                &lt;option value=&quot;space-between&quot;&gt;space-between&lt;/option&gt;
                &lt;option value=&quot;space-around&quot;&gt;space-around&lt;/option&gt;
                &lt;option value=&quot;space-evenly&quot;&gt;space-evenly&lt;/option&gt;
            &lt;/select&gt;
            &lt;br/&gt;
            &lt;section class=&quot;container&quot; id=&quot;container&quot;&gt;
                &lt;div style=&quot;background : dodgerblue;&quot;&gt;
                    1
                &lt;/div&gt;
                &lt;div style=&quot;background : green;&quot;&gt;
                    2
                &lt;/div&gt;
                &lt;div style=&quot;background : pink;&quot;&gt;
                    3
                &lt;/div&gt;
            &lt;/section&gt;
            &lt;script&gt;
            const justify = document.getElementById(&quot;justify-value&quot;);
            const container = document.getElementById(&quot;container&quot;);
            justify.addEventListener(&quot;change&quot;, function () {
              container.style[&quot;justify-content&quot;] = justify.value;
            });
            &lt;/script&gt;
        &lt;/body&gt;
    '
&gt;
    &lt;p&gt;이 문구가 보인다면, iframe 이 막혀 CSS 예시를 볼 수 없습니다.
&lt;/iframe&gt;

&lt;p&gt;&lt;strong&gt;end&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;위의 2 가지 예제를 이어서 보여줬는데,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;justify-content&lt;/code&gt; 에 올 수 있는 6 가지 값의 차이와,&lt;/p&gt;
&lt;p&gt;주 축의 경로를 변화시킬 수 있는 &lt;code&gt;flex-direction&lt;/code&gt; 이&lt;/p&gt;
&lt;p&gt;시각적으로 얼마나 큰 연관관계를 가졌는지 이해 할 수 있을 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;align-xxx 속성 3 개에 대해서 알아보자.&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;align&lt;/code&gt; 이 붙은 속성은 총 3개이다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;align-content&lt;/code&gt; - 컨테이너에서 선언&lt;/li&gt;
&lt;li&gt;&lt;code&gt;align-items&lt;/code&gt; - 컨테이너에서 선언&lt;/li&gt;
&lt;li&gt;&lt;code&gt;align-self&lt;/code&gt; - 컨테이너에 속한 flex-item 이 선언&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;p&gt;각 공식문서에서는 이 &lt;code&gt;align&lt;/code&gt; 에 대한 개별적이며 구체적인 설명이 없기 때문에,&lt;/p&gt;
&lt;p&gt;따로 Gemini-2.5 에 물어보아 그 답을 알 수 있었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;전반부에서 말했듯이, &lt;code&gt;display : flex&lt;/code&gt; 는 1 차원 나열 표시 형식을 의미하는데,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;flex-wrap : wrap&lt;/code&gt; 이 같이 선언 되어 있다면,&lt;/p&gt;
&lt;p&gt;flex-item 이 가득차기 전에, 이 flex-item 이 마치 글자처럼 &amp;quot;줄넘김&amp;quot; 된다.&lt;/p&gt;
&lt;p&gt;그렇지 않다면, flex-item 은 고유의 너비를 스스로 줄여 container 에 맞춘다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;여기서, &lt;code&gt;align-content&lt;/code&gt;, &lt;code&gt;align-items&lt;/code&gt; 의 차이가 드러난다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;align-content&lt;/code&gt; 는, 이 &amp;quot;줄넘김&amp;quot; 되었을 때를 가정하여,&lt;/p&gt;
&lt;p&gt;&amp;quot;각 row group&amp;quot; 이 어떻게 떨어질지를 결정한다. 즉, 줄 그룹에 대한 명령이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;align-items&lt;/code&gt; 는, 형성된 &amp;quot;각 줄&amp;quot; 에서 자신이 어디에 정렬 될 것인지,&lt;/p&gt;
&lt;p&gt;모든 하위 엘리먼트, flex-item 들에 명령하는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;align-self&lt;/code&gt; 는, flex-item 에서 선언하는 속성인데,&lt;/p&gt;
&lt;p&gt;자신은 컨테이너에서 선언한 &lt;code&gt;align-items&lt;/code&gt; 와 달리,&lt;/p&gt;
&lt;p&gt;자신은 스스로 정렬을 수행하겠다는 것이, &lt;code&gt;align-self&lt;/code&gt; 이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;2 가지 예제로 알아보는 align&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;align-content&lt;/code&gt;, &lt;code&gt;align-items&lt;/code&gt; 를 조합하는 예제,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;align-self&lt;/code&gt; 를 통해 개별 정렬을 수행하는 예제를 살펴보자.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;align-content&lt;/code&gt;, &lt;code&gt;align-items&lt;/code&gt; 를 조합하는 예제 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.container {
    display : flex;
    flex-direction : row;
    flex-wrap : wrap;
    width : 20rem;
    height : 20rem;
    background : lightgray;
    gap : 1rem;
}
.container div {
    width : 3.5rem;
    border : 1px solid green;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body&amp;gt;
    align-content : &amp;lt;select id=&amp;quot;align-content-val&amp;quot;&amp;gt;
        &amp;lt;option value=&amp;quot;stretch&amp;quot;&amp;gt;stretch&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;center&amp;quot;&amp;gt;center&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;flex-start&amp;quot;&amp;gt;flex-start&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;flex-end&amp;quot;&amp;gt;flex-end&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;space-between&amp;quot;&amp;gt;space-between&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;space-around&amp;quot;&amp;gt;space-around&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;space-evenly&amp;quot;&amp;gt;space-evenly&amp;lt;/option&amp;gt;
    &amp;lt;/select&amp;gt;
    &amp;lt;br/&amp;gt;
    align-items : &amp;lt;select id=&amp;quot;align-items-val&amp;quot;&amp;gt;
        &amp;lt;option value=&amp;quot;stretch&amp;quot;&amp;gt;stretch&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;baseline&amp;quot;&amp;gt;baseline&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;center&amp;quot;&amp;gt;center&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;flex-start&amp;quot;&amp;gt;flex-start&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;flex-end&amp;quot;&amp;gt;flex-end&amp;lt;/option&amp;gt;
    &amp;lt;/select&amp;gt;
    &amp;lt;br/&amp;gt;
    &amp;lt;section class=&amp;quot;container&amp;quot; id=&amp;quot;container&amp;quot;&amp;gt;
        &amp;lt;div&amp;gt;
            1
        &amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
            2
        &amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
            3
        &amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
            4
        &amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
            5
        &amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
            6
        &amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
            7
        &amp;lt;/div&amp;gt;
    &amp;lt;/section&amp;gt;
    &amp;lt;script&amp;gt;
    const alignContent = document.getElementById(&amp;quot;align-content-val&amp;quot;);
    const alignItems = document.getElementById(&amp;quot;align-items-val&amp;quot;);
    const container = document.getElementById(&amp;quot;container&amp;quot;);

    alignContent.addEventListener(&amp;quot;change&amp;quot;, function () {
      container.style[&amp;quot;align-content&amp;quot;] = alignContent.value;
    })
    alignItems.addEventListener(&amp;quot;change&amp;quot;, function () {
      container.style[&amp;quot;align-items&amp;quot;] = alignItems.value;
    })
    &amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Preview&lt;/strong&gt; :&lt;/p&gt;
&lt;iframe
    style='width : 70%; height : 30rem;'
    srcdoc='
        &lt;style&gt;
            body {
                background : white;
            }
            .container {
                display : flex;
                flex-direction : row;
                flex-wrap : wrap;
                width : 20rem;
                height : 20rem;
                background : lightgray;
                gap : 1rem;
            }
            .container div {
                width : 3.5rem;
                border : 1px solid green;
            }
        &lt;/style&gt;
        &lt;body&gt;
            align-content : &lt;select id=&quot;align-content-val&quot;&gt;
                &lt;option value=&quot;stretch&quot;&gt;stretch&lt;/option&gt;
                &lt;option value=&quot;center&quot;&gt;center&lt;/option&gt;
                &lt;option value=&quot;flex-start&quot;&gt;flex-start&lt;/option&gt;
                &lt;option value=&quot;flex-end&quot;&gt;flex-end&lt;/option&gt;
                &lt;option value=&quot;space-between&quot;&gt;space-between&lt;/option&gt;
                &lt;option value=&quot;space-around&quot;&gt;space-around&lt;/option&gt;
                &lt;option value=&quot;space-evenly&quot;&gt;space-evenly&lt;/option&gt;
            &lt;/select&gt;
            &lt;br/&gt;
            align-items : &lt;select id=&quot;align-items-val&quot;&gt;
                &lt;option value=&quot;stretch&quot;&gt;stretch&lt;/option&gt;
                &lt;option value=&quot;baseline&quot;&gt;baseline&lt;/option&gt;
                &lt;option value=&quot;center&quot;&gt;center&lt;/option&gt;
                &lt;option value=&quot;flex-start&quot;&gt;flex-start&lt;/option&gt;
                &lt;option value=&quot;flex-end&quot;&gt;flex-end&lt;/option&gt;
            &lt;/select&gt;
            &lt;br/&gt;
            &lt;section class=&quot;container&quot; id=&quot;container&quot;&gt;
                &lt;div&gt;
                    1
                &lt;/div&gt;
                &lt;div&gt;
                    2
                &lt;/div&gt;
                &lt;div&gt;
                    3
                &lt;/div&gt;
                &lt;div&gt;
                    4
                &lt;/div&gt;
                &lt;div&gt;
                    5
                &lt;/div&gt;
                &lt;div&gt;
                    6
                &lt;/div&gt;
                &lt;div&gt;
                    7
                &lt;/div&gt;
            &lt;/section&gt;
            &lt;script&gt;
            const alignContent = document.getElementById(&quot;align-content-val&quot;);
            const alignItems = document.getElementById(&quot;align-items-val&quot;);
            const container = document.getElementById(&quot;container&quot;);
            alignContent.addEventListener(&quot;change&quot;, function () {
              container.style[&quot;align-content&quot;] = alignContent.value;
            })
            alignItems.addEventListener(&quot;change&quot;, function () {
              container.style[&quot;align-items&quot;] = alignItems.value;
            })
            &lt;/script&gt;
        &lt;/body&gt;
    '
&gt;
    &lt;p&gt;이 문구가 보인다면, iframe 이 막혀 CSS 예시를 볼 수 없습니다.
&lt;/iframe&gt;

&lt;p&gt;&lt;strong&gt;end&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;align-content&lt;/code&gt;, &lt;code&gt;align-items&lt;/code&gt;, &lt;code&gt;align-self&lt;/code&gt; 모두,&lt;/p&gt;
&lt;p&gt;기본 값은 &lt;code&gt;stretch&lt;/code&gt; 이다.&lt;/p&gt;
&lt;p&gt;위의 예제는, &lt;code&gt;align-content&lt;/code&gt;, &lt;code&gt;align-items&lt;/code&gt; 의 조합을 변경하며&lt;/p&gt;
&lt;p&gt;각각 어떤 역할을 하는지 직접 볼 수 있게 만들었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;여기 &lt;code&gt;baseline&lt;/code&gt; 이라는 기능이 있는데, 그다지 많이 사용되지는 않는다.&lt;/p&gt;
&lt;p&gt;위에 붙어있어야 하는지, 혹은 정해진 영역을 나누어서 사용해야 하는지에 따라&lt;/p&gt;
&lt;p&gt;크게 &lt;code&gt;flex-start&lt;/code&gt;, or &lt;code&gt;space-xxx&lt;/code&gt; 로 사용 할 것 같다.&lt;/p&gt;
&lt;p&gt;중요 한 것은, &amp;quot;줄&amp;quot; 에 내리는 명령과, &amp;quot;엘리먼트&amp;quot; 에 내리는 명령이&lt;/p&gt;
&lt;p&gt;&lt;code&gt;align-content&lt;/code&gt;, &lt;code&gt;align-items&lt;/code&gt; 로 다르다는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;align-self&lt;/code&gt; 를 통해 개별 교차 축 정렬을 수행하는 예제 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.container {
    display : flex;
    flex-direction : row;
    flex-wrap : wrap;
    width : 15rem;
    height : 10rem;
    background : lightgray;
    gap : 1rem;
}
.container div {
    width : 3.5rem;
    border : 1px solid green;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body&amp;gt;
    align-self : &amp;lt;select id=&amp;quot;align-self-val&amp;quot;&amp;gt;
        &amp;lt;option value=&amp;quot;stretch&amp;quot;&amp;gt;stretch&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;baseline&amp;quot;&amp;gt;baseline&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;center&amp;quot;&amp;gt;center&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;flex-start&amp;quot;&amp;gt;flex-start&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;flex-end&amp;quot;&amp;gt;flex-end&amp;lt;/option&amp;gt;
    &amp;lt;/select&amp;gt;
    &amp;lt;br/&amp;gt;
    &amp;lt;br/&amp;gt;
    &amp;lt;section class=&amp;quot;container&amp;quot;&amp;gt;
        &amp;lt;div id=&amp;quot;div-item&amp;quot; style=&amp;quot;background : skyblue;&amp;quot;&amp;gt;
            변환하는 곳
        &amp;lt;/div&amp;gt;
        &amp;lt;div style=&amp;quot;background : white;&amp;quot;&amp;gt;
            1
        &amp;lt;/div&amp;gt;
        &amp;lt;div style=&amp;quot;background : #222; color : white;&amp;quot;&amp;gt;
            2
        &amp;lt;/div&amp;gt;
    &amp;lt;/section&amp;gt;
    &amp;lt;script&amp;gt;
    const select = document.getElementById(&amp;quot;align-self-val&amp;quot;);
    const divItem = document.getElementById(&amp;quot;div-item&amp;quot;);
    select.addEventListener(&amp;quot;change&amp;quot;, function () {
      divItem.style[&amp;quot;align-self&amp;quot;] = select.value;
    });
    &amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Preview&lt;/strong&gt; :&lt;/p&gt;
&lt;iframe
    style='width : 70%; height : 20rem;'
    srcdoc='
        &lt;style&gt;
            body {
                background : white;
            }
            .container {
                display : flex;
                flex-direction : row;
                flex-wrap : wrap;
                width : 15rem;
                height : 10rem;
                background : lightgray;
                gap : 1rem;
            }
            .container div {
                width : 3.5rem;
                border : 1px solid green;
            }
        &lt;/style&gt;
        &lt;body&gt;
            align-self : &lt;select id=&quot;align-self-val&quot;&gt;
                &lt;option value=&quot;stretch&quot;&gt;stretch&lt;/option&gt;
                &lt;option value=&quot;baseline&quot;&gt;baseline&lt;/option&gt;
                &lt;option value=&quot;center&quot;&gt;center&lt;/option&gt;
                &lt;option value=&quot;flex-start&quot;&gt;flex-start&lt;/option&gt;
                &lt;option value=&quot;flex-end&quot;&gt;flex-end&lt;/option&gt;
            &lt;/select&gt;
            &lt;br/&gt;
            &lt;br/&gt;
            &lt;section class=&quot;container&quot;&gt;
                &lt;div id=&quot;div-item&quot; style=&quot;background : skyblue;&quot;&gt;
                    변환하는 곳
                &lt;/div&gt;
                &lt;div style=&quot;background : white;&quot;&gt;
                    1
                &lt;/div&gt;
                &lt;div style=&quot;background : #222; color : white;&quot;&gt;
                    2
                &lt;/div&gt;
            &lt;/section&gt;
            &lt;script&gt;
            const select = document.getElementById(&quot;align-self-val&quot;);
            const divItem = document.getElementById(&quot;div-item&quot;);
            select.addEventListener(&quot;change&quot;, function () {
              divItem.style[&quot;align-self&quot;] = select.value;
            });
            &lt;/script&gt;
        &lt;/body&gt;
    '
&gt;
    &lt;p&gt;이 문구가 보인다면, iframe 이 막혀 CSS 예시를 볼 수 없습니다.
&lt;/iframe&gt;


&lt;p&gt;&lt;strong&gt;end&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;FlexBox 내부의 flex-item 에,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;align-self&lt;/code&gt; 를 직접 선언하여 자신의 줄에서 &amp;quot;어디에 정렬될지&amp;quot; 선택 할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;이번 글은 웹, 혹은 타 어플리케이션에 있어서의 중요한 레이아웃 방식을 배웠다고 생각한다.&lt;/p&gt;
&lt;p&gt;즉, 이번 글은 &lt;strong&gt;Flexbox&lt;/strong&gt; 라는 레이아웃을 알게 된 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이번에 &lt;code&gt;display : flex&lt;/code&gt; 로 생성되는 FlexBox 컨테이너와, 다루는 법을 알아보았다.&lt;/p&gt;
&lt;p&gt;나는 이번에 자세히 공부 해 본 것이라 당연히 숙련되었다고 말할 수는 없지만,&lt;/p&gt;
&lt;p&gt;위에서 많은 예시를 직접 코드로 작성하며 익혔다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;이번 학습으로 인하여 풀린 의문점은 무엇인가?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;나의 의문점은 CSS 스타일링의 핵심 기술인 Flexbox 에 대한 자세한 설명 없이&lt;/p&gt;
&lt;p&gt;컴포넌트 제작과 웹의 데이터 흐름에만 집중하여 CSS 스타일링을 복사하거나,&lt;/p&gt;
&lt;p&gt;혹은 이를 따라치는 과정에서 발생하는 의문점을 해소 할 수 없어 이번 글을 작성하게 되었다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;이 글을 읽고 의문점이 있다면,&lt;/h3&gt;
&lt;p&gt;저는 타인이 특정 기술에 대해 질문하는 상황을 매우 환영합니다!&lt;/p&gt;
&lt;p&gt;혹시라도 제 글을 읽고 궁금증이나 특정 질문이 있으시다면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Email&lt;/code&gt; : &lt;code&gt;rhdwhdals8765@gmail.com&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;으로 메일을 보내주시면 좋습니다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;제 글을 읽어주시는 분들께 항상 감사하다는 말씀을 드리고 싶습니다.&lt;/p&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;h2&gt;참조 사이트들&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;web.dev - (구글 관계자 팀이 만든 css 블로그)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://web.dev/learn/css/flexbox?hl=ko&quot;&gt;https://web.dev/learn/css/flexbox?hl=ko&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;W3Schools - CSS with Flex&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.w3schools.com/cssref/css3_pr_flex.php&quot;&gt;https://www.w3schools.com/cssref/css3_pr_flex.php&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;MDN 공식 문서 - (flexbox)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://developer.mozilla.org/ko/docs/Web/CSS/CSS_flexible_box_layout/Basic_concepts_of_flexbox&quot;&gt;https://developer.mozilla.org/ko/docs/Web/CSS/CSS_flexible_box_layout/Basic_concepts_of_flexbox&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;</description>
      <category>Web-Server/웹 지식</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/239</guid>
      <comments>https://codecreature.tistory.com/239#entry239comment</comments>
      <pubDate>Sun, 26 Oct 2025 16:33:27 +0900</pubDate>
    </item>
    <item>
      <title>CSS 레이아웃, display 에 대해서 알아놓자</title>
      <link>https://codecreature.tistory.com/238</link>
      <description>&lt;h2&gt;제목 : CSS 정렬의 핵심, display 속성에 대해서 알아보자&lt;/h2&gt;
&lt;h3&gt;부제 : flex, grid, block 등등에 대해서 알아보자.&lt;/h3&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;이 글을 작성하는 이유&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;리액트를 배운 적은 정말 많았다.&lt;/p&gt;
&lt;p&gt;2년전 책으로 리액트를 배울 때도,&lt;/p&gt;
&lt;p&gt;리액트를 공식문서로 찾아가 배울 때도, 심지어 프로그래머스 부트캠프에서도 리액트를 배웠다.&lt;/p&gt;
&lt;p&gt;그러나, 페이지 Layout 및 Common Component Design 및 Item Listing 과정에서,&lt;/p&gt;
&lt;p&gt;&amp;quot;왜 이 컴포넌트의 css 에 &lt;code&gt;display : flex&lt;/code&gt; 를 넣는가?&amp;quot; 를 알려주지 않았다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;동영상이나 책, 혹은 공식문서에서 알려주는 대로 css 파일을 따라 치는 것은 당연히 문제가 되질 않았다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;background&lt;/code&gt;, &lt;code&gt;width&lt;/code&gt;, &lt;code&gt;height&lt;/code&gt;, &lt;code&gt;color&lt;/code&gt;, &lt;code&gt;border&lt;/code&gt;, .... 등등&lt;/p&gt;
&lt;p&gt;CSS Property 중 직관적인 항목은 바로바로 이해가 되었지만,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;display : block&lt;/code&gt; Default 값에서 왜?&lt;/p&gt;
&lt;p&gt;&lt;code&gt;display : flex&lt;/code&gt; 로 바꾼 후, &lt;code&gt;justify-content&lt;/code&gt;, &lt;code&gt;align-items&lt;/code&gt; 속성을&lt;/p&gt;
&lt;p&gt;이리저리 바꾸어 스타일링을 해야 하는지 정확히 알지 못했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;심지어는 &lt;code&gt;display : grid&lt;/code&gt; 선언도 존재하는데,&lt;/p&gt;
&lt;p&gt;이것 또한 &lt;code&gt;flex&lt;/code&gt; 처럼 알아두어야 한다는 영상 강의의 말만 떠오를 뿐이었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;css 의 기본 선택자조차도 제대로 이해하고 있지 않은 상황에서,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;flex&lt;/code&gt;, &lt;code&gt;grid&lt;/code&gt; 와 같은 특수 속성을 이용하고,&lt;/p&gt;
&lt;p&gt;원하는 스타일에 걸맞는 &lt;code&gt;Property : Value&lt;/code&gt; 를 적을 수 있을리는 만무했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;따라서, 나는 이번에 제대로 &lt;code&gt;display&lt;/code&gt; 에 대해서 알아놓으려고 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;혹시라도 CSS 에 대해서 정말 하나도 아는 것이 없고, 문법도 모른다면,&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/237&quot;&gt;미리 알아두면 좋았을 CSS 기초 및 응용 예제 - (직접 작성)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;위의 글을 읽으면 CSS 가 뭐하는 건지 알 수 있으리라고 생각한다.&lt;/p&gt;
&lt;p&gt;위에서 내가 CSS 기초를 왜 다시 배우는건지에 대해서 많이 작성해 놓긴 했다.&lt;/p&gt;
&lt;p&gt;이는 개인적 견해라서, 간단히 읽고 CSS 의 부분 기초를 빠르게 훑으면 좋다고 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;혹은 잘 정리된 기초 공식 문서가 필요하다면&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://web.dev/learn/css/welcome?hl=ko&quot;&gt;web.dev 공식문서&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;위의 사이트에 들어가면, 구글 팀원이 작성한 CSS 의 지식을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;여타 다른 사이트들의 용어가 사전 지식이 조금 필요한 수준이라면,&lt;/p&gt;
&lt;p&gt;이는 사전 지식이 많이 없어도 되고, 기초부터 알아갈 수 있는 좋은 사이트이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;display 를 다루기 전 중요한 정보, Box Model.&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;display&lt;/code&gt; 속성은 작성하려는 컨텐츠의 정렬이나, 표현 상태 등을 표현하는 매우 중요한 속성이다.&lt;/p&gt;
&lt;p&gt;그래서, 이 속성을 다루며 시작하는 것이 옳다고 생각했으나, 여러 공식문서를 읽다 보니,&lt;/p&gt;
&lt;p&gt;&amp;quot;박스 모델&amp;quot; 이라는 기초 및 근간을 이해하지 못하면,&lt;/p&gt;
&lt;p&gt;애초에 왜 &lt;strong&gt;display :&lt;/strong&gt; &lt;code&gt;block&lt;/code&gt;, &lt;code&gt;inline-block&lt;/code&gt;, ... 등등&lt;/p&gt;
&lt;p&gt;왜 이러한 속성이 존재하는지 직관적으로 이해하기 어렵다고 판단했다.&lt;/p&gt;
&lt;p&gt;따라서, 이 박스 모델이라는 것 부터 시작해야 한다고 판단했다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;그래서 박스 모델이 뭘까?&lt;/h3&gt;
&lt;p&gt;혹시라도 CSS 를 아예 처음부터 배우는 사람을 위해서 이 글에서 사용될 &lt;strong&gt;Term&lt;/strong&gt;(용어) 를&lt;/p&gt;
&lt;p&gt;정리해야 한다고 생각했다.&lt;/p&gt;
&lt;p&gt;우리가 사용하는 극히 대부분의 태그들은, &amp;quot;박스&amp;quot; 로 이루어져 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;div&lt;/code&gt;, &lt;code&gt;p&lt;/code&gt;, &lt;code&gt;section&lt;/code&gt;, &lt;code&gt;article&lt;/code&gt;, &lt;code&gt;ol&lt;/code&gt;, &lt;code&gt;li&lt;/code&gt;, &lt;code&gt;table&lt;/code&gt;, ....&lt;/p&gt;
&lt;p&gt;그리고 텍스트 관련된 &lt;code&gt;i&lt;/code&gt;, &lt;code&gt;span&lt;/code&gt;, &lt;code&gt;b&lt;/code&gt;, &lt;code&gt;del&lt;/code&gt; 등등...&lt;/p&gt;
&lt;p&gt;물론, &lt;code&gt;abbr&lt;/code&gt; 이나, &lt;code&gt;dfn&lt;/code&gt; 과 같이 사용자에게 상호작용에 대한 설명을 덧붙여주는 특수 태그들이 있긴 하지만,&lt;/p&gt;
&lt;p&gt;특수 상황이므로, 우리가 대부분 사용하는 위의 일반적인 태그들에 집중하자.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;div&lt;/code&gt;, &lt;code&gt;p&lt;/code&gt; 는 서로 사용처가 다르긴 하지만, 박스처럼 컨텐츠를 감싼다는 것은 알 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이 박스 모델에서 가장 중요하게 알고 넘어가야 할 용어(Term) 은 이것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;img src=&quot;https://github.com/damhyeong/Image-and-content/blob/main/blog-content/display-of-css/Box-Model.001.png?raw=true&quot; width=&quot;90%&quot;&gt;

&lt;br/&gt;

&lt;p&gt;(&lt;code&gt;Content Box&lt;/code&gt; - 컨텐츠 박스)&lt;/p&gt;
&lt;p&gt;우리가 선언한 &lt;code&gt;div&lt;/code&gt; 태그 내부에 특정 글이나 이미지와 같은 컨텐츠가 있다고 가정하자.&lt;/p&gt;
&lt;p&gt;이들을 &amp;quot;낭비 없이&amp;quot; 정확히 직사각형으로 영역을 그었을 때 나오는 박스&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;(&lt;code&gt;Padding Box&lt;/code&gt; - 패딩 박스)&lt;/p&gt;
&lt;p&gt;우리가 선언한 &lt;code&gt;div&lt;/code&gt; 태그의 테두리와, 컨텐츠 박스 사이의 모든 영역을 의미한다.&lt;/p&gt;
&lt;p&gt;쉽게 풀어서 말하자면, &lt;code&gt;div&lt;/code&gt; 라는 브랜드 잠바를 입었는데,&lt;/p&gt;
&lt;p&gt;컨텐츠는 &lt;code&gt;Padding Box&lt;/code&gt; 라는 패딩이 중간에 끼어 있는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;(&lt;code&gt;Border Box&lt;/code&gt; - 경계 박스)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;div&lt;/code&gt; 를 강조하기 위해 테두리를 강조 할 수 있다.&lt;/p&gt;
&lt;p&gt;예를 들어, &lt;code&gt;border : 2px solid black;&lt;/code&gt; 을 선언한다고 가정 해 보자.&lt;/p&gt;
&lt;p&gt;그렇다면, &lt;code&gt;div&lt;/code&gt; 태그는 2 픽셀의 두께를 가진, 검정색 테두리가 형성 될 것이다.&lt;/p&gt;
&lt;p&gt;우리는 이 테두리를 10 픽셀, 혹은 100 픽셀로도 만들 수 있다.&lt;/p&gt;
&lt;p&gt;이 때, 이 테두리 영역을 &lt;code&gt;Border Box&lt;/code&gt; 라고 부른다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;(&lt;code&gt;Margin Box&lt;/code&gt; - 여백 박스)&lt;/p&gt;
&lt;p&gt;위에서 &lt;code&gt;Padding Box&lt;/code&gt; 를 다룰 때, 태그의 영역과, 실제 컨텐츠 사이의 모든 영역을 말했다.&lt;/p&gt;
&lt;p&gt;패딩 박스는, 다른 시각으로 말할 때 컨텐츠에게 일종의 여유 공간을 만들어 준다고도 볼 수 있다.&lt;/p&gt;
&lt;p&gt;너무 가까우면, 컨텐츠가 시각적으로 좋지 않을 수도 있기 때문이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Margin Box&lt;/code&gt; 도 동일하게 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;div&lt;/code&gt; 라는 태그가 있는데, 나는 이 태그가 외부 요인에 의해 시각적으로 여유가 있었으면 좋겠다 싶으면,&lt;/p&gt;
&lt;p&gt;&amp;quot;마진&amp;quot;(Margin) 을 설정하여 부모, 혹은 형제 수준의 컴포넌트로부터 일정 간격을 떨어뜨리게 만들 수 있다.&lt;/p&gt;
&lt;p&gt;특정 상품을 팔 때, 투자된 금액에 비례하여 &amp;quot;몇 퍼센트의 마진이 남는다&amp;quot; 라는 말이 있는데,&lt;/p&gt;
&lt;p&gt;이러한 문구로 마진이 무엇인지 직관적으로 이해 할 수도 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;즉 박스 모델을 요약하자면,&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;CSS 에서 표시하는 모든 것은 &amp;quot;상자&amp;quot; 이므로, CSS 박스 모델 작동 방식을 아는 것이 핵심이라는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;이제 display 속성에 대해서 알아 보자.&lt;/h2&gt;
&lt;p&gt;모든 태그가 &amp;quot;박스&amp;quot; 이기 때문에,&lt;/p&gt;
&lt;p&gt;모든 태그가 Default 로 &lt;code&gt;display : block&lt;/code&gt; 을 가진 것은 아니다.&lt;/p&gt;
&lt;p&gt;구역 형태, 즉, 영역을 구분짓는 &lt;code&gt;div&lt;/code&gt;, &lt;code&gt;article&lt;/code&gt;, &lt;code&gt;section&lt;/code&gt;, ... 등등&lt;/p&gt;
&lt;p&gt;과 같은 태그들은 Default 로 &lt;code&gt;display : block&lt;/code&gt; 속성을 가지나,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;span&lt;/code&gt;, &lt;code&gt;a&lt;/code&gt;, &lt;code&gt;strong&lt;/code&gt;, &lt;code&gt;i&lt;/code&gt; 와 같은 텍스팅 태그들은 그렇지 않다.&lt;/p&gt;
&lt;p&gt;이들은 기본적으로 &lt;code&gt;display : inline&lt;/code&gt; 을 가진다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;생각보다 많은 display 의 속성들&lt;/h3&gt;
&lt;p&gt;보통 일반적으로 사용되는 &lt;code&gt;display&lt;/code&gt; 의 속성은,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;block&lt;/code&gt;, &lt;code&gt;inline&lt;/code&gt;, &lt;code&gt;flex&lt;/code&gt;, &lt;code&gt;grid&lt;/code&gt; .. 가 있다.&lt;/p&gt;
&lt;p&gt;그러나, 그 외의 속성들도 다양하게 존재한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;MDN 문서에서 보여주는 수많은 display 의 속성들&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/* precomposed values */
display: block;
display: inline;
display: inline-block;
display: flex;
display: inline-flex;
display: grid;
display: inline-grid;

/* Box suppression */
display: none;
display: contents;

/* multi-keyword syntax */
display: block flex;
display: block grid;
display: inline flex;
display: inline grid;

/* other values */
display: table;
display: table-row; /* all table elements have an equivalent CSS display value */
display: list-item;

/* Global values */
display: inherit;
display: initial;
display: revert;
display: revert-layer;
display: unset;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이러한 CSS 예시를 보여준 mdn 의 주소는 : &lt;a target=&quot;_blank&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/display&quot;&gt;MDN CSS - (display)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;이 속성 값의 열거들을 보며 느낀 것은,&lt;/p&gt;
&lt;p&gt;이 속성들을 알게 된다면, CSS 레이아웃의 흐름을 대략적으로 파악할 수 있겠구나 하는 감상이었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;위에서, 친숙한 키워드도 보이지만, 상당수는 처음 보는 키워드들이었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이 모든 속성들의 설명을 간략히 보여주는 사이트를 찾으려 했으나,&lt;/p&gt;
&lt;p&gt;그 목적에 가장 가까웠던 &amp;quot;web.dev&amp;quot; 홈페이지도&lt;/p&gt;
&lt;p&gt;&lt;code&gt;block&lt;/code&gt;, &lt;code&gt;flex&lt;/code&gt;, &lt;code&gt;grid&lt;/code&gt; 에 치중되어 있었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;나는 평소에 AI 에게 &amp;quot;정답&amp;quot; 을 달라고 말하지는 않는다.&lt;/p&gt;
&lt;p&gt;나 스스로의 성장을 위해 &amp;quot;힌트&amp;quot; 를 줘야 한다고 제한을 두었다.&lt;/p&gt;
&lt;p&gt;Gemini 는 나에게 이 속성들을 Structuring 하여 구별 할 수 있게 힌트를 주었다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;박스 모델이 중요했던 이유&lt;/h3&gt;
&lt;p&gt;박스 모델은 우리가 선언한 영역에서&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;margin&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;outline &amp;amp; shadow&lt;/code&gt; (이거는 직관화를 위해 예시에서 뺐었음)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;border&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;padding&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;content&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;로 나눌 수 있다.&lt;/p&gt;
&lt;p&gt;각 태그는 브라우저가 가지고 있는 Default 속성에 의해 아주 조금씩 다를 수 있는데,&lt;/p&gt;
&lt;p&gt;대부분 동일 한 것은, &amp;quot;박스 모델&amp;quot; 이기 때문이다.&lt;/p&gt;
&lt;p&gt;여기서 위에 존재하는 &lt;code&gt;block&lt;/code&gt;, &lt;code&gt;flex&lt;/code&gt;, &lt;code&gt;grid&lt;/code&gt; 와,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;inline-...&lt;/code&gt; 들의 차이점을 알아보자.&lt;/p&gt;
&lt;p&gt;이 차이점은, 박스 모델이 깊이 관여한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;예시가 될 코드는 이러합니다.&lt;/h4&gt;
&lt;p&gt;내 블로그에서 직접 &lt;code&gt;div&lt;/code&gt;, &lt;code&gt;article&lt;/code&gt;, &lt;code&gt;section&lt;/code&gt; 과 같은 영역 표시자나,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;p&lt;/code&gt; 태그를 함부로 마크다운으로 작성 할 수가 없다.&lt;/p&gt;
&lt;p&gt;이유는 Custom Theme 을 위해 이들을 따로 조정 해 놓았기 때문이다.&lt;/p&gt;
&lt;p&gt;따라서, 내가 블로그에 작성하게 될 HTML + CSS + JavaScript 는,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; 이라는 태그로 진행하게 된다.&lt;/p&gt;
&lt;p&gt;만약에 이 글을 읽으시는 분께서 브라우저의 호환성이나,&lt;/p&gt;
&lt;p&gt;브라우저의 개인 설정으로 인해 실제 CSS 예제가 보이지 않을 수도 있습니다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;-실제 문서에서 작성되는 iframe 예시-&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;iframe
  width=&amp;quot;80%&amp;quot;
  ;
  srcdoc=&amp;quot;
        &amp;lt;style&amp;gt;
            &amp;lt;!-- iframe 의 기본 배경색이 투명이라서 초기화. --&amp;gt;
            body {
                background : white;
            }
            &amp;lt;!-- css 예제를 실제 적용하게 되는 장소. --&amp;gt;
            h1 {
                color : skyblue;
            }
        &amp;lt;/style&amp;gt;
        &amp;lt;!-- 예시를 들기 위한 HTML 태그를 작성하는 장소 --&amp;gt;
        &amp;lt;body&amp;gt;
            &amp;lt;h1&amp;gt;Heloow World!!&amp;lt;/h1&amp;gt;
        &amp;lt;/body&amp;gt;
    &amp;quot;
&amp;gt;
  &amp;lt;p&amp;gt;이 문구가 보인다면, 예제가 뜨지 않은 겁니다.&amp;lt;/p&amp;gt;
&amp;lt;/iframe&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이러한 형식으로 블로그에 적용됩니다.&lt;/p&gt;
&lt;h4&gt;inline 이 붙었을 때와, 아닐 때의 차이는?&lt;/h4&gt;
&lt;p&gt;확실히 짚고 넘어가자면, &lt;code&gt;display : block&lt;/code&gt; 값과, 박스 모델은 동의어가 아니다.&lt;/p&gt;
&lt;p&gt;박스 모델은 언제 어디서나 적용되는 모든 스타일링의 기준이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;block&lt;/code&gt;, &lt;code&gt;flex&lt;/code&gt;, &lt;code&gt;grid&lt;/code&gt; 와 같은 대표적인 &lt;code&gt;display&lt;/code&gt; 값들을 살펴 보자.&lt;/p&gt;
&lt;p&gt;위의 속성을 제각각 가지고 있는 3 개의 &lt;code&gt;div&lt;/code&gt; 가 나란히 형제 레벨에서 붙어 있다고 가정 해 보자.&lt;/p&gt;
&lt;p&gt;그리고, 각 &lt;code&gt;div&lt;/code&gt; 들은 그저 &lt;code&gt;&amp;quot;hello&amp;quot;&lt;/code&gt; 라는 문자열만 담고 있다.&lt;/p&gt;
&lt;p&gt;위의 상황의 결과는,&lt;/p&gt;
&lt;p&gt;각 줄 (row) 마다, &lt;code&gt;&amp;quot;hello&amp;quot;&lt;/code&gt; 라는 글자가 나올 것이다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.b-style {
  display: block;
}
.f-style {
  display: flex;
}
.g-style {
  display: grid;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body&amp;gt;
  &amp;lt;div class=&amp;quot;b-style&amp;quot;&amp;gt;hello! in Block.&amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;f-style&amp;quot;&amp;gt;hello! in Flex.&amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;g-style&amp;quot;&amp;gt;Hello! in Grid.&amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;iframe
    width=&quot;80%&quot;
    max-height=&quot;10rem&quot;
    srcdoc='
    &lt;style&gt;
        body {
            background : white;
        }
        .b-style {
            display : block;
        }
        .f-style {
            display : flex;
        }
        .g-style {
            display : grid;
        }
    &lt;/style&gt;
    &lt;body&gt;
        &lt;div class=&quot;b-style&quot;&gt;
            hello! in Block.
        &lt;/div&gt;
        &lt;div class=&quot;f-style&quot;&gt;
            hello! in Flex.
        &lt;/div&gt;
        &lt;div class=&quot;g-style&quot;&gt;
            Hello! in Grid.
        &lt;/div&gt;
    &lt;/body&gt;
    '
&gt;
    &lt;p&gt;이 문구가 보인다면, CSS 예제를 볼 수 없습니다.
&lt;/iframe&gt;

&lt;br/&gt;

&lt;p&gt;위의 예제에서 말하고자 하는 것은, &lt;code&gt;block&lt;/code&gt;, &lt;code&gt;flex&lt;/code&gt;, &lt;code&gt;grid&lt;/code&gt; 속성을 가진 &lt;code&gt;div&lt;/code&gt; 태그 모두,&lt;/p&gt;
&lt;p&gt;상위 단계, 혹은 같은 레벨에서 바라보았을 때, 하나의 줄을 차지해야만 하는 &lt;code&gt;block&lt;/code&gt; 의 특성을 가진다는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그렇다면, 위의 예제에서, CSS 만 &lt;code&gt;inline&lt;/code&gt; 으로 변경 해 보겠다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.b-style {
  display: inline-block;
}
.f-style {
  display: inline-flex;
}
.g-style {
  display: inline-grid;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;iframe
    width=&quot;80%&quot;
    max-height=&quot;10rem&quot;
    srcdoc='
    &lt;style&gt;
        body {
            background : white;
        }
        .b-style {
            display : inline-block;
        }
        .f-style {
            display : inline-flex;
        }
        .g-style {
            display : inline-grid;
        }
    &lt;/style&gt;
    &lt;body&gt;
        &lt;div class=&quot;b-style&quot;&gt;
            hello! in Block.
        &lt;/div&gt;
        &lt;div class=&quot;f-style&quot;&gt;
            hello! in Flex.
        &lt;/div&gt;
        &lt;div class=&quot;g-style&quot;&gt;
            Hello! in Grid.
        &lt;/div&gt;
    &lt;/body&gt;
    '
&gt;
    &lt;p&gt;이 문구가 보인다면, CSS 예제를 볼 수 없습니다.
&lt;/iframe&gt;

&lt;br/&gt;

&lt;p&gt;위의 예제를 보면, 3 줄에서, 1 줄로 변경 된 것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;무슨 차이일까?&lt;/p&gt;
&lt;p&gt;이 세 가지 모두, 영역을 지닌 박스 모델이 맞다.&lt;/p&gt;
&lt;p&gt;그러나, &lt;code&gt;inline&lt;/code&gt; 이 붙으면, 해당 DOM 은 &lt;strong&gt;&amp;quot;텍스트처럼 취급&amp;quot;&lt;/strong&gt; 하게 된다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;block&lt;/code&gt;, &lt;code&gt;flex&lt;/code&gt;, &lt;code&gt;grid&lt;/code&gt; 모두 원래는, 하나의 영역으로서, 한 줄을 담당하게 되는데,&lt;/p&gt;
&lt;p&gt;만약 &lt;code&gt;inline-xxx&lt;/code&gt; 형태가 된다면, 내부의 영역을 &amp;quot;그대로&amp;quot; 보전하되,&lt;/p&gt;
&lt;p&gt;상층부나 같은 레벨에서 취급되는 것은 &amp;quot;텍스트&amp;quot; 처럼 취급된다는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;요약하면&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;inline&lt;/code&gt; 이 붙으면, 해당 영역은 &amp;quot;글자처럼&amp;quot; 취급된다.&lt;/p&gt;
&lt;p&gt;그러나, 박스 모델의 근간인 &lt;code&gt;margin&lt;/code&gt;, &lt;code&gt;border&lt;/code&gt;, &lt;code&gt;padding&lt;/code&gt; 설정이 가능하다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;멀티 키워드 문법 - Multi-Keyword&lt;/h3&gt;
&lt;p&gt;MDN 에서 보여준 예시는 이러했다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/* multi-keyword syntax */
display: block flex;
display: block grid;
display: inline flex;
display: inline grid;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 값은, 굉장히 간단한데,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;display: flex;
display: grid;
display: inline-flex;
display: inline-grid;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이와 동일하다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;컨텐츠 없애기 및 감추기&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;display : none;&lt;/code&gt; 이라는 속성이 존재한다.&lt;/p&gt;
&lt;p&gt;이는 말 그대로, &amp;quot;보여주지 않겠다&amp;quot; 라는 속성이다.&lt;/p&gt;
&lt;p&gt;이 속성을 사용하게 되면, &lt;code&gt;DOM&lt;/code&gt; 체계에서 해당된 태그가 &amp;quot;없어진 듯한&amp;quot; 느낌을 받는다.&lt;/p&gt;
&lt;p&gt;이 속성이 차지해야 할 영역 대신, 그 다음 태그가 자리를 차지하기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그리고 반대로 다시 보여주고 싶다면, &lt;code&gt;display : xxx(원래 값)&lt;/code&gt; 으로 돌려놓으면 된다.&lt;/p&gt;
&lt;p&gt;다시 되돌려놓는다면 뿅! 하고 감춰졌던 태그가 나타나며, 이후의 태그들이 밀려난다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.p-none {
  display: none;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body&amp;gt;
  &amp;lt;button onclick=&amp;quot;toggleParagraph()&amp;quot;&amp;gt;문단 Toggle Btn&amp;lt;/button&amp;gt;
  &amp;lt;br /&amp;gt;
  &amp;lt;p&amp;gt;첫 번째 문단&amp;lt;/p&amp;gt;
  &amp;lt;p id=&amp;quot;hidden-p&amp;quot;&amp;gt;두 번째 문단 (Toggle)&amp;lt;/p&amp;gt;
  &amp;lt;p&amp;gt;세 번째 문단&amp;lt;/p&amp;gt;
  &amp;lt;script&amp;gt;
    let toggleDom = document.getElementById(&amp;quot;hidden-p&amp;quot;);
    toggleDom.className.add(&amp;quot;p-none&amp;quot;);

    function toggleParagraph() {
      toggleDom = document.getElementById(&amp;quot;hidden-p&amp;quot;);
      toggleDom.classList.toggle(&amp;quot;p-none&amp;quot;);
    }
  &amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;iframe
    width=&quot;80%&quot;
    max-height=&quot;10rem&quot;
    srcdoc='
    &lt;style&gt;
        body {
            background : white;
        }
        .p-none {
            display : none;
        }
    &lt;/style&gt;
    &lt;body&gt;
        &lt;button onclick=&quot;toggleParagraph()&quot;&gt;문단 Toggle Btn&lt;/button&gt;
        &lt;br/&gt;
        &lt;p&gt;첫 번째 문단&lt;/p&gt;
        &lt;p id=&quot;hidden-p&quot;&gt;두 번째 문단 (Toggle)&lt;/p&gt;
        &lt;p&gt;세 번째 문단&lt;/p&gt;
        &lt;script&gt;
        let toggleDom = document.getElementById(&quot;hidden-p&quot;);
        toggleDom.className.add(&quot;p-none&quot;);
        function toggleParagraph() {
          toggleDom = document.getElementById(&quot;hidden-p&quot;);
          toggleDom.classList.toggle(&quot;p-none&quot;);
        }
        &lt;/script&gt;
    &lt;/body&gt;
    '
&gt;
    &lt;p&gt;이 문구가 보인다면, CSS 예제를 볼 수 없습니다.
&lt;/iframe&gt;

&lt;br/&gt;

&lt;p&gt;위의 예제 토글 버튼을 클릭 해 보면,&lt;/p&gt;
&lt;p&gt;없어진 2 번째 p 태그 대신, 3 번째 p 태그가 그 자리를 &amp;quot;차지&amp;quot;하는 것을 볼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;컨텐츠를 감추되, 영역(공간)을 유지하고 싶다면&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이 상황에서는 &lt;code&gt;display&lt;/code&gt; 를 건들지 않는다.&lt;/p&gt;
&lt;p&gt;이 때는 &lt;code&gt;visibility : xxx&lt;/code&gt; 라는 &lt;code&gt;Attribute : Value&lt;/code&gt; 형식을 사용한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;visibility : visible&lt;/code&gt; --&amp;gt; 보이는 상태(원래 상태)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;visibility : hidden&lt;/code&gt; --&amp;gt; 보이지 않지만, 공간을 차지하는 상태(감춰짐)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.is-visible {
  visibility: visible;
}
.not-visible {
  visibility: hidden;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body&amp;gt;
  &amp;lt;button onclick=&amp;quot;toggleParagraph()&amp;quot;&amp;gt;문단 Toggle Btn&amp;lt;/button&amp;gt;
  &amp;lt;br /&amp;gt;
  &amp;lt;p&amp;gt;첫 번째 문단&amp;lt;/p&amp;gt;
  &amp;lt;p id=&amp;quot;hidden-p&amp;quot;&amp;gt;두 번째 문단 (Toggle)&amp;lt;/p&amp;gt;
  &amp;lt;p&amp;gt;세 번째 문단&amp;lt;/p&amp;gt;
  &amp;lt;script&amp;gt;
    let toggleDom = document.getElementById(&amp;quot;hidden-p&amp;quot;);
    toggleDom.classList.add(&amp;quot;is-visible&amp;quot;);

    function toggleParagraph() {
      toggleDom = document.getElementById(&amp;quot;hidden-p&amp;quot;);
      toggleDom.classList.toggle(&amp;quot;is-visible&amp;quot;);
      toggleDom.classList.toggle(&amp;quot;not-visible&amp;quot;);
    }
  &amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;iframe
    width=&quot;80%&quot;
    max-height=&quot;10rem&quot;
    srcdoc='
    &lt;style&gt;
        body {
            background : white;
        }
        .is-visible {
            visibility : visible;
        }
        .not-visible {
            visibility : hidden;
        }
    &lt;/style&gt;
    &lt;body&gt;
        &lt;button onclick=&quot;toggleParagraph()&quot;&gt;문단 Toggle Btn&lt;/button&gt;
        &lt;br/&gt;
        &lt;p&gt;첫 번째 문단&lt;/p&gt;
        &lt;p id=&quot;hidden-p&quot;&gt;두 번째 문단 (Toggle)&lt;/p&gt;
        &lt;p&gt;세 번째 문단&lt;/p&gt;
        &lt;script&gt;
        let toggleDom = document.getElementById(&quot;hidden-p&quot;);
        toggleDom.classList.add(&quot;is-visible&quot;);
        function toggleParagraph() {
          toggleDom = document.getElementById(&quot;hidden-p&quot;);
          toggleDom.classList.toggle(&quot;is-visible&quot;);
          toggleDom.classList.toggle(&quot;not-visible&quot;);
        }
        &lt;/script&gt;
    &lt;/body&gt;
    '
&gt;
    &lt;p&gt;이 문구가 보인다면, CSS 예제를 볼 수 없습니다.
&lt;/iframe&gt;

&lt;br/&gt;

&lt;p&gt;위의 예제 버튼을 통해 Toggle 해보면,&lt;/p&gt;
&lt;p&gt;이번에는 3 번째 문단이, 2 번째 문단의 영역을 침범하지 않는것을 볼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;즉, 요약해서&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;display : none&lt;/code&gt; --&amp;gt; 해당된 DOM 이 &amp;quot;없어진 것 처럼&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;visibility : hidden&lt;/code&gt; --&amp;gt; 해당된 DOM 은 &amp;quot;보이지만 않고&amp;quot; 영역은 차지한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;전역 값 (Global Values)&lt;/h3&gt;
&lt;p&gt;나는 이 값에서 &lt;code&gt;inherit&lt;/code&gt; (상속) 말고는, 다 처음 보는 값들이었다.&lt;/p&gt;
&lt;p&gt;내가 미리 이 값들을 본 결과, &lt;code&gt;inherit&lt;/code&gt;, &lt;code&gt;unset&lt;/code&gt; 을 제외하면,&lt;/p&gt;
&lt;p&gt;사용처가 적지 않을까 생각이 든다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;inherit&lt;/code&gt; :&lt;/p&gt;
&lt;p&gt;DOM 체계에서 부모가 가지고 있는 &lt;code&gt;속성 : 값&lt;/code&gt; 을 그대로 물려받는다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;unset&lt;/code&gt; :&lt;/p&gt;
&lt;p&gt;상속 가능한 &lt;code&gt;속성 : 값&lt;/code&gt; 이라면 &lt;code&gt;inherit&lt;/code&gt; 으로 동작하지만,&lt;/p&gt;
&lt;p&gt;그렇지 않다면 &lt;code&gt;initial&lt;/code&gt; (초기화) 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;initial&lt;/code&gt; :&lt;/p&gt;
&lt;p&gt;굉장히 조심히 써야 하는 값이라고 생각하는데,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;div&lt;/code&gt; 는 &lt;code&gt;display : block&lt;/code&gt; 이 기본이고,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;a&lt;/code&gt; 는 &lt;code&gt;color : blue&lt;/code&gt; 가 기본이다.&lt;/p&gt;
&lt;p&gt;만약에 &lt;code&gt;div&lt;/code&gt; 태그에&lt;/p&gt;
&lt;p&gt;&lt;code&gt;display : flex or grid&lt;/code&gt; 했다가, &lt;code&gt;display : initial&lt;/code&gt; 을 적용하면, 어떻게 될까?&lt;/p&gt;
&lt;p&gt;&lt;code&gt;display : inline&lt;/code&gt; 이 된다.&lt;/p&gt;
&lt;p&gt;조금 있다가 &lt;code&gt;revert&lt;/code&gt; 랑 비교가 될 텐데,&lt;/p&gt;
&lt;p&gt;저 영어 초기화라는 것이, 내가 작성한 DOM 태그의 기본 속성으로 초기화? 가 아니라,&lt;/p&gt;
&lt;p&gt;작성된 &amp;quot;속성을 기본값&amp;quot; 으로 만들어 버린다는 것이, 위험하다고 생각이 된다.&lt;/p&gt;
&lt;p&gt;요즘 웹 스타일링에는 인터랙션을 위해 벼러별 경우에 스타일링을 만들어 놓을텐데,&lt;/p&gt;
&lt;p&gt;만약에 여기서 &lt;code&gt;initial&lt;/code&gt; 값을 이용한다면 재밌는 디버깅 시간을 가지게 될 것이다!&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;또한, &lt;code&gt;color : initial&lt;/code&gt; 도 큰 문제이다.&lt;/p&gt;
&lt;p&gt;이 또한, 위에서 설명 한 것 처럼, &amp;quot;적용된 DOM 이 가지는 기본 값으로 초기화&amp;quot; 가 절대 아니고,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;color&lt;/code&gt; 라는 &amp;quot;속성이 가지는 기본값&amp;quot;, &lt;code&gt;black&lt;/code&gt; 으로 된다.&lt;/p&gt;
&lt;p&gt;이것이 바로 CSS 의 Global Value, &lt;code&gt;initial&lt;/code&gt; 에 대한 간략한 설명이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;revert&lt;/code&gt; :&lt;/p&gt;
&lt;p&gt;이 속성은 &amp;quot;타겟팅 된 DOM 이 가지는 기본 속성값&amp;quot; 으로 초기화 해 준다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;a&lt;/code&gt; 태그에서 &lt;code&gt;color : revert&lt;/code&gt; 를 사용한다면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;a&lt;/code&gt; 태그가 가지는 기본 색상, &lt;code&gt;color : blue&lt;/code&gt; 를 적용 해 준다.&lt;/p&gt;
&lt;p&gt;그러나, 이 태그 또한 자주 사용되기는 어렵다고 생각되는 것이,&lt;/p&gt;
&lt;p&gt;개발자가 지정한 스타일 시트를 무시하고, 브라우저가 설정한 기본 값으로 되돌린다는 점이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;기타 값&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;table&lt;/code&gt;, &lt;code&gt;list-item&lt;/code&gt; 이라는 값이 남아있다.&lt;/p&gt;
&lt;p&gt;우리가 사용하는 &lt;code&gt;table&lt;/code&gt; 태그에, 이 &lt;code&gt;display : table&lt;/code&gt; 이 기본으로 들어가며,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;li&lt;/code&gt; 태그에 &lt;code&gt;list-item&lt;/code&gt; 이 들어간다.&lt;/p&gt;
&lt;p&gt;정확한 태그를 사용하지 않고, 커스텀 Tag 를 &amp;quot;테이블처럼&amp;quot;, &amp;quot;리스트처럼&amp;quot; 만들고 싶다면,&lt;/p&gt;
&lt;p&gt;이러한 속성을 적용하여 커스텀 할 수 있다고 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;flex 와 grid&lt;/h2&gt;
&lt;p&gt;우선, &lt;code&gt;div&lt;/code&gt; 나 &lt;code&gt;article&lt;/code&gt; 과 같은 일반 영역 태그들이 가지는 속성,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;display : block&lt;/code&gt; 쌍이, 같은 레벨 ~ 상위 레벨이 CSS 로서 인터랙션 할 때의 반응과,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;display : flex;&lt;/code&gt;, &lt;code&gt;display : grid;&lt;/code&gt; 간의 반응은 동일하다.&lt;/p&gt;
&lt;p&gt;그러나, &lt;code&gt;flex&lt;/code&gt;, &lt;code&gt;grid&lt;/code&gt; 의 차이점은, 스타일링 도메인에 있어서 더 다양한 시도를 할 수 있다는 것이다.&lt;/p&gt;
&lt;p&gt;이 말은, 기존 스타일링에 한계를 두지 않고, 개발자 원하는 대로 배치할 수 있게 만들어 주었다는 말과 동일하다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;간단하게 알아보는 &amp;quot;flex 란?&amp;quot;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;flex&lt;/code&gt; 를 사용한다는 것은, 1차원 나열 형식을 사용하겠다는 말과 동일하다.&lt;/p&gt;
&lt;p&gt;즉, 1차원 레이아웃을 위해 존재한다.&lt;/p&gt;
&lt;p&gt;물론, &lt;code&gt;block&lt;/code&gt; 값도 1차원 나열 형식이 가능하긴 하지만,&lt;/p&gt;
&lt;p&gt;내부 엘리먼트 정렬 형식과 세로 기준, 가로 기준의 자유로움이 부족하다.&lt;/p&gt;
&lt;p&gt;이러한 면에 있어 &lt;code&gt;flex&lt;/code&gt; 는 &lt;code&gt;block&lt;/code&gt; 보다 훨씬 자유롭다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;특히 &lt;code&gt;flex&lt;/code&gt; 는 반응형 웹 디자인에 굉장히 유리한데,&lt;/p&gt;
&lt;p&gt;유저가 브라우저에 접속한다는 것은, 휴대폰, 태블릿, 모니터 등등 여러 크기가 존재한다.&lt;/p&gt;
&lt;p&gt;콘텐츠의 크기가 화면보다 크거나, 작을 때 브라우저에게 유연한 명령을 내릴 수 있다.&lt;/p&gt;
&lt;p&gt;단, 자유로운 대신 연계되어 있는 여러 &lt;code&gt;속성 : 값&lt;/code&gt; 을 알아야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;간단하게 &lt;code&gt;flex&lt;/code&gt; 를 선언하여 하위 엘리먼트들이 어떻게 보이는지 확인 해 보자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.container-flex {
  display: flex;
}
div {
  border: 2px solid black;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body class=&amp;quot;container-flex&amp;quot;&amp;gt;
  &amp;lt;div&amp;gt;Very Long Very Long Very Long Very Long Very Long&amp;lt;/div&amp;gt;
  &amp;lt;div&amp;gt;
    Very Long Very Long Very Long Very Long Very Long Very Long Very Long Very
    Long Very Long Very Long
  &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;iframe
    width=&quot;40%&quot;
    max-height=&quot;10rem&quot;
    srcdoc='
    &lt;style&gt;
        body {
            background : white;
        }
        .container-flex {
            display : flex;
        }
        div {
            border : 2px solid black;
        }
    &lt;/style&gt;
    &lt;body class=&quot;container-flex&quot;&gt;
        &lt;div&gt;
            Very Long Very Long Very Long Very Long Very Long
        &lt;/div&gt;
        &lt;div&gt;
            Very Long Very Long Very Long Very Long Very Long
            Very Long Very Long Very Long Very Long Very Long
        &lt;/div&gt;
    &lt;/body&gt;
    '
&gt;
    &lt;p&gt;이 문구가 보인다면, CSS 예제를 볼 수 없습니다.
&lt;/iframe&gt;

&lt;br/&gt;

&lt;p&gt;위의 예제를 보면, &lt;code&gt;block&lt;/code&gt; 과 달리, &lt;code&gt;flex&lt;/code&gt; 는 기본으로 옆쪽으로 늘어진다. (horizontal - 가로축)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;flex&lt;/code&gt; 는 고유의 속성을 통해 정렬 방향을 정할 수 있다. 가로, 세로, 등등..&lt;/p&gt;
&lt;p&gt;혹은 &lt;code&gt;flex-wrap&lt;/code&gt; 속성을 통해, 강제 1차원 나열이 아니라,&lt;/p&gt;
&lt;p&gt;이 DOM 에게 주어진 최대 &lt;code&gt;width&lt;/code&gt; 에 따라, 동적으로 하위 엘리먼트들이 어떻게 나열될 지 선택할 수 있다.&lt;/p&gt;
&lt;p&gt;예를 들어, 충분한 넓이가 주어졌을 때는 1차원 나열이 맞지만,&lt;/p&gt;
&lt;p&gt;넓이가 부족하다면, 하위 엘리먼트들을 글자처럼 &amp;quot;줄넘김&amp;quot; 해서 보여주는 것이다.&lt;/p&gt;
&lt;p&gt;단순 글자가 아니라, 블록 수준에서 주어진 넓이에 따른 유연한 레이아웃을 보여 줄 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.container-flex {
  display: flex;
}

.container-flex[data-wrap] {
  flex-wrap: wrap;
}

div {
  border: 2px solid black;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body class=&amp;quot;container-flex&amp;quot; id=&amp;quot;container&amp;quot;&amp;gt;
    &amp;lt;button onclick=&amp;quot;toggleWrap()&amp;quot;&amp;gt;Wrap Toggle&amp;lt;/button&amp;gt;
    &amp;lt;div&amp;gt;Very Long Very Long Very Long Very Long Very Long&amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;
        Very Long Very Long Very Long Very Long Very Long Very Long Very Long Very Long Very Long Very Long
    &amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;
        Very Long Very Long Very Long Very Long Very Long Very Long Very Long Very Long Very Long Very Long
    &amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;
        Very Long Very Long Very Long Very Long Very Long Very Long Very Long Very Long Very Long Very Long
    &amp;lt;/div&amp;gt;
    &amp;lt;script&amp;gt;
    function toggleWrap() {
      const container = document.getElementById(&amp;quot;container&amp;quot;);

      if(!container.getAttribute(&amp;quot;data-wrap&amp;quot;)) {
        container.setAttribute(&amp;quot;data-wrap&amp;quot;, &amp;quot;true&amp;quot;);
      } else {
        container.removeAttribute(&amp;quot;data-wrap&amp;quot;);
      }
    }
    &amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;iframe
    width=&quot;40%&quot;
    max-height=&quot;10rem&quot;
    srcdoc='
    &lt;style&gt;
        body {
            background : white;
        }
        .container-flex {
          display: flex;
        }
        .container-flex[data-wrap] {
          flex-wrap: wrap;
        }
        div {
          border: 2px solid black;
        }
    &lt;/style&gt;
    &lt;body class=&quot;container-flex&quot; id=&quot;container&quot;&gt;
        &lt;button onclick=&quot;toggleWrap()&quot;&gt;Wrap Toggle&lt;/button&gt;
        &lt;div&gt;Very Long Very Long Very Long Very Long Very Long&lt;/div&gt;
        &lt;div&gt;
            Very Long Very Long Very Long Very Long Very Long Very Long Very Long Very Long Very Long Very Long
        &lt;/div&gt;
        &lt;div&gt;
            Very Long Very Long Very Long Very Long Very Long Very Long Very Long Very Long Very Long Very Long
        &lt;/div&gt;
        &lt;div&gt;
            Very Long Very Long Very Long Very Long Very Long Very Long Very Long Very Long Very Long Very Long
        &lt;/div&gt;
        &lt;script&gt;
        function toggleWrap() {
          const container = document.getElementById(&quot;container&quot;);
          if(!container.getAttribute(&quot;data-wrap&quot;)) {
            container.setAttribute(&quot;data-wrap&quot;, &quot;true&quot;);
          } else {
            container.removeAttribute(&quot;data-wrap&quot;);
          }
        }
        &lt;/script&gt;
    &lt;/body&gt;
    '
&gt;
    &lt;p&gt;이 문구가 보인다면, CSS 예제를 볼 수 없습니다.
&lt;/iframe&gt;

&lt;br/&gt;

&lt;p&gt;위의 예제에서 &amp;quot;Wrap Toggle&amp;quot; 을 눌러보면,&lt;/p&gt;
&lt;p&gt;기본 &lt;code&gt;flex&lt;/code&gt; 속성으로 인한 horizontal(가로) 정렬을 유지하기 위해&lt;/p&gt;
&lt;p&gt;뺵빽하게 늘어섰던 엘리먼트들이,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;flex-wrap : wrap&lt;/code&gt; 으로 인해 엘리먼트들이 &amp;quot;줄넘김&amp;quot; 되고 있는 것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;여기서 눈여겨 봐야 하는 것은,&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이건 horizaontal(수평) 에서, vertical(수직) 으로 전환 된 것이 절대 아니다.&lt;/p&gt;
&lt;p&gt;단지, 래핑으로 인해 &amp;quot;줄넘김&amp;quot; 화 된 것이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;또한, 버튼의 크기를 보자.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;flex&lt;/code&gt; 선언시, 하위의 엘리먼트들을 기본적으로 &amp;quot;동일한 너비 정렬&amp;quot; 이 된다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;horizontal&lt;/code&gt; 시에는 세로크기 동일화,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;vertical&lt;/code&gt; 시에는, 가로크기 동일화가 수행된다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;간단하게 알아보는 &amp;quot;grid 란?&amp;quot;&lt;/h3&gt;
&lt;p&gt;Flex 가 &amp;quot;1차원 엘리먼트 나열&amp;quot;을 다뤘다면,&lt;/p&gt;
&lt;p&gt;Grid 는 2 차원 (row, column) 을 다룬다.&lt;/p&gt;
&lt;p&gt;grid 는 몇 개의 열이 존재하는지 지정 할 수 있는데,&lt;/p&gt;
&lt;p&gt;예를 들어, x = 6 개의 열이 고정된 grid 라고 한다면,&lt;/p&gt;
&lt;p&gt;grid 가 선언된 블록 내에서 자식 엘리먼트가 6 개가 넘어가면,&lt;/p&gt;
&lt;p&gt;그 다음 엘리먼트부터는 &amp;quot;다음 줄&amp;quot; 부터 시작된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;web.dev 사이트에 존재하는 grid 예시를 조금 참조하여 만들어 본다면,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.container {
    display : grid;
    grid-template-columns : repeat(6, 1fr);
    gap : 1rem;
}
div {
    border : 2px solid black;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body class=&amp;quot;container&amp;quot;&amp;gt;
    &amp;lt;div&amp;gt;
        Box 1
    &amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;
        Box 2
    &amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;
        Box 3
    &amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;
        Box 4
    &amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;
        Box 5
    &amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;
        Box 6
    &amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;
        Box 7
    &amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;
        Box 8
    &amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;
        Box 9
    &amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;
        Box 10
    &amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;
        Box 11
    &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;iframe
    width=&quot;40%&quot;
    max-height=&quot;10rem&quot;
    srcdoc='
    &lt;style&gt;
        body {
            background : white;
        }
        .container {
            display : grid;
            grid-template-columns : repeat(6, 1fr);
            gap : 1rem;
        }
        div {
            border : 2px solid black;
        }
    &lt;/style&gt;
    &lt;body class=&quot;container&quot;&gt;
        &lt;div&gt;
            Box 1
        &lt;/div&gt;
        &lt;div&gt;
            Box 2
        &lt;/div&gt;
        &lt;div&gt;
            Box 3
        &lt;/div&gt;
        &lt;div&gt;
            Box 4
        &lt;/div&gt;
        &lt;div&gt;
            Box 5
        &lt;/div&gt;
        &lt;div&gt;
            Box 6
        &lt;/div&gt;
        &lt;div&gt;
            Box 7
        &lt;/div&gt;
        &lt;div&gt;
            Box 8
        &lt;/div&gt;
        &lt;div&gt;
            Box 9
        &lt;/div&gt;
        &lt;div&gt;
            Box 10
        &lt;/div&gt;
        &lt;div&gt;
            Box 11
        &lt;/div&gt;
    &lt;/body&gt;
    '
&gt;
    &lt;p&gt;이 문구가 보인다면, CSS 예제를 볼 수 없습니다.
&lt;/iframe&gt;

&lt;br/&gt;

&lt;p&gt;위와 같이 영어, 한글이 넣어지는 순서대로 Grid 가 채워지는 것을 볼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;아예 CSS 에 대한 공부 마무리라기보단,&lt;/p&gt;
&lt;p&gt;Flexbox, Grid 에 본격적으로 들어가기 전,&lt;/p&gt;
&lt;p&gt;이 &lt;code&gt;display&lt;/code&gt; 라는 속성이 CSS 를 통해 DOM 스타일링에&lt;/p&gt;
&lt;p&gt;얼마나 큰 영향을 주는지 배우고 싶었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;display&lt;/code&gt; 라는 속성은, 결국 Layout 과 거의 비슷한 말이다.&lt;/p&gt;
&lt;p&gt;&amp;quot;내부 엘리먼트를 어떠한 방식으로 표현 할 것인가?&amp;quot; 를 지정한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;내부 엘리먼트가 1차원 나열 형태로 표현 될 것인가?&lt;/p&gt;
&lt;p&gt;아니면 다차원 형태로 표현 될 것인가에 따라, &lt;code&gt;display&lt;/code&gt; 는 달라진다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;이 글을 작성하며 느낀 점.&lt;/h3&gt;
&lt;p&gt;스타일링을 못하는 내가,&lt;/p&gt;
&lt;p&gt;이 글을 작성하기 전에 작성한 CSS 기초부터,&lt;/p&gt;
&lt;p&gt;display 에 대한 공식문서 자료를 살펴 본 후의 분석 글도 적어보았다.&lt;/p&gt;
&lt;p&gt;내가 미적 감각이 떨어져서 스타일링을 잘 못하고, CSS 영역에서 부족 한 줄 알았는데,&lt;/p&gt;
&lt;p&gt;알고 보니 충분히 공부하지 않았기에, 알지 못하여 관심을 느끼지 못한 것이었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이러한 생각이 들 정도로, CSS 는 간단한 영역은 아니다.&lt;/p&gt;
&lt;p&gt;논리에 치중된 프로그래밍 언어도 제각각의 Convention 을 가지고 있다.&lt;/p&gt;
&lt;p&gt;그러나 CSS 는 논리 비중이 적은 편이며,&lt;/p&gt;
&lt;p&gt;대부분의 경우 Convention 에 초점이 맞춰져 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;어디 공식문서를 추천하냐면&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이 분야에 대해서는, &lt;a target=&quot;_blank&quot; href=&quot;https://web.dev/&quot;&gt;web.dev&lt;/a&gt; 사이트를 추천한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;아직 공부해야 할 부분이 매우 많다.&lt;/p&gt;
&lt;p&gt;특히, CSS 에서 사용되는 일종의 함수들이 존재하는데,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;var()&lt;/code&gt;, &lt;code&gt;repeat(...)&lt;/code&gt;, ... 등등이 존재한다.&lt;/p&gt;
&lt;p&gt;이러한 함수가 어떻게 동작하는지,&lt;/p&gt;
&lt;p&gt;그리고 Flex, Grid 에 대해서도 더 공부해야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;h2&gt;참조 사이트&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;MDN 공식 문서 - (display-CSS)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/display&quot;&gt;https://developer.mozilla.org/en-US/docs/Web/CSS/display&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;web.dev (사실상 구글) 공식 문서&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://web.dev/learn/css?hl=ko&quot;&gt;https://web.dev/learn/css?hl=ko&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Game - (FlexBox Froggy)&lt;/strong&gt; --&amp;gt; flex 익히기 좋음&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://flexboxfroggy.com/#ko&quot;&gt;https://flexboxfroggy.com/#ko&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Game - (Grid Garden)&lt;/strong&gt; --&amp;gt; grid 익히기 좋음&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;</description>
      <category>Web-Server/웹 지식</category>
      <category>CSS</category>
      <category>css display 란</category>
      <category>css display 속성</category>
      <category>css flex</category>
      <category>css grid</category>
      <category>css 박스</category>
      <category>Display</category>
      <category>display block</category>
      <category>display flex</category>
      <category>flexbox</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/238</guid>
      <comments>https://codecreature.tistory.com/238#entry238comment</comments>
      <pubDate>Fri, 17 Oct 2025 22:35:57 +0900</pubDate>
    </item>
    <item>
      <title>미리 알아두면 좋았을 CSS 기초 및 응용 예제</title>
      <link>https://codecreature.tistory.com/237</link>
      <description>&lt;h2&gt;제목 : 미리 알아두면 좋았을 CSS 기초 및 응용예제&lt;/h2&gt;
&lt;h3&gt;부제 : 다시 처음부터 배우기 시작하는 CSS&lt;/h3&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;이 글을 작성하는 이유&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;나는 React 를 통해서 웹 제작을 배우기 시작했다.&lt;/p&gt;
&lt;p&gt;HTML, CSS, JS 의 기초적인 지식보다 먼저 배우게 된 React 강의는&lt;/p&gt;
&lt;p&gt;뒤이어 알려주게 된 &lt;code&gt;SCSS&lt;/code&gt; or &lt;code&gt;SASS&lt;/code&gt; 라고 불리는 스타일링 파일을 가르쳐 주었다.&lt;/p&gt;
&lt;p&gt;당연히 강사에게 잘못은 없지만, 이 파일이 진정한 CSS 형태가 아니며,&lt;/p&gt;
&lt;p&gt;편의를 위해 개발되었으며, TypeScript 처럼 &amp;quot;트랜스파일링&amp;quot; 이 필요한 파일 이라는 것은&lt;/p&gt;
&lt;p&gt;생각보다 오랜 시간이 지난 이후에 알게 되었다.&lt;/p&gt;
&lt;p&gt;나는 CSS 에 대해서는 잘 알지 못한다. DOM 이라는 것을 문서로 배우며, CSSOM 이라는&lt;/p&gt;
&lt;p&gt;스타일링 객체 모델이 존재한다는 것을 알게 되었지만, 이는 결국 지식으로만 알 뿐,&lt;/p&gt;
&lt;p&gt;특정 컴포넌트를 직관적으로 잘 작성하는 것은 하지 못한다.&lt;/p&gt;
&lt;p&gt;따라서, 나는 CSS 의 기초부터 다시 닦으려고 한다.&lt;/p&gt;
&lt;p&gt;이러한 행동은 모래성으로 기반을 쌓는 것이 아니라, 콘크리트로 굳히려는 일종의 행동이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;나의 기술적인 약점&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;나는 C 언어를 통한 Low-Level 의 시스템 조작,&lt;/p&gt;
&lt;p&gt;심지어는 Webpack 이나 Babel 이라는 방법론이 개발자에게 어떤 이득을 전달해주는지&lt;/p&gt;
&lt;p&gt;분석하여 글까지 쓰는 사람이다.&lt;/p&gt;
&lt;p&gt;즉, 나는 특정 기술이 &amp;quot;왜&amp;quot; 사용되고 있는지, 이 프로세스를 이해하는 것에 즐거움을 느낀다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, 나의 큰 약점은, &lt;strong&gt;사용자에게 보여주기 위한 스타일링을 어떻게 해야 하는지 모른다&lt;/strong&gt; 는 것이다.&lt;/p&gt;
&lt;p&gt;이러한 약점을 그냥 &amp;quot;재능이 없어서&amp;quot; 혹은 &amp;quot;감각이 없어서&amp;quot; 와 같이 뭉퉁그려 툭 던질 수 있지만,&lt;/p&gt;
&lt;p&gt;좀 더 본질적인 이유는, &lt;strong&gt;&amp;quot;재미가 없어서&amp;quot;&lt;/strong&gt; 일 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;왜 재미가 없을까?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;어떠한 행동 자체에 &amp;quot;재미가 없다&amp;quot; 라는 것은 다양한 의미를 내포한다고 생각한다.&lt;/p&gt;
&lt;p&gt;내가 특정 카테고리에 속하는 행동을 잘 못하기 때문일 수도 있고,&lt;/p&gt;
&lt;p&gt;내가 원하는 행동에 속하지 않기 때문일 수도 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, 나는 조금 다르게 생각한다.&lt;/p&gt;
&lt;p&gt;&amp;quot;나&amp;quot; 나, &amp;quot;사람들&amp;quot; 의 행동이나 대화 주제를 살펴보았을 때,&lt;/p&gt;
&lt;p&gt;어떠한 대화나 행동이 &amp;quot;재미가 없다&amp;quot; 라는 상황이 나오려면, 대부분 하나로 귀결된다고 지금 깨닫는다.&lt;/p&gt;
&lt;p&gt;그 이유는, &lt;strong&gt;&amp;quot;내가 모르기 때문&amp;quot;&lt;/strong&gt; 이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;&amp;quot;모르는 것과 재미가 무슨 상관?&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;나는 컴퓨터를 배우면서 &amp;quot;흥미&amp;quot; 라는 것이 다각적으로 다가올 수 있다는 것을 깨닫았다.&lt;/p&gt;
&lt;p&gt;대학교에서 처음 컴퓨터 언어를 배울 때, C 언어를 배웠다.&lt;/p&gt;
&lt;p&gt;지금이야 &lt;code&gt;python&lt;/code&gt; 이나, &lt;code&gt;JavaScript&lt;/code&gt; 로 시작하는 경우가 많기 때문에,&lt;/p&gt;
&lt;p&gt;진입 장벽의 낮아짐과 더불어 낮은 수준의 목적을 이루기에는 최적화 되어 흥미를 얻기가 쉽다.&lt;/p&gt;
&lt;p&gt;어떠한 목적도 흥미도 없는 채로, 어려운 C 언어를 이용하여 &amp;quot;포인터&amp;quot;, &amp;quot;레퍼런스&amp;quot; &amp;quot;루프&amp;quot;, &amp;quot;메모리&amp;quot; &amp;quot;에러&amp;quot; 등등..&lt;/p&gt;
&lt;p&gt;이러한 단어들을 들었을 때, 흥미는 커녕 어떠한 목적의식도 얻지 못했다.&lt;/p&gt;
&lt;p&gt;처음 들었던 C 언어 강의에서는 아마 D 나 C+ 를 받았던 것으로 기억한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;낮은 학점에 20살로 빠르게 군대를 갔다오며, 1년간의 휴학과 그 동안의 짧은 창업을 겪은 결과,&lt;/p&gt;
&lt;p&gt;컴퓨터를 통한 모든 시스템 로직의 세상 그 자체가 무한대에 가까워 제한이 없는 영역이라는 것을 알게 되었다.&lt;/p&gt;
&lt;p&gt;흥미는 생겨났지만, 그래도 컴퓨터 자체가 재밌지는 않았다. 그저 컴퓨터의 무한한 가능성에 흥미가 생겨났을 뿐이었다.&lt;/p&gt;
&lt;p&gt;대학교에서 가르치던 이론만을 떠나, 오픈소스를 겉핥기식으로 배우고, 그저 강의에서 알려주는 대로 따라하기 바빴다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;만약에 이 글을 읽고 있는 분이 계시다면 이런 생각을 해 주었으면 좋겠다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;왜 Docker 를 써야 하는가?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;왜 JavaScript 는 가장 인기가 많을까?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;왜 TypeScript 를 작성해야 하나?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;React 를 사용하는데 왜 JSX 를 사용해야 하나?&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;이러한 궁금증은 그저 넘길 수 밖에 없었던 것이, 새로 알려주는 지식들도 집어넣기 매우 바빴다.&lt;/p&gt;
&lt;p&gt;내일은 Docker 와 Dockerfile 을 배운다는데,&lt;/p&gt;
&lt;p&gt;왜 React 는 JSX or TSX 를 사용하는지, 빌드 후에는 왜 오래된 문법으로 바뀌는지&lt;/p&gt;
&lt;p&gt;이런 것을 조사할 시간이 없다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러다 결국, 대학교 시절과 프로그래머스 부트캠프를 종료한 후,&lt;/p&gt;
&lt;p&gt;나는 나만의 규칙 하나를 정했다.&lt;/p&gt;
&lt;p&gt;모르는 방법론이 존재한다면, 이를 분석하고 이해해야 한다.&lt;/p&gt;
&lt;p&gt;방법론이라는 것은 매우 추상적인 개념이라서, 내가 정하는 것과도 비슷하다.&lt;/p&gt;
&lt;p&gt;React 는 방법론이다. (내가 정한 개념)&lt;/p&gt;
&lt;p&gt;JSX 는 방법론이다. (내가 정한 개념)&lt;/p&gt;
&lt;p&gt;Webpack 은 방법론이다. (내가 정한 개념)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;최대한 배우는 영역 내에서 모르는 것을 습득하여 분석하고, 글로 남기기도 하는 이 과정은&lt;/p&gt;
&lt;p&gt;결코 빠른 학습을 기대 할 수 없으며, 임시적으로 세운 배움의 기준 또한 시시각각 변하기도 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;그러나,&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;내가 세운 기준에 의해 배우는 지식과 자기주도적인 분석은, 그토록 찾았던 컴퓨터에 대한&lt;/p&gt;
&lt;p&gt;진정한 흥미와 호기심을 찾는데 성공하게 해 주었다.&lt;/p&gt;
&lt;p&gt;이 덕분에 공식 문서를 보는 속도(독해력) 뿐만 아니라, 오픈소스를 파헤쳐 분석할 수 있을 정도로&lt;/p&gt;
&lt;p&gt;집요함 또한 생겨나게 되었다. 내가 흥미가 있고, 궁금하기 때문이다!&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;이번에는,&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;내가 HTML 정식 스펙상에 존재하는 대부분의 DOM 태그를 다룬 적이 있었다.&lt;/p&gt;
&lt;p&gt;그건 각각의 글이 5 편까지 작성되어 있다.&lt;/p&gt;
&lt;p&gt;마지막 글 :&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/222&quot;&gt;https://codecreature.tistory.com/222&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;나는 CSS 를 &lt;strong&gt;알지 못했으므로&lt;/strong&gt; 흥미를 느끼지 못했다고 생각한다. 그러나, 자연스레 스타일링에도 흥미가 없다고 생각한다.&lt;/p&gt;
&lt;p&gt;이번에는 기초부터 다시 학습하여, 기존에 알던 지식을 두뇌에서 재정립 하려고 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;안타깝게도 내가 공부 및 학습하는 방식은 &amp;quot;취업&amp;quot; 그 자체에는 좋은 영향을 주지 못할 것이라고 판단한다.&lt;/p&gt;
&lt;p&gt;아마 빠르게 변화하는 트렌드 속을 따라오기 힘들것이라고 생각 할 것이다.&lt;/p&gt;
&lt;p&gt;C, Java, JavaScript 등 언어들도 되게 다양하게 사용하고,&lt;/p&gt;
&lt;p&gt;Low-Middle 레벨인 C, Java 에서 어려운 자료구조들도 직접 만들어 알고리즘 문제를 풀기도 했다.&lt;/p&gt;
&lt;p&gt;이러한 경향이 블로그 글이나 깃허브에서 자주 보이므로,&lt;/p&gt;
&lt;p&gt;특정 도메인의 작업을 수행하고 있는 전문가에게는 현재 일에 적합하지 않은 인재로 판단되기 쉽다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, 나는 반대로 이런 질문을 던지고 싶다.&lt;/p&gt;
&lt;p&gt;빠른 학습과 트렌드에 대한 이해라는 것은 &amp;quot;새로운 도구를 잘 이용하는 사람&amp;quot; 을 의미하는가?&lt;/p&gt;
&lt;p&gt;&amp;quot;새로운 도구를 이해하는 사람&amp;quot; 을 의미하는가?&lt;/p&gt;
&lt;p&gt;물론 나는 취업 시장이 그 중도에 위치한 사람을 찾고 있다고 생각한다.&lt;/p&gt;
&lt;p&gt;그러나, 나는 취업 시장이 이야기하는 빠른 트렌드에 대한 습득에 대해서 시각이 다르다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;공식 문서와 기술 문서를 쓰고 있는 나의 현 시점에서,&lt;/p&gt;
&lt;p&gt;신기술과 트렌드를 따라가기 위한 가장 중요한 요소는 &amp;quot;왜?&amp;quot; 라는 의문이 가장 크다고 생각한다.&lt;/p&gt;
&lt;p&gt;솔직히 프로그래머로서 구직활동을 하고 있는 내가 할 말은 아니긴 하지만,&lt;/p&gt;
&lt;p&gt;&amp;quot;지금 Next.js 가 뜨고 있으며, 구직활동 하기에 Next.js 가 좋아&amp;quot; 라는 이유로&lt;/p&gt;
&lt;p&gt;해당 프레임워크를 배우는 것을 너무나 많이 보았다. (Next.js 는 예시)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그리고 나는 홀로 생각을 해보았다.&lt;/p&gt;
&lt;p&gt;나의 꿈은 컴퓨터 엔지니어로서 사람들이 사용할 프로그램 혹은 무언가를 만드는 것이 좋다.&lt;/p&gt;
&lt;p&gt;그게 Low Level 이던, High Level 이던간에.&lt;/p&gt;
&lt;p&gt;심지어는 집에 버려져 있던 발판이었던 나무 원판을 이용해서 책상도 만든 적도 있다.&lt;/p&gt;
&lt;p&gt;지금은 어머니가 잘 사용하고 계신다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;내가 사람들이 사용할 프로그램을 만들기 위해서, 빠른 트렌드에 집중할 지,&lt;/p&gt;
&lt;p&gt;아니면 흘려 보냈던 기초 지식과 &amp;quot;방법론&amp;quot; 이라는 시각에 도전할지 고민했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;트렌드 기술에 및 도구에 능통한 사람이 사람이 기초 지식에 다가가는 것이 쉬울까?&lt;/p&gt;
&lt;p&gt;아니면 기초 지식에 능통한 사람이 트렌드 기술 및 도구에 다가가는 것이 쉬울까?&lt;/p&gt;
&lt;p&gt;아무리 생각해도 후자인 기초 지식에 능통한 사람이 유리하다고 생각되었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;말이 많이 길어졌는데,&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;결국 내가 CSS 를 가장 효과적으로 이해하고, 흥미를 느끼며,&lt;/p&gt;
&lt;p&gt;좋은 디자인을 만들기 위한 하나의 길로 CSS 를 기초부터 닦기로 결정했다.&lt;/p&gt;
&lt;p&gt;결국 내가 CSS 를 못하고, 흥미를 못하는 것은, == 내가 CSS 를 잘 모르기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;사실, 내가 말한 정의에 따르면, CSS 또한 &amp;quot;방법론&amp;quot; 으로 치부할 수도 있다.&lt;/p&gt;
&lt;p&gt;그러나, CSS 는 브라우저에서의 스타일링 도메인에서는 C 언어와 동일하게 사용되고 있다.&lt;/p&gt;
&lt;p&gt;그리고 CSS 의 단점을 극복하기 위해 파생 스타일링 확장자들이 탄생했으니,&lt;/p&gt;
&lt;p&gt;CSS 는 기초로서 시작하는 것이 딱 맞다고 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;CSS 란 무엇인가?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;CSS&lt;/strong&gt; 란, Cascading Style Sheets 의 약자이다.&lt;/p&gt;
&lt;p&gt;이를 직역하면 종속하는 스타일 시트라고 읽혀지는데,&lt;/p&gt;
&lt;p&gt;즉 HTML 파일의 스타일은 이 시트에 종속됨을 알린다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;현재는 CSS 를 통해 다채로운 애니메이션과 Interaction 효과를 볼 수 있지만,&lt;/p&gt;
&lt;p&gt;1990 ~ 2000 년대만 해도, CSS 가 존재했음에도 불구하고 다채로운 애니메이션은 볼 수 없다.&lt;/p&gt;
&lt;p&gt;그 때 기억나는게 window 98 을 사용하고 있었는데, 2006년쯤?&lt;/p&gt;
&lt;p&gt;인터넷을 키면 상대적으로 많은 리소스를 차지함으로 인해서 컴퓨터가 영원히 기다리거나,&lt;/p&gt;
&lt;p&gt;자꾸 꺼져서, 모든 프로그램을 꺼 둔 상태로, 인터넷을 켰던 기억이 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그 때 봤던 인터넷은 주로 날 것의 HTML 이었으며,&lt;/p&gt;
&lt;p&gt;일방적인 정보의 전달 (인터넷 --&amp;gt; 나) 이 주 목적을 이뤘다.&lt;/p&gt;
&lt;p&gt;하드웨어가 지금과는 비교할 수 없을 정도로 성능이 낮았기 때문에,&lt;/p&gt;
&lt;p&gt;성능에 부하가 올 수 있는 스타일링은 최대한 자제하는 성향이 주를 이루었다.&lt;/p&gt;
&lt;p&gt;대신에, 브라우저에서 기본으로 제공하는 정식 태그, &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;section&amp;gt;&lt;/code&gt; 과 같은&lt;/p&gt;
&lt;p&gt;기본 태그들로 충실히 채워놓았다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, 하드웨어가 발달하며 좀 더 직관적인 UI/UX 가 발달할 수 있는 상황이 되었다.&lt;/p&gt;
&lt;p&gt;기존에는 성능 최적화를 위해 Low Level 프로세스가 필요했다면,&lt;/p&gt;
&lt;p&gt;Low Level 만큼은 아니겠지만, 최적화가 되어 있는 스타일 파일, &lt;code&gt;CSS&lt;/code&gt; 가 더욱 사용되었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;요약하자면 CSS 는 주어진 HTML 파일의 태그를 꾸며주는 역할을 하는데,&lt;/p&gt;
&lt;p&gt;CSS 가 어떻게 작성되느냐에 따라서 각 페이지는 전혀 다른 형태의 느낌을 줄 수 있다.&lt;/p&gt;
&lt;p&gt;CSS 는 HTML 태그가 어떻게 정렬되어야 하는지, 어떤 블록 형태를 가지는지,&lt;/p&gt;
&lt;p&gt;얼마나 큰 영역을 가지는지, 색상은 어떻게 되어야 하는지 등등..&lt;/p&gt;
&lt;p&gt;이러한 스타일 지시 자체를 &lt;code&gt;DOM&lt;/code&gt; 의 시각에서 풀어내는 Style 언어라고 할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;CSS 의 문법&lt;/h3&gt;
&lt;p&gt;CSS 의 문법은 일반적인 프로그래밍 언어(Low ~ High) 와는 다르다.&lt;/p&gt;
&lt;p&gt;그러나, 그 규칙 자체가 많지 않아서 단순하다고는 말할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;아마 이 글을 읽고 있는 독자들은 대부분 영어권 나라에서 태어난 분들은 아닐 것이다.&lt;/p&gt;
&lt;p&gt;나 또한 그러한데, CSS 의 속성 이름, 속성 값은 영어권 사람에게 배우기 유리하다는 인상을 준다.&lt;/p&gt;
&lt;p&gt;그러나, 조금이라도 CSS 를 유리하게 배울 수 있는 시각이 있다면,&lt;/p&gt;
&lt;p&gt;CSS 는 DOM (HTML 태그 혹은 선택자) 를 기준으로 꾸며준다 라는 것이다.&lt;/p&gt;
&lt;p&gt;CSS 가 어떤 문법을 가지는지 알아보기 전에,&lt;/p&gt;
&lt;p&gt;이 글에서 어떻게 CSS 에 대한 예시를 보여줄 것인지 보자.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;CSS 예시는 어떻게 보여줄 것인가?&lt;/h2&gt;
&lt;p&gt;일단, 나의 블로그는 Markdown 문서가 파싱된 HTML 태그 결과물에 대해서&lt;/p&gt;
&lt;p&gt;읽기 편하도록 미리 CSS 를 지정 해 놓았으므로, (커스텀을 해 놓음)&lt;/p&gt;
&lt;p&gt;이 페이지에 CSS 를 넣어서 완성하는 것은 오류의 위험성이 매우 크다.&lt;/p&gt;
&lt;p&gt;따라서, &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; 태그를 이용하여 예시를 즉석으로 보이기로 했다.&lt;/p&gt;
&lt;p&gt;전에 HTML 의 모든 태그를 다루면서 &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; 을 알게 되었는데,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;src&lt;/code&gt; 속성에 페이지 위치를 넣어서 파일을 요청할 수도 있지만,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;srcdoc&lt;/code&gt; 속성에 &amp;quot;직접&amp;quot; HTML 파일을 작성하여 &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; 을 작성 할 수도 있다.&lt;/p&gt;
&lt;p&gt;그러나, 이 방식은 보안을 위해 잠겨져 있는 경우가 있어 테스팅 해 보았다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;다행히 티스토리 블로그의 Article 영역이 &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; 을 허락해서 다행이라고 생각한다.&lt;/p&gt;
&lt;p&gt;보안을 위해 제한을 걸어놓았을 경우가 존재하기 때문이다.&lt;/p&gt;
&lt;p&gt;내가 현재 작성하게 될 &lt;code&gt;iframe&lt;/code&gt; 의 기본 템플릿은,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;iframe srcdoc=&amp;quot;
    &amp;lt;style&amp;gt;
        body {
            background : white;
        }
    &amp;lt;/style&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;p&amp;gt;hello1&amp;lt;/p&amp;gt;
        &amp;lt;/p&amp;gt;hello2&amp;lt;/p&amp;gt;
    &amp;lt;/body&amp;gt;
    &amp;quot;
    width=&amp;quot;600&amp;quot;
    height=&amp;quot;400&amp;quot;
&amp;gt;
    &amp;lt;p&amp;gt;안되면 이게 보임&amp;lt;/p&amp;gt;
&amp;lt;/iframe&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이러한 방식으로 작성하게 될 것이다.&lt;/p&gt;
&lt;p&gt;위의 예제는 밑과 같이 보인다.&lt;/p&gt;
&lt;iframe srcdoc=&quot;
    &lt;style&gt;
        body {
            background : white;
        }
    &lt;/style&gt;
    &lt;body&gt;
        &lt;p&gt;hello1&lt;/p&gt;
        &lt;/p&gt;hello2&lt;/p&gt;
    &lt;/body&gt;
    &quot;
    width=&quot;600&quot;
    height=&quot;400&quot;
&gt;
    &lt;p&gt;안되면 이게 보임&lt;/p&gt;
&lt;/iframe&gt;

&lt;br/&gt;

&lt;p&gt;예제의 스타일 길이에 따라 &lt;code&gt;width &amp;amp;&amp;amp; height&lt;/code&gt; 속성은 달라질 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;CSS 의 기본 구조 분해&lt;/h3&gt;
&lt;p&gt;아주 기본적인 CSS 의 문법은 밑과 같이 표현 할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;
Selector  |    Declaration   |     Declaration

   h1 {     color : skyblue;   font-weight : bold;   }

         &amp;quot;property&amp;quot; : &amp;quot;value&amp;quot; | &amp;quot;property&amp;quot; : &amp;quot;value&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 스타일이 HTML 스타일 시트로 들어갈 경우,&lt;/p&gt;
&lt;p&gt;HTML 파일 내부의 &lt;code&gt;h1&lt;/code&gt; 태그 내부의 텍스트들은&lt;/p&gt;
&lt;p&gt;전부 &lt;code&gt;skyblue&lt;/code&gt; 색상을 가지며, &lt;code&gt;bold&lt;/code&gt; 한 폰트 두께를 가진다.&lt;/p&gt;
&lt;br/&gt;

&lt;ul&gt;
&lt;li&gt;선택자는 앞에 존재하며, 여러 개가 동시에 올 수 있다. EX - &lt;code&gt;h1 h2 {...}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ul&gt;
&lt;li&gt;한 번의 선언이 끝날 때 마다, &lt;code&gt;;&lt;/code&gt; 마침을 통해 &amp;quot;속성&amp;quot; : &amp;quot;값&amp;quot; 선언줄이 끝남을 알린다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ul&gt;
&lt;li&gt;대괄호 &lt;code&gt;{&lt;/code&gt;, &lt;code&gt;}&lt;/code&gt; 를 꼭 사용해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;이러한 간단한 규칙을 이해하고 있어도 앞으로 CSS 가 난잡해 지는 것이,&lt;/p&gt;
&lt;p&gt;Styling 이라는 Domain 내부에서 단순히 색상, 텍스트 크기만을 정하는 것이 아니기 때문이다.&lt;/p&gt;
&lt;p&gt;이와 관련된 중요한 속성과 값을 알아야 하며, 이를 이해해야 제대로 CSS 를 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;나도 CSS 를 잘 사용하지 못한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;CSS 선택자&lt;/h3&gt;
&lt;p&gt;CSS 는 하나 이상의 DOM 을 한번에 스타일링 할 수 있다.&lt;/p&gt;
&lt;p&gt;이는 단순 파일에서 그치는 것이 아니라, 한 번의 선언에서 스타일링 할 수도 있다.&lt;/p&gt;
&lt;p&gt;이는 수많은 HTML 태그 중 비슷한 성격의 태그들을 뭉쳐서 같은 스타일로 만들 수 있다.&lt;/p&gt;
&lt;p&gt;주로 HTML 스타일링 템플릿에서 비슷한 성격의 선언들을 볼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그래서, 선택자를 먼저 봐 보자.&lt;/p&gt;
&lt;p&gt;위에서 말했듯이 &lt;code&gt;h1&lt;/code&gt; 으로 하나의 태그를 선택할 수 있으며,&lt;/p&gt;
&lt;p&gt;혹은 두 개 이상의 태그를 한 번에 스타일링 할 수 있다.&lt;/p&gt;
&lt;p&gt;이에 대한 예제를 보자.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;HTML 일반 태그를 선택했을 경우&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;h2 {
    background : skyblue;
}

article, section {
    background : #555;
}&lt;/code&gt;&lt;/pre&gt;
&lt;iframe
    srcdoc='
    &lt;style&gt;
        body {
            background : white;
        }
        h2 {
            background : skyblue;
        }
        article, section {
            background : #555;
        }
    &lt;/style&gt;
    &lt;body&gt;
        &lt;h2&gt;HeadLine 2&lt;/h2&gt;
        &lt;br/&gt;
        &lt;article&gt;
            아티클 영역 - article
        &lt;/article&gt;
        &lt;br/&gt;
        &lt;section&gt;
            섹션 영역 - section
        &lt;/section&gt;
    &lt;/body&gt;
    '
    width=&quot;1000&quot;
    height=&quot;800&quot;
&gt;
    &lt;p&gt;iframe 이 뜨지 않는다면 이 문구가 뜹니다.&lt;/p&gt;
&lt;/iframe&gt;

&lt;br/&gt;

&lt;h4&gt;class 속성을 선택 할 경우&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;CSS&lt;/code&gt; 에서 HTML 일반 태그를 선택 할 경우,&lt;/p&gt;
&lt;p&gt;그 태그를 그대로 넣어서 스타일링 하면 된다.&lt;/p&gt;
&lt;p&gt;그러나, &lt;code&gt;class&lt;/code&gt; 속성을 선택 할 경우, 앞에 &lt;code&gt;.&lt;/code&gt; 하나가 붙는다.&lt;/p&gt;
&lt;p&gt;예를 들어, 컨텐츠를 담는 특정 태그를 &lt;code&gt;container&lt;/code&gt; 라는 클래스 속성으로 지정 해 보자.&lt;/p&gt;
&lt;p&gt;그렇다면, &lt;code&gt;&amp;lt;div class=&amp;quot;container&amp;quot;&amp;gt;....&amp;lt;/div&amp;gt;&lt;/code&gt; 로 표현한다.&lt;/p&gt;
&lt;br/&gt;

&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.container {
    background : #aaa;
    text-align : center;
}

/* 일반 HTML 태그 */
span {
    background : pink;
}&lt;/code&gt;&lt;/pre&gt;
&lt;iframe
    srcdoc='
    &lt;style&gt;
        body {
            background : white;
        }
        .container {
            background : #aaa;
            text-align : center;
        }
        /* 일반 HTML 태그 */
        span {
            background : pink;
        }
    &lt;/style&gt;
    &lt;body&gt;
        &lt;div
            class=&quot;container&quot;
        &gt;
            .container 입니다.
            &lt;br/&gt;
            &lt;span&gt;span 텍스트 영역입니다.&lt;/span&gt;
        &lt;/div&gt;
    &lt;/body&gt;
    '
    width=&quot;1000&quot;
    height=&quot;800&quot;
&gt;
    &lt;p&gt;iframe 이 뜨지 않는다면 이 문구가 뜹니다.&lt;/p&gt;
&lt;/iframe&gt;

&lt;br/&gt;

&lt;p&gt;당연히, 클래스를 선택 할 때, &lt;code&gt;.container, ...&lt;/code&gt; 로 지정이 가능하다.&lt;/p&gt;
&lt;p&gt;클래스 이름을 만들 때 주로 사용되는 Naming Convention 을 배우는 것이 좋겠다고 생각이 드는게,&lt;/p&gt;
&lt;p&gt;&amp;quot;컨테이너 내부에 들어가는 엘리먼트는 어떤 클래스 이름을 가지지?&amp;quot;&lt;/p&gt;
&lt;p&gt;라는 의문이 들었다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;id 속성을 선택 할 경우&lt;/h4&gt;
&lt;p&gt;HTML 태그에는 &lt;code&gt;id&lt;/code&gt; 속성이 들어갈 수 있다.&lt;/p&gt;
&lt;p&gt;단, 단일 HTML 파일 내부에서 &lt;code&gt;id&lt;/code&gt; 속성은 상하관계 상관없이,&lt;/p&gt;
&lt;p&gt;페이지 내부에서 &lt;code&gt;Unique&lt;/code&gt; 규칙을 지켜야 한다.&lt;/p&gt;
&lt;p&gt;동일한 값의 &lt;code&gt;id&lt;/code&gt; 는 하나의 HTML 파일에서 존재해서는 안된다는 규칙이 존재한다.&lt;/p&gt;
&lt;p&gt;자유로운 HTML 특성상 렌더링이 될 수는 있겠으나, 콘솔에서는 분명히 경고가 뜰 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;div id=&amp;quot;side&amp;quot;&amp;gt;...&amp;lt;/div&amp;gt;&lt;/code&gt; 라는 DOM 오브젝트가 존재한다고 가정하자.&lt;/p&gt;
&lt;p&gt;이 DOM 을 선택하고 싶다면, &lt;code&gt;#side&lt;/code&gt; 로 선택자를 선언할 수 있다.&lt;/p&gt;
&lt;p&gt;생각보다 &lt;code&gt;id&lt;/code&gt; 속성은 자주 사용되는데,&lt;/p&gt;
&lt;p&gt;중첩된 태그들 내부에서 &amp;quot;유일&amp;quot; 한 기능 or View 를 보여주어야 하는 DOM 들이 있다.&lt;/p&gt;
&lt;p&gt;그러면서, 브라우저의 통신 이후 값을 빠르게 변경해 주어야 한다면,&lt;/p&gt;
&lt;p&gt;이 DOM 을 계단식으로 찾는 것이 아니라, 빠르게 &lt;code&gt;id&lt;/code&gt; 로 찾을 수 있다. (유일하므로)&lt;/p&gt;
&lt;p&gt;하여튼, 이러한 기능으로 &lt;code&gt;id&lt;/code&gt; 를 사용할 수도 있다는 잡 이야기이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;예를 들면, 검색 창이 있을 것 같다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;#user_input {
    text-align : center;
    padding : 10px;
}

.container {
    width : 100%;
    height : 3rem;
    align : center;
    background : green;
}&lt;/code&gt;&lt;/pre&gt;
&lt;iframe
    srcdoc='
    &lt;style&gt;
        body {
            background : white;
        }
        #user_input {
            margin-top : 5px;
            text-align : center;
            padding : 10px;
        }
        .container {
            width : 100%;
            height : 3rem;
            text-align : center;
            background : green;
        }
    &lt;/style&gt;
    &lt;body&gt;
        &lt;div
            class=&quot;container&quot;
        &gt;
            &lt;input
                id=&quot;user_input&quot;
                placeholder=&quot;예시 입력창 (기능 없음)&quot;
            &gt;
        &lt;/div&gt;
    &lt;/body&gt;
    '
    width=&quot;1000&quot;
    height=&quot;800&quot;
&gt;
    &lt;p&gt;iframe 이 뜨지 않는다면 이 문구가 뜹니다.&lt;/p&gt;
&lt;/iframe&gt;

&lt;br/&gt;

&lt;h3&gt;CSS 전체 선택자 의 경우&lt;/h3&gt;
&lt;p&gt;CSS 선택자 중에서 &lt;code&gt;*&lt;/code&gt; 이 존재한다.&lt;/p&gt;
&lt;p&gt;이는 HTML 파일에 존재하는 모든 엘리먼트에 대해서 적용한다는 의미이다.&lt;/p&gt;
&lt;p&gt;이를 통해, 각 브라우저마다 존재하는 Default 스타일이 제각각 다른데,&lt;/p&gt;
&lt;p&gt;직접 &lt;code&gt;* { ... }&lt;/code&gt; 를 선언하여 스타일을 초기화 할 수 있다.&lt;/p&gt;
&lt;p&gt;또 다른 역할로는, 조금 이따 보게 될 결합 선택자로 다양한 스타일링을 추구 할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;* {
    text-align : center;
    padding : 10px;
}

.container1 {
    background : red;
}

.container2 {
    background : green;
}

.container3 {
    background : blue;
}&lt;/code&gt;&lt;/pre&gt;
&lt;iframe
    srcdoc='
    &lt;style&gt;
        body {
            background : white;
        }
        * {
            text-align : center;
            padding : 10px;
        }
        .container1 {
            background : red;
        }
        .container2 {
            background : green;
        }
        .container3 {
            background : blue;
        }
    &lt;/style&gt;
    &lt;body&gt;
        &lt;div class=&quot;container1&quot;&gt;
            container1
            &lt;div class=&quot;container2&quot;&gt;
                container2
                &lt;div class=&quot;container3&quot;&gt;container3&lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/body&gt;
    '
    width=&quot;1000&quot;
    height=&quot;800&quot;
&gt;
    &lt;p&gt;iframe 이 뜨지 않는다면 이 문구가 뜹니다.&lt;/p&gt;
&lt;/iframe&gt;

&lt;br/&gt;

&lt;p&gt;이처럼, 모든 엘리먼트에 단순 중앙 정렬을 요구하니,&lt;/p&gt;
&lt;p&gt;각 계층이 중앙 정렬을 통해 집합처럼 다른 색상을 보이게 만들 수 있다.&lt;/p&gt;
&lt;p&gt;위의 예제는 아이디어고, &lt;code&gt;*.container&lt;/code&gt; 처럼 조합으로 사용한다고 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;CSS 특성 선택자의 경우&lt;/h3&gt;
&lt;p&gt;여기서 말하는 &amp;quot;특성&amp;quot; 은 &lt;code&gt;attribute&lt;/code&gt; 를 의미한다.&lt;/p&gt;
&lt;p&gt;나도 이 선택자는 처음 접해보는데, 활용도가 무궁무진 할 것 같다는 생각이 든다.&lt;/p&gt;
&lt;p&gt;먼저, attr 이란 것은 `&lt;div class=&quot;..&quot; id=&quot;..&quot;&gt; 등등&lt;/p&gt;
&lt;p&gt;&lt;code&gt;class&lt;/code&gt;, &lt;code&gt;id&lt;/code&gt; 를 특성, 즉 Attribute 라고 말할 수 있다.&lt;/p&gt;
&lt;p&gt;단순히 &amp;quot;이 속성(특성) 을 가졌다&amp;quot; 해서 콕 집을 수도 있겠지만,&lt;/p&gt;
&lt;p&gt;MDN 문서의 예제를 보고 그 활용 예제가 많다는 것을 느꼈다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;MDN 문서에서 예시로 준 CSS&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/* &amp;lt;a&amp;gt; elements with a title attribute */
/* &amp;lt;a&amp;gt; 태그에 title 이라는 속성이 &amp;quot;존재&amp;quot; 한다면 */
a[title] {
  color: purple;
}

/* &amp;lt;a&amp;gt; elements with an href matching &amp;quot;https://example.org&amp;quot; */
/* &amp;lt;a&amp;gt; 태그에 href 속성이 존재하는데, 이 속성의 값이 &amp;quot;https://example.org&amp;quot; 라면 */
a[href=&amp;quot;https://example.org&amp;quot;]
{
  color: green;
}

/* &amp;lt;a&amp;gt; elements with an href containing &amp;quot;example&amp;quot; */
/* &amp;lt;a&amp;gt; 태그의 href 가 &amp;quot;example&amp;quot; 을 어떤 형태이던 가지고 있다면. */
a[href*=&amp;quot;example&amp;quot;] {
  font-size: 2em;
}

/* &amp;lt;a&amp;gt; elements with an href ending &amp;quot;.org&amp;quot; */
/* &amp;lt;a&amp;gt; 태그의 href 속성 값이 &amp;quot;.org&amp;quot; 로 끝난다면. */
a[href$=&amp;quot;.org&amp;quot;] {
  font-style: italic;
}

/* &amp;lt;a&amp;gt; elements whose class attribute contains the word &amp;quot;logo&amp;quot; */
/* &amp;lt;a&amp;gt; 태그의 class 속성이 &amp;quot;logo&amp;quot; 를 가지고 있을 때. */
/* 이러한 표현법은, class=&amp;quot;logo large bold&amp;quot; 이러한 방식처럼, 여러 개가 중첩되었을 때 사용이 가능하다. */
a[class~=&amp;quot;logo&amp;quot;] {
  padding: 2px;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이를 다시 재해석하여 내 방식으로 작성한다면,&lt;/p&gt;
&lt;p&gt;본래 문서보다 설명이 떨어질 것이라는 게 내 판단이었다.&lt;/p&gt;
&lt;p&gt;나는 속성 선택자를 처음 배우기 때문이다.&lt;/p&gt;
&lt;p&gt;다시 보면, 속성 선택자를 보더라도, CSS 를 다시 배우길 잘했단 생각도 든다..&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://developer.mozilla.org/ko/docs/Web/CSS/Attribute_selectors&quot;&gt;MDN CSS 특성 선택자 문서&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;이것을 보면 &amp;quot;특성(속성) 선택자&amp;quot; 라는 영역에 대해서 굉장히 자세히 배울 수 있다.&lt;/p&gt;
&lt;p&gt;강의나 책 수준에서는 다루지 않았던 Detail 이 쏟아져 나오는데,&lt;/p&gt;
&lt;p&gt;CSS 단순 지식에서 돌파하기 어려운 복잡한 스타일 구성을&lt;/p&gt;
&lt;p&gt;속성(특성) 선택자를 이용하여 상당수 돌파할 수 있으리라고 생각된다.&lt;/p&gt;
&lt;p&gt;내가 공식 문서를 보며 배우는 것이 이러한 묘미에서 나온 것이 아닐까 생각된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;특성 선택자에 대한 정확한 구문은 밑의 링크를 클릭하면 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://developer.mozilla.org/ko/docs/Web/CSS/Attribute_selectors#:~:text=%EA%B0%99%EC%9D%B4%20%EB%B3%B4%EA%B8%B0-,%EA%B5%AC%EB%AC%B8,-%5Battr%5D&quot;&gt;MDN CSS 특성 선택자 - (구문)&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;나도 처음 사용 해 보는, 속성 선택자를 이용 해 볼 시간이다.&lt;/p&gt;
&lt;p&gt;예를 들어, 이렇게 만들 수 있을 것 같다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;input&lt;/code&gt; 태그에는 &lt;code&gt;placeholder&lt;/code&gt; 라는 속성이 존재한다.&lt;/p&gt;
&lt;p&gt;사이트 사용자가 입력 시 어떤 형태로 입력해야 하는지 예시로 보여주는 역할을 한다.&lt;/p&gt;
&lt;p&gt;간단하게, 이메일 형태가 있을 것이다.&lt;/p&gt;
&lt;p&gt;그리고 사용자의 &amp;quot;별명&amp;quot; 도 입력할 수 있을 것인데,&lt;/p&gt;
&lt;p&gt;여기에는 &lt;code&gt;data-nickname&lt;/code&gt; 이라는 개발자의 HTML 전용 Convention 을 이용해서 작성한다.&lt;/p&gt;
&lt;p&gt;즉, 개발자가 따로 일반 Tag 에 원하는 &amp;quot;속성&amp;quot; 을 생성하고 싶다면,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;data-*&lt;/code&gt; 의 형태로 개발된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;input[placeholder] {
    border-radius : 5px;
    font-size : bold;
    color : pink;
}
input[data-nickname] {
    margin-top : 10px;
    color : blue;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body&amp;gt;
    &amp;lt;input placeholder=&amp;quot;example@example.com&amp;quot;/&amp;gt;
    &amp;lt;br/&amp;gt;
    &amp;lt;input data-nickname/&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;iframe
    srcdoc='
    &lt;style&gt;
        body {
            background : white;
        }
        input[placeholder] {
            border-radius : 5px;
            font-size : bold;
            color : pink;
        }
        input[data-nickname] {
            margin-top : 10px;
            color : blue;
        }
    &lt;/style&gt;
    &lt;body&gt;
        &lt;input placeholder=&quot;example@example.com&quot;/&gt;
        &lt;br/&gt;
        &lt;input data-nickname/&gt;
    &lt;/body&gt;
    '
    width=&quot;1000&quot;
    height=&quot;800&quot;
&gt;
    &lt;p&gt;iframe 이 뜨지 않는다면 이 문구가 뜹니다.&lt;/p&gt;
&lt;/iframe&gt;

&lt;br/&gt;

&lt;p&gt;위의 예제를 보다시피, 동일한 태그인 &lt;code&gt;input&lt;/code&gt; 임에도 불구하고,&lt;/p&gt;
&lt;p&gt;태그에 어떤 속성이 선언되었느냐에 따라 스타일링이 다르게 표출되는 것을 알 수 있다.&lt;/p&gt;
&lt;p&gt;위의 CSS 선언은 어떻게 해석 할 수 있을까?&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;사용자에게 안내가 필요한 부분은(&lt;code&gt;placeholder&lt;/code&gt;)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;입력 칸 모서리 둥글게&lt;/li&gt;
&lt;li&gt;폰트는 두껍게&lt;/li&gt;
&lt;li&gt;텍스트는 핑크색&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;사용자가 &amp;quot;별명&amp;quot; 을 입력하는 칸 (&lt;code&gt;data-nickname&lt;/code&gt; - 개발자의 커스텀 속성 선언시)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;바로 위의 &lt;code&gt;input&lt;/code&gt; 과는 10 픽셀 조금 떨어뜨리고,&lt;/li&gt;
&lt;li&gt;텍스트 색상은 파란색.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;내가 보여준 예제는 MDN 에서 보여준 것과는 다르게 매우 간단하므로,&lt;/p&gt;
&lt;p&gt;이 속성(특성) 선택자를 활용하기 위해서는 위의 다양한 형태에 대해서 읽어볼 필요가 있다.&lt;/p&gt;
&lt;p&gt;특히, 특정 속성에 &amp;quot;어떤 내용을 내포할 때&amp;quot; 적용하는 개별 스타일 &lt;code&gt;a[href*=&amp;quot;example&amp;quot;]&lt;/code&gt; 이라는 예시는&lt;/p&gt;
&lt;p&gt;정말 사용할 데가 많아 보인다.&lt;/p&gt;
&lt;p&gt;뿐만 아니라, 웹을 개발하는 도중에 하나의 태그에 중첩된 클래스 이름을 빈 칸인 &lt;code&gt;&lt;/code&gt; 로 떨어뜨리는 경우가 있는데,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;a[class~=&amp;quot;logo&amp;quot;]&lt;/code&gt; 라는 선택자로 가져오는 것은,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;classnames&lt;/code&gt; 라는 NPM 모듈 대신 선택할 수 있는 대체품으로 보이기까지 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;&amp;amp; 을 사용하는 중첩 선택자의 경우 (&amp;amp; nesting)&lt;/h3&gt;
&lt;p&gt;속성(특성) 선택자를 처음 배운 것 처럼,&lt;/p&gt;
&lt;p&gt;나는 중첩 선택자를 처음 본다. 아주 정확히는, CSS 에서도 되는 지 몰랐다.&lt;/p&gt;
&lt;p&gt;아마 CSS 가 발전하면서 들어간 기능이라고 &amp;quot;예측&amp;quot; 되는데,&lt;/p&gt;
&lt;p&gt;그 이유는 &lt;code&gt;scss&lt;/code&gt;, &lt;code&gt;sass&lt;/code&gt; 처럼 중첩된 형태로 CSS 를 작성할 경우,&lt;/p&gt;
&lt;p&gt;결국 브라우저가 이를 따로 나눈 것과 동일하게 인식하기 떄문이다.&lt;/p&gt;
&lt;p&gt;예를 들어서,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.parent-css {
    /* 부모 스타일링 규칙 */
    .child-css {
        /* 자식 스타일링 규칙 */
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이러한 형태로 CSS 를 작성한다면,&lt;/p&gt;
&lt;p&gt;브라우저가 중첩 선택자를 파싱하는 과정에서, 자동으로 이 선택자 사이에 공백을 추가하여&lt;/p&gt;
&lt;p&gt;선택자 규칙을 생성하기 때문이다.&lt;/p&gt;
&lt;p&gt;위의 중첩 표현식은 이렇게 파싱된다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.parent-css {
    /* 부모 스타일링 규칙 */
}

.parent-css .child-css {
    /* 자식 스타일링 규칙 */

    /* 이 형태는 부모 스타일링 규칙 요소에 대한 자식 요소의 스타일링 규칙을 의미한다. */
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;공백인 &lt;code&gt;&amp;#39; &amp;#39;&lt;/code&gt; 을 사용하여 따로 생성하는데,&lt;/p&gt;
&lt;p&gt;이는 여러 개의 선택자를 한 번에 선언하는 &lt;code&gt;.parent-css, .child-css {...}&lt;/code&gt; 과는 다르다.&lt;/p&gt;
&lt;p&gt;그리고, 위에 선언된 &amp;quot;파싱이 필요없는&amp;quot; CSS 구문은,&lt;/p&gt;
&lt;p&gt;밑의 CSS 와도 동일하다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.parent-css {
    /* 부모 스타일링 */
    &amp;amp; .child-css {
        /* 자식 스타일링 */
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;이는 아무리 봐도 &lt;code&gt;SASS&lt;/code&gt; 문법 같은데, 이런걸 흔히 알고 있나? 궁금해서 AI 에게 질문 해 보았다.&lt;/p&gt;
&lt;p&gt;(Gemini 2.5 Pro)&lt;/p&gt;
&lt;p&gt;그런데, 중첩 선택자는 SASS 에서만 가능하다고 말하는 것이다.&lt;/p&gt;
&lt;p&gt;따라서, MDN 의 정확한 Reference 를 대고 너의 주장이 확실한 것인지,&lt;/p&gt;
&lt;p&gt;아니면 너무 최신의 정보라 너가 인식하지 못한 것인지 물어보니까,&lt;/p&gt;
&lt;p&gt;자신의 정보가 틀렸다고 인정하고, 최신 브라우저에 적용되는 문법이라고 말해 주었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.parent-css {
    background : pink;
    text-align : center;
    width : 250px;
    .child-css {
        background : dodgerblue;
        text-align : center;
        width : 125px;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body&amp;gt;
    &amp;lt;div class=&amp;quot;parent-css&amp;quot;&amp;gt;
        parent &amp;lt;br/&amp;gt;
        &amp;lt;div class=&amp;quot;child-css&amp;quot;&amp;gt;child&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;br/&amp;gt;
    &amp;lt;div class=&amp;quot;child-css&amp;quot;&amp;gt;child&amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;iframe
    srcdoc='
    &lt;style&gt;
        body {
            background : white;
        }
        .parent-css {
            background : pink;
            text-align : center;
            width : 250px;
            .child-css {
                background : dodgerblue;
                text-align : center;
                width : 125px;
            }
        }
    &lt;/style&gt;
    &lt;body&gt;
        &lt;div class=&quot;parent-css&quot;&gt;
            parent &lt;br/&gt;
            &lt;div class=&quot;child-css&quot;&gt;child&lt;/div&gt;
        &lt;/div&gt;
        &lt;br/&gt;
        &lt;div class=&quot;child-css&quot;&gt;child&lt;/div&gt;
    &lt;/body&gt;
    '
    width=&quot;1000&quot;
    height=&quot;800&quot;
&gt;
    &lt;p&gt;iframe 이 뜨지 않는다면 이 문구가 뜹니다.&lt;/p&gt;
&lt;/iframe&gt;

&lt;p&gt;위의 예시를 본다면,&lt;/p&gt;
&lt;p&gt;부모인 &lt;code&gt;parent-css&lt;/code&gt; 클래스가 적용된 component 와,&lt;/p&gt;
&lt;p&gt;자식인 &lt;code&gt;child-css&lt;/code&gt; 클래스가 적용된 component 는 정상적으로 스타일링 되는 것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;그러나, 그 밑에 부모 컴포넌트 없이,&lt;/p&gt;
&lt;p&gt;단순히 &lt;code&gt;child-css&lt;/code&gt; 만이 적용된 컴포넌트는, 그 어떤 스타일링도 되지 않은 것을 볼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;즉, 중첩 선택자로 적용되었을 때,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.child-css&lt;/code&gt; 는 하나만 똑 떼어져서 선언되는 것이 아니라,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.parent-css .child-css&lt;/code&gt; 로 선택자가 생성된다.&lt;/p&gt;
&lt;p&gt;이는, 부모 컴포넌트가 &lt;code&gt;.parent-css&lt;/code&gt; 클래스이며, 자식 컴포넌트가 &lt;code&gt;.child-css&lt;/code&gt; 클래스를 가져야만`&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.child-css&lt;/code&gt; 가 적용될 수 있는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;브라우저가 최신 전처리를 통해 파싱한 정확한 css 는,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.parent-css {
    background : pink;
    text-align : center;
    width : 250px;
}

.parent-css .child-css {
    background : dodgerblue;
    text-align : center;
    width : 125px;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이기 때문이다. 즉, 선택자 중 &lt;code&gt;.child-css&lt;/code&gt; 만 선언되는 경우는 없다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;그렇다면 이 최신 기능은 어떤 시점의 브라우저에서부터 적용되나?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;일단, MDN 의 이 목록을 보는 것이 좋다.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://developer.mozilla.org/ko/docs/Web/CSS/Nesting_selector#%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80_%ED%98%B8%ED%99%98%EC%84%B1:~:text=%23%20nest%2Dselector-,%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80%20%ED%98%B8%ED%99%98%EC%84%B1,-Report%20problems%20with&quot;&gt;중첩 선택자 브라우저 호환성&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;인터넷 브라우저의 대표 주자인 &lt;code&gt;Chrome&lt;/code&gt; 은, &lt;code&gt;2023-12&lt;/code&gt; 에 정식 Release 되었다.&lt;/p&gt;
&lt;p&gt;이는 매우 최신 기능이라고 볼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;중첩 선택자의 중요성&lt;/h4&gt;
&lt;p&gt;우리가 CSS 를 통해 만들게 될 여러 Element 들을 Interaction 하게 꾸밀 수 있다.&lt;/p&gt;
&lt;p&gt;이 때, 우리가 가장 중요하게 사용될 문자는 바로 &lt;code&gt;&amp;amp;&lt;/code&gt; 이다.&lt;/p&gt;
&lt;p&gt;이 문자가 들어가냐, 들어가지 않냐에 따라서 그 의미가 달라지기 때문이다.&lt;/p&gt;
&lt;p&gt;예를 들어 보자.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;유저와 소통하는 듯한 HTML 컴포넌트의 Interaction 을 생각 해 보자.&lt;/p&gt;
&lt;p&gt;이를 위해 HTML 태그에는 &amp;quot;가상 클래스&amp;quot; 라는 것이 존재한다.&lt;/p&gt;
&lt;p&gt;예를 들자면,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;active&lt;/code&gt; &amp;lt;--&amp;gt; &lt;code&gt;disabled&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;focus&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hover&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;예를 들어, 특정 영역 위에 마우스가 올라갔을 때,&lt;/p&gt;
&lt;p&gt;색상 변화 혹은 트랜지션을 통해 인터랙션 UX 를 주는 것이 대표 예시 중 하나라고 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.container {
    text-align : center;
    padding : 10px;
    margin : 5px;
    background : black;
    color : #999;
    &amp;amp;:hover {
        color : #555;
        background : #aaa;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body&amp;gt;
    &amp;lt;div class=&amp;quot;container&amp;quot;&amp;gt;
        container
    &amp;lt;/div&amp;gt;
    &amp;lt;div class=&amp;quot;container&amp;quot;&amp;gt;
        container
    &amp;lt;/div&amp;gt;
    &amp;lt;div class=&amp;quot;container&amp;quot;&amp;gt;
        container
    &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;iframe
    srcdoc='
    &lt;style&gt;
        body {
            background : white;
        }
        .container {
            text-align : center;
            padding : 10px;
            margin : 5px;
            background : black;
            color : #999;
            &amp;:hover {
                color : #555;
                background : #aaa;
            }
        }
    &lt;/style&gt;
    &lt;body&gt;
        &lt;div class=&quot;container&quot;&gt;
            container
        &lt;/div&gt;
        &lt;div class=&quot;container&quot;&gt;
            container
        &lt;/div&gt;
        &lt;div class=&quot;container&quot;&gt;
            container
        &lt;/div&gt;
    &lt;/body&gt;
    '
    width=&quot;1000&quot;
    height=&quot;800&quot;
&gt;
    &lt;p&gt;iframe 이 뜨지 않는다면 이 문구가 뜹니다.&lt;/p&gt;
&lt;/iframe&gt;

&lt;br/&gt;

&lt;p&gt;위의 예제에 마우스를 가져다 대 보면,&lt;/p&gt;
&lt;p&gt;마우스가 올라간 엘리먼트는 마치 Interaction 한 느낌을 줄 수 있다.&lt;/p&gt;
&lt;p&gt;물론, 트랜지션까지 넣은 것은 아니라서, 아직은 미완성적인 느낌을 줄 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그렇다면, &lt;code&gt;&amp;amp;&lt;/code&gt; 를 없애면 어떻게 될까?&lt;/p&gt;
&lt;p&gt;여기가 포인트인데, &lt;code&gt;&amp;amp;:hover&lt;/code&gt; 에서, &lt;code&gt;:hover&lt;/code&gt; 로 배치가 바뀌면 어떻게 될지 보는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그렇게 된다면, &lt;code&gt;container&lt;/code&gt; 클래스 속성을 가진 엘리먼트가 &lt;code&gt;:hover&lt;/code&gt; 가 되었을 때가 아니라,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.container :hover&lt;/code&gt; 로 선언되기 때문에,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;container&lt;/code&gt; 이라는 클래스 속성을 가진 엘리먼트의 &amp;quot;자식 엘리먼트가&amp;quot; &lt;code&gt;:hover&lt;/code&gt; 되었을 때,&lt;/p&gt;
&lt;p&gt;로 변경된다.&lt;/p&gt;
&lt;br/&gt;

&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.container {
    background : dodgerblue;
    margin : 10px;

    :hover {
        background : black;
        color : white;
    }
}

div {
    background : skyblue;
    margin : 5px;
    padding : 10px;
    text-align : center;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body&amp;gt;
    &amp;lt;div class=&amp;quot;container&amp;quot;&amp;gt;
        &amp;lt;div&amp;gt;div 1&amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;div 2&amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;div 3&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;iframe
    srcdoc='
    &lt;style&gt;
        body {
            background : white;
        }
        .container {
            background : dodgerblue;
            margin : 10px;
            :hover {
                background : black;
                color : white;
            }
        }
        div {
            background : skyblue;
            margin : 5px;
            padding : 10px;
            text-align : center;
        }
    &lt;/style&gt;
    &lt;body&gt;
        &lt;div class=&quot;container&quot;&gt;
            &lt;div&gt;div 1&lt;/div&gt;
            &lt;div&gt;div 2&lt;/div&gt;
            &lt;div&gt;div 3&lt;/div&gt;
        &lt;/div&gt;
    &lt;/body&gt;
    '
    width=&quot;1000&quot;
    height=&quot;800&quot;
&gt;
    &lt;p&gt;iframe 이 뜨지 않는다면 이 문구가 뜹니다.&lt;/p&gt;
&lt;/iframe&gt;

&lt;br/&gt;

&lt;p&gt;이 예제의 바로 직전에서 &lt;code&gt;container&lt;/code&gt; 클래스 속성을 &amp;quot;가진&amp;quot; 엘리먼트에 한하여 처리를 했던 반면,&lt;/p&gt;
&lt;p&gt;이 예제는 &lt;code&gt;container&lt;/code&gt; 클래스 속성을 가진 엘리먼트의 &amp;quot;자식&amp;quot; 엘리먼트에 한하여 &lt;code&gt;:hover&lt;/code&gt; 처리를 했다.&lt;/p&gt;
&lt;p&gt;이러한 패턴은 특정 클래스 속성 컴포넌트 내부에서 공통 Interaction 을 지정 할 때 유용할 것이라고 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;중첩 선택자 &amp;quot;&amp;amp;&amp;quot; 에 대한 직관적인 이해&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;중첩 선택자 &lt;code&gt;&amp;amp;&lt;/code&gt; 문자는 CSS 작성에 있어 편리함을 주지만,&lt;/p&gt;
&lt;p&gt;프로그래밍에 있어 직관적인 이해 또한 필요하다고 생각한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;amp;&lt;/code&gt; 는 위와 같은 기능을 가능하게 해 주며,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;amp;&lt;/code&gt; 는 &amp;quot;정확히&amp;quot; 부모 엘리먼트를 의미한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.container {
    background : black;
    color : white;

    &amp;amp; &amp;gt; a {
        color : blue;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 css 는 정확히&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.container {
    background : black;
    color : white;
}

.container &amp;gt; a {
    color : blue;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이렇게 파싱된다.&lt;/p&gt;
&lt;p&gt;괄호 속의 괄호 표현식을 가능하게 해 주는 &lt;code&gt;&amp;amp;&lt;/code&gt; (레퍼런스) 문자는, 이러한 의미를 담고 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;또 다른 예시를 들어 보자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.level-1 {
    &amp;amp;:hover {
        background : red;
    }

    &amp;amp; &amp;gt; .level-2 {
        &amp;amp;:hover {
            background : green;
        }

        &amp;amp; &amp;gt; .level-3 {
            &amp;amp;:hover {
                background : blue;
            }
        }
    }
}

div {
    text-align : center;
    margin : 10px;
    padding : 5px;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기 최상위 &lt;code&gt;level-1&lt;/code&gt; 클래스 속성을 가진 컴포넌트부터,&lt;/p&gt;
&lt;p&gt;최하위 &lt;code&gt;level-3&lt;/code&gt; 클래스 속성을 가지는 컴포넌트를 꾸미는 CSS 가 있다.&lt;/p&gt;
&lt;p&gt;위에서 &lt;code&gt;&amp;amp;&lt;/code&gt; 레퍼런스 문자가 많이 사용되었는데, 이는 어떻게 스타일링될까?&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body&amp;gt;
    &amp;lt;div class=&amp;quot;level-1&amp;quot;&amp;gt;
        level-1
        &amp;lt;div class=&amp;quot;level-2&amp;quot;&amp;gt;
            level-2
            &amp;lt;div class=&amp;quot;level-3&amp;quot;&amp;gt;level-3&amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;iframe
    srcdoc='
    &lt;style&gt;
        body {
            background : white;
        }
        .level-1 {
            &amp;:hover {
                background : red;
            }
            &amp; &gt; .level-2 {
                &amp;:hover {
                    background : green;
                }
                &amp; &gt; .level-3 {
                    &amp;:hover {
                        background : blue;
                    }
                }
            }
        }
        div {
            text-align : center;
            margin : 10px;
            padding : 5px;
        }
    &lt;/style&gt;
    &lt;body&gt;
        &lt;div class=&quot;level-1&quot;&gt;
            level-1
            &lt;div class=&quot;level-2&quot;&gt;
                level-2
                &lt;div class=&quot;level-3&quot;&gt;level-3&lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/body&gt;
    '
    width=&quot;1000&quot;
    height=&quot;800&quot;
&gt;
    &lt;p&gt;iframe 이 뜨지 않는다면 이 문구가 뜹니다.&lt;/p&gt;
&lt;/iframe&gt;

&lt;br/&gt;

&lt;p&gt;위의 예제에 &lt;code&gt;level-1&lt;/code&gt; 부터 &lt;code&gt;level-3&lt;/code&gt; 텍스트까지 호버링 해 보자.&lt;/p&gt;
&lt;p&gt;그렇다면, 위의 &lt;code&gt;CSS&lt;/code&gt; 예제에서 선언된 &lt;code&gt;&amp;amp;&lt;/code&gt; 들은 서로 다른 Scope 를 가졌음을 알 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;amp;&lt;/code&gt;(레퍼런스) 는, 현재 자신을 괄호 &lt;code&gt;{ ... }&lt;/code&gt; 로 담고 있는 선택자를 의미한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;CSS 인접 형제 결합자의 경우 (&amp;quot;+&amp;quot;)&lt;/h3&gt;
&lt;p&gt;이번에는 &amp;quot;동일 계층에 존재하는&amp;quot; DOM 을 위한 스타일링 문법을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;만약에, &lt;code&gt;a + p { ... }&lt;/code&gt; 가 된다면, 이러한 의미를 가진다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;a&lt;/code&gt; 직후 &lt;code&gt;p&lt;/code&gt; 가 들어와야 발동한다.&lt;/li&gt;
&lt;li&gt;위의 선택자 예시는 &lt;code&gt;p&lt;/code&gt; 를 스타일링한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;p&gt;위의 결합자(&lt;code&gt;+&lt;/code&gt;) 라는 것을 어디에 사용할까.. 상상을 해 보니,&lt;/p&gt;
&lt;p&gt;예를 들어 &lt;code&gt;img&lt;/code&gt; 이미지는 이후 설명이 들어가는 경우가 많다.&lt;/p&gt;
&lt;p&gt;따라서, &lt;code&gt;img&lt;/code&gt; 태그 이후 설명에 관한 &lt;code&gt;p&lt;/code&gt; (Paragraph) 문장이 올 경우,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;img + p { ... }&lt;/code&gt; 로 꾸밀 수 있을 것 같다.&lt;/p&gt;
&lt;p&gt;여기서 &lt;code&gt;img&lt;/code&gt; 만 와도 딱히 상관이 없으며,&lt;/p&gt;
&lt;p&gt;바로 직후 &lt;code&gt;p&lt;/code&gt; 태그가 있을 경우, 이미지 설명에 관한 스타일링을 해 주겠다.&lt;/p&gt;
&lt;p&gt;이러한 목적으로 작성할 수 있을 것 같다.&lt;/p&gt;
&lt;br/&gt;

&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;div {
    margin : 0px;
}

img {
    width : 100%;
    margin : 10px;
}

img + p {
    padding : 5px;
    border : 2px solid dodgerblue;
    border-radius : 5px;
    text-align : center;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body&amp;gt;
    &amp;lt;div&amp;gt;
        &amp;lt;img src=&amp;quot;https://tistory1.daumcdn.net/tistory/6142901/attach/eaf30c1affbe45adb0179071560647bc&amp;quot;/&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;
        &amp;lt;img src=&amp;quot;https://tistory1.daumcdn.net/tistory/6142901/attach/eaf30c1affbe45adb0179071560647bc&amp;quot;/&amp;gt;
        &amp;lt;p&amp;gt;코딩크리쳐 로고 이미지입니다.&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;iframe
    srcdoc='
    &lt;style&gt;
        body {
            background : white;
        }
        div {
            margin : 0px;
        }
        img {
            width : 100%;
            margin : 10px;
        }
        img + p {
            padding : 5px;
            border : 2px solid dodgerblue;
            border-radius : 5px;
            text-align : center;
        }
    &lt;/style&gt;
    &lt;body&gt;
        &lt;div&gt;
            &lt;img src=&quot;https://tistory1.daumcdn.net/tistory/6142901/attach/eaf30c1affbe45adb0179071560647bc&quot;/&gt;
        &lt;/div&gt;
        &lt;div&gt;
            &lt;img src=&quot;https://tistory1.daumcdn.net/tistory/6142901/attach/eaf30c1affbe45adb0179071560647bc&quot;/&gt;
            &lt;p&gt;코딩크리쳐 로고 이미지입니다.&lt;/p&gt;
        &lt;/div&gt;
    &lt;/body&gt;
    '
    width=&quot;1000&quot;
    height=&quot;800&quot;
&gt;
    &lt;p&gt;iframe 이 뜨지 않는다면 이 문구가 뜹니다.&lt;/p&gt;
&lt;/iframe&gt;

&lt;br/&gt;

&lt;p&gt;위와 같은 방식으로 이미지에 따라붙는 설명 목적의 &lt;code&gt;p&lt;/code&gt; 에만 따로 스타일링을 추가할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;CSS 일반 형제 결합자의 경우 (&amp;quot;~&amp;quot;)&lt;/h3&gt;
&lt;p&gt;직전에 알게 되었던 &amp;quot;인접 형제 결합자&amp;quot; (&lt;code&gt;+&lt;/code&gt;) 와는 달리,&lt;/p&gt;
&lt;p&gt;일반 형제 결합자는 더 넓은 Scope 를 의미한다.&lt;/p&gt;
&lt;p&gt;&amp;quot;인접&amp;quot; 형제 결합자의 경우 &lt;code&gt;img + p { ... }&lt;/code&gt; 가 된다면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;img&lt;/code&gt; 태그 &amp;quot;직후&amp;quot; &lt;code&gt;p&lt;/code&gt; 태그만 스타일링 했다.&lt;/p&gt;
&lt;p&gt;그러나, 일반 형제 결합자 &lt;code&gt;~&lt;/code&gt; 의 경우,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;img ~ p { ... }&lt;/code&gt; 가 된다면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;img&lt;/code&gt; 태그 이후 나타나는 &amp;quot;모든&amp;quot; &lt;code&gt;p&lt;/code&gt; 태그에 대해 스타일링을 적용한다.&lt;/p&gt;
&lt;p&gt;물론, 이는 &lt;code&gt;img&lt;/code&gt; 와 같은 계층의 &lt;code&gt;p&lt;/code&gt; 태그일 경우에만 적용이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;특정 태그 이후에 나타나는 &amp;quot;같은 계층의&amp;quot; 특정 태그를 &amp;quot;모두&amp;quot; 선택한다는 기능은&lt;/p&gt;
&lt;p&gt;꽤 유연한 기능으로 보여, 오히려 어떤 용도로 사용될지 감이 잡히진 않았다.&lt;/p&gt;
&lt;p&gt;따라서, 이에 대한 사용 Convention 을 Gemini 에게 질문했는데,&lt;/p&gt;
&lt;p&gt;답변의 요약은, &lt;strong&gt;&amp;quot;주로 Interaction CSS 에 사용된다&amp;quot;&lt;/strong&gt; 였다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;AI 가 제시한 예시와 방식은,&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;어떻게 특정 웹 사이트는, 내가 클릭하는 것에 대해서 다른 요소를 띄워줄 수 있는가?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;에 대한 의문을 해소시켜주기에 충분했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이러한 예제를 만들어 보기로 했다.&lt;/p&gt;
&lt;p&gt;처음 나타나는 요소는, 2 가지의 체크박스가 존재한다.&lt;/p&gt;
&lt;p&gt;그런데, 하나의 체크박스를 활성화 할 때 마다,&lt;/p&gt;
&lt;p&gt;더 디테일 한 선택지를 하위에 보여 주는 식으로 제작할 수 있다는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이번에는 HTML 을 먼저 작성하는 것이, CSS 파일을 이해하는 데 더 가독성이 좋을 것이라고 판단하여&lt;/p&gt;
&lt;p&gt;HTML 파일을 작성 한 후, CSS 를 작성해 보기로 했다.&lt;/p&gt;
&lt;p&gt;개발자가 현재 선호하는 파트와, 관련된 언어를 선택하는 체크박스를 만들기로 했다.&lt;/p&gt;
&lt;br/&gt;

&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body&amp;gt;
    &amp;lt;fieldset&amp;gt;
        &amp;lt;legend&amp;gt;일반 형제 선택자 예시&amp;lt;/legend&amp;gt;
        &amp;lt;div&amp;gt;
            &amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;front-end&amp;quot; name=&amp;quot;front&amp;quot;/&amp;gt;
            &amp;lt;label for=&amp;quot;front&amp;quot;&amp;gt;Front-End&amp;lt;/label&amp;gt;
            &amp;lt;div class=&amp;quot;front-switch&amp;quot;&amp;gt;
                &amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;js-front&amp;quot; name=&amp;quot;javascript&amp;quot; /&amp;gt;
                &amp;lt;label for=&amp;quot;javascript&amp;quot;&amp;gt;JavaScript&amp;lt;/label&amp;gt;
                &amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;python-front&amp;quot; name=&amp;quot;python&amp;quot; /&amp;gt;
                &amp;lt;label for=&amp;quot;python&amp;quot;&amp;gt;Python&amp;lt;/label&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;br/&amp;gt;
            &amp;lt;br/&amp;gt;
            &amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;back-end&amp;quot; name=&amp;quot;back&amp;quot; /&amp;gt;
            &amp;lt;label for=&amp;quot;back&amp;quot;&amp;gt;Back-End&amp;lt;/label&amp;gt;
            &amp;lt;div class=&amp;quot;back-switch&amp;quot;&amp;gt;
                &amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;js-back&amp;quot; name=&amp;quot;javascript&amp;quot; /&amp;gt;
                &amp;lt;label for=&amp;quot;javascript&amp;quot;&amp;gt;JavaScript&amp;lt;/label&amp;gt;
                &amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;java-back&amp;quot; name=&amp;quot;java&amp;quot; /&amp;gt;
                &amp;lt;label for=&amp;quot;java&amp;quot;&amp;gt;Java&amp;lt;/label&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/fieldset&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.front-switch, .back-switch {
    display : none;
}

#front-end:checked ~ .front-switch {
    display : block;
}

#back-end:checked ~ .back-switch {
    display : block;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;iframe
    srcdoc='
    &lt;style&gt;
        body {
            background : white;
        }
        .front-switch, .back-switch {
            display : none;
        }
        #front-end:checked ~ .front-switch {
            display : block;
        }
        #back-end:checked ~ .back-switch {
            display : block;
        }
    &lt;/style&gt;
    &lt;body&gt;
        &lt;fieldset&gt;
            &lt;legend&gt;일반 형제 선택자 예시&lt;/legend&gt;
            &lt;div&gt;
                &lt;input type=&quot;checkbox&quot; id=&quot;front-end&quot; name=&quot;front&quot;/&gt;
                &lt;label for=&quot;front&quot;&gt;Front-End&lt;/label&gt;
                &lt;div class=&quot;front-switch&quot;&gt;
                    &lt;input type=&quot;checkbox&quot; id=&quot;js-front&quot; name=&quot;javascript&quot; /&gt;
                    &lt;label for=&quot;javascript&quot;&gt;JavaScript&lt;/label&gt;
                    &lt;input type=&quot;checkbox&quot; id=&quot;python-front&quot; name=&quot;python&quot; /&gt;
                    &lt;label for=&quot;python&quot;&gt;Python&lt;/label&gt;
                &lt;/div&gt;
                &lt;br/&gt;
                &lt;br/&gt;
                &lt;input type=&quot;checkbox&quot; id=&quot;back-end&quot; name=&quot;back&quot; /&gt;
                &lt;label for=&quot;back&quot;&gt;Back-End&lt;/label&gt;
                &lt;div class=&quot;back-switch&quot;&gt;
                    &lt;input type=&quot;checkbox&quot; id=&quot;js-back&quot; name=&quot;javascript&quot; /&gt;
                    &lt;label for=&quot;javascript&quot;&gt;JavaScript&lt;/label&gt;
                    &lt;input type=&quot;checkbox&quot; id=&quot;java-back&quot; name=&quot;java&quot; /&gt;
                    &lt;label for=&quot;java&quot;&gt;Java&lt;/label&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/fieldset&gt;
    &lt;/body&gt;
    '
    width=&quot;1000&quot;
    height=&quot;800&quot;
&gt;
    &lt;p&gt;iframe 이 뜨지 않는다면 이 문구가 뜹니다.&lt;/p&gt;
&lt;/iframe&gt;

&lt;br/&gt;

&lt;p&gt;위의 예제를 실제로 클릭 해 보면,&lt;/p&gt;
&lt;p&gt;각 체크박스를 클릭 할 때&lt;/p&gt;
&lt;p&gt;JS 코드 없이 CSS 만으로 원하는 다른 체크박스를 나타나게 만들 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;물론 위의 HTML 을 보면, 선택자의 위치 상 &amp;quot;인접 형제 선택자&amp;quot; 인 &lt;code&gt;+&lt;/code&gt; 로 변경해도 된다.&lt;/p&gt;
&lt;p&gt;그러나, 그 사이에 설명에 해당하는 &lt;code&gt;p&lt;/code&gt; 태그가 들어가도, &lt;code&gt;~&lt;/code&gt; 선택자 덕분에 스타일이 변경되지 않는다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;CSS 의 가상 클래스란 무엇인가?&lt;/h2&gt;
&lt;p&gt;위에서 보여준 예제 중, &lt;code&gt;:hover&lt;/code&gt; 이 가상 클래스에 해당하고,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;:focus&lt;/code&gt; , ... 등등 다양한 가상 클래스 요소가 CSS 에 존재한다.&lt;/p&gt;
&lt;p&gt;그렇다면, 가상 클래스란 무엇인가?&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;완벽하진 않지만, 가상 클래스란, HTML 태그 내부에&lt;/p&gt;
&lt;p&gt;&amp;quot;설정되었을 수도&amp;quot; &amp;quot;설정되지 않았을 수도&amp;quot; 있는 클래스를 의미한다.&lt;/p&gt;
&lt;p&gt;MDN 에 따르면, &amp;quot;선택자에 추가하는 키워드로, 선택한 요소가 특별한 상태여야 만족 할 수 있다&amp;quot; 라고 한다.&lt;/p&gt;
&lt;p&gt;예를 들어서, 위에서 체크박스 예시를 사용 할 때,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;:checked&lt;/code&gt; 를 사용하여 선택자를 선언했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;HTML 을 작성 할 때, 우리는 CheckBox 나, RadioButton 여러 개를 선언 할 때,&lt;/p&gt;
&lt;p&gt;기본적으로 &amp;quot;선택된 상태&amp;quot; 로 나타날 지, &amp;quot;선택되지 않은 상태&amp;quot; 로 나타날 지 선택할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; ... checked&amp;gt;&lt;/code&gt; ==&amp;gt; 이 체크박스는 선택된 상태로 초기화 된다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; ...&amp;gt;&lt;/code&gt; ==&amp;gt; 선택되지 않은 상태로 나타난다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;즉, &lt;code&gt;input&lt;/code&gt; Element 에서, &lt;code&gt;checked&lt;/code&gt; 라는 가상 항목은, &amp;quot;있을수도, 없을 수도&amp;quot; 있다.&lt;/p&gt;
&lt;p&gt;HTML 태그들은 웹 페이지를 작성함에 있어, 위와 같은 &amp;quot;그럴수도, 아닐수도&amp;quot; 에 대한 &amp;quot;가상 클래스&amp;quot; 들을&lt;/p&gt;
&lt;p&gt;마련 해 두었다. 40개 쯤 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;참고로, 이 가상 클래스라는 것은, &lt;code&gt;class=&amp;quot;..&amp;quot;&lt;/code&gt; 로 선택할 수 없으며, &lt;code&gt;...[checked]&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;와 같이 선택 할 수 없다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;element:가상 클래스&lt;/code&gt; 로 선택 할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;먼저, 내가 예시를 들기 전, 대부분의 가상 클래스에 대해 예시를 들고 있는 MDN 링크를 둔다.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://developer.mozilla.org/ko/docs/Web/CSS/Pseudo-classes&quot;&gt;MDN 공식문서 - (Pseudo-classes)&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;유용 해 보이는 CSS 가상 클래스 선택자들&lt;/h3&gt;
&lt;p&gt;이 CSS 가상 클래스 선택자들은, HTML 과 CSS 의 &amp;quot;정해진 소통&amp;quot; 과 흡사하다고 생각된다.&lt;/p&gt;
&lt;p&gt;HTML 과 CSS 는 서로에게 상관 할 수 없다.&lt;/p&gt;
&lt;p&gt;그러나, 이 가상 클래스 선택자(Pseudo classes) 들은 HTML 과 CSS 간의&lt;/p&gt;
&lt;p&gt;정해진 변경 사항에 대한 스타일링 소통 이라고 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;가상 클래스 선택자들은 수십가지가 되는데,&lt;/p&gt;
&lt;p&gt;기초 상태에서 알아야 할 가상 클래스 선택자는 이 글에 넣을 수 있겠다 생각된다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;:hover&lt;/h4&gt;
&lt;p&gt;위에서 작성한 인터랙션 예시에서 &lt;code&gt;:hover&lt;/code&gt; 을 즐겨 사용 한 것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;그 만큼 유저가 직접적으로 맞닥들일 수 있는 현재 상태에 대한 가상 클래스이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;만약에 사용자가 특정 HTML 요소에 마우스(Pointing 디바이스)로 커서를 올려 놓는다면,&lt;/p&gt;
&lt;p&gt;해당 HTML 요소에 &lt;code&gt;:hover&lt;/code&gt; 이라는 가상 클래스가 붙게 된다.&lt;/p&gt;
&lt;p&gt;이를 &amp;quot;호버링&amp;quot; 이라고 부르는데, 사용자가 현재 커서를 올려두고 있는 요소에 집중하게 해 주는 용도로&lt;/p&gt;
&lt;p&gt;자주 사용된다.&lt;/p&gt;
&lt;p&gt;아마 스타일링 장인분들은 다양한 사용처에 대해 알고 있겠지만... 나는 일단 요 정도로 알고 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;애니메이션까지 더하면 금상첨화겠지만, 기초부터 탄탄히 해야 한다는 나의 철학과는 비견될 수 밖에 없다.&lt;/p&gt;
&lt;p&gt;글의 실용성을 위해, 잠시 &lt;code&gt;transition&lt;/code&gt; 을 사용하기로 마음먹었다.&lt;/p&gt;
&lt;br/&gt;

&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.interaction-button {
    margin : 10px;
    padding : 10px;
    color : white;
    background : black;

    transition : background 0.3s ease-in-out;

    &amp;amp;:hover {
        background : dodgerblue;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body&amp;gt;
    &amp;lt;button class=&amp;quot;interaction-button&amp;quot;&amp;gt;btn 1&amp;lt;/button&amp;gt;
    &amp;lt;br/&amp;gt;
    &amp;lt;button class=&amp;quot;interaction-button&amp;quot;&amp;gt;btn 2&amp;lt;/button&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;iframe
    srcdoc='
    &lt;style&gt;
        body {
            background : white;
        }
        .interaction-button {
            margin : 10px;
            padding : 10px;
            color : white;
            background : black;
            transition : background 0.3s ease-in-out;
            &amp;:hover {
                background : dodgerblue;
            }
        }
    &lt;/style&gt;
    &lt;body&gt;
        &lt;button class=&quot;interaction-button&quot;&gt;btn 1&lt;/button&gt;
        &lt;br/&gt;
        &lt;button class=&quot;interaction-button&quot;&gt;btn 2&lt;/button&gt;
    &lt;/body&gt;
    '
    width=&quot;1000&quot;
    height=&quot;800&quot;
&gt;
    &lt;p&gt;iframe 이 뜨지 않는다면 이 문구가 뜹니다.&lt;/p&gt;
&lt;/iframe&gt;

&lt;br/&gt;

&lt;p&gt;위의 예제의 버튼에 커서를 올려보면, 자연스럽게 파란색으로 변했다가,&lt;/p&gt;
&lt;p&gt;다시 원래의 색으로 돌아오는 것을 볼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;:active&lt;/h4&gt;
&lt;p&gt;이 가상 클래스는 특정 엘리먼트를 &amp;quot;클릭 한 후 뗄 때 까지&amp;quot; 해당 엘리먼트에 활성화된다.&lt;/p&gt;
&lt;p&gt;주로 다른 페이지의 링크를 가르키는 &lt;code&gt;a&lt;/code&gt; 태그에 사용된다고 하는데,&lt;/p&gt;
&lt;p&gt;나는 인접 형제 선택자와 더불어서, 궁금할 수 있는 단어를 클릭하는 동안,&lt;/p&gt;
&lt;p&gt;밑에 그 설명을 보조하는 공간이 있으면 어떨까 생각을 해 보았다.&lt;/p&gt;
&lt;br/&gt;

&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body&amp;gt;
    &amp;lt;article&amp;gt;
        &amp;lt;p&amp;gt;리액트란 무엇일까? - 클릭&amp;lt;/p&amp;gt;
        &amp;lt;ol&amp;gt;
            &amp;lt;li&amp;gt;현대적인 웹 프로젝트를 위한 라이브러리이다.&amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;Webpack, Babel 등등의 방법론을 통해 트랜스파일링 결과물이 나온다.&amp;lt;/li&amp;gt;
        &amp;lt;/ol&amp;gt;
    &amp;lt;/article&amp;gt;
    &amp;lt;article&amp;gt;
        &amp;lt;p&amp;gt;CSS 란 무엇일까? - 클릭&amp;lt;/p&amp;gt;
        &amp;lt;ol&amp;gt;
            &amp;lt;li&amp;gt;Cascading Style Sheet 의 약자이다.&amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;HTML 파일 내의 엘리먼트를 스타일링 하기 위해 존재한다.&amp;lt;/li&amp;gt;
        &amp;lt;/ol&amp;gt;
    &amp;lt;/article&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;p {
    padding : 3px;
    border : 3px solid black;
    border-radius : 4px;
}

ol {
    display : none;
}

p:active + ol {
    display : block;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;iframe
    srcdoc='
    &lt;style&gt;
        body {
            background : white;
        }
        p {
            padding : 3px;
            border : 3px solid black;
            border-radius : 4px;
        }
        ol {
            display : none;
        }
        p:active + ol {
            display : block;
        }
    &lt;/style&gt;
    &lt;body&gt;
        &lt;article&gt;
            &lt;p&gt;리액트란 무엇일까? - 클릭&lt;/p&gt;
            &lt;ol&gt;
                &lt;li&gt;현대적인 웹 프로젝트를 위한 라이브러리이다.&lt;/li&gt;
                &lt;li&gt;Webpack, Babel 등등의 방법론을 통해 트랜스파일링 결과물이 나온다.&lt;/li&gt;
            &lt;/ol&gt;
        &lt;/article&gt;
        &lt;article&gt;
            &lt;p&gt;CSS 란 무엇일까? - 클릭&lt;/p&gt;
            &lt;ol&gt;
                &lt;li&gt;Cascading Style Sheet 의 약자이다.&lt;/li&gt;
                &lt;li&gt;HTML 파일 내의 엘리먼트를 스타일링 하기 위해 존재한다.&lt;/li&gt;
            &lt;/ol&gt;
        &lt;/article&gt;
    &lt;/body&gt;
    '
    width=&quot;1000&quot;
    height=&quot;800&quot;
&gt;
    &lt;p&gt;iframe 이 뜨지 않는다면 이 문구가 뜹니다.&lt;/p&gt;
&lt;/iframe&gt;

&lt;br/&gt;

&lt;p&gt;해당 영역을 &amp;quot;클릭하는 동안&amp;quot;, 직후에 나타나는&lt;/p&gt;
&lt;p&gt;Ordered List 스타일의 설명이 나타날 수 있게 만들었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;만약에 이 가상 클래스를 전문적으로 사용한다면,&lt;/p&gt;
&lt;p&gt;블록을 자유자재로 옮길 수 있는 Notion 이나, Jira 같은 곳에서&lt;/p&gt;
&lt;p&gt;타임라인을 옮기는 데에 사용하지 않을까? 하고 상상 해 본다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;:focus&lt;/h4&gt;
&lt;p&gt;주로 Tab 키 나, 실제 입력이 가능한 엘리먼트는 &lt;code&gt;focus&lt;/code&gt; 가상 클래스를 가질 수 있다.&lt;/p&gt;
&lt;p&gt;단, &lt;code&gt;div&lt;/code&gt;, &lt;code&gt;section&lt;/code&gt;, &lt;code&gt;article&lt;/code&gt;, ... 등등 단순한 블록 선언 엘리먼트는 가질 수 없다.&lt;/p&gt;
&lt;p&gt;대신, &lt;code&gt;:focus-within&lt;/code&gt; 이라는 속성을 통해 자손이 포커싱을 받았을 때, 공유하는 개념은 존재한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이번에는 포커싱을 통해 무엇을 제작 해 볼까... 하다가,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;form&lt;/code&gt; 내부에 여러 &lt;code&gt;input&lt;/code&gt; 을 넣어 놓고,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;input&lt;/code&gt; 이 포커싱을 받았을 때,&lt;/p&gt;
&lt;p&gt;그리고 &lt;code&gt;input&lt;/code&gt; 이 포커싱을 받았으므로 자손으로 두는 &lt;code&gt;form&lt;/code&gt; 의 변화도 같이 다루기로 했다.&lt;/p&gt;
&lt;br/&gt;

&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;input {
    margin : 10px;
    padding : 10px;
    background : #999;

    transition : background 0.3s ease-in-out;

    &amp;amp;:focus {
        border : 3px solid dodgerblue;
        background : #fff; /* white */
    }
}

form {
    border : 2px solid black;
    border-radius : 10px;
    padding : 15px;

    transition : border 0.3s ease-in-out;

    &amp;amp;:focus-within {
        border : 3px solid blue;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body&amp;gt;
    &amp;lt;form&amp;gt;
        &amp;lt;legend&amp;gt;이름 및 별명&amp;lt;/legend&amp;gt;
        이름 : &amp;lt;input type=&amp;quot;text&amp;quot;&amp;gt;&amp;lt;/input&amp;gt; &amp;lt;br/&amp;gt; &amp;lt;br/&amp;gt;
        별명 : &amp;lt;input type=&amp;quot;text&amp;quot;&amp;gt;&amp;lt;/input&amp;gt;
    &amp;lt;/form&amp;gt;
    &amp;lt;br/&amp;gt;
    &amp;lt;form&amp;gt;
        &amp;lt;legend&amp;gt;사용 언어 및 프레임워크&amp;lt;/legend&amp;gt;
        언어 : &amp;lt;input type=&amp;quot;text&amp;quot;&amp;gt;&amp;lt;/input&amp;gt; &amp;lt;br/&amp;gt; &amp;lt;br/&amp;gt;
        프레임워크 : &amp;lt;input type=&amp;quot;text&amp;quot;&amp;gt;&amp;lt;/input&amp;gt;
    &amp;lt;/form&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;iframe
    width=&quot;500px&quot;
    height=&quot;300px&quot;
    srcdoc='
    &lt;style&gt;
        body {
            background : white;
        }
        input {
            margin : 10px;
            padding : 10px;
            background : #999;
            transition : background 0.3s ease-in-out;
            &amp;:focus {
                border : 3px solid dodgerblue;
                background : #fff; /* white */
            }
        }
        form {
            border : 2px solid black;
            border-radius : 10px;
            padding : 15px;
            transition : border 0.3s ease-in-out;
            &amp;:focus-within {
                border : 3px solid blue;
            }
        }
    &lt;/style&gt;
    &lt;body&gt;
        &lt;form&gt;
            &lt;legend&gt;이름 및 별명&lt;/legend&gt;
            이름 : &lt;input type=&quot;text&quot;&gt;&lt;/input&gt; &lt;br/&gt; &lt;br/&gt;
            별명 : &lt;input type=&quot;text&quot;&gt;&lt;/input&gt;
        &lt;/form&gt;
        &lt;br/&gt;
        &lt;form&gt;
            &lt;legend&gt;사용 언어 및 프레임워크&lt;/legend&gt;
            언어 : &lt;input type=&quot;text&quot;&gt;&lt;/input&gt; &lt;br/&gt; &lt;br/&gt;
            프레임워크 : &lt;input type=&quot;text&quot;&gt;&lt;/input&gt;
        &lt;/form&gt;
    &lt;/body&gt;
    '
&gt;
    &lt;p&gt;iframe 이 뜨지 않는다면 이 문구가 뜹니다.&lt;/p&gt;
&lt;/iframe&gt;

&lt;br/&gt;

&lt;p&gt;위의 예제에서 각 &lt;code&gt;form&lt;/code&gt; 내부에 존재하는 &lt;code&gt;input&lt;/code&gt; 에 포커싱을 주면,&lt;/p&gt;
&lt;p&gt;해당 &lt;code&gt;input&lt;/code&gt; 과 더불어, 이를 포함하는 &lt;code&gt;form&lt;/code&gt; 까지 같이 포커싱이 되도록 만들었다.&lt;/p&gt;
&lt;p&gt;나중에 회원가입이나, 제출 양식 같은 것을 만들 때,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;:focus-within&lt;/code&gt; 을 사용하면 더 좋은 인터랙션을 만들 수 있을 것 같아 같이 사용 해 보았다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;:nth-*&lt;/h4&gt;
&lt;p&gt;이 가상 클래스는 같은 요소가 나열되어 있을 때,&lt;/p&gt;
&lt;p&gt;각 줄이 가독성을 가지게 만들 수 있다는 점에서 중요하다 판단했다.&lt;/p&gt;
&lt;p&gt;예를 들어,&lt;/p&gt;
&lt;p&gt;제품 리스트 수십 개가 한 번에 나열되어 있을 때, 가독성은 떨어질 수 밖에 없다.&lt;/p&gt;
&lt;p&gt;특히 하나의 줄에 여러 Column 까지 있다면, 읽기는 더 힘들다.&lt;/p&gt;
&lt;p&gt;따라서, 연속되는 속성에 따라 배경색을 달리 한다면, 가독성을 확보할 수 있겠다고 생각했다.&lt;/p&gt;
&lt;br/&gt;

&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;li:nth-child(2n) {
    background : #333;
    color : #aaa;
}

li:nth-child(odd) {
    background : white;
    color : black;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body&amp;gt;
    &amp;lt;ol id=&amp;quot;ordered-list&amp;quot;&amp;gt;
    &amp;lt;/ol&amp;gt;
    &amp;lt;script&amp;gt;
    const olObj = document.getElementById(&amp;quot;ordered-list&amp;quot;);

    for(let i = 0; i &amp;lt; 20; i++) {
      let newLiObj = document.createElement(&amp;quot;li&amp;quot;);

      newLiObj.textContent = &amp;quot;item : &amp;quot; + i;

      olObj.appendChild(newLiObj);
    }
    &amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;iframe
    width=&quot;500px&quot;
    height=&quot;300px&quot;
    srcdoc='
    &lt;style&gt;
        body {
            background : white;
        }
        li:nth-child(2n) {
            background : #333;
            color : #aaa;
        }
        li:nth-child(odd) {
            background : white;
            color : black;
        }
    &lt;/style&gt;
    &lt;body&gt;
        &lt;ol id=&quot;ordered-list&quot;&gt;
        &lt;/ol&gt;
        &lt;script&gt;
        const olObj = document.getElementById(&quot;ordered-list&quot;);
        for(let i = 0; i &lt; 20; i++) {
          let newLiObj = document.createElement(&quot;li&quot;);
          newLiObj.textContent = &quot;item : &quot; + i;
          olObj.appendChild(newLiObj);
        }
        &lt;/script&gt;
    &lt;/body&gt;
    '
&gt;
    &lt;p&gt;iframe 이 뜨지 않는다면 이 문구가 뜹니다.&lt;/p&gt;
&lt;/iframe&gt;

&lt;br/&gt;

&lt;p&gt;위의 예시 색상은 조금 극단적으로 가기는 했다.&lt;/p&gt;
&lt;p&gt;위에서 &lt;code&gt;css&lt;/code&gt; 예시를 보면, 보기 드문 괄호 &lt;code&gt;(..)&lt;/code&gt; 가 들어갔다.&lt;/p&gt;
&lt;p&gt;이는 &amp;quot;몇 번째?&amp;quot; 를 정확하게 하기 위함이다.&lt;/p&gt;
&lt;p&gt;여기에 단순 숫자를 넣는다면, 해당 순번만 스타일링이 되며,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;xn&lt;/code&gt; 형식으로 넣는다면, &lt;code&gt;x&lt;/code&gt; 번째마다 스타일링이 된다.&lt;/p&gt;
&lt;p&gt;혹은, &lt;code&gt;even&lt;/code&gt;, &lt;code&gt;odd&lt;/code&gt; 만 그대로 괄호에 넣어 번갈아 가며 스타일링 할 수 있게 해 주었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이 &lt;strong&gt;:nth-*&lt;/strong&gt; 에는, 위와 같은 사용처 뿐만 아니라,&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://developer.mozilla.org/ko/docs/Web/CSS/:nth-child&quot;&gt;MDN (nth-* 예시)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;문서를 통해 더 자세한 내용을 볼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h1&gt;이 글을 끝내며&lt;/h1&gt;
&lt;p&gt;맨 처음, 이 글을 시작하며 &amp;quot;작성하게 된 이유&amp;quot; 를 덧붙였다.&lt;/p&gt;
&lt;p&gt;CSS 를 배우는데 이렇게 장황한 이유로 시작 할 이유가 있나? 라고 생각이 들 만큼 길게 작성한 것 같다.&lt;/p&gt;
&lt;p&gt;그래도, 오랫동안 기억했으면 좋을 내용을 따로 만들어 작성하는 나로서는,&lt;/p&gt;
&lt;p&gt;왜 이 글을 작성하는지 보자마자 떠올릴 수 있다.&lt;/p&gt;
&lt;p&gt;아, 이 때 무엇을 했었고, 무엇을 배웠구나 하고 말이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;아직 CSS 에서 중요한 부분은 너무나도 많이 남아 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;display&lt;/code&gt; 속성에 따른 스타일링 방식&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;더 많은 가상 클래스와, 아직 다루지 않은 가상 속성(pseudo elements)&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;애니메이션 및 트랜지션&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;Modal (모달 만들기)&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;p&gt;이러한 것들은 실제 프로젝트를 진행하면서, 더 파고들게 되면 그 때 글로 다룰 생각이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;SASS 와의 차이점?&lt;/h3&gt;
&lt;p&gt;웹 브라우저의 CSS 전처리기가 발달하며 조금 복잡해 진 중첩 선택자도 알아들을 수 있게 되었지만,&lt;/p&gt;
&lt;p&gt;여전히 브라우저가 &amp;quot;직접&amp;quot; 파싱한다는 점에서 클라이언트에게 일을 떠맡긴 셈이 되었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;SASS 의 기능을 다수 기본 CSS 파일에서도 처리 할 수 있도록 만들었지만,&lt;/p&gt;
&lt;p&gt;여전히 SASS 의 강점은 존재한다.&lt;/p&gt;
&lt;p&gt;즉, 자체적인 동적 스크립팅은 SASS, SCSS 가 더 우세하다고 생각한다.&lt;/p&gt;
&lt;p&gt;또한, 작성한 동적 스크립팅 함수에 대한 재사용성이 좋아, 개발 시에는 SASS 기반 스타일링이 편할 것 같다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그리고 또 다른 차이점은, Bundler 의 이용 여부에 있다.&lt;/p&gt;
&lt;p&gt;CSS 또한, 선언된 위치와 순위에 따라 우선순위가 달라지기는 하지만,&lt;/p&gt;
&lt;p&gt;SASS 는 React 컴포넌트 파일과 함께 작성되기 마련이다.&lt;/p&gt;
&lt;p&gt;이 때, 번들러의 의존성 Resolver 덕분에, CSS 파생 파일들이 한 군데 합쳐지거나,&lt;/p&gt;
&lt;p&gt;사용자가 원하는 만큼의 Chunk 로 분해해서, 이를 브라우저가 Load 할 수 있게 만들어 준다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;이번 글에서 크게 배운 점.&lt;/h2&gt;
&lt;p&gt;CSS 를 잘 알지 못했기 때문에,&lt;/p&gt;
&lt;p&gt;HTML 엘리먼트에서 복잡한 클래스를 선언하고, CSS 파일을 단순화 하는 방향으로 스타일링을 진행했었다.&lt;/p&gt;
&lt;p&gt;이는 나중에 오히려 엘리먼트나 복합 컴포넌트를 추가 할 때 문제가 되었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;아직도 CSS 에 대해서 &amp;quot;이해했냐?&amp;quot; 하면 기초를 조금 이해하게 되었다고 말할 수 있을 것 같다.&lt;/p&gt;
&lt;p&gt;아직 여전히 많이 사용되는 기능과 표현에 대해서는 모르며, 이에 대한 실전 사용법을 잘 모르기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, 이번에 다양한 선택자(Selector) 를 둘러보면서 실제 예제를 만들었다.&lt;/p&gt;
&lt;p&gt;중첩 연산자(&lt;code&gt;&amp;amp;&lt;/code&gt;), 형제 인접 연산자(&lt;code&gt;+&lt;/code&gt;), 일반 형제 연산자(&lt;code&gt;~&lt;/code&gt;) 를 통해&lt;/p&gt;
&lt;p&gt;깔끔하게 작성하지 못하던 CSS 문법을 매끄럽게 작성할 수 있으리라는 생각이 든다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;독자들에게 전하는 말&lt;/h2&gt;
&lt;p&gt;궁금 한 것이 있다면,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;email&lt;/code&gt; : &lt;a href=&quot;mailto:rhdwhdals8@naver.com&quot;&gt;rhdwhdals8@naver.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 곳으로 물어보시면 환영입니다!&lt;/p&gt;
&lt;br/&gt;


&lt;hr&gt;
&lt;h2&gt;참조 사이트&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;W3School CSS Tutorial&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.w3schools.com/css/default.asp&quot;&gt;https://www.w3schools.com/css/default.asp&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;W3School CSS Reference&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.w3schools.com/CSSref/index.php&quot;&gt;https://www.w3schools.com/CSSref/index.php&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GeeksForGeeks - (CSS Properties Complete Reference)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.geeksforgeeks.org/css/css-properties-complete-reference/&quot;&gt;https://www.geeksforgeeks.org/css/css-properties-complete-reference/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;위키피디아 (CSS)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.wikipedia.org/wiki/CSS&quot;&gt;https://ko.wikipedia.org/wiki/CSS&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;MDN 문서 - (CSS 선택자)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://developer.mozilla.org/ko/docs/Web/CSS/CSS_selectors&quot;&gt;https://developer.mozilla.org/ko/docs/Web/CSS/CSS_selectors&lt;/a&gt;&lt;/p&gt;</description>
      <category>Web-Server/웹 지식</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/237</guid>
      <comments>https://codecreature.tistory.com/237#entry237comment</comments>
      <pubDate>Wed, 8 Oct 2025 01:27:55 +0900</pubDate>
    </item>
    <item>
      <title>JavaScript 개발자를 위한 스프링, NestJS 에 대해서</title>
      <link>https://codecreature.tistory.com/236</link>
      <description>&lt;h2&gt;제목 : Node.js 의 혼란 속에서 NestJS 가 제시한 질서란&lt;/h2&gt;
&lt;h3&gt;부제목 : 다양한 언어와 프레임워크를 둘러본 시각으로서의 NestJS&lt;/h3&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;이 글을 작성하는 이유&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;다양한 프로그래밍 언어, C, C++, Java, JavaScript, TypeScript, .. 를 작성하여 프로그램을 제작해 보며,&lt;/p&gt;
&lt;p&gt;기존 언어들을 넘기 위해 개발 생산성과 프로그램 성능을 둘 다 잡은 신생 프로그래밍 언어&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Rust&lt;/li&gt;
&lt;li&gt;Go&lt;/li&gt;
&lt;li&gt;Zig&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;를 살펴보며, 현대식 프로그래밍 언어가 어떤 점을 표방하고 만들었는지 분석하며 보았다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rust 는 어떤 느낌이었냐면&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Rust 는 Memory Address Borrow 라는 통칭 &amp;quot;메모리 빌림&amp;quot; 에 초점을 두고 있었다.&lt;/p&gt;
&lt;p&gt;기존 C, C++ 에서 발생하는 메모리 누수를 원천적으로 차단하기 위해 만들어진 대표적인 언어의 기능이기도 했지만,&lt;/p&gt;
&lt;p&gt;Memory Leak 를 막는 기능이 도리어 개발 생산성을 해치는 결과로 이어지기도 한다.&lt;/p&gt;
&lt;p&gt;메모리에 관련된 에러 스택은 여타 다른 프로그래밍 언어와 다르기 때문에, 신생 개발자들이 굉장히 힘들어한다.&lt;/p&gt;
&lt;p&gt;입문은 굉장히 힘들지만, Rust 에 전문화 된 집단 (유저 그룹) 을 살펴보면,&lt;/p&gt;
&lt;p&gt;&amp;quot;모든 것을 Rust 로 재창조하자&amp;quot; 라는 기조가 깔려있다고 볼 수 있다.&lt;/p&gt;
&lt;p&gt;Microsoft 에서 TypeScript 컴파일러를 &lt;code&gt;golang&lt;/code&gt; 으로 바꾼다고 발표했을 때,&lt;/p&gt;
&lt;p&gt;Reddit 에서 &amp;quot;Change Everything to Rust&amp;quot; 라는 댓글이 달렸었는데,&lt;/p&gt;
&lt;p&gt;여기에 무수한 반대 댓글이 달렸었던 걸로 기억한다.&lt;/p&gt;
&lt;p&gt;Rust 커뮤니티는 &amp;quot;정복자&amp;quot; 의 느낌으로 프로그래밍을 진행하며,&lt;/p&gt;
&lt;p&gt;이러한 태도로 인해 다른 언어 커뮤니티에서 견제하고 있다는 것을 알게 되었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Zig 는 어떤 느낌이냐면&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Rust&lt;/code&gt; 가 &lt;code&gt;C++&lt;/code&gt; 을 대체하려는 신생 언어의 움직임을 띈다면,&lt;/p&gt;
&lt;p&gt;Zig 는 C 언어를 대체할 수 있으면 대체하되, C 와 공존하는 모습을 보인다.&lt;/p&gt;
&lt;p&gt;Zig 예찬론자는 아니지만, &lt;code&gt;zig&lt;/code&gt; 명령어로 &lt;code&gt;.c&lt;/code&gt; 파일을 손쉽게 컴파일 할 수 있는 모습으로 알게 되었다.&lt;/p&gt;
&lt;p&gt;기존의 &lt;code&gt;C&lt;/code&gt; 언어와 &amp;quot;쉽게&amp;quot; 더불어 사용할 수 있는 모습 또한 보인다. (Rust 도 가능하다)&lt;/p&gt;
&lt;p&gt;Rust 는 &amp;quot;메모리를 가지는 주체가 명확&amp;quot; 해야 한다는 컨셉이 있다면,&lt;/p&gt;
&lt;p&gt;Zig 는 &lt;code&gt;C&lt;/code&gt; 의 방식을 차용하되, 새로운 Tool 객체를 통해 메모리를 생성하고,&lt;/p&gt;
&lt;p&gt;코드 작성과 컴파일러를 통해 메모리 누수를 막는 방식으로 개발된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Go (golang) 은 어떤 느낌이냐면&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Google 에서 만들어진 golang 은, &lt;code&gt;C&lt;/code&gt; 의 간편한 버전이라고 볼 수 있다.&lt;/p&gt;
&lt;p&gt;그러나, &lt;code&gt;C&lt;/code&gt;, &lt;code&gt;Rust&lt;/code&gt;, &lt;code&gt;Zig&lt;/code&gt; 가 메모리를 수동으로 제어하지만,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Go&lt;/code&gt; 는 C 의 방식을 차용하되, GC (Garbage Collection) 을 추가했다.&lt;/p&gt;
&lt;p&gt;덕분에 개발자는 생성한 동적 주소나 메모리에 대한 관리를 걱정할 필요 없이, 작성하면 된다.&lt;/p&gt;
&lt;p&gt;Java 보다는 속도가 빠르나, 메모리를 수동으로 제어하는 언어에 비하면 느리다.&lt;/p&gt;
&lt;p&gt;그래도 이 언어는 개발 생산성과 프로그램 최적화를 둘 다 균형있게 잡았기 때문에,&lt;/p&gt;
&lt;p&gt;최근 굉장히 트렌디하게 뜨고 있는 언어이다.&lt;/p&gt;
&lt;p&gt;너무나도 유명한 &lt;code&gt;TypeScript&lt;/code&gt; 가 7.0 버전 이후로부터는 &lt;code&gt;Go&lt;/code&gt; 로 트랜스파일링 된다는 이야기가 들렸을 때,&lt;/p&gt;
&lt;p&gt;Golang 이 비교적 다른 언어보다는 오래 살아남겠구나 라는 생각이 들었었다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;그래서 NestJS 를 바라보았을 때, 어떤 생각이 드는가?&lt;/h2&gt;
&lt;p&gt;위에서 &amp;quot;이 글을 작성하는 이유&amp;quot; 에 대해서 길게 작성을 했는데,&lt;/p&gt;
&lt;p&gt;앞으로 말할 NestJS 에 대한 시각을 설명하기 위해서 다른 언어에 대한 관찰을 적을 수 밖에 없었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;&amp;quot;NestJS 에 대해서 본격적으로 이야기 하기 전에,&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이 이야기에 필수적인 JavaScript 와 TypeScript 에 대해서 이야기 하지 않을 수가 없다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;솔직히 나의 의견을 말해보자면&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;생 초보 개발자로 입문하기 위해 &lt;code&gt;JavaScript&lt;/code&gt; 만한 언어가 없다고 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;물론, JavaScript 가 여타 다른 언어에 비해 얼마나 프로그램의 속도가 느린지 알고 있다.&lt;/p&gt;
&lt;p&gt;나는 이것을 극복할 수단으로 &lt;code&gt;wasm&lt;/code&gt; 을 적극 도입해야 한다고 생각하는 사람이다.&lt;/p&gt;
&lt;p&gt;(&lt;code&gt;wasm&lt;/code&gt; 이란, 다른 언어에서 작성되어 어셈블리로 파생된 파일을 의미한다.)&lt;/p&gt;
&lt;p&gt;(주로 V8 or NodeJS + JS 로 실행했을 때 계산 부하가 걸리는 부분을 &lt;code&gt;wasm&lt;/code&gt; 으로 해결 할 수 있다.)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그리고 JavaScript 는 가장 거대한 유저 커뮤니티를 이루고 있다.&lt;/p&gt;
&lt;p&gt;이 말은, 전문가와 초보를 가리지 않고, 사람들이 JavaScript 로 프로젝트를 가장 많이 생성하고 있다는 이야기이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;(저는 나의 첫 개발 시절은 대학생 시절을, 만약에 이걸 읽고 있는 독자가 계시다면, 첫 개발 당시를 회고해 주시면 됩니다.)&lt;/p&gt;
&lt;p&gt;처음 개발을 할 때, C 나, C++ 혹은 Java 로 시작하게 된다.&lt;/p&gt;
&lt;p&gt;지금 돌이켜보면,&lt;/p&gt;
&lt;p&gt;현재 한국의 개발자 채용 시장이 C 기반이거나, Spring 프레임워크로 인한 Java 사용자가 필요했기 때문일 것이다.&lt;/p&gt;
&lt;p&gt;그러나 지금은 상황이 많이 다르다.&lt;/p&gt;
&lt;p&gt;국가 차원에서 &amp;quot;코딩&amp;quot; 이라는 단어를 앞세워 화이트칼라의 진입점의 대표격으로 만들기도 했지만,&lt;/p&gt;
&lt;p&gt;그보다도 업무 프로세스에 존재하는 Legacy(옛 것) 한 아날로그 방식을 현대화 시키기에 최적화 되었다고 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;C 기반과 Java 기반은 &amp;quot;시각적으로&amp;quot; 빠르게 프로젝트를 완성하기 어렵다.&lt;/p&gt;
&lt;p&gt;규칙에 대한 엄격한 문법이 장벽이 되며, 어려운 외부 프로그램 사용은 전문성을 요구한다.&lt;/p&gt;
&lt;p&gt;그러나 JavaScript 를 생각 해 보자.&lt;/p&gt;
&lt;p&gt;우리가 작은 프로젝트를 완성하기 위해 &amp;quot;가장 편리한&amp;quot; 언어를 꼽자면 어떤 언어일까?&lt;/p&gt;
&lt;p&gt;나는 당연히 JavaScript 를 꼽을 것이다.&lt;/p&gt;
&lt;p&gt;컴퓨터 엔지니어도 처음에는 대형 프로젝트로 시작 할 수 없을 것이다.&lt;/p&gt;
&lt;p&gt;당연히 작은 프로젝트로 컴퓨터에 대한 감각을 키워가게 되는데,&lt;/p&gt;
&lt;p&gt;다양한 언어로 전문화 된 사람이 들어오는 다리가 존재한다면,&lt;/p&gt;
&lt;p&gt;현재는 &lt;code&gt;JavaScript&lt;/code&gt; 가 그 교두보가 되어 다른 유저 커뮤니티에 뿌려주는 형태가 되었다.&lt;/p&gt;
&lt;p&gt;(계산과 AI 는 &lt;code&gt;python&lt;/code&gt; 이 교두보가 되었다.)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;NestJS 는 먼저 &amp;quot;결론적으로&amp;quot; 말해보자면,&lt;/p&gt;
&lt;p&gt;TypeScript 로 작성되는 체계적인 Back-End 프레임워크이다.&lt;/p&gt;
&lt;p&gt;이에 대한 의견을 뒷받침하기 위해 나의 시각을 펼쳐본다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;JavaScript 의 막대한 영향력&lt;/h3&gt;
&lt;p&gt;위에서 언급한 내용은 결국 &amp;quot;요즈음 개발자들은 간단한 웹 페이지 서비스를 만들며 컴퓨터를 배운다&amp;quot; 라고 요약할 수 있다.&lt;/p&gt;
&lt;p&gt;아무리 핸드폰 앱이 많고, AI 질문이 대세라지만, 검색 엔진이 더 우위다. (아직은)&lt;/p&gt;
&lt;p&gt;검색 엔진으로 Chrome, Edge, Oracle, 등등이 존재한다.&lt;/p&gt;
&lt;p&gt;이 엔진은 JavaScript 기반으로 동작한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&amp;quot;아주 정확하게 말하자면&amp;quot;&lt;/p&gt;
&lt;p&gt;HTML + CSS + JavaScript 로 동작한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;사람들이 가장 많이 사용하는 &amp;quot;검색 엔진&amp;quot; 에서 사용하는 &amp;quot;동적 언어&amp;quot; 는, &lt;code&gt;JavaScript&lt;/code&gt; 이다.&lt;/p&gt;
&lt;p&gt;다른 언어로도 물론 웹 제작이 가능하긴 하다. (예를 들면 &lt;code&gt;Rust&lt;/code&gt; or &lt;code&gt;go&lt;/code&gt; or &lt;code&gt;C&lt;/code&gt;, ..)&lt;/p&gt;
&lt;p&gt;그러나, 결국엔 &lt;code&gt;JavaScript&lt;/code&gt; 로 Glue Code 를 제작하여 &amp;quot;부착&amp;quot; 시켜줘야 한다.&lt;/p&gt;
&lt;p&gt;그러니까, 검색 엔진은 &lt;code&gt;JavaScript&lt;/code&gt; 와 바이너리 패키지를 인식한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;JavaScript 를 항상 지금처럼 현대적으로 사용할 수 있었던 것은 아니다.&lt;/p&gt;
&lt;p&gt;ES6 버전 (2016 년) 에 발표된 기능들, 기본적인 &lt;code&gt;async&lt;/code&gt;, &lt;code&gt;await&lt;/code&gt;, &lt;code&gt;Promise&lt;/code&gt; 등등과 같은&lt;/p&gt;
&lt;p&gt;비동기, 동기 스위칭 기능이 발표되며 JavaScript 는 더한 영향력을 얻게 되었다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;하드웨어, JavaScript 에 날개를 달아주다&lt;/h2&gt;
&lt;p&gt;Low, Middle 수준의 프로그래밍 언어가 각광받았던 이유 중 큰 이유는,&lt;/p&gt;
&lt;p&gt;제한된 하드웨어 안에서 원활히 실행할 수 있었기 때문이다.&lt;/p&gt;
&lt;p&gt;프로그램을 실행하기 전에 이미 Binary or Assembly 수준으로 파싱된 후 실행되는 프로세스는&lt;/p&gt;
&lt;p&gt;파일의 크기를 줄여 줄 뿐만 아니라, 컴파일러 단계에서 일부 코드를 캐싱하여 최적화까지 해 주었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;프로그래밍 역사는 자주 바뀌기 때문에 어떻게 보면 짧은 시간이지만,&lt;/p&gt;
&lt;p&gt;현재 NVM (Node Virtual Machine) 을 실행하기 위해 250MB 정도를 사용하는 것을 보면,&lt;/p&gt;
&lt;p&gt;왜 옛날 기기에 데이터를 계산하는 용도로 JavaScript 를 사용하지 않았는지를 알 수 있다.&lt;/p&gt;
&lt;p&gt;10 년 정도 이전이라면 최고 등급의 하드웨어라면 무리없이 돌릴 수도 있었겠지만,&lt;/p&gt;
&lt;p&gt;일반적인 기기를 생각해 본다면, 이는 하드웨어 낭비라고 충분히 생각 될 수 있다고 판단된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, 현재 손 안의 핸드폰 조차도 기본 8GB 메모리를 탑재하는 현재 세상은&lt;/p&gt;
&lt;p&gt;그러한 단점은 없는 수준으로 변했다고도 볼 수 있다.&lt;/p&gt;
&lt;p&gt;계산 속도의 비약적인 향상과 휘발성 저장소 (RAM) 크기의 성장은 JavaScript 기반의 문제를&lt;/p&gt;
&lt;p&gt;충분히 상쇄시켰다.&lt;/p&gt;
&lt;p&gt;Interpreter (인터프리터) 언어의 문제로,&lt;/p&gt;
&lt;p&gt;이미 작성된 자연어와 비슷한 코드를 미리 Binary, 혹은 Assembly 로 파싱하지 않고,&lt;/p&gt;
&lt;p&gt;JavaScript 엔진이 실시간으로 Parsing 하여 번역한다는 문제가 존재했다.&lt;/p&gt;
&lt;p&gt;그러나, JIT(Just In Time) 컴파일러를 추가하며 코드의 실행 속도를 높였다.&lt;/p&gt;
&lt;p&gt;이 전에는 번역된 코드를 따로 캐싱하지 않고 실행했다는데,&lt;/p&gt;
&lt;p&gt;이미 번역된 코드를 캐싱하지 않고 다시 번역해서 사용한다면, 지금조차도 감당이 될까 의문이 든다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;성장한 하드웨어는 주로 웹이라는 도메인에서 활동했던 JavaScript 가&lt;/p&gt;
&lt;p&gt;복잡한 데이터 처리와 네트워크 처리에도 사용할 수 있게 해 주었다.&lt;/p&gt;
&lt;p&gt;이미 &lt;code&gt;bcrypt&lt;/code&gt;, &lt;code&gt;fetch&lt;/code&gt; 와 같은 내장 모듈들은 저수준의 언어로 패키징되어 사용될 수 있지만,&lt;/p&gt;
&lt;p&gt;개발자가 이를 이용하여 원하는 Logic 을 처리할 수 있는 수준까지 하드웨어가 발전 한 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;JavaScript 의 확산&lt;/h3&gt;
&lt;p&gt;웹 도메인을 처리하며 JavaScript 의 전문가 수준까지 도달한 개발자들은 이미 많았을 것이다.&lt;/p&gt;
&lt;p&gt;그러나, 주 언어가 JS 였으나, 상대적으로 잘 모르는 Low ~ Middle 수준의 언어를 이용하여&lt;/p&gt;
&lt;p&gt;데이터 처리를 구성했을 것이다.&lt;/p&gt;
&lt;p&gt;그러나, 이제는 그럴 필요가 없다.&lt;/p&gt;
&lt;p&gt;웹 전문가도 Express 라는 라이브러리를 통해 자신이 원하는 대로 Back 로직을 구성할 수 있다.&lt;/p&gt;
&lt;p&gt;컴파일 과정에서 디버깅에 약한 JavaScript 의 약점을 보완하기 위해 만들어진 TypeScript 도,&lt;/p&gt;
&lt;p&gt;동시에 발전하며 타입 기반의 언어로 탈바꿈시켰다. (컴파일은 아닌 &amp;quot;트랜스파일링&amp;quot;)&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;너무나 거대한 Node Package Manager&lt;/h3&gt;
&lt;p&gt;통칭 &lt;code&gt;NPM&lt;/code&gt; 이라고 부르는 패키지 매니저이다.&lt;/p&gt;
&lt;p&gt;우리가 어떠한 언어를 사용하더라도, &amp;quot;의존성&amp;quot;(Dependency) 에 대해서 고려할 수 밖에 없다.&lt;/p&gt;
&lt;p&gt;(이는 모든 언어에 대해서 핵심적인 부분이며, 프로젝트를 진행한다면 대부분은 의존성을 사용하게 된다.)&lt;/p&gt;
&lt;p&gt;너무나 다양한 외부 의존성 패키지가 NPM 에는 2 백만개가 넘는다.&lt;/p&gt;
&lt;p&gt;그만큼 유저들이 쉽게 Registry 에 배포 할 수 있으며, 접근이 가능하다.&lt;/p&gt;
&lt;p&gt;이 패키지들은 전 세계의 모든 프로그래밍 언어를 비교해도 넘기 어려운 외부 의존성을 제공한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;프로젝트 생성 시 &lt;code&gt;npm init&lt;/code&gt; 명령어를 통해 간단히 &lt;code&gt;package.json&lt;/code&gt; 에 외부 의존성 메타데이터를&lt;/p&gt;
&lt;p&gt;작성할 수 있는 파일도 만들 수 있다.&lt;/p&gt;
&lt;p&gt;Dependency Installation 과정은 로컬 및 여타 기기에 설치된 &lt;code&gt;npm&lt;/code&gt; 전역 모듈을 통해 간단히&lt;/p&gt;
&lt;p&gt;명령 및 의존성 관리를 수행 할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;NestJS 의 탄생&lt;/h2&gt;
&lt;p&gt;너무나 빠르게 성장해버린 JavaScript 커뮤니티였을까, Back-End 로직의 컨벤션에 대한&lt;/p&gt;
&lt;p&gt;정확한 수립이 어려웠다고 한다.&lt;/p&gt;
&lt;p&gt;JS 의 백엔드 대표격 라이브러리 &lt;code&gt;express&lt;/code&gt; 를 사용하면 느낄 수 있는게,&lt;/p&gt;
&lt;p&gt;정말 내 맘대로 작성해도 상관없다는 느낌을 받았다.&lt;/p&gt;
&lt;p&gt;아마 이전에 만들어 본 Back-End 프로그램이 &lt;code&gt;Spring&lt;/code&gt; 이었기 때문일 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;물론 &lt;code&gt;express&lt;/code&gt; 에서 딱히 성능 문제가 제기되는 경우는 거의 없다.&lt;/p&gt;
&lt;p&gt;아마 그 정도의 네트워크 및 계산 부하가 걸린다면,&lt;/p&gt;
&lt;p&gt;다른 언어와 연결된 프레임워크 모듈을 새로 생성하여 부착하거나,&lt;/p&gt;
&lt;p&gt;Container 관리 프로그램으로 분산시키는 것이 맞을 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, &lt;code&gt;express&lt;/code&gt; 의 자율성은 오히려 팀이나 조직의 프로젝트 프로그램 생성에 장애물이 되었다고 한다.&lt;/p&gt;
&lt;p&gt;물론 자체적인 Convention 을 지정하면 상관은 없겠지만,&lt;/p&gt;
&lt;p&gt;나의 의견으로, 새로운 인원이 내부 로직을 파악하기 위해 걸리는 시간이 많이 소요 될 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;따라서, NestJS 를 만든 분은 TypeScript + Decorator 기반의 백엔드 프레임워크를 제작했다고 한다.&lt;/p&gt;
&lt;p&gt;웹 진영에서는 &lt;code&gt;React&lt;/code&gt;, &lt;code&gt;Vue&lt;/code&gt;, &lt;code&gt;Angular&lt;/code&gt; 와 같이 잘 정돈되어 수립된 웹 프레임워크가 존재하지만,&lt;/p&gt;
&lt;p&gt;서버 사이드의 JavaScript 는 제대로 수립된 프레임워크가 별로 없었다고 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이러한 배경에 근거하여, &lt;code&gt;Angular&lt;/code&gt; 에 큰 영감을 받아 아키텍쳐를 구상했다고 한다.&lt;/p&gt;
&lt;p&gt;나는 오히려 &lt;code&gt;Java - Annotaion&lt;/code&gt; 과 매우 비슷한 &lt;code&gt;JS - Decorator&lt;/code&gt; 때문에,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Spring&lt;/code&gt; 에서도 큰 영감을 받았을 것으로 추측한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;NestJS 를 사용해야 하는 이유는 무엇일까?&lt;/h2&gt;
&lt;p&gt;나는 2 개의 프로젝트를 NestJS 로 구축하며 NestJS 의 사용법을 알며, 컨벤션 또한 숙지했다.&lt;/p&gt;
&lt;p&gt;덕분에 온라인으로 편하게 팀원과 프로젝트를 구축할 수 있었는데,&lt;/p&gt;
&lt;p&gt;이는 NestJS 가 Express 에서 강제하지 않은 컨벤션을 NestJS 는 Spring 처럼 정해주었기 때문이다.&lt;/p&gt;
&lt;p&gt;이러한 프레임워크의 단점으로는, 편한 팀워크를 위해 해당 프로그램을 &amp;quot;배워야&amp;quot; 한다는 단점이 존재하기 마련이다.&lt;/p&gt;
&lt;p&gt;그러나, 이건 단점으로 보기 힘들다고 판단했는데, 프로그램을 배웠기 때문에 오히려 편리해졌기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그런데도 불구하고, 나는 NestJS 를 아주 적극적으로 추천하지는 않는다.&lt;/p&gt;
&lt;p&gt;아주 크게 보면 프로그램 세상에서 &amp;quot;정답&amp;quot; 인 방법론은 존재하지 않는다고 생각한다.&lt;/p&gt;
&lt;p&gt;프로그램이 명세에 맞으며, 유지보수성을 보았을 때 올바로이 작성되었다면, 그게 정답이 아닐까 생각한다.&lt;/p&gt;
&lt;p&gt;좁은 시야에서 보자면, 굳이 JavaScript 기반의 NestJS 를 써야 하나요? 라고 물어봐야 한다고 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;내가 이러한 의견을 필력하는 데에는 이유가 있다.&lt;/p&gt;
&lt;p&gt;NestJS 는 백엔드 프로그램으로서, 보이지 않는 복잡한 데이터를 처리하는 프레임워크이다.&lt;/p&gt;
&lt;p&gt;NestJS 는 코드 생산성과 성능 면에서 모두 압도 할 만큼의 프로그램이라고는 말할 수 없다.&lt;/p&gt;
&lt;p&gt;Java 를 알고, 백엔드 로직에만 집중하고자 한다면, Spring 을 적극 추천한다.&lt;/p&gt;
&lt;p&gt;C 나 C++ 를 잘 다루는데, 보안 관련된 프로그램이나 블록체인이 관련되어 있다면, &lt;code&gt;Rust&lt;/code&gt; 의 프레임워크를 추천할 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, 만약에 JavaScript 기반의 웹 개발자이며, 백엔드 프로그램을 배워서 팀워크를 구축하고 싶다면,&lt;/p&gt;
&lt;p&gt;나는 단번에 &lt;code&gt;NestJS&lt;/code&gt; 를 사용해야 한다고 말할 것이다.&lt;/p&gt;
&lt;p&gt;AI 시대로 인해 전체적인 구상도를 만드는 상황이 매우 편리해졌다지만,&lt;/p&gt;
&lt;p&gt;결국 코드에서의 디테일과 컨벤션은 사람이 직접 건드려야 한다.&lt;/p&gt;
&lt;p&gt;또한, 2 개의 서로 다른 컨셉의 프로그래밍 언어를 사용하여 풀스택을 완성하는 것은 상대적으로 쉽지 않은 일이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;나는 몇년 전만 해도 큰 도메인(Front, Back) 에 따라 선호되는 언어로 프로그램을 작성해야 한다고 믿고 있었다.&lt;/p&gt;
&lt;p&gt;다양한 방법론들과 컨벤션을 각기 다른 언어와 프레임워크에서 살펴보며 느낀 것은,&lt;/p&gt;
&lt;p&gt;생산성이라는 생각이 든다.&lt;/p&gt;
&lt;p&gt;Java 를 잘하면, Spring 기반의 백엔드 프레임워크에 의존성을 추가하여, 웹 페이지를 제작할 수 있다.&lt;/p&gt;
&lt;p&gt;심지어는 Rust 를 잘하면, Rust 커뮤니티에서 제작한 컴포넌트 시스템을 통해&lt;/p&gt;
&lt;p&gt;&lt;code&gt;wasm&lt;/code&gt; 파일로 브라우저에 접착시켜 웹 페이지를 만드는 것이 가능하다.&lt;/p&gt;
&lt;p&gt;Rust 또한, 백 엔드 프레임워크가 존재한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;각자의 관심사와 원하는 로직 처리의 분야는 개개인이 다를 것이다.&lt;/p&gt;
&lt;p&gt;그러나, 현재 소프트웨어의 시대는 당연하고, 언어들이 담당하고 있던 서로의 도메인을 먹어치우는 과정이 포함되어&lt;/p&gt;
&lt;p&gt;특정 언어를 잘한다면, 해당 언어로 제작하면 되는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;따라서, NestJS 를 추천하는 상황이라면, 브라우저 웹을 제작하는 것을 넘어,&lt;/p&gt;
&lt;p&gt;Back-End 에서 처리되던 로직을 웹 개발자나 JavaScript and TypeScript 개발자가 처리해야 된다면&lt;/p&gt;
&lt;p&gt;NestJS 를 강력 추천한다.&lt;/p&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;h2&gt;참조 사이트&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Reddit (TypeScript 트랜스파일러가 Go 로 바뀌었을 때 반응)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.reddit.com/r/golang/comments/1j8shzb/microsoft_rewriting_typescript_in_go/&quot;&gt;https://www.reddit.com/r/golang/comments/1j8shzb/microsoft_rewriting_typescript_in_go/&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;NodeJS 공식 문서 (V8 JavaScript 엔진)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://nodejs.org/ko/learn/getting-started/the-v8-javascript-engine&quot;&gt;https://nodejs.org/ko/learn/getting-started/the-v8-javascript-engine&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;NPM 공식 문서&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://docs.npmjs.com/about-npm&quot;&gt;https://docs.npmjs.com/about-npm&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;NestJS 공식 문서&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://docs.nestjs.com/&quot;&gt;https://docs.nestjs.com/&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;</description>
      <category>Node.js/잡다 지식</category>
      <category>javascript</category>
      <category>javascript backend</category>
      <category>js</category>
      <category>js backend</category>
      <category>nestjs</category>
      <category>node.js</category>
      <category>typescript</category>
      <category>의견</category>
      <category>자바스크립트 백엔드</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/236</guid>
      <comments>https://codecreature.tistory.com/236#entry236comment</comments>
      <pubDate>Sun, 28 Sep 2025 02:31:30 +0900</pubDate>
    </item>
    <item>
      <title>멀티 스레드의 특성과 C 에서의 사용법 - 1편</title>
      <link>https://codecreature.tistory.com/235</link>
      <description>&lt;h2&gt;제목 : 멀티 스레드의 특성과 C 에서의 사용법 - 1편&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;이 글을 작성하는 이유는?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;사실 컴퓨터를 배우는 입장에서 보면, 멀티 스레드라는 개념을 맞닥들일 일이 많지는 않을 것이다.&lt;/p&gt;
&lt;p&gt;배우는 과정에서도&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;알고리즘&lt;/li&gt;
&lt;li&gt;자료구조&lt;/li&gt;
&lt;li&gt;네트워크&lt;/li&gt;
&lt;li&gt;인프라&lt;/li&gt;
&lt;li&gt;등등..&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이를 중점으로 배우게 된다.&lt;/p&gt;
&lt;p&gt;프로세스와 스레드는 운영체제에서 효율적으로 관리하고,&lt;/p&gt;
&lt;p&gt;사용자 스레드 단에서도 프레임워크가 굳이 프로세스를 복잡하게 작성하지 않게 도와주며,&lt;/p&gt;
&lt;p&gt;심지어는 굳이 스레드를 생성하지 않고, Docker or K8s(쿠버네티스) 와 같은&lt;/p&gt;
&lt;p&gt;&amp;quot;Infra Ochestration&amp;quot;(오케스트레이션) 과 같은 도구들로 단숨에 동일한 프로그램을 복제 할 수 있다.&lt;/p&gt;
&lt;p&gt;사실상 그 상위인 가벼운 운영체제를 복제한다고 볼 수 있다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;물론, 이러한 주제들 또한 단순 프로그래밍 수준이 아니라,&lt;/p&gt;
&lt;p&gt;각 프로그램 언어가 가진 고유의 문단을 이해하고, 이를 이용하여 단축하는 수준에 이르러서야&lt;/p&gt;
&lt;p&gt;이들을 그래도 어느정도 이해한다고 말할 수 있지 않을까 싶다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;특히나, 일반적인 프로그래밍에서 멀티 스레드를 사용 할 일은 거의 없다.&lt;/p&gt;
&lt;p&gt;워낙에 하드웨어 성능이 좋으며, 현재까지 만들어진 컴퓨터 알고리즘 라이브러리들은&lt;/p&gt;
&lt;p&gt;빠른 성능에 날개를 달아준 수준인데, 연산을 상황에 따라 100 배, 1000배, 등등 더한 수준도 최적화한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, 이는 어디까지나 하드웨어와 소프트웨어가 받쳐주는 수준이지,&lt;/p&gt;
&lt;p&gt;실제 프로덕션 상황에서는 단일 스레드 프로그램으로 인한 병목 현상을 맞이 할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;프로세스와 스레드는 동의어 수준으로 사용된다. (물론 프로세스는 스레드의 상위 개념)&lt;/p&gt;
&lt;p&gt;프로세스와 스레드에 대한 고찰을 자세히 적은 글이 있는데,&lt;/p&gt;
&lt;p&gt;이것을 읽고 오면 앞으로 말할 프로세스와 스레드에 대해 자세히 알게 될 것이라고 생각한다.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/216&quot;&gt;프로세스와 스레드&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;Tomcat 과 Spring 은 유저의 요청에 따라 스레드가 생성되고,&lt;/p&gt;
&lt;p&gt;완료 시 스레드를 Pool (Thread Pool) 에 반환하는 특성을 가진다.&lt;/p&gt;
&lt;p&gt;이를 자동으로 성립시켜 주는데,&lt;/p&gt;
&lt;p&gt;덕분에 Tomcat, Spring 기반의 프로젝트를 실행하는 개발자는&lt;/p&gt;
&lt;p&gt;굳이 각 연결에 대한 비동기적인 처리를 걱정 할 필요 없이,&lt;/p&gt;
&lt;p&gt;&amp;quot;한 사람에 대한 연산 및 데이터 요청&amp;quot; 에 집중할 수 있다.&lt;/p&gt;
&lt;p&gt;마치 단일 스레드 로직을 작성하는 것 같지만, 결국 프레임워크가 스레드를 pool 에서 꺼내어&lt;/p&gt;
&lt;p&gt;이를 동시에 처리하는 것이 매우 인상적이라고 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, 하나의 프로세스에서 처리되는 과정에서,(그래픽, GUI, 연산 등등)&lt;/p&gt;
&lt;p&gt;우리는 프레임워크에 할당된 기능만으로는 한계점이 분명히 존재한다는 것을 인지해야 한다.&lt;/p&gt;
&lt;p&gt;그 분들이 만들어 놓은 깊고도 깊은 기능을 이용하고도 제작 못하는 논리구조가 있을 가능성을 고려하면.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;거의 95 % 는 심연의 Function or Class 로 해결할 수 있을 테지만,&lt;/p&gt;
&lt;p&gt;단순히 특정 기능을 외워서 사용하기보단, 이러한 기능의 동작 원리를 이해해야&lt;/p&gt;
&lt;p&gt;다양한 언어 및 프레임워크 환경에서도 나의 생산성이 올라갈 수 있을 것이라고 생각했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;현재는 AI 시대의 개발자의 역할에 대해서 깊게 고민했으며,&lt;/p&gt;
&lt;p&gt;나의 역할에 대한 고찰과 그 과정 중에 있다.&lt;/p&gt;
&lt;p&gt;현재는 C 언어를 통해, 다양한 언어들이 어떻게 편의적으로 표현되었는지,&lt;/p&gt;
&lt;p&gt;어떤 특징을 따왔는지 배우며, 직접 구현하고 있다.&lt;/p&gt;
&lt;p&gt;예시로, 나는 이런 글을 작성한다.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/233&quot;&gt;C 에서 입력 토큰화 메서드 작성하기&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;C 언어를 통해서 스레드를 구현하면,&lt;/p&gt;
&lt;p&gt;다양한 언어들이 각자 생성하는 프로세스, 스레드의 방식을 이해할 수 있을 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;pthread 란 무엇인가?&lt;/h2&gt;
&lt;p&gt;POSIX 규칙 및 API 를 따르고 적용하고 있는 운영체제,&lt;/p&gt;
&lt;p&gt;예를 들어 윈도우, 리눅스, 리눅스 배포판, macOS 와 같은 운영체제에서 사용 가능한 라이브러리이다.&lt;/p&gt;
&lt;p&gt;이정도면 Cross Platform Lib 로 취급해도 무방하다는 생각이 든다.&lt;/p&gt;
&lt;p&gt;&amp;quot;pthread&amp;quot; 는, &lt;strong&gt;POSIX Threads&lt;/strong&gt; 의 약자이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;pthread&lt;/strong&gt; 라이브러리는 여러 운영체제에서 스레드를 다루기 위한 다양한 API 를 제공한다.&lt;/p&gt;
&lt;p&gt;또한, 스레드에 원하는 조건이나 객체(구조체?) 속성을 지정하여 원하는 대로 스레드를 사용할 수도 있다.&lt;/p&gt;
&lt;p&gt;속성에 대한 예를 들면,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;스레드 및 스레드 속성 오브젝트&lt;/li&gt;
&lt;li&gt;뮤텍스 및 뮤텍스 속성 오브젝트&lt;/li&gt;
&lt;li&gt;조건 변수 및 조건 속성 오브젝트&lt;/li&gt;
&lt;li&gt;읽기 및 쓰기 잠금&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;여기서 처음 보는 것은, Mutex 라는 용어이다.&lt;/p&gt;
&lt;p&gt;살짝 미리 보았는데, 예를 들어서 &amp;quot;터미널에 출력&amp;quot; 하는 I/O 객체 혹은 구조체 데이터가 공유되고 있을 때,&lt;/p&gt;
&lt;p&gt;특정 스레드가 터미널 I/O 를 시작하면서, Mutex 라는 오브젝트를 이용하여 &amp;quot;잠금&amp;quot; 을 한다.&lt;/p&gt;
&lt;p&gt;&amp;quot;잠금&amp;quot; 시, 입출력 객체를 공유하고 있는 스레드 사이에서, 현재 &amp;quot;잠금&amp;quot; 이후 사용중인 스레드를 제외하고,&lt;/p&gt;
&lt;p&gt;어떠한 스레드도 현재 입출력 객체를 사용할 수 없다.&lt;/p&gt;
&lt;p&gt;그리고, I/O 가 끝나면, 해당 스레드는 Mutex 를 이용하여 &amp;quot;해제&amp;quot; 를 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;원하는 디테일한 상황이 아니라면, 단순히 &lt;code&gt;NULL&lt;/code&gt; 을 속성에 집어넣어 Default 속성으로 사용할 수도 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;pthread 스레드 초기 작성 이전, 알아두면 좋을 함수와 구조체&lt;/h3&gt;
&lt;p&gt;나 또한 스레드를 저수준으로 다루어 보는 것은 처음이라서,&lt;/p&gt;
&lt;p&gt;IBM 기술문서, 그리고 네이버 기술 블로그를 혼합해서 pthread 를 이해하는 중이다.&lt;/p&gt;
&lt;p&gt;여기서 기술되며, 내가 앞으로 말하게 될 단어와 용어에 대해서 정리를 해야겠다고 생각한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;루틴 : &lt;code&gt;routine&lt;/code&gt; : 스레드를 생성할 때, 실행 할 함수(함수 포인터)&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pthread_t&lt;/code&gt; : 스레드 ID --&amp;gt; 정수이며, 생성된 스레드 ID 를 담을 타입 --&amp;gt; 스레드 핸들러&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pthread_attr_t&lt;/code&gt; : 스레드 속성 Object or 구조체 포인터 --&amp;gt; 스레드 성질&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pthread_attr_init(&amp;amp;pthread_attr_r)&lt;/code&gt; : 스레드 속성 객체를 기본값으로 초기화한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pthread_attr_destroy(&amp;amp;pthread_attr_r)&lt;/code&gt; : 스레드 속성 객체를 소멸시킨다.  &lt;br/&gt; --&amp;gt; init 에서 동적 할당한 저장영역 해제&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;int pthread_create(&amp;amp;pthread_t, &amp;amp;pthread_attr_t, init_routine, args)&lt;/code&gt; &lt;br/&gt; : 이 함수를 이용하여 스레드를 생성 후 &lt;code&gt;init_routine&lt;/code&gt; 을 실행한다. &lt;br/&gt; : 첨부된 루틴이 끝나면, 자동으로 끝난다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;int pthread_join(&amp;amp;pthread_t, void** status)&lt;/code&gt; &lt;br/&gt; : 이 메서드를 호출한 스레드는, 인자로 들어간 스레드가 종료 될 때 까지 로직을 중단 및 차단한다. &lt;br/&gt; : 그리고 인자로 들어간 대상 스레드의 종료 상태는 &lt;code&gt;status&lt;/code&gt; 매개변수로 주입한다. &lt;br/&gt; : 성공 시 &lt;code&gt;0&lt;/code&gt; 을 반환하며, 오류 시 해당 오류 번호를 리턴한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;int pthread_detach(&amp;amp;pthread_t)&lt;/code&gt; &lt;br/&gt; : 이 함수는 바로 위의 &lt;code&gt;join&lt;/code&gt; 과 대치되는 로직으로 사용된다. &lt;br/&gt; : 인자로 들어간 스레드는 로직이 종료 될 경우, 위의 함수를 사용하지 않고, 그 즉시 모든 자원을 free 한다. &lt;br/&gt; : &lt;code&gt;pthread_join&lt;/code&gt; 의 경우, 메인 스레드가 서브 스레드가 종료 될 때 까지 기다리는 느낌이라면, 이 함수는 기다리지 않는다는 느낌으로 해석했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;void pthread_exit(void* status)&lt;/code&gt; &lt;br/&gt; : 이 함수는 프로세스의 전체 종료를 의미하는 &lt;code&gt;exit&lt;/code&gt; 함수와는 다르게, 스레드를 종료한다. &lt;br/&gt; :이 스레드를 호출하면, 호출 스레드가 종료된다. &lt;br/&gt; : 여기서 메인 함수에서 만약에 &amp;quot;호출 스레드 종료&amp;quot; 인 &lt;code&gt;pthread_exit&lt;/code&gt; 을 선언하게 된다면, &lt;br/&gt; 메인이 선언한 스레드들은 자신들의 역할이 끝날 때 까지 끝나지 않는다는 것이, &lt;br/&gt; &lt;code&gt;exit&lt;/code&gt; 과의 차이점이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;h3&gt;pthread 선언의 흐름&lt;/h3&gt;
&lt;p&gt;아무래도 내가 &lt;code&gt;pthread.h&lt;/code&gt; 에 대해서 자세히 아는 것은 아니라서,&lt;/p&gt;
&lt;p&gt;전체적인 스레드 관리의 흐름과 더불어 되새김질 하기 위해서 그래프를 만든다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TB

subgraph variable [&amp;quot;스레드를 위한 변수 공간 생성&amp;quot;]
    pthread_t(&amp;quot;pthread_t &amp;lt;br/&amp;gt; : 스레드 핸들러 및 스레드 ID&amp;quot;)
    pthread_attr_t(&amp;quot;pthread_attr_t &amp;lt;br/&amp;gt; : 해당 스레드의 속성 혹은 성질&amp;quot;)
end

subgraph initialize [&amp;quot;스레드 속성 초기화 -&amp;gt; 디폴트&amp;quot;]
    pthread_attr_init(&amp;quot;pthread_attr_init &amp;lt;br/&amp;gt; : 스레드 속성 디폴트 혹은 사용자 지정&amp;quot;)
end

subgraph create [&amp;quot;스레드 생성 With 함수 포인터&amp;quot;]
    pthread_create(&amp;quot;pthread_create &amp;lt;br/&amp;gt; : 인자는, &amp;lt;br/&amp;gt; - pthread_t &amp;lt;br/&amp;gt; - pthread_attr_t &amp;lt;br/&amp;gt; - function ptr &amp;lt;br/&amp;gt; - arg(인자)&amp;quot;)
end

subgraph join [&amp;quot;메인은 서브 스레드를 기다린다.&amp;quot;]
    pthread_join(&amp;quot;pthread_join &amp;lt;br/&amp;gt; : 이 함수를 호출한 스레드는 인자로 넣은 스레드가 종료 될 때 까지 로직을 중단하고 기다린다.&amp;quot;)
end

subgraph destroy [&amp;quot;안쓰는 메모리 속성 해제&amp;quot;]
    pthread_attr_destroy(&amp;quot;pthread_attr_destroy &amp;lt;br/&amp;gt; : 더 이상 스레드 생성 하지 않거나, 참조 불필요 시 속성 해제&amp;quot;)
end

subgraph exit [&amp;quot;스레드 종료&amp;quot;]
    pthread_exit(&amp;quot;pthread_exit &amp;lt;br/&amp;gt; : 이 함수(루틴) 을 선언한 스레드는 정상 종료 반환을 하게 된다. &amp;lt;br/&amp;gt; 이는 프로세스 전체 종료가 아니며, 선언한 해당 스레드만을 의미한다. &amp;lt;br/&amp;gt; 만약 메인에서 호출한다면, 메인에서 호출된 스레드들은 종료되지 않으며, 자기 할 일을 하고 끝낸다.&amp;quot;)
end

variable --&amp;gt; initialize

initialize --&amp;gt; create

create --&amp;gt; join

join --&amp;gt; destroy

destroy --&amp;gt; exit&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이는 프로세스의 기능 중에서 가장 중요하고, 단순한 것들을 위주로 설명했다.&lt;/p&gt;
&lt;p&gt;더 자세한 이해를 원한다면, 맨 밑의 참조 사이트에서 IBM 과 JOINC 사이트를 참조하면 좋다!&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;코드 작성 후 동작 확인&lt;/h2&gt;
&lt;p&gt;프로세스와 스레드 이론을 이전에 공부하면서 느꼈던 건데,&lt;/p&gt;
&lt;p&gt;잘못하면 내 컴퓨터에 Fork Bomb 을 만들어 버릴 수 있어서 조심히 다루어겠다는 생각이 든다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;Fork Bomb&lt;/strong&gt; 이란, 스레드가 프로세스를 영원히 fork 해버리는 상황. &lt;br/&gt;&lt;br&gt;순식간에 2^xx 번 복제실행을 할 수 있다는 생각이 든다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;br/&gt;

&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include&amp;lt;pthread.h&amp;gt;
#include&amp;lt;stdio.h&amp;gt;

void *test_routine(void* data) {
    int i = 0;
    while(i &amp;lt;= 5) {
        fputc(i++ + &amp;#39;0&amp;#39;, stdout); fputc(&amp;#39;\n&amp;#39;, stdout);
    }

    return data;
}


int main(void) {
    pthread_t thread_t;
    pthread_attr_t thread_attr_t;

    pthread_attr_init(&amp;amp;thread_attr_t);

    pthread_create(&amp;amp;thread_t, &amp;amp;thread_attr_t, test_routine, NULL);

    int status;
    pthread_join(thread_t, (void*)&amp;amp;status);

    // or pthread_exit(&amp;amp;status);

    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;만약, &lt;code&gt;pthread_join&lt;/code&gt; 이나, &lt;code&gt;pthread_exit&lt;/code&gt; 을 선언하지 않는다면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;main&lt;/code&gt; 함수에서 먼저 &lt;code&gt;return 0&lt;/code&gt; 해버리면서, 프로세스 내 모든 스레드가 종료되면서&lt;/p&gt;
&lt;p&gt;숫자들을 출력 할 수 없다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ &amp;lt;만든 파일.c&amp;gt; -Wall -Wextra -pthread -o &amp;lt;원하는 파일 이름&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-Wall&lt;/code&gt; : 컴파일 단계에서 알려줄 수 있는 모든 경고를 출력.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-Wextra&lt;/code&gt; : 확장된 영역의 경고를 추가한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-pthread&lt;/code&gt; : &lt;code&gt;pthread.h&lt;/code&gt; 를 사용한다면, 무조건 선언해야 하는 옵션이다. &lt;br/&gt; 컴파일 중 링크 단계에서 pthread 를 인식하게 해주므로 매우 중요.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-o&lt;/code&gt; : 어떤 파일 이름으로 출력물을 내뱉을 건지.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;원한다면, 이런식으로 축소해도 된다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ &amp;lt;만든 파일.c&amp;gt; -pthread -o &amp;lt;원하는 이름&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;그리고, 파일을 간단하게 실행 해 보자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ ./&amp;lt;컴파일 된 출력물 이름&amp;gt;

# 나의 경우. --&amp;gt; 나는 현재 문서에 따른 분류를 위해 &amp;#39;번호&amp;#39; 를 매기고 있음.
$ ./6-exam-1
0
1
2
3
4
5&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;레퍼런스 &amp;#39;&amp;amp;&amp;#39; 는 왜 사용하는 걸까?&lt;/h3&gt;
&lt;p&gt;나는 백준 문제를 다양한 프로그래밍 언어로 풀었는데,&lt;/p&gt;
&lt;p&gt;현재는 그 중 C99 기준 C 를 사용하여 문제를 풀고 있다.&lt;/p&gt;
&lt;p&gt;극도로 제한된 라이브러리들을 &amp;#39;직접 구현&amp;#39; 하며, 문법 표현력을 강제로 늘리고 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그 과정에서, 나는 레퍼런스를 사용하지 않고, 모든 구조체나 배열의 값을 &lt;code&gt;*&lt;/code&gt; 포인터를 사용하여 표현했는데,&lt;/p&gt;
&lt;p&gt;프로세스를 다루는 과정에서는 &lt;code&gt;int&lt;/code&gt; 타입을 &lt;code&gt;void*&lt;/code&gt; 로 넣기 위해, &lt;code&gt;&amp;amp;&lt;/code&gt;(레퍼런스) 를 사용하는 등,&lt;/p&gt;
&lt;p&gt;메인 콜백 메모리에 잠시 남을 데이터나 원시값을 &lt;code&gt;&amp;amp;&lt;/code&gt; 를 이용하여 리턴값을 가져오는 것이 이해가 되지 않았다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;정확한 레퍼런스와 포인터의 의미&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;amp;&lt;/code&gt; : 해당 타입의 &amp;#39;주소값&amp;#39; 을 생성하여 넘김.(혹은 이미 있는 주소값)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;*&lt;/code&gt; : 이 값에 지정된 포인터의 메모리로 간다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;결과적으로&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;예제에서 &lt;code&gt;int&lt;/code&gt; 타입을 인자로 전해 줄 때, &lt;code&gt;&amp;amp;&lt;/code&gt; 를 사용 한 이유는,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;int&lt;/code&gt; 인자를 받는 함수가 내부적으로 &lt;code&gt;int*&lt;/code&gt; 를 결국 참조하기 때문이다.&lt;/p&gt;
&lt;p&gt;즉, 굳이 예제에서 복잡하게 동적 메모리, &lt;code&gt;malloc&lt;/code&gt; 등등을 이용하여 참조하게 만들고,&lt;/p&gt;
&lt;p&gt;이 메모리를 해제하는 과정을 배제하고, 정확히 스레드 로직에 집중하기 위해 이러한 예제를 든 것이다.&lt;/p&gt;
&lt;p&gt;내가 원한다면, (레퍼런스를 없애고 싶다면,) 전부 &lt;code&gt;*&lt;/code&gt; 포인터 타입으로 만들어서 동작하게 만들 수 있다.&lt;/p&gt;
&lt;p&gt;그러나, 예제에서는 선택과 집중을 위해 레퍼런스&lt;code&gt;&amp;amp;&lt;/code&gt; 를 사용하여 간단히 표현했다.&lt;/p&gt;
&lt;br/&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;amp;thread_t&lt;/code&gt; 가 된 이유는, 이미 &lt;code&gt;thread_t&lt;/code&gt; 가 포인터 타입으로 선언되었기 때문이다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;amp;thread_attr_t&lt;/code&gt; 가 된 이유도, 위의 이유와 동일하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;h3&gt;함수 앞에 &amp;#39;*&amp;#39; 는 왜 붙은 걸까?&lt;/h3&gt;
&lt;p&gt;C 언어를 사용하여 문제를 풀면서 느낀 건데,&lt;/p&gt;
&lt;p&gt;자료구조를 구조체로 만들어서 사용하면서, 함수의 주입이 생각보다 편하다는 것이었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;함수는 포인터 메모리로서 저장될 수 있다.&lt;/p&gt;
&lt;p&gt;또한, 함수는 특정한 인자들과 반환값을 가지는 &amp;#39;함수 포인터&amp;#39; 를 생성 할 수도 있다.&lt;/p&gt;
&lt;p&gt;나는, 이러한 함수를 만들 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int test(int n, int m);

int main(void) {

    int (*function)(int, int) = *test;

    int result = function(1, 2);

    return 0;
}

int test(int n, int m) {
    return n + m;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;void* 는 무엇일까?&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;void&lt;/code&gt; 는 어떠한 것도 없는 &amp;#39;공허&amp;#39; 를 의미한다.&lt;/p&gt;
&lt;p&gt;반환 값에 &lt;code&gt;void&lt;/code&gt; 가 붙는다면, 어떠한 것도 반환하지 않음을 의미한다.&lt;/p&gt;
&lt;p&gt;그러나, &lt;code&gt;void*&lt;/code&gt; 처럼, 포인터가 붙으면 의미가 180 도 변한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;void*&lt;/code&gt; 는, 해당 값이 어떠한 값도 될 수 있음을 의미한다. (단순한 값, 혹은 포인터)&lt;/p&gt;
&lt;p&gt;여기서 포인터 &lt;code&gt;*&lt;/code&gt; 가 하나씩 더 붙을 때 마다, 이 변수에서 참조할 수 있는 차원이 점점 높아진다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;보통, &lt;code&gt;void*&lt;/code&gt;, &lt;code&gt;void**&lt;/code&gt; 를 많이 이용한다.&lt;/p&gt;
&lt;p&gt;JavaScript, TypeScript 를 배운 사람에게 이를 설명하자면, 약간 &lt;code&gt;any&lt;/code&gt; 의 느낌이 있다고 보면 된다.&lt;/p&gt;
&lt;p&gt;단, 전달된 값을 &amp;#39;정확히&amp;#39; Parsing 해야 사용할 수 있다. (주소 or 타입 or 구조체 or 배열 등등..)&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;스레드 간의 동기화&lt;/h2&gt;
&lt;p&gt;네트워크 통신, 그리고 멀티 스레딩의 꽃이라고 부를 수 있는 주제가 아닐까 생각한다.&lt;/p&gt;
&lt;p&gt;나는 멀티 스레드를 처음 구현한 언어가 JavaScript 였다.&lt;/p&gt;
&lt;p&gt;여기서 상호작용을 구현하면서 공유 데이터 사용과 변화를 적용하는 것이 매우 헷갈렸었는데,&lt;/p&gt;
&lt;p&gt;이번에 IBM 문서를 통해 이해하게 될 것 같다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;스레드 라이브러리는, 이러한 &amp;quot;동기화 매커니즘&amp;quot; 을 제공한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&amp;quot;뮤텍스&amp;quot; (&lt;code&gt;Mutex&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;조건 변수&lt;/li&gt;
&lt;li&gt;읽기-쓰기 잠금&lt;/li&gt;
&lt;li&gt;&amp;quot;조인&amp;quot; (&lt;code&gt;pthread_join&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;h3&gt;뮤텍스란? (Mutex)&lt;/h3&gt;
&lt;p&gt;IBM 문서의 설명을 그대로 가져오면,&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;뮤텍스는 상호 배타적 잠금입니다. 단 하나의 스레드만이 잠글 수 있습니다.&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;라고 표현한다.&lt;/p&gt;
&lt;p&gt;누군가 집에서 화장실을 이용하고 있다면, 다른 누군가는 나올 때 까지 기다려야 할 것이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;사람 == 스레드&lt;/strong&gt;, &lt;strong&gt;화장실 == 뮤텍스 속성 오브젝트&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이렇게 표현 할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그래서 뮤텍스는 왜 필요할까?&lt;/p&gt;
&lt;p&gt;만약에 입출력에 관련된 멀티 스레드를 작성하고, 이를 공유한다고 가정 해 보자.&lt;/p&gt;
&lt;p&gt;A 스레드와 B 스레드는 출력하고자 하는 내용이 다르다고 생각 해 보자.&lt;/p&gt;
&lt;p&gt;A 스레드와 B 스레드가 각각 출력하고자 하는 긴 내용을 각자 거의 동시에 출력을 시작한다.&lt;/p&gt;
&lt;p&gt;그렇다면, A 와 B 에서 내보낸 내용이 섞여서 나올 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이러한 현상을 막고자, &amp;quot;뮤텍스&amp;quot; 를 사용한다.&lt;/p&gt;
&lt;h3&gt;뮤텍스 과정에서 사용되는 간단한 구조체와 함수들&lt;/h3&gt;
&lt;p&gt;뮤텍스 과정에서 사용될 수 있는 &amp;quot;상대적으로&amp;quot; 간략한 구조체와 함수들이라고 볼 수 있다.&lt;/p&gt;
&lt;p&gt;내가 &amp;quot;상대적으로 간략&amp;quot; 하다고 말하는 이유가,&lt;/p&gt;
&lt;p&gt;뮤텍스 구조체(객체)가 정말로 일종의 &amp;quot;Key&amp;quot; 처럼 이용되기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;스레드, 혹은 프로세스에는 현재 상태를 나타내는 문자열들이 존재한다.&lt;/p&gt;
&lt;p&gt;여기서, &lt;code&gt;R&lt;/code&gt;, &lt;code&gt;S&lt;/code&gt;, &lt;code&gt;X&lt;/code&gt;, ... 등등 여러가지 상태가 있다.&lt;/p&gt;
&lt;p&gt;준비중, 대기중, 실행중 등등을 &lt;code&gt;ps&lt;/code&gt; 명령어로 볼 수 있는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;여기서 중요한 것은, 어떻게 프로세스나 스레드가 &amp;quot;대기 상태&amp;quot; 에 있을 수 있나 하는 것이다.&lt;/p&gt;
&lt;p&gt;프로세스나 스레드가 &amp;quot;대기 상태&amp;quot; 로 진입하는 것은, &lt;code&gt;pthread_join&lt;/code&gt; 루틴 호출 후 대기와 같은 상황도 있겠지만,&lt;/p&gt;
&lt;p&gt;Mutex 객체를 공유하며, 특정 스레드가 Mutex 에 &lt;code&gt;lock&lt;/code&gt; 을 걸어버린 경우,&lt;/p&gt;
&lt;p&gt;해당 스레드가 &lt;code&gt;unlock&lt;/code&gt; 을 할 때 까지 기다리는 상황도 &amp;quot;대기 상태&amp;quot; 라고 부를 수 있다. EX - &lt;code&gt;S&lt;/code&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;처음 제시했던 함수들을 다시 복기하며, 새로운 구조체와 함수들도 익혀보자.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pthread_t&lt;/code&gt; : 스레드 ID --&amp;gt; 스레드 핸들러 (이전에도 사용)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pthread_mutex_t&lt;/code&gt; : 뮤텍스 구조체 --&amp;gt; 이거 사용해서 스레드를 unlock 및 lock 한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pthread_mutex_attr_t&lt;/code&gt; : 뮤텍스 사용 할 때 행동할 방침같은 것&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pthread_mutex_init&lt;/code&gt; : 뮤텍스 객체(구조체) 를 디폴트 상태로 만듬&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pthread_mutex_destroy&lt;/code&gt; : 다 사용했으면 메모리를 해제하는 데 사용한다.(뮤텍스 객체를)&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pthread_create&lt;/code&gt; : 스레드 생성 시 기본으로 사용됨. (이전에도 사용됨)&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pthread_mutex_lock&lt;/code&gt; : 현재 뮤텍스 객체에 잠금을 가하면, 같은 뮤텍스에 접근하는 다른 스레드들이 대기한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pthread_mutex_unlock&lt;/code&gt; : 보통 lock 을 한 스레드가 &lt;code&gt;unlock&lt;/code&gt; 하여 기다리는 스레드가 뮤텍스에 접근할 수 있도록 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;h3&gt;코드 작성 후 동작을 확인 해 보자.&lt;/h3&gt;
&lt;p&gt;몇 가지 예시를 들 건데,&lt;/p&gt;
&lt;p&gt;파일 스코프로 선언된 전역 뮤텍스 객체에 접근하는 예제와,&lt;/p&gt;
&lt;p&gt;인자로 스레드 함수에 뮤텍스 객체가 전달되어 공유되는 예제이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;상황 - 뮤텍스 없음&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;
// 함수 인자로 뮤텍스 객체를 공유하는 예제

#include&amp;lt;stdio.h&amp;gt;
#include&amp;lt;pthread.h&amp;gt;
#include&amp;lt;unistd.h&amp;gt; // unix 에서 파생된 유틸리티들. 파일을 읽고 쓰거나, 기다리는 행위를 보조한다.


void *fn_1(void* data);
void *fn_2(void* data);

// 파일 전역 객체인 Mutex 선언
pthread_mutex_t mutex;

int main(void) {
    pthread_t t1;
    pthread_t t2;

    pthread_attr_t attr;

    pthread_attr_init(&amp;amp;attr);

    int start = 1;
    pthread_create(&amp;amp;t1, &amp;amp;attr, fn_1, &amp;amp;start);
    sleep(1);
    pthread_create(&amp;amp;t2, &amp;amp;attr, fn_2, &amp;amp;start);


    // status 를 조회할 필요는 없어 NULL 로 넣음.
    pthread_join(t1, NULL); pthread_join(t2, NULL);


    return 0;
}

void *fn_1(void* data) {
    int start = *(int*)data;

    int idx = start;
    while(idx &amp;lt; start + 4) {
        printf(&amp;quot;%d &amp;quot;, idx++);
        sleep(2);
    }
    printf(&amp;quot;\n&amp;quot;);

    pthread_exit(NULL);

    return data;
}
void *fn_2(void* data) {
    int start = *(int*)data;

    int idx = start;
    while(idx &amp;lt; start + 4) {
        printf(&amp;quot;%d &amp;quot;, idx++);
        sleep(2);
    }
    printf(&amp;quot;\n&amp;quot;, stdout);

    pthread_exit(NULL);

    return data;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ gcc &amp;lt;이 파일.c&amp;gt; -Wall -Wextra -pthread -o &amp;lt;이 파일 이름&amp;gt;
$ ./&amp;lt;이 파일 이름&amp;gt;
1 1 2 2 3 3 4 4

$&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;보면, &lt;code&gt;sleep&lt;/code&gt; 을 사용하여 1초, 혹은 2 초를 기다리게 했는데,&lt;/p&gt;
&lt;p&gt;이는 각 스레드 출력을 교차시켜 뮤텍스가 없는 상황을 연출하기 위함이다.&lt;/p&gt;
&lt;p&gt;보다시피, 스레드가 대기하지 않고, 메인 스레드만이 &lt;code&gt;sleep(1)&lt;/code&gt; 을 통해 1 초만 기다리도록&lt;/p&gt;
&lt;p&gt;지시했기 때문에, &lt;code&gt;fn_1&lt;/code&gt; 스레드와, &lt;code&gt;fn_2&lt;/code&gt; 스레드가 &amp;quot;동시에&amp;quot; 실행 된 것을 볼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;상황 - 전역 뮤텍스 객체에 접근&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;// 전역 스코프로 뮤텍스 객체에 접근하는 예제

#include&amp;lt;stdio.h&amp;gt;
#include&amp;lt;pthread.h&amp;gt;
#include&amp;lt;unistd.h&amp;gt;

void *fn_1(void* data);
void *fn_2(void* data);

// 파일 전역 객체인 Mutex 선언
pthread_mutex_t mutex;

int main(void) {
    pthread_t t1;
    pthread_t t2;

    pthread_attr_t attr;

    // 스레드 기본 속성 Default
    pthread_attr_init(&amp;amp;attr);

    // 뮤텍스 객체 기본 속성으로 설정 --&amp;gt; NULL
    pthread_mutex_init(&amp;amp;mutex, NULL);

    int start = 1;
    pthread_create(&amp;amp;t1, &amp;amp;attr, fn_1, &amp;amp;start);
    sleep(1);
    pthread_create(&amp;amp;t2, &amp;amp;attr, fn_2, &amp;amp;start);


    // status 를 조회할 필요는 없어 NULL 로 넣음.
    pthread_join(t1, NULL); pthread_join(t2, NULL);


    return 0;
}

void *fn_1(void* data) {
    int start = *(int*)data;

    // 뮤텍스 잠금
    pthread_mutex_lock(&amp;amp;mutex);

    int idx = start;
    while(idx &amp;lt; start + 4) {
        printf(&amp;quot;%d &amp;quot;, idx++);
        sleep(2);
    }
    printf(&amp;quot;\n&amp;quot;);

    // 뮤텍스 잠금 해제
    pthread_mutex_unlock(&amp;amp;mutex);

    pthread_exit(NULL);

    return data;
}
void *fn_2(void* data) {
    int start = *(int*)data;

    // 뮤텍스 잠금
    pthread_mutex_lock(&amp;amp;mutex);

    int idx = start;
    while(idx &amp;lt; start + 4) {
        printf(&amp;quot;%d &amp;quot;, idx++);
        sleep(2);
    }
    printf(&amp;quot;\n&amp;quot;, stdout);

    // 뮤텍스 잠금 해제
    pthread_mutex_unlock(&amp;amp;mutex);

    pthread_exit(NULL);

    return data;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ gcc &amp;lt;이 파일.c&amp;gt; -Wall -Wextra -pthread -o &amp;lt;이 파일 이름&amp;gt;
$ ./&amp;lt;이 파일 이름&amp;gt;
1 2 3 4
1 2 3 4
$&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;이제 &amp;quot;파일 스코프&amp;quot;로 선언된, 전역 객체 &lt;code&gt;pthread_mutex_t&lt;/code&gt; 를 이용하여,&lt;/p&gt;
&lt;p&gt;스레드 잠금 및 해제를 수행한 것을 볼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;상황 - 인자로 전달된 뮤텍스 객체에 접근&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;
// 함수 인자로 뮤텍스 객체를 공유하는 예제

#include&amp;lt;stdio.h&amp;gt;
#include&amp;lt;stdlib.h&amp;gt;
#include&amp;lt;unistd.h&amp;gt;
#include&amp;lt;pthread.h&amp;gt;

// void* data 인자로 전달할 Element 구조체 선언.
typedef struct Element {
    int start;
    pthread_mutex_t* mutex; // 생성된 뮤텍스 객체의 &amp;quot;주소&amp;quot; 를 담기 위함
    FILE* stream; // 출력 스트림
} Element;

void *fn_1(void* data);
void *fn_2(void* data);

int main(void) {
    pthread_t thread_1; // --&amp;gt; fn_1 실행
    pthread_t thread_2; // --&amp;gt; fn_2 실행
    pthread_attr_t thread_attr;

    pthread_mutex_t mutex; // 여기서 뮤텍스 객체가 자동으로 생성됨. --&amp;gt; 메모리가 있는 상태 (중요)

    pthread_mutexattr_t mutex_attr;

    // 속성들 초기화
    pthread_attr_init(&amp;amp;thread_attr);
    pthread_mutexattr_init(&amp;amp;mutex_attr);

    // 디폴트 상태로 뮤텍스 객체 초기화
    pthread_mutex_init(&amp;amp;mutex, &amp;amp;mutex_attr);

    // 스레드에 전달할 데이터 구조체 메모리 선언
    Element* elem = (Element*)malloc(sizeof(Element));
    elem-&amp;gt;mutex = &amp;amp;mutex; elem-&amp;gt;start = 1; elem-&amp;gt;stream = stdout;

    // fn_1 스레드 시작
    pthread_create(&amp;amp;thread_1, &amp;amp;thread_attr, fn_1, (void*)elem);
    sleep(1);

    // 1 초 후 fn_2 스레드 시작
    pthread_create(&amp;amp;thread_2, &amp;amp;thread_attr, fn_2, (void*)elem);

    // 모든 스레드가 종료 될 때 까지 기다리기
    pthread_join(thread_1, NULL);
    pthread_join(thread_2, NULL);

    // 생성했던 뮤텍스, 스레드 속성 객체를 해제.
    pthread_mutexattr_destroy(&amp;amp;mutex_attr);
    pthread_attr_destroy(&amp;amp;thread_attr);

    // 메인 로직 스레드 해제
    pthread_exit(NULL);

    return 0;
}

void *fn_1(void* data) {
    // 인자로 전달된 데이터는 Element* 형태이다.
    Element* elem = (Element*)data;

    // 저장된 뮤텍스 객체의 &amp;quot;주소&amp;quot; 를 가져온다.
    pthread_mutex_t* mutex = elem-&amp;gt;mutex;

    // 뮤텍스 잠금
    pthread_mutex_lock(mutex);

    int start = elem-&amp;gt;start;
    FILE* stream = elem-&amp;gt;stream;

    int idx = start;
    while(idx &amp;lt; start + 3){
        fputc(idx + &amp;#39;0&amp;#39;, stream); fputc(&amp;#39; &amp;#39;, stream);
        idx++;
        sleep(2);
    }
    fputc(&amp;#39;\n&amp;#39;, stream);

    elem-&amp;gt;start = idx;

    // 뮤텍스 해제
    pthread_mutex_unlock(mutex);

    // 스레드 해제
    pthread_exit(NULL);
}
void *fn_2(void* data) {
    Element* elem = (Element*)data;

    pthread_mutex_t* mutex = elem-&amp;gt;mutex;

    pthread_mutex_lock(mutex);

    int start = elem-&amp;gt;start;
    FILE* stream = elem-&amp;gt;stream;

    int idx = start;
    while(idx &amp;lt; start + 3){
        fputc(idx + &amp;#39;0&amp;#39;, stream); fputc(&amp;#39; &amp;#39;, stream);
        idx++;
        sleep(2);
    }
    fputc(&amp;#39;\n&amp;#39;, stream);

    pthread_mutex_unlock(mutex);

    pthread_exit(NULL);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;실행 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ gcc &amp;lt;파일.c&amp;gt; .... -pthread -o &amp;lt;파일&amp;gt;
$ ./&amp;lt;파일&amp;gt;
1 2 3
4 5 6&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;위의 예제에는 큰 맹점이 존재한다.&lt;/h3&gt;
&lt;p&gt;위의 예제를 생각했던 대로 차례대로 실행시키기 위하여 생각보다 많은 수정이 필요했다.&lt;/p&gt;
&lt;p&gt;공식 문서에서는 파일 전역 스코프로 선언되어 함수 내부에서 곧바로 참조하던데,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;main&lt;/code&gt; 함수에서 뮤텍스를 선언하고, 이를 넘기고 나면, 자꾸 동기화가 풀리는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이 현상이 도무지 이해가 되지 않았다.&lt;/p&gt;
&lt;p&gt;알고 보니, 동적으로 생성한 뮤텍스가 아니라, &amp;quot;정적 스코프로 선언한 뮤텍스 객체&amp;quot; 인 것이다.&lt;/p&gt;
&lt;p&gt;나도 이게 이해가 되질 않았다. 마치 일반 타입으로 선언되어,&lt;/p&gt;
&lt;p&gt;새로운 스레드 함수에서 단순하게 &lt;code&gt;pthread_mutex_t mutex = ...&lt;/code&gt; 로 할당을 할 수가 없는 것이다.&lt;/p&gt;
&lt;p&gt;동기화가 깨지고, 다시 2 개의 스레드가 동시에 출력하고 있는 것을 뵬 수 있었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;정답은, &lt;code&gt;pthread_mutex_t mutex&lt;/code&gt; 를 선언하는 그 즉시, 새로운 뮤텍스 객체가 생성되는 것이었다.&lt;/p&gt;
&lt;p&gt;내가 하는 행동은,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;main&lt;/code&gt; 의 뮤텍스, &lt;code&gt;fn_1&lt;/code&gt; 의 뮤텍스, &lt;code&gt;fn_2&lt;/code&gt; 의 뮤텍스를 각각 생성하여 락인하는 것과 동일했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;pthread_mutex_t&lt;/code&gt; 타입으로 &lt;code&gt;main&lt;/code&gt; 함수에서 단순하게 선언했을 경우,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pthread_mutex_init&lt;/code&gt; 으로 디폴트 값을 채우게 되는데,&lt;/p&gt;
&lt;p&gt;이 때 &amp;quot;정적 타입&amp;quot; 이 되버리기 때문에, &lt;strong&gt;&amp;quot;주소값&amp;quot;&lt;/strong&gt; 을 넘겨주어야 한다.&lt;/p&gt;
&lt;p&gt;그러니까, &lt;code&gt;main&lt;/code&gt; 에 &lt;code&gt;pthread_mutex_t&lt;/code&gt; 가 묶여있는 거고, (정적 타입으로 선언되어서)&lt;/p&gt;
&lt;p&gt;이걸 다른 스레드에서 &lt;code&gt;pthread_mutex_t&lt;/code&gt; 로 할당받을 수 없다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pthread_mutex_t&lt;/code&gt; 가 &lt;code&gt;main&lt;/code&gt; 에서 생성된 뮤텍스 객체의 내부를 &amp;quot;얕은 복사&amp;quot; 로 가져가기 때문에,&lt;/p&gt;
&lt;p&gt;정확히 &lt;code&gt;main&lt;/code&gt; 뮤텍스를 참조하는 것이 아니라, 새로운 스레드가 &amp;quot;새로운 뮤텍스&amp;quot; 를 생성하는 꼴이 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;따라서, 우리는 &amp;quot;만약에&amp;quot; 뮤텍스를 인자로 넘기고 싶다면,&lt;/p&gt;
&lt;p&gt;처음 &lt;code&gt;pthread_mutex_t&lt;/code&gt; 선언 후, init 하고,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;amp;&lt;/code&gt;, 그리고 &lt;code&gt;*&lt;/code&gt; 로 참조해야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;참고로, &lt;code&gt;pthread_mutex_t&lt;/code&gt; 정의를 직접 열어 보면,&lt;/p&gt;
&lt;p&gt;이미 상위 선언에서 &lt;code&gt;xxxxx*&lt;/code&gt; 로 포인터 선언이 되어 있는 것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;그래서 더 헷갈릴 수도 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Mutex 를 동적으로 내 마음대로 다루고 싶다면,&lt;/h3&gt;
&lt;p&gt;이는 역으로,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pthread_mutex_t* mutex = malloc(sizeof(pthread_mutex_t));&lt;/code&gt; 가 가능하다.&lt;/p&gt;
&lt;p&gt;물론, 단순 선언이 아니라,&lt;/p&gt;
&lt;p&gt;동적 메모리 생성이기 때문에, &lt;code&gt;free(mutex)&lt;/code&gt; 를 적절한 때에 해 줘야 하는 걸 잊어선 안된다.&lt;/p&gt;
&lt;br/&gt;


&lt;h2&gt;중간 마무리 --&amp;gt; 다음 내용은 이어서 제작&lt;/h2&gt;
&lt;h3&gt;요약&lt;/h3&gt;
&lt;p&gt;스레드를 만드는 다양한 라이브러리들이 존재하지만,&lt;/p&gt;
&lt;p&gt;프로세스 단에 속한 중요한 요소이기 때문에, 이는 Standard 라이브러리로 제공된다.&lt;/p&gt;
&lt;p&gt;즉, 직접적으로 스레드를 &amp;quot;생성&amp;quot; 하는 것이 아니라,&lt;/p&gt;
&lt;p&gt;제공되는 저 수준의 라이브러리를 통해, &amp;quot;스레드 생성&amp;quot; 을 수행한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;예를 들어 위의 코드를 보자면, 코드에서 단순히&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;타입&lt;/li&gt;
&lt;li&gt;함수&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;를 import 해서 사용하지,&lt;/p&gt;
&lt;p&gt;우리는 커널에 직접적으로 스레드를 요청하지 않았다.&lt;/p&gt;
&lt;p&gt;이는 편의성과 보안을 위해서이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;중요한 것은, &lt;strong&gt;스레드의 동기화&lt;/strong&gt; 부분이다.&lt;/p&gt;
&lt;p&gt;다양한 스레드를 생성하여 단일 계산에서 벗어나, 시분할 시스템으로 효율있게 계산하는 것은 좋지만,&lt;/p&gt;
&lt;p&gt;특정 프로젝트를 진행하거나, 프로그램을 실행 할 때,&lt;/p&gt;
&lt;p&gt;우리는 특정 스레드가 특정 영역만을 담당하고, 해당 영역은 또 다른 스레드가 요청하는 데이터 일 수도 있다.&lt;/p&gt;
&lt;p&gt;따라서, 이 과정은 스레드 간의 &amp;quot;의존성&amp;quot; 이 생기는 부분일 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이러한 의존성에 대한 부분을 해결해 주는 것이, 바로 &lt;strong&gt;&amp;quot;Mutex&amp;quot;&lt;/strong&gt; 이다.&lt;/p&gt;
&lt;p&gt;스레드 간의 동기화 부분에서, 뮤텍스 뿐만 아니라, Condition 을 사용하는 등, 방법이 많다.&lt;/p&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;h2&gt;참조 사이트 모음&lt;/h2&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;IBM --&amp;gt; AIX 공식 문서&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.ibm.com/docs/ko/aix/7.2.0?topic=processes-pthread-implementation-files&quot;&gt;https://www.ibm.com/docs/ko/aix/7.2.0?topic=processes-pthread-implementation-files&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;위키피디아 --&amp;gt; pthread&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.wikipedia.org/wiki/POSIX_%EC%8A%A4%EB%A0%88%EB%93%9C&quot;&gt;https://ko.wikipedia.org/wiki/POSIX_%EC%8A%A4%EB%A0%88%EB%93%9C&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;네이버 기술 블로그 - &amp;quot;[C] pthread란? pthread예제&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://m.blog.naver.com/whtie5500/221692793640&quot;&gt;https://m.blog.naver.com/whtie5500/221692793640&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;JOINC - &amp;quot;Pthread API 레퍼런스&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.joinc.co.kr/w/Site/Thread/Beginning/PthreadApiReference&quot;&gt;https://www.joinc.co.kr/w/Site/Thread/Beginning/PthreadApiReference&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;GeeksForGeesks - &amp;quot;Function Pointer in C&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.geeksforgeeks.org/c/function-pointer-in-c/&quot;&gt;https://www.geeksforgeeks.org/c/function-pointer-in-c/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;</description>
      <category>Computer Science</category>
      <category>C</category>
      <category>C thread</category>
      <category>Mutex</category>
      <category>pthread</category>
      <category>Thread</category>
      <category>멀티 스레드</category>
      <category>뮤텍스</category>
      <category>스레드</category>
      <category>프로세스</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/235</guid>
      <comments>https://codecreature.tistory.com/235#entry235comment</comments>
      <pubDate>Sat, 27 Sep 2025 22:01:40 +0900</pubDate>
    </item>
    <item>
      <title>create-react-app 은 레거시화 되었다. - 빠르게 변화하는 웹 진영 템플릿에 대한 생각</title>
      <link>https://codecreature.tistory.com/234</link>
      <description>&lt;h2&gt;제목 : create-react-app 은 레거시화 되었다. - 빠르게 변화하는 웹 진영 템플릿에 대한 생각&lt;/h2&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;이 글을 작성하는 이유&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;&lt;code&gt;create-react-app&lt;/code&gt; 이라는 명령어는 주로&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ npx create-react-app &amp;lt;생성할 프로젝트 디렉토리 이름&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;혹은&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ npm init create-react-app &amp;lt;생상할 프로젝트 디렉토리 이름&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이러한 명령어로 기본 템플릿이 생성되었다.&lt;/p&gt;
&lt;p&gt;처음부터 제작할 필요가 없이, 개발 환경을 곧바로 세팅해 주는 마법의 명령어는&lt;/p&gt;
&lt;p&gt;얼마나 파급력이 있었는지 &amp;quot;CRA&amp;quot; 라는 명칭으로 불릴 정도였다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, 현재 이 명령어는 Deprecated 되었다. 공식적으로 더 이상의 지원은 없다.&lt;/p&gt;
&lt;p&gt;이 말은, 앞으로 나올 지속적인 신 기능이 CRA 명령어를 통해서는 곧바로 적용이 되지 않는다는 것이다.&lt;/p&gt;
&lt;p&gt;이에 대한 자세한 내용은&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/create-react-app&quot;&gt;create-react-app 레포지토리&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;이 링크를 통해 확인하면 알 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;심지어는 리액트의 공식 문서 사이트도 &lt;code&gt;react.dev&lt;/code&gt; 라는 도메인으로 옮겨갔다.&lt;/p&gt;
&lt;p&gt;기존 공식 문서는 &amp;quot;인간의 빠른 학습&amp;quot; 에 초점이 맞춰져 있었다면,&lt;/p&gt;
&lt;p&gt;이번 문서는 가상 에디터에서 따라쳐서 학습하거나, AI 에게 선택지를 제공하는 것 처럼 느껴졌다.&lt;/p&gt;
&lt;p&gt;그렇지 않고서야, 중간에 존재하는 그 수없이 많은 방법론들,&lt;/p&gt;
&lt;p&gt;Babel, JSX, Webpack, npm, package.json .... 등등등&lt;/p&gt;
&lt;p&gt;이러한 언급 없이 아주 빠른 기초 프로젝트 생성에만 대부분 초점을 맞출 리가 없다고 생각했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;또한, 이제는 리액트가 자체적으로 자신들만의 라이브러리로 시작하는 것을 추천하기보다는,&lt;/p&gt;
&lt;p&gt;React 의 라이브러리를 통해 프레임워크를 구축한 다양한 프로그램을 추천하고 있다.&lt;/p&gt;
&lt;p&gt;즉, 풀스택 프로젝트로 시작하기를 권장하고 있는 것이다.&lt;/p&gt;
&lt;p&gt;웹 페이지 제작을 HTML + JavaScript 로 시작하는 사람과, React 로 시작하는 사람이 나뉠 정도로&lt;/p&gt;
&lt;p&gt;정말 많다.&lt;/p&gt;
&lt;p&gt;그런데, 웹 페이지만 만드는 것이 아닌, 갑작스런 풀스택 프레임워크 우선 추천은 내 입장에서는 황당했다.&lt;/p&gt;
&lt;p&gt;이젠 그 정도로, AI 가 웹페이지를 더 잘 만드는 세상이 왔다고도 생각이 든다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;너무나도 프로젝트 생성 방식이 많아진 현 상황에서,&lt;/p&gt;
&lt;p&gt;이러한 생성 방식들을 하나씩 살펴 보며 리액트의 의도와 웹 페이지의 미래 향방을 예측해 보기로 했다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;CRA - create-react-app 명령어는 왜 레거시가 되었을까?&lt;/h2&gt;
&lt;p&gt;이제는 create-react-app 과 동일한 프로젝트를 생성하기 위해서는,&lt;/p&gt;
&lt;p&gt;Vite 라는 프로그램이 주관하는 리액트 프로젝트로 시작하는 것이 비슷할 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;도대체 왜, CRA 는 레거시가 되었을까?&lt;/p&gt;
&lt;p&gt;현재 facebook 의 create-react-app 레포는 공식적으로 2021년 Deprecated 가 되었다.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.react.dev/learn/creating-a-react-app&quot;&gt;https://ko.react.dev/learn/creating-a-react-app&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;이 React 공식 사이트의 &amp;quot;새로운 React 앱 만들기&amp;quot; 를 들어가 보면,&lt;/p&gt;
&lt;p&gt;&amp;quot;React 로 새로운 앱 혹은 웹사이트를 구축하려면 프레임워크부터 시작&amp;quot; 하라고 말한다.&lt;/p&gt;
&lt;p&gt;그리고, 공부를 원하거나, 자체 프레임워크를 사용하고 싶다면 다른 명령어로 구축하라고 말한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;따라서 처음부터 React 앱을 만들 수 있는 명령어들을 살펴보면,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vite&lt;/li&gt;
&lt;li&gt;Parcel&lt;/li&gt;
&lt;li&gt;Rsbuild&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 3 가지를 추천한다.&lt;/p&gt;
&lt;p&gt;단순한 웹 페이지 제작에 사용되는 이 3 개의 프로그램은,&lt;/p&gt;
&lt;p&gt;기존 CRA 에 탑재된 기능들보다 우월하다.&lt;/p&gt;
&lt;p&gt;즉, 빌드 속도, 테스팅, 리프레시 등등&lt;/p&gt;
&lt;p&gt;CRA 에 엮인 수많은 기능들의 조합으로 인한 속도를 향상시켜준다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;리액트는 이미 전 세계적으로 유명한 웹 사이트 제작 라이브러리이다.&lt;/p&gt;
&lt;p&gt;이제는 굳이 스스로 React 를 사용하기 위해 빌드 도구나 배포 도구를 만들 필요 없으며,&lt;/p&gt;
&lt;p&gt;React 와 관련된 수많은 영역에서 다른 회사나 조직이 부족한 부분을 심화적으로 해결하여&lt;/p&gt;
&lt;p&gt;열정적으로 보조하고 있는 것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;이제 리액트는 지금까지 부족했던 최적화나 호환성 에러가 아니라,&lt;/p&gt;
&lt;p&gt;신 기능에 집중해도 된다는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;기존 CRA 에 존재하던 난잡한 시스템&lt;/h3&gt;
&lt;p&gt;JavaScript 로 생성된 프로젝트들은 정말 수많은 의존성을 가진다.&lt;/p&gt;
&lt;p&gt;이 의존성은 거대한 NPM 레포의 다른 패키지들을 가져와서 스스로의 패키지를 완성한다.&lt;/p&gt;
&lt;p&gt;문제는, 이러한 패키지 의존성 문제는 자체적으로 중복 다운로드를 거부하여 차단할 수 있는데,&lt;/p&gt;
&lt;p&gt;리액트라는 기초 템플릿을 완성하는 과정에서, 구성된 &amp;quot;프로그램&amp;quot; 의 문제가 생긴다. (패키지 아님)&lt;/p&gt;
&lt;p&gt;이러한 프로그램들은 리액트에서 코드 작성, 빌드, 테스트, 명령어 등등 다양한 구역을 담당한다.&lt;/p&gt;
&lt;p&gt;또 이런 프로그램들을 모아서 순서가 맞게 실행하는 것이 Webpack 이었는데,&lt;/p&gt;
&lt;p&gt;이제는 Webpack 의 기능까지 아우르며, 더 확장된 기능 편의성까지 담당할 수 있는 프로그램,&lt;/p&gt;
&lt;p&gt;대표적으로 Vite(예시 - 단순 웹), NextJS(예시 - 풀스택) 과 같은 프로그램들이 나오기 시작하면서,&lt;/p&gt;
&lt;p&gt;CRA 의 위상은 꺾여 갔다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;그렇다면, CRA 대체제는?&lt;/h3&gt;
&lt;p&gt;옛날에 생성했던 웹 페이지와 스타일링, request agent 를 생성하여 커스텀하며 배워간다면 CRA 가 옳고,&lt;/p&gt;
&lt;p&gt;CRA 와 방식은 동등하나, 3년 이내의 최신식 기능을 사용하기 위해서는 vite 를 통해 만들어야 한다.&lt;/p&gt;
&lt;p&gt;물론, vite 외 다른 프로그램들도 존재하며, 대표적으로는 vite 이다.&lt;/p&gt;
&lt;p&gt;vite 는 npm, yarn, pnpm 중 하나를 선택하여 의존성을 관리할 수 있게도 만들어 준다.&lt;/p&gt;
&lt;p&gt;이는 vite 로 프로젝트 시작 시, 콘솔에 나타나는 선택 창으로 직접 선택할 수 있다는 편리성이 존재한다.&lt;/p&gt;
&lt;p&gt;뿐만 아니라, Go, Rust 와 같이 최적화 된 빌드 시스템으로 테스트 할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;도대체 왜 React 는 풀스택 프로그램을 강력하게 추천할까?&lt;/h2&gt;
&lt;p&gt;나는 만들어진 소프트웨어 제품의 &amp;quot;사용법&amp;quot; 만 알고, &amp;quot;원리&amp;quot; 를 모르면 한계가 명확하다고 생각하는 사람이다.&lt;/p&gt;
&lt;p&gt;따라서, 이전에 나는 Webpack 을 주제로 긴 글을 작성한 적도 있다.&lt;/p&gt;
&lt;p&gt;뿐만 아니라, JSX 없이 리액트 프로그램을 작성하는 법이나,&lt;/p&gt;
&lt;p&gt;0 에서 리액트 프로젝트까지 제작하는 법을 다뤘었다.&lt;/p&gt;
&lt;p&gt;내부에 적용된 사람의 방법만 해도 수십가지가 족히 넘을 것이라고 단연코 예상한다.&lt;/p&gt;
&lt;p&gt;적용된 방법 하나만 집중적으로 공부해도 생각보다 긴 시간을 집중해야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;적용된 프로그램의 원리를 이해하기도 바쁜데, 이제는 리액트 프로젝트가 프로덕션이 되는 것을&lt;/p&gt;
&lt;p&gt;논리적으로 이해할 수 있는 것이 아니라, 만들어 둔 사람의 방식을 이해하고 받아들여야 하는 수준이다.&lt;/p&gt;
&lt;p&gt;처음에는 너무나도 많은 방향이 존재하여 불만이 조금 존재했지만, 공식문서를 읽으며 이내 그 이유를 알 것 같았다.&lt;/p&gt;
&lt;p&gt;그 이유는, &amp;quot;굳이 작동 원리를 이해할 필요가 없는 세상이 왔다&amp;quot; 라는 판단이 들었다.&lt;/p&gt;
&lt;p&gt;물론, 복잡하게 엮인 각 프로그램의 역할을 하나로 묶어 관리하는 프로그램은 관리 측면에서 너무나도 편리하다.&lt;/p&gt;
&lt;p&gt;웹 페이지의 작동과 목표, 보안, 스타일링 등등에만 집중할 수 있기 때문이다.&lt;/p&gt;
&lt;p&gt;현재 누가 &lt;code&gt;index.html&lt;/code&gt; 내부를 신경쓰고 있을까? 대부분은 지원되는 메서드나 기능으로 메타데이터를 주입하고 관리 할 것이다.&lt;/p&gt;
&lt;p&gt;그리고 목적에 맞는 다양한 프로그램이 존재할 수록, 사용하기 좋아지는 것은 AI 이다.&lt;/p&gt;
&lt;p&gt;나도 참 아이러니 했는데, C 로 커스텀 제작한 HashMap 의 경우 수정 요청을 하면 에러를 일으키는데,&lt;/p&gt;
&lt;p&gt;인공지능이 제작한 자바스크립트 코드는 틀린 것을 본 적이 없었다.&lt;/p&gt;
&lt;p&gt;그만큼 JavaScript 가 자연어와 비슷하다는 소리이기도 하고, AI 가 이해하고 작성하기에도 편하다는 것이다.&lt;/p&gt;
&lt;p&gt;나는 현재 대부분의 프로그래밍 언어들이 AI 를 지향한다고 강력하게 예상한다.&lt;/p&gt;
&lt;p&gt;그 이유는, AI 가 코드를 작성 해 주는 것이 있겠지만,&lt;/p&gt;
&lt;p&gt;그보다도 특정 프로그램을 제작하기 위해 AI 는 &amp;quot;너무 깊이 생각 할 필요가 없는&amp;quot; 프로그램을 선택 할 것이다.&lt;/p&gt;
&lt;p&gt;또한, AI 에게 코드를 묻는 사람들에게도 엄청난 파장이 존재할 것이다.&lt;/p&gt;
&lt;p&gt;예를 들어서, 누가 콘솔 프로그램을 만들어 달라고 AI 에게 부탁한다면,&lt;/p&gt;
&lt;p&gt;AI 는 가장 편한 JS 혹은 Python 을 이용하여 콘솔 프로그램을 만들어 낼 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;새로운 개발의 세상은 풀스택을 요구한다.&lt;/h3&gt;
&lt;p&gt;거대한 회사의 경우 개발에 분업화가 이루어져 특정 목적만을 담당하는 코드를 작성 할 것이다.&lt;/p&gt;
&lt;p&gt;예로부터 전해 내려오는 분업을 예를 들자면, 프론트엔드, 백엔드, 데이터베이스 로 나눌 수 있을 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, 현재 AI 로 인해 개발이 매우 편해 진 세상에서, 간단한 웹 페이지를 제작하기 위해&lt;/p&gt;
&lt;p&gt;사람을 고용하지는 않을 것이다.&lt;/p&gt;
&lt;p&gt;AI 와 더불어서 풀스택을 만들 수 있는 사람을 고용할 것이라는 것이다.&lt;/p&gt;
&lt;p&gt;React 는 이러한 향방을 알고, 풀스택을 최우선 순위로 삼는다고 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;그렇다면, 우리는 어떠한 개발을 해야 하는가?&lt;/h2&gt;
&lt;p&gt;React 사이트를 보며, 이미 하나의 길은 알려줬다고 생각한다.&lt;/p&gt;
&lt;p&gt;프론트엔드라는 영역 안에 갇혀 있는 것이 아니라, 여러 영역을 동시에 관리하는 웹 제작자가 되라는 것.&lt;/p&gt;
&lt;p&gt;그렇기에 React 와 관련된 완성도 높은 프레임워크나 라이브러리를 추천하는 것이다.&lt;/p&gt;
&lt;p&gt;웹 사이트도 제작하고, 정보를 관리하는 백엔드 쪽도 NextJS 로 관리할 수 있고,&lt;/p&gt;
&lt;p&gt;NextJS 로 데이터베이스에 접근 할 수도 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;다른 방법은 무엇이 있을까?&lt;/h3&gt;
&lt;p&gt;다른 방향은 명확하다고 생각한다.&lt;/p&gt;
&lt;p&gt;AI 가 사용하기 좋은 프로그램을 제작하는 것이다.&lt;/p&gt;
&lt;p&gt;내가 생각하는 개발의 미래는 2 가지라고 생각한다.&lt;/p&gt;
&lt;p&gt;첫 번째로는 AI 와 협업하여 다양한 영역을 두루 관리하는 사람,&lt;/p&gt;
&lt;p&gt;두 번째로는, AI 가 사용하기 좋은 프로그램을 제작하는 사람이다.&lt;/p&gt;
&lt;p&gt;AI 가 사용하기 좋은 프로그램이란 정확히 정의되기 어렵다고 생각한다.&lt;/p&gt;
&lt;p&gt;AI 가 사용하기 좋은 자연스러운 프로그래밍 언어를 의미하거나,&lt;/p&gt;
&lt;p&gt;어떠한 상황에서도 서버를 띄울 수 있는 python3 개발 서버와 비슷한 프로그램을 만들거나.&lt;/p&gt;
&lt;p&gt;혹은, 관리하기 힘든 클라우드 인프라에서 사용할 수 있는 공인된 암호 관리 프로그램을 만들거나.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;즉, 어떤 방향을 선택해도 쉬운 길은 절대로 없다는 게 포인트이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;리액트에 대한 깊은 이해는 중급 이상의 개발자로 가기 위해 반드시 필요한 과정이었다.&lt;/p&gt;
&lt;p&gt;그런데, AI 의 성장 속도를 보니, 사이트의 완벽을 추구하지 않는 이상, AI 로 제작하는 것이 옳을 수도 있겠단 생각이 든다.&lt;/p&gt;
&lt;p&gt;간단한 사이트를 만들기 위해 우리가 React 를 건들 필요가 있는가?&lt;/p&gt;
&lt;p&gt;멋진 사이트를 만들기 위해 색상을 일일히 바꿔가며 체크 할 필요가 있는가?&lt;/p&gt;
&lt;p&gt;AI 에게 물어보면 그만일 것이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;그러나&lt;/strong&gt;,&lt;/p&gt;
&lt;p&gt;AI 는 인간과의 상생이 여전히 필요하다.&lt;/p&gt;
&lt;p&gt;AI 는 아직 자체적으로 복잡한 프레임워크를 만들어서 대답하지 않는다.&lt;/p&gt;
&lt;p&gt;사람이 만든 최적화 된 방식으로 이해했기 때문에,&lt;/p&gt;
&lt;p&gt;우리가 대형 프로젝트를 AI 와 함께 한다면, 필연적으로 AI 는 편리한 기술을 찾아 제작할 것이다.&lt;/p&gt;
&lt;p&gt;이 과정에서, 누적되는 기술 부채에 관한 내용은 인간인 우리가 직접 해결해야 할 문제라고 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;뿐만 아니라, 가장 영향력이 큰 프로그램은 리액트와 같은 변동성이 잦은 라이브러리가 아니라,&lt;/p&gt;
&lt;p&gt;여전히 버전 별 호환성을 지니며, 큰 변동이 없는 프로그램이라는 것이다.&lt;/p&gt;
&lt;p&gt;AI 입장에서는 C 언어와 같은 로우 레벨 언어가 매우 귀찮을 것이라고 생각한다.&lt;/p&gt;
&lt;p&gt;매우 정확해야 하므로 검증은 필수이고, 다른 언어에서 한 줄이면 되는 것을,&lt;/p&gt;
&lt;p&gt;C 에서는 수 줄에서 수십 줄로 늘어나기도 하니, 허용 토큰을 많이 사용하는 언어로 각인될 수 밖에 없다.&lt;/p&gt;
&lt;p&gt;그러나, 현재까지 언어 중에서 C 를 모티베이션 하지 않은 언어가 존재하는가?&lt;/p&gt;
&lt;p&gt;내가 알고 있는 한 대부분 ~ 전부 다양한 프로그래밍 언어들은 1972 년에 탄생한 이 C 라는 언어의 구조를&lt;/p&gt;
&lt;p&gt;아주 크게 벗어나지 않았다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;또한, 어느 언어를 살펴보더라도, C or C++ 과 같은 언어를 사용하지 않았던 언어가 거의 없었다.&lt;/p&gt;
&lt;p&gt;(Go, Rust, Zig, Assembly 와 같은 저레벨 제외)&lt;/p&gt;
&lt;p&gt;JavaScript 가 상대적으로 느리지만 현재 프로덕션 백엔드에서도 사용될 수 있는 이유는,&lt;/p&gt;
&lt;p&gt;기본 탑재 라이브러리 EX - bcrypt 와 같은 라이브러리들이 C or C++ 로 작성되었기 때문이다.&lt;/p&gt;
&lt;p&gt;이들은 Web Assembly Module 로서 자바스크립트 실행을 빠르도록 도와 준다.&lt;/p&gt;
&lt;p&gt;(요즘은 Rust 로 wasm 이 많이 제작되는 추세긴 함.)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;다시 돌아와서&lt;/strong&gt;,&lt;/p&gt;
&lt;p&gt;갑자기 C 예찬론자가 되었는데, 그 만큼 저 레벨의 언어가 아직 중요함을 알리고 싶었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;현재 React, Vue 등등 많은 웹 라이브러리나 프레임워크가 인기가 얻고 있다고 해서,&lt;/p&gt;
&lt;p&gt;정말로 대부분의 중요 회사들이 이들을 적극 사용하는 것은 아니다.&lt;/p&gt;
&lt;p&gt;신뢰성이 매우 중요하기 때문에, 아직도 JQuery 를 사용하여 내부망 사이트를 제작하는 회사가 정말로 많다.&lt;/p&gt;
&lt;p&gt;간단해도 되며, 단순히 데이터 로직에만 신경써도 되기 때문에 WAS 에 홈페이지를 넣어놓기도 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;우리는 AI 세상에서 살고 있다.&lt;/p&gt;
&lt;p&gt;하지만, 결국 AI 또한 우리가 만든 언어를 사용하고 있다는 것을 간과해선 안된다고 생각한다.&lt;/p&gt;
&lt;p&gt;웹 페이지를 그렇게나 잘 만들어도, 결국 홈페이지는 &lt;code&gt;.html&lt;/code&gt; 파일에서 시작한다는 것을 잊어서는 안된다.&lt;/p&gt;
&lt;p&gt;사람들이 정말 많이 사용하는 프로그램에서 한 줄의 로직만 바꿔도 속도가 수십 퍼센트 빨라질 수도 있는 것이&lt;/p&gt;
&lt;p&gt;소프트웨어라는 것을 잊어서는 안된다.&lt;/p&gt;
&lt;br/&gt;



&lt;br/&gt;

&lt;hr&gt;
&lt;h2&gt;참조 사이트 목록&lt;/h2&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.react.dev/learn/build-a-react-app-from-scratch&quot;&gt;https://ko.react.dev/learn/build-a-react-app-from-scratch&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://vite.dev/guide/philosophy.html&quot;&gt;https://vite.dev/guide/philosophy.html&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.npmjs.com/package/create-react-app&quot;&gt;https://www.npmjs.com/package/create-react-app&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/create-react-app&quot;&gt;https://github.com/facebook/create-react-app&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://react.dev/learn/creating-a-react-app&quot;&gt;https://react.dev/learn/creating-a-react-app&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;</description>
      <category>Web-Server/React</category>
      <category>Ai</category>
      <category>CRA</category>
      <category>Create React App</category>
      <category>react</category>
      <category>react cra</category>
      <category>react 풀스택</category>
      <category>vite</category>
      <category>개인 생각</category>
      <category>생각 정리</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/234</guid>
      <comments>https://codecreature.tistory.com/234#entry234comment</comments>
      <pubDate>Wed, 20 Aug 2025 23:23:07 +0900</pubDate>
    </item>
    <item>
      <title>C 그리고 fgets 라인 입력만으로 tokenizer 메서드 제작하기 (하드코어)</title>
      <link>https://codecreature.tistory.com/233</link>
      <description>&lt;h2&gt;제목 : C, 그리고 fgets 라인 입력만으로 입력 토큰화 메서드 제작하기&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;이 글을 작성하는 이유&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;물론, C 에서의 특정 기본 라이브러리나,&lt;/p&gt;
&lt;p&gt;C++ 의 특정 기본 라이브러리를 가져와서 하나의 문자열을 토큰화 할 수 있다.&lt;/p&gt;
&lt;p&gt;그러나, 나는 내가 가진 기존의 개발자 역량에서, 엔지니어 역량으로 이끌기 위해 여러 제약을 걸었다.&lt;/p&gt;
&lt;p&gt;(알고리즘 문제에 제한해서.)&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;stdio.h&lt;/code&gt; 라이브러리만 사용한다.&lt;/li&gt;
&lt;li&gt;동적 메모리 할당 메서드만 &lt;code&gt;extern&lt;/code&gt; 키워드로 가져온다.&lt;/li&gt;
&lt;li&gt;입출력은 모두 &lt;code&gt;fgets&lt;/code&gt; &lt;code&gt;fgetc&lt;/code&gt; &lt;code&gt;fputs&lt;/code&gt; 와 같이, &lt;br/&gt; &lt;code&gt;'\0'&lt;/code&gt; 을 참조하는 메서드로 해결한다.&lt;/li&gt;
&lt;li&gt;필요한 모든 유틸리티 메서드와 구조체를 &amp;quot;직접&amp;quot; 작성하여 해결한다.&lt;/li&gt;
&lt;li&gt;작성한 코드는 헤더 파일로 만들어 재사용하지 않으며, 하나의 문제마다 모두 재작성하여 사용한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;p&gt;이러한 제약을 스스로 걸어, 나 스스로 포인터라는 개념 그 자체를 직관적으로 이해했다.&lt;/p&gt;
&lt;p&gt;현재까지 70 문제쯤을 이러한 제약 속에서 해결했으며,&lt;/p&gt;
&lt;p&gt;1 줄의 코드로 해결할 수 있는 문제도 300 줄이 나오는 경우가 대부분이었다.&lt;/p&gt;
&lt;p&gt;그러나, 역설적으로 편의성 라이브러리들이 왜 존재하는지 진정으로 이해할 수 있었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;내가 이 글을 작성하는 이유는 궁극적으로,&lt;/p&gt;
&lt;p&gt;이 과정을 설명함으로서 &amp;quot;이해했다&amp;quot; 라는 결과에 도달하기 위함이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;또한, 프로그래밍 언어가 자연어에 가까울 수록 개발자는 그 역할을 잃어가는 AI 세상에서,&lt;/p&gt;
&lt;p&gt;마지막까지 컴퓨터 코드 엔지니어로서 남기 위한 최후의 발악이기도 하다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그리고 이 기능은, 내가 Java 에서 주로 사용하던 클래스인 &lt;code&gt;StringTokenizer&lt;/code&gt; 에서&lt;/p&gt;
&lt;p&gt;영감을 받아 비슷하게 제작했다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;&amp;quot;토큰화&amp;quot; 란?&lt;/h2&gt;
&lt;p&gt;컴퓨터 도메인에서의 토큰화란, 이런 의미를 가진다.&lt;/p&gt;
&lt;p&gt;연속된 데이터가 존재할 때,&lt;/p&gt;
&lt;p&gt;지정된 특정 데이터 패턴을 기준삼아 연속된 데이터를 원하는 형태로 분리하는 것을 의미한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;바로 프로그램으로 들어가지 않고, 예제를 상상 해 보자.&lt;/p&gt;
&lt;p&gt;만약에 &lt;code&gt;&amp;quot;2025-08-12&amp;quot;&lt;/code&gt; 라는 데이터가 존재한다.&lt;/p&gt;
&lt;p&gt;물론, 이 데이터는 누가 보더라도 지구의 연도를 말한다는 것을 안다.&lt;/p&gt;
&lt;p&gt;특히나, 날짜를 관리하는 메서드가 많다는 것을 고려했을 때,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;{&amp;quot;2025&amp;quot;, &amp;quot;08&amp;quot;, &amp;quot;12&amp;quot;}&lt;/code&gt; 로 인식하기보다는,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;2025&lt;/code&gt; : &lt;code&gt;getYear&lt;/code&gt; 과 비슷한 메서드로 가져올 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;08&lt;/code&gt; : &lt;code&gt;getMonth&lt;/code&gt; 와 비슷한 메서드로 가져 올 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;12&lt;/code&gt; : &lt;code&gt;getDay&lt;/code&gt; 와 비슷한 메서드로 가져 올 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이렇게 먼저 인식하는 개발자들도 굉장히 많을 것이다.&lt;/p&gt;
&lt;p&gt;왜냐하면, 날짜 그 자체는 각각 다른 의미를 가지기 때문에,&lt;/p&gt;
&lt;p&gt;이를 배열로 만들어 가져오기보다는, 기본 유틸리티로 가져오는 것이 편리하기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그렇다면, 기존 유틸리티는 &lt;code&gt;&amp;quot;2025-08-12&amp;quot;&lt;/code&gt; 라는 데이터를,&lt;/p&gt;
&lt;p&gt;특정 메서드로 추론한다는 생각을 가지고 있을까?&lt;/p&gt;
&lt;p&gt;절대 아닐 것이다. 특히나 나라별 순서 또한 다르기 때문에,&lt;/p&gt;
&lt;p&gt;로컬(지역) 을 요구하는 게 이해가 될 것이다.&lt;/p&gt;
&lt;p&gt;데이터를 쉽게 제공하는 유틸리티 함수나 클래스, 혹은 구조체는,&lt;/p&gt;
&lt;p&gt;이 데이터를 &amp;quot;토큰화&amp;quot; 한 뒤 유저에게 제공한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;즉, 이 데이터를 &amp;quot;토큰화&amp;quot; 했다고 가정 할 때,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;{&amp;quot;2025&amp;quot;, &amp;quot;08&amp;quot;, &amp;quot;12&amp;quot;}&lt;/code&gt; 로 인식한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;토크나이저 기능의 유용함. (Tokenizer)&lt;/h3&gt;
&lt;p&gt;대부분의 프로그래밍 언어들은 토큰화 기능을 제공하는 유틸리티 메서드 혹은 클래스를 제공한다.&lt;/p&gt;
&lt;p&gt;주로 토큰화는 문자열 그 자체를 Target 으로 삼는다.&lt;/p&gt;
&lt;p&gt;특정 조직이나 개인 이렇게 각자 다른 목적을 가진 프로그래머들은 저마다의 프로그램마다&lt;/p&gt;
&lt;p&gt;데이터에 있어 특정 패턴이 &amp;quot;분리&amp;quot; 조건을 의미할 수 있다.&lt;/p&gt;
&lt;p&gt;그것은 단순한 빈 칸 &lt;code&gt;&amp;#39; &amp;#39;&lt;/code&gt; 일 수도 있고, &lt;code&gt;&amp;#39;-&amp;#39;&lt;/code&gt; 와 같은 하나의 문자 일 수 있으며,&lt;/p&gt;
&lt;p&gt;혹은 문자열 &lt;code&gt;&amp;quot;--&amp;quot;&lt;/code&gt; 가 될 수도 있다.&lt;/p&gt;
&lt;p&gt;조직과 단체, 혹은 개인까지, 원하는 대로 어떤 기준으로 토큰화 할 것인지 지정할 수 있다.&lt;/p&gt;
&lt;h3&gt;토큰화의 기준, Delimeter&lt;/h3&gt;
&lt;p&gt;보통 한글로 델림, 혹은 델리미터로 부르는 이것은,&lt;/p&gt;
&lt;p&gt;토큰화를 수행 할 때, 연속된 데이터를 나눌 특정한 &amp;quot;기준&amp;quot; 을 의미한다.&lt;/p&gt;
&lt;p&gt;개인적으로 생각했을 때,&lt;/p&gt;
&lt;p&gt;아주 포괄적으로 말했을 때에는 &amp;quot;연속된 데이터를 나눌 특정한 데이터 패턴&amp;quot; 을 말할 수 있으며,&lt;/p&gt;
&lt;p&gt;알고리즘으로 말했을 때에는, &amp;quot;문자열 데이터를 나누는 모든 특정 문자들의 집합&amp;quot; 을 말할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이러한 Delimeter 는, 하나의 문자가 될 수도 있으며, &amp;quot;문자열&amp;quot; 이 될 수도 있다.&lt;/p&gt;
&lt;p&gt;대부분의 프로그래밍 언어들에서는 기본 delim 으로 빈 칸 (&lt;code&gt;&amp;#39; &amp;#39;&lt;/code&gt;) 으로 세팅할 수도 있으며,&lt;/p&gt;
&lt;p&gt;자신이 원하는 delim 을 추가적인 인자로 넣어서 연속 데이터를 나눌 수도 있다.&lt;/p&gt;
&lt;p&gt;뭐, 위에서 제시한 날짜 데이터의 경우, &lt;code&gt;&amp;#39;-&amp;#39;&lt;/code&gt; 를 사용 할 수도 있겠다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;문자열에서의 token 을 어떻게 만들까?&lt;/h2&gt;
&lt;p&gt;먼저, 이 글은 &lt;code&gt;c++&lt;/code&gt; 도 아니고, &lt;code&gt;c&lt;/code&gt; 라는 언어를 사용했으므로,&lt;/p&gt;
&lt;p&gt;그 외의 프로그래밍 언어를 사용하는 사람들은 기본 유틸리티 메서드를 사용하는 것이&lt;/p&gt;
&lt;p&gt;성능 면에서도, 오류 면에서도, 코드의 절감 면에서도 우월하다.&lt;/p&gt;
&lt;p&gt;따라서, 내가 작성하는 프로그램을 이해 할 필요는 없을 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, &amp;quot;어떻게 데이터를 나누고, 분리된 데이터를 반환할 것인가?&amp;quot; 에 대해서는&lt;/p&gt;
&lt;p&gt;확실히 이해할 수 있으리라 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;토큰화 자체의 의미. 연속된 데이터를 주어진, 혹은 특정한 데이터 패턴에 따라 &amp;quot;나눈다&amp;quot; 라는 것을&lt;/p&gt;
&lt;p&gt;깊이 숙지하고 생각해야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;문자열이라는 연속된 데이터를 어떻게 분리된 문자열 데이터로 만드나?&lt;/h2&gt;
&lt;p&gt;나는 이미 이에 대한 알고리즘을 구축하고 수십 번을 작성했기 때문에,&lt;/p&gt;
&lt;p&gt;초창기 논리 로직으로 이해했던 것을 지금은 직관으로 받아들인 상태다.&lt;/p&gt;
&lt;p&gt;현재는 직관으로 이해한 논리를 다시 논리 로직으로 풀어가게 될 것 같다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;&amp;quot;분리된&amp;quot; 데이터란?&lt;/h3&gt;
&lt;p&gt;이 용어는 토큰화 과정을 이 글을 읽는 사람들이 이해하기 위해 사용한 용어다.&lt;/p&gt;
&lt;p&gt;정해져 있는 것은 아니고,&lt;/p&gt;
&lt;p&gt;정말 말 그대로 어떻게? 연속된 데이터를 분리 할 것인지에 대해서 묻는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;특정 프로그래밍 언어를 사용한다면 문자열을 &lt;code&gt;String&lt;/code&gt; 과 같이 이해할 것이므로,&lt;/p&gt;
&lt;p&gt;문자열이 아닌, 다른 타입으로 이해 해 보자.&lt;/p&gt;
&lt;p&gt;만약,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;{1, 2, 3, 0, 9, 8, 7}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위와 같은 데이터 패턴이 주어지고, Delimeter 로는 &lt;code&gt;0&lt;/code&gt; 을 선택했다.&lt;/p&gt;
&lt;p&gt;그렇다면, 위의 배열은 토큰화(Tokenizer) 과정을 거치고 난 후, 이러한 양상을 띄게 된다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;{1, 2, 3}

{9, 8, 7}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 결과가 나오기 위해서는, 이러한 데이터 패턴도 주어질 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;{0, 1, 2, 3, 0, 9, 8, 7}
or
{1, 2, 3, 0, 0, 9, 8, 7}
or
{1, 2, 3, 0, 9, 8, 7, 0}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;토큰화의 결과는 주어진 데이터보다 차원이 높아진다.&lt;/h3&gt;
&lt;p&gt;정말 말 그대로, 데이터 표현 상 1 차원이 높아진다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;{1, 2, 3, 0, 9, 8, 7}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 데이터는,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;{1, 2, 3}

{9, 8, 7}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이라는 결과를 낳는다.&lt;/p&gt;
&lt;p&gt;그리고, 만약 &lt;code&gt;{1, 2, 0, 3, 0, 9, 8, 7}&lt;/code&gt; 이라는 데이터 입력이 주어졌을 때,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;{1, 2}

{3}

{9, 8, 7}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이라는 토큰화 결과를 낳는다.&lt;/p&gt;
&lt;p&gt;즉, 주어진 입력에 대한 데이터의 결과는, &amp;quot;데이터 상에서의 차원을 한 단계 올린다.&amp;quot;&lt;/p&gt;
&lt;p&gt;직관적으로 말했을 때, 우리는 &amp;quot;연속된 데이터를 분리했기 때문이다.&amp;quot;&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;데이터의 차원이 높아진다?&lt;/h3&gt;
&lt;p&gt;즉, 여타 대부분의 프로그래밍 언어로 표현했을 때,&lt;/p&gt;
&lt;p&gt;정수의 배열은 &lt;code&gt;Integer[]&lt;/code&gt; 로 표현할 수 있다.&lt;/p&gt;
&lt;p&gt;그렇다면, 토큰화로 인해 나오는 결과는 &lt;code&gt;Integer[][]&lt;/code&gt; 라는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;만약 문자열을 &lt;code&gt;String&lt;/code&gt; 이라고 칭한다면, 결과물은 &lt;code&gt;String[]&lt;/code&gt; 이 될 것이다.&lt;/p&gt;
&lt;p&gt;C 언어에서는 문자열을 &lt;code&gt;char*&lt;/code&gt; 로 지정한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;char&lt;/code&gt; : 하나의 문자&lt;/li&gt;
&lt;li&gt;&lt;code&gt;char*&lt;/code&gt; : 문자들을 담고 있는 배열의 주소 or 문자가 연속됨을 알 수 있음.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;char**&lt;/code&gt; : 문자들을 담고 있는 배열의 주소를 담는 배열&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이렇게 구성된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;즉, 입력된 데이터는 &amp;quot;문자열&amp;quot; 이므로, &lt;code&gt;char*&lt;/code&gt; 데이터 형태를 띄고 있다.&lt;/p&gt;
&lt;p&gt;그렇다면, 나오는 토큰화 결과물은 &lt;code&gt;char**&lt;/code&gt; 이다.&lt;/p&gt;
&lt;p&gt;데이터의 차원이 올라간 것이다. (1차원 --&amp;gt; 2차원)&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;어떻게 하나의 토큰을 만들까?&lt;/h2&gt;
&lt;p&gt;주어지는 데이터 구성은 이러하다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;연속된 데이터&lt;/li&gt;
&lt;li&gt;연속된 데이터를 나눌 기준 or 데이터 패턴&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;대부분의 경우, 주어지는 연속 데이터는 &amp;quot;문자열&amp;quot; 이다.&lt;/p&gt;
&lt;p&gt;그리고 Delim(기준) 은 유저가 선택하는 편이다.&lt;/p&gt;
&lt;p&gt;예제를 위해 확실한 기준들을 세워보자.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;연속된 데이터 == (문자열)&lt;/li&gt;
&lt;li&gt;연속 데이터를 나눌 기준 or 데이터 패턴 == (개행문자들 || 빈 칸 || 종료문자)&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;만약에, &lt;code&gt;&amp;quot;abc def&amp;quot;&lt;/code&gt; 가 주어진다면, &lt;code&gt;&amp;quot;abc&amp;quot;&lt;/code&gt;, &lt;code&gt;&amp;quot;def&amp;quot;&lt;/code&gt; 로 나누어 질 것이다.&lt;/p&gt;
&lt;p&gt;그래서, &amp;quot;어떻게 나누어야 하는가?&amp;quot;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;신경써야 할 카테고리를 나누어 보자면,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;인덱스&lt;/li&gt;
&lt;li&gt;메모리 생성&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;으로 정말정말 추상적으로 나눌 수 있다.&lt;/p&gt;
&lt;p&gt;메모리 생성은 여타 다른 언어들에서는 쉬울 수 있으나, C 에서는 상대적으로 고려해야 할 부분이 좀 많다.&lt;/p&gt;
&lt;p&gt;과정도 추상화 해서 나누어 보자면,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;토큰화 결과물을 저장할 메모리 배열들 생성&lt;/li&gt;
&lt;li&gt;인덱스 탐색으로 시작 지점과 종료 지점을 선택한다.&lt;/li&gt;
&lt;li&gt;그 구간을 순회하여 새로운 배열에 넣는다. (1 번 배열이 아님.)&lt;/li&gt;
&lt;li&gt;입력 데이터가 마무리되었을 때, 저장된 토큰화 결과물을 반환한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;h3&gt;토큰을 만들기 위한 인덱스 탐색을 알아보자.&lt;/h3&gt;
&lt;p&gt;참고로 &amp;quot;인덱스 탐색&amp;quot; 은 통용되는 단어가 아니다.&lt;/p&gt;
&lt;p&gt;토큰화 과정 중 하나의 토큰을 추출하기 위해,&lt;/p&gt;
&lt;p&gt;토큰의 &amp;quot;시작 인덱스&amp;quot;, &amp;quot;마지막 인덱스&amp;quot; 를 탐색해야 한다.&lt;/p&gt;
&lt;p&gt;이 과정을 직관적으로 &amp;quot;인덱스 탐색&amp;quot; 이라고 말한 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;즉, 하나의 토큰을 만들기 위해서는 &amp;quot;토큰 시작 인덱스&amp;quot;, &amp;quot;토큰 마지막 인덱스&amp;quot;&lt;/p&gt;
&lt;p&gt;이 두 개의 인덱스를 과정 중에 관리해야 한다.&lt;/p&gt;
&lt;p&gt;이는 한 번의 순회(loop) 만에 토큰화를 완수하기 위함이다.&lt;/p&gt;
&lt;p&gt;(한 번의 순회로 토큰화를 완수할 수 있는 다른 방식이 있다면, 그것도 옳은 방식이라고 말할 수 있다.)&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;하나의 토큰을 만드는 것은 slice 메서드와 거의 동일하다.&lt;/h3&gt;
&lt;p&gt;혹시 특정 프로그래밍 언어만을 숙달하여 이러한 메서드를 접해보지 못했을 수도 있는데,&lt;/p&gt;
&lt;p&gt;이 메서드는 예를 들어서, (JavaScript 에서.)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;let str = &amp;quot;abcdefg&amp;quot;&lt;/code&gt; 라는 문자열이 존재 할 때,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;str.slice(1, 4) == &amp;quot;bcd&amp;quot;&lt;/code&gt; 를 의미한다. (인덱스 1 부터, 4 까지. 1 ~ 3 을 포함.)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;여기서 &lt;code&gt;1 == 토큰 시작 인덱스&lt;/code&gt; 이며, &lt;code&gt;4 == 토큰 종료 인덱스&lt;/code&gt; 를 의미한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그렇다면, 한번 표로 살펴보자.&lt;/p&gt;
&lt;p&gt;밑의 표는 &lt;code&gt;ab cd e&lt;/code&gt; 를 입력하고 &amp;quot;엔터&amp;quot; 를 입력했을 때,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;fgets&lt;/code&gt; 가 어떻게 문자열을 나열하는지 보여준다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;center&quot;&gt;Index&lt;/th&gt;
&lt;th&gt;0&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;th&gt;5&lt;/th&gt;
&lt;th&gt;6&lt;/th&gt;
&lt;th&gt;7&lt;/th&gt;
&lt;th&gt;8&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;Input String&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'a'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'b'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;' '&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'c'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'d'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;' '&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'e'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'\n'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'\0'&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;Start Index&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;End Index&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;토큰 시작 인덱스와 종료 인덱스는 시작과 함께 처음 인덱스인 &lt;code&gt;0&lt;/code&gt; 에서 시작한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Start Index : 하나의 토큰이 시작되는 인덱스.&lt;/li&gt;
&lt;li&gt;End Index : 토큰이 끝나는 인덱스 or &amp;quot;델리미터(Delim)&amp;quot; 에 해당하는 인덱스.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이를 요약하자면,&lt;/p&gt;
&lt;p&gt;Start Index 는 지정한 Delim 이 아니라면 멈춰서 기다리고,&lt;br&gt;End Index 는 지정된 Delim 에 해당하는 문자가 나올 때 까지 탐색한다.(이동한다.)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;현재 상태는,&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;완전한 초기 상태로, Start Index 와 End Index 가 아직&lt;/p&gt;
&lt;p&gt;Slice 하기 위한 위치에 안착하지 않은 상태라고 인식해야 한다.&lt;/p&gt;
&lt;p&gt;(&lt;code&gt;&amp;#39;a&amp;#39;&lt;/code&gt; 대신에 &lt;code&gt;&amp;#39; &amp;#39;&lt;/code&gt; 가 올 수도 있으므로.)&lt;/p&gt;
&lt;p&gt;알고리즘을 위한 델리미터로,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;빈 칸(&lt;code&gt;&amp;#39; &amp;#39;&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;개행문자(&lt;code&gt;&amp;#39;\n&amp;#39;&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;종료문자(&lt;code&gt;&amp;#39;\0&amp;#39;&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;등등..&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;을 Delimeter 로 지정했다고 생각해야 한다.&lt;/p&gt;
&lt;p&gt;정확히는 ch == 32 || ch == 0 || 9 &amp;lt;= ch &amp;lt;= 13&lt;/p&gt;
&lt;p&gt;이라는 유니코드에 해당하면 전부 Delimeter 로 간주한다. (이거 매우 유용)&lt;/p&gt;
&lt;p&gt;그렇다면, 위에서 입력된 문자 리스트에서,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;#39; &amp;#39;&lt;/code&gt;, &lt;code&gt;&amp;#39;\n&amp;#39;&lt;/code&gt;, &lt;code&gt;&amp;#39;\0&amp;#39;&lt;/code&gt; 문자는 전부 Delimeter 에 해당하는 것이다. 이를 염두해 두자.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;Input String 에 따라 문자 하나씩 순회한다고 가정 할 때,&lt;/p&gt;
&lt;p&gt;Start Index 와 End Index 가 어떻게 움직이는지 직접 만든 예제로 함께 보자.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;center&quot;&gt;Index&lt;/th&gt;
&lt;th&gt;0&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;th&gt;5&lt;/th&gt;
&lt;th&gt;6&lt;/th&gt;
&lt;th&gt;7&lt;/th&gt;
&lt;th&gt;8&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;Input String&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'a'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'b'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;' '&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'c'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'d'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;' '&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'e'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'\n'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'\0'&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;Start Index&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;End Index&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;center&quot;&gt;Index&lt;/th&gt;
&lt;th&gt;0&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;th&gt;5&lt;/th&gt;
&lt;th&gt;6&lt;/th&gt;
&lt;th&gt;7&lt;/th&gt;
&lt;th&gt;8&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;Input String&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'a'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'b'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;' '&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'c'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'d'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;' '&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'e'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'\n'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'\0'&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;Start Index&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;End Index&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;현재 상태는, Start Index 와 End Index 가 하나의 토큰을 추출(Slicing) 하기 위한&lt;/p&gt;
&lt;p&gt;위치에 정확히 안착해 있는 상황이다.&lt;/p&gt;
&lt;p&gt;(Start Index != 델리미터) &amp;amp;&amp;amp; (End Index == 델리미터)&lt;/p&gt;
&lt;p&gt;이 상황에서 Start Index 와, End Index 를 사용하여 주어진 문자열에서 새로운 문자열을 생성한다.&lt;/p&gt;
&lt;p&gt;그리고, 미리 만들어 둔 문자열 주소 배열(&lt;code&gt;char**&lt;/code&gt;)에 등록한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그래서, 생각 해 보자.&lt;/p&gt;
&lt;p&gt;현재 표로서, 우리는 토큰 시작 인덱스가 &lt;code&gt;0&lt;/code&gt; 이며, 끝이 &lt;code&gt;2&lt;/code&gt; 라는 것을 알게 되었다.&lt;/p&gt;
&lt;p&gt;그렇다면, 이 부분을 따로 저장하면 된다.&lt;/p&gt;
&lt;p&gt;만약에 유틸리티 메서드를 사용한다면, &lt;code&gt;strcmp&lt;/code&gt; 를 사용해도 되고,&lt;/p&gt;
&lt;p&gt;나처럼 유틸리티 메서드를 전혀 사용하지 않고 전부 구현한다면, 이를 복사하는 과정을 만들어야 한다.&lt;/p&gt;
&lt;p&gt;현재 위의 상황은 Start Index 가 &amp;quot;토큰 시작 인덱스&amp;quot; 에 정확히 안착했으며,&lt;/p&gt;
&lt;p&gt;End Index 는 &amp;quot;토큰 종료 인덱스&amp;quot; 에 정확히 안착했다. 이 조건 하에서 문자를 복사하여 저장한다.&lt;/p&gt;
&lt;p&gt;지정된 영역의 토큰을 저장했으므로, Start Index 와 End Index 는 &amp;quot;그 다음 인덱스&amp;quot; 로 이동한다.&lt;/p&gt;
&lt;br/&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;center&quot;&gt;Index&lt;/th&gt;
&lt;th&gt;0&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;th&gt;5&lt;/th&gt;
&lt;th&gt;6&lt;/th&gt;
&lt;th&gt;7&lt;/th&gt;
&lt;th&gt;8&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;Input String&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'a'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'b'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;' '&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'c'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'d'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;' '&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'e'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'\n'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'\0'&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;Start Index&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;End Index&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;center&quot;&gt;Index&lt;/th&gt;
&lt;th&gt;0&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;th&gt;5&lt;/th&gt;
&lt;th&gt;6&lt;/th&gt;
&lt;th&gt;7&lt;/th&gt;
&lt;th&gt;8&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;Input String&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'a'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'b'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;' '&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'c'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'d'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;' '&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'e'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'\n'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'\0'&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;Start Index&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;End Index&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;center&quot;&gt;Index&lt;/th&gt;
&lt;th&gt;0&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;th&gt;5&lt;/th&gt;
&lt;th&gt;6&lt;/th&gt;
&lt;th&gt;7&lt;/th&gt;
&lt;th&gt;8&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;Input String&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'a'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'b'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;' '&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'c'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'d'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;' '&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'e'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'\n'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'\0'&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;Start Index&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;End Index&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;이 상황에서 다시 토큰화를 수행하여 저장한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;결국 이러한 과정은&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;center&quot;&gt;Index&lt;/th&gt;
&lt;th&gt;0&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;th&gt;5&lt;/th&gt;
&lt;th&gt;6&lt;/th&gt;
&lt;th&gt;7&lt;/th&gt;
&lt;th&gt;8&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;Input String&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'a'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'b'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;' '&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'c'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'d'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;' '&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'e'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'\n'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'\0'&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;Start Index&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;End Index&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;문자열의 마지막을 의미하는 &lt;code&gt;&amp;#39;\0&amp;#39; == 0&lt;/code&gt; 데이터에 도달하고 나서야 종료된다.&lt;/p&gt;
&lt;p&gt;최종적으로 하나의 문자열을 토큰화 한 뒤 나온 결과물은,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;ab&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;cd&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;e&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;가 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;위에서 제시한 예제는, 이러한 트리거를 가진다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;1. 만약에 Start, End 인덱스가 Delim 에 해당하지 않을 경우&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이는 &lt;code&gt;Start&lt;/code&gt;, &lt;code&gt;End&lt;/code&gt; 가 각각 &lt;code&gt;&amp;#39;a&amp;#39;&lt;/code&gt;, &lt;code&gt;&amp;#39;b&amp;#39;&lt;/code&gt; 라는 예시를 들어도 될 것 같다.&lt;/p&gt;
&lt;p&gt;이 상황에서는 아직 &lt;code&gt;End&lt;/code&gt; 인덱스가 종료 인덱스로 정확히 안착하지 않은 상황이다.&lt;/p&gt;
&lt;p&gt;따라서, &lt;code&gt;End&lt;/code&gt; 인덱스만 한 칸 이동시킨다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;2. 만약에 Start, End 인덱스가 Delim 에 전부 해당 될 경우&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이 경우, 애초에 토큰을 시작할 인덱스를 &lt;code&gt;Start&lt;/code&gt; 가 찾지도 못한 상황이기 때문에,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Start&lt;/code&gt; 와 &lt;code&gt;End&lt;/code&gt; 둘 다 하나씩 더한다.&lt;/p&gt;
&lt;p&gt;참고로, 곧 작성하게 될 코드에서, 이 경우 &lt;code&gt;Start&lt;/code&gt;, &lt;code&gt;End&lt;/code&gt; 는 각각 인덱스가 동일하다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;3. 만약에 Start 인덱스는 Delim 이 아니며, End 인덱스는 Delim 에 해당 할 경우&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이 경우는 정확하게 &lt;code&gt;Start&lt;/code&gt; 인덱스는 토큰의 시작 인덱스에 안착했으며,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;End&lt;/code&gt; 인덱스는 정확하게 토큰 종료 인덱스에 안착 한 상황이다.&lt;/p&gt;
&lt;p&gt;이 조건 하에 저장할 토큰을 생성하여 저장하고, 반환될 토큰 배열에 추가한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;위의 세 가지 조건이 맞물려서 Tokenizer 가 만들어진다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Tokenizer 구현하기&lt;/h2&gt;
&lt;p&gt;사실상 유틸리티를 내가 직접 구현했기 때문에, 다른 사람의 눈으로 보기에는&lt;/p&gt;
&lt;p&gt;해석이 조금 힘들 수 있으므로, 먼저 구현하게 될 내용을 작성 해 보자면,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&amp;quot;주소&amp;quot; 를 저장할 유연한 배열을 만든다. (&lt;code&gt;char**&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;주어진 문자열을 순회할 포인터를 생성한다.&lt;/li&gt;
&lt;li&gt;시작 인덱스와 종료 인덱스를 생성한다.&lt;/li&gt;
&lt;li&gt;순회 포인터를 통해 문자열을 탐색한다. (1 글자씩.)&lt;/li&gt;
&lt;li&gt;위의 3 가지 조건에 따라 시작 인덱스와 종료 인덱스를 이동시킨다.&lt;/li&gt;
&lt;li&gt;토큰화에 적합한 인덱스에 머물렀을 때, 새로운 &amp;quot;문자열 장소&amp;quot;를 생성하며 그 안에 저장한다.&lt;/li&gt;
&lt;li&gt;만약에 1 번에서 생성한 &amp;quot;주소&amp;quot; 저장 배열이 꽉 찼을 경우, 용량을 2 배로 늘린다. (&lt;code&gt;realloc&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;4 로 이동한다.&lt;/li&gt;
&lt;li&gt;토큰화가 마무리 되었을 경우, 현재 &amp;quot;주소&amp;quot; 포인터에 무조건적으로 &lt;code&gt;NULL&lt;/code&gt; 을 넣어준다.&lt;/li&gt;
&lt;li&gt;이 토큰들을 반환한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;참고로, 나의 제약은 &lt;code&gt;stdio.h&lt;/code&gt; 라이브러리만 사용하며, &lt;code&gt;malloc&lt;/code&gt;, &lt;code&gt;free&lt;/code&gt;, &lt;code&gt;realloc&lt;/code&gt;, &lt;code&gt;calloc&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;만 사용한다. 그리고, &lt;code&gt;scanf&lt;/code&gt;, &lt;code&gt;printf&lt;/code&gt; 는 사용하지 않는다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;코드&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;// 필요 라이브러리
#include&amp;lt;stdio.h&amp;gt;

extern void* malloc(size_t byte);
extern void free(void* memory);
extern void* realloc(void* memory, size_t byte);

// 토큰화를 위한 메서드들
char** tokenizer(const char* input);
void freeTokens(char** tokens);
_Bool isBlank(char ch);

// 컴파일 후 즉시 실행될 메인 로직
int main(void) {
    char input[255];

    // sizeof(input) == 255 이며, input 이 동적 배열일 경우, 사이즈를 직접 넣어줘야 함.
    fgets(input, sizeof(input), stdin);

    char** tokens = (char**)tokenizer(input);
    char** tokensPtr = tokens;

    while(*tokensPtr) {
        fputs(*tokensPtr++, stdout); fputc(&amp;#39;\n&amp;#39;, stdout);
    }

    freeTokens(tokens);

    return 0;
}

// 입력된 문자열을 토큰화하여 반환한다.
// const 로 지정한 이유는 input 자체의 주소를 변경하지 않겠다는 일종의 표식으로 남겨둠.
char** tokenizer(const char* input) {
    // 처음 만들 &amp;quot;주소 배열&amp;quot; 의 사이즈는 5 로 지정한다.
    int size = 5;
    // 현재 &amp;quot;주소 배열&amp;quot; 의 저장 현황. 초기화로 0 으로 지정한다.
    int currSize = 0;

    // 생성될 토큰의 주소를 보관할 메모리 공간을 창출한다.
    char** tokens = (char**)malloc(sizeof(char*) * (size + 1));
    // 생성된 토큰을 저장할 순회 포인터를 생성한다.
    char** tokensPtr = (char**)tokens;

    // 입력된 문자열을 순회할 포인터를 생성한다.
    // 애초에 const 로 선언된 포인터는 건드리지 않는 것이 좋은데,
    // char* 에 해당하도록 알맞게 바꾸고 싶다면 앞에 (char*) 을 붙여야 한다.
    char* inputPtr = (char*)input;

    // 토큰 시작 인덱스, 종료 인덱스를 지정한다.
    // 나는 tknIdx, currIdx 로 사용하는데, 혹시 이 코드를 보는 분을 위해서 명확히 작명했다.
    int start = 0;
    int end = 0;

    // 입력된 문자열이 &amp;#39;\0&amp;#39; == 0 에 도달 할 때 까지 순회한다. (끝까지 순회한다.)
    while(*inputPtr) {
        // 현재 문자를 추출하고, 입력 포인터를 동시에 한 칸 옮긴다.
        char ch = *inputPtr++;

        // 만약에, 현재 순회 문자가 Delimeter 에 해당한다면.
        if(isBlank(ch) == 1) {
            // 이 경우, 시작 인덱스와 종료 인덱스 둘 다 안착하지 못한 상황.
            if(start == end) {
                // 나의 경우, 전위 연산자를 이용하여 둘 다 이런 식으로 이동시킨다.
                start = ++end;
                // 곧바로 다시 다음 문자 탐색으로 넘어간다.
                // char ch = *inputPtr++; 로 이동.
                continue;
            }

            // 이 분기에 들어왔다면, 시작 인덱스와 종료 인덱스 둘 다 성공적으로 안착.

            // 토큰의 크기는 정확히 &amp;quot;종료 인덱스 - 시작 인덱스&amp;quot; 가 된다.
            int tknSize = end - start;
            // 새로운 토큰을 저장하기 위해 문자열 메모리를 생성한다.
            char* newTkn = (char*)malloc(sizeof(char) * (tknSize + 1));
            // 토큰을 이후에 정확하게 사용하기 위해서는,(기본 라이브러리까지 포함)
            // 문자열의 마지막을 정확하게 0 이라는 데이터로 넣어주어야 한다.
            *(newTkn + tknSize) = 0;
            // 생성된 토큰 문자열을 순회하기 위한 포인터 생성
            char* newTknPtr = newTkn;

            // 토큰 시작 인덱스 문자부터, 종료 직전까지 문자들을 추출하여 저장한다.
            // 따로 인덱스를 생성해서 순회해도 되는데, 나는 좀 직관적으로 start 를 이용했다.
            while(start != end) {
                // 입력된 문자열을 인덱스로 접근하여 추출하고, 생성된 문자열 메모리에 삽입한다.
                // 코드가 길어질 것 같아서, 둘 다 후위 연산자를 이용했다.
                // 포인터는 가르키는 주소 자체가 이동하고, 정수는 +1 을 의미한다.
                *newTknPtr++ = input[start++];
            }
            // 복사를 완성한 토큰 문자열은 곧 &amp;quot;주소&amp;quot; 이므로,
            // 현재 가르키는 토큰 주소에 삽입하고,
            // 현재 저장된 토큰의 사이즈를 더한다.
            *tokensPtr++ = newTkn; currSize++;

            // 종료 인덱스를 먼저 더하고, 그 값을 start 에 할당한다.
            // 위에서 제시했던 예제를 보면 알겠지만, 이러한 표현식이 정확하게 일치할 것이다.
            // 이건 내가 사용하는 방식이고, 다른 방식으로 개조해도 상관이 없다.
            start = ++end;

            // 만약에 우리가 맨 위에서 생성했던 주소 배열의 최대치에 도달했다면,
            // realloc 을 통해 데이터를 보존 한 채로 2 배로 늘린다.
            if(size == currSize) {
                size *= 2;
                tokens = (char**)realloc(tokens, sizeof(char*) * (size + 1));
                // 새로운 토큰 주소 배열이 할당되었으므로, 기존의 tokensPtr 은 잘못됨.
                // 따라서, 용량이 늘어난 주소를 바탕으로 현재 사이즈를 더해 포인터를 복구.
                tokensPtr = (char**)(tokens + currSize);
            }

        // 그렇지 않다면, 미리 선언한 조건에 따라 토큰 종료 인덱스를 한 칸 옮긴다.
        } else { // 이 경우, 시작 인덱스는 안착에 성공했으며, 종료 인덱스가 안착하지 못한 상황.
            end++;
        }
    }
    *tokensPtr = NULL;

    return tokens;
}

// tokenizer 로 생성된 모든 메모리를 해제하기 위한 메서드.
void freeTokens(char** tokens) {
    // tokenizer 로 인해 생성된 모든 토큰들을 가져와서, 시작 주소를 tokensPtr 에 할당한다.
    char** tokensPtr = (char**)tokens;

    // 우리가 지정한 NULL 값이 나올 때 까지 순회한다.
    while(*tokensPtr) {
        // 해당 문자열 메모리 해제
        free(*tokensPtr++);
    }
    // 주소값들을 저장하는 배열을 해제한다.
    free(tokens);

    // 종료.
    return;
}

// Delimeter 에 해당하면 1 을 반환하며, 그렇지 않다면 0 을 반환한다.
// 알고리즘 전용으로 만들었으므로, 개행문자, 빈 칸, 혹은 문자열 종료 문자를 포함하도록 만들었다.
_Bool isBlank(char ch) {
    // &amp;#39; &amp;#39; 혹은 &amp;#39;\0&amp;#39; 일 경우.
    if(ch == 32 || ch == 0) {
        return 1;
    // 그 외 개행문자, 혹은 `\t` 와 같은 특수 공백문자를 의미하는 경우.
    } else if(ch &amp;gt;= 9 &amp;amp;&amp;amp; ch &amp;lt;= 13) {
        return 1;
    // 그렇지 않다면, 특수기호나 알파벳 숫자 등등에 해당됨.
    } else {
        return 0;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ gcc 5-tokenizer.c -Wall -Wextra -pedantic -O2 -o 5-tokenizer
$ ./5-tokenizer
ab c def g
ab
c
def
g&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 코드에 주석을 촘촘히 넣어놨는데,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;tokenizer&lt;/code&gt; 메서드는 중간에 따로 메서드로 뺄 부분도 존재한다.&lt;/p&gt;
&lt;p&gt;예를 들어, 지정된 두 인덱스 사이의 값을 배열로 반환하는 과정이다.&lt;/p&gt;
&lt;p&gt;그러나, 나는 내 코드라서 전부 읽히기 때문에 작성했지만,&lt;/p&gt;
&lt;p&gt;이러한 코드 또한 자신만의 Clean Code 로서 만들 수 있다.&lt;/p&gt;
&lt;p&gt;아마, 굳이 이러한 제약을 두지 않고 로직을 작성한다면, 훨씬 깔끔하고 적게 작성할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;솔직히 누가 토큰화 과정을 이해하고 싶어할까? 생각이 들기도 한다.&lt;/p&gt;
&lt;p&gt;실제로, 나도 나만의 방식과 계기로 토큰화 유틸리티를 제작하기 전 까지는&lt;/p&gt;
&lt;p&gt;이해할 필요를 상상조차 하지 못했다.&lt;/p&gt;
&lt;p&gt;그러나, 이 과정은 &amp;quot;특정 데이터 패턴&amp;quot; 에 따라, 데이터를 분리한다.&lt;/p&gt;
&lt;p&gt;또한, 메모리 생성, 그리고 해제. 유연한 주소 배열 관리라는 항목이 들어가 있다.&lt;/p&gt;
&lt;p&gt;인덱스에 대한 철저한 관리와, 포인터를 통한 내부 데이터 조회와 삽입이라는 과정도 겪는다.&lt;/p&gt;
&lt;p&gt;처음에는 정말 어려운데, 지속적으로 작성하고 관리하다 보면,&lt;/p&gt;
&lt;p&gt;이러한 과정을 머릿속으로 이해 할 뿐만 아니라, 적용이 가능한 특수 상황에서도 떠올릴 수 있을 것이다.&lt;/p&gt;
&lt;p&gt;그 예로, 알고리즘 문제를 풀 때, 대다수가 사용하는 라이브러리를 사용하지 못하므로,&lt;/p&gt;
&lt;p&gt;해당 라이브러리를 직접 구현하여 사용하거나, 최소화 혹은 최적화하여 사용한다.&lt;/p&gt;
&lt;p&gt;예를 들면, Queue, Stack 이 존재한다.&lt;/p&gt;
&lt;p&gt;(구조체를 작성하고 적용하기 위해선 수백 줄이 더 작성되므로 최소화 방안을 찾게 된다....)&lt;/p&gt;
&lt;br/&gt;


&lt;hr&gt;
&lt;h2&gt;참조&lt;/h2&gt;
&lt;p&gt;없뜸&lt;/p&gt;</description>
      <category>Algorithm</category>
      <category>C</category>
      <category>c token</category>
      <category>c tokenizer</category>
      <category>c 언어</category>
      <category>C 토큰</category>
      <category>C 토큰화</category>
      <category>fgets</category>
      <category>Token</category>
      <category>커스터마이징</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/233</guid>
      <comments>https://codecreature.tistory.com/233#entry233comment</comments>
      <pubDate>Thu, 14 Aug 2025 18:34:00 +0900</pubDate>
    </item>
    <item>
      <title>C 프로그램에서 정수, 문자열 상호변환 메서드 만들기</title>
      <link>https://codecreature.tistory.com/232</link>
      <description>&lt;h2&gt;제목 : C 프로그램에서 정수, 문자열 상호변환 메서드 만들기&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;이 글을 작성하는 이유&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;C 에서도 문자열을 수로 바꾸는 메서드가 존재한다.&lt;/p&gt;
&lt;p&gt;이러한 내장 메서드는 어셈블리 급으로 최적화를 해 놓았고,&lt;/p&gt;
&lt;p&gt;또한 검증되었기 때문에 사용하는 것이 더 정확하고 편하다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, 나는 C 언어로 알고리즘을 푸는 데 있어, 제약을 걸었다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;stdio.h&lt;/code&gt; 내장 라이브러리를 제외한 모든 유틸리티 메서드를 &amp;quot;직접&amp;quot; 제작하는 것.&lt;/p&gt;
&lt;p&gt;물론, &lt;code&gt;malloc&lt;/code&gt;, &lt;code&gt;free&lt;/code&gt;, &lt;code&gt;realloc&lt;/code&gt;, &lt;code&gt;calloc&lt;/code&gt; 과 같은&lt;/p&gt;
&lt;p&gt;동적 메모리 할당과 해제에 필요한 메서드는 &lt;code&gt;extern&lt;/code&gt; 으로 가져와서 사용한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;나는 이러한 제약을 스스로 걸어서, 각 라이브러리가 &amp;quot;어떻게&amp;quot; 동작하는지 이해하기 위해&lt;/p&gt;
&lt;p&gt;이러한 제약을 지키고 있다.&lt;/p&gt;
&lt;p&gt;(물론 어떤 문제들은 까마득하기도 하다.)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;알고리즘 문제를 풀기 위해 &amp;quot;나만의 메서드&amp;quot; 를 만들어서 사용하고 있는데,&lt;/p&gt;
&lt;p&gt;혹시라도 특정 상황에서 문자열과 정수 상호변환을 직접 구현할 필요가 있는 경우,&lt;/p&gt;
&lt;p&gt;내가 만든 메서드가 도움이 될 것이라고 판단했다.&lt;/p&gt;
&lt;p&gt;따라서, 이 글을 작성한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;생각보다 유의해서 봐야 할 점&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;타입의 변환은 항상 문자, 그리고 문자열에 초점을 둬야 한다.&lt;/p&gt;
&lt;p&gt;다루고 있는 정수는 어떤 과정으로 문자열로 변할 수 있는지,&lt;/p&gt;
&lt;p&gt;그리고 현재 문자열은 어떻게 정수로 변할 수 있는지 상상 해 보자.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이 과정을 이해하기 위해서는,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;10진수를 사용하고 있다.&lt;/li&gt;
&lt;li&gt;문자와 문자열에 집중해야 한다.&lt;/li&gt;
&lt;li&gt;그렇다면 &amp;quot;음수&amp;quot; 는 어떻게 구현하지?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 3 가지를 꼭 기억해야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;isBlank - 현재 문자는 공백, 혹은 마지막인가?&lt;/h2&gt;
&lt;p&gt;문자열을 다룰 때 가장 중요한 것이, 현재 지정한 문자는 공백, 혹은 마지막인 &lt;code&gt;0&lt;/code&gt; 에 해당하는&lt;/p&gt;
&lt;p&gt;데이터인지 확인하는 것이다.&lt;/p&gt;
&lt;p&gt;알고리즘을 풀 때, 주어지는 데이터의 분할은 &amp;quot;공백&amp;quot; 혹은 &amp;quot;개행&amp;quot; 으로 이루어진다.&lt;/p&gt;
&lt;p&gt;예를 들어, 입력은 이러한 방식으로 주어진다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;3 2 4&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;fgets&lt;/code&gt; 메서드로 이 데이터를 담았을 때, 이러한 형상을 이룬다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Index&lt;/th&gt;
&lt;th&gt;0&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;th&gt;5&lt;/th&gt;
&lt;th&gt;6&lt;/th&gt;
&lt;th&gt;7&lt;/th&gt;
&lt;th&gt;...&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&amp;#39;3&amp;#39;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;' '&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;#39;2&amp;#39;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;' '&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;#39;4&amp;#39;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'\n'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;or&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;3
2
4
6
5&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;다행히도, &lt;code&gt;fgets&lt;/code&gt; 는 공백 다음 주소에 &lt;code&gt;0&lt;/code&gt; 을 삽입해 준다.&lt;/p&gt;
&lt;p&gt;그러나, 여전히 &amp;quot;개행 문자&amp;quot; 들은 그대로 담아서 준다.&lt;/p&gt;
&lt;p&gt;그렇다면, 우리는 이 숫자 문자들을 하나의 토큰 으로 만들기 위해서,&lt;/p&gt;
&lt;p&gt;개행 혹은 빈 칸을 인식 할 필요가 있다. (제거하기 위함)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그런데, 나는 C 를 이용하여 알고리즘을 풀 때, &lt;code&gt;stdio.h&lt;/code&gt; 를 제외한 모든 유틸리티를&lt;/p&gt;
&lt;p&gt;제작하기로 결정했다.&lt;/p&gt;
&lt;p&gt;따라서, 빈칸이나, 개행에 해당하는 모든 문자의 유니코드를 간단히 외워 사용하면 된다.&lt;/p&gt;
&lt;p&gt;개행이나, 탭과 같은 문자들도 포함하는 영역이 있으므로, 간단히 사용하면 된다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;#39; &amp;#39;&lt;/code&gt; == 32&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0&lt;/code&gt; == 0&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\n&lt;/code&gt; or &lt;code&gt;\t&lt;/code&gt; 및 잡다 --&amp;gt; 9 ~~ 13&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;따라서, 32 or 0 or 9 ~ 13&lt;/p&gt;
&lt;p&gt;이것만 외우면 된다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;_Bool isBlank(char ch) {
    // if(ch == 32 || ch == 0)
    if(ch == &amp;#39; &amp;#39; || ch == &amp;#39;\0&amp;#39;) {
        return 1;
    } else if(ch &amp;gt;= 9 &amp;amp;&amp;amp; ch &amp;lt;= 13) {
        return 1;
    } else {
        return 0;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;맨 위는 문자 형식으로 표현했는데, 나는 사실 숫자로 편하게 지정한다. (수백번은 사용한듯)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;#39;\0&amp;#39;&lt;/code&gt; == &lt;code&gt;0&lt;/code&gt; 은 문자 상 &amp;quot;빈 칸&amp;quot; 혹은 &amp;quot;개행&amp;quot; 에 비슷한 과에 속할 수 있나?&lt;/p&gt;
&lt;p&gt;생각을 했었는데,&lt;/p&gt;
&lt;p&gt;바로 밑에서 &lt;code&gt;parseInt&lt;/code&gt; 를 입력받은 수에 바로 사용하기 위해서는 추가하는게 훨씬 편하다는 것을&lt;/p&gt;
&lt;p&gt;깨닫게 되어 이러한 방식으로 추가했다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;parseInt - 문자열을 정수로 변환하기&lt;/h2&gt;
&lt;p&gt;아무래도 Java 를 이용하여 알고리즘을 풀다가, C 로 다시 시작하여&lt;/p&gt;
&lt;p&gt;나에게 가장 직관적인 메서드 이름인 parseInt 로 정하게 되었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그렇다면, 어떻게 문자 배열(문자열) 을 &amp;quot;정수&amp;quot; 로 변환할까?&lt;/p&gt;
&lt;p&gt;나는 처음에 이러한 역할을 하는 유틸리티 메서드를 제작할 때 많은 고민이 있었다.&lt;/p&gt;
&lt;p&gt;만약에 헷갈린다면, 이러한 생각을 해 보자.&lt;/p&gt;
&lt;p&gt;만약에, 기존 정수를 &amp;quot;이진값&amp;quot; 으로 만들려면 어떻게 해야 하는가?&lt;/p&gt;
&lt;p&gt;또한, &amp;quot;이진값&amp;quot; 을 기존 정수로 만들려면 어떻게 해야 하는가?&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이러한 발상에서 나의 문자열로 정수 상호변환에 대한 유틸리티 메서드가 탄생했다.&lt;/p&gt;
&lt;p&gt;설명은 이따가 하는게 낫다는 생각이 들고, 일단 코드를 보자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int parseInt(char* str) {
    // 음수 양수 구별 위함.
    int sign = 1;
    // 메서드에서 만들게 될 최종 값을 담아둘 장소.
    int result = 0;

    // 순회용 포인터를 생성
    char* strPtr = (char*)str;

    char first = *strPtr; // or str[0];

    if(first == &amp;#39;-&amp;#39;) {
        // 파싱하게 될 문자열은 &amp;quot;음수&amp;quot; 이다.
        sign = -1;
        // 순회용 포인터를 한 칸 옮긴다. (그 다음부터는 숫자를 의미하므로)
        strPtr++;
    }

    // 숫자로 파싱하려는 현재 문자가 빈 공간을 의미하는 문자가 아닐 때 까지 반복한다!
    while(isBlank(*strPtr) != 1) {
        // 현재 포인터가 가르키는 문자를 가져온다.
        char currCh = *strPtr;
        // 포인터를 그 다음으로 한 칸 옮긴다.
        strPtr++;

        // 숫자 문자에서 숫자로 변환한다.
        int currNum = currCh - &amp;#39;0&amp;#39;;

        // 기존 수를 10 배로 해서 1 번째 자리를 열어준다.
        result *= 10;
        // 확장된 1 번째 자리에 현재 수를 넣어준다.
        result += currNum;
    }

    // 순수 숫자와, 이 문자열이 가르키는 음수 혹은 양수로 만들어 주기 위해 sign 을 곱한다.
    return result * sign;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 방식은 숫자 문자열을 처리하기 위한 방식이며, 고정된 지식이 아니다.&lt;/p&gt;
&lt;p&gt;자신만의 다른 의견이 있다면, 이를 참조하여 더 깔끔하게 만들 수 있다.&lt;/p&gt;
&lt;p&gt;위의 코드는 예시를 들기 위한 것이고, 이를 단축하여 만든다면,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int parseInt(const char* str) {
    int sign = 1;
    int result = 0;

    char* strPtr = (char*)str;
    if(*strPtr == &amp;#39;-&amp;#39;) {
        sign = -1;
        strPtr++;
    }

    while(isBlank(*strPtr) != 1) {
        result *= 10;
        result += *strPtr++ - &amp;#39;0&amp;#39;;
    }

    return result * sign;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이렇게 깔끔한 형식을 유지 할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;물론, for 문을 사용하여 관리하는 것이 직관적이다.&lt;/p&gt;
&lt;p&gt;그러나, 나는 포인터를 적극적으로 사용하고, 이 포인터를 루프로 돌리는 것이 개인적으로 더 직관적이었다.&lt;/p&gt;
&lt;p&gt;C 를 사용하면서 든 생각은, 생각보다 포인터를 자주 쓰다 보면 편하다는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;intToStr - 정수를 문자열로 변환하기&lt;/h2&gt;
&lt;p&gt;이름을 좀 생소하게 지었는데,&lt;/p&gt;
&lt;p&gt;Java 처럼 &lt;code&gt;toString&lt;/code&gt; 으로 지을까 하다가, 메서드가 조금 더 직관적으로 보이도록 조정했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;말 그대로, &lt;code&gt;int&lt;/code&gt; --&amp;gt; &lt;code&gt;char*&lt;/code&gt;(문자열) 로 변환하는 메서드이다.&lt;/p&gt;
&lt;p&gt;이 때 중요한 것은, 문자열의 마지막은 개행이나 빈칸 따위가 아닌, 정확히 &lt;code&gt;0&lt;/code&gt; 이 들어가야 한다는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그렇다면, 이 유틸 메서드에서 중요하게 여겨야 할 요소는 무엇일까? 생각해 보았다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;변환될 문자열의 정확한 길이를 알아야 한다.&lt;/li&gt;
&lt;li&gt;각 자리수의 문자를 &amp;quot;어떻게&amp;quot; 문자열에 넣을 것인지 고민해야 한다.&lt;/li&gt;
&lt;li&gt;만약에, &amp;quot;음수&amp;quot; 라면 어떻게 할 것인가?&lt;/li&gt;
&lt;li&gt;정적 문자열을 사용할 것인가? 동적 문자열을 사용할 것인가?&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;p&gt;나는 동적 문자열을 사용하고, 다 쓴 후에는 &lt;code&gt;free&lt;/code&gt;(메모리 해제) 를 시켰다.&lt;/p&gt;
&lt;p&gt;물론, &lt;code&gt;malloc&lt;/code&gt; 을 먼저 사용했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;먼저 설명에 가까운 코드를 먼저 짜 보겠다&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;extern void* malloc(size_t byte); // stdlib.c 에서 추출됨.

char* intToStr(int target) {
    // 음수인지 양수인지 알아야 한다.
    int sign = 1;

    // 만약 변환 타겟이 음수라면, 기호를 바꿔야 한다. target, sign 둘 다
    if(target &amp;lt; 0) {
        sign = -1;
        target *= -1;
    }

    // 우리가 10 을 곱해가며 정수에 자리를 마련했었다.
    // 이를 역으로 이용하여, 10을 나눠가며 마련해야 할 문자열 길이를 추출한다.
    int len = 0;

    // temp 는 길이 추출용 임시 변수
    int temp = target;

    // do - while 구문을 사용하는 이유는, 0 일 수도 있기 때문이다.
    // target 이 0 이라면, 문자열 길이는 0 으로 도출되어 버리는 것을 방지한다.
    //
    // 즉, 이 함수는 단순 숫자로서의 문자열 길이를 도출한다.
    do {
        // 우리는 10 진수를 사용하므로.
        temp /= 10;
        len++;
    } while(temp != 0);

    /**
    여기서 가장 중요한 점은, 양수인가? 음수인가? 이다.
    음수일 경우, 문자열 &amp;quot;가장 앞&amp;quot;에 &amp;#39;-&amp;#39; 라는 문자가 와야 한다.
    이를 염두에 두고 계산해야 한다.

    그런데, 어떻게 가장 앞에 &amp;#39;-&amp;#39; 를 둬야 할까?

    맨 앞의 수부터 추출해야 할까? 맨 뒤 부터 추출해야 할까?

    나의 경우, 1, 10, 100, ... 순으로 추출하여 문자를 넣는다.

    즉, 뒤에서부터 문자를 채운다는 의미이다.

    뒤에서부터 앞 부분까지 문자를 채우되,

    음수의 경우 &amp;quot;맨 앞&amp;quot; 이 &amp;#39;-&amp;#39; 가 되고, 양수는 이 분기를 무시한다.
    */

    int size;
    // 양수 일 경우, 계산된 len 변수는 정확한 문자열 길이가 된다.
    if(sign == 1) {
        size = len;
    } else { // sign == -1
        // 음수일 경우, 맨 앞에 &amp;#39;-&amp;#39; 를 놓아야 하기 때문에, 공간을 하나 더 만든다.
        size = len + 1;
    }

    // 우리가 만들 문자열의 길이는 구해졌으므로, 동적 할당을 이용하여 문자 공간을 형성한다.
    char* result = (char*)malloc(sizeof(char) * (size + 1));
    // 맨 마지막에 &amp;quot;종료&amp;quot; 를 알리는 것은 필수필수필수이다.
    *(result + size) = 0;

    // 문자를 채우는 과정은 뒤에서부터이며, 문자열의 마지막 인덱스는 정확히 size - 1 이다.
    int endIdx = size - 1;

    // 음수일 경우, &amp;#39;-&amp;#39; 를 넣어야 하고, 양수일 경우 넣지 않는다.
    int startIdx;
    if(sign == 1) {
        // 양수일 경우, 숫자만 채워진다.
        startIdx = 0;
    } else {
        // 음수일 경우, 인덱스 0 에 &amp;#39;-&amp;#39; 를 넣어줘야 한다.
        startIdx = 1;
    }

    // 순회용 인덱스. 뒤에서부터 숫자 문자를 넣어준다.
    int idx = endIdx;

    // startIdx 는 음수일 경우 1, 양수일 경우 0 이다.
    // 즉, 음수일 경우 맨 앞을 비워 &amp;#39;-&amp;#39; 를 넣을 준비를 한다.
    while(idx &amp;gt;= startIdx) {
        // 숫자로 따졌을 때, 가장 낮은 수를 추출한다.
        int bottomNum = target % 10;

        // 그 다음 수를 추출하기 위해 가장 낮은 자리를 자른다.
        target /= 10;

        // 현재 1 자리 수를 정확한 문자 수로 곧바로 변환.
        char currChar = bottomNum + &amp;#39;0&amp;#39;;

        // result[idx] = currChar; 과 동일하다.
        *(result + idx) = currChar;

        idx--;
    }
    // 만약, 음수라면, 비어있는 첫 번째 자리를 &amp;#39;-&amp;#39; 로 채워주면 된다.
    if(sign == -1) {
        // result[0] = &amp;#39;-&amp;#39;; 과 동일하다.
        *reseult = &amp;#39;-&amp;#39;;
    }

    return result;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 함수는 동적 할당을 통해서 결과물을 내놓으므로, 반드시 외부에서 free 해 줘야 한다.&lt;/p&gt;
&lt;p&gt;만약에 정적 할당을 하고 싶다면, 변수로 문자열 공간을 받고,&lt;/p&gt;
&lt;p&gt;해당 공간에 문자를 작성하면 된다.&lt;/p&gt;
&lt;p&gt;하지만, 반드시 문자열의 마지막에 &lt;code&gt;0&lt;/code&gt; == &lt;code&gt;&amp;#39;\0&amp;#39;&lt;/code&gt; 을 넣어주는 것을 잊으면 안된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;간략화 버전&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;char* intToStr(int target) {
    int sign = 1;
    int len = 0;

    if(target &amp;lt; 0) {
        sign = -1;
        target *= -1;
    }

    int temp = target;
    do {
        temp /= 10;
        len++;
    } while(temp != 0);

    int size = sign == 1 ? len : len + 1;
    int endIdx = size - 1;
    int startIdx = sign == 1 ? 0 : 1;

    char* result = (char*)malloc(sizeof(char) * (size + 1));
    *(result + size) = 0;

    int idx = endIdx;
    while(idx &amp;gt;= startIdx) {
        *(result + idx--) = (target % 10) + &amp;#39;0&amp;#39;;
        target /= 10;
    }
    if(sign == -1) {
        *result = &amp;#39;-&amp;#39;;
    }

    return result;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이러한 방식으로 간결하게 작성 할 수도 있다.&lt;/p&gt;
&lt;p&gt;간결한 코드를 작성하기 위해서, 전위, 후위 연산자에 대한 개념을 숙지하는 것은 매우 중요하다.&lt;/p&gt;
&lt;p&gt;이는 비단 단순 숫자만이 아니라, 포인터 그 자체에도 적용되기 때문이다.&lt;/p&gt;
&lt;p&gt;그리고, 삼항 연산자에 대한 이해도 필수라고 생각한다. (코드를 간결하게 만들고 싶다면)&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;실제로 사용 해 보기&lt;/h2&gt;
&lt;p&gt;이러한 유틸 메서드가 생성된 데에는,&lt;/p&gt;
&lt;p&gt;내가 알고리즘 문제를 C 로 풀 때, &lt;code&gt;stdio.h&lt;/code&gt; 와 동적 메모리와 관련된 함수 빼고,&lt;/p&gt;
&lt;p&gt;모든 유틸 메서드를 &amp;quot;직접&amp;quot; 전부 일일이 작성하여 푸는 제약 때문이다.&lt;/p&gt;
&lt;p&gt;따라서, &lt;code&gt;fgets&lt;/code&gt;, &lt;code&gt;fputs&lt;/code&gt;, &lt;code&gt;fputc&lt;/code&gt; 를 이용하여 입출력을 진행한다.&lt;/p&gt;
&lt;p&gt;(scanf, printf 도 사용하지 않는다는 제약을 걸음.)&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;예제&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include&amp;lt;stdio.h&amp;gt;

extern void* malloc(size_t byte);
extern void free(void* memory);

char* intToStr(int target);
int parseInt(const char* str);
_Bool isBlank(char ch);

int main(void) {
    char input[255];

    fgets(input, sizeof(input), stdin);

    int result = parseInt(input);

    char* resultStr = intToStr(result);

    fputs(resultStr, stdout); fputc(&amp;#39;\n&amp;#39;, stdout);

    free(resultStr);

    return 0;
}

char* intToStr(int target) {
    int sign = 1;
    int len = 0;

    if(target &amp;lt; 0) {
        sign = -1;
        target *= -1;
    }

    int temp = target;
    do {
        temp /= 10;
        len++;
    } while(temp != 0);

    int size = sign == 1 ? len : len + 1;
    int endIdx = size - 1;
    int startIdx = sign == 1 ? 0 : 1;

    char* result = (char*)malloc(sizeof(char) * (size + 1));
    *(result + size) = 0;

    int idx = endIdx;
    while(idx &amp;gt;= startIdx) {
        *(result + idx--) = (target % 10) + &amp;#39;0&amp;#39;;
        target /= 10;
    }
    if(sign == -1) {
        *result = &amp;#39;-&amp;#39;;
    }

    return result;
}
int parseInt(const char* str) {
    int sign = 1;
    int result = 0;

    char* strPtr = (char*)str;
    if(*strPtr == &amp;#39;-&amp;#39;) {
        sign = -1;
        strPtr++;
    }

    while(isBlank(*strPtr) != 1) {
        result *= 10;
        result += *strPtr++ - &amp;#39;0&amp;#39;;
    }

    return result * sign;
}
_Bool isBlank(char ch) {
    if(ch == 32 || ch == 0) {
        return 1;
    } else if(ch &amp;gt;= 9 &amp;amp;&amp;amp; ch &amp;lt;= 13) {
        return 1;
    } else {
        return 0;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Result 1&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ gcc int-str.c -Wall -Wextra -pedantic -O2 -o int-str
$ ./int-str
100
100&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Result 2&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ gcc int-str.c -Wall -Wextra -pedantic -O2 -o int-str
$ ./int-str
15792024
15792024&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Result 3&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ ./int-str
-12345
-12345&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;이렇게 정수-문자열 변환을 쉽게 만들 수 있다.&lt;/p&gt;
&lt;p&gt;물론, 자기만의 메서드를 만드는 과정은 어렵다.&lt;/p&gt;
&lt;p&gt;특히, 앞으로 이 메서드를 사용함에 있어 오류가 존재할지, 어느 상황에 사용할 지는&lt;/p&gt;
&lt;p&gt;직접 사용해 보며 알아야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;printf 를 쓰지 않으니까 맞는 답인데도 오류가 난다?&lt;/h2&gt;
&lt;p&gt;그건 &lt;code&gt;fputs&lt;/code&gt; 의 특성으로, 정말 전달된 문자열만 전달한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;그게 백준 결과에서 어떤 문제가 되는가?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;백준의 정답은 우리가 출력 할 형식만 지정하고 출력하면 끝이 아니다.&lt;/p&gt;
&lt;p&gt;기본적으로 &lt;code&gt;printf&lt;/code&gt; 를 사용한다는 가정 하에 제작된다.&lt;/p&gt;
&lt;p&gt;즉, 마지막에 개행 문자를 통해 다음 줄로 넘어가야 정답으로 인정이 된다.&lt;/p&gt;
&lt;p&gt;만약에 마지막에 &lt;code&gt;fputc(&amp;#39;\n&amp;#39;, stdout);&lt;/code&gt; 을 작성하지 않는다면,&lt;/p&gt;
&lt;p&gt;이건 출력 부족과 동일한 상황이다. 물론, 백준의 에러 메세지에서는 표현되지 않겠지만.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;C 에서의 문자열 입출력은 원래부터도 간단한 문제는 아니었다.&lt;/p&gt;
&lt;p&gt;그러나, &lt;code&gt;fgets&lt;/code&gt;, &lt;code&gt;fputs&lt;/code&gt; 는 문자열을 다루는 데 있어 더 고난도의 작업을 요구한다.&lt;/p&gt;
&lt;p&gt;그러나, 내가 직접 함수를 제작한다면, 이미 누군가가 작성해 놓은 기능을 따라가기 위해&lt;/p&gt;
&lt;p&gt;따로 공부할 필요는 없다. (물론, 이미 작성되어 있는 기능을 사용하는 것이 훨씬 편하다)&lt;/p&gt;
&lt;p&gt;즉, 우리가 다룬 것은 이러하다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;알고리즘의 입출력 관리를 위해 &amp;quot;빈 공간&amp;quot; 에 해당하는 문자를 검사하는 메서드 작성&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;입력된 문자열을 숫자로 바꾸는 메서드 작성&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;결과 출력을 위해 문자열을 숫자로 바꾸는 메서드 작성.&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;p&gt;참고로, &lt;code&gt;isBlank&lt;/code&gt; 메서드는 추후 작성하게 될,&lt;/p&gt;
&lt;p&gt;하나의 입력 줄에 대해서 토큰화를 시키는 데 지대한 영향을 끼친다.&lt;/p&gt;
&lt;p&gt;꼭 기억해 두는 것이 좋지 않을까? 생각된다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;이 글을 작성하며 느낀 것.&lt;/h2&gt;
&lt;p&gt;확실히, 어떤 기능을 만들었다고 곧바로 포스팅하지 않고,&lt;/p&gt;
&lt;p&gt;기능에 충실하며 일관성을 유지하는 메서드를 곧바로 작성할 수준까지 올라오고 나서&lt;/p&gt;
&lt;p&gt;이러한 주제에 대해서 작성한 것이 옳바른 선택이었다고 생각한다.&lt;/p&gt;
&lt;p&gt;그렇지 않았다면, 나의 설명은 분명히 빈약했을 것이라고 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;분명히 개발자 특성상 (나 또한 어느정도) 빠르게 훑겠지만,&lt;/p&gt;
&lt;p&gt;누군가에게는 분명히 도움이 될 것이라고 생각한다.&lt;/p&gt;
&lt;p&gt;혹시라도 궁금한 것이 있다면, &lt;a href=&quot;mailto:rhdwhdals8@gmail.com&quot;&gt;rhdwhdals8@gmail.com&lt;/a&gt; 으로 질문 해주셔도 됩니다.&lt;/p&gt;
&lt;p&gt;환영할게요!&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;만약 나의 C 알고리즘 프로그램 행보가 궁금하다면,&lt;/h2&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://github.com/damhyeong/Algorithm-Exam/blob/main/%EB%B0%B1%EC%A4%80/Silver/18870.%E2%80%85%EC%A2%8C%ED%91%9C%E2%80%85%EC%95%95%EC%B6%95/%EC%A2%8C%ED%91%9C%E2%80%85%EC%95%95%EC%B6%95.c&quot;&gt;공담형의 C 백준 코드 모음집&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;이 사이트가 적정할 것이라고 판단됩니다.&lt;/p&gt;
&lt;p&gt;이 레포에서 C 에 해당하는 코드들을 살펴보시면, 제가 어떻게 작성하는지 알 수 있으실 겁니다.&lt;/p&gt;</description>
      <category>Algorithm</category>
      <category>C</category>
      <category>C 문자열 변환</category>
      <category>C 정수 변환</category>
      <category>fgets</category>
      <category>fputs</category>
      <category>Free</category>
      <category>malloc</category>
      <category>정수 문자열 변환</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/232</guid>
      <comments>https://codecreature.tistory.com/232#entry232comment</comments>
      <pubDate>Mon, 4 Aug 2025 04:25:13 +0900</pubDate>
    </item>
    <item>
      <title>C 알고리즘 문제 scanf, printf 없이 입출력 수행하기 - (fgets, fputs)</title>
      <link>https://codecreature.tistory.com/231</link>
      <description>&lt;h2&gt;제목 : C 알고리즘 문제 scanf, printf 없이 입출력 수행하기&lt;/h2&gt;
&lt;h3&gt;부제 : fgets, fputs 사용법&lt;/h3&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;이 글을 작성하는 이유&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;처음 프로그래밍을 배울 때, 우리는 C 나 Java 로 시작한다.&lt;/p&gt;
&lt;p&gt;나 또한 C, Java 를 처음 대학교에서 접했을 때 scanf 에 해당하는 기능으로&lt;/p&gt;
&lt;p&gt;입력과 출력을 수행했다. (문자열을 통해 입력을 원하는 데이터로 변환)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, 내가 알고리즘을 풀 뿐만 아니라, 파일을 읽는 과정에서&lt;/p&gt;
&lt;p&gt;scanf 는 도리어 보안적으로 위험할 수도 있다는 것을 알게 되었으며,&lt;/p&gt;
&lt;p&gt;또한 동적 입력에 대해 생각보다 잘 대처하지 못한다는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그래서 왜 scanf, printf 를 사용하지 않느냐면,&lt;/p&gt;
&lt;p&gt;내가 이전에 알고리즘을 풀 때 &lt;code&gt;BufferedReader&lt;/code&gt; 객체를 이용해서,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;StringTokenizer&lt;/code&gt; 객체에 넣어 토큰화 시킨 후 문제를 풀었다.&lt;/p&gt;
&lt;p&gt;즉,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;BufferedReader&lt;/code&gt; 를 이용하여 하나의 줄을 통째로 읽는다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;StringTokenizer&lt;/code&gt; 생성시 읽은 줄을 통째로 넣어 문자열 토큰으로 변환했다.&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;p&gt;또한, C 언어에서는 입력의 토큰화를 위한 도구가 &lt;code&gt;strtok&lt;/code&gt; 라는 것이 존재하는데,&lt;/p&gt;
&lt;p&gt;따로 토큰을 담아둘 장소도 외부에서 마련해야 하고,&lt;/p&gt;
&lt;p&gt;복사를 외부에서 해야 한다는 번거로운 점들이 있었다. (C 자체의 함수 무결성을 보장하기 위함이 아닐까?)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;위의 단점들을 극복하기 위해 직접 토큰화 메서드를 제작해서&lt;/p&gt;
&lt;p&gt;지금까지 수십개의 문제를 풀어오며 잘 사용했다. (매번 직접 작성함)&lt;/p&gt;
&lt;p&gt;단, 메모리 해제를 위한 메서드는 각 2차원 배열마다 잘 작성 해 주거나,&lt;/p&gt;
&lt;p&gt;혹은 공통타입을 의미하는 &lt;code&gt;void*&lt;/code&gt; 를 잘 사용하여 해제해야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;C 언어는 C++ 에 비해서도 OOP(객체 지향.) 적인 부분이 굉장히 부족하다.&lt;/p&gt;
&lt;p&gt;특정 데이터를 메인 로직에서 벗어난 다른 로직에서 관리하도록 설정 할 수는 있는데,&lt;/p&gt;
&lt;p&gt;결국 메모리 관리는 메인 로직에서 엄격하게 관리해야만 한다.&lt;/p&gt;
&lt;p&gt;이러한 문제를 풀기 위해 직접 tokenizer 메서드를 제작했다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;내가 사용하는 입출력 함수는?&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;stdio.h&lt;/code&gt; 라는 기본 라이브러리 함수는 다양한 입출력 함수들을 가지고 있다.&lt;/p&gt;
&lt;p&gt;우리가 사용하는 &lt;code&gt;scanf&lt;/code&gt;, &lt;code&gt;printf&lt;/code&gt; 는, 첫 번째 인자로 &lt;code&gt;%d&lt;/code&gt;, &lt;code&gt;%s&lt;/code&gt; 와 같은&lt;/p&gt;
&lt;p&gt;문자열 시그니쳐를 통해 메서드에게 현재 들어오는 문자열들에 대해 어떤 방식으로 파싱 할 것인지&lt;/p&gt;
&lt;p&gt;지침을 내려주는 역할을 해 준다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, 나는 입출력 함수로 &lt;code&gt;fgets&lt;/code&gt;, &lt;code&gt;fputs&lt;/code&gt;, &lt;code&gt;fputc&lt;/code&gt; 를 사용한다.&lt;/p&gt;
&lt;p&gt;이 메서드들은 이러한 인자가 들어간다. (알고리즘 문제 기준.)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;fgets&lt;/code&gt; : &lt;code&gt;char* 입력을 저장할 메모리&lt;/code&gt;, &lt;code&gt;몇 개의 문자를 저장할 것인지&lt;/code&gt;, &lt;code&gt;stdin&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;fputs&lt;/code&gt; : &lt;code&gt;char* 출력할 문자열 - 공백 및 개행문자 포함&lt;/code&gt;, &lt;code&gt;stdout&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;fputc&lt;/code&gt; : &lt;code&gt;char 출력할 문자 - 공백 및 개행문자 포함&lt;/code&gt;, &lt;code&gt;stdout&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;여기서 정말 중요한 점은, 입력과 출력 모두, 개행, 혹은 빈 칸을 포함한다는 것이다.&lt;/p&gt;
&lt;p&gt;특히, &lt;code&gt;fputs&lt;/code&gt; 는 내부에 전달된 문자열에서 &lt;code&gt;&amp;#39;\0&amp;#39;&lt;/code&gt; == &lt;code&gt;0&lt;/code&gt; 을 만날 때 까지 출력한다.&lt;/p&gt;
&lt;p&gt;따라서, 출력할 문자열들의 맨 마지막에는 항상 &lt;code&gt;&amp;#39;\0&amp;#39;&lt;/code&gt; 을 꼭 넣어주어야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;fgets, fputs, fputc 로 입출력을 해 보자.&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;fgetc&lt;/code&gt; 는 없냐고 할 수 있는데,&lt;/p&gt;
&lt;p&gt;있다!&lt;/p&gt;
&lt;p&gt;단순하게 필요가 없어서 사용하지는 않았는데,&lt;/p&gt;
&lt;p&gt;만약에 500만 개의 글자(공백개행 포함) 를 다뤄야 하는 경우,&lt;/p&gt;
&lt;p&gt;따로 메서드를 만들어 &lt;code&gt;fgetc&lt;/code&gt; 를 활용하는 예제를 만들면, 메모리를 굉장히 아낄 수 있을 것이다.&lt;/p&gt;
&lt;p&gt;(&lt;code&gt;char ch = fgetc(stdin)&lt;/code&gt; 이렇게 사용하면 됩니다)&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;fgets, fputs 사용해 보기&lt;/h3&gt;
&lt;p&gt;fgets 를 사용하는 것은 생각보다 단순하다.&lt;/p&gt;
&lt;p&gt;그러나, 꼭 기억해야 할 요소가 있다. 이는 바로 밑의 예제에서 볼 수 있을 것이다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include&amp;lt;stdio.h&amp;gt;

int main(void) {
    char input[255];

    fgets(input, sizeof(input), stdin);

    fputs(input, stdout);

    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  testDir gcc test.c -Wall -Wextra -pedantic -O2 -o test
➜  testDir ./test
입력 한 대로 동일하게 출력되는 것을 볼 수 있다.
입력 한 대로 동일하게 출력되는 것을 볼 수 있다.&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;여기서 눈여겨 봐야 할 요소는 무엇일까?&lt;/p&gt;
&lt;p&gt;이 입출력 메서드를 수백번을 사용하며 중요하게 생각하는 것들이 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;sizeof&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;문자열의 &lt;code&gt;&amp;#39;\0&amp;#39; == 0&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;왜 이 두 개가 중요할까?&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이를 다루기 전에, 동적 메모리 할당을 통한 &amp;quot;동일한 수행 코드&amp;quot; 를 보자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include&amp;lt;stdio.h&amp;gt;

extern void* malloc(size_t byte);
extern void free(void* memory);

int main(void) {
    char* input;

    int inputSize = 255; // or size_t inputSize = 255;

    input = (char*)malloc(sizeof(char) * (inputSize + 1));
    *(input + inputSize) = 0;
    // or input[inputSize] = 0;
    // or input[inputSize] = &amp;#39;\0&amp;#39;;

    fgets(input, inputSize, stdin);

    fputs(input, stdout);

    free(input);

    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  testDir gcc test.c -Wall -Wextra -pedantic -O2 -o test
➜  testDir ./test
abcdefg
abcdefg&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;무언가 조금 추가되었지만, 완벽하게 위, 아래 코드는 동일한 역할을 수행한다.&lt;/p&gt;
&lt;p&gt;그리고, 이 2 개의 방식에 대한 차이를 다 알고 있어야&lt;/p&gt;
&lt;p&gt;알고리즘에서 fgets 를 사용할 때 &amp;amp;&amp;amp; 하나의 입력 줄이 500만 문자일 때,&lt;/p&gt;
&lt;p&gt;대처할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그렇다면, 이 코드를 합쳐서 보자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include&amp;lt;stdio.h&amp;gt;

/** stdlib.h 라는 기본 내장 함수 중 일부를 추출하는 것.
extern void* malloc(size_t byte);
extern void free(void* memory);
*/

int main(void) {
    char input[255];
    /** 위의 코드는 밑과 동일하다.
    int inputSize = 255;

    input = (char*)malloc(sizeof(char) * (inputSize + 1));
    *(input + inputSize) = 0;
    */

    fgets(input, sizeof(input), stdin);
    // fgets(input, inputSize, stdin);

    fputs(input, stdout);
    // 동일.

    // free(input);
    // 동적 메모리는 항상 해제 해 줘야 한다.

    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;주석으로 처리 된 부분은 동적 메모리에 해당하는 코드로서,&lt;/p&gt;
&lt;p&gt;정적 메모리로 생성되었을 때와 아닐 때의 차이를 볼 수 있게 만들었다.&lt;/p&gt;
&lt;h3&gt;정적 메모리와 동적 메모리의 차이?&lt;/h3&gt;
&lt;p&gt;우선, 정적 메서드의 &lt;code&gt;sizeof&lt;/code&gt; 의 의미와, 동적 메서드의 &lt;code&gt;sizeof&lt;/code&gt; 의미는 다르다.&lt;/p&gt;
&lt;p&gt;정적 메서드의 경우, 선언된 배열의 길이 자체를 반환한다.&lt;/p&gt;
&lt;p&gt;정적 배열 --&amp;gt; &lt;code&gt;sizeof(input) == 255&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;그러나 동적 메서드의 경우, &lt;code&gt;sizeof(input)&lt;/code&gt; 을 다르게 인식한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;input&lt;/code&gt; 은 &lt;code&gt;char*&lt;/code&gt; 로서, 포인터로 선언되었다.&lt;/p&gt;
&lt;p&gt;따라서, 동적 배열 --&amp;gt; &lt;code&gt;sizeof(input) == sizeof(unsigned int) == 8?&lt;/code&gt; 와 동일하다.&lt;/p&gt;
&lt;p&gt;즉, 동적 배열을 그대로 &lt;code&gt;sizeof&lt;/code&gt; 해버리면, &amp;quot;주소값을 저장하는 타입의 바이트 크기&amp;quot; 로 인식한다.&lt;/p&gt;
&lt;p&gt;따라서, 정적 배열과 동적 배열의 입력 차이가 존재할 수 밖에 없다는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;코드가 조금 많아진다는 차이를 제외하고,&lt;/p&gt;
&lt;p&gt;동적 메모리 코드를 살펴보면, 내가 &lt;code&gt;*(input + inputSize) = 0;&lt;/code&gt; 처리 한 부분을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;그런데, 왜 &lt;code&gt;0&lt;/code&gt; 을 마지막에 넣어줄까??&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;정적 메모리의 경우, 내부를 &lt;code&gt;0&lt;/code&gt; 으로 채워준다.&lt;/p&gt;
&lt;p&gt;즉, 값을 사용하고 읽을 때, 나머지 부분이 &lt;code&gt;0&lt;/code&gt; 이기 때문에, 마지막을 쉽게 알 수 있다.&lt;/p&gt;
&lt;p&gt;그러나, 동적 할당, 특히 &lt;code&gt;malloc&lt;/code&gt; 의 경우, 메모리가 초기화 되지 않는다.&lt;/p&gt;
&lt;p&gt;쓰레기 값이 들어가 있는데, 이들은 &lt;code&gt;0&lt;/code&gt; 값이 아니다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;fputs&lt;/code&gt; 함수는 입력된 문자열에 대해서, &lt;code&gt;0&lt;/code&gt; 을 인지 할 때 까지 문자를 출력한다.&lt;/p&gt;
&lt;p&gt;정적 메서드의 경우 0 으로 초기화 되기 때문에 마지막이 보장되지만,&lt;/p&gt;
&lt;p&gt;동적 메서드의 경우 스스로 지정해 주지 않는 이상 마지막이 보장되지 않는다.&lt;/p&gt;
&lt;p&gt;따라서, 동적 메모리를 사용하기 위해서는 &amp;quot;마지막&amp;quot; 을 개발자가 기억해야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;즉, 시스템이 인지하는 끝, &lt;code&gt;0&lt;/code&gt; 이라는 데이터를 개발자가 직접 넣어줘야 한다.&lt;/p&gt;
&lt;p&gt;따라서, 나는 배열의 맨 마지막에 &lt;code&gt;&amp;#39;\0&amp;#39;&lt;/code&gt; 에 해당하는 문자인 &lt;code&gt;0&lt;/code&gt; 을 넣어 준 것이다.&lt;/p&gt;
&lt;p&gt;이것이 매우 번거롭다면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;extern void* calloc(size_t size, size_t byte)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;를 선언하여 사용하면 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;그러면, 동적 메모리는 fputs 를 통해 254 글자를 출력해야 하지 않나?&lt;/h3&gt;
&lt;p&gt;정적 메모리는 선언 시 입력된 크기 만큼 &lt;code&gt;0&lt;/code&gt; 으로 초기화되기 때문에,&lt;/p&gt;
&lt;p&gt;fgets 메서드를 통해 입력된 글자를 그대로 출력한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그런데, 방금 내가 만들어 둔 동적 메모리를 통해 입출력 또한 동일했다.&lt;/p&gt;
&lt;p&gt;어떻게 이게 가능했던 것일까? 쓰레기 값이 나와야 하지 않았을까?&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;비밀은 &lt;code&gt;fgets&lt;/code&gt; 의 처리에 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;scanf&lt;/code&gt; 와 달리, &lt;code&gt;fgets&lt;/code&gt; 는 빈칸, 개행문자도 들어온다.&lt;/p&gt;
&lt;p&gt;그런데, &lt;code&gt;fgets&lt;/code&gt; 는 입력된 &lt;code&gt;\n&lt;/code&gt; 에 대해, 그 다음 문자를 &lt;code&gt;0&lt;/code&gt; 으로 입력 해 준다.&lt;/p&gt;
&lt;p&gt;즉, 만약에 동적 메모리 &lt;code&gt;input&lt;/code&gt; 이 255 바이트로 할당되었다면,&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Index&lt;/th&gt;
&lt;th&gt;0&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;th&gt;5&lt;/th&gt;
&lt;th&gt;6&lt;/th&gt;
&lt;th&gt;...&lt;/th&gt;
&lt;th&gt;255&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;입력&lt;/td&gt;
&lt;td&gt;a&lt;/td&gt;
&lt;td&gt;b&lt;/td&gt;
&lt;td&gt;c&lt;/td&gt;
&lt;td&gt;d&lt;/td&gt;
&lt;td&gt;e&lt;/td&gt;
&lt;td&gt;&lt;code&gt;\n&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fgets&lt;/td&gt;
&lt;td&gt;a&lt;/td&gt;
&lt;td&gt;b&lt;/td&gt;
&lt;td&gt;c&lt;/td&gt;
&lt;td&gt;d&lt;/td&gt;
&lt;td&gt;e&lt;/td&gt;
&lt;td&gt;&lt;code&gt;\n&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;input&lt;/td&gt;
&lt;td&gt;a&lt;/td&gt;
&lt;td&gt;b&lt;/td&gt;
&lt;td&gt;c&lt;/td&gt;
&lt;td&gt;d&lt;/td&gt;
&lt;td&gt;e&lt;/td&gt;
&lt;td&gt;&lt;code&gt;\n&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;무의미 값들&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;code&gt;fgets&lt;/code&gt; 는 입력된 문자열에 대해 &amp;quot;개행&amp;quot; 을 발견하면, 이를 포함시키나, 그 다음 문자는 &lt;code&gt;0&lt;/code&gt; 으로 고정한다.&lt;/p&gt;
&lt;p&gt;이는 나중에 &amp;quot;정수 변환&amp;quot; 및 &amp;quot;토큰화&amp;quot; 에 굉장히 편리해진다.&lt;/p&gt;
&lt;p&gt;즉, 동적 메모리 또한 &lt;code&gt;fgets&lt;/code&gt; 덕분에 입력된 문자 마지막에 &lt;code&gt;0&lt;/code&gt; 을 넣어,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;fputs&lt;/code&gt; 메서드 실행 시, &lt;code&gt;0&lt;/code&gt; 을 정적 메서드와 동일하게 발견한다.&lt;/p&gt;
&lt;p&gt;따라서, 정적 메모리와 동적 메모리 둘 다 동일한 출력 결과가 나온 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;-&lt;strong&gt;그렇다면, 왜 마지막에 0 을 넣어줬을까?&lt;/strong&gt;-&lt;/p&gt;
&lt;p&gt;이는 일종의 보험과 같은 보호막으로 넣어준 것이다.&lt;/p&gt;
&lt;p&gt;문자 배열에서는 마지막에 &lt;code&gt;0&lt;/code&gt; 값을 넣어두는 것이 매우매우 중요하다.&lt;/p&gt;
&lt;p&gt;(그렇지 않으면 수많은 에러의 향연을 맛볼 수 있어요~)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;만약에 내가 C 에 존재하는 수많은 라이브러리를 사용하여 구현했다면,&lt;/p&gt;
&lt;p&gt;&amp;quot;이렇게까지&amp;quot; 중요하게 생각하지는 않을 것 같다.&lt;/p&gt;
&lt;p&gt;그러나, 나는 &lt;code&gt;stdio.h&lt;/code&gt; 라는 기본 입출력 보조 메서드 외,&lt;/p&gt;
&lt;p&gt;나머지 유틸리티 함수는 &amp;quot;전부&amp;quot; 직접 구현하고 있다. &lt;strong&gt;(메모리 할당 및 해제 제외)&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이러한 방식으로 입출력을 진행 할 수 있으나, 너무나도 중요한 것이 빠졌다.&lt;/p&gt;
&lt;p&gt;바로,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;문자열 정수 변환&lt;/li&gt;
&lt;li&gt;정수를 문자열로 변환&lt;/li&gt;
&lt;li&gt;토큰화&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;p&gt;이에 대해서는 앞으로 작성할 블로그 글에 나온다.&lt;/p&gt;
&lt;p&gt;하나 하나가 내가 사용하는 메모리 방식과 연계되어 있으며,&lt;/p&gt;
&lt;p&gt;어찌 보면 내가 무의식적으로 만들어 낸 일종의 컨벤션이 추가되어 있을 수도 있기 때문이다.&lt;/p&gt;
&lt;p&gt;따라서, 나는 &amp;quot;이것이 정답이다!&amp;quot; 가 아니라, 이러한 방식으로 코드를 바라볼 수가 있구나,&lt;/p&gt;
&lt;p&gt;하는 시각으로 글을 작성하고 싶다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;이 글을 작성하며 느낀 점&lt;/h2&gt;
&lt;p&gt;C 언어는 에러에 매우 취약하며, 하드웨어적 요소, 즉, Memory 와 매우 깊게 연관되어 있다.&lt;/p&gt;
&lt;p&gt;또한 GC(Garbage Collector or Collection) 가 없으므로,&lt;/p&gt;
&lt;p&gt;모든 동적 메모리를 &amp;quot;직접&amp;quot; 해제해 줘야 하는 기능을 추가로 작성해야 한다.&lt;/p&gt;
&lt;p&gt;하지만, 나는 오히려 즐겁게 알고리즘을 풀고 있다.&lt;/p&gt;
&lt;p&gt;이유는, 내가 사용했던 모든 유틸리티 라이브러리의 소중함을 깨닫는 것을 넘어,&lt;/p&gt;
&lt;p&gt;내가 직접 최적화 할 수 있다는 자신감이 생기기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;특히, 나는 이러한 C 언어에 대한 설명 글을 만들기 위해 굉장히 고심했다.&lt;/p&gt;
&lt;p&gt;사유는 바로 적당한 수준의 전문성을 갖춰야 한다는 생각이 깊게 들었기 때문이다.&lt;/p&gt;
&lt;p&gt;&amp;quot;틀릴 수도 있는&amp;quot; 정보를 전달하면 누군가는 이를 보고 치명적인 에러에 맞닥들일 수 있다.&lt;/p&gt;
&lt;p&gt;이게 내가 가진 책임감이었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;앞으로 작성 할 글에,&lt;/p&gt;
&lt;p&gt;문자열 정수 변환, 정수를 문자열로 변환, 입력 문자열에 대한 토큰화&lt;/p&gt;
&lt;p&gt;이러한 세 가지 주제를 다루고 심화로 넘어 갈 건데,&lt;/p&gt;
&lt;p&gt;비교적 쉬운 &amp;quot;문자열 정수 변환&amp;quot; 그리고 &amp;quot;정수 문자열 변환&amp;quot; 을 다루어야&lt;/p&gt;
&lt;p&gt;&amp;quot;입력 문자열에 대한 토큰화&amp;quot; 를 더 잘 이해 할 수 있을 거라는 생각이 들었다.&lt;/p&gt;
&lt;p&gt;따라서,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;문자열 정수 상호변환&lt;/li&gt;
&lt;li&gt;토크나이저&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;순으로 글을 작성 할 것이다.&lt;/p&gt;
&lt;br/&gt;</description>
      <category>Algorithm</category>
      <category>C io</category>
      <category>c 언어</category>
      <category>C 입출력</category>
      <category>fgets</category>
      <category>fputs</category>
      <category>Free</category>
      <category>malloc</category>
      <category>printf</category>
      <category>scanf</category>
      <category>백준 C</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/231</guid>
      <comments>https://codecreature.tistory.com/231#entry231comment</comments>
      <pubDate>Fri, 1 Aug 2025 04:01:44 +0900</pubDate>
    </item>
    <item>
      <title>C 언어와 stdio.h 라이브러리만으로 백준 풀며 포텐셜 올리기</title>
      <link>https://codecreature.tistory.com/230</link>
      <description>&lt;h2&gt;제목 : stdio.h 만 가지고 백준 문제 풀어본 결과&lt;/h2&gt;
&lt;hr&gt;
&lt;h2&gt;코드 결과를 빠르게 보고 싶다면, 밑의 텍스트를 클릭하세요&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;#code-exam&quot;&gt;코드로 바로 이동하기&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;다양한 라이브러리가 존재하는데, 굳이 힘들게 푸는 이유는?&lt;/h3&gt;
&lt;p&gt;이러한 힘든 도전을 하는 이유는, &lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/220&quot;&gt;&amp;quot;C 와 최소한의 Lib 로 알고리즘 풀어보기 도전&amp;quot; 썼던 글&lt;/a&gt; 에 상세히 적어놓았다.&lt;/p&gt;
&lt;p&gt;요약하자면, Node.js 엔진 기반의 JS 의 성능적 한계점을 느끼고, 최적화를 수행하고 구현하면서,&lt;/p&gt;
&lt;p&gt;최적화 과정이 저 수준의 언어로 제작하는 것 보다 훨씬 더 어렵다는 것을 느꼈다.&lt;/p&gt;
&lt;p&gt;이럴 거면 애초에 최적화 타겟 언어로 프로그램을 제작하는 것이 더 낫다는 판단을 했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그리고, 특히 특정 도메인의 프로그램을 제작하기 위해 다양한 라이브러리의 역할도 알지 못한 채,&lt;/p&gt;
&lt;p&gt;쉽게 가져와서 사용하기만 해버리는 단순한 방법론적 프로그래밍에 회의감이 들었다.&lt;/p&gt;
&lt;p&gt;특히, 특정 프레임워크를 선택하고 공부 할 때,&lt;/p&gt;
&lt;p&gt;이것을 구현하기 위해, 저 메서드, 클래스를 간단히 가져와서 쓴다, 라는 관점으로 볼 때는&lt;/p&gt;
&lt;p&gt;빠른 구현을 위해 프레임워크를 도입하는 것이 좋다고 생각한다.&lt;/p&gt;
&lt;p&gt;그러나, 프레임워크 자체가 하나의 또다른 언어 수준으로 공부가 필요 해 지는 시기가 온다.&lt;/p&gt;
&lt;p&gt;그 이유는, 프레임워크에서 가져온 변수나 메서드들이 어떻게 작동하는지 모르고, 이를 외우기 때문이라고 생각한다.&lt;/p&gt;
&lt;p&gt;처음에는 나도 공식문서를 따라서 구현하며 만든 컴포넌트, 혹은 WAS(백엔드) 프로그램이 간단히 제작되는 것을 보고 좋아했는데,&lt;/p&gt;
&lt;p&gt;구조적으로 개편하거나, 최적화를 진행하는 순간, 나는 알려주는 것 말고 배운 것 말고 아무것도 아는 것이 없었다는 것을 알게 된다.&lt;/p&gt;
&lt;p&gt;이러한 회의감은 React, NestJS, Express, TypeORM, Spring, Next.js 등등 수많은 공식문서를 배우며 느끼게 되었다.&lt;/p&gt;
&lt;p&gt;결국 나의 학습 방법이 틀렸다는 것을 인지하고, 모르는 것이 생겼을 때,&lt;/p&gt;
&lt;p&gt;생기는 의문이 미래의 학습에 얼마나 도움이 되는지를 생각한다.&lt;/p&gt;
&lt;p&gt;예를 들어, 현재 내가 가상화 컨테이너에 대해 알고 싶어 조사하며 글을 작성하고 싶다고 한다면,&lt;/p&gt;
&lt;p&gt;이는 작성할 수 있다. 실질적인 온라인 배포 및 클라이언트 유치를 위해 꼭 필요한 과정이기 때문이다.&lt;/p&gt;
&lt;p&gt;그러나, K8S(Kubernetes) 가 뭔지 궁금하다고 지금 당장 공부하지 않을 것이다.&lt;/p&gt;
&lt;p&gt;첫 번째로 지금 공부하고 있는 도메인과 조금 떨어져 있을 뿐 더러,&lt;/p&gt;
&lt;p&gt;아직 컨테이너 오케스트레이션과, 도커, 가상화 기술에 대해 제대로 알고 있지 않기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이러한 성찰을 바탕으로, &amp;quot;나는 어떻게 알고리즘을 공부해야 하는가?&amp;quot; 에 대해 혼란스러워 했다.&lt;/p&gt;
&lt;p&gt;언뜻 나는 백엔드에 가까우니 Spring 을 할 것 같으니, 알고리즘을 Java 로 공부했다.&lt;/p&gt;
&lt;p&gt;그러나, 지금 나의 행보는 Spring 에 가깝지 않으며, 오히려 웹 서버 개발과,&lt;/p&gt;
&lt;p&gt;그에 반대되는 저 수준의 언어를 이용하여 직접 메모리 관리하는 것에 좀 더 가까웠다.&lt;/p&gt;
&lt;p&gt;EX - 다양한 정렬 알고리즘 직접 작성, 집합, 맵 등 여러 자료구조 작성.&lt;/p&gt;
&lt;p&gt;따라서, 역사가 매우 깊으며, 대부분의 언어가 형식을 참조하거나, 이를 아직도 활용중인&lt;/p&gt;
&lt;p&gt;C 언어를 사용하여 알고리즘을 풀기로 했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이는 내가 미래에 진행하게 될 프로그램에 대한 여러 전략을 생각한 것인데,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;웹 개발 공부를 진행하며, 웹 모듈(&lt;code&gt;.wasm&lt;/code&gt;) 으로 최적화 전략을 생각할 수 있음&lt;/li&gt;
&lt;li&gt;필요 할 시, 범용성 높은 &lt;code&gt;c++&lt;/code&gt; 로 전환이 가능함.&lt;/li&gt;
&lt;li&gt;GC 탑재 및 쉬운 C 버전인 &lt;code&gt;go&lt;/code&gt; 언어로 전환이 가능함.&lt;/li&gt;
&lt;li&gt;메모리 직접 관리로 GC 의 중요성 깨닫기&lt;/li&gt;
&lt;li&gt;클래스가 존재하지 않으므로, &lt;code&gt;struct&lt;/code&gt; (구조) 를 이용한 추상화 및 구현 전략 채택하여 &lt;code&gt;Golang&lt;/code&gt; 고려&lt;/li&gt;
&lt;li&gt;현재 사용중인 에디터가 &lt;strong&gt;Zed&lt;/strong&gt; 라는 Rust 로 제작된 에디터인데, 부족한 마크다운 프리뷰를 고치고 싶음&lt;ul&gt;
&lt;li&gt;Rust 의 메모리 빌림을 더 정확하게 이해하기 위해서 메모리 관리를 직접 해 보는 것이 중요하다 생각(개인적)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;포인터에 대한 직감 발달을 위함&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;h2&gt;Java 입출력 전략을 C 로 가져오기&lt;/h2&gt;
&lt;h3&gt;입력 문자열에 대한 토큰화 진행하기&lt;/h3&gt;
&lt;p&gt;Java 에는 입력 문자열에 대해 토큰화를 수행하는 &lt;code&gt;StringTokenizer&lt;/code&gt; 라는 클래스가 존재한다.&lt;/p&gt;
&lt;p&gt;생성자와 함께 인수로 &lt;strong&gt;토큰화를 수행 할 문자열&lt;/strong&gt; 을 넣어주면,&lt;/p&gt;
&lt;p&gt;생성된 객체를 통해 토큰을 하나씩 꺼내 사용 할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, C 에서는 &lt;code&gt;scanf(&amp;quot;%d&amp;quot;, &amp;amp;받을 숫자)&lt;/code&gt; 와 같은 메서드를 통해 하나씩 꺼내 사용하고 있었다.&lt;/p&gt;
&lt;p&gt;나중에 입력의 토큰 수가 동적으로 변할 문제들을 고려하여, &lt;code&gt;tokenizer&lt;/code&gt; 라는 나만의 메서드를 만들었다.&lt;/p&gt;
&lt;p&gt;입력된 문자 배열 &lt;code&gt;char*&lt;/code&gt; 를 &lt;code&gt;toeknizer&lt;/code&gt; 에 보내주면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;char**&lt;/code&gt; 형태로 반환되는데, 이는 &lt;strong&gt;&amp;quot;각 문자 배열의 시작 주소를 가진&amp;quot;&lt;/strong&gt; , &lt;strong&gt;&amp;quot;주소 배열&amp;quot;&lt;/strong&gt; 이다.&lt;/p&gt;
&lt;p&gt;마지막 주소에는 NULL 이 들어가 있기 때문에, 확실하게 끝을 인지할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;숫자 문자열을 정수로 변환하기&lt;/h3&gt;
&lt;p&gt;보통 &lt;code&gt;scanf(&amp;quot;%d&amp;quot;, ...)&lt;/code&gt; 를 통해서 이미 정수 변수로 할당 받으면 상관하지 않아도 될 부분이기도 하다.&lt;/p&gt;
&lt;p&gt;그러나, 나는 이 부분도 &lt;code&gt;parseInt&lt;/code&gt; 라는 메서드로 제작했다.&lt;/p&gt;
&lt;p&gt;먼저, 간단하게 수 &lt;code&gt;0&lt;/code&gt; 을 가진 변수를 만들어 놓고,&lt;/p&gt;
&lt;p&gt;숫자 문자 배열의 마지막이 올 때 까지, 변수를 x10 하고, 현재 숫자 문자를 실제 수로 변환하여 더한다.&lt;/p&gt;
&lt;p&gt;이에 대한 예시는 맨 밑의 코드를 참조하길 바란다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;숫자 문자를 수로 변환하기&lt;/h3&gt;
&lt;p&gt;이건 매우 쉬운데, 먼저 알아야 할 것은, 문자 배열의 순서는 &lt;code&gt;&amp;#39;0&amp;#39;&lt;/code&gt; --&amp;gt; &lt;code&gt;&amp;#39;9&amp;#39;&lt;/code&gt; 순으로 간다는 것이다.&lt;/p&gt;
&lt;p&gt;따라서, &lt;code&gt;char - &amp;#39;0&amp;#39;&lt;/code&gt; 으로 계산하면, 바로 현재 수 문자의 정수가 추출된다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;빈 공간을 감지하기&lt;/h3&gt;
&lt;p&gt;위에서 &lt;code&gt;tokenizer&lt;/code&gt; 라는 토큰 문자열 주소 배열을 반환하기 위해서 사용되는 기능인데,&lt;/p&gt;
&lt;p&gt;빈 공간을 감지한다면, 이는 넘거거나, 이제 토큰화를 시작해야 한다는 의미이다.&lt;/p&gt;
&lt;p&gt;이 때, 물론 C 라이브러리로 해결 할 수 있지만, 나는 조금 간단히 생각했다.&lt;/p&gt;
&lt;p&gt;char 값으로 &lt;code&gt;32&lt;/code&gt; = &lt;code&gt;&amp;#39; &amp;#39;&lt;/code&gt;(스페이스) 이며, &lt;code&gt;9&lt;/code&gt; ~ &lt;code&gt;13&lt;/code&gt; 까지가 줄넘김, 탭 등등을 의미한다.&lt;/p&gt;
&lt;p&gt;이러한 값을 인지하는 &lt;code&gt;isBlank&lt;/code&gt; 함수를 만들었다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Java 에서 C 를 사용하니 느껴지는 제약들&lt;/h2&gt;
&lt;h3&gt;메모리를 직접 관리해야 한다.&lt;/h3&gt;
&lt;p&gt;Java 에서는, JVM 에서 Garbage Collector(쓰레기 수집기) 와 상호작용하면서,&lt;/p&gt;
&lt;p&gt;곧 폐기처분 될 확률이 높은 Eden 영역, 오래 살아남는 메모리 영역 Old 영역을 분리하여 관리한다.&lt;/p&gt;
&lt;p&gt;그러나, C 에서는 그런거 없다. &lt;code&gt;calloc&lt;/code&gt;, &lt;code&gt;malloc&lt;/code&gt;, 혹은 &lt;code&gt;realloc&lt;/code&gt; 으로 동적 메모리 확장한&lt;/p&gt;
&lt;p&gt;모든 메모리들은 &lt;code&gt;stdlib.c&lt;/code&gt; 헤더의 &lt;code&gt;free&lt;/code&gt; 메서드를 이용하여 직접 메모리 해제를 해 주어야 한다.&lt;/p&gt;
&lt;p&gt;직접 문제를 풀기 위해 다양한 기능을 만들면서 느꼈던 것인데, 요즘 왜 &lt;code&gt;Zig&lt;/code&gt; 나 &lt;code&gt;Rust&lt;/code&gt; 가 뜨는지 이해가 되었다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;둘 다 메모리 안정성을 기반에 두고 만든 저 수준의 언어&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;br/&gt;

&lt;h3&gt;클래스가 없다.&lt;/h3&gt;
&lt;p&gt;내가 이전에 Java 로 백준을 풀 때는, 특정 도메인의 유틸 기능들을 모아놓은 클래스를 직접 제작하여 문제를 해결했다.&lt;/p&gt;
&lt;p&gt;HashMap, HashSet, Tree, Queue, Stack 등등을 직접 구현하며 자료구조에 대한 이해도를 높였다.&lt;/p&gt;
&lt;p&gt;그러나, C 에서는 C++ 과 달리, 클래스가 존재하지 않는다. 대신, &lt;code&gt;struct&lt;/code&gt; 가 존재한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;예를 들어, 우리가 특정 클래스를 만든다면, 클래스가 가질 고유의 &amp;quot;변수&amp;quot; 와 &amp;quot;객체&amp;quot; 를 선언하고,&lt;/p&gt;
&lt;p&gt;이 클래스를 위해 사용될 메서드를 내부에서 직접 작성했다.&lt;/p&gt;
&lt;p&gt;그러나, &lt;code&gt;struct&lt;/code&gt; 는, 구조 가 가질 고유의 &amp;quot;변수&amp;quot; 와 &amp;quot;객체&amp;quot; 까지는 선언이 가능하나,&lt;/p&gt;
&lt;p&gt;생성자와 소멸자를 외부에서 만들어야 하며, 이 구조(struct) 는 메서드를 추상화로 선언한다.&lt;/p&gt;
&lt;p&gt;그리고, 외부에서 추상화 된 메서드를 직접 구현하는 형식이다.&lt;/p&gt;
&lt;p&gt;이에 대해 굉장히 잘 작성 해 놓은 분이 계셔서, 글을 공유한다.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://blog.naver.com/ruvendix/220980152324&quot;&gt;https://blog.naver.com/ruvendix/220980152324&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;포인터와 메모리 주소에 대한 개념을 숙지하고 있어야 한다.&lt;/h3&gt;
&lt;p&gt;Java 에서는 배열과 객체에 대한 정보를 JVM 이 관리하여,&lt;/p&gt;
&lt;p&gt;우리가 직접 시스템 수준의 메모리를 건드리지 않고, JVM 이 생성 및 소멸시켜 관리했다.&lt;/p&gt;
&lt;p&gt;이 과정에서, JVM 은 객체와 배열의 정보를 내부에 메타데이터로 저장했다.&lt;/p&gt;
&lt;p&gt;역시나, C 는 그런것이 없다.&lt;/p&gt;
&lt;p&gt;따라서, 우리는 객체를 생성하거나, 배열을 생성 할 때, 이 객체 혹은 배열에 대한 사이즈를 명확히 작성해야 한다.&lt;/p&gt;
&lt;p&gt;그런데 이건, 쉽게 &lt;code&gt;sizeof(&amp;lt;원시데이터&amp;gt;)&lt;/code&gt;, &lt;code&gt;sizeof(&amp;lt;struct 이름&amp;gt;)&lt;/code&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;주소의 끝을 인식할 다양한 방법을 알고 있어야 한다.&lt;/h3&gt;
&lt;p&gt;내가 백준 문제를 풀면서, 문자 배열을 순회할 때, &lt;code&gt;for&lt;/code&gt; 을 거의 사용하지 않고, 대부분 &lt;code&gt;while&lt;/code&gt; 로 작성했다.&lt;/p&gt;
&lt;p&gt;이는 문자 (&lt;code&gt;&amp;#39;\0&amp;#39;&lt;/code&gt;) 가 문자 배열의 &amp;quot;끝&amp;quot; 을 알리기 때문이다.&lt;/p&gt;
&lt;p&gt;그리고, 객체 혹은 배열의 시작 주소를 가진 포인터 배열은 &lt;code&gt;NULL&lt;/code&gt; 로 표현이 가능하다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, 만약에 수 배열이 온다면, 이야기가 달라진다.&lt;/p&gt;
&lt;p&gt;숫자 배열은 END 값을 &lt;code&gt;0&lt;/code&gt; 으로 가진다. 따라서, 우리는 길이에 대한 메타데이터를 직접 생성하여 관리해야 한다.&lt;/p&gt;
&lt;p&gt;이 경우, &lt;code&gt;for&lt;/code&gt; 문을 사용하여 해결했다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;모든 함수와 객체, 변수에 대한 정보는 추상화 시켜 미리 작성해야 한다.&lt;/h3&gt;
&lt;p&gt;이것도 C, C++ 이 서로 다른 경향인데,&lt;/p&gt;
&lt;p&gt;C 는 위쪽에서 작성된 코드가 아래의 구조 혹은 메서드를 호출 할 때, 무엇인지 알지 못한다.&lt;/p&gt;
&lt;p&gt;이는 컴파일러가 위에서부터 아래로 읽기 때문이라서, 파일의 맨 위 쪽에 미리 추상화를 시켜놓아야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이러한 제약은 생각 외로, 이 문제를 풀기 위해 어떤 기능이 필요할지 세밀하게 생각하게 해 주는 계기가 되었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;span id=&quot;code-exam&quot;&gt;C 로 푼 문제 :&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.acmicpc.net/problem/1008&quot;&gt;https://www.acmicpc.net/problem/1008&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;깃허브 코드 주소&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;_blank&quot; href=&quot;https://github.com/damhyeong/Algorithm-Exam/blob/main/%EB%B0%B1%EC%A4%80/Bronze/1008.%E2%80%85A%EF%BC%8FB/A%EF%BC%8FB.c&quot;&gt;작성한 정답 코드 예시 - 깃허브 새창&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;코드의 구조는 대략적으로 이렇게 표현된다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include&amp;lt;stdio.h&amp;gt;

extern ... // malloc, free, realloc, calloc 등등 가져오기

int parseInt(const char* str);
_Bool isBlank(ch);
// ... 등등 추상화 메서드 선언

int main(void) {

    // 메인 로직 수행
    //
    // 위에 선언한 추상화 함수를 참조하여 로직을 미리 구성할 수 있다.
    //
    // 동적 메모리 풀어주기

    return 0;
}

// ... 추상화 메서드들 작성.
int parseInt(const char* str) {
    // ... 문자열 --&amp;gt; 정수 과정
    return result * sign;
}
_Bool isBlank(char ch) {
    // 현재 문자가 &amp;quot;빈 칸&amp;quot; 을 의미하는 &amp;quot;문자들&amp;quot; 인지 아닌지.
    return 1 or 0
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;간단한 파싱 함수들과 유틸리티를 직접 작성한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;수십개의 문제를 풀고 나서 느껴지는 현재까지의 문제점.&lt;/h2&gt;
&lt;br/&gt;

&lt;h3&gt;1. 실수가 어디서 발생했는지 정말 알기 어렵다.&lt;/h3&gt;
&lt;p&gt;백준의 알고리즘 문제는 전부 &amp;quot;문자열&amp;quot; 로 입력되기 때문에,&lt;/p&gt;
&lt;p&gt;이를 파싱하거나 나누는 과정에서 같은 유틸리티 함수를 기입한다.&lt;/p&gt;
&lt;p&gt;그러나, 내가 사람이기 때문에 가끔 메모리 접근 과정에서 실수를 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;예를 들어, &lt;strong&gt;문자열을 들어 보자.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;나는 문자열을 관리 할 때 주로 &amp;quot;포인터(&lt;code&gt;*&lt;/code&gt;)&amp;quot; 를 가장 많이 애용한다.&lt;/p&gt;
&lt;p&gt;직관적이기도 하고, &lt;code&gt;&amp;#39;\0&amp;#39;&lt;/code&gt; == 0 값을 만난다면 자동으로 끝나는 &lt;code&gt;while&lt;/code&gt; 문법을&lt;/p&gt;
&lt;p&gt;쉽게 작성할 수 있기 때문이다.&lt;/p&gt;
&lt;p&gt;즉, 길이를 알지 않아도 Iterater 역할을 해 주는 포인터가 루프문을 끝낸다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;하지만 가끔, 새로운 동적 문자열을 생성하고 나서, 맨 마지막 부분에 &lt;code&gt;0&lt;/code&gt; 값을 넣어주지 않았다.&lt;/p&gt;
&lt;p&gt;그렇다면, 결과 출력 과정에서 Segmentation Fault 에러가 난다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;내가 인덱스 참조를 잘못 했는지, 혹은 이미 free 된 메모리에 접근했는지&lt;/p&gt;
&lt;p&gt;전부 살펴봐야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;2. 코드 이해력은 높아지지만, 알고리즘 시험에는 절대 적합하지 않다.&lt;/h3&gt;
&lt;p&gt;위와 같은 메모리 접근 에러가 가장 많이 일어나고,&lt;/p&gt;
&lt;p&gt;이 때문에 유틸리티 함수를 작성하면서도 뇌는 백그라운드로 돌아가며 체킹한다.&lt;/p&gt;
&lt;p&gt;덕분에 코드 가독성은 매우 높아지지만, 이러한 방식은 알고리즘 시험에 적합하지 않다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;만약에, 시험 문제로 &lt;code&gt;HashSet&lt;/code&gt;, &lt;code&gt;HashMap&lt;/code&gt; 을 사용해야 하는 상황이 있다고 가정해 보자.&lt;/p&gt;
&lt;p&gt;나는 만약에 이러한 문제가 나온다면 Heap Sort 와 Binary Search 를 작성하여 풀게 된다.&lt;/p&gt;
&lt;p&gt;그러나, 누군가는 바로 Standard Library 에서 기능들을 가져와 단숨에 풀 것이다.&lt;/p&gt;
&lt;p&gt;기존에 존재하는 유틸리티를 가져오는 데는 1 줄이지만,&lt;/p&gt;
&lt;p&gt;그 기능을 구현하기 위해서는 150 줄 정도를 직접 작성해야 하는 것이다. (최소)&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;3. 다차원 배열의 생성, 삭제, 관리&lt;/h3&gt;
&lt;p&gt;이는 &lt;code&gt;void*&lt;/code&gt; 라는 타입으로 쉽게 유틸리티 함수를 생성할 수 있다.&lt;/p&gt;
&lt;p&gt;또한, 삭제하는 과정도 쉽게 따로 메서드를 작성할 수 있다.&lt;/p&gt;
&lt;p&gt;이는 &lt;code&gt;malloc&lt;/code&gt; 과 같은 동적 배열 생성 메서드의 기능으로서,&lt;/p&gt;
&lt;p&gt;우리가 처음 접근하게 되는 인덱스 0 주소 앞에, 이 배열에 대한 메타데이터가 저장된다.&lt;/p&gt;
&lt;p&gt;그러나, 우리가 접근 하면 헤더 무결성이 깨질 가능성이 존재하며,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;free&lt;/code&gt; 가 이 데이터를 읽어 우리가 생성한 동적 데이터를 자유롭게 삭제할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;하지만, 다차원 배열의 주소값 관리와 접근은 일반 개발자 입장에서 어려울 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;예를 들어, 고차원 프로그래밍 방식으로 &lt;code&gt;arr[2][3]&lt;/code&gt; 형식을 가진 정수 배열이 존재한다고 가정하자.&lt;/p&gt;
&lt;p&gt;그렇다면, 나는 C 언어로 2 행, 2 열의 데이터를 가져오려고 한다면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;*(*(arr + 1) + 1)&lt;/code&gt; 이 된다. (인덱스 1 은 &amp;quot;2번째&amp;quot; 가르킴을 상정.)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;물론, &lt;code&gt;arr[1][1]&lt;/code&gt; 방식도 가능하다. 그러나, 포인터에 대한 추상적인 직감 및 이해도를 높이기 위해&lt;/p&gt;
&lt;p&gt;선택한 방식이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;위의 &lt;code&gt;*(*(arr + 1) + 1)&lt;/code&gt; 는 무엇을 의미할까?&lt;/p&gt;
&lt;p&gt;먼저, &lt;code&gt;arr&lt;/code&gt; 는 1차원 배열 시작 주소를 가지고 있는 배열이다.&lt;/p&gt;
&lt;p&gt;그리고 이 1차원 배열 시작 주소를 참조 할 수 있어야, 그 배열 내부의 &amp;quot;값&amp;quot; 을 관리할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;조금 보기 편하게 만들어 보자면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;*(*(arr + row) + col)&lt;/code&gt; 이라고 볼 수 있다. &lt;code&gt;== arr[row][col]&lt;/code&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;포인터적인 관점에서 보자면, 다차원 배열의 관리는 타 언어들보다 어려운 편이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;4. 배열 크기 데이터는 나만 알고 있다.&lt;/h3&gt;
&lt;p&gt;물론 &lt;code&gt;malloc&lt;/code&gt; 으로 인해 동적 배열 생성 시, 헤더 메타데이터로 바이트를 남기긴 하지만,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;free&lt;/code&gt; 메서드를 위한 거지, 개발자를 위하진 않는다.&lt;/p&gt;
&lt;p&gt;물론, 이렇게 동적으로 생성된 배열의 크기를 알 수 있는 라이브러리 메서드가 존재한다.&lt;/p&gt;
&lt;p&gt;그러나, 정적 배열과 동적 배열의 크기를 알아내는 방식이 다르기 때문에,&lt;/p&gt;
&lt;p&gt;따로 길이를 메인 콜백에서 관리하는 것이다.&lt;/p&gt;
&lt;p&gt;(물론, 문자열의 경우 &lt;code&gt;&amp;#39;\0&amp;#39;&lt;/code&gt; 값을 통해 끝을 알릴 수 있다.)&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;5. 서로를 참조하는 코드 구조가 형성 될 경우, 메모리 해제 선언이 번거롭다.&lt;/h3&gt;
&lt;p&gt;쉬운 코드를 작성 할 때는 몰랐던 부분이었는데,&lt;/p&gt;
&lt;p&gt;실제로 자료구조 타입을 만들고 엮는 과정에서,&lt;/p&gt;
&lt;p&gt;이들을 말끔하게 메모리 해제하는 것이 문제 해결 자체보다 &amp;quot;더 어렵다&amp;quot; 는 생각이 자주 들었다.&lt;/p&gt;
&lt;p&gt;예를 들어, HashMap 을 만들었다고 가정 한다면,&lt;/p&gt;
&lt;p&gt;이들을 전부 해제하기 위해 BFS 혹은 DFS 방식으로 하나씩 참조 해 나가며 해제해야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;결국 HashMap 문제가 실버 5 수준이라면, 거의 골드 4 수준으로 난이도가 올라간다..&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;이러한 문제에도 불구하고 알고리즘 풀이를 진행하는 이유는,&lt;/h2&gt;
&lt;br/&gt;

&lt;h3&gt;1. AI 에게 쉽게 대체 될 수 없다.&lt;/h3&gt;
&lt;p&gt;정말 중요한 요소인데, 저수준의 프로그래밍 언어는 AI 가 절대로 선호하지 않는다.&lt;/p&gt;
&lt;p&gt;GPT 가 막 유명해졌을 초창기부터 현재까지 20 달러를 결제하여 여태까지 사용하고 있는데,&lt;/p&gt;
&lt;p&gt;AI 는 저수준 언어를 선호하지 않는다.&lt;/p&gt;
&lt;p&gt;정확히는, 인간의 언어와 비슷한 고차원 언어(EX - JavaScript, TypeScript, Python)&lt;/p&gt;
&lt;p&gt;이러한 언어들을 매우 선호한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;왜 그럴까 생각 해 보면, 이러한 언어들은 컴퓨터 하드웨어적 부분에서 크게 조심해야 할 부분이 없다.&lt;/p&gt;
&lt;p&gt;즉, AI 입장에서 하드웨어를 딱히 더 신경써서 틀린 답을 내기 어렵다는 의미이다.&lt;/p&gt;
&lt;p&gt;하지만, 저수준 언어 + 나만의 커스텀 자료구조 가 형성되면, 이놈도 신기하게 틀린 답을 낸다.&lt;/p&gt;
&lt;p&gt;(현재 GPT-o3)&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;2. 상황에 따라 다른 언어를 쉽게 익힐 수 있다.&lt;/h3&gt;
&lt;p&gt;거의 대부분의 언어의 패러다임이 C, C++ 에서 시작했다고 말할 수 있을 만큼,&lt;/p&gt;
&lt;p&gt;프로그래밍 역사로 보면 C 와 C++ 은 너무나도 오래 생존하고 현재도 활발히 사용중인 언어이다.&lt;/p&gt;
&lt;p&gt;현재도 이러한 언어들을 대체하겠다는 시도들이 많은데, 대부분이 사장되고 잊혀진다.&lt;/p&gt;
&lt;p&gt;결국 저수준 언어에서 살아남는 것은 C, C++ 였다.&lt;/p&gt;
&lt;p&gt;(물론, Zig 나 Rust 와 같은 메모리 안전 언어로 대체하려는 시도가 보이긴 한다.)&lt;/p&gt;
&lt;p&gt;(예를 들어, AWS 의 일부 기능이 Rust 로 이전한 것. 등등)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;어셈블리와 같은 &amp;quot;하드웨어 수준 언어&amp;quot; 를 제외하면, 이것보다 어려운 언어가 존재할까 의문이 든다.&lt;/p&gt;
&lt;p&gt;따라서, 요즘 유행중인 &amp;quot;쉬운 C&amp;quot; 인 &lt;code&gt;go&lt;/code&gt; 를 쉽게 배울 수 있으며,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;C&lt;/code&gt; 를 대체하고자 노력하는 &lt;code&gt;Zig&lt;/code&gt; 를 배울 수도 있고,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;C++&lt;/code&gt; 을 대체하고자 하는 &lt;code&gt;Rust&lt;/code&gt; 를 배울 수도 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;3. 고 수준의 프로그래밍 언어의 최적화를 구성 할 수 있다.&lt;/h3&gt;
&lt;p&gt;아무리 JavaScript 와 Python 의 최적화가 많이 진행되어 빠르다고는 하지만,&lt;/p&gt;
&lt;p&gt;인터프리터 언어의 특성상 저수준 언어의 모듈이 부착되어 최적화가 진행되어야 하는 부분이 많다.&lt;/p&gt;
&lt;p&gt;당연히 JavaScript 의 기본 모듈들은 저수준 언어들의 모듈을 적극적으로 사용하여 최적화한다.&lt;/p&gt;
&lt;p&gt;저 수준의 프로그래밍을 진행하다 보니,&lt;/p&gt;
&lt;p&gt;프로그래머스 풀스택 과정(JS &amp;amp; TS) 을 돌이켜보며,&lt;/p&gt;
&lt;p&gt;C or C++ 와 같은 저수준 언어를 따로 &lt;code&gt;.wasm&lt;/code&gt; 으로 부착하여 최적화 할 수 있는 부분이&lt;/p&gt;
&lt;p&gt;정말정말 많다는 생각이 든다.&lt;/p&gt;
&lt;p&gt;JavaScript, Python 의 진입 장벽이 낮지만, 최적화 면에서는 여전히 부족한 부분이 많으므로,&lt;/p&gt;
&lt;p&gt;아직 최적화 되지 않은 부분을 모듈로 개발하여 배포한다면, 많은 개발자들이 사랑해 줄 것으로 생각된다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;요약 및 마무리&lt;/h2&gt;
&lt;p&gt;그래서 내가 진행 한 것은,&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;C 로 백준 문제를 풀되, stdio.h 를 제외한 모든 유틸리티는 직접 작성하기&amp;quot;&lt;/strong&gt; 이다.&lt;/p&gt;
&lt;p&gt;물론 나 스스로도 이러한 학습 방식이 &amp;quot;옳다&amp;quot; &amp;quot;나쁘다&amp;quot; 를 결정할 수는 없었다.&lt;/p&gt;
&lt;p&gt;그 이유는 결정적으로 컴퓨팅 지능 포텐셜을 &lt;strong&gt;압도적으로&lt;/strong&gt; 높일 수 있는 방식이었기 때문이다.&lt;/p&gt;
&lt;p&gt;저 수준의 메모리 관리와 어려운 표현식을 다루다 보니, 시각을 넓힐 수 있다는 것이 매우 큰 수확이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;이 공부 방식을 권장하지 않는 경우&lt;/h2&gt;
&lt;p&gt;만약에, 다른 프로그래밍의 추상적인 패러다임과 하드웨어 접근에 대한 조금의 이해도가 부족하다면,&lt;/p&gt;
&lt;p&gt;나는 추천하지 않는다.&lt;/p&gt;
&lt;p&gt;거의 95 퍼센트 이상은 지쳐서 포기할 것이라고 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;내가 만약에 이 공부 방식을 추천한다면,&lt;/p&gt;
&lt;p&gt;&amp;quot;나는 절대로 AI 에게 코딩을 전부 맡기지 않겠다&amp;quot;&lt;/p&gt;
&lt;p&gt;혹은 &amp;quot;C or C++ 를 이용하여 특정 프로그램을 커스텀 해야 한다&amp;quot; 의 경우 추천할 것 같다.&lt;/p&gt;
&lt;br/&gt;</description>
      <category>Algorithm</category>
      <category>C</category>
      <category>C 포인터</category>
      <category>pointer</category>
      <category>stdio.h</category>
      <category>struct</category>
      <category>구현</category>
      <category>백준</category>
      <category>제약</category>
      <category>포인터</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/230</guid>
      <comments>https://codecreature.tistory.com/230#entry230comment</comments>
      <pubDate>Sat, 26 Jul 2025 05:49:05 +0900</pubDate>
    </item>
    <item>
      <title>Yarn 과 pnpm 패키지 매니저는 무엇일까?</title>
      <link>https://codecreature.tistory.com/229</link>
      <description>&lt;h2&gt;제목 : Yarn 과 pnpm 패키지 매니저는 무엇인가?&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;이 글을 작성하는 이유&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;방대한 JavaScript 기반의 패키지 레지스터를 보유하고 있는 장소가 바로 NPM 이다.&lt;/p&gt;
&lt;p&gt;JavaScript 는 언어 자체로 IO(Input Output) 빌트인 메서드가 간단하며,&lt;/p&gt;
&lt;p&gt;또한 표현식이 간단하여 프로그래머들의 진입 언어로 매우 유용하게 사용되고 있다.&lt;/p&gt;
&lt;p&gt;그런 만큼, 그 커뮤니티의 크기는 감히 말할 수 없을 정도로 커졌으며,&lt;/p&gt;
&lt;p&gt;언어 자체가 가지고 있던 성능의 한계를 다양한 내장 메서드 최적화,&lt;/p&gt;
&lt;p&gt;재사용 코드 캐싱 등의 기능으로 돌파하고 있다. (그래도 저레벨 언어에 비해서는 한계가 있지만.)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;npm 은 JavaScript 기반의 템플릿 프로젝트를 만들기 위해서 필수적인 요소이다.&lt;/p&gt;
&lt;p&gt;주로 프로젝트 루트에 &lt;code&gt;package.json&lt;/code&gt; 을 생성하여 내부에 모듈을 설치하기 위한 설정파일을 만들거나,&lt;/p&gt;
&lt;p&gt;로컬 컴퓨터에 node, npm 패키지가 설치되어 있으면, 간단하게 &lt;code&gt;npm i ...&lt;/code&gt; 로 설치 할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;package-lock.json&lt;/code&gt; 은 설치된 의존성의 상세한 레지스터 주소 뿐 만 아니라,&lt;/p&gt;
&lt;p&gt;이 패키지를 설치하기 위한 또 다른 필요 의존성을 거론한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그런데, NPM 의 부족한 특성으로 인해 다른 Node 패키지 매니저인&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Yarn&lt;/li&gt;
&lt;li&gt;PNpm(정식 이름)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이러한 프로그램이 나오게 되었다.&lt;/p&gt;
&lt;p&gt;Yarn 은 자주 사용하는 사람들이 있던데, PNpm 은 왜 사용하는 건지 의문이 들었다.&lt;/p&gt;
&lt;p&gt;NPM 은 겹치는 의존성 관리, 혹은 느린 의존성 파악, 보안, 등등 여러 부족한 부분을 보완하여&lt;/p&gt;
&lt;p&gt;나머지 패키지 매니저들과 비슷한 부분까지 도달했다고 들었다.&lt;/p&gt;
&lt;p&gt;그렇다면 나머지 패키징 매니저들은 현재 어떤 포지션으로 여전히 개발자들을 돕고 있을지 매우 궁금해졌다.&lt;/p&gt;
&lt;p&gt;특히, 아직 Yarn 을 사용하는 프론트엔드 개발자들이 존재하여 이를 알아둘 필요가 있다고 판단되었다.&lt;/p&gt;
&lt;p&gt;그리고 각자 나머지 패키지들보다 우세한 점을 공식 홈페이지에서 거론하고 있기 때문에,&lt;/p&gt;
&lt;p&gt;공식 문서를 2 순위로 정하고, 위키백과에 나온 상대적으로 객관적인 내용을 1순위로 보기로 했다.&lt;/p&gt;
&lt;p&gt;2025-07 기준, NPM &amp;gt; Yarn &amp;gt; pnpm 순으로 사용되고 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Yarn 이란 무엇인가?&lt;/h2&gt;
&lt;p&gt;역시 React 의 시초인 회사라고 해야 할까.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Yarn&lt;/strong&gt; 이란, 페이스북이 2016년 Node.js 기반의 자바스크립트 런타임 환경을 위해&lt;/p&gt;
&lt;p&gt;개발한 소프트웨어 패키지 시스템이라고 한다.&lt;/p&gt;
&lt;p&gt;대형 코드의 일관성, 보안, 성능 문제를 해결하기 위해 개발되었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;위키백과에 &lt;strong&gt;NPM 과의 비교&lt;/strong&gt; 가 적혀있기에, 적절한 것 같아 내용을 넣어보자면,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Yarn 은 로컬 캐시로부터 패키지를 설치한다.&lt;/li&gt;
&lt;li&gt;Yarn 은 패키지 버전을 강력하게 바인딩한다.&lt;/li&gt;
&lt;li&gt;Yarn 은 데이터 무결성 보장을 위해 Checksum 을 사용하며, npm 은 sha-512 를 사용한다.&lt;/li&gt;
&lt;li&gt;Yarn 은 병렬로 패키지를 설치하며, npm 은 하나의 패키지씩 설치한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;위에까지가 위키백과의 설명이지만, 여태까지 공식문서를 읽어온 내 입장에서,&lt;/p&gt;
&lt;p&gt;오랜 역사를 가진 프로그램이 아니라면, 최신 프로그램에 대한 정보 변질이 발생하는 경우가 많았다.&lt;/p&gt;
&lt;p&gt;따라서, 공식 문서를 읽으며 다른 추가 정보와 기능들을 더 살펴보기로 결정했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;소개&lt;/strong&gt; 항목을 요약하자면, 오픈소스이며, 자바스크립트 프로젝트 의존성을 관리하는데 사용된다.&lt;/p&gt;
&lt;p&gt;Yarn 은 속도, 정확성, 보안, 그리고 개발자 경험에 초점을 맞추고 있으며,&lt;/p&gt;
&lt;p&gt;작업 공간, 오프라인 캐싱, 병렬 설치, 강화 모드, 대화형 명령 등 혁신적인? 기능을 활용하여&lt;/p&gt;
&lt;p&gt;모든 방향을 향상시키고 있다고 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;흠... 일단 현재로서 알 수 있는 것은, 작업 공간, 병렬 설치, 대화형 명령이다.&lt;/p&gt;
&lt;p&gt;오프라인 캐싱과 강화 모드에 대해선 모르겠다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span style=&quot;color : skyblue&quot;&gt;작업 공간&lt;/span&gt; : 만약에 다른 로컬 개발 환경에서 의존성을 설치한다면, &lt;br/&gt; 해당 의존성 모듈이 이미 전역 공간에 저장되어 있는지 확인하고, 없다면 다운로드 한다. &lt;br/&gt; 즉, 컴퓨터 자체에 모듈을 저장해 두고, 동일한 모듈을 다른 프로젝트에서 사용한다면, &lt;br/&gt; 공간 아깝게 또 모듈을 개별적으로 설치하지 않고, &amp;quot;참조&amp;quot; 하겠다는 의미&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ul&gt;
&lt;li&gt;&lt;span style=&quot;color : skyblue&quot;&gt;병렬 설치&lt;/span&gt; : 하나씩 모듈을 설치하는 npm 과는 다르게, &lt;br/&gt; 네트워크 요청을 병렬로 수행하여 이를 동시에 다운로드 받겠다는 의미.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;ul&gt;
&lt;li&gt;&lt;span style=&quot;color : skyblue&quot;&gt;대화형 명령&lt;/span&gt; : 기존 npm 보다 더 &amp;quot;유연한&amp;quot; 명령어 체계를 갖췄다는 의미이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;따라서, 미리 공식 문서를 읽어 봤는데,&lt;/p&gt;
&lt;p&gt;Hardened Mode (강화 모드) 라는 &amp;quot;Mode&amp;quot; 에 대한 설명이 없다.&lt;/p&gt;
&lt;p&gt;즉, 이건 공식 문서 상에서 특정 기능을 추상화 한 설명이라는 추측이 들었다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Yarn 오프라인 캐싱(offline caching 이란?)&lt;/h3&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://yarnpkg.com/features/caching&quot;&gt;Cache Strategies(캐싱 전략) 공식 문서&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;기본적으로 영문서를 번역하기 때문에, 문서를 읽기 복잡한 분은 제가 작성하는 요약본을 보셔도 좋습니다!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;요약&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;오프라인 캐싱이라는 개념을 알기 위해서, Yarn 이 주장하는 &lt;strong&gt;캐싱 전략&lt;/strong&gt; 문서를 살펴봐야 한다.&lt;/p&gt;
&lt;p&gt;이 개념에 &lt;strong&gt;강력하게&lt;/strong&gt; 연관된 것은, 바로 &amp;quot;작업 공간&amp;quot; 즉, &amp;quot;Workspace&amp;quot; 이다.&lt;/p&gt;
&lt;p&gt;Yarn 은 확실히 npm 패키지 매니저를 뒤이어 강력한 툴체인으로 거듭날 만한 것이, 바로,&lt;/p&gt;
&lt;p&gt;모듈을 전역으로 저장하여 재사용한다는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;캐싱의 2 가지 전략. Offline Mirror and Zero Installs&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;[Offline-Mirror]&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;자바스크립트 프로젝트를 생성하고, 현재까지 설치된 모듈의 목록을 같은 팀원들에게 전달하는 것은&lt;/p&gt;
&lt;p&gt;어렵지 않다. 그러나, 여태까지 알려진 경험으로서 NPM 에서 모듈을 다운로드 할 때, 종종&lt;/p&gt;
&lt;p&gt;설치에 실패하는 이슈가 발생 해 왔다.&lt;/p&gt;
&lt;p&gt;이러한 모듈 설치 실패 자체는 개발에 영향을 끼쳐 현재 개발중인 프로젝트의 Git 버전을&lt;/p&gt;
&lt;p&gt;롤백하거나, 브랜치를 스위칭해야하는 등, CI/CD 면에서 여러 제약이 걸린다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, Yarn 에서는 이러한 상황을 막고 &amp;quot;확실한&amp;quot; 설치를 보장하기 위해 밑의 기능을 제공한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;enableGlobalCache&lt;/code&gt; 라는 항목을 &lt;code&gt;false&lt;/code&gt; 로 설정하면, 패키지 설치 모듈 캐시를&lt;/p&gt;
&lt;p&gt;직접 프로젝트 루트 폴더 내의 &lt;code&gt;.yarn/cache&lt;/code&gt; 에 저장한다.&lt;/p&gt;
&lt;p&gt;그리고, Git 에 저장한다.&lt;/p&gt;
&lt;p&gt;이러한 방식으로 구성하면, 모든 커밋이 설치에 실패하지 않는 커밋이라고 보장할 수 있다.&lt;/p&gt;
&lt;p&gt;심지어, 해당 npm 레지스트리가 망가진다 할 지언정.&lt;/p&gt;
&lt;p&gt;이러한 패턴을 &amp;quot;Offline Mirror&amp;quot; 이라는 패턴으로 부른다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;[Zero-Installs]&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이게 위의 Offline-Mirror 과 다른 상반되는 전략이라고 생각했는데,&lt;/p&gt;
&lt;p&gt;다른 카테고리의 캐싱 전략을 의미한다는 것을 알고 봐야 한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Yarn PnP&lt;/strong&gt;(Plug and Play 약자) 와 Offline-Mirror 기능을 조합한 캐싱 전략으로,&lt;/p&gt;
&lt;p&gt;브랜치를 이동할 때 마다, 굳이 &lt;code&gt;yarn install&lt;/code&gt; 할 필요 없이, 그대로 프로젝트를 가동하는 것이다.&lt;/p&gt;
&lt;p&gt;이게 어떻게 가능한가? 보니, PnP 는 로컬 머신에 설치되었던 모듈을 캐싱하며,&lt;/p&gt;
&lt;p&gt;Offline Mirror 은 npm 레지스트리에 의존하지 않고 내부 &lt;code&gt;.yarn/cache&lt;/code&gt; 에 의존한다.&lt;/p&gt;
&lt;p&gt;이 둘을 조합하여 따로 Yarn Install 하지 않고도, 둘의 의존성을 Yarn 로더가 알아서 파악하여&lt;/p&gt;
&lt;p&gt;&lt;code&gt;yarn install&lt;/code&gt; 을 한 것과 같은 효과를 낸다는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;확실히, 팀 프로젝트를 진행하면 수많은 브랜치들이 갈라져 나오게 되는데,&lt;/p&gt;
&lt;p&gt;변경사항이 생길 때 마다 추가 모듈을 적용하기 위해 &lt;code&gt;npm i&lt;/code&gt; 하는 것은 불편한 일이기도 하다.&lt;/p&gt;
&lt;p&gt;그런데, Yarn 은 내부적으로 &lt;strong&gt;Yarn PnP&lt;/strong&gt; 그리고, &lt;strong&gt;Offline Mirror&lt;/strong&gt; 를 이용하여&lt;/p&gt;
&lt;p&gt;Zero-Install 즉, &amp;quot;모듈 설치 안함&amp;quot; 패턴을 이러한 방식으로 구현한다는 의미이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Hardened Mode 란?&lt;/h3&gt;
&lt;p&gt;NPM 의 &lt;code&gt;package-lock.json&lt;/code&gt; 이나, Yarn 에서의 lock-file 은 정확한 레지스트리의 주소를 가르킨다.&lt;/p&gt;
&lt;p&gt;그렇다는 것은, 레지스트리의 주소를 바꿔서 스크립트에 직접적인 공격 코드를 주입할 수 있다는 이야기이다.&lt;/p&gt;
&lt;p&gt;Yarn 은 이러한 상황을 막기 위해, &lt;strong&gt;Hardened Mode&lt;/strong&gt; 를 만들었다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;enablehardenedMode&lt;/code&gt; 를 true 하거나,&lt;/p&gt;
&lt;p&gt;셸 자체에 &lt;code&gt;YARN_ENABLE_HARDENED_MODE=1&lt;/code&gt; 로 환경 변수를 정의한다면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;lockfile poisoning&lt;/code&gt; 이라는 공격 기법을 차단할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;PNpm 이란 무엇인가?&lt;/h2&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://pnpm.io/&quot;&gt;PNpm 공식 사이트&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;굉장히 독특한 게, 개인 유튜버가 자신이 찾은 npm 모듈 시스템의 고질적인 문제를 파악하여,&lt;/p&gt;
&lt;p&gt;이에 대응하는 프로그램을 만들어 냈다는 것이다.&lt;/p&gt;
&lt;p&gt;1년전 개발자 레딧이 관련 사이트로 나오길래 댓글을 살펴보았는데,&lt;/p&gt;
&lt;p&gt;Yarn S**ks.. 라는 굉장한 단어까지 볼 수 있었다..&lt;/p&gt;
&lt;p&gt;pnpm 은 npm, yarn 에 비해 굉장히 최신 프로그램이며, 아직도 발전 해 나가고 있는 프로그램이다.&lt;/p&gt;
&lt;p&gt;즉, 기업이나 단체에 의해 만들어 진 개념보다는, 개인 + 개발 유튜버 가 만들어 낸 오픈소스라는 데&lt;/p&gt;
&lt;p&gt;중점이 있다.&lt;/p&gt;
&lt;p&gt;PNpm 이 왜 요즘 트렌드인지 살펴보자.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;PNpm 이 나오게 된 계기.&lt;/h3&gt;
&lt;p&gt;NPM 모듈의 설치는 굉장히 공간을 낭비한다.&lt;/p&gt;
&lt;p&gt;아무리 JavaScript 프로젝트의 모듈들이 최소한으로 공간을 저장하도록 노력한다 하더라도,&lt;/p&gt;
&lt;p&gt;토이 프로젝트의 모듈 크기 수준이 50~100MB 수준이라면,(프레임워크 수준)&lt;/p&gt;
&lt;p&gt;10 ~ 20 개의 프로젝트만 생성해도 1 GB 수준의 저장공간을 사용하는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이건 내가 생각해도 정말로 엄청난 공간 낭비인데, pnpm 은 이러한 문제를 극단적으로 해소했다.&lt;/p&gt;
&lt;p&gt;즉, 생성하는 프로젝트에 필요한 &amp;quot;모든 의존성&amp;quot; 을, &lt;code&gt;content-addressable&lt;/code&gt; 이라는 저장소에&lt;/p&gt;
&lt;p&gt;전부 저장하는 것이다.&lt;/p&gt;
&lt;p&gt;그리고 해당 프로젝트는, 필요 의존성을 전부 &lt;code&gt;content-addressable&lt;/code&gt; 저장소에 &amp;quot;Hard Link&amp;quot; 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;만약에, 새로운 버전의 모듈 의존성을 다운받았다고 해도, 해당 의존성에서 필요로 하는 다른 의존성이&lt;/p&gt;
&lt;p&gt;1 개인 경우, 저장소에서 &amp;quot;1개&amp;quot; 만 업데이트하고, 나머지 의존성은 동일하므로 다시 하드 링크하는 방식이다.&lt;/p&gt;
&lt;p&gt;위의 방식은 평균적인 개인 컴퓨터 저장소인 256 GB 에서 매우 유용하게 사용할 수 있다고 생각된다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;PNpm 만의 독특한 의존성 폴더 구조&lt;/h3&gt;
&lt;p&gt;위에서는 PNpm 의 하드 링크 방식으로 디스크 공간을 획기적으로 줄이는 방법을 소개했다.&lt;/p&gt;
&lt;p&gt;그렇다면, JavaScript 프로젝트에는 의존성을 사용하기 위해 &lt;code&gt;node_modules&lt;/code&gt; 를 필요로 하는데,&lt;/p&gt;
&lt;p&gt;이를 어떻게 해소했을까?&lt;/p&gt;
&lt;p&gt;공식문서에서 가져온 구조를 함께 보자 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;node_modules
└── .pnpm
    ├── bar@1.0.0
    │   └── node_modules
    │       └── bar
    │           ├── index.js     -&amp;gt; &amp;lt;store&amp;gt;/001
    │           └── package.json -&amp;gt; &amp;lt;store&amp;gt;/002
    └── foo@1.0.0
        └── node_modules
            └── foo
                ├── index.js     -&amp;gt; &amp;lt;store&amp;gt;/003
                └── package.json -&amp;gt; &amp;lt;store&amp;gt;/004&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;보통 &lt;code&gt;node_modules&lt;/code&gt; 디렉토리 아래에는 &lt;code&gt;.pnpm&lt;/code&gt; 이 있지 않으며,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;bar...&lt;/code&gt; 그리고 &lt;code&gt;foo...&lt;/code&gt; 가 있을 것이다.&lt;/p&gt;
&lt;p&gt;그러나, 폴더 자체를 &lt;code&gt;.pnpm&lt;/code&gt; 으로 나누어 확실히 분리했다.&lt;/p&gt;
&lt;p&gt;결국, 각각의 모듈들은 내부적으로 필요한 내부 의존성이 있으며,&lt;/p&gt;
&lt;p&gt;또한 모듈의 메서드를 한번에 내보내기 위한 &lt;code&gt;index.js&lt;/code&gt; 파일이 컨벤션적으로나 일반적으로나 존재한다.&lt;/p&gt;
&lt;p&gt;이 때, &lt;code&gt;index.js&lt;/code&gt; 와 &lt;code&gt;package.json&lt;/code&gt; 을&lt;/p&gt;
&lt;p&gt;PNpm 의 &lt;code&gt;content-addressable&lt;/code&gt; 저장소에서 참조하여 Symlink 하는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;NPM 과 나머지 2 개의 패키지 매니저의 비교 (간단)&lt;/h2&gt;
&lt;p&gt;간단한 설치 시스템을 가진 NPM 과 나머지 패키지 매니저와의 성능 차이는 명백하다.&lt;/p&gt;
&lt;p&gt;나머지 매너지들은 &amp;quot;병렬 설치&amp;quot; 뿐만 아니라, 모듈 설치 공간을 따로 마련하여 작업 공간을 아껴주기도 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그럼에도 불구하고, Yarn 과 PNpm 의 차이점은 나의 시각에서 이렇게 보인다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Yarn 은 미리 대안 패키지 매니저로서 정착하여 다양한 기업에서 사용한다. &lt;br/&gt; 그러나, PNpm 은 아직 성장 중이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Yarn 은 NPM 레지스트리 참조와 전역 모듈 공간 참조를 섞은데 포커싱이 되어 있다면, &lt;br/&gt; PNpm 은 개인 개발자의 로컬 컴퓨팅 경험에 초점을 맞췄다.&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Yarn 은 npm 의 기본 패키지 매니저와 비슷한 구조를 띄지만, &lt;br/&gt; PNpm 은 그 구조부터가 극단적인 효율성을 띈다. (EX - Synlink)&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;h2&gt;NPM 은 어떻게 대응하고 있는가? (개인적 생각)&lt;/h2&gt;
&lt;p&gt;NPM 은 기존 레지스트리의 보호와 일관성 측면에서 매우 노력했으며,&lt;/p&gt;
&lt;p&gt;나머지 부가적인 기능들을 외부에 외주 맡기듯 기다렸다는 생각이 든다.&lt;/p&gt;
&lt;p&gt;결국, Yarn 이나, PNpm 이나, NPM 자체가 보유한 레지스트리가 존재하지 않으면,&lt;/p&gt;
&lt;p&gt;필요 가치가 거의 없어지는 프로그램에 가깝기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;NPM 에서는 이러한 부가 기능을 가진 외부 프로그램의 기능을 먹어치우는 느낌 보다는,&lt;/p&gt;
&lt;p&gt;본연이 가진 레지스트리 저장, 보호, 그리고 새로운 레지스트리 등록에 집중하는 모습을 보인다.&lt;/p&gt;
&lt;p&gt;심지어 NPM 공식 유튜브에서조차 외부 프로그램을 사용하여 의존성을 파악하는 영상을 띄울 정도니,&lt;/p&gt;
&lt;p&gt;외부 오픈소스에 얼마나 열려있는지 알 수 있는 항목이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;각 언어마다 고유한 의존성 레지스트리 장소가 다르며, 그 사용 형태도 명백히 다르다.&lt;/p&gt;
&lt;p&gt;JavaScript 는 NPM 이 public 오픈소스 레지스트리 장소로 꼽히며,&lt;/p&gt;
&lt;p&gt;그 위세는 전혀 꺾일 것이라고 보이진 않는다.&lt;/p&gt;
&lt;p&gt;애초에 모듈을 참조하거나, 내용을 보기 위해 NPM 사이트에서 검색하는데,&lt;/p&gt;
&lt;p&gt;해당 모듈을 다시 다른 패키지 매니저에 맞추기 위해 배우는 것은 해당 커뮤니티의 성격에 맞지 않을 수 있기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, 나는 나머지 Package Manager 들의 탄생 목적과 그 기능을 훌륭하다고 말하고 싶다.&lt;/p&gt;
&lt;p&gt;로컬 머신에서 개발하는데, 동일한 패키지와 버전을 다운받는데 어떠한 캐싱도 없이,&lt;/p&gt;
&lt;p&gt;그대로 다운받아 각 프로젝트에 저장한다는 것은, 큰 용량을 가진 컴퓨터가 아니라면&lt;/p&gt;
&lt;p&gt;분명히 한계를 가지는 방법이다.&lt;/p&gt;
&lt;p&gt;따라서, NPM 의 방식과 스스로의 방식을 섞은 Yarn,&lt;/p&gt;
&lt;p&gt;그리고 극단적인 디스크 용량 절약을 보이는 PNpm 의 방식 둘 다 존중한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그리고 사이트를 돌다가 개발자 레딧을 들어가게 되었는데,&lt;/p&gt;
&lt;p&gt;PNpm 이 요즈음 트렌드로 올라가고 있는 것 같다.&lt;/p&gt;
&lt;p&gt;그러나, 비교적 최신 프로그램이며, 아주 복잡한 기업 프로그램에서 사용될 수 있을지는 미지수라고 생각한다.&lt;/p&gt;
&lt;p&gt;기업은 오래되고 불편하더라도 &amp;quot;정확성&amp;quot; 을 매우 중시하기 때문이다.&lt;/p&gt;
&lt;p&gt;Yarn 이 가진 고유의 사용자 파이를 먹기 위해서,&lt;/p&gt;
&lt;p&gt;PNpm 은 npm 의 불편함을 감수할 수 있는 편리한 방식을 고안해야 할 것이라는 생각도 든다.&lt;/p&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;h2&gt;참조 사이트&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Yarn 공식 홈페이지&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://yarnpkg.com/&quot;&gt;https://yarnpkg.com/&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;PNpm 공식 홈페이지&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://pnpm.io/&quot;&gt;https://pnpm.io/&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Yarn 깃허브 페이지&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://github.com/yarnpkg/berry&quot;&gt;https://github.com/yarnpkg/berry&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;pnpm 깃허브 페이지&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://github.com/pnpm/pnpm&quot;&gt;https://github.com/pnpm/pnpm&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;NPM 공식 홈페이지 문서 - (About npm)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://docs.npmjs.com/about-npm&quot;&gt;https://docs.npmjs.com/about-npm&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;위키백과 - Yarn&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.wikipedia.org/wiki/Yarn_(%ED%8C%A8%ED%82%A4%EC%A7%80_%EA%B4%80%EB%A6%AC%EC%9E%90)&quot;&gt;https://ko.wikipedia.org/wiki/Yarn_(%ED%8C%A8%ED%82%A4%EC%A7%80_%EA%B4%80%EB%A6%AC%EC%9E%90)&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;</description>
      <category>Node.js/잡다 지식</category>
      <category>javascript</category>
      <category>js npm</category>
      <category>node package manager</category>
      <category>npm</category>
      <category>pnpm</category>
      <category>Yarn</category>
      <category>공부</category>
      <category>기초</category>
      <category>종류</category>
      <category>패키지 매니저</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/229</guid>
      <comments>https://codecreature.tistory.com/229#entry229comment</comments>
      <pubDate>Fri, 18 Jul 2025 01:27:40 +0900</pubDate>
    </item>
    <item>
      <title>index.html 에서 시작하는 점진적인 리액트 프로젝트 적용 방법에 대해서</title>
      <link>https://codecreature.tistory.com/228</link>
      <description>&lt;h2&gt;제목 : 점진적인 리액트 프로젝트 도입 과정과 문제 해결 과정&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;이 글을 작성하는 이유는?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;빠른 프로젝트 생성과 웹 페이지 생성에 초점이 맞춰진 현대 프로그래밍 트렌드는 AI 로 인해&lt;/p&gt;
&lt;p&gt;더욱 중요해진 요소로 꼽히고 있다.&lt;/p&gt;
&lt;p&gt;현재 프로그래밍 세상에서, 오히려 해당 분야에 대한 깊은 공부는 공감받지 못하는 사회라는 것을 느낀다.&lt;/p&gt;
&lt;p&gt;오히려, 빠른 개발을 요하는 실제 프로덕션 세상에서, 깊은 이해를 위한 시간 소비는 사치라고 생각할 지 모른다.&lt;/p&gt;
&lt;p&gt;아니, 그것이 당연할 지도 모른다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, 나는 역발상으로, 리액트라는 단어가 가지는 수많은 의미를 탐색해 왔다.&lt;/p&gt;
&lt;p&gt;리액트는 무엇인지, 어떤 것으로 이루어져 있는지, 어떤 방법론을 사용하는지 등등..&lt;/p&gt;
&lt;p&gt;심지어는 JavaScript 의 es6 이상 버전이 어떻게 es5 로 폴리필 되는지 확인할 정도였다.&lt;/p&gt;
&lt;p&gt;이 과정에서 사람 친화적, 웹 친화적인 자바스크립트가 되기 위해 어떤 변화와 방법이 적용되었는지 조사했다.&lt;/p&gt;
&lt;p&gt;예를 들면,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;JS 에 클래스는 Functional 함수의 Sugar Syntax 버전이다.&lt;ul&gt;
&lt;li&gt;즉, 클래스는 개발자 친화적인 지원일 뿐, 실제로는 JS 의 함수로 컴파일 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;NPM 은 JS 의 커뮤니티를 거대화 시켰으며, 매우 간편한 명령어들을 지원한다.&lt;/li&gt;
&lt;li&gt;JavaScript 는 Web, WAS 개발 시장을 동시에 잡은 프로그래밍 언어이다.&lt;ul&gt;
&lt;li&gt;최적화를 위해서라면 저 레벨 언어를 사용하는 것이 좋겠지만, &lt;br/&gt; 자바스크립트 스스로의 최적화와 수많은 템플릿은 개발 시장에서 굳건하게 자리를 차지하게 해 주었다. &lt;br/&gt; 특히 메타프로그래밍이 매우 중요한 시대에서 자바스크립트는 간편한 메타프로그래밍을 지원한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;JavaScript 의 디버깅과 타입 지원을 위한 SuperSet 인 타입스크립트는 JS 의 자리를 지켜준다.&lt;/li&gt;
&lt;li&gt;느린 TS -&amp;gt; JS 컴파일을 위해 최신 Microsoft 가 Go 를 이용한 컴파일 지원을 준비하고 있다.&lt;/li&gt;
&lt;li&gt;JavaScript 는 C 와 같은 저레벨 언어로 제작된 수많은 라이브러리를 가지고 있다.&lt;ul&gt;
&lt;li&gt;이를 이용하여 JS 에서 부족한 성능 최적화를 어느정도 보완할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;JavaScript 언어 자체의 성능을 극복하기 위해서 &lt;code&gt;wasm&lt;/code&gt; 모듈을 도입할 수 있다.&lt;/li&gt;
&lt;li&gt;멀티스레드 시스템을 지원한다. - 단, 스레드들은 독립적인 메모리 공간을 가진다.&lt;/li&gt;
&lt;li&gt;등등...&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;p&gt;웹 시장에서 거대한 도구로 자리매김하고 있는 React 는 더욱이 최신 JS 스펙을 적용하며 최적화했으며,&lt;/p&gt;
&lt;p&gt;특히 타입스크립트와 발전된 IDE, Language Server 덕분에 리액트 제작은 더욱 간결하고 편리해 졌다.&lt;/p&gt;
&lt;p&gt;그러나, 나는 이전에 React 의 Fiber, Lane, EventPriority, executionContext 등&lt;/p&gt;
&lt;p&gt;리액트 개발 과정에서 볼 수 없던 수많은 내부 아키텍쳐 변수와 메서드를 살펴보며 이러한 궁금증이 들었다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;리액트 개발 템플릿 생성을 위한 명령어를 입력하지 않고, 리액트 + 타입스크립트 프로젝트를 어떻게 만들 수 있을까?&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이를 위해 진행하게 될 절차는 다음과 같다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;index.html&lt;/code&gt; 생성 및 루트 DOM id 등록&lt;/li&gt;
&lt;li&gt;해당 프로젝트 내부에 npm, tsconfig 설정을 이용하여 &lt;code&gt;dist&lt;/code&gt; 이용 예정&lt;/li&gt;
&lt;li&gt;컴파일된 파일을 HTML 파일 내부에서 실행하여 DOM 을 렌더링한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;아주 추상적으로 계획을 잡았는데, 이는 중간에 어떤 과제가 더 존재할지 모르기 때문이다.&lt;/p&gt;
&lt;p&gt;(사실, 체계적으로 계획을 잡았다가 해당 방법이 안 될 경우를 고려한 계획.)&lt;/p&gt;
&lt;p&gt;한번 날것으로 리액트를 만들어 보고 싶었는데, 오늘이 그 날인가 싶다!&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;기본적인 프로젝트 세팅하기&lt;/h2&gt;
&lt;p&gt;우선, 마음가짐을 처음으로 되돌렸다.&lt;/p&gt;
&lt;p&gt;나의 프로젝트는 &lt;code&gt;index.html&lt;/code&gt;, &lt;code&gt;index.js&lt;/code&gt; 에서 출발하기로 마음먹었다.&lt;/p&gt;
&lt;h3&gt;간단한 웹 서버 실행&lt;/h3&gt;
&lt;p&gt;파일 형식으로 웹 파일을 열게 된다면, &lt;code&gt;file&lt;/code&gt; 과 &lt;code&gt;localhost&lt;/code&gt; 자체의 CORS 가 성립되지 않아&lt;/p&gt;
&lt;p&gt;모듈 형식의 파일이 로드되지 않는 것을 확인했다.&lt;/p&gt;
&lt;p&gt;따라서,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ npx http-server -c-1&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 명령어로 현재 명령어 위치에서 &lt;code&gt;index.html&lt;/code&gt; 을 찾아 실행한다.&lt;/p&gt;
&lt;p&gt;파일의 로드 현황과 로드 에러를 실시간으로 CLI 로 볼 수 있어 편하다.&lt;/p&gt;
&lt;p&gt;그리고 &lt;code&gt;-c-1&lt;/code&gt; 인 이유는, &lt;strong&gt;파일의 캐싱&lt;/strong&gt; 때문에 넣은 옵션이다.&lt;/p&gt;
&lt;p&gt;우리가 웹을 로드할 때, 아주 최근에 동일한 리소스를 요청한 적이 있었다면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;304&lt;/code&gt; 라는 네트워크 신호와 함께, 리소스가 다시 로드되지 않는다.&lt;/p&gt;
&lt;p&gt;즉, 브라우저가 스스로 저장하고 있던 기존의 파일을 다시 되돌려준다.&lt;/p&gt;
&lt;p&gt;이러한 기능은 네트워크를 매우 절약해 주는 브라우저만의 특성이지만, 개발시에는 필요없다.&lt;/p&gt;
&lt;p&gt;따라서, &lt;code&gt;-c-1&lt;/code&gt; 을 지정하여, 캐싱 파일 따위 저장하지 않겠다는 개발자의 면모를 보여주자..&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;index.html 기본 형태&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
        &amp;lt;title&amp;gt;처음부터 만드는 TS 리액트 프로젝트&amp;lt;/title&amp;gt;
        &amp;lt;script crossorigin src=&amp;quot;https://unpkg.com/react@18/umd/react.development.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
        &amp;lt;script crossorigin src=&amp;quot;https://unpkg.com/react-dom@18/umd/react-dom.development.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
        &amp;lt;script defer src=&amp;quot;./index.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;div id=&amp;quot;root&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;우선, bundler 가 없다는 것을 전제로 간단한 React 웹 렌더링을 수행하기로 마음 먹었다.&lt;/p&gt;
&lt;h3&gt;index.js 기본 형태&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const e = React.createElement;

const domRoot = document.getElementById(&amp;quot;root&amp;quot;);

const root = ReactDOM.createRoot(domRoot);

root.render(
  e(
    &amp;quot;h2&amp;quot;,
    null,
    &amp;quot;rendering react for h2&amp;quot;
  )
);&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;결과물은,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;screen-1.png&quot; data-origin-width=&quot;514&quot; data-origin-height=&quot;112&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXRYeZ/btsPkucRpsR/poZlWEEOodzVkS95gvRm01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXRYeZ/btsPkucRpsR/poZlWEEOodzVkS95gvRm01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXRYeZ/btsPkucRpsR/poZlWEEOodzVkS95gvRm01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXRYeZ%2FbtsPkucRpsR%2FpoZlWEEOodzVkS95gvRm01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;514&quot; height=&quot;112&quot; data-filename=&quot;screen-1.png&quot; data-origin-width=&quot;514&quot; data-origin-height=&quot;112&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;이와 같이 정상적으로 나타나는 것을 확인 할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;우리가 제작 한 것은 매우 간단한 RootDOM(&lt;code&gt;FiberRoot&lt;/code&gt;) 로부터 시작되는 렌더 예제이다.&lt;/p&gt;
&lt;p&gt;이제 간단한 컴포넌트를 제작하여 직접 렌더링 해 보자.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;index.js 기본적인 컴포넌트 예제&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const { useState, createElement } = React;

const e = createElement;

const domRoot = document.getElementById(&amp;quot;root&amp;quot;);

const root = ReactDOM.createRoot(domRoot);

const Count = () =&amp;gt; {
  const [count, setCount] = useState(0);

  return e(
    &amp;quot;div&amp;quot;,
    null,
    e(
      &amp;quot;h2&amp;quot;,
      null,
      count
    ),
    e(
      &amp;quot;button&amp;quot;,
      { onClick : () =&amp;gt; {setCount(count + 1)}},
      &amp;quot;+ 1&amp;quot;
    )
  )
}

root.render(
  e(
    Count,
    null,
  )
);&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;결과물&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;screen-2.png&quot; data-origin-width=&quot;248&quot; data-origin-height=&quot;188&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7PGlP/btsPiZZqGii/6etKW5WZiDukqzln2NlGV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7PGlP/btsPiZZqGii/6etKW5WZiDukqzln2NlGV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7PGlP/btsPiZZqGii/6etKW5WZiDukqzln2NlGV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7PGlP%2FbtsPiZZqGii%2F6etKW5WZiDukqzln2NlGV1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;248&quot; height=&quot;188&quot; data-filename=&quot;screen-2.png&quot; data-origin-width=&quot;248&quot; data-origin-height=&quot;188&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;index.html&lt;/code&gt; 은 건들 것이 아직 없기에 변경하지 않았고,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;index.js&lt;/code&gt; 에서 변경점이 많다.&lt;/p&gt;
&lt;p&gt;우선,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;함수 컴포넌트를 이용하여 렌더링을 했다.&lt;/li&gt;
&lt;li&gt;함수 컴포넌트 내부에 React 기본 Hook 인 &lt;code&gt;useState&lt;/code&gt; 를 사용했다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;button&lt;/code&gt; 엘리먼트를 생성 할 때, JSX 처럼 속성을 주입했다.&lt;/li&gt;
&lt;li&gt;번들러를 위해 &lt;code&gt;import&lt;/code&gt; 구문을 사용한 것이 아니라, 추출 형식을 채택했다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;React&lt;/code&gt;, &lt;code&gt;ReactDOM&lt;/code&gt; 2 가지 라이브러리 자체는 현재 윈도우의 전역 변수로 등록되었다.&lt;/p&gt;
&lt;p&gt;우리는, &lt;code&gt;defer&lt;/code&gt; 이라는 속성 덕분에, &lt;code&gt;DOMContentLoaded&lt;/code&gt; 이벤트 직접에 이 파일을 실행하는 것이다.&lt;/p&gt;
&lt;p&gt;즉, 렌더링 직전 모든 DOM 이 구성되었지만, 렌더링하기 직전에 이 코드를 실행한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;우리가 JSX 혹은 TSX 를 이용하지 못하여 &lt;code&gt;createElement&lt;/code&gt; 를 불편하게 사용하는 것 외에는,&lt;/p&gt;
&lt;p&gt;크게 코드 작성 기법이 다르다는 생각을 하진 않는다.&lt;/p&gt;
&lt;p&gt;참고로, &lt;code&gt;createElement&lt;/code&gt; 메서드는 현재 &lt;code&gt;e&lt;/code&gt; 라는 값으로 &amp;quot;간소화&amp;quot; 시켰으며,&lt;/p&gt;
&lt;p&gt;이 메서드의 인자는 각각 이러한 것을 의미한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;사용자 정의 컴포넌트, 혹은 기본 태그를 렌더링 할 경우 문자열로 전달&lt;/li&gt;
&lt;li&gt;해당 컴포넌트에 전달할 props 혹은 내부 예약 속성&lt;/li&gt;
&lt;li&gt;해당 컴포넌트의 &lt;code&gt;children&lt;/code&gt; 에 해당될 인자를 전달한다. (선택 인자)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;즉, 우리가 JSX 기법을 이용하지 못하는 것을 제외하면, 대부분 동일한 형식을 가진다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;간단한 정보 입력 및 리스트 구축하기 - Just JavaScript 버전&lt;/h2&gt;
&lt;p&gt;위의 방식은 이러했다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;cdn 으로 리액트 라이브러리 파일을 가져와 &lt;code&gt;window&lt;/code&gt; 객체에 속성으로 매핑한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;defer&lt;/code&gt; 속성을 이용하여 나의 코드를 마지막에 실행시킨다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;그러나, 위의 방식은 모두 UMD 방식으로, 컴포넌트 별 파일로 분리시키기에는 한계가 명확했다.&lt;/p&gt;
&lt;p&gt;이유는, &lt;code&gt;window.ButtonComponent&lt;/code&gt; 이런 식으로 매핑해야 되었기 때문.&lt;/p&gt;
&lt;p&gt;혹은, 리액트 프로젝트의 Chunk 파일처럼, 하나의 파일에 모든 자바스크립트 코드를 넣는 경우는 상관없다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, 나는 파일 별로 대표 컴포넌트를 나눠, 관심사를 분리하고 싶었다.&lt;/p&gt;
&lt;p&gt;따라서, 나의 자바스크립트 파일은 &lt;code&gt;type : &amp;quot;module&amp;quot;&lt;/code&gt; 로서, &lt;code&gt;import&lt;/code&gt; 스코프에 해당하도록 만들고,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;cdn&lt;/code&gt; 방식을 &lt;code&gt;import&lt;/code&gt; 스코프, 즉, 모듈 스코프(ESM) 으로 만들기 위해 다른 스크립트 태그를 사용했다.&lt;/p&gt;
&lt;h3&gt;index.html (ESM 의존성 방식)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
        &amp;lt;title&amp;gt;처음부터 만드는 TS 리액트 프로젝트&amp;lt;/title&amp;gt;
        &amp;lt;script type=&amp;quot;importmap&amp;quot;&amp;gt;
          {
            &amp;quot;imports&amp;quot;: {
              &amp;quot;react&amp;quot;: &amp;quot;https://esm.sh/react@18&amp;quot;,
              &amp;quot;react-dom/client&amp;quot;: &amp;quot;https://esm.sh/react-dom@18/client&amp;quot;
            }
          }
          &amp;lt;/script&amp;gt;
        &amp;lt;script type=&amp;quot;module&amp;quot; src=&amp;quot;./index.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;div id=&amp;quot;root&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 방식으로, &lt;code&gt;react&lt;/code&gt;, &lt;code&gt;react-dom/client&lt;/code&gt; 라이브러리는&lt;/p&gt;
&lt;p&gt;이전의 cdn 처럼 &lt;code&gt;.development&lt;/code&gt; 로서 개발용이 아니라, 실제 프로덕션에서도 사용되는 라이브러리이다.&lt;/p&gt;
&lt;p&gt;즉, &lt;code&gt;import .. from ...&lt;/code&gt; 을 빌드 전에 개발하는 것처럼 가져올 수 있게 해 주는 기능이다.&lt;/p&gt;
&lt;p&gt;MDN 공식문서 확인 결과, 모든 브라우저에서 지원된다.&lt;/p&gt;
&lt;p&gt;그러나, 멀티 &amp;quot;imports&amp;quot; 선언은 극소수의 브라우저에서 지원되지 않는다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;굳이 Multi Imports 를 선언 할 필요가 있는가..?&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;br/&gt;

&lt;p&gt;또한, 리액트 렌더링의 진입점을 명확히 작성했다.&lt;/p&gt;
&lt;p&gt;즉, &lt;code&gt;./index.js&lt;/code&gt; 경로를 통해 모든 DOM 이 체계화 되는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;index.js&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import { createElement } from &amp;quot;react&amp;quot;;

import {createRoot} from &amp;quot;react-dom/client&amp;quot;

import InputInformation from &amp;quot;./components/InputInformation.js&amp;quot;;

const e = createElement;

const domRoot = document.getElementById(&amp;quot;root&amp;quot;);

const root = createRoot(domRoot);

root.render(
  e(
    InputInformation,
    null,
  ),
);&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;InputInformation.js - 입력 및 리스트 포함 컴포넌트&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import { useState, createElement } from &amp;quot;react&amp;quot;;
import InputComponent from &amp;quot;./InputComponent.js&amp;quot;;
import ListInformation from &amp;quot;./ListInformation.js&amp;quot;;

export default function InputInformation() {
  const [text, setText] = useState(&amp;#39;&amp;#39;);
  const [list, setList] = useState([]);

  const onClickInsert = () =&amp;gt; {
    const newList = list.concat(text);

    setList(newList);
  }

  return createElement(
    &amp;quot;div&amp;quot;,
    null,
    createElement(
      InputComponent,
      {text : text, setText : setText, onClickInsert : onClickInsert}
    ),
    createElement(
      ListInformation,
      {list : list}
    )
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;InputComponent.js - 입력 컴포넌트&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import { createElement } from &amp;quot;react&amp;quot;;

export default function InputComponent ({text, setText, onClickInsert}) {

  function onClickIns() {
    onClickInsert(text);
    setText(&amp;quot;&amp;quot;);
  }

  return createElement(
    &amp;quot;div&amp;quot;,
    null,
    createElement(
      &amp;quot;input&amp;quot;,
      { onChange: (e) =&amp;gt; { setText(e.currentTarget.value); }, value : text},
    ),
    createElement(
      &amp;quot;button&amp;quot;,
      {onClick : () =&amp;gt; onClickIns()},
      &amp;quot;리스트에 텍스트를 추가한다&amp;quot;
    )
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;ListInformation.js - 입력된 리스트 컴포넌트&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import { createElement, useEffect } from &amp;quot;react&amp;quot;

export default function ListInformation ({list}) {

  useEffect(() =&amp;gt; {
    listing(list);
  }, [list]);

  const listing = (list) =&amp;gt; {
    const arr = list.map((text, idx) =&amp;gt; {
      return createElement(
        &amp;quot;li&amp;quot;,
        {id : idx},
        text
      )
    })

    return arr;
  }

  return createElement(
    &amp;quot;ul&amp;quot;,
    null,
    listing(list)
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;결과물은,&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;screen-3.png&quot; data-origin-width=&quot;536&quot; data-origin-height=&quot;228&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kOLLM/btsPi1Qwyka/hW0kpaKHnH4WnYHxKRET0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kOLLM/btsPi1Qwyka/hW0kpaKHnH4WnYHxKRET0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kOLLM/btsPi1Qwyka/hW0kpaKHnH4WnYHxKRET0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkOLLM%2FbtsPi1Qwyka%2FhW0kpaKHnH4WnYHxKRET0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;536&quot; height=&quot;228&quot; data-filename=&quot;screen-3.png&quot; data-origin-width=&quot;536&quot; data-origin-height=&quot;228&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;이를 그래프로 표현 해 보자면,&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TB

subgraph index.html
    html-1(&amp;quot;웹을 표현하기 위한 리소스를 포함하는 루트 리소스&amp;quot;)
    html-2(&amp;quot;React 에서 RootDOM 이 될 기본 DOM 이 최소 1개는 있어야 한다.&amp;quot;)
end

subgraph index.js
    index-1(&amp;quot;리액트를 사용하는 첫 번째 파일이자, 의존성을 관리&amp;quot;)
    index-2(&amp;quot;루트 돔을 안착시키는 부분이자, 전역 컨텍스트를 관리하도록 만들 수 있는 중요한 부분&amp;quot;)
end

subgraph InputInformation.js
    input-info-1(&amp;quot;입력과 리스트를 포함&amp;quot;)
    input-info-2(&amp;quot;입력에 대한 데이터를 관리하며, 버튼 클릭 시 리스트를 변경하는 메서드를 생성한다.&amp;quot;)
    input-info-3(&amp;quot;이러한 함수와 변수를 props 속성 형식으로 내려주어 하단 컴포넌트에서 실행한다.&amp;quot;)
end

subgraph InputComponent.js
    input-component-1(&amp;quot;텍스트 입력 칸과 입력 버튼이 존재&amp;quot;)
    input-component-2(&amp;quot;상단에서 내려준 state 참조, 설정 값을 매칭시켜, 리스트 데이터 추가&amp;quot;)
end

subgraph ListInformation.js
    list-info-1(&amp;quot;InputComponent.js 에서 입력된 텍스트를 리스팅한다.&amp;quot;)
    list-info-2(&amp;quot;입력된 데이터는 리스트의 변경을 말하며, 이를 감지하여 컴포넌트를 재 렌더링 한다.&amp;quot;)
end

index.html --&amp;gt; index.js

index.js --&amp;gt; InputInformation.js

InputInformation.js -- 핸들러 주입 --&amp;gt; InputComponent.js

InputInformation.js -- 리스트 데이터 주입 --&amp;gt; ListInformation.js&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;ListInformation.js&lt;/code&gt; 파일에는 &lt;code&gt;useEffect&lt;/code&gt; Hook 을 통하여&lt;/p&gt;
&lt;p&gt;&lt;code&gt;list&lt;/code&gt; 의 변화 시 재렌더링을 수행하게 만들며, 콘솔에 렌더링 마크를 찍었다.&lt;/p&gt;
&lt;p&gt;그런데, 이상한 것이, &lt;code&gt;ListInformation.js&lt;/code&gt; 는 리스트의 변화 시에만 재렌더링시 실시되어야 하는데,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;InputComponent.js&lt;/code&gt; 의 &lt;code&gt;input&lt;/code&gt; 태그에서 글자 입력 시 마다&lt;/p&gt;
&lt;p&gt;오히려 &lt;code&gt;ListInformation.js&lt;/code&gt; 의 컴포넌트 re-render 가 일어났다.&lt;/p&gt;
&lt;p&gt;왜 그런가 하니,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;InputInformation.js&lt;/code&gt; 컴포넌트는 입력 칸에 들어갈 정보를 &lt;code&gt;useState&lt;/code&gt; Hook 으로 관리 중이다.&lt;/p&gt;
&lt;p&gt;여기에 최적화 방법을 적용하지 않기도 했고, 컴포넌트 내부의 &lt;code&gt;state&lt;/code&gt; 변경 시, 해당 선언&lt;/p&gt;
&lt;p&gt;컴포넌트는 리렌더링이 일어나야 하기 때문에, 하위 컴포넌트인 &lt;code&gt;ListInformation.js&lt;/code&gt; 에서&lt;/p&gt;
&lt;p&gt;지속적으로 re-render 과정이 일어나는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;그런데, 절대적으로 알아야 할 사실이 있다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;사실, 컴포넌트 자체는 재 렌더링 과정으로 갔다는 것이 사실이다.&lt;/p&gt;
&lt;p&gt;그러나, React 는 정말 고도의 diffing 기술을 가지며,&lt;/p&gt;
&lt;p&gt;특정 diffing 값에만 Commit 과정을 거친다.&lt;/p&gt;
&lt;p&gt;즉, 컴포넌트 상태 변화로 인해 Fiber Architecture 가 실행하더라도,&lt;/p&gt;
&lt;p&gt;실질적으로 대부분의 컴포넌트는 이전과 별 다를 바가 없기 때문에, 리스트에 추가되는 것만 보일 뿐이다.&lt;/p&gt;
&lt;p&gt;그러나, 상태 변화로 인한 버블링 과정과 계산은 컴포넌트가 복잡 해 질 수록 최적화를 요구하게 될 것이다.&lt;/p&gt;
&lt;p&gt;즉, 데모 프로젝트에서 동작이 잘 된다 하더라도, &lt;code&gt;useEffect&lt;/code&gt;, &lt;code&gt;useCallback&lt;/code&gt; 혹은&lt;/p&gt;
&lt;p&gt;커스텀 훅을 이용하여 특정 상황에서만 Fiber Architecture 논리로 들어갈 수 있도록 노력해야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;참고로, 바로 직전 주제에서 &lt;code&gt;setState&lt;/code&gt; 로 인한 컴포넌트 재렌더링 논리를 대부분 훑어보았다가,&lt;/p&gt;
&lt;p&gt;Fiber Architecture 에 대한 내용을 알게 되었다.&lt;/p&gt;
&lt;p&gt;따라서, 나는 어찌하여 React 논리 재실행과 실질 렌더링의 차이가 이질적으로 느껴질 수 밖에 없는지 조금은 알 것 같다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;스타일을 추가해볼까?&lt;/h2&gt;
&lt;p&gt;스타일 또한 &lt;code&gt;createElement&lt;/code&gt; 의 요소로 들어갈 수 있다.&lt;/p&gt;
&lt;p&gt;이전에 리액트를 할 때, 간헐적 스타일의 경우 직접 컴포넌트에 &lt;code&gt;style={...}&lt;/code&gt; 로 넣었었다.&lt;/p&gt;
&lt;p&gt;그리고 프로그래머스에서 ThemeContext 에 대해서 다룬 적이 있는데,&lt;/p&gt;
&lt;p&gt;나는 CSS 부문에 있어 이해력이 굉장히 낮기 때문에, 허겁지겁 따라 치기에 바빴었다.&lt;/p&gt;
&lt;p&gt;즉, &lt;code&gt;createElement&lt;/code&gt; == &lt;code&gt;JSX&lt;/code&gt; 는, 스타일 요소를 객체로 받아 적용한다는 것이다.&lt;/p&gt;
&lt;p&gt;이에 대한 경험으로, 스타일 컨텍스트를 분리한다는 느낌으로 디렉토리를 만들고,&lt;/p&gt;
&lt;p&gt;내부 파일을 작성했다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;현재 디렉토리 상황&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;.
├── components
│   ├── InputComponent.js
│   ├── InputInformation.js
│   └── ListInformation.js
├── index.html
├── index.js
└── styles
    ├── InputComponent.css.js
    └── ListInformation.css.js&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이렇게 &lt;code&gt;styles&lt;/code&gt; 로 일단은 분리시켜 놓았다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;참고로 나는 CSS 를 정말 못한다. 즉, 스타일링 기법을 거의 모른다.&lt;/p&gt;
&lt;p&gt;디자인이 정말 나빠도 예시로 봐주길 바랍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;screen-4.png&quot; data-origin-width=&quot;1190&quot; data-origin-height=&quot;532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rG3mP/btsPljBDs6j/Ziocz29CrwVNQK8gOFYmkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rG3mP/btsPljBDs6j/Ziocz29CrwVNQK8gOFYmkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rG3mP/btsPljBDs6j/Ziocz29CrwVNQK8gOFYmkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrG3mP%2FbtsPljBDs6j%2FZiocz29CrwVNQK8gOFYmkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1190&quot; height=&quot;532&quot; data-filename=&quot;screen-4.png&quot; data-origin-width=&quot;1190&quot; data-origin-height=&quot;532&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;JavaScript 에서 CSS 적용 객체 만들기&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;index.js&lt;/code&gt; 는 그대로&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;InputComponent.css.js&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;export const InputComponentCss = {
  display : &amp;quot;flex&amp;quot;,
  width : &amp;quot;40rem&amp;quot;,
  padding: &amp;quot;2rem&amp;quot;,
  border : &amp;quot;2px solid blue&amp;quot;,
  borderRadius : &amp;quot;2rem&amp;quot;,
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;ListInformation.css.js&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;export const ListInformationCss = {
  width : &amp;quot;40rem&amp;quot;,
  backgroundColor : &amp;quot;#eee&amp;quot;,
  fontSize : &amp;quot;1.5rem&amp;quot;,
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;CSS 객체를 적용한 파일들의 모습&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;InputInformationjs&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import { useState, createElement } from &amp;quot;react&amp;quot;;
import InputComponent from &amp;quot;./InputComponent.js&amp;quot;;
import ListInformation from &amp;quot;./ListInformation.js&amp;quot;;
import { InputComponentCss } from &amp;quot;../styles/InputComponent.css.js&amp;quot;;
import { ListInformationCss } from &amp;quot;../styles/ListInformation.css.js&amp;quot;;

export default function InputInformation() {
  const [text, setText] = useState(&amp;#39;&amp;#39;);
  const [list, setList] = useState([]);

  const onClickInsert = () =&amp;gt; {
    const newList = list.concat(text);

    console.log(newList);

    setList(newList);
  }

  return createElement(
    &amp;quot;div&amp;quot;,
    null,
    createElement(
      InputComponent,
      {
        text : text,
        setText : setText,
        onClickInsert : onClickInsert,
        style : InputComponentCss
      },
    ),
    createElement(
      ListInformation,
      {
        list : list,
        style : ListInformationCss
      }
    )
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;InputComponent.js&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import { createElement } from &amp;quot;react&amp;quot;;

export default function InputComponent ({text, setText, onClickInsert, style}) {

  function onClickIns() {
    onClickInsert(text);
    setText(&amp;quot;&amp;quot;);
  }

  return createElement(
    &amp;quot;div&amp;quot;,
    {style : style},
    createElement(
      &amp;quot;input&amp;quot;,
      {
        onChange: (e) =&amp;gt; { setText(e.currentTarget.value); },
        value : text,
        style : {
          width : &amp;quot;80%&amp;quot;,
          height : &amp;quot;3rem&amp;quot;,
          fontSize : &amp;quot;2rem&amp;quot;,
          border : &amp;quot;3px solid gray&amp;quot;,
          borderRadius : &amp;quot;0.5rem&amp;quot;,
        }
      },
    ),
    createElement(
      &amp;quot;button&amp;quot;,
      {
        onClick : () =&amp;gt; onClickIns(),
        style : {
          width : &amp;quot;20%&amp;quot;,
          height : &amp;quot;3rem&amp;quot;,
          fontSize : &amp;quot;2rem&amp;quot;,
          marginLeft : &amp;quot;1rem&amp;quot;
        }
      },
      &amp;quot;+&amp;quot;
    )
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;ListInformation.js&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import { createElement, useEffect } from &amp;quot;react&amp;quot;

export default function ListInformation ({list, style}) {

  useEffect(() =&amp;gt; {
    listing(list);
  }, [list]);

  const listing = (list) =&amp;gt; {
    console.log(&amp;quot;listing start&amp;quot;);

    const arr = list.map((text, idx) =&amp;gt; {
      return createElement(
        &amp;quot;li&amp;quot;,
        {
          id : idx,
          style : {
            margin : &amp;quot;1rem&amp;quot;,
            padding : &amp;quot;1rem&amp;quot;,
            background : &amp;quot;#789&amp;quot;,
            boxShadow : &amp;quot;10px 5px 5px gray&amp;quot;,
            borderRadius : &amp;quot;0.5rem&amp;quot;
          }
        },
        text
      )
    })

    return arr;
  }

  return createElement(
    &amp;quot;ul&amp;quot;,
    {
      style : style,
    },
    listing(list)
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;지금와서 보면, 오히려 상위 컴포넌트의 css 객체를 만들 것이 아니라,&lt;/p&gt;
&lt;p&gt;하위 객체들에 적용할 css 객체를 만들어야 했다는 생각이 든다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이는 마치 CSS-in-JS 방식으로,&lt;/p&gt;
&lt;p&gt;React 내부의 ReactCssProperties 를 사용한 것과 비슷하다.&lt;/p&gt;
&lt;p&gt;즉, 나중에 React 의 &lt;code&gt;Context&lt;/code&gt; 기능을 이용하여,&lt;/p&gt;
&lt;p&gt;컨텍스트 컴포넌트 하위에 존재하는 컴포넌트들이 원할 때 적용할 수 있는 객체를 만들 생각이다.&lt;/p&gt;
&lt;p&gt;물론, 이 글은 React 와 TypeScript 를 적용해 가는 과정에 있으므로, 그건 나중에 다루자.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;JSX 적용하기&lt;/h2&gt;
&lt;p&gt;나의 리액트 프로젝트는 아주 간단하고 &amp;quot;조악한&amp;quot; ㅠㅠ 예제를 보여주었다.&lt;/p&gt;
&lt;p&gt;즉, JSX 없이, 프로젝트 세팅 없이도, 기본적인 조합으로 리액트 기능을 이용 할 수 있다는 것이다.&lt;/p&gt;
&lt;p&gt;그러나, 리액트의 가장 큰 장점은, JavaScript 코드와 HTML 태그를 동시에 사용하는&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;JSX&amp;quot;&lt;/strong&gt; 기능에 있다.&lt;/p&gt;
&lt;p&gt;내가 위에서 &lt;code&gt;createElement&lt;/code&gt; 를 &lt;code&gt;react&lt;/code&gt; 라이브러리에서 가져왔던 것 처럼,&lt;/p&gt;
&lt;p&gt;JSX 방식의 표현은 &lt;code&gt;createElement&lt;/code&gt; 를 대신 해 주는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;따라서, JSX 파일을 적용하는 방식에 대해서 구상해 봐야 한다.&lt;/p&gt;
&lt;p&gt;현재 내가 드는 생각은,&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 사용자 브라우저에 JSX 파싱 기능 추가&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이건 정말 개발용이라는 생각에 가깝고, JSX 파싱 연산을 클라이언트에게 추가하는 것과 동일하다.&lt;/p&gt;
&lt;p&gt;보통 우리가 리액트 프로젝트를 빌드하고, &lt;code&gt;index.html&lt;/code&gt; 을 본다면,&lt;/p&gt;
&lt;p&gt;단순한 Chunk(청크) 파일 &lt;code&gt;xxx.js&lt;/code&gt;, &lt;code&gt;xxx.css&lt;/code&gt; 로 입력 된 것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;그러니까, 브라우저 사용자는 JSX 파일을 컴파일(파싱) 하지 않고, 곧바로 실행하여 인터랙션 하는 것이다.&lt;/p&gt;
&lt;p&gt;그러나, 만약 브라우저에서 JSX 파싱을 실행 할 경우, 사용자는 컴포넌트의 렌더링 시 마다,&lt;/p&gt;
&lt;p&gt;컴파일 과정을 거쳐야 된다는 이야기이다. (매우 무서운 사실)&lt;/p&gt;
&lt;p&gt;따라서, 이는 테스팅이나 매우 간단한 미니 프로젝트가 아닌 이상, 이 방식은 채택되어선 안된다고 생각했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;2. 이제 npm 을 이용하여 프로젝트를 만들고, Babel 을 이용하여 파싱 결과물을 이용하자&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;JSX 은 XML 기반의 마크업 문서를 의미한다. (JS + XML)&lt;/p&gt;
&lt;p&gt;JSX 는 리액트를 원활히 사용하기 위해, Facebook 이 만든 확장자 파일이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;참조 문서&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://en.wikipedia.org/wiki/JSX_(JavaScript)&quot;&gt;https://en.wikipedia.org/wiki/JSX_(JavaScript)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;참고로, 리액트 기본 프로젝트 세팅 시, React 전용 Babel 을 따로 내부적으로 설치한다.&lt;/p&gt;
&lt;p&gt;일단 Babel 말고도 다른 폴리필 프로그램이 있을지는 모르나,&lt;/p&gt;
&lt;p&gt;Babel 은 가장 대표적으로 널리 사용되는 폴리필 프로그램이라고 할 수 있다.&lt;/p&gt;
&lt;p&gt;즉, 2015 년 부터 적용된 JS 스펙 - (es2015 버전) 이후에 도입된 기능들은 당연히&lt;/p&gt;
&lt;p&gt;현대적인 브라우저에서 지원한다.&lt;/p&gt;
&lt;p&gt;그러나, Babel 은 그 이전 버전 또한 호환하기 위해 버전을 낮추는 기능을 제공하며,&lt;/p&gt;
&lt;p&gt;추가적인 기능으로 리액트 JSX 를 JavaScript 로 변환시켜주는 기능 또한 탑재했다.&lt;/p&gt;
&lt;p&gt;물론, 이는 따로 모듈로 설치해야 할 부분이기는 하다.&lt;/p&gt;
&lt;p&gt;따라서, 현재 프로젝트 내부의 js 부분을 모두 삭제한다.&lt;/p&gt;
&lt;p&gt;그리고, 프로젝트를 npm 프로젝트로 변화시킨다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;프로젝트 상황&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;.
└── index.html

1 directory, 1 file&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;기존의 &lt;code&gt;index.html&lt;/code&gt; 은 놔둔다.&lt;/p&gt;
&lt;p&gt;그렇다면, 기존의 &lt;code&gt;&amp;lt;script type=&amp;quot;importmap&amp;quot;&amp;gt;...&amp;lt;/script&amp;gt;&lt;/code&gt; 을 놔두는 이유는,&lt;/p&gt;
&lt;p&gt;웹팩과 같은 번들러가 사용될 의존성을 나의 코드와 함께 청크로 내보내지 않기 때문이다.&lt;/p&gt;
&lt;p&gt;이제 프로젝트 루트에서 이러한 명령어를 실행한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ npm init -y
Wrote to /../&amp;lt;프로젝트 루트&amp;gt;/package.json:

{
  &amp;quot;name&amp;quot;: &amp;quot;5-react-project&amp;quot;,
  &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
  &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,
  &amp;quot;main&amp;quot;: &amp;quot;index.js&amp;quot;,
  &amp;quot;scripts&amp;quot;: {
    &amp;quot;test&amp;quot;: &amp;quot;echo \&amp;quot;Error: no test specified\&amp;quot; &amp;amp;&amp;amp; exit 1&amp;quot;
  },
  &amp;quot;keywords&amp;quot;: [],
  &amp;quot;author&amp;quot;: &amp;quot;&amp;quot;,
  &amp;quot;license&amp;quot;: &amp;quot;ISC&amp;quot;,
  &amp;quot;type&amp;quot;: &amp;quot;commonjs&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 프로젝트에서 개발 시 필요한 의존성들을 기억하고 설정할 기본 파일 &lt;code&gt;package.json&lt;/code&gt; 이&lt;/p&gt;
&lt;p&gt;생성 및 초기화 되었다.&lt;/p&gt;
&lt;p&gt;이제 필요한 기본 의존성을 다운로드 하자 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ npm i react react-dom&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;package.json&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  ...,
  &amp;quot;dependencies&amp;quot;: {
    &amp;quot;react&amp;quot;: &amp;quot;^19.1.0&amp;quot;,
    &amp;quot;react-dom&amp;quot;: &amp;quot;^19.1.0&amp;quot;
  },
  ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그리고, &lt;code&gt;package-lock.json&lt;/code&gt; 이라는 의존성 세부정보 파일 또한 생겨났을 것이다.&lt;/p&gt;
&lt;p&gt;명심해야 할 것은, 아직 &lt;code&gt;index.html&lt;/code&gt; 에서 &lt;code&gt;index.js&lt;/code&gt; 만을 진입점으로 두고 있다는 점이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;index.jsx 생성하기&lt;/h3&gt;
&lt;p&gt;폴더 &lt;code&gt;src&lt;/code&gt; 를 생성 한 후, 내부에 새로운 파일 &lt;code&gt;index.jsx&lt;/code&gt; 도 생성하자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import { createRoot } from &amp;quot;react-dom/client&amp;quot;;

const root = document.getElementById(&amp;quot;root&amp;quot;);

const RootReact = createRoot(root);

RootReact.render(
  &amp;lt;div&amp;gt;Start With NPM With React and Babel!!!!!&amp;lt;/div&amp;gt;
)&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;.jsx&lt;/code&gt; 확장자 파일을 &lt;code&gt;.js&lt;/code&gt; 로 변환시키기 위해서는 Babel 플러그인이 필요하다.&lt;/p&gt;
&lt;h3&gt;Babel 플러그인 설치하기&lt;/h3&gt;
&lt;p&gt;기본적인 JSX 파일을 변환하기 위해서는 Babel 플러그인이 필요하다.&lt;/p&gt;
&lt;p&gt;그래서 &lt;code&gt;babel&lt;/code&gt; 은 무엇을 하는 것일까?&lt;/p&gt;
&lt;p&gt;npm 에는 정말 상상 못할 정도의 라이브러리를 지녔으며, 또 각각의 목적을 가지고 있다.&lt;/p&gt;
&lt;p&gt;단순히 프로젝트 어플리케이션이 사용하는 의존성들을 가졌다고 생각한다면 오산이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;babel&lt;/code&gt; 은, 현재 우리가 생성한 프로젝트 코드베이스 EX - &lt;code&gt;src&lt;/code&gt; 와 같은 폴더를&lt;/p&gt;
&lt;p&gt;통째로 가져다가 컴파일한다.&lt;/p&gt;
&lt;p&gt;즉, 2015년 직후 새로워진 JavaScript 와 생성된 기능들을 그 이전으로 폴리필(호환) 하도록&lt;/p&gt;
&lt;p&gt;만들 필요가 있었다.&lt;/p&gt;
&lt;p&gt;따라서, 화살표 함수라던지, Async Await 조합 등등..&lt;/p&gt;
&lt;p&gt;이러한 것들은 2015년 이후에 생성된 문법과 기능이다.&lt;/p&gt;
&lt;p&gt;따라서, Babel 은 &amp;quot;기초적으로는&amp;quot; 이러한 기능들이 옛날 버전과도 호환되도록,&lt;/p&gt;
&lt;p&gt;&amp;quot;Polyfill&amp;quot; 이라는 기능을 가지고 있는 것이다.&lt;/p&gt;
&lt;p&gt;그런데, 가장 대표적인 기능으로 React 의 JSX 를 컴파일하는 기능이 있다.&lt;/p&gt;
&lt;p&gt;그 뿐만 아니라, StyledComponent 와 같은 독특한 문법을 필요로 하는 경우,&lt;/p&gt;
&lt;p&gt;바벨의 preset 을 이용하여 문법 하이라이팅을 &amp;quot;보조&amp;quot; 할 수도 있다.&lt;/p&gt;
&lt;p&gt;일단, 우리가 사용하게 될 Babel 의 기능은,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@babel/preset-react&lt;/code&gt; 이다.&lt;/p&gt;
&lt;p&gt;JSX 를 위한 문법 하이라이팅부터, JSX to JS 파싱까지 도맡아 하는 모듈이다.&lt;/p&gt;
&lt;p&gt;그리고, 바벨 사용 시 꼭 필요한 모듈 &lt;code&gt;@babel/core&lt;/code&gt; 과 &lt;code&gt;@babel/cli&lt;/code&gt; 가 자동으로 설치된다.&lt;/p&gt;
&lt;p&gt;뿐만 아니라, React 를 위한 다양한 하위 모듈들이 추가로 설치된다.&lt;/p&gt;
&lt;p&gt;확인하고 싶다면, &lt;code&gt;package-lock.json&lt;/code&gt; 을 보면 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;# 개발용으로 설치 옵션 -D or --save-dev
$ npm i -D @babel/core @babel/cli @babel/preset-env @babel/preset-react,&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이제, 추후 사용할 Babel CLI(모듈 내부를 사용) 를 위해,&lt;/p&gt;
&lt;p&gt;바벨 설정 파일 &lt;code&gt;babel.config.json&lt;/code&gt; 을 루트에 생성하자.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;root&amp;gt;.config.json&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;presets&amp;quot; : [
    &amp;quot;@babel/preset-react&amp;quot;,
  ],
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;Babel 명령어로 브라우저 실행 가능 파일 만들기&lt;/h3&gt;
&lt;p&gt;현재 우리의 디렉토리 현황은,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;&amp;lt;Project Root&amp;gt;
├── babel.config.json
├── index.html
├── node_modules
├── package-lock.json
├── package.json
└── src
    └── index.jsx&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이렇게 구성되어 있는 상황이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;Babel 로 &lt;code&gt;src&lt;/code&gt; 디렉토리 내부를 파싱하려면,&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2 가지 방식이 존재하는데, 모두 알아야 할 만큼 중요하다&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;./node_modules/.bin/babel&lt;/code&gt; 위치를 이용하여 바벨 실행하기&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;주로 명령어를 이용하여 특정 파일을 리딩하거나, 파싱하는 프로그램의 특성상,&lt;/p&gt;
&lt;p&gt;모듈 어디엔가 CLI 파일이 존재한다.&lt;/p&gt;
&lt;p&gt;이러한 CLI 파일은, 해당 모듈을 대표하는 사이트에서 제공하므로, 잘 찾아봐야 한다.&lt;/p&gt;
&lt;p&gt;즉, 바벨의 명령어 실행을 위해서, &lt;code&gt;@babel/cli&lt;/code&gt; 는 필요한 모듈이라는 의미이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;아니면 코드로 직접 바벨을 불러와서 파싱하는 방법도 있음 --&amp;gt; 드문 경우&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;br/&gt;

&lt;p&gt;현재 디렉토리 루트에서 명령어 탭을 열고,&lt;/p&gt;
&lt;p&gt;이러한 명령어를 입력한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ ./node_modules/.bin/babel src --out-dir dist

Successfully compiled 1 file with Babel (149ms).&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;code&gt;npx&lt;/code&gt; 를 이용하여 바벨 실행하기&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;NPX 는 NPM 모듈을 CLI 로 쉽게 실행하거나, 특정 명령어의 모음을 쉽게 실행하게 해 주는&lt;/p&gt;
&lt;p&gt;NPM 의 프로그램이다.&lt;/p&gt;
&lt;p&gt;개발 장소인 로컬 컴퓨터에서는 물론, npx 로 바벨을 실행 할 수 있지만,&lt;/p&gt;
&lt;p&gt;만약 바벨 실행 장소가 클라우드 서버와 같은 경우, &lt;code&gt;npx&lt;/code&gt; 실행을 위해 &lt;code&gt;npm&lt;/code&gt; 을 설치 해 주어야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;훨씬 더 압축된 형태로 명령어 실행이 가능하다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ npx babel src --out-dir dist

Successfully compiled 1 file with Babel (143ms).&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;이제 바벨로 컴파일 된 파일을 까보자!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;root&amp;gt;/dist/index.js&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import { createRoot } from &amp;quot;react-dom/client&amp;quot;;
const root = document.getElementById(&amp;quot;root&amp;quot;);
const RootReact = createRoot(root);
RootReact.render(/*#__PURE__*/React.createElement(&amp;quot;div&amp;quot;, null, &amp;quot;Start With NPM With React and Babel!!!!!&amp;quot;));&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;초창기에 이 글에서 &lt;code&gt;createElement&lt;/code&gt; 를 이용하여 리액트 컴포넌트를 제작했던 것과 완전 동일하게,&lt;/p&gt;
&lt;p&gt;Babel 은 &lt;code&gt;createElement&lt;/code&gt; 로 JSX 파일을 컴파일한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;JSX --&amp;gt; JavaScript 브라우저 실행 가능 확인하기&lt;/h3&gt;
&lt;p&gt;먼저, &lt;code&gt;index.html&lt;/code&gt; 에서 한 줄을 변경해야 한다.&lt;/p&gt;
&lt;p&gt;우리의 진입 파일은 &lt;code&gt;./index.js&lt;/code&gt; 가 아니라, &lt;code&gt;./dist/index.js&lt;/code&gt; 이다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
        &amp;lt;title&amp;gt;처음부터 만드는 TS 리액트 프로젝트&amp;lt;/title&amp;gt;
        &amp;lt;script type=&amp;quot;importmap&amp;quot;&amp;gt;
          {
            &amp;quot;imports&amp;quot;: {
              &amp;quot;react&amp;quot;: &amp;quot;https://esm.sh/react@18&amp;quot;,
              &amp;quot;react-dom/client&amp;quot;: &amp;quot;https://esm.sh/react-dom@18/client&amp;quot;
            }
          }
          &amp;lt;/script&amp;gt;
        &amp;lt;!-- src 속성을 바꿈 --&amp;gt;
        &amp;lt;script type=&amp;quot;module&amp;quot; src=&amp;quot;./dist/index.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;div id=&amp;quot;root&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;결과&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;screen-5.png&quot; data-origin-width=&quot;415&quot; data-origin-height=&quot;81&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XC0kk/btsPkwuWCok/LoqmKsnr8TOjTeIZfdw8x0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XC0kk/btsPkwuWCok/LoqmKsnr8TOjTeIZfdw8x0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XC0kk/btsPkwuWCok/LoqmKsnr8TOjTeIZfdw8x0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXC0kk%2FbtsPkwuWCok%2FLoqmKsnr8TOjTeIZfdw8x0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;415&quot; height=&quot;81&quot; data-filename=&quot;screen-5.png&quot; data-origin-width=&quot;415&quot; data-origin-height=&quot;81&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;렌더링 되지 않은 상태를 보여주었다.&lt;/p&gt;
&lt;p&gt;그렇다면 어디가 문제였을까?&lt;/p&gt;
&lt;p&gt;&lt;code&gt;index.js&lt;/code&gt; 파일을 다시 보자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import { createRoot } from &amp;quot;react-dom/client&amp;quot;;
const root = document.getElementById(&amp;quot;root&amp;quot;);
const RootReact = createRoot(root);
RootReact.render(/*#__PURE__*/React.createElement(&amp;quot;div&amp;quot;, null, &amp;quot;Start With NPM With React and Babel!!!!!&amp;quot;));&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 코드에서, &lt;code&gt;React&lt;/code&gt; 가 문제가 난 것이다.&lt;/p&gt;
&lt;p&gt;즉, 이 파일에서 &lt;code&gt;React&lt;/code&gt; 를 정의한 적도, 가져 온 적도 없다는 의미이다.&lt;/p&gt;
&lt;p&gt;따라서, &lt;code&gt;index.jsx&lt;/code&gt; 에 이러한 코드를 맨 위에 추가해야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;index.jsx&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import * as React from &amp;quot;react&amp;quot;;
import { createRoot } from &amp;quot;react-dom/client&amp;quot;;
const root = document.getElementById(&amp;quot;root&amp;quot;);
const RootReact = createRoot(root);
RootReact.render(/*#__PURE__*/React.createElement(&amp;quot;div&amp;quot;, null, &amp;quot;Start With NPM With React and Babel!!!!!&amp;quot;));&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;그리고, 바벨을 실행 한 후, 서버를 다시 열어 보면,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;screen-6.png&quot; data-origin-width=&quot;234&quot; data-origin-height=&quot;30&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lvVq9/btsPk3y0GIP/1kH8O8ocGDpCzb2crInQL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lvVq9/btsPk3y0GIP/1kH8O8ocGDpCzb2crInQL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lvVq9/btsPk3y0GIP/1kH8O8ocGDpCzb2crInQL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlvVq9%2FbtsPk3y0GIP%2F1kH8O8ocGDpCzb2crInQL0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;234&quot; height=&quot;30&quot; data-filename=&quot;screen-6.png&quot; data-origin-width=&quot;234&quot; data-origin-height=&quot;30&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;이제서야 정상적으로 렌더링이 되는 것을 볼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;바벨 명령어 통합을 위한 자그마한 작업&lt;/h2&gt;
&lt;p&gt;이 글이 누구한테 어떻게 언제 읽히게 될지 모르기에, &lt;code&gt;npx&lt;/code&gt; 를 통해 바벨을 실행하는 것이 아니라,&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;직접 명령어 스크립트를 작성하여 개발 세팅을 통합하는 것이 중요하다는 생각이 들었다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;먼저, &lt;code&gt;./node_modules/.bin/babel src --out-dir dist&lt;/code&gt; 라는 명령어를 기억하자.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그걸 그대로, &lt;code&gt;package.json&lt;/code&gt; 파일의 &lt;code&gt;&amp;quot;scripts&amp;quot;&lt;/code&gt; 부분에 추가한다.&lt;/p&gt;
&lt;p&gt;즉,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;package.json&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  // ...
  &amp;quot;main&amp;quot;: &amp;quot;index.js&amp;quot;,
  &amp;quot;scripts&amp;quot;: {
    &amp;quot;test&amp;quot;: &amp;quot;echo \&amp;quot;Error: no test specified\&amp;quot; &amp;amp;&amp;amp; exit 1&amp;quot;,
    // 추가 된 부분
    &amp;quot;build&amp;quot;: &amp;quot;./node_modules/.bin/babel src --out-dir dist&amp;quot;
  },
  &amp;quot;keywords&amp;quot;: [],
  &amp;quot;author&amp;quot;: &amp;quot;&amp;quot;,
  &amp;quot;license&amp;quot;: &amp;quot;ISC&amp;quot;,
  &amp;quot;type&amp;quot;: &amp;quot;commonjs&amp;quot;,
  &amp;quot;dependencies&amp;quot;: {
    // ...
  },
  &amp;quot;devDependencies&amp;quot;: {
    // ...
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;위 처럼 &lt;code&gt;package.json&lt;/code&gt; 을 적용 해 주고 나서,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;npm run build&lt;/code&gt; 를 실행 해 보자 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ npm run build

&amp;gt; 5-react-project@1.0.0 build
&amp;gt; ./node_modules/.bin/babel src --out-dir dist

Successfully compiled 1 file with Babel (141ms).&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이러한 방식으로 우리는&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;npx babel src --out-dir dist&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;에서,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;npm run build&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;만 입력하면 바벨이 &lt;code&gt;src&lt;/code&gt; 폴더를 파싱하여 &lt;code&gt;dist&lt;/code&gt; 로 만들어 준다.&lt;/p&gt;
&lt;p&gt;더욱 간편해 졌다!&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;현재까지의 진행 요약과 아직 부족한 점.&lt;/h3&gt;
&lt;p&gt;현재 디렉토리는&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;.
├── babel.config.json
├── dist
│   └── index.js
├── index.html
├── node_modules
│   └──... 수많은 모듈들
├── package-lock.json
├── package.json
└── src
    └── index.jsx

79 directories, 6 files&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이러한 상황이다.&lt;/p&gt;
&lt;p&gt;즉, 디렉토리를 읽고, 이렇게 해석 할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;1. npm 을 이용한 웹 앱 프로젝트이다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;index.html&lt;/code&gt; 의 존재와, &lt;code&gt;package.json&lt;/code&gt; 의 동시 존재는&lt;/p&gt;
&lt;p&gt;npm 의 모듈 의존성을 이용하여 현재 웹 앱을 개발중이라는 것을 파악할 수 있게 해 준다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;2. Babel 을 이용하여 프로젝트를 빌드하고 있다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;프로젝트 내부에는 &lt;code&gt;babel.config.json&lt;/code&gt; 이 있다.&lt;/p&gt;
&lt;p&gt;이는, Babel 이 어떠한 방식이던 실행되어 컴파일을 실행한다면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;babel.config.json&lt;/code&gt; 옵션에 따라 컴파일을 수행한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;3. src 폴더의 컴파일 결과는 dist 에 해당한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이건 개발 컨벤션으로서, 특정 라이브러리를 결과물로 만들고자 했다면, &lt;code&gt;lib&lt;/code&gt; 가 될 것이고,&lt;/p&gt;
&lt;p&gt;특정 상황에서의 결과물 폴더는 각자 다른 이름을 가진다.&lt;/p&gt;
&lt;p&gt;그러나, 보통 대부분 결과물 파일의 이름은 &lt;code&gt;dist&lt;/code&gt; 를 가진다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;4. 이 웹 앱은 프로덕션 or 그냥 실행 시 dist 폴더의 소스를 통해 실행한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;index.html&lt;/code&gt; 과, &lt;code&gt;dist&lt;/code&gt; 폴더가 공존 할 때 우리는&lt;/p&gt;
&lt;p&gt;웹 앱의 결과물(dist) 가, &lt;code&gt;index.html&lt;/code&gt; 에서 실행된다고 판단한다.&lt;/p&gt;
&lt;p&gt;모든 웹 앱, 즉, 브라우저는 &lt;code&gt;html&lt;/code&gt; 파일을 통해 리소스를 로드하고 순서에 맞게 실행한다.&lt;/p&gt;
&lt;p&gt;우리가 웹 개발의 편의성으로 인해 잊을 수 있지만, 항상 &lt;code&gt;html&lt;/code&gt; 을 통해&lt;/p&gt;
&lt;p&gt;브라우저 내에서 클라이언트에게 서비스를 제공할 수 있다는 점을 기억해야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;부족한 점은 무엇일까?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;나는 프로젝트를 Babel CLI 로 빌드하고, &lt;code&gt;index.html&lt;/code&gt; 에서&lt;/p&gt;
&lt;p&gt;&lt;code&gt;index.js&lt;/code&gt; 를 실행하는 과정에서 1 가지 문제점이 발생했다.&lt;/p&gt;
&lt;p&gt;바로, &lt;code&gt;React&lt;/code&gt; 는 현재 프로그램에 존재하지 않는다는 것이었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;나는 이를 &lt;code&gt;index.jsx&lt;/code&gt; 맨 위쪽에 &lt;code&gt;import * as React from &amp;quot;react&amp;quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;를 선언하는 것으로 해결했다.&lt;/p&gt;
&lt;p&gt;그러나, &amp;quot;모든 jsx 파일&amp;quot; 상단에 이러한 React 를 가져오는 구문은 옛날 방식이다.&lt;/p&gt;
&lt;p&gt;실제로, 몇 년 전에는 항상 &lt;code&gt;import * as React from &amp;quot;react&amp;quot;&lt;/code&gt; 이런 식으로&lt;/p&gt;
&lt;p&gt;상단에 리액트를 가져와야만 에러가 나지 않았었다.&lt;/p&gt;
&lt;p&gt;이제야 그 이유를 알것 같다..&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://babeljs.io/docs/babel-preset-react#options&quot;&gt;@babel/preset-react 옵션 세팅 방법&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;먼저, 설정한 Babel 의 옵션을 다시 보자&lt;/p&gt;
&lt;p&gt;&lt;code&gt;babel.config.json&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;presets&amp;quot;: [
    [
      &amp;quot;@babel/preset-react&amp;quot;
    ]
  ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;추가 옵션을 위해 배열을 내부에 하나 더 넣은 상황. 곧 알게됨.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;현재 설정은 &lt;code&gt;@babel/preset-react&lt;/code&gt; 로, 기본 옵션을 채택하고 있다.&lt;/p&gt;
&lt;p&gt;만약에 Babel 8 이상의 버전이라면, default 는 &lt;code&gt;&amp;quot;runtime&amp;quot; : &amp;quot;automatic&amp;quot;&lt;/code&gt; 으로 바뀐다.&lt;/p&gt;
&lt;p&gt;그 이전이라면, &lt;code&gt;&amp;quot;runtime&amp;quot; : &amp;quot;classic&amp;quot;&lt;/code&gt; 이다.&lt;/p&gt;
&lt;p&gt;클래식의 경우, 리액트 메서드 &amp;quot;그대로&amp;quot; &lt;code&gt;React.createElement&lt;/code&gt; 로 JSX 를 파싱한다.&lt;/p&gt;
&lt;p&gt;따라서, &lt;code&gt;React&lt;/code&gt; 를 항상 파일마다 import 해 주어야 하는 것이다.&lt;/p&gt;
&lt;p&gt;하지만, 나는 현재 Babel 8 이상의 버전이 아니다.&lt;/p&gt;
&lt;p&gt;따라서, 이를 명시적으로 추가 옵션에 적어주어야 한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;babel.config.json&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;presets&amp;quot;: [
    [
      &amp;quot;@babel/preset-react&amp;quot;,
      {
        &amp;quot;runtime&amp;quot; : &amp;quot;automatic&amp;quot;
      }
    ]
  ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이후, 다시 바벨로 빌드한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ npm run build

&amp;gt; 5-react-project@1.0.0 build
&amp;gt; ./node_modules/.bin/babel src --out-dir dist

Successfully compiled 1 file with Babel (151ms).&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;index.js&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import { createRoot } from &amp;quot;react-dom/client&amp;quot;;
import { jsx as _jsx } from &amp;quot;react/jsx-runtime&amp;quot;;
const root = document.getElementById(&amp;quot;root&amp;quot;);
const RootReact = createRoot(root);
RootReact.render(/*#__PURE__*/_jsx(&amp;quot;div&amp;quot;, {
  children: &amp;quot;Start With NPM With React and Babel!!!!!&amp;quot;
}));&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;기존과 다르게, &lt;code&gt;createElement&lt;/code&gt; 메서드가 아니라, &lt;code&gt;_jsx&lt;/code&gt; 로 대체하고 있는 것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;이것이 바로 현재 최신 리액트 프로젝트에서 jsx or tsx 파일 작성 시,&lt;/p&gt;
&lt;p&gt;상단에 항상 React 를 가져올 필요가 없는 이유이다.&lt;/p&gt;
&lt;p&gt;그러나, 하나 더 추가해야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;저기 &lt;code&gt;jsx&lt;/code&gt; 메서드를 가져오는 &lt;code&gt;&amp;quot;react/jsx-runtime&amp;quot;&lt;/code&gt; 에 해당하는 라이브러리를,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;index.html&lt;/code&gt; 파일의 importmap 에 추가해야 한다.&lt;/p&gt;
&lt;p&gt;간단히 불러오는 방법이 있는데, 바로 경로를 애매하게 두는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;애매한 경로로 하위 모듈을 모두 불러오기&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
        &amp;lt;title&amp;gt;처음부터 만드는 TS 리액트 프로젝트&amp;lt;/title&amp;gt;
        &amp;lt;script type=&amp;quot;importmap&amp;quot;&amp;gt;
          {
            &amp;quot;imports&amp;quot;: {
              &amp;quot;react&amp;quot;: &amp;quot;https://esm.sh/react@18&amp;quot;,
              // 애매하게 절대경로 &amp;quot;/&amp;quot; 를 걸쳐놓은 것을 볼 수 있다.
              &amp;quot;react/&amp;quot;: &amp;quot;https://esm.sh/react@18/&amp;quot;
              &amp;quot;react-dom/client&amp;quot;: &amp;quot;https://esm.sh/react-dom@18/client&amp;quot;
            }
          }
          &amp;lt;/script&amp;gt;
        &amp;lt;script type=&amp;quot;module&amp;quot; src=&amp;quot;./dist/index.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;div id=&amp;quot;root&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;저 애매한 경로의 하위 모듈에는 jsx 를 위한 메서드를 제공할 뿐만 아니라,&lt;/p&gt;
&lt;p&gt;jsx 개발 런타임, 버전, 메타데이터를 코드로 불러올 수 있다.&lt;/p&gt;
&lt;p&gt;하지만, 현재 가장 중요한 모듈은 jsx 이다.&lt;/p&gt;
&lt;p&gt;따라서, 추가 경로를 통해 JSX + Babel (runtime : automatic) 으로,&lt;/p&gt;
&lt;p&gt;굳이 파일 상단에 &lt;code&gt;import * as React from &amp;quot;react&amp;quot;&lt;/code&gt; 할 필요 없이,&lt;/p&gt;
&lt;p&gt;곧바로 작성하기만 하면 끝이다!&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;결과는?&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;screen-7.png&quot; data-origin-width=&quot;635&quot; data-origin-height=&quot;49&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zGwmD/btsPjppOL9z/yRf3TxQWiuNH3wdNB41Se1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zGwmD/btsPjppOL9z/yRf3TxQWiuNH3wdNB41Se1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zGwmD/btsPjppOL9z/yRf3TxQWiuNH3wdNB41Se1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzGwmD%2FbtsPjppOL9z%2FyRf3TxQWiuNH3wdNB41Se1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;635&quot; height=&quot;49&quot; data-filename=&quot;screen-7.png&quot; data-origin-width=&quot;635&quot; data-origin-height=&quot;49&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;code&gt;index.jsx&lt;/code&gt; 에서 렌더링하는 태그를 &amp;quot;h2&amp;quot; 로 바꾸고, 내용을 좀 바꿔봤다!&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import { createRoot } from &amp;quot;react-dom/client&amp;quot;;

const root = document.getElementById(&amp;quot;root&amp;quot;);

const RootReact = createRoot(root);

RootReact.render(
  &amp;lt;h2&amp;gt;Babel Options (runtime : automatic) and importsmap in jsx runtime!!!!!!&amp;lt;/h2&amp;gt;
)&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;컴파일링 된 &lt;code&gt;index.js&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import { createRoot } from &amp;quot;react-dom/client&amp;quot;;
import { jsx as _jsx } from &amp;quot;react/jsx-runtime&amp;quot;;
const root = document.getElementById(&amp;quot;root&amp;quot;);
const RootReact = createRoot(root);
RootReact.render(/*#__PURE__*/_jsx(&amp;quot;h2&amp;quot;, {
  children: &amp;quot;Babel Options (runtime : automatic) and importsmap in jsx runtime!!!!!!&amp;quot;
}));&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;이러한 식으로, NPM + React + Babel + JSX 조합으로,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;index.html&lt;/code&gt; 에서 성공적으로 프로젝트를 빌드할 수 있음을 증명했다!!&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;타입스크립트 추가&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;현재까지 정말로 읽으신 분이 계시다면,&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;타입스크립트의 설정 파일인 &lt;code&gt;tsconfig.json&lt;/code&gt; 와 연관이 깊어진다는 말씀을 먼저 드리고 시작합니다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;타입스크립트란 무엇일까? - 구체적으로 생각해보기&lt;/h3&gt;
&lt;p&gt;&amp;quot;아주 정확하게&amp;quot; 말하는 타입스크립트란, &amp;quot;자바스크립트의 SuperSet&amp;quot; 이다.&lt;/p&gt;
&lt;p&gt;슈퍼셋이 뭘까? 곰곰이 생각해보면, 자바스크립트를 포함하고 있는 거대한 타입스크립트의 모습을 상상할 수 있다.&lt;/p&gt;
&lt;p&gt;그렇다면, 타입스크립트는 스스로 구동하나? --&amp;gt; 당연히 절대 아니다.&lt;/p&gt;
&lt;p&gt;그렇다면, 타입스크립트는 프로그래밍 언어이냐? --&amp;gt; 자바스크립트의 약점인 &amp;quot;타입&amp;quot; 을 강화 해 주는 언어다.&lt;/p&gt;
&lt;p&gt;또한, 타입스크립트는 타입을 &amp;quot;먼저 검사&amp;quot; 후, 자바스크립트로 컴파일하는 역할만 수행 하지 않고,&lt;/p&gt;
&lt;p&gt;데코레이터라고 부르는(EX - &lt;code&gt;@ClassDecorator(...)&lt;/code&gt;) 메타데이터 전용 기능을 사용할 수 있게 해 준다.&lt;/p&gt;
&lt;p&gt;이는 현재 NestJS 라고 부르는 Node.js 백엔드 프레임워크의 근간을 이루는 매우 중요한 기능이다.&lt;/p&gt;
&lt;p&gt;하지만, 아직 자바스크립트 정식 최신 스펙에 추가되지 않아, 이를 트랜스파일 해 주는 것이 타입스크립트이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그래서, 사전적 의미와 공식 문서를 통틀어서, 현재 진행되고 있는 이 글 자체에 어떤 의미를 가지고 있을까?&lt;/p&gt;
&lt;p&gt;바로, 발전을 거듭하며 복잡해진 모듈을 가진 React 라는 아주아주 거대한 라이브러리를,&lt;/p&gt;
&lt;p&gt;개발자에게 있어 편리한 기능을 &amp;quot;유도&amp;quot; 하는 역할을 한다.&lt;/p&gt;
&lt;p&gt;이게 뭔 말인가? 싶긴 하지만, 생각해 보면, 리액트는 이러한 템플릿으로 주로 움직인다.&lt;/p&gt;
&lt;p&gt;(물론 내가 응용은 잘 못하지만, React Fiber Architecture 를 까보면서 알게 되었다.)&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;리액트가 가지고 있는 흔한 패턴이란?&lt;/h3&gt;
&lt;p&gt;위에서 잠깐 TypeScript 가 가지고 있는 일반적 정의와,&lt;/p&gt;
&lt;p&gt;아주 일부의 편리성 기능만을 언급했다.&lt;/p&gt;
&lt;p&gt;그리고, 나는 &amp;quot;타입을 강화시켜주는&amp;quot; 타입스크립트가,&lt;/p&gt;
&lt;p&gt;오히려 리액트의 편리한 기능으로 &amp;quot;유도&amp;quot; 한다고 언급했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;리액트에는 &lt;code&gt;react&lt;/code&gt;, &lt;code&gt;react-dom&lt;/code&gt;, &lt;code&gt;jsx&lt;/code&gt; 라는 JavaScript React 기본 모듈이 존재한다.&lt;/p&gt;
&lt;p&gt;그러나, 혹시라도 리액트를 사용 해 본 분이라면, 아마, 타입스크립트로 템플릿이 이루어진&lt;/p&gt;
&lt;p&gt;리액트 초기 프로젝트보다 훨씬 더 고려해야 할 사항이 많다는 것을 느꼈을 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그 이유는, 타입스크립트를 적용하면, &amp;quot;타입 유도 기능&amp;quot; 이 엄청난 역할을 한다.&lt;/p&gt;
&lt;p&gt;만약에 자바스크립트로 작성한다면, 사용자가 원하는 유도 메서드를 알려주는 기능이 별로 없다.&lt;/p&gt;
&lt;p&gt;하지만, 타입스크립트와 그 설정 파일 &lt;code&gt;tsconfig.json&lt;/code&gt; 의 옵션들을 사용한다면,&lt;/p&gt;
&lt;p&gt;사용자가&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;useState&lt;/code&gt; --&amp;gt; 컴포넌트가 책임지는 스스로의 state&lt;/li&gt;
&lt;li&gt;&lt;code&gt;useEffect&lt;/code&gt; --&amp;gt; 컴포넌트가 re-render 되려면, 어떤 state 가 바뀌었을 때인지.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;useContext&lt;/code&gt; --&amp;gt; 컨텍스트 컴포넌트를 지정하고, 사용할 &amp;quot;지역적 전역 state&amp;quot; 를 지정한다.&lt;ul&gt;
&lt;li&gt;[v] 여기서 말하는 지역적 컨텍스트란, &lt;br/&gt; 리액트 프로젝트 전체에서 관리하는 전역 상태가 아니라, &lt;br/&gt; 컨텍스트를 관리하는 컴포넌트 내부의 &lt;code&gt;children&lt;/code&gt; 이 곧바로 접근할 수 있게 만드는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;타입스크립트는 이 세 가지 패턴을 가지고도, 아주 복잡한 프로젝트를 생성할 수 있다.&lt;/p&gt;
&lt;p&gt;그러나, 타입스크립트는 &amp;quot;자동 완성 기능&amp;quot; 뿐만 아니라, 사용된 메서드의 사용법까지 유도한다. (경고문)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;따라서, 현대 세상에서 빠른 제작과 구현이 필수 덕목이라면, 타입스크립트와 리액트의 조합은&lt;/p&gt;
&lt;p&gt;꼭 필요한 조합이 되었다고 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;뿐만 아니라, 리액트의 외부 모듈 호환성 덕분에, 리액트의 모듈은 아니지만, 연관 모듈의 import 가&lt;/p&gt;
&lt;p&gt;매우 간편해졌다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;이제 기존 프로젝트에서 타입스크립트를 적용 해 보자.&lt;/h2&gt;
&lt;p&gt;먼저, npm 개발 의존성으로 &lt;code&gt;@babel/preset-typescript&lt;/code&gt; 를 추가한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ npm i -D @babel/preset-typescript&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;babel.config.json&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;presets&amp;quot;: [
    [
      &amp;quot;@babel/preset-react&amp;quot;,
      {
        &amp;quot;runtime&amp;quot; : &amp;quot;automatic&amp;quot;
      }
    ],
    [
      &amp;quot;@babel/preset-typescript&amp;quot;,
      {
        &amp;quot;isTSX&amp;quot; : true,
        &amp;quot;allExtensions&amp;quot; : true
      }
    ]
  ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;지금 바로, &lt;code&gt;index.tsx&lt;/code&gt; 로 변환해서 트랜스파일링 할 수 있지만,&lt;/p&gt;
&lt;p&gt;타입스크립트 파일을 사용할 수 있는 것은 아니다.&lt;/p&gt;
&lt;p&gt;바벨은 트랜스파일링을 위한 도구이며, 타입스크립트는 프로젝트에서 &lt;code&gt;.ts&lt;/code&gt;, &lt;code&gt;.d.ts&lt;/code&gt;, &lt;code&gt;tsx&lt;/code&gt; 등등&lt;/p&gt;
&lt;p&gt;타입스크립트와 관련된 파일을 사용하기 위한 기초적인 개발 프로그램이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;타입스크립트를 위한 준비&lt;/h3&gt;
&lt;p&gt;먼저, &lt;code&gt;typescript&lt;/code&gt; 로 전역 명령어로 준비한 사람과,&lt;/p&gt;
&lt;p&gt;그렇지 않고 개발 파일로 타입스크립트를 컴파일 하는 사람이 존재 할 것이다.&lt;/p&gt;
&lt;p&gt;그렇다면, &lt;code&gt;tsconfig.json&lt;/code&gt; 을 만들어야 하는 것은 동일한데, 어떻게 초기화 해야 할까?&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;1. typescript 가 전역 CLI 로 설치되어 있을 경우.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이럴 때는 쉽게 &lt;code&gt;tsc --init&lt;/code&gt; 으로 생성하면 된다.&lt;/p&gt;
&lt;p&gt;생성된 &lt;code&gt;tsconfig.json&lt;/code&gt; 파일 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;compilerOptions&amp;quot;: {
    /* Language and Environment */
    &amp;quot;target&amp;quot;: &amp;quot;es2016&amp;quot;,/* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */

    /* Modules */
    &amp;quot;module&amp;quot;: &amp;quot;commonjs&amp;quot;, /* Specify what module code is generated. */

    &amp;quot;esModuleInterop&amp;quot;: true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables &amp;#39;allowSyntheticDefaultImports&amp;#39; for type compatibility. */
    &amp;quot;forceConsistentCasingInFileNames&amp;quot;: true,/* Ensure that casing is correct in imports. */

    /* Type Checking */
    &amp;quot;strict&amp;quot;: true,  /* Enable all strict type-checking options. */
    &amp;quot;skipLibCheck&amp;quot;: true   /* Skip type checking all .d.ts files. */
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;명령어로 생성된 &lt;code&gt;tsconfig.json&lt;/code&gt; 에는 정말 기본적인 내용들로만 이루어져 있으며,&lt;/p&gt;
&lt;p&gt;대부분, 혹은 모든 옵션들에 대한 내용과 함께, 주석 처리가 되어 있다.&lt;/p&gt;
&lt;p&gt;위의 json 파일은 주석 처리 옵션을 전부 삭제한 상황이다.&lt;/p&gt;
&lt;p&gt;이 CLI 명령으로 생성된 &lt;code&gt;tsconfig.json&lt;/code&gt; 옵션의 주석들을 확인하며,&lt;/p&gt;
&lt;p&gt;현재 리액트 프로젝트에 필요한 옵션들을 고르면 된다.&lt;/p&gt;
&lt;p&gt;혹은, &lt;a target=&quot;_blank&quot; href=&quot;https://ko.react.dev/learn/typescript#adding-typescript-to-an-existing-react-project&quot;&gt;타입스크립트 설정 참조 위치&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;2. 타입스크립트 전역 명령어를 따로 설치하지 않고, 개발 의존성으로 처리하는 경우&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이럴 때는, 개발 의존성으로 &lt;code&gt;tsconfig.json&lt;/code&gt; 을 생성하고,&lt;/p&gt;
&lt;p&gt;이후 &lt;code&gt;typescript&lt;/code&gt;, &lt;code&gt;@types/react&lt;/code&gt;, &lt;code&gt;@types/react-dom&lt;/code&gt; 을 설치해야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;프로젝트 루트에 생성한 아주 간단한 (아직 완성 x) &lt;code&gt;tsconfig.json&lt;/code&gt; 예시 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;compilerOptions&amp;quot;: {
    &amp;quot;target&amp;quot;: &amp;quot;ES2020&amp;quot;,
    &amp;quot;module&amp;quot;: &amp;quot;ESNext&amp;quot;,
    &amp;quot;jsx&amp;quot;: &amp;quot;preserve&amp;quot;,
    &amp;quot;lib&amp;quot;: [&amp;quot;DOM&amp;quot;],
    &amp;quot;skipLibCheck&amp;quot;: true
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;정확히, 이 프로젝트에서 &lt;code&gt;.tsx&lt;/code&gt; 파일이 인식이 되며,&lt;/p&gt;
&lt;p&gt;정상적으로 브라우저 웹 앱을 제작하기 위한 &amp;quot;최소한의&amp;quot; 기능만을 추가했다.&lt;/p&gt;
&lt;p&gt;정확히 현재 프로젝트에서 오류가 나지 않으며,&lt;/p&gt;
&lt;p&gt;바벨로 트랜스파일이 가능한 상태는 위와 같은 옵션으로 지정할 수 있다.&lt;/p&gt;
&lt;p&gt;(추후 기능을 추가하면 다른 옵션이 필요함.)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이러한 옵션들의 의미는 이러하다 :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;target&lt;/code&gt; : 출력물이 컴파일 되어 나올 때 이 버전으로 지정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;module&lt;/code&gt; : 코드 작성 시, 최신 문법을 적용할 수 있음. (리액트는 최신 문법을 적용하기 때문에 필요한편)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;jsx&lt;/code&gt; : &amp;quot;jsx&amp;quot; 혹은 &amp;quot;tsx&amp;quot; 파일을 인식하기 위한 타입스크립트 옵션&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lib&lt;/code&gt; : 브라우저 웹 앱을 만들기 위해 필요한 내부 모듈&lt;/li&gt;
&lt;li&gt;&lt;code&gt;skipLibCheck&lt;/code&gt; : 타입 체킹 시, 모듈로 다운로드 한 파일까지 타입 체크를 하지 않는다.&lt;ul&gt;
&lt;li&gt;이 옵션에 &lt;code&gt;true&lt;/code&gt; 를 설정 한 이유는, 같은 이름을 가진 정의파일이 서로 다른 타입을 가질 수 있기 때문이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;이렇게 설정하게 되면,&lt;/p&gt;
&lt;p&gt;리액트 내부에서 타입스크립트를 통한 타입 체킹과 디버깅, 편의성 기능 추가가 용이해지며,&lt;/p&gt;
&lt;p&gt;바벨을 통한 정상적인 트랜스파일링이 가능해진다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;지금 상태로 간단한 컴포넌트 작성 해 보기&lt;/h2&gt;
&lt;p&gt;현재 &lt;code&gt;index.tsx&lt;/code&gt; 컴포넌트를 간단하게 &lt;code&gt;npm run build&lt;/code&gt; 명령어를 통해서 트랜스파일링 해 보았는데,&lt;/p&gt;
&lt;p&gt;전혀 &lt;code&gt;tsx&lt;/code&gt; 가 적용되지 않고, 이전의 JSX 의 결과물이 보이고 있는 상황이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그런데, 타입스크립트를 적용하기 위해서는 &lt;code&gt;package.json&lt;/code&gt; 의 명령어를 약간 바꿔야 한다.&lt;/p&gt;
&lt;p&gt;기존 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;&amp;quot;scripts&amp;quot;: {
  &amp;quot;test&amp;quot;: &amp;quot;echo \&amp;quot;Error: no test specified\&amp;quot; &amp;amp;&amp;amp; exit 1&amp;quot;,
  &amp;quot;build&amp;quot;: &amp;quot;./node_modules/.bin/babel src --out-dir dist&amp;quot;
},&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이후 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;&amp;quot;scripts&amp;quot;: {
  &amp;quot;test&amp;quot;: &amp;quot;echo \&amp;quot;Error: no test specified\&amp;quot; &amp;amp;&amp;amp; exit 1&amp;quot;,
  &amp;quot;build&amp;quot;: &amp;quot;./node_modules/.bin/babel src --out-dir dist --extensions \&amp;quot;.ts,.tsx\&amp;quot;&amp;quot;
},&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;정해질 데이터 인터페이스와 TSX 파일을 직접 지정하여 트랜스파일 하도록 명령해야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;문제는 여기서 끝나지 않는다.&lt;/p&gt;
&lt;p&gt;타입스크립트 프로젝트를 진행하게 된다면,&lt;/p&gt;
&lt;p&gt;같은 타입스크립트 과의 파일을 import 할 때, 확장자를 생략한다.&lt;/p&gt;
&lt;p&gt;Babel 은, 물론 이 과정에서 파일을 인식하여 오류 없이 TSX 를 JavaScript 로 변경한다.&lt;/p&gt;
&lt;p&gt;그러나, 브라우저는 인식하지 못한다.&lt;/p&gt;
&lt;p&gt;예시로, &lt;code&gt;index.tsx&lt;/code&gt; 와, &lt;code&gt;Main.tsx&lt;/code&gt; 가 존재한다고 가정한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;index.tsx&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import { createRoot } from &amp;quot;react-dom/client&amp;quot;;
import Main from &amp;quot;./components/Main&amp;quot;;

const root = document.getElementById(&amp;quot;root&amp;quot;);

const RootReact = createRoot(root);

RootReact.render(
  &amp;lt;Main /&amp;gt;
)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그리고 이를 등록해둔 명령어를 사용하기 위해 &lt;code&gt;npm run build&lt;/code&gt; 를 실행하면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;index.js&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import { createRoot } from &amp;quot;react-dom/client&amp;quot;;
// 여기가 문제이다.
import Main from &amp;quot;./components/Main&amp;quot;;
import { jsx as _jsx } from &amp;quot;react/jsx-runtime&amp;quot;;
const root = document.getElementById(&amp;quot;root&amp;quot;);
const RootReact = createRoot(root);
RootReact.render(/*#__PURE__*/_jsx(Main, {}));&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;만약에 &amp;quot;Bundler&amp;quot;(번들러) 를 통해 트랜스파일링을 수행한다면,&lt;/p&gt;
&lt;p&gt;알아서 각 파일의 의존성을 파악하여 필요한 확장자를 붙여준다.&lt;/p&gt;
&lt;p&gt;그러나, 우리의 트랜스파일링 도구는 Babel 이다.&lt;/p&gt;
&lt;p&gt;위의 파일은 브라우저에서 인식하지 못하기 때문에, (&lt;code&gt;.js&lt;/code&gt; 가 붙지 않아서)&lt;/p&gt;
&lt;p&gt;렌더링이 불가하다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;확장자가 없어서 렌더링이 되지 않는다?&lt;/h3&gt;
&lt;p&gt;말 그대로 확장자가 붙지 않기 때문에, 브라우저는 &lt;code&gt;./components/Main&lt;/code&gt; 에 요청을 해도,&lt;/p&gt;
&lt;p&gt;해당 파일을 찾지 못해 에러가 난다. (404 NOT FOUND 에러)&lt;/p&gt;
&lt;p&gt;왜 이러한 에러가 날까?&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;바벨, 그리고 typescript 개발 상황에서는, 이러한 경로로 작성한다면,&lt;/p&gt;
&lt;p&gt;자신과 동일한 확장자를 가진 파일에 의존성을 두고 있음을 자동으로 인식한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, 문제는 타입스크립트 개발 상황과 Babel 은 경로를 resolve 하여 알고 있지만,&lt;/p&gt;
&lt;p&gt;브라우저는 &lt;code&gt;./components/Main&lt;/code&gt; 을 그대로 서버에 요청한다.&lt;/p&gt;
&lt;p&gt;서버는 &lt;code&gt;./components/Main.js&lt;/code&gt; 를 가지고 있지만, 확장자가 없기 때문에 NOT FOUND 404&lt;/p&gt;
&lt;p&gt;에러를 돌려준다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;확장자 문제를 해결하는 방법은?&lt;/h3&gt;
&lt;p&gt;조금 아쉽지만, 바벨 + TypeScript 상황에서 이러한 확장자 문제를 종식시킬 수 있는 방법은,&lt;/p&gt;
&lt;p&gt;TSX 확장자나, TS 확장자 파일 내부에서 &lt;code&gt;import&lt;/code&gt; 하는 특정 모듈의 경로에&lt;/p&gt;
&lt;p&gt;&lt;code&gt;xxx.tsx&lt;/code&gt; or &lt;code&gt;xxx.ts&lt;/code&gt; 를 붙여주는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;물론, 타입스크립트는 타입스크립트 파일에 확장자를 직접 붙이는 것에 기본적으로 경고를 날리므로,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;tsconfig.json&lt;/code&gt; 설정을 약간 추가해주고, &lt;code&gt;babel.config.json&lt;/code&gt; 설정을 바꿔준다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;즉,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;import Main from &amp;quot;./components/Main&amp;quot;&lt;/code&gt; 이라는 형식으로 컴포넌트를 가져왔다면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;import Main from &amp;quot;./components/Main.tsx&lt;/code&gt; 이라는 정확한 형식으로 컴포넌트를 가져와야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;tsconfig.json&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;compilerOptions&amp;quot;: {
    &amp;quot;target&amp;quot;: &amp;quot;ES2020&amp;quot;,
    &amp;quot;module&amp;quot;: &amp;quot;ESNext&amp;quot;,
    &amp;quot;jsx&amp;quot;: &amp;quot;preserve&amp;quot;,
    &amp;quot;lib&amp;quot;: [&amp;quot;DOM&amp;quot;],
    &amp;quot;skipLibCheck&amp;quot;: true,
    &amp;quot;noEmit&amp;quot;: true,
    &amp;quot;isolatedModules&amp;quot;: true,
    &amp;quot;allowImportingTsExtensions&amp;quot;: true,
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;allowImportingTsExtension&lt;/code&gt; 옵션을 통해 정확한 확장자명을 지정할 수 있다.&lt;/p&gt;
&lt;p&gt;그러나, 이는 &lt;code&gt;noEmit&lt;/code&gt; 또한 &lt;code&gt;true&lt;/code&gt; 일 때 설정이 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.typescriptlang.org/tsconfig/#noEmit&quot;&gt;noEmit 옵션 설명&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;noEmit&lt;/code&gt; 옵션이란, 컴파일러가 자바스크립트 소스 코드, 소스 맵, 정의 파일들과 같은&lt;/p&gt;
&lt;p&gt;출력물 파일들을 내보내지 않는다는 의미이다.&lt;/p&gt;
&lt;p&gt;왜 이런 옵션이 있냐면, &lt;code&gt;Babel&lt;/code&gt;, &lt;code&gt;swc&lt;/code&gt; 와 같은 TypeScript to JavaScript 컴파일러와 같은&lt;/p&gt;
&lt;p&gt;도구에서 직접적으로 이러한 데이터를 건드리거나, 만들 수 있게 해 주는 옵션이라고 한다.&lt;/p&gt;
&lt;p&gt;따라서, &lt;code&gt;tsconfig.json&lt;/code&gt; 은 단순히 해당 프로젝트의 타입을 검사해 주는 기능만을 사용하게 되고,&lt;/p&gt;
&lt;p&gt;직접 컴파일(트랜스파일링) 하는 역할은 Babel, SWC 에게 맡기게 되는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;babel.config.json&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;presets&amp;quot;: [
    [
      &amp;quot;@babel/preset-react&amp;quot;,
      {
        &amp;quot;runtime&amp;quot; : &amp;quot;automatic&amp;quot;
      }
    ],
    [
      &amp;quot;@babel/preset-typescript&amp;quot;,
      {
        &amp;quot;rewriteImportExtensions&amp;quot; : true
      }
    ]
  ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://babeljs.io/docs/babel-preset-typescript#rewriteimportextensions&quot;&gt;rewriteImportExtensions 옵션 공식 설명서&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;여기서 &lt;code&gt;@babel/preset-typescript&lt;/code&gt; 의 옵션 &lt;code&gt;rewriteImportExtensions&lt;/code&gt; 를 다루게 되는데,&lt;/p&gt;
&lt;p&gt;이 옵션이 활성화 되었을 경우,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;import xxx from &amp;quot;./xxx.ts&amp;quot;&lt;/code&gt; 라는 문구는,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;import xxx from &amp;quot;./xxx.js&amp;quot;&lt;/code&gt; 라는 문구로 바뀌게 된다.&lt;/p&gt;
&lt;p&gt;그러나, 우리가 타입스크립트 프로젝트에서 &amp;quot;명확한&amp;quot; 확장자인 &lt;code&gt;.ts&lt;/code&gt; 를 작성하기 위해서는,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;allowImportingTsExtensions : true&lt;/code&gt; 를 설정 해 주어야 하며,&lt;/p&gt;
&lt;p&gt;이 옵션은 &lt;code&gt;noEmit : true&lt;/code&gt; 를 필요로 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;바뀐 index.tsx 와 그 출력물&lt;/h3&gt;
&lt;p&gt;그렇다면, 이제 한번 트랜스파일 전후를 비교 해 보자.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;index.tsx&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import { createRoot } from &amp;quot;react-dom/client&amp;quot;;
import Main from &amp;quot;./components/Main.tsx&amp;quot;;

const root = document.getElementById(&amp;quot;root&amp;quot;);

const RootReact = createRoot(root);

RootReact.render(
  &amp;lt;Main /&amp;gt;
)&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import { createRoot } from &amp;quot;react-dom/client&amp;quot;;
// 원래 없던 확장자 &amp;quot;.js&amp;quot; 가 뒤에 붙은 것을 볼 수 있다.
import Main from &amp;quot;./components/Main.js&amp;quot;;
import { jsx as _jsx } from &amp;quot;react/jsx-runtime&amp;quot;;
const root = document.getElementById(&amp;quot;root&amp;quot;);
const RootReact = createRoot(root);
RootReact.render(/*#__PURE__*/_jsx(Main, {}));&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;결과물&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;screen-8.png&quot; data-origin-width=&quot;106&quot; data-origin-height=&quot;154&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkgjBy/btsPkpvPXlS/koetYigExNHd17Zy49jDH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkgjBy/btsPkpvPXlS/koetYigExNHd17Zy49jDH0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkgjBy/btsPkpvPXlS/koetYigExNHd17Zy49jDH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkgjBy%2FbtsPkpvPXlS%2FkoetYigExNHd17Zy49jDH0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;106&quot; height=&quot;154&quot; data-filename=&quot;screen-8.png&quot; data-origin-width=&quot;106&quot; data-origin-height=&quot;154&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;이제야 확장자 문제로 인해 브라우저에서 404 NOT FOUND 가 뜨지 않고,&lt;/p&gt;
&lt;p&gt;정상적으로 &lt;code&gt;Main.js&lt;/code&gt; 를 지정하여 가져오는 것을 볼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;왜 현재 바벨 프로젝트에는 확장자 지정이 꼭 필요한 것이지?&lt;/h2&gt;
&lt;p&gt;나도 이 문제에 대해서 StackOverFlow 나, DEV 블로거 사이트 등등..&lt;/p&gt;
&lt;p&gt;해외 사이트를 많이 뒤져봤는데, 이에 대한 정답은 나오지 않았다.&lt;/p&gt;
&lt;p&gt;대부분의 경우, 이 확장자 문제를 WebPack 과 같은 번들러 프로그램을 이용하여 해결했다.&lt;/p&gt;
&lt;p&gt;번들러 프로그램은, 우리가 리액트나, 뷰 등등 정말 많은 라이브러리와 프레임워크들의 의존성 관계를&lt;/p&gt;
&lt;p&gt;쉽게 해소해 주기 위한 프로그램이다.&lt;/p&gt;
&lt;p&gt;번들러는 그 내부가 복잡하고 편의성 기능이 매우 많아서, WebPack 사이트에 들어가&lt;/p&gt;
&lt;p&gt;직접 무엇을 할 수 있는지 직접 리스팅을 보는 것이 가장 적합한 행동이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;즉, 우리는 번들러 프로그램을 사용하지 않았기 때문에, &amp;quot;확장자&amp;quot; 를 명시해야 했다.&lt;/p&gt;
&lt;p&gt;이 말은, &amp;quot;모듈의 의존성&amp;quot; 을 풀지 못하기 때문에 확장자 명시가 꼭 필요하단 의미가 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;즉, Babel 이란, 우리가 타겟으로 하는 &lt;code&gt;src&lt;/code&gt; 폴더의 파일 메타데이터를 저장하고 이를 읽으며,&lt;/p&gt;
&lt;p&gt;내부 파일을 읽고, 이에 매칭되는 특정 표현식을 알맞는 JavaScript 표현으로 바꿔준다는 것이다.&lt;/p&gt;
&lt;p&gt;이러한 과정을 JavaScript 코드로 쉽게 읽을 수 있게 해 주는 프로그램이며,&lt;/p&gt;
&lt;p&gt;이러한 과정 덕분에 공식 모듈이 아닌 여러가지 모듈이 존재한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;Webpack 과 같은 번들러도 Babel을 사용한다.&lt;/p&gt;
&lt;p&gt;리액트는 기본 템플릿 상 Webpack 을 사용한다.&lt;/p&gt;
&lt;p&gt;즉, 우리는 React --&amp;gt; Webpack --&amp;gt; Babel 로서 이를 인식할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;요약&lt;/h2&gt;
&lt;p&gt;아주 간단한 리액트 프로젝트의 예시부터,&lt;/p&gt;
&lt;p&gt;TypeScript + JSX + Babel + tsconfig + npm 의 예제까지 모두 도달했다.&lt;/p&gt;
&lt;p&gt;이 글에 작성된 프로젝트의 과정을 요약하자면 다음과 같다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; &lt;code&gt;index.html&lt;/code&gt; &lt;strong&gt;에 리액트 cdn 2개로 리액트 프로젝트 실행해보기&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CDN&lt;/code&gt;(Content Delivery Network) 를 통해, 웹 앱 실행 브라우저 전역 변수로&lt;/p&gt;
&lt;p&gt;&lt;code&gt;React&lt;/code&gt;, &lt;code&gt;ReactDOM&lt;/code&gt; 을 추가한다.&lt;/p&gt;
&lt;p&gt;이후 간단한 JavaScript 파일을 추가하여 리액트 컴포넌트를 렌더링하였다.&lt;/p&gt;
&lt;p&gt;JSX 를 사용하지 않았으며, 단순히 &lt;code&gt;createElement&lt;/code&gt; 로 JSX 를 대체했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;2.&lt;/strong&gt; &lt;code&gt;index.html&lt;/code&gt; &lt;strong&gt;의 리액트 프로젝트를 모듈 형식의 프로젝트로 변경&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;기존의 CDN 경로로 가져오는 리액트 프로그램은, &lt;code&gt;window.React&lt;/code&gt;, &lt;code&gt;window.ReactDOM&lt;/code&gt; 처럼&lt;/p&gt;
&lt;p&gt;전역 변수에 할당된다. 내가 브라우저에 주입하는 모든 자바스크립트 파일에서 이를 손쉽게 뽑아 쓸 수 있지만,&lt;/p&gt;
&lt;p&gt;모듈 형식의 파일이 아닌, 전역 JS 파일 형식으로 프로그램을 작성하기 때문에&lt;/p&gt;
&lt;p&gt;컴포넌트 계층 형식으로 디렉토리와 파일을 제작 할 수 없다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;더군다나 모듈 형식으로 컴포넌트 계층을 확보한다고 한들,&lt;/p&gt;
&lt;p&gt;모듈 코드 내부에서는 &lt;code&gt;import&lt;/code&gt; 형식으로 프로그램을 가져와야 하는데,&lt;/p&gt;
&lt;p&gt;React 는 전역 변수 형식으로 가져와야 하기 때문에,(모듈에서는 사용 불가)&lt;/p&gt;
&lt;p&gt;React 프로그램을 &lt;code&gt;import&lt;/code&gt; 할 수 있도록 내부 Map 에 넣어주는 스크립트로 변경.&lt;/p&gt;
&lt;p&gt;(이후, React 기본 메서드들은 개발 프로젝트처럼 쉽게 가져오고 사용할 수 있었다.)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;3. 간단한 형식으로 기본 React Hook 들을 사용한 컴포넌트 조합 예제를 생성&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;아직 JSX 가 적용되지는 않았지만, &lt;code&gt;createElement&lt;/code&gt; 는 &lt;code&gt;JSX&lt;/code&gt; 의 JS 메서드 표현 방식이므로,&lt;/p&gt;
&lt;p&gt;이 메서드를 사용하여 간단한 리스트 추가 예제를 만들었다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;InputComponent&lt;/code&gt;, &lt;code&gt;ListComponent&lt;/code&gt;, &lt;code&gt;InputInformation&lt;/code&gt;, &lt;code&gt;index&lt;/code&gt; 파일로&lt;/p&gt;
&lt;p&gt;입력 리스트 예제를 생성했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;4. 이 상태에서 조악한 형식의 스타일 객체를 만들어 컴포넌트에 적용&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;createElement&lt;/code&gt; 의 2 번째 인자에는 props 로 넘겨줄 정보와,&lt;/p&gt;
&lt;p&gt;React Component 가 가질 수 있는 기본 DOM 의 속성을 React 변수 형식으로 넘겨서&lt;/p&gt;
&lt;p&gt;해당 컴포넌트 파일에서 props 로 스타일 객체를 넘겨받아 적용했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;비효율적인 방식이지만, 추후 적용 가능성이 매우매우 높은 StyleContext 의 이해를 위해서&lt;/p&gt;
&lt;p&gt;직접 만들어서 적용 해 보았다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;5. JSX 적용하기&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;편한 리액트 웹 앱 프로젝트 개발에서 빠질 수 없는 꽃, JSX 확장자를 적용하기 개발하기 단계이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.jsx&lt;/code&gt; 확장자는 브라우저가 바로 이해할 수 있는 파일 형식이 아니다.&lt;/p&gt;
&lt;p&gt;즉, &lt;code&gt;React.createElement&lt;/code&gt; 의 복잡한 컴포넌트 개발 방식을,&lt;/p&gt;
&lt;p&gt;JavaScript + HTML(XML) 형식으로 쉽게 복잡한 컴포넌트를 개발하기 위한 파일이다.&lt;/p&gt;
&lt;p&gt;이 단계부터는 NPM 이 사용되는데, Babel 의 도구를 설치하고, 적용하기 위함이다.&lt;/p&gt;
&lt;p&gt;(그리고 TypeScript 를 적용하기 위해서는, npm 모듈 관리가 필수적이기도 했다.)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이 단계는 옵션 설정이 복잡할 수 밖에 없는 단계이기도 했다.&lt;/p&gt;
&lt;p&gt;NPM 프로젝트 생성(&lt;code&gt;package.json&lt;/code&gt; 초기화), Babel 필요 모듈 설치,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;package.json&lt;/code&gt; 에 Babel CLI 사용을 위한 명령어 스크립트 작성,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;babel.config.json&lt;/code&gt; 을 루트에 생성하여 Babel CLI 에 응답하는 설정 파일 작성.&lt;/p&gt;
&lt;p&gt;이러한 과정을 통해 Babel CLI 생성 시, JSX 를 트랜스파일 하여 웹 페이지에서 렌더링을 했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;6. TypeScript 적용하기&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.tsx&lt;/code&gt; 파일을 사용한 단계이다.&lt;/p&gt;
&lt;p&gt;TypeScript 자체가 JavaScript 로 다시 컴파일 하기 위한 특정한 도구&lt;/p&gt;
&lt;p&gt;(타입 검사, 편의성 도구 추가, 문법 검사 등등 정말 수많은 기능이 추가된다)&lt;/p&gt;
&lt;p&gt;이기 때문에, 까다로운 단계였다.&lt;/p&gt;
&lt;p&gt;타입스크립트 적용을 위해서 &lt;code&gt;tsconfig.json&lt;/code&gt; 을 생성 및 옵션 설정을 마쳤으며,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@types/react&lt;/code&gt;, &lt;code&gt;@types/react-dom&lt;/code&gt; 설치를 진행했다.&lt;/p&gt;
&lt;p&gt;이를 통해서 실질적으로 개발 상황에서 리액트를 &amp;quot;검사&amp;quot; 하기 위한 과정을 모조리 마쳤다.&lt;/p&gt;
&lt;p&gt;그리고, 바벨이 타입스크립트 대신 컴파일(트랜스파일) 하는 역할을 맡았기 때문에,&lt;/p&gt;
&lt;p&gt;새로운 타입스크립트 플러그인을 설치하고, 적용했다.&lt;/p&gt;
&lt;p&gt;이를 통해, &lt;code&gt;.tsx&lt;/code&gt; --&amp;gt; &lt;code&gt;.ts&lt;/code&gt; --&amp;gt; &lt;code&gt;.js&lt;/code&gt; 변환이 바벨을 통해 이루어 졌다.&lt;/p&gt;
&lt;p&gt;그러나, 개발 환경에서는 같은 확장자 &lt;code&gt;.ts&lt;/code&gt; or &lt;code&gt;.tsx&lt;/code&gt; 를 import 해서 적용한다는 것을&lt;/p&gt;
&lt;p&gt;암묵적 규칙처럼 검사했지만,&lt;/p&gt;
&lt;p&gt;실질 컴파일 결과에서는 의존 파일의 경로에서 확장자가 무시되어 브라우저가 인식하지 못하는 상황이 나왔다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;7. 확장자 추가하기&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;개발 환경에서 &lt;code&gt;import xxx from &amp;quot;./xxx&amp;quot;&lt;/code&gt; 선언 시,&lt;/p&gt;
&lt;p&gt;이것이 &lt;code&gt;import xxx from &amp;quot;./xxx.ts or ./xxx.tsx&amp;quot;&lt;/code&gt; 라는 것이 암묵적 규칙으로서 인정되었지만,&lt;/p&gt;
&lt;p&gt;결과물은 확장자가 무시되면 안되었기에, &lt;code&gt;babel.config.json&lt;/code&gt; 에 새로운 규칙을 추가하고,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;tsconfig.json&lt;/code&gt; 에도 새로운 옵션 규칙을 생성하여 최종 결과물이 브라우저에서 렌더링 되도록 만들었다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;babel.config.json&lt;/code&gt; 에는 기본 타입스크립트 프리셋 개발 모듈을 설치하고 이를 명시했으며,&lt;/p&gt;
&lt;p&gt;옵션으로 &lt;code&gt;rewriteImportExtensions&lt;/code&gt; 를 활성화 하여,&lt;/p&gt;
&lt;p&gt;타입스크립트 프로젝트에서 직접적으로 의존 파일 경로에 타입스크립트 관련 확장자가 작성되어 있을 경우,&lt;/p&gt;
&lt;p&gt;이것을 최종 결과물 파일인 &lt;code&gt;.js&lt;/code&gt; 로 변환하도록 설정했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그런데, 기본 타입스크립트 프로젝트에서 &lt;code&gt;.ts&lt;/code&gt;, &lt;code&gt;.tsx&lt;/code&gt; 와 같은 동일한 확장자를 명시하는 것은&lt;/p&gt;
&lt;p&gt;에러 및 경고를 발생시킨다. 이를 에러와 경로를 일으키지 않고, 하나의 규칙으로 인식하도록&lt;/p&gt;
&lt;p&gt;&lt;code&gt;tsconfig.json&lt;/code&gt; 에 옵션을 추가했다.&lt;/p&gt;
&lt;p&gt;이후, 변경된 규칙과 트랜스파일로 브라우저에서 바로 적용 가능한 결과물이 나왔으며,&lt;/p&gt;
&lt;p&gt;간단한 카운터 컴포넌트 렌더링을 통해 React 와 TypeScript 프로젝트를 적용시키는 것을 성공했다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;아주 간단한 &lt;code&gt;index.html&lt;/code&gt; 파일 하나에서부터,&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;index.html + ESM CDN 의존성 + NPM + Babel + TypeScript + JSX&lt;/strong&gt; 를 완수했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;React + TypeScript 프로젝트를 만든다는 주요한 목적 말고,&lt;/p&gt;
&lt;p&gt;이 글을 어떻게 치환할 수 있을까? 생각을 해 보니까,&lt;/p&gt;
&lt;p&gt;&amp;quot;레거시 시스템 혹은 다른 웹 프로그램 기반 도구에서 리액트로 마이그레이션 하기&amp;quot; 가 적당하지 않을까 싶다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;그러나, 이 과정은 완벽하지 않고, 부족한 점이 많다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이 글을 끝까지 &lt;code&gt;index.html&lt;/code&gt; 에 필요 의존성을 esm 으로서 &lt;code&gt;importmap&lt;/code&gt; 에 집어넣고 있다.&lt;/p&gt;
&lt;p&gt;즉, 바벨이라는 도구는 의존성까지 파악하여 하나의 코드 덩어리(Chunk) 로 내보낼 수는 없다.&lt;/p&gt;
&lt;p&gt;만약에 필요 라이브러리인 &lt;code&gt;react&lt;/code&gt;, &lt;code&gt;react-dom&lt;/code&gt; 까지 한번에 내보내고 싶다면,&lt;/p&gt;
&lt;p&gt;Webpack 과 같은 번들러 프로그램을 이용하여 청크 파일을 적용해야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;의존성 파악을 위한 로직은 매우 복잡하기 때문에, 따로 주제를 잡아 글을 작성해야겠다는 생각이 든다.&lt;/p&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;h2&gt;참조 사이트&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;React 공식 사이트 - TypeScript 사용하기&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.react.dev/learn/typescript&quot;&gt;https://ko.react.dev/learn/typescript&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;React 공식 사이트 - 기존 프로젝트에 React 추가하기&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.react.dev/learn/add-react-to-an-existing-project&quot;&gt;https://ko.react.dev/learn/add-react-to-an-existing-project&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;React 이전 문서 - CDN 링크&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.legacy.reactjs.org/docs/cdn-links.html&quot;&gt;https://ko.legacy.reactjs.org/docs/cdn-links.html&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;MDN 문서 (JavaScript Modules)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Modules&quot;&gt;https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Modules&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;위키백과 (JSX)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://en.wikipedia.org/wiki/JSX_(JavaScript)&quot;&gt;https://en.wikipedia.org/wiki/JSX_(JavaScript)&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Babel 공식 사이트 - (@babel/preset-react)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://babeljs.io/docs/babel-preset-react&quot;&gt;https://babeljs.io/docs/babel-preset-react&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;TypeScript 공식 홈페이지 - (Using Babel with TypeScript)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.typescriptlang.org/docs/handbook/babel-with-typescript.html&quot;&gt;https://www.typescriptlang.org/docs/handbook/babel-with-typescript.html&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;리액트 공식 홈페이지 문서 - (TypeScript 사용하기)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.react.dev/learn/typescript&quot;&gt;https://ko.react.dev/learn/typescript&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Babel 문서 - (rewriteImportExtensions)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://babeljs.io/docs/babel-preset-typescript#rewriteimportextensions&quot;&gt;https://babeljs.io/docs/babel-preset-typescript#rewriteimportextensions&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;TypeScript 공식 문서 - (tsconfig - noEmit 옵션)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.typescriptlang.org/tsconfig/#noEmit&quot;&gt;https://www.typescriptlang.org/tsconfig/#noEmit&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;a target=&quot;&quot; href=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;</description>
      <category>Web-Server/React</category>
      <category>Babel</category>
      <category>index.html</category>
      <category>JSX</category>
      <category>npm</category>
      <category>react</category>
      <category>react babel</category>
      <category>TSX</category>
      <category>typescript</category>
      <category>리액트 도입</category>
      <category>리액트 마이그레이션</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/228</guid>
      <comments>https://codecreature.tistory.com/228#entry228comment</comments>
      <pubDate>Tue, 15 Jul 2025 19:50:28 +0900</pubDate>
    </item>
    <item>
      <title>React 런타임 코드 해부 노트 : Fiber와 Scheduler를 따라간 21일</title>
      <link>https://codecreature.tistory.com/227</link>
      <description>&lt;h2&gt;제목 : React 런타임 코드 해부 노트 : Fiber와 Scheduler를 따라간 21일&lt;/h2&gt;
&lt;h2&gt;부제 : setState 를 이해하기 위한 3500줄 추적기&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;이 글을 작성하는 이유&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;리액트의 상태 관리는 매우 복잡하게 연계되어 있다.&lt;/p&gt;
&lt;p&gt;옛날에 리액트를 배울 때, 함수 컴포넌트, 클래스 컴포넌트의 상태 변경 방법을 공부했다.&lt;/p&gt;
&lt;p&gt;아마 리액트를 써 보기만 해도 알 텐데, &lt;code&gt;useState&lt;/code&gt; 와, &lt;code&gt;setState&lt;/code&gt; 이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;useState&lt;/code&gt; 를 이용하여, 컴포넌트에서 사용한 내부 상태를 생성한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;setState&lt;/code&gt; 란, 클래스형 컴포넌트를 사용 할 시, 상태를 변경하기 위한 함수이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;useState&lt;/code&gt; Hook 의 상태 설정 메서드는&lt;/p&gt;
&lt;p&gt;&lt;code&gt;setNum&lt;/code&gt;, &lt;code&gt;setNumber&lt;/code&gt;, &lt;code&gt;setString&lt;/code&gt;, &lt;code&gt;setData&lt;/code&gt; 등등 원하는 대로 지정할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;브라우저에서 변경된 내용에 대해 렌더링을 수행하기 위해 스스로 처음부터 코드를 작성한다면,&lt;/p&gt;
&lt;p&gt;이러한 기능들이 필요 할 것이라고 생각한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;특정 객체 혹은 원시값을 저장 할 저장소&lt;/li&gt;
&lt;li&gt;이 저장소의 변경 사항을 구독하게 해 주는 기능&lt;/li&gt;
&lt;li&gt;구독된 저장소가 변경되었을 때, 감지하여 리렌더링 해 주는 기능&lt;/li&gt;
&lt;li&gt;구독한 저장소의 데이터를 하위 데이터에 뿌리고 있을 때, &lt;br/&gt; 자신을 포함한 모든 컴포넌트를 리렌더링 하지 않고, 변경된 데이터를 받는 컴포넌트만 재 렌더링&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;위의 기능들은 이미 리액트에서 지원하며, 더욱 풍부한 기능들을 제공한다.&lt;/p&gt;
&lt;p&gt;지역적 데이터 뿐만 아니라, 전역 데이터까지 이를 적용하게 해 준다.&lt;/p&gt;
&lt;p&gt;그런데, React 는 이를 어떻게 적용시켜줄까?&lt;/p&gt;
&lt;p&gt;궁금증에 대해서 탐구를 시작해 보자.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;이 글을 읽을 때 굉장히 중요한 점.&lt;/h2&gt;
&lt;p&gt;나는 단순히 함수형, 클래스형 컴포넌트의 개인 상태와 변화에 대해서 간단히 설명하려고 하지 않는다.&lt;/p&gt;
&lt;p&gt;리액트의 개인, 전역 상태 지정과 변화에 대해서 빠르게 알고자 한다면, 리액트 공식문서를 참조하는 것이&lt;/p&gt;
&lt;p&gt;훨씬 낫다는 것을 강력하게 주장한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;나는 조금 더 원론적으로 궁금증을 해소하고자 한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;quot;리액트 컴포넌트에 개인 state 를 선언하고, 상호작용 시 어떤 과정으로 컴포넌트가 변하는가?&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;위와 같은 스스로의 질문에 응답하기 위해 글을 작성한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;그런데,&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;사실 앞서서 facebook/react 깃허브 패키지를 뒤지며 해당되는 여러 클래스와 함수를 살펴보았다.&lt;/p&gt;
&lt;p&gt;그 결과, 내가 해석 한 내용이 AI 가 답변 할 내용보다 정확할 수 있나? 라는 생각이 든다.&lt;/p&gt;
&lt;p&gt;따라서 만약 이 글을 읽으시는 분이 있으시다면,&lt;/p&gt;
&lt;p&gt;다시 React 를 깊게 공부하려는 사람이 원론적으로 기반 지식을 공부했을 때 나오는 요약본이라고 생각하시면 됩니다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;가장 단순한 형태의 React.Component&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;ReactBaseClasses.js&lt;/code&gt; - &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/main/packages/react/src/ReactBaseClasses.js&quot;&gt;Github (ReactBaseClasses.js)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;클래스로 컴포넌트를 선언 할 때,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;React.Component&lt;/code&gt; 선언 시 불러오는 가장 기본적인 형태의 컴포넌트이다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// ...

/**
 * Base class helpers for the updating state of a component.
 */
function Component(props, context, updater) {
  this.props = props;
  this.context = context;
  // If a component has string refs, we will assign a different object later.
  this.refs = emptyObject;
  // We initialize the default updater but the real one gets injected by the
  // renderer.
  this.updater = updater || ReactNoopUpdateQueue;
}
// ...

Component.prototype.forceUpdate = function (callback) {
  this.updater.enqueueForceUpdate(this, callback, &amp;#39;forceUpdate&amp;#39;);
};

// ...
Component.prototype.setState = function (partialState, callback) {
  if (
    typeof partialState !== &amp;#39;object&amp;#39; &amp;amp;&amp;amp;
    typeof partialState !== &amp;#39;function&amp;#39; &amp;amp;&amp;amp;
    partialState != null
  ) {
    throw new Error(
      &amp;#39;takes an object of state variables to update or a &amp;#39; +
        &amp;#39;function which returns an object of state variables.&amp;#39;,
    );
  }

  this.updater.enqueueSetState(this, partialState, callback, &amp;#39;setState&amp;#39;);
};&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;위의 코드는 &lt;code&gt;ReactBaseClasses.js&lt;/code&gt; 에서 컴포넌트에 해당하는 부분을 가져왔습니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;먼저 JavaScript 를 정말 깊게 익힌 사람들이라면 이미 알고 있을 테지만,&lt;/p&gt;
&lt;p&gt;JS 의 클래스는 JS 함수의 확장판이다. 즉, &amp;quot;문법적 설탕&amp;quot; 이라고 부르는 부분이다.&lt;/p&gt;
&lt;p&gt;&amp;quot;Component&amp;quot; 가 왜 함수인가? 할 수 있지만, JavaScript 에서 함수는&lt;/p&gt;
&lt;p&gt;클래스와 동등한 객체이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;물론, 최신 스펙에서 클래스만을 위한 &amp;quot;#&amp;quot; - (private 지원) 기능이 있지만, 넘어가도록 하자!&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;br/&gt;

&lt;h3&gt;평소에 사용하던 리액트의 컴포넌트와 다른 점이 많은데?&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Component&lt;/code&gt; 는 우리가 알고 있는 것과 조금 다르게 생겼다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;props&lt;/code&gt; 가 상위 컴포넌트에서 내려오는 것은 알겠지만,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;context&lt;/code&gt;, &lt;code&gt;updater&lt;/code&gt; 는 왜 받는가? 그리고 &lt;code&gt;updater&lt;/code&gt; 는 무엇인가?&lt;/p&gt;
&lt;p&gt;먼저 알아야 할 것이 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;ol&gt;
&lt;li&gt;여기서 선언된 &lt;code&gt;Component&lt;/code&gt; 함수는 &lt;code&gt;React.Component&lt;/code&gt; 구현체와 &amp;quot;정확히&amp;quot; 일치한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;updater&lt;/code&gt; 인수는 컴포넌트 장착 및 처리 과정에서 update Queue 스케쥴러와 연결된다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;React.Component&lt;/code&gt; 에서 &lt;code&gt;this.setState&lt;/code&gt; 실행 시,  &lt;br/&gt; 할당 된 updater 의 스케쥴러 큐에 넘긴다.&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;p&gt;사실 여기까지 알기 위해 깃허브 코드들을 뒤졌는데,&lt;/p&gt;
&lt;p&gt;코드 가독력을 좀 더 키워야 하겠다는 생각을 많이 하게 되었다..&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;왜 Component 에서 render 가 없는데 어떻게 이를 호출하나?&lt;/h3&gt;
&lt;p&gt;자바스크립트는 타입이 매우 자유롭다. 그리고, 메타프로그래밍을 원활하게 지원한다.&lt;/p&gt;
&lt;p&gt;메타프로그래밍의 일부 기능으로서, 특정 함수, 클래스, 변수에 대한 메타 정보를 삽입 및 추출 하는 기능이 있다.&lt;/p&gt;
&lt;p&gt;즉, &lt;code&gt;Component&lt;/code&gt; 는 리액트 컴포넌트 처리 후 장착 과정에서 &lt;code&gt;render&lt;/code&gt; 함수를 인식하는 것이다.&lt;/p&gt;
&lt;p&gt;원본 JavaScript 코드에서 &lt;code&gt;render&lt;/code&gt; 함수가 없는 이유는, 이러한 예시를 들 수 있을 것 같다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;아직 가공되지 않은 보석&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;React 는 이러한 &amp;quot;가공되지 않은 보석&amp;quot;에 대한 코드,&lt;/p&gt;
&lt;p&gt;&amp;quot;보석을 가공하는&amp;quot; 코드, &amp;quot;보석을 전시하는&amp;quot; 코드 등등이 존재한다.&lt;/p&gt;
&lt;p&gt;React 의 상태 관리 자체를 자세히 설명하기가 굉장히 까다로운 이유가 이것이다.&lt;/p&gt;
&lt;p&gt;리액트 자체가 기능이 굉장히 다양한데, 역할이 굉장히 세분화 되어 있어,&lt;/p&gt;
&lt;p&gt;라이브러리 자체의 데이터 흐름을 이해해야 한다는 것이 어려움으로 작용한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;render 를 인식하지만, 메서드는 개발자가 선언해야 한다.&lt;/h3&gt;
&lt;p&gt;요즈음 프로그래밍 언어 서버가 매우 발달되고 효율화되면서,&lt;/p&gt;
&lt;p&gt;JavaScript 에서 사용되는 클래스를 TypeScript 레퍼런스로 알려준다.&lt;/p&gt;
&lt;p&gt;우리는 이를 통해 &amp;quot;아 render 메서드를 통해 하위 컴포넌트를 렌더링 하는구나!&amp;quot; 하고 알 수 있다.&lt;/p&gt;
&lt;p&gt;그러나, 우리가 보다시피, &lt;code&gt;Component.prototype.render&lt;/code&gt; 이라는 문구는 전혀 없다.&lt;/p&gt;
&lt;p&gt;즉, 사용자가 선언 해 주어야 한다.&lt;/p&gt;
&lt;p&gt;그렇다면, 리액트에서는 &lt;code&gt;render&lt;/code&gt; 를 도대체 어떻게 인식하는 걸까?&lt;/p&gt;
&lt;p&gt;위에서 말했듯, JavaScript 는 메타프로그래밍 기법이 굉장히 활발하게 사용된다.&lt;/p&gt;
&lt;p&gt;리액트는, 개발자가 선언한 React.Component 가 처리되는 과정에서,&lt;/p&gt;
&lt;p&gt;이 컴포넌트가 &amp;quot;함수&amp;quot; 인지, &amp;quot;객체&amp;quot; 인지 판별한다.&lt;/p&gt;
&lt;p&gt;만약에 클래스형 함수라면, 전달된 객체 인스턴스에서 &lt;code&gt;.render&lt;/code&gt; 를 추출하여 업데이트 큐로 전달한다.&lt;/p&gt;
&lt;p&gt;그러나, 만약에 함수형 함수라면, &lt;code&gt;.render&lt;/code&gt; 를 추출하지 않고, 반환값을 업데이트 큐로 바로 전달한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;변경 사항을 &amp;quot;객체&amp;quot; 로 전달하거나, &amp;quot;함수&amp;quot; 로 전달하기&lt;/h3&gt;
&lt;p&gt;함수, 혹은 객체가 가지고 있는 고유의 상태(&lt;code&gt;state&lt;/code&gt;) 가 존재한다.&lt;/p&gt;
&lt;p&gt;이 state(상태) 를 변경하기 위해, 2 가지의 방식이 존재한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;1. 객체로 전달하기&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import React from &amp;#39;react&amp;#39;;

class Test extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      count : 1
    }

    this.increase = this.increase.bind(this);
  }

  // 여기를 집중!
  increase() {
    // 객체로 전달하는 것을 볼 수 있음
    this.setState({
      count : this.state.count + 1
    });
  }

  render() {
    return (
      &amp;lt;div&amp;gt;
        &amp;lt;div&amp;gt;
          count : {this.state.count}
        &amp;lt;/div&amp;gt;
        &amp;lt;button onClick={this.increase}&amp;gt;
          +1
        &amp;lt;/button
      &amp;lt;/div&amp;gt;
    )
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 처럼 클래스형 컴포넌트에서 &lt;code&gt;this.setState&lt;/code&gt; 를 사용할 때, 객체를 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;코드의 일부분을 보자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;increase() {
  this.setState({
    count : this.state.count + 1
  });
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 코드의 경우, 우리가 사용하고 있는 원본 코드의 &lt;code&gt;Component.prototype.setState&lt;/code&gt; 를 사용하여&lt;/p&gt;
&lt;p&gt;새로운 상태의 적용을 &lt;code&gt;this.updater.enqueueSetState(...)&lt;/code&gt; 로 보낸다.&lt;/p&gt;
&lt;p&gt;그런데, 동일한 코드를 한 번 더 &lt;code&gt;increase()&lt;/code&gt; 내부에 적었을 때,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;increase() {
  this.setState({
    count : this.state.count + 1
  });
  this.setState({
    count : this.state.count + 1
  });
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;결국 이 코드는 중복 적용되지 않고, &lt;code&gt;count : this.state.count + 1&lt;/code&gt; 의 형태로 종료된다.&lt;/p&gt;
&lt;p&gt;왜 그렇냐면, 이 코드가 실행 될 때, &lt;code&gt;this.state.count&lt;/code&gt; 는 고정된다.&lt;/p&gt;
&lt;p&gt;즉, 만약에 &lt;code&gt;this.state.count&lt;/code&gt; 가 현재 &lt;code&gt;0&lt;/code&gt; 이라면 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;increase() {
  // 마지막만 남음
  // this.setState({
  //   count : this.state + 1
  // });
  this.setState({
    count : this.state + 1
  });
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이러한 형태가 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;따라서, 객체로 전달 할 경우, 같은 값을 설정하게 되는 중복 코드는 회피해야 한다.&lt;/p&gt;
&lt;p&gt;위 2 개의 &lt;code&gt;this.setState&lt;/code&gt; 는 객체에게 할당된 정보를 결과적으로 스케줄러에게 넘어가는데,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Fiber&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Update&amp;lt;State&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Lane&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Component 객체의 정보를 위의 3 개의 정보로 다시 만들고, 스케쥴러에게 넘겨 업데이트한다.&lt;/p&gt;
&lt;p&gt;그러나, 실행 순서와는 무관하게,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;this.setState&lt;/code&gt; 는 우리가 위에서 볼 수 있듯 결국 &lt;code&gt;1&lt;/code&gt; 로만 설정 될 것이라는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;2. 함수로 전달하기&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;함수로 전달 할 경우, 중복 코드가 가능해진다.&lt;/p&gt;
&lt;p&gt;그런데, 이전처럼 &lt;code&gt;this.state&lt;/code&gt; 를 사용해서 접근하는 것이 아니라,&lt;/p&gt;
&lt;p&gt;함수 자체에 원하는 형태의 인수를 적어서 사용해야 한다.&lt;/p&gt;
&lt;p&gt;예시를 들어 보자면,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import React from &amp;#39;react&amp;#39;;

class Test extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      count : 10
    }

    this.decrease = this.decrease.bind(this);
  }

  // increase 에 이은, 감소, decrease
  decrease() {
    // prev 는 뭘까요? 한번 생각 해 봅시다.
    this.setState((prev) =&amp;gt; ({
      count : prev.count - 1
    }));
  }

  render() {
    return (
      &amp;lt;div&amp;gt;
        &amp;lt;p&amp;gt;현재 count : ${this.state.count}&amp;lt;/p&amp;gt;
        &amp;lt;button onClick={this.decrease}&amp;gt;-1&amp;lt;/button
      &amp;lt;/div&amp;gt;
    )
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이번에는 클래스형 컴포넌트에서 &lt;code&gt;this.setState&lt;/code&gt; 인수로, &lt;code&gt;function&lt;/code&gt; 타입인 &amp;quot;함수&amp;quot; 를 넣었다.&lt;/p&gt;
&lt;p&gt;인수인 &lt;code&gt;prev&lt;/code&gt; 는 무엇을 의미하는 것일까?&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;this.setState&lt;/code&gt; 는 &amp;quot;객체&amp;quot; 혹은 &amp;quot;함수&amp;quot; 에 따라서 다르게 동작한다.&lt;/p&gt;
&lt;p&gt;&amp;quot;함수&amp;quot; 로 작성할 시, 인자 &amp;quot;이름&amp;quot; 을 줄 수 있는데,&lt;/p&gt;
&lt;p&gt;이 때, &lt;code&gt;prev&lt;/code&gt; 혹은, &lt;code&gt;prevState&lt;/code&gt; 로 이름을 마음대로 정할 수 있다.&lt;/p&gt;
&lt;p&gt;이 인자가 바로 중첩 가능하도록 만들어주는 역할을 해 준다.&lt;/p&gt;
&lt;p&gt;즉, &lt;code&gt;prev&lt;/code&gt; 라고 작성한다면, &lt;code&gt;prev&lt;/code&gt; 는 업데이트 큐에서 현재 인스턴스의 &lt;code&gt;this.state&lt;/code&gt; 를&lt;/p&gt;
&lt;p&gt;동기적으로 접근하게 해 주는 인자가 되는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;decrease() {
  this.setState((prev) =&amp;gt; ({
    count : prev.count - 1 // --&amp;gt; -1
  }));

  // 한 번 계산된 이후, 다시 계산할 수 있게 해 준다.
  this.setState((prev) =&amp;gt; ({
    count : prev.count - 1 // --&amp;gt; -2
  }))
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;만약에 이러한 형태로 중첩 코드를 작성해도,&lt;/p&gt;
&lt;p&gt;업데이트 큐에서 차례로 실행될 때, &lt;code&gt;prev&lt;/code&gt; 의 상태는 이미 결정된 것이 아니며,&lt;/p&gt;
&lt;p&gt;업데이트 시, 계산된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;따라서, 중첩 상태 관리 코드가 필요 할 경우, 우리는 함수를 인자로 주어&lt;/p&gt;
&lt;p&gt;원하지 않는 상태를 예방할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;this.setState 함수는 어떻게 생겼을까? (Fiber 클래스 진입)&lt;/h2&gt;
&lt;p&gt;Fiber 관련 클래스 파일 진입 전,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ReactBaseClasses.js&lt;/code&gt; 파일에서 &lt;code&gt;React.Component&lt;/code&gt; 의 원형 코드를 살펴보자 :&lt;/p&gt;
&lt;p&gt;그런데, 여기에 이런 코드 조각이 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;Component.prototype.setState = function (partialState, callback) {
  if (
    typeof partialState !== &amp;#39;object&amp;#39; &amp;amp;&amp;amp;
    typeof partialState !== &amp;#39;function&amp;#39; &amp;amp;&amp;amp;
    partialState != null
  ) {
    throw new Error(
      &amp;#39;takes an object of state variables to update or a &amp;#39; +
        &amp;#39;function which returns an object of state variables.&amp;#39;,
    );
  }

  this.updater.enqueueSetState(this, partialState, callback, &amp;#39;setState&amp;#39;);
};&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;설명하자면&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;partialState&lt;/code&gt; 는 위에서 &amp;quot;함수&amp;quot; 혹은 &amp;quot;객체&amp;quot;의 형태로 들어갈 수 있다고 말했다.&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;code&gt;callback&lt;/code&gt; 은 지정한 상태 변화가 끝난 뒤, 무엇을 할 지 콜백 함수를 넣어주는 공간이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;예를 들어, &lt;code&gt;function () {console.log(....)}&lt;/code&gt; 이렇게 넣을 수도 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;&lt;code&gt;partialState&lt;/code&gt; 가 &amp;quot;함수&amp;quot; 나 &amp;quot;객체&amp;quot; 형태가 아닐 경우, 에러를 던진다.&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;&lt;p&gt;우리가 &lt;code&gt;this.setState(...)&lt;/code&gt; 할 경우, 최종적으로 &lt;br/&gt; &lt;code&gt;this.updater.enqueueSetState&lt;/code&gt; 메서드가 실행된다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;이 &lt;code&gt;updater&lt;/code&gt; 는, 서로 다른 renderer 에 따라 업데이터가 &lt;br/&gt; 하나는 기본적으로 주입되어 초기화되어야 한다고 &amp;quot;주석으로&amp;quot; 설명되어 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;h3&gt;this.updater 가 뭐지?&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function Component(props, context, updater) {
  this.props = props;
  this.context = context;
  // emptyObject 는 {} 이다.
  this.refs = emptyObject;

  // ReactNoopUpdateQueue 는 아직 마운트 되지 않았을 때만 할당된다. - 아직 신경쓰지 않아도 됨.
  // 렌더링 환경에 따라, 다른 업데이터가 들어간다. EX- React Native, or 브라우저용 React 등등
  this.updater = updater || ReactNoopUpdateQueue;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;나는 React.Component 를 사용 할 때, &lt;code&gt;updater&lt;/code&gt; 에 대해서 들어 본 적이 없다.&lt;/p&gt;
&lt;p&gt;도대체 이건 뭘까?&lt;/p&gt;
&lt;p&gt;Github 에는 훌륭한 기능으로, 동일한 메서드 Symbol 을 가지고 있는 모든 파일을 찾아주는 기능이 있다.&lt;/p&gt;
&lt;p&gt;이 기능을 이용하여,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;this.updater.enqueueSetState&lt;/code&gt; 중 &lt;code&gt;enqueueSetState&lt;/code&gt; 메서드를 가진 클래스를 찾아냈다.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/374dfe8edf8beef62e5bb312f99c600a232f353f/packages/react-reconciler/src/ReactFiberClassComponent.js#L168&quot;&gt;enqueueSetState 코드 위치&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;일단 확실 한 것은, &lt;code&gt;classComponentUpdater&lt;/code&gt; 라는 값이&lt;/p&gt;
&lt;p&gt;&lt;code&gt;React.Component&lt;/code&gt; 의 &lt;code&gt;this.updater&lt;/code&gt; 로 들어간다는 것을 확인했다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;code&gt;this.updater.enqueueSetState(...., &quot;setState&quot;)&lt;/code&gt; &lt;br/&gt;&lt;br&gt;부분이 있는데, &amp;quot;setState&amp;quot; 는 무시된 상태로 적용됩니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;br/&gt;

&lt;h3&gt;도대체 enqueueSetState 에서 무슨일이 일어나고 있는 거지?&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;ReactFiberClassComponent.js&lt;/code&gt; 에서 &lt;code&gt;enqueueSetState&lt;/code&gt; 메서드의 행방을 찾았다.&lt;/p&gt;
&lt;p&gt;그러나, 이 메서드는 그동안 보지 못했던 수많은 함수와 변수들로 가득 차 있었다.&lt;/p&gt;
&lt;p&gt;한번 원본 코드를 보자 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const classComponentUpdater = {
  // inst 는 &amp;quot;this&amp;quot;,
  // payload 는 &amp;quot;함수&amp;quot; 혹은 &amp;quot;객체&amp;quot; 이며,
  // callback 은 작성했거나, 없다. == undefined or null
  enqueueSetState(inst: any, payload: any, callback) {
    const fiber = getInstance(inst);
    const lane = requestUpdateLane(fiber);

    const update = createUpdate(lane);
    update.payload = payload;
    if (callback !== undefined &amp;amp;&amp;amp; callback !== null) {
      if (__DEV__) {
        warnOnInvalidCallback(callback);
      }
      update.callback = callback;
    }

    const root = enqueueUpdate(fiber, update, lane);
    if (root !== null) {
      startUpdateTimerByLane(lane, &amp;#39;this.setState()&amp;#39;);
      scheduleUpdateOnFiber(root, fiber, lane);
      entangleTransitions(root, fiber, lane);
    }

    if (enableSchedulingProfiler) {
      markStateUpdateScheduled(fiber, lane);
    }
  },
  // ... 등등 여러 메서드들
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 부분부터 매우 당혹스러웠는데, 알 수 있는 것 부터 차근차근 정리하기로 했다.&lt;/p&gt;
&lt;p&gt;각 변수의 생성과 데이터 변환 과정을 먼저 정리하기로 결정했다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TB

inst -- getInstance --&amp;gt; fiber

fiber -- requestUpdateLane --&amp;gt; lane

lane -- createUpdate --&amp;gt; update&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;code&gt;update&lt;/code&gt; 이후, 우리가 지정한 &amp;quot;상태 객체&amp;quot; or &amp;quot;상태 객체 변환 함수&amp;quot; 를 지정하며, &lt;br/&gt;&lt;br&gt;지정한 &lt;code&gt;callback&lt;/code&gt; 또한 &lt;code&gt;update&lt;/code&gt; 로 지정한다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;중간의 이러한 메서드들이 호출되면서, 처음 보는 변수들을 생성하고 있었다.&lt;/p&gt;
&lt;p&gt;그렇다면, 생성된 &lt;code&gt;fiber&lt;/code&gt;, &lt;code&gt;update&lt;/code&gt;, &lt;code&gt;lane&lt;/code&gt; 변수는 무엇을 위해 생겨났냐면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;const root = enqueueUpdate(fiber, update, lane)&lt;/code&gt; 으로 사용되기 위해서이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;inst, fiber, lane, update 변수를 생성하는 메서드 코드를 살펴보자.&lt;/h3&gt;
&lt;p&gt;리액트는 상태 변화를 적용 할 때, 단순하게 컴포넌트에 선언된 상태를 즉시 변환시키는 것이 아니라,&lt;/p&gt;
&lt;p&gt;상태가 변환된 DOM 객체, 변화시킬 &amp;quot;함수&amp;quot; 혹은 &amp;quot;객체&amp;quot;를 받아서 적용하고 있었다.&lt;/p&gt;
&lt;p&gt;그러한 과정을, &lt;code&gt;enqueueSetState&lt;/code&gt; 메서드로 실행하고 있었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, &lt;code&gt;enqueueSetState&lt;/code&gt; 메서드 내부에는 처음 보는 단어들과 객체들이 존재했다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Fiber&lt;/code&gt; 는 무엇이고, &lt;code&gt;Lane&lt;/code&gt; 은 또 무엇인가?&lt;/p&gt;
&lt;p&gt;한번 찾아보자.&lt;/p&gt;
&lt;p&gt;먼저, &lt;code&gt;classComponentUpdater&lt;/code&gt; 변수에 존재하던 &lt;code&gt;enqueueSetState&lt;/code&gt; 메서드의&lt;/p&gt;
&lt;p&gt;원본 코드를 다시 살펴보자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// inst : React.Component
// payload : this.setState 에 들어간 객체 혹은 함수
// 상태 업데이트 후 실행하고 싶은 개발자의 코드
enqueueSetState(inst: any, payload: any, callback) {
    const fiber = getInstance(inst);
    const lane = requestUpdateLane(fiber);

    const update = createUpdate(lane);
    update.payload = payload;

    // ... if --&amp;gt; update.callback = callback;

    const root = enqueueUpdate(fiber, update, lane);

    // ... root 가 없을 시 실행
  },&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;코드를 읽고, 현재 알아보려는 내용과 상관이 깊지 않는 내용들은 주석으로 처리했다.&lt;/p&gt;
&lt;p&gt;이제,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;const fiber = getInstance(inst)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;const lane = requestUpdateLane(fiber)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;const update = createUpdate(lane)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;const root = enqueueUpdate(fiber, update, lane)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이러한 처음보는 변수와 메서드를 해석 해 보자.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;만약에, &lt;code&gt;fiber, lane, update&lt;/code&gt; 타입이 궁금하다면,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Fiber&lt;/code&gt; : &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/06e89951be5b4b23ca343d02721521fe392e94c5/packages/react-reconciler/src/ReactInternalTypes.js#L88&quot;&gt;Fiber 타입 선언 위치&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Lane&lt;/code&gt; : &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/06e89951be5b4b23ca343d02721521fe392e94c5/packages/react-reconciler/src/ReactFiberLane.js#L18&quot;&gt;Lane 타입 선언 위치&lt;/a&gt; - 그냥 number 이다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Update&amp;lt;State&amp;gt;&lt;/code&gt; : &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/06e89951be5b4b23ca343d02721521fe392e94c5/packages/react-reconciler/src/ReactFiberFlags.js#L25&quot;&gt;Update 상수 선언 위치&lt;/a&gt; - Binary 형태로 수가 선언되어 있다. &lt;br/&gt; &lt;code&gt;0b0000000000000000000000000000100&lt;/code&gt; == &lt;code&gt;4&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;h4&gt;1. getInstance(inst) : Fiber&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;getInstance&lt;/code&gt; 메서드 이름은 원본 이름이 아니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ReactFiberClassComponent.js&lt;/code&gt; 코드의 상단에서&lt;/p&gt;
&lt;p&gt;메서드를 가져오면서, &lt;code&gt;get&lt;/code&gt; 메서드를 &lt;code&gt;getInstance&lt;/code&gt; 로 정정했다.&lt;/p&gt;
&lt;p&gt;이 메서드를 가져온 곳은,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ReactInstanceMap.js&lt;/code&gt; 라는, 전혀 다른 디렉토리에 존재하는 파일이었다.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/main/packages/shared/ReactInstanceMap.js&quot;&gt;ReactInstanceMap.js 파일 위치&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;이 파일의 내부 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;/**
 * `ReactInstanceMap` maintains a mapping from a public facing stateful
 * instance (key) and the internal representation (value). This allows public
 * methods to accept the user facing instance as an argument and map them back
 * to internal methods.
 *
 * Note that this module is currently shared and assumed to be stateless.
 * If this becomes an actual Map, that will break.
 */

export function get(key) {
  return key._reactInternals;
}

export function set(key, value) {
  key._reactInternals = value;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;내부는 매우 간단하게 구현되어 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;const fiber = getInstance(inst)&lt;/code&gt; 로 사용되고 있는데,&lt;/p&gt;
&lt;p&gt;즉, &lt;code&gt;key&lt;/code&gt; 는 &lt;code&gt;React.Component&lt;/code&gt; 인 것이다.&lt;/p&gt;
&lt;p&gt;그런데, 기본 리액트 컴포넌트 코드에서 지금에 이르기까지,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;_reactInternals&lt;/code&gt; 를 본 적이 없다.&lt;/p&gt;
&lt;p&gt;즉, 리액트의 컴포넌트가 장착, 혹은 생성되는 과정에서,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;set(key, value)&lt;/code&gt; 를 사용하여, &lt;code&gt;React.Component&lt;/code&gt; 에&lt;/p&gt;
&lt;p&gt;&lt;code&gt;_reactInternals = value&lt;/code&gt; 를 실행시켜 &lt;code&gt;value&lt;/code&gt; 를 넣어주는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;결국, &lt;code&gt;classComponentUpdater&lt;/code&gt; 변수에서 &lt;code&gt;const fiber = getInstance(inst)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;로 사용되었기 때문에,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;set&lt;/code&gt; 과정에서&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;key&lt;/code&gt; : React.Component&lt;/li&gt;
&lt;li&gt;&lt;code&gt;value&lt;/code&gt; : Fiber&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;로 설정 된 것이다.&lt;/p&gt;
&lt;p&gt;이 파일은 &lt;code&gt;react/packages/shared/ReactInstanceMap.js&lt;/code&gt; 인데,&lt;/p&gt;
&lt;p&gt;내부의 메서드가 &amp;quot;특정 목적만을 위해&amp;quot; 만들어졌다고 보기는 어렵다.&lt;/p&gt;
&lt;p&gt;즉, 인스턴스 내부 &lt;code&gt;_reactInternals&lt;/code&gt; 변수에 다양한 형태의 정보를 넣을 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;React.Component&lt;/code&gt; 의 경우, &lt;code&gt;Fiber&lt;/code&gt; 가 들어간다고 예측 할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;2. requestUpdateLane(fiber) : Lane&lt;/h4&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/06e89951be5b4b23ca343d02721521fe392e94c5/packages/react-reconciler/src/ReactFiberWorkLoop.js#L743&quot;&gt;requestUpdateLane(fiber) 메서드 파일의 위치&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;export function requestUpdateLane(fiber: Fiber): Lane {
  // Special cases
  const mode = fiber.mode;
  if (!disableLegacyMode &amp;amp;&amp;amp; (mode &amp;amp; ConcurrentMode) === NoMode) {
    return (SyncLane: Lane);
  } else if (
    (executionContext &amp;amp; RenderContext) !== NoContext &amp;amp;&amp;amp;
    workInProgressRootRenderLanes !== NoLanes
  ) {
    // This is a render phase update. These are not officially supported. The
    // old behavior is to give this the same &amp;quot;thread&amp;quot; (lanes) as
    // whatever is currently rendering. So if you call `setState` on a component
    // that happens later in the same render, it will flush. Ideally, we want to
    // remove the special case and treat them as if they came from an
    // interleaved event. Regardless, this pattern is not officially supported.
    // This behavior is only a fallback. The flag only exists until we can roll
    // out the setState warning, since existing code might accidentally rely on
    // the current behavior.
    return pickArbitraryLane(workInProgressRootRenderLanes);
  }

  const transition = requestCurrentTransition();
  if (transition !== null)

    // ... if(__DEV__)

    return requestTransitionLane(transition);
  }

  return eventPriorityToLane(resolveUpdatePriority());
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 코드의 &lt;code&gt;return pickArbitraryLane(...);&lt;/code&gt; 위의 주석을 살펴보면,&lt;/p&gt;
&lt;p&gt;공식적으로 지원되지는 않는 코드라고 명시해 놓았다. 따라서,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;requestCurrentTransition()&lt;/code&gt; 의 결과에 따라,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;NULL 이다 =&amp;gt; &lt;code&gt;requestTransitionLane(transition)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;NULL 이 아니다. =&amp;gt; &lt;code&gt;eventPriorityToLane(resolveUpdatePriority())&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;중 하나가 반환된다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;requestCurrentTransition()&lt;/code&gt; 코드 : &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/fe3f0ec0374b7323bf259e4154eb4ee739caac7b/packages/react-reconciler/src/ReactFiberTransition.js#L190&quot;&gt;코드 위치&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import ReactSharedInternals from &amp;#39;shared/ReactSharedInternals&amp;#39;;

// ...

export function requestCurrentTransition(): Transition | null {
  return ReactSharedInternals.T;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;즉, 리액트 프로젝트가 자체가 공유하고 있는 &lt;code&gt;ReactSharedInternals&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;값을 반환한다는 것을 추측할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ReactSharedInternals&lt;/code&gt; 객체 코드 : &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/main/packages/shared/ReactSharedInternals.js&quot;&gt;코드 위치&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import * as React from &amp;#39;react&amp;#39;;

const ReactSharedInternals =
  React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;

export default ReactSharedInternals;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Transition&lt;/code&gt; 타입 위치 : &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/fe3f0ec0374b7323bf259e4154eb4ee739caac7b/packages/react/src/ReactStartTransition.js#L30&quot;&gt;코드 위치&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;export type Transition = {
  types: null | TransitionTypes, // enableViewTransition
  gesture: null | GestureProvider, // enableGestureTransition
  name: null | string, // enableTransitionTracing only
  startTime: number, // enableTransitionTracing only
  _updatedFibers: Set&amp;lt;Fiber&amp;gt;, // DEV-only
  ...
};&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기에서 굉장히 뇌정지가 왔다.&lt;/p&gt;
&lt;p&gt;최대한 코드를 파헤치며, 이 코드가 무엇을 의미하는지 이해하려고 항상 노력하는데,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ReactSharedInternals&lt;/code&gt; 가,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;이 값이라는 것을 어떻게 해도 이해 할 수가 없었다.&lt;/p&gt;
&lt;p&gt;위에서 가져온 &lt;code&gt;import * as React from &amp;quot;react&amp;quot;;&lt;/code&gt; 코드는&lt;/p&gt;
&lt;p&gt;나의 생각만 더 혼란스럽게 만들었다..&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;현재 &lt;code&gt;Transition&lt;/code&gt; 을 요청하는 것은 알겠는데, 왜 요청하는지 모른다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Transition&lt;/code&gt; 이 어디서 사용하는지 모른다.&lt;/li&gt;
&lt;li&gt;위의 &lt;code&gt;requestUpdateLane(fiber) : Lane&lt;/code&gt; 메서드의 분기를 제대로 이해하지 않았다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;따라서, 이 갈래에 대한 부분은 GPT 에게 물어보기로 결정했다. (현재 GPT-o3)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그래서 이 분기에 대한 답은 이러했다.&lt;/p&gt;
&lt;p&gt;만약에, React17 버전 이하를 사용하고 있다면, &lt;code&gt;SyncLane&lt;/code&gt; 을 반환한다.&lt;/p&gt;
&lt;p&gt;그렇지 않고, React18 버전을 이용한다면,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;대부분 자동으로 ConcurrentMode 가 적용된다. - &lt;code&gt;createRoot&lt;/code&gt; --&amp;gt; &lt;code&gt;render&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;그리고, &lt;code&gt;requestCurrentTransition&lt;/code&gt; 은, 현재 상태 변화가 &lt;code&gt;useTransition&lt;/code&gt; 으로 생성된&lt;/p&gt;
&lt;p&gt;트랜지션 내부에 상태 변화가 첨부되었는가를 물어보는 것이다.&lt;/p&gt;
&lt;p&gt;당연히 &lt;code&gt;useTransition&lt;/code&gt; 이 아니고, 단순히 &lt;code&gt;setState&lt;/code&gt; 만 사용중이므로,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;transition&lt;/code&gt; 은 &lt;code&gt;null&lt;/code&gt; 이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;따라서, React 18 이상을 사용하는 대부분의 프로젝트의 경우,&lt;/p&gt;
&lt;p&gt;현재 &lt;code&gt;eventPriorityToLane(resolveUpdatePriority())&lt;/code&gt; 를 사용한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;resolveUpdatePriority()&lt;/code&gt; 메서드는, &lt;code&gt;./ReactFiberConfig.js&lt;/code&gt; 파일로부터 가져왔다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;ReactFiberConfig.js&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;/* eslint-disable react-internal/prod-error-codes */

// We expect that our Rollup, Jest, and Flow configurations
// always shim this module with the corresponding host config
// (either provided by a renderer, or a generic shim for npm).
//
// We should never resolve to this file, but it exists to make
// sure that if we *do* accidentally break the configuration,
// the failure isn&amp;#39;t silent.

throw new Error(&amp;#39;This module must be shimmed by a specific renderer.&amp;#39;);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;하하하하하하하하하!!!!!!!&lt;/p&gt;
&lt;p&gt;분명히 가져온 경로가 맞는데... 이 모듈을 가져오면 에러가 일어나도록 만들어져 있다.&lt;/p&gt;
&lt;p&gt;그래서 위의 주석을 약간 해석 해 보자면..&lt;/p&gt;
&lt;p&gt;Jest 와 같은 테스팅 환경이나 플로우 설정 환경에서,&lt;/p&gt;
&lt;p&gt;이 모듈을 가져오고 있는 환경이 특정 렌더러에 의해 다른 레인을 가져오고 있다는 것이다.&lt;/p&gt;
&lt;p&gt;그리고, 호스트의 상황에 따라 현재 파일이 아닌, 다른 코드로 &amp;quot;주입&amp;quot;(shim) 되어야 한다는 것이다.&lt;/p&gt;
&lt;p&gt;즉, 이 에러가 난다면, 렌더러 주입 설정이 잘못된 것으로 &amp;quot;의도적으로&amp;quot; 에러를 일으키는 것이라는 거다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;따라서, Github 의 기능을 통해, 같은 이름을 가진 메서드를 찾게 되었다!&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/fa3feba6720c96ca10fb42d5f53a9b4fa9aa6ccd/packages/react-reconciler/src/forks/ReactFiberConfig.custom.js#L83&quot;&gt;ReactFiberConfig.custom.js 코드 위치&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;여기에 &lt;code&gt;resolveUpdatePriority()&lt;/code&gt; 에 대한 정보를 곧바로 내보내고 있었다.&lt;/p&gt;
&lt;p&gt;그런데, 이를 보니까, 파일 이름에 &lt;code&gt;custom.js&lt;/code&gt; 가 붙여져 있었는데,&lt;/p&gt;
&lt;p&gt;이는 React 를 렌더링하려는 여러 상황 :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;브라우저&lt;/li&gt;
&lt;li&gt;네이티브&lt;/li&gt;
&lt;li&gt;테스팅&lt;/li&gt;
&lt;li&gt;등등..&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;개발자가 다른 호스팅 상황을 구현하기 위해 사용하려는 커스텀 파일이다.&lt;/p&gt;
&lt;p&gt;즉, 이 파일은 내가 찾는 파일이 아니다.&lt;/p&gt;
&lt;p&gt;내가 찾는 파일은,&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/main/packages/react-reconciler/src/forks/ReactFiberConfig.dom.js&quot;&gt;ReactFiberConfig.dom.js 코드 위치&lt;/a&gt; 이다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;export * from &amp;#39;react-dom-bindings/src/client/ReactFiberConfigDOM&amp;#39;;
export * from &amp;#39;react-client/src/ReactClientConsoleConfigBrowser&amp;#39;;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;즉, &lt;code&gt;resolveUpdatePriority()&lt;/code&gt; 함수는 위의 경로에서 올 것이다.&lt;/p&gt;
&lt;p&gt;위의 경로와 파일 이름 &lt;code&gt;dom&lt;/code&gt; 으로 알수 있듯, 브라우저 환경에서 구축되는&lt;/p&gt;
&lt;p&gt;&lt;code&gt;react-reconciler&lt;/code&gt; 환경변수와 함수들이 주입된다고 볼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;1. react-dom-bindings/src/client/ReactFiberConfigDOM.js&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이 파일에는 현재 디렉토리 계층에 존재하는 다양한 형태의 함수를 가져와서 다시 내보내고 있다.&lt;/p&gt;
&lt;p&gt;파일 자체가 6005 줄이기 때문에, 필요한 코드만 보여주자면 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;/**
import ....
*/
export {
  setCurrentUpdatePriority,
  getCurrentUpdatePriority,
  // 여기에서 내보내는 중.
  resolveUpdatePriority,
} from &amp;#39;./ReactDOMUpdatePriority&amp;#39;;

/**
export ....
*/&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;따라서, &lt;code&gt;ReactDomUpdatePriority.js&lt;/code&gt; 파일을 보자 :&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/main/packages/react-dom-bindings/src/client/ReactDOMUpdatePriority.js#L35&quot;&gt;ReactDomUpdatePriority.js 코드 위치&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;참고로, 이 파일은 버릴 것이 거의 없는 파일이라고 생각한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import type {EventPriority} from &amp;#39;react-reconciler/src/ReactEventPriorities&amp;#39;;

import {getEventPriority} from &amp;#39;../events/ReactDOMEventListener&amp;#39;;
import {
  NoEventPriority,
  DefaultEventPriority,
} from &amp;#39;react-reconciler/src/ReactEventPriorities&amp;#39;;

import ReactDOMSharedInternals from &amp;#39;shared/ReactDOMSharedInternals&amp;#39;;

export function setCurrentUpdatePriority(
  newPriority: EventPriority,
  // Closure will consistently not inline this function when it has arity 1
  // however when it has arity 2 even if the second arg is omitted at every
  // callsite it seems to inline it even when the internal length of the function
  // is much longer. I hope this is consistent enough to rely on across builds
  IntentionallyUnusedArgument?: empty,
): void {
  ReactDOMSharedInternals.p /* currentUpdatePriority */ = newPriority;
}

export function getCurrentUpdatePriority(): EventPriority {
  return ReactDOMSharedInternals.p; /* currentUpdatePriority */
}

export function resolveUpdatePriority(): EventPriority {
  const updatePriority = ReactDOMSharedInternals.p; /* currentUpdatePriority */
  if (updatePriority !== NoEventPriority) {
    return updatePriority;
  }
  const currentEvent = window.event;
  if (currentEvent === undefined) {
    return DefaultEventPriority;
  }
  return getEventPriority(currentEvent.type);
}

export function runWithPriority&amp;lt;T&amp;gt;(priority: EventPriority, fn: () =&amp;gt; T): T {
  const previousPriority = getCurrentUpdatePriority();
  try {
    setCurrentUpdatePriority(priority);
    return fn();
  } finally {
    setCurrentUpdatePriority(previousPriority);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;왜 이 파일이 중요하다고 생각했을까?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이 글을 보고있으시다면, 우리는 &lt;code&gt;resolveUpdatePriority()&lt;/code&gt; 함수를 찾기 위해&lt;/p&gt;
&lt;p&gt;잠깐의 여정을 떠났다.&lt;/p&gt;
&lt;p&gt;그런데, 이 함수는 특이한 점이 있다.&lt;/p&gt;
&lt;p&gt;바로, &lt;span style=&quot;color : skyblue&quot;&gt;인자가 없다&lt;/span&gt; 는 점이다.&lt;/p&gt;
&lt;p&gt;그렇다면 도대체, 현재 컴포넌트가 업데이트 될 &amp;quot;Lane&amp;quot; 을 구하기 위해,&lt;/p&gt;
&lt;p&gt;현재 상태 변경을 요청한 컴포넌트가 들어간 상황도 아니고, 변환된 &lt;code&gt;fiber&lt;/code&gt; 변수도 들어가지 않는다.&lt;/p&gt;
&lt;p&gt;왜 그럴까?&lt;/p&gt;
&lt;p&gt;여기서 요청하는 &lt;code&gt;ReactDOMSharedInternals.p&lt;/code&gt; 데이터는,&lt;/p&gt;
&lt;p&gt;리액트 시스템이 전역적으로 공유하며 넣어주기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;2 번째 메서드였던 &lt;code&gt;requestUpdateLane(fiber) : Lane&lt;/code&gt; 설명이 길어지고 있지만,&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;필요한 내용이라고 판단해서 지속한다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;먼저,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;setCurrentUpdatePriority(newPriority : EventPriority)&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getCurrentUpdatePriority()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 2 개는 &lt;code&gt;ReactDomSharedInternals.p&lt;/code&gt; 데이터를 설정하고, 가져오는 메서드라는 것만 알면 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그래서, &amp;quot;도대체 어떤 코드가 &lt;code&gt;ReactDOMSharedInternals.p&lt;/code&gt; 를 설정해 주는지 알아보기 위해,&lt;/p&gt;
&lt;p&gt;깃허브의 자동 검색 기능을 이용하여 &lt;code&gt;setCurrentUpdatePriority&lt;/code&gt; 코드 사용하는 위치를 추적했다.&lt;/p&gt;
&lt;p&gt;한 30개가 넘는 참조 코드 위치가 존재했는데, 살펴 본 결과,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;src/events/ReactDOMEventListner.js&lt;/code&gt; 가 거의 정확할 것이라고 판단했다.&lt;/p&gt;
&lt;p&gt;버튼 이벤트 --&amp;gt; DOM 이벤트 (&lt;code&gt;dispatchDiscreteEvent&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;ReactDOMEventListener.js : &lt;code&gt;function dispatchDiscreteEvent&lt;/code&gt; : &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/12eaef7ef5fbf6c9d7ec6e16a04bc207a1a68b91/packages/react-dom-bindings/src/events/ReactDOMEventListener.js#L121&quot;&gt;코드 위치&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function dispatchDiscreteEvent(
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
  container: EventTarget,
  nativeEvent: AnyNativeEvent,
) {
  const prevTransition = ReactSharedInternals.T;
  ReactSharedInternals.T = null;
  const previousPriority = getCurrentUpdatePriority();
  try {
    setCurrentUpdatePriority(DiscreteEventPriority);
    dispatchEvent(domEventName, eventSystemFlags, container, nativeEvent);
  } finally {
    setCurrentUpdatePriority(previousPriority);
    ReactSharedInternals.T = prevTransition;
  }
}

// function dispatchContinuousEvent ( 동일 ) {
//    setCurrentUpdatePriority(ContinuousEventPriority); 만 다름
// }
//
//
// export function dispatchEvent( .... ) : void { .... }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;자... 드디어 &lt;code&gt;ReactSharedInternals.p&lt;/code&gt; 를 설정 해 주는 메서드를 찾았다.&lt;/p&gt;
&lt;p&gt;이 코드 위치에 들어가서 맨 밑으로 내리면,&lt;/p&gt;
&lt;p&gt;어떤 DOM 이벤트가 Discrete 에 해당하는지,&lt;/p&gt;
&lt;p&gt;Continuous 에 해당하는지 알 수 있다.&lt;/p&gt;
&lt;p&gt;만약에, 모든 경우에 해당하지 않는 DOM 이벤트 이름을 전해주면,&lt;/p&gt;
&lt;p&gt;기본적으로 &lt;code&gt;DefaultEventPriority&lt;/code&gt; 를 반환 해 준다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;결과적으로 보았을 때,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;setCurrentUpdatePriority&lt;/code&gt; 를 사용하고 있는 이 파일의 코드는,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;dispatchDiscreteEvent&lt;/code&gt; 함수와, &lt;code&gt;dispatchContinuousEvent&lt;/code&gt; 함수 2 개였다.&lt;/p&gt;
&lt;p&gt;그런데, 여기서 문제가 생긴다. 이 코드를 참조하는 다른 파일이 없다는 것이다.&lt;/p&gt;
&lt;p&gt;아니 그렇다면, &lt;code&gt;setCurrentUpdatePriority&lt;/code&gt; 는 도대체 누가 전역적으로 설정해 주는 건가?&lt;/p&gt;
&lt;p&gt;그렇다면, &lt;code&gt;reconciler&lt;/code&gt; 패키지 함수에 있던 &lt;code&gt;setCurrentUpdatePriority&lt;/code&gt; 를 사용하는가?&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;물론, 위의 &lt;code&gt;reconciler&lt;/code&gt; 를 사용하는 경우도 있겠지만,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;function dispatchDiscreteEvent&lt;/code&gt; 코드 &amp;quot;바로 위&amp;quot; 를 보면, 이런 함수가 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;createEventListenerWrapperWithPriority(...)&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;export function createEventListenerWrapperWithPriority(
  targetContainer: EventTarget,
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
): Function {
  const eventPriority = getEventPriority(domEventName);
  let listenerWrapper;
  switch (eventPriority) {
    case DiscreteEventPriority:
      listenerWrapper = dispatchDiscreteEvent;
      break;
    case ContinuousEventPriority:
      listenerWrapper = dispatchContinuousEvent;
      break;
    case DefaultEventPriority:
    default:
      listenerWrapper = dispatchEvent;
      break;
  }
  return listenerWrapper.bind(
    null,
    domEventName,
    eventSystemFlags,
    targetContainer,
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 함수는 외부에서 &lt;code&gt;dispatchDiscreteEvent&lt;/code&gt; 를 즉각 불러와서 사용하게 만드는 것이 아니고,&lt;/p&gt;
&lt;p&gt;전달된 이벤트의 이름에 따라, EX - key down, mouse move, ... 등등&lt;/p&gt;
&lt;p&gt;리액트 Listener 에 다른 이벤트를 &amp;quot;주입&amp;quot; 하고 있었던 것이다.&lt;/p&gt;
&lt;p&gt;나의 경우, 버튼을 눌르는 예제를 들었기 때문에,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;case DiscreteEventPriority:&lt;/code&gt; 에 해당한다.&lt;/p&gt;
&lt;p&gt;그리고, 마지막으로 &lt;code&gt;listenerWrapper.bind&lt;/code&gt; 는,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;dispatchDiscreteEvent(...)&lt;/code&gt; 와 동일한 함수를 의미하게 된다.&lt;/p&gt;
&lt;p&gt;참고로, &lt;code&gt;.bind(null, ...)&lt;/code&gt; 은, &lt;code&gt;this&lt;/code&gt; 역할을 해 줄 인자를 없앤 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;-그래서 &lt;code&gt;createEventListenerWrapperWithPriority&lt;/code&gt; 는 무슨 역할을 하나?-&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이 메서드는 React 가 사용할 &amp;quot;Event Listener&amp;quot; 를 만드는데,&lt;/p&gt;
&lt;p&gt;단순한 리스너 --&amp;gt; DefaultEvent 가 아니라,&lt;/p&gt;
&lt;p&gt;앞으로 전담하게 될 이벤트들 - EX : 마우스, 키보드, 등등 DOM 이벤트들을&lt;/p&gt;
&lt;p&gt;&amp;quot;미리 결합시켜 놓는&amp;quot; 역할을 수행 해 준다. - &lt;code&gt;bind&lt;/code&gt; 로 묶음&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;잠깐만, 정리하고 넘어가겠습니다.&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;createEventListenerWrapperWithPriority(EventTarget, DOMEventName, EventSystemFlags)&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;내부에서 리액트 이벤트 리스너를 &amp;quot;우선순위&amp;quot; 와 함께 bind 해 주고, 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;code&gt;dispatchDiscreteEvent(DOMEventName, EventSystemFlags, EventTarget, ..)&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;DOM 이벤트 중, 버튼 클릭, 복사, 키 누름, 등등과 같은 단발성 이벤트에 대해서 동작한다.&lt;/li&gt;
&lt;li&gt;여기에서 &lt;code&gt;setCurrentUpdatePriority(...)&lt;/code&gt; 를 통해 리액트의 전역 값을 담고 있는,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ReactDOMSharedInternals.p&lt;/code&gt; 를 설정한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;p&lt;/code&gt; : priority, &lt;code&gt;T&lt;/code&gt; : 현재 트랜지션 상태라면 null, undefined 가 아님&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;&lt;code&gt;setCurrentUpdatePriority&lt;/code&gt; --&amp;gt; &lt;code&gt;resolveUpdatePriority() : EventPriority&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;test 환경을 제외한 &amp;quot;모든&amp;quot; 경우, 어떠한 함수나 객체이던 &lt;code&gt;ReactDOMInternals.p&lt;/code&gt; 를 &lt;br/&gt; 설정하기 위해 &lt;code&gt;setCurrentUpdatePriority&lt;/code&gt; 를 사용해야 한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;resolveUpdatePriority()&lt;/code&gt; 메서드는, 일반적인 경우에는  &lt;br/&gt; &lt;code&gt;ReactDOMSharedInternals.p&lt;/code&gt; --&amp;gt; 우선순위를 반환하는 것과 동일하고, &lt;br/&gt;&lt;/li&gt;
&lt;li&gt;만약, 리액트에서 특정되지 않은 DOM 이벤트라면, &lt;code&gt;DefaultEventPriority&lt;/code&gt; 를 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;&lt;code&gt;requestUpdateLane(fiber : Fiber) : Lane&lt;/code&gt; - 함수 내부의 &lt;br/&gt; &lt;code&gt;eventPriorityToLane(resolveUpdatePriority())&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;fiber&lt;/code&gt; 은 리액트 컴포넌트를, 현재 보고 있는 업데이트 우선순위와 변경 논리에 추가하기 위한 일종의 변형 정보이다. 즉, &lt;code&gt;fiber&lt;/code&gt; 는 리액트 컴포넌트 스스로를 담고 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;eventPriorityToLane&lt;/code&gt; 코드 위치 : &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactEventPriorities.js#L51&quot;&gt;위치&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;위 파일에서 &lt;code&gt;eventPriorityToLane&lt;/code&gt; 은, 버튼 클릭과 같은 이벤트에 &lt;br/&gt; &lt;code&gt;SyncLane&lt;/code&gt; 을 반환하도록 &amp;quot;명시&amp;quot; 되어 있다.&lt;/li&gt;
&lt;li&gt;이 말은, 최대한 빠르게 즉시 처리한다는 의미이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;&lt;code&gt;enqueueSetState(instance, payload:변경할 상태, callback)&lt;/code&gt; 함수 내부의 &lt;br/&gt; &lt;code&gt;const lane = requestUpdateLane(fiber)&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;이 메서드에서는 &lt;code&gt;fiber&lt;/code&gt;, &lt;code&gt;lane&lt;/code&gt;, &lt;code&gt;update&lt;/code&gt; 정보를 가지고, 동시성 업데이트 정보를 만들어서,&lt;/li&gt;
&lt;li&gt;스케쥴러에 등록한다.&lt;/li&gt;
&lt;li&gt;단, 아직 이 메서드의 동작 과정은 전부 파악하지 않은 상태이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;ol start=&quot;6&quot;&gt;
&lt;li&gt;&lt;code&gt;Component.prototype.setState&lt;/code&gt; 함수 내부의 &lt;br/&gt; &lt;code&gt;this.updater.enqueueSetState(this, partialState, callback, &amp;#39;setState&amp;#39;)&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;위의 &amp;quot;모든 과정&amp;quot; 을 트리거하는 함수이다.&lt;/li&gt;
&lt;li&gt;우리가 단순하게 사용했던 &lt;code&gt;state&lt;/code&gt; 에 대한 관리는, 위와 같은 방식 그 이상으로 관리된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;계층적으로 추적했으며, 내부의 함수 의미에 대해서도 알아보았다.&lt;/p&gt;
&lt;p&gt;조금만 더.... 조금만 가면 끝이 나오지 않을까....?&lt;/p&gt;
&lt;p&gt;정확하게 말하자면, 어떻게 &lt;code&gt;setState&lt;/code&gt; 가 실행되었을 때,&lt;/p&gt;
&lt;p&gt;이 상태 변환 이벤트에 대해서, 미리 &amp;quot;우선순위 (priority)&amp;quot; 가 설정되었는지 알지 못한다.&lt;/p&gt;
&lt;p&gt;아마, &lt;code&gt;createEventListenerWrapperWithPriority&lt;/code&gt; 메서드를 사용하는 &amp;quot;또 다른&amp;quot; 메서드가,&lt;/p&gt;
&lt;p&gt;현재 내가 시작한 &lt;code&gt;Component&lt;/code&gt; 를 등록하는 과정에서,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;this.state&lt;/code&gt; or &lt;code&gt;this.setState&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;상태(state) 를 사용하기 위해 등록한 컴포넌트&lt;/li&gt;
&lt;li&gt;특정 과정에서 인식한 이벤트 이름&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이를 인수로 삼고 있다고 예상된다.&lt;/p&gt;
&lt;p&gt;좀 더 나아가 보자.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;createEventListenerWrapperWithPriority&lt;/code&gt; 함수를 사용하는 장소는 다행히 단, 한 장소였다.&lt;/p&gt;
&lt;p&gt;바로, &lt;code&gt;DOMPluginEventSystem.js&lt;/code&gt; 파일의 &lt;code&gt;addTrappedEventListener&lt;/code&gt; 함수이다.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/main/packages/react-dom-bindings/src/events/DOMPluginEventSystem.js#L467&quot;&gt;코드 위치&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function addTrappedEventListener(
  targetContainer: EventTarget,
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
  isCapturePhaseListener: boolean,
  isDeferredListenerForLegacyFBSupport?: boolean,
) {
  let listener = createEventListenerWrapperWithPriority(
    targetContainer,
    domEventName,
    eventSystemFlags,
  );
  // If passive option is not supported, then the event will be
  // active and not passive.
  let isPassiveListener: void | boolean = undefined;
  if (passiveBrowserEventsSupported) {
    // Browsers introduced an intervention, making these events
    // passive by default on document. React doesn&amp;#39;t bind them
    // to document anymore, but changing this now would undo
    // the performance wins from the change. So we emulate
    // the existing behavior manually on the roots now.
    // https://github.com/facebook/react/issues/19651
    if (
      domEventName === &amp;#39;touchstart&amp;#39; ||
      domEventName === &amp;#39;touchmove&amp;#39; ||
      domEventName === &amp;#39;wheel&amp;#39;
    ) {
      isPassiveListener = true;
    }
  }

  targetContainer =
    enableLegacyFBSupport &amp;amp;&amp;amp; isDeferredListenerForLegacyFBSupport
      ? (targetContainer: any).ownerDocument
      : targetContainer;

  let unsubscribeListener;
  // When legacyFBSupport is enabled, it&amp;#39;s for when we
  // want to add a one time event listener to a container.
  // This should only be used with enableLegacyFBSupport
  // due to requirement to provide compatibility with
  // internal FB www event tooling. This works by removing
  // the event listener as soon as it is invoked. We could
  // also attempt to use the {once: true} param on
  // addEventListener, but that requires support and some
  // browsers do not support this today, and given this is
  // to support legacy code patterns, it&amp;#39;s likely they&amp;#39;ll
  // need support for such browsers.
  if (enableLegacyFBSupport &amp;amp;&amp;amp; isDeferredListenerForLegacyFBSupport) {
    const originalListener = listener;
    // $FlowFixMe[missing-this-annot]
    listener = function (...p) {
      removeEventListener(
        targetContainer,
        domEventName,
        unsubscribeListener,
        isCapturePhaseListener,
      );
      return originalListener.apply(this, p);
    };
  }
  // TODO: There are too many combinations here. Consolidate them.
  if (isCapturePhaseListener) {
    if (isPassiveListener !== undefined) {
      unsubscribeListener = addEventCaptureListenerWithPassiveFlag(
        targetContainer,
        domEventName,
        listener,
        isPassiveListener,
      );
    } else {
      unsubscribeListener = addEventCaptureListener(
        targetContainer,
        domEventName,
        listener,
      );
    }
  } else {
    if (isPassiveListener !== undefined) {
      unsubscribeListener = addEventBubbleListenerWithPassiveFlag(
        targetContainer,
        domEventName,
        listener,
        isPassiveListener,
      );
    } else {
      unsubscribeListener = addEventBubbleListener(
        targetContainer,
        domEventName,
        listener,
      );
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;알고 넘어가야 할 이 메서드 내부의 정보 :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;EventTarget&lt;/code&gt; 은 리액트 내부에서 정한 타입이 아니고, 브라우저 DOM 타입 그 자체를 의미한다.&lt;ul&gt;
&lt;li&gt;드디어.... 드디어! 리액트와 기본 DOM 이 연결되는 과정을 곧 볼 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;eventSysteFlags&lt;/code&gt; 는 숫자이며, 이따가 &lt;code&gt;0&lt;/code&gt; 이 들어가는 것을 볼 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Legacy&lt;/code&gt; 에 해당하지 않으므로, 관련된 변수는 &lt;code&gt;undefined&lt;/code&gt; 이거나, &lt;code&gt;false&lt;/code&gt; 이다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;listener&lt;/code&gt; 은, 이전에 다룬 &lt;code&gt;createEventListenerWrapperWithPriority(...)&lt;/code&gt; 결과물이다.&lt;ul&gt;
&lt;li&gt;결과물은 JS 기본 타입인 &lt;code&gt;Function&lt;/code&gt; 이며, DOM 에 부착 될 리스너 함수가 될 예정이다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DOMEventName&lt;/code&gt; 은 기본 문자열로, html 개발 시 사용하는 &lt;code&gt;click&lt;/code&gt; 과 같은 문자열이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;onClickCapture&lt;/code&gt; 속성을 등록하지 않으므로, &lt;code&gt;isCapturePhaseListener&lt;/code&gt; 은 &lt;code&gt;false&lt;/code&gt; 이다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;isPassiveListener&lt;/code&gt; 은 지속적으로 발생하는 이벤트 =&amp;gt; 스크롤, 마우스 휠 감지 리스너를 의미한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;p&gt;즉, 위의 정보들을 조합하면, 결국 &lt;code&gt;addTrappedEventListener&lt;/code&gt; 함수는,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;addEventBubbleListener(targetContainer : EventTarget, domEventName : string, listener : Function)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;의 형식으로 실행되고, 반환값은 &lt;code&gt;unsubscribeListener&lt;/code&gt; 에 할당된다.&lt;/p&gt;
&lt;p&gt;그런데, 이 변수는 지역 변수이지만, 사용되지 않으므로 신경쓰지 않아도 될 듯 하다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;결국, &amp;quot;클릭&amp;quot; 속성을 &amp;quot;버튼&amp;quot; 에 할당하고, 이를 &lt;code&gt;this.setState&lt;/code&gt; 로 연결했을 때,&lt;/p&gt;
&lt;p&gt;현재까지 추적된 메서드는 &lt;code&gt;addEventBubbleListener(...)&lt;/code&gt; 이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;react-dom-bindings/src/events/EventListener.js&lt;/code&gt; : &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/7a3ffef70339c10f8d65a27b88cd73bfbe13eb8a/packages/react-dom-bindings/src/events/EventListener.js#L10&quot;&gt;코드 위치&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;export function addEventBubbleListener(
  target: EventTarget,
  eventType: string,
  listener: Function,
): Function {
  target.addEventListener(eventType, listener, false);
  return listener;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;감격의 순간이다.&lt;/p&gt;
&lt;p&gt;이 메서드와 내부는 전부 React 의 얽혀있는 타입이 아니라, 순수 JS, DOM 관련 타입이다.&lt;/p&gt;
&lt;p&gt;즉,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;target&lt;/code&gt; : DOM 객체로, 빌트인 메서드로 &lt;code&gt;addEventListener&lt;/code&gt; 를 등록할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이는, 인자 3 개를 받는데, 1 -- 이벤트 문자열, 2 -- 이벤트 함수, 3 -- 콜백함수&lt;/p&gt;
&lt;p&gt;이렇게 되어 있다.&lt;/p&gt;
&lt;p&gt;즉, 우리는 &lt;code&gt;addTrappedEventListener&lt;/code&gt; 메서드가 받는 인자&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;domEventName&lt;/code&gt; : 이벤트 이름 - 문자열&lt;/li&gt;
&lt;li&gt;&lt;code&gt;targetContainer&lt;/code&gt; : 진짜 브라우저 DOM&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;그리고 &lt;code&gt;createEventListenerWrapperWithPriority(...)&lt;/code&gt; 를 통해 생성된&lt;/p&gt;
&lt;p&gt;리스너 함수를 &amp;quot;실제로&amp;quot; DOM 객체에 등록하게 된 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;그 다음은? (이제 곧 우리가 리액트에서 보던 문법이 나옴.)&lt;/h4&gt;
&lt;p&gt;이제, &lt;code&gt;addTrappedEventListener&lt;/code&gt; 를 호출하는 또 다른 클래스 혹은 함수를 찾아 보자.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;addTrappedEventListener&lt;/code&gt; 를 사용하는 또 다른 함수들은 전부 같은 파일 안에 존재한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;export function listenToNonDelegatedEvent(이벤트 이름, Element - 기본 DOM 타입)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;... listenToNativeEvent(이벤트 이름, 캡쳐여부 - false 예정, EventTarget)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;... listenToNativeEventForNonManagedEventTarget(이벤트 이름, 캡쳐여부, EventTarget)&lt;/code&gt; &lt;br&gt; 일반 DOM 이벤트인데, 부착 대상이 &lt;code&gt;window&lt;/code&gt; 와 같은 최상위 요소로, &lt;code&gt;DOM&lt;/code&gt; 요소가 아닌 경우.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;결과적으로, &lt;code&gt;listenToNativeEvent&lt;/code&gt; 가 실행된다. -&amp;gt; &amp;quot;버튼 클릭&amp;quot; 이벤트라서 그렇다.&lt;/p&gt;
&lt;p&gt;delegate 라는 단어는 &amp;quot;위임&amp;quot;, &amp;quot;대리&amp;quot; 하다 라는 의미인데,&lt;/p&gt;
&lt;p&gt;버튼 클릭은 Root 에 &amp;quot;위임&amp;quot; 혹은 &amp;quot;대리&amp;quot; 시켜야 할 이벤트는 아니다.&lt;/p&gt;
&lt;p&gt;따라서, &lt;code&gt;listenToNativeEvent&lt;/code&gt; 함수가 실행된다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;export function listenToNativeEvent(
  domEventName: DOMEventName,
  isCapturePhaseListener: boolean,
  target: EventTarget,
): void {
  if (__DEV__) {
    if (nonDelegatedEvents.has(domEventName) &amp;amp;&amp;amp; !isCapturePhaseListener) {
      console.error(
        &amp;#39;Did not expect a listenToNativeEvent() call for &amp;quot;%s&amp;quot; in the bubble phase. &amp;#39; +
          &amp;#39;This is a bug in React. Please file an issue.&amp;#39;,
        domEventName,
      );
    }
  }

  // listenToNonDelegatedEvent 와는 다른 시스템 플래그이다.
  let eventSystemFlags = 0;
  if (isCapturePhaseListener) {
    eventSystemFlags |= IS_CAPTURE_PHASE;
  }
  addTrappedEventListener(
    target,
    domEventName,
    eventSystemFlags,
    isCapturePhaseListener,
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;이 함수는 &lt;br/&gt; (이벤트 이름 - 문자열, 리액트가 관리하는 캡쳐링 이벤트 x - false, EventTarget) &lt;br/&gt; 을 인자로 받는다.&lt;/li&gt;
&lt;li&gt;만약에, 인자의 이벤트 이름이 &amp;quot;위임&amp;quot; 될 수 없는 이벤트이고, 리액트 루트에 캡쳐링되지 않았다면, &lt;br/&gt; 에러를 던진다.&lt;/li&gt;
&lt;li&gt;마지막으로 &lt;code&gt;addTrappedEventListener(...)&lt;/code&gt; 를 실행한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;그리고, 동일한 파일에서, &amp;quot;단 하나&amp;quot; 의 메서드에서, &lt;code&gt;listenToNativeEvent(...)&lt;/code&gt; 를 실행한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;listenToAllSupportedEvents(rootContainerElement : EventTarget)&lt;/code&gt; : &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/7a3ffef70339c10f8d65a27b88cd73bfbe13eb8a/packages/react-dom-bindings/src/events/DOMPluginEventSystem.js#L432&quot;&gt;코드 위치&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const listeningMarker = &amp;#39;_reactListening&amp;#39; + Math.random().toString(36).slice(2);

export function listenToAllSupportedEvents(rootContainerElement: EventTarget) {
  if (!(rootContainerElement: any)[listeningMarker]) {
    (rootContainerElement: any)[listeningMarker] = true;
    allNativeEvents.forEach(domEventName =&amp;gt; {
      // We handle selectionchange separately because it
      // doesn&amp;#39;t bubble and needs to be on the document.
      if (domEventName !== &amp;#39;selectionchange&amp;#39;) {
        if (!nonDelegatedEvents.has(domEventName)) {
          listenToNativeEvent(domEventName, false, rootContainerElement);
        }
        listenToNativeEvent(domEventName, true, rootContainerElement);
      }
    });
    const ownerDocument =
      (rootContainerElement: any).nodeType === DOCUMENT_NODE
        ? rootContainerElement
        : (rootContainerElement: any).ownerDocument;
    if (ownerDocument !== null) {
      // The selectionchange event also needs deduplication
      // but it is attached to the document.
      if (!(ownerDocument: any)[listeningMarker]) {
        (ownerDocument: any)[listeningMarker] = true;
        listenToNativeEvent(&amp;#39;selectionchange&amp;#39;, false, ownerDocument);
      }
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;먼저, &lt;code&gt;listeningMarker&lt;/code&gt; 에 집중 해 보자. 결과적으로는 &lt;code&gt;_reactListeneing19zisb7tn...&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;이러한 문자열을 의미하게 된다.&lt;/p&gt;
&lt;p&gt;그런데, 이 메서드 내부에서는 인자로 받는 &lt;code&gt;rootContainerElement : EventTarget&lt;/code&gt; 에,&lt;/p&gt;
&lt;p&gt;만약 &lt;code&gt;_reactListening19zisb7tn....&lt;/code&gt; 와 같이 정확한 &amp;quot;Key&amp;quot; 가 이미 설정되거나, 존재한다면,&lt;/p&gt;
&lt;p&gt;&amp;quot;아무것도&amp;quot; 실행되지 않는다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;if(!rootContainerElement[listeningMarker])&lt;/code&gt; 조건문 때문이다. - 조금 축약함.&lt;/p&gt;
&lt;p&gt;이는 &lt;code&gt;listenToAllSupportedEvents&lt;/code&gt; 가 단 한번만 실행된다는 강력한 반증이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그리고, allNativeEvents 는 뭘까?&lt;/p&gt;
&lt;p&gt;일단, 위에서 NonDelegated 와 Native 를 나누어 메서드를 실행했기 때문에,&lt;/p&gt;
&lt;p&gt;단발성 이벤트만 &lt;code&gt;allNativeEvents&lt;/code&gt; 에 들어갔다고 생각했다.&lt;/p&gt;
&lt;p&gt;그런데, &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/main/packages/react-dom-bindings/src/events/DOMEventNames.js&quot;&gt;allNativeEvents 코드 위치&lt;/a&gt; 를 보고, &amp;quot;거의 대부분의 DOM 이벤트&amp;quot; 가 해당된다는 것을 알았다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;allNativeEvents&lt;/code&gt; 는 순회하며, 루트 컨테이너와 함께, 이벤트 이름으로 넘어간다.&lt;/p&gt;
&lt;p&gt;이 때, 버블링, 즉, 단발성 이벤트의 경우&lt;/p&gt;
&lt;p&gt;&lt;code&gt;listenToNativeEvent(이벤트 이름, false, 루트 컨테이너)&lt;/code&gt; --&amp;gt; 버블링&lt;/p&gt;
&lt;p&gt;로 넘어가며,&lt;/p&gt;
&lt;p&gt;지속성 이벤트의 경우, &lt;code&gt;listenToNativeEvent(이벤트 이름, true, 루트 컨테이너)&lt;/code&gt; --&amp;gt; 캡쳐링&lt;/p&gt;
&lt;p&gt;으로 넘어간다.&lt;/p&gt;
&lt;p&gt;그 이후는 현재 루트 노드가 &lt;code&gt;selectionchange&lt;/code&gt; 이벤트를 수용하기 위해 수행하는 로직이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이제, &lt;code&gt;listenToAllSupportedEvents&lt;/code&gt; 를 사용하는 함수 혹은 클래스를 찾아보자.&lt;/p&gt;
&lt;p&gt;이 메서드를 사용하는 함수는 총 2 개가 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;createRoot(Element | Document | DocumentFragment, CreateRootOptions)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hydrateRoot(Document | Element, ReactNodeList, HydrateRootOptions)&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;하하하하하하하하하하!!!!&lt;/p&gt;
&lt;p&gt;드디어 리액트 개발 시 루트 엘리먼트를 지정하는 메서드인 &lt;code&gt;createRoot&lt;/code&gt; 까지 도달했다!!!!&lt;/p&gt;
&lt;p&gt;&lt;code&gt;createRoot&lt;/code&gt; : &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/b42341ddc757129db062298f9fe3ad041c580d2a/packages/react-dom/src/client/ReactDOMRoot.js#L171&quot;&gt;코드 위치&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;export function createRoot(
  container: Element | Document | DocumentFragment,
  options?: CreateRootOptions,
): RootType {
  if (!isValidContainer(container)) {
    throw new Error(&amp;#39;Target container is not a DOM element.&amp;#39;);
  }

  // __DEV__ 상황 일 때만 발동 (지금은 별 상관없는 메서드)
  warnIfReactDOMContainerInDEV(container);

  const concurrentUpdatesByDefaultOverride = false;
  let isStrictMode = false;
  let identifierPrefix = &amp;#39;&amp;#39;;
  let onUncaughtError = defaultOnUncaughtError;
  let onCaughtError = defaultOnCaughtError;
  let onRecoverableError = defaultOnRecoverableError;
  let onDefaultTransitionIndicator = defaultOnDefaultTransitionIndicator;
  let transitionCallbacks = null;

  if (options !== null &amp;amp;&amp;amp; options !== undefined) {
    // 인자로 넘어온 options 가 존재한다면,
    // 바로 위의 let 변수들은 options 에 담겨온 값으로 할당된다.
  }

  /**
   * RootTag = 0 | 1;
   * LegacyRoot = 0;
   * ConcurrentRoot = 1;
   */
  const root = createContainer(
    container,
    ConcurrentRoot, --&amp;gt; 1
    null,
    isStrictMode,
    concurrentUpdatesByDefaultOverride,
    identifierPrefix,
    onUncaughtError,
    onCaughtError,
    onRecoverableError,
    onDefaultTransitionIndicator,
    transitionCallbacks,
  );
  markContainerAsRoot(root.current, container);

  const rootContainerElement: Document | Element | DocumentFragment
    = !disableCommentsAsDOMContainers &amp;amp;&amp;amp; container.nodeType === COMMENT_NODE
      ? (container.parentNode: any)
      : container;
  listenToAllSupportedEvents(rootContainerElement);

  // $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions
  return new ReactDOMRoot(root);
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;createRoot&lt;/code&gt; 는 어디에 사용될까?&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt; ... &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;div id=&amp;quot;root&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그리고, 이 파일과 연계된 JS 파일에는,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// jsx 파일이라고 가정한다.
const rootDOM = document.getElementById(&amp;quot;root&amp;quot;);

const root = ReactDOM.createRoot(rootDOM);
root.render(
  ...
)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이러한 형식으로, 리액트는 실제 루트가 될 DOM 하나를 가져와서,&lt;/p&gt;
&lt;p&gt;이를 리액트 값(객체) 형식의 루트로 변형한다.&lt;/p&gt;
&lt;p&gt;그 후, 생성된 리액트 루트를 렌더 함수를 통해 재귀적 렌더링을 시작한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;span style=&quot;color : skyblue&quot;&gt;자, 이제 우선순위가 어떻게 결정되는지 정리하자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;우리는 아직 2번 메서드&lt;/strong&gt; &lt;code&gt;resolveUpdatePriority()&lt;/code&gt; 의 비밀을 풀기 위해 먼 길을 달려왔다.&lt;/p&gt;
&lt;p&gt;인수가 존재하지 않던 이 메서드가 어떻게 우선순위를 반환했는지 이해하면,&lt;/p&gt;
&lt;p&gt;3 번 메서드로 넘어갈 수 있다. (&lt;code&gt;createUpdate(lane) : Update&amp;lt;State&amp;gt;&lt;/code&gt;)&lt;/p&gt;
&lt;h3&gt;3 번 메서드로 넘어가기 전, 그래프 정리&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TB

subgraph createRoot
    createRoot-role1(&amp;quot;사용자는 이 메서드를 통해 리액트 루트 컨테이너를 생성할 수 있다.&amp;quot;)
    createRoot-role2(&amp;quot;여기서 생성된 리액트 컨테이너-루트 를 인자로 listenToAllSupportedEvents 를 실행한다.&amp;quot;)
end

subgraph listenToAllSupportedEvents
    listenToAllSupportedEvents-role1(&amp;quot;생성된 루트 컨테이너에 &amp;#39;대부분의 이벤트&amp;#39; 리스너를 달아준다.&amp;quot;)
    listenToAllSupportedEvents-role2(&amp;quot;루프 과정 내부에서, listenToNativeEvent 메서드를 실행하며, 루트 컨테이너를 인자로 넣어준다.&amp;quot;)
end

subgraph listenToNativeEvent
    listenToNativeEvent-role1(&amp;quot;단발성 이벤트 -EX 클릭- 와 같은 리스너, 혹은 스크롤과 같은 연속성 이벤트이나, 캡쳐링이 true 인 이벤트를 감지하며, 아니라면 오류를 검출한다.&amp;quot;)
    listenToNativeEvent-role2(&amp;quot;이 때, 시스템 플래그를 0 으로 설정해 두며, addTrappedEventListener 메서드를 &amp;#39;실행&amp;#39; 한다.&amp;quot;)
end

subgraph addTrappedEventListener
    addTrappedEventListener-role1(&amp;quot;다양한 갈래의 이벤트 등록 함수들을 케이스에 따라 실행 하는 역할을 해준다.&amp;quot;)
    addTrappedEventListener-role2(&amp;quot;addEvent... 이름을 가진 다양한 함수를 인수에 따라 실행시켜주는 역할을 해 준다.&amp;quot;)
    addTrappedEventListener-role3(&amp;quot;addEvent... 이름을 가진 함수들은, 실제 targetContainer 인 React 루트에 이벤트 리스너를 JS 형태로 등록한다.&amp;quot;)
    addTrappedEventListener-role4(&amp;quot;실제로 리스너를 등록 해 주기 위해, createEventListenerWrapperWithPriority(...) 메서드를 실행하여 리스너를 반환받는다.&amp;quot;)
end

subgraph createEventListenerWrapperWithPriority
    createEventListenerWrapperWithPriority-role1(&amp;quot;인수로 내려온 domEventName 문자열에 따라, getEventPriority(domEventName) 실행으로 어떤 우선순위인지 확인한다.&amp;quot;)
    createEventListenerWrapperWithPriority-role2(&amp;quot;이 이벤트 우선순위는 동적으로 결정되지 않으며, 단발성 이벤트, 연속성 이벤트에 따라 다른 이벤트 함수가 &amp;#39;그대로&amp;#39; 바인딩된다.&amp;quot;)
    createEventListenerWrapperWithPriority-role3(&amp;quot;이 과정에서, 클릭 이벤트에 대해 dispatchDiscreteEvent 함수가 이 메서드에 그대로 바인딩된다.&amp;quot;)
end

subgraph dispatchDiscreteEvent
    dispatchDiscreteEvent-role1(&amp;quot;이 함수는 단발성 이벤트를 위해 실행되는 함수이다.&amp;quot;)
    dispatchDiscreteEvent-role2(&amp;quot;전역적으로 공유되는 ReactSharedInternals 변수 중 트랜지션과 우선순위를 잠깐 저장하고, 트랜지션은 null 로 만든다.&amp;quot;)
    dispatchDiscreteEvent-role3(&amp;quot;전역적 공유 변수 Reac.. 의 우선순위를 DiscreteEventPriority 로 변경한다.&amp;quot;)
    dispatchDiscreteEvent-role4(&amp;quot;dispatchEvent 메서드를 실행한다.&amp;quot;)
    dispatchDiscreteEvent-role5(&amp;quot;이벤트 처리 이후, 다시 전역 객체 ReactSharedInternals 의 트랜지션과 우선순위를 그대로 다시 할당 해 준다.&amp;quot;)
end

subgraph React-Component
    subgraph values
        this-props
        this-context
        this-updater
        this-props ~~~ this-context ~~~ this-updater
    end
    subgraph prototypes
        isReactComponent
        setState
        forceUpdate
        isReactComponent ~~~ setState ~~~ forceUpdate
    end
end

subgraph Component-setState
    Component-setState-role1(&amp;quot;변경할 상태가 함수나 객체인지 확인한다.&amp;quot;);
    Component-setState-role2(&amp;quot;컴포넌트 자신과 변경할 상태, 콜백 정보를 enqueueSetState 에 넘긴다.&amp;quot;)
end

subgraph enqueueSetState
    enqueueSetState-role1(&amp;quot;클래스 컴포넌트에서 this.setState 를 실행했을 때, 이 메서드가 실행된다.&amp;quot;)
    enqueueSetState-role2(&amp;quot;Fiber, Lane, Update&amp;lt;State&amp;gt; 정보를 활용하여 업데이트를 실행한다.&amp;quot;)
end

subgraph ReactSharedInternals
    ReactInternals-role1(&amp;quot;코드 실행 시 다양한 전역 컨텍스트를 관리함&amp;quot;)

    ReactInternals-method1(&amp;quot;setCurrentUpdatePriority(우선순위)&amp;quot;)
    ReactInternals-method2(&amp;quot;getCurrentUpdatePriority() : 전역 우선순위&amp;quot;)
end

subgraph requestUpdateLane
    requestUpdateLane-role1(&amp;quot;setState 를 실행한 인스턴스를 Fiber 클래스로 변형한 인자를 받는다.&amp;quot;)
    requestUpdateLane-role2(&amp;quot;현재 상태가 트랜지션에 속한 상태인지, 아닌지에 따라 반환하는 lane 이 달라진다.&amp;quot;)
    requestUpdateLane-role3(&amp;quot;Lane == EventPriority 타입 동일이기 때문에, 클릭의 경우 SyncLane 을 반환한다.&amp;quot;)
    requestUpdateLane-role4(&amp;quot;이 메서드에서 resolveUpdatePriority() 메서드를 실행한다.&amp;quot;)
end

subgraph eventPriorityToLane
    eventPriorityToLane-role1(&amp;quot;resolveUpdatePriority 메서드로 우선순위를 받아온다.&amp;quot;)
    eventPriorityToLane-role2(&amp;quot;우선순위는 Lane 타입과 매칭되어 트랜지션 상황이 아닌 이상, 각각의 이벤트에 대해 매칭된 Lane, 클릭의 경우 SyncLane 을 반환한다.&amp;quot;)
end

subgraph resolveUpdatePriority
    resolveUpdatePriority-role1(&amp;quot;전역 객체 ReactDOMSharedInternals 의 우선순위 p 를 빼낸다.&amp;quot;)
    resolveUpdatePriority-role2(&amp;quot;추출한 우선순위는 Lane 타입과 일치하며, Discrete 이벤트의 경우 SyncLane 과 동일하다.&amp;quot;)
end



createRoot --&amp;gt; listenToAllSupportedEvents

listenToAllSupportedEvents --&amp;gt; listenToNativeEvent

listenToNativeEvent --&amp;gt; addTrappedEventListener

addTrappedEventListener --&amp;gt; createEventListenerWrapperWithPriority

createEventListenerWrapperWithPriority --&amp;gt; dispatchDiscreteEvent

dispatchDiscreteEvent &amp;lt;--&amp;gt; ReactSharedInternals

React-Component --&amp;gt; Component-setState

Component-setState --&amp;gt; enqueueSetState

enqueueSetState --&amp;gt; requestUpdateLane

requestUpdateLane --&amp;gt; eventPriorityToLane

eventPriorityToLane --&amp;gt; resolveUpdatePriority

resolveUpdatePriority --&amp;gt; ReactSharedInternals&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 과정에서, 아직 &lt;code&gt;dispatchEvent&lt;/code&gt; 라는 중요한 메서드를 파악하지 못했다.&lt;/p&gt;
&lt;p&gt;사실 그 내용도 작성하고 싶지만, 요약해서 말하는 것이 맞다고 판단했다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;이후에 &lt;code&gt;dispatchEvent&lt;/code&gt; 를 추적 및 조사했는데, &lt;br/&gt;&lt;br&gt;이 메서드 내부에서 &lt;code&gt;executionContext&lt;/code&gt; 라는 ReactFiberWorkLoop.js 파일 전역 변수를 &lt;br/&gt;&lt;br&gt;&lt;code&gt;BatchedContext = 0b001&lt;/code&gt; 로 변경한다. (나중에 중요함)&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;br/&gt;

&lt;p&gt;위의 그래프는, 이러한 의미를 담는다.&lt;/p&gt;
&lt;p&gt;컴포넌트가 &lt;code&gt;setState&lt;/code&gt; 라는 메서드를 실행하게 되었을 때,&lt;/p&gt;
&lt;p&gt;결과적으로 업데이트를 실행할 레인을 전역적으로 구하기 위해 &lt;code&gt;resolveUpdatePriority&lt;/code&gt; 를 실행한다.&lt;/p&gt;
&lt;p&gt;그런데, 이 메서드에는 &lt;strong&gt;&amp;quot;인자&amp;quot;&lt;/strong&gt; 가 없다. 어떻게 이 변경점에 대해 우선순위를 판별하는 것인가?&lt;/p&gt;
&lt;p&gt;따라서, &lt;code&gt;resolveUpdatePriority&lt;/code&gt; 메서드는 &lt;code&gt;ReactSharedInternals.p&lt;/code&gt; 와 완벽하게 연관관계가 존재한다.&lt;/p&gt;
&lt;p&gt;따라서, 이 값을 관리하는 &lt;code&gt;setCurrentUpdatePriority&lt;/code&gt; 메서드를 사용하는 또 다른 함수를&lt;/p&gt;
&lt;p&gt;&amp;quot;역으로&amp;quot; 추적하는 것이다.&lt;/p&gt;
&lt;p&gt;이 때, 나는 &amp;quot;버튼 클릭&amp;quot; 이라는 가상의 시나리오를 만들어서 추적했다.&lt;/p&gt;
&lt;p&gt;결과적으로, &lt;code&gt;createRoot&lt;/code&gt; 라는 리액트 로직 최상단의 메서드까지 도달했다.&lt;/p&gt;
&lt;p&gt;그러나, 아직 풀리지 않은 의문점들은 여전히 많다.&lt;/p&gt;
&lt;p&gt;즉,&lt;/p&gt;
&lt;p&gt;&amp;quot;버튼 클릭&amp;quot; 시, dispatchDiscreteEvent 를 실행하는 주체가 ReactDOM 의 Root 라는 것.&lt;/p&gt;
&lt;p&gt;물론, 연속적 이벤트의 경우, Root 가 관리하기 보다는, 해당 이벤트를 관리하는 컴포넌트가 할당된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;역추적 중 알게 된 사실&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;루트 돔이 생성되는 순간, &amp;quot;대부분의&amp;quot; 이벤트들은 미리 루트 돔에 등록된다.&lt;/p&gt;
&lt;p&gt;우리가 일반적으로 사용하는 DOM 에 리스너를 장착하는 방식과 조금 달리,&lt;/p&gt;
&lt;p&gt;위임 가능한 이벤트, 불가능한 이벤트로 나뉘어 등록된다. (delegated, nonDelegated)&lt;/p&gt;
&lt;p&gt;루트 돔이 &amp;quot;대부분의&amp;quot; 이벤트 리스너를 등록하는 과정에서, 각각의 이벤트 리스너는 고유의 우선순위 상수를 부여받는다.&lt;/p&gt;
&lt;p&gt;그리고 이러한 우선순위는 해당 이벤트가 발생했을 때 &amp;quot;정해진&amp;quot; Priority 를 전역으로 설정한다.&lt;/p&gt;
&lt;p&gt;즉, 컴포넌트에서 setState 가 실행된다면, 이 Priority 를 읽어 Lane 을 얻게 된다.&lt;/p&gt;
&lt;p&gt;그러나, 특정 로딩, 즉, 트랜지션과 함께 묶이는 상황, Transition 과 묶인 setState 라면,&lt;/p&gt;
&lt;p&gt;그 우선순위는 바뀐다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;정확한 것은 리액트 공식문서의 &lt;code&gt;useTransition&lt;/code&gt; 을 검색해 보길 바랍니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;br/&gt;

&lt;h4&gt;3. createUpdate(lane) : Update&lt;State&gt;&lt;/h4&gt;
&lt;p&gt;드디어 길고 길었던 &amp;quot;우선순위 설정&amp;quot; 에 대한 의문을 풀고, 넘어왔다.&lt;/p&gt;
&lt;p&gt;우리가 &lt;code&gt;createUpdate(lane)&lt;/code&gt; 의 로직을 보려는 것은,&lt;/p&gt;
&lt;p&gt;아직 &lt;code&gt;enqueueSetState&lt;/code&gt; 에 대한 로직 파악이 끝나지 않았기 때문이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;classComponentUpdater.enqueueSetState == Component.(this.updater)&lt;/code&gt; : &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/4db4b21c63ebc4edc508c5f7674f9df50d8f9744/packages/react-reconciler/src/ReactFiberClassComponent.js#L168&quot;&gt;코드 위치&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;enqueueSetState(inst: any, payload: any, callback) {
    const fiber = getInstance(inst); // 완료 - 컴포넌트의 fiber 정보 객체로 추출
    const lane = requestUpdateLane(fiber); // 완료 - 추출된 fiber 로 우선순위 레인 추출

    const update = createUpdate(lane); // 이제 이 로직을 살펴 볼 차례
    update.payload = payload;
    if (callback !== undefined &amp;amp;&amp;amp; callback !== null) {
      if (__DEV__) {
        warnOnInvalidCallback(callback);
      }
      update.callback = callback;
    }

    const root = enqueueUpdate(fiber, update, lane);
    if (root !== null) {
      startUpdateTimerByLane(lane, &amp;#39;this.setState()&amp;#39;);
      scheduleUpdateOnFiber(root, fiber, lane);
      entangleTransitions(root, fiber, lane);
    }

    if (enableSchedulingProfiler) {
      markStateUpdateScheduled(fiber, lane);
    }
  },&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;createUpdate(lane : Lane) : Update&amp;lt;mixed&amp;gt;&lt;/code&gt; : &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/d2a288febf61a1755b78ce98b3cb17dd412b81e3/packages/react-reconciler/src/ReactFiberClassUpdateQueue.js#L210&quot;&gt;코드 위치&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// Update&amp;lt;State&amp;gt; 타입
export type Update&amp;lt;State&amp;gt; = {
  lane: Lane,

  tag: 0 | 1 | 2 | 3,
  payload: any,
  callback: (() =&amp;gt; mixed) | null,

  next: Update&amp;lt;State&amp;gt; | null,
};

export const UpdateState = 0;
export const ReplaceState = 1;
export const ForceUpdate = 2;
export const CaptureUpdate = 3;

export function createUpdate(lane: Lane): Update&amp;lt;mixed&amp;gt; {
  const update: Update&amp;lt;mixed&amp;gt; = {
    lane,

    tag: UpdateState,
    payload: null,
    callback: null,

    next: null,
  };
  return update;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이는 업데이트 상태를 나타낼 특정 객체를 만들어내는 일종의 new .... 와 비슷한 과정이다.&lt;/p&gt;
&lt;p&gt;업데이트는 어떤 Lane 을 타야 하는지, 어떤 태그를 가지는지,&lt;/p&gt;
&lt;p&gt;어떻게 바꿔야 하는지(payload), 이 업데이트가 끝나면서 어떤 콜백 함수가 실행되어야 하는지,&lt;/p&gt;
&lt;p&gt;그리고 &lt;code&gt;next&lt;/code&gt; 다음 업데이트는 무엇인지에 대한 정보를 담게 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이 초기화 된 &lt;code&gt;Update&amp;lt;State&amp;gt;&lt;/code&gt; 를 받고 나서,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;update.payload = payload&lt;/code&gt; == this.setState 내부에 있던 객체 혹은 함수&lt;/p&gt;
&lt;p&gt;&lt;code&gt;update.callback = callback&lt;/code&gt; == 개발자가 원하면 콜백 함수를 넣을 수 있음&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;4. enqueueUpdate(fiber, update, lane) : FiberRoot | null&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;enqueueSetState(inst: any, payload: any, callback) {
    const fiber = getInstance(inst); // 완료 - 컴포넌트의 fiber 정보 객체로 추출
    const lane = requestUpdateLane(fiber); // 완료 - 추출된 fiber 로 우선순위 레인 추출

    // 완료 - 우선순위 레인으로 Update&amp;lt;State&amp;gt; 초기화 객체 가져옴. tag : 0 인 상태.
    const update = createUpdate(lane);
    update.payload = payload; // 업데이트 객체에 우리가 삽입한 변경 객체 혹은 함수를 주입
    if (callback !== undefined &amp;amp;&amp;amp; callback !== null) {
      // ...
      update.callback = callback; // this.setState 에 인자로 준 콜백 함수 주입
    }

    const root = enqueueUpdate(fiber, update, lane); // 이 코드를 파헤칠 시간
    if (root !== null) {
      startUpdateTimerByLane(lane, &amp;#39;this.setState()&amp;#39;);
      scheduleUpdateOnFiber(root, fiber, lane);
      entangleTransitions(root, fiber, lane);
    }

    if (enableSchedulingProfiler) {
      markStateUpdateScheduled(fiber, lane);
    }
  },&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여태까지 조사 한 것들과, &lt;code&gt;enqueueUpdate(fiber, update, lane)&lt;/code&gt; 을 보자면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;this.setState&lt;/code&gt; 를 실행시킨 컴포넌트의 &lt;code&gt;Fiber&lt;/code&gt; 형태,&lt;/p&gt;
&lt;p&gt;그리고 초기화 된 &lt;code&gt;Update&amp;lt;State&amp;gt;&lt;/code&gt; 객체,&lt;/p&gt;
&lt;p&gt;현재 이벤트에 대한 우선순위를 기반으로 설정된 &lt;code&gt;Lane&lt;/code&gt; 이라는 바이너리 값까지 들어간다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;enqueue&lt;/code&gt; 는 &amp;quot;꼬리&amp;quot; 를 의미한다. 아마 이 메서드는 새로운 업데이트를 큐에 넣는다는 의미가 아닐까?&lt;/p&gt;
&lt;p&gt;라는 추측하며 코드를 분석 해 본다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;enqueueUpdate(fiber, update, lane) : FiberRoot | null&lt;/code&gt; : &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/d2a288febf61a1755b78ce98b3cb17dd412b81e3/packages/react-reconciler/src/ReactFiberClassUpdateQueue.js#L223&quot;&gt;코드위치&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;export function enqueueUpdate&amp;lt;State&amp;gt;(
  fiber: Fiber,
  update: Update&amp;lt;State&amp;gt;,
  lane: Lane,
): FiberRoot | null {
  const updateQueue = fiber.updateQueue;
  if (updateQueue === null) {
    // 주석 해석 : 오로지 파이버가 언마운트 되었을 때만 null 로 반환한다.
    return null;
  }

  const sharedQueue: SharedQueue&amp;lt;State&amp;gt; = (updateQueue: any).shared;

  if (__DEV__) {
    // 개발 상황 시
  }

  if (isUnsafeClassRenderPhaseUpdate(fiber)) {
    /**
     * UNSAFE_ 접두어가 붙은 메서드를 사용했을 때 이 분기로 들어온다.
     * 그러나, 개발할 때 이런 접두어를 쓰지 않았다면 신경쓰지 않아도 된다.
     */
    return unsafe_markUpdateLaneFromFiberToRoot(fiber, lane);
  } else {
    // 결국 이 분기가 선택됨.
    return enqueueConcurrentClassUpdate(fiber, sharedQueue, update, lane);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 메서드는 &lt;code&gt;enqueueConcurrentClassUpdate&lt;/code&gt; 메서드를 실행하기 위해,&lt;/p&gt;
&lt;p&gt;특별한 인자 &lt;code&gt;sharedQueue&lt;/code&gt; 를 기존 인스턴스의 &lt;code&gt;fiber&lt;/code&gt; 로부터 &lt;code&gt;updateQueue&lt;/code&gt; 를 추출하고 있다.&lt;/p&gt;
&lt;p&gt;나는 이 글의 초창기 수준에서 살펴보았던 &lt;code&gt;Fiber&lt;/code&gt; 타입을 다시 열어보았다 : &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/ed077194b5b76df6f8fdbf805e1b895e2deb5a07/packages/react-reconciler/src/ReactInternalTypes.js#L88&quot;&gt;Fiber 타입 선언&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;여기서 &lt;code&gt;Fiber&lt;/code&gt; 타입의 &lt;code&gt;updateQueue&lt;/code&gt; 를 살펴보려고 하는데, 전혀 발견되지 않았다.&lt;/p&gt;
&lt;p&gt;즉, 특정 코드에서 &lt;code&gt;updateQueue&lt;/code&gt; 를 타입에 맞지 않게 주입해 줬다는 결론에 도달했다.&lt;/p&gt;
&lt;p&gt;이번에는 왠일로, `updateQueue 를 주입해 준 코드를 쉽게 찾을 수 있었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;enqueueUpdate&lt;/code&gt; 코드가 작성된 동일한 파일에서, 176 번째 줄&lt;/p&gt;
&lt;p&gt;&lt;code&gt;function initializeUpdateQueue&amp;lt;State&amp;gt;(fiber : Fiber) : void&lt;/code&gt; 에서 찾았다.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/d2a288febf61a1755b78ce98b3cb17dd412b81e3/packages/react-reconciler/src/ReactFiberClassUpdateQueue.js#L176&quot;&gt;코드 위치&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;export function initializeUpdateQueue&amp;lt;State&amp;gt;(fiber: Fiber): void {
  const queue: UpdateQueue&amp;lt;State&amp;gt; = {
    baseState: fiber.memoizedState,
    firstBaseUpdate: null,
    lastBaseUpdate: null,
    shared: {
      pending: null,
      lanes: NoLanes,
      hiddenCallbacks: null,
    },
    callbacks: null,
  };
  fiber.updateQueue = queue;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;quot;업데이트 큐를 초기화&amp;quot; 하는 과정에서,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;fiber.updateQueue&lt;/code&gt; 의 값에 어떠한 값이 초기화 되어 들어가는지 보았다.&lt;/p&gt;
&lt;p&gt;그리고, 기존 코드에서는 &lt;code&gt;SharedQueue&amp;lt;State&amp;gt;&lt;/code&gt; 타입으로 &lt;code&gt;fiber.updateQueue.shared&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;이 값을 추출하고 있었다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Update&amp;lt;State&amp;gt; &amp;amp;&amp;amp; SharedQueue&amp;lt;State&amp;gt; &amp;amp;&amp;amp; UpdateQueue&amp;lt;State&amp;gt;&lt;/code&gt; : &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/d2a288febf61a1755b78ce98b3cb17dd412b81e3/packages/react-reconciler/src/ReactFiberClassUpdateQueue.js#L141&quot;&gt;코드 위치&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;export type Update&amp;lt;State&amp;gt; = {
  lane: Lane,

  tag: 0 | 1 | 2 | 3,
  payload: any,
  callback: (() =&amp;gt; mixed) | null,

  next: Update&amp;lt;State&amp;gt; | null,
};

export type SharedQueue&amp;lt;State&amp;gt; = {
  pending: Update&amp;lt;State&amp;gt; | null,
  lanes: Lanes,
  hiddenCallbacks: Array&amp;lt;() =&amp;gt; mixed&amp;gt; | null,
};

export type UpdateQueue&amp;lt;State&amp;gt; = {
  baseState: State,
  firstBaseUpdate: Update&amp;lt;State&amp;gt; | null,
  lastBaseUpdate: Update&amp;lt;State&amp;gt; | null,
  shared: SharedQueue&amp;lt;State&amp;gt;,
  callbacks: Array&amp;lt;() =&amp;gt; mixed&amp;gt; | null,
};&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;나는 각 객체의 타입이 어떤 형식을 갖추는지 정확히 알겠으나,&lt;/p&gt;
&lt;p&gt;어찌하여 업데이트 Queue와, 공유 Queue 가 따로 만들어져 있는지 알 수는 없었다. (곧 알게 되겠지만.)&lt;/p&gt;
&lt;p&gt;하나 확실한 것은, 우리가 &lt;code&gt;this.setState(State)&lt;/code&gt; 로 실행한 이후,&lt;/p&gt;
&lt;p&gt;새로운 &lt;code&gt;State&lt;/code&gt; 요청에 대해 업데이트를 수행하기 위해, 기본적인 유닛은 &lt;code&gt;Update&amp;lt;State&amp;gt;&lt;/code&gt; 라는 것이다.&lt;/p&gt;
&lt;p&gt;참고로 &lt;code&gt;mixed&lt;/code&gt; 는, 어떠한 형태던 반환될 수 있는 일종의 표식이라고 이해 할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;자 이제, &lt;code&gt;enqueueUpdate(fiber, update, lane) : FiberRoot | null&lt;/code&gt; 메서드에 내부의&lt;/p&gt;
&lt;p&gt;&lt;code&gt;return enqueueConcurrentClassUpdate(fiber, sharedQueue, update, lane)&lt;/code&gt; 을&lt;/p&gt;
&lt;p&gt;이제 알아볼 차례다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;enqueueConcurrentClassUpdate(fiber, sharedQueue, update, lane)&lt;/code&gt; : &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/7a3ffef70339c10f8d65a27b88cd73bfbe13eb8a/packages/react-reconciler/src/ReactFiberConcurrentUpdates.js#L152&quot;&gt;코드 위치&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// 타입의 이름이 바뀐 것을 알 수 있음
import type {
  SharedQueue as ClassQueue,
  Update as ClassUpdate,
} from &amp;#39;./ReactFiberClassUpdateQueue&amp;#39;;

// ConcurrentUpdate 와 ConcurrentQueue 타입에 대한 추가 정보
export type ConcurrentUpdate = {
  next: ConcurrentUpdate,
  lane: Lane,
};
type ConcurrentQueue = {
  pending: ConcurrentUpdate | null,
};
/**
...
 */

export function enqueueConcurrentClassUpdate&amp;lt;State&amp;gt;(
  fiber: Fiber,
  queue: ClassQueue&amp;lt;State&amp;gt;, // SharedQueue&amp;lt;State&amp;gt; 에서 변형됨 - 바로 위를 참조
  update: ClassUpdate&amp;lt;State&amp;gt;, // Update&amp;lt;State&amp;gt; 에서 변형됨 - 바로 위로 참조
  lane: Lane,
): FiberRoot | null {
  const concurrentQueue: ConcurrentQueue = (queue: any);
  const concurrentUpdate: ConcurrentUpdate = (update: any);
  enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane);
  return getRootForUpdatedFiber(fiber);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위를 보면, 오히려 &lt;code&gt;Update&amp;lt;State&amp;gt;&lt;/code&gt;, &lt;code&gt;SharedQueue&amp;lt;State&amp;gt;&lt;/code&gt; 가&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ConcurrentQueue&lt;/code&gt;, &lt;code&gt;ConcurrentUpdate&lt;/code&gt; 보다 프로퍼티가 적은 것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;왜 그럴까?&lt;/p&gt;
&lt;p&gt;&lt;code&gt;queue : any&lt;/code&gt;, &lt;code&gt;update : any&lt;/code&gt; 인 것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;즉, 이 부분에서 추측 할 수 있는 것은, 실제로는 프로퍼티가 줄어들지는 않았지만,&lt;/p&gt;
&lt;p&gt;TypeScript 에게 좁은 스코프를 주기 위함이며, 또한 가독성을 위해 타입을 선언했다는 것을 알 수 있었다.&lt;/p&gt;
&lt;p&gt;그렇지 않으면, 굳이 &lt;code&gt;: any&lt;/code&gt; 를 선언 할 필요가 없었기 때문이다.&lt;/p&gt;
&lt;p&gt;자, 이제 &lt;code&gt;enqueueUpdate&lt;/code&gt; 메서드와, &lt;code&gt;getRootForUpdatedFiber&lt;/code&gt; 메서드를 조사 할 시간이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane)&lt;/code&gt; : &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/7a3ffef70339c10f8d65a27b88cd73bfbe13eb8a/packages/react-reconciler/src/ReactFiberConcurrentUpdates.js#L89&quot;&gt;코드 위치&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function enqueueUpdate(
  fiber: Fiber,
  queue: ConcurrentQueue | null,
  update: ConcurrentUpdate | null,
  lane: Lane,
) {
  // Don&amp;#39;t update the `childLanes` on the return path yet. If we already in
  // the middle of rendering, wait until after it has completed.
  concurrentQueues[concurrentQueuesIndex++] = fiber;
  concurrentQueues[concurrentQueuesIndex++] = queue;
  concurrentQueues[concurrentQueuesIndex++] = update;
  concurrentQueues[concurrentQueuesIndex++] = lane;

  concurrentlyUpdatedLanes = mergeLanes(concurrentlyUpdatedLanes, lane);

  // The fiber&amp;#39;s `lane` field is used in some places to check if any work is
  // scheduled, to perform an eager bailout, so we need to update it immediately.
  // TODO: We should probably move this to the &amp;quot;shared&amp;quot; queue instead.
  fiber.lanes = mergeLanes(fiber.lanes, lane);
  const alternate = fiber.alternate;
  if (alternate !== null) {
    alternate.lanes = mergeLanes(alternate.lanes, lane);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;enqueueUpdate&lt;/code&gt; 는 바로 직전의 &lt;code&gt;enqueueConcurrentClassUpdate&lt;/code&gt; 메서드와&lt;/p&gt;
&lt;p&gt;동일한 인자를 받고 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;enqueueConcurrentClassUpdate&lt;/code&gt; 는, 인자의 &lt;code&gt;queue&lt;/code&gt; 와 &lt;code&gt;update&lt;/code&gt; 타입 스코프를 줄여주는&lt;/p&gt;
&lt;p&gt;역할만 수행했다. (물론 객체 안에 미리 든 프로퍼티는 사라지지 않는다.)&lt;/p&gt;
&lt;p&gt;이 메서드는 어떠한 값도 반환하지 않는다. 하지만, 주목해야 할 점이 2 개가 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;이 파일의 전역 배열 &lt;code&gt;concurrentQueues&lt;/code&gt; 에 인자로 주어진 &amp;quot;모든&amp;quot; 정보를 차례대로 넣는다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fiber.lanes = mergeLane(fiber.lanes, lane);&lt;/code&gt; 코드를 통해, &lt;br/&gt; &lt;code&gt;fiber.lanes&lt;/code&gt; 가 갱신이 된다는 것이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;concurrentQueues&lt;/code&gt; 배열은 어떻게 정의되어 있나? : &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/7a3ffef70339c10f8d65a27b88cd73bfbe13eb8a/packages/react-reconciler/src/ReactFiberConcurrentUpdates.js#L45&quot;&gt;배열 선언 위치&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// If a render is in progress, and we receive an update from a concurrent event,
// we wait until the current render is over (either finished or interrupted)
// before adding it to the fiber/hook queue. Push to this array so we can
// access the queue, fiber, update, et al later.
const concurrentQueues: Array&amp;lt;any&amp;gt; = [];
let concurrentQueuesIndex = 0;

let concurrentlyUpdatedLanes: Lanes = NoLanes;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;일단, 이 배열 객체와 인덱스는 외부로 &lt;code&gt;export&lt;/code&gt; 하지 않는다.&lt;/p&gt;
&lt;p&gt;즉, 이 파일에서만 전역적으로 접근하는 값이라는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;-주석은 뭐라고 하고 있을까?-&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;만약에, 렌더가 진행중인데, 동시성 이벤트로부터 업데이트 객체를 수신한다면,&lt;/p&gt;
&lt;p&gt;현재 렌더가 끝날 때 까지 기다린 후에, (종료되거나, 인터럽 되거나.)&lt;/p&gt;
&lt;p&gt;fiber/hook 큐에 추가한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;솔직히 말해서, 배열 안에 &amp;quot;동일한 형태의&amp;quot; 요소들은 나열하는 것이 아니라,&lt;/p&gt;
&lt;p&gt;각기 다른 형태의 객체, 혹은 원시값을 차례대로 넣는것이 과연 효율적인가? 에 대해서 의문이 들기도 한다.&lt;/p&gt;
&lt;p&gt;어떤 의미가 있을 지 모르니, 다시 코드로 돌아와서 로직을 살펴보자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;export function getConcurrentlyUpdatedLanes(): Lanes {
  return concurrentlyUpdatedLanes;
}

function enqueueUpdate(
  fiber: Fiber,
  queue: ConcurrentQueue | null,
  update: ConcurrentUpdate | null,
  lane: Lane,
) {
  concurrentQueues[concurrentQueuesIndex++] = fiber;
  concurrentQueues[concurrentQueuesIndex++] = queue;
  concurrentQueues[concurrentQueuesIndex++] = update;
  concurrentQueues[concurrentQueuesIndex++] = lane;

  concurrentlyUpdatedLanes = mergeLanes(concurrentlyUpdatedLanes, lane);

  // The fiber&amp;#39;s `lane` field is used in some places to check if any work is
  // scheduled, to perform an eager bailout, so we need to update it immediately.
  // TODO: We should probably move this to the &amp;quot;shared&amp;quot; queue instead.
  fiber.lanes = mergeLanes(fiber.lanes, lane);
  const alternate = fiber.alternate;
  if (alternate !== null) {
    alternate.lanes = mergeLanes(alternate.lanes, lane);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;코드를 다시 본다면, &lt;code&gt;ReactFiberConcurrentUpdate.js&lt;/code&gt; 코드 내부의 전역 배열인,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;concurrentQueues&lt;/code&gt; 에, 차례대로 인자를 넣고 있는 것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;만약에, 현재 &lt;code&gt;concurrentQueuesIndex&lt;/code&gt; 가 &lt;code&gt;0&lt;/code&gt; 이라면,&lt;/p&gt;
&lt;ol start=&quot;0&quot;&gt;
&lt;li&gt;state 이벤트를 일으킨 인스턴스의 fiber 객체 - &lt;code&gt;Fiber&lt;/code&gt; 타입&lt;/li&gt;
&lt;li&gt;이 fiber 객체의 &lt;code&gt;updateQueue.shared&lt;/code&gt; 속성 - &lt;code&gt;SharedQueue&amp;lt;State&amp;gt;&lt;/code&gt; 타입&lt;/li&gt;
&lt;li&gt;state 상태 변화 요구에 대한 정보를 담고 있는 &lt;code&gt;Update&amp;lt;State&amp;gt;&lt;/code&gt; 타입&lt;/li&gt;
&lt;li&gt;이 이벤트를 처리할 우선순위 레인 - 클릭의 경우 &lt;code&gt;SyncLane&lt;/code&gt; (바이너리) == 2&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이렇게 각기 다른 4 개의 객체 혹은 원시값을 파일의 전역 배열에 담고 있다.&lt;/p&gt;
&lt;p&gt;그런데, 위 코드를 다시 보자. 이 메서드는 &amp;quot;전역 배열을 사용하지 않는다.&amp;quot;&lt;/p&gt;
&lt;p&gt;따라서, 우리는 앞으로 수행할 로직 중, 특정 메서드가 이를 사용하거나, 다시 초기화시킨다는 것으로&lt;/p&gt;
&lt;p&gt;추정할 수 있다.&lt;/p&gt;
&lt;p&gt;실제로, &lt;code&gt;finishQueueingConcurrentUpdates()&lt;/code&gt; 라는 메서드 : &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/7a3ffef70339c10f8d65a27b88cd73bfbe13eb8a/packages/react-reconciler/src/ReactFiberConcurrentUpdates.js#L50&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;가 이 전역 배열을 초기화 해 주긴 한다. (배열 내부를 순회하며 전부 null 로 만듬)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그 다음으로,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;concurrentlyUpdatedLanes = mergeLanes(currentlyUpdatedLanes, lane)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;메서드를 살펴보자.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;concurrentlyUpdatedLane&lt;/code&gt; 전역 변수 (&lt;code&gt;Lanes&lt;/code&gt; 타입) 은, 외부로부터 노출되는 변수이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;mergeLanes(a : Lanes | Lane, b : Lanes | Lane) : Lanes&lt;/code&gt; : &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/4db4b21c63ebc4edc508c5f7674f9df50d8f9744/packages/react-reconciler/src/ReactFiberLane.js#L739&quot;&gt;코드 위치&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;export function mergeLanes(a: Lanes | Lane, b: Lanes | Lane): Lanes {
  return a | b;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lanes 와, Lane 은 기본적으로 &lt;code&gt;number&lt;/code&gt; 로 표기되어 있지만,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;0b00000000000000000....&lt;/code&gt; 로 표시되는 바이너리 숫자이다.&lt;/p&gt;
&lt;p&gt;그런데, 이 메서드에서는 이진 연산 표기법이 나왔다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;amp;&lt;/code&gt; : 각 자리수 비교시 동일하게 1 이 나왔을 때만 그 자리에 1이 오고, 나머지는 0 처리&lt;/li&gt;
&lt;li&gt;&lt;code&gt;|&lt;/code&gt; : 각 자리수 비교시 한 쪽이라도 1 이 나왔을 때, 해당 자리는 1 이 온다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;그렇다면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;concurrentlyUpdatedLanes = mergeLanes(concurrentlyUpdatedLanes, lane);&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;이 코드는 무엇을 의미할까?&lt;/p&gt;
&lt;p&gt;즉, 전역 변수인 &lt;code&gt;concurrentlyUpdatedLanes&lt;/code&gt; 는 &lt;code&gt;NoLanes&lt;/code&gt; 로, &lt;code&gt;0&lt;/code&gt; 을 의미한다.&lt;/p&gt;
&lt;p&gt;그런데, 우리가 전달해 준 &lt;code&gt;lane&lt;/code&gt; 은 &lt;code&gt;SyncLane&lt;/code&gt; 으로, &lt;code&gt;2&lt;/code&gt; 에 해당하는 바이너리이다.&lt;/p&gt;
&lt;p&gt;그렇다면, 이 둘을 &lt;code&gt;|&lt;/code&gt; 연산하면, &lt;code&gt;concurrentlyUpdatedLanes&lt;/code&gt; 는, &lt;code&gt;2&lt;/code&gt; 가 된다.&lt;/p&gt;
&lt;p&gt;따라서 이 파일의 전역 변수 &lt;code&gt;concurrentlyUpdatedLanes&lt;/code&gt; 는 &lt;code&gt;2&lt;/code&gt; 로 참조가 된다.&lt;/p&gt;
&lt;p&gt;그리고, &lt;code&gt;fiber.lanes = mergeLanes(fiber.lanes, lane);&lt;/code&gt; 에도 동일한 연산이 수행된다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Fiber&lt;/code&gt; 타입의 기본적인 &lt;code&gt;lanes&lt;/code&gt; 속성 또한 어디서 초기화 되었을 수도 있기 때문에,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ReactFiberClassComponent.js&lt;/code&gt; 파일의 &lt;code&gt;constructClassInstance&lt;/code&gt; 함수를 살펴보았으나,&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/4db4b21c63ebc4edc508c5f7674f9df50d8f9744/packages/react-reconciler/src/ReactFiberClassComponent.js#L529&quot;&gt;코드 위치&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Fiber&lt;/code&gt; 객체 중 &lt;code&gt;lanes&lt;/code&gt; 나 &lt;code&gt;lane&lt;/code&gt; 을 조정하는 코드는 없었다.&lt;/p&gt;
&lt;p&gt;즉, 초기화 된 &lt;code&gt;NoLanes&lt;/code&gt; 라고 가정한다. == &lt;code&gt;0&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;즉, &lt;code&gt;fiber.lanes = mergeLanes(0, 2);&lt;/code&gt; 가 되며,&lt;/p&gt;
&lt;p&gt;결론적으로 &lt;code&gt;fiber.lanes = 2&lt;/code&gt; 가 된다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;fiber&lt;/code&gt; 의 &lt;code&gt;alternate&lt;/code&gt; 변수의 역할에 대해 이해하지 못해서 GPT o3 에게 물어보았는데,&lt;/p&gt;
&lt;p&gt;Work In Progress &amp;lt;--&amp;gt; current&lt;/p&gt;
&lt;p&gt;이 두 개의 트리는 &lt;code&gt;Fiber&lt;/code&gt; 로 이루어져 있는데,&lt;/p&gt;
&lt;p&gt;이 과정에서 각 노드를 의미하는 &lt;code&gt;fiber&lt;/code&gt; 가 얕은 복사를 하며 같은 계층을 구성하고,&lt;/p&gt;
&lt;p&gt;서로 다른 트리의 동일한 노드를 &lt;code&gt;alternate&lt;/code&gt; 로 가지는 것이라고 한다.&lt;/p&gt;
&lt;p&gt;즉, &lt;code&gt;alternate.lanes = mergeLanes(alternate.lanes, lane)&lt;/code&gt; 을 통해 동일한 연산을 수행한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;다시 돌아와서&lt;/strong&gt;, &lt;code&gt;enqueueConcurrentClassUpdate&amp;lt;State&amp;gt;&lt;/code&gt; 메서드를 보자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;export function enqueueConcurrentClassUpdate&amp;lt;State&amp;gt;(
  fiber: Fiber,
  queue: ClassQueue&amp;lt;State&amp;gt;,
  update: ClassUpdate&amp;lt;State&amp;gt;,
  lane: Lane,
): FiberRoot | null {
  const concurrentQueue: ConcurrentQueue = (queue: any); // SharedQueue&amp;lt;State&amp;gt;
  const concurrentUpdate: ConcurrentUpdate = (update: any); // Update&amp;lt;State&amp;gt;
  enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane); // 완료

  return getRootForUpdatedFiber(fiber);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;우리는 &lt;code&gt;enqueueUpdate&lt;/code&gt; 메서드를 통해 어떤 작업을 수행했냐면,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;fiber&lt;/code&gt;, &lt;code&gt;concurrentQueue&lt;/code&gt;, &lt;code&gt;concurrentUpdate&lt;/code&gt;, &lt;code&gt;lane&lt;/code&gt; 을 &lt;br/&gt; 전역 배열 &lt;code&gt;concurrentQueues&lt;/code&gt; 에 차례대로 넣었다.&lt;/li&gt;
&lt;li&gt;전역 변수 &lt;code&gt;concurrentlyUpdatedLanes&lt;/code&gt; 바이너리를 &lt;code&gt;lane&lt;/code&gt; 으로 병합시켰다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fiber&lt;/code&gt;, &lt;code&gt;fiber.alternate&lt;/code&gt; 를 주어진 인자 &lt;code&gt;lane&lt;/code&gt; 으로 병합시켰다.&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;p&gt;자, 이제 &lt;code&gt;getRootForUpdatedFiber(fiber)&lt;/code&gt; 를 살펴보자.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;getRootForUpdatedFiber(sourceFiber : Fiber) : FiberRoot | null&lt;/code&gt; : &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/7a3ffef70339c10f8d65a27b88cd73bfbe13eb8a/packages/react-reconciler/src/ReactFiberConcurrentUpdates.js#L251&quot;&gt;코드위치&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function getRootForUpdatedFiber(sourceFiber: Fiber): FiberRoot | null {
  // 순환참조 오류 때문에 업데이트가 업데이트를 불러오고, 이것이 서로 연결되었다면 에러를 던짐.
  throwIfInfiniteUpdateLoopDetected();

  // 개발 상황 시에만 고려
  detectUpdateOnUnmountedFiber(sourceFiber, sourceFiber);
  let node = sourceFiber;
  let parent = node.return;
  while (parent !== null) {
    detectUpdateOnUnmountedFiber(sourceFiber, node);
    node = parent;
    parent = node.return;
  }
  return node.tag === HostRoot ? (node.stateNode: FiberRoot) : null;
}

function detectUpdateOnUnmountedFiber(sourceFiber: Fiber, parent: Fiber) {
  if (__DEV__) {
    const alternate = parent.alternate;
    if (
      alternate === null &amp;amp;&amp;amp;
      (parent.flags &amp;amp; (Placement | Hydrating)) !== NoFlags
    ) {
      warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber);
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;detectUpdateOnUnmountedFiber&lt;/code&gt; 는, 우리가 아직 개발 상황으로 리액트를 조작할 때 실행되는&lt;/p&gt;
&lt;p&gt;로직이다.&lt;/p&gt;
&lt;p&gt;이 메서드는 현재 &lt;code&gt;fiber&lt;/code&gt; 가 마운트 중(아직 렌더링 안됨), 언마운트 중(렌더링에서 빼는중)&lt;/p&gt;
&lt;p&gt;이 상황일 때, 에러를 던진다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;getRootForUpdatedFiber(sourceFiber: Fiber): FiberRoot | null&lt;/code&gt; 메서드를 분석해보자.&lt;/p&gt;
&lt;p&gt;먼저, 우리는 이 메서드를 &lt;code&gt;setState&lt;/code&gt; 를 일으킨 인스턴스의 &lt;code&gt;fiber&lt;/code&gt; 를 인자로 받아 실행한다.&lt;/p&gt;
&lt;p&gt;그리고,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;  let node = sourceFiber;
  let parent = node.return;
  while (parent !== null) {
    detectUpdateOnUnmountedFiber(sourceFiber, node);
    node = parent;
    parent = node.return;
  }

  return node.tag === HostRoot ? (node.stateNode : FiberRoot) : null;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 코드 조각은 정확히 이렇게 의미한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;Fiber.return&lt;/code&gt; 은 현재 &lt;code&gt;fiber&lt;/code&gt; 의 부모 &lt;code&gt;fiber&lt;/code&gt; 를 의미한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;while&lt;/code&gt; 문의 결과로, &lt;code&gt;parent&lt;/code&gt; 는 &lt;code&gt;null&lt;/code&gt; 이 되며, &lt;code&gt;node&lt;/code&gt; 는 최고 부모인 &lt;code&gt;root&lt;/code&gt; 가 된다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;node&lt;/code&gt; 는 루트이기 때문에, 이 루트 &lt;code&gt;fiber&lt;/code&gt; 노드를 반환한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;다시 돌아와서,&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;enqueueUpdate(fiber, update, lane) : FiberRoot | null&lt;/code&gt; 로 돌아오자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;export function enqueueUpdate&amp;lt;State&amp;gt;(
  fiber: Fiber,
  update: Update&amp;lt;State&amp;gt;,
  lane: Lane,
): FiberRoot | null {
  const updateQueue = fiber.updateQueue;
  if (updateQueue === null) {
    // 주석 해석 : 오로지 파이버가 언마운트 되었을 때만 null 로 반환한다.
    return null;
  }

  const sharedQueue: SharedQueue&amp;lt;State&amp;gt; = (updateQueue: any).shared;

  if (__DEV__) {
    // 개발 상황 시
  }

  if (isUnsafeClassRenderPhaseUpdate(fiber)) {
    /**
     * UNSAFE_ 접두어가 붙은 메서드를 사용했을 때 이 분기로 들어온다.
     * 그러나, 개발할 때 이런 접두어를 쓰지 않았다면 신경쓰지 않아도 된다.
     */
    return unsafe_markUpdateLaneFromFiberToRoot(fiber, lane);
  } else {
    // 결국 이 분기가 선택됨.
    return enqueueConcurrentClassUpdate(fiber, sharedQueue, update, lane);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이제, &lt;code&gt;enqueueConcurrentClassUpdate&lt;/code&gt; 메서드가 &lt;code&gt;FiberRoot&lt;/code&gt; 를 반환하는 과정을 이해했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;다시 돌아와서, 우리는 이러한 논리를 이해했다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const classComponentUpdater = {
  // Component.prototype.setState 실행 시 이 메서드 실행
  enqueueSetState(inst: any, payload: any, callback) {
    // 타겟 인스턴스에 등록된 &amp;quot;Fiber&amp;quot; 객체를 추출.
    const fiber = getInstance(inst);
    // 이벤트가 일어남과 동시에 전역으로 설정되어 있던 고정된 레인을 반환받음(트랜지션 상황 제외)
    const lane = requestUpdateLane(fiber);

    // 우리가 업데이트 할 내용을 담을 Update&amp;lt;State&amp;gt; 타입 객체를 초기화 - lane =&amp;gt; tag 정보와 함께.
    const update = createUpdate(lane);
    // 업데이트 객체에 우리가 설정한 함수 혹은 객체를 주입
    update.payload = payload;
    // 만약에 setState 메서드에 콜백 함수도 넣었다면, 이것도 업데이트 객체에 주입
    if (callback !== undefined &amp;amp;&amp;amp; callback !== null) {
      if (__DEV__) {
        warnOnInvalidCallback(callback);
      }
      update.callback = callback;
    }

    // ReactFiberConcurrentUpdates.js 파일 내부의
    // 파일 전역 데이터 concurrentQueues 배열, 배열의 인덱스, --&amp;gt; 배열에 차례로 주입
    // concurrentlyUpdatedLanes : Lanes 데이터를 조정한다. 즉, &amp;quot;클릭&amp;quot; 시 SyncLane
    // 결국 while 문을 통해 현재 fiber 인스턴스에 대한 root Fiber 를 반환한다.
    const root = enqueueUpdate(fiber, update, lane);

    // 이제 RootFiber, 현재 인스턴스의 Fiber, 정해진 전역 우선순위 lane 을 통해 논리 수행
    // 이제 여기를 조사.
    if (root !== null) {
      startUpdateTimerByLane(lane, &amp;#39;this.setState()&amp;#39;);
      scheduleUpdateOnFiber(root, fiber, lane);
      entangleTransitions(root, fiber, lane);
    }

    if (enableSchedulingProfiler) {
      markStateUpdateScheduled(fiber, lane);
    }
  },&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이제,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;startUpdateTimerByLane(lane, &amp;#39;this.setState()&amp;#39;);&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;scheduleUpdateOnFiber(root, fiber, lane);&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;entangleTransitions(root, fiber, lane);&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;세 가지 로직을 이해해야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;추출된 fiber, lane, update, root 를 통해 스케쥴러에 등록하기.&lt;/h3&gt;
&lt;p&gt;자, 이제 위에서 언급한 3 가지 내용을 분석하면&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Component.prototype.setState&lt;/code&gt; 실행으로 일어난 로직을 대부분 파악했다고 말할 수 있겠다.&lt;/p&gt;
&lt;p&gt;그러나, &amp;quot;Scheduler&amp;quot;, &amp;quot;Fiber&amp;quot;, &amp;quot;FiberRoot&amp;quot;, &amp;quot;lane&amp;quot;&lt;/p&gt;
&lt;p&gt;이러한 정보들의 연관관계는 명확하게 파악되지 않았다.&lt;/p&gt;
&lt;p&gt;물론, 검색하면 추상적으로 이해할 수 있겠지만, 나의 코드 가독력 및 분석력 향상을 위해 이대로 진행한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;1. startUpdateTimerByLane(lane: Lane, method: string) : void&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;function startUpdateTimerByLane(lane : Lane, method : string) : void&lt;/code&gt; :&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/811e203ed42c1a496790426a687d5045c473653d/packages/react-reconciler/src/ReactProfilerTimer.js#L91&quot;&gt;코드 위치&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;export function startUpdateTimerByLane(lane: Lane, method: string): void {
  if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
    return;
  }
  if (isSyncLane(lane) || isBlockingLane(lane)) {
    if (blockingUpdateTime &amp;lt; 0) {
      // window.performance.now() 와 동일하다.
      // 이 기능은 기존의 Date.now() 밀리세컨드보다 더 정밀하게 double 타입으로 할당한다.
      blockingUpdateTime = now();

      // 기본 객체 console 에 &amp;quot;createTask&amp;quot; 메서드를 선언 해 놓음
      // createTask(label : string) : ConsoleTask
      blockingUpdateTask = createTask(method);
      if (isAlreadyRendering()) {
        blockingSpawnedUpdate = true;
      }
      const newEventTime = resolveEventTimeStamp();
      const newEventType = resolveEventType();
      if (
        newEventTime !== blockingEventTime ||
        newEventType !== blockingEventType
      ) {
        blockingEventIsRepeat = false;
      } else if (newEventType !== null) {
        // If this is a second update in the same event, we treat it as a spawned update.
        // This might be a microtask spawned from useEffect, multiple flushSync or
        // a setState in a microtask spawned after the first setState. Regardless it&amp;#39;s bad.
        blockingSpawnedUpdate = true;
      }
      blockingEventTime = newEventTime;
      blockingEventType = newEventType;
    }
  } else if (isTransitionLane(lane)) {
    // 트랜지션 레인이 아니라, SyncLane 이기에, 분기에 들어가지 않는다.
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 로직을 이해하기 위해서, 당연지사, 각 메서드의 의미를 알아야 했다.&lt;/p&gt;
&lt;p&gt;먼저 이 메서드 내부의 전역 변수들은,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;blockingUpdateTime&lt;/code&gt; : &lt;code&gt;double = -1.1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;blockingUpdateTask&lt;/code&gt; : &lt;code&gt;ConsoleTask | null = null&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;blockingEventTime&lt;/code&gt; : &lt;code&gt;double = -1.1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;blockingEventType&lt;/code&gt; : &lt;code&gt;string | null = null&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;blockingEventIsRepeat&lt;/code&gt; : &lt;code&gt;boolean = false&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;blockingSpawnedUpdate&lt;/code&gt; : &lt;code&gt;boolean = false&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이며, 이 메서드 내부에서 인자 없이, 다른 파일의 값을 읽어오는 메서드는,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;isAlreadyRendering()&lt;/code&gt; : &lt;code&gt;./ReactFiberWorkLoop&lt;/code&gt; 파일로부터.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;resolveEventTimeStamp()&lt;/code&gt; : &lt;code&gt;./ReactFiberConfig&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;resolveEventType()&lt;/code&gt; : &lt;code&gt;./ReactFiberConfig&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;blockingUpdateTime = now();&lt;/code&gt; 이 코드 부분은 주석으로 이미 충분한 설명을 작성했다.&lt;/p&gt;
&lt;p&gt;더욱 정밀한 밀리세컨드 이하 마이크로세컨드를 위해 &lt;code&gt;window.performance.now()&lt;/code&gt; 를 수행한다.&lt;/p&gt;
&lt;p&gt;(글이 너무 길어지는 것을 예방.)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;blockingUpdateTask = createTask(method)&lt;/code&gt; 에 대해서 알아보자.&lt;/p&gt;
&lt;p&gt;해당 파일의 위쪽에 올라가면 이러한 코드 조각이 존재한다. : &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/811e203ed42c1a496790426a687d5045c473653d/packages/react-reconciler/src/ReactProfilerTimer.js#L44&quot;&gt;코드 위치&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const createTask =
  // eslint-disable-next-line react-internal/no-production-logging
  __DEV__ &amp;amp;&amp;amp; console.createTask
    ? // eslint-disable-next-line react-internal/no-production-logging
      console.createTask
    : (name: string) =&amp;gt; null;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;즉, 현재 &amp;quot;개발 모드&amp;quot; 인지 &amp;quot;프로덕션 모드&amp;quot; 인지에 따라 갈린다.&lt;/p&gt;
&lt;p&gt;우리는 여태까지 &amp;quot;개발 모드&amp;quot; 를 염두에 두고 조사를 수행하진 않았다.&lt;/p&gt;
&lt;p&gt;즉, 여기서 &lt;code&gt;createTask&lt;/code&gt; 는 &lt;code&gt;(name : string) =&amp;gt; null&lt;/code&gt; 이라고 생각하면 끝이다.&lt;/p&gt;
&lt;p&gt;그러나, 나의 이목을 끈 것이 있었으니, 바로 &lt;code&gt;console.createTask&lt;/code&gt; 였다.&lt;/p&gt;
&lt;p&gt;기본적으로, 브라우저나 node.js 의 &lt;code&gt;console&lt;/code&gt; 기본 객체는 이러한 메서드를 가지지 않는다.&lt;/p&gt;
&lt;p&gt;그래서 도대체 무엇이지 하니, &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/811e203ed42c1a496790426a687d5045c473653d/scripts/flow/environment.js#L36&quot;&gt;console 재선언 위치&lt;/a&gt; 에서 확인이 가능했다.&lt;/p&gt;
&lt;p&gt;즉, 프로그램 스코프의 &lt;code&gt;console&lt;/code&gt; 객체 기능을 &amp;quot;재선언&amp;quot; 한 것과 동일하다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;createTask&lt;/code&gt; 메서드의 결과로, &lt;code&gt;ConsoleTask&lt;/code&gt; 객체를 반환하는데,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;declare interface ConsoleTask {
  run&amp;lt;T&amp;gt;(f: () =&amp;gt; T): T;
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;즉, 이 객체에서 &lt;code&gt;createTask(&amp;quot;this.setState()&amp;quot;)&lt;/code&gt; 실행 및 반환 이후,&lt;/p&gt;
&lt;p&gt;해당 값에서 &lt;code&gt;run&lt;/code&gt; 을 수행하여 원하는 &lt;code&gt;T&lt;/code&gt; 형식의 값을 반환받는 메서드였다.&lt;/p&gt;
&lt;p&gt;결과적으로 &lt;code&gt;blockingUpdateTask&lt;/code&gt; 라는 전역 객체에 &lt;code&gt;ConsoleTask&lt;/code&gt; 를 할당하게 되었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그 다음으로는 &lt;code&gt;isAlreadyRendering()&lt;/code&gt; 메서드였다. : &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/811e203ed42c1a496790426a687d5045c473653d/packages/react-reconciler/src/ReactFiberWorkLoop.js#L1778&quot;&gt;코드 위치&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;export function isAlreadyRendering(): boolean {
  // Used by the renderer to print a warning if certain APIs are called from
  // the wrong context, and for profiling warnings.
  return (executionContext &amp;amp; (RenderContext | CommitContext)) !== NoContext;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 코드는 &lt;code&gt;ReactFiberWorkLoop.js&lt;/code&gt; 파일의 전역 변수 &lt;code&gt;executionContext&lt;/code&gt; 를 읽는데,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;type ExecutionContext = number;

export const NoContext = /*             */ 0b000;
const BatchedContext = /*               */ 0b001;
export const RenderContext = /*         */ 0b010;
export const CommitContext = /*         */ 0b100;

let executionContext: ExecutionContext = NoContext;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 타입 정보를 보면 쉽게 이해 할 수 있다.&lt;/p&gt;
&lt;p&gt;이 메서드에는 2 가지의 이진 계산 기호가 들어간다. &lt;code&gt;&amp;amp;&lt;/code&gt; 와, &lt;code&gt;|&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;즉, &lt;code&gt;RenderContext&lt;/code&gt;, &lt;code&gt;CommitContext&lt;/code&gt; 의 바이너리가 병합되며, &lt;code&gt;0b110&lt;/code&gt; 이 된다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;executionContext &amp;amp; 0b110&lt;/code&gt; 이라는 계산식으로 줄여지는데,&lt;/p&gt;
&lt;p&gt;만약 &lt;code&gt;executionContext&lt;/code&gt; 가, &lt;code&gt;0b000&lt;/code&gt; or &lt;code&gt;0b001&lt;/code&gt; 이라면, 반환 결과는 &lt;code&gt;false&lt;/code&gt; 가 되며,&lt;/p&gt;
&lt;p&gt;현재 실행 컨텍스트가 렌더, 혹은 커밋이라면, 즉시 &lt;code&gt;true&lt;/code&gt; 로 반환한다.&lt;/p&gt;
&lt;p&gt;그런데, 이 글을 작성하는 때는, 단순히 버튼 클릭 후 카운트 텍스트를 변경하는 예제를 사용했다.&lt;/p&gt;
&lt;p&gt;따라서, 이 메서드의 결과는 &lt;code&gt;false&lt;/code&gt; 이므로, &lt;code&gt;blockingSpawnedUpdate&lt;/code&gt; 는 그대로 &lt;code&gt;false&lt;/code&gt; 이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;resolveEventTimeStamp()&lt;/code&gt;, &lt;code&gt;resolveEventType()&lt;/code&gt; 을 가져오는 &lt;code&gt;./ReactFiberConfig&lt;/code&gt; 경로는,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;throw new Error(&amp;#39;This module must be shimmed by a specific renderer.&amp;#39;);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이러한 에러를 내보내고 있다.&lt;/p&gt;
&lt;p&gt;이 파일을 아마 글의 초중반기에서 겪었던 기억이 있다.&lt;/p&gt;
&lt;p&gt;이 때는, 다른 상위 디렉토리의 &lt;code&gt;react-dom-bindings/src/client/ReactFiberConfigDOM&lt;/code&gt; 으로 찾아가야 한다. (브라우저 개발이므로)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.../client/ReactFiberConfigDOM.js&lt;/code&gt; : &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/811e203ed42c1a496790426a687d5045c473653d/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js#L730&quot;&gt;코드 위치&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let schedulerEvent: void | Event = undefined;
export function trackSchedulerEvent(): void {
  schedulerEvent = window.event;
}

export function resolveEventType(): null | string {
  const event = window.event;
  return event &amp;amp;&amp;amp; event !== schedulerEvent ? event.type : null;
}

export function resolveEventTimeStamp(): number {
  const event = window.event;
  return event &amp;amp;&amp;amp; event !== schedulerEvent ? event.timeStamp : -1.1;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;기본적으로 &lt;code&gt;window.event&lt;/code&gt; 는 웹사이트의 코드가 &amp;quot;현재 처리 중인&amp;quot; 이벤트를 의미한다.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://developer.mozilla.org/ko/docs/Web/API/Window/event&quot;&gt;MDN 참고 문서 - (Window.event)&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;event.type&lt;/code&gt; : 현재 처리중인 이벤트의 타입 (문자열)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;event.timeStamp&lt;/code&gt; : &amp;quot;시간 원점&amp;quot; 으로부터 이벤트가 생성되기까지 &amp;quot;경과한 시간&amp;quot;을 밀리초 단위로 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;여기서 말하는 &amp;quot;시간 원점&amp;quot; 이란, 웹사이트 스크립트의 문서 로딩을 시작했던 시점을 의미한다.&lt;/p&gt;
&lt;p&gt;즉, 일반적으로 반환하는 1970년 1월 1일 부터의 타임스탬프는 아니란 의미이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그런데, &lt;code&gt;schedulerEvent&lt;/code&gt; 는 전역 변수로, &lt;code&gt;Event&lt;/code&gt; 타입 값을 지정받았다.&lt;/p&gt;
&lt;p&gt;코드에서 보다시피, 우리는 아직 알지 못하지만, 또 다른 로직에서 &lt;code&gt;trackSchedulerEvent()&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;메서드를 실행하여, 현재 &lt;code&gt;window.event&lt;/code&gt; 를 캡쳐하여 &lt;code&gt;schedulerEvent&lt;/code&gt; 를 저장 해 두는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그렇다면, &lt;code&gt;resolveEventType()&lt;/code&gt;, &lt;code&gt;resolveEventTimeStamp()&lt;/code&gt; 가 의미하는 바는 동일하다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;현재 윈도우에서 이벤트가 일어난 것이 맞으며,&lt;/li&gt;
&lt;li&gt;이 이벤트가, 전역 변수로 이미 등록된 &lt;code&gt;schedulerEvent&lt;/code&gt; 와 동일하지 않다면, &lt;br/&gt; 윈도우 이벤트 정보를 반환한다.&lt;/li&gt;
&lt;li&gt;만약에 이 이벤트가 거꾸로 &lt;code&gt;schedulerEvent&lt;/code&gt; 와 동일하다면, &lt;br/&gt; 윈도우 이벤트 정보를 반환하지 않고, &lt;code&gt;ReactProfilerTimer.js&lt;/code&gt; 의 전역 변수 기본 설정과 동일한 &lt;code&gt;null&lt;/code&gt;, &lt;code&gt;-1.1&lt;/code&gt; 을 반환한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;p&gt;이제 다시, &lt;code&gt;startUpdateTimerByLane(lane: Lane, method: string): void&lt;/code&gt; 로 돌아오자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;export function startUpdateTimerByLane(lane: Lane, method: string): void {
  if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
    return;
  }
  if (isSyncLane(lane) || isBlockingLane(lane)) {
    if (blockingUpdateTime &amp;lt; 0) {
      // window.performance.now() 와 동일하다.
      // 이 기능은 기존의 Date.now() 밀리세컨드보다 더 정밀하게 double 타입으로 할당한다.
      blockingUpdateTime = now();

      // 기본 객체 console 에 &amp;quot;createTask&amp;quot; 메서드를 선언 해 놓음
      // createTask(label : string) : ConsoleTask
      blockingUpdateTask = createTask(method);

      // 렌더링 상황에서 상태를 변경하는 것이 아니므로, 이 분기에 들어가지 않는다.
      if (isAlreadyRendering()) {
        blockingSpawnedUpdate = true;
      }

      // 이벤트 타임스탬프 밀리세컨드
      const newEventTime = resolveEventTimeStamp();
      // 이벤트 이름
      const newEventType = resolveEventType();

      /**
        이 분기가 의미하는 것은, resolveEventTimeStamp(), resolveEventType()
        메서드 실행 과정에서, 이미 누군가가 ReactFiberConfigDOM.js 파일에서
        윈도우에서 일어난 이벤트를 &amp;quot;저장&amp;quot; 해 두었다는 것이다.

        따라서, blockingEventTime = -1.1; blockingEventType = null;
        이렇게 기본값인 상황에서, 위의 2 메서드가 유의미한 결과를 얻어왔다면,
        blockingEventIsRepeat = false; 가 설정된다.
        */
      if (
        newEventTime !== blockingEventTime ||
        newEventType !== blockingEventType
      ) {
        blockingEventIsRepeat = false;
      } else if (newEventType !== null) {
        // If this is a second update in the same event, we treat it as a spawned update.
        // This might be a microtask spawned from useEffect, multiple flushSync or
        // a setState in a microtask spawned after the first setState. Regardless it&amp;#39;s bad.
        /**
         이 분기는 실제 DOM 이벤트로 일어난 setState 를 처리하는 것이 아니라,
         해당 이벤트가 일어나면서, 연결된 state 변경 상황에서 이 분기로 들어온다.

         주석 해설 :
          만약에 현재 논리 진행이 같은 이벤트 내부에서의 2 번째 업데이트라면,
          spawned 업데이트라고 칭한다.

          이 분기는 useEffect 로부터 생성(spawned)된 microtask 이거나,
          다중 flushSync 상황이라고 예상할 수 있다.

          혹은, 첫 번째 setState 이후에 생성된 microtask 의 setState 를 의미한다.
          바로 위의 상황이라면 이건 나쁜 상황임에도 불구하고 수행한다.
        */
        blockingSpawnedUpdate = true;
      }

      // 파일 전역 데이터인 이벤트 시간과 타입을 resolve 한 정보로 변경한다.
      blockingEventTime = newEventTime;
      blockingEventType = newEventType;
    }
  } else if (isTransitionLane(lane)) {
    // 트랜지션 레인이 아니라, SyncLane 이기에, 분기에 들어가지 않는다.
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;자, 이제 우리는 &lt;code&gt;classComponentUpdater&lt;/code&gt; 변수의 &lt;code&gt;enqueueSetState&lt;/code&gt; 메서드 내부에서&lt;/p&gt;
&lt;p&gt;&lt;code&gt;startUpdateTimerByLane&lt;/code&gt; 메서드가 어떤 역할을 하는지 이해하게 되었다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ReactProfilerTimer.js&lt;/code&gt; 파일의 전역 변수들을 설정한다.&lt;/li&gt;
&lt;li&gt;성능 측정에 필요한 변수들을 설정 해 준 것이다.&lt;/li&gt;
&lt;li&gt;개발자가 디벨롭 상황이 아니라, 실 상황에서도 컴포넌트 이벤트에 대한 성능 측정 및 정보 추출을 위해 만들어 놓은 파일이라고 해석했다.&lt;/li&gt;
&lt;li&gt;그 이유는, &lt;code&gt;enableProfilerTimer&lt;/code&gt;, &lt;code&gt;enableComponentPerformanceTrack&lt;/code&gt; 두 전역 값이 설정되어 있지 않으면, 애초에 메서드가 바로 반환하기 때문이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;h4&gt;2. scheduleUpdateOnFiber(root, fiber, lane)&lt;/h4&gt;
&lt;p&gt;우리의 &lt;code&gt;enqueueSetState&lt;/code&gt; 코드를 다시 보자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const classComponentUpdater = {
  // Component.prototype.setState 실행 시 이 메서드 실행
  enqueueSetState(inst: any, payload: any, callback) {
    // 타겟 인스턴스에 등록된 &amp;quot;Fiber&amp;quot; 객체를 추출.
    const fiber = getInstance(inst);
    // 이벤트가 일어남과 동시에 전역으로 설정되어 있던 고정된 레인을 반환받음(트랜지션 상황 제외)
    const lane = requestUpdateLane(fiber);

    // 우리가 업데이트 할 내용을 담을 Update&amp;lt;State&amp;gt; 타입 객체를 초기화 - lane =&amp;gt; tag 정보와 함께.
    const update = createUpdate(lane);
    // 업데이트 객체에 우리가 설정한 함수 혹은 객체를 주입
    update.payload = payload;
    // 만약에 setState 메서드에 콜백 함수도 넣었다면, 이것도 업데이트 객체에 주입
    if (callback !== undefined &amp;amp;&amp;amp; callback !== null) {
      if (__DEV__) {
        warnOnInvalidCallback(callback);
      }
      update.callback = callback;
    }

    // ReactFiberConcurrentUpdates.js 파일 내부의
    // 파일 전역 데이터 concurrentQueues 배열, 배열의 인덱스, --&amp;gt; 배열에 차례로 주입
    // concurrentlyUpdatedLanes : Lanes 데이터를 조정한다. 즉, &amp;quot;클릭&amp;quot; 시 SyncLane
    // 결국 while 문을 통해 현재 fiber 인스턴스에 대한 root Fiber 를 반환한다.
    const root = enqueueUpdate(fiber, update, lane);

    // 이제 RootFiber, 현재 인스턴스의 Fiber, 정해진 전역 우선순위 lane 을 통해 논리 수행
    // 이제 여기를 조사.
    if (root !== null) {
      // 개발자가 프로파일링 도구를 켜 놨다면, 관련된 파일 전역 변수와 값을 세팅한다.
      startUpdateTimerByLane(lane, &amp;#39;this.setState()&amp;#39;);
      scheduleUpdateOnFiber(root, fiber, lane);
      entangleTransitions(root, fiber, lane);
    }

    if (enableSchedulingProfiler) {
      markStateUpdateScheduled(fiber, lane);
    }
  },
  // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;개인적으로 정말 긴 길을 걸었다고 생각한다.&lt;/p&gt;
&lt;p&gt;오픈소스를 보는 방법에 눈을 떠 주게 해 줬다고 해야하나?&lt;/p&gt;
&lt;p&gt;그러나, 나는 &lt;code&gt;scheduleUpdateOnFiber&lt;/code&gt; 코드 원본을 보고, 깨닫았다.&lt;/p&gt;
&lt;p&gt;&amp;quot;마지막이라고 생각할 때가 시작이구나..&amp;quot;&lt;/p&gt;
&lt;p&gt;이제 본격적으로 &lt;code&gt;FiberRoot&lt;/code&gt;, &lt;code&gt;Fiber&lt;/code&gt;, &lt;code&gt;Lane&lt;/code&gt; 에 세 가지 정해진 값을 통해&lt;/p&gt;
&lt;p&gt;스케쥴링 업데이트를 수행 할 시간이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;코드가 생각보다 길으므로, 적당히 주석으로 줄이겠다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이미 리액트 속에서 먼 길을 걸었지만, 마음을 더 굳건히 먹고 시작한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;scheduleUpdateOnFiber(root: FiberRoot, fiber: Fiber, lane: Lane)&lt;/code&gt; : &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/811e203ed42c1a496790426a687d5045c473653d/packages/react-reconciler/src/ReactFiberWorkLoop.js#L867&quot;&gt;코드위치&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;export function scheduleUpdateOnFiber(
  root: FiberRoot,
  fiber: Fiber,
  lane: Lane,
) {
  if (__DEV__) {
    // 개발 상황
  }

  if (__DEV__) {
    // 개발 상황
  }

  // 주석 해석 : work 루프가 현재 suspended(대기중) 이며, 로딩을 끝내기 위해 데이터를 기다리는 상황인지 확인
  // 버튼을 클릭 한 후, WAS 에서 데이터를 기다리기 위해 suspend 를 사용하지 않고,
  // &amp;quot;버튼 클릭&amp;quot; 상황만을 가정했으므로, 이 분기는 들어가지 않는다.
  if (
    // Suspended render phase
    (root === workInProgressRoot &amp;amp;&amp;amp;
      (workInProgressSuspendedReason === SuspendedOnData ||
        workInProgressSuspendedReason === SuspendedOnAction)) ||
    // Suspended commit phase
    root.cancelPendingCommit !== null
  ) {
    // ...
  }

  // Mark that the root has a pending update.
  // 현재 RootFiber 가 pending(보류) 중인 업데이트를 가지고 있다는 것을 &amp;quot;표시&amp;quot; 한다.
  markRootUpdated(root, lane);

  // setState 실행 과정에서 `executionContext` 값을 BatchedContext, 0b001 로 설정했다.
  // 결국 괄호 내부가 NoContext 이므로, 이 분기는 false 이며, 들어가지 않는다.
  // 이 상황은 리액트가 현재 렌더링 상황 중이라는 것을 의미한다.
  if (
    (executionContext &amp;amp; RenderContext) !== NoContext &amp;amp;&amp;amp;
    root === workInProgressRoot
  ) {
    // This update was dispatched during the render phase. This is a mistake
    // if the update originates from user space (with the exception of local
    // hook updates, which are handled differently and don&amp;#39;t reach this
    // function), but there are some internal React features that use this as
    // an implementation detail, like selective hydration.
    warnAboutRenderPhaseUpdatesInDEV(fiber);

    // Track lanes that were updated during the render phase
    workInProgressRootRenderPhaseUpdatedLanes = mergeLanes(
      workInProgressRootRenderPhaseUpdatedLanes,
      lane,
    );
  } else {
    // This is a normal update, scheduled from outside the render phase. For
    // example, during an input event.
    //
    // enableUpdaterTracking 상수는 &amp;quot;shared/ReactFeatureFlags.js&amp;quot; 파일에 존재한다.
    // enableUpdaterTracking = __PROFILE__; 로 되어 있는데,
    // 이는 글로벌 변수로서, 렌더링 상황(브라우저, 앱 등) 에 따라 값과 형태가 달라진다.
    // 개발 상황 시, fiber 인스턴스 맵을 펼치기 위해 사용되는 분기이다.
    if (enableUpdaterTracking) {
      if (isDevToolsPresent) {
        addFiberToLanesMap(root, fiber, lane);
      }
    }

    // 개발 상황 시.
    warnIfUpdatesNotWrappedWithActDEV(fiber);

    // 기본적으로 트랜지션 상황을 가정하지도 않았지만,
    // 개발 상황 시에만 사용한다.
    if (enableTransitionTracing) {
      const transition = ReactSharedInternals.T;
      if (transition !== null &amp;amp;&amp;amp; transition.name != null) {
        if (transition.startTime === -1) {
          transition.startTime = now();
        }

        addTransitionToLanesMap(root, transition, lane);
      }
    }

    // workInProgressRoot 를 파헤쳤고, fiberRoot 와 WIP 의 Root 또한 같다고 생각했다.
    // 그러나, &amp;quot;현재 렌더 중&amp;quot; 상태를 보여주는 것이 바로 &amp;quot;workInProgressRoot&amp;quot; 였으며,
    // 대부분의 상황에서 workInProgressRoot 는 null 이라는 것을 AI 가 알려줬다.
    // 즉, 이 분기는 해당되지 않는다.
    if (root === workInProgressRoot) {
      // Received an update to a tree that&amp;#39;s in the middle of rendering. Mark
      // that there was an interleaved update work on this root.
      // 즉, 렌더링 중간에도 트리에 대한 업데이트는 받는다.
      //
      if ((executionContext &amp;amp; RenderContext) === NoContext) {
        workInProgressRootInterleavedUpdatedLanes = mergeLanes(
          workInProgressRootInterleavedUpdatedLanes,
          lane,
        );
      }
      if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
        // The root already suspended with a delay, which means this render
        // definitely won&amp;#39;t finish. Since we have a new update, let&amp;#39;s mark it as
        // suspended now, right before marking the incoming update. This has the
        // effect of interrupting the current render and switching to the update.
        // TODO: Make sure this doesn&amp;#39;t override pings that happen while we&amp;#39;ve
        // already started rendering.
        const didAttemptEntireTree = false;
        markRootSuspended(
          root,
          workInProgressRootRenderLanes,
          workInProgressDeferredLane,
          didAttemptEntireTree,
        );
      }
    }

    // 말 그대로 루트 fiber 가 스케줄에 등록되도록 만드는 기능을 수행한다.
    // 보통은 루트가 1개가 등록되는데, 여러 루트를 등록할 경우,
    // 스케쥴러가 여러 루트들을 전부 관리하기 위해 이러한 메서드 기능을 넣어놨다.
    ensureRootIsScheduled(root);

    // 이 분기에는 해당하지 않음.
    if (
      lane === SyncLane &amp;amp;&amp;amp;
      executionContext === NoContext &amp;amp;&amp;amp;
      !disableLegacyMode &amp;amp;&amp;amp;
      (fiber.mode &amp;amp; ConcurrentMode) === NoMode
    ) {
      if (__DEV__ &amp;amp;&amp;amp; ReactSharedInternals.isBatchingLegacy) {
        // Treat `act` as if it&amp;#39;s inside `batchedUpdates`, even in legacy mode.
      } else {
        // Flush the synchronous work now, unless we&amp;#39;re already working or inside
        // a batch. This is intentionally inside scheduleUpdateOnFiber instead of
        // scheduleCallbackForFiber to preserve the ability to schedule a callback
        // without immediately flushing it. We only do this for user-initiated
        // updates, to preserve historical behavior of legacy mode.
        resetRenderTimer();
        flushSyncWorkOnLegacyRootsOnly();
      }
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;자... 이제, &lt;code&gt;enqueueSetState&lt;/code&gt; 메서드 내부의 &lt;code&gt;scheuleUpdateOnFiber&lt;/code&gt; 함수의 의미를&lt;/p&gt;
&lt;p&gt;알아볼 시간이다. 우선, &amp;quot;프로덕션&amp;quot;, &amp;quot;버튼 클릭&amp;quot;, &amp;quot;트랜지션 X&amp;quot; 상황에 맞게 필요한 내부 값들을 펼쳐보자.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;markRootUpdated(root, lane);&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ensureRootIsScheduled(root);&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;function markRootUpdated(root: FiberRoot, updateLane: Lane)&lt;/code&gt; : &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/811e203ed42c1a496790426a687d5045c473653d/packages/react-reconciler/src/ReactFiberLane.js#L772&quot;&gt;코드 위치&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;export function markRootUpdated(root: FiberRoot, updateLane: Lane) {
  root.pendingLanes |= updateLane;

  // 트랜지션 관련 - 이 분기는 들어가지 않는다.
  if (enableDefaultTransitionIndicator) {
    // 주석 해석 : 이 레인이 로딩 인디케이터를 보여줘야 할 필요가 있다는 것을 표시한다.
    root.indicatorLanes |= updateLane &amp;amp; TransitionLanes;
  }

  // 주석 해석 :
  // 만약에 어떠한 대기 트랜지션이 있다면, 이 새 업데이트가 블록 해제될 가능성이 있다.
  // 대기 레인을 지움으로서, 우리는 이 이벤트 렌더링을 다시 시도할 수 있다.
  //
  // TODO: We really only need to unsuspend only lanes that are in the
  // `subtreeLanes` of the updated fiber, or the update lanes of the return
  // path. This would exclude suspended updates in an unrelated sibling tree,
  // since there&amp;#39;s no way for this update to unblock it.
  //
  // 만약에 지금 들어온 업데이트가 idle 레인이라면, 이를 수행하지 않는다.
  // 왜냐면 idle 업데이트는 &amp;quot;모든&amp;quot; 정규 업데이트가 끝날 때 까지 절대 처리되지 않기 때문이다.
  // 트랜지션을 해제할 수 있는 것은 없다.
  if (updateLane !== IdleLane) {
    root.suspendedLanes = NoLanes;
    root.pingedLanes = NoLanes;
    root.warmLanes = NoLanes;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;먼저, 이 함수는 전달된 RootFiber 객체의 &lt;code&gt;pendingLanes&lt;/code&gt; 속성에&lt;/p&gt;
&lt;p&gt;&lt;code&gt;updateLane == SyncLane&lt;/code&gt; 을 주입한다.&lt;/p&gt;
&lt;p&gt;그리고, &lt;code&gt;updateLane&lt;/code&gt; 는 &lt;code&gt;SyncLane&lt;/code&gt; 이므로,&lt;/p&gt;
&lt;p&gt;전달된 인자인 &lt;code&gt;root : FiberRoot&lt;/code&gt; 에,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;FiberRoot.suspendedLanes = NoLanes&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FiberRoot.pingedLanes = NoLanes&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FiberRoot.warmLanes = NoLanes&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이렇게 3 개의 속성이 &lt;code&gt;NoLanes&lt;/code&gt; 로 초기화? 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;파일 : &lt;code&gt;react-reconciler/src/ReactFiberRootScheduler.js&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ensureRootIsScheduled(root : FiberRoot) : void&lt;/code&gt; : &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/3cfcdfb30720a5b8de0e981c8fdabec1abb61588/packages/react-reconciler/src/ReactFiberRootScheduler.js#L115&quot;&gt;코드 위치&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// 주석 해설 :
// 보류된 작업을 가지고 있는 모든 root 들에 대한 Linked List 를 의미한다.
// 보통은 하나의 root 를 가지지만, 우리는 다중 root 앱들도 지원한다.
// 따라서 이는 추가 복잡성을 일으킨다.
// 그러나, 이 모듈은 단일 root 의 경우에 대해 최적화 되어 있다.
export let firstScheduledRoot: FiberRoot | null = null;
let lastScheduledRoot: FiberRoot | null = null;

// 주석 해설 : microtask 가 중복으로 스케쥴 되는 것을 예방하는 데 사용한다.
let didScheduleMicrotask: boolean = false;

// 간단 해설 : 개발 상황 시 사용
let didScheduleMicrotask_act: boolean = false;

// 주석 해설 : 수행 할 동기적 작업이 없을 때, flushSync 를 빠르게 종료하기 위해 사용한다.
let mightHavePendingSyncWork: boolean = false;

let isFlushingWork: boolean = false;

let currentEventTransitionLane: Lane = NoLane;

export function ensureRootIsScheduled(root: FiberRoot): void {
  // 주석 해석 :
  // 이 함수는 루트가 업데이트를 받을 때 마다 호출된다.
  // 이 함수는 2 가지를 실행한다.
  //
  // 1) RootFiber 가 root schedule 내부에 있다는 것을 보증한다.
  // 2) 위로 인해 root schedule 를 처리할 pending microtask 가 있다는 것을 보증한다.
  //
  // 대부분의 실제 스케쥴링 로직은,
  // &amp;quot;scheduleTaskForRootDuringMicrotask&amp;quot; 가 실행 될 때 까지 발생하지 않는다.

  // 주석 해설 : 스케쥴에 root(FiberRoot) 를 추가한다.
  if (root === lastScheduledRoot || root.next !== null) {
    // 주석 해설 : 빠른 경로. 이 root(FiberRoot) 는 이미 스케쥴되었다.
  } else {
    if (lastScheduledRoot === null) {
      // 대부분의 경우 우리는 루트를 하나만 만들기 때문에 이 부분에 해당한다.
      firstScheduledRoot = lastScheduledRoot = root;
    } else {
      lastScheduledRoot.next = root;
      lastScheduledRoot = root;
    }
  }

  // 주석 해설 :
  // 어떤 시간이던 root 가 업데이트를 수신하면, 우리가 스케쥴을 처리하는 다음 때 까지 true 로 설정한다.
  // 만약 false 라면, 스케쥴을 확인하지 않고, 빠르게 flushSync 를 종료할 수 있다.
  mightHavePendingSyncWork = true;

  // 바로 아래에 코드가 존재함.
  ensureScheduleIsScheduled();

  // if(개발 상황 &amp;amp;&amp;amp; 레거시 상황)
}
export function ensureScheduleIsScheduled(): void {
  // 주석 해설 : 현재 이벤트의 종료에서, 각각의 루트들을 들러서
  // 알맞은 우선순위로 작업이 스케쥴 되어 있는지 보증한다.

  // 개발 상황 시를 가정하기에, 이 분기는 들어가지 않는다.
  if (__DEV__ &amp;amp;&amp;amp; ReactSharedInternals.actQueue !== null) {
    // We&amp;#39;re inside an `act` scope.
    if (!didScheduleMicrotask_act) {
      didScheduleMicrotask_act = true;
      scheduleImmediateRootScheduleTask();
    }
  } else { // 이 분기에 들어간다.
    if (!didScheduleMicrotask) {
      // 현재 파일 전역 값인 didScheduleMicrotask : boolean 값을 조정하고,
      didScheduleMicrotask = true;
      // scheduleImmediateRootScheduleTask() 를 실행한다.
      scheduleImmediateRootScheduleTask();
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;현재 상황에서의 이 코드와 해당 파일의 의미는,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;루트 1개가 보통이나, 관대하게 멀티 루트도 지원한다. 무조건 하나의 scheduledRoot 는 존재한다.&lt;/li&gt;
&lt;li&gt;현재 이벤트를 스케쥴 할 루트에서 작업을 수행하고 있는지 확인할 수 있는 변수들을 지원한다.&lt;/li&gt;
&lt;li&gt;즉, 이 메서드가 호출된다는 것은, 인자로 넣은 FiberRoot 의 작업이 무조건 있다는 것을 의미한다.&lt;/li&gt;
&lt;li&gt;결국 다른 파일의 메서드인 &lt;code&gt;scheduleImmediateRootScheduleTask()&lt;/code&gt; 를 실행한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;같은 파일 : ReactFiberRootScheduler.js&lt;/strong&gt; 의 메서드 참조&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function scheduleImmediateRootScheduleTask() {

  if (__DEV__ &amp;amp;&amp;amp; ReactSharedInternals.actQueue !== null) {
    // 개발 상황
  }

  // 간략정보 : ReactFiberConfigDOM 설정을 사용하므로,
  // supportsMicrotasks 는 &amp;quot;true&amp;quot; 로 설정된다.
  // queueMicrotask 라는 기능을 지원하는가 아닌가로 분류된다.
  // 따라서, 이 분기를 타게 된다.
  if (supportsMicrotasks) {
    // 간략 정보 :
    // scheduleMicrotask 메서드는 렌더러에 의해 결정되는 일종의 주입된 함수을 지칭한다.
    // 이는 기본 기능인 Promise 를 이용해서, scheduleMicrotask 내부의 함수를
    // 브라우저에서 알아서 비동기적으로 처리하도록 만든다.
    //
    scheduleMicrotask(() =&amp;gt; {
      // 정보 : ReactFiberWorkLoop.js 파일의 executionContext 전역 변수를 반환한다.
      // 이 전역 값은 이전 로직으로 인해 BatchedContext 로 변경되어 있다.
      // 따라서, 밑의 분기는 if(NoContext !== NoContext) 가 되므로, 성립하지 않는다.
      const executionContext = getExecutionContext();

      // 이 분기를 타지 않음. - 이 분기는 렌더링 중이거나, 적용 중 일때 탄다.
      if ((executionContext &amp;amp; (RenderContext | CommitContext)) !== NoContext) {
        // ...
      }
      processRootScheduleInMicrotask();
    });
  } else { // 이 분기는 브라우저에서 실행되지 않는다. - 렌더러에 의해 달라짐.(앱, 브라우저 등등..)
    // If microtasks are not supported, use Scheduler.
    Scheduler_scheduleCallback(
      ImmediateSchedulerPriority,
      processRootScheduleInImmediateTask,
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;supportsMicroTasks&lt;/code&gt; 변수는 전역 변수이나, &lt;code&gt;ReactFiberConfigDOM.js&lt;/code&gt; 의 파일 전역 변수이다.&lt;/p&gt;
&lt;p&gt;분명히 &lt;code&gt;supportsMicroTask&lt;/code&gt; 는 &lt;code&gt;ReactFiberConfig.js&lt;/code&gt; 에서 가져왔으나,&lt;/p&gt;
&lt;p&gt;해당 파일은 에러를 내보내는 2 줄의 코드만 담겨 있다.&lt;/p&gt;
&lt;p&gt;그리고 주석으로, 렌더러에 의해, 이 경로를 거치는 메서드와 변수는 모두 &amp;quot;렌더러&amp;quot; 에 의해 결정(주입)&lt;/p&gt;
&lt;p&gt;된다고 적혀 있다. 우리는 &amp;quot;브라우저&amp;quot; 라는 렌더러에 의해 결정된다.&lt;/p&gt;
&lt;p&gt;즉, 우리는 DOM 을 사용하는 설정(Config) 를 사용하므로, &lt;code&gt;ReactFiberConfigDOM.js&lt;/code&gt; 파일에서&lt;/p&gt;
&lt;p&gt;가져온 것이다. 이런 것이 바로 Convention 을 잘 익혀야 하는 이유가 아닐까 생각이 된다.&lt;/p&gt;
&lt;p&gt;동일한 의미에서, &lt;code&gt;scheduleMicrotask(콜백함수)&lt;/code&gt; 또한, 렌더러에 의해 결정되는 다른 파열 메서드이다.&lt;/p&gt;
&lt;p&gt;이상하게도 이는 깃허브의 자동 검색에서는 원하는 결과가 나오지 않았다.&lt;/p&gt;
&lt;p&gt;이 메서드 또한 &lt;code&gt;ReactFiberConfig.js&lt;/code&gt; 파일에서 불러오기에, &lt;code&gt;ReactFiberConfigDOM.js&lt;/code&gt; 파일에서 찾아보았다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// -------------------
//     Microtasks
// -------------------
export const supportsMicrotasks = true;
export const scheduleMicrotask: any =
  typeof queueMicrotask === &amp;#39;function&amp;#39;
    ? queueMicrotask
    : typeof localPromise !== &amp;#39;undefined&amp;#39;
      ? callback =&amp;gt;
          localPromise.resolve(null).then(callback).catch(handleErrorInNextTick)
      : scheduleTimeout; // TODO: Determine the best fallback here.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;직접적으로 &lt;code&gt;MicroTasks&lt;/code&gt; 라는 친절한 주석과 함께 전역 파일 변수들이 선언되어 있다.&lt;/p&gt;
&lt;p&gt;이게 무엇을 의미할까?&lt;/p&gt;
&lt;p&gt;신형 브라우저와 Node, Bun 대부분의 환경은 결국 &lt;code&gt;queueMicrotask === &amp;#39;function&amp;#39;&lt;/code&gt; 이므로,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;queueMicrotask&lt;/code&gt; 가 반환된다. 즉, 콜백으로 등록한 함수가 결국 그대로 콜백으로 실행된다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;그런데 뭔가 이상하다&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;실행하면 그냥 실행하면 될 것이지, &amp;quot;왜? 굳이 콜백으로 실행하나?&amp;quot;&lt;/p&gt;
&lt;p&gt;나는 Microtask 라는 개념을 &amp;quot;리액트&amp;quot; 가 또 만들어서 사용하는 줄 알았다.&lt;/p&gt;
&lt;p&gt;그런데, 내가 몰랐던 JavaScript 의 내부 기능이라는 것을 처음 알았다.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://javascript.info/event-loop?utm_source=chatgpt.com#tasks&quot;&gt;Event Loop : microtasks and macrotasks 공식 문서&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;queueMicrotask&lt;/code&gt; 는, 현재 콜백 함수들이 &amp;quot;비워질때까지&amp;quot; 기다리고, 비워지는 순간 실행된다.&lt;/p&gt;
&lt;p&gt;즉, 내가 &amp;quot;버튼 클릭&amp;quot; 이라는 행위로 인해 발생하는 &amp;quot;메인 콜백 함수들&amp;quot; 이 모두 실행되어&lt;/p&gt;
&lt;p&gt;상태 변경과 같은 메인 로직이 끝날 때 까지 기다린 후, diffing 과 repaint 와 같은&lt;/p&gt;
&lt;p&gt;실제 렌더링 과정을 실행하겠다는 이야기이다.&lt;/p&gt;
&lt;p&gt;그렇다면, 우리는 &lt;code&gt;scheduleMicrotask&lt;/code&gt; 로 콜백 함수를 등록하는 것이,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;queueMicrotask&lt;/code&gt; 내장 기능을 이용하여,&lt;/p&gt;
&lt;p&gt;우리가 만든 하나의 이벤트가 일으킨 상태 변화들이 마무리 될 때 까지 기다린 후,&lt;/p&gt;
&lt;p&gt;이를 한번에 처리한다는 의미가 되는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;동일한 &lt;code&gt;ReactFiberRootScheduler.js&lt;/code&gt; 파일의 &lt;code&gt;processRootScheduleInMicrotask()&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/65c4decb565b4eb1423518e76dbda7bc40a01c04/packages/react-reconciler/src/ReactFiberRootScheduler.js#L258&quot;&gt;코드 위치&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function processRootScheduleInMicrotask() {
  // 주석 해석 : 이 함수는 언제나 microtask 내부에서 호출된다.
  // 이건 절대로 동기적(일반 함수) 적으로 호출되면 안된다.
  //
  // 개인 해석 : 스케쥴러가 줄줄이 등록 될 때는 true 로 이어져서 등록되지만,
  // 메인 콜스택이 끝나면서 queueTask 가 실행될 때, didScheduleMicrotask = false;
  // 가 발동된다.
  didScheduleMicrotask = false;
  if (__DEV__) {
    didScheduleMicrotask_act = false;
  }

  // 주석 해석 : 우리는 모든 루트들과(특수 케이스) 스케줄링들을 돌아가면서 재계산 할 것이다.
  mightHavePendingSyncWork = false;

  let syncTransitionLanes = NoLanes;

  // 만약 현재 트랜지션 상황이라면, - 이 분기에 들어가지 않는다.
  if (currentEventTransitionLane !== NoLane) {
    // ...
  }

  // 지금 시각을 가져온다.
  const currentTime = now();

  let prev = null;
  let root = firstScheduledRoot; // 대부분의 경우, first..., lastScheduleRoot 가 동일
  while (root !== null) { // 멀티 루트일 경우 상정 - 보통은 한 번만 iterate 한다.

    // 현재 root 의 다음 스케줄 된 root 를 미리 추출 해 놓는다. -
    // 단일 루트라서 null 이다.
    const next = root.next;
    // 현재 루트와 시각을 같이 전달하여 업데이트 할 레인을 계산한다.
    const nextLanes = scheduleTaskForRootDuringMicrotask(root, currentTime);
    if (nextLanes === NoLane) {
      // 주석 해석 : 이 루트는 더 이상 대기&amp;amp;보류(pending) 된 작업이 없다.
      // 스케쥴로부터 이 루트를 제거한다.
      // 이러한 미묘한? 재 진입 버그를 막기 위해, 이 microtask를 수행하는 유일한 장소이다.
      // - 언제든 루트들을 스케쥴에 추가할 수 있지만, 오로지 이곳에서만 루트를 삭제 할 수 있는 곳이다.

      // 주석 해석 : 현재 root 에 대한 객체들을 null 로 만들어서 스케줄러로부터 제거한다.
      root.next = null;
      if (prev === null) {
        // 주석 해석 : 이 리스트의 새로운 헤드가 된다.
        firstScheduledRoot = next;
      } else {
        prev.next = next;
      }
      if (next === null) {
        // 주석 해석 : 이 스케줄 리스트의 새로운 꼬리가 된다.
        lastScheduledRoot = prev;
      }
    } else {
      // 주석 해석 : 이 루트는 여전히 작업할 것이 있다. 따라서 리스트에 남겨놓는다.
      prev = root;

      // Transition 상황일 시 발동. 그 외 위의 메서드의 결과가 SyncLane 으로 작업이 있을 때,
      // 등등과 같은 상황이 매칭 될 시 발동.
      if (
        syncTransitionLanes !== NoLanes ||
        includesSyncLane(nextLanes) ||
        (enableGestureTransition &amp;amp;&amp;amp; isGestureRender(nextLanes))
      ) {
        mightHavePendingSyncWork = true;
      }
    }

    // 이미 next 는 처음 루프문 돌 때, 단일 루트이기 때문에 null 이다.
    root = next; // root = null; 과 동일.
  }

  // 밑의 2 개의 분기 모두 트랜지션 상황에서 고려하는 분기이다. - 2 개 다 실행 x
  // 자세한 설명은 위의 코드 위치에서 확인하길 바랍니다.
  if (!hasPendingCommitEffects()) {
    flushSyncWorkAcrossRoots_impl(syncTransitionLanes, false);
  }

  if (currentEventTransitionLane !== NoLane) {
    // Reset Event Transition Lane so that we allocate a new one next time.
    currentEventTransitionLane = NoLane;
    startDefaultTransitionIndicatorIfNeeded();
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기서 제일 중요한 것은,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;const nextLanes = scheduleTaskForRootDuringMicrotask(root, currentTime);&lt;/code&gt; 이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;우리는 여러 개의 루트 돔을 만들지 않았다. -&amp;gt; 이 루프는 1 번만 돈다.&lt;/li&gt;
&lt;li&gt;트랜지션을 적용하지 않으며, 개발 상황을 상정하지 않는다. -&amp;gt; 프로덕션 &amp;amp;&amp;amp; Transition 관련 x&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;function scheduleTaskForRootDuringMicrotask(root: FiberRoot, currentTime: number) : Lane&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;a&gt;코드 위치 target=&amp;quot;_blank&amp;quot; href=&amp;quot;&lt;a href=&quot;https://github.com/facebook/react/blob/65c4decb565b4eb1423518e76dbda7bc40a01c04/packages/react-reconciler/src/ReactFiberRootScheduler.js#L383&amp;quot;&quot;&gt;https://github.com/facebook/react/blob/65c4decb565b4eb1423518e76dbda7bc40a01c04/packages/react-reconciler/src/ReactFiberRootScheduler.js#L383&amp;quot;&lt;/a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;이번 코드는 고려해야 할 것이 &amp;quot;정말정말&amp;quot; 많다..&lt;/p&gt;
&lt;p&gt;마음을 다잡고 또다시 분석한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function scheduleTaskForRootDuringMicrotask(
  root: FiberRoot,
  currentTime: number,
): Lane {
  // 주석 해석 : 이 함수는 언제나 microtask 내부에서 호출된다.
  // 혹은, 렌더링 작업 마지막 부분에서 메인 스레드에 양보하기 직전에 호출된다.
  // 이 함수는 절대로 동기적으로 호출되면 안된다.
  //
  // 또한 이 함수는 절대로 리액트의 작업을 동기적으로 수행하지 않는다.
  // 이 함수는 오로지 나중에 수행할 작업한 스케줄해야 한다.

  // 다른 작업으로 인해 레인이 부족한지 확인한다.
  // 이들을 expired 로 표시하여 다음 작업을 진행할 수 있게 만든다.
  markStarvedLanesAsExpired(root, currentTime);

  // 다음에 작업할 레인을 결정하고, 이 작업의 우선순위도 결정한다.
  const rootWithPendingPassiveEffects = getRootWithPendingPassiveEffects();
  const pendingPassiveEffectsLanes = getPendingPassiveEffectsLanes();
  const workInProgressRoot = getWorkInProgressRoot();
  const workInProgressRootRenderLanes = getWorkInProgressRootRenderLanes();
  const rootHasPendingCommit =
    root.cancelPendingCommit !== null || root.timeoutHandle !== noTimeout;
  const nextLanes =
    enableYieldingBeforePassive &amp;amp;&amp;amp; root === rootWithPendingPassiveEffects
      ? // This will schedule the callback at the priority of the lane but we used to
        // always schedule it at NormalPriority. Discrete will flush it sync anyway.
        // So the only difference is Idle and it doesn&amp;#39;t seem necessarily right for that
        // to get upgraded beyond something important just because we&amp;#39;re past commit.
        pendingPassiveEffectsLanes
      : getNextLanes(
          root,
          root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
          rootHasPendingCommit,
        );

  const existingCallbackNode = root.callbackNode;
  if (
    // Check if there&amp;#39;s nothing to work on
    nextLanes === NoLanes ||
    // If this root is currently suspended and waiting for data to resolve, don&amp;#39;t
    // schedule a task to render it. We&amp;#39;ll either wait for a ping, or wait to
    // receive an update.
    //
    // Suspended render phase
    (root === workInProgressRoot &amp;amp;&amp;amp; isWorkLoopSuspendedOnData()) ||
    // Suspended commit phase
    root.cancelPendingCommit !== null
  ) {
    // Fast path: There&amp;#39;s nothing to work on.
    if (existingCallbackNode !== null) {
      cancelCallback(existingCallbackNode);
    }
    root.callbackNode = null;
    root.callbackPriority = NoLane;
    return NoLane;
  }

  // Schedule a new callback in the host environment.
  if (
    includesSyncLane(nextLanes) &amp;amp;&amp;amp;
    // If we&amp;#39;re prerendering, then we should use the concurrent work loop
    // even if the lanes are synchronous, so that prerendering never blocks
    // the main thread.
    !checkIfRootIsPrerendering(root, nextLanes)
  ) {
    // Synchronous work is always flushed at the end of the microtask, so we
    // don&amp;#39;t need to schedule an additional task.
    if (existingCallbackNode !== null) {
      cancelCallback(existingCallbackNode);
    }
    root.callbackPriority = SyncLane;
    root.callbackNode = null;
    return SyncLane;
  } else {
    // We use the highest priority lane to represent the priority of the callback.
    const existingCallbackPriority = root.callbackPriority;
    const newCallbackPriority = getHighestPriorityLane(nextLanes);

    if (
      newCallbackPriority === existingCallbackPriority &amp;amp;&amp;amp;
      // Special case related to `act`. If the currently scheduled task is a
      // Scheduler task, rather than an `act` task, cancel it and re-schedule
      // on the `act` queue.
      !(
        __DEV__ &amp;amp;&amp;amp;
        ReactSharedInternals.actQueue !== null &amp;amp;&amp;amp;
        existingCallbackNode !== fakeActCallbackNode
      )
    ) {
      // The priority hasn&amp;#39;t changed. We can reuse the existing task.
      return newCallbackPriority;
    } else {
      // Cancel the existing callback. We&amp;#39;ll schedule a new one below.
      cancelCallback(existingCallbackNode);
    }

    let schedulerPriorityLevel;
    switch (lanesToEventPriority(nextLanes)) {
      // Scheduler does have an &amp;quot;ImmediatePriority&amp;quot;, but now that we use
      // microtasks for sync work we no longer use that. Any sync work that
      // reaches this path is meant to be time sliced.
      case DiscreteEventPriority:
      case ContinuousEventPriority:
        schedulerPriorityLevel = UserBlockingSchedulerPriority;
        break;
      case DefaultEventPriority:
        schedulerPriorityLevel = NormalSchedulerPriority;
        break;
      case IdleEventPriority:
        schedulerPriorityLevel = IdleSchedulerPriority;
        break;
      default:
        schedulerPriorityLevel = NormalSchedulerPriority;
        break;
    }

    const newCallbackNode = scheduleCallback(
      schedulerPriorityLevel,
      performWorkOnRootViaSchedulerTask.bind(null, root),
    );

    root.callbackPriority = newCallbackPriority;
    root.callbackNode = newCallbackNode;
    return newCallbackPriority;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h2&gt;여기서 일단 종료하는 것이 옳다.&lt;/h2&gt;
&lt;p&gt;렌더링을 위한 최종 커밋 과정까지 리뷰하기 위해 미리 코드를 추적하여 &lt;code&gt;commitRoot&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;메서드까지 도달했다. 그리고 나서 이 과정을 여기서 종료하기로 마음먹었다.&lt;/p&gt;
&lt;p&gt;현재 에디터에 3500줄이 좀 넘게 작성했는데, 마지막까지 가지 못해 아쉽다는 생각이 남았다.&lt;/p&gt;
&lt;h3&gt;현재까지 파악된 setState 의 데이터 흐름 그래프&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TB

subgraph Component.setState
    Com-setState1(&amp;quot;개발자가 지시한 사항이 객체인지 함수인지 파악 -&amp;gt; 둘 다 아니면 오류&amp;quot;)
    Com-setState2(&amp;quot;컴포넌트 자신과, 지시된 사항, callback 함수를 enqueueSetState 에게 넘김&amp;quot;)
end

subgraph updater.enqueueSetState
    subgraph Fiber
        fiber(&amp;quot;전달된 컴포넌트는 하나의 fiber 와 연결되어 있으므로, 이를 추출한다.&amp;quot;)
    end
    subgraph Lane
        lane-1(&amp;quot;현재 일어난 DOM 이벤트와 우선순위 레인을 이미 지정해 놓음 - 트랜지션 상황 가정 x&amp;quot;)
        lane-2(&amp;quot;즉, setState 와 DOM 이벤트는 떨어져 있으며, 타이밍을 맞게 설정해 놓음&amp;quot;)
    end
    subgraph Update
        update-1(&amp;quot;전달된 지시사항과 콜백 함수를 넣기 위한 기본적인 초기화 인스턴스를 반환&amp;quot;)
        update-2(&amp;quot;반환 후, payload, callback 으로 속성을 주입&amp;quot;)
    end
    subgraph FiberRoot
        fiberRoot-1(&amp;quot;인스턴스의 fiber 객체로부터 최종 부모를 찾는데, 이는 FiberRoot 이다.&amp;quot;)
        fiberRoot-2(&amp;quot;일반적인 경우 리액트 프로젝트 자체의 루트는 1 개이며, 멀티 루트도 지원한다.&amp;quot;)
        fiberRoot-3(&amp;quot;루트는 상태를 관리하는 핵심적인 정보이다.&amp;quot;)
    end
end

subgraph updater.scheduleUpdateOnFiber
    schedule-1(&amp;quot;루트 fiber, 컴포넌트 fiber, 우선순위 lane 을 인자로 받는다.&amp;quot;)
    schedule-2(&amp;quot;리액트에서 발생할 수 있는 모든 상황에 대해 특정 논리로 대응한다.&amp;quot;)
    schedule-3(&amp;quot;수십개의 파일에 존재하는 파일 전역 변수들을 가져와서 현재 상황을 파악한다.&amp;quot;)
    schedule-4(&amp;quot;이 메서드 실행 과정에서 queueMicrotask 를 쌓아놓는다.&amp;quot;)
    schedule-5(&amp;quot;즉, 렌더링 및 커밋 할 수행 목록들을 스케줄에 넣어놓는다&amp;quot;)
    schedule-6(&amp;quot;결과물을 계산하여 결과적으로 commitRoot 를 실행하여 렌더링한다.&amp;quot;)
    schedule-7(&amp;quot;diff 와 commit 과정에서 executionContext 는 RenderContext 나 CommitContext 로 잠시 변경한다.&amp;quot;)
    schedule-8(&amp;quot;이전의 과정들은 executionContext 에 따라 블로킹 될지, 언블로킹인지 결정된다.&amp;quot;)
end

Component.setState --&amp;gt; updater.enqueueSetState

updater.enqueueSetState --&amp;gt; updater.scheduleUpdateOnFiber&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;왜 그만두나?&lt;/h3&gt;
&lt;p&gt;나는 컴포넌트 내장 메서드의 &lt;code&gt;setState&lt;/code&gt; 를 실행시켰을 때, 상태 변화와 렌더링 과정을 지켜보고자 했다.&lt;/p&gt;
&lt;p&gt;조금은 긴 여정이 될 줄 알았던 코드의 구조는 끝이 보이지 않을 정도로 복잡했다.&lt;/p&gt;
&lt;p&gt;그렇다고 스파게티 코드란 것은 아니고, 하나의 거대하고 복잡한 예술품을 분석하는 느낌이었다.&lt;/p&gt;
&lt;p&gt;그러나, 나는 setState 수준에서 머물지 않고, &amp;quot;거의 모든&amp;quot; 연관 코드를 분석했다.&lt;/p&gt;
&lt;p&gt;이 과정에서 Virtual DOM + Fiber 아키텍쳐가 적용되었다는 것을 알 수 있었다.&lt;/p&gt;
&lt;p&gt;그러나, 나는 &lt;code&gt;enqueueSetState&lt;/code&gt; 메서드의 &lt;code&gt;scheduleUpdateOnFiber&lt;/code&gt; 메서드 내부를&lt;/p&gt;
&lt;p&gt;조사하면서 한계를 느꼈다.&lt;/p&gt;
&lt;p&gt;그 이유는, 바로 &lt;strong&gt;이 아키텍쳐의 작동 방식&lt;/strong&gt; 자체를 모르기 때문이다.&lt;/p&gt;
&lt;p&gt;나는 사실 React 에서 &lt;code&gt;useContext&lt;/code&gt; 를 어떻게 사용했었는지도 까먹었다.&lt;/p&gt;
&lt;p&gt;그런 사람이, 내부의 &lt;code&gt;fiber&lt;/code&gt;, &lt;code&gt;lane&lt;/code&gt;, &lt;code&gt;WIP Tree&lt;/code&gt;, &lt;code&gt;EventPriority&lt;/code&gt; 를 다룬 것이다.&lt;/p&gt;
&lt;p&gt;또 다른 이유로는, &amp;quot;setState&amp;quot; 를 통해 알아가는 로직 이 아니라,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;constructClassComponent&lt;/code&gt; 변수를 먼저 분석하여,&lt;/p&gt;
&lt;p&gt;루트 컴포넌트와 일반 컴포넌트의 설정 속성을 이해하고,&lt;/p&gt;
&lt;p&gt;각 컴포넌트 fiber 와 fiberRoot 가 가지는 속성들을 분석하여 알아야 했다.&lt;/p&gt;
&lt;p&gt;그렇지 않고 역으로 하나의 이벤트에서 아키텍쳐를 분석하니,&lt;/p&gt;
&lt;p&gt;이제는 처음 보는 수십개의 전역 파일 import 변수들이 정말로 내가 생각하는 값이 맞는지 알 수 없었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이러한 이유들로 인해 이 주제에 대한 React 아키텍쳐 분석 글은 마친다.&lt;/p&gt;
&lt;p&gt;나도 이러한 이유들이, 반대로 바라보아, 내가 도망친 이유라고도 할 수 있겠다.&lt;/p&gt;
&lt;p&gt;그러나, 나는 결국 다시 돌아와서 공부하게 될 것이다.&lt;/p&gt;
&lt;p&gt;나는 모르는 것이 있으면 반드시 알기로 약속했고, 개발자가 아닌 개발 엔지니어가 되기로 약속했다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;리액트를 잘 사용하지 못하면서 오픈소스를 경험한 느낌은?&lt;/h2&gt;
&lt;p&gt;리액트를 &amp;quot;잘&amp;quot; 사용하지는 못하지만, 기본적인 응용 방식은 안다.&lt;/p&gt;
&lt;p&gt;내가 확신하건대, 리액트 &amp;quot;사용법&amp;quot; 을 아무리 잘 아는 사람이라도,&lt;/p&gt;
&lt;p&gt;오픈소스를 까서 이해하기는 정말정말 어려운 도전이 될 것이라고 자신한다.&lt;/p&gt;
&lt;p&gt;그러나, 이 도전으로 당신은 뛰어난 코드 분석 능력을 갖추게 될 것이라고 자신있게 말할 수 있다.&lt;/p&gt;
&lt;p&gt;그리고 또 말할 수 있는 것은, 리액트는 JS 최신 기능을 접목하여 최적화 시킨,&lt;/p&gt;
&lt;p&gt;하나의 논리 예술품이라고 말할 수 있다. 이정도의 복잡성을 갖췄는데, 스파게티 코드가 없다.&lt;/p&gt;
&lt;p&gt;정말 미친 수준의 라이브러리 아닌가?&lt;/p&gt;
&lt;p&gt;그리고 깃허브의 코드 linking 기술은 프로젝트의 구조를 파악하는데 정말로 많은 도움이 되었다.&lt;/p&gt;
&lt;p&gt;이 기능이 없었다면, 이 정도까지 분석하지 못하고, 절반 정도만 가능했을지도 모른다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;아직 마무리하지 못한 기능을 설명하자면..&lt;/h2&gt;
&lt;p&gt;먼저 이 글을 작성하면서 파악했던 내용을 추상적으로 펼쳐보자면,&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Fiber&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;각 컴포넌트가 가지고 있는 고유의 속성 인스턴스이다.&lt;/p&gt;
&lt;p&gt;컴포넌트는 생성과 함께 &lt;code&gt;fiber&lt;/code&gt; 라는 속성과 1 : 1 매칭한다.&lt;/p&gt;
&lt;p&gt;그리고, 리액트 아키텍쳐의 실행에 필수적인 내용들을 포함한다.&lt;/p&gt;
&lt;p&gt;또한, &amp;quot;렌더링&amp;quot; 과정에서 &lt;strong&gt;Work In Progress&lt;/strong&gt; 라는 WIP 트리가 생성되는데,&lt;/p&gt;
&lt;p&gt;이 &lt;code&gt;fiber&lt;/code&gt; 인스턴스 하나 당, 동일한 속성을 가지는 WIP &lt;code&gt;fiber&lt;/code&gt; 가 생겨난다.&lt;/p&gt;
&lt;p&gt;이 WIP &lt;code&gt;fiber&lt;/code&gt; 는, 기존의 &lt;code&gt;fiber&lt;/code&gt; 객체의 &lt;code&gt;alternate&lt;/code&gt; 속성으로 할당되며,&lt;/p&gt;
&lt;p&gt;WIP &lt;code&gt;fiber&lt;/code&gt; 또한, 기존의 &lt;code&gt;fiber&lt;/code&gt; 객체를 &lt;code&gt;alternate&lt;/code&gt; 속성으로 할당하며, 체인을 형성한다.&lt;/p&gt;
&lt;p&gt;또한, 내부에는 파이버 공유 큐가 존재하는데, 이에 대해서 자세히 파악하지 못했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;2. Lane&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;우선순위를 가지고 있는 &amp;quot;특정 큐&amp;quot; 라고 생각하는 것이 맞다고 생각한다.&lt;/p&gt;
&lt;p&gt;이 레인들은 특정 이벤트에 대응하는 여러 Lane 들을 가진다.&lt;/p&gt;
&lt;p&gt;예시로, SyncLane, DefaultLane, TransitionLane, 등등 많음&lt;/p&gt;
&lt;p&gt;리액트는 &lt;code&gt;createRoot&lt;/code&gt; 와 함께, 현재 브라우저에 존재하는 대부분의 이벤트에 대해&lt;/p&gt;
&lt;p&gt;특정 이벤트 리스너를 &amp;quot;wrapper&amp;quot; 로 감싼다.&lt;/p&gt;
&lt;p&gt;이 래퍼는 &lt;code&gt;dispatchEvent&lt;/code&gt; 를 실행하며, 각 위치의 파일 전역 변수들을 세팅한다.&lt;/p&gt;
&lt;p&gt;즉, 현재 일어난 이벤트에 대해 &amp;quot;위임 가능&amp;quot; 혹은 &amp;quot;불가능&amp;quot; 으로 나누어&lt;/p&gt;
&lt;p&gt;컴포넌트가 이벤트를 대응할지, 아니면 컴포넌트 루트의 &lt;code&gt;FiberRoot&lt;/code&gt; 가 담당할지 나뉜다.&lt;/p&gt;
&lt;p&gt;특히, &lt;code&gt;dispatchEvent&lt;/code&gt; 는 &lt;code&gt;batchedUpdate&lt;/code&gt; 메서드를 통해 &lt;code&gt;executionContext&lt;/code&gt; 라는&lt;/p&gt;
&lt;p&gt;매우 중요한 현재 컨텍스트 정보를 업데이트 한다.&lt;/p&gt;
&lt;p&gt;이 부분을 제대로 다루지 못하고 넘어가는 것이 아쉽다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;3. Update&lt;State&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;우리가 컴포넌트에서 만약 &lt;code&gt;this.setState(() =&amp;gt; ({...}))&lt;/code&gt; 를 실행한다면,&lt;/p&gt;
&lt;p&gt;설정할 값이 어떻게 적용되는지를 알려주게 된 타입이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;enqueueSetState&lt;/code&gt; 에서는 초기화 된 &lt;code&gt;Update&amp;lt;State&amp;gt;&lt;/code&gt; 를 통하여, (&lt;code&gt;lane&lt;/code&gt; 에 의한 적용은 있음)&lt;/p&gt;
&lt;p&gt;적용할 업데이트 정보를 &lt;code&gt;enqueueUpdate&lt;/code&gt; 메서드에 넣는다.&lt;/p&gt;
&lt;p&gt;이 때, 해당 메서드의 파일이 관리하는 전역 배열이 존재하는데,&lt;/p&gt;
&lt;p&gt;여기에 &lt;code&gt;fiber&lt;/code&gt;, &lt;code&gt;queue&lt;/code&gt;, &lt;code&gt;update&lt;/code&gt;, &lt;code&gt;lane&lt;/code&gt; 을 차례로 넣는다.&lt;/p&gt;
&lt;p&gt;(&lt;code&gt;queue&lt;/code&gt; 는 컴포넌트 &lt;code&gt;fiber&lt;/code&gt; 내부의 공유 큐를 의미한다. 이것도 무언인지 파악하지 못했다.)&lt;/p&gt;
&lt;p&gt;이 과정에서, &lt;code&gt;fiber&lt;/code&gt; 내부 속성 레인이,&lt;/p&gt;
&lt;p&gt;리액트가 정적으로 고정 해 놓은 우선순위 레인으로 결합되며 정보가 바뀐다.&lt;/p&gt;
&lt;p&gt;중요한 건, &lt;code&gt;Update&amp;lt;State&amp;gt;&lt;/code&gt; 타입에, 우리가 설정한 함수 혹은 객체와, 콜백 함수가 들어간다는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이제 돌아와서, 아직 진행 중이던 &lt;code&gt;scheduleUpdateOnFiber&lt;/code&gt; 를 말하자면,&lt;/p&gt;
&lt;p&gt;이 때가 바로 위에서&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;fiber = 리액트 인스턴스 필요 정보&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lane = 이 이벤트 처리 우선순위&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;update = 우리가 설정한 처리 방식&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이를 적용하게 되는 부분이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;scheduleUpdateOnFiber&lt;/code&gt; 메서드를 따라 렌더링 정보까지 변경되는 최종 위치는,&lt;/p&gt;
&lt;p&gt;바로 &lt;code&gt;commitRoot&lt;/code&gt; 메서드이다.&lt;/p&gt;
&lt;p&gt;여태까지 봐 온 소수의 여러 파일들의 전역 변수나 메서드를 사용할 줄 알았지만,&lt;/p&gt;
&lt;p&gt;내가 전혀 예상하지 못한 파일들의 전역 변수와 메서드들로 가득 찼었다.&lt;/p&gt;
&lt;p&gt;이 메서드는 &amp;quot;스케줄러&amp;quot; 역할을 하는 파일들과 관련이 당연히 깊은데,&lt;/p&gt;
&lt;p&gt;이 스케줄러는 멀티 루트 시스템을 지원한다. 그러나, 주석으로 멀티 루트는 복잡하다고 언급이 되어 있다.&lt;/p&gt;
&lt;p&gt;이 메서드는 &amp;quot;현재 렌더링 중인지&amp;quot;, &amp;quot;적용 중인지&amp;quot;, 에 따른 변화가 존재하며,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;microTask&lt;/code&gt; 라는 매우 중요한 기능을 소개시켜 주었다.&lt;/p&gt;
&lt;p&gt;즉, 현재 실행중인 이벤트에 대한 처리가 끝난다면,&lt;/p&gt;
&lt;p&gt;엔진 자체적으로 &lt;code&gt;queueTask&lt;/code&gt; 로 등록된 함수들을 일괄 처리하는 것이다.&lt;/p&gt;
&lt;p&gt;그러니까, 현재 상태 변화로 인해 계산중인 상황이 끝날 때 까지(메인 함수 큐가 빌 때 까지)&lt;/p&gt;
&lt;p&gt;기다리다가, &lt;code&gt;queueTask&lt;/code&gt; 에 쌓인 업무들을 한꺼번에 &lt;code&gt;flush&lt;/code&gt;(쏟아내듯이 처리) 한다.&lt;/p&gt;
&lt;p&gt;이에 대한 내용은 반드시 검색해 보길 바란다. (최신 기능이긴 한데, 개념이 중요하다.)&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;이 글을 마무리 하면서 느낀 점&lt;/h2&gt;
&lt;p&gt;공부에도 순서가 있다는 것을 &amp;quot;처참하게&amp;quot; 느낀 경험이었다.&lt;/p&gt;
&lt;p&gt;당연히 돌아와서 이 Fiber Architecture 에 대해서 파악하기 위해 돌아올 테지만,&lt;/p&gt;
&lt;p&gt;아직은 때가 아니라는 판단이 든다.&lt;/p&gt;
&lt;p&gt;이유는, 나는 아직 &lt;code&gt;useContext&lt;/code&gt; 이나 커스텀 훅, 등 리액트를 제대로 사용해 보지 않은 사람이기 떄문이다.&lt;/p&gt;
&lt;p&gt;물론, 이에 대한 클론 코딩은 프로그래머스 부트캠프에서 진행했으나, 이에 대해 &amp;quot;이해했다&amp;quot; 라고는&lt;/p&gt;
&lt;p&gt;절대로 말할 수 없다.&lt;/p&gt;
&lt;p&gt;&amp;quot;리액트를 사용할 줄 알아요&amp;quot; 와, &amp;quot;리액트를 이해했어요&amp;quot; 는 매우 다른 개념이라고 생각한다.&lt;/p&gt;
&lt;p&gt;나는 후자인 &amp;quot;이해한다&amp;quot; 라는 접근법으로 공부했지만, &lt;strong&gt;그 순서가 틀린&lt;/strong&gt; 경우이다.&lt;/p&gt;
&lt;p&gt;만약에 이 글을 마지막까지 읽은 분이 계시다면, 끝까지 추적하지 못해 죄송하다는 말씀을 드립니다.&lt;/p&gt;
&lt;p&gt;그러나, 제 특성상 언젠간 이 아키텍쳐를 완벽히 이해하기 위해서 돌아올 것이라고 약속을 드립니다.&lt;/p&gt;
&lt;br/&gt;



&lt;hr&gt;
&lt;h2&gt;참조 사이트&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;React Github 코드 패키지 (ReactBaseClasses.js)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/main/packages/react/src/ReactBaseClasses.js&quot;&gt;https://github.com/facebook/react/blob/main/packages/react/src/ReactBaseClasses.js&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;React Github 코드 패키지 (ReactJSXElement.js)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;&quot; href=&quot;https://github.com/facebook/react/blob/main/packages/react/src/jsx/ReactJSXElement.js&quot;&gt;https://github.com/facebook/react/blob/main/packages/react/src/jsx/ReactJSXElement.js&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;DEV 사이트 블로그 글 (Turning &amp;#39;class ... extends React.Component&amp;#39; into a coding lesson)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://dev.to/carlmungazi/turning-class-app-extends-react-component-into-a-coding-lesson-3mod?utm_source=chatgpt.com&quot;&gt;https://dev.to/carlmungazi/turning-class-app-extends-react-component-into-a-coding-lesson-3mod?utm_source=chatgpt.com&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;React 레거시 문서 (제목 : React.Component)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;&quot; href=&quot;https://ko.legacy.reactjs.org/docs/react-component.html#render&quot;&gt;https://ko.legacy.reactjs.org/docs/react-component.html#render&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;React Github 코드 패키지 (ReactInstanceMap.js)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://github.com/facebook/react/blob/main/packages/shared/ReactInstanceMap.js&quot;&gt;https://github.com/facebook/react/blob/main/packages/shared/ReactInstanceMap.js&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;수십개의 외 참조 사이트들&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;깃허브의 &lt;code&gt;facebook/react/../src/react-reconciler/...&lt;/code&gt; 들입니다.&lt;/p&gt;</description>
      <category>Web-Server/React</category>
      <category>fiber</category>
      <category>Microtask</category>
      <category>queuetask</category>
      <category>react</category>
      <category>react 깃허브 소스</category>
      <category>scheduler</category>
      <category>setState</category>
      <category>virtualdom</category>
      <category>소스코드 해부</category>
      <category>오픈소스 읽기</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/227</guid>
      <comments>https://codecreature.tistory.com/227#entry227comment</comments>
      <pubDate>Wed, 2 Jul 2025 21:02:21 +0900</pubDate>
    </item>
    <item>
      <title>HTML 파일에서 React 사용하는 법 - (부제 : 클래스, 함수 컴포넌트 적용)</title>
      <link>https://codecreature.tistory.com/226</link>
      <description>&lt;h2&gt;제목 : HTML 파일에서 React 사용하는 법&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;이 글을 작성하는 이유&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;npm&lt;/code&gt;, &lt;code&gt;node&lt;/code&gt; 패키지로 &lt;code&gt;npx&lt;/code&gt; 명령어를 실행하여 손쉽게 React 시작 템플릿을 가져올 수 있다.&lt;/p&gt;
&lt;p&gt;즉, 일종의 프로젝트 초기화 명령어인데, 리액트로 프로젝트를 시작한다면, 이를 통해 손쉽게 구축이 가능하다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, React 는 컴퓨터 프로그램의 역사를 따져봤을 때, 긴 역사를 가지고 있지는 않다.&lt;/p&gt;
&lt;p&gt;즉, 아직 &lt;code&gt;tomcat&lt;/code&gt; 서버를 이용하거나, &lt;code&gt;httpd&lt;/code&gt; 와 같은 오래된 웹 서버로&lt;/p&gt;
&lt;p&gt;(물론, &lt;code&gt;tomcat&lt;/code&gt; 은 웹과 WAS 의 역할을 동시에 수행한다.)&lt;/p&gt;
&lt;p&gt;바닐라 JavaScript 와 HTML, CSS 파일로 스타일링을 진행하며,&lt;/p&gt;
&lt;p&gt;동적 DOM 구축을 위해 JQuery 를 사용하고 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이미 내부적으로 데이터셋을 명시하고, 이를 어떻게 보여 줄 지&lt;/p&gt;
&lt;p&gt;복잡한 코드로 구현된 이 HTML, CSS, JavaScript 레이어를,&lt;/p&gt;
&lt;p&gt;다시 React 로 시작해버리겠다는 것은 정말 터무니 없는 일에 가까울 것이다.&lt;/p&gt;
&lt;p&gt;차라리, 회사나 특정 조직, 혹은 팀에서 새로운 도메인의 웹 페이지를 만들겠다는 것이 훨씬 현실적이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그런 의미에서, 현재 이미 구축되어 있는 웹 서버 내부의 코드를 React 형식으로 바꾸겠다는 것은,&lt;/p&gt;
&lt;p&gt;DevOps 측면이나, 스타일링, 컴포넌트 계층 형성 자체를 전부 바꿔버리는 것과 동일하다고 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;따라서, 거대하면서 복잡한 웹 서버 코드를 &amp;quot;점진적으로&amp;quot; 변경하는 방법이 존재하는데,&lt;/p&gt;
&lt;p&gt;바로 &lt;strong&gt;CDN&lt;/strong&gt; 을 이용한 React 패키지 코드를 가져오는 것이다.&lt;/p&gt;
&lt;p&gt;이 방식을 이용하면, 현재 복잡하게 구축된 HTML 계층과 더불어,&lt;/p&gt;
&lt;p&gt;React cdn 이 제공하는 패키지의 클래스와 메서드를 이용하여 특정 부분을 React 화 시킬 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;참고로, &lt;strong&gt;CDN&lt;/strong&gt; 이란, Content Delivery Network 의 약어 로,&lt;br/&gt;&lt;br&gt;반복 요청되는 컨텐츠에 대해 가장 가까운 호스팅 서버에서 컨텐츠를 빠르게 전달해 주는 네트워크이다. &lt;br/&gt;&lt;br&gt;요즘 웹 서버를 띄우는 방식이 대부분 호스팅 사이트의 CDN 네트워크를 통해 올라오기 때문에, &lt;br/&gt;&lt;br&gt;이 개념에 대해서 잘 알고 있는 것이 중요하다. (Cloudflare 사이트가 잘 설명 해 놓음.)&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;br/&gt;

&lt;p&gt;React 와 같이 사용되는 수많은 라이브러리와 방법론으로 시작하는 방법은 공식 문서에 잘 나와 있다.&lt;/p&gt;
&lt;p&gt;그러나, 순수 React 를 일반 HTML 파일에서 시작하는 것은&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Legacy 시스템을 바꾸는 데 매우매우 중요한 지식이라고 생각하여 이 글을 작성하게 되었다.&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;일반 HTML 파일에서 React 구현해 보기&lt;/h2&gt;
&lt;hr&gt;

&lt;h3&gt;위에서 HTML 파일 내부에 CDN 으로 패키지는 왜 가져올까?&lt;/h3&gt;
&lt;p&gt;HTML 파일에서 우리는&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;script src=&amp;quot;{cdn 패키지 경로}&amp;quot; crossorigin&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;이 태그를 통하여 HTML 파일에서 JavaScript 를 이용 할 때,&lt;/p&gt;
&lt;p&gt;해당 패키지 경로로 JS 파일을 요청 한 뒤, 곧바로 파일을 실행한다.&lt;/p&gt;
&lt;p&gt;그리고 이제, 해당 페이지 내부의 Script 문으로 &lt;code&gt;React&lt;/code&gt; 컴포넌트를 사용 할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;패키지는 총 2 개가 필요하다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;&lt;a href=&quot;https://unpkg.com/react@18/umd/react.development.js&quot;&gt;https://unpkg.com/react@18/umd/react.development.js&lt;/a&gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&lt;a href=&quot;https://unpkg.com/react-dom@18/umd/react-dom.development.js&quot;&gt;https://unpkg.com/react-dom@18/umd/react-dom.development.js&lt;/a&gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;html 파일에서 react 사용하기&amp;lt;/title&amp;gt;
    &amp;lt;script
      src=&amp;quot;https://unpkg.com/react@18/umd/react.development.js&amp;quot;
      crossorigin
    &amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script
      src=&amp;quot;https://unpkg.com/react-dom@18/umd/react-dom.development.js&amp;quot;
      crossorigin
    &amp;gt;&amp;lt;/script&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;div id=&amp;quot;test-container&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;script&amp;gt;
      // 리액트 렌더링을 위한 DOM 을 가져오기
      const testContainer = document.querySelector(&amp;quot;#test-container&amp;quot;);

      // 구축될 리액트 루트를 생성 한 뒤, root 에 할당한다.
      const root = ReactDOM.createRoot(testContainer);

      root.render(
        React.createElement(&amp;quot;p&amp;quot;, null, &amp;quot;p 태그로 제작된 리액트 컴포넌트이다.&amp;quot;),
      );
    &amp;lt;/script&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;렌더링 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;screen1.png&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;128&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dEHkPq/btsOA7jttC6/ULAQKXFwgJMB5KKeh1SknK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dEHkPq/btsOA7jttC6/ULAQKXFwgJMB5KKeh1SknK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dEHkPq/btsOA7jttC6/ULAQKXFwgJMB5KKeh1SknK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdEHkPq%2FbtsOA7jttC6%2FULAQKXFwgJMB5KKeh1SknK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;522&quot; height=&quot;128&quot; data-filename=&quot;screen1.png&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;128&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;개발자 도구 Elements 결과 :&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- 코드 엘리먼트 구조 --&amp;gt;
&amp;lt;div id=&amp;quot;test-container&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;

&amp;lt;!-- 렌더링 엘리먼트 구조 --&amp;gt;
&amp;lt;div id=&amp;quot;test-container&amp;quot;&amp;gt;
    &amp;lt;p&amp;gt;p 태그로 제작된 리액트 컴포넌트이다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;렌더링 이후의 화면과, 엘리먼트 계층을 보면&lt;/p&gt;
&lt;p&gt;React 가 성공적으로 실행 되어 JavaScript 로 DOM 을 구축 한 것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;특히, 원하는 버전이 있다면, cdn 경로에서 숫자 &amp;quot;18&amp;quot; 을 &amp;quot;xx&amp;quot; 로 변경 해 주면 된다.&lt;/p&gt;
&lt;p&gt;그러나, 프로덕션 버전으로 배포 할 생각이라면,&lt;/p&gt;
&lt;p&gt;마지막 경로 문자열들에서&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;react.development.js&lt;/code&gt; --&amp;gt; &lt;code&gt;react.production.min.js&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;react-dom-development.js&lt;/code&gt; --&amp;gt; &lt;code&gt;react-dom.production.min.js&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이렇게 바꿔주면 된다. (최소 용량, 성능 최적화 버전)&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;React 의 상태 관리는 어떻게 할까?&lt;/h3&gt;
&lt;p&gt;템플릿에서는 함수형 컴포넌트와 &lt;code&gt;useEffect&lt;/code&gt; Hook 으로 생명주기와 렌더링을 매우 쉽게 조작 할 수 있다.&lt;/p&gt;
&lt;p&gt;그러나, 그 이전에는 class 형식의 컴포넌트가 자주 사용되기도 했다.&lt;/p&gt;
&lt;p&gt;컴포넌트의 상태 변수 관리는 컴포넌트 함수나 컴포넌트 클래스에서 이루어지는데,&lt;/p&gt;
&lt;p&gt;공식 문서에는 컴포넌트 클래스를 이용한 예제만 존재한다.&lt;/p&gt;
&lt;p&gt;클래스 형식의 컴포넌트를 알고 있는 사람들은 이미 알고 있겠지만,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;useEffect&lt;/code&gt; 는 클래스 컴포넌트가 가지고 있는&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;componentDidMount&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;shouldComponentUpdate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;등등..&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;과 같은 수많은 내장 메서드를&lt;/p&gt;
&lt;p&gt;&lt;code&gt;useEffect&lt;/code&gt; 콜백 함수로 쉽게 표현 할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;만약에&lt;/strong&gt;,&lt;/p&gt;
&lt;p&gt;정말로 기본 html 파일에서 리액트 컴포넌트 내에서 상태별 상황을 표현 할 것이라면,&lt;/p&gt;
&lt;p&gt;위에서 제시한 리액트 클래스 컴포넌트 내장 메서드 를 사용하거나,&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;함수 컴포넌트&lt;/strong&gt; 를 사용할 수 있다 :&lt;/p&gt;
&lt;br/&gt;

&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const {useState, useEffect} = React;

...

function TestFunctionalComponent = () =&amp;gt; {
  const [count, setCount] = useState(0);

  useEffect(() =&amp;gt; {
    console.log(&amp;quot;함수형 컴포넌트가 안착됨!&amp;quot;);
  }, []);

  return React.createElement(
    ...
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이러한 형식으로도 만들 수 있다!&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Component Class 를 이용한 예제&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;react-test.html&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
        &amp;lt;title&amp;gt;html 파일에서 react 사용하기&amp;lt;/title&amp;gt;
        &amp;lt;script src=&amp;quot;https://unpkg.com/react@18/umd/react.development.js&amp;quot; crossorigin&amp;gt;&amp;lt;/script&amp;gt;
        &amp;lt;script src=&amp;quot;https://unpkg.com/react-dom@18/umd/react-dom.development.js&amp;quot; crossorigin&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;div id=&amp;quot;test-container&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
        &amp;lt;script src=&amp;quot;p_tag_test.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;같은 계층에 존재하는 &lt;code&gt;p_tag_test.js&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// 리액트 렌더링을 위한 DOM 을 가져오기
let testContainer = document.querySelector(&amp;quot;#test-container&amp;quot;);

class CountArea extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count : 1,
    }
  }

  render() {
    return React.createElement(
      &amp;#39;p&amp;#39;,
      {onClick : () =&amp;gt; this.setState({count : this.state.count + 1})},
      `현재 이 글을 클릭 한 횟수는, ${this.state.count} 번 입니다.`
    )
  }
}

// 구축될 리액트 루트를 생성 한 뒤, root 에 할당한다.
const root = ReactDOM.createRoot(testContainer);

root.render(
  React.createElement(
    React.Fragment,
    null,
    React.createElement(
      &amp;quot;p&amp;quot;,
      null,
      &amp;quot;p 태그로 제작된 리액트 컴포넌트이다.&amp;quot;
    ),
    React.createElement(
      CountArea,
      null,
    )
  )
);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;결과&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;screen2.png&quot; data-origin-width=&quot;584&quot; data-origin-height=&quot;166&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FnIW3/btsOBatJx2D/xirXcspNwLBxuN7kAh5Yy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FnIW3/btsOBatJx2D/xirXcspNwLBxuN7kAh5Yy1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FnIW3/btsOBatJx2D/xirXcspNwLBxuN7kAh5Yy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFnIW3%2FbtsOBatJx2D%2FxirXcspNwLBxuN7kAh5Yy1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;584&quot; height=&quot;166&quot; data-filename=&quot;screen2.png&quot; data-origin-width=&quot;584&quot; data-origin-height=&quot;166&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;해석&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;React 를 홈페이지 단에서 사용하기 위해서,&lt;/p&gt;
&lt;p&gt;React 패키지 CDN 소스를 먼저 불러와야 한다. (그래야 그 다음 소스코드에서 React 를 인식하므로)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;div id=&amp;quot;test-container&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt; 는, 리액트가 Root 로 삼을 DOM 을 의미한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;script src=&amp;quot;p_tag_test.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;이 부분은, 위에 작성 해 놓은 &lt;code&gt;p_tag_test.js&lt;/code&gt; 소스코드를 실행시켜서,&lt;/p&gt;
&lt;p&gt;불러온 React 라이브러리를 통하여 컴포넌트를 실제로 렌더링 시키는 부분이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;내가 작성한 코드에서 아쉬운 부분&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;컴포넌트 클래스 부분과, 실제 DOM 과 React 결합 부분은 따로 나누는 것이 좋겠다고 생각한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;class CountArea...&lt;/code&gt; 는, &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; 태그 내부에 존재하지만,&lt;/p&gt;
&lt;p&gt;React CDN 라이브러리 부분의 하단에 선언하고,&lt;/p&gt;
&lt;p&gt;나머지 JS 코드 부분은 Root 가 될 DOM 인 &lt;code&gt;#test-container&lt;/code&gt; 속성 하단에&lt;/p&gt;
&lt;p&gt;선언하면, React 컴포넌트가 안정적으로 부착되는 것을 볼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;왜 태그를 사용하지 않고, createElement 를 사용하나?&lt;/h2&gt;
&lt;p&gt;현재 이 글을 작성하는 이유는, Legacy (오래된) 프로젝트에&lt;/p&gt;
&lt;p&gt;React 를 도입해야 할 경우, 어떻게 도입해야 하는가를 다루고 있다.&lt;/p&gt;
&lt;p&gt;물론 JSX 는 매우 편하다. 이 표현식을 일부로 사용하지 않는 것이 아니라,&lt;/p&gt;
&lt;p&gt;JSX 는 결국 JS 로 파싱되는데, 이 부분은 Webpack 과 같은 번들러 프로그램이 필요하기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;만약에 복잡한 Legacy 시스템에 JSX or TSX 를 도입하려면, (물론 TSX 도입 시 설정 복잡성이 미친듯이 증가할듯.)&lt;/p&gt;
&lt;p&gt;JSX 를 파싱하는 번들러 프로그램의 설정 파일에 대한 이해와, &lt;code&gt;package.json&lt;/code&gt;, &lt;code&gt;tsconfig.json&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;이 두 파일의 설정값 속성들을 이해하고 있어야 하며, 오래된 시스템과 맞물리는지 꼼꼼하게 확인해야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;그래도 무조건 JSX 표현식을 도입해야 한다면?&lt;/h3&gt;
&lt;p&gt;내가 만약에 오래된 웹 프로젝트에 React 를 추가해야 한다면, (일단 머리가 아프겠지만,)&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;React JSX 가 파싱되는 프로젝트는 기존 웹 페이지 소스의 하단 계층에서 제작한다.&lt;ul&gt;
&lt;li&gt;즉, npm 설정 환경을 기존 레거시 환경과 &amp;quot;격리&amp;quot; 한다.&lt;/li&gt;
&lt;li&gt;이게 가능한 이유는, 프로젝트는 가장 가까운 npm 설정과 모듈을 레퍼런스하기 때문이다.&lt;/li&gt;
&lt;li&gt;하단 계층이란, 직접 하위 디렉토리에 React 프로젝트를 생성한다는 의미이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;React DOM 이 안착될 Root DOM 을 레거시 웹 페이지에서 미리 선언 해 놓는다.&lt;ul&gt;
&lt;li&gt;그래야 React 가 시작할 Root 를 알고 가져올 수 있기 때문.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;&lt;code&gt;Webpack&lt;/code&gt; 설정을 바꾸어서, 결과물을 프로젝트 내부 &lt;code&gt;dist&lt;/code&gt; 가 아니라, 상단 계층에 생성하도록 만든다.&lt;ul&gt;
&lt;li&gt;이를 수행하기 위해서는 npm cli 명령으로 &lt;code&gt;웹팩 eject&lt;/code&gt; 를 사용해야 파일을 볼 수 있다.&lt;/li&gt;
&lt;li&gt;목적 파일의 경로 뿐만 아니라, 생성된 React 청크 파일을 기존 홈페이지에 어떻게 적용할지 생각해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;생성된 js, css 청크 파일을, 기존의 홈페이지의 태그에 추가해야 한다.&lt;ul&gt;
&lt;li&gt;Webpack 이라는 시스템은 새로운 React 프로젝트 내부의 시스템으로, Legacy 와는 격리되어 있다.&lt;/li&gt;
&lt;li&gt;그렇다면, 생성된 청크 파일들을 로드하기 위해서, Legacy 프로젝트에서 DOM 에 추가해 줘야 한다.&lt;/li&gt;
&lt;li&gt;이를 위해서는, 파일 이름 난독화를 해제하고, 코드 난독화도 해제 할 지 생각해야 한다.&lt;/li&gt;
&lt;li&gt;결국, 설정에 대한 깊은 이해와, 프로젝트 구조의 이해력에 이 문제가 달려 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;p&gt;위에 레거시 프로젝트에 리액트를 추가하는 법을 작성했는데,&lt;/p&gt;
&lt;p&gt;&amp;quot;어떤 기능과 DOM 까지 바꿀건지&amp;quot; 가 제일 중요할 것 같다..&lt;/p&gt;
&lt;p&gt;또한, 이 시스템을 점차 React 로 변환 할 것 이라면,&lt;/p&gt;
&lt;p&gt;기존 레거시 시스템 관리자와, 리액트 관리자의 소통이 매우매우 잘 되거나, or (세세한 문서화)&lt;/p&gt;
&lt;p&gt;기존 레거시 시스템에 대한 이해와,&lt;/p&gt;
&lt;p&gt;리액트 &amp;quot;전체&amp;quot; 시스템에 대한 이해도가 높은 사람이 진행해야 한다고 생각한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;물론, React 템플릿으로 Legacy 프로젝트 내부에 삽입해야 한다면, &lt;br/&gt;&lt;br&gt;Webpack 설정으로 충분히 커버가 가능하다. &lt;br/&gt;&lt;br/&gt;&lt;br&gt;그러나, &lt;code&gt;babel.js&lt;/code&gt; 이라는 Webpack 내장 모듈이 존재한다. &lt;br/&gt;&lt;br&gt;이를 통해서 JSX 를 충분히 JS, CSS 로 변환시킬 수도 있다. &lt;br/&gt;&lt;br&gt;하지만 항상 그렇듯이, 이 라이브러리 또한 설정 파일에 대한 공부가 필요하다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;br/&gt;

&lt;h3&gt;이런 복잡한 과정 말고, 그냥 홈페이지 소스 파일에 JSX 를 넣고싶다면?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;공식문서에서 말하길, 공부 목적과 간단한 데모 사이트&lt;/strong&gt; 에서는 괜찮지만,&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.legacy.reactjs.org/docs/add-react-to-a-website.html&quot;&gt;일반 홈페이지에서 JSX 파일 인식하게 만들기 (공식 문서)&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;성능 이슈로 인해 &lt;strong&gt;프로덕션&lt;/strong&gt; 상황에서는 맞지 않다고 한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;script src=&amp;quot;https://unpkg.com/babel-standalone@6/babel.min.js&amp;quot;&amp;gt;
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;즉, 홈페이지 사이트 자체에, Babel 라이브러리를 장착시켜서,&lt;/p&gt;
&lt;p&gt;클라이언트 브라우저가 JSX 파일 파싱을 직접 수행하도록 만드는 것이다.&lt;/p&gt;
&lt;p&gt;만약에 홈페이지에서 직접 JSX 파일을 불러오려면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;script type=&amp;quot;text/babel&amp;quot; src=&amp;quot;{JSX 파일}&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;형식으로 불러 올 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;다시 한번 말하지만&lt;/strong&gt;,&lt;/p&gt;
&lt;p&gt;프로덕션 환경에서는 &amp;quot;절대로&amp;quot; 권장하지 않는다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;안그래도 렌더링이 빨라야 하는데, JSX 파싱까지 하면 &lt;br/&gt;&lt;br&gt;클라이언트가 JSX 파일과 상호작용 할 때 마다, 버벅임을 보일 수 있다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;br/&gt;

&lt;h2&gt;함수형 컴포넌트 예시&lt;/h2&gt;
&lt;p&gt;역시, 클래스형 컴포넌트 말고, 함수형 컴포넌트도 예시로 들고 마무리하는 것이 좋을 것 같다.&lt;/p&gt;
&lt;p&gt;cdn 으로 가져온 패키지를 변경 할 필요는 없고,&lt;/p&gt;
&lt;p&gt;대신, 우리가 사용할 js 코드를 변경해야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;p_tag_test.js&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// 리액트 렌더링을 위한 DOM 을 가져오기
let testContainer = document.querySelector(&amp;quot;#test-container&amp;quot;);

const root = ReactDOM.createRoot(testContainer);

const { useState, useEffect } = React;

function CountArea () {
  const [count, setCount] = useState(0);

  useEffect(() =&amp;gt; {
    console.log(&amp;quot;함수형 컴포넌트가 안착됨&amp;quot;);
    return () =&amp;gt; {
      console.log(&amp;quot;함수형 컴포넌트가 해제됨&amp;quot;);
    }
  }, []);

  return React.createElement(
    &amp;quot;div&amp;quot;,
    {
      style : {
        color : &amp;quot;blue&amp;quot;,
        backgroundColor : &amp;quot;darkgray&amp;quot;,
        margin: 20
      }
    },
    React.createElement(
      &amp;quot;p&amp;quot;,
      null,
      `현재 이 부위가 클릭 된 횟수는 : ${count} 번 입니다.`
    ),
    React.createElement(
      &amp;quot;button&amp;quot;,
      { onClick : () =&amp;gt; setCount(count + 1)},
      &amp;quot;Count + 1&amp;quot;
    )
  )
}

root.render(
  React.createElement(
    React.Fragment,
    null,
    React.createElement(
      &amp;quot;p&amp;quot;,
      null,
      &amp;quot;p 태그로 제작된 리액트 컴포넌트이다.&amp;quot;
    ),
    React.createElement(
      CountArea
    )
  )
);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;screen3.png&quot; data-origin-width=&quot;2722&quot; data-origin-height=&quot;320&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfzPO9/btsOCCiaXj3/ePO4pUk5YvsYEKGRiM4TEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfzPO9/btsOCCiaXj3/ePO4pUk5YvsYEKGRiM4TEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfzPO9/btsOCCiaXj3/ePO4pUk5YvsYEKGRiM4TEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfzPO9%2FbtsOCCiaXj3%2FePO4pUk5YvsYEKGRiM4TEk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2722&quot; height=&quot;320&quot; data-filename=&quot;screen3.png&quot; data-origin-width=&quot;2722&quot; data-origin-height=&quot;320&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;설명&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;우리가 JSX 로 반환하는 형식 말고, &lt;code&gt;Reacr.createElement&lt;/code&gt; 메서드를 사용하면,&lt;/p&gt;
&lt;p&gt;결국 우리는 거의 동일한 형식으로 컴포넌트를 만들 수 있다!&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;이 글을 조사 및 작성하며 배운 것&lt;/h2&gt;
&lt;p&gt;요즘 나는 &amp;quot;방법론&amp;quot; 에 꽂혔다.&lt;/p&gt;
&lt;p&gt;이 말은, &amp;quot;나는 Webpack, Babel, npm, node, Library 를 통해 편하게 제작하는 것이 좋아!&amp;quot;&lt;/p&gt;
&lt;p&gt;가 아니고, &amp;quot;방법론&amp;quot; 에 휘둘리는 나는 진정한 소프트웨어 엔지니어가 될 수 없겠구나 판단을 내린 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그런 의미에서, Legacy 시스템에 어떻게 React 를 적용 할 수 있을까? 뿐만이 아니라,&lt;/p&gt;
&lt;p&gt;React 템플릿 내부에 갇혀, 이 도구들이 행하는 진정한 의미를 모른 채 사용하는 것이 스스로 안타까웠다.&lt;/p&gt;
&lt;p&gt;매우 편하고 생산성 있지만, 결국 이 도구들의 로직을 파악해야 중-고급 개발자로 넘어갈 수 있다는 것이&lt;/p&gt;
&lt;p&gt;나의 판단이었다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;차라리 이 노력으로 편하게 프로젝트를 만들면 포트폴리오에 좋지 않나? 취업해야지!&lt;/h3&gt;
&lt;p&gt;맞다. 너무나도 맞는 말이다.&lt;/p&gt;
&lt;p&gt;그런데, 먼저 내 블로그 최신 글 리스트를 보고,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;quot;이 글을 작성하는 이유&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;이 글을 작성하고 나서 배운 것&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 2 가지를 보면 내가 바라보는 소프트웨어 프로그래밍의 미래 예측과 방향성을 알 수 있을 것이다.&lt;/p&gt;
&lt;p&gt;나는 단순히 특정 프레임워크와 라이브러리에 종속되어 나의 한계를 지정하고 싶지 않다.&lt;/p&gt;
&lt;p&gt;나는 예측이 안 되는 모르는 지식을 넘기지 말고, 꼭 공부하여&lt;/p&gt;
&lt;p&gt;진정한 의미의 컴퓨터 엔지니어가 되겠다.&lt;/p&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;h2&gt;참고 사이트&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;이전 개발 문서 (웹 사이트에 React 추가하기)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.legacy.reactjs.org/docs/add-react-to-a-website.html&quot;&gt;https://ko.legacy.reactjs.org/docs/add-react-to-a-website.html&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;이전 개발 문서 (CDN 링크)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.legacy.reactjs.org/docs/cdn-links.html&quot;&gt;https://ko.legacy.reactjs.org/docs/cdn-links.html&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;이전 개발 문서 (JSX 없이 사용하는 React)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.legacy.reactjs.org/docs/react-without-jsx.html&quot;&gt;https://ko.legacy.reactjs.org/docs/react-without-jsx.html&lt;/a&gt;&lt;/p&gt;</description>
      <category>Web-Server/React</category>
      <category>html react</category>
      <category>html react cdn</category>
      <category>html react 적용법</category>
      <category>html 리액트 usestate</category>
      <category>html 리액트 적용</category>
      <category>react</category>
      <category>리액트</category>
      <category>리액트 html 적용</category>
      <category>클래스 컴포넌트</category>
      <category>함수 컴포넌트</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/226</guid>
      <comments>https://codecreature.tistory.com/226#entry226comment</comments>
      <pubDate>Mon, 16 Jun 2025 01:17:27 +0900</pubDate>
    </item>
    <item>
      <title>React, 기반 뿌리부터 살펴보자 - (부제 : 다양한 방법론의 조합)</title>
      <link>https://codecreature.tistory.com/225</link>
      <description>&lt;h2&gt;제목 : React, 기반 뿌리부터 살펴보자 - (부제 : 다양한 방법론의 조합)&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;이 글을 작성하는 이유&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;드디어, JavaScript 의 Syntax Sugar 의 형태,&lt;/p&gt;
&lt;p&gt;JS 의 private 의 또 다른 의미, (실질적으로는 보안을 위해 &lt;code&gt;#&lt;/code&gt; 으로 21 년 도입됨)&lt;/p&gt;
&lt;p&gt;Webpack 의 동작 원리, &lt;code&gt;package.json&lt;/code&gt;, &lt;code&gt;package-lock.json&lt;/code&gt;, &lt;code&gt;tsconfig.json&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;이 파일의 존재 이유에 대해서 파헤쳤다.&lt;/p&gt;
&lt;p&gt;또한, HTML5 정식 스펙상에 존재하는 &amp;quot;모든 카테고리의 태그&amp;quot; 를 다루었다. (실제 예제 및 렌더링 예시까지)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;특히, JS 로 제작된 대부분의 최신 프레임워크는 아직 정식 스펙에 도입되지 않은 문법을 채택하여&lt;/p&gt;
&lt;p&gt;이를 &lt;code&gt;tsconfig.json&lt;/code&gt; 의 옵션에서 적용하고 있다는 것이 놀라웠다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;async&lt;/code&gt; 와 &lt;code&gt;Promise&lt;/code&gt; 의 역할, 싱글 스레드라는 Node.js 의 기본 한계점을 없애기 위해&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Worker&lt;/code&gt; 라이브러리를 공부했으며, 이를 통해 JS 의 한계점 또한 알게 되었다.&lt;/p&gt;
&lt;p&gt;위의 내용을 보자면, &lt;strong&gt;&amp;quot;JS 는 단점이 많지 않나?&amp;quot;&lt;/strong&gt; 라고 생각이 들 수도 있지만,&lt;/p&gt;
&lt;p&gt;초-중반의 JS 문법 자체의 간단함과 그 커뮤니티의 거대함(EX - NPM) 은 일반인들도 쉽게 개발할 수 있게 만들었다.&lt;/p&gt;
&lt;p&gt;특히나, JS 자체가 2015 년 이후로 최적화되어 이전보다 속도가 훨등히 빨라졌기 때문에,&lt;/p&gt;
&lt;p&gt;유저 인터랙션과 수많은 컴포넌트들을 관리하는 과정에서 생각해야 할 최적화는 약간 후순위로 밀려났다.&lt;/p&gt;
&lt;p&gt;특히, 브라우저의 동적 시스템은 JS 를 기반으로 동작한다.&lt;/p&gt;
&lt;p&gt;물론, 모든 기능과 컴포넌트를 &lt;code&gt;.wasm&lt;/code&gt; 으로 만들어서(C++, Rust, Java, Go 로 만든 모듈)&lt;/p&gt;
&lt;p&gt;속도 면에서 월등하게 최적화가 가능하겠지만, 생태계가 주는 라이브러리와 컨벤션은 이길 수 없다.&lt;/p&gt;
&lt;p&gt;나는 Development Engineer 가 되기 위해 2 가지 트랙을 동시에 진행 할 것이다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;C++ 도 아닌 C 에서, 최소한의 라이브러리를 사용하여 파생 언어의 특징을 쉽게 받아들일 수 있도록 만든다.&lt;/li&gt;
&lt;li&gt;현재(25년 6월 기준) 가장 활발하게 사용되고 있는 React 라이브러리를 사용하여 &lt;br/&gt; 현재 웹 사이트 제작의 트렌드(컨벤션) 을 배우며, 내가 웹 모듈로 최적화 할 수 있는 부분을 찾는다.&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;p&gt;C 언어를 사용하는 것도 정말 Legacy 시스템을 사용하는 것이지만,&lt;/p&gt;
&lt;p&gt;나는 개인적으로 &amp;quot;최소한의 필요 라이브러리&amp;quot; 만을 사용한다는 제약을 걸었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;또한, React 를 통해 JS 를 통한 문서 개발에 착수한다.&lt;/p&gt;
&lt;p&gt;이는 상반된 공부라는 것을 잘 알고 있지만, 두 가지의 공부 과정에서 얻는 지식과 연결고리는 중요하다고 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;그래서 React 란 무엇인가?&lt;/h2&gt;
&lt;p&gt;현대 모던 웹 사이트 프로그래밍은 10년 전과 비교할 수 없을 정도로 복잡해졌다.&lt;/p&gt;
&lt;p&gt;하드웨어의 발전과, 소프트웨어의 최적화 및 접근성이 발달됨에 따라 개발의 문턱은 낮아졌다.&lt;/p&gt;
&lt;p&gt;이전에 웹 문서를 작성하기 위해 &lt;code&gt;html&lt;/code&gt; 파일로 작업하고, 필요 한 경우 JavaScript 모듈을&lt;/p&gt;
&lt;p&gt;부착하여 기능을 추가하는 방식으로 진화했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;하지만, 점점 고도화 되어 웹 사이트에 수많은 유틸성 기능과 애니메이션이 추가되기 시작됐다.&lt;/p&gt;
&lt;p&gt;이는 단순히 JavaScript 의 모듈 복잡성을 일으킬 뿐만 아니라, 각 코드의 의존성 계층을&lt;/p&gt;
&lt;p&gt;전부 파악하여 순서대로 놓아야 하는 지경에까지 이르른 것이다.&lt;/p&gt;
&lt;p&gt;물론, 이 때 당시 JQuery 를 통한 DOM 조작 라이브러리가 존재했지만,&lt;/p&gt;
&lt;p&gt;따로 &amp;quot;컴포넌트&amp;quot; 개념을 만들어 가상의 DOM 을 사용하면서, 최적화를 이룬 것이 React 이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;웹 페이지는 더 이상 정보를 전송해 주기만 하는 어플리케이션이 아니었다.&lt;/p&gt;
&lt;p&gt;이제는, 사용자의 생활 편의성을 위한 프로그램, 보안, 상호작용, 게임 등등 수많은 역할을 하고 있다.&lt;/p&gt;
&lt;p&gt;이러한 프로그램을 작성하기 위해서는 &amp;quot;정적&amp;quot; 속성에 가까운 HTML 파일만으로는 한계가 존재했다.&lt;/p&gt;
&lt;p&gt;따라서, 현대 웹 개발 시스템에 걸맞은 개발 라이브러리로 React 가 각광받았으며, 가장 큰 커뮤니티를 가지고 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;현재 React 는 여러 방법론의 조합 결과이다.&lt;/h3&gt;
&lt;p&gt;위의 소제목은 내가 React 를 다시 공부하기 전, 부족했던 웹 지식을 다시 쌓으면서 깨닫은&lt;/p&gt;
&lt;p&gt;개인적인 문구이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;현재 사람들은, 웹 개발을 위해 먼저 HTML 을 공부하는 것이 아니라, React 로 접하는 경우가 많아졌다.&lt;/p&gt;
&lt;p&gt;심지어는, React 를 통해 JavaScript 를 익히게 된다.&lt;/p&gt;
&lt;p&gt;사실상, React 의 최종 결과물은 HTML, JS, CSS 이며,&lt;/p&gt;
&lt;p&gt;제작 과정은 JavaScript, TypeScript, Webpack 으로 이루어져 있는데,&lt;/p&gt;
&lt;p&gt;React 를 시작하고, 아름다운 결과물을 먼저 관람 한 뒤, 구성된 프로그램을 나중에 배우게 된다.&lt;/p&gt;
&lt;p&gt;나 또한 React 로 웹 개발을 시작 한 사람이다. 위의 과정은 실제로 내가 실행했던 순서이다.&lt;/p&gt;
&lt;p&gt;React 는 공부 순서가 거꾸로 되어도 웹 페이지를 제작 할 수 있을 만큼, 쉽게 매력적인 라이브러리이다.&lt;/p&gt;
&lt;p&gt;그러나, React 를 통해 웹 제작 실력을 중급으로 끌어올리기 위해서는 굉장히 힘든 과정이 이어진다.&lt;/p&gt;
&lt;p&gt;그 이유는, React 를 사용하더라도, 결국 브라우저 유틸리티, JS 문법, 필요 라이브러리,&lt;/p&gt;
&lt;p&gt;Webpack 의 구성 결과, &lt;code&gt;package.json&lt;/code&gt;, &lt;code&gt;tsconfig.json&lt;/code&gt; 의 설정 파일,&lt;/p&gt;
&lt;p&gt;등등 수없이 많은 기초 지식이 결국은 사용되기 때문이다.&lt;/p&gt;
&lt;p&gt;단순히 코드를 따라서 치다 보면, 따라가기가 어려워서 &amp;quot;일단 외우고 보자&amp;quot; 가 된다.&lt;/p&gt;
&lt;p&gt;이 메서드와, 라이브러리, 및 프로그램을 &amp;quot;왜?&amp;quot; 사용하는지도 모르고 말이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;따라서, React 는 &lt;strong&gt;여러 방법론의 조합&lt;/strong&gt; 으로 나온 매우 간편한 라이브러리라는 것을 알아야 한다.&lt;/p&gt;
&lt;p&gt;하나의 언어처럼 이를 익힌다면, 중급 이상의 기능을 익히기 위해&lt;/p&gt;
&lt;p&gt;다시 기초로 돌아가야 하는 상황이 거의 무조건 일어날 것이라고 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;그래서, React 는 어떤 방법론을 사용하고 있나?&lt;/h3&gt;
&lt;p&gt;먼저, HTML, CSS, JS 만으로 웹 개발을 시작한 사람과,&lt;/p&gt;
&lt;p&gt;React 로 웹 개발을 시작한 사람 사이에서 이해할 수 있는 방법론의 차이는 존재한다.&lt;/p&gt;
&lt;p&gt;React 자체가 모든 것을 해 주고 있었으므로, 어떤 것을 도와줬는지 파악하기 어렵다.&lt;/p&gt;
&lt;p&gt;나는 웹 개발 실력의 포텐셜(잠재력) 을 높이기 위해, 다시 태초마을로 돌아와서&lt;/p&gt;
&lt;p&gt;사용되었던 방법론들을 공부 한 사람이다. 한번, 대표적인 예시를 들어 보겠다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;1. JSX, TSX 파일&lt;/h4&gt;
&lt;p&gt;리액트에서 컴포넌트를 제작하기 위해 사용되는 대표적인 파일이다.&lt;/p&gt;
&lt;p&gt;웹 페이지 개발 시, 특정 시멘틱 태그를 이용한 DOM 조작에는 여러 방식이 존재한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;document&lt;/code&gt; 최상위 객체에 존재하는 기능을 이용하여 DOM 객체 생성 &amp;amp; 속성 조작&lt;/li&gt;
&lt;li&gt;특정 태그 컴포넌트를 추출하여, 기본적으로 존재하는 기능 &lt;code&gt;innerHTML&lt;/code&gt; 로 생성&lt;/li&gt;
&lt;li&gt;&lt;code&gt;append&lt;/code&gt;, &lt;code&gt;appendChild&lt;/code&gt;, &lt;code&gt;removeChild&lt;/code&gt; 등등 NodeList 조작 메서드 사용&lt;/li&gt;
&lt;li&gt;등등등...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;여러 방식이 존재하나, 사용자 정의 컴포넌트나, 속성 주입 및 동적인 변화를 주기에 어렵다.&lt;/p&gt;
&lt;p&gt;그리고, 애초에 JavaScript 에서 &lt;code&gt;&amp;lt;div&amp;gt;...&amp;lt;/div&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;span&amp;gt;...&amp;lt;/span&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;과 같이, 처음부터 태그를 사용하여 내부 중첩된 태그 표현식을 사용할 수 없다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, JSX, TSX 파일을 이용하여 컴포넌트를 제작하면, 컴포넌트 제작이 매우 쉬워진다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;공유 유틸리티 Hook 들을 주입하기 매우 간편하다.&lt;/li&gt;
&lt;li&gt;컴포넌트 자체적으로 변수를 가질 수 있다.&lt;/li&gt;
&lt;li&gt;React 에서, 대부분의 시멘틱 태그들을 구현 해 놓았다.&lt;/li&gt;
&lt;li&gt;컴포넌트 생성, 부착, 제거 등등의 과정에서 특정 행동을 지시 할 수 있다.&lt;/li&gt;
&lt;li&gt;제작한 컴포넌트와 사용자가 상호작용 하는 과정을 매우 쉽게 제작할 수 있다.&lt;/li&gt;
&lt;li&gt;무엇보다도, html 파일에 태그를 넣듯이 복잡한 계층을 쉽게 표현 할 수 있다.&lt;/li&gt;
&lt;li&gt;컴포넌트에 속성에 자주 변하는 속성을 넣을 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;예를 들어서, JSX 파일은 이렇게 작성된다 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;const CustomComponent1 = () =&amp;gt; {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;span&amp;gt;내가 직접 만든 컴포넌트&amp;lt;/span&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

// or

function CustomComponent2 = ({props}) =&amp;gt; {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;span&amp;gt;이 컴포넌트들 사용하는 상위 컴포넌트가 props 를 내려줄 수도 있음&amp;lt;/span&amp;gt;
      &amp;lt;CustomComponent1 /&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;굉장히 쉽게 제작이 되고 있는 것을 알 수 있다.&lt;/p&gt;
&lt;p&gt;심지어, 내가 만든 컴포넌트가 또 다른 컴포넌트에 삽입되어 반복, 혹은 공용 컴포넌트 제작이 간편하다.&lt;/p&gt;
&lt;p&gt;이는 JavaScript 와, XML 표현 방식이 어울러진 방식으로, &lt;strong&gt;JSX&lt;/strong&gt; 라고 부른다.&lt;/p&gt;
&lt;p&gt;물론, 여기에 TypeScript 를 적용한다면, &lt;strong&gt;TSX&lt;/strong&gt; 가 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;2. Webpack 을 이용한 번들링&lt;/h4&gt;
&lt;p&gt;위의 JSX, TSX 내용과 이어지는데,&lt;/p&gt;
&lt;p&gt;위의 시멘틱 태그 및 커스텀 태그 표현식은 JavaScript 정식 Syntax 가 아니다.&lt;/p&gt;
&lt;p&gt;원래는,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const CustomElement1 = () =&amp;gt; React.createElement(
  &amp;#39;div&amp;#39;,
  null,
  React.createElement(&amp;#39;span&amp;#39;, null, &amp;#39;내가 직접 만든 컴포넌트&amp;#39;);
);

const CustomElement2 = (props) =&amp;gt; React.createElement(
  &amp;#39;div&amp;#39;,
  null,
  React.createElement(
    &amp;#39;span&amp;#39;,
    null,
    &amp;#39;이 컴포넌트를 사용하는 상위 컴포넌트가 props 를 내려 줄 수도 있음&amp;#39;
  ),
  React.createElement(
    CustomElement1,
    null
  )
)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위와 같은 형식으로, 진정한 JavaScript 표현식으로 생성된다.&lt;/p&gt;
&lt;p&gt;Webpack 은 JSX, TSX 파일에서 만들어진 컴포넌트들을&lt;/p&gt;
&lt;p&gt;&lt;code&gt;React.createElement&lt;/code&gt; 의 형식으로 재귀적으로 생성한다.&lt;/p&gt;
&lt;p&gt;즉, JSX, TSX 파일은 그 자체로 정식 스펙이 아니라,&lt;/p&gt;
&lt;p&gt;결국 Webpack 과 같은 번들링 프로그램으로 &amp;quot;다시 Parsing&amp;quot; 되는 파일이라는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;또한, &lt;code&gt;sass&lt;/code&gt; 와 같은 스타일링 외부 확장자 또한 Webpack 이 인식하여&lt;/p&gt;
&lt;p&gt;특정 컴포넌트에 안착시켜주는 역할도 진행한다.&lt;/p&gt;
&lt;p&gt;따라서, 배포 시, 혹은 개발 시 웹 어플리케이션은 무조건 JS 와 CSS 로 실행되므로,&lt;/p&gt;
&lt;p&gt;Webpack 은 이러한 의존 관계를 해석하여 &lt;code&gt;js&lt;/code&gt;, &lt;code&gt;css&lt;/code&gt; 로 만들고,&lt;/p&gt;
&lt;p&gt;이를 각각 하나의 Chunk 파일로 만들어서 웹에 제공한다.&lt;/p&gt;
&lt;p&gt;이 때, 파일의 크기가 너무 크다면, 이를 나누어서 Code Spliting(코드 스플리팅) 도 해준다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이러한 파싱의 결과는 생각보다 외부에서 취약점을 찾는 데 쉬울 수도 있다.&lt;/p&gt;
&lt;p&gt;따라서, Webpack 은 &amp;quot;코드 난독화&amp;quot; 를 진행하여, 읽지 못하도록 만들어 버린다.&lt;/p&gt;
&lt;p&gt;즉, Webpack 은 개발은 편하게 만들어 주며, 컴파일 자동화 를 수행하고 있다.&lt;/p&gt;
&lt;p&gt;그런데, 만약에 React + TypeScript 프로젝트를 진행하면서,&lt;/p&gt;
&lt;p&gt;예를 들어 경로를 &lt;code&gt;tsconfig.json&lt;/code&gt; 에 선언하여 간소화 시켰다면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;webpack&lt;/code&gt; 에도 이를 알려주는 설정 파일을 작성해야 할 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;3. React Hook 과 Custom Hook&lt;/h4&gt;
&lt;p&gt;동적 페이지를 가장 간편하게 만들어주며,&lt;/p&gt;
&lt;p&gt;개발자가 의도하는 페이지의 상호작용을 직접 커스텀하게 해 주는&lt;/p&gt;
&lt;p&gt;가장 중요한 기능 중 하나라고 생각한다.&lt;/p&gt;
&lt;p&gt;&amp;quot;아주 대표적인 Hook&amp;quot; 은, 바로 &lt;code&gt;useState&lt;/code&gt; 이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;우리가 홈페이지를 HTML 로 작성한다고 상상 해 보자.&lt;/p&gt;
&lt;p&gt;우리는, 직접 만든 DOM 에 어떻게 변화된 데이터를 출력 할 것인가?&lt;/p&gt;
&lt;p&gt;아마 HTML 태그 선언 후, 이러한 js 코드가 붙게 될 것이다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div&amp;gt;
    &amp;lt;span id=&amp;quot;counter&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;
    &amp;lt;button onclick=&amp;quot;count()&amp;quot;&amp;gt;카운트&amp;lt;/button&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;script&amp;gt;
let count = 0;
const countText = document.getElementById(&amp;quot;counter&amp;quot;);
countText.innerText = count;

function count() {
  count++;
  countText.innerText = count;
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 형식은, &amp;quot;정적 속성&amp;quot; DOM 과,&lt;/p&gt;
&lt;p&gt;&amp;quot;동적 수행&amp;quot; 담당 JS 부분이 따로 나뉜 것이다.&lt;/p&gt;
&lt;p&gt;물론 이러한 방식으로 동적 페이지 구성이 가능하긴 하겠지만,&lt;/p&gt;
&lt;p&gt;그 수많은 컴포넌트의 동적 변화를 구성하기 위해서,&lt;/p&gt;
&lt;p&gt;가독성이 매우 떨어질 뿐만 아니라, 이벤트 구독 및 수행을 구현하기 위해 너무 많은 코드가 작성된다.&lt;/p&gt;
&lt;p&gt;여기서, React 는 각 컴포넌트가 독립적인 &amp;quot;함수 or 클래스&amp;quot; 로서,&lt;/p&gt;
&lt;p&gt;자체적인 변수와 이벤트 리스너를 가지며, 이를 반환할 JSX 표현식에 사용 할 수 있다.&lt;/p&gt;
&lt;p&gt;즉, React 만의 Hook 들이 존재하는데, 이 라이브러리가 쉽게 표현 해 준다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;const TestingComponent = () =&amp;gt; {
  const [count, setCount] = useState(0);

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;span&amp;gt;{count}&amp;lt;/span&amp;gt;
      &amp;lt;button onClick={ () =&amp;gt; setCount(count + 1) }&amp;gt;카운트 올리기&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;jsx 표현식으로 매우 간단하게 표현 할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;useState&lt;/code&gt; 는 아주 기본적인 React 의 Hook 인데,&lt;/p&gt;
&lt;p&gt;count 는 &lt;code&gt;useState&lt;/code&gt; 로 인해 등록된 변수를 &amp;quot;참조&amp;quot; 할 수 있는 일종의 변수이고, (변경 불가능)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;setCount&lt;/code&gt; 는 &lt;code&gt;useState&lt;/code&gt; 로 인해 생겨난 변수를 조작할 수 있는 &lt;code&gt;Dispatcher&lt;/code&gt; 이다.&lt;/p&gt;
&lt;p&gt;현재는 매우 지역적으로(좁은 영역으로) 사용하고 있어서 잘 못느끼겠지만,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Dispatcher&lt;/code&gt; 라는 개념은 React 에서 매우매우 중요하다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;또한, 들어오는 도메인 데이터를 중심으로 Custom Hook 파일을 만들어서,&lt;/p&gt;
&lt;p&gt;컴포넌트가 원하는 Hook 들만 가져올 수 있게끔 만들수도 있다.&lt;/p&gt;
&lt;br&gt;

&lt;h4&gt;4. React 와 TypeScript&lt;/h4&gt;
&lt;p&gt;모든 JavaScript 기반 엔진들이 공통적으로 가지는 장점과 단점 (Pros and Cons) 가 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;장점 : 타입이 너무 자유롭다&lt;/li&gt;
&lt;li&gt;단점 : 타입이 너무 자유롭다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;JavaScript 로 처음 공부하면 너무나도 자유로운 타입 덕분에 코딩이 매우 쉽다.&lt;/p&gt;
&lt;p&gt;컴파일 언어가 아니라, 인터프리터 언어이기에, 실행 중에 번역되어 사용된다.&lt;/p&gt;
&lt;p&gt;저수준 ~ 중간 수준의 언어는 &amp;quot;먼저 타입 선언&amp;quot; 이 필수이기 때문에,&lt;/p&gt;
&lt;p&gt;주고받으며, 처리하는 데이터의 유형이 코드에 명확히 선언되어야 한다.&lt;/p&gt;
&lt;p&gt;그러나, JavaScript 는 전혀 그렇지 않다.&lt;/p&gt;
&lt;p&gt;어떤 코드 방식까지도 가능하냐면,&lt;/p&gt;
&lt;p&gt;&amp;quot;일단 변수명을 선언 해 놓고, 원하는 데이터를 내부에서 추출하자&amp;quot; 가 가능하다.&lt;/p&gt;
&lt;p&gt;이는 초기 단계에서는 매우 간편하나, 데이터가 복잡해질 경우&lt;/p&gt;
&lt;p&gt;스파게티 코드가 될 확률이 매우매우 높다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이런 JavaScript 의 단점을 해소하기 위해, 타입 선언적 언어로 만들어 주는 것이&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TypeScript&lt;/strong&gt; 이다. 물론, 그 자체로 &amp;quot;언어&amp;quot;라기 보다는,&lt;/p&gt;
&lt;p&gt;JavaScript 의 SuperSet 이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TypeScript&lt;/strong&gt; 는 결국 JavaScript 로 컴파일 되어야 한다.&lt;/p&gt;
&lt;p&gt;그럼에도 불구하고, &lt;strong&gt;TypeScript&lt;/strong&gt; 는 강력한 타입 체계를 가지고 있으며,&lt;/p&gt;
&lt;p&gt;타 언어보다 타입 선언에 대해 자유롭다는 장점이 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;React 와 TypeScript 는 어떤 조합을 이룰까?&lt;/p&gt;
&lt;p&gt;React 는 사용자의 상호작용과 데이터 변화에 매우 민감하다.&lt;/p&gt;
&lt;p&gt;어떤 이벤트가 발생하면, React 내부의 Virtual DOM 에서 이를 받아들여 변화시키기 때문이다.&lt;/p&gt;
&lt;p&gt;React 는 특정 컴포넌트가 상위 컴포넌트로부터 받는 &lt;code&gt;props&lt;/code&gt; 라는 객체와,&lt;/p&gt;
&lt;p&gt;자신의 하위 컴포넌트에게 내려주는 &lt;code&gt;props&lt;/code&gt; 객체가 있다.&lt;/p&gt;
&lt;p&gt;이 데이터들은 일관성을 지킬 필요가 있는데,&lt;/p&gt;
&lt;p&gt;컴포넌트들은 개발자의 의도와 방향에 따라 &lt;code&gt;props&lt;/code&gt; 의 데이터에 의해&lt;/p&gt;
&lt;p&gt;다양한 방식으로 표현 될 수 있기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;뿐만 아니라, 전역 데이터 객체, 지역 데이터 객체를 정해 줄 때&lt;/p&gt;
&lt;p&gt;타입 시스템은 매우매우 중요하다.&lt;/p&gt;
&lt;p&gt;개발 과정에서 개발자의 가독성은 물론, 데이터를 정하여 규칙을 세울 수 있기 때문이다.&lt;/p&gt;
&lt;p&gt;만약에 TypeScript 가 존재하지 않았다면, 일관성 있는 웹 어플리케이션 개발은 훨씬 어려워 질 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;뿐만 아니라, React 프로젝트 내부에 따로 TypeScript 인터페이스 파일을 지정하여&lt;/p&gt;
&lt;p&gt;React 에서 사용될 모든 객체에 대해서 정확한 형식을 명명할 수 있으며,&lt;/p&gt;
&lt;p&gt;이를 다시 활용하여 복잡한 객체의 타입 시스템을 만들 수 있다.&lt;/p&gt;
&lt;p&gt;타입 시스템 덕분에, 리액트 테스팅 과정에서&lt;/p&gt;
&lt;p&gt;원치 않는 데이터를 걸러내거나, 경고 화면을 보아 사전에 버그를 차단할 수 있다.&lt;/p&gt;
&lt;p&gt;물론, 이 모든 데이터들은 프로덕션 과정에서 JS 로 파싱된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;사실, 애초에 TypeScript 는 모두 JS 로 파싱되어 실행된다. &lt;br/&gt;&lt;br&gt;단순히 개발 편의성을 위해 결과 파일만 내놓지 않을 뿐이다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;br/&gt;

&lt;h4&gt;React 와 Style Library&lt;/h4&gt;
&lt;p&gt;리액트는 컴포넌트 스타일링에 대해 다양한 방법론을 제시한다.&lt;/p&gt;
&lt;p&gt;그리고, 이 스타일링 기법들은 React 컴포넌트 형식에 대해 안성맞춤 급으로 편리하다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;하지만, 스타일링 기법은 주로 2 가지로 나눌 수 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;css 결과물로 나옴&lt;/li&gt;
&lt;li&gt;css 결과물 없이 js 로 같이 컴파일&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;p&gt;그렇다면, &lt;code&gt;css&lt;/code&gt; 로 결과물이 나오는 방법은 무엇일까?&lt;/p&gt;
&lt;p&gt;일단 생각나는 대로 적자면,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sass&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;scss&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;css&lt;/code&gt; - 주로 전역 스타일링으로 사용됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;위에서 적힌 &lt;code&gt;sass&lt;/code&gt;, &lt;code&gt;scss&lt;/code&gt; 와 같은 확장자 파일은,&lt;/p&gt;
&lt;p&gt;css 문법에서 확장된 버전이다.&lt;/p&gt;
&lt;p&gt;특정 컴포넌트에만 사용하기 위해서는 &lt;code&gt;jsx&lt;/code&gt; or &lt;code&gt;tsx&lt;/code&gt; 파일에서 &lt;code&gt;import&lt;/code&gt; 로 가져와야 한다.&lt;/p&gt;
&lt;p&gt;이유는, 바로 Webpack 이 각 파일을 순회하며 의존성을 파악해야 하기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;두 번째로, &lt;code&gt;js&lt;/code&gt; 로 스타일링 결과가 나오는 방식이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tailwind&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Banilla Extract&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Styled-Component&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;-Tailwind-&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;테일윈드는 DOM 자체의 &lt;code&gt;class&lt;/code&gt; 속성을 이용하여 스타일링 한다.&lt;/p&gt;
&lt;p&gt;하지만, 클래스 이름이 너무 복잡해 진다는 단점이 존재한다.&lt;/p&gt;
&lt;p&gt;그리고, tailwind 만의 문법을 따로 배워야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;-Banilla Extract-&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;바닐라 익스트랙트는 팀 프로젝트에서 공통된 css 를 사용하기 위해 좋은 선택이라고 생각한다.&lt;/p&gt;
&lt;p&gt;사용할 css 객체 파일을 생성하여 따로 작성하며,&lt;/p&gt;
&lt;p&gt;이 객체는 내부에서 각 DOM 의 태그, 혹은 클래스에 대한 스타일링을 먼저 선언 해 놓는다.&lt;/p&gt;
&lt;p&gt;이 복잡한 객체는 &lt;code&gt;props&lt;/code&gt; 로 하위 태그들에 전해지고,&lt;/p&gt;
&lt;p&gt;개발자는 css 객체 &lt;code&gt;props&lt;/code&gt; 에서 원하는 스타일링을 꺼내서 컴포넌튿에 적용 할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;-Styled-Component-&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;특정 컴포넌트, 혹은 태그 컴포넌트 등등에 사용할 수 있는&lt;/p&gt;
&lt;p&gt;css 적용 객체를 선언하는 것이다.&lt;/p&gt;
&lt;p&gt;주로 템플릿 문자열 &lt;code&gt;``&lt;/code&gt; 을 사용하며,&lt;/p&gt;
&lt;p&gt;Styled-Component 로 제작한 객체는 그 자체로 컴포넌트가 되어서,&lt;/p&gt;
&lt;p&gt;이를 렌더링 할 수 있다.&lt;/p&gt;
&lt;p&gt;즉, 커스텀 컴포넌트를 생성하여 만드는 것과 큰 차이는 없다.&lt;/p&gt;
&lt;p&gt;그러나, 항상 정적으로 적용되는 css 파일에서&lt;/p&gt;
&lt;p&gt;현재 JavaScript or TypeScript 의 데이터 상황에 따라 변경할 수 있다는 것이&lt;/p&gt;
&lt;p&gt;매우 큰 장점이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;React 가 방법론의 조합이라는 것이 나쁜 것인가?&lt;/h2&gt;
&lt;p&gt;그건 절대로 아니다.&lt;/p&gt;
&lt;p&gt;React 는 정말 수많은 방법론을 스스로 자유롭게 결합시키면서,&lt;/p&gt;
&lt;p&gt;독보적인 웹 개발 라이브러리로서 일어섰다.&lt;/p&gt;
&lt;p&gt;특히, 스스로를 &amp;quot;프레임워크&amp;quot; 가 아닌, &amp;quot;라이브러리&amp;quot; 라고 지칭하는 데에&lt;/p&gt;
&lt;p&gt;매우 깊은 의미가 있음을 알 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;-내가 말하고자 하는 것은,-&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;React 에 어떤 것이 적용되었는지를 알아야, React 중~고급 수준으로 갈 수 있다는 것이다.&lt;/p&gt;
&lt;p&gt;처음에 &lt;code&gt;useState&lt;/code&gt; 를 통한 컴포넌트의 re-렌더링을 본다면, 도파민이 나올 수도 있다.&lt;/p&gt;
&lt;p&gt;그러나, 나중에 Context 와 전역 데이터 객체, &lt;code&gt;useEffect&lt;/code&gt; 와 생명주기를 같이 다룬다면,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;React 가 변수로 함수를 왜 주는지,&lt;/li&gt;
&lt;li&gt;React 는 내가 원하는 페이지를 왜 렌더링 하지 않았는지,&lt;/li&gt;
&lt;li&gt;왜 두 번의 Dispatch 이벤트가 적용되지 않고, 한 번만 적용되었는지&lt;/li&gt;
&lt;li&gt;등등 수많은 현상들..&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이러한 현상들을 이해 할 수 없으며, 디버깅하기 매우 어려울 것이다.&lt;/p&gt;
&lt;p&gt;특히나, 현재로서는 AI 가 매우 발달 했기 때문에,(현재 o3)&lt;/p&gt;
&lt;p&gt;이러한 웹 개발은 AI 가 다 해줘서 괜찮다고 생각 할 수도 있다.&lt;/p&gt;
&lt;p&gt;그러나 한번 생각 해 보자.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;AI 가 생성한 React 코드를 스스로 이해 할 수 있는가?&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;만약에 AI 한테 특정 데이터셋에 대한 컴포넌트 계층과 코드를 만들어 달라고 부탁했다고 가정 해 보자.&lt;/p&gt;
&lt;p&gt;여러 번의 질문을 통해, 10 개의 복잡하며 깔끔한 파일이 탄생했고,&lt;/p&gt;
&lt;p&gt;이제 이 10 개의 컴포넌트를 사용하는 포용적인 컴포넌트를 사용한다고 가정 해 보자.&lt;/p&gt;
&lt;p&gt;그렇다면, AI 에게 어떤 질문을 해야 할까?&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그렇다. AI 에게 올바른 질문을 하기 위해서는, 나 또한 AI 와 같이 융화 될 정도로&lt;/p&gt;
&lt;p&gt;사용된 기술에 대한 이해도를 갖춰야 하는 것이다.&lt;/p&gt;
&lt;p&gt;이해도를 결국 갖추지 못한다면, AI 에 끌려 다니거나,&lt;/p&gt;
&lt;p&gt;결국 AI 가 만든 깔끔한 코드들은 대부분 &amp;quot;기술 부채&amp;quot; 가 되어 나에게 화살로 날아올 것이다.&lt;/p&gt;
&lt;p&gt;이것은, 프로젝트를 처음 만드는 것 보다 못하다고 개인적으로 생각된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;따라서, &amp;quot;방법론&amp;quot; 이라는 키워드에 집중하는 것은,&lt;/p&gt;
&lt;p&gt;React 에 적용된 수많은 편리성 라이브러리들의 결합과 사용을 이해하지 못한다면,&lt;/p&gt;
&lt;p&gt;결국 프로덕션 수준의 웹 어플리케이션은 제작하기 매우 어려울 것이란 것이 내 판단이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;그렇다면 어떻게 React 공부를 해야 할까?&lt;/h3&gt;
&lt;p&gt;나 스스로도 매우 고민했던 부분이기도 하다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;우리는 개발자, 혹은 개발 엔지니어로서, React 를 배운다.&lt;/p&gt;
&lt;p&gt;그래서 우리는 React 를 왜 배우는가?&lt;/p&gt;
&lt;p&gt;다양한 목적이 있을 것이다. 그러나, 내가 대부분 맞을 거라고 생각하는 의견은,&lt;/p&gt;
&lt;p&gt;React 를 &amp;quot;실제 사용자가 이용할 수 있을 정도의 프로덕션 웹을 만드는 것&amp;quot; 이라고 생각한다.&lt;/p&gt;
&lt;p&gt;어쨌든 웹 어플리케이션을 제작한다는 것은, 혼자만의 만족을 위해서라기 보다는,&lt;/p&gt;
&lt;p&gt;사용자의 편의성과 사용에 초점이 훨씬 더 맞춰져 있다고 생각한다.&lt;/p&gt;
&lt;p&gt;따라서, &lt;strong&gt;&amp;quot;프로덕션 급으로 제작하기 위한 실력을 갖춰야 한다&amp;quot;&lt;/strong&gt; 가 내 의견이다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;HTML 구조 이해&lt;/li&gt;
&lt;li&gt;DOM 객체와 JavaScript 조작 과정을 이해&lt;/li&gt;
&lt;li&gt;CSS 파일에 대한 이해&lt;/li&gt;
&lt;li&gt;JavaScript 의 기본 메서드에 대한 이해&lt;/li&gt;
&lt;li&gt;JSON 객체 사용법의 이해&lt;/li&gt;
&lt;li&gt;JavaScript 와 이벤트 등록 이해 - AI 한테 물어보기를 추천&lt;/li&gt;
&lt;li&gt;클라이언트 사이드에서 데이터 fetch 하는 방법 - EX - &lt;code&gt;axios&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;이번 글을 작성하며 느낀 것&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;나는 이전에 React 로 웹 공부를 시작했었고, 단순 React 지식만으로는 분명한 한계를 느꼈다.&lt;/p&gt;
&lt;p&gt;따라서, JavaScript 를 공부했으며, 프로젝트에 NestJS 를 공부하기도 하며,&lt;/p&gt;
&lt;p&gt;DOM 의 공식문서를 이용한 간단한 Component 시스템 제작,&lt;/p&gt;
&lt;p&gt;&amp;quot;웹 서버란 무엇인가?&amp;quot; 에 대한 블로그 글을 작성하며 조사,&lt;/p&gt;
&lt;p&gt;그리고 HTML5 정식 스펙에 존재하는 &amp;quot;모든 태그&amp;quot; 150개 정도? 를 조사하여 작성했다.&lt;/p&gt;
&lt;p&gt;뿐만 아니라, Webpack 의 작동 원리와 의존성 관계 파악 이해,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;package.json&lt;/code&gt;, &lt;code&gt;tsconfig.json&lt;/code&gt; 의 역할과 옵션을 명시하여 설명하는 블로그 글도 제작했다.&lt;/p&gt;
&lt;p&gt;그리고, &lt;code&gt;package.json&lt;/code&gt; 과 npm 모듈에 내장되어 있는 CLI 옵션을 사용하는 법에 대해서도 공부했다.&lt;/p&gt;
&lt;p&gt;음... 이렇게 보면 웹 개발이라는 목적에 비해 과하게 공부하고 조사 한 감이 있긴 하지만,&lt;/p&gt;
&lt;p&gt;나는 이전에 느꼈던 한계를 빠르게 느끼기 싫다.&lt;/p&gt;
&lt;p&gt;나는 마치 로그라이크 게임처럼, 태초마을로 돌아와서 공부하고 있지만,&lt;/p&gt;
&lt;p&gt;이번에는 아주 멀리멀리 날아 갈 생각이다.&lt;/p&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;h2&gt;참고 사이트&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;최신 리액트 공식 사이트&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.react.dev/&quot;&gt;https://ko.react.dev/&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;이전 리액트 문서 (시작하기)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.legacy.reactjs.org/docs/getting-started.html&quot;&gt;https://ko.legacy.reactjs.org/docs/getting-started.html&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;이전 리액트 문서 (웹사이트에 React 추가)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.legacy.reactjs.org/docs/add-react-to-a-website.html&quot;&gt;https://ko.legacy.reactjs.org/docs/add-react-to-a-website.html&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;이전 리액트 문서 (JSX 이해하기)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.legacy.reactjs.org/docs/jsx-in-depth.html&quot;&gt;https://ko.legacy.reactjs.org/docs/jsx-in-depth.html&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;이전 리액트 문서 (Hook 소개)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.legacy.reactjs.org/docs/hooks-intro.html&quot;&gt;https://ko.legacy.reactjs.org/docs/hooks-intro.html&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;</description>
      <category>Web-Server/React</category>
      <category>react</category>
      <category>react 는 무엇으로 이루어졌는가</category>
      <category>react 란</category>
      <category>리액트</category>
      <category>리액트란</category>
      <category>방법론</category>
      <category>회고</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/225</guid>
      <comments>https://codecreature.tistory.com/225#entry225comment</comments>
      <pubDate>Tue, 10 Jun 2025 04:15:56 +0900</pubDate>
    </item>
    <item>
      <title>C 언어 - stdio.h 만 가지고 백준 문제 풀어본 결과</title>
      <link>https://codecreature.tistory.com/224</link>
      <description>&lt;h2&gt;제목 : stdio.h 만 가지고 백준 문제 풀어본 결과&lt;/h2&gt;
&lt;hr&gt;
&lt;h2&gt;코드 결과를 빠르게 보고 싶다면, 밑의 텍스트를 클릭하세요&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;#code-exam&quot;&gt;코드로 바로 이동하기&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;다양한 라이브러리가 존재하는데, 굳이 힘들게 푸는 이유는?&lt;/h3&gt;
&lt;p&gt;이러한 힘든 도전을 하는 이유는, &lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/220&quot;&gt;&amp;quot;C 와 최소한의 Lib 로 알고리즘 풀어보기 도전&amp;quot; 썼던 글&lt;/a&gt; 에 상세히 적어놓았다.&lt;/p&gt;
&lt;p&gt;요약하자면, Node.js 엔진 기반의 JS 의 성능적 한계점을 느끼고, 최적화를 수행하고 구현하면서,&lt;/p&gt;
&lt;p&gt;최적화 과정이 저 수준의 언어로 제작하는 것 보다 훨씬 더 어렵다는 것을 느꼈다.&lt;/p&gt;
&lt;p&gt;이럴 거면 애초에 최적화 타겟 언어로 프로그램을 제작하는 것이 더 낫다는 판단을 했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그리고, 특히 특정 도메인의 프로그램을 제작하기 위해 다양한 라이브러리의 역할도 알지 못한 채,&lt;/p&gt;
&lt;p&gt;쉽게 가져와서 사용하기만 해버리는 단순한 방법론적 프로그래밍에 회의감이 들었다.&lt;/p&gt;
&lt;p&gt;특히, 특정 프레임워크를 선택하고 공부 할 때,&lt;/p&gt;
&lt;p&gt;이것을 구현하기 위해, 저 메서드, 클래스를 간단히 가져와서 쓴다, 라는 관점으로 볼 때는&lt;/p&gt;
&lt;p&gt;빠른 구현을 위해 프레임워크를 도입하는 것이 좋다고 생각한다.&lt;/p&gt;
&lt;p&gt;그러나, 프레임워크 자체가 하나의 또다른 언어 수준으로 공부가 필요 해 지는 시기가 온다.&lt;/p&gt;
&lt;p&gt;그 이유는, 프레임워크에서 가져온 어려운 방법론이 어떻게 작동하는지 모르고, 이를 외우기 때문이라고 생각한다.&lt;/p&gt;
&lt;p&gt;처음에는 나도 공식문서를 따라서 구현하며 만든 컴포넌트, 혹은 WAS(백엔드) 프로그램이 간단히 제작되는 것을 보고 좋아했는데,&lt;/p&gt;
&lt;p&gt;구조적으로 개편하거나, 최적화를 진행하는 순간, 나는 알려주는 것 말고 아무것도 아는 것이 없었다는 것을 알게 된다.&lt;/p&gt;
&lt;p&gt;이러한 회의감은 React, NestJS, Express, TypeORM, Spring, Next.js 등등 수많은 공식문서를 배우며 느끼게 되었다.&lt;/p&gt;
&lt;p&gt;결국 나의 학습 방법이 틀렸다는 것을 인지하고, 모르는 것이 생겼을 때, 이 의문이 미래의 학습에 얼마나 도움이 되는지를 생각한다.&lt;/p&gt;
&lt;p&gt;예를 들어, 현재 내가 가상화 컨테이너에 대해 알고 싶어 조사하며 글을 작성하고 싶다고 한다면,&lt;/p&gt;
&lt;p&gt;이는 작성할 수 있다. 실질적인 온라인 배포 및 클라이언트 유치를 위해 꼭 필요한 과정이기 때문이다.&lt;/p&gt;
&lt;p&gt;그러나, K8S(Kubernetes) 가 뭔지 궁금하다고 지금 당장 공부하지 않을 것이다.&lt;/p&gt;
&lt;p&gt;첫 번째로 지금 공부하고 있는 도메인과 조금 떨어져 있을 뿐 더러,&lt;/p&gt;
&lt;p&gt;아직 컨테이너 오케스트레이션과, 도커, 가상화 기술에 대해 제대로 알고 있지 않기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이러한 성찰을 바탕으로, &amp;quot;나는 어떻게 알고리즘을 공부해야 하는가?&amp;quot; 에 대해 혼란스러워 했다.&lt;/p&gt;
&lt;p&gt;언뜻 나는 백엔드에 가까우니 Spring 을 할 것 같으니, 알고리즘을 Java 로 공부했다.&lt;/p&gt;
&lt;p&gt;그러나, 지금 나의 행보는 Spring 에 가깝지 않으며, 오히려 웹 서버 개발과,&lt;/p&gt;
&lt;p&gt;그에 반대되는 저 수준의 언어를 이용하여 직접 메모리 관리하는 것에 좀 더 가까웠다.&lt;/p&gt;
&lt;p&gt;따라서, 역사가 매우 깊으며, 대부분의 언어가 형식을 참조하거나, 이를 아직도 활용중인&lt;/p&gt;
&lt;p&gt;C 언어를 사용하여 알고리즘을 풀기로 했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이는 여러 전략이 있는데,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;웹 개발 공부를 진행하며, 웹 모듈(&lt;code&gt;.wasm&lt;/code&gt;) 으로 최적화 전략을 생각할 수 있음&lt;/li&gt;
&lt;li&gt;필요 할 시, 범용성 높은 &lt;code&gt;c++&lt;/code&gt; 로 전환이 가능함&lt;/li&gt;
&lt;li&gt;메모리 직접 관리로 GC 의 중요성 깨닫기&lt;/li&gt;
&lt;li&gt;클래스가 존재하지 않으므로, &lt;code&gt;struct&lt;/code&gt; (구조) 를 이용한 추상화 및 구현 전략 채택하여 &lt;code&gt;Golang&lt;/code&gt; 고려&lt;/li&gt;
&lt;li&gt;현재 사용중인 에디터가 &lt;strong&gt;Zed&lt;/strong&gt; 라는 Rust 로 제작된 에디터인데, 부족한 마크다운 프리뷰를 고치고 싶음&lt;ul&gt;
&lt;li&gt;Rust 의 메모리 빌림을 더 정확하게 이해하기 위해서 메모리 관리를 직접 해 보는 것이 중요하다 생각(개인적)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;포인터에 대한 직감 발달을 위함&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;h2&gt;Java 입출력 전략을 C 로 가져오기&lt;/h2&gt;
&lt;h3&gt;입력 문자열에 대한 토큰화 진행하기&lt;/h3&gt;
&lt;p&gt;Java 에는 입력 문자열에 대해 토큰화를 수행하는 &lt;code&gt;StringTokenizer&lt;/code&gt; 라는 클래스가 존재한다.&lt;/p&gt;
&lt;p&gt;생성자와 함께 인수로 &lt;strong&gt;토큰화를 수행 할 문자열&lt;/strong&gt; 을 넣어주면,&lt;/p&gt;
&lt;p&gt;생성된 객체를 통해 토큰을 하나씩 꺼내 사용 할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, C 에서는 &lt;code&gt;scanf(&amp;quot;%d&amp;quot;, &amp;amp;받을 숫자)&lt;/code&gt; 와 같은 메서드를 통해 하나씩 꺼내 사용하고 있었다.&lt;/p&gt;
&lt;p&gt;나중에 입력의 토큰 수가 동적으로 변할 문제들을 고려하여, &lt;code&gt;tokenizer&lt;/code&gt; 라는 나만의 메서드를 만들었다.&lt;/p&gt;
&lt;p&gt;입력된 문자 배열 &lt;code&gt;char*&lt;/code&gt; 를 &lt;code&gt;toeknizer&lt;/code&gt; 에 보내주면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;char**&lt;/code&gt; 형태로 반환되는데, 이는 &lt;strong&gt;&amp;quot;각 문자 배열의 시작 주소를 가진&amp;quot;&lt;/strong&gt; , &lt;strong&gt;&amp;quot;주소 배열&amp;quot;&lt;/strong&gt; 이다.&lt;/p&gt;
&lt;p&gt;마지막 주소에는 NULL 이 들어가 있기 때문에, 확실하게 끝을 인지할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;숫자 문자열을 정수로 변환하기&lt;/h3&gt;
&lt;p&gt;보통 &lt;code&gt;scanf(&amp;quot;%d&amp;quot;, ...)&lt;/code&gt; 를 통해서 이미 정수 변수로 할당 받으면 상관하지 않아도 될 부분이기도 하다.&lt;/p&gt;
&lt;p&gt;그러나, 나는 이 부분도 &lt;code&gt;parseInt&lt;/code&gt; 라는 메서드로 제작했다.&lt;/p&gt;
&lt;p&gt;먼저, 간단하게 수 &lt;code&gt;0&lt;/code&gt; 을 가진 변수를 만들어 놓고,&lt;/p&gt;
&lt;p&gt;숫자 문자 배열의 마지막이 올 때 까지, 변수를 x10 하고, 현재 숫자 문자를 실제 수로 변환하여 더한다.&lt;/p&gt;
&lt;p&gt;이에 대한 예시는 맨 밑의 코드를 참조하길 바란다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;숫자 문자를 수로 변환하기&lt;/h3&gt;
&lt;p&gt;이건 매우 쉬운데, 먼저 알아야 할 것은, 문자 배열의 순서는 &lt;code&gt;&amp;#39;0&amp;#39;&lt;/code&gt; --&amp;gt; &lt;code&gt;&amp;#39;9&amp;#39;&lt;/code&gt; 순으로 간다는 것이다.&lt;/p&gt;
&lt;p&gt;따라서, &lt;code&gt;char - &amp;#39;0&amp;#39;&lt;/code&gt; 으로 계산하면, 바로 현재 수 문자의 정수가 추출된다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;빈 공간을 감지하기&lt;/h3&gt;
&lt;p&gt;위에서 &lt;code&gt;tokenizer&lt;/code&gt; 라는 토큰 문자열 주소 배열을 반환하기 위해서 사용되는 기능인데,&lt;/p&gt;
&lt;p&gt;빈 공간을 감지한다면, 이는 넘거거나, 이제 토큰화를 시작해야 한다는 의미이다.&lt;/p&gt;
&lt;p&gt;이 때, 물론 C 라이브러리로 해결 할 수 있지만, 나는 조금 간단히 생각했다.&lt;/p&gt;
&lt;p&gt;char 값으로 &lt;code&gt;32&lt;/code&gt; = &lt;code&gt;&amp;#39; &amp;#39;&lt;/code&gt;(스페이스) 이며, &lt;code&gt;9&lt;/code&gt; ~ &lt;code&gt;13&lt;/code&gt; 까지가 줄넘김, 탭 등등을 의미한다.&lt;/p&gt;
&lt;p&gt;이러한 값을 인지하는 &lt;code&gt;isBlank&lt;/code&gt; 함수를 만들었다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Java 에서 C 를 사용하니 느껴지는 제약들&lt;/h2&gt;
&lt;h3&gt;메모리를 직접 관리해야 한다.&lt;/h3&gt;
&lt;p&gt;Java 에서는, JVM 에서 Garbage Collector(쓰레기 수집기) 와 상호작용하면서,&lt;/p&gt;
&lt;p&gt;곧 폐기처분 될 확률이 높은 Eden 영역, 오래 살아남는 메모리 영역 Old 영역을 분리하여 관리한다.&lt;/p&gt;
&lt;p&gt;그러나, C 에서는 그런거 없다. &lt;code&gt;calloc&lt;/code&gt;, &lt;code&gt;malloc&lt;/code&gt;, 혹은 &lt;code&gt;realloc&lt;/code&gt; 으로 동적 메모리 확장한&lt;/p&gt;
&lt;p&gt;모든 메모리들은 &lt;code&gt;stdlib.c&lt;/code&gt; 헤더의 &lt;code&gt;free&lt;/code&gt; 메서드를 이용하여 직접 메모리 해제를 해 주어야 한다.&lt;/p&gt;
&lt;p&gt;직접 문제를 풀기 위해 다양한 기능을 만들면서 느꼈던 것인데, 요즘 왜 &lt;code&gt;Zig&lt;/code&gt; 나 &lt;code&gt;Rust&lt;/code&gt; 가 뜨는지 이해가 되었다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;둘 다 메모리 안정성을 기반에 두고 만든 저 수준의 언어&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;br/&gt;

&lt;h3&gt;클래스가 없다.&lt;/h3&gt;
&lt;p&gt;내가 이전에 Java 로 백준을 풀 때는, 특정 도메인의 유틸 기능들을 모아놓은 클래스를 직접 제작하여 문제를 해결했다.&lt;/p&gt;
&lt;p&gt;HashMap, HashSet, Tree, Queue, Stack 등등을 직접 구현하며 자료구조에 대한 이해도를 높였다.&lt;/p&gt;
&lt;p&gt;그러나, C 에서는 C++ 과 달리, 클래스가 존재하지 않는다. 대신, &lt;code&gt;struct&lt;/code&gt; 가 존재한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;예를 들어, 우리가 특정 클래스를 만든다면, 클래스가 가질 고유의 &amp;quot;변수&amp;quot; 와 &amp;quot;객체&amp;quot; 를 선언하고,&lt;/p&gt;
&lt;p&gt;이 클래스를 위해 사용될 메서드를 내부에서 직접 작성했다.&lt;/p&gt;
&lt;p&gt;그러나, &lt;code&gt;struct&lt;/code&gt; 는, 구조 가 가질 고유의 &amp;quot;변수&amp;quot; 와 &amp;quot;객체&amp;quot; 까지는 선언이 가능하나,&lt;/p&gt;
&lt;p&gt;생성자와 소멸자를 외부에서 만들어야 하며, 이 구조(struct) 는 메서드를 추상화로 선언한다.&lt;/p&gt;
&lt;p&gt;그리고, 외부에서 추상화 된 메서드를 직접 구현하는 형식이다.&lt;/p&gt;
&lt;p&gt;이에 대해 굉장히 잘 작성 해 놓은 분이 계셔서, 글을 공유한다.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://blog.naver.com/ruvendix/220980152324&quot;&gt;https://blog.naver.com/ruvendix/220980152324&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;포인터와 메모리 주소에 대한 개념을 숙지하고 있어야 한다.&lt;/h3&gt;
&lt;p&gt;Java 에서는 배열과 객체에 대한 정보를 JVM 이 관리하여,&lt;/p&gt;
&lt;p&gt;우리가 직접 시스템 수준의 메모리를 건드리지 않고, JVM 이 생성 및 소멸시켜 관리했다.&lt;/p&gt;
&lt;p&gt;이 과정에서, JVM 은 객체와 배열의 정보를 내부에 메타데이터로 저장했다.&lt;/p&gt;
&lt;p&gt;역시나, C 는 그런것이 없다.&lt;/p&gt;
&lt;p&gt;따라서, 우리는 객체를 생성하거나, 배열을 생성 할 때, 이 객체 혹은 배열에 대한 사이즈를 명확히 작성해야 한다.&lt;/p&gt;
&lt;p&gt;그런데 이건, 쉽게 &lt;code&gt;sizeof(&amp;lt;원시데이터&amp;gt;)&lt;/code&gt;, &lt;code&gt;sizeof(&amp;lt;struct 이름&amp;gt;)&lt;/code&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;주소의 끝을 인식할 다양한 방법을 알고 있어야 한다.&lt;/h3&gt;
&lt;p&gt;내가 백준 문제를 풀면서, 문자 배열을 순회할 때, &lt;code&gt;for&lt;/code&gt; 을 거의 사용하지 않고, 대부분 &lt;code&gt;while&lt;/code&gt; 로 작성했다.&lt;/p&gt;
&lt;p&gt;이는 문자 (&lt;code&gt;&amp;#39;\0&amp;#39;&lt;/code&gt;) 가 문자 배열의 &amp;quot;끝&amp;quot; 을 알리기 때문이다.&lt;/p&gt;
&lt;p&gt;그리고, 객체 혹은 배열의 시작 주소를 가진 포인터 배열은 &lt;code&gt;NULL&lt;/code&gt; 로 표현이 가능하다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, 만약에 수 배열이 온다면, 이야기가 달라진다.&lt;/p&gt;
&lt;p&gt;숫자 배열은 END 값을 &lt;code&gt;0&lt;/code&gt; 으로 가진다. 따라서, 우리는 길이에 대한 메타데이터를 직접 생성하여 관리해야 한다.&lt;/p&gt;
&lt;p&gt;이 경우, &lt;code&gt;for&lt;/code&gt; 문을 사용하여 해결했다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;모든 함수와 객체, 변수에 대한 정보는 추상화 시켜 미리 작성해야 한다.&lt;/h3&gt;
&lt;p&gt;이것도 C, C++ 이 서로 다른 경향인데,&lt;/p&gt;
&lt;p&gt;C 는 위쪽에서 작성된 코드가 아래의 구조 혹은 메서드를 호출 할 때, 무엇인지 알지 못한다.&lt;/p&gt;
&lt;p&gt;이는 컴파일러가 위에서부터 아래로 읽기 때문이라서, 파일의 맨 위 쪽에 미리 추상화를 시켜놓아야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이러한 제약은 생각 외로, 이 문제를 풀기 위해 어떤 기능이 필요할지 세밀하게 생각하게 해 주는 계기가 되었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;span id=&quot;code-exam&quot;&gt;C 로 푼 문제 :&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.acmicpc.net/problem/1008&quot;&gt;https://www.acmicpc.net/problem/1008&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;결과 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;/**
시간 제한    메모리 제한    제출    정답    맞힌 사람    정답 비율
2 초    128 MB    872353    296109    244527    34.468%
문제
두 정수 A와 B를 입력받은 다음, A/B를 출력하는 프로그램을 작성하시오.

입력
첫째 줄에 A와 B가 주어진다. (0 &amp;lt; A, B &amp;lt; 10)

출력
첫째 줄에 A/B를 출력한다. 실제 정답과 출력값의 절대오차 또는 상대오차가 10-9 이하이면 정답이다.

예제 입력 1
1 3
예제 출력 1
0.33333333333333333333333333333333
10-9 이하의 오차를 허용한다는 말은 꼭 소수 9번째 자리까지만 출력하라는 뜻이 아니다.

예제 입력 2
4 5
예제 출력 2
0.8
 */

#include&amp;lt;stdio.h&amp;gt;

extern void* malloc(size_t);
extern void free(void*);
extern void* realloc(void*, size_t);

int parseInt(const char* str); // 주어진 문자열을 정수로 만든다. - 입력 파싱
char* doubleToString(double target); // 결과로 출력해야 하는 double 타입을 문자열로 만든다.

char** tokenizer(const char* str); // 입력으로 주어진 한 줄 입력을 토큰으로 만들어 주소값 배열을 반환한다.
int chToInt(char ch); // 단순한 숫자 문자 하나를 정수로 바꿔 전달한다.
_Bool isBlank(char ch); // 현재 문자가 빈 칸에 해당하는 문자인지 알려준다.


int main () {
    char chList[255];

    char* str = fgets(chList, sizeof(chList), stdin);

    char** tokens = tokenizer(str);
    char** tokensPtr = (char**)tokens;

    double baseNum = (double)parseInt(*tokensPtr);
    tokensPtr++;

    while(*tokensPtr) {
        baseNum /= (double)parseInt(*tokensPtr);
        free(*tokensPtr);
        tokensPtr++;
    }
    free(*tokens);

    char* result = doubleToString(baseNum);

    fputs(result, stdout);

    // 결과 출력으로 사용한 문자열 풀어주기
    free(result);

    // free 작업
}

int parseInt(const char* str) {
    int result = 0;
    short sign = 1;

    char* strPtr = (char*)str;

    // 음수 문자가 올 경우, 이를 따로 처리 및 저장.
    if(*strPtr == &amp;#39;-&amp;#39;) {
        sign = -1;
        strPtr++;
    }

    // 숫자 문자 앞에서부터 파싱하여 넣는다.
    while(*strPtr) {
        // 이미 계산된 수는 이번 수를 넣기 위해 공간을 창출한다.
        result *= 10;

        int currNum = chToInt(*strPtr);

        // 빈 공간에 수 넣기
        result += currNum;

        // 다음 문자로 이동
        strPtr++;
    }

    return result * (int)sign;
}



char* doubleToString(double target) {
    int sign = 1;

    if(target &amp;lt; 0) {
        sign = -1; target *= -1;
    }

    // 소수부를 무시한 값으로 정수 값을 추출
    int intPart = (int)target;

    // 정수부를 다시 소수부로 만들어서 정수만 빼고, 소수부만 남기기
    double doublePart = target - (double)intPart;


    // 정수 문자가 들어갈 사이즈
    int intSize = 0;
    // 정수 길이를 구하기 위한 임시 정수
    int tempInt = intPart;

    // 정수 길이를 구하기
    while(tempInt != 0) {
        tempInt /= 10;
        intSize++;
    }

    char* intStr = (char*)malloc(sizeof(char) * intSize + 1);
    intStr[intSize] = &amp;#39;\0&amp;#39;;

    // 생성된 문자 배열의 마지막부터 시작하여 수를 넣는다.
    for(int i = intSize - 1; i &amp;gt;= 0; i--) {
        // 항상 첫 번째 자리수를 추출한다.
        int number = intPart % 10;
        // 추출한 자리수의 문자는 &amp;#39;0&amp;#39; 을 더해주면 된다.
        intStr[i] = (char)(number + &amp;#39;0&amp;#39;);
        // 그리고, 모든 자리수를 하나씩 이동한다.
        intPart /= 10;
    }

    // 문제에서는 9 자리까지 맞으면 된다고 하니, 10 자리까지 정확도를 올림.
    const int MAX_SIZE = 10;

    char* doubleStr = (char*)malloc(sizeof(char) * MAX_SIZE + 1);
    doubleStr[MAX_SIZE] = &amp;#39;\0&amp;#39;;


    int currDeep = 0;

    // 10 개를 넣을 예정.
    while(currDeep &amp;lt; MAX_SIZE) {
        // 지속적으로 각 소수부를 위로 한 칸씩 올림
        doublePart *= 10;

        // 정수가 된 소수부를 추출
        int extractNum = (int)doublePart;

        doublePart -= extractNum;

        // 이를 가지고 문자열로 형성
        doubleStr[currDeep++] = (char)(extractNum + &amp;#39;0&amp;#39;);
    }

    // 정수 문자열, 소수 문자열이 전부 완료 된 상황.

    // 만약에, 정수가 애초에 0 이라서 제대로 할당이 되지 못했을 경우 (&amp;quot;.33333&amp;quot; 으로 나옴)
    if(intSize == 0) {
        // 0 을 위한 공간으로 재할당
        intStr = (char*)realloc(intStr, 2);
        intStr[0] = &amp;#39;0&amp;#39;;
        intStr[1] = &amp;#39;\0&amp;#39;;
        // 0 이 들어갔으므로.
        intSize = 1;
    }

    // .(점) 을 넣을 공간까지 고려한 사이즈. and 마이너스 일 경우 대비
    int totalStrSize = intSize + MAX_SIZE + (sign == 1 ? 0 : 1);

    char* resultStr = (char*)malloc(sizeof(char) * totalStrSize + 1);
    resultStr[totalStrSize] = &amp;#39;\0&amp;#39;;

    // 반환할 결과 문자 배열 인덱스 순회용
    int resultIdx = 0;

    // 만약 음수라면, 맨 앞에 &amp;#39;-&amp;#39; 를 붙여 음수임을 알린다.
    if(sign == -1) {
        resultStr[resultIdx] = &amp;#39;-&amp;#39;;
        resultIdx++;
    }

    // 정수 문자열 순회 포인터 가져오기
    char* intStrPtr = (char*)intStr;
    char* doubleStrPtr = (char*)doubleStr;

    // 정수 문자열을 이용하여 순회
    while(*intStrPtr) {
        char ch = *intStrPtr;
        resultStr[resultIdx++] = ch;
        intStrPtr++;
    }
    // 정수 끝났으면 &amp;#39;.&amp;#39; 넣어서 소수부 준비하기
    resultStr[resultIdx++] = &amp;#39;.&amp;#39;;

    // 소수 문자열을 이용하여 순회
    while(*doubleStrPtr) {
        char ch = *doubleStrPtr;
        resultStr[resultIdx++] = ch;
        doubleStrPtr++;
    }

    // 사용이 끝난 문자 배열 할당은 풀어주기
    free(intStr);
    free(doubleStr);

    return resultStr;
}

/**
 * @Param : 토큰화 할 입력 문자 배열
 *  - 앞뒤 빈칸 인식하여 파싱
 * @Return : 토큰 문자 배열의 주소들을 가지고 있는 배열 반환
 *  - 각 토큰의 시작 주소를 배열로 가지고 있음
 */
char** tokenizer(const char* str) {
    int size = 5;
    int currSize = 0;

    // 문자 순회용
    char* strPtr = (char*)str;

    // 현재 마지막에 NULL 처리를 하지 않고, 이 메서드의 마지막에 NULL 처리 한다.
    char** tokens = (char**) malloc(sizeof(char*) * size + 1);

    // 토큰 주소 순회용 포인터
    char** tokensPtr = (char**)tokens;

    int tknStartIdx = 0;
    int currIdx = 0;

    // 입력 문자 마지막까지 진행
    while(*strPtr) {
        // 현재 문자 추출
        char ch = *strPtr;

        // 바로 다음 입력 문자 인덱스로 이동
        strPtr++;

        // 만약 현재 문자가 빈칸에 해당한다면.
        if(isBlank(ch) == 1) {
            // 아직 토큰 시작점도 정해지지 않았다 ==&amp;gt; 토큰 시작 인덱스와 현재 탐색 인덱스가 동일.
            // 따라서, 둘 다 올리고 건너뛴다.
            if(tknStartIdx == currIdx) {
                tknStartIdx++; currIdx++;
                continue;
            }

            // 토큰 시작 위치와 탐색 위치 간의 차이를 계산.
            int tokenSize = currIdx - tknStartIdx;
            // 그 크기만큼 배열을 동적 생성
            char* newToken = (char*) malloc(sizeof(char) * tokenSize + 1);
            // 마지막에 미리 END 알림
            newToken[tokenSize] = &amp;#39;\0&amp;#39;;

            // 새로 생성된 토큰 순회용
            char* newTknPtr = newToken;

            // 토큰 시작 인덱스와 현재 탐색 인덱스가 동일하지 않을 때 까지 진행 : 토큰 시작 인덱스가 하나씩 오름
            while(currIdx != tknStartIdx) {
                // 문자열의 토큰 시작 인덱스를 이동시키며, 생성된 토큰에 넣을 거임
                char ch = str[tknStartIdx];
                // 새로 생성된 토큰 인덱스에 문자 삽입
                *newTknPtr = ch;

                newTknPtr++; tknStartIdx++;
            }

            // 새로 만들어진 토큰을 토큰 배열에 넣기
            *tokensPtr = newToken;
            // 토큰 주소 배열 이동하고, 현재 토큰 주소 배열 엘리먼트 개수 표시 증가
            tokensPtr++; currSize++;

            // 토큰화를 끝냈으니, 다음 탐색 인덱스로 이동
            currIdx++;

            // 다시 토큰 시작 인덱스와 탐색 인덱스를 동일시 한다 - 토큰화가 끝났으므로
            tknStartIdx = currIdx;

            // 만약에, 현재 토큰 주소 배열이 꽉 찼다면, 2 배로 길이를 늘려서 다시 할당한다.
            if(currSize == size) {
                // 2 배로 길이를 늘린 토큰 주소 배열 할당
                tokens = (char**)realloc(tokens, sizeof(char*) * (size * 2) + 1);
                // tokensPtr 은 이전 주소를 가르키므로, 곧 토큰을 넣어야 하는 주소로 다시 할당 해 준다.

                size *= 2;

                tokensPtr = (char**) tokens[currSize];
            }
        } else { // 빈 칸이 아니라면, 탐색 인덱스는 진행한다.
            currIdx++;
        }
    }

    // 1 개의 토큰만 존재하거나, 문자열 마지막에 정확히 마지막 토큰이 존재한다면, 실행된다.
    if(tknStartIdx != currIdx) {
        int newTknSize = currIdx - tknStartIdx;
        char* lastToken = (char*)malloc(sizeof(char) * newTknSize + 1);
        lastToken[newTknSize] = &amp;#39;\0&amp;#39;;

        // 순회용Ptr
        char* lastTokenPtr = lastToken;

        while(currIdx != tknStartIdx) {
            char ch = *strPtr;
            *lastTokenPtr = ch;
            lastTokenPtr++; strPtr++;
        }

        *tokensPtr = lastToken;
        tokensPtr++;
    }

    *tokensPtr = NULL;

    return tokens;
}

/**
 * @Param : 숫자 문자만을 타겟으로 한다.
 *  - 만약 숫자 문자가 아니라면, -1 을 반환한다.
 * @Return : 0 ~ 9 or -1
 */
int chToInt(char ch) {
    if(ch &amp;gt;= &amp;#39;0&amp;#39; &amp;amp;&amp;amp; ch &amp;lt;= &amp;#39;9&amp;#39;) {
        return ch - &amp;#39;0&amp;#39;;
    } else {
        return -1;
    }
}

/**
 * @Param : 빈 칸에 해당하는 문자인지 확인하려는 문자
 *  - 빈 칸, 혹은 끝(&amp;#39;\0&amp;#39;) 이면, 1 (True) 반환
 * @Return : 1 or 0 (True or False)
 */
_Bool isBlank(char ch) {
    if(ch == 32) {
        return 1;
    } else if(ch &amp;gt;= 9 &amp;amp;&amp;amp; ch &amp;lt;= 13) {
        return 1;
    } else {
        return 0;
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>백준-단계별로 풀어보기</category>
      <category>c token</category>
      <category>c tokenizer</category>
      <category>c 언어 토큰</category>
      <category>stdio.h</category>
      <category>도전</category>
      <category>예제 코드</category>
      <category>코드</category>
      <category>토큰화</category>
      <category>후기</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/224</guid>
      <comments>https://codecreature.tistory.com/224#entry224comment</comments>
      <pubDate>Fri, 6 Jun 2025 22:02:31 +0900</pubDate>
    </item>
    <item>
      <title>동시성 (Concurrency) 이란 무엇일까? - 부제 : 동시 실행이라는 착각</title>
      <link>https://codecreature.tistory.com/223</link>
      <description>&lt;h2&gt;제목 : 동시성 (concurrency) 는 무엇일까?&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;이 글을 작성하게 된 계기&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;프로세스와 스레드의 관계, 그리고 이들을 이루는 정보의 집합을 알아보았으며,&lt;/p&gt;
&lt;p&gt;또한, OS 스레드와 사용자 라이브러리 스레드가 서로 다르다는 것을 알게 되었다.&lt;/p&gt;
&lt;p&gt;나는 이전에 &amp;quot;프로세스와 스레드&amp;quot; 주제로 글을 다루었는데,&lt;/p&gt;
&lt;p&gt;이전에 운영체제에 대해 갖고 있던 잘못된 편견이 바로,&lt;/p&gt;
&lt;p&gt;&amp;quot;프로세스가 할당된 프로세서는 해당 프로세스만 실행한다&amp;quot; 였다.&lt;/p&gt;
&lt;p&gt;본래 프로세스와 스레드의 시간 분할 실행은 확실하게 맞는 표현이지만,&lt;/p&gt;
&lt;p&gt;이는 물리적으로 1 개의 프로세서만 존재 할 때의 일이라는 것이다.&lt;/p&gt;
&lt;p&gt;실제로는, 운영체제를 관장하는 &amp;quot;커널 (Kernel)&amp;quot; 이,&lt;/p&gt;
&lt;p&gt;하나의 프로세스에 할당된 많은 스레드들을 감지하여, 이를 여러 프로세서에 나누어서 실행 해 준다.&lt;/p&gt;
&lt;p&gt;즉, 현재 멀티 프로세서가 당연한 시대에서, 시간 분할 예시는 좀 더 생각해서 받아들일 필요가 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;하지만, 컴퓨팅 세상에서 연산 실행에 대한 카테고리는 단순히 동시성, 병렬성으로 나눌 수 없었다.&lt;/p&gt;
&lt;p&gt;컴퓨터 과학 용어에는, 동기, 비동기에 대한 개념도 존재한다.&lt;/p&gt;
&lt;p&gt;나는 사실 동기, 비동기에 대한 개념을 먼저 알고 있었다. 자바스크립트를 공부했기 때문이다.&lt;/p&gt;
&lt;p&gt;그러나, 프로세스와 스레드의 상태와 이동, 시간 분할을 안다고 해서,&lt;/p&gt;
&lt;p&gt;(동시성/병행성) 과, (병렬성) 을 이해하고 있다고 말할 수는 없었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그리고 현재 뜨고 있는 모던 프로그래밍 언어들이 존재하는데,&lt;/p&gt;
&lt;p&gt;이 언어들은 특정 언어의 단점을 해소하고, 문법적 어려움을 최소화 한 언어인 경우가 많았다.&lt;/p&gt;
&lt;p&gt;그리고 이러한 최신 언어들은, 비동기 도입, 그리고 &amp;quot;동시성&amp;quot; 에 대한 개념을 내세우고 있었다.&lt;/p&gt;
&lt;p&gt;공식 문서에서도 말할 정도로 좋은 기능인 &amp;quot;언어적 차원의 동시성&amp;quot; 이, 왜 좋은지 알 수 없었다.&lt;/p&gt;
&lt;p&gt;좋은게 좋은거라고 넘길 수도 있겠지만, 나는 이 개념이 얼마나 중요한지 알고 싶었다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;동시성/병행성 이란?&lt;/h2&gt;
&lt;p&gt;병렬성과 비슷하게,&lt;/p&gt;
&lt;p&gt;컴퓨터 과학에서 여러 계산을 동시에 수행하는 시스템의 특성으로,&lt;/p&gt;
&lt;p&gt;잠재적으로는 서로 상호 작용이 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;위의 위키백과 정의를 보건대 예상되는 것은&lt;/strong&gt;,&lt;/p&gt;
&lt;p&gt;프로세스와 스레드를 의미하는 것이라고 생각한다.&lt;/p&gt;
&lt;p&gt;프로세스 내에 스레드는 최소 1개가 존재하고, 컴퓨터 용어로는 종종 스레드를 프로세스라고 부르기도 한다.&lt;/p&gt;
&lt;p&gt;결국, 프로세스(Process) 는 생성, 준비, 대기, 실행, 완료 등등의 상태가 존재하지만,&lt;/p&gt;
&lt;p&gt;이는 내부에서 실행되는 스레드(Thread) 또한 마찬가지라는 의미이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;다중 프로세서로 병렬 수행하지 않고 동시성을 사용하는 이유는?&lt;/h3&gt;
&lt;p&gt;먼저, 나는 20 대 중후반으로, window 98 을 처음으로 사용하기 시작한 사람이다. (2025 년 기준.)&lt;/p&gt;
&lt;p&gt;나의 컴퓨터는 &amp;quot;싱글 코어&amp;quot; 였으며, 이는 프로세서가 1개였다.&lt;/p&gt;
&lt;p&gt;모든 프로그램이 꺼진 상태에서야 마우스의 움직임이 그나마 부드러웠고,&lt;/p&gt;
&lt;p&gt;인터넷이 켜졌을 경우, 마우스의 움직임은 매우 끊겼다.&lt;/p&gt;
&lt;p&gt;그 당시에는 많은 프로그램을 실행하기에 당연히 GUI 의 움직임 또한 끊긴다고 생각했지만,&lt;/p&gt;
&lt;p&gt;단일 프로세스가 수행하고 있는 동시성/병행성 원리를 알고 나니, 왜 프로그램이 느려졌는지 알 수 있었다.&lt;/p&gt;
&lt;p&gt;이 단일 프로세스는 이러한 이벤트들을 동시에 수행하고 있었다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GUI 변화 감지 및 렌더링&lt;/li&gt;
&lt;li&gt;외부 프로그램을 실행&lt;/li&gt;
&lt;li&gt;네트워크 처리&lt;/li&gt;
&lt;li&gt;등등 수많은 연산을 처리하기 위한 프로세스 및 스레드 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;단일 프로세스는 아주아주 단순하게 생각해 보면, 하나의 도메인 영역에 대한 계산을 수행한다.&lt;/p&gt;
&lt;p&gt;특히나, 옛날 컴퓨터는 프로세서 자체가 하나이기 때문에 당연하지 않나 싶었다.&lt;/p&gt;
&lt;p&gt;그러나, 우리는 그 당시에도 브라우저를 사용하면서, 동시에 마우스 컨트롤, 키보드 컨트롤, 네트워크를 동시에 수행 했다.&lt;/p&gt;
&lt;p&gt;이것이 어떻게 가능했을까?&lt;/p&gt;
&lt;p&gt;바로 동시/병행 컴퓨팅 덕분이었다.&lt;/p&gt;
&lt;p&gt;각각의 도메인 연산은 병렬 연산을 통해 동시에 일어나는 것 같지만,&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;실제로 맞긴 하다. 물론, 현대에는 2 가지 개념을 혼합하여 사용한다. (스레드가 프로세서보다 많으므로)&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;그 시절 1프로세서 시절에는 병행 컴퓨팅을 통해 필요한 연산을 동시에 수행했다.&lt;/p&gt;
&lt;h3&gt;하나의 프로세서가 어떻게 연산을 동시에 할 수 있나?&lt;/h3&gt;
&lt;p&gt;옛날에는 컴퓨터가 다~ 해주지 생각하며 지냈었지만,&lt;/p&gt;
&lt;p&gt;현재 동시성과 병렬성에 대한 개념을 접하고,&lt;/p&gt;
&lt;p&gt;프로세스와 스레드와 관계, 상태, 정보 블록, 시간 분할 연산을 알고 난 뒤&lt;/p&gt;
&lt;p&gt;그 당시 하나의 프로세서가 정말 많은 작업을 해 주고 있었구나.. 라고 생각한다.&lt;/p&gt;
&lt;p&gt;그도 그럴 것이,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;마우스의 이동, 키보드 입력, 화면 출력&lt;/li&gt;
&lt;li&gt;GUI 구성&lt;/li&gt;
&lt;li&gt;어플리케이션 연산&lt;/li&gt;
&lt;li&gt;네트워크 송수신 및 보안&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;등등 셀 수 없는 프로세스를 동시에 단 하나의 프로세서가 수행하고 있었다는 말이 된다.&lt;/p&gt;
&lt;p&gt;그러나, 나는 더더욱이 이해가 되지 않았다.&lt;/p&gt;
&lt;p&gt;&amp;quot;동시&amp;quot; 라는 말은, 찰나의 순간을 포착했을 때, 여러 프로세스(스레드) 가 동시에 연산되고 있는 것을&lt;/p&gt;
&lt;p&gt;의미하는 것이 아니었나? 물론 이 말도 맞다.&lt;/p&gt;
&lt;p&gt;하나의 프로세서에서 &amp;quot;동시&amp;quot; 는, 프로세서의 연산 시간을 &amp;quot;분할&amp;quot; 하여,&lt;/p&gt;
&lt;p&gt;여러 프로세스가 나눠서 사용하고 있다는 말이다.&lt;/p&gt;
&lt;p&gt;약간 더 이해를 가미하기 위해 말하자면, 프로세서는 여러 프로세스를 시간 분할하여 실행한다.&lt;/p&gt;
&lt;p&gt;먼저 프로세스와 스레드의 관계를 말하자면, 밑의 예시와 같이 들 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;|  시간   | &amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;
----------
|        |       |
|        | 스레드1 |~~~~~~~|                     |~~~~~~~~~~~~
| 프로세스 |       |
|        |-------------------------------------------------
|        |       |
|        | 스레드2 |       |~~~~~~~~~~~~~~~~~~~~|
|        |       |&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;프로세스는 1 개 이상의 스레드를 가지고 있으며, 다중 스레드에 대해서 시간 분할 실행을 한다.&lt;/p&gt;
&lt;p&gt;어? 그렇다면, 프로세스 또한 내부의 스레드에게 자신이 할당받은 시간을 분할한다는 말이 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;여기서, 1 개의 프로세서가 2 개의 프로세스를 할당받았을 때의 상황을 보자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;|  시간   | &amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;
-------------------------------------------------------------------------
|         |       |
|         | 스레드1 |~~~|                      |~~~~~~~|
| 프로세스 1|       |
|         |-------|
|         |       |
|         | 스레드2 |        |~~~~~~|
|         |       |
-------------------------------------------------------------------------
|         |       |
|         | 스레드1 |   |~~~~|                         |~~~~|
|         |       |
|         |-------|
|         |       |
| 프로세스 2| 스레드2 |                |~~~~~~~~|
|         |       |
|         |-------|
|         |       |
|         | 스레드3 |                                        |~~~~~|
|         |       |&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이는 직접 작성 해 본 예시인데,&lt;/p&gt;
&lt;p&gt;&amp;quot;시간&amp;quot; 이라는 자원을 상위 자원이 분배한다고 가정했을 때,&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;프로세서(물리) -&amp;gt; 프로세스(논리) -&amp;gt; 쓰레드(논리)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이러한 시간 분배 작전으로, 수 많은 일들을 &amp;quot;동시에&amp;quot; 하고 있었던 것이다.&lt;/p&gt;
&lt;p&gt;아주아주 정확히 말하자면, &amp;quot;동시에 하는 것 처럼&amp;quot; 하고 있었던 것이다.&lt;/p&gt;
&lt;p&gt;그러다가 중간에 상당한 양의 논리 연산이 필요한 &amp;quot;스레드&amp;quot; 가 생겨난다면,&lt;/p&gt;
&lt;p&gt;가끔씩 프로그램이 느려지거나, 마우스가 동에 떴다 서에 떴다 하던 것이다.&lt;/p&gt;
&lt;p&gt;이제야 이해가 간다.&lt;/p&gt;
&lt;p&gt;위에서 예시로 들은 입출력 기계, 네트워크, 어플리케이션들은,&lt;/p&gt;
&lt;p&gt;병행성, 즉, 동기성을 통해 서로가 시간을 분배하며 사용하며, 서로가 소통한다.&lt;/p&gt;
&lt;p&gt;예시 :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;마우스 기기에서 움직임에 대한 데이터 정보를 받음, GUI 프로세스가 연산하여 마우스 렌더링 위치 조정&lt;/li&gt;
&lt;li&gt;브라우저는 네트워크를 통해 특정 웹 사이트에서 페이지 정보를 요청. 네트워크 프로세스 응답 이후 렌더링 시작&lt;/li&gt;
&lt;li&gt;백엔드 어플리케이션 시작(실행 후 슬립) -&amp;gt; 요청 네트워크 연산 후 응답 -&amp;gt; 다시 슬립&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이렇게 시간을 분배하여 사용하고, 서로 소통한다는 의미는, &amp;quot;프로세스&amp;quot; 자체라기보다는,&lt;/p&gt;
&lt;p&gt;&amp;quot;스레드&amp;quot; 가 더 정확하다. 프로세스의 실질 구성원은 &amp;quot;스레드&amp;quot; 이기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;동기성(병행성) 에서 나타날 수 있는 문제&lt;/h3&gt;
&lt;p&gt;우리가 동시에 실행해야 하는 프로그램들(스레드들) 이 존재하고,&lt;/p&gt;
&lt;p&gt;이를 제한된 수의 프로세서에서 원활히 실행하기 위해서는 동기성 개념이 필수적이다.&lt;/p&gt;
&lt;p&gt;우리는 동일한 알고리즘의 계산을 수행하기 위해 동기성을 사용하지 않는다.&lt;/p&gt;
&lt;p&gt;다양한 카테고리의 프로그램(프로세스 및 스레드)을 실행하여, 서로 상호작용 하는 형태로 실행한다.&lt;/p&gt;
&lt;p&gt;이 과정에서 가장 대표적인 것이,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;브라우저 네트워크 요청 후 대기(Block)&lt;/li&gt;
&lt;li&gt;백엔드 프로그램 의존성 구축 및 서버 실행 후 요청 대기(Block)&lt;/li&gt;
&lt;li&gt;등등..&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;스레드가 다른 스레드의 응답을 기다린다는 예시를 작성 해 보았다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;즉, 특정 스레드는, 다른 특정 스레드의 응답을 기다린다는 것이다.&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이 과정에서 문제가 발생한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;1. 교착 상태 (Dead Lock)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이는 리소스(물리)적인 문제보단, 논리적인 문제이다.&lt;/p&gt;
&lt;p&gt;한번, 교착 상태에 빠진 작업(스레드) 의 그래프 예시를 들어보겠다 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;---
title : 교착 상태 예시 (Dead Lock)
---
flowchart LR

Task1(&amp;quot;Task1&amp;quot;)
Task2(&amp;quot;Task2&amp;quot;)
Task3(&amp;quot;Task3&amp;quot;)
Task4(&amp;quot;Task4&amp;quot;)

Task1 -- 요청 후 대기 --&amp;gt; Task2

Task2 -- 요청 후 대기 --&amp;gt; Task3

Task3 -- 요청 후 대기 --&amp;gt; Task4

Task4 -- 요청 후 대기 --&amp;gt; Task1

Task5 -- 요청 후 대기 --&amp;gt; Task3

Task6 -- 요청 후 대기 --&amp;gt; Task4&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 Task 들은 모두 응답을 받아야만 프로그램을 완료하고&lt;/p&gt;
&lt;p&gt;자신을 요청하고 있는 다른 자원들에게 응답할 수 있는 상황이다.&lt;/p&gt;
&lt;p&gt;즉, 정말 골치아픈 상황이다.&lt;/p&gt;
&lt;p&gt;여기 중 하나라도 단순 계산을 통한 응답이라면, 비동기 방식으로 해결할 수 있겠지만,&lt;/p&gt;
&lt;p&gt;안타깝게도 정말 타겟 스레드의 응답만을 바라는 상태라면, 이는 해결 할 수 없는 문제이다.&lt;/p&gt;
&lt;p&gt;여기서 교착 상태의 조건이 4 가지가 존재한다 :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;상호 배제 : 프로세스들이 필요로 하는 자원에 대해 &amp;quot;배타적인&amp;quot; 통제권을 요구한다. - 해당자원내꺼&lt;/li&gt;
&lt;li&gt;점유 대기 : 프로세스가 할당된 자원을 가진 상태에서 다른 자원을 기다린다.&lt;/li&gt;
&lt;li&gt;비선점 : 프로세스가 어떤 자원의 사용을 끝낼 때 까지 해당 자원을 뺏을 수 없다.&lt;/li&gt;
&lt;li&gt;순환대기 : 각각의 프로세스는 &amp;quot;순환적&amp;quot; 으로 다음 프로세스가 요구하는 자원을 가지고 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 조건이 &amp;quot;모두&amp;quot; 만족해야 교착 상태가 발생한다.&lt;/p&gt;
&lt;p&gt;그러나, 현대의 대부분의 운영 체제들은 4 번째 조건을 막으면서 교착 상태를 방지한다고 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;2. 기아 상태 (Starvation)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;동기성/병행성 시스템은 시간 분할을 통해, 모든 작업을 동시에 실행한다.&lt;/p&gt;
&lt;p&gt;그런데, 지속적으로 필요한 컴퓨터 자원을 가져오지 못하는 상황(기아 상태) 가 일어날 수 있다고 한다.&lt;/p&gt;
&lt;p&gt;왜 그럴까?&lt;/p&gt;
&lt;p&gt;이는 주로 낮은 품질(제대로 만들어지지 않은) 멀티태스킹 시스템에 의해 발생한다고 한다.&lt;/p&gt;
&lt;p&gt;사용자의 OS 는 다양한 목적에 의해 수많은 프로세스들이 만들어지는데,&lt;/p&gt;
&lt;p&gt;예를 들어서 화면 출력과 키보드 입력, 마우스 입력이 존재한다.&lt;/p&gt;
&lt;p&gt;이러한 프로세스들은 끊임없이 필요한 자원을 요구한다. 변화를 보여주어야 하기 때문이다.&lt;/p&gt;
&lt;p&gt;그러나, 스케줄링 시스템이 잘못 설계된 경우, 필요한 자원을 지속적으로 응답하지 못할 가능성이 높다고 한다.&lt;/p&gt;
&lt;p&gt;그리고 옛날 방식의 공격이 있는데, Fork Bomb(포크 폭탄) 이라고 하는 공격에 의해 발생할 수도 있다고 한다.&lt;/p&gt;
&lt;p&gt;프로세스는 스스로를 fork 하여 복제 할 수 있는데,&lt;/p&gt;
&lt;p&gt;특정 프로세스가 스스로를 여러개로 복제하고, 또 복제된 프로세스들이 스스로를 여러개로 복제하는 공격이다.&lt;/p&gt;
&lt;p&gt;이렇게 되는 경우, 너무 많은 프로세스들이 존재하게 되어 끊임없이 자원을 필요로 하는 프로세스가 제때 응답을 받지 못한다고 한다.&lt;/p&gt;
&lt;p&gt;이를 &amp;quot;서비스 거부 공격&amp;quot; 이라고 말한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;그래서 동기성/병행성을 요약해서 말하자면?&lt;/h3&gt;
&lt;p&gt;프로세서보다 많은 프로세스를 &amp;quot;동시에&amp;quot; 수행할 수 있다.&lt;/p&gt;
&lt;p&gt;그러나, 진정한 의미의 &amp;quot;동시에&amp;quot; 가 아니라, 프로세서가 시간 분할 알고리즘에 따라&lt;/p&gt;
&lt;p&gt;각각의 프로세스를 나눠서 실행한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;다른 개념인 병렬성이란?&lt;/h3&gt;
&lt;p&gt;병렬성이란, &amp;quot;동시에 실행 하는 것 처럼 보이는 것&amp;quot; 이 아니라,&lt;/p&gt;
&lt;p&gt;진정한 의미로 &amp;quot;동시에 실행&amp;quot; 하는 것을 의미한다.&lt;/p&gt;
&lt;p&gt;병렬 컴퓨팅을 수행하기 위해서는 현재 실행하는 프로그램만큼, 프로세서가 존재해야 한다.&lt;/p&gt;
&lt;p&gt;따라서, 슈퍼 컴퓨터, GPU 와 같은 장치에서 가장 중요하게 다뤄지는 개념이다.&lt;/p&gt;
&lt;p&gt;그러나, 제한된 리소스를 최대한 활용하기 위해 사용되는 동기성/병행성 개념과 달리,&lt;/p&gt;
&lt;p&gt;같은 연산, 혹은 프로그램을 병렬 연산하는 과정에서 자원 분배, 경쟁에 대한 논리가 더 복잡하다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;동시성이 좋은 언어가 있다?&lt;/h2&gt;
&lt;p&gt;TypeScript 의 파싱 과정을 Go 로 대체하는 과정에서 Golang 공식 홈페이지에 들어가 보았는데,&lt;/p&gt;
&lt;p&gt;자신의 언어가 동시성에 매우 좋다는 것을 어필했었다.&lt;/p&gt;
&lt;p&gt;여기서 잠깐, &amp;quot;동시성이 좋다&amp;quot; 라는 의미에 대해서 짚고 넘어가야 할 필요가 있다고 생각한다.&lt;/p&gt;
&lt;p&gt;대부분의 언어는 OS 스레드와, 사용자 라이브러리 스레드를 1 : 1 매칭시켜 실행한다.&lt;/p&gt;
&lt;p&gt;즉, 우리가 만드는 Node.js 의 &lt;code&gt;Worker&lt;/code&gt; 이나,&lt;/p&gt;
&lt;p&gt;Java 언어에서의 &lt;code&gt;Runnable&lt;/code&gt;, 혹은 &lt;code&gt;Thread&lt;/code&gt; 와 같은 것들은 1 : 1 매칭이다.&lt;/p&gt;
&lt;p&gt;그리고, OS 스레드들은는 커널에 의해 훌륭한 시분할 알고리즘을 사용하여 실행되고,&lt;/p&gt;
&lt;p&gt;이들은 결국 동시성이 고려된 상태로 실행된다. 즉, 동시성이 좋다.&lt;/p&gt;
&lt;p&gt;또 잠깐, 그렇다면, 결국 대부분의 언어는 동시성을 사용할 수 있다는 말이 아닌가?&lt;/p&gt;
&lt;p&gt;맞다. 대부분의 언어, 말하지 않았던 고수준의 언어인 Python 까지도 OS 레벨 스레드를 생성하여&lt;/p&gt;
&lt;p&gt;동시성을 고려한 프로그래밍이 가능하다.&lt;/p&gt;
&lt;p&gt;그런데, 왜 특정 언어에서는 &amp;quot;동시성이 좋다&amp;quot; 라고 말하는 것일까?&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;나도 이전에 작성했던 &amp;quot;프로세스와 스레드에 관하여&amp;quot; 블로그 글을 작성하면서 알게 되었던 것인데,&lt;/p&gt;
&lt;p&gt;Golang 과 같은 언어에서 동시성 전용 메서드 코드 혹은 struct 와 같은 용어를 사용한다면,&lt;/p&gt;
&lt;p&gt;이들은 컴파일 과정에서 OS 레벨 스레드 &lt;code&gt;M&lt;/code&gt; 개, 사용자 레벨 스레드 &lt;code&gt;N&lt;/code&gt; 개가 생성되어&lt;/p&gt;
&lt;p&gt;이들을 &amp;quot;멀티플렉싱&amp;quot; 하여 실행한다.&lt;/p&gt;
&lt;p&gt;주로 특징이, 프로세스가 컨텍스트 스위칭(상태변환 EX - 실행, 블록, 대기, 준비 등등)&lt;/p&gt;
&lt;p&gt;과정에서는 그 뒤의 과정에 멈춘다. 그러나, 이런 멀티플렉싱 기법을 통하여,&lt;/p&gt;
&lt;p&gt;자주 일어나는 컨텍스트 스위칭 시간조차도 활용하여 남은 과정을 실행한다는 특징이 있다.&lt;/p&gt;
&lt;p&gt;Go 언어에서는 자체적인 &lt;code&gt;M : N&lt;/code&gt; 매칭 알고리즘을 만들어 놓았는데, 이를 goroutine 이라고 부른다.&lt;/p&gt;
&lt;p&gt;Rust 언어에서도 동기성을 위해 특정 기능을 넣어놓을 것으로 보인다. 하지만,&lt;/p&gt;
&lt;p&gt;Rust 언어의 &amp;quot;빌림(Borrow)&amp;quot; 기능에 대해서 잘 이해하지 못하는 상황에서 설명하기는 어려워 보인다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;이 글을 작성하면서 배운 점은&lt;/h2&gt;
&lt;p&gt;결국 시분할 실행 시스템과 동시성/병행성 은 뗄려야 뗄 수 없는 거의 동등한 관계라는 것을 알게 되었다.&lt;/p&gt;
&lt;p&gt;무엇보다, 이 주제를 다루면서 너무나도 어릴 적 형의 컴퓨터를 사용할 때, 버벅이는 이유를 알게 되었다.&lt;/p&gt;
&lt;p&gt;왜 인터넷 브라우저를 실행하고 나서 마우스를 움직이면 버벅였는지,&lt;/p&gt;
&lt;p&gt;그때 당시에는 프로그램 하나를 켜 놓고, 꼭 완료 될 때 까지 기다리는 습관이 있었는데,&lt;/p&gt;
&lt;p&gt;아마 이를 몸으로 체득하고 기다리지 않았을까 생각한다.&lt;/p&gt;
&lt;p&gt;한정된 리소스인 프로세서에서 그보다 훨씬 많은 프로세스와 스레드들을 실행하기 위해,&lt;/p&gt;
&lt;p&gt;&amp;quot;동시에 실행&amp;quot; 이라는 개념을 비틀어서 분할하여 동시에 실행이라는 개념을 알게 되었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;거꾸로 생각해 보니, 예를 들어&lt;/p&gt;
&lt;p&gt;코드를 작성하면서, 이 코드를 설명하는 마크다운 문서를 작성하면서, 소통하는 중이라고 가정 해 보자.&lt;/p&gt;
&lt;p&gt;&amp;quot;나&amp;quot; 라는 사람은 뇌가 1개 뿐이다. 하지만, 나는 이 3 개의 작업을 하고 있다.&lt;/p&gt;
&lt;p&gt;물론, 코드를 잠깐 작성하고, 시선을 돌려 문서를 작성하며, 프로그램을 켜서 슬랙으로 소통하고 있다.&lt;/p&gt;
&lt;p&gt;누군가 와서, &amp;quot;당신은 지금 무슨 일을 하고 있습니까?&amp;quot; 라고 물어본다면,&lt;/p&gt;
&lt;p&gt;나는&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Task1 : 코드를 작성하며,&lt;/li&gt;
&lt;li&gt;Task2 : 이 코드가 작성되는 대로 이에 대한 문서를 작성하며,&lt;/li&gt;
&lt;li&gt;Task3 : 진행도에 대한 내용을 팀원과 소통하고 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 세 가지를 &amp;quot;동시에&amp;quot; 하고 있다고 설명 할 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;h2&gt;참조 사이트&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;위키백과 (Concurrency - 병행성 == 동시성)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.wikipedia.org/wiki/%EB%B3%91%ED%96%89%EC%84%B1&quot;&gt;https://ko.wikipedia.org/wiki/%EB%B3%91%ED%96%89%EC%84%B1&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;위키백과 (낙관적 병행 수행 제어)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.wikipedia.org/wiki/%EB%82%99%EA%B4%80%EC%A0%81_%EB%B3%91%ED%96%89_%EC%88%98%ED%96%89_%EC%A0%9C%EC%96%B4&quot;&gt;https://ko.wikipedia.org/wiki/%EB%82%99%EA%B4%80%EC%A0%81_%EB%B3%91%ED%96%89_%EC%88%98%ED%96%89_%EC%A0%9C%EC%96%B4&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;위키백과 (병렬 컴퓨팅 or 병렬 연산 - Parallel Computing)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.wikipedia.org/wiki/%EB%B3%91%EB%A0%AC_%EC%BB%B4%ED%93%A8%ED%8C%85&quot;&gt;https://ko.wikipedia.org/wiki/%EB%B3%91%EB%A0%AC_%EC%BB%B4%ED%93%A8%ED%8C%85&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;위키백과 (Task Parallelism - 작업 병렬성)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.wikipedia.org/wiki/%EC%9E%91%EC%97%85_%EB%B3%91%EB%A0%AC%EC%84%B1&quot;&gt;https://ko.wikipedia.org/wiki/%EC%9E%91%EC%97%85_%EB%B3%91%EB%A0%AC%EC%84%B1&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;위키백과 - 교착 상태&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.wikipedia.org/wiki/%EA%B5%90%EC%B0%A9_%EC%83%81%ED%83%9C&quot;&gt;https://ko.wikipedia.org/wiki/%EA%B5%90%EC%B0%A9_%EC%83%81%ED%83%9C&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;위키백과 - 기아 상태&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.wikipedia.org/wiki/%EA%B8%B0%EC%95%84_%EC%83%81%ED%83%9C&quot;&gt;https://ko.wikipedia.org/wiki/%EA%B8%B0%EC%95%84_%EC%83%81%ED%83%9C&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;</description>
      <category>잡다 지식</category>
      <category>concurrency</category>
      <category>동시 실행</category>
      <category>동시성</category>
      <category>병렬성</category>
      <category>병행성</category>
      <category>비동기</category>
      <category>스레드</category>
      <category>프로세서</category>
      <category>프로세스</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/223</guid>
      <comments>https://codecreature.tistory.com/223#entry223comment</comments>
      <pubDate>Wed, 4 Jun 2025 23:04:48 +0900</pubDate>
    </item>
    <item>
      <title>HTML 문서 정식 태그 &amp;quot;전부&amp;quot; 공부하기 - 마무리 편 (코드 작성 및 구현까지)</title>
      <link>https://codecreature.tistory.com/222</link>
      <description>&lt;h2&gt;제목 : HTML 문서 태그 &amp;quot;전부&amp;quot; 공부하기 - 5편&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;이전 편인 4 편의 주소&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/221&quot;&gt;https://codecreature.tistory.com/221&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;이 글을 작성하는 이유&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;HTML 태그를 &amp;quot;전부&amp;quot; 다루기 위해 1 편부터 4편 까지 달려왔다..&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;태그 정의&lt;/strong&gt;, &lt;strong&gt;태그 예시&lt;/strong&gt;, &lt;strong&gt;태그 실제 표현 예시&lt;/strong&gt;, &lt;strong&gt;동적 스크립트&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이를 전부 구현 및 작성한다는 것은 정말 쉽지는 않은 일이었다. (태그가 많아서)&lt;/p&gt;
&lt;p&gt;일반적으로 상황에 맞게 자주 사용되는 태그들이 존재하는데,&lt;/p&gt;
&lt;p&gt;나는 JS 는 잘 사용하지만, 시멘틱 태그 자체에는 약해서 공부를 하게 되었었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그렇다면, &amp;quot;나는 HTML 태그를 잘 알고 있느냐?&amp;quot; 라는 질문이 스스로에게 떠오른다..&lt;/p&gt;
&lt;p&gt;정말로 &amp;quot;모든&amp;quot; 태그를 다루고 있기에 힘들게 공부했지만, 자만하지 않고&lt;/p&gt;
&lt;p&gt;서로 정반대의 대답을 해 보도록 하자.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;- 나는 HTML 태그를 잘 알고 있습니다. - 의 경우&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;나는 HTML5 정식 스펙상에 들어가 있는 &amp;quot;모든&amp;quot; 태그를 배우고, 작성 해 보았다.&lt;/p&gt;
&lt;p&gt;태그가 어떠한 상황에 사용되는지, 실제 정의는 어떻게 되는지, 전부 이해하고 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;- 나는 HTML 태그를 잘 알고 있지 않다. - 의 경우&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;HTML 태그를 전부 알고 있더라도, 블로그 글의 특성상,&lt;/p&gt;
&lt;p&gt;JS 에서 제한되는 메서드와 기능이 있어, HTML 태그를 잘 활용한다고 볼 수는 없다.&lt;/p&gt;
&lt;p&gt;즉, 곧 공부하게 될 React 에서 태그 활용성이 극대화 될 것이라고 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;현재 심정은?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;하.... 생각보다 HTML5 스펙에 존재하는 태그가 정말 많다는 것을 느꼈다..&lt;/p&gt;
&lt;p&gt;하지만, 별로 중요하게 생각하고 있지 않던 태그가, 웹에서 생각보다 인터랙션을 위해&lt;/p&gt;
&lt;p&gt;중요한 역할을 하고 있다는 것을 깨닫기도 한다. EX - &lt;code&gt;dfn&lt;/code&gt;, &lt;code&gt;abbr&lt;/code&gt; 등등&lt;/p&gt;
&lt;p&gt;현재까지 마무리 한 태그 카테고리는 이러하다&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Basic HTML - 기본 HTML 태그들&lt;/li&gt;
&lt;li&gt;Formatting - 형식 위주의 태그들&lt;/li&gt;
&lt;li&gt;Forms and Input - form 과 내부의 input 관련 태그들&lt;/li&gt;
&lt;li&gt;Frames - HTML5 스펙으로 오면서 &lt;code&gt;iframe&lt;/code&gt; 으로 기능이 통합됨.&lt;/li&gt;
&lt;li&gt;Images - 이미지와 간단한 캔버스, 그리고 &lt;code&gt;svg&lt;/code&gt; 의 구성을 알게 됨&lt;/li&gt;
&lt;li&gt;Audio / Video - 오디오와 비디오, 그리고 소스 태그에 대해서 배우는데, 이건 JS 가 중요하다 생각.&lt;/li&gt;
&lt;li&gt;Links - 연결된 문서에 대한 태그와, 해당 문서와 현재 문서 간의 관계를 배운다.&lt;/li&gt;
&lt;li&gt;Lists - 정렬, 비-정렬 배열에 대한 요소와, 각 태그의 속성에 따라 달라지는 마커 컨텐츠를 배운다.&lt;/li&gt;
&lt;li&gt;Tables - 엑셀과 비슷한 형식의 데이터를 표현하기 위해 정말 필수적으로 배워야 할 태그들이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;여기까지만 해도.. 거의 120 개 정도? 는 된다.&lt;/p&gt;
&lt;p&gt;간단히 다루지도 않았고, &lt;strong&gt;정의&lt;/strong&gt;, &lt;strong&gt;코드 예제&lt;/strong&gt;, &lt;strong&gt;실사 렌더링&lt;/strong&gt;, &lt;strong&gt;기본 css&lt;/strong&gt;, &lt;strong&gt;커스텀&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이들을 한꺼번에 다루니, 생각보다 글을 작성하는 것이 쉽진 않다.&lt;/p&gt;
&lt;p&gt;그나마, 여태까지 Markdown 문서를 500 개 정도는 작성해 온 짬이 있어 작성할 수 있는 것이 아닌가 생각된다...&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;이 문서가 마지막 HTML 태그에 대한 블로그 글이다!!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이제 남은 카테고리는&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Styles and Semantics - 스타일과 시멘틱 태그들&lt;/li&gt;
&lt;li&gt;Meta Info - 메타정보 모음&lt;/li&gt;
&lt;li&gt;Programming - HTML 문서 내부 프로그래밍 관련 태그들&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이렇게 남았다..&lt;/p&gt;
&lt;p&gt;이렇게 보니, 정말 긴 길을 달려왔구나 생각이 든다.&lt;/p&gt;
&lt;p&gt;마지막 글이니만큼, 조금 길 수도 있다.&lt;/p&gt;
&lt;p&gt;그러나, 위의 리스트에서 볼 수 있는 태그들을 보다 보면,&lt;/p&gt;
&lt;p&gt;우리가 웹 개발을 할 때 무심코 생각했던 태그들이 중요한 역할을 했다는 것을 알 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Category - Styles and Semantics (스타일과 시멘틱 태그)&lt;/h2&gt;
&lt;h3&gt;&lt;code&gt;style&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;문서에 대한 스타일 정보를 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;문서(HTML) 자체에서 정의하는 css 정보를 의미한다.&lt;/p&gt;
&lt;p&gt;주로 간단한 문서를 작성 할 때, &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; 태그 내부에 메타데이터처럼 넣어서 사용한다.&lt;/p&gt;
&lt;p&gt;그리고, 반드시 &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; 태그 내부에 들어가야 한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;style&amp;gt;
      p {
        color: purple;
      }
    &amp;lt;/style&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    ....
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; 를 임시적으로 블로그 아티클에 표시 할 수 없어 스타일 태그도 따로 넣을 수 없다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; 특수 속성 :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;media&lt;/code&gt; : 유저의 기기와 미디어 상황에 따라 리소스를 최적화 하는 데 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;style {
  display: none;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;div&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;문서의 특정 부분을 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;리액트를 사용하면서, &amp;quot;가장 많이 사용하는 태그&amp;quot; 가 아닐까 생각된다.&lt;/p&gt;
&lt;p&gt;공식 문서 상으로, &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; 태그는 HTML 문서 내부에서&lt;/p&gt;
&lt;p&gt;분할, 혹은 구역을 정의한다.&lt;/p&gt;
&lt;p&gt;그리고, &lt;code&gt;div&lt;/code&gt; 는 다양한 HTML 요소들을 담은 컨테이너로서 사용된다.&lt;/p&gt;
&lt;p&gt;그리고, 동일한 &lt;code&gt;div&lt;/code&gt; 를 통해 컨테이너를 분할하더라도,&lt;/p&gt;
&lt;p&gt;내부의 &lt;code&gt;class&lt;/code&gt; 속성을 통해 쉽게 스타일링이 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;p&amp;gt;첫 번째 분할 구역 선언&amp;lt;/p&amp;gt;

&amp;lt;div&amp;gt;플레인 div&amp;lt;/div&amp;gt;

&amp;lt;p&amp;gt;두 번째 분할 구역 선언&amp;lt;/p&amp;gt;

&amp;lt;div style=&amp;quot;background-color : gray&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt; 첫 번째 분할 구역 선언 &lt;/p&gt;

&lt;div&gt;플레인 div&lt;/div&gt;

&lt;p&gt; 두 번째 분할 구역 선언 &lt;/p&gt;

&lt;div style=&quot;background-color : gray&quot;&gt;배경 색상이 회색&lt;/div&gt;

&lt;br/&gt;

&lt;p&gt;보다시피, &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; 태그는 기본 설정이 &lt;code&gt;block&lt;/code&gt; 으로서, 하나의 줄을 차지한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;div {
  display: block;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;span&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;문서 내부의 특정 텍스트나, 구역을 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;div 태그가 블록 단위로 선언되어, 수많은 다른 태그들을 담을 수 있는 한편,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt; 태그는 블록이 아니라, 텍스트 수준에서의 자유도를 올려주는 태그이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;span&amp;lt;/code&amp;gt; 태그에 어떠한 수정도 가하지 않은 경우&amp;lt;/p&amp;gt;

span 태그는 마크다운에서 &amp;lt;span&amp;gt;색상을 바꾸고 싶은 경우에도&amp;lt;/span&amp;gt; 사용합니다.

&amp;lt;br /&amp;gt;

&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;span&amp;lt;/code&amp;gt; 태그에 색상 스타일링을 추가 한 경우&amp;lt;/p&amp;gt;

span 태그는 마크다운에서
&amp;lt;span style=&amp;quot;color : skyblue&amp;quot;&amp;gt;색상을 바꾸고 싶은 경우에도&amp;lt;/span&amp;gt; 사용합니다.

&amp;lt;br /&amp;gt;

&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;span&amp;lt;/code&amp;gt; 태그에 백그라운드 색상으로 하이라이팅 한 경우&amp;lt;/p&amp;gt;

span 태그는 백그라운드 색상을 통해
&amp;lt;span style=&amp;quot;background-color : #567&amp;quot;&amp;gt;하이라이팅&amp;lt;/span&amp;gt; 을 가할 수 있습니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;&lt;code&gt;span&lt;/code&gt; 태그에 어떠한 수정도 가하지 않은 경우&lt;/p&gt;

&lt;p&gt;span 태그는 마크다운에서 &lt;span&gt;색상을 바꾸고 싶은 경우에도&lt;/span&gt; 사용합니다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;span&lt;/code&gt; 태그에 색상 스타일링을 추가 한 경우&lt;/p&gt;

&lt;p&gt;span 태그는 마크다운에서 &lt;span style=&quot;color : skyblue&quot;&gt;색상을 바꾸고 싶은 경우에도&lt;/span&gt; 사용합니다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;span&lt;/code&gt; 태그에 백그라운드 색상으로 하이라이팅 한 경우&lt;/p&gt;

&lt;p&gt;span 태그는 백그라운드 색상을 통해 &lt;span style=&quot;background-color : #567&quot;&gt;하이라이팅&lt;/span&gt; 을 가할 수 있습니다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;위의 실제 렌더링 예시를 보다시피, &lt;code&gt;div&lt;/code&gt; 는 블럭 단위의 커스텀 블럭이며,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;span&lt;/code&gt; 은 인라인 텍스팅 기반의 커스텀 스타일링 도구라고 볼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;없음&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;header&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;문서나 특정 부분에 대한 헤더를 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이 태그 또한 &lt;code&gt;div&lt;/code&gt; 와 동일한 디폴트 css 를 가지지만,&lt;/p&gt;
&lt;p&gt;그 태그의 이름에서 중요한 의미를 가진다.&lt;/p&gt;
&lt;p&gt;즉, 이를 번역 해 보자면 &amp;quot;머릿말&amp;quot; 인데, 만약에 우리가 전체 글에서 특정 소제목을 정의하거나,&lt;/p&gt;
&lt;p&gt;태그의 의미로서 특정 블록의 헤더들을 가독성 좋게 만들어 주는 역할도 수행하며,&lt;/p&gt;
&lt;p&gt;css 스타일링 시 지정이 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div
  class=&amp;quot;article&amp;quot;
  style=&amp;quot;border : 2px solid #567; padding : 1rem; border-radius : 1rem&amp;quot;
&amp;gt;
  &amp;lt;header style=&amp;quot;background-color : #765&amp;quot;&amp;gt;
    &amp;lt;h3&amp;gt;아티클 블록의 머릿말을 선언하기&amp;lt;/h3&amp;gt;
  &amp;lt;/header&amp;gt;
  &amp;lt;p&amp;gt;아티클 내부에 선언되는 Paragraph&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;div
    class=&quot;article&quot;
    style=&quot;border : 2px solid #567; padding : 1rem; border-radius : 1rem&quot;
&gt;
    &lt;header style=&quot;background-color : #765&quot;&gt;
        &lt;h3&gt;아티클 블록의 머릿말을 선언하기&lt;/h3&gt;
    &lt;/header&gt;
    &lt;p&gt;아티클 내부에 선언되는 Paragraph&lt;/p&gt;
&lt;/div&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;header&amp;gt;&lt;/code&gt; 태그는 &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; 와 동일한 태그들의 컨테이너로서, 다른 태그를 담을 수 있다.&lt;/p&gt;
&lt;p&gt;헤더 태그는 주로 이러한 태그들을 담는다 :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;하나 이상의 헤딩 태그 (h1 ~ h6)&lt;/li&gt;
&lt;li&gt;로고나 아이콘&lt;/li&gt;
&lt;li&gt;저자의 정보&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;header&amp;gt;&lt;/code&gt; 태그는 HTML 문서 내에서 여러번 선언이 가능하다.&lt;/p&gt;
&lt;p&gt;그런데, 확실히 시멘틱(semantic) 태그라서 그런지,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;footer&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;address&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;또 다른 &lt;code&gt;&amp;lt;header&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이러한 요소에는 들어갈 수 없다고 한다.&lt;/p&gt;
&lt;p&gt;즉, 헤더의 역할을 확실히 지정해 주고 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;header&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;header {
  display: block;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;hgroup&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;헤딩, 그리고 관련된 컨텐츠를 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;내부의 설명을 보고 &lt;code&gt;&amp;lt;header&amp;gt;&lt;/code&gt; 와 다른 점이 무엇이 있나... 고민했다.&lt;/p&gt;
&lt;p&gt;즉, &lt;code&gt;&amp;lt;header&amp;gt;&lt;/code&gt; 태그는 아이콘이나 저자의 정보 등, 다양한 정보를 담는다.&lt;/p&gt;
&lt;p&gt;그러나, &lt;code&gt;&amp;lt;hgroup&amp;gt;&lt;/code&gt; 은 컨벤션 상 주로 &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; 와, &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; ~ &lt;code&gt;&amp;lt;h6&amp;gt;&lt;/code&gt; 까지의 헤딩 태그들을 담는다.&lt;/p&gt;
&lt;p&gt;즉, 단순 태그의 기능 상으로는 구별이 되지 않지만,&lt;/p&gt;
&lt;p&gt;다양한 컨텐츠가 늘어남에 따라, 헤딩을 묶고, 내부의 패러그래프들을 묶어 스타일링을 해야 할 때,&lt;/p&gt;
&lt;p&gt;혹은 상호작용이 필요 할 때, 사용하는 태그라고 한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;hgroup style=&amp;quot;background-color : #654; padding : 2rem; border-radius : 1rem&amp;quot;&amp;gt;
  &amp;lt;h3&amp;gt;hgroup 태그 내부에 들어간 h3 태그이다.&amp;lt;/h3&amp;gt;
  &amp;lt;p&amp;gt;헤딩과 Paragraph 를 묶어 정리 할 때 사용한다고 한다.&amp;lt;/p&amp;gt;
&amp;lt;/hgroup&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;hgroup style=&quot;background-color : #654; padding : 2rem; border-radius : 1rem&quot;&gt;
    &lt;h3&gt;hgroup 태그 내부에 들어간 h3 태그이다.&lt;/h3&gt;
    &lt;p&gt;헤딩과 Paragraph 를 묶어 정리 할 때 사용한다고 한다.&lt;/p&gt;
&lt;/hgroup&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;hgroup&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;hgroup {
  display: block;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;footer&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;문서나 특정 부분에 대한 아래 부분(하단)을 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;우리가 쿠팡이나, 네이버 등등 특정 사이트의 맨 하단까지 보면,&lt;/p&gt;
&lt;p&gt;항상 copyright 나, 사업자 정보, 저자 정보 등등이 작성되어 있다.&lt;/p&gt;
&lt;p&gt;이러한 정보들은 주로 &lt;code&gt;footer&lt;/code&gt; 에 작성된다.&lt;/p&gt;
&lt;p&gt;공식 문서에서 제시하는 태그들은,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;저자 정보&lt;/li&gt;
&lt;li&gt;저작권 정보&lt;/li&gt;
&lt;li&gt;연락 정보&lt;/li&gt;
&lt;li&gt;사이트맵&lt;/li&gt;
&lt;li&gt;현재 사이트 맨 위로 가게 해 주는 링크&lt;/li&gt;
&lt;li&gt;관련된 문서들&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div style=&amp;quot;padding : 1rem; border : 2px solid white; border-radius : 1rem&amp;quot;&amp;gt;
  &amp;lt;header style=&amp;quot;background-color : #567; padding : 2rem; border-radius : 1rem&amp;quot;&amp;gt;
    &amp;lt;h3&amp;gt;공담형 드디어 이번 편으로 마지막으로 HTML 시리즈를 종료해..&amp;lt;/h3&amp;gt;
    &amp;lt;p&amp;gt;그동안의 노고는 충분했나?..&amp;lt;/p&amp;gt;
  &amp;lt;/header&amp;gt;
  &amp;lt;div style=&amp;quot;padding : 1rem&amp;quot;&amp;gt;
    &amp;lt;p&amp;gt;
      공담형 씨는 &amp;lt;b&amp;gt;코딩크리쳐&amp;lt;/b&amp;gt; 라는 사이트에 HTML 태그 시리즈를 시작했었다.
    &amp;lt;/p&amp;gt;
    &amp;lt;p&amp;gt;
      이 당시 공담형 씨는 정식 스펙의 &amp;quot;모든&amp;quot; 태그들을 작성하자는 미친 생각을
      했다.
    &amp;lt;/p&amp;gt;
    &amp;lt;p&amp;gt;당시 공담형 씨는 간단하게 3편으로 종료될 줄 알았다고 전해..&amp;lt;/p&amp;gt;
    &amp;lt;p&amp;gt;현재는 매우 긴 길이의 장편으로 5편까지 마무리를 지었다고 전했다.&amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;footer
    style=&amp;quot;background-color : #333; padding : 1rem; border-radius : 0.5rem&amp;quot;
  &amp;gt;
    &amp;lt;p&amp;gt;&amp;lt;copyright&amp;gt;저자 : 공담순&amp;lt;/copyright&amp;gt;&amp;lt;/p&amp;gt;
    &amp;lt;p&amp;gt;
      &amp;lt;a target=&amp;quot;_blank&amp;quot; href=&amp;quot;mailto:rhdwhdals8@naver.com&amp;quot;
        &amp;gt;rhdwhdals8@naver.com&amp;lt;/a
      &amp;gt;
    &amp;lt;/p&amp;gt;
  &amp;lt;/footer&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;div style=&quot;padding : 1rem; border : 2px solid white; border-radius : 1rem&quot;&gt;
    &lt;header style=&quot;background-color : #567; padding : 2rem; border-radius : 1rem&quot;&gt;
        &lt;h3&gt;공담형 드디어 이번 편으로 마지막으로 HTML 시리즈를 종료해..&lt;/h3&gt;
        &lt;p&gt;그동안의 노고는 충분했나?..&lt;/p&gt;
    &lt;/header&gt;
    &lt;div style=&quot;padding : 1rem&quot;&gt;
        &lt;p&gt;공담형 씨는 &lt;b&gt;코딩크리쳐&lt;/b&gt; 라는 사이트에 HTML 태그 시리즈를 시작했었다.&lt;/p&gt;
        &lt;p&gt;이 당시 공담형 씨는 정식 스펙의 &quot;모든&quot; 태그들을 작성하자는 미친 생각을 했다.&lt;/p&gt;
        &lt;p&gt;당시 공담형 씨는 간단하게 3편으로 종료될 줄 알았다고 전해..&lt;/p&gt;
        &lt;p&gt;현재는 매우 긴 길이의 장편으로 5편까지 마무리를 지었다고 전했다.&lt;/p&gt;
    &lt;/div&gt;
    &lt;footer style=&quot;background-color : #333; padding : 1rem; border-radius : 0.5rem&quot;&gt;
        &lt;p&gt;&lt;copyright&gt;저자 : 공담순&lt;/copyright&gt;&lt;/p&gt;
        &lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;mailto:rhdwhdals8@naver.com&quot;&gt;rhdwhdals8@naver.com&lt;/a&gt;
    &lt;/footer&gt;
&lt;/div&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;footer&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;footer {
  display: block;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;main&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;문서의 주요 컨텐츠를 지정한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이 태그는 생각보다 중요한 의미를 가지는 태그이다.&lt;/p&gt;
&lt;p&gt;번역 그대로 &amp;quot;주요&amp;quot; 컨텐츠를 담는 태그이기 때문에,&lt;/p&gt;
&lt;p&gt;HTML 문서에는 하나의 &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt; 만 가진다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt; 태그는,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;article&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;aside&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;footer&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;header&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;태그들의 자식과 후손이 되면 안된다. (하위 태그이면 안된다는 의미)&lt;/p&gt;
&lt;p&gt;HTML 문서에서 &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt; 태그 내부의 컨텐츠는 유일해야 한다.&lt;/p&gt;
&lt;p&gt;문서 전반에 걸쳐 반복되는 태그들은 담지 않도록 주의해야 한다고 말한다.&lt;/p&gt;
&lt;p&gt;예시 : 사이드바, 링크, 저작권 정보, 사이트 로고들, 검색 폼 등등..&lt;/p&gt;
&lt;p&gt;즉, 페이지를 이동해도 동일한 컨텐츠를 가지고 있는 태그들은 사용하지 말라는 의미이다.&lt;/p&gt;
&lt;p&gt;이를 보면, &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt; 태그는 상위에 위치해야 하지만,&lt;/p&gt;
&lt;p&gt;사이트의 목적에 따라, 페이지 이동 시 달라지는 컨텐츠만 담으라는 의미이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- &amp;lt;body&amp;gt; --&amp;gt;

&amp;lt;main&amp;gt;
  &amp;lt;article class=&amp;quot;describe&amp;quot;&amp;gt;
    &amp;lt;h3&amp;gt;http란?&amp;lt;/h3&amp;gt;
    &amp;lt;p&amp;gt;HyperText Transper Protocol 의 약자이다.&amp;lt;/p&amp;gt;
  &amp;lt;/article&amp;gt;
  &amp;lt;article class=&amp;quot;describe&amp;quot;&amp;gt;
    &amp;lt;h3&amp;gt;컨테이너란 무엇을 의미하나?&amp;lt;/h3&amp;gt;
    &amp;lt;p&amp;gt;
      도메인에 따라 의미가 달라지는데, 서버를 담거나, 코드를 담는다는 의미로
      주로 나뉜다.
    &amp;lt;/p&amp;gt;
  &amp;lt;/article&amp;gt;
&amp;lt;/main&amp;gt;
&amp;lt;footer&amp;gt;
  &amp;lt;p&amp;gt;&amp;lt;copyright&amp;gt;저자 : 공담순&amp;lt;/copyright&amp;gt;&amp;lt;/p&amp;gt;
  &amp;lt;p&amp;gt;
    &amp;lt;a target=&amp;quot;_blank&amp;quot; href=&amp;quot;mailto:rhdwhdals8@naver.com&amp;quot;
      &amp;gt;rhdwhdals8@naver.com&amp;lt;/a
    &amp;gt;
  &amp;lt;/p&amp;gt;
&amp;lt;/footer&amp;gt;
&amp;lt;!-- &amp;lt;/body&amp;gt; --&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;!-- &lt;body&gt; --&gt;

&lt;main style=&quot;border : 2px solid white; padding : 2rem;&quot;&gt;
    &lt;article class=&quot;describe&quot;&gt;
        &lt;h3&gt;http란?&lt;/h3&gt;
        &lt;p&gt;HyperText Transper Protocol 의 약자이다.&lt;/p&gt;
    &lt;/article&gt;
    &lt;article class=&quot;describe&quot;&gt;
        &lt;h3&gt;컨테이너란 무엇을 의미하나?&lt;/h3&gt;
        &lt;p&gt;도메인에 따라 의미가 달라지는데, 서버를 담거나, 코드를 담는다는 의미로 주로 나뉜다.&lt;/p&gt;
    &lt;/article&gt;
&lt;/main&gt;
&lt;footer style=&quot;text-align : center&quot;&gt;
    &lt;p&gt;&lt;copyright&gt;저자 : 공담순&lt;/copyright&gt;&lt;/p&gt;
    &lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;mailto:rhdwhdals8@naver.com&quot;&gt;rhdwhdals8@naver.com&lt;/a&gt;
&lt;/footer&gt;
&lt;!-- &lt;/body&gt; --&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;main {
  display: block;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;section&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;문서 내부의 특정 부분을 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이것도 &lt;code&gt;div&lt;/code&gt; 와 동일한 속성을 가지고 있는데,&lt;/p&gt;
&lt;p&gt;어떤 컴포넌트를 작성 할 때, 특정 &amp;quot;구역&amp;quot; 으로 나눌 필요가 있을 때 사용한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;section&amp;gt;&lt;/code&gt; 태그는 &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; 와 동일하게 자유도가 굉장히 높은 태그라고 생각한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;section
  style=&amp;quot;background-color : #567; padding : 1rem; border-radius : 0.5rem&amp;quot;
&amp;gt;
  &amp;lt;h3&amp;gt;섹션 1&amp;lt;/h3&amp;gt;
  &amp;lt;p&amp;gt;컨테이너로서 대부분의 태그를 담을 수 있음.&amp;lt;/p&amp;gt;
&amp;lt;/section&amp;gt;

&amp;lt;section
  style=&amp;quot;background-color : #567; padding : 1rem; border-radius : 0.5rem&amp;quot;
&amp;gt;
  &amp;lt;h3&amp;gt;섹션 2&amp;lt;/h3&amp;gt;
  &amp;lt;p&amp;gt;컨테이너로서 대부분의 태그를 담을 수 있음.&amp;lt;/p&amp;gt;
&amp;lt;/section&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;section style=&quot;background-color : #567; padding : 1rem; border-radius : 0.5rem&quot;&gt;
    &lt;h3&gt;섹션 1&lt;/h3&gt;
    &lt;p&gt;컨테이너로서 대부분의 태그를 담을 수 있음.&lt;/p&gt;
&lt;/section&gt;

&lt;br/&gt;

&lt;section style=&quot;background-color : #765; padding : 1rem; border-radius : 0.5rem&quot;&gt;
    &lt;h3&gt;섹션 2&lt;/h3&gt;
    &lt;p&gt;컨테이너로서 대부분의 태그를 담을 수 있음.&lt;/p&gt;
&lt;/section&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;section&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;section {
  display: block;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;search&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;검색 부분을 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;사실, 태그 이름만 다르지, &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; 와 &lt;code&gt;&amp;lt;section&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;header&amp;gt;&lt;/code&gt; 태그들은&lt;/p&gt;
&lt;p&gt;기본 스타일이 동일하다. (&lt;code&gt;display : block;&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;하지만, 태그 이름에서 사용처가 구별되는 것 처럼, 이 태그 또한 동일하다.&lt;/p&gt;
&lt;p&gt;이 태그는 주로, 내부에 &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; 과 &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; 요소들을 넣는 데 집중한다.(컨벤션)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;button onclick=&amp;quot;getAllTags()&amp;quot;&amp;gt;모든 태그 정보 가져오기&amp;lt;/button&amp;gt; &amp;lt;br /&amp;gt;

&amp;lt;section style=&amp;quot;padding : 2rem; border : 2px solid white;&amp;quot;&amp;gt;
  &amp;lt;search&amp;gt;
    &amp;lt;form&amp;gt;
      어떤 태그를 원하시나요? &amp;lt;br /&amp;gt;
      &amp;lt;br /&amp;gt;
      &amp;lt;input
        id=&amp;quot;search-input&amp;quot;
        list=&amp;quot;search-datalist&amp;quot;
        style=&amp;quot;width : 16rem; padding : 0.25rem&amp;quot;
        placeholder=&amp;quot;위 버튼 클릭 시, 데이터를 불러 올 수 있습니다.&amp;quot;
      /&amp;gt;
      &amp;lt;br /&amp;gt;
      &amp;lt;br /&amp;gt;
      &amp;lt;input
        onclick=&amp;quot;gotoTag()&amp;quot;
        type=&amp;quot;button&amp;quot;
        value=&amp;quot;이동&amp;quot;
        style=&amp;quot;padding : 0.25rem;&amp;quot;
      /&amp;gt;
    &amp;lt;/form&amp;gt;
  &amp;lt;/search&amp;gt;
&amp;lt;/section&amp;gt;

&amp;lt;div id=&amp;quot;search-warn&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;

&amp;lt;datalist id=&amp;quot;search-datalist&amp;quot;&amp;gt; &amp;lt;/datalist&amp;gt;

&amp;lt;script&amp;gt;
  const datalistDOM = document.getElementById(&amp;quot;search-datalist&amp;quot;);
  const inputDOM = document.getElementById(&amp;quot;search-input&amp;quot;);
  const warnDOM = document.getElementById(&amp;quot;search-warn&amp;quot;);

  // 이 페이지에 선언된 모든 태그를 가져 온다.
  let allTags;

  function getAllTags() {
    allTags = document.querySelectorAll(&amp;quot;h3 &amp;gt; a&amp;quot;);

    let datalistInnerHTML = &amp;quot;&amp;quot;;

    for (let i = 0; i &amp;lt; allTags.length; i++) {
      const text = allTags[i].innerText;
      datalistInnerHTML += `&amp;lt;option value=&amp;quot;${text}&amp;quot;/&amp;gt;`;
    }

    datalistDOM.innerHTML = datalistInnerHTML;
  }

  function gotoTag() {
    const currInputText = inputDOM.value;

    let findTag;

    for (let i = 0; i &amp;lt; allTags.length; i++) {
      const currTagText = allTags[i].innerText;

      if (currTagText === currInputText) {
        findTag = allTags[i];
        break;
      }
    }

    if (!findTag) {
      warnDOM.innerHTML = `&amp;lt;p style=&amp;quot;color : red&amp;quot;&amp;gt;입력에 일치하는 태그가 없습니다.&amp;lt;/p&amp;gt;`;
    } else {
      warnDOM.innerHTML = &amp;quot;&amp;quot;;

      findTag.scrollIntoView({
        behavior: &amp;quot;smooth&amp;quot;,
        block: &amp;quot;center&amp;quot;,
      });
    }
  }
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;&lt;button onclick=&quot;getAllTags()&quot;&gt;모든 태그 정보 가져오기&lt;/button&gt; &lt;br/&gt;&lt;/p&gt;
&lt;section style=&quot;padding : 2rem; border : 2px solid white;&quot;&gt;
    &lt;search&gt;
        &lt;form&gt;
            어떤 태그를 원하시나요? &lt;br/&gt; &lt;br/&gt;
            &lt;input
                id=&quot;search-input&quot;
                list=&quot;search-datalist&quot;
                style=&quot;width : 16rem; padding : 0.25rem&quot;
                placeholder=&quot;위 버튼 클릭 시, 데이터를 불러 올 수 있습니다.&quot;
            /&gt; &lt;br/&gt; &lt;br/&gt;
            &lt;input
                onclick=&quot;gotoTag()&quot;
                type=&quot;button&quot;
                value=&quot;이동&quot;
                style=&quot;padding : 0.25rem;&quot;
            /&gt;
        &lt;/form&gt;
    &lt;/search&gt;
&lt;/section&gt;

&lt;div id=&quot;search-warn&quot;&gt;&lt;/div&gt;

&lt;datalist id=&quot;search-datalist&quot;&gt;
&lt;/datalist&gt;

&lt;script&gt;
const datalistDOM = document.getElementById(&quot;search-datalist&quot;);
const inputDOM = document.getElementById(&quot;search-input&quot;);
const warnDOM = document.getElementById(&quot;search-warn&quot;);

// 이 페이지에 선언된 모든 태그를 가져 온다.
let allTags;

function getAllTags() {
  allTags = document.querySelectorAll(&quot;h3 &gt; a&quot;);

  let datalistInnerHTML = &quot;&quot;;

  for(let i = 0; i &lt; allTags.length; i++) {
    const text = allTags[i].innerText;
    datalistInnerHTML += `&lt;option value=&quot;${text}&quot;/&gt;`;
  }

  datalistDOM.innerHTML = datalistInnerHTML;
}

function gotoTag() {
  const currInputText = inputDOM.value;

  let findTag;

  for(let i = 0; i &lt; allTags.length; i++) {
    const currTagText = allTags[i].innerText;

    if(currTagText === currInputText) {
      findTag = allTags[i];
      break;
    }
  }

  if(!findTag) {
    warnDOM.innerHTML = `&lt;p style=&quot;color : red&quot;&gt;입력에 일치하는 태그가 없습니다.&lt;/p&gt;`;
  } else {
    warnDOM.innerHTML = &quot;&quot;;

    findTag.scrollIntoView({
      behavior: &quot;smooth&quot;,
      block: &quot;center&quot;,
    });
  }
}
&lt;/script&gt;

&lt;br/&gt;

&lt;p&gt;위와 같이 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;전 편에도 내가 이러한 방식으로 구현했는데, 다듬어 지지 않은 부분이 많다.&lt;/p&gt;
&lt;p&gt;또한 블로그 내부 상으로 제약이 걸려있는 메서드들이 존재하여 우회했다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;만약에 이 글을 읽고 있으시다면, &lt;code&gt;lodash&lt;/code&gt; 라이브러리를 사용하여 최적하 하시길 바랍니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;search&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;search {
  display: block;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;article&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;아티클을 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;사실, 이 태그도 &lt;code&gt;display : block&lt;/code&gt; 을 디폴트로 가지고 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;section&lt;/code&gt;, &lt;code&gt;div&lt;/code&gt;, &lt;code&gt;article&lt;/code&gt; 셋 다 동일한 블럭 형태이다.&lt;/p&gt;
&lt;p&gt;그렇다면, &lt;code&gt;article&lt;/code&gt; 은 주로 어디에 사용해야 할까?&lt;/p&gt;
&lt;p&gt;우선, &lt;code&gt;article&lt;/code&gt; 을 번역하자면, &amp;quot;기사(뉴스)&amp;quot; 이다.&lt;/p&gt;
&lt;p&gt;즉, 주제가 정해진 일종의 문장이라고 보면 되겠다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;div&lt;/code&gt; 는 커스텀 블록을 의미하며,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;section&lt;/code&gt; 은 특정 구역 을 의미하며,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;article&lt;/code&gt; 은 특정 주제를 가진 글을 의미한다.&lt;/p&gt;
&lt;p&gt;따라서, 공식문서에서는 3 가지의 주요 목적을 가르킨다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;포럼 포스팅&lt;/li&gt;
&lt;li&gt;블로그 포스팅&lt;/li&gt;
&lt;li&gt;뉴스 이야기&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;article&amp;gt;&lt;/code&gt; 태그를 사용하되,&lt;/p&gt;
&lt;p&gt;자신의 웹 페이지 스타일에 맞는 css 를 구성하여 사용하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;article
  class=&amp;quot;article-group&amp;quot;
  style=&amp;quot;padding : 1rem; border : 3px solid white; border-radius : 2rem&amp;quot;
&amp;gt;
  &amp;lt;article class=&amp;quot;chrome&amp;quot;&amp;gt;
    &amp;lt;h3&amp;gt;크롬&amp;lt;/h3&amp;gt;
    &amp;lt;p&amp;gt;크롬은 가장 대표적으로 사용되는 구글의 검색엔진 프로그램입니다.&amp;lt;/p&amp;gt;
  &amp;lt;/article&amp;gt;
  &amp;lt;article class=&amp;quot;naver whare&amp;quot;&amp;gt;
    &amp;lt;h3&amp;gt;네이버 웨일&amp;lt;/h3&amp;gt;
    &amp;lt;p&amp;gt;네이버 웨일은 네이버를 자주 사용하는 사람에게 유용합니다.&amp;lt;/p&amp;gt;
  &amp;lt;/article&amp;gt;
&amp;lt;/article&amp;gt;

&amp;lt;script&amp;gt;
  const group = document.querySelector(&amp;quot;.article-group&amp;quot;);

  const articles = group.children;

  for (let i = 0; i &amp;lt; articles.length; i++) {
    const node = articles[i];
    node.style = `background-color : #567`;
  }
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;article class=&quot;article-group&quot; style=&quot;padding : 1rem; border : 3px solid white; border-radius : 2rem&quot;&gt;
    &lt;article class=&quot;chrome&quot;&gt;
        &lt;h3&gt;크롬&lt;/h3&gt;
        &lt;p&gt;크롬은 가장 대표적으로 사용되는 구글의 검색엔진 프로그램입니다.&lt;/p&gt;
    &lt;/article&gt;
    &lt;article class=&quot;naver whare&quot;&gt;
        &lt;h3&gt;네이버 웨일&lt;/h3&gt;
        &lt;p&gt;네이버 웨일은 네이버를 자주 사용하는 사람에게 유용합니다.&lt;/p&gt;
    &lt;/article&gt;
&lt;/article&gt;

&lt;script&gt;
const group = document.querySelector(&quot;.article-group&quot;);

const articles = group.children;

// 전역 스타일 css 를 할 수 없으니, 예시로 JS 로 스타일링하는 법.
for(let i = 0; i &lt; articles.length; i++) {
  const node = articles[i];
  node.style = `background-color : #567; padding : 1rem; margin-top : 1rem; margin-bottom : 1rem; border-radius : 0.5rem;`;
}
&lt;/script&gt;

&lt;br/&gt;

&lt;p&gt;위와 같은 형식으로 사용 할 수 있다.&lt;/p&gt;
&lt;p&gt;안타깝게도, 블로그 전체 스타일 적용은 이 글 뿐만 아니라,&lt;/p&gt;
&lt;p&gt;블로그 내의 모든 페이지, 글에 적용되기 때문에,&lt;/p&gt;
&lt;p&gt;JS 로 어떻게 스타일을 간단하게 추가할 수 있는지 작성 해 보았다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;article&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;article {
  display: block;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;aside&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;페이지 컨텐츠 외 부분을 정의한다.(외부 컨텐츠)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;aside&amp;gt;&lt;/code&gt; 태그 또한, &lt;code&gt;display : block&lt;/code&gt; 을 디폴트로 가지고 있다.&lt;/p&gt;
&lt;p&gt;이 태그의 용도는, 현재 작성되고 있는 글의 흐름에서, 많이, 혹은 적게 벗어난 글을 작성하는 데 사용된다.&lt;/p&gt;
&lt;p&gt;완전히 벗어났다기 보다는, 주변 내용과 간접적으로 연관되어야 한다는 것이다.&lt;/p&gt;
&lt;p&gt;공식문서에서 주어진 팁은, &lt;code&gt;&amp;lt;aside&amp;gt;&lt;/code&gt; 태그가 주로 사이드바로 배치된다는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div
  class=&amp;quot;aside-div&amp;quot;
  style=&amp;quot;display : flex; padding : 0.5rem; border : 2px solid white;&amp;quot;
&amp;gt;
  &amp;lt;!-- 여기에 스크립트로 &amp;quot;모든 태그&amp;quot; 로 이동할 수 있는 사이드바를 만들어 보기(동적) --&amp;gt;
  &amp;lt;aside
    class=&amp;quot;side-bar&amp;quot;
    style=&amp;quot;width : 4rem; padding : 0.25rem; border : 2px solid skyblue;&amp;quot;
  &amp;gt;
    &amp;lt;!-- 모든 태그를 읽어 내부 html 을 구성 할 예정 --&amp;gt;
  &amp;lt;/aside&amp;gt;
  &amp;lt;!-- 메인 컨텐츠 --&amp;gt;
  &amp;lt;article class=&amp;quot;aside-article&amp;quot;&amp;gt;
    &amp;lt;h3&amp;gt;메인 콘텐츠 형식&amp;lt;/h3&amp;gt;
    &amp;lt;hr /&amp;gt;
    &amp;lt;div class=&amp;quot;aside-contents&amp;quot;&amp;gt;
      &amp;lt;p&amp;gt;이러한 스타일로 사이드바를 보며, 주요 컨텐츠를 볼 수 있어용&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;
        주로 하나의 문단을 정의 할 때, &amp;lt;code&amp;gt;p&amp;lt;/code&amp;gt; 로 쉽게 나누기도 가능하죠.
      &amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/article&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;script&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;div
    class=&quot;aside-div&quot;
    style=&quot;display : flex; padding : 0.5rem; border : 2px solid white;&quot;
&gt;
    &lt;!-- 여기에 스크립트로 &quot;모든 태그&quot; 로 이동할 수 있는 사이드바를 만들어 보기(동적) --&gt;
    &lt;aside
        class=&quot;side-bar&quot;
        style=&quot;
            width : 7rem;
            padding : 0.25rem;
            border : 2px solid skyblue;
            border-radius : 0.25rem;
        &quot;
    &gt;
        &lt;!-- 모든 태그를 읽어 내부 html 을 구성 할 예정 --&gt;
        &lt;ol&gt;
            &lt;li&gt;사이드 1&lt;/li&gt;
            &lt;li&gt;사이드 2&lt;/li&gt;
            &lt;li&gt;사이드 3&lt;/li&gt;
            &lt;li&gt;사이드 4&lt;/li&gt;
            &lt;li&gt;사이드 5&lt;/li&gt;
            &lt;li&gt;사이드 6&lt;/li&gt;
            &lt;li&gt;사이드 7&lt;/li&gt;
            &lt;li&gt;사이드 8&lt;/li&gt;
            &lt;li&gt;사이드 9&lt;/li&gt;
            &lt;li&gt;사이드 10&lt;/li&gt;
            &lt;li&gt;사이드 11&lt;/li&gt;
            &lt;li&gt;사이드 12&lt;/li&gt;
        &lt;/ol&gt;
    &lt;/aside&gt;
    &lt;!-- 메인 컨텐츠 --&gt;
    &lt;article
        class=&quot;aside-article&quot;
        style=&quot;padding : 2rem&quot;
    &gt;
        &lt;h3&gt;메인 콘텐츠 형식&lt;/h3&gt;
        &lt;hr/&gt;
        &lt;div class=&quot;aside-contents&quot;&gt;
            &lt;p&gt;이러한 스타일로 사이드바를 보며, 주요 컨텐츠를 볼 수 있어용&lt;/p&gt;
            &lt;p&gt;주로 하나의 문단을 정의 할 때, &lt;code&gt;p&lt;/code&gt; 로 쉽게 나누기도 가능하죠.&lt;/p&gt;
        &lt;/div&gt;
    &lt;/article&gt;
&lt;/div&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;aside&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;aside {
  display: block;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;details&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;유저가 보거나, 유저로부터 감춰진 추가적인 세부사항을 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;먼저, 내부에 &lt;code&gt;&amp;lt;summary&amp;gt;&lt;/code&gt; 라는 태그가 들어가는데,&lt;/p&gt;
&lt;p&gt;이 태그는 토글이 닫혀 있는 상태이더라도, 외부에 보이는 상태이다.&lt;/p&gt;
&lt;p&gt;그러나, &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt; 의 토글을 연다면, &lt;code&gt;&amp;lt;summary&amp;gt;&lt;/code&gt; 태그 외에 작성된 모든 컨텐츠들이 보인다.&lt;/p&gt;
&lt;p&gt;일종의 문장 토글 버튼이라고 생각이 든다. (&lt;code&gt;&amp;lt;summary&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt; 는 연결되어 있는 태그이다.)&lt;/p&gt;
&lt;p&gt;예시를 보면, 바로 이해할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;details&amp;gt;
  &amp;lt;summary&amp;gt;Summary 태그 텍스트&amp;lt;/summary&amp;gt;
  &amp;lt;p&amp;gt;
    이것이 토글 버튼을 누르면 보이는 텍스트이며, &amp;lt;code&amp;gt;details&amp;lt;/code&amp;gt; 블록이다.
  &amp;lt;/p&amp;gt;
&amp;lt;/details&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;details&gt;
    &lt;summary&gt;Summary 태그 텍스트&lt;/summary&gt;
    &lt;p&gt;이것이 토글 버튼을 누르면 보이는 텍스트이며, &lt;code&gt;details&lt;/code&gt; 블록이다.&lt;/p&gt;
&lt;/details&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt; 특수 속성 :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;open&lt;/code&gt; : 이 속성을 이용하여 JS 로 열고 닫기를 수행할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;details {
  display: block;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;dialog&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;대화 박스나 특정 창을 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;굉장히 유용한 태그라고 생각하는데,&lt;/p&gt;
&lt;p&gt;모달 창을 띄우거나, 대화 창을 만드는 데에 특화된 태그이다.&lt;/p&gt;
&lt;p&gt;내부 속성 &lt;code&gt;open&lt;/code&gt; 을 통해서 드러내거나, 속성을 없애서 보이지 않게 만들 수 있다.&lt;/p&gt;
&lt;p&gt;재밌는 것이, 마치 중앙에 뜬금없이 뜨는 것 처럼 보이더라도,&lt;/p&gt;
&lt;p&gt;현재 사이에 있는 block 의 중앙에서 모달 창이 시작하는 것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div&amp;gt;
  &amp;lt;p&amp;gt;텍스트 1&amp;lt;/p&amp;gt;
  &amp;lt;p&amp;gt;텍스트 2&amp;lt;/p&amp;gt;
  &amp;lt;p&amp;gt;텍스트 3&amp;lt;/p&amp;gt;
  &amp;lt;dialog id=&amp;quot;dialog-dom&amp;quot; style=&amp;quot;width : 70%; height : 5rem&amp;quot;&amp;gt;
    이게 보인다면, 대화 창, 모달 창 이 떠 있는 상태입니다.
  &amp;lt;/dialog&amp;gt;
  &amp;lt;p&amp;gt;텍스트 4&amp;lt;/p&amp;gt;
  &amp;lt;p&amp;gt;텍스트 5&amp;lt;/p&amp;gt;
  &amp;lt;button onclick=&amp;quot;dialogToggle()&amp;quot;&amp;gt;모달 창 토글&amp;lt;/button&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;script&amp;gt;
  // 처음에는 띄워지지 않았음.
  let isOn = false;
  const dialogDOM = document.getElementById(&amp;quot;dialog-dom&amp;quot;);

  function dialogToggle() {
    if (isOn) {
      dialogDOM.open = false;
      isOn = !isOn;
    } else {
      dialogDOM.open = true;
      isOn = !isOn;
    }
  }
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;div&gt;
    &lt;p&gt;텍스트 1&lt;/p&gt;
    &lt;p&gt;텍스트 2&lt;/p&gt;
    &lt;p&gt;텍스트 3&lt;/p&gt;
    &lt;dialog
        id=&quot;dialog-dom&quot;
        style=&quot;width : 70%; height : 5rem&quot;
    &gt;이게 보인다면, 대화 창, 모달 창 이 떠 있는 상태입니다.
    &lt;/dialog&gt;
    &lt;p&gt;텍스트 4&lt;/p&gt;
    &lt;p&gt;텍스트 5&lt;/p&gt;
    &lt;button onclick=&quot;dialogToggle()&quot;&gt;모달 창 토글&lt;/button&gt;
&lt;/div&gt;

&lt;script&gt;
// 처음에는 띄워지지 않았음.
let isOn = false;
const dialogDOM = document.getElementById(&quot;dialog-dom&quot;);

function dialogToggle() {
  if(isOn) {
    dialogDOM.open = false;
    isOn = !isOn;
  } else {
    dialogDOM.open = true;
    isOn = !isOn;
  }
}
&lt;/script&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;없음&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;summary&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt; &lt;strong&gt;요소에 대한 가시적(보여지는) 헤딩을 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이미 거의 직전에 &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt; 에 대해서 다루었는데,&lt;/p&gt;
&lt;p&gt;토글 버튼을 통해서 블록을 보여지게 만들 수 있는 태그였다.&lt;/p&gt;
&lt;p&gt;그런데, 토글 버튼과 함께 보여지는 텍스트가 존재했다.&lt;/p&gt;
&lt;p&gt;이 텍스트가 바로 &lt;code&gt;&amp;lt;summary&amp;gt;&lt;/code&gt; 이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;details&amp;gt;
  &amp;lt;summary&amp;gt;
    이 텍스트는 토글이 닫힌 상태에서 보여지는 &amp;lt;code&amp;gt;details&amp;lt;/code&amp;gt; 텍스트이다.
  &amp;lt;/summary&amp;gt;
  &amp;lt;p&amp;gt;토글이 열렸기에, 이 부분이 보인다.&amp;lt;/p&amp;gt;
&amp;lt;/details&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;details&gt;
    &lt;summary&gt;
        이 텍스트는 토글이 닫힌 상태에서 보여지는 &lt;code&gt;details&lt;/code&gt; 텍스트이다.
    &lt;/summary&gt;
    &lt;p&gt;
        토글이 열렸기에, 이 부분이 보인다.
    &lt;/p&gt;
&lt;/details&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;summary&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;summary {
  display: block;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;data&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;주어진 컨텐츠에, 기계가 읽을 수 있는 값을 추가한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;단순한 텍스트에 특정 데이터를 부여하기 위해 사용하는 태그이다.&lt;/p&gt;
&lt;p&gt;그런데, 현재 Safari 에서 지원하지 않는 것을 보아 사용하지 않는 태그일 확률이 매우 높다.&lt;/p&gt;
&lt;p&gt;따라서, W3Schools 의 예시를 가져오겠다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;ul&amp;gt;
  &amp;lt;li&amp;gt;&amp;lt;data value=&amp;quot;21053&amp;quot;&amp;gt;Cherry Tomato&amp;lt;/data&amp;gt;&amp;lt;/li&amp;gt;
  &amp;lt;li&amp;gt;&amp;lt;data value=&amp;quot;21054&amp;quot;&amp;gt;Beef Tomato&amp;lt;/data&amp;gt;&amp;lt;/li&amp;gt;
  &amp;lt;li&amp;gt;&amp;lt;data value=&amp;quot;21055&amp;quot;&amp;gt;Snack Tomato&amp;lt;/data&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;data value=&quot;21053&quot;&gt;Cherry Tomato&lt;/data&gt;&lt;/li&gt;
  &lt;li&gt;&lt;data value=&quot;21054&quot;&gt;Beef Tomato&lt;/data&gt;&lt;/li&gt;
  &lt;li&gt;&lt;data value=&quot;21055&quot;&gt;Snack Tomato&lt;/data&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;data&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;없음&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Category - Meta Info (메타 정보 태그)&lt;/h2&gt;
&lt;h3&gt;&lt;code&gt;head&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;문서에 대한 정보를 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; 태그는 메타데이터에 대한 컨테이너이다.&lt;/p&gt;
&lt;p&gt;그리고, &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; 태그 내부에 선언되며, &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; 가 선언되기 전에 시작되고 끝난다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; 태그에 들어가는 메타데이터들은 말 그대로&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;문서의 제목&lt;/li&gt;
&lt;li&gt;문자열 집합&lt;/li&gt;
&lt;li&gt;스타일&lt;/li&gt;
&lt;li&gt;스크립트&lt;/li&gt;
&lt;li&gt;또 다른 메타 정보&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;를 정의한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; 태그에는 이러한 요소들이 들어간다 :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; : 문서 제목&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; : 문서의 스타일을 지정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;base&amp;gt;&lt;/code&gt; : 문서에 선언될 &lt;code&gt;href&lt;/code&gt; 와 &lt;code&gt;target&lt;/code&gt; 기본 속성을 선언한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; : 문서와 연결된 또 다른 문서 (스타일 문서가 대부분임)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; : 메타데이터 선언&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; : JS 코드를 선언&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;noscript&amp;gt;&lt;/code&gt; : JS 코드가 지원되지 않는 기기에서 이 코드를 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;현재 &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; 태그는 이미 블로그 문서에 선언되어 있어서, W3Schools 예시롤 보자.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;!-- 경로만 선언될 모든 리소스, 혹은 하이퍼링크의 접두어로 추가된다. --&amp;gt;
    &amp;lt;base href=&amp;quot;https://www.w3schools.com/&amp;quot; target=&amp;quot;_blank&amp;quot; /&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;img src=&amp;quot;images/stickman.gif&amp;quot; width=&amp;quot;24&amp;quot; height=&amp;quot;39&amp;quot; alt=&amp;quot;stickman&amp;quot; /&amp;gt;
    &amp;lt;a href=&amp;quot;tags/tag_base.asp&amp;quot;&amp;gt;HTML base Tag&amp;lt;/a&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
  &lt;div&gt;
    &lt;img src=&quot;https://www.w3schools.com/images/stickman.gif&quot; width=&quot;24&quot; height=&quot;39&quot; alt=&quot;stickman&quot; /&gt;
    &lt;a href=&quot;https://www.w3schools.com/tags/tag_base.asp&quot;&gt;HTML base Tag&lt;/a&gt;
&lt;/div&gt;

&lt;br/&gt;

&lt;p&gt;위의 예시에서 스틱맨이 보이는 이유는,&lt;/p&gt;
&lt;p&gt;현재 모든 소스의 경로를 &amp;quot;전체 경로&amp;quot; 로 만들었기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;head {
    display : none;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;meta&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;HTML 문서에 대한 메타데이터를 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이 문서가 어떻게 인코딩되어야 할 지 정의하고,&lt;/p&gt;
&lt;p&gt;검색 엔진에서 어떤 키워드로 검색 되는지를 정의하고,&lt;/p&gt;
&lt;p&gt;저자는 누구이며, 반응형 웹에서 어떻게 크기를 형성해야 하는지 정의한다.&lt;/p&gt;
&lt;p&gt;SEO 에서 굉장히 중요한 태그이다.&lt;/p&gt;
&lt;p&gt;메타데이터는 브라우저에서 사용되며, viewport 를 통해 반응형 웹에서 알맞게 크기를 조정한다.&lt;/p&gt;
&lt;p&gt;이 또한 &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; 내부에 들어가기 때문에, W3Schools 의 예제를 살펴보자.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;/&amp;gt;
    &amp;lt;!-- 웹 페이지의 설명 --&amp;gt;
    &amp;lt;meta name=&amp;quot;description&amp;quot; content=&amp;quot;무료 웹 Tutorials&amp;quot; /&amp;gt;
    &amp;lt;!-- 검색 엔진에 대한 키워드를 정의 --&amp;gt;
    &amp;lt;meta name=&amp;quot;keywords&amp;quot; content=&amp;quot;HTML, CSS, JavaScript&amp;quot; /&amp;gt;
    &amp;lt;!-- 페이지의 저자를 정의 --&amp;gt;
    &amp;lt;meta name=&amp;quot;author&amp;quot; content=&amp;quot;공담형&amp;quot; /&amp;gt;
    &amp;lt;!-- 모든 기기에서 웹사이트가 보기 좋아지게 만들려면 이렇게 세팅. --&amp;gt;
    &amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width, initial-scale=1.0&amp;quot; /&amp;gt;
&amp;lt;/head&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;특히, viewport 설정은 브라우저에게 특정 지침(스케일링을 어떻게 하고..) 을 내린다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;width=device-width&lt;/code&gt; 부분은 웹 페이지의 너비를 기기의 너비로 맞춘다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;initial-scale=1.0&lt;/code&gt; 부분은 브라우저가 처음 로드되었을 때의 페이지 줌 레벨로 초기화 한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;없음`&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; 태그의 특수 속성들 :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;charset&lt;/code&gt; : HTML 문서에 대한 인코딩 문자를 지정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;content&lt;/code&gt; : &lt;code&gt;http-equiv&lt;/code&gt; 혹은 &lt;code&gt;name&lt;/code&gt; 속성에 관련된 값을 지정한다 (텍스트)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;http-equiv&lt;/code&gt; : 컨텐츠 속성의 정보와 값에 대한 HTTP 헤더를 제공한다.&lt;ul&gt;
&lt;li&gt;content-security-policy&lt;/li&gt;
&lt;li&gt;content-type&lt;/li&gt;
&lt;li&gt;default-style&lt;/li&gt;
&lt;li&gt;refresh&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;name&lt;/code&gt; : 메타 정보의 이름을 지정&lt;ul&gt;
&lt;li&gt;application-name&lt;/li&gt;
&lt;li&gt;author&lt;/li&gt;
&lt;li&gt;description&lt;/li&gt;
&lt;li&gt;generator&lt;/li&gt;
&lt;li&gt;keywords&lt;/li&gt;
&lt;li&gt;viewport&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;없음.&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;base&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;문서의 모든 상대적 URL 에 대한 &amp;quot;기본&amp;quot; URL 과 대상을 지정한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;문서 내 존재하는 모든 상대 경로 URL 에 대한 기본 URL 과 타겟을 지정한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;base&amp;gt;&lt;/code&gt; 태그는 &lt;code&gt;href&lt;/code&gt;, &lt;code&gt;target&lt;/code&gt; 속성 둘 중 하나는 무조건 가지거나, 둘 다 가진다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;base&amp;gt;&lt;/code&gt; 요소는 문서에 단 하나만 존재하는데, 무조건 &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; 요소 내부에 들어가야 한다.&lt;/p&gt;
&lt;p&gt;W3Schools 예시를 보자.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;head&amp;gt;
  &amp;lt;base href=&amp;quot;https://www.w3schools.com/&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;
&amp;lt;/head&amp;gt;

&amp;lt;body&amp;gt;
&amp;lt;img src=&amp;quot;images/stickman.gif&amp;quot; width=&amp;quot;24&amp;quot; height=&amp;quot;39&amp;quot; alt=&amp;quot;Stickman&amp;quot;&amp;gt;
&amp;lt;a href=&amp;quot;tags/tag_base.asp&amp;quot;&amp;gt;HTML base Tag&amp;lt;/a&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위에서 이미지는 &lt;code&gt;https://www.w3schools.com/images/stickman.fig&lt;/code&gt; 경로를 가지게 되며,&lt;/p&gt;
&lt;p&gt;하이퍼링크의 참고경로는, &lt;code&gt;https://www.w3schools.com/tags/tag_base.asp&lt;/code&gt; 경로이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;위의 코드를 그대로 표현 할 수는 없다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;base&amp;gt;&lt;/code&gt; 특수 속성들 :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;href&lt;/code&gt; : 페이지 내부의 모든 상대 경로들에 대한 기본 경로를 지정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;target&lt;/code&gt; : 페이지의 모든 form 이나, 하이퍼링크에 대한 기본 타겟을 지정한다.&lt;ul&gt;
&lt;li&gt;_blank&lt;/li&gt;
&lt;li&gt;_parent&lt;/li&gt;
&lt;li&gt;_self&lt;/li&gt;
&lt;li&gt;_top&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;base&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;없다&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Category - Programming (프로그래밍 관련 태그들)&lt;/h2&gt;
&lt;h3&gt;&lt;code&gt;script&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;클라이언트 측의 JS 스크립트를 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;내가 HTML 태그 모두 다르기 시리즈를 작성하면서 정말 많이 사용한 태그이다.&lt;/p&gt;
&lt;p&gt;단순한 정적 HTML 문서 내부에서 표현되는 데이터를 동적으로 만들거나,&lt;/p&gt;
&lt;p&gt;유저와의 인터랙션을 추가할 수 있게 만들어 준다.&lt;/p&gt;
&lt;p&gt;그리고, 웹 개발 시 &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; 내부에 &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; 가 대부분 들어가는데,&lt;/p&gt;
&lt;p&gt;이러한 태그들을 이해하기 위해서 속성을 이해하는 것이 필수적이라고 생각된다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div id=&amp;quot;script-sample&amp;quot;&amp;gt;0&amp;lt;/div&amp;gt;

&amp;lt;button onclick=&amp;quot;plus()&amp;quot;&amp;gt;+1&amp;lt;/button&amp;gt; &amp;lt;button onclick=&amp;quot;minus()&amp;quot;&amp;gt;-1&amp;lt;/button&amp;gt;

&amp;lt;script&amp;gt;
const sampleDOM = document.getElementById(&amp;quot;script-sample&amp;quot;);

function plus() {
  const currVal = sampleDOM.innerText;

  let result = parseInt(currVal) + 1;

  sampleDOM.innerText = result;
}

function minus() {
  const currVal = sampleDOM.innerText;

  let result = parseInt(currVal) - 1;

  sampleDOM.innerText = result;
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;br/&gt;

&lt;div id=&quot;script-sample&quot;&gt;0&lt;/div&gt;

&lt;p&gt;&lt;button onclick=&quot;plus()&quot;&gt;+1&lt;/button&gt; &lt;button onclick=&quot;minus()&quot;&gt;-1&lt;/button&gt;&lt;/p&gt;
&lt;script&gt;
const sampleDOM = document.getElementById(&quot;script-sample&quot;);

function plus() {
  const currVal = sampleDOM.innerText;

  let result = parseInt(currVal) + 1;

  sampleDOM.innerText = result;
}

function minus() {
  const currVal = sampleDOM.innerText;

  let result = parseInt(currVal) - 1;

  sampleDOM.innerText = result;
}
&lt;/script&gt;

&lt;br/&gt;

&lt;p&gt;위에서 작성한 스크립트는 내부에 작성되었는데, 나는 예제를 보여주거나,&lt;/p&gt;
&lt;p&gt;&amp;quot;아주 간단하고, 다시는 반복 사용되지 않을 메서드와 변수&amp;quot; 를 사용할 때 괜찮다고 생각한다.&lt;/p&gt;
&lt;p&gt;보통은 &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; 내부에 &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; 를 넣어서 전역 변수, 메서드를 선언하여&lt;/p&gt;
&lt;p&gt;반응형 웹 사이트를 구축하는 편이다.&lt;/p&gt;
&lt;p&gt;또한, 브라우저 자체에서 다양한 라이브러리를 CDN 으로 가져오기 위해 사용하기도 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; 특수 속성들 (중요) :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;async&lt;/code&gt; : 이 스크립트가 페이지가 파싱되는 동안, 병렬로 다운로드 할 지 지정하고, 가능 한 빨리 실행한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;crossorigin&lt;/code&gt; : HTTP CORS 요청의 모드를 세팅한다.&lt;ul&gt;
&lt;li&gt;&lt;code&gt;anonymous&lt;/code&gt; : 누구나&lt;/li&gt;
&lt;li&gt;&lt;code&gt;use-credentials&lt;/code&gt; : 중요 정보 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;defer&lt;/code&gt; : 이 스크립트가 페이지 파싱과 동시에 다운로드(병렬)하고, 페이지 파싱 후 실행한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;integrity&lt;/code&gt; : 브라우저는 조작된 소스를 절대로 로드하지 않는다는 것을 보장한다.&lt;ul&gt;
&lt;li&gt;filehash : 파일 해시 텍스트&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nomodule&lt;/code&gt; : ES2015 모듈을 지원하는 브라우저에서 스크립트가 실행되지 않도록 지정&lt;ul&gt;
&lt;li&gt;True&lt;/li&gt;
&lt;li&gt;False&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;referrerpolicy&lt;/code&gt; : 스크립트를 요청 할 때 보낼 참조 정보를 지정한다.&lt;ul&gt;
&lt;li&gt;&lt;code&gt;no-referrer&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;no-referrer-when-downgrade&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;origin&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;origin-when-cross-origin&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;same-origin&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;strict-origin&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;strict-origin-when-cross-origin&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;unsafe-url&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src&lt;/code&gt; : 외부 스크립트 파일의 URL 을 의미한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;type&lt;/code&gt; : 스크립트의 미디어 타입을 지정한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;script {
    display : none;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;noscript&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;클라이언트 측 JS 스크립트를 지원하지 않는 유저들을 위한 대체적인 컨텐츠를 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;스크립트가 지원되지 않는 오래된 기기를 위해서이기도 하지만,&lt;/p&gt;
&lt;p&gt;스크립트 실행 자체를 브라우저에서 중지시킨 사용자들을 위해 작성하는 태그이기도 하다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;script&amp;gt;console.log(&amp;quot;스크립트가 브라우저에서 실행된다&amp;quot;);&amp;lt;/script&amp;gt;
&amp;lt;noscript&amp;gt;스크립트가 실행되지 않습니다.&amp;lt;/noscript&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;noscript&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;없음&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;embed&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;외부 리소스에 대한 컨테이너를 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;더이상 사용되지 않는 태그라고 한다.&lt;/p&gt;
&lt;p&gt;그 이유가, 대부분의 브라우저가 더 이상 Java 애플릿과, 관련 플러그인을 지원하지 않기 때문이다.&lt;/p&gt;
&lt;p&gt;또한, 현재 ActiveX 가 사용되지 않기 때문이다.&lt;/p&gt;
&lt;p&gt;대신하여 사용할 수 있는 태그들이, &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;audio&amp;gt;&lt;/code&gt; 태그로 대체한다.&lt;/p&gt;
&lt;p&gt;한번 W3Schools 예시를 보자.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;embed type=&amp;quot;image/jpg&amp;quot; src=&amp;quot;pic_trulli.jpg&amp;quot; width=&amp;quot;300&amp;quot; height=&amp;quot;200&amp;quot;&amp;gt;

&amp;lt;embed type=&amp;quot;text/html&amp;quot; src=&amp;quot;snippet.html&amp;quot; width=&amp;quot;500&amp;quot; height=&amp;quot;200&amp;quot;&amp;gt;

&amp;lt;embed type=&amp;quot;video/webm&amp;quot; src=&amp;quot;video.mp4&amp;quot; width=&amp;quot;400&amp;quot; height=&amp;quot;300&amp;quot;&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;브라우저에서 지원하지 않는다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;embed&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;embed:focus {
    outline : none;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;object&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;외부 리소스에 대한 컨테이너를 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이것도 현재 사용되지 않는데, 위의 &lt;code&gt;&amp;lt;embed&amp;gt;&lt;/code&gt; 태그와 동일한 상황이다.&lt;/p&gt;
&lt;p&gt;이미지, iframe, 영상, 오디오 등등을 넣는 데 사용했다고 한다.&lt;/p&gt;
&lt;p&gt;이는 위와 동일하기 때문에, 생략하도록 하겠다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;param&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;객체에 대한 파라미터를 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;원래 바로 위에서 말한 &lt;code&gt;&amp;lt;object&amp;gt;&lt;/code&gt; 에 사용되는 태그라던데,&lt;/p&gt;
&lt;p&gt;왜 이 태그는 사장되지 않았는가? 궁금했다.&lt;/p&gt;
&lt;p&gt;왜 그런가 했더니, 문서의 id 탐색을 통해서, 선언한 &lt;code&gt;&amp;lt;param&amp;gt;&lt;/code&gt; 의 값을 가져올 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;param id=&amp;quot;param-name&amp;quot; value=&amp;quot;param-Result&amp;quot;&amp;gt;

&amp;lt;div id=&amp;quot;param-result&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;

&amp;lt;script&amp;gt;
const paramDOM = document.getElementById(&amp;quot;param-name&amp;quot;);
const resultDOM = document.getElementById(&amp;quot;param-result&amp;quot;);

resultDOM.innerText = paramDOM.value;

&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;param id=&quot;param-name&quot; value=&quot;param-Result&quot;&gt;

&lt;div id=&quot;param-result&quot;&gt;&lt;/div&gt;

&lt;script&gt;
const paramDOM = document.getElementById(&quot;param-name&quot;);
const resultDOM = document.getElementById(&quot;param-result&quot;);

resultDOM.innerText = paramDOM.value;

&lt;/script&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;param&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;없음&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;h2&gt;HTML 태그 장편을 마무리하며 배운 것. (드디어 드디어 ㅠㅠ)&lt;/h2&gt;
&lt;p&gt;정말 많기도 하던 HTML의 &amp;quot;모든&amp;quot; 태그를 작성했다.&lt;/p&gt;
&lt;p&gt;와..... 정말 생각보다 HTML5 정식 스펙의 태그들이 많다는 것을 뼈에서부터 알게 되었다.&lt;/p&gt;
&lt;p&gt;마무리를 하며, 여기까지 다룬 모든 HTML 태그들의 카테고리 리스트는 이러하다 :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Basic HTML - 기본 HTML 태그&lt;/li&gt;
&lt;li&gt;Formatting - 형식 태그&lt;/li&gt;
&lt;li&gt;Forms and Input - 폼과 입력&lt;/li&gt;
&lt;li&gt;Frames - iframe 하나&lt;/li&gt;
&lt;li&gt;Images - 이미지 와 캔버스, svg 관련 태그&lt;/li&gt;
&lt;li&gt;Audio / Video - 오디오와 비디오, 소스 태그&lt;/li&gt;
&lt;li&gt;Links - 하이퍼링크 관련 태그&lt;/li&gt;
&lt;li&gt;Lists - ul, ol 및 리스트 관련 태그&lt;/li&gt;
&lt;li&gt;Tables - 테이블과 내부 구성 태그&lt;/li&gt;
&lt;li&gt;Styles and Semantics - 스타일 태그와 컨벤션으로 지정된 시멘틱 태그들&lt;/li&gt;
&lt;li&gt;Meta Info - 웹 사이트에 선언되는 메타 정보 태그들&lt;/li&gt;
&lt;li&gt;Programming - HTML 내부 프로그래밍 관련 태그들.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;태그의 정의, 실제 코드 구성 예제, 코드의 렌더링 결과, 특수 속성, 기본 css 까지 모두 다루었다.&lt;/p&gt;
&lt;p&gt;이 과정에서 긴 글을 가진 아티클을 총 5개를 만들게 되었지만,&lt;/p&gt;
&lt;p&gt;결과적으로 단일 HTML 문서에서 유저와의 인터랙션, 렌더링, 텍스팅을 위해&lt;/p&gt;
&lt;p&gt;어떤 태그들이 탄생했으며, 역사 속으로 사라졌는지 알 수 있었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;나는 NPM 과, Node.js 엔진, &lt;code&gt;package.json&lt;/code&gt;, &lt;code&gt;tsconfig.json&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;JavaScript&lt;/code&gt;, &lt;code&gt;TypeScript&lt;/code&gt; 에 대해서 이미 공부를 마쳤다.&lt;/p&gt;
&lt;p&gt;이제는 React 를 공부해도 기술적 부채가 발생 할 일은 거의 없을 것이다... 라고 생각하긴 한다.&lt;/p&gt;
&lt;p&gt;(React 를 구성하는 또 다른 라이브러리의 관계를 이해 못하는 것은 아마 조그마한 부채가 되지 않을까?)&lt;/p&gt;
&lt;p&gt;이제, React 를 제대로 다시 다뤄보려고 한다.&lt;/p&gt;
&lt;p&gt;만약에 이 글을 읽어주신 분이 있다면 너무 감사드리다고 말씀드리고 싶다.&lt;/p&gt;
&lt;h2&gt;END&lt;/h2&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;h2&gt;참고 사이트&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;W3Schools HTML Element Reference&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.w3schools.com/TAGS/ref_byfunc.asp&quot;&gt;https://www.w3schools.com/TAGS/ref_byfunc.asp&lt;/a&gt;&lt;/p&gt;</description>
      <category>Web-Server/웹 지식</category>
      <category>footer</category>
      <category>header</category>
      <category>html</category>
      <category>main</category>
      <category>meta</category>
      <category>noscript</category>
      <category>script</category>
      <category>Style</category>
      <category>태그</category>
      <category>태그 의미</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/222</guid>
      <comments>https://codecreature.tistory.com/222#entry222comment</comments>
      <pubDate>Sun, 1 Jun 2025 23:39:43 +0900</pubDate>
    </item>
    <item>
      <title>HTML 문서 정식 태그 &amp;quot;전부&amp;quot; 공부하기 - 4편 (코드 작성 및 구현까지)</title>
      <link>https://codecreature.tistory.com/221</link>
      <description>&lt;h2&gt;제목 : HTML 문서 태그 공부하기 - 4편&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;3편 주소&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/219&quot;&gt;https://codecreature.tistory.com/219&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;이 글을 작성하는 이유&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;웹 페이지 개발에 있어 포텐셜을 높이기 위해, 웹 제작에 사용되는 태그들을 &amp;quot;모두&amp;quot; 익히고자&lt;/p&gt;
&lt;p&gt;시작한 프로젝트이다. 카테고리별로 나누어진 태그들을 &amp;quot;모두&amp;quot; 배우고 이를 실현 해 보며,&lt;/p&gt;
&lt;p&gt;나중에 어떻게 사용할지도 생각하며 작성하고 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;솔직히 말해서&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이렇게까지 오래 걸릴 줄은 몰랐다.. 한 길어야 3 편 정도를 작성하겠지? 했는데&lt;/p&gt;
&lt;p&gt;아마 현재 작성하고 있는 4 편까지 포함해서, 5 편까지 나올 것 같다..&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;현재까지 작성 완료한 카테고리 목록은&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Basic HTML - 기본적인 HTML 태그들&lt;/li&gt;
&lt;li&gt;Formatting - 형식 조작 태그들&lt;/li&gt;
&lt;li&gt;Forms and Input - 폼과 입력 태그들&lt;/li&gt;
&lt;li&gt;Frames - 프레임&lt;/li&gt;
&lt;li&gt;Images - 이미지 관련 태그&lt;/li&gt;
&lt;li&gt;Audio / Video - 오디오와 비디오 관련 태그&lt;/li&gt;
&lt;li&gt;Links - 문서 링크 관련 태그&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;그리고, &lt;strong&gt;아직 남은 카테고리 목록은&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Lists - 배열 태그&lt;/li&gt;
&lt;li&gt;Tables - 테이블 형식 태그&lt;/li&gt;
&lt;li&gt;Styles and Semantics - 스타일 태그와 시멘틱 태그들&lt;/li&gt;
&lt;li&gt;Meta Info - 문서의 메타데이터 선언 관련 태그들&lt;/li&gt;
&lt;li&gt;Programming - 문서 프로그래밍 관련 태그들&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;p&gt;지금까지 &amp;quot;모든&amp;quot; HTML 태그들에 대해서 다룬 기억을 더듬어 보자면,&lt;/p&gt;
&lt;p&gt;내가 지금 짐작하는 세밀함보다 조금만 더 디테일하게 작성한다면, 6 편 까지 나올 것 같다.&lt;/p&gt;
&lt;p&gt;따라서, JS 라이브러리나 JS 코드에 의해 쉽게 대체 될 수 있는 속성이나 기능 설명들은&lt;/p&gt;
&lt;p&gt;조금씩 생략을 하면서 구현 할 생각이다.&lt;/p&gt;
&lt;p&gt;예를 들어서, 현재 작성중인 글은 블로그 내부의 아티클로 들어가는데,&lt;/p&gt;
&lt;p&gt;동일한 문서에 &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; 를 다시 넣을 수는 없다.&lt;/p&gt;
&lt;p&gt;이러한 경우, 간단한 예제를 제시하고, &amp;quot;실제 구현 결과&amp;quot; 는 생략 할 것이다.&lt;/p&gt;
&lt;br/&gt;


&lt;h2&gt;Category - Lists (배열)&lt;/h2&gt;
&lt;h3&gt;&lt;code&gt;menu&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;비-정렬 배열 대체 태그를 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;보통, 우리는 HTML 태그에서 크게 2 가지 종류의 리스트(배열) 을 사용한다.&lt;/p&gt;
&lt;p&gt;첫 번째는 &amp;quot;정렬 배열&amp;quot; 이며, 두 번째는 &amp;quot;비-정렬 배열&amp;quot; 이다.&lt;/p&gt;
&lt;p&gt;여기서 &amp;quot;비-정렬 대체 태그&amp;quot; 라는 것은, &lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt;(UnOrdered List) 와 동일한 역할을 하지만,&lt;/p&gt;
&lt;p&gt;역할을 구분해야 할 때, 사용할 수 있는 태그라는 것이다. (동일한 CSS 스타일을 지니기에.)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;menu&amp;gt;
    &amp;lt;li&amp;gt;Markdown - 마크다운&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;Latex - 라텍스&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;HTML&amp;lt;/li&amp;gt;
&amp;lt;/menu&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;menu&gt;
    &lt;li&gt;Markdown - 마크다운&lt;/li&gt;
    &lt;li&gt;Latex - 라텍스&lt;/li&gt;
    &lt;li&gt;HTML&lt;/li&gt;
&lt;/menu&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;menu&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;menu {
    display : block;
    list-style-type: disc;
    margin-block-start : 1em;
    margin-block-end : 1em;
    margin-inline-start : 0px;
    margin-inline-end : 0px;
    padding-inline-start : 40px;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기서 &lt;code&gt;&amp;lt;menu&amp;gt;&lt;/code&gt; 의 태그는 블록 형식으로 렌더링 된다.&lt;/p&gt;
&lt;p&gt;이 때, 내부에 들어가는 &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt; 의 스타일을 정할 수 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;margin-block-start&lt;/code&gt; : 위 엘리먼트와의 마진 설정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;margin-block-end&lt;/code&gt; : 아래 엘리먼트와의 마진 설정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;margin-inline-start&lt;/code&gt; : 내부 리스트가 왼쪽에서 몇 픽셀 떨어져 있는지.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;margin-inline-end&lt;/code&gt; : 내부 리스트가 오른쪽에서 몇 픽셀 떨어져야 하는지.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;padding-inline-start&lt;/code&gt; : 리스트 엘리먼트 전부가 왼쪽에서 몇 픽셀 떨어진 위치에서 시작하는지.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;즉, &lt;code&gt;margin-inline-start&lt;/code&gt; 의 렌더링 결과는,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;padding-inline-start&lt;/code&gt; + &lt;code&gt;margin-inline-start&lt;/code&gt; 인 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;ul&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;비-정렬 배열을 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;순서가 정해져 있는 리스트가 아니라, 순서가 중요하지 않은 요소들을 나열하기 위해 정의하는 태그이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;p&amp;gt;기본적인 UnOrdered 배열&amp;lt;/p&amp;gt;

&amp;lt;ul&amp;gt;
    &amp;lt;li&amp;gt;요소 1&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;요소 2&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;요소 3&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;

&amp;lt;p&amp;gt;비정렬 스타일링 요소가 원형인 경우&amp;lt;/p&amp;gt;

&amp;lt;ul style=&amp;quot;list-style-type:circle&amp;quot;&amp;gt;
    &amp;lt;li&amp;gt;요소 1&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;요소 2&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;요소 3&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;

&amp;lt;p&amp;gt;비정렬 스타일링 요소가 원형이며, 색상이 차 있는 형태&amp;lt;/p&amp;gt;

&amp;lt;ul style=&amp;quot;list-style-type:disc&amp;quot;&amp;gt;
    &amp;lt;li&amp;gt;요소 1&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;요소 2&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;요소 3&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;

&amp;lt;p&amp;gt;비정렬 스타일링 요소가 정사각형인 경우&amp;lt;/p&amp;gt;

&amp;lt;ul style=&amp;quot;list-style-type:square&amp;quot;&amp;gt;
    &amp;lt;li&amp;gt;요소 1&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;요소 2&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;요소 3&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;

&amp;lt;ul&amp;gt;
    &amp;lt;li&amp;gt;요소1&amp;lt;/li&amp;gt;
    &amp;lt;ul&amp;gt;
        &amp;lt;li&amp;gt;내부 요소1&amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;내부 요소2&amp;lt;/li&amp;gt;
    &amp;lt;/ul&amp;gt;
    &amp;lt;li&amp;gt;요소2&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;

&amp;lt;p&amp;gt;기본 스타일인 상태에서, 비-정렬 리스트 내부에 다시 비-정렬 리스트가 들어갈 경우&amp;lt;/p&amp;gt;

&amp;lt;ul&amp;gt;
    &amp;lt;li&amp;gt;요소1&amp;lt;/li&amp;gt;
    &amp;lt;ul&amp;gt;
        &amp;lt;li&amp;gt;내부 요소1&amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;내부 요소2&amp;lt;/li&amp;gt;
    &amp;lt;/ul&amp;gt;
    &amp;lt;li&amp;gt;요소2&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;기본적인 UnOrdered 배열&lt;/p&gt;

&lt;ul&gt;
    &lt;li&gt;요소 1&lt;/li&gt;
    &lt;li&gt;요소 2&lt;/li&gt;
    &lt;li&gt;요소 3&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;비정렬 스타일링 요소가 원형인 경우&lt;/p&gt;

&lt;ul style=&quot;list-style-type:circle&quot;&gt;
    &lt;li&gt;요소 1&lt;/li&gt;
    &lt;li&gt;요소 2&lt;/li&gt;
    &lt;li&gt;요소 3&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;비정렬 스타일링 요소가 원형이며, 색상이 차 있는 형태&lt;/p&gt;

&lt;ul style=&quot;list-style-type:disc&quot;&gt;
    &lt;li&gt;요소 1&lt;/li&gt;
    &lt;li&gt;요소 2&lt;/li&gt;
    &lt;li&gt;요소 3&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;비정렬 스타일링 요소가 정사각형인 경우&lt;/p&gt;

&lt;ul style=&quot;list-style-type:square&quot;&gt;
    &lt;li&gt;요소 1&lt;/li&gt;
    &lt;li&gt;요소 2&lt;/li&gt;
    &lt;li&gt;요소 3&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;기본 스타일인 상태에서, 비-정렬 리스트 내부에 다시 비-정렬 리스트가 들어갈 경우&lt;/p&gt;

&lt;ul&gt;
    &lt;li&gt;요소1&lt;/li&gt;
    &lt;ul&gt;
        &lt;li&gt;내부 요소1&lt;/li&gt;
        &lt;li&gt;내부 요소2&lt;/li&gt;
    &lt;/ul&gt;
    &lt;li&gt;요소2&lt;/li&gt;
&lt;/ul&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;ul {
    display : block;
    list-style-type : disc;
    margin-top : 1em;
    margin-bottom : 1em;
    margin-left : 0;
    margin-right : 0;
    padding-left : 40px
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt; 태그의 대체제로서 &lt;code&gt;&amp;lt;menu&amp;gt;&lt;/code&gt; 를 사용할 수 있다고 해서 동일한 디폴트 스타일을 가질 줄 알았는데,&lt;/p&gt;
&lt;p&gt;생각 외로 내부 설명자가 달라서 당황했다.&lt;/p&gt;
&lt;p&gt;사실, 기존에 사용하던 스타일링 속성에 익숙해서 더 좋긴 하다.&lt;/p&gt;
&lt;p&gt;결국, 이러한 속성들의 의미를 생각 해 보면, &lt;code&gt;ul&lt;/code&gt;, &lt;code&gt;menu&lt;/code&gt; 둘 다 의미는 동일하긴 하다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;ol&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;정렬된 배열을 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;기본 스타일은 숫자 증강 형태로 나타난다.&lt;/p&gt;
&lt;p&gt;그렇다면, &lt;code&gt;ul&lt;/code&gt;, &lt;code&gt;ol&lt;/code&gt; 의 차이는 무엇일까?&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ul&lt;/code&gt; 은 위에서 보다시피, 네모, 원형 등등 동일한 형태의 이미지 혹은 컨텐츠를 지닌다.&lt;/p&gt;
&lt;p&gt;그러나, &lt;code&gt;ol&lt;/code&gt; 은 내부의 &lt;code&gt;li&lt;/code&gt; 만큼 증강시킨 식별자를 부여해야 한다.&lt;/p&gt;
&lt;p&gt;숫자, 알파벳, 로마숫자 등등이 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;p&amp;gt;기본 스타일&amp;lt;/p&amp;gt;

&amp;lt;ol&amp;gt;
    &amp;lt;li&amp;gt;요소1&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;요소2&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;요소3&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;

&amp;lt;p&amp;gt;시작 숫자를 15 로 지정&amp;lt;/p&amp;gt;

&amp;lt;ol start=&amp;quot;15&amp;quot;&amp;gt;
    &amp;lt;li&amp;gt;요소1&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;요소2&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;요소3&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;

&amp;lt;p&amp;gt;시작 숫자는 25 지만, 거꾸로 진행&amp;lt;/p&amp;gt;

&amp;lt;ol start=&amp;quot;25&amp;quot; reversed&amp;gt;
    &amp;lt;li&amp;gt;요소1&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;요소2&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;요소3&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;

&amp;lt;p&amp;gt;type 요소로 직접 &amp;#39;1&amp;#39; 숫자 을 선언&amp;lt;/p&amp;gt;

&amp;lt;ol type=&amp;quot;1&amp;quot;&amp;gt;
    &amp;lt;li&amp;gt;요소1&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;요소2&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;요소3&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;

&amp;lt;p&amp;gt;타입 요소로 직접 &amp;#39;A&amp;#39; 대문자 선언&amp;lt;/p&amp;gt;

&amp;lt;ol type=&amp;quot;A&amp;quot;&amp;gt;
    &amp;lt;li&amp;gt;요소1&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;요소2&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;요소3&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;

&amp;lt;p&amp;gt;타입 요소로 &amp;#39;a&amp;#39; 소문자 선언&amp;lt;/p&amp;gt;

&amp;lt;ol type=&amp;quot;a&amp;quot;&amp;gt;
    &amp;lt;li&amp;gt;요소1&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;요소2&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;요소3&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;

&amp;lt;p&amp;gt;로마 표기법 대문자 선언&amp;lt;/p&amp;gt;

&amp;lt;ol type=&amp;quot;I&amp;quot;&amp;gt;
    &amp;lt;li&amp;gt;요소1&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;요소2&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;요소3&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;

&amp;lt;p&amp;gt;로마 표기법 소문자 선언&amp;lt;/p&amp;gt;

&amp;lt;ol type=&amp;quot;i&amp;quot;&amp;gt;
    &amp;lt;li&amp;gt;요소1&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;요소2&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;요소3&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;

&amp;lt;p&amp;gt;스타일로 직접 &amp;quot;로마 대문자&amp;quot; 지정&amp;lt;/p&amp;gt;

&amp;lt;ol style=&amp;quot;list-style-type:upper-roman&amp;quot;&amp;gt;
    &amp;lt;li&amp;gt;요소1&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;요소2&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;요소3&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;

&amp;lt;p&amp;gt;스타일로 직접 &amp;quot;알파벳 소문자&amp;quot; 지정&amp;lt;/p&amp;gt;

&amp;lt;ol style=&amp;quot;list-style-type:lower-alpha&amp;quot;&amp;gt;
    &amp;lt;li&amp;gt;요소1&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;요소2&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;요소3&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;기본 스타일&lt;/p&gt;

&lt;ol&gt;
    &lt;li&gt;요소1&lt;/li&gt;
    &lt;li&gt;요소2&lt;/li&gt;
    &lt;li&gt;요소3&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;시작 숫자를 15 로 지정&lt;/p&gt;

&lt;ol start=&quot;15&quot;&gt;
    &lt;li&gt;요소1&lt;/li&gt;
    &lt;li&gt;요소2&lt;/li&gt;
    &lt;li&gt;요소3&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;시작 숫자는 25 지만, 거꾸로 진행&lt;/p&gt;

&lt;ol start=&quot;25&quot; reversed&gt;
    &lt;li&gt;요소1&lt;/li&gt;
    &lt;li&gt;요소2&lt;/li&gt;
    &lt;li&gt;요소3&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;type 요소로 직접 '1' 숫자 을 선언&lt;/p&gt;

&lt;ol type=&quot;1&quot;&gt;
    &lt;li&gt;요소1&lt;/li&gt;
    &lt;li&gt;요소2&lt;/li&gt;
    &lt;li&gt;요소3&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;타입 요소로 직접 'A' 대문자 선언&lt;/p&gt;

&lt;ol type=&quot;A&quot;&gt;
    &lt;li&gt;요소1&lt;/li&gt;
    &lt;li&gt;요소2&lt;/li&gt;
    &lt;li&gt;요소3&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;타입 요소로 'a' 소문자 선언&lt;/p&gt;

&lt;ol type=&quot;a&quot;&gt;
    &lt;li&gt;요소1&lt;/li&gt;
    &lt;li&gt;요소2&lt;/li&gt;
    &lt;li&gt;요소3&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;로마 표기법 대문자 선언&lt;/p&gt;

&lt;ol type=&quot;I&quot;&gt;
    &lt;li&gt;요소1&lt;/li&gt;
    &lt;li&gt;요소2&lt;/li&gt;
    &lt;li&gt;요소3&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;로마 표기법 소문자 선언&lt;/p&gt;

&lt;ol type=&quot;i&quot;&gt;
    &lt;li&gt;요소1&lt;/li&gt;
    &lt;li&gt;요소2&lt;/li&gt;
    &lt;li&gt;요소3&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;스타일로 직접 &quot;로마 대문자&quot; 지정&lt;/p&gt;

&lt;ol style=&quot;list-style-type:upper-roman&quot;&gt;
    &lt;li&gt;요소1&lt;/li&gt;
    &lt;li&gt;요소2&lt;/li&gt;
    &lt;li&gt;요소3&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;스타일로 직접 &quot;알파벳 소문자&quot; 지정&lt;/p&gt;

&lt;ol style=&quot;list-style-type:lower-alpha&quot;&gt;
    &lt;li&gt;요소1&lt;/li&gt;
    &lt;li&gt;요소2&lt;/li&gt;
    &lt;li&gt;요소3&lt;/li&gt;
&lt;/ol&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;ol&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;ol {
    display : block;
    list-style-type : decimal;
    margin-top : 1em;
    margin-bottom : 1em;
    margin-left : 0;
    margin-right : 0;
    padding-left : 40px;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;li&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;배열 내부에 들어갈 아이템을 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;menu&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;ol&amp;gt;&lt;/code&gt; 모두, 이 태그가 없다면 아이템을 표현 할 수 없다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;ol&amp;gt;
    &amp;lt;li style=&amp;quot;list-style-type:lower-alpha&amp;quot;&amp;gt;요소1&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;요소1&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;요소1&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;

&amp;lt;ul&amp;gt;
    &amp;lt;li style=&amp;quot;list-style-type:square&amp;quot;&amp;gt;요소1&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;요소1&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;요소1&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;ol&gt;
    &lt;li style=&quot;list-style-type:lower-alpha&quot;&gt;요소1&lt;/li&gt;
    &lt;li&gt;요소1&lt;/li&gt;
    &lt;li&gt;요소1&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
    &lt;li style=&quot;list-style-type:square&quot;&gt;요소1&lt;/li&gt;
    &lt;li&gt;요소1&lt;/li&gt;
    &lt;li&gt;요소1&lt;/li&gt;
&lt;/ul&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;li {
    display : list-item;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;dl&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;설명 배열(목록) 을 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;위에서 정의한 &lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;ol&amp;gt;&lt;/code&gt; 과 비슷한 형태이긴 하나,&lt;/p&gt;
&lt;p&gt;이 태그는 정보 구조화에 좀 더 초점이 맞춰져 있다.&lt;/p&gt;
&lt;p&gt;Q&amp;amp;A, FAQ, 쌍을 이루는 Key, Value 에 알맞는 태그이다.&lt;/p&gt;
&lt;p&gt;여기서 &lt;code&gt;&amp;lt;dl&amp;gt;&lt;/code&gt; 은, &lt;strong&gt;Description List&lt;/strong&gt; 의 약자로,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;dt&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;dd&amp;gt;&lt;/code&gt; 를 내부에 사용하게 된다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;dl&amp;gt;
    &amp;lt;dt&amp;gt;설명할 용어&amp;lt;/dt&amp;gt;
    &amp;lt;dd&amp;gt;용어를 설명&amp;lt;/dd&amp;gt;
    &amp;lt;dt&amp;gt;Markdown이란?&amp;lt;/dt&amp;gt;
    &amp;lt;dd&amp;gt;HTML 용어들을 쉽게 작성하게 해 주는 일종의 문법&amp;lt;/dd&amp;gt;
&amp;lt;/dl&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;dl&gt;
    &lt;dt&gt;설명할 용어&lt;/dt&gt;
    &lt;dd&gt;용어를 설명&lt;/dd&gt;
    &lt;dt&gt;Markdown이란?&lt;/dt&gt;
    &lt;dd&gt;HTML 용어들을 쉽게 작성하게 해 주는 일종의 문법&lt;/dd&gt;
&lt;/dl&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;dl&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;dl {
    display : block;
    margin-top : 1em;
    margin-bottom : 1em;
    margin-left : 0;
    margin-right : 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;dt&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;설명 할 목록에서 용어, 혹은 이름을 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;위에서 보았듯이, &lt;code&gt;&amp;lt;dl&amp;gt;&lt;/code&gt; 은 내부 설명 리스트를 정의하는 일종의 구역이라고 볼 수 있고,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;dt&amp;gt;&lt;/code&gt; 는, 어떤 용어를 설명하거나, 어떤 문장 혹은 이름을 정의한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;dt&amp;gt;&lt;/code&gt; 는, Description Term 의 약자로, Description Name 으로 불리기도 한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;dl&amp;gt;
    &amp;lt;dt&amp;gt;설명 용어, 혹은 설명할 이름&amp;lt;/dt&amp;gt;
    &amp;lt;dd&amp;gt;용어, 혹은 이름을 설명&amp;lt;/dd&amp;gt;
    &amp;lt;dt&amp;gt;dt 란?&amp;lt;/dt&amp;gt;
    &amp;lt;dd&amp;gt;Discription Term 의 약자이다.&amp;lt;/dd&amp;gt;
&amp;lt;/dl&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;dl&gt;
    &lt;dt&gt;설명 용어, 혹은 설명할 이름&lt;/dt&gt;
    &lt;dd&gt;용어, 혹은 이름을 설명&lt;/dd&gt;
    &lt;dt&gt;dt 란?&lt;/dt&gt;
    &lt;dd&gt;Discription Term 의 약자이다.&lt;/dd&gt;
&lt;/dl&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;dt&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;dt {
    display : block;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;dd&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;설명 목록에 있는 용어/이름에 대한 설명&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;위의 설명을 보면 추리 할 수 있듯이,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Description list of Discription&lt;/code&gt; 이 아닐까 생각된다.&lt;/p&gt;
&lt;p&gt;즉, 이 과정에서 &lt;code&gt;dd&lt;/code&gt; 가 되지 않았을까? 조심껏 추리해 본다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;dl&amp;gt;
    &amp;lt;dt&amp;gt;
         정말로 모든 HTML 태그를 다룹니까?
    &amp;lt;/dt&amp;gt;
    &amp;lt;dd&amp;gt;
        예 죽을 맛입니다..
    &amp;lt;/dd&amp;gt;
&amp;lt;/dl&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;dl&gt;
    &lt;dt&gt;
         정말로 모든 HTML 태그를 다룹니까?
    &lt;/dt&gt;
    &lt;dd&gt;
        예 죽을 맛입니다..
    &lt;/dd&gt;
&lt;/dl&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;dd&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;dd {
    display : block;
    margin-left : 40px;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h2&gt;category - Tables (테이블)&lt;/h2&gt;
&lt;h3&gt;&lt;code&gt;table&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;테이블을 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;드디어 HTML 태그에서 굉장히 중요하다고 할 수 있는 table 에 대해 다루게 되었다.&lt;/p&gt;
&lt;p&gt;table 의 형식은 일종의 Excel 형식 처럼 보인다.&lt;/p&gt;
&lt;p&gt;이에 대한 길이와 지정 속성, 지정 값을 이용하여 동적인 테이블을 구현 할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt; 태그는 여러가지 태그를 내부에서 사용할 수 있는데,&lt;/p&gt;
&lt;p&gt;아주 기본적인 요소 :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;tr&amp;gt;&lt;/code&gt; : 테이블 내부의 row(줄)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;th&amp;gt;&lt;/code&gt; : 테이블의 줄 내부의 Head 요소 (맨 위 속성으로 자주 사용)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;td&amp;gt;&lt;/code&gt; : 테이블 줄 내부의 하나의 요소&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;그 외 집합 요소와 텍스팅 요소 :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;thead&amp;gt;&lt;/code&gt; : 테이블의 헤더 부분을 그룹핑&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;tbody&amp;gt;&lt;/code&gt; : 테이블의 몸체 부분을 그룹핑&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;tfoot&amp;gt;&lt;/code&gt; : 테이블의 footer(밑 부분) 을 그룹핑&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;caption&amp;gt;&lt;/code&gt; : 테이블의 캡션 (제목) 을 정의 - 텍스트 표시됨&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;colgroup&amp;gt;&lt;/code&gt; : 내부 &lt;code&gt;&amp;lt;col&amp;gt;&lt;/code&gt; 을 그룹핑 - 컬럼 별 스타일링 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;p&amp;gt;아주 간단한 Table 예시&amp;lt;/p&amp;gt;

&amp;lt;table&amp;gt;
    &amp;lt;tr&amp;gt;
        &amp;lt;th&amp;gt;X&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;+&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;Y&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;X + Y&amp;lt;/th&amp;gt;
    &amp;lt;/tr&amp;gt;
    &amp;lt;tr&amp;gt;
        &amp;lt;td&amp;gt;1&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;+&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;2&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;3&amp;lt;/td&amp;gt;
    &amp;lt;/tr&amp;gt;
    &amp;lt;tr&amp;gt;
        &amp;lt;td&amp;gt;10&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;+&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;12&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;22&amp;lt;/td&amp;gt;
    &amp;lt;/tr&amp;gt;
    &amp;lt;tr&amp;gt;
        &amp;lt;td&amp;gt;첫 번째 수&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;기호&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;두 번째 수&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;결과&amp;lt;/td&amp;gt;
    &amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;

&amp;lt;p&amp;gt;그룹핑 태그를 통해 각 태그를 구별&amp;lt;/p&amp;gt;

&amp;lt;table&amp;gt;
    &amp;lt;thead&amp;gt;
        &amp;lt;th&amp;gt;X&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;+&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;Y&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;X + Y&amp;lt;/th&amp;gt;
    &amp;lt;/thead&amp;gt;
    &amp;lt;tbody&amp;gt;
        &amp;lt;tr&amp;gt;
            &amp;lt;td&amp;gt;1&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;+&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;2&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;3&amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
        &amp;lt;tr&amp;gt;
            &amp;lt;td&amp;gt;10&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;+&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;12&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;22&amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
    &amp;lt;/tbody&amp;gt;
    &amp;lt;tfoot&amp;gt;
        &amp;lt;td&amp;gt;첫 번째 수&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;기호&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;두 번째 수&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;결과&amp;lt;/td&amp;gt;
    &amp;lt;/tfoot&amp;gt;
&amp;lt;/table&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;아주 간단한 Table 예시&lt;/p&gt;

&lt;table&gt;
    &lt;tr&gt;
        &lt;th&gt;X&lt;/th&gt;
        &lt;th&gt;+&lt;/th&gt;
        &lt;th&gt;Y&lt;/th&gt;
        &lt;th&gt;X + Y&lt;/th&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;+&lt;/td&gt;
        &lt;td&gt;2&lt;/td&gt;
        &lt;td&gt;3&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;10&lt;/td&gt;
        &lt;td&gt;+&lt;/td&gt;
        &lt;td&gt;12&lt;/td&gt;
        &lt;td&gt;22&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;첫 번째 수&lt;/td&gt;
        &lt;td&gt;기호&lt;/td&gt;
        &lt;td&gt;두 번째 수&lt;/td&gt;
        &lt;td&gt;결과&lt;/td&gt;
    &lt;/tr&gt;
&lt;/table&gt;

&lt;p&gt;그룹핑 태그를 통해 각 태그를 구별&lt;/p&gt;

&lt;table&gt;
    &lt;thead&gt;
        &lt;th&gt;X&lt;/th&gt;
        &lt;th&gt;+&lt;/th&gt;
        &lt;th&gt;Y&lt;/th&gt;
        &lt;th&gt;X + Y&lt;/th&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
        &lt;tr&gt;
            &lt;td&gt;1&lt;/td&gt;
            &lt;td&gt;+&lt;/td&gt;
            &lt;td&gt;2&lt;/td&gt;
            &lt;td&gt;3&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;10&lt;/td&gt;
            &lt;td&gt;+&lt;/td&gt;
            &lt;td&gt;12&lt;/td&gt;
            &lt;td&gt;22&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tbody&gt;
    &lt;tfoot&gt;
        &lt;td&gt;첫 번째 수&lt;/td&gt;
        &lt;td&gt;기호&lt;/td&gt;
        &lt;td&gt;두 번째 수&lt;/td&gt;
        &lt;td&gt;결과&lt;/td&gt;
    &lt;/tfoot&gt;
&lt;/table&gt;

&lt;p&gt;헤더 단일 요소에 `colspan` 속성으로 칸을 차지하기&lt;/p&gt;

&lt;table style=&quot;border-collapse : separate;&quot;&gt;
    &lt;thead&gt;
        &lt;th&gt;X&lt;/th&gt;
        &lt;th&gt;+&lt;/th&gt;
        &lt;th&gt;Y&lt;/th&gt;
        &lt;th&gt;X + Y&lt;/th&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
        &lt;tr&gt;
            &lt;td&gt;1&lt;/td&gt;
            &lt;td&gt;+&lt;/td&gt;
            &lt;td&gt;2&lt;/td&gt;
            &lt;td&gt;3&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;10&lt;/td&gt;
            &lt;td&gt;+&lt;/td&gt;
            &lt;td&gt;12&lt;/td&gt;
            &lt;td&gt;22&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tbody&gt;
    &lt;tfoot&gt;
        &lt;td colspan=&quot;3&quot; style=&quot;text-align:center&quot;&gt;x 와 y 의 함수&lt;/td&gt;
        &lt;td&gt;결과&lt;/td&gt;
    &lt;/tfoot&gt;
&lt;/table&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;table {
    display : table;
    border-collapse : separate;
    border-spacing: 2px;
    border-color : gray;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;우리가 보통 보던 테이블의 경계선은 예제를 보다시피, 하나의 줄로 만들어져 있다.&lt;/p&gt;
&lt;p&gt;내가 블로그 CSS 를 어느정도 Custom 화 시켰기에 기본적으로 &amp;quot;collapse&amp;quot; 로 합쳐져 있는데,&lt;/p&gt;
&lt;p&gt;만약, &lt;code&gt;style&lt;/code&gt; 속성에 &lt;code&gt;border-collapse : separate&lt;/code&gt;(분리) 를 선언하면,&lt;/p&gt;
&lt;p&gt;각각의 요소들이 조금씩 떨어져 있는 것을 확인 할 수 있다.&lt;/p&gt;
&lt;p&gt;또한, 요소들의 간격은 &lt;code&gt;border-spacing&lt;/code&gt; 으로 정할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;caption&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;테이블의 캡션(제목) 을 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이 제목(캡션) 은 왼, 우, 아래, 위에 배치시킬 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;caption&amp;gt;&lt;/code&gt; 태그는 테이블 태그 내부의 가장 하단에 배치하더라도,&lt;/p&gt;
&lt;p&gt;맨 위에 뜬다. 따라서, 우리는 캡션이 원하는 위치에 뜨도록 만들고 싶다면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;style&lt;/code&gt; 을 통해서 이를 해결 해 주어야 한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;table&amp;gt;
    &amp;lt;caption&amp;gt;Caption Test&amp;lt;/caption&amp;gt;
    &amp;lt;thead&amp;gt;
        &amp;lt;th&amp;gt;th1&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;th2&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;th3&amp;lt;/th&amp;gt;
    &amp;lt;/thead&amp;gt;
    &amp;lt;tbody&amp;gt;
        &amp;lt;tr&amp;gt;
            &amp;lt;td&amp;gt;td1&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;td2&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;td3&amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
        &amp;lt;tr&amp;gt;
            &amp;lt;td&amp;gt;td1&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;td2&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;td3&amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
    &amp;lt;/tbody&amp;gt;
    &amp;lt;tfoot&amp;gt;
        &amp;lt;tr&amp;gt;
            &amp;lt;td&amp;gt;td1&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;td2&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;td3&amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
    &amp;lt;/tfoot&amp;gt;
&amp;lt;/table&amp;gt;

&amp;lt;table&amp;gt;
    &amp;lt;caption style=&amp;quot;text-align:left&amp;quot;&amp;gt;Caption Test&amp;lt;/caption&amp;gt;
    &amp;lt;thead&amp;gt;
        &amp;lt;th&amp;gt;th1&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;th2&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;th3&amp;lt;/th&amp;gt;
    &amp;lt;/thead&amp;gt;
    &amp;lt;tbody&amp;gt;
        &amp;lt;tr&amp;gt;
            &amp;lt;td&amp;gt;td1&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;td2&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;td3&amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
        &amp;lt;tr&amp;gt;
            &amp;lt;td&amp;gt;td1&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;td2&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;td3&amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
    &amp;lt;/tbody&amp;gt;
    &amp;lt;tfoot&amp;gt;
        &amp;lt;tr&amp;gt;
            &amp;lt;td&amp;gt;td1&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;td2&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;td3&amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
    &amp;lt;/tfoot&amp;gt;
&amp;lt;/table&amp;gt;

&amp;lt;table&amp;gt;
    &amp;lt;caption style=&amp;quot;caption-side:bottom; text-align:right&amp;quot;&amp;gt;Caption Test&amp;lt;/caption&amp;gt;
    &amp;lt;thead&amp;gt;
        &amp;lt;th&amp;gt;th1&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;th2&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;th3&amp;lt;/th&amp;gt;
    &amp;lt;/thead&amp;gt;
    &amp;lt;tbody&amp;gt;
        &amp;lt;tr&amp;gt;
            &amp;lt;td&amp;gt;td1&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;td2&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;td3&amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
        &amp;lt;tr&amp;gt;
            &amp;lt;td&amp;gt;td1&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;td2&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;td3&amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
    &amp;lt;/tbody&amp;gt;
    &amp;lt;tfoot&amp;gt;
        &amp;lt;tr&amp;gt;
            &amp;lt;td&amp;gt;td1&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;td2&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;td3&amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
    &amp;lt;/tfoot&amp;gt;
&amp;lt;/table&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;table&gt;
    &lt;caption&gt;Caption Test&lt;/caption&gt;
    &lt;thead&gt;
        &lt;th&gt;th1&lt;/th&gt;
        &lt;th&gt;th2&lt;/th&gt;
        &lt;th&gt;th3&lt;/th&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
        &lt;tr&gt;
            &lt;td&gt;td1&lt;/td&gt;
            &lt;td&gt;td2&lt;/td&gt;
            &lt;td&gt;td3&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;td1&lt;/td&gt;
            &lt;td&gt;td2&lt;/td&gt;
            &lt;td&gt;td3&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tbody&gt;
    &lt;tfoot&gt;
        &lt;tr&gt;
            &lt;td&gt;td1&lt;/td&gt;
            &lt;td&gt;td2&lt;/td&gt;
            &lt;td&gt;td3&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tfoot&gt;
&lt;/table&gt;

&lt;table&gt;
    &lt;caption style=&quot;text-align:left&quot;&gt;Caption Test&lt;/caption&gt;
    &lt;thead&gt;
        &lt;th&gt;th1&lt;/th&gt;
        &lt;th&gt;th2&lt;/th&gt;
        &lt;th&gt;th3&lt;/th&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
        &lt;tr&gt;
            &lt;td&gt;td1&lt;/td&gt;
            &lt;td&gt;td2&lt;/td&gt;
            &lt;td&gt;td3&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;td1&lt;/td&gt;
            &lt;td&gt;td2&lt;/td&gt;
            &lt;td&gt;td3&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tbody&gt;
    &lt;tfoot&gt;
        &lt;tr&gt;
            &lt;td&gt;td1&lt;/td&gt;
            &lt;td&gt;td2&lt;/td&gt;
            &lt;td&gt;td3&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tfoot&gt;
&lt;/table&gt;

&lt;table&gt;
    &lt;caption style=&quot;caption-side:bottom; text-align:right&quot;&gt;Caption Test&lt;/caption&gt;
    &lt;thead&gt;
        &lt;th&gt;th1&lt;/th&gt;
        &lt;th&gt;th2&lt;/th&gt;
        &lt;th&gt;th3&lt;/th&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
        &lt;tr&gt;
            &lt;td&gt;td1&lt;/td&gt;
            &lt;td&gt;td2&lt;/td&gt;
            &lt;td&gt;td3&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;td1&lt;/td&gt;
            &lt;td&gt;td2&lt;/td&gt;
            &lt;td&gt;td3&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tbody&gt;
    &lt;tfoot&gt;
        &lt;tr&gt;
            &lt;td&gt;td1&lt;/td&gt;
            &lt;td&gt;td2&lt;/td&gt;
            &lt;td&gt;td3&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tfoot&gt;
&lt;/table&gt;

&lt;p&gt;&lt;code&gt;caption&lt;/code&gt; 의 줄에서, 왼쪽 오른쪽 부착 형태를 정의하고 싶다면, &lt;code&gt;text-align&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;caption&lt;/code&gt; 이 form 에서 위, 혹은 아래를 정하고 싶다면, &lt;code&gt;caption-side:bottom&lt;/code&gt; 을 사용한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;caption&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;caption {
    display : table-caption;
    text-align : center;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;th&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;테이블 내부의 헤더 Cell 을 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;thead&amp;gt;&lt;/code&gt; 는 그룹핑을 수행하기도 하지만,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;tr&amp;gt;&lt;/code&gt; 의 역할을 수행하기도 한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;th&amp;gt;&lt;/code&gt; 는 헤더 cell 로서, &lt;code&gt;&amp;lt;thead&amp;gt;&lt;/code&gt; 에 들어가기도 하지만,&lt;/p&gt;
&lt;p&gt;단순히 &lt;code&gt;&amp;lt;tr&amp;gt;&lt;/code&gt; 내부에도 선언 될 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;p&amp;gt;단순 &amp;lt;code&amp;gt;tr&amp;lt;/code&amp;gt; 태그로 &amp;lt;code&amp;gt;th&amp;lt;/code&amp;gt;태그를 넣을 경우

&amp;lt;table&amp;gt;
    &amp;lt;tr&amp;gt;
        &amp;lt;th&amp;gt;사원번호&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;이름&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;부서이름&amp;lt;/th&amp;gt;
    &amp;lt;/tr&amp;gt;
    &amp;lt;tr&amp;gt;
        &amp;lt;td&amp;gt;14359272&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;공담형&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;AllOps&amp;lt;/td&amp;gt;
    &amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;

&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;thead&amp;lt;/code&amp;gt; 를 통한 태그로 헤더 선언, 그룹핑 태그로 묶기&amp;lt;/p&amp;gt;

&amp;lt;table&amp;gt;
    &amp;lt;caption&amp;gt;사원 정보&amp;lt;/caption&amp;gt;
    &amp;lt;thead&amp;gt;
        &amp;lt;th&amp;gt;사원번호&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;이름&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;부서이름&amp;lt;/th&amp;gt;
    &amp;lt;/thead&amp;gt;
    &amp;lt;tbody&amp;gt;
        &amp;lt;tr&amp;gt;
            &amp;lt;td&amp;gt;14359272&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;공담형&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;AllOps&amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
    &amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;단순 &lt;code&gt;tr&lt;/code&gt; 태그로 &lt;code&gt;th&lt;/code&gt;태그를 넣을 경우

&lt;table&gt;
    &lt;tr&gt;
        &lt;th&gt;사원번호&lt;/th&gt;
        &lt;th&gt;이름&lt;/th&gt;
        &lt;th&gt;부서이름&lt;/th&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;14359272&lt;/td&gt;
        &lt;td&gt;공담형&lt;/td&gt;
        &lt;td&gt;AllOps&lt;/td&gt;
    &lt;/tr&gt;
&lt;/table&gt;

&lt;p&gt;&lt;code&gt;thead&lt;/code&gt; 를 통한 태그로 헤더 선언, 그룹핑 태그로 묶기&lt;/p&gt;

&lt;table&gt;
    &lt;caption&gt;사원 정보&lt;/caption&gt;
    &lt;thead style=&quot;background-color:#333&quot;&gt;
        &lt;th&gt;사원번호&lt;/th&gt;
        &lt;th&gt;이름&lt;/th&gt;
        &lt;th&gt;부서이름&lt;/th&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
        &lt;tr&gt;
            &lt;td&gt;14359272&lt;/td&gt;
            &lt;td&gt;공담형&lt;/td&gt;
            &lt;td&gt;AllOps&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tbody&gt;
&lt;/table&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;th&amp;gt;&lt;/code&gt; 태그 특수 속성들 :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;abbr&lt;/code&gt; - Text : 헤더 셀에 있는 컨텐츠를 설명하는 약어.(마우스를 올리면 1초 있다가 설명탭이 뜸)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;colspan&lt;/code&gt; - Number : 현재 선언할 헤더 셀을 얼마나 크게 합칠 것인지.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;headers&lt;/code&gt; - ID : 헤더 셀과 연관된 또 다른 셀을 지정한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rowspan&lt;/code&gt; - Number : 헤더 셀이 얼만큼의 줄을 확장해야 하는지&lt;/li&gt;
&lt;li&gt;&lt;code&gt;scope&lt;/code&gt; : 자신이 속한 그룹 혹은 셀이 무엇인지 지정한다.&lt;ul&gt;
&lt;li&gt;&lt;code&gt;col&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;colgroup&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;row&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rowgroup&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;th&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;th {
    display : table-cell;
    vertical-align : inherit;
    font-weight : bold;
    tet-align: center;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;


&lt;h3&gt;&lt;code&gt;tr&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;테이블의 한 줄(row) 를 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;테이블에서 하나의 줄(row) 를 정의할 때, 가장 대표적으로 사용되는 태그이다.&lt;/p&gt;
&lt;p&gt;예를 들어, 맨 위의 헤더 정보는 하위에 존재하는 줄들의 정보를 설명하는 중요한 Cell 들이다.&lt;/p&gt;
&lt;p&gt;따라서, 줄 하나를 스타일링하여 강조할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;table&amp;gt;
    &amp;lt;tr style=&amp;quot;background-color : #234&amp;quot;&amp;gt;
        &amp;lt;th&amp;gt;header1&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;header2&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;header3&amp;lt;/th&amp;gt;
    &amp;lt;/tr&amp;gt;
    &amp;lt;tbody&amp;gt;
        &amp;lt;tr&amp;gt;
            &amp;lt;td&amp;gt;요소1&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;요소2&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;요소3&amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
    &amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;table&gt;
    &lt;tr style=&quot;background-color : #234&quot;&gt;
        &lt;th&gt;header1&lt;/th&gt;
        &lt;th&gt;header2&lt;/th&gt;
        &lt;th&gt;header3&lt;/th&gt;
    &lt;/tr&gt;
    &lt;tbody&gt;
        &lt;tr style=&quot;text-align: center&quot;&gt;
            &lt;td&gt;요소1&lt;/td&gt;
            &lt;td&gt;요소2&lt;/td&gt;
            &lt;td&gt;요소3&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td rowspan=&quot;2&quot;&gt;수직 요소&lt;/td&gt;
            &lt;td&gt;요소2&lt;/td&gt;
            &lt;td&gt;요소3&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;요소2&lt;/td&gt;
            &lt;td&gt;요소3&lt;/td&gt;
        &lt;tr/&gt;
    &lt;/tbody&gt;
&lt;/table&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;tr&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;tr {
    display : table-row;
    vertical-align: inherit;
    border-color: inherit;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;


&lt;h3&gt;&lt;code&gt;td&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;테이블의 하나의 셀(Cell) 을 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;테이블 태그 내부의 속성을 구성하는 가장 작은 단위의 태그이다.&lt;/p&gt;
&lt;p&gt;하나의 줄(&lt;code&gt;&amp;lt;tr&amp;gt;&lt;/code&gt;) 에, &lt;code&gt;&amp;lt;th&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;td&amp;gt;&lt;/code&gt; 가 들어가서 구성한다는 것은 잘 알 것이다.&lt;/p&gt;
&lt;p&gt;따라서, 이번 예제는 한번 &lt;code&gt;colspan&lt;/code&gt;, &lt;code&gt;rowspan&lt;/code&gt; 속성을 통해 상하좌우 통합하는 방식을 학습하자.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;table&amp;gt;
    &amp;lt;thead&amp;gt;
        &amp;lt;th colspan=&amp;quot;4&amp;quot;&amp;gt;&amp;lt;code&amp;gt;colspan&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;rowspan&amp;lt;/code&amp;gt; 예제&amp;lt;/th&amp;gt;
    &amp;lt;/thead&amp;gt;
    &amp;lt;tbody&amp;gt;
        &amp;lt;tr&amp;gt;
            &amp;lt;td rowspan=&amp;quot;4&amp;quot;&amp;gt;1&amp;lt;/td&amp;gt;
            &amp;lt;td colspan=&amp;quot;3&amp;quot;&amp;gt;2&amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
        &amp;lt;tr&amp;gt;
            &amp;lt;td rowspan=&amp;quot;3&amp;quot;&amp;gt;3&amp;lt;/td&amp;gt;
            &amp;lt;td colspan=&amp;quot;2&amp;quot;&amp;gt;4&amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
        &amp;lt;tr&amp;gt;
            &amp;lt;td rowspan=&amp;quot;2&amp;quot;&amp;gt;5&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;6&amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
        &amp;lt;tr&amp;gt;
            &amp;lt;td&amp;gt;7&amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
    &amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;table&gt;
    &lt;thead&gt;
        &lt;th colspan=&quot;4&quot;&gt;&lt;code&gt;colspan&lt;/code&gt;, &lt;code&gt;rowspan&lt;/code&gt; 예제&lt;/th&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
        &lt;tr&gt;
            &lt;td rowspan=&quot;4&quot;&gt;1&lt;/td&gt;
            &lt;td colspan=&quot;3&quot;&gt;2&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td rowspan=&quot;3&quot;&gt;3&lt;/td&gt;
            &lt;td colspan=&quot;2&quot;&gt;4&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td rowspan=&quot;2&quot;&gt;5&lt;/td&gt;
            &lt;td&gt;6&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;7&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tbody&gt;
&lt;/table&gt;

&lt;br/&gt;

&lt;p&gt;위와 같이 &lt;code&gt;colspan&lt;/code&gt;, &lt;code&gt;rowspan&lt;/code&gt; 속성으로 수직, 수평 병합을 실시하여 자유롭게 셀을 통합할 수 있다.&lt;/p&gt;
&lt;p&gt;그러나, 꼭 유의해야 할 점이, &lt;code&gt;rowspan&lt;/code&gt; 은 줄을 몇 개를 사용하느냐를 정의하는 만큼,&lt;/p&gt;
&lt;p&gt;그 다음 &lt;code&gt;&amp;lt;tr&amp;gt;&lt;/code&gt; 태그에서 작성해야 할 &lt;code&gt;&amp;lt;td&amp;gt;&lt;/code&gt; 요소가 하나씩 줄어든다는 것을 명심하자.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;td&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;td {
    display : table-cell;
    vertical-align : inherit;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;


&lt;h3&gt;&lt;code&gt;thead&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;테이블의 헤더 컨텐츠를 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;물론 위에서 &lt;code&gt;&amp;lt;thead&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;tbody&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;tfoot&amp;gt;&lt;/code&gt; 에 대한 태그를 간단히 다루었다.&lt;/p&gt;
&lt;p&gt;이는 줄들을 카테고리화 (그룹핑) 하는 태그인데,&lt;/p&gt;
&lt;p&gt;이러한 태그가 쓸모없어 보이더라도, 내부 스타일링에 있어 중요한 역할을 하며,&lt;/p&gt;
&lt;p&gt;추후 개발 시 테이블 내에서 특정 영역이 어떤 부분에 속하는지 쉽게 알 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;table&amp;gt;
    &amp;lt;!-- 테이블의 헤더 부분을 정의 --&amp;gt;
    &amp;lt;thead style=&amp;quot;background-color : #567&amp;quot;&amp;gt;
        &amp;lt;tr&amp;gt;
            &amp;lt;th&amp;gt;헤더 1&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;헤더 2&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;헤더 3&amp;lt;/th&amp;gt;
        &amp;lt;/tr&amp;gt;
    &amp;lt;/thead&amp;gt;
    &amp;lt;!-- 테이블의 몸통 부분을 정의 --&amp;gt;
    &amp;lt;tbody style=&amp;quot;background-color : #765&amp;quot;&amp;gt;
        &amp;lt;tr&amp;gt;
            &amp;lt;td&amp;gt;몸체 1&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;몸체 2&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;몸체 3&amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
        &amp;lt;tr&amp;gt;
            &amp;lt;td&amp;gt;몸체 4&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;몸체 5&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;몸체 6&amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
    &amp;lt;/tbody&amp;gt;
    &amp;lt;!-- 테이블의 마무리 or 마지막 부분을 정의 --&amp;gt;
    &amp;lt;tfoot style=&amp;quot;background-color : #666&amp;quot;&amp;gt;
        &amp;lt;tr&amp;gt;
            &amp;lt;td&amp;gt;마무리 1&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;마무리 2&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;마무리 3&amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
    &amp;lt;/tfoot&amp;gt;
&amp;lt;/table&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;table&gt;
    &lt;!-- 테이블의 헤더 부분을 정의 --&gt;
    &lt;thead style=&quot;background-color : #567&quot;&gt;
        &lt;tr&gt;
            &lt;th&gt;헤더 1&lt;/th&gt;
            &lt;th&gt;헤더 2&lt;/th&gt;
            &lt;th&gt;헤더 3&lt;/th&gt;
        &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;!-- 테이블의 몸통 부분을 정의 --&gt;
    &lt;tbody style=&quot;background-color : #765&quot;&gt;
        &lt;tr&gt;
            &lt;td&gt;몸체 1&lt;/td&gt;
            &lt;td&gt;몸체 2&lt;/td&gt;
            &lt;td&gt;몸체 3&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;몸체 4&lt;/td&gt;
            &lt;td&gt;몸체 5&lt;/td&gt;
            &lt;td&gt;몸체 6&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tbody&gt;
    &lt;!-- 테이블의 마무리 or 마지막 부분을 정의 --&gt;
    &lt;tfoot style=&quot;background-color : #666&quot;&gt;
        &lt;tr&gt;
            &lt;td&gt;마무리 1&lt;/td&gt;
            &lt;td&gt;마무리 2&lt;/td&gt;
            &lt;td&gt;마무리 3&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tfoot&gt;
&lt;/table&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;thead&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;thead {
    display : table-header-group;
    vertical-align : middle;
    border-color : inherit;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;


&lt;h3&gt;&lt;code&gt;tbody&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;테이블의 몸체 부분을 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;thead&amp;gt;&lt;/code&gt; 와 비슷하게,&lt;/p&gt;
&lt;p&gt;테이블 내부에 들어가는 &lt;code&gt;&amp;lt;tr&amp;gt;&lt;/code&gt; (table row) 들의 그룹을 정의한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;table&amp;gt;
    &amp;lt;caption&amp;gt;xxx 부대 의료진단자&amp;lt;/caption&amp;gt;
    &amp;lt;thead&amp;gt;
        &amp;lt;tr&amp;gt;
            &amp;lt;th&amp;gt;1월&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;2월&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;3월&amp;lt;/th&amp;gt;
        &amp;lt;/tr&amp;gt;
    &amp;lt;/thead&amp;gt;
    &amp;lt;tbody&amp;gt;
        &amp;lt;tr&amp;gt;
            &amp;lt;td&amp;gt;1,027 명&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;892 명&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;569 명&amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
    &amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;table&gt;
    &lt;caption&gt;xxx 부대 의료진단자&lt;/caption&gt;
    &lt;thead&gt;
        &lt;tr&gt;
            &lt;th&gt;1월&lt;/th&gt;
            &lt;th&gt;2월&lt;/th&gt;
            &lt;th&gt;3월&lt;/th&gt;
        &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
        &lt;tr&gt;
            &lt;td&gt;1,027 명&lt;/td&gt;
            &lt;td&gt;892 명&lt;/td&gt;
            &lt;td&gt;569 명&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tbody&gt;
&lt;/table&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;tbody&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;tbody {
    display : table-row-group;
    vertical-align : middle;
    border-color : inherit;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;


&lt;h3&gt;&lt;code&gt;tfoot&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;테이블의 마지막(밑부분) 컨텐츠를 그룹화한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;엑셀 프로그램을 예시로 들어보면,&lt;/p&gt;
&lt;p&gt;여러 내부 컨텐츠에 대한 요약화 (평균) 을 정의하거나,&lt;/p&gt;
&lt;p&gt;총 비용, 총 인원을 계산하여 보여주는 역할로 종종 사용된다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;table&amp;gt;
    &amp;lt;caption&amp;gt;xx 학원 언어별 사용자 현황&amp;lt;/caption&amp;gt;
    &amp;lt;thead&amp;gt;
        &amp;lt;tr&amp;gt;
            &amp;lt;th&amp;gt;C++&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;Java&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;JavaScript&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;Python&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;그 외의 언어&amp;lt;/th&amp;gt;
        &amp;lt;/tr&amp;gt;
    &amp;lt;/thead&amp;gt;
    &amp;lt;tbody&amp;gt;
        &amp;lt;tr id=&amp;quot;tfoot-datas&amp;quot;&amp;gt;
            &amp;lt;td&amp;gt;10&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;31&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;25&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;22&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;5&amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
    &amp;lt;/tbody&amp;gt;
    &amp;lt;tfoot&amp;gt;
        &amp;lt;tr&amp;gt;
            &amp;lt;td&amp;gt;총 인원 :&amp;lt;/td&amp;gt;
            &amp;lt;td colspan=&amp;quot;4&amp;quot; id=&amp;quot;tfoot-result&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
    &amp;lt;/tfoot&amp;gt;
&amp;lt;/table&amp;gt;

&amp;lt;script&amp;gt;
const dataRowDOM = document.getElementById(&amp;quot;tfoot-datas&amp;quot;);
const resultDOM = document.getElementById(&amp;quot;tfoot-result&amp;quot;);

const datas = dataRowDOM.children;

let result = 0;
for(let i = 0; i &amp;lt; datas.length; i++) {
  const num = parseInt(datas[i].innerText);
  result += num;
}

resultDOM.innerText = result;
&amp;lt;/script&amp;gt;

&amp;lt;table&amp;gt;
    &amp;lt;caption&amp;gt;학원 별 언어 사용자 현황&amp;lt;/caption&amp;gt;
    &amp;lt;thead&amp;gt;
        &amp;lt;tr&amp;gt;
            &amp;lt;th&amp;gt;학원 이름&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;C++&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;Java&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;JavaScript&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;Python&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;그 외의 언어&amp;lt;/th&amp;gt;
        &amp;lt;/tr&amp;gt;
    &amp;lt;/thead&amp;gt;
    &amp;lt;tbody id=&amp;quot;tfoot-tbody&amp;quot;&amp;gt;
        &amp;lt;tr&amp;gt;
            &amp;lt;td&amp;gt;xx 학원&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;10&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;31&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;25&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;22&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;5&amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
        &amp;lt;tr&amp;gt;
            &amp;lt;td&amp;gt;yy 학원&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;20&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;15&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;41&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;32&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;9&amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
    &amp;lt;/tbody&amp;gt;
    &amp;lt;tfoot style=&amp;quot;background-color : #765&amp;quot;&amp;gt;
        &amp;lt;tr id=&amp;quot;tfoot-results&amp;quot;&amp;gt;
        &amp;lt;/tr&amp;gt;
    &amp;lt;/tfoot&amp;gt;
&amp;lt;/table&amp;gt;

&amp;lt;script&amp;gt;
const bodyDOM = document.getElementById(&amp;quot;tfoot-tbody&amp;quot;);
const tfootResultDOM = document.getElementById(&amp;quot;tfoot-results&amp;quot;);

const rowDOMs = bodyDOM.children;

const colRength = rowDOMs[0].children.length;

let calculatedHTML = &amp;quot;&amp;quot;;

// 0 번째는 &amp;quot;xx 학원&amp;quot; 또는 &amp;quot;yy 학원&amp;quot; 이다.
for(let i = 1; i &amp;lt; colRength; i++) {
  // 하나의 언어에 대한 인원을 담을 예정
  let tempNum = 0;

  // 각 줄을 번갈아서 실행
  for(let j = 0; j &amp;lt; rowDOMs.length; j++) {
    // 하나의 줄을 가져옴
    const row = rowDOMs[j].children;

    tempNum += parseInt(row[i].innerText);
  }


  calculatedHTML = calculatedHTML + &amp;quot;&amp;lt;td&amp;gt;&amp;quot; + tempNum + &amp;quot;&amp;lt;/td&amp;gt;&amp;quot;;
}

const resultHTML = &amp;quot;&amp;lt;td&amp;gt;언어 사용자 총합&amp;lt;/td&amp;gt;&amp;quot; + calculatedHTML;

tfootResultDOM.innerHTML = resultHTML;
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;table&gt;
    &lt;caption&gt;xx 학원 언어별 사용자 현황&lt;/caption&gt;
    &lt;thead&gt;
        &lt;tr&gt;
            &lt;th&gt;C++&lt;/th&gt;
            &lt;th&gt;Java&lt;/th&gt;
            &lt;th&gt;JavaScript&lt;/th&gt;
            &lt;th&gt;Python&lt;/th&gt;
            &lt;th&gt;그 외의 언어&lt;/th&gt;
        &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
        &lt;tr id=&quot;tfoot-datas&quot;&gt;
            &lt;td&gt;10&lt;/td&gt;
            &lt;td&gt;31&lt;/td&gt;
            &lt;td&gt;25&lt;/td&gt;
            &lt;td&gt;22&lt;/td&gt;
            &lt;td&gt;5&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tbody&gt;
    &lt;tfoot&gt;
        &lt;tr&gt;
            &lt;td&gt;총 인원 :&lt;/td&gt;
            &lt;td colspan=&quot;4&quot; id=&quot;tfoot-result&quot;&gt;&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tfoot&gt;
&lt;/table&gt;

&lt;script&gt;
const dataRowDOM = document.getElementById(&quot;tfoot-datas&quot;);
const resultDOM = document.getElementById(&quot;tfoot-result&quot;);

const datas = dataRowDOM.children;

let result = 0;
for(let i = 0; i &lt; datas.length; i++) {
  const num = parseInt(datas[i].innerText);
  result += num;
}

resultDOM.innerText = result;
&lt;/script&gt;

&lt;table&gt;
    &lt;caption&gt;학원 별 언어 사용자 현황&lt;/caption&gt;
    &lt;thead&gt;
        &lt;tr&gt;
            &lt;th&gt;학원 이름&lt;/th&gt;
            &lt;th&gt;C++&lt;/th&gt;
            &lt;th&gt;Java&lt;/th&gt;
            &lt;th&gt;JavaScript&lt;/th&gt;
            &lt;th&gt;Python&lt;/th&gt;
            &lt;th&gt;그 외의 언어&lt;/th&gt;
        &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody id=&quot;tfoot-tbody&quot;&gt;
        &lt;tr&gt;
            &lt;td&gt;xx 학원&lt;/td&gt;
            &lt;td&gt;10&lt;/td&gt;
            &lt;td&gt;31&lt;/td&gt;
            &lt;td&gt;25&lt;/td&gt;
            &lt;td&gt;22&lt;/td&gt;
            &lt;td&gt;5&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;yy 학원&lt;/td&gt;
            &lt;td&gt;20&lt;/td&gt;
            &lt;td&gt;15&lt;/td&gt;
            &lt;td&gt;41&lt;/td&gt;
            &lt;td&gt;32&lt;/td&gt;
            &lt;td&gt;9&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tbody&gt;
    &lt;tfoot style=&quot;background-color : #765&quot;&gt;
        &lt;tr id=&quot;tfoot-results&quot;&gt;
        &lt;/tr&gt;
    &lt;/tfoot&gt;
&lt;/table&gt;

&lt;script&gt;
const bodyDOM = document.getElementById(&quot;tfoot-tbody&quot;);
const tfootResultDOM = document.getElementById(&quot;tfoot-results&quot;);

const rowDOMs = bodyDOM.children;

const colRength = rowDOMs[0].children.length;

let calculatedHTML = &quot;&quot;;

// 0 번째는 &quot;xx 학원&quot; 또는 &quot;yy 학원&quot; 이다.
for(let i = 1; i &lt; colRength; i++) {
  // 하나의 언어에 대한 인원을 담을 예정
  let tempNum = 0;

  // 각 줄을 번갈아서 실행
  for(let j = 0; j &lt; rowDOMs.length; j++) {
    // 하나의 줄을 가져옴
    const row = rowDOMs[j].children;

    tempNum += parseInt(row[i].innerText);
  }


  calculatedHTML = calculatedHTML + &quot;&lt;td&gt;&quot; + tempNum + &quot;&lt;/td&gt;&quot;;
}

const resultHTML = &quot;&lt;td&gt;언어 사용자 총합&lt;/td&gt;&quot; + calculatedHTML;

tfootResultDOM.innerHTML = resultHTML;
&lt;/script&gt;

&lt;br/&gt;

&lt;p&gt;사실, 내가 이러한 방식으로 &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; 를 선언하여 사용하는 것은 절대로 좋은 코드가 아니다.&lt;/p&gt;
&lt;p&gt;나는 블로그에서 제한된 JS 를 우회하기 위해, 가져와야 할 데이터 태그와, 결과 태그에 &lt;code&gt;id&lt;/code&gt; 를 붙여&lt;/p&gt;
&lt;p&gt;다른 방식으로 계산하고 있다.&lt;/p&gt;
&lt;p&gt;위와 같은 방식으로 진행한다면, 나중에 &lt;code&gt;table&lt;/code&gt; 에 &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; 태그를 넣을 때,&lt;/p&gt;
&lt;p&gt;굉장히 곤란 해 질 수도 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;tfoot&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;tfoot {
    display : table-footer-group;
    vertical-align : middle;
    border-color : inherit;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;


&lt;h3&gt;&lt;code&gt;col&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;colgroup&amp;gt;&lt;/code&gt; &lt;strong&gt;그룹 요소 내부에 선언되는데, 각각의 컬럼의 속성을 지정한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;나는 처음에 이걸로 &lt;code&gt;&amp;lt;tr&amp;gt;&lt;/code&gt; 이 아니라, &lt;code&gt;&amp;lt;col&amp;gt;&lt;/code&gt; 로 수직 방향의 데이터를 정하는 줄 알았는데,&lt;/p&gt;
&lt;p&gt;그것이 아니고, 각 컬럼에 속하게 되는 &lt;code&gt;&amp;lt;td&amp;gt;&lt;/code&gt; 와 &lt;code&gt;&amp;lt;th&amp;gt;&lt;/code&gt; 의 속성을 정하게 되는 것이었다..&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;table&amp;gt;
    &amp;lt;colgroup&amp;gt;
        &amp;lt;col style=&amp;quot;color : yellow&amp;quot;/&amp;gt;
        &amp;lt;col span=&amp;quot;3&amp;quot; style=&amp;quot;background-color : #765&amp;quot; /&amp;gt;
    &amp;lt;/colgroup&amp;gt;
    &amp;lt;caption&amp;gt;xx 학원 언어별 사용자 현황&amp;lt;/caption&amp;gt;
    &amp;lt;thead&amp;gt;
        &amp;lt;tr&amp;gt;
            &amp;lt;th&amp;gt;C++&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;Java&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;JavaScript&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;Python&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;그 외의 언어&amp;lt;/th&amp;gt;
        &amp;lt;/tr&amp;gt;
    &amp;lt;/thead&amp;gt;
    &amp;lt;tbody&amp;gt;
        &amp;lt;tr&amp;gt;
            &amp;lt;td&amp;gt;10&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;31&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;25&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;22&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;5&amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
    &amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;table&gt;
    &lt;colgroup&gt;
        &lt;col style=&quot;color : yellow&quot;/&gt;
        &lt;col span=&quot;3&quot; style=&quot;background-color : #765&quot;/&gt;
    &lt;/colgroup&gt;
    &lt;caption&gt;xx 학원 언어별 사용자 현황&lt;/caption&gt;
    &lt;thead&gt;
        &lt;tr&gt;
            &lt;th&gt;C++&lt;/th&gt;
            &lt;th&gt;Java&lt;/th&gt;
            &lt;th&gt;JavaScript&lt;/th&gt;
            &lt;th&gt;Python&lt;/th&gt;
            &lt;th&gt;그 외의 언어&lt;/th&gt;
        &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
        &lt;tr&gt;
            &lt;td&gt;10&lt;/td&gt;
            &lt;td&gt;31&lt;/td&gt;
            &lt;td&gt;25&lt;/td&gt;
            &lt;td&gt;22&lt;/td&gt;
            &lt;td&gt;5&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tbody&gt;
&lt;/table&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;col&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;col {
    display : table-colmn;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;


&lt;h3&gt;&lt;code&gt;colgroup&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;테이블의 형식에 대한 하나 혹은 그 이상의 컬럼들의 그룹을 지정한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;위에서 보다시피, 우리가 각 컬럼을 어떤 스타일과 속성을 지정 할 것인지&lt;/p&gt;
&lt;p&gt;설명하는 &lt;code&gt;&amp;lt;col&amp;gt;&lt;/code&gt; 태그들을 그룹화 하는 태그이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;table&amp;gt;
    &amp;lt;colgroup&amp;gt;
        &amp;lt;col style=&amp;quot;color : yellow&amp;quot;/&amp;gt;
        &amp;lt;col span=&amp;quot;3&amp;quot; style=&amp;quot;background-color : #765&amp;quot;/&amp;gt;
        &amp;lt;col style=&amp;quot;background-color : #567&amp;quot;
    &amp;lt;/colgroup&amp;gt;
    &amp;lt;caption&amp;gt;xx 학원 언어별 사용자 현황&amp;lt;/caption&amp;gt;
    &amp;lt;thead&amp;gt;
        &amp;lt;tr&amp;gt;
            &amp;lt;th&amp;gt;C++&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;Java&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;JavaScript&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;Python&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;그 외의 언어&amp;lt;/th&amp;gt;
        &amp;lt;/tr&amp;gt;
    &amp;lt;/thead&amp;gt;
    &amp;lt;tbody&amp;gt;
        &amp;lt;tr&amp;gt;
            &amp;lt;td&amp;gt;10&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;31&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;25&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;22&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;5&amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
    &amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;table&gt;
    &lt;colgroup&gt;
        &lt;col style=&quot;color : yellow&quot;/&gt;
        &lt;col span=&quot;3&quot; style=&quot;background-color : #765&quot;/&gt;
        &lt;col style=&quot;background-color : #567&quot;
    &lt;/colgroup&gt;
    &lt;caption&gt;xx 학원 언어별 사용자 현황&lt;/caption&gt;
    &lt;thead&gt;
        &lt;tr&gt;
            &lt;th&gt;C++&lt;/th&gt;
            &lt;th&gt;Java&lt;/th&gt;
            &lt;th&gt;JavaScript&lt;/th&gt;
            &lt;th&gt;Python&lt;/th&gt;
            &lt;th&gt;그 외의 언어&lt;/th&gt;
        &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
        &lt;tr&gt;
            &lt;td&gt;10&lt;/td&gt;
            &lt;td&gt;31&lt;/td&gt;
            &lt;td&gt;25&lt;/td&gt;
            &lt;td&gt;22&lt;/td&gt;
            &lt;td&gt;5&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tbody&gt;
&lt;/table&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;colgroup&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;colgroup {
    display : table-column-group;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h2&gt;이번 글을 작성하며 배운 것&lt;/h2&gt;
&lt;p&gt;이번 글에는 &lt;strong&gt;Lists&lt;/strong&gt; 배열 관련 태그들과,&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tables&lt;/strong&gt; 테이블과 내부 태그에 대한 정보들을 습득했다.&lt;/p&gt;
&lt;p&gt;습득하는 것에서 멈추지 않고, 항상 실제 예제를 사용하여 표현하고 있으며,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; 태그 내부에 코드를 작성하여 동적 예제 또한 첨부하고 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이번 글을 압축해서 말하자면, &amp;quot;정보 블록 표현 형식&amp;quot; 을 배웠다고 말할 수 있다.&lt;/p&gt;
&lt;p&gt;내 블로그의 글 쓰기와, 미리보기 기능으로 스크립트 기능이 동작하는지 살펴보는 과정에서,&lt;/p&gt;
&lt;p&gt;특정 태그의 스타일링이 약간 모자르거나,&lt;/p&gt;
&lt;p&gt;리스팅 과정에서 표현되는 Marker (앞부분) 이 이상해서 몇 가지를 커스텀 할 수 있었다.&lt;/p&gt;
&lt;p&gt;그리고 항상 느끼는 거지만, 블로그 아티클 내부에서 금지된 중요 기능들이 존재한다. (XSS 방지)&lt;/p&gt;
&lt;p&gt;이 때문에 우회하여 JS 스크립트를 작성하는 과정에서, 노드에 대한 속성과 정보를 알게 되었다.&lt;/p&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;h2&gt;참고 사이트&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;W3Schools HTML Element Reference&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.w3schools.com/TAGS/ref_byfunc.asp&quot;&gt;https://www.w3schools.com/TAGS/ref_byfunc.asp&lt;/a&gt;&lt;/p&gt;</description>
      <category>Web-Server/웹 지식</category>
      <category>COL</category>
      <category>colgroup</category>
      <category>dl</category>
      <category>dt</category>
      <category>Li</category>
      <category>menu</category>
      <category>Ol</category>
      <category>table</category>
      <category>THEAD</category>
      <category>UL</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/221</guid>
      <comments>https://codecreature.tistory.com/221#entry221comment</comments>
      <pubDate>Fri, 30 May 2025 23:49:28 +0900</pubDate>
    </item>
    <item>
      <title>C 와 최소한의 Lib 로 알고리즘 풀어보기 도전</title>
      <link>https://codecreature.tistory.com/220</link>
      <description>&lt;h2&gt;제목 : C 와 최소한의 Lib 로 알고리즘 풀어보기 도전&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;이 도전을 하는 계기&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;수많은 언어와 라이브러리들은 매우 간편하다.&lt;/p&gt;
&lt;p&gt;수많은 언어들은 자체적인 GC(Garbage Collector) 를 갖추고, 특정 언어들은 자체적인 VM 을 갖춰서 실행한다.&lt;/p&gt;
&lt;p&gt;이 과정에서 우리는 메모리 관리를 딱히 할 필요가 없으며, 수 많은 타입과 데이터셋에 대한 자체적인 라이브러리를 지원한다.&lt;/p&gt;
&lt;p&gt;간편한 언어의 접근성과, 대중적인 라이브러리의 조합은 &amp;quot;프레임워크&amp;quot; 로 탄생했고, 비즈니스 논리에 집중 할 수 있게 해 주었다.&lt;/p&gt;
&lt;p&gt;나는 대학교 재학 시절 C 언어를 배우고, Java 언어를 배웠으며, 웹 개발을 위해 JavaScript 를 접하게 되었다.&lt;/p&gt;
&lt;p&gt;그리고 프로그래머스 풀스택 부트캠프를 통해 JavaScript 로 작성되는 백엔드 개발을 프로젝트로 수행했었다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;그러면, 잘하는 언어로 취업을 준비하는 것이 맞지 않나?**&lt;/h3&gt;
&lt;p&gt;나도 이 말에 전적으로 동의한다.&lt;/p&gt;
&lt;p&gt;JavaScript(+ TypeScript) 를 잘하는 사람을 원하는 기업이 있으며,&lt;/p&gt;
&lt;p&gt;현재 Tomcat 기반이나, Spring API 서버를 이용하는 회사는 Java 를 잘하는 사람을 뽑을 것이다.&lt;/p&gt;
&lt;p&gt;특히나 복잡한 현대 세상에서, 확실한 Entity 가 아닌, 행동의 결과물로서 나오는 개념적인 데이터를&lt;/p&gt;
&lt;p&gt;컴퓨터로 이식하는 과정에서, 비즈니스 논리에도 집중하기 힘든 세상이 되었다고 생각한다.&lt;/p&gt;
&lt;p&gt;얼마나 다양하고 많은 비즈니스 논리가 존재하면,&lt;/p&gt;
&lt;p&gt;AOP(관점 지향 프로그래밍) 라는 개념이 중요한 개념이 되었을지 상상이 되지 않는다.&lt;/p&gt;
&lt;p&gt;왠만해서도 복잡한 엔터티들도 정규화하여 대중적인 MySQL, Oracle, MS SQL 과 같은&lt;/p&gt;
&lt;p&gt;RDBMS(Relational Database Management System) 으로도 충분했을 텐데,&lt;/p&gt;
&lt;p&gt;너무나 많은 메타데이터의 출현으로 인해, 카테고리를 딱히 정할 수가 없는 데이터들이 나오기 시작했다.&lt;/p&gt;
&lt;p&gt;따라서, 빅 데이터의 중요성이 떠올랐고, 정확한 정규화와 무결성을 목적으로 하지 않는 NoSQL 이 각광받기 시작했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;다시 돌아와서, &amp;quot;잘하는 언어로 프레임워크를 사용하여, 취업하는것이 맞지 않나?&amp;quot; 라는 의견에는 전적으로 동의한다.&lt;/p&gt;
&lt;p&gt;그러나, 내가 여태까지 컴퓨터를 배울 때는&lt;/p&gt;
&lt;p&gt;&amp;quot;이 방법론(언어, 라이브러리, 프로그래밍, 프로젝트 구조, 인프라 구조 등등) 을 사용하여 비즈니스를 구현한다&amp;quot;&lt;/p&gt;
&lt;p&gt;였다.&lt;/p&gt;
&lt;p&gt;그런데, 나는 이러한 방법론들을 이러한 문제들에 &amp;quot;왜?&amp;quot; 적용하는지 이해 할 수 없었다.&lt;/p&gt;
&lt;p&gt;이전에 작성했던 나의 포트폴리오들과, 이력서를 살펴보며 나는 무엇에 집중했고, 부족했는지를 살펴보았다.&lt;/p&gt;
&lt;p&gt;나의 문제는 이러했다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;특정 도메인에 파고들지 않고, 너무 다양한 도메인에 대한 프로그램을 제작했다.&lt;/li&gt;
&lt;li&gt;하나의 언어에 매우 전문적이지 않다.&lt;/li&gt;
&lt;li&gt;개발자 전국시대에서 내일 당장 투입할 수 있는 인력은 아니다.&lt;/li&gt;
&lt;li&gt;내가 대학교 시절에 수행했던 창업에 대한 이야기를 써 놓았는데, &lt;br/&gt; 이는 회사 입장에서 creative 하게 보이지 않고, 통제하기 힘든 사람으로 보일 수도 있다.&lt;/li&gt;
&lt;li&gt;실제 클라이언트를 사이트로 유치 해 본 적이 없다.&lt;/li&gt;
&lt;li&gt;현재 운영중인 사이트가 없다.&lt;/li&gt;
&lt;li&gt;AI API 를 이용한 프로그램이 존재하지 않는다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이러한 단점을 지금 작성 해 보았는데, 이 중 몇가지는 이미 인지하고 있는 상태이기에, 최근에 진행했던 NestJS 프레임워크에 집중했었다.&lt;/p&gt;
&lt;p&gt;이 NestJS 프레임워크를 공부하면서, 백엔드인 WAS 역할로서, Node.js 엔진 기반의 서버는 분명히 한계점에 빠르게 도달하기 마련이며,&lt;/p&gt;
&lt;p&gt;대부분이 메타프로그래밍 방식으로 이루어진 NestJS 를 보며, 다른 라이브러리에 지나치게 의존하는 NestJS 의 특성상 기술적 한도도 명확하고,&lt;/p&gt;
&lt;p&gt;많은 요청을 처리하기 위해 쓰레드 클러스터를 만들어 분배하거나, 컨테이너 서버 방식으로 여러대를 늘려 서버의 비용을 증가시키기 보단,&lt;/p&gt;
&lt;p&gt;그 노력으로 컴파일 된 서버를 운용하는 것이 기술적 부채, 리소스 확장성, 최적화 면에서 압도적으로 유리하다고 생각했다.&lt;/p&gt;
&lt;p&gt;이러한 분석적 시각을 다른 언어와 프레임워크에 적용하여 고도화 시킨다면, AI로 쉽게 대체 될 수 있는 프로토타입 WAS 를 만들지 않고,&lt;/p&gt;
&lt;p&gt;유니크한 개발 엔지니어가 될 수 있을 것이란 생각에 다다랐다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;NestJS 를 공부하다가 어떤 생각이 들었길래?&lt;/h3&gt;
&lt;p&gt;나는 웹 개발 커뮤니티와, NPM 모듈의 거대함을 느끼고, 쉬운 언어로서 인식했다.&lt;/p&gt;
&lt;p&gt;나는 웹 개발 자체보다는, 인프라와 백엔드에 좀 더 치중되어 있는 사람이다.&lt;/p&gt;
&lt;p&gt;이게 굉장히 어색하다는 것을 나중에 알게 되었는데,&lt;/p&gt;
&lt;p&gt;인프라와 백엔드에 관심이 있으면, Java, Go 와 같은 언어를 전문적으로 다뤄야 하는데,&lt;/p&gt;
&lt;p&gt;NestJS 의 메타프로그래밍 방식의 프레임워크에 호기심을 가져&lt;/p&gt;
&lt;p&gt;프로젝트 이후에 따로 깊이 공부를 하게 되었다.&lt;/p&gt;
&lt;p&gt;그런데 중요한 것은, NestJS 는 주로 웹 개발과 백엔드 프로토타입을 개발하는 사람이 사용하는 프레임워크라는 것을 나중에 알게 되었다.&lt;/p&gt;
&lt;p&gt;나는 NestJS 를 공부하면서, 이것이 왜, 무슨 이유로 사용되는지 알기 위해 Node.js 공부를 병행했다.&lt;/p&gt;
&lt;p&gt;이 과정에서 단일 스레드에, 이벤트 기반 I/O 로 작동하는 Node.js 의 구조와,&lt;/p&gt;
&lt;p&gt;단일 콜스택 스레드를 타파할 수 있는 방식 중 &lt;code&gt;Worker&lt;/code&gt; 라는 기본 라이브러리를 알게 되었다.&lt;/p&gt;
&lt;p&gt;이를 통해, 메인 스레드는 클라이언트의 비즈니스 논리에 집중하고,&lt;/p&gt;
&lt;p&gt;방대한 계산이 필요한 논리는 만들어진 &lt;code&gt;Worker&lt;/code&gt; 스레드에 넘겨 최적화했다.&lt;/p&gt;
&lt;p&gt;이 과정에서, 나는 Spring 과 NestJS 간의 성능 차이를 보게 되었고,&lt;/p&gt;
&lt;p&gt;NestJS 가 Node.js 베이스인 만큼, 메모리 사용량이 높으며, 속도가 느릴 수 밖에 없다는 것을 인지했다.&lt;/p&gt;
&lt;p&gt;따라서, &lt;code&gt;Worker&lt;/code&gt; 를 배우게 되었는데, 나는 더 나아가서 &lt;code&gt;Worker&lt;/code&gt; 에 &lt;code&gt;.wasm&lt;/code&gt; (웹 어셈블리) 를 적용했다.&lt;/p&gt;
&lt;p&gt;테스트에서 속도는 월등히 빨라졌다.&lt;/p&gt;
&lt;p&gt;그러나, 나는 이 과정과 결과에서 엄청난 회의감을 느낄 수 밖에 없었다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;차라리 저 수준 언어와 라이브러리를 사용하는 것이 낫겠는데?&lt;/h3&gt;
&lt;p&gt;나는 웹 어셈블리를 &lt;code&gt;Worker&lt;/code&gt; 이라는 새로운 스레드에 반영하는 과정에서,&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AssemblyScript&lt;/strong&gt; 를 사용하게 되었고, 이 언어는 마치 타입스크립트와 비슷하다.&lt;/p&gt;
&lt;p&gt;그러나, 이 작성 방식은 개발자 사이에서 주된 방식은 아니며, 주로 웹 어셈블리 모듈(&lt;code&gt;.wasm&lt;/code&gt;) 은&lt;/p&gt;
&lt;p&gt;C++ 이나 Rust 로 주로 작성되었다.&lt;/p&gt;
&lt;p&gt;웹 어셈블리 모듈을 제작하는 수많은 예제들을 보면서,&lt;/p&gt;
&lt;p&gt;내가 AssemblyScript 로 모듈을 작성하기 위해 설정하는 노력보다,&lt;/p&gt;
&lt;p&gt;저 수준의 언어로 모듈을 작성하는 것이 훨씬 낫다는 생각을 했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그래도, JavaScript 와 TypeScript 가 가진 방대한 모듈과 접근성을 위해서라면,&lt;/p&gt;
&lt;p&gt;언어가 어려워지기 보다는 좀 더 쉬워서 커뮤니티 크기를 유치하는 것이 더 유리하겠다는 생각이 들었다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;JavaScript 를 익히며 느낀 회의감이 왜 C 로 알고리즘을 푸는 것으로 이어지나?&lt;/h3&gt;
&lt;p&gt;나는 이전에 알고리즘을 풀 때, Java 로 해결했다. (정확히 java 11)&lt;/p&gt;
&lt;p&gt;물론, 기업들이 알고리즘 테스트를 진행하기 때문에 시작하긴 했다.&lt;/p&gt;
&lt;p&gt;이 과정에서, 입출력, 배열 관리, 인덱싱(색인), DP, 그리디, BFS, DFS, Set, Map, Binary Search, Tree 등등.. 을 사용하여 문제를 풀었다.&lt;/p&gt;
&lt;p&gt;물론, 정렬 방법론에서 log_2 N 을 대부분 보장하는 Merge(병합 정렬), Heap(힙 정렬) 과 같은 정렬도 직접 구현했다.&lt;/p&gt;
&lt;p&gt;위의 과정은 그냥 &lt;code&gt;Arrays.sort(정렬할 배열)&lt;/code&gt; 하면 자동으로 해 준다. or &lt;code&gt;Collections.sort(...)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;그런데, 이 중 &lt;code&gt;Set&lt;/code&gt;, &lt;code&gt;Map&lt;/code&gt; 에 대한 것을 java 의 기본 라이브러리인 java.util 에서 가져온다는 것에 집중했다.&lt;/p&gt;
&lt;p&gt;Set 과 Map 자료구조 클래스는 왜 특정 메서드만 가지고 있는 것인지, 어떤 확장성을 가지고 있는지, 실제로 어떻게 작동했는지 궁금했다.&lt;/p&gt;
&lt;p&gt;따라서, &lt;code&gt;Set&lt;/code&gt;, &lt;code&gt;Map&lt;/code&gt; 을 직접 구현하며 재귀에 대한 추가적인 지식과,&lt;/p&gt;
&lt;p&gt;레벨링이 2 이상 차이나지 않게 조절하도록 알고리즘을 만들었다. EX - LeftRotate, RightRotate&lt;/p&gt;
&lt;p&gt;물론, JVM 이 메모리 관리를 수행하기 때문에, (Eden, Old 분류 덕분) 내가 직접 조절 할 필요는 없었지만,&lt;/p&gt;
&lt;p&gt;실제로 Tree 의 구성과 관리, 어떤 자료구조(클래스) 가 필요한지, 속성이 필요한지,&lt;/p&gt;
&lt;p&gt;이 상황에서 어떤 정렬이 효과적인지, 백준에서 각 문제별로 정렬 알고리즘의 메모리 사용량과 시간을 비교하며 문제를 풀었다.&lt;/p&gt;
&lt;p&gt;특히, heap 정렬은 재귀적인 특성이 강하고, 매 랜덤 배열마다 하나의 재귀가 어느 깊이까지 갈지 모르기 때문에 log2N 을 충족하기 힘들어&lt;/p&gt;
&lt;p&gt;시간과 메모리가 많이 소요될 것이라고 생각했는데, 가장 메모리와 시간을 적게 먹어서 놀랐던 경험이 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;요약하자면&lt;/strong&gt; : 나는 방법론 보다는, 이것이 어떻게 동작하고, 왜 사용해야 하는지에 집중했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;내가 부트캠프에서 배우고, 프로젝트까지 진행했던 JS + TS 의 언어적 한계점과, Node.js 의 성능적 한계점을 알게 된 뒤,&lt;/p&gt;
&lt;p&gt;나는 내가 배우고 있는 프레임워크 자체에 빨려들어가고 있다고 자각했다.&lt;/p&gt;
&lt;p&gt;즉, 프레임워크가 편하여 이를 마치 언어처럼 배우고 있었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;나는 빠르게 발전하는 것을 넘어, 지속적으로 변화하는 개발 트렌드에도 나 자신을 맞추기가 어렵다고 생각한다.&lt;/p&gt;
&lt;p&gt;이것을 어떻게 해소 할 수 있는가? 를 지속적으로 고민했다.&lt;/p&gt;
&lt;p&gt;그 결과로, 다양한 언어들이 참고했으며, 패러다임을 참고 한 곳, 바로 C 언어였다.&lt;/p&gt;
&lt;p&gt;웹 개발을 하면 당연하게도 JavaScript 를 사용하겠지만, Was 환경에서 JS 를 사용할 수 밖에 없다면,&lt;/p&gt;
&lt;p&gt;내가 직접 모듈을 저수준 언어로 개발하여 장착 할 수도 있겠다는 생각이 들었다.&lt;/p&gt;
&lt;p&gt;그리고, C 를 사용하며 불편함을 느낄 텐데, 이러한 불편함은 내가 편한 언어를 사용하며 놓친 컴퓨터 지식이라고 생각하고,&lt;/p&gt;
&lt;p&gt;더욱 공부하려고 한다.&lt;/p&gt;
&lt;p&gt;사실 이미 C 언어에서 알고리즘 문제 1개를 풀었는데,&lt;/p&gt;
&lt;p&gt;나는 &lt;code&gt;scanf&lt;/code&gt; 대신에, &lt;code&gt;fgets&lt;/code&gt; 를 사용하여 한 줄의 입력을 받았으며,&lt;/p&gt;
&lt;p&gt;이를 토큰화 한 뒤, 숫자로 만들어서 계산하고,&lt;/p&gt;
&lt;p&gt;이를 출력했다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;printf&lt;/code&gt; 는 &lt;code&gt;fputs&lt;/code&gt; 로 할 수 있었는데,&lt;/p&gt;
&lt;p&gt;숫자를 다시 문자로 바꾸는 과정까지 만들면 일단 300 줄이 넘어갈 것 같아서 다음 문제부터 구현하려고 한다.&lt;/p&gt;
&lt;br/&gt;</description>
      <category>백준-단계별로 풀어보기</category>
      <category>C</category>
      <category>직접 구현</category>
      <category>회고록</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/220</guid>
      <comments>https://codecreature.tistory.com/220#entry220comment</comments>
      <pubDate>Thu, 29 May 2025 23:06:25 +0900</pubDate>
    </item>
    <item>
      <title>HTML 문서 정식 태그 &amp;quot;전부&amp;quot; 공부하기 - 3편 (코드 작성 및 구현까지)</title>
      <link>https://codecreature.tistory.com/219</link>
      <description>&lt;h2&gt;제목 : HTML 문서 태그 공부하기 - 3편&lt;/h2&gt;
&lt;hr&gt;
&lt;h3&gt;2편 글 주소&lt;/h3&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/218&quot;&gt;https://codecreature.tistory.com/218&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;이 글을 작성하는 이유&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;나는 정식 스펙상에 존재하는 &amp;quot;모든&amp;quot; HTML 태그들을 공부하고 있다.&lt;/p&gt;
&lt;p&gt;이는 어찌 보면 미련한 짓일수도 있고, 미친짓을 하는 것일 수도 있다.&lt;/p&gt;
&lt;p&gt;그러나, 앞으로 다양한 웹 프레임워크와 라이브러리는 종류가 다양해지고 있는데,&lt;/p&gt;
&lt;p&gt;트렌드를 따라가기 위해 위 프로그램들의 공식문서를 보며, 공부하는 것도 괜찮다고 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, 지속적으로 점유율이 바뀌고 있는 프로그램들을 살펴보았을 때,&lt;/p&gt;
&lt;p&gt;내가 적용하고자 하는 웹 라이브러리와 프레임워크가 존재 할 때,&lt;/p&gt;
&lt;p&gt;이러한 태그를 왜 사용해야 하는지, 공식문서에서 간단한 헤더 영역을 만들 때,&lt;/p&gt;
&lt;p&gt;왜 해당 태그를 사용하는지 이해하지 못한 상태로 공부하고 싶지는 않았다.&lt;/p&gt;
&lt;p&gt;그리고 특히, 해당 태그들이 가지는 일종의 &amp;quot;컨벤션&amp;quot;과 Default 스타일을 알고 싶었다.&lt;/p&gt;
&lt;p&gt;그리고, 태그들이 가지는 여러 속성들도 공부하며, 어떤 상황에 사용하는 것이 적합한지 알고 싶었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;2편까지 한 70 개 정도의 태그들을 다뤘다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;기본적인 HTML 태그&lt;/li&gt;
&lt;li&gt;인라인 Formatter 태그&lt;/li&gt;
&lt;li&gt;Form 과 Input 관련 태그들&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;블로그에 실제 인라인 예시와 form and input 관련 태그 예시들을 실제로 블로그에 구현하는 과정에서&lt;/p&gt;
&lt;p&gt;내가 예상했던 글 작성 속도가 늦춰지게 되었다.&lt;/p&gt;
&lt;p&gt;그러나, 이 과정에서 다시 웹 태그 작성 스킬이 조금씩 늘어나는 것을 느낄 수 있었다.&lt;/p&gt;
&lt;p&gt;조금만 더 하면, 이제 모든 태그들을 알 수 있게 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Category - Images (이미지)&lt;/h2&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;img&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;이미지를 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이미지 태그는 주어지는 &lt;code&gt;src&lt;/code&gt; 속성에 따라 유연하게 여러 소스들을 표현한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;jpg&lt;/code&gt;, &lt;code&gt;jpeg&lt;/code&gt;, &lt;code&gt;gif&lt;/code&gt;, &lt;code&gt;png&lt;/code&gt; 등등 여러 소스를 유연하게 파싱하여 표현한다.&lt;/p&gt;
&lt;p&gt;그리고, &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; 태그는 &lt;code&gt;src&lt;/code&gt;, &lt;code&gt;alt&lt;/code&gt; 라는 속성을 필수로 가져야 한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;src&lt;/code&gt; : 이미지를 가져 올 장소 혹은 소스&lt;/li&gt;
&lt;li&gt;&lt;code&gt;alt&lt;/code&gt; : 이미지 표시에 실패했을 때, 대신 보여줄 텍스트 혹은 이미지&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;그리고, 이미지를 클릭했을 때, 특정 사이트로 안내 해 주고 싶다면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; 태그 내부에 &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; 를 넣어 안내 할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;p&amp;gt;
    이미지에서
    &amp;lt;img
        src=&amp;quot;https://img1.daumcdn.net/thumb/C428x428/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Ftistory1.daumcdn.net%2Ftistory%2F6142901%2Fattach%2Feaf30c1affbe45adb0179071560647bc&amp;quot;
        alt=&amp;quot;내 블로그 유저 이미지&amp;quot;
        width=&amp;quot;50&amp;quot;
        height=&amp;quot;50&amp;quot;
    /&amp;gt;
    src 속성이 올바르게 로딩되었을 때
&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;
    이미지에서
    &amp;lt;img
        src=&amp;quot;&amp;quot;
        alt=&amp;quot;내 블로그 유저 이미지&amp;quot;
        width=&amp;quot;50&amp;quot;
        height=&amp;quot;50&amp;quot;
    /&amp;gt;
    src 속성이 로딩되지 못했을 때
&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;
    이미지에서
    &amp;lt;img
        src=&amp;quot;https://img1.daumcdn.net/thumb/C428x428/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Ftistory1.daumcdn.net%2Ftistory%2F6142901%2Fattach%2Feaf30c1affbe45adb0179071560647bc&amp;quot;
        alt=&amp;quot;내 블로그 유저 이미지&amp;quot;
        width=&amp;quot;50&amp;quot;
        height=&amp;quot;50&amp;quot;
        style=&amp;quot;vertical-align : bottom;&amp;quot;
    /&amp;gt;
    style 이 vertical-align : bottom 일 때
&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;
    이미지에서
    &amp;lt;img
        src=&amp;quot;https://img1.daumcdn.net/thumb/C428x428/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Ftistory1.daumcdn.net%2Ftistory%2F6142901%2Fattach%2Feaf30c1affbe45adb0179071560647bc&amp;quot;
        alt=&amp;quot;내 블로그 유저 이미지&amp;quot;
        width=&amp;quot;50&amp;quot;
        height=&amp;quot;50&amp;quot;
        style=&amp;quot;vertical-align : middle;&amp;quot;
    /&amp;gt;
    style 이 vertical-align : middle
&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;
    이미지에서
    &amp;lt;img
        src=&amp;quot;https://img1.daumcdn.net/thumb/C428x428/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Ftistory1.daumcdn.net%2Ftistory%2F6142901%2Fattach%2Feaf30c1affbe45adb0179071560647bc&amp;quot;
        alt=&amp;quot;내 블로그 유저 이미지&amp;quot;
        width=&amp;quot;50&amp;quot;
        height=&amp;quot;50&amp;quot;
        style=&amp;quot;vertical-align : top&amp;quot;
    /&amp;gt;
    style 이 vertical-align : top
&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;
    이미지에서
    &amp;lt;img
        src=&amp;quot;https://img1.daumcdn.net/thumb/C428x428/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Ftistory1.daumcdn.net%2Ftistory%2F6142901%2Fattach%2Feaf30c1affbe45adb0179071560647bc&amp;quot;
        alt=&amp;quot;내 블로그 유저 이미지&amp;quot;
        width=&amp;quot;50&amp;quot;
        height=&amp;quot;50&amp;quot;
        style=&amp;quot;float : right&amp;quot;
    /&amp;gt;
    style 이 float : right 일 때
&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;
    이미지에서
    &amp;lt;img
        src=&amp;quot;https://img1.daumcdn.net/thumb/C428x428/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Ftistory1.daumcdn.net%2Ftistory%2F6142901%2Fattach%2Feaf30c1affbe45adb0179071560647bc&amp;quot;
        alt=&amp;quot;내 블로그 유저 이미지&amp;quot;
        width=&amp;quot;50&amp;quot;
        height=&amp;quot;50&amp;quot;
        style=&amp;quot;float : left&amp;quot;
    /&amp;gt;
    style 이 float : left 일 때
&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt; 이미지에서
    &amp;lt;img
        src=&amp;quot;https://img1.daumcdn.net/thumb/C428x428/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Ftistory1.daumcdn.net%2Ftistory%2F6142901%2Fattach%2Feaf30c1affbe45adb0179071560647bc&amp;quot;
        alt=&amp;quot;내 블로그 유저 이미지&amp;quot;
        width=&amp;quot;50&amp;quot;
        height=&amp;quot;50&amp;quot;
        style=&amp;quot;margin : 0px 50px&amp;quot;
    /&amp;gt;
    양 옆 마진을 50 픽셀을 넣어줄 때
&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;
    이미지에서
    &lt;img
        src=&quot;https://img1.daumcdn.net/thumb/C428x428/?scode=mtistory2&amp;fname=https%3A%2F%2Ftistory1.daumcdn.net%2Ftistory%2F6142901%2Fattach%2Feaf30c1affbe45adb0179071560647bc&quot;
        alt=&quot;내 블로그 유저 이미지&quot;
        width=&quot;50&quot;
        height=&quot;50&quot;
    /&gt;
    src 속성이 올바르게 로딩되었을 때
&lt;/p&gt;

&lt;p&gt;
    이미지에서
    &lt;img
        src=&quot;&quot;
        alt=&quot;내 블로그 유저 이미지&quot;
        width=&quot;50&quot;
        height=&quot;50&quot;
    /&gt;
    src 속성이 로딩되지 못했을 때
&lt;/p&gt;

&lt;p&gt;
    이미지에서
    &lt;img
        src=&quot;https://img1.daumcdn.net/thumb/C428x428/?scode=mtistory2&amp;fname=https%3A%2F%2Ftistory1.daumcdn.net%2Ftistory%2F6142901%2Fattach%2Feaf30c1affbe45adb0179071560647bc&quot;
        alt=&quot;내 블로그 유저 이미지&quot;
        width=&quot;50&quot;
        height=&quot;50&quot;
        style=&quot;vertical-align : bottom;&quot;
    /&gt;
    style 이 vertical-align : bottom 일 때
&lt;/p&gt;

&lt;p&gt;
    이미지에서
    &lt;img
        src=&quot;https://img1.daumcdn.net/thumb/C428x428/?scode=mtistory2&amp;fname=https%3A%2F%2Ftistory1.daumcdn.net%2Ftistory%2F6142901%2Fattach%2Feaf30c1affbe45adb0179071560647bc&quot;
        alt=&quot;내 블로그 유저 이미지&quot;
        width=&quot;50&quot;
        height=&quot;50&quot;
        style=&quot;vertical-align : middle;&quot;
    /&gt;
    style 이 vertical-align : middle
&lt;/p&gt;

&lt;p&gt;
    이미지에서
    &lt;img
        src=&quot;https://img1.daumcdn.net/thumb/C428x428/?scode=mtistory2&amp;fname=https%3A%2F%2Ftistory1.daumcdn.net%2Ftistory%2F6142901%2Fattach%2Feaf30c1affbe45adb0179071560647bc&quot;
        alt=&quot;내 블로그 유저 이미지&quot;
        width=&quot;50&quot;
        height=&quot;50&quot;
        style=&quot;vertical-align : top&quot;
    /&gt;
    style 이 vertical-align : top
&lt;/p&gt;

&lt;p&gt;
    이미지에서
    &lt;img
        src=&quot;https://img1.daumcdn.net/thumb/C428x428/?scode=mtistory2&amp;fname=https%3A%2F%2Ftistory1.daumcdn.net%2Ftistory%2F6142901%2Fattach%2Feaf30c1affbe45adb0179071560647bc&quot;
        alt=&quot;내 블로그 유저 이미지&quot;
        width=&quot;50&quot;
        height=&quot;50&quot;
        style=&quot;float : right&quot;
    /&gt;
    style 이 float : right 일 때
&lt;/p&gt;

&lt;p&gt;
    이미지에서
    &lt;img
        src=&quot;https://img1.daumcdn.net/thumb/C428x428/?scode=mtistory2&amp;fname=https%3A%2F%2Ftistory1.daumcdn.net%2Ftistory%2F6142901%2Fattach%2Feaf30c1affbe45adb0179071560647bc&quot;
        alt=&quot;내 블로그 유저 이미지&quot;
        width=&quot;50&quot;
        height=&quot;50&quot;
        style=&quot;float : left&quot;
    /&gt;
    style 이 float : left 일 때
&lt;/p&gt;

&lt;p&gt; 이미지에서
    &lt;img
        src=&quot;https://img1.daumcdn.net/thumb/C428x428/?scode=mtistory2&amp;fname=https%3A%2F%2Ftistory1.daumcdn.net%2Ftistory%2F6142901%2Fattach%2Feaf30c1affbe45adb0179071560647bc&quot;
        alt=&quot;내 블로그 유저 이미지&quot;
        width=&quot;50&quot;
        height=&quot;50&quot;
        style=&quot;margin : 0px 50px&quot;
    /&gt;
    양 옆 마진을 50 픽셀을 넣어줄 때
&lt;/p&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; 속성들 :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;alt&lt;/code&gt; : 이미지를 대신 할 수 있는 텍스트 지정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;crossorigin&lt;/code&gt; : Third-Party(다른 사이트) 에서 이미지를 가져올 수 있게 허용할 수 있음&lt;ul&gt;
&lt;li&gt;&lt;code&gt;anonymous&lt;/code&gt; : 허용&lt;/li&gt;
&lt;li&gt;&lt;code&gt;use-credentials&lt;/code&gt; : 같은 origin 이어야만 허용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;height&lt;/code&gt; : 높이&lt;/li&gt;
&lt;li&gt;&lt;code&gt;width&lt;/code&gt; : 넓이&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ismap&lt;/code&gt; : 서버 측의 이미지 map 인지 지정한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;loading&lt;/code&gt; : 브라우저가 로딩 될 때 바로 이미지를 구현 할 지, 특정 조건 만족 시 까지 기다릴지 지정&lt;ul&gt;
&lt;li&gt;&lt;code&gt;eager&lt;/code&gt; : 즉시&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lazy&lt;/code&gt; : 천천히&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;longdesc&lt;/code&gt; : 이미지에 대한 자세한 설명을 가진 URL 을 지정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;referrerpolicy&lt;/code&gt; : 참조 정책 - 이미지는 저작권에 관련이 되어 있으므로.&lt;ul&gt;
&lt;li&gt;다양한 값이 있으므로, &lt;a target=&quot;_blank&quot; href=&quot;https://www.w3schools.com/TAGS/tag_img.asp#:~:text=of%20an%20image-,referrerpolicy,-no%2Dreferrer%0Ano&quot;&gt;여기&lt;/a&gt; 를 참조&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sizes&lt;/code&gt; : 다양한 페이지 레이아웃에 대한 이미지 크기를 지정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src&lt;/code&gt; : 이미지의 경로 혹은 소스를 지정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;srcset&lt;/code&gt; : 경로 혹은 소스에 대한 리스트이며, 서로 다른 상황에 사용할 이미지 파일들의 배열&lt;/li&gt;
&lt;li&gt;&lt;code&gt;usemap&lt;/code&gt; : EX - (&lt;code&gt;#mapname&lt;/code&gt;) 클라이언트 측의 이미지 map 을 지정&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;img {
    display : inline-block;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;map&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;클라이언트 측의 이미지 map 을 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;도대체 이 &lt;code&gt;map&lt;/code&gt; 이라는 것이 무엇인가? 모르겠었는데,&lt;/p&gt;
&lt;p&gt;이미지 내부에서 우리가 지정한 map 영역 을 클릭하면,&lt;/p&gt;
&lt;p&gt;상호작용 할 수 있게 해 주는 태그이다.&lt;/p&gt;
&lt;p&gt;그러니까, 우리가 옛날에 플래시 게임. 즉, 예를 들어 퍼즐 플래시 게임같은 것을 할 때,&lt;/p&gt;
&lt;p&gt;우리는 단서를 찾기 위해서 마우스를 모든 곳에 클릭 해 본 경험이 있을 것이다.&lt;/p&gt;
&lt;p&gt;같은 기술을 아니지만, 거의 비슷한 경험을 주는 태그이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이것을 도대체 어떻게 구현해야 할까.... 하다가,&lt;/p&gt;
&lt;p&gt;위에서 사용한 나의 유저 이미지를 다시 활용하기로 했다.&lt;/p&gt;
&lt;p&gt;상단부터 절반까지, 해당 영역을 클릭 시, 나의 블로그 메인으로 새 창이 띄워질 것이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;라고 생각했는데,&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;블로그 글 자체가 샌드박스화 되어 있어&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;map&amp;gt;&lt;/code&gt; 내부의 &lt;code&gt;area&lt;/code&gt; 에서 창을 따로 띄울 수 없었다.&lt;/p&gt;
&lt;p&gt;따라서, &lt;code&gt;area&lt;/code&gt; 에 따로 &lt;code&gt;onclick&lt;/code&gt; 을 달아 상호작용만 확인 할 수 있게 바꿨다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;p&amp;gt;
    &amp;lt;img
        src=&amp;quot;https://img1.daumcdn.net/thumb/C428x428/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Ftistory1.daumcdn.net%2Ftistory%2F6142901%2Fattach%2Feaf30c1affbe45adb0179071560647bc&amp;quot;
        alt=&amp;quot;내 블로그 유저 이미지&amp;quot;
        width=&amp;quot;200&amp;quot;
        height=&amp;quot;200&amp;quot;
        usemap=&amp;quot;#map-test&amp;quot;
        id=&amp;quot;map-img&amp;quot;
    /&amp;gt;
    &amp;lt;map name=&amp;quot;map-test&amp;quot; id=&amp;quot;map-test&amp;quot;&amp;gt;
    &amp;lt;/map&amp;gt;
&amp;lt;/p&amp;gt;

&amp;lt;div id=&amp;quot;map-result&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;

&amp;lt;script&amp;gt;
const mapDOM = document.getElementById(&amp;quot;map-test&amp;quot;);
const imgDOM = document.getElementById(&amp;quot;map-img&amp;quot;);
const resultDOM = document.getElementById(&amp;quot;map-result&amp;quot;);
let isMapOn = false;

mapDOM.innerHTML = `
  &amp;lt;area
    shape=&amp;quot;rect&amp;quot;
    coords=&amp;quot;0,0,${imgDOM.width},${imgDOM.height / 2}&amp;quot;
    alt=&amp;quot;test&amp;quot;
    onclick=&amp;quot;mapFunc()&amp;quot;
  /&amp;gt;
  `;

function mapFunc() {
  if(isMapOn) {
    resultDOM.innerHTML = &amp;quot;&amp;quot;;
    isMapOn = !isMapOn;
  } else {
    resultDOM.innerHTML = `&amp;lt;p&amp;gt;이미지 상단을 클릭하셨습니다. 이 문구를 없애려면 한 번 더 클릭하세요.&amp;lt;/p&amp;gt;`;
    isMapOn = !isMapOn;
  }
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;
    &lt;img
        src=&quot;https://img1.daumcdn.net/thumb/C428x428/?scode=mtistory2&amp;fname=https%3A%2F%2Ftistory1.daumcdn.net%2Ftistory%2F6142901%2Fattach%2Feaf30c1affbe45adb0179071560647bc&quot;
        alt=&quot;내 블로그 유저 이미지&quot;
        width=&quot;200&quot;
        height=&quot;200&quot;
        usemap=&quot;#map-test&quot;
        id=&quot;map-img&quot;
    /&gt;
    &lt;map name=&quot;map-test&quot; id=&quot;map-test&quot;&gt;
    &lt;/map&gt;
&lt;/p&gt;

&lt;div id=&quot;map-result&quot;&gt;&lt;/div&gt;

&lt;script&gt;
const mapDOM = document.getElementById(&quot;map-test&quot;);
const imgDOM = document.getElementById(&quot;map-img&quot;);
const resultDOM = document.getElementById(&quot;map-result&quot;);
let isMapOn = false;

mapDOM.innerHTML = `
  &lt;area
    shape=&quot;rect&quot;
    coords=&quot;0,0,${imgDOM.width},${imgDOM.height / 2}&quot;
    alt=&quot;test&quot;
    onclick=&quot;mapFunc()&quot;
  /&gt;
  `;

function mapFunc() {
  if(isMapOn) {
    resultDOM.innerHTML = &quot;&quot;;
    isMapOn = !isMapOn;
  } else {
    resultDOM.innerHTML = `&lt;p&gt;이미지 상단을 클릭하셨습니다. 이 문구를 없애려면 한 번 더 클릭하세요.&lt;/p&gt;`;
    isMapOn = !isMapOn;
  }
}
&lt;/script&gt;

&lt;br/&gt;

&lt;p&gt;이미지 상단을 클릭해 보면, 문구가 나올텐데,&lt;/p&gt;
&lt;p&gt;이제 한 번 더 상단을 클릭 해 보면, 해당 문구를 사라지게 스위칭 함수로 제작했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;map&amp;gt;&lt;/code&gt; 속성 :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;name&lt;/code&gt; : &lt;code&gt;img&lt;/code&gt; 가 참조할 일종의 &lt;code&gt;map&lt;/code&gt; 의 &lt;code&gt;id&lt;/code&gt; 이름을 지정한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;map&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;map {
    display : inline;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;area&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;이미지 map 내부의 영역을 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;위에서 사용했던 것을 보면 된다.&lt;/p&gt;
&lt;p&gt;만약에, 이미지를 클릭 시 페이지를 이동하도록 만들고 싶다면,&lt;/p&gt;
&lt;p&gt;W3Schools 의 예제를 보면 알 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;img src=&amp;quot;planets.gif&amp;quot; width=&amp;quot;145&amp;quot; height=&amp;quot;126&amp;quot; alt=&amp;quot;Planets&amp;quot;
usemap=&amp;quot;#planetmap&amp;quot;&amp;gt;

&amp;lt;map name=&amp;quot;planetmap&amp;quot;&amp;gt;
  &amp;lt;area shape=&amp;quot;rect&amp;quot; coords=&amp;quot;0,0,82,126&amp;quot; href=&amp;quot;sun.htm&amp;quot; alt=&amp;quot;Sun&amp;quot;&amp;gt;
  &amp;lt;area shape=&amp;quot;circle&amp;quot; coords=&amp;quot;90,58,3&amp;quot; href=&amp;quot;mercur.htm&amp;quot; alt=&amp;quot;Mercury&amp;quot;&amp;gt;
  &amp;lt;area shape=&amp;quot;circle&amp;quot; coords=&amp;quot;124,58,8&amp;quot; href=&amp;quot;venus.htm&amp;quot; alt=&amp;quot;Venus&amp;quot;&amp;gt;
&amp;lt;/map&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 예제는 이미지에 대한 영역 선언과 함께, 어떠한 방식으로 연결시키는지 볼 수 있다.&lt;/p&gt;
&lt;p&gt;나의 사이트에서는 이를 구현 할 수 없는데,&lt;/p&gt;
&lt;p&gt;이는 내 사이트에 &lt;code&gt;sum.htm&lt;/code&gt;, &lt;code&gt;mercur.htm&lt;/code&gt;, &lt;code&gt;venus.htm&lt;/code&gt; 에 대한 페이지 소스가 없을 뿐만 아니라,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;planets.gif&lt;/code&gt; 에 대한 리소스도 없다.&lt;/p&gt;
&lt;p&gt;리소스 주소가 아니라, 리소스 경로를 의미하므로, 이는 웹 사이트 서버 (웹 서버) 에 요구하는 것인데,&lt;/p&gt;
&lt;p&gt;내 블로그에는 그런 리소스를 탑재하지 않았다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;area&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;area {
    display : none;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;canvas&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;JavaScript 를 통해 그래픽을 그리는 데 사용되는 태그이다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;예제를 보니까, 대부분의 작업을 JS 를 이용하여 컨텍스트를 추출하고,&lt;/p&gt;
&lt;p&gt;해당 컨텍스트 기능을 통하여 &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; 에 그림을 그리는 기능을 가지고 있다.&lt;/p&gt;
&lt;p&gt;기본 속성으로 &lt;code&gt;width&lt;/code&gt;, &lt;code&gt;height&lt;/code&gt; 를 가지고 있으며, 기본 값은 각각 &lt;code&gt;300&lt;/code&gt;, &lt;code&gt;150&lt;/code&gt; 으로,&lt;/p&gt;
&lt;p&gt;수평으로 길게 되어 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;canvas id=&amp;quot;canvas-test-1&amp;quot;&amp;gt;
    렌더링 안 되면 이 텍스트가 나옴
&amp;lt;/canvas&amp;gt;

&amp;lt;script&amp;gt;
const canvas1 = document.getElementById(&amp;quot;canvas-test-1&amp;quot;);
const context1 = canvas1.getContext(&amp;quot;2d&amp;quot;);
context1.fillStyle = &amp;quot;red&amp;quot;;
context1.fillRect(0, 0, canvas1.width, canvas1.height);

context1.fillStyle = &amp;quot;green&amp;quot;;
context1.fillRect(0, 0, canvas1.width, canvas1.height / 2);

context1.fillStyle = &amp;quot;blue&amp;quot;;
context1.fillRect(0, 0, canvas1.width / 2, canvas1.height / 2);
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;canvas id=&quot;canvas-test-1&quot;&gt;
    렌더링 안 되면 이 텍스트가 나옴
&lt;/canvas&gt;

&lt;script&gt;
const canvas1 = document.getElementById(&quot;canvas-test-1&quot;);
const context1 = canvas1.getContext(&quot;2d&quot;);
context1.fillStyle = &quot;red&quot;;
context1.fillRect(0, 0, canvas1.width, canvas1.height);

context1.fillStyle = &quot;green&quot;;
context1.fillRect(0, 0, canvas1.width, canvas1.height / 2);

context1.fillStyle = &quot;blue&quot;;
context1.fillRect(0, 0, canvas1.width / 2, canvas1.height / 2);
&lt;/script&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;canvas {
    height : 150px;
    width : 300px;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;figcaption&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;figure&amp;gt;&lt;/code&gt; &lt;strong&gt;요소 내부의 캡션을 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;figurecaption&amp;gt;&lt;/code&gt; 은 내부에 담겨질 &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; 소스에 대한 설명을 의미한다.&lt;/p&gt;
&lt;p&gt;즉, &lt;code&gt;&amp;lt;figure&amp;gt;&lt;/code&gt; 은 이미지와 설명에 대한 일종의 컨테이너라고 볼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;figure&amp;gt;
    &amp;lt;img
        src=&amp;quot;https://img1.daumcdn.net/thumb/C428x428/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Ftistory1.daumcdn.net%2Ftistory%2F6142901%2Fattach%2Feaf30c1affbe45adb0179071560647bc&amp;quot;
        width=&amp;quot;200&amp;quot;
        height=&amp;quot;200&amp;quot;
        alt=&amp;quot;내 이미지&amp;quot;/&amp;gt;
    &amp;lt;figcaption&amp;gt;제 블로그를 대표하는 이미지입니다.&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;figure&gt;
    &lt;img
        src=&quot;https://img1.daumcdn.net/thumb/C428x428/?scode=mtistory2&amp;fname=https%3A%2F%2Ftistory1.daumcdn.net%2Ftistory%2F6142901%2Fattach%2Feaf30c1affbe45adb0179071560647bc&quot;
        width=&quot;200&quot;
        height=&quot;200&quot;
        alt=&quot;내 이미지&quot;/&gt;
    &lt;figcaption&gt;제 블로그를 대표하는 이미지입니다.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;figcaption&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;figcaption {
    display : block;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;figure&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;위에서 보았듯이, 이미지와 캡션과 같은 컨텐츠를 담는 컨테이너를 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;figure&amp;gt;
    &amp;lt;img
        src=&amp;quot;https://img1.daumcdn.net/thumb/C428x428/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Ftistory1.daumcdn.net%2Ftistory%2F6142901%2Fattach%2Feaf30c1affbe45adb0179071560647bc&amp;quot;
        width=&amp;quot;200&amp;quot;
        height=&amp;quot;200&amp;quot;
        alt=&amp;quot;내 이미지&amp;quot;/&amp;gt;
    &amp;lt;figcaption&amp;gt;제 블로그를 대표하는 이미지입니다.&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;figure&gt;
    &lt;figcaption&gt;캡션은 위에도 올 수 있다.&lt;/figcaption&gt;
    &lt;img
        src=&quot;https://img1.daumcdn.net/thumb/C428x428/?scode=mtistory2&amp;fname=https%3A%2F%2Ftistory1.daumcdn.net%2Ftistory%2F6142901%2Fattach%2Feaf30c1affbe45adb0179071560647bc&quot;
        width=&quot;200&quot;
        height=&quot;200&quot;
        alt=&quot;내 이미지&quot;/&gt;
&lt;/figure&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;figure&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;figure {
    display : block;
    margin-top : 1em;
    margin-bottom : 1em;
    margin-left : 40px;
    margin-right : 40px;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;picture&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;여러 이미지 리소스에 대한 컨테이너&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이게 어떤 의미인지 헷갈렸는데,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; 태그 내부에는 2 가지 태그를 담는다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; 태그는 이미지 리소스를 지정하는 데 더 많은 유연성을 제공한다고 작성되어 있다.&lt;/p&gt;
&lt;p&gt;이 태그는 현재 미디어의 상태에 따라 다른 리소스를 표출하는 특수한 컨테이너의 일종이다.&lt;/p&gt;
&lt;p&gt;우리가 &lt;code&gt;img&lt;/code&gt; 태그에 &lt;code&gt;alt&lt;/code&gt; 를 작성해서 혹시 모를 에러에 대처하는 것 처럼,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; 태그 또한, &lt;code&gt;alt&lt;/code&gt; 와 비슷하게 마지막으로 &lt;code&gt;img&lt;/code&gt; 태그를 사용하여,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; 태그의 속성인 &lt;code&gt;media&lt;/code&gt; 쿼리에 모두 해당하지 않는 경우,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; 로 fallback 하도록 만들었다.&lt;/p&gt;
&lt;p&gt;이에 대한 예시는 W3Schools 를 가져오겠다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;picture&amp;gt;
  &amp;lt;source media=&amp;quot;(min-width:650px)&amp;quot; srcset=&amp;quot;img_pink_flowers.jpg&amp;quot;&amp;gt;
  &amp;lt;source media=&amp;quot;(min-width:465px)&amp;quot; srcset=&amp;quot;img_white_flower.jpg&amp;quot;&amp;gt;
  &amp;lt;img src=&amp;quot;img_orange_flowers.jpg&amp;quot; alt=&amp;quot;Flowers&amp;quot; style=&amp;quot;width:auto;&amp;quot;&amp;gt;
&amp;lt;/picture&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;현재 렌더링이 465 px 이하라면 : img_white_flower.jpg&lt;/li&gt;
&lt;li&gt;현재 렌더링이 465 ~ 650 px 이라면 : img_pink_flowers.jpg&lt;/li&gt;
&lt;li&gt;현재 렌더링이 650 px 를 초과했다면 : img_orange_flowers.jpg&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;없음&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;svg&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;SVG 그래픽을 위한 컨테이너를 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;SVG 파일이 주로 아이콘으로 사용되는데,&lt;/p&gt;
&lt;p&gt;이 파일을 그래픽 에디터에 올려보면, svg 파일이 각 픽셀의 정보를 담는 것이 아니라,&lt;/p&gt;
&lt;p&gt;특정 수학적 경로를 따라 이동하며 칠했다는 것을 알 수 있다.&lt;/p&gt;
&lt;p&gt;밑의 예시들은 W3Schools 에서 제공하는 예시다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;svg width=&amp;quot;100&amp;quot; height=&amp;quot;100&amp;quot;&amp;gt;
  &amp;lt;circle cx=&amp;quot;50&amp;quot; cy=&amp;quot;50&amp;quot; r=&amp;quot;40&amp;quot; stroke=&amp;quot;green&amp;quot; stroke-width=&amp;quot;4&amp;quot; fill=&amp;quot;yellow&amp;quot; /&amp;gt;
&amp;lt;/svg&amp;gt;

&amp;lt;svg width=&amp;quot;300&amp;quot; height=&amp;quot;200&amp;quot;&amp;gt;
  &amp;lt;polygon points=&amp;quot;100,10 40,198 190,78 10,78 160,198&amp;quot;
  style=&amp;quot;fill:lime;stroke:purple;stroke-width:5;fill-rule:evenodd;&amp;quot; /&amp;gt;
&amp;lt;/svg&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;svg width=&quot;100&quot; height=&quot;100&quot;&gt;
  &lt;circle cx=&quot;50&quot; cy=&quot;50&quot; r=&quot;40&quot; stroke=&quot;green&quot; stroke-width=&quot;4&quot; fill=&quot;yellow&quot; /&gt;
&lt;/svg&gt;

&lt;svg width=&quot;300&quot; height=&quot;200&quot;&gt;
  &lt;polygon points=&quot;100,10 40,198 190,78 10,78 160,198&quot;
  style=&quot;fill:lime;stroke:purple;stroke-width:5;fill-rule:evenodd;&quot; /&gt;
&lt;/svg&gt;

&lt;br/&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;없음.&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Category - Audio / Video (오디오, 비디오)&lt;/h2&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;audio&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;음원 컨텐츠를 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;옛날 블로그들에서 배경음악을 듣게 만들었던 태그이다.&lt;/p&gt;
&lt;p&gt;음원을 끄기 위해 블로그 상단에서 열심히 오디오 태그를 찾던 기억이 난다..&lt;/p&gt;
&lt;p&gt;여기서도 &lt;code&gt;&amp;lt;audio&amp;gt;&lt;/code&gt; 태그 내부에 &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; 가 사용되는데,&lt;/p&gt;
&lt;p&gt;여러 음원의 종류를 나열하여 브라우저와 기기의 차이에 대응할 수 있게 만들어 놓았다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;audio controls&amp;gt;
  &amp;lt;source src=&amp;quot;https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3&amp;quot; type=&amp;quot;audio/mpeg&amp;quot;&amp;gt;
&amp;lt;/audio&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;MDN 사이트에서 제공하는 샘플을 사용했다 : 소의 울음소리?&lt;/p&gt;
&lt;audio controls&gt;
  &lt;source src=&quot;https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3&quot; type=&quot;audio/mpeg&quot;&gt;
&lt;/audio&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;audio&amp;gt;&lt;/code&gt; 특수 속성들 :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;autoplay&lt;/code&gt; : 준비되는 대로 바로 오디오가 시작됨&lt;/li&gt;
&lt;li&gt;&lt;code&gt;controls&lt;/code&gt; : 시작, 정지 버튼과 같은 컨트롤 요소가 표시되어야 하는지 지정한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;loop&lt;/code&gt; : 오디오 트랙이 끝날 때 마다 다시 곧바로 시작하게 만들 것인지.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;muted&lt;/code&gt; : 현재 오디오 출력을 음소거 해야 하는지.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;preload&lt;/code&gt; : 페이지가 로딩될 때 오디오도 로드해야 하는지, 아니면 어떻게 로드 할 것인지 지정&lt;ul&gt;
&lt;li&gt;&lt;code&gt;auto&lt;/code&gt; : 브라우저의 판단&lt;/li&gt;
&lt;li&gt;&lt;code&gt;metadata&lt;/code&gt; : 오디오의 메타데이터만 가져옴&lt;/li&gt;
&lt;li&gt;&lt;code&gt;none&lt;/code&gt; : 전혀 불러오지 않고, 이벤트 혹은 JS 로 제어&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src&lt;/code&gt; : 오디오 파일의 URL 을 지정한다.&lt;ul&gt;
&lt;li&gt;&lt;code&gt;URL&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;audio&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;없음&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;source&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;미디어 요소들을 위한 여러 미디어 리소스를 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;특히, &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;audio&amp;gt;&lt;/code&gt; 에 해당한다.&lt;/p&gt;
&lt;p&gt;오디오 태그 내부에 존재하는 &lt;code&gt;source&lt;/code&gt; 태그는 브라우저 자체에서 지원되는 형식을 위해&lt;/p&gt;
&lt;p&gt;여러 개의 소스 파일을 나열하기도 한다.&lt;/p&gt;
&lt;p&gt;이 때, 브라우저는 순서대로 읽으며, 호환 되는 &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; 태그를 리소스로 사용한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;video width=&amp;quot;720&amp;quot; height=&amp;quot;480&amp;quot; controls&amp;gt;
    &amp;lt;source
        src=&amp;quot;https://samplelib.com/lib/preview/mp4/sample-5s.mp4&amp;quot;
        type=&amp;quot;video/mp4&amp;quot;
    /&amp;gt;
&amp;lt;/video&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;video width=&quot;70%&quot; height=&quot;45%&quot; controls&gt;
    &lt;source
        src=&quot;https://samplelib.com/lib/preview/mp4/sample-5s.mp4&quot;
        type=&quot;video/mp4&quot;
    /&gt;
    이 글자가 보인다면, &lt;code&gt;video&lt;/code&gt; 태그가 호환되지 않는 것 입니다.
&lt;/video&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;없음&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;track&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;미디어 요소들에 대한 텍스트 트랙들을 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이게 어떤 의미인가 하면, 우리가 유튜브를 볼 때, 영어 혹은 한글로 번역을 보았을 것이다.&lt;/p&gt;
&lt;p&gt;그것도 거의 동일한 기능이라고 생각하면 된다. (특정 브라우저는 지원하지 않음)&lt;/p&gt;
&lt;p&gt;이 태그는 &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; 와 &lt;code&gt;&amp;lt;audio&amp;gt;&lt;/code&gt; 에 대해서 작동한다.&lt;/p&gt;
&lt;p&gt;아쉽게도, 나는 번역 및 텍스트 트랙에 대한 파일 (&lt;code&gt;.vtt&lt;/code&gt;) 가 없으므로,&lt;/p&gt;
&lt;p&gt;이에 대한 예시는 W3Schools 를 참조하길 바란다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;video width=&amp;quot;320&amp;quot; height=&amp;quot;240&amp;quot; controls&amp;gt;
  &amp;lt;source src=&amp;quot;forrest_gump.mp4&amp;quot; type=&amp;quot;video/mp4&amp;quot;&amp;gt;
  &amp;lt;source src=&amp;quot;forrest_gump.ogg&amp;quot; type=&amp;quot;video/ogg&amp;quot;&amp;gt;
  &amp;lt;track src=&amp;quot;fgsubtitles_en.vtt&amp;quot; kind=&amp;quot;subtitles&amp;quot; srclang=&amp;quot;en&amp;quot; label=&amp;quot;English&amp;quot;&amp;gt;
  &amp;lt;track src=&amp;quot;fgsubtitles_no.vtt&amp;quot; kind=&amp;quot;subtitles&amp;quot; srclang=&amp;quot;no&amp;quot; label=&amp;quot;Norwegian&amp;quot;&amp;gt;
&amp;lt;/video&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;`` 기본 CSS 설정 :&lt;/p&gt;
&lt;p&gt;없음&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;video&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;비디오 혹은 영화를 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;내가 &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; 예제를 들 때 사용했던 코드를 가져오겠다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;video width=&amp;quot;70%&amp;quot; height=&amp;quot;45%&amp;quot; controls&amp;gt;
    &amp;lt;source
        src=&amp;quot;https://samplelib.com/lib/preview/mp4/sample-5s.mp4&amp;quot;
        type=&amp;quot;video/mp4&amp;quot;
    /&amp;gt;
    이 글자가 보인다면, &amp;lt;code&amp;gt;video&amp;lt;/code&amp;gt; 태그가 호환되지 않는 것 입니다.
&amp;lt;/video&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;video width=&quot;70%&quot; height=&quot;45%&quot; controls&gt;
    &lt;source
        src=&quot;https://samplelib.com/lib/preview/mp4/sample-5s.mp4&quot;
        type=&quot;video/mp4&quot;
    /&gt;
    이 글자가 보인다면, &lt;code&gt;video&lt;/code&gt; 태그가 호환되지 않는 것 입니다.
&lt;/video&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;controls&lt;/code&gt; 속성 선언으로 인해 우리가 평소에 보던 플레이어를 볼 수 있는데,&lt;/p&gt;
&lt;p&gt;옵션 값을 본다면, &lt;strong&gt;다운로드&lt;/strong&gt; 가 존재한다.&lt;/p&gt;
&lt;p&gt;유튜브도 그렇고, 보통 다운로드 옵션은 없었을 텐데.. 생각을 해 보았는데,&lt;/p&gt;
&lt;p&gt;영상 자체의 크기와,&lt;/p&gt;
&lt;p&gt;웹 서버가 제공하는 영상을 다시 클라이언트에게 제공하는 과정에서 많은 계산이 일어날 것이라고 생각이 들었다.&lt;/p&gt;
&lt;p&gt;저작권 문제 또한 존재하므로, 이러한 기능은 없애 놓은 것으로 추정한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; 태그 특수 옵션들 :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;src&lt;/code&gt; : 비디오 파일의 URL 을 지정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;autoplay&lt;/code&gt; : 비디오가 준비 되는 대로 시작 할 건지 지정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;controls&lt;/code&gt; : 비디오 제어판을 표시 할 건지 지정 (실행/정지 버튼 등등)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;height&lt;/code&gt; : 높이 지정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;width&lt;/code&gt; : 넓이 지정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;loop&lt;/code&gt; : 비디오가 끝나도 다시 지속적으로 시작 할 것인지 지정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;muted&lt;/code&gt; : 비디오는 나오되, 오디오는 음소거 할 것인지 지정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;poster&lt;/code&gt; : 유저가 &amp;quot;실행&amp;quot; 버튼을 누를 때 까지, 비디오가 다운로드 되는 동안 보여 질 이미지를 지정 (썸네일)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;preload&lt;/code&gt; : &lt;code&gt;auto&lt;/code&gt;, &lt;code&gt;metadata&lt;/code&gt;, &lt;code&gt;none&lt;/code&gt; 을 가지며, 네트워크 절약과 관련이 있음 (위를 참조)&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;없음&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;br/&gt;

&lt;h2&gt;Category - Links (링크 - 연결)&lt;/h2&gt;
&lt;h3&gt;&lt;code&gt;a&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;하이퍼링크를 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;주로 &lt;span style=&quot;color : blue&quot;&gt;파란색&lt;/span&gt; 으로 표현된다.&lt;/p&gt;
&lt;p&gt;나도 마크다운에서 기본으로 제공하는 하이퍼링크 Syntax 대신에,&lt;/p&gt;
&lt;p&gt;나는 &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; 태그를 사용한다. - 기본 제공 문법은 현재 페이지에서 이동하기 때문에.&lt;/p&gt;
&lt;p&gt;참고 사이트를 정의하거나, 특정 이미지를 클릭했을 때, 어떠한 사이트로 인도하도록 해 주는것도 이 태그이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;p&amp;gt;
    &amp;lt;a href=&amp;quot;https://naver.com&amp;quot;&amp;gt;
        &amp;lt;img
            src=&amp;quot;https://i.namu.wiki/i/cpwBJ81IoxAasrKB-oSIkkhiqd8im2wMYVrYSWx_oSN8_qC6cZ_xBbiWvE1jVJC_zU0o-RD84LD4jTaG_nZ7n16U1AopO7SFpbp6SBmsnloAeebwuMlrYzOPxjdJ9Fve3H1dEX2ZtMGVsJ8zaYvkxA.svg&amp;quot;
            width=&amp;quot;50&amp;quot;
            height=&amp;quot;50&amp;quot;
            alt=&amp;quot;네이버 로고&amp;quot;
        /&amp;gt;
    &amp;lt;/a&amp;gt;
&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;
    &amp;lt;a href=&amp;quot;https://naver.com&amp;quot; target=&amp;quot;_blank&amp;quot;&amp;gt;
        네이버 사이트를 &amp;quot;새로운&amp;quot; 창에서 열기
    &amp;lt;/a&amp;gt;
&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;
    &amp;lt;a href=&amp;quot;mailto:rhdwhdals8765@gmail.com&amp;quot;&amp;gt;
        디폴트 메일 프로그램이 켜질 겁니다.
    &amp;lt;/a&amp;gt;
&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;
    &amp;lt;a href=&amp;quot;tel:+12342345&amp;quot;&amp;gt;
        디폴트 전화 프로그램이 켜질 겁니다.
    &amp;lt;/a&amp;gt;
&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;
    &lt;a href=&quot;https://naver.com&quot;&gt;
        &lt;img
            src=&quot;https://i.namu.wiki/i/cpwBJ81IoxAasrKB-oSIkkhiqd8im2wMYVrYSWx_oSN8_qC6cZ_xBbiWvE1jVJC_zU0o-RD84LD4jTaG_nZ7n16U1AopO7SFpbp6SBmsnloAeebwuMlrYzOPxjdJ9Fve3H1dEX2ZtMGVsJ8zaYvkxA.svg&quot;
            width=&quot;50&quot;
            height=&quot;50&quot;
            alt=&quot;네이버 로고&quot;
        /&gt;
    &lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;a href=&quot;https://naver.com&quot; target=&quot;_blank&quot;&gt;
        네이버 사이트를 &quot;새로운&quot; 창에서 열기
    &lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;a href=&quot;mailto:rhdwhdals8765@gmail.com&quot;&gt;
        디폴트 메일 프로그램이 켜질 겁니다.
    &lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;a href=&quot;tel:+12342345&quot;&gt;
        디폴트 전화 프로그램이 켜질 겁니다.
    &lt;/a&gt;
&lt;/p&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; 태그 특수 속성들 :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;download&lt;/code&gt; : 다운로드 될 태그에 대한 설명인데, 유저가 클릭했을 때, 다운로드 될 파일을 지정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;href&lt;/code&gt; : 가야하는 페이지에 대한 URL 링크를 지정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hreflang&lt;/code&gt; : 연결된 문서의 언어를 지정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;media&lt;/code&gt; : 미디어 쿼리로 유저의 기기에 따른 효율화 된 문서로 연결한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ping&lt;/code&gt; : 이 속성은 &amp;quot;추적&amp;quot; 에 자주 사용되는데, 빈 칸(&amp;#39; &amp;#39;) 으로 단순 요청을 보낼 url 리스트를 정의&lt;/li&gt;
&lt;li&gt;&lt;code&gt;referpolicy&lt;/code&gt; : 참조 시 어떤 정보까지 보낼 것인지 지정.&lt;ul&gt;
&lt;li&gt;정말 많은 속성이 존재&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rel&lt;/code&gt; : 현재 문서와 연결된 문서 간의 관계를 지정&lt;ul&gt;
&lt;li&gt;이것도 정말 많은 속성이 존재&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;target&lt;/code&gt; : 여기에 선언된 문서를 어디에서 열 것인지.&lt;ul&gt;
&lt;li&gt;&lt;code&gt;_blank&lt;/code&gt; : 새로운 탭으로 문서를 연다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_parent&lt;/code&gt; : 부모 문서에서 문서를 연다. - &lt;code&gt;iframe&lt;/code&gt; 때문에 생긴 옵션인듯.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_self&lt;/code&gt; : 현재 탭에서 이동&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_top&lt;/code&gt; : 최상위인 브라우저에서 열기&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;type&lt;/code&gt; : 연결된 문서의 미디어 타입을 지정&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;a:link, a:visited {
    color : (internal value);
    text-decoration : underline;
    cursor : auto;
}

a:link:active, a:visited:active {
    color : (internal value);
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;link&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;문서와 외부 리소스 간의 관계를 정의한다. (주로 stylesheet - css)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;외부에 HTML 문서를 위한 &lt;code&gt;css&lt;/code&gt; 파일을 정의 해 두었다면,&lt;/p&gt;
&lt;p&gt;우리는 HTML 문서에 &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; 태그를 이용하여 가져와야 한다.&lt;/p&gt;
&lt;p&gt;이 때, &lt;code&gt;rel=&amp;quot;stylesheet&lt;/code&gt;, &lt;code&gt;href=&amp;quot;&amp;lt;css 파일&amp;gt;&amp;quot;&lt;/code&gt; 을 사용한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;link&lt;/code&gt; 는 내부적으로 요소를 담지 않으며, 오로지 속성만 가진다.&lt;/p&gt;
&lt;p&gt;예를 들어, 대부분을 이렇게 사용한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;head&amp;gt;
    &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;styles.css&amp;quot;&amp;gt;
&amp;lt;/head&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;현재 이 블로그의 테마가, 위 처럼 적용된 결과입니다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;link&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;link {
    display : none;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;nav&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;네비게이션이 경로를 안내 해 주는 것 처럼, 주요 요소를 안내 해 주는 태그를 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;현재 보고 있는 블로그에서도, 맨 위에 &lt;code&gt;&amp;lt;h.&amp;gt;&lt;/code&gt; 태그들에 대한 안내 블록이 보일 것이다.&lt;/p&gt;
&lt;p&gt;이것을 &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt; 라고 보면 된다.&lt;/p&gt;
&lt;p&gt;주로 내부에 &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; 태그를 사용하는데, 위에서 &lt;code&gt;a&lt;/code&gt; 태그를 통해서 페이지를 이동하거나, 해당 &lt;code&gt;id&lt;/code&gt; 로 이동한다.&lt;/p&gt;
&lt;p id=&quot;nav-id-1&quot;&gt;테스팅 DOM1&lt;/p&gt; &lt;br/&gt; &lt;br/&gt; &lt;br/&gt;

&lt;p id=&quot;nav-id-2&quot;&gt;테스팅 DOM2&lt;/p&gt; &lt;br/&gt;&lt;br/&gt;&lt;br/&gt;



&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;nav&amp;gt;
    &amp;lt;a href=&amp;quot;#nav-id-1&amp;quot;&amp;gt;테스팅 DOM1 으로 이동&amp;lt;/a&amp;gt; &amp;lt;br/&amp;gt;
    &amp;lt;a href=&amp;quot;#nav-id-2&amp;quot;&amp;gt;테스팅 DOM2 으로 이동&amp;lt;/a&amp;gt;
&amp;lt;/nav&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;nav&gt;
    &lt;a href=&quot;#nav-id-1&quot;&gt;테스팅 DOM1 으로 이동&lt;/a&gt; &lt;br/&gt;
    &lt;a href=&quot;#nav-id-2&quot;&gt;테스팅 DOM2 으로 이동&lt;/a&gt;
&lt;/nav&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;nav&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;nav {
    display : block;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h2&gt;이 글을 작성하며 배운 것은,&lt;/h2&gt;
&lt;p&gt;1, 2 편에 거쳐, 가장 대중적인 태그들과, 형식 태그에 대해서 모두 보았다.&lt;/p&gt;
&lt;p&gt;옛날 웹 개발자들이 각 구역에 대한 역할을 나누기 위해 노력한 모습이 보일 정도였다.&lt;/p&gt;
&lt;p&gt;그러나, 웹 개발이 보편화되고 간편해진 요즘, 특정 태그들은 JS 라이브러리로 통합되었거나,&lt;/p&gt;
&lt;p&gt;다른 태그의 역할로 통합되었다고 느낀다.&lt;/p&gt;
&lt;p&gt;이번 3 편에서는 &lt;strong&gt;이미지&lt;/strong&gt;, &lt;strong&gt;캔버스&lt;/strong&gt;, &lt;strong&gt;오디오&lt;/strong&gt;, &lt;strong&gt;비디오&lt;/strong&gt;, &lt;strong&gt;하이퍼링크&lt;/strong&gt; 관련 태그에 대해서 배웠다.&lt;/p&gt;
&lt;p&gt;특히나 옛날처럼 텍스트만을 보기 위해서 웹을 여는 것이 아니라, 컨텐츠를 보기 위해서 인터넷을 하는 이 시대에,&lt;/p&gt;
&lt;p&gt;위와 같은 이미지, 캔버스 와 같은 태그들은 정말 속성이 많고, 속성에 배치될 수 있는 값들도 많았다.&lt;/p&gt;
&lt;p&gt;그리고, 비디오와 오디오 태그에서 default 로 &amp;quot;다운로드&amp;quot; 옵션이 있다는 것에 놀랐다.&lt;/p&gt;
&lt;p&gt;평소 우리가 접하는 유튜브나 SNS 같은 곳에서 동영상 다운로드는 찾을 수가 없을텐데,&lt;/p&gt;
&lt;p&gt;왜 그럴까 생각해보니, 웹 서버 혹은 백 서버가 무지막지한 용량의 데이터를 하나의 클라이언트에게 주어야 한다는 것이 문제라고 생각했다.&lt;/p&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;h2&gt;참고 사이트&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;W3Schools HTML Element Reference&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.w3schools.com/TAGS/ref_byfunc.asp&quot;&gt;https://www.w3schools.com/TAGS/ref_byfunc.asp&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;</description>
      <category>Web-Server/웹 지식</category>
      <category>a</category>
      <category>Audio</category>
      <category>Canvas</category>
      <category>html</category>
      <category>html 태그</category>
      <category>img</category>
      <category>Nav</category>
      <category>tag</category>
      <category>video</category>
      <category>모든 html 태그</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/219</guid>
      <comments>https://codecreature.tistory.com/219#entry219comment</comments>
      <pubDate>Wed, 28 May 2025 23:16:03 +0900</pubDate>
    </item>
    <item>
      <title>HTML 문서 정식 태그 &amp;quot;전부&amp;quot; 공부하기 - 2편 (코드 작성 및 구현까지)</title>
      <link>https://codecreature.tistory.com/218</link>
      <description>&lt;h2&gt;제목 : HTML 문서 태그 공부하기 - 2편&lt;/h2&gt;
&lt;hr&gt;
&lt;h3&gt;1편 글 주소&lt;/h3&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/217&quot;&gt;https://codecreature.tistory.com/217&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;이 글을 작성하는 이유&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;위의 1 편 글을 잠깐 살펴보면서 리스트를 보았다면 알겠지만,&lt;/p&gt;
&lt;p&gt;모든 태그에 대한 의미와 사용처, 그리고 실제 구현까지 살펴보는 것은 보통 일이 아니었다.&lt;/p&gt;
&lt;p&gt;내가 React 를 통해 처음 웹 개발에 입문하였을 때, 나는 단순히 블럭을 선언하기 위해 &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; 를 사용했다.&lt;/p&gt;
&lt;p&gt;대표적인 태그 몇 개만 사용하며, 클래스로 구분하는 것은 그 때 당시엔 문제가 없었지만,&lt;/p&gt;
&lt;p&gt;추후 협업이나, 중급에서 고급 수준의 웹 페이지를 제작하는 과정에서 한계가 있을 것이라 판단했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;또한 각 태그가 가지고 있는 고유의 기능이나, Default 스타일링을 알고 싶었는데,&lt;/p&gt;
&lt;p&gt;정말 효과적으로 배웠다고 생각한다.&lt;/p&gt;
&lt;p&gt;옛날 개발자들이 협업과 가독성을 위해 만들어 놓았을 법한 태그들도 많이 존재했고,&lt;/p&gt;
&lt;p&gt;특히, 생각보다 활용하기 좋아 보이는 &lt;code&gt;&amp;lt;abbr&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;dfn&amp;gt;&lt;/code&gt; 같은 태그들도 발견했다. (약어 설명 태그)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;전 편에서는 &amp;quot;기본 태그&amp;quot; 와, &amp;quot;형식 태그&amp;quot;(Formatter) 에 대해서 배웠다.&lt;/p&gt;
&lt;p&gt;형식 태그는 &lt;code&gt;inline&lt;/code&gt; 으로서, 텍스트를 정의하는 것만 있을 줄 알았는데,&lt;/p&gt;
&lt;p&gt;의외로 게이지, 진행률, 및 비렌더링 DOM 을 지원하는 태그들도 존재했다.&lt;/p&gt;
&lt;p&gt;나중에 이러한 태그들을 사용 할 때, 모호하게 넘어 갈 일이 없을 것 같아 좋다고 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, 아직 알아야 할 태그들은 많이 남았다.&lt;/p&gt;
&lt;p&gt;완료 한 카테고리는 이렇다 :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Basic Tags (완료)&lt;/li&gt;
&lt;li&gt;Formatter (완료)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;남은 카테고리는 이렇다 :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Forms and Input&lt;/li&gt;
&lt;li&gt;Frames&lt;/li&gt;
&lt;li&gt;Images&lt;/li&gt;
&lt;li&gt;Audio / Video&lt;/li&gt;
&lt;li&gt;Links&lt;/li&gt;
&lt;li&gt;Lists&lt;/li&gt;
&lt;li&gt;Tables&lt;/li&gt;
&lt;li&gt;Styles and Semantics&lt;/li&gt;
&lt;li&gt;Meta Info&lt;/li&gt;
&lt;li&gt;Programming&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;p&gt;아직 배워야 할 카테고리 자체는 많이 남았지만, Formatter 가 압도적으로 많은 수를&lt;/p&gt;
&lt;p&gt;차지했기 때문에, 1편에서 2 개의 카테고리밖에 다루지 못한 것도 있다.&lt;/p&gt;
&lt;p&gt;그러나, 나머지 카테고리는 전부 Formatter (거의 50개) 보다는 적기 때문에,&lt;/p&gt;
&lt;p&gt;1편에서 설명한 틀을 최대한 유지하며, 작성 할 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Category - Forms and Input (폼과 입력)&lt;/h2&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;form&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;유저의 입력을 위한 HTML 폼을 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;처음부터 매우 중요하고, 속성이 매우 많으며,&lt;/p&gt;
&lt;p&gt;내부에 작성될 수 있는 태그들이 많은 요소가 나왔는데,&lt;/p&gt;
&lt;p&gt;한 번 예시를 통해 감을 잡아보자.&lt;/p&gt;
&lt;p&gt;먼저, W3Schools 에서 만들어 놓은 웹 사이트 샌드박스 사이트가 존재하는데,&lt;/p&gt;
&lt;p&gt;궁금하다면&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.w3schools.com/TAGS/tryit.asp?filename=tryhtml_form_submit&quot;&gt;https://www.w3schools.com/TAGS/tryit.asp?filename=tryhtml_form_submit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;여기서 시도 해 보길 바란다.&lt;/p&gt;
&lt;p&gt;실제 &lt;code&gt;action&lt;/code&gt; 속성이 이루어지는 것을 사이트에서 보여주므로,&lt;/p&gt;
&lt;p&gt;예제를 그대로 옮겨서 확인 해 보자.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;form action=&amp;quot;/action_page.php&amp;quot; method=&amp;quot;get&amp;quot;&amp;gt;
  &amp;lt;label for=&amp;quot;fname&amp;quot;&amp;gt;First name:&amp;lt;/label&amp;gt;
  &amp;lt;input type=&amp;quot;text&amp;quot; id=&amp;quot;fname&amp;quot; name=&amp;quot;fname&amp;quot; /&amp;gt;&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;
  &amp;lt;label for=&amp;quot;lname&amp;quot;&amp;gt;Last name:&amp;lt;/label&amp;gt;&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;
  &amp;lt;input type=&amp;quot;text&amp;quot; id=&amp;quot;lname&amp;quot; name=&amp;quot;lname&amp;quot; /&amp;gt;&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;
  &amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;Submit&amp;quot; /&amp;gt;
&amp;lt;/form&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 폼은 내부에 유저가 입력하는 데이터가 들어 있으며,&lt;/p&gt;
&lt;p&gt;Event 인 &amp;quot;submit&amp;quot; 이 일어날 때, GET 메서드로 &lt;code&gt;action_page.php&lt;/code&gt; 에 넘긴다.&lt;/p&gt;
&lt;p&gt;결과적으로, &lt;code&gt;action_page.php&lt;/code&gt; 는 이 form 의 내용을 파싱하여 원하는 데이터를 보여줄 수 있다.&lt;/p&gt;
&lt;p&gt;그런데, 궁금 한 것이 생겼다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;form 이 전달 될 때 어떤 형태의 네트워크로 전달될까?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;따라서, 나는 네트워크 도구 탭을 열고 이를 확인 해 보았다.&lt;/p&gt;
&lt;p&gt;결과적으로, 내가 입력한 데이터는 쿼리 형식으로 날아갔다.&lt;/p&gt;
&lt;table&gt;
    &lt;thead&gt;
        &lt;th&gt;Request URL&lt;/th&gt;
        &lt;th&gt;Method&lt;/th&gt;
        &lt;th&gt;Full URL&lt;/th&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
        &lt;tr&gt;
            &lt;td&gt;https://www.w3schools.com/action_page&lt;/td&gt;
            &lt;td&gt;200&lt;/td&gt;
            &lt;td&gt;...../action_page?fname=Gong&amp;lname=Damhyeong&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;script&gt;
const form1 = document.getElementById(&quot;test-form-1&quot;);

function test1() {
  const first = document.getElementById(&quot;fname&quot;).value;
  const last = document.getElementById(&quot;lname&quot;).value;

  document.getElementById(&quot;result-form-1&quot;).innerHTML = `
    &lt;p&gt; 당신이 입력한 성은 ${first} 이며, &lt;/p&gt;
    &lt;p&gt; 이름은 ${last}  입니다.&lt;/p&gt;
    `;
}

&lt;/script&gt;

&lt;form id=&quot;test-form-1&quot;&gt;
    &lt;label for=&quot;fname&quot;&gt;First name:&lt;/label&gt;
    &lt;input type=&quot;text&quot; id=&quot;fname&quot; name=&quot;fname&quot; style=&quot;background : #222222&quot;&gt;&lt;br/&gt;&lt;br/&gt;
    &lt;label for=&quot;lname&quot;&gt;Last name:&lt;/label&gt;
    &lt;input type=&quot;text&quot; id=&quot;lname&quot; name=&quot;lname&quot; style=&quot;background : #222222&quot;&gt;&lt;br/&gt;&lt;br/&gt;
    &lt;input type=&quot;button&quot; value=&quot;Submit&quot; onclick=&quot;test1()&quot; style=&quot;border : 1px solid white; border-radius : 5px;&quot;&gt;
&lt;/form&gt;

&lt;div id=&quot;result-form-1&quot;&gt;&lt;/div&gt;

&lt;br/&gt;

&lt;p&gt;위에 제작된 실제 표현 효과는, &lt;code&gt;form&lt;/code&gt; 의 효과를 흉내내었을 뿐이다.&lt;/p&gt;
&lt;p&gt;내가 실현하기 위해 구현한 실제 코드는 이렇다 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;script&amp;gt;
  const form1 = document.getElementById(&amp;quot;test-form-1&amp;quot;);

  function test1() {
    const first = document.getElementById(&amp;quot;fname&amp;quot;).value;
    const last = document.getElementById(&amp;quot;lname&amp;quot;).value;

    document.getElementById(&amp;quot;result-form-1&amp;quot;).innerHTML = `
    &amp;lt;p&amp;gt; 당신이 입력한 성은 ${first} 이며, &amp;lt;/p&amp;gt;
    &amp;lt;p&amp;gt; 이름은 ${last}  입니다.&amp;lt;/p&amp;gt;
    `;
  }
&amp;lt;/script&amp;gt;

&amp;lt;form id=&amp;quot;test-form-1&amp;quot;&amp;gt;
  &amp;lt;label for=&amp;quot;fname&amp;quot;&amp;gt;First name:&amp;lt;/label&amp;gt;
  &amp;lt;input
    type=&amp;quot;text&amp;quot;
    id=&amp;quot;fname&amp;quot;
    name=&amp;quot;fname&amp;quot;
    style=&amp;quot;background : #222222&amp;quot;
  /&amp;gt;&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;
  &amp;lt;label for=&amp;quot;lname&amp;quot;&amp;gt;Last name:&amp;lt;/label&amp;gt;
  &amp;lt;input
    type=&amp;quot;text&amp;quot;
    id=&amp;quot;lname&amp;quot;
    name=&amp;quot;lname&amp;quot;
    style=&amp;quot;background : #222222&amp;quot;
  /&amp;gt;&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;
  &amp;lt;input
    type=&amp;quot;button&amp;quot;
    value=&amp;quot;Submit&amp;quot;
    onclick=&amp;quot;test1()&amp;quot;
    style=&amp;quot;border : 1px solid white; border-radius : 5px;&amp;quot;
  /&amp;gt;
&amp;lt;/form&amp;gt;

&amp;lt;div id=&amp;quot;result-form-1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;여기서 &lt;code&gt;label&lt;/code&gt; 태그의 &lt;code&gt;for&lt;/code&gt; 에 대해서 나오는데,&lt;/p&gt;
&lt;p&gt;이는 &lt;code&gt;label&lt;/code&gt; 태그가 어떤 유저의 입력 태그에 부착되었는가를 설명하는 속성이다.&lt;/p&gt;
&lt;p&gt;여기서 &lt;code&gt;input&lt;/code&gt; 의 &lt;code&gt;id&lt;/code&gt; 가 &lt;code&gt;label&lt;/code&gt; 의 &lt;code&gt;for&lt;/code&gt; 과 일치한다.&lt;/p&gt;
&lt;p&gt;따라서, 입력 칸이 아니라, &amp;quot;First name:&amp;quot; or &amp;quot;Last name:&amp;quot; 을 클릭하면,&lt;/p&gt;
&lt;p&gt;입력 칸으로 Focus 가 된다는 것을 알 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; 태그는 이러한 엘리먼트들을 포함할 수 있다 :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;option&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;optgroup&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;fieldset&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;output&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; 태그는 이러한 속성들을 가지고 있다 :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;accept-charset&lt;/code&gt; : 폼 제출에 사용되기 위한 문자 인코딩 지정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;action&lt;/code&gt; : 폼 데이터를 제출할 때 어디에 보낼건지. (url 무조건)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;autocomplete&lt;/code&gt; : 자동완성 할 건지 (&amp;quot;on&amp;quot; or &amp;quot;off&amp;quot;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;enctype&lt;/code&gt; : post 상황에서 서버로 데이터 전송 될 때 어떻게 폼 데이터를 인코딩해야하는지.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;method&lt;/code&gt; : get, post, dialog(특수상황) 사용&lt;/li&gt;
&lt;li&gt;&lt;code&gt;name&lt;/code&gt; : 폼의 이름을 지정한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;novalidate&lt;/code&gt; : 제출 시 폼을 검증하지 말아야 하는지.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rel&lt;/code&gt; : 현재 문서와 리소스 간의 연결 관계를 지정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;target&lt;/code&gt; : 폼을 제출하고 받은 응답을 어디에 표시 할 것인지.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;form {
  display: block;
  margin-top: 0em;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;input&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;사용자 입력을 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;다양한 형태의 사용자 입력 형태들을 속성 &lt;code&gt;type&lt;/code&gt; 으로 정의할 수 있다.&lt;/p&gt;
&lt;p&gt;이미 바로 위에서 &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; 을 사용 한 모습을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;이 &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; 에 대한 다양한 사용 예제를 보자.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;form&amp;gt;
  &amp;lt;label&amp;gt; text : &amp;lt;input type=&amp;quot;text&amp;quot; /&amp;gt; &amp;lt;/label&amp;gt;&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;
  &amp;lt;label&amp;gt; checkbox : &amp;lt;input type=&amp;quot;checkbox&amp;quot; /&amp;gt; &amp;lt;/label&amp;gt;&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;
  &amp;lt;label&amp;gt; radio : &amp;lt;input type=&amp;quot;radio&amp;quot; /&amp;gt; &amp;lt;/label&amp;gt;&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;
  &amp;lt;label&amp;gt; file : &amp;lt;input type=&amp;quot;file&amp;quot; /&amp;gt; &amp;lt;/label&amp;gt;&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;
  &amp;lt;label&amp;gt; password : &amp;lt;input type=&amp;quot;password&amp;quot; /&amp;gt; &amp;lt;/label&amp;gt;&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;
&amp;lt;/form&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;form&gt;
    &lt;label&gt;
        text : &lt;input type=&quot;text&quot;&gt;
    &lt;/label&gt;&lt;br/&gt;&lt;br/&gt;
    &lt;label&gt;
        checkbox : &lt;input type=&quot;checkbox&quot;&gt;
    &lt;/label&gt;&lt;br/&gt;&lt;br/&gt;
    &lt;label&gt;
        radio : &lt;input type=&quot;radio&quot;&gt;
    &lt;/label&gt;&lt;br/&gt;&lt;br/&gt;
    &lt;label&gt;
        file : &lt;input type=&quot;file&quot;&gt;
    &lt;/label&gt;&lt;br/&gt;&lt;br/&gt;
    &lt;label&gt;
        password : &lt;input type=&quot;password&quot;&gt;
    &lt;/label&gt;&lt;br/&gt;&lt;br/&gt;
&lt;/form&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; 태그와 수많은 속성들 :&lt;/p&gt;
&lt;p&gt;내가 바로 위에서 코드와 결과로 보여주었듯이,&lt;/p&gt;
&lt;p&gt;하나의 태그로 다양한 형태의 입력을 표현할 수 있다.&lt;/p&gt;
&lt;p&gt;그러나, 각각의 태그들은 활용도가 굉장히 높다.&lt;/p&gt;
&lt;p&gt;텍스트의 최대 길이는 몇 자이며, 입력 칸의 크기와 텍스트 크기, 패턴 등등..&lt;/p&gt;
&lt;p&gt;파일은 어떤 유형을 받을 것인지,&lt;/p&gt;
&lt;p&gt;속성이 정말로 많다.&lt;/p&gt;
&lt;p&gt;이에 대한 자세한 속성들은 &lt;a target=&quot;_blank&quot; href=&quot;https://www.w3schools.com/TAGS/tag_input.asp#:~:text=Yes-,Attributes,-Attribute&quot;&gt;Attributes&lt;/a&gt; 에서 확인하면 좋다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;p&gt;없음&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;textarea&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;유저의 여러 줄 입력을 제어한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;기존의 &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; 태그가 하나의 줄로서 입력을 정의하는 데 반하여,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt; 는 여러 줄을 입력 할 수 있도록 만들어 준다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt; 또한 입력에 속하기 때문에, &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; 로 묶일 수 있으며,&lt;/p&gt;
&lt;p&gt;이 영역이 클릭되면 자동으로 &lt;code&gt;textarea&lt;/code&gt; 로 포커싱이 이동한다.&lt;/p&gt;
&lt;p&gt;또한, &lt;code&gt;rows&lt;/code&gt;, &lt;code&gt;cols&lt;/code&gt; 속성으로 줄, 열 크기를 제어해 크기를 조정할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;label&amp;gt;
  &amp;lt;!-- textarea 사용 --&amp;gt;
  &amp;lt;textarea
    id=&amp;quot;textarea-test-1&amp;quot;
    name=&amp;quot;textarea-test-1&amp;quot;
    rows=&amp;quot;4&amp;quot;
    cols=&amp;quot;20&amp;quot;
    placeholder=&amp;quot;이 텍스트는 안내 용도로 사용한다. (글씨 전부 지우면 볼 수 있음)&amp;quot;
    style=&amp;quot;background : #222&amp;quot;
  &amp;gt;
        이 텍스트는 이미 입력된 텍스트로 처리된다.
    &amp;lt;/textarea
  &amp;gt;&amp;lt;br /&amp;gt;
&amp;lt;/label&amp;gt;
&amp;lt;button onclick=&amp;quot;findBlanks1()&amp;quot; style=&amp;quot;border : 2px white solid&amp;quot;&amp;gt;
  빈 칸 개수 알아보기
&amp;lt;/button&amp;gt;
&amp;lt;br /&amp;gt;

&amp;lt;div id=&amp;quot;textarea-result-1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;

&amp;lt;script&amp;gt;
  const textarea1 = document.getElementById(&amp;quot;textarea-test-1&amp;quot;);

  function findBlanks1() {
    const str = textarea1.value;

    let blanks = 0;
    for (let i = 0; i &amp;lt; str.length; i++) {
      blanks = str[i] == &amp;quot; &amp;quot; ? blanks + 1 : blanks;
    }

    document.getElementById(&amp;quot;textarea-result-1&amp;quot;).innerHTML = `
          &amp;lt;p&amp;gt;현재 textarea 내부의 빈 칸 개수는, ${blanks} 개 입니다.&amp;lt;/p&amp;gt;
          `;
  }
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;label&gt;
  &lt;!-- textarea 사용 --&gt;
  &lt;textarea
    id=&quot;textarea-test-1&quot;
    name=&quot;textarea-test-1&quot;
    rows=&quot;4&quot;
    cols=&quot;20&quot;
    placeholder=&quot;이 텍스트는 안내 용도로 사용한다. (글씨 전부 지우면 볼 수 있음)&quot;
    style=&quot;background : #222&quot;
  &gt;
        이 텍스트는 이미 입력된 텍스트로 처리된다.
    &lt;/textarea
  &gt;&lt;br /&gt;
&lt;/label&gt;
&lt;button
    onclick=&quot;findBlanks1()&quot;
    style=&quot;border : 2px white solid&quot;
&gt;빈 칸 개수 알아보기&lt;/button&gt; &lt;br /&gt;

&lt;div id=&quot;textarea-result-1&quot;&gt;&lt;/div&gt;

&lt;script&gt;
  const textarea1 = document.getElementById(&quot;textarea-test-1&quot;);

  function findBlanks1() {
    const str = textarea1.value;

    let blanks = 0;
    for (let i = 0; i &lt; str.length; i++) {
      blanks = (str[i] == ' ') ? blanks + 1 : blanks;
    }

    document.getElementById(&quot;textarea-result-1&quot;).innerHTML = `
          &lt;p&gt;현재 textarea 내부의 빈 칸 개수는, ${blanks} 개 입니다.&lt;/p&gt;
          `;
  }
&lt;/script&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt; 주요 속성들 :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;autofocus&lt;/code&gt; : 페이지 로드 시 자동으로 포커싱 할 건지&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cols&lt;/code&gt; : 가로 영역 크기 선언(글자 크기 단위)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rows&lt;/code&gt; : 세로 영역 크기 선언(이것도 글자 크기 단위)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;disabled&lt;/code&gt; : 텍스트 영역을 비활성화 할 것인지&lt;/li&gt;
&lt;li&gt;&lt;code&gt;form&lt;/code&gt; : 자신이 속하는 &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; id 선언&lt;/li&gt;
&lt;li&gt;&lt;code&gt;maxlength&lt;/code&gt; : 최대 글자 수 선언&lt;/li&gt;
&lt;li&gt;&lt;code&gt;name&lt;/code&gt; : 이 텍스트 영역의 이름 - 전송 시 Key 로 됨.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;placeholder&lt;/code&gt; : 회색으로 보여지는 안내 문구 색상.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;readonly&lt;/code&gt; : 수정할 수 없고, 읽기만 가능하게 만들기&lt;/li&gt;
&lt;li&gt;&lt;code&gt;required&lt;/code&gt; : 내부에 데이터가 있어야만 한다는 선언&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;없음&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;button&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;클릭 가능한 버튼을 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;위에서 &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; 사용을 지속적으로 보여주었는데,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; 에는 &lt;code&gt;inline&lt;/code&gt; 요소를 넣을 수 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;i&amp;gt;&lt;/code&gt; : 이탈릭체&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;b&amp;gt;&lt;/code&gt; : bold 글씨&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;strong&amp;gt;&lt;/code&gt; : 강조 == bold&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;br&amp;gt;&lt;/code&gt; : 줄넘김&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; : 이미지&lt;/li&gt;
&lt;li&gt;등등...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;button type=&amp;quot;button&amp;quot;&amp;gt;button 타입 버튼&amp;lt;/button
&amp;lt;br/&amp;gt;
&amp;lt;button&amp;gt;&amp;lt;img src=&amp;quot;https://tistory1.daumcdn.net/tistory/6142901/attach/eaf30c1affbe45adb0179071560647bc&amp;quot;&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;&lt;button type=&quot;button&quot;&gt;button 타입 버튼&lt;/button&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;button style=&quot;padding : 2rem&quot;&gt;&lt;img src=&quot;https://tistory1.daumcdn.net/tistory/6142901/attach/eaf30c1affbe45adb0179071560647bc&quot; width=&quot;40&quot; height=&quot;40&quot;&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;button&gt;
    텍스트 &lt;strong&gt;강조&lt;/strong&gt; 할 수 있으며, &lt;br/&gt;&lt;br/&gt;
    또한 줄넘김을 수행 할 수 있다.
&lt;/button&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; 내부 속성들 :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;autofocus&lt;/code&gt; : 페이지 로딩 시 자동으로 포커싱&lt;/li&gt;
&lt;li&gt;&lt;code&gt;disabled&lt;/code&gt; : 버튼 비활성화 (클릭 못함)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;form&lt;/code&gt; : 해당되는 폼 id&lt;/li&gt;
&lt;li&gt;&lt;code&gt;name&lt;/code&gt; : 버튼의 이름&lt;/li&gt;
&lt;li&gt;&lt;code&gt;type&lt;/code&gt; : &lt;code&gt;button&lt;/code&gt;, &lt;code&gt;reset&lt;/code&gt;, &lt;code&gt;submit&lt;/code&gt; 중 하나의 타입&lt;/li&gt;
&lt;li&gt;&lt;code&gt;value&lt;/code&gt; : 버튼의 초기값을 지정&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;버튼은 어떻게 보면 단일 클릭 개체로서 스타일링을 많이 하게 되는 개체인 것 같다.&lt;/p&gt;
&lt;p&gt;사용자의 편의를 위해 그림자를 넣을 수도 있으며, 마우스 올려졌을 때, 클릭 시, 완료 시 등등&lt;/p&gt;
&lt;p&gt;여러 이벤트에 따라 스타일링을 다르게 하여 피드백을 줄 수도 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;없음&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;select&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Drop-Down (드롭다운) 리스트를 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;드롭다운 리스트란, 내부에 여러 선택 요소를 가지고 있지만,&lt;/p&gt;
&lt;p&gt;처음 보이기에는 하나의 요소만 표출되어 있고, &amp;quot;v&amp;quot; 라는 표식을 가지고 있다.&lt;/p&gt;
&lt;p&gt;이 태그를 클릭하면, 내부에 가지고 있는 태그, 혹은 선택 정보들이 펼쳐진다.&lt;/p&gt;
&lt;p&gt;이 태그 또한, &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; 과 정말 많은 연관관계가 있다.&lt;/p&gt;
&lt;p&gt;먼저, 예시와 구현에 들어가기 전에 알아야 할 것이 있다고 생각한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; 는 &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; 내부에서 사용되는 편이다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; 는 내부에 &lt;code&gt;&amp;lt;option&amp;gt;&lt;/code&gt; 태그들을 가지고 있다.&lt;/li&gt;
&lt;li&gt;이러한 &lt;code&gt;&amp;lt;option&amp;gt;&lt;/code&gt; 들로 드롭다운 엘리먼트들을 생성할 수 있다.&lt;/li&gt;
&lt;li&gt;실제 정보 Submit 실행 시, &lt;br/&gt; &lt;code&gt;select&lt;/code&gt; 의 &lt;code&gt;name&lt;/code&gt; 이 Key 로, 선택된 &lt;code&gt;options&lt;/code&gt; 의 &lt;code&gt;value&lt;/code&gt; 속성이 Value 로 넘어간다.&lt;/li&gt;
&lt;li&gt;EX - GET : &lt;code&gt;...?&amp;lt;select.name&amp;gt;=&amp;lt;options.value&amp;gt;&lt;/code&gt; &lt;br/&gt; POST : &lt;code&gt;{select.name : options.value}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;티스토리에서는 구현에 제약이 있다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이 때문에, 실제 사용 예시와, 실제 표현 결과를 위한 코드로 나누어 보여줄 것이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;문서 예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;form action=&amp;quot;/action_page.php&amp;quot;&amp;gt;
  &amp;lt;label for=&amp;quot;cars&amp;quot;&amp;gt;Choose a car:&amp;lt;/label&amp;gt;
  &amp;lt;select name=&amp;quot;cars&amp;quot; id=&amp;quot;cars&amp;quot;&amp;gt;
    &amp;lt;option value=&amp;quot;volvo&amp;quot;&amp;gt;Volvo&amp;lt;/option&amp;gt;
    &amp;lt;option value=&amp;quot;saab&amp;quot;&amp;gt;Saab&amp;lt;/option&amp;gt;
    &amp;lt;option value=&amp;quot;opel&amp;quot;&amp;gt;Opel&amp;lt;/option&amp;gt;
    &amp;lt;option value=&amp;quot;audi&amp;quot;&amp;gt;Audi&amp;lt;/option&amp;gt;
  &amp;lt;/select&amp;gt;
  &amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;
  &amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;Submit&amp;quot; /&amp;gt;
&amp;lt;/form&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;블로그 표현 예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;form&amp;gt;
  &amp;lt;label&amp;gt;
    당신은 어떤 언어를 주로 사용하십니까? :
    &amp;lt;select name=&amp;quot;cars&amp;quot; id=&amp;quot;test-select-1&amp;quot; style=&amp;quot;background:#333&amp;quot;&amp;gt;
      &amp;lt;option value=&amp;quot;c&amp;quot;&amp;gt;C 언어&amp;lt;/option&amp;gt;
      &amp;lt;option value=&amp;quot;c++&amp;quot;&amp;gt;C++ 언어&amp;lt;/option&amp;gt;
      &amp;lt;option value=&amp;quot;java&amp;quot;&amp;gt;java 언어&amp;lt;/option&amp;gt;
      &amp;lt;option value=&amp;quot;js&amp;quot;&amp;gt;JavaScript&amp;lt;/option&amp;gt;
      &amp;lt;option value=&amp;quot;c#&amp;quot;&amp;gt;C#&amp;lt;/option&amp;gt;
      &amp;lt;option value=&amp;quot;rust&amp;quot;&amp;gt;Rust&amp;lt;/option&amp;gt;
      &amp;lt;option value=&amp;quot;go&amp;quot;&amp;gt;Golang&amp;lt;/option&amp;gt;
      &amp;lt;option value=&amp;quot;asm&amp;quot;&amp;gt;Assembly&amp;lt;/option&amp;gt;
    &amp;lt;/select&amp;gt;
  &amp;lt;/label&amp;gt;
&amp;lt;/form&amp;gt;

&amp;lt;button onclick=&amp;quot;getOption()&amp;quot; value=&amp;quot;Submit&amp;quot; /&amp;gt;

&amp;lt;div id=&amp;quot;select-result-1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;

&amp;lt;script&amp;gt;
  const container = document.getElementById(&amp;quot;select-result-1&amp;quot;);

  function getOption() {
    const selectDOM = document.getElementById(&amp;quot;test-select-1&amp;quot;);

    const currVal = selectDOM.value;

    container.innerHTML = `
    &amp;lt;p&amp;gt; 당신이 선택한 언어는 ${currVal} 입니다.&amp;lt;/p&amp;gt;
    `;
  }
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;form&gt;
    &lt;label&gt;
        당신은 어떤 언어를 주로 사용하십니까? :
        &lt;select
            name=&quot;cars&quot;
            id=&quot;test-select-1&quot;
            style=&quot;background:#333&quot;
        &gt;
            &lt;option value=&quot;c&quot;&gt;C 언어&lt;/option&gt;
            &lt;option value=&quot;c++&quot;&gt;C++ 언어&lt;/option&gt;
            &lt;option value=&quot;java&quot;&gt;java 언어&lt;/option&gt;
            &lt;option value=&quot;js&quot;&gt;JavaScript&lt;/option&gt;
            &lt;option value=&quot;c#&quot;&gt;C#&lt;/option&gt;
            &lt;option value=&quot;rust&quot;&gt;Rust&lt;/option&gt;
            &lt;option value=&quot;go&quot;&gt;Golang&lt;/option&gt;
            &lt;option value=&quot;asm&quot;&gt;Assembly&lt;/option&gt;
        &lt;/select&gt;
    &lt;/label&gt;
&lt;/form&gt;

&lt;p&gt;&lt;button onclick=&quot;getOption()&quot;&gt;Submit&lt;/button&gt;&lt;/p&gt;
&lt;div id=&quot;select-result-1&quot;&gt;&lt;/div&gt;

&lt;script&gt;
const container = document.getElementById(&quot;select-result-1&quot;);

function getOption() {
  const selectDOM = document.getElementById(&quot;test-select-1&quot;);

  const currVal = selectDOM.value;

  container.innerHTML = `
    &lt;p&gt; 당신이 선택한 언어는 ${currVal} 입니다.&lt;/p&gt;
    `;
}
&lt;/script&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; 해당 속성들 :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;autofocus&lt;/code&gt; : 페이지 로드 후 포커싱을 가져야 하는지&lt;/li&gt;
&lt;li&gt;&lt;code&gt;disabled&lt;/code&gt; : 드롭 다운을 비활성화 할 지 지정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;form&lt;/code&gt; : 드롭 다운이 속한 form id 지정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;multiple&lt;/code&gt; : 한 번에 여러 선택지를 지정할 수 있게 만들어줌.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;name&lt;/code&gt; : 드롭다운 리스트의 이름을 지정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;required&lt;/code&gt; : 유저가 form 을 제출하기 전에, 반드시 선택되어야 함을 의미.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;size&lt;/code&gt; : 드롭다운 리스트에서 보여 질 수 있는 선택지의 개수를 정의&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;없음&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;optgroup&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;위에서 언급한 &lt;code&gt;select&lt;/code&gt; 태그 내부에 들어가는 &lt;code&gt;option&lt;/code&gt; 태그의 그룹을 정의&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;위에서는 예시로 어떤 언어롤 주로 사용하냐고 했지만,&lt;/p&gt;
&lt;p&gt;어떤 프레임워크, 라이브러리를 사용하냐고 묻는다면, 이것은 언어 자체가 하나의 그룹명이 되어야 한다.&lt;/p&gt;
&lt;p&gt;예시를 보면 이해가 된다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;form&amp;gt;
    &amp;lt;label&amp;gt;
        당신이 선호하거나, 현재 사용하는 프레임워크, 라이브러리를 선택 해 주세요. &amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;
        &amp;lt;select id=&amp;quot;optgroup-test-1&amp;quot;&amp;gt;
            &amp;lt;optgroup label=&amp;quot;Java&amp;quot;&amp;gt;
                &amp;lt;option value=&amp;quot;tomcat&amp;quot;&amp;gt;Tomcat&amp;lt;/option&amp;gt;
                &amp;lt;option value=&amp;quot;spring&amp;quot;&amp;gt;Spring&amp;lt;/option&amp;gt;
            &amp;lt;/optgroup&amp;gt;
            &amp;lt;optgroup label=&amp;quot;ECMAScript&amp;quot;&amp;gt;
                &amp;lt;option value=&amp;quot;express&amp;quot;&amp;gt;Express&amp;lt;/option&amp;gt;
                &amp;lt;option value=&amp;quot;react&amp;quot;&amp;gt;React&amp;lt;/option&amp;gt;
                &amp;lt;option value=&amp;quot;nestjs&amp;quot;&amp;gt;NestJS&amp;lt;/option&amp;gt;
            &amp;lt;/optgroup&amp;gt;
            &amp;lt;optgroup label=&amp;quot;Python&amp;quot;&amp;gt;
                &amp;lt;option value=&amp;quot;django&amp;quot;&amp;gt;DJango&amp;lt;/option&amp;gt;
            &amp;lt;/optgroup&amp;gt;
        &amp;lt;/select&amp;gt;
    &amp;lt;/label&amp;gt; &amp;lt;br/&amp;gt; &amp;lt;br/&amp;gt;
    &amp;lt;input type=&amp;quot;button&amp;quot; onclick=&amp;quot;optgroupFunc()&amp;quot; value=&amp;quot;Submit&amp;quot;&amp;gt;&amp;lt;/input&amp;gt;
&amp;lt;/form&amp;gt;

&amp;lt;div id=&amp;quot;optgroup-result-1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;

&amp;lt;script&amp;gt;
const selectDOM = document.getElementById(&amp;quot;optgroup-test-1&amp;quot;);

const container = document.getElementById(&amp;quot;optgroup-result-1&amp;quot;);

function optgroupFunc() {
  const framework = selectDOM.value;

  container.innerHTML = `
    &amp;lt;p&amp;gt; 서버에 당신의 선호 프레임워크가 전송되었습니다 : &amp;lt;strong&amp;gt;${framework}&amp;lt;/strong&amp;gt; &amp;lt;/p&amp;gt;
    `
}

&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;form&gt;
    &lt;label&gt;
        당신이 선호하거나, 현재 사용하는 프레임워크, 라이브러리를 선택 해 주세요. &lt;br/&gt;&lt;br/&gt;
        &lt;select id=&quot;optgroup-test-1&quot;&gt;
            &lt;optgroup label=&quot;Java&quot;&gt;
                &lt;option value=&quot;tomcat&quot;&gt;Tomcat&lt;/option&gt;
                &lt;option value=&quot;spring&quot;&gt;Spring&lt;/option&gt;
            &lt;/optgroup&gt;
            &lt;optgroup label=&quot;ECMAScript&quot;&gt;
                &lt;option value=&quot;express&quot;&gt;Express&lt;/option&gt;
                &lt;option value=&quot;react&quot;&gt;React&lt;/option&gt;
                &lt;option value=&quot;nestjs&quot;&gt;NestJS&lt;/option&gt;
            &lt;/optgroup&gt;
            &lt;optgroup label=&quot;Python&quot;&gt;
                &lt;option value=&quot;django&quot;&gt;DJango&lt;/option&gt;
            &lt;/optgroup&gt;
        &lt;/select&gt;
    &lt;/label&gt; &lt;br/&gt; &lt;br/&gt;
    &lt;input type=&quot;button&quot; onclick=&quot;optgroupFunc()&quot; value=&quot;Submit&quot;&gt;&lt;/input&gt;
&lt;/form&gt;

&lt;div id=&quot;optgroup-result-1&quot;&gt;&lt;/div&gt;

&lt;script&gt;
const selectDOM = document.getElementById(&quot;optgroup-test-1&quot;);

const container = document.getElementById(&quot;optgroup-result-1&quot;);

function optgroupFunc() {
  const framework = selectDOM.value;

  container.innerHTML = `
    &lt;p&gt; 서버에 당신의 선호 프레임워크가 전송되었습니다 : &lt;strong&gt;${framework}&lt;/strong&gt; &lt;/p&gt;
    `
}

&lt;/script&gt;

&lt;br/&gt;

&lt;p&gt;제출 버튼을 클릭 해 보면, 내가 모킹 해 놓은 결과를 볼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;optgroup&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;없음&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;option&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;드롭다운 리스트에서의 선택지를 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;위에서 연관된 &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;optgroup&amp;gt;&lt;/code&gt; 을 사용 해 보면서, 이 태그를 사용했다.&lt;/p&gt;
&lt;p&gt;그런데, 상위 태그로 &lt;code&gt;&amp;lt;datalist&amp;gt;&lt;/code&gt; 라는 태그를 사용할 수도 있었다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;input list=&amp;quot;browsers&amp;quot; /&amp;gt;

&amp;lt;datalist id=&amp;quot;browsers&amp;quot;&amp;gt;
  &amp;lt;option value=&amp;quot;크롬&amp;quot; /&amp;gt;
  &amp;lt;option value=&amp;quot;엣지&amp;quot; /&amp;gt;
  &amp;lt;option value=&amp;quot;파이어폭스&amp;quot; /&amp;gt;
  &amp;lt;option value=&amp;quot;오페라&amp;quot; /&amp;gt;
&amp;lt;/datalist&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;input list=&quot;browsers&quot;/&gt;

&lt;datalist id=&quot;browsers&quot;&gt;
    &lt;option value=&quot;크롬&quot;/&gt;
    &lt;option value=&quot;엣지&quot;/&gt;
    &lt;option value=&quot;파이어폭스&quot;/&gt;
    &lt;option value=&quot;오페라&quot;/&gt;
&lt;/datalist&gt;

&lt;br/&gt;

&lt;p&gt;보다시피, &lt;code&gt;&amp;lt;option&amp;gt;&lt;/code&gt; 태그는 단순한 인풋 태그에도, 사용자의 입력을 도와주는 역할을 해 준다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;option&amp;gt;&lt;/code&gt; 속성들 :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;disabled&lt;/code&gt; : 비활성화 되어야 할 선택지라는 것을 지정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;label&lt;/code&gt; : 옵션 텍스트를 의미하는 더 짧은 레이블을 지정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;selected&lt;/code&gt; : 페이지가 로딩 될 때 미리 선택되어져야 할 선택지 지정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;value&lt;/code&gt; : 서버로 보내져야 할 값을 지정&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;option&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;p&gt;없음&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;label&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;input&lt;/code&gt; 요소에 대한 레이블을 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;말 그대로, &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; 에도 포커싱을 줄 수 있는 태그이기도 하지만,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; 나, &lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt; 와 같은 인풋 요소의 태그에도 포커싱 혹은 유도를 수행하기도 한다.&lt;/p&gt;
&lt;p&gt;레이블 안에 input 관련 태그들을 넣어서 포커싱, 유도를 수행 할 수 있기도 한데,&lt;/p&gt;
&lt;p&gt;특정 인풋 태그에 &lt;code&gt;id&lt;/code&gt; 요소가 있다면, &lt;code&gt;&amp;lt;label for=&amp;quot;담당하는 인풋 태그의 id&amp;quot;&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;로 지정 할 수도 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;form&amp;gt;
  &amp;lt;label&amp;gt; 레이블 안에 Input 넣기 : &amp;lt;input /&amp;gt; &amp;lt;/label&amp;gt;
  &amp;lt;label for=&amp;quot;test-input-1&amp;quot;&amp;gt;레이블과 Input 나눠서 작성하기&amp;lt;/label&amp;gt;
  &amp;lt;input id=&amp;quot;test-input-1&amp;quot; /&amp;gt;
&amp;lt;/form&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;form&gt;
    &lt;label&gt;
        레이블 안에 Input 넣기 : &lt;input&gt;
    &lt;/label&gt; &lt;br/&gt;&lt;br/&gt;
    &lt;label for=&quot;test-input-1&quot;&gt;레이블과 Input 나눠서 작성하기&lt;/label&gt;
    &lt;input id=&quot;test-input-1&quot; /&gt; &lt;br/&gt; &lt;br/&gt;
&lt;/form&gt;

&lt;br/&gt;

&lt;p&gt;한 번 글씨를 클릭 해 보면, &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; dp 포커싱이 되는 것을 볼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; 속성들 :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;for&lt;/code&gt; : 어떤 인풋 태그 id 에 대한 레이블인지.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;form&lt;/code&gt; : 자신이 현재 속한 &lt;code&gt;form&lt;/code&gt; id 를 지정.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;label {
  cursor: default;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;fieldset&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;form 내부에서 연관된 엘리먼트들을 그룹화한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;실제로 진행하게 되는 프로젝트에서, &lt;code&gt;form&lt;/code&gt; 태그는 내부에 많은 인풋 요소들을 가지게 된다.&lt;/p&gt;
&lt;p&gt;이 때, 인풋 요소들이 연관성을 가져서, 다른 데이터셋을 받거나, 스타일링 될 수도 있다.&lt;/p&gt;
&lt;p&gt;이 때, &lt;code&gt;&amp;lt;fieldset&amp;gt;&lt;/code&gt; 과 관련된 태그가 &lt;code&gt;&amp;lt;legend&amp;gt;&lt;/code&gt; 인데, 이 태그는 필드셋의 제목과 같은 역할을 수행하기도 한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;form&amp;gt;
    &amp;lt;fieldset&amp;gt;
        &amp;lt;legend&amp;gt;이름 입력 칸&amp;lt;/legend&amp;gt;
        &amp;lt;label&amp;gt;
            당신의 성 : &amp;lt;input id=&amp;quot;fdset-input-fname&amp;quot; /&amp;gt;
        &amp;lt;/label&amp;gt; &amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;
        &amp;lt;label&amp;gt;
            * 당신의 이름 : &amp;lt;input id=&amp;quot;fdset-input-lname&amp;quot; required/&amp;gt;
        &amp;lt;/label&amp;gt;
    &amp;lt;/fieldset&amp;gt; &amp;lt;br/&amp;gt;
    &amp;lt;fieldset&amp;gt;
        &amp;lt;legend&amp;gt;연락처 입력 칸&amp;lt;/legend&amp;gt;
        &amp;lt;label&amp;gt;
            * 이메일 : &amp;lt;input id=&amp;quot;fdset-input-email&amp;quot; placeholder=&amp;quot;example@example.com&amp;quot; required/&amp;gt;
        &amp;lt;/label&amp;gt;&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;
        &amp;lt;label&amp;gt;
            핸드폰 : &amp;lt;input id=&amp;quot;fdset-input-phone&amp;quot; placeholder=&amp;quot;010-1234-5678&amp;quot;/&amp;gt;
        &amp;lt;/label&amp;gt;
    &amp;lt;/fieldset&amp;gt; &amp;lt;br/&amp;gt;
    &amp;lt;fieldset&amp;gt;
        &amp;lt;legend&amp;gt;선호 프로그램 언어 선택&amp;lt;/legend&amp;gt;
        &amp;lt;label&amp;gt; * 당신이 선호하는 프로그래밍 언어를 선택 해 주세요. &amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;
            &amp;lt;select id=&amp;quot;fdset-select-language&amp;quot; required&amp;gt;
                &amp;lt;optgroup label=&amp;quot;저 수준 언어&amp;quot;&amp;gt;
                    &amp;lt;option value=&amp;quot;C&amp;quot;&amp;gt;C 언어&amp;lt;/option&amp;gt;
                    &amp;lt;option value=&amp;quot;C++&amp;quot;&amp;gt;C++ 언어&amp;lt;/option&amp;gt;
                &amp;lt;/optgroup&amp;gt;
                &amp;lt;optgroup label=&amp;quot;중간 수준 언어&amp;quot;&amp;gt;
                    &amp;lt;option value=&amp;quot;Java&amp;quot;&amp;gt;Java 언어&amp;lt;/option&amp;gt;
                    &amp;lt;option value=&amp;quot;C#&amp;quot;&amp;gt;C# 언어&amp;lt;/option&amp;gt;
                &amp;lt;/optgroup&amp;gt;
                &amp;lt;optgroup label=&amp;quot;높은 수준 언어&amp;quot;&amp;gt;
                    &amp;lt;option value=&amp;quot;Python&amp;quot;&amp;gt;Python 언어&amp;lt;/option&amp;gt;
                    &amp;lt;option value=&amp;quot;JavaScript&amp;quot;&amp;gt;JavaScript 언어&amp;lt;/option&amp;gt;
                &amp;lt;/optgroup&amp;gt;
            &amp;lt;/select&amp;gt;
        &amp;lt;/label&amp;gt;
    &amp;lt;/fieldset&amp;gt; &amp;lt;br/&amp;gt;
    &amp;lt;input type=&amp;quot;button&amp;quot; onclick=&amp;quot;fdsetFunc()&amp;quot; value=&amp;quot;서버에 제출&amp;quot;&amp;gt;&amp;lt;/input&amp;gt;
&amp;lt;/form&amp;gt;

&amp;lt;div id=&amp;quot;fdset-result-1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;

&amp;lt;!-- 블로그에서 form 제출 기능을 막아놔서 Mocking 하는 것 --&amp;gt;
&amp;lt;script&amp;gt;
const fnameDOM = document.getElementById(&amp;quot;fdset-input-fname&amp;quot;);
const lnameDOM = document.getElementById(&amp;quot;fdset-input-lname&amp;quot;);
const emailDOM = document.getElementById(&amp;quot;fdset-input-email&amp;quot;);
const phoneDOM = document.getElementById(&amp;quot;fdset-input-phone&amp;quot;);
const pLanDOM = document.getElementById(&amp;quot;fdset-select-language&amp;quot;);

function fdsetFunc() {
  const container = document.getElementById(&amp;quot;fdset-result-1&amp;quot;);

  const fname = fnameDOM.value;
  const lname = lnameDOM.value;
  const email = emailDOM.value;
  const phone = phoneDOM.value;
  const programmingLan = pLanDOM.value;

  let textHTML;

  if(!lname) {
    textHTML = &amp;quot;&amp;lt;p&amp;gt;이름은 꼭 있어야 합니다.&amp;lt;/p&amp;gt;&amp;quot;
    lnameDOM.focus();
  } else if(!email) {
    textHTML = &amp;quot;&amp;lt;p&amp;gt;연락처 중 이메일이 필요합니다.&amp;lt;/p&amp;gt;&amp;quot;
    emailDOM.focus();
  } else if(!programmingLan) {
    textHTML = &amp;quot;&amp;lt;p&amp;gt;프로그래밍 언어를 선택 해 주세요.&amp;lt;/p&amp;gt;&amp;quot;
    pLanDOM.focus();
  } else {
    textHTML = `
      &amp;lt;p&amp;gt;서버에 전송된 데이터 형태는 이렇다 : &amp;lt;/p&amp;gt;
      &amp;lt;table&amp;gt;
        &amp;lt;thead&amp;gt;
          ${fname ? &amp;quot;&amp;lt;th&amp;gt;성&amp;lt;/th&amp;gt;&amp;quot; : &amp;quot;&amp;quot;}
          &amp;lt;th&amp;gt;이름&amp;lt;/th&amp;gt;
          &amp;lt;th&amp;gt;이메일&amp;lt;/th&amp;gt;
          ${phone ? &amp;quot;&amp;lt;th&amp;gt;핸드폰 번호&amp;lt;/th&amp;gt;&amp;quot; : &amp;quot;&amp;quot;}
          &amp;lt;th&amp;gt;선호 프로그래밍 언어&amp;lt;/th&amp;gt;
        &amp;lt;/thead&amp;gt;
        &amp;lt;tbody&amp;gt;
        &amp;lt;tr&amp;gt;
          ${fname ? `&amp;lt;td&amp;gt;${fname}&amp;lt;/td&amp;gt;` : &amp;quot;&amp;quot;}
          &amp;lt;td&amp;gt;${lname}&amp;lt;/td&amp;gt;
          &amp;lt;td&amp;gt;${email}&amp;lt;/td&amp;gt;
          ${phone ? `&amp;lt;td&amp;gt;${phone}&amp;lt;/td&amp;gt;` : &amp;quot;&amp;quot;}
          &amp;lt;td&amp;gt;${programmingLan}&amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
        &amp;lt;/tbody&amp;gt;
      &amp;lt;/table&amp;gt;
      `

    container.innerHTML = textHTML;
  }
  container.innerHTML = textHTML;
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;form&gt;
    &lt;fieldset&gt;
        &lt;legend&gt;이름 입력 칸&lt;/legend&gt;
        &lt;label&gt;
            당신의 성 : &lt;input id=&quot;fdset-input-fname&quot; /&gt;
        &lt;/label&gt; &lt;br/&gt;&lt;br/&gt;
        &lt;label&gt;
            *(필수) 당신의 이름 : &lt;input id=&quot;fdset-input-lname&quot; required/&gt;
        &lt;/label&gt;
    &lt;/fieldset&gt; &lt;br/&gt;
    &lt;fieldset&gt;
        &lt;legend&gt;연락처 입력 칸&lt;/legend&gt;
        &lt;label&gt;
            *(필수) 이메일 : &lt;input id=&quot;fdset-input-email&quot; placeholder=&quot;example@example.com&quot; required/&gt;
        &lt;/label&gt;&lt;br/&gt;&lt;br/&gt;
        &lt;label&gt;
            핸드폰 : &lt;input id=&quot;fdset-input-phone&quot; placeholder=&quot;010-1234-5678&quot;/&gt;
        &lt;/label&gt;
    &lt;/fieldset&gt; &lt;br/&gt;
    &lt;fieldset&gt;
        &lt;legend&gt;선호 프로그램 언어 선택&lt;/legend&gt;
        &lt;label&gt; *(필수) 당신이 선호하는 프로그래밍 언어를 선택 해 주세요. &lt;br/&gt;&lt;br/&gt;
            &lt;select id=&quot;fdset-select-language&quot; required&gt;
                &lt;optgroup label=&quot;저 수준 언어&quot;&gt;
                    &lt;option value=&quot;C&quot;&gt;C 언어&lt;/option&gt;
                    &lt;option value=&quot;C++&quot;&gt;C++ 언어&lt;/option&gt;
                &lt;/optgroup&gt;
                &lt;optgroup label=&quot;중간 수준 언어&quot;&gt;
                    &lt;option value=&quot;Java&quot;&gt;Java 언어&lt;/option&gt;
                    &lt;option value=&quot;C#&quot;&gt;C# 언어&lt;/option&gt;
                &lt;/optgroup&gt;
                &lt;optgroup label=&quot;높은 수준 언어&quot;&gt;
                    &lt;option value=&quot;Python&quot;&gt;Python 언어&lt;/option&gt;
                    &lt;option value=&quot;JavaScript&quot;&gt;JavaScript 언어&lt;/option&gt;
                &lt;/optgroup&gt;
            &lt;/select&gt;
        &lt;/label&gt;
    &lt;/fieldset&gt; &lt;br/&gt;
    &lt;input type=&quot;button&quot; onclick=&quot;fdsetFunc()&quot; value=&quot;서버에 제출&quot;&gt;&lt;/input&gt;
&lt;/form&gt;

&lt;div id=&quot;fdset-result-1&quot;&gt;&lt;/div&gt;

&lt;!-- 블로그에서 form 제출 기능을 막아놔서 Mocking 하는 것 --&gt;
&lt;script&gt;
const fnameDOM = document.getElementById(&quot;fdset-input-fname&quot;);
const lnameDOM = document.getElementById(&quot;fdset-input-lname&quot;);
const emailDOM = document.getElementById(&quot;fdset-input-email&quot;);
const phoneDOM = document.getElementById(&quot;fdset-input-phone&quot;);
const pLanDOM = document.getElementById(&quot;fdset-select-language&quot;);

function fdsetFunc() {
  const container = document.getElementById(&quot;fdset-result-1&quot;);

  const fname = fnameDOM.value;
  const lname = lnameDOM.value;
  const email = emailDOM.value;
  const phone = phoneDOM.value;
  const programmingLan = pLanDOM.value;

  let textHTML;

  if(!lname) {
    textHTML = &quot;&lt;p&gt;이름은 꼭 있어야 합니다.&lt;/p&gt;&quot;
    lnameDOM.focus();
  } else if(!email) {
    textHTML = &quot;&lt;p&gt;연락처 중 이메일이 필요합니다.&lt;/p&gt;&quot;
    emailDOM.focus();
  } else if(!programmingLan) {
    textHTML = &quot;&lt;p&gt;프로그래밍 언어를 선택 해 주세요.&lt;/p&gt;&quot;
    pLanDOM.focus();
  } else {
    textHTML = `
      &lt;p&gt;서버에 전송된 데이터 형태는 이렇다 : &lt;/p&gt;
      &lt;table&gt;
        &lt;thead&gt;
          ${fname ? &quot;&lt;th&gt;성&lt;/th&gt;&quot; : &quot;&quot;}
          &lt;th&gt;이름&lt;/th&gt;
          &lt;th&gt;이메일&lt;/th&gt;
          ${phone ? &quot;&lt;th&gt;핸드폰 번호&lt;/th&gt;&quot; : &quot;&quot;}
          &lt;th&gt;선호 프로그래밍 언어&lt;/th&gt;
        &lt;/thead&gt;
        &lt;tbody&gt;
        &lt;tr&gt;
          ${fname ? `&lt;td&gt;${fname}&lt;/td&gt;` : &quot;&quot;}
          &lt;td&gt;${lname}&lt;/td&gt;
          &lt;td&gt;${email}&lt;/td&gt;
          ${phone ? `&lt;td&gt;${phone}&lt;/td&gt;` : &quot;&quot;}
          &lt;td&gt;${programmingLan}&lt;/td&gt;
        &lt;/tr&gt;
        &lt;/tbody&gt;
      &lt;/table&gt;
      `

    container.innerHTML = textHTML;
  }
  container.innerHTML = textHTML;
}
&lt;/script&gt;

&lt;br/&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;fieldset&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;fieldset {
  display: block;
  margin-left: 2px;
  margin-right: 2px;
  padding-top: 0.35em;
  padding-bottom: 0.35em;
  padding-left: 0.75em;
  padding-right: 0.75em;
  border: 2px groove (internal value);
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;legend&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;fieldset&lt;/code&gt; 요소에 대한 &amp;quot;캡션&amp;quot; 을 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;위에서 &lt;code&gt;&amp;lt;legend&amp;gt;&lt;/code&gt; 라는 태그로, 인풋 요소에 대한 그룹핑을 &lt;code&gt;&amp;lt;fieldset&amp;gt;&lt;/code&gt; 으로 수행하고,&lt;/p&gt;
&lt;p&gt;이에 대한 간략한 제목은 &lt;code&gt;&amp;lt;legend&amp;gt;&lt;/code&gt; 로 정의했었다.&lt;/p&gt;
&lt;p&gt;그 결과가, 바로 위에 표시되고 있을 것이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;위에 보시면 됩니다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;위에 잘 정리 해 놓았어요&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;legend&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;legend {
  displayu: block;
  padding-left: 2px;
  padding-right: 2px;
  border: none;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;datalist&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;입력 칸에 미리 정의된 선택지 리스트를 정의해준다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;우리가 인터넷을 할 때 정말 많은 도움을 받는 기능이라고 생각하는데,&lt;/p&gt;
&lt;p&gt;내가 딱히 &lt;code&gt;&amp;lt;datalist&amp;gt;&lt;/code&gt; 로 지정 해 놓지 않아도,&lt;/p&gt;
&lt;p&gt;브라우저가 각 인풋 태그들을 자동으로 인식하여 어떤 정보를 자동으로 넣어줄지 선택할 수 있는&lt;/p&gt;
&lt;p&gt;일종의 캐싱 기능을 제공한다.&lt;/p&gt;
&lt;p&gt;그것은 브라우저가 자동으로 유저에게 제공 해 주는 기능이고,&lt;/p&gt;
&lt;p&gt;웹 개발자는 아마 검색 기능에 이를 유용하게 사용할 수 있지 않을까 생각한다.&lt;/p&gt;
&lt;p&gt;예를 들면, 우리가 구글이나 네이버에 &amp;quot;서울&amp;quot; 을 입력한다면, &amp;quot;서울&amp;quot; 키워드를 포함한&lt;/p&gt;
&lt;p&gt;&amp;quot;서울 맛집&amp;quot;, &amp;quot;서울 관광지&amp;quot;, &amp;quot;서울 ...&amp;quot; 등등을 입력칸 바로 아래에 표시할 수 있을 것이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;이번 예시는 어떻게 구현 할 거냐면,&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이 블로그 글 내에 내가 작성한 &lt;code&gt;&amp;lt;h3&amp;gt;&lt;/code&gt; 에 해당하는 모든 DOM 을 가져와서,&lt;/p&gt;
&lt;p&gt;힌트를 줄 수 있게 만들 것이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;button onclick=&amp;quot;loadAllH3()&amp;quot;&amp;gt;이 페이지의 모든 태그 리스트 가져오기&amp;lt;/button&amp;gt;
&amp;lt;br /&amp;gt;

&amp;lt;label&amp;gt;
  &amp;lt;input id=&amp;quot;datalist-input&amp;quot; list=&amp;quot;all-h3&amp;quot; /&amp;gt;
&amp;lt;/label&amp;gt;
&amp;lt;br /&amp;gt;

&amp;lt;br /&amp;gt;
&amp;lt;button onclick=&amp;quot;datalistFunc()&amp;quot;&amp;gt;이동하기&amp;lt;/button&amp;gt;

&amp;lt;datalist id=&amp;quot;all-h3&amp;quot;&amp;gt; &amp;lt;/datalist&amp;gt;

&amp;lt;script&amp;gt;
  const inputDOM = document.getElementById(&amp;quot;datalist-input&amp;quot;);
  const dataDOM = document.getElementById(&amp;quot;all-h3&amp;quot;);
  let optionList = [];

  function loadAllH3() {
    const h3List = document.querySelectorAll(&amp;quot;h3 &amp;gt; code&amp;quot;);

    optionList = h3List.map((codeNode) =&amp;gt; {
      return `&amp;lt;option value=&amp;quot;${codeNode.innerText}&amp;quot;/&amp;gt;`;
    });

    let optionListHTML = &amp;quot;&amp;quot;;

    for (let i = 0; i &amp;lt; optionList.length; i++) {
      optionListHTML += optionList[i];
    }

    dataDOM.innerHTML = optionListHTML;
  }

  function datalistFunc() {
    const result = inputDOM.value;

    const resultDOM = optionList.find((node) =&amp;gt; {
      return node.innerText == result;
    });

    if (resultDOM) {
      resultDOM.scrollIntoView({ behavior: &amp;quot;smooth&amp;quot;, block: &amp;quot;start&amp;quot; });
    }
  }
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;&lt;button onclick=&quot;loadAllH3()&quot;&gt;이 페이지의 모든 태그 리스트 가져오기&lt;/button&gt; &lt;br/&gt;&lt;/p&gt;
&lt;label&gt;
    &lt;input id=&quot;datalist-input&quot; list=&quot;all-h3&quot;/&gt;
&lt;/label&gt; &lt;br/&gt;

&lt;br/&gt;
&lt;button onclick=&quot;datalistFunc()&quot;&gt;이동하기&lt;/button&gt;

&lt;datalist id=&quot;all-h3&quot;&gt;
&lt;/datalist&gt;

&lt;div id=&quot;datalist-result-1&quot;&gt;&lt;/div&gt;

&lt;script&gt;
const inputDOM = document.getElementById(&quot;datalist-input&quot;);
const dataDOM = document.getElementById(&quot;all-h3&quot;);
let h3List;
let optionList = [];


function loadAllH3() {
  h3List = document.querySelectorAll(&quot;h3 &gt; a&quot;);

  for(let i = 0; i &lt; h3List.length; i++) {
    const optionTagHTML = `&lt;option value=&quot;${h3List[i].innerText}&quot;/&gt;`;
    optionList.push(optionTagHTML);
  }

  let optionListHTML = &quot;&quot;;

  for(let i = 0; i &lt; optionList.length; i++) {
    optionListHTML += optionList[i];
  }

  dataDOM.innerHTML = optionListHTML;
}

function datalistFunc() {
  const result = inputDOM.value;

  let resultDOM;
  for(let i = 0; i &lt; h3List.length; i++) {
    const h3Node = h3List[i];
    if(h3Node.innerText == result) {
      resultDOM = h3Node;
      break;
    }
  }

  if(resultDOM) {
    resultDOM.scrollIntoView({behavior : &quot;smooth&quot;, block : &quot;start&quot;});
    document.getElementById(&quot;datalist-result-1&quot;).innerHTML = ``;
  } else {
    document.getElementById(&quot;datalist-result-1&quot;).innerHTML = `
      &lt;p style=&quot;color : red&quot;&gt;존재하지 않는 엘리먼트로 이동 할 수 없습니다.&lt;/p&gt;
      `;
  }
}
&lt;/script&gt;

&lt;br/&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;datalist&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;datalist {
  display: none;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;output&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;계산의 결과를 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; 태그 내부에서 JS 없이 계산하기 위한 일종의 태그인데,&lt;/p&gt;
&lt;p&gt;개인적으로 태그를 사용한 방법론보다는, 단편적으로 &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; 를 넣어 해결하는 것이&lt;/p&gt;
&lt;p&gt;훨씬 더 가독성과 확장성이 좋다고 생각한다.&lt;/p&gt;
&lt;p&gt;따라서, 이는 W3Schools 의 예제를 가져오겠다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;form oninput=&amp;quot;x.value=parseInt(a.value)+parseInt(b.value)&amp;quot;&amp;gt;
  &amp;lt;input type=&amp;quot;range&amp;quot; id=&amp;quot;a&amp;quot; value=&amp;quot;50&amp;quot; /&amp;gt;
  +&amp;lt;input type=&amp;quot;number&amp;quot; id=&amp;quot;b&amp;quot; value=&amp;quot;25&amp;quot; /&amp;gt; =&amp;lt;output
    name=&amp;quot;x&amp;quot;
    for=&amp;quot;a b&amp;quot;
  &amp;gt;&amp;lt;/output&amp;gt;
&amp;lt;/form&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;form oninput=&quot;x.value=parseInt(a.value)+parseInt(b.value)&quot;&gt;
  &lt;input type=&quot;range&quot; id=&quot;a&quot; value=&quot;50&quot;&gt;
  +&lt;input type=&quot;number&quot; id=&quot;b&quot; value=&quot;25&quot;&gt;
  =&lt;output name=&quot;x&quot; for=&quot;a b&quot;&gt;&lt;/output&gt;
&lt;/form&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;output&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;output {
  display: inline;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h2&gt;Category - Frames (프레임)&lt;/h2&gt;
&lt;p&gt;HTML도 버전이 업그레이드 되면서 다양한 태그의 역할이 합쳐지거나 없어졌는데,&lt;/p&gt;
&lt;p&gt;이 카테고리의 &amp;quot;frame&amp;quot; 영역에서는, &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; 하나밖에 존재하지 않는다.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;iframe&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;내부 프레임을 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;즉, 현재의 웹 사이트에, 또 다른 사이트를 넣는다.&lt;/p&gt;
&lt;p&gt;이 때, &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; 에는 다시 &lt;code&gt;html&lt;/code&gt; 태그가 들어갈 수 있으며,&lt;/p&gt;
&lt;p&gt;아니면, &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; 전용의 &lt;code&gt;src&lt;/code&gt; 속성으로 페이지를 정의 할 수 있다.&lt;/p&gt;
&lt;p&gt;그런데, 요즘은 보안 정책으로 인해, 타사의 사이트를 맘대로 넣기가 굉장히 어렵다.&lt;/p&gt;
&lt;p&gt;따라서, 나는 내 블로그 글 페이지 URL 을 기반으로 src 를 작성 할 것이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;iframe
  id=&amp;quot;iframe-dom-1&amp;quot;
  src=&amp;quot;https://codecreature.tistory.com/175&amp;quot;
  style=&amp;quot;width : 70%; height : 10rem; border: 2px solid #444;&amp;quot;
&amp;gt;&amp;lt;/iframe&amp;gt;
&amp;lt;br /&amp;gt;
&amp;lt;br /&amp;gt;

&amp;lt;button value=&amp;quot;iframe 화면 조작하기&amp;quot; onclick=&amp;quot;iframeFunc()&amp;quot; /&amp;gt; &amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;

&amp;lt;button value=&amp;quot;다시 블로그 프레임으로 보기&amp;quot; onclick=&amp;quot;iframeReturnFunc()&amp;quot; /&amp;gt;
&amp;lt;br /&amp;gt;

&amp;lt;script&amp;gt;
  const iframeDOM = document.getElementById(&amp;quot;iframe-dom-1&amp;quot;);

  function iframeFunc() {
    const newFrameHTML = `
      &amp;lt;!DOCTYPE html&amp;gt;
      &amp;lt;html&amp;gt;
        &amp;lt;head&amp;gt;
          &amp;lt;title&amp;gt;새로운 프레임 HTML&amp;lt;/title&amp;gt;
        &amp;lt;/head&amp;gt;
        &amp;lt;body&amp;gt;
          &amp;lt;p&amp;gt;내부 JS 로 프레임의 내용이 바뀌었습니다.&amp;lt;/p&amp;gt;
        &amp;lt;/body&amp;gt;
      &amp;lt;/html&amp;gt;
      `;
    iframeDOM.innerHTML = newFrameHTML;
  }
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현 결과&lt;/strong&gt; :&lt;/p&gt;
&lt;iframe
    id=&quot;iframe-dom-1&quot;
    src=&quot;https://codecreature.tistory.com/175&quot;
    style=&quot;width : 70%; height : 10rem; border: 2px solid #444;&quot;
&gt;&lt;/iframe&gt;&lt;br/&gt;&lt;br/&gt;

&lt;p&gt;&lt;button onclick=&quot;iframeFunc()&quot;&gt;iframe 화면 조작하기&lt;/button&gt; &lt;br/&gt;&lt;br/&gt;&lt;/p&gt;
&lt;script&gt;

const iframeDOM = document.getElementById(&quot;iframe-dom-1&quot;);

function iframeFunc() {
  iframeDOM.src = &quot;&quot;;
  iframeDOM.srcdoc=&quot;&lt;p&gt;srcdoc 속성을 이용하여 프레임 조작&lt;/p&gt;&quot;;

}

&lt;/script&gt;


&lt;br/&gt;

&lt;p&gt;&lt;code&gt;iframe&lt;/code&gt; 태그에 접근하기 위한 2 가지 방법론이 존재하는데,&lt;/p&gt;
&lt;p&gt;그 중 나는 &lt;code&gt;iframe&lt;/code&gt; 의 속성인 &lt;code&gt;srcdoc&lt;/code&gt; 을 수정하는 것으로 조작했다.&lt;/p&gt;
&lt;p&gt;그리고, &lt;code&gt;iframe&lt;/code&gt; 은 페이지 내부에 별도의 또 다른 페이지를 삽입하는 태그이기 때문에,&lt;/p&gt;
&lt;p&gt;여러 보안 요소, 혹은 &lt;code&gt;iframe&lt;/code&gt; 을 포함하는 페이지와의 통신을 위해서 여러 특별 속성이 존재한다.&lt;/p&gt;
&lt;p&gt;해당 속성들은 &lt;a target=&quot;_blank&quot; href=&quot;https://www.w3schools.com/TAGS/tag_iframe.asp#:~:text=Yes-,Attributes,-Attribute&quot;&gt;여기서 확인&lt;/a&gt; 하길 바란다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;iframe:focus {
    outline : none;
}

iframe[seamless] {
    display : block;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h2&gt;이 글을 작성하며 배운 것.&lt;/h2&gt;
&lt;p&gt;이번에는 이 글을 읽어주는 독자들이 잘 이해 할 수 있도록,&lt;/p&gt;
&lt;p&gt;제한된 기능들을 Mocking 하여 구현했다.&lt;/p&gt;
&lt;p&gt;특히, 실제 웹 &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; 전송 환경이 아니라,&lt;/p&gt;
&lt;p&gt;내부의 엘리먼트들의 &lt;code&gt;id&lt;/code&gt; 를 추출하여 직접 값을 이용하다 보니,&lt;/p&gt;
&lt;p&gt;바닐라 자바스크립트로 어떻게 DOM 을 다뤄야 하는지 좀 더 잘 이해하게 되었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그리고, 블로그 글은 &lt;code&gt;#article&lt;/code&gt; 이라는 &lt;code&gt;id&lt;/code&gt; 를 가진 블록 내부에&lt;/p&gt;
&lt;p&gt;나의 마크다운이 파싱되어 HTML 로 표현되는데,&lt;/p&gt;
&lt;p&gt;블로그가 내 사이트의 보안과 관련된 안전을 위해 여러가지 기능들을 차단 해 놓았다는 것을 알게 되었다.&lt;/p&gt;
&lt;p&gt;(이것이 내가 기능들을 직접 Mocking 한 이유.)&lt;/p&gt;
&lt;p&gt;특히, 이번에 배운 태그 중에서는 &lt;code&gt;&amp;lt;datalist&amp;gt;&lt;/code&gt; 가 나중에 유용하게 사용할 것 같다는 생각을 했다.&lt;/p&gt;
&lt;p&gt;특히, 나중에 검색 엔진을 개발 할 때, &lt;code&gt;postgres&lt;/code&gt; 데이터베이스의 NoSQL 기능으로&lt;/p&gt;
&lt;p&gt;검색 보조를 구현하고 싶다는 생각을 했다.&lt;/p&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;h2&gt;참고 사이트&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;W3Schools HTML Element Reference&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.w3schools.com/TAGS/ref_byfunc.asp&quot;&gt;https://www.w3schools.com/TAGS/ref_byfunc.asp&lt;/a&gt;&lt;/p&gt;</description>
      <category>Web-Server/웹 지식</category>
      <category>Button</category>
      <category>form</category>
      <category>html</category>
      <category>html 태그</category>
      <category>html 폼</category>
      <category>input</category>
      <category>label</category>
      <category>option</category>
      <category>select</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/218</guid>
      <comments>https://codecreature.tistory.com/218#entry218comment</comments>
      <pubDate>Mon, 26 May 2025 23:20:19 +0900</pubDate>
    </item>
    <item>
      <title>HTML 문서 정식 태그 &amp;quot;전부&amp;quot; 공부하기 - 1편 (코드 작성 및 구현까지)</title>
      <link>https://codecreature.tistory.com/217</link>
      <description>&lt;h2&gt;제목 : HTML 문서 태그 공부하기 - 1편&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;이 글을 작성하는 이유&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;단순한 웹 페이지를 작성 할 때 뿐만 아니라, html 파일에 들어가는 tag 들을 알고 있는 것도 중요하다.&lt;/p&gt;
&lt;p&gt;나는 여태까지, JS 의 방법론 기술들 (React, Webpack, package.json, 등등 수많음)&lt;/p&gt;
&lt;p&gt;을 그냥 쓰지 않고, 최소한 그 의미를 알고 사용하기 위해서 노력 해 왔다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;물론, 같은 맥락에서 보자면, HTML 태그를 단순하게 곧바로 사용하는 것이 아니라,&lt;/p&gt;
&lt;p&gt;각 시멘틱 태그의 의미와 사용법을 안다는 취지에서 비슷하다고는 할 수 있다.&lt;/p&gt;
&lt;p&gt;그러나, 좀 더 엄격하게 바라보자면, 각 태그가 어떻게 프로그래밍적으로 블록을 구성하는지 조사하지 않기 때문에,&lt;/p&gt;
&lt;p&gt;태그 자체를 알려고 노력하지 않는다고 할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, 이러한 HTML 태그를 다양하게 활용하는 것은 여러 방면에서 효과적이다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;SEO 를 통한 검색엔진 노출 더욱 활성화&lt;/li&gt;
&lt;li&gt;시멘틱 태그를 통해 정확히 어떤 역할을 하는지 알 수 있음&lt;/li&gt;
&lt;li&gt;간단히 표준 태그를 통해 미리 정해진 컴포넌트를 부착하고, 원하는 속성을 넣을 수 있음&lt;/li&gt;
&lt;li&gt;각각의 태그는 사용법이 존재하며, 태그 내부의 속성으로 커스텀이 가능함&lt;/li&gt;
&lt;li&gt;태그를 모를 때, 나는 버튼을 &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; 로 제작했던 흑역사가 있다..&lt;/li&gt;
&lt;li&gt;이 외에도 태그를 아는 것은 많은 도움이 된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;h2&gt;HTML 태그를 어떻게 익힐 것인가?&lt;/h2&gt;
&lt;p&gt;나는 HTML 태그 자체를 아예 모르는 편은 아니다.&lt;/p&gt;
&lt;p&gt;지금 내가 작성하고 있는 마크다운 파일도, 모든 것을 자체 문법으로 커버할 수 없다.&lt;/p&gt;
&lt;p&gt;예를 들면,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;br/&amp;gt;&lt;/code&gt; : 한 줄 띄기&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt; : 테이블 내부에 코드 블럭을 넣어야 할 때&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;a target=&amp;quot;_blank&amp;quot; ...&amp;gt;&lt;/code&gt; : 자체적으로 제공하는 MD 링크는 현재 사이트를 이동하게 되어 있다. &lt;br/&gt; 이걸 개량해서 위 처럼 직접 작성하는데, 덕분에 새 창에서 참고 자료를 볼 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;code&amp;gt;&lt;/code&gt; : 마크다운 자체적으로 아예 코드 선언 방식 &lt;code&gt;`xxx`&lt;/code&gt; 가 존재하는데, &lt;br/&gt; 아주 가끔 지정된 마크다운 문법이 &lt;code&gt;``&lt;/code&gt; 를 무시할 때가 있을 때 사용한다.&lt;/li&gt;
&lt;li&gt;등등...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;하지만, 마크다운 자체에서 HTML 을 겹쳐서 사용할 수 있다는 장점 때문에,&lt;/p&gt;
&lt;p&gt;HTML 을 사용하면 마크다운을 더 풍부하게 사용 할 수 있기도 하다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;W3Schools 사이트에 카테고리별로 태그를 익힐 수 있도록 지원한다.&lt;/p&gt;
&lt;p&gt;따라서, 나는 중제목, 소제목으로 나누어 파트를 분리하고,&lt;/p&gt;
&lt;p&gt;각 태그의 실제 상황에 맞게 코드를 작성 해 볼 생각이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Category - 제일 기본적인 HTML 태그&lt;/h2&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;!DOCTYPE&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;문서의 유형을 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
  ....
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;모든 HTML 문서들은 &lt;code&gt;&amp;lt;!DOCTYPE&amp;gt;&lt;/code&gt; 정의로 시작되어야 한다.&lt;/p&gt;
&lt;p&gt;사실 이 정의는 html 태그는 아니고, 브라우저에게 &amp;quot;어떤 유형의 브라우저이다&amp;quot; 라고 알려주는 정보이다.&lt;/p&gt;
&lt;p&gt;그리고, 이 태그는 대소문자를 신경쓰지 않아 소문자로 작성해도 된다.&lt;/p&gt;
&lt;p&gt;EX - &lt;code&gt;&amp;lt;!doctype html&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;html&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;HTML 문서를 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    ...
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    ...
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;html&lt;/code&gt; 태그는 HTML 문서 자체의 루트를 의미, 표현한다.&lt;/p&gt;
&lt;p&gt;즉, 내부의 어떤 태그이던, 부모 DOM 으로 거슬러 올라간다면 &lt;code&gt;html&lt;/code&gt; 태그인 것이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;html&lt;/code&gt; 태그는 &lt;code&gt;&amp;lt;!DOCTYPE&amp;gt;&lt;/code&gt; 태그를 제외하고 모든 종류의 HTML 요소를 포함한다.&lt;/p&gt;
&lt;p&gt;그리고, 태그 내부에 &lt;code&gt;lang&lt;/code&gt; 속성을 넣을 수 있는데,&lt;/p&gt;
&lt;p&gt;이는 웹 페이지의 언어를 정의하기 위해 사용한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;!-- 혹은 lang=&amp;quot;en&amp;quot; --&amp;gt;
&amp;lt;html lang=&amp;quot;ko-kr&amp;quot;&amp;gt;
  ...
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;W3Schools 에서 의외로 중요한 부분도 보여주는데,&lt;/p&gt;
&lt;p&gt;바로 CSS 기본 설정이다.&lt;/p&gt;
&lt;p&gt;특정 스타일링을 진행하기 위해 이미 설정되어 있는 정보는 매우 중요하다고 생각한다.&lt;/p&gt;
&lt;p&gt;그래야 이 컴포넌트가 왜 이렇게 표현되는지 알 수 있기 때문이다. (개인적 생각)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;html {
  display: block;
}

html:focus {
  outline: none;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; 태그는 애초부터 Root 요소이기 때문에, 블록이라는 것을 이해했다.&lt;/p&gt;
&lt;p&gt;여기서 설정된 &lt;code&gt;display : block&lt;/code&gt; 이란, 한 줄 전체를 자치한다는 것을 의미한다.&lt;/p&gt;
&lt;p&gt;이는 &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; 와도 동일하다.&lt;/p&gt;
&lt;p&gt;그리고, &lt;code&gt;:focus&lt;/code&gt; 는 해당 요소에 사용자의 인터랙션이 발생했을 때를 의미한다.&lt;/p&gt;
&lt;p&gt;여기서 &lt;code&gt;outline: none&lt;/code&gt; 은, 브라우저가 기본적으로 요소에 제공하는 테두리를 없애겠다는 것이다.&lt;/p&gt;
&lt;p&gt;크기를 차지하지는 않는데, 보통 &lt;code&gt;none&lt;/code&gt; 으로 설정 후, &lt;code&gt;box-shadow&lt;/code&gt; 를 통해&lt;/p&gt;
&lt;p&gt;사용자에게 시각적인 피드백을 주는 편이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;head&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;문서에 대한 메타데이터와 정보를 담는다&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;어찌 보면, 웹 개발 시 동적 브라우저를 위한 JS 코드 컨테이너 역할을 하는 중요한 태그라고 생각한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;헤드 태그에는 웹사이트의 제목을 달 수 있음&amp;lt;/title&amp;gt;
    &amp;lt;script src=&amp;quot;https://CDN 으로 가져올 코드 베이스&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script&amp;gt;
      // 혹은, 내부에 JS 코드를 넣을 수도 있다.
    &amp;lt;/script&amp;gt;
    &amp;lt;!-- 혹은 문서 상위에 style css 를 직접 선언 할 수도 있다. --&amp;gt;
    &amp;lt;style&amp;gt;
      .container {
        display: flex;
      }
    &amp;lt;/style&amp;gt;
    &amp;lt;!-- 웹 서버의 styles.css 를 가져와서 스타일링 한다. --&amp;gt;
    &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; type=&amp;quot;text/css&amp;quot; href=&amp;quot;styles.css&amp;quot; /&amp;gt;
    &amp;lt;!-- 등등.. --&amp;gt;
  &amp;lt;/head&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; 태그 내부에 들어 갈 수 있는 또 다른 태그들은 이렇다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;base&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;noscript&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; 의 기본 CSS 설정&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;head {
  display: none;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;어찌 보면, &lt;code&gt;head&lt;/code&gt; 태그는 메타데이터 선언 및 불러오기 역할이라서&lt;/p&gt;
&lt;p&gt;보여지지 않는것이 당연하지 않을까 생각된다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;title&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;문서의 제목을 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;위에서 정의했던 것 처럼, &lt;code&gt;head&lt;/code&gt; 태그 내부에 &lt;code&gt;title&lt;/code&gt; 을 넣어 문서의 제목을 정한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;타이틀은 SEO 를 위해 중요하다&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;우리는 만든 웹사이트를 사람들에게 노출시키기 위해 만든다고 할 수 있다.&lt;/p&gt;
&lt;p&gt;그렇다면, 우리는 SEO(Search Engine Optimization) 를 신경 쓸 수 밖에 없다.&lt;/p&gt;
&lt;p&gt;브라우저가 페이지를 리스팅 할 때 검색 엔진 알고리즘으로 사용하는 것이 &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; 속성이라고 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;또한, &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; 은 브라우저 탭(ToolBar) 의 제목을 정한다.&lt;/p&gt;
&lt;p&gt;즐겨찾기 시에 내부의 제목으로 제공된다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;body&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;문서의 body(몸체)를 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    ...
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;h3&amp;gt;mini heading&amp;lt;/h3&amp;gt;
    &amp;lt;p&amp;gt;패러그래프&amp;lt;/p&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; 태그는 &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; 문서에 &amp;quot;단 한번&amp;quot; 만 정의된다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; 는 HTML 문서의 모든 컨텐츠를 담는다.&lt;/p&gt;
&lt;p&gt;여기서 헤딩, 이미지, 링크, 테이블, 리스트, 등등이 담겨진다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;html&lt;/code&gt; 이후로 처음 등장한, 유저에게 보여지는 &lt;code&gt;DOM&lt;/code&gt; 인데,&lt;/p&gt;
&lt;p&gt;메타데이터를 제외하고, 컨테이너 DOM 으로서는 최고 레벨이라고도 할 수 있다.&lt;/p&gt;
&lt;p&gt;만약 &lt;code&gt;body&lt;/code&gt; 태그에 백그라운드 이미지나 색상을 설정한다면,&lt;/p&gt;
&lt;p&gt;문서 전체가 해당 이미지, 혹은 색상을 가지는 것을 볼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; 의 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;body {
  display: block;
  margin: 8px;
}

body: focus {
  outline: none;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;h1&lt;/code&gt; ~ &lt;code&gt;h6&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;HTML 헤딩을 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;사실 내가 마크다운에서 사용하고 있는,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;# 제일 큰 헤딩&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;## 내가 마크다운 문서에서 title 을 작성할 때 사용&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;### 각각의 소제목을 정할 때 사용&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이러한 문법들은 결국 &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; ~ &lt;code&gt;&amp;lt;h3&amp;gt;&lt;/code&gt; 까지를 의미한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    ...
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;h1&amp;gt;가장 큼&amp;lt;/h1&amp;gt;
    &amp;lt;h2&amp;gt;...&amp;lt;/h2&amp;gt;
    &amp;lt;!-- ... --&amp;gt;
    &amp;lt;h6&amp;gt;가장 작음&amp;lt;/h6&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;heading 은 한 줄 자체를 차지하기 때문에, 그 영역의 색상과 내부 글자 색상을 정할 수 있다.&lt;/p&gt;
&lt;p&gt;그리고, &lt;code&gt;text-align&lt;/code&gt; 이라는 속성을 통해 정렬을 할 수도 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;h2 style=&amp;quot;text-align: center&amp;quot;&amp;gt;헤딩 2&amp;lt;/h2&amp;gt;
&amp;lt;h3 style=&amp;quot;text-align: right&amp;quot;&amp;gt;헤딩 3&amp;lt;/h3&amp;gt;
&amp;lt;h4 style=&amp;quot;text-align: left&amp;quot;&amp;gt;헤딩 4&amp;lt;/h4&amp;gt;
&amp;lt;h5 style=&amp;quot;text-align: justify&amp;quot;&amp;gt;헤딩 5&amp;lt;/h5&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기서 &lt;code&gt;justify&lt;/code&gt; 속성이 뭔가 싶었는데,&lt;/p&gt;
&lt;p&gt;헤딩 크기에 따라 글자를 정확히 맞춰서 벌린다.&lt;/p&gt;
&lt;p&gt;&amp;quot;123456&amp;quot; 이라는 글자 영역과,&lt;/p&gt;
&lt;p&gt;&amp;quot;1234&amp;quot; 이라는 글자 영역이 동일하다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;hX&amp;gt;&lt;/code&gt; 의 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;h&amp;lt;숫자&amp;gt; {
    display : block;
    margin-left: 0;
    margin-right: 0;
    font-weight: bold;
    // h1 --&amp;gt; h6
    font-size : 2em; 1.5em; 1.17em; 1em; .83em; .67em;
    margin-top : 0.67em; 0.83em; 1em; 1.17em; 1.5em; 2em;
    margin-bottom : 0.67em; 0.83em; 1em; 1.17em; 1.5em; 2em;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;헤딩 자체가 클 수록 차지하는 위아래 비중이 커질 줄 알았는데,&lt;/p&gt;
&lt;p&gt;헤딩 자체의 크기를 고정하기 위해 &lt;code&gt;font-size&lt;/code&gt;, &lt;code&gt;margin-top and bottom&lt;/code&gt; 의 영역이&lt;/p&gt;
&lt;p&gt;서로 반비례하는 것을 볼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;p&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;문단(절)을 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    ...
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;p&amp;gt;문단을 정의할 때 쓰는 태그이다&amp;lt;/p&amp;gt;
    &amp;lt;p&amp;gt;한 번 더 쓰면 다음 줄에서 시작됨~&amp;lt;/p&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;사실상 블로그 글에서 가장 많이 사용되는 태그가 아닌가 싶다.&lt;/p&gt;
&lt;p&gt;브라우저는 자동으로 &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; 전후 Single Blank Line(단일 공백줄) 을 추가한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; 의 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;p {
  display: block;
  margin-top: 1em;
  margin-bottom: 1em;
  margin-left: 0;
  margin-right: 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;br&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;줄바꿈을 의미한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;예를 들어서, 문단 사이의 간격이 좁게 설정되어 있다고 생각한다면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;margin&lt;/code&gt; 을 설정하여 해결 할 수도 있겠지만,&lt;/p&gt;
&lt;p&gt;아예 &lt;code&gt;&amp;lt;br&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;br/&amp;gt;&lt;/code&gt; 을 넣어서 빈 줄을 넣을 수도 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    ...
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;p&amp;gt;1 번째 문단&amp;lt;/p&amp;gt;
    &amp;lt;br /&amp;gt;
    &amp;lt;p&amp;gt;2 번째 문단&amp;lt;/p&amp;gt;

    &amp;lt;p&amp;gt;
      생각보다 긴 문단이 있다면, 내부에 이 태그를 삽입하여 &amp;lt;br /&amp;gt;
      해결 할 수 있습니다. (p 는 작성 한 대로 표현되지 않고,)&amp;lt;br /&amp;gt;
      줄바꿈을 없애기 때문.
    &amp;lt;/p&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;br&amp;gt;&lt;/code&gt; 의 기본 CSS 설정 :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;없음&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;hr&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;컨텐츠의 주제적 변화를 정의한다&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;위에서는 W3Schools 에서 말한거긴 하지만, 이 태그는 여러 방면으로 사용할 수 있다고 생각한다.&lt;/p&gt;
&lt;p&gt;주로, 스타일링 쪽에서 많이 사용되지 않을까 생각된다.&lt;/p&gt;
&lt;p&gt;물론, 나도 마크다운에서 &lt;code&gt;---&lt;/code&gt; 표현을 통해 &lt;code&gt;&amp;lt;hr&amp;gt;&lt;/code&gt; 을 표현하는데,&lt;/p&gt;
&lt;p&gt;이를 사용 할 때는 나도 주제의 변화 시에 사용한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;이번에는 블로그에서 직접 독자들이 볼 수 있도록 여기서 태그를 작성 해 보겠다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;hr style=&amp;quot;width:50%; text-align:left; margin-left:0&amp;quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr style=&quot;width:50%; text-align:left; margin-left:0&quot;&gt;

&lt;br/&gt;

&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;hr style=&amp;quot;height:2px; border-width:0; color:gray; background-color: white&amp;quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr style=&quot;height:5px; border-width:0; color:gray; background-color: blue&quot;&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;hr&amp;gt;&lt;/code&gt; 의 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;hr {
  display: block;
  margin-top: 0.5em;
  margin-bottom: 0.5em;
  margin-left: auto;
  margin-right: auto;
  border-style: inset;
  border-width: 1px;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;br/&gt;

&lt;h2&gt;Category - 형식, 서식에 관한 HTML 태그&lt;/h2&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;abbr&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;약어를 정의한다.&lt;/strong&gt; (약어 : Abbreviation)&lt;/p&gt;
&lt;p&gt;약어는 위에서 말했던 SEO (Search Engine Obtimazation) 과 같은 단어들을 말한다.&lt;/p&gt;
&lt;p&gt;이 태그 위에 마우스를 올려두면, 내부 속성이 설명을 해 준다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;abbr title=&amp;quot;Search Engine Optimazation&amp;quot;&amp;gt;SEO&amp;lt;/abbr&amp;gt; 는 매우 중요하다.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;&lt;abbr title=&quot;Search Engine Optimazation&quot;&gt;SEO&lt;/abbr&gt; 는 매우 중요하다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;abbr&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;abbr {
  display: inline;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;address&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;문서, 아티클에 대한 저자, 소유자의 연락처를 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;address&amp;gt;
  이 글의 저자는 &amp;lt;a href=&amp;quot;mailto:rhdwhdals8@naver.com&amp;quot;&amp;gt;공담형&amp;lt;/a&amp;gt; 입니다. &amp;lt;br /&amp;gt;
  지구, 대한민국에 있습니다.
&amp;lt;/address&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현&lt;/strong&gt; :&lt;/p&gt;
&lt;address&gt;
    이 글의 저자는 &lt;a href=&quot;mailto:rhdwhdals8@naver.com&quot;&gt;공담형&lt;/a&gt; 입니다. &lt;br/&gt;
    지구, 대한민국에 있습니다.
&lt;/address&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;address&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;address {
  display: block;
  font-style: italic;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;b&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;굵은(두꺼운) 텍스트를 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;정말 많이 사용되는 태그라고 생각된다.&lt;/p&gt;
&lt;p&gt;단순 텍스트에서, 특정 단어만을 두껍게 만들어 약간의 하이라이팅 효과를 볼 수 있다.&lt;/p&gt;
&lt;p&gt;그런데, 정식 사양에서는 &lt;code&gt;&amp;lt;em&amp;gt;&lt;/code&gt; 태그를 추천하고,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;b&amp;gt;&lt;/code&gt; 는 정말 최종적으로 사용해야 한다고 말한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;p&amp;gt;안녕하세요, 제 블로그 이름은 &amp;lt;b&amp;gt;코딩크리쳐&amp;lt;/b&amp;gt; 입니다.&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;안녕하세요, 제 블로그 이름은 &lt;b&gt;코딩크리쳐&lt;/b&gt; 입니다.&lt;/p&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;b&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;b {
  font-weight: bold;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;blockquote&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;다른 소스로부터 가져온 인용문 구간을 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;blockquote&amp;gt;
  인용문을 가져 올 때 많이 사용되던 것이기도 한데, &amp;lt;br /&amp;gt;
  나는 개인적으로 마크다운 작성 시, 내 의견을 크게 어필 할 때 사용한다. &amp;lt;br /&amp;gt;
  &amp;lt;blockquote&amp;gt;blockquote 안의 blockquote&amp;lt;/blockquote&amp;gt;
&amp;lt;/blockquote&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현&lt;/strong&gt; :&lt;/p&gt;
&lt;blockquote&gt;
    인용문을 가져 올 때 많이 사용되던 것이기도 한데, &lt;br/&gt;
    나는 개인적으로 마크다운 작성 시, 내 의견을 크게 어필 할 때 사용한다. &lt;br/&gt;
    &lt;blockquote&gt;
        blockquote 안의 blockquote
    &lt;/blockquote&gt;
&lt;/blockquote&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;blockquote&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;blockquote {
  display: block;
  margin-top: 1em;
  margin-bottom: 1em;
  margin-left: 40px;
  margin-right: 40px;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;cite&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;작품의 제목을 정의할 때 사용한다고 한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;cite 라는 단어라서 뭔가 했는데,&lt;/p&gt;
&lt;p&gt;대표적인 그림, 음악, 영화 같은 작품들을 표현 할 때 사용한다고 한다.&lt;/p&gt;
&lt;p&gt;굳이... 싶기도 하지만, 태그 자체로 역할을 구분한다는 점에서 이해하고 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;cite&amp;gt;타이타닉&amp;lt;/cite&amp;gt; 는 정말 유명한 영화이다.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;&lt;cite&gt;타이타닉&lt;/cite&gt; 는 정말 유명한 영화이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;cite&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;cite {
  font-style: italic;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;심지어 폰트도 그냥 &lt;code&gt;&amp;lt;i&amp;gt;&lt;/code&gt; 랑 동일하다..&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;code&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;모든 종류의 컴퓨터 언어 조각을 정의할 수 있다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;내가 문서를 작성 할 때, 가장 많이 사용하는 태그이다.&lt;/p&gt;
&lt;p&gt;아마 개발자라면, 기술 블로그를 작성 할 때, 많이 사용하는 태그가 아닐까 생각된다.&lt;/p&gt;
&lt;p&gt;현재 작성중인 이 문서에도 거의 100개 정도가 들어갔을 것이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;p&amp;gt;디렉토리 생성하기 : &amp;lt;code&amp;gt;$ mkdir newDir&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;파일 생성하기 : &amp;lt;code&amp;gt;$ echo &amp;quot;&amp;quot; &amp;gt; newDir/new-file.md&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;파일 내용 출력하기&amp;lt;code&amp;gt;$ cat newDir/new-file.md&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;디렉토리 생성하기 : &lt;code&gt;$ mkdir newDir&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;파일 생성하기 : &lt;code&gt;$ echo &quot;&quot; &gt; newDir/new-file.md&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;파일 내용 출력하기&lt;code&gt;$ cat newDir/new-file.md&lt;/code&gt;&lt;/p&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;code&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;code {
  font-family: monospace;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;바로 사용하면 코드 조각인지 알아차리기 힘들어서,&lt;/p&gt;
&lt;p&gt;보통 &lt;code&gt;code&lt;/code&gt; 태그에 그림자 효과, 패딩, 백그라운드 색상을 변경하여 만드는 편이다.&lt;/p&gt;
&lt;p&gt;내 블로그도 그러한 방식으로 코드 조각을 커스텀했다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;del&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;문서에서 삭제된 텍스트를 정의합니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;아마, 다양한 도메인의 공식문서에서 애매한 표현을 고치기 위해 사용하는 태그가 아닐까 생각된다.&lt;/p&gt;
&lt;p&gt;중간에 그어지는 선을 바탕으로 텍스트가 그려지는데, 직접 보면 알것이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;NestJS 는 &amp;lt;del&amp;gt;Express&amp;lt;/del&amp;gt; express 모듈을 디폴트로 사용합니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;NestJS 는 &lt;del&gt;Express&lt;/del&gt; express 모듈을 디폴트로 사용합니다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;del&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;del {
  text-decoration: line-through;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;dfn&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;컨텐츠 안에 작성된 글이 어떤 의미를 가지는지 설명할 수 있다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;만약에, 우리가 모르는 용어의 탭이 존재한다면, 해당 탭 위에 마우스를 3초 이상 올려두어&lt;/p&gt;
&lt;p&gt;설명 박스가 나올 때 까지 기다리던 경험이 있을 것이다. &lt;del&gt;아마?&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;dfn title=&amp;quot;Node Package Manager&amp;quot;&amp;gt;NPM&amp;lt;/dfn&amp;gt; 은 프로젝트를 관리하기에 편리한
프로그램이다.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;&lt;dfn title=&quot;Node Package Manager&quot;&gt;NPM&lt;/dfn&gt; 은 프로젝트를 관리하기에 편리한 프로그램이다.&lt;/p&gt;
&lt;p&gt;^ 위의 &lt;strong&gt;NPM&lt;/strong&gt; 글자에 마우스를 올려 보자&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;dfn&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;dfn {
  font-style: italic;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;em&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;특정 텍스트 강조를 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;b&amp;gt;&lt;/code&gt; 태그 대신에 &lt;code&gt;&amp;lt;em&amp;gt;&lt;/code&gt; 태그를 권장하던데, 폰트가 두껍냐, 이탈릭체이냐 차이이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;em&amp;gt;html 태그&amp;lt;/em&amp;gt; 들이 생각보다 정말 많다...&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;&lt;em&gt;html 태그&lt;/em&gt; 들이 생각보다 정말 많다...&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;em&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;em {
  font-style: italic;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;i&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;기존 텍스트와 다른 분위기를 내고 싶을 때 사용한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;주로 기술적 용어, 다른 언어, 나의 생각을 작성 할 때 &lt;code&gt;&amp;lt;i&amp;gt;&lt;/code&gt; 이탈릭체를 사용한다고 한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;내 생각은, &amp;lt;i&amp;gt;태그만으로 아티클 내부의 역할을 이해할 수 있다&amp;lt;/i&amp;gt; 라는 것이다.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;내 생각은, &lt;i&gt;태그만으로 아티클 내부의 역할을 이해할 수 있다&lt;/i&gt; 라는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;i&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;i {
  font-style: italic;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;ins&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;del 과는 반대로, 새로 삽입된 텍스트 문구를 의미한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;요약하자면, 글씨 밑에 밑줄이 존재하는 텍스트를 의미한다.&lt;/p&gt;
&lt;p&gt;왜 &lt;code&gt;ins&lt;/code&gt; 인지 몰랐는데, &lt;strong&gt;Insert&lt;/strong&gt; 의 앞 3글자만 따 왔다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;NestJS 는 &amp;lt;del&amp;gt;Express&amp;lt;/del&amp;gt; express 모듈을 사용합니다. &amp;lt;br/&amp;gt;

&amp;lt;ins&amp;gt;뿐만 아니라, fastify 모듈을 사용하여 몇 배는 빠르게 통신 할 수 있습니다. &amp;lt;/br&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;NestJS 는 &lt;del&gt;Express&lt;/del&gt; express 모듈을 사용합니다. &lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;ins&gt;뿐만 아니라, fastify 모듈을 사용하여 몇 배는 빠르게 통신 할 수 있습니다. &lt;/br&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;ins&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;ins {
  text-decoration: underline;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;kbd&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;키보드의 입력을 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이 태그는 &lt;code&gt;&amp;lt;code&amp;gt;&lt;/code&gt; 와 동일한 기본 스타일 속성을 지닌다.&lt;/p&gt;
&lt;p&gt;나는 글을 작성 하면서 키보드의 입력을 나타 낼 때, &lt;code&gt;&amp;lt;code&amp;gt;&lt;/code&gt; 태그로 나타냈는데,&lt;/p&gt;
&lt;p&gt;키보드의 입력을 태그로서 구분하고 싶다면, &lt;code&gt;&amp;lt;kbd&amp;gt;&lt;/code&gt; 를 사용하면 될 것 같다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;윈도우에서 복사하려면, &amp;lt;kbd&amp;gt;Ctrl&amp;lt;/kbd&amp;gt; + &amp;lt;kbd&amp;gt;c&amp;lt;/kbd&amp;gt; 를 누르면 되고, &amp;lt;br /&amp;gt;

맥에서 복사하려면, &amp;lt;kbd&amp;gt;Cmd&amp;lt;/kbd&amp;gt; + &amp;lt;kbd&amp;gt;c&amp;lt;/kbd&amp;gt; 를 누르면 된다.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;윈도우에서 복사하려면, &lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;c&lt;/kbd&gt; 를 누르면 되고, &lt;br/&gt;&lt;/p&gt;
&lt;p&gt;맥에서 복사하려면, &lt;kbd&gt;Cmd&lt;/kbd&gt; + &lt;kbd&gt;c&lt;/kbd&gt; 를 누르면 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;kbd&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;kbd {
  font-family: monospace;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;mark&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;특정 텍스트를 강조하거나 하이라이팅 할 때 사용한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이번엔 조금 특수하다고 생각하는데,&lt;/p&gt;
&lt;p&gt;기본 스타일링에서 배경 색상이 노란색이라는 것이다.&lt;/p&gt;
&lt;p&gt;또한, 글자 색상도 &amp;quot;검정&amp;quot; 으로 선언되어 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;오늘은 내가, &amp;lt;mark&amp;gt;짜파게티&amp;lt;/mark&amp;gt; 요리~사~&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;오늘은 내가, &lt;mark&gt;짜파게티&lt;/mark&gt; 요리&lt;del&gt;사&lt;/del&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;mark&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;mark {
  background-color: yellow;
  color: black;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;meter&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;알려진 범위 내에서 스칼라 측정을 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이게 무슨 말인가 했는데, 실제 구현을 보면 바로 이해가 된다.&lt;/p&gt;
&lt;p&gt;일단, 정적으로 나타내 진 게이지 바 라고 생각한다.&lt;/p&gt;
&lt;p&gt;나중에 설명할 &lt;code&gt;&amp;lt;progress&amp;gt;&lt;/code&gt; 와 비슷하지만, 더 디테일하다.&lt;/p&gt;
&lt;p&gt;이번에는 W3Schools 가 나보다 더 잘 표현 할 것 같아서 가져온다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;label for=&amp;quot;disk_c&amp;quot;&amp;gt;Disk usage C:&amp;lt;/label&amp;gt;
&amp;lt;meter id=&amp;quot;disk_c&amp;quot; value=&amp;quot;2&amp;quot; min=&amp;quot;0&amp;quot; max=&amp;quot;10&amp;quot;&amp;gt;2 out of 10&amp;lt;/meter&amp;gt;&amp;lt;br /&amp;gt;

&amp;lt;label for=&amp;quot;disk_d&amp;quot;&amp;gt;Disk usage D:&amp;lt;/label&amp;gt;
&amp;lt;meter id=&amp;quot;disk_d&amp;quot; value=&amp;quot;0.6&amp;quot;&amp;gt;60%&amp;lt;/meter&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;&lt;label for=&quot;disk_c&quot;&gt;Disk usage C:&lt;/label&gt;&lt;br&gt;&lt;meter id=&quot;disk_c&quot; value=&quot;2&quot; min=&quot;0&quot; max=&quot;10&quot;&gt;2 out of 10&lt;/meter&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;&lt;label for=&quot;disk_d&quot;&gt;Disk usage D:&lt;/label&gt;&lt;br&gt;&lt;meter id=&quot;disk_d&quot; value=&quot;0.6&quot;&gt;60%&lt;/meter&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;meter&amp;gt;&lt;/code&gt; 에 들어가는 특수 속성 설명 :&lt;/p&gt;
&lt;table&gt;
    &lt;thead&gt;
        &lt;tr&gt;
        &lt;td&gt;
            &lt;b&gt;속성 이름&lt;/b&gt;
        &lt;/td&gt;
        &lt;td&gt;
            &lt;b&gt;속성 값&lt;/b&gt;
        &lt;/td&gt;
        &lt;td&gt;
            &lt;b&gt;설명&lt;/b&gt;
        &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
        &lt;tr&gt;
            &lt;td&gt;form&lt;/td&gt;
            &lt;td&gt;form_id&lt;/td&gt;
            &lt;td&gt;&lt;code&gt;meter&lt;/code&gt; 가 속하는 &lt;code&gt;form&lt;/code&gt; 을 지정한다.&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;high&lt;/td&gt;
            &lt;td&gt;number&lt;/td&gt;
            &lt;td&gt;높다고 생각되는 값을 지정한다.&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;low&lt;/td&gt;
            &lt;td&gt;number&lt;/td&gt;
            &lt;td&gt;낮다고 생각되는 값을 지정한다.&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;max&lt;/td&gt;
            &lt;td&gt;number&lt;/td&gt;
            &lt;td&gt;될 수 있는 최대 값을 지정한다.&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;min&lt;/td&gt;
            &lt;td&gt;number&lt;/td&gt;
            &lt;td&gt;될 수 있는 최소 값을 지정한다.&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;optimum&lt;/td&gt;
            &lt;td&gt;number&lt;/td&gt;
            &lt;td&gt;이 게이지 바에서 최적의 값을 지정한다.&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;value&lt;/td&gt;
            &lt;td&gt;number&lt;/td&gt;
            &lt;td&gt;필수 속성이며, 게이지에서 현재 값을 지정한다.&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tbody&gt;
&lt;/table&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;meter&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;없다&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;pre&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;이미 지정된 형식의 텍스트를 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;우리가 &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; 태그를 이용하여 텍스트를 내부에 작성하며 엔터를 쳐도,&lt;/p&gt;
&lt;p&gt;실제로 엔터가 반영되지는 않는다.&lt;/p&gt;
&lt;p&gt;그러나, &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; 태그는 정확히 엔터 값 부터, 얼마나 떨어져있는지를 반영하여 보여준다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;pre&amp;gt;
    여기에 작성되는 글을
    정말로 그대로 반영되어 페이지에 나타난다.
    나는 지금 br 태그를 작성하지 않고 글을 넘기는 중이다.
&amp;lt;/pre&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;
    여기에 작성되는 글을
    정말로 그대로 반영되어 페이지에 나타난다.
    나는 지금 br 태그를 작성하지 않고 글을 넘기는 중이다.
&lt;/pre&gt;

&lt;p&gt;위에서 코드 블럭으로 나타날 수도 있는데,&lt;/p&gt;
&lt;p&gt;이는 현재 내 블로그에 적용된 &lt;code&gt;highlights.js&lt;/code&gt; 때문으로,&lt;/p&gt;
&lt;p&gt;마크다운에서 작성된 코드블럭들은 &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; 로 작성되기 때문에, 이를 파싱하는 과정에서&lt;/p&gt;
&lt;p&gt;위의 예시도 코드 블럭으로 나타 날 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;pre {
  display: block;
  font-family: monospace;
  white-space: pre;
  margin: 1em 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;progress&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;작업의 상황을 표현한다.&lt;/strong&gt; - &lt;code&gt;&amp;lt;meter&amp;gt;&lt;/code&gt; 와 거의 동일하지만, 속성이 더 부족함.&lt;/p&gt;
&lt;p&gt;라벨과 함께 이런 식으로 표현 될 수 있다고 한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;label for=&amp;quot;file&amp;quot;&amp;gt;파일 다운로드중 :&amp;lt;/label&amp;gt;
&amp;lt;progress id=&amp;quot;file&amp;quot; value=&amp;quot;32&amp;quot; max=&amp;quot;100&amp;quot;&amp;gt;32%&amp;lt;/progress&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;&lt;label for=&quot;file&quot;&gt;파일 다운로드중 :&lt;/label&gt;&lt;br&gt;&lt;progress id=&quot;file&quot; value=&quot;32&quot; max=&quot;100&quot;&gt; 32% &lt;/progress&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;progress&amp;gt;&lt;/code&gt; 의 특수 속성 :&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;속성&lt;/th&gt;
&lt;th&gt;값&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;max&lt;/td&gt;
&lt;td&gt;number&lt;/td&gt;
&lt;td&gt;전체 작업의 총량을 지정한다. 기본 값은 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;value&lt;/td&gt;
&lt;td&gt;number&lt;/td&gt;
&lt;td&gt;얼마나 많은 작업이 완료되었는지 지정한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;progress&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;없음&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;JS 에서 &lt;code&gt;&amp;lt;progress&amp;gt;&lt;/code&gt; 태그와 상호작용하는 라이브러리가 있는 모양이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;q&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;짧은 인용문을 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;위에서 &lt;code&gt;&amp;lt;blockquote&amp;gt;&lt;/code&gt; 를 다뤘는데, 이번에는 블록이 아니라, 인라인 버전이다.&lt;/p&gt;
&lt;p&gt;기본 스타일링으로 인해, 맨 앞과 뒤에 간단히 &lt;code&gt;&amp;quot;&lt;/code&gt; 쌍따옴표가 붙는다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;짧게 인용하기 위해서는, &amp;lt;q&amp;gt;이렇게 인용할 수 있습니다.&amp;lt;/q&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;짧게 인용하기 위해서는, &lt;q&gt;이렇게 인용할 수 있습니다.&lt;/q&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;q&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;q {
  display: inline;
}

q:before {
  content: open-quote;
}

q:after {
  content: close-quote;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;s&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;더 이상 맞지 옳지 않은 텍스트를 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;즉, 중앙에 가로선이 하나 그어지며, &lt;code&gt;&amp;lt;del&amp;gt;&lt;/code&gt; 처럼 된다.&lt;/p&gt;
&lt;p&gt;정식 스펙에서는 &amp;quot;삭제될 텍스트&amp;quot; 일 경우에는 &lt;code&gt;&amp;lt;del&amp;gt;&lt;/code&gt; 을 사용하고, 나머지는 &lt;code&gt;&amp;lt;s&amp;gt;&lt;/code&gt; 를 사용하라고 한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;s&amp;gt;지금 10개 남음!&amp;lt;/s&amp;gt;

판매 완료&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;&lt;s&gt;지금 10개 남음!&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;판매 완료&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;s&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;s {
  text-decoration: line-through;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;samp&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;컴퓨터 프로그램의 출력물 샘플&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;사실상 &lt;code&gt;&amp;lt;code&amp;gt;&lt;/code&gt; 태그와 동일하다.&lt;/p&gt;
&lt;p&gt;물론, HTML 태그로 구분하여 출력물에 대한 스타일링을 따로 진행 할 수 있겠다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;code&amp;gt;char* str = &amp;quot;Hello&amp;quot;;&amp;lt;/code&amp;gt;

&amp;lt;code&amp;gt;printf(&amp;quot;%d&amp;quot;, sizeof(*str));&amp;lt;/code&amp;gt;

결과 : &amp;lt;samp&amp;gt;1&amp;lt;/samp&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;&lt;code&gt;char* str = &quot;Hello&quot;;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;printf(&quot;%d&quot;, sizeof(*str));&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;결과 : &lt;samp&gt;1&lt;/samp&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;samp&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;samp {
  font-family: monospace;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;small&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;일반 텍스트보다 더 작은 텍스트를 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;b&amp;gt;&lt;/code&gt; 과는 반대의 개념으로, &lt;code&gt;font-size&lt;/code&gt; 가 &lt;code&gt;smaller&lt;/code&gt; 이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;일반 텍스트는 이렇게 생겼습니다. 작은 텍스트는 &amp;lt;small&amp;gt;이렇습니다.&amp;lt;/small&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;일반 텍스트는 이렇게 생겼습니다.&lt;/p&gt;
&lt;p&gt;작은 텍스트는 &lt;small&gt;이렇습니다.&lt;/small&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;small&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;small {
  font-size: smaller;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;strong&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;중요한 텍스트를 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;스타일링 자체는 &lt;code&gt;&amp;lt;b&amp;gt;&lt;/code&gt; 와 동일하다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;strong&amp;gt;C 포인터&amp;lt;/strong&amp;gt; 는 메모리 안정성을 배우는데 중요하다.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;C 포인터&lt;/strong&gt; 는 메모리 안정성을 배우는데 중요하다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;strong&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;strong {
  font-weight: bold;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;sub&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;아래 첨자 텍스트를 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이게 무슨 말이냐면, 일반 줄보다 반 문자 정도 아래에 나타난다.&lt;/p&gt;
&lt;p&gt;따라서, 수학 기호를 작성하기엔 좋다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;Tree 자료구조는 검색을 매우 빠르게 해 주는 중요한 요소인데, 특정 요소 검색 시,
O( log&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; N ) 시간 복잡도를 보장한다.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;Tree 자료구조는 검색을 매우 빠르게 해 주는 중요한 요소인데,&lt;/p&gt;
&lt;p&gt;특정 요소 검색 시, O( log&lt;sub&gt;2&lt;/sub&gt; N ) 시간 복잡도를 보장한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;sub&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;sub {
  vertical-align: sub;
  font-size: smaller;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;sup&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;위 첨자 텍스트를 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;바로 위에서 &amp;quot;아래 첨자&amp;quot; 에 대해서 설명 한 것 처럼,&lt;/p&gt;
&lt;p&gt;&amp;quot;위 첨자&amp;quot; 는 기준선보다 반 문자 위에 나타난다.&lt;/p&gt;
&lt;p&gt;이 태그도 수학 기호를 작성하기에 좋다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;컴퓨터 프로그래밍에서 Bit 는 굉장히 중요한 개념이다. 하나의 주소에 4 Bit 타입이
할당되어 있다면, 이 타입은 최대 2&amp;lt;sup&amp;gt;4&amp;lt;/sup&amp;gt; == 16 개의 상태를 표현 할 수 있다.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;컴퓨터 프로그래밍에서 Bit 는 굉장히 중요한 개념이다.&lt;/p&gt;
&lt;p&gt;하나의 주소에 4 Bit 타입이 할당되어 있다면,&lt;/p&gt;
&lt;p&gt;이 타입은 최대 2&lt;sup&gt;4&lt;/sup&gt; == 16 개의 상태를 표현 할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;sup&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;sup {
  vertical-align: super;
  font-size: smaller;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;template&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;이 HTML 페이지가 로드 될 때, 감춰져야 되는 컨텐츠를 위한 컨테이너를 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;나도 처음에 이 정의를 보았을 때, 들었던 생각이&lt;/p&gt;
&lt;p&gt;그렇다면, 처음에 페이지가 렌더링 된 후, 개발자 도구에 들어가면 보이지 않나? 였다.&lt;/p&gt;
&lt;p&gt;그런데, 그것이 아니고, 클라이언트의 HTML 페이지 자체에는 &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; 를 볼 수 있다.&lt;/p&gt;
&lt;p&gt;하지만, 만약에 웹 페이지 제작자가, 특정 이유로 &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; 안에 들어가는 요소들을&lt;/p&gt;
&lt;p&gt;먼저 페이지에 보여주지 않고, 나중에 JavaScript 로 보여주고 싶다면, 그 때 사용할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;단점이 있는데, 결국 HTML 페이지에 Tag 로서 넣어준다는 것 자체가 클라이언트가 직접 콘솔에서&lt;/p&gt;
&lt;p&gt;접근 할 수 있다는 이야기이다.&lt;/p&gt;
&lt;p&gt;따라서, 이러한 일을 원천 차단하고 싶다면, 아예 JS 로 DOM 을 만들어 넣어주는 것이 좋다고 한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;Testing Template tag&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;template&amp;gt;
      &amp;lt;h2&amp;gt;템플릿 문자열이 나타났다!&amp;lt;/h2&amp;gt;
    &amp;lt;/template&amp;gt;

    &amp;lt;h1&amp;gt;Testing&amp;lt;/h1&amp;gt;

    &amp;lt;button onclick=&amp;quot;showTemplate()&amp;quot;&amp;gt;show Template&amp;lt;/button&amp;gt;

    &amp;lt;div id=&amp;quot;container&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;

    &amp;lt;script&amp;gt;
      function showTemplate() {
        // 맨 밑에 작성 해 놓은 DOM - div 를 의미
        const container = document.getElementById(&amp;quot;container&amp;quot;);

        // 우리가 이 문서에 작성한 template 태그는 하나밖에 없고, 1 번째이다.
        const innerTemp = document.getElementsByTagName(&amp;quot;template&amp;quot;)[0];

        // template DOM 의 모든 컨텐츠를 가져온다.
        const tempDOMs = innerTemp.content.cloneNode(true);

        container.appendChild(tempDOMs);
      }
    &amp;lt;/script&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;이는 직접 &lt;code&gt;html&lt;/code&gt; 파일을 만들어서, 위의 코드를 붙여넣은 뒤,&lt;/p&gt;
&lt;p&gt;직접 버튼을 클릭하여 실행 해 보길 권장한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;p&gt;애초에 CSS 설정이 가능한 태그가 아님.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;time&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;특정 시간 (혹은 datetime) 을 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;우리가 사용하는 &lt;code&gt;&amp;lt;time&amp;gt;&lt;/code&gt; 태그는 어떠한 스타일링을 주지 않는다.&lt;/p&gt;
&lt;p&gt;그러나, 이 태그의 의미는 2 가지가 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;태그를 통해 이 텍스트가 &amp;quot;날짜&amp;quot; 라는 차별점을 줄 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;datetime&lt;/code&gt; 속성은 기계가 읽을 수 있어, SEO 에 차별점을 둘 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;내가 주로 가는 카페는 &amp;lt;time&amp;gt;10:00&amp;lt;/time&amp;gt; 에 열고, &amp;lt;time&amp;gt;02:00&amp;lt;/time&amp;gt; 에 닫는다.

이 글을 작성하는 현재 날짜는 거의 &amp;lt;time datetime=&amp;quot;2025-05-24 02:52&amp;quot;&amp;gt;5월 말, 새벽&amp;lt;/time&amp;gt; 이다.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;내가 주로 가는 카페는 &lt;time&gt;10:00&lt;/time&gt; 에 열고, &lt;time&gt;02:00&lt;/time&gt; 에 닫는다.&lt;/p&gt;
&lt;p&gt;이 글을 작성하는 현재 날짜는 거의 &lt;time datetime=&quot;2025-05-24 02:52&quot;&gt;5월 말, 새벽&lt;/time&gt; 이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;time&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;없음&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;u&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;일반 텍스트와는 다르게 스타일링 된 일부 텍스트를 정의합니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이 또한 굉장히 난해한 정의라고 생각하는데, 실제 구현 및 결과물을 보면 이해가 되기도 한다.&lt;/p&gt;
&lt;p&gt;기본 스타일은 &lt;code&gt;underline&lt;/code&gt; 으로, 굉장히 평범하지만,&lt;/p&gt;
&lt;p&gt;이 태그는 조금의 스타일링을 거치면 꽤 괜찮은 스타일링이 된다.&lt;/p&gt;
&lt;p&gt;구현을 보자.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
        &amp;lt;title&amp;gt;
            unarticled 텍스트 테스팅
        &amp;lt;/title&amp;gt;
        &amp;lt;style&amp;gt;
            .spelling-warn {
                text-decoration-line : underline;
                text-decoration-style : wavy;
                text-decoration-color : yellow;
            }
        &amp;lt;/style&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;p&amp;gt; 스펠링이 틀린 문자는 &amp;lt;u class=&amp;quot;spelling-warn&amp;quot;&amp;gt;dattime&amp;lt;/u&amp;gt; 처럼 처리 할 수 있다.
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt; 스펠링이 틀린 문자는
&lt;u
    class=&quot;spelling-warn&quot;
    style=&quot;text-decoration-line : underline; text-decoration-style : wavy; text-decoration-color : yellow;&quot;
&gt;
    dattime
&lt;/u&gt; 처럼 처리 할 수 있다.

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;u&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;u {
    text-decoration : underline;
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;var&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;변수를 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;기본 스타일은 단순하게 &lt;code&gt;italic&lt;/code&gt; 처리가 되어 있다.&lt;/p&gt;
&lt;p&gt;이 또한, 태그 자체로서 사용처를 구분한다는 목적이 있다.&lt;/p&gt;
&lt;p&gt;주로, 수학 표현식에서나, 프로그래밍 변수를 정의하는 데 사용한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;p&amp;gt; &amp;lt;var&amp;gt;1 + 2&amp;lt;/var&amp;gt; 는, 3 입니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt; &lt;var&gt;1 + 2&lt;/var&gt; 는, 3 입니다.

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;var&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;var {
    font-style : italic;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;단순하게 태그만 사용하는 것 보다는, 태그 자체를 스타일링하여 사용하는 데 의미가 있다고 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;wbr&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;줄바꿈 가능성을 정의한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;일단, 위의 &lt;code&gt;w&lt;/code&gt; 는 일단 단어인 &lt;strong&gt;word&lt;/strong&gt; 를 의미한다.&lt;/p&gt;
&lt;p&gt;여기서 왜, 단어에 &lt;code&gt;br&lt;/code&gt; 이 들어갔냐 하면,&lt;/p&gt;
&lt;p&gt;현재 텍스트 컨텐츠가 들어가는 장소에, 아주아주아주 긴 단어가 들어갔다고 가정한다.&lt;/p&gt;
&lt;p&gt;그런데, 그냥 작성하면, 아주 긴 단어가 중간에 잘리고, 그 다음 줄에서 이어 나간다.&lt;/p&gt;
&lt;p&gt;그러나, &lt;code&gt;&amp;lt;wbr&amp;gt;&lt;/code&gt; 을 사용한다면, 이 단어가 만약에 중간에 잘려야 한다면,&lt;/p&gt;
&lt;p&gt;그냥 이 단어 자체를 다음 줄로 넘겨 완전하게 보이도록 만들라는 것이다.&lt;/p&gt;
&lt;p&gt;따라서, 이는 단어 자체의 &amp;quot;줄 바꿈 가능성을 정의&amp;quot; 하는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;p&amp;gt;해당 태그 없이 아주아주 긴 단어 작성 해 보기 : ahflkwhri3ehsjkldhlkauibwfuhssdfjniuwhfuvbwjnfviusdrnkwejkhsdirhcjhvuwbdjvbxjdfhjafhuekvbjshdv&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;wbr 태그와 함께 아주아주 긴 단어 작성 해 보기 : &amp;lt;wbr&amp;gt;ahflkwhri3ehsjkldhlkauibwfuhssdfjniuwhfuvbwjnfviusdrnkwejkhsdirhcjhvuwbdjvbxjdfhjafhuekvbjshdv&amp;lt;/wbr&amp;gt;&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실제 표현&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;해당 태그 없이 아주아주 긴 단어 작성 해 보기 : ahflkwhri3ehsjkldhlkauibwfuhssdfjniuwhfuvbwjnfviusdrnkwejkhsdirhcjhvuwbdjvbxjdfhjafhuekvbjshdv&lt;/p&gt;

&lt;p&gt;wbr 태그와 함께 아주아주 긴 단어 작성 해 보기 : ahflkwhri3ehsjkldhlkaui&lt;wbr&gt;bwfuhssdfjniuwhfuvbwjnfviusdrnkwejkhsdirhcjhvuwbdjvbxjdfhjafhuekvbjshdv&lt;/wbr&gt;&lt;/p&gt;

&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;wbr&amp;gt;&lt;/code&gt; 기본 CSS 설정 :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;없음&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;이번 글을 작성하며 배운 것.&lt;/h2&gt;
&lt;p&gt;HTML 파일만을 활용하여 열심히 페이지를 꾸미던 옛날, &lt;del&gt;거의 2000년대쯤?&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;JS 라이브러리와 개발 환경이 미숙하던 그 당시. 파일 내부에서 구역과 사용처를 나누기 위해&lt;/p&gt;
&lt;p&gt;얼마나 열심히 개발했을지 상상이 안 될 정도로 HTML Tag 들이 많다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이전에 React 로 개발 할 때, 대부분의 태그들을 &lt;code&gt;div&lt;/code&gt; 로 작성했었다.&lt;/p&gt;
&lt;p&gt;물론, 이 방식 또한 옳기도 한데, 나는 스타일링과 기능 자체를 클래스명으로 구분했었기 때문이다.&lt;/p&gt;
&lt;p&gt;그러나, 같은 기능을 가지지만, 내가 작성하고자 하는 텍스트에 조금 더 알맞는 태그를 사용하여&lt;/p&gt;
&lt;p&gt;이를 동료 개발자들과 공유하면서, 목적을 확실히 할 수 있겠다는 생각을 했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;특히, &lt;code&gt;progress&lt;/code&gt;, &lt;code&gt;meter&lt;/code&gt; 라는 진행 상황 혹은 게이지를 알려주는 특수 formatter 를 알게 되었고,,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;template&lt;/code&gt; 블럭은 옛날 개발자들이,&lt;/p&gt;
&lt;p&gt;&amp;quot;처음에 렌더링 하지 말고, 동적 DOM 상호작용을 위한 태그를 만들어보자&amp;quot; 라는 개념으로&lt;/p&gt;
&lt;p&gt;개발 한 것 같다는 생각이 들었다.&lt;/p&gt;
&lt;p&gt;요즘은 JS 번들러와 개발 환경이 좋아서 사용하지는 않겠지만..&lt;/p&gt;
&lt;p&gt;이러한 태그가 무엇이 부족하여 나오게 되었는지 알게 되는 것 만으로 신선한 공부를 했다고 말할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;h2&gt;참고 사이트&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;W3Schools HTML Element Reference&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.w3schools.com/TAGS/ref_byfunc.asp&quot;&gt;https://www.w3schools.com/TAGS/ref_byfunc.asp&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;MDN HTML 시작하기&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://developer.mozilla.org/ko/docs/Learn_web_development/Core/Structuring_content/Basic_HTML_syntax&quot;&gt;https://developer.mozilla.org/ko/docs/Learn_web_development/Core/Structuring_content/Basic_HTML_syntax&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;
</description>
      <category>Web-Server/웹 지식</category>
      <category>Block</category>
      <category>div</category>
      <category>DOCTYPE</category>
      <category>H1</category>
      <category>Head</category>
      <category>html</category>
      <category>html 태그</category>
      <category>inline</category>
      <category>p</category>
      <category>title</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/217</guid>
      <comments>https://codecreature.tistory.com/217#entry217comment</comments>
      <pubDate>Sat, 24 May 2025 05:27:26 +0900</pubDate>
    </item>
    <item>
      <title>프로세스와 스레드 (부제 : 정확한 의미를 알고, 명령어로 검색 해 보자)</title>
      <link>https://codecreature.tistory.com/216</link>
      <description>&lt;h2&gt;제목 : 프로세스와 스레드&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;위의 주제로 글을 작성하게 된 이유&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;프로세스와 스레드는 우리가 사용하는 프로그래밍 언어마다 각각 프로세스와 스레드를 사용하는 방식이 다르다.&lt;/p&gt;
&lt;p&gt;심지어는 이 두 개의 리소스를 어떤 인프라 환경에서 사용하게 되냐에 따라서도 달라지게 되는데,&lt;/p&gt;
&lt;p&gt;요즘은 Docker 와 k8s(kubernetes) 라는 편리한 컨테이너, 네임스페이스 관리 프로그램이 존재하여&lt;/p&gt;
&lt;p&gt;우리가 크게 프로세스와 스레드에 대해 신경 쓸 필요는 없었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;나는 C 언어를 대학교에서 배우고, Java 를 배우게 되었으며, 그 이후 Swift(찍먹),&lt;/p&gt;
&lt;p&gt;JavaScript, TypeScript (JS 수퍼셋) 을 배우게 되었다.&lt;/p&gt;
&lt;p&gt;다양한 언어를 접했지만, 이 프로그래밍 언어들이 기기의 환경에 알맞게 실행되게 컴파일 된다는 것 까지만 알았고,&lt;/p&gt;
&lt;p&gt;GC (Garbage Collection), JVM(Java Virtual Machine) 과 같은 어려운 요소는 배우려 하지 않았었다.&lt;/p&gt;
&lt;p&gt;그러나, 나는 프로그래머스 풀스택 부트캠프에서 JavaScript, TypeScript 를 다루게 되었는데,&lt;/p&gt;
&lt;p&gt;이 과정에서 백엔드로서의 JS, TS 를 깊게 배우고 조사했다.&lt;/p&gt;
&lt;p&gt;이 때 정말 신경쓰이는 요소가 있었는데, 컴파일 결과 JS 로 실행되는 NestJS 가 Spring 급의&lt;/p&gt;
&lt;p&gt;퍼포먼스를 낼 수 있는가에 대한 집착이었다.&lt;/p&gt;
&lt;p&gt;JavaScript 는 인터프리터 언어이기 때문에, 런타임 환경에서 컴파일 된다.&lt;/p&gt;
&lt;p&gt;시간이 지나며 JavaScript 의 인기와 커뮤니티에 알맞는 최적화를 가지게 되었지만,&lt;/p&gt;
&lt;p&gt;결국 이미 바이너리 수준으로 컴파일 된 Java 의 JAR 파일을 이기기에는 분명한 한계를 가진다고 생각했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그럼에도 Node 환경에서의 백엔드 서비스가 나쁘지 않은 이유는, 결국 프로그램 실행 환경이 &amp;quot;비동기적&amp;quot;&lt;/p&gt;
&lt;p&gt;이기 때문이었다.&lt;/p&gt;
&lt;p&gt;요약하자면, Spring 은 비즈니스 로직을 처리하기 위해 스레드가 할당되고, 이 스레드들은 메인 로직이 가진&lt;/p&gt;
&lt;p&gt;자원을 서로 공유하며 처리한다. 즉, 각각의 스레드는 동기적으로 작업된다.&lt;/p&gt;
&lt;p&gt;그러나, Node 환경은 기본적으로 스레드가 1 개이다. 정확히 말하자면, &amp;quot;코드 실행을 기억하기 위한 콜스택이 1개다&amp;quot; 라고 하겠다.&lt;/p&gt;
&lt;p&gt;(그 외의 몇 개의 스레드들은 JS 를 파싱하고 AST 로 만들거나, 바이트코드로 해석하기 위한 스레드)&lt;/p&gt;
&lt;p&gt;Node 는 단일 스레드 환경에서 사용자의 비즈니스 로직을 처리한다. 이렇게 되면 당연히 Java 환경보다 느리겠지만,&lt;/p&gt;
&lt;p&gt;단일 스레드 환경에서 동시다발적인 요청(이벤트) 를 처리하기 위해 &amp;quot;비동기&amp;quot; 환경을 도입한다는 것이다.&lt;/p&gt;
&lt;p&gt;이 덕분에, 어떠한 요청도 막히지 않고 일단 받아들여주는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;비동기 덕분에, 모든 요청이 동시에 처리 될 수는 있었지만, 여전히 단일 스레드라는 점이 성능에 발목을 잡힌다.&lt;/p&gt;
&lt;p&gt;따라서, 내가 조사했던 것은 &lt;code&gt;Worker&lt;/code&gt; 라는 라이브러리였는데, 이는 서브 스레드를 만들어서&lt;/p&gt;
&lt;p&gt;계산 로직을 분리 해 줄 수 있게 해 주었다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;작성했던 글&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/201&quot;&gt;https://codecreature.tistory.com/201&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그런데, 문제가 하나 있었다.&lt;/p&gt;
&lt;p&gt;싱글 스레드 비동기 이벤트 Driven I/O 방식은 공유 자원이 없어&lt;/p&gt;
&lt;p&gt;다른 스레드가 특정 리소스를 놓아줄 때 까지 기다릴 필요가 없다는 장점이 있다.&lt;/p&gt;
&lt;p&gt;여기서 질문을 해 보자면, 그렇다면, 이 환경에서 새로운 스레드를 생성한다는 것을 무엇을 의미할까?&lt;/p&gt;
&lt;p&gt;그렇다. 새로운 스레드의 실행을 위한 격리된 환경을 새로 만들어 주어야 한다는 것이다.&lt;/p&gt;
&lt;p&gt;그래도 이전과는 나아진 것이,&lt;/p&gt;
&lt;p&gt;만약에 WAS 를 제작 할 때, 계산이 많이 소비되는 로직이 있다면, 메인 스레드가 아니라,&lt;/p&gt;
&lt;p&gt;Worker 스레드가 이를 분담하여 실행하고, 결과 값을 메인 스레드에 전달해 줄 수 있다는 점이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, 나는 한 발자국 더 나아가기로 결정했다.&lt;/p&gt;
&lt;p&gt;Node 와 브라우저의 JS 실행 환경에서는 웹 어셈블리 모듈을 붙일 수 있다.&lt;/p&gt;
&lt;p&gt;이게 무슨 말이냐면, 새로 작성한 스레드가 JavaScript 로서 작동하는 것이 아니라,&lt;/p&gt;
&lt;p&gt;아예 어셈블리 수준에서 작동한다는 것이었다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;이에 대해 작성한 글은,&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/202&quot;&gt;https://codecreature.tistory.com/202&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;나는 웹 어셈블리 모듈 (&lt;code&gt;.wasm&lt;/code&gt;) 을 만들기 위해서, AssemblyScript 라는 언어를 사용했다.&lt;/p&gt;
&lt;p&gt;그런데, 오히려 C 언어로 만드는 것 보다 더 어렵다는 생각을 했다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;AssemblyScript 라는 언어는 TypeScript 랑 비슷한 형태를 한다.&lt;/li&gt;
&lt;li&gt;그러나, TypeScript 와는 달리 새로운 타입을 사용해야 한다. (Ex - &lt;code&gt;i32&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;AssemblyScript 라는 언어를 사용하기 위해, 또 다른 복잡한 프로그램을 설치해야 한다.&lt;/li&gt;
&lt;li&gt;Node 프로젝트에 AssemblyScript 를 적용하기 위해 Node 프로젝트 설정값을 복잡하게 설정해야 한다.&lt;/li&gt;
&lt;li&gt;AssemblyScript 는 영향력 있는 언어가 아니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;아마 처음 배우는 과정에서 설정하는 것이 매우 어렵기도 했지만,&lt;/p&gt;
&lt;p&gt;이렇게 Node 환경을 최적화 하기 위해 노력 할 것이면,&lt;/p&gt;
&lt;p&gt;이 노력으로 차라리 C 혹은 Rust, Zig 를 배우는 것이 낫지 않나? 라는 생각이 정말 많이 들었다.&lt;/p&gt;
&lt;p&gt;그러나, JavaScript, TypeScript 가 가진 영향력과 웹에서의 개발 환경, 브라우저의 주요 언어를 생각 해 본다면,&lt;/p&gt;
&lt;p&gt;애초에 JS 실행 환경에서 WASM 을 도입하는 것 자체가 희귀한 일이 아닐까 생각된다.&lt;/p&gt;
&lt;p&gt;(그래픽 렌더링, 코드 편집기, 코인 거래소 사이트 등등 제외)&lt;/p&gt;
&lt;p&gt;그러나, 나는 언어 자체의 한계점과 이를 돌파하기 위한 노력으로 여러 방면을 알게 되었다는 점에 집중했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;다시 돌아와서,&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;프로그래밍 언어나 인프라 환경, 혹은 사용 어플리케이션에 따라 프로세스와 스레드의 사용 용도는 지속적으로 변화한다.&lt;/p&gt;
&lt;p&gt;Saas 급의 개발 환경으로서의 진화가 나를 매우 편하게 만들어 주고,&lt;/p&gt;
&lt;p&gt;매우 많은 개발 템플릿들로 개발 생산성을 높이는 것을 찬성하지만,&lt;/p&gt;
&lt;p&gt;내가 만든 프로그램의 제작 고도화 과정에서 무조건적으로 프로세스와 스레드 관리 도메인이 나올 것이라고 생각한다.&lt;/p&gt;
&lt;p&gt;애초에 나는 명령어를 통한 디렉토리, 파일 확인에 익숙하고,&lt;/p&gt;
&lt;p&gt;Vim, Neovim 에서 Zed 에서 여러 명령어를 사용하고 있으니,&lt;/p&gt;
&lt;p&gt;현재 내가 사용하고 있는 프로그램이 어떤 프로세스 ID 를 가지며,&lt;/p&gt;
&lt;p&gt;몇 개의 스레드를 파생시켰는지 알 필요가 있었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;프로세스와 스레드 개념을 꼭 알아야 하는 이유&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;현재 동시성 (Concurrency) 라는 모던 프로그래밍 개념이 강력하게 추진되고 있다.&lt;/p&gt;
&lt;p&gt;이러한 개념을 필두로 한 프로그래밍 언어들도 나오고 있으며,&lt;/p&gt;
&lt;p&gt;기존의 동기적 프로그래밍 방식의 단점을 해결하는 방식으로도 사용되고 있었다.&lt;/p&gt;
&lt;p&gt;Golang 은 동시성 개념을 탑재하여 스레드 몇천개를 순식간에 만들 수도 있다던데,&lt;/p&gt;
&lt;p&gt;왜 이런 현상이 놀라운 것인지 나도 알고 싶었다.&lt;/p&gt;
&lt;p&gt;그리고, 나는 개인적으로 개발 템플릿에 잡아먹히고 싶진 않았다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;프로세스란?&lt;/h2&gt;
&lt;p&gt;먼저, 위키백과를 통해 프로세스의 사전적 정의를 살펴보자 :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;프로세스&lt;/strong&gt; 는, 컴퓨터에서 연속적으로 실행되고 있는 프로그램이다.&lt;/p&gt;
&lt;p&gt;종종, 스케줄링의 대상이 되는 &lt;strong&gt;작업(Task)&lt;/strong&gt; 라는 용어와 거의 같은 의미로 사용된다.&lt;/p&gt;
&lt;p&gt;여러 개의 프로세서를 사용하는 것을 &lt;strong&gt;멀티프로세싱&lt;/strong&gt;(Multi-Processing) 이라고 부르며,&lt;/p&gt;
&lt;p&gt;같은 시간에 여러 개의 프로그램을 띄우는 &lt;strong&gt;&amp;quot;시분할&amp;quot;&lt;/strong&gt; 방식을 &lt;strong&gt;멀티태스팅&lt;/strong&gt;(Multi-Tasking) 이라고 한다.&lt;/p&gt;
&lt;p&gt;이러한 프로세스 관리는, 운영 체제의 중요한 부분이 되었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;위의 정의를 보고 든 생각이, 대학교에서 들은 &lt;strong&gt;운영체제&lt;/strong&gt; 과목이 아직 내 머리속에 남아있구나 하는 것이었다.&lt;/p&gt;
&lt;p&gt;각각의 프로세스를 효율적으로 관리하기 위해 박사 분들이 열심히 프로그램을 관리하고 있으시다 들었는데,&lt;/p&gt;
&lt;p&gt;정말 경의를 표한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;프로세스의 상태&lt;/h3&gt;
&lt;p&gt;먼저 &lt;strong&gt;커널&lt;/strong&gt; (kernel) 이라는 것을 알아야 하는데,&lt;/p&gt;
&lt;p&gt;시스템의 &amp;quot;모든 것&amp;quot; 을 완전히 제어(control) 한다.&lt;/p&gt;
&lt;p&gt;운영 체제의 특정 작업 및 응용 프로그램(Applications) 수행에 필요한 여러 가지 서비스를 제공한다.&lt;/p&gt;
&lt;p&gt;커널은 &lt;strong&gt;보안&lt;/strong&gt;, &lt;strong&gt;자원관리&lt;/strong&gt;, &lt;strong&gt;추상화&lt;/strong&gt;를 수행한다.&lt;/p&gt;
&lt;p&gt;커널은 프로세스에 처리기(프로세서) 를 할당하며, 이를 스케줄링 (Scheduling) 이라고 한다.&lt;/p&gt;
&lt;p&gt;또한, 커널은 시스템 자원을 제어하기 때문에, 여기에 접근하거나 상호작용할 수 있는 인터페이스를 제공한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;커널은 위와 같은 작업 중, 프로세스를 관리하기 위해,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ready Queue = 준비 큐 : 준비 상태인 프로세스들을 큐로 관리&lt;/li&gt;
&lt;li&gt;Waiting Queue = 대기 큐 : 대기 상태인 프로세스들을 큐로 관리&lt;/li&gt;
&lt;li&gt;Running Queue = 실행 큐 : 실행중인 프로세스들을 큐로 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이러한 자료구조를 가지고 있으며, 이를 이용하여 프로세스의 상태를 관리한다.&lt;/p&gt;
&lt;p&gt;참고로 Queue 는,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart LR

New(&amp;quot;새로운 엘리먼트&amp;quot;)

Queue(&amp;quot;Queue (큐)&amp;quot;)

Old(&amp;quot;오래된 엘리먼트&amp;quot;)

New -- 추가 --&amp;gt; Queue -- Pop --&amp;gt; Old&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이러한 형태를 가지며, 큐에서 엘리먼트를 뽑을 시, 가장 오래된 엘리먼트가 pop(추출) 된다.&lt;/p&gt;
&lt;p&gt;그래서, 프로세스는 자체적으로 이러한 상태를 가진다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;생성 - Create : 프로세스가 생성되는 중이다.&lt;/li&gt;
&lt;li&gt;실행 - Running : 프로세스가 CPU 를 차지하여 명령어들이 실행되고 있다.&lt;/li&gt;
&lt;li&gt;준비 - Ready : 프로세스가 CPU 를 사용하고 있지는 않지만, 언제든지 사용할 수 있는 상태이다. &lt;br/&gt; 이는 CPU 가 할당되기를 기다리는 상태이며, 준비 Queue 의 프로세스 중 우선순위가 높은 프로세스가 CPU 를 할당받는다.&lt;/li&gt;
&lt;li&gt;대기 - Waiting : 보류(Block) 라고 부르기도 한다. &lt;br/&gt; 프로세스 입출력 완료, Signal 수신 등 특정 사건을 기다리고 있는 상태이다.&lt;/li&gt;
&lt;li&gt;종료 - Terminated : 프로세스의 실행이 종료된 것을 의미한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;조금 넓게 보자면,&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;kernel(커널) 이 프로세스를 관리하며, 프로세스에게 이러한 상태들을 부여한다는 의미이다.&lt;/p&gt;
&lt;p&gt;아마 그대로의 Queue 를 사용하는 것이 아니라, 우선순위 큐 (Priority Queue) 와 같은 자료구조를 사용 할 것 같다.&lt;/p&gt;
&lt;h3&gt;Process 의 상태 전이&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TB

Swap-Wait(&amp;quot;Swapped out and waiting (메모리에서 waiting)&amp;quot;)

Swap-Block(&amp;quot;Swapped out and blocked (메모리에서 blocked)&amp;quot;)

Created(&amp;quot;Created (생성)&amp;quot;)

Waiting(&amp;quot;Waiting (대기)&amp;quot;)

Running(&amp;quot;Running (실행)&amp;quot;)

Blocked(&amp;quot;Blocked (중단)&amp;quot;)

Terminated(&amp;quot;Terminated (종료)&amp;quot;)

Created -- 준비 큐로 이동 --&amp;gt; Waiting

Waiting -- 실행 큐로 이동-dispatch --&amp;gt; Running

Running -- 시간제한됨-timeout --&amp;gt; Waiting

Running -- 보류-block --&amp;gt; Blocked

Blocked -- 깨우기-wakeup --&amp;gt; Waiting

Running -- 프로세스 종료 --&amp;gt; Terminated

Waiting -- 메모리에서 디스크로 이동 --&amp;gt; Swap-Wait

Swap-Wait -- 디스크에서 ready 로 이동 --&amp;gt; Waiting

Blocked -- 메모리에서 디스크로 이동 --&amp;gt; Swap-Block

Swap-Block -- 이벤트 발생 후 Blocked 로 이동 --&amp;gt; Blocked
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위키백과의 그래프를 참조했는데, &lt;code&gt;waiting&lt;/code&gt; 과 &lt;code&gt;block&lt;/code&gt; 키워드의 의미가 조금 겹치는 것이 있어,&lt;/p&gt;
&lt;p&gt;그래프 상에서 내가 배운 운영체제 지식과 결합하여 상세히 적어보았다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;먼저, Created 된 프로세스는 Ready(준비) 큐로 들어간다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Dispatch (디스패치)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;준비 큐의 맨 앞 프로세스가 CPU 를 점유하게 되는 것을 의미한다.&lt;/p&gt;
&lt;p&gt;준비 상태에서 실행 상태로 바뀌는 것을 dispatch 라고 한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;dispatch (processname) : ready -&amp;gt; running&lt;/code&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Block (보류)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;실행 상태의 프로세스가 허가된 시간을 다 쓰기 전에, 입출력 동작을 필요로 할 경우,&lt;/p&gt;
&lt;p&gt;프로세스는 CPU 를 스스로 반납하고 보류(Blocked) 상태로 넘어 간다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;block (processname) : running -&amp;gt; blocked&lt;/code&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;wakeup (깨우기)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;입출력 작업을 기다리며 Block 되었던 프로세스가,&lt;/p&gt;
&lt;p&gt;입출력 작업 종료 등 특정 사건이 일어났을 때, 보류(block) 에서 준비(waiting and ready) 상태로 넘어가는 과정을 의미한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;wakeup (processname) : blocked -&amp;gt; ready&lt;/code&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;timeout (시간제한)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;운영체제의 커널은 process 가 processor 를 독점하지 못하게 Clock Interrupt 을 둔다.&lt;/p&gt;
&lt;p&gt;따라서, 프로세스는 일정 시간 동안만 프로세서를 점유 할 수 있게 만든다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;timeout (processname) : running -&amp;gt; ready&lt;/code&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;대략적인 프로세스의 상태와 전이는 이렇다.&lt;/p&gt;
&lt;p&gt;이것 말고도 Swapped out and waiting, Swapped out and blocked 상태가 있는데,&lt;/p&gt;
&lt;p&gt;이는 &lt;a target=&quot;_blank&quot; href=&quot;https://ko.wikipedia.org/wiki/%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4&quot;&gt;위키백과 (프로세스)&lt;/a&gt; 에 들어가 보면 자세히 알 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;프로세스의 정보 자료구조 (PCB)&lt;/h3&gt;
&lt;p&gt;현재 작업중인 프로세스 리스트와 상세 정보를 볼 수 있는 것도 이러한 자료구조 덕분이라고 생각한다.&lt;/p&gt;
&lt;p&gt;프로세스 제어 블록 (PCB) 라고 부르는데, Process Control Block 이라고 부른다. 혹은 (Task Control Block)&lt;/p&gt;
&lt;br&gt;

&lt;p&gt;운영체제마다 PCB 에 탑재되는 항목이 조금씩 다를 수 있다고 한다.&lt;/p&gt;
&lt;p&gt;하지만, 일반적으로 다음과 같은 정보를 포함한다고 한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Process ID(PID) - 프로세스 식별자 ID&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Process State - 프로세스의 현재 상태&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;생성 (Create)&lt;/li&gt;
&lt;li&gt;준비 (Ready)&lt;/li&gt;
&lt;li&gt;실행 (Running)&lt;/li&gt;
&lt;li&gt;대기 (Waiting)&lt;/li&gt;
&lt;li&gt;완료 (Terminated)&lt;/li&gt;
&lt;li&gt;유예 준비상태 (Suspended Ready) - Swapped out and Waiting (disk 에 존재)&lt;/li&gt;
&lt;li&gt;유예 대기상태 (Suspended Wait) - Swapped out and Blocked (disk 에 존재)&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Program Counter - 프로그램 계수기&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이 프로세스가 다음에 실행 할 명령어의 주소를 가르킨다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;CPU Register or 일반 레지스터&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;CPU 스케줄링 정보&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;우선 순위&lt;/li&gt;
&lt;li&gt;최종 실행 시각&lt;/li&gt;
&lt;li&gt;CPU 점유시간&lt;/li&gt;
&lt;li&gt;등등&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;메모리 관리 정보&lt;/strong&gt; - 해당 프로세스의 주소 공간 등등&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Process 계정 정보&lt;/strong&gt; - 페이지 테이블, 스케줄링 큐 포인터, 소유자, 부모 프로세스 등&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;입출력 상태 정보&lt;/strong&gt; - Process 에 할당된 입출력장치 목록, 열린 파일 목록 등등&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Thread 란?&lt;/h2&gt;
&lt;p&gt;특히, 개발자는 응용 어플리케이션을 가장 많이 다루게 된다고 개인적으로 생각하는데,&lt;/p&gt;
&lt;p&gt;특정 언어를 통해 프로세스 까지는 건들기 힘들더라도, 스레드는 쉽게 생성 할 수 있다.&lt;/p&gt;
&lt;p&gt;이는 사용자의 컴퓨팅 리소스를 보호함을 위해 프로세스를 보호한다고 들었다.&lt;/p&gt;
&lt;p&gt;스레드 또한, 위키백과의 정의를 통해 시작하자.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;정의&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;스레드(thread) 는, 프로세스 내에서 실행되는 흐름의 단위를 말한다.&lt;/p&gt;
&lt;p&gt;일반적으로 하나의 프로그램은 하나의 스레드를 가지고 있지만,&lt;/p&gt;
&lt;p&gt;프로그램 환경에 따라 둘 이상의 스레드를 동시에 실행 할 수 있다.&lt;/p&gt;
&lt;p&gt;이러한 실행 방식을 &lt;strong&gt;&amp;quot;멀티스레드&amp;quot;&lt;/strong&gt; (Multi-Thread) 라고 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Process 와 Thread 는 무엇이 다를까?&lt;/h3&gt;
&lt;p&gt;멀티 프로세스와, 멀티 스레드는 여러 흐름이 동시에 진행된다는 공통점을 가진다.&lt;/p&gt;
&lt;p&gt;하지만, 멀티 프로세스는 독립적인 환경에서 각각 실행되며,&lt;/p&gt;
&lt;p&gt;멀티 스레드는 소속되는 프로세스의 환경(메모리) 를 공유하여 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;그리고, 프로세스 전환보다 스레드 간의 전환이 빠르다고 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;멀티스레드의 장점은, CPU 가 여러 개일 경우, 각각의 cpu 가 스레드를 나눠 담당하는 방식으로 속도를 올릴 수 있다.&lt;/p&gt;
&lt;p&gt;이러한 방식으로 실제 시간상으로 동시에 수행 될 수 있다고 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;단점으로는, 각각의 스레드 중 어떤 것이 먼저 실행될지 그 순서를 알수 없다고 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;프로세스 내부의 스레드 실행 환경&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;|  시간   | &amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;
----------
|        |       |
|        | 스레드1 |~~~~~~~|                     |~~~~~~~~~~~~
| 프로세스 |       |
|        |-------------------------------------------------
|        |       |
|        | 스레드2 |       |~~~~~~~~~~~~~~~~~~~~|
|        |       |&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;하나의 프로세스에서 실행되는 2 개의 스레드 환경은 이렇게 표현 할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;스레드의 종류&lt;/h3&gt;
&lt;p&gt;공식 문서를 보면서 든 생각은, 내가 다룬 스레드는 아마 대부분 사용자 레벨 스레드일 것이라고 생각한다.&lt;/p&gt;
&lt;p&gt;한번 2 가지 스레드 종류를 보자.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 사용자 레벨 스레드&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;사용자 스레드는 커널 영역의 상위에서 지원하며, 사용자 레벨의 라이브러리를 통해 구현된다.&lt;/p&gt;
&lt;p&gt;이 라이브러리는 스레드의 생성 및 스케줄링 등등에 관한 관리 기능을 제공한다.&lt;/p&gt;
&lt;p&gt;장점은 속도가 빠르며,&lt;/p&gt;
&lt;p&gt;단점으로는 스레드 중 하나의 스레드가 중단되면, 모든 스레드가 중단된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;2. 커널 레벨 스레드&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이는 사용자 라이브러리가 관리하는 것과는 다르게, 커널 자체에서 직접 스레드를 관리한다.&lt;/p&gt;
&lt;p&gt;장점은 스레드 중 하나가 중단되더라도 다른 스레드를 계속 실행시켜주며, 다중 processor 에 스레드를 할당하기도 한다.&lt;/p&gt;
&lt;p&gt;단점은, 사용자 스레드에 비해 생성 및 관리가 느리다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;스레드 데이터&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. 스레드의 기본 데이터&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;스레드 또한 Process 처럼 실행 흐름이므로, 실행과 관련된 데이터가 필요하다.&lt;/p&gt;
&lt;p&gt;일반적으로 스레드는 이러한 정보를 가진다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Thread ID&lt;/li&gt;
&lt;li&gt;Program Counter&lt;/li&gt;
&lt;li&gt;Register Set&lt;/li&gt;
&lt;li&gt;Stack&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;2. 스레드의 특정 데이터&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;위의 기본 데이터 외에도 스레드의 추가 정보를 필요로 하는 경우가 있는데,&lt;/p&gt;
&lt;p&gt;이는 스레드가 &amp;quot;트랜잭션&amp;quot; 을 처리 할 경우이다.&lt;/p&gt;
&lt;p&gt;이 때, TSD 를 필요로 하는데, Thread Specific Data 의 약자이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;프로세스와 스레드는 실제로 생각 한 것처럼 동작하지 않는다.&lt;/h2&gt;
&lt;p&gt;나는 위에서 1 개의 프로세스에 2 개의 스레드를 할당 했을 때 시간 분배가 어떻게 일어나는지 그렸다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;|  시간   | &amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;
----------
|        |       |
|        | 스레드1 |~~~~~~~|                     |~~~~~~~~~~~~
| 프로세스 |       |
|        |-------------------------------------------------
|        |       |
|        | 스레드2 |       |~~~~~~~~~~~~~~~~~~~~|
|        |       |&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;실제 기기에서 스레드는 위의 그래프처럼 동작하기도, 동작하지 않기도 한다.&lt;/p&gt;
&lt;p&gt;왜 그럴까?&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;프로세스는 스레드 정보를 추상화 한 정보이다.&lt;/h3&gt;
&lt;p&gt;프로세스는 최소 1 개의 스레드를 가지고 실행된다.&lt;/p&gt;
&lt;p&gt;프로세스는 내가 위에 작성했던 &lt;strong&gt;&amp;quot;Process 의 상태 전이&amp;quot;&lt;/strong&gt; 를 따라서 운용된다.&lt;/p&gt;
&lt;p&gt;그런데, 이 그래프를 따르는 실제 단위는 사실상 Thread 이다.&lt;/p&gt;
&lt;p&gt;물론, 프로세스가 위의 그래프를 따르기 때문에, 소속된 스레드들도 동일한 상태를 가지게 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;어떤 것이 생각 한 것 처럼 동작하지 않는다는 걸까?&lt;/h3&gt;
&lt;p&gt;먼저, 프로세스 내부의 스레드는 위의 그래프를 기반으로 운용되는 것이 사실이다.&lt;/p&gt;
&lt;p&gt;그런데, 내가 떠올린 질문이 있었다.&lt;/p&gt;
&lt;p&gt;&amp;quot;하나의 프로세스에 스레드를 몇백개 할당한다면, 하나의 스레드에서 처리하던 것 보단 빠르던데?&amp;quot;&lt;/p&gt;
&lt;p&gt;여기서 나는 스스로 함정에 빠졌는데, 프로세스 내부의 스레드는 무조건 위의 형식을 따른다는 전제를 깐 것이다.&lt;/p&gt;
&lt;p&gt;도대체 의문을 해소하지 못하여 이를 GPT 에게 직접 물어보아 의문을 해결했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;현대 컴퓨터들은 대부분 멀티 프로세서를 사용한다.&lt;/p&gt;
&lt;p&gt;즉, cpu 코어가 1 개만 존재하는 것이 아니라는 것이다.&lt;/p&gt;
&lt;p&gt;만약에 특정 프로세스에 너무 많은 스레드가 할당된다면,&lt;/p&gt;
&lt;p&gt;프로세스 내부의 스레드를 다른 프로세스에 할당하여 해결하는 것이 아니라,&lt;/p&gt;
&lt;p&gt;프로세스 내부의 스레드들의 관계를 &amp;quot;그대로&amp;quot; 유지하되,&lt;/p&gt;
&lt;p&gt;커널이 여러 스레드들을 프로세서에 할당하여 계산한다는 것이었다.&lt;/p&gt;
&lt;p&gt;이렇게 된다면, 하나의 프로세스를 정확히 콕 집어서 그 안에서 시간 분배를 하여 계산하는 것이 아니라,&lt;/p&gt;
&lt;p&gt;멀티 프로세서를 통하여 멀티 프로세스를 수행하는 효과를 얻을 수 있는 것이다.&lt;/p&gt;
&lt;p&gt;여기서 내가 확실히 알게 된 것은, 프로세서는 물리 개념으로서, 프로세스는 로직 개념으로서 분리되어 있다는 것이다.&lt;/p&gt;
&lt;p&gt;프로세스는 물리적으로 계산하는 기계이며, 프로세스는 계산해야 하는 Task 를 의미하는 정보이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;프로세스와 스레드를 볼 수 있는 방법은 무엇일까&lt;/h2&gt;
&lt;p&gt;우리는 손쉽게 프로세스를 볼 수 있다.&lt;/p&gt;
&lt;p&gt;윈도우 사용자들은 &amp;quot;작업 관리자&amp;quot;를 통하여 볼 수 있고,&lt;/p&gt;
&lt;p&gt;맥 사용자들은 &amp;quot;활성 상태 보기&amp;quot; 를 통하여 볼 수 있다.&lt;/p&gt;
&lt;p&gt;혹시 모를 리눅스 GUI 사용자들도 쉽게 볼 수 있는 프로그램이 있을 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, 클라우드를 통한 컴퓨팅 서비스가 매우 활성화되었으며,&lt;/p&gt;
&lt;p&gt;Docker, Kubernetes 를 통한 컨테이너 배포 방식이 매우 보편화 되어 있다.&lt;/p&gt;
&lt;p&gt;여기서 우리가 어플리케이션을 배포했을 때, 위와 같은 프로그램과 서비스를 제외한다면,&lt;/p&gt;
&lt;p&gt;우리가 프로세스가 정상적으로 진행되고 있는지 알 방법이 있을까?&lt;/p&gt;
&lt;p&gt;따라서, 나는 명령어를 통해 현재 탑재된 프로세스 중에서, 내가 원하는 것만 볼 수 있는 방법을 알아보기로 결정했다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;프로세스와 스레드 정보를 명령어 탭에서 확인해 보자&lt;/h3&gt;
&lt;p&gt;현재는 커널 수준에서 Thread ID 를 보호하여 막는다고 한다.&lt;/p&gt;
&lt;p&gt;따라서, 이제는 실행된 Process ID 와, 파생된 스레드들을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;예를 들어, PID 가 3 이고, 해당 프로세스에서 5 개의 스레드가 파생되었다면,&lt;/p&gt;
&lt;p&gt;PID 를 가진 줄이 6 줄이다. (1줄 : 프로세스, 5줄 : 스레드)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;참고로, 리눅스, 윈도우, 맥, 등등 각자의 운영체제에서 가지는 단축키와 명령어가 다를 수 있다.&lt;/p&gt;
&lt;p&gt;예를 들어, 리눅스에서는 프로세스와 연관 스레드를 보기 위해,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ ps -eLf&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;라는 명령어를 사용하지만,&lt;/p&gt;
&lt;p&gt;Mac 에서는 현재 &lt;code&gt;-L&lt;/code&gt; 옵션이 불가하다.&lt;/p&gt;
&lt;p&gt;맥에서는 옵션이 &lt;code&gt;-M&lt;/code&gt; 으로 매칭되어 있다.&lt;/p&gt;
&lt;p&gt;따라서,&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mac 명령어 전용&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ ps -eMf
...
...
거의 수백줄?&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;우리는 특정 프로세스에서 파생된 스레드의 개수와 현황을 파악하고 싶어한다.&lt;/p&gt;
&lt;p&gt;이렇게 명령어를 입력하면, 특히 백그라운드 프로그램이 많은 로컬 기기의 특성상&lt;/p&gt;
&lt;p&gt;원하는 정보를 얻기 굉장히 힘들다.&lt;/p&gt;
&lt;p&gt;그래서 대부분의 기기에 존재하는 기본 명령어가 있는데, 바로 &lt;code&gt;grep&lt;/code&gt; 이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;우리는 명령어 파이프 (&lt;code&gt;|&lt;/code&gt;) 를 사용하여, 기존에 나온 문자열 결과물을 &lt;code&gt;grep&lt;/code&gt; 으로 넘겨서,&lt;/p&gt;
&lt;p&gt;원하는 PID 를 얻을 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;WAS 프로그램 세팅&lt;/h3&gt;
&lt;p&gt;나는 NestJS 프로그램을 실행시켜놓고, 실제 실행되고 있는 Process 와 스레드 정보를 얻을 것이다.&lt;/p&gt;
&lt;p&gt;먼저, NestJS 프로젝트에 들어가서,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  mycv git:(main) ✗ npm run start

&amp;gt; mycv@0.0.1 start
&amp;gt; cross-env NODE_ENV=development nest start

development
[Nest] 8901  - 05/20/2025, 10:38:38 PM     LOG [NestFactory] Starting Nest application...
[Nest] 8901  - 05/20/2025, 10:38:38 PM     LOG [InstanceLoader] TypeOrmModule dependencies initialized +22ms
....&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;명령어를 통해 프로젝트를 실행시킨다.&lt;/p&gt;
&lt;p&gt;먼저 알려주자면, 위의 8901 숫자는, 현재 실행되고 있는 NestJS 서버의 실제 PID 를 의미한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그리고, 이러한 명령어를 입력한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ ps -e -o pid,ppid,comm | grep npm&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;-v&lt;/code&gt; 명령어는 맥에서 프로세스 기본 속성들을 나열하기 위해 존재하는데,&lt;/p&gt;
&lt;p&gt;우리는 현재 보고자 하는 속성 &lt;code&gt;pid&lt;/code&gt;, &lt;code&gt;ppid&lt;/code&gt;, &lt;code&gt;comm&lt;/code&gt; 만 검색하기 위해 &lt;code&gt;-o&lt;/code&gt; 옵션을 사용했다.&lt;/p&gt;
&lt;p&gt;이 옵션은 기본 속성 후에 추가로 붙여지거나, 해당 속성들만 노출된다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;grep npm&lt;/code&gt; 을 한 이유는, 우리가 &lt;code&gt;npm run start&lt;/code&gt; 로 서버를 시작했기 때문이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pid&lt;/code&gt; : 자신의 프로세스 ID&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ppid&lt;/code&gt; : 자신을 파생시킨 프로세스 ID - parent Process ID&lt;/li&gt;
&lt;li&gt;&lt;code&gt;comm&lt;/code&gt; : 이 프로세스가 실행 된 명령어&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  ~ ps -e -o pid,ppid,command | grep npm
 9374  8989 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn --exclude-dir=.idea --exclude-dir=.tox --exclude-dir=.venv --exclude-dir=venv npm
 8869  7102 npm run start&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;npm run start&lt;/code&gt; 의 프로세스 ID 는 8869 이다.&lt;/p&gt;
&lt;p&gt;그렇다면, 이를 검색 해 보자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  ~ ps -e -o pid,ppid,command | grep 8869
 9442  8989 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn --exclude-dir=.idea --exclude-dir=.tox --exclude-dir=.venv --exclude-dir=venv 8869
 8869  7102 npm run start
 8884  8869 node /Users/&amp;lt;중간 디렉토리들&amp;gt;/nestjs/mycv/node_modules/.bin/cross-env NODE_ENV=development nest start&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이를 통해, pid, ppid 가 8869 인 문장을 잡을 수 있다.&lt;/p&gt;
&lt;p&gt;실제로, 본인은 프로젝트의 &lt;code&gt;package.json&lt;/code&gt; 에 &lt;code&gt;scripts&lt;/code&gt; 부분에 저렇게 정의 해 놓았다.&lt;/p&gt;
&lt;p&gt;이제 끝이 나올 때 까지 부모 프로세스 PID 를 이용하여 가보자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  ~ ps -e -o pid,ppid,command | grep 8884
 8884  8869 node /Users/gongdamhyeong/intelliJ/udemy/nestjs/mycv/node_modules/.bin/cross-env NODE_ENV=development nest start
 8885  8884 node /Users/gongdamhyeong/intelliJ/udemy/nestjs/mycv/node_modules/.bin/nest start&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  ~ ps -e -o pid,ppid,command | grep 8885
 8885  8884 node /Users/gongdamhyeong/intelliJ/udemy/nestjs/mycv/node_modules/.bin/nest start
 8901  8885 node --enable-source-maps /Users/gongdamhyeong/intelliJ/udemy/nestjs/mycv/dist/src/main&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  ~ ps -e -o pid,ppid,command | grep 8901
 8901  8885 node --enable-source-maps /Users/gongdamhyeong/intelliJ/udemy/nestjs/mycv/dist/src/main&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;마지막까지 자식 프로세스를 추적 해 보니,&lt;/p&gt;
&lt;p&gt;우리가 처음에 봤던&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;[Nest] 8901  - 05/20/2025, 10:38:38 PM     LOG [NestFactory] Starting Nest application...&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;의 8901 PID 를 볼 수 있다.&lt;/p&gt;
&lt;p&gt;그렇다면, 실제 실행중인 PID 들의 관계와 실행 상태를 보았으니,&lt;/p&gt;
&lt;p&gt;스레드 개수와 상태를 알 수 있는 명령어를 입력 해 보자 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  ~ ps -Mv 8901
USER            PID   TT   %CPU STAT PRI     STIME     UTIME COMMAND      TIME  SL  RE PAGEIN      VSZ    RSS   LIM     TSIZ %MEM
gongdamhyeong  8901 s004    0.0 S    31T   0:00.15   0:00.72 node --   0:01.15   0   0      0 35689140 115456     -        0  0.7
               8901         0.0 S    31T   0:00.00   0:00.01           0:01.15   0   0      0 35689140 115456     -        0  0.7
               8901         0.0 S    31T   0:00.00   0:00.00           0:01.15   0   0      0 35689140 115456     -        0  0.7
               8901         0.0 S    31T   0:00.01   0:00.04           0:01.15   0   0      0 35689140 115456     -        0  0.7
               8901         0.0 S    31T   0:00.01   0:00.06           0:01.15   0   0      0 35689140 115456     -        0  0.7
               8901         0.0 S    31T   0:00.01   0:00.05           0:01.15   0   0      0 35689140 115456     -        0  0.7
               8901         0.0 S    31T   0:00.01   0:00.04           0:01.15   0   0      0 35689140 115456     -        0  0.7
               8901         0.0 S    31T   0:00.00   0:00.00           0:01.15   0   0      0 35689140 115456     -        0  0.7
               8901         0.0 S    31T   0:00.00   0:00.00           0:01.15   0   0      0 35689140 115456     -        0  0.7
               8901         0.0 S    31T   0:00.00   0:00.00           0:01.15   0   0      0 35689140 115456     -        0  0.7
               8901         0.0 S    31T   0:00.00   0:00.05           0:01.15   0   0      0 35689140 115456     -        0  0.7
               8901         0.0 S    31T   0:00.00   0:00.00           0:01.15   0   0      0 35689140 115456     -        0  0.7&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;맨 위의 프로세스 행을 제외하면, 11 개의 스레드가 NestJS 서버 기동에 사용되고 있다는 것을 알 수 있다.&lt;/p&gt;
&lt;p&gt;일단 예상되는 스레드는,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;코드 실행을 수행하는 콜스택 스레드 1개&lt;/li&gt;
&lt;li&gt;libuv 스레드 4개&lt;/li&gt;
&lt;li&gt;GC 1개&lt;/li&gt;
&lt;li&gt;JIT 1개&lt;/li&gt;
&lt;li&gt;async hook X개&lt;/li&gt;
&lt;li&gt;타입 source map 을 이용하여 체킹하는 스레드 x 개&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이렇게 구성되어 있는 것 같다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;명령어를 통한 스레드 확인 후기를 보자면..&lt;/h3&gt;
&lt;p&gt;내가 한 방식은 절대로 정상적인 방식은 아닌 것 같다.&lt;/p&gt;
&lt;p&gt;아마 &lt;code&gt;ps&lt;/code&gt;, &lt;code&gt;grep&lt;/code&gt; 외의 특별한 명령어 라이브러리를 통해 쉽게 펼칠 수도 있을 것이다.&lt;/p&gt;
&lt;p&gt;하지만, 내가 아는 한의 명령어 사용법을 통해,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;현재 PID 와 해당 PID 를 부모로 삼는 프로세스 추적&lt;/li&gt;
&lt;li&gt;마지막에 도달한 실제 서버 Process 를 펼쳐 11 개의 스레드가 실행되고 있음을 알 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;S&lt;/code&gt; 는 Sleep 을 의미하는데, 이는 실제로 서버에 특별한 이벤트가 없어서 대기하고 있는 중이다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;R&lt;/code&gt; 은 현재 실행중인 프로세스 혹은 스레드를 의미한다.&lt;/li&gt;
&lt;li&gt;더 많은 명령어와 옵션으로 쉽게 추적할 수 있을 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;h2&gt;이번 글을 작성하며 배운 것&lt;/h2&gt;
&lt;p&gt;프로세스와 스레드의 정확한 의미,&lt;/p&gt;
&lt;p&gt;그리고 사용자 라이브러리 단계에서 어떻게 사용되는지 배우게 되었다.&lt;/p&gt;
&lt;p&gt;특히, 프로세스의 실행 단계가 아주 정확히는 스레드 자체라는 것에 살짝 충격을 얻었는데,&lt;/p&gt;
&lt;p&gt;이는 프로세스 자체가 그렇게 실행이 되고,&lt;/p&gt;
&lt;p&gt;스레드는 내부적으로 처리되는 특정 단계가 있을 것이라 예측했기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그리고, Java 의 Thread 와, Node.js 의 Worker 가&lt;/p&gt;
&lt;p&gt;OS 스레드와 사용자 스레드로서의 1 : 1 매칭이라는 것 또한 알게 되었다.&lt;/p&gt;
&lt;p&gt;나는 OS 스레드에 한정된 다중 사용자 스레드로서 매칭되어 M : 1 이 될 줄 알았다.&lt;/p&gt;
&lt;p&gt;즉, 언어들이 따로 직접적으로 스레드를 생성할 시,&lt;/p&gt;
&lt;p&gt;OS 스레드와 1 : 1 매칭시켜 커널 수준에서 스스로 최적화 하도록 만들었다는 것이다.&lt;/p&gt;
&lt;p&gt;OS 는 사용자 스레드가 어떻게 실행되는지 알 수 없기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그리고, 이 글을 작성 한 계기는 프로세스와 스레드, 멀티프로세스, 멀티스레드 환경에 대한&lt;/p&gt;
&lt;p&gt;정확한 이해를 위함이기도 했지만,&lt;/p&gt;
&lt;p&gt;더 나아가 &amp;quot;동시성&amp;quot; 을 말하는 &amp;quot;Concurrency&amp;quot; 를 배우고 글을 작성하기 위함이었다.&lt;/p&gt;
&lt;p&gt;요즘 현대적 언어들은 동시성을 탑재하거나, 추후 이를 지원하는 기능을 추가했다.&lt;/p&gt;
&lt;p&gt;그 만큼 병렬 연산을 빠르게 하기 위해 진심인 것인데,&lt;/p&gt;
&lt;p&gt;나는 동시성이라는 단어를 들어만 보고 개념만 조금 알고 있을 뿐, 정확히는 알지 못했다.&lt;/p&gt;
&lt;p&gt;먼저, 이 글을 작성하게 되면서 알게 된 것은,&lt;/p&gt;
&lt;p&gt;OS 스레드 M 개와, 사용자 스레드 N 개와 매칭되어 계산한다는 것이다.&lt;/p&gt;
&lt;p&gt;기존이 스레드는 1 : 1 이었던 반면,&lt;/p&gt;
&lt;p&gt;언어 수준에서 N 개의 사용자 스레드를 생성하고,&lt;/p&gt;
&lt;p&gt;이를 OS 스레드 M 개와 매칭시켜 최적화한다는 발상은 굉장히 신선했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그리고, 이번에 실제로 명령어 수준에서 프로세스를 검색하고,&lt;/p&gt;
&lt;p&gt;부모 프로세스의 PID 를 통해 자식 프로세스를 탐색하는 과정은 명령어에 대한 이해도를 한층 끌어올려주었다.&lt;/p&gt;
&lt;p&gt;조금 아쉬운 것은, 내가 &lt;code&gt;ps&lt;/code&gt; 와 &lt;code&gt;grep&lt;/code&gt; 에 대한 명령어 라이브러리 이해도가 높지 않아&lt;/p&gt;
&lt;p&gt;편하게 검색하기 어려웠다는 점이다.&lt;/p&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;h2&gt;참고 사이트&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;위키백과 (프로세스)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.wikipedia.org/wiki/%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4&quot;&gt;https://ko.wikipedia.org/wiki/%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;위키백과 (커널 - 컴퓨팅)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.wikipedia.org/wiki/%EC%BB%A4%EB%84%90_(%EC%BB%B4%ED%93%A8%ED%8C%85)&quot;&gt;https://ko.wikipedia.org/wiki/%EC%BB%A4%EB%84%90_(%EC%BB%B4%ED%93%A8%ED%8C%85)&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;위키백과 (스레드 - 컴퓨팅)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.wikipedia.org/wiki/%EC%8A%A4%EB%A0%88%EB%93%9C_(%EC%BB%B4%ED%93%A8%ED%8C%85)&quot;&gt;https://ko.wikipedia.org/wiki/%EC%8A%A4%EB%A0%88%EB%93%9C_(%EC%BB%B4%ED%93%A8%ED%8C%85)&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;위키백과 (멀티스레딩)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.wikipedia.org/wiki/%EB%A9%80%ED%8B%B0%EC%8A%A4%EB%A0%88%EB%94%A9&quot;&gt;https://ko.wikipedia.org/wiki/%EB%A9%80%ED%8B%B0%EC%8A%A4%EB%A0%88%EB%94%A9&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;위키백과 (프로세스 관리)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.wikipedia.org/wiki/%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4_%EA%B4%80%EB%A6%AC&quot;&gt;https://ko.wikipedia.org/wiki/%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4_%EA%B4%80%EB%A6%AC&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;</description>
      <category>잡다 지식</category>
      <category>PS</category>
      <category>Thread</category>
      <category>그래프</category>
      <category>멀티스레드</category>
      <category>명령어</category>
      <category>스레드</category>
      <category>쓰레드</category>
      <category>프로세서</category>
      <category>프로세스</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/216</guid>
      <comments>https://codecreature.tistory.com/216#entry216comment</comments>
      <pubDate>Tue, 20 May 2025 23:50:43 +0900</pubDate>
    </item>
    <item>
      <title>Domain 과 SSL 에 대하여 (부제 : https 는 뭘까?)</title>
      <link>https://codecreature.tistory.com/215</link>
      <description>&lt;h2&gt;제목 : Domain 과 SSL 에 대하여&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;이 글을 작성하게 된 계기&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이전에 팀 프로젝트를 수행 할 때, DevOps 역할을 맡았던 내가 골머리를 썩혔던 구간이,&lt;/p&gt;
&lt;p&gt;바로 Domain 과, SSL 부분이었다.&lt;/p&gt;
&lt;p&gt;나는 백엔드 서버를 단순 IP 로서 접근하게 만들기보다는,&lt;/p&gt;
&lt;p&gt;도메인을 통해 정확한 백엔드 서버로 도착하게 만들고 싶었다.&lt;/p&gt;
&lt;p&gt;그 이유가, NestJS 에서 API 목록 제공 시 사용하는 외부 API 가 Swagger 라는 라이브러리인데,&lt;/p&gt;
&lt;p&gt;이는 백엔드 루트 주소에서 특정 루트로 입장 시 내가 작성한 API 코드를 자동으로 인식하여&lt;/p&gt;
&lt;p&gt;목록으로 정돈되어 보여주기 때문이었다.&lt;/p&gt;
&lt;p&gt;나는 AWS EC2 + RDS (MariaDB) 를 사용하였는데,&lt;/p&gt;
&lt;p&gt;EC2 (Amazon Linux) 를 기반으로 처음부터 프로그램을 설치하여 Express 어댑터 기반의 NestJS 를 만들었다.&lt;/p&gt;
&lt;p&gt;지금은 프로젝트를 수행했기 때문에 안 사실이지만, EC2 에서 &lt;code&gt;https&lt;/code&gt; 프로토콜을 지원하기 위해서는&lt;/p&gt;
&lt;p&gt;특정 프로그램을 설치하고, 적용해야 한다는 것이었다.&lt;/p&gt;
&lt;p&gt;이 때 도메인 이름을 적용하는 방식과 SSL 을 무료로 사용하는 방법(Let&amp;#39;s Encrypt) 을 알게 되었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그런데, 나는 웹 사이트 개발과 배포 과정에서&lt;/p&gt;
&lt;p&gt;수많은 사이트 프로그램들이 도메인 이름과 SSL 인증서를 동시에 지원해 주기 때문에,&lt;/p&gt;
&lt;p&gt;웹 사이트 배포 시 이를 신경쓰지 않아도 된다는 것을 발견했다.&lt;/p&gt;
&lt;p&gt;이런 과정을 대신 해 주기 때문에, 배포 시 &lt;code&gt;https&lt;/code&gt; 프로토콜을 지원하는 자신의 웹을 볼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;나는 프로젝트 시 WAS(백엔드) 파트를 맡았기 때문에,&lt;/p&gt;
&lt;p&gt;웹에서 제공하는 &lt;code&gt;https&lt;/code&gt; 프로토콜을 지원하기 위해 도메인과 ssl 을 설치하고 적용했다.&lt;/p&gt;
&lt;p&gt;이 과정이 힘들었던 이유는, 도메인과 ssl 의 개념에 대해서 알지 못한 채 적용했기 때문이라고 생각한다.&lt;/p&gt;
&lt;p&gt;뿐만 아니라, 어떻게 웹사이트 자동 배포 사이트에서 &lt;code&gt;https&lt;/code&gt; 를 쉽게 적용했을까 의문이 들기 시작했다.&lt;/p&gt;
&lt;p&gt;이들도 결국 도메인과 ssl, Certificate 라는 개념에 대해 정확히 알고 있기 때문에,&lt;/p&gt;
&lt;p&gt;개발자들의 편의 니즈를 정확히 건드린 것이라고 생각한다.&lt;/p&gt;
&lt;p&gt;그리고 혹시 아는가? 내가 이 개념을 배우는 과정에서 또 다른 편의성 프로그램을 만들지는 미지수다.&lt;/p&gt;
&lt;p&gt;따라서 이 개념과 인증이 어떤 역할을 해주는지 정확하게 알기 위해서 이 글을 작성하며 배운다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;도메인 (Domain) 이란 무엇인가?&lt;/h2&gt;
&lt;p&gt;모든 서버는 IP 주소를 갖는다.&lt;/p&gt;
&lt;p&gt;이 때, IP 주소는 2 가지 버전을 가진다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;1. IPv4 버전&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;EX - &lt;code&gt;xxx.xxx.xxx.xxx&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;각각의 숫자 파트를 &lt;code&gt;xxx&lt;/code&gt; 로 표현했으며, &lt;code&gt;0&lt;/code&gt; ~ &lt;code&gt;255&lt;/code&gt; 까지 할당 할 수 있다. (맨 앞은 &lt;code&gt;254&lt;/code&gt; 까지인듯)&lt;/p&gt;
&lt;p&gt;이는 각 숫자의 파트가 8비트를 가지기 때문이다.&lt;/p&gt;
&lt;p&gt;이 버전의 IP 는 각종 디지털 디바이스가 늘어나기에 빠르게 소진되어 IPv6 라는 새로운 버전이 생기게 되었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;2. IPv6 버전&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;IPv4 의 주소가 빠르게 소진됨과 동시에, 새로이 만들어지는 스마트 기기에 IP 주소를 할당하기 위해 나타난 체계이다.&lt;/p&gt;
&lt;p&gt;EX - &lt;code&gt;xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;총 8 개의 파트를 가지고 있는데,&lt;/p&gt;
&lt;p&gt;IPv4 에서 &lt;code&gt;xxx&lt;/code&gt; 는 10 진수이며, &lt;code&gt;0&lt;/code&gt; ~ &lt;code&gt;255&lt;/code&gt; 까지 값을 가진 반면에,&lt;/p&gt;
&lt;p&gt;IPv6 에서는 각 숫자 표기법이 다르다.&lt;/p&gt;
&lt;p&gt;각 &lt;code&gt;x&lt;/code&gt; 는 &lt;code&gt;0&lt;/code&gt; ~ &lt;code&gt;e&lt;/code&gt; 로 표현되는데, 이는 16진수 표기법이다.&lt;/p&gt;
&lt;p&gt;그래서, &lt;code&gt;20ab:8ae9:....:5be9&lt;/code&gt; 이러한 형식으로 작성 할 수 있다.&lt;/p&gt;
&lt;p&gt;IPv4 의 $256^4$ 주소 영역과, IPv6 의 $(16^4)^8$ 를 비교 해 본다면,&lt;/p&gt;
&lt;p&gt;IPv4 에 비해 IPv6 가 얼마나 방대한 주소 영역을 가지는지 알 수 있다.&lt;/p&gt;
&lt;p&gt;IPv4 : 2^32 개, IPv6 : 2^128 개&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;다시 돌아와서, 도메인 이름은 IPv4 이던, IPv6 이던 굉장히 복잡한 숫자로 표현되므로,&lt;/p&gt;
&lt;p&gt;이러한 숫자를 쉽게 문자열로 입력하여 검색 할 수 있도록 IP 와 연결된 문자열을 &amp;quot;도메인 이름&amp;quot; 이라고 한다.&lt;/p&gt;
&lt;p&gt;우리는 &lt;code&gt;naver.com&lt;/code&gt; 을 IP 주소로 입력하지 않는다.&lt;/p&gt;
&lt;p&gt;문장으로 되어 있으니, 사람이 잘 기억할 수 있는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, 수많은 서버가 생기면서 당연히 수많은 도메인 이름 또한 생겨났다.&lt;/p&gt;
&lt;p&gt;이를 관리해야 할 체계가 필요하면서, 도메인 이름에 따른 관리 서버가 생겨났다.&lt;/p&gt;
&lt;p&gt;이 체계를 바로 &lt;strong&gt;&amp;quot;도메인 체계&amp;quot;&lt;/strong&gt; 라고 부른다.&lt;/p&gt;
&lt;p&gt;위에서 언급한 네이버의 &lt;code&gt;.com&lt;/code&gt; 또한 DNS (Domain Name System) 의 최상위 서버이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.com&lt;/code&gt;, &lt;code&gt;.org&lt;/code&gt;, &lt;code&gt;.net&lt;/code&gt;, ... 등등 여러가지 최상위 도메인이 존재한다.&lt;/p&gt;
&lt;p&gt;예를 들어, 한국 도메인의 경우 &lt;code&gt;.kr&lt;/code&gt; 이 있으며, 주로 &lt;code&gt;.co.kr&lt;/code&gt; 를 많이 보았을 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;예를 들어서, &lt;code&gt;damsoon.co.kr&lt;/code&gt; 사이트가 있다고 가정 해 보자.&lt;/p&gt;
&lt;p&gt;우리는 이 사이트를 현재 탐색하려는 기기에서 검색 한 적이 없다고 가정한다.&lt;/p&gt;
&lt;p&gt;그렇다면, 내 기기의 DNS Resolver 는 제일 먼저 어디로 요청을 보낼까?&lt;/p&gt;
&lt;p&gt;먼저, root 도메인 이름 서버 (Root Domain Name Server)로 요청을 보낸다.&lt;/p&gt;
&lt;p&gt;그리고, TLD (Top Level Domain) 인 &lt;code&gt;kr&lt;/code&gt; 도메인 이름 서버에 요청을 보낸다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;kr&lt;/code&gt; 도메인 이름 서버는 &lt;code&gt;co&lt;/code&gt; 도메인 이름 서버에 요청을 보낸다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;co&lt;/code&gt; 도메인 이름 서버는 &lt;code&gt;damsoon&lt;/code&gt; 사이트의 IP 주소를 반환한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이것을 그래프로 변환 해 본다면,&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;참고로, TLD 는 Top Level Domain Name Server 를 의미하고, &lt;br/&gt;&lt;br&gt;SLD 는 Second Level Domain Name Server 를 의미한다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TB

subgraph Client-Device
    Client(&amp;quot;클라이언트&amp;quot;)
    Client-DNS-Resolver(&amp;quot;클라이언트 DNS Resolver&amp;quot;)

    Client &amp;lt;--&amp;gt; Client-DNS-Resolver
end

subgraph DNS-Servers
    Root-Server[&amp;quot;.(Root)&amp;quot;]

    TLD[&amp;quot;.kr(TLD)&amp;quot;]

    SLD[&amp;quot;.co(SLD)&amp;quot;]

    Damsoon(&amp;quot;damsoon IP 주소!&amp;quot;)

    Root-Server &amp;lt;--&amp;gt; TLD

    TLD &amp;lt;--&amp;gt; SLD

    SLD &amp;lt;--&amp;gt; Damsoon
end

Client-Device ~~~ DNS-Servers

Client-DNS-Resolver &amp;lt;--&amp;gt; Root-Server
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 상황에서 &amp;quot;한 번도 방문 한 적 없다&amp;quot; 라고 조건을 걸었는데,&lt;/p&gt;
&lt;p&gt;이는 검색 했을 경우, DNS 서버에서 찾을 필요가 없고,&lt;/p&gt;
&lt;p&gt;로컬 기기에서 찾고 끝이기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;항상 어딘가에 웹을 요청하기 위해 DNS 서버에 이름을 찾게 된다면,&lt;/p&gt;
&lt;p&gt;이는 네트워크 상으로 큰 문제일 것이다.&lt;/p&gt;
&lt;p&gt;따라서, 로컬 기기의 브라우저, 혹은 DNS Resolver 가 문자열에 해당되는 도메인 이름을 캐싱 해 놓는다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;여기서 우리가 깨닫을 수 있는 것은,&lt;/p&gt;
&lt;p&gt;우리가 문자열로 특정 웹 사이트를 방문 하게 된다면, 무조건 DNS 서버에서 IP 주소를 가져 온 적이 있다는 것이다.&lt;/p&gt;
&lt;p&gt;그리고, 이러한 DNS 서버들은 전 세계에 퍼져 있다.&lt;/p&gt;
&lt;h3&gt;원하는 도메인 이름은 구입해야 한다.&lt;/h3&gt;
&lt;p&gt;이것이 포인트인데, 사용자들이 편히 웹 사이트에 접근하기 위해서는 도메인 이름을 사용하는데,&lt;/p&gt;
&lt;p&gt;이를 등록 해 주는 사이트와, 관리하는 네임서버 회사들이 따로 존재한다는 것이다.&lt;/p&gt;
&lt;p&gt;kisa 사이트에서 등록대행자 회사를 선택하고 구입이 가능 한 것으로도 알고 있지만,&lt;/p&gt;
&lt;p&gt;특히 한국에서 Gabia (가비아) 사이트가 굉장히 유명하다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.com&lt;/code&gt; 과 같은 유명한 TLD 의 경우, 오만원에서 십만원 쯤 하며,&lt;/p&gt;
&lt;p&gt;본 적 없는 것 같아 보이는 도메인의 경우 거의 공짜 수준으로 사용 할 수 있기도 하다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;SSL 이란 무엇일까?&lt;/h2&gt;
&lt;p&gt;먼저, SSL 인증서를 알아보기 전에, 이에 대한 개념을 정확하게 짚고 넘어가야 한다고 생각했다.&lt;/p&gt;
&lt;p&gt;SSL 이란, (Secure Sockets Layer) 의 약자이다. 암호화 기반 인터넷 보안 protocol 이다.&lt;/p&gt;
&lt;p&gt;공식문서를 보니까, TLS 는 SSL 의 다음 버전이며 현재도 사용되고 있는 것은 TLS 인데,&lt;/p&gt;
&lt;p&gt;사람들이 혼용하여 사용하여 그냥 SSL 을 말하면 TLS 로 인식하면 된다고 한다.&lt;/p&gt;
&lt;p&gt;즉, &amp;quot;SSL 인증서&amp;quot;는 사실 공식적으로 &amp;quot;TLS 인증서&amp;quot; 라고 한다.&lt;/p&gt;
&lt;p&gt;나도 공식적으로는 TLS 라고 인식하겠지만, 편하게 SSL 이라고 부르겠다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;다시 돌아와서, SSL 을 사용하는 웹 사이트는 &lt;code&gt;https&lt;/code&gt; 를 사용한다.&lt;/p&gt;
&lt;p&gt;왜 기존의 &lt;code&gt;http&lt;/code&gt; 프로토콜을 사용하지 않느냐 하면,&lt;/p&gt;
&lt;p&gt;우리가 작성해야 서버로 보내는 정보들이 전부 평문으로 전달되기 때문이다.&lt;/p&gt;
&lt;p&gt;로그인 form 에서 이메일과 비밀번호를 평문으로 전달했다고 가정 해 보자.&lt;/p&gt;
&lt;p&gt;갖은 방식으로 데이터를 탈취하는 스푸퍼들은 해석 할 필요도 없이 나의 계정을 탈취 할 것이다.&lt;/p&gt;
&lt;p&gt;따라서, 이러한 스푸핑을 막기 위해, 웹 사이트는 SSL 인증서를 전달하여&lt;/p&gt;
&lt;p&gt;보안성과 무결성을 챙겼다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;SSL/TLS 을 더 정확히 정의 해 보자.&lt;/h3&gt;
&lt;p&gt;SSL/TLS 는 일종의 암호화 과정 규약이라고 말할 수 있다.&lt;/p&gt;
&lt;p&gt;어떤 과정을 거치느냐면, SSL/TLS 핸드셰이크를 수행한다. 이는 TCP 핸드셰이크랑은 엄연히 다르다.&lt;/p&gt;
&lt;p&gt;TCP 의 경우, DNS 조회를 통해 IP 를 알고 나서,&lt;/p&gt;
&lt;p&gt;브라우저와 웹 서버가 이러한 TCP 핸드셰이크 과정을 거친다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;sequenceDiagram

Client-&amp;gt;&amp;gt;Server: SYN
Server-&amp;gt;&amp;gt;Client: SYN + ACK
Client-&amp;gt;&amp;gt;Server: ACK
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;양측의 서버 통신이 양호함을 확인 한 후, TLS 핸드셰이크 과정을 수행한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;sequenceDiagram

Client-&amp;gt;&amp;gt;Server: ClientHello
Server-&amp;gt;&amp;gt;Client: ServerHello + Certificate
Server-&amp;gt;&amp;gt;Client: ServerHelloDone
Client-&amp;gt;&amp;gt;Server: ClientKeyExchange + ChangeCipherSpec
Client-&amp;gt;&amp;gt;Server: Finished
Server-&amp;gt;&amp;gt;Client: ChangeCipherSpec
Server-&amp;gt;&amp;gt;Client: Finished&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;TLS 는 인터넷 통신을 위한 암호화 및 인증 프로토콜이다.&lt;/p&gt;
&lt;p&gt;TLS 핸드셰이크는, TLS 암호화를 사용하는 통신 세션을 실행하는 프로세스이다.&lt;/p&gt;
&lt;p&gt;여기서 핸드셰이크 중에, 양 측에서는 메세지를 교환하여 서로를 인식하고, 검증하며,&lt;/p&gt;
&lt;p&gt;사용할 암호화 알고리즘을 구성하고 세션 키에 합의한다. (그래서 TLS Negotiation(협상) 이라고 부르는듯.)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;위에서 표현된 &lt;code&gt;Certificate&lt;/code&gt; 는, SSL 인증서를 의미한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ClientHello 란?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이 메세지에는 클라이언트가 지원하는 TLS 버전, 암호 제품군,&lt;/p&gt;
&lt;p&gt;그리고 &amp;quot;Client Random&amp;quot; 이라고 부르는 무작위 바이트 문자열이 포함된다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ServerHello 란?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;클라이언트 헬로 메세지의 응답으로 서버의 SSL 인증서,&lt;/p&gt;
&lt;p&gt;서버에서 선택한 클라이언트와 호환되는 암호 제품군,&lt;/p&gt;
&lt;p&gt;그리고 서버 측에서 생성한 무작위 바이트 문자열 &amp;quot;Server Random&amp;quot; 을 보낸다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;ServerHelloDone 이후,&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;클라이언트는 서버로부터 받은 SSL 인증서를 CA (인증서 발행 기관) 을 통해 검증한다.&lt;/p&gt;
&lt;p&gt;인증서를 검증하는 과정에서, 서버가 실제로 인증서에 명시된 서버인지,&lt;/p&gt;
&lt;p&gt;그리고 클라이언트가 상호작용 중인 서버가 실제 해당 도메인의 소유자인지 확인한다.&lt;/p&gt;
&lt;p&gt;맞다면, 이제 브라우저에서 경고문 없이 &lt;code&gt;https&lt;/code&gt; 프로토콜로 통신할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;세션 키와 예비 마스터 키 생성 및 대칭화 과정은 넣지 않았다.&lt;/p&gt;
&lt;p&gt;더 정확한 정보를 알고 싶다면,&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.cloudflare.com/ko-kr/learning/ssl/what-happens-in-a-tls-handshake/&quot;&gt;https://www.cloudflare.com/ko-kr/learning/ssl/what-happens-in-a-tls-handshake/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;여기에 방문하면 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;SSL 인증서는 무엇을 포함하고, 어떤 역할을 하는가?&lt;/h2&gt;
&lt;p&gt;이제 SSL/TLS 암호화 과정을 알게 되었으니, 중간에서 전달되는 Certificate 인 SSL 인증서가&lt;/p&gt;
&lt;p&gt;어떤 역할을 하는지 알아봐도 좋다고 생각한다.&lt;/p&gt;
&lt;p&gt;SSL 인증서는 단일 데이터 파일에 이러한 정보들을 포함한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;인증서가 발급된 대상 도메인 이름&lt;/li&gt;
&lt;li&gt;발급 받은 사람 혹은 조직(회사) 혹은 장치&lt;/li&gt;
&lt;li&gt;발급해준 인증 기관(CA)&lt;/li&gt;
&lt;li&gt;인증 기관의 디지털 서명&lt;/li&gt;
&lt;li&gt;관련 하위 도메인&lt;/li&gt;
&lt;li&gt;인증서 발급 날짜&lt;/li&gt;
&lt;li&gt;인증서 만료 날짜&lt;/li&gt;
&lt;li&gt;공개 키 (개인 키는 비밀)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;만약에 SSL/TLS 인증서가 제대로 효력을 발휘하지 않는다면,&lt;/p&gt;
&lt;p&gt;브라우저에 &amp;quot;안전하지 않음&amp;quot; 이라는 경고문이 뜨며 사용자가 알아차릴 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;SSL 인증서 받는 법&lt;/h3&gt;
&lt;p&gt;나는 처음 도메인을 발급받을 때, 도메인 지정과 함께 SSL 인증서도 함께 받는 줄 알았다.&lt;/p&gt;
&lt;p&gt;그런데, 그것이 아니고, 도메인 발급 후 해당 도메인으로 SSL 인증서를 발급해야 했다.&lt;/p&gt;
&lt;p&gt;여기서, 클라이언트 브라우저에서 신뢰받는 웹으로 뜨고 싶다면, 인증 기관(CA) 에서 SSL 인증서를 발급받아야 한다.&lt;/p&gt;
&lt;p&gt;물론, 도메인에도 수수료를 부과받지만, SSL 인증서에도 수수료가 부과된다는 점이다..&lt;/p&gt;
&lt;p&gt;SSL 인증서가 발급되었다면, 이는 실제 웹 서버에 설치하고 활성화 시켜야 한다.&lt;/p&gt;
&lt;p&gt;이러한 인증서는 &lt;code&gt;httpd&lt;/code&gt; 나, &lt;code&gt;nginx&lt;/code&gt; 와 같은 서버에서 인식하게 만들 수 있으며,&lt;/p&gt;
&lt;p&gt;각 웹 서버는 컨벤션 된 위치나 공식 문서에서 말하는 위치에 배치하고,&lt;/p&gt;
&lt;p&gt;설정 파일 EX-&lt;code&gt;.conf&lt;/code&gt; 파일에서 SSL 인증서 파일의 경로를 제공하여 매칭시킬 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;무료 SSL/TLS 인증서 받는 법&lt;/h3&gt;
&lt;p&gt;나의 경우, Let&amp;#39;s Encrypt 라는 사이트에 들어가서 ssl 인증서를 받는 법을 알게 되었다.&lt;/p&gt;
&lt;p&gt;이 사이트에서는 무료로 ssl 을 얻을 수 있는 프로그램을 제공했는데, 이는 파이썬 기반이었던 것으로 기억한다.&lt;/p&gt;
&lt;p&gt;무료로 받은 인증서에 대한 만료 기간을 설정하고, 또한 언제 또 새로 갱신할지에 대해서도 지정할 수 있었다.&lt;/p&gt;
&lt;p&gt;아마 그 때 당시 서버의 cron job 을 통해 정각에 새로운 ssl 인증서를 받도록 설정했던 것으로 기억한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;혹은, 내가 현재 읽고 있는 Cloudflare 공식 문서에서도 자사의 ssl 인증서를 무료로 제공한다고 말한다.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://developers.cloudflare.com/ssl/edge-certificates/universal-ssl/?_gl=1*vqlxna*_gcl_aw*R0NMLjE3NDY5Njg3NzcuQ2owS0NRandsWUhCQmhEOUFSSXNBTFJ1MDlxSWluZ0tVdWtRQmcxaTh5R1VRd3JCbVdjNVpwUzVjcHdxM0F1YzNsNmxwS2M3OGFuN3FYd2FBbi1URUFMd193Y0I.*_gcl_dc*R0NMLjE3NDY5Njg3NzcuQ2owS0NRandsWUhCQmhEOUFSSXNBTFJ1MDlxSWluZ0tVdWtRQmcxaTh5R1VRd3JCbVdjNVpwUzVjcHdxM0F1YzNsNmxwS2M3OGFuN3FYd2FBbi1URUFMd193Y0I.*_gcl_au*NjkzODk5NjU5LjE3NDY2MjY5Nzc.*_ga*ZjQxMTYxYTUtMDVkMi00Nzc1LTllYTAtMWY2ZmE1YTg2NDE4*_ga_SQCRB0TXZW*czE3NDc1NzU2MjIkbzkkZzEkdDE3NDc1NzkwMDgkajYwJGwwJGgwJGRGeFoxUm95MEhTSGxBaElLVTlmZUNUeHpFQzlobkpkRFVn&quot;&gt;Cloudflare 사이트의 Universal SSL 공식 문서&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;이번 글을 작성하면서 배운 점&lt;/h2&gt;
&lt;p&gt;간단히 웹을 배포해 주는 프로그램이나 웹 사이트는 내부적으로 도메인과 ssl 인증서를 자동으로 보급 해 준다.&lt;/p&gt;
&lt;p&gt;그러나, 이러한 무료 사이트는 특정 트래픽 이상, 혹은 특정 프로젝트 수 이상 을 달성한다면,&lt;/p&gt;
&lt;p&gt;그 때 부터 요금이 붙기 시작한다.&lt;/p&gt;
&lt;p&gt;물론, 개발 과정과 특정 스타트업의 성장 과정에서 이렇게 편리한 프로그램을 사용하는 것은,&lt;/p&gt;
&lt;p&gt;개발 비용과 시간을 줄이는 것이라 매우 유용한 것이 사실이다.&lt;/p&gt;
&lt;p&gt;나 또한, 웹을 프로덕션으로 놓는다면,&lt;/p&gt;
&lt;p&gt;Github Pages 혹은 Vercel, Netlify 와 같은 정적 웹 페이지 serving 프로그램을 사용할 것이다.&lt;/p&gt;
&lt;p&gt;개발 과정에서는 무료라고 봐도 무방하기 때문이다.&lt;/p&gt;
&lt;p&gt;그러나, 실제 프로덕션 상황에 직면하고 보안이 더 중요해 질 때,&lt;/p&gt;
&lt;p&gt;도메인 이름을 지정하고, 상황에 맞는 SSL 인증서를 발급하는 방법을 아는 것이 매우 중요하다고 생각된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;요약하자면&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;웹 페이지를 개발하는 과정에서 편리한 사이트와 프로그램을 이용하여&lt;/p&gt;
&lt;p&gt;클라이언트에게 페이지를 쉽게 서빙하더라도,&lt;/p&gt;
&lt;p&gt;도메인 이름 커스텀과 그 적용 과정에서는 위와 같은 정보가 필요하다고 생각한다.&lt;/p&gt;
&lt;p&gt;특히, 개발을 편하게 해 주는 서비스들은, 우리가 설정하지 않았으며,&lt;/p&gt;
&lt;p&gt;모르는 과정을 인질잡아 언제 요금을 높일지 모르기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;h2&gt;참고 사이트&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;한국인터넷정보센터(KRNIC) 의 &amp;quot;도메인이란?&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://xn--3e0bx5euxnjje69i70af08bea817g.xn--3e0b707e/jsp/resources/domainInfo/domainInfo.jsp&quot;&gt;https://xn--3e0bx5euxnjje69i70af08bea817g.xn--3e0b707e/jsp/resources/domainInfo/domainInfo.jsp&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;위키백과 (IP 주소)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.wikipedia.org/wiki/IP_%EC%A3%BC%EC%86%8C&quot;&gt;https://ko.wikipedia.org/wiki/IP_%EC%A3%BC%EC%86%8C&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;위키백과 (IPv4)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.wikipedia.org/wiki/IPv4&quot;&gt;https://ko.wikipedia.org/wiki/IPv4&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;위키백과 (IPv6)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.wikipedia.org/wiki/IPv6&quot;&gt;https://ko.wikipedia.org/wiki/IPv6&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Cloudfalre (DNS 란 무엇입니까?)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.cloudflare.com/ko-kr/learning/dns/what-is-dns/&quot;&gt;https://www.cloudflare.com/ko-kr/learning/dns/what-is-dns/&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Cloudflare (SSL 인증서란 무엇입니까?)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.cloudflare.com/ko-kr/learning/ssl/what-is-an-ssl-certificate/&quot;&gt;https://www.cloudflare.com/ko-kr/learning/ssl/what-is-an-ssl-certificate/&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Cloudflare (TLS 핸드셰이크의 원리는 무엇일까요?)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.cloudflare.com/ko-kr/learning/ssl/what-happens-in-a-tls-handshake/&quot;&gt;https://www.cloudflare.com/ko-kr/learning/ssl/what-happens-in-a-tls-handshake/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;</description>
      <category>잡다 지식</category>
      <category>Domain</category>
      <category>Handshake</category>
      <category>SSL</category>
      <category>ssl 무료 인증서</category>
      <category>SSL 인증서</category>
      <category>tls</category>
      <category>도메인</category>
      <category>도메인 서버</category>
      <category>도메인 이름</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/215</guid>
      <comments>https://codecreature.tistory.com/215#entry215comment</comments>
      <pubDate>Mon, 19 May 2025 00:06:26 +0900</pubDate>
    </item>
    <item>
      <title>Webpack 이라는 번들러는 무엇일까?</title>
      <link>https://codecreature.tistory.com/214</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Result1.png&quot; data-origin-width=&quot;344&quot; data-origin-height=&quot;68&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6tcMB/btsN2QugIaf/044QBKK8Ym0hYCFyCLiW7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6tcMB/btsN2QugIaf/044QBKK8Ym0hYCFyCLiW7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6tcMB/btsN2QugIaf/044QBKK8Ym0hYCFyCLiW7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6tcMB%2FbtsN2QugIaf%2F044QBKK8Ym0hYCFyCLiW7k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;344&quot; height=&quot;68&quot; data-filename=&quot;Result1.png&quot; data-origin-width=&quot;344&quot; data-origin-height=&quot;68&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Result2.png&quot; data-origin-width=&quot;354&quot; data-origin-height=&quot;62&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNcXNs/btsN1RtUtLU/tBKAiR4pnbLUvAcTPyKb5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNcXNs/btsN1RtUtLU/tBKAiR4pnbLUvAcTPyKb5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNcXNs/btsN1RtUtLU/tBKAiR4pnbLUvAcTPyKb5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcNcXNs%2FbtsN1RtUtLU%2FtBKAiR4pnbLUvAcTPyKb5k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;354&quot; height=&quot;62&quot; data-filename=&quot;Result2.png&quot; data-origin-width=&quot;354&quot; data-origin-height=&quot;62&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Result3.png&quot; data-origin-width=&quot;740&quot; data-origin-height=&quot;66&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/V8ueH/btsN1fCh8il/6TiBzOLtNSz2tGNXEvY1eK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/V8ueH/btsN1fCh8il/6TiBzOLtNSz2tGNXEvY1eK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/V8ueH/btsN1fCh8il/6TiBzOLtNSz2tGNXEvY1eK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FV8ueH%2FbtsN1fCh8il%2F6TiBzOLtNSz2tGNXEvY1eK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;740&quot; height=&quot;66&quot; data-filename=&quot;Result3.png&quot; data-origin-width=&quot;740&quot; data-origin-height=&quot;66&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Result4.png&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ln2i4/btsN1ZrY00i/f9e5YGGC4ixWNgKOHKTF11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ln2i4/btsN1ZrY00i/f9e5YGGC4ixWNgKOHKTF11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ln2i4/btsN1ZrY00i/f9e5YGGC4ixWNgKOHKTF11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLn2i4%2FbtsN1ZrY00i%2Ff9e5YGGC4ixWNgKOHKTF11%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1284&quot; height=&quot;238&quot; data-filename=&quot;Result4.png&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;238&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Result5.png&quot; data-origin-width=&quot;472&quot; data-origin-height=&quot;68&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cSc0VG/btsN2eCoq7q/mBymdsZsv6AkFhmSx503KK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSc0VG/btsN2eCoq7q/mBymdsZsv6AkFhmSx503KK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSc0VG/btsN2eCoq7q/mBymdsZsv6AkFhmSx503KK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcSc0VG%2FbtsN2eCoq7q%2FmBymdsZsv6AkFhmSx503KK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;472&quot; height=&quot;68&quot; data-filename=&quot;Result5.png&quot; data-origin-width=&quot;472&quot; data-origin-height=&quot;68&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Result6.png&quot; data-origin-width=&quot;466&quot; data-origin-height=&quot;64&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ApzF7/btsN1hmyGWL/kyWzRNi1VRkfAvH7XtZb10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ApzF7/btsN1hmyGWL/kyWzRNi1VRkfAvH7XtZb10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ApzF7/btsN1hmyGWL/kyWzRNi1VRkfAvH7XtZb10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FApzF7%2FbtsN1hmyGWL%2FkyWzRNi1VRkfAvH7XtZb10%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;466&quot; height=&quot;64&quot; data-filename=&quot;Result6.png&quot; data-origin-width=&quot;466&quot; data-origin-height=&quot;64&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2&gt;제목 : 번들러와 Webpack&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;번들러에 대한 내용을 다루는 이유&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;웹 사이트 제작은 수많은 라이브러리와 프레임워크로 이루어 질 수 있다.&lt;/p&gt;
&lt;p&gt;특히, 기존 웹 사이트 개발에 사용하던 단순 JavaScript, CSS 의 단점을 상쇄시킬 수 있는&lt;/p&gt;
&lt;p&gt;여러 확장자 (EX - &lt;code&gt;.sass&lt;/code&gt;, &lt;code&gt;.scss&lt;/code&gt;, &lt;code&gt;.jsx&lt;/code&gt;, &lt;code&gt;.tsx&lt;/code&gt; 등등) 가 탄생했으며,&lt;/p&gt;
&lt;p&gt;TypeScript 의 적용으로 이 또한 JavaScript 로 변환해야 할 필요가 있다.&lt;/p&gt;
&lt;p&gt;특히, 가장 중요한 것은 이러한 모듈들 간의 순서, 의존성이다.&lt;/p&gt;
&lt;p&gt;우리는 현재 JSX, TSX 에서 &lt;code&gt;scss&lt;/code&gt; 파일을 &lt;code&gt;import&lt;/code&gt; 해오기도 하고,&lt;/p&gt;
&lt;p&gt;작성된 컴포넌트들이 특정 컴포넌트를 가져오기 때문에, 이는 의존성이라고 부를 수 있다.&lt;/p&gt;
&lt;p&gt;그렇다면, 엔트리 파일(시작 파일) 인 &lt;code&gt;index.html&lt;/code&gt; 에 작성되어야 할 JavaScript 파일의 순서도&lt;/p&gt;
&lt;p&gt;매우 중요 할 것이다.&lt;/p&gt;
&lt;p&gt;또한, JSX, TSX 파일은 결국 JS 로 파싱되는데, 이 파일에서 &lt;code&gt;import&lt;/code&gt; 해온&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sass&lt;/code&gt;, &lt;code&gt;scss&lt;/code&gt; 와 같은 파일들도 결국 &lt;code&gt;css&lt;/code&gt; 와 &lt;code&gt;js&lt;/code&gt; 로 순서 의존성을 해결 해 줄 필요가 있다.&lt;/p&gt;
&lt;p&gt;또한, Node.js 에서 사용하는 방식인 &lt;code&gt;commonjs&lt;/code&gt; 모듈 임포트의 방식 또한 변환 해 줄 필요가 있다.&lt;/p&gt;
&lt;p&gt;개발 편의성을 갖추기 위해 모듈식 코드 작성이 보편화 되었지만,&lt;/p&gt;
&lt;p&gt;정작 이 파일이 어떻게 변환되는지는 공부하지 않은 나 자신을 반성한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이러한 역할을 해 주는 JS 번들러는 여러 프로그램이 존재한다.&lt;/p&gt;
&lt;p&gt;Webpack, Parcel, esbuild, Vite, SWC, Turbopack 등등이 있다.&lt;/p&gt;
&lt;p&gt;이 중, React 에서 흔히 사용되며, 거대한 커뮤니티를 가진 Webpack 을 중심으로 공부하려고 한다.&lt;/p&gt;
&lt;p&gt;Webpack 이 거대한 커뮤니티를 가진 만큼, 다른 번들러도 비슷한 개념을 가지고 있을 것이라 생각하기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Webpack 의 컨셉&lt;/h2&gt;
&lt;p&gt;웹팩은 Modern JS 어플리케이션을 위한 &amp;quot;정적 모듈 번들러&amp;quot; 이다.&lt;/p&gt;
&lt;p&gt;webpack 이 Application 을 처리 할 때,&lt;/p&gt;
&lt;p&gt;내부적으로 프로젝트에 필요한 모든 모듈을 매핑하고, 하나 이상의 번들을 생성하는&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;Dependency Graph&amp;quot;&lt;/strong&gt; (의존성 그래프) 를 그린다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;의존성 그래프란&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;어떤 파일이 다른 파일에 의존(&lt;code&gt;require&lt;/code&gt; or &lt;code&gt;import&lt;/code&gt;) 할 때, webpack 은 이를 의존성으로 취급한다.&lt;/li&gt;
&lt;li&gt;webpack 은 img 또는 web font 와 같은 코드가 아닌 Asset 을 가져와서, 의존성으로 제공 할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;webpack 의 컨셉을 이해하기 위해서는 특정 핵심 개념들을 이해해야 한다고 공식문서에 작성되어 있다.&lt;/p&gt;
&lt;p&gt;따라서, 문서에서 제공하는 핵심 개념들을 차례로 다뤄보자.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Entry (엔트리)&lt;/li&gt;
&lt;li&gt;Output (출력물)&lt;/li&gt;
&lt;li&gt;Loaders (로더)&lt;/li&gt;
&lt;li&gt;Plugins (플러그인)&lt;/li&gt;
&lt;li&gt;Mode (모드)&lt;/li&gt;
&lt;li&gt;Browser Compatibility (브라우저 호환성)&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;h3&gt;Entry (엔트리) 란?&lt;/h3&gt;
&lt;p&gt;엔트리 포인트란, webpack 이 내부 Dependency Graph 를 생성하기 위해 사용해야 하는 모듈이다.&lt;/p&gt;
&lt;p&gt;Webpack 은 엔트리 포인트가 의존하는 다른 모듈과 라이브러리를 찾아낸다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;이게 무슨 말인가?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;의존성 그래프를 생성하기 위해 사용해야 하는 모듈을 엔트리 포인트로 등록해야 한다니,&lt;/p&gt;
&lt;p&gt;이것이 무엇을 의미하는지 헷갈렸다.&lt;/p&gt;
&lt;p&gt;따라서 나는 이전에 제작했던 테스트용 React + TS 를 켰고,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ npm run eject&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;를 통해 react 내장 webpack 에서 외장으로 꺼내 직접적으로 볼 수 있게 만들었다.&lt;/p&gt;
&lt;p&gt;먼저 말하자면, React + TS 의 엔트리 포인트는 &lt;code&gt;index.tsx&lt;/code&gt; 이다.&lt;/p&gt;
&lt;p&gt;물론, JS 환경이라면 &lt;code&gt;index.jsx&lt;/code&gt; 가 될 수도, &lt;code&gt;index.js&lt;/code&gt; 가 될 수도 있다.&lt;/p&gt;
&lt;p&gt;React 에 내장되어 있는 webpack config 는 이미 수많은 코드로 구성되어 있었는데,&lt;/p&gt;
&lt;p&gt;여기서 entry 에 대한 내용을 찾아 본 결과, &lt;code&gt;index.tsx&lt;/code&gt; 를 가져오고 있었다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;그렇다면, 왜 &lt;code&gt;index.tsx&lt;/code&gt; 인가?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;우리가 React 에서 만든 컴포넌트는 모두 &lt;code&gt;index.tsx&lt;/code&gt; 로 모인다.&lt;/p&gt;
&lt;p&gt;역으로 말하자면, &lt;code&gt;index.tsx&lt;/code&gt; 에서 시작한 파일들은 서로를 부르고 호출하는 과정에 따라,&lt;/p&gt;
&lt;p&gt;결국 컴포넌트, 유틸 함수 등등 여러 파일에 대한 의존성 그래프를 만들 수 있다는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;index.tsx&lt;/code&gt; 는 &lt;code&gt;App.tsx&lt;/code&gt; 를 부르고, &lt;code&gt;App.tsx&lt;/code&gt; 는 또 다른 컴포넌트를 불러온다.&lt;/p&gt;
&lt;p&gt;이 때, &lt;code&gt;index.tsx&lt;/code&gt; 는 &lt;code&gt;App.tsx&lt;/code&gt; 에 대한 의존성을 가지고 있다고 볼 수 있다.&lt;/p&gt;
&lt;p&gt;Webpack 은 &lt;code&gt;index.tsx&lt;/code&gt; 를 엔트리 포인트로 삼아 연결된 &amp;quot;모든&amp;quot; 의존성을 파악하는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시:&lt;/strong&gt; - &lt;code&gt;webpack.config.js&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;module.exports = {
  entry: &amp;quot;./src/index.tsx&amp;quot;
}

// 위의 구문은 밑의 코드가 축약 된 형태라고 한다.
module.exports = {
  entry : {
    main : &amp;quot;./src/index.tsx&amp;quot;
  }
}

// 혹은, 여러 개의 메인 엔트리가 존재하는 경우 배열로 제공 할 수도 있다.
module.exports = {
  entry : [&amp;quot;./src/index.tsx&amp;quot;, &amp;quot;./src/index2.tsx&amp;quot;, ...],
  // 여러 의존성 파일이 주입되고, 하나의 파일로 번들링한다. (청크 - chunk)
  output : {
    filename : &amp;quot;newBundle.js&amp;quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위에서 사용한 &lt;code&gt;entry&lt;/code&gt; 는 &amp;quot;단일 엔트리 구문&amp;quot; 이라고 한다.&lt;/p&gt;
&lt;p&gt;하나의 엔트리 포인트만 존재한다면, 애플리케이션과 도구에 대해 설정을 빠르게 할 수 있으나,&lt;/p&gt;
&lt;p&gt;추후 설정을 확장 할 수 있는 유연성이 떨어지게 된다고 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;엔트리 포인트 설명 객체 (EntryDescription Object)&lt;/h3&gt;
&lt;p&gt;엔트리 포인트 객체에서 의존성 확립과 경로 설정에 대한 지정 옵션들이 존재하는데,&lt;/p&gt;
&lt;p&gt;내가 직접 React 에서 &lt;code&gt;npm run eject&lt;/code&gt; 후 본 옵션들이 있어&lt;/p&gt;
&lt;p&gt;알아놔야 할 것 같다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;dependOn&lt;/code&gt; : 현재 엔트리 포인트가 의존하는 또 다른 엔트리 포인트. &lt;br/&gt; 해당 엔트리 포인트는 먼저 로드해야 한다. (위에 쓰자)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;filename&lt;/code&gt; : 디스크에 존재하는 출력 파일의 이름을 지정한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;import&lt;/code&gt; : 시작 시 로드되는 모듈이다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;library&lt;/code&gt; : 현재 엔트리에서 라이브러리 번들링 시 라이브러리 옵션을 지정.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;runtime&lt;/code&gt; : 런타임 청크의 이름이다. 설정 시 해당 이름의 런타임 청크가 생성되며, &lt;br/&gt; 설정되지 않는다면 기존 엔트리 포인트의 이름이 사용된다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;publicPath&lt;/code&gt; : 브라우저에서 참조 시, 이 엔트리의 출력 파일에 대한 공용 URL 주소를 지정한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;유연성 확장과 의존성 설정&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;위에서 제시한 방식은 &lt;code&gt;entry&lt;/code&gt; 를 단일 문자열, 혹은 배열로 사용했다.&lt;/p&gt;
&lt;p&gt;공식문서에서 말하는 가장 확장 가능한 방식이란, 객체처럼 사용하는 것이다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// 여러 엔트리를 확장 가능한 가장 유연한 방식이며, 엔트리끼리 참조가 가능.
module.exports = {
  entry : {
    app : &amp;quot;./src/app.js&amp;quot;,
    adminApp : &amp;quot;./src/adminApp.js&amp;quot;
  }
}

// 특정 엔트리가 의존성을 가지는 경우
module.exports = {
  entry : {
    mainDepend : &amp;quot;mainDependFile.js&amp;quot;,
    app : {
      dependOn : &amp;quot;mainDepend&amp;quot;,
      import : &amp;quot;./src/app.js&amp;quot;
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;React 의 엔트리 포인트는 위의 예시처럼 정확히 표기되지 않았는데,&lt;/p&gt;
&lt;p&gt;다른 확장자를 대비하여 js 에 관련된 모든 확장자를 표기 해 놓고,&lt;/p&gt;
&lt;p&gt;해당 확장자의 &lt;code&gt;index.&amp;lt;확장자&amp;gt;&lt;/code&gt; 존재 시, 해당 파일을 엔트리 포인트로 삼을 수 있게 해 두었다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Output (출력) 이란?&lt;/h3&gt;
&lt;p&gt;이 속성은 생성된 번들을 내보낼 위치, 그리고 파일의 이름을 지정하는 방법을 webpack 에서 알려준다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;기본 출력&amp;quot;&lt;/strong&gt; 파일의 경우 &lt;code&gt;./dist/main.js&lt;/code&gt; 로,&lt;/p&gt;
&lt;p&gt;생성된 기타 파일의 경우 &lt;code&gt;./dist&lt;/code&gt; 폴더로 설정된다.&lt;/p&gt;
&lt;p&gt;공식 문서에서 좋은 예제를 내 주었는데, 이를 활용해서 작성 해 보자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const path = require(&amp;quot;path&amp;quot;);

module.exports = {
  entry : &amp;quot;./src/index.tsx&amp;quot;,
  output : {
    path : path.resolve(__dirname, &amp;quot;dist&amp;quot;),
    filename : &amp;quot;static/js/bundle.js&amp;quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;웹팩의 동작은 Node.js 환경에서 이루어지기 때문에, &lt;br/&gt;&lt;br&gt;commonjs 방식으로 모듈을 가져온다. - &lt;code&gt;require&lt;/code&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;위의 예제는 내가 만약에 &lt;code&gt;webpack.config.js&lt;/code&gt; 를 직접 작성했을 때,&lt;/p&gt;
&lt;p&gt;컴포넌트 구성 JS 에 대한 영역이 &lt;code&gt;/dist/static/js/bundle.js&lt;/code&gt; 에 위치하도록 만들었다.&lt;/p&gt;
&lt;p&gt;물론, 이는 테스트 상황을 의미하고, 만약에 프로덕션이라면 해싱 기능을 넣어야 한다.&lt;/p&gt;
&lt;p&gt;그리고 &lt;code&gt;path&lt;/code&gt; 라이브러리는 매우 중요한데, 해당 라이브러리가 있어야 &lt;code&gt;__dirname&lt;/code&gt; 과 같이,&lt;/p&gt;
&lt;p&gt;현재 실행 디렉토리를 가져올 수 있다.&lt;/p&gt;
&lt;p&gt;이 외에도 &lt;code&gt;output&lt;/code&gt; 속성은 매우 많은 기능을 가지고 있는데, 이는 밑의 사이트에서 참조하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://webpack.kr/configuration/output&quot;&gt;https://webpack.kr/configuration/output&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Loaders (로더) 란?&lt;/h3&gt;
&lt;p&gt;Loader 는 &lt;code&gt;module.exports&lt;/code&gt; 에 직접적으로 바로 선언되는 형식은 아니고,&lt;/p&gt;
&lt;p&gt;먼저 &lt;code&gt;module&lt;/code&gt; 이라는 최상위 옵션을 두고,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;rules&lt;/code&gt; 옵션을 &lt;code&gt;module&lt;/code&gt; 내에 넣어 배열로 관리한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;왜 로더 라는 개념을 만들어서 규칙을 통해 선언하나 싶었는데,&lt;/p&gt;
&lt;p&gt;생각해 보니 애초에 webpack 은 JavaScript 와 JSON 파일만 이해한다고 한다.&lt;/p&gt;
&lt;p&gt;webpack 이 다른 유형의 파일을 처리하거나 모듈로 변환하기 위해서는,&lt;/p&gt;
&lt;p&gt;이를 JS 혹은 JSON 으로 바꿔주는 특정 모듈이 필요하다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;예를 들어, webpack 에서는 의존성 그래프를 만들기 위해 JS 로 인식한다.&lt;/p&gt;
&lt;p&gt;이를 위해서, 각각의 확장자는 특정 loader 패키지를 가져와 JS 모듈로 만들어 버린다.&lt;/p&gt;
&lt;p&gt;예를 들어, &lt;code&gt;css&lt;/code&gt;, &lt;code&gt;ts&lt;/code&gt; 에 대한 의존성 그래프를 형성하기 위해,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;webpack.config.js&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;module.exports = {
  module : {
    rules : [
      { test : /\.css$/, use : &amp;quot;css-loader&amp;quot; },
      { test : /\.ts$/, use : &amp;quot;ts-loader&amp;quot; }
    ]
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;먼저, &lt;code&gt;webpack.config.js&lt;/code&gt; 에서도 npm 패키지로 위에 선언된 2 개의 패키지가 설치되어야 한다.&lt;/p&gt;
&lt;p&gt;이 때, 개발 의존성으로 &lt;code&gt;css-loader&lt;/code&gt;, &lt;code&gt;ts-loader&lt;/code&gt; 를 설치한다.&lt;/p&gt;
&lt;p&gt;만약에 React 와 같이 템플릿이 만들어져 있는 경우, 이러한 로더들은 이미 npm 모듈로 설치되어 있다.&lt;/p&gt;
&lt;p&gt;따라서, 로더는 이 2 개의 속성에 중점을 둔다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;test&lt;/code&gt; : 변환이 필요한 파일들을 식별하는 속성 (&lt;strong&gt;Regex 사용&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;use&lt;/code&gt; : 변환을 수행하는 데 사용되는 로더를 가르키는 속성&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;h3&gt;Plugins (플러그인) 이란?&lt;/h3&gt;
&lt;p&gt;나는 webpack 에서 플러그인 이라는 단어를 듣고 조금 난해하다고 생각했다.&lt;/p&gt;
&lt;p&gt;개발 환경에서 생겨난 파일 의존성과 출력물을 생성하는 과정에서,&lt;/p&gt;
&lt;p&gt;어떤 플러그인이 필요한 것인가?&lt;/p&gt;
&lt;p&gt;webpack 에는 플러그인을 활용하여 번들을 최적화하거나,&lt;/p&gt;
&lt;p&gt;정적 에셋들을 관리하고, 환경변수 주입과 같은 광범위한 작업을 수행 할 수 있다고 한다.&lt;/p&gt;
&lt;p&gt;즉, webpack 에서의 플러그인은 기존 작업에서의 추가 옵션과 비슷한 의미인 것이다.&lt;/p&gt;
&lt;p&gt;이 옵션은 다양한 객체를 &lt;code&gt;new HtmlWebpackPlugin(...)&lt;/code&gt; 과 같은 형식으로 추가한다.&lt;/p&gt;
&lt;p&gt;React 에서도 위의 옵션을 사용하는데, 이는 생성된 모든 번들을 삽입하여&lt;/p&gt;
&lt;p&gt;어플리케이션 전용 HTML 파일을 생성한다는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Mode 란?&lt;/h3&gt;
&lt;p&gt;Webpack 에는 내장된 환경 변수가 있는데, 바로 &lt;code&gt;mode&lt;/code&gt; 로 설정할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;development&lt;/code&gt;, &lt;code&gt;production&lt;/code&gt;, &lt;code&gt;none&lt;/code&gt; 이 있다.&lt;/p&gt;
&lt;p&gt;webpack 에서 주는 모드에 따라 우리는 설정을 동적으로 설정할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Browser Compatibility (브라우저 호환성)&lt;/h3&gt;
&lt;p&gt;만약에 구형 브라우저를 지원하려면, Polyfill 을 지원해야 할 수도 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Webpack 핵심 결과를 요약하자면,&lt;/h3&gt;
&lt;p&gt;웹 어플리케이션을 제작하기 위해 다양한 확장자, 외부 모듈인 npm,&lt;/p&gt;
&lt;p&gt;심지어는 TypeScript, JSX, TSX, sass, scss 등등 여러 변환이 필요하다.&lt;/p&gt;
&lt;p&gt;웹을 제작하기 편하게, 혹은 기능을 더 추가하기 위해 여러 파일과 모듈이 등장했는데,&lt;/p&gt;
&lt;p&gt;웹에서 간단하게 사용하던 JavaScript 의 파일이 서로를 의존함에 따라,&lt;/p&gt;
&lt;p&gt;의존성을 고려하지 않으면 웹에서 JS 파일이 제대로 실행되지 않는 상황이 발생한다.&lt;/p&gt;
&lt;p&gt;이러한 의존성을 고려하여 웹 페이지에 의존성을 제대로 로드할 수 있는 순서대로 기입함과 동시에,&lt;/p&gt;
&lt;p&gt;웹 사이트가 로드할 수 있는 js, css, html 로만 변환 해 준다.&lt;/p&gt;
&lt;p&gt;이 때, 여러 컴포넌트로 나뉘어 있거나, 유틸로 작성되어 있는 js,&lt;/p&gt;
&lt;p&gt;각 페이지의 스타일링을 위해 나뉘어 있는 scss, sass 를 css 로 만든다.&lt;/p&gt;
&lt;p&gt;아주 간단히 얘기하자면, 개발 상황 --&amp;gt; 웹 페이지 로드 가능 이다.&lt;/p&gt;
&lt;p&gt;Webpack 은 리액트에 내장되어 있는데, 이는 개발 환경에서 곧바로 소스코드 변환은 인지하여&lt;/p&gt;
&lt;p&gt;해당 부분만 변경시켜 렌더링하기도 하고, 개발 상황과 제품 상황에 나뉘어 환경 변수를 주입하기도 한다.&lt;/p&gt;
&lt;p&gt;이러한 역할을 집약하여 정의하자면, Bundler (번들러) 라고 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Webpack 실습 In JavaScript&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;참고 공식문서&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://webpack.kr/guides/getting-started/&quot;&gt;https://webpack.kr/guides/getting-started/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Webpack 의 컨셉 문서와 공식문서들을 통해 이것이 왜 사용되는지 알았지만,&lt;/p&gt;
&lt;p&gt;사용법은 알지 못한다. 대부분의 경우, &lt;code&gt;react-scripts&lt;/code&gt; 와 같은 편의성 스크립트 덕분에&lt;/p&gt;
&lt;p&gt;딱히 번들링을 고려해야 할 필요는 없었기 때문이다.&lt;/p&gt;
&lt;p&gt;그런데, TOAST UI 에서 고려해야 할 이유를 찾았다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;기본적인 리액트 환경에서는 크게 최적화의 필요성을 느끼기가 힘들다.&lt;/p&gt;
&lt;p&gt;그도 그럴 것이, 초창기에는 작성한 파일의 수, 외부 모듈의 수가 적기 때문이다.&lt;/p&gt;
&lt;p&gt;그러나, React 에 빌드된 목록을 보니, JavaScript 청크 1개, css 청크 1 개를 불러오고 있었다.&lt;/p&gt;
&lt;p&gt;만약에 내가 이 프로젝트에서 로드되기까지 시간이 조금 걸리는 무거운 모듈을 추가하게 된다면,&lt;/p&gt;
&lt;p&gt;브라우저에서는 단 1 개의 청크를 로드하기 위해 시간이 걸린다는 의미였다.&lt;/p&gt;
&lt;p&gt;따라서, 가벼운 서비스 chunk 1 개, 무거운 모듈 chunk 1개로 나누어도 좋겠다는 생각을 했다.&lt;/p&gt;
&lt;p&gt;이러한 생각에 이어 직접 webpack 프로젝트를 간단히 만들어 보기로 결정했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;기본 셋업&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;만약에, 이 글을 보고 따라하시는 분이 있다면, &lt;code&gt;node&lt;/code&gt;, &lt;code&gt;npm&lt;/code&gt;, &lt;code&gt;git&lt;/code&gt;, &lt;code&gt;ts-node&lt;/code&gt;, &lt;code&gt;typescript&lt;/code&gt; 등등,&lt;/p&gt;
&lt;p&gt;기본 프로그램들이 다운되어 있다는 전제를 깔고 시작하겠습니다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;먼저, 프로젝트를 작성할 디렉토리를 하나 만든다. (나는 &lt;code&gt;demo-webpack-js&lt;/code&gt;)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;→ &amp;lt;상위 디렉토리&amp;gt;    $ mkdir demo-webpack-js # 프로젝트 디렉토리 생성
→ &amp;lt;....&amp;gt;          $ cd demo-webpack-js # 생성된 프로젝트 디렉토리 이동
→ demo-webpack-js $ npm init -y # 이 디렉토리의 npm 초기화 - 기본 package.json 생성

# webpack 은 &amp;quot;개발 의존성&amp;quot; 이기 때문에,
# npm i -D ... or npm i --save-dev ... 으로 다운로드 한다.
→ demo-webpack-js $ npm i -D webpack webpack-cli&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;웹 프레임워크나 라이브러리의 경우, 자사의 번들러를 가지고 있거나, webpack 을 동봉한다.&lt;/p&gt;
&lt;p&gt;그러나, 크로스플랫폼이나, 특정 모듈만을 타겟으로 제작 할 경우, webpack 을 따로 설치하여 개발 할 수 있다.&lt;/p&gt;
&lt;p&gt;이후, 프로젝트에 이러한 파일과 디렉토리를 추가한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-diff&quot;&gt;/프로젝트 디렉토리 루트
    /node_modules
    package.json
    package-lock.json
+   /dist
+       index.html
+   /src
+       index.js&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 예시는 브라우저를 타겟으로 하는 웹 애플리케이션의 경우를 말하는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;index.html&lt;/code&gt; 은 &lt;code&gt;src&lt;/code&gt; 폴더에 있는 코드를 번들링하여 결과를 브라우저에 출력한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;index.html&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
        &amp;lt;meta charset=&amp;quot;utf-8&amp;quot; /&amp;gt;
        &amp;lt;title&amp;gt;Testing JS Webpack&amp;lt;/title&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;script src=&amp;quot;main.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;그리고, JS 최적화를 위한 npm 모듈 &lt;code&gt;lodash&lt;/code&gt; 를 설치한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  webpack-demo-js npm i lodash

added 1 package, and audited 120 packages in 983ms

17 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;우리가 생성했던 &lt;code&gt;src/index.js&lt;/code&gt; 파일을 작성 해 보자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import _ from &amp;quot;lodash&amp;quot;;

function component() {
  const element = document.createElement(&amp;quot;div&amp;quot;);

  element.innerHTML = _.join([&amp;quot;Hello!&amp;quot;, &amp;quot;Webpack World!&amp;quot;], &amp;#39; &amp;#39;);

  return element;
}

document.body.appendChild(component());&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;그리고, &lt;code&gt;package.json&lt;/code&gt; 의 &lt;code&gt;scripts&lt;/code&gt; 에 새로운 문장을 넣는다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-diff&quot;&gt;&amp;quot;scripts&amp;quot; : {
    &amp;quot;test&amp;quot;: &amp;quot;echo \&amp;quot;Error: no test specified\&amp;quot; &amp;amp;&amp;amp; exit 1&amp;quot;,
+   &amp;quot;build&amp;quot; : &amp;quot;webpack&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;webpack&lt;/code&gt; 명령어가 참조 할 파일을 만들자 :&lt;/p&gt;
&lt;p&gt;&lt;code&gt;webpack.config.js&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const path = require(&amp;quot;path&amp;quot;);

module.exports = {
  entry : &amp;quot;./src/index.js&amp;quot;,
  output : {
    filename : &amp;quot;main.js&amp;quot;,
    path : path.resolve(__dirname, &amp;quot;dist&amp;quot;)
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이후, 명령어를 실행한다 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  webpack-demo-js npm run build

&amp;gt; webpack-demo@1.0.0 build
&amp;gt; webpack

asset main.js 69.5 KiB [compared for emit] [minimized] (name: main) 1 related asset
runtime modules 1010 bytes 5 modules
cacheable modules 532 KiB
  ./src/index.js 225 bytes [built] [code generated]
  ./node_modules/lodash/lodash.js 531 KiB [built] [code generated]

WARNING in configuration
The &amp;#39;mode&amp;#39; option has not been set, webpack will fallback to &amp;#39;production&amp;#39; for this value.
Set &amp;#39;mode&amp;#39; option to &amp;#39;development&amp;#39; or &amp;#39;production&amp;#39; to enable defaults for each environment.
You can also set it to &amp;#39;none&amp;#39; to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/

webpack 5.99.8 compiled with 1 warning in 1718 ms&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;이 명령어를 실행하면, 최종 폴더 계층은 이렇다 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-diff&quot;&gt;/프로젝트 디렉토리 루트
    /node_modules
    package.json
    package-lock.json
    /dist
        index.html
+       main.js
+       main.js.LICENSE.txt
    /src
        index.js
    webpack.config.js&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기서, 우리는 폴더 탐색기로, &lt;code&gt;index.html&lt;/code&gt; 을 열어보자.&lt;/p&gt;
&lt;p&gt;그렇다면, 우리는 이러한 결과를 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Result1.png&quot; data-origin-width=&quot;344&quot; data-origin-height=&quot;68&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6tcMB/btsN2QugIaf/044QBKK8Ym0hYCFyCLiW7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6tcMB/btsN2QugIaf/044QBKK8Ym0hYCFyCLiW7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6tcMB/btsN2QugIaf/044QBKK8Ym0hYCFyCLiW7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6tcMB%2FbtsN2QugIaf%2F044QBKK8Ym0hYCFyCLiW7k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;344&quot; height=&quot;68&quot; data-filename=&quot;Result1.png&quot; data-origin-width=&quot;344&quot; data-origin-height=&quot;68&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그래서, 우리가 &lt;code&gt;index.html&lt;/code&gt; 에서 사용한 &lt;code&gt;main.js&lt;/code&gt; 파일은 어떻게 되어 있을까?&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;/*! For license information please see main.js.LICENSE.txt */
(()=&amp;gt;{var n={543:function(n,t,r){var e;n=r.nmd(n),function(){var u,i=&amp;quot;Expected a function&amp;quot;,o=&amp;quot;__lodash_hash_undefined__&amp;quot;,f=&amp;quot;__lodash_placeholder__&amp;quot;,a=32,c=128,l=1/0,s=9007199254740991,h=NaN,p=4294967295,v=[[&amp;quot;ary&amp;quot;,c],[&amp;quot;bind&amp;quot;,1],[&amp;quot;bindKey&amp;quot;,2],[&amp;quot;curry&amp;quot;,8],[&amp;quot;curryRight&amp;quot;,16],[&amp;quot;flip&amp;quot;,512],[&amp;quot;partial&amp;quot;,a],[&amp;quot;partialRight&amp;quot;,64],[&amp;quot;rearg&amp;quot;,256]],_=&amp;quot;[object Arguments]&amp;quot;,g=&amp;quot;[object Array]&amp;quot;,y=&amp;quot;[object Boolean]&amp;quot;,d=&amp;quot;[object Date]&amp;quot;,b=&amp;quot;[object Error]&amp;quot;,w=&amp;quot;[object Function]&amp;quot;,m=&amp;quot;[object GeneratorFunction]&amp;quot;,x=&amp;quot;[object Map]&amp;quot;,j=&amp;quot;[object Number]&amp;quot;,A=&amp;quot;[object Object]&amp;quot;,k=&amp;quot;[object Pr.........
// 이후로 50 줄이 더 존재&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;아니, &lt;code&gt;src/index.js&lt;/code&gt; 에 작성 해 놓은 간단한 메서드 함수가 이렇게 변했다.&lt;/p&gt;
&lt;p&gt;이를 &amp;quot;난독화&amp;quot; (Uglify) 라고 부르는데, 사람이 읽을 수 없게 만드는 것이다.&lt;/p&gt;
&lt;p&gt;이러한 과정 속에서 Webpack 은 최적화도 수행한다.&lt;/p&gt;
&lt;p&gt;난독화 된 파일은 사람이 읽을 수 없을 뿐, 우리가 작성한 &lt;code&gt;index.js&lt;/code&gt; 와 동일하게 동작한다.&lt;/p&gt;
&lt;p&gt;그렇기에, 우리는 &lt;code&gt;dist/index.html&lt;/code&gt; 에서 결과물을 볼 수 있던 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Webpack 실습 With CSS&lt;/h2&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://webpack.kr/guides/asset-management/&quot;&gt;https://webpack.kr/guides/asset-management/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;그렇다면, webpack 에서 css 는 어떻게 처리할까?&lt;/p&gt;
&lt;p&gt;webpack 은 기본적으로 JavaScript, JSON 만 인식 할 수 있다.&lt;/p&gt;
&lt;p&gt;그렇다면, 다양한 확장자 파일들과 상호작용을 어떻게 하고 있을까?&lt;/p&gt;
&lt;p&gt;이는 NPM 에서 제공하는 &lt;code&gt;&amp;lt;모듈이름&amp;gt;-loader&lt;/code&gt; 모듈을 다운로드 받아 webpack 에 적용해야 한다.&lt;/p&gt;
&lt;p&gt;CSS 실습은 위에서 작업한 결과물을 기반으로 실행 해 볼 것이다.&lt;/p&gt;
&lt;p&gt;Webpack 을 통해 단순 CSS 파일을 번들링 하기 위해서는 2 개의 의존성 모듈이 필요하다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;css-loader&lt;/code&gt; - webpack 이 인식하기 위해 css 파일을 js 모듈로 변환함.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;style-loader&lt;/code&gt; - 변환된 css-js 모듈을 인식하여 &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; 내부에 &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; 태그로 css 를 넣어줌.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  webpack-demo-js $ npm i -D style-loader css-loader

added 15 packages, and audited 135 packages in 3s

21 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그리고, &lt;code&gt;src&lt;/code&gt; 폴더 내부에 넣을 &lt;code&gt;css&lt;/code&gt; 파일을 웹팩이 인식하게 만들기 위해&lt;/p&gt;
&lt;p&gt;&lt;code&gt;webpack.config.js&lt;/code&gt; 를 변경한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const path = require(&amp;quot;path&amp;quot;);

module.exports = {
  entry : &amp;quot;./src/index.js&amp;quot;,
  output : {
    filename : &amp;quot;main.js&amp;quot;,
    path : path.resolve(__dirname, &amp;quot;dist&amp;quot;)
  },
  // 하위의 코드는 모두 추가된 코드들.
  module : {
    rules : [
      {
        test : /\.css$/,
        use: [&amp;quot;style-loader&amp;quot;, &amp;quot;css-loader&amp;quot;]
      }
    ]
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그리고, &lt;code&gt;index.js&lt;/code&gt; 와 같은 위치를 가지는 &lt;code&gt;style.css&lt;/code&gt; 를 만든다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.hello {
    color : blue;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이후, &lt;code&gt;index.js&lt;/code&gt; 에 우리가 만들었던 &lt;code&gt;element&lt;/code&gt; 에 새로운 클래스 이름을 추가해준다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import _ from &amp;quot;lodash&amp;quot;;
// 새로이 만든 css 파일을 여기서 &amp;quot;import&amp;quot; 해 준다. - webpack 에게 의존성을 알려주기 위해
import &amp;quot;./style.css&amp;quot;;

function component() {
  const element = document.createElement(&amp;quot;div&amp;quot;);

  element.innerHTML = _.join([&amp;quot;Hello!&amp;quot;, &amp;quot;Webpack World!&amp;quot;], &amp;#39; &amp;#39;);
  // 엘리먼트는 이제 새로운 클래스 이름 &amp;quot;hello&amp;quot; 를 가진다.
  element.classList.add(&amp;quot;hello&amp;quot;);

  return element;
}

document.body.appendChild(component());&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;그리고 이제 번들링 해 보자 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  webpack-demo-js npm run build

&amp;gt; webpack-demo@1.0.0 build
&amp;gt; webpack

asset main.js 73.4 KiB [emitted] [minimized] (name: main) 1 related asset
runtime modules 1.02 KiB 6 modules
orphan modules 1.1 KiB [orphan] 1 module
cacheable modules 541 KiB
  modules by path ./node_modules/ 539 KiB
    modules by path ./node_modules/style-loader/dist/runtime/*.js 5.84 KiB
      ./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js 2.42 KiB [built] [code generated]
      ./node_modules/style-loader/dist/runtime/styleDomAPI.js 1.5 KiB [built] [code generated]
      + 4 modules
    modules by path ./node_modules/css-loader/dist/runtime/*.js 2.31 KiB
      ./node_modules/css-loader/dist/runtime/noSourceMaps.js 64 bytes [built] [code generated]
      ./node_modules/css-loader/dist/runtime/api.js 2.25 KiB [built] [code generated]
    ./node_modules/lodash/lodash.js 531 KiB [built] [code generated]
  modules by path ./src/ 1.82 KiB
    ./src/index.js + 1 modules 1.38 KiB [built] [code generated]
    ./node_modules/css-loader/dist/cjs.js!./src/style.css 455 bytes [built] [code generated]

WARNING in configuration
The &amp;#39;mode&amp;#39; option has not been set, webpack will fallback to &amp;#39;production&amp;#39; for this value.
Set &amp;#39;mode&amp;#39; option to &amp;#39;development&amp;#39; or &amp;#39;production&amp;#39; to enable defaults for each environment.
You can also set it to &amp;#39;none&amp;#39; to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/

webpack 5.99.8 compiled with 1 warning in 1988 ms&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이제, &lt;code&gt;dist/index.html&lt;/code&gt; 을 열어보면,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Result2.png&quot; data-origin-width=&quot;354&quot; data-origin-height=&quot;62&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNcXNs/btsN1RtUtLU/tBKAiR4pnbLUvAcTPyKb5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNcXNs/btsN1RtUtLU/tBKAiR4pnbLUvAcTPyKb5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNcXNs/btsN1RtUtLU/tBKAiR4pnbLUvAcTPyKb5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcNcXNs%2FbtsN1RtUtLU%2FtBKAiR4pnbLUvAcTPyKb5k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;354&quot; height=&quot;62&quot; data-filename=&quot;Result2.png&quot; data-origin-width=&quot;354&quot; data-origin-height=&quot;62&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;파란색으로 스타일링 된 결과를 볼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;그런데, css 파일이 따로 출력물로 나오지 않았다?&lt;/h3&gt;
&lt;p&gt;위에서 언급했다시피, 우리는 모듈로 &lt;code&gt;style-loader&lt;/code&gt; 를 사용했다.&lt;/p&gt;
&lt;p&gt;이 로더는 런타임 시, &lt;code&gt;css&lt;/code&gt; 파일을 &lt;code&gt;head&lt;/code&gt; 로 넣어주는 역할을 한다.&lt;/p&gt;
&lt;p&gt;즉, 파일로 출력하지 않고 아예 JS 를 이용하여 동적으로 넣어주는 역할을 한다.&lt;/p&gt;
&lt;p&gt;따라서, &lt;code&gt;index.html&lt;/code&gt; 에 단 하나의 파일 &lt;code&gt;main.js&lt;/code&gt; 만 가져오고도 스타일링이 된 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Webpack 실습 With Output&lt;/h2&gt;
&lt;p&gt;위의 프로젝트를 그대로 사용 한 상태에서,&lt;/p&gt;
&lt;p&gt;이번에는 단순히 &lt;code&gt;main.js&lt;/code&gt; 에 모든 것을 넣지 않고,&lt;/p&gt;
&lt;p&gt;파일을 분리 해 보자.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;먼저, &lt;code&gt;src&lt;/code&gt; 폴더에 &lt;code&gt;print.js&lt;/code&gt; 라는 새로운 파일을 만든다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;export default function printTest() {
  console.log(&amp;quot;Method : PrintTest&amp;quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그리고 이 &lt;code&gt;printTest()&lt;/code&gt; 메서드는 밑에서 만든 버튼에 의해 콘솔로 출력된다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;index.js&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-diff&quot;&gt;import _ from &amp;quot;lodash&amp;quot;;
import &amp;quot;./style.css&amp;quot;;
+ import printMe from &amp;quot;./print&amp;quot;;

function component() {
  const element = document.createElement(&amp;quot;div&amp;quot;);
+ const btn = document.createElement(&amp;quot;button&amp;quot;);

  element.innerHTML = _.join([&amp;quot;Hello!&amp;quot;, &amp;quot;Webpack World!&amp;quot;], &amp;#39; &amp;#39;);
  element.classList.add(&amp;quot;hello&amp;quot;);

+ btn.innerHTML = &amp;quot;버튼 클릭 시 콘솔에 문자열 출력됨&amp;quot;;
+ btn.onclick = printMe;

+ element.appendChild(btn);

  return element;
}

document.body.appendChild(component());&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;우리가 printMe 를 따로 생성하여 html 파일에 적용하기 위해 2 가지 방법이 존재한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;html 파일의 &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; 쪽에 추가한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;webpack.config.js&lt;/code&gt; 의 &lt;code&gt;module.exports&lt;/code&gt; 에 &lt;code&gt;plugins + HtmlWebpackPlugin&lt;/code&gt; 를 추가한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;1 번은 우리가 파일 청크를 새로 생성 할 때 마다, 지정 해 주어야 한다.&lt;/p&gt;
&lt;p&gt;그리고 특히, 나중에 파일 이름까지 해싱이 진행되면 직접 지정하기는 더 곤란해진다.&lt;/p&gt;
&lt;p&gt;따라서, 우리가 작성한 파일들의 의존성을 알아서 파악하여 Webpack 이 html 파일로 작성 할 수 있도록,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;plugins&lt;/code&gt; 에 작성 해 놓으면 된다.&lt;/p&gt;
&lt;p&gt;일단, Webpack 이 플러그인을 가져 올 수 있도록 개발 의존성으로 플러그인을 설치한다 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ npm i -D html-webpack-plugin&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;변경된 &lt;code&gt;webpack.config.js&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-diff&quot;&gt;const path = require(&amp;quot;path&amp;quot;);
+ const HtmlWebpackPlugin = require(&amp;quot;html-webpack-plugin&amp;quot;);

module.exports = {
- entry : &amp;quot;./src/index.js&amp;quot;,
+ entry: {
+   index : &amp;quot;./src/index.js&amp;quot;,
+   print : &amp;quot;./src/print.js&amp;quot;,
+ }
  output : {
-   filename : &amp;quot;main.js&amp;quot;,
+   filename : &amp;quot;[name].bundle.js&amp;quot;,
+   clean : true,
    path : path.resolve(__dirname, &amp;quot;dist&amp;quot;)
  },
+ plugins: [
+   new HtmlWebpackPlugin({
+       title : &amp;quot;출력물 관리 테스팅&amp;quot;
+   }),
+ ]
  module : {
    rules : [
      {
        test : /\.css$/,
        use: [&amp;quot;style-loader&amp;quot;, &amp;quot;css-loader&amp;quot;]
      }
    ]
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위에 추가된 여러 옵션들에 대한 설명은 뒤로 잠깐 미루고, 결과를 보자 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  webpack-demo-js $ npm run build

&amp;gt; webpack-demo@1.0.0 build
&amp;gt; webpack

asset index.bundle.js 73.4 KiB [emitted] [minimized] (name: index) 1 related asset
asset index.html 290 bytes [emitted]
asset print.bundle.js 23 bytes [emitted] [minimized] (name: print)
runtime modules 1.4 KiB 8 modules
orphan modules 1.1 KiB [orphan] 1 module
cacheable modules 541 KiB
  modules by path ./node_modules/ 539 KiB
    modules by path ./node_modules/style-loader/dist/runtime/*.js 5.84 KiB
      ./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js 2.42 KiB [built] [code generated]
      + 5 modules
    modules by path ./node_modules/css-loader/dist/runtime/*.js 2.31 KiB
      ./node_modules/css-loader/dist/runtime/noSourceMaps.js 64 bytes [built] [code generated]
      ./node_modules/css-loader/dist/runtime/api.js 2.25 KiB [built] [code generated]
    ./node_modules/lodash/lodash.js 531 KiB [built] [code generated]
  modules by path ./src/ 1.93 KiB
    ./src/index.js + 1 modules 1.41 KiB [built] [code generated]
    ./src/print.js 77 bytes [built] [code generated]
    ./node_modules/css-loader/dist/cjs.js!./src/style.css 455 bytes [built] [code generated]

WARNING in configuration
The &amp;#39;mode&amp;#39; option has not been set, webpack will fallback to &amp;#39;production&amp;#39; for this value.
Set &amp;#39;mode&amp;#39; option to &amp;#39;development&amp;#39; or &amp;#39;production&amp;#39; to enable defaults for each environment.
You can also set it to &amp;#39;none&amp;#39; to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/

webpack 5.99.8 compiled with 1 warning in 2215 ms&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;자꾸 경고가 뜨는데, 이는 &lt;code&gt;mode&lt;/code&gt; 를 설정 해 주지 않아서 그렇다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;결과적으로&lt;/strong&gt; 이런 폴더 형태를 구축한다 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-diff&quot;&gt;/프로젝트 폴더
    /dist
+       index.html - clean 옵션으로 인해 항상 삭제 후 다시 생성됨
-       main.js
-       main.js.LICENSE.txt
+       index.bundle.js
+       index.bundle.js.LICENSE.txt
+       print.bundle.js
    /node_modules
    /src
        index.js
+       print.js
        style.css
    package.json
    package-lock.json
    webpack.config.js&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;자, 이제 &lt;code&gt;index.html&lt;/code&gt; 을 보자 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!doctype html&amp;gt;
&amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
        &amp;lt;meta charset=&amp;quot;utf-8&amp;quot; /&amp;gt;
        &amp;lt;title&amp;gt;출력물 관리 테스팅&amp;lt;/title&amp;gt;
        &amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width,initial-scale=1&amp;quot; /&amp;gt;
        &amp;lt;script defer=&amp;quot;defer&amp;quot; src=&amp;quot;index.bundle.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
        &amp;lt;script defer=&amp;quot;defer&amp;quot; src=&amp;quot;print.bundle.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;결과물&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Result3.png&quot; data-origin-width=&quot;740&quot; data-origin-height=&quot;66&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/V8ueH/btsN1fCh8il/6TiBzOLtNSz2tGNXEvY1eK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/V8ueH/btsN1fCh8il/6TiBzOLtNSz2tGNXEvY1eK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/V8ueH/btsN1fCh8il/6TiBzOLtNSz2tGNXEvY1eK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FV8ueH%2FbtsN1fCh8il%2F6TiBzOLtNSz2tGNXEvY1eK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;740&quot; height=&quot;66&quot; data-filename=&quot;Result3.png&quot; data-origin-width=&quot;740&quot; data-origin-height=&quot;66&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Result4.png&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ln2i4/btsN1ZrY00i/f9e5YGGC4ixWNgKOHKTF11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ln2i4/btsN1ZrY00i/f9e5YGGC4ixWNgKOHKTF11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ln2i4/btsN1ZrY00i/f9e5YGGC4ixWNgKOHKTF11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLn2i4%2FbtsN1ZrY00i%2Ff9e5YGGC4ixWNgKOHKTF11%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1284&quot; height=&quot;238&quot; data-filename=&quot;Result4.png&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;238&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;의존성을 파악하여, 자동으로 &lt;code&gt;index.html&lt;/code&gt; 구축&lt;/li&gt;
&lt;li&gt;번들링 빌드 시 마다, 항상 모든 파일 삭제 후 재구축&lt;/li&gt;
&lt;li&gt;엔트리에 따라 여러 개의 청크 번들 파일로 분리 할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;위 리스트의 기능을 수행 할 수 있었던 것은, &lt;code&gt;webpack.config.js&lt;/code&gt; 에 작성한 코드 덕분이다.&lt;/p&gt;
&lt;p&gt;다시 코드를 보자 :&lt;/p&gt;
&lt;p&gt;&lt;code&gt;webpack.config.js&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const path = require(&amp;quot;path&amp;quot;);
const HtmlWebpackPlugin = require(&amp;quot;html-webpack-plugin&amp;quot;);

module.exports = {
  entry: {
    index: &amp;quot;./src/index.js&amp;quot;,
    print: &amp;quot;./src/print.js&amp;quot;,
  },
  output: {
    filename: &amp;quot;[name].bundle.js&amp;quot;,
    clean: true,
    path: path.resolve(__dirname, &amp;quot;dist&amp;quot;),
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: &amp;quot;출력물 관리 테스팅&amp;quot;,
    }),
  ],
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [&amp;quot;style-loader&amp;quot;, &amp;quot;css-loader&amp;quot;],
      },
    ],
  },
};&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;1. entry 의 기본 속성은 main 이다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;처음 Webpack 을 배우면서 다루었던 내용인데, 단일 엔트리(단일 문자열 or 배열 문자열) 로 작성했을 경우,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;entry : {
  main : &amp;quot;./src/index.js&amp;quot; // or [&amp;quot;./src/index.js&amp;quot;, &amp;quot;./src/print.js&amp;quot;]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;로 작성한 것과 다름이 없다.&lt;/p&gt;
&lt;p&gt;하지만, &lt;code&gt;entry&lt;/code&gt; 속성 배열을 활용하여 직접 key 를 지정 해 줄 경우, 확장 가능한 형태가 된다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;index&lt;/code&gt;, &lt;code&gt;print&lt;/code&gt; 라고 따로 속성 이름을 지어 준 이유는, 추후 2 개의 파일로 나누기 위함도 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;2. webpack 의 대괄호 기능은 다양하다&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;그 밑의 &lt;code&gt;output&lt;/code&gt; 객체를 보자.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;filename : &amp;quot;[name].bundle.js&lt;/code&gt; 라고 되어 있다.&lt;/p&gt;
&lt;p&gt;이 의미는, 각 엔트리의 속성 key 를 name 으로 넣고, 이를 각각의 파일로 추출하겠다는 의미이다.&lt;/p&gt;
&lt;p&gt;만약에 이 글을 자세히 읽고 있는 분이 있으시다면, (감사합니다)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;dist/static/&lt;/code&gt; 폴더 내부의 파일 이름을 보자.&lt;/p&gt;
&lt;p&gt;파일 이름이 해싱화 된 것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;이것은 &lt;code&gt;webpack&lt;/code&gt; 에서의 대괄호 기능을 이용 한 것인데,&lt;/p&gt;
&lt;p&gt;직접 &lt;code&gt;npm run eject&lt;/code&gt; 이후 &lt;code&gt;webpack.config.js&lt;/code&gt; 를 살펴보면,&lt;/p&gt;
&lt;p&gt;React 에서는 이러한 기본 대괄호 기능들을 사용한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;[name]&lt;/code&gt; : 엔트리의 속성 key 를 가져온다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[hash]&lt;/code&gt; : 실행 시 랜덤 해시값을 넣는다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[contenthash:8]&lt;/code&gt; : 컨텐츠의 내용에 따라 해시값을 생성하고, 이를 8 길이로 만든다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[ext]&lt;/code&gt; : 해당 엔트리 속성에 할당된 파일의 확장자를 가져온다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이를 통해 파일의 이름을 랜덤화 하거나, 규칙화 하여 내보낼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;3. 결과 폴더를 청소하고 새로운 파일로 번들링하기&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;output&lt;/code&gt; 옵션에 &lt;code&gt;clean : true&lt;/code&gt; 를 넣는다면, 이전 파일이 남지 않고 깔끔하게 삭제된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;4. Webpack 은 번들링 과정에 필요한 다양한 외부 플러그인을 지원한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;웹 어플리케이션의 빌드를 위해서, &lt;code&gt;index.html&lt;/code&gt; 의 &lt;code&gt;script&lt;/code&gt; 를 지속적으로 변경하는 수고로움을 덜어주려면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;HtmlWebpackPlugin&lt;/code&gt; 객체를 넣어 Webpack 에서 알아서 &lt;code&gt;index.html&lt;/code&gt; 에 넣으라고 지시한다.&lt;/p&gt;
&lt;p&gt;이 때, 위의 객체를 넣어주기 위해서 npm 에서 모듈을 다운로드 받아야 한다. &lt;code&gt;html-webpack-plugin&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;이 외에도, css 최적화 청크, 난독화, 청크 최소화 등등 여러 플러그인이 외부에 존재한다.&lt;/p&gt;
&lt;p&gt;이러한 플러그인을 사용하기 위해서는 npm 을 이용해 개발 의존성으로 다운로드 해야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Webpack 실습 In TypeScript&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;참고 공식문서&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://webpack.kr/guides/typescript/#basic-setup&quot;&gt;https://webpack.kr/guides/typescript/#basic-setup&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;이번에는 새로운 프로젝트 폴더를 생성하여 타입스크립트를 webpack 으로 번들링 해 볼 생각이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;기존에 적용되어 있던 &lt;code&gt;style-loader&lt;/code&gt; 를 해제하여 css 파일로 만들어 보기&lt;/li&gt;
&lt;li&gt;TS 적용으로 인한 옵션 추가 및 변경과 동시에, 내부 옵션에 집중&lt;/li&gt;
&lt;li&gt;새로 추가되는 &lt;code&gt;ts-loader&lt;/code&gt; 로 인해, 참조되는 &lt;code&gt;tsconfig.json&lt;/code&gt; 옵션에 집중&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;자! 나는 이제 새로운 프로젝트 &lt;code&gt;webpack-demo-ts&lt;/code&gt; 를 만들었다.&lt;/p&gt;
&lt;p&gt;기본적으로 만들었던 우리의 개발 의존성들을 생각 해 보자!&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;webpack&lt;/code&gt; - 웹팩 코어 모듈&lt;/li&gt;
&lt;li&gt;&lt;code&gt;webpack-cli&lt;/code&gt; - 웹팩을 명령어로 실행 할 수 있게 해 주는 모듈&lt;/li&gt;
&lt;li&gt;&lt;code&gt;html-webpack-plugin&lt;/code&gt; - 의존성 선언된 &lt;code&gt;index.html&lt;/code&gt; 삭제 후, 다시 기입 해 주는 모듈&lt;/li&gt;
&lt;li&gt;&lt;code&gt;css-loader&lt;/code&gt; - webpack 은 js, json 만 이해 할 수 있으므로, css 파일을 js 모듈로 만듬&lt;ul&gt;
&lt;li&gt;부가설명 : 일반 js, ts 파일에서 &lt;code&gt;import ..css 파일&lt;/code&gt; 되었을 경우, 이를 의존성으로 파악 후 js 모듈로 만듬.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;style-loader&lt;/code&gt; - &lt;code&gt;css-loader&lt;/code&gt; 과 함께 콜라보레이션 하는 모듈&lt;ul&gt;
&lt;li&gt;부가설명 : &lt;code&gt;css-loader&lt;/code&gt; 만 적용되었을 경우, css 파일은 따로 번들링 후 추출된다. &lt;br/&gt; 그러나, &lt;code&gt;style-loader&lt;/code&gt; 가 적용되었을 경우, &lt;code&gt;index.html&lt;/code&gt; 런타임 시 &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; 태그 내부에 스타일 태그를 생성하여 직접 넣어준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;(개발 의존성 X) &lt;code&gt;lodash&lt;/code&gt; - JS 에서 직접 iter 문, 객체 조정 시 많은 리소스가 낭비된다. &lt;br/&gt; 이를 아주 효율적으로 바꿔주는 의존성 - &lt;strong&gt;개인적으로 진짜 중요하다 생각되서 이것도 문서로 만들 예정&lt;/strong&gt;&lt;br&gt;그리고, 우리가 추가해야 할 개발 의존성이 존재한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이미 어느정도 npm 과 JS, TS 와의 상호작용에 대해서 알고 있는 분들은 아시겠지만,&lt;/p&gt;
&lt;p&gt;위의 정보는 이미 &lt;code&gt;package.json&lt;/code&gt; 에 &lt;code&gt;dependencies&lt;/code&gt;, &lt;code&gt;devDependencies&lt;/code&gt; 에 명시되어 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;자! 그러면 typescript 환경에 필요한 개발 의존성은 무엇인가?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;1. &lt;code&gt;typescript&lt;/code&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;우리는 로컬 기기 자체에 typescript 를 설치하여 LSP 를 사용하기도 하지만,&lt;/p&gt;
&lt;p&gt;현재 프로젝트에 &amp;quot;정확한&amp;quot; 버전 기입을 통해 프로젝트에 사용될 TS 버전을 명시한다.&lt;/p&gt;
&lt;p&gt;특히, 버전을 &amp;quot;정확히&amp;quot; 명시하지 않는 이상, 최신 버전의 typescript 를 가져온다.&lt;/p&gt;
&lt;p&gt;이를 통해 &lt;code&gt;typescript&lt;/code&gt; 개발 의존성이 설치된 프로젝트는 레거시 메서드 및 클래스를 사용 할 가능성이 거의 없다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;b&gt;2. &lt;code&gt;ts-loader&lt;/code&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;이 loader 의 경우, 프로젝트에 존재하는 &lt;code&gt;tsconfig.json&lt;/code&gt; 의 설정을 따른다.&lt;/p&gt;
&lt;p&gt;타입스크립트 번들링을 위해서는 필수적인 로더이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;프로젝트에 본격적으로 개발 의존성과 일반 의존성을 설치 해 보자 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;# npm 모듈 보관소 초기화 - &amp;quot;package.json&amp;quot; 생성
$ npm init -y

# 개발 의존성부터 설치
$ npm i -D webpack webpack-cli html-webpack-plugin css-loader typescript ts-loader

# 일반 의존성 설치
$ npm i lodash

# lodash 기본 타입 지원이 없어 개발 의존성으로 설치
$ npm i -D @types/lodash&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이후, 보여지는 최종 &lt;code&gt;package.json&lt;/code&gt; 은,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;name&amp;quot;: &amp;quot;webpack-demo-ts&amp;quot;,
  &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
  &amp;quot;main&amp;quot;: &amp;quot;index.js&amp;quot;,
  &amp;quot;scripts&amp;quot;: {
    &amp;quot;test&amp;quot;: &amp;quot;echo \&amp;quot;Error: no test specified\&amp;quot; &amp;amp;&amp;amp; exit 1&amp;quot;,
    &amp;quot;build&amp;quot;: &amp;quot;webpack&amp;quot;
  },
  &amp;quot;keywords&amp;quot;: [],
  &amp;quot;author&amp;quot;: &amp;quot;&amp;quot;,
  &amp;quot;license&amp;quot;: &amp;quot;ISC&amp;quot;,
  &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,
  &amp;quot;devDependencies&amp;quot;: {
    &amp;quot;@types/lodash&amp;quot;: &amp;quot;^4.17.16&amp;quot;,
    &amp;quot;css-loader&amp;quot;: &amp;quot;^7.1.2&amp;quot;,
    &amp;quot;html-webpack-plugin&amp;quot;: &amp;quot;^5.6.3&amp;quot;,
    &amp;quot;ts-loader&amp;quot;: &amp;quot;^9.5.2&amp;quot;,
    &amp;quot;typescript&amp;quot;: &amp;quot;^5.8.3&amp;quot;,
    &amp;quot;webpack&amp;quot;: &amp;quot;^5.99.8&amp;quot;,
    &amp;quot;webpack-cli&amp;quot;: &amp;quot;^6.0.1&amp;quot;
  },
  &amp;quot;dependencies&amp;quot;: {
    &amp;quot;lodash&amp;quot;: &amp;quot;^4.17.21&amp;quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이러하다.&lt;/p&gt;
&lt;p&gt;그리고, 현재 디렉토리 형태를 이러하다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;/TS 프로젝트 루트
  /node_modules
  package.json
  package-lock.json&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그렇다면, 우리는 다시 이전과 같은 디렉토리를 생성 해 주자 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-diff&quot;&gt;/TS 프로젝트 루트
  /node_modules
+ /dist
  package.json
  package-lock.json
+ /src
+   index.ts
+ webpack.config.js&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이번에는 TypeScript 설정 파일이 필요하다.&lt;/p&gt;
&lt;p&gt;두 가지 방식이 있는데,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;직접 &lt;code&gt;tsconfig.json&lt;/code&gt; 파일을 생성한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tsc --init&lt;/code&gt; 명령을 통해서 &lt;code&gt;tsconfig.json&lt;/code&gt; 파일을 생성한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;여기서 &lt;code&gt;tsconfig.json&lt;/code&gt; 파일이 필요한 이유가 있는데,&lt;/p&gt;
&lt;p&gt;이는 &lt;code&gt;webpack&lt;/code&gt; 에서 사용될 &lt;code&gt;ts-loader&lt;/code&gt; 는 &lt;code&gt;tsconfig.json&lt;/code&gt; 설정을 기반으로 로딩하기 때문이고,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;jsx&lt;/code&gt;, &lt;code&gt;tsx&lt;/code&gt; 와 같은 외부 확장자를 &lt;code&gt;ts&lt;/code&gt;, &lt;code&gt;js&lt;/code&gt; 파일과 상호작용 할 수 있게 함에 있고,&lt;/p&gt;
&lt;p&gt;작성된 TypeScript 파일이 어떤 환경을 위해 컴파일 될 지 결정되야 하기 때문이다.&lt;/p&gt;
&lt;p&gt;(Ex - 크로스플랫폼?, 웹?, 어플리케이션?, 단순 모듈?, 등등)&lt;/p&gt;
&lt;p&gt;또한, &lt;code&gt;tsconfig.json&lt;/code&gt; 파일에서는 현재 JavaScript 정식 스펙에 포함되지 않은 기능들을 사용할 수도 있게 해 준다.&lt;/p&gt;
&lt;p&gt;개발 과정 중에 유틸과 컴포넌트가 많아짐에 따라 폴더의 깊이가 깊어지면, 특정 컴포넌트 혹은 유틸을 불러 올 때 상대경로 문자열이 복잡해진다.&lt;/p&gt;
&lt;p&gt;이를 &lt;code&gt;paths&lt;/code&gt; 옵션을 통해 특정 경로를 매핑 할 수도 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;다시 돌아와서, &lt;code&gt;tsconfig.json&lt;/code&gt; 설정 파일이 JS 로 컴파일 시 많은 역할을 해 준다는 것은 알겠다.&lt;/p&gt;
&lt;p&gt;그런데, 이러한 역할들을 어떻게 일일히 기억 할 것인가?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;맞다!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;따라서, 나는 기존의 라이브러리, 혹은 프레임워크 프레임을 통해 npm 프로젝트를 시작하는 것이 아니라,&lt;/p&gt;
&lt;p&gt;직접 커스텀하여 시작하는 경우, &lt;code&gt;tsc --init&lt;/code&gt; 명령어를 통해 &lt;code&gt;tsconfig.json&lt;/code&gt; 파일을 생성하는 것을 권장한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  webpack-demo-ts tsc --init

Created a new tsconfig.json with:
                                                                                          TS
  target: es2016
  module: commonjs
  strict: true
  esModuleInterop: true
  skipLibCheck: true
  forceConsistentCasingInFileNames: true


You can learn more at https://aka.ms/tsconfig&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 명령 결과가 말해주는 것은, &lt;code&gt;tsconfig.json&lt;/code&gt; 에 이 옵션들이 들어갔다는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;tsconfig.json&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;compilerOptions&amp;quot;: {
    /* Visit https://aka.ms/tsconfig to read more about this file */

    /* Projects */
    // &amp;quot;incremental&amp;quot;: true,                              /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
    // &amp;quot;composite&amp;quot;: true,                                /* Enable constraints that allow a TypeScript project to be used with project references. */
    // &amp;quot;tsBuildInfoFile&amp;quot;: &amp;quot;./.tsbuildinfo&amp;quot;,              /* Specify the path to .tsbuildinfo incremental compilation file. */
    // &amp;quot;disableSourceOfProjectReferenceRedirect&amp;quot;: true,  /* Disable preferring source files instead of declaration files when referencing composite
    // ...
    &amp;quot;target&amp;quot;: &amp;quot;es2016&amp;quot;,                                  /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
    // &amp;quot;lib&amp;quot;: [],                                        /* Specify a set of bundled library declaration files that describe the target runtime environment. */
    // &amp;quot;jsx&amp;quot;: &amp;quot;preserve&amp;quot;,                                /* Specify what JSX code is generated. */
    //.....

    /* Modules */
    &amp;quot;module&amp;quot;: &amp;quot;commonjs&amp;quot;,                                /* Specify what module code is generated. */

    // ......
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;너무나 주석이 많은 파일에 당황하겠지만,&lt;/p&gt;
&lt;p&gt;해당 타입스크립트 프로젝트를 진행함에 있어 특정 옵션을 사용 할 때가 되면,&lt;/p&gt;
&lt;p&gt;이러한 설명들이 정말 친절했구나 라는 것을 알게 된다.&lt;/p&gt;
&lt;p&gt;위의 파일은 주석을 100 줄 정도는 없앤 결과이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;사실, 이 &lt;code&gt;tsconfig.json&lt;/code&gt; 의 옵션에 대해서는 따로 공부 해야 할 정도로 조금 양이 많다.&lt;/p&gt;
&lt;p&gt;사실, TypeScript 빌드에서 가장 중요한 것은 &lt;code&gt;module&lt;/code&gt;, &lt;code&gt;target&lt;/code&gt; 이다.&lt;/p&gt;
&lt;p&gt;그 전에 &lt;code&gt;tsc --init&lt;/code&gt; 으로 생겨난 &lt;code&gt;tsconfig.json&lt;/code&gt; 의 기본 옵션의 의미에 대해서 알아보자 :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. target&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;타입스크립트 파일들은 결국은 자바스크립트 파일로 컴파일 된다.&lt;/p&gt;
&lt;p&gt;그런데, 자바스크립트 파일은 진화를 거쳐 지원하는 버전이 서로 다르다.&lt;/p&gt;
&lt;p&gt;그 중, &lt;code&gt;es5&lt;/code&gt;, &lt;code&gt;es6&lt;/code&gt;, &lt;code&gt;es2021&lt;/code&gt; 는 최종 컴파일 결과물이 실행될 환경과 기기에 맞춘 결과이다.&lt;/p&gt;
&lt;p&gt;이는 버전이 업그레이드 됨에 따라, 우리가 알고 있던 기능이 추가되었기 때문이다.&lt;/p&gt;
&lt;p&gt;아주 대표적으로, &lt;code&gt;async&lt;/code&gt;, &lt;code&gt;await&lt;/code&gt; 과 같은 키워드가 있다.&lt;/p&gt;
&lt;p&gt;이러한 구문은 &lt;code&gt;es6&lt;/code&gt; 부터 지원되며, 만약 그 이하의 버전을 요구한다면 &lt;code&gt;es5&lt;/code&gt; 를 사용한다.&lt;/p&gt;
&lt;p&gt;이렇게 될 경우, 비동기 흐름문은 Polyfill 이라는 또 다른 컴파일 과정을 통해&lt;/p&gt;
&lt;p&gt;동일한 역할을 하는 코드를 생산한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;2. module&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;모듈은 현재 작성할 개발 코드가 &amp;quot;어떤 버전의 모듈을 사용할 것이냐&amp;quot;는 것이다.&lt;/p&gt;
&lt;p&gt;여기서 작성되는 버전명 또한 위와 굉장히 유사하긴 한데, 웹 개발 템플릿 명령어로 생성했다면,&lt;/p&gt;
&lt;p&gt;대부분 &lt;code&gt;esnext&lt;/code&gt; 일 것이다.&lt;/p&gt;
&lt;p&gt;그 이유가, 웹 템플릿 프로그램은 대부분 최신 트렌드를 따라가기에 모두 지원한다는 것도 있지만,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;webpack&lt;/code&gt; 이나, &lt;code&gt;webpack&lt;/code&gt; 과 같은 번들러와 호환되기 위해서는 최신 버전을 사용해야 한다.&lt;/p&gt;
&lt;p&gt;또한, 이는 출력물이 어떤 역할을 하느냐에 따라 달라지는데,&lt;/p&gt;
&lt;p&gt;WAS (백엔드) 역할을 할 경우, 모듈을 동기적으로 가져와야 하기에 &lt;code&gt;require&lt;/code&gt; 가 되어야 하며,&lt;/p&gt;
&lt;p&gt;웹 브라우저에 보여지는 사이트를 개발 할 경우, 브라우저에 모듈을 비동기적으로 가져오기 때문에 &lt;code&gt;import&lt;/code&gt; 를 사용한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;esnext&lt;/code&gt;, &lt;code&gt;commonjs&lt;/code&gt; 에 따라 출력물에 &lt;code&gt;import&lt;/code&gt; 냐, &lt;code&gt;require&lt;/code&gt; 인지, 달라진다.&lt;/p&gt;
&lt;p&gt;하지만, 웹 개발 시 &lt;code&gt;es?????&lt;/code&gt; 모듈을 사용한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;3. strict&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이는 정해진 타입을 엄격히 지키는지 확인하는 옵션으로,&lt;/p&gt;
&lt;p&gt;타입스크립트를 제대로 사용한다면 &lt;code&gt;true&lt;/code&gt; 가 사용된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;4. esModuleInterop&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이는 TypeScript 에서 사용되는 &lt;code&gt;import&lt;/code&gt; 문 외에도&lt;/p&gt;
&lt;p&gt;&lt;code&gt;require&lt;/code&gt; 문을 사용하여 불러와야 하는 동기적 모듈이 허용되는지에 대한 옵션이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;5. skipLibCheck&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;우리는 기존에 JavaScript 환경에서 사용하던 라이브러리들을 TypeScript 에서도 사용하는데,&lt;/p&gt;
&lt;p&gt;타입 체크를 위해 &lt;code&gt;@types/????&lt;/code&gt; 처럼 새롭게 개발 의존성을 설치하게 된다.&lt;/p&gt;
&lt;p&gt;따라서 대부분의 NPM 모듈은 타입 체킹을 위한 모듈 &lt;code&gt;@types/&amp;lt;기존 모듈 이름&amp;gt;&lt;/code&gt; 을 지원하는데,&lt;/p&gt;
&lt;p&gt;이 프로젝트에 해당하는 &lt;code&gt;node_modules&lt;/code&gt; 라이브러리의 타입 체킹 라이브러리도 확인을 해야 하냐는 것이다.&lt;/p&gt;
&lt;p&gt;이를 &lt;code&gt;true&lt;/code&gt; 로 선언하는 것이 보통인데,&lt;/p&gt;
&lt;p&gt;만약 &lt;code&gt;false&lt;/code&gt; 로 설정하게 되면, 수많은 라이브러리들이 서로 다르게 타입을 선언을 하는 경우가 존재하기 때문에,&lt;/p&gt;
&lt;p&gt;굉장히 골머리를 썩힐 수 있다. 따라서, &lt;code&gt;true&lt;/code&gt; 로 설정하여 라이브러리까지 타입 체킹을 하지 않는다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;6. forceConsistentCasingInFileNames&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이 옵션은 특정 파일을 &lt;code&gt;import&lt;/code&gt; 할 때, 대소문자를 엄격히 지키게 만드냐에 대한 옵션이다.&lt;/p&gt;
&lt;p&gt;나는 보통 특정 모듈이나 라이브러리를 불러 올 때 대소문자를 지켜서 잘 불러오기 때문에,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;true&lt;/code&gt; 로 놔두어도 되고, 중복 이름을 가진 파일을 대소문자로 나누지 않는다고 하면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;false&lt;/code&gt; 로 설정해도 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;자! 위의 설명을 보았다면, &lt;code&gt;tsconfig.json&lt;/code&gt; 에서 추구하는 방향을 어느정도 알게 되었을 것이다.&lt;/p&gt;
&lt;p&gt;그렇다면, 공식문서에서 제공하는 &lt;code&gt;tsconfig.json&lt;/code&gt; 에 대한 내용을 살펴보자 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;compilerOptions&amp;quot;: {
    &amp;quot;outDir&amp;quot;: &amp;quot;./dist/&amp;quot;,
    &amp;quot;noImplicitAny&amp;quot;: true,
    &amp;quot;module&amp;quot;: &amp;quot;es6&amp;quot;,
    &amp;quot;target&amp;quot;: &amp;quot;es5&amp;quot;,
    &amp;quot;jsx&amp;quot;: &amp;quot;react&amp;quot;,
    &amp;quot;allowJs&amp;quot;: true,
    &amp;quot;moduleResolution&amp;quot;: &amp;quot;node&amp;quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 설정을 그대로 &lt;code&gt;tsconfig.json&lt;/code&gt; 에 붙여넣어 주자.&lt;/p&gt;
&lt;p&gt;자.. 설명하지 않은 옵션을 설명하고 넘어 가 보자 :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. outDir&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;프로젝트에서 컴파일되는 모든 &lt;code&gt;ts&lt;/code&gt; 종류의 파일의 결과물을 어디에 배치 할 것이냐 묻는 것이다.&lt;/p&gt;
&lt;p&gt;우리는 &lt;code&gt;dist&lt;/code&gt; 라는 폴더에 출력물을 관리했으므로, &lt;code&gt;outDir&lt;/code&gt; 이 &lt;code&gt;./dist&lt;/code&gt; 이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;2. noImplicitAny&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이는 타입스크립트를 제대로 사용하기 위해 필요한 옵션이기도 하다.&lt;/p&gt;
&lt;p&gt;null, undefined 도 타입이 존재하는데, 굳이 특정 객체나 타입을 &lt;code&gt;any&lt;/code&gt; 로 지정하여,&lt;/p&gt;
&lt;p&gt;추후 이 데이터를 사용 할 사람에게 혼란을 줄 필요가 없다.&lt;/p&gt;
&lt;p&gt;모든 데이터에 &lt;code&gt;const test : any&lt;/code&gt; 이러한 형식으로 자유를 풀어주지 않는다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;3. jsx&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이는 &lt;code&gt;jsx&lt;/code&gt;, &lt;code&gt;tsx&lt;/code&gt; 파일에서 React 를 사용하기 위해 선언되는 옵션이다.&lt;/p&gt;
&lt;p&gt;여기서 &lt;code&gt;jsx&lt;/code&gt; 나, &lt;code&gt;react-jsx&lt;/code&gt; 를 선택 할 수 있는데,&lt;/p&gt;
&lt;p&gt;React 17 버전 이후로부터는 &lt;code&gt;react-jsx&lt;/code&gt; 옵션을 통해,&lt;/p&gt;
&lt;p&gt;하나의 파일마다 &lt;code&gt;import React from &amp;quot;react&amp;quot;&lt;/code&gt; 할 필요 없이, 자동 임포트 해 준다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;4. allowJs&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이는 타입스크립트 파일 외에도 일반 JavaScript 파일을 사용하는 것을 허용한다는 의미이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;5. moduleResolution&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;우리가 &lt;code&gt;import path from &amp;quot;path&amp;quot;&lt;/code&gt; 했던 것 처럼,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;node&lt;/code&gt; 로 설정함으로서 기본 임포트 시 &lt;code&gt;node_modules&lt;/code&gt; 에서 가져오는 것으로 알겠다는 의미이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;오랜만에 옵션의 의미와 사용법에 대해서 다루니까 머리가 아프다.&lt;/p&gt;
&lt;p&gt;사용하기 편리한 JS 가 오히려 CS 지식을 더 필요로 하는 것 같다는 생각이 든다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;TypeScript 버전의 webpack.config.js&lt;/h3&gt;
&lt;p&gt;기존에 우리가 진행했던 JavaScript + Webpack 프로젝트와는 설정이 조금 다르다.&lt;/p&gt;
&lt;p&gt;이는 Webpack 이 &lt;code&gt;tsconfig.json&lt;/code&gt; 을 같이 고려하면서 번들링하기 때문인데,&lt;/p&gt;
&lt;p&gt;이 과정에서 새로운 확장자들이 고려되기 때문에 그렇다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;공식 문서&lt;/strong&gt; &lt;code&gt;webpack.config.js&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const path = require(&amp;quot;path&amp;quot;);
const HtmlWebpackPlugin = require(&amp;quot;html-webpack-plugin&amp;quot;);

module.exports = {
  entry : &amp;quot;./src/index.ts&amp;quot;,
  module : {
    rules : [
      {
        test: /\.tsx?$/,
        use: &amp;quot;ts-loader&amp;quot;,
        exclude: /node_modules/
      },
    ]
  },
  resolve : {
    extensions : [&amp;quot;tsx&amp;quot;, &amp;quot;ts&amp;quot;, &amp;quot;js&amp;quot;]
  },
  output : {
    filename : &amp;quot;[name].bundle.js&amp;quot;,
    path : path.resolve(__dirname, &amp;quot;dist&amp;quot;)
  },
  plugins: [
    new HtmlWebpackPlugin()
  ],
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;일단, &lt;code&gt;css-loader&lt;/code&gt; npm 의존성에 대한 내용을 적지 않았는데,&lt;/p&gt;
&lt;p&gt;이는 또 다른 모듈이 설치되어야 해서, 나중에 기입 후 설명 할 예정이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;1. exclude 옵션은 무엇인가?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이건 정말 모르겠어서 gpt 에게 물어보았는데,&lt;/p&gt;
&lt;p&gt;요즘 NPM 모듈들은 기본적으로 번들러 과정을 생각하여 번들링 된 결과물을 제공한다고 한다.&lt;/p&gt;
&lt;p&gt;따라서, 우리가 내부에서 사용하는 node_modules 의존성을 딱히 번들링하지 않기 위해서라고 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;2. resolve 옵션은 무엇인가?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이는 번들링이 어떤 확장자를 인식하는가에 대한 설명이다.&lt;/p&gt;
&lt;p&gt;예를 들어, 우리가 &lt;code&gt;import component from &amp;quot;./Component&amp;quot;;&lt;/code&gt; 처럼 가져오는데,&lt;/p&gt;
&lt;p&gt;여기엔 확장자가 빠져 있다.&lt;/p&gt;
&lt;p&gt;여기서 빠져 있는 확장자를 어떠한 확장자들로 자동으로 인식 할 것인가에 대한 내용이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. &lt;code&gt;/.tsx$/&lt;/code&gt; 는 무엇을 의미하는가?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Regex (Regular Expression - 정규표현식) 에서 사용하는 문법인데,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;?&lt;/code&gt; 바로 앞에 붙은 문자가 있을 수도, 없을 수도 있다는 것이다.&lt;/p&gt;
&lt;p&gt;즉, &lt;code&gt;/\.ts$/&lt;/code&gt;, &lt;code&gt;/\.tsx$/&lt;/code&gt; 둘 다 의미하기에, &lt;code&gt;.ts&lt;/code&gt;, &lt;code&gt;tsx&lt;/code&gt; 확장자를 전부 인식하여 로드한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;코드를 작성 해 보자!&lt;/p&gt;
&lt;p&gt;&lt;code&gt;./src/index.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import * as _ from &amp;quot;lodash&amp;quot;;

function testComponent(strArr : string[]) {
  const element = document.createElement(&amp;quot;div&amp;quot;);

  element.innerHTML = _.join(strArr, &amp;#39; &amp;#39;);

  return element;
}

const arr: string[] = [&amp;quot;Hello!&amp;quot;, &amp;quot;Webpack Plus TypeScript!&amp;quot;];

document.body.appendChild(testComponent(arr));&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그리고, 우리가 이전에 &lt;code&gt;package.json&lt;/code&gt; 에 작성했던 스크립트를 넣는다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  // ....
  &amp;quot;scripts&amp;quot;: {
    &amp;quot;test&amp;quot;: &amp;quot;echo \&amp;quot;Error: no test specified\&amp;quot; &amp;amp;&amp;amp; exit 1&amp;quot;,
    &amp;quot;build&amp;quot;: &amp;quot;webpack&amp;quot;
  },
  // .....
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이후 npm 명령어로 &lt;code&gt;webpack-cli&lt;/code&gt; 를 실행 해 주자 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  webpack-demo-ts $ npm run build

&amp;gt; webpack-demo-ts@1.0.0 build
&amp;gt; webpack

asset main.bundle.js 69.5 KiB [emitted] [minimized] (name: main) 1 related asset
asset index.html 221 bytes [emitted]
runtime modules 1010 bytes 5 modules
cacheable modules 532 KiB
  ./src/index.ts 275 bytes [built] [code generated]
  ./node_modules/lodash/lodash.js 531 KiB [built] [code generated]

WARNING in configuration
The &amp;#39;mode&amp;#39; option has not been set, webpack will fallback to &amp;#39;production&amp;#39; for this value.
Set &amp;#39;mode&amp;#39; option to &amp;#39;development&amp;#39; or &amp;#39;production&amp;#39; to enable defaults for each environment.
You can also set it to &amp;#39;none&amp;#39; to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/

webpack 5.99.8 compiled with 1 warning in 3624 ms&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그리고, &lt;code&gt;./dist/index.html&lt;/code&gt; 파일을 열어보면,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Result5.png&quot; data-origin-width=&quot;472&quot; data-origin-height=&quot;68&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cSc0VG/btsN2eCoq7q/mBymdsZsv6AkFhmSx503KK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSc0VG/btsN2eCoq7q/mBymdsZsv6AkFhmSx503KK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSc0VG/btsN2eCoq7q/mBymdsZsv6AkFhmSx503KK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcSc0VG%2FbtsN2eCoq7q%2FmBymdsZsv6AkFhmSx503KK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;472&quot; height=&quot;68&quot; data-filename=&quot;Result5.png&quot; data-origin-width=&quot;472&quot; data-origin-height=&quot;68&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;정상적으로 작동하는 것을 볼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;css 를 런타임이 아닌 파일로 따로 출력하기&lt;/h3&gt;
&lt;p&gt;사실, webpack 에 존재하는 옵션으로 최대한 해결 할 수 있으면 하려고 했는데,&lt;/p&gt;
&lt;p&gt;아무리 생각해도 따로 개발용 플러그인을 설치해서 이를 적용하는 것이 훨씬 효율적이었다.&lt;/p&gt;
&lt;p&gt;이유는, 플러그인 자체가 css, scss, sass 를 위한 모듈이었으며,&lt;/p&gt;
&lt;p&gt;나중에 스타일링을 위해 서로 &lt;code&gt;@url()&lt;/code&gt; 을 사용했을 때도, 의존성을 분석 해 주기 때문이었다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;mini-css-extract-plugin&lt;/code&gt; 이라는 npm 개발 의존성을 설치 해 주면 된다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ npm i -D mini-css-extract-plugin&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이후, 웹팩 설정 파일을 변경한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;webpack.config.js&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-diff&quot;&gt;const path = require(&amp;quot;path&amp;quot;);
const HtmlWebpackPlugin = require(&amp;quot;html-webpack-plugin&amp;quot;);
+ const MiniCssExtractPlugin = require(&amp;quot;mini-css-extract-plugin&amp;quot;)

module.exports = {
  entry : &amp;quot;./src/index.ts&amp;quot;,
  module : {
    rules : [
      {
        test: /\.tsx?$/,
        use: &amp;quot;ts-loader&amp;quot;,
        exclude: /node_modules/
      },
+     {
+       test : /\.(sa|sc|c)ss$/,
+       use: [
+         MiniCssExtractPlugin.loader,
+         &amp;quot;css-loader&amp;quot;
+       ]
+     }
    ]
  },
  resolve : {
    extensions : [&amp;quot;tsx&amp;quot;, &amp;quot;ts&amp;quot;, &amp;quot;js&amp;quot;]
  },
  output : {
    filename : &amp;quot;[name].bundle.js&amp;quot;,
-   path : path.resolve(__dirname, &amp;quot;dist&amp;quot;)
+   path : path.resolve(__dirname, &amp;quot;dist/static&amp;quot;),
+   clean : true
  },
  plugins: [
    new HtmlWebpackPlugin(),
+   new MiniCssExtractPlugin({
+     filename : &amp;quot;[name].[contenthash:8].css&amp;quot;,
+   })
  ],
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;정규표현식을 위처럼 사용하면 &lt;code&gt;scss&lt;/code&gt;, &lt;code&gt;sass&lt;/code&gt;, &lt;code&gt;css&lt;/code&gt; 모두 파싱이 가능하다.&lt;/p&gt;
&lt;p&gt;경로를 바꾼 이유는, React 의 기본 js, css 파일 출력물이&lt;/p&gt;
&lt;p&gt;&lt;code&gt;dist/static&lt;/code&gt; 에 출력되기에 한번 해 보았다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그리고, &lt;code&gt;./src/styles.css&lt;/code&gt; 파일을 생성하여 태그를 작성 해 보자.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;./src/styles.css&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.container {
    color : skyblue;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이미 작성되어 있던 &lt;code&gt;src/index.ts&lt;/code&gt; 에 코드를 조금 추가한다. (클래스 속성 추가)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-diff&quot;&gt;import * as _ from &amp;quot;lodash&amp;quot;;
+ import &amp;quot;./styles.css&amp;quot;;

function testComponent(strArr : string[]) {
  const element = document.createElement(&amp;quot;div&amp;quot;);

  element.innerHTML = _.join(strArr, &amp;#39; &amp;#39;);
+ element.className = &amp;quot;container&amp;quot;

  return element;
}

const arr: string[] = [&amp;quot;Hello!&amp;quot;, &amp;quot;Webpack Plus TypeScript!&amp;quot;];

document.body.appendChild(testComponent(arr));&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  webpack-demo-ts npm run build

&amp;gt; webpack-demo-ts@1.0.0 build
&amp;gt; webpack

asset main.bundle.js 69.5 KiB [compared for emit] [minimized] (name: main) 1 related asset
asset index.html 269 bytes [emitted]
asset main.32ab1364.css 37 bytes [emitted] [immutable] (name: main)
Entrypoint main 69.6 KiB = main.bundle.js 69.5 KiB main.32ab1364.css 37 bytes
runtime modules 1.9 KiB 9 modules
orphan modules 2.81 KiB [orphan] 4 modules
cacheable modules 532 KiB (javascript) 36 bytes (css/mini-extract)
  ./src/index.ts 335 bytes [built] [code generated]
  ./node_modules/lodash/lodash.js 531 KiB [built] [code generated]
  css ./node_modules/css-loader/dist/cjs.js!./src/styles.css 36 bytes [built] [code generated]

WARNING in configuration
The &amp;#39;mode&amp;#39; option has not been set, webpack will fallback to &amp;#39;production&amp;#39; for this value.
Set &amp;#39;mode&amp;#39; option to &amp;#39;development&amp;#39; or &amp;#39;production&amp;#39; to enable defaults for each environment.
You can also set it to &amp;#39;none&amp;#39; to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/

webpack 5.99.8 compiled with 1 warning in 3507 ms&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;결과물&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Result6.png&quot; data-origin-width=&quot;466&quot; data-origin-height=&quot;64&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ApzF7/btsN1hmyGWL/kyWzRNi1VRkfAvH7XtZb10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ApzF7/btsN1hmyGWL/kyWzRNi1VRkfAvH7XtZb10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ApzF7/btsN1hmyGWL/kyWzRNi1VRkfAvH7XtZb10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FApzF7%2FbtsN1hmyGWL%2FkyWzRNi1VRkfAvH7XtZb10%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;466&quot; height=&quot;64&quot; data-filename=&quot;Result6.png&quot; data-origin-width=&quot;466&quot; data-origin-height=&quot;64&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;경로를 바꾸고, &lt;code&gt;clean&lt;/code&gt; 옵션을 넣었음에도, &lt;code&gt;dist/index.html&lt;/code&gt;, &lt;code&gt;dist/...&lt;/code&gt; 가 사라지지 않고,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;dist/static/...&lt;/code&gt; 에 파일들이 생성되었다.&lt;/p&gt;
&lt;p&gt;따라서, &lt;code&gt;dist.index.html&lt;/code&gt;, &lt;code&gt;dist/...&lt;/code&gt; 를 삭제한다.&lt;/p&gt;
&lt;p&gt;이후 &lt;code&gt;dist&lt;/code&gt; 형태 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  dist tree
.
└── static
    ├── index.html
    ├── main.32ab1364.css
    ├── main.bundle.js
    └── main.bundle.js.LICENSE.txt

2 directories, 4 files&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;index.html&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!doctype html&amp;gt;
&amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
        &amp;lt;meta charset=&amp;quot;utf-8&amp;quot; /&amp;gt;
        &amp;lt;title&amp;gt;Webpack App&amp;lt;/title&amp;gt;
        &amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width,initial-scale=1&amp;quot; /&amp;gt;
        &amp;lt;script defer=&amp;quot;defer&amp;quot; src=&amp;quot;main.bundle.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
        &amp;lt;link href=&amp;quot;main.32ab1364.css&amp;quot; rel=&amp;quot;stylesheet&amp;quot; /&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;HtmlWebpackPlugin&lt;/code&gt; 이 &lt;code&gt;index.html&lt;/code&gt; 에 정상적으로 의존성 파일들을 추가해 주는 것을 볼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;이 글을 작성하며 배운 것 정리&lt;/h2&gt;
&lt;p&gt;솔직히 말해서, 이번 글은 역대급으로 힘들고 지치는 주제였다.&lt;/p&gt;
&lt;p&gt;왜 이렇게 지칠까 생각 해 보니, 현재 내가 추구하고 있는 공부 방식과,&lt;/p&gt;
&lt;p&gt;webpack 을 쉽게 익히기 위한 방식이 상충되기 때문이었다.&lt;/p&gt;
&lt;p&gt;나는 현재 React 라는 풍부한 웹 개발 라이브러리를 사용하기 위해,&lt;/p&gt;
&lt;p&gt;중간 단계에서 사용되는 과정과 개발 도구들을 공부한다.&lt;/p&gt;
&lt;p&gt;&amp;quot;이것이 왜 필요하고, 어떻게 사용해야 하는가?&amp;quot; 를 중심으로 공부한다.&lt;/p&gt;
&lt;p&gt;Webpack 이라는 도구는 React 에 있어 거의 대부분 사용되어야 하는 도구인데,&lt;/p&gt;
&lt;p&gt;따라서 &amp;quot;이것이 왜 필요한가?&amp;quot; 에 대한 부분은 명확히 이해했다.&lt;/p&gt;
&lt;p&gt;웹팩은 다양한 확장자 파일을 인식하고 파싱하며, 웹에 소스를 넣을 때 의존성을 고려하기 때문이다.&lt;/p&gt;
&lt;p&gt;또한, 코드 난독화를 통해 인간이 일반적으로 읽을 수 없게 만드는 것에 대해서도 감탄을 지어냈다.&lt;/p&gt;
&lt;p&gt;개발 과정에서 &lt;code&gt;jsx&lt;/code&gt;, &lt;code&gt;tsx&lt;/code&gt;, &lt;code&gt;scss&lt;/code&gt;, &lt;code&gt;sass&lt;/code&gt; 가 사용되는 것은 알았지만,&lt;/p&gt;
&lt;p&gt;이것이 어떻게 컴파일 되는지에 대한 과정 또한 알았다. React 템플릿을 통해 개발한다면,&lt;/p&gt;
&lt;p&gt;웹팩 번들러는 필수불가결한 프로그램이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;그러나,&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&amp;quot;어떻게 사용해야 하는가?&amp;quot; 에 대한 것이 너무 난해했다.&lt;/p&gt;
&lt;p&gt;js, ts 실행 파일을 통해 webpack 설정을 한다는 것은 충분히 이해했지만,&lt;/p&gt;
&lt;p&gt;여기서 사용되는 방법론 카테고리가 진짜 너무나도 많다고 느꼈다.&lt;/p&gt;
&lt;p&gt;특히, webpack 이라는 프로그램에서 사용되는 옵션명,&lt;/p&gt;
&lt;p&gt;그리고 옵션에 지정해야 하는 또 다른 옵션, 그리고 사용법이 제각각 달랐다.&lt;/p&gt;
&lt;p&gt;또한, 파싱을 위해 loader 를 추가로 개발 의존성으로 설치해야 하며,&lt;/p&gt;
&lt;p&gt;이 로더에 들어가야 할 옵션 또한 알고 있어야 했다.&lt;/p&gt;
&lt;p&gt;게다가 정규표현식인 Regex 는 덤이었다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;하지만,&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;그럼에도 불구하고, 이 방식이 번들러에 있어서는 최선이라는 것이다.&lt;/p&gt;
&lt;p&gt;무엇이 최선이냐고 한다면, &amp;quot;일반 개발자들이 건드릴 일 없이 번들링 하게 해 준다&amp;quot; 에 가깝다고 생각한다.&lt;/p&gt;
&lt;p&gt;이를 충족시켜 주기 위해, &amp;quot;번들링&amp;quot; 이라는 것을 쉽게 하기 위하여 정말 많은 방법론을 적용했다.&lt;/p&gt;
&lt;h3&gt;결과적으로 말하자면,&lt;/h3&gt;
&lt;p&gt;웹 개발은 단순한 html, css, js 파일들만으로 이루어지지 않는 세상에서,&lt;/p&gt;
&lt;p&gt;다양한 확장자와 프로그램을 연동하기 위해 번들러가 반드시 필요한 세상이다.&lt;/p&gt;
&lt;p&gt;특히, js 파일과 css 파일 간의 의존성 생성도 하나의 이유이기도 하지만,&lt;/p&gt;
&lt;p&gt;특히 프로젝트가 복잡해짐에 따라 서로 얽기고 섥힌 js 끼리의 의존성을 풀어주는 것이&lt;/p&gt;
&lt;p&gt;번들러의 최대 장점이 아닐까 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;h2&gt;참고 사이트&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;TOAST UI 글 (번들러)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ui.toast.com/fe-guide/ko_BUNDLER&quot;&gt;https://ui.toast.com/fe-guide/ko_BUNDLER&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Webpack 공식 사이트&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://webpack.kr/&quot;&gt;https://webpack.kr/&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Github Gist (JavaScript Bundler 에 대해 이해하기)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://gist.github.com/jeffminsungkim/9cd5c592dfe39a5eaae93ffcc1818cef&quot;&gt;https://gist.github.com/jeffminsungkim/9cd5c592dfe39a5eaae93ffcc1818cef&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;DEV 글 (The What, Why and How of JavaScript Bundlers)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://dev.to/sayanide/the-what-why-and-how-of-javascript-bundlers-4po9&quot;&gt;https://dev.to/sayanide/the-what-why-and-how-of-javascript-bundlers-4po9&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;TOAST UI (의존성 관리)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ui.toast.com/fe-guide/ko_DEPENDENCY-MANAGE&quot;&gt;https://ui.toast.com/fe-guide/ko_DEPENDENCY-MANAGE&lt;/a&gt;&lt;/p&gt;</description>
      <category>Node.js/잡다 지식</category>
      <category>bundler</category>
      <category>webpack</category>
      <category>Webpack 사용법</category>
      <category>리액트 컴파일</category>
      <category>번들러</category>
      <category>번들러란</category>
      <category>웹팩</category>
      <category>웹팩 기능</category>
      <category>웹팩 사용법</category>
      <category>웹팩이란</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/214</guid>
      <comments>https://codecreature.tistory.com/214#entry214comment</comments>
      <pubDate>Sun, 18 May 2025 00:30:09 +0900</pubDate>
    </item>
    <item>
      <title>브라우저의 동작 과정과 기능들</title>
      <link>https://codecreature.tistory.com/213</link>
      <description>&lt;h2&gt;제목 : 브라우저의 동작 과정과 기능들&lt;/h2&gt;
&lt;hr&gt;
&lt;h3&gt;이 주제로 포스팅, 및 공부를 하는 이유&lt;/h3&gt;
&lt;p&gt;웹 사이트를 제작한다는 의미는, 현재 그 의미가 많이 다르다고 생각한다.&lt;/p&gt;
&lt;p&gt;정적인 HTML 만을 보여주던 웹 사이트 라는 의미가 무색하게,&lt;/p&gt;
&lt;p&gt;현재는 애니메이션 CSS, 웹 3D 렌더링, 웹 사이트 Code Editor, 실시간 채팅 및 그래프 표시 등등,&lt;/p&gt;
&lt;p&gt;웹의 다양성은 상상을 초월한다.&lt;/p&gt;
&lt;p&gt;단순히 웹 사이트를 개발한다는 것은, HTML 파일과 CSS 스타일링, 반응형 JS 를 제작한다는 것 뿐 만이 아니라,&lt;/p&gt;
&lt;p&gt;위의 파일들을 이용하여 클라이언트에게 화면을 보여주는 브라우저의 역할 또한 알아야 한다는 의미이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;나는 이전에 React 를 공부 할 때, 전역 변수 지정과 로컬, 세션 Storage 및 Cookie 를 공부 할 때&lt;/p&gt;
&lt;p&gt;매우 난감했다. 이러한 정보를 사용하여 클라이언트와 개발자에게 편의성을 제공해 준다는 것은 알겠다.&lt;/p&gt;
&lt;p&gt;하지만, 이러한 정보는 어디에 저장되는가? 에 대한 의문이 해소되지는 않았다.&lt;/p&gt;
&lt;p&gt;그 때 당시는 JS 에 대한 이해가 부족 할 뿐더러, 브라우저의 기능이라는 것을 이해하지도 못했으니,&lt;/p&gt;
&lt;p&gt;결국 의미없는 코드를 작성하고 실행 해 볼 뿐이었다.&lt;/p&gt;
&lt;p&gt;나는 다시 React 를 활용하여 웹을 개발하는 과정에서 이해 없이 개발하지 않기로 결심했다.&lt;/p&gt;
&lt;p&gt;React 는 너무 훌륭한 라이브러리이며 다른 다양한 JS 모듈과 결합 할 수 있지만,&lt;/p&gt;
&lt;p&gt;다른 방법론들을 웹 개발이라고 단순히 치부하고 싶지는 않았다.&lt;/p&gt;
&lt;p&gt;브라우저에서는 클라이언트를 위한 보안, 웹 렌더링 및 서버 커넥션, 개발자를 위한 편의성 등등&lt;/p&gt;
&lt;p&gt;나열하지 않은 수 많은 기능들을 가지고 있다.&lt;/p&gt;
&lt;p&gt;우리가 작성한 웹 페이지는 결국 Node.js 환경이 아닌, 클라이언트의 브라우저 환경에서 구동되므로&lt;/p&gt;
&lt;p&gt;Node.js 를 공부하듯, 브라우저를 다가가 보기로 했다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;웹 브라우저란?&lt;/h2&gt;
&lt;p&gt;먼저, 위키피디아의 웹 브라우저에 대한 정의를 훑고 지나가보자.&lt;/p&gt;
&lt;p&gt;브라우저란, 웹 사이트에 접근하기 위한 &amp;quot;어플리케이션&amp;quot; 이다.&lt;/p&gt;
&lt;p&gt;유저가 특정 웹사이트로부터 웹 페이지를 요청할 때,&lt;/p&gt;
&lt;p&gt;브라우저는 웹 서버로부터 이에 대한 파일을 탐색하고, 유저의 화면에 페이지를 표시한다.&lt;/p&gt;
&lt;p&gt;또한, 브라우저는 유저의 기기에 저장되어 있는 컨텐츠를 표시 할 수도 있다.&lt;/p&gt;
&lt;p&gt;브라우저는 데스크탑, 랩탑, 태블릿, 스마트폰, 스마트워치를 포함하는 다양한 기기에서 사용된다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;웹 브라우저는 어떻게 동작할까?&lt;/h3&gt;
&lt;p&gt;웹 브라우저를 킨 뒤, 유저가 하는 1 번째 행위는 바로 &amp;quot;탐색 (Retrieve)&amp;quot; 일 것이다.&lt;/p&gt;
&lt;p&gt;웹 페이지를 가져오기 위해서는, 검색(탐색) 해야 하기 때문이다.&lt;/p&gt;
&lt;p&gt;그런데, 이 탐색 과정에서 배울 것이 많다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;1. 우리는 검색 할 때 URL 을 사용한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;우리가 웹 사이트의 주소, 예로 들면 &lt;code&gt;https://naver.com&lt;/code&gt; 을 검색한다면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;naver.com&lt;/code&gt; 를 통해 곧장 웹 페이지를 가져오지 않는다.&lt;/p&gt;
&lt;p&gt;모든 서버는 고유의 IP 주소를 가지고 있다. (&lt;code&gt;xxx.xxx.xxx.xxx&lt;/code&gt;) 와 같이 가지고 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;브라우저는 먼저, 유저의 기기에 &lt;code&gt;naver.com&lt;/code&gt; 이라는 도메인 정보, IP 를 가지고 있는지 확인한다.&lt;/p&gt;
&lt;p&gt;만약에 기기에 &lt;code&gt;naver.com&lt;/code&gt; 이라는 IP 정보가 없다면, 도메인 정보를 관리하는 서버에 이를 전달한다.&lt;/p&gt;
&lt;p&gt;(도메인 관리 서버 == &lt;code&gt;DNS - (Domain Namespace Server)&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;먼저, &lt;code&gt;.com&lt;/code&gt; 에 해당하는 &lt;code&gt;com&lt;/code&gt; 도메인 이름을 관리하는 서버에 요청을 날리고,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;com&lt;/code&gt; 을 관리하는 서버는 &lt;code&gt;naver&lt;/code&gt; 에 대한 정보를 가지고 있는 서버에 요청을 날린다.&lt;/p&gt;
&lt;p&gt;최종적으로 &lt;code&gt;naver.com&lt;/code&gt; 이라는 IP 주소 &lt;code&gt;xxx.xxx.xxx.xxx&lt;/code&gt; 정보를 Resolve 하게 되면,&lt;/p&gt;
&lt;p&gt;네임스페이스 서버(도메인 정보 관리하는 서버들 - 전세계) 는 이를 다시 유저 기기에 전달 해 주게 된다.&lt;/p&gt;
&lt;p&gt;받게 된 &lt;code&gt;xxx.xxx.xxx.xxx&lt;/code&gt; IP 주소를 검색하고, 그 결과 &lt;code&gt;https://naver.com&lt;/code&gt; 의 웹 사이트를 보게 된다.&lt;/p&gt;
&lt;p&gt;위와 같은 과정을 &lt;code&gt;DNS Loopkup (DNS 조회)&lt;/code&gt; 라고 부른다.&lt;/p&gt;
&lt;p&gt;이를 통해 우리는 드디어 웹 페이지와 정적 리소스를 제공하는 웹 서버의 진짜 주소를 얻게 되는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;만약에 우리가 입력한 Full URL 이 &lt;code&gt;https://naver.com/api/v1/login&lt;/code&gt; 이었다면,&lt;/p&gt;
&lt;p&gt;우리가 얻은 IP 주소의 웹 서버에서 &lt;code&gt;api/v1/login&lt;/code&gt; 에 해당하는 리소스를 전달 해 주게 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;2. 브라우저는 보안된 환경을 제공해 준다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;먼저, 1 번 URL 예시에 빠진 것이 있는데, 바로 보안 환경 구축 과정이다.&lt;/p&gt;
&lt;p&gt;대부분의 현대적인 사이트에서는 &lt;code&gt;http&lt;/code&gt; 를 사용하지 않는다.&lt;/p&gt;
&lt;p&gt;클라이언트와 웹 서버 사이에서 오가는 정보가 Plain Text (일반 텍스트) 로 전달된다고 가정해 보자 - http&lt;/p&gt;
&lt;p&gt;로그인 과정만 생각 해 보더라도, 비밀번호가 노출되는 상황이 벌어진다.&lt;/p&gt;
&lt;p&gt;이러한 대참사를 막기 위해 2 가지 행동을 수행한다.&lt;/p&gt;
&lt;p&gt;첫 번째는 &lt;strong&gt;TCP Handshake&lt;/strong&gt; 이고, 두 번째는 &lt;strong&gt;TLS Negotiation&lt;/strong&gt; 이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TCP Handshake&lt;/strong&gt; 를 통해 서로의 통신 연결 상태를 확인하고, &amp;quot;연결&amp;quot; 을 수립한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;sequenceDiagram
    Client -&amp;gt;&amp;gt; Server: SYN 패킷
    Server -&amp;gt;&amp;gt; Client: SYN + ACK 패킷
    Client -&amp;gt;&amp;gt; Server: ACK 패킷&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이는 두 컴퓨터 간의 TCP 세션을 시작하기 위해 3 개의 메세지를 전달하는 과정이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;TLS Negotiation&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;TCP 핸드셰이크 이후, 우리는 각 서버의 연결 상태를 수립했다.&lt;/p&gt;
&lt;p&gt;이후, 우리는 웹 서버에 요청 및 응답을 수행하기 위해 &lt;strong&gt;&amp;quot;암호화&amp;quot;&lt;/strong&gt; 를 해야 한다.&lt;/p&gt;
&lt;p&gt;암호화 단계가 바로 TLS Negotiation (TLS 협상) 이다.&lt;/p&gt;
&lt;p&gt;먼저 과정을 보자 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;sequenceDiagram
    Client -&amp;gt;&amp;gt; Server: ClientHello
    Server -&amp;gt;&amp;gt; Client: ServerHello &amp;amp; Certificate(인증서)
    Client -&amp;gt;&amp;gt; Server: ClientKey
    Server -&amp;gt;&amp;gt; Client: Finished
    Client -&amp;gt;&amp;gt; Server: Finished&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Cloudflare 에서 TLS 핸드셰이크에 대한 정확한 설명을 가지고 있다.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.cloudflare.com/ko-kr/learning/ssl/what-happens-in-a-tls-handshake/&quot;&gt;Cloudflare TLS 핸드셰이크 문서&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;TLS 핸드셰이크(협상) 을 통해, 양 측은 암호화 된 정보를 주고 받을 수 있게 되었다.&lt;/p&gt;
&lt;p&gt;이 과정 이후 감청자는 메세지를 변환할 수 없으며, 해독 할 수 없다.&lt;/p&gt;
&lt;p&gt;그리고 마침내, HTML 페이지를 가져온다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;3. 브라우저는 구문 분석(Parsing) 을 통해 페이지를 렌더링한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;HTML 은 마치 이미 기계어처럼 바로 실행하거나, 곧바로 렌더링 할 수 있는 구문이 아니다.&lt;/p&gt;
&lt;p&gt;HTML 은 브라우저가 분석하여&lt;/p&gt;
&lt;p&gt;&lt;code&gt;DOM&lt;/code&gt; (Document Object Model), &lt;code&gt;CSSOM&lt;/code&gt; (Cascade Style Sheet Object Model) 로 바꾼다.&lt;/p&gt;
&lt;p&gt;웹 사이트는 컴포넌트 렌더링을 통해 화면을 송출한다.&lt;/p&gt;
&lt;p&gt;그러나, 렌더링이라는 것이 이미지처럼 렌더링 된다는 의미가 아니고,&lt;/p&gt;
&lt;p&gt;먼저 컴포넌트의 연결과 부모 자식 관계를 형성하고, 이를 통해 컴포넌트를 화면에 붙이는 것이다.&lt;/p&gt;
&lt;p&gt;그리고, 컴포넌트의 특성과 해당 컴포넌트에 정의된 css 특성을 통해 스타일링 된다.&lt;/p&gt;
&lt;p&gt;DOM 은 Tree 의 형태를 가지고 있으며, CSSOM 도 마찬가지로 Tree 의 형태를 가진다.&lt;/p&gt;
&lt;p&gt;실제로, 우리는 HTML 에 들어가는 JavaScript 에서 &lt;code&gt;document&lt;/code&gt; 객체를 사용 할 수 있다.&lt;/p&gt;
&lt;p&gt;Node.js 는 어플리케이션을 전제로 만들어진 엔진이라서 &lt;code&gt;document&lt;/code&gt; 선언 시 오류가 나지만,&lt;/p&gt;
&lt;p&gt;기본적으로 브라우저 환경의 JavaScript 에서는 &lt;code&gt;window&lt;/code&gt;, &lt;code&gt;document&lt;/code&gt;, ... 등등이 전역 객체로 선언된다.&lt;/p&gt;
&lt;p&gt;이 때, 우리는 &lt;code&gt;document&lt;/code&gt; 객체에서 제공하는 수 많은 메서드로 DOM 의 특정 컴포넌트를 추출하고,&lt;/p&gt;
&lt;p&gt;이를 다룰 수 있다.&lt;/p&gt;
&lt;p&gt;브라우저에서 DOM 을 변화하는 순간, 브라우저도 이에 맞춰 동적으로 렌더링된다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DOM 예시&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TB

html

head

body

html --&amp;gt; head &amp;amp; body

subgraph InnerHead [&amp;quot;InnerHead&amp;quot;]
    meta
    title
    link
    script1
end

head --&amp;gt; InnerHead

subgraph InnerBody [&amp;quot;InnerBody&amp;quot;]
    direction LR
    h1
    p --&amp;gt; a
    div
    div --&amp;gt; img
    script2
end

body --&amp;gt; InnerBody&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;그렇다면, CSSOM 은 무엇인가?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;CSSOM 은 문서의 스타일 관련 (CSS) 정보를 읽고, 이를 수정하기 위한 API 세트이다.&lt;/p&gt;
&lt;p&gt;위에서 JS + DOM 으로 문서 구조와 내용을 읽고 수정하는 것 처럼,&lt;/p&gt;
&lt;p&gt;CSSOM 을 이용하여 JS 에서 문서의 스타일을 읽고 수정 할 수 있다.&lt;/p&gt;
&lt;p&gt;CSS 를 수정하는 방법은 다양한데,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CSSOM 배열 혹은 객체를 변경하는 방법&lt;/li&gt;
&lt;li&gt;DOM 내의 &lt;code&gt;.style.background&lt;/code&gt; 을 이용하여 색상을 바꾸는 방법 (인라인 스타일 변경 )&lt;/li&gt;
&lt;li&gt;... 이 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;CSSOM 자체가 DOM 과 비슷하게 동작하지만, 따로 Rule 이 존재한다.&lt;/p&gt;
&lt;p&gt;CSSOM 을 깊이 이해하고 싶다면, 아래의 링크를 보길 바란다&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://drafts.csswg.org/cssom/#the-cssrulelist-interface&quot;&gt;CSSOM 영어 스펙&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;CSSOM 은 style sheet 를 따라 구성하는데,&lt;/p&gt;
&lt;p&gt;이는 태그, 클래스, id 등등에 따라 매칭되는 DOM 을 스타일링한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;4. (Preload Scanner) : 브라우저는 DOM 을 파싱하며, 필요한 리소스들을 동시에 요청한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;브라우저는 html 파일을 DOM 으로 파싱하는 와중에, 필요한 리소스들을 동시에 요청한다.&lt;/p&gt;
&lt;p&gt;브라우저의 메인 스레드는 DOM 트리를 구성하는 데 집중한다.&lt;/p&gt;
&lt;p&gt;그 와중에 프리로드 스캐너는 DOM 을 스타일링 하는 데 필요한 css 파일이나,&lt;/p&gt;
&lt;p&gt;이미지 구성을 위한 파일이나, JavaScript 파일을 요청한다.&lt;/p&gt;
&lt;p&gt;이미지나 css 파일은 DOM 을 구성하는 데 있어 영향을 미치지는 않는다.&lt;/p&gt;
&lt;p&gt;하지만, JavaScript 는 종종 DOM 을 실제로 건드리는 경향이 있어 동기적으로 가져온다.&lt;/p&gt;
&lt;p&gt;따라서, DOM 에 영향을 미치지 않는 JavaScript 파일의 경우 &lt;code&gt;async&lt;/code&gt; 나 &lt;code&gt;defer&lt;/code&gt; 키워드로 가져와야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;웹 브라우저의 저장소 각종 Storage 들&lt;/h2&gt;
&lt;p&gt;웹 브라우저에는 크게 알려진 저장소가 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;로컬저장소&lt;/li&gt;
&lt;li&gt;세션저장소&lt;/li&gt;
&lt;li&gt;쿠키&lt;/li&gt;
&lt;li&gt;IndexedDB - 새로 배움&lt;/li&gt;
&lt;li&gt;Web Storage - 새로 배움&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;각각의 사용법을 완벽히 알지도 못하고, 내가 알지 못하는 저장소 또한 존재한다고 판별하여 이를 조사한다.&lt;/p&gt;
&lt;p&gt;위와 같은 저장소는 JavaScript API 로 접근이 가능하다. (쿠키는 서버 측에서 &lt;code&gt;httpOnly&lt;/code&gt; 가 아닐 때에만 가능하지 않을까?)&lt;/p&gt;
&lt;p&gt;유저의 브라우저 기기에 저장되는 저장소들은, 서버가 판단하여 각종 목적으로 사용 할 수 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;유저가 어떤 형태로 로그인 했었는지&lt;/li&gt;
&lt;li&gt;유저가 아직 로그인 세션을 잃지 않았는지&lt;/li&gt;
&lt;li&gt;커머스 사이트의 장바구니&lt;/li&gt;
&lt;li&gt;유저의 사이트 설정 기억하기 (Ex - 다크모드, 라이트모드 등)&lt;/li&gt;
&lt;li&gt;사이트 내에서의 활동 기록&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;h3&gt;Cookies&lt;/h3&gt;
&lt;p&gt;MDN 피셜 Old School 이라고 한다. 그 만큼 오래된 역사를 지닌 저장소라는 것이다.&lt;/p&gt;
&lt;p&gt;바로 밑에 Web Storage, IndexedDB 를 말하는 것을 보면, 두 기능을 잘 보여주기 위해 올드스쿨이라고 한 것 같다.&lt;/p&gt;
&lt;p&gt;로컬과 세션과 달리, 쿠키 정보는 요청 시 항상 같이 전달된다.&lt;/p&gt;
&lt;p&gt;웹 서버를 개발할 때 요청 Agent 개발 시, 쿠키의 값을 추출하여 전달 할 필요 없이,&lt;/p&gt;
&lt;p&gt;그냥 요청만 해도 서버에서는 쿠키 정보를 전달받는다.&lt;/p&gt;
&lt;p&gt;현재 웹사이트에서는 세션 ID, 액세스 토큰 및 리프레쉬 토큰에 자주 사용된다.&lt;/p&gt;
&lt;p&gt;쿠키는 도메인을 중심으로 저장하고, EX - &lt;code&gt;naver.com&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;해당 도메인에 요청을 보낼 때 마다 쿠키 정보를 자동으로 보내준다.&lt;/p&gt;
&lt;p&gt;서버는 내 도메인에 할당된 쿠키 정보를 수정, 혹은 추가하거나, 삭제하기 위해 이러한 헤더를 설정한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Set-Cookie: &amp;lt;쿠키-key&amp;gt;=&amp;lt;쿠키-value&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이러한 헤더는 서버 측에서 1 번이 아니라, 여러번 선언하여 설정 할 수도 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Web Storage&lt;/h3&gt;
&lt;p&gt;MDN 에서 새로운 기능으로서 말하는 기능이다.&lt;/p&gt;
&lt;p&gt;Web Storage 라는 새로운 기능이 생겼나? 했는데,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;localStorage&lt;/code&gt;, &lt;code&gt;sessionStorage&lt;/code&gt; 라고 한다. (위에서 말한 저장소)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;localStorage, sessionStorage&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;로컬과 세션의 차이는 &amp;quot;브라우저, 탭이 닫혔을 때, 정보를 저장하는가?&amp;quot; 이다.&lt;/p&gt;
&lt;p&gt;그리로 쿠키와 차이가 있다면, 저장 공간이 쿠키보다는 크며,&lt;/p&gt;
&lt;p&gt;쿠키처럼 자동으로 요청에 첨가하지 않고 선택적으로 첨가 할 수 있다.&lt;/p&gt;
&lt;p&gt;웹 개발 시,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Window.sessionStorage&lt;/code&gt; 혹은 &lt;code&gt;Window.localStorage&lt;/code&gt; 로 접근 할 수 있다.&lt;/p&gt;
&lt;p&gt;한번, &lt;code&gt;localStorage&lt;/code&gt; 로 예시를 들어 보자 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// Web Storage 의 local 에
// Key 는 &amp;quot;myname&amp;quot;, Value 는 &amp;quot;공담형&amp;quot; 이 설정된다.
localStorage.setItem(&amp;quot;myname&amp;quot;, &amp;quot;공담형&amp;quot;);

// local 저장소에서 Key 인 &amp;quot;myname&amp;quot; 인 Value 를 가져온다.
console.log(localStorage.getItem(&amp;quot;myname&amp;quot;));
// 공담형

// local 저장소에서 Key &amp;quot;myname&amp;quot; 을 삭제한다.
localStorage.removeItem(&amp;quot;myname&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;IndexedDB&lt;/strong&gt; (색인 DB)&lt;/p&gt;
&lt;p&gt;IndexedDB 는 복잡한 데이터(큰 데이터) 를 브라우저에 영구적으로 저장 할 수 있는 방법 중 하나라고 한다.&lt;/p&gt;
&lt;p&gt;공식문서를 보니까, 정말 파일 시스템 DB 를 JavaScript 로 접근 할 수 있는 API 를 제공하고 있다.&lt;/p&gt;
&lt;p&gt;이러한 특성 덕분에, 온라인과 오프라인 환경에서 모두 동작 가능한 웹 어플리케이션을 제작 할 수 있다고 한다.&lt;/p&gt;
&lt;p&gt;IndexedDB 는 브라우저 기기의 데이터베이스에 접근하기 위한 API 를 제공한다.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://developer.mozilla.org/ko/docs/Web/API/IndexedDB_API&quot;&gt;MDN (IndexedDB API)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;실제 트랜잭션을 생성하고, 쿼리를 통해 데이터를 가져올 수 있는 예제가 존재한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;이 글을 작성하며 배운 것.&lt;/h2&gt;
&lt;p&gt;웹 사이트를 개발하기 위해서 필연적으로 배워야 하는 것은 웹 사이트가 가진 고유의 기능이다.&lt;/p&gt;
&lt;p&gt;최근 개발 환경의 편의성이 많이 올라가고, 반응형 웹 사이트라는 제목에 많이 이끌렸지만,&lt;/p&gt;
&lt;p&gt;결국 웹 사이트는 단순히 JavaScript 파일에 의해 동작하는 것이 아니라,&lt;/p&gt;
&lt;p&gt;브라우저가 제공하는 API 와 JavaScript 파일과 융합하여 동작하기 때문이다.&lt;/p&gt;
&lt;p&gt;나는 브라우저의 기능과 JavaScript 의 본래 모습 (EX- es5 버전) 을 보기 전에,&lt;/p&gt;
&lt;p&gt;먼저 최신 JS 버전의 React 를 공부했었다.&lt;/p&gt;
&lt;p&gt;하지만, 후반에 가서야 특정 API 들을 &amp;quot;왜&amp;quot; 사용하는지 전혀 알 수 없었다.&lt;/p&gt;
&lt;p&gt;왜 WAS 와 상호작용 하기 위해 특정 설정을 해야 하는지, CORS 에러가 무엇인지,&lt;/p&gt;
&lt;p&gt;웹 페이지에 진입 시 어디서 사용자의 정보를 가져오는 건지,&lt;/p&gt;
&lt;p&gt;모르는 것이 정말 많았지만, 코드를 따라 치기만 할 뿐이었다.&lt;/p&gt;
&lt;p&gt;하지만 나는 이러한 배움의 실수를 다시 하고 싶지 않다.&lt;/p&gt;
&lt;p&gt;다시 기초로 내려 온 것도 3번째 인 것 같다.&lt;/p&gt;
&lt;p&gt;3 번 같은 이유로 실수했다면, 그것은 실수가 아니고 실력이라고 생각하기 때문이다.&lt;/p&gt;
&lt;p&gt;브라우저는 HTML, CSS, JS 를 인식한다.&lt;/p&gt;
&lt;p&gt;우리가 React 를 배울 때 사용하는 TypeScript, JSX, TSX 와 같은 형식은 사용되지 않는다.&lt;/p&gt;
&lt;p&gt;브라우저의 실행 환경을 고려하여 Webpack 을 사용하여 번들링(Bundlering) 하게 되는데,&lt;/p&gt;
&lt;p&gt;이 다음 주제로 번들링을 공부하기로 결정했다.&lt;/p&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;h2&gt;참고 사이트&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;MDN (브라우저는 어떻게 동작하는가)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://developer.mozilla.org/ko/docs/Web/Performance/Guides/How_browsers_work&quot;&gt;https://developer.mozilla.org/ko/docs/Web/Performance/Guides/How_browsers_work&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;MDN (DOM 소개)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://developer.mozilla.org/ko/docs/Web/API/Document_Object_Model/Introduction&quot;&gt;https://developer.mozilla.org/ko/docs/Web/API/Document_Object_Model/Introduction&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;MDN (CSS 객체 모델 - CSSOM)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://developer.mozilla.org/ko/docs/Web/API/CSS_Object_Model&quot;&gt;https://developer.mozilla.org/ko/docs/Web/API/CSS_Object_Model&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;위키피디아 (Web Browser)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://en.wikipedia.org/wiki/Web_browser&quot;&gt;https://en.wikipedia.org/wiki/Web_browser&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;MDN (TCP 핸드셰이크)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://developer.mozilla.org/ko/docs/Glossary/TCP_handshake&quot;&gt;https://developer.mozilla.org/ko/docs/Glossary/TCP_handshake&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Cloudflare 문서 (TLS 핸드셰이크란 무엇일까요?)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.cloudflare.com/ko-kr/learning/ssl/what-happens-in-a-tls-handshake/&quot;&gt;https://www.cloudflare.com/ko-kr/learning/ssl/what-happens-in-a-tls-handshake/&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;MDN (Client-side Storage)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Learn_web_development/Extensions/Client-side_APIs/Client-side_storage&quot;&gt;https://developer.mozilla.org/en-US/docs/Learn_web_development/Extensions/Client-side_APIs/Client-side_storage&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;MDN (HTTP 쿠키)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://developer.mozilla.org/ko/docs/Web/HTTP/Guides/Cookies&quot;&gt;https://developer.mozilla.org/ko/docs/Web/HTTP/Guides/Cookies&lt;/a&gt;&lt;/p&gt;</description>
      <category>잡다 지식</category>
      <category>Browser</category>
      <category>browser storage</category>
      <category>browser tcp</category>
      <category>browser 저장소</category>
      <category>cssom</category>
      <category>dom</category>
      <category>tls</category>
      <category>브라우저</category>
      <category>브라우저 기능</category>
      <category>브라우저 동작 과정</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/213</guid>
      <comments>https://codecreature.tistory.com/213#entry213comment</comments>
      <pubDate>Thu, 15 May 2025 00:25:17 +0900</pubDate>
    </item>
    <item>
      <title>웹 서버는 무엇이고 Local, Production 의 차이는 무엇일까?</title>
      <link>https://codecreature.tistory.com/212</link>
      <description>&lt;h2&gt;제목 : 웹 서버는 무엇이고, local, production 차이는 무엇일까?&lt;/h2&gt;
&lt;hr&gt;
&lt;h3&gt;웹 서버에 대한 내용을 다루는 이유&lt;/h3&gt;
&lt;p&gt;우리는 어플리케이션을 배포 할 때, 항상 Server 를 사용한다.&lt;/p&gt;
&lt;p&gt;이는 보통 알려진 프론트엔드, 백엔드, 할 것 없이, 모든 영역에서 사용한다.&lt;/p&gt;
&lt;p&gt;개발을 위해 테스팅을 할 때도, 배포를 할 때도, 마이크로서비스 또한 서버를 사용한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;나의 의문점은 웹 페이지를 서빙 해 주는 웹 서버 자체에 있었다.&lt;/p&gt;
&lt;p&gt;개발 도구의 편의성이 너무 높아, 내가 어떤 웹 서버를 사용하느냐는 신경 쓸 틈도 없이,&lt;/p&gt;
&lt;p&gt;웹 페이지의 결과를 브라우저에서 너무 잘 보여주고 있었다.&lt;/p&gt;
&lt;p&gt;웹 페이지를 load 하기 위해 가장 먼저 제공하는 파일은 보통 &lt;code&gt;index.html&lt;/code&gt; 이므로,&lt;/p&gt;
&lt;p&gt;그냥 나의 컴퓨터 자체가 &lt;code&gt;index.html&lt;/code&gt; 에서 원하는&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;JavaScript&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CSS&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;html&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;과 같은 파일들을 알아서 load 한다고 생각했다.&lt;/p&gt;
&lt;p&gt;클라이언트는 파일을 요청 (GET or HEAD)할 뿐이지,&lt;/p&gt;
&lt;p&gt;상호작용 하는 과정은 없다고 생각했기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그런데, 생각 해 보니까 클라이언트가 요청하는 경로는 웹 서버가 어떻게 해석하며,&lt;/p&gt;
&lt;p&gt;웹 서버는 수 많은 클라이언트의 요청에 어떻게 대처하는가? 같은 질문이 떠올랐다.&lt;/p&gt;
&lt;p&gt;이미 정적 파일로 제작되었거나, 빌드된 결과에 의해 정적 파일 형태로 생성되었다고 해서,&lt;/p&gt;
&lt;p&gt;웹 결과물을 제공 해 주는 특정 프로그램이 없다는 것이 아니며,&lt;/p&gt;
&lt;p&gt;요청의 분산과 자료 요청에 효과적으로 대응하는 Web Server 프로그램이 필요하다는 것을 깨닫았다.&lt;/p&gt;
&lt;p&gt;WAS (Web Application Server) 를 만드는 데에 치중하다 보니,&lt;/p&gt;
&lt;p&gt;프로그램에 사용하던 Nginx 조차 프론트에서 적극 활용되고 있다는 사실을 잊게 된 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이번에 React 를 제대로 다루기 전에, 배움의 한계점 자체를 높이기 위해 기초를 튼튼히 하기로 결심했다.&lt;/p&gt;
&lt;p&gt;그리고, 최근 웹 제작 라이브러리 혹은 프레임워크의 진화가 이루어지며, CSR 뿐만 아니라, SSR 도 중요하다.&lt;/p&gt;
&lt;p&gt;(Client Side Rendering -- Server Side Rendering)&lt;/p&gt;
&lt;p&gt;빌드된 파일을 정적으로 운용하고, 이를 사용자가 자체 브라우저에서 렌더링한다면 CSR 이며,&lt;/p&gt;
&lt;p&gt;사용자의 렌더링 계산을 줄이고, 쾌활한 사이트를 제공하기 위해 서버에서 계산을 분담한다면 SSR 이다.&lt;/p&gt;
&lt;p&gt;위에서 말한 CSR, SSR 에 대한 나의 의견은 완벽히 정확하지 않으며, 다양한 의미를 담고 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;사용자에게 요청받는 사이트를 제공하며,&lt;/p&gt;
&lt;p&gt;혹은 자주 요청받는 사이트를 캐싱하기도 하며, (서버 내부 계산 효율화)&lt;/p&gt;
&lt;p&gt;사용자의 트래픽을 여러 서버로 분산시키기도 하는 웹 서버를 지금부터 제대로 배우고자 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Web Server 란 무엇인가?&lt;/h2&gt;
&lt;p&gt;먼저, 위키백과에서 정의한 &amp;quot;웹 서버&amp;quot; 에 대한 정의를 말하자면,&lt;/p&gt;
&lt;p&gt;웹 브라우저와 같은 클라이언트로부터 HTTP 요청을 받아들이고,&lt;/p&gt;
&lt;p&gt;HTML 문서와 같은 웹 페이지를 반환하는 &amp;quot;컴퓨터 프로그램&amp;quot; 을 의미한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;대다수의 웹 서버는 Server-side scripting 를 지원한다.&lt;/p&gt;
&lt;p&gt;이는 서버 소프트웨어의 변경 없이도, 웹 서버가 수행 할 동작을&lt;/p&gt;
&lt;p&gt;분리된 Server-side script 언어에 기술 할 수 있다는 의미이다.&lt;/p&gt;
&lt;p&gt;이를 통해, HTML 문서를 동적으로 생성하는 것을 말한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart LR
subgraph WebServer [&amp;quot;Web Server&amp;quot;]
    Process(&amp;quot;HTTP Server Process&amp;quot;)
    File(&amp;quot;File System&amp;quot;)
    File --&amp;gt; Process
end

Network(&amp;quot;Network...&amp;quot;)

Process &amp;lt;--&amp;gt; Network

subgraph Clients [&amp;quot;클라이언트&amp;quot;]
    Client1(&amp;quot;클라이언트 1&amp;quot;)
    Client2(&amp;quot;클라이언트 2&amp;quot;)
    Client3(&amp;quot;클라이언트 3&amp;quot;)

    Client1 ~~~ Client2 ~~~ Client3
end

Network &amp;lt;--&amp;gt; Clients&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;웹 서버는 웹 뿐만 아니라, 프린터, 라우터, 웹 캠과 같은 임베딩 장치에서도 사용된다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Web Server 들의 공통 기능&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;HTTP&lt;/li&gt;
&lt;li&gt;통신 기록&lt;/li&gt;
&lt;li&gt;정적 콘텐츠 관리&lt;/li&gt;
&lt;li&gt;HTTPS 지원&lt;/li&gt;
&lt;li&gt;콘텐츠 압축&lt;/li&gt;
&lt;li&gt;등등...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;웹 서버, 웹 어플리케이션 서버는 모두 https 를 사용한다 (production 에서)&lt;/p&gt;
&lt;p&gt;단순히 웹 서버는 HTML 파일과 정적 파일을 serving 한다는 나의 안일한 생각이 부끄러워지는데,&lt;/p&gt;
&lt;p&gt;생각 해 보면 https 사용을 위한 SSL과, 도메인 인증, 리버스 프록시 지원을 해 준다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Web Server 와 CGI&lt;/h3&gt;
&lt;p&gt;CGI 는 Common Gateway Interface 의 약자이다.&lt;/p&gt;
&lt;p&gt;CGI 는 정적인 리소스를 전달하는 &amp;quot;웹 서버&amp;quot; 상에서, 사용자의 프로그램을 동작시키기 위한 조합이다.&lt;/p&gt;
&lt;p&gt;수많은 웹 서버 프로그램은 CGI 기능을 이용 할 수 있다.&lt;/p&gt;
&lt;p&gt;서버 프로그램에서는 정보를 동적으로 생성하고, 클라이언트에 송신하려는 것이 불가능했다.&lt;/p&gt;
&lt;p&gt;이로 인해, 서버 프로그램에서 다른프로그램을 불러내고, 그 처리 결과를 클라이언트에 송신하는 방법이 고안되었다.&lt;/p&gt;
&lt;p&gt;이를 위해 서버 프로그램과 외부 프로그램과의 연계법을 정한 것이 &lt;strong&gt;CGI&lt;/strong&gt; 라고 한다.&lt;/p&gt;
&lt;p&gt;CGI 는 환경 변수나 표준 입출력을 다룰 수 있는 프로그래밍 언어라면 구별하지 않고 확장하여 이용하는 것이 가능하다.&lt;/p&gt;
&lt;p&gt;실제로, Node.js 환경으로 웹 서버를 만들 수도 있다. (기본 라이브러리, 혹은 Express 로 구현 가능)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CGI 는 아래와 같은 동작을 수행한다.&lt;/strong&gt; - CGI를 경유하여 실행되는 프로그램을 &lt;strong&gt;CGI 프로그램&lt;/strong&gt; 이라 부른다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CGI 프로그램은 웹 서버가 클라이언트로부터 요청에 응답하여 동작한다. (동적 컨텐츠 말하는듯)&lt;/li&gt;
&lt;li&gt;만약에 프로그램에 대응하는 URI 로의 요청이 있다면, 서버는 프로그램을 CGI 의 결정에 따라 호출한다.&lt;/li&gt;
&lt;li&gt;CGI 프로그램의 정보 입력은 명령중 &lt;strong&gt;인수&lt;/strong&gt;, &lt;strong&gt;환경변수&lt;/strong&gt;, &lt;strong&gt;표준 입력&lt;/strong&gt; 에 의해 이루어진다.&lt;/li&gt;
&lt;li&gt;프로그램의 표준 입력에 출력된 데이터는 웹 서버를 경유한 클라이언트에게 보내진다.&lt;/li&gt;
&lt;li&gt;이 데이터는 정당한 HTTP 헤더로 시작해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;개인적으로 생각했을 때, 환경 변수나 표준 입출력을 다룰 수 있는 프로그래밍 언어가 CGI 프로그램이 될 수 있다면,&lt;/p&gt;
&lt;p&gt;우리가 들어봤을 법한 언어들은 거의 전부 CGI 프로그램으로서 만들어 질 수 있다는 이야기이기도 하다.&lt;/p&gt;
&lt;p&gt;저 레벨 언어인 C, C++ 와 같은 언어로 CGI 프로그램을 이루고 웹 서버가 되기도 하지만,&lt;/p&gt;
&lt;p&gt;최상위 레벨 언어인 JavaScript, Python 으로도 웹 서버가 만들어 지는 것을 보니,&lt;/p&gt;
&lt;p&gt;경량화와 속도를 위해서 httpd, nginx 가 사용되고,&lt;/p&gt;
&lt;p&gt;접근성과 편의성을 위해 python 이 사용되는 이유를 알 수 있었다.&lt;/p&gt;
&lt;p&gt;위키백과의 설명 이미지를 mermaid 로 표현하자면,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart LR

Web-Users

HTTP

subgraph Server-Program-Group [&amp;quot;서버 프로그램 그룹&amp;quot;]
    direction TB
    Web-Server(&amp;quot;웹 서버&amp;quot;)
    CGI(&amp;quot;CGI&amp;quot;)
    Gateway-Program(&amp;quot;게이트웨이 프로그램&amp;quot;)
    Web-Server &amp;lt;--&amp;gt; CGI
    CGI &amp;lt;--&amp;gt; Gateway-Program

end

Web-Users &amp;lt;--&amp;gt; HTTP

HTTP &amp;lt;--&amp;gt; Server-Program-Group&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h2&gt;Web Server 는 무엇이 유명할까?&lt;/h2&gt;
&lt;p&gt;알 만한 프로그램도 있고, 최근에 갑작스럽게 떠오르는 Web Server 들도 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Apache httpd - &lt;a target=&quot;_blank&quot; href=&quot;https://httpd.apache.org/&quot;&gt;https://httpd.apache.org/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Nginx - &lt;a target=&quot;_blank&quot; href=&quot;https://nginx.org/&quot;&gt;https://nginx.org/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Cloudflare - &lt;a target=&quot;_blank&quot; href=&quot;https://www.cloudflare.com/ko-kr/&quot;&gt;https://www.cloudflare.com/ko-kr/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;OpenResty - &lt;a target=&quot;_blank&quot; href=&quot;https://openresty.org/en/&quot;&gt;https://openresty.org/en/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;위의 프로그램들은 상용 제품인 것도 있으며, 오픈소스 인 것도 존재한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Apache httpd 웹 서버&lt;/h3&gt;
&lt;p&gt;httpd 이름의 유래는, HTTP Daemon 으로부터 왔다고 한다.&lt;/p&gt;
&lt;p&gt;나는 httpd 를 처음 접한 프로그램이 Docker 였다.&lt;/p&gt;
&lt;p&gt;Docker 이미지 중, httpd 를 사용하는 예제를 따라 해 본적이 있었다.&lt;/p&gt;
&lt;p&gt;그 때는 왜 httpd 라는 프로그램을 사용하는지도 모르고 이미지 컨테이너 실행을 한 뒤,&lt;/p&gt;
&lt;p&gt;80 번 포트에서 웹 페이지를 본 경험이 있다.&lt;/p&gt;
&lt;p&gt;지금에서야 httpd 가 대표적인 웹 서버라는 것을 깨닫게 되었다.&lt;/p&gt;
&lt;p&gt;Apache 재단에서 만들었으니, Java 가 사용되었나? 싶었는데, 프로그램의 대부분은 C 로 이루어져 있다.&lt;/p&gt;
&lt;p&gt;(Apache Topcat 이랑 헷갈린 듯 - 내가)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;httpd - Github&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://github.com/apache/httpd?tab=readme-ov-file&quot;&gt;https://github.com/apache/httpd?tab=readme-ov-file&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;httpd&lt;/strong&gt; 는 들어오는 요청을 다루기 위해 다중 프로세스를 사용하는 웹 서버이다.&lt;/p&gt;
&lt;p&gt;정확히는, 서버 상황에 맞게 동일한 웹 서버를 생성하고,&lt;/p&gt;
&lt;p&gt;각각의 웹 서버는 트래픽에 따라 최소 ~ 최대 로 설정된 쓰레드 풀을 생성하고 유지한다.&lt;/p&gt;
&lt;p&gt;생성된 최소 ~ 최대 쓰레드 풀은 클라이언트로부터 요청을 처리하는 역할을 맡는다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;요약하자면,&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;부모 프로세스 == &lt;strong&gt;싱글 스레드&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;자식 프로세스들 == &lt;code&gt;conf&lt;/code&gt; 에 설정된 &lt;code&gt;ThreadsPerChild + 1&lt;/code&gt; 만큼의 스레드를 생성&lt;/li&gt;
&lt;li&gt;자식 프로세스들은 지정된 서버의 구성을 지시받음.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;만약 시작 서버가 2개이고, ThreadsPerChild 가 20 이라면&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;---
title : httpd 프로그램의 초기 프로세스 현황 (예시)
---
flowchart LR

Parent1{{&amp;quot;Parent&amp;quot;}}

Parent1 --&amp;gt; Child1(&amp;quot;Child1&amp;quot;)

Parent1 --&amp;gt; Child2(&amp;quot;Child2&amp;quot;)

Parent1 --&amp;gt; Child3(&amp;quot;....&amp;quot;)

Parent1 --&amp;gt; Child4(&amp;quot;Child21&amp;quot;)

Parent2{{&amp;quot;Parent&amp;quot;}}

Parent2 --&amp;gt; Child5(&amp;quot;Child1&amp;quot;)

Parent2 --&amp;gt; Child6(&amp;quot;Child2&amp;quot;)

Parent2 --&amp;gt; Child7(&amp;quot;....&amp;quot;)

Parent2 --&amp;gt; Child8(&amp;quot;Child21&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이러한 초기 pstree 형태를 가지게 된다.&lt;/p&gt;
&lt;p&gt;그렇다면, 이 2 개의 서버는 초기에 40개의 동시성 커넥션을 다룰 수 있다.&lt;/p&gt;
&lt;p&gt;나중에 &lt;code&gt;httpd&lt;/code&gt; 프로그램의 설정 파일(&lt;code&gt;xxxx.conf&lt;/code&gt;) 에서 최소, 최대 자식 쓰레드를 기입함으로서,&lt;/p&gt;
&lt;p&gt;리소스 할당을 조절 할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Nginx 웹 서버&lt;/h3&gt;
&lt;p&gt;httpd 의 스레드 하나가 유저의 커넥션 연결을 다루었다면,&lt;/p&gt;
&lt;p&gt;nginx 의 요청 응답 (커넥션) 방식은, &lt;strong&gt;&amp;quot;비동기 이벤트 기반 구조&amp;quot;&lt;/strong&gt; 를 가진다.&lt;/p&gt;
&lt;p&gt;예전에 Docker 와 k8s 를 다룰 때 Nginx 를 접했는데,&lt;/p&gt;
&lt;p&gt;k8s (kubernetes) 에서는 ingress 로 사용하여&lt;/p&gt;
&lt;p&gt;Reverse Proxy 및 Load Balancer 로 사용했었다.&lt;/p&gt;
&lt;p&gt;하지만, httpd 와 마찬가지로 Nginx 도 정적 파일 serving 기능이 존재한다.&lt;/p&gt;
&lt;p&gt;Nginx 에서 각각의 웹 서버에 트래픽을 분산하는 것에 집중하기 위해,&lt;/p&gt;
&lt;p&gt;Nginx 내부 설정 파일에 HTTPS 인증을 위한 SSL 인증서를 저장하여 사용한다. (httpd) 도 가능.&lt;/p&gt;
&lt;p&gt;Nginx 는 가벼우며, 리버스 프록시와 로드 밸런싱에 대한 지원이 풍부하여&lt;/p&gt;
&lt;p&gt;각각의 웹 서버에 트래픽을 분산하는 데 자주 사용된다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Github 코드베이스 (Nginx)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://github.com/nginx/nginx&quot;&gt;https://github.com/nginx/nginx&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Cloudflare 웹 서버?&lt;/h3&gt;
&lt;p&gt;요즘 웹 서버 서비스를 담당하는 순위표에서,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Apache httpd&lt;/li&gt;
&lt;li&gt;Nginx&lt;/li&gt;
&lt;li&gt;IIS - 마이크로소프트&lt;/li&gt;
&lt;li&gt;GWS - 구글 웹 서버&lt;/li&gt;
&lt;li&gt;Cloudflare - 클라우드플레어&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;클라우드플레어의 순위표가 굉장히 높아졌다.&lt;/p&gt;
&lt;p&gt;Cloudflare 의 서비스를 읽어보며 느낀 것은, 위에서 다룬 &amp;quot;웹 서버&amp;quot; 의 기능과 동일하지 않다는 것이다.&lt;/p&gt;
&lt;p&gt;Cloudflare 는 httpd 와 nginx 와 동일한 역할로, 웹 사이트를 제공한다.&lt;/p&gt;
&lt;p&gt;그런데, 그 방식은 가까이서 보았을 때 다르다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;&amp;quot;일반적인 웹 서버와 무엇이 다른가?&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Cloudflare 에서 웹 사이트를 제공하는 방식이 2 개가 존재한다.&lt;/p&gt;
&lt;p&gt;정확히는, &lt;strong&gt;&amp;quot;원본 웹 서버&amp;quot;&lt;/strong&gt; 는 nginx 와 같은 웹 서버로 작성하고, CDN + Reverse-Proxy 로 사용하거나,&lt;/p&gt;
&lt;p&gt;아예 CDN 워커를 만들어서 웹 사이트 정적 자산 (html, css, js, img, 등등..) 을 전달하여,&lt;/p&gt;
&lt;p&gt;원본 서버가 없어도 되게 만든다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CDN&lt;/code&gt; : Content Delivery Network 의 약자. 자주 사용되는 정적 파일을 &amp;quot;캐싱&amp;quot; 하여 제공한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Reverse-Proxy&lt;/code&gt; : 우리가 웹 사이트를 직접 찾아가기 위해 &amp;quot;정확한 주소&amp;quot; 를 알고 가는 것이 아니라, &lt;br/&gt; 리버스 프록시 역할을 하는 서버의 주소에 입력된 URI 에 따라, 해당 주소에 알맞는 웹 서버에 요청을 넣는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;그렇다면, Cloudflare 가 어떻게 웹 서버의 역할을 해 주는고... 하니,&lt;/p&gt;
&lt;p&gt;Cloudflare 는 웹 호스팅의 역할을 해준다.&lt;/p&gt;
&lt;p&gt;원본 웹 서버가 존재한다면, Cloudflare CDN 이 먼저 클라이언트가 요청한 정적 자산이 존재하는지 검색하고,&lt;/p&gt;
&lt;p&gt;존재하지 않는다면, 웹 서버에게 요청을 넣는다. (캐싱)&lt;/p&gt;
&lt;p&gt;그러나, 클라이언트가 요청한 정적 자산이 캐싱된 상태라면, 원본 웹 서버에게 요청을 넣지 않고, 스스로 응답한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;만약에 원본 웹 서버가 없다면, Cloudflare CDN 은 등록된 정적 자산을 기준으로 웹 사이트 호스팅을 한다.&lt;/p&gt;
&lt;p&gt;이러한 방식을 &amp;quot;서버 리스&amp;quot; (Serverless) 라고 한다.&lt;/p&gt;
&lt;p&gt;보안이 중요해진 시대에서, 최근에 디도스 공격을 방어 한 사례로 유명해져&lt;/p&gt;
&lt;p&gt;상용 사이트임에도 불구하고 사용자가 굉장히 많다고 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;지금까지의 내용을 요약하자면,&lt;/h3&gt;
&lt;p&gt;전통적으로 고객과의 커넥션을 직접 관리하여 스레드로 연결하는 웹 서버는 &lt;strong&gt;Apache httpd&lt;/strong&gt; 이며,&lt;/p&gt;
&lt;p&gt;리소스를 아끼고, 중간 서버로서의 기능을 갖춰 모듈처럼 사용할 수 있는 경량화 웹 서버는 &lt;strong&gt;Nginx&lt;/strong&gt; 이다.&lt;/p&gt;
&lt;p&gt;그리고, 상용으로 사용되지만, 대표적으로 서버리스로 웹을 호스팅 할 수 있게 도와주며,&lt;/p&gt;
&lt;p&gt;웹 서버의 자산을 스스로 캐싱하여 전달하고 원본 서버의 계산을 줄일 수 있게 해 주는 것이 &lt;strong&gt;Cloudflare&lt;/strong&gt; 이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;로컬 테스팅 과정에서는 어떤 웹 서버가 사용될까?&lt;/h2&gt;
&lt;p&gt;우선, 우리는 위에서 다양한 웹 서버에 대해서 다루어 보았다.&lt;/p&gt;
&lt;p&gt;&amp;quot;어떤&amp;quot; 환경에서 웹을 개발하냐에 따라서 웹 서버는 달라진다.&lt;/p&gt;
&lt;p&gt;나는 먼저 React 를 개발하기 전에, 웹 서버를 공부하겠다고 말했다.&lt;/p&gt;
&lt;p&gt;React 를 개발 할 때, 대부분의 사람들은 NPM 모듈과 명령어를 통해 React 웹사이트를 볼 것이다.&lt;/p&gt;
&lt;p&gt;(대부분이라고 말한 이유는, React CDN 을 통해서 JS 를 작성하는 사람이 있을 수도 있다.)&lt;/p&gt;
&lt;p&gt;React 는 NPM 에서, &lt;code&gt;create-react-app&lt;/code&gt; 명령어를 통해서 템플릿을 생성한다.&lt;/p&gt;
&lt;p&gt;이 때, React 는 JSX 혹은 TSX 를 사용하며, 스타일링을 위해 다양한 css 확장자를 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;예시로, &lt;code&gt;.scss&lt;/code&gt; 혹은 &lt;code&gt;.sass&lt;/code&gt; 등등이 있다.&lt;/p&gt;
&lt;p&gt;이러한 파일들을 최종 파일인 &lt;code&gt;js&lt;/code&gt;, &lt;code&gt;html&lt;/code&gt;, &lt;code&gt;css&lt;/code&gt; 로 컴파일하기 위해, WebPack 이 사용된다.&lt;/p&gt;
&lt;p&gt;개발 과정에서 발생하는 변경 사항을 항상 빌드하고 빌드 파일을 통해 확인하는 것이 아니라,&lt;/p&gt;
&lt;p&gt;Webpack 의 DevServer 를 통해 확인한다.&lt;/p&gt;
&lt;p&gt;이 때, Webpack 에서 사용되는 기본 개발 서버는 &lt;strong&gt;Express&lt;/strong&gt; 이며,&lt;/p&gt;
&lt;p&gt;선택에 따라 &lt;strong&gt;Fasify&lt;/strong&gt; 로 선택 할 수도 있다.&lt;/p&gt;
&lt;h3&gt;Webpack 이 뭘까?&lt;/h3&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; hef=&quot;https://webpack.kr/&quot;&gt;웹팩 공식 홈페이지&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;위에서 간단히 Webpack 이 React 라이브러리를 컴파일 할 때 어떤 행동을 하는지 작성했지만,&lt;/p&gt;
&lt;p&gt;Webpack 은 정확히 번들링 도구이다. (Bundler)&lt;/p&gt;
&lt;p&gt;우리가 작성한 JSX 혹은 TSX, 혹은 SCSS, SASS 파일, 그리고 JS or TS 파일들은&lt;/p&gt;
&lt;p&gt;빌드 시 정적 파일로 컴파일된다.&lt;/p&gt;
&lt;p&gt;React 라는 라이브러리는 코드를 통해 컴포넌트를 계층화 시켰지만,&lt;/p&gt;
&lt;p&gt;빌드 후에는 하나의 파일로 합쳐진다는 의미이다.&lt;/p&gt;
&lt;p&gt;그렇다면, 개발 폴더에서 작성된 수많은 파일과, 나눠진 컴포넌트들은 어떻게 파트별로&lt;/p&gt;
&lt;p&gt;하나의 파일로 합쳐질까?&lt;/p&gt;
&lt;p&gt;위의 역할을 수행 해 주는 프로그램이 바로 Webpack 이다.&lt;/p&gt;
&lt;p&gt;프로덕션에 올라갈 정적 코드, 그리고 html, css 파일이 어떻게 구성될 지를 결정 해 주는 것이 Webpack 이다.&lt;/p&gt;
&lt;p&gt;혹은, 개발 시 코드 파일의 변화를 감지하고 실시간으로 반영 해 주는 것도, Webpack 이다.&lt;/p&gt;
&lt;p&gt;사실, Webpack 자체도 중~상급 포스팅 주제이기 때문에, 설명하기엔 너무 긴 감이 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;요약하자면&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;로컬에서 웹 서버 개발 시, 어떤 웹을 개발하냐에 따라 웹 서버는 달라질 수도 있다.&lt;/li&gt;
&lt;li&gt;하지만, NPM 과 함께 웹 개발 시, Webpack 을 사용 할 것이다. (EX - React)&lt;/li&gt;
&lt;li&gt;Webpack 의 기본 웹 서버는 Express 이며, Fastify 를 선택 할 수도 있다.&lt;/li&gt;
&lt;li&gt;그러나, 대부분의 경우 Webpack 을 추출하여 사용하는 경우는 없다.&lt;/li&gt;
&lt;li&gt;따라서, 대부분의 경우 Express 를 사용한다고 말할 수 있다.&lt;/li&gt;
&lt;li&gt;(Next.js) 는 잘 모르것다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;h2&gt;프로덕션 과정에서는 어떤 웹 서버가 사용될까?&lt;/h2&gt;
&lt;p&gt;위에서 언급했던 Cloudflare 과 같이, 정적 자산으로 호스팅하는 경우가 굉장히 많아졌다.&lt;/p&gt;
&lt;p&gt;당연히 웹 사이트를 게시하기 위해 서버를 따로 장만하여 운용하는 경우는 거의 없을 것이다.&lt;/p&gt;
&lt;p&gt;옛날에는 WAS(백엔드 or 마이크로서비스) 와 비슷하게, VM 에 서버를 직접 장만하여 연결했다고 하는데,&lt;/p&gt;
&lt;p&gt;요즘은 Vercel 이나, Netlify, Github Pages 와 같이, 정적 리소스를 통해 웹 사이트를 제공한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;CDN 호스팅이 아닌, 이전 방식의 웹 사이트 배포는 어떻게 수행되었을까?&lt;/h3&gt;
&lt;p&gt;웹 사이트 호스팅은 현재 시대에는 정말 쉽다. (25-05-13 기준)&lt;/p&gt;
&lt;p&gt;Vercel, Nelify 와 같은 대표적인 사이트들은 그냥 Github Repo 와 연결해 주기만 해도 끝이기도 하다.&lt;/p&gt;
&lt;p&gt;물론, 도메인 변경과, SSL 새로 지정과 같은 행위들은 해당 프로그램 사이트에서 지정 해 주어야 할 것이다.&lt;/p&gt;
&lt;p&gt;쉽게 쉽게 사이트를 배포하는 법도 알면 정말 좋겠지만, 나는 웹 서버의 동작을 알고자 한다.&lt;/p&gt;
&lt;p&gt;이전 방식의 웹 사이트 배포는, 위에서 다뤘던 Nginx, Apache httpd 를 활용하면 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Nginx, Apache httpd&lt;/strong&gt; 는 오픈 소스이다.&lt;/p&gt;
&lt;p&gt;cdn 을 통해 빠르게 정적 자산을 배포해 주는 Cloudflare 조차도, 스스로 Nginx 를 개조하여 사용한다고 한다.&lt;/p&gt;
&lt;p&gt;다시 돌아와서, Nginx 와 Apache httpd 는 웹 사이트를 배포하는 과정이 비슷하다.&lt;/p&gt;
&lt;p&gt;CGI 를 만족하는 이러한 웹 서버 오픈소스는, 웹 사이트에 해당하는 자산들을 지정된 위치에 배포 해 주어야 한다.&lt;/p&gt;
&lt;p&gt;이 때, https 지원을 위해 SSL 인증서를 등록하거나, HTTP/2 버전을 지원한다.&lt;/p&gt;
&lt;p&gt;각자의 오픈소스는 리소스를 어떻게 할당 할 것인지, 경로는 어떻게 지정 할 것인지 선언하는 파일이 존재한다.&lt;/p&gt;
&lt;p&gt;Apache httpd, nginx 둘 다 &lt;code&gt;.conf&lt;/code&gt; 를 사용한다. 물론, 서로 디렉토리는 당연히 다르다.&lt;/p&gt;
&lt;p&gt;하지만, httpd 의 경우, 설정 파일 확장자가 더 존재한다.&lt;/p&gt;
&lt;p&gt;두 프로그램 모두, 지정된 디렉토리에 존재하는 홈페이지 정적 리소스를 보관하고,&lt;/p&gt;
&lt;p&gt;클라이언트에게 원활히 제공하기 위해 부모 스레드, 자식 스레드를 사용하여 제공한다.&lt;/p&gt;
&lt;p&gt;Nginx 는 C 기반이지만, 마치 Node.js 엔진처럼 비동기로 작동하여, 리소스를 예측하기 쉽다고 한다.&lt;/p&gt;
&lt;p&gt;httpd 와 Nginx 는 서로 다른 동작 방식을 가지기 때문에, 함부로 언급하지는 않겠다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;이 글을 작성하며 배운 것&lt;/h2&gt;
&lt;p&gt;React 를 공부하기 전, 문득 내부의 어려운 요소를 공부하는 과정에서 이어지지 않는 지식을 갖추고자&lt;/p&gt;
&lt;p&gt;연관된 CS 지식을 공부하고 있다. 의문이 드는 요소는 조사하고, 이를 직접 습득하고 있다.&lt;/p&gt;
&lt;p&gt;웹 사이트 배포 및 호스팅이 너무나 쉬워진 세상에서 웹 서버를 공부한다는 것은 다시 돌아가는 행위 일 수도 있다.&lt;/p&gt;
&lt;p&gt;신기술을 습득하기도 어려운 세상에서, 이전 기술을 배우는 것은 사실 취업과 거리가 멀 수도 있다.&lt;/p&gt;
&lt;p&gt;그러나, 나는 새로운 신기술은 항상 튼튼한 기초를 베이스로 생겨난다고 생각한다.&lt;/p&gt;
&lt;p&gt;새로운 기술과 새로운 기술이 합쳐지면 세상 편하게 개발을 할 수는 있겠으나,&lt;/p&gt;
&lt;p&gt;누군가는 기초 지식을 통해 해당 기술을 최적화하여 대체하기 매우 쉽다고 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이 글을 작성하며 &amp;quot;웹 서버&amp;quot; 에 대해서 배웠는데,&lt;/p&gt;
&lt;p&gt;내가 WAS (NestJS) 를 프로덕션에 배포 할 때 사용했던 Nginx 의 역할에 대해서 다시 재고 해 보았다.&lt;/p&gt;
&lt;p&gt;Nginx 를 SSL 터미네이션 프록시 + 리버스 프록시로 사용했는데,&lt;/p&gt;
&lt;p&gt;공식 사이트를 보니, 정말 다양한 기능을 지원하고 있었다.&lt;/p&gt;
&lt;p&gt;개인적인 생각으로, CGI (Common Gateway Interface) 기능을 가진 웹 서버는,&lt;/p&gt;
&lt;p&gt;정적 자산만을 전송하는 것에 집중하지 않고, CGI 프로그램에 훨씬 비중을 둔 서버는 WAS 라고 생각한다.&lt;/p&gt;
&lt;p&gt;웹 서버 --&amp;gt; 웹 어플리케이션 서버(WAS) 인 이유를 알게 된 것 같다.&lt;/p&gt;
&lt;p&gt;특히, React 를 공부하면 항상 Webpack 의 도움을 받게 될 텐데,&lt;/p&gt;
&lt;p&gt;이에 대해 깊이 알지는 못해도, 어떤 역할을 하는지 정확하게 알게 되어서,&lt;/p&gt;
&lt;p&gt;나중에 서버 사이드 라이브러리를 사용 할 때 도움이 될 것이라고 생각한다.&lt;/p&gt;
&lt;p&gt;아직 작성해야 할 글이 너무나 많은데, 나의 학습 속도가 조금 느린 것 같아서 집중해야 할 것 같다.&lt;/p&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;h2&gt;참고 사이트&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;위키백과 (웹 서버)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.wikipedia.org/wiki/%EC%9B%B9_%EC%84%9C%EB%B2%84&quot;&gt;https://ko.wikipedia.org/wiki/%EC%9B%B9_%EC%84%9C%EB%B2%84&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;위키백과 (아파치 HTTP 서버)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.wikipedia.org/wiki/%EC%95%84%ED%8C%8C%EC%B9%98_HTTP_%EC%84%9C%EB%B2%84&quot;&gt;https://ko.wikipedia.org/wiki/%EC%95%84%ED%8C%8C%EC%B9%98_HTTP_%EC%84%9C%EB%B2%84&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;위키백과 (CGI - Common Gateway Interface)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.wikipedia.org/wiki/%EA%B3%B5%EC%9A%A9_%EA%B2%8C%EC%9D%B4%ED%8A%B8%EC%9B%A8%EC%9D%B4_%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4&quot;&gt;https://ko.wikipedia.org/wiki/%EA%B3%B5%EC%9A%A9_%EA%B2%8C%EC%9D%B4%ED%8A%B8%EC%9B%A8%EC%9D%B4_%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;위키백과 (Nginx)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.wikipedia.org/wiki/Nginx&quot;&gt;https://ko.wikipedia.org/wiki/Nginx&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;netcraft (HTTP 서버 랭킹)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.netcraft.com/blog/january-2024-web-server-survey/&quot;&gt;https://www.netcraft.com/blog/january-2024-web-server-survey/&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Web Server 제공 업체, 오픈소스 공식 사이트&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Apache httpd - &lt;a target=&quot;_blank&quot; href=&quot;https://httpd.apache.org/&quot;&gt;https://httpd.apache.org/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Nginx - &lt;a target=&quot;_blank&quot; href=&quot;https://nginx.org/&quot;&gt;https://nginx.org/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Cloudflare - &lt;a target=&quot;_blank&quot; href=&quot;https://www.cloudflare.com/ko-kr/&quot;&gt;https://www.cloudflare.com/ko-kr/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;OpenResty - &lt;a target=&quot;_blank&quot; href=&quot;https://openresty.org/en/&quot;&gt;https://openresty.org/en/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;httpd 깃허브&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://github.com/apache/httpd?tab=readme-ov-file&quot;&gt;https://github.com/apache/httpd?tab=readme-ov-file&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Medium 글 (서버와 인터페이스 - Web Server, CGI, WAS, WSGI 에 대한 이해)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://medium.com/svelte-seoul/%EC%84%9C%EB%B2%84-web-server-cgi-was-wsgi-%EC%97%90-%EB%8C%80%ED%95%9C-%EC%9D%B4%ED%95%B4-2ab0f9bfabd4&quot;&gt;https://medium.com/svelte-seoul/%EC%84%9C%EB%B2%84-web-server-cgi-was-wsgi-%EC%97%90-%EB%8C%80%ED%95%9C-%EC%9D%B4%ED%95%B4-2ab0f9bfabd4&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Medium 글 (Apache HTTP Server -- Multi-process architecture)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://medium.com/brundas-tech-notes/apache-http-server-multi-process-architecture-8fb14438a2ce&quot;&gt;https://medium.com/brundas-tech-notes/apache-http-server-multi-process-architecture-8fb14438a2ce&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;</description>
      <category>Web-Server/웹 지식</category>
      <category>httpd</category>
      <category>NGINX</category>
      <category>production</category>
      <category>react</category>
      <category>server</category>
      <category>Web Server</category>
      <category>기초</category>
      <category>원리</category>
      <category>웹 서버</category>
      <category>웹 서버란</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/212</guid>
      <comments>https://codecreature.tistory.com/212#entry212comment</comments>
      <pubDate>Tue, 13 May 2025 03:22:53 +0900</pubDate>
    </item>
    <item>
      <title>React 를 다시 공부하기 전 스스로에 대한 고찰 및 비판</title>
      <link>https://codecreature.tistory.com/211</link>
      <description>&lt;h2&gt;제목 : React 를 다시 공부하기 전 스스로에 대한 고찰 및 비판&lt;/h2&gt;
&lt;hr&gt;
&lt;h3&gt;이 글을 작성하게 된 계기&lt;/h3&gt;
&lt;p&gt;웹 사이트를 제작하기 위해서 어떤 도구를 사용하는가? 스스로에게 질문 해 보았다.&lt;/p&gt;
&lt;p&gt;현재 (25-05-10) 기준으로, 간단한 웹 사이트를 만들기 위해 사용하는 템플릿은 &lt;strong&gt;React&lt;/strong&gt; 일 것이다.&lt;/p&gt;
&lt;p&gt;Vibe Coding (바이브 코딩) 이라는 패러다임이 현재 유형하는 상황인데,&lt;/p&gt;
&lt;p&gt;이는 코드를 건드리지 않고, 단순히 AI 와 글 혹은 언어로 상호작용 함으로서 특정 어플리케이션을 만들어 가는 것을 의미한다.&lt;/p&gt;
&lt;p&gt;물론, 뒤따라올 기술 부채는 전혀 생각하지 않고 만드는 것이니, 오래 갈 수 있는 프로그램을 작성하는 것은 거의 불가능하다 생각한다.&lt;/p&gt;
&lt;p&gt;기술 부채란, 어떠한 프로그램을 작성하기 위해 이미 존재하는 편리한 프로그램들을 아무 생각 없이 사용하는 것을 의미하는데,&lt;/p&gt;
&lt;p&gt;이 과정에서 사용되는 기술이 추후 고도화를 위해서 오히려 발목을 잡는 상황을 의미한다.&lt;/p&gt;
&lt;p&gt;즉, 지금 사용하기 쉬운 기술을 &lt;strong&gt;&amp;quot;빌려서&amp;quot;&lt;/strong&gt; 작성 한 뒤, 프로그램 고도화를 위해 &lt;strong&gt;&amp;quot;기술로 갚는&amp;quot;&lt;/strong&gt; 상황이라는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;다시 돌아와서,&lt;/strong&gt; React 의 사용 의미와 기술을 알지 못 한 채 웹 사이트를 제작하는 것은,&lt;/p&gt;
&lt;p&gt;결국 웹사이트 고도화 과정 중에 반드시 특정 기술 부채는 반드시 발생하게 될 것이라고 생각한다.&lt;/p&gt;
&lt;p&gt;위의 발언은 개인적인 생각인데, 그 만큼 React 가 HTML, CSS, JavaScript 의 역할을 대체하고 있기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;React 로 기술 부채가 생긴다니, 위험한 발언이 아닌가?&lt;/h3&gt;
&lt;p&gt;React 라이브러리와, React 와 상호작용하는 수많은 모듈들은 정말로 웹 사이트 자체를 만들 수 있다.&lt;/p&gt;
&lt;p&gt;React 로 웹 사이트 제작에 입문하고, &lt;code&gt;npm run start&lt;/code&gt; 를 통해, 로컬에서 자신의 웹 사이트를 보는 것은 정말 신난다.&lt;/p&gt;
&lt;p&gt;공식 문서를 통해 곧바로 &lt;strong&gt;Todo List&lt;/strong&gt; 를 만들 수도 있으며, 관리하기 힘든 상태 변수도 저장 할 수 있다.&lt;/p&gt;
&lt;p&gt;심지어는, 어느정도 풀스택을 지원하는 &lt;strong&gt;Next.js&lt;/strong&gt; 를 사용 할 수도 있으며,&lt;/p&gt;
&lt;p&gt;나중에 핸드폰 어플을 제작하는 &lt;strong&gt;React Native&lt;/strong&gt; 를 배울 수도 있다!&lt;/p&gt;
&lt;p&gt;그런데, 지금까지의 내가 공부 한 과정을 살펴보면, 나 스스로 간과한 부분이 많았다.&lt;/p&gt;
&lt;p&gt;나는, 개인적으로 정말 많은 영어 공식문서를 직접 번역해서 저장하기도 하고,&lt;/p&gt;
&lt;p&gt;특정 프레임워크나 언어를 배우기 위해 AI 보다 공식문서를 먼저 보는 편이기도 하다.&lt;/p&gt;
&lt;p&gt;내가 간과한 것은, 특정 라이브러리, 프레임워크의 &lt;strong&gt;공식문서&lt;/strong&gt; 들이 광고와 비슷하다는 것이다.&lt;/p&gt;
&lt;p&gt;한 번 모든 종류의 광고를 생각 해 보자.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;자신의 단점과, 자신의 서비스를 사용 했을 때 나타날 수 있는 단점을 알려주는 광고가 존재하는가?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;라이브러리와 프레임워크들의 공식 문서 또한 비슷하다.&lt;/p&gt;
&lt;p&gt;이러한 공식 문서가 작성 될 수 밖에 없는 이유 또한 이해하고 있는데,&lt;/p&gt;
&lt;p&gt;자신의 라이브러리, 혹은 프레임워크가 대중성을 지녀 오래가길 원하기 때문이다.&lt;/p&gt;
&lt;p&gt;이미 위의 기술에 대한 한계점을 알 때 까지 공부했을 때는, 같은 기술의 라이브러리나 프레임워크로 교체하기가 정말 어렵다.&lt;/p&gt;
&lt;p&gt;React 또한, &amp;quot;웹 페이지 제작&amp;quot; 이라는 기술 도메인에 있어 막강한 위치를 가지고 있다.&lt;/p&gt;
&lt;p&gt;그러나, &lt;strong&gt;React&lt;/strong&gt; 또한, &lt;code&gt;index.html&lt;/code&gt; 를 제공하는 웹 서버를 만드는 일종의 도구라는 것이다.&lt;/p&gt;
&lt;p&gt;사이트를 구성하는 컴포넌트는 &lt;code&gt;index.html&lt;/code&gt; 내부의 &lt;code&gt;&amp;lt;div id=&amp;quot;root&amp;quot;&amp;gt;...&amp;lt;/div&amp;gt;&lt;/code&gt; 내부에 들어가고,&lt;/p&gt;
&lt;p&gt;동적 컴포넌트와 사이트를 구성하기 위해 &lt;code&gt;index.html&lt;/code&gt; 내부에서 &lt;code&gt;&amp;lt;script src=&amp;quot;....&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt; 문구를 통해 JS 를 불러온다.&lt;/p&gt;
&lt;p&gt;사이트, 컴포넌트의 styling 을 위해 &lt;code&gt;index.html&lt;/code&gt; 내부에서 CSS 를 구성한다.&lt;/p&gt;
&lt;p&gt;게다가, React 는 최종 결과물을 서버에 배포하기 위해 빌드하는 과정에서, 특정 builder 들을 선택한다.&lt;/p&gt;
&lt;p&gt;우리가 코드로 스타일링 한 &lt;code&gt;sass&lt;/code&gt;, &lt;code&gt;scss&lt;/code&gt; 등등 CSS 파일들도, 결국은 &lt;code&gt;.css&lt;/code&gt; 로 만들어 져야 하기 때문이고,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;JSX&lt;/code&gt; 혹은 &lt;code&gt;TSX&lt;/code&gt; 파일로 제작한 컴포넌트들도, 결국은 &lt;code&gt;.js&lt;/code&gt; 로 변형되어야 하기 때문이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;그래서, 나에게 이런 질문을 해 보았다.&lt;/strong&gt; :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;quot;나는 위와 같은 작업들이 왜 일어나는지 알고 있나?&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;React 가 왜 기존 방식의 웹 페이지 제작을 JSX 로 제작하는가?&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;JS or TS 코드로 작성된 React 는, 어떻게 HTML 로 변형 될 수 있었나?&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;React 가 웹 사이트를 제작하는 대표적인 라이브러리로 성장 할 수 있던 이유가 무엇인가?&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;부끄럽게도, 나는 위에 작성한 스스로의 질문들을 정확히 답변 할 수 없었다.&lt;/p&gt;
&lt;p&gt;이전과 같이 &amp;quot;알려주는 대로&amp;quot;, &amp;quot;흐름 대로&amp;quot; 공부하게 된다면,&lt;/p&gt;
&lt;p&gt;어느 순간 React 에서 다루는 전역 변수 관리, 데이터 흐름 관리, 아름다운 레이아웃을 만드는 과정이&lt;/p&gt;
&lt;p&gt;다시 잊혀 질 것이 분명했다. 이유는, &amp;quot;왜 이렇게 작성되는지&amp;quot; 모르기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;그럼 이번에는 어떻게 React 를 배울 것인가?&lt;/h3&gt;
&lt;p&gt;React 에서 만들어 놓은 웹 서버 제작 방식은 정말 편리하다.&lt;/p&gt;
&lt;p&gt;그리고, 다양한 NPM 모듈과도 결합 할 수 있게 제작되었다.&lt;/p&gt;
&lt;p&gt;데이터 fetching 후, 컴포넌트에 데이터를 뿌리고, 컴포넌트에 속한 하위 컴포넌트 또한 데이터를 받을 수도 있다.&lt;/p&gt;
&lt;p&gt;React 를 대표하는 Virtual DOM (가상 DOM) 덕분에,&lt;/p&gt;
&lt;p&gt;변화가 필요한 컴포넌트와 하위 컴포넌트가 전부 다시 렌더링 되지 않고, 변화된 컴포넌트들만 변경한다.&lt;/p&gt;
&lt;p&gt;이러한 기능들은 기존의 웹 제작 방식에 존재하던 문제를 쉽게 해결 할 수 있게 해 주었다.&lt;/p&gt;
&lt;p&gt;그러나, React 자체를 &amp;quot;웹을 제작하는 방식&amp;quot; 으로 인식하지 않고,&lt;/p&gt;
&lt;p&gt;웹 서버를 제작하기 위한 &amp;quot;특정한 방법론&amp;quot; 으로 생각하며 배울 것이다.&lt;/p&gt;
&lt;p&gt;즉, HTML, CSS 기초를 배우고, HTML 이 JS src 를 불러오는 과정을 배우고,&lt;/p&gt;
&lt;p&gt;웹 사이트 기본적으로 가지고 있는 기능들을 익히고 들어 갈 것이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;느린 방식으로 학습하는 이유는,&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;결국 React 또한 웹의 기본 기능을 참고하여 만들어 진 라이브러리이기 때문이다.&lt;/p&gt;
&lt;p&gt;나는 React 고도화 과정에서 기술적 스트레스를 받았다. (왜 사용하는지 몰라 외웠기 때문)&lt;/p&gt;
&lt;p&gt;즉, 기본 기능을 몰라 React 를 배우는 과정에서조차 &lt;strong&gt;(기술적 부채)&lt;/strong&gt; 가 발생 한 것이다.&lt;/p&gt;
&lt;p&gt;이러한 상황을 방지하고자 기초를 정말 튼튼히 하고자 하는 목적도 있다.&lt;/p&gt;
&lt;p&gt;또 다른 목적도 있는데, 바로 오픈소스 기여이다.&lt;/p&gt;
&lt;p&gt;나는 리액트가 처음부터 다양한 기능을 가졌다고 생각하지 않는다.&lt;/p&gt;
&lt;p&gt;많은 사람들이 리액트를 사용하며 겪은 한계를 바탕으로, 리액트가 휼륭한 라이브러리가 되었다고 생각한다.&lt;/p&gt;
&lt;p&gt;한계를 인지하기 위해서는 웹이 가진 기능을 알아야 하고, 기여하기 위해서는 라이브러리 자체를 이해해야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;기초는 어떻게 쌓을 것인가?&lt;/h3&gt;
&lt;p&gt;굉장히 신중해야 하는 기로라고 생각한다.&lt;/p&gt;
&lt;p&gt;여기서 어떤 기초를 쌓느냐에 따라 내가 React 를 활용 할 수 있는 한계가 정해 질 것이다.&lt;/p&gt;
&lt;p&gt;다행인 것은, 내가 NestJS 를 익히는 과정에서 JavaScript 와, TypeScript 에 대한 이해도가 높다는 것이다.&lt;/p&gt;
&lt;p&gt;또한, Node.js 환경에서의 스레드를 생성하고, 해당 스레드를 WASM(웹 어셈블리 모듈) 로 실행 하는 과정까지 다루고 글을 작성했다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;package.json&lt;/code&gt; 의 역할, cli 스크립팅, &lt;code&gt;tsconfig.json&lt;/code&gt; 에서의 설정을 모두는 아니지만, 중요한 것은 알고 있다.&lt;/p&gt;
&lt;p&gt;따라서, 나는 앞으로 웹 개발을 진행하기 전 쌓게 될 지식은 다음과 같다 :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;웹 서버의 존재 자체&lt;/li&gt;
&lt;li&gt;브라우저의 웹 렌더링 과정&lt;/li&gt;
&lt;li&gt;브라우저가 가진 기능&lt;/li&gt;
&lt;li&gt;브라우저 DOM, CSSOM&lt;/li&gt;
&lt;li&gt;WebPack (번들러) - 외에도 어떤 번들러가 유행하는지&lt;/li&gt;
&lt;li&gt;CSS 레이아웃&lt;/li&gt;
&lt;li&gt;HTML, CSS 태그&lt;/li&gt;
&lt;li&gt;Flex, Grid 레이아웃 기초&lt;/li&gt;
&lt;li&gt;CommonJS 와 다른 ESM 의 정보&lt;/li&gt;
&lt;li&gt;JSX, TSX 컴파일 과정&lt;/li&gt;
&lt;li&gt;Custom Hook 고도화&lt;/li&gt;
&lt;li&gt;전역 변수 관리 라이브러리 알고리즘 이해 (어떤 브라우저 기능을 이용하는지)&lt;/li&gt;
&lt;li&gt;컴포넌트에 CSS 를 적용하기 위한 방법론 공부 (EX- &lt;code&gt;styled-component&lt;/code&gt; or &lt;code&gt;vanilla-javascript&lt;/code&gt; or &lt;code&gt;sass&lt;/code&gt; 등등)&lt;/li&gt;
&lt;li&gt;웹 서버의 유닛 테스트, E2E 테스트&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;사실 1 시간을 더 생각해 보면 공부 할 리스트가 더 늘어 날 것이다.&lt;/p&gt;
&lt;p&gt;그러나, 그것은 위에 대한 정보들을 블로그 글로 남기면서, 차차 진행하기로 결정했다.&lt;/p&gt;
&lt;br/&gt;

&lt;hr&gt;</description>
      <category>Web-Server/웹 지식</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/211</guid>
      <comments>https://codecreature.tistory.com/211#entry211comment</comments>
      <pubDate>Sat, 10 May 2025 23:08:44 +0900</pubDate>
    </item>
    <item>
      <title>TypeORM CLI 와 데이터베이스 마이그레이션</title>
      <link>https://codecreature.tistory.com/210</link>
      <description>&lt;h1&gt;제목 : TypeORM CLI 와 데이터베이스 마이그레이션&lt;/h1&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;TypeORM&lt;/strong&gt; 은 어디서 마주치게 될까?&lt;/p&gt;
&lt;p&gt;혹시 이 글을 읽고 있는 독자가 &lt;strong&gt;TypeORM&lt;/strong&gt; 에 대해서 모를 수 있기 때문에 특징을 설명하자면,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;데코레이터를 사용한다. - (Java 에 익숙한 분들은 &amp;quot;애너테이션&amp;quot; 으로 인식하면 됩니다.)&lt;ul&gt;
&lt;li&gt;예시 : &lt;code&gt;@UseInerceptor(...)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;데코레이터로 정의된 클래스 속성을 이용하여 데이터베이스에 &amp;quot;자동으로&amp;quot; 스키마를 생성한다.&lt;/li&gt;
&lt;li&gt;TypeORM 에서 제공하는 추상 메서드 (EX - &lt;code&gt;findOne&lt;/code&gt;) 를 통해 &lt;br/&gt; 각 데이터베이스의 메서드 사용 차이가 없다. (거의)&lt;ul&gt;
&lt;li&gt;&lt;code&gt;find&lt;/code&gt;,&lt;code&gt;findOne&lt;/code&gt;,&lt;code&gt;save&lt;/code&gt; 등등...&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TypeORM&lt;/code&gt; 을 사용할 때, &lt;code&gt;reflect-data&lt;/code&gt; 라이브러리는 필수이다.&lt;ul&gt;
&lt;li&gt;엔티티 등록을 위해 직접 등록하지 않고, &lt;code&gt;@Entity()&lt;/code&gt; 와 같이 등록한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reflect-data&lt;/code&gt; 라이브러리와 특정 데코레이터를 사용하여 매핑 정보를 수집한다.&lt;ul&gt;
&lt;li&gt;프레임워크가 시작하면서 &lt;code&gt;reflect-data&lt;/code&gt; 를 통해 구조를 수집, 및 데이터를 주입한다.&lt;/li&gt;
&lt;li&gt;또한, 각 컴포넌트 간의 의존성을 파악하여 계층을 구성 해 준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;다시 돌아와서,&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;TypeORM 을 접하게 되는 가장 큰 경로는 &lt;strong&gt;NestJS&lt;/strong&gt; 를 사용하면서부터라고 생각한다.&lt;/p&gt;
&lt;p&gt;그 이유가 뭘까?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NestJS&lt;/strong&gt; 는 &lt;code&gt;reflect-metadata&lt;/code&gt; 라이브러리를 적극 활용하여&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@Controller&lt;/code&gt;, &lt;code&gt;@Injectable&lt;/code&gt;, &lt;code&gt;@Inject&lt;/code&gt; 외의 수 많은 데코레이터를 활용한다.&lt;/p&gt;
&lt;p&gt;위의 데코레이터들은 비즈니스 서비스 계층을 이루게 되는데,&lt;/p&gt;
&lt;p&gt;단순히 데코레이터가 선언되었다고 비즈니스 서비스 계층을 이루진 않는다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NestJS&lt;/strong&gt; 는, 수집된 &amp;quot;클래스&amp;quot;, &amp;quot;메서드&amp;quot;, &amp;quot;파라미터&amp;quot; 와 같은 정보를 메타데이터로 저장 한 뒤,&lt;/p&gt;
&lt;p&gt;순차적으로 계층을 구성한다.&lt;/p&gt;
&lt;p&gt;우리가 직접 서비스 레이어 계층과 모듈을 구성하는 것은 맞지만, 대부분의 흐름을 프레임워크에 맡기는 것이다.&lt;/p&gt;
&lt;p&gt;이를 통해, 데이터베이스 연결과 마이그레이션 과정을 맡길 수 있다는 장점이 크게 작용한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;TypeORM 과 reflect-data 라이브러리&lt;/h2&gt;
&lt;p&gt;TypeORM 은 방금 위에서 말한 과정과 매우 흡사하다.&lt;/p&gt;
&lt;p&gt;TypeORM 은 &lt;code&gt;@Entity()&lt;/code&gt; 데코레이터와, 내부 클래스에 선언된 속성들의 데코레이터(&lt;code&gt;Column()&lt;/code&gt;)들도 인식한다.&lt;/p&gt;
&lt;p&gt;여러 개의 &lt;code&gt;@Entity()&lt;/code&gt; 로 선언된 클래스와, 연관되어 있는 다른 &lt;code&gt;@Entity()&lt;/code&gt; 와의 관계를 인식하여&lt;/p&gt;
&lt;p&gt;라이브러리와 호환되는 다양한 데이터베이스들을 쉽게 Migration 할 수 있게 해 준다.&lt;/p&gt;
&lt;p&gt;모듈에 메서드로 직접 등록하지 않고, 동떨어진 데코레이터에 반응 할 수 있게 해 주는 것은&lt;/p&gt;
&lt;p&gt;&lt;code&gt;tsconfig.json&lt;/code&gt; 의 옵션과도 관련이 있는데,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;experimental-decorator&lt;/code&gt; 가 &lt;code&gt;true&lt;/code&gt; 여야 한다.&lt;ul&gt;
&lt;li&gt;아직 정식 스펙에 추가되지 않은 데코레이터 &lt;code&gt;@....&lt;/code&gt; 를 사용하기 위해서이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;emitDecoratorMetadata&lt;/code&gt; 가 &lt;code&gt;true&lt;/code&gt; 여야 한다.&lt;ul&gt;
&lt;li&gt;이는 &lt;code&gt;reflect-metadata&lt;/code&gt; 와 관련된 라이브러리로, 데코레이터의 메타데이터를 JS 로 내보낸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;데코레이터라는 개념은 TS 에서 사용되지만, 정식 스펙에 추가되지는 않았기 때문이다.&lt;/p&gt;
&lt;p&gt;위의 옵션이 존재해야만, 데코레이터를 사용 할 수 있고,&lt;/p&gt;
&lt;p&gt;데코레이터 로직 내부에서 메타데이터를 등록 할 수 있게 해 주는 것은 &lt;code&gt;relfect-data&lt;/code&gt; 라이브러리이다.&lt;/p&gt;
&lt;p&gt;따라서, 다양한 메타데이터 데코레이터를 이용하여 의존성과 모듈을 등록하는 NestJS,&lt;/p&gt;
&lt;p&gt;그리고 동일하게 데코레이터를 사용하는 &lt;code&gt;TypeORM&lt;/code&gt; 라이브러리는 서로 공생관계에 있다고 할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;결국 단독으로 사용해야 하는 TypeORM&lt;/h2&gt;
&lt;p&gt;혹시라도, 여기서 Express + TypeORM 을 사용하고 있는 분이라면,&lt;/p&gt;
&lt;p&gt;이미 알고 있는 내용 일 수도 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;내가 위의 소제목 &amp;quot;결국 단독으로 사용 해야 하는 TypeORM&amp;quot; 이라는 문구를 달아놓은 이유는,&lt;/p&gt;
&lt;p&gt;마지막 프로덕션 단계에서 &lt;strong&gt;TypeORM&lt;/strong&gt; 을 단독으로 사용할 줄 알아야 하기 때문이다.&lt;/p&gt;
&lt;p&gt;NestJS 는 &lt;code&gt;typeorm&lt;/code&gt;, &lt;code&gt;@nestjs/typeorm&lt;/code&gt; 라이브러리로 편하게 실시간 마이그레이션이 가능하고,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;app.module.ts&lt;/code&gt; 와 같은 파일에서 &lt;code&gt;typeorm&lt;/code&gt; 과 내가 원하는 데이터베이스에 연결하기 위한 리소스를&lt;/p&gt;
&lt;p&gt;매우 간편하게 작성 할 수 있다.&lt;/p&gt;
&lt;p&gt;물론, 이 과정은 유닛 테스팅, E2E 테스팅, 개발 까지의 단계에서 해당한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;프레임워크 내부에서 간편하게 엔티티 클래스를 작성하고, 실행하면 내가 만든 코드에 따라&lt;/p&gt;
&lt;p&gt;실시간으로 변하는 컬럼을 볼 수 있다. 이는 &lt;code&gt;typeorm&lt;/code&gt; 의 &lt;code&gt;synchronize&lt;/code&gt; 옵션이 &lt;code&gt;true&lt;/code&gt; 이기 때문이다.&lt;/p&gt;
&lt;p&gt;테스팅과 개발 과정에서는, 프로덕션이 아니기 때문에, 대부분의 경우 실행과 동시에 바뀐 데이터 구조를 적용해도 괜찮을 수도 있다.&lt;/p&gt;
&lt;p&gt;그러나, Production 과정을 생각 해 보자. 과연 Production 데이터베이스 스키마를 설정하면서,&lt;/p&gt;
&lt;p&gt;방금 만든 컬럼 혹은 특정 속성을 곧바로 적용할까? 서비스가 터질 수도 있는데?&lt;/p&gt;
&lt;p&gt;위의 상황은 실제로 팀 프로젝트를 진행하면서 겪었던 문제이다.&lt;/p&gt;
&lt;p&gt;이 과정은 &lt;code&gt;TypeORM&lt;/code&gt; 에서 지원하는 CLI 를 통해 직접 마이그레이션 과정을 옮겨야 한다.&lt;/p&gt;
&lt;p&gt;내가 위에서 Express 와 TypeORM 을 사용 해 본 사람은 이미 알 내용 일 수도 있다는 이야기는,&lt;/p&gt;
&lt;p&gt;우리가 &lt;code&gt;TypeORM.forRoot(...)&lt;/code&gt; 에 들어가야 하는 &lt;code&gt;DataSource&lt;/code&gt; 를 따로 파일을 만들어서 관리해야 하기 때문이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;이제와서, TypeORM 옵션을 다른 파일로 옮겨두어야 한다고?&lt;/strong&gt; 라고 생각했다.&lt;/p&gt;
&lt;p&gt;그런데, TypeORM 마이그레이션을 안전하게 수행하기 위해서는,&lt;/p&gt;
&lt;p&gt;따로 &lt;code&gt;DataSource&lt;/code&gt; 를 &lt;code&gt;export&lt;/code&gt; 혹은, &lt;code&gt;module.exports&lt;/code&gt; 로 내보낸 단독 파일이 필요하다.&lt;/p&gt;
&lt;p&gt;데이터베이스 스키마 마이그레이션 과정은 &lt;code&gt;typeorm&lt;/code&gt; 명령어로 진행된다.&lt;/p&gt;
&lt;p&gt;이 과정에서 &lt;code&gt;typeorm&lt;/code&gt; 은 파일에 적시된 &lt;code&gt;DataSource&lt;/code&gt; 를 읽어야 한다.&lt;/p&gt;
&lt;p&gt;그러나 만약 &lt;code&gt;app.module.ts&lt;/code&gt; 와 같은 장소에 &lt;code&gt;TypeORM.forRoot(...)&lt;/code&gt; 로 작성 되어 있다면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;typeorm&lt;/code&gt; CLI 는 &lt;code&gt;DataSource&lt;/code&gt; 를 읽어 올 수가 없다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;그래서 내가 하고 싶은 말은,&lt;/h2&gt;
&lt;p&gt;NestJS 라는 프레임워크와 &lt;code&gt;typeorm&lt;/code&gt; 라이브러리가 하나의 몸체라고 생각하면 오산이라는 것이다.&lt;/p&gt;
&lt;p&gt;NestJS 의 메타데이터 주입 기능과 &lt;code&gt;typeorm&lt;/code&gt; 에서의 메타데이터 추출 및 주입 기능이 매우 자연스러우나,&lt;/p&gt;
&lt;p&gt;그건 어디까지나 NestJS 의 훌륭한 지원에서 비롯된 것이며, &lt;code&gt;typeorm&lt;/code&gt; 을 프로덕션에서 안전하게 사용하려면&lt;/p&gt;
&lt;p&gt;결국 &lt;code&gt;DataSource&lt;/code&gt; 를 따로 파일로 빼서 관리해야 한다는 것이다.&lt;/p&gt;
&lt;p&gt;그래야, 나중에 &lt;code&gt;npm run typeorm:generate -- -d .....&lt;/code&gt; 와 같은 스크립트로&lt;/p&gt;
&lt;p&gt;마이그레이션 파일을 만들 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;code&gt;npm run typeorm:generate -- -n ...&lt;/code&gt; 의 &lt;code&gt;-n&lt;/code&gt; 옵션은 &lt;br/&gt; 공식문서에도 없었으므로, 아마 &lt;i&gt;deprecated&lt;/i&gt; 되지 않았나 싶습니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;br/&gt;

&lt;h3&gt;메타데이터 주입으로 인해 로직을 보기 어렵다면, 이 npm 패키지를 참조해 보자&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;NPM Sequelize 모듈의 공식 사이트&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://sequelize.org/&quot;&gt;https://sequelize.org/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NPM 지원중인 mysql 모듈의 mysqljs/mysql 깃허브 코드 사이트&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://github.com/mysqljs/mysql&quot;&gt;https://github.com/mysqljs/mysql&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;내가 왜 같은 기능을 하는 Sequelize 라이브러리 외, mysql 라이브러리를 참조했냐면,&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sequelize&lt;/strong&gt; 라이브러리는 &lt;code&gt;reflect-metadata&lt;/code&gt; (데코레이터) 를 사용하지 않는 버전의 &lt;code&gt;typeorm&lt;/code&gt;  이라고 생각한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mysql&lt;/strong&gt; 라이브러리는 &lt;strong&gt;Sequelize&lt;/strong&gt; 나, &lt;strong&gt;TypeORM&lt;/strong&gt; 모두 필요로 하는 모듈이다.&lt;/p&gt;
&lt;p&gt;즉, 말하고자 하는 것은, 어떠한 ORM 패키지를 선택하든간에,&lt;/p&gt;
&lt;p&gt;내부에는 데이터베이스 순수 연결 로직이 작성되어 있지 않다는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;mysql&lt;/code&gt; 모듈 코드를 보면, 커넥션 Pool 생성 과정과 쿼리 커밋 과정을 날 것으로 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sequalize&lt;/code&gt; 모듈 코드를 보면, 다양한 DB에 연결하기 위한 공통된 비즈니스 로직을 어떻게 처리했는지 볼 수 있다.&lt;/p&gt;
&lt;p&gt;이어서 &lt;code&gt;typeorm&lt;/code&gt; 모듈 코드를 본다면, 데이터 마이그레이션을 위한 메타데이터를 어떻게 중앙에 모아 처리하는지 볼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;TypeORM 데이터 리소스 이전 과정 (NestJS)&lt;/h2&gt;
&lt;p&gt;우선 먼저 TypeORM 에서 내가 어떤 개발 데이터베이스를 사용하는지 알아야 한다.&lt;/p&gt;
&lt;p&gt;나는 &lt;code&gt;sqlite&lt;/code&gt; 데이터베이스를 통해 개발 스키마를 검증하고 있는 상황이다.&lt;/p&gt;
&lt;p&gt;그리고, 프로덕션 상황에서는 &lt;code&gt;postgres&lt;/code&gt; 데이터베이스를 사용한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;프로젝트 구조 상황&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;&amp;lt;rootDir&amp;gt;
  /dist
    &amp;lt;src 가 빌드된 JS 파일들..&amp;gt;
  /src
    main.ts # 어플 bootstrap 과정
    data-source.ts # 중요!!!! typeorm 마이그레이션을 위해 따로 떼어놓은 파일
    app.module.ts # 어플에 적용될 모든 모듈과 설정을 집약 해 놓은 파일
    /users # 유저 도메인 처리
      /...
    /reports # 차량 보고서 도메인 처리
      /...
    /공통 모듈 # 인터셉터, 미들웨어, 공통 함수 등등...
  package.json # 개발 및 그냥 의존성, jest 설정 등등
  tsconfig.json # NestJS 개발을 위한 ts 설정들&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;app.module.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;TypeOrmModule.forRoot({
  type: &amp;#39;sqlite&amp;#39;,
  database: process.env.NODE_ENV === &amp;#39;test&amp;#39; ? &amp;#39;test.sqlite&amp;#39; : &amp;#39;db.sqlite&amp;#39;,
  entities: [User, Report],
  synchronize: true,
}),&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;TypeOrmModule.forRoot(...)&lt;/code&gt; 를 통해,&lt;/p&gt;
&lt;p&gt;NestJS 프로젝트에서 사용될 엔티티 매핑을 코드로 적용하고 있는 상황이다.&lt;/p&gt;
&lt;p&gt;이는 따로 파일로 빼낸 상황은 아니며, 엔티티 매핑 또한 클래스로 적용하고 있는 상황이다.&lt;/p&gt;
&lt;p&gt;프로덕션에 적용하지 않는 &lt;code&gt;synchronize&lt;/code&gt; 가 &lt;code&gt;true&lt;/code&gt; 였으니,&lt;/p&gt;
&lt;p&gt;나는 development 와 test 환경에서, 즉시 마이그레이션이 적용되었다는 것을 알 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;data-source.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// app.module.ts 에, 따로 TypeOrmModule.forRoot 로 적용하기 위해 타입을 사용
import { DataSource, DataSourceOptions } from &amp;quot;typeorm&amp;quot;;

// 현재 데이터 마이그레이션 하는 상황을 추출 &amp;quot;test&amp;quot; or &amp;quot;development&amp;quot; or &amp;quot;production&amp;quot;
const env = process.env.NODE_ENV;

/**
typeorm 명령어를 통한 데이터 마이그레이션 수행 시, (&amp;quot;development&amp;quot; or &amp;quot;production&amp;quot;)
데이터 마이그레이션은 &amp;quot;빌드된 파일&amp;quot; 로부터 실행한다는 것이다.
이를 통해 갑자스럽게, 혹은 인가되지 않은 엔티티 파일이 실제 DB 서버에 추가되는 것을 방지 할 수 있다.

밑의 옵션들은, Nest 기본 시작에서는 사용하지 않으며, typeorm 명령어 마이그레이션 시 참조된다.
*/
const dbConfig = {
  migrations: env === &amp;quot;test&amp;quot; ? [&amp;quot;src/migrations/*.ts&amp;quot;] : [&amp;quot;dist/migrations/*.js&amp;quot;],
  cli: {
    migrationsDir: env === &amp;quot;test&amp;quot; ? &amp;quot;src/migrations&amp;quot; : &amp;quot;dist/migrations&amp;quot;
  },
} as Partial&amp;lt;DataSourceOptions&amp;gt;;
// DataSourceOptions 로 선언하면, 해당 객체에 대한 &amp;quot;모든&amp;quot; 옵션을 적어주어야 한다.
// 따라서, TS 의 문법인 Partial 을 이용하여, 입력하지 않은 옵션들도 존재하게 만든다. (꼼수 같긴 하다)

// typeorm CLI 와 NestJS 설정을 동시에 고려 한 옵션이다.
switch(env) {
  case &amp;#39;development&amp;#39;:
    Object.assign(dbConfig, {
      type: &amp;#39;sqlite&amp;#39;,
      database: &amp;#39;db.sqlite&amp;#39;,

      // 개발 상황을 고려하여, &amp;quot;src/**/*.entity.ts&amp;quot; 로 해도 되지만,
      // 나는 실제 개발 상황을 &amp;quot;빌드된 엔티티만&amp;quot; 인식 할 수 있도록 만든 것이다.
      // 개발하면서, &amp;quot;start:dev&amp;quot; 시, 실시간으로 변경되는 엔티티 코드가 적용되지 않도록 만든 것이다.
      entities: [&amp;quot;dist/**/*.entity.js&amp;quot;],

      // 현재 &amp;quot;xx.entity.ts&amp;quot; 로 존재하는 파일들이 시작, 혹은 코드 변경 시 적용되지 않도록 설정.
      synchronize : false,
    })
    break;
  case &amp;#39;test&amp;#39;:
    Object.assign(dbConfig, {
      type: &amp;#39;sqlite&amp;#39;,
      database: &amp;#39;test.sqlite&amp;#39;,
      dropSchema : true,
      entities: [&amp;quot;src/**/*.entity.ts&amp;quot;],
      synchronize : true,
    })
    break;
  case &amp;#39;production&amp;#39;:
    Object.assign(dbConfig, {
      type : &amp;#39;postgres&amp;#39;,
      url: process.env.DATABASE_URL,
      migrationsRun: true,
      entities: [&amp;quot;dist/**/*.entity.js&amp;quot;],
    })
    break;
  default:
    throw new Error(&amp;quot;Unknown Environment :: NOT(&amp;#39;development&amp;#39; | &amp;#39;test&amp;#39; | &amp;#39;production&amp;#39;)&amp;quot;);
}

// 내보낼 DataSource 를 만들 때, 완전한 객체로서 내보낸다.
const dbSource = new DataSource(dbConfig as DataSourceOptions);

// NestJS 와 typeorm CLI 가 모두 인식 할 수 있도록 송출.
export default dbSource;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그리고, &lt;code&gt;app.module.ts&lt;/code&gt; 에서는&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;.....
import dataSource from &amp;quot;./data-source&amp;quot;

@Module({
  imports: [
    ...,
    TypeOrmModule.forRoot(dbSource.options),
  ].
  ....
})&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;TypeOrmModule.forRoot 내부에 들어 갈 수 있는 Type 은,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;DataSource&lt;/code&gt; 와는 다르다. 따라서, &lt;code&gt;DataSource&lt;/code&gt; 클래스에서 &lt;code&gt;.options&lt;/code&gt; 를 붙여주면,&lt;/p&gt;
&lt;p&gt;모듈에 넣을 수 있는 타입으로 편하게 내보내 준다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;다시 복기하기&lt;/h2&gt;
&lt;p&gt;내가 위에서 Code 를 직접 적어놓은 이유는,&lt;/p&gt;
&lt;p&gt;TypeORM 모듈을 &lt;code&gt;app.module.ts&lt;/code&gt; 에서 직접 작성하여 사용하지만,&lt;/p&gt;
&lt;p&gt;프로덕션에서의 데이터베이스 스키마 마이그레이션을 위해서 &amp;quot;따로 옵션을 빼야한다&amp;quot; 라는 것이다.&lt;/p&gt;
&lt;p&gt;나는 따로 &lt;code&gt;data-source.ts&lt;/code&gt; 라는 파일로 &lt;code&gt;DataSource&lt;/code&gt; 객체를 송출했다.&lt;/p&gt;
&lt;p&gt;이제, NestJS 프로젝트에서도, typeorm CLI 에서도 옵션을 인식하여,&lt;/p&gt;
&lt;p&gt;각자 할 일을 할 수 있게 되었다.&lt;/p&gt;
&lt;p&gt;변경점이 있다면, 나는 개발 상황에서 내가 변경한 엔티티 테이블 스키마가 곧바로 적용되지 않도록,&lt;/p&gt;
&lt;p&gt;빌드된 파일만 매핑하게 만들었다.&lt;/p&gt;
&lt;p&gt;그리고 Jest 테스팅 도구의 E2E 테스팅 환경은 &lt;code&gt;.ts&lt;/code&gt; 이기도 하고,&lt;/p&gt;
&lt;p&gt;딱히 빌드된 상황을 염두에 둘 필요가 없었다.&lt;/p&gt;
&lt;p&gt;(만약에 Github Actions 에서 CI/CD 를 염두한다면, 빌드된 엔티티를 따로 적용하도록 바꿀 것 같긴 하다.)&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;TypeORM CLI 사용법&lt;/h2&gt;
&lt;p&gt;typeorm 라이브러리의 데코레이터를 이용하여 엔티티를 등록, 및 매핑하는 과정은&lt;/p&gt;
&lt;p&gt;수많은 블로그들에서 다루는 것을 보았다.&lt;/p&gt;
&lt;p&gt;그러나, 나는 최신화 버전의 &lt;strong&gt;typeorm&lt;/strong&gt; 버전에서 마이그레이션 하는 과정을&lt;/p&gt;
&lt;p&gt;정확히 담은 블로그를 거의 보지 못했다.&lt;/p&gt;
&lt;p&gt;따라서, 나는 &lt;strong&gt;TypeORM CLI&lt;/strong&gt; 를 이용하여 마이그레이션 하는 방법을 다룰 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;현재 디렉토리 상황 예시&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;&amp;lt;rootDir&amp;gt;
  /dist
    &amp;lt;src 가 빌드된 JS 파일들..&amp;gt;
  /src
    main.ts
    data-source.ts # 중요!!!! typeorm 마이그레이션을 위해 따로 떼어놓은 파일
    app.module.ts
    /users # 유저 도메인 처리
      users.entity.ts # 테이블 엔티티 파일
      ...
    /reports # 차량 보고서 도메인 처리
      reports.entity.ts # 테이블 엔티티 파일
      ...
    /공통 모듈 # 인터셉터, 미들웨어, 공통 함수 등등...&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위와 같은 상황이라고 가정한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;먼저 알아야 할 개념&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TypeORM&lt;/strong&gt; 에서는 로컬 머신에 &lt;code&gt;typeorm&lt;/code&gt; CLI 를 설치하는 방법과,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;typeorm&lt;/code&gt; NPM 라이브러리에 탑재된 CLI 를 다루는 2 가지 방법을 동시에 다루고 있다.&lt;/p&gt;
&lt;p&gt;공식 사이트 자체에서 마이그레이션 하는 방법을 순서대로 다루고 있다고 보기에는 조금 어려울 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://typeorm.io/migrations&quot;&gt;https://typeorm.io/migrations&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://typeorm.io/using-cli&quot;&gt;https://typeorm.io/using-cli&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;내가 진행하는 상황은,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;NestJS 프로젝트를 생성했다. (node, npm, ts-node, 등등 이미 프로젝트 실행을 위한 프로그램이 있다는 가정)&lt;/li&gt;
&lt;li&gt;TypeORM 소스를 따로 파일로 빼 둔 상황이다. (위에서 빼 놓은 실제 파일을 참조)&lt;/li&gt;
&lt;li&gt;NestJS 프로젝트에 &lt;code&gt;typeorm&lt;/code&gt;, &lt;code&gt;@nestjs/typeorm&lt;/code&gt;, &lt;code&gt;sqlite3&lt;/code&gt; 의존성이 설치 된 상황이다.&lt;/li&gt;
&lt;li&gt;Linux, Mac, Windows 환경이 다름을 인지하여 &lt;code&gt;cross-env&lt;/code&gt; 라이브러리로 환경 변수를 주입하는 상황이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;나는, &lt;code&gt;typeorm&lt;/code&gt; 마이그레이션을 위해서 실제로 NPM 전역 레포에 설치하는 것을 꺼리는 상황이다.&lt;/p&gt;
&lt;p&gt;만약에 편하게 스크립트를 입력하고 싶다면, &lt;code&gt;npm i -G typeorm&lt;/code&gt; 을 하면 된다.&lt;/p&gt;
&lt;p&gt;그러나, npm 모듈에 이미 CLI 가 설치되어 있으므로, 나는 &lt;code&gt;package.json&lt;/code&gt; 의 &lt;code&gt;scripts&lt;/code&gt; 옵션을&lt;/p&gt;
&lt;p&gt;활용 할 생각이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;먼저, 타입스크립트로 작성된 엔티티 파일을 인식하기 위해서 이러한 스크립트를 &lt;code&gt;package.json&lt;/code&gt; 에 추가한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;&amp;quot;scripts&amp;quot;: {
  &amp;quot;...&amp;quot; : &amp;quot;.....&amp;quot;,
  &amp;quot;typeorm&amp;quot;: &amp;quot;cross-env NODE_ENV=development typeorm-ts-node-commonjs&amp;quot;,
},&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;typeorm-ts-node-commonjs&lt;/code&gt; 가 왜 스크립트에 들어가는지 의문 일 수 있는데,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;node -r ts-node/register ./node_modules/typeorm/cli.js&lt;/code&gt; 스크립트를 요약하기 위해&lt;/p&gt;
&lt;p&gt;&lt;code&gt;typeorm&lt;/code&gt; 라이브러리 에서 마련 해 놓은 명령어이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;나도 까먹었을 수도 있고, 정확성을 위해 이미 사용하고 있던 migrations 폴더와 소스를 모두 삭제하고 시작하겠다.&lt;/p&gt;
&lt;p&gt;먼저, 프로젝트 내부에서 이러한 명령어를 실행하자 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;# 명령어
➜ npm run typeorm migration:create -- src/migrations/InitData

# 결과
&amp;gt; mycv@0.0.1 typeorm
&amp;gt; cross-env NODE_ENV=development typeorm-ts-node-commonjs migration:create src/migrations/InitData

Migration .../&amp;lt;rootDir&amp;gt;/src/migrations/1746806314329-InitData.ts has been generated successfully.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그 결과, 프로젝트 폴더는&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;&amp;lt;rootDir&amp;gt;
  /dist
    &amp;lt;src 가 빌드된 JS 파일들..&amp;gt;
  /src
    /... 다양한 폴더와 파일
    /migrations # 방금 생성된 폴더
      1746806314329-InitData.ts # 방금 생성된 파일&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;1746806314329-InitData.ts&lt;/code&gt; == &lt;code&gt;&amp;lt;현재 TimeStamp&amp;gt;-&amp;lt;입력한 파일 이름&amp;gt;.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import { MigrationInterface, QueryRunner } from &amp;quot;typeorm&amp;quot;;

export class InitData1746806314329 implements MigrationInterface {
    // 마이그레이션 시 적용
    public async up(queryRunner: QueryRunner): Promise&amp;lt;void&amp;gt; {
    }
    // 마이그레이션 취소 시 적용
    public async down(queryRunner: QueryRunner): Promise&amp;lt;void&amp;gt; {
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;migration:create&lt;/code&gt; 명령어로, 사용자가 직접 쿼리를 커스텀 할 수 있는 파일이 생성되었다.&lt;/p&gt;
&lt;p&gt;커스텀의 목적으로 생성 할 수 있기도 하지만, 나는 디렉토리와 파일을 생성하기 위해 먼저 실행했다.&lt;/p&gt;
&lt;p&gt;이 명령어는 현재 &lt;code&gt;data-source.ts&lt;/code&gt; 의 영향을 받지 않고, 그냥 파일을 생성 한 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;먼저, 다양한 의문이 들 것이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;왜 명령어 script 에 &lt;code&gt;--&lt;/code&gt; 가 들어갔는지,&lt;/li&gt;
&lt;li&gt;공식 홈페이지의 명령어를 입력해도 왜 제대로 실행되지 않는지&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;차례로 알아보자.&lt;/p&gt;
&lt;h3&gt;npm scripts 에 옵션을 보내기 위해 &amp;quot;--&amp;quot; 를 사용해야 한다.&lt;/h3&gt;
&lt;p&gt;일단, &lt;code&gt;package.json&lt;/code&gt; 에 적힌 &lt;code&gt;typeorm&lt;/code&gt; 을 보자 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;&amp;quot;scripts&amp;quot;: {
  &amp;quot;...&amp;quot; : &amp;quot;.....&amp;quot;,
  &amp;quot;typeorm&amp;quot;: &amp;quot;cross-env NODE_ENV=development typeorm-ts-node-commonjs&amp;quot;,
},&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위에 적힌 &lt;code&gt;typeorm&lt;/code&gt; 스크립트 덕분에, 전역 패키지로 &lt;code&gt;typeorm&lt;/code&gt; 을 설치하지 않고도 CLI 를 사용 할 수 있다.&lt;/p&gt;
&lt;p&gt;그런데, &lt;code&gt;typeorm&lt;/code&gt; 전역 명령어와는 다른 점이 있다.&lt;/p&gt;
&lt;p&gt;이걸 공식 홈페이지나 블로그에서 상세히 다루지 않아 굉장히 곤란했는데,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;npm run typeorm migration:&amp;lt;원하는 명령어&amp;gt;&lt;/code&gt; 까지는 정상적으로 인식되지만,&lt;/p&gt;
&lt;p&gt;만약에 &lt;code&gt;--&lt;/code&gt; 없이 입력하면, 이후의 &lt;code&gt;src/migrations/InitData&lt;/code&gt; 옵션을&lt;/p&gt;
&lt;p&gt;npm 의 옵션으로 인식 해 버린다는 것이다.&lt;/p&gt;
&lt;p&gt;만약에 내가 따로 입력한 스크립트의 CLI 에 특정 option 들을 주고 싶다면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;--&lt;/code&gt; 를 입력 한 뒤, 실행하면 된다. (이거 생각보다 진짜 중요하다)&lt;/p&gt;
&lt;p&gt;만약에 공식 홈페이지나 특정 블로그에서 &lt;code&gt;typeorm&lt;/code&gt; 마이그레이션 과정을 참조하고 싶다면,&lt;/p&gt;
&lt;p&gt;위에서 언급 한 내용을 절대로 잊지 말자.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;typeorm 마이그레이션 파일을 생성 해 보자.&lt;/h3&gt;
&lt;p&gt;현재 빌드된, 혹은 생성된 &lt;code&gt;*.entity.ts&lt;/code&gt; 파일에 대한 마이그레이션 파일을 생성하기 위해서는,&lt;/p&gt;
&lt;p&gt;다음과 같은 명령어를 입력하면 된다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;# npm run typeorm migration:generate -- &amp;lt;마이그레이션 파일이 생성될 디렉토리/파일이름&amp;gt; -d &amp;lt;빼 놓은 DataSource 파일&amp;gt;
➜ npm run typeorm migration:generate -- ./src/migrations/InitData -d ./src/data-source.ts

&amp;gt; mycv@0.0.1 typeorm
&amp;gt; cross-env NODE_ENV=development typeorm-ts-node-commonjs migration:generate ./src/migrations/InitData -d ./src/data-source.ts

development
Migration ...../&amp;lt;rootDir&amp;gt;/src/migrations/1746808169533-InitData.ts has been generated successfully.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그리고 생성된 &lt;code&gt;1746808169533-InitData.ts&lt;/code&gt; 을 보면,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import { MigrationInterface, QueryRunner } from &amp;quot;typeorm&amp;quot;;

export class InitData1746808169533 implements MigrationInterface {
    name = &amp;#39;InitData1746808169533&amp;#39;

    public async up(queryRunner: QueryRunner): Promise&amp;lt;void&amp;gt; {
        await queryRunner.query(`CREATE TABLE &amp;quot;reports&amp;quot; (&amp;quot;id&amp;quot; integer PRIMARY KEY AUTOINCREMENT NOT NULL, &amp;quot;approved&amp;quot; boolean NOT NULL DEFAULT (0), &amp;quot;price&amp;quot; integer NOT NULL, &amp;quot;make&amp;quot; varchar NOT NULL, &amp;quot;model&amp;quot; varchar NOT NULL, &amp;quot;year&amp;quot; integer NOT NULL, &amp;quot;lng&amp;quot; float NOT NULL, &amp;quot;lat&amp;quot; float NOT NULL, &amp;quot;mileage&amp;quot; integer NOT NULL, &amp;quot;userId&amp;quot; integer)`);
        await queryRunner.query(`CREATE TABLE &amp;quot;users&amp;quot; (&amp;quot;id&amp;quot; integer PRIMARY KEY AUTOINCREMENT NOT NULL, &amp;quot;email&amp;quot; varchar NOT NULL, &amp;quot;password&amp;quot; varchar NOT NULL, &amp;quot;isAdmin&amp;quot; boolean NOT NULL DEFAULT (1))`);
        await queryRunner.query(`CREATE TABLE &amp;quot;temporary_reports&amp;quot; (&amp;quot;id&amp;quot; integer PRIMARY KEY AUTOINCREMENT NOT NULL, &amp;quot;approved&amp;quot; boolean NOT NULL DEFAULT (0), &amp;quot;price&amp;quot; integer NOT NULL, &amp;quot;make&amp;quot; varchar NOT NULL, &amp;quot;model&amp;quot; varchar NOT NULL, &amp;quot;year&amp;quot; integer NOT NULL, &amp;quot;lng&amp;quot; float NOT NULL, &amp;quot;lat&amp;quot; float NOT NULL, &amp;quot;mileage&amp;quot; integer NOT NULL, &amp;quot;userId&amp;quot; integer, CONSTRAINT &amp;quot;FK_bed415cd29716cd707e9cb3c09c&amp;quot; FOREIGN KEY (&amp;quot;userId&amp;quot;) REFERENCES &amp;quot;users&amp;quot; (&amp;quot;id&amp;quot;) ON DELETE CASCADE ON UPDATE NO ACTION)`);
        await queryRunner.query(`INSERT INTO &amp;quot;temporary_reports&amp;quot;(&amp;quot;id&amp;quot;, &amp;quot;approved&amp;quot;, &amp;quot;price&amp;quot;, &amp;quot;make&amp;quot;, &amp;quot;model&amp;quot;, &amp;quot;year&amp;quot;, &amp;quot;lng&amp;quot;, &amp;quot;lat&amp;quot;, &amp;quot;mileage&amp;quot;, &amp;quot;userId&amp;quot;) SELECT &amp;quot;id&amp;quot;, &amp;quot;approved&amp;quot;, &amp;quot;price&amp;quot;, &amp;quot;make&amp;quot;, &amp;quot;model&amp;quot;, &amp;quot;year&amp;quot;, &amp;quot;lng&amp;quot;, &amp;quot;lat&amp;quot;, &amp;quot;mileage&amp;quot;, &amp;quot;userId&amp;quot; FROM &amp;quot;reports&amp;quot;`);
        await queryRunner.query(`DROP TABLE &amp;quot;reports&amp;quot;`);
        await queryRunner.query(`ALTER TABLE &amp;quot;temporary_reports&amp;quot; RENAME TO &amp;quot;reports&amp;quot;`);
    }

    public async down(queryRunner: QueryRunner): Promise&amp;lt;void&amp;gt; {
        await queryRunner.query(`ALTER TABLE &amp;quot;reports&amp;quot; RENAME TO &amp;quot;temporary_reports&amp;quot;`);
        await queryRunner.query(`CREATE TABLE &amp;quot;reports&amp;quot; (&amp;quot;id&amp;quot; integer PRIMARY KEY AUTOINCREMENT NOT NULL, &amp;quot;approved&amp;quot; boolean NOT NULL DEFAULT (0), &amp;quot;price&amp;quot; integer NOT NULL, &amp;quot;make&amp;quot; varchar NOT NULL, &amp;quot;model&amp;quot; varchar NOT NULL, &amp;quot;year&amp;quot; integer NOT NULL, &amp;quot;lng&amp;quot; float NOT NULL, &amp;quot;lat&amp;quot; float NOT NULL, &amp;quot;mileage&amp;quot; integer NOT NULL, &amp;quot;userId&amp;quot; integer)`);
        await queryRunner.query(`INSERT INTO &amp;quot;reports&amp;quot;(&amp;quot;id&amp;quot;, &amp;quot;approved&amp;quot;, &amp;quot;price&amp;quot;, &amp;quot;make&amp;quot;, &amp;quot;model&amp;quot;, &amp;quot;year&amp;quot;, &amp;quot;lng&amp;quot;, &amp;quot;lat&amp;quot;, &amp;quot;mileage&amp;quot;, &amp;quot;userId&amp;quot;) SELECT &amp;quot;id&amp;quot;, &amp;quot;approved&amp;quot;, &amp;quot;price&amp;quot;, &amp;quot;make&amp;quot;, &amp;quot;model&amp;quot;, &amp;quot;year&amp;quot;, &amp;quot;lng&amp;quot;, &amp;quot;lat&amp;quot;, &amp;quot;mileage&amp;quot;, &amp;quot;userId&amp;quot; FROM &amp;quot;temporary_reports&amp;quot;`);
        await queryRunner.query(`DROP TABLE &amp;quot;temporary_reports&amp;quot;`);
        await queryRunner.query(`DROP TABLE &amp;quot;users&amp;quot;`);
        await queryRunner.query(`DROP TABLE &amp;quot;reports&amp;quot;`);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그리고 &lt;code&gt;migrations&lt;/code&gt; 폴더를 보면,&lt;/p&gt;
&lt;p&gt;각자의 타임스탬프에 따라 두 개의 &lt;code&gt;&amp;lt;timestamp&amp;gt;-InitData.ts&lt;/code&gt; 파일이 존재한다.&lt;/p&gt;
&lt;p&gt;이를 통해 순서도 파악 할 수 있고, 나중에 되돌리고 싶을 때 &lt;code&gt;revert&lt;/code&gt; 할 수 있다.&lt;/p&gt;
&lt;p&gt;즉, 우리는 현재 오류가 났을 때, 혹은 에러가 났을 때 되돌릴 수 있는 기록을 성공적으로 생성 한 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;typeorm 을 이용하여 데이터베이스를 마이그레이션 하자.&lt;/h3&gt;
&lt;p&gt;이제 우리는 &lt;code&gt;synchronize&lt;/code&gt; 옵션을 &lt;code&gt;true&lt;/code&gt; 하지 않고,&lt;/p&gt;
&lt;p&gt;데이터베이스에 변경된 사항을 적용 할 수 있다.&lt;/p&gt;
&lt;p&gt;생성된 &lt;code&gt;&amp;lt;timestamp&amp;gt;-InitData.ts&lt;/code&gt; 파일로 마이그레이션 하고 싶다면,&lt;/p&gt;
&lt;p&gt;실제 &lt;code&gt;main.ts&lt;/code&gt; 와 같은 파일에서,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import dataSource from &amp;#39;./data-source&amp;#39;;

async function bootstrap() {
  // 데이터 소스 초기화 - 커넥션 생성
  await dataSource.initialize();

  // 미적용 마이그레이션 실행
  await dataSource.runMigrations();

  // 애플리케이션 시작
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}

bootstrap();&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이러한 방식으로 적용 할 수 있기도 하다.&lt;/p&gt;
&lt;p&gt;그러나, CLI 로 쉽게 적용 할 수 있는데,&lt;/p&gt;
&lt;p&gt;바로 &lt;code&gt;typeorm migration:run&lt;/code&gt; 명령어를 사용하는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이전까지 우리는 현재까지 데이터베이스에 적용되어야 하는 마이그레이션 파일을 생성했다.&lt;/p&gt;
&lt;p&gt;이는 기록으로서, 위의 코드처럼 적용 할 수 있기도 하고, 명령어로 &lt;code&gt;revert&lt;/code&gt; (되돌리기) 할 수 있다.&lt;/p&gt;
&lt;p&gt;그러나, &lt;code&gt;run&lt;/code&gt; 옵션을 통해, 방금 기록한 마이그레이션 기록 그대로, 옵션을 그대로 가져와서&lt;/p&gt;
&lt;p&gt;직빵으로 데이터베이스에 변경 사항을 적용 할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;그런데, 내가 여기서 잘못 적용한 옵션이 있다!!!!!!&lt;/strong&gt; (참조 필수)&lt;/p&gt;
&lt;p&gt;내가 &lt;code&gt;data-source.ts&lt;/code&gt; 에서 &lt;code&gt;DataSource&lt;/code&gt; 객체를 만들 때,&lt;/p&gt;
&lt;p&gt;맨 처음 부분에 잘못 적용한 부분이 있다.&lt;/p&gt;
&lt;p&gt;따라서, 이처럼 바꿔주면 된다. &lt;code&gt;dist/migrations/*.js&lt;/code&gt; --&amp;gt; &lt;code&gt;dist/src/migrations/*.js&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;const dbConfig = {
  migrations: env === &amp;quot;test&amp;quot; ? [&amp;quot;src/migrations/*.ts&amp;quot;] : [&amp;quot;dist/src/migrations/*.js&amp;quot;],
  cli: {
    migrationsDir: env === &amp;quot;test&amp;quot; ? &amp;quot;src/migrations&amp;quot; : &amp;quot;dist/migrations&amp;quot;
  },
} as Partial&amp;lt;DataSourceOptions&amp;gt;;

// ...
export default dataSource;&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;그리고, 이러한 명령어를 입력하자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;# 명령어
➜ npm run typeorm migration:run -- -d src/data-source.ts

# 결과물
&amp;gt; mycv@0.0.1 typeorm
&amp;gt; cross-env NODE_ENV=development typeorm-ts-node-commonjs migration:run -d src/data-source.ts

development
query: SELECT * FROM &amp;quot;sqlite_master&amp;quot; WHERE &amp;quot;type&amp;quot; = &amp;#39;table&amp;#39; AND &amp;quot;name&amp;quot; = &amp;#39;migrations&amp;#39;
query: SELECT * FROM &amp;quot;migrations&amp;quot; &amp;quot;migrations&amp;quot; ORDER BY &amp;quot;id&amp;quot; DESC
0 migrations are already loaded in the database.
2 migrations were found in the source code.
2 migrations are new migrations must be executed.
query: PRAGMA foreign_keys = OFF
query: BEGIN TRANSACTION
query: INSERT INTO &amp;quot;migrations&amp;quot;(&amp;quot;timestamp&amp;quot;, &amp;quot;name&amp;quot;) VALUES (1746806314329, ?) -- PARAMETERS: [&amp;quot;InitData1746806314329&amp;quot;]
Migration InitData1746806314329 has been executed successfully.
query: CREATE TABLE &amp;quot;reports&amp;quot; (&amp;quot;id&amp;quot; integer PRIMARY KEY AUTOINCREMENT NOT NULL, &amp;quot;approved&amp;quot; boolean NOT NULL DEFAULT (0), &amp;quot;price&amp;quot; integer NOT NULL, &amp;quot;make&amp;quot; varchar NOT NULL, &amp;quot;model&amp;quot; varchar NOT NULL, &amp;quot;year&amp;quot; integer NOT NULL, &amp;quot;lng&amp;quot; float NOT NULL, &amp;quot;lat&amp;quot; float NOT NULL, &amp;quot;mileage&amp;quot; integer NOT NULL, &amp;quot;userId&amp;quot; integer)
query: CREATE TABLE &amp;quot;users&amp;quot; (&amp;quot;id&amp;quot; integer PRIMARY KEY AUTOINCREMENT NOT NULL, &amp;quot;email&amp;quot; varchar NOT NULL, &amp;quot;password&amp;quot; varchar NOT NULL, &amp;quot;isAdmin&amp;quot; boolean NOT NULL DEFAULT (1))
query: CREATE TABLE &amp;quot;temporary_reports&amp;quot; (&amp;quot;id&amp;quot; integer PRIMARY KEY AUTOINCREMENT NOT NULL, &amp;quot;approved&amp;quot; boolean NOT NULL DEFAULT (0), &amp;quot;price&amp;quot; integer NOT NULL, &amp;quot;make&amp;quot; varchar NOT NULL, &amp;quot;model&amp;quot; varchar NOT NULL, &amp;quot;year&amp;quot; integer NOT NULL, &amp;quot;lng&amp;quot; float NOT NULL, &amp;quot;lat&amp;quot; float NOT NULL, &amp;quot;mileage&amp;quot; integer NOT NULL, &amp;quot;userId&amp;quot; integer, CONSTRAINT &amp;quot;FK_bed415cd29716cd707e9cb3c09c&amp;quot; FOREIGN KEY (&amp;quot;userId&amp;quot;) REFERENCES &amp;quot;users&amp;quot; (&amp;quot;id&amp;quot;) ON DELETE CASCADE ON UPDATE NO ACTION)
query: INSERT INTO &amp;quot;temporary_reports&amp;quot;(&amp;quot;id&amp;quot;, &amp;quot;approved&amp;quot;, &amp;quot;price&amp;quot;, &amp;quot;make&amp;quot;, &amp;quot;model&amp;quot;, &amp;quot;year&amp;quot;, &amp;quot;lng&amp;quot;, &amp;quot;lat&amp;quot;, &amp;quot;mileage&amp;quot;, &amp;quot;userId&amp;quot;) SELECT &amp;quot;id&amp;quot;, &amp;quot;approved&amp;quot;, &amp;quot;price&amp;quot;, &amp;quot;make&amp;quot;, &amp;quot;model&amp;quot;, &amp;quot;year&amp;quot;, &amp;quot;lng&amp;quot;, &amp;quot;lat&amp;quot;, &amp;quot;mileage&amp;quot;, &amp;quot;userId&amp;quot; FROM &amp;quot;reports&amp;quot;
query: DROP TABLE &amp;quot;reports&amp;quot;
query: ALTER TABLE &amp;quot;temporary_reports&amp;quot; RENAME TO &amp;quot;reports&amp;quot;
query: INSERT INTO &amp;quot;migrations&amp;quot;(&amp;quot;timestamp&amp;quot;, &amp;quot;name&amp;quot;) VALUES (1746808169533, ?) -- PARAMETERS: [&amp;quot;InitData1746808169533&amp;quot;]
Migration InitData1746808169533 has been executed successfully.
query: COMMIT
query: PRAGMA foreign_keys = ON&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;드디어, 데이터베이스 스키마를 적용했다!!&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;# 데이터베이스 실행
➜ sqlite3 db.sqlite
SQLite version 3.43.2 2023-10-10 13:08:14
Enter &amp;quot;.help&amp;quot; for usage hints.
# 테이블 &amp;quot;reports&amp;quot;, &amp;quot;users&amp;quot; 있는지 확인
sqlite&amp;gt; .table
# 정상적으로 2 개의 테이블이 생성됨
migrations  reports     users&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h2&gt;요약&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. TypeORM 은 어플 호환성이 높은, &amp;quot;독립적인&amp;quot; DB 마이그레이션 도구이다&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;나는 위의 문구를 꼭 기억해야 한다고 생각한다.&lt;/p&gt;
&lt;p&gt;물론, &lt;code&gt;TypeOrmModule.forRoot&lt;/code&gt;, &lt;code&gt;...forRootAsync&lt;/code&gt;, ... 등등,&lt;/p&gt;
&lt;p&gt;아예 NestJS 의 모듈에서 &amp;quot;내부 코드로 등록하는&amp;quot; 간편한 절차가 존재한다.&lt;/p&gt;
&lt;p&gt;그러나, 실제 데이터베이스 마이그레이션 과정을 거치기 위해서는, &lt;code&gt;DataSource&lt;/code&gt; 객체를 따로 관리해야 한다.&lt;/p&gt;
&lt;p&gt;따로 관리하게 된다면, 이는 NestJS 어플리케이션에서 옵션을 불러 올 수도 있고,&lt;/p&gt;
&lt;p&gt;또한 이 글을 읽고 있는 독자가 &lt;code&gt;typeorm&lt;/code&gt; 전역 명령어를 사용하던,&lt;/p&gt;
&lt;p&gt;혹은 npm &lt;code&gt;typeorm&lt;/code&gt; 모듈에 포함된 CLI 를 사용하던, 2 가지 방식을 모두 채택 할 수 있기 때문이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;참고로, 버전이 최신화 되어서 그런지, &lt;br/&gt;&lt;br&gt;루트 프로젝트에 직접 orm config 파일을 작성한 뒤, &lt;br/&gt;&lt;br&gt;&lt;code&gt;app.module.ts&lt;/code&gt; 에서 단순히 &lt;code&gt;TypeOrmModule.forRoot()&lt;/code&gt; &lt;br/&gt;&lt;br&gt;형식으로는 불러 올 수 없었다. 이는 내가 &lt;code&gt;src&lt;/code&gt; 폴더에 DataSource 파일을 작성 한 이유이기도 하다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;strong&gt;TypeORM&lt;/strong&gt; 은, NestJS 에서의 비즈니스 로직 편의성과, 독립적인 DB 마이그레이션 도구라는 것을 잊지 않았으면 좋겠다 (진심) - 혹시 더 전문적인 분이 있다면, 댓글 달아주세요. 배우고 싶어용&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;2. TypeORM 은 메타데이터(데코레이터)를 적극 활용한다&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;ORM 을 처음 접하거나, 코드를 통해 테이블 스키마와 연결을 구성하는 개념이 처음이라면,&lt;/p&gt;
&lt;p&gt;TypeORM 에서 사용하는 데코레이터의 의미를 깊이 생각하지 않고 넘길 수 있다.&lt;/p&gt;
&lt;p&gt;그러나, TypeORM 은 자기 자신을 참조할 수 있는 특정 객체를 넘겨주지 않는다.&lt;/p&gt;
&lt;p&gt;즉, 프로젝트 루트 하위에 존재하는 파일 중, &lt;code&gt;@Entity()&lt;/code&gt;, &lt;code&gt;@Column()&lt;/code&gt; 등등,&lt;/p&gt;
&lt;p&gt;TypeORM 의 전체 메타데이터를 구성하고 이에 대한 정보를 데이터베이스에 넘겨 주기 전에,&lt;/p&gt;
&lt;p&gt;위의 데코레이터와 같은 메타데이터를 먼저 추출 한 뒤, 정해진 흐름에 따라 데이터베이스 스키마를 결정한다.&lt;/p&gt;
&lt;p&gt;단점이라면, 다양한 종류의 데코레이터를 알고 있지 않다면, &lt;strong&gt;TypeORM&lt;/strong&gt; 자체의 흐름을 익히기 어려울 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;3. TypeORM 을 단순 토이 프로젝트용이 아니라, 프로덕션으로 올려야 한다면, 결국 CLI 가 필요하다&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;NestJS 에서, 원활한 호환을 위해 &lt;code&gt;@nestjs/typeorm&lt;/code&gt; 이 있다는 것이 매우 좋으나,&lt;/p&gt;
&lt;p&gt;결국 &lt;code&gt;typeorm&lt;/code&gt; 라이브러리의 정석적인 마이그레이션을 수행하기 위해서는 명령어가 필요하다는 것이다.&lt;/p&gt;
&lt;p&gt;내가 &lt;code&gt;typeorm&lt;/code&gt; 을 npm 전역 패키지로 설치하지 않은 이유는, 물론 저장 공간을 낭비하지 않음도 있지만,&lt;/p&gt;
&lt;p&gt;실제 프로덕션이 올라가 있는 서버에 굳이 &lt;code&gt;typeorm&lt;/code&gt; 패키지를 설치하지 않고도,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;node_modules&lt;/code&gt; 에 존재하는 &lt;code&gt;typeorm&lt;/code&gt; CLI 를 활용 할 수 있기 때문이다.&lt;/p&gt;
&lt;p&gt;우리가 사용 했던 명령어들은 다음과 같았다는 것을 기억하며 된다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;npm run typeorm migration:create -- src/migrations/InitData&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;사용 할 마이그레이션 폴더 생성 및, 사용자가 커스텀 할 수 있는 migration 파일 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;npm run typeorm migration:generate -- src/migrations/FirstSchema -d src/data-source.ts&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;따로 빼 놓은 &lt;code&gt;DataSource&lt;/code&gt; 객체 파일을 참조하여 &lt;code&gt;migrations&lt;/code&gt; 폴더에 &lt;code&gt;&amp;lt;timestamp&amp;gt;-FirstSchema.ts&lt;/code&gt; 파일 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;npm run typeorm migration:run -- -d src/data-source.ts&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;현재 정의되어 있는 &lt;code&gt;DataSource&lt;/code&gt; 객체 파일을 참조하여, DB 마이그레이션 진행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;p&gt;아직 부족 한 점이 많은 블로거입니다.&lt;/p&gt;
&lt;p&gt;글에 틀린 부분이 있다면, 댓글로 남겨주시면 감사하겠습니다&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;참고 사이트&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;위키피디아 (Object-Relational Mapping)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping&quot;&gt;https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;TypeORM 공식 사이트&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://typeorm.io/&quot;&gt;https://typeorm.io/&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;mysqljs/mysql 깃허브 코드 레포&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://github.com/mysqljs/mysql/blob/master/lib/Connection.js&quot;&gt;https://github.com/mysqljs/mysql/blob/master/lib/Connection.js&lt;/a&gt;&lt;/p&gt;</description>
      <category>잡다 지식</category>
      <category>nestjs</category>
      <category>nestjs db 마이그레이션</category>
      <category>nestjs typeorm</category>
      <category>nestjs 데이터베이스 마이그레이션</category>
      <category>ORM</category>
      <category>typeorm</category>
      <category>typeorm cli</category>
      <category>typeorm 마이그레이션</category>
      <category>typeorm 명령어</category>
      <category>데이터베이스 마이그레이션</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/210</guid>
      <comments>https://codecreature.tistory.com/210#entry210comment</comments>
      <pubDate>Sat, 10 May 2025 05:36:32 +0900</pubDate>
    </item>
    <item>
      <title>PostgreSQL 데이터베이스는 무엇일까?</title>
      <link>https://codecreature.tistory.com/209</link>
      <description>&lt;h1&gt;제목 : PostgreSQL 데이터베이스는 무엇일까?&lt;/h1&gt;
&lt;hr&gt;
&lt;p&gt;나는 이전에 파일을 이용하는 데이터베이스 SQLite 를 다룬 적이 있다.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/199&quot;&gt;SQLite 데이터베이스는 무엇일까?&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;파일 시스템을 이용하는 데이터베이스는 SQLite 뿐만 아니라, H2 라는 데이터베이스도 존재한다.&lt;/p&gt;
&lt;p&gt;H2 는 Java 계열에서 사용하는 데이터베이스이며, JDBC 와 연동이 아주 잘 되어 있다.&lt;/p&gt;
&lt;p&gt;SQLite 는 안드로이드 시스템에서도 사용되는 파일 데이터베이스이며,&lt;/p&gt;
&lt;p&gt;Node 계열에서 TypeORM 라이브러리를 사용 할 때, 연동이 잘 되는 데이터베이스이다.&lt;/p&gt;
&lt;p&gt;각각의 언어나 프레임워크에서 사용되는 편리한 파일 데이터베이스는 다를 수 있는데,&lt;/p&gt;
&lt;p&gt;이는 프레임워크나 특정 언어에서 테이블 엔티티를 각자 작성했을 때,&lt;/p&gt;
&lt;p&gt;자동으로 마이그레이션 해 주는 파일 데이터베이스가 다르기 때문이다.&lt;/p&gt;
&lt;p&gt;즉, 데이터베이스 시스템에 직접 들어가 Schema(스키마) 를 직접 제작 해 줄 필요 없이,&lt;/p&gt;
&lt;p&gt;어플리케이션을 실행하기만 해도 자동으로 스키마를 생성 해 주는 것이다.&lt;/p&gt;
&lt;p&gt;이건 사용 해 보면 너무나도 편리한 라이브러리이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;다시 돌아와서 PostgreSQL 데이터베이스를 다루는 이유는, 내가 듣던 강의에서&lt;/p&gt;
&lt;p&gt;데이터베이스에 대한 설명을 거의 하지 않고 연동만 다루었기 때문이다.&lt;/p&gt;
&lt;p&gt;아 물론, 각 언어와 프레임워크마다 데이터베이스를 자동으로 마이그레이션 해 주는 프로그램과 라이브러리가 있어서&lt;/p&gt;
&lt;p&gt;딱히 데이터베이스의 시스템을 이해하지 않아도 된다고 생각 할 수도 있다.&lt;/p&gt;
&lt;p&gt;그러나, 나는 지난 3 년 동안, 컴퓨터를 이해하지 않고 외우려고만 했던 지난 날들을 매우 반성한다.&lt;/p&gt;
&lt;p&gt;외우면 &amp;quot;훌륭한 개발자&amp;quot; 는 될 수 있겠으나, &amp;quot;컴퓨터 엔지니어&amp;quot; 로서의 내 꿈에 다가갈 수 없기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그리고, MySQL 이나, MariaDB 와 같은 훌륭한 데이터베이스도 있지만, 왜 PostgreSQL 이 그렇게 유명한지 궁금하다.&lt;/p&gt;
&lt;p&gt;예전에 FireShip 이라는 유튜브 채널에서 PostgreSQL 에 대해 말하는 영상을 본 적이 있는데,&lt;/p&gt;
&lt;p&gt;갖출 기능은 전부 갖추었는데, 사용하기가 매우 편리하다고 한다.&lt;/p&gt;
&lt;p&gt;그런데, 유명하다 못해 SQL 의 대표격인 MySQL 이 있는데, 왜 PostgreSQL 이 뜨게 된 걸까?&lt;/p&gt;
&lt;p&gt;분명히 모던 프로그래밍의 시대가 열리면서, PostgreSQL 의 특정 기능들이 접근성을 넓혔다고 예상했다.&lt;/p&gt;
&lt;p&gt;그리고, 이 글을 보기 전에, Google Cloud 에서 PostgreSQL 과 여타 다른 DB 와의 차이점을&lt;/p&gt;
&lt;p&gt;너무나도 잘 정리 해 놓은 글이 있다. 이를 먼저 참고하길 바란다!&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://cloud.google.com/discover/what-is-postgresql?hl=ko&quot;&gt;Google Cloud - PostgreSQL 이란?&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;데이터베이스는 크게 2 가지 종류로 나뉜다.&lt;/p&gt;
&lt;p&gt;바로, RDBMS 와, NoSQL 이다.&lt;/p&gt;
&lt;p&gt;굉장히 편하게 말하자면, &amp;quot;정형 데이터 vs 비정형 데이터&amp;quot; 라고 볼 수 있다.&lt;/p&gt;
&lt;p&gt;NoSQL 의 경우, MongoDB 가 대표적이다.&lt;/p&gt;
&lt;p&gt;물론, 이 글에서는 Postgres (PostgreSQL) 에 대해서 다룰 것이다.&lt;/p&gt;
&lt;h2&gt;Postgres (PostgreSQL) 은 객체 관계형이다?&lt;/h2&gt;
&lt;p&gt;Postgres 는 아주 정확히 말하자면, ORDBMS 이다.&lt;/p&gt;
&lt;p&gt;즉, Object Relational Database Management System 의 약자이다.&lt;/p&gt;
&lt;p&gt;그렇다면, RDBMS 와 어떤 차이점을 가지고 있나 찾아보니까,&lt;/p&gt;
&lt;p&gt;구글 문서에서 말하길,&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;RDBMS&lt;/strong&gt; 는 데이터의 관계형 모델을 기반으로 한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ORDBMS&lt;/strong&gt; 는 클래스, 객체, 상속과 같은 객체 지향 개념을 추가로 지원하는 관계형 모델을 기반으로 한다.&lt;/p&gt;
&lt;p&gt;예를 들어, ORDBMS 는 RDBMS 에서 처리할 수 없던 &amp;quot;동영상&amp;quot;, &amp;quot;오디오&amp;quot;, &amp;quot;이미지&amp;quot; 파일과 같은&lt;/p&gt;
&lt;p&gt;데이터 유형을 처리할 수 있다고 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Posgres 와 일반적인 SQL 데이터 서버와의 차이점&lt;/h2&gt;
&lt;p&gt;그리고 Postgres (PostgreSQL) 의 대표적인 특성은, 완전한 Open Source 라는 것이다.&lt;/p&gt;
&lt;p&gt;MySQL, Oracle, MSSQL(마이크로소프트) 와는 달리,&lt;/p&gt;
&lt;p&gt;내가 돈 벌 목적으로 사용해도 라이선스 비용을 내지 않는다.&lt;/p&gt;
&lt;p&gt;물론, 비슷한 특성을 가진 MariaDB 가 있기는 하다.&lt;/p&gt;
&lt;p&gt;그러나, Postgres 와는 서로 다른 특성을 가지고 있기에 선택지가 존재한다.&lt;/p&gt;
&lt;p&gt;우선, MariaDB 는 MySQL 의 완전 오픈소스화 버전이다.&lt;/p&gt;
&lt;p&gt;MySQL 은 경량화, 속도에 더 유리하다.&lt;/p&gt;
&lt;p&gt;따라서, 딱히 비구조화 데이터를 많이 활용하지 않을 경우,&lt;/p&gt;
&lt;p&gt;즉, 대부분 정형화 된(정해진) 데이터를 사용 할 경우, MariaDB 가 더 유리하다.&lt;/p&gt;
&lt;p&gt;Postgres (PostgreSQL) 은 비구조화 데이터 유형을 지원한다.&lt;/p&gt;
&lt;p&gt;예를 들면 오디오, 동영상, 이미지와 같은 데이터를 저장 할 수 있다.&lt;/p&gt;
&lt;p&gt;또한 함수, 커스텀 데이터 유형, 언어 등을 추가 할 수 있는 확장이 높다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;요약하자면&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;MySQL or MariaDB : 빠름, 정형화 데이터에 치중&lt;/p&gt;
&lt;p&gt;Postgres(PostgreSQL) : 상대적으로 느림 (그래도 빠른편), 정형화 및 비정형화 데이터 모두 지원&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Postgres 는 RDBMS 와 NoSQL 역할 둘 다 가능하다?&lt;/h2&gt;
&lt;p&gt;Postgres 를 살펴보면서 놀랐던 것은, NoSQL 로서의 역할도 가능하다는 것이었다.&lt;/p&gt;
&lt;p&gt;물론, MongoDB 와 같이 NoSQL 전문으로 사용되는 데이터베이스보다는 제한이 있겠지만,&lt;/p&gt;
&lt;p&gt;RDBMS 종류에 속하는 Postgres 가 NoSQL 기능도 어느정도 지원하다는 것이 매우 놀라웠다.&lt;/p&gt;
&lt;p&gt;그도 그럴 것이, 비정형 데이터를 다룰 때, ACID 원칙이 지켜지지 어려울 것이라고 생각하는데,&lt;/p&gt;
&lt;p&gt;어떻게 ACID 를 지키면서 NoSQL 기능을 다루는가? 에 대해 궁금증이 생겼다.&lt;/p&gt;
&lt;p&gt;먼저, 이에 대해 훌륭한 글을 남기고 있는 Medium 의 글이 있었다.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://medium.com/@dudkamv/nosql-capabilities-in-postgresql-9eec822886d9&quot;&gt;Medium - NoSQL Capabilities in PostgreSQL&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;위의 글은 안타깝게도 매우 길고 영어로 되어 있으므로,&lt;/p&gt;
&lt;p&gt;간략하게 요약해 보자.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;1. Relational, NoSQL 데이터베이스로서의 PostgreSQL&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Postgres 의 굉장히 독특한 관점은, 관계형과 NoSQL 데이터베이스 둘 모두의 기능을 갖추었다는 것이다.&lt;/p&gt;
&lt;p&gt;이러한 양 측의 능력은 JSON 에 대한 진보된 지원 뿐만 아니라,&lt;/p&gt;
&lt;p&gt;다양한 데이터 타입이 지원되기에,&lt;/p&gt;
&lt;p&gt;NoSQL 의 유연성과 관계형 데이터베이스의 안정성 및 무결성이 모두 필요한 어플에 알맞는 선택이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. JSON 과 JSONB 데이터 타입&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Postgres 는 JSON 데이터에 대해서 2 가지 데이터 타입을 지원한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;JSON&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;JSONB&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;JSON&lt;/code&gt; 은 (JavaScript Object Notation) 의 약자에 알맞게&lt;/p&gt;
&lt;p&gt;정확하게 입력 형태에 따라 텍스트 형식으로 데이터가 저장된다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;JSONB&lt;/code&gt; 는, (JavaScript Object Notation Binary) 의 약자이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;JSONB&lt;/code&gt; 는, 바이너리 형태로 분해되어 데이터로 저장되는데,&lt;/p&gt;
&lt;p&gt;쿼리와 조작을 수행 할 때, 데이터가 이미 바이너리 형태로 파싱되었기 때문에 더 효율적으로 만들어 준다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. JSON 데이터 색인(indexing - 인덱싱)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Postgres&lt;/strong&gt; 의 강점 중 하나는 JSON 데이터를 인덱싱(색인) 하는 능력이라고 한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GIN&lt;/strong&gt; (Generalized Inverted Index) 인덱스를 사용하여 효과적으로 &lt;strong&gt;JSONB&lt;/strong&gt; 데이터를 쿼리 할 수 있다고 한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GIN&lt;/strong&gt; 이란, 역 색인 이라고 한다. 거꾸로 색인을 의미한다는 것 까지는 이해했지만, 어떻게 거꾸로 색인을 하는 것인가?&lt;/p&gt;
&lt;p&gt;이에 대한 내용이 Postgres 의 공식 문서에 존재했다.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.postgresql.org/docs/current/gin.html&quot;&gt;https://www.postgresql.org/docs/current/gin.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;GIN 은 복합적인 값을 색인 하기 위해 설계되었다.&lt;/p&gt;
&lt;p&gt;그리고, 인덱스에서 처리하는 쿼리는 복합적인 값 내에 나타나는 요소를 검색해야 한다.&lt;/p&gt;
&lt;p&gt;예를 들어 복잡적인 값은 &amp;quot;문서&amp;quot; 가 될 수 있으며,&lt;/p&gt;
&lt;p&gt;쿼리는 특정 단어들을 담고 있는 문서들을 검색 할 수 있다.&lt;/p&gt;
&lt;p&gt;그리고 단어를 사용하여 색인(인덱싱) 되어야 할 복잡 값을 참조 할 수 있다.&lt;/p&gt;
&lt;p&gt;그리고 단어 key 는 &amp;quot;요소의 값&amp;quot; 을 참조한다.&lt;/p&gt;
&lt;p&gt;위의 설명을 보고 &amp;quot;역 색인&amp;quot; 이라는 의미는 이해했지만, GPT 에게 한 번 더 질문했다. (현재 GPT o3)&lt;/p&gt;
&lt;p&gt;GPT 의 설명을 보고 요약하자면,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;하나의 Low 는, 쿼리가 찾는 여러 값을 가질 수 있다. (토큰, 배열, 문서, JSON 객체 등)&lt;/li&gt;
&lt;li&gt;이 때, 찾고자 하는 토큰이 어느 Low 에 속하는지 역으로 (inverted) 관리한다.&lt;/li&gt;
&lt;li&gt;예를 들어, &amp;quot;apple&amp;quot; : [doc2, doc6], &amp;quot;car&amp;quot; : [doc2, doc5] 이러한 형식&lt;/li&gt;
&lt;li&gt;항상 테이블을 생성 할 때, 성능을 위해 &lt;code&gt;CREATE INDEX idx_docs_data ON docs USING GIN (data);&lt;/code&gt; &lt;br/&gt; 와 같은 인덱스를 생성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Postgres 의 데이터베이스는, 일반적인 정규화 SQL 서버들과는 다른 기능을 가지고 있기에,&lt;/p&gt;
&lt;p&gt;따라 올 수 있는 단점들이 생각났다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;키가 아닌, 값에 대한 메타데이터 참조 또한 저장하기 때문에 더 많은 저장공간이 필요 할 것이다.&lt;/li&gt;
&lt;li&gt;추후 메타데이터 저장 및 생성을 위해 로직이 추가되어 있으므로, 데이터 저장 과정에서 딜레이가 발생 할 것이다.&lt;/li&gt;
&lt;li&gt;ACID 를 정확히 지키는 RDBMS 에 속하기 때문에, 메타데이터 색인이 최고조로 최적화 되기는 힘들 것이다.&lt;/li&gt;
&lt;li&gt;따라서, NoSQL 처럼 메타데이터 조회 시, 방대한 데이터에서는 튜닝이 필요 할 것이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;그러나, 이전에 수영장 정보에 대한 프로젝트를 진행했을 때,&lt;/p&gt;
&lt;p&gt;수영장 검색 창에서 어떠한 키워드가 매칭되었을 때, 해당되는 수영장 목록을 보내주고 싶다는 생각을 했었다.&lt;/p&gt;
&lt;p&gt;이는 구글이나 네이버처럼, 자동완성 기능을 넣고 싶은 욕심이었다.&lt;/p&gt;
&lt;p&gt;그러나, MariaDB 와 같은 SQL 서버에서 &lt;code&gt;WHERE LIKE &amp;#39;%&amp;lt;키워드&amp;gt;%&lt;/code&gt; 를 사용하기에는,&lt;/p&gt;
&lt;p&gt;너무 많은 컴퓨팅 리소스가 발생하고, 비효율적이었다.&lt;/p&gt;
&lt;p&gt;그러나, 만약에 Postgres 를 사용한다고 가정했을 때,&lt;/p&gt;
&lt;p&gt;메타데이터를 저장하는 GIN 의 알고리즘으로 해당 기능을 타협 할 수 있다고 생각한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;기존의 SQL 서버로 키워드 검색 시&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;키워드 검색 요청 --&amp;gt; 해당 테이블의 모든 데이터를 훑고, 일치 여부 확인 --&amp;gt; 비효율&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Postgres 로 키워드 검색 시&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;키워드 검색 요청 --&amp;gt; Postgres 의 메타데이터 테이블에서 키워드 검색&lt;/p&gt;
&lt;p&gt;--&amp;gt; 키워드에 일치하는 모든 low 검색 및 추출 --&amp;gt; 효율적&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;# 유저는 로그인 이메일, 비번, 유저이름, 스스로의 태그를 가진다. (태그는 스스로 정한다 가정)
CREATE TABLE users (
    id    serial  PRIMARY KEY,
    email text,
    pwd   text,
    username  text,
    tags  text[] # GIN 을 사용 할 예정
);

# 특정 태그를 배열에 가지고 있는 유저를 메타데이터로 저장하기 위해 GIN 을 사용하여 색인 구성.
CREATE INDEX idx_users_tags ON users USING GIN (tags);

# tags 배열에 &amp;quot;tech&amp;quot; 키워드를 가진 모든 유저의 이메일을 가져온다.
SELECT email FROM users
WHERE tags @&amp;gt; ARRAY[&amp;#39;tech&amp;#39;];&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위에서 SQL 문법을 보면 알 수 있듯이, 기존에 사용하지 않았던 &lt;code&gt;@&amp;gt;&lt;/code&gt; 라는 키워드를 사용한다.&lt;/p&gt;
&lt;p&gt;그리고, &lt;code&gt;ARRAY[&amp;#39;&amp;lt;찾는 키워드&amp;gt;&amp;#39;]&lt;/code&gt; 를 이용한다.&lt;/p&gt;
&lt;p&gt;이는 메타데이터 검색 전용 키워드들이라고 생각하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;AUTO_INCREMENT&lt;/code&gt; 대신, &lt;code&gt;serial&lt;/code&gt; 을 사용하고 있는 모습도 볼 수 있었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;또한, Postgres 에서 지원하는 데이터 타입 &lt;code&gt;JSON&lt;/code&gt; 과, &lt;code&gt;JSONB&lt;/code&gt; 중에서, &lt;code&gt;JSONB&lt;/code&gt; 를 예시로 든다면,&lt;/p&gt;
&lt;p&gt;먼저, 주문 정보를 &amp;quot;전부&amp;quot; &lt;code&gt;JSONB&lt;/code&gt; 형태로 가지고 있다고 가정하자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;# 주문된 제품의 특징을 JSON Binary 정보로 가지고 있다 가정.
CREATE TABLE order_attr (
    id    serial    PRIMARY KEY,
    attr  JSONB
);

# 딱히 idx 를 넣지 않아도 되지만, 최적화를 위해 넣는다고 가정
CREATE INDEX idx_order_attr ON order_attr USING GIN (attr);

# 미국에서 발송된 모든 제품의 이름을 검색한다고 가정
SELECT attr-&amp;gt;&amp;gt;&amp;quot;product_name&amp;quot; AS product_name
FROM order_attr WHERE attr @&amp;gt; &amp;#39;{&amp;quot;sending_country&amp;quot;: &amp;quot;USA&amp;quot;}&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;JSONB 에 저장되어 있는 &lt;code&gt;key&lt;/code&gt;, &lt;code&gt;value&lt;/code&gt; 또한 메타데이터의 토큰으로 저장되어,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@&amp;gt;&lt;/code&gt; 키워드로 빠르게 찾을 수 있다.&lt;/p&gt;
&lt;p&gt;화살표를 사용하는 것은 &lt;code&gt;JSON&lt;/code&gt; 형태에서 특정 속성을 지정하기 위해 사용하는데,&lt;/p&gt;
&lt;p&gt;이는 다양한 사용법이 존재하여 검색해 보는 것을 추천한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Postgres 의 NoSQL 기능, HStore 데이터 타입?&lt;/h3&gt;
&lt;p&gt;위에서 다룬 &lt;code&gt;JSONB&lt;/code&gt;, 그리고 &lt;code&gt;JSON&lt;/code&gt; 은 JS 객체처럼 엄청난 자유도를 가지고 있다.&lt;/p&gt;
&lt;p&gt;위 2 개의 데이터 속성은 데이터 계층에서도 거의 무한한 자유도를 가진다.&lt;/p&gt;
&lt;p&gt;그러나, 자주 바뀔 수 있는 테이블의 속성에 대해 대응하는 데이터 타입이 존재한다.&lt;/p&gt;
&lt;p&gt;그것이 바로 &lt;code&gt;HStore&lt;/code&gt; 데이터 타입이다.&lt;/p&gt;
&lt;p&gt;예를 들어, 제품의 속성이다.&lt;/p&gt;
&lt;p&gt;제품의 속성 또한 정규화 된 테이블로 &amp;quot;가격&amp;quot;, &amp;quot;품목&amp;quot;, &amp;quot;이미지 경로&amp;quot; 와 같이 지정 할 수 있겠지만,&lt;/p&gt;
&lt;p&gt;제품 그 자체는 다양한 카테고리가 존재한다. 음식, 컴퓨터, 그림, 등등..&lt;/p&gt;
&lt;p&gt;다양한 카테고리 또한 정규화 할 수 있겠지만, 카테고리는 생겨 날 수도, 없어 질 수도, 변경 될 수도 있다.&lt;/p&gt;
&lt;p&gt;따라서, 이에 알맞는 데이터 타입은 &lt;code&gt;HStore&lt;/code&gt; 이라고 할 수 있다.&lt;/p&gt;
&lt;p&gt;그러나, &lt;code&gt;HStore&lt;/code&gt; 은 &lt;code&gt;{key: value}&lt;/code&gt; 로서 하나의 계층만 가질 수 있다.&lt;/p&gt;
&lt;p&gt;대신, 여러 쌍을 가질 수 있다.&lt;/p&gt;
&lt;p&gt;그리고 HStore 에 들어간 &lt;code&gt;value&lt;/code&gt; 에 대해 &lt;code&gt;Unique&lt;/code&gt; 를 보장한다는 점이 더욱 놀랍다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;# 제품의 &amp;quot;모든&amp;quot; 속성을 전부 커스텀화 하도록 만듬 (실제론 이렇게 만들지 않을 거 같네요)
CREATE TABLE products (
    id   serial  PRIMARY  KEY,
    attr hstore
)

# 컴퓨터 제품을 등록 해 보자!
INSERT INTO products (attr)
VALUES (
&amp;#39;name=&amp;gt;&amp;quot;Mac Book&amp;quot;, category=&amp;gt;&amp;quot;computer&amp;quot;, size=&amp;gt;&amp;quot;16 inch&amp;quot;, storage=&amp;gt;&amp;quot;1 TB&amp;quot;, color=&amp;gt;&amp;quot;Space Gray&amp;quot;&amp;#39;
);

# 제품 중, 컴퓨터 카테고리에 속하는 모든 제품들을 불러오자
SELECT * FROM products
WHERE attr @&amp;gt; &amp;#39;category=&amp;gt;&amp;quot;computer&amp;quot;&amp;#39;;&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h2&gt;이 글을 작성하며 배운 것&lt;/h2&gt;
&lt;p&gt;이 글을 작성하게 된 계기는, Udemy 에서 NestJS 강의 마지막에 존재한다.&lt;/p&gt;
&lt;p&gt;해당 강의는 NestJS 에 집중한 강의였지, TypeORM 과 연결하는 PostgreSQL 을 다루는 강의가 아니다.&lt;/p&gt;
&lt;p&gt;따라서, 내가 자주 사용하던 데이터베이스인 MySQL 과 MariaDB 가 아니라, 왜 Postgres 를 사용하는지 몰랐다.&lt;/p&gt;
&lt;p&gt;물론, 다양한 데이터베이스에 대한 이점과 단점이 존재하겠지만, 왜? Postgres 인지 조사하고 싶었다.&lt;/p&gt;
&lt;p&gt;이 참에, 데이터베이스의 특성도 다시 익히고, 모던 기능이 탑재된 데이터베이스의 특성도 알고 싶었다.&lt;/p&gt;
&lt;p&gt;나중에 어플리케이션을 제작하게 될 때, 내가 제작하게 될 어플리케이션의 한계점을 더 높이고,&lt;/p&gt;
&lt;p&gt;또한 생각날 아이디어를 현실화 시키기 위해서는 다양한 CS 지식은 필수이기 때문이었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;매우 놀란 것은, Postgres (PostgreSQL) 이 NoSQL 로도 사용 할 수 있다는 것이었다.&lt;/p&gt;
&lt;p&gt;정규화가 불가능한 매우 방대한 데이터셋을 다루는 NoSQL 기능으로도 사용할 수 있다니,&lt;/p&gt;
&lt;p&gt;이렇게 자유도가 높은 데이터베이스가 있나? 매우 놀랐다.&lt;/p&gt;
&lt;p&gt;Postgres 의 기능 커스텀을 통해 사이트를 만든 사람도 있다던데,&lt;/p&gt;
&lt;p&gt;나는 이걸 장난으로 받아들였다.&lt;/p&gt;
&lt;p&gt;그러나, 기능이 정말 많고, 데이터 유형의 자유도가 높음에 따라 진짜겠구나 생각하고 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;정규화 된 데이터베이스는 경량화 되어 있기도 하지만, 기능의 제한도 존재한다.&lt;/p&gt;
&lt;p&gt;그러나, 정규화와 비정규화를 동시에 지원하는 Postgres 는, 현대의 어플리케이션 제작에&lt;/p&gt;
&lt;p&gt;더욱 특화될 수 있다고 생각한다. 이유는, 방대한 데이터셋을 정의하기 위해 정규화 하는 것도 중요하지만,&lt;/p&gt;
&lt;p&gt;때에 따라서 속성이 변경 될 수 있는 비정규화 데이터셋 또한 품을 수 있어야 하기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;틀린 내용이 있거나, 의견이 있으시면, 댓글로 달아주시면 감사하겠습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;참고 사이트&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;PostgreSQL 공식 사이트&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.postgresql.org/&quot;&gt;https://www.postgresql.org/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.postgresql.org/docs/current/datatype-json.html&quot;&gt;https://www.postgresql.org/docs/current/datatype-json.html&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;IBM 테크 블로그 (PostgreSQL 이란 무엇입니까?)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.ibm.com/kr-ko/topics/postgresql&quot;&gt;https://www.ibm.com/kr-ko/topics/postgresql&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Google Cloud (What-is-postgresql?)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.ibm.com/kr-ko/topics/postgresql&quot;&gt;https://www.ibm.com/kr-ko/topics/postgresql&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;MongoDB 리소스 (SQL 대 NoSQL 데이터베이스 이해)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.mongodb.com/ko-kr/resources/basics/databases/nosql-explained/nosql-vs-sql&quot;&gt;https://www.mongodb.com/ko-kr/resources/basics/databases/nosql-explained/nosql-vs-sql&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Medium 블로그 (제목 : &amp;quot;NoSQL Capabilities in PostgreSQL&amp;quot;)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://medium.com/@dudkamv/nosql-capabilities-in-postgresql-9eec822886d9&quot;&gt;https://medium.com/@dudkamv/nosql-capabilities-in-postgresql-9eec822886d9&lt;/a&gt;&lt;/p&gt;</description>
      <category>잡다 지식</category>
      <category>json 데이터베이스</category>
      <category>json 호환</category>
      <category>NoSQL</category>
      <category>ORDBMS</category>
      <category>postgres</category>
      <category>PostgreSQL</category>
      <category>RDBMS</category>
      <category>데이터베이스</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/209</guid>
      <comments>https://codecreature.tistory.com/209#entry209comment</comments>
      <pubDate>Thu, 8 May 2025 22:18:33 +0900</pubDate>
    </item>
    <item>
      <title>.http 파일과 httpyac</title>
      <link>https://codecreature.tistory.com/208</link>
      <description>&lt;h1&gt;제목 : .http 파일과 httpyac&lt;/h1&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;VSCode&lt;/strong&gt;, &lt;strong&gt;IntelliJ&lt;/strong&gt; 와 같은 IDE 에서는 &lt;code&gt;.http&lt;/code&gt; 확장자를 가진 요청 파일을 지원한다.&lt;/p&gt;
&lt;p&gt;이 때, 위의 에디터들은 &lt;code&gt;.http&lt;/code&gt; 파일에 대한 구문과, 하이라이팅을 지원한다.&lt;/p&gt;
&lt;p&gt;뿐만 아니라, 내부의 내장된 Request Agent 가 쿠키와 세션을 유지하여 테스팅이 편리하다.&lt;/p&gt;
&lt;p&gt;하지만, 다른 에디터를 사용 할 경우, &lt;code&gt;.http&lt;/code&gt; 파일의 실행을 지원하는 플러그인이 없다.&lt;/p&gt;
&lt;p&gt;따라서, 로컬 머신에 &lt;code&gt;httpyac&lt;/code&gt; 이라는 프로그램을 설치 할 수 있다.&lt;/p&gt;
&lt;p&gt;먼저, &lt;code&gt;httpyac&lt;/code&gt; 을 이용하기 위해서, 내부에 &lt;code&gt;###&lt;/code&gt; 을 통해 하나의 요청에 대한 주석을 작성해야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;httpyac 은 뭐지?&lt;/h2&gt;
&lt;p&gt;이는 에디터에서 &lt;code&gt;http&lt;/code&gt; 플러그인을 사용하는 대신, Homebrew 와 같은 패키지 매니저를 통해&lt;/p&gt;
&lt;p&gt;직접 명령어로 테스팅 할 수 있는 것이다.&lt;/p&gt;
&lt;p&gt;나는 Vim 에서 NeoVim 을 사용했고,&lt;/p&gt;
&lt;p&gt;NeoVim 에디터의 커스텀으로도 불편 한 점이 많아,&lt;/p&gt;
&lt;p&gt;터미널 기능과 에디터의 중간의 위치를 가진 Zed 에디터를 선택했다.&lt;/p&gt;
&lt;p&gt;현재는 매우 만족하면서 사용하고 있는데, 문제는 VSCode 나 IntelliJ 처럼&lt;/p&gt;
&lt;p&gt;내부 Http Request Agent 를 지원 해 주지 않는다는 것이다.&lt;/p&gt;
&lt;p&gt;따라서, 편리한 &lt;code&gt;.http&lt;/code&gt; 파일을 사용하기 위해, HttpYac 을 선택했다.&lt;/p&gt;
&lt;p&gt;기본적으로, 실행은 이러하다 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  users git:(main) ✗ httpyac requests.http
? please choose which region to use
❯ all
  새로운 유저 생성
  유저 로그인
  주어진 ID 로 특정한 유저 찾기
  whoami 메서드로 요청을 보내 세션을 통한 나를 찾기
  로그아웃
  주어진 email 로 모든 유저 찾기
(Use arrow keys to reveal more choices)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;### 새로운 유저 생성
POST http://localhost:3000/auth/signup
content-type: application/json

{
    &amp;quot;email&amp;quot; : &amp;quot;test@gmail.com&amp;quot;,
    &amp;quot;password&amp;quot; : &amp;quot;12341234&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;내부의 &lt;code&gt;.http&lt;/code&gt; 파일의 내용을 이렇게 생겼다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;###&lt;/code&gt; 을 통해 실행 시, 보여 질 수 있는 제목을 정해놓았다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;body&lt;/code&gt; 로 들어 갈 내용을 `{ ... } 로 적을 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;httpyac CLI 로서의 문제점&lt;/h2&gt;
&lt;p&gt;문제는, VSCode 나 IntelliJ 와 다르게, Request Agent 로서의 정보를 저장하는 기능이 없다.&lt;/p&gt;
&lt;p&gt;즉, 로그인 후 쿠키를 전송하는 행위는 위의 http 파일처럼 작성해서는 전송이 안된다는 것이다.&lt;/p&gt;
&lt;p&gt;Request CLI 로 실행 시, 한 번의 실행동안 쿠키나 세션이 유지되고,&lt;/p&gt;
&lt;p&gt;완수되고 나면 모든 캐싱 정보들이 사라진다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;httpyac 에서의 Request Agent 캐싱 정보 유지하기&lt;/h2&gt;
&lt;p&gt;httpyac 공식 홈페이지에서 제공하는 캐싱 유지 방식들이 있긴 한데,&lt;/p&gt;
&lt;p&gt;그 이전에 나는 httpyac 에서 지원하는 몇 가지 키워드를 사용했다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@name xxxx&lt;/code&gt; - 현재 파일, 혹은 외부 파일에서 해당 요청을 참조 할 수 있게 정한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@import ../xxxx.http&lt;/code&gt; - `사용하고자 하는 요청이 현재 .http 파일에 없을 경우, 해당 경로에서 가져온다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@ref xxxx&lt;/code&gt; - 현재 파일, 혹은 외부 파일에서 &lt;code&gt;@name&lt;/code&gt; 과 같이 송출된 요청을 먼저 실행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;위의 3 가지 키워드를 사용한다면, 로그인 후 자신의 정보를 추출하는 요청이 가능해진다.&lt;/p&gt;
&lt;p&gt;한번, 예시를 들어 본다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;users/requests.http&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;### 유저 로그인
# @name signin
POST http://localhost:3000/auth/signin
content-type: application/json

{
    &amp;quot;email&amp;quot; : &amp;quot;test@gmail.com&amp;quot;,
    &amp;quot;password&amp;quot; : &amp;quot;12341234&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@name&lt;/code&gt; : 내부 혹은 외부에서 &lt;code&gt;@ref&lt;/code&gt; 혹은 &lt;code&gt;@forceRef&lt;/code&gt; 로 자신을 요청 할 수 있다.\&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@import&lt;/code&gt; : 원하는 요청이 다른 &lt;code&gt;.http&lt;/code&gt; 파일에 존재 할 경우, 경로로 가져올 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@ref&lt;/code&gt; : 내부 혹은 외부에서 별칭으로 참조 할 수 있는 요청을 가져온다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;reports/request.http&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;### POST http://localhost:3000/reports
# @import ../users/requests.http
# @ref signin
POST http://localhost:3000/reports
content-type: application/json
{
    &amp;quot;price&amp;quot; : 500000,
    &amp;quot;make&amp;quot; : &amp;quot;toyota&amp;quot;,
    &amp;quot;model&amp;quot; : &amp;quot;corolla&amp;quot;,
    &amp;quot;year&amp;quot; : 1980,
    &amp;quot;lng&amp;quot; : 0,
    &amp;quot;lat&amp;quot; : 0,
    &amp;quot;mileage&amp;quot; : 100000
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h2&gt;httpyac 사용법&lt;/h2&gt;
&lt;p&gt;위에서는 httpyac 을 사용하면서 마주할 수 밖에 없는 문제점에 대해서 설명했다.&lt;/p&gt;
&lt;p&gt;그렇다면, 어떻게 사용할까?&lt;/p&gt;
&lt;p&gt;위에서 나는 &lt;code&gt;POST http://localhost:3000/auth/signin&lt;/code&gt; 를 요청해보자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  users git:(main) ✗ httpyac requests.http
✔ please choose which region to use 유저 로그인

---------------------

=== 유저 로그인 ===
# 요청 본문 시작
POST http://localhost:3000/auth/signin
accept: */*
accept-encoding: gzip, deflate, br
content-length: 63
content-type: application/json
user-agent: httpyac

{
    &amp;quot;email&amp;quot; : &amp;quot;test@gmail.com&amp;quot;,
    &amp;quot;password&amp;quot; : &amp;quot;12341234&amp;quot;
} # 요청 본문 종료.
HTTP/1.1 201  - Created # 응답 본문 시작
connection: keep-alive
content-length: 33
content-type: application/json; charset=utf-8
date: Mon, 05 May 2025 14:19:37 GMT
etag: W/&amp;quot;21-nIb9OrtC7n5iRpqXg0Bp/3uF/RA&amp;quot;
keep-alive: timeout=5
set-cookie: session=eyJ1c2VySWQiOjF9; path=/; httponly,session.sig=Assbm1VoBVhl6uJ90YDXls2YHPs; path=/; httponly
x-powered-by: Express

{
  &amp;quot;id&amp;quot;: 1,
  &amp;quot;email&amp;quot;: &amp;quot;test@gmail.com&amp;quot;
}

1 requests processed (1 succeeded) # 응답 본문 종료.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위에서 볼 수 있듯이, CLI 에 익숙하지 않은 사람은 읽기가 어려울 수 있다.&lt;/p&gt;
&lt;p&gt;하지만, 요청 본문과 응답 본문을 전부 볼 수 있다는 점에서 굉장히 유용하다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;그렇다면, 로그인 상태를 필요로 하는 요청을 해 보자&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;위에서 제시한 &lt;code&gt;POST http://localhost:3000/reports&lt;/code&gt; 는 로그인 상태를 기반으로 한다.&lt;/p&gt;
&lt;p&gt;즉, 로그인 되지 않는다면 예외가 반환된다.&lt;/p&gt;
&lt;p&gt;위에서 보여준 예시는 로그인 상태를 먼저 요청하게 해 주므로,&lt;/p&gt;
&lt;p&gt;단순한 하나의 reports 요청을 해 보자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;### POST http://localhost:3000/reports
POST http://localhost:3000/reports
content-type: application/json
{
    &amp;quot;price&amp;quot; : 500000,
    &amp;quot;make&amp;quot; : &amp;quot;toyota&amp;quot;,
    &amp;quot;model&amp;quot; : &amp;quot;corolla&amp;quot;,
    &amp;quot;year&amp;quot; : 1980,
    &amp;quot;lng&amp;quot; : 0,
    &amp;quot;lat&amp;quot; : 0,
    &amp;quot;mileage&amp;quot; : 100000
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위에서는 &lt;code&gt;@import&lt;/code&gt;, &lt;code&gt;@ref&lt;/code&gt; 키워드는 존재하지 않는다.&lt;/p&gt;
&lt;p&gt;즉, CLI 단계에서 명령어가 끝난다면, 쿠키와 세션은 종료된다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  reports git:(main) ✗ httpyac requests.http
✔ please choose which region to use POST http://localhost:3000/reports

---------------------

=== POST http://localhost:3000/reports ===

POST http://localhost:3000/reports
accept: */*
accept-encoding: gzip, deflate, br
content-length: 145
content-type: application/json
user-agent: httpyac

{
    &amp;quot;price&amp;quot; : 500000,
    &amp;quot;make&amp;quot; : &amp;quot;toyota&amp;quot;,
    &amp;quot;model&amp;quot; : &amp;quot;corolla&amp;quot;,
    &amp;quot;year&amp;quot; : 1980,
    &amp;quot;lng&amp;quot; : 0,
    &amp;quot;lat&amp;quot; : 0,
    &amp;quot;mileage&amp;quot; : 100000
}
HTTP/1.1 403  - Forbidden
connection: keep-alive
content-length: 69
content-type: application/json; charset=utf-8
date: Mon, 05 May 2025 14:29:10 GMT
etag: W/&amp;quot;45-MZJWZc+Y+RUbHpnhz2B2Vipii24&amp;quot;
keep-alive: timeout=5
x-powered-by: Express

{
  &amp;quot;message&amp;quot;: &amp;quot;Forbidden resource&amp;quot;,
  &amp;quot;error&amp;quot;: &amp;quot;Forbidden&amp;quot;,
  &amp;quot;statusCode&amp;quot;: 403
}

1 requests processed (1 succeeded)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 요청은 단순히 어떠한 credential 정보도 없이 전송되었다.&lt;/p&gt;
&lt;p&gt;쿠키, 캐시, 세션, JWT 등 어떠한 것도 전송되지 않은 것이다.&lt;/p&gt;
&lt;p&gt;따라서, 프로그램에 입력된 &lt;code&gt;Guard&lt;/code&gt; 에 따라 403(Forbidden)이 반환되었다. (NestJS)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그렇다면, &lt;code&gt;@import&lt;/code&gt;, &lt;code&gt;@ref&lt;/code&gt; 키워드를 통해 &lt;code&gt;users/&lt;/code&gt; 디렉토리에 존재하는&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/users/request.http&lt;/code&gt; 파일을 참조 해 보자 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;### POST http://localhost:3000/reports
# @import ../users/requests.http
# @ref signin
POST http://localhost:3000/reports
content-type: application/json
{
    &amp;quot;price&amp;quot; : 500000,
    &amp;quot;make&amp;quot; : &amp;quot;toyota&amp;quot;,
    &amp;quot;model&amp;quot; : &amp;quot;corolla&amp;quot;,
    &amp;quot;year&amp;quot; : 1980,
    &amp;quot;lng&amp;quot; : 0,
    &amp;quot;lat&amp;quot; : 0,
    &amp;quot;mileage&amp;quot; : 100000
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  reports git:(main) ✗ httpyac requests.http
✔ please choose which region to use POST http://localhost:3000/reports

---------------------

=== 유저 로그인 ===

POST http://localhost:3000/auth/signin
accept: */*
accept-encoding: gzip, deflate, br
content-length: 63
content-type: application/json
user-agent: httpyac

{
    &amp;quot;email&amp;quot; : &amp;quot;test@gmail.com&amp;quot;,
    &amp;quot;password&amp;quot; : &amp;quot;12341234&amp;quot;
}
HTTP/1.1 201  - Created
connection: keep-alive
content-length: 33
content-type: application/json; charset=utf-8
date: Mon, 05 May 2025 14:31:55 GMT
etag: W/&amp;quot;21-nIb9OrtC7n5iRpqXg0Bp/3uF/RA&amp;quot;
keep-alive: timeout=5
set-cookie: session=eyJ1c2VySWQiOjF9; path=/; httponly,session.sig=Assbm1VoBVhl6uJ90YDXls2YHPs; path=/; httponly
x-powered-by: Express

{
  &amp;quot;id&amp;quot;: 1,
  &amp;quot;email&amp;quot;: &amp;quot;test@gmail.com&amp;quot;
}

---------------------

=== POST http://localhost:3000/reports ===

POST http://localhost:3000/reports
accept: */*
accept-encoding: gzip, deflate, br
content-length: 145
content-type: application/json
cookie: session=eyJ1c2VySWQiOjF9; session.sig=Assbm1VoBVhl6uJ90YDXls2YHPs
user-agent: httpyac

{
    &amp;quot;price&amp;quot; : 500000,
    &amp;quot;make&amp;quot; : &amp;quot;toyota&amp;quot;,
    &amp;quot;model&amp;quot; : &amp;quot;corolla&amp;quot;,
    &amp;quot;year&amp;quot; : 1980,
    &amp;quot;lng&amp;quot; : 0,
    &amp;quot;lat&amp;quot; : 0,
    &amp;quot;mileage&amp;quot; : 100000
}

HTTP/1.1 201  - Created
connection: keep-alive
content-length: 130
content-type: application/json; charset=utf-8
date: Mon, 05 May 2025 14:31:55 GMT
etag: W/&amp;quot;82-w4pyzgCUzmyC/qLHjFocf3p6bmg&amp;quot;
keep-alive: timeout=5
x-powered-by: Express

{
  &amp;quot;id&amp;quot;: 3,
  &amp;quot;price&amp;quot;: 500000,
  &amp;quot;year&amp;quot;: 1980,
  &amp;quot;lng&amp;quot;: 0,
  &amp;quot;lat&amp;quot;: 0,
  &amp;quot;make&amp;quot;: &amp;quot;toyota&amp;quot;,
  &amp;quot;model&amp;quot;: &amp;quot;corolla&amp;quot;,
  &amp;quot;mileage&amp;quot;: 100000,
  &amp;quot;approved&amp;quot;: false,
  &amp;quot;userId&amp;quot;: 1
}

2 requests processed (2 succeeded)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;users/request.http&lt;/code&gt; 파일의 &lt;code&gt;signin&lt;/code&gt; 을 통해 로그인 세션을 가져 올 수 있다.&lt;/p&gt;
&lt;p&gt;그런데, 위의 내용이 너무 긴 것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;이유는 실제로 &lt;code&gt;@ref&lt;/code&gt; 하는 과정에서, &lt;code&gt;signin&lt;/code&gt; 요청을 실행 한 뒤,&lt;/p&gt;
&lt;p&gt;이후로 &lt;code&gt;POST reports&lt;/code&gt; 했기 때문이다. 이러한 결과는 가독성을 해치는 결과를 낳는다.&lt;/p&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;h2&gt;httpyac 에서 쿠키 캐싱하기&lt;/h2&gt;
&lt;p&gt;httpyac 은 실제 NodeJS 구문 기반의 scripting 을 지원한다.&lt;/p&gt;
&lt;p&gt;아주 간단한 예시를 들자면,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;{{
    console.log(&amp;quot;Test Script&amp;quot;);
}}
GET http://localhost:3000/users
.....&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위와 같은 형식으로 JavaScript 와 상호작용할 수 있다.&lt;/p&gt;
&lt;p&gt;그리고 만약에 &lt;code&gt;request&lt;/code&gt; 혹은 &lt;code&gt;response&lt;/code&gt; 에 간섭하고 싶다면,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;{{@request
    request.headers[&amp;#39;Cookie&amp;#39;] = ....
}}
GET &amp;lt;url&amp;gt;

{{@response
    const cookieArr = response.headers[&amp;quot;Set-Cookie&amp;quot;];
    ....
}}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이러한 형식으로, 요청 앞 뒤에서 요청과 응답 객체에 간섭 및 추출 행위가 가능해진다.&lt;/p&gt;
&lt;p&gt;빠른 테스팅을 위해, 아직 httpyac 에서 &lt;code&gt;.js&lt;/code&gt; 파일로 이어주는 기능을 사용하지 않고,&lt;/p&gt;
&lt;p&gt;기능이 동작하는지 확인하기 위해 먼저 바로 포워딩 해 보았다.&lt;/p&gt;
&lt;p&gt;먼저, 나의 프로젝트 디렉토리 현황은 대충 이러하다 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;&amp;lt;root&amp;gt;
  /.httpyac # 직접 만든 디렉토리
    # cookies.json 이라는 파일이 들어갈 예정
  /src
    /users
      requests.http
    /reports
      requests.http&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이제 JS Scripting 을 만들어 볼 건데, &lt;code&gt;fs&lt;/code&gt;, &lt;code&gt;path&lt;/code&gt; 디폴트 라이브러리를 사용한다.&lt;/p&gt;
&lt;p&gt;사실 위 디렉토리를 살펴보았다면, &lt;code&gt;requests.http&lt;/code&gt; 파일들이 비효율적인 위치에 존재한다는 것을 알 것이다.&lt;/p&gt;
&lt;p&gt;이후에 최적화 할 것이라서, 일단 바로 코드를 작성 해 보겠다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;root&amp;gt;/src/users/request.http&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;### 유저 로그인
# @name signin
POST http://localhost:3000/auth/signin
content-type: application/json

{
    &amp;quot;email&amp;quot; : &amp;quot;test@gmail.com&amp;quot;,
    &amp;quot;password&amp;quot; : &amp;quot;12341234&amp;quot;
}

{{@response
    const fs = require(&amp;#39;fs&amp;#39;);
    const path = require(&amp;#39;path&amp;#39;);
    const cookies = response.headers[&amp;#39;set-cookie&amp;#39;] || [];
    const jar = cookies.map(c =&amp;gt; c.split(&amp;#39;;&amp;#39;)[0]);

    fs.writeFileSync(
        path.resolve(__dirname + &amp;#39;../../../.httpyac/cookies.json&amp;#39;),
        JSON.stringify(jar, null, 2) // indent 2 번으로 가독성 확보
    );
}}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;root&amp;gt;/.httpyac/cookies.json&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;[
  &amp;quot;session=eyJ1c2VySWQiOjF9&amp;quot;,
  &amp;quot;session.sig=Assbm1VoBVhl6uJ90YDXls2YHPs&amp;quot;
]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;root&amp;gt;/src/reports/requests.http&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;### POST http://localhost:3000/reports
POST http://localhost:3000/reports
content-type: application/json
{
    &amp;quot;price&amp;quot; : 500000,
    &amp;quot;make&amp;quot; : &amp;quot;toyota&amp;quot;,
    &amp;quot;model&amp;quot; : &amp;quot;corolla&amp;quot;,
    &amp;quot;year&amp;quot; : 1980,
    &amp;quot;lng&amp;quot; : 0,
    &amp;quot;lat&amp;quot; : 0,
    &amp;quot;mileage&amp;quot; : 100000
}
{{@request
    const fs = require(&amp;#39;fs&amp;#39;);
    const path = require(&amp;#39;path&amp;#39;);
    const jar = JSON.parse(
        fs.readFileSync(
            path.resolve(&amp;#39;../../.httpyac/cookies.json&amp;#39;)
        )
    );
    // 배열을 연결할 때, 중간에 &amp;quot;; &amp;quot; 를 넣어준다.
    request.headers[&amp;#39;Cookie&amp;#39;] = jar.join(&amp;#39;; &amp;#39;);
}}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;물론, 경로 문제를 해결하기 위해, 한 곳에 전부 파일을 넣으면 매우 깔끔해진다.&lt;/p&gt;
&lt;p&gt;최적화 하기 전에, 먼저 결과는 2 개 모두 정상작동한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;다시 키워드를 사용하는게 낫지 않을까?&lt;/h3&gt;
&lt;p&gt;맞는 말이다. 나도 스크립팅을 작성하면서,&lt;/p&gt;
&lt;p&gt;가독성을 해치게 되더라도 그냥 키워드를 사용하는게 더 편하겠다 생각한다.&lt;/p&gt;
&lt;p&gt;심지어, 하나의 요청마다 거의 7 줄의 스크립트를 넣어야 하는 상황이다.&lt;/p&gt;
&lt;p&gt;문제를 나열 해 보자 :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;경로가 너무 일관되지 않는다.&lt;/li&gt;
&lt;li&gt;쿠키 정보 주입, 추출 시 마다 스크립트가 7 줄씩 들어간다.&lt;/li&gt;
&lt;li&gt;도메인 정보가 주어지지 않고, 그냥 쿠키들만 배열로 json 으로 들어간다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;일단, 1 번은 쉽게 해결 할 수 있다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;기존의 디렉토리 구조는 이러하다 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;&amp;lt;root&amp;gt;
  /.httpyac
    cookies.json
  /src
    /users
      requests.http
    /reports
      requests.http&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;httpyac 은, 현재 실행되고 있는 로컬 서버에 요청을 날린다.&lt;/p&gt;
&lt;p&gt;즉, 딱히 &lt;code&gt;users&lt;/code&gt;, &lt;code&gt;reports&lt;/code&gt; 디렉토리에 &lt;code&gt;.http&lt;/code&gt; 파일이 존재 할 이유는 없다.&lt;/p&gt;
&lt;p&gt;디렉토리 구조를 바꾼다 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;&amp;lt;root&amp;gt;
  /.httpyac
    cookies.json
    users-requests.http
    reports-requests.http
  /src
    /users
      기존 파일들..
    /reports
      기존 파일들..&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그러나, 경로 문제만 해결되었을 뿐, 반복 스크립팅 문제가 해결되지 않았다.&lt;/p&gt;
&lt;p&gt;이를 해결하기 위해 httpyac 에서 어떠한 기능을 발견했는데, 바로 &lt;strong&gt;Hooks&lt;/strong&gt; 기능이었다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hooks 기능이란?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;우선, &lt;code&gt;httpyac.config.js&lt;/code&gt; 파일을 만들면서 사용할 수 있는 다양한 훅이 존재한다.&lt;/p&gt;
&lt;p&gt;단순하게 &lt;code&gt;httpyac&lt;/code&gt; 명령어를 사용한다고 가정 했을 때,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;package.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;httpyac.config.js&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.httpyac.js&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.httpyac.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.env&lt;/code&gt; - 이건 사이트를 참조하길 바랍니다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이러한 파일에 Hook 을 작성 할 수 있다.&lt;/p&gt;
&lt;p&gt;httpyac 명령어와 &lt;code&gt;xxx.http&lt;/code&gt; 파일을 실행하게 될 시,&lt;/p&gt;
&lt;p&gt;현재 디렉토리 --&amp;gt; 부모 디렉토리 --&amp;gt; 위의 파일명이 존재하는 디렉토리&lt;/p&gt;
&lt;p&gt;순으로 거쳐가며 정해진 파일을 찾는다.&lt;/p&gt;
&lt;p&gt;나의 경우,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;&amp;lt;root&amp;gt;
  httpyaac.config.js

  /.httpyac
    users-requests.http
    reports-requests.http
    cookies.json&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이러한 파일 계층으로 만들어 두었다.&lt;/p&gt;
&lt;p&gt;이는 &lt;a target=&quot;_blank&quot; href=&quot;https://httpyac.github.io/plugins/plugin-api.html&quot;&gt;https://httpyac.github.io/plugins/plugin-api.html&lt;/a&gt; 의 &lt;code&gt;hooks&lt;/code&gt; 를 참고하면 된다.&lt;/p&gt;
&lt;p&gt;그래서, 우리가 &lt;code&gt;.http&lt;/code&gt; 파일을 &lt;code&gt;httpyac&lt;/code&gt; 으로 실행하는 과정에서 훅은 도대체 왜 필요할까?&lt;/p&gt;
&lt;p&gt;아주 간단하게 말하자면,&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;.http 파일에 어떠한 추가 키워드 없이, 마치 세션이 이어 진 것 처럼 쿠키를 사용 할 수 있다&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;hooks 에는 기본적으로 Request, Response 객체를 조작 및 추출할 수 있는데,&lt;/p&gt;
&lt;p&gt;이는 파일에 직접적으로 작성 할 수 있는 공간을 마련 해 준다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;httpyac.config.js&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;/**
* @author damhyeong
* @email rhdwhdals8765@gmail.com
*
* 밑의 코드는 서버에서 날아오는 &amp;quot;Set-Cookie&amp;quot; 에 대해 자동으로 설정 해 주는 코드입니다.
*
* VSCode 나 IntelliJ 는 `.http` 파일에 대한 기본적인 실행 환경을 편리하게 만들어 주지만,
*
* 실제로 httpyac 명령어 만으로는 한 번 명령이 수행 된 이후, 모든 세션이 사라지게 됩니다.
*
* 명령이 끝난 이후에도 설정된 Cookies 에 대한 값을 유지하기 위해, File System 을 활용합니다.
*
* (1) Response --&amp;gt; &amp;quot;Set-Cookie&amp;quot; 배열 혹은 단순 문자열 추출
* (2) 추출된 문자열을 분리하여 현재 디렉토리의 &amp;quot;cookies.json&amp;quot; 으로 저장
* (3) Request --&amp;gt; &amp;quot;Cookie&amp;quot; 에 들어가야 할 정보가 필요
* (4) &amp;quot;cookies.json&amp;quot; 으로부터 JSON 파싱
* (5) request.headers[&amp;quot;Cookie&amp;quot;] 에 들어가야 할 정보가 필요.
* (6) 이 때, 파일 시스템으로 쿠키 배열, 혹은 문자열 추출
* (7) request.headers[&amp;quot;Cookie&amp;quot;] 에 파싱된 쿠키들 적용
* (8) 굳이 불편하게 .http 파일에 스크립팅을 적용 할 필요가 없어진다!
*/

module.exports = {
  // httpyac 명령어 실행 시, 해당 프로세스에서 쿠키를 자동으로 저장하는 Jar 을 잠깐 만든다.
  cookieJarEnabled: true,
  // 커스텀 Hook 제작 - Request 그리고 Response
  configureHooks: function (api) {
    // Response 반응.
    api.hooks.onResponse.addHook(&amp;quot;saveCookies&amp;quot;, function (response) {
      // 파일 시스템 라이브러리
      const fs = require(&amp;#39;fs&amp;#39;);
      // 경로 파악 라이브러리 (__dirname 혹은 __filename 사용 가능)
      const path = require(&amp;#39;path&amp;#39;);
      // 만약에, 서버에서 쿠키 설정 헤더가 없다면, 빈 배열로 할당한다.
      const cookies = response.headers[&amp;#39;set-cookie&amp;#39;] || [];

      // 서버가 설정 쿠키를 전달하였다면,
      if (cookies.length !== 0) {
        //
        const jar = cookies.map(c =&amp;gt; c.split(&amp;#39;;&amp;#39;)[0]);
        console.log(jar);
        fs.writeFileSync(
          path.resolve(__dirname + &amp;#39;cookies.json&amp;#39;),
          JSON.stringify(jar, null, 2)
        );
      }
    });

    // Request 반응.
    api.hooks.onRequest.addHook(&amp;quot;insertCookies&amp;quot;, function (request) {
      const fs = require(&amp;#39;fs&amp;#39;);
      const path = require(&amp;#39;path&amp;#39;);

      const cookieArrStr = fs.readFileSync(
        path.resolve(__dirname + &amp;quot;cookies.json&amp;quot;)
      );
      const jar = JSON.parse(cookieArrStr);
      request.headers[&amp;#39;Cookie&amp;#39;] = jar.join(&amp;#39;; &amp;#39;);
    })
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 코드를 그대로 복사 붙여넣기 해도, 이 글을 활용하기로 결정했다면,&lt;/p&gt;
&lt;p&gt;단순 쿠키 저장 및 전달에는 큰 문제가 없다. (같은 디렉토리에 요청, 쿠키 파일이 있다는 가정 하에)&lt;/p&gt;
&lt;p&gt;위의 설정 파일의 API 에는 2 가지의 Hook 이 존재한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;api.hooks.onResponse&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.http&lt;/code&gt; 파일 실행 시, 요청 후 응답 객체에서 정보를 추출하여 어떠한 행동이든 할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;api.hooks.onRequest&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.http&lt;/code&gt; 파일 실행 시, 요청 전 요청 객체에 어떠한 정보를 추가 및 삭제 할 지 결정 할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;글이 너무 길어 질 것 같아서, 말하자면, &lt;code&gt;.http&lt;/code&gt; 본문에는 어떠한 메타데이터도 넣지 않은 상황이다.&lt;/p&gt;
&lt;p&gt;그리고, &lt;code&gt;cookies.json&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;[
  &amp;quot;session=eyJ1c2VySWQiOjF9&amp;quot;,
  &amp;quot;session.sig=Assbm1VoBVhl6uJ90YDXls2YHPs&amp;quot;
]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;요청 시, &lt;code&gt;response&lt;/code&gt; 의 &lt;code&gt;Set-Cookie&lt;/code&gt; 헤더를 추출하여 정상적으로 파일에 저장하고,&lt;/p&gt;
&lt;p&gt;요청 전, &lt;code&gt;request&lt;/code&gt; 의 &lt;code&gt;Cookie&lt;/code&gt; 헤더에 파일로부터 가져온 쿠키들을 &lt;code&gt;;&lt;/code&gt; 로 연결하여 전달한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;결과&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;나는 사용하면서 인식한 3 가지 문제점을 해결했다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;경로의 비 일관성&lt;/li&gt;
&lt;li&gt;실행 시, 다른 요청 또한 실행되어 가독성을 해친다.&lt;/li&gt;
&lt;li&gt;쿠키 정보를 주입 및 추출하기 위해 스크립트를 7 줄씩 작성해야 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;그러나, 도메인 정보는 해결하지 않았는데, 이는 &lt;code&gt;.http&lt;/code&gt; 파일이 단순히 내 로컬 파일에서 실행되기 때문이다.&lt;/p&gt;
&lt;p&gt;만약에 도메인 정보까지 넣고자 한다면, &lt;code&gt;cookies.json&lt;/code&gt; 에 들어갈 내용의 구조가 바뀌며,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;onResponse&lt;/code&gt;, &lt;code&gt;onRequest&lt;/code&gt; 의 내부 로직이 추가된다.&lt;/p&gt;
&lt;p&gt;아마, &lt;code&gt;cookies.json&lt;/code&gt; 은 이러한 형태를 가지게 될 것이다. (내가 만든다면..)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;
{
  &amp;quot;localhost&amp;quot; : [
    {
      &amp;quot;path&amp;quot; : &amp;quot;/&amp;quot;,
      &amp;quot;name&amp;quot; : &amp;quot;session&amp;quot;,
      &amp;quot;value&amp;quot; : &amp;quot;eyJ1c2VySWQiOm51bGx9&amp;quot;,
      &amp;quot;date&amp;quot; : -1
    },
    {
      &amp;quot;path&amp;quot; : &amp;quot;/&amp;quot;,
      &amp;quot;name&amp;quot; : &amp;quot;session.sig&amp;quot;,
      &amp;quot;value&amp;quot; : &amp;quot;tUU3b4KEiPsDoss45BkeyGzl3rQ&amp;quot;,
      &amp;quot;date&amp;quot; : -1
    }
  ],
  &amp;quot;&amp;lt;실제 도메인&amp;gt;&amp;quot; : [
    ....
  ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이러한 쿠키 정보를 건네 줄 때는 보통 &amp;quot;domain=xxxx&amp;quot; 도메인 정보가 와야 하는데,&lt;/p&gt;
&lt;p&gt;로컬 머신에서는 따로 &lt;code&gt;domain&lt;/code&gt; 속성을 보내 주지 않는다.&lt;/p&gt;
&lt;p&gt;따라서, &lt;code&gt;domain&lt;/code&gt; 이 쿠키 정보에 없을 경우, &lt;code&gt;localhost&lt;/code&gt; 로 작성 해 놓는 것이다.&lt;/p&gt;
&lt;p&gt;그리고 &lt;code&gt;date&lt;/code&gt; 가 &lt;code&gt;-1&lt;/code&gt; 을 가지는 이유는, 만료일을 정해 놓지 않았기 때문이다. (무한)&lt;/p&gt;
&lt;p&gt;여기엔 숫자 1710223432.. 와 같은 숫자가 들어가며, 이는 1970년 1월 1일부터의 밀리세컨드를 말한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;httpyac 의 Hook 은 무엇이 있을까?&lt;/h2&gt;
&lt;p&gt;내가 작성 한 2 개의 Hook 말고도, httpyac 에서 제공하는 다양한 기능이 존재한다.&lt;/p&gt;
&lt;p&gt;몇 가지 예를 들자면..&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;ParseHook&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;api.hooks.parse.addHook&lt;/code&gt; 으로 사용이 가능하며, &lt;code&gt;.http&lt;/code&gt; 본문을 읽어 메타데이터를 주입한다.&lt;/p&gt;
&lt;p&gt;이 Hook 은 정말 &lt;code&gt;.http&lt;/code&gt; 파일을 고도화하여 사용 할 것이라면 추천하지만,&lt;/p&gt;
&lt;p&gt;그렇지 않다면 딱히 추천하지 않는다... (regex 사용해서 매칭시킴.)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;ReplaceVariableHook&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;api.hooks.provideVariables.addHook&lt;/code&gt; 으로 사용이 가능하며,&lt;/p&gt;
&lt;p&gt;작성한 환경 변수 파일 &lt;code&gt;.env....&lt;/code&gt; 을 참조 할 수도 있으며,&lt;/p&gt;
&lt;p&gt;.http 파일에 특정 변수를 제공 할 수도 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;OnRequestHook&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;api.hooks.onRequest.addHook&lt;/code&gt; 으로 사용이 가능하다.&lt;/p&gt;
&lt;p&gt;내가 위에 작성 해 놓은 예제를 보면 알 수 있듯이,&lt;/p&gt;
&lt;p&gt;JS 환경의 &lt;code&gt;req&lt;/code&gt; 객체를 조작한다고 생각하면 된다.&lt;/p&gt;
&lt;p&gt;새로운 헤더를 넣을 수 있으며, 또한 쿠키도 주입 할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;OnResponseHook&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;api.hooks.onResponse.addHook&lt;/code&gt; 으로 사용이 가능하다.&lt;/p&gt;
&lt;p&gt;이것 또한 위에 쿠키를 추출하기 위한 예제를 만들어 놓았는데,&lt;/p&gt;
&lt;p&gt;JS 환경의 &lt;code&gt;res&lt;/code&gt; 객체에서 정보를 추출할 수 있다.&lt;/p&gt;
&lt;p&gt;이 때, 만약에 나 처럼 쿠키가 아니라,&lt;/p&gt;
&lt;p&gt;따로 &lt;code&gt;body&lt;/code&gt; 에 &lt;strong&gt;액세스 토큰&lt;/strong&gt;, &lt;strong&gt;리프레쉬 토큰&lt;/strong&gt; 을 주었을 경우,&lt;/p&gt;
&lt;p&gt;이를 포착하여 내가 했던 것 처럼 파일에 저장 할 수도 있다.&lt;/p&gt;
&lt;p&gt;더 많은 정보는 &lt;a target=&quot;_blank&quot; href=&quot;https://httpyac.github.io/plugins/plugin-api.html&quot;&gt;https://httpyac.github.io/plugins/plugin-api.html&lt;/a&gt; 를 참조하면 된다.&lt;/p&gt;
&lt;p&gt;그리고, 여기서 다루지 않는 변수 사용법과 scripting 과 같은 좋은 자료들이 공식 문서에 있다.&lt;/p&gt;
&lt;p&gt;맨 밑에 참고 사이트로 &amp;quot;공식 사이트&amp;quot; 를 걸어놓았으니, 보면 굉장히 유용 할 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;이 글을 작성하며 배운 점&lt;/h2&gt;
&lt;p&gt;사실 나 또한 API 요청을 쉽게 할 수 있는 프로그램이 많다는 것을 안다.&lt;/p&gt;
&lt;p&gt;POSTMAN 을 통해 요청을 넣어도 되고, (가장 쉬운 방법)&lt;/p&gt;
&lt;p&gt;아니면 E2E 테스팅을 돌려서 확인하는 것이 쉬울 것이다. (Node 의 경우, &lt;code&gt;supertest&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;그런데, HttpYac (&lt;code&gt;.http&lt;/code&gt; 를 의미) 은 이 중간에 위치한다고 생각한다.&lt;/p&gt;
&lt;p&gt;POSTMAN 의 경우, CI/CD 과정의 복잡성이 늘어나고, 프로그램을 더 켜야 한다는 문제점이 있다.&lt;/p&gt;
&lt;p&gt;E2E 테스팅의 경우, 요청 검증을 위해 매번 모듈을 &amp;quot;테스트가 직접 생성&amp;quot; 하여 느리다는 문제가 있다.&lt;/p&gt;
&lt;p&gt;하지만 &lt;code&gt;.http&lt;/code&gt; 파일을 사용한다면, 현재 실행중인 Dev 서버에 곧바로 요청을 날릴 수 있다.&lt;/p&gt;
&lt;p&gt;그러나, HttpYac 프로그램 자체의 한계성도 느낄 수 있었다.&lt;/p&gt;
&lt;p&gt;IntelliJ 나 VSCode 와는 달리, CLI 로서 세션을 유지하기 위해,&lt;/p&gt;
&lt;p&gt;파일 시스템을 다루며 직접 정보를 작성해야 하고,&lt;/p&gt;
&lt;p&gt;Request 혹은 Response 객체에 직접 세션 정보들을 넣어주어야 한다는 것이다.&lt;/p&gt;
&lt;p&gt;IntelliJ 에서는 요청과 응답 내용에 대한 내용을&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.idea&lt;/code&gt; 폴더에 직접 저장하여 사용한다.&lt;/p&gt;
&lt;p&gt;이 때문에 딱히 Hook 을 작성하지 않고도 세션을 유지하는 것 처럼 요청을 보낼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;만약에 E2E 테스팅 도구들이 적고 기능이 별반 없었으면&lt;/p&gt;
&lt;p&gt;CI/CD 과정에서 굉장히 유용하게 사용했을 것 같은데,&lt;/p&gt;
&lt;p&gt;Node.js 환경에서는 Jest 와 Supertest 라는 훌륭한 E2E 테스팅 라이브러리가 있어&lt;/p&gt;
&lt;p&gt;로컬 머신에서 간단히 테스팅 하는 용도로 사용 할 것 같다.&lt;/p&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;h2&gt;참고 사이트&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;HttpYac 공식 사이트&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://httpyac.github.io/&quot;&gt;https://httpyac.github.io/&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;IntelliJ IDEA 의 요청 세션 저장 방식을 참고함&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;&amp;lt;IntelliJ 프로젝트 루트&amp;gt;
  /.idea
    /httpRequests
      &amp;lt;연-월-일-시간&amp;gt;.&amp;lt;응답코드&amp;gt;.json
      ...
      http-client.cookies # 여기에 쿠키를 저장 (텍스트 형식으로)
      ...&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Node.js/잡다 지식</category>
      <category>.http</category>
      <category>.http 파일</category>
      <category>Authorization</category>
      <category>http</category>
      <category>http 파일</category>
      <category>httpyac</category>
      <category>httpyac cli</category>
      <category>httpyac 사용법</category>
      <category>세션</category>
      <category>쿠키</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/208</guid>
      <comments>https://codecreature.tistory.com/208#entry208comment</comments>
      <pubDate>Wed, 7 May 2025 18:55:04 +0900</pubDate>
    </item>
    <item>
      <title>Jest 와 유닛 테스트</title>
      <link>https://codecreature.tistory.com/207</link>
      <description>&lt;h2&gt;제목 : Jest 와 유닛 테스트&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;프로그래머가 되어가는 과정은 항상 험난하다.&lt;/p&gt;
&lt;p&gt;단순히 코드의 기능을 구현하고, 품질을 유지하는데에 이어,&lt;/p&gt;
&lt;p&gt;직접 구현한 코드가 대부분의 상황에서 동작하는지 검증해야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;특히, 특정 비즈니스 로직을 작성하는 데 있어 요청된 결과물의 일관성은 매우 중요한 일이다.&lt;/p&gt;
&lt;p&gt;프로그래머스 부트캠프에서 백엔드로서 팀 프로젝트를 진행하며 가장 크게 느낀 것 중 하나는,&lt;/p&gt;
&lt;p&gt;코드의 재사용화가 진행됨에 따라 코드는 점점 복잡해지는데, 내가 방금 생성한 비즈니스 로직 함수 하나가&lt;/p&gt;
&lt;p&gt;정말로 의도대로 동작하고 있는지 모를 수도 있다는 것이었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;따라서, 웹 서버 (프론트엔드) 팀의 Next.js 도입 과정에서&lt;/p&gt;
&lt;p&gt;EndPoint 의 데이터가 의도대로 들어오지 않았는데, 이 때 어떤 서버에서 에러가 났는지 모르므로&lt;/p&gt;
&lt;p&gt;협력하는 양 팀이 전부 각자의 코드를 다시 훑어보게 되는 비효율적인 일이 일어났다.&lt;/p&gt;
&lt;p&gt;결과적으로 보자면, 쿠키를 전송하고 받는 과정에서 React 와 Next 의 Receiver 환경이 달랐고,&lt;/p&gt;
&lt;p&gt;백엔드 서버는 쿠키 전송 가능 영역을 전부 열어야 했다.&lt;/p&gt;
&lt;p&gt;결과적으로 이 에러를 해결하기 위해 몇 일 정도가 소요되었는데,&lt;/p&gt;
&lt;p&gt;이 과정에서 로직의 무결성을 입증하는 테스트가 존재했다면, 몇 시간 정도로 단축되었을 거라는 생각이 들었다.&lt;/p&gt;
&lt;p&gt;물론, 위의 상황만을 가정한다면, 모든 비즈니스 로직을 거치는 E2E-Test (End To End Test) 가 적절 할 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;내 프로젝트는 유저에게 API 접근권을 의미하는 쿠키를 Middleware 를 통해서 생산하고 전송했다.&lt;/p&gt;
&lt;p&gt;이 과정에서 JWT 를 사용하여 유저의 간단한 정보를 넣었다.&lt;/p&gt;
&lt;p&gt;문제는 쿠키를 관리하는 함수를 재사용하는 것 까지는 좋았는데,&lt;/p&gt;
&lt;p&gt;쿠키로 인증이 필요한 장소에서 적절히 함수가 사용되었는지 제대로 확인해 보질 못했다는 것이다.&lt;/p&gt;
&lt;p&gt;특히 POSTMAN 기본 상태는 cookie 가 만료되어도 브라우저처럼 삭제하지 않기 때문에,&lt;/p&gt;
&lt;p&gt;브라우저 상황 뿐만 아니라, 테스팅 환경인 POSTMAN 도 인식하여 로직을 작성했다.&lt;/p&gt;
&lt;p&gt;아마 이 과정에서 유닛 테스트를 훌륭히 작성했다면,&lt;/p&gt;
&lt;p&gt;굳이 &lt;code&gt;console.log(...)&lt;/code&gt; 작성할 필요 없이, 로직의 일관성을 확인했을 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Unit Test (단위 테스트) 란 무엇인가?&lt;/h2&gt;
&lt;p&gt;단위 테스트란, 코드의 가장 작은 기능적 단위를 테스트하는 프로세스이다.&lt;/p&gt;
&lt;p&gt;소프트웨어를 작은 Unit 단위로 작성 한 다음, 코드 단위별 테스트를 작성하는 것이 소프트웨어 개발 모범 사례이다.&lt;/p&gt;
&lt;p&gt;단위 테스트 작성 후, 소프트웨어 코드를 변경 할 때 마다, 테스트 코드를 자동으로 실행한다.&lt;/p&gt;
&lt;p&gt;테스트 실패의 경우, 버그나 오류가 있는 코드 영역을 빠르게 분리하여 고칠 수 있다.&lt;/p&gt;
&lt;p&gt;위의 글은 AWS 에서 가져왔는데, 좋은 시야를 가지고 작성된 글이라고 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Unit Test 전략&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. 논리 검사&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;시스템이 올바른 계산을 수행하여 예상되는 입력을 출력하는가?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 경계 검사&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;주어진 입력에 대해 시스템이 예상되는 범위의 값을 출력하는가?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 오류 처리&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;주어진 입력에 대해 오류가 있을 때, 시스템은 의도한 에러가 출력되는가?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;객체 지향 검사&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;코드를 실행했을 때, 객체의 상태가 변경되면 객체가 올바르게 업데이트되는가?&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;개인적인 생각&lt;/h2&gt;
&lt;p&gt;유닛 테스트를 배우게 되면 무조건 나오게 되는 개발 전략은 바로, &lt;strong&gt;TDD&lt;/strong&gt; 이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TDD&lt;/strong&gt; 는, Test Driven Development 의 약자로, 테스트 먼저 작성, 이후 실제 코드 작성이다.&lt;/p&gt;
&lt;p&gt;나의 개인적인 생각은 이렇다.&lt;/p&gt;
&lt;p&gt;&amp;quot;모든 코드의 Unit Test&amp;quot; 가 아니라, 중요 혹은 복잡 코드의 Unit Test 가 필요하다 이다.&lt;/p&gt;
&lt;p&gt;물론, 프로그램 작성에 대해 시간이 넉넉하다면, 모든 함수에 대한 유닛 테스트를 해도 상관 없다고 생각한다.&lt;/p&gt;
&lt;p&gt;그러나, 시간이 촉박한 상황에서, 과연 모든 테스트를 작성할 수 있는가에 대해서 생각이 든다.&lt;/p&gt;
&lt;p&gt;자신에게 주어진 &amp;quot;시간&amp;quot; 이라는 자원에 따라, 유닛 테스트의 범위와 양은 달라진다고 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;Jest 란 무엇인가?&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Jest&lt;/code&gt; 는 JavaScript, 혹은 TypeScript 환경에서 사용하는 Unit 혹은 E2E 테스팅 도구라고 생각하면 된다.&lt;/p&gt;
&lt;p&gt;React, Express, NestJS 와 같은 프로그램에서 자주 사용하는 테스팅 도구인데,&lt;/p&gt;
&lt;p&gt;JavaScript 로 실행되는 어떠한 형태의 프로그램이던 모두 사용할 수 있는 라이브러리이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;우선, jest 테스팅을 위한 새로운 폴더를 만들고,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ npm install --save-dev jest&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 명령을 해당 폴더에서 실행한다.&lt;/p&gt;
&lt;p&gt;이후, 생성된 &lt;code&gt;package.json&lt;/code&gt; 에, 밑의 옵션을 넣는다 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;scripts&amp;quot; : {
    &amp;quot;test&amp;quot;: &amp;quot;jest&amp;quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;이후, &lt;code&gt;jest&lt;/code&gt; 가 잘 동작하는지 확인하기 위해, 제공되는 2 개의 파일을 먼저 생성 해 보자 :&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sum.js&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function sum (a, b) {
  return a + b;
}

module.exports = sum;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;sum.test.js&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const sum = require(&amp;quot;./sum&amp;quot;);

test(&amp;quot;1 + 2 는 3이다!&amp;quot;, () =&amp;gt; {
  expect(sum(1, 2)).toBe(3);
})&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그리고 나서, &lt;code&gt;npm run test&lt;/code&gt; 혹은 &lt;code&gt;npm test&lt;/code&gt; 를 실행시켜 보자 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  jest npm run test

&amp;gt; test
&amp;gt; jest

 PASS  ./sum.test.js
  ✓ 1 + 2 은 3 과 동일하다! (2 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.194 s, estimated 1 s
Ran all test suites.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;동작을 실행한다면, &lt;code&gt;npm&lt;/code&gt; 은 &lt;code&gt;package.json&lt;/code&gt; 에 적혀 있는 &lt;code&gt;test&lt;/code&gt; 라는 스크립트를 읽어 실행한다.&lt;/p&gt;
&lt;p&gt;내가 만들어 둔 1 개의 &lt;code&gt;test&lt;/code&gt; 메서드에 반응하여, 1 개의 테스트가 통과함을 잘 보여주고 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그런데, 나는 궁금증이 들기 시작했다.&lt;/p&gt;
&lt;p&gt;node 에 탑재된 기본 라이브러리가 아니라면, &lt;code&gt;jest&lt;/code&gt; 전용인 &lt;code&gt;expect&lt;/code&gt;, &lt;code&gt;toBe&lt;/code&gt; 메서드는 명시적으로 가져와야 한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;test&lt;/code&gt; 는 이미 &lt;code&gt;node&lt;/code&gt; 에 탑재되어 있으므로, 나머지 두 개의 메서드의 행방을 찾기 시작했다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;node_modules&lt;/code&gt; 를 뒤진 결과,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;jest&lt;/code&gt; --&amp;gt; &lt;code&gt;@jest&lt;/code&gt; 경로로 유틸 메서드가 존재했다.&lt;/p&gt;
&lt;p&gt;또 다른 궁금증도 들었는데, &lt;code&gt;node_modules&lt;/code&gt; 에 존재하는 &lt;code&gt;expect&lt;/code&gt;, &lt;code&gt;toBe&lt;/code&gt; 메서드는 왜 가져오지 않는가? 였다.&lt;/p&gt;
&lt;p&gt;이에 대해 결국 GPT 에게 물어보았는데,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;npm run test&lt;/code&gt; 는 &lt;code&gt;jest&lt;/code&gt; 의 cli 를 실행하며, 테스팅 환경에 필요한 global 변수 및 함수들을&lt;/p&gt;
&lt;p&gt;주입해 준다는 것이었다.&lt;/p&gt;
&lt;p&gt;따라서, 따로 &lt;code&gt;expect&lt;/code&gt;, &lt;code&gt;toBe&lt;/code&gt; 메서드를 라이브러리에서 명시적으로 가져오지 않아도,&lt;/p&gt;
&lt;p&gt;실행 시 코드는 전역 함수 혹은 변수를 참조하므로 코드가 정상적으로 실행된다는 것이었다.&lt;/p&gt;
&lt;p&gt;또한, 기존의 &lt;code&gt;node:test&lt;/code&gt; 를 사용하는 것이 아니라, &lt;code&gt;jest&lt;/code&gt; 라이브러리의 &lt;code&gt;test&lt;/code&gt; 를 사용한다는 것이었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;결과적으로, 테스팅 라이브러리를 사용하면서 명시적으로 메서드를 가져오지 않는 이유는,&lt;/p&gt;
&lt;p&gt;실행하면서 자동으로 글로벌 메서드로 &lt;code&gt;test&lt;/code&gt;, &lt;code&gt;expect&lt;/code&gt;, &lt;code&gt;toBe&lt;/code&gt; 를 주입하기 때문이었다.&lt;/p&gt;
&lt;p&gt;명시적으로 가져오고 싶다면, 아래와 같이 코드를 작성하면 된다 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const sum = require(&amp;quot;./sum&amp;quot;);
const {test, expect} = require(&amp;quot;@jest/globals&amp;quot;)

test(&amp;quot;1 + 2 은 3 과 동일하다!&amp;quot;, () =&amp;gt; {
  expect(sum(1, 2)).toBe(3);
})&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h2&gt;Jest 와 TypeScript&lt;/h2&gt;
&lt;p&gt;우선, TypeScript 의 &lt;strong&gt;2 가지 실행 방법&lt;/strong&gt; 에 대해서 알고 있어야 한다.&lt;/p&gt;
&lt;p&gt;TypeScript 는 실행 타겟 언어가 아니다.&lt;/p&gt;
&lt;p&gt;TypeScript 는 결국 JavaScript 로 번역되어, 다시 실행 될 뿐이다.&lt;/p&gt;
&lt;p&gt;그렇다면, TypeScript 파일은 어떻게 실행해야 하는가?&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;1. JS 로 컴파일 후, 실행&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;결국 타입스크립트는 자바스크립트로 컴파일 되므로,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;tsc sum.ts&lt;/code&gt;, &lt;code&gt;tsc sum.test.ts&lt;/code&gt; 를 각각 입력하여 JavaScript 파일로 내보내거나,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;tsconfig.json&lt;/code&gt; 을 생성하여 각자 원하는 옵션을 기입 한 후, &lt;code&gt;tsc&lt;/code&gt; 명령어 하나로&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sum.ts&lt;/code&gt;, &lt;code&gt;sum.test.ts&lt;/code&gt; 를 동시에 컴파일 할 수 있다.&lt;/p&gt;
&lt;p&gt;이후,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ npm run test&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;를 통해 &lt;code&gt;.js&lt;/code&gt; 로 변경된 테스트를 진행 할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;그런데, 여기엔 치명적인 오류가 있다. &lt;br/&gt;&lt;br&gt;&lt;code&gt;jest&lt;/code&gt; 의 기본 설정은 &lt;code&gt;.ts&lt;/code&gt; 파일을 실행할 수 없다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;br/&gt;

&lt;p&gt;일단, 아주 기본적인 작업으로 JS 변환 후, &lt;code&gt;jest&lt;/code&gt; 를 실행 해 보자 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  ts npm run test

&amp;gt; test
&amp;gt; jest

 PASS  js/sum.test.js
 PASS  ts/sum.test.js
 FAIL  ts/sum.test.ts # 따로 &amp;quot;ts&amp;quot; 폴더로 구분하였는데, `.ts` 테스팅까지 들어가 버리는 현상.
  ● Test suite failed to run

    Jest encountered an unexpected token

    Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.

    .....


      at Runtime.createScriptFromCode (node_modules/jest-runtime/build/index.js:1505:14)

# js/sum.test.js 통과, ts.sum.test.js 통과, ts.sum.test.ts (&amp;quot;실패&amp;quot;)
Test Suites: 1 failed, 2 passed, 3 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        0.352 s, estimated 1 s
Ran all test suites.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;즉, &lt;code&gt;jest&lt;/code&gt; 는 컴파일 된 JS 코드를 타겟으로 하므로, &lt;code&gt;.ts&lt;/code&gt; 확장자는 적용될 수 없다.&lt;/p&gt;
&lt;p&gt;바로 밑에 &lt;code&gt;ts-jest&lt;/code&gt; 를 통해 &lt;code&gt;.ts&lt;/code&gt; 파일 또한 실행이 가능하지만,&lt;/p&gt;
&lt;p&gt;자바스크립트와 타입스크립트 테스팅을 구분하고 싶은 사람을 위해 밑에 작성하겠다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;2. ts-node 와 같은 환경처럼, ts-jest 자체를 실행&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;위에서 말했듯, TS 는 결국 JS 로 실행된다.&lt;/p&gt;
&lt;p&gt;하지만, &lt;code&gt;ts-node&lt;/code&gt; 환경을 이용한다면, &lt;code&gt;.ts&lt;/code&gt; 확장자를 굳이 &lt;code&gt;.js&lt;/code&gt; 로 컴파일 하지 않고도,&lt;/p&gt;
&lt;p&gt;타입스크립트 특유의 환경 변수를 그대로 가져와 사용할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;여기서 일어나는 문제는, &lt;code&gt;.js&lt;/code&gt; 확장자는 적용이 되지 않는다는 것이다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;code&gt;jest&lt;/code&gt; 를 타입스크립트 환경에서도 동일하게 사용하고 싶다면, &lt;code&gt;ts-jest&lt;/code&gt; 를 install 해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ts-jest&lt;/code&gt; 를 사용한다면, &lt;code&gt;.js&lt;/code&gt; 와 &lt;code&gt;.ts&lt;/code&gt; 확장자도 실행 할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ts-jest&lt;/code&gt; 는 &lt;code&gt;.js&lt;/code&gt; 를 만난다면 자바스크립트 그대로 실행하며,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.ts&lt;/code&gt; 를 만난다면, 굳이 자바스크립트 출력물 없이 그대로 실행이 가능하다. (하이브리드)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ npm i -D ts-jest # 혹은 npm i --save-dev ts-jest&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;package.json&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;scripts&amp;quot;: {
    &amp;quot;test&amp;quot;: &amp;quot;jest&amp;quot;
  },
  &amp;quot;devDependencies&amp;quot;: {
    &amp;quot;@types/jest&amp;quot;: &amp;quot;^29.5.14&amp;quot;,
    &amp;quot;jest&amp;quot;: &amp;quot;^29.7.0&amp;quot;,
    &amp;quot;ts-jest&amp;quot;: &amp;quot;^29.3.2&amp;quot;
  },
  &amp;quot;jest&amp;quot;: {
    &amp;quot;preset&amp;quot;: &amp;quot;ts-jest&amp;quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;&amp;quot;scripts&amp;quot;&lt;/code&gt; 속성을 통해, 우리는 &lt;code&gt;jest&lt;/code&gt; 를 로컬로 직접 설치하지 않고도 테스팅을 수행 할 수 있다.&lt;/p&gt;
&lt;p&gt;그리고, &lt;code&gt;package.json&lt;/code&gt; 파일의 하단에 &lt;code&gt;&amp;quot;jest&amp;quot;&lt;/code&gt; 를 적어놓았는데,&lt;/p&gt;
&lt;p&gt;이는 CLI 로서 사용될 &lt;code&gt;jest&lt;/code&gt; 라이브러리의 디폴트 설정을 바꿀 수 있게 만들어주는 것이다.&lt;/p&gt;
&lt;p&gt;정말 수 많은 옵션들이&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://jestjs.io/docs/configuration&gt;https://jestjs.io/docs/configuration&quot;&gt;https://jestjs.io/docs/configuration&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;이곳에 존재한다.&lt;/p&gt;
&lt;p&gt;그 중에서, 우리는 &lt;code&gt;preset&lt;/code&gt; 이란 속성을 통해,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;jest&lt;/code&gt; 의 실행 환경을 TS 와 JS 모두 실행 가능하도록 변경 한 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그렇다면, 한번 실행 해 보자 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜ npm run test

&amp;gt; test
&amp;gt; jest

ts-jest[config] (WARN) message TS151001: If you have issues related to imports, you should consider setting `esModuleInterop` to `true` in your TypeScript configuration file (usually `tsconfig.json`). See https://blogs.msdn.microsoft.com/typescript/2018/01/31/announcing-typescript-2-7/#easier-ecmascript-module-interoperability for more information.
 PASS  ts/sum.test.ts
 PASS  js/sum.test.js
 PASS  ts/sum.test.js

Test Suites: 3 passed, 3 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        2.239 s
Ran all test suites.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ts-jest&lt;/code&gt; 에서 실행 한 테스팅 메서드는 총 3 개이다.&lt;/p&gt;
&lt;p&gt;그런데, 2개는 같은 테스트를 의미한다. (ts --&amp;gt; js 컴파일)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ts-jest&lt;/code&gt; 는 JS, TS 동시 실행 환경을 지원하지만, 각 확장자 별 분리도 필요하다.&lt;/p&gt;
&lt;p&gt;예를 들면, 유닛 테스팅과 e2e-테스팅 간의 실행 환경 차이가 있을 수도 있기 때문이다.&lt;/p&gt;
&lt;p&gt;(EX - &lt;code&gt;ts-node&lt;/code&gt; 환경 or &lt;code&gt;node&lt;/code&gt; 환경)&lt;/p&gt;
&lt;p&gt;이럴 때, 우리는 &lt;code&gt;package.json&lt;/code&gt; 의 &lt;code&gt;&amp;quot;jest&amp;quot;&lt;/code&gt; 환경을 override 한 것 처럼,&lt;/p&gt;
&lt;p&gt;각 실행 환경을 분리 할 수도 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;어떻게 실행 환경을 분리할 것인지는 각자의 판단이 있지만,&lt;/p&gt;
&lt;p&gt;나는 &lt;code&gt;package.json&lt;/code&gt; 을 통해 명령어 스크립트로 나누었다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;package.json&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;scripts&amp;quot;: {
    &amp;quot;test:ts&amp;quot;: &amp;quot;jest --testRegex=&amp;#39;\\.test.ts&amp;#39;&amp;quot;,
    &amp;quot;test:js&amp;quot;: &amp;quot;jest --testRegex=&amp;#39;\\.test.js&amp;#39;&amp;quot;
  },
  &amp;quot;devDependencies&amp;quot;: {
    &amp;quot;@types/jest&amp;quot;: &amp;quot;^29.5.14&amp;quot;,
    &amp;quot;jest&amp;quot;: &amp;quot;^29.7.0&amp;quot;,
    &amp;quot;ts-jest&amp;quot;: &amp;quot;^29.3.2&amp;quot;
  },
  &amp;quot;jest&amp;quot;: {
    &amp;quot;preset&amp;quot;: &amp;quot;ts-jest&amp;quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위에 보면 2 개의 스크립트로 나뉜 것을 알 수 있다.&lt;/p&gt;
&lt;p&gt;사용법은 &lt;code&gt;npm run test:ts&lt;/code&gt; or &lt;code&gt;npm run test:js&lt;/code&gt; 이다.&lt;/p&gt;
&lt;p&gt;여기서, 내가 테스팅을 시작 할 정확한 파일 extension 을 정해 놓은 것이다.&lt;/p&gt;
&lt;p&gt;2 개의 script 는 모두 &lt;code&gt;jest&lt;/code&gt; 와 &lt;code&gt;preset=ts-jest&lt;/code&gt; 이지만,&lt;/p&gt;
&lt;p&gt;각자 가진 &lt;code&gt;testRegex&lt;/code&gt; 속성은 다른 것이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;참고로, &lt;code&gt;.test&lt;/code&gt; 를 넣어주지 않으면, &lt;code&gt;.ts&lt;/code&gt; or &lt;code&gt;.js&lt;/code&gt; 파일 또한 실행되기 때문에, &lt;br/&gt;&lt;br&gt;정확한 테스팅 파일 확장자를 넣어주어야 한다. &lt;br/&gt;&lt;br&gt;jest 가 자동으로 찾아주기를 원한다면, &lt;code&gt;testPathPattern&lt;/code&gt; 옵션을 각자 넣어주면 된다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h3&gt;npm run test:js 결과&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜ npm run test:js

&amp;gt; test:js
&amp;gt; jest --testRegex=&amp;#39;\.test.js&amp;#39;

 PASS  ts/sum.test.js
 PASS  js/sum.test.js

Test Suites: 2 passed, 2 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        0.361 s, estimated 1 s
Ran all test suites.&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;npm run test:ts 결과&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  ts npm run test:ts

&amp;gt; test:ts
&amp;gt; jest --testRegex=&amp;#39;\.test.ts&amp;#39;

ts-jest[config] (WARN) message TS151001: If you have issues related to imports, you should consider setting `esModuleInterop` to `true` in your TypeScript configuration file (usually `tsconfig.json`). See https://blogs.msdn.microsoft.com/typescript/2018/01/31/announcing-typescript-2-7/#easier-ecmascript-module-interoperability for more information.
 PASS  ts/sum.test.ts
  ✓ TS 버전 1 + 2 = 3 (3 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.57 s, estimated 2 s
Ran all test suites.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;결과를 보면, 일단 타입스크립트 테스팅이 통과하긴 했지만, 여전히 무언가 경고가 뜨고 있다.&lt;/p&gt;
&lt;p&gt;이는, 내가 &lt;code&gt;import&lt;/code&gt; 구문을 어떻게 처리 할 지에 대해서 &lt;code&gt;tsconfig.json&lt;/code&gt; 에 지정하지 않았기 때문이다.&lt;/p&gt;
&lt;p&gt;즉, 내 &lt;code&gt;tsconfig.json&lt;/code&gt; 은 텅 비어있다.&lt;/p&gt;
&lt;p&gt;따라서, 설정 파일을 이렇게 바꿔준다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;tsconfig.json&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;compilerOptions&amp;quot;: {
    &amp;quot;module&amp;quot;: &amp;quot;CommonJS&amp;quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;정말 간단하게 1 개의 옵션만 적어놓았다.&lt;/p&gt;
&lt;p&gt;나는 &lt;code&gt;tsconfig.json&lt;/code&gt; 의 옵션에 대해서 상세하게 적어놓은 글이 있다.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/196&quot;&gt;https://codecreature.tistory.com/196&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;여기서 상단 부분에 적어놓은 &amp;quot;module&amp;quot; 을 읽으면, 왜 &lt;code&gt;ESM&lt;/code&gt; 과 &lt;code&gt;commonjs&lt;/code&gt; 간의&lt;/p&gt;
&lt;p&gt;영역 정리가 필요한지 알게 될 것이다.&lt;/p&gt;
&lt;p&gt;그리고, 바뀐 &lt;code&gt;tsconfig.json&lt;/code&gt; 과 함께, &lt;code&gt;npm run test:ts&lt;/code&gt; 를 실행해 보자 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  ts npm run test:ts

&amp;gt; test:ts
&amp;gt; jest --testRegex=&amp;#39;\.test.ts&amp;#39;

 PASS  ts/sum.test.ts
  ✓ TS 버전 1 + 2 = 3 (5 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.488 s, estimated 2 s
Ran all test suites.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이제 어떠한 경고도 뜨지 않고, 정상적으로 실행되는 것을 볼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;h2&gt;Jest 로 본격적인 테스팅하기 (Matcher 사용하기)&lt;/h2&gt;
&lt;p&gt;주로 프레임워크에서 Jest 의 형태를 미리 갖춰놓는 경우가 있는데,&lt;/p&gt;
&lt;p&gt;이 때, &lt;code&gt;describe&lt;/code&gt;, &lt;code&gt;it&lt;/code&gt; 이라는 메서드가 존재한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;describe&lt;/code&gt; 는 일정 테스트 메서드들을 Grouping(그룹화) 하는 역할을 한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;it&lt;/code&gt; 은 &lt;code&gt;test&lt;/code&gt; 의 별칭으로 사용된다. 둘의 행동은 동일하다.&lt;/p&gt;
&lt;p&gt;주로, &lt;code&gt;describe&lt;/code&gt; 로 테스팅 하고자 하는 도메인 영역을 설명하고,&lt;/p&gt;
&lt;p&gt;각각의 테스팅 유닛들을 내부에 &lt;code&gt;it&lt;/code&gt; 혹은 &lt;code&gt;test&lt;/code&gt; 로 작성한다.&lt;/p&gt;
&lt;p&gt;먼저, 가장 작은 유닛인 &lt;code&gt;test&lt;/code&gt; 부터 살펴보자.&lt;/p&gt;
&lt;p&gt;아 그리고, TypeScript 를 이용하여 테스팅하겠다.&lt;/p&gt;
&lt;p&gt;그리고, 내가 원하는 파일만 따로 &lt;code&gt;jest&lt;/code&gt; or &lt;code&gt;ts-jest&lt;/code&gt; 테스팅을 하기 위해,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;package.json&lt;/code&gt; 의 &lt;code&gt;scripts&lt;/code&gt; 에 따로 명령어를 넣어주자 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;&amp;quot;scripts&amp;quot;: {
  &amp;quot;test&amp;quot; : &amp;quot;jest&amp;quot;, // (기입함) 기존의 일반 명령어
  &amp;quot;test:ts&amp;quot;: &amp;quot;jest --testRegex=&amp;#39;\\.test.ts&amp;#39;&amp;quot;,
  &amp;quot;test:js&amp;quot;: &amp;quot;jest --testRegex=&amp;#39;\\.test.js&amp;#39;&amp;quot;
},
...&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이런 식으로, 모든 &lt;code&gt;js&lt;/code&gt; 실행을 원한다면 &lt;code&gt;npm run test:js&lt;/code&gt; 를,&lt;/p&gt;
&lt;p&gt;모든 &lt;code&gt;ts&lt;/code&gt; 실행을 원한다면, &lt;code&gt;npm run test:ts&lt;/code&gt; 를 실행하면 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Matcher 1 - &lt;code&gt;&lt;code&gt;toBe&lt;/code&gt;&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;우리는 &lt;code&gt;sum&lt;/code&gt; 이라는 파일을 따로 제작해서 &lt;code&gt;sum&lt;/code&gt; 메서드를 송출시켰지만,&lt;/p&gt;
&lt;p&gt;결국 이러한 코드로 만들어진다. :&lt;/p&gt;
&lt;p&gt;&lt;code&gt;to-be.test.ts&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;test(&amp;quot;2 더하기 2 는 4 입니다.&amp;quot;, () =&amp;gt; {
  // &amp;quot;toBe&amp;quot; 라는 Matcher 를 사용했다!
  expect(2 + 2).toBe(4);
})&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜ npm run test -- to-be.test
# 이는 &amp;quot;jest to-be.test&amp;quot; 명령어와 동일하다.

&amp;gt; test
&amp;gt; jest to-be.test

 PASS  ts/to-be.test.ts
  ✓ 2 더하기 2 는 4 입니다. (3 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.988 s, estimated 2 s
Ran all test suites matching /to-be.test/i.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;방금 위에서 &lt;code&gt;npm run test&lt;/code&gt; 라는 지정 스크립트 외, &lt;code&gt;--&lt;/code&gt; 라는 키워드를 추가했다.&lt;/p&gt;
&lt;p&gt;이는, &lt;code&gt;npm run test&lt;/code&gt; 를 입력하면 실행될 &amp;quot;실제&amp;quot; 프로그램에 원하는 옵션 혹은 명령을 보내는 역할을 한다.&lt;/p&gt;
&lt;p&gt;따라서, 내가 만들어 둔 폴더 산하에 여러 테스팅 파일이 존재하는 가운데,&lt;/p&gt;
&lt;p&gt;이 중 &lt;code&gt;to-be.test&lt;/code&gt; 라는 키워드가 들어가는 파일만 실행하겠다는 것이다.&lt;/p&gt;
&lt;p&gt;이를 통해, 굳이 모든 테스팅 결과를 보지 않고도, 하나 혹은 그 이상의 파일들만 골라서 실행 할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;일단, &lt;code&gt;expect()&lt;/code&gt; 메서드는 &lt;code&gt;expectation&lt;/code&gt; 이라는 객체를 반환한다.&lt;/p&gt;
&lt;p&gt;이 객체는 &lt;code&gt;matcher&lt;/code&gt; 에 반응하여 이 결과가 옳은지, 틀린지를 자동으로 판별 해 준다.&lt;/p&gt;
&lt;p&gt;만약에 &lt;code&gt;toBe(5)&lt;/code&gt; 를 입력했다면,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜ npm run test -- to-be.test

&amp;gt; test
&amp;gt; jest to-be.test

 FAIL  ts/to-be.test.ts
  ✕ 2 더하기 2 는 4 입니다. (5 ms)

  ● 2 더하기 2 는 4 입니다.

    expect(received).toBe(expected) // Object.is equality

    Expected: 5
    Received: 4

      1 | test(&amp;quot;2 더하기 2 는 4 입니다.&amp;quot;, () =&amp;gt; {
    &amp;gt; 2 |   expect(2 + 2).toBe(5);
        |                 ^
      3 | })
      4 |

      at Object.&amp;lt;anonymous&amp;gt; (ts/to-be.test.ts:2:17)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        2.233 s
Ran all test suites matching /to-be.test/i.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 결과를 보다시피, &lt;code&gt;jest&lt;/code&gt; 라이브러리의 훌륭한 출력과 더불어,&lt;/p&gt;
&lt;p&gt;왜 실패했는지를 자세히 보여주는 것을 확인 할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Matcher 2 - &lt;code&gt;toEqual&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;toBe&lt;/code&gt; 메서드는 값의 동일성을 취급한다.&lt;/p&gt;
&lt;p&gt;그런데, 클래스 타입 (객체) 의 값 동일성은 어떻게 취급할 것인가?&lt;/p&gt;
&lt;p&gt;일반적인 원시 값과 다르게, 객체의 값 동일성은 쉽게 판단할 수 있는 것이 아니다.&lt;/p&gt;
&lt;p&gt;객체는 자신만의 고유 주소값을 가지므로, 내부 필드의 값이 모두 동일하더라도,&lt;/p&gt;
&lt;p&gt;서로 다른 객체라면, 값의 동일성을 따질 수 없다.&lt;/p&gt;
&lt;p&gt;따라서, 값이 동일한지 내부 필드들을 모두 &amp;quot;재귀적으로&amp;quot; 확인하는 &lt;code&gt;toEqual&lt;/code&gt; 메서드가 존재한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;to-equal.test.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;test(&amp;quot;객체의 동일성은 판단하는 매처가 존재한다?&amp;quot;, () =&amp;gt; {
  const obj = {
    test1: [&amp;quot;배열로 넣어보는 테스팅 값!&amp;quot;],
    test2 : {
      first : 1,
      second : &amp;quot;two&amp;quot;,
    }
  }

  expect(obj).toEqual({
    test1: [&amp;quot;배열로 넣어보는 테스팅 값!&amp;quot;],
    test2 : {
      first : 1,
      second : &amp;quot;two&amp;quot;,
    }
  })
})&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜ npm run test -- to-equal

&amp;gt; test
&amp;gt; jest to-equal

 PASS  ts/to-equal.test.ts
  ✓ 객체의 동일성은 판단하는 매처가 존재한다? (3 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        2.156 s
Ran all test suites matching /to-equal/i.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;보다시피, 둘의 객체 주소값은 다르지만, 내부의 값을 재귀적으로 파악하여&lt;/p&gt;
&lt;p&gt;동일한 필드와 값을 가지고 있다는 것을 알 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Matcher 3 - &lt;code&gt;not&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;toBe&lt;/code&gt; 와, &lt;code&gt;toEqual&lt;/code&gt; 까지는 &amp;quot;일치하는 값&amp;quot; 을 의미하지만,&lt;/p&gt;
&lt;p&gt;어떠한 테스트에서는, &amp;quot;일치해서는 안되는 값&amp;quot; 을 사용해야 할 수도 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;not.test.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// 2 + 3 은, 4 가 &amp;quot;되어서는 안된다&amp;quot;
test(&amp;quot;not 을 이용한 테스팅!&amp;quot;, () =&amp;gt; {
  expect(2 + 3).not.toBe(4);
})&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜ npm run test -- not

&amp;gt; test
&amp;gt; jest not

 PASS  ts/not.test.ts
  ✓ not 을 이용한 테스팅! (2 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        2.148 s
Ran all test suites matching /not/i.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이러한 &lt;code&gt;not&lt;/code&gt; 은, 당연히 &lt;code&gt;.not.toEqual(...)&lt;/code&gt; 로도 사용할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Matcher 4 - 여러가지 타입 판별 매처들&lt;/h3&gt;
&lt;p&gt;물론, 위에서 다룬 내용들은 굉장히 자주 사용되지만,&lt;/p&gt;
&lt;p&gt;현재 &lt;code&gt;expect&lt;/code&gt; 에 들어간 값 혹은 객체가 어떠한 상태인지 &amp;quot;바로 판별 가능한&amp;quot; 매처들도 굉장히 유용하다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;toBeNull()&lt;/code&gt; - &lt;code&gt;null&lt;/code&gt; 값이다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;toBeUndefined()&lt;/code&gt; - &lt;code&gt;undefined&lt;/code&gt; 값이다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;toBeDefined()&lt;/code&gt; - &lt;code&gt;expect&lt;/code&gt; 내부의 내용이 &amp;quot;정의되어 있다&amp;quot; (값이 있다.)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;toBeTruthy()&lt;/code&gt; - &lt;code&gt;expect&lt;/code&gt; 내부 결과가 &lt;code&gt;if&lt;/code&gt; 정의문으로 판별했을 때, &lt;code&gt;true&lt;/code&gt; 의 값이다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;toBeFalsy()&lt;/code&gt; - &lt;code&gt;expect&lt;/code&gt; 내부 결과가 &lt;code&gt;if&lt;/code&gt; 정의문으로 판별했을 때, &lt;code&gt;false&lt;/code&gt; 의 값이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;expect&lt;/code&gt; 의 내부 결과 타입을 예상 할 수 있는 메서드들이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Matcher 5 - 숫자 관련 매처들&lt;/h3&gt;
&lt;p&gt;숫자, 타입, 일치 matcher 들이 있다보니, 사실상 &lt;code&gt;toBe&lt;/code&gt; 만 치면, 관련 메서드들이 전부 나오긴 한다.&lt;/p&gt;
&lt;p&gt;이렇게 다양한 메서드들이 보여지는 것이 &lt;code&gt;jest&lt;/code&gt; 가 쉽게 접근할 수 있는 상황이 아닌가 생각이 든다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;toBeGreaterThan(x)&lt;/code&gt; - &lt;code&gt;x&lt;/code&gt; 값 보다는 크다 (&lt;code&gt;expect &amp;gt; x&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;toBeGreaterThanOrEqual(x)&lt;/code&gt; - &lt;code&gt;x&lt;/code&gt; 값 보다는 크거나 같다 (&lt;code&gt;expect &amp;gt;= x&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;toBeLessThan(x)&lt;/code&gt; - &lt;code&gt;x&lt;/code&gt; 값 보다는 작다 (&lt;code&gt;expect &amp;lt; x&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;toBeLessThanOrEqual(x)&lt;/code&gt; - &lt;code&gt;x&lt;/code&gt; 값 보다는 작거나 같다 (&lt;code&gt;expect &amp;lt;= x&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Matcher 6 - 문자열 매처&lt;/h3&gt;
&lt;p&gt;나는 사실, regex 에 대해서는 잘 아는 편은 아니다!&lt;/p&gt;
&lt;p&gt;정규표현식을 사용하여 판별하는데, 공식문서는 이렇게 표현되어 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;test(&amp;quot;Team 문자열에 &amp;#39;I&amp;#39; 는 포함되어 있지 않다!&amp;quot;, () =&amp;gt; {
  expect(&amp;quot;Team&amp;quot;).not.toMatch(/I/);
});

test(&amp;quot;Iterable 문자열에는 &amp;#39;rable&amp;#39; 이 포함되어 있다!&amp;quot;, () =&amp;gt; {
  expect(&amp;quot;Iterable&amp;quot;).toMatch(/rable/);
})&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜ npm run test -- string

&amp;gt; test
&amp;gt; jest string

 PASS  ts/string.test.ts
  ✓ Team 문자열에 &amp;#39;I&amp;#39; 는 포함되어 있지 않다! (2 ms)
  ✓ Iterable 문자열에는 &amp;#39;rable&amp;#39; 이 포함되어 있다! (1 ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        2.081 s
Ran all test suites matching /string/i.&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;Matcher 7 - &lt;code&gt;toThrow&lt;/code&gt; 에러를 예상한다&lt;/h3&gt;
&lt;p&gt;이는 특히, Back 사이드의 어플리케이션이나, Front 사이드의 어플이나 동일하게&lt;/p&gt;
&lt;p&gt;정말 자주 사용되는 매처라고 생각한다.&lt;/p&gt;
&lt;p&gt;바로, 내가 일부로 잘못된 정보를 주었으니, 내가 설정한 예외가 반출되어야 한다는 것이다.&lt;/p&gt;
&lt;p&gt;반출된 예외의 클래스 타입, 혹은 내부에 담긴 내용도 &lt;code&gt;toThrow&lt;/code&gt; 를 통해 일치시킬 수 있다.&lt;/p&gt;
&lt;p&gt;이는 TypeScript, JavaScript 에서 &lt;code&gt;throw new Error(&amp;quot;....&amp;quot;)&lt;/code&gt; 를 통해 트리거를 걸 수 있으며,&lt;/p&gt;
&lt;p&gt;어플리케이션의 특성상 의존성 주입 과정의 에러와 비즈니스 로직의 무결성을 증명하는&lt;/p&gt;
&lt;p&gt;백엔드 프로그램에서 유용하게 사용할 수 있었다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;공식 문서를 참고 및 변형한 예제&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// isError 변수를 통해, 에러를 반환하거나, 일반 문자열을 반환 할 수 있게 만들었다.
function compileLogicCode(isError : boolean) : string {
  if(isError) {
    throw new Error(&amp;quot;return Error&amp;quot;);
  } else {
    return &amp;quot;Pass&amp;quot;
  }
}

test(&amp;quot;toThrow 매처를 통해 에러를 잡아내는 예제 작성!&amp;quot;, () =&amp;gt; {
  // 어떤 형식의 에러이던 잡아 낼 수 있다.
  expect(() =&amp;gt; compileLogicCode(true)).toThrow();
  expect(() =&amp;gt; compileLogicCode(false)).not.toThrow();

  // 특정 형태의 오류인지 특정 할 수 있다.
  expect(() =&amp;gt; compileLogicCode(true)).toThrow(Error);

  // 에러의 메세지를 특정 할 수 있다.
  expect(() =&amp;gt; compileLogicCode(true)).toThrow(&amp;quot;return Error&amp;quot;);
  // Regex (정규표현식) 을 통해, &amp;quot;Error&amp;quot; 라는 메세지가 내부에 있는지 확인 할 수 있다.
  expect(() =&amp;gt; compileLogicCode(true)).toThrow(/Error/);
})&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  ts npm run test -- exception

&amp;gt; test
&amp;gt; jest exception

 PASS  ts/exception.test.ts
  ✓ toThrow 매처를 통해 에러를 잡아내는 예제 작성! (11 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        2.314 s
Ran all test suites matching /exception/i.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 에러를 판별하는 matcher 는 추후 백엔드 프로그램에서 특정 &lt;code&gt;HttpException&lt;/code&gt; 을 잡아내는 데에 매우 탁월하다.&lt;/p&gt;
&lt;h3&gt;Matcher else - 이 곳을 참조하세요. (Array 나 Iterable 등등)&lt;/h3&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://jestjs.io/docs/expect&quot;&gt;https://jestjs.io/docs/expect&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;흔히 보이는 &lt;code&gt;describe&lt;/code&gt; 와 &lt;code&gt;it&lt;/code&gt; 는 무엇일까?&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;describe&lt;/code&gt; 는 테스트를 묶어 주는 일종의 Grouping 메서드이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;it&lt;/code&gt; 은 위에서 주구장창 사용했던 &lt;code&gt;test&lt;/code&gt; 의 &lt;strong&gt;alias&lt;/strong&gt; 이다.&lt;/p&gt;
&lt;p&gt;한번 NestJS 에서 &lt;code&gt;nest cli&lt;/code&gt; 를 사용하여 생성된 기본 형태의 &lt;code&gt;xxx.controller.spec.ts&lt;/code&gt; 를 살펴보자 :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import { Test, TestingModule } from &amp;#39;@nestjs/testing&amp;#39;;
import { ReportsController } from &amp;#39;./reports.controller&amp;#39;;

describe(&amp;#39;ReportsController&amp;#39;, () =&amp;gt; {
  let controller: ReportsController;

  beforeEach(async () =&amp;gt; {
    const module: TestingModule = await Test.createTestingModule({
      controllers: [ReportsController],
    }).compile();

    controller = module.get&amp;lt;ReportsController&amp;gt;(ReportsController);
  });

  it(&amp;#39;should be defined&amp;#39;, () =&amp;gt; {
    expect(controller).toBeDefined();
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 글에서 내가 직접 다루지 않은 메서드들이 많이 보일 테지만,&lt;/p&gt;
&lt;p&gt;이에 대해 간단한 설명을 들으면 이해가 될 것이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;beforeEach(() =&amp;gt; {...})&lt;/code&gt; --&amp;gt; &lt;code&gt;let controller&lt;/code&gt; 에 의존성 주입된 객체를 만들어 주기 위함.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;import { Test, TestingModule } from &amp;quot;@nestjs/testing&amp;quot;&lt;/code&gt; &lt;br/&gt; --&amp;gt; NestJS 에서 만들어진 컴포넌트가 &lt;code&gt;jest&lt;/code&gt; 라이브러리와 상호작용 하기 위한 도구 라이브러리&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;그래서, 이 테스팅 파일은 이렇게 묘사 될 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;describe&lt;/code&gt; 는, &lt;code&gt;ReportsController&lt;/code&gt; 클래스 유닛을 테스팅하기 위한 그룹이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;it&lt;/code&gt; 은, &lt;code&gt;ReportsController&lt;/code&gt; 클래스에 존재하는 유닛을 하나씩 테스팅한다.&lt;/p&gt;
&lt;p&gt;참고로, 파일이 나누어지지 않았다면, 위에서 아래로 차례로 실행된다.&lt;/p&gt;
&lt;p&gt;이 때, &lt;code&gt;beforeEach&lt;/code&gt; 는 &lt;code&gt;describe&lt;/code&gt; 내부에서 원활한 테스트를 위한 준비 작업이라고 생각하면 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그런데, 이 파일로 테스팅 결과를 보여주려고 하지만, &lt;code&gt;ReportsController&lt;/code&gt; 에는 하나의 의존성이 필요하다.&lt;/p&gt;
&lt;p&gt;바로, &lt;code&gt;ReportsService&lt;/code&gt; 이다.&lt;/p&gt;
&lt;p&gt;만약에, 내가 직접 만든 &lt;code&gt;ReportsService&lt;/code&gt; 를 넣는다면, 또 서비스에 필요한 repo 도 넣어주어야 한다.&lt;/p&gt;
&lt;p&gt;그렇게 되면, 유닛 테스트에 적합하지 않고, e2e 테스팅에 적합하게 된다.&lt;/p&gt;
&lt;p&gt;따라서, 테스팅 모듈 생성 과정에서 &lt;code&gt;ReportsService&lt;/code&gt; 를 Mocking 해 보자!&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import { Test, TestingModule } from &amp;#39;@nestjs/testing&amp;#39;;
import { ReportsController } from &amp;#39;./reports.controller&amp;#39;;
import { ReportsService } from &amp;#39;./reports.service&amp;#39;;
import { Report } from &amp;#39;./reports.entity&amp;#39;;

describe(&amp;#39;ReportsController&amp;#39;, () =&amp;gt; {
  let controller: ReportsController;
  let mockReportsService: Partial&amp;lt;ReportsService&amp;gt;;

  beforeEach(async () =&amp;gt; {
    const module: TestingModule = await Test.createTestingModule({
      controllers: [ReportsController],
      providers : [
        {
          provide : ReportsService,
          useValue : mockReportsService,
        }
      ]

    }).compile();

    controller = module.get&amp;lt;ReportsController&amp;gt;(ReportsController);
  });

  it(&amp;#39;should be defined&amp;#39;, () =&amp;gt; {
    expect(controller).toBeDefined();
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위에서 달라진 것은, &lt;code&gt;providers&lt;/code&gt; 라는 옵션이 추가되었다.&lt;/p&gt;
&lt;p&gt;이는, NestJS 에서 &lt;code&gt;ReportsController&lt;/code&gt; 에 필요한 의존성을 주입 하기 위해 존재하는 옵션이다.&lt;/p&gt;
&lt;p&gt;그 과정에서 &lt;code&gt;ReportsService&lt;/code&gt; 의존성 토큰을 필요로 한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Partial&lt;/code&gt; 은 &lt;code&gt;&amp;lt;&amp;gt;&lt;/code&gt; 기호 내부에 사용된 모든 타입들을 &amp;quot;기본적으로&amp;quot; 가진다는 이야기이다.&lt;/p&gt;
&lt;p&gt;이를 통해, &lt;code&gt;ReportsService&lt;/code&gt; 가 완벽하게 Stub 으로 구현되지 않더라도,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Partial&amp;lt;ReportsService&amp;gt;&lt;/code&gt; 를 선언함으로서, &lt;code&gt;ReportsController&lt;/code&gt; 의 의존성을 해결 할 수 있는 것이다.&lt;/p&gt;
&lt;p&gt;어쨋든, &lt;code&gt;ReportsService&lt;/code&gt; 는 &lt;code&gt;ReportsController&lt;/code&gt; 에 필요한 의존성이기 때문에,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ReportsController&lt;/code&gt; 는 &lt;code&gt;TestingModule&lt;/code&gt; 메셔드를 통해 의존성이 충족 된 상태로 나오게 된다.&lt;/p&gt;
&lt;p&gt;따라서, &lt;code&gt;it(&amp;quot;should be defined&amp;quot;, () =&amp;gt; expect(conteroller).toBeDefined())&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;위에서 요구하는 &lt;code&gt;ReportsController&lt;/code&gt; 가 정의되어 있는가? 에 대한 질문은&lt;/p&gt;
&lt;p&gt;&lt;code&gt;defined&lt;/code&gt; 라고 말할 수 있는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;실제 stub 까지 구현된 유닛 테스팅의 경우&lt;/h2&gt;
&lt;p&gt;밑의 코드는 실제로 Stub 까지 구현 한 유닛 테스트인데,&lt;/p&gt;
&lt;p&gt;훑어보면서 참고만 해도 충분할 것이다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import { Test, TestingModule } from &amp;#39;@nestjs/testing&amp;#39;;
import { UsersController } from &amp;#39;./users.controller&amp;#39;;
import { User } from &amp;#39;./users.entity&amp;#39;;
import { UsersService } from &amp;#39;./users.service&amp;#39;;
import { AuthService } from &amp;#39;./auth.service&amp;#39;;
import { BadRequestException, NotFoundException } from &amp;#39;@nestjs/common&amp;#39;;

describe(&amp;#39;UsersController&amp;#39;, () =&amp;gt; {
  let controller: UsersController;
  let fakeUsersService: Partial&amp;lt;UsersService&amp;gt;;
  let fakeAuthService: Partial&amp;lt;AuthService&amp;gt;;

  let users: User[] = [];
  let currentUser: User;

  beforeEach(async () =&amp;gt; {
    // UsersService Mocking 하기
    fakeUsersService = {
      findOne: (id: number): Promise&amp;lt;User&amp;gt; =&amp;gt; {
        const findUser: User = users.find((user: User) =&amp;gt; user.id === id);
        // TypeORM 에서는 검색 유저가 없더라도, 에러를 보내지 않는다는 것을 감안함.
        return Promise.resolve(findUser);

        // return findUser
          // ? Promise.resolve(findUser)
          // : Promise.reject(findUser);
      },
      find: (email: string): Promise&amp;lt;User[]&amp;gt; =&amp;gt; {
        const findUsers: User[] = users.filter(
          (user: User) =&amp;gt; user.email === email,
        );

        // TypeORM 의 특성을 고려.
        return Promise.resolve(findUsers);
        // return findUsers.length
          // ? Promise.resolve(findUsers)
          // : Promise.reject(NotFoundException);
      },
      update: (id: number, attrs: Partial&amp;lt;User&amp;gt;) =&amp;gt; {
        const findUser: User = users.find((user: User) =&amp;gt; user.id === id);

        // 수정하려는 유저의 id 가 존재하지 않다면.
        if (!findUser) {
          return Promise.reject(NotFoundException);
        }

        Object.assign(findUser, attrs);

        users = users.map((user: User) =&amp;gt; {
          return user.id !== id ? user : findUser;
        });

        return Promise.resolve(findUser);
      },
      remove: (id: number): Promise&amp;lt;User&amp;gt; =&amp;gt; {
        let findUser: User;
        users = users.filter((user: User) =&amp;gt; {
          findUser = user;
          return user.id !== id;
        });

        // 삭제가 성공한다면 해당 유저 정보를 반환, 아니면 에러
        return findUser
          ? Promise.resolve(findUser)
          : Promise.reject(NotFoundException);
      },
    };
    // AuthService 모킹하기 (단, 해싱은 제외했음.)
    fakeAuthService = {
      signUp: (email, password) =&amp;gt; {
        const isAlreadyUser: User = users.find(
          (user: User) =&amp;gt; user.email === email,
        );

        // 이미 이메일이 존재한다면, 회원가입 하지 못한다.
        if (isAlreadyUser) {
          return Promise.reject(BadRequestException);
        }

        currentUser = {
          id: Math.floor(Math.random() * 9999),
          email : email,
          password : password,
        } as User

        users.push(currentUser as User);

        return Promise.resolve(currentUser as User);
      },
      signIn: (email, password) =&amp;gt; {
        const findUser = users.find((user: User) =&amp;gt; user.email);

        if (findUser.password === password) {
          return Promise.resolve(findUser);
        } else {
          return Promise.reject(NotFoundException);
        }
      },
    };

    const module: TestingModule = await Test.createTestingModule({
      controllers: [UsersController],
      providers: [
        {
          provide: AuthService,
          useValue: fakeAuthService,
        },
        {
          provide: UsersService,
          useValue: fakeUsersService,
        },
      ],
    }).compile();

    controller = module.get&amp;lt;UsersController&amp;gt;(UsersController);
  });

  it(&amp;#39;should be defined&amp;#39;, () =&amp;gt; {
    expect(controller).toBeDefined();
  });

  it(&amp;quot;회원가입&amp;quot;, async () =&amp;gt; {
    await expect(
      controller.createUser(
        {
          email: &amp;quot;test@gmail.com&amp;quot;,
          password: &amp;quot;1234&amp;quot;
        }
      )
    ).resolves.toBeDefined();

    console.log(currentUser);
  });

  it(&amp;quot;로그인&amp;quot;, async () =&amp;gt; {
    await expect(
      controller.signIn(currentUser, {})
    ).resolves.toBeDefined();
  });

  it(&amp;quot;특정 유저 id 로 찾기&amp;quot;, async () =&amp;gt; {
    await expect(
      controller.findUser(currentUser.id)
    ).resolves.toBeDefined();
  });

  it(&amp;quot;없는 유저 id 로 에러 일으키기&amp;quot;, async () =&amp;gt; {
    await expect(
      controller.findUser(0)
    ).rejects.toThrow(NotFoundException)
  });

  it(&amp;quot;이메일로 유저 정보 얻기&amp;quot;, async () =&amp;gt; {
    await expect(
      controller.findAllUsers(currentUser.email)
    ).resolves.toBeDefined();
  });

  it(&amp;quot;사용자 정보 수정하기&amp;quot;, async () =&amp;gt; {
    await expect(
      controller.updateUser(currentUser.id, {
        email: &amp;quot;new@gmail.com&amp;quot;,
        password: currentUser.password
      })
    ).resolves.toBeDefined();
  })

  it(&amp;quot;사용자 계정 이메일로 삭제하기&amp;quot;, async () =&amp;gt; {
    await expect(
      controller.removeUser(currentUser.id)
    ).resolves.toBeDefined();
  });

  it(&amp;quot;사용자 계정 삭제 후 조회해 보기&amp;quot;, async () =&amp;gt; {
    await expect(
      controller.findUser(currentUser.id)
    ).rejects.toThrow(NotFoundException);
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;이 블로그 문서를 정리하면서 배운 것&lt;/h2&gt;
&lt;p&gt;유닛 테스트는 하나의 메서드 일 수도, 프로그램을 구성하는 어플리케이션의 일부 클래스일 수도 있다.&lt;/p&gt;
&lt;p&gt;다양한 유닛 테스트 종류가 존재하지만, 주로 사용되는 유닛 테스트 경향은 굉장히 간단하게 사용된다는 편이라고 한다.&lt;/p&gt;
&lt;p&gt;그리고 특히, 특정 프레임워크에서 사용되는 데코레이터는 유닛 테스트 과정에서 직접적으로 적용되지 않는다.&lt;/p&gt;
&lt;p&gt;따라서, 실제로 들어갈 수 있는 JWT 혹은 암호화 과정은 직접 구현해야 한다는 점이 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;h2&gt;참고 사이트&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;AWS 문서&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://aws.amazon.com/ko/what-is/unit-testing/&quot;&gt;https://aws.amazon.com/ko/what-is/unit-testing/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Jest 공식 문서&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://jestjs.io/docs&quot;&gt;https://jestjs.io/docs&lt;/a&gt;&lt;/p&gt;</description>
      <category>Node.js/잡다 지식</category>
      <category>Jest</category>
      <category>jest 사용법</category>
      <category>matcher</category>
      <category>tobe</category>
      <category>ts-jest</category>
      <category>unit test</category>
      <category>unit test jest</category>
      <category>단위 테스트</category>
      <category>유닛 테스트</category>
      <category>유닛 테스트 jest</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/207</guid>
      <comments>https://codecreature.tistory.com/207#entry207comment</comments>
      <pubDate>Mon, 5 May 2025 22:33:09 +0900</pubDate>
    </item>
    <item>
      <title>삽입 정렬 (Insertion Sort) - with Java</title>
      <link>https://codecreature.tistory.com/206</link>
      <description>&lt;h2&gt;제목 : 삽입 정렬 (Insertion Sort) - with Java&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;기초적인 정렬 중, $ O(N^2) $ 를 가진 방법이다.&lt;/p&gt;
&lt;p&gt;바로 직전에 &lt;strong&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/205&quot;&gt;선택 정렬 (Selection Sort)&lt;/a&gt;&lt;/strong&gt; 에 대해서 주제를 다뤘는데,&lt;/p&gt;
&lt;p&gt;선택 정렬과 삽입 정렬은, &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;현재 인덱스 이전이 완벽하게 정렬 되어 있는가?&lt;/strong&gt; 로 나뉜다고 생각한다.&lt;/p&gt;
&lt;p&gt;선택 정렬의 경우, 현재 인덱스 이전이 완벽하게 정렬되어 있다.&lt;/p&gt;
&lt;p&gt;삽입 정렬의 경우, 현재 인덱스 이전 요소들에 한하여, 해당 영역은 정렬되어 있다.&lt;/p&gt;
&lt;p&gt;하지만, 결과적으로 봤을 때 완벽하게 정렬되어 있지 않다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;또 다른 관점에서 둘을 비교해 보자.&lt;/p&gt;
&lt;p&gt;Selection Sort 의 경우, &lt;code&gt;i&lt;/code&gt; 번째 요소에 넣기 위한 &amp;quot;결과론적으로 완벽한&amp;quot; 요소를 찾는다.&lt;/p&gt;
&lt;p&gt;Insertion Sort 의 경우, &lt;code&gt;i&lt;/code&gt; 번째 요소는 &lt;code&gt;1&lt;/code&gt; 번째 부터, &lt;code&gt;i - 1&lt;/code&gt; 번째 까지의 영역 중 하나의 장소에 들어간다.&lt;/p&gt;
&lt;p&gt;즉, &lt;code&gt;1&lt;/code&gt; 번째 부터, &lt;code&gt;i - 1&lt;/code&gt; 번째까지의 요소는 &amp;quot;해당 영역에서만&amp;quot; 정렬되어 있다.&lt;/p&gt;
&lt;p&gt;Insertion Sort 의 경우, 모든 루프가 끝날 때 까지, 어떠한 인덱스 범위도 완벽하게 정렬되어 있다 장담 할 수 없다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;삽입 정렬은 무엇이지?&lt;/h3&gt;
&lt;p&gt;기존의 포스팅들은 이미 삽입 정렬의 알고리즘과 구현에 대해서 다루고 있다.&lt;/p&gt;
&lt;p&gt;하지만, 나는 &lt;strong&gt;관점&lt;/strong&gt; 에 대해서 말해보고 싶다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;관점은 왜 다루나?&lt;/h3&gt;
&lt;p&gt;나는 이전에 알고리즘을 공부하는 도중, 모르는 것이 있으면 그냥 외워 버렸었다.&lt;/p&gt;
&lt;p&gt;이러한 방식은 단기적으로 알고리즘 문제 자체를 푸는 데에는 효과적이었지만,&lt;/p&gt;
&lt;p&gt;정렬의 본질적인 로직을 조금만 비틀어도 엄청나게 고민하고 있는 나 자신을 발견했다.&lt;/p&gt;
&lt;p&gt;이후에 알고리즘을 이해하는 방식을 조금 바꾸었는데,&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;이 알고리즘은 어떤 관점에서 파생되었을까?&lt;/strong&gt; 하는 것이다.&lt;/p&gt;
&lt;p&gt;이 알고리즘이 있다. 라는 것을 배우는 것이 아니라, 이 알고리즘이 어떤 관점 or 상황에서 필요한가? 하는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;그래서 삽입 정렬에서 어떤 관점을 볼 수 있나?&lt;/h3&gt;
&lt;p&gt;나는 삽입 정렬(Insertion Sort) 이, 인간이 행하는 가장 흔한 정렬 중 하나라고 생각한다.&lt;/p&gt;
&lt;p&gt;그래서, 우리는 특정 상황을 떠올려 볼 수 있다.&lt;/p&gt;
&lt;p&gt;만약에 숫자 1 부터, 40 까지 적혀 있는 카드 혹은 종이를 손에 들고 있다고 가정한다.&lt;/p&gt;
&lt;p&gt;이 숫자의 순서는 랜덤으로 배치되어 있으며, 오름차순으로 정리 할 것이다.&lt;/p&gt;
&lt;p&gt;그렇다면, 우리는 &lt;strong&gt;어떤 관점으로&lt;/strong&gt; 정렬 할 것인가?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;정렬되지 않은 카드들을 &lt;strong&gt;전부&lt;/strong&gt; 본 뒤, 가장 작은 수를 가진 카드를 따로 앞에 배치한다. - (선택 정렬 - Selection)&lt;/li&gt;
&lt;li&gt;앞에서부터 카드를 하나씩 보며, 이미 정렬 해 놓은 카드들 사이에서 적절한 위치에 삽입한다. - (삽입 정렬 - Insertion)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;내가 생각한 관점의 차이는 이러하다.&lt;/p&gt;
&lt;p&gt;나라면, 선택 정렬보다는 삽입 정렬을 선택했을 것 같다. &lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;삽입 정렬의 로직은?&lt;/h3&gt;
&lt;p&gt;이렇게 먼저 관점을 펼쳐놓고 로직을 생각 해 보니, &lt;/p&gt;
&lt;p&gt;확실히 로직은 관점으로부터 파생되는 것 같다는 생각이 든다.&lt;/p&gt;
&lt;p&gt;일단 생각은 제쳐두고, 어떠한 방식으로 정렬이 수행되는지 살펴보자.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;index&lt;/th&gt;
&lt;th&gt;0&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;value&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;이러한 배열이 존재하며, 우리는 이 값들을 정렬해야 한다고 가정 해 보자.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;먼저, 삽입 정렬은 로직 수행을 위해 index &lt;code&gt;1&lt;/code&gt; 에서부터 시작한다.&lt;/p&gt;
&lt;p&gt;예시로 든 상황을 보면 알 수 있듯이, &amp;quot;정렬 된 카드&amp;quot; 가 필요하기 때문이다.&lt;/p&gt;
&lt;p&gt;어짜피 현재 정렬 된 카드가 5 하나뿐이라면, 이 또한 정렬되었다고 말할 수 있다!&lt;/p&gt;
&lt;br/&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;index&lt;/th&gt;
&lt;th&gt;0&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;value&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;b style=&quot;color : red&quot;&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;숫자 &lt;code&gt;2&lt;/code&gt; 와 &lt;code&gt;5&lt;/code&gt; 를 비교해보자.&lt;/p&gt;
&lt;p&gt;오름차순으로 정렬하므로, &lt;code&gt;2&lt;/code&gt; 가 당연히 &lt;code&gt;5&lt;/code&gt; 앞에 와야 할 것이다.&lt;/p&gt;
&lt;p&gt;그런데, 우리가 정해야 할 것이 있다.&lt;/p&gt;
&lt;p&gt;컴퓨터는 사람과 달리, 숫자들을 펼쳐서 볼 수 없다.&lt;/p&gt;
&lt;p&gt;즉, 컴퓨터는 숫자를 하나씩 뽑아서 보아야 한다는 사실을 알고, 다음 단계로 넘어가자.&lt;/p&gt;
&lt;br/&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;index&lt;/th&gt;
&lt;th&gt;0&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;value&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;b style=&quot;color : red&quot;&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;정렬된 배열의 마지막 부분 &lt;code&gt;5&lt;/code&gt; 와, 이제 정렬된 배열로 넣을 &lt;code&gt;3&lt;/code&gt; 을 비교해보자.&lt;/p&gt;
&lt;p&gt;당연히 현재 인덱스는 &lt;code&gt;3&lt;/code&gt; 을 가르키므로, 정렬될 배열로 들어가야 한다.&lt;/p&gt;
&lt;p&gt;이제 정해주어야 하는 것은, &lt;strong&gt;어떻게 앞 부분이 정렬된 상태를 유지하나?&lt;/strong&gt; 인 것이다.&lt;/p&gt;
&lt;p&gt;만약에, 현재 인덱스가 가르키는 값이 &lt;code&gt;3&lt;/code&gt; 이 아니라, &lt;code&gt;1&lt;/code&gt; 이라고 가정 해 보자.&lt;/p&gt;
&lt;p&gt;그렇다면, 단순히 &lt;code&gt;5&lt;/code&gt; 와 &lt;code&gt;1&lt;/code&gt; 이 바뀐다고 해결되지 않는다. &lt;code&gt;2&lt;/code&gt;, &lt;code&gt;1&lt;/code&gt;, &lt;code&gt;5&lt;/code&gt; 는 정렬 된 상태가 아니다.&lt;/p&gt;
&lt;p&gt;그렇다면, 앞 쪽의 배열은 어떻게 정렬된 상태를 유지하는가?&lt;/p&gt;
&lt;p&gt;이는 조금 건너뛰어 실제 값 &lt;code&gt;1&lt;/code&gt; 이 존재하는 인덱스 4 로 가보자.&lt;/p&gt;
&lt;br/&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;index&lt;/th&gt;
&lt;th&gt;0&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;value&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;b style=&quot;color : red&quot;&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;자, 이제 선명하게 앞 쪽은 정렬된 배열이라는 것을 알 수 있고,&lt;/p&gt;
&lt;p&gt;뒤 쪽은 아직 정렬되지 않은 부분이라는 것을 알 수 있다.&lt;/p&gt;
&lt;p&gt;이제 다시 생각해보자. 어떻게 정렬해야 하는가?&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;당연히 &lt;code&gt;1&lt;/code&gt; 은 배열의 가장 앞 부분에 와야 할 것이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;1&lt;/code&gt; 을 맨 앞에 넣기 위해, IDX &lt;code&gt;0&lt;/code&gt; 과 IDX &lt;code&gt;4&lt;/code&gt; 를 그대로 바꿔버릴 순 없다. 정렬되지 않기 때문이다.&lt;/p&gt;
&lt;p&gt;따라서, 현재 정렬된 배열에 넣을 숫자 &lt;code&gt;1&lt;/code&gt; 에서부터 역 순으로 조회하며,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;1&lt;/code&gt; 보다 큰 수라면, 해당 배열의 요소는 앞으로 Shift 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;하나의 루프 과정을 촘촘하게 파헤쳐 보자&lt;/h3&gt;
&lt;p&gt;이제, &lt;code&gt;1&lt;/code&gt; 을 넣기 위해, 숫자들을 Shift 하는 과정을 보자.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;center&quot;&gt;비교할 수는 따로 저장&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;&lt;b style=&quot;color : red&quot;&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;5 는 1 보다 크다&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;index&lt;/th&gt;
&lt;th&gt;0&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;value&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;5 -&amp;gt;&lt;/td&gt;
&lt;td&gt;-&amp;gt; 5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;4 는 1 보다 크다&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;index&lt;/th&gt;
&lt;th&gt;0&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;value&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4 -&amp;gt;&lt;/td&gt;
&lt;td&gt;-&amp;gt; 4&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;3 은 1 보다 크다&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;index&lt;/th&gt;
&lt;th&gt;0&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;value&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3 -&amp;gt;&lt;/td&gt;
&lt;td&gt;-&amp;gt; 3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;2 는 1 보다 크다&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;index&lt;/th&gt;
&lt;th&gt;0&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;value&lt;/td&gt;
&lt;td&gt;2 -&amp;gt;&lt;/td&gt;
&lt;td&gt;-&amp;gt; 2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;결과&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;index&lt;/th&gt;
&lt;th&gt;0&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;value&lt;/td&gt;
&lt;td&gt;&lt;b style=&quot;color : red&quot;&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br/&gt;

&lt;p&gt;위의 과정을 통해 왼쪽의 배열이 어떻게 정렬된 상태를 유지하는 지 알 수 있었을 것이다.&lt;/p&gt;
&lt;p&gt;만약에, &lt;code&gt;0&lt;/code&gt; 이 있었다면, 아마 &lt;code&gt;1&lt;/code&gt; 은 &lt;code&gt;0&lt;/code&gt; 을 발견하고, &lt;strong&gt;1 은 0 보다 작다&lt;/strong&gt; 조건이 발동되어 바로 뒤에 배치 될 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;구현 - With Java&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;예제 입력&lt;/strong&gt;, &lt;strong&gt;예제 출력&lt;/strong&gt; 은,&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;예제 입력&lt;/th&gt;
&lt;th&gt;예제 출력&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;pre&gt;5&lt;br&gt;5&lt;br&gt;2&lt;br&gt;3&lt;br&gt;4&lt;br&gt;1&lt;/pre&gt;&lt;/td&gt;
&lt;td&gt;&lt;pre&gt;1&lt;br&gt;2&lt;br&gt;3&lt;br&gt;4&lt;br&gt;5&lt;/pre&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;이라고 가정한다.&lt;/p&gt;
&lt;p&gt;맨 위의 입력은 &lt;code&gt;N&lt;/code&gt; 이고, 하나의 줄에 입력이 들어온다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;N&lt;/code&gt; 개의 줄에 걸쳐 입력이 들어오고, &lt;code&gt;N&lt;/code&gt; 개의 줄로 결과를 출력해야 한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;import java.util.*;
import java.io.*;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        // N 개의 요소를 정렬해야함.
        int N = Integer.parseInt(br.readLine());

        int[] arr = new int[N];

        for(int i = 0; i &amp;lt; N; i++) {
            arr[i] = Integer.parseInt(br.readLine());
        }

        // 삽입 정렬 - Insertion Sort 실행
        insertionSort(arr);

        StringBuilder sb = new StringBuilder();
        for(int i = 0; i &amp;lt; arr.length; i++) {
            sb.append(arr[i]).append(&amp;quot;\n&amp;quot;);
        }
        System.out.println(sb.toString());
    }

    // 삽입 정렬 수행 함수 
    public static void insertionSort(int[] arr) {

        // 이미 정렬된 범위가 필요하므로, 처음이 아니라 그 다음부터 시작한다.
        for(int i = 1; i &amp;lt; arr.length; i++) {
            // 비교할 수를 선택하여 따로 저장 해 둔다.
            int currNum = arr[i];

            // j 를 for 문 안에서 소멸시키지 않고, 마지막에 재사용하기 위함.
            int j = i - 1;
            // j 는 0 이라는 끝 까지 가거나, 현재 지정 수가 arr[j] 보다 작을 때만 수행한다.
            for(; j &amp;gt;= 0 &amp;amp;&amp;amp; arr[j] &amp;gt; currNum; j--) {
                // 비교할 정렬된 숫자를 추출한다.
                int diffNum = arr[j];

                // shift 수행 
                arr[j + 1] = arr[j];
            }
            // 지정된 위치로 숫자를 배치시킨다.
            arr[j + 1] = currNum;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;h2&gt;참고 사이트&lt;/h2&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.w3schools.com/dsa/dsa_algo_insertionsort.php&quot;&gt;https://www.w3schools.com/dsa/dsa_algo_insertionsort.php&lt;/a&gt;&lt;/p&gt;</description>
      <category>Algorithm/Sort</category>
      <category>insertion sort</category>
      <category>insertion 정렬</category>
      <category>java</category>
      <category>sort</category>
      <category>삽입 정렬</category>
      <category>원리</category>
      <category>정렬</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/206</guid>
      <comments>https://codecreature.tistory.com/206#entry206comment</comments>
      <pubDate>Wed, 16 Apr 2025 18:38:20 +0900</pubDate>
    </item>
    <item>
      <title>선택 정렬 (Selection sort) 에 대하여 - With Java</title>
      <link>https://codecreature.tistory.com/205</link>
      <description>&lt;h2&gt;제목 : 선택 정렬 (Selection sort) 에 대하여 - With Java&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;가장 기초적인 정렬 중 하나이다.&lt;/p&gt;
&lt;p&gt;시간 복잡도는 $ O(N^2) $ 이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;나는 선택 정렬을 설명하기 위해 이러한 상황을 예시로 들고 싶다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;상황&lt;/strong&gt; : 선생님이 걷어온 쪽지 시험을 학생의 고유 번호대로 상승하도록 정렬 해 달라고 부탁하셨다.&lt;/p&gt;
&lt;p&gt;그렇다면, 약 40장 정도가 존재한다고 가정한다면 나는 어떻게 정렬해야 할까?&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;믈론 사람마다 종이를 순서대로 나열하는 데 있어 다른 방식이 있을 수도 있다.&lt;/p&gt;
&lt;p&gt;하지만, 1 번부터 찾고 왼쪽에 둔다. 그 다음 2 번을 찾아 1 번의 밑에 둔다.&lt;/p&gt;
&lt;p&gt;그리고 또 3 번을 찾고 2번의 밑에 둔다. 그리고 4번도...&lt;/p&gt;
&lt;p&gt;이러한 방식으로 1 번부터 40 번 까지 정렬 할 수 있다.&lt;/p&gt;
&lt;p&gt;이 방식이 바로 &lt;strong&gt;선택 정렬(Selection Sort)&lt;/strong&gt; 방식이다.&lt;/p&gt;
&lt;p&gt;아 물론, 실제 선택 정렬의 상황과 많이 다른 감이 있다. &lt;del&gt;정직하게 수가 주어지지 않으므로..&lt;/del&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;라이브러리가 존재하지 않는다는 가정 하에, 이는 구현 할 수 있는 매우 간단한 정렬 방식일 것이다.&lt;/p&gt;
&lt;p&gt;그러나, 하나의 요소를 적층하기 위해, 모든 배열을 루프마다 또 탐색해야 한다는 단점이 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;컴퓨터로 상황을 변환 해 보자.&lt;/h2&gt;
&lt;p&gt;우리의 프로그램은 랜덤한 정수 5 개를 정렬해야 한다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;index&lt;/th&gt;
&lt;th&gt;0&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;value&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;수는 &lt;strong&gt;오름차순&lt;/strong&gt; 으로 정렬 해야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그렇다면, 이 수를 &amp;quot;정렬&amp;quot; 하기 위해서는 어떤 프로그래밍적 행위가 필요할까?&lt;/p&gt;
&lt;p&gt;나는 위의 질문이 정렬 알고리즘 구현을 배우는 것 만큼 매우 중요하다고 생각한다.&lt;/p&gt;
&lt;p&gt;이에 대한 답은 내가 예시로 들었던 상황과 행동에서 추출할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;현재 몇 번째에 종이를 넣을 것인가? : 어떤 인덱스에 수를 넣을 것인가?&lt;/h3&gt;
&lt;p&gt;이 글의 의미는, 1 번째로 들어갈 종이를 찾는다면, &lt;/p&gt;
&lt;p&gt;우리는 배열의 인덱스 0 에 들어갈 가장 작은 수를 찾고 있는 것이고,&lt;/p&gt;
&lt;p&gt;2 번째로 들어갈 종이를 찾는다면,&lt;/p&gt;
&lt;p&gt;우리는 배열의 인덱스 1 에 들어갈 가장 작은 수를 찾고 있는 것이다.&lt;/p&gt;
&lt;p&gt;이 때, 가장 작은 수는 삽입할 인덱스부터 시작하여 끝 까지 탐색한다.&lt;/p&gt;
&lt;p&gt;이미 앞의 인덱스들은, 탐색할 인덱스보다 모두 작기 때문이다. (이미 정렬이 끝난 수 이므로)&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;가장 작은 번호를 가진 종이를 찾아야 해! : 현재 인덱스에 들어갈 가장 작은 수를 찾자!&lt;/h3&gt;
&lt;p&gt;사람과 컴퓨터의 인식은 개념 자체가 다르다.&lt;/p&gt;
&lt;p&gt;사람은 인식과 사고가 유연하기 때문에, 각 번째에 들어갈 가장 작은 수를 찾기 위해,&lt;/p&gt;
&lt;p&gt;이미 어떤 수가 어디쯤에 존재하는지 추상적으로 기억 해 둘 수도 있다.&lt;/p&gt;
&lt;p&gt;심지어는, 꽤 앞 쪽의 번호 같다면, 미리 앞쪽으로 종이를 끼워 둘 수도 있는 행동이 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;그러나,&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;컴퓨터는 다르다. 음.. 어찌 본다면, 인간의 유연한 사고에 비해, 컴퓨터의 행동 반경은 정말이지, 바보이다.&lt;/p&gt;
&lt;p&gt;물론, 인공지능을 빼고 한 말이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;마트가서 우유사고, 만약에 아보카도 있으면 6개 사와.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;프로그래머라면, 언젠가 한 번은 봤을 법한 밈이다.&lt;/p&gt;
&lt;p&gt;프로그래머 남편은, 이 말을 아내에게 듣고 마트 갔다와서&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;아보카도 있었어&amp;quot;&lt;/strong&gt; --&amp;gt; 우유 6개를 자랑스럽게 들고 있으며..&lt;/p&gt;
&lt;p&gt;웃기기도 하지만, 참으로 컴퓨터적 사고를 결합했다고도 말할 수 있겠다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이것이 바로 컴퓨터의 사고이다.&lt;/p&gt;
&lt;p&gt;컴퓨터가 무엇을 알겠는가? 컴퓨터는 정해진 이진 데이터를 읽거나, 반응하는 수 밖에 없다.&lt;/p&gt;
&lt;p&gt;이에 기반해서, 우리가 &lt;strong&gt;정렬&lt;/strong&gt; 즉, &lt;strong&gt;Sorting&lt;/strong&gt; 을 위해서 컴퓨터에게 어떻게 명령해야 하는지 알아야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;우리가 정렬을 수행하기 위해, 필요한 컴퓨터의 사고는 이러하다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;비교 - Compare&lt;/li&gt;
&lt;li&gt;삽입 - Insert&lt;/li&gt;
&lt;li&gt;추출 - Extract&lt;/li&gt;
&lt;li&gt;저장 - Data Store&lt;/li&gt;
&lt;li&gt;반복 - Loop&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;위의 5 가지 행동 요소가 정렬의 주된 요소이다.&lt;/p&gt;
&lt;p&gt;이를 통해 들어갈 가장 작은 수를 찾아 보자. &lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;i&lt;/code&gt; 번째에 들어갈 가장 작은 수를 찾아야 한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;i - 1&lt;/code&gt; 번째까지는 이미 정렬되어 있으므로, &lt;code&gt;i&lt;/code&gt; 번째부터 시작하여 탐색을 시작한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;N - 1&lt;/code&gt; 번 쨰 까지 탐색하며, 지금까지의 가장 작은 수와, 현재 탐색 인덱스의 수를 비교한다.&lt;/li&gt;
&lt;li&gt;현재 인덱스의 값이 지금까지의 가장 작은 수 보다 작다면, 현재 인덱스가 가장 작은 수를 가진 인덱스로 저장된다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;N - 1&lt;/code&gt; 번까지 탐색하여, 가장 작은 수가 존재하는 인덱스를 저장한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;i&lt;/code&gt; 번째 인덱스와, 가장 작은 인덱스의 값을 교환한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;i&lt;/code&gt; 가 &lt;code&gt;N - 1&lt;/code&gt; 일 때 까지 위의 행동을 반복한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;h2&gt;구현 With Java&lt;/h2&gt;
&lt;p&gt;위의 과정을 적용하여 실제 코드로 만들어 보자.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예제 입력&lt;/strong&gt;, &lt;strong&gt;예제 출력&lt;/strong&gt; 은,&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;예제 입력&lt;/th&gt;
&lt;th&gt;예제 출력&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;pre&gt;5&lt;br&gt;5&lt;br&gt;2&lt;br&gt;3&lt;br&gt;4&lt;br&gt;1&lt;/pre&gt;&lt;/td&gt;
&lt;td&gt;&lt;pre&gt;1&lt;br&gt;2&lt;br&gt;3&lt;br&gt;4&lt;br&gt;5&lt;/pre&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;이라고 가정한다.&lt;/p&gt;
&lt;p&gt;맨 위의 입력은 &lt;code&gt;N&lt;/code&gt; 이고, 하나의 줄에 입력이 들어온다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;N&lt;/code&gt; 개의 줄에 걸쳐 입력이 들어오고, &lt;code&gt;N&lt;/code&gt; 개의 줄로 결과를 출력해야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;import java.util.*;
import java.io.*;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        // N 개를 정렬해야 한다.
        int N = Integer.parseInt(br.readLine());

        // 정렬 할 공간, 즉, 배열을 마련한다.
        int[] arr = new int[N];

        // N 번에 걸쳐 arr 배열에 값을 넣어준다.
        for(int i = 0; i &amp;lt; N; i++) {
            arr[i] = Integer.parseInt(br.readLine());
        }

        // 선택 정렬 수행!
        selectionSort(arr);

        // 콘솔에 출력
        StringBuilder sb = new StringBuilder();
        for(int i = 0; i &amp;lt; arr.length; i++) {
            sb.append(arr[i]).append(&amp;quot;\n&amp;quot;);
        }
        System.out.println(sb.toString());

    }

    // 선택 정렬 함수 
    public static void selectionSort(int[] arr) {
        // i 번째 부터 배열의 끝 까지 실행한다.
        for(int i = 0; i &amp;lt; arr.length; i++) {
            // i 번째에 들어가야 할 가장 작은 수, 이를 포함한 인덱스를 초기화 해준다.
            int minVal = Integer.MAX_VALUE;
            int minIdx = -1;

            // i - 1 인덱스까지는 전부 정렬이 된 상태이므로, i 인덱스부터 탐색을 시작한다.
            for(int j = i; j &amp;lt; arr.length; j++) {
                // 만약, 현재 가장 작은 값 보다, 현재 인덱스의 값이 더 작다면,
                if(arr[j] &amp;lt; minVal){
                    // 가장 작은 값을 변경하고, 지정 인덱스를 현재 인덱스로 변경한다.
                    minVal = arr[j];
                    minIdx = j;
                }
            }

            // 하나의 루프를 돌면서 가장 작은 값을 지닌 인덱스를 찾았으므로, 이를 i 번째에 넣어 준다.
            swap(arr, i, minIdx);
        }
    }

    // 배열 간의 요소 교환 관심사 분리
    public static void swap(int[] arr, int x, int y) {
        int temp = arr[x];
        arr[x] = arr[y];
        arr[y] = temp;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;알고리즘 코드를 작성 할 때, Loop 문법으로 인해 난잡해 질 코드를 편리하게 만들기 위해,&lt;/p&gt;
&lt;p&gt;특정 관심사를 분리하여 Method 로 만들어 놓는 것이 매우 좋은 습관이라고 생각한다. - 의견&lt;/p&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;h2&gt;참고 사이트&lt;/h2&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.w3schools.com/dsa/dsa_algo_selectionsort.php&quot;&gt;https://www.w3schools.com/dsa/dsa_algo_selectionsort.php&lt;/a&gt;&lt;/p&gt;</description>
      <category>Algorithm/Sort</category>
      <category>java</category>
      <category>Selection</category>
      <category>selection sort</category>
      <category>sort</category>
      <category>sorting</category>
      <category>선택 정렬</category>
      <category>정렬</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/205</guid>
      <comments>https://codecreature.tistory.com/205#entry205comment</comments>
      <pubDate>Wed, 16 Apr 2025 16:23:38 +0900</pubDate>
    </item>
    <item>
      <title>비밀번호는 왜 해싱할까? - With Node.js</title>
      <link>https://codecreature.tistory.com/204</link>
      <description>&lt;h2&gt;제목 : 비밀번호는 왜 해싱할까? - With Node.js&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;웹 어플리케이션 서버(BE) 를 만들 때, &lt;/p&gt;
&lt;p&gt;단순한 엔드포인트 제작에 대한 것을 배우고 나서 회원가입 처리를 배운다.&lt;/p&gt;
&lt;p&gt;데이터베이스에 Plain Text 로 비밀번호를 저장한다면, 보안상 굉장히 위험하므로,&lt;/p&gt;
&lt;p&gt;Encryption 과정을 거친 후, 데이터베이스에 저장한다.&lt;/p&gt;
&lt;p&gt;이 때, Encryption 과정은 양방향이 아닌, 단방향 해싱이라고 배운다.&lt;/p&gt;
&lt;p&gt;따라서, 관리자조차 원래의 비밀번호를 알지 못하기 때문에, 유저는 직접 비밀번호를 바꾸어야 한다.&lt;/p&gt;
&lt;p&gt;위의 과정을 듣고 나서 대부분의 사이트들이 왜 비밀번호를 찾을 때, 원래의 비밀번호를 알려주지 않고&lt;/p&gt;
&lt;p&gt;새로운 비밀번호로 지정하게 하는지 이해되었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, 위의 과정만 필요하다면 이 포스팅은 시작하지는 않았을 것이다.&lt;/p&gt;
&lt;p&gt;푸는 데 꽤 오랜 시간이 걸리는 암호화 과정에 왜 &lt;code&gt;Salt&lt;/code&gt; (소금??) 라는 개념이 필요한가?&lt;/p&gt;
&lt;p&gt;여기에서 시작했다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;단어 자체에 집중 해 보다.&lt;/h3&gt;
&lt;p&gt;나는 의외로 &amp;quot;소금&amp;quot; 이라는 단어에 집중했다.&lt;/p&gt;
&lt;p&gt;충분히 버무려졌을 법한 해싱 문자열에 &amp;quot;소금&amp;quot; 은 왜 뿌려야 하는가?&lt;/p&gt;
&lt;p&gt;분명히 보안이라는 목적에 충실하기에는 조금 부족하지 않았기에 추가되었을 것이라는 가정을 세웠다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;무엇이 보안이라는 목적에 조금 부족했는가?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;단방향 암호화 해시 함수는 단방향 작용으로, 결과물을 보고 역산할 수 없다.&lt;/p&gt;
&lt;p&gt;그런데, 여기서 문제점이 생긴다. &lt;/p&gt;
&lt;p&gt;대신에 모든 단방향 해싱 결과를 &amp;quot;미리 기록해놓은&amp;quot; 레인보우 테이블에 취약하다는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;레인보우 테이블이란?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;사람들이 매우 자주 사용하는 비밀번호에 대해 해싱된 결과를 리스팅 한 것이 레인보우 테이블이다.&lt;/p&gt;
&lt;p&gt;문제는, 이것이 수백만 개를 가지고 있는 사람은, 단순 암호화가 된 비밀번호를 즉각적으로 알아낼 수도 있다는 것이다.&lt;/p&gt;
&lt;p&gt;이러한 문제로 인해, &lt;code&gt;Salt&lt;/code&gt; 즉, 소금이라는 개념이 생기게 되었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;단방향 해싱 함수는 단 하나의 문자열만 추가되거나 다른 것이라면, &lt;/p&gt;
&lt;p&gt;거의 어떠한 공통점도 찾아볼 수 없을 정도로 달라진다.&lt;/p&gt;
&lt;p&gt;그러나, 이러한 특징도 결국 결과물을 대조하는 레인보우 테이블에 취약하다.&lt;/p&gt;
&lt;p&gt;이는 유저들이 특수 기호를 많이 사용하지 않고, 일반 알파벳을 주로 넣는 습성때문에 어쩔 수 없는 것일 거다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그렇다면, 서버에서 자체적으로 생성한 &amp;quot;추가 비밀번호&amp;quot; 를 붙여주면 어떨까?&lt;/p&gt;
&lt;p&gt;그것도 외부 엔트로피로 인해 예측할 수 없는 랜덤 비밀번호를 붙여준다면?&lt;/p&gt;
&lt;p&gt;레인보우 테이블은 더 이상 유저의 비밀번호를 대조할 수 없을 것이다.&lt;/p&gt;
&lt;p&gt;위에서 말한 &amp;quot;자체적으로 생성한 추가 비밀번호&amp;quot; 가 바로 &lt;code&gt;salt&lt;/code&gt; 로 비유할 수 있다.&lt;/p&gt;
&lt;p&gt;유저들이 알파벳을 주로 사용하고, 자주 겹치기에 레인보우 테이블에 약하다면,&lt;/p&gt;
&lt;p&gt;서버는 특수기호가 덕지덕지 붙은 &lt;code&gt;salt&lt;/code&gt; 를 추가하여 비밀번호를 만들어 주면 될 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이 글을 포스팅하기 이전엔 &lt;code&gt;salt&lt;/code&gt; 자체를 환경 변수로 넣어서 비밀번호를 생성했는데,&lt;/p&gt;
&lt;p&gt;해킹하기로 마음먹고 무작위로 비밀번호를 지속적으로 대입한다면,&lt;/p&gt;
&lt;p&gt;생성된 해시 문자열로부터 &lt;code&gt;salt&lt;/code&gt; 자체도 파훼될 수도 있겠다는 생각이 들었다.&lt;/p&gt;
&lt;p&gt;그렇다면 어떻게 &lt;code&gt;salt&lt;/code&gt; 를 관리해야 할까?&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;유명한 라이브러리 &lt;code&gt;bcryptjs&lt;/code&gt; 의 경우&lt;/h3&gt;
&lt;p&gt;bcrypt 는 자체적으로 &lt;code&gt;salt&lt;/code&gt; 를 생성하여 문자열에 포함시킨다.&lt;/p&gt;
&lt;p&gt;bcrypt 는 &amp;quot;라운드&amp;quot; 를 지정하여 최종 해싱 문자열을 데이터베이스에 저장한다.&lt;/p&gt;
&lt;h3&gt;NodeJS 라이브러리 &lt;code&gt;scrypt&lt;/code&gt; 의 경우&lt;/h3&gt;
&lt;p&gt;scrypt 의 경우, 라운드 개념이 없고, 내부 파라미터들을 자체적으로 조합하여 KEY 를 만든다.&lt;/p&gt;
&lt;p&gt;물론 임의의 값으로 배정된다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;salt&lt;/code&gt; 는 자체적으로 생성 해 주어야 하며, 각 비밀번호 마다 고유하다(Unique).&lt;/p&gt;
&lt;p&gt;그러므로, 사용자마다 &lt;code&gt;salt&lt;/code&gt; 를 저장 해 주어야 한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;해싱 문자열 결과물에 &lt;code&gt;salt&lt;/code&gt; 를 덧붙인다.&lt;/li&gt;
&lt;li&gt;혹은 데이터베이스 정규화를 통해 &lt;code&gt;salt&lt;/code&gt; 를 따로 저장한다.&lt;/li&gt;
&lt;li&gt;원하는 다른 방법으로 저장한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;h3&gt;bcrypt 예시&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;비밀번호 생성&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import * as bcrypt from &amp;quot;bcryptjs&amp;quot;;

const salt = bcrypt.genSaltSync(10);

const hash = bcrypt.hashSync(&amp;quot;비밀번호&amp;quot;, salt);

// 해싱된 비밀번호 == hash 를 데이터베이스에 입력&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;비밀번호 비교&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import * as bcrypt from &amp;quot;bcryptjs&amp;quot;

const comparePassword = &amp;quot;비밀번호&amp;quot;;

const hashedPassword = &amp;quot;?????&amp;quot;; // 정체를 모르는 다채로운 문자들 예시

bcrypt.compare(&amp;quot;비밀번호&amp;quot;, hashedPassword, (err, res) =&amp;gt; {
  // 비밀번호가 같다면, 여기서 처리 
  // 함수 내부에서 사용 할 거면, await bcrypt.compare(..) 로 사용하는 것이 편하다
})
&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h3&gt;scrypt 예시&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;비밀번호 생성&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import {randomBytes, scrypt, scryptSync} from &amp;#39;node:crypto&amp;#39;;

// randomBytes 는 여러 외부 엔트로피 요인과 결합되어 랜덤한 데이터를 배출한다.
const salt = randomBytes(8).toString(&amp;quot;hex&amp;quot;);

// 방법 1
scrypt(&amp;quot;비밀번호&amp;quot;, salt, 64, (err, derivedKey) =&amp;gt; {
  if(err)
    throw err;

  console.log(derivedKey.toString(&amp;quot;hex&amp;quot;));

  const resultPassword = derivedKey.toString(&amp;quot;hex&amp;quot;) + &amp;quot;.&amp;quot; + salt;
  // 비밀번호 저장
});

// 방법 2
const hash = scryptSync(&amp;quot;비밀번호&amp;quot;, salt, 64).toString(&amp;quot;hex&amp;quot;);

console.log(hash);
const resultPassword = derivedKey.toString(&amp;quot;hex&amp;quot;) + &amp;quot;.&amp;quot; + salt;
// 비밀번호 저장 &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;비밀번호 비교&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import {scrypt} from &amp;quot;node:crypto&amp;quot;

const password = &amp;quot;비교될 비밀번호&amp;quot;;

const hashedPwd = &amp;quot;?????????.같이 저장되었던 salt&amp;quot;

// 뒤에 저장된 캐시 문자열만 가져오기
const salt = hashedPwd.split(&amp;quot;.&amp;quot;)[1];

const pwdHash : string = scryptSync(&amp;quot;비교될 비밀번호&amp;quot;, salt, 64).toString(&amp;quot;hex&amp;quot;);

// 동일한 문자열 데이터라면 true, 아니라면 false.
const isSame : boolean = pwdHash === hashedPwd&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;bcrypt 방식과, node 엔진에 탑재된 scrypt 방식은 조금 다르다. 이에 대해 알아보자!&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;bcrypt(bcryptjs) 에 대한 설명&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;bcrypt&lt;/code&gt; 방식은 &lt;code&gt;scrypt&lt;/code&gt; 방식보다는 생성과 비교가 매우 간편하다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;salt&lt;/code&gt; 도 자동으로 생성해주고, 해싱된 패스워드 내부에 암호화 알고리즘과 &lt;code&gt;salt&lt;/code&gt; 가 들어 있다.&lt;/p&gt;
&lt;p&gt;여기서 지정하는 &lt;code&gt;round&lt;/code&gt; 는 $ 2^N $ 에서 &lt;code&gt;N&lt;/code&gt; 을 의미한다. 해당 수 만큼 라운딩 된 결과를 내뱉는다는 것이다.&lt;/p&gt;
&lt;p&gt;그리고, &lt;code&gt;bcrypt&lt;/code&gt; 는 내부에 암호화 알고리즘과 &lt;code&gt;salt&lt;/code&gt; 를 동봉하기 때문에, &lt;/p&gt;
&lt;p&gt;제공하는 메서드로 곧바로 결과를 알 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;nodejs 의 crypto 라이브러리에 대한 설명&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;bcrypt&lt;/code&gt; 보다는 조금 low 한 방식이다. 그래도 이것도 편한 방식이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;node:crypto&lt;/code&gt; 라는 엔진에 탑재된 라이브러리에서 &lt;code&gt;ramdomBytes&lt;/code&gt;, &lt;code&gt;scrypt&lt;/code&gt; 메서드를 가져온다.&lt;/p&gt;
&lt;p&gt;두 메서드 모두, &lt;code&gt;Buffer&lt;/code&gt; 라는 데이터 배열을 반환한다.&lt;/p&gt;
&lt;p&gt;이 때문에, 꼭 &lt;code&gt;toString(&amp;quot;hex&amp;quot;)&lt;/code&gt; 를 통해 문자열 변환을 해 주어야 한다.&lt;/p&gt;
&lt;p&gt;그리고, &lt;code&gt;bcrypt&lt;/code&gt; 처럼 자체적인 &lt;code&gt;compare&lt;/code&gt; 메서드는 없기 때문에,&lt;/p&gt;
&lt;p&gt;사용자가 저장해 둔 &lt;code&gt;salt&lt;/code&gt; 를 다시 가져와서, 비교 할 패스워드를 동일하게 해싱한다.&lt;/p&gt;
&lt;p&gt;저장된 해싱 비밀번호와, 비교할 해싱 비밀번호가 동일하면, 입력된 비밀번호가 동일하다는 이야기이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;배운 것&lt;/h2&gt;
&lt;p&gt;프로젝트를 시작하고, 백엔드 어플리케이션을 구현한 기억이 난다.&lt;/p&gt;
&lt;p&gt;그 때에는 &amp;quot;빠르게 구현&amp;quot; 이라는 키워드에 붙잡혔기 때문에, &lt;/p&gt;
&lt;p&gt;팀원이 &lt;code&gt;bcrypt&lt;/code&gt; 로 사용하라는 의견에 동참하여 가져다 썼다. 동작하기만 하면 되었기 때문이다.&lt;/p&gt;
&lt;p&gt;하지만, 마음속에서는 &lt;strong&gt;bcrypt 는 어떻게 비밀번호를 바로 검증하지?&lt;/strong&gt; 라는 의문이 들었었다.&lt;/p&gt;
&lt;p&gt;왜냐면 &lt;code&gt;salt&lt;/code&gt; 를 데이터베이스에 저장하는 로직이 없었기 때문이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;bcrypt&lt;/code&gt; 의 최종 해싱 비밀번호의 형태는 &lt;/p&gt;
&lt;p&gt;&lt;code&gt;$[알고리즘]$[비용]$[salt][hash]&lt;/code&gt; 이다.&lt;/p&gt;
&lt;p&gt;즉, 이미 bcrypt 는 비밀번호를 생성 할 때, 추후 비교를 위해서 &lt;/p&gt;
&lt;p&gt;&lt;code&gt;salt&lt;/code&gt; 를 비밀번호 안에 넣어놨었던 것이다.&lt;/p&gt;
&lt;p&gt;그렇기 때문에, &lt;code&gt;compare&lt;/code&gt; 메서드 구현시 &lt;code&gt;salt&lt;/code&gt; 를 가져올 필요가 없었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;요즘에 들어서야 항상 궁금한 것이 생기면, 누군가도 이것을 궁금할 것이라고 생각하여 블로그 글을 쓴다.&lt;/p&gt;
&lt;p&gt;단순하게 배운 것이 아니라, 이해하는 것이 제일 중요하다고 생각한다.&lt;/p&gt;
&lt;p&gt;포스팅 할 때 오래 걸리는 주제도 있고, 하루가 걸리는 주제도 있지만,&lt;/p&gt;
&lt;p&gt;항상 느끼는 것은, 내가 발전하고 있다는 것이다.&lt;/p&gt;
&lt;p&gt;개발자가 아닌, 개발 엔지니어가 되도록 항상 노력해야 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;h2&gt;참고 사이트&lt;/h2&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://nodejs.org/api/crypto.html#nodecrypto-module-methods-and-properties&quot;&gt;https://nodejs.org/api/crypto.html#nodecrypto-module-methods-and-properties&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://nodejs.org/api/crypto.html#cryptorandombytessize-callback&quot;&gt;https://nodejs.org/api/crypto.html#cryptorandombytessize-callback&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://nodejs.org/api/crypto.html#cryptoscryptpassword-salt-keylen-options-callback&quot;&gt;https://nodejs.org/api/crypto.html#cryptoscryptpassword-salt-keylen-options-callback&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://velog.io/@gth1123/bcrypt&quot;&gt;https://velog.io/@gth1123/bcrypt&lt;/a&gt;&lt;/p&gt;</description>
      <category>Node.js/잡다 지식</category>
      <category>Bcrypt</category>
      <category>Crypto</category>
      <category>node:crypto</category>
      <category>nodejs</category>
      <category>password</category>
      <category>randombytes</category>
      <category>scrypt</category>
      <category>비밀번호</category>
      <category>비밀번호 구현</category>
      <category>비밀번호 해싱</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/204</guid>
      <comments>https://codecreature.tistory.com/204#entry204comment</comments>
      <pubDate>Tue, 15 Apr 2025 17:04:03 +0900</pubDate>
    </item>
    <item>
      <title>NestJS 의 Interceptor 는 무엇일까? - (NestInterceptor)</title>
      <link>https://codecreature.tistory.com/203</link>
      <description>&lt;h2&gt;제목 : NestJS 의 Interceptor&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;프로그래머스 풀스택 부트캠프에서 팀 프로젝트를 진행했을 때,&lt;/p&gt;
&lt;p&gt;Express 와 NestJS 의 선택지가 존재했는데,&lt;/p&gt;
&lt;p&gt;나는 Spring CRUD 정도는 경험 해 본 지라,&lt;/p&gt;
&lt;p&gt;NestJS 에서의 데코레이터를 통한 메타 프로그래밍 방식을 익히고,&lt;/p&gt;
&lt;p&gt;파일과 코드 컨벤션을 통한 팀 프로젝트 협업을 배우고자 하였다.&lt;/p&gt;
&lt;p&gt;특히, Express 의 경우 디렉토리 구조, 클라이언트와 컨트롤러 사이의 로직이 너무나 자유로워&lt;/p&gt;
&lt;p&gt;팀 프로젝트에서 큰 혼란을 줄 수도 있다는 판단이 들었기 때문에 NestJS 를 선택했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;아예 NestJS 를 처음 사용해 보기 때문에, 공식문서와 팀원분의 코드를 읽으며 빠르게 구현하는 법을 학습했다.&lt;/p&gt;
&lt;p&gt;하지만, 모든 프레임워크가 그렇듯, 구현을 우선시하다가 이해를 넘겨버리게 되었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;지금에 와서, 나는 새로이 udemy NestJS 강의를 들으며 숙련도를 높이고 있다.&lt;/p&gt;
&lt;p&gt;그리고, 궁금한 것은 모두 직접 조사 및 공부하여 포스팅하고 있다.&lt;/p&gt;
&lt;p&gt;그렇다면, 왜 NestJS 의 interceptor 를 포스팅하냐 하면,&lt;/p&gt;
&lt;p&gt;팀 프로젝트 당시 인터셉터로 나눌 수 있었던 비즈니스 로직을 전부 Middleware 로 만들었기 때문이다.&lt;/p&gt;
&lt;p&gt;물론 대부분의 기능을 미들웨어로 만들 수 있었지만, 쉽게 로직을 추가하기 위해 인터셉터를 사용했으면&lt;/p&gt;
&lt;p&gt;더 좋지 않았을까 생각이 든다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;NestJS Interceptor 와 AOP 의 관계&lt;/h3&gt;
&lt;p&gt;NestJS 는 AOP (Aspect Oriented Programming) 을 지원하는 인터셉터를 만들었다.&lt;/p&gt;
&lt;p&gt;우선, 요청을 검수할 수 있는 다른 기능들과 달리,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;NestInterceptor&lt;/code&gt; 는 클라이언트와 요청 사이, 클라이언트와 응답 사이&lt;/p&gt;
&lt;p&gt;이 두 개의 로직을 추가하거나, 세분화 할 수 있다는 장점이 있다.&lt;/p&gt;
&lt;p&gt;그래서 AOP 라는 개념을 어떻게 적용시킬까?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AOP&lt;/strong&gt; (Aspect Oriented Programming) &lt;/p&gt;
&lt;p&gt;프로그래밍 기술이 발달함에 따라, 단순히 웹과 서버, 데이터베이스 서버를 관리하는 것이 아니라,&lt;/p&gt;
&lt;p&gt;프로그래밍 과정에서 나오는 산출물이 매우 중요해지는 시기가 이미 왔다.&lt;/p&gt;
&lt;p&gt;이러한 산출물들을 &amp;quot;어떤 시점으로 바라보냐&amp;quot; 에 따라서, 프로그램 코드가 추가되는 것을&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AOP&lt;/strong&gt; 라고 부르지 않나 생각된다.&lt;/p&gt;
&lt;p&gt;밑은 AOP 의 예시다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 고객이 요청을 했는데, 특정 비즈니스 구간에서 느려지는 경향이 있다고 보고한다&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;그렇다면, 우리가 제공하고 있는 API 들의 수행 시간을 모두 검사 해 보자.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. 고객이 어떤 기능을 자주 사용하는지 알기 위해 외부로 데이터를 로깅 할 수 있도록 송출해야겠다&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;글로벌(전역) 혹은, 어떠한 컨트롤러, 혹은 메서드에 직접 적용하여 중간에 로직을 추가해야겠다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. 어떠한 요청 혹은 다수를 처리하는 데 있어 일정 시간이 지난다면, 특정 에러를 반환하고 싶다&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;그렇다면 응답 구간에 특정 로직을 추가해야겠다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;한번 내가 상상 해 본 AOP 의 예시들이다.&lt;/p&gt;
&lt;p&gt;우리는 프로그램에 로직을 추가하거나, 최적화 하기 위해 다양한 관점으로 프로그램을 볼 수 밖에 없다.&lt;/p&gt;
&lt;p&gt;기존의 방향성이 옳은지, 혹은 틀린지를 판단하기 위해서라도 시각을 달리 볼 수 밖에 없다고 생각한다.&lt;/p&gt;
&lt;p&gt;그리고, 로직의 어떤 부분, 과정에 넣을지도 선택 할 수 있다.&lt;/p&gt;
&lt;p&gt;요청, 응답 둘 다 처리 하는 것인지, 혹은 둘 중 하나만을 검사하고 처리하는 것인지도 선택할 수 있다.&lt;/p&gt;
&lt;p&gt;그리고 특히, 이미 만들어진 로직에 핵심적인 영향을 주지 않는다는 것이 중요하다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;따라서, NestInterceptor 의 개념과 사용법을 파헤쳐 볼 이유는 충분하다고 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;NestInterceptor 와 RxJS?&lt;/h3&gt;
&lt;p&gt;우선, &lt;code&gt;NestInterceptor&lt;/code&gt; 는 &lt;code&gt;RxJS&lt;/code&gt; 라이브러리와 상호작용한다.&lt;/p&gt;
&lt;p&gt;단순히 구현만 하다보면, 그냥 프레임워크에 잡아먹힌다.&lt;/p&gt;
&lt;p&gt;특히나 외부 모듈에 굉장히 의존적인 Node.js 의 특성상, 하나를 놓치면 나머지는 잡을 수도 없다.&lt;/p&gt;
&lt;p&gt;그래서, &lt;code&gt;RxJS&lt;/code&gt; 에 대해서 짚고 넘어가기로 결정했다.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://rxjs.dev/guide/overview&quot;&gt;RxJS 문서&lt;/a&gt; 를 참조하여 작성한다. (내 지식과 의견도 함께)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;아... RxJS 의 공식문서를 보았는데, RxJS 는 단순히 내가 작성하고 있는 Article 의 일부분으로서만 &lt;/p&gt;
&lt;p&gt;작성하기에는 굉장히 다양한 사용법과, 응용법이 존재했다.&lt;/p&gt;
&lt;p&gt;따라서, RxJS 의 기본 Philosophy 를 다루며,&lt;/p&gt;
&lt;p&gt;간단한 예제를 통한 의미를 살펴 볼 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;RxJS 의 관찰자(Observer) 와 사용법&lt;/h3&gt;
&lt;p&gt;NestJS 에서 &lt;code&gt;NestInterceptor&lt;/code&gt; 를 사용하기 위해서는 &lt;code&gt;rxjs&lt;/code&gt; 를 사용해야 한다.&lt;/p&gt;
&lt;p&gt;즉, 내부 프레임워크에서 자체적으로 인터셉터를 모두 구현 해 놓은 것이 아니라, &lt;code&gt;rxjs&lt;/code&gt; 라이브러리와&lt;/p&gt;
&lt;p&gt;상호작용하는 것이다.&lt;/p&gt;
&lt;p&gt;Example : &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import { CallHandler, ExecutionContext, NestInterceptor } from &amp;#39;@nestjs/common&amp;#39;;
import { Observable } from &amp;#39;rxjs&amp;#39;;

export class DeleteInterceptor implements NestInterceptor {
  intercept(
    context: ExecutionContext,
    next: CallHandler&amp;lt;any&amp;gt;,
  ): Observable&amp;lt;any&amp;gt; | Promise&amp;lt;Observable&amp;lt;any&amp;gt;&amp;gt; {
    return next.handle();
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이것이 &lt;code&gt;NestInterceptor&lt;/code&gt; 의 기본 골자이다.&lt;/p&gt;
&lt;p&gt;보면, 인터페이스를 구현하는 과정에서, 반드시 &lt;code&gt;rxjs&lt;/code&gt; 의 라이브러리의 객체인 &lt;code&gt;Observable&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;을 반환하고 있다. &lt;/p&gt;
&lt;p&gt;인터셉터는 로직을 끼우거나 커스텀 할 수 있다면서, 무엇인지도 모를 &lt;code&gt;Observable&lt;/code&gt; 을 반환하고 있는가?&lt;/p&gt;
&lt;p&gt;이에 대해서 알아볼 시간이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;나는 프레임워크가 왜 외부 라이브러리인 &lt;code&gt;rxjs&lt;/code&gt; 를 사용하는지 이해가 되지 않았다.&lt;/p&gt;
&lt;p&gt;특히나, 인터셉터라는 Nest 자신만의 객체를 외부 라이브러리의 힘까지 가지고 와서 구현 할 필요가 있었을까?&lt;/p&gt;
&lt;p&gt;왜 그랬는지 알아내기 위해 직접 공식문서에서 &lt;code&gt;rxjs&lt;/code&gt; 의 사용법을 보기 시작했다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;rxjs 공식문서 예시&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// 순수 JS
document.addEventListener(&amp;quot;click&amp;quot;, () =&amp;gt; console.log(&amp;quot;Clicked&amp;quot;));

// RxJS 사용하는 JS
import {fromEvent} from &amp;quot;rxjs&amp;quot;;
fronEvent(document, &amp;quot;click&amp;quot;).subscribe(() =&amp;gt; console.log(&amp;quot;Clicked&amp;quot;));&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 예시는 &lt;code&gt;DOM&lt;/code&gt; (Document Object Model) 에 탑재된 &amp;quot;기본 이벤트&amp;quot; 를 이용한 예제이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;document&lt;/code&gt; 객체는 &lt;code&gt;DOM&lt;/code&gt; 으로서, 유저 interaction(클릭) 에 대한 정보를 가지고 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;RxJS&lt;/code&gt; 에서 보여주고자 한 것은, &lt;code&gt;DOM&lt;/code&gt; 오브젝트가 가지고 있는 기본 이벤트 리스너를 이용하여&lt;/p&gt;
&lt;p&gt;특정 이벤트가 실행되었을 때 실행 할 함수를 Callback 함수로 지정 해 놓은 것이다.&lt;/p&gt;
&lt;p&gt;즉, 특정 이벤트를 &amp;quot;구독&amp;quot;(subscribe) 한다는 것이다.&lt;/p&gt;
&lt;p&gt;하지만, NestJS 의 오브젝트는 &lt;code&gt;DOM&lt;/code&gt; 오브젝트는 아니므로, 좀 더 탐구하기로 했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;RxJS 공식사이트 예제 2&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// 순수 JS
let count = 0;
document.addEventListener(&amp;#39;click&amp;#39;, () =&amp;gt; console.log(`Clicked ${++count} times`));

// RxJS
import { fromEvent, scan } from &amp;#39;rxjs&amp;#39;;

fromEvent(document, &amp;#39;click&amp;#39;)
  .pipe(scan((count) =&amp;gt; count + 1, 0))
  .subscribe((count) =&amp;gt; console.log(`Clicked ${count} times`));&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;NestInterceptor&lt;/code&gt; 에서 사용되는 &lt;code&gt;pipe&lt;/code&gt; 에 대한 정보를 볼 수 있다.&lt;/p&gt;
&lt;p&gt;그렇다면, 위에서 보여주고자 하는 정보를 요약 해 보자.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;pipe(...)&lt;/code&gt; 를 통해 해당 이벤트가 일어났을 때, &lt;br/&gt; &lt;code&gt;subscribe&lt;/code&gt; 에 전달 해 주기 위한 결과물을 특정 처리한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;subscribe&lt;/code&gt; 를 통해, 내부에 저장된 &lt;code&gt;count&lt;/code&gt; 를 이용하여 콜백 함수를 실행한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;count&lt;/code&gt; 는 &lt;code&gt;scan&lt;/code&gt; 함수를 통해 저장된 변수이며, 이는 리액트의 커스텀 디스패치와 유사하다.&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;p&gt;그렇다면, NestJS 의 인터셉터와 비교하기 전에, 마지막으로 rxjs 의 &lt;code&gt;map&lt;/code&gt; 을 알아보자.&lt;/p&gt;
&lt;p&gt;여타 다른 언어와 비슷하게, &lt;code&gt;map&lt;/code&gt; 은 기존 객체 혹은 변수의 값들을 특정 조건에 의해 변환하는 데 사용한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;EX&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import { fromEvent, map } from &amp;#39;rxjs&amp;#39;;

const clicks = fromEvent&amp;lt;PointerEvent&amp;gt;(document, &amp;#39;click&amp;#39;);
const positions = clicks.pipe(map(ev =&amp;gt; ev.clientX));

positions.subscribe(x =&amp;gt; console.log(x));&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그런데, &lt;code&gt;fromEvent&lt;/code&gt; 를 살펴보면 알 수 있는 것은,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;quot;click&amp;quot;&lt;/code&gt; 이라는 이벤트에 대해서 구독을 신청 한 것은 &lt;code&gt;document&lt;/code&gt; 객체 하나뿐이다.&lt;/p&gt;
&lt;p&gt;즉, &lt;code&gt;&amp;quot;click&amp;quot;&lt;/code&gt; 이라는 이벤트가 일어났을 때, &lt;/p&gt;
&lt;p&gt;&lt;code&gt;clicks&lt;/code&gt; 이벤트 객체 &lt;code&gt;ev&lt;/code&gt; 에서 &lt;code&gt;clientX&lt;/code&gt; 로 꺼내 &lt;code&gt;Observable&lt;/code&gt; 타입으로 래핑하는 것이다.&lt;/p&gt;
&lt;p&gt;그리고 나서, 마지막 줄인 &lt;code&gt;subscribe&lt;/code&gt; 에서 &lt;code&gt;x&lt;/code&gt; 는 매핑된 &lt;code&gt;ev.clientX&lt;/code&gt; 를 출력하게 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;NestJS 의 인터셉터와 비교 해 보자.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import { CallHandler, ExecutionContext, NestInterceptor } from &amp;#39;@nestjs/common&amp;#39;;
import { Observable } from &amp;#39;rxjs&amp;#39;;

export class DeleteInterceptor implements NestInterceptor {
  intercept(
    context: ExecutionContext,
    next: CallHandler&amp;lt;any&amp;gt;,
  ): Observable&amp;lt;any&amp;gt; | Promise&amp;lt;Observable&amp;lt;any&amp;gt;&amp;gt; {
    return next.handle();
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;context&lt;/code&gt; 는 여기서 사용되지 않는다. &lt;code&gt;next&lt;/code&gt; 에 신경쓰면 된다.&lt;/p&gt;
&lt;p&gt;그런데, 재밌는 것은, &lt;code&gt;context&lt;/code&gt; 와 &lt;code&gt;next&lt;/code&gt; 둘 다 타입으로만 선언되어 있다.&lt;/p&gt;
&lt;p&gt;즉, &lt;code&gt;NestInterceptor&lt;/code&gt; 라고 선언하는 것 자체가, 형식에 맞춘 객체를 생성한다고 생각하면 된다.&lt;/p&gt;
&lt;p&gt;이는 이따가 설명하겠다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;중요 한 것은, &lt;code&gt;next&lt;/code&gt; 가 응답 스트림에 반응한다는 것이다.&lt;/p&gt;
&lt;p&gt;그리고, &lt;code&gt;next.handle()&lt;/code&gt; 은 &lt;code&gt;Observable&lt;/code&gt; 을 반환한다.&lt;/p&gt;
&lt;p&gt;NestJS 는 &lt;code&gt;rxjs&lt;/code&gt; 라이브러리의 타입을 반환한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;요약하자면&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;next&lt;/code&gt; 는 응답 스트림에 반응하는 타입이다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;handle()&lt;/code&gt; 은 그런 응답 스트림에 반응하는 &lt;code&gt;Observable&lt;/code&gt; 을 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;그렇다면, &lt;code&gt;rxjs&lt;/code&gt; 는 NestJS 의 타입 지원과 함께, 응답 스트림에 반응하며,&lt;/p&gt;
&lt;p&gt;실제 응답 시 반환되는 데이터를 어떻게 처리 할 것인지 만들 수 있는 것이다.&lt;/p&gt;
&lt;p&gt;그런데, NestInterceptor 를 실제로 구성하는 로직은 찾을 수 없었다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;여기서 드는 의문점 : NestInterceptor 는 도대체 무슨 객체지?&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;context : ExecutionContext&lt;/code&gt; 와, &lt;code&gt;next : CallHandler&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;이 둘의 실제 로직을 살펴보기 위해 정의 파일을 뒤졌다.&lt;/p&gt;
&lt;p&gt;그런데, 이들은 &lt;code&gt;interface&lt;/code&gt; 나 &lt;code&gt;type&lt;/code&gt; 으로 구성되어 있을 뿐,&lt;/p&gt;
&lt;p&gt;실제로 컨텍스트를 조작하거나, 응답에 반응할 수 있는 로직을 반환하지 않았다.&lt;/p&gt;
&lt;p&gt;그렇다면 도대체 &lt;code&gt;NestInterceptor&lt;/code&gt; 는 무엇인가? &lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;나는 잠시 잊고 있었던 NestJS 프로젝트의 구성 과정을 떠올렸다.&lt;/p&gt;
&lt;p&gt;NestJS 는 메타 프로그래밍 언어로 구성된다는 걸.&lt;/p&gt;
&lt;p&gt;즉, &lt;code&gt;NestInterceptor&lt;/code&gt; 를 &lt;code&gt;implements&lt;/code&gt; 한다는 것은,&lt;/p&gt;
&lt;p&gt;NestJS 가 인터셉터로서 로직을 수행하기 위해,&lt;/p&gt;
&lt;p&gt;알맞은 타입을 입력하여 로직을 구성하고,&lt;/p&gt;
&lt;p&gt;NestJS 는 데코레이터를 통해 함수나 클래스의 메타데이터를 읽은 후,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;context&lt;/code&gt; 와 &lt;code&gt;next&lt;/code&gt; 에 걸맞는 데이터를 주입한다는 것이었다.&lt;/p&gt;
&lt;p&gt;이 때, &lt;code&gt;@UseInterceptor()&lt;/code&gt; 를 사용한다면,&lt;/p&gt;
&lt;p&gt;괄호 내부에 들어간 인터셉터들이 &lt;code&gt;MethodDecorator&lt;/code&gt; 혹은 &lt;code&gt;ClassDecorator&lt;/code&gt; 로 변환되어,&lt;/p&gt;
&lt;p&gt;메서드나 클래스 내부의 로직을 중간에 가로채는 것이다.&lt;/p&gt;
&lt;p&gt;이 과정에서, &lt;code&gt;rxjs&lt;/code&gt; 를 사용하여 요청 스트림, 응답 스트림을 가로챌 수 있게 만들어 둔 것이다.&lt;/p&gt;
&lt;p&gt;여기까지 이해하는 데 조금 시간이 걸렸다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;요약하여,&lt;/strong&gt; &lt;code&gt;NestInterceptor&lt;/code&gt; 는 요청, 응답 스트림 중간에 정보를 변환할 수 있는,&lt;/p&gt;
&lt;p&gt;일종의 메서드 데코레이터, 클래스 데코레이터라는 것이다.&lt;/p&gt;
&lt;p&gt;우리는 NestJS 에서 주입하는 정보의 형태를 맞추기 위해 &lt;code&gt;NestInterceptor&lt;/code&gt; 를 &lt;code&gt;implements&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;하는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;자, 이제 구현을 해 보자.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;&lt;code&gt;NestInterceptor&lt;/code&gt; 사용법&lt;/h3&gt;
&lt;p&gt;반환의 개념은 위에서 먼저 보여주었으니, &lt;code&gt;context&lt;/code&gt; 를 의미하는 &lt;code&gt;ExecutionContext&lt;/code&gt; 를 먼저 시작해보자.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ExecutionContext&lt;/code&gt; 는 다루게 될 요청, 응답 객체를 조정할 수 있다.&lt;/p&gt;
&lt;p&gt;그리고, &lt;code&gt;NestInterceptor&lt;/code&gt; 가 사용된 메서드 이름, 그리고 클래스 타입도 알 수 있다.&lt;/p&gt;
&lt;p&gt;또한, &lt;code&gt;context&lt;/code&gt; 는 해당 메서드에 할당된 인자를 배열로 가져올 수 있으며,&lt;/p&gt;
&lt;p&gt;index 와 함께 특정 인자만을 가져올 수도 있다.&lt;/p&gt;
&lt;p&gt;이에 대한 메서드나 변형 방식은 &lt;a target=&quot;_blank&quot; href=&quot;https://docs.nestjs.com/fundamentals/execution-context&quot;&gt;NestJS 공식문서 Execution context&lt;/a&gt; 를 참조하면 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;상황 1. 특정 api 들이 요청하고 응답하는 시간을 측정하고 싶다.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;export class TestInterceptor implements NestInterceptor {
  intercept(
    context: ExecutionContext,
    next: CallHandler&amp;lt;any&amp;gt;,
  ) : Observable&amp;lt;any&amp;gt; | Promise&amp;lt;Observable&amp;lt;any&amp;gt;&amp;gt; {
    // 요청 받은 시각을 기록 - ms 측정 단위를 가진 숫자
    const reqTime = Date.now();

    // 결과에 출력할 컨트롤러와 메서드 정보 기록
    const classWithMethod = context.getClass().name + &amp;quot; - &amp;quot; + context.getHandler().name;

    return next.handle().pipe(
      tap(() =&amp;gt; console.log(`${classWithMethod} 요청과 응답 사이의 시간 : ${Date.now() - reqTime} ms`))
    )
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;context&lt;/code&gt; 에 주입된 메타데이터와, 인터페이스가 가지는 고유의 변수 저장을 이용하여&lt;/p&gt;
&lt;p&gt;인터셉터가 적용된 컨트롤러와, 메서드 이름, 현재 시각을 기록하고,&lt;/p&gt;
&lt;p&gt;반환 시 그 때의 시간과 이전 시각의 차이를 이용하여 실제 소비 시간을 기록할 수 있었다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;UsersController - findUser 요청과 응답 사이의 시간 : 8 ms&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;상황 2. 유저가 보낸 요청에 권한이 있는지 확인해야겠다.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import { CallHandler, ExecutionContext, NestInterceptor } from &amp;#39;@nestjs/common&amp;#39;;
import { Observable } from &amp;#39;rxjs&amp;#39;;

export class AuthInterceptor implements NestInterceptor {
  intercept(
    context: ExecutionContext, 
    next: CallHandler&amp;lt;any&amp;gt;
  ): Observable&amp;lt;any&amp;gt; | Promise&amp;lt;Observable&amp;lt;any&amp;gt;&amp;gt; {
    // 요청 받은 시각을 기록 - ms 측정 단위를 가진 숫자
    const httpCtx = context.switchToHttp();

    const request = httpCtx.getRequest&amp;lt;Request&amp;gt;();

    // Redis, 혹은 JWT 토큰을 파싱하여 헤더의 권한을 인증
    console.log(request.headers);

    return next.handle();
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt; : &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;{
  authorization: &amp;#39;user&amp;#39;,
  &amp;#39;user-agent&amp;#39;: &amp;#39;IntelliJ HTTP Client/IntelliJ IDEA 2024.3.5&amp;#39;,
  &amp;#39;accept-encoding&amp;#39;: &amp;#39;br, deflate, gzip, x-gzip&amp;#39;,
  accept: &amp;#39;*/*&amp;#39;,
  host: &amp;#39;localhost:3000&amp;#39;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;간단하게 &lt;code&gt;.http&lt;/code&gt; 파일을 이용하여 전달하였으며,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Authorization&lt;/code&gt; 헤더에 &lt;code&gt;user&lt;/code&gt; 값을 넣었다.&lt;/p&gt;
&lt;p&gt;여기에는 보통 컨벤션으로 &lt;code&gt;Bearer xxxxxx&lt;/code&gt; 를 넣어 전송하지만,&lt;/p&gt;
&lt;p&gt;JWT 페이로드의 의미와 파싱, 그리고 암호화 부분은 여기서 다룰 부분은 아니라 생각하여 &lt;code&gt;user&lt;/code&gt; 로 대체했다.&lt;/p&gt;
&lt;p&gt;먼저, &lt;code&gt;ExecutionContext&lt;/code&gt; 는 &lt;code&gt;http&lt;/code&gt; 객체로 바꾸는데, &lt;/p&gt;
&lt;p&gt;여기서 &lt;code&gt;Express&lt;/code&gt; 를 사용했기 때문에, &lt;code&gt;req&lt;/code&gt;, &lt;code&gt;res&lt;/code&gt; 를 얻을 수 있다.&lt;/p&gt;
&lt;p&gt;들어온 요청에 대한 &lt;code&gt;Headers&lt;/code&gt; 혹은 &lt;code&gt;Cookies&lt;/code&gt; 를 가져올 수 있다.&lt;/p&gt;
&lt;p&gt;사용자의 보안 인가 및 인증 절차는 프로그램의 요구 사항에 따라 매우 달라진다.&lt;/p&gt;
&lt;p&gt;이를 감안하여 보면 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;상황 3. 해당 api 요청이 5 초 넘게 걸린다면, 에러를 즉시 내보낸다.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;export class TimeoutInterceptor implements NextInterceptor {
  intercept(
    context: ExecutionContext, 
    next: CallHandler&amp;lt;any&amp;gt;
  ): Observable&amp;lt;any&amp;gt; | Promise&amp;lt;Observable&amp;lt;any&amp;gt;&amp;gt; {
    return next.handle()
      .pipe(
        // 인터셉터가 적용되는 api 들은 5 초의 제한시간을 가진다.
        timeout(5000),
        // 만약에 에러가 발견되었을 때,
        catchError(err =&amp;gt; {
          // 던져진 에러가 &amp;quot;TimeoutError&amp;quot; 객체의 인스턴스라면,
          if(err instanceof TimeoutError) {
            // 클라이언트에게 요청시간 예외를 보낸다.
            return throwError(() =&amp;gt; new RequestTimeoutException());
          }
          // 사실상 else --&amp;gt; 그렇지 않은 에러라면, 그대로 예외를 내보낸다.
          return throwError(() =&amp;gt; err);
        })
      )
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 인터셉터는 &lt;code&gt;RxJS&lt;/code&gt; 의 &lt;code&gt;catchError&lt;/code&gt; 를 이용하여 에러를 처리하는데,&lt;/p&gt;
&lt;p&gt;시간에 대한 에러만 특별히 처리하고, 그렇지 않은 에러는 그대로 반환한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;배운 점&lt;/h2&gt;
&lt;p&gt;강의를 듣다가, 클라이언트와 서버 사이의 로직을 처리하는 인터셉터의 원리가 궁금하여 파헤쳐 보았다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;NestInterceptor&lt;/code&gt; 라는 형태가 존재하기에, 이는 따로 객체 형태로 존재하여 구현된 줄 알았다.&lt;/p&gt;
&lt;p&gt;하지만, 이는 메타프로그래밍 언어로서의 NestJS 가 의존성 주입을 위해 어떠한 형태를 잡아놓은 것이었다.&lt;/p&gt;
&lt;p&gt;즉, &lt;code&gt;NestInterceptor&lt;/code&gt; 는 클래스가 아니고, 인터페이스이다.&lt;/p&gt;
&lt;p&gt;그것도, &lt;code&gt;interceptor()&lt;/code&gt; 라는 메서드를 가지며, &lt;code&gt;rxjs&lt;/code&gt; 의 &lt;code&gt;Observable&lt;/code&gt; 을 반환하는 인터페이스.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그렇다면, 인터페이스는 &lt;code&gt;interceptor&lt;/code&gt; 메서드 내부 인자가 &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ExecutionContext&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CallHandler&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 둘을 가지는데, 이 값은 어떻게 인지되는가? 하면,&lt;/p&gt;
&lt;p&gt;이 또한 모두 인터페이스로서 구현 된 것이다.&lt;/p&gt;
&lt;p&gt;실질적으로, 우리는 NestJS 에서 제공한 &lt;code&gt;NestInterceptor&lt;/code&gt; 의 실질적인 로직(클래스)를 제작 한 것이 아니라,&lt;/p&gt;
&lt;p&gt;데코레이터 및 컨테이너 IoC 에 메타정보를 주입하기 위한 어떠한 틀을, 다시 NestJS 에게 제공 한 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그리고 또한 배운 것은, NestJS 는 &lt;code&gt;interceptor&lt;/code&gt;, &lt;code&gt;filter&lt;/code&gt;, &lt;code&gt;guard&lt;/code&gt; 와 같은 컴포넌트들을&lt;/p&gt;
&lt;p&gt;단순히 내부적인 프레임워크에서 독립적으로 처리하지 않고, &lt;code&gt;rxjs&lt;/code&gt; 라는 외부 라이브러리와 깊이 연동시켜 놓았다.&lt;/p&gt;
&lt;p&gt;물론, &lt;code&gt;rxjs&lt;/code&gt; 의 요청, 응답 스트림에 반응하여 데이터를 조작할 수 있는 것은 좋지만,&lt;/p&gt;
&lt;p&gt;기본적인 &lt;code&gt;rxjs&lt;/code&gt; 라이브러리의 작성 형태를 알아야 한다는 말이기도 하다.&lt;/p&gt;
&lt;br/&gt; 

&lt;h3&gt;간단한 RxJS 의 사용법 - 진짜 단순하게&lt;/h3&gt;
&lt;p&gt;RxJS 는 스스로 구독하거나, 감시 할 수 있는 객체인 &lt;code&gt;Observable&lt;/code&gt; 에 &lt;/p&gt;
&lt;p&gt;로직을 끼워넣거나, 추가 이벤트를 넣을 수 있다.&lt;/p&gt;
&lt;p&gt;그리고, 이러한 &lt;code&gt;Observable&lt;/code&gt; 을 처음 만들기 위해서는,&lt;/p&gt;
&lt;p&gt;일반 객체는 &lt;code&gt;EventTarget&lt;/code&gt; 을 계승하거나, 그 자체여야 한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const {fromEvent} = require(&amp;quot;rxjs&amp;quot;)

const ob = new EventTarget();

ob.addEventListener(&amp;quot;test-event&amp;quot;, () =&amp;gt; {
  console.log(&amp;quot;테스트 이벤트 작동&amp;quot;);
})

fromEvent(ob, &amp;quot;test-event&amp;quot;).subscribe(
  (event) =&amp;gt; {
    console.log(&amp;quot;rxjs 가 &amp;#39;test-event&amp;#39; 에 따라 이벤트 발동&amp;quot;);
    console.log(event);
  }
)

const ev = new Event(&amp;#39;test-event&amp;#39;);

ob.dispatchEvent(ev);

console.log(ev);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt; : &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ node rxjs-exam-1.js
테스트 이벤트 작동 # ob 에 이미 등록해 둔 기본 이벤트 action
rxjs 가 &amp;#39;test-event&amp;#39; 에 따라 이벤트 발동 # rxjs 를 이용하여 구독 해 둔 &amp;quot;test-event&amp;quot; 에 반응하여 발동
Event { # `Observable` 이 내부 콜백 함수에 전달해 주는 event 값을 출력
  type: &amp;#39;test-event&amp;#39;,
  defaultPrevented: false,
  cancelable: false,
  timeStamp: 101.452125
}
Event { # JS 의 실제 `Event` - `Observable` 에서 다뤄지는 이벤트와 동일하다.
  type: &amp;#39;test-event&amp;#39;,
  defaultPrevented: false,
  cancelable: false,
  timeStamp: 105.842542
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;JS 의 실제 &lt;code&gt;Event&lt;/code&gt; 와 동일한 속성을 가지고 있는 것을 확인 할 수 있다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;참고 사이트&lt;/h2&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://docs.nestjs.com/interceptors&quot;&gt;https://docs.nestjs.com/interceptors&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://rxjs.dev/guide/overview&quot;&gt;https://rxjs.dev/guide/overview&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;</description>
      <category>Node.js/잡다 지식</category>
      <category>callhandler</category>
      <category>executionContext</category>
      <category>intercept</category>
      <category>interceptor</category>
      <category>nestinterceptor</category>
      <category>nestjs</category>
      <category>nestjs 인터셉터</category>
      <category>RxJS</category>
      <category>인터셉터</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/203</guid>
      <comments>https://codecreature.tistory.com/203#entry203comment</comments>
      <pubDate>Sat, 12 Apr 2025 13:19:01 +0900</pubDate>
    </item>
    <item>
      <title>WebAssembly 와 Node.js</title>
      <link>https://codecreature.tistory.com/202</link>
      <description>&lt;h2&gt;제목 : 웹 어셈블리와 Node.js&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;Node.js 에서 싱글스레드 기반으로 운용되는 Node.js 환경을 멀티스레딩 환경으로 만들어,&lt;/p&gt;
&lt;p&gt;CPU 집약 비즈니스를 나눌 수 있는 방식을 탐색 했다.&lt;/p&gt;
&lt;p&gt;이후, 더 최적화 할 수 있는 방식을 연구하며&lt;/p&gt;
&lt;p&gt;Web Assembly 라는 것을 알게 되었다. 이 글은 밑의 블로그 포스팅과 이어진다.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/201&quot;&gt;https://codecreature.tistory.com/201&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;갑자기 왜 어셈블리?&lt;/h3&gt;
&lt;p&gt;Node.js 는 현재 웹 서버와 웹 어플리케이션 서버에서 주요하게 사용되고 있다.&lt;/p&gt;
&lt;p&gt;하지만, 아무리 CPU 집약 비즈니스 코드를 나누어 스레드로 새로 생성하더라도,&lt;/p&gt;
&lt;p&gt;새로운 스레드 또한 JavaScript 코드를 해석하기 위해 Node.js 엔진을 사용한다.&lt;/p&gt;
&lt;p&gt;물론, V8 엔진도 훌륭하지만, 기계어가 가깝게 이미 파싱되어 있는 코드보다는 성능이 떨어 질 수 밖에 없다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;여기서 실제 웹과, 백엔드에서 사용할 수 있는 &amp;quot;웹 어셈블리&amp;quot; 라는 개념이 등장한다.&lt;/p&gt;
&lt;p&gt;실제 어셈블리 코드라기보다는, 웹을 위한 어셈블리코드라는 개념으로 &amp;quot;Web Assembly&amp;quot; 라고 부른다.&lt;/p&gt;
&lt;p&gt;여기서 Web Assembly 의 파일 형태를 2 개로 나뉘는데,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.wasm&lt;/code&gt; : 이진 형태의 모듈 &lt;/li&gt;
&lt;li&gt;&lt;code&gt;.wat&lt;/code&gt; : 웹 어셈블리의 코드 텍스트를 가지고 있는 모듈&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;로 나뉜다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;또한, 웹 어셈블리는 다양한 언어로부터 컴파일 될 수 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;C, C++&lt;/li&gt;
&lt;li&gt;Rust&lt;/li&gt;
&lt;li&gt;Go&lt;/li&gt;
&lt;li&gt;AssemblyScript (TypeScript 와 닮은 문법 But 인지도는 낮은듯)&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;-&lt;strong&gt;여기서 잠깐 드는 생각&lt;/strong&gt;-&lt;/p&gt;
&lt;p&gt;Node.js 에서 웹 어셈블리를 사용 할 정도가 되면, 최적화가 많이 필요하다는 것인데,&lt;/p&gt;
&lt;p&gt;어셈블리 모듈을 사용하여 최적화를 할 정도면, 백엔드 프레임워크와 언어 자체를 바꾸는게 맞지 않나? 생각이 든다.&lt;/p&gt;
&lt;p&gt;그래도 NestJS 를 공부하여 팀 프로젝트로 백엔드를 구축 해 봤으므로, &lt;/p&gt;
&lt;p&gt;이미 NestJS 를 이용하여 의존성 모듈 구성 및 프로젝트를 완성했다면, 이러한 기능 또한 필요할 것이다.&lt;/p&gt;
&lt;p&gt;새로운 언어와 프레임워크로 바꾸면서 드는 비용과, 웹 어셈블리를 지속적으로 도입하여 투자하는 비용.&lt;/p&gt;
&lt;p&gt;두 개를 비교했을 때, 어느 한 쪽이 항상 우세하다고 판단하기는 어렵기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;어떤 언어로 웹 어셈블리를 구성하면 좋을까?&lt;/h3&gt;
&lt;p&gt;이건 현재 Node.js 를 배우는 사람에게 달린 문제인 것 같다.&lt;/p&gt;
&lt;p&gt;C 혹은 C++ 을 배운 적이 있다면, C 혹은 C++ 로 하면 된다.&lt;/p&gt;
&lt;p&gt;그리고, Rust 를 공부했다면, Rust 로 하면 된다.&lt;/p&gt;
&lt;p&gt;혹시라도 Node.js 를 먼저 접해 다른 언어를 배워본 적 없다면, AssemblyScript 를 사용하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;del&gt;JavaScript 를 사용하면서 여기까지 구현하고자 한다면, 정말 의지가 대단한 사람이라고 칭찬하고 싶다..&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;사실, JavaScript 는 입문은 매우 간단하지만, C 나 C++ 보다도 훨씬 어렵다고 생각이 든다.&lt;/p&gt;
&lt;p&gt;쉽게 사칙연산을 수행하거나, 함수를 선언 할 수 있으며, 자유도가 높은 것은 당연히 JavaScript 일 것이다.&lt;/p&gt;
&lt;p&gt;하지만, 신뢰성과 최적화를 수행하기는 가장 어려운 언어가 아닌가 생각이 든다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;하지만, 유연한 커뮤니티와 거대한 의존성 레지스트리를 가지고 있는 Node.js 환경에서&lt;/p&gt;
&lt;p&gt;쉽게 다른 언어로 이전하기 어렵다는 것 또한 인지하고 있다.&lt;/p&gt;
&lt;p&gt;따라서, 이번 예제는 웹 어셈블리 변환을 지원하는 언어 중,&lt;/p&gt;
&lt;p&gt;가장 타입스크립트와 유사해 보이는 AssemblyScript 를 사용 해 볼 것이다.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.assemblyscript.org/introduction.html&quot;&gt;AssemblyScript 공식 가이드&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;일단, 우리가 기존에 사용하던 타입이 약간 다르다는 게 보인다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;공식 페이지 가이드 코드&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;export function fib(n: i32): i32 {
  var a = 0, b = 1
  if (n &amp;gt; 0) {
    while (--n) {
      let t = a + b
      a = b
      b = t
    }
    return b
  }
  return a
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 코드는 간단한 피보나치 수열 계산 함수이다.&lt;/p&gt;
&lt;p&gt;다행히 백준에서 10억번째 피보나치 수를 N 으로 나누었을 때의 나머지는 얼마인가에 대해 풀어 본적이 있어서&lt;/p&gt;
&lt;p&gt;위의 코드를 쉽게 이해 할 수 있었다. &lt;del&gt;행렬을 이용해야 했다..&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;잡설은 그만두고, 이 파일을 그대로 타입스크립트 파일로 넣어보면, &lt;code&gt;i32&lt;/code&gt; 타입이 인식되지 않는다.&lt;/p&gt;
&lt;p&gt;이는, AssemblyScript 를 ts 로 인식하기 위한 프로젝트 설정을 해 주지 않았기 때문이다.&lt;/p&gt;
&lt;p&gt;현재 코드 예제들을 &lt;code&gt;tsconfig.json&lt;/code&gt; 이 존재하는 장소에 저장하고 있는데,&lt;/p&gt;
&lt;p&gt;이번에는 새로운 디렉토리를 생성하여, &lt;code&gt;.ts&lt;/code&gt; 확장자가 &lt;code&gt;AssemblyScript&lt;/code&gt; 로 인식하도록 만들어 보겠다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;1. 디렉토리 따로 생성&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ mkdir web-asm-exam
$ cd web-asm-exam
$ npm init --y # web-asm-exam 디렉토리에 &amp;quot;package.json&amp;quot; 파일 생성됨.&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;2. assemblyscript 를 개발 의존성으로 설치&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ npm install --save-dev assemblyscript # &amp;quot;devDependencies&amp;quot; 에 assemblyscript 가 들어간다.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;어떤 개발 의존성들이 설치되었을까 궁금해서 &lt;code&gt;package-lock.json&lt;/code&gt; 을 뜯어봤다.&lt;/p&gt;
&lt;p&gt;보니, 2 개의 연관 의존성들이 추가로 설치되었다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;assemblyscript&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;&lt;code&gt;binaryen&lt;/code&gt; : AssemblyScript 를 위한 특별 타입 지원&lt;/li&gt;
&lt;li&gt;&lt;code&gt;long&lt;/code&gt; : 64 비트 수준의 숫자 안정성 지원&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;3. AssemblyScript 컴파일러 설치&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ npx asinit .&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;명령어 이후에 어셈블리스크립트를 지원하기 위한 다양한 폴더들이 생성된다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ex&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  web-asm-exam tree -L 2
.
├── asconfig.json
├── assembly
│   ├── index.ts
│   └── tsconfig.json
├── build
├── index.html
├── node_modules
│   ├── assemblyscript
│   ├── binaryen
│   └── long
├── package-lock.json
├── package.json
└── tests
    └── index.js&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;내부적으로 설치되는 &lt;code&gt;node_modules&lt;/code&gt; 가 있어, 2 단계까지만 간단히 보이도록 했다.&lt;/p&gt;
&lt;p&gt;보다시피, 구성 파일과 디렉토리 구조를 생성한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그리고, 파일을 작성하는 디렉토리는 &lt;code&gt;&amp;lt;asm 프로젝트&amp;gt;/assembly&lt;/code&gt; 이다.&lt;/p&gt;
&lt;p&gt;여기에 기본적인 예시가 담겨진 파일 &lt;code&gt;index.ts&lt;/code&gt; 가 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// The entry file of your WebAssembly module.

export function add(a: i32, b: i32): i32 {
  return a + b;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 파일을 &lt;code&gt;.wasm&lt;/code&gt; 으로 빌드하기 위해서는, &lt;/p&gt;
&lt;p&gt;프로젝트 root 위치에서, &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ npm run asbuild

&amp;gt; web-asm-exam@1.0.0 asbuild
&amp;gt; npm run asbuild:debug &amp;amp;&amp;amp; npm run asbuild:release


&amp;gt; web-asm-exam@1.0.0 asbuild:debug
&amp;gt; asc assembly/index.ts --target debug


&amp;gt; web-asm-exam@1.0.0 asbuild:release
&amp;gt; asc assembly/index.ts --target release&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이러한 로그 결과물을 남긴다.&lt;/p&gt;
&lt;p&gt;그리고, &lt;code&gt;&amp;lt;프로젝트 root&amp;gt;/build/..&lt;/code&gt; 에 결과물이 생긴다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  build tree
.
├── debug.d.ts
├── debug.js
├── debug.wasm
├── debug.wasm.map
├── debug.wat
├── release.d.ts
├── release.js
├── release.wasm
├── release.wasm.map
└── release.wat

1 directory, 10 files&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기서 &lt;code&gt;degug&lt;/code&gt; 를 가지고 있는 파일은 &lt;code&gt;npm test&lt;/code&gt; 를 통해 실행되는 디버깅 파일들이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;release&lt;/code&gt; 이름을 가지고 있는 파일들이 실제 실행 파일들이다.&lt;/p&gt;
&lt;p&gt;하지만, 굳이 전문적으로 &lt;code&gt;AssemblyScript&lt;/code&gt; 를 배울 필요는 없다 생각하여,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;release.wasm&lt;/code&gt; 만 빼서 넣기로 결정했다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.wat&lt;/code&gt;, &lt;code&gt;.wasm&lt;/code&gt; 이 &lt;code&gt;npm run asbuild&lt;/code&gt; 를 통해 나온 최종 결과물이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;.wasm 파일을 node 환경에서 실행하는 법&lt;/h3&gt;
&lt;p&gt;먼저, node.js 환경에서 이진 파일인 &lt;code&gt;.wasm&lt;/code&gt; 을 곧바로 사용할 수는 없다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.wasm&lt;/code&gt; 파일을 사용하기 위해, 3 가지 단계를 지난다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;파일을 읽어와 Node.js 엔진의 Buffer 에 담는다.&lt;/li&gt;
&lt;li&gt;Node.js 전역객체인 &lt;code&gt;WebAssembly&lt;/code&gt; 를 이용하여, 버퍼에 담긴 파일을 인스턴스화 한다.&lt;/li&gt;
&lt;li&gt;전달된 어셈블리 모듈을 이용하여 JS 로 이용한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;먼저, &lt;code&gt;.wasm&lt;/code&gt; 파일과 &lt;code&gt;.ts&lt;/code&gt; or &lt;code&gt;.js&lt;/code&gt; 파일이 공존할 디렉토리를 만들거나, 지정한다.&lt;/p&gt;
&lt;p&gt;해당 디렉토리에 빌드된 &lt;code&gt;.wasm&lt;/code&gt; 과, &lt;code&gt;ts&lt;/code&gt; or &lt;code&gt;js&lt;/code&gt; 파일을 넣는다.&lt;/p&gt;
&lt;p&gt;내가 빌드한 웹 어셈블리 파일은 &lt;code&gt;add&lt;/code&gt; 함수를 내보내기 때문에,&lt;/p&gt;
&lt;p&gt;이름을 &lt;code&gt;add.wasm&lt;/code&gt; 으로 바꾸었다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import * as fs from &amp;quot;fs&amp;quot;
import * as path from &amp;#39;path&amp;#39;;

// 파일을 자체적으로 가져와 버퍼로 이동
const wasmBuffer : Buffer = fs.readFileSync(path.join(__dirname, &amp;quot;./add.wasm&amp;quot;));

// 인스턴스화 된 모듈은 어떠한 형태인지 짐작 할 수 없다. 하지만, 우리는 이미 .wasm 파일이 어떤 함수나 값을 방출하는지 알기에, 인터페이스로 알려준다.
interface AddExports {
  add(a : number, b : number) : number;
}

// 모듈 초기화 및 함수 구성
async function init (buffer : Buffer) {
  // 단순한 buffer 데이터를 Node.js 에서 사용할 수 있는 형태로 가공한다.
  const wasmModule = await WebAssembly.instantiate(buffer);

  /*
  곧바로 as AddExports 하면 생기는 에러는 :
  Conversion of type Exports to type AddExports may be a mistake because neither type sufficiently overlaps with the other.
  If this was intentional, convert the expression to unknown first.

  이 뜻은, 내가 적용한 AddExports 인터페이스가, exports 유형으로 선언된 (내부 파일) type ExportValue = Function | Global | Memory | Table;
  위의 유형들과 겹치지 않기 때문이다.

  따라서, 이것이 만약 의도 된 행동이라면, 표현식을 unknown 으로 변환하라는 것이다.

  이에 따라 나는 as unknown 으로 래핑하고, 다시 AddExports 타입으로 래핑했다.
   */
  const {add} = wasmModule.instance.exports as unknown as AddExports;

  // Promise 로 감싸진 어셈블리 함수 add 를 내보낸다.
  return {add};
}

// Promise 로 감싸진 어셈블리 모듈을 가져온다.
const addWasmModule = init(wasmBuffer);

// return 되는 결과물에는 이제 우리가 찾던 함수 &amp;quot;add&amp;quot; 가 존재한다. 이를 이용하여 계산한다.
addWasmModule.then((moduleExports) =&amp;gt; {
  const result = moduleExports.add(3, 5);
  //
  console.log(result);
})&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt; : &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  worker-exam tsc wasm-js-exam-1.ts 
➜  worker-exam node wasm-js-exam-1.js
8&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h2&gt;이 내용을 탐구하고 나서 드는 생각.&lt;/h2&gt;
&lt;p&gt;각각의 고유한 언어에는 &lt;strong&gt;Learning Curve&lt;/strong&gt; 라는 것이 존재한다.&lt;/p&gt;
&lt;p&gt;처음 각각의 언어를 접했을 때, 첫 인상은 당연히 다를 수 밖에 없다.&lt;/p&gt;
&lt;p&gt;JavaScript 와, &lt;strong&gt;SuperSet&lt;/strong&gt; 인 TypeScript 와의 결합은 쉽게 일반인들도 입문할 수 있게 만들어졌다고 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, JavaScript 의 클래스 선언은 &amp;quot;문법적 설탕&amp;quot; 이라고 부르는 것이었다.&lt;/p&gt;
&lt;p&gt;물론, JS 의 모든 것이 &lt;code&gt;Function&lt;/code&gt; 객체는 아니지만, 결국 &lt;code&gt;Function&lt;/code&gt; 객체로 통한다는 것이다.&lt;/p&gt;
&lt;p&gt;JavaScript 최신 문법을 도입하기 위한 Babel 의 결과물은 다시 JavaScript 였다.&lt;/p&gt;
&lt;p&gt;JavaScript 에서 개발 중 타입 안정성을 구현하기 위해 개발된 TypeScript 조차, JS 로 컴파일된다.&lt;/p&gt;
&lt;p&gt;나는 단순히 &amp;quot;실행된다&amp;quot; 에 멈추지 않고, &amp;quot;어떻게 컴파일 되었는가&amp;quot; 에 집중했다.&lt;/p&gt;
&lt;p&gt;이는 내가 소프트웨어 엔지니어로서 조금 더 성숙해졌다는 증거이기도 했다.&lt;/p&gt;
&lt;p&gt;하지만, 나는 Node.js 를 실행하기 위해 사용되는 JS 문법의 난해함과,&lt;/p&gt;
&lt;p&gt;오히려 JS 를 깊이 사용하기 위해 결국은 최적화 된 언어를 사용해야 한다는 회의감이 들었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이 글을 작성 한 이유는, 모두가 Node.js 환경에서 다른 여타 언어들 (Go, Java, Rust 등) 보다 느리다는 것을 알고,&lt;/p&gt;
&lt;p&gt;Node.js 환경에서의 한계를 극복하려고 나 나름대로 노력한 것이다.&lt;/p&gt;
&lt;p&gt;그런데, 결국은 JS 를 더 파고들수록, 최신 모던 프로그래밍 언어의 트렌드를 따라가고,&lt;/p&gt;
&lt;p&gt;이를 단순히 JS 로 적용하기 위해서만 만들어졌다는 심증을 없애기는 무리였다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;사실, 나는 지금 NestJS 의 커스텀 데코레이터와 더불어, 성숙도를 높이려고 노력하고 있었다.&lt;/p&gt;
&lt;p&gt;하지만, 나는 흔들리는 중이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;최신 마이크로 소프트에서 TypeScript 를 컴파일하는 데 있어 느린 속도를 개선하기 위해 &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Go&lt;/strong&gt; 를 도입했다는 소식을 들었다. 7.0 버전부터 적용될 것이다.&lt;/p&gt;
&lt;p&gt;허나, 나는 다른 생각이 들기 시작했다.&lt;/p&gt;
&lt;p&gt;물론 TypeScript 를 번역하기 위해 다시 TypeScript 를 사용하는 것은, 비효율적인 과정일 수 있다.&lt;/p&gt;
&lt;p&gt;하지만, TypeScript 를 번역하기 위해 GO 를 사용한다는 것은, 결국 TypeScript 의 역할,&lt;/p&gt;
&lt;p&gt;즉, 결국 JS 의 역할을 Go 가 10 배 더 빠른 효율로 제공한다는 것이 아닌가?&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;물론, 최신 브라우저가 사용하고 있는 V8 엔진 성능의 훌륭함과,&lt;/p&gt;
&lt;p&gt;더불어 javascript 도 효율적으로 사용 될 수 있다는 것을 안다.&lt;/p&gt;
&lt;p&gt;그리고, JS 와 TS 의 커뮤니티 활동성도 굉장하다는 것을 알고 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;나는 구현하고자 하는 사람인가? 아니면 탐구하고자 하는 사람인가?&lt;/p&gt;
&lt;p&gt;위에 대한 대답은 조만간 이루어 질 것 이라고 생각한다.&lt;/p&gt;
&lt;br/&gt;



&lt;hr&gt;
&lt;h2&gt;참고 사이트 - 새 창으로 열림&lt;/h2&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://webassembly.org/getting-started/js-api/&quot;&gt;https://webassembly.org/getting-started/js-api/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.assemblyscript.org/introduction.html&quot;&gt;https://www.assemblyscript.org/introduction.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://developer.mozilla.org/ko/docs/WebAssembly&quot;&gt;https://developer.mozilla.org/ko/docs/WebAssembly&lt;/a&gt;&lt;/p&gt;</description>
      <category>Node.js/잡다 지식</category>
      <category>assemblyscript</category>
      <category>node.js</category>
      <category>nodejs</category>
      <category>nodejs webassembly</category>
      <category>wasm</category>
      <category>WAT</category>
      <category>WebAssembly</category>
      <category>webassembly nodejs</category>
      <category>웹 어셈블리</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/202</guid>
      <comments>https://codecreature.tistory.com/202#entry202comment</comments>
      <pubDate>Tue, 8 Apr 2025 05:09:09 +0900</pubDate>
    </item>
    <item>
      <title>node.js 로 멀티 스레드 구현하기 (Worker)</title>
      <link>https://codecreature.tistory.com/201</link>
      <description>&lt;h2&gt;제목 : Node.js 엔진에서 스레드 추가하기 (Worker)&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;세상에는 정말 빠른 실행 시간과 계산을 보장하는 언어들이 많다.&lt;/p&gt;
&lt;p&gt;벌써 떠오르기를, &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;C, C++&lt;/li&gt;
&lt;li&gt;Java&lt;/li&gt;
&lt;li&gt;Python&lt;/li&gt;
&lt;li&gt;Rust&lt;/li&gt;
&lt;li&gt;Swift&lt;/li&gt;
&lt;li&gt;Go&lt;/li&gt;
&lt;li&gt;Kotlin&lt;/li&gt;
&lt;li&gt;등등..&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Node.js 기반의 엔진보다 CPU 실행 성능이 뛰어나며, Memory 절약도 뛰어난 언어일 것이다. (Python 은 약간 더 뛰어날듯?)&lt;/p&gt;
&lt;p&gt;JavaScript 는 위의 언어들 중 몇 개들 보다 더 일찍 만들어 졌지만, &lt;/p&gt;
&lt;p&gt;웹 페이지에서의 Dynamic 한 인터랙션을 위해 만들어졌다.&lt;/p&gt;
&lt;p&gt;기존의 HTML 문서는 DOM (Document Object Model) 로 파싱되어,&lt;/p&gt;
&lt;p&gt;JavaScript 를 통해 엘리먼트들의 위치나 속성을 동적으로 변경할 수 있게 되었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;웹을 위한 언어에서 머무르던 JavaScript 는, Node.js 엔진의 최적화와 TypeScript 의 대중화에 힘입어&lt;/p&gt;
&lt;p&gt;많은 사람들의 입문 언어가 되기 시작했고, 그 아성은 끝내 많은 마이크로서비스 아키텍쳐의 서버에 사용되기도 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;자바스크립트는 인터프리터 언어로, 기계어나 어셈블리어 수준으로 미리 컴파일되지는 않는다.&lt;/p&gt;
&lt;p&gt;JIT(Just In Time) 컴파일 방식을 사용하는데, 이는 런타임 과정에서 코드를 읽는다는 것이다.&lt;/p&gt;
&lt;p&gt;당연하겠지만, 이러한 방식은 미리 바이너리로 컴파일 되는 방식보다는 런타임 환경에서 최적화가 낮을 수 밖에 없다.&lt;/p&gt;
&lt;p&gt;하지만, 웹 제작 과정에서 자바스크립트는 피할 수 없는 운명이고,&lt;/p&gt;
&lt;p&gt;자바스크립트와 관련된 커뮤니티의 방대함과 NPM 패키지 매니저의 편리함은 유저 풀을 더 형성하기에 이르렀다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, JavaScript 엔진이 타 언어의 런타임보다 느리다는 것은 여전히 치명적으로 작용한다고 생각한다.&lt;/p&gt;
&lt;p&gt;코드 리터럴의 편리성과 메타 프로그래밍 언어로서의 동적 속성은 위의 단점을 상쇄하기도 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;팀 프로젝트에서 NestJS 를 이용한 메타 프로그래밍을 접한 이후,&lt;/p&gt;
&lt;p&gt;JS, TS 와 더불어 메타 프로그래밍을 이해하고자 현재 NestJS 강의를 udemy 사이트에서 듣고 있다.&lt;/p&gt;
&lt;p&gt;그런데, 공부를 할 수록, NestJS 와 Spring 간의 간극은 더욱 커 질 수 밖에 없을 것 같다는 생각이 들었다.&lt;/p&gt;
&lt;p&gt;그도 그럴 것이, NestJS 는 당연히 Node.js 엔진을 사용하기에, Single Thread 와 Event Driven I/O 라는 특성이&lt;/p&gt;
&lt;p&gt;타 언어에 비해 사용자 요청을 더 효율적으로 처리할 수 있다는 장점과, 자원 경쟁 문제가 없다는 장점이 있었다.&lt;/p&gt;
&lt;p&gt;그렇지만, Single Thread 의 문제점은, 이미지나 비디오 처리, 혹은 CPU 집약 처리에 있어서 쥐약이라는 말과&lt;/p&gt;
&lt;p&gt;동일하다고 생각했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;반면에 Spring 은, 유저의 요청을 처리하기 위해 미리 쓰레드 풀을 만들어 놓고, &lt;/p&gt;
&lt;p&gt;들어오는 요청에 쓰레드를 할당시키고 있었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이렇게 되면, 결국 &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;싱글 스레드, 이벤트 기반 I/O&lt;/strong&gt; VS &lt;strong&gt;멀티 스레드, 병렬 처리&lt;/strong&gt; 가 될 것이라고 생각했다.&lt;/p&gt;
&lt;p&gt;당연하게도, I/O 처리에서도 병렬 처리를 수행하는 Spring 이 우위일 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;나는 이러한 생각의 과정에서, NestJS 혹은 express 와 같은 라이브러리가, &lt;/p&gt;
&lt;p&gt;어떻게 Spring 의 속도를 따라갈 수 있을까? 를 고민했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;당연하게도, Node.js 엔진 기반의 JavaScript 또한 Thread 를 생성 할 수 있었다.&lt;/p&gt;
&lt;p&gt;그러나, 기존의 메모리 pool 을 공유한 상태로 시작하는 것이 아니라, &lt;/p&gt;
&lt;p&gt;새로운 메모리 pool 을 생성하여 실행한다는 것이었다.&lt;/p&gt;
&lt;p&gt;생각 해 보니, Node.js 는 하나의 스레드에 메모리를 할당하고, 처리 과정은 Event I/O 로 하니,&lt;/p&gt;
&lt;p&gt;만약에 새로운 스레드를 생성하고 같은 메모리를 차지하면, Event Loop 과정이 박살날 것 같다는 생각이 든다.&lt;/p&gt;
&lt;p&gt;따라서, 다른 메모리 공간을 할당하여 새로운 스레드에 이벤트 루프 할 공간을 마련해 주는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;만약에 CPU 집약적인 처리를 새로운 스레드에 할당하면,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart LR

Client(&amp;quot;클라이언트&amp;quot;)

subgraph framework [&amp;quot;Node.js 기반의 프레임워크&amp;quot;]
    Node(&amp;quot;Main Thread&amp;quot;)

    Thread(&amp;quot;CPU 집약 처리를 위해 생성된 Thread&amp;quot;)

    Node -- 처리 데이터 전달 : 기다림 --&amp;gt; Thread
    Thread -- 데이터 처리 완료 : 완료 --&amp;gt; Node
end

Client &amp;lt;-- 요청/응답 --&amp;gt; Node&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;메인 스레드는 그대로 클라이언트에게서 밀려오는 요청을 처리하고,&lt;/p&gt;
&lt;p&gt;특정 CPU 집약 처리를 새로운 Thread 에게 일임함으로서, &lt;/p&gt;
&lt;p&gt;메인 스레드가 집약 처리를 위해 잠깐 I/O 를 멈추는 일은 없게 만드는 것이다. &lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이 과정에서, &lt;code&gt;child_process&lt;/code&gt; 혹은 &lt;code&gt;cluster&lt;/code&gt; 라이브러리와는 달리,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;worker_threads&lt;/code&gt; 라이브러리는 참조할 배열 버퍼를 보내고, 이를 메인 Thread 와 공유 할 수 있다.&lt;/p&gt;
&lt;p&gt;이번 포스팅에서는 &lt;code&gt;worker_threads&lt;/code&gt; 를 사용 할 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;우선, NestJS 프로젝트에 적용 할 생각을 가지고 있기에,&lt;/p&gt;
&lt;p&gt;나는 TypeScript 를 사용하여 &lt;code&gt;worker_threads&lt;/code&gt; 를 구현할 것이다.&lt;/p&gt;
&lt;p&gt;이 과정에서, 타입스크립트를 업데이트하고, &lt;/p&gt;
&lt;p&gt;&lt;code&gt;tsconfig.json&lt;/code&gt; 에 &lt;code&gt;devDependencies&lt;/code&gt; 에 &lt;code&gt;@types/node&lt;/code&gt; 를 추가하면 된다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ npm i -D @types/node # 내장 빌트인 함수 참조 가능하게 만들어줌. (TypeScript)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그리고 &lt;code&gt;tsc&lt;/code&gt; 명령어로 일일이 컴파일하고 &lt;code&gt;node&lt;/code&gt; 로 실행하기 귀찮다면,&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;HomeBrew&lt;/strong&gt; 에 &lt;code&gt;ts-node&lt;/code&gt; 를 설치하거나,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ npm i -g ts-node # 전역 명령어로 ts-node 실행 가능&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;하면 된다. (개인적으로는 패키지 관리 프로그램에 설치..)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;워커를 생성하고, 할당하는 Main Thread&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import { Worker } from &amp;#39;worker_threads&amp;#39;;
import * as path from &amp;#39;path&amp;#39;;

function startWorker(): Promise&amp;lt;number&amp;gt; {
  return new Promise((resolve, reject) =&amp;gt; {
    const worker = new Worker(path.resolve(__dirname, &amp;#39;./script.js&amp;#39;));

    worker.on(&amp;#39;message&amp;#39;, (result) =&amp;gt; {
      console.log(&amp;#39;Worker 결과:&amp;#39;, result);
      resolve(result);
    });

    worker.on(&amp;#39;error&amp;#39;, reject);
    worker.on(&amp;#39;exit&amp;#39;, (code) =&amp;gt; {
      if (code !== 0) {
        reject(new Error(`워커가 비정상 종료 (exit code ${code})`));
      }
    });
  });
}

async function work() {
  const result = await startWorker();
  console.log(result);
}

work();&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;script.ts&lt;/code&gt; : &lt;code&gt;Worker&lt;/code&gt; 가 실행 할 스크립트가 존재하는 위치 - sub 스레드 시작.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import { parentPort } from &amp;#39;worker_threads&amp;#39;;

let sum = 0; 
for (let i = 1; i &amp;lt;= 100_000; i++) {
  sum += i;
}

parentPort?.postMessage(sum);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;먼저, TypeScript 환경에서 Worker Thread 에 &lt;strong&gt;URL&lt;/strong&gt; 을 넘길 때 문제가 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;script.ts&lt;/code&gt; 파일이 물론 &lt;code&gt;TypeScript&lt;/code&gt; 로 작성되었지만,&lt;/p&gt;
&lt;p&gt;결국 Node.js 환경으로 구동되기 때문에, &lt;code&gt;.js&lt;/code&gt; 확장자로 실행한다는 것이다.&lt;/p&gt;
&lt;p&gt;따라서, &lt;code&gt;TypeScript&lt;/code&gt; 로 작성된 파일이더라도, &lt;code&gt;.js&lt;/code&gt; 로 입력해 주어야 한다.&lt;/p&gt;
&lt;p&gt;만약에 &lt;code&gt;TypeScript&lt;/code&gt; 파일을 그대로 넣는다면,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  worker-exam ts-node worker-1.ts
(node:3217) ExperimentalWarning: Type Stripping is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
Worker 결과: 5000050000
5000050000
(node:3217) [MODULE_TYPELESS_PACKAGE_JSON] Warning: Module type of file:///Users/xxxx/xxxx/script.ts is not specified and it doesn&amp;#39;t parse as CommonJS.
Reparsing as ES module because module syntax was detected. This incurs a performance overhead.
To eliminate this warning, add &amp;quot;type&amp;quot;: &amp;quot;module&amp;quot; to /Users/xxxx/xxxx/package.json.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;.js&lt;/code&gt; 확장자로 바꾸어 준다면,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  worker-exam ts-node worker-1.ts
Worker 결과: 5000050000
5000050000&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;.js&lt;/code&gt; 파일에서 다시 &lt;code&gt;.ts&lt;/code&gt; 파일을 불러와 컴파일 하는 과정을 없애주므로,&lt;/p&gt;
&lt;p&gt;경고 로깅도 뜨지 않는 것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;따라서, 위에 URL 참조로 &lt;code&gt;script.js&lt;/code&gt; 를 넣은 것은, 내가 컴파일 된 환경에서&lt;/p&gt;
&lt;p&gt;&lt;code&gt;node xxx&lt;/code&gt; 로 실행할 것을 염두에 두었기 때문이다.&lt;/p&gt;
&lt;br/&gt;



&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;Node.js 공식 홈페이지의 Worker 소개&lt;/strong&gt; &lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://nodejs.org/docs/latest/api/worker_threads.html&quot;&gt;https://nodejs.org/docs/latest/api/worker_threads.html&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;공식 홈페이지에서는, 파일의 가독성을 위해 &amp;quot;하나의 파일 안&amp;quot; 에 &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Main Thread 의 작업&lt;/li&gt;
&lt;li&gt;Worker Thread 의 작업&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;의 내용이 모두 들어가 있다.&lt;/p&gt;
&lt;p&gt;이는 굳이 파일의 내용을 나누지 않고, 런타임 환경에서&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;메인 스레드에서 실행하는가?&lt;/strong&gt; 혹은 &lt;strong&gt;워커 스레드에서 실행하는가?&lt;/strong&gt; 로 나눌 수 있는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import {Worker, isMainThread, parentPort, workerData} from &amp;#39;worker_threads&amp;#39;;

if (isMainThread) {
  new Worker(__filename);
} else {
  console.log(&amp;quot;내부에서 워커가 실행됨!&amp;quot;);
  console.log(isMainThread);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  worker-exam tsc
➜  worker-exam node node-exam-1.js
내부에서 워커가 실행됨!
false&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;통상적으로 &lt;code&gt;.js&lt;/code&gt; 파일로 실행하여 경고 로그를 없애기 위해, 컴파일 후 &lt;code&gt;node&lt;/code&gt; 로 &lt;code&gt;.js&lt;/code&gt; 파일로 실행.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;isMainThread&lt;/code&gt; 는 &lt;code&gt;node&lt;/code&gt; 라이브러리에서 제공하는 환경 변수로, &lt;br/&gt; 현재 실행중인 스레드 환경에 따라 &lt;code&gt;true&lt;/code&gt; or &lt;code&gt;false&lt;/code&gt; 로 반환된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;p&gt;이처럼, &lt;code&gt;isMainThread&lt;/code&gt; 이라는 &lt;code&gt;node&lt;/code&gt; 의 환경 변수를 통해,&lt;/p&gt;
&lt;p&gt;하나의 파일에서 메인 스레드가 실행 할 코드와, 서브 스레드가 실행 할 코드를 분리 할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;Worker&lt;/code&gt; 라이브러리는 메인 스레드와는 다른 메모리 공간을 가지는 서브 스레드를 만들어 준다.&lt;/p&gt;
&lt;p&gt;그렇지만, 메인 스레드에서 각각의 스레드에게 제공할 데이터를 만들어 줄 수도 있고,&lt;/p&gt;
&lt;p&gt;혹은 &amp;quot;모든 서브 스레드&amp;quot; 에서 참고할 환경 변수 (정적 데이터?) 를 설정 해 줄 수도 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;하나의 서브 스레드에 데이터 전달하기&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import { isMainThread, Worker, workerData } from &amp;#39;worker_threads&amp;#39;;

if(isMainThread) {
  const worker = new Worker(__filename, {
    workerData : {
      sendData : &amp;quot;메인 스레드가 서브 스레드로 이 문제열을 제공합니다.&amp;quot;
    }
  })
} else {
  console.log(&amp;quot;서브 스레드 시작.&amp;quot;);

  const sendData = workerData.sendData;

  console.log(sendData);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt; : &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  worker-exam tsc                 
➜  worker-exam node node-exam-2.js
서브 스레드 시작.
메인 스레드가 서브 스레드로 이 문제열을 제공합니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;new Worker(...)&lt;/code&gt; 실행 시,&lt;/p&gt;
&lt;p&gt;이 생성자 식에 1 번쨰로는 &amp;quot;실행 할 파일의 경로&amp;quot; 를 의미하고,&lt;/p&gt;
&lt;p&gt;2 번째는 선택적인데, &amp;quot;서브 스레드에서 참조할 데이터&amp;quot; 를 의미한다.&lt;/p&gt;
&lt;p&gt;그리고 나서 Sub Thread 에서 실행 할 때,&lt;/p&gt;
&lt;p&gt;자신에게 주어진 참조 데이터를 알기 위해 &lt;code&gt;workerData&lt;/code&gt; 를 라이브러리에서 가져오고,&lt;/p&gt;
&lt;p&gt;여기서 &lt;code&gt;sendData&lt;/code&gt; 를 추출하는 것이다.&lt;/p&gt;
&lt;p&gt;물론, &lt;code&gt;workerData&lt;/code&gt; 는 &lt;code&gt;any&lt;/code&gt; 형식이므로, 단순하게 primitive 타입으로 넣어도 된다.&lt;/p&gt;
&lt;p&gt;나는 예시로 객체를 넣어봤다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;모든 서브 스레드에서 참조할 환경 변수 설정하기&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import { isMainThread, Worker, getEnvironmentData, setEnvironmentData } from &amp;#39;node:worker_threads&amp;#39;;

if (isMainThread) {
  setEnvironmentData(1, 2);
  setEnvironmentData(&amp;quot;MyKey&amp;quot;, &amp;quot;MyValue&amp;quot;);
  const worker = new Worker(__filename);
} else {
  console.log(&amp;quot;서브 스레드 시작 ----&amp;quot;);

  console.log(`공통 환경 변수 1 은, = ${getEnvironmentData(1)}`)
  console.log(`공통 환경 변수 &amp;quot;MyKey&amp;quot; 는, = ${getEnvironmentData(&amp;quot;MyKey&amp;quot;)}`);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt; : &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  worker-exam tsc
➜  worker-exam node node-exam-3.js
서브 스레드 시작 ----
공통 환경 변수 1 은, = 2
공통 환경 변수 &amp;quot;MyKey&amp;quot; 는, = MyValue&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;메인 스레드에서 서브 스레드가 공통으로 참조할 수 있는 환경 변수 2 개를 만들었다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;1&lt;/code&gt; : &lt;code&gt;2&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;MyKey&amp;quot;&lt;/code&gt; : &lt;code&gt;&amp;quot;MyValue&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;서브 스레드는 메인 스레드가 등록 해 놓은 환경 변수를 참조하여,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;2&lt;/code&gt; 와 &lt;code&gt;&amp;quot;MyValue&amp;quot;&lt;/code&gt; 를 출력하고 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;위에서 제시한 데이터 전달 방식은,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;스레드가 생성 될 때 마다, 해당 스레드가 참조할 데이터 지정&lt;/li&gt;
&lt;li&gt;스레드가 생성 될 때 마다, 자동으로 참조 할 수 있는 환경 변수 지정&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이렇게 나뉘게 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;메인 스레드가 서브 스레드에서의 결과를 기다린다.&lt;/h2&gt;
&lt;p&gt;사실, 워커 스레드를 배우는 가장 큰 이유가 이것이라고 생각한다.&lt;/p&gt;
&lt;p&gt;이 글을 작성하는 이유는, NestJS, Express 와 같은 프레임워크 및 라이브러리는,&lt;/p&gt;
&lt;p&gt;따로 스레드를 생성하지 않고, 싱글 스레드로 작동한다.&lt;/p&gt;
&lt;p&gt;이로 인해 Input, Output 과정에서는 기다림이 없지만,&lt;/p&gt;
&lt;p&gt;해당 서버를 사용하는 클라이언트 입장에서는 하나의 서버가 자신들의 모든 요청을 처리하는 것이다.&lt;/p&gt;
&lt;p&gt;이러한 특성으로 인해, 내부에 CPU 집약적인 과정에 포함 될 경우, 데이터 반환이 느려질 수 있다.&lt;/p&gt;
&lt;p&gt;모든 클라이언트 요청을 동시에 수행하지만, 어떠한 클라이언트 요청이 CPU 집약적이라면,&lt;/p&gt;
&lt;p&gt;일부 클라이언트의 응답이 느려질 수 밖에 없는 것이다. - 어쨋뜬 CPU 집약 작업을 해야 하기에.&lt;/p&gt;
&lt;p&gt;따라서, 메인 스레드는 클라이언트의 요청과 응답에 집중하되,&lt;/p&gt;
&lt;p&gt;서브 스레드 &lt;code&gt;Worker&lt;/code&gt; 를 생성하여, CPU 집약 과정을 따로 수행하도록 만드는 것이다.&lt;/p&gt;
&lt;p&gt;이러한 과정을 사용하면, Node.js 특유의 싱글 스레드를 타파할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그렇다면, &lt;code&gt;Worker&lt;/code&gt; 는 CPU 집약 과정을 수행하고, &lt;code&gt;Main Thread&lt;/code&gt; 는 그 과정을 기다린다.&lt;/p&gt;
&lt;p&gt;그 후, &lt;code&gt;Main Thread&lt;/code&gt; 가 클라이언트에게 수행된 결과를 보내준다.&lt;/p&gt;
&lt;p&gt;하지만, &lt;code&gt;Node.js&lt;/code&gt; 환경에서 &amp;quot;어떻게 기다릴 것&amp;quot; 인가?&lt;/p&gt;
&lt;p&gt;이 방식은 &lt;code&gt;Promise&lt;/code&gt; 로 해결할 수 있다.&lt;/p&gt;
&lt;h3&gt;왜 Promise 인가?&lt;/h3&gt;
&lt;p&gt;내가 위에서 제공한 방식은, 메인 스레드가 서브 스레드를 생성하고, 기다리지 않는다.&lt;/p&gt;
&lt;p&gt;그러니까, 단순하게 워커를 생성하고 실행하게 방치 할 뿐, 기다리지는 않는다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;sequenceDiagram
    Main-Thread-&amp;gt;&amp;gt;Worker-Thread: 새로운 워커 할당
    Main-Thread-&amp;gt;&amp;gt;Worker-Thread: 또 다시 워커 할당
    Worker-Thread--&amp;gt;&amp;gt;Main-Thread: 반환하지를 않음.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;만약에 Promise 로 감싸지 않는다면?&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;sequenceDiagram
    Client-&amp;gt;&amp;gt;Main-Thread: 요청
    Main-Thread-&amp;gt;&amp;gt;Worker-Thread: CPU 계산 요청
    Main-Thread-&amp;gt;&amp;gt;Client: 응답
    Worker-Thread-&amp;gt;&amp;gt;Main-Thread: CPU 계산 완료 및 응답(이미 늦음)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Node.js 서버의 특유 이벤트 루프로 인해, 기다리지 않고 그대로 반환한다.&lt;/p&gt;
&lt;p&gt;이를 위해, &lt;code&gt;Promise&lt;/code&gt; 객체를 이용하여, &lt;code&gt;Worker&lt;/code&gt; 가 특정 이벤트를 받아&lt;/p&gt;
&lt;p&gt;&lt;code&gt;resolve&lt;/code&gt; 할 때 까지 기다리게 만드는 것이다.&lt;/p&gt;
&lt;p&gt;그렇게 한다면,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;sequenceDiagram
    Client-&amp;gt;&amp;gt;Main-Thread: 계산 요청
    Main-Thread-&amp;gt;&amp;gt;Worker-Thread: CPU 계산 요청
    Worker-Thread-&amp;gt;&amp;gt;Main-Thread: CPU 계산 완료 및 응답
    Main-Thread-&amp;gt;&amp;gt;Client: 계산 결과 응답&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;순차적으로 진행 할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;Promise&lt;/code&gt; 객체는 &amp;quot;약속&amp;quot; 을 의미한다.&lt;/p&gt;
&lt;p&gt;어떠한 약속이냐면, &lt;code&gt;Promise&lt;/code&gt; 객체는 무조건 &lt;code&gt;resolve&lt;/code&gt;, &lt;code&gt;reject&lt;/code&gt; 둘 중 하나의 상태를 지닌다는 약속이다.&lt;/p&gt;
&lt;p&gt;그 때 까지, &lt;code&gt;Promise&lt;/code&gt; 객체를 부른 함수는 &lt;code&gt;resolve&lt;/code&gt;, &lt;code&gt;reject&lt;/code&gt; 가 될 때 까지 기다린다.&lt;/p&gt;
&lt;p&gt;&lt;del&gt;물론, 함수는 async 를 사용해야 한다.&lt;/del&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이를 사용하여 코드를 작성 해 보자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import { Worker, isMainThread, workerData } from &amp;#39;node:worker_threads&amp;#39;;
import { parentPort } from &amp;#39;worker_threads&amp;#39;;


if (isMainThread) {

  function getSummary(num : number) {
    return new Promise((resolve, reject) =&amp;gt; {
      const worker = new Worker(__filename);

      // 워커가 &amp;quot;error&amp;quot; 이벤트를 보낸다면, reject 상태값과 함께(err) 객체를 반환한다.
      worker.on(&amp;#39;error&amp;#39;, (err) =&amp;gt; {
        reject(err);
      });

      // 워커로부터 &amp;quot;message&amp;quot; 이벤트를 받는다면, 해당 메세지를 출력하고, 종료한다.
      worker.on(&amp;#39;message&amp;#39;,async (num : number) =&amp;gt; {
        console.log(`메인 스레드에서 결과 출력 : ${num}`);

        resolve(num);

        // 완료했으므로, 이제 종료한다.
        await worker.terminate();
      });

      // 워커에 직접 (&amp;#39;message&amp;#39;) 이벤트를 보내며, 문자열 데이터를 보낸다.
      worker.postMessage(10);
    })
  }
  getSummary(10).then((res) =&amp;gt; console.log(res));
} else {
  function summary(number : number) {
    let sum = 0;
    for(let i = 0; i &amp;lt; number; i++) {
      sum += i;
    }
    return sum;
  }

  parentPort.on(&amp;#39;message&amp;#39;, (num : number) =&amp;gt; {

    const sum = summary(num);

    parentPort.postMessage(sum);
  })
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt; : &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  worker-exam tsc                
➜  worker-exam node node-exam-4.js
메인 스레드에서 결과 출력 : 45
45&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 코드는 메인 스레드와 워커 스레드가 수행해야 할 내용이 모두 포함되어 있으므로,&lt;/p&gt;
&lt;p&gt;2 개로 나누어서 파악해야 한다.&lt;/p&gt;
&lt;p&gt;먼저, &lt;code&gt;worker.on&lt;/code&gt;, &lt;code&gt;parentPort.on&lt;/code&gt; 의 의미와 차이점을 알아야 한다.&lt;/p&gt;
&lt;p&gt;우선, &lt;code&gt;Worker&lt;/code&gt; 는 이벤트를 등록할 수 있다.&lt;/p&gt;
&lt;p&gt;이게 어떤 의미냐면, &lt;/p&gt;
&lt;p&gt;Main --&amp;gt; Sub 로 이벤트를 보냈을 때, Sub 에서는 어떻게 반응 할 것인가?&lt;/p&gt;
&lt;p&gt;Sub --&amp;gt; Main 로 이벤트를 보냈을 때, Main 에서는 어떻게 반응 할 것인가?&lt;/p&gt;
&lt;p&gt;이러한 의미이다.&lt;/p&gt;
&lt;p&gt;-&lt;code&gt;worker.on&lt;/code&gt;- 의미&lt;/p&gt;
&lt;p&gt;생성된 worker 스레드는 자신의 parent 스레드인 main 스레드에 이벤트를 보낼 수 있다.&lt;/p&gt;
&lt;p&gt;이 때, 부모 스레드인 Main 스레드가 반응 할 이벤트를 지정하는 것이 &lt;code&gt;worker.on&lt;/code&gt; 이다.&lt;/p&gt;
&lt;p&gt;-&lt;code&gt;parentPort.on&lt;/code&gt;- 의미&lt;/p&gt;
&lt;p&gt;이 문구는 worker 스레드 코드가 이용하며, 메인 스레드가 어떤 이벤트를 주었을 때, &lt;/p&gt;
&lt;p&gt;어떤 행동이나 응답을 할 지 서술하는 장소이다. &lt;/p&gt;
&lt;p&gt;가장 많이 예제로 사용되는 것이 바로 &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;worker.on(&amp;#39;message&amp;#39;, () =&amp;gt; {...})&lt;/code&gt; - 워커 스레드가 &lt;code&gt;message&lt;/code&gt; 이벤트와 데이터를 보낸다면.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;parentPort.on&amp;#39;message&amp;#39;, () =&amp;gt; {...})&lt;/code&gt; - 부모인 Main 스레드가 &lt;code&gt;message&lt;/code&gt; 이벤트와 데이터를 보낸다면.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 두 개이다.&lt;/p&gt;
&lt;p&gt;그리고 각자, &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;메인 스레드 --&amp;gt; 워커 스레드 : &lt;code&gt;worker.postMessage(...)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;워커 스레드 --&amp;gt; 메인 스레드 : &lt;code&gt;parentPort.postMessage(...)&lt;/code&gt; &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;로, 서로의 이벤트를 trigger 할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그렇다면, 위의 지식을 기반으로, 메인과 워커 스레드의 코드를 분석 해 보자.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Main Thread&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;function getSummary(num : number) {
  return new Promise((resolve, reject) =&amp;gt; {
    const worker = new Worker(__filename);

    // 워커가 &amp;quot;error&amp;quot; 이벤트를 보낸다면, reject 상태값과 함께(err) 객체를 반환한다.
    worker.on(&amp;#39;error&amp;#39;, (err) =&amp;gt; {
      reject(err);
    });

    // 워커로부터 &amp;quot;message&amp;quot; 이벤트를 받는다면, 해당 메세지를 출력하고, 종료한다.
    worker.on(&amp;#39;message&amp;#39;,async (num : number) =&amp;gt; {
      console.log(`메인 스레드에서 결과 출력 : ${num}`);

      resolve(num);

      // 완료했으므로, 이제 종료한다.
      await worker.terminate();
    });

    // 워커에 직접 (&amp;#39;message&amp;#39;) 이벤트를 보내며, 문자열 데이터를 보낸다.
    worker.postMessage(10);
  })
}

// 워커 스레드 시작 및 기다림 후 응답 결과를 &amp;quot;res&amp;quot; 로 추출
getSummary(10).then((res) =&amp;gt; console.log(res));&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;클라이언트가 요구하는 계산 사항을 워커 스레드를 이용하여 계산하되,&lt;/p&gt;
&lt;p&gt;워커 스레드가 계산을 완료 했을 때, 다음 과정을 이어갈 수 있도록, &lt;code&gt;Promise&lt;/code&gt; 객체로 감싼다.&lt;/p&gt;
&lt;p&gt;여기서 다음 과정을 이어갈 수 있게 만드는 것은 &lt;code&gt;resolve&lt;/code&gt;, &lt;code&gt;reject&lt;/code&gt; 로,&lt;/p&gt;
&lt;p&gt;에러가 일어났을 때는 &lt;code&gt;error&lt;/code&gt;, 워커가 부모에게 &lt;code&gt;message&lt;/code&gt; 이벤트를 트리거한다면,&lt;/p&gt;
&lt;p&gt;결과인 &lt;code&gt;num&lt;/code&gt; 을 바탕으로 결과를 출력하며, 생성된 워커를 종료한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;Worker Thread&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;function summary(number : number) {
    let sum = 0;
    for(let i = 0; i &amp;lt; number; i++) {
      sum += i;
    }
    return sum;
}

parentPort.on(&amp;#39;message&amp;#39;, (num : number) =&amp;gt; {
    const sum = summary(num);

    parentPort.postMessage(sum);
})&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;worker 스레드는 main 스레드로부터 &lt;code&gt;num&lt;/code&gt; 변수를 받는데,&lt;/p&gt;
&lt;p&gt;이 &lt;code&gt;num&lt;/code&gt; 변수는 main 스레드가 &lt;code&gt;message&lt;/code&gt; 이벤트 트리거와 함께 제공한다.&lt;/p&gt;
&lt;p&gt;이를 바탕으로, 워커 스레드는 &lt;code&gt;num&lt;/code&gt; 까지 더하여 합한 수를 &lt;/p&gt;
&lt;p&gt;&amp;quot;다시&amp;quot; 메인 스레드에게 전달하는데, &lt;code&gt;message&lt;/code&gt; 트리거와 함께 수행한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;메인 스레드와 워커 스레드의 이벤트 트리거를 등록하고,&lt;/p&gt;
&lt;p&gt;메인 스레드에서 워커 스레드의 이벤트를 발동(트리거) 시킨다면,&lt;/p&gt;
&lt;p&gt;워커 스레드는 결과와 함께 메인 스레드의 이벤트를 발동(트리거) 하며, 워커 스레드는 종료한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;worker.terminate() 는 뭘까?&lt;/h3&gt;
&lt;p&gt;이건 생각보다 중요하게 생각 해야 할 부분인데,&lt;/p&gt;
&lt;p&gt;워커 스레드에 이벤트가 등록되면, 워커 스레드는 자신만의 이벤트 루프에 &lt;/p&gt;
&lt;p&gt;이벤트를 등록한다.&lt;/p&gt;
&lt;p&gt;물론, &lt;code&gt;resolve(...)&lt;/code&gt; 만으로 &lt;code&gt;Promise&lt;/code&gt; 문이 충족되어, &lt;/p&gt;
&lt;p&gt;프로그램이 넘어가 지며, 로직 상으로는 문제가 없지만,&lt;/p&gt;
&lt;p&gt;이것은 컴퓨터 자원을 지속적으로 갉아먹고 있는 것과 동일하기 때문에,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;worker.terminate()&lt;/code&gt; 를 통해, 기기에 리소스를 반환하는 것은 매우 중요하다.&lt;/p&gt;
&lt;p&gt;그 이유는, 앞으로 제작하게 될, &lt;strong&gt;Thread Pool&lt;/strong&gt; 에 매우 중요한 요소이기 때문이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;흐름의 결과는 어떨까?&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;sequenceDiagram
    Client-&amp;gt;&amp;gt;Main Thread: 요청
    Main Thread-&amp;gt;&amp;gt;Worker Thread: 워커 스레드 생성
    Worker Thread-&amp;gt;&amp;gt;Worker Thread: 이벤트 등록
    Main Thread-&amp;gt;&amp;gt;Main Thread: 이벤트 등록
    Main Thread-&amp;gt;&amp;gt;Worker Thread: 이벤트 trigger (&amp;#39;message&amp;#39;)
    Worker Thread-&amp;gt;&amp;gt;Worker Thread: 계산 수행
    Worker Thread-&amp;gt;&amp;gt; Main Thread: 이벤트 trigger (&amp;#39;message&amp;#39;)
    Main Thread-&amp;gt;&amp;gt; Client: 결과 데이터 반환
    Main Thread-&amp;gt;&amp;gt; Worker Thread: 워커 스레드 종료 지시&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위에서 진행했던 마지막 예제의 흐름은 이렇다.&lt;/p&gt;
&lt;p&gt;하지만, 조금 아쉬운 것은 스레드 간 바로 메모리 공간을 공유하지 않는다는 것이다.&lt;/p&gt;
&lt;p&gt;메모리 공간을 공유한다면, 메모리를 새로이 생성하는 불필요한 과정을 생략할 수 있을 것이다.&lt;/p&gt;
&lt;p&gt;하지만, 여기서 특유의 우회법은 여전히 존재했다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;SharedArrayBuffer&lt;/h3&gt;
&lt;p&gt;우리가 &lt;code&gt;workerData&lt;/code&gt; 혹은 &lt;code&gt;setEnvironmentData([key], [value])&lt;/code&gt; 형식으로 &lt;/p&gt;
&lt;p&gt;워커 스레드가 참조할 데이터를 전달 해 주거나, 설정 해 주었다.&lt;/p&gt;
&lt;p&gt;데이터를 굳이 공유하지 않고, 스레드가 스레드에게 데이터를 전달 해 주는 이유는,&lt;/p&gt;
&lt;p&gt;각각의 스레드가 각자의 메모리 공간을 가지고 있기 때문이다. (싱글 스레드로 동시에 여러 일을 수행.)&lt;/p&gt;
&lt;p&gt;그러나, Node.js 에서도 스레드 간 메모리를 공유할 수 있는 방식은 존재한다.&lt;/p&gt;
&lt;p&gt;바로, &lt;code&gt;SharedArrayBuffer&lt;/code&gt; 이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SharedArrayBuffer&lt;/code&gt; 는, Node.js 환경에서 서로 다른 스레드 환경에서 같은 메모리를 참조하게 해 준다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;SharedArrayBuffer 란?&lt;/h3&gt;
&lt;p&gt;각자 다른 메모리 공간을 가지고 있는 스레드들이,&lt;/p&gt;
&lt;p&gt;같은 메모리 공간을 참조할 수 있게 만들어 주는 배열이다.&lt;/p&gt;
&lt;p&gt;메인 스레드가 워커 스레드에게 값을 전달 할 때 해당 스레드의 메모리 공간을 생성하거나,&lt;/p&gt;
&lt;p&gt;워커 스레드에게 값을 전달 할 때 메인 스레드 공간의 메모리 공간을 생성 할 필요가 없다.&lt;/p&gt;
&lt;p&gt;하지만, 편의성을 주장하는 Node.js 와 달리, 사용하기 위해 몇 가지 절차가 필요하다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;길이는 바이트 단위로 선언해야 한다&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SharedArrayBuffer&lt;/code&gt; 는 고정된 길이의 원시 바이너리 데이터 버퍼를 표현해야 한다.&lt;/p&gt;
&lt;p&gt;따라서, 스레드 간 공유될 이 데이터 버퍼가, 어떤 데이터 타입의 배열을 가지게 되느냐에 따라서&lt;/p&gt;
&lt;p&gt;가질 수 있는 길이가 제한된다. &lt;/p&gt;
&lt;p&gt;예를 들어, 우리가 일반적으로 사용하는 (Java, C 라고 가정) 정수의 타입은, &lt;code&gt;Int&lt;/code&gt; 이며, 이는 4 BYTE 이다.&lt;/p&gt;
&lt;p&gt;따라서, &lt;code&gt;SharedArrayBuffer&lt;/code&gt; 는 4 바이트 정수 타입으로 사용 할 것이기 때문에,&lt;/p&gt;
&lt;p&gt;길이는 4 의 배수가 되어야 한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// Node.js 환경에서 편하게 생성하던 배열의 형식
const arr = [1, 2, 3, 4];

// SharedArrayBuffer 에서 사용하는 배열의 형식 - 16 Byte 데이터 버퍼 생성 - 모든 스레드 공유 가능.
const sharedArr = new SharedArrayBuffer(16);

// 이를 바탕으로, 모든 스레드에서 공유 가능한 4 byte Integer 형식의 배열 생성. - Length : 4
const intArr = new Int32Array(sharedArr);
intArr[0] = 5;
intArr[1] = 6;
intArr[2] = 7;
intArr[3] = 8;

//Result

for(let i in arr) {
  console.log(`arr 내용물 : ${i}`);
}

// 위와 같은 for..in 방식은 안된다.
for(let i = 0; i &amp;lt; intArr.length; i++) {
  console.log(`intArr 내용물 : ${intArr[i]}`);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt; &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  worker-exam node main.js
arr 내용물 : 0
arr 내용물 : 1
arr 내용물 : 2
arr 내용물 : 3
intArr 내용물 : 5
intArr 내용물 : 6
intArr 내용물 : 7
intArr 내용물 : 8&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;한 번 생성된 배열은 &lt;code&gt;.append()&lt;/code&gt; 와 같이 사용해서는 안되고, (byte 단위 데이터 버퍼이기 때문)&lt;/p&gt;
&lt;p&gt;전용 메서드인 &lt;code&gt;grow()&lt;/code&gt; 로 길이를 늘려야 한다.&lt;/p&gt;
&lt;p&gt;물론, 다시 &lt;code&gt;Int32Array&lt;/code&gt; 로 감싸야 할 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;SharedArrayBuffer 를 통해 스레드 간 메모리를 공유해보자&lt;/h3&gt;
&lt;p&gt;이제, &lt;code&gt;Worker&lt;/code&gt; 예제와 함께, Main 스레드와 Worker 스레드 간 메모리를 공유 해 보자.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그런데, 나는 커스텀 이벤트 문자열을 만들어서 적용하려고 시도했다.&lt;/p&gt;
&lt;p&gt;그럼에도 불구하고 &lt;code&gt;parentPort.on(&amp;#39;plus&amp;#39;)&lt;/code&gt; 와 같은 커스텀 이벤트는 되지 않았다.&lt;/p&gt;
&lt;p&gt;커스텀 이벤트를 제작하기 위해서는, 미리 정해진 &lt;code&gt;&amp;#39;message&amp;#39;&lt;/code&gt; 로 전달하는 것이 &lt;/p&gt;
&lt;p&gt;옳다고 한다.&lt;/p&gt;
&lt;p&gt;따라서, 모든 이벤트를 &lt;code&gt;&amp;#39;message&amp;#39;&lt;/code&gt; 내부에 들어가도록 변경했다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import { Worker, isMainThread, parentPort, workerData } from &amp;#39;worker_threads&amp;#39;;

interface MainReceiveMessage {
  type : string;
  value : undefined | string;
}

interface WorkerReceiveMessage {
  type : string;
  value : undefined | number;
}

if (isMainThread) {
  function TestSharingMemory(arr : number[]) {
    return new Promise&amp;lt;Int32Array&amp;gt;((resolve, reject) =&amp;gt; {
      // 4Byte Integer Array 가정.
      const sharedArrayBuffer = new SharedArrayBuffer(4 * arr.length);

      // int 배열로 사용할 수 있도록 제작.
      const int32Array = new Int32Array(sharedArrayBuffer);

      // 들어온 변수를 공유 배열 메모리에 할당한다.
      for(let i = 0; i &amp;lt; arr.length; i++) {
        int32Array[i] = arr[i];
      }

      // 배열 계산 과정을 수행할 워커 생성(초기화)
      const worker = new Worker(__filename, {
        workerData : int32Array
      });

      // 워커에게 이 에러를 받으면, 이러한 행동을 한다고 선언.
      worker.on(&amp;#39;error&amp;#39;, async (err) =&amp;gt; {
        reject(err);
        await worker.terminate();
      })

      worker.on(&amp;#39;message&amp;#39;, async (message : MainReceiveMessage) =&amp;gt; {

        if(message.type === &amp;#39;done&amp;#39;){
          console.log(&amp;quot;지정된 워커의 계산이 끝남.&amp;quot;)
          console.log(message.value);

        } else if(message.type === &amp;#39;close&amp;#39;) {
          resolve(int32Array);
          await worker.terminate();

        } else {
          console.log(`워커 스레드가 메세지를 전해옴 : ${message}`);
        }
      })

      worker.postMessage({
        type : &amp;#39;plus&amp;#39;,
        value : 10
      });

      worker.postMessage({
        type : &amp;#39;minus&amp;#39;,
        value : 20
      });

      worker.postMessage({
        type : &amp;#39;close&amp;#39;
      });
    })
  }

  TestSharingMemory([1, 2, 3, 4, 5]).then((arr) =&amp;gt; {
    console.log(&amp;quot;---------------&amp;quot;);
    console.log(`최종 결과 : `);
    console.log(arr.toString());
  })
} else {

  function calculatePlusArr(arr : Int32Array, num : number) {
    // 공유된 배열 메모리의 각 요소를 주어진 만큼 증가시킨다.
    for(let i = 0; i &amp;lt; arr.length; i++) {
      arr[i] = arr[i] + num;
    }
  }
  function calculateMinusArr(arr : Int32Array, num : number) {
    // 공유된 배열 메모리의 각 요소를 주어진 만큼 감소시킨다.
    for(let i = 0; i &amp;lt; arr.length; i++) {
      arr[i] = arr[i] - num;
    }
  }

  // 워커 데이터로 공유 메모리를 참조한다.
  const int32arr : Int32Array = workerData;

  parentPort.on(&amp;#39;message&amp;#39;, (msg : WorkerReceiveMessage) =&amp;gt; {
    switch (msg.type) {
      case &amp;quot;plus&amp;quot;:
        parentPort.postMessage(&amp;quot;플러스 계산 수행 시작 : &amp;quot; + msg.value);
        calculatePlusArr(int32arr, msg.value);
        parentPort.postMessage({
          type : &amp;quot;done&amp;quot;,
          value : int32arr.toString()
        })
        break;
      case &amp;quot;minus&amp;quot;:
        parentPort.postMessage(&amp;quot;마이너스 계산 수행 : &amp;quot; + msg.value);
        calculateMinusArr(int32arr, msg.value);
        parentPort.postMessage({
          type : &amp;quot;done&amp;quot;,
          value : int32arr.toString()
        });
        break;
      case &amp;quot;close&amp;quot;:
        console.log(&amp;quot;메인 스레드에서 종료 메세지가 왔으니, 상태를 확인하고 반환.&amp;quot;);

        // 확인할 상태가 있다면, 같이 전달.
        parentPort.postMessage({
          type : &amp;quot;close&amp;quot;
        });
        break;
    }
  })
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt; : &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  worker-exam node node-exam-5.js

워커 스레드가 메세지를 전해옴 : 플러스 계산 수행 시작 : 10 # parentPort.postMessage(&amp;quot;플러스 계산 수행 시작 : &amp;quot; + msg.value);
지정된 워커의 계산이 끝남. # console.log(&amp;quot;지정된 워커의 계산이 끝남.&amp;quot;)
11,12,13,14,15 # console.log(message.value);
워커 스레드가 메세지를 전해옴 : 마이너스 계산 수행 : 20 # ...
지정된 워커의 계산이 끝남. # ...
-9,-8,-7,-6,-5 # ...
---------------
최종 결과 : 
-9,-8,-7,-6,-5 # TestSharingMemory([1, 2, 3, 4, 5]).then((arr) =&amp;gt; { ...
메인 스레드에서 종료 메세지가 왔으니, 상태를 확인하고 반환. # 워커를 이제서야 반환한다!&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;p&gt;어느 누구라도, 다른 사람이 작성한 코드를 읽기란 매우 어려운 일이다.&lt;/p&gt;
&lt;p&gt;자신이 작성한 코드라도 헷갈려 하는 세상에서,&lt;/p&gt;
&lt;p&gt;남이 내가 작성한 코드를 한눈에 읽을 것이라는 생각은 버리겠다. (나의 클린 코드의 문제일 것이다!)&lt;/p&gt;
&lt;p&gt;따라서, 메인 스레드에서의 코드, 워커 스레드에서의 코드를 분리해서 이해해 보자.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;Main Thread&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// 메세지 전달 객체를 기반으로 데이터를 전달 할 것이다.
interface MainReceiveMessage {
  type : string;
  value : undefined | string;
}

// 메인 스레드 코드 시작 
function TestSharingMemory(arr : number[]) {
  return new Promise&amp;lt;Int32Array&amp;gt;((resolve, reject) =&amp;gt; {
    // 4Byte Integer Array 가정.
    const sharedArrayBuffer = new SharedArrayBuffer(4 * arr.length);

    // int 배열로 사용할 수 있도록 제작.
    const int32Array = new Int32Array(sharedArrayBuffer);

    // 들어온 변수를 공유 배열 메모리에 할당한다.
    for(let i = 0; i &amp;lt; arr.length; i++) {
      int32Array[i] = arr[i];
    }

    // 배열 계산 과정을 수행할 워커 생성(초기화)
    const worker = new Worker(__filename, {
      workerData : int32Array
    });

    // 워커에게 이 에러를 받으면, 이러한 행동을 한다고 선언.
    worker.on(&amp;#39;error&amp;#39;, async (err) =&amp;gt; {
      reject(err);
      await worker.terminate();
    })

    worker.on(&amp;#39;message&amp;#39;, async (message : MainReceiveMessage) =&amp;gt; {

      if(message.type === &amp;#39;done&amp;#39;){
        console.log(&amp;quot;지정된 워커의 계산이 끝남.&amp;quot;)
        console.log(message.value);

      } else if(message.type === &amp;#39;close&amp;#39;) {
        resolve(int32Array);
        await worker.terminate();

      } else {
        console.log(`워커 스레드가 메세지를 전해옴 : ${message}`);
      }
    })

    worker.postMessage({
      type : &amp;#39;plus&amp;#39;,
      value : 10
    });

    worker.postMessage({
      type : &amp;#39;minus&amp;#39;,
      value : 20
    });

    worker.postMessage({
      type : &amp;#39;close&amp;#39;
    });
  })
}

TestSharingMemory([1, 2, 3, 4, 5]).then((arr) =&amp;gt; {
  console.log(&amp;quot;---------------&amp;quot;);
  console.log(`최종 결과 : `);
  console.log(arr.toString());
})&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 코드를 작성 한 이유는, &lt;code&gt;SharedArrayBuffer&lt;/code&gt; 를 이용하여,&lt;/p&gt;
&lt;p&gt;서로 다른 쓰레드에서 메모리를 공유 할 수 있다는 것을 증명하기 위함이다.&lt;/p&gt;
&lt;p&gt;즉, 서로 다른 메모리 공간을 가질 수 밖에 없는 스레드들에게, 공유 컨텍스트를 제공하는 것이다.&lt;/p&gt;
&lt;p&gt;그리고, 메인 스레드와 워커 스레드가 서로에게 이벤트를 날리는데,&lt;/p&gt;
&lt;p&gt;이 이벤트는 &lt;code&gt;&amp;#39;message&amp;#39;&lt;/code&gt; 로 한정되어 있다는 말을 하는 것이다.&lt;/p&gt;
&lt;p&gt;커스텀 이벤트는 등록이 불가하며,&lt;/p&gt;
&lt;p&gt;이벤트를 받는 쪽은 &lt;code&gt;on(&amp;#39;message&amp;#39;, (msg) =&amp;gt; {...})&lt;/code&gt; 를 작성하며,&lt;/p&gt;
&lt;p&gt;이벤트를 보내는 쪽은, &lt;code&gt;postMessage({type : ..., value : ...})&lt;/code&gt; 를 작성한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;여기서, 커스텀 이벤트는 등록할 수 없다.&lt;/p&gt;
&lt;p&gt;그렇다면, 메인 스레드에서 워커 스레드에게 &amp;quot;특정 행동&amp;quot; 을 지시하려면 어떻게 해야 할까?&lt;/p&gt;
&lt;p&gt;그것은 메세징 객체에 &lt;code&gt;type&lt;/code&gt; 을 보내는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;양방향 통신을 위해 중간에 보낼 수 있는 이벤트는 &lt;code&gt;message&lt;/code&gt; 밖에 없다.&lt;/p&gt;
&lt;p&gt;그 외에는 특정 상황밖에 없다.&lt;/p&gt;
&lt;p&gt;그렇다면, &lt;code&gt;message&lt;/code&gt; 이벤트와 함께, 내부 객체를 스스로 정의하는 것이다.&lt;/p&gt;
&lt;p&gt;이를 통해 위 코드에서는 &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;배열에 특정 값을 모두 더하기&lt;/li&gt;
&lt;li&gt;배열에 특정 값을 모두 빼기&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;를 구별하였다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이 워커 인스턴스를 Promise 로 감싸서 실행하는 &lt;code&gt;TestSharingMemory&lt;/code&gt; 함수는,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;resolve&lt;/code&gt; 를 통해 전달된 정수 배열을 최종 결과로 출력하는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;Worker Thread&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;interface WorkerReceiveMessage {
  type : string;
  value : undefined | number;
}

function calculatePlusArr(arr : Int32Array, num : number) {
  // 공유된 배열 메모리의 각 요소를 주어진 만큼 증가시킨다.
  for(let i = 0; i &amp;lt; arr.length; i++) {
    arr[i] = arr[i] + num;
  }
}
function calculateMinusArr(arr : Int32Array, num : number) {
  // 공유된 배열 메모리의 각 요소를 주어진 만큼 감소시킨다.
  for(let i = 0; i &amp;lt; arr.length; i++) {
    arr[i] = arr[i] - num;
  }
}

// 워커 데이터로 공유 메모리를 참조한다.
const int32arr : Int32Array = workerData;

parentPort.on(&amp;#39;message&amp;#39;, (msg : WorkerReceiveMessage) =&amp;gt; {
  switch (msg.type) {
    case &amp;quot;plus&amp;quot;:
      parentPort.postMessage(&amp;quot;플러스 계산 수행 시작 : &amp;quot; + msg.value);
      calculatePlusArr(int32arr, msg.value);
      parentPort.postMessage({
        type : &amp;quot;done&amp;quot;,
        value : int32arr.toString()
      })
      break;
    case &amp;quot;minus&amp;quot;:
      parentPort.postMessage(&amp;quot;마이너스 계산 수행 : &amp;quot; + msg.value);
      calculateMinusArr(int32arr, msg.value);
      parentPort.postMessage({
        type : &amp;quot;done&amp;quot;,
        value : int32arr.toString()
      });
      break;
    case &amp;quot;close&amp;quot;:
      console.log(&amp;quot;메인 스레드에서 종료 메세지가 왔으니, 상태를 확인하고 반환.&amp;quot;);

      // 확인할 상태가 있다면, 같이 전달.
      parentPort.postMessage({
        type : &amp;quot;close&amp;quot;
      });
      break;
  }
})&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Main Thread 에서 자신이 받을 커스텀 이벤트를 &lt;/p&gt;
&lt;p&gt;&lt;code&gt;message&lt;/code&gt; 이벤트에 정의된 전달 객체 내부 &lt;code&gt;type&lt;/code&gt; 으로 정의했듯이,&lt;/p&gt;
&lt;p&gt;워커 프로세스도 기본적으로 &lt;code&gt;message&lt;/code&gt; 이벤트를 받되,&lt;/p&gt;
&lt;p&gt;내부 &lt;code&gt;type&lt;/code&gt; 에 따라, 서로 다른 함수를 실행하여 공통 배열 메모리 데이터를 변형하거나,&lt;/p&gt;
&lt;p&gt;메인 스레드에서 자신을 종료하기 위해 닫는다고 메세징 할 때,&lt;/p&gt;
&lt;p&gt;뒤처리를 할 수 있도록 제작하였다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;switch&lt;/code&gt; 문을 사용하니, 좀 더 간결해 보여 사용했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;위의 코드는 외부에서 특정 숫자 배열을 넣고,&lt;/p&gt;
&lt;p&gt;해당 결과를 확인할 수 있는 방식으로 제작되었다.&lt;/p&gt;
&lt;p&gt;그렇다면, 만약에, 해당 워커를 생성 해 놓고,&lt;/p&gt;
&lt;p&gt;종료하지 않으며 재사용 하려면 어떻게 해야 할까?&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;바로 &lt;code&gt;resolve&lt;/code&gt; 를 배열의 결과가 아닌, &lt;code&gt;worker&lt;/code&gt; 로 하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Main Thread&lt;/strong&gt; &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;function TestSharingMemory(arr : number[]) {
  return new Promise&amp;lt;Worker&amp;gt; ((resolve, reject) =&amp;gt; {
    // 4Byte Integer Array 가정.
    const sharedArrayBuffer = new SharedArrayBuffer (4 * arr.length);

    // int 배열로 사용할 수 있도록 제작.
    const int32Array = new Int32Array (sharedArrayBuffer);

    // 들어온 변수를 공유 배열 메모리에 할당한다.
    for (let i = 0; i &amp;lt; arr.length; i++) {
      int32Array[i] = arr[i];
    }

    // 배열 계산 과정을 수행할 워커 생성(초기화)
    const worker = new Worker (__filename, {
      workerData: int32Array
    });

    // 워커에게 이 에러를 받으면, 이러한 행동을 한다고 선언.
    worker.on (&amp;#39;error&amp;#39;, async (err) =&amp;gt; {
      reject (err);
      await worker.terminate ();
    })

    // 워커 스레드로부터 &amp;quot;.postMessage(..)&amp;quot; 를 받았을 때 행동한다고 선언.
    worker.on (&amp;#39;message&amp;#39;, async (message: MainReceiveMessage) =&amp;gt; {

      if (message.type === &amp;#39;done&amp;#39;) {
        console.log (&amp;quot;지정된 워커의 계산이 끝남.&amp;quot;)
        console.log (message.value);

      } else if (message.type === &amp;#39;close&amp;#39;) {
        await worker.terminate ();

      } else {
        console.log (`워커 스레드가 메세지를 전해옴 : ${message}`);
      }
    })

    resolve (worker);
  })
}

TestSharingMemory([1, 2, 3, 4, 5]).then((worker) =&amp;gt; {
  worker.postMessage({
    type : &amp;#39;plus&amp;#39;,
    value : 10
  });

  worker.postMessage({
    type : &amp;#39;minus&amp;#39;,
    value : 20
  });

  worker.postMessage({
    type : &amp;#39;close&amp;#39;
  });
})&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt; : &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  worker-exam tsc
➜  worker-exam node node-exam-6.js

워커 스레드가 메세지를 전해옴 : 플러스 계산 수행 시작 : 10
지정된 워커의 계산이 끝남.
11,12,13,14,15
워커 스레드가 메세지를 전해옴 : 마이너스 계산 수행 : 20
지정된 워커의 계산이 끝남.
-9,-8,-7,-6,-5
메인 스레드에서 종료 메세지가 왔으니, 상태를 확인하고 반환.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;외부에서 &amp;quot;직접&amp;quot; 생성된 워커의 종료 시기를 정할 수 있으므로,&lt;/p&gt;
&lt;p&gt;이는 즉 워커 풀을 생성하고 보존할 수 있다는 이야기이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;후기&lt;/h2&gt;
&lt;p&gt;이 글을 작성하는 데, 3 일이 걸렸다.&lt;/p&gt;
&lt;p&gt;싱글 스레드인 Node.js 가 어떻게 멀티 스레드로 운용될 수 있는지에 대한 공부,&lt;/p&gt;
&lt;p&gt;그리고 공유 컨텍스트는 무엇으로 생성되는 지에 대해서 공부했다. (EX &lt;code&gt;Atomic&lt;/code&gt;)&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이를 통해서, C 언어의 &lt;code&gt;allocate&lt;/code&gt; 문법과 비슷하게 데이터 바이트 배열을 생성했다.&lt;/p&gt;
&lt;p&gt;자율 문법을 추구하는 Node.js 에서 할당 기법을 사용 할 정도로 공부했으니, 나도 꽤 만족했다.&lt;/p&gt;
&lt;p&gt;그리고 가장 중요한 것 중 하나는, Node.js 도 스레드 풀을 가질 수 있다는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;Node.js 의 스레드 생성 방식을 익히기 전까지는,&lt;/p&gt;
&lt;p&gt;Docker 의 컨테이너 마다 하나의 스레드만을 할당하여, 비효율적이라고 생각했다.&lt;/p&gt;
&lt;p&gt;그도 그럴 것이, 아무리 Event Driven I/O 방식이라지만, 결국엔 1 개의 스레드이기 때문이다.&lt;/p&gt;
&lt;p&gt;심지어 원래 빠른 Java 는 멀티 쓰레드를 통해 연결 I/O 를 담당하는 스레드가 미리 준비되어 있었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;하지만, 이번에 Node.js 환경에서의 멀티 스레딩 및 병렬 방식을 배우게 되면서,&lt;/p&gt;
&lt;p&gt;프레임워크가 해결 해 주지 못하는 CPU 집약 계산을 최적화하는 법을 배우게 되었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;다음 포스팅은 웹 어셈블리 (Web Assembly) 이다.&lt;/p&gt;
&lt;p&gt;나는 Node.js 가 백엔드로서의 위상이 낮을 수 밖에 없다는 것을 깨닫았다.&lt;/p&gt;
&lt;p&gt;아무리 편하고 Spring 스럽지만, 결국 속도로 인해 성장의 제한은 걸려있었다.&lt;/p&gt;
&lt;p&gt;하지만, 멀티 스레딩에, 웹 어셈블리까지 조합한다면, 더 빨라지지 않을까? 생각한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;참고 사이트&lt;/h2&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://morsoftware.com/blog/fastest-programming-languages&quot;&gt;https://morsoftware.com/blog/fastest-programming-languages&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://nodejs.org/docs/latest/api/worker_threads.html&quot;&gt;https://nodejs.org/docs/latest/api/worker_threads.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://nodejs.org/docs/latest/api/cluster.html&quot;&gt;https://nodejs.org/docs/latest/api/cluster.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer&quot;&gt;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://grepper.tistory.com/122&quot;&gt;https://grepper.tistory.com/122&lt;/a&gt;&lt;/p&gt;</description>
      <category>Node.js/잡다 지식</category>
      <category>javascript</category>
      <category>Multi thread</category>
      <category>node.js</category>
      <category>nodejs</category>
      <category>web worker</category>
      <category>worker</category>
      <category>구현</category>
      <category>노드</category>
      <category>멀티 스레드</category>
      <category>자바스크립트</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/201</guid>
      <comments>https://codecreature.tistory.com/201#entry201comment</comments>
      <pubDate>Mon, 7 Apr 2025 00:29:12 +0900</pubDate>
    </item>
    <item>
      <title>package.json 과 package-lock.json 은 무엇일까?</title>
      <link>https://codecreature.tistory.com/200</link>
      <description>&lt;h2&gt;제목 : package.json 과 package-lock.json&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;이 두 파일을 접하게 된 시기는 2~3년 전, 처음으로 &lt;strong&gt;React&lt;/strong&gt; 라이브러리를 설치하는 과정에서,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;package.json&lt;/code&gt; 과 &lt;code&gt;package-lock.json&lt;/code&gt; 이 생겨나는 것을 보았을 때 였다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;항상 처음 라이브러리나 어떠한 프레임워크를 사용 할 때, 해당 라이브러리나 프레임워크에 집중하듯&lt;/p&gt;
&lt;p&gt;저 위의 2 파일은 내 관심사의 밖이었었다.&lt;/p&gt;
&lt;p&gt;심지어는 JavaScript 를 제대로 익히지 않고 먼저 React 에 집중하게 되었으니, 당연한 결과이기도 했다.&lt;/p&gt;
&lt;br&gt;

&lt;p&gt;그러나, Node.js 엔진을 이용한 라이브러리와 프레임워크를 이용하며 체계적인 폴더 구조를 익히고,&lt;/p&gt;
&lt;p&gt;그 내부의 철학 또한 익히는 과정에서 &lt;code&gt;package.json&lt;/code&gt; 과 &lt;code&gt;package-lock.json&lt;/code&gt; 이&lt;/p&gt;
&lt;p&gt;이러한 라이브러리와 프레임워크를 지탱하고 있다는 것을 알게 되었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;심지어는 &lt;code&gt;CI/CD&lt;/code&gt; 과정에서 &lt;code&gt;package.json&lt;/code&gt; 내부의 &lt;code&gt;script&lt;/code&gt; 를 이용해서 &lt;/p&gt;
&lt;p&gt;어플리케이션을 실행하거나, 실행 전 테스팅 파일이 통과하는지를 검사하거나,&lt;/p&gt;
&lt;p&gt;내 스스로의 명령어 스크립트를 만들어서 &lt;code&gt;script&lt;/code&gt; 에 추가 할 수도 있었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그러나, &lt;code&gt;package.json&lt;/code&gt; 의 역할은 이것 뿐 만이 아니었다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;package.json&lt;/code&gt; 은 &lt;code&gt;{}&lt;/code&gt; 내부에 적힌 수많은 옵션들이 존재했는데,&lt;/p&gt;
&lt;p&gt;이는 NPM 레지스트리에 내가 창조한 라이브러리를 다른 유저들이 사용할 수도 있게끔 만들 수도 있었다.&lt;/p&gt;
&lt;p&gt;위의 역할은 프로그래머스 부트캠프 강의 중 알게 된 것이었다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;.json 확장자란?&lt;/h3&gt;
&lt;p&gt;나는 &lt;code&gt;.json&lt;/code&gt; 확장자를 가진 파일은 &lt;code&gt;xml&lt;/code&gt; 형식의 저장 파일들의 가독성을 해결하기 위해 나왔다고 알고 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;xml 을 접하게 된 개인적인 경험&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;대학교 재학 시절, 군대에 속하되, 대학교에서 강의를 하시는 교수님이 계셨다.&lt;/p&gt;
&lt;p&gt;강의 내용은 전쟁의 역사와 나라가 평화를 이룩하는 방법에 대해서 논의하는 강의였었다.&lt;/p&gt;
&lt;p&gt;나는 보통 강의를 듣고 나서, 다들 나간 강의실에서 혼자 노트북에 코딩하는 것을 좋아했다.&lt;/p&gt;
&lt;p&gt;그 때 당시 쿠팡의 사이트 렌더링 과정에서 나온 부산물이,&lt;/p&gt;
&lt;p&gt;쿠팡에서 제품을 판매하는 회사에서 제공하는 구독 프로그램(월 100 만원) 에 치명적인 정보이기에,&lt;/p&gt;
&lt;p&gt;나는 이것을 분석하여 실제 프로그램으로 만들었으며, 이를 짧은 10초 동영상으로 제출하여&lt;/p&gt;
&lt;p&gt;쿠팡 본사에 제출했었던 시기였다. - 결국은 내가 3,000 달러의 포상금을 얻었다. &lt;del&gt;세금으로 80 만원을 뜯겼다..&lt;/del&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이야기가 좀 샜는데, 그 때 혼자 남아서 노트북에 열심히 무언가를 두드리는 내가 신기하셨는지,&lt;/p&gt;
&lt;p&gt;교수님하고 이야기를 하게 되었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;교수님은 제대로 정규화되지 않아 빠져있는 해양 선박 데이터를 규합하고 싶어하셨고,&lt;/p&gt;
&lt;p&gt;나는 공공데이터를 이용하여 &amp;quot;적어도 한 번은 등록된 적이 있는&amp;quot; 데이터를 전부 규합했다.&lt;/p&gt;
&lt;p&gt;물론 옛날에 수기로 등록된 데이터에 의해 빠진 정보가 존재했지만, 교수님도 충분히 만족하셨었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이 때, 내가 공공데이터에 요청한 데이터는 &lt;code&gt;xml&lt;/code&gt; 의 형태로 날아왔었다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;xml&lt;/code&gt; 은 &lt;code&gt;&amp;lt;&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;/&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;&amp;gt; &amp;lt;&amp;gt;&amp;lt;/&amp;gt; &amp;lt;/&amp;gt;&lt;/code&gt; 이러한 형태로 데이터가 날아오는 형식이다.&lt;/p&gt;
&lt;p&gt;그러니까, &lt;code&gt;html&lt;/code&gt; 을 보는 것과 매우 유사하다고 말할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;어지러운 데이터를 보다 보니, 새로운 데이터 전송 방식인 &lt;code&gt;json&lt;/code&gt; 이 왜 탄생했는지 알 것 같았다.&lt;/p&gt;
&lt;p&gt;하지만, 옛날에 사용되던 방식인 &lt;code&gt;xml&lt;/code&gt; 은 옛날 통신 장비들이 여전히 사용되고 있다.&lt;/p&gt;
&lt;p&gt;따라서, 공공데이터가 &lt;code&gt;xml&lt;/code&gt; 을 제공하던 것도, 진화를 하지 않은 것이 아니라, 일종의 최적화라고 판단했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;요약해서, &lt;code&gt;.json&lt;/code&gt; 확장자의 파일은, &lt;code&gt;.xml&lt;/code&gt; 의 데이터 형식의 가독성을 매우 높였다고 말할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;package.json 을 알기 전에, NPM 부터!&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;NPM 은, Node Package Manager&lt;/strong&gt; 의 약자이다. &lt;/p&gt;
&lt;p&gt;사실, &lt;code&gt;package.json&lt;/code&gt; 에 대해서 작성하기 전에, &lt;code&gt;npm&lt;/code&gt; 에 대해서 먼저 글을 작성해야 한다고 지금 생각이 든다.&lt;/p&gt;
&lt;p&gt;아마 곧 작성할 것 같다.&lt;/p&gt;
&lt;p&gt;이 글은 &lt;strong&gt;package.json&lt;/strong&gt; 에 대해서 얘기하므로, 간단하게 짚고 넘어가도록 하자.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;npm&lt;/code&gt; 을 알게 되는 경로는 다양할 것이라고 생각한다.&lt;/p&gt;
&lt;p&gt;하지만, 그 중에서도 대다수의 사람들이 Node.js 엔진에서 파생된 라이브러리, 혹은 프레임워크를 사용하기 위해&lt;/p&gt;
&lt;p&gt;혹은 수업에서 사용하니까 &lt;code&gt;npm init --y&lt;/code&gt; 로 처음 접해보았을 것이다.&lt;/p&gt;
&lt;p&gt;이는 &lt;code&gt;npm&lt;/code&gt; 명령어를 사용하여 라이브러리를 설치하기 전에,&lt;/p&gt;
&lt;p&gt;설치된 라이브러리의 메타데이터(설정정보 및 버전, 레지스트리 주소...) 를 저장하기 위해&lt;/p&gt;
&lt;p&gt;&lt;code&gt;package.json&lt;/code&gt; 을 미리 만들어 두는 작업이다.&lt;/p&gt;
&lt;p&gt;&lt;del&gt;&lt;code&gt;npm init&lt;/code&gt; 으로 하면, 조금 설정이 귀찮아 지기도 한다.&lt;/del&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;기본적으로 &lt;code&gt;npm&lt;/code&gt; 은 로컬 스토리지(&lt;code&gt;내 플젝&lt;/code&gt;) 에 &lt;code&gt;package.json&lt;/code&gt; 을 만들기도 하지만,&lt;/p&gt;
&lt;p&gt;아예 &amp;quot;전역 환경&amp;quot; 을 구축해 주기도 한다.&lt;/p&gt;
&lt;p&gt;그런데, 나는 HomeBrew 라는 전역 패키지 매니저를 사용해서, 굳이 &lt;code&gt;npm&lt;/code&gt; 을 이용해서 내 컴퓨터에&lt;/p&gt;
&lt;p&gt;전역 명령어를 설치하지는 않는다.&lt;/p&gt;
&lt;p&gt;요약 : &lt;code&gt;npm&lt;/code&gt; 은 프로젝트에 설치 할 때 &lt;code&gt;package.json&lt;/code&gt; 을 이용하고, &lt;/p&gt;
&lt;p&gt;시스템 전반에서 이용하게 만들려면, &lt;code&gt;npm i -g xxxxx&lt;/code&gt; 로 설치한다.&lt;/p&gt;
&lt;p&gt;이는 로컬 컴퓨터가 아닌, 리눅스 인스턴스에서 사용하기 편하겠다는 생각을 한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;웹 페이지로서의 NPM&lt;/h3&gt;
&lt;p&gt;나는 &lt;code&gt;package.json&lt;/code&gt;, &lt;code&gt;package-lock.json&lt;/code&gt; 을 배우기 위해서,&lt;/p&gt;
&lt;p&gt;웹 페이지로서의 NPM 도 알아야 한다고 생각한다.&lt;/p&gt;
&lt;p&gt;그 이유는, 방대한 라이브러리 속에서 내가 원하는 라이브러리를 찾기 위해서,&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.npmjs.com/&quot;&gt;NPM 사이트&lt;/a&gt; 에 들어가야 하기 때문이다.&lt;/p&gt;
&lt;p&gt;그리고, NPM 오픈소스를 등록하는 사람들도 이 사이트를 이용한다.&lt;/p&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;h2&gt;package.json&lt;/h2&gt;
&lt;h3&gt;의존성을 사용하기 위한 측면&lt;/h3&gt;
&lt;p&gt;사실, 위에서 NPM 에 대해서 설명 한 이유는 &lt;code&gt;package.json&lt;/code&gt; 과 이어진다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;package.json&lt;/code&gt; 은, &lt;code&gt;npm&lt;/code&gt; 명령어가 의존성을 관리하기 위해 만들어 진 파일이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;의존성이란 무엇일까?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;물론 Vanilla JavaScript 로 Node.js 엔진을 구동하거나,&lt;/p&gt;
&lt;p&gt;웹 페이지를 라이브러리 없이 구현하는 사람들도 존재한다.&lt;/p&gt;
&lt;p&gt;그러나, Node.js 엔진 스펙에 추가되지 않아 필요해진 수 많은 라이브러리들을 추가 해 주어야 한다.&lt;/p&gt;
&lt;p&gt;이러한 라이브러리들은 &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;npm install --save xxxx&lt;/code&gt; or &lt;code&gt;npm i xxxx&lt;/code&gt; - &lt;code&gt;package.json&lt;/code&gt; 이 속한 프로젝트에 의존성 추가&lt;/li&gt;
&lt;li&gt;&lt;code&gt;npm install --save-dev xxxx&lt;/code&gt; or &lt;code&gt;npm i -D xxxx&lt;/code&gt; - 파일이 속한 프로젝트에 &amp;quot;개발&amp;quot; 의존성 추가&lt;/li&gt;
&lt;li&gt;&lt;code&gt;npm install -g xxxx&lt;/code&gt; - &lt;code&gt;npm&lt;/code&gt; 이 설치된 기기의 위치에 전역 명령어로 사용할 수 있는 라이브러리 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이러한 형식으로 다운로드 된다.&lt;/p&gt;
&lt;p&gt;설치된 라이브러리는 &lt;code&gt;node_modules&lt;/code&gt; 라는 정해진 폴더명에 저장된다.&lt;/p&gt;
&lt;p&gt;이후, 우리는 &lt;code&gt;import&lt;/code&gt;, &lt;code&gt;require&lt;/code&gt; 문으로 외부 의존성을 쉽게 사용 할 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;의존성을 등록하기 위한 측면&lt;/h3&gt;
&lt;p&gt;위에서 말했듯이, &lt;code&gt;npm&lt;/code&gt; 명령어를 이용하여 외부 의존성(라이브러리)를 이용하는 방법도 있지만,&lt;/p&gt;
&lt;p&gt;또한 누군가가 나의 오픈소스 프로그램을 &lt;code&gt;npm&lt;/code&gt; 으로 사용하게 만들기 위해 &lt;/p&gt;
&lt;p&gt;&lt;code&gt;package.json&lt;/code&gt; 이 사용되기도 한다.&lt;/p&gt;
&lt;p&gt;실제로 &lt;code&gt;npm init --y&lt;/code&gt; 를 통해 간단하게 &lt;code&gt;package.json&lt;/code&gt; 의 초기 모습을 본다면,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ cat package.json 
{
  &amp;quot;name&amp;quot;: &amp;quot;test&amp;quot;,
  &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
  &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,
  &amp;quot;main&amp;quot;: &amp;quot;index.js&amp;quot;,
  &amp;quot;scripts&amp;quot;: {
    &amp;quot;test&amp;quot;: &amp;quot;echo \&amp;quot;Error: no test specified\&amp;quot; &amp;amp;&amp;amp; exit 1&amp;quot;
  },
  &amp;quot;keywords&amp;quot;: [],
  &amp;quot;author&amp;quot;: &amp;quot;&amp;quot;,
  &amp;quot;license&amp;quot;: &amp;quot;ISC&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;우리가 &lt;code&gt;npm install&lt;/code&gt; 혹은 &lt;code&gt;npm i -D&lt;/code&gt; 로 설치해야 나타나는 &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;dependencies&lt;/code&gt; : 의존성&lt;/li&gt;
&lt;li&gt;&lt;code&gt;devDependencies&lt;/code&gt; : 개발 의존성&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 항목은 보이지 않는다.&lt;/p&gt;
&lt;p&gt;즉, &lt;code&gt;npm&lt;/code&gt; 은 개발 의존성을 관리 해 주기도 하지만, &lt;/p&gt;
&lt;p&gt;기본적으로 &lt;code&gt;npm&lt;/code&gt; 레지스트리에 올리기 위해 준비를 해 주는 행동과도 동일하다고 볼 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;기본적인 package.json 의 옵션 의미&lt;/h3&gt;
&lt;p&gt;기초적으로 생성되는 옵션과 더불어,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;npm i&lt;/code&gt; 혹은 &lt;code&gt;npm i -D&lt;/code&gt; 를 통해 의존성을 설치했을 때 나타나는 명칭과 의미를 살펴보자.&lt;/p&gt;
&lt;br/&gt; 

&lt;h4&gt;dependencies - 의존성&lt;/h4&gt;
&lt;p&gt;이 항목은, Node.js 엔진이 실제로 사용하게 될 라이브러리들을 의미한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;express&lt;/code&gt; 와 같이, 실제로 Node.js 엔진에 장착하게 될 라이브러리를 의미한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt; : &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ npm i express # npm install 이 처음이라면, package.json 에 &amp;quot;dependencies&amp;quot; 항목이 추가됨.&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h4&gt;devDependencies - 개발 의존성&lt;/h4&gt;
&lt;p&gt;프로젝트의 산출물에 관여하지 않는 특성이 있으며, 테스팅 및 타입 리졸버에 해당한다.&lt;/p&gt;
&lt;p&gt;다양한 라이브러리들이 타입스크립트와 자신의 라이브러리를 리졸브하기 위해&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@types/&amp;lt;기존 라이브러리 이름&amp;gt;&lt;/code&gt; 으로 등록한다.&lt;/p&gt;
&lt;p&gt;이 때, &lt;code&gt;npm i -D @types/&amp;lt;기존 라이브러리 이름&amp;gt;&lt;/code&gt; 으로 개발 의존성을 추가할 수 있다.&lt;/p&gt;
&lt;p&gt;하지만, 설치하기 전에 NPM 사이트에 들어가서 누군가 기존 라이브러리에 &lt;/p&gt;
&lt;p&gt;이름 &lt;code&gt;@types/&lt;/code&gt; 만 붙여서 장난 쳐 놓은 것은 아닐지 보는 것을 추천한다.&lt;/p&gt;
&lt;p&gt;개발 의존성에는 주로 이러한 것들이 들어간다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;사용 라이브러리에 해당하는 Type Resolver&lt;/li&gt;
&lt;li&gt;테스팅 관련 라이브러리 - jest 같은 것&lt;/li&gt;
&lt;li&gt;eslint 관련 라이브러리&lt;/li&gt;
&lt;li&gt;최신 &lt;code&gt;typescript&lt;/code&gt; 자체&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;h4&gt;name - 프로젝트 폴더명&lt;/h4&gt;
&lt;p&gt;디폴트로 생성했을 때, 현재 &lt;code&gt;package.json&lt;/code&gt; 이 존재하는 프로젝트 폴더명을 가르킨다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;version - 내 오픈소스 라이브러리의 버전&lt;/h4&gt;
&lt;p&gt;만약에 자신이 npm 모듈로서 오픈소스를 배포하고자 할 때 중요한 부분인데,&lt;/p&gt;
&lt;p&gt;버전은 전 세계 모두가 보고, 컨벤션이 정해져 있으므로,&lt;/p&gt;
&lt;p&gt;Versioning Convention 을 참고하는 것이 중요하다.&lt;/p&gt;
&lt;p&gt;내가 HyperSkill 영문서를 번역한 것이 있는데,&lt;/p&gt;
&lt;p&gt;이것을 참조하면 좋을 것이라고 생각한다.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://codecreature.tistory.com/67&quot;&gt;Semantic versioning - 의미론적 버전 관리&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;description - 내 프로젝트에 대한 설명&lt;/h4&gt;
&lt;p&gt;현재 배포하는 나의 패키지에 대한 설명이며, 디폴트로 &lt;code&gt;&amp;quot;&amp;quot;&lt;/code&gt; 로 비워져 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;scripts - npm 으로 실행 가능한 실제 명령어&lt;/h4&gt;
&lt;p&gt;내가 직접 커맨드를 입력하여 커스텀 할 수도 있으며,&lt;/p&gt;
&lt;p&gt;수 많은 프레임워크들이 &lt;code&gt;build&lt;/code&gt;, &lt;code&gt;testing&lt;/code&gt;, &lt;code&gt;linting&lt;/code&gt;, &lt;code&gt;debugging&lt;/code&gt;, &lt;code&gt;execute&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;이러한 역할을 위해 미리 &lt;code&gt;scripts&lt;/code&gt; 를 선언 해 놓기도 한다.&lt;/p&gt;
&lt;p&gt;나중에 &lt;code&gt;CI&lt;/code&gt; 과정에서 유용하게 사용 할 수도 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;keywords - NPM 사이트에서 내 프로젝트를 검색 할 수 있는 키워드&lt;/h4&gt;
&lt;p&gt;NPM 사이트에서 내 패키지를 검색 할 때 어떤 키워드로 검색되었으면 좋겠는지 &lt;/p&gt;
&lt;p&gt;문자열 배열로 작성 해 놓는 옵션이다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;author - 저자&lt;/h4&gt;
&lt;p&gt;보통은 &lt;code&gt;&amp;quot;&amp;quot;&lt;/code&gt; 로 비워져 있는데, 이 패키지를 작성한 사람을 입력하는 곳이다.&lt;/p&gt;
&lt;p&gt;이메일을 입력해도 되고, 이 라이브러리를 홍보하거나 설명하는 사이트를 입력해도 된다.&lt;/p&gt;
&lt;p&gt;EX - &lt;code&gt;anybody@example.com&lt;/code&gt; or &lt;code&gt;https://example.com&lt;/code&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;license - 기본 ISC&lt;/h4&gt;
&lt;p&gt;수 많은 오픈소스들이 자신을 사용 할 수 있도록 열어놓았지만,&lt;/p&gt;
&lt;p&gt;각 오픈소스들은 자신들만의 라이센스를 만들어 놓았다.&lt;/p&gt;
&lt;p&gt;어떤 것은 상관이 없고, 어떤 것은 상업적 용도로 사용 할 수 없다.&lt;/p&gt;
&lt;p&gt;어떤 재미있는 라이센스는, &lt;strong&gt;&amp;quot;만났을 때 나 맥주 한 잔 사줘&amp;quot;&lt;/strong&gt; 도 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;homepage - 이 프로젝트를 알려주는 홈페이지&lt;/h4&gt;
&lt;p&gt;말 그대로, 이 라이브러리를 알려주는 홈페이지를 넣으면 된다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;다른 옵션들&lt;/h3&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://docs.npmjs.com/cli/v11/configuring-npm/package-json&quot;&gt;https://docs.npmjs.com/cli/v11/configuring-npm/package-json&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;h2&gt;package-lock.json&lt;/h2&gt;
&lt;p&gt;이 파일은 &lt;code&gt;package.json&lt;/code&gt; 생성 후, &lt;code&gt;npm i&lt;/code&gt; 를 실제로 수행했을 때 생성된다.&lt;/p&gt;
&lt;p&gt;혹은 곧바로 &lt;code&gt;npm i&lt;/code&gt; 를 통해 의존성을 생성해도 동일하다.&lt;/p&gt;
&lt;p&gt;이 파일은 &lt;code&gt;package.json&lt;/code&gt; 에 담긴 의존성과 개발 의존성이&lt;/p&gt;
&lt;p&gt;실제로 어떤 버전인지, 어디에서 라이브러리를 다운로드 받는지, &lt;/p&gt;
&lt;p&gt;그리고 이 라이브러리를 사용하기 위해 어떤 하위 라이브러리가 필요한지 서술한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;name&lt;/strong&gt; - &lt;code&gt;package.json&lt;/code&gt; 과 동일하다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;version&lt;/strong&gt; - &lt;code&gt;package.json&lt;/code&gt; 과 동일하다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;lockfileVersion&lt;/strong&gt; &lt;/p&gt;
&lt;p&gt;이 lock 파일 자체의 버전을 말하는데, npm v9 이후부터 사용된다고 한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;packages&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;여기서부터 다운받은 모든 의존성에 대한 메타데이터를 서술한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;packages&lt;/code&gt; 밑에 바로 &lt;code&gt;&amp;quot;&amp;quot;&lt;/code&gt; 라는 하나의 key 가 있으며,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;quot;&amp;quot;&lt;/code&gt; 밑에 &lt;code&gt;node_modules&lt;/code&gt; 에 존재하는 모든 의존성에 대한 데이터를 서술한다.&lt;/p&gt;
&lt;br/&gt;

&lt;br/&gt;

&lt;hr&gt;
&lt;h2&gt;참고 사이트&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;npm Docs&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://docs.npmjs.com/creating-a-package-json-file&quot;&gt;https://docs.npmjs.com/creating-a-package-json-file&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://docs.npmjs.com/cli/v11/configuring-npm/package-json&quot;&gt;https://docs.npmjs.com/cli/v11/configuring-npm/package-json&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://docs.npmjs.com/cli/v11/configuring-npm/package-lock-json&quot;&gt;https://docs.npmjs.com/cli/v11/configuring-npm/package-lock-json&lt;/a&gt;&lt;/p&gt;</description>
      <category>Node.js/잡다 지식</category>
      <category>npm i</category>
      <category>package-lock</category>
      <category>package-lock.json</category>
      <category>package-lock.json 옵션</category>
      <category>package.json</category>
      <category>package.json 옵션</category>
      <category>옵션</category>
      <category>옵션 의미</category>
      <category>의존성</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/200</guid>
      <comments>https://codecreature.tistory.com/200#entry200comment</comments>
      <pubDate>Thu, 3 Apr 2025 01:25:23 +0900</pubDate>
    </item>
    <item>
      <title>SQLite 데이터베이스는 무엇일까?</title>
      <link>https://codecreature.tistory.com/199</link>
      <description>&lt;h2&gt;제목 : SQLite 데이터베이스는 무엇일까?&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;고도화를 위해 &lt;strong&gt;NestJS&lt;/strong&gt; 강의를 들으며 &lt;strong&gt;SQLIte&lt;/strong&gt; 데이터베이스 엔진을 접하게 되었으며, &lt;/p&gt;
&lt;p&gt;매우 유명한 RDBMS (Relational Database Management System) 인&lt;/p&gt;
&lt;p&gt;MySQL, Oracle 과는 다르다는 것을 알게 되었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;NestJS 프로젝트에서 &lt;strong&gt;TypeORM&lt;/strong&gt; 라이브러리를 사용하여 간단하게 데이터베이스 연결 예제를 수행하게 되었는데,&lt;/p&gt;
&lt;p&gt;이 때 &lt;strong&gt;SQLite&lt;/strong&gt; 를 처음 사용하게 되었다. &lt;/p&gt;
&lt;p&gt;내가 든 생각은, Github Actions VM 머신에서 E2E 테스팅을 이용하여 RDBMS 를 모방할 수 있겠다는 생각이었다!&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;현재 나의 NestJS 예제 디렉토리는 이러하다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  mycv git:(master) ✗ tree -L 1
.
├── README.md
├── db.sqlite # SQLite 의 데이터베이스를 의미 
├── dist
├── nest-cli.json
├── node_modules
├── package-lock.json
├── package.json
├── src
├── test
├── tsconfig.build.json
└── tsconfig.json&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nest CLI 를 통해 만든 대부분의 폴더와 비슷할텐데,&lt;/p&gt;
&lt;p&gt;중간에 &lt;code&gt;db.sqlite&lt;/code&gt; 가 있는 것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;이 파일은 &lt;strong&gt;TypeORM&lt;/strong&gt; 라이브러리에서 단순히 &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;TypeOrmModule.forRoot({
  type : &amp;quot;sqlite&amp;quot;,
  database : &amp;#39;db.sqlite&amp;#39;,
  entities : [User, Report],
  synchronize : true, // 이건 개발시에만.
})&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 모듈을 &lt;code&gt;app.module.ts&lt;/code&gt; 에 넣어 적용시킨 결과물인 것이다!&lt;/p&gt;
&lt;p&gt;그리고, IntelliJ IDEA 에서 자동으로 데이터베이스 스키마 인식을 해주는 것을 알게 되었다.&lt;/p&gt;
&lt;p&gt;나는 이러한 생각이 들었다.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;XML 형식으로 저장되어 있을까? 아니면 JSON 형식으로 저장되어 있을까?&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  mycv git:(master) ✗ cat db.sqlite 

?j?}QtablereportsreportsCREATE TABLE &amp;quot;reports&amp;quot; (&amp;quot;id&amp;quot; integer PRIMARY KEY AUTOINCREMENT NOT NULL, &amp;quot;price&amp;quot; integer NOT NULL)P++Ytablesqlite_sequencesqlite_sequenceCREATE TABLE sqlite_sequence(name,seq)??tableusersusersCREATE TABLE &amp;quot;users&amp;quot; (&amp;quot;id&amp;quot; integer PRIMARY KEY AUTOINCREMENT NOT NULL, &amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;확실한 것은, 텍스트 형식으로 출력되도록 유도된 파일은 아니라는 것이었다.&lt;/p&gt;
&lt;br/&gt;

&lt;h2&gt;그래서 SQLite 가 뭐지?&lt;/h2&gt;
&lt;p&gt;우선, 편하게 넓은 지식들을 접하는 것도 좋지만,&lt;/p&gt;
&lt;p&gt;공식문서를 통해, 정확한 사실을 통해 지식을 얻기로 했다.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.sqlite.org/index.html&quot;&gt;SQLite 공식 사이트&lt;/a&gt; - &lt;del&gt;클릭하면 새로운 탭으로 이동됩니다&lt;/del&gt;&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;SQLite&lt;/strong&gt; Home 에서 말하는 것&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;SQLite 는 &lt;code&gt;C&lt;/code&gt; 언어로 이루어져 있는 라이브러리이다.&lt;/li&gt;
&lt;li&gt;이를 통해 작고, 빠르며, 독립적이고, 높은 신뢰성과, 모든 기능을 갖춘 SQL 데이터베이스 엔진을 만들었다 한다.&lt;/li&gt;
&lt;li&gt;SQLite 는 모든 모바일 폰과, 대부분의 컴퓨터에 내장되어 있다. &lt;/li&gt;
&lt;li&gt;사람들이 매일 사용하는 어플리케이션들에 기본으로 포함되어 제공된다.&lt;/li&gt;
&lt;li&gt;그리고 SQLite 는 파일 형식이다.&lt;/li&gt;
&lt;li&gt;SQLite 는 오픈 소스로 열려있는 소스 코드이며, 어떠한 목적으로도 이용 할 수 있다.&lt;/li&gt;
&lt;li&gt;SQLite 는 직접적인 파일시스템 I/O 보다 더 빠를 수 있다.&lt;/li&gt;
&lt;li&gt;메모리가 더 많을수록 속도가 더 빠르긴 하지만, 적은 메모리 환경에서도 꽤 좋은 성능을 낸다.&lt;/li&gt;
&lt;li&gt;SQLite 는 컴팩트한 라이브러리인데, 사이즈가 750KiB 보다도 작아도 모든 기능이 활성화 된다.&lt;/li&gt;
&lt;li&gt;SQLite 는 2000-05-09 부터 시작된 프로젝트이다. &lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;

&lt;h3&gt;주니어 개발자가 생각 할 수 있는 SQLite 의 용도?&lt;/h3&gt;
&lt;p&gt;프로그래밍 언어들과 프레임워크의 용도, 사용법을 배우고 있는 와중,&lt;/p&gt;
&lt;p&gt;사실 데이터베이스의 차별점과 특장점을 세세히 구분하여 적용하기는 힘들 수도 있다.&lt;/p&gt;
&lt;p&gt;바로 지금의 내 상황이 그러한데, &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;MySQL&lt;/strong&gt; 은 굉장히 신뢰성 높고, 유명하기 때문에 사용했다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;MariaDB&lt;/strong&gt; 는 MySQL 과 거의 엔진이 동일하며, 상업적 용도로 사용할 수 있는 라이선스이기 때문에 사용했다.&lt;/p&gt;
&lt;p&gt;만약에 그냥 개발자라면 훑고 지나가도 상관 없다고 생각했다. 그런데, 나의 목표는 &lt;strong&gt;엔지니어&lt;/strong&gt; 이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이러한 블로그 글, 혹은 알게 된 지식을 남기는 것이 중요하다는 것을 알게 된 것은 최근이다.&lt;/p&gt;
&lt;p&gt;지식을 습득하는 것은 쉽지만, 내 것으로 만들기는 굉장히 어렵다고 생각했기 때문이다.&lt;/p&gt;
&lt;p&gt;따라서, 나는 최근 학습과 동시에 모르는 것을 글로 남기기로 결정했다.&lt;/p&gt;
&lt;p&gt;따라서 이러한 소제목 &amp;quot;주니어 개발자가 생각 할 수 있는 SQLite 의 용도&amp;quot; 로 글을 작성해본다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;SQLite&lt;/strong&gt; 는 굉장히 컴팩트하고, 어플리케이션 자체에 내장할 수 있다.&lt;/p&gt;
&lt;p&gt;이러한 특성으로, 사용자의 메타데이터를 저장하는 용도로 핸드폰, 컴퓨터에 적용되지 않았을까 추측해 본다.&lt;/p&gt;
&lt;p&gt;심지어는 Node 22 버전부터 SQLite 가 내장되기 시작했다. 얼마나 컴팩트하길래..&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;strong&gt;어떻게 사용할 수 있을까?&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;1. 로컬 어플리케이션에서 테스팅용으로 사용&lt;/h4&gt;
&lt;p&gt;로컬 어플리케이션에서 데이터베이스를 접근하기 위해, 직접 MySQL 엔진을 실행하거나,&lt;/p&gt;
&lt;p&gt;아니면 Docker 로 데이터베이스 이미지를 띄우는 것 대신, &lt;/p&gt;
&lt;p&gt;파일 시스템을 이용하여 편리하게 테스팅이 가능하다고 판단된다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;2. 개인 프로젝트나 팀 프로젝트에서, Github Actions 에 SQLite 를 탑재하여 End-to-End 테스팅 실행&lt;/h4&gt;
&lt;p&gt;Github Actions 는 특정 브랜치, 혹은 모든 브랜치에 있어 특정 트리거가 걸리면 실행되는 VM 인데,&lt;/p&gt;
&lt;p&gt;여기서 내장된 db.sqlite 파일 덕분에 편하게 E2E 테스팅을 진행할 수 있을 것 같다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;3. SQL 데이터베이스 엔진이므로, 결국 RDBMS 처럼 동작하니까, 테이블 스키마 에러 확인용으로 사용&lt;/h4&gt;
&lt;p&gt;사실, 데이터베이스 자체는 무결성 즉, 신뢰도를 중심으로 움직이므로 자체적으로 에러가 날 확률은 적을 것이다.&lt;/p&gt;
&lt;p&gt;여기서 말한 에러는, 나 혹은 팀 전체가 설계한 데이터베이스 스키마가 &lt;/p&gt;
&lt;p&gt;어플리케이션에서 접근하려는 것과 다르지 않은지 확인 할 수 있을 거라고 생각한다.&lt;/p&gt;
&lt;p&gt;실제로 팀 프로젝트를 진행하면서 데이터베이스 때문에 난리난 적이 있었는데,&lt;/p&gt;
&lt;p&gt;그건 바로 PK, FK 로 묶여 있는 관계형 테이블에서,&lt;/p&gt;
&lt;p&gt;PK 역할을 해줘야 하는 레코드가 삭제되어서 무결성이 붕괴된 것이다.&lt;/p&gt;
&lt;p&gt;사실 데이터베이스 엔진 자체는 문제가 없다.  PK 역할을 하게 되면, 애초에 삭제할 수가 없다. &lt;/p&gt;
&lt;p&gt;그런데, 어플리케이션에서 특정 행동으로 인해 PK 역할의 레코드를 삭제한 것으로 보였다.&lt;/p&gt;
&lt;p&gt;심지어 해당 PK 레코드는 홈페이지를 들어오자마자 나타나는 &amp;quot;게시물&amp;quot; 수준의 데이터여서,&lt;/p&gt;
&lt;p&gt;데이터 무결성이 터지고, 백엔드는 오류를 쏟으며, 프론트엔드는 메인 페이지를 작성 할 수가 없었다.&lt;/p&gt;
&lt;p&gt;이러한 경험에 따라, 테이블 스키마 에러 확인용으로도 괜찮다고 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;h4&gt;4. 클라우드 컴퓨팅에서 중요한 것 중 하나는 분산된 컴퓨팅 리소스들이(컨테이너들..) 원하는 캐시 데이터 저장&lt;/h4&gt;
&lt;p&gt;클라우드 컴퓨팅과 DevOps 가 유행인 지금, &lt;/p&gt;
&lt;p&gt;Redis 는 분산된 컴퓨팅 리소스들이 필요로 하는 공동의 데이터를 저장해 주었다.&lt;/p&gt;
&lt;p&gt;마크다운의 mermaid 로 예시를 들어 보자면,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TB

subgraph 클라우드
    direction TB
    Redis(&amp;quot;Redis&amp;quot;)

    Container1(&amp;quot;컨테이너 1&amp;quot;)
    Container2(&amp;quot;컨테이너 2&amp;quot;)
    Container3(&amp;quot;컨테이너 3&amp;quot;)

    Redis &amp;lt;--&amp;gt; Container1
    Redis &amp;lt;--&amp;gt; Container2
    Redis &amp;lt;--&amp;gt; Container3

    Proxy{&amp;quot;프록시&amp;quot;}

    Container1 &amp;lt;--&amp;gt; Proxy
    Container2 &amp;lt;--&amp;gt; Proxy
    Container3 &amp;lt;--&amp;gt; Proxy
end

Proxy &amp;lt;--&amp;gt; Outside-Con(&amp;quot;외부 요청&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;예를 들어서, 모든 컨테이너는 동일한 어플리케이션 이미지를 실행하고 있다고 가정한다.&lt;/p&gt;
&lt;p&gt;이 때, 수많은 연결 요청은 Proxy 를 거치고, 이 요청들은 분산되어 컨테이너 1, 2, 3 에게 간다.&lt;/p&gt;
&lt;p&gt;Redis 는 주로 캐시 데이터를 저장한다. 캐시 데이터는 사용자의 세션, 혹은 시스템 데이터를 저장한다고 예시한다.&lt;/p&gt;
&lt;p&gt;물론, Redis 는 엄청나게 빠르다. 그리고 임시 데이터를 처리하기 위해 아주 좋은 솔루션을 마련한다.&lt;/p&gt;
&lt;p&gt;그리고 많은 타입을 지원한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;내가 상상한 것은, 이러한 모양새이다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TB

subgraph 클라우드
    direction TB

    subgraph Capsule [&amp;quot;캡슐&amp;quot;]
        Application-with-SQLite(&amp;quot;SQLite 를 사용하는 어플리케이션&amp;quot;)
    end


    Container1(&amp;quot;컨테이너 1&amp;quot;)
    Container2(&amp;quot;컨테이너 2&amp;quot;)
    Container3(&amp;quot;컨테이너 3&amp;quot;)

    Capsule &amp;lt;--&amp;gt; Container1
    Capsule &amp;lt;--&amp;gt; Container2
    Capsule &amp;lt;--&amp;gt; Container3

    Proxy{&amp;quot;프록시&amp;quot;}

    Container1 &amp;lt;--&amp;gt; Proxy
    Container2 &amp;lt;--&amp;gt; Proxy
    Container3 &amp;lt;--&amp;gt; Proxy
end

Proxy &amp;lt;--&amp;gt; Outside-Con(&amp;quot;외부 요청&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기서 &amp;quot;SQLite 를 사용하는 어플리케이션&amp;quot; 이란,&lt;/p&gt;
&lt;p&gt;외부의 요청을 받아 SQLite 의 데이터를 추가 및 삭제할 수 있는 &lt;/p&gt;
&lt;p&gt;모든 프레임워크를 의미한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;물론 위의 아키텍쳐대로 설계한다면, Redis 보다는 느리고, 자유도가 떨어질 것이다.&lt;/p&gt;
&lt;p&gt;하지만, 캡슐화 된 어플리케이션이 파일 형태로 데이터를 저장하므로,&lt;/p&gt;
&lt;p&gt;정해진 형태의 데이터를 저장하고, 삭제할 수 있게도 만들 수 있다고 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;생각해 보지 않은 말이었는데,&lt;/p&gt;
&lt;p&gt;혹시라도 다른 생각이나 아이디어가 있다면, 댓글은 항상 환영한다! - (로봇 빼고)&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;참고 사이트&lt;/h2&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.sqlite.org/index.html&quot;&gt;https://www.sqlite.org/index.html&lt;/a&gt;&lt;/p&gt;</description>
      <category>잡다 지식</category>
      <category>database</category>
      <category>DBMS</category>
      <category>SQL</category>
      <category>SQLite</category>
      <category>데이터베이스</category>
      <category>파일 데이터베이스</category>
      <category>편리한 데이터베이스</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/199</guid>
      <comments>https://codecreature.tistory.com/199#entry199comment</comments>
      <pubDate>Tue, 1 Apr 2025 23:05:31 +0900</pubDate>
    </item>
    <item>
      <title>JavaScript, TypeScript 에서 진짜 Private 구현하기</title>
      <link>https://codecreature.tistory.com/198</link>
      <description>&lt;h2&gt;제목 : 자바스크립트에서 진짜 private 구현하기&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;현대의 자바스크립트 문법(es6 이상) 에서는 &lt;strong&gt;&amp;quot;class&amp;quot;&lt;/strong&gt; 문법이 지원된다.&lt;/p&gt;
&lt;p&gt;그러나, 자바스크립트를 배우는 지난 과정 속, 데코레이터(Decorator) 를 배우면서,&lt;/p&gt;
&lt;p&gt;실질적으로 JavaScript 의 Class 또한, function 의 일환이라는 것을 발견했다.&lt;/p&gt;
&lt;p&gt;이를 발견하게 된 것은, 기존의 TypeScript Class 코드를 JavaScript ES5 버전으로 변경하면서 발견했다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt; : &lt;/p&gt;
&lt;p&gt;&lt;code&gt;typescript&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// 클래스 컴파일 결과 확인용
class FunctionClass {
    title : string;
    static staticalMember : number = 100;

    constructor() {
        this.title = &amp;quot;클래스가 어떻게 함수가 되는거지?&amp;quot;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;javascript&lt;/code&gt; -- &lt;code&gt;tsc&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// 클래스 컴파일 결과 확인용
var FunctionClass = /** @class */ (function () {
    function FunctionClass() {
        this.title = &amp;quot;클래스가 어떻게 함수가 되는거지?&amp;quot;;
    }
    FunctionClass.staticalMember = 100;
    return FunctionClass;
}());&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;클래스와 프로토타입으로 생성된 인스턴스와,&lt;/p&gt;
&lt;p&gt;클래스, 프로토타입, 인스턴스가 연결된 프로토타입 체인을 공부하며 헷갈렸던 연결 그 자체를,&lt;/p&gt;
&lt;p&gt;컴파일 이후의 자바스크립트를 보면서 지식들을 찬찬히 연결시켜 나갔다.&lt;/p&gt;
&lt;p&gt;클래스를 선언하는 방식이 언어 자체에 종속되어 있는 것이 아니라,&lt;/p&gt;
&lt;p&gt;또 다른 함수를 변수에 담음으로서 클래스를 생성한다니, 정말 예상치도 못한 부분이였다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;var FunctionClass&lt;/code&gt; 는 결과적으로 내부의 &lt;code&gt;function FunctionClass() {...}&lt;/code&gt; 를 의미한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;var FunctionClass&lt;/code&gt; 에 클래스를 의미하는 함수 객체를 할당하는 로직을 작성하기 위해, &lt;br/&gt; &lt;code&gt;function FuntionClass()&lt;/code&gt; 메서드에 속성을 주입하고 반환하는 로직을 &lt;strong&gt;익명 함수, 즉시 실행&lt;/strong&gt; 으로 작성했다.&lt;/li&gt;
&lt;li&gt;예를 들어 &lt;code&gt;const instance = new FunctionClass()&lt;/code&gt; 에서 &lt;code&gt;instance.staticalMember&lt;/code&gt; 로 &lt;br/&gt; 클래스의 정적 부분을 바로 접근할 수 없던데, 그것은 &amp;quot;별개의 함수&amp;quot; 였기 때문이었다.&lt;/li&gt;
&lt;li&gt;별개의 함수이지만, 결국 인스턴스의 클래스, 프로토타입의 연결 정보는 &lt;ul&gt;
&lt;li&gt;&lt;strong&gt;prototype&lt;/strong&gt; : &lt;code&gt;Object.getPrototype(instance)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;class&lt;/strong&gt; : &lt;code&gt;Object.getPrototype(instance).constructor&lt;/code&gt; 로 구할 수 있었다.&lt;/li&gt;
&lt;li&gt;클래스와 프로토타입 객체는 서로의 연결 정보를 가지고 있기 때문에 위의 방식이 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;p&gt;타입스크립트를 다시 번역한 자바스크립트의 문법을 보면서, &lt;code&gt;private&lt;/code&gt; 적용 여부도 궁금했다.&lt;/p&gt;
&lt;p&gt;자바스크립트 문법에도, 타입스크립트 문법에도 private 은 존재한다.&lt;/p&gt;
&lt;p&gt;그렇다면, &amp;quot;클래스&amp;quot; 가 함수로 번역되는 자바스크립트에서 &lt;code&gt;private&lt;/code&gt; 은 어떻게 나타날 것인가?&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;typescript&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// 클래스 컴파일 결과 확인용
class FunctionClass {
    private title : string;
    private author : string;
    static staticalMember : number = 100;

    constructor() {
        this.title = &amp;quot;private 으로 선언된 title&amp;quot;;
        this.author = &amp;quot;private 으로 선언된 author&amp;quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;javascript&lt;/code&gt; -- &lt;code&gt;tsc&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// 클래스 컴파일 결과 확인용
var FunctionClass = /** @class */ (function () {
    function FunctionClass() {
        this.title = &amp;quot;private 으로 선언된 title&amp;quot;;
        this.author = &amp;quot;private 으로 선언된 author&amp;quot;;
    }
    FunctionClass.staticalMember = 100;
    return FunctionClass;
}());&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;이상하다? private 이 적용되지 않는데?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;먼저 들었던 생각이다.&lt;/p&gt;
&lt;p&gt;나는 분명히 &lt;code&gt;private&lt;/code&gt; 을 선언하고, 해당 인스턴스 변수를&lt;/p&gt;
&lt;p&gt;콘솔로 출력하려고 한다면, 에러가 떴다.&lt;/p&gt;
&lt;p&gt;그런데, 자바스크립트로 출력된 내용은 완전히 &lt;code&gt;public&lt;/code&gt; 이나 다름이 없었다.&lt;/p&gt;
&lt;p&gt;즉, TypeScript 에서는 문법적으로 보호 해 주지만, 컴파일 된 자바스크립트는 그렇지 않다는 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이쯤되니, 이러한 생각이 들었다.&lt;/p&gt;
&lt;p&gt;어플리케이션의 실행은 주로 타입스크립트의 내용이 해석된 &lt;code&gt;dist&lt;/code&gt; or 다양한 이름을 가진 폴더에서 실행하는데,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;dist&lt;/code&gt; 의 내용에 JavaScript 로, private 선언을 했던 객체에 직접적인 접근이 가능하다는 것이다.&lt;/p&gt;
&lt;p&gt;그렇다면, 코드 내부적으로 JavaScript 의 보안을 지키기는 어렵다고 생각했다.&lt;/p&gt;
&lt;p&gt;따라서, 나는 TypeScript 를 사용하면서도, JavaScript 로 번역되었을 때,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;private&lt;/code&gt; 으로 선언된 변수들이 컴파일 되더라도 보안을 유지하는 방식이 궁금했다.&lt;/p&gt;
&lt;p&gt;방대한 NPM 의 세계만큼, JavaScript 도 방법이 있을 거라고 확신했다.&lt;/p&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;h2&gt;JavaScript 에서 내부적인 프로퍼티는 어떻게 보호할까?&lt;/h2&gt;
&lt;p&gt;우선, JavaScript 자체적으로 private 을 사용할 수 있게 해 주기도 한다.&lt;/p&gt;
&lt;p&gt;그런데, 자체 빌트인으로 사용하는 것이 아니라, 타입스크립트 상으로 &lt;code&gt;target : 2015&lt;/code&gt; 이상의 문법을 사용해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example :&lt;/strong&gt; &lt;code&gt;javascript&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;class TestPrivate {
    #title;
    constructor() {
        this.#title = &amp;quot;private title&amp;quot;;
    }

    get title() {
        return this.#title;
    }
}

const instance = new TestPrivate();

console.log(instance.title);

console.log(instance[&amp;quot;#title&amp;quot;]);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt; : &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ node test-private.js
private title # instance.title
undefined # instance[&amp;quot;#title&amp;quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;바닐라 자바스크립트로 작성했을 때, 현재 &amp;quot;25-3-30&amp;quot; 기준으로, 명세서에 등재되기 직전 상태라고 한다.&lt;/p&gt;
&lt;p&gt;따라서, 잘 작동하고 있다.&lt;/p&gt;
&lt;p&gt;그런데, 자바스크립트에서 이런 말이 있었다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;JavaScript 에서의 class 문은 &amp;quot;문법적 설탕&amp;quot; 이다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;따라서, 이것이 어떻게 원초적으로 작성되어 있을지 보기로 했다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;&lt;code&gt;typescript&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;class TsPrivate {
    #title : string;
    constructor() {
        this.#title = &amp;quot;TypeScript Private Title&amp;quot;;
    }
}

const TsInstance = new TsPrivate();&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;javascript&lt;/code&gt; : &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;var __classPrivateFieldSet = (this &amp;amp;&amp;amp; this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
    if (kind === &amp;quot;m&amp;quot;) throw new TypeError(&amp;quot;Private method is not writable&amp;quot;);
    if (kind === &amp;quot;a&amp;quot; &amp;amp;&amp;amp; !f) throw new TypeError(&amp;quot;Private accessor was defined without a setter&amp;quot;);
    if (typeof state === &amp;quot;function&amp;quot; ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(&amp;quot;Cannot write private member to an object whose class did not declare it&amp;quot;);
    return (kind === &amp;quot;a&amp;quot; ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var _TsPrivate_title;
var TsPrivate = /** @class */ (function () {
    function TsPrivate() {
        _TsPrivate_title.set(this, void 0);
        __classPrivateFieldSet(this, _TsPrivate_title, &amp;quot;TypeScript Private Title&amp;quot;, &amp;quot;f&amp;quot;);
    }
    return TsPrivate;
}());
_TsPrivate_title = new WeakMap(); // 여기에 집중 
var TsInstance = new TsPrivate();&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;내부 프로퍼티를 위한 강력한 은닉화 방법으로 &lt;code&gt;WeakMap&lt;/code&gt; 을 사용하는 것을 볼 수 있었다.&lt;/p&gt;
&lt;p&gt;그러나, 실제 브라우저나 Node.js 환경 (es6 +) 에서, &lt;code&gt;#&lt;/code&gt; 접두어를 붙여 private 를 전부 구현 할 수 있다.&lt;/p&gt;
&lt;p&gt;컴파일 과정에서 &lt;code&gt;WeakMap&lt;/code&gt; 이 나오는 이유는, 노후화 된 모듈과도 연결될 수 있기 때문에&lt;/p&gt;
&lt;p&gt;아직 타입스크립트 private 구문이 &lt;code&gt;WeakMap&lt;/code&gt; 을 이용하여 컴파일되는 것이 아닐까 조심스럽게 예측해 본다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;프로퍼티 은닉화 방법들&lt;/h2&gt;
&lt;h3&gt;1. Underscore - &lt;code&gt;_&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;컨벤션으로서 &amp;quot;이 변수는 private 처럼 운용하니까, 추출하지 마세요&amp;quot; 를 의미한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;class TestClass {
  _title : string;
  constructor(title : string) {
    this._title = title;
  }

  getTitle() : string {
    return this._title;
  }
  setTitle(title : string) {
    this._title = title;
  }
}

const testInstance = new TestClass(&amp;quot;Custom Title&amp;quot;);

console.log(testInstance.getTitle());

console.log(testInstance._title);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt; : &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ ts-node class-and-function.js
Custom Title # console.log(testInstance.getTitle());
Custom Title # console.log(testInstance._title);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;_&lt;/code&gt;(언더스코어) 로 작성된 변수는, 같은 프로젝트를 작성하는 개발자에게 있어&lt;/p&gt;
&lt;p&gt;직접적으로 건드리지 않는 변수라고 알리는 것과 동일하다.&lt;/p&gt;
&lt;p&gt;하지만, 결국 인스턴스를 통해 바로 직접적으로 접근이 가능하다.&lt;/p&gt;
&lt;h3&gt;WeakMap&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;먼저, WeakMap 이 (es6) - 2015 년에 도입되었다고 한다.. 따라서 지금부터 es6 로 바꾼다!!&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;JavaScript 의 일반적인 &lt;code&gt;Map&lt;/code&gt; 과 달리, &lt;strong&gt;객체&lt;/strong&gt; 혹은 &lt;strong&gt;등록되지 않은 Symbol&lt;/strong&gt; 만 키로 삼을 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;여기서 &amp;quot;등록되지 않은 Symbol&amp;quot; 이란, &lt;br/&gt;&lt;br&gt;&lt;code&gt;Symbol.for(&quot;None Registered Symbol!&quot;)&lt;/code&gt; 와 같이, &lt;br/&gt;&lt;br&gt;전역 상태로 등록된 심볼을 의미한다. &lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;일단 객체에서는 &lt;code&gt;WeakMap&lt;/code&gt; 의 키로 &amp;quot;자기 자신&amp;quot; 을 넣으며,&lt;/p&gt;
&lt;p&gt;값으로는 &lt;code&gt;private&lt;/code&gt; 으로 사용할 값들을 객체 내부로 넣는다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt; : &lt;code&gt;typescript&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;const dataStore = new WeakMap&amp;lt;object, any&amp;gt;();

class TestClass {
  constructor(pValue1 : number, pValue2 : number) {
    dataStore.set(this, {
      pValue1,
      pValue2
    });
  }

  getPrivateValue1() : number {
    const data = dataStore.get(this);
    const value1  = data.pValue1;

    return value1;
  }
  plusPrivateValue1() : void {
    const data = dataStore.get(this);
    const value1 = data.pValue1 + 1;
    dataStore.set(this, {...data, pValue1 : value1})
  }

  getPrivateValue2() : number {
    const data = dataStore.get(this);
    const value2 = data.pValue2;

    return value2;
  }
  plusPrivateValue2() : void {
    const data = dataStore.get(this);
    const value2 = data.pValue2 + 1;

    dataStore.set(this, {...data, pValue2 : value2});
  }
}

const testInstance = new TestClass(1, 10);

// 1 번째 private 변수 관리

console.log(testInstance.getPrivateValue1());

testInstance.plusPrivateValue1();

console.log(testInstance.getPrivateValue1());

// 2 번째 private 변수 관리

console.log(testInstance.getPrivateValue2());

testInstance.plusPrivateValue2();

console.log(testInstance.getPrivateValue2());&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt; : &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ ts-node class-and-function.ts
# $ tsc &amp;amp;&amp;amp; node class-and-function.js
1
2
10
11&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;보는 것 처럼, &lt;code&gt;WeakMap&lt;/code&gt; 에 저장된 &lt;code&gt;private&lt;/code&gt; 한 변수들을 가져와서,&lt;/p&gt;
&lt;p&gt;내부에서만 건드릴 수 있게 만들어 놓았다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;그런데, 이것도 &amp;quot;완전히 보안&amp;quot; 은 아니다.&lt;/p&gt;
&lt;p&gt;왜냐하면 &lt;code&gt;WeakMap&lt;/code&gt; 의 key 부분은 인스턴스로 접근이 가능한데,&lt;/p&gt;
&lt;p&gt;&amp;quot;현재 파일 코드 영역&amp;quot; 에서만 접근이 가능하다.&lt;/p&gt;
&lt;p&gt;만약 &lt;code&gt;dataStore.get(this)&lt;/code&gt; 로 접근한다면,&lt;/p&gt;
&lt;p&gt;여전히 &lt;code&gt;TestClass&lt;/code&gt; 인스턴스의 private 변수에 접근이 가능하다.&lt;/p&gt;
&lt;p&gt;그럼에도 불구하고 &lt;code&gt;WeakMap&lt;/code&gt; 이 좋은 보안을 지니는 이유가,&lt;/p&gt;
&lt;p&gt;내부 프로퍼티 추출 시 어떠한 &lt;code&gt;private&lt;/code&gt; 처럼 운용하는 변수가 나오지 않기 때문이다.&lt;/p&gt;
&lt;p&gt;우리는 전역 스코프에 존재하는 &lt;code&gt;WeakMap&lt;/code&gt; 에 &lt;code&gt;private&lt;/code&gt; 변수들을 넣어놨지,&lt;/p&gt;
&lt;p&gt;클래스에 선언하지 않았다. 이로서 어느정도 캡슐화에는 성공한 것이다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;하지만 다시 생각 해 보자.&lt;/p&gt;
&lt;p&gt;내가 만든 이 클래스는, 이 파일에서만 사용되는 것이 아니라, 외부에서 가져와 사용 할 수 있다.&lt;/p&gt;
&lt;p&gt;만약에 &lt;code&gt;TestClass&lt;/code&gt; 가 &lt;code&gt;export class TestClass&lt;/code&gt; 라면,&lt;/p&gt;
&lt;p&gt;외부에서 접근 할 때, 파일 스코프로 선언된 &lt;code&gt;const dataStore&lt;/code&gt; 은 접근 할 수가 없다!&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Symbol&lt;/h3&gt;
&lt;p&gt;아까 위에서 &lt;code&gt;WeakMap&lt;/code&gt; 선언을 &amp;quot;파일 스코프&amp;quot; 로 만들고,&lt;/p&gt;
&lt;p&gt;클래스 내부에서 해당 위크맵에 인스턴스 스스로를 등록하여 은닉하도록 만들었다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Symbol&lt;/code&gt; 사용 또한 동일하다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// private 데이터 2개 선언.
const pData1 = Symbol(&amp;quot;123&amp;quot;);
const pData2 = Symbol(&amp;quot;456&amp;quot;);

class TestClass {
    // 내부에 심볼 데이터로 넣기
    [pData1] : number;
    [pData2] : string;
    constructor() {
        this[pData1] = 10;
        this[pData2] = &amp;quot;123&amp;quot;;
    }

    getPData1PublicMethod() {
        return this[pData1];
    }

    getPData2PublicMethod() {
        return this[pData2];
    }
}

// 인스턴스 생성
const testInstance = new TestClass();

console.log(testInstance.getPData1PublicMethod());

console.log(testInstance.getPData2PublicMethod());

// 객체 내부에 존재하는 모든 심볼의 목록을 가져온다.
const instanceSymbol1 = Object.getOwnPropertySymbols(testInstance)[0].valueOf()

// 첫 번째 심볼을 출력
console.log(instanceSymbol1);

// 첫 번째 심볼로 인스턴스 내부의 private 한 변수에 접근이 가능하다.
console.log(testInstance[instanceSymbol1]);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt; : &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ tsc &amp;amp;&amp;amp; node class-and-function.js
10 # testInstance.getPData1PublicMethod()
123 # testInstance.getPData2PublicMethod()
Symbol(123) # instanceSymbol1
10 # testInstance[instanceSymbol1]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;하지만, 문제가 있다면, &lt;code&gt;Object&lt;/code&gt; 라는 전역 객체가 인스턴스 내부의 심볼들을 가져올 수 있다.&lt;/p&gt;
&lt;p&gt;이를 이용하여, 인스턴스 내부에서 변수들을 &lt;code&gt;private&lt;/code&gt; 화 시키는 심볼들이 추출된다.&lt;/p&gt;
&lt;p&gt;마지막에 보이는 것 처럼, 다시 인스턴스에 심볼을 넣어 내부 정보를 가져올 수 있다는 단점이 있다.&lt;/p&gt;
&lt;p&gt;파일 스코프로 인해 직접적으로 심볼을 가져올 수는 없지만,&lt;/p&gt;
&lt;p&gt;결국 private 데이터를 가져올 수 있다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Closure&lt;/h3&gt;
&lt;p&gt;사실상 클로저가 가장 private 에 가깝지 않을까 생각된다.&lt;/p&gt;
&lt;p&gt;&amp;quot;TOAST UI&amp;quot; 사이트에서 말하길, 모듈 패턴으로 구성되며,&lt;/p&gt;
&lt;p&gt;이는 ES6 모듈을 사용하기 시작한 이래로 점차 사용하지 않는 형태라고 한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;function TestModule() {
    const privateValue = &amp;quot;It is Private value&amp;quot;;

    const publicGetPrivateValue = () =&amp;gt; {
        return privateValue;
    }

    function functionGetValue () {
        return privateValue;
    }

    return {
        publicGetPrivateValue
    }
}

// TestModule 형태
console.log(TestModule());

// 클로저 내부의 private 값을 조회한다.
console.log(TestModule().publicGetPrivateValue());

// Object 로도 조회할 수 있는 프로퍼티는 return 시킨 메서드 뿐이다.
console.log(Object.getOwnPropertyNames(TestModule()));&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt; : &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;➜  tempTs tsc &amp;amp;&amp;amp; node class-and-function.js

{ publicGetPrivateValue: [Function: publicGetPrivateValue] } # TestModule()
It is Private value # TestModule().publicGetPrivateValue()
[ &amp;#39;publicGetPrivateValue&amp;#39; ] # Object.getOwnPropertyNames(TestModule())&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;보다시피, 함수 자체에서 &lt;code&gt;return&lt;/code&gt; 하지 않은 프로퍼티와 메서드들은 노출이 되지 않는다.&lt;/p&gt;
&lt;p&gt;이는 &lt;code&gt;WeakMap&lt;/code&gt; 을 사용하는 캡슐화 방식보다도 훨씬 더 강하게 캡슐화하고 있다.&lt;/p&gt;
&lt;p&gt;만약에 &lt;code&gt;return&lt;/code&gt; 문을 없애버리면, 내부 함수에 존재하는 어떠한 함수 혹은 프로퍼티도  접근할 수 없다.&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;Private Class Field - &lt;code&gt;#&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;이번에는 클래스를 사용하고도 &lt;code&gt;private&lt;/code&gt; 한 프로퍼티들을 접근할 수 없는 방식이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ES2019&lt;/code&gt; 부터 &lt;code&gt;#&lt;/code&gt; 을 붙이면, 외부에서 접근 할 수 없게 되었다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;
//
// // 클래스 컴파일 결과 확인용
// class FunctionClass {
//     #title : string;
//     #author : string;
//     static staticalMember : number = 100;
//
//     constructor() {
//         this.#title = &amp;quot;private 으로 선언된 title&amp;quot;;
//         this.#author = &amp;quot;private 으로 선언된 author&amp;quot;;
//     }
//
//     getTitle() : string {
//         return this.#title;
//     }
//     setTitle(title : string) {
//         this.#title = title;
//     }
// }

/*
const dataStore = new WeakMap&amp;lt;object, any&amp;gt;();

class TestClass {
    constructor(pValue1 : number, pValue2 : number) {
        dataStore.set(this, {
            pValue1,
            pValue2
        });
    }

    getPrivateValue1() : number {
        const data = dataStore.get(this);
        const value1  = data.pValue1;

        return value1;
    }
    plusPrivateValue1() : void {
        const data = dataStore.get(this);
        const value1 = data.pValue1 + 1;
        dataStore.set(this, {...data, pValue1 : value1})
    }

    getPrivateValue2() : number {
        const data = dataStore.get(this);
        const value2 = data.pValue2;

        return value2;
    }
    plusPrivateValue2() : void {
        const data = dataStore.get(this);
        const value2 = data.pValue2 + 1;

        dataStore.set(this, {...data, pValue2 : value2});
    }
}

const testInstance = new TestClass(1, 10);

// 1 번째 private 변수 관리

console.log(testInstance.getPrivateValue1());

testInstance.plusPrivateValue1();

console.log(testInstance.getPrivateValue1());

// 2 번째 private 변수 관리

console.log(testInstance.getPrivateValue2());

testInstance.plusPrivateValue2();

console.log(testInstance.getPrivateValue2());


const testInstance2 = new TestClass(5, 15);

console.log(dataStore.get(testInstance));

*/

/*
// private 데이터 2개 선언.
const pData1 = Symbol(&amp;quot;123&amp;quot;);
const pData2 = Symbol(&amp;quot;456&amp;quot;);

class TestClass {
    // 내부에 심볼 데이터로 넣기
    [pData1] : number;
    [pData2] : string;
    constructor() {
        this[pData1] = 10;
        this[pData2] = &amp;quot;123&amp;quot;;
    }

    getPData1PublicMethod() {
        return this[pData1];
    }

    getPData2PublicMethod() {
        return this[pData2];
    }
}

// 인스턴스 생성
const testInstance = new TestClass();

console.log(testInstance.getPData1PublicMethod());

console.log(testInstance.getPData2PublicMethod());

// 객체 내부에 존재하는 모든 심볼의 목록을 가져온다.
const instanceSymbol1 = Object.getOwnPropertySymbols(testInstance)[0].valueOf()

// 첫 번째 심볼을 출력
console.log(instanceSymbol1);

// 첫 번째 심볼로 인스턴스 내부의 private 한 변수에 접근이 가능하다.
console.log(testInstance[instanceSymbol1]);

console.log(Object.getOwnPropertyNames(testInstance));
*/

/*
function TestModule() {
    const privateValue = &amp;quot;It is Private value&amp;quot;;

    const publicGetPrivateValue = () =&amp;gt; {
        return privateValue;
    }

    function functionGetValue () {
        return privateValue;
    }

    return {
        publicGetPrivateValue
    }
}

// TestModule 형태
console.log(TestModule());

// 클로저 내부의 private 값을 조회한다.
console.log(TestModule().publicGetPrivateValue());

// Object 로도 조회할 수 있는 프로퍼티는 return 시킨 메서드 뿐이다.
console.log(Object.getOwnPropertyNames(TestModule()));
*/

class TestClass {
  #pValue1 : number;
  #pValue2 : number;

  constructor() {
    this.#pValue1 = 10;
    this.#pValue2 = 20;
  }

  getPrivateValues() : number[] {
    return [this.#pValue1, this.#pValue2];
  }

  #setPValue1(pValue1 : number) {
    this.#pValue1 = pValue1;
  }
  #setPValue2(pValue2 : number) {
    this.#pValue2 = pValue2;
  }

  setPrivateValues(value1 : number, value2 : number) {
    this.#setPValue1(value1);
    this.#setPValue2(value2);
  }
}

const testInstance = new TestClass();

console.log(testInstance.getPrivateValues());

testInstance.setPrivateValues(1, 2);

console.log(testInstance.getPrivateValues());

console.log(Object.getOwnPropertyNames(testInstance));&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt; : &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ tsc &amp;amp;&amp;amp; node class-and-function.js

[ 10, 20 ] # testInstance.getPrivateValues()
[ 1, 2 ] # testInstance.getPrivateValues()
[] # Object.getOwnPropertyNames(testInstance)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;보다시피, 클래스 내부에 선언되는 인스턴스 프로퍼티들은 전부 &lt;code&gt;#&lt;/code&gt; 접두어(prefix) 를 가진다.&lt;/p&gt;
&lt;p&gt;이는 &lt;code&gt;ES2019&lt;/code&gt; 부터 추가된 스펙으로, C++, Java 와 같이 클래스 &lt;code&gt;private&lt;/code&gt; 처럼 취급해 준다.&lt;/p&gt;
&lt;br/&gt;

&lt;hr&gt;
&lt;h2&gt;배운 점&lt;/h2&gt;
&lt;p&gt;이번에 JavaScript, TypeScript 에서 어떻게 &lt;code&gt;private&lt;/code&gt; 을 구현 할 수 있을까?&lt;/p&gt;
&lt;p&gt;이것을 작성하게 된 이유는 바로, &lt;code&gt;TypeScript&lt;/code&gt; 의 &lt;code&gt;private&lt;/code&gt; 구문을 가진 코드가&lt;/p&gt;
&lt;p&gt;컴파일 된 이후, 자바스크립트에서 실제로 보안 처리가 되지 않았기 때문이었다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;JavaScript 를 열심히 사용 해 온 사람들이 &lt;/p&gt;
&lt;p&gt;클래스 내부의 프로퍼티와 함수들을 private 화 시키기 위해 해 온 노력들을 알게 되었다.&lt;/p&gt;
&lt;p&gt;웹 사이트에서 사용하던 언어에서, 인지도 상승과 최적화를 거치며 영역이 넓어진 JS 가,&lt;/p&gt;
&lt;p&gt;입문은 쉽지만, 깊은 이해는 여타 다른 언어와 비슷하게 배워야 할 것이 많다고 생각한다.&lt;/p&gt;
&lt;br/&gt;

&lt;p&gt;이 다음 Node.js 에 대한 내용은 아마 다중 스레드를 다루는 &lt;code&gt;worker&lt;/code&gt; API 를 습득 할 것인데,&lt;/p&gt;
&lt;p&gt;하나의 쓰레드에 비동기적 실행을 채택한 Node.js 가 어떻게 쓰레드를 사용하는지 배워 볼 것이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;참고 사이트&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;MDN Symbol&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Symbol&quot;&gt;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Symbol&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;MDN WeakMap&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/WeakMap&quot;&gt;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/WeakMap&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tistory 프론트엔드 개발자 블로그&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://frontdev.tistory.com/entry/JavaScript%EC%97%90%EC%84%9C-Priavate-%EB%B3%80%EC%88%98-%EA%B5%AC%ED%98%84&quot;&gt;https://frontdev.tistory.com/entry/JavaScript%EC%97%90%EC%84%9C-Priavate-%EB%B3%80%EC%88%98-%EA%B5%AC%ED%98%84&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;모던 JavaScript 튜토리얼&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://ko.javascript.info/private-protected-properties-methods&quot;&gt;https://ko.javascript.info/private-protected-properties-methods&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TOAST UI 포스팅&lt;/strong&gt; - NHN 에서 개발한 UI 오픈소스 라이브러리&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;_blank&quot; href=&quot;https://ui.toast.com/weekly-pick/ko_20200312&quot;&gt;https://ui.toast.com/weekly-pick/ko_20200312&lt;/a&gt;&lt;/p&gt;</description>
      <category>Node.js/잡다 지식</category>
      <category>javascript 클래스 private</category>
      <category>자바스크립트</category>
      <category>자바스크립트 private</category>
      <category>타입스크립트</category>
      <category>타입스크립트 private</category>
      <category>프로퍼티 private</category>
      <category>함수 private</category>
      <author>코딩크리처</author>
      <guid isPermaLink="true">https://codecreature.tistory.com/198</guid>
      <comments>https://codecreature.tistory.com/198#entry198comment</comments>
      <pubDate>Mon, 31 Mar 2025 23:51:40 +0900</pubDate>
    </item>
  </channel>
</rss>