<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>MJ 개발일지</title>
        <link>https://velog.io/</link>
        <description>이전 블로그: https://c11.kr/dyb0</description>
        <lastBuildDate>Wed, 06 May 2026 12:59:42 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>MJ 개발일지</title>
            <url>https://velog.velcdn.com/images/mj_journal/profile/f2d78f38-c4e4-4e43-ae02-25e3d1969d52/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. MJ 개발일지. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/mj_journal" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[picoCTF 문제 풀이[Eavesdrop]]]></title>
            <link>https://velog.io/@mj_journal/picoCTF-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4Eavesdrop</link>
            <guid>https://velog.io/@mj_journal/picoCTF-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4Eavesdrop</guid>
            <pubDate>Wed, 06 May 2026 12:59:42 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/mj_journal/post/c69e41ae-6b1d-47b9-9c5f-5f99b1f623d1/image.png" alt=""></p>
<p>파일을 와이샤크로 일단 열어보면!
<img src="https://velog.velcdn.com/images/mj_journal/post/9b7376fa-32cb-4206-b159-f9b788bdaceb/image.png" alt="">
하지만 여기보다는 Statistics -&gt; Coversations 여기를 보는 게 더 편하다.
<img src="https://velog.velcdn.com/images/mj_journal/post/b3ead3de-270a-433e-9635-ca38aebeb035/image.png" alt="">
주요한 게 3개가 있으니 패킷 하나씩 눌러서 왼쪽에 있는 follow stream 버튼을 눌러보자.</p>
<p>첫번째 
<img src="https://velog.velcdn.com/images/mj_journal/post/f50243c1-f3c3-4ab6-83ac-9334b5eee641/image.png" alt="">
어 이거 좀 salted 이러는 거 보니까 암호화됐다는 게 보인다. 수상한데...</p>
<p>두번째
<img src="https://velog.velcdn.com/images/mj_journal/post/806a25df-db7f-4c97-81a1-19fd0a4d4f15/image.png" alt="">
오 여기서 명령어와 비밀번호를 알려준다. 첫번째 꺼 가지고 풀어보라는 소리 같아 보인다.</p>
<p>일단 그럼 첫번째꺼를 저장해보자.
<img src="https://velog.velcdn.com/images/mj_journal/post/81a0da39-d436-41c1-bde7-27d9d445e3d1/image.png" alt="">
처음에는 show as가 ASCII로 되어 있으므로 Raw로 변경하고 Save as를 누른다.</p>
<p><img src="https://velog.velcdn.com/images/mj_journal/post/4cdb04f7-c69a-4300-a15e-fa5752fd62d0/image.png" alt="">
두번째 내용에서 파일인 file.des3를 가지고 풀면 file.txt로 저장된다고 되어 있으니까 file.des3로 내용을 넣고 Save 버튼을 누른다.</p>
<p><img src="https://velog.velcdn.com/images/mj_journal/post/f678b97f-383e-45ab-9fd3-bf06994cd197/image.png" alt="">
그럼 이처럼 파일이 생김을 알 수 있다.
<img src="https://velog.velcdn.com/images/mj_journal/post/46fea35d-42bd-49b2-852f-8754a17d02c0/image.png" alt="">
아까 봤던 명령어를 그대로 치면
<img src="https://velog.velcdn.com/images/mj_journal/post/a308f68d-4bc4-4de1-bbf1-0380b263d8ec/image.png" alt="">
위와 같은 파일이 생긴다.</p>
<p><img src="https://velog.velcdn.com/images/mj_journal/post/195301d1-a59d-4a73-931e-5a2c9dd5b925/image.png" alt="">
그럼 위와 같은 정답이 나온다. 타-다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[picoCTF 문제 풀이[Disk, disk, sleuth! II]]]></title>
            <link>https://velog.io/@mj_journal/picoCTF-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4Disk-disk-sleuth-II</link>
            <guid>https://velog.io/@mj_journal/picoCTF-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4Disk-disk-sleuth-II</guid>
            <pubDate>Thu, 30 Apr 2026 08:32:55 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/mj_journal/post/34a1b026-172a-44a2-a40f-2fcad32aa4f5/image.png" alt="">
img 파일이다. 이건 일단 내부를 들여다 봐야 알 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/mj_journal/post/82634b77-ef3a-4d01-88e4-07dac1066461/image.png" alt="">
파티션 첫번째랑 두번째는 일단 아예 데이터가 없거나 할당되지 않아서 아예 내용이 없다. 볼 건 두번째 밖에 없다. 두번째 내부를 들여다 보자!</p>
<p><img src="https://velog.velcdn.com/images/mj_journal/post/14906b08-fd33-4d55-b2cc-b077696be42e/image.png" alt="">
<code>fls</code> 명령어를 써서 파티션 시작하는 오프셋을 넣으면 해당 파티션 내부를 볼 수 있고, 아무래도 리눅스를 봤을 때 제일 수상한 건 <code>root</code>이다. 서버나 개발할 때도 보통은 root에 여러가지고 시도를 많이 해보니까..? 
뭐 아니면 다른데도 더 찾아보면 되는거고~</p>
<p>그럼 root도 내부를 확인해보자~
<img src="https://velog.velcdn.com/images/mj_journal/post/48983d6e-8721-4469-985b-02e511b17d2c/image.png" alt="">
오 <code>root</code>를 확인해보니, 아까 문제에서 <code>down-at-the-bottom.txt</code>에 정답이 있다고 했는데 발견할 수 있다!</p>
<p><img src="https://velog.velcdn.com/images/mj_journal/post/aa6bc1c1-6a54-4732-a8a0-8509ea194c0a/image.png" alt="">
명령어인 <code>icat</code>으로 해당 파일을 확인해보면, 드디어 정답이 나왔다.
이걸로 끝!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[picoCTF 문제 풀이[shark on wire 1]]]></title>
            <link>https://velog.io/@mj_journal/picoCTF-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4shark-on-wire-1</link>
            <guid>https://velog.io/@mj_journal/picoCTF-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4shark-on-wire-1</guid>
            <pubDate>Tue, 28 Apr 2026 14:53:22 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/mj_journal/post/4b7cc3c6-e915-4239-b4a7-ae9397c992b2/image.png" alt=""></p>
<p>일단 패킷인거 보니까 와이어 샤크로 열 수 있는 문제이다.
<img src="https://velog.velcdn.com/images/mj_journal/post/b3ed92d3-4b33-496e-9bdf-5e3958ac2681/image.png" alt="">
실제로 열어 보면 많은 패킷들이 있고 이 패킷들 사이에서 정답을 찾아내야 한다.</p>
<p><img src="https://velog.velcdn.com/images/mj_journal/post/5be07d73-2d2c-418d-9f62-197098ea980e/image.png" alt="">
프로토콜 하이라키로 보면 UDP 아래에 데이터가 있다.
보통은 데이터 안에 정답을 숨기기 쉽기 때문에 UDP에 무언가 숨겨져 있는지 찾아봐야 한다.</p>
<p><img src="https://velog.velcdn.com/images/mj_journal/post/193fe360-f630-4bce-aee7-f1a5b866c681/image.png" alt="">
여기서 conversations를 클릭해서 보면
<img src="https://velog.velcdn.com/images/mj_journal/post/55a9872f-a1fc-4d3b-a8eb-a617be48fc3c/image.png" alt="">
이런 식으로 나오고 아까 데이터를 숨길만한 곳은 UDP라고 했으니 UDP를 봐야 한다.</p>
<p><img src="https://velog.velcdn.com/images/mj_journal/post/66f3a617-ad20-4596-955a-d6c2430680f9/image.png" alt="">
여기서 수상한 구석은 다른 패킷은 다 용량이 바이트 단위인데 저부분은 KB 단위이다. 일반적인 구석보다 용량이 크다는 의미이다.</p>
<p>수상한 구석을 하나하나 살펴보려면 왼쪽에 있는 Follow Stream을 하나하나 눌러보자
<img src="https://velog.velcdn.com/images/mj_journal/post/06f78d7a-881c-4130-8bcc-99e3be0a291b/image.png" alt="">
그럼 이와 같은 정답을 알 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[picoCTF 문제 풀이[hideme]]]></title>
            <link>https://velog.io/@mj_journal/picoCTF-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4hideme</link>
            <guid>https://velog.io/@mj_journal/picoCTF-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4hideme</guid>
            <pubDate>Mon, 27 Apr 2026 10:06:23 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/mj_journal/post/92ed9d9b-ee72-4e0a-ac10-4267b90b703d/image.png" alt=""></p>
<p>here에서 파일을 다운받으면
<img src="https://velog.velcdn.com/images/mj_journal/post/88656a57-bb06-4d93-8e3e-a44f13a564cc/image.png" alt="">
flag.png 파일이 생긴다. 
(나는 flag.png 파일이 있어서 이름을 바꿨다.)</p>
<p>혹시 파일이 png가 원래 맞는지 확인해보자
<img src="https://velog.velcdn.com/images/mj_journal/post/ae026f3a-6a38-4e3f-a3fb-cb262e206fee/image.png" alt="">
일단 png는 맞다.</p>
<p>파일 내부 데이터에 정답은 없는지 체크해보자</p>
<pre><code class="language-shell">strings flag2.png | grep &quot;picoCTF&quot;</code></pre>
<p>음 나오는 게 없다.</p>
<p>혹시 png 파일 안에 압축파일이 있는지 체크해보자
<img src="https://velog.velcdn.com/images/mj_journal/post/945c7f60-e4cc-47f4-816d-523e8e9039e3/image.png" alt="">
와 내부에 파일이 숨겨져 있었다.
<img src="https://velog.velcdn.com/images/mj_journal/post/59b2f5ba-a433-419d-a0f7-7aac834b569f/image.png" alt="">
flag.png 파일을 열어보자!</p>
<p><img src="https://velog.velcdn.com/images/mj_journal/post/7b4048f0-eead-4f0b-b601-0b0497f635d8/image.png" alt="">
따란! 정답 확인!!!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[picoCTF 문제 풀이[Matryoshka doll]]]></title>
            <link>https://velog.io/@mj_journal/picoCTF-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4Matryoshka-doll</link>
            <guid>https://velog.io/@mj_journal/picoCTF-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4Matryoshka-doll</guid>
            <pubDate>Fri, 24 Apr 2026 08:30:59 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/mj_journal/post/e2713b89-6ac7-41ac-90b3-ed37daa1306a/image.png" alt="">
마트료시카 문제 나왔을 때 예상했어야 하는건데...<em>ㅠ</em>...</p>
<p>일단 문제는 이미지니까 이미지를 다운받아보면
<img src="https://velog.velcdn.com/images/mj_journal/post/2ad8572f-40f0-4c5d-82b0-43e1e335ddca/image.png" alt="">
이미지가 jpg이다 확장자가 하지만 믿을 수 없지.
일단 이미지가 jpg가 맞는지 확인해보자
<img src="https://velog.velcdn.com/images/mj_journal/post/3ff034b7-90c7-4def-8b0c-ab49ee9e46d5/image.png" alt="">
이야 파일 타입이 보이는가 png에 해당한다. 역시 눈으로 봐선 믿을 수 없군.</p>
<p>png 파일 안에 숨겨진 데이터가 있는지 없는지 찾아보자.</p>
<pre><code>unzip dolls.png</code></pre><p>터미널에서 위와 같은 명령어를 치면 
<img src="https://velog.velcdn.com/images/mj_journal/post/a69d12c1-f4e2-4753-904f-f9708553de4d/image.png" alt="">
숨겨진 파일이 나온다.
<img src="https://velog.velcdn.com/images/mj_journal/post/9f1206c5-91bd-4df5-9a45-38744094b755/image.png" alt="">
음 파일이 달라지긴 했는데, 여전히 별 게 없다.</p>
<p>이 이미지도 혹시 모르니 확인해보자
<img src="https://velog.velcdn.com/images/mj_journal/post/dfc3f291-457e-488b-bd8c-cbace847599b/image.png" alt="">
이야 이 이미지도 역시 png이네. 이 이미지도 똑같이 <code>unzip</code>으로 풀어보자.
<img src="https://velog.velcdn.com/images/mj_journal/post/118415b3-2c7c-4ae0-be32-f3fcaf6d664f/image.png" alt="">
아까의 2_c.png랑 다르게 나오긴 했으나 여전히 마트료시카 이미지 밖에 나오지 않는다.
이 이미지 역시 또 확인을..
<img src="https://velog.velcdn.com/images/mj_journal/post/5d82d16a-5dd2-4ce4-800f-6c145cbf2447/image.png" alt="">
또 png, 또P @-@ 눈 돌아갈 듯. 또 풀어보자 <code>unzip</code>으로 png 파일 풀기 GoGo!
<img src="https://velog.velcdn.com/images/mj_journal/post/fd59e3f5-feb4-4864-b10f-4cf77a7cfeb6/image.png" alt="">
또 달..달라지긴 했다.. 또 파일 확인
<img src="https://velog.velcdn.com/images/mj_journal/post/abf132b7-916d-4c63-8bee-66bb5f969bf2/image.png" alt="">
이젠 기계적으로 다시 <code>unzip</code> 시작
<img src="https://velog.velcdn.com/images/mj_journal/post/eabf95be-c84f-4105-a18d-e59dca1cdadc/image.png" alt="">
오 드디어 flag.txt 파일 확인 완료
<img src="https://velog.velcdn.com/images/mj_journal/post/60c3607f-a2db-4ea7-b9c3-0dca44dd9954/image.png" alt="">
자 드디어 정답 나왔다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[picoCTF 문제 풀이[Trivial Flag Transfer Protocol]]]></title>
            <link>https://velog.io/@mj_journal/picoCTFTrivial-Flag-Transfer-Protocol</link>
            <guid>https://velog.io/@mj_journal/picoCTFTrivial-Flag-Transfer-Protocol</guid>
            <pubDate>Thu, 23 Apr 2026 09:33:39 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/mj_journal/post/972d494c-4b24-4e3f-a1df-b60d9c538546/image.png" alt=""></p>
<p>보자. 확장자가 pcapng이네 이건 wireshark에서 열면 되겠다.</p>
<p><img src="https://velog.velcdn.com/images/mj_journal/post/982e5240-197d-4913-b365-0df9e86d958a/image.png" alt="">
세상에서 열어보니 너무 많은 리스트들이 보인다.
일단 여기서 파일 제목? 주제가 tftp임으로 필터를 tftp로 해서 걸러보자.
<img src="https://velog.velcdn.com/images/mj_journal/post/09794df3-265f-4997-87a3-e0d6c1163499/image.png" alt="">
한 번 필터를 거르면 이런 식으로 나오고, 여기에 해당하는 내용을 뽑아보자.</p>
<p><img src="https://velog.velcdn.com/images/mj_journal/post/2180f873-d3b5-4015-9e55-1f13af577c30/image.png" alt="">
Export Objects에서 TFPT를 선택하고 나면
<img src="https://velog.velcdn.com/images/mj_journal/post/9274710a-8043-40ee-9dd5-becef3c54776/image.png" alt="">
이런 식으로 텍스트 파일이나 이미지 등 파일이 있음을 알 수 있다.
모두가 힌트가 될 꺼 같으니 다 다운받아 보자 <code>Save All</code>을 선택하면 모두가 다운된다.</p>
<p>여기서 좀 수상한 게 있다. 다른 건 다 확장자가 있는데 plan 파일만 확장자가 없어서 어떤 파일인지 알기가 힘들다.
<code>exiftool</code> 명령어를 통해 plan 파일을 확인해보면 
<img src="https://velog.velcdn.com/images/mj_journal/post/c01060ce-a7eb-4525-b5f2-88153c4115b0/image.png" alt="">
파일 확장자가 텍스트 파일임을 알 수 있다. plan 파일의 확장자를 추가해주고,
이제는 plan 파일과 instructions 파일을 열어보자!
<img src="https://velog.velcdn.com/images/mj_journal/post/7d99632a-b455-4c4a-89aa-7d235edba173/image.png" alt=""></p>
<p>아니..또륵.. 이건 뭔 말일까
base64 디코딩을 온라인 사이트에 돌려봐도 이상한 말이 나와서 ROT13으로 인코딩을 돌렸나 싶어서 ROT13 디코딩 사이트에 돌려보기로 한다.
<img src="https://velog.velcdn.com/images/mj_journal/post/31a12753-d18e-42e3-b167-7ca78873a4c1/image.png" alt="">
오, 돌려보니 영어긴 하지만 뛰어쓰기를 중간중간에 넣어주니 flag라는 파일을 찾아 낼 것과 plan이라는 파일을 보라는 내용이 마지막쯤에 달려있다.
그럼 plan 파일 역시 ROT13으로 돌리면
<img src="https://velog.velcdn.com/images/mj_journal/post/911d9fc5-ed86-4360-bf14-23e788944589/image.png" alt="">
역시 뛰어쓰기가 없어서 알아보기는 힘들지만 프로그램 파일을 사용해서 내용을 숨겼고 비밀번호는 <code>DUEDILIGENCE</code>라고 말하고 있다. 또한 이미지를 살펴보라고 말하고 있다.</p>
<p>아까 다운 받았던 <code>program.deb</code> 파일을 풀어보자. 
환경이 맥이기 때문에 ar를 사용해서 푼다.</p>
<pre><code>ar x program.deb</code></pre><p>풀게 되면
<img src="https://velog.velcdn.com/images/mj_journal/post/97a0b5eb-2ddf-444e-a392-2d6a36f51753/image.png" alt="">
이런 식으로 나오는데
뭔가 data가 중요해 보인다. 이건 압축 파일이니까 이걸 풀게 되면
<img src="https://velog.velcdn.com/images/mj_journal/post/cb63617c-b739-4c7c-851b-c5a7ed12a3fa/image.png" alt="">
bin에 들어가면
<img src="https://velog.velcdn.com/images/mj_journal/post/c923bb08-6d7d-4f3b-b0cf-132bc6f59994/image.png" alt="">
<code>steghide</code> 파일이 있음을 알 수 있는데, 다만 내 노트북의 환경이 맥임으로 저걸 제대로 실행할 수가 없다.
그래서 나는 맥북에 도커가 예전부터 깔려있었어서 도커를 background로 실행하고 도커를 이용해서 <code>steghide</code>를 사용해보기로 한다.
일단 터미널에서</p>
<pre><code>docker run -it --rm -v $(pwd):/data ubuntu bash</code></pre><p>도커를 통해서 우분투를 일단 실행한다.
별 이상없이 들어가게 되면 아래의 명령어를 실행 후, 데이터 폴더로 들어간다.</p>
<pre><code>apt-get update &amp;&amp; apt-get install -y steghide
cd /data</code></pre><p>그 후 차례차례 이미지 파일을 <code>steghide</code> 명령어를 통해 이미지 셋 중에 어디에 숨겨져 있는지 찾아본다.
<img src="https://velog.velcdn.com/images/mj_journal/post/9a1f405a-0f97-4fdf-93fa-6543865930b3/image.png" alt=""></p>
<p>드디어 마지막 3번째 이미지 파일에 flag.txt 파일이 있다는 걸 알 수 있었다.
<img src="https://velog.velcdn.com/images/mj_journal/post/b2b76e5b-5eee-4e9b-9ff8-cd40daa3bc79/image.png" alt="">
flag.txt 파일을 열면 따란! 바로 정답이 나온다!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[picoCTF 문제 풀이[extensions]]]></title>
            <link>https://velog.io/@mj_journal/extensions</link>
            <guid>https://velog.io/@mj_journal/extensions</guid>
            <pubDate>Wed, 22 Apr 2026 10:25:57 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/mj_journal/post/bcc77ea2-e33e-45f4-9b6f-8e794a02aaeb/image.png" alt="">
이 문제는 텍스트 파일을 다운받아서 확인하는 문제이다.
일단 파일을 다운받아 보자
<img src="https://velog.velcdn.com/images/mj_journal/post/c8ace540-ccc9-41e1-82cc-ea52ef7590aa/image.png" alt=""></p>
<p>이 파일을 열어보자 일단 열리는지 확인
<img src="https://velog.velcdn.com/images/mj_journal/post/0eb0516b-4c03-4883-baf0-0b57a124412a/image.png" alt="">
이 파일 역시 열리지 않는다.</p>
<p>이번에는 이 파일을 확인해보자. 이게 진짜 텍스트 파일이 맞는지 아니면 다른 파일인지. 
어떤 파일인지 체크할 수 있는 명령어인 <code>exiftool</code>로 일단 확인해보기.
<img src="https://velog.velcdn.com/images/mj_journal/post/3c9b11ca-767a-41b0-a188-e9643e2a8e70/image.png" alt=""></p>
<p>이 파일은 원래 txt가 아니고, 이미지 파일이다.
해당파일을 <code>flag.png</code>로 수정하고 열어보자!
<img src="https://velog.velcdn.com/images/mj_journal/post/3a016faa-5930-4952-a115-80f18e3898a9/image.png" alt="">
답이 바로 나온다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[picoCTF 문제 풀이[tunn3l v1s10n]]]></title>
            <link>https://velog.io/@mj_journal/picoCTF-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4tunn3l-v1s10n</link>
            <guid>https://velog.io/@mj_journal/picoCTF-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4tunn3l-v1s10n</guid>
            <pubDate>Wed, 22 Apr 2026 10:08:31 GMT</pubDate>
            <description><![CDATA[<p><strong>일단 tunn3l v1s10n 문제의 파일 다운로드 시작</strong>
<img src="https://velog.velcdn.com/images/mj_journal/post/fe9c1dfd-0dee-4543-ad9f-4891e91bece9/image.png" alt=""></p>
<p>다운받으면 파일이
<img src="https://velog.velcdn.com/images/mj_journal/post/0b70ea6e-18fb-4a7d-a73b-56d05c4a9d27/image.png" alt="">
이런 식으로 확장자도 없고 단순히 문서라고 되어 있는 것을 알 수 있다.</p>
<p>이 파일의 확장자를 알기 위해서 일단 터미널에서 시도 해보자.</p>
<pre><code class="language-shell">xxd tunn3l_v1s10n | head -5</code></pre>
<p>열어 보면</p>
<pre><code class="language-shell">00000000: 424d 8e26 2c00 0000 0000 bad0 0000 bad0  BM.&amp;,...........
00000010: 0000 6e04 0000 3201 0000 0100 1800 0000  ..n...2.........
00000020: 0000 5826 2c00 2516 0000 2516 0000 0000  ..X&amp;,.%...%.....
00000030: 0000 0000 0000 231a 1727 1e1b 2920 1d2a  ......#..&#39;..) .*
00000040: 211e 261d 1a31 2825 352c 2933 2a27 382f  !.&amp;..1(%5,)3*&#39;8/
</code></pre>
<p>이런 식으로 무엇인지 모르겟는데 싶은데 이럴 경우에는 <a href="https://en.wikipedia.org/wiki/List_of_file_signatures">https://en.wikipedia.org/wiki/List_of_file_signatures</a> 에 들어가서 맨 처음 4글자인 <code>42 4d</code>를 검색해보면 바로 bmp 파일임을 알 수도 있고,</p>
<pre><code>brew install exiftool</code></pre><p>명령어로 설치해서 해당 파일을 열어보면
<img src="https://velog.velcdn.com/images/mj_journal/post/1c208917-4ba5-4448-a4e1-0b1e49e4e3b4/image.png" alt="">
이런 식으로 해당 파일에 대한 정보를 알 수 있다.
이 파일은 타입이 bmp이고, bit 깊이 및 이미지 가로, 세로 길이까지도 알 수 있다.</p>
<p><code>tunn3l v1s10n</code> 파일에 확장자인 bmp를 넣고 열어보자!
<img src="https://velog.velcdn.com/images/mj_journal/post/718ad73b-973d-45f3-a56d-75f60c1cb54c/image.png" alt="">
아 여전히 파일이 안 열린다.
이 파일은 왜 열리지 않는걸까?
이럴 때는 AI 도움도 더 받아본다. 
(별로 공부하지 않은 분야임으로 더듬더듬 일단 해본다.)
파일 bmp 데이터가 잘못되어 있어서 안열리는 것 같다. 이런 데이터를 수정하기 위해서 아래 명령어를 다운받고 해당 파일을 열어보자</p>
<pre><code class="language-shell">brew install hexedit
hexedit tunn3l_v1s10n.bmp</code></pre>
<p>해당 파일을 일단 열어보면
<img src="https://velog.velcdn.com/images/mj_journal/post/65b3ef22-5187-4461-a8c8-a148bc601db4/image.png" alt="">
이런 식으로 나오고 뭔가 뭔지 잘 모르겠다.</p>
<p>이럴 때는 일반적인 bmp 파일 데이터와 비교해보자
<img src="https://velog.velcdn.com/images/mj_journal/post/fd604521-dbad-4e81-b29a-211788474a8a/image.png" alt="">
첫 줄에 보면 36과 28 빼면 나머지는 다 0인데, 기존 <code>tunn3l_v1s10n</code> 파일의 경우에는 <code>BA DO</code> 두 개가 반복되어서 살짝 이상함이 느껴진다.</p>
<p>게다가 bmp 파일의 경우에는 파일 데이터 포맷을 살펴보면 0xA이 DataOffset이 시작된다.
<img src="https://velog.velcdn.com/images/mj_journal/post/25d6da6f-e64b-4926-9120-80ee8400c1fa/image.png" alt=""></p>
<p>위키에서도 살펴보면 DataOffset의 경우에는 해당 파일의 크기가 4byte밖에 되지 않는다.
<img src="https://velog.velcdn.com/images/mj_journal/post/2817dbca-732d-4f45-8e9f-4ae49bba04c3/image.png" alt="">
그럼 이미지에 표시한대로 <code>BA DO 00 00</code> 이 부분이 크기가 올바른지를 알아 보자. 참고로 비트맵의 케이스 같은 경우 리틀 엔디엔 형식을 쓰고 있으므로 그대로 읽으면 안되고 <code>00 00 DO BA</code>와 같이 무조건 반대로 읽어야 한다.</p>
<p>위키에서 보면 비트맵 파일 헤더는 14byte로 명시되어 있으며, 비트맵 인포 헤더는 40byte로 명시되어 있다. 그렇게 되면 DataOffset에 명시되어야 할 바이트 total, <strong>54 byte</strong>라는 말이 된다.</p>
<p><code>00 00 DO BA</code>이 얼마인지 계산해보면
앞에 &#39;0&#39; 4개는 아무리 계산해도 0임으로 제외하고
<code>D0 BA</code>는 우선 16진수에서 A=10, B=11, C=12, D=13, E=14, F=15
에 해당하므로 
<code>D</code>는 13에 해당하고, 16진수에서 3번째자리에 있으므로 계산식으로는 16의 3제곱 X 13
<code>0</code>는 0에 해당하고, 16진수에서 2번째자리에 있지만 0은 아무리 곱해도 0임으로 계산 제외
<code>B</code>는 11에 해당하고, 16진수에서 첫번째자리에 있으므로 계산식으로는 16 X 11 
<code>A</code>는 10에 해당하고, 16진수에서 0번째자리에 있으므로 계산식으로는 1 X 10</p>
<p>이렇게 됐을 때 총체적으로 계산을 해보면</p>
<pre><code>(16 X 16 X 16 X 13) + (16 X 11) + (1 X 10) = 53,434</code></pre><p>임으로 54 바이트에서는 한참 벗어나 있는 것을 알 수 있다.</p>
<p>그럼 아까 봤던 일반적인 bmp 파일 데이터에서 있던 <code>36 00 00 00</code> 을 계산해보자. 
리틀 엔디엔으로 하게 되면 <code>00 00 00 36</code>이 되고, 
여기서도 앞에 &#39;0&#39; 6개는 아무리 계산해도 0임으로 일단 제외</p>
<p><code>36</code>은 우선 16진수에서
<code>3</code>는 3에 해당하고, 16진수에서 첫번째자리에 있으므로 계산식으로는 16 X 3 
<code>6</code>는 10에 해당하고, 16진수에서 0번째자리에 있으므로 계산식으로는 1 X 6</p>
<p>이렇게 됐을 때 총체적으로 계산을 해보면</p>
<pre><code>(16 X 3) + (1 X 6) = 54</code></pre><p>임으로 <code>36 00 00 00</code>이 딱 54바이트에 알맞다는 것을 알 수 있다.</p>
<p>음, <code>36 00 00 00</code>로 고쳐서 열어봐도 역시 똑같이 열리지 않는다.</p>
<p>아까 봤던 부분 중에 또 달랐던 부분이
<img src="https://velog.velcdn.com/images/mj_journal/post/a42c6e25-3adc-4649-b681-dc3ea8cf1717/image.png" alt="">
이 부분이다. 여기 부분은 0E 부분으로 비트맵 인포 헤더에서 사이즈에 해당한다.
<img src="https://velog.velcdn.com/images/mj_journal/post/5b306b61-7b62-4d5e-b669-9de1f614ea4d/image.png" alt="">
역시 해당 크기는 4바이트 임으로 노란색 부분에 해당한다.
아까 말한대로 비트맵 인포 헤더는 크기가 40 byte임으로 저 노란색 부분에 40 byte라고 명시해야 한다.</p>
<p>첫번째 계산했던 내용과 여기의 크기가 똑같으므로, <code>53,434</code>의 크기는 올바르지 않다.
그럼 아까 봤던 <code>00 00 00 28</code>은 몇 바이트인지 알아보자!
리틀 엔디엔으로 하게 되면 <code>00 00 00 28</code>이 되고, 
여기서도 앞에 &#39;0&#39; 6개는 아무리 계산해도 0임으로 일단 제외</p>
<p><code>28</code>은 우선 16진수에서
<code>2</code>는 2에 해당하고, 16진수에서 첫번째자리에 있으므로 계산식으로는 16 X 2
<code>8</code>는 10에 해당하고, 16진수에서 0번째자리에 있으므로 계산식으로는 1 X 8</p>
<p>이렇게 됐을 때 총체적으로 계산을 해보면</p>
<pre><code>(16 X 2) + (1 X 8) = 40</code></pre><p>임으로 <code>28 00 00 00</code>이 딱 40바이트에 알맞다는 것을 알 수 있다.</p>
<p>음, <code>28 00 00 00</code>로 고쳐서 열어보면 열리긴 열리는데 좀 뭔가 이상하다.
<img src="https://velog.velcdn.com/images/mj_journal/post/1800d300-3305-49ca-8964-40dd95f7a158/image.bmp" alt="">
아, 이미지가 짤린 것 같다.
사이즈 설정이 잘못된 거 같다.</p>
<p>비트맵의 예상 사이즈는 아래와 같은데,</p>
<pre><code>너비 × 높이 × (비트깊이 ÷ 8) = 예상 이미지 크기</code></pre><p>다시 아까 위에 올렸던 파일 정보를 확인해보자.
<img src="https://velog.velcdn.com/images/mj_journal/post/1c208917-4ba5-4448-a4e1-0b1e49e4e3b4/image.png" alt="">
현재 파일의 크기는 2.9MB이고, bit Depth는 24이며, 너비는 1134, 높이는 306</p>
<pre><code>1134 × 306 × (24 ÷ 8) = 1,041,012(바이트) = 약 1MB</code></pre><p>2.9MB와는 맞지 않는다.
아까 열린 이미지를 봤을 때는 가로 길이는 1000이 넘어서, 부족해보는 Hegiht를 늘려보자</p>
<p>2.9MB는 바이트로 <code>2.9 × 1024 × 1024 = 3,040,870 바이트</code>에 해당한다. 그러므로 </p>
<pre><code>3,040,870  ÷ (3 × 1134) = 약 893</code></pre><p>높이는 893을 넣으면 제대로된 이미지의 크기가 나올 것으로 보인다.
<img src="https://velog.velcdn.com/images/mj_journal/post/3c2dbedb-3c9a-4a04-8e53-d0214224af32/image.png" alt="">
해당 데이터에서 보면 오프셋은 16에서 시작한다는 걸 알 수 있다.</p>
<p>다만, 해당 숫자인 893을 그대로 넣을 수는 없기 때문에
이걸 16진수로 변환할 것이다.</p>
<pre><code>893 ÷ 16 = 나눗몫 55, 나머지 13
55 ÷ 16 = 나눗몫 3, 나머지는 7</code></pre><p>3은 더이상 나눌몫이 없으므로 나눗몫인 3을 포함하여 아래부터 위로 나머지를 나열하게 되면 <code>3, 7, 13</code> 이 나온다. <code>13</code>은 16진수에서 D이니까 <code>03 7D</code>가 나오고 리틀 엔디엔 형식때문에 반대로 <code>7D 03 00 00</code> 바꿔서 넣는다. </p>
<p>그렇게 되면 원하는 답이 나올 것이다.
<img src="https://velog.velcdn.com/images/mj_journal/post/73dd36e6-33a4-49cf-a1b8-10388c632f78/image.png" alt="">
상단에 있는 정답을 넣으면 문제 해결!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[picoCTF 문제 풀이 [Glory of the Garden]]]></title>
            <link>https://velog.io/@mj_journal/picoCTF-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-Glory-of-the-Garden</link>
            <guid>https://velog.io/@mj_journal/picoCTF-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-Glory-of-the-Garden</guid>
            <pubDate>Tue, 21 Apr 2026 09:12:18 GMT</pubDate>
            <description><![CDATA[<p>오늘의 문제는 이미지 데이터에 정답이 숨어 있는데 그 정보를 찾아내기</p>
<p><img src="https://velog.velcdn.com/images/mj_journal/post/1c1be3a9-80b1-4bd4-8dce-08024bb66f04/image.png" alt="">
문제자체 수준이 Easy 모드이기 때문에 터미널을 이용해서 <code>strings</code> 명령어로 찾아내면 금방 끝나는 문제이다.</p>
<p><img src="https://velog.velcdn.com/images/mj_journal/post/c0444555-9f42-43ea-9918-6d78c87cbeed/image.png" alt="">
명령어를 통해서 picoCTF를 검색하면 이렇게 나오는 걸 바로 알 수 있다.</p>
<p>여기서 푼 문제는 이미지에서 텍스트를 찾아내는 거였지만, 이미지 데이터에 넣는 방법도 있을테니 궁금해서 확인해보니 2가지의 방법이 있다고 했다.</p>
<ol>
<li><p><code>echo</code> 명령어를 통해서 텍스트를 넣는 방법</p>
<pre><code class="language-shell">echo &quot;this_is_text&quot; &gt;&gt; image.png </code></pre>
<p>이런식으로 넣고 명령어 <code>strings</code>로 터미널에 넣으면 금방 찾을 수 있다.</p>
</li>
<li><p>LSB 스테가노그래피의 방식으로 텍스트를 넣는 방법 
이건 전용 툴이 있어서 그걸로 해봐도 되고, 쉽게 해보는 건 지원해주는 사이트도 있어서 여기서 쉽게 테스트해볼 수 있다.
여기서는 비밀번호 설정도 할 수 있고, 일반적인 방법으로는 찾기 힘들다.</p>
</li>
</ol>
<p>내 눈에는 보이지 않지만, 이런 식으로 이미지에서 텍스트를 찾아내기도 하는 게 재밌다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[picoCTF 문제 풀이 [Sleuthkit Intro]]]></title>
            <link>https://velog.io/@mj_journal/picoCTF-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-Sleuthkit-Intro</link>
            <guid>https://velog.io/@mj_journal/picoCTF-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-Sleuthkit-Intro</guid>
            <pubDate>Fri, 17 Apr 2026 10:40:03 GMT</pubDate>
            <description><![CDATA[<p>요즘 이력서도 자소서도 하기 싫어서 해보고 있는데,
진짜 재밌다.
하루에 한 개씩은 푸는 듯</p>
<p><img src="https://velog.velcdn.com/images/mj_journal/post/0c5c494c-5c20-4aac-84da-5ed5f3f587dc/image.png" alt=""></p>
<p>이번 문제는 좀 얼레벌레 푼 게 있긴 한데,
영어에 좀 약해서 그냥 되는 데로 풀어버렸다.</p>
<p>일단 해보고 어떤 문제인지 알아보니까 디스크 구조를 파악하는 문제인데, 디스크에 수상한 게 있을 수도 있으니까 그걸 파악하기 위한 문제라고 했다.</p>
<p>의외로 답은 간단한데,
해당 디스크 이미지를 다운받고
문제에서 인스턴스를 시작하고, picoCTF 홈페이지에서 shell을 열어서
<code>nc saturn.picoctf.net 52982</code> 명령어를 치면</p>
<pre><code class="language-shell">What is the size of the Linux partition in the given disk image?
Length in sectors:</code></pre>
<p>위의 내용을 물어본다. </p>
<p>답을 하기 위해서 다운받은 이미지의 디스크를 파악해야 한다.
<code>brew install sleuthkit</code>를 내 로컬에서 돌려서 다운이 끝나고 나면
명령어인 <code>mmls disk.img</code>를 치면 아래와 같은 디스크 구조를 파악할 수 있다.
<img src="https://velog.velcdn.com/images/mj_journal/post/8e49b783-2a56-4cdb-adb5-2e9887262198/image.png" alt=""></p>
<p>다시 picoCTF 홈페이지로 이동해서 쉘에서 위의 이미지에 있는 숫자에서 리눅스 길이를 확인해보면 <code>0000202752</code>임을 알 수 있다.
<img src="https://velog.velcdn.com/images/mj_journal/post/a8a11792-b561-4126-8698-aa14ec89856a/image.png" alt="">
그러면 위의 이미지처럼 Greate work!가 나오고 정답이 나오는데 그건 직접 본인이 풀어보길!</p>
<p>요즘 하루 중에 이거랑 운동하는게 제일 재밌는듯~</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[1-3. 과제 복습 일지]]></title>
            <link>https://velog.io/@mj_journal/1-3.-%EA%B3%BC%EC%A0%9C-%EB%B3%B5%EC%8A%B5-%EC%9D%BC%EC%A7%80</link>
            <guid>https://velog.io/@mj_journal/1-3.-%EA%B3%BC%EC%A0%9C-%EB%B3%B5%EC%8A%B5-%EC%9D%BC%EC%A7%80</guid>
            <pubDate>Fri, 16 Jan 2026 08:28:12 GMT</pubDate>
            <description><![CDATA[<h2 id="13-과제를-되돌아-보자면">1.3 과제를 되돌아 보자면,</h2>
<p>그때 처음에 과제할 때도, 그랬지만 지금 다시 풀어도 나는 왜 훅을 그렇게 개발하면서 맨날 사용하면서 그 내부의 원리를 파악한다던지 이런 생각을 안했을까 되돌아 보게 된다. 훅을 사용하는 만큼 훅에 대해서 알면서 알수록 더 사용하기 쉬어질지도, 혹은 더 잘 사용하게 될지도 모르는데 말이다.
반성하는 말로 서두를 시작하게 된다.</p>
<h2 id="13-과제-톺아보기">1.3 과제 톺아보기!</h2>
<p>이번 과제는 
일단 useState를 가지고 여러 훅을 만들어보는 거였다.
게다가 훅을 만들 때 보니 모든 것의 기초는 당연하게도 useState였는데 평소에는 그런 건 생각하지 않고 훅을 가지고 원하는 곳에 사용하다 보니 개념이 좀 흐릿한 훅이 있었는데 하다보니까 왜 이런 훅을 이런 곳에서 사용할 수 있기도 했다.</p>
<p>나한테 대표적인 건
<strong><code>useRef</code></strong>
였다.</p>
<p>나는 <code>useRef</code>를 사용할 때 보통은 input에 포커스 갈 때 사용한다. 이런 동작들 위주로만 생각해서 사용한다고 생각했고 그것도 맞긴 하지만 <code>useRef</code>가 기본적으로 어떤 곳에서 사용해야 하는지에 대한 기본적인 개념이 좀 부족하다고 생각했다.</p>
<p>보통 <code>useState</code>는 상태관리를 위해 사용하며 그 상태가 바뀌면 당연히 리렌더링도 이루어진다.
<code>useRef</code>를 직접 만들어보면서 이해하게 된 점은 <code>usestate</code>와 달리 </p>
<ul>
<li>리렌더링을 원하지 않지만, 동작은 하기를 원할 때
그래서 input같은 포커스를 줄 때 혹은 드래그 할 때 사용하기 좋은 게 <code>useRef</code>인 것이다.</li>
<li>값은 저장하돼, 리렌더링이 되지 않는 조용한 저장을 원할 때 써먹을 수 있는 부분이다. 이런 useRef를 활용해서 <code>useMemo</code>나 <code>usecallback</code>에 사용하기 좋다. 혹은 화면에 렌더링할 필요가 없다면 데이터를 저장할 때 대안으로 될 수 있다는 게 흥미롭다.</li>
</ul>
<p>만들어본 것 중에 가장 신세계였던건? 
<strong><code>useAutoCallback</code></strong> 
였던 듯 하다.</p>
<p>좀 재밌었던 건,
<code>usecallback</code>으로 deps를 관리하는 건 항상 귀찮은 일이었다.
하지만, 이 커스텀 훅을 통해서 자동으로 deps 관리를 할 수 있도록 만들어 본 건 재밌는 일이었다.
특히나 앞에서 내가 만들었던 <code>useRef</code>의 조용한 저장과 <code>usecallback</code>의 리렌더링 방지를 조합해서 만들어 보는 건, 그 때도 만들어보면서도 감탄했다.
이런 훅을 만들 수도 있는 거였구나! 하고 말이다.
그래서 비유적으로 이해할 때 배의 닻을 고정할 수 있는 건 <code>usecallback</code> 이고, 배 안의 물건들이 계속 바뀔 수 있는 건 <code>useRef</code>으로 이해하였다.</p>
<p>그 외에 만들었던 것 중에서는 음...
<code>useStorage</code>와 <code>useStore</code>를 만들고도 이건 뭘 위해 만든 것인지 생각해보게 됐다. 
만들 때, 과제니까 너무 생각없이 만든 것도 있는 것 같다.</p>
<p><strong><code>useStorage</code></strong> 의 경우,
우리가 만들 때 <code>localStorage.getItem</code>와 같은 코드를 줄이고, <code>localStorage</code>와 리액트의 중간다리 역할을 <code>useSyncExternalStore</code>에게 시킴으로써 외부 상태 관리를 패턴화시키는데 의의가 있달까. 
아무래도 우리는 실제로 페이지를 만들어 보면 알겠지만, 인증을 사용해서 토큰을 가져오면 어찌됐든 우리는 토큰을 어딘가에는 저장할 공간이 필요하다.
그럴 때 많이 사용하는데 중에 하나가 storage를 사용하는 것이다. 그렇다 보면 storage에서 저장해놓고 get해와서 많이 사용하게 되는데, 그럴 때 사용하게 된다면 매우 유용하게 사용될 수 있을 것 같다.</p>
<p><strong><code>useStore</code></strong> 의 경우,
무엇을 위해 만드는 걸까 싶었는데, 여기 또한 <code>useSyncExternalStore</code>가 중간 다리 역할을 하여 리액트와 store를 이어주어, Zustand처럼 전역에서 데이터를 내려주어 접근할 수 있게 만든 것인데 Zustand보다는 더 간단하게 만들어 볼 수 있는 것이다. 아 물론 Zustand가 라이브러리니까 더 집약적이고 더 잘 만들었겠지만, 이걸 통해서 Zustand 쓰지 않고도 이렇게 만들어 볼 수도 있겠구나 싶어서 좀 흥미롭게 느껴졌다.</p>
<h2 id="과제를-마무리-하면서">과제를 마무리 하면서,</h2>
<p>초반에는 실제 사용하는 훅들을 다시 만들어보면서 딥다이브?도 좀 해보고...
<del>(그전에는 훅을 한번도 까본적 없으니까 나한테는 딥다이브가 맞다!!)</del></p>
<p>과제를 통해 <strong><code>useSyncExternalStore</code></strong> 이라는 것도 존재하는 것을 
처음 알게 되었다. </p>
<p>기존에 문제가 되지 않았지만, <strong>React 18</strong>부터 우선순위를 기준으로 처리하기 때문에 현재 처리 중인 걸 중단하고 우선 순위가 높은 걸 먼저 처리할 수 있게 되면서, 외부 상태와 내부 상태가 동기화되지 않아서 이를 위해 새로 만들어졌다는 것도 과제를 하면서 새롭게 알게 됐다. </p>
<p>이렇게 새롭게 알게 된 건 재밌는 일이지만, React를 쓰면서도 몰랐다니 반성할 거리 투성이다!!! 
또한 과제할 때는 하느냐 정신 없어서 이런 훅은 왜 필요한가, <code>useSyncExternalStore</code>에 대해서는 무엇인지 개념에 대해서는 제대로 모르고, 그냥 만들었던 게 아쉬운 점이다. 
그래서 이렇게 복습하는 게 한 편으로는 다행이라고 생각한다. 
복습이라는 걸 하고 있어서 말이다. <del>(복습을 통해 블로그글 쓰는 것도 다행!!!)</del></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2025년 회고]]></title>
            <link>https://velog.io/@mj_journal/2025%EB%85%84-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@mj_journal/2025%EB%85%84-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Fri, 19 Dec 2025 11:38:27 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>올해를 한 마디로 정의하면 &#39;<strong>도전</strong>&#39; 그 자체</p>
</blockquote>
<h3 id="첫번째-도전-취업">첫번째 도전: 취업</h3>
<p>재작년에 퇴사하면서 작년 한 해를 쉬는 한 해로 보냈다. 아마 재작년에 근무하면서 좀 번아웃도 오고, 어떤 방향으로 다시 공부하고 회사를 다녀야 할지 몰랐던 해였던 것 같다. 그러면서 어떤 회사를 가야할지 고민했던 한 해라서 올해 취업할 때는 내가 원하는 비전이나 관심사가 비슷한 회사에 취업하고 싶다는 생각을 했다. <del>(게다가 SI는 가고 싶지 않았다...)</del></p>
<p>그래서 실제로 취업할 때 남들은 막 거의 50~100개 가까이 이력서를 뿌렸다는데, 나는 사실 30개도 뿌리지 않았다. 내 관심사는 뇌과학이나 심리 이런 쪽으로 관심이 있었는데, 그 때 마침 관련된 회사가 취업공고가 났었고, 어쩌다 보니 첫 면접이었는데 운 좋게 합격하여 다니게 되었다. 물론 이전에도 풀스택을 했었고, 이번 회사에서도 풀 스택을 맡게 되었다. </p>
<p>다만 이번 회사에서 나는 프론트 엔드의 메인 역할을 맡게 되었다. 그러면서 처음에는 되게 막막함도 같이 들었던 것 같다. 이 프로젝트를 어떻게 이끌고 가야할지 모르겠다는 생각도 들게 되었다. 처음에는 무작정 했는데, 내가 과연 잘 하고 있는가? 라는 의문을 점점 품게 되었다. 나는 어떤 부분을 보완해야할까? 어떤 공부를 더 해야하지라는 고민과 방황이 들게 되었다.</p>
<h3 id="두번째-도전-부트-캠프향해-플러스">두번째 도전: 부트 캠프(향해 플러스)</h3>
<p>근데 이런 고민하면서 찾아서 그런지 몰라도 인스타에서 부트 캠프가 홍보가 알고리즘부터 계속 떴다. 그래서 그 때 고민했던 게 멋쟁이 사자처럼이랑 향해 플러스였던가. 이 2개가 중점적으로 나한테 자꾸 뜨는 것이었다. 그 때 떴을 때 나한테 무엇이 맞는가라는 생각이 들었고, 그 때 눈에 띄었던 게 리액트 딥다이브 과정이 눈에 띄었던 것 같다. 왜냐면 생각해 보니 나는 리액트를 쓰면서도 리액트에 대한 원리가 어떻게 구성되어 있고, 어떤 과정을 통해 내가 사용할 수 있는가에 대해서는 한 번도 생각해보지 않은 것 같았다.</p>
<p>그래서 &#39;이거다&#39; 라는 생각에 멋쟁이 사자처럼은 모집을 안했던가? 뭔가 안맞았던가 해서 건너뛰고, 향해 플러스가 주니어? 취직한지 얼마 안되는 개발자들을 위한 과정이라는 걸 알고 향해 플러스 프론트엔드 과정을 해보자 싶었다.
실제 과정을 하기 전에 자바스크립트 기초와 타입스크립트 기초를 강좌로 제공해줘서 다시 한 번 복습을 하기도 하고, 해보니까 까먹은 부분도 많았던...<del>(컨텍스트나 클로저 같은..)</del> 
또한 팀 스터디로 원하는 부분에 대해서 예비로 공부를 할 수도 있었다. 나는 그 때 테스트 코드를 팀 스터디로 공부하였다.</p>
<p>그러고 나서 드디어 무더운 여름 7월부터 본 과정을 하기 시작했다. 역시 본 과정은 달라도 너무 달랐다.</p>
<p>이야 1주차 때, 리액트로는 무심코 짰던 코드들을 순수 자바스크립트로 모두 코드로 만들어 보는 게 과제였다. 게다가 순수 자바스크립트로 짜보는 건 너무 오래만인데다가 과제가 양도 많은데, 어쩜 이리 테스트 코드 마다 다 Fail을 하는지 놀라울 따름이었다. </p>
<p>과제는 해야 하지, 근데 또 출근해서 일은 해야하지 진짜 미치고 팔짝 뛸 노릇이었다. 누가 나 좀 붙잡고 &#39;이건 이렇게 하시면 돼요&#39; 라고 대놓고 가르쳐줬으면 좋겠다고 생각할 정도로 너무 힘들었던 것 같다. 하지만 어느 누구도 이 일을 대신해주지 않고, 내가 벌인 일인데 내가 하는거지 누가해주지 않는다. 그러니 당연히 내가 수습할 뿐이다. 시간이 부족하지만, 내가 할 수 있는 최선을 다할 뿐이다. 실제로도 최선을 다했고. 내가 생각한 최고의 코드는 아니었지만 최선의 코드는 짰던 것 같다. 내가 할 수 있는...</p>
<p>그나마, 2주차와 3주차는 적응해서 할만했던 것 같은데, 왠걸 4주차가 절정이었다. 클린코드 첫주차... 양을 보고 미친 거 아닐까라는 생각을 했다. 해도해도 끝나지 않고, 심지어 코드를 갈아엎었다 보니 이전까지는 그래도 모든 과제를 통과를 했다가 4주차 과제에서 처음으로 Fail을 먹었다 보니 처음으로 의욕도 좀 떨어지고 현타도 오고... 내가 이럴려고 과제를 했었나 싶었던 생각도 들었다. 이렇게 Fail할 꺼면 처음부터 과제를 안했으면 똑같은 결과를 맺지 않았을까라는 부정적인 생각도 들었다.<del>(눈물도 살알짝 났다... 짜증나서)</del></p>
<p>근데 또 과제를 중간에 포기하자니 아깝다. 내가 들인 돈<del>(물론 환불 받을 수 있기한데..얼마 안 되겠찌...)</del>과 이 때까지 들인 시간과 내가 어떤 생각을 가지고 처음에 들어왔는지 다시 한 번 리마인드하면서 미친듯이 잠도 자고, 되도록이면 아무 생각없이 과제를 하도록 노력했다.</p>
<p>그래도 5주차~8주차까지는 그래도 할만했다. 5주차, 6주차는 아키텍쳐 관련이라서 FSD도 내가 평소에 고민하면서 적용해보던 아키텍쳐이기도 했고, 5주차도 차근차근보면 따라갈 수 있고, 7주차나 8주차는 테스트 코드인데 이건 좀 노가다 성(?)이 짙어서 시간만 들이면 크게 어렵지 않게 따라갔다.</p>
<p>와... 드디어 미친 9주차... 이전 기수에는 없던 과제라는데. Nextjs처럼 SSG나 SSR을 Node의 express를 사용해서 만들어보는 과제였다. 이건 진짜 미쳤다고 생각하는 게 기본과제는 자바스크립트로 SSR나 SSR을 만들어보고 advanced가 본격적으로 리액트를 만들어보는 거 였던 듯 진짜 마치 1주차로 돌아온것 같은 기시감.. 미친 거 아닐까... 그래도 풀 스택을 해서 초반에는 그래도 좀 할만했던 것 같은데 리액트 버전으로 들어가면서 난이도 하드인 것을 체감... 어렵다... 그래도 다 하고 나니 뿌듯한 것 중에 손에 꼽는 과제이긴 했다.</p>
<p>10주차 때 역시, 마지막은 최적화아니겠나.. 최적화는 좀 생각보다 난해했다고 생각했다. 이게 어떻게 최적화되는지 알기 어렵다고 해야하나. 그래서 업데이트할 때 하이라이트되는 기준으로 해서 최대한 렌더링이 적게 될 수 있도록 작업하기 위해 노력했다.</p>
<p>이렇게 10주를 달려왔다. 회사-카페-집<del>(가끔 발제들으러 팀스파르타 건물을 가곤했지...)</del>을 어떻게 해냈나 싶다. 사실 그때 운동도 같이 했었다. 일주일 2번 필라테스랑 PT를 병행했었다. 근데 부트캠프를 하면서 운동을 해주지 않았다면 나는 체력이 훨씬 많이 떨어졌을 것이고, 10주차를 견디지 못해지 못했을지도 모른다. </p>
<p>무더웠던 여름에 시작해서 슬슬 가을이 오겠다 싶은 계절에 부트캠프는 마무리 되었다. 정말 좋았던 건 과제를 하거나 멘토 코칭도 정말 좋았지만, 같은 과제를 하는 동지가 적어도 40명 이상은 되었다는 것...? 힘들 때 같은 처지이기 때문에 비로소 서로 기댈 수 있는 사람들. zep에 와서 한 번씩 한탄도 할 수 있고.
<del>(하루에 루틴 중에 하나가 한바뀌 쭉 순찰하는게 루틴이었던... &#39;나만 힘들지 않잖고 모두가 힘들잖아&#39;를 마음에 새길 수 있는 루틴이었던달까. 다른 사람 뭐하는지 구경도 좀 하고 ㅎ_ㅎ)</del>
부트 캠프가 끝나고 나서 같이 모각코도 하고, 송년회도 하고 올해는 연말을 재밌게 보내게 되었다.
게다가 같은 나이의 동기도 2명이나 있었다. 같은 나잇대가 있다는 건 생각보다 되게 안심이 되는 일이다.. 덜 외롭지 않기 때문일까. 아니다 동질감이 느껴져서인지도 모르겠다.</p>
<p>새삼 벌써 수료한지 2달이 넘었다니 진짜 시간이 빠르다 빨라. 게다가 2025년도 얼마 남지 않았던가. 2025년이 넘기 전에 회고하면서 끝낼 수 있어서 다행이다.
<img src="https://velog.velcdn.com/images/mj_journal/post/53c363bd-1118-4f79-894d-61e8b611b38b/image.png" width="50%" height="auto">
마지막으로 수료한 뱃지를 올리며 마무리해본다.</p>
<p>p.s. 진짜진짜로 힘들었지만, 했던 걸 후회하지 않는다. 사람은 도전과 어려움 속에 성장한다고 하지 않나. 그 동안 당신이 이루지못했던 성취감을 맛볼지도 모른다. 그러니 또 도전하고 나아가야 한다. 그 끝에 이룬 결실은 생각보다 더 달콤할지도 모르니까.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[1-2. 과제 복습 일지]]></title>
            <link>https://velog.io/@mj_journal/1-2.-%EA%B3%BC%EC%A0%9C-%EB%B3%B5%EC%8A%B5-%EC%9D%BC%EC%A7%80</link>
            <guid>https://velog.io/@mj_journal/1-2.-%EA%B3%BC%EC%A0%9C-%EB%B3%B5%EC%8A%B5-%EC%9D%BC%EC%A7%80</guid>
            <pubDate>Wed, 03 Dec 2025 10:30:18 GMT</pubDate>
            <description><![CDATA[<h2 id="1-2를-돌아보면">1-2를 돌아보면,</h2>
<p>1주차가 너무 힘들었던지.
‘2주차의 과제는 좀 할만한데’ 라는 생각이 들 정도였다.
그 때 AI가 다해주긴 했지만, 어쨌든 생각보다 빨리 끝났다.
1주차에 비하면 천국인데?? 싶었더랬찌</p>
<h2 id="두번째도-생각보다-쉽지-않았다">두번째도 생각보다 쉽지 않았다.</h2>
<ul>
<li>게다가 이번에는 TS도 추가해주었다. 타입이 추론되니까 어떤 매개변수가 오게 되는지 좀 더 명확해져서 TS를 왜 추가해야 하는지 JS 때 빼져리게 느낀 것 같다. 없으면 어떤 타입이 오는 지 알기가 너무 힘드로~</li>
<li>처음 작업할 때는 1주차처럼 되지 않기 위해서 마음이 급해져서 마구잡이로 좀 사람들의 코드를 훔치러 다닌 감이 없지 않아 있다. 괴도 키드처럼</li>
<li>게다가 대부분을 AI에게 많이 의존하였다. 이번에도 좀 있었지만, 첫번째 처럼은 아니었다. 그래서 마지막에 테스트 코드 e2e가 팡팡 터지는데 문제가 무엇인지 알기가 힘들었다.</li>
</ul>
<blockquote>
<p><strong>결국 찾은 문제의 원인</strong></p>
</blockquote>
<ul>
<li>바로 <code>boolean</code> 처리를 게을리 하였다. 괜찮을지 알고 냅뒀는데 알고 보니, 이게 원인이었다. 꽤 오랫동안 디버깅이며 뭐며 했는데 알기가 힘들었는데 다행히도 이전 코드랑 비교를 해봤을 때. 달라진 부분은 이것밖에 었었다. </li>
<li>역시 사람은 미루면 안된다 미루니까 이렇게 되는 것... 
다시 한 번 반성하게 된다!! <strong>미 루 지 말 자!!!!!!!!</strong></li>
</ul>
<h2 id="이번에는-개선점보다-실제-react에서-virtaul-dom을-사용해서-html-렌더링되는-과정을-풀어보자">이번에는 개선점보다 실제 React에서 Virtaul Dom을 사용해서 HTML 렌더링되는 과정을 풀어보자.</h2>
<ul>
<li>리액트에서 어떻게 컴포넌트와 태그가 렌더링되는지 알면 알수록 재밌다. </li>
<li>실제로 구현해보면 생각보다 많은 필터링을 거치고, react가 하나부터 열까지 다 해주기 때문에 우리가 컴포넌트와 태그를 작성하면 화면에서 확인할 수 있다는 사실을 눈으로 보고 코드로 작성하면서 익힐 수 있는 시간이기도 하다. 이벤트 기능 구현까지도 말이다.</li>
</ul>
<h2 id="초기-렌더링의-흐름을-보자">초기 렌더링의 흐름을 보자</h2>
<p><img src="https://velog.velcdn.com/images/mj_journal/post/49c2751e-8e4f-452a-956c-b592d9d6d54f/image.png" alt=""></p>
<h3 id="렌더링을-위한-객체-생성-createvnode-함수">렌더링을 위한 객체 생성: createVNode 함수</h3>
<pre><code class="language-javascript">export function createVNode(
  type: string | Function,
  props: Record&lt;string, any&gt; | null,
  ...children: VNodeChild[]
): VNode {
  return {
    type,
    props,
    children: children
      .flat(2)
      .filter((child) =&gt; child !== null &amp;&amp; child !== undefined &amp;&amp; child !== true &amp;&amp; child !== false) as VNodeChild[],
  };
}
</code></pre>
<blockquote>
<ul>
<li>리액트는 기본적으로 얕은 비교를 하기 때문에 <code>flat</code> 메소드로 배열을 평평하게 만들어 준다.</li>
</ul>
</blockquote>
<ul>
<li>엘리먼트에 사용되지 않는 <code>null, boolean, undefined</code>는 제거 해준다.</li>
<li>이렇게 만들어진 객체를 <code>renderElement</code> 함수에 매개변수로 전달해준다.</li>
</ul>
<h3 id="렌더링의-시작-renderelement-함수">렌더링의 시작: renderElement 함수</h3>
<pre><code class="language-javascript">export function renderElement(vNode: VNodeChild, container: HTMLElement) {
  const node = normalizeVNode(vNode);

  if (!container.firstChild) {
    const elements = createElement(node);
    container.append(elements);
  } else {
    updateElement(container, node, container[&quot;_vNode&quot;]);
  }

  container[&quot;_vNode&quot;] = node;

  return setupEventListeners(container);
}
</code></pre>
<blockquote>
</blockquote>
<ul>
<li>먼저, <code>renderElement</code>에서 먼저 들어오는 컴포넌트 혹은 HTML 태그와 부모 컨테이너를 확인한다. </li>
<li>이 컨테이너가 처음 렌더링하는 것인지 이미 존재하는 컨테이너인지 확인하여, 컨테이너에 태그를 추가할지 <code>updateElement</code> 함수로 돌려서 업데이트시킨다.</li>
<li>마지막에 이벤트 함수를 <code>addEventListner</code>에 추가 혹은 제거하여 컨테이너를 반환한다.</li>
</ul>
<h3 id="vnode의-정규화-normalizevnode-함수">vNode의 정규화: normalizeVNode 함수</h3>
<pre><code class="language-javascript">export function normalizeVNode(vNode: VNodeChild) {
  if (typeof vNode === &quot;boolean&quot; || typeof vNode === &quot;undefined&quot; || vNode === null) {
    return &quot;&quot;;
  }

  if (typeof vNode === &quot;number&quot; || typeof vNode === &quot;string&quot;) {
    return `${vNode}`;
  }

  const node = vNode as VNode;

  if (typeof node?.type === &quot;function&quot;) {
    return normalizeVNode(node.type({ ...node.props, children: node.children }));
  }

  if ((node?.children ?? []).length &gt; 0) {
    return { ...vNode, children: node.children.map((child) =&gt; normalizeVNode(child)).filter(Boolean) };
  }

  return vNode;
}
</code></pre>
<blockquote>
<ul>
<li>boolean, undefined, null은 화면 렌더링하는데 필요하지 않음으로 빈 문자열로 리턴</li>
</ul>
</blockquote>
<ul>
<li>어짜피 숫자는 화면에 보일 때는 무조건 문자열이기 때문에 문자열과 같이 문자열로 리턴</li>
<li>매개변수가 위의 사항에 해당하지 않고, 타입이 function인 경우에는 아직 컴포넌트라는 얘기이기 때문에 현재 해당 함수를 재귀시킨다.</li>
<li>또한 매개변수에게 children이 있는 경우, 컴포넌트가 아직 있을 수 있고, 해당 값을 처리하기 위해 또한 현재 해당 함수를 재귀시킨다.</li>
</ul>
<h3 id="createelement-함수">CreateElement 함수</h3>
<pre><code class="language-javascript">export function createElement(vNode: VNodeChild) {
  if (typeof vNode === &quot;boolean&quot; || vNode === undefined || vNode === null) {
    return createTextNode(&quot;&quot;);
  }

  const node = vNode as VNode;

  if (Array.isArray(vNode)) {
    const fragment = document.createDocumentFragment();
    vNode.forEach((item) =&gt; {
      fragment.appendChild(createElement(item));
    });

    return fragment;
  }

  if (typeof node.type === &quot;string&quot;) {
    const tag = updateAttributes(createTag(node.type), node.props);

    node.children.forEach((item) =&gt; {
      tag.appendChild(createElement(item));
    });

    return tag;
  }

  if (typeof node?.type === &quot;function&quot;) {
    throw Error();
  }

  return createTextNode(`${vNode}`);
}</code></pre>
<blockquote>
<ul>
<li>실제로 이 부분은 화면을 렌더링하기 위해서 태그를 만들어 내는 부분이다.</li>
</ul>
</blockquote>
<ul>
<li>부모에게 텍스트를 appendchild를 시키기 위해서는 text node만 가능하기 때문에, 기본 자료형같은 경우에는 빈문자열 또는 문자열로 <code>document.createTextNode</code>하여 텍스트 노드를 만들어 내어 appendchild 되기 전에 상태로 만들어 준다.</li>
<li>다만 배열인 경우에는, <code>document.createDocumentFragment</code>로 빈 노드 객체를 생성시켜서 배열을 돌려서 빈 노드 객체에 appendchild한다. 다만, 여기서도 구조가 복잡한 경우, 재귀화시켜서 모두 빠짐없이 Element를 만들 수 있도록 해준다.</li>
<li>타입이 문자열인 경우에는, 더 이상 컴포넌트가 아니라는 소리이기 때문에, 일단 컨테이너에게 <code>updateAttributes</code>로 태그에 해당하는 className이나 disabled..또 이벤트 함수를 addEvent로 적용해준다.</li>
</ul>
<h3 id="엘리먼트에-이벤트-함수를-추가-setupeventlisteners">엘리먼트에 이벤트 함수를 추가: setupEventListeners</h3>
<pre><code class="language-javascript">export function setupEventListeners(root: HTMLElement) {
  rootContainer = root;

  for (const eventType of eventTypes) {
    const existingListener = listenerMap.get(eventType);

    rootContainer.removeEventListener(eventType, existingListener); // 같은 함수!
    listenerMap.set(eventType, newListener);
    rootContainer.addEventListener(eventType, newListener);
  }
}

export function addEvent(element: HTMLElement, eventType: string, handler: Function) {
  //...생략
}

export function removeEvent(element: HTMLElement, eventType: string, handler: Function) {
  //...생략
}
</code></pre>
<blockquote>
<ul>
<li>CreateElement 함수에서 <code>updateAttributes</code>를 통해 이전에 <code>addEvent</code>를 해주었다. <code>addEvent</code> 함수는 <code>setupEventListeners</code>에서 실제로 실행하기 위해 추가해야하는 이벤트의 타입과 함수를 저장하는 역할을 한다.</li>
</ul>
</blockquote>
<ul>
<li><code>removeEvent</code>는 <code>updateElement</code>에서 이벤트 함수를 삭제해야 할 경우, 기존에 저장되어 있는 상수에서 삭제해야 하는 이벤트 함수와 더 나아가서는 이벤트 타입까지 삭제한다.</li>
<li><code>renderElement</code> 함수에서 값을 리턴하기 전에 먼저, <code>setupEventListeners</code>를 실행해준다. 왜냐면 여기서 필요한 이벤트 함수를 붙여주기 때문이다. 물론 혹시나 기존의 이벤트 함수 때문에 영향을 받을 수 있기 때문에 그 전에 영향을 받지 않도록 이벤트를 모두 삭제 한 후에 다시 붙여준다. </li>
</ul>
<h2 id="업데이트-렌더링이-초기-렌더링과-다른-건">업데이트 렌더링이 초기 렌더링과 다른 건...</h2>
<p>딱 하나! updateElement 함수이다.
<img src="https://velog.velcdn.com/images/mj_journal/post/756225b4-ea40-4ca3-ae63-9a03e79f1ef2/image.png" alt=""></p>
<h3 id="다른-부분을-비교하여-업데이트-updateelement-함수">다른 부분을 비교하여 업데이트: updateElement 함수</h3>
<pre><code class="language-javascript">export function updateElement(parentElement: HTMLElement, newNode: VNodeChild, oldNode: VNodeChild, index = 0) {
  const rNewNode = newNode as VNode;
  const rOldNode = oldNode as VNode;

  if (!oldNode &amp;&amp; newNode) {
    // 새 요소 추가!
    parentElement.appendChild(createElement(rNewNode));
  } else if (oldNode &amp;&amp; !newNode) {
    // 자식 제거할 때 children 범위가 앞당겨질 경우, index가 벗어나지 않도록 조정
    const oldIndex = parentElement.childNodes.length &lt;= index ? parentElement.childNodes.length - 1 : index;
    const oldElement = parentElement.childNodes?.[oldIndex];

    if (oldElement) {
      // 요소 제거!
      parentElement.removeChild(oldElement);
    }
  } else if (rNewNode?.type !== rOldNode?.type) {
    const node = createElement(rNewNode);

    // 완전 교체!
    parentElement.replaceChild(node, parentElement.childNodes[index]);
  } else if (typeof newNode === &quot;string&quot;) {
    if (newNode !== oldNode) {
      parentElement.childNodes[index].textContent = newNode;
    }
  } else {
    const element = parentElement.childNodes[index];
    // 속성 업데이트
    const target = updateAttributes(element as HTMLElement, rNewNode.props, rOldNode.props);

    // 자식들 재귀 업데이트!
    const maxLength = Math.max((rNewNode.children || []).length, (rOldNode.children || []).length);
    for (let i = 0; i &lt; maxLength; i++) {
      updateElement(target, rNewNode.children[i] ?? null, rOldNode.children[i], i);
    }
  }

  return parentElement;
}
</code></pre>
<blockquote>
<ul>
<li>업데이트할 때, 기존의 element와 새로운 element를 비교하여 </li>
</ul>
</blockquote>
<ul>
<li>기존의 element가 없는데 새로운 element가 추가된거면 createElement 함수로 새로운 elemetn를 생성하여 컨테이너에 appnendchild시켜주고, </li>
<li>new element가 없는 경우에는 기존의 element를 삭제하며,</li>
<li>기존의 type과 새로운 type이 다른 경우 element를 변경하며,</li>
<li>새로 들어오는 vNode가 다만, element가 아니라 문자열인 경우에는 textContent를 이용하여 문자열을 추가해주며,</li>
<li>그 외 같은 경우에는 updateAttributes 함수로 필요한 부분이나 필요없는 부분을 삭제 또는 추가하여 업데이트하고, 만약에 자식들이 있는 경우에는 재귀화를 시켜서 현재 컨테이너를 리턴한다.</li>
</ul>
<h2 id="마무리를-하며">마무리를 하며,</h2>
<ul>
<li>1-1 과제에서는 주로 개선한 점을 찾았는데, 이번 과제는 개선하는 것 보다는 어떠 흐름인지 과정인지를 아는 게 더. 중요하다고 생각해서 이번에는 rendering되는 흐름을 위주로 해서 포스팅하여 마무리지었다. </li>
<li>항상 하는 생각이지만, A를 넣어서 B가 딱 하고 그냥 나오는 결과를 아는 건 생각보다 재미없다. A가 어떤 과정을 거쳐서 필터링 되고 깍아져서 B로 나왔는지 알았을 때 비로소 B의 결과물이 빛난다고 생각한다. 물론 재미의 면도 똑같고. 이거랑 똑같은 결이 아마도 1-3의 과제와 비슷할 것 같다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[1-1.  과제 복습 일지]]></title>
            <link>https://velog.io/@mj_journal/1-1.-%EA%B3%BC%EC%A0%9C-%EB%B3%B5%EC%8A%B5-%EC%9D%BC%EC%A7%80</link>
            <guid>https://velog.io/@mj_journal/1-1.-%EA%B3%BC%EC%A0%9C-%EB%B3%B5%EC%8A%B5-%EC%9D%BC%EC%A7%80</guid>
            <pubDate>Mon, 10 Nov 2025 10:45:33 GMT</pubDate>
            <description><![CDATA[<p>(순수 자바스크립트로 저런 걸 만드는 과제였따.)</p>
<p>저 때는 쇼핑몰을 한 번만 할 줄 알았찌...
또핑몰이 될지는 몰랐던 ㅎ</p>
<blockquote>
<p>1-1. 프레임워크 없이 SPA 만들기</p>
</blockquote>
<h2 id="1-1-과제를-잠깐-돌아보면">1-1 과제를 잠깐 돌아보면,</h2>
<p>처음에는 이 정도면 할만할 꺼 같은데..? 라는 생각으로 뛰어들었지만 그거슨 나의 메타인지 부족이었다. 
시간은 부족한데 테스트 코드에 내 코드는 자꾸 Fail을 하고 있었다.
순수 자바스크립트는 너무나도 오랜만에 핸들링해봐서 이벤트 함수를 선언하는 방법조차 까먹었고 있었다.
어찌저찌 GPT도 이용해먹고~ 과제 통과를 조금 쉽게 낮춰줘서 얼레벌레 통과한 감이 없지 않아 있었다.
그래서 복습해야하는 과제 중에 Top3 안에 든다고 생각했다.</p>
<p>그렇게 시간은 약 한 달 정도 걸려서 과제를 완성했다. 그 때 과제를 막 받았을 때 어떻게 한 주 만에 했는지 스스로도 의아할 지경이긴 했다.
아마도 지금은,</p>
<ol>
<li>강제성이 없다.</li>
<li>그 전보다 구조화된 코드를 짜기 위해 노력했다.</li>
<li>과제할 당시에는 하지 않았던 리팩토링을 여러 번 하였다.</li>
<li>그 때는 과제를 하기 위해 직장에 있을 때 빼고는 온종히 과제를 하기 위해 매달렸고, 지금은 이래저래 과제에 온전히 몰입하지 못했기 때문이 아닐까</li>
</ol>
<p>로 예상해본다.</p>
<h2 id="처음할-때보다-무엇을-개선했는가">처음할 때보다 무엇을 개선했는가</h2>
<h3 id="1-상태-관리-observer-패턴으로-모듈화">1. 상태 관리 Observer 패턴으로 모듈화</h3>
<p><strong>이전</strong>
과제에서는 <code>const</code>도 아니고 <code>let</code>을 해당 페이지 컴포넌트에서 선언하여 어디서든 변경이 가능한 전역 구조의 형태가 띄었다.</p>
<pre><code class="language-javascript">// src/pages/HomePage.js
// 아래와 같은 방식으로 선언하여 내부에서 사용하였다.

let state = { products: [], ... };</code></pre>
<p>또한 상태 변경이 됐을 때, 일일히 <code>innerHTML</code>를 사용해서 수동으로 렌더링해주는 기함을 토했다.. 
<del>(그 때는 이런 스파게티같은 코드를 신경쓸 틈이 없었다고...)</del></p>
<pre><code class="language-javascript">
// 대놓고 state에 데이터를 저장시켰다. wow.. (불변성은 어디감? ㅋㅋ)
  state.products = projectData.products;
// 심지어 아래의 코드를 반복해서 쓰는데 함수화시킬 생각조차도 생각하지 못했다는 게 아쉬운 대목...
  document.body.querySelector(&quot;#root&quot;).innerHTML = Main(state)</code></pre>
<p><strong>현재</strong>
createStore로 상태관리를 캡슐화</p>
<pre><code class="language-javascript">// createStore에 초기상태를 받아서 선언해주면 되도록 바꿔주었다.
    const productListStore = createStore(initialState);

// 그래서 실제 다른데서 갖다 쓸 때는 커스텀 훅처럼 선언하여 쓸 수 있다.
    const { getState, getProductDetail, setState } = productItemDetail();</code></pre>
<p>상태 변화를 구독하여 자동으로 렌더링</p>
<pre><code class="language-javascript">// 이전에 선언했던것처럼 하지 않아도 구독하고 있기 때문에 setState로 데이터를 변경하면 자동으로 알아서 렌더링된다.
    productListStore.setState({ products: newProducts });</code></pre>
<h3 id="2-아키텍쳐-개선">2. 아키텍쳐 개선</h3>
<p><strong>이전</strong>
컴포넌트화도 제대로 하지 않고, page 파일에 모든 로직을 박아넣었기 때문에 굉장히 폴더 구조가 간단하다.</p>
<pre><code>src/
├── components/ 
│   ├── FirstCategory.js
│   ├── List.js
│   ├── LoadingProduct.js
│   ├── Product.js
│   └── detail.js
├── pages/      
│   ├── HomePage.js                       
│   ├── Main.js                          
│   ├── NotFoundPage.js
│   ├── ProjectDetail.js
│   └── ProjectDetailPage.js
└── main.js</code></pre><p><strong>현재</strong></p>
<ul>
<li>state, pages, components, utils, type별로 폴더화하여 아키텍쳐를 개선하였다.</li>
<li>또한 그 페이지에서만 쓰는 컴포넌트는 pages에서 해당 폴더 안에 컴포넌트로 구현하여 구조를 개선시켰다.<pre><code>src/
├── state/                                
│   ├── baseStore.ts                      Observer 패턴 구현
│   ├── cart.ts                           
│   ├── productList.ts                   
│   ├── productDetail.ts                  
│   └── toast.ts                    
├── pages/                               
│   ├── list/                           
│   │   ├── index.ts                   
│   │   ├── card.ts                   
│   │   ├── searchBar.ts    
│   │   ├── loading.ts                    
│   │   └── constants.ts (list에서만 쓰는 상수)
│   │
│   ├── detail/   
│   │   ├── index.ts 
│   │   ├── detail.ts
│   │   ├── breadcrumb.ts
│   │   ├── products.ts
│   │   ├── card.ts
│   │   ├── review.ts
│   │   └── loading.ts 
│   │
│   └── error/ 
├── components/ 
│   ├── layout.ts
│   ├── header.ts
│   ├── footer.ts 
│   ├── skeleton.ts 
│   ├── cart/
│   └── toast/
│
├── utils/ 
│   ├── eventDelegation.ts 
│   ├── fetch.ts 
│   └── clean.ts                         cleanup 함수
│
├── type/                        
├── router.ts                           라우터 모듈
└── main.js
</code></pre></li>
</ul>
<pre><code>
### 3. 라우팅 모듈화
**이전**
- `main.js`에 `router` 코드를 진짜 단순하게 만들어서 그냥 `main.js`에서 사용했다.
- 게다가 페이지 이동할 때 쓰겠다고 `router` 함수 `export`를 사용해서 만들어 버렸다.

```javascript
  export function router() {
    const [, route, id] = location.pathname.replace(&quot;/front_6th_chapter1-1&quot;, &quot;&quot;).split(&quot;/&quot;);

    if (route === &quot;product&quot; &amp;&amp; id) {
      return ProjectDetailPage(id);
    }

    if (!route) {
      return HomePage();
    }

    document.body.querySelector(&quot;#root&quot;).innerHTML = _404_();
  }</code></pre><p><strong>현재</strong>
<code>main.js</code>에서는 state를 구독하기 위한 코드로 넣어졌다.</p>
<pre><code class="language-javascript">// main.js
// subscribeToStore를 통해 state를 구독하였다.

  function main() {
    const appRouter = router();

    const root = document.getElementById(&quot;root&quot;);
    if (root) {
      root.innerHTML = appRouter.render();
    }

    // ✅ 구독 - 상태 변경시 자동 렌더링
    appRouter.subscribeToStore(toastStore);
    appRouter.subscribeToStore(cartStore);
    appRouter.subscribeToStore(productStore);
    appRouter.subscribeToStore(productListStore);

    // popstate 이벤트 리스너 등록 (뒤로가기/앞으로가기, 테스트의 goTo 지원)
    window.addEventListener(&quot;popstate&quot;, () =&gt; {
      appRouter.render();
    });
  }
</code></pre>
<p><code>router</code> 파일을 따로 구축하여 <code>main.js</code>의 코드를 개선했다. 
또한 캡슐화하면서 <code>render, push, subscribe</code>를 같이 구현하였다.</p>
<pre><code class="language-javascript">// router.ts
export const router = () =&gt; {
  const render = (path?: string) =&gt; {
    const link = path ? path : location.pathname;
    const originLink = checkParameter(link)[0];
    const pathnames = link.split(&quot;/&quot;);
    let html = &quot;&quot;;

    // (... 컴포넌트 결정하는 함수 생략)

    // DOM 업데이트
    const root = document.getElementById(&quot;root&quot;);
    if (root) {
      root.innerHTML = html;
      // DOM 변경 이벤트 발생
      root.dispatchEvent(new Event(&quot;DOMContentLoaded&quot;, { bubbles: true }));
    }
    return html;
  };

  const push = (path: string) =&gt; {
    window.history.pushState(null, &quot;&quot;, path);
    const pathnames = path.split(&quot;/&quot;);

    //(...reset을 위한 코드 생략)

    render(path);
  };

  // store를 구독 - 상태 변경시 자동 렌더링
  const subscribeToStore = (store: { subscribe: (listener: () =&gt; void) =&gt; () =&gt; void }) =&gt; {
    return store.subscribe(() =&gt; {
      render();
    });
  };

  return { render, push, subscribeToStore };
};</code></pre>
<h3 id="4-이벤트-리스너-모듈화-및-메모리-누수-개선">4. 이벤트 리스너 모듈화 및 메모리 누수 개선</h3>
<p><strong>이전</strong>
어떤 <del>(가령.. Proudct List, Product detail 라던지)</del> 페이지에 addEventListener의 동일한 패턴의 코드를 계속 나열해서 사용하였다.</p>
<pre><code class="language-javascript">// HomePage.js
// 정말 쓸데없이 document.addEventListener의 계속 똑같은 코드가 나열되고 있다.
  document.addEventListener(&quot;change&quot;, (e) =&gt; {
    if (e.target.matches(&quot;#limit-select&quot;)) { ... }
    if (e.target.matches(&quot;#sort-select&quot;)) { ... }
  });

  document.addEventListener(&quot;input&quot;, (e) =&gt; {
    if (e.target.matches(&quot;#search-input&quot;)) { ... }
  });

  document.addEventListener(&quot;click&quot;, (e) =&gt; {
    if (e.target.matches(&quot;#quantity-increase&quot;)) { ... }
  });</code></pre>
<p><strong>현재</strong></p>
<ul>
<li>아예 utils 폴더에 eventDelegation라는 파일로 따로 분리하여 캡슐화하여 다른 페이지에서는 import해서 사용할 수 있다.</li>
<li><code>AbortController</code>의 signal로 이벤트와 연결하여 cleanup 할 때는 abort하여 일괄 제거함으로써 메모리 누수를 방지하였다.</li>
</ul>
<pre><code class="language-javascript">// eventDelegation.ts
const createEventManager = () =&gt; {
  const registeredHandlers: {
    input: Map&lt;string, (e: Event) =&gt; void&gt;;
    change: Map&lt;string, (e: Event) =&gt; void&gt;;
    click: Map&lt;string, (e: Event) =&gt; void&gt;;
  } = {
    input: new Map(),
    change: new Map(),
    click: new Map(),
  };

  let globalInitialized = false;
  let abortController = new AbortController();

  // 공통 로직을 헬퍼 함수로 추출(생략)

  const initializeGlobalListeners = () =&gt; {
    if (globalInitialized) return;

    document.addEventListener(&quot;input&quot;, (e: Event) =&gt; {
      const id = (e.target as HTMLElement).id;
      registeredHandlers.input.get(id)?.(e);
    });

    document.addEventListener(&quot;change&quot;, (e: Event) =&gt; {
      const id = (e.target as HTMLElement).id;
      registeredHandlers.change.get(id)?.(e);
    });

    document.addEventListener(&quot;click&quot;, (e: Event) =&gt; {
      const target = e.target as HTMLElement;
    });

    globalInitialized = true;
  };

  return {
    register: (handlers: EventHandlers) =&gt; {
      initializeGlobalListeners();

      registerHandlers(&quot;input&quot;, handlers.input);
      registerHandlers(&quot;change&quot;, handlers.change);

      registerHandlers(&quot;click&quot;, handlers.click, (id, handler) =&gt; {
        document.querySelectorAll(`#${id}`).forEach((element) =&gt; {
          if (!element.hasAttribute(&quot;data-click-attached&quot;)) {
            element.setAttribute(&quot;data-click-attached&quot;, &quot;true&quot;);
            element.addEventListener(&quot;click&quot;, handler, { signal: abortController.signal });
          }
        });
      });
    },

    cleanup: () =&gt; {
      abortController.abort();
      abortController = new AbortController();
    },
  };
};

// 싱글톤 인스턴스
const manager = createEventManager();

export const createEventDelegation = (handlers: EventHandlers) =&gt; {
  return () =&gt; manager.register(handlers);
};
</code></pre>
<p>다른 파일에서 사용할 때는, <code>createEventDelegation</code>를 import해와서 선언하여 사용하면 이전처럼 일일히 <code>addEventListener</code>를 선언하지 않아도 사용할 수 있게 되었다.</p>
<pre><code class="language-javascript">  createEventDelegation({
    input: {
      &quot;search-input&quot;: (e) =&gt; { ... }
    },
    change: {
      &quot;limit-select&quot;: (e) =&gt; { ... },
      &quot;sort-select&quot;: (e) =&gt; { ... }
    },
})();</code></pre>
<h3 id="5-ts-추가">5. TS 추가</h3>
<p><strong>이전</strong>
원래는 JS 파일을 쓰고 있었다. 그러다 보니 타입 추론하기가 쉽지 않아 화면에서 <code>undefined</code>가 나타나 프로덕션이 안정적이지 않았다.</p>
<pre><code class="language-javascript">let state = {
  products: [],
  pagination: {}
};</code></pre>
<p><strong>현재</strong>
타입을 추가하면서 특히 API에서 어떤 데이터가 떨어지는지 알기 쉽게 되어 타입에 대한 에러가 있을 경우, 즉각적으로 알 수 있게 되어 이전보다 한층 안정적인 프로덕션이 됐다.</p>
<pre><code class="language-javascript">interface Product {
  title: string;
  lprice: string;
  productId: string;
  // ...
}

interface Pagination {
  page: number;
  limit: number;
  total: number;
  // ...
}

const state: {
  products: Product[];
  pagination: Pagination;
} = { ... };</code></pre>
<h2 id="깨달은-점">깨달은 점</h2>
<p>순수 자바스크립트로 리액트의 동작을 구현해보면서, 
아 &#39;이래서 상태변화는 최소한으로 하는게 좋구나&#39;를 알 수 있고, 순수 자바스크립트에서 가상 돔을 구현하지 않는 이상 무조건 root의 innerHTML를 돌려야 하기 때문에 큰 처리 비용이 들어서 무조건 상태 변화는 적어야 프로덕션에 무조건 이득이라서 리액트에서도 왜 상태 처리를 한 번 하려고 하는지 알 수 있던 덕목 중에 하나이며,
라우터는 페이지 이동이나 화면에서 알맞는 컴포넌트를 렌더링하는 것으로 알고 있었긴 했는데 역시 라우터 또한 상태를 &#39;subscribe&#39;했기 때문에 알맞는 화면을 렌더링할 수 있다는 것을 이번에 복습하면서 특히 알 수 있었던 부분이기도 했다.</p>
<h2 id="마치며">마치며...</h2>
<p>이번에 다시 과제를 하면서 느꼈지만, 왜 리팩토링은 해도 해도 끝이 없는 거 같달까 그래도 이전 했던 과제의 코드보다는 많은 것을 개선한 것 같다.
과제도 easy 버전만 했다면 이번에는 hard 버전까지 완료하면서 
리팩토링 마친 것으로 끝!!!
(아니, 근데 솔루션 코드 보는데 라이프 사이클 구현이라니... 역시 클라쓰가 다르다 ㅋㅋㅋㅋ)</p>
]]></description>
        </item>
    </channel>
</rss>