<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>kim unknown.log</title>
        <link>https://velog.io/</link>
        <description>과거의 나에게 묻기 위한 기록</description>
        <lastBuildDate>Thu, 10 Jul 2025 07:48:33 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>kim unknown.log</title>
            <url>https://velog.velcdn.com/cloudflare/kim_unknown_/01c4feb5-7715-4075-a0ca-829fda403bdb/d391b78af2c2b37119e4d301b6dd43ca.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. kim unknown.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/kim_unknown_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[html-to-image Safari 크로스 브라우징]]></title>
            <link>https://velog.io/@kim_unknown_/html-to-image-Safari-%ED%81%AC%EB%A1%9C%EC%8A%A4-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A7%95</link>
            <guid>https://velog.io/@kim_unknown_/html-to-image-Safari-%ED%81%AC%EB%A1%9C%EC%8A%A4-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A7%95</guid>
            <pubDate>Thu, 10 Jul 2025 07:48:33 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>웹페이지를 이미지로 저장하는 기능을 구현하게 되었다. 하지만 언제나 복병인 ios/safari에서 웹페이지 내부에 자리하고 있는 이미지 요소가 누락되어 저장되는 이미지가 발생했고, 이를 해결한 과정을 기록해보려고 한다.</p>
</blockquote>
<p>우선 나는 이미지화를 위해 <code>html-to-image</code> 라이브러리를 활용했다.
html-to-image의 동작 방식부터 알아보자.</p>
<br>

<h2 id="1-html-to-image-동작-방식">1. html-to-image 동작 방식</h2>
<ol>
<li>DOM 요소를 SVG 문자열로 직렬화</li>
<li>SVG를 <code>&lt;img&gt;</code>로 만든 후 <code>&lt;canvas&gt;</code>에 그림</li>
<li>그린 결과물을 다시 <code>toBlob()</code>으로 추출
👉 이 때, SVG 내부에 들어간 image, font 등이 GPU 레이어에 반영되기 전이면
빈 그림이 그려지며 이미지가 누락된 캡쳐가 발생</li>
</ol>
<br>


<h2 id="2-이슈-원인--safari-비동기-렌더링">2. 이슈 원인 : Safari 비동기 렌더링</h2>
<p>내가 파악한 이슈의 원인은 다음과 같다.</p>
<p>Safari는 html-to-image로 DOM을 캡쳐할 때, 외부 이미지나 blob URL을 렌더링 직전에 비동기로 불러온다.
렌더 타이밍에 따라 이미지가 GPU 레이어에 올라가기 전에 toBlob이 실행될 수 있다.
👉 이로 인해 간헐적으로 canvas에 그림을 못 그리고,  빈 blob이 생성된 채로 캡쳐 이미지가 생성된 것이었다.</p>
<p><em>💡 Safari는 DOM에 그려진 것처럼 보여도, 내부적으로 GPU 레이어에 실제 이미지나 폰트가 올라오지 않은 상태일 수 있다.</em></p>
<p><em>💡 Safari는 리소스 최적화를 위해 백그라운드 탭에서의 이미지 로딩을 지연시키거나, 크로스-오리진 이미지에서 CORS 정책을 더 엄격하게 적용하기도 한다.</em></p>
<br>

<h2 id="3-해결-방안--blob-사이즈를-활용하기">3. 해결 방안 : Blob 사이즈를 활용하기</h2>
<p>구글링해 본 결과 html-to-image 라이브러리에도 이미 해당 이슈가 등록 되어 있었고, 여러가지 방법을 시도해보았지만 이슈를 해결할 수 있었던 건 Blob 사이즈를 검사하는 방식이었다.</p>
<p>✨ Blob의 사이즈를 확인하여 이미지 요소가 정상적으로 렌더되었는지 확인 후 이미지 저장을 시도하도록 시점을 변경하였다.
⇒ Safari가 GPU 렌더링까지 끝낼 수 있도록 시간을 벌면서 반복 재시도
⇒ 이미지가 완전히 그려진 후 캡쳐됨으로써 이미지 누락 문제 해결</p>
<br>

<h2 id="4-시도한-safari-대응-방법">4. 시도한 Safari 대응 방법</h2>
<table>
<thead>
<tr>
<th>방법</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>await document.fonts.ready</td>
<td>폰트 렌더 대기 후 완료 확인</td>
</tr>
<tr>
<td>await requestAnimationFrame()</td>
<td>DOM이 GPU에 올라갈 시간 확보</td>
</tr>
<tr>
<td>프로젝트 내부 public 이미지 사용</td>
<td>CORS 대응을 위해 외부 이미지 대신 로컬 이미지 사용</td>
</tr>
<tr>
<td>pixelRatio는 최대 2로 설정</td>
<td>GPU 병목 방지</td>
</tr>
<tr>
<td>Blob size 체크 &amp; 반복 재시도</td>
<td>이미지 요소 정상 렌더 완료 확인</td>
</tr>
</tbody></table>
<br>

<p><strong>Summary</strong>
Safari가 이미지/폰트 렌더링을 완료하기 전에 캡쳐가 시도되어 이미지가 누락 되는 이슈 발생. 정상적으로 렌더링된 Blob의 사이즈인지 검사 후 캡쳐 시도하도록 개선.</p>
<br>

<hr>
<blockquote>
<p>🔥 <a href="https://github.com/bubkoo/html-to-image/issues/361">Image is not showing in some cases iOS, Safari</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[인프라 입문기 - Kubernetes]]></title>
            <link>https://velog.io/@kim_unknown_/start-infra-kubernetes</link>
            <guid>https://velog.io/@kim_unknown_/start-infra-kubernetes</guid>
            <pubDate>Fri, 10 Jan 2025 01:46:48 GMT</pubDate>
            <description><![CDATA[<h1 id="1-쿠버네티스-개념">1. 쿠버네티스 개념</h1>
<p><code>쿠버네티스</code>는 컨테이너 런타임을 통해 컨테이너를 다루는 도구를 말한다. 여러 서버에 컨테이너를 분산 배치하거나 문제가 생긴 컨테이너를 교체하는 등의 일을 해주며, 이를 <code>컨테이너 오케스트레이션</code>이라고 한다.</p>
<ul>
<li><p><strong>Pod</strong>
  Kubernetes의 기본 실행 단위이다.
  하나 이상의 컨테이너로 구성되며, 같은 네트워크 환경에서 실행된다.
  각 Pod는 자체 IP 주소를 가지며, 컨테이너들 간의 내부 통신이 가능하다.</p>
</li>
<li><p><strong>Service</strong>
  Pod들을 외부와 연결하거나 클러스터 내에서 접근할 수 있게 한다.
  로드 밸런싱, 서비스 디스커버리 등을 제공하여 여러 Pod들을 단일 IP로 묶어 관리한다.</p>
</li>
<li><p><strong>Deployment</strong>
  애플리케이션의 상태를 정의하고, 업데이트를 관리한다.
  애플리케이션의 버전 관리와 자동 롤백, 확장 등을 지원한다.</p>
</li>
<li><p><strong>ReplicaSet</strong>
  특정 수의 Pod 복제본을 항상 유지하기 위해 사용한다.</p>
<pre><code> Deployment에 의해 관리되며, Pod의 수를 자동으로 조정한다.</code></pre></li>
<li><p><strong>Namespace</strong>
  클러스터 내에서 리소스를 논리적으로 분리할 수 있는 방법이다.
  여러 환경에서 동일한 리소스 이름을 사용하면서도 충돌을 방지할 수 있다.</p>
</li>
<li><p><strong>Node</strong>
  Kubernetes 클러스터 내에서 애플리케이션을 실행하는 서버이다.
  각 Node는 컨테이너를 실행할 수 있는 하나 이상의 Kubelet 프로세스를 실행하고 있다.</p>
</li>
<li><p><strong>Ingress</strong>
  외부 HTTP/HTTPS 요청을 클러스터 내부 서비스로 라우팅하는 규칙을 정의한다.
  일반적으로 로드 밸런서와 함께 사용되며, URL 경로 기반으로 요청을 처리할 수 있다.</p>
</li>
<li><p><strong>ConfigMap과 Secret</strong>
  애플리케이션에 대한 설정 정보를 저장하는 방법입니다.
  ConfigMap은 텍스트 기반 설정 정보를, Secret은 암호화된 민감한 정보를 저장합니다.</p>
</li>
</ul>
<h4 id="장점">장점</h4>
<ul>
<li><code>자동화</code>: 컨테이너의 배포, 확장, 복구를 자동으로 처리한다.</li>
<li><code>확장성</code>: 애플리케이션의 요구 사항에 맞게 클러스터 리소스를 동적으로 확장할 수 있다.</li>
<li><code>자원 최적화</code>: 클러스터 내 리소스를 효율적으로 분배하고, 여러 애플리케이션을 격리하여 실행할 수 있다.</li>
<li><code>운영 간소화</code>: 상태 관리, 모니터링, 로깅 등을 중앙에서 처리할 수 있어 운영 효율성이 높다.</li>
</ul>
<br>

<h1 id="2-쿠버네티스-실습">2. 쿠버네티스 실습</h1>
<br>

<h2 id="2-0-미니-쿠베-명령어">2-0. 미니 쿠베 명령어</h2>
<pre><code class="language-shell"># 미니쿠베 시작 (단일노드)
minikube start
minikube start -n 2 # 다중노드
minikube start --driver=docker # 도커를 가상 드라이버로 설정
minikube start -p helloworld # helloworld라는 이름의 프로필로 생성

# 미니쿠베 프로필 목록 확인
minikube profile list

# 현재 사용중인 프로필 확인
minikube profile

# 프로필 변경
minikube profile helloworld # helloword로 변경

# 미니쿠베 상태 확인
minikube status

# 미니쿠베 일시정지
minikube pause

# 미니쿠베 종료
minikube stop

# 미니쿠베 삭제
minikube delete # 미니쿠베 삭제하기</code></pre>
<br>

<h2 id="2-1-미니쿠베-설치-및-시작하기">2-1. 미니쿠베 설치 및 시작하기</h2>
<br>

<h3 id="1-미니쿠베-설치">(1). 미니쿠베 설치</h3>
<pre><code class="language-bash">&gt; brew install minikube

==&gt; Auto-updating Homebrew...
Adjust how often this is run with HOMEBREW_AUTO_UPDATE_SECS or disable with
HOMEBREW_NO_AUTO_UPDATE. Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).
.
.
.
==&gt; minikube
zsh completions have been installed to:
  /opt/homebrew/share/zsh/site-functions</code></pre>
<br>

<h3 id="2-미니쿠베-실행">(2). 미니쿠베 실행</h3>
<pre><code class="language-bash">&gt; minikube start
&gt;
😄  Darwin 15.2 (arm64) 의 minikube v1.34.0
✨  자동적으로 docker 드라이버가 선택되었습니다
📌  Using Docker Desktop driver with root privileges
👍  Starting &quot;minikube&quot; primary control-plane node in &quot;minikube&quot; cluster
🚜  Pulling base image v0.0.45 ...
💾  쿠버네티스 v1.31.0 을 다운로드 중 ...
    &gt; preloaded-images-k8s-v18-v1...:  307.61 MiB / 307.61 MiB  100.00% 21.89 M
    &gt; gcr.io/k8s-minikube/kicbase...:  441.45 MiB / 441.45 MiB  100.00% 12.76 M
🔥  Creating docker container (CPUs=2, Memory=7789MB) ...
🐳  쿠버네티스 v1.31.0 을 Docker 27.2.0 런타임으로 설치하는 중
    ▪ 인증서 및 키를 생성하는 중 ...
    ▪ 컨트롤 플레인을 부팅하는 중 ...
    ▪ RBAC 규칙을 구성하는 중 ...
🔗  bridge CNI (Container Networking Interface) 를 구성하는 중 ...
🔎  Kubernetes 구성 요소를 확인...
    ▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5
🌟  애드온 활성화 : storage-provisioner, default-storageclass
🏄  끝났습니다! kubectl이 &quot;minikube&quot; 클러스터와 &quot;default&quot; 네임스페이스를 기본적으로 사용하도록 구성되었습니다.

# 실행 상태 확인
&gt; minikube status
&gt;
minikube
type: Control Plane
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured</code></pre>
<br>

<h3 id="3-미니쿠베-프로필-설정">(3). 미니쿠베 프로필 설정</h3>
<pre><code class="language-bash"># 기존 실행 중인 미니쿠베 종료
&gt; minikube stop
&gt;
✋  &quot;minikube&quot; 노드를 중지하는 중 ...
🛑  &quot;minikube&quot;를 SSH로 전원을 끕니다 ...
🛑  1개의 노드가 중지되었습니다.

# testProfile 이라는 이름의 프로필 생성 및 시작
&gt; minikube start -p testProfile
&gt;
😄  [testProfile] Darwin 15.2 (arm64) 의 minikube v1.34.0
✨  자동적으로 docker 드라이버가 선택되었습니다
📌  Using Docker Desktop driver with root privileges
👍  Starting &quot;testProfile&quot; primary control-plane node in &quot;testProfile&quot; cluster
🚜  Pulling base image v0.0.45 ...
🔥  Creating docker container (CPUs=2, Memory=7789MB) ...
🐳  쿠버네티스 v1.31.0 을 Docker 27.2.0 런타임으로 설치하는 중
    ▪ 인증서 및 키를 생성하는 중 ...
    ▪ 컨트롤 플레인을 부팅하는 중 ...
    ▪ RBAC 규칙을 구성하는 중 ...
🔗  bridge CNI (Container Networking Interface) 를 구성하는 중 ...
🔎  Kubernetes 구성 요소를 확인...
    ▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5
🌟  애드온 활성화 : storage-provisioner, default-storageclass
🏄  끝났습니다! kubectl이 &quot;testProfile&quot; 클러스터와 &quot;default&quot; 네임스페이스를 기본적으로 사용하도록 구성되었습니다.

# 생성된 프로필 리스트 확인
&gt;  minikube profile list
&gt;
|-------------|-----------|---------|--------------|------|---------|---------|-------|----------------|--------------------|
|   Profile   | VM Driver | Runtime |      IP      | Port | Version | Status  | Nodes | Active Profile | Active Kubecontext |
|-------------|-----------|---------|--------------|------|---------|---------|-------|----------------|--------------------|
| minikube    | docker    | docker  | ---.---.--.- | 8443 | v1.31.0 | Stopped |     1 | *              |                    |
| testProfile | docker    | docker  | ---.---.--.- | 8443 | v1.31.0 | Running |     1 |                | *                  |
|-------------|-----------|---------|--------------|------|---------|---------|-------|----------------|--------------------|

# 프로필 변경
&gt; minikube profile minikube
&gt; ✅  minikube profile was successfully set to minikube</code></pre>
<br>

<h2 id="2-2-도커-이미지-배포하기">2-2. 도커 이미지 배포하기</h2>
<br>

<h3 id="1-deploymentyaml-파일-작성">(1). deployment.yaml 파일 작성</h3>
<pre><code class="language-yaml">apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-web
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-web
  template:
    metadata:
      labels:
        app: my-web
    spec:
      containers:
        - name: my-web
          image: my-web:1.0
          ports:
            - containerPort: 3000
---
apiVersion: v1
kind: Service
metadata:
  name: my-web-service
spec:
  selector:
    app: my-web
  ports:
    - protocol: TCP
      port: 80
      targetPort: 3000
  type: NodePort
</code></pre>
<br>

<h3 id="2-미니쿠베-이미지-업로드">(2). 미니쿠베 이미지 업로드</h3>
<ul>
<li>미니쿠베 활성화 : <code>eval $(minikube docker-env)</code></li>
</ul>
<pre><code class="language-bash"># 미니쿠베 도커 활성화
&gt; eval $(minikube docker-env)

# 도커 이미지 빌드
&gt; docker build -t my-web:1.0 .
&gt; [+] Building 19.2s (12/12) FINISHED     

# 이미지 전송
&gt; minikube image load my-web:1.0

# kubectl 컨텍스트 확인
&gt; kubectl config current-context
&gt; minikube

# 컨텍스트가 미니쿠베가 아니라면 미니쿠베로 변경 필요
&gt; kubectl config use-context minikube

# 쿠버네티스 배포
&gt; kubectl apply -f deployment.yaml
&gt; 
deployment.apps/my-web created
service/my-web-service created</code></pre>
<br>

<h3 id="3-배포-확인">(3). 배포 확인</h3>
<ul>
<li>미니쿠베 리소스 확인 : <code>kubectl get all</code></li>
</ul>
<pre><code class="language-bash"># minikube 리소스 확인
&gt; kubectl get all
&gt;
NAME                            READY   STATUS             RESTARTS   AGE
pod/my-web-57bc889df4-zc84r     0/1     ImagePullBackOff   0          29m

NAME                       TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
service/my-web-service     NodePort    10.101.36.165   &lt;none&gt;        80:32136/TCP   29m
service/kubernetes         ClusterIP   10.96.0.1       &lt;none&gt;        443/TCP        163m

NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/my-web     0/1     1            0           29m

NAME                                  DESIRED   CURRENT   READY   AGE
replicaset.apps/my-web-57bc889df4     1         1         0       29m</code></pre>
<br>

<h2 id="2-3-외부에서-접근할-수-있도록-설정">2-3. 외부에서 접근할 수 있도록 설정</h2>
<br>

<h3 id="1-serviceyaml-파일-생성---nodeport-설정">(1). service.yaml 파일 생성 - nodePort 설정</h3>
<p><code>Node Port</code>는 외부에서 접근할 수 있도록 포트를 개방한다. <code>Node Port</code>를 사용하면 외부에서 지정된 포트를 통해 서비스에 접속할 수 있다.</p>
<pre><code class="language-yaml">apiVersion: v1
kind: Service
metadata:
  name: my-web-service
spec:
  type: NodePort
  selector:
    app: my-web
  ports:
    - protocol: TCP
      port: 8080        # 외부에서 접근할 기본 포트
      targetPort: 3000  # 컨테이너의 포트
      nodePort: 30001   # 외부에서 접근할 NodePort (범위: 30000-32767)</code></pre>
<br>

<h3 id="2-deployment--service-배포">(2). deployment / service 배포</h3>
<ul>
<li>deployment 반영 : <code>kubectl apply -f deployment.yaml</code></li>
<li>service 반영 : <code>kubectl apply -f service.yaml</code></li>
</ul>
<pre><code class="language-bash">&gt; kubectl apply -f deployment.yaml
&gt; deployment.apps/my-web created

&gt; kubectl apply -f service.yaml
&gt; service/my-web-service configured</code></pre>
<br>

<h3 id="3-서비스-접근">(3). 서비스 접근</h3>
<p><code>http://&lt;Minikube IP&gt;:&lt;NodePort&gt;</code> url로 접근 가능하게 된다</p>
<ul>
<li>pod 상태 확인 : <code>kubectl get pods</code></li>
</ul>
<pre><code class="language-bash"># Pod 상태 확인. Running으로 표시되어야 정상 실행중인 것
&gt; kubectl get pods
&gt;
NAME                        READY   STATUS    RESTARTS   AGE
my-web-78f96cb845-8fhlw     1/1     Running   0          12s

# 미니쿠베 ip 확인
&gt; minikube ip
&gt; 192.168.49.2

# service 확인. 접근 가능 url 출력
&gt; minikube service my-web-service
&gt;
|-----------|------------------|-------------|---------------------------|
| NAMESPACE |       NAME       | TARGET PORT |            URL            |
|-----------|------------------|-------------|---------------------------|
| default   |  my-web-service  |        8080 | http://192.168.49.2:30001 |
|-----------|------------------|-------------|---------------------------|
🏃  my-web-service 서비스의 터널을 시작하는 중
|-----------|------------------|-------------|------------------------|
| NAMESPACE |       NAME       | TARGET PORT |          URL           |
|-----------|------------------|-------------|------------------------|
| default   |  my-web-service  |             | http://127.0.0.1:64487 |
|-----------|------------------|-------------|------------------------|
🎉  Opening service default/my-web-service in default browser...
❗  darwin 에서 Docker 드라이버를 사용하고 있기 때문에, 터미널을 열어야 실행할 수 있습니다</code></pre>
<br>

<h3 id="4-그-외">(4). 그 외</h3>
<ul>
<li>deployment 확인 : <code>kubectl get deployment</code></li>
<li>pod 로그 확인 : <code>kubectl describe pod &lt;POD_NAME&gt;</code></li>
<li>서비스 삭제 : <code>kubectl delete service city-web-service</code></li>
<li>쿠버네티스 모든 리소스 삭제 : <code>kubectl delete all --all</code></li>
</ul>
<pre><code class="language-bash"># deployment 확인
&gt; kubectl get deployment
&gt;
NAME       READY   UP-TO-DATE   AVAILABLE   AGE
my-web     1/1     1            1           15m

# pod 로그 확인
&gt; kubectl describe pod my-web-57bc889df4-lh2dh
&gt;
Name:             my-web-57bc889df4-lh2dh
Namespace:        default
Priority:         0
Service Account:  default
Node:             minikube/192.168.49.2
Start Time:       Mon, 30 Dec 2024 10:57:12 +0900
Labels:           app=my-web
                  pod-template-hash=57bc889df4
Annotations:      &lt;none&gt;
Status:           Pending
IP:               10.244.0.7
IPs:
  IP:           10.244.0.7
Controlled By:  ReplicaSet/my-web-57bc889df4
Containers:
  my-web:
    Container ID:   
    Image:          my-web:1.0
    Image ID:       
    Port:           3000/TCP
    Host Port:      0/TCP
    State:          Waiting
      Reason:       ImagePullBackOff
    Ready:          False
    Restart Count:  0
    Environment:    &lt;none&gt;
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-wlpqw (ro)
.
.
.
Events:
  Type     Reason     Age                  From               Message
  ----     ------     ----                 ----               -------
  Normal   Scheduled  2m20s                default-scheduler  Successfully assigned default/my-web-57bc889df4-lh2dh to minikube
  Normal   Pulling    41s (x4 over 2m20s)  kubelet            Pulling image &quot;my-web:1.0&quot;
  Warning  Failed     39s (x4 over 2m17s)  kubelet            Failed to pull image &quot;my-web:1.0&quot;: Error response from daemon: pull access denied for my-web, repository does not exist or may require &#39;docker login&#39;: denied: requested access to the resource is denied
  Warning  Failed     39s (x4 over 2m17s)  kubelet            Error: ErrImagePull
  Warning  Failed     25s (x6 over 2m17s)  kubelet            Error: ImagePullBackOff
  Normal   BackOff    13s (x7 over 2m17s)  kubelet            Back-off pulling image &quot;my-web:1.0&quot;</code></pre>
<br>

<h2 id="2-4-port-forwarding">2-4. Port Forwarding</h2>
<p><code>Port Forwarding</code>은 로컬 머신에서 쿠버네티스 Pod으로 트래픽을 포워딩하는 방법이다. 외부에서 서비스에 바로 접근할 수 없고 개발 환경에 사용한다.</p>
<table>
<thead>
<tr>
<th><strong>항목</strong></th>
<th><strong>NodePort</strong></th>
<th><strong>Port Forwarding</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>접속 범위</strong></td>
<td>외부에서 접근 가능</td>
<td>로컬 머신에서만 접속 가능</td>
</tr>
<tr>
<td><strong>설정 위치</strong></td>
<td>service.yaml 파일에 포트(<code>NodePort</code>) 지정</td>
<td><code>kubectl port-forward</code> 명령어로 설정</td>
</tr>
<tr>
<td><strong>용도</strong></td>
<td>프로덕션 환경, 다른 시스템에서 접근할 때 사용</td>
<td>개발 및 테스트 환경에서 임시로 연결할 때 사용</td>
</tr>
</tbody></table>
<br>

<h3 id="1-port-forwarding-설정">(1). Port Forwarding 설정</h3>
<ul>
<li>포트포워딩 :<code>kubectl port-forward pod/&lt;POD_NAME&gt; &lt;LOCAL_PORT&gt;:&lt;POD_PORT&gt;</code></li>
</ul>
<pre><code class="language-bash">&gt; kubectl port-forward pod/my-web-78f96cb845-8fhlw 8080:3000
&gt;
Forwarding from 127.0.0.1:8080 -&gt; 3000
Forwarding from [::1]:8080 -&gt; 3000
Handling connection for 8080
Handling connection for 8080
Handling connection for 8080
...</code></pre>
<br>

<h3 id="2-서비스-접근">(2). 서비스 접근</h3>
<p><code>http://localhost:&lt;LOCAL_PORT&gt;</code> 로 접근 가능하다.</p>
<br>

<h2 id="2-5-volume">2-5. Volume</h2>
<p><code>볼륨</code> : pod 내의 컨테이너에서 데이터를 저장하거나 공유할 수 있도록 하는 스토리지</p>
<p>컨테이너는 일시적인 파일 시스템을 사용하므로, 컨테이너가 재시작되거나 삭제되면 데이터가 사라진다. 이러한 문제를 해결하기 위해 볼륨을 사용한다.</p>
<blockquote>
<p>볼륨 특징</p>
</blockquote>
<ul>
<li>pod 내의 여러 컨테이너에서 동시에 접근할 수 있다. 이를 통해 컨테이너 간 데이터 공유가 가능하다.</li>
<li>볼륨은 pod의 라이프사이클 동안 유지가 된다. 데이터 영속성을 제공한다.</li>
</ul>
<p>볼륨 주요 타입 </p>
<ul>
<li><p><strong>EmptyDir</strong>
  pod이 실행되는 동안에만 존재하는 임시 볼륨이다. 여러 컨테이너가 데이터를 공유해야하는 경우에 사용한다. pod이 삭제되면 데이터도 함께 삭제된다.</p>
<pre><code class="language-yaml">  volumes:
  - name: my-empty-dir
    emptyDir: {}</code></pre>
</li>
<li><p><strong>hostPath</strong>
  노드의 특정 디렉토리를 pod에 마운트한다. 로컬 파일 시스템 접근이 필요한 경우에 사용한다. 호스트 의존적이므로 클러스터 간 이식성이 떨어진다.</p>
<pre><code class="language-yaml">  volumes:
  - name: my-host-path
    hostPath:
      path: /path/on/host
      type: Directory</code></pre>
</li>
<li><p><strong>persistentVolumeClaim (PVC)</strong>
  PV와 연동하여 스토리지를 동적으로 할당받는다. 클라우드 스토리지나 네트워크 파일 시스템 사용 시에 사용한다. 데이터가 pod의 라이프사이클과 독립적으로 유지된다.</p>
<pre><code class="language-yaml">  volumes:
  - name: my-pvc
    persistentVolumeClaim:
      claimName: my-pvc</code></pre>
</li>
</ul>
<br>

<h3 id="1-로컬-디렉토리-생성">(1). 로컬 디렉토리 생성</h3>
<p><code>mkdir -p /Users/unknown/k8sData</code></p>
<br>

<h3 id="2-컨테이너-내-디렉토리-탐색">(2). 컨테이너 내 디렉토리 탐색</h3>
<pre><code class="language-bash"># 실행 중인 컨테이너 확인
&gt; docker ps
&gt;
CONTAINER ID   IMAGE                                 COMMAND                   CREATED      STATUS        PORTS                                                                                                                                  NAMES
73b86b6b2209   gcr.io/k8s-minikube/kicbase:v0.0.45   &quot;/usr/local/bin/entr…&quot;   3 days ago   Up 24 hours   127.0.0.1:55674-&gt;22/tcp, 127.0.0.1:55675-&gt;2376/tcp, 127.0.0.1:55677-&gt;5000/tcp, 127.0.0.1:55678-&gt;8443/tcp, 127.0.0.1:55676-&gt;32443/tcp   minikube

# 컨테이너 쉘 실행
&gt; docker exec -it 73b86b6b2209 /bin/bash

# 내부 경로 확인
&gt; ls /
CHANGELOG  Release.key  bin  boot  data  dev  docker.key  etc  home  kic.txt  kind  lib  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var  version.json
&gt; ls /mnt
&gt; data</code></pre>
<br>

<h3 id="3-로컬-디렉토리를-vm에-마운트">(3). 로컬 디렉토리를 VM에 마운트</h3>
<p>로컬의 <code>/Users/unknown/k8sData</code> 디렉토리를 컨테이너 <code>/mnt/data</code>로 마운트</p>
<pre><code class="language-yaml">&gt; minikube mount /Users/unknown/k8sData:/mnt/data
&gt;
📁  Mounting host path /Users/unknown/k8sData into VM as /mnt/data ...
    ▪ Mount type:   9p
    ▪ User ID:      docker
    ▪ Group ID:     docker
    ▪ 버전:      9p2000.L
    ▪ 메시지 사이즈: 262144
    ▪ 옵션:      map[]
    ▪ 연결된 주소: 127.0.0.1:51567
🚀  Userspace file server: ufs starting
✅  Successfully mounted /Users/unknown/k8sData to /mnt/data</code></pre>
<br>

<h3 id="4-deployment-파일-수정">(4). deployment 파일 수정</h3>
<p>hostPath 볼륨을 사용하여 /Users/unknown/k8sData 로컬 디렉토리를 컨테이너 내 /app/data 경로에 마운트</p>
<pre><code class="language-yaml">    containers:
          volumeMounts:
            - mountPath: /mnt/data  # 컨테이너 내 경로
              name: my-web-volume
      volumes:
        - name: my-web-volume
          hostPath:
            path: /mnt/data  # Minikube VM에서 마운트된 경로
            type: Directory</code></pre>
<br>

<h3 id="5-deployment-적용">(5). deployment 적용</h3>
<p>수정한 deployment.yaml 파일을 쿠버네티스 클러스터에 적용하여 pod을 배포한다.</p>
<p>명령어 실행 시 쿠버네티스 클러스터 내에 pod이 생성되고, <code>hostPath</code> 볼륨이 설정되어 로컬 PC의 <code>/Users/unknown/k8sData</code> 디렉토리와 컨테이너의 <code>/app/data</code> 경로가 연결된다.</p>
<pre><code class="language-yaml">&gt; kubectl apply -f deployment.yaml
&gt; deployment.apps/my-web configured</code></pre>
<br>

<h2 id="2-6-replicas-적용">2-6. replicas 적용</h2>
<br>

<h3 id="1-deployment-수정">(1). deployment 수정</h3>
<pre><code class="language-yaml">    # deployment.yaml

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: my-web
    spec:
      replicas: 2  # replicas 2
      selector:
        matchLabels:
          app: my-web
      template:
        metadata:
          labels:
            app: my-web
        spec:
          containers:
            - name: my-web
              image: my-web:1.0
              imagePullPolicy: Never
              ports:
                - containerPort: 3000
              volumeMounts:
                - mountPath: /mnt/data 
                  name: my-web-volume
          volumes:
            - name: my-web-volume
              hostPath:
                path: /mnt/data 
                type: Directory</code></pre>
<br>

<h3 id="2-수정한-deployment-적용">(2). 수정한 deployment 적용</h3>
<pre><code class="language-bash">&gt; kubectl apply -f deployment.yaml
&gt; deployment.apps/my-web configured</code></pre>
<br>

<h3 id="3-pod이-2개로-유지-실행되는지-확인">(3). pod이 2개로 유지 실행되는지 확인</h3>
<pre><code class="language-bash"># pod이 2개 실행되고 있는지 확인 (status: Running이어야 정상 실행중)
&gt; kubectl get pods
&gt;                
NAME                        READY   STATUS    RESTARTS   AGE
my-web-54677cd5f6-ffftd     1/1     Running   0          8s
my-web-54677cd5f6-ht6fm     1/1     Running   0          8s

# pod 하나 죽여보기
&gt; kubectl delete pod my-web-54677cd5f6-ffftd
&gt; pod &quot;my-web-54677cd5f6-ffftd&quot; deleted

# 새로운 pod이 자동으로 생성되는지 확인
&gt; kubectl get pods
&gt;
NAME                        READY   STATUS    RESTARTS   AGE
my-web-54677cd5f6-bchbh     1/1     Running   0          4s
my-web-54677cd5f6-ht6fm     1/1     Running   0          5m37s</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[인프라 입문기 - docker]]></title>
            <link>https://velog.io/@kim_unknown_/start-infra-docker</link>
            <guid>https://velog.io/@kim_unknown_/start-infra-docker</guid>
            <pubDate>Fri, 03 Jan 2025 01:49:12 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>글을 시작하기에 앞서,
나는 3년차 프론트엔드 개발자로, 그 동안 회사에서 프론트 개발 위주의 업무만 했었다.
당장 프론트만 해도 기술 트렌드가 금방금방 바뀌다보니 그거 따라잡기 바빴었다. (react 했다가 vue 했다가.. 다시 react+next하고 난리법석)
그러다가 이번에 이직을 하게 되었는데, 주니어로만 구성되어있던 전 직장과 달리 감사하게도 나 빼고 다 시니어 개발자분들로 개발팀이 구성되어 있었고 이번에 주니어로 내가 합류하게 되면서 많은 가르침을 받고 있다. 
프론트엔드 개발자도 인프라에 대해 어느정도 알아두면 좋다며 docker, kubernetes, aws 등등 공부해보자 하셨고, 프로젝트가 잠시 여유있는 기간 동안 인프라에 대해 공부해보게 되었다. (즉 이 글은 인프라에 매우 무지한 프론트 개발자가 찍먹한 인프라를 기록용으로 쓰는 글이라는 점...ㅎㅎ)</p>
</blockquote>
<br/>

<h2 id="1-컨테이너와-도커-쿠버네티스-개념-정리">1. 컨테이너와 도커, 쿠버네티스 개념 정리</h2>
<p><code>컨테이너</code>는 실행 환경까지 포함하여 독립적으로 프로그램을 실행할 수 있도록 도와주는 기술이다. 컨테이너 환경을 묶어서 배포한 컨테이너 이미지를 내려받아 구동하면 각종 설정 과정을 줄일 수 있다. 이러한 컨테이너를 사용할 때 필요한 도구가 <code>컨테이너 런타임</code>이며, 그 중 유명한 것이 <code>도커</code>이다.</p>
<blockquote>
<p>*용어 정리
<code>컨테이너</code> : 앱이 구동되는 환경까지 감싸서 실행할 수 있도록 하는 격리 기술
<code>도커</code> : 컨테이너를 다루는 도구 (컨테이너 런타임)
<code>쿠버네티스</code> : 컨테이너 런타임을 통해 컨테이너를 오케스트레이션하는 도구
<code>오케스트레이션</code> : 여러 서버에 걸친 컨테이너 및 사용하는 환경 설정을 관리하는 행위</p>
</blockquote>
<br/>

<h2 id="2-docker-실습">2. Docker 실습</h2>
<p><code>Docker</code>는 컨테이너를 사용해 애플리케이션을 실행하기 위한 플랫폼이다. 컨테이너는 애플리케이션과 필요한 모든 라이브러리, 의존성을 하나의 패키지로 묶어 운영체제에 독립적인 환경을 제공한다. 즉, <strong>어떤 환경에서든 프로그램이 동일하게 운영되게</strong> 도와준다.</p>
<p><code>Docker Image</code>는 컨테이너를 실행하기 위한 불변의 템플릿이다. 애플리케이션 코드, 라이브러리, 환경설정 파일 등을 하나의 이미지로 저장하는 것이다. </p>
<br>

<h3 id="2-1-dockerfile-작성">2-1. Dockerfile 작성</h3>
<p>프로젝트 루트 위치에 Dockerfile 생성</p>
<pre><code class="language-js">&gt; Dockerfile

# 베이스 이미지 선택
FROM node:22

# 작업 디렉토리 설정
WORKDIR /app

# 패키지 파일 복사
COPY package*.json ./

# 의존성 설치
RUN npm install

# 애플리케이션 코드 복사
COPY . .

# 애플리케이션 실행
CMD [&quot;npm&quot;, &quot;start&quot;]

# 컨테이너가 사용할 포트
EXPOSE 3000</code></pre>
<h4 id="2-1-1-docker-composeyml-작성">2-1-1. docker-compose.yml 작성</h4>
<pre><code class="language-yaml">services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - &quot;8080:3000&quot;
    volumes:
      - ./app:/app
    environment:
      NODE_ENV: production</code></pre>
<br>

<h3 id="2-2-도커-이미지-빌드">2-2. 도커 이미지 빌드</h3>
<p><code>docker build -t &lt;이미지_이름&gt;:&lt;태그&gt; &lt;경로&gt;</code></p>
<pre><code class="language-bash"> &gt; docker build -t my-web:1.0 .</code></pre>
<p><code>my-web</code> : 이름으로 이미지 태깅
<code>1.0</code> : 태그 정보
<code>.</code> : 현재 디렉토리를 빌드 컨텍스트로 사용</p>
<h4 id="2-2-1-docker-compose로-이미지-빌드">2-2-1. docker-compose로 이미지 빌드</h4>
<p><code>docker-compose build</code></p>
<br>

<h3 id="2-3-빌드된-이미지-리스트-확인">2-3. 빌드된 이미지 리스트 확인</h3>
<p><code>docker images</code></p>
<pre><code class="language-bash">&gt; docker images

&gt; 결과물
&gt; REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
&gt; my-web     1.0       2d22502f7d55   30 seconds ago   3.21GB</code></pre>
<p><code>REPOSITORY</code>: 이미지 이름
<code>TAG</code>: 이미지 태그
<code>IMAGE ID</code>: 이미지의 고유 ID
<code>SIZE</code>: 이미지 크기</p>
<br>

<h3 id="2-4-도커-컨테이너-실행">2-4. 도커 컨테이너 실행</h3>
<p><code>docker run -p &lt;호스트_포트&gt;:&lt;컨테이너_포트&gt; &lt;이미지_이름&gt;:&lt;태그&gt;</code></p>
<pre><code class="language-bash">&gt; docker run -p 8080:3000 my-web:1.0</code></pre>
<p><code>p 8080:3000</code>: 호스트의 8080번 포트를 컨테이너의 3000번 포트와 연결
<code>my-web:1.0</code>: 사용할 이미지 이름과 태그</p>
<blockquote>
<p><strong>동작 방식</strong>
∙호스트 포트 : 도커 컨테이너 외부에서 접근 시 사용되는 포트
∙컨테이너 포트 : 도커 컨테이너 내에서 실행되는 포트 <br/>
<em>호스트 포트를 통해 브라우저나 다른 클라이언트가 컨테이너 내 프로젝트에 접근한다. → 컨테이너는 내부적으로 3000번 포트에서 프로젝트를 실행하고 있으며, 외부에서 직접 접근 불가능하다. → 8080번 포트로 접근 시 컨테이너의 300번 포트로 요청이 전달된다.</em></p>
</blockquote>
<br>

<h3 id="2-5-도커-컨테이너-로그-확인">2-5. 도커 컨테이너 로그 확인</h3>
<p><img src="https://velog.velcdn.com/images/kim_unknown_/post/eb02fc47-d3e0-47d4-888f-cd83b5ef39ca/image.png" alt=""></p>
<br>

<h3 id="2-6-도커-볼륨">2-6. 도커 볼륨</h3>
<h4 id="2-6-1-볼륨-생성하기">2-6-1. 볼륨 생성하기</h4>
<p><code>docker volume create &lt;볼륨 이름&gt;</code></p>
<pre><code class="language-bash">&gt; docker volume create volumeTest</code></pre>
<h4 id="2-6-2-생성된-볼륨-확인하기">2-6-2. 생성된 볼륨 확인하기</h4>
<pre><code class="language-bash">// 볼륨 리스트 확인
&gt; docker volume ls
&gt; DRIVER    VOLUME NAME
&gt; local     volumeTest

// 볼륨 정보 확인
&gt; docker volume inspect volumeTest
&gt; 
[
    {
        &quot;CreatedAt&quot;: &quot;2024-12-26T04:18:25Z&quot;,
        &quot;Driver&quot;: &quot;local&quot;,
        &quot;Mountpoint&quot;: &quot;/var/lib/docker/volumes/volumeTest/_data&quot;,
        &quot;Name&quot;: &quot;volumeTest&quot;,
        &quot;Options&quot;: null,
        &quot;Scope&quot;: &quot;local&quot;
    }
]</code></pre>
<h4 id="2-6-3-컨테이너와-연결">2-6-3. 컨테이너와 연결</h4>
<pre><code class="language-bash">&gt; docker run -p 8080:3000 -v /Users/joy/log:/app/logs my-web:1.0</code></pre>
<p>→ 확인</p>
<pre><code class="language-bash">// 실행 중인 컨테이너 확인
&gt; docker ps
&gt; CONTAINER ID   IMAGE          COMMAND                   CREATED         STATUS         PORTS                    NAMES
&gt; 7cbce098572b   my-web:1.0   &quot;docker-entrypoint.s…&quot;   6 minutes ago   Up 6 minutes   0.0.0.0:8080-&gt;3000/tcp   gifted_mclaren

&gt; docker exec -it 7cbce098572b sh // 컨테이너 접속
&gt; cd /app
&gt; ls -l // 파일 리스트 확인
-rw-r--r--  1 root root    365 Dec 26 05:23 Dockerfile
-rw-r--r--  1 root root   1253 Dec 26 00:45 README.md
-rw-r--r--  1 root root    238 Dec 26 05:15 app.log
-rw-r--r--  1 root root    393 Dec 24 06:12 eslint.config.mjs
drwxr-xr-x  2 root root   4096 Dec 26 05:33 logs
-rw-r--r--  1 root root    228 Dec 24 06:17 next-env.d.ts
-rw-r--r--  1 root root    744 Dec 26 02:15 next.config.mjs
drwxr-xr-x  1 root root  16384 Dec 26 05:32 node_modules
-rw-r--r--  1 root root 298276 Dec 26 00:13 package-lock.json
-rw-r--r--  1 root root    807 Dec 26 00:13 package.json
-rw-r--r--  1 root root    135 Dec 24 06:12 postcss.config.mjs
drwxr-xr-x  2 root root   4096 Dec 24 06:12 public
drwxr-xr-x 12 root root   4096 Dec 24 06:22 src
-rw-r--r--  1 root root   4328 Dec 26 00:37 tailwind.config.ts
-rw-r--r--  1 root root    602 Dec 24 06:12 tsconfig.json
&gt; exit

// 로그 파일 생성 확인
&gt; ls /Users/joy/log</code></pre>
<br>

<h3 id="2-7-도커-이미지-업로드-docker-hub--aws">2-7. 도커 이미지 업로드 (docker hub / aws)</h3>
<h4 id="2-7-1-dockerhub에-이미지-업로드하기">2-7-1. dockerhub에 이미지 업로드하기</h4>
<p>(1). 도커 로그인</p>
<pre><code class="language-bash">&gt; docker login
&gt; Authenticating with existing credentials...
&gt; Login Succeeded</code></pre>
<p>(2). 이미지 태그 지정</p>
<p><code>docker tag &lt;로컬_이미지&gt; &lt;dockerhub_사용자명&gt;/&lt;리포지토리_이름&gt;:&lt;태그&gt;</code></p>
<pre><code class="language-bash">&gt; docker tag my-web:1.0 joy/my-web:1.0</code></pre>
<p>(3). 이미지 푸시</p>
<p><code>docker push &lt;dockerhub_사용자명&gt;/&lt;리포지토리_이름&gt;:&lt;태그&gt;</code></p>
<pre><code class="language-bash"> &gt; docker push joy/my-web:1.0

 &gt;
 The push refers to repository [docker.io/joy/my-web]
 ...
 92b12b0dccf2: Pushed 
 fd63102cac36: Pushed 
 1.0: digest: sha256:209aff250058014a9c073d2a311ee9644ca2d98221105d6f741aa3a7b712b0e3 size: 856</code></pre>
<p>(4). dockerhub 업로드 확인</p>
<p><img src="https://velog.velcdn.com/images/kim_unknown_/post/1eccbaba-c2f9-499a-b709-589f32c46ffa/image.png" alt=""></p>
<h4 id="2-7-2-aws-erc-에-이미지-업로드하기">2-7-2. aws erc 에 이미지 업로드하기</h4>
<p>(1). aws configure 설정</p>
<pre><code class="language-bash">&gt; aws configure
&gt; AWS Access Key ID [None]:
&gt; AWS Secret Access Key [None]:
&gt; Default region name [None]: ap-northeast-2
&gt; Default output format [None]: 

// 작업 증명 활성화 홧인
&gt; aws sts get-caller-identity
&gt; 
{
    &quot;UserId&quot;: &quot;&quot;,
    &quot;Account&quot;: &quot;&quot;,
    &quot;Arn&quot;: &quot;&quot;
}
</code></pre>
<p>(2). ecr 레포지토리 생성</p>
<p>(3). aws ecr 로그인</p>
<p><code>aws ecr create-repository --repository-name &lt;리포지토리_이름&gt; --region &lt;리전_이름&gt;</code></p>
<pre><code class="language-bash">&gt; aws ecr get-login-password --region ap-southeast-2 | docker login --username ~~~             
&gt; Login Succeeded</code></pre>
<p>(4). 이미지 태그 변경</p>
<p><code>docker tag &lt;로컬_이미지_이름&gt;:&lt;태그&gt; &lt;AWS_Account_ID&gt;.dkr.ecr.&lt;리전_이름&gt;.amazonaws.com/&lt;리포지토리_이름&gt;:&lt;태그&gt;</code></p>
<pre><code class="language-bash">&gt; docker tag my-web:1.0 ~~~</code></pre>
<p>(5). 이미지 푸시</p>
<p><code>docker push &lt;AWS_Account_ID&gt;.dkr.ecr.&lt;리전_이름&gt;.amazonaws.com/&lt;리포지토리_이름&gt;:&lt;태그&gt;</code></p>
<pre><code class="language-bash">&gt; docker push ~~~

&gt; The push refers to repository ~~~
...
3981e863d195: Pushed 
92b12b0dccf2: Pushed 
ff27b22d4715: Pushed 
7bd0f29b7dc7: Pushed 
1.0: digest: sha256:209aff250058014a9c073d2a311ee9644ca2d98221105d6f741aa3a7b712b0e3 size: 856</code></pre>
<p>(6). 이미지 업로드 확인</p>
<p><img src="https://velog.velcdn.com/images/kim_unknown_/post/8e142738-41a4-4bde-ac3a-121c391375cc/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JS] pull to refresh 구현시 ios  scroll bounce 비활성화 시키기]]></title>
            <link>https://velog.io/@kim_unknown_/ios-scroll-bounce</link>
            <guid>https://velog.io/@kim_unknown_/ios-scroll-bounce</guid>
            <pubDate>Sat, 07 Sep 2024 09:40:18 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>pull to refresh를 구현하다가 막혔던 ios 이슈를 해결한 방법을 기록해보려고 한다. 현업에서 일하다 보면 내 블로그를 내가 참고하게 되는 일이 참 많다. 과거의 나에게 또 물을 수 있도록 미루던 블로깅을 이제서야 한다.🥲</p>
</blockquote>
<p>ios에서는 기본적으로 <code>scroll bounce</code> 이라는 효과를 제공한다. 이는 페이지 최상/하단에 도달했을 때 페이지가 고무줄처럼 늘어나는 효과인데, 스크롤이 관성적으로 작용한다.
좋은 UX이긴 하지만, 프론트엔드 개발자로서 다양한 이벤트들을 적용하다보면 골칫거리가 되기도 한다...ㅎㅎ</p>
<p>pull to refresh UI를 구현하다가 scroll bounce와 스크롤 이벤트가 충돌하여 애를 먹은 적이 있었다. (왜 항상 aos에서는 정상 작동하는데, ios 너만...)</p>
<br>

<p>서론이 길었지만, 스크롤 바운스 막는 방법은 다음과 같다.</p>
<ul>
<li>html, body 태그 <code>overflow: hidden;</code></li>
<li>body 태그 <code>position: fixed; inset: 0</code></li>
</ul>
<br>

<pre><code class="language-js">// pulling 중일 때 ios 스크롤 바운스 비활성화 처리
isPulling(n) {
  if (n) {
    // ios 스크롤 바운스 방지
    document.documentElement.style.overflow = &#39;hidden&#39;;
    document.body.style.overflow = &#39;hidden&#39;;
    document.body.style.position = &#39;fixed&#39;;
    document.body.style.inset = &#39;0px&#39;;
  } else {
    // ios 스크롤 바운스 방지 해제
    document.documentElement.style.overflow = &#39;auto&#39;;
    document.body.style.overflow = &#39;auto&#39;;
    document.body.style.position = &#39;&#39;;
    document.body.style.inset = &#39;&#39;;
  }
}</code></pre>
<p><em>참고로 나는 pull to refresh 시에만 스크롤 바운스를 비활성화 시키고 싶었기에 pulling 이벤트가 진행 중일 땐 해당 css를 적용시켰다가 pulling이 끝나면 css를 초기화 시켜 비활성화를 해지시켰다.</em></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Next.js] 뿌시기]]></title>
            <link>https://velog.io/@kim_unknown_/Next.js-break-1</link>
            <guid>https://velog.io/@kim_unknown_/Next.js-break-1</guid>
            <pubDate>Tue, 23 Jul 2024 16:08:23 GMT</pubDate>
            <description><![CDATA[<h2 id="1-nextjs란">1. Next.js란?</h2>
<p>Next.js는 리액트 기반의 서버사이드 렌더링 프레임워크이다.
React는  클라이언트 사이드에서만 작동한다는 특징이 있는데, 이로 인해 SEO 효과가 저조했으며 초기 렌더링시 성능 부담이 크다는 문제점이 존재했다. 이러한 문제점을 해결하기 위해 Next.js가 도입되기 시작했다.</p>
<h3 id="📑-next13-이후에-변경된-점">📑 Next13 이후에 변경된 점</h3>
<p><code>app router</code>가 도입되면서 <code>page router</code> 와 <code>app router</code> 두 가지 방식으로 나뉘게 되었다.</p>
<ul>
<li>app router : <code>app/</code> 디렉토리로 라우팅을 처리하는 방식</li>
<li>page router : <code>pages/</code> 디렉토리로 라우팅을 처리하는 방식</li>
</ul>
<p>두 라우팅 방식의 차이점은 서버 컴포넌트를 사용하는지에 대한 유무이다.</p>
<blockquote>
<p>*서버 컴포넌트 : 넥스트 서버에서 리액트를 미리 렌더링해서 프론트 또는 브라우저 또는 클라이언트로 데이터를 보내줄 때 완성된 HTML을 미리 보내주는 것<br>
프론트 입장에서 HTML 로딩 시간 감소, HTML과 JS 용량 감소 등의 이점이 생겼다.<br>
그러나, 서버 컴포넌트는 유저의 브라우저에서 진행하던 일을 넥스트 서버에서 다 진행하기 때문에 서버의 부담이 증가한다. 
이 문제를 원활하게 해결하고자 서버에서 캐시를 적극적으로 활용했다.</p>
</blockquote>
<br>

<h2 id="2-nextjs-프로젝트-생성하기">2. Next.js 프로젝트 생성하기</h2>
<p><code>npx create-next-app 프로젝트명</code> </p>
<p><em>*리액트 프로젝트 생성 명령어와 매우 유사하다 (<code>npx create-react-app</code>)</em></p>
<br>

<h2 id="3-next-폴더-구조">3. Next 폴더 구조</h2>
<ul>
<li><strong>public</strong> : 퍼블릭 폴더 내부에 위치한 것들은 넥스트 서버에서 누구나 접근할 수 있게 서빙 해준다. 때문에 모든 사람이 접근해도 무방한 이미지와 같은 파일들이 내부에 위치하게 된다.</li>
<li><strong>src</strong> : 보통은 루트 아래에 src, app이 별도로 존재하는 구조이지만, src 내에 app 폴더를 두게 되면 타입스크립트를 한번에 처리하기에 용이하다.</li>
<li><strong>app</strong> : 라우터와 관련된 파일들이 위치한다.</li>
<li><strong>next.config.js</strong> : 넥스트에 대한 설정 파일</li>
<li><strong>ts-config.json</strong> : 타입스크립트에 대한 설정 파일</li>
</ul>
<br>

<h2 id="4-root-layout">4. Root Layout</h2>
<p><code>app router</code>는 app 디렉토리 내에 root layout을 필수로 포함해야 한다.
<code>Root Layout</code>은 모든 페이지에 공통적으로 적용되는 레이아웃으로, 필수 레이아웃이다. Root Layout에는 html 태그와 body 태그가 포함되어야 한다.</p>
<blockquote>
<p>만일, 라우터별로 레이아웃을 따로 만들고 싶다면 해당 디렉토리 내에 <code>layout.tsx</code> 파일을 별도로 생성하면 된다.
*RootLayout &gt; HomeLayout &gt; HomePage</p>
</blockquote>
<pre><code class="language-ts">export default function RootLayout({ 
  children,
}: {
  children: React.ReactNode
}) {
  return (
    &lt;html lang=&quot;en&quot;&gt;
      &lt;body&gt;{children}&lt;/body&gt;
    &lt;/html&gt;
  )
}</code></pre>
<br>

<h2 id="5-dynamic-routes">5. [Dynamic Routes]</h2>
<p><code>Dynamic Routes</code>는 동적 데이터(변수)로 라우터를 설정할 때 사용한다. 디렉토리명을 <code>대괄호[]</code> 로 묶어서 생성하면 해당 경로는 동적 데이터를 사용할 수 있다.</p>
<p>예를 들어, <code>https://velog.io/@kim_unknown_</code> 과 같이 경로에 유저의 id가 들어가야 한다면, <code>[userId]</code> 와 같이 디렉토리를 생성한다.</p>
<br>

<h2 id="6-route-groups">6. (Route Groups)</h2>
<p><code>Route Groups</code>는 라우터에 영향을 주지 않으면서 라우터를 그룹화할 때 사용한다. 디렉토리명을 <code>소괄호()</code>로 묶어서 생성하면 내부에 있는 디렉토리들을 그룹화할 수 있다.</p>
<p>예를 들어, 아래와 같이 특정 서비스의 로그인 전 단계와 로그인 후 단계의 경로를 구분하여 그룹화할 경우, (beforeLogin) 과 (afterLogin)은 경로가 되지 않는다. 단순 묶음 폴더의 역할을 하는 것 뿐이다.
따라서 실제 브라우저 주소창에는 <code>/login</code> , <code>/join</code> , <code>/logout</code> , <code>/leave</code> 로 경로가 보이게 된다.</p>
<pre><code class="language-js">app
    /* 로그인 전 라우터 */
  ⌞(beforeLogin) 
    ⌞login 
    ⌞join
   /* 로그인 후 라우터 */
  ⌞(afterLogin)
    ⌞logout
    ⌞leave</code></pre>
<br>

<h2 id="7-layouttsx--templatetsx">7. layout.tsx / template.tsx</h2>
<ul>
<li><code>Layout</code> : 페이지를 넘나들 때 새로 마운트(리렌더링)가 되지 않는다.</li>
<li><code>Template</code> :  페이지를 넘나들 때마다 새로 마운트(리렌더링)가 된다.</li>
</ul>
<p>때문에 마운트 필요에 따라 적절하게 레이아웃과 템플릿을 구분하여 사용해야 한다. 레이아웃과 템플릿은 공존하면 안된다.</p>
<br>

<h2 id="8-link">8. Link</h2>
<p>Next에서는 <code>&lt;a&gt;</code> 태그 대신 <code>&lt;Link&gt;</code> 컴포넌트를 사용한다.
<code>&lt;a&gt;</code> 태그를 사용하여 경로 이동 시, 화면이 깜빡거리며 페이지가 새로고침 되기 때문에 좀 더 부드러운 경로 이동을 위해 <code>&lt;Link&gt;</code> 컴포넌트를 사용한다.</p>
<pre><code class="language-js">import Link from &quot;next/link&quot;;

&lt;Link href=&quot;/home&quot;&gt;홈&lt;/Link&gt;</code></pre>
<br>

<h2 id="9-image">9. Image</h2>
<p>Next에서는 <code>&lt;img&gt;</code> 태그 대신 <code>&lt;Image&gt;</code> 컴포넌트를 사용한다.
<code>&lt;Image&gt;</code> 컴포넌트를 사용하게 되면 넥스트에서 이미지를 최적화해준다.</p>
<p>Image 컴포넌트에서 제공하는 기능은 다음과 같다.</p>
<ul>
<li>lazy loading</li>
<li>이미지 사이즈 최적화</li>
<li>placeholder 제공</li>
</ul>
<pre><code class="language-js">import Image from &quot;next/image&quot;;
import logo from &#39;../public/logo.png&#39;;

&lt;Image src={logo} alt=&quot;logo&quot; /&gt;</code></pre>
<br>

<h2 id="10-redirect">10. Redirect</h2>
<p><code>redirect</code> 함수는 다른 URL로 리다이렉트 시킬 때 사용한다. </p>
<pre><code class="language-js">import { redirect } from &#39;next/navigation&#39;;

export default function Default() {
  redirect(&#39;/home&#39;);
}</code></pre>
<br>

<h2 id="11-parallel-route">11. @Parallel Route</h2>
<p><code>Parallel Route</code>는 여러 개의 페이지를 동시에 보여줄 때 사용한다. (ex. 뒤에 페이지가 남아있으면서 그 위로 모달을 띄울 때)
Parallel Route를 적용할 때는 디렉토리명 앞에 <code>골뱅이(@)</code>를 명시한다. (ex.<code>@modal</code>)</p>
<p>다만, 같은 계층에 위치한 페이지끼리 병렬적으로 렌더링할 수 있기 때문에 다른 계층 구조의 페이지는 병렬적으로 렌더링할 수 없다.</p>
<br>

<h2 id="12-use-client">12. &quot;use client&quot;</h2>
<ul>
<li><strong>서버 컴포넌트</strong> : 서버에서 도는 컴포넌트, 리액트 Hook 사용 불가</li>
<li><strong>클라이언트 컴포넌트</strong> : 클라이언트단 컴포넌트, 리액트 Hook 사용 가능</li>
</ul>
<p>서버 컴포넌트를 클라이언트 컴포넌트로 전환하기 위해서는 파일 최상단에 <code>&quot;use client&quot;</code> 를 명시한다.</p>
<blockquote>
<p>*서버 컴포넌트는 클라이언트 컴포넌트를 임포트하는 것이 가능하지만, 클라이언트 컴포넌트는 서버 컴포넌트를 임포트는 것이 불가능하다.
*클라이언트 컴포넌트가 서버 컴포넌트를 임포트하면 클라이언트 컴포넌트로 바뀌어버린다.</p>
</blockquote>
<ul>
<li>서버 -&gt; 클라이언트 (O)</li>
<li>클라이언트 -&gt; 서버 (X)</li>
</ul>
<br>

<h2 id="13-defaulttsx">13. default.tsx</h2>
<p><code>default.tsx</code>는 Parallel Route의 기본값이다. </p>
<pre><code class="language-js">export default function Default(){
    return null;
}</code></pre>
<br>

<h2 id="14-intercepting-routes">14. (.)Intercepting Routes</h2>
<p><code>Intercepting Routes</code>는 서로 다른 라우트에서 한 화면을 같이 뜰 수 있게 해준다. Intercepting Routes를 적용할 때는 디렉토리명 앞에 <code>(상대경로..)</code> 를 명시한다.</p>
<ul>
<li><code>(.)</code>을 사용하여 동일 레벨 세그먼트 매칭</li>
<li><code>(..)</code>을 사용하여 1단계 윗 레벨 세그먼트 매칭</li>
<li><code>(..)(..)</code>을 사용하여 2단계 윗 레벨 세그먼트 매칭</li>
<li><code>(...)</code>을 사용하여 app 디렉토리의 루트에 위치한 세그먼트 매칭</li>
</ul>
<p>ex. (.)myPage (인터셉트 라우트) / myPage (일반 라우트)</p>
<blockquote>
<p>*Interception Routes는 클라이언트 단을 통해서 진입할 때 적용되며, 브라우저로 주소창을 입력하여 직접 진입하거나 새로고침을 할 경우에는 일반 라우트로 넘어간다.
따라서, 가로채기가 발생하는 상황과 발생하지 않는 상황에 따라 두 디렉토리가 모두 필요하다</p>
</blockquote>
<br>

<h2 id="15-_private-folder">15. _Private Folder</h2>
<p><code>Private Folder</code>는 폴더 정리용으로 사용되며, 라우터에 영향을 주지 않는다. Private Folder를 적용할 때는 디렉토리명 앞에 <code>언더바(_)</code>를 명시한다.</p>
<blockquote>
<p>라우터에 영향을 안주는 폴더 3가지</p>
</blockquote>
<ul>
<li>(그룹 폴더)</li>
<li>@페러렐 라우트</li>
<li>_프라이빗 폴더</li>
</ul>
<p><img src="https://velog.velcdn.com/images/kim_unknown_/post/ee5b6ba0-59d1-4579-9674-38605800a7ba/image.png" alt="folders"></p>
<br>

<blockquote>
<p>참고자료
🎥 <a href="https://www.inflearn.com/course/next-react-query-sns%EC%84%9C%EB%B9%84%EC%8A%A4/dashboard">Next + React Query로 SNS 서비스 만들기 강의</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JavaScript] History API 제대로 알고 쓰기]]></title>
            <link>https://velog.io/@kim_unknown_/JavaScript-History-API</link>
            <guid>https://velog.io/@kim_unknown_/JavaScript-History-API</guid>
            <pubDate>Wed, 15 May 2024 15:45:08 GMT</pubDate>
            <description><![CDATA[<h2 id="1-history-api란">1. History API란?</h2>
<p>자바스크립트에서<code>History API</code>는 history 전역 객체를 사용하여 브라우저의 세션 히스토리에 대한 접근과 조작 기능을 제공한다. 여기서 세션 히스토리는 페이지 방문 이력을 의미한다. 즉, 세션은 유저가 새 페이지를 열 때마다 쌓이며, 이를 통해 브라우저에서 앞으로 가기, 뒤로가기가 가능하다.</p>
<br>

<table>
<thead>
<tr>
<th align="center">뒤로가기</th>
<th align="center">앞으로가기</th>
<th align="center">세션 length</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><img src="https://velog.velcdn.com/images/kim_unknown_/post/b550ee6e-102a-4455-bfbd-4a8caafb8f83/image.png" alt="뒤로가기"></td>
<td align="center"><img src="https://velog.velcdn.com/images/kim_unknown_/post/e92be00f-d9cf-45cc-9f2c-0dbe545231a1/image.png" alt="앞으로가기"></td>
<td align="center"><img src="https://velog.velcdn.com/images/kim_unknown_/post/8ba5e834-f13e-44fc-8499-05c2d70ad52d/image.png" alt="history length"></td>
</tr>
</tbody></table>
<br>

<h2 id="2-history-api-메소드">2. History API 메소드</h2>
<p>History API의 메소드는 크게 2가지 특성으로 분류해서 설명할 수 있을 것 같다.
페이지 이동을 위한 메소드(<code>back, forward, go</code>)와 세션 히스토리 변경을 위한 메소드(<code>pushState, replaceState</code>)이다.</p>
<h3 id="historyback">history.back()</h3>
<p><code>history.back()</code>은 브라우저 상에서 뒤로가기 버튼을 누른 것과 같은 효과를 낼 수 있다. 즉 하위 state로 이동하며, history의 length는 유지된다.</p>
<h3 id="historyforward">history.forward()</h3>
<p><code>history.forward()</code>는 브라우저 상에서 앞으로가기 버튼을 누른 것과 같은 효과를 낼 수 있다. 상위 state로 이동하며, history의 length는 유지된다.</p>
<h3 id="historygo">history.go()</h3>
<p><code>history.go()</code>는 현재 state를 기준으로 특정 위치의 state로 이동시키는 효과를 낼 수 있다. 파라미터로 양수를 넘기면 앞으로 이동하며, 음수를 넘기면 뒤로 이동한다. 또한 0을 넘기면 해당 페이지를 새로고침한다. 따라서, go()는 back()과 forward()를 모두 대체 가능하다.</p>
<pre><code class="language-js">history.back();      // 뒤로가기
history.forward();   // 앞으로가기

history.go(-1);      // 현재 state 기준 뒤로 1칸 가기 (=history.back())
history.go(0);       // 새로 고침
history.go(1);       // 현재 state 기준 앞으로 1칸 가기 (=history.forward())</code></pre>
<p>*즉, back, forward, go 메소드는 세션 히스토리를 변경시키지 않는다.</p>
<h3 id="historypushstate">history.pushState()</h3>
<p><code>history.pushState(state, title, url)</code></p>
<blockquote>
<ul>
<li>state : 직렬화가 가능한 아무 값을 넣을 수 있으며, <code>history.state</code>로 꺼내어 사용 가능하다.</li>
</ul>
</blockquote>
<ul>
<li>title : 사용하진 않지만 필수로 넣어야하는 값으로, 주로 빈문자열을 넣는다.</li>
<li>url : 새로 히스토리에 쌓을 url 이다.</li>
</ul>
<p>pushState()는 파라미터로 들어온 url을 현재 state의 다음 state로 추가하며, 뒤에 있던 state는 모두 날려버린다. 즉, 파라미터로 들어온 url이 가장 최상위 state가 되는 것이다.</p>
<p><img src="https://velog.velcdn.com/images/kim_unknown_/post/e9f66ffd-56d7-4ec0-9882-a8928e0c84eb/image.png" alt="pushState"></p>
<h3 id="historyreplacestate">history.replaceState()</h3>
<p><code>history.replaceState(state, title, url)</code>
*파라미터는 pushState와 동일</p>
<p>replaceState()는 파라미터로 들어온 url로 현재 페이지의 url을 변경한다. 즉, 새로운 state를 쌓는 것이 아닌 현재 state를 대체하는 것이며 state length의 변화가 없다.</p>
<p><img src="https://velog.velcdn.com/images/kim_unknown_/post/ec714f71-c396-4ef1-812f-5c88fdfd212b/image.png" alt="replaceState"></p>
<h2 id="3-popstate-이벤트">3. popstate 이벤트</h2>
<p>브라우저에서 뒤로가기, 앞으로가기 액션이 일어나면 <code>popstate</code> 이벤트가 발생된다. 이 popstate 이벤트를 활용하여 <a href="https://velog.io/@kim_unknown_/Vue.js-%EC%9B%B9%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EB%92%A4%EB%A1%9C%EA%B0%80%EA%B8%B0%EC%8B%9C-%EB%9D%BC%EC%9A%B0%ED%8C%85-%EB%B3%80%EA%B2%BD-%EC%97%86%EC%9D%B4-%EB%AA%A8%EB%8B%AC%EB%A7%8C-%EB%8B%AB%ED%9E%88%EA%B2%8C-%ED%95%98%EA%B8%B0">뒤로가기 누르면 모달 닫기</a> 등의 적절한 처리가 가능하다.</p>
<pre><code class="language-js">window.addEventListener(&quot;popstate&quot;, (event) =&gt; {
 console.log(event.state)
});</code></pre>
<blockquote>
<p>참고자료
🎥 <a href="https://www.youtube.com/watch?v=xvmqaUGAZpo&amp;t=134s">[10분 테코톡] 참새의 history api</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Vue.js] 웹브라우저 뒤로가기시 라우팅 변경 없이 모달만 닫히게 하기]]></title>
            <link>https://velog.io/@kim_unknown_/Vue.js-%EC%9B%B9%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EB%92%A4%EB%A1%9C%EA%B0%80%EA%B8%B0%EC%8B%9C-%EB%9D%BC%EC%9A%B0%ED%8C%85-%EB%B3%80%EA%B2%BD-%EC%97%86%EC%9D%B4-%EB%AA%A8%EB%8B%AC%EB%A7%8C-%EB%8B%AB%ED%9E%88%EA%B2%8C-%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@kim_unknown_/Vue.js-%EC%9B%B9%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EB%92%A4%EB%A1%9C%EA%B0%80%EA%B8%B0%EC%8B%9C-%EB%9D%BC%EC%9A%B0%ED%8C%85-%EB%B3%80%EA%B2%BD-%EC%97%86%EC%9D%B4-%EB%AA%A8%EB%8B%AC%EB%A7%8C-%EB%8B%AB%ED%9E%88%EA%B2%8C-%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 12 May 2024 18:25:28 GMT</pubDate>
            <description><![CDATA[<p>웹개발을 하다보면 자주 접하게 되는 UI 중 하나가 모달 혹은 팝업이다. 그런데 이런 모달이나 팝업을 띄웠을 때 고려해야 하는 것 중 하나가 바로 뒤로가기 버튼이다.</p>
<p>UX적으로 생각해보았을 때 모달은 페이지 변경이 아닌 레이어 위에 쌓이는 형태이기 때문에 라우팅 변경없이 띄워지고 사라질 때로 라우팅 변경없이 모달 창만 사라지는게 가장 자연스럽다고 생각했다.</p>
<p>또한, 만약 이 모달이 띄워진 상태에서 유저가 브라우저 창의 뒤로가기 버튼을 눌렀을 시에도 라우터가 뒤로가는 게 아닌 모달창이 닫히는 게 자연스러운 사용자 경험을 제공할 것이라 생각했다. 이러한 고민을 바탕으로 내가 어떤 식으로 모달을 관리했는지 공유해보려고 한다.</p>
<blockquote>
</blockquote>
<p>내가 생각한 로직은 다음과 같다.</p>
<ul>
<li>모달을 열 때는 store에 현재 여는 모달을 기록하고 history를 한 번 쌓아준다.</li>
<li>모달을 store에 기록하는 이유는 브라우저 상에서 모달이나 팝업 등이 중첩으로 열리는 경우도 있기 때문에 이를 전역적으로 상태관리를 하기 위함이다.</li>
<li>브라우저 상에서 뒤로가기/닫기 액션이 이루어졌을 때는 store에 담아둔 모달 리스트를 체크하고 띄워져 있는 모달이 있다면 모달만 닫는다. 이 때 쌓아둔 히스토리를 제거한다.</li>
</ul>
<h2 id="1-모달-관리-스토어-생성하기">1. 모달 관리 스토어 생성하기</h2>
<pre><code class="language-js">// modal 관리를 위한 스토어 생성
export default {
  name: &#39;modal&#39;,
  state() {
    return {
      modalList: [] // 쌓인 모달을 담을 배열
    };
  },
  getters: {
    getModalList(state) {
      return state.modalList;
    }
  },
  mutations: {
    // 모달이 띄워질 때마다 modalList에 추가
    ADD_MODAL_LIST(state, value) {
      state.modalList.push(value);
    },
    // 모달이 닫힐시 modalList에서 제거
    REMOVE_MODAL_LIST(state, value) {
      state.modalList.splice(value, 1);
    },
    // modalList 제일 뒤에 있는 요소 제거
    POP_MODAL_LIST(state, value) {
      state.modalList.pop();
    }
  },
  actions: {
    // 현재 닫으려고 하는 모달을 찾아 modalList에서 제거
    // 다만 동일한 모달이 여러 개가 띄워졌을 경우 가장 위에 쌓인 하나만 제거 되도록 함
    removeLastModal({ getters, commit }, modalElement) {
      const modalList = window.$nuxt.$store.getters[&#39;router/getModalList&#39;];
      const findIndex = modalList.indexOf(modalElement);
      if (findIndex &gt; -1) {
        window.$nuxt.$store.commit(&#39;router/REMOVE_MODAL_LIST&#39;, findIndex);
      }
    }
  }
};
</code></pre>
<br>

<h2 id="2-모달-open-close-공용함수-만들기">2. 모달 open, close 공용함수 만들기</h2>
<pre><code class="language-js">computed: {
  ...mapGetters({
    modalList: &#39;modal/getModalList&#39;
  })
},
methods: {
  ...mapMutations({
    ADD_MODAL_LIST: &#39;modal/ADD_MODAL_LIST&#39;,
    POP_MODAL_LIST: &#39;modal/POP_MODAL_LIST&#39;
  }),
  ...mapActions({
    removeLastModal: &#39;modal/removeLastModal&#39;
  })
  ...
}</code></pre>
<pre><code class="language-js">// 모달, 팝업 띄우기
openModalHandler(component, props = {}, actions = {}) {
  /* 컴포넌트 띄우는 함수(openHandler) 실행 시 mount와 close 이벤트 액션을 넘김 */
  return this.openHandler(component, props, { ...actions,
    // mount event action
    mount: (componentEl) =&gt; {
      // 컴포넌트가 마운트 되면 mount 이벤트를 emit
      // history 쌓기
      window.history.pushState(null, &#39;&#39;, window.location.href);
      this.ADD_MODAL_LIST(componentEl); // store에 마운트된 컴포넌트 담기
    },
    // close event action            
    close: (componentEl) =&gt; {
      // 모달 내부적으로 존재하는 닫기 버튼을 누르면 close 이벤트 emit
      // 컴포넌트 제거 함수 (closeHandler) 실행하여 띄워진 요소 닫기
      this.closeHandler(componentEl);
      this.removeLastModal(componentEl); // store removeLastModal 함수 실행
    }
  });
}


// 브라우저 뒤로가기 시 모달, 팝업만 닫기
closeModalHandler() {
  // 가장 위에 쌓인 모달 요소 찾기
  const modalEl = this.modalList[this.modalList.length - 1];
  // 찾은 모달 닫기 액션
  this.closeHandler(modalEl);
  this.POP_MODAL_LIST(); // store에서 modal 제거
}</code></pre>
<br>

<h2 id="3-브라우저-뒤로가기-이벤트-감지">3. 브라우저 뒤로가기 이벤트 감지</h2>
<p>브라우저 페이지 이동 시에 발생하는 <code>onpopstate</code> 이벤트를 활용하여 브라우저 뒤로가기 이벤트를 감지시 예외처리를 해주었다.</p>
<pre><code class="language-js">// 브라우저 레이어가 mount 됐을 때 popstate 이벤트 핸들러 등록
mounted() {
  window.addEventListener(&#39;popstate&#39;, this.historyHandler);
},
beforeDestroy() {
  window.removeEventListener(&#39;popstate&#39;, this.historyHandler);
},

...

// 브라우저 뒤로가기 클릭시 모달만 닫히도록 예외처리
historyHandler() {
  // 스토어 modalList length를 체크하여
  // 떠 있는 팝업이 존재하면 팝업만 닫히게 closeModalHandler 실행
  if (this.modalList.length &gt; 0) {
    this.closeModalHandler();
  }
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] eject시 발생하는 Parsing Error 해결하기]]></title>
            <link>https://velog.io/@kim_unknown_/React-eject-Parsing-Error</link>
            <guid>https://velog.io/@kim_unknown_/React-eject-Parsing-Error</guid>
            <pubDate>Sun, 21 Jan 2024 17:18:11 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>프론트엔드 스터디에서 eject를 해보게 되었는데, eject를 하고 나니 Parsing Error가 뜨면서 파일마다 빨간줄이 뜨고 난리가 났었다.ㅠㅠ
생각보다 간단하게 해결이 되었는데, 여러 삽질 끝에 해결한 방법을 공유해보겠다.</p>
</blockquote>
<br/>

<p>우선 eject가 무엇인지 알고 들어가자.</p>
<h2 id="1-eject란">1. eject란</h2>
<p>CRA로 생성한 프로젝트는 설정파일과 종속성 등등의 것들이 숨겨져 있는데, 이 설정들을 보이도록 꺼내주는 것이 <code>eject</code>이다.
eject는 한 번 하면 되돌릴 수 없기 때문에 신중히 해야한다.</p>
<pre><code>npm run eject</code></pre><p>eject를 하고나면 아래와 같은 설정 파일들이 나타나는 것을 볼 수 있다.
<img src="https://velog.velcdn.com/images/kim_unknown_/post/4afce47e-fe26-403b-9c59-1affa9e06713/image.png" alt="eject 후 추출되는 파일들"></p>
<h2 id="2-eject-후-나타나는-parsing-error-해결하기">2. eject 후 나타나는 Parsing Error 해결하기</h2>
<p>eject를 실행하고 났더니 아래와 같이 parsing error가 나타났다.
<img src="https://velog.velcdn.com/images/kim_unknown_/post/f656df03-bd67-4526-a965-925c1a081c4d/image.png" alt="Parsing Error"></p>
<p>수시간의 삽질 끝에 내가 해결한 방법은 아래와 같다.
package.json &gt; &quot;eslintConfig&quot;에 아래 코드를 추가했더니 해결되었다.</p>
<pre><code class="language-js">// package.json

  &quot;eslintConfig&quot;: {
    ...
    &quot;parserOptions&quot;: {
      &quot;babelOptions&quot;: {
        &quot;presets&quot;: [
          [
            &quot;babel-preset-react-app&quot;,
            false
          ]
        ]
      }
    }
  },</code></pre>
<p>혹시 나와 같은 오류로 헤매고 있는 사람이 있다면 이 글이 도움이 되었으면 좋겠다🥲</p>
<br/>

<blockquote>
<p>참고자료
🗒️<a href="https://github.com/velopert/learning-react/issues/348">https://github.com/velopert/learning-react/issues/348</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] CRA+TypeScript 환경에서 Craco를 활용하여 alias 적용하기]]></title>
            <link>https://velog.io/@kim_unknown_/React-CRA-TS-Craco</link>
            <guid>https://velog.io/@kim_unknown_/React-CRA-TS-Craco</guid>
            <pubDate>Sun, 21 Jan 2024 16:48:57 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>CRA 환경에서 eject하지 않고 Craco를 활용하여 절대경로 설정할 수 있다. 구글링 해보면 <code>craco-alias</code>를 활용하는 방법이 많이 나오는데, 현재는 지원하지 않는다고 한다. 몇 시간의 삽질 끝에 성공한 craco만으로 적용하는 방법을 정리해보려고 한다.</p>
</blockquote>
<br/>

<h2 id="1-craco-설치">1. Craco 설치</h2>
<p><em><code>craco</code>는 *</em>C<strong>reate **R</strong>eact <strong>A</strong>pp <strong>C</strong>onfiguration <strong>O</strong>verride의 약자로, CRA에 config 설정을 덮어쓰기 위한 패키지이다.</p>
<pre><code>npm install @craco/craco</code></pre><p><img src="https://velog.velcdn.com/images/kim_unknown_/post/e7541fca-e31d-4475-991e-9b72bcfbf778/image.png" alt="파일구조"></p>
<h2 id="2-tsconfigpathsjson-생성">2. tsconfig.paths.json 생성</h2>
<p>tsconfig.json 파일의 경로설정을 외부파일(<code>tsconfig.paths.json</code>)로 분리한다. 파일을 루트 위치에 생성한다.</p>
<pre><code class="language-js">// tsconfig.paths.json

{
    &quot;compilerOptions&quot;: {
        &quot;baseUrl&quot;: &quot;./src&quot;,
        &quot;paths&quot;: {
            &quot;@components/*&quot;: [&quot;./components/*&quot;],
            &quot;@pages/*&quot;: [&quot;./pages/*&quot;],
            &quot;@styles/*&quot;: [&quot;./styles/*&quot;]
        }
    }
}</code></pre>
<h2 id="3-tsconfigts-파일-override-설정">3. tsconfig.ts 파일 override 설정</h2>
<p>외부로 분리된 경로 설정을 tsconfig.json에서 인식할 수 있도록 override 설정해준다.</p>
<pre><code class="language-js">//tsconfig.ts

&quot;extends&quot;: &quot;./tsconfig.paths.json&quot;</code></pre>
<h2 id="4-cracoconfigjs-생성">4. craco.config.js 생성</h2>
<p>루트 위치에 craco.config.js 파일을 생성하여 override 할 설정을 적용한다.</p>
<pre><code class="language-js">const path = require(&#39;path&#39;);

module.exports = {
  webpack: {
    alias: {
      &#39;@src&#39;: path.resolve(__dirname, &#39;./src&#39;),
      &#39;@components&#39;: path.resolve(__dirname, &#39;./src/components&#39;),
      &#39;@pages&#39;: path.resolve(__dirname, &#39;./src/pages&#39;),
      &#39;@styles&#39;: path.resolve(__dirname, &#39;./src/styles&#39;)
    }
  }
};</code></pre>
<h2 id="5-packagejson-수정">5. package.json 수정</h2>
<p>마지막으로 package.json의 스크립트를 <code>react-scripts</code> -&gt; <code>craco</code> 로 수정한다. (*eject는 건들 필요 없다)</p>
<pre><code class="language-js">// package.json

&quot;scripts&quot;: {
    &quot;start&quot;: &quot;craco start&quot;,
    &quot;build&quot;: &quot;craco build&quot;,
    &quot;test&quot;: &quot;craco test&quot;,
    &quot;eject&quot;: &quot;react-scripts eject&quot;
}</code></pre>
<p>위 과정을 완료한 후 <code>npm run start</code>를 실행하면 아래와 같이 절대경로로 파일을 import 해올 수 있다.</p>
<pre><code class="language-js">import { Hello } from &#39;@components/Hello&#39;;
import { World } from &#39;@pages/World&#39;;</code></pre>
<blockquote>
<p>*개발서버가 실행 중인 상황에서는 즉각적으로 반영되지 않는다. 종료 후 서버를 재실행시키면 적용될 것이다!</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Vue.js] 입문하기 - 기본 문법]]></title>
            <link>https://velog.io/@kim_unknown_/Vue.js-Intro</link>
            <guid>https://velog.io/@kim_unknown_/Vue.js-Intro</guid>
            <pubDate>Mon, 10 Oct 2022 12:53:05 GMT</pubDate>
            <description><![CDATA[<h1 id="vue">Vue</h1>
<p>MVVM 패턴의 ViewModel 레이어에 해당하는 화면(View)단 라이브러리</p>
<p>✨Vue의 핵심 - <code>Reactivity</code> (반응성)
-&gt; 데이터가 바뀌면 변화를 감지하고 알아서 화면에 반영함</p>
<br/>

<h2 id="1-뷰-인스턴스instance">1. 뷰 인스턴스(instance)</h2>
<p>인스턴스는 뷰로 화면을 개발하기 위한 생성 단위이다.</p>
<p>Vue를 <strong>생성자</strong>라고 함 → 필요한 모든 기능을 생성자에 미리 정의해 놓고 사용자가 그 기능을 재정의하여 편리하게 사용하기 때문</p>
<p>인스턴스 생성 방법</p>
<pre><code class="language-js"> new Vue();</code></pre>
<p>인스턴스 속성 </p>
<pre><code class="language-js">new Vue({
  el: ,
  template: ,
  data: ,
  methods: ,
  created: ,
  watch: ,
});</code></pre>
<ul>
<li><code>el</code> : 인스턴스가 그려지는 화면의 시작점 (특정  HTML  태그)</li>
<li><code>template</code> :  화면에 표시할 요소 ( HTML, CSS  등)</li>
<li><code>data</code> :  뷰의 반응성(Reactivity)이 반영된 데이터 속성</li>
<li><code>method</code> :  화면의 동작과 이벤트 로직을 제어하는 메서드</li>
<li><code>created</code> :  뷰의 라이프 사이틀과 관련된 속성</li>
<li><code>watch</code> : data에서 정의한 속성이 변화했을 때  추가 동작을 수행할 수 있게 정의하는 속성</li>
</ul>
<pre><code class="language-js">var vm = new Vue({
  // body 태그 안에서#app을 찾아 해당 인스턴스를 붙이겠다는 의미
  // el을 정의하지 않으면 속성들을 정의하더라도#app에서 사용할 수 없음
  // 즉, 무조건 el을 특정 태그에 붙여줘야만 사용 가능
  el : &#39;#app&#39;
});</code></pre>
<br/>

<h2 id="2-템플릿-문법">2. 템플릿 문법</h2>
<h3 id="2-1-데이터-바인딩">2-1. 데이터 바인딩</h3>
<p>뷰 인스턴스에서 정의한 속성들을 화면에 표시하는 방법. <code>콧수염 괄호 {{ }}</code></p>
<pre><code class="language-jsx">&lt;div&gt;{{ message }}&lt;/div&gt;

new Vue({
    data: {
        message: &#39;Hello World&#39;;
    }
})</code></pre>
<h3 id="2-2-디렉티브">2-2. 디렉티브</h3>
<p>화면 요소를 조작하기 위한 문법.</p>
<ul>
<li><code>v-text</code> → 자바스크립트 innerText</li>
</ul>
<pre><code class="language-jsx">&lt;span v-text=&quot;msg&quot;&gt;&lt;/span&gt;
&lt;!-- 같은 방법 --&gt;
&lt;span&gt;{{msg}}&lt;/span&gt;</code></pre>
<ul>
<li><code>v-html</code> → 자바스크립트 innerHTML</li>
</ul>
<pre><code class="language-jsx">&lt;div v-html=&quot;html&quot;&gt;&lt;/div&gt;</code></pre>
<ul>
<li><code>v-if</code> → 조건부 렌더링</li>
<li><code>v-else</code></li>
<li><code>v-else-if</code></li>
</ul>
<pre><code class="language-jsx">&lt;!-- v-if - loading이 참일 때 보임 --&gt;
&lt;div v-if=&quot;loading&quot;&gt;Loading...&lt;/div&gt;

&lt;!-- v-else - loading이 거짓일 때 보임 --&gt;
&lt;div v-else&gt;test user has been logged in&lt;/div&gt;

new Vue({
    data: {
        loading: false
    }
})</code></pre>
<ul>
<li><code>v-show</code>
v-if와 비슷하게 조건에 따라 엘리먼트 표시</li>
</ul>
<pre><code class="language-jsx">&lt;div v-show=&quot;loading&quot;&gt;Loading...&lt;/div&gt;

new Vue({
    data: {
        loading: true
    }
})</code></pre>
<blockquote>
<ul>
<li>v-if와 v-show의 차이
v-show를 엘리먼트는 항상 렌더링 되어 DOM에 남아있다. 즉, v-show는 단순히 display 속성(display:none)을 변경하는 것이고, v-if는 조건에 따라 실제로 돔을 제거하고 생성한다.</li>
</ul>
</blockquote>
<ul>
<li><code>v-for</code>
데이터 속성의 개수만큼 화면의 요소를 반복하여 출력</li>
</ul>
<pre><code class="language-jsx">&lt;ul&gt;
  &lt;li v-for=&quot;(item,i) in items&quot;&gt;{{ item }}&lt;/li&gt;
&lt;/ul&gt;

new  Vue({
    data: {
        items: [&#39;Home&#39;, &#39;About&#39;, &#39;Login&#39;]
    }
})</code></pre>
<ul>
<li><code>v-bind</code>
동적으로 엘리먼트 속성 설정</li>
</ul>
<pre><code class="language-html">&lt;div v-bind:속성=&quot;사용할 속성명(data)&quot;&gt;&lt;/div&gt;</code></pre>
<p>🍀예시</p>
<pre><code class="language-jsx">&lt;!-- id, class 바인딩 --&gt;
&lt;p v-bind:id=&quot;uuid&quot; v-bind:class=&quot;name&quot;&gt;{{number}}&lt;/p&gt;

&lt;script&gt;
  new Vue({
    el: &quot;#app&quot;,
    data: {
      uuid: &quot;abc123&quot;,
      name: &quot;text-blue&quot;,
    },
  });
&lt;/script&gt;</code></pre>
<ul>
<li><code>v-on</code>
이벤트 처리</li>
</ul>
<pre><code class="language-html">&lt;button v-on:이벤트 이름=&quot;실행할 메소드&quot;&gt;&lt;/button&gt;</code></pre>
<p>🍀예시</p>
<pre><code class="language-jsx">&lt;!-- 클릭할 때마다 이벤트 실행 --&gt;
&lt;button v-on:click=&quot;logText&quot;&gt;Click Me!&lt;/button&gt;
&lt;!-- 키보드를 누를 때마다 이벤트 실행 --&gt;
&lt;input type=&quot;text&quot; v-on:keypress=&quot;logText&quot; /&gt;
&lt;!-- 엔터키를 누를 때마다 이벤트 실행 --&gt;
&lt;input type=&quot;text&quot; v-on:keypress.enter=&quot;logText&quot; /&gt;

&lt;script&gt;
  new Vue({
    el: &quot;#app&quot;,
    methods: {
      // 이벤트 함수
      logText: function () {
        console.log(&quot;clicked&quot;);
      },
    },
  });
&lt;/script&gt;</code></pre>
<p>*<code>v-on:이벤트이름.prevent</code> - 이벤트 새로고침 막기 (event.preventDefault()와 동일)</p>
<ul>
<li><code>v-model</code>
input 값 구하기</li>
</ul>
<pre><code class="language-jsx">&lt;input type=&quot;text&quot; v-model=&quot;message&quot; /&gt;
&lt;p&gt;{{message}}&lt;/p&gt;

&lt;script&gt;
  new Vue({
    el: &quot;#app&quot;,
    data: {
            // input 값이 바뀔 때마다 자동으로 message가 변경됨
      message: &quot;&quot;,
    },
  });
&lt;/script&gt;</code></pre>
<h3 id="2-3-this">2-3. this</h3>
<p>인스턴스 안의 메서드에서 사용되는 this는 해당 인스턴스를 가리킨다.</p>
<p>→  객체에 속한 메서드에서 사용될 때 this가 해당 메서드를 호출한 객체를 가리키는 것과 비슷한 관점에서 바라보면 됨</p>
<h3 id="2-4-computed-속성">2-4. computed 속성</h3>
<p>간단한 연산 수행 → data가 변경되면 연산 수행</p>
<p><strong>computed 속성은 반응형(reactive) 종속성에 기반하여 캐시된다</strong></p>
<pre><code class="language-jsx">&lt;script&gt;
new Vue({ 
  el: &quot;#app&quot;,
  data: {
    number: 100,
  },
  // number의 값이 바뀌면 doubleNum도 같이 바뀜
  computed: {
    doubleNum: function () {
      return this.number * 2;
    },
  },
});
&lt;/script&gt;</code></pre>
<h3 id="2-5-watch-속성">2-5. watch 속성</h3>
<p>매번 실행되기 부담스러운 무거운 로직 (ex. 데이터 요청)</p>
<pre><code class="language-jsx">&lt;script&gt;
  new Vue({
    el: &quot;#app&quot;,
    data: {
      num: 10,
    },
    // watch - 데이터의 변화에 따라 특정 로직을 실행
    watch: {
      num: function () {
        this.logText();
      },
    },
    methods: {
      addNum: function () {
        this.num++;
      },
      logText: function () {
        console.log(`${this.num} changed`);
      },
    },
  });
&lt;/script&gt;</code></pre>
<p>*왠만하면 watch보다는 computed를 사용하는걸 추천</p>
<br/>

<h2 id="3-뷰-컴포넌트">3. 뷰 컴포넌트</h2>
<p><img src="https://v3.ko.vuejs.org/images/components.png" alt=""></p>
<p>컴포넌트는 화면의 영역을 구분하여 개발할 수 있는 뷰의 기능이다.
컴포넌트 기반으로 화면을 개발하게 되면 코드의 재사용성이 올라가고 빠르게 화면을 제작할 수 있다.</p>
<p>컴포넌트는 전역 컴포넌트와 지역 컴포넌트로 구분하여 생성할 수 있다.</p>
<ul>
<li><code>전역 컴포넌트</code>는 인스턴스를 생성할 때마다 따로 등록할 필요 없이 모든 인스턴스에서 사용 가능</li>
<li><code>지역 컴포넌트</code>는 해당 인스턴스 내에서만 사용 가능, 인스턴스마다 새로 생성해야 함</li>
</ul>
<p>전역 컴포넌트 생성 방법</p>
<pre><code class="language-jsx">Vue.component(&#39;컴포넌트 이름&#39;, {
  // 컴포넌트 내용
});</code></pre>
<p>지역 컴포넌트 생성 방법</p>
<pre><code class="language-jsx">new Vue({
  el: &quot;#app&quot;, // #app에서만 사용할 수 있는 지역 컴포넌트
  components: {
    // &#39;컴포넌트 이름&#39; : { 컴포넌트 내용 },
  }
});</code></pre>
<p>🍀예시</p>
<pre><code class="language-jsx">// 표시
&lt;div id=&quot;app&quot;&gt;
  &lt;app-header&gt;&lt;/app-header&gt;
&lt;/div&gt;

// 생성
Vue.component(&quot;app-header&quot;, {
  template: &quot;&lt;h1&gt;Header&lt;/h1&gt;&quot;,
});</code></pre>
<h2 id="4-컴포넌트간의-통신-방식">4. 컴포넌트간의 통신 방식</h2>
<p><img src="https://joshua1988.github.io/vue-camp/assets/img/component-communication.2bb1d838.png" alt=""></p>
<p>상위에서 하위로는 데이터를 내려주고, 하위에서 상위로는 이벤트를 올려준다.</p>
<ul>
<li>하위 컴포넌트 → (이벤트 발생) → 상위 컴포넌트</li>
<li>상위 컴포넌트 → (props 전달) → 하위 컴포넌트</li>
</ul>
<h3 id="4-1-props-상위→하위">4-1. props (상위→하위)</h3>
<p>props에도 Reactivity가 반영된다.</p>
<p>props 사용 방식</p>
<pre><code class="language-jsx">// 하위 컴포넌트의 내용
var childComponent = {
  props: [&#39;프롭스 속성 명&#39;]
}

&lt;!-- 상위 컴포넌트의 템플릿 --&gt;
&lt;div id=&quot;app&quot;&gt;
  &lt;child-component v-bind:프롭스 속성 명=&quot;상위 컴포넌트의 data 속성&quot;&gt;&lt;/child-component&gt;
&lt;/div&gt;</code></pre>
<br/>

<p>🍀예시</p>
<pre><code class="language-jsx">&lt;div id=&quot;app&quot;&gt;
    &lt;app-header v-bind:propsData=&quot;message&quot;&gt;&lt;/app-header&gt;
&lt;/div&gt;

&lt;script&gt;
  // 컴포넌트를 분리하여 작성하고 등록하는 방식으로 가독성을 높임
  var appHeader = {
    template: &quot;&lt;h1&gt;{{ propsData }}&lt;/h1&gt;&quot;,
    props: [&quot;propsData&quot;], // props 속성 이름 지정
  };

  new Vue({
    el: &quot;#app&quot;,
    components: {
      &quot;app-header&quot;: appHeader,
    }
    data: {
      message: &quot;hi&quot;,
    },
  });
&lt;/script&gt;</code></pre>
<h3 id="4-2-이벤트-하위→상위">4-2. 이벤트 (하위→상위)</h3>
<pre><code class="language-jsx">// 하위 컴포넌트의 내용
this.$emit(&#39;이벤트 명&#39;);

&lt;!-- 상위 컴포넌트의 템플릿 --&gt;
&lt;div id=&quot;app&quot;&gt;
  &lt;child-component v-on:하위 컴포넌트에서 발생한 이벤트 명=&quot;상위 컴포넌트의 실행할 메서드 이름&quot;&gt;&lt;/child-component&gt;
&lt;/div&gt;</code></pre>
<br/>

<p>🍀예시</p>
<pre><code class="language-jsx">&lt;div id=&quot;app&quot;&gt;
    &lt;app-header v-on:pass=&quot;logText&quot;&gt;&lt;/app-header&gt;
&lt;/div&gt;

&lt;script&gt;
  var appHeader = {
    template: &quot;&lt;button v-on:click=&#39;passEvent&#39;&gt;Click Me&lt;/button&gt;&quot;,
    methods: {
      passEvent: function () {
        this.$emit(&quot;pass&quot;);  // 이벤트 명
      },
    },
  };

  new Vue({
    el: &quot;#app&quot;,
    components: {
      &quot;app-header&quot;: appHeader,
    },
    methods: {
            // 이벤트 발생시 실행할 메서
      logText: function () {
        console.log(&quot;hi&quot;);
      },
    },
  });
&lt;/script&gt;</code></pre>
<h3 id="4-3-같은-레벨의-컴포넌트-간의-통신-방식">4-3. 같은 레벨의 컴포넌트 간의 통신 방식</h3>
<p><img src="https://velog.velcdn.com/images%2Ffreejia%2Fpost%2F39f0b132-6bb9-4a6e-975d-550cb866db2d%2Fimage.png" alt=""></p>
<p>이벤트로 Root로 전달하고 Root에서 props로 전달</p>
<pre><code class="language-jsx">var appContent = {
  template:
    &quot;&lt;div&gt;content&lt;button v-on:click=&#39;passNum&#39;&gt;pass&lt;/button&gt;&lt;/div&gt;&quot;,
  methods: {
    passNum: function () {
      // 10을 같은 레벨인 appHeader에 전달하기 위해서
      // 먼저 Root로 올려 전달하고 Root에서 appHeader로 내려 전달하는 방식
      // 상위로 올릴 때는 event 내릴 때는 props
      this.$emit(&quot;pass&quot;, 10);
    },
  },
};</code></pre>
<br/>

<h2 id="5-뷰-라우터">5. 뷰 라우터</h2>
<p>뷰 라우터는 뷰 라이브러리를 이용하여 SPA를 구현할 때 사용하는 라이브러리이다.</p>
<p>설치 방법</p>
<ul>
<li><p>CDN</p>
<pre><code class="language-jsx">&lt;script src=&quot;https://unpkg.com/vue-router@3.5.3/dist/vue-router.js&quot;&gt;</code></pre>
</li>
<li><p>NPM</p>
<pre><code class="language-jsx">npm install vue-router</code></pre>
</li>
</ul>
<p>라우터 인스턴스 생성</p>
<pre><code class="language-jsx">// 라우터 인스턴스 생성
var router = new VueRouter({
  // 라우터 옵션
})

// 인스턴스에 라우터 등록
new Vue({
  router: router
})</code></pre>
<p>라우터 옵션</p>
<ul>
<li><code>routes</code> : 페이지의 라우팅 정보 (라우팅할 url과 컴포넌트)<ul>
<li><code>path</code> : 페이지의 url</li>
<li><code>component</code> : 해당 url에서 표시될 컴포넌트</li>
</ul>
</li>
<li><code>mode</code> : URL의 해쉬 값(#) 제거 속성</li>
</ul>
<h3 id="router-view"><strong>router-view</strong></h3>
<p>url에 따라 해당하는 컴포넌트를 보여주는 태그로 뷰 인스턴스에 라우터를 연결해야 사용 가능하다.</p>
<p><code>&lt;router-view&gt;&lt;/router-view&gt;</code></p>
<h3 id="router-link">router-link</h3>
<p>특정 url로 이동시켜주는 태그로 html a태그와 같은 역할을 한다.</p>
<p><code>&lt;router-link to=&quot;이동할 url&quot;&gt;&lt;/router-link&gt;</code></p>
<pre><code class="language-jsx">&lt;div id=&quot;app&quot;&gt;
  &lt;div&gt;
    &lt;!-- 특정 페이지로 이동시켜줌 --&gt;
    &lt;router-link to=&quot;/login&quot;&gt;Login&lt;/router-link&gt;
    &lt;router-link to=&quot;/home&quot;&gt;Home&lt;/router-link&gt;
  &lt;/div&gt;
    &lt;!-- url에 따라 해당 하는 컴포넌트를 보여줌 --&gt;
  &lt;router-view&gt;&lt;/router-view&gt;
&lt;/div&gt;

&lt;script src=&quot;https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js&quot;&gt;&lt;/script&gt;
&lt;!-- CDN vue-router --&gt;
&lt;script src=&quot;https://unpkg.com/vue-router@3.5.3/dist/vue-router.js&quot;&gt;&lt;/script&gt;

&lt;script&gt;
var LoginComponent = {
  template: &quot;&lt;div&gt;login&lt;/div&gt;&quot;,
};
var HomeComponent = {
  template: &quot;&lt;div&gt;home&lt;/div&gt;&quot;,
};

new VueRouter({
  mode: &#39;history&#39;,  // url 해시값 제거
  routes: [
        // &#39;/login&#39;에서 LoginComponent 컴포넌트 표시
    { path: &#39;/login&#39;, component: LoginComponent },
        // &#39;/home&#39;에서 HomeComponent 컴포넌트 표시
    { path: &#39;/home&#39;, component: HomeComponent }
  ]
})

new Vue({
    // 인스턴스에 라우터 연결
    router: router,
  });
&lt;/script&gt;</code></pre>
<br/>

<h2 id="6-vue-cli">6. Vue CLI</h2>
<p><code>CLI (Command Line Interface)</code> : 명령어를 통해 특정 액션을 실행하는 도구.</p>
<p>프로젝트 생성</p>
<pre><code class="language-jsx">vue create &#39;프로젝트 폴더 위치&#39;</code></pre>
<p>프로젝트 실행</p>
<pre><code class="language-jsx">npm run serve</code></pre>
<br/>

<h2 id="7-싱글-파일-컴포넌트">7. 싱글 파일 컴포넌트</h2>
<p>파일 구조</p>
<pre><code class="language-jsx">&lt;template&gt;
&lt;!-- HTML --&gt;
&lt;/template&gt;

&lt;script&gt;
// Javascript - 인스턴스 속성들
&lt;/script&gt;

&lt;style&gt;
/* css */
&lt;/style&gt;</code></pre>
<p>*템플릿 루트는 무조건 하나여야 한다. -&gt; 전체 태그를 하나의 태그가 감싸고 있어야 함</p>
<p>*파스칼 케이스로 네이밍 + 최소 두단어 이상으로 조합</p>
<br/>

<blockquote>
<p>참고 자료
📝 <a href="https://www.inflearn.com/course/age-of-vuejs">Vue.js 시작하기 - Age of Vue.js 강의</a>
📝 <a href="https://joshua1988.github.io/vue-camp/textbook.html">Cracking Vue.js</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[🗂️프론트엔드 개발자 면접 질문 모음]]></title>
            <link>https://velog.io/@kim_unknown_/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EA%B0%9C%EB%B0%9C%EC%9E%90-%EB%A9%B4%EC%A0%91-%EC%A7%88%EB%AC%B8-%EB%AA%A8%EC%9D%8C</link>
            <guid>https://velog.io/@kim_unknown_/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EA%B0%9C%EB%B0%9C%EC%9E%90-%EB%A9%B4%EC%A0%91-%EC%A7%88%EB%AC%B8-%EB%AA%A8%EC%9D%8C</guid>
            <pubDate>Mon, 10 Oct 2022 08:33:53 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>8월 말부터 취준을 시작하여 감사하게도 한 달만에 취업이 되었다. 한 달 동안 면접을 보러 다니면서 느낀 점은 질문 내용이 다 비슷해서 단골 질문 리스트만 잘 공부해가면 기술 면접은 문제 없다는 것이었다. 그래서 내가 면접 대비하면서 공부하고 실제로 받았던 질문 리스트를 올려보려고 한다. 이 글이 누군가에게는 도움이 되길...!🍀</p>
</blockquote>
<br/>
⭐은 실제 면접에서 질문 받은 빈도수를 기반으로 하여 개인적으로 생각한 중요도이므로 참고만 부탁드립니다.🙏🏻

<br/>

<h1 id="1-기술면접💻">1. 기술면접💻</h1>
<h2 id="1-cs">1. CS</h2>
<ol>
<li><p>브라우저 렌더링 원리를 설명해보세요. ⭐⭐</p>
</li>
<li><p>주소창에 <a href="http://www.google.com">www.google.com</a> 을 입력하면 생기는 일을 설명해주세요.</p>
</li>
<li><p>웹 프로토콜이란?</p>
</li>
<li><p>HTTP와 HTTPS의 차이점을 말해주세요.</p>
</li>
<li><p>RESTFUL API란? ⭐</p>
</li>
<li><p>get, post 차이를 설명해주세요. ⭐⭐</p>
</li>
<li><p>브라우저 저장소 (로컬 스토리지, 세션 스토리지, 쿠키의 차이) ⭐⭐⭐</p>
</li>
<li><p>JWT란?</p>
</li>
<li><p>CORS란?</p>
</li>
<li><p>MVC와 MVVM 패턴의 차이를 설명해주세요. ⭐</p>
</li>
<li><p>OOP (Object Oriented Programming)란 무엇인가요? ⭐⭐</p>
</li>
<li><p>CSR과 SSR의 차이를 설명해주세요. ⭐</p>
</li>
<li><p>SPA와 MPA의 차이를 설명해주세요. ⭐</p>
</li>
<li><p>Git을 사용해보셨나요? Git에 대해 아는 것을 말해보세요. ⭐</p>
</li>
<li><p>웹팩과 바벨이 무엇인가요?</p>
</li>
<li><p>깊은복사와 얕은복사의 차이점을 말해주세요.</p>
</li>
<li><p>크로스 브라우징이란?</p>
</li>
<li><p>Sementic HTML란 무엇인가요?</p>
</li>
<li><p>라이브러리와 프레임워크에 대해 설명해주세요.</p>
</li>
</ol>
<h2 id="2-javascript">2. JavaScript</h2>
<ol start="19">
<li><p>실행 컨텍스트가 무엇인가요? ⭐⭐⭐</p>
</li>
<li><p>호이스팅에 대해 설명해주세요. ⭐⭐⭐</p>
</li>
<li><p>클로저와 스코프에 대해 설명해주세요. ⭐⭐⭐</p>
</li>
<li><p>화살표 함수와 일반함수의 차이를 설명해보세요.</p>
</li>
<li><p>this에 대해 아는대로 설명해보세요.</p>
</li>
<li><p>call( ), apply( ), bind( )의 차이점을 말해주세요.</p>
</li>
<li><p>자바스크립트 비동기 처리(callback, promise, async/await)에 대해 설명해주세요 ⭐⭐⭐
-&gt; promise와 async/await의 차이점을 설명해주세요.</p>
</li>
<li><p>이벤트 루프가 무엇인가요?</p>
</li>
<li><p>마이크로태스크 큐와 태스크 큐의 차이점을 아시나요?</p>
</li>
<li><p>Promise와 setTimeout 우선 순위에 대해 말해주세요.</p>
</li>
<li><p>이벤트 버블링과 캡쳐링이란 무엇인가요?</p>
</li>
<li><p>event.target과 event.currentTarget의 차이점을 말해주세요.</p>
</li>
<li><p>var, let, const의 차이점을 말해주세요. ⭐⭐</p>
</li>
<li><p>자바스크립트의 원시타입에 대해 설명해주세요.</p>
</li>
<li><p>ES6 문법 아는 것이 있다면 말해주세요.</p>
</li>
<li><p>forEach( )와 Map( )의 차이가 무엇인가요?</p>
</li>
<li><p>Sass/Scss/css-in-js를 사용해보셨나요? (혹은 차이점을 말해주세요)</p>
</li>
<li><p>AJAX란? </p>
</li>
<li><p>HTML이 렌더링 중에 자바스크립트가 실행되면 렌더링이 멈추는 이유를 설명해주세요.</p>
</li>
<li><p>package.json에서 dependencies와 devDependencies의 차이점을 말해주세요.</p>
</li>
<li><p>타입스크립트를 써보신 경험이 있나요? 타입스트립트에 대해서 어떻게 생각하시나요? ⭐</p>
</li>
</ol>
<h2 id="3-react">3. React</h2>
<ol start="44">
<li><p>React에 대해 아는대로 말씀해주세요. (원리, 특징, 장단점)</p>
</li>
<li><p>Virtual DOM이란? Virtual DOM을 사용하는 이유는 무엇인가요? 
⭐⭐</p>
</li>
<li><p>props와 state에 대해 설명해주세요. ⭐⭐</p>
</li>
<li><p>React Hook이 무엇인가요?</p>
</li>
<li><p>JSX란?</p>
</li>
<li><p>React 라이프사이클에 대해서 설명해주세요. ⭐</p>
</li>
<li><p>useMemo와 useCallback의 차이점을 말해주세요.</p>
</li>
<li><p>Class와 Hooks의 차이를 아시나요?</p>
</li>
<li><p>전역상태관리 툴을 사용해보신 경험이 있나요? 있다면 말씀해주세요.</p>
</li>
</ol>
<br/>

<h1 id="2-인성면접🤔">2. 인성면접🤔</h1>
<ol>
<li><p>자기소개 부탁드립니다.</p>
</li>
<li><p>왜 개발자가 되고 싶은가요?(전업했다면 그 이유), 개발자는 어떤 직업이라고 생각하나요?</p>
</li>
<li><p>본인이 생각하는 본인의 성격 장단점은 무엇인가요?</p>
</li>
<li><p>왜 굳이 프론트엔드를 선택했나요? 프론트엔드란 어떤 분야인가요?</p>
</li>
<li><p>개발자로서 중요하게 생각하는 점이 있다면 무엇인가요?</p>
</li>
<li><p>개발은 평생 공부해야하는 분야인데 어떤 식으로 학습을 하고 있나요? 
본인만의 학습법이 있다면?</p>
</li>
<li><p>개발자로서 목표가 무엇인가요? 혹은 본인의 인생의 목표가 무엇인가요?</p>
</li>
<li><p>최근 공부해보고 싶은 기술이 있다면 무엇이 있을까요?</p>
</li>
<li><p>스트레스 관리는 어떻게 하시나요?</p>
</li>
<li><p>프론트엔드는 기술이 빠르게 변화하는 데 두려워하진 않나요?</p>
</li>
<li><p>프로젝트를 진행하면서 어려웠던 점과 어떻게 극복했는지 말해주세요.</p>
</li>
<li><p>마지막으로 회사에 궁금한 점 혹은 하고 싶은 말</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[원시값과 참조값, 얕은 복사와 깊은 복사]]></title>
            <link>https://velog.io/@kim_unknown_/DataType-Copy</link>
            <guid>https://velog.io/@kim_unknown_/DataType-Copy</guid>
            <pubDate>Thu, 08 Sep 2022 19:09:38 GMT</pubDate>
            <description><![CDATA[<h2 id="1-원시값과-참조값">1. 원시값과 참조값</h2>
<p>자바스크립트에서의 데이터 타입은 원시값과 참조값으로 구분한다.</p>
<ul>
<li>원시 타입(Primitive Type): String, Number, Boolean, Null, Undefined, Symbol</li>
<li>객체 타입(Reference Type): Array, Object, Function</li>
</ul>
<p>원시값은 읽기 전용으로 변경 불가능한 값이다. 원시 값을 할당한 변수는 원시 값 자체를 값으로 갖는다.</p>
<p>여기서 변경 불가능한 값은 변수에 재할당이 불가능하단 의미가 아니라, 메모리에 올라간 값이 변경되지 않는다는 의미이다. 원시값이 변수에 저장될 때에는 메모리 공간이 확보되어 그 공간에 값이 저장되고 변수는 그 주소를 참조한다. 재할당도 마찬가지로 새로운 메모리 공간을 확보하여 값을 저장하고 변수가 참조하던 기존 주소를 새로 할당한 값의 주소로 변경하는 것이다. 즉, 값이 직접 변경되는 것이 아닌 참조하는 메모리 공간의 주소가 변경되는 것이다.</p>
<p><img src="https://publizm.github.io/static/1115c69ae58291530490b09e00d729ee/d0f75/memory.jpg" alt=""></p>
<p>참조값은 변경 가능한 값이다. 참조 값을 변수에 할당하면 변수에는 참조 값이 저장된다. 실제 객체의 값은 별도의 메모리 공간에 저장되며 변수는 그 공간을 참조하는 주소를 값으로 갖는다. 즉, 참조값은 생성된 객체가 저장된 메모리 공간의 주소를 의미한다. 참조값은 재할당 없이 직접적으로 객체에 접근하여 프로퍼티를 동적으로 추가, 갱신, 삭제가 가능하다. 이것이 변경 가능한 값의 의미이다.</p>
<p><img src="https://publizm.github.io/static/01d4e5c59a62aa8d5f44a2b9a8879da6/d0f75/memory_add.jpg" alt=""></p>
<p>원시 타입과 참조 타입은 값을 복사할 때에도 큰 차이가 있다. 원시 타입은 값이 복사되어 전달되며, 이것을 <strong>값에 의한 전달</strong>이라고 한다. 반면, 참조 타입은 실제 값이 아닌 참조값이 전달되며, 이것을 <strong>참조에 의한 전달</strong>이라고 한다.</p>
<p><img src="https://velog.velcdn.com/images/kim_unknown_/post/e7c41991-2bb3-4cfa-934e-785dbf970f9d/image.png" alt=""></p>
<h4 id="요약">요약</h4>
<p>원시값은 변경 불가능한 값으로 원시값이 변수에 저장될 때에는 메모리 공간이 확보되어 그 공간에 값이 저장되고 변수는 그 주소를 참조한다. 재할당을 하게되면 값이 직접 변경되는 것이 아닌 참조하는 메모리 공간의 주소가 변경된다.</p>
<p>참조값은 변경 가능한 값으로 참조 값을 변수에 할당하면 실제 객체의 값은 별도의 메모리 공간에 저장되며 변수는 그 공간을 참조하는 주소를 값으로 갖는다. 참조값은 직접적으로 객체에 접근하여 추가, 갱신, 삭제가 가능하다.</p>
<hr>
<h2 id="2-얕은-복사와-깊은-복사">2. 얕은 복사와 깊은 복사</h2>
<p>원시 타입의 값은 새로운 메모리 공간에 독립적인 값을 저장하기 때문에 깊은 복사가 되고 참조 타입값은 얕은 복사가 된다. </p>
<p>즉, 얕은 복사는 <strong>&#39;메모리 주소 값&#39;을 복사</strong>한 것이다. 반대로 깊은 복사는 <strong>새로운 메모리 공간을 확보해 완전히 복사</strong>하는 것이다.</p>
<h3 id="2-1-얕은-복사">2-1. 얕은 복사</h3>
<p><code>얕은 복사(Shallow Copy)</code>란 객체를 복사할 때 원본 값과 복사된 값이 같은 참조(=메모리 주소)를 가리키고 있는 것을 말한다. 객체 안에 객체가 있을 경우 한 개의 객체라도 원본 객체를 참조하고 있다면 이를 얕은 복사라고 한다.</p>
<p>아래와 같이 일반적인 복사의 경우 참조에 의한 할당이 이루어지므로 둘은 같은 주소를 참조하게 된다. 즉, 똑같은 데이터가 하나 더 생성되는 것이 아니라, 해당 데이터의 메모리 주소를 복사함으로써 한 데이터를 같이 공유하는 것이다. 이와 같은 경우를 얕은 복사라고 한다.</p>
<p>얕은 복사의 경우 서로 같은 참조를 가리키기 때문에 복사 후에 객체를 수정하면 기존 객체에도 영향을 끼친다.</p>
<pre><code class="language-js">const obj1 = { a: &quot;a&quot;, b: &quot;a&quot;, c: &quot;c&quot;};
const obj2 = obj1;

// 원본 객체(obj1)와 복사 객체(obj2)가 서로 같은 참조를 가리킴
console.log( obj1 === obj2 );  // true

obj2.a = &quot;apple&quot;;  // 복사한 객체 수정

// 원본 객체에도 영향을 미침 둘 다 값이 바뀜
console.log(obj1);  // { a: &quot;apple&quot;, b: &quot;a&quot;, c: &quot;c&quot;}
console.log(obj2);  // { a: &quot;apple&quot;, b: &quot;a&quot;, c: &quot;c&quot;}</code></pre>
<p><code>Object.assign()</code>, <code>spread 연산자{...obj}</code>는 객체 자체는 깊은 복사가 수행되지만, 2차원 이상의 객체는 얕은 복사가 수행된다. 아래와 같이 객체 자체는 서로 다른 주소를 참조하고 있는 깊은 복사가 이루어지지만 내부의 객체는 같은 주소를 참조한다.</p>
<pre><code class="language-js">let origin = {
    a: &quot;a&quot;,
    b: { c: &quot;c&quot; }
};

// Object.assign()을 활용한 복사
let copyAssign = Object.assign({}, origin);
// 전개 구문을 활용한 복사
let copySpread = {...origin}

// 복사한 객체 수정
copyAssign.b.c = &quot;cat&quot;
copySpread.b.c = &quot;cat&quot;

// 객체 자체는 깊은 복사가 되어 서로 다른 주소를 참조함
console.log(origin === copyAssign) // false
console.log(origin === copySpread) // false
// 하지만 2차원 이상의 객체는 얕은 복사가 수행되어 같은 주소를 참조함
console.log(origin.b.c === copyAssign.b.c) // true
console.log(origin.b.c === copySpread.b.c) // true</code></pre>
<h3 id="2-2-깊은-복사">2-2. 깊은 복사</h3>
<p><code>깊은 복사(Deep Copy)</code>는 복사된 객체가 다른 주소를 참조하며 내부의 값만 복사된다. 객체 안에 객체가 있을 경우에도 원본과의 참조가 완전히 끊어졌다면 이를 깊은 복사라고 한다.</p>
<p><code>JSON.stringify()</code>는 객체를 json 문자열로 변환하는데 이 과정에서 원본 객체와의 참조가 모두 끊어진다. 객체를 json 문자열로 변환 후, JSON.parse()를 이용해 다시 원래 객체(자바스크립트 객체)로 만들어준다.</p>
<pre><code class="language-js">const obj1 = {
  a: &quot;a&quot;,
  b: {
    c: &quot;c&quot;,
  },
};

const obj2 = JSON.parse(JSON.stringify(obj1));

obj2.b.c = &quot;cat&quot;;

console.log(obj1); // { a: &quot;a&quot;, b: { c: &quot;c&quot; } }
console.log(obj1.b.c === obj2.b.c); // false</code></pre>
<p>커스텀 재귀 함수를 구현하면 깊은 복사를 할 수 있다.</p>
<pre><code class="language-js">let origin = {
    a: 1,
    b: { c: 2 }
};

function isCopyObj(origin) {
    let res = {};

    for (let key in origin) {
      if (typeof origin[key] === &#39;object&#39;) {
          res[key] = isCopyObj(obj[key]);
      } else {
          res[key] = origin[key];
      }
    }

    return res;
}

let copy = isCopyObj(origin);

copy.b.c = 3
console.log(origin.b.c === copy.b.c) //false</code></pre>
<h4 id="요약-1">요약</h4>
<p>얕은 복사(Shallow Copy)란 객체를 복사할 때 원본 값과 복사된 값이 같은 참조(=메모리 주소)를 가리키고 있는 것을 말한다. 객체 안에 객체가 있을 경우 한 개의 객체라도 원본 객체를 참조하고 있다면 이를 얕은 복사라고 한다.</p>
<p>깊은 복사(Deep Copy)는 복사된 객체가 다른 주소를 참조하며 내부의 값만 복사된다. 객체 안에 객체가 있을 경우에도 원본과의 참조가 완전히 끊어졌다면 이를 깊은 복사라고 한다.</p>
<br/>

<blockquote>
<p>참고 자료
📝 <a href="https://wonyoung2257.tistory.com/28">[JavaScript] - 원시 값과 참조 값</a>
📝 <a href="https://cocobi.tistory.com/156">[JS] 참조 타입의 얕은 복사와 깊은 복사(Shallow Copy &amp; Deep copy)</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[CSR과 SSR, SPA와 MPA]]></title>
            <link>https://velog.io/@kim_unknown_/CSR-SSR-SPA-MPA</link>
            <guid>https://velog.io/@kim_unknown_/CSR-SSR-SPA-MPA</guid>
            <pubDate>Sat, 03 Sep 2022 17:52:19 GMT</pubDate>
            <description><![CDATA[<h2 id="1-csr과-ssr">1. CSR과 SSR</h2>
<h3 id="1-1-csr">1-1. CSR</h3>
<p><img src="https://d2.naver.com/content/images/2020/06/csr.png" alt=""></p>
<p><code>CSR(Client Side Rendering)</code>은 렌더링이 클라이언트 측에서 발생한다.</p>
<p>CSR은 유저가 웹사이트에 방문하면 브라우저가 서버에 콘텐츠를 요청하고 서버는 빈 뼈대만 있는 html을 응답으로 보내준다. 이 때문에 처음에 접속하면 빈 화면만 보인다는 문제점이 있다. 이 후 브라우저는 링크된 js 파일을 서버로부터 다운받게 된다. 이 js 파일 안에는 어플리케이션을 구동하는 라이브러리와 프레임워크의 소스 코드까지 모두 포함되어 있기 때문에 시간이 오래 걸릴 수도 있다. 이러한 과정을 거쳐 동적으로 페이지를 띄워주게 된다.</p>
<p>첫 로딩 시 다운로드 받아야 할 파일이 많아 사용자가 첫 화면을 보기 까지 시간이 오래 걸린다. 하지만 이후에는 필요한 데이터만 서버에 요청하기 때문에 나머지 속도는 빠르며, 서버 성능에도 무리가 가지 않는다.</p>
<p>CSR은 html 내용이 아래와 같이 텅 비어있기 때문에 검색엔진최적화(SEO)가 불리하다. 검색 엔진이 색인할 만한 컨텐츠가 존재하지 않기 때문이다.</p>
<pre><code class="language-html">&lt;body&gt;
  &lt;div id=&quot;root&quot;&gt;&lt;/div&gt;
  &lt;script src=&quot;app.js&quot;&gt;&lt;/script&gt;
&lt;/body&gt;</code></pre>
<h3 id="1-2-ssr">1-2. SSR</h3>
<p><img src="https://d2.naver.com/content/images/2020/06/ssr.png" alt=""></p>
<p><code>SSR(Server Side Rendering)</code>은 서버에서 렌더링을 마치고 클라이언트로 전달하는 방식이다.</p>
<p>SSR은 유저가 웹사이트에 방문하면 브라우저가 서버에 콘텐츠를 요청한다. 서버에서 필요한 데이터를 모두 가져와서 렌더링 준비를 마친 html과 js를 전달한다. 클라이언트 상에서는 잘 만들어진 html 파일을 받아와서 바로 화면에 띄운다. 때문에 사용자가 첫 화면 빠르게 볼 수 있지만, 동적으로 데이터를 처리하는 js 로직과 연결될 때까지 사용자의 입력에 반응이 없을 수도 있다.</p>
<p>첫 로딩 속도는 빠르지만 나머지 로딩 시에도 동일한 과정을 거치기 때문에 나머지 로딩 속도가 느리고 화면 깜박임 문제가 존재한다. 또한, 사용자가 클릭을 할 때마다 서버에 요청을 해야하므로 서버 과부하에 걸리기 쉽다.  또한, 모든 컨텐츠가 html 파일에 담겨져 있기 때문에 검색엔진최적화(SEO)가 유리하다. </p>
<h4 id="요약">요약</h4>
<p>CSR은 렌더링이 클라이언트 측에서 발생한다. 첫 로딩시 모든 정적 파일을 다운로드 받아야하기 때문에 첫 로딩 속도는 느리지만 이후 나머지 로딩은 빠르다. 필요한 데이터만 서버에 요청하기 때문에 서버 성능에도 무리가 가지 않는다. 다만 SEO에 불리하다.</p>
<p>SSR은 서버에서 렌더링을 마치고 클라이언트로 전달하는 방식이다. 첫 로딩 속도는 빠르지만 나머지 로딩 시에도 동일한 과정을 거치기 때문에 나머지 로딩 속도가 느리다. 사용자가 클릭을 할 때마다 서버에 요청을 해야하므로 서버 과부하에 걸리기 쉽다. 다만 SEO에 유리하다.</p>
<hr>
<h2 id="2-spa와-mpa">2. SPA와 MPA</h2>
<p><img src="https://asperbrothers.com/wp-content/uploads/2019/11/spa-mpa-feature.jpg" alt=""></p>
<h3 id="2-1-spa">2-1. SPA</h3>
<p><code>SPA(Single Page Application)</code>는 한 개(Single)의 Page로 구성된 Application이다.</p>
<p>SPA는 웹 에플리케이션에 필요한 모든 정적 리소스(HTML, CSS, JavaScript)를 첫 로딩 시 한 번에 다운로드한다. 그 이후 다른 페이지 요청이 있을 때, 페이지 갱신에 필요한 데이터만 전달 받아서 페이지를 갱신한다. 즉, 첫 로딩 시 페이지를 받아온 이후에는 페이지 새로고침 없이 필요한 부분만 수정하는 것이다. 따라서, SPA는 CSR(Client Side Rendering) 방식을 채택하고 있는 것이다.</p>
<p>SPA 특징</p>
<ul>
<li>페이지 전체를 새로고침하지 않기 때문에 페이지 이동 시 깜빡임이 없고 자연스러운 사용자 경험을 제공할 수 있다.</li>
<li>첫 로딩 시 필요한 모든 파일을 다운로드 해야하기 때문에 첫 로딩 속도가 느리다.</li>
<li>첫 로딩 이후 페이지 이동 등의 나머지 로딩 시에는 이미 필요한 파일을 모두 다운로드 받은 상태이므로 나머지 로딩 속도가 빠르다.</li>
<li>데이터 요청이 있을 때만 서버에 요청하기 때문에 서버에 부담이 적다. </li>
<li>html 파일에 검색 엔진이 색인할 컨텐츠가 없어 검색엔진최적화(SEO)가 불리하다.</li>
</ul>
<h3 id="2-2-mpa">2-2. MPA</h3>
<p><code>MPA(Multiple Page Application)</code>는 여러 개(Single)의 Page로 구성된 Application이다.</p>
<p>MPA는 페이지를 요청할 때마다 서버에서 렌더링 된 정적 리소스(HTML, CSS, JavaScript)를 매번 다운로드한다. 즉, 첫 로딩 시에나 다른 페이지로 이동 시에나 동일하게 서버로부터 완전한 페이지를 받아오는 것이다. 따라서, MPA는 SSR(Server Side Rendering) 방식을 채택하고 있는 것이다.</p>
<p>MPA 특징</p>
<ul>
<li>페이지 이동이 발생할 때마다 전체 페이지의 새로고침이 발생하기 때문에 화면 깜빡임이 존재한다.</li>
<li>서버에서 이미 렌더링을 마친 상태로 전달받기 때문에 첫 로딩 시에 다운로드 받을 파일이 많지 않아 첫 로딩 속도가 빠르다.</li>
<li>JS 파일을 모두 다운로드하고 적용하기 전까지는 각각의 기능이 동작하지 않는다.</li>
<li>첫 로딩 이후 페이지 이동 등의 나머지 로딩 시에도 첫 로딩과 동일한 로딩 과정을 거쳐야하기 때문에 나머지 로딩 속도가 느리다.</li>
<li>매번 서버에 요청을 하기 때문에 서버에 부담이 갈 수 있다.</li>
<li>서버로부터 완성된 형태의 HTML파일을 전달 받기 때문에 검색 엔진이 페이지를 크롤링하기에 적합하여 검색엔진최적화(SEO)에 유리하다.</li>
</ul>
<p><img src="https://www.hestabit.com/blog/wp-content/uploads/2021/08/re-create-1024x536.jpg" alt=""></p>
<h4 id="요약-1">요약</h4>
<p>SPA는 한 개의 Page로 구성된 Application이다. 첫 로딩 시 웹 에플리케이션에 필요한 모든 정적 리소스를 한 번에 다운로드하며, 다른 페이지 요청이 있을 때 페이지 갱신에 필요한 데이터만 전달 받아서 페이지를 갱신한다. 일반적으로 CSR 방식을 채택하고 있다.</p>
<p>MPA는 여러 개의 Page로 구성된 Application이다. 페이지를 요청할 때마다 서버에서 렌더링 된 정적 리소스를 매번 다운로드하며 매번 전체 페이지가 재렌더링 된다. 일반적으로 SSR 방식을 채택하고 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[This, CORS, MVC와 MVVM 패턴, REST API]]></title>
            <link>https://velog.io/@kim_unknown_/technicalInterview-3</link>
            <guid>https://velog.io/@kim_unknown_/technicalInterview-3</guid>
            <pubDate>Tue, 30 Aug 2022 17:18:35 GMT</pubDate>
            <description><![CDATA[<h2 id="1-this">1. This</h2>
<p><code>this</code>는 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수이다. this를 통해 자신이 속한 객체 또는 자신이 생성할 인스턴스의 프로퍼티나 메서드를 참조할 수 있다. 자바스크립트의 this는 호출에 따라 달라진다.</p>
<h3 id="1-1-기본-바인딩-전역-함수-안에서-사용">1-1. 기본 바인딩 (전역, 함수 안에서 사용)</h3>
<p>this를 단독 호출하거나 함수 안에서 호출하면 전역 객체(global object)에 바인딩된다. 브라우저에서 호출하는 경우 [object Window]가 된다. (엄격모드라면 undefined)</p>
<h3 id="1-2-암시적-바인딩-메서드-안에서-사용">1-2. 암시적 바인딩 (메서드 안에서 사용)</h3>
<p>this를 메서드 안에서 호출하면 해당 메서드를 호출한 객체로 바인딩된다.
<span style="font-size: 0.9rem">* 메서드: 어떤 객체의 프로퍼티에 할당된 함수</span></p>
<h3 id="1-3명시적-바인딩-call-apply-bind">1-3.명시적 바인딩 (call(), apply(), bind())</h3>
<p>자바스크립트의 함수 프로토타입 메서드인 call(), apply(), bind() 중 하나를 호출하여 this 바인딩을 코드에서 명시하면 this는 직접 명시한 객체에 바인딩된다.
단 bind 메서드는 새로운 함수를 반환하지만 call, apply 메서드는 그 자리에서 함수를 호출한다는 차이점이 있다.</p>
<h3 id="1-4-화살표-함수">1-4. 화살표 함수</h3>
<p>위에서 본 this 바인딩은 함수가 호출되는 시점에 발생하는 <code>동적 바인딩</code>이지만, 화살표 함수는 <code>정적 바인딩</code>이다. 즉, 함수가 선언된 순간 코드상으로 바로 바깥쪽에 있는 스코프의 this를 사용한다.</p>
<h4 id="요약">요약</h4>
<p><code>this</code>는 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수이다. 자바스크립트의 this는 호출에 따라 결정된다. 전역범위나 함수에서 사용될 때 this는 전역객체를 가르킨다. 객체에 속한 메서드에서 사용될때 this는 해당 메서드를 호출한 객체를 가르킨다. 객체에 속한 메서드의 내부함수에서 사용될땐 전역객체를 가르킨다. call(), apply(), bind()를 사용하면 this는 직접 명시한 객체를 가리킨다.</p>
<hr>
<h2 id="2-cors">2. CORS</h2>
<p><img src="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/cors_principle.png" alt=""></p>
<p><code>CORS(Cross Origin Resource Sharing)</code>란 도메인이 다른 자원에 리소스를 요청할 때 접근 권한을 부여하는 메커니즘이다. 즉, 무분별하게 클라이언트가 다른 리소스에 접근하는 것을 막는 보안 이슈이다.</p>
<p>쉽게 말하자면 친구의 물건을 쓰려면 친구가 제한하는 규약안에서 사용해야 하듯, 다른 도메인의 자원을 쓰려면 자원의 주인이 허락한 규약을 지켜야 하는 것이고 이러한 규약을 표준화한 것이 CORS 이다.</p>
<p>CORS는 도메인 혹은 포트가 다른 서버의 자원을 요청할 때 생긴다. 예를 들어, 클라이언트는 3000번 포트를, 서버는 5000번 포트를 사용하는 경우와 같이 처음 리소스를 요청했던 그 주소가 아니라면 차단한다. 해당 문제를 해결하려면 Access-Control-Allow-Origin를 세팅하거나 proxy를 사용하면 된다.</p>
<hr>
<h2 id="3-mvc-mvvm-패턴">3. MVC, MVVM 패턴</h2>
<h3 id="3-1-mvc">3-1. MVC</h3>
<p><img src="https://velog.velcdn.com/images%2Fdin0121%2Fpost%2F0329d7ab-089a-4303-8795-d28763962c4b%2F%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202020-12-17%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%207.21.34.png" alt=""></p>
<p><code>MVC 패턴</code>은 프로그램을 역할에 따라 <code>Model</code>, <code>View</code>, <code>Controller</code>로 분리하여 설계한 이키텍처 패턴이다. </p>
<ul>
<li>Model : 어플리케이션에서 사용되는 데이터와 그 데이터를 처리하는 부분</li>
<li>View : 사용자에게 보여지는 UI부분</li>
<li>Controller : 사용자의 입력(Action)을 받고 처리하는 부분</li>
</ul>
<p>MVC 패턴은 가장 단순한 패턴이며 널리 사용되지만, View와 Model간의 의존성이 높아 복잡성이 높아지고 유지보수가 어려워진다는 단점이 있다. 또한, View에서 데이터의 업데이트가 발생하면 그에 따라 연쇄적으로 업데이트가 발생한다. 즉, MVC 패턴은 데이터의 변경 사항을 신속하게 전파하기가 어렵다.</p>
<h3 id="3-2-mvvm">3-2. MVVM</h3>
<p><img src="https://velog.velcdn.com/images%2Fdin0121%2Fpost%2Faf708306-e4d3-4055-b9e5-6871a5b27216%2F%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202020-12-17%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%207.21.23.png" alt=""></p>
<p><code>MVVM 패턴</code>은 각 구성 요소가 독립적으로 작성되고 테스트될 수 있도록 <code>Model</code>, <code>View</code>, <code>ViewModel</code>로 분리하여 설계한 아키텍처 패턴이다.</p>
<ul>
<li>Model : 앱에서 사용되는 데이터와 그 데이터를 처리하는 부분</li>
<li>View : 사용자에서 보여지는 UI 부분</li>
<li>View Model : View를 나타내 주기 위한 Model이자 View를 나타내기 위한 데이터 처리를 하는 부분</li>
</ul>
<p>MVVM 패턴은 MVC패턴과 달리 <u>View와 Model 사이의 의존성이 없다</u>. 또한 <code>Command 패턴</code>과 <code>Data Binding</code>을 사용하여 View와 ViewModel사이의 의존성 또한 없앤 패턴이다. 즉 각 구성요소의 의존성이 없고 독립적으로 작성된다. 그리고 중복되는 코드를 모듈화 하여 개발할 수 있다. 단, View Model의 설계가 쉽지 않다는 단점이 있다.</p>
<h4 id="요약-1">요약</h4>
<p><code>MVC 패턴</code>은 프로그램을 역할에 따라 <code>Model</code>, <code>View</code>, <code>Controller</code>로 분리하여 설계한 이키텍처 패턴이다. <code>MVVM 패턴</code>은 각 구성 요소가 독립적으로 작성되고 테스트될 수 있도록 <code>Model</code>, <code>View</code>, <code>ViewModel</code>로 분리하여 설계한 아키텍처 패턴이다.</p>
<p>MVC 패턴은 단순하고 직관적이지만, View와 Model 사이의 의존성으로 인해 어플리케이션이 커질수록 복잡해지고, 유지보수가 어렵다는 단점이 있다. MVVM 패턴은 View와 Model 사이의 의존성이 없고, View와 View Model사이의 의존성을 없어 각 구성요소가 독립적이다. 그러나 View Model의 설계가 쉽지 않다는 단점이 있다.</p>
<h2 id="4-rest-api">4. REST API</h2>
<p><code>REST</code>란 웹의 기존 기술과 HTTP 프로토콜을 그대로 활용하기 때문에 웹의 장점을 최대한 활용할 수 있는 아키텍처 스타일이다. REST란 HTTP URI를 통해 자원을 명시하고, HTTP Method (POST, GET, PUT, DELETE)를 통해 해당 자원에 대한 CRUD OPERATION을 적용하는 것을 의미한다.</p>
<p><img src="https://gmlwjd9405.github.io/images/network/rest.png" alt=""></p>
<ul>
<li><p>REST의 구성
자원(Resource) - URL
행위(Verb) - Http Method
표현(Representations)</p>
</li>
<li><p>REST의 특징
클라이언트-서버 구조(Client-Server)
무상태성(Stateless
캐시 처리 가능(Cacheable)
자체 표현 구조 (Self - descriptiveness)
계층화(Layered System)
인터페이스 일관성(Uniform Interface)</p>
</li>
</ul>
<p><code>REST API</code>는 &#39;REST하게 클라이언트와 서버간에 데이터를 주고받는 방식&#39;이다. REST API를 제공하는 웹 서비스를 <code>RESTful</code> 하다고 할 수 있다.</p>
<br/>

<blockquote>
<p>참고 자료
📑 <a href="https://seungtaek-overflow.tistory.com/21">[JS] 알쏭달쏭 자바스크립트 this 바인딩</a>
📑 <a href="https://nykim.work/71">[JS] 자바스크립트에서의 this</a>
📑 <a href="https://tecoble.techcourse.co.kr/post/2020-07-18-cors/">CORS란?</a>
📑 <a href="https://tried.tistory.com/76">Cors란 무엇인가?</a>
📑 <a href="https://scshim.tistory.com/407">MVVM vs MVC, 장점과 단점</a>
📑 <a href="https://velog.io/@somday/RESTful-API-%EC%9D%B4%EB%9E%80">RESTful API 이란</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[브라우저 렌더링 과정, HTTP와 HTTPS, 브라우저 저장소, JWT]]></title>
            <link>https://velog.io/@kim_unknown_/technicalInterview-2</link>
            <guid>https://velog.io/@kim_unknown_/technicalInterview-2</guid>
            <pubDate>Mon, 29 Aug 2022 08:48:38 GMT</pubDate>
            <description><![CDATA[<h2 id="1-브라우저-렌더링-과정">1. 브라우저 렌더링 과정</h2>
<p><img src="https://velog.velcdn.com/images/kim_unknown_/post/affe8130-619e-44b9-8ee8-87aa404b0756/image.png" alt=""></p>
<ol>
<li><p><strong><code>DOM</code> 생성</strong>
렌더링 엔진은 HTML 문서를 파싱하여 HTML 문서에 있는 모든 것들로 DOM을 구성한다.</p>
</li>
<li><p><strong><code>CSSOM</code> 생성</strong>
렌더링 엔진은 DOM을 생성하다가 css 태그를 만나면 css를 파싱하고 CSSOM을 만든다. CSSOM은 DOM이 화면에 어떻게 표시될지를 알려주는 역할을 한다.</p>
</li>
</ol>
<p><img src="https://blog.kakaocdn.net/dn/2i4wC/btq96EVaAYG/KDfP5cRNfDNzYjkELKlpM0/img.png" alt=""></p>
<ol start="3">
<li><p><strong><code>Render Tree</code> 생성</strong>
DOM과 CSSOM을 결합하여 Render Tree를 생성한다. Render Tree는 화면에 표시되어야 할 모든 노드의 컨텐츠, 스타일 정도를 포함하는 트리이다.
Render Tree는 DOM 트리의 루트(html)에서부터 각 노드를 순회하면서 각각의 맞는 CSSOM을 찾아서 규칙을 적용하여 생성된다.
참고로 화면에 표시되지 않는 노드는 렌더 트리에 포함되지 않는다. (&lt;head&gt;, &lt;title&gt;, &lt;script&gt; 등)</p>
<blockquote>
<p>❗주의
<code>display: none</code> 은 눈에 보이지도 않고 공간을 차지하지도 않기 때문에 렌더 트리에 포함되지 않는다.
<code>visibility: hidden</code>은 눈에 보이지는 않지만 공간을 차지하므로 렌더 트리에 포함된다.</p>
</blockquote>
</li>
<li><p><strong><code>Layout</code> (Render Tree 배치)</strong>
기기의 뷰포트(viewport) 내에서 노드의 정확한 위치와 크기를 계산한다. css에서 상대적인 단위(%, em 등)를 사용한 부분은 뷰포트에 맞춰서 절대적인 px단위로 변환된다.
이렇게 레이아웃 과정에서 렌더링 엔진은 각 요소들이 어떻게 생겼고 어떻게 보여줘야 하는지 알게 된다.</p>
</li>
<li><p><strong><code>Paint</code> (Render Tree 그리기)</strong>
렌더 트리를 순회하면서 페인트 함수를 호출해 노드를 화면에 실제 픽셀로 그려지도록 표현한다. 이렇게 하면 최초 렌더링이 완료된다.</p>
</li>
</ol>
<h4 id="요약">요약</h4>
<p>① 렌더링 엔진은 HTML 문서를 파싱하고 DOM을 생성한다.
② css 태그를 만나면 css를 파싱하고 CSSOM을 만든다.
③ DOM과 CSSOM을 결합하여 Render Tree를 생성한다.
④ 뷰포트 내에서 노드의 정확한 위치와 크기를 계산하여 Render Tree를 배치한다.
⑤ Render Tree의 노드를 화면에 실제 픽셀로 그린다.</p>
<hr>
<h2 id="2-http와-https">2. HTTP와 HTTPS</h2>
<p><img src="https://www.komputermedia.com/wp-content/uploads/2018/09/Perbedaan-HTTP-HTTPS-dan-FTP-Secara-Singkat.png" alt=""></p>
<h3 id="2-1-http">2-1. HTTP</h3>
<p><code>HTTP(Hyper Text Transfer Protocol)</code>는 서버/클라이언트 모델을 따라 데이터를 주고 받기 위한 프로토콜이다. 즉, HTTP는 인터넷에서 하이퍼텍스트를 교환하기 위한 통신 규약으로, 80번 포트를 사용하고 있다. HTTP 서버가 80번 포트에서 요청을 기다리고 있으며, 클라이언트는 80번 포트로 요청을 보내게 된다. HTTP는 상태를 가지고 있지 않는 <strong>Stateless</strong> 프로토콜이며 Method, Path, Version, Headers, Body 등으로 구성된다.
<span style="font-size: 0.9rem">* HTTP는 1989년 <code>팀 버너스 리(Tim Berners Lee)</code>에 의해 처음 설계되었다.</span></p>
<p>그러나, HTTP는 암호화가 되지 않은 평문 데이터를 전송하는 프로토콜이였기에 개인정보를 주고 받으면 제3자가 정보를 조회할 수 있는 문제점이 있었다. 이러한 문제를 해결하기 위해 등장한 것이 HTTPS이다.</p>
<h3 id="2-1-https">2-1. HTTPS</h3>
<p><code>HTTPS(Hypertext Transfer Protocol Secure)</code>는 HTTP에 데이터 암호화가 추가된 프로토콜이다. HTTPS는 SSL(Secure Socket Layer) 보안 소켓 계층을 이용하여 HTTP의 보안 문제를 해결하였다. 43번 포트를 사용하며, 네트워크 상에서 중간에 제3자가 정보를 볼 수 없도록 암호화를 지원하고 있다.</p>
<p>HTTPS를 사용하면 검색엔진 최적화(SEO)에 있어서 유리하다. 그러나, 암호화/복호화의 과정이 필요하기 때문에 HTTP보다 속도가 느려진다는 단점이 있다. 또한 인증서를 발급하고 유지하기 위한 비용도 발생한다. 따라서, 상황에 맞게 HTTP와 HTTPS를 구분하여 사용하는 것이 옳은 방법이다. </p>
<p><img src="https://seopressor.com/wp-content/uploads/2017/07/Difference-Between-HTTP-and-HTTPS.png" alt=""></p>
<h4 id="요약-1">요약</h4>
<p>HTTP와 HTTPS의 가장 큰 차이점은 <strong>보안(SSL 인증서)</strong>이다. HTTP는 보안에 취약한 반면 HTTPS는 안전하게 데이터를 주고 받을 수 있다. 그러나, 암호화/복호화의 과정이 필요하기 때문에 HTTP보다 속도가 느리고 인증서를 발급하고 유지하기 위한 비용이 발생한다.
따라서, 개인 정보와 같은 중요한 데이터를 주고 받아야 한다면 HTTPS를 이용해야 하지만, 노출이 되어도 무관한 정보라면 HTTP를 이용해도 괜찮다.</p>
<hr>
<h2 id="3-브라우저-저장소">3. 브라우저 저장소</h2>
<p><img src="https://www.educative.io/v2api/editorpage/5012590601699328/image/5821048469061632" alt=""></p>
<p>먼저 쿠키와 세션을 사용하는 이유는 HTTP의 특성이자 약점을 보완하기 위해서이다.</p>
<p>HTTP 프로토콜 환경은 <strong>Connectionless</strong>, <strong>Stateless</strong>한 특성을 가지기 때문에 서버는 클라이언트가 누구인지 매번 확인해야한다. 이 특성을 보완하기 위해서 쿠키와 세션을 사용하는 것이다.</p>
<blockquote>
<p>🔎Connectionless와 Stateless
⁕ <code>Connectionless</code> : 클라이언트가 요청을 한 후 응답을 받으면 그 연결을 끊어 버리는 특징
⁕ <code>Stateless</code> : 통신이 끝나면 상태정보를 유지하지 않는 특징
<span style="font-size: 0.9rem">→ 정보가 유지되지 않으면, 매번 페이지를 이동할 때마다 로그인을 다시 하는 등의 일이 발생할 수 있다.</span></p>
</blockquote>
<h3 id="3-1-cookie🍪">3-1. Cookie🍪</h3>
<p><code>쿠키(Cookie)</code>는 클라이언트가 서버에 방문한 정보를 클라이언트 단에 저장하는 작은 파일이다. HTTP에서 클라이언트의 상태 정보를 클라이언트의 PC에 저장하였다가 필요시 정보를 참조하거나 재사용할 수 있다. </p>
<p>쿠키는 매번 서버에 전송되므로 크기가 클 경우 서버에 부담이 갈 수 있다. 대부분의 브라우저가 지원하며, 저장 용량이 4KB로 작다. 만료 기간이 존재하며, 만료기간이 남아있다면 브라우저가 종료되어도 데이터가 보존된다. 보안이 취약하며, SameSite 옵션이 Strict가 아닌 경우, 다른 도메인에서 요청할 때도 자동 전송되는 위험성이 있다. (CSRF 취약)</p>
<blockquote>
<ul>
<li>쿠키의 동작 방식
① 클라이언트가 페이지를 요청한다. (→ 사용자가 웹사이트에 접근)
② 웹 서버는 쿠키를 생성한다.
③ HTTP 헤더에 쿠키를 포함 시켜 응답한다.
④ 넘겨받은 쿠키는 클라이언트가 보관하고 있다가(로컬 PC에 저장) 다시 서버에 요청할 때 요청과 함께 쿠키를 전송한다. (브라우저가 종료되어도 쿠키 만료 기간이 있다면 클라이언트에서 보관하고 있는다.)
⑤ 동일 사이트 재방문 시 클라이언트의 PC에 해당 쿠키가 있는 경우, 요청 페이지와 함께 쿠키를 전송한다.</li>
</ul>
</blockquote>
<h3 id="3-2-web-storage">3-2. Web Storage</h3>
<p><code>웹 스토리지(Web Storage)</code>는 클라이언트에 데이터를 저장할 수 있도록 HTML5부터 새롭게 지원하는 저장소이다. 데이터의 지속성에 따라 <code>로컬 스토리지(Local Storage)</code>와 <code>세션 스토리지(Session Storage)</code>로 나뉜다.</p>
<ul>
<li><p><code>로컬 스토리지(Local Storage)</code>는 데이터의 만료기간이 없으며 사용자가 데이터를 지우지 않은 한 영구적으로 보존된다. 사용자 설정 저장이나, 브라우저를 닫고 열었음에도 정보가 남아야 하는 것들을 저장할 때 사용한다. 즉, 자동 로그인을 할 때 사용한다.</p>
</li>
<li><p><code>세션 스토리지(Session Storage)</code>는 브라우저를 종료하면(세션이 종료되면) 데이터를 삭제한다. 즉, 일회성 로그인을 할 때 사용한다.</p>
</li>
</ul>
<p>HTML5부터 등장하였기 때문에 HTML5를 지원하지 않는 브라우저에서는 사용할 수 없다. 키(Key)와 값(Value)의 쌍 형태로 데이터를 저장하며, 단순 문자열을 넘어서 객체 정보 저장이 가능하다. 쿠키와 달리, 서버에 전송되지 않으므로 서버에 부담이 가지 않는다. 저장 용량이 5MB로 쿠키보다 크며, 만료 기간이 존재하지 않는다.
또한, 필요한 경우에만 꺼내 쓰는 것이므로 자동 전송의 위험성이 없다. 다른 오리진에서 요청하는 경우에는, 꺼내 쓰고 싶어도 오리진 단위로 접근이 제한되는 특성 덕분에 값을 꺼내 쓸 수 없다. (CSRF 안전)</p>
<blockquote>
<ul>
<li>세션의 동작 방식
① 클라이언트가 페이지에 요청한다. (사용자가 웹사이트에 접근)
② 서버는 접근한 클라이언트의 Request-Header 필드인 Cookie를 확인하여, 클라이언트가 해당 session-id를 보냈는지 확인한다.
③ session-id가 존재하지 않는다면 서버는 session-id를 생성해 클라이언트에게 돌려준다.
④ 서버에서 클라이언트로 돌려준 session-id를 쿠키를 사용해 서버에 저장한다.
⑤ 클라이언트는 재접속 시, 이 쿠키를 이용해 session-id 값을 서버에 전달</li>
</ul>
</blockquote>
<p><img src="https://juneyr.dev/static/3e4da315bd3b834de1c7992dceefb636/7d769/cookie.001.png" alt=""></p>
<h4 id="요약-2">요약</h4>
<p>쿠키는 클라이언트가 서버에 방문한 정보를 클라이언트 단에 저장하는 작은 파일이다. 모든 브라우저에서 지원하지만, 매번 서버에 전송이 되므로 서버에 부담이 갈 수 있다. 저장 용량이 작고 보안에 취약하다는 단점이 있다. 만료기간이 존재하며, 만료기간이 남아 있다면 브라우저가 종료되어도 데이터가 보존된다.</p>
<p>웹스토리지는 쿠키의 단점을 보완하여 HTML5부터 등장하였다. 웹 스토리지는 클라이언트에 저장만 할 뿐 서버로 전송되지는 않으므로 서버에 부담이 없다. 쿠키보다 저장용량이 크며, 만료기간이 존재하지 않는다. 또한, 단순 문자열을 넘어서 객체 정보 저장이 가능하며, 데이터의 지속성에 따라 로컬 스토리지와 세션 스토리지로 나뉜다.</p>
<p>로컬 스토리지는 브라우저 자체에 반영구적으로 저장되며 브라우저가 종료되어도 데이터가 유지된다. 세션 스토리지는 탭 윈도우 단위로 생성되며, 탭 윈도우를 닫을 때 데이터가 삭제된다.</p>
<hr>
<h2 id="4-jwt">4. JWT</h2>
<p>앞서 말한 HTTP의 특성(Connectionless, Stateless)으로 인해 쿠키, 세션 외에도 JWT 인증 방식을 사용한다.</p>
<p><code>JWT(Json Web Token)</code> 모바일이나 웹의 사용자 인증을 위해 사용하는 암호화된 토큰을 의미한다. JWT 정보를 request에 담아 사용자응 정보 열람, 수정 등 개인적인 작업 등을 수행할 수 있게한다. </p>
<ul>
<li>JWT 구성요소
<code>Header(헤더)</code> : JWT 토큰을 어떻게 해석해야 하는지를 알려주는 부분으로, 토큰의 타입과 해시 암호화 알고리즘으로 구성되어 있다.
<code>Payload(내용)</code> : 토큰에 담을 정보로, 정보의 한조각을 <code>클레임(claim)</code>이라고 부른다. 클레임은 <code>name/value</code>의 한 쌍으로 구성된다. 클레임의 종류는 <code>등록된(registered)클레임</code>, <code>공개(public)클레임</code>, <code>비공개(private)클레임</code> 이 있다.
<code>Signature(서명)</code> : 누군가 JWT를 탈취하여 위변조 했는지 검증하기 위한 부분으로, 헤더의 인코딩 값과 정보의 인코딩 값을 합친 후 비밀키로 해쉬를 하여 생성한다.<br/>
JWT는 <code>.</code>을 구분자로 하여 위 3가지 문자열로 구성된다. Header와 Payload는 암호화 한 것이 아니라 단순히 JSON 문자열을 base64로 인코딩한 것이다. 따라서 디코딩하면 평문으로 해독이 가능하기 때문에 민감한 정보를 넣으면 안된다. 그러나 Signature는 복호화할 수 없다.</li>
</ul>
<p><img src="https://images.velog.io/images/wldus9503/post/73aad6e7-4baa-4167-8535-cdcf1c2162cb/token2.png" alt=""></p>
<p>JWT을 사용하면 토큰 값만 알고 있다면 어떤 서버로 요청이 들어가던 상관이 없다. 즉, 별도의 저장소(세션스토리지)에 정보를 저장해 둘 필요가 없다. 이러한 이유로 CORS 문제도 해결된다. JWT는 어떤 도메인에서도 토큰만 유효하다면 처리가 가능하기 때문이다. 또한, 보안성 쿠키를 전달하지 않아도 되므로 쿠키를 사용함으로써 발생하는 취약점이 사라진다.</p>
<p>하지만, 모든 요청에 대해서 토큰이 전송되므로 토큰에 담기는 정보가 증가할 수록 네트워크 부하가 증가한다. 또한 토큰 자체에 정보를 담고 있기 때문에 JWT가 만료시간 전에 탈취당하면 보안 문제가 발생한다. 그래서 JWE(JSON Web Encryption)를 통해 암호화 하거나 중요데이터를 Payload에 넣어서는 안 된다. 마지막으로 토큰은 한 번 생성되면 임의로 삭제하는 것이 불가능하므로 만료 시간을 꼭 넣어 주어야 한다.</p>
<h4 id="요약-3">요약</h4>
<p>JWT는 모바일이나 웹의 사용자 인증을 위해 사용하는 암호화된 토큰을 말한다. Header(헤더), Payload(내용), Signature(서명) 3가지로 구성되어 있으며, Header와 Payload는 디코딩하면 평문으로 해독이 가능하기 때문에 민감한 정보를 넣으면 안된다.</p>
<p>JWT을 사용하면 토큰 값만 알고 있다면 어떤 서버로 요청이 들어가던 상관이 없다. 또한, 보안성 쿠키를 전달하지 않아도 되므로 쿠키를 사용함으로써 발생하는 취약점이 사라진다. 하지만, 모든 요청에 대해서 토큰이 전송되므로 토큰에 담기는 정보가 증가할 수록 네트워크 부하가 증가한다. 토큰 자체에 정보를 담고 있기 때문에 JWT가 만료시간 전에 탈취당하면 보안 문제가 발생할 수도 있다. 토큰은 한 번 생성되면 임의로 삭제하는 것이 불가능하므로 만료 시간을 꼭 넣어 주어야 한다.</p>
<br>

<blockquote>
<p>참고 자료
📑 <a href="https://blog.imqa.io/webpage_loading_process/">웹 페이지 로딩 과정 이해하기</a>
📑 <a href="https://gloriajun.github.io/frontend/2018/10/23/frontend-reflow-repaint.html">(Frontend) Browser - Reflow &amp; Repaint</a>
📑 <a href="https://intrepidgeeks.com/tutorial/how-the-web-browser-works">[WEB] 브라우저의 동작 원리</a>
📑 <a href="https://mangkyu.tistory.com/98">[WEB] HTTP와 HTTPS의 개념 및 차이점</a>
📑 <a href="https://it-eldorado.tistory.com/90">[Web] 쿠키, 웹 스토리지 (로컬 스토리지, 세션 스토리지)</a>
📑 <a href="https://racoonlotty.tistory.com/entry/%EC%BF%A0%ED%82%A4%EC%99%80-%EC%84%B8%EC%85%98-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EB%A1%9C%EC%BB%AC-%EC%8A%A4%ED%86%A0%EB%A6%AC%EC%A7%80%EC%99%80-%EC%84%B8%EC%85%98-%EC%8A%A4%ED%86%A0%EB%A6%AC%EC%A7%80">쿠키와 세션 그리고 로컬 스토리지와 세션 스토리지</a>
📑 <a href="https://covenant.tistory.com/201">JWT란?</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[호이스팅, 스코프, 클로저, 실행 컨텍스트]]></title>
            <link>https://velog.io/@kim_unknown_/technicalInterview-1</link>
            <guid>https://velog.io/@kim_unknown_/technicalInterview-1</guid>
            <pubDate>Mon, 29 Aug 2022 04:57:02 GMT</pubDate>
            <description><![CDATA[<h2 id="1-호이스팅">1. 호이스팅</h2>
<p><code>호이스팅(Hoisting)</code>은 변수 선언문이 마치 호이스팅 최상단에 끌어올려진 듯한 현상이다. 실제로 선언문이 있는 코드라인을 물리적으로 최상단으로 끌어올린 것이 아니라, 자바스크립트 엔진이 먼저 코드 전체를 스캔하면서 변수를 실행 컨텍스트에 미리 기록해두기 때문에 발생하는 것이다.</p>
<p><code>var</code>, <code>let</code>, <code>const</code> 모두 호이스팅이 발생한다. 하지만 변수 생성 과정의 차이로 인해 값이 할당되기 전에 참조하면 서로 다른 결과를 도출한다.</p>
<p>변수의 생성 단계는 3가지로 나뉜다.</p>
<ol>
<li><strong>선언 단계</strong></li>
<li><strong>초기화 단계</strong></li>
<li><strong>할당 단계</strong></li>
</ol>
<p><code>var</code>는 선언과 초기화가 동시에 진행된다. 이 때 값을 undefined로 초기화하기 때문에 값을 할당하기 전에 참조해도 오류가 나지 않고 undefined 값을 얻는다.</p>
<p>반면 <code>let</code>과 <code>const</code>는 선언 단계와 초기화 단계가 분리되어 진행된다. 호이스팅 되면서 선언단계가 이루어지지만, 초기화 단계는 실제 코드에 도달했을 때 진행된다. 따라서, 실제 코드에 도달하기 전에 먼저 참조를 하게 되면 ReferenceError가 발생한다.</p>
<p>이는 <code>Temporary Dead Zone(TDZ)</code> 때문이다. let과 const로 선언된 변수들은 실제 코드에 도달하기 전까지는 TDZ 영역에 존재하며, 접근할 수 없다. TDZ로 인해 잠재적인 버그를 줄일 수 있다.</p>
<p><span style="font-size:0.9rem">* 변수 선언 코드에 도달하기 전까지의 영역을 변수에 접근할 수 없다는 의미로 Temporary Dead Zone(TDZ)이라고 한다.</span></p>
<p>함수 표현식(<code>const func = () =&gt; {}</code>)은 함수를 변수에 담고 있기 때문에 변수 호이스팅과 동일하게 동작한다.</p>
<p>함수 선언문(<code>function func() {}</code>)은 실행 컨텍스트에 함수 전체가 통채로 저장되기 때문에 함수 이름을 key로 하고 함수 자체를 value로 저장하여 완전하게 초기화된다. 따라서, 완전하게 저장된 상태에서 접근하는 것이기 때문에 변수 선언 이전에 변수에 접근하더라도 변수를 참조할 수 있다.</p>
<h4 id="요약">요약</h4>
<p>호이스팅은 변수 선언문이 마치 호이스팅 최상단에 끌어올려진 듯한 현상이다. 
var로 선언된 변수는 선언과 초기화가 동시에 진행된다. 값을 할당하기 전에 참조해도 오류가 나지 않고 undefined 값을 얻는다.
let과 const로 선언된 변수는 선언과 초기화가 분리되어 진행된다. 실제 코드에 도달하기 전까지는 TDZ 영역에 존재하며, 접근할 수 없기 때문에 ReferenceError가 발생한다.</p>
<hr>
<h2 id="2-스코프">2. 스코프</h2>
<p><code>스코프</code>는 변수의 접근 가능 유효범위로 변수 이름, 함수 이름과 같은 식별자가 선언된 위치에 따라 다른 코드에서 참조 가능 여부가 결정되는 것이다.</p>
<p>전역적으로 선언된 변수는 <code>전역 스코프</code>에, 지역적으로 선언된 변수는 <code>지역 스코프</code>에 있다.
전역스코프와 지역스코프는 서로 계층적으로 연결되어 있는 데, 이것을 <code>스코프 체인</code>이라 한다. 스코프 체인은 물리적으로 존재하며 자바스크립트 엔진은 이 스코프 체인을 통해 변수를 참조한다.</p>
<p>스코프 체인은 무조건 상위(지역-&gt;전역) 스코프로만 올라가면서 참조한다. 자신의 스코프에서 변수를 찾고 해당 변수가 없다면, 상위 스코프로 타고 타고 올라가면서 변수를 찾는다. 이 과정에서 변수를 찾으면 반환하고, 가장 상위인 전역 스코프까지 와서도 끝내 못 찾는다면 ReferenceError를 반환한다.</p>
<p>이렇게 계층적으로 연결되어 있는 스코프 체인 덕분에 하위 스코프에서 상위 스코프에 있는 변수를 참조할 수 있는 것이다.</p>
<p>*생명 주기를 마감한 상위 함수의 변수를 참조할 수 있는 이유 -&gt; 클로저</p>
<p><img src="https://image.toast.com/aaaadh/alpha/2016/techblog/scopchain.png" alt=""></p>
<blockquote>
<p>스코프체인 참조 과정</p>
</blockquote>
<ol>
<li>fooColor를 출력하려할 때 bar 스코프에는 fooColor가 존재하지 않음.</li>
<li>스코프 체인을 타고 foo 스코프로 이동하여 변수를 찾아내고 출력함.</li>
<li>마찬가지로 globalColor를 출력하려할 때 bar 스코프에는 fooColor가 존재하지 않음</li>
<li>스코프 체인을 타고 foo 스코프로 이동하여 변수를 찾음. 하지만, foo 스코프에도 존재하지 않음.</li>
<li>global 스코프로 이동하여 변수를 찾아내고 출력함.</li>
</ol>
<h4 id="요약-1">요약</h4>
<p>스코프는 변수의 접근 가능 유효범위이며, 상위 스코프와 하위 스코프들이 계층적으로 연결되어 있는 것을 스코프 체인이라고 한다. 이 때문에 하위 스코프에서 상위 스코프에 있는 변수를 참조할 수 있는 것이다.</p>
<hr>
<h2 id="3-클로저">3. 클로저</h2>
<p><code>클로저</code>는 함수와 그 함수의 렉시컬 환경의 조합이다. 클로저의 핵심은 스코프를 이용해서, 변수의 접근 범위를 지정하는 것이다.</p>
<p>클로저를 사용하면 스코프에 데이터를 보존하기 때문에 외부 함수의 실행이 끝나더라도 외부 함수 스코프에서 선언된 변수에 접근이 가능하다.
또한, 정보의 접근을 제한할 수 있다. 
클로저 함수를 변수에 할당하면 각자 독립적으로 값을 사용하고 보존할 수 있기 때문에 모듈화에 유리해진다.</p>
<p><img src="https://poiemaweb.com/img/closure.png" alt=""></p>
<h4 id="요약-2">요약</h4>
<p>클로저는 함수와 그 함수의 렉시컬 환경의 조합이다. 클로저의 핵심은 스코프를 이용해서, 변수의 접근 범위를 지정하는 것이다. 클로저를 사용하면 데이터 보존이 가능하고 정보의 접근을 제한할 수 있으며, 모듈화에 유리해진다.</p>
<hr>
<h2 id="4-실행-컨텍스트">4. 실행 컨텍스트</h2>
<p><code>실행 컨텍스트</code>는 코드를 실행하는 데 필요한 환경(조건이나 상태)을 제공하는 객체이며, 식별자를 더욱 효율적으로 결정하기 위한 수단이 된다.</p>
<p>실행 컨텍스트는 <code>Variable Environment</code>, <code>Lexical Environment</code>, <code>ThisBinding</code>을 구성 요소로 가진다.</p>
<ul>
<li>VariableEnvironment : 현재 컨텍스트 내의 식별자들에 대한 정보 + 외부 환경 정보. 선언 시점의 LexicalEnvironment의 스냅샷으로, 변경 사항은 반영되지 않음</li>
<li>LexicalEnvironment : 처음에는 VariableEnvironment와 같지만 변경 사항이 실시간으로 반영됨.</li>
<li>ThisBinding : this 식별자가 바라봐야 할 대상 객체</li>
</ul>
<p>실행 컨텍스트는 동일한 환경에 있는 코드들을 실행할 때 필요한 환경 정보들을 모아 객체를 구성하고, 이를 콜 스택에 쌓아올렸다가, 가장 위에 쌓여있는 컨텍스트와 관련 있는 코드들을 실행한다. 이렇게 해서 전체 코드의 환경과 순서를 보장할 수 있다. 어떤 실행 컨텍스트가 활성화되는 시점에 선언된 변수를 위로 끌어올리고 외부 환경 정보를 구성하고, this 값을 설정하는 등의 동작을 수행한다.</p>
<h4 id="요약-3">요약</h4>
<p><code>실행 컨텍스트</code>는 코드를 실행하는 데 필요한 환경(조건이나 상태)을 제공하는 객체이며, 식별자를 더욱 효율적으로 결정하기 위한 수단이다. 실행 컨텍스트로 전체 코드의 환경과 순서를 보장할 수 있다.</p>
<br>

<blockquote>
<p>참고 자료
📑 <a href="https://hanamon.kr/javascript-%ED%81%B4%EB%A1%9C%EC%A0%80/">[JavaScript] 클로저(Closures)란 무엇일까?</a>
📑 <a href="https://www.youtube.com/watch?v=EWfujNzSUmw">[10분 테코톡] 💙 하루의 실행 컨텍스트</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[회고 - TwoGather]]></title>
            <link>https://velog.io/@kim_unknown_/twogather</link>
            <guid>https://velog.io/@kim_unknown_/twogather</guid>
            <pubDate>Sun, 07 Aug 2022 10:40:33 GMT</pubDate>
            <description><![CDATA[<h3 id="프로젝트-소개">프로젝트 소개</h3>
<ul>
<li><p><strong>프로젝트 주제</strong>: 공간 대여 예약 플랫폼</p>
</li>
<li><p><strong>프로젝트명</strong>: TWOGATHER(투게더)</p>
</li>
<li><p><strong>제작 기간</strong>: 22.07.04 ~ 22.07.24 (3주)</p>
</li>
<li><p><strong>기술 스택</strong>
  프론트엔드: React, JavaScript, Styled-components, Axios
  백엔드: TypeScript, Nest, MySQL, typeORM, enkins, AWS EC2, S3(lambda)
  기타 협업 도구 : Swagger, Postman, Slack</p>
</li>
<li><p><strong>팀 구성</strong>: 프론트엔드(4명) + 백엔드(2명)</p>
</li>
<li><p><strong>구현 기능</strong>
① 회원가입, 로그인, 로그아웃
• 카카오 소셜 회원가입 및 로그인
• 로그인 시 유저 권한에 따라 페이지 접근
• 비밀번호 분실 시, 가입한 이메일로 임시 비밀번호 발송
② 마이페이지
• 내 정보 수정 및 프로필 사진 등록
• 내 예약 내역
• 내가 작성한 리뷰 및 Q&amp;A 목록
③ 공간 조회
• 지역, 공간유형, 공간명, 해시태그 등 키워드로 공간 검색
• 등록순, 가격순 등 공간 조회 필터링
④ 공간 예약
• 공간 이미지 슬라이드
• 공간 설명 및 지도
• 날짜별 예약 불가능한 시간대 필터링
• 시간대별 룸 예약
⑤ 공간 호스팅
• 호스트 등록
• 공간/룸 등록 및 수정, 삭제
• 내 공간 예약 내역 관리
• 내 공간 리뷰 및 Q&amp;A 관리
⑥ 관리자
• 유저 관리
• 전체 예약 내역 관리
• 공지사항</p>
</li>
<li><p><strong>담당 포지션 및 업무</strong>: 프론트엔드 - 공간 상세, 공간 예약, 호스트/어드민 예약관리, 호스트 Q&amp;A 관리</p>
</li>
<li><p><strong>결과물 살짝</strong>
<img src="https://velog.velcdn.com/images/kim_unknown_/post/12d7fe06-84ce-4b0f-b959-c7f44d3ebb01/image.png" alt=""></p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/kim_unknown_/post/8ecab9ea-234b-471e-b7e3-a11006e84d97/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kim_unknown_/post/866cbb1f-8ba9-4566-ab65-c483aa5ccc85/image.png" alt=""></p>
<br/>

<h3 id="프로젝트-중-발생한-이슈-및-해결">프로젝트 중 발생한 이슈 및 해결</h3>
<ol>
<li>컴포넌트 간의 state 공유
개발을 하다보니 하위 컴포넌트들끼리 state를 공유해야 하거나 상위 컴포넌트에서 state를 사용해야 하는 상황을 마주하게 되었다. state 끌어올리기에 대해서 잘 모르고 있던 상태였는데, 구글링 해보니 공식 문서에도 있을 만큼 자주 사용되는 개념이었다. 생소한 개념이다보니 어떻게 적용시켜야하나 고민이 많았는데 유튜브에 설명이 잘 되어있는 영상이 있어서 그걸 참고하여 적용시켰다.
막상 이해하고 보니 어려운 개념이 전혀 아니었다. 내가 속도가 빠른 편이었어서 팀원들 중에서 가장 먼저 state 끌어올리기를 적용시킨 것이었는 데 이후 팀원들도 나와 같은 상황에 마주했을 때 내가 해결 방법을 설명해줄 수 있었다. 먼저 공부한 내용을 다른 팀원들에게 알려주어 도움을 줄 수 있어서 매우 뿌듯했다!<br/>  </li>
<li>setState 무한 루프에 빠지다.
 공간 상세 페이지에서 해당 공간의 상세 룸들의 정보를 뿌려줘야 했다. 그래서 map 안에서 api를 요청하여 그 결과를 setState로 저장했더니 무한 루프에 빠져버리는 문제가 발생했다. 내가 의도한 것은 룸의 개수만큼만 api를 요청하는 것이었는 데 api 요청이 끝없이 발생하는 것이었다. 무한루프는 처음 마주해봤는 데 해결방법을 몰라 패닉에 빠졌었다.😱
 오랜 시간동안 삽질을 하고, 팀원분들과 이 문제에 대해서 같이 머리를 싸매고 고민한 결과 다음과 같은 방식으로 겨우 해결할 수 있었다. 우선 첫 렌더링 시에만 api 요청을 하도록 useEffect를 사용하였다. 그런데 이것만으로는 해결이 되지 않았고, api 요청이 map 안에만 들어가면 무한루프에 빠지는 현상이었기 때문에 api 요청을 바깥으로 분리하여 state로 관리하였다. 그리고 map 안에서 해당 state를 불러서 사용하는 방식으로 분기하였다.</li>
</ol>
<br/>

<h3 id="소감">소감</h3>
<p>리액트를 처음 배우고 첫 프로젝트였기 때문에 시작할 때 걱정이 컸다. 나와 마찬가지로 다른 프론트엔드 팀원들도 리액트가 처음인 상황이었다. 그래서 첫 회의때 다른 새로운 기술을 도입하기 보다는 리액트에 집중해서 개발하는 게 어떻겠냐고 제안했고, 팀원분들도 모두 좋다고 해주셔서 리액트로만 개발하게 되었다. 덕분에 프로젝트를 진행하면서 리액트를 제대로 적용시켜볼 수 있었고, 역시 직접 해보니 더욱 이해가 잘 되었다.</p>
<p>이번 프로젝트는 저번 프로젝트보다 개발 기간이 1주가 더 길었지만, 기획부터 우리가 다 해야했기 때문에 사실상 주어진 시간이 많지 않았다. 그래서 첫 주차부터 새벽까지 코딩을 하면서 정말 바쁜 3주를 보냈다. 잠도 잘 못 자는 상태에서 하루종일 코딩을 하다보니 팀원들이 예민해져서 서로 날카로워지기도 했었다. 좋지 않은 상황에서도 팀원들 모두 다 열심히 해주었고 마지막날까지 밤샌 끝에 완성할 수 있었다.</p>
<p>이번 프로젝트를 하면서 저번 프로젝트에 비해서 내 개발 실력이 많이 늘었다는 것을 느낄 수 있었다. 속도도 내가 가장 빠르다보니 다른 팀원들을 더 많이 도와줄 수 있었다. 뭔가 문제가 생겼을 때 같이 코드를 보면서 해결하는 등 팀원들을 도와주는 상황이 많았었다. 물론 나도 난관에 봉착했을 때 다른 팀원들에게 도움을 얻을 수 있었다. 서로서로 도와가면서 개발하는 분위기가 정말 좋았던 것 같다. 팀에 도움이 될 수 있는 사람이어서 행복했다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[부트캠프 13Week]]></title>
            <link>https://velog.io/@kim_unknown_/elice-13week</link>
            <guid>https://velog.io/@kim_unknown_/elice-13week</guid>
            <pubDate>Mon, 04 Jul 2022 13:37:14 GMT</pubDate>
            <description><![CDATA[<h3 id="--220627--220628">- 220627 ~ 220628</h3>
<p>서버사이드 렌더링 복습</p>
<blockquote>
<ul>
<li>렌더링 방식
<code>CSR</code> - Client Side Rendering
<code>SSR</code> - Server Side Rendering
<code>SSG</code> - Static Site Generation
<code>ISR</code> - Incremental Static Regeneration<br/>
</li>
</ul>
</blockquote>
<ul>
<li><code>Client Side Rendering</code> : useEffect
렌더링이 클라이언트 측에서 발생.
자바스크립트 코드가 많으면 앱 로딩이 느려짐
SEO에 취약</li>
<li><code>Server Side Rendering</code> : <code>getServerSideProps</code>
서버쪽에서 렌더링 준비를 끝마친 상태로 클라이언트에 전달
SEO에 유리<br/></li>
<li><code>next js</code> - React로 만드는 서버사이드 렌더링 프레임워크</li>
<li><code>yarn dev</code> <code>npm run dev</code> 개발 환경 세팅</li>
<li><code>yarn build</code> <code>npm run build</code> 실서버 배포 버전 생성</li>
<li><code>yarn build</code> <code>npm run start</code> 실서버 구동<br/></li>
<li>pages/index.js는 엔트리 파일</li>
<li>nextjs는 라우팅을 따로 처리해주지 않아도 자동으로 설정됨</li>
<li>id는 가변적인 파라미터로 처리. <code>useRouter</code>를 통해서 id 값에 접근할 수 있음<pre><code class="language-jsx">import {useRouter} from &#39;next/router&#39;;
export default function Topic() {
const  router = useRouter();
const id = router.query.id;
return &lt;&gt;/sub/{id}.js&lt;/&gt;
}</code></pre>
</li>
<li><code>Link</code> : 페이지 리로드 없이 페이지를 전환하도록 돕는 컴포넌트<pre><code class="language-jsx">&lt;Link href=&quot;/sub&quot;&gt;Index&lt;/Link&gt;</code></pre>
</li>
<li>nextjs는 express 처럼 백엔드 기능이 내장</li>
<li>/pages/api 폴더는 server side api를 위한 전용공간<pre><code class="language-js">export default (req, res) =&gt; {
const { id } = req.query;
const topic = topics.filter(e =&gt; e.id === Number(id))[0]
res.status(200).json(topic);
};</code></pre>
</li>
<li>Vercel 이용해서 git repository 끌어와서 배포</li>
<li>Freenom으로 무료 도메인 생성</li>
</ul>
<br/>

<h3 id="--220629--220631">- 220629 ~ 220631</h3>
<p>여태까지 배운 내용 정리하고 스타일 컴포넌트 복습</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[회고 - EliceBucks]]></title>
            <link>https://velog.io/@kim_unknown_/elicebucks</link>
            <guid>https://velog.io/@kim_unknown_/elicebucks</guid>
            <pubDate>Sat, 25 Jun 2022 18:08:06 GMT</pubDate>
            <description><![CDATA[<p>1차 프로젝트를 마친 후 바빠서 미뤘던 프로젝트 회고를 작성해보고자 한다.</p>
<h3 id="프로젝트-소개">프로젝트 소개</h3>
<ul>
<li><strong>프로젝트 주제</strong> - 캡슐커피 쇼핑몰</li>
<li><strong>프로젝트명</strong> - ${elicebucks} (엘리스벅스)</li>
<li><strong>제작 기간</strong> - 220523 ~ 220605 (2주)</li>
<li><strong>기술 스택</strong> - <code>Vanilla js</code>, <code>Node js</code>, <code>Express</code>, <code>Mongo DB</code> 등</li>
<li><strong>팀 구성</strong> - 5명 (프론트엔드 3명 + 백엔드 2명)<br/></li>
<li><strong>구현 기능</strong>
  ①  회원가입, 로그인, 회원정보 수정 및 탈퇴 등 사용자 관련 CRUD
  ② 카테고리 관련 CRUD, 제품 관련 CRUD, 주문 관련 CRUD
  ③ 장바구니 관련 기능을 프론트 단에서 수행
  ④ 관리자 페이지
  ⑤ 추가 개선 기능 (공지사항, 페이지네이션, 쇼핑몰 현황, 로딩)<br/>    </li>
<li><strong>담당 포지션</strong>
  프론트엔드
  팀장<br/>    </li>
<li><strong>담당 업무</strong>
  메인
  상품 목록
  상품 카테고리
  상품 상세
  +) 공지사항
  +) 페이지네이션<br/>    </li>
<li><strong>결과물 살짝 ⬇️</strong>
<img src="https://user-images.githubusercontent.com/73158122/173199442-af986401-fc49-4200-906b-8b9470d31553.png" alt="메인">
<img src="https://user-images.githubusercontent.com/73158122/173199449-fc8226ef-9261-4501-a273-450bec5b85de.png" alt="상품">
<img src="https://user-images.githubusercontent.com/73158122/173199454-4574b17c-d9a6-4967-88df-2a119a17b6d8.png" alt="상세"></li>
</ul>
<br/>

<h3 id="프로젝트-중-발생한-이슈-및-해결">프로젝트 중 발생한 이슈 및 해결</h3>
<ol>
<li>컴포넌트 임포트 실패
우리 팀은 상단바 특정 부분(로그인, 로그아웃)을 따로 컴포넌트로 빼놓고 임포트해서 사용하는 식으로 구현을 했다. 그런데 일부 파일에서 임포트가 되지 않는 문제가 발생했다. 상대 경로로 ( ../url ) 임포트를 하고 있었는 데 해당 경로가 먹히지 않고 있었다. 이 부분에 대해서 원인을 알아보니 서버 사이드 렌더링이 아닌 클라이언트 사이드 렌더링이라서 클라이언트 브라우저 상 경로로 찾아가줘야 하는 것이었다. 그래서 ( /url ) 이나 (./../url) 이런식으로 임포트를 하니 문제가 해결되었다.<br/></li>
<li>페이지네이션 처리 영역 구분
페이지네이션을 프론트와 백엔드 중 어디서 처리할 지에 대한 이슈도 있었다. 처음에 나는 프론트엔드에서 처리하는 것이 더 낫지 않나 생각했었다. 백엔드에서 페이지네이션을 처리하면 페이지 목록을 클릭할 때마다 url 쿼리가 변경되기 때문에 매번 재렌더링 되는 부분이 다소 어색하다고 느껴졌기 때문이다. 페이지네이션은 같은 페이지를 단순히 나눠서 보여주는 것이기 때문에 페이지 목록을 이동을 하더라도 재렌더링되지 않고 같은 페이지에 머물고 있다는 느낌을 줘야한다고 생각이 들었다. <br/>
이 부분에 대해서 백엔드 담당자분과 논의를 해보다가 결국은 결론이 나지 않아 코치님께 조언을 구했다. 코치님께서는 프론트에서 모두 처리하게 되면 프론트가 모든 데이터를 계속 보관하고 있어야하기 때문에 서버 성능에 무리가 갈 것이라고 말씀하셨다.
코치님의 조언을 듣고 서버의 성능을 위해 백엔드에서 처리하는 방법으로 결정하게 되었다.<br/>
프론트엔드 담당인 나는 단순히 유저 경험만을 고려하고 있었다는 사실을 깨달았고 프로젝트를 만들 때 서버 성능을 고려하는 것도 중요하다는 것을 잊지 말아야겠다고 생각했다.<br/></li>
<li>백엔드와의 협업
팀원 분중에 이번이 코딩이 처음이신 분이 계셨었다. 그 분이 백엔드 상품 DB 담당이셨고, 나는 상품 목록, 상세 등을 담당했었기 때문에 그 분과 교류가 정말 많을 수 밖에 없었다. 처음이신걸 고려하고 어려움이 많으실 것 같아 도움을 드릴 수 있는 부분은 많이 도와드리려 했었다! 깃 사용법을 알려드린다던가, 개발을 하다가 자잘한 오류를 발견하여 담당자분께 알려드리는 등 조금씩 도와드리면서 개발을 진행을 했었다.<br/>
하루는 백엔드 담당자분께서 데이터베이스 관련으로 도움을 요청하셔서 함께 화상으로 코드를 뜯어보면서 이것저것 시도해보았지만 이상하게 잘 해결이 되지 않았다. 나는 그 과정에서 뭔가 백엔드 부분이 전체적으로 꼬여있는 듯한 느낌을 받았다..! 백엔드 3계층 구조를 모두 왔다갔다 하며 원인을 확인한 결과 함수를 엉뚱한 이름으로 불러와서 사용하고 계시는 등의 실수가 원인이었다. 3계층으로 개발을 하다보니 혼동이 있으셨던거 같다. 그렇게 3시간동안의 회의 끝에 원인을 찾고 오류를 잘 해결할 수 있었다!👏<br/>
나는 이번 일을 통해 프론트엔드가 백엔드의 지식도 어느정도 알아야겠다는 생각이 들었다. 오류가 발생했을 때 프론트엔드의 문제인지 백엔드의 문제인지 구분이 잘 안될 때가 있는 데, 백엔드를 어느정도 알면 구분이 쉬워지기 때문이다! 또한, 내가 생각했던거보다 프론트엔드는 다른 담당자분들과 교류가 정말정말 많았다. 그런 이유에서도 원활한 의사소통을 위해 어느정도는 알아야겠다는 생각이 들었다.</li>
</ol>
<br/>

<h3 id="소감">소감</h3>
<p>제대로 된 팀 프로젝트는 이번이 처음이었던지라 많이 떨리고 부담감이 컸다. 그리고 얼떨결에 팀장까지 맡게 되어서 임무가 더 막중해진 기분이었다. 팀장인데 내가 맡은 1인분도 제대로 못 해내면 정말 팀원들 보기 부끄럽겠다는 생각이 들어서 부담이 너무 컸다🥲
부담감을 껴안고 프로젝트를 진행하다보니 뭔가 제대로 해결이 안 될 때마다 울고 싶기도 하고 이걸 못 해내면 안된다는 생각이 머릿속을 지배했다. (덕분에 꿈 속에서도 오류를 해결하고 있었다.)</p>
<p>시간 안에 마무리 해야한다는 압박감이 커서 2주 동안 잠도 제대로 못 자고 밥 먹는 시간도 아껴가면서 개발을 했다. 프로젝트를 진행하는 2주는 정말 역경과 고난의 연속이었지만, 프로젝트가 마무리 되어갈 쯤에는 전체적인 그림이 나오고 끝이 보이기 시작하니 정말 뿌듯했다. 그리고 팀원분들 모두 묵묵히 자기 역할을 다 잘해주셔서 완성할 수 있었던 거 같다.</p>
<p>팀프로젝트를 한 번 경험해보니 감이 잡혀서 다음 프로젝트는 이번보다는 익숙하게 할 수 있을 것 같다. 그리고 나에게 항상 깃은 어려운 존재였어서 늘 쓰던 기능만 썼었다. (add, commit, push...) 하지만, 이번 팀프로젝트를 진행하면서 깃에 대한 장벽을 뚫을 수 있었다.</p>
<p>프로젝트 시작 전에 깃을 익혀놔야겠다 싶어서 조금 만져보니 금방 익힐 수 있었고 깃을 헤매는 팀원분들께 사용법을 알려드리기도 했다! (뭔가 뿌듯...ㅎㅎ) 처음에 conflict 날 때 정말 당황스러웠는데 계속 하다보니 나중에는 conflict가 나도 능숙하게 해결할 수 있었다.</p>
<p>이번 경험을 통해 프로젝트에 대한 두려움을 뿌시고 많이 성장할 수 있었던 거 같다. 개발은 직접 해봐야 늘 수 있다는 것을 뼈저리게 느꼈다. 앞으로 시간 날 때 팀프로젝트나 개인프로젝트를 더 많이 해봐야할 것 같다.</p>
<p><img src="https://blog.kakaocdn.net/dn/dsXrza/btroasUAubU/a4ag2QKFY0dBrncWpwLOR1/img.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[부트캠프 12Week]]></title>
            <link>https://velog.io/@kim_unknown_/elice-12week</link>
            <guid>https://velog.io/@kim_unknown_/elice-12week</guid>
            <pubDate>Sat, 25 Jun 2022 15:34:45 GMT</pubDate>
            <description><![CDATA[<h3 id="--220620--220621">- 220620 ~ 220621</h3>
<p>crud의 전체적인 내용 정리와 상태 관리 훅에 대해서 학습했다. 온라인 강의 실습 내용이 유독 어려웠는 데, 기본 개념이 제대로 학습되지 않은 상태에서 코드를 짜려하니 너무 힘들었다. 그래서 결국은 혼자 블로깅 하면서 따로 개념 학습을 했다.🥲
상태관리 훅이 이렇게 많이 있는지 몰랐는 데, 각 훅들의 특성을 잘 알고 상황에 따라 잘 사용해야겠다는 생각이 들었다. 잘못하면 성능이 되려 나빠질 수도 있기 때문에..</p>
<blockquote>
<ul>
<li>useEffect는 promise를 입력값으로 받지 않는다.</li>
</ul>
</blockquote>
<ul>
<li>Link의 to는 click했을 때 해당 url로 변경하라는 의미 -&gt; a 링크와 동일</li>
<li>Route의 path는 바뀐 url과 매치되는 컨포넌트를 렌더링<br></li>
<li><code>useRef</code>는 useState와 달리 ref의 값이 변경되어도 컴포넌트가 재렌더링되지 않는다. useRef 함수는 current 속성을 가지고 있는 ref객체를 반환한다.<br></li>
<li><code>useContext</code>는 state를 컴포넌트 간에 전역적으로 공유하여 사용할 수 있다. <code>createContext</code>로 context 객체를 생성할 수 있고, 생성된 context 객체는 <code>Provider</code>를 통해 하위 컴포넌트에게 전달될 수 있다.</li>
<li><code>context = createContext(default value)</code> 컨텍스트 생성</li>
<li><code>&lt;context.Provider value={provider value}&gt;</code> 컨텍스트로부터 컴포넌트 생성</li>
<li><code>contextValue = useContext(context)</code> context의 값을 불러옴 -&gt; 가까운 Provider의 영향을 받음<br></li>
<li><code>useReducer</code>는 useState와 똑같이 상태 관리를 하지만, 보다 복잡한 상태 관리에 유리하다.</li>
<li>다음 state가 이전 state에 의존적인 경우에 보통 useState보다 useReducer를 사용하는 것이 좋다.</li>
<li><code>const [state, dispatch] = useReducer(reducer, initialArg, init)</code></li>
<li>useReducer를 은행으로 비유<pre><code class="language-js">const initialState = {count: 0};
// reducer는 은행
// state는 현재 장부 값, action은 사용자가 시킨 행동
function reducer(state, action) {
switch (action.type) {
  case &#39;increment&#39;:
    // return 값이 state 값이 됨
    return {count: state.count + 1};
  case &#39;decrement&#39;:
    return {count: state.count - 1};
  default:
    throw new Error();
}
}
// state는 장부 값
// dispatch는 창구 직원, dispatch를 부르면 reducer 함수 실행
// initialState는 state의 초기값
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
  &lt;&gt;
    Count: {state.count}
    &lt;button onClick={() =&gt; dispatch({type: &#39;decrement&#39;})}&gt;-&lt;/button&gt;
    &lt;button onClick={() =&gt; dispatch({type: &#39;increment&#39;})}&gt;+&lt;/button&gt;
  &lt;/&gt;
);
}</code></pre>
</li>
</ul>
<br>

<h3 id="--220622--220623">- 220622 ~ 220623</h3>
<p>리덕스에 대해서 배웠는 데 너무너무너무 어려웠다. 아직 상태관리 훅에도 익숙해지지 않았는 데 리덕스까지 배우려니 너무 힘들었다.. 리덕스랑 리액트 리덕스랑 리덕스 툴킷을 한 번에 배우려니 너무 혼동이 왔다😱 그래도 리덕스는 중요한 개념이니 잘 챙겨가야겠다는 생각이 들었다.</p>
<blockquote>
<ul>
<li>Redux: 전역적으로 상태 관리</li>
</ul>
</blockquote>
<ul>
<li><code>npm install redux react-redux</code> redux 설치</li>
<li>하나의 store에 모든 state를 저장하고 관리 -&gt; createStore</li>
<li><code>import { createStore } from &quot;redux&quot;;</code></li>
<li><code>const store = createStore(reducer, initialState);</code>;</li>
<li><code>import store from &quot;./Store&quot;;</code>
Store.js<pre><code class="language-js">import { createStore } from &#39;redux&#39;;
const reducer = (state, action) =&gt; {
if (action.type === &#39;up&#39;) {
  return { ...state, value: state+value + action.step };
}
return state;
};
const store = createStore(reducer, {value: 0});
export default store;</code></pre>
</li>
<li>react-redux로 리액트와 리덕스 연결 필요<br/></li>
<li>Provider는 Store를 전역적으로 사용할 수 있게 해줌</li>
<li><code>import { Provider } from &quot;react-redux&quot;;</code></li>
<li><code>&lt;Provider store={store}&gt;</code> store를 전역적으로 공급
index.js<pre><code class="language-jsx">import store from &quot;./Example/ExStore&quot;;
import { Provider } from &quot;react-redux&quot;;
// 최상위에서 Provider로 컴포넌트들을 감싸줘야 함
root.render(
&lt;Provider store={store}&gt;
  &lt;App /&gt;
&lt;/Provider&gt;
);</code></pre>
</li>
<li>useSelector는 상태를 읽을 때 사용, useDispatch는 상태를 변경할 때 사용, reducer는 상태 변경을 처리하는 함수</li>
<li><code>import { useSelector, useDispatch } from &quot;react-redux&quot;;</code></li>
<li><code>useSelector((state) =&gt; state.value);</code> 값을 읽을 때 사용<br/></li>
<li><code>npm install @reduxjs/toolkit</code> redux toolkit 설치</li>
<li>스토어를 분리해서 사용 -&gt; slice</li>
<li><code>createSlice</code> slice 생성</li>
<li><code>configureStore</code> slice를 합친 store 생성 -&gt; createStore 대체<pre><code class="language-js">import { configureStore } from &quot;@reduxjs/toolkit&quot;;
import countUp from &quot;./countUpSlice&quot;;
import countDown from &quot;./countDownSlice&quot;;
const store = configureStore({
reducer: {
  countUp: countUp.reducer,
  countDown: countDown.reducer
}
});
export default store;</code></pre>
</li>
<li><code>createAction</code> 액션 생성 함수 생성</li>
<li><code>createReducer</code> reducer 함수 생성</li>
<li><code>createSelector</code> state 값 가져오기 -&gt; useSelector 대체</li>
<li>Action의 <code>payload</code>는 내용이라는 뜻</li>
</ul>
<br>

<h3 id="--220624">- 220624</h3>
<p>테스팅은 처음 학습해봤는 데 처음부터 끝까지 초면인지라 처음엔 감잡기가 힘들었는데 실습 문제를 몇번 풀어보니 대충 이런거구나 감을 잡을 수 있었다. 이번주에 배운 내용들은 전체적으로 어렵지만 하나도 버릴게 없고 중요한 내용들이었다. 힘들지만 얼마 안남았으니 파이팅해야겠다..!</p>
<blockquote>
<ul>
<li>테스트 종류 3가지
• <code>유닛(Unit) 테스트</code> : 프로젝트의 기능을 잘게잘게 쪼개서 테스트
• <code>통합(Integrated) 테스트</code> : 기능단위로 묶어서 잘 작동하는지 테스트
• <code>E2E(End-to-End) 테스트</code> : 프로젝트가 브라우저 위에서 잘 동작하는지 사용자 관점에서 테스트</li>
</ul>
</blockquote>
<ul>
<li>테스팅 용어
• <code>Mocking</code> : 특정 동작을 흉내 내는 것
• <code>Stubbing</code> : 더미를 채워 넣는 것</li>
<li>테스팅 구성 3가지
• <code>setup</code> : 테스트를 위한 환경 생성
• <code>expectation</code> : 테스트를 위한 코드를 작성
• <code>assertion</code> : 테스트 결과가 원하는 대로 나왔는지를 검증<br></li>
<li>리액트 테스팅 툴 - <code>jest</code>, <code>react-testing-library</code><br></li>
<li><strong>jest</strong>
• Assertion Matchers : <code>toBe()</code> <code>toEqual()</code> <code>toContain()</code> <code>toMatch()</code> <code>toThrow()</code>
• Async assertion : <code>test()</code> <code>done()</code>
• Mock functions : <code>jest.fn()</code> <code>jest.mock()</code> <code>mockReturnValueOnce()</code> <code>mockResolvedValue()</code>
• Mock functions assertion : <code>toHaveBeenCalled</code> <code>toHaveBeenCalledWith</code> <code>toHaveBeenLastCalledWith</code>
• Lifecycle functions : <code>beforeEach</code> <code>afterEach</code> <code>beforeAll</code> <code>afterAll</code>
• Grouping : <code>describe()</code> <code>test()</code>
• Snapshot testing : <code>toMatchSnapshot()</code> <code>toMatchInlineSnapshot()</code><br></li>
<li><strong>react-testing-library</strong>
Query - 렌더링 된 DOM 노드에 접근하여 엘리먼트를 가져오는 메서드
• <code>get</code> - 동기적으로 처리되며 타겟을 찾지 못할 시 에러를 던짐
<em>getBy-, getAllBy-</em>
• <code>find</code> - 비동기적으로 처리되며 타겟을 찾지 못할 시 에러를 던짐 
<em>findBy-, findAllBy-</em>
• <code>query</code> - 동기적으로 처리되며 타겟을 찾지 못할 시 null을 반환 
<em>queryBy-, queryAllBy-</em>
• dom 테스팅 - <code>toBeInTheDocument()</code>, <code>toHaveValue()</code>, <code>toBeDisabled()</code>, <code>toBeVisible()</code>
⇨ <code>ByRole</code> <code>ByText</code> <code>ByPlaceholderText</code> <code>ByLabelText</code> <code>ByDisplayValue</code> <code>ByAltText</code> <code>ByTitle</code>, <code>ByTestId</code>
이벤트 - <code>userEvent</code>, <code>fireEvent</code>, <code>createEvent</code></li>
</ul>
]]></description>
        </item>
    </channel>
</rss>