<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>dev4jaehun_kim.log</title>
        <link>https://velog.io/</link>
        <description>개발하면서 새롭게 배운 내용, 시행착오한 내용들을 잊지 않기 위해 기록합니다.</description>
        <lastBuildDate>Sun, 27 Aug 2023 09:28:27 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>dev4jaehun_kim.log</title>
            <url>https://velog.velcdn.com/images/dev4jaehun_kim/profile/38ca6355-c7c6-4c93-bf3b-90adf7bd70a1/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. dev4jaehun_kim.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dev4jaehun_kim" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[GCP Kubernetes Pod의 외부 IP 알아내기]]></title>
            <link>https://velog.io/@dev4jaehun_kim/GCP-Kubernetes-Pod%EC%9D%98-%EC%99%B8%EB%B6%80-IP-%EC%95%8C%EC%95%84%EB%82%B4%EA%B8%B0</link>
            <guid>https://velog.io/@dev4jaehun_kim/GCP-Kubernetes-Pod%EC%9D%98-%EC%99%B8%EB%B6%80-IP-%EC%95%8C%EC%95%84%EB%82%B4%EA%B8%B0</guid>
            <pubDate>Sun, 27 Aug 2023 09:28:27 GMT</pubDate>
            <description><![CDATA[<p>GCP, AWS 등 클라우드 환경에 배포된 Kubernetes Cluster의 외부 IP를 알아내는 방법에는 다양한 방법이 있습니다.</p>
<p>이 중에서 클러스터가 다중 리전, 다중 존에 배포되어 있는 게 아닐 경우 간단하고 빠르게 외부 IP를 확인하는 방법에 대해 작성해보겠습니다.</p>
<ol>
<li>Pod 터미널에 접속해서 curl, wget 등 HTTP 요청을 보낼 수 있는 CLI 도구를 하나 설치합니다.</li>
<li>다음과 같이 요청 시 요청한 곳의 IP 주소를 반환하는 서버에 GET 요청을 보냅니다.<pre><code class="language-bash"> curl https://api.ipify.org</code></pre>
</li>
</ol>
<p>위 방식으로 간단하게 IP 주소를 확인할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Kubernetes CronJob에서 kubectl로 리소스 제어 자동화하기]]></title>
            <link>https://velog.io/@dev4jaehun_kim/Kubernetes-CronJob%EC%97%90%EC%84%9C-kubectl%EB%A1%9C-%EB%A6%AC%EC%86%8C%EC%8A%A4-%EC%A0%9C%EC%96%B4-%EC%9E%90%EB%8F%99%ED%99%94%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dev4jaehun_kim/Kubernetes-CronJob%EC%97%90%EC%84%9C-kubectl%EB%A1%9C-%EB%A6%AC%EC%86%8C%EC%8A%A4-%EC%A0%9C%EC%96%B4-%EC%9E%90%EB%8F%99%ED%99%94%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 13 Aug 2023 07:09:37 GMT</pubDate>
            <description><![CDATA[<p>Kubernetes(이하 k8s)에서 어떠한 작업을 수행하기 위해 자동으로 사전에 선언해둔 형태로 pod을 만들고, 내부에서 원하는 작업을 수행할 수 있습니다. 이것을 Job이라고 하는데요. 주기적으로 Job을 생성하는 CronJob의 형태로 생성할 수도 있습니다.</p>
<p>하지만 k8s 공식 문서에서는 단편적인 예제만 제공하고, 각 속성에서 어떤 값을 사용할 수 있는지 파악하기 어려웠습니다. 이에 기록의 필요성을 느껴 글을 작성하게 되었습니다.</p>
<h1 id="원인-파악">원인 파악</h1>
<p>서비스 동작 후 3-4시간이 지난 시점에 동작의 문제가 발생하며, 재시작하면 문제가 일시적으로 해결되는 상황입니다.</p>
<p>해당 서비스의 특성상 재시작으로 인한 데이터 유실은 걱정하지 않아도 되었으므로, 재현 환경을 별도로 구성한 뒤 개발 단에서 원인 파악 및 해결이 완료될 때까지 2시간마다 재시작을 반복하기로 했습니다.</p>
<h1 id="cronjob-작성하기">CronJob 작성하기</h1>
<h2 id="업무-절차-파악하기">업무 절차 파악하기</h2>
<p>이해하기 쉬운 CronJob을 작성하려면, 각 과정에서 해야 하는 작업을 명확히 하는 것이 좋습니다. 우리 서비스에서 &#39;재시작&#39;이 의미하는 바는 다음과 같습니다.</p>
<ol>
<li>Pod을 제어하는 Deployment에서 Scale을 &#39;0&#39;으로 설정합니다.</li>
<li>Pod이 캐시를 저장하는 데 사용하는 redis key를 모두 제거합니다.
(<code>flush all</code> 명령으로 제거할 수 있습니다.)</li>
<li>Deployment에서 Scale을 다시 &#39;1&#39;로 설정합니다.</li>
</ol>
<h2 id="명령어로-테스트하기">명령어로 테스트하기</h2>
<p>특수한 경우가 아니라면 명령어로 목표한 목적을 실행할 수 있어야 자동화가 간편합니다. 이에 각 절차를 한 줄의 명령으로 실행하여 잘 동작하는지 검증합니다.</p>
<h3 id="pod-스케일-조정하기">Pod 스케일 조정하기</h3>
<p><code>kubectl</code>이라고 하는 k8s 설정을 위한 CLI 도구가 존재하는데, 이를 이용하면 Pod의 스케일을 조정하는 작업을 손쉽게 할 수 있습니다.</p>
<pre><code class="language-bash"># 스케일을 0으로 설정
# =&gt; 해당 Deployment가 제어하는 Pod이 0개가 되게 함(즉 모두 삭제)
kubectl scale deployment &#39;서비스의 Deployment 이름&#39; -n &#39;네임스페이스 이름&#39; --replicas=0

# 스케일을 1로 설정
# =&gt; 해당 Deployment가 제어하는 Pod을 1개 배포
kubectl scale deployment &#39;서비스의 Deployment 이름&#39; -n &#39;네임스페이스 이름&#39; --replicas=0</code></pre>
<h3 id="redis-캐시-모두-지우기">redis 캐시 모두 지우기</h3>
<p>kubectl은 <code>exec</code> 명령을 이용해 특정 pod에 연결하거나, 명령을 실행할 수 있습니다.</p>
<p>이를 확인하기 위해 우선 redis 캐시를 모두 지우는 과정이 어떻게 진행되는지 정리합니다. 저는 Lens라고 부르는 GUI 앱을 사용하고 있으며, 이를 통해 pod shell에 접근할 수 있어 접속하는 과정은 생략합니다.</p>
<pre><code class="language-bash"># pod에서 redis-cli로 접근
redis-cli
# redis-cli가 켜지면 아래 명령어 실행
flushall</code></pre>
<p>이제 실행해야 하는 명령어들을 확인했으므로 이를 kubectl에 그대로 옮겨줍니다.</p>
<pre><code class="language-bash">kubectl exec &#39;redis pod 이름&#39; -n &#39;네임스페이스 이름&#39; -- redis-cli flushall</code></pre>
<h2 id="yaml-파일로-옮기기">yaml 파일로 옮기기</h2>
<p>각 행위를 아래와 같이 yaml 파일의 형태로 작성합니다.</p>
<pre><code class="language-yaml"># CronJob을 작성하기 위해 종류(kind)를 CronJob으로 선언하고,
# apiVersion은 `batch/v1`을 사용합니다.
kind: CronJob
apiVersion: batch/v1
metadata:
  name: restart-cronjob # 해당 CronJob의 이름을 설정합니다.
spec:
  # schedule에 반복할 주기를 설정합니다.
  schedule: &quot;0 */2 * * *&quot;  # 2시간마다 실행
  jobTemplate:
    spec:
      # 실패할 때마다 다시 시도할 횟수를 설정합니다.
      # 적은 숫자만큼 Pod을 계속 생성하기 때문에, 0으로 설정했습니다.
      backoffLimit: 0
      # CronJob이 성공해도 기본적으로 CronJob이 동작하면서 생성한 Job과 Pod들은 제거되지 않습니다.
      # ttl 값을 설정하면 입력한 숫자(단위: 초)만큼 시간이 지난 후 작업을 위해 생성됐던 Job과 Pod이 제거됩니다.
      ttlSecondsAfterFinished: 30
      template:
        spec:
          # pod이 kubectl 명령어를 통해 자신이 속한 클러스터에서 작업을 실행하려면 권한이 필요합니다.
          # 필요한 권한이 설정된 서비스 계정 이름을 입력합니다.
          serviceAccountName: default
          # Job이 실패했을 때 반복할지 여부를 입력합니다. Never 또는 OnFailure만 설정할 수 있습니다.
          restartPolicy: Never
          # 작업을 실행할 컨테이너들을 순서대로 입력합니다.
          containers:
          - name: scale-down-service
            # kubectl 명령어가 기본으로 제공되는 도커 이미지를 이용합니다.
            image: bitnami/kubectl:latest
            command:
            - /bin/sh
            - -c
            - kubectl scale deployment example-deployment -n example-namespace --replicas=0
          - name: redis-flush-all
            image: bitnami/kubectl:latest
            command: 
            - /bin/sh
            - -c
            - kubectl exec example-redis -n redis -- redis-cli flushall
          - name: scale-up-service
            image: bitnami/kubectl:latest
            command: 
            - /bin/sh
            - -c
            - kubectl scale deployment aggregator-worker -n orakl --replicas=1</code></pre>
<h1 id="계정-생성하기">계정 생성하기</h1>
<p>위에 최종적으로 작성한 yaml 파일을 유심히 보면, kubectl 명령을 실행하기 위한 권한들이 있음을 알 수 있습니다. 저는 네임스페이스 생성 시 기본적으로 생성되는 서비스 계정인 <code>default</code> 사용자에게 필요한 권한들을 부여하여 해결했습니다.</p>
<p>k8s에서는 권한과 권한 할당, 사용자가 모두 분리되어 있는데, 이를 통해 중복되는 권한을 하나로 묶어 통일하고 이를 여러 사용자에게 할당할 수 있습니다.</p>
<p>이렇게 하나로 묶인 것을 역할(Role)이라 부르고, RoleBinding을 통해 사용자에게 Role을 할당할 수 있습니다. 하지만 하나의 Role은 자신이 속한 네임스페이스에서만 권한을 행사할 수 있으므로 예시 CronJob처럼 조작해야 하는 리소스들이 여러 네임스페이스에 걸쳐 있는 경우 ClusterRole을 이용할 수 있습니다.</p>
<p>ClusterRole은 모든 네임스페이스에 대해 동일한 권한을 갖습니다.</p>
<pre><code class="language-yaml"># (Cluster 포함)Role, RoleBinding 종류를 처리하기 위한 apiVersion은 `rbac.authorization.k8s.io/v1`을 사용합니다.
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: restart-role
rules:
  # deployment를 확인할 수 있어야 그와 연계되는 명령을 실행할 수 있습니다.
  # 따라서 deployment에 대한 get, list 권한을 할당합니다.
  - apiGroups: [&quot;apps&quot;]
    resources: [&quot;deployments&quot;]
    verbs: [&quot;get&quot;, &quot;list&quot;]
  # scale 명령은 별도의 리소스로 취급됩니다.
  # scale의 값을 올리고 내리는 작업은 patch 권한이 필요하므로 이를 할당합니다.
  - apiGroups: [&quot;apps&quot;]
    resources: [&quot;deployments/scale&quot;]
    verbs: [&quot;patch&quot;]
  # pod은 별도 apiGroup이 없으므로 빈 문자열을 사용합니다.
  # exec 명령을 사용하려면 pod을 조회할 수 있어야 하므로 get, list 권한도 함께 할당합니다.
  - apiGroups: [&quot;&quot;]
    resources: [&quot;pods&quot;]
    verbs: [&quot;get&quot;, &quot;list&quot;, &quot;exec&quot;]
  # exec 권한이 있어도 그 뒤에 따라오는 작업은 exec 리소스의 create 권한을 필요로 합니다.
  # 따라서 해당 권한을 할당합니다.
  - apiGroups: [&quot;&quot;]
    resources: [&quot;pods/exec&quot;]
    verbs: [&quot;create&quot;]

---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: restart-binding
# subjects에서 역할이 할당될 대상을 지정할 수 있습니다.
subjects:
- kind: ServiceAccount
  name: default
  namespace: exampleNamespace
# roleRef를 통해 할당할 역할을 지정할 수 있습니다.
roleRef:
  kind: ClusterRole
  name: restart-role
  apiGroup: rbac.authorization.k8s.io</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[PostgreSQL 신규 사용자 생성 시 권한 할당 관련 문제 해결하기 (with RLS)]]></title>
            <link>https://velog.io/@dev4jaehun_kim/PostgreSQL-%EC%8B%A0%EA%B7%9C-%EC%82%AC%EC%9A%A9%EC%9E%90-%EC%83%9D%EC%84%B1-%EC%8B%9C-%EA%B6%8C%ED%95%9C-%ED%95%A0%EB%8B%B9-%EA%B4%80%EB%A0%A8-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0-with-RLS</link>
            <guid>https://velog.io/@dev4jaehun_kim/PostgreSQL-%EC%8B%A0%EA%B7%9C-%EC%82%AC%EC%9A%A9%EC%9E%90-%EC%83%9D%EC%84%B1-%EC%8B%9C-%EA%B6%8C%ED%95%9C-%ED%95%A0%EB%8B%B9-%EA%B4%80%EB%A0%A8-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0-with-RLS</guid>
            <pubDate>Sat, 12 Aug 2023 11:17:54 GMT</pubDate>
            <description><![CDATA[<p>PostgreSQL에서 사용자를 잘 생성하고, 테이블에 대한 SELECT 권한을 할당했음에도 잘 동작하지 않았던 문제 상황을 겪었습니다.
추후 같은 문제를 겪지 않기 위하여 겪었던 문제와 그에 대한 해결방법을 정리하여 기록합니다.</p>
<h1 id="증상-1-permission-denied">증상 1: Permission Denied</h1>
<h2 id="원인">원인</h2>
<p>사용자를 생성하고, 테이블에 대한 SELECT 권한을 부여했습니다. 그리고 SELECT 명령을 실행했는데, Permission Denied -&gt; 권한 문제가 발생했습니다.</p>
<p>그래서 테이블에 컬럼들을 살펴본 결과, COLUMN_DEFAULT를 통해 설정되는 기본값에 nextval이라는 시퀀스가 사용되고 있었습니다.</p>
<p>시퀀스는 보통 유일한 값을 생성하기 위해 사용되며 DBMS마다 조금은 다르지만 다양한 기능을 제공하는데요. 시퀀스를 사용하기 위해 스키마에 요구되는 권한(USAGE)이 있고, 이게 SELECT할 때는 쓰이지 않지만 할당된 이상 요구되는 그 권한을 할당해줘야 하는 문제였습니다.</p>
<h2 id="해결">해결</h2>
<p>테이블이 속해 있는 스키마에 대한 USAGE 권한을 생성한 사용자에게 할당해 해결할 수 있습니다.</p>
<pre><code class="language-sql">GRANT USAGE ON SCHEMA &#39;스키마 이름&#39; TO &#39;사용자 이름&#39;;</code></pre>
<h1 id="증상-2-오류-없는-조회-실패">증상 2: 오류 없는 조회 실패</h1>
<h2 id="원인-1">원인</h2>
<p>증상 1의 해결 이후 더 이상 조회할 때 오류는 발생하지 않으나, 관리자가 조회할 때는 모든 값이 잘 조회되는 테이블이 마치 비어있는 테이블처럼 보이는 문제가 발생했습니다.</p>
<p>여기서는 ChatGPT의 도움을 받아 이런 상황에 대해 물어보니 RLS의 문제일 수도 있겠다는 힌트를 주어, 관련 내용을 찾아봤습니다.</p>
<p>찾아보니, RLS(Row Level Security)는 행 단위에 보안을 위해 정책을 설정할 수 있는 기능으로, <code>pg_policies</code> 테이블에 확인해보니 문제가 발생하는 테이블에 실제로 정책이 설정되어 있는 것을 확인할 수 있었습니다.</p>
<h2 id="해결-1">해결</h2>
<p>기존 정책은 사내 보안사항이기 때문에 설명하지 않겠지만, 유지가 필요한 정책이어서 충돌되지 않는 새 정책을 추가하여 문제를 해결했습니다.</p>
<p>아래는 실행한 명령어로 생성한 사용자가 SELECT를 시도하는 경우 허용하도록 하는 정책입니다. USING은 WHERE 절처럼 사용하여, 해당 조건에 부합하는 경우만 정책이 적용되도록 하는 것인데, true로 설정하면 모든 상황에 적용됩니다.</p>
<pre><code class="language-sql">CREATE POLICY &quot;Allow SELECT by 사용자 이름&quot;
ON &#39;스키마.테이블&#39; FOR SELECT TO &#39;사용자 이름&#39;
USING (true); </code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스/js] 폰켓몬]]></title>
            <link>https://velog.io/@dev4jaehun_kim/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4js-%ED%8F%B0%EC%BC%93%EB%AA%AC</link>
            <guid>https://velog.io/@dev4jaehun_kim/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4js-%ED%8F%B0%EC%BC%93%EB%AA%AC</guid>
            <pubDate>Wed, 14 Jun 2023 06:43:42 GMT</pubDate>
            <description><![CDATA[<p>프로그래머스 고득점 Kit 해시 문제 중 하나인 <a href="https://school.programmers.co.kr/learn/courses/30/lessons/1845">폰켓몬</a> 풀이입니다.</p>
<h1 id="문제-설명">문제 설명</h1>
<p>폰켓몬은 종류마다 자신의 종류 번호가 있고, nums라는 배열로 폰켓몬 종류 번호가 담긴 배열을 제공 받습니다. <code>nums = [3,1,2,3]</code>과 같이 넘어오면 3번 폰켓몬 2마리, 1,2번 폰켓몬이 각각 1마리씩 있다는 의미입니다.</p>
<p>우리는 폰켓몬을 반마리만 고를 수 있습니다. 위에 nums로 제공 받은 폰켓몬은 총 4마리이므로, 우리가 고를 수 있는 폰켓몬은 2마리입니다.</p>
<p>우리는 고를 수 있는 폰켓몬 중 최대한 많은 종류의 폰켓몬을 고르려고 합니다. 즉 중복되는 선택의 조합은 피해야 합니다.</p>
<h1 id="나의-풀이">나의 풀이</h1>
<p>가장 많은 종류의 폰켓몬 조합은 결국 중복되는 수를 제외한 모든 폰켓몬 조합입니다.
중복되는 수를 모두 제거한 배열의 크기가 우리가 최대한 다양하게 고른 경우의 수와 동일할 것입니다.
하지만 우리가 고를 수 있는 폰켓몬의 마리 수는 최대 반마리로 제한되어 있습니다.</p>
<p>따라서 중복을 제거한 배열의 크기가 반마리로 제한된 마리 수보다 크다면 반마리를 반환하고,
작다면 중복을 제거한 배열의 크기를 반환하면 됩니다.</p>
<p>이를 코드로 표현하면 다음과 같습니다.</p>
<pre><code class="language-javascript">function solution(nums) {
    // 중복을 제거하기 위해 Set 객체로 만듬
    const uniqueSpecies = new Set(nums);

    // Set 객체의 크기와 반마리의 값을 각각 변수로 저장
    const species = uniqueSpecies.size;
    const halfOfNums = nums.length / 2;

    // 그 둘을 비교해서 더 작은 값을 반환
    return (halfOfNums &gt; species) ? species : halfOfNums;
}</code></pre>
<h1 id="다른-사람의-풀이">다른 사람의 풀이</h1>
<p>다른 사람의 풀이는 오히려 불필요하게 배열로 다시 반환하는 과정을 거쳤는데요. 그 외에 특이하게 다른 접근법은 없어서 이번 문제에서 추가로 개선할 사항은 없습니다.</p>
<pre><code class="language-javascript">function solution(nums) {
  const max = nums.length / 2;
  const arr = [...new Set(nums)];

  return arr.length &gt; max ? max : arr.length
}</code></pre>
<p>감사합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스/JS] 같은 숫자는 싫어]]></title>
            <link>https://velog.io/@dev4jaehun_kim/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4JS-%EA%B0%99%EC%9D%80-%EC%88%AB%EC%9E%90%EB%8A%94-%EC%8B%AB%EC%96%B4</link>
            <guid>https://velog.io/@dev4jaehun_kim/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4JS-%EA%B0%99%EC%9D%80-%EC%88%AB%EC%9E%90%EB%8A%94-%EC%8B%AB%EC%96%B4</guid>
            <pubDate>Wed, 14 Jun 2023 05:20:59 GMT</pubDate>
            <description><![CDATA[<p>프로그래머스 고득점 Kit에 스택/큐 유형 문제 중 하나인 <strong>같은 숫자는 싫어</strong> 풀이입니다.
<a href="https://school.programmers.co.kr/learn/courses/30/lessons/12906">문제 풀러가기</a></p>
<p>JavaScript를 이용해 풀었습니다.</p>
<h1 id="문제-설명">문제 설명</h1>
<p>배열 arr이 주어지면 연속으로 등장하는 숫자를 제거합니다.
예를 들어 <code>arr = [1,1,3,3,0,1,1]</code>과 같은 배열이 주어졌다면 결과값은 <code>answer = [1,3,0,1]</code> 형태로 반환해야 합니다.</p>
<h1 id="나의-풀이">나의 풀이</h1>
<p>arr을 순회하며 현재 회차의 숫자가 다음 회차의 숫자와 일치한다면 아무 동작도 하지 않고, 다를 때만 answer에 값을 추가하도록 했습니다.</p>
<p>이를 순서대로 정리해보면 다음과 같습니다.</p>
<ol>
<li>현재값을 저장하는 변수 currentNum 선언</li>
<li>arr을 순회하는 반복문 작성<ol>
<li>다음 루프의 값이 curretNum과 같다면 아무 동작도 하지 않음</li>
<li>다음 루프의 값이 currentNum과 다르다면 현재 currentNum을 answer 배열에 추가 후 currentNum을 새 값으로 재할당</li>
</ol>
</li>
<li>마지막 요소는 비교 작업을 수행하지 않기 때문에 가장 마지막 요소를 answer 배열에 추가하고 프로그램 종료</li>
</ol>
<p>이를 코드로 표현하면 다음과 같이 작성할 수 있습니다.</p>
<pre><code class="language-javascript">function solution(arr)
{
    var answer = [];
    let currentNum = arr[0];

    for (let item of arr) {
        if (currentNum !== item) {
            answer.push(currentNum);
            currentNum = item;
        }
    }

    answer.push(arr[arr.length - 1]);

    return answer;
}</code></pre>
<h1 id="가장-효율적인-풀이를-보고-배우기">가장 효율적인 풀이를 보고 배우기</h1>
<p>프로그래머스에서는 문제를 다 풀었을 때 다른 사람의 풀이를 볼 수 있는 기능을 제공하고 있습니다.
이를 이용해 가장 많은 좋아요를 받은 풀이를 보며 개선점을 찾을 수 있습니다.</p>
<pre><code class="language-javascript">function solution(arr)
{
    return arr.filter((val,index) =&gt; val != arr[index+1]);
}</code></pre>
<p>위 코드를 보면 filter 메서드를 이용해 값을 비교하고 있습니다.
현재 값과 다음 값을 비교하는 로직이라는 점에서 접근 방식은 비슷했으나, index 인자를 같이 전달하여 별도의 변수 선언 없이 다음 요소의 값을 구했습니다.</p>
<p>또한 마지막 요소 문제도 마지막 index에서 +1을 하면 undefined가 반환되는 것을 이용하여 해결했기 때문에 별도 작성 없이 해결이 가능합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Terraform 배우기]]></title>
            <link>https://velog.io/@dev4jaehun_kim/Terraform-%EB%B0%B0%EC%9A%B0%EA%B8%B0</link>
            <guid>https://velog.io/@dev4jaehun_kim/Terraform-%EB%B0%B0%EC%9A%B0%EA%B8%B0</guid>
            <pubDate>Tue, 13 Jun 2023 12:15:16 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요. 이전에 작성한 <a href="https://velog.io/@dev4jaehun_kim/DevOps-%EC%97%AD%EB%9F%89-%EA%B0%95%ED%99%94%ED%95%98%EA%B8%B0-0.-%ED%95%99%EC%8A%B5%EC%A0%84%EB%9E%B5-%EC%88%98%EB%A6%BD%ED%95%98%EA%B8%B0">DevOps 역량 강화</a>의 일환으로 Terraform을 학습하며 배운 내용을 기록했습니다.</p>
<h1 id="terraform-개념">Terraform 개념</h1>
<p>Terraform은 인프라 코드를 선언형으로 관리할 수 있는 도구입니다.</p>
<p>명령형 프로그래밍은 전통적인 방식으로 프로그램이 어떤 <strong>흐름</strong>으로 동작할지 작성합니다.
반면, 선언형 프로그래밍은 프로그램이 어떤 <strong>동작</strong>을 해야하는지 작성하면 프로그램이 동작하는 런타임 레벨에서 그 동작의 결과가 나오게 만듭니다.</p>
<p>이를 인프라 관리에 적용해보면 Terraform을 사용할 경우 인프라가 갖춰야 할 모습을 작성(<code>선언</code>)하면 그 형태로 인프라가 자동으로 구성됩니다.</p>
<blockquote>
<p><strong>Terraform 학습하기</strong>
아래에서 설명할 내용들은 모두 튜토리얼에서 학습한 내용을 기반으로 합니다.
AWS, GCP, Docker 등 다양한 환경에 배포하는 시나리오로 튜토리얼을 제공하고 있으며, 본 글에서는 AWS 과금을 피하기 위해 Docker로 실습했습니다. (<a href="https://developer.hashicorp.com/terraform/tutorials/docker-get-started">링크</a>)</p>
</blockquote>
<h1 id="배포-단계">배포 단계</h1>
<p>Terraform이 권장하는 배포 단계는 총 5단계로 이뤄집니다.
(Scope -&gt; Author -&gt; Initialize -&gt; Plan -&gt; Apply)</p>
<h2 id="scope">Scope</h2>
<p>Scope 단계에서는 프로젝트를 배포하기 위해 필요한 인프라 규모를 식별하는 단계입니다.</p>
<p>해당 단계는 인프라가 구성된 결과를 추상적으로 설계해야 하는 단계입니다. 그렇기에 복잡한 인프라를 구성해야 한다면 필기구를 이용하여 그림을 그려보거나, 향후 산출물로 활용하기 위해 다이어그램으로 만들 수도 있습니다.</p>
<blockquote>
<p>Terraform의 config 파일을 기준으로 다이어그램을 자동으로 생성해주는 <strong>pluralith</strong>라는 시각화 도구도 존재합니다. Scope 단계에서 어떤 형태로든 다이어그램을 만들어둔다면 시각화 도구로 생성한 내용과 대조하여 원하는 대로 잘 구성됐는지 확인이 가능할 것입니다.</p>
</blockquote>
<h2 id="author">Author</h2>
<p>Author 단계는 추상적으로 설계된 인프라를 실제 코드로 작성하는 단계입니다.</p>
<p>Terraform은 <strong>Terraform Language</strong>라고 부르는 configuration 언어로 인프라 코드를 작성하게 됩니다. (과거에는 HCL; Hashicorp Configuration Language)</p>
<p><code>.tf</code> 확장자를 사용하며, json과 유사한 형태로 코드를 작성하게 됩니다.
아래 사진은 Terraform 공식 튜토리얼에서 가져온 코드의 예시로 docker라는 provider를 사용하여, 아래 2개의 리소스를 만들 것이라고 선언한 코드입니다.
<img src="https://velog.velcdn.com/images/dev4jaehun_kim/post/1ae224e7-73c4-4173-922a-c52422ba68d5/image.png" alt="Terraform config 파일 예시"></p>
<p>Terraform이 선언형 IaC를 구현한 방법의 주요 기능인 Provider는 각 provider가 요구하는 형태로 resource를 작성하면, resource를 생성하기 위한 세부적인 동작은 provider가 처리하도록 추상화되어 있습니다.</p>
<blockquote>
<p><strong>[Terraform provider 추상화의 한계]</strong>
resource 부분에 코드를 그대로 유지한 상태에서 provider를 갈아끼우는 것만으로 배포될 곳을 바꾸는 수준의 완전한 추상화는 불가합니다.</p>
</blockquote>
<h2 id="initialize">Initialize</h2>
<p>Terraform이 인프라를 구성하기 전에 필요한 플러그인을 설치하는 초기화 단계입니다.</p>
<p>모든 provider는 init 단계에서 설치되며, 위에서 첨부했던 사진을 예시로 들면 초기화 명령어인 <code>terraform init</code>을 입력했을 때 docker provider <code>kreuzwerker/docker</code>라는 출처로부터 설치됩니다.</p>
<h2 id="plan">Plan</h2>
<p>Terraform이 앞으로 인프라를 어떻게 구성할 것인지 확인하는 단계입니다.</p>
<p>작성된 configuration file을 기준으로 실제 구성을 진행하는 명령어는 <code>terraform apply</code>입니다. 해당 명령을 실행하면 아래처럼 구성될 것이라 알리는 내용과 동의 여부를 묻는 대화형 인터페이스가 실행됩니다.</p>
<p>아래 사진에서 <code>Plan: 2 to add, ...</code> 부분을 통해 2개의 리소스가 추가될 것이라는 것을 알 수 있습니다.
<img src="https://velog.velcdn.com/images/dev4jaehun_kim/post/3d0075c4-4aea-42bc-98d7-d2b3a3e81d49/image.png" alt="terraform apply 명령 실행 화면"></p>
<p><code>terraform plan</code> 명령어를 사용하면 배포를 실행하지 않고, 실행됐을 때 어떤 변경사항이 발생하는지만 확인할 수 있습니다.</p>
<p>어떤 것을 기준으로 테스트를 작성할지는 고민할 내용이지만, 실행됐을 때 예상 결과를 미리 볼 수 있기 때문에 CI에 접목시키기 간단하겠다는 생각이 들었습니다.</p>
<h2 id="apply">Apply</h2>
<p>배포가 실제로 실행되는 단계입니다.</p>
<p>Plan 단계에서 설명했던 <code>terraform apply</code> 실행 시 등장하는 대화형 인터페이스에서 <code>yes</code>를 입력해 실행했다고 가정합니다.</p>
<p>사진으로 첨부했던 configuration file에는 docker image 리소스를 만드는 구성과 생성된 이미지로 도커 컨테이너를 만드는 구성이 작성되어 있었습니다.</p>
<p>실제로 <code>docker ps</code> 명령어를 통해 실행 중인 컨테이너를 확인해보면 config 파일에 작성한 대로 컨테이너가 생성되어 실행 중임을 알 수 있습니다.
<img src="https://velog.velcdn.com/images/dev4jaehun_kim/post/109e89ed-60a0-4bd7-87ea-18b6b6ae7e88/image.png" alt="docker ps 명령 실행 화면"></p>
<p>또한 이렇게 자동으로 프로비저닝 된 인프라는 <code>terraform destroy</code> 명령을 통해 고스란히 제거가 가능합니다.</p>
<h1 id="state">State</h1>
<p>Terraform은 앞서 설명했던 것처럼 선언형 도구이기 때문에 우리가 프로그램의 흐름을 직접 작성하지 않아도 됐습니다.</p>
<p>그런데 변경내용이 발생했다면 어떻게 해야 할까요?
변경내용이 파괴적인 동작을 일으키는지 미리 알 수는 없을까요?
Terraform은 어떻게 자신이 구성한 인프라만 기억하고 제거하죠?</p>
<p>위 질문들을 해결하기 위해서 Terraform은 state(상태)라는 기능을 제공합니다.</p>
<h2 id="terraformtfstate">terraform.tfstate</h2>
<p>Terraform 에 상태는 apply 명령 이후 같은 폴더에 <code>terraform.tfstate</code>라는 이름으로 자동 생성됩니다. 파일을 직접 열어볼 수도 있겠지만 <code>terraform show</code> 명령을 통해 현재 구성된 인프라의 상태를 확인할 수 있습니다.
<img src="https://velog.velcdn.com/images/dev4jaehun_kim/post/e0cc5725-26c0-4f38-b451-bc76f8e71ab8/image.png" alt="terraform show 명령 실행 화면"></p>
<p>위 사진처럼 Terraform이 어떤 docker 이미지를 생성했고, 컨테이너를 생성했는지 기억하고 있는 파일이 있기 때문에 다음 apply 명령 실행 때 변경될 내용들을 미리 파악하거나, Terraform 자신이 생성한 부분만 파괴하는 것이 가능합니다.</p>
<blockquote>
<p>❓<strong>저는 <code>terraform show</code> 명령을 실행해도 아무런 결과가 나오지 않아요</strong>
해당 명령어는 결국 파일에 있는 내용을 터미널에 출력해줄 뿐입니다.
따라서 파일이 없는 위치에서 실행한다면 아무런 결과가 나오지 않거나, <code>No State.</code>라는 결과를 출력할 수 있습니다.</p>
</blockquote>
<h1 id="글을-마치며">글을 마치며</h1>
<p>튜토리얼에 있는 내용을 기록용으로 재작성한 것이라 글이 매끄럽지 못 한 점 양해 부탁드립니다.
이 글을 통해 배워가실 게 있으시면 좋겠고, 관련해서 궁금한 점이 있다면 댓글 남겨주세요.</p>
<p>감사합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[DevOps 역량 강화하기 - 0. 학습전략 수립하기]]></title>
            <link>https://velog.io/@dev4jaehun_kim/DevOps-%EC%97%AD%EB%9F%89-%EA%B0%95%ED%99%94%ED%95%98%EA%B8%B0-0.-%ED%95%99%EC%8A%B5%EC%A0%84%EB%9E%B5-%EC%88%98%EB%A6%BD%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dev4jaehun_kim/DevOps-%EC%97%AD%EB%9F%89-%EA%B0%95%ED%99%94%ED%95%98%EA%B8%B0-0.-%ED%95%99%EC%8A%B5%EC%A0%84%EB%9E%B5-%EC%88%98%EB%A6%BD%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 31 May 2023 09:33:55 GMT</pubDate>
            <description><![CDATA[<h1 id="글을-시작하며">글을 시작하며...</h1>
<p>글을 시작하기 전에 DevOps 개발자가 되려는 이유를 잘 설명하기 위해서 저의 개인적인 경험과 목적을 상세히 풀어 써보려고 합니다.
어떻게 공부할 것인지만 궁금하신 경우에는 다음으로 넘어가 주세요.</p>
<p>저는 어릴 때부터 항상 컴퓨터를 최종 사용자 관점에서 사용하는 것보다는 컴퓨터들이 속하는 시스템을 구축하고, 이를 보다 편하고 효율적으로 하는 방법에 더 관심이 많았습니다. 이러한 기질 때문일지 자연스럽게 고등학교에서는 가상의 시나리오를 따라 Linux, Windows Server, Cisco 장비를 통해 인프라를 구성하는 대회에 선수로 출전하거나, 졸업 이후에도 자연스럽게 클라우드 환경(MS Azure)에서 업무할 수 있는 회사로 커리어를 이어 나갔습니다.</p>
<p>최근에는 직접 프로그래밍하는 것에 흥미가 동해, 6개월 간 Front-End 부트캠프를 경험해봤습니다. 어떻게 보면 인프라에 더 가깝다고 느껴질 수 있고, 기존의 네트워크 지식을 활용할 수 있는 Back-End가 아닌 Front-End 부트캠프를 선택했는지 글을 읽으시는 분 입장에서 의문을 느끼실 수 있을 것 같습니다.</p>
<p>저 또한 그렇게 생각했었지만 직접 구현되는 걸 눈으로 볼 수 있다는 즐거움도 있었고, 사이드 프로젝트를 혼자 진행할 때 눈에 보이는 부분을 그럴싸하게 만들고 싶다는 욕심도 있어 Front-End 분야로 학습을 진행했습니다.</p>
<p>부트캠프를 마치고, DevOps 개발자로 취업을 준비하며 회사들이 요구하는 자격 요건을 정말 오랜만에 보게 되었는데요. 클라우드의 지엽적인 부분만 다뤘던 경험으로 인해 내가 이론적으로 알고 있는 베이스보다 실무 능력이 따라주지 않는다는 것을 느꼈습니다.</p>
<p>그래서 부족한 부분을 보강하기 위해, 아래 로드맵과 ChatGPT의 도움으로 커리큘럼을 만들었습니다.
<a href="https://roadmap.sh/devops">DevOps 로드맵</a></p>
<p>생성된 커리큘럼을 따라가면서 제게 부족한 부분을 강화하려 하며, 저의 전략을 세우는 방법이 마음에 드시다면 다른 분들도 새로운 것을 배울 때 참고해주시면 좋을 것 같습니다.</p>
<h1 id="커리큘럼-만들기">커리큘럼 만들기</h1>
<p>웹 개발자(를 시작하는 신입 개발자) 사이에서 많이들 참고하는 로드맵 사이트가 있습니다.
<a href="https://roadmap.sh">roadmap.sh</a> 사이트인데 이곳에는 제가 참고한 DevOps 외에도, Front-End, Back-End, 외 다양한 분야에서 요구되는 개발 역량을 갖추기 위해 알아야 할 이론적 지식과 어떠한 실제적 경험이 필요한지 그림으로 보여주고 있습니다.</p>
<p>마침 ChatGPT Plus를 구독 중이어서 ChatGPT가 웹 사이트에 내용을 읽어보고, 답변하는 모델을 사용할 수 있었는데요. 이에 도움을 받아 커리큘럼을 상세히 작성해보았습니다.</p>
<p>저는 다음과 같은 과정을 통해 커리큘럼을 만들었습니다.
    a. <a href="https://roadmap.sh/devops">DevOps Roadmap</a>을 보면서 제가 이미 알고 있는 지식을 다음과 같이 리스트업 했습니다.
        <img src="https://velog.velcdn.com/images/dev4jaehun_kim/post/6ebaf671-59aa-426b-9540-6092ad1ef484/image.png" alt=""></p>
<pre><code>b. GPT-4(with Web Browsing) 모델에 다음과 같은 프롬프트를 입력해서 커리큘럼 초안을 작성했습니다.
    `https://roadmap.sh/devops 해당 사이트는 DevOps 개발자가 되기 위한 학습 여정을 그린 로드맵 사이트야. 내가 해당 로드맵에서 이미 알고 있는 지식은 다음과 같아. {리스트업한 내용} DevOps 개발 역량 강화를 위해서 내가 이미 알고 있는 내용 외에 로드맵에서 등장했던 내용들을 추가로 학습하려고 해. 한 학기(15주) 분량으로 커리큘럼을 작성해줄 수 있을까?`
c. b번에서 작성된 내용을 기반으로 GPT-4(일반) 모델에 다음과 같은 프롬프트를 입력해서 커리큘럼 첨삭을 요청했습니다.
    `DevOps 분야에서 나의 역량을 강화하고자 부족한 부분을 추가 학습하는 15주 짜리 커리큘럼을 작성해봤어. 너에게 바라는 요구사항은 2가지야. 1. 작성된 커리큘럼에서 더 효율적으로 학습 방향성 교정 2. 15주차 프로젝트 세션 때 앞서 배운 내용들을 모두 아울러 다룰 수 있는 프로젝트 주제 제안 {b번에서 생성된 커리큘럼}`
d. 생성된 프롬프트를 읽어보면서 말이 되지 않는 부분이 생성되었거나, 학습 방향성이 잘못됐다고 느껴지는 부분들이 있는지 확인합니다.</code></pre><h1 id="글을-마치며-with-생성된-커리큘럼">글을 마치며 (with 생성된 커리큘럼)</h1>
<p>생성된 커리큘럼을 소개합니다.
앞으로 이어질 글에서는 이 커리큘럼을 따라 학습 일지를 남겨보려고 합니다.</p>
<p>오늘도 제 블로그에 방문해주시고, 제가 쓴 글 읽어주셔서 감사합니다.</p>
<h2 id="1-2주-iacinfrastructure-as-code">1-2주: IaC(Infrastructure as Code)</h2>
<ul>
<li>Terraform을 이용한 인프라 환경 구축 방법을 학습합니다.</li>
<li>Ansible을 이용한 시스템 설정 및 SW 관리 방법을 학습합니다.</li>
<li>Vault를 이용한 Secret 관리 방법을 학습합니다.</li>
</ul>
<h2 id="3-4주-container-orchestration">3-4주: Container Orchestration</h2>
<ul>
<li>Docker를 이용해 서비스를 컨테이너화하고, 배포하는 방법을 학습합니다.</li>
<li>Kubernetes를 이용한 대규모 컨테이너 배포, 스케일링, 관리 방법을 학습합니다.</li>
</ul>
<h2 id="5-6주-monitoring-and-logging">5-6주: Monitoring and Logging</h2>
<ul>
<li>Datadog을 이용한 모니터링 방법을 학습합니다.</li>
<li>ELK 스택을 이용한 로그 관리 방법을 학습합니다.</li>
</ul>
<h2 id="7-8주-테스트-자동화와-테스트-주도-개발tdd">7-8주: 테스트 자동화와 테스트 주도 개발(TDD)</h2>
<ul>
<li>테스트 자동화 도구를 이용한 테스트 작성 방법을 학습합니다.</li>
<li>테스트 주도 개발(TDD) 접근법을 이해하고, 실제로 적용합니다.</li>
<li>GitHub CI/CD를 사용한 지속적인 통합/배포를 학습합니다.</li>
</ul>
<h2 id="9-10주-gitops">9-10주: GitOps</h2>
<ul>
<li>ArgoCD를 이용한 GitOps 방법을 학습합니다.</li>
</ul>
<h2 id="11-12주-클라우드-보안">11-12주: 클라우드 보안</h2>
<ul>
<li>AWS에서 제공하는 보안 서비스 및 Best Practice를 학습합니다.</li>
</ul>
<h2 id="13-14주-msamicro-service-architecture">13-14주: MSA(Micro-Service Architecture)</h2>
<ul>
<li>MSA의 기본 원칙과 패턴을 학습합니다.</li>
<li>Service Mesh의 개념을 이해하고, Istio를 통한 구현 방법을 학습합니다.</li>
</ul>
<h2 id="15주또는-그-이상-개인-프로젝트-및-리뷰">15주(또는 그 이상): 개인 프로젝트 및 리뷰</h2>
<ul>
<li>지금까지 배운 내용을 기반으로 서비스를 AWS와 Azure 양쪽에 동시 배포합니다.<ul>
<li>Terraform과 Kubernetes를 조합하여 애플리케이션 배포를 자동화해야 합니다.</li>
<li>Datadog, ELK 스택 등 모니터링 및 로깅 솔루션을 이용하여 측정 가능한 인프라를 구성해야 합니다.</li>
<li>MSA 아키텍쳐로 확장성이 확보된 인프라를 구성해야 합니다.</li>
</ul>
</li>
<li>프로젝트 완료 후 회고 리뷰를 작성합니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[2023년 5월 5주차 기능구현 - 1]]></title>
            <link>https://velog.io/@dev4jaehun_kim/2023%EB%85%84-5%EC%9B%94-5%EC%A3%BC%EC%B0%A8-%EA%B8%B0%EB%8A%A5%EA%B5%AC%ED%98%84-1</link>
            <guid>https://velog.io/@dev4jaehun_kim/2023%EB%85%84-5%EC%9B%94-5%EC%A3%BC%EC%B0%A8-%EA%B8%B0%EB%8A%A5%EA%B5%AC%ED%98%84-1</guid>
            <pubDate>Mon, 29 May 2023 16:56:27 GMT</pubDate>
            <description><![CDATA[<ul>
<li><strong>주제</strong>: 영화 데이터를 크롤링하여 무한 스크롤이 적용된 사이트를 원하는 스타일로 자유롭게 구현합니다.
(5월 4주차에 끝내지 못 해서 이어서 함)</li>
<li><strong>활용 데이터</strong>: <a href="https://www.themoviedb.org/">https://www.themoviedb.org</a></li>
<li><strong>링크</strong>: <a href="https://github.com/dev4jaehunkim/infinite-scroll-movie-gallery">Git 저장소</a>, <a href="https://infinite-scroll-movie-gallery.vercel.app/">배포</a></li>
</ul>
<h1 id="요약">요약</h1>
<p>영화 정보를 20개씩 읽어오고, 읽어온 내용으로 영화 목록을 렌더링합니다.
목록에서 마지막 5번째 요소가 화면에 등장할 때 다음 20개를 추가로 읽어와서 기존 요소에 더해 렌더링합니다.</p>
<h2 id="intersection-observer-api-이해하기">Intersection Observer API 이해하기</h2>
<p>해당 글은 내용이 길어져 별도 포스트로 정리했습니다.
👉 <a href="https://velog.io/@dev4jaehun_kim/Intersection-Observer-API%EC%97%90%EC%84%9C-%EB%AC%B4%ED%95%9C-%EC%8A%A4%ED%81%AC%EB%A1%A4%EC%97%90-%ED%95%84%EC%9A%94%ED%95%9C-%EB%B6%80%EB%B6%84%EB%A7%8C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0">Intersection Observer API에서 무한 스크롤에 필요한 부분만 이해하기</a> 읽으러 가기</p>
<h2 id="nextjs-13에서-클라이언트-컴포넌트-작성하기">Next.js 13에서 클라이언트 컴포넌트 작성하기</h2>
<p>저는 다음과 같은 이유로 Next.js 프레임워크를 사용해 React 앱을 작성했습니다.</p>
<ol>
<li><a href="https://react.dev">react.dev</a>로 React 공식 문서가 업그레이드 된 이후 프레임워크를 사용해 React 앱을 작성하기를 권장함</li>
<li>Vercel의 git repo 연동 및 배포 자동화하기가 간편함</li>
</ol>
<p>하지만 Next.js의 최신 버전(작성일 기준 13.4)에서는 App Router가 stable하게 사용 가능합니다. 이 App Router 방식은 컴포넌트가 기본적으로 서버 컴포넌트로만 동작하게 하는데요.
따라서 useState, useEffect 등 클라이언트 사이드에서 변경 작업이 필요한 React 훅을 사용하려면 <code>use client;</code> 구문을 page.js 최상단에 작성해주어야 합니다.</p>
<blockquote>
<p><strong>❓ 왜 Next.js 13의 기본 컴포넌트 방식인 서버 컴포넌트로 작성하지 않나요?</strong>
무한 스크롤은 클라이언트에서 특정 요소가 화면에 나타날 때까지 대기하고, 이를 관측한 뒤 클라이언트의 요청에 의해 새로운 네트워크 요청이 발생합니다. 또한 그에 대한 응답을 기반으로 다시 렌더링하기 때문에 서버 컴포넌트는 별로 적합한 방식이 아닙니다.</p>
<p>서버 컴포넌트는 서버에서 접근할 수 있는 리소스들을 모아 하나의 페이지를 완성한 후 클라이언트에게 전달해야 할 때 보다 적합합니다. (해당 내용은 SSR; 서버 사이드 렌더링과는 다릅니다. 하지만 SSR이 서버 컴포넌트를 구현하기 위한 좋은 방법이 될 수 있습니다.)</p>
<p><a href="https://nextjs.org/blog/next-13-4">Next.js 13.4 소개</a>에서는 추후 패턴이 정립되어 가며, 생태계가 풍부해지면 좀 더 쉽게 문제를 해결할 수 있게 될거라고 설명하고 있습니다.
<img src="https://velog.velcdn.com/images/dev4jaehun_kim/post/e218a6f8-2f35-4b53-bab7-56fc62c8e9bd/image.png" alt=""></p>
</blockquote>
<h2 id="영화-목록-가져오기">영화 목록 가져오기</h2>
<h3 id="앞으로-사용하게-될-기본-fetch-구문-작성">앞으로 사용하게 될 기본 fetch 구문 작성</h3>
<p>무한스크롤로 영화 목록을 렌더링하기 위해서 기반이 되는 데이터를 긁어오는 로직을 먼저 작성했습니다.
화면에 렌더링하는 로직을 제외한 상태에서 정보가 제대로 불러오는지 확인하기 위해 console.log를 사용하여 원하는 형태(배열)가 될 때까지 코드를 작성했습니다.</p>
<pre><code class="language-javascript">const fetchMovies = async () =&gt; {
    try {
        const response = await fetch(`https://api.themoviedb.org/3/discover/movie?api_key=${process.env.tmdbApiKey}&amp;page=${moviePage}&amp;language=ko-KR`);
        const json = await response.json();
        console.log(json.results);
    } catch (err) {
        console.error(err);
    }
}

fetchMovies();</code></pre>
<p>위 함수의 실행 결과는 다음과 같습니다.
<img src="https://velog.velcdn.com/images/dev4jaehun_kim/post/876f2634-37bb-47ad-b7e5-06f22b735b60/image.png" alt=""></p>
<p>해당 함수를 실행하기 위한 API key는 <code>next.config.js</code> 파일에서 env 속성으로 작성하여 추가했습니다.</p>
<h3 id="값을-상태에-저장하고-useeffect로-갱신하기">값을 상태에 저장하고, useEffect로 갱신하기</h3>
<p>영화 정보를 불러오는 API는 페이지네이션이 적용되어 있고, 한 페이지마다 20개의 항목을 가져옵니다.
이를 응용하여 스크롤을 내려 추가 렌더링 요청이 들어올 때마다 page 값을 +1하여 기존 영화 정보와 합쳐주게끔 작성했습니다.</p>
<p>페이지가 1페이지라면 받아온 결과만 movies에 저장하고, 2페이지부터는 spread syntax를 사용해 기존 movies와 API 응답 결과를 한 배열로 합쳐 movies에 저장하도록 로직을 구성했습니다.</p>
<pre><code class="language-javascript">// 몇 페이지의 영화 목록을 fetch할지 저장한 상태
// 초기값이 1이므로 화면이 처음 렌더링될 때는 1페이지의 영화 목록을 렌더링
const [moviePage, setMoviePage] = useState(1);

// 영화 항목들이 배열 형태로 담겨진 상태
const [movies, setMovies] = useState();

// 페이지를 증가시키는 사이드 이펙트
// 아래 상태에서는 무한 스크롤 로직이 추가되지 않아 아직 동작하지 않음
useEffect(() =&gt; {
    setMoviePage((prevPage) =&gt; prevPage+1);
}, [movies]);

// 네트워크 요청을 통해 영화 정보를 상태에 저장
useEffect(() =&gt; {
    const fetchMovies = async () =&gt; {
        try {
            const response = await fetch(`https://api.themoviedb.org/3/discover/movie?api_key=${process.env.tmdbApiKey}&amp;page=${moviePage}&amp;language=ko-KR`);
            const json = await response.json();
            if (moviePage &gt; 1) {
                setMovies([...movies, ...json.results]);
            } else {
                setMovies(json.results);
            }
        } catch (err) {
            console.error(err);
        }
    }
    fetchMovies();
}, [moviePage]);</code></pre>
<h2 id="영화-정보-렌더링하기">영화 정보 렌더링하기</h2>
<p>네트워크 요청을 통해 영화 정보도 받았고, 이를 useState 훅을 통하여 상태로도 저장해두었습니다.
이제 상태에 저장된 내용을 표시하는 컴포넌트를 작성해보겠습니다.</p>
<h3 id="컴포넌트-폴더-구조">컴포넌트 폴더 구조</h3>
<p>Next.js 13의 App Router에서는 폴더 이름이 새로운 path로 라우팅되는 방식입니다. <code>()</code> 둥근 괄호를 적용해, 폴더가 새 라우팅 경로로 인식되지 않도록 할 수 있습니다.
이에 <code>(MovieCard)</code>라는 이름으로 폴더를 만들어 컴포넌트를 생성하고, 실제 로직을 수행하는 요소와 styled-components를 통해 작성된 스타일을 구분하여 각각 index.js와 style.js 이름으로 코드를 작성했습니다.</p>
<p><strong>index.js</strong></p>
<pre><code class="language-javascript">&#39;use client&#39;;

import { useState, forwardRef } from &#39;react&#39;;
// Style 컴포넌트는 로직을 포함하는 컴포넌트와 구분 짓기 위하여 S라는 postfix를 붙임
import * as S from &#39;./style&#39;;

const MovieCard = ({ movie }) =&gt; {

    const [poster, setPoster] = useState(movie.poster_path);
    return (
        &lt;S.MovieCard src={poster} alt={`영화 ${movie.title}의 포스터 사진`}&gt;
            &lt;S.Poster&gt;
                &lt;img src={poster} alt={`영화 ${movie.title}의 포스터 사진`} /&gt;
            &lt;/S.Poster&gt;
            &lt;S.MovieInfo&gt;
                &lt;h1&gt;{movie.title}&lt;/h1&gt;
                &lt;p&gt;{`평점 : ${movie.vote_average} / 10`}&lt;/p&gt;
            &lt;/S.MovieInfo&gt;
        &lt;/S.MovieCard&gt;
    )
});

export default MovieCard;</code></pre>
<p><strong>style.js</strong></p>
<pre><code class="language-js">import styled from &quot;styled-components&quot;;

export const MovieCard = styled.div`
    width: 30vw;
    min-width: 200px;
    max-width: 400px;

    height: 40vh;
    min-height: 300px;
    max-height: 500px;

    display: flex;
`;

export const Poster = styled.div`
    width: 30%;
    height: 100%;

    display: flex;
    justify-content: center;
    align-items: center;

    img {
        height: 90%;
    }
`;

export const MovieInfo = styled.div`
    width: 70%;
    height: 100%;

    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;

    color: white;
    background: rgba(0, 0, 0, 0.7);

    h1 {
        font-size: 1.5rem;
        font-weight: bold;
    }

    p {
        font-size: 1rem;
    }
`;</code></pre>
<h3 id="영화-정보-렌더링">영화 정보 렌더링</h3>
<p>이제 Next.js의 기본으로 렌더링되는 코드인 page.js에서 작성된 컴포넌트를 통해 렌더링할 수 있습니다.</p>
<p>처음 화면이 렌더링될 때는 movies가 아직 초기화되지 않은 상태입니다. 네트워크 요청은 비동기적으로 발생하며, 네트워크 요청이 완료될 때 movies 값이 갱신됩니다.
따라서 movies 값이 유효한지 <code>movies ?</code> 구문을 통해 먼저 확인하고, 아직 네트워크 요청이 완료되지 않아 값이 없다면 Loading...을 먼저 표시하도록 했습니다.</p>
<p>movies 값이 존재한다면 영화 정보를 받을 수 있도록 map으로 나누어 MovieCard 컴포넌트를 렌더링합니다.</p>
<p><strong>page.js</strong></p>
<pre><code class="language-js">&#39;use client&#39;;

import { useEffect, useRef, useState } from &quot;react&quot;;
import MovieCard from &quot;./(MovieCard)/index&quot;;

// 요소가 적당히 나열되도록 만든 스타일
import * as S from &#39;./style&#39;;

export default function Home() {
    ... // 영화 정보 가져오는 부분 생략

    return (
    &lt;S.ListContainer&gt;
        {movies ? movies.map((movie) =&gt; (
            &lt;MovieCard key={movie.id} movie={movie} /&gt;
        )) : &lt;p&gt;Loading...&lt;/p&gt;}
    &lt;/S.ListContainer&gt;
    )
}</code></pre>
<h3 id="bug-fixed영화-정보의-포스터-사진이-표시되지-않음">[Bug] (fixed)영화 정보의 포스터 사진이 표시되지 않음</h3>
<p>하지만 요소는 잘 렌더링되는데 포스터 사진이 깨지는 문제가 있었습니다.
영화의 포스터 사진 속성인 <code>poster_path</code> 값을 보면 원인을 알 수 있는데, <code>/</code> 문자로 시작하는 것을 보아 이미지가 업로드된 네트워크 경로를 의미하는 것으로 유추가 가능합니다.
<code>poster_path: &#39;/kHen25Yk0DnaB2pqaB1mcZDKqMv.jpg&#39;,</code></p>
<p>저는 이미지의 웹 주소를 알아내기 위해서 실제 사이트에 접속한 뒤 <code>새로운 탭에서 이미지 열기</code>를 클릭했습니다.
<img src="https://velog.velcdn.com/images/dev4jaehun_kim/post/fce1e85f-7bda-4ed9-b208-d9952fa9691c/image.png" alt=""></p>
<p>그 결과, 이미지의 웹 주소는 <code>https://image.tmdb.org/t/p/w440_and_h660_face/{poster_path}</code> 꼴로 이뤄지는 것을 알아낼 수 있었고, 이를 MovieCard 코드에 적용하여 해결했습니다.</p>
<p><strong>(MovieCard)/index.js</strong></p>
<pre><code class="language-js">&#39;use client&#39;;

import { useState, forwardRef } from &#39;react&#39;;
import * as S from &#39;./style&#39;;

const MovieCard = ({ movie }) =&gt; {
    // 참고한 웹 주소 형태로 상태 저장
    const [poster, setPoster] = useState(`https://image.tmdb.org/t/p/w440_and_h660_face${movie.poster_path}`);
    return (
        ...
    )
};

export default MovieCard;</code></pre>
<p><strong>결과 화면</strong>
<img src="https://velog.velcdn.com/images/dev4jaehun_kim/post/a60dd86c-8947-4fb2-8554-9050a978720c/image.png" alt=""></p>
<h2 id="무한-스크롤-구현하기">무한 스크롤 구현하기</h2>
<p>이제 영화 정보도 잘 받아올 수 있고, 화면 렌더링도 성공했으니 스크롤을 내릴 때마다 렌더링을 반복하여 무한히 스크롤이 가능하도록 구현해보겠습니다.</p>
<p>이해한 Intersection Observer API의 동작을 기반으로 기능을 구현해보겠습니다.</p>
<h3 id="target이-될-대상-선정하기">target이 될 대상 선정하기</h3>
<p>저는 적당히 밑으로 스크롤했을 때 추가 렌더링이 이뤄지길 원했고, 대강 마지막에서 5번째 요소가 화면에 등장했을 때 새 요소를 렌더링하면 되겠다고 생각했습니다.</p>
<p>target은 HTML element 형태로 저장되어야 하는데요. React에서는 가상 DOM과 실제 DOM을 평가하는 과정을 거친 뒤에 실제 DOM에 요소가 렌더링되기 때문에 코드 작성 시점에 런타임 때 생성될 DOM의 위치를 알 수 없는 문제가 존재합니다.</p>
<p>React에서는 이를 위해 useRef 훅이 존재하는데요. 훅을 통해서 변수를 만들고, HTML 요소에 ref 속성으로 연결하면 화면에 해당 요소가 나타났을 때 위치를 변수에 저장해줍니다.</p>
<p>또한, map에서 두번째 매개변수가 인덱스 값인 것을 이용하여 목록 끝에서부터 5번째인 항목을 구하는 데 사용했습니다.
구하는 것은 movies 상태의 전체 길이에서 -5를 하여 구했습니다.</p>
<p><strong>page.js</strong></p>
<pre><code class="language-js">&#39;use client&#39;;

import { useEffect, useRef, useState } from &quot;react&quot;;
import MovieCard from &quot;./(MovieCard)/index&quot;;
import * as S from &#39;./style&#39;;

export default function Home() {
    // 일반적으로 초기값은 null로 선언
    const ref = useRef(null);

    ...

    return (
        &lt;S.ListContainer&gt;
            {movies ? movies.map((movie, idx) =&gt; (
                idx === (movies.length - 5) ? &lt;MovieCard key={movie.id} movie={movie} ref={ref} /&gt;
                : &lt;MovieCard key={movie.id} movie={movie} /&gt;
            )) : &lt;p&gt;Loading...&lt;/p&gt;}
        &lt;/S.ListContainer&gt;
    )
}</code></pre>
<h3 id="관측할-observer-생성하기">관측할 observer 생성하기</h3>
<p>관측을 위해 observer를 생성하지만, 마찬가지로 React 특성상 요소가 생긴 뒤에 observer를 생성해야 합니다.
따라서 useEffect를 통해 observer를 생성하고, dependancy array를 영화 목록이 저장된 movies로 지정합니다.</p>
<p>이렇게 하면 영화가 추가로 렌더링될 때마다 연장된 영화 목록에서 마지막 5번째 요소를 관측하는 observer를 새로 생성할 수 있습니다.</p>
<p>또한 observer가 새로 생성되면, 기존 observer는 파괴하기 위해 useEffect의 cleanup 함수를 이용했습니다.</p>
<blockquote>
<p><strong>❓ unobserve()와 disconnect()의 차이</strong>
제 코드에서는 disconnect를 사용하고 있는데, Intersection Observer API에는 관측 중인 연결을 끊기 위해서 unobserve() 메서드를 사용할 수도 있습니다.</p>
<p>unobserve()는 한 observer로 여러 요소를 관측 중일 때, 하나의 특정 요소에 대한 연결을 끊기 위해 사용되며, disconnect()는 해당 observer 객체와 연결된 모든 연결을 끊는다는 차이가 있습니다.</p>
</blockquote>
<p><strong>page.js</strong></p>
<pre><code class="language-js">...
export default function Home() {
    // 일반적으로 초기값은 null로 선언
    const ref = useRef(null);
    ...
    // 화면 요소를 1 증가시키는 사이드 이펙트
    useEffect(() =&gt; {
        // ref 달린 요소가 뷰포트의 가장 하단에 등장했을 때 페이지 값을 1 증가시키는 옵저버 생성
        const observer = new IntersectionObserver((entries) =&gt; {
            // target이 화면에 등장했을 때, moviePage 값 증가
            if (entries[0].isIntersecting) {
                setMoviePage((prevPage) =&gt; prevPage+1);
            }
        }, {
            root: null,
            threshold: 1,
        });

        // 대상이 존재할 때만 옵저버로 관측
        if (ref.current) {
            observer.observe(ref.current);
        }

        // cleanup
        return () =&gt; observer.disconnect();
    }, [movies]);

    return (
        &lt;S.ListContainer&gt;
            {movies ? movies.map((movie, idx) =&gt; (
                idx === (movies.length - 5) ? &lt;MovieCard key={movie.id} movie={movie} ref={ref} /&gt;
                : &lt;MovieCard key={movie.id} movie={movie} /&gt;
            )) : &lt;p&gt;Loading...&lt;/p&gt;}
        &lt;/S.ListContainer&gt;
    )
}</code></pre>
<h3 id="bug-fixedforwardref-오류-발생">[Bug] (fixed)forwardRef 오류 발생</h3>
<p>위와 같이 무한 스크롤을 적용하고, 새로 고침을 하니 다음과 같은 오류가 발생했습니다.</p>
<pre><code>Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()? Check the render method of `Home`. at MovieCard</code></pre><p>해당 오류로 검색하니 금방 원인을 찾을 수 있었습니다. React는 기본적으로 다른 컴포넌트에서 또 다른 컴포넌트의 DOM 요소에 접근하는 것이 불가능하다고 합니다. 그것이 자식 컴포넌트일지라도 예외는 없는데요. 대신 forwardRef API를 통해 ref를 자식 컴포넌트의 HTML 요소에게 전달하여 해결이 가능하다고 합니다.
<img src="https://velog.velcdn.com/images/dev4jaehun_kim/post/116d05a2-e8ff-423c-9f0e-3c2e4ef7ac6b/image.png" alt=""></p>
<p>이를 제 코드에 바로 적용해보았습니다.</p>
<p><strong>(MovieCard)/index.js</strong></p>
<pre><code class="language-js">import { useState, forwardRef } from &#39;react&#39;;
...
const MovieCard = forwardRef(({ movie }, ref) =&gt; {
    // 참고한 웹 주소 형태로 상태 저장
    const [poster, setPoster] = useState(`https://image.tmdb.org/t/p/w440_and_h660_face${movie.poster_path}`);
    return (
        // 기존 내용을 감싸기 위한 wrapping용 div를 추가하고 여기에 ref를 연결
        &lt;div ref={ref}&gt;
            ...
        &lt;/div&gt;
    )
});

export default MovieCard;</code></pre>
<h3 id="bug-fixed-component-definition-is-missing-display-nameeslintreactdisplay-name-오류-발생">[Bug] (fixed) <code>Component definition is missing display nameeslintreact/display-name</code> 오류 발생</h3>
<p>ref 문제를 해결하니까 새로운 문제가 곧바로 발생했는데요. ESLint의 React 관련 규칙에는 컴포넌트가 displayName을 가지고 있어야 한다는 규칙이 있다고 합니다.
하지만 forwardRef를 통해 생성된 컴포넌트는 기본적으로 displayName을 가지지 않아 생기는 문제였습니다.</p>
<p>아래와 같이 코드에 displayName을 수동으로 설정하도록 수정하여 해결했습니다.</p>
<p><strong>(MovieCard)/index.js</strong></p>
<pre><code class="language-js">import { useState, forwardRef } from &#39;react&#39;;
...
const MovieCard = forwardRef(({ movie }, ref) =&gt; {
    ...
});

MovieCard.displayName = &#39;MovieCard&#39;;

export default MovieCard;</code></pre>
<h1 id="결과-화면">결과 화면</h1>
<p><img src="https://velog.velcdn.com/images/dev4jaehun_kim/post/6f46124b-2c9c-48d1-b5cd-47393158f47d/image.gif" alt=""></p>
<h1 id="마치며">마치며</h1>
<p>5월 4주차 동안은 구현을 못 하다가 Intersection Observer API의 동작 원리를 이해하고, 곧바로 구현에 성공할 수 있었습니다. 이에 무작정 구현부터 시도하기 보다는 구현 시도와 기반이 되는 기술에 대한 학습을 적절히 섞어야겠다는 생각을 했습니다.</p>
<p>다음 글에는 이어서 스타일을 수정하여 화면의 완성도를 높이는 걸 목표로 합니다.</p>
<p>감사합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Intersection Observer API에서 무한 스크롤에 필요한 부분만 이해하기]]></title>
            <link>https://velog.io/@dev4jaehun_kim/Intersection-Observer-API%EC%97%90%EC%84%9C-%EB%AC%B4%ED%95%9C-%EC%8A%A4%ED%81%AC%EB%A1%A4%EC%97%90-%ED%95%84%EC%9A%94%ED%95%9C-%EB%B6%80%EB%B6%84%EB%A7%8C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dev4jaehun_kim/Intersection-Observer-API%EC%97%90%EC%84%9C-%EB%AC%B4%ED%95%9C-%EC%8A%A4%ED%81%AC%EB%A1%A4%EC%97%90-%ED%95%84%EC%9A%94%ED%95%9C-%EB%B6%80%EB%B6%84%EB%A7%8C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 29 May 2023 16:48:39 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>⚠️ Disclamer ⚠️
해당 요소는 제가 Intersection Observer API를 학습하며, 이해한 내용을 바탕으로 작성한 글입니다. 따라서 잘못된 이해로 인해 실제와 차이가 있을 수 있습니다.
잘못된 내용이 있다면 댓글로 알려주시면 감사하겠습니다. 🙏</p>
</blockquote>
<h1 id="시작하기에-앞서">시작하기에 앞서</h1>
<p>Intersection Observer API를 사용하여 무한스크롤을 구현하며, 도저히 내용이 이해가 되지 않아서 구현에 어려움이 있었습니다. 그러다가 아래 글들을 읽고, 드디어 이해하고 구현까지 성공했습니다.
감사한 마음에 글 가장 앞에 링크를 작성했습니다.</p>
<p> <strong>Special Thanks</strong></p>
<blockquote>
<p>다음 글들이 이해에 큰 도움이 되었습니다.
<a href="https://growingtangerine.tistory.com/70">https://growingtangerine.tistory.com/70</a>
<a href="https://growingtangerine.tistory.com/71">https://growingtangerine.tistory.com/71</a>
<a href="http://blog.hyeyoonjung.com/2019/01/09/intersectionobserver-tutorial/">http://blog.hyeyoonjung.com/2019/01/09/intersectionobserver-tutorial/</a></p>
<p>공식 링크는 다음을 참고 부탁드립니다.
<a href="https://developer.mozilla.org/ko/docs/Web/API/Intersection_Observer_API">https://developer.mozilla.org/ko/docs/Web/API/Intersection_Observer_API</a></p>
</blockquote>
<blockquote>
<p><strong>해당 API를 어떻게 무한스크롤에 적용했는지 궁금하다면...</strong>
<a href="https://velog.io/@dev4jaehun_kim/2023%EB%85%84-5%EC%9B%94-5%EC%A3%BC%EC%B0%A8-%EA%B8%B0%EB%8A%A5%EA%B5%AC%ED%98%84-1">https://velog.io/@dev4jaehun_kim/2023년-5월-5주차-기능구현-1</a></p>
</blockquote>
<h1 id="intersection-observer-api란">Intersection Observer API란?</h1>
<p>Intersection Observer API는 브라우저가 제공하는 Web API로 화면에 교차로 등장하는 부분을 관측해서 이를 알리는 API입니다.
어떠한 요소들이 교차되려면 하나의 요소로는 불가합니다. 따라서 해당 observer도 root 요소와 target 요소로 구분 지어 설명합니다.</p>
<h2 id="keyword">Keyword</h2>
<h3 id="root--target">root &amp; target</h3>
<p>root 요소는 null이거나 특정 element일 수 있습니다.
null로 설정한 경우 viewport, 즉 지금 화면에 보여지는 영역을 root로 설정합니다.</p>
<p>target은 직접 지정하는 요소입니다. target을 어떤 observer로 관측할지 observer() 메서드를 통해 지정할 수 있습니다. 무슨 말인지 이해되지 않더라도 이어서 읽어주시면 감사하겠습니다.</p>
<h3 id="entry">entry</h3>
<p>요소가 관측됐을 때 콜백 함수가 받는 객체입니다. IntersectionObserverEntry 타입의 객체로 이뤄지며, 콜백 함수에서는 배열 형태로 넘어오기 때문에 entries라는 이름으로 받습니다.
여러 요소를 관측할 수도 있기 때문에 배열로 넘어오며, 무한 스크롤을 구현할 때 있어 중요한 내용은 아니기 때문에 그냥 그렇구나 정도로 이해하고 넘어갔습니다.
<img src="https://velog.velcdn.com/images/dev4jaehun_kim/post/c3c0b677-3c4e-46af-955e-5fba837c9f94/image.png" alt=""></p>
<h3 id="isintersecting">isIntersecting</h3>
<p>entry가 가지는 속성으로 요소가 현재 화면에 나타나는 지 구분하는 boolean 타입의 속성입니다.
특정 요소를 관측하도록 observer() 메서드가 동작하면 false 값으로 설정된 entry가 생성되고, 스크롤을 내려 특정 요소가 나오면 true 값으로 전환 -&gt; 스크롤을 더욱 내려 특정 요소가 사라지면 다시 false 값으로 전환됩니다.</p>
<h3 id="threshold">threshold</h3>
<p>root와 target, 두 요소가 얼마나 교차해서 나타나는 지를 의미하는 값입니다.
해당 값은 0~1까지(소수점 두 자리로 나타냄; 0.01 ... 0.99, 1.00) target이 root의 어떤 위치에서 교차되는 지 나타냅니다.</p>
<h1 id="intersection-observer-api가-요소를-감지하는-방법">Intersection Observer API가 요소를 감지하는 방법</h1>
<p>그림으로 예시를 들면 {threshold: 0}은 target이 root의 최상단에 교차할 때를 의미합니다. (설명을 용이하게 하기 위해 root는 null값, 즉 뷰포트인 상태를 가정합니다.)
아래와 같이 아직 스크롤이 되지 않았다면 isIntersecting 값은 false입니다.
<img src="https://velog.velcdn.com/images/dev4jaehun_kim/post/e71d8a69-7197-487b-a706-f09dbd8ff6cd/image.png" alt=""></p>
<p>스크롤을 내리다가 target이 root의 최상위에 닿으면 isIntersecting 값이 true로 전환됩니다.
<img src="https://velog.velcdn.com/images/dev4jaehun_kim/post/7c2cf8bc-dee1-4881-add8-b7a69d4c3c75/image.png" alt=""></p>
<p>해당 값은 꼭 위끼리 닿을 필요 없이 아래가 닿거나 중간에 걸쳐있어도 교차하고 있는 것으로 판단합니다.
<img src="https://velog.velcdn.com/images/dev4jaehun_kim/post/8b805020-07cb-4ff7-b07b-ecd886c5e3c6/image.png" alt=""></p>
<p>threshold 값은 배열로도 설정할 수 있는데, 25%마다 target이 교차하는지 관측하고 싶은 경우라면 <code>[0, 0.25, 0.5 0.75, 1]</code>처럼 설정할 수 있습니다.
<img src="https://velog.velcdn.com/images/dev4jaehun_kim/post/f47b77e0-e5c6-486c-936c-d0b00e4d065b/image.png" alt=""></p>
<h1 id="마치며">마치며</h1>
<p>해당 글에서는 무한 스크롤을 구현하며 이해가 어려웠던 부분을 정리한 글입니다.
Intersection Observer API의 전체 내용이나 구현과 관련된 부분은 다루지 않고, 동작에 대한 개념적인 부분만 정리했습니다.</p>
<p>따라서 아래 내용은 별도로 작성하지 않았습니다.</p>
<ol>
<li>바닐라js인지 react인지, 아니면 또 다른 프론트엔드 라이브러리/프레임워크인지에 따라 달라지는 세부 구현</li>
<li>무한 스크롤 외 Intersection Observer API로 구현할 수 있는 다른 시나리오</li>
</ol>
<p>감사합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[내가 프로젝트를 시작할 때 기술 스택을 선택하는 법]]></title>
            <link>https://velog.io/@dev4jaehun_kim/%EB%82%B4%EA%B0%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EB%A5%BC-%EC%8B%9C%EC%9E%91%ED%95%A0-%EB%95%8C-%EA%B8%B0%EC%88%A0-%EC%8A%A4%ED%83%9D%EC%9D%84-%EC%84%A0%ED%83%9D%ED%95%98%EB%8A%94-%EB%B2%95</link>
            <guid>https://velog.io/@dev4jaehun_kim/%EB%82%B4%EA%B0%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EB%A5%BC-%EC%8B%9C%EC%9E%91%ED%95%A0-%EB%95%8C-%EA%B8%B0%EC%88%A0-%EC%8A%A4%ED%83%9D%EC%9D%84-%EC%84%A0%ED%83%9D%ED%95%98%EB%8A%94-%EB%B2%95</guid>
            <pubDate>Tue, 23 May 2023 07:51:42 GMT</pubDate>
            <description><![CDATA[<p>기술 스택을 선택할 때 제가 생각하는 중요한 요소들을 정리해봤습니다.
보통 나름의 기준을 가지고 이 스택이 적정 기술인지 생각해보고, 그 이유를 글로 작성해봅니다.
또한 실제로 이를 어떻게 스택 선택에 적용했는지 사례도 함께 가져왔습니다.</p>
<h1 id="생각하기">생각하기</h1>
<p>저는 새 프로젝트를 시작할 때 기술 스택을 선택하기에 앞서 다음과 같은 사항들을 고려합니다.</p>
<ol>
<li><strong>해당 기술이 스스로에게 있어 러닝커브가 큰지 판단합니다.</strong>
기존에도 이미 개발할 때 많이 사용해봤는지, 아니면 개발하면서 학습할 수 있는 정도인지, 별도의 몰입하는 학습이 필요한지로 판단합니다.</li>
<li><strong>파트별로 구식이지만 안정된 기술, 유행하는 기술, 완전히 새로운 기술인지를 판단합니다.</strong>
가급적이면 유행하는 기술로 선택하려고 합니다. 그래야 참고할 레퍼런스를 구하기 쉬우면서도, 성능과 안정성, 사용 편의성을 모두 취할 수 있습니다.</li>
<li><strong>1, 2번 과정을 거쳐 나온 후보의 장단점을 조사합니다.</strong></li>
</ol>
<h1 id="글로-작성하기">글로 작성하기</h1>
<p>후보들의 장단점을 머리 속으로 어느 정도 정리했다면 글로 정리하면서 다시 한 번 내용을 정리해봅니다.
작성한 내용을 읽으면서 다시 한 번 이 기술이 정말 적정 기술인지, 유행하는 기술로 구성한 나머지 러닝 커브가 너무 커지지 않았는지 다시 한 번 생각합니다.</p>
<p>사용법을 정리하기 어렵지 않다거나 팀원들이 모두 익숙한 개념과 비교하여 설명이 가능하다면 간단한 예제 코드도 정리하여 작성한 글에 첨부합니다.</p>
<blockquote>
<p>이렇게 정리해도 제한된 시간 내에 내용을 정리했기 때문에, 기술에 대해 잘못 이해했거나 잘못된 선택을 하는 경우가 분명 존재합니다. 하지만 항상 정답을 선택할 수 없는 게 사람이므로 빠르게 시도해보고 실패로부터 배워야 한다고 생각합니다.</p>
</blockquote>
<h1 id="예시">예시</h1>
<p>아래는 프론트엔드 부트캠프에서 팀프로젝트를 진행할 때 위와 같은 방식으로 기술 스택을 정리한 예시입니다.
<img src="https://velog.velcdn.com/images/dev4jaehun_kim/post/6841a9e1-c58b-49de-a696-e6ae4b876682/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/dev4jaehun_kim/post/8c6477d2-9794-4c1e-8faa-8370891ac112/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/dev4jaehun_kim/post/8c51e2a6-5e02-4aa5-9059-099bd300b52a/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/dev4jaehun_kim/post/6206b65b-ce8b-4e2e-8095-2134e4d4de61/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/dev4jaehun_kim/post/235daf16-243b-478d-8dff-8395f67248c0/image.png" alt=""></p>
<h1 id="결론">결론</h1>
<p>모두 기술스택을 선택할 때 자신만의 이유가 있겠지만, 단순히 많이 쓰이니까 또는 익숙하니까 선택하는 경우도 많은 것 같습니다.</p>
<p>따라서 스택을 선택한 이유를 글로 정리해서 다른 사람을 설득해보면, 이게 정말로 필요한지 또한 우리 팀의 역량에서 잘 소화할 수 있는지 알 수 있을 것 같아 저의 방법을 정리해 공유해봤습니다.</p>
<p>감사합니다. 😄</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2023년 5월 4주차 기능구현 - 1]]></title>
            <link>https://velog.io/@dev4jaehun_kim/2023%EB%85%84-5%EC%9B%94-4%EC%A3%BC%EC%B0%A8-%EA%B8%B0%EB%8A%A5%EA%B5%AC%ED%98%84-1</link>
            <guid>https://velog.io/@dev4jaehun_kim/2023%EB%85%84-5%EC%9B%94-4%EC%A3%BC%EC%B0%A8-%EA%B8%B0%EB%8A%A5%EA%B5%AC%ED%98%84-1</guid>
            <pubDate>Mon, 22 May 2023 08:27:42 GMT</pubDate>
            <description><![CDATA[<p>배운것에 비해 구현 속도가 많이 더딘 것 같아 취업 준비하는 기간 동안 스킬업과 구현 속도를 증가시키고자 부트캠프를 함께 했던 사람들과 스터디를 만들었습니다.</p>
<h1 id="스터디-진행-방식">스터디 진행 방식</h1>
<p>스터디 진행 방식은 다음과 같습니다.</p>
<ol>
<li><strong>주제선정</strong>
매주 하나의 프로젝트 주제를 선정합니다. 어떤 기술을 구현하는 걸 목표로 할 수도 있지만, 보통은 프론트엔드 분야의 작은 과제들이 모여 있는 <a href="https://www.frontendmentor.io/challenges">프론트엔드 멘토</a> 사이트에서 하나의 챌린지를 선택합니다.</li>
<li><strong>구현</strong>
해당 스터디에서 가장 많은 시간을 할애하는 부분입니다. 선정된 주제를 그 주 금요일까지 구현하여 공유합니다.</li>
<li><strong>리뷰</strong>
스터디원이 각자 공유한 코드를 읽고, 어려웠던 부분들을 찾아보거나 질문하고 싶은 내용들을 미리 정리합니다.</li>
<li><strong>회의</strong>
매주 일요일 오전 10시 30분에 Google Meet으로 모여 정리해둔 질문을 서로 나누며 의견을 짧게 주고 받고, 다음 주에 진행할 챌린지를 선정합니다.</li>
</ol>
<h1 id="5월-4주차-주제">5월 4주차 주제</h1>
<ul>
<li><strong>주제</strong>: 영화 데이터를 크롤링하여 무한 스크롤이 적용된 사이트를 원하는 스타일로 자유롭게 구현합니다.</li>
<li><strong>활용 데이터</strong>: <a href="https://www.themoviedb.org">https://www.themoviedb.org</a></li>
<li><strong>링크</strong>: <a href="https://github.com/dev4jaehunkim/infinite-scroll-movie-gallery">Git 저장소</a>, <a href="https://infinite-scroll-movie-gallery.vercel.app">배포</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Q&A] AWS S3에 업로드된 웹 페이지에 접속 시 로그인이 동작하지 않음]]></title>
            <link>https://velog.io/@dev4jaehun_kim/QA-AWS-S3%EC%97%90-%EC%97%85%EB%A1%9C%EB%93%9C%EB%90%9C-%EC%9B%B9-%ED%8E%98%EC%9D%B4%EC%A7%80%EC%97%90-%EC%A0%91%EC%86%8D-%EC%8B%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8%EC%9D%B4-%EB%8F%99%EC%9E%91%ED%95%98%EC%A7%80-%EC%95%8A%EC%9D%8C</link>
            <guid>https://velog.io/@dev4jaehun_kim/QA-AWS-S3%EC%97%90-%EC%97%85%EB%A1%9C%EB%93%9C%EB%90%9C-%EC%9B%B9-%ED%8E%98%EC%9D%B4%EC%A7%80%EC%97%90-%EC%A0%91%EC%86%8D-%EC%8B%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8%EC%9D%B4-%EB%8F%99%EC%9E%91%ED%95%98%EC%A7%80-%EC%95%8A%EC%9D%8C</guid>
            <pubDate>Wed, 17 May 2023 05:45:16 GMT</pubDate>
            <description><![CDATA[<h1 id="질문">질문</h1>
<p>AWS S3에 업로드된 웹 페이지에 접속하면 로그인 기능이 동작하지 않으며, 로컬에서는 정상 동작합니다.</p>
<h1 id="원인">원인</h1>
<p>로컬에서는 ID/PW를 입력하고 로그인 버튼 클릭 시 /login 주소로 라우팅되도록 하고 있었으며, 문제가 발생하신 분은 S3에 접속할 때 html 파일 주소로 접근하고 있었습니다.</p>
<p>이에 javascript가 제대로 연결되지 않아 react-router-dom이 동작하지 않으며 /login 주소로 라우팅되지 않았고, 그게 로그인이 안 되는 증상으로 나타났습니다.</p>
<h1 id="해결">해결</h1>
<p>S3는 웹 사이트를 호스팅해주는 기능이 있으며, <strong>버킷 웹사이트 엔드포인트 주소</strong>라는 이름으로 제공됩니다. 해당 주소로 접속 시 라우팅이 동작하여 기능을 정상적으로 사용할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Q&A] 어떤 경우에 외부에서 DOM 요소를 참조가 발생하는지]]></title>
            <link>https://velog.io/@dev4jaehun_kim/QA-%EC%96%B4%EB%96%A4-%EA%B2%BD%EC%9A%B0%EC%97%90-%EC%99%B8%EB%B6%80%EC%97%90%EC%84%9C-DOM-%EC%9A%94%EC%86%8C%EB%A5%BC-%EC%B0%B8%EC%A1%B0%EA%B0%80-%EB%B0%9C%EC%83%9D%ED%95%98%EB%8A%94%EC%A7%80</link>
            <guid>https://velog.io/@dev4jaehun_kim/QA-%EC%96%B4%EB%96%A4-%EA%B2%BD%EC%9A%B0%EC%97%90-%EC%99%B8%EB%B6%80%EC%97%90%EC%84%9C-DOM-%EC%9A%94%EC%86%8C%EB%A5%BC-%EC%B0%B8%EC%A1%B0%EA%B0%80-%EB%B0%9C%EC%83%9D%ED%95%98%EB%8A%94%EC%A7%80</guid>
            <pubDate>Wed, 17 May 2023 05:37:57 GMT</pubDate>
            <description><![CDATA[<h1 id="질문">질문</h1>
<p>DOM 외부에서의 참조는 GC(Garbage Collector)에 의해 자동으로 비워지지 않기 때문에 성능 저하가 발생된다고 하는데, 그러한 참조가 어느 경우에 발생하는지 궁금합니다.</p>
<h1 id="답변">답변</h1>
<h2 id="상황-가정">상황 가정</h2>
<p>아래와 같이 코드를 작성해서 <code>test</code>, <code>remove</code> 두 버튼을 만들었습니다. elemMap 객체에는 <code>test</code> 버튼이 연결된 상태입니다.</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot;&gt;
    &lt;meta http-equiv=&quot;X-UA-Compatible&quot; content=&quot;IE=edge&quot;&gt;
    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;
    &lt;title&gt;Document&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;button id=&quot;button&quot;&gt;test&lt;/button&gt;
    &lt;button id=&quot;remove&quot;&gt;remove&lt;/button&gt;
    &lt;script&gt;
        var elemMap = {
            button: document.getElementById(&#39;button&#39;),
        };

        function removeButton() {
            document.body.removeChild(document.getElementById(&#39;button&#39;));
        }

        const removeBtn = document.querySelector(&#39;#remove&#39;);
        removeBtn.addEventListener(&#39;click&#39;, removeButton);
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<h2 id="결과">결과</h2>
<ol>
<li>elemMap 객체가 선언되는 시점에 <code>test</code> 버튼의 DOM 요소를 참조하는 레퍼런스가 생성됩니다.<img src="https://velog.velcdn.com/images/dev4jaehun_kim/post/f4ee089b-bb75-49cb-af0e-7f590f520f19/image.png" alt=""></li>
<li><code>remove</code> 버튼을 누르면 DOM 요소에서는 <code>test</code> 버튼이 제거됩니다.<img src="https://velog.velcdn.com/images/dev4jaehun_kim/post/e22948c6-01b5-4c54-b77b-ab9e64c1753f/image.png" alt=""></li>
<li>하지만 개발자 도구의 console을 확인해보면 elemMap 객체에는 DOM 요소가 여전히 연결된 상태기 때문에 GC에 의해 비워지지 않습니다.
<img src="https://velog.velcdn.com/images/dev4jaehun_kim/post/e4de16e7-3bff-4fb7-a774-7ccced70b245/image.png" alt=""></li>
<li>DOM을 다시 제거하려 해도 삭제되지 않으며, 객체를 null 값으로 덮어 씌우거나 delete 연산자를 사용해야 삭제됩니다.<img src="https://velog.velcdn.com/images/dev4jaehun_kim/post/8f82b7e1-d49b-4b6e-9aa8-91b82c93a8fb/image.png" alt=""></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Q&A] 빈 배열 값에 reduce를 사용할 때 TypeError가 발생하는 이유]]></title>
            <link>https://velog.io/@dev4jaehun_kim/QA-%EB%B9%88-%EB%B0%B0%EC%97%B4-%EA%B0%92%EC%97%90-reduce%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%A0-%EB%95%8C-TypeError%EA%B0%80-%EB%B0%9C%EC%83%9D%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0</link>
            <guid>https://velog.io/@dev4jaehun_kim/QA-%EB%B9%88-%EB%B0%B0%EC%97%B4-%EA%B0%92%EC%97%90-reduce%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%A0-%EB%95%8C-TypeError%EA%B0%80-%EB%B0%9C%EC%83%9D%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0</guid>
            <pubDate>Wed, 17 May 2023 05:21:18 GMT</pubDate>
            <description><![CDATA[<p>이번 질문은 javascript에서 빈 배열 값(<code>const arr = []</code>)을 인덱스로 참조할 때(<code>arr[0]</code>) undefined로 평가되는데, 왜 reduce를 적용하려 하면 NaN이 아니라 Type Error가 발생하는지 궁금하다는 내용이었습니다.</p>
<p>MDN에 찾아본 결과, 초기값이 없는 빈 배열에 대해 reduce를 실행하는 경우 TypeError를 반환한다고 나와있습니다. <a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Errors/Reduce_of_empty_array_with_no_initial_value">링크</a></p>
<p>Array.prototype.reduce에 spec을 보면 해당 메서드가 호출됐을 때 실행되는 동작이 단계별로 나옵니다. 이 단계에서 4번을 보시면 길이가 0이거나 초기값이 존재하지 않는 배열인 경우 TypeError를 반환한다고 합니다.</p>
<p>즉, undefined 값을 평가해서 콜백함수에 전달하기 전에 저런 예외사항이 발견되면 즉시 예외를 반환하도록 방지 처리가 되어있기 때문입니다. <a href="https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.prototype.reduce">링크</a>
<img src="https://velog.velcdn.com/images/dev4jaehun_kim/post/adc3ec7e-58cd-4f99-804a-f65f677a0d0f/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Q&A] 권한이 있는 private repository에서 SSH 방식으로 git clone 실패]]></title>
            <link>https://velog.io/@dev4jaehun_kim/QA-%EA%B6%8C%ED%95%9C%EC%9D%B4-%EC%9E%88%EB%8A%94-private-repository%EC%97%90%EC%84%9C-SSH-%EB%B0%A9%EC%8B%9D%EC%9C%BC%EB%A1%9C-git-clone-%EC%8B%A4%ED%8C%A8</link>
            <guid>https://velog.io/@dev4jaehun_kim/QA-%EA%B6%8C%ED%95%9C%EC%9D%B4-%EC%9E%88%EB%8A%94-private-repository%EC%97%90%EC%84%9C-SSH-%EB%B0%A9%EC%8B%9D%EC%9C%BC%EB%A1%9C-git-clone-%EC%8B%A4%ED%8C%A8</guid>
            <pubDate>Wed, 17 May 2023 04:28:20 GMT</pubDate>
            <description><![CDATA[<h1 id="문제">문제</h1>
<p>부트캠프의 GitHub private repository들은 모두 SSH 방식으로만 clone할 수 있게 설정되어 있었는데요. 일부 사용자들은 SSH key 등록 이후에도 git clone이 안 되는 문제가 발생했습니다.</p>
<h1 id="단서-얻기">단서 얻기</h1>
<p>clone이 실패하는 사람들은 공통적으로 명령 실행 시 <code>ssh: connect to host github.com port 22: Operation timed out</code> 오류가 발생했습니다.</p>
<p>통신을 시도 했을 때 방화벽에 닫혀 통신이 실패하는 경우 time out 오류가 발생하기 때문에, 해당 키워드를 활용하여 구글에 검색을 시도한 결과 GitHub 공식 문서를 찾을 수 있었습니다.
<a href="https://docs.github.com/en/authentication/troubleshooting-ssh/using-ssh-over-the-https-port">https://docs.github.com/en/authentication/troubleshooting-ssh/using-ssh-over-the-https-port</a></p>
<h1 id="원인-및-해결방법">원인 및 해결방법</h1>
<p>문서의 내용을 요약하면 22번 포트가 차단된 경우 HTTPS의 포트인 443번을 통해 SSH를 연결해야 합니다.</p>
<p>아래 절차대로 진행하여 SSH 연결을 HTTPS 포트로 변경할 수 있습니다.</p>
<ol>
<li>터미널을 실행합니다.</li>
<li>터미널에서 <code>code ~/.ssh/config</code> 명령어를 실행합니다.</li>
<li>VS Code 창에 config이라는 이름으로 빈 파일이 열리면 아래 내용을 적어줍니다.<pre><code>Host github.com
 Hostname ssh.github.com
 Port 443</code></pre></li>
<li>내용을 다 적었으면 저장하고 파일을 닫은 뒤 다시 클론을 시도합니다.</li>
<li>SSH 특성상 처음 연결을 시도하면 서로의 키를 주고받은 뒤 정말로 연결할 거냐고 다시 한 번 묻습니다. 선택지로는 (yes|no|<footprint>) 형태로 표시되며, <code>yes</code>를 해주세요.
5-1. 한 번 yes해주시면 다시 묻지 않습니다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Q&A] typeof 1 < 2는 왜 false인가요?]]></title>
            <link>https://velog.io/@dev4jaehun_kim/QA-typeof-1-2%EB%8A%94-%EC%99%9C-false%EC%9D%B8%EA%B0%80%EC%9A%94</link>
            <guid>https://velog.io/@dev4jaehun_kim/QA-typeof-1-2%EB%8A%94-%EC%99%9C-false%EC%9D%B8%EA%B0%80%EC%9A%94</guid>
            <pubDate>Wed, 17 May 2023 04:15:53 GMT</pubDate>
            <description><![CDATA[<p>부트캠프 채팅에서 javascript에서 <code>typeof 1 &lt; 2</code>의 결과값이 왜 false인지 물어보는 질문이 올라와 이유를 정리해봤습니다.
<img src="https://velog.velcdn.com/images/dev4jaehun_kim/post/545676bb-7850-42e0-af8d-b817bdb51550/image.png" alt=""></p>
<h1 id="단서-얻기">단서 얻기</h1>
<p>우선 질문에 나온 것처럼 typeof를 각각 실행했을 때 어떤 결과가 나오는지 출력해봅니다.
크롬 개발자 도구에서 실행해보면 질문에서 말한 것과 똑같이 실행되는 것을 확인할 수 있습니다.
<img src="https://velog.velcdn.com/images/dev4jaehun_kim/post/abc8c935-b1fc-4b50-81e2-922f6c08857f/image.png" alt=""></p>
<p>javascript는 메서드나 연산자가 요구하는 자료 형태로 들어오는 값의 형태를 자동으로 바꿉니다.
다시 말해, 문구를 입력 받아 화면에 알림창을 띄워주는 alert()을 사용한다고 가정하면 <code>alert(null)</code>과 같이 null을 입력해도 이것은 <code>alert(&quot;null&quot;)</code>로 자동 변환되어 문제 없이 실행됩니다.</p>
<p>개발자 도구를 통해 확인해보면 지금 발생하는 문제에서도 같은 원리로 <code>typeof 1</code>의 결과값이 <code>&#39;number&#39;</code>로 변환되었다는 것을 알 수 있습니다.<img src="https://velog.velcdn.com/images/dev4jaehun_kim/post/4694237b-5ade-4b3c-bbd1-287135141ab2/image.png" alt=""></p>
<p>그럼 이 NaN을 각각의 숫자들과 비교해봅시다.
아래 사진을 보면 NaN은 어떤 숫자와 비교해도 숫자의 크고 작음과 관계 없이 항상 false를 반환하는 것을 발견할 수 있습니다.
<img src="https://velog.velcdn.com/images/dev4jaehun_kim/post/ca9fa059-f396-454a-89cc-26f9b86cf2e2/image.png" alt=""></p>
<h1 id="원인-찾기">원인 찾기</h1>
<p>기존 javascript의 형변환 시스템을 생각하면 falsy 값 중 하나인 NaN은 다른 숫자와 비교할 때 0으로 반환되어야 할 것 같습니다.
하지만 어떤 숫자 값과 비교해도 false를 반환한다는 것은, 직관적으로 생각했을 때 그렇게 반환되도록 정해진 결과라고 생각해볼 수 있습니다.</p>
<p>표준으로 그런 내용이 있는지 확인할 때는 MDN 문서를 참고하는 것이 가장 좋습니다. 예상대로 관계 비교 연산자와 함께 사용할 때는 항상 false를 반환한다는 것을 찾을 수 있었습니다.(<a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/NaN">출처</a>)
<img src="https://velog.velcdn.com/images/dev4jaehun_kim/post/3f9023cc-62d2-4d60-b10d-e0ef31d2c058/image.png" alt=""></p>
<p>p.s) NaN의 동작에 대해서는 MDN 문서에서도 나와있듯 javascript만의 고유한 동작이 아닌, IEEE 754에서 지정된 표준 동작입니다.</p>
<h1 id="결론">결론</h1>
<p><code>typeof 1</code>의 값이 숫자와의 비교를 위해 숫자로 변환을 시도하고, 그 과정에서 NaN 값이 되어 발생한 문제였습니다. 비교 연산자를 사용할 때는 되도록 숫자 형태로 값의 타입을 일치시켜준다면 이러한 문제를 회피할 수 있겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[웹 표준과 웹 접근성]]></title>
            <link>https://velog.io/@dev4jaehun_kim/%EC%9B%B9-%ED%91%9C%EC%A4%80%EA%B3%BC-%EC%9B%B9-%EC%A0%91%EA%B7%BC%EC%84%B1</link>
            <guid>https://velog.io/@dev4jaehun_kim/%EC%9B%B9-%ED%91%9C%EC%A4%80%EA%B3%BC-%EC%9B%B9-%EC%A0%91%EA%B7%BC%EC%84%B1</guid>
            <pubDate>Mon, 02 Jan 2023 13:52:03 GMT</pubDate>
            <description><![CDATA[<h1 id="들어가며">들어가며</h1>
<p>안녕하세요. 오늘은 웹 표준과 웹 접근성에 대해서 공부한 내용을 정리해보려 합니다.</p>
<p>정리하는 내용에서는 표준이 왜 필요하고, 표준을 잘 지킴으로써 얻는 이점이 무엇이 있는지 알아보며, 이를 통해 보다 좋은 프로그래머가 될 수 있도록 합니다.</p>
<p>웹 접근성 파트에서는 기술을 위한 기술이 아닌, 인간을 위한 기술에 대해 고민해보고 이를 달성하기 위한 방법들에 대해 알아볼 예정입니다.</p>
<h1 id="웹-표준">웹 표준</h1>
<p>웹 표준은 말 그대로 &#39;웹&#39;이라는 공간에서 통용되는 여러 표준들을 통틀어 웹 표준이라고 부릅니다. 이를 권고하는 기관은 W3C(World Wide Web Consortium)로 웹에서 표준적으로 사용되는 기술이나 규칙들을 표준으로 배포합니다. HTML, CSS, JS에 대한 표준도 이에 포함됩니다.</p>
<p>표준이 없던 시절에는 웹 브라우저마다 지원하는 태그나 기능들이 달라 각 브라우저에 대응되는 사이트를 만들어야 했다고 합니다. 상상만 해도 끔찍한 일이 아닐 수가 없는데요. 현대에 이르러서는 표준을 잘 지켜 개발하는 것만으로 어떤 운영체제/브라우저에서 웹 페이지에 접속하더라도 동등한 정보를 제공합니다.</p>
<blockquote>
<p>여기서 &#39;동등&#39;은 &#39;동일&#39;과는 다른 의미입니다. 브라우저가 다르다면 한 코드로 100% 동일한 화면을 제공한다는 것이 어려울 수 있습니다. 하지만 표준을 잘 지켜 개발할 시 누가 정보에 접근하든 개발자가 의도한 정보를 오롯이 표시할 수 있습니다.</p>
</blockquote>
<h2 id="seo와-웹-접근성-그리고-웹-표준의-관계">SEO와 웹 접근성, 그리고 웹 표준의 관계</h2>
<p>앞으로 글에서 설명할 SEO와 웹 접근성을 아래와 같이 정의합니다.</p>
<ul>
<li>SEO(Search Engine Optimization): 검색 시 우리의 웹 사이트가 검색 결과에 잘 표시되도록 최적화하는 행위</li>
<li>웹 접근성: 장애인, 고연령자와 같은 정보 소외 계층이나 통상적인 사용이 제한된 환경에서도 동등하게 정보에 접근할 수 있게 하는 특성</li>
</ul>
<p>SEO와 웹 접근성, 이 2개가 웹 표준과 어떻게 연관될까요? 우선 SEO부터 살펴보면 웹 표준, 그 중에서도 HTML 코드를 작성 시 의미가 분명하게 드러나도록 하는 요소들이 존재합니다. 이를 시맨틱(Semantic) 요소라고 부르는데요. 시맨틱 요소는 글의 주된 내용이 어디서 등장하는지를 마크업으로 나타내기 때문에 검색 엔진이 참고할 때 개발자의 의도대로 참조될 확률을 높여줍니다.</p>
<p>따라서 웹 표준을 지켜 코드를 작성, 즉 시맨틱 요소를 사용하여 코드를 작성한다면 자연스럽게 검색 엔진이 검색 결과에 등록할 확률이 높은 방식으로 코드를 작성하게 됩니다.</p>
<p>웹 접근성의 경우에도 이와 마찬가지로 시각장애인을 위한 스크린 리더 등이 동작할 때 어떤 부분이 본문인지 안내함으로써 실제 사용자에게 알리려고 하는 정보를 효율적으로 전달할 수 있습니다.</p>
<p>이처럼 웹 표준을 잘 지키는 것만으로 검색 효율성(SEO로 인한)과 웹 접근성이 향상되는 관계라고 설명할 수 있습니다.</p>
<h2 id="semantic-html">Semantic HTML</h2>
<p>현재의 HTML 표준에서는 시맨틱 요소로 코드를 작성하길 권장합니다. 실제로도 HTML로 복잡한 레이아웃을 작성하다 보면 div와 span만으로 화면을 구성하는 것보다 시맨틱 요소로 화면을 구성하는 것이 나중에 볼 때도 코드를 이해하기 쉽게 만들어줍니다.</p>
<p><code>&lt;header&gt;</code>, <code>&lt;nav&gt;</code>, <code>&lt;main&gt;</code> 등이 있으며 매우 많은 요소들이 있어 이를 모두 외우기 보다는 <a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element">공식 문서</a>를 참고하여 작성하기를 권장 드리는데요. 일부러 외우는 것보다 코드를 작성하며 자연스럽게 익히는 것이 더 효율적이기 때문입니다.</p>
<h1 id="웹-접근성">웹 접근성</h1>
<p>웹 접근성이 필요한 이유는 그것이 <em><strong>&#39;당연히&#39;</strong></em> 필요하기 때문이에요. 즉 당연히 컴퓨터나 스마트폰 등의 전자기기 사용이 능숙한 비장애인이 아니더라도 웹을 통해 정보에 접근할 수 있어야 합니다.</p>
<p>물론 웹 접근성은 소수자만을 위한 배려가 아니에요. 웹 접근성을 보장하는 것으로 운전 중과 같이 시각적인 확인이 어려울 때는 시각장애인이 웹을 사용하는 것처럼 스크린 리더를 통해 소리로 정보에 접근할 수 있기 때문입니다. 웹 사이트나 새로운 서비스를 만들 때도 항상 접근성을 고려하면 이용자 증가, 새로운 타겟 고객층 발굴, 다양한 사용환경 지원 확보로 이어집니다.</p>
<p>물론 자신이 경험해보지 못 한 환경의 접근성을 직관으로만 충족하기란 매우 어려운 일입니다. 그렇기에 웹 접근성을 잘 확보했는지 점검하기 위한 여러 지침들이 존재하는데요. W3C에서 배포하는 WCAG(Web Content Accessibility Guidelines) 2.0이 그 예입니다.</p>
<p>자세한 내용은 해당 지침을 기반으로 한국에 맞게 수정한 지침인 <strong>&#39;<a href="http://www.wa.or.kr/board/view.asp?sn=161&amp;page=1&amp;search=&amp;SearchString=&amp;BoardID=0004&amp;cate=">한국형 웹 콘텐츠 접근성 지침 2.1</a>&#39;</strong>을 참고 부탁드립니다.</p>
<h1 id="마지막으로">마지막으로</h1>
<p>사실 공부할 때나 토이 프로젝트에서까지 웹 접근성을 신경 쓰는 경우는 제 경험으로 미뤄보았을 때 통상적인 경우는 아닌 것 같습니다. 하지만 모든 사용자에게 가치 있는 서비스를 전달하기 위해서는 이러한 내용이 있다는 것을 기억하고, 적용하려는 노력이 중요한 것 같습니다.</p>
<p>웹 표준의 경우도 SEO나 웹 접근성 향상을 목적으로만 지키는 것이 아니라 내가 읽기 쉬운 코드를 작성한다는 측면에서 잘 지켜야 할 것입니다.</p>
<h1 id="인용">인용</h1>
<ul>
<li><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element">https://developer.mozilla.org/ko/docs/Web/HTML/Element</a></li>
<li><a href="http://www.wa.or.kr/board/view.asp?sn=161&amp;page=1&amp;search=&amp;SearchString=&amp;BoardID=0004&amp;cate=">http://www.wa.or.kr/board/view.asp?sn=161&amp;page=1&amp;search=&amp;SearchString=&amp;BoardID=0004&amp;cate=</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Redux 데이터 흐름(feat. Flux)]]></title>
            <link>https://velog.io/@dev4jaehun_kim/Redux-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%9D%90%EB%A6%84feat.-Flux</link>
            <guid>https://velog.io/@dev4jaehun_kim/Redux-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%9D%90%EB%A6%84feat.-Flux</guid>
            <pubDate>Sun, 01 Jan 2023 09:57:25 GMT</pubDate>
            <description><![CDATA[<h1 id="tldr">tl;dr</h1>
<ul>
<li>Redux를 통해 상태를 전역에서 관리할 수 있다.</li>
<li>Flux 패턴으로 전역 상태도 단방향 흐름을 유지할 수 있다.</li>
</ul>
<h1 id="들어가며">들어가며</h1>
<p>순수 React만을 사용하여 상태 관리를 하다 보면 숱하게 많은 어려움을 겪게 됩니다. React의 단방향 데이터 흐름이라는 특성 때문에 서로 다른 자식 컴포넌트가 같은 상태를 공유해야 하는 경우 공통된 부모 컴포넌트로 상태와 상태 갱신 함수를 올리고, Props Drilling이라는 기법을 통해 데이터를 전달해야 합니다.</p>
<h1 id="props-drilling">Props Drilling</h1>
<p>Props Drilling은 상위 컴포넌트가 가지고 있는 상태를 props를 통해 실제 그 상태를 사용하려는 컴포넌트에게 전달하는 기법입니다. 하지만 Props Drilling만으로 상태를 관리하다 보면 여러 문제상황을 만나게 되는데요. 아래 시나리오를 따라가며 어떤 문제들이 발생하는지 알아봅시다.</p>
<p><code>#문제1</code> 코드의 가독성을 해친다.
컴포넌트의 구조가 아래 트리와 같이 구성되어 있다고 가정해봅시다.
<img src="https://velog.velcdn.com/images/dev4jaehun_kim/post/eb141590-b437-4d9c-845d-88f3c993a237/image.png" alt="그림1. Props Drilling을 보여주기 위한 컴포넌트 트리구조 예시"></p>
<p><code>&lt;SecondChild D&gt;</code>가 화면에 Number 타입의 count 상태를 렌더링해주는 컴포넌트고, <code>&lt;SecondChild A&gt;</code>에서 +, - 버튼을 누를 때마다 count 상태가 변경되는 +1, -1되는 <strong>구조라고 하면</strong> 양 끝 모두에 상태를 내려주기 위하여 상태를 가져야 하는 공통 부모는 <code>&lt;Root&gt;</code> 컴포넌트가 됩니다.</p>
<p>여기서 첫번째 문제의 의미를 알 수 있는데요. 지금은 깊이가 3단이라서 추적이 쉽지만 하위로 컴포넌트가 늘어나다 보면 이 상태를 어디서부터 내려받는지, 상태 갱신이 어디서 되는지 추적하기가 매우 어려워집니다.</p>
<p><code>#문제2</code> 불필요한 리렌더링이 발생한다.
위 시나리오에서 count 상태가 변경됐을 때 렌더링이 다시 발생해야 하는 컴포넌트는 <code>&lt;SecondChild D&gt;</code>인데요. 해당 컴포넌트만이 실제 상태를 렌더링에 사용하고 있기 때문입니다.
<img src="https://velog.velcdn.com/images/dev4jaehun_kim/post/68697669-45e2-48f9-885f-f763029def3d/image.png" alt="그림2. 그림1에서 렌더링이 다시 되어야 하는 컴포넌트에 주황색 배경칠"></p>
<p>하지만 실제로는 모든 컴포넌트에서 리렌더링이 발생합니다. 그 이유는 <code>&lt;Root&gt;</code> 컴포넌트가 상태를 가지고 있기 때문인데요. React는 상태 값이 업데이트될 때 useState()로 상태가 선언된 컴포넌트를 리렌더링하므로 <code>&lt;Root&gt;</code>를 포함한 하위의 모든 컴포넌트가 리렌더링됩니다.
<img src="https://velog.velcdn.com/images/dev4jaehun_kim/post/5cdee1a6-d83e-4c1c-9c35-1a7a0aff95be/image.png" alt="그림3. 그림1에서 모든 컴포넌트에 주황색 배경칠"></p>
<h1 id="redux와-flux">Redux와 Flux</h1>
<p>Props Drilling 기법으로 상태를 교환할 때 발생하는 문제점을 해결하기 위한 도구가 Redux로, 상태를 Store라는 한 공간에 저장한 뒤 전역에서 이 Store에 접근할 수 있게 합니다.</p>
<p>Redux를 사용할 때는 아래 세 가지 원칙을 지켜야 합니다.
1.Single source of truth
    - 동일한 데이터는 항상 같은 곳에서 가지고 와야 합니다.
    - Redux에서는 Store라는 하나의 데이터 저장공간을 활용하여 구현합니다.
2. State is read-only
    - Redux 상태는 읽기 전용이므로 Action 객체를 통해 변경해야 합니다.
3. Changes are made with pure functions
    - 예상치 못 한 동작을 방지하기 위해 순수함수로 작성해야 합니다.</p>
<p>Flux 패턴은 Redux를 효율적으로 사용하기 위한 데이터 흐름 패턴입니다. 이 패턴은 Redux 원칙을 효율적으로 지키면서 여러 컴포넌트에서 발생하는 데이터 변경을 하나의 단방향 흐름으로 유지하도록 도움을 줍니다. 이를 위해 각 요소들의 역할을 제한하는데 예를 들면 아래와 같습니다.</p>
<ol>
<li>사용자에게 보이는 화면에서 사용자에 의해 발생한 &#39;입력&#39;(마우스 클릭 등을 포함한)으로부터 액션이 발생할 준비를 합니다.</li>
<li>액션은 상태를 어떻게 변경해야 할지를 명시한 객체입니다. 직접 기재할 수도 있지만 일반적으로 액션 생성자라고 하는 액션을 리턴하는 함수를 작성합니다. 액션 생성자는 액션을 포맷에 맞게 만들어 디스패쳐에 넘깁니다.</li>
<li>디스패쳐는 들어온 액션을 확인해서 알맞은 스토어로 보내는 역할을 합니다. 스토어는 자신 스스로 값을 설정하지 않으며 모든 액션을 받기 때문에 디스패쳐의 선별 과정이 필요합니다.</li>
<li>스토어가 업데이트 되면 이를 구독 중인 컴포넌트들에서 리렌더링이 발생합니다.</li>
</ol>
<p>위의 말은 아래 글을 요약한 글로 그림 자료가 포함된 상세 내용을 보면 이해가 더욱 쉽습니다.
<a href="https://bestalign.github.io/translation/cartoon-guide-to-flux/">https://bestalign.github.io/translation/cartoon-guide-to-flux/</a></p>
<h1 id="마지막으로">마지막으로</h1>
<p>Redux가 필요한 이유와 그게 어떤 것인지, 사용할 때는 어떤 패턴으로 사용하는지를 정리했습니다. 잘못된 내용이나 오탈자가 발견된다면 코멘트 부탁드리며, Flux 패턴에 대한 글은 강추 드리는 글이니 꼭 한 번 읽어보시면 좋을 것 같습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[HTTP] REST API에 대하여]]></title>
            <link>https://velog.io/@dev4jaehun_kim/HTTP-REST-API%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC</link>
            <guid>https://velog.io/@dev4jaehun_kim/HTTP-REST-API%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC</guid>
            <pubDate>Fri, 02 Dec 2022 05:56:29 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요. 오늘은 REST API에 대해 알아보려 합니다.</p>
<h1 id="rest-api의-역사">REST API의 역사</h1>
<p>REST API의 REST는 Representational State Transfer로 직역하자면 상태를 표현한 내용을 전달하는 것입니다. 처음 고안한 사람은 Roy Fielding으로 박사학위 논문 <a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm">Architectural Styles and
the Design of Network-based Software Architectures</a>에서 이를 소개하고 있습니다.</p>
<p>해당 논문의 챕터5 REST 부분을 보시면 통신에 있어 상세 구현이나 프로토콜적인 부분은 추상화하고, 통신할 때 필요한 자료와 자원에 집중하여 인터페이스를 설계하자는 개념입니다. 흔히 쓰는 RESTful의 의미는 REST의 개념을 잘 이해하고 적용한 좋은 설계의 API들을 RESTful하다고 표현합니다.</p>
<p>보다 RESTful한 API를 설계하기 위해 Conceptual했던 초기안을 기반으로 Leonard Richardson이라는 사람이 2008년 <a href="https://en.wikipedia.org/wiki/Richardson_Maturity_Model">RMM(Richardson Maturity Model)</a>을 제안합니다. REST 성숙도 모델이라고도 부르는 이 모델은 총 4단계로 이뤄지며 모델에서 요구하는 사항을 충족하는 것으로 예전보다 손쉽게 RESTful한 설계가 가능해졌습니다.</p>
<h1 id="rest-api란">REST API란?</h1>
<p>그러면 REST API의 탄생 배경을 알아봤으니 어떤 형태의 통신을 REST API라고 하는지 직접 알아봅시다.
이에 앞서 <a href="https://ko.wikipedia.org/wiki/HTTP">HTTP</a>에 대한 사전지식이 필요합니다. HTTP 글에서 클라이언트 요청을 인용하자면 아래와 같이 표현할 수 있습니다.</p>
<pre><code>GET /restapi/v1.0 HTTP/1.1
Accept: application/json
Authorization: Bearer UExBMDFUMDRQV1MwMnzpdvtYYNWMSJ7CL8h0zM6q6a9ntw</code></pre><p>이렇게 서버에서 요청을 받기 위한 주소와 방식을 제공하고 올바른 요청이 들어왔을 때 그에 대한 응답을 주는 것이 HTTP에서의 흔한 API 예라고 볼 수 있는데, RMM에서 제안한대로 올바른 메서드와 데이터 표현 방식을 가지 통신한다면 REST API입니다.</p>
<h1 id="rmm의-단계">RMM의 단계</h1>
<h2 id="0단계">0단계</h2>
<p>단순히 HTTP 프로토콜을 사용하기만 하는 단계로 목적에 알맞지 않은 메서드를 사용해서 요청하거나, 그에 대한 응답을 하는 API들이 해당하는 단계입니다. 0단계에 해당하는 API들은 REST API라고 볼 수 없습니다.
RESTful한 메서드들은 CRUD로 나눌 수 있는데 각각은 Create(생성), Read(조회), Update(갱신), Delete(삭제)를 의미합니다.
REST API의 개념이 널리 퍼져있지 않던 2000년대 초반에는 0단계에 해당하는 API들이 많았으며 조회를 하는데 POST 메서드를 사용하는 등에 관습이 있었는데요. 이때문에 요청과 응답을 직관적으로 이해하기 쉽지 않았습니다.</p>
<p>예를 들어 <code>POST /users</code>라는 요청을 보냈는데 0번째 사용자 정보를 반환하는 API가 있다면 아래 정보를 얻기 위해서는 어떤 API를 사용해야 하는지 직관적으로 이해하기 어렵습니다.</p>
<ol>
<li>모든 사용자의 정보를 조회하고 싶을 때</li>
<li>사용자를 생성하고 싶을 때</li>
<li>1, 2, ..., n번 사용자 정보를 조회하고 싶을 때</li>
</ol>
<h2 id="1단계">1단계</h2>
<p>1단계에서는 클라이언트가 요청할 때 특정 리소스를 확실하게 지정합니다.
위 예에서 <code>/users/</code>에 해당했던 부분을 endpoint라고 부르는데요. 모든 자원은 자신을 가리키는 endpoint를 가지게 하자는 개념입니다.
이렇게 하면 0단계에서 발생한 문제 3가지 중 1, 3번을 손쉽게 해결할 수 있습니다.</p>
<ol>
<li>모든 사용자의 정보를 조회하고 싶을 때
<code>POST /users</code>로 요청 시 모든 사용자 정보를 반환합니다.</li>
<li>특정 사용자 정보를 조회하고 싶을 때
<code>POST /users/1</code>, <code>POST /users/2</code>와 같은 형태로 사용자에 해당하는 id나 몇 번째 사용자를 호출할지를 지정할 수 있습니다.</li>
</ol>
<h2 id="2단계">2단계</h2>
<p>1단계에서도 여전히 POST로만 활용한다면 사용자 정보를 생성, 갱신, 삭제할 때는 별도의 이름으로 endpoint를 만들어야 하는데요. 이는 API의 명료성을 저하시킵니다.
그렇기 때문에 HTTP의 각 목적에 맞는 메서드를 사용하여 API를 요청하게 한다면 보다 RESTful한 설계가 가능합니다.</p>
<h3 id="post-crud---create">POST (CRUD - Create)</h3>
<p>자원을 생성할 때 사용하는 메서드입니다.<code>POST /users/1</code>로 요청하며 적절한 사용자 정보를 body에 담아 요청하면 사용자가 생성되어야 합니다. 2단계를 충족하기 위해서는 응답코드도 요청 방식에 따라 달라져야 하며 자세한 내용은 <a href="https://ko.wikipedia.org/wiki/HTTP">HTTP</a>를 참고해주세요.</p>
<p>생성 요청 기준으로 성공 시 <code>201 Created</code>가 반환됩니다.</p>
<h3 id="get-crud---read">GET (CRUD - Read)</h3>
<p>자원을 요청할 때 사용하는 메서드입니다. <code>GET /users/1</code>로 요청하면 id: 1의 사용자 또는 사용자 배열 중 1번째 사용자를 반환합니다. 이는 서버의 구현에 따라 달라질 수 있으며 id가 되는 것이 조금 더 의미가 분명한 디자인이라 볼 수 있습니다.</p>
<p>성공 시 서버는 <code>200 OK</code> 코드를 반환합니다.
또한 GET 요청으로는 서버의 데이터를 변화시킬 수 없어야 합니다.</p>
<h3 id="put-patch-crud---update">PUT, PATCH (CRUD - Update)</h3>
<p>자원을 갱신할 때 사용하는 메서드입니다. PUT은 데이터를 완전히 교체, PATCH는 데이터의 특정 부분만을 수정할 때 사용합니다.</p>
<p>멱등성(매 요청마다 같은 리소스를 반환하는 특성)을 가지는 메서드인지 잘 구분해서 사용해야 하는데요. POST는 멱등성을 가지지 않는 메서드입니다.</p>
<h3 id="delete-crud---delete">DELETE (CRUD - Delete)</h3>
<p>자원을 삭제할 때 사용하는 메서드입니다. 요청 시 자원이 삭제되어야 합니다.</p>
<h2 id="3단계">3단계</h2>
<p>요청은 2단계와 동일하게 요청하지만 서버는 HATEOAS(Hypermedia As The Engine Of Application State)라고 부르는 하이퍼미디어 컨트롤을 적용하여 응답합니다.</p>
<p>말이 어려울 수 있는데요. 쉽게 말하자면 요청이 들어왔을 때 서버가 응답하는 본문에 관련된 API들의 링크를 달아주는 형태입니다. 사용자 생성 요청이 들어왔는데 이미 해당 id로 사용자가 있다면 응답에 해당 사용자를 조회할 때 필요한 endpoint 주소와 어떤 메서드를 사용해야 하는지를 함께 반환하는 식의 응답 형태를 말합니다.</p>
<h1 id="open-api">Open API</h1>
<p>누구에게나 접근이 허용된 API를 말합니다.
이는 무제한적으로 사용이 가능하다는 의미는 아니며, 사전에 접근을 승인받거나(API Key를 신청하는 등) 요청 횟수 등에 제한이 있을 수 있습니다.</p>
<p>우리가 주변에서 흔히 접할 수 있는 Open API에는 정부가 <a href="https://www.data.go.kr/">공공 데이터 포털</a>을 통해 제공하는 API가 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JS] 프로토타입]]></title>
            <link>https://velog.io/@dev4jaehun_kim/JS-%ED%94%84%EB%A1%9C%ED%86%A0%ED%83%80%EC%9E%85</link>
            <guid>https://velog.io/@dev4jaehun_kim/JS-%ED%94%84%EB%A1%9C%ED%86%A0%ED%83%80%EC%9E%85</guid>
            <pubDate>Fri, 18 Nov 2022 06:46:30 GMT</pubDate>
            <description><![CDATA[<p>이번에는 프로토타입과 프로토타입 체인에 대해 학습한 내용을 정리해 보겠습니다.</p>
<blockquote>
<p><strong>학습목표</strong></p>
</blockquote>
<ol>
<li>프로토타입을 이해하고, 클래스/인스턴스와의 차이에 대해 설명할 수 있다.</li>
<li><code>.prototype</code> 속성과 <code>.__proto__</code> 속성이 무엇인지 이해하고, 차이를 설명할 수 있다.</li>
<li>프로토타입 체인에 대해 이해하고, 설명할 수 있다.</li>
</ol>
<h1 id="프로토타입">프로토타입</h1>
<p>프로토타입은 JS에서 객체 지향을 달성하기 위해 도입한 개념으로 객체의 원형을 복사하여 새로운 객체를 만드는 식으로 상속을 구현합니다. 엄밀히 말하면 새로운 객체를 만들 때 객체의 원형에 담긴 속성과 메서드가 복사되는 것이 아니며, <code>프로토타입 체인</code>이라는 것을 따라 올라가며 원형에 링크되는 형태로 동작합니다.</p>
<p>자식 클래스를 기반으로 인스턴스를 생성할 때 super 연산자를 통해 불러온 속성과 메서드를 인스턴스에 담는다면,
프로토타입을 통한 방식은 상속하여 새 객체를 생성할 때 프로토타입 속성에 담긴 값을 따라 상위 프로토타입 객체로 올라갑니다.</p>
<h1 id="프로토타입-속성">프로토타입 속성</h1>
<p>그러면 프로토타입 속성을 직접 열어보며 어떤 식으로 링크되는지 알아봅니다.
예를 들어 createElement를 이용해 span 태그를 생성했다고 가정하면, 해당 태그의 프로토타입 속성은 HTMLSpanElement가 됩니다.</p>
<pre><code class="language-javascript">const elSpan = document.createElement(&#39;span&#39;)
elSpan.__proto__</code></pre>
<p><img src="https://velog.velcdn.com/images/dev4jaehun_kim/post/aa88fb6c-e3ae-438a-85eb-8706693a0acf/image.png" alt="위 코드에서 생성한 `elSpan.__proto__`의 값"></p>
<p>여기에서 다시 한 번 프로토타입 속성을 조회를 반복해보면 상속 관계를 확인할 수 있습니다. (부모&lt;-자식 형태로 상속관계 표현)</p>
<blockquote>
<p>Object &lt;- EventTarget &lt;- Node &lt;- Element &lt;- HTMLElement &lt;- HTMLSpanElement</p>
</blockquote>
<p>위에서 사용한 <code>.__proto__</code> 속성 외에 <code>.prototype</code>이라는 속성도 존재하는데 전자의 속성은 객체의 원형 객체를 링크하는 속성으로 개별 객체가 가지는 속성입니다.</p>
<p>후자의 속성의 경우 생성자의 속성을 의미하는데, 위에서 예로 들었던 <code>elSpan</code> 변수를 다시 가져와서 설명해보면 <code>elSpan.__proto__</code>의 값은 elSpan의 생성자 함수인 <code>HTMLSpanElement.prototype</code>과 동일한 것입니다.</p>
<h1 id="프로토타입-체인">프로토타입 체인</h1>
<p>위에서 줄곧 설명했던 맞물려 상속된 형태가 곧 프로토타입 체인입니다.
<code>Object.create()</code> 메서드를 이용해 주어진 객체를 프로토타입 삼아 새 객체를 생성하는 것도 가능합니다.</p>
<p>이 경우 생성자 함수의 원형을 몰라도 객체의 원형을 유추하도록 시켜서 같은 프로토타입 속성을 가지는 새 인스턴스를 생성할 수 있습니다.</p>
<pre><code class="language-javascript">let person2 = Object.create(person1)
person1.__proto__
// person1의 프로토타입 속성에는 값이 빈 생성자 함수가 존재
// -&gt; constructor: ƒ Person(first, last, age, gender, interests)

person2.__proto__
// 반면 person2에는 person1의 값이 채워진 채로 등장
// -&gt; Person {first: &#39;Bob&#39;, last: &#39;Smith&#39;, age: 32, gender: &#39;male&#39;, interests: Array(2)}

// 새 객체를 생성한 것이기 때문에 값을 변경해도 원본 값이 바뀌진 않음
person2.age = 22
person1.age // -&gt; 32</code></pre>
<p>또한 클래스를 사용한 문법도 브라우저 내부적으로는 프로토타입 상속으로 변환되며, 가독성을 위해 클래스 문법을 지원합니다. 이와 같이 편의를 위해 제공하는 문법을 문법적 설탕(Syntactic Sugar)이라고 부릅니다.</p>
]]></description>
        </item>
    </channel>
</rss>