<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>웹개발 하고싶어용</title>
        <link>https://velog.io/</link>
        <description>프론트엔드 개발자 지망생입니다</description>
        <lastBuildDate>Wed, 08 Oct 2025 05:42:43 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>웹개발 하고싶어용</title>
            <url>https://velog.velcdn.com/images/liso_o/profile/8b79c48e-34d4-4665-8bd1-30ee3c2448db/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. 웹개발 하고싶어용. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/liso_o" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[코딩테스트] 자료구조 - Map & Set]]></title>
            <link>https://velog.io/@liso_o/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-Map-Set</link>
            <guid>https://velog.io/@liso_o/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-Map-Set</guid>
            <pubDate>Wed, 08 Oct 2025 05:42:43 GMT</pubDate>
            <description><![CDATA[<h3 id="1-set">1. Set</h3>
<p><code>Set</code>은 중복 없는 데이터를 저장하는 자료구조다</p>
<ul>
<li>중복 자동 제거</li>
<li>순서는 보장하지 않음</li>
<li>O(1) 시간복잡도</li>
<li>가장 빠름</li>
</ul>
<pre><code class="language-java">HashSet&lt;Integer&gt; set = new HashSet&lt;&gt;();
set.add(1);
set.add(2);
set.add(1);  // 중복, 추가 안됨
System.out.println(set.size());  // 2

// 존재 여부 확인
if (set.contains(1)) { }  // O(1)

// 삭제
set.remove(1);  // O(1)</code></pre>
<h4 id="1-1-treeset">1-1) TreeSet</h4>
<ul>
<li>자동 정렬(오름차순)</li>
<li>O(log n)</li>
<li>범위 검색이 필요할 때 사용</li>
</ul>
<pre><code class="language-java">TreeSet&lt;Integer&gt; set = new TreeSet&lt;&gt;();
set.add(5);
set.add(1);
set.add(3);
// 저장 순서: [1, 3, 5] 자동 정렬됨

// 유용한 메서드들
set.first();     // 1 (최솟값)
set.last();      // 5 (최댓값)
set.lower(3);    // 1 (3보다 작은 수 중 최대)
set.higher(3);   // 5 (3보다 큰 수 중 최소)
set.floor(4);    // 3 (4 이하인 수 중 최대)
set.ceiling(2);  // 3 (2 이상인 수 중 최소)


// 역순 정렬
TreeSet&lt;Integer&gt; set2 = new TreeSet&lt;&gt;(Collections.reverseOrder());</code></pre>
<h3 id="2-map">2. Map</h3>
<ul>
<li>Key-value 형태로 쌍을 저장함</li>
<li>순서를 보장하지 않음</li>
</ul>
<pre><code class="language-java">HashMap&lt;String, Integer&gt; map = new HashMap&lt;&gt;();

// 추가/수정
map.put(&quot;apple&quot;, 1);
map.put(&quot;banana&quot;, 2);
map.put(&quot;apple&quot;, 3);  // 덮어쓰기, apple의 값이 3으로 변경

// 조회
map.get(&quot;apple&quot;);  // 3
map.get(&quot;grape&quot;);  // null (없으면 null 반환)
map.getOrDefault(&quot;grape&quot;, 0);  // 0 (없으면 기본값 반환)

// 존재 여부
map.containsKey(&quot;apple&quot;);    // true
map.containsValue(2);        // true

// 삭제
map.remove(&quot;apple&quot;);

// 크기
map.size();</code></pre>
<h4 id="2-1-treemap">2-1) TreeMap</h4>
<ul>
<li>Key 기준으로 자동정렬</li>
</ul>
<pre><code class="language-java">TreeMap&lt;Integer, String&gt; map = new TreeMap&lt;&gt;();
map.put(3, &quot;three&quot;);
map.put(1, &quot;one&quot;);
map.put(2, &quot;two&quot;);
// Key 순서: 1, 2, 3 (자동 정렬)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[코딩테스트] 자료구조 - 리스트,스택,큐,우선순위 큐]]></title>
            <link>https://velog.io/@liso_o/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%EB%A6%AC%EC%8A%A4%ED%8A%B8%EC%8A%A4%ED%83%9D%ED%81%90%EC%9A%B0%EC%84%A0%EC%88%9C%EC%9C%84-%ED%81%90</link>
            <guid>https://velog.io/@liso_o/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%EB%A6%AC%EC%8A%A4%ED%8A%B8%EC%8A%A4%ED%83%9D%ED%81%90%EC%9A%B0%EC%84%A0%EC%88%9C%EC%9C%84-%ED%81%90</guid>
            <pubDate>Thu, 11 Sep 2025 05:57:12 GMT</pubDate>
            <description><![CDATA[<h3 id="1-리스트">1. 리스트</h3>
<p>리스트는 순서가 있는 자료구조로 중복을 허용하는 자료구조입니다.</p>
<pre><code class="language-java">// 선언
List&lt;Integer&gt; arrayList = new ArrayList&lt;&gt;();
List&lt;Integer&gt; linkedList = new LinkedList&lt;&gt;();

//할당부의 new ArrayList&lt;&gt;()의 &lt;&gt;()는 각각 제너릭 옵션,초기화 옵션이다.

// 배열에서 초기화
Integer[] array = {1, 2, 3, 4, 5};
List&lt;Integer&gt; listFromArray = new ArrayList&lt;&gt;(Arrays.asList(array));

// 리스트의 제너릭 옵션으로 리스트를 받을 수 있다.
// 보통 정점-간선 느낌의 그래프를 선언할때 해당 방식 씀
    private List&lt;List&lt;Integer&gt;&gt; adjList;

    public Graph(int vertices) {
        // 정점 수만큼 용량 지정
        adjList = new ArrayList&lt;&gt;(vertices);

        // 각 정점마다 빈 리스트 초기화
        for (int i = 0; i &lt; vertices; i++) {
            adjList.add(new ArrayList&lt;&gt;());
        }
    }</code></pre>
<ul>
<li>arraylist : 배열 기반이므로 인덱스 접근이 빠름.</li>
<li>linkedlist : 이중 연결 리스트 기반이므로 삽입,삭제가 빠름.</li>
</ul>
<h4 id="1-1-메소드crud">1-1) 메소드(CRUD)</h4>
<pre><code class="language-java">// 추가
list.add(element);              // 끝에 추가
list.add(index, element);       // 특정 위치에 삽입
list.addAll(collection);        // 다른 컬렉션 전체 추가

// addAll은 리스트에 리스트를 넣는다고 생각하면 됨
List&lt;Integer&gt; list1 = new ArrayList&lt;&gt;(Arrays.asList(1, 2, 3));
List&lt;Integer&gt; list2 = new ArrayList&lt;&gt;(Arrays.asList(4, 5, 6));

// - CASE 1
// list2의 모든 요소를 list1 끝에 추가
list1.addAll(list2);
System.out.println(list1); // [1, 2, 3, 4, 5, 6]

// - CASE 2
// 인덱스 1 위치에 list2 추가
list1.addAll(1,list2);
System.out.println(list1); // [1, 4, 5, 6, 2, 3]

// 조회
list.get(index);               // 인덱스로 조회
list.size();                   // 크기 반환
list.isEmpty();                // 비어있는지 확인

// 삭제
list.remove(index);            // 인덱스로 삭제
list.remove(element);          // 객체로 삭제
list.clear();                  // 전체 삭제</code></pre>
<h4 id="1-2-메소드검색-및-확인">1-2) 메소드(검색 및 확인)</h4>
<pre><code class="language-java">list.contains(element);        // 요소 포함 여부(boolean)
list.indexOf(element);         // 중복일 경우 처음에 나타나는 요소의 인덱스 번호
list.lastIndexOf(element);     // 마지막 인덱스</code></pre>
<h4 id="1-3-메소드정렬">1-3) 메소드(정렬)</h4>
<pre><code class="language-java">Collections.sort(list)
Collections.reverse(list)</code></pre>
<h3 id="2-스택">2. 스택</h3>
<p>스택은 위에 작성한 자바의 Arraylist와 linkedlist로 구현이 가능합니다.</p>
<h3 id="3-큐">3. 큐</h3>
<p>큐는 Arraylist보단 Linkedlist로 구현하는것이 더 효율적입니다</p>
<ul>
<li>Arraylist로 구현 시 <code>poll()</code> 연산에서 시간복잡도가 O(n)</li>
</ul>
<h3 id="3-1-큐-연산">3-1) 큐 연산</h3>
<pre><code class="language-java">queue.offer(1) // 큐 뒤쪽에 연산 추가

queue.poll() // 큐 앞쪽 요소 제거하고 *반환*

queue.peek() // 큐 앞쪽 요소 조회 (제거 안함)

queue.isEmpty() // 큐가 비어있는지 확인</code></pre>
<h3 id="3-2-큐--bfs">3-2) 큐 &amp; BFS</h3>
<pre><code class="language-java">public void bfs(int[][] graph, int start) {
    Queue&lt;Integer&gt; queue = new LinkedList&lt;&gt;();
    boolean[] visited = new boolean[graph.length];

    queue.offer(start);
    visited[start] = true;

    while (!queue.isEmpty()) {
        int current = queue.poll();
        System.out.print(current + &quot; &quot;);

        // 인접한 모든 노드를 큐에 추가
        for (int i = 0; i &lt; graph[current].length; i++) {
            if (graph[current][i] == 1 &amp;&amp; !visited[i]) {
                queue.offer(i);
                visited[i] = true;
            }
        }
    }
}</code></pre>
<p>큐는 추후에 정리할 너비 우선 탐색기법인 <code>BFS</code>에서 주로 사용합니다.</p>
<h3 id="3-3-큐의-구현체">3-3) 큐의 구현체</h3>
<pre><code class="language-java">
// 1. LinkedList
Queue&lt;Integer&gt; queue = new LinkedList&lt;&gt;();
// 장점: offer/poll 모두 O(1)
// 단점: 메모리 오버헤드

// 2. ArrayDeque
Queue&lt;Integer&gt; queue = new ArrayDeque&lt;&gt;();
// 장점: 메모리 효율적, 빠른 성능
// 단점: 없음 (대부분의 경우 최선)
// 웬만하면 이거 사용

// * Arraylist로는 큐 구현하지 말기</code></pre>
<h2 id="4-우선순위-큐priorityqueue">4. 우선순위 큐(PriorityQueue)</h2>
<p>우선순위 큐는 큐 내에서 우선순위가 높은 요소가 먼저 나오는 자료구조입니다.
최소 힙으로 구현되어 있으며, 숫자가 작을수록 높은 우선순위입니다.</p>
<ul>
<li>힙 구조라 큐 자체는 정렬된 순서가 아님</li>
</ul>
<h3 id="4-1-우선순위-큐-구현">4-1) 우선순위 큐 구현</h3>
<pre><code class="language-java">
// 1. 기본
PriorityQueue&lt;Integer&gt; minHeap = new PriorityQueue&lt;&gt;();

// 2. 역순으로 구현
PriorityQueue&lt;Integer&gt; maxHeap = new PriorityQueue&lt;&gt;(Collections.reverseOrder());

// 3. 커스텀 정렬
// 절댓값 기준 정렬
PriorityQueue&lt;Integer&gt; absHeap = new PriorityQueue&lt;&gt;((a, b) -&gt; {
    if (Math.abs(a) == Math.abs(b)) {
        return a - b;  // 절댓값 같으면 더 작은 수 우선
    }
    return Math.abs(a) - Math.abs(b);
});</code></pre>
<h2 id="etc-큐리스트의-용량-및-성능">etc) 큐,리스트의 용량 및 성능</h2>
<p>큐나 Array리스트 선언 시 초기 용량을 설정해주는 편이 더 성능이 좋아지는 특징이 있습니다.</p>
<p>자료구조의 용량이 예상 가능하면 선언시 용량을 설정해주는 편이 좋습니다.</p>
<pre><code class="language-java">// 문제의 범위가 1~10000이면
List&lt;Integer&gt; result = new ArrayList&lt;&gt;(10000);</code></pre>
<p><img src="https://velog.velcdn.com/images/liso_o/post/c184bc9b-17fc-4070-9dbb-5f7e799a8048/image.png" alt=""></p>
<ul>
<li>용량 지정이 없을경우 요소가 추가될때마다 재할당 및 요소 복사됨</li>
<li>용량 지정해줄 경우 추가만 됨</li>
</ul>
<p><img src="https://velog.velcdn.com/images/liso_o/post/844e86d8-7847-44e3-a068-f32645bd5ea5/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 7662-이중 우선순위 큐 & lazy deletion + clean]]></title>
            <link>https://velog.io/@liso_o/%EB%B0%B1%EC%A4%80-7662-%EC%9D%B4%EC%A4%91-%EC%9A%B0%EC%84%A0%EC%88%9C%EC%9C%84-%ED%81%90-lazy-deletion-clean</link>
            <guid>https://velog.io/@liso_o/%EB%B0%B1%EC%A4%80-7662-%EC%9D%B4%EC%A4%91-%EC%9A%B0%EC%84%A0%EC%88%9C%EC%9C%84-%ED%81%90-lazy-deletion-clean</guid>
            <pubDate>Wed, 03 Sep 2025 12:28:15 GMT</pubDate>
            <description><![CDATA[<h3 id="0-문제">0. 문제</h3>
<pre><code>https://www.acmicpc.net/problem/7662</code></pre><br>
<br>

<p>문제를 간략하게 요약하자면</p>
<p><code>기능 값</code> 모양으로 입력받으며 기능에 따라 값을 넣을지 뺄지를 선택하는 것이다.
<br></p>
<p>D 1 : 큐의 최댓값 삭제
D -1 : 큐의 최소값 삭제
I n : n이란값을 넣기
<br></p>
<p>최종적으로 <code>최댓값 최소값</code> 형태를 출력하는 것이고 만약 큐가 비어있다면 <code>empty</code> 를 출력한다.</p>
<br>

<h3 id="1-내-풀이">1. 내 풀이</h3>
<pre><code class="language-java">import java.util.*;
import java.io.*;

public class Main{
    public static void Que(String[] input,PriorityQueue&lt;Integer&gt; upperqueue,PriorityQueue&lt;Integer&gt; lowerqueue){
        // 우선순위큐를 2개 써서 하나는 오름차순 하나는 내림차순 으로 사용함

        String order = input[0];
        int num = Integer.parseInt(input[1]);

        if(Objects.equals(order, &quot;D&quot;)){
            if(num==-1){
                Integer a = upperqueue.poll(); // 최소값
                if(a!=null){
                    lowerqueue.remove(a);
                }

            }
            else{
                Integer a = lowerqueue.poll(); // 최댓값
                if(a!=null){
                    upperqueue.remove(a);
                }

            }
        }
        else{
            upperqueue.add(num);
            lowerqueue.add(num);
        }

    }
    public static void main(String[] args) throws IOException {
        BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));

        int tc = Integer.parseInt(bf.readLine());

        for(int i=0;i&lt;tc;i++){
            PriorityQueue&lt;Integer&gt; upperqueue = new PriorityQueue&lt;&gt;();
            PriorityQueue&lt;Integer&gt; lowerqueue = new PriorityQueue&lt;&gt;(Collections.reverseOrder());
            int ordercount = Integer.parseInt(bf.readLine());
            for(int j=0;j&lt;ordercount;j++){
                String[] input = bf.readLine().split(&quot; &quot;);
                Que(input,upperqueue,lowerqueue);
            }
            StringBuilder sb = new StringBuilder();
            if(upperqueue.isEmpty() &amp;&amp; lowerqueue.isEmpty()){
                sb.append(&quot;EMPTY&quot;);
            }
            else{
                sb.append(lowerqueue.peek()).append(&quot; &quot;).append(upperqueue.peek());
            }
            System.out.println(sb);
        }
    }
}</code></pre>
<br>
<br>
<br>
<br>

<p>나는 우선 순위 큐를 사용하였다.</p>
<br>

<pre><code class="language-java">PriorityQueue&lt;Integer&gt; upperqueue = new PriorityQueue&lt;&gt;();
PriorityQueue&lt;Integer&gt; lowerqueue = new PriorityQueue&lt;&gt;(Collections.reverseOrder());</code></pre>
<br>

<p>PriorityQueue는 기본적으로 어떤 값이 들어와도 오름차순 형태로 정렬해주는 Queue 자료구조다.
<br>
PriorityQueue 뒤에 <code>Collections.reverseOrder())</code> 를 사용하면 역순으로 정렬해준다.
<br>
PriorityQueue의 <code>peek() , poll()</code> 을 사용하여 각 큐에서 최솟값, 최댓값을 추출할 생각이다.</p>
<br>
<br>

<pre><code class="language-java">public static void Que(String[] input,PriorityQueue&lt;Integer&gt; upperqueue,PriorityQueue&lt;Integer&gt; lowerqueue){
        // 우선순위큐를 2개 써서 하나는 오름차순 하나는 내림차순 으로 사용함

        String order = input[0];
        int num = Integer.parseInt(input[1]);

        if(Objects.equals(order, &quot;D&quot;)){
            if(num==-1){
                Integer a = upperqueue.poll(); // 최소값
                if(a!=null){
                    lowerqueue.remove(a);
                }
            }
            else{
                Integer a = lowerqueue.poll(); // 최댓값
                if(a!=null){
                    upperqueue.remove(a);
                }
            }
        }
        else{
            upperqueue.add(num);
            lowerqueue.add(num);
        }

    }</code></pre>
<br>

<p><code>poll()</code> 과 <code>peek()</code>은 큐가 비었을 경우 <code>null</code>을 반환시킨다.</p>
<p>따라서 추출한 값이 null이 아닌 경우(실제로 추출했을때)만 다른 큐도 똑같이 해당 값을 삭제해줘서 두개의 큐를 동기화 시켜줬다.
<br>
로직만으로는 정상 작동하였지만 시간초과가 발생하였다.</p>
<h3 id="2-문제점">2. 문제점</h3>
<p>시간초과의 원인은 <code>Queue.remove(num)</code> 에서 발생하였다.
<code>Queue.remove(num)</code> 은 내부를 선형 탐색하여 값을 찾는것으로 한쪽에서 값을 삭제(추출) 시키면 다른쪽에서 해당 값을 찾는데 처음부터 끝까지 순회해서 찾아야 한다.
<br>
따라서 개수가 많아질수록 당연히 선형 탐색하는 시간이 길어져 시간초과가 날 수 밖에 없다.
<br></p>
<h3 id="3-해결법">3. 해결법</h3>
<h4 id="3-1-lazy-detection--clean">3-1. Lazy detection + clean()</h4>
<p>이번에 처음 듣는 기법이였다.</p>
<ul>
<li>Lazy detection
Lazy detection은 두개의 큐를 동기화할때 한쪽만 처리하고 나머지 큐는 Map을 통해 검증하여 쓰레기값일 경우 그냥 없애버리는 방식이다.<br>
해당 기법의 가장 핵심은 ```Map<Integer,Integer> cnt``` 를 통한 유효 카운팅을 이용해 유효 값만 남기고 나머지는 버려버리는 기법이다.
<br>
```java
upperqueue.add(x);
lowerqueue.add(x);

</li>
</ul>
<p>if (map.containsKey(x)) {
    map.put(x, map.get(x) + 1);
} else {
    map.put(x, 1);
}</p>
<pre><code>x라는 값을 두 큐에 넣었을 경우 ```map``` 자료구조에 해당 값이 몇개 있는지 넣어준다.

```java
static void clean(PriorityQueue&lt;Integer&gt; pq, Map&lt;Integer,Integer&gt; cnt) {
    while (!pq.isEmpty()) {
        int x = pq.peek();
        Integer c = cnt.get(x);
        if (c == null || c == 0) pq.poll(); // 구식 값 버리기
        else break; // 유효한 값이면 멈춤
    }
}</code></pre><p>여기가 가장 중요한 곳이다.
큐의 <code>head</code>값을 검사하여 <code>map</code>에 더이상 없는(카운팅이 되지 않는)값일경우 전부 버려버린다.
언제까지 &gt; 큐가 전부 비거나 유효값(아직 살아있는값)이 나올때까지 버린다.
<br>
위 방식을 이용하면 결론적으로 선형 큐를 딱 한번만 탐색하는 모습이 그려진다.
<br>
<code>clean()</code> 함수가 동작하는 작은 예시로</p>
<pre><code>I 3
I 3
I 2
D 1</code></pre><p>라는 테스트케이스가 있다고 가정하자.</p>
<ul>
<li><p>case1 (I 3)</p>
<pre><code>minPQ = [3]
maxPQ = [3]
cnt = {3:1}</code></pre></li>
<li><p>case2 (I 3)</p>
<pre><code>minPQ = [3,3]
maxPQ = [3,3]
cnt = {3:2}</code></pre></li>
<li><p>case3 (I 2)</p>
<pre><code>minPQ = [2,3,3] // 오름차순 우선순위큐
maxPQ = [3,3,2] // 내림차순 우선순위큐
cnt = {3:2 , 2:1}</code></pre></li>
<li><p>case4 (D 1) 최댓값 삭제</p>
<pre><code>clean(maxPQ,cnt)
maxPQ.peek() = 3 // cnt[3] = 2니까 유효한 상태임
maxPQ.poll() // cnt[3] = 1 꺼냇으니까 1 줄어듬
</code></pre></li>
</ul>
<p>clean(minPQ,cnt)
minPQ.peek() = 2 // cnt[2] = 1니까 유효한 상태임 건드리기x</p>
<pre><code>
결론적으로 최종 코드는
```java
import java.util.*;
import java.io.*;

public class Main {

    // pq의 꼭대기에 이미 반대쪽에서 소진되어 유효하지 않은 값(= cnt[x]==0)이 올라와 있으면
    // 그 값을 버리면서( poll ) 유효한 값이 나올 때까지 정리해주는 함수
    static void clean(PriorityQueue&lt;Integer&gt; pq, Map&lt;Integer, Integer&gt; cnt) {
        while (!pq.isEmpty()) {
            int x = pq.peek();
            Integer c = cnt.get(x);
            if (c == null || c == 0) {
                pq.poll(); // 구식(stale) 값 제거
            } else {
                break;     // 유효한 값이면 중단
            }
        }
    }


    public static void Que(String[] input,
                           PriorityQueue&lt;Integer&gt; minPQ,
                           PriorityQueue&lt;Integer&gt; maxPQ,
                           Map&lt;Integer,Integer&gt; cnt) {

        String order = input[0];
        int num = Integer.parseInt(input[1]);

        if (order.equals(&quot;I&quot;)) {
            // 삽입: 두 힙에 모두 넣고 유효 개수 +1
            minPQ.offer(num);
            maxPQ.offer(num);
            cnt.put(num, cnt.getOrDefault(num, 0) + 1);
        } else { // &quot;D&quot;
            if (num == 1) { // 최댓값 삭제
                clean(maxPQ, cnt);
                if (!maxPQ.isEmpty()) {
                    int top = maxPQ.poll();
                    int c = cnt.get(top) - 1;
                    if (c == 0) cnt.remove(top);
                    else cnt.put(top, c);
                }
            } else {        // num == -1, 최솟값 삭제
                clean(minPQ, cnt);
                if (!minPQ.isEmpty()) {
                    int top = minPQ.poll();
                    int c = cnt.get(top) - 1;
                    if (c == 0) cnt.remove(top);
                    else cnt.put(top, c);
                }
            }
        }
    }

    public static void main(String[] args) throws IOException {
        BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
        StringBuilder out = new StringBuilder();

        int tc = Integer.parseInt(bf.readLine());

        for (int i = 0; i &lt; tc; i++) {
            int ordercount = Integer.parseInt(bf.readLine());

            PriorityQueue&lt;Integer&gt; minPQ = new PriorityQueue&lt;&gt;();                         // 최소힙
            PriorityQueue&lt;Integer&gt; maxPQ = new PriorityQueue&lt;&gt;(Collections.reverseOrder()); // 최대힙
            Map&lt;Integer,Integer&gt; cnt = new HashMap&lt;&gt;();                                    // 유효 개수 카운트

            for (int j = 0; j &lt; ordercount; j++) {   // ← j 사용 주의!
                String[] input = bf.readLine().split(&quot; &quot;);
                Que(input, minPQ, maxPQ, cnt);
            }

            // 출력 전 최종 정리
            clean(minPQ, cnt);
            clean(maxPQ, cnt);

            if (cnt.isEmpty()) {
                out.append(&quot;EMPTY\n&quot;);
            } else {
                out.append(maxPQ.peek()).append(&#39; &#39;).append(minPQ.peek()).append(&#39;\n&#39;);
            }
        }

        System.out.print(out.toString());
    }
}</code></pre><h4 id="3-2-treemap">3-2. Treemap</h4>
<p>PQ는 head만 처리하고 tail은 처리하지 않아 Lazy Detection 기법을 사용해서 풀어야만 했다.</p>
<p>하지만, 중복을 허용하면서 정렬 기반 자료구조며 tail도 처리 가능한 자료구조인 <code>Treemap</code>을 사용하는 방법이 오히려 이 문제에 더 적합한 자료구조다.</p>
<ul>
<li>전체 코드<pre><code class="language-java">import java.io.*;
import java.util.*;
</code></pre>
</li>
</ul>
<p>public class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringBuilder out = new StringBuilder();
        int T = Integer.parseInt(br.readLine());</p>
<pre><code>    for (int tc = 0; tc &lt; T; tc++) {
        int k = Integer.parseInt(br.readLine());
        TreeMap&lt;Integer, Integer&gt; map = new TreeMap&lt;&gt;();

        for (int i = 0; i &lt; k; i++) {
            StringTokenizer st = new StringTokenizer(br.readLine());
            String op = st.nextToken();
            int x = Integer.parseInt(st.nextToken());

            if (op.equals(&quot;I&quot;)) {
                map.put(x, map.getOrDefault(x, 0) + 1);
            } else { // &quot;D&quot;
                if (map.isEmpty()) continue; // 비었으면 무시
                if (x == 1) { // 최댓값 삭제
                    Map.Entry&lt;Integer,Integer&gt; e = map.lastEntry();
                    int cnt = e.getValue() - 1;
                    if (cnt == 0) map.pollLastEntry(); else map.put(e.getKey(), cnt);
                } else {      // 최솟값 삭제
                    Map.Entry&lt;Integer,Integer&gt; e = map.firstEntry();
                    int cnt = e.getValue() - 1;
                    if (cnt == 0) map.pollFirstEntry(); else map.put(e.getKey(), cnt);
                }
            }
        }

        if (map.isEmpty()) out.append(&quot;EMPTY\n&quot;);
        else out.append(map.lastKey()).append(&#39; &#39;).append(map.firstKey()).append(&#39;\n&#39;);
    }
    System.out.print(out.toString());
}</code></pre><p>}</p>
<pre><code>
위 코드를 살펴보면
```java
if (map.isEmpty()) continue;</code></pre><p>우선 삭제 명령인 D가 들어갔는데 맵이 비어있으면 아무것도 하지 않고 넘겨준다.</p>
<pre><code class="language-java">                    if (x == 1) { // 최댓값 삭제
                        Map.Entry&lt;Integer,Integer&gt; e = map.lastEntry();
                        int cnt = e.getValue() - 1;
                        if (cnt == 0) map.pollLastEntry(); else map.put(e.getKey(), cnt);
                    } else {      // 최솟값 삭제
                        Map.Entry&lt;Integer,Integer&gt; e = map.firstEntry();
                        int cnt = e.getValue() - 1;
                        if (cnt == 0) map.pollFirstEntry(); else map.put(e.getKey(), cnt);
                    }
</code></pre>
<p>최댓값 삭제일 경우 오름차순 정렬이므로 가장 뒤에 있는 값의 <code>Entry</code>를 가져온다.
여기서 <code>Entry</code>는 <code>(key,value)</code>쌍을 담는 객체다.
최댓값을 추출했으므로 해당 값의 카운팅(cnt)를 하나 줄여준 뒤에 값을 검사한다.
cnt가 0이면 더이상 필요없는 값이므로 map에서 없애준다 <code>pollLastEntry</code>
cnt가 0이 아니면 값만 갱신해준다. <code>map.put</code></p>
<pre><code class="language-java">if (map.isEmpty()) out.append(&quot;EMPTY\n&quot;);
else out.append(map.lastKey()).append(&#39; &#39;).append(map.firstKey()).append(&#39;\n&#39;);</code></pre>
<p>결론적으로 정렬된 map을 통해 최대,최소 혹은 비어있는지 유무까지 검사하여 최종 답안을 도출할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 1919 - 애너그램]]></title>
            <link>https://velog.io/@liso_o/%EB%B0%B1%EC%A4%80-1919-%EC%95%A0%EB%84%88%EA%B7%B8%EB%9E%A8</link>
            <guid>https://velog.io/@liso_o/%EB%B0%B1%EC%A4%80-1919-%EC%95%A0%EB%84%88%EA%B7%B8%EB%9E%A8</guid>
            <pubDate>Thu, 27 Feb 2025 09:00:03 GMT</pubDate>
            <description><![CDATA[<h3 id="1-문제">1. 문제</h3>
<pre><code>두 영어 단어가 철자의 순서를 뒤바꾸어 같아질 수 있을 때, 
그러한 두 단어를 서로 애너그램 관계에 있다고 한다. 
예를 들면 occurs 라는 영어 단어와 succor 는 서로 애너그램 관계에 있는데, 
occurs의 각 문자들의 순서를 잘 바꾸면 succor이 되기 때문이다.

한 편, dared와 bread는 서로 애너그램 관계에 있지 않다. 
하지만 dared에서 맨 앞의 d를 제거하고, bread에서 제일 앞의 b를 제거하면,
ared와 read라는 서로 애너그램 관계에 있는 단어가 남게 된다.

두 개의 영어 단어가 주어졌을 때, 
두 단어가 서로 애너그램 관계에 있도록 만들기 위해서 제거해야 하는 최소 개수의 문자 수를 구하는 프로그램을 작성하시오. 
문자를 제거할 때에는 아무 위치에 있는 문자든지 제거할 수 있다.


</code></pre><p><a href="https://www.acmicpc.net/problem/1919">https://www.acmicpc.net/problem/1919</a></p>
<h3 id="2-내-풀이">2. 내 풀이</h3>
<pre><code class="language-java">import java.io.*;
import java.sql.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;
import java.util.stream.Collectors;


public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

        String word1 = bf.readLine();
        String word2 = bf.readLine();

        String[] parts1 = word1.split(&quot;&quot;);
        String[] parts2 = word2.split(&quot;&quot;);

        Arrays.sort(parts1);
        Arrays.sort(parts2);
        int count1 = 0;
        int count2 = 0;
        for(int i=0;i&lt;parts1.length;i++){
            for(int j=0;j&lt;parts2.length;j++){
                if(Objects.equals(parts1[i], parts2[j])){
                    parts1[i]=&quot;0&quot;;
                    parts2[j]=&quot;0&quot;;
                    count1++;
                    count2++;
                    break;
                }
            }
        }
        int result = parts1.length-count1 + parts2.length-count2;
        bw.write(result+&quot;&quot;);
        bw.flush();
        bw.close();
    }
}</code></pre>
<h3 id="3-다른-풀이">3. 다른 풀이</h3>
<pre><code class="language-java">import java.io.*;
import java.util.Arrays;
import java.util.List;
import java.util.ArrayList;
import java.util.Scanner;


public class Main {
    public static void main(String[] args) throws IOException {
            Scanner sc = new Scanner(System.in);

            String a = sc.next();
            String b = sc.next();

            int[] countA = new int[26];
            int[] countB = new int[26];

            for(int i=0;i&lt;a.length();i++){
                countA[a.charAt(i)-&#39;a&#39;]++; // char - char 계산은 아스키코드로 변환 후 정수 계산 방식으로 전환된다.
            }
            for(int i=0;i&lt;b.length();i++){
                countB[b.charAt(i)-&#39;a&#39;]++;
            }

            int answer = 0;
            for(int i=0;i&lt;26;i++){
                if(countA[i]&gt;countB[i]){ // A의 단어 갯수가 더 많은 경우
                    answer += countA[i]-countB[i]; // 만약 a,b 둘다 가지고 있는 단어지만 A가 더 많을 경우 A에서 지워야 할 갯수를 알려준다.
                }
                if(countB[i]&gt;countA[i]){
                    answer += countB[i]-countA[i];
                }
            }
            System.out.println(answer+&quot;&quot;);
    }
}</code></pre>
<p>아스키 코드를 이용한 방법이다.</p>
<p><code>char-char</code> 를 통해 정수 방식으로 계산하여 몇 번째 인덱스인지 확인하는 방법이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 18258 - 큐2]]></title>
            <link>https://velog.io/@liso_o/%EB%B0%B1%EC%A4%80-18258-%ED%81%902</link>
            <guid>https://velog.io/@liso_o/%EB%B0%B1%EC%A4%80-18258-%ED%81%902</guid>
            <pubDate>Mon, 10 Feb 2025 12:38:45 GMT</pubDate>
            <description><![CDATA[<h3 id="1-문제">1. 문제</h3>
<p><a href="https://www.acmicpc.net/problem/18258">https://www.acmicpc.net/problem/18258</a></p>
<pre><code class="language-java">정수를 저장하는 큐를 구현한 다음, 입력으로 주어지는 명령을 처리하는 프로그램을 작성하시오.

명령은 총 여섯 가지이다.

push X: 정수 X를 큐에 넣는 연산이다.
pop: 큐에서 가장 앞에 있는 정수를 빼고, 그 수를 출력한다. 만약 큐에 들어있는 정수가 없는 경우에는 -1을 출력한다.
size: 큐에 들어있는 정수의 개수를 출력한다.
empty: 큐가 비어있으면 1, 아니면 0을 출력한다.
front: 큐의 가장 앞에 있는 정수를 출력한다. 만약 큐에 들어있는 정수가 없는 경우에는 -1을 출력한다.
back: 큐의 가장 뒤에 있는 정수를 출력한다. 만약 큐에 들어있는 정수가 없는 경우에는 -1을 출력한다.

#입력
첫째 줄에 주어지는 명령의 수 N (1 ≤ N ≤ 2,000,000)이 주어진다. 
둘째 줄부터 N개의 줄에는 명령이 하나씩 주어진다. 
주어지는 정수는 1보다 크거나 같고, 100,000보다 작거나 같다. 
문제에 나와있지 않은 명령이 주어지는 경우는 없다.</code></pre>
<h3 id="2-내-풀이">2. 내 풀이</h3>
<pre><code class="language-java">import java.io.*;
import java.util.List;
import java.util.ArrayList;


public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));


        int count = Integer.parseInt(bf.readLine()); // 명령의 갯수
        List&lt;Integer&gt; queue = new ArrayList&lt;&gt;();

        for(int i=0;i&lt;count;i++){
            String input = bf.readLine();
            String[] part = input.split(&quot; &quot;);

            String order = part[0];
            int value=0;
            if(part.length==2){
                value = Integer.parseInt(part[1]);
            }
            switch (order) {
                case &quot;push&quot;:
                    queue.add(value);
                    break;
                case &quot;pop&quot;:
                    if (queue.isEmpty()) {
                        bw.write(-1 + &quot;\n&quot;);
                    } else {
                        int popnum = queue.get(0);
                        queue.remove(Integer.valueOf(popnum));
                        bw.write(popnum + &quot;\n&quot;);
                    }
                    break;
                case &quot;size&quot;:
                    bw.write(queue.size() + &quot;\n&quot;);
                    break;
                case &quot;empty&quot;:
                    int isEmpty = queue.isEmpty() ? 1 : 0;
                    bw.write(isEmpty + &quot;\n&quot;);
                    break;
                case &quot;front&quot;:
                    if (queue.isEmpty()) {
                        bw.write(-1 + &quot;\n&quot;);
                    } else {
                        bw.write(queue.get(0) + &quot;\n&quot;);
                    }
                    break;
                case &quot;back&quot;:
                    if (queue.isEmpty()) {
                        bw.write(-1 + &quot;\n&quot;);
                    } else {
                        bw.write(queue.get(queue.size() - 1) + &quot;\n&quot;);
                    }
                    break;
                default:
                    break;
            }
        }
        bw.flush();
        bw.close();

    }

}
</code></pre>
<p>큐는 동적으로 크기가 변환되는 특징을 가지고 있기 때문에 java의 배열로는 구현이 힘들다고 생각했다.</p>
<p>따라서 ArrayList를 이용하여 동적 배열 및 switch를 통해 메소드 case에 따른 동작을 지정해주었다.</p>
<p>하지만, 위 코드는 시간초과가 났다.</p>
<p>내 코드의 문제점은 다음과 같았다.</p>
<h4 id="1-arraylist를-큐로-사용한-점">1. ArrayList를 큐로 사용한 점</h4>
<ul>
<li>ArrayList를 큐로 사용하면 queue.remove(0)같은 연산은 O(N) 만큼의 시간이 걸린다</li>
<li>ArrayList 대신 LinkedList나 ArrayDequeue 사용</li>
<li>또한 큐 구현에는  Dequeue가 더 적합하다.<h4 id="2-queueremoveintegervalueofpopnum">2. queue.remove(Integer.valueOf(popnum));</h4>
</li>
<li>pop 에서 작성한 코드인데 어차피 맨 앞의 인덱스 빼는건데 왜 굳이 이렇게 사용했는지 모르겠음</li>
</ul>
<h3 id="3-최종-풀이">3. 최종 풀이</h3>
<pre><code class="language-java">import java.io.*;
import java.util.Deque;
import java.util.ArrayDeque;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

        int count = Integer.parseInt(bf.readLine());
        Deque&lt;Integer&gt; queue = new ArrayDeque&lt;&gt;();

        for (int i = 0; i &lt; count; i++) {
            String input = bf.readLine();
            String[] part = input.split(&quot; &quot;);

            switch (part[0]) {
                case &quot;push&quot;:
                    queue.addLast(Integer.parseInt(part[1]));  // O(1) 연산
                    break;
                case &quot;pop&quot;:
                    bw.write(queue.isEmpty() ? &quot;-1\n&quot; : queue.pollFirst() + &quot;\n&quot;);  // O(1) 연산
                    break;
                case &quot;size&quot;:
                    bw.write(queue.size() + &quot;\n&quot;);
                    break;
                case &quot;empty&quot;:
                    bw.write(queue.isEmpty() ? &quot;1\n&quot; : &quot;0\n&quot;);
                    break;
                case &quot;front&quot;:
                    bw.write(queue.isEmpty() ? &quot;-1\n&quot; : queue.peekFirst() + &quot;\n&quot;);
                    break;
                case &quot;back&quot;:
                    bw.write(queue.isEmpty() ? &quot;-1\n&quot; : queue.peekLast() + &quot;\n&quot;);
                    break;
            }
        }
        bw.flush();
        bw.close();
    }
}</code></pre>
<ul>
<li><code>Dequeue&lt;Integer&gt; queue = new ArrayDequeue&lt;&gt;();</code> 를 통해 큐 구현</li>
<li>정수를 입력하는 push에만 <code>part[1]</code> (사용자입력란) 넣어줌</li>
<li>Dequeue 내 메소드 ex)pollFirst , peekFirst , peekLast  사용</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 1193 - 분수 찾기]]></title>
            <link>https://velog.io/@liso_o/%EB%B0%B1%EC%A4%80-1193-%EB%B6%84%EC%88%98-%EC%B0%BE%EA%B8%B0</link>
            <guid>https://velog.io/@liso_o/%EB%B0%B1%EC%A4%80-1193-%EB%B6%84%EC%88%98-%EC%B0%BE%EA%B8%B0</guid>
            <pubDate>Thu, 23 Jan 2025 06:52:48 GMT</pubDate>
            <description><![CDATA[<h3 id="1-문제">1. 문제</h3>
<p><a href="https://www.acmicpc.net/problem/1193">https://www.acmicpc.net/problem/1193</a></p>
<blockquote>
</blockquote>
<p>무한히 큰 배열에 다음과 같이 분수들이 적혀있다.
1/1    1/2    1/3    1/4    1/5    …
2/1    2/2    2/3    2/4    …    …
3/1    3/2    3/3    …    …    …
4/1    4/2    …    …    …    …
5/1    …    …    …    …    …
…    …    …    …    …    …
이와 같이 나열된 분수들을 1/1 → 1/2 → 2/1 → 3/1 → 2/2 → … 과 같은 지그재그 순서로 차례대로 1번, 2번, 3번, 4번, 5번, … 분수라고 하자.
X가 주어졌을 때, X번째 분수를 구하는 프로그램을 작성하시오.</p>
<h3 id="2-내-풀이">2. 내 풀이</h3>
<p>대각선 경로 하나당 한 사이클의 규칙을 찾아 코드를 작성했다</p>
<pre><code class="language-java">import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;

import java.util.Objects;


public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

        int input = Integer.parseInt(bf.readLine());
        int count = 1; // 현재 수
        int check = 1; // 기준 수

        while(true){
            if(input==1){
                bw.write(&quot;1/1&quot;);
                break;
            }
            if(count+check&lt;input){
                count=count+check; // 현재 수
                check++; // 기준 수 증가
            }
            else if(count+check&gt;input){
                bw.flush();
                int k =(input-count)+1;
                int n =(count+check)-input;
                if(check%2==1){
                    //기준 수가 홀수일 경우
                    bw.write(n+&quot;/&quot;+k);
                    bw.flush();
                    break;
                }
                else{
                    // 기준 수가 짝수일 경우
                    bw.write(k+&quot;/&quot;+n);
                    bw.flush();
                    break;
                }
            }
        }



        bw.close();

    }

}</code></pre>
<p>위 풀이는 시간초과가 걸렸다.</p>
<p>생각해보니까 <code>count+check == input</code> 인 경우를 if문에 넣어주지 않았다.
<del>(위 이유때문에 특정 수에서 무한루프 돌았음)</del></p>
<p>하지만 위 조건을 넣어줄 경우 input이 2일때 바로 결과를 출력하는 if문으로 넘어가게 된다.</p>
<p>바로 넘어갈 경우 기준 수가 1인데 두번째 라인에서 함수가 돌고있는 엉뚱한 상황이 발생한다.</p>
<p>처음에 시간초과가 떳길래 풀이를 찾아봤는데 나랑 똑같은 로직이라 일부분을 수정하여 리팩토링 하였다.(변수명 변경 등등)</p>
<h3 id="3-최종-풀이">3. 최종 풀이</h3>
<pre><code class="language-java">import java.io.*;


public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

        int input = Integer.parseInt(bf.readLine());
        int prevLastnum = 0; // 이전 라인의 마지막 수
        int cross_count = 1; // 이번 라인에서의 분수의 나열 갯수

        while(true){
            if(input==1){
                bw.write(&quot;1/1&quot;);
                break;
            }
            if(prevLastnum + cross_count &lt;input){
                prevLastnum = prevLastnum + cross_count; // 이전 라인의 마지막 수 증가(범위 내에 없으므로 끝까지 보냄)
                cross_count++; // 다음라인(분수 나열갯수 증가)
            }
            else if(prevLastnum + cross_count &gt;=input){
                bw.flush();
                int k =(input- prevLastnum);
                int n =(prevLastnum + cross_count)-input+1;
                if(cross_count %2==1){
                    //라인 수가 홀수일 경우
                    bw.write(n+&quot;/&quot;+k);
                    bw.flush();
                    break;
                }
                else{
                    // 라인 수가 짝수일 경우
                    bw.write(k+&quot;/&quot;+n);
                    bw.flush();
                    break;
                }
            }
        }



        bw.close();

    }

}
</code></pre>
<p><img src="https://velog.velcdn.com/images/liso_o/post/0cc4778f-22d8-4a77-b56f-124c0d09f415/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Ch 01. 문자열]]></title>
            <link>https://velog.io/@liso_o/Ch-01.-%EB%AC%B8%EC%9E%90%EC%97%B4</link>
            <guid>https://velog.io/@liso_o/Ch-01.-%EB%AC%B8%EC%9E%90%EC%97%B4</guid>
            <pubDate>Wed, 22 Jan 2025 08:16:55 GMT</pubDate>
            <description><![CDATA[<h2 id="01-java-string">01. Java String</h2>
<p><code>Java.lang.String</code> 으로 사용되는 java 문자열 클래스인 String은 별도의 import 없이 사용 가능하다.</p>
<h3 id="01-1-문자열-수정">01-1. 문자열 수정</h3>
<ul>
<li>한 번 인스턴스가 생성되면 수정 할 수 없음<pre><code class="language-java">String str = &quot;abcdef&quot;;
str[4] = &#39;E&#39;;
System.out.println(str); // 컴파일 에러</code></pre>
</li>
<li>값의 변경은 불가능하지만 새 String을 만들어서 바꿀 수는 있음.<pre><code class="language-java">String str = &quot;abcdef&quot;;
str = &quot;ABCDEF&quot;;
System.out.println(str); // ABCDEF
</code></pre>
</li>
</ul>
<p>======================================</p>
<p>String str = &quot;abcdef&quot;;
char[] temp_arr = str.toCharArray(); // 배열로 변환
temp_arr[4] = &#39;E&#39;;
str = new String(temp_arr) // 배열을 string으로 변환 후 기존 str에 대입
System.out.println(str)// abceEf</p>
<pre><code>
### 01-2. 서로 같은 두 문자열


java에서 ```==``` 연산은 주소값을 비교하기 때문에 다음과 같은 경우에는 서로 다른 주소이므로 false가 발생한다

```java
String str1 = &quot;abc&quot;; 
String str2 = new String(&quot;abc&quot;); 

str1==str2 // false 
</code></pre><p>리터럴 방식으로 선언한 서로 같은 두 문자열을 비교 시 true를 반환한다.</p>
<p>리터럴 방식으로 선언하면 <code>String Constant Pool</code> 이라는 영역에 할당된다.</p>
<pre><code class="language-java">// 리터럴 방식 선언
String str_literal1 = &quot;test&quot;;
String str_literal2 = &quot;test&quot;;
str_literal1 == str_literal2 // true</code></pre>
<p><img src="https://velog.velcdn.com/images/liso_o/post/b77a99fb-4ca0-41db-8198-33974ba0af26/image.png" alt=""></p>
<p>위와 같이 선언하면 <code>String constant pool</code> 영역 내에 &quot;test&quot;가 할당되고
두개 모두 하나의 &quot;test&quot;를 가르키게 된다.</p>
<p>따라서 비교시 true가 나옴</p>
<p>반면 객체 방식으로 선언한 서로 같은 두 문자열을 비교하면 false를 반환한다.</p>
<pre><code class="language-java">// 객체 방식 선언
String str_object1 = new String(&quot;test&quot;);
String str_object2 = new String(&quot;test&quot;);

str_object1==str_object2 // false</code></pre>
<p><img src="https://velog.velcdn.com/images/liso_o/post/092d8d4c-d9bd-4797-8844-8667e25bf56a/image.png" alt=""></p>
<p>기존에 같은 값을 가지고 있는 string이 heap 영역에 있더라도 따로 확인하지 않고 새로운 주소에 선언하기 때문.</p>
<p>String의 value를 비교하는 방법은 <code>.equals(value)</code>  를 사용하면 된다.</p>
<pre><code class="language-java">String str_literal1 = &quot;test&quot;;

String str_object1 = new String(&quot;test&quot;);
String str_object2 = new String(&quot;test&quot;);

str_literal1.equals(str_object1) // true
str_literal1.equals(str_object2) // true
str_object1.equals(str_object2) // true</code></pre>
<ul>
<li>자주 사용하는 String Methods</li>
</ul>
<table>
<thead>
<tr>
<th>Method name</th>
<th align="left">Return Value</th>
<th align="center">설명</th>
</tr>
</thead>
<tbody><tr>
<td>charAt(int index)</td>
<td align="left">char</td>
<td align="center">index 번째 문자</td>
</tr>
<tr>
<td>length()</td>
<td align="left">int</td>
<td align="center">문자열의 길이</td>
</tr>
<tr>
<td>equals(Object)</td>
<td align="left">boolean</td>
<td align="center">두 문자열 비교</td>
</tr>
<tr>
<td>toCharArray()</td>
<td align="left">char[]</td>
<td align="center">문자열을 변환한 character array</td>
</tr>
<tr>
<td>compareTo(String)</td>
<td align="left">int</td>
<td align="center">두 문자열을 사전순으로 비교한 결과</td>
</tr>
<tr>
<td>contains(char)</td>
<td align="left">boolean</td>
<td align="center">문자열 포함 여부</td>
</tr>
<tr>
<td>replace(char target,char replacement)</td>
<td align="left">String</td>
<td align="center">targer을 replacement로 변경</td>
</tr>
<tr>
<td>substring(int startIndex,int endIndex</td>
<td align="left">String</td>
<td align="center">[startIndex,endIndex) fmf 가지는 새 문자열 반환</td>
</tr>
<tr>
<td>indexOf(int ch,int fromIndex</td>
<td align="left">int</td>
<td align="center">fromIndex 부터 ch 문자가 나타나는 가장 첫 인덱스</td>
</tr>
</tbody></table>
<p><del>2025.01.22</del></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL 24) CORS 정책과 Proxy]]></title>
            <link>https://velog.io/@liso_o/TIL-24-CORS-%EC%A0%95%EC%B1%85%EA%B3%BC-Proxy</link>
            <guid>https://velog.io/@liso_o/TIL-24-CORS-%EC%A0%95%EC%B1%85%EA%B3%BC-Proxy</guid>
            <pubDate>Wed, 07 Jun 2023 12:32:02 GMT</pubDate>
            <description><![CDATA[<h2 id="1-cors">1. CORS</h2>
<p>CORS란 특정 유형의 악성 웹 콘텐츠로부터 사용자를 보호하기 위해 만들어진 보안 메커니즘이다.</p>
<p>웹 페이지나 애플리케이션이 제공하는 출처가 아닌 다른 출처의 리소스와 상호 작용할 수 있는 방법을 결정하는 규칙이다.</p>
<h3 id="1-sop">1. SOP</h3>
<p>CORS를 사용하기 전에 SOP를 먼저 알아두면 좋다.</p>
<blockquote>
<p>Same Origin Policy
동일 출처 정책, 같은 출처의 리소스만 공유 가능하다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/liso_o/post/80991414-59a4-49d7-960a-a1669493f5f2/image.png" alt=""></p>
<p>URL은 <code>Protocol / Host / Path</code> 로 나뉘어지는데 이 3개가 모두 동일하면 같은 출처라고 볼 수 있다.</p>
<p>하나라도 다르면 다른 출처라고 인식된다.</p>
<blockquote>
<p>만약 로그인 토큰이 어떠한 방법으로 인해 탈취당한 후 악성 사용자가 토큰을 이용해 부정적인 이득을 취하려 해도 동일 출처가 아니기 때문에 SOP에 위배된다.</p>
</blockquote>
<p>하지만, 다른 출처의 접근도 허용할 경우가 생긴다. 이때 사용하는 것이 CORS이다.</p>
<h3 id="2-cors">2. CORS</h3>
<p>CORS는 다른 출처의 자원을 공유하는 것으로 크게 3가지 동작 방식이 있다.</p>
<blockquote>
<ul>
<li>Preflight Request</li>
<li>Simple Request</li>
<li>Credentialed Request</li>
</ul>
</blockquote>
<p><code>Preflight Request</code>는 실제 요청을 보내기 전, Option 메서드로 사전 요청을 보내 접근 권한이 있는지 확인하는 요청이다.</p>
<p><code>Access-Control-Allow-Origin</code>으로 요청을 보낸 출처가 돌아오면 실제 요청을 보내게 된다.</p>
<p><code>simple Request</code> 는 특정 조건이 만족되면 프리폴라이트 요청을 생략하고 보내는 것이다.</p>
<p>조건에는 GET,HEAD,POST 요청 중 하나이며 Accept, Accept-Language, Content-Language, Content-Type 헤더의 값만 수동으로 설정하 수 있어야 한다.</p>
<h2 id="2proxy">2.Proxy</h2>
<p>React 라이브러리에서 CORS를 우회하는 방법이 있는데 그것이 바로 proxy다.</p>
<p><img src="https://velog.velcdn.com/images/liso_o/post/e152dab1-f8b5-4203-aff8-0f266ba9e1f2/image.png" alt=""></p>
<p>Proxy를 적용하기 전의 흐름</p>
<p><img src="https://velog.velcdn.com/images/liso_o/post/b68eb9a4-7c78-468a-a27d-8cde1789c484/image.png" alt=""></p>
<p>Proxy를 적용한 후 흐름</p>
<p>Proxy를 사용하는 방법엔 두 가지가 있다.</p>
<p>첫 번째는 webpack dev server에서 제공하는 proxy 기능을 이용하는 방법이 있다.</p>
<p>package.json에서 proxy를 설정해줄 수 있다.</p>
<pre><code class="language-json">&quot;browserslist&quot;: {
    &quot;production&quot;: [
      &quot;&gt;0.2%&quot;,
      &quot;not dead&quot;,
      &quot;not op_mini all&quot;
    ],
    &quot;development&quot;: [
      &quot;last 1 chrome version&quot;,
      &quot;last 1 firefox version&quot;,
      &quot;last 1 safari version&quot;
    ]
  },
    &quot;proxy&quot; : &quot;우회할 API 주소&quot;
}</code></pre>
<p>그 후, 기존의 api call 코드에서 도메인 부분을 제거한다.</p>
<pre><code class="language-jsx">export async function getAllfetch() {

    const response = await fetch(&#39;우회할 api주소/params&#39;);
    .then(() =&gt; {
            ...
        })
}


export async function getAllfetch() {

    const response = await fetch(&#39;/params&#39;);
    .then(() =&gt; {
            ...
        })
}</code></pre>
<p>React에서 proxy를 사용하는 다른 방법은 <code>http-proxy-middleware</code> 라이브러리를 설치하는 것이다.</p>
<p><code>npm install http-proxy-middleware --save</code> 로 설치 후</p>
<pre><code class="language-jsx">// setupProxy.js
const { createProxyMiddleware } = require(&#39;http-proxy-middleware&#39;);

module.exports = function(app) {
  app.use(
    &#39;/api&#39;, //proxy가 필요한 path prameter를 입력합니다.
    createProxyMiddleware({
      target: &#39;http://localhost:5000&#39;, //타겟이 되는 api url를 입력합니다.
      changeOrigin: true, //대상 서버 구성에 따라 호스트 헤더가 변경되도록 설정하는 부분입니다.
    })
  );
};</code></pre>
<p>프록시 setup파일을 설정해준다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL 23) CI/CD]]></title>
            <link>https://velog.io/@liso_o/TIL-23-AWS</link>
            <guid>https://velog.io/@liso_o/TIL-23-AWS</guid>
            <pubDate>Mon, 05 Jun 2023 06:55:33 GMT</pubDate>
            <description><![CDATA[<p>CI/CD에서 CI는 개발자를 위한 자동화 프로세스인 지속적인 통합</p>
<p>CD는 지속적인 서비스 제공 및 지속적인 배포를 의미한다.</p>
<p>CI를 성공적으로 구현할 경우, app에 대해 새로운 코드 변경 사항이 정기적으로 빌드 및 테스트 되어 공유 repo에 통합되므로, 여러 명의 개발에 대한 코드가 서로 충돌할 수 있는 문제를 해결할 수 있다. </p>
<h2 id="1-ci지속적-통합">1. CI(지속적 통합)</h2>
<p><img src="https://velog.velcdn.com/images/liso_o/post/d543648e-486b-4e4b-a275-c268e6c64eab/image.png" alt=""></p>
<p>일반적인 개발의 단계는 다음과 같다.</p>
<p><code>Code - Build - Test</code>  단계에서 CI를 고려해 볼 수 있다.</p>
<blockquote>
<ul>
<li>Code : 개발자가 코드를 원격 저장소에 PUSH 하는 단계</li>
<li>Build : 원격 저장소에서 코드를 가져와 테스트 후 빌드하는 단계</li>
<li>Test : 코드 빌드의 결과물이 다른 컴포넌트와 잘 통합되는지 확인하는 과정</li>
</ul>
</blockquote>
<p>이 과정에서 지속적으로 코드를 push하고 build하며 해당 build의 Test 결과를 통해 개선 방안을 찾는다.</p>
<p>이러한 과정을 통해 개발자는 버그를 일찍 발견할 수 있고 테스트가 완료된 코드에 빠른 전달이 가능해진다.</p>
<p>위와 같은 지속적 통합을 통해 보안 이슈, 에러 등을 빠르게 파악할 수 있으며 보다 더 훨씬 효율적인 개발이 가능하다.</p>
<h2 id="2-cd지속적-배포">2. CD(지속적 배포)</h2>
<p><code>Release - Deploy - Operate</code> 단계에서 지속적 배포를 고려해 볼 수 있다.</p>
<blockquote>
<ul>
<li>Release : 배포 가능한 소프트웨어 패키지 작성</li>
<li>Deploy : 프로비저닝을 실행하고 서비스를 사용자에게 노출, 실질적인 배포 부분</li>
<li>Operate : 서비스 현황을 파악하고 생길 수 있는 문제를 감지함.</li>
</ul>
</blockquote>
<p>지속적 배포의 경우, 코드 변경 사항의 병합부터 프로덕션에 적합한 빌드 제공에 이르는 모든 단계를 뜻한다. </p>
<p>이 프로세스를 완료하면, Repo에 자동으로 배포가 가능하므로 운영팀이 보다 빠르고 손쉽게 App을 배포할 수 있게 된다.</p>
<h2 id="3-github-action">3. Github Action</h2>
<p>Github Action은 Github 내에서 위와 같은 CI/CD의 자동화를 제공하는 플랫폼이다.</p>
<p>Repo에서 <code>PR</code> 이나 <code>push</code> 같은 트리거로 워크플로우를 구성할 수 있다.</p>
<p>이 때, 워크플로우는 자체 가상 머신에서 실행된다.</p>
<p><code>.yml</code> 파일 내에서 워크플로우가 구성 되며 <code>.github/workflows</code> 디렉토리 아래에 위치된다.</p>
<pre><code class="language-yml"># .github/workflows/client.yml
name: client // 현재 디렉토리 이름
on: // 트리거
  push: 
    branches:
      - main // 현재 branch
jobs:
  build:
    runs-on: ubuntu-20.04
    steps:
      - name: Checkout source code.
        uses: actions/checkout@v2
      - name: Install dependencies
        run: npm install // 실행하는 npm
        working-directory: ./my-agora-states-client-react
      - name: Build
        run: npm run build
        working-directory: ./my-agora-states-client-react
      - name: SHOW AWS CLI VERSION
        run: aws --version
      - name: Sync Bucket
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 
          AWS_EC2_METADATA_DISABLED: true
        run: |
          aws s3 sync \
            --region ap-northeast-2 \
            build s3://fe-99-limjaesub-s3 \
            --delete
        working-directory: ./my-agora-states-client-react</code></pre>
<p>여기서 env 부분의 AWS key 부분은 직접적으로 입력하면 AWS에서 보안상의 이유로 접근을 거부할 수 있다.</p>
<p>따라서, Github Action에서 Secret Key를 설정해줘야한다.
<img src="https://velog.velcdn.com/images/liso_o/post/09d76c59-e07c-45b6-b91c-d2d5a3995371/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/liso_o/post/6f20807a-5e2c-4feb-afa4-9e42ef1719a9/image.png" alt=""></p>
<p>S3에서 잘 보이는것을 확인할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL 23) TypeScript(2)]]></title>
            <link>https://velog.io/@liso_o/TIL-23-TypeScript2</link>
            <guid>https://velog.io/@liso_o/TIL-23-TypeScript2</guid>
            <pubDate>Wed, 31 May 2023 08:13:35 GMT</pubDate>
            <description><![CDATA[<h2 id="1-enum">1. Enum</h2>
<p>타입스크립트의 Enum은 특정 값의 집합을 정의할 때 사용한다.</p>
<pre><code class="language-ts">enum Color{
  Red,
  Green,
  Blue,
}</code></pre>
<p>위 열거형에 값을 지정할 수도 있다.</p>
<pre><code class="language-ts">enum Color{
  Red=1,
  Green=2,
  Blue=4,
}</code></pre>
<p>지정한 값을 가지고 산술 연산도 수행이 가능하다.</p>
<pre><code class="language-ts">enum Color {
  Red = 1,
  Green = 2,
  Blue = 4,
}

let c: Color = Color.Green;
let greenValue: number = Color.Green;
let blueValue: number = Color.Blue;

console.log(c);          // 출력: 2
console.log(greenValue);  // 출력: 2
console.log(blueValue);   // 출력: 4</code></pre>
<p>숫자 말고도 문자로도 지정할 수 있다.</p>
<pre><code class="language-ts">enum Direction {
  Up = &quot;UP&quot;,
  Down = &quot;DOWN&quot;,
  Left = &quot;LEFT&quot;,
  Right = &quot;RIGHT&quot;,
}

let myDirection: Direction = Direction.Up;
console.log(myDirection); // 출력: &quot;UP&quot;</code></pre>
<p>문자형 열거형은 다음과 같은 상황에서 정의하기 좋다.</p>
<pre><code class="language-ts">enum HttpMethod {
  Get = &quot;GET&quot;,
  Post = &quot;POST&quot;,
  Put = &quot;PUT&quot;,
  Delete = &quot;DELETE&quot;,
}

function makeRequest(url: string, method: HttpMethod) {
  // ...
}

makeRequest(&quot;/api/data&quot;, HttpMethod.Post);</code></pre>
<p>HTTP 요청 방식을 열거형으로 정리하여 코드의 가독성과 안정성을 높였다.</p>
<p>숫자형 열거형에는 <code>역 매핑</code> 이란것이 존재한다.</p>
<pre><code class="language-ts">enum Enum{
  A
}
let a = Enum.A;
let akey = Enum[a]; // A</code></pre>
<h2 id="2-interface">2. Interface</h2>
<p>타입스크립트에서 인터페이스는 타입 체크를 위해 사용이 된다.</p>
<pre><code class="language-ts">interface User {
    name?: string;
    age: number;
}

const user1: User = {
    name: &quot;anna&quot;,
    age: 20
}

const user2: User = {
    age: 20,
    name: &quot;anna&quot;
}

const user3: User = {
  age:20
}</code></pre>
<p>인터페이스는 다음과 같이 객체처럼 사용될 수 있다.</p>
<p>다만, 반드시 정의된 프로퍼티를 사용해야 하며, <code>?</code> 를 통해 선택적으로도 사용할 수 있다.</p>
<p>인터페이스는 함수도 정의할 수 있다.</p>
<pre><code class="language-ts">interface User{
  name:string;
  age:number;
  job:string;
}

interface Greeting {
  (user:User,greeting:string):string; // 두 개의 매개변수와 리턴 타입
}

const greet:Greeting = (user,greeting)=&gt;{
  return `${greeting}, ${user.name}! Your job : ${user.job}.`;
}

const user: User = {
    name: &quot;anna&quot;,
    age: 30,
    job: &quot;developer&quot;
};

const message = greet(user, &quot;Hi&quot;);

console.log(message);</code></pre>
<p>인터페이스는 클래스와 같이 <code>extends</code>를 이용하여 기존 인터페이스를 상속해서 확장이 가능하다.</p>
<pre><code class="language-ts">interface Person {
    name: string;
    age: number;
}

interface Developer extends Person {
    language: string;
}

const person: Developer = {
    language: &quot;TypeScript&quot;,
    age: 20,
    name: &quot;Anna&quot;,
}</code></pre>
<p>또한, 여러 인터페이스를 상속받아 사용할 수도 있다.</p>
<pre><code class="language-ts">interface FoodStuff {
    name: string;
}

interface FoodAmount {
    amount: number;
}

interface FoodFreshness extends FoodStuff, FoodAmount { // 2개의 인터페이스를 FoodFreshness랑 사용가능
       isFreshed: boolean;
}

const food = {} as FoodFreshness;

food.name = &quot;egg&quot;;
food.amount = 2;
food.isFreshed = true;</code></pre>
<h2 id="3-타입-별칭">3. 타입 별칭</h2>
<p>타입 별칭은 타입의 새로운 이름을 만드는 것이다.</p>
<pre><code class="language-ts">type myType = string;

let str1:myType = &quot;hello&quot;;</code></pre>
<pre><code class="language-ts">type Person = {
  id: number;
  name: string;
  email: string;
}

//Commentary 인터페이스에서 Person 타입을 참조하고 있습니다.
interface Commentary {
  id: number;
  content: string;
  user: Person;
}

//객체에서 Commentary 인터페이스를 참조하고 있습니다.
let comment1: Commentary = {
    id: 1,
    content: &quot;뭐예요?&quot;,
    user: {
        id: 1,
        name: &quot;김코딩&quot;,
        email: &quot;kimcoding@codestates.com&quot;,
    },
}</code></pre>
<p>위처럼 타입별칭-&gt;인터페이스-&gt;객체 로 사용할 수 있다.</p>
<p>인터페이스와 비슷한 것 같지만, 차이점이 있다. 바로 타입 별칭은 인터페이스처럼 확장이 불가능하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL 22) TypeScript ]]></title>
            <link>https://velog.io/@liso_o/TIL-22-TypeScript</link>
            <guid>https://velog.io/@liso_o/TIL-22-TypeScript</guid>
            <pubDate>Tue, 30 May 2023 08:39:28 GMT</pubDate>
            <description><![CDATA[<h2 id="1-ts와-js의-차이점">1. Ts와 Js의 차이점</h2>
<p>ts는 js의 한계점을 극복하기 위해 만들어졌다.</p>
<pre><code class="language-jsx">let add = (x,y)=&gt;{
  return x+y;
}
add(5,&quot;7&quot;); // &quot;57&quot;</code></pre>
<p>Javascript는 위와 같은 코드에서 number타입의 변수를 강제적으로 문자열로 변환시켜서 원하지 않는 결과를 초래한다.</p>
<p>이런 문제점을 보완한 게 TypeScript다.</p>
<pre><code class="language-ts">let add = (x:number,y:number):number=&gt;{
  return x+y;
}
add(5,7); // 12</code></pre>
<h2 id="2-typescript의-타입">2. TypeScript의 타입</h2>
<h3 id="1-boolean-타입">1. Boolean 타입</h3>
<pre><code class="language-ts">let imtrue:boolean = true;
let imfalse:boolean = false;</code></pre>
<h3 id="2-number-타입">2. Number 타입</h3>
<pre><code class="language-ts">let number1:number = 5;
let number2:number = 0.1;</code></pre>
<h3 id="3-string-타입">3. String 타입</h3>
<pre><code class="language-ts">let firstName: string = &quot;coding&quot;;
let lastName: string = &#39;kim&#39;;
let longString: string = `Kimcoding is a developer.</code></pre>
<h3 id="4-array-타입">4. Array 타입</h3>
<pre><code class="language-ts">//첫 번째 방법
let items: string[] = [&quot;apple&quot;, &quot;banana&quot;, &quot;grape&quot;];

//두 번째 방법
let numberList: Array&lt;number&gt; = [4, 7, 100];</code></pre>
<h3 id="5-tuple-타입">5. Tuple 타입</h3>
<pre><code class="language-ts">let user: [string, number, boolean] = [&quot;kimcoding&quot;, 20, true];</code></pre>
<p> 요소의 타입과 갯수가 고정된 배열을 표현한다.</p>
<h3 id="6-object-타입">6. Object 타입</h3>
<pre><code class="language-ts">let user: {name: string, age: number} = {
    name: &quot;kimcoding&quot;,
    age: 20
}</code></pre>
<h3 id="7-any-타입">7. Any 타입</h3>
<p>any 타입을 사용하게 되면 변수의 타입에 구애받지 않고 값을 재할당 가능하다.</p>
<pre><code class="language-ts">let maybe: any = 4;


maybe = true;// 값 재할당이 정상적으로 이루어진다.</code></pre>
<h2 id="3-typescript의-함수">3. TypeScript의 함수</h2>
<p>다음과 같은 함수를 Ts로 변환해보겠다.</p>
<pre><code class="language-jsx">// Javascript
function add(x, y){
    return x + y;
}

let add = (x, y) =&gt; {
    return x + y;
}


// TypeScript
function add(x: number, y: number):number {
    return x + y;
}

let add = (x: number, y: number): number =&gt; {
    return x + y;
}</code></pre>
<p>함수의 리턴 값에도 Type을 명시해줘야 하며, 함수에 리턴값이 없는 함수는 <code>void</code>를 이용한다.</p>
<pre><code class="language-ts">let consolePrint = ():void=&gt;{
  console.log(&quot;hello&quot;);
}</code></pre>
<p>또한 타입스크립트는 매개변수의 갯수에 맞춰서 인자를 전달해야한다.</p>
<pre><code class="language-ts">let greeting = (firstName: string, lastName: string): string =&gt; {
    return `hello, ${firstName} ${lastName}`;
}

//error
greeting(&#39;coding&#39;);

//Good
greeting(&#39;coding&#39;, &#39;kim&#39;);

//error
greeting(&#39;coding&#39;, &#39;kim&#39;, &#39;hacker&#39;);</code></pre>
<p>위 코드에서 선택적으로 매개변수를 보낼 경우(2갠데 1개만 보낼때) 매개변수의 이름 끝에 <code>?</code>를 붙인다.</p>
<pre><code class="language-ts">let greeting = (firstName: string, lastName?: string): string =&gt; {
    return `hello, ${firstName} ${lastName}`;
}

//Good
greeting(&#39;coding&#39;);

//Good
greeting(&#39;coding&#39;, &#39;kim&#39;);

//error
greeting(&#39;coding&#39;, &#39;kim&#39;, &#39;hacker&#39;);</code></pre>
<h2 id="4-union--intersection">4. Union &amp; InterSection</h2>
<h3 id="1-union-type">1. Union Type</h3>
<p>유니온 타입은 둘 이상의 타입을 합쳐서 만든 새로운 타입이다.</p>
<pre><code class="language-ts">function printValue(value: number|string): void {
  if (typeof value === &quot;number&quot;) {
    console.log(`The value is a number: ${value}`);
  } else {
    console.log(`The value is a string: ${value}`);
  }
}

printValue(10); // The value is a number: 10
printValue(&quot;hello&quot;); // The value is a string: hello</code></pre>
<p>value에 number또는 string형태의 인자가 들어갈 수 있다.</p>
<p>다만, 아래와 같은 경우는 주의를 해야한다.</p>
<pre><code class="language-ts">interface Developer {
  name: string;
  skill: string;
}

interface Person {
  name: string;
  age: number;
}



function askSomeone(someone: Developer | Person) {
  // in 연산자 : 타입스크립트에서 객체의 속성이 존재하는지를 체크하는 연산자
  // in 연산자는 객체의 속성 이름과 함께 사용하여 해당 속성이 객체 내에 존재하는지 여부를 검사
  if (&#39;skill&#39; in someone) {
    console.log(someone.skill);
  }

  if (&#39;age&#39; in someone) {
    console.log(someone.age);
  }
}</code></pre>
<p>name은 공통이지만, skill과 age는 공통이 아니라 사용할 수 없다.</p>
<p>위와 같은 경우는 in 연산자를 사용한다.</p>
<h3 id="2-intersection-type">2. InterSection Type</h3>
<p>인터섹션 타입은 유니온 타입과 마찬가지로 둘 이상의 타입을 결합한다.</p>
<p>다만 in 연산자를 사용하지 않아도 된다.(타입 가드가 필요 없음)</p>
<pre><code class="language-ts">interface Developer {
  name: string;
  skill: string;
}

interface Person {
  name: string;
  age: number;
}

function askSomeone(someone: Developer &amp; Person) {
  console.log(someone.age);
    console.log(someone.name);
    console.log(someone.skill);
}</code></pre>
<p>대신 모든 프로퍼티를 전부 보내줘야한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL 21) React Custom Hooks]]></title>
            <link>https://velog.io/@liso_o/TIL-21-React-Custom-Hooks</link>
            <guid>https://velog.io/@liso_o/TIL-21-React-Custom-Hooks</guid>
            <pubDate>Mon, 22 May 2023 05:52:00 GMT</pubDate>
            <description><![CDATA[<h2 id="react-custom-hook">React Custom Hook</h2>
<p>반복되는 로직을 하나의 커스텀 훅으로 재사용 할 수 있다.</p>
<pre><code class="language-jsx">//FriendStatus : 친구가 online인지 offline인지 return하는 컴포넌트
function FriendStatus(props) {
      // 이부분
  const [isOnline, setIsOnline] = useState(null);
  useEffect(() =&gt; {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () =&gt; {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
    // 이부분
  });

  if (isOnline === null) {
    return &#39;Loading...&#39;;
  }
  return isOnline ? &#39;Online&#39; : &#39;Offline&#39;;
}

//FriendListItem : 친구가 online일 때 초록색으로 표시하는 컴포넌트
function FriendListItem(props) {
      // 이부분
  const [isOnline, setIsOnline] = useState(null);
  useEffect(() =&gt; {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () =&gt; {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
    // 이부분
  });

  return (
    &lt;li style={{ color: isOnline ? &#39;green&#39; : &#39;black&#39; }}&gt;
      {props.friend.name}
    &lt;/li&gt;
  );
}</code></pre>
<p>지금 코드를 보면</p>
<pre><code class="language-jsx">const [isOnline, setIsOnline] = useState(null);
  useEffect(() =&gt; {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () =&gt; {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };</code></pre>
<p>부분이 중복되는 로직인 것을 확인할 수 있다.</p>
<p>따라서, 이것만 따로 빼서 customHook을 제작한다.</p>
<pre><code class="language-jsx">function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() =&gt; {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () =&gt; {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });

  return isOnline;
}</code></pre>
<p>이러한 커스텀 훅을 만들때는 규칙이 필요하다.</p>
<blockquote>
<h3 id="custom-hook의-규칙">CUSTOM HOOK의 규칙</h3>
</blockquote>
<ul>
<li>Custom Hook을 정의할 때는 함수 이름 앞에 use를 붙이는 것이 규칙이다.</li>
<li>대개의 경우 프로젝트 내의 hooks 디렉토리에 Custom Hook을 위치시킨다.</li>
<li>Custom Hook으로 만들 때 함수는 조건부 함수가 아니어야 한다. 즉 return 하는 값은 조건부여서는 안 된다. 그렇기 때문에 위의 이 useFriendStatus Hook은 온라인 상태의 여부를 boolean 타입으로 반환하고 있다.</li>
</ul>
<p>위에서 만든 커스텀 hook인 useFriendStatus를 다른 컴포넌트에 적용시켜보겠다.</p>
<pre><code class="language-jsx">function FriendStatus(props) {
  const isOnline = useFriendStatus(props.friend.id);

  if (isOnline === null) {
    return &#39;Loading...&#39;;
  }
  return isOnline ? &#39;Online&#39; : &#39;Offline&#39;;
}

function FriendListItem(props) {
  const isOnline = useFriendStatus(props.friend.id);

  return (
    &lt;li style={{ color: isOnline ? &#39;green&#39; : &#39;black&#39; }}&gt;
      {props.friend.name}
    &lt;/li&gt;
  );
}</code></pre>
<h2 id="custom-hook-예시">CUSTOM HOOK 예시</h2>
<pre><code class="language-jsx">import &quot;./styles.css&quot;;
import { useEffect, useState } from &quot;react&quot;;

export default function App() {
  const [data, setData] = useState();

  useEffect(() =&gt; {
    fetch(&quot;data.json&quot;, {
      headers: {
        &quot;Content-Type&quot;: &quot;application/json&quot;,
        Accept: &quot;application/json&quot;
      }
    })
      .then((response) =&gt; {
        return response.json();
      })
      .then((myJson) =&gt; {
        setData(myJson);
      })
      .catch((error) =&gt; {
        console.log(error);
      });
  }, []);

  return (
    &lt;div className=&quot;App&quot;&gt;
      &lt;h1&gt;To do List&lt;/h1&gt;
      &lt;div className=&quot;todo-list&quot;&gt;
        {data &amp;&amp;
          data.todo.map((el) =&gt; {
            return &lt;li key={el.id}&gt;{el.todo}&lt;/li&gt;;
          })}
      &lt;/div&gt;
    &lt;/div&gt;
  );
}</code></pre>
<p>다음과 같은 코드가 있다.</p>
<p>useEffect 내부의 로직을 커스텀 훅으로 분리시키려고 한다.</p>
<pre><code class="language-jsx">// 1. custom hook
```jsx
import { useState, useEffect } from &quot;react&quot;;
const useHooks = (fetchUrl) =&gt; {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  useEffect(() =&gt; {
    fetch(fetchUrl, {
      headers: {
        &quot;Content-Type&quot;: &quot;application/json&quot;,
        Accept: &quot;application/json&quot;
      }
    })
      .then((response) =&gt; {
        return response.json();
      })
      .then((myJson) =&gt; {
        setData(myJson);
      })
      .catch((error) =&gt; {
        setError(error);
      });
  }, [fetchUrl]);

  return { data, error };
};

export default useHooks;

// 2. components
import &quot;./styles.css&quot;;
import { useEffect, useState } from &quot;react&quot;;
import useHooks from &quot;./util/hooks&quot;;
export default function App() {
  const {data,error} = useHooks(&#39;data.json&#39;); // Custom Hook 사용
  return (
    &lt;div className=&quot;App&quot;&gt;
      &lt;h1&gt;To do List&lt;/h1&gt;
      &lt;div className=&quot;todo-list&quot;&gt;
        {data &amp;&amp;
          data.todo.map((el) =&gt; {
            return &lt;li key={el.id}&gt;{el.todo}&lt;/li&gt;;
          })}
      &lt;/div&gt;
    &lt;/div&gt;
  );
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL 20) useMemo & useCallback]]></title>
            <link>https://velog.io/@liso_o/TIL-20-useMemo</link>
            <guid>https://velog.io/@liso_o/TIL-20-useMemo</guid>
            <pubDate>Sun, 21 May 2023 12:34:34 GMT</pubDate>
            <description><![CDATA[<p>리액트 컴포넌트는 상태가 변경되거나 부모 컴포넌트가 렌더링이 될 때마다 리렌더링을 한다.</p>
<p>하지만, 너무 잦은 리렌더링은 앱에 좋지 않은 성능을 끼친다.</p>
<p>이때, 사용하는 React Hook이 useMemo와 useCallback이다.</p>
<h2 id="1-usememo">1. UseMemo</h2>
<p>useMemo는 특정 값을 재사용하고자 할 때 사용한다.</p>
<pre><code class="language-jsx">function Calculator({value}){

    const result = calculate(value);

    return &lt;&gt;
      &lt;div&gt;
        {result}
      &lt;/div&gt;
  &lt;/&gt;;
}</code></pre>
<p>위 컴포넌트는 값을 받아와서 calculate라는 함수에넣은 후 result값을 출력하는 함수다.</p>
<p>만약 calculate가 복잡한 연산을 하는 함수면, 렌더링을 할 때 마다 함수를 호출하여 오랜 시간이 걸릴 것이다.</p>
<p>만약, result의 값이 변하지 않는데도 불구하고 계속 렌더링이 되면 그만큼 성능이 떨어질 것이다.</p>
<pre><code class="language-jsx">import { useMemo } from &quot;react&quot;;

function Calculator({value}){

    const result = useMemo(() =&gt; calculate(value), [value]);

    return &lt;&gt;
      &lt;div&gt;
        {result}
      &lt;/div&gt;
  &lt;/&gt;;
}</code></pre>
<p>위 처럼 useMemo를 사용하면 변하지 않을 경우의 value값을 어딘가에 저장할 수 있다.</p>
<h2 id="2usecallback">2.useCallback</h2>
<p>useCallback은 useMemo와 마찬가지로 메모이제이션 기법을 이용한 Hook이며</p>
<p>useMemo는 값을 재사용하는 목적이지만, useCallback은 함수의 재사용을 위해 사용하는 hook이다.</p>
<pre><code class="language-jsx">function Calculator({x, y}){

    const add = () =&gt; x + y;

    return &lt;&gt;
      &lt;div&gt;
        {add()}
      &lt;/div&gt;
  &lt;/&gt;;
}</code></pre>
<p>위와 같은 함수도 렌더링 될 떄마다 실행되게 된다.</p>
<p>하지만, 의존값인 x와 y가 바뀌지 않으면 함수를 그대로 사용해도 된다.</p>
<p>다만, param으로 값을 받는 함수는 해당이 되지 않는다.</p>
<pre><code class="language-jsx">import React, { useCallback } from &quot;react&quot;;

function Calculator({x, y}){

    const add = useCallback(() =&gt; x + y, [x, y]);

    return &lt;&gt;
      &lt;div&gt;
        {add()}
      &lt;/div&gt;
  &lt;/&gt;;
}</code></pre>
<p>useCallback은 props로 함수를 전달해줄 때 사용하기 좋다.</p>
<p>React는 리렌더링 시 함수를 새로이 만들어서 호출을 한다. </p>
<p>새로이 만들어 호출된 함수는 기존의 함수와 같은 함수가 아니다.</p>
<p>그러나 useCallback을 이용해 함수 자체를 저장해서 다시 사용하면 함수의 메모리 주소 값을 저장했다가 다시 사용한다는 것과 같다고 볼 수 있다.</p>
<p>따라서 React 컴포넌트 함수 내에서 다른 함수의 인자로 넘기거나 자식 컴포넌트의 prop으로 넘길 때 예상치 못한 성능 문제를 막을 수 있는 효과가 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JavaScript] DOM]]></title>
            <link>https://velog.io/@liso_o/JavaScript-DOM</link>
            <guid>https://velog.io/@liso_o/JavaScript-DOM</guid>
            <pubDate>Wed, 17 May 2023 05:50:56 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h4 id="0시작하기">0.시작하기</h4>
</blockquote>
<p>dom은 html 요소를 조작할수 있는 model이다. javascript로 HTML을 조작이 가능하다.</p>
<p>DOM을 이용하면 보다 동적인 웹 페이지 구현이 가능하다.</p>
<blockquote>
<h4 id="1-createelement">1. createElement</h4>
</blockquote>
<p>DOM조작을 script.js 파일에서 진행하겠다.
div를 생성하려면 createElement를 사용한다</p>
<pre><code class="language-jsx">const newdiv = document.createElement(&quot;div&quot;);</code></pre>
<p><img src="https://velog.velcdn.com/images/liso_o/post/62355b80-9c26-4ec1-b227-f59e15d3d5c1/image.png" alt=""></p>
<p>하지만 현재 tree로 연결되있지 않는 공중부양 상태가되어 화면에 출력되지 않는다.</p>
<p>위 div를 출력하려면 <code>append</code>를 사용해야한다.</p>
<blockquote>
<h4 id="2append">2.append</h4>
</blockquote>
<p>append를 사용해 상위 root에 연결시켜준다</p>
<pre><code class="language-jsx">document.body.append(newdiv);</code></pre>
<p><img src="https://velog.velcdn.com/images/liso_o/post/6c5e6cc0-5f7b-4926-ae01-058834de7482/image.png" alt=""></p>
<p>div가 추가되는 것을 볼 수 있다. 하지만 안에 아직 아무런 내용도 없어서 화면에 보이지가 않는다.</p>
<p>내용을 넣으려면 textContent를 사용해야 한다.</p>
<blockquote>
<h4 id="3update--read">3.Update &amp; Read</h4>
</blockquote>
<p>div 생성 후 안에 내용과 class를 추가 하려고 한다.</p>
<pre><code class="language-jsx">const newdiv = document.createElement(&quot;div&quot;);
newdiv.textContent = &quot;hello&quot;;
newdiv.classList.add(&quot;tweet&quot;);</code></pre>
<p><code>textcontent</code>로 내용을 추가해주고 <code>classList.add</code>로 class까지 추가해줘서 css를 적용받게 한다.</p>
<p>다만 새 div의 부모요소는 container여야기 때문에 container에 append를 해줘야한다.</p>
<p>container를 받아오는 방법에는 <code>querySelector</code>가 있다.</p>
<pre><code class="language-jsx">const container = document.querySelector(&quot;#container&quot;);
container.append(newdiv);</code></pre>
<p><img src="https://velog.velcdn.com/images/liso_o/post/2540a6c8-6703-4e1f-b3bf-5b3ab3047b95/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/liso_o/post/00c3be29-48fd-48ae-a8e2-806b4db8b1ad/image.png" alt=""></p>
<p>잘 적용되는 것을 볼 수 있다.</p>
<blockquote>
<h4 id="4delete">4.Delete</h4>
</blockquote>
<p>요소를 삭제하는 방법은 다양하다.</p>
<ol>
<li>remove<pre><code class="language-jsx">newdiv.remove()</code></pre>
</li>
</ol>
<p>혹은 특정 class를 선언하고 remove로 삭제를 해도 된다</p>
<pre><code class="language-jsx">const tweets = document.querySelectorAll(&#39;.tweet&#39;)
tweets.forEach(function(tweet){
    tweet.remove();
})</code></pre>
<p>2.innerHTML(여러개 지우기)</p>
<pre><code class="language-jsx">document.querySelector(&quot;#container&quot;).innerHTML=&quot;&quot;;</code></pre>
<p>이 방법을 쓰면 id가 container인 자식 요소를 모두 지운다. 하지만 innerHTML은 보안에서 몇 가지 문제를 가지고 있으므로 다른 방법을 사용하는 편이 좋다.</p>
<p>3.removeChild+반복문</p>
<pre><code class="language-jsx">const container = document.querySelector(&#39;#container&#39;);
while (container.firstChild) {
  container.removeChild(container.firstChild);
}</code></pre>
<p>위 코드는 첫번째 자식요소가 남아있으면 첫번째 자식요소를 삭제하는 코드다.</p>
<p>위 방법중 상황에 맞는 것을 찾아 사용하면 될 것 같다.</p>
<blockquote>
<h4 id="5append">5.append</h4>
</blockquote>
<p>사용자가 버튼을 클릭하면, 그 버튼의 textContent(또는 innerHTML)을 이용해 메뉴의 이름을 가져올 수 있다. 
사용자가 누른 버튼에 따라 출력되는 이름이 달라지므로, 클릭된 이벤트 객체에서 메뉴의 이름을 가져온다. 
다시 말해, 이벤트 객체는 사용자 입력(onclick, onkeyup, onscroll 등)을 트리거로 발생한 이벤트 정보를 담은 객체다.</p>
<p><img src="https://velog.velcdn.com/images/liso_o/post/5779b514-df99-4f69-b4f4-7718d54dbbf2/image.png" alt=""></p>
<p>이벤트를 직접 출력시켜 보면 맨 밑에 Type란이 나오는데</p>
<p>위 이벤트는 onClick으로 진행 된 Type이다.</p>
<pre><code class="language-jsx">let menus = document.querySelectorAll(&quot;button&quot;); 

let btnAmericano = menus[0];
let btnCaffelatte = menus[1];

btnAmericano.onclick = handleClick;
btnCaffelatte.onclick = handleClick; 

function handleClick(e) {
  let currentMenu = e.target; 
  console.log(currentMenu + &quot;를 클릭하셨습니다.&quot;);
}</code></pre>
<p>따라서 각 버튼마다 target이 설정되므로 밑의 함수처럼 event를 param으로 받아 출력이 가능하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL 19) Tree]]></title>
            <link>https://velog.io/@liso_o/TIL-19-Tree</link>
            <guid>https://velog.io/@liso_o/TIL-19-Tree</guid>
            <pubDate>Thu, 11 May 2023 12:32:20 GMT</pubDate>
            <description><![CDATA[<h2 id="1-tree">1. Tree</h2>
<p>자료구조 Tree는 나무의 형태를 띈 계층적 자료구조다. </p>
<p>하나의 데이터 아래 여러 개의 데이터가 존재하는 비선형 구조다.</p>
<p>아래로만 뻗어나가기 때문에 사이클이 존재하지 않는다.</p>
<h3 id="1-트리의-구조">1. 트리의 구조</h3>
<p>트리는 Root라는 하나의 꼭짓점 데이터를 시작으로 여러 개의 데이터를 간선으로 연결한다.</p>
<p>각 데이터를 노드라 하며, 간선으로 연결되면 부모/자식 관계가 된다.</p>
<p>자식이 없는 노드는 Leaf Node(리프 노드) 가 된다.</p>
<h3 id="2-트리의-구현">2. 트리의 구현</h3>
<p>트리는 javascript의 class로 구현 할 수 있다.</p>
<pre><code class="language-jsx">class Tree {
  // 트리의 생성자(구조)
  constructor(value) {
    this.value = value;
    this.children = [];
  }

  // 트리의 삽입 메서드
  insertNode(value) {
    const childNode = new Tree(value); // 자식도 결국 서브트리의 node기 때문에 tree로 선언해줌.
    this.children.push(childNode);
  }

  // 트리 안에 해당 값이 포함되어 있는지 확인하는 메서드
  contains(value) {
    if (this.value === value) {
      return true;
    }
    for (let i = 0; i &lt; this.children.length; i += 1) {
      if (this.children[i].contains(value)) { // 재귀를 이용해 탐색을 진행함.
        return true;
      }
    }
    return false;
  }
}</code></pre>
<h3 id="3-이진-트리">3. 이진 트리</h3>
<p>이진 트리는 자식 노드가 최대 두 개인 노드로 구성된 트리다.</p>
<p><img src="https://velog.velcdn.com/images/liso_o/post/3417f952-aa56-4b94-bfa4-52b50aea5484/image.png" alt=""></p>
<p>이진 트리는 자료의 삽입 삭제 방법에 따라 위 3개로 나뉘어진다.</p>
<h3 id="4-이진-탐색-트리">4. 이진 탐색 트리</h3>
<p>이진 탐색을 적용한 이진 트리다.</p>
<blockquote>
<h3 id="이진-탐색">이진 탐색?</h3>
</blockquote>
<ul>
<li>이진 탐색 알고리즘은 정렬된 데이터 중에서 특정한 값을 찾기 위한 탐색 알고리즘중 하나다.</li>
<li>배열을 오름차순으로 정렬 후 중간값부터 시작하여 찾고자 하는 값의 탐색 범위를 제한하는 알고리즘이다.</li>
</ul>
<p>이진 트리의 특징은 트리 안에 찾고자 하는 값이 없더라도 최대 트리의 높이만큼 탐색이 진행된다.</p>
<h3 id="5-트리-순회">5. 트리 순회</h3>
<ol>
<li>전위 순회 : 루트에서 시작해 왼쪽의 노드를 전부 탐색한다. 이후 오른쪽으로 넘어간다.
<img src="https://velog.velcdn.com/images/liso_o/post/304a517c-8c48-42c4-925b-61d674db7b65/image.png" alt=""></li>
</ol>
<blockquote>
<p>Root -&gt; Left -&gt; Right</p>
</blockquote>
<ol start="2">
<li>중위 순회 : 왼쪽에서 시작해 부모를 거쳐 오른쪽으로 넘어간다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/liso_o/post/2512e2cb-d0dd-45fd-944c-125a9fa21d45/image.png" alt=""></p>
<blockquote>
<p>Left -&gt; Root -&gt; Right</p>
</blockquote>
<ol start="3">
<li>후위 순회 : 왼쪽 자식 노드에서 시작해 오른쪽 노드를 거쳐 부모 노드로 넘어간다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/liso_o/post/bc786ca7-e1f5-4607-9889-ba5ceafc163e/image.png" alt=""></p>
<blockquote>
<p>Left -&gt; Right -&gt; Root</p>
</blockquote>
<h3 id="6-이진-탐색-트리-구현">6. 이진 탐색 트리 구현</h3>
<p>```jsx
class BinarySearchTree {
  constructor(value) {
    this.value = value;
    this.left = null;
    this.right = null;
  }
  insert(value) {
    if (value &lt; this.value) {
      if (this.left === null) {
        this.left = new BinarySearchTree(value);
      }
      else {
        this.left.insert(value);
      }
    }
    else if (value &gt; this.value) {
      if (this.right === null) {
        this.right = new BinarySearchTree(value);
      }
      else {
        this.right.insert(value);
      }
    } else {
    }
  }
  contains(value) {
    if (value === this.value) {
      return true;
    }
    if (value &lt; this.value) {
      return !!(this.left &amp;&amp; this.left.contains(value));
    }
    if (value &gt; this.value) {
      return !!(this.right &amp;&amp; this.right.contains(value));
    }
  }
  preorder(callback) {
    callback(this.value);
    if (this.left) {
      this.left.preorder(callback);
    }
    if (this.right) {
      this.right.preorder(callback);
    }
  }
  inorder(callback) {
    if (this.left) {
      this.left.inorder(callback);
    }
    callback(this.value);
    if (this.right) {
      this.right.inorder(callback);
    }
  }
  postorder(callback) {
    if (this.left) {
      this.left.postorder(callback);
    }
    if (this.right) {
      this.right.postorder(callback);
    }
    callback(this.value);
  }
}</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL 18) Session & Token]]></title>
            <link>https://velog.io/@liso_o/TIL-18</link>
            <guid>https://velog.io/@liso_o/TIL-18</guid>
            <pubDate>Wed, 03 May 2023 02:39:24 GMT</pubDate>
            <description><![CDATA[<h2 id="1-session">1. Session</h2>
<p>세션은 쿠키와 다르게 중요한 데이터를 서버에 저장한다.</p>
<p><img src="https://velog.velcdn.com/images/liso_o/post/9bf4dd38-e6a6-48db-a452-631613a5bf2d/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/liso_o/post/6857874a-69a7-4196-9487-b6011a8d1021/image.png" alt=""></p>
<p>중요한 데이터를 클라이언트에 전달할 때 직접 전달하지 않고 암호화 된 상태로 전달한다.</p>
<p>마치 신분증과 같은 역할을 하는 것.</p>
<p>세션은 서버에서 추가적인 검증을 하므로 좀 더 보안에 용이하다.</p>
<p>Node.js에서는 세션을 관리해주는 <code>express-session</code> 이라는 모듈이 있다.</p>
<pre><code class="language-jsx">const express = require(&#39;express&#39;);
const session = require(&#39;express-session&#39;);

const app = express();

app.use(
  session({
    secret: &#39;@codestates&#39;, // 비밀키 이용
    resave: false,
    saveUninitialized: true,
    cookie: {
      domain: &#39;localhost&#39;,
      path: &#39;/&#39;,
      maxAge: 24 * 6 * 60 * 10000,
      sameSite: &#39;none&#39;,
      httpOnly: false,
      secure: true,
    },
  })
);</code></pre>
<h3 id="세션으로-로그인-유지하기">세션으로 로그인 유지하기</h3>
<blockquote>
<p><a href="https://velog.io/@liso_o/TIL-17-Cookie">https://velog.io/@liso_o/TIL-17-Cookie</a> 상세하게 써논 로직</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/liso_o/post/6ca29af0-61f0-4bb7-a98f-053ba847e529/image.png" alt=""></p>
<p>지난번 쿠키와 같은 로직을 사용한다. 다만, server 단에서 세션으로 받아오는 것의 차이가 있다.</p>
<pre><code class="language-jsx">//server/index.js
app.use(
  session({
    secret: &#39;@codestates&#39;,
    resave: false,
    saveUninitialized: true,
    cookie: {
      domain: &#39;localhost&#39;,
      path: &#39;/&#39;,
      httpOnly: true,
      sameSite: &#39;strict&#39;,
    },
  })
);</code></pre>
<p>서버에서 세션을 사용한다고 선언 후,</p>
<pre><code class="language-jsx">//server/login.js
const { USER_DATA } = require(&quot;../../db/data&quot;);
module.exports = (req, res) =&gt; {
  const { userId, password } = req.body.loginInfo;
  const { checkedKeepLogin } = req.body;
  const userInfo = {
    ...USER_DATA.filter(
      (user) =&gt; user.userId === userId &amp;&amp; user.password === password
    )[0],
  };
  if (!userInfo.id) {
    res.status(401).send(&quot;Not Authorized&quot;);
  } else if (checkedKeepLogin) {
    req.session.sessionId = userInfo.id;
    console.log(req.session);
  }
};</code></pre>
<p>로그인 처리에서 세션의 구조를 확인해보면 다음과 같이 나온다.</p>
<p><img src="https://velog.velcdn.com/images/liso_o/post/76487dd1-5dcb-4f93-9d19-9863c0c98318/image.png" alt=""></p>
<p>usefInfo에서 필터링된 id는 sessionId에 들어가고 그 외의 추가적인 옵션이 담겨져있는 쿠키는 
<code>req.session.cookie</code> 안에 있다.</p>
<p>따라서 <code>req.session.cookie</code> 안에 정보를 넣어주면 된다.</p>
<blockquote>
<p>express-session의 핵심은 <code>req</code> 객체를 사용하는 것!! <code>res</code> 아님 헷갈리지 않게 주의</p>
</blockquote>
<pre><code class="language-jsx">//server/login.js
const { USER_DATA } = require(&#39;../../db/data&#39;);
module.exports = (req, res) =&gt; {
  const { userId, password } = req.body.loginInfo;
  const { checkedKeepLogin } = req.body;
  const userInfo = {
    ...USER_DATA.filter((user) =&gt; user.userId === userId &amp;&amp; user.password === password)[0],
  };
  if (!userInfo.id) {
    res.status(401).send(&#39;Not Authorized&#39;);
  } else if (checkedKeepLogin) {
    req.session.userId = userInfo.id;
    req.session.cookie.maxAge = 1000 * 60 * 30;
    res.redirect(&#39;/userinfo&#39;);
  } else {
    req.session.userId = userInfo.id;
    res.redirect(&#39;/userinfo&#39;);
  }
};</code></pre>
<p>이후 redirect로 보낸 데이터를 <code>userinfo</code> 에서 관리한다.</p>
<pre><code class="language-jsx">//server/userinfo.js
const { USER_DATA } = require(&#39;../../db/data&#39;);

module.exports = (req, res) =&gt; {
  const userInfo = USER_DATA.filter((it)=&gt;{
    return it.id===req.session.userId
  })[0];

  if(!userInfo){
    res.status(401).send(&quot;Not Authorized&quot;);
  }
  else{
    delete userInfo.password;
    res.send(userInfo);
    // 혹은 이렇게 작성도 가능
    // res.json({...userInfo,password:undefined});
  }
};</code></pre>
<p>세션에 담긴 userId와 알맞은 유저의 정보를 보내고,</p>
<p>세션 내의 쿠키의 옵션을 통해 추가적인 옵션을 부여해준다.</p>
<p>비밀번호는 민감한 정보니 꼭 삭제해서 보내자.</p>
<pre><code class="language-jsx">//server/logout.js
  req.session.destroy();
  res.status(205).send(&quot;Logged Out Successfully&quot;);</code></pre>
<p>이후 로그아웃 시 세션을 없애준다.</p>
<h2 id="2-token">2. Token</h2>
<p>토큰 기반 인증은 기존의 세션 기반 인증의 단점을 보완하기 위해 만들어졌다.</p>
<p><img src="https://velog.velcdn.com/images/liso_o/post/a03e4c57-e1b2-4446-94f5-19dfed55776b/image.png" alt=""></p>
<p>세션 기반 인증은 서버에서 유저의 상태를 관리해야한다.</p>
<p>만약 서버가 여러 개 있으면, 하나의 큰 store를 통해 서버끼리 세션 정보를 교환해야 한다.</p>
<p>이는, 서버에게 부담이 갈 수가 있다.</p>
<p>이를 해결하기 위한 토큰 기반 인증방식은, 유저의 인증상태를 서버가 아닌 클라이언트에 저장하는 방식이다.</p>
<p><img src="https://velog.velcdn.com/images/liso_o/post/7551b017-0723-41f5-b352-75db93207b18/image.png" alt=""></p>
<p>토큰 전달 방식이다.</p>
<p>세션 기반 인증이랑 비슷해 보이지만, 서버 메모리에 저장하는 거와는 달리 토큰과 서버의 비밀 키를 생성해서 클라이언트에 전달해준다.</p>
<p>전달 해줄때 쿠키가 아닌 Authorization 헤더를 통해 전달 해주는데 이는 쿠키에 크기 제한이 있기 때문이다.</p>
<p>클라이언트는 요청과 함께 토큰을 전달하는데 이때 서버는 해당 토큰이 유효한 토큰인지를 검증한다.</p>
<p>이러한 토큰 기반 인증 방식을 대표적으로 사용하는 기술이 <code>JSON Web Token (JWT)</code> 방식이다.</p>
<h2 id="3-jwt">3. JWT</h2>
<p>JWT 방식은 JSON 객체에 정보를 담고 이를 토큰으로 암호화하여 전송하는 기술이다.</p>
<p><img src="https://velog.velcdn.com/images/liso_o/post/7a283843-c406-4465-b945-c7165dd0bce1/image.png" alt=""></p>
<p>JWT는 다음과 같이 <code>.</code> 과 문자열로 구성된 부분이 존재하여 <code>.</code> 으로 세 부분으로 나눌 수 있다.</p>
<h3 id="1-header">1. Header</h3>
<p>Header에는 HTTP의 헤더처럼 해당 토큰 자체를 설명하는 데이터가 담겨 있다.</p>
<pre><code class="language-jsx">{
  &quot;alg&quot;: &quot;HS256&quot;,
  &quot;typ&quot;: &quot;JWT&quot;
}</code></pre>
<p>이 JSON 객체를 base64방식으로 인코딩하면 JWT의 헤더 부분이 완성된다.</p>
<blockquote>
<p> base64 방식은 원한다면 얼마든지 디코딩할 수 있는 인코딩 방식이다. 따라서 비밀번호와 같이 노출되어서는 안 되는 민감한 정보를 담지 않도록 해야 한다.</p>
</blockquote>
<h3 id="2-payload">2. Payload</h3>
<p>HTTP의 Payload와 마찬가지로 전달하려는 내용물이 담겨있다.</p>
<p>어떤 정보에 접근 가능한지, 토큰의 발급 만료 시간, 유저의 이름 같은 개인정보 등이 담겨있다.</p>
<pre><code class="language-jsx">{
  &quot;sub&quot;: &quot;someInformation&quot;,
  &quot;name&quot;: &quot;phillip&quot;,
  &quot;iat&quot;: 151623391
}</code></pre>
<p>마찬가지로, 위 JSON을 base64로 인코딩하면 Payload가 완성된다.</p>
<h3 id="3-signature">3. Signature</h3>
<p>Signature는 토큰의 무결성을 확인할 수 있는 부분이다.</p>
<p>Header와 Payload를 salt를 추가시켜 Header에서 지정한 알고리즘을 이용해 해싱한다.</p>
<pre><code class="language-jsx">HMACSHA256(base64UrlEncode(header) + &#39;.&#39; + base64UrlEncode(payload), secret);</code></pre>
<h3 id="4-토큰기반-인증의-한계">4. 토큰기반 인증의 한계</h3>
<ul>
<li><p>무상태성 : 제 3자가 토큰을 탈취하는 경우 서버가 해당 토큰을 강제로 만료시킬 수 없다.</p>
</li>
<li><p>유효 기간 : 토큰 탈취를 대비해 토큰 유효기간을 짧게 설정하면 사용자가 불쾌한 경험을 가질 수 있다. (ex)재로그인 비중 증가)</p>
</li>
<li><p>토큰의 크기 : 토큰에 많은 데이터를 담으면 암호화하는 과정도 길어지며 클라이언트~서버 간의 네트워크 비용도 증가한다.</p>
</li>
</ul>
<h3 id="5-access-token--refresh-token">5. Access Token &amp; Refresh Token</h3>
<blockquote>
<ul>
<li><h3 id="access-token">Access Token</h3>
액세스 토큰은 말 그대로 서버에 접근하기 위한 토큰으로 앞서 다룬 토큰과 비슷한 역할을 합니다. 따라서 보안을 위해 보통 24시간 정도의 짧은 유효기간이 설정되어 있습니다.</li>
</ul>
</blockquote>
<blockquote>
<ul>
<li><h3 id="refresh-token">Refresh Token</h3>
리프레시 토큰은 서버 접근을 위한 토큰이 아닌 액세스 토큰이 만료되었을 때 새로운 액세스 토큰을 발급받기 위해 사용되는 토큰입니다. 따라서 리프레시 토큰은 액세스 토큰보다 긴 유효기간을 설정합니다.</li>
</ul>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL 17) Cookie]]></title>
            <link>https://velog.io/@liso_o/TIL-17-Cookie</link>
            <guid>https://velog.io/@liso_o/TIL-17-Cookie</guid>
            <pubDate>Tue, 02 May 2023 14:00:43 GMT</pubDate>
            <description><![CDATA[<h2 id="1-cookie">1. Cookie</h2>
<p>쿠키는 어떤 웹사이트에 들어갔을 때, 서버가 일방적으로 클라이언트에 전달하는 작은 데이터다.</p>
<p>서버가 웹 브라우저(클라이언트)에 정보를 저장하고 불러올 수 있는 수단으로 
해당 도메인에 쿠키가 존재하면 웹 브라우저는 도메인에게 http 요청 시 쿠키를 함께 전달한다. </p>
<p><img src="https://velog.velcdn.com/images/liso_o/post/08cd45e2-654f-4bb1-a942-b268dfb906da/image.png" alt=""></p>
<blockquote>
<p>서버가 클라이언트에게 응답 헤더를 전달할 때 위와 같이 Set-Cookie를 전달한다.
클라이언트는 이후 매 요청 시마다 헤더에 쿠키의 이름과 정보를 전달한다.</p>
</blockquote>
<p>쿠키는 클라이언트에 남아서 로그인 유지나 테마 유지 등의 기능을 한다.</p>
<h3 id="쿠키-옵션">쿠키 옵션</h3>
<blockquote>
<ul>
<li>Domain : 서버와 요청의 도메인이 일치하는 경우 쿠키 전송
<a href="http://www.localhost.com:3000/users/login">http://www.localhost.com:3000/users/login</a> 에서 localhost.com이 도메인으로써 
요청과 응답이 같은 도메인이여야 전송됨</li>
</ul>
</blockquote>
<blockquote>
<ul>
<li>Path : 서버와 요청의 세부 경로가 일치하는 경우 쿠키 전송
<a href="http://www.localhost.com:3000/users/login">http://www.localhost.com:3000/users/login</a> 에서 라우팅단인 /users/login이 Path로써 최상위 경로만 알맞다면 쿠키 전송이 가능하다
ex) cookie path : /users 일 경우 /users/option 같은 경로면 쿠키 전송 가능
하지만, /items/option 같이 상위 경로가 다르면 전송 불가능.</li>
</ul>
</blockquote>
<blockquote>
<ul>
<li>MaxAge or Expires : 쿠키의 유효기간을 설정한다.
쿠키는 이 옵션의 여부에 따라 세션 쿠키와 영속성 쿠키로 나뉜다.
세션 쿠키 : 위 옵션이 없는 쿠키,브라우저가 실행 중일때 사용할수 있는 쿠키로 브라우저 종료시 쿠키도 삭제된다.
영속성 쿠키 : 위 옵션에 지정된 시간만큼 사용가능하다.</li>
</ul>
</blockquote>
<blockquote>
<ul>
<li>Secure : <code>Https</code> 프로토콜에만 쿠키 전송 여부 결정</li>
</ul>
</blockquote>
<blockquote>
<ul>
<li>SameSite : CORS 요청의 옵션 및 메서드에 따라 쿠키 전송 여부 결정</li>
</ul>
</blockquote>
<ol>
<li>Lax : GET 메서드 요청만 쿠키 전송 가능</li>
<li>Strict : 쿠키 전송 불가</li>
<li>None : 모든 메서드 요청에 대해 쿠키 전송 가능, 단 Secure 쿠키 옵션이 필요
<img src="https://velog.velcdn.com/images/liso_o/post/f7292200-43ea-45f8-a017-442d78928c2e/image.png" alt="">
위와 같이 CSRF 공격을 받았을 경우에 효과적이다.</li>
</ol>
<h3 id="쿠키-활용-실습">쿠키 활용 실습</h3>
<p>로그인 상태 유지를 하는 기능을 만들어보려 한다.</p>
<p>한 파일에 서버/클라이언트가 있으며, 각각의 요구사항을 충족하면 된다.</p>
<p><img src="https://velog.velcdn.com/images/liso_o/post/1d6ab7a6-c451-4f26-a4fa-cf8bafc58378/image.png" alt=""></p>
<h4 id="1-cors-설정">1. CORS 설정</h4>
<p>서버의 index.js에서 cors 설정을 해야한다.</p>
<p>cors 설정은 클라이언트와 서버가 서로 쿠키를 주고받기 위해 꼭 필요한 설정이다.</p>
<pre><code class="language-jsx">// server/index.js

const corsOptions = {
  // TODO
  // CORS 설정
  origin: &quot;http://localhost:3000&quot;, // 접근 권한을 허용하는 도메인
  credentials: true, // Access-Control-Allow-Origin 접근 허용
  methods: [&quot;GET&quot;, &quot;POST&quot;, &quot;OPTION&quot;], // 허용할 메소드
};

app.use(cors(corsOptions));
// cors 설정을 사용함

app.post(&quot;/login&quot;, controllers.login);
app.post(&quot;/logout&quot;, controllers.logout);
app.get(&quot;/userinfo&quot;, controllers.userInfo);
// 각 컨트롤러마다 endpoint를 연결해줌</code></pre>
<h4 id="2-클라이언트-서버로-로그인-요청-보내기">2. &lt;클라이언트&gt; 서버로 로그인 요청 보내기</h4>
<p><img src="https://velog.velcdn.com/images/liso_o/post/79d1c337-9e64-4881-8027-1718f58a693e/image.png" alt=""></p>
<p>총 3개의 state가 있다.</p>
<pre><code class="language-jsx">  const [loginInfo, setLoginInfo] = useState({
    userId: &quot;&quot;,
    password: &quot;&quot;,
  }); // 로그인 정보
  const [checkedKeepLogin, setCheckedKeepLogin] = useState(false); // 로그인 유지 할건지
  const [errorMessage, setErrorMessage] = useState(&quot;&quot;); // 에러 발생 시 로그인 버튼 밑에 에러메시지 표시 여부</code></pre>
<p>첫 번째로 ID와 Password가 업데이트 되는 <code>LoginInfo</code>
두 번째로 로그인 유지 할건지에 대한 상태 <code>checkedKeepLogin</code>
세 번째로 에러 발생시 에러 메시지은 <code>errorMessage</code> 이다.</p>
<ol>
<li><p>로그인 에러가 발생하는 경우는 아이디 또는 비밀번호가 입력되지 않았을 경우다.</p>
</li>
<li><p>아이디와 비밀번호가 전부 잘 입력되었으면, 서버에 로그인 데이터를 보낸다.</p>
</li>
</ol>
<pre><code class="language-jsx">  const loginRequestHandler = () =&gt; {
    // 1. 아이디,비밀번호 중 하나라도 입력이 되지 않았다면 에러를 띄워줌
    if (!loginInfo.userId || !loginInfo.password) {
      setErrorMessage(&quot;아이디와 비밀번호를 입력하세요&quot;);
      return;
    }
    // 2. 입력이 전부 들어왔으면, axios.post를 통해 정보를 전달해줌
    // endpoint는 server/users/index.js를 보면 login이라고 적혀있다.
    // endpoint의 login은 controllers의 user에 login 컨트롤러로 데이터가 전달된다.
    return axios
      .post(&quot;http://localhost:4000/login&quot;, { loginInfo, checkedKeepLogin })
      .then((res) =&gt; {
        return res;
      });
  };</code></pre>
<p><img src="https://velog.velcdn.com/images/liso_o/post/97219a88-cefd-4d1a-9e08-5c7153e45267/image.png" alt=""></p>
<p>위 상태로 로그인정보를 보내고,</p>
<pre><code class="language-jsx">// server/controller/login.js
const { USER_DATA } = require(&quot;../../db/data&quot;);

module.exports = (req, res) =&gt; {
  const { userId, password } = req.body.loginInfo;
  const { checkedKeepLogin } = req.body;
  const userInfo = {
    ...USER_DATA.filter(
      (user) =&gt; user.userId === userId &amp;&amp; user.password === password // req로 들어온 데이터와 같은 id,password인 user를 가져옴.
    )[0],
  };
  console.log(req.body);
  console.log(userInfo);
}</code></pre>
<p>database에 있는 USER_DATA를 가져온 다음 입력된 정보와 비교해본다.</p>
<p>userInfo는 req.body에 있는 loginInfo와 비교하여 알맞은 요소를 추출한다.</p>
<p><img src="https://velog.velcdn.com/images/liso_o/post/f920e5b2-6dc6-4ec1-b53b-36195bfe8780/image.png" alt=""></p>
<p>콘솔창을 띄워보면 위와 같이 req.body의 정보와 userInfo를 확인할 수 있다.</p>
<h4 id="3-서버-로그인-요청-처리하기">3. &lt;서버&gt; 로그인 요청 처리하기</h4>
<p>2번에서 userInfo는 올바른 아이디와 비밀번호가 들어오면 객체를 리턴한다.</p>
<p>하지만, 올바르지 않은 아이디 또는 비밀번호가 들어오면 빈 배열을 리턴한다.</p>
<p>이것을 이용해 로그인 성공 여부를 가릴 수 있다.</p>
<p>로그인 실패 시 401 상태코드와 함께 <code>Not Authorized</code> 라는 문구를 보내야하고</p>
<p>로그인 성공 시 로그인 유지를 체크하여 쿠키를 전송하면 된다.</p>
<p>다만, 클라이언트에 바로 쿠키를 보내지 않고 userInfo.js 로 리다이렉트 시킨 후 검증해서 보낸다.</p>
<pre><code class="language-jsx">// server/controller/login.js

// 쿠키의 옵션 설정해줌
  const cookiesOption = {
    domain: &quot;localhost&quot;,
    path: &quot;/&quot;,
    secure: true,
    httpOnly: true,
    sameSite: &quot;strict&quot;,
  };


  if (userInfo.id === undefined) {
    // 로그인 실패!
    res.status(401).send(&quot;Not Authorized&quot;);
  } else if (checkedKeepLogin === true) {
    // 로그인 유지가 true일 경우 cookiesOption의 max-age와 expires 옵션을 추가해준다.
    const time = 1000 * 60 * 30;
    cookiesOption.maxAge = time; // 단위 ms, 해당 시간 동안 쿠키 &quot;유지&quot;
    cookiesOption.expires = new Date(Date.now() + time); // 지금시간+넣은 시간 후에 쿠키 &quot;삭제&quot;
    // 로그인 성공!
    // express에서 쿠키를 전송하려면 cookie() 메소드를 사용하면 된다.
    // cookie 메서드는 전달인자로 쿠키 이름,값,옵션을 받는다.
    res.cookie(&quot;cookieId&quot;, userInfo.id, cookiesOption);
    res.redirect(&quot;/userinfo&quot;);
  } else {
    // 로그인 유지를 안 하고 싶을때
    res.cookie(&quot;cookieId&quot;, userInfo.id, cookiesOption); // 추가적인 옵션 없이 쿠키 설정
    res.redirect(&quot;/userinfo&quot;);
    // userinfo controller로 redirect
  }
};

// 여기선 쿠키만 생성 !
// userInfo에서 쿠키 검증</code></pre>
<p>이후 userInfo에서 쿠키를 검증한다.</p>
<pre><code class="language-jsx">//server/controller/userInfo
const { USER_DATA } = require(&quot;../../db/data&quot;);

module.exports = (req, res) =&gt; {
  const userInfo = {
    ...USER_DATA.filter(
      (user) =&gt; user.id === cookieId // id와 비교
    )[0],
  };
  if (!cookieId || !userInfo.id) {
    res.status(401).send(&quot;Not Authorized&quot;);
  } else {
    // 비밀번호는 민감한 정보라서 삭제 후에 보내야 함.
    delete userInfo.password;
    res.send(userInfo); // 클라이언트로 다시 보내주기.
  }
};</code></pre>
<p>이러면 userInfo에서 쿠키 검증 후 다시 클라이언트로 보내지게 된다.</p>
<h4 id="4-클라이언트-응답-쿠키로-react-상태-업데이트">4. &lt;클라이언트&gt; 응답 쿠키로 React 상태 업데이트</h4>
<p>userInfo에서 받아온 쿠키는 어떤 모습인지부터 확인해보겠다.</p>
<p><img src="https://velog.velcdn.com/images/liso_o/post/f631842e-3319-43c0-b730-5472ea9c09ed/image.png" alt=""></p>
<p>위 정보를 가지고 React의 상태를 업데이트 해보겠다.</p>
<p>우선, App.js에서 각각 Login과 Mypage에 setIsLogin과 setUserInfo를 넘겨준다.</p>
<pre><code class="language-jsx">//client/Login.js
    return axios
      .post(&quot;http://localhost:4000/login&quot;, { loginInfo, checkedKeepLogin })
      .then((res) =&gt; {
        setUserInfo(res.data);
        setIsLogin(true);
        setErrorMessage(&quot;&quot;);
      })
      .catch((err)=&gt;{
        setErrorMessage(&quot;로그인 실패&quot;);
      });</code></pre>
<p>로그인이 성공해 쿠키가 담긴 res가 오면 사용자 정보에 res.data를 넘겨주고 setIsLogin을 통해 현재 로그인 상태를 변경시켜준다.</p>
<p>client의 userInfo는 받아온 쿠키를 가지고 로그인 페이지를 렌더링시킨다.</p>
<pre><code class="language-jsx">//client/userInfo
return (
    &lt;div className=&#39;container&#39;&gt;
      &lt;div className=&#39;left-box&#39;&gt;
        &lt;span&gt;
          {`${userInfo.name}(${userInfo.userId})`}님,
          &lt;p&gt;반갑습니다!&lt;/p&gt;
        &lt;/span&gt;
      &lt;/div&gt;
      &lt;div className=&#39;right-box&#39;&gt;
        &lt;h1&gt;AUTH STATES&lt;/h1&gt;
        &lt;div className=&#39;input-field&#39;&gt;
          &lt;h3&gt;내 정보&lt;/h3&gt;
          &lt;div className=&#39;userinfo-field&#39;&gt;
            &lt;div&gt;{`💻 ${userInfo.position}`}&lt;/div&gt;
            &lt;div&gt;{`📩 ${userInfo.email}`}&lt;/div&gt;
            &lt;div&gt;{`📍 ${userInfo.location}`}&lt;/div&gt;
            &lt;article&gt;
              &lt;h3&gt;Bio&lt;/h3&gt;
              &lt;span&gt;{userInfo.bio}&lt;/span&gt;
            &lt;/article&gt;
          &lt;/div&gt;
          &lt;button className=&#39;logout-btn&#39; onClick={logoutHandler}&gt;
            LOGOUT
          &lt;/button&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );</code></pre>
<h4 id="5-클라이언트-로그아웃-버튼-클릭-시-요청-보내기">5. &lt;클라이언트&gt; 로그아웃 버튼 클릭 시 요청 보내기</h4>
<p>userInfo에 로그아웃 버튼을 누르면 로그아웃 되는 기능을 구현해야한다.</p>
<p>그러면 현재 로그인 상태도 변경해줘야 하고 유저의 정보도 없애줘야한다.</p>
<p>로그아웃의 endpoint는 /logout 이므로 다음과 같이 작성해준다.</p>
<pre><code class="language-jsx">//client/userInfo

  const logoutHandler = () =&gt; {

    return axios
      .post(&quot;http://localhost:4000/logout&quot;)
      .then((res) =&gt; {
        setIsLogin(false);
        setUserInfo(null);
      })
      .catch((err) =&gt; {
         console.log(err);
      });
  };</code></pre>
<h4 id="6-서버-로그아웃-로직">6. &lt;서버&gt; 로그아웃 로직</h4>
<p>로그아웃 요청을 보냈으면 서버에서 로그아웃을 해줘야한다.</p>
<p>쿠키를 삭제해야 하므로 쿠키 삭제에 쓰이는 clearCookie를 이용한다.</p>
<pre><code class="language-jsx">//server/logout
module.exports = (req, res) =&gt; {
  res
    .status(205)
    .clearCookie(&#39;cookieId&#39;, {
      domain: &#39;localhost&#39;,
      path: &#39;/&#39;,
      sameSite: &#39;strict&#39;,
      secure: true,
    })
    .send(&#39;Logged Out Successfully&#39;);
};</code></pre>
<p>클라이언트에서 따로 데이터를 담아 요청하지 않았으므로 req는 사용하지 않는다.</p>
<h4 id="7-클라이언트-로그인-유지-로직">7. &lt;클라이언트&gt; 로그인 유지 로직</h4>
<p>로그인 페이지에 도달했을때 클라이언트에 이미 쿠키가 있다면(로그인 후 쿠키가 유지되어있으면) 바로 userInfo 페이지를 보여지게 만든다.</p>
<pre><code class="language-jsx">//client/app.js
  const authHandler = () =&gt; {
    axios
      .get(&#39;http://localhost:4000/userinfo&#39;)
      .then((res) =&gt; {
        setIsLogin(true);
        setUserInfo(res.data);
      })
      .catch((err) =&gt; {
        if (err.response.status === 401) {
          console.log(err.response.data);
        }
      });
  };

  useEffect(() =&gt; {
    authHandler();
  }, []);</code></pre>
<p>useEffect로 리렌더링 될때마다 실행시킨다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL 16) DFS & BFS with Javascript]]></title>
            <link>https://velog.io/@liso_o/TIL-16-DFS-BFS-with-Javascript</link>
            <guid>https://velog.io/@liso_o/TIL-16-DFS-BFS-with-Javascript</guid>
            <pubDate>Mon, 01 May 2023 07:33:17 GMT</pubDate>
            <description><![CDATA[<h2 id="1-dfs">1. DFS</h2>
<p>dfs는 깊이 우선 탐색이라고 하며, 그래프에서 깊은 부분을 우선적으로 탐색하는 알고리즘이다.
<img src="https://velog.velcdn.com/images/liso_o/post/4f7d2613-c3db-4279-ac71-2f503870bd2e/image.png" alt=""></p>
<h3 id="1-1-스택을-이용한-dfs-구현">1-1) 스택을 이용한 DFS 구현</h3>
<p>탐색을 마친 노드와 탐색이 필요한 노드를 각각 배열로 선언한다.</p>
<p>첫 번째 노드를 탐색이 필요한 노드에 push 하면서 탐색이 시작된다.</p>
<p>탐색이 필요한 노드가 없어질 때까지 탐색을 계속한다.</p>
<pre><code class="language-jsx">const graph = {
  A: [&quot;B&quot;, &quot;C&quot;],
  B: [&quot;A&quot;, &quot;D&quot;],
  C: [&quot;A&quot;, &quot;G&quot;, &quot;H&quot;, &quot;I&quot;],
  D: [&quot;B&quot;, &quot;E&quot;, &quot;F&quot;],
  E: [&quot;D&quot;],
  F: [&quot;D&quot;],
  G: [&quot;C&quot;],
  H: [&quot;C&quot;],
  I: [&quot;C&quot;, &quot;J&quot;],
  J: [&quot;I&quot;],
};


const dfs1 = (graph, startNode) =&gt; {
  const visited = []; // 탐색을 마친 노드
  let needVisit = []; // 탐색이 필요한 노드

  needVisit.push(startNode); // 탐색 시작

  while (needVisit.length &gt; 0) {
    // 탐색이 필요한 노드가 0이 될때까지 반복
    const node = needVisit.shift(); // pop과 shift로 어디 방향부터 탐색할 지 정한다. shift는
    if (!visited.includes(node)) {
      // 탐색을 마친 노드에 없다면?
      console.log(`탐색을 마친 노드 node ${node}`);
      visited.push(node); //탐색을 마쳤으므로 탐색을 마친 노드에 넣어줌
      needVisit = [...graph[node], ...needVisit];
      console.log(`다음 탐색할 노드인 needVisit : ${needVisit}`);
    }
  }
  return visited;
};


// output
탐색을 마친 노드 node A
다음 탐색할 노드인 needVisit : B,C
탐색을 마친 노드 node B
다음 탐색할 노드인 needVisit : A,D,C
탐색을 마친 노드 node D
다음 탐색할 노드인 needVisit : B,E,F,C
탐색을 마친 노드 node E
다음 탐색할 노드인 needVisit : D,F,C
탐색을 마친 노드 node F
다음 탐색할 노드인 needVisit : D,C
탐색을 마친 노드 node C
다음 탐색할 노드인 needVisit : A,G,H,I
탐색을 마친 노드 node G
다음 탐색할 노드인 needVisit : C,H,I
탐색을 마친 노드 node H
다음 탐색할 노드인 needVisit : C,I
탐색을 마친 노드 node I
다음 탐색할 노드인 needVisit : C,J
탐색을 마친 노드 node J
다음 탐색할 노드인 needVisit : I
[
  &#39;A&#39;, &#39;B&#39;, &#39;D&#39;, &#39;E&#39;,
  &#39;F&#39;, &#39;C&#39;, &#39;G&#39;, &#39;H&#39;,
  &#39;I&#39;, &#39;J&#39;
]</code></pre>
<h3 id="1-2-재귀를-이용한-dfs-구현">1-2) 재귀를 이용한 DFS 구현</h3>
<p>스택을 계속해서 반복하는 것과 비슷하게 재귀도 구현하면 된다.</p>
<p>방문하지 않은 노드만을 찾아서 dfs함수를 다시 호출하여 실행한다.</p>
<pre><code class="language-jsx">// 그래프는 위와 같음

let visited = [];
const dfs2 = (graph, start) =&gt; {
  if (!visited.includes(start)) {
    // 방문하지 않은 노드만 탐색
    visited.push(start);
    for (let i = 0; i &lt; graph[start].length; i++) {
      dfs2(graph, graph[start][i]); 
      // 해당 노드의 자식,부모 모두 dfs2의 param으로 전달해서 재호출한다.
    }
  }
};

dfs2(graph, &quot;A&quot;);
console.log(visited);

// output
[
  &#39;A&#39;, &#39;B&#39;, &#39;D&#39;, &#39;E&#39;,
  &#39;F&#39;, &#39;C&#39;, &#39;G&#39;, &#39;H&#39;,
  &#39;I&#39;, &#39;J&#39;
]</code></pre>
<h2 id="2-bfs">2. BFS</h2>
<p>BFS는 Breadth Frist Search의 약자로 넓이 우선 탐색이다.</p>
<p><img src="https://velog.velcdn.com/images/liso_o/post/3a47e17b-5bbc-49e4-842e-470ca5bedde6/image.png" alt=""></p>
<h3 id="2-1-bfs를-큐로-구현">2-1) BFS를 큐로 구현</h3>
<pre><code class="language-jsx">const Bfs = (graph, startNode) =&gt; {
  const visited = []; // 탐색을 마친 노드
  let needVisit = []; // 탐색이 필요한 노드

  needVisit.push(startNode); // 탐색 시작

  while (needVisit.length &gt; 0) {
    // 탐색이 필요한 노드가 0이 될때까지 반복
    const node = needVisit.shift(); // pop과 shift로 어디 방향부터 탐색할 지 정한다. shift는
    if (!visited.includes(node)) {
      // 탐색을 마친 노드에 없다면?
      console.log(`탐색을 마친 노드 node ${node}`);
      visited.push(node); //탐색을 마쳤으므로 탐색을 마친 노드에 넣어줌
      needVisit = [...needVisit, ...graph[node]];
      //console.log(`need Visit : ${needVisit}`);
      console.log(`다음 탐색할 노드의 집합인 needVisit : ${needVisit}`);
      console.log(
        `추가 탐색을 위해 needVisit에 들어가야할 ...graph[node]:${graph[node]}`
      );
      console.log(&quot;=======&quot;);
    }
  }
  return visited;
};
console.log(Bfs(graph, &quot;A&quot;));


// output
탐색을 마친 노드 node A
추가 탐색을 위해 needVisit에 들어가야할 ...graph[node]:B,C
다음 탐색할 노드의 집합인 needVisit : B,C
=======
탐색을 마친 노드 node B
추가 탐색을 위해 needVisit에 들어가야할 ...graph[node]:A,D
다음 탐색할 노드의 집합인 needVisit : C,A,D
=======
탐색을 마친 노드 node C
추가 탐색을 위해 needVisit에 들어가야할 ...graph[node]:A,G,H,I
다음 탐색할 노드의 집합인 needVisit : A,D,A,G,H,I
=======
탐색을 마친 노드 node D
추가 탐색을 위해 needVisit에 들어가야할 ...graph[node]:B,E,F
다음 탐색할 노드의 집합인 needVisit : A,G,H,I,B,E,F
=======
탐색을 마친 노드 node G
추가 탐색을 위해 needVisit에 들어가야할 ...graph[node]:C
다음 탐색할 노드의 집합인 needVisit : H,I,B,E,F,C
=======
탐색을 마친 노드 node H
추가 탐색을 위해 needVisit에 들어가야할 ...graph[node]:C
다음 탐색할 노드의 집합인 needVisit : I,B,E,F,C,C
=======
탐색을 마친 노드 node I
추가 탐색을 위해 needVisit에 들어가야할 ...graph[node]:C,J
다음 탐색할 노드의 집합인 needVisit : B,E,F,C,C,C,J
=======
탐색을 마친 노드 node E
추가 탐색을 위해 needVisit에 들어가야할 ...graph[node]:D
다음 탐색할 노드의 집합인 needVisit : F,C,C,C,J,D
=======
탐색을 마친 노드 node F
추가 탐색을 위해 needVisit에 들어가야할 ...graph[node]:D
다음 탐색할 노드의 집합인 needVisit : C,C,C,J,D,D
=======
탐색을 마친 노드 node J
추가 탐색을 위해 needVisit에 들어가야할 ...graph[node]:I
다음 탐색할 노드의 집합인 needVisit : D,D,I
=======
[
  &#39;A&#39;, &#39;B&#39;, &#39;C&#39;, &#39;D&#39;,
  &#39;G&#39;, &#39;H&#39;, &#39;I&#39;, &#39;E&#39;,
  &#39;F&#39;, &#39;J&#39;
]</code></pre>
<h2 id="3-예제">3. 예제</h2>
<p>사실 이번 TIL은 코드스테이츠의 코플릿에 있는 DFS/BFS 문제를 풀면서 작성하였다.</p>
<p>기존에는 DFS/BFS의 개념만 알고있었을 뿐, 이렇게 상세히 구현은 해본 적이 없다.</p>
<p>코플릿에서의 문제는 다음과 같다.</p>
<blockquote>
<h3 id="문제">문제</h3>
</blockquote>
<pre><code class="language-jsx">let bfs = function (node) {
  // TODO: 여기에 코드를 작성합니다.
};
// 이 아래 코드는 변경하지 않아도 됩니다. 자유롭게 참고하세요.
let Node = function (value) {
  this.value = value;
  this.children = [];
};
// 위 Node 객체로 구성되는 트리는 매우 단순한 형태의 트리입니다.
// membership check(중복 확인)를 따로 하지 않습니다.
Node.prototype.addChild = function (child) {
  this.children.push(child);
  return child;
};</code></pre>
<h3 id="입출력-예시">입출력 예시</h3>
<pre><code class="language-jsx">let root = new Node(1);
let rootChild1 = root.addChild(new Node(2));
let rootChild2 = root.addChild(new Node(3));
let leaf1 = rootChild1.addChild(new Node(4));
let leaf2 = rootChild1.addChild(new Node(5));
let output = bfs(root);
console.log(output); // --&gt; [1, 2, 3, 4, 5]
leaf1.addChild(new Node(6));
rootChild2.addChild(new Node(7));
output = bfs(root);
console.log(output); // --&gt; [1, 2, 3, 4, 5, 7, 6]</code></pre>
<p>BFS만 예로 들어보겠다. 위에 작성한 것처럼 TODO도 동일한 로직으로 작성하면 된다.</p>
<pre><code class="language-jsx">let bfs = function (node) {
    let visited = [];
  let needVisited = [];
  visited.push(node.value);
  needVisited = [...node.children];
  while(needVisited.length&gt;0){
    const visitnode = needVisited.shift(); // 방문 배열의 맨 앞에서 하나 추출
    visited.push(visitnode.value);
    needVisited = [...needVisited,...visitnode.children];
  }</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL 15) React-Redux로 상태 관리하기]]></title>
            <link>https://velog.io/@liso_o/TIL-15-React-Redux%EB%A1%9C-%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@liso_o/TIL-15-React-Redux%EB%A1%9C-%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 26 Apr 2023 13:50:19 GMT</pubDate>
            <description><![CDATA[<p>지난번엔 useState를 통해 상태를 관리했었다.</p>
<p>하지만, 컴포넌트 간 props로만 상태를 전달하고 관리하려니, <code>props drilling</code> 이 일어나게 되었다.</p>
<p>이럴 때 사용하는 것이 <code>Redux</code> 이다.</p>
<h3 id="redux-란">Redux 란?</h3>
<p>Redux는 자바스크립트에서 상태를 관리하기 위한 도구다.</p>
<p>Redux는 단 하나의 store에서 전체 app의 상태를 관리하는 것이다.</p>
<p>store는 읽기 전용이며, action이라 불리는 객체를 통해 상태를 변경 할 수 있다.</p>
<h3 id="redux에서의-reducer">Redux에서의 Reducer</h3>
<p>Reducer는 현재 상태와 액션을 입력으로 받아서 새로운 상태를 반환하는 순수 함수다.</p>
<p>순수 함수이므로, 예측 가능한 결과를 도출한다.</p>
<pre><code class="language-jsx">const itemReducer = (state = initialState, action) =&gt; {
  // 초기값,action
  switch (action.type) {
    case ADD_TO_CART:
      //TODO..
    case REMOVE_FROM_CART:
      //TODO..
    case SET_QUANTITY:
        // TODO..
    default:
      return state;
  }
};</code></pre>
<p>action에 따라 state를 업데이트 시킨다.</p>
<p>리듀서는 순수 함수이고 내부 case는 객체의 불변성을 지키므로 App 상태의 추론을 더욱 쉽게 만들어준다.</p>
<h3 id="react-redux">React-Redux</h3>
<p>react-redux는 react 안에서 보다 redux를 편하게 사용할 수 있게 해주는 라이브러리다.</p>
<p>react-redux의 사용 방법은 다음과 같다. (Counter)</p>
<blockquote>
</blockquote>
<h4 id="1-react-redux-설치하기">1. React-Redux 설치하기</h4>
<p><code>npm install react-redux // npm install redux</code></p>
<h4 id="2-액션-생성자-만들기">2. 액션 생성자 만들기</h4>
<pre><code class="language-jsx">export const incrementCounter = () =&gt; ({
  type: &#39;INCREMENT_COUNTER&#39;
});
export const decrementCounter = () =&gt; ({
  type: &#39;DECREMENT_COUNTER&#39;
});</code></pre>
<h4 id="3-액션-생성자를-통한-리듀서-만들기">3. 액션 생성자를 통한 리듀서 만들기.</h4>
<pre><code class="language-jsx">const initialState = {
  count: 0
};
const counterReducer = (state = initialState, action) =&gt; {
  switch (action.type) {
    case &#39;INCREMENT_COUNTER&#39;:
      return {
        ...state,
        count: state.count + 1
      };
    case &#39;DECREMENT_COUNTER&#39;:
      return {
        ...state,
        count: state.count - 1
      };
    default:
      return state;
  }
};
export default counterReducer;</code></pre>
<h4 id="4-reducer를-기반으로-한-store-만들기">4. Reducer를 기반으로 한 store 만들기</h4>
<pre><code class="language-jsx">import { createStore } from &#39;redux&#39;;
import counterReducer from &#39;./reducers&#39;;
const store = createStore(counterReducer);</code></pre>
<h4 id="5-app의-최상위-컴포넌트를-provider로-감싸주기">5. App의 최상위 컴포넌트를 Provider로 감싸주기</h4>
<pre><code class="language-jsx">import { Provider } from &#39;react-redux&#39;;
import store from &#39;./store&#39;;
function App() {
  return (
    &lt;Provider store={store}&gt;
      &lt;Counter /&gt;
    &lt;/Provider&gt;
  );
}</code></pre>
<h4 id="6위에서-만든-것을-사용하는-counter컴포넌트-만들기">6.위에서 만든 것을 사용하는 Counter컴포넌트 만들기</h4>
<pre><code class="language-jsx">import { useSelector, useDispatch } from &#39;react-redux&#39;;
import { incrementCounter, decrementCounter } from &#39;./actions&#39;;
function Counter() {
  const count = useSelector(state =&gt; state.count);// useSelector를 이용해 store에서 state를 가져온다. 
  //state는 store에서 count:0이 default임.
  const dispatch = useDispatch();
  const increment = () =&gt; {
    dispatch(incrementCounter());
  };
  const decrement = () =&gt; {
    dispatch(decrementCounter());
  };
  return (
    &lt;div&gt;
      &lt;p&gt;Count: {count}&lt;/p&gt;
      &lt;button onClick={increment}&gt;Increment&lt;/button&gt;
      &lt;button onClick={decrement}&gt;Decrement&lt;/button&gt;
    &lt;/div&gt;
  );
}</code></pre>
<h3 id="cmarket에서의-데이터-흐름">CMARKET에서의 데이터 흐름</h3>
<p><img src="https://velog.velcdn.com/images/liso_o/post/b54a7be4-4164-4491-809a-2ba04c0fff95/image.png" alt=""></p>
<p>각 컴포넌트들은 Reducer에서 state를 받아와 하위 컴포넌트에 전달해주고</p>
<p>컴포넌트에서 상태 변경이 필요할 경우 dispatch를 통해 Reducer로 넘겨준다.</p>
<h3 id="store">Store</h3>
<pre><code class="language-jsx">//store.js
import {legacy_createStore as createStore} from &quot;redux&quot;
import rootReducer from &#39;../reducers/index&quot;

const store = createStore(rootReducer);</code></pre>
<p>store.js에서 store를 선언 해준다.</p>
<p>그 후, rootReducer를 import 해와서 Store에 넣어준다.</p>
<p>이러면, rootReducer에 있는 모든 reducer가 store에 저장이 된다.</p>
<h3 id="reducer">Reducer</h3>
<pre><code class="language-jsx">// reducers/index.js
import { combineReducers } from &quot;redux&quot;;
import itemReducer from &quot;./itemReducer&quot;;
import notificationReducer from &quot;./notificationReducer&quot;;

const rootReducer = combineReducers({
  itemReducer,
  notificationReducer,
});

export default rootReducer;</code></pre>
<p>reducer가 선언되는 곳이다.</p>
<p>현재 CMARKET에는 두 개의 reducer가 사용되는데</p>
<p>여러 개의 reducer를 사용하려면 redux에 있는 <code>combineReducers</code>를 사용한다.</p>
<h4 id="initialstate">initialState</h4>
<pre><code class="language-jsx">// reducers/initialState.js
export const initialState = {
  items: [
    {
      id: 1,
      name: &quot;노른자 분리기&quot;,
      img: &quot;../images/egg.png&quot;,
      price: 9900,
    },
    {
      id: 2,
      name: &quot;2020년 달력&quot;,
      img: &quot;../images/2020.jpg&quot;,
      price: 12000,
    },
    ......

    cartItems: [
    {
      itemId: 1,
      quantity: 1,
    },
    {
      itemId: 5,
      quantity: 7,
    },  
    {
      itemId: 2,
      quantity: 3,
    },
  ],</code></pre>
<p>Reducer의 초기값을 담당하는 객체 배열이다.</p>
<p>상품 item과 카트에 담긴 상품의 배열이 담겨져있다.</p>
<h4 id="itemreducersjs">itemReducers.js</h4>
<pre><code class="language-jsx">// reducers/itemReducer.js

import { REMOVE_FROM_CART, ADD_TO_CART, SET_QUANTITY } from &quot;../actions/index&quot;;//action의 type과 payload를 가져오는 함수
import { initialState } from &quot;./initialState&quot;;//초기값

const itemReducer = (state = initialState, action) =&gt; {
  switch (action.type) {
    case ADD_TO_CART:
      //TODO : cartItems에 아이템 추가
      // 객체의 불변성을 위해 ...state로 기존 배열을 복사한 후 추가한다.
      //state(initialState)의 cartItems를 변경시키는 것.
      return {
        ...state,
        cartItems: [...state.cartItems, action.payload],
      };
    case REMOVE_FROM_CART:
      //TODO : cartItem에서 해당 상품 제거
      // action.payload &lt;&lt; 여기에 itemId 들어가 있음.
      const removearr = state.cartItems.filter((it) =&gt; {
        return it.itemId !== action.payload.itemId;
      });
      return {
        ...state,
        cartItems: removearr,
      };
    case SET_QUANTITY:
      // TODO : 상품의 수량 변경
      let idx = state.cartItems.findIndex(
        (el) =&gt; el.itemId === action.payload.itemId
      ); // cartItem 배열에서 payload로 온 itemid랑 같은 인덱스
      let updatedcart = [...state.cartItems];
      // cartItems 배열 복사
      const sameItem = updatedcart[idx];
      // 복사한 배열에서 상품 추출

      let updated = {
        ...sameItem,
        quantity: action.payload.quantity,
      };
      updatedcart[idx] = updated;
      return {
        ...state,
        cartItems: updatedcart,
      };
    default:
      return state;
  }
};

export default itemReducer;</code></pre>
<p>초기값을 state로 사용하는 itemReducer다.</p>
<p>각 action.type마다 실행하는 로직이 있으며, initialState의 상태를 변경시킨다.</p>
<h3 id="action">action</h3>
<pre><code class="language-jsx">//actions/index.js
export const ADD_TO_CART = &quot;ADD_TO_CART&quot;;
export const REMOVE_FROM_CART = &quot;REMOVE_FROM_CART&quot;;
export const SET_QUANTITY = &quot;SET_QUANTITY&quot;;


export const addToCart = (itemId) =&gt; {
  return {
    type: ADD_TO_CART,
    payload: {
      quantity: 1,
      itemId,
    },
  };
};

export const removeFromCart = (itemId) =&gt; {
  return {
    type: REMOVE_FROM_CART,
    payload: {
      itemId,
    },
  };
};

export const setQuantity = (itemId, quantity) =&gt; {
  return {
    type: SET_QUANTITY,
    payload: {
      quantity,
      itemId,
    },
  };
};</code></pre>
<p>각 함수마다 전달하는 action type,payload가 담겨져있다.</p>
<h3 id="itemlistcontainer">itemListContainer</h3>
<pre><code class="language-jsx">//pages/ItemListContainer
import React from &quot;react&quot;;
import { addToCart, notify } from &quot;../actions/index&quot;;
import { useSelector, useDispatch } from &quot;react-redux&quot;;
import Item from &quot;../components/Item&quot;;

function ItemListContainer() {
  const state = useSelector((state) =&gt; state.itemReducer);
  // rootReducer에서 가져온 reducer 중 하나인 itemReudcer
  // 해당 reducer에서 useSelector를 통하여 state를 가져온다.
  const { items, cartItems } = state;
  // state(initialState)는 총 2개의 배열이 있으며 이것을 구조 분해 할당을 통해 변수로 선언해준다.
  const dispatch = useDispatch();
// action을 전달하기 위한 useDispatch 선언
  const handleClick = (item) =&gt; {
    if (!cartItems.map((el) =&gt; el.itemId).includes(item.id)) {
      dispatch(addToCart(item.id));
      dispatch(notify(`장바구니에 ${item.name}이(가) 추가되었습니다.`));
    } else {
      dispatch(notify(&quot;이미 추가된 상품입니다.&quot;));
    }
  };

  return (
    &lt;div id=&quot;item-list-container&quot;&gt;
      &lt;div id=&quot;item-list-body&quot;&gt;
        &lt;div id=&quot;item-list-title&quot;&gt;쓸모없는 선물 모음&lt;/div&gt;
        {items.map((item, idx) =&gt; (
          &lt;Item
            item={item}
            key={idx}
            handleClick={() =&gt; {
              handleClick(item);
            }}
          /&gt;
        ))}
      &lt;/div&gt;
    &lt;/div&gt;
  );
}

export default ItemListContainer;</code></pre>
<p>상품을 보여주는 메인 화면이다.</p>
<p>상품 추가를 해주는 함수 handleClick이 선언되어있다.</p>
<h3 id="shoppingcart">ShoppingCart</h3>
<pre><code class="language-jsx">// pages/ShoppingCart.js

import React, { useState } from &quot;react&quot;;
import { useDispatch, useSelector } from &quot;react-redux&quot;;
import { removeFromCart, setQuantity } from &quot;../actions&quot;;
import CartItem from &quot;../components/CartItem&quot;;
import OrderSummary from &quot;../components/OrderSummary&quot;;

export default function ShoppingCart() {
  const state = useSelector((state) =&gt; state.itemReducer);
  const { cartItems, items } = state;
  const dispatch = useDispatch();
  const [checkedItems, setCheckedItems] = useState(
    cartItems.map((el) =&gt; el.itemId)
  );

  const handleCheckChange = (checked, id) =&gt; {
    if (checked) {
      setCheckedItems([...checkedItems, id]);
    } else {
      setCheckedItems(checkedItems.filter((el) =&gt; el !== id));
    }
  };

  const handleAllCheck = (checked) =&gt; {
    if (checked) {
      setCheckedItems(cartItems.map((el) =&gt; el.itemId));
    } else {
      setCheckedItems([]);
    }
  };

  // 수량 수정 함수
  const handleQuantityChange = (quantity, itemId) =&gt; {
    // 선택된 상품의 id와 수량을 넘겨준다.
    dispatch(setQuantity(itemId, quantity));
  };

  // 장바구니 상품 제거 함수
  const handleDelete = (itemId) =&gt; {
    setCheckedItems(checkedItems.filter((el) =&gt; el !== itemId));
    dispatch(removeFromCart(itemId));
  };

  const getTotal = () =&gt; {
    let cartIdArr = cartItems.map((el) =&gt; el.itemId);
    let total = {
      price: 0,
      quantity: 0,
    };
    for (let i = 0; i &lt; cartIdArr.length; i++) {
      if (checkedItems.indexOf(cartIdArr[i]) &gt; -1) {
        let quantity = cartItems[i].quantity;
        let price = items.filter((el) =&gt; el.id === cartItems[i].itemId)[0]
          .price;

        total.price = total.price + quantity * price;
        total.quantity = total.quantity + quantity;
      }
    }
    return total;
  };

  const renderItems = items.filter(
    (el) =&gt; cartItems.map((el) =&gt; el.itemId).indexOf(el.id) &gt; -1
  );
  const total = getTotal();

  return (
    &lt;div id=&quot;item-list-container&quot;&gt;
      &lt;div id=&quot;item-list-body&quot;&gt;
        &lt;div id=&quot;item-list-title&quot;&gt;장바구니&lt;/div&gt;
        &lt;span id=&quot;shopping-cart-select-all&quot;&gt;
          &lt;input
            type=&quot;checkbox&quot;
            checked={checkedItems.length === cartItems.length ? true : false}
            onChange={(e) =&gt; handleAllCheck(e.target.checked)}
          &gt;&lt;/input&gt;
          &lt;label&gt;전체선택&lt;/label&gt;
        &lt;/span&gt;
        &lt;div id=&quot;shopping-cart-container&quot;&gt;
          {!cartItems.length ? (
            &lt;div id=&quot;item-list-text&quot;&gt;장바구니에 아이템이 없습니다.&lt;/div&gt;
          ) : (
            &lt;div id=&quot;cart-item-list&quot;&gt;
              {renderItems.map((item, idx) =&gt; {
                const quantity = cartItems.filter(
                  (el) =&gt; el.itemId === item.id
                )[0].quantity;
                return (
                  &lt;CartItem
                    key={idx}
                    handleCheckChange={handleCheckChange}
                    handleQuantityChange={handleQuantityChange}
                    handleDelete={handleDelete}
                    item={item}
                    checkedItems={checkedItems}
                    quantity={quantity}
                  /&gt;
                );
              })}
            &lt;/div&gt;
          )}
          &lt;OrderSummary total={total.price} totalQty={total.quantity} /&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>장바구니를 보여주는 페이지다.</p>
<p>장바구니에서 상품 삭제 및 수량 변경이 가능하다.</p>
<h3 id="그-외-컴포넌트들">그 외 컴포넌트들</h3>
<h4 id="1-item">1. item</h4>
<pre><code class="language-jsx">import React from &#39;react&#39;

export default function Item({ item, handleClick }) {

  return (
    &lt;div key={item.id} className=&quot;item&quot;&gt;
      &lt;img className=&quot;item-img&quot; src={item.img} alt={item.name}&gt;&lt;/img&gt;
      &lt;span className=&quot;item-name&quot; data-testid={item.name}&gt;{item.name}&lt;/span&gt;
      &lt;span className=&quot;item-price&quot;&gt;{item.price}&lt;/span&gt;
      &lt;button className=&quot;item-button&quot; onClick={(e) =&gt; handleClick(e, item.id)}&gt;장바구니 담기&lt;/button&gt;
    &lt;/div&gt;
  )
}</code></pre>
<p>ItemListContainer에 있는 컴포넌트로 장바구니 담기의 기능을 담당하는 함수에 params를 전달해준다.</p>
<h4 id="2-cartitem">2. cartItem</h4>
<pre><code class="language-jsx">import React from &#39;react&#39;

export default function CartItem({
  item,
  checkedItems,
  handleCheckChange,
  handleQuantityChange,
  handleDelete,
  quantity
}) {
  return (
    &lt;li className=&quot;cart-item-body&quot;&gt;
      &lt;input
        type=&quot;checkbox&quot;
        className=&quot;cart-item-checkbox&quot;
        onChange={(e) =&gt; {
          handleCheckChange(e.target.checked, item.id)
        }}
        checked={checkedItems.includes(item.id) ? true : false} &gt;
      &lt;/input&gt;
      &lt;div className=&quot;cart-item-thumbnail&quot;&gt;
        &lt;img src={item.img} alt={item.name} /&gt;
      &lt;/div&gt;
      &lt;div className=&quot;cart-item-info&quot;&gt;
        &lt;div className=&quot;cart-item-title&quot; data-testid={`cart-${item.name}`}&gt;{item.name}&lt;/div&gt;
        &lt;div className=&quot;cart-item-price&quot;&gt;{item.price} 원&lt;/div&gt;
      &lt;/div&gt;
      &lt;input
        type=&quot;number&quot;
        min={1}
        className=&quot;cart-item-quantity&quot;
        value={quantity.toString().replace(/(^0+)/, &quot;&quot;)}
        // input에서 한 자리 숫자면 앞에 0이 붙는걸 제거해주는 표현식
        onChange={(e) =&gt; {
          handleQuantityChange(Number(e.target.value), item.id)
        }}&gt;
      &lt;/input&gt;
      &lt;button className=&quot;cart-item-delete&quot; onClick={() =&gt; { handleDelete(item.id) }}&gt;삭제&lt;/button&gt;
    &lt;/li &gt;
  )
}</code></pre>
<p>장바구니 페이지인 ShoppingCart에 있는 컴포넌트.</p>
<p>수량 변경 및 장바구니에서 상품 삭제를 담당하는 함수에 params를 넘겨준다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL 14) 클라이언트-서버]]></title>
            <link>https://velog.io/@liso_o/TIL</link>
            <guid>https://velog.io/@liso_o/TIL</guid>
            <pubDate>Tue, 25 Apr 2023 02:47:10 GMT</pubDate>
            <description><![CDATA[<h3 id="1-클라이언트-서버-아키텍처">1. 클라이언트-서버 아키텍처</h3>
<p>우리가 사용하는 웹 페이지는 대부분 2티어 아키텍처로 이루어져 있다.</p>
<p>2티어 아키텍처란, 리소스가 존재하는 곳과 리소스를 사용하는 앱으로 분리시킨 구조를 말한다.</p>
<p><img src="https://velog.velcdn.com/images/liso_o/post/265c29a5-b672-413a-a279-af78e1dd8196/image.png" alt=""></p>
<p>다른말로 <code>클라이언트-서버 아키텍처</code> 라고 부르기도 한다.</p>
<p>클라이언트 - 서버는 각각 서로에게 요청과 응답을 주고받는다.</p>
<p>여기서, 서버가 리소스를 다른 공간에서 가져오는 방식일 경우 3티어 아키텍처라고 부른다.</p>
<p>다른 공간의 대표적 예시는 <code>데이터베이스</code> 이다.</p>
<p><img src="https://velog.velcdn.com/images/liso_o/post/9f71fb14-474d-4e70-b1c3-329e22a37b38/image.png" alt=""></p>
<p>우리는 이 3티어 아키텍처를 크게 2개의 단으로 나눌수 있다.</p>
<ol>
<li>사용자가 눈으로 보고 사용하는 클라이언트 부분인 <code>프론트엔드</code></li>
<li>리소스를 전달 및 저장하는 서버-데이터베이스 부분인 <code>백엔드</code></li>
</ol>
<h3 id="2-클라이언트-서버-통신와-api">2. 클라이언트-서버 통신와 API</h3>
<h4 id="1-프로토콜">1) 프로토콜</h4>
<p>클라이언트와 서버 간의 통신을 알아보려면, 프로토콜이라는 개념을 알아야 한다.</p>
<p>프로토콜은 통신 규약으로 클라이언트가 서버에게 요청을 할때 반드시 지켜야 할 규약이 있다.</p>
<p>웹 애플리케이션에서 클라이언트와 서버가 서로 <code>HTTP</code> 라는 프로토콜을 이용해서 요청과 응답을 주고받는다.</p>
<p><code>HTTP</code> 를 이용해 주고받는 메시지를 <code>HTTP 메시지</code> 라고부른다.</p>
<p><img src="https://velog.velcdn.com/images/liso_o/post/803b92ee-6acf-451a-bb29-fb6b34f8c2a8/image.png" alt=""></p>
<p>HTTP 프로토콜 말고도 다른 종류의 프로토콜이 있다.</p>
<p>여러 종류의 프로토콜이 있는 만큼 각각 지켜야 할 규약이 있다.
<img src="https://velog.velcdn.com/images/liso_o/post/056a5abd-3120-4ed2-bbba-c0f45b8158ff/image.png" alt=""></p>
<h4 id="2-api">2) API</h4>
<p>클라이언트가 리소스를 요청했을때 서버는 클라이언트에게 리소스를 잘 활용할 수 있도록 인터페이스를 제공해주는데, 이것을 <code>API</code> 라고 한다.</p>
<p><img src="https://velog.velcdn.com/images/liso_o/post/7c80505d-7ae3-40e1-8839-c0f458f0645d/image.png" alt=""></p>
<p>서버가 API를 구축해놓아야 클라이언트가 이용할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/liso_o/post/e922df81-a23a-4d0c-b6cf-3ec01881618b/image.png" alt=""></p>
<h3 id="3-url--uri">3. URL &amp; URI</h3>
<h4 id="1-url">1) URL</h4>
<p>URL은 네트워크 상에서 웹 페이지,이미지,동영상 등의 파일이 위치한 정보를 나타낸다.</p>
<p>url은 다음과 같이 총 3개로 구분할 수 있다.</p>
<ol>
<li>scheme : 통신 방식(프로토콜)을 정의함</li>
<li>hosts : 웹 서버의 이름이나 도메인을 사용하여 주소를 나타냄</li>
<li>url-path : 루트 디렉토리부터 웹 페이지 등이 위치한 경로와 파일명을 나타냄</li>
</ol>
<p>URI는 url에 추가적으로 <code>query</code> 를 더한다. </p>
<ul>
<li>query : 웹 서버에 보내는 추가적인 질문</li>
</ul>
<p><img src="https://velog.velcdn.com/images/liso_o/post/d8cb4d66-2946-45ee-8318-0a813206cf66/image.png" alt=""></p>
<h3 id="4-도메인--dns">4. 도메인 &amp; DNS</h3>
<ul>
<li><p>도메인 : 웹 브라우저를 통해 특정 사이트에 진입을 할 때 IP 주소를 대신해 사용하는 주소를 도메인이라고 한다. <code>nslookup 도메인</code> 을 통해 해당 도메인의 IP주소를 확인할 수 있다.</p>
</li>
<li><p>DNS : 위 도메인과 IP주소를 매칭해주는 작업을 하는 서버를 뜻한다.</p>
</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>