<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>ahyeon.log</title>
        <link>https://velog.io/</link>
        <description>https://a-honey.tistory.com/</description>
        <lastBuildDate>Sun, 25 Feb 2024 14:41:57 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>ahyeon.log</title>
            <url>https://velog.velcdn.com/images/a-honey/profile/3ad166a8-1338-4410-8c1a-b2414b08b92a/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. ahyeon.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/a-honey" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[데이터모델링]]></title>
            <link>https://velog.io/@a-honey/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%AA%A8%EB%8D%B8%EB%A7%81</link>
            <guid>https://velog.io/@a-honey/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%AA%A8%EB%8D%B8%EB%A7%81</guid>
            <pubDate>Sun, 25 Feb 2024 14:41:57 GMT</pubDate>
            <description><![CDATA[<h3 id="데이터-모델링">데이터 모델링</h3>
<p>개념적 구조 정하는 것 &gt; 논리적 모델링
데이터베이스 구축에 필요한 걸 정하는 것 &gt; 물리적 모델링 // 제약사항, 데이터 타입 설정 등
<img src="https://velog.velcdn.com/images/a-honey/post/742eaa7c-fb84-4cb3-b2bb-eee024d95b06/image.png" alt=""></p>
<h3 id="데이터-모델">데이터 모델</h3>
<p>데이터를 사용하려는 목적에 맞게 정리하고 체계화해놓은 모형</p>
<p>개체Entity: 저장하고 싶은 데이터의 대상
Entity: 실제 대상 하나하나 row
Entity Type: 일반화한 Entity 종류 table
속성Attribute: Entity에 대해서 저장하려는 내용 column
관계Relationship: Entity들 사이 연결점
제약 조건 Constraint: 여러 데이터 요소들에 있는 규칙</p>
<p>데이터 모델링은 Entity, Attribute, Relationship, Constraint를 파악하고, 발전시켜 데이터 모델을 만드는 과정
저장하고자 하는 데이터에서 Entity, Attribute, Relationship, Constraint 파악하여
데이터베이스를 구축할 때 기반이 될 모델 만들기</p>
<p>Relational Model: 데이터를 relation, 즉 테이블로 정리해서 표현한 모델
Relation은 테이블을 의미하는 수학적 표현
Relationship 테이블들 사이 맺어지는 관계</p>
<p>단점
row가 있어서 Entity, Attribute, Relationship, Constraint 등 구조 설계 불필요
Foregin Key를 확인해야하기 때문에 relationship의 특성을 한눈에 알기 힘듦.</p>
<p>Entity Relationship Model(ERM)
Entity와 Relationship 중심으로 표현
Entity에 attribute를 넣고 Entity 선으로 연결</p>
<h4 id="데이터-모델-스펙트럼">데이터 모델 스펙트럼</h4>
<ul>
<li>개념 모델Conceptual Model: 큼지막한 entity와 간단한 연결 관계, 대략적인 구조 파악 :: 경영인, 또는 기획자
사진 설명을 입력하세요.</li>
<li>논리 모델Logical Model: entity와 attribute, primary key foreign key 표시 :: 개발자 구체화 단계
사진 설명을 입력하세요.</li>
<li>물리 모델Physical Model: 변수이름, 데이터타입, 인덱스 등 구체적으로 표시 :: 데이터베이스 구축 단계</li>
</ul>
<h4 id="단점">단점</h4>
<p>데이터 중복 저장, NULL이 많음
연산 실행이 너무 오래 걸림
원하는 정보를 찾을 수 없음
틀린 데이터를 저장하고 있음</p>
<h4 id="장점">장점</h4>
<p>중복, 틀린 데이터 없음
빠르고 정확하게 데이터를 다룰 수 있음
중복되는 데이터가 저장되지 않는다
NULL이 생기지 않는다
데이터가 늘어날 때 테이블 구조가 변하지 않는</p>
<p>데이터 모델링을 어떻게 했는지에 따라, 데이터베이스가 좋을 수도 나쁠 수도 있음</p>
<h3 id="erd">ERD</h3>
<p>엔터티-관계 다이어그램(Entity-Relationship Diagram)
데이터 모델링의 일종
각 엔터티는 시스템 내의 구체적인 데이터 항목을 나타내며, 관계는 이러한 엔터티 간의 연결을 보여줌</p>
<p>데이터 모델링은 ERD를 만들고 관계형 데이터베이스 설계를 포함하는 과정 전반을 의미</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[FEConf 2023 [B2] SSR 환경(Node.js) 메모리 누수 디버깅 가이드]]></title>
            <link>https://velog.io/@a-honey/FEConf-2023-B2-SSR-%ED%99%98%EA%B2%BDNode.js-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EB%88%84%EC%88%98-%EB%94%94%EB%B2%84%EA%B9%85-%EA%B0%80%EC%9D%B4%EB%93%9C</link>
            <guid>https://velog.io/@a-honey/FEConf-2023-B2-SSR-%ED%99%98%EA%B2%BDNode.js-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EB%88%84%EC%88%98-%EB%94%94%EB%B2%84%EA%B9%85-%EA%B0%80%EC%9D%B4%EB%93%9C</guid>
            <pubDate>Sun, 18 Feb 2024 15:55:48 GMT</pubDate>
            <description><![CDATA[<p><a href="https://youtu.be/P3C7fzMqIYg?si=7UlAFYzOPb9VmF3x">https://youtu.be/P3C7fzMqIYg?si=7UlAFYzOPb9VmF3x</a></p>
<p>SSR 환경(Node.js) 메모리 누수 디버깅 가이드
메모리 누수는 실제로는 필요하지 않은데, 메모리를 계속 차지하고 있는 현상</p>
<p>JS를 동작시킬 메모리가 부족하니까, 성능이 좋지 않게 됨
-GC의 활동이 늘어나면, CPU 사용량이 늘어나요.
-CPU intensive한 작업이 늘어나면, 이벤트 루프가 블로킹되서 연산이 느려져요
띄워놓은 Node.js 서버가 죽어요
-SIGABRT 등의 시그널로 인한 프로세스 종료
-인스턴스가 재시작되고 일부 요청에 대한 응답이 실패
-로드밸런서가 HTTP Status 502(Bad Gateway) 에러를 뱉을 수 있어요.
-가용성에 문제가 있을 수 있단 뜻이에요.</p>
<p>힙메모리를 늘려주거나,
메모리 누수의 범인을 디버깅한다</p>
<p>메모리 누수가 있는지 어떻게 알 수 있어요?
Allocation failed-JavaScript heap out of memory</p>
<p>Server Side
Grafana, DATADOG, AWS Cloudwatch</p>
<p>힙메모리를 늘려줘도, 이 코드는 계속 메모리 누수를 일으켜요.</p>
<p>V8의 메모리 관리 방식</p>
<p>살아남은 객체들은 계속해서 힙메모리에 존재해요!
가비지 콜렉터로부터 계속 살아남으면 어떻게 될까요?
<img src="https://velog.velcdn.com/images/a-honey/post/051b031f-5ebf-43b9-85c5-75fe6c399af0/image.png" alt=""></p>
<p>Node.js 메모리 부족 어떻게 해결함? –max-old-space-size</p>
<p>계속 살아남은 객체는 Old space로,
계속 힙메모리를 차지하면 OOM</p>
<p>힙메모리를 늘리는 것이 무조건적인 정답은 아님 </p>
<p>대표적인 메모리 누수를 일으키는 요인들
-전역변수
-해제되지 않은 타이머
-클로저</p>
<p>Node –inspect index.js</p>
<p>개발자 도구 – 메모리
 <img src="https://velog.velcdn.com/images/a-honey/post/9f6918d4-12d2-4274-aa55-6d8fd54d7db1/image.png" alt=""></p>
<p>Shallow Size: 오브젝트 자신의 크기bytes
Retained Size: 나 자신 + 참조하고 있는 오브젝트들의 크기</p>
<p>Shallow Size에 비해서 Retained Size가 큰 것들을 찾기</p>
<p>Using
현재 JavaScript TC39 Stage 3
C#의 using
Let, const, var 같이 변수를 선언할 때 쓰는 키워드
스코프 끝에서 Symbol.dispose()를 호출해서 클린업 할 수 있음</p>
<p>서버 환경과 클라이언트 환경으로 나눠서 디버깅하자
서버 환경에서는 node –inspect 옵션을 쓰자
timeline으로 힙메모리 사용량을 프로파일링하는 것이 유용하다
Shallow Size 대비 Retained Size가 큰 객체를 찾자
Using을 잘 쓰면 덜 고통스러울지도!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Singleton Pattern]]></title>
            <link>https://velog.io/@a-honey/Singleton-Pattern</link>
            <guid>https://velog.io/@a-honey/Singleton-Pattern</guid>
            <pubDate>Sun, 18 Feb 2024 12:32:29 GMT</pubDate>
            <description><![CDATA[<h1 id="singleton-pattern">Singleton Pattern</h1>
<p>어떤 클래스가 단 하나의 인스턴스만을 가지도록 보장하며, 이 인스턴스에 접근할 수 있는 전역적인 접근점을 제공하는 디자인 패턴
어떤 클래스에 대해 하나의 인스턴스만 필요할 때 유용</p>
<h4 id="private-constructor">Private Constructor</h4>
<p>클래스의 생성자를 private으로 만들어 외부에서 직접 객체를 생성하는 것을 막음</p>
<h4 id="static-instance">Static Instance</h4>
<p>클래스 내부에서 유일한 인스턴스를 생성하고, 이에 접근할 수 있는 정적 메서드나 속성을 제공</p>
<h4 id="lazy-initialization지연-초기화">Lazy Initialization(지연 초기화)</h4>
<p>인스턴스가 필요한 시점에 생성</p>
<h2 id="헤드퍼스트-디자인-패턴">헤드퍼스트 디자인 패턴</h2>
<p>스레드 풀, 캐시, 대화상자, 사용자 설정, 레지스트리 설정을 처리하는 객체, 로그 기록용 객체, 디바이스 드라이버 등 객체를 사용할 때 인스턴스가 2개 이상이면 오류가 나는 경우, 싱글톤 패턴을 통해 인스턴스를 하나만 생성</p>
<p>전역 변수를 사용할 때와 마찬가지로 객체 인스턴스를 어디서든지 엑세스할 수 있게 만들 수 있으며, 전역 변수를 쓸 때처럼 여러 단점을 감수할 필요도 없음</p>
<h2 id="자바스크립트의-싱글톤-패턴">자바스크립트의 싱글톤 패턴</h2>
<pre><code>class Singleton {
  constructor() {
    ...
  }

  static instance = null;

  static getInstance() {
    if (!Singleton.instance) {
      Singleton.instance = new Singleton();
    }
    return Singleton.instance;
  }

}

const singleton1 = Singleton.getInstance();
const singleton2 = Singleton.getInstance();

console.log(singleton1 === singleton2); // true</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Factory Pattern]]></title>
            <link>https://velog.io/@a-honey/Factory-Pattern</link>
            <guid>https://velog.io/@a-honey/Factory-Pattern</guid>
            <pubDate>Tue, 06 Feb 2024 19:21:47 GMT</pubDate>
            <description><![CDATA[<h1 id="factory-pattern">Factory Pattern</h1>
<p>객체 생성을 캡슐화하고, 생성되는 구체적인 객체의 유형을 클라이언트 코드로부터 분리하는 데 사용됨</p>
<p>객체 생성을 담당하는 팩토리 클래스를 도입. 클라이언트 코드는 팩토리 클래스를 통해 객체를 생성하며, 이를 통해 클라이언트 코드는 구체적인 객체의 클래스에 대한 지식 없이도 해당 객체를 얻을 수 있음</p>
<h4 id="product-interface">Product Interface</h4>
<p>생성될 객체에 대한 인터페이스를 정의</p>
<h4 id="concrete-product-classes">Concrete Product Classes</h4>
<p>제품 인터페이스를 구현한 실제 객체들</p>
<h4 id="factory-interface">Factory Interface</h4>
<p>객체를 생성하는 메서드를 선언한 인터페이스</p>
<h4 id="concrete-product-classes-1">Concrete Product Classes</h4>
<p>팩토리 인터페이스를 구현하여 객체를 생성하는 구체적인 클래스들</p>
<h4 id="client">Client</h4>
<p>팩토리 클래스를 통해 객체를 생성하는 주체로, 구체적인 객체의 클래스에 대한 지식 없이도 객체를 생성 가능자</p>
<h2 id="자바스크립트-코드로-알아보기">자바스크립트 코드로 알아보기</h2>
<pre><code>// 제품 인터페이스
class Product {
  display() {
    throw new Error(&quot;Method &#39;display&#39; must be implemented&quot;);
  }
}

// 구체적인 제품 클래스
class ConcreteProductA extends Product {
  display() {
    console.log(&quot;Product A&quot;);
  }
}

class ConcreteProductB extends Product {
  display() {
    console.log(&quot;Product B&quot;);
  }
}

// 팩토리 인터페이스
class Factory {
  createProduct() {
    throw new Error(&quot;Method &#39;createProduct&#39; must be implemented&quot;);
  }
}

// 구체적인 팩토리 클래스
class ConcreteFactoryA extends Factory {
  createProduct() {
    return new ConcreteProductA();
  }
}

class ConcreteFactoryB extends Factory {
  createProduct() {
    return new ConcreteProductB();
  }
}

// 클라이언트
const factoryA = new ConcreteFactoryA();
const productA = factoryA.createProduct();
productA.display(); // 출력: Product A

const factoryB = new ConcreteFactoryB();
const productB = factoryB.createProduct();
productB.display(); // 출력: Product B
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Decorator Pattern]]></title>
            <link>https://velog.io/@a-honey/Decorator-Pattern</link>
            <guid>https://velog.io/@a-honey/Decorator-Pattern</guid>
            <pubDate>Sun, 04 Feb 2024 11:11:17 GMT</pubDate>
            <description><![CDATA[<h1 id="decorator-pattern">Decorator Pattern</h1>
<p>객체에 동적으로 새로운 책임을 추가하는 패턴
상속을 통해 기능을 확장하는 대신, 객체를 감싸는 방식으로 기능을 추가하거나 변경
기능을 조합하여 새로운 객체를 생성 가능</p>
<p>서브클래스를 만드는 방식으로 행동을 상속받으면, 그 행동은 컴파일할 때 완전히 결정됨. 게다가 모든 서브클래스에서 똑같은 행동을 상속받아야함 =&gt; 구성을 통해 객체의 행동을 확장하면 실행 중에 동적으로 행동을 설정가능함</p>
<h4 id="component-구성-요소">Component 구성 요소</h4>
<p>인터페이스나 추상 클래스로 정의된 기본 객체
객체에 동적으로 책임을 추가할 수 있어야 함</p>
<h4 id="concretecomponent-구체적인-구성-요소">ConcreteComponent 구체적인 구성 요소</h4>
<p>Component를 구현한 구체적인 클래스
기본 기능을 제공</p>
<h4 id="decorator-데코레이터">Decorator 데코레이터</h4>
<p>Component와 동일한 인터페이스를 가지면서 내부에 Component를 갖는 클래스
자신의 행위를 수행한 후에 또 다른 Component를 호출하여 작업을 전달</p>
<h4 id="concretedecorator-구체적인-데코레이터">ConcreteDecorator 구체적인 데코레이터</h4>
<p>Decorator를 구현한 구체적인 클래스
추가적인 기능을 제공하거나 기존 기능을 수정함</p>
<h3 id="확장성">확장성</h3>
<p>새로운 기능을 추가하거나 변경하기 쉬움. 상속보다 유연하게 기능을 확장 가능</p>
<h3 id="구성을-통한-재사용">구성을 통한 재사용</h3>
<p>객체를 감싸는 방식을 통해 기능을 조합하여 재사용 가능</p>
<h2 id="자바스크립트에서-이해하기">자바스크립트에서 이해하기</h2>
<h4 id="component">Component</h4>
<pre><code>class Coffee {
  cost() {
    return 5; // 기본 가격
  }

  description() {
    return &#39;기본 커피&#39;;
  }
}</code></pre><h4 id="decorator">Decorator</h4>
<pre><code>class CoffeeDecorator {
  constructor(coffee) {
    this._coffee = coffee;
  }

  cost() {
    return this._coffee.cost();
  }

  description() {
    return this._coffee.description();
  }
}</code></pre><h4 id="concretecomponent">ConcreteComponent</h4>
<pre><code>class MilkDecorator extends CoffeeDecorator {
  constructor(coffee) {
    super(coffee);
  }

  cost() {
    return super.cost() + 2; // 우유 추가 가격
  }

  description() {
    return super.description() + &#39;, 우유 추가&#39;;
  }
}</code></pre><pre><code>class SugarDecorator extends CoffeeDecorator {
  constructor(coffee) {
    super(coffee);
  }

  cost() {
    return super.cost() + 1; // 설탕 추가 가격
  }

  description() {
    return super.description() + &#39;, 설탕 추가&#39;;
  }
}</code></pre><h4 id="실행">실행</h4>
<pre><code>// 기본 커피 주문
let simpleCoffee = new Coffee();
console.log(simpleCoffee.description()); // 기본 커피
console.log(simpleCoffee.cost()); // 5

// 우유 추가한 커피 주문
let milkCoffee = new MilkDecorator(simpleCoffee);
console.log(milkCoffee.description()); // 기본 커피, 우유 추가
console.log(milkCoffee.cost()); // 7

// 설탕 추가한 커피 주문
let sugarCoffee = new SugarDecorator(simpleCoffee);
console.log(sugarCoffee.description()); // 기본 커피, 설탕 추가
console.log(sugarCoffee.cost()); // 6

// 우유와 설탕 모두 추가한 커피 주문
let milkAndSugarCoffee = new SugarDecorator(new MilkDecorator(simpleCoffee));
console.log(milkAndSugarCoffee.description()); // 기본 커피, 우유 추가, 설탕 추가
console.log(milkAndSugarCoffee.cost()); // 8
</code></pre><h2 id="ocpopen-closed-principle">OCP(Open-Closed Principle)</h2>
<p>클래스는 확장에는 열려 있어야 하지만 변경에는 닫혀있어야한다는 디자인 원칙</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Observer Pattern]]></title>
            <link>https://velog.io/@a-honey/Observer-Pattern</link>
            <guid>https://velog.io/@a-honey/Observer-Pattern</guid>
            <pubDate>Sun, 28 Jan 2024 10:00:37 GMT</pubDate>
            <description><![CDATA[<h1 id="observer-pattern">Observer Pattern</h1>
<p>옵저번 패턴은 객체 간의 일대다 의존 관계를 정의하는 패턴
어떤 객체의 상태가 변할 때, 그 객체에 의존하는 다른 객체들이 이 변화를 통지받아 자동으로 업데이트될 수 있도록 하는 구조
객체 간의 결합도를 낮추고, 확장성을 높이는데 도움을 줌</p>
<h3 id="장점">장점</h3>
<p>비동기적인 이벤트 처리에 유용하며, 객체 간의 강한 결합을 피하고, 이벤트가 발생할 때마다 관련된 옵저버들이 동작할 수 있도록 해줌
새로운 옵저버를 추가하거나 기존의 옵저버를 제거하는 것이 간단해 코드의 유지보수성과 확장성을 높이는데 도움
주체와 옵저버가 각각 독립된 객체로 존재하기 때문에 코드의 재사용성과 모듈화가 강화됨
이벤트 기반의 자바스크립트에 적합</p>
<h3 id="단점">단점</h3>
<p>옵저버가 등록된 순서대로 이벤트를 받기 때문에, 순서가 중요하지 않은 경우 번거로울 수 있음
주체에서 옵저버를 명시적으로 제거하지 않을 경우 메모리 누수가 일어날 수 있음
이벤트가 발생할 때마다 모든 옵저버에게 통지되므로, 성능에 영향을 줄 수 있음
규모가 커지면서 관리해야할 옵저버의 수가 많아질 경우 코드가 복잡해질 수 있음</p>
<h4 id="subject">Subject</h4>
<p>상태를 관리하는 주체 객체
옵저버들을 등록하고, 상태가 변할 때 등록된 옵저버들에게 알림을 보냄</p>
<h4 id="observer">Observer</h4>
<p>주체의 상태 변화를 감시하고, 변화가 있을 경우 특정 동작을 수행하는 객체
주체에 등록되어 있어야 하며, 주체로부터 상태 변화에 대한 통지를 받게 됨</p>
<pre><code>class Observable {
  constructor() {
    this.observers = [];  // 이벤트가 일어날때 알릴 observers list
  }

  subscribe(func) {  // observers list에 observer를 추가하는 메서드
    this.observers.push(func);
  }

  unsubscribe(func) {  // observers list에서 observer를 제거하는 메서드
    this.observers = this.observers.filter((observer) =&gt; observer !== func);
  }

  notify(data) {  // 이벤트가 일어날 때 모든 observers에게 알리는 메서드
    this.observers.forEach((observer) =&gt; observer(data));
  }
}</code></pre><pre><code>export default function App() {
  return (
    &lt;div className=&quot;App&quot;&gt;
      &lt;Button&gt;Click me!&lt;/Button&gt;
      &lt;FormControlLabel control={&lt;Switch /&gt;} /&gt;
    &lt;/div&gt;
  );
}</code></pre><pre><code>import { ToastContainer, toast } from &quot;react-toastify&quot;;

function logger(data) {
  console.log(`${Date.now()} ${data}`);
}

function toastify(data) {
  toast(data);
}

export default function App() {
  return (
    &lt;div className=&quot;App&quot;&gt;
      &lt;Button&gt;Click me!&lt;/Button&gt;
      &lt;FormControlLabel control={&lt;Switch /&gt;} /&gt;
      &lt;ToastContainer /&gt;
    &lt;/div&gt;
  );
}</code></pre><pre><code>import { ToastContainer, toast } from &quot;react-toastify&quot;;

function logger(data) {
  console.log(`${Date.now()} ${data}`);
}

function toastify(data) {
  toast(data);
}

observable.subscribe(logger);
observable.subscribe(toastify);

export default function App() {
  function handleClick() {
    observable.notify(&quot;User clicked button!&quot;);
  }

  function handleToggle() {
    observable.notify(&quot;User toggled switch!&quot;);
  }

  return (
    &lt;div className=&quot;App&quot;&gt;
      &lt;Button&gt;Click me!&lt;/Button&gt;
      &lt;FormControlLabel control={&lt;Switch /&gt;} /&gt;
      &lt;ToastContainer /&gt;
    &lt;/div&gt;
  );
}</code></pre><h2 id="헤드-퍼스트-디자인-패턴">헤드 퍼스트 디자인 패턴</h2>
<h3 id="느슨한-결합의-위력">느슨한 결합의 위력</h3>
<ul>
<li>subject는 옵저버가 특정 인터페이스를 구현한다는 사실만 압니다.</li>
<li>옵저버는 언제든지 새로 추가할 수 있습니다.</li>
<li>새로운 형식의 옵저버를 추가할 때도 주제를 변경할 필요가 전혀 없습니다</li>
<li>주제와 옵저버는 서로 독립적으로 재사용할 수 있습니다.
주제나 옵저버가 달라져도 서로에게 영향을 미치지 않습니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[리액트 hook]]></title>
            <link>https://velog.io/@a-honey/%EB%A6%AC%EC%95%A1%ED%8A%B8-hook</link>
            <guid>https://velog.io/@a-honey/%EB%A6%AC%EC%95%A1%ED%8A%B8-hook</guid>
            <pubDate>Thu, 18 Jan 2024 19:42:59 GMT</pubDate>
            <description><![CDATA[<h2 id="리액트-hook">리액트 hook</h2>
<h3 id="리액트-hook의-등장-배경">리액트 hook의 등장 배경</h3>
<ol>
<li><p>상태 관리나 생명주기 메서드를 사용하기 위해서는 클래스 컴포넌트 안에 많은 코드를 작성해야 했기 때문에, 컴포넌트의 가독성이 떨어지고 유지보수하기 어려움</p>
</li>
<li><p>클래스 컴포넌트에서는 this 키워드를 사용해야 하기 때문에, 코드 작성 시 바인딩 문제로 혼란스러움</p>
</li>
</ol>
<p>클래스형 컴포넌트의 이러한 문제점으로 리액트 16.8 버전에서 hook이 추가됨</p>
<h3 id="함수형-반응형-프로그래밍">함수형 반응형 프로그래밍</h3>
<p>순수 함수와 데이터 스트림을 사용하여 시간에 따른 시스템 동작을 설명하는 데 중점을 두는 프로그래밍 패러다임
사용자 입력과 같이 데이터가 생성되는 대로 처리하고 시스템 전체에 변경 사항을 자동으로 전파하는 반응형 프로그래밍을 강조
FRP에서는 일반적으로 시간이 지남에 따라 새로운 값을 방출하는 데이터 스트림인 옵저버와 이러한 데이터 스트림의 변경에 반응하는 옵저버로 작업</p>
<h2 id="노마드-코더-실전형-리액트-hooks">노마드 코더 실전형 리액트 Hooks</h2>
<h3 id="usestate">useState</h3>
<h4 id="useinput">useInput</h4>
<pre><code>const useInput = (initialValue, validator) =&gt; {
  const [value, setValue] = useState(initialValue);
  const onChange = e =&gt; {
    const { target: { value }
    } = e;
    let willUpdate = true;
    if (typeof validator === &quot;function&quot;) {
      willUpdate = validator(value);
    }
    if (willUpdate) {
      setValue(value);
    }
  };

  return { value, onChange };
};</code></pre><h4 id="usetabs">useTabs</h4>
<pre><code>const useTabs = (initialTab, allTabs) =&gt; {
  if (!allTabs || Array.isArray(allTabs)) {
    return;
  }
  const [currentIndex, setCurrentIndex = useState(initailTab);

return { 
  currentItem: allTabs[currentIndex], 
  changeItem: setCurrentIndex  
  };
};</code></pre><h3 id="useeffect">useEffect</h3>
<h4 id="usetitle">useTitle</h4>
<pre><code>const useTitle = initialTitle =&gt; {
  const [title, setTitle] = useState(initialTitle);
  const updateTitle = () =&gt; {
    const htmlTitle = document.querySelector(&#39;title&#39;);
    htmlTitle.innerText = title;
  };

  useEffect(updateTitle, [title]);
  return setTitle;
};</code></pre><h4 id="useclick">useClick</h4>
<pre><code>const useClick = (onClick) =&gt; {
  if (typeof onClick !== &quot;function&quot;){
    return;
  }
  const element = useRef();

  useEffect(() =&gt; {
    if (element.current) {
      element.current.addEventListener(&quot;click&quot;, onClick);
    }
    return () =&gt; {
      if (element.current) {
        element.current.removeEventListener(&quot;click&quot;, onClick);
      }
    };
  }, []);
  return element;
};</code></pre><h4 id="useconfirm--usepreventleave">useConfirm &amp; usePreventLeave</h4>
<pre><code>const useConfirm = (message, callback, rejected) =&gt; {
  if (typeof callback !== &quot;function&quot; || typeof rejected !== &quot;function&quot;) {
    return;
  }
  const confirmAction = () =&gt; {
    if (confirm(message)) {
      callback();
    } else {
      rejected();
    }
  };
  return confirmAction;
};</code></pre><pre><code>const usePreventLeave = () =&gt; {
  const listner = (event) =&gt; {
    event.preventDefault();
    event.returnValue = &quot;&quot;;
  };
  const enablePrevent = () =&gt; {
    console.log(&quot;enablePrevent&quot;);
    window.addEventListener(&quot;beforeunload&quot;, listner);
  };
  const disablePrevent = () =&gt;
    window.removeEventListener(&quot;beforeunload&quot;, listner);
  return { enablePrevent, disablePrevent };
};</code></pre><h4 id="usebeforeleave">useBeforeLeave</h4>
<pre><code>const useBeforeLeave = (onBefore) =&gt; {
  const handle = (event) =&gt; {
    const { clientY } = event;
    console.log(clientY);
    if (clientY &lt; 0) onBefore();
  };

  useEffect(() =&gt; {
    document.addEventListener(&quot;mouseleave&quot;, handle);
  }, []);
};</code></pre><h4 id="usefadein--usenetwork">useFadeIn &amp; useNetwork</h4>
<pre><code>const useFadeIn = (duration = 1, delay = 0) =&gt; {
  if (typeof duration !== &quot;number&quot; || typeof delay !== &quot;number&quot;) {
    return;
  }
  const element = useRef();
  useEffect(() =&gt; {
    if (element.current) {
      const { current } = element;
      current.style.transition = `opacity ${duration}s ease-in-out ${delay}s`;
      current.style.opacity = 1;
    }
  }, []);
  return { ref: element, style: { opacity: 0 } };
};</code></pre><pre><code>const useNetwork = () =&gt; {
  const [status, setStatus] = useState(navigator.onLine);
  const handleChange = () =&gt; {
    console.log(&quot;handle chage&quot;);
    setStatus(navigator.onLine);
  };
  console.log(&quot;useNetwork&quot;, navigator.onLine);
  useEffect(() =&gt; {
    window.addEventListener(&quot;online&quot;, handleChange);
    console.log(&quot;online&quot;);
    return () =&gt; window.removeEventListener(&quot;online&quot;, handleChange);
  }, []);
  return status;
};</code></pre><h4 id="usescroll--usefullscreen">useScroll &amp; useFullscreen</h4>
<pre><code>const useScroll = () =&gt; {
  const [state, setState] = useState({ x: 0, y: 0 });
  const onScroll = (event) =&gt; {
    setState({ y: window.scrollY, x: window.scrollX });
  };
  useEffect(() =&gt; {
    window.addEventListener(&quot;scroll&quot;, onScroll);
    return () =&gt; {
      window.removeEventListener(&quot;scroll&quot;, onScroll);
    };
  }, []);
  return state;
};</code></pre><pre><code>const useFullScreen = () =&gt; {
  const element = useRef();
  const triggerFul = () =&gt; {
    if (element.current) {
      console.log(element);
      element.current.webkitRequestFullscreen();
    }
  };
  return { element, triggerFul };
};</code></pre><h4 id="usenotification">useNotification</h4>
<pre><code>const useNotification = (title, options) =&gt; {
  if (!(&quot;Notification&quot; in window)) {
    return;
  }
  const fireNoti = () =&gt; {
    if (Notification.permission !== &quot;granted&quot;) {
      Notification.requestPermission().then((permission) =&gt; {
        if (permission === &quot;granted&quot;) {
          new Notification(title, options);
        } else {
          return;
        }
      });
    } else {
      console.log(&quot;aaa&quot;);
      new Notification(title, options);
    }
  };
  return fireNoti;
};</code></pre><h4 id="useaxios">useAxios</h4>
<pre><code>const useAxios = (opts, axiosInsntance = defaultAxios) =&gt; {
  const [state, setState] = useState({
    loading: true,
    error: null,
    data: null
  });
  const [trigger, setTrigger] = useState(0);
  if (!opts.url) {
    return;
  }
  const refetch = () =&gt; {
    setState({
      ...state,
      loading: true
    });
    setTrigger(Date.now());
    console.log(&quot;refetch&quot;, Date.now());
  };
  useEffect(
    () =&gt; async () =&gt; {
      await axiosInsntance(opts)
        .then((response) =&gt; {
          setState({
            loading: false,
            error: null,
            data: JSON.stringify(response)
          });
        })
        .catch((error) =&gt; {
          setState({
            ...state,
            error: error
          });
        });
    },
    [trigger]
  );
  return { ...state, refetch };
};
export default useAxios;</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[How and When to Memoize Your React Application]]></title>
            <link>https://velog.io/@a-honey/How-and-When-to-Memoize-Your-React-Application</link>
            <guid>https://velog.io/@a-honey/How-and-When-to-Memoize-Your-React-Application</guid>
            <pubDate>Sun, 14 Jan 2024 08:33:54 GMT</pubDate>
            <description><![CDATA[<h1 id="how-and-when-to-memoize-your-react-application">How and When to Memoize Your React Application</h1>
<h2 id="what-is-memoization">What is Memoization?</h2>
<p>Memoization은 메모리 공간을 더 많이 사용하는 대가로 컴퓨터 프로그램의 속도를 올리는데 사용되는 최적화 기술이다.</p>
<p>이 속도는 같은 input parameter가 제공되었을 때 반복적인 계산을 피하고 대신에 캐시된 결과를 사용하기 때문이다. 동시에, 캐시된 결과를 위해 여분의 공간이 사용되기 때문에 높은 메모리 사용을 초래한다.</p>
<p>만약 다음과 같은 이슈를 다뤄야 한다면, memoizing을 해야한다:</p>
<ol>
<li>많은 계산을 하는 높은 함수는 효율적인 수행을 위해 결과를 캐시해야한다.</li>
<li>큰 컴포넌트는 같은 input이 주어졌을때 같은 결과를 렌더링하고 이를 Pure Component라고 부른다. 이 경우, 부모가 리렌더링되지만 Pure Component의 props는 변하지 않을때, 불필요한 비싼 렌더가 memoized되어 스킵될 수 있다.</li>
<li>Pure component의 props나 dependencies to a dependency array가 전달될 때객체나 함수의 참조 무결성을 유지한다.</li>
</ol>
<h2 id="how-does-memoization-work">How Does Memoization Work?</h2>
<p>몇몇 memoization 기술의 경우, React는 컴포넌트가 리렌더링될 것인지를 결정하기 위해 컴포넌트의 props의 얕은 동등 비교를 수행한다. 예를 들어, React.Memo &amp; React.PureComponent가 있다.</p>
<p>만약 얕은 비교 이상을 수행하려면, React.momo는 두 번째 인자로 사용자가 정의한 동등 비교 함수를 가지며 PureComponent의 경우 shouldComponentUpdate 라이프사이클 메서드를 재정의한다.</p>
<p>다른 한편으로, useMemo()와 useCallback은 캐시된 값이 유효한지판단하기 위해 dependency array에 의존한다.</p>
<p>만약 dependency array가 비어있다면, 캐시 무효화가 발생하지 않을 것이다.</p>
<h2 id="drawbacks-of-permature-memoization">Drawbacks of Permature Memoization</h2>
<p>너무 멀리가기 전에, premature optimization에 관해 경고하고 싶다.</p>
<p>당신은 애플리케이션에서 프로그램의 병목현상으로 나타나는 일부만 memoize해야한다. 필요하지 않은 코드를 memoization하면 더 좋아지는 대신 더 나빠질 수 있다.</p>
<p>예를 들어:</p>
<ol>
<li>memoization의 CPU/memory 비용은, 전에 언급한 것처럼, 더 많은 메모리 공간을 소비하면서 결과를 캐시함으로써 작동한다. 게다가, memoizationd은 캐시된 결과를 리턴할지 하지 않을지 결정하기 위해 props와 dependencies 비교를 필요로 하며 이는 추가적인처리가 필요하다,</li>
<li>React에서 Memoization은 더 많은 dependencies를 관리하기 위해 기존 코드에 추가 복잡성을 더한다. 모든 추가 복잡성은 고려해야할 overhead이다.</li>
<li>memoization의 결과로 캐시된 값들이 예상하지 못한 상황에서 다시 계산되지 않는다는 가정을 해서는 안된다.React는 필요한 경우에 캐시된 값들을 무효화할 수 있다. 따라서 bug를 피하기 위해서는 성능최적화를 위해서만 memoization을 사용해야한다.</li>
</ol>
<p>useMemo에 관한 React 문서에 따르면:</p>
<p>&quot;React에서 memoization을 의미적인 보장으로 사용하는 것이 아니라 성능 최적화로만 의존해야 한다.&quot;</p>
<p>또한 React.memo 문서에서 비슷한 문장을 찾을 수 있다:</p>
<p>&quot;이 method는 성능최적화를 위해서만 존재한다. 렌더링을 &quot;막기&quot; 위해 의존하지 말아야 한다. 이는 버그를 일으킬 수 있다.&quot;</p>
<p>원칙적으로, 필요할 때만 최적화해라. 그것은 무슨 의미인가?</p>
<h2 id="when-to-memoize-in-react">When to Memoize in React</h2>
<p>memoize하지 말아야하는 경우를 알았으니, 사용할 경우와 어떻게 도움이 되는지에 대해 이야기해보겠다.</p>
<p>우선, 컴포넌트를 실행함으로써 시작하고 어떤 성능 이슈가 없는지 확인한다. React Devtools는 성능 이슈를 파악하는데 도움을 주는 유용한 도구이다.</p>
<p>만약 어떤 성능 이슈를 마주친다면, 다음 섹션에서 언급되는 memoization 기술을 사용하여 문제있는 컴포넌트를 최적화할 지 고려할 수 있다.</p>
<p>최적화가 완료된 후에는, 전과 후의 결과를 비교하여 성능향상이 있었는지 확인한다.</p>
<p>만약 성능향상이 미미하다면, 버그와 추가적인 overhead를 피하기 위해 memoization의 변경사항을 버리는 것이 좋을 것이다.</p>
<h2 id="how-to-implement-memoization-in-react">How to Implement Memoization in React</h2>
<h3 id="reactmemo">React.memo</h3>
<h3 id="usememo">useMemo</h3>
<h3 id="usecallback">useCallback</h3>
<h2 id="how-to-memoize-class-based-components">How to Memoize Class-based Components</h2>
<h3 id="reactpurecomponent">React.PureComponent</h3>
<h2 id="conclusion">Conclusion</h2>
<p>React의 Memoization은 강력한 도구이지만, trade-off가 따른기 때문에 React의 모든 것을 초기 최적화하는 것은 피하는 것을 권장한다.</p>
<p>React는 성능이 우수한 프레임워크이자 이미 여러 최적화를 진행 중이다. 따라서 특별히 필요하지 않으면, 추가적인 최적화는 필요하지 않을 수 있다.</p>
<p>이제 React에서 memoization을 사용하는 여러가지법을 알아봤고, 그것을 사용할때와 피해야할 때를 이해했을 것이다.</p>
<hr>
<p>원문 <a href="https://www.bitovi.com/blog/how-and-when-to-memoize-your-react-application">https://www.bitovi.com/blog/how-and-when-to-memoize-your-react-application</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[객체 지향 프로그래밍]]></title>
            <link>https://velog.io/@a-honey/%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D</link>
            <guid>https://velog.io/@a-honey/%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D</guid>
            <pubDate>Tue, 09 Jan 2024 09:38:04 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/a-honey/post/6644ca11-a9ce-4747-bb2e-31ea59603c6f/image.png" alt=""></p>
<h1 id="객체-지향-프로그래밍">객체 지향 프로그래밍</h1>
<h2 id="객체-지향-프로그래밍이란">객체 지향 프로그래밍이란?</h2>
<p><img src="https://velog.velcdn.com/images/a-honey/post/5d0d6aa5-362c-4c93-a917-3b7966dcdd20/image.png" alt=""></p>
<p>초기 프로그래밍은 단순히 코드의 순서대로 실행되는 순차적 프로그래밍 방식이었습니다. 반복문이 존재했지만 한계가 있었고 실행 순서를 강제로 변경하는 등의 방법으로 코드의 흐름을 제어하기 힘들었습니다. 이후 일정하게 반복되는 코드를 함수로 만들어 사용하는 절차적 프로그래밍 패러다임이 등장했습니다. 하지만 전역 변수를 사용했기 때문에 프로그램이 커질수록 변수명 관리에 어려움을 겪었습니다. 비슷한 변수명을 묶어서 관리하면서 서로 연관있는 데이터들을 모아두기 시작했고, 이는 해당 변수에 접근할 수 있는 구조체를 만들어냈습니다. 이 구조체를 바탕으로 데이터를 중심으로 코딩을 하게 되면 프로그래밍이 커져도 일관성을 유지하기 편하며, 함수까지 더해지면서 class가 탄생하게 되었습니다. 즉, 데이터와 처리방법을 분리해서 개발하던 절차식 프로그래밍과 달리, 데이터가 처리방식을 하나의 모듈로서 관리를 하게 되면서 작은 프로그래밍들이 독립적으로 돌아가는 형태가 되었고, 이를 조립하고 결합하는 방식의 개발 방법론이 등장하게되었습니다. 이과정에서 구조체와 함수를 합쳐서 선언하는 것을 class라고 부르기로 했고, class 내의 값과 동작을 실체로 보며 object로 칭했습니다.
결국, 프로그램을 객체로 바라보는 관점으로 프로그래밍하는 객체지향 프로그래밍Object-Oriented Programming(OOP)방법이 지금까지도 유용한 방법론으로 소개되고 있습니다.</p>
<h3 id="캡슐화">캡슐화</h3>
<p>객체를 독립적으로 분리하면서, 객체의 모든 데이터에 접근할 필요성이 사라졌고, 내부의 데이터는 내부에서 알아서 조작할 수 있도록 하고 외부에서는 필요한 내용만 만들어 두는 편이 안정성과 사용성 측면에서 효율적이었습니다. 따라서 외부로 노출해야 하는 값과 내부에서만 사용하는 값을 구분하는 기능을 추가하였습니다. 이것을 데이터를 보호해주는 캡슐을 통해 내부 데이터에 바로 접근하지 못하게 하고 필요한 메소드만 열어두는 캡슐화라고 부릅니다.</p>
<h4 id="-프리픽스"># 프리픽스</h4>
<pre><code>class Human {
  #age = 10;

  getAge() {  // getter
    return this.#age; 
  }
}

const person = new Human();

console.log(person.#age); // Error TS18013: Property &#39;#age&#39; is not accessible outside class &#39;Human&#39; because it has a private identifier.
console.log(person.getAge()); // 10</code></pre><h3 id="상속">상속</h3>
<p>재사용성을 위해 객체를 사용하는 과정에서, 여러 개의 변수와 여러 개의 함수가 섞여 있다보니 일부 로직은 같고, 일부는 달라져야하는 상황이 발생했습니다. 따라서 객체의 일부분만 재사용하는 방법이 필요해졌고, 상속을 통해 객체에서 공통된 부분만 따로 만들어서 그 코드를 같이 상속받아서 활용을 하고 나머지 달라지는 것들만 각자 코드를 작성하는 방식을 만들게 되었습니다.</p>
<h3 id="추상화">추상화</h3>
<p>상속을 통해 객체를 분리하고 연결하는 과정에서, 공통적인 부분을 모아서 상위의 개념으로 새롭게 이름붙이는 추상화가 필요해졌습니다. 예를들어, 강아지와 고양이 등의 클래스의 공통부분을 동물이라는 클래스로 만들어 상속할 수 있습니다.</p>
<h3 id="다형성">다형성</h3>
<pre><code>let zergling1 = new Zergling()
let zergling2 = new Zergling()
let hydralisk1 = new Hydralisk()
let hydralisk2 = new Hydralisk()
let mutalisk = new Mutalisk()
let overload = new Overload()

let units = [zergling1, zergling2, hydralisk1, hydralisk2, mutalisk, overload]

// 모두 같은 이름의 moveTo 메소드를 호출하지만 각자의 방식대로 동작한다.
units.forEach(unit =&gt; unit.moveTo(300, 400))</code></pre><p>상속과 추상화를 사용해서 추상화된 유닛이라는 타입이 하위 타입인 여러가지 타입으로 참조할 수 있는 다형성을 활용할 수도 있습니다. 예를들어, 같은 Unit의 moveTo 메소드를 사요하지만, 각자 정의된 방식이 있기 때문에 각자의 방식대로 동작하며 다형성을 통해 객체의 일부분만 재사용할 수 있습니다. </p>
<h3 id="정리">정리</h3>
<p><img src="https://velog.velcdn.com/images/a-honey/post/32c67182-48e5-44b6-92da-b4be8dd41752/image.png" alt=""></p>
<p>객체지향 프로그래밍은 프로그램이 커지면서 생기는 문제점을 해결하기 위해 나온 하나의 관점이자 방법론입니다. 기존의 방식에서 변수를 하나씩 관리하다 보니 변수명 관리에 어려움이 있었고, 이러한 문제를 해결하기 위해 구조체라는 타입을 만들어 데이터를 중심으로 관리를 하였습니다. 또한 추가로 데이터와 함수를 한데 묶어서 관리하는 관점이 생겨났고, 하나의 큰 프로그래밍을 작은 문제를 해결하는 독립적인 단위로 만들 수 있었습니다. 이렇게 작은 단위로 관리를 하게 되니 개발과 유지보수가 간편하게 된다는 장점이 있었으며 현재 널리 사용되고 있는 방법론입니다.</p>
<h2 id="자바스크립트의-클래스">자바스크립트의 클래스</h2>
<p>사실 자바스크립트의 탄생 시점에서는 객체지향 프로그래밍의 필요성이 널리 알려지지 않은 상태였기 때문에, 자바스크립트에 클래스는 없었고, 생성자 함수를 통해 클래스를 흉내내는 방식이었습니다. 그러다 ES6에 들어 클래스가 추가되었습니다.</p>
<p>프로그래밍의 독립적인 단위인 객체를 설계하고 찍어낼 수 있는 틀과 같은 구조를 클래스라고 하고, 클래스에 만들어진 인스턴스를 객체라고 하여 프로그래밍의 모든 것들을 객체로 간주하여 객체간의 상호작용을 중심으로 생각하고 설계하는 프로그래밍 개념이 바로 Object-Oriented Programming입니다.</p>
<h3 id="constructor와-메서드">constructor와 메서드</h3>
<h4 id="constructor">constructor</h4>
<p>생성자는 클래스로부터 객체를 생성할 때 호출되는 특별한 메서드입니다. 클래스 내부에서 constructor 키워드를 사용하여 정의하며, 객체의 초기 상태를 설정하거나 인스턴스 변수를 초기화하는 등의 작업을 수행하기 때문에 클래스 내에 단 하나만 존재할 수 있습니다.</p>
<h4 id="메서드">메서드</h4>
<p>메서드는 클래스에 속한 함수로, 객체가 수행할 수 있는 동작이나 기능을 정의합니다. 클래스 내에 일반적인 함수처럼 정의되며 인스턴스가 생성될 때마다 메서드는 해당 인스턴스에 대해 별도로 생성되지 않고 클래스의 프로토타입에 할당됩니다.</p>
<h3 id="클래스의-인스턴스-생성-과정">클래스의 인스턴스 생성 과정</h3>
<pre><code>class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  greet() {
    console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
  }
}

// 객체 생성
const person1 = new Person(&#39;John&#39;, 30);

// 메서드 호출
person1.greet(); // Hello, my name is John and I am 30 years old.</code></pre><ol>
<li>런타임이 실행되기 전에, 자바스크립트 엔진의 평가과정에서 Person 클래스 선언이 호이스팅되지만 초기화되지 않은 상태로 TDZ에 들어간다.</li>
<li>Person 생성자 함수가 호출되면서 빈객체가 생성된다.</li>
<li>생성자 함수의 constructor가 호출되면서 빈객체에 해당 인스턴스의 name 프로퍼티에 인수로 전달받은 name을 추가된다.</li>
<li>해당 인스턴스의 age 프로퍼티에 인수로 전달받은 age가 추가된다.</li>
<li>person1의 greet이 호출되면, 스코프 체인에서 person1 식별자를 검색한 후 프로토타입 체인에서 greet 메서드를 검색하며 person1의 프로토타입인 Person.prototype에서 상속받은 greet 메서드를 호출한다.</li>
</ol>
<h2 id="카드-뒤집기-게임-살펴보기">카드 뒤집기 게임 살펴보기</h2>
<p>대부분의 분들이 카드 클래스를 만들어 카드 인스턴스를 생성하고 모듈화하는 것을 첫 접근으로 잘 시작해주는 것을 확인할 수 있었습니다. 여기에 객체지향 프로그래밍 관점을 좀 더 추가해보겠습니다.</p>
<p>단, 당연히 설계자의 의도에 따라 코드는 달라지기 때문에, 정답 코드가 아님을 명시합니다.</p>
<h3 id="단일-책임-원칙">단일 책임 원칙</h3>
<p><img src="https://velog.velcdn.com/images/a-honey/post/c21daec5-1df5-43ea-9863-1fd5461a54ea/image.png" alt=""></p>
<p>객체지향프로그래밍의 관점에서 보면, 각각의 클래스는 독립되지만 서로 연결되어 하나의 프로그래밍을 구성하게 됩니다. 즉, 카드 요소를 생성해서 이벤트핸들러를 추가하는 작업은 물론, 카드 인스턴스를 배열에 보관하고, 이 게임을 관리하는 것까지 객체로 만들어서 서로 상호작용하여 객체지향적인 프로그래밍으로 만들 수 있을 것입니다.</p>
<p>이때, 각각의 클래스를 어떻게 분리하여 어떤 클래스가 어떤 데이터를 갖고 있고 어떤 동작을 수행할지에 대한 고민을 시작해야합니다. 즉 각각의 클래스에 하나의 책임을 부여하면 클래스 간의 의존성을 줄이고 독립적으로 변경 및 확장하며 유지보수가 쉬워집니다. 이것을 단일책임원칙이라고 부릅니다.</p>
<h4 id="게임을-관리하는-클래스">게임을 관리하는 클래스</h4>
<p>1단계에서는 게임을 시작하는 기능만 추가되겠지만 추후 확장을 고려하여 게임을 관리하는 클래스를 만들어 게임의 흐름을 제어하는 등의 게임의 주요 로직을 담당하는 클래스입니다.</p>
<h4 id="카드를-보관하는-클래스">카드를 보관하는 클래스</h4>
<p>전체 카드를 생성하고 관리하며, 카드를 섞는 등의 카드 관련 작업을 수행할 수 있으며, 게임을 관리하는 클래스에서 게임을 시작했을 때 카드를 섞는 메서드가 동작하도록 연결시킬 수 있습니다. 즉, 이 클래스는 전체 카드를 관리하는 작업에만 집중하고 게임의 진행과는 독립적으로 진행되어야합니다.</p>
<h4 id="카드-클래스">카드 클래스</h4>
<p>개별 카드의 속성을 나타내고, 카드의 상태를 추적하는 등 개별 카드에 특화된 작업을 수행하는 클래스입니다. 카드의 스타일 속성을 지정하거나, 이벤트 핸들러를 추가하는 등의 작업을 수행할 수 있습니다.</p>
<h3 id="상속과-추상화">상속과 추상화</h3>
<p><img src="https://velog.velcdn.com/images/a-honey/post/73a42420-51e7-4cd5-a834-518f79f61efe/image.png" alt=""></p>
<p>또한 추상화와 상속을 사용하여 카드 클래스를 더 구체화할 수 있습니다. WinningCard와 LossingCard로 분리하고, 추상화된 Card 클래스를 만들어 공통된 카드 속성을 추가한 후 WinningCard와 LossingCard에 서로 다른 역할을 하는 메서드를 오버라이딩할 수 있습니다.</p>
<pre><code>class AbstractCard {
  constructor(isWinningCard) {
    this.node = this.createCardElement();
    this.isWinningCard = isWinningCard;
    this.handleCardClick();
  }

  createCardElement() {
    const button = document.createElement(&quot;button&quot;);
    button.style.height = &quot;200px&quot;;
    button.style.width = &quot;100px&quot;;
    button.innerText = &quot;두근두근&quot;;
    return button;
  }

  handleCardClick() {
    this.node.addEventListener(&quot;click&quot;, () =&gt; {
      this.selectCard();
    });
  }

  // 추상 메서드로 하위 클래스에서 구현하여 오버라이딩되도록 함
  selectCard() {
    throw new Error(&#39;Abstract method. Subclasses should implement this.&#39;);
  }
}

class WinningCard extends AbstractCard {
  selectCard() {
    const contents = document.querySelector(&quot;#contents&quot;);
    contents.innerText = &quot;당첨입니다 :D&quot;;
  }
}

class LosingCard extends AbstractCard {
  selectCard() {
    const contents = document.querySelector(&quot;#contents&quot;);
    contents.innerText = &quot;꽝입니다!&quot;;
  }
}
</code></pre><h3 id="의존성-주입">의존성 주입</h3>
<p><img src="https://velog.velcdn.com/images/a-honey/post/97bb4e5b-0f34-47b3-b72f-fed58185abf3/image.png" alt=""></p>
<p>카드를 보관하는 클래스 내에서 카드 인스턴스를 생성하여 카드 인스턴스들을 관리하는 방식은 클래스 간의 의존성이 높아 결합도가 높습니다. 카드를 보관하는 클래스가 카드 클래스에 의존하고 있다고 표현하는데, 이를 외부에서 주입받도록 구현하여 클래스 간의 결합도를 낮추고, 코드의 유연성과 재사용성을 높일 수 있습니다.</p>
<pre><code>class GameManager {
  constructor(cardsManager) {
    this.cardsManager = cardsManager;
  }

  startGame() {
    this.cardsManager.shuffleCards();
    // 게임 시작 로직 
  }
}

class CardsManager {
  constructor() {
    this.cards = [];
    this.createCards();
  }

  createCards() {
    // 전체 카드 생성 및 초기화...
  }

  shuffleCards() {
    // 카드 섞기 로직...
  }

  // 다른 카드 관련 메서드들...
}

class Card {
  // 카드의 속성과 메서드...
}

const cardsManager = new CardsManager();
const gameManager = new GameManager(cardsManager);

gameManager.startGame();</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[아토믹 디자인 패턴]]></title>
            <link>https://velog.io/@a-honey/%EC%95%84%ED%86%A0%EB%AF%B9-%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@a-honey/%EC%95%84%ED%86%A0%EB%AF%B9-%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Sun, 07 Jan 2024 13:07:12 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/a-honey/post/39bc91f4-415e-407c-9c8b-f101a747e714/image.png" alt=""></p>
<h1 id="아토믹-디자인-패턴">아토믹 디자인 패턴</h1>
<p>소스코드를 아주 작은 컴포넌트 단위로 쪼개는 방법
컴포넌트 중복을 최소화하기 위해서 사용됨</p>
<h3 id="atom">Atom</h3>
<p>가장 작은 단위의 컴포넌트
디자인과 기능의 최소 단위
원자는 어떠한 context가 주어지든지 이에 해당하는 컴포넌트가 생성될 수 있어야 한다.
따라서, 다양한 state를 가지고 있어야하며 추상적이지만 최대한 포용할 수 있게 설계 되어야 한다. 상태, 색상, 폰트, 애니메이션과 같은 추상적인 요소 포함
Button, Lavel, Text, CheckBox, Icon 등</p>
<h3 id="molecule">Molecule</h3>
<p>2개 이상의 원자를 엮어 조금 복잡한 단위의 분자가 생성됨
하나의 단위로 함께 동작하는 UI 컴포넌트들의 단순한 그룹
분자는 분자만의 프로퍼티를 가지고 있을 수 있고 이를 활용해 원자에 기능을 만들어 줄 수 있음
분자가 원자의 위치값을 지정하기도 함
InputForm, Navigation, Card 등</p>
<h3 id="oraganisms">Oraganisms</h3>
<p>원자와 분자를 조합하여 사용하는 컴포넌트
인터페이스가 어떻게 보이는지 시작하는 단계
유기체는 분자를 엮어 만들어서 생성되고 때로는 분자가 되지 않은 원자가 엮이기도 한다
유기체가 완성되면 컴포넌트가 최종 모습을 가지게 된다
하지만 여전히 contents에 따라 최대한 재사용성 높게 개발하는 것이 중요하다.
유기체는 분자와 원자의 위치값을 조정한다
예를 들어, InputForm을 Header 내에 포함하거나 Card 여러 개를 그리드 형태로 관리하는 것</p>
<h3 id="template">Template</h3>
<p>여러 유기체가 모여있는 단위
템플릿은 만들어진 유기체와 컴포넌트의 positions, placements를 정해주는 역할
단, 템플릿에서는 Styling이나 Color는 들어가지 않는다
템플릿의 역할은 페이지의 그리드를 정해주는 역할뿐임</p>
<h3 id="page">Page</h3>
<p>템플릿을 이용해서 각 그리드에 컴포넌트를 그려서 디스플레이</p>
<h2 id="적용해보기">적용해보기</h2>
<h3 id="atom-1">Atom</h3>
<pre><code>import React from &#39;react&#39;;
import Image from &#39;next/image&#39;;
import KakaoLogo from &#39;../../../assets/kakao_login.png&#39;;

interface KaKaoLoginButtonProps {
  onClick: React.MouseEventHandler&lt;HTMLDivElement&gt;;
}

const KaKaoLoginButton: React.FC&lt;KaKaoLoginButtonProps&gt; = ({ onClick }) =&gt; {
  return (
    &lt;div onClick={onClick}&gt;
      &lt;Image src={KakaoLogo} alt=&quot;카카오로그인&quot; width={200} height={40} /&gt;
    &lt;/div&gt;
  );
};

export default KaKaoLoginButton;
</code></pre><h3 id="organism">Organism</h3>
<pre><code>import React, { useEffect } from &#39;react&#39;;
import KaKaoLoginButton from &#39;../../atoms/SocialLoginButton/KaKaoLoginButton&#39;;

const kakaoOauthURL = `https://kauth.kakao.com/oauth/authorize?client_id=${process.env.NEXT_PUBLIC_REACT_APP_KAKAO_API_KEY}&amp;redirect_uri=${process.env.NEXT_PUBLIC_KAKAO_REDIRECT_URI}&amp;response_type=code`;

const SocialLogin: React.FC = () =&gt; {
  const requestKakao = (): void =&gt; {
    window.location.href = kakaoOauthURL;
  };

  useEffect(() =&gt; {
    const urlParams = new URLSearchParams(window.location.search);
    const code = urlParams.get(&#39;code&#39;);

    if (code !== null &amp;&amp; code !== undefined) {
      console.log(&#39;받아온 코드:&#39;, code);
    }
  }, []);

  return &lt;KaKaoLoginButton onClick={requestKakao} /&gt;;
};

export default SocialLogin;
</code></pre><h2 id="나눠보기">나눠보기</h2>
<p><img src="https://velog.velcdn.com/images/a-honey/post/65ae51b1-cfdc-4094-92eb-aa1bf20c9491/image.png" alt=""></p>
<p>여전한 보노보노 색상 감각이지만 대충 이런 느낌인거같다..!</p>
<h2 id="구조보기">구조보기</h2>
<pre><code>src/
|-- components/
|   |-- atoms/
|   |   |-- AtomComponent1/
|   |   |   |-- index.js
|   |   |   |-- AtomComponent1.js
|   |   |   |-- AtomComponent1.css
|   |   |-- AtomComponent2/
|   |   |   |-- index.js
|   |   |   |-- AtomComponent2.js
|   |   |   |-- AtomComponent2.css
|   |-- molecules/
|   |   |-- MoleculeComponent1/
|   |   |   |-- index.js
|   |   |   |-- MoleculeComponent1.js
|   |   |   |-- MoleculeComponent1.css
|   |   |-- MoleculeComponent2/
|   |   |   |-- index.js
|   |   |   |-- MoleculeComponent2.js
|   |   |   |-- MoleculeComponent2.css
|   |-- organisms/
|   |   |-- OrganismComponent1/
|   |   |   |-- index.js
|   |   |   |-- OrganismComponent1.js
|   |   |   |-- OrganismComponent1.css
|   |   |-- OrganismComponent2/
|   |   |   |-- index.js
|   |   |   |-- OrganismComponent2.js
|   |   |   |-- OrganismComponent2.css</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[프론트엔드의 테스트]]></title>
            <link>https://velog.io/@a-honey/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C%EC%9D%98-%ED%85%8C%EC%8A%A4%ED%8A%B8</link>
            <guid>https://velog.io/@a-honey/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C%EC%9D%98-%ED%85%8C%EC%8A%A4%ED%8A%B8</guid>
            <pubDate>Tue, 26 Dec 2023 05:38:41 GMT</pubDate>
            <description><![CDATA[<p>테스트는 에러를 예상하고 의도하지 않은 동작을 방지하여 사용자 경험을 향상시키며, 코드의 안정성을 유지하기 위해 필요함
CI/CD 환경에서 자동화된 테스트 스크립트를 실행하여 코드 변경 사항이 통합되고 배포되기 전에 안정성을 검증</p>
<h2 id="테스트의-종류">테스트의 종류</h2>
<h3 id="유닛-테스트unit-test">유닛 테스트Unit Test</h3>
<p>코드의 개별 컴포넌트나 함수를 테스트하는 것
주로 모듈, 함수 혹은 컴포넌트 단위로 진행하며, 해당 단위가 의도한대로 동작하는지 검증</p>
<h3 id="통합테스트integration-test">통합테스트Integration Test</h3>
<p>여러 개의 유닛이나 컴포넌트를 함께 테스트하여 이들 간의 상호작용과 통합이 올바르게 이뤄지는지 확인. 시스템 전체의 안정성을 보장하는 데 기여.</p>
<h3 id="e2e-테스트end-to-end-test">E2E 테스트(End-to-End Test)</h3>
<p>사용자 시나리오를 흉내내어 전체 애플리케이션의 동작을 테스트 
실제 사용자가 마주할 상황을 최대한 모방하여 테스트하므로, 애플리케이션의 품질과 사용자 경험을 평가하는데 유용</p>
<h3 id="스냅샷-테스트">스냅샷 테스트</h3>
<p>UI 컴포넌트의 렌더링 결과를 스냅샷으로 캡처하고, 변경 사항이 있는지 확인
UI 변경을 쉽게 감지하고 예상하지 못한 레이아웃 변화를 방지함</p>
<h3 id="회귀-테스트regression-test">회귀 테스트Regression Test</h3>
<p>새로운 코드 변경이나 기능 추가 후에 시스템이 여전히 예전에 작동하던 기능들을 망가뜨리지 않았는지 확인하는 테스트
변경 사항이나 새로운 기능 추가로 인해 발생할 수 있는 예기치 못한 부작용을 방지하기 위해 필요</p>
<h3 id="성능-테스트performance-test">성능 테스트Performance Test</h3>
<p>시스템의 성능과 안정성을 확인하는데 중점
부하 테스트, 성능 테스트, 신뢰성 테스트 등
대량의 데이터나 동시 사용자가 많은 상황에서 시스템이 어떻게 동작하는지를 평가</p>
<h3 id="안정성-테스트security-test">안정성 테스트Security Test</h3>
<p>시스템의 보안에 중점을 둔 테스트, 잠재적인 보안 취약점을 찾고 해결함으로써 시스템을 보호
SQL 인젝션, 크로스 사이트 스크립팅(XSS), 크로스 사이트 요청 위조(CSRF) 등을 확인하는데 사용</p>
<h3 id="사용성-테스트usability-test">사용성 테스트Usability Test</h3>
<p>사용자가 제품을 쉽게 이해하고 조작할 수 있는지를 평가
인터페이스, 플로우, 접근성 등을 고려하여 사용자 경험을 향상시키는데 도움</p>
<h3 id="ab-테스트">A/B 테스트</h3>
<p>두 가지 또는 그 이상의 변형을 사용하여 어떤 버전이 사용자에게 더 효과적인지를 비교하는 테스트
주로 마케팅 캠페인이나 사용자 경험 개선을 위해 사용됨</p>
<h2 id="테스트-도구">테스트 도구</h2>
<h3 id="jest">Jest</h3>
<p>Facebook에서 만든 JavaScript 테스트 프레임워크
주로 유닛 테스트에 사용
Snapshot 테스트, 비동기 코드 테스트, mock를 지원하여 간편하게 사용 가능</p>
<h3 id="cypress">Cypress</h3>
<p>E2E 테스트에 특화된 도구
실제 브라우저에서 애플리케이션을 실행하며 테스트
사용자 인터랙션을 테스트하고 디버깅하는 데 용이하며, 가독성 좋은 테스트 코드를 작성 가능</p>
<h3 id="storybook">Storybook</h3>
<p>UI 컴포넌트의 렌더링 결과를 보여줌</p>
<h3 id="testing-library">Testing Library</h3>
<p>React,Angular, Vue 등 다양한 프레임워크에 대한 테스트 도구를 제공하는 생태계
사용자 중심의 테스트 방법론을 채택하여 실제 사용자의 관점에서 테스트를 수행</p>
<h2 id="tdd-test-driven-development">TDD, Test-Driven Development</h2>
<p>개발자가 코드를 작성하기 전에 테스트를 먼저 작성하는 방법론
코드의 요구 사항을 명확히 이해하고, 안정성 있는 코드를 유지할 수 있음</p>
<ol>
<li>구현하려는 동작을 설명하는 테스트 코드 작성</li>
<li>테스트 실행, 아직 코드를 작성하지 않았으므로 실패함</li>
<li>테스트를 통과하기 위한 코드를 작성</li>
<li>테스트 실행하여 코드를 확인</li>
<li>테스트를 통과하면 코드를 리팩터링</li>
</ol>
<ul>
<li>테스트를 먼저 작성함으로써, 개발자는 구현하려는 동작에 대해 먼저 생각하게 되어 특정한 요구사항에 맞는 코드를 작성할 수 있음</li>
<li>코드의 유지보수성을 향상시키고, 시간이 지나도 프로젝트의 유지 관리가 쉬워짐</li>
<li>코드 수정 시 미처 생각지 못한 부분에 대한 문제점을 미리 파악 가능</li>
<li>애플리케이션엥 대한 신뢰성이 높아짐</li>
<li>테스트 코드는 동작 방식을 설명하니, 테스트 파일 자체를 문서로 활용 가능</li>
</ul>
<hr>
<h1 id="그냥-사용만-해보기">그냥 사용만 해보기</h1>
<h3 id="cypress-1">cypress</h3>
<pre><code>const { defineConfig } = require(&#39;cypress&#39;);

module.exports = defineConfig({
  screenshotOnRunFailure: false,
  video: false,
  blockHosts: [&#39;cdn.jsdelivr.net&#39;],
  e2e: {
    setupNodeEvents(on, config) {},
    specPattern: &#39;test/*.spec.js&#39;,
    supportFile: false,
  },
});</code></pre><pre><code>describe(&quot;카드 컴포넌트 테스트&quot;, () =&gt; {
  it(&quot;카드 컴포넌트가 렌더링된다.&quot;, () =&gt; {
    cy.visit(&quot;http://localhost:3000&quot;);

    cy.get(&quot;.card&quot;).should(&quot;be.visible&quot;);
  });

  it(&quot;게임이 다시 렌더링된다.&quot;, () =&gt; {
    cy.visit(&quot;http://localhost:3000&quot;);

    cy.contains(&quot;button&quot;, &quot;카드입니다&quot;).click();

    cy.contains(&quot;button&quot;, &quot;다시 하기&quot;).click();

    cy.get(&quot;.card&quot;).should(&quot;be.visible&quot;);
  });
});</code></pre><h3 id="jest-1">jest</h3>
<pre><code>const { Card, Cards } = require(&quot;../src/index&quot;);

describe(&quot;Card&quot;, () =&gt; {
  test(&quot;카드 결과보기 테스트&quot;, () =&gt; {
    const resetGameMock = jest.fn();
    const card = new Card(true, 0, resetGameMock, null);

    card.selectCard();

    expect(resetGameMock).toHaveBeenCalledTimes(1);
  });
});

describe(&quot;Cards&quot;, () =&gt; {
  test(&quot;카드 생성하고 리셋하기 테스트&quot;, () =&gt; {
    const createCardsMock = jest.spyOn(Cards.prototype, &quot;createCards&quot;);
    const renderCardsMock = jest.spyOn(Cards.prototype, &quot;renderCards&quot;);

    const cards = new Cards(5);
    cards.resetGame();

    expect(createCardsMock).toHaveBeenCalledTimes(1);
    expect(renderCardsMock).toHaveBeenCalledTimes(1);
  });
});</code></pre><p>jest는 jsdom 라이브러리를 따로 또 설치한다는 점에서 그냥 cypress가 e2e테스트 무난한듯,, 사실 아직 어디서 테스트를 해야하는지 감이 잘 안잡히다보니 그냥 이런 도구가 있군,,하고 지나가게 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[아키텍처 패턴]]></title>
            <link>https://velog.io/@a-honey/%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@a-honey/%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Sun, 24 Dec 2023 11:47:15 GMT</pubDate>
            <description><![CDATA[<h2 id="mvc-mvvm-mvp">MVC, MVVM, MVP</h2>
<h3 id="mvcmodel-view-controller">MVC(Model-View-Controller)</h3>
<ul>
<li><p>Model: 데이터와 비즈니스 로직을 관리</p>
</li>
<li><p>View: 사용자에게 표시되는 인터페이스를 담당</p>
</li>
<li><p>Controller: 모델과 뷰 간의 상호 작용을 조정하고 사용자 입력을 처리
MVC는 각 구성 요소가 서로 독립적이어야 하며, 모델과 뷰 간에 직접적인 연결이 없어야함
사용자 입력에 대한 응답으로 컨트롤러가 동작하며, 모델이 엄데이트되면 뷰가 갱신됨
일반적으로 html, css를 View, 컨트롤러를 js, 백에서 받아온 데이터를 Model로 생각</p>
<pre><code>// 데이터와 비즈니스 로직을 담당하는 View
class UserModel {
constructor(name) {
  this.name = name;
}

updateName(newName) {
  this.name = newName;
}
}</code></pre><pre><code>// 사용자에게 표시되는 인터페이스를 담당하는 View
class UserView {
render(user) {
  console.log(`사용자 이름: ${user.name}`);
}
}</code></pre><pre><code>class UserController {
constructor(model, view) {
  this.model = model;
  this.view = view;
}

handleNameChange(newName) {
  this.model.updateName(newName);
  this.view.render(this.model);
}
}</code></pre></li>
</ul>
<h3 id="mvvmmodel-view-viewmodel">MVVM(Model-View-ViewModel)</h3>
<ul>
<li>Model: 데이터와 비즈니스 로직을 관리</li>
<li>View: 사용자에게 표시되는 인터페이스를 담당</li>
<li>ViewModel: 뷰를 표현하기 위한 뷰의 상태와 행동을 캡슐화하고, 뷰와 모델간의 통신을 중계
데이터 바인딩을 사용하여 뷰모델과 뷰 간에 간단한 통신을 가능하게 함
뷰모델은 주로 사용자 인터페이스의 상태를 유지하고 뷰에서 모델로의 변환 작업을 처리</li>
</ul>
<pre><code>// 뷰를 표현하기 위한 상태와 행동을 캡슐화하는 View Model
class UserViewModel {
  constructor(model) {
    this.model = model;
  }

  get name() {
    return this.model.name;
  }

  set name(newName) {
    this.model.updateName(newName);
  }
}</code></pre><h3 id="mvpmodel-view-presenter">MVP(Model-View-Presenter)</h3>
<ul>
<li><p>Model: 데이터와 비즈니스 로직을 관리</p>
</li>
<li><p>View: 사용자에게 표시되는 인터페이스를 담당</p>
</li>
<li><p>Presenter: 뷰와 모델 간의 중간 매개체. 사용자 입력을 처리하고 모델에서 데이터를 가져와 뷰에 업데이트하는 역할
뷰와 모델 간의 직접적인 연결을 피하면서 프레젠터를 통해 중재하도록 설계. 사용자 입력에 대한 로직은 프레젠터에서 처리되고, 뷰는 단순히 사용자 인터페이스를 표시하는 역할만 수행</p>
<pre><code>// 뷰와 모델 간의 중간 매개체로 동작하며 사용자 입력을 처리하고 모델에서 데이터를 가져와 뷰에 업데이트하는 Presenter
class UserPresenter {
constructor(model, view) {
  this.model = model;
  this.view = view;
}

handleNameChange(newName) {
  this.model.updateName(newName);
  this.view.render(this.model.name);
}
}</code></pre></li>
</ul>
<h2 id="controller-view-model-presenter">Controller, View Model, Presenter</h2>
<h3 id="controller">Controller</h3>
<p>사용자 입력을 감지하고 모델과 뷰를 업데이트하는 역할
사용자 입력을 처리하고 모델과 뷰 간의 상호 작용을 조정하는 것이 주된 목적</p>
<ul>
<li>사용자의 액션을 감지하고 이에 대한 응답을 결정</li>
<li>모델의 업데이트를 담당하고, 모델이 변경되었을 때 뷰를 업데이트</li>
<li>일반적으로 컨트롤러와 뷰는 직접적으로 통신</li>
</ul>
<h3 id="viewmodel">ViewModel</h3>
<p>뷰에 필요한 데이터를 제공하고, 뷰와 모델 사이의 중간 매개체 역할
뷰를 표현하기 위한 상태와 행동을 캡슐화하여 뷰와 모델 간의 결합을 완화하는 것이 주된 목적</p>
<ul>
<li>View를 표현하기 위한 데이터를 제공</li>
<li>사용자 인터페이스의 상태와 동작을 캡슐화하고, 뷰에 바인딩되어있는 데이터를 조작</li>
<li>주로 양방향 데이터 바인딩을 통해 뷰와 자동으로 동기화</li>
</ul>
<h3 id="presenter">Presenter</h3>
<p>사용자의 입력을 처리하고, 모델과 뷰 간의 중개자 역할
뷰와 모델 간의 결합을 느슨하게 유지하면서 사용자 입력을 처리하고 뷰를 업데이트하는 것이 주된 목적</p>
<ul>
<li>사용자 입력을 처리하고, 이에 대한 응답으로 모델을 업데이트하며 뷰를 업데이트</li>
<li>뷰와 모델 간의 직접적인 의존성을 피하기 위해 중간 역할을 수행</li>
<li>MVP 패턴에서는 일반적으로 뷰와 모델 사이의 인터페이스 역할</li>
</ul>
<h2 id="다시보기">다시보기</h2>
<h3 id="mvc">MVC</h3>
<p><img src="https://velog.velcdn.com/images/a-honey/post/c50d5136-a6e6-4bf7-9919-ef3836a944a5/image.png" alt=""></p>
<ol>
<li>사용자의 action이 Controller로 들어온다.</li>
<li>Controller는 사용자의 action을 확인하고, Model을 업데이트</li>
<li>Controller는 Model을 나타내줄 View를 선택</li>
<li>View는 Model을 이용하여 화면을 나타냄</li>
</ol>
<p>Controller가 Model을 업데이트하고 View를 선택하면 View가 Model을 나타냄
View와 Model 사이의 의존성이 높아 어플이케이션이 커질 수록 복잡하고 유지보수가 어려움</p>
<pre><code>/src
  /components
    UserView.js
    UserController.js
  /models
    UserModel.js
  /views
    App.js</code></pre><pre><code>import React from &#39;react&#39;;
import UserModel from &#39;../models/UserModel&#39;;
import UserView from &#39;../components/UserView&#39;;

class UserController extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      user: new UserModel(&#39;Ahhyeon&#39;),
    };
  }

  handleNameChange = (newName) =&gt; {
    this.state.user.updateName(newName);
    this.forceUpdate();
  }

  render() {
    return (
      &lt;div&gt;
        &lt;UserView user={this.state.user} /&gt;
        &lt;button onClick={() =&gt; this.handleNameChange(&#39;Ahyeon&#39;)}&gt;Change Name&lt;/button&gt;
      &lt;/div&gt;
    );
  }
}

export default UserController;</code></pre><h3 id="mvvm">MVVM</h3>
<p><img src="https://velog.velcdn.com/images/a-honey/post/490473ea-5fe5-4d82-8f07-6e1a7120b880/image.png" alt=""></p>
<ol>
<li>사용자의 action이 View를 통해 들어옴</li>
<li>View에 action이 들어오면, 캡슐화하여 View Model에 Action을 전달</li>
<li>View Model은 Model에게 데이터를 요청</li>
<li>Model은 View Model에게 요청받은 데이터를 응답</li>
<li>View Model은 응답받은 데이터를 가공하여 저장</li>
<li>View는 View Model과 Data Binding하여 화면을 나타냄</li>
</ol>
<p>View와 Model의 의존성이 없음
캡슐화와 양방향 바인딩을 통해 View와 View Model의 의존성이 없음
각각의 부분이 독립적이기 때문에 모듈화하여 개발 가능</p>
<pre><code>/src
  /components
    UserView.js
    UserViewModel.js
  /models
    UserModel.js
  /views
    App.js</code></pre><pre><code>// View
import React from &#39;react&#39;;

class UserView extends React.Component {
  render() {
    // 양방향 바인딩
    return &lt;div&gt;User Name: {this.props.userViewModel.name}&lt;/div&gt;;
  }
}

export default UserView;

// View Model
import React from &#39;react&#39;;
import UserModel from &#39;../models/UserModel&#39;;

class UserViewModel extends React.Component {
  constructor(model) {
    super();
    this.model = model;
  }

  get name() {
    return this.model.name;
  }

  set name(newName) {
    this.model.updateName(newName);
  }
}

export default UserViewModel;</code></pre><h3 id="mvp">MVP</h3>
<p><img src="https://velog.velcdn.com/images/a-honey/post/2af18fce-f888-40bf-aa8a-9b9dcf1e9b05/image.png" alt=""></p>
<ol>
<li>사용자의 action이 View를 통해 들어옴</li>
<li>View는 Presenter에게 데이터를 요청</li>
<li>Presenter는 Model에게 데이터를 요청</li>
<li>Model은 Presenter에서 요청받은 데이터를 응답</li>
<li>Presenter는 View에게 데이터를 응답</li>
<li>View는 Presenter가 응답한 데이터를 이용하여 화면을 나타냄</li>
</ol>
<pre><code>import React from &#39;react&#39;;
import UserModel from &#39;../models/UserModel&#39;;
import UserView from &#39;../components/UserView&#39;;

class UserPresenter extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      user: new UserModel(&#39;John Doe&#39;),
    };
  }

  handleNameChange = (newName) =&gt; {
    this.state.user.updateName(newName);  // View Model을 업데이트
    this.forceUpdate();
  }

  render() {
    return (
      &lt;div&gt;
        // View에 전달 
        &lt;UserView userName={this.state.user.name} /&gt;
        &lt;button onClick={() =&gt; this.handleNameChange(&#39;Ahyeon&#39;)}&gt;Change Name&lt;/button&gt;
      &lt;/div&gt;
    );
  }
}

export default UserPresenter;</code></pre><h2 id="그-외의-패턴">그 외의 패턴</h2>
<h3 id="flux-패턴">Flux 패턴</h3>
<p>단방향 데이터 흐름을 유지하여 상태 관리를 간소화하는 데 중점을 둔 패턴</p>
<ul>
<li>Action: 사용자 또는 시스템 이벤트</li>
<li>Dispatcher: 액션을 받아 상태를 변경하고, 등록된 스토어에 변경 사항을 전파</li>
<li>Store: 애플리케이션의 상태를 저장하고 변경된 상태를 뷰에 전달</li>
<li>View: 사용자 인터페이스를 표시하고, 스토어의 데이터를 표시</li>
</ul>
<h3 id="redux-패턴">Redux 패턴</h3>
<p>Flux 패턴을 기반으로 하되, 단순한 구조와 불변성을 강조하여 상태관리를 효과적으로 처리하는 데 중점을 둔 패턴</p>
<ul>
<li>Action: 상태 변경을 설명하는 객체</li>
<li>Reducer: 현재 상태와 액션을 받아 새로운 상태를 반환</li>
<li>Store: 애플리케이션의 상태를 저장하고, 리듀서를 통해 상태를 변경</li>
<li>View: 상태를 표시하고, 스토어의 상태 변경을 구독하여 업데이트</li>
</ul>
<h3 id="singleton-패턴">Singleton 패턴</h3>
<p>어플리케이션 전반에서 단일 객체 인스턴스만을 생성하고 이에 접근하는 패턴</p>
<h3 id="observer-패턴">Observer 패턴</h3>
<p>객체 간 일대다 의존성을 정의하여 한 객체의 상태가 변경되면 종속 객체에 자동으로 알릴 수 있게 하는 패턴</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로젝트하다가 드는 생각]]></title>
            <link>https://velog.io/@a-honey/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%ED%95%98%EB%8B%A4%EA%B0%80-%EB%93%9C%EB%8A%94-%EC%83%9D%EA%B0%81</link>
            <guid>https://velog.io/@a-honey/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%ED%95%98%EB%8B%A4%EA%B0%80-%EB%93%9C%EB%8A%94-%EC%83%9D%EA%B0%81</guid>
            <pubDate>Sun, 17 Dec 2023 14:37:51 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/a-honey/post/a17c3746-167e-4e60-b5ba-f73ed1e1b1f7/image.gif" alt="">
<img src="https://velog.velcdn.com/images/a-honey/post/00328216-9668-490f-bf0f-cd752c79c17e/image.gif" alt=""></p>
<p>뭔가 아픈 손가락이 된 sponsor me now,,</p>
<p>처음에는 호기롭게 시작했다. 한달까지는 그냥 vite 써보고, 결제 시스템 이해해보면서 재미있었다. 둘 다 늦장부리는 타입은 아니었기 때문에 진도도 금방뺄 수 있었다. 결제 도입하는 과정에서 vite가 업데이트되는 바람에 vite, node 싹 다 업데이트하고 고생을 하긴 했지만, 오랜만에 하루 내내 쓰는 에러여서 나름 재밌기도 하고 trigger가 필요해서 날 기다리는 백엔드도 적당히 부담스럽고 재밌었다. 하지만 마무리가 되어가는게 느껴질수록, 매듭지어질 수 있나에 대한 의문이 들었다. 회원가입, 게시글 작성, 결제, 결제 내역 불러오기 등 각 기능은 다 작동이 잘되는데, 유저가 사용하는 하나의 서비스라기엔 오묘하게 모든 곳에서 약간의 어색함이 있었다. 마무리 단계에서는 빨리 완성하고 싶은 마음이 더 컸는데, 처음으로 어떻게 매듭지어야될지 모르겠어서 쳐다만 보다가 그냥 블로그만 뒤적거리는게 반복되어갔다. 그러다가 다른 프로젝트를 시작하면서 기획회의를 하다가, 첫단추부터 잘못지어졌다는 걸 알았고, 포기 선언을 했다. 최소한 프로토타입은 만들어놓겠다고 했지만, 뭘 더 해야하는지도 모르겠어서 그냥 그대로 끝내게 되었다.</p>
<p>근데 사실 이게 애매한게, 초반에 기획한건 다 구현했다. 어쨌든 돌아가는 서비스다. 근데 매듭이 안지어진다.
어디서부터 잘못된건가..!</p>
<h2 id="프로젝트에는-무엇이-필요한가">프로젝트에는 무엇이 필요한가</h2>
<h3 id="기획">기획</h3>
<p>프론트는 사용자와 가장 맞닿아있는 부분을 다뤄야하기 때문에, 사용자가 어떤 서비스를 왜, 어디에, 어떻게 쓸 것인지 이해하고 그에 맞춘 UX를 제공해야한다. 이 때 사용자의 페르소나, 요구사항, 시나리오 등등을 전부 생각하고, 해당 서비스가 왜 필요하고 어떻게 도움을 줄 수 있을지 정확하게 확정하고 가야한다. 적어도 와이어프레임을 그리는 과정에서 확정하며 서비스의 산출물을 예상할 수 있어야한다. 그런데 이번에는 조금 많이 달랐다. 사실 엘리스에서 제공해주는 주제도 없는 상황에서, &#39;결제기능을 해보자&#39;로 시작했고, 결제기능사용할꺼면 후원 서비스를 제작하자하고 출발했다. 와이어프레임도 늘 그랬듯이 그냥 즉석에서 그리고 바로 개발에 들어갔다. 기획보단 개발이 더 재밌으니까 빨리 떼버리겠다는 마음이 지배적이었단게 문제였다. 딱, 지금 막힌것과 똑같이 각 기능을 구현하는 페이지만 있고, 그 이후 마무리 설정이 전혀되어있지 않았다. 유저 시나리오가 전혀 없다보니 그냥 기능 위주로 페이지로 나와버린거다. 이제와서 기획을 엎고 가기엔 2주동안 마음이 떠버린 상태였다.</p>
<p>특히 이번에 동아리에서 PM과 함께 기획을 하면서 그 부족함을 계속 깨닫게 된다. 솔직히 엘리스에서 진행하면서 개인적으로 뭔가 백엔드를 수단으로 삼은 경향이 있다. 이런 기능이 필요하니까 만들어 달라고, 이런 데이터가 필요하니까 넣어달라고 프론트를 만드는 과정에서 계속 요청했다. 원래 그 기능 확정을 기획단계에서 마무리하고, 디자이너, 프론트와 백엔드는 각자 작업을 한 후 연결과정에서만 만났어야 했다. 그런데 PM, 디자이너, 프론트엔드를 내가 잡고 하다보니까 기능이 계속해서 변경되어야 했던거다. 그래서 모든 기획을 알고 있는 사람이 나밖에 없었고, 점점 더 부담스러웠던 것 같다. 이번에도 비슷한 결로 시작했다가 같은 문제가 생긴 셈이다. 프론트하고 싶으니까 기획은 대충하게 되고, 대충한 기획으로 붙들고 나가기가 쉽지 않다.</p>
<h3 id="디자인">디자인</h3>
<p>와이어프레임 기획을 제외하고 봐도, 디자인의 퀄리티면에서 부족함이 너무 보였다. 이게 화면이 큰 PC버전의 경우 조금 퀄리티가 덜해도 괜찮아보였는데, 웹뷰로 제작하다보니까 이 디자인의 부족함이 너무 눈에 보였다. 미묘한 차이가 바로바로 느껴져버리니까 퍼블리싱하고 싶은 마음도 점점 줄어들었다. 사실 눈에 보이는 산출물이 별로라는게 예상되다보니까 기획을 엎어도 똑같겠다는 마음이 크기도 했다. 이게 디자이너의 작업물을 한번 받아보니까 퀄리티 차이가 명확하게 드러나서 내가 안하고 사람을 구하는게 낫겠다는 생각을 하기 시작했다. 
디자이너 뿐만 아니라 일단 기획자, 디자이너, 백엔드, 프론트엔드 각각 2명씩은 있어야 프로젝트를 진행하면서 발전을 할 수 있을거같다. 모두와 소통해야하는 프론트의 숙명,,</p>
<h3 id="환경">환경</h3>
<p>엘리스에서 VM을 제공했고, 제공된 VM을 바탕으로 백엔드분들이 배포를 해줘서 나름 편하게 작업을 할 수 있었다. 하지만 둘이서 하다보니 AWS 프리티어를 사용해야했고, 프리티어여서 굉장히 느렸다. 백엔드분이 연구하다가 뭔가 가상 램을 설정해서 조금 편해지긴 했지만, 이미지 같은 경우 묘하게 싱크가 안맞았다. 사실 이 부분은 난 프론트만 하다보니까 못느꼈는데, 어쨌든 다음 프로젝트에서 배포를 어떻게 할 것인가에 대한 방법을 생각하고 참여해야함을 알 수 있었다. 이번에는 백엔드분이 알아서 다 해주셔서 나는 그냥 편하게 있었는데, 다른 프로젝트에도 이런 분이 있을지는 모르겠다. 이래서 그냥 백엔드하고 협업하는건 좀 어려울거같기도 하다. 나는 장기전을 바라봐야하는 사람인데 도중에 백서버가 자꾸 꺼지다보니까 뭔가 아쉬움이 크다. 다들 백 서버 유지 어떻게 하는거지..?</p>
<h2 id="그냥-생각">그냥 생각</h2>
<p>단기성 프로젝트를 프론트 혼자 하다보니까 처음에는 재밌었는데, 이제 조금씩 한계가 보인다. 내가 작성한 코드에서 더 나아가지 못하는 느낌이 크게 든다. 그냥 돌아다니다가 리액트에서 탬플릿을 사용하는 프로젝트를 봤는데, 이해가 안가는거다. 거기서 좀 내 코드에 갇혀있기 쉽겠다는 생각을 하고, 다른 프로젝트의 코드를 둘러보기 시작했다. 그리고 리액트 스터디를 하면서, 초면인 개념이야 당연히 그냥 배웠는데, useState같은 아는 것들을 막상 까보니까 전혀 이해가 안가서 놀랐다. 이제는 프로젝트는 적당히 하고, 개념을 좀 채워나가고 싶다. 혼자서 책읽다가 스터디하면서 읽으니까 좀 더 자세히 읽게 되고 자존심 미쳐서 더 알아보고 싶어서 혼자 난리다. 난 분명 2개월동안 열심히 놀고 먹으려고 했는데, 왜 이러고 있는지 모르겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[무한스크롤 구현하기]]></title>
            <link>https://velog.io/@a-honey/%EB%AC%B4%ED%95%9C%EC%8A%A4%ED%81%AC%EB%A1%A4-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@a-honey/%EB%AC%B4%ED%95%9C%EC%8A%A4%ED%81%AC%EB%A1%A4-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 04 Dec 2023 15:24:29 GMT</pubDate>
            <description><![CDATA[<h2 id="높이에-따른-데이터-불러오기">높이에 따른 데이터 불러오기</h2>
<p>사용자가 눈으로 보고 있는 화면의 높이(clientHeight)와 사용자가 스크롤해서 내린만큼의 높이(scrollTop)를 더한 값이, 전체화면에 대한 높이(scrollHeight)와 같으면 새로운 데이터를 받아오는 방식</p>
<pre><code>window.addEventListner(&quot;scroll&quot;, infiniteScroll);
fetch(`${API}/posts?offset=${offset}&amp;limit=${limit}`)
  .then(res =&gt; res.json())
  .then(res =&gt; {
    setState({
      products: res.products,
      offset: offset + limit,
      });
    });

const infiniteScroll = () =&gt; {
  const scrollHeight = Math.max(
    document.documentElement.scrollHeight,
    document.body.scrollHeight
  );
  const scrollTop = Math.max(
    document.documentElement.scrollTop,
    document.body.scrollTop
  );
  const clientHeight = document.documentElement.clientHeight;
  if (scrollTop + clientHeight === scrollHeight) {
    fetch(`${API}/posts?offset=${offset}&amp;limit=${limit}`)
      .then(res =&gt; res.json)
      .then(res =&gt; {
        setState({
          products: [...products, ...res.products],
          offset: offset + limit,
        });
        setState({ isLoading: true});
        setTimeout(() =&gt; {
          setState({ isLoading: false });
        }, 350);
      });
    }</code></pre><p><img src="https://velog.velcdn.com/images/a-honey/post/0ae7a0f8-c55e-4fac-9351-0996e90b5de2/image.png" alt=""></p>
<h2 id="요소에-따른-데이터-불러오기">요소에 따른 데이터 불러오기</h2>
<p>타겟 요소와 타겟의 부모 혹은 상위 요소의 뷰포트가 교차되는 부분을 비동기적으로 관찰하는 API, IntersectionObserver를 활용하여 화면에 지정한 타겟 요소가 보이면 데이터를 불러옴</p>
<pre><code>function ProductPage () {
  const [ target, setTarget ] = useState(null);
// ...

  const _fetchProductItems = () =&gt; {
    const productItems = apiProductItems(itemLength);

    if (!productItems.length) {
      actions.isLoaded(dispatch)(false);
      return;
    }

    // ...
  };

  useEffect(() =&gt; {
    let observer;
    if (target) {
      observer = new IntersectionObserver(_onIntersect, { threshold: 1 });
      observer.observe(target);
    }

    return () =&gt; observer &amp;&amp; observer.disconnect();
  }, [ target ]);

  const _onIntersect = ([ entry ]) =&gt; {
    if (entry.isIntersecting) {
      _fetchProductItems();
    }
  };

// ...

  return (
    &lt;&gt;
      // ...
      {state.isLoaded &amp;&amp; &lt;div ref={setTarget}&gt;loading&lt;/div&gt;}
    &lt;/&gt;
  );
}

export default ProductPage;</code></pre><h2 id="useinfinitequery">useInfiniteQuery</h2>
<p>tanstack-query의  useInfiniteQuery를 활용한 무한 스크롤</p>
<pre><code>const {
  fetchNextPage,
  fetchPreviousPage,
  hasNextPage,
  hasPreviousPage,
  isFetchingNextPage,
  isFetchingPreviousPage,
  ...result
} = useInfiniteQuery({
  queryKey,
  queryFn: ({ pageParam }) =&gt; fetchPage(pageParam),
  initialPageParam: 1,
  ...options,
  getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) =&gt;
    lastPage.nextCursor,
  getPreviousPageParam: (firstPage, allPages, firstPageParam, allPageParams) =&gt;
    firstPage.prevCursor,
})</code></pre><pre><code>import PostItem from &#39;../../../components/common/PostItem&#39;;
import styles from &#39;../styles/Main.News.module.scss&#39;;
import {
  QueryFunction,
  QueryFunctionContext,
  useInfiniteQuery,
} from &#39;@tanstack/react-query&#39;;
import { instance } from &#39;@/api/instance&#39;;

interface PostsDataType {
  pages: {
    totalPage: number;
    currentPage: number;
    posts: {
      id: number;
      title: string;
      content: string;
      createdAt: string;
      updatedAt: string;
      authorId: number;
      viewCount: number;
      postImg: null | string;
    }[];
  }[];
}
export interface IRepository {
  total_count: number;
  incomplete_results: boolean;
  items: PostsDataType[];
  nextCursor?: string;
  prevCursor?: string;
}

type QueryKey = [string, string, string];

const News = () =&gt; {
  const page = 1;

  const fetchRepositories: QueryFunction&lt;
    IRepository,
    QueryKey,
    unknown
  &gt; = async ({ queryKey }: QueryFunctionContext&lt;QueryKey&gt;) =&gt; {
    const [, page, search] = queryKey;
    return await instance
      .get&lt;IRepository&gt;(
        `/post/list?page=${page}&amp;limit=7${search &amp;&amp; `&amp;search=${search}`}`,
      )
      .then((res) =&gt; res.data);
  };
  const {
    fetchNextPage,
    fetchPreviousPage,
    hasNextPage,
    hasPreviousPage,
    isFetchingNextPage,
    isFetchingPreviousPage,
    ...result
  } = useInfiniteQuery&lt;IRepository, Error, PostsDataType&gt;({
    queryKey: [&#39;postList&#39;, page.toString(), &#39;all&#39;],
    queryFn: fetchRepositories,
    getNextPageParam: (lastPage) =&gt; lastPage.nextCursor,
    getPreviousPageParam: (firstPage) =&gt; firstPage.prevCursor,
  });

  console.log(result?.data);
  console.log(
    fetchNextPage,
    fetchPreviousPage,
    hasNextPage,
    hasPreviousPage,
    isFetchingNextPage,
    isFetchingPreviousPage,
  );
  return (
    &lt;div className={styles.container}&gt;
      {result?.data?.pages[0].posts.map((item) =&gt; (
        &lt;PostItem key={item.id} data={item} /&gt;
      ))}
    &lt;/div&gt;
  );
};

export default News;
</code></pre><h2 id="이슈">이슈</h2>
<p>데이터는 불러와지는데 페이지 이동은 어떻게 하는거지..
데이터가 없으니까 된건지 확인할수가없다,,</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[iamport 라이브러리 사용하기]]></title>
            <link>https://velog.io/@a-honey/iamport-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@a-honey/iamport-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 02 Dec 2023 17:09:17 GMT</pubDate>
            <description><![CDATA[<h2 id="결제를-해보자">결제를 해보자</h2>
<p>결제 과정은 프론트에서 결제사에 금액과 정보 등을 가지고 요청을 보내고, 식별가능한 유니크 id를 받은 다음, 해당 id를 백에 전달한다. id를 받은 백은 해당 id를 결제사에 보내 유효한 결제 id인지 확인한 후, 해당 결제 정보를 받아와서 db에 저장한다.</p>
<h4 id="왜-id만-주고-받을까">왜 id만 주고 받을까</h4>
<p>백은 어떤 경우든 프론트에서 완벽한 정보만 올 것이라고 생각해서는 안됨. 결제 id는 1000원으로 결제 해놓고 100000 만원이라고 서버에 보낼 가능성이 존재하기 때문에 안전하게 결제사에서 정보를 받아와야함.</p>
<h4 id="왜-결제-정보를-결제사와-서버에서-저장할까">왜 결제 정보를 결제사와 서버에서 저장할까</h4>
<p>결제 내역 history를 제어하기 위함. 특정 기간의 결제 내역을 불러오거나, 특정 결제 대상 품목 필터링 등을 위해서 서버에 저장한다. 결제 내역을 서버에 저장하는게 추후 get할 때 결제사 개입 없어서 더 편함. 데이터 유지가능.</p>
<pre><code>import postPaymentHistory from &#39;@/api/post/postPaymentHistory&#39;;
import { useLoginStore } from &#39;@/store&#39;;
import { useState } from &#39;react&#39;;
import styles from &#39;../styles/UserId.PaymentSelect.module.scss&#39;;

const PaymentSelect = ({
  email,
  username,
  id,
  toggleIsOpenPaymentSelect,
}: {
  email: string;
  username: string;
  id: number;
  toggleIsOpenPaymentSelect: () =&gt; void;
}) =&gt; {
  const [selectedAmount, setSelectedAmount] = useState&lt;null | number&gt;(null);
  const [customAmount, setCustomAmount] = useState&lt;undefined | number&gt;();

  const handleSelectChange = (amount: number | null) =&gt; {
    setSelectedAmount(amount);
    setCustomAmount(0);
  };

  const handleCustomInputChange = (
    event: React.ChangeEvent&lt;HTMLInputElement&gt;,
  ) =&gt; {
    const inputValue = Number(event.target.value);
    if (isNaN(inputValue)) {
      alert(&#39;숫자를 입력해주세요.&#39;);
      setCustomAmount(undefined);
      return;
    } else {
      setSelectedAmount(null);
      setCustomAmount(inputValue);
    }
  };

  return (
    &lt;div className={styles.container}&gt;
      &lt;h2&gt;후원하기&lt;/h2&gt;
      &lt;label&gt;
        &lt;input
          type=&quot;radio&quot;
          name=&quot;paymentAmount&quot;
          value=&quot;10000&quot;
          checked={selectedAmount === 10000}
          onChange={() =&gt; handleSelectChange(10000)}
        /&gt;
        10000원
      &lt;/label&gt;

      &lt;label&gt;
        &lt;input
          type=&quot;radio&quot;
          name=&quot;paymentAmount&quot;
          value=&quot;5000&quot;
          checked={selectedAmount === 5000}
          onChange={() =&gt; handleSelectChange(5000)}
        /&gt;
        5000원
      &lt;/label&gt;

      &lt;label&gt;
        &lt;input
          type=&quot;radio&quot;
          name=&quot;paymentAmount&quot;
          value=&quot;3000&quot;
          checked={selectedAmount === 3000}
          onChange={() =&gt; handleSelectChange(3000)}
        /&gt;
        3000원
      &lt;/label&gt;

      &lt;label&gt;
        &lt;input
          type=&quot;radio&quot;
          name=&quot;paymentAmount&quot;
          value=&quot;1000&quot;
          checked={selectedAmount === 1000}
          onChange={() =&gt; handleSelectChange(1000)}
        /&gt;
        1000원
      &lt;/label&gt;

      &lt;label&gt;
        &lt;input
          type=&quot;radio&quot;
          name=&quot;paymentAmount&quot;
          value=&quot;custom&quot;
          checked={selectedAmount === null &amp;&amp; customAmount === undefined}
          onChange={() =&gt; handleSelectChange(null)}
        /&gt;
        직접입력
      &lt;/label&gt;

      &lt;input
        type=&quot;text&quot;
        placeholder=&quot;원하는 금액 입력&quot;
        value={customAmount}
        onChange={handleCustomInputChange}
        disabled={selectedAmount !== null}
      /&gt;
      &lt;RequestPay
        payAmount={
          selectedAmount !== null &amp;&amp; selectedAmount !== 0
            ? selectedAmount
            : Number(customAmount)
        }
        email={email}
        username={username}
        id={id}
        toggleIsOpenPaymentSelect={toggleIsOpenPaymentSelect}
      /&gt;
      &lt;button className={styles.gray} onClick={toggleIsOpenPaymentSelect}&gt;
        후원 취소
      &lt;/button&gt;
    &lt;/div&gt;
  );
};

export default PaymentSelect;

interface Response {
  apply_num?: number;
  bank_name?: string;
  buyer_addr?: string;
  buyer_email?: string;
  buyer_name?: string;
  buyer_postcode?: string;
  buyer_tel?: string;
  card_name?: string;
  card_quota?: number;
  custom_data?: string;
  imp_uid?: string;
  merchant_uid?: string;
  name?: string;
  paid_amount?: number;
  paid_at?: string;
  pay_method?: string;
  pg_provider?: string;
  pg_tid?: string;
  receipt_url?: string;
  status?: string;
  success: boolean;
}

interface RequestPayParams {
  pg: string;
  pay_method: string;
  merchant_uid: string;
  name: string;
  amount: number;
  buyer_email: string;
  buyer_name: string;
  buyer_tel: string;
  buyer_addr: string;
  buyer_postcode: string;
}

const RequestPay = ({
  email,
  username,
  payAmount,
  id,
  toggleIsOpenPaymentSelect,
}: {
  email: string;
  username: string;
  id: number;
  payAmount: number;
  toggleIsOpenPaymentSelect: () =&gt; void;
}) =&gt; {
  const code = import.meta.env.VITE_IAMPORT_CODE;

  const { loginUsername, loginEmail } = useLoginStore();

  if (!window.IMP || !code) return;
  const { IMP } = window;
  IMP.init(code);

  const today = new Date();
  const hours = today.getHours(); // 시
  const minutes = today.getMinutes(); // 분
  const seconds = today.getSeconds(); // 초
  const milliseconds = today.getMilliseconds();
  const makeMerchantUid = hours + minutes + seconds + milliseconds;
  const requestPay = () =&gt; {
    (
      IMP.request_pay as (
        params: RequestPayParams,
        callback: (rsp: Response) =&gt; void,
      ) =&gt; void
    )(
      {
        pg: `kakaopay.${import.meta.env.VITE_IAMPORT_KAKAOPAY_ID!}`,
        pay_method: &#39;card&#39;,
        merchant_uid: `IMP_${makeMerchantUid}`,
        name: `${loginUsername}의 ${username} 후원`,
        amount: payAmount,
        buyer_email: loginEmail!,
        buyer_name: loginUsername!,
        buyer_tel: &#39;010-4242-4242&#39;,
        buyer_addr: &#39;서울특별시 강남구 신사동&#39;,
        buyer_postcode: &#39;01181&#39;,
      },
      (rsp: Response) =&gt; {
        if (rsp.success) {
          postPaymentHistory({
            sellerName: username,
            sellerEmail: email,
            impUid: rsp.imp_uid,
            sellerId: id,
          });
        } else {
          console.log(&#39;결제실패&#39;, rsp);
          toggleIsOpenPaymentSelect();
        }
      },
    );
  };

  return (
    &lt;button className={styles.green} onClick={requestPay}&gt;
      후원하기
    &lt;/button&gt;
  );
};</code></pre><p><img src="https://velog.velcdn.com/images/a-honey/post/2f09b0b7-1bf2-4bf1-99b4-c9696a6b1818/image.png" alt=""></p>
<h2 id="결제를-취소해보자">결제를 취소해보자</h2>
<p>프론트에서는 결제 내역의 유니크값을 가지고 백 서버에 delete 요청을 보냄. 백은 유니크값을 가지고 결제내역 데이터를 찾음. 유니크값 결제 id를 결제사에 보내 결제 취소요청을 보내고, 취소된 정보를 받아서 결제내역 데이터를 변경함.</p>
<h4 id="왜-삭제하지-않고-변경을-하는가">왜 삭제하지 않고 변경을 하는가</h4>
<p>결제 관련 데이터는 중요함. 환불 일시, 환불 금액, 환불 카드사 등 추후 문제가 될 수 있는 부분을 가지고 있어야함</p>
<h2 id="생각하기">생각하기</h2>
<p>전의 모든 trigger는 테스트 버전이다. 사업자등록 필요한데, 법적인 문제가 걸려있어 발들이기 어려움. 유저에게 노출가능한 값은 무엇이 있는가. 특정 정보만으로 결제사로 들어가 접근할 수 없도록 보안이 필요함. 프론트에서 해줄 수 있는 보안 강화는 무엇이 있을까. 수수료는 어디서 얼마나 빠져나가야하는가.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Understanding "return null" vs "return false" in React
]]></title>
            <link>https://velog.io/@a-honey/Understanding-return-null-vs-return-false-in-React</link>
            <guid>https://velog.io/@a-honey/Understanding-return-null-vs-return-false-in-React</guid>
            <pubDate>Sun, 26 Nov 2023 10:42:00 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/a-honey/post/ca73408b-46a9-41ac-918a-e85c83c24693/image.png" alt=""></p>
<p>많은 사람들이 null을 리턴하는 것과 false를 리턴하는 것이 같다고 생각한다. 그들은 매우 비슷하지만, React가 동작하는 방법과 관련하여 몇 가지 차이점이 존재한다.</p>
<p>React로 개발을 할때, 특정 조건에 따라 다른 컴포넌트를 보여주기 위한 조건부 렌더링을 흔히 사용한다. 이러한 경우, 컴포넌트가 렌더되지 않아야함을 명확하게 나타나길 원할 것이다. 여기서 null과 false가 사용된다. 둘 다 컴포넌트가 렌더링되는 것을 막지만, 그들 사이에 차이점이 있다.</p>
<h3 id="returning-null">Returning null</h3>
<p>컴포넌트가 null을 리턴할때, React에게 그 컴포넌트의 어느것도 DOM에서 렌더하지 말라고 말하는 것이다. 이것은 상태를 충족하지 않았을때 어느것도 렌더링하지 않길 원하거나 특정 상태에 따라 컴포넌트를 조건부로 렌더링하길 원할 때 유용할 수 있다.</p>
<pre><code>const Greeting = (props) =&gt; {
  if (!props.name) {
    return null;
  }

  return &lt;h1&gt;Hello, {props.name}!&lt;/h1&gt;;
};</code></pre><p>만약 name prop이 제공되지 않았다면, 컴포넌트는 null을 리턴하고 h1 태그가 렌더링되는 것을 막을 것이다. 만약 제공된다면, h1 태그는 렌더링된다.</p>
<h3 id="returning-false">Returning false</h3>
<p>컴포넌트가 false를 리턴할때, null과 같이 컴포넌트를 DOM의 어디에도 렌더링하지 말라고 말한다. 그러나, 중요한 차이점이 있다. false 리턴은 컴포넌트가 미래에 업데이트되는 것을 막는다. 이것은 React가 false를 전체 DOM으로부터 제거하라는 의미인 컴포넌트 언마운트의 신호로 해석하기 때문이다.</p>
<pre><code>const Button = (props) =&gt; {
  if (!props.onClick) {
    return false;
  }

  return &lt;button onClick={props.onClick}&gt;Click me&lt;/button&gt;;
};</code></pre><p>onClick prop이 제공되지 않는다면, 컴포넌트는 false를 리턴하고, button 태그가 렌더링되는 것을 막을 것이다. 만약 제공된다면, 특정 클릭 핸들러를 가진 button이 렌더될 것이다.</p>
<p>false 리턴하는 것은 업데이트가 되어야할때도 업데이트를 막는 것과 같은 의도치 않은 결과를 가져올 수 있기 때문에 중요하다. 이런 이유로, 미래 업데이트에 영향을 주고 싶지 않을 때는 null을 사용해야 더 안전하다.</p>
<p>컴포넌트의 렌더 방법으로 false를 리턴하는 것은 상태 업데이트나 API 요청과 같은 side effect를 가지지 않았을 때만 유용하다. 만약 side effect를 가지고 false를 리턴한다면, 이런 side effect는 기대하지 않은 행동을 초래할 수 있다.</p>
<h3 id="returning-fragment">Returning Fragment</h3>
<p>Fragment는 컴포넌트를 렌더링할 때 불필요한 DOM 요소를 생성하지않는다. DOM 구조에 영향을 주지 않고 요소를 함께 그룹화하는 데 사용한다. Fragment는 DOM에 렌더되지 않고 Fragment의 자식요소들은 렌더되므로 사용하는 케이스가 다르다.</p>
<hr>
<p>결론?: false로 하면 이상한 경우가 있으니 null 사용해라</p>
<h3 id="그런-경우가-어떤-경우인가">그런 경우가 어떤 경우인가</h3>
<p>shouldComponentUpdate
어떤 컴포넌트의 render()가 자신이 다시 호출될 필요가 없음에도 호출되는 것을 막기위해 사용하는 것
반환값이 true이면 render가 호출되고, false이면 render가 호출되지 않음</p>
<pre><code>shouldComponentUpdate(nextProps: Props, nextState: State) {
  return this.props.title !== nextProps.title || this.state.input !== nextState.input
  }</code></pre><h3 id="reference">Reference</h3>
<p><a href="https://tech.jotform.com/return-null-vs-return-false-in-react-826d8abcc429">https://tech.jotform.com/return-null-vs-return-false-in-react-826d8abcc429</a>
<a href="https://junghyeonsu.com/posts/react-fragment-vs-null/">https://junghyeonsu.com/posts/react-fragment-vs-null/</a>
모던 리액트 Deep Dive</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[상태관리 라이브러리를 선택하는 기준]]></title>
            <link>https://velog.io/@a-honey/%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC%EB%A5%BC-%EC%84%A0%ED%83%9D%ED%95%98%EB%8A%94-%EA%B8%B0%EC%A4%80</link>
            <guid>https://velog.io/@a-honey/%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC%EB%A5%BC-%EC%84%A0%ED%83%9D%ED%95%98%EB%8A%94-%EA%B8%B0%EC%A4%80</guid>
            <pubDate>Sun, 19 Nov 2023 09:30:20 GMT</pubDate>
            <description><![CDATA[<h2 id="poc">P..o..C..?</h2>
<p>PoC(Proof of Concept). 어떤 기술을 도입할 때 왜 도입했는지, 어떤 효과가 가대되는지에 대해 토론하자. 기술 도입을 위한 개념 증명, 논리 확보를 위해 자료를 수집하고 정리하기. 서로 토론하면 더 좋다.
=&gt; 제대로 알고 라이브러리를 선택해라!!</p>
<h2 id="나의-라이브러리-선택-기준">나의 라이브러리 선택 기준</h2>
<p><img src="https://velog.velcdn.com/images/a-honey/post/0dbc8658-4161-4e90-bda4-9dc0fa87e6ac/image.png" alt="">
이건 정말 할 말이 없지만.. 지금까지 내 라이브러리 선택 기준 1순위는 로고가 얼마나 귀여운가였다. 진짜 그러면 안되는걸 알면서도 뭔가 로고가 좀 올드하거나 화질이 저세상이면 손이 안 갔다. 일단 라이브러리 성능에 대한 기준이 없다보니 그냥 귀여운 라이브러리는 줍줍해서 쓰려고 했다. react-query가 이 분야의 정점인데, 꽃모양이 너무 귀여워서 쓰고야말겠다는 생각이 날 지배했다. 그러다가 아다리가 맞게도 적합한 이슈가 생긴 시점에 그냥 선택한 react-query가 해결해주게 되면서, 라이브러리의 성능에 대한 관심이 생겨났다.
아무튼 그래서 내 최애 라이브러리는 Storybook.. 사실 아직도 귀여워..
이건 그냥 추측인데 만약 react보다 express 로고가 더 귀여웠다면 난 백엔드 했을지도..?</p>
<p>react-query나 storybook 같은 경우는 그냥 로고가 귀여워서 언젠가 쓰고 싶은 라이브러리였고, 그래도 나름 생각해서 선택하긴했다. 일단 라이브러리쓰기 전에 상태관리조차도 제대로 모를때 prop주기 귀찮으니까 특정 페이지 안에서만 상태관리를 하고 싶어서 Context API를 사용했다. 그러다가 /:id search로 Context API를 쉽게 대체할 수 있었다. 그리고 로그인 상태를 전역관리하고 싶어서 라이브러리를 찾아봤고, login이나 logout에서 뭔가 액션이 있다고 판단해서 redux-Toolkit을 사용했다.(redux 대체된다길래 모른척했는데 다시 공부하러가야한다) 그리고 액션이 없는 것들은 간단하게 상태를 저장하고 바꾸는정도만하면 되겠다 싶어서 recoil을 사용했다. 그리고 최근에는 recoil보다 zustand가 가볍다길래 zustand를 사용했다.</p>
<h2 id="무맥락에-논리를-넣어보자">무맥락에 논리를 넣어보자</h2>
<h3 id="context-api">Context API</h3>
<p>React에서 지원하는 context를 사용하기 위한 API이다. React에서 지원하기 때문에 별도의 패키지를 다운받을 필요없이 상용가능하며, 러닝커브가 작다. 그러나 하나의 상태만 저장가능하며, <strong>상태값이 변경되면 Provider로 감싼 모든 자식 컴포넌트가 리렌더링</strong>된다.</p>
<h3 id="redux">Redux</h3>
<p><img src="https://velog.velcdn.com/images/a-honey/post/bd2c433d-2c74-4e3f-b4b6-4ede8fbb3e5a/image.png" alt=""></p>
<p>Flux 아키텍처 모델을 기반으로 하는 중앙 집중식 상태 관리 솔루션을 사용. Action이 발생하면 Dispatcher에서 이를 해석한 후 Store에 저장된 정보를 갱신하고, 그 결과가 다시 View로 전달된다. 복잡한 로직을 처리하기 위한 강력한 미들웨어를 지원하지만 더 많은 보일러플레이트 코드가 발생할 수 있으며 러닝커브가 있다.</p>
<h3 id="mobx">MobX</h3>
<p><img src="https://velog.velcdn.com/images/a-honey/post/7577984b-4bf8-48dd-9149-57e938fd8fc6/image.png" alt=""></p>
<p>상태를 프록시 객체로래핑하여 직접 객체를 다루지 않고, 프록시를 통해 작업을 수행하므로 중첩된 객체의 상태 관리에 유용하다. 일반적으로 중첩된 객체의 상태를 관리하려면 상태를 복사하고, 속성을 수정하고, 수정한 상태를 다시 덮어쓰는 과정이 필요한데, MobX에서는 프록시를 통해 상태를 직접 변경할 수 있어 중첩 객체 상태 관리가 편해진다. </p>
<h3 id="recoil">Recoil</h3>
<p><img src="https://velog.velcdn.com/images/a-honey/post/21744dcf-6b89-48a2-aeb0-a4a46ebd77c2/image.png" alt=""></p>
<p>Context API 기반으로 페이스북에서 만든 함수형 컴포넌트에서만 사용 가능한 전역 상태관리 라이브러리로, react 내장 hooks를 사용하는 방식과 크게 다르지 않아 러닝커브가 작다. redux의 thunk나 saga같은 미들웨어를 사용할 필요없이 비동기 처리가 내장되어 있으며 보일러 플레이트가 작다. 그러나 클래스형 컴포넌트에서 사용할 수 없다.</p>
<h3 id="zustand">Zustand</h3>
<p><img src="blob:https://velog.io/4d5a20ff-4ecf-4061-87b4-f990112a6e1e" alt="업로드중.."></p>
<p>React Hooks를 기반으로 하여 함수형 컴포넌트에 쉽게 통합되며, 가볍고 번들 크기가 작다.</p>
<h2 id="기준을-세워보자">기준을 세워보자</h2>
<ul>
<li>커뮤니티 크기
Redux &gt; MobX &gt; Recoil = Zustand &gt; Context API</li>
<li>러닝커브
Redux &gt; MobX &gt; Recoil &gt; Zustand &gt; Context API</li>
<li>프로젝트 크기
큰 프로젝트: Redux = MobX &gt; Recoil &gt; Zustand = Context API
중간 프로젝트: MobX &gt; Recoil = Zustand &gt; Redux &gt; Context API
작은 프로젝트: Zustand &gt; Recoil &gt; MobX &gt; Redux &gt; Context API</li>
<li>설치 및 설정 간편성
Zustand &gt; Recoil &gt; Context API &gt; MobX &gt; Redux</li>
<li>배우기 쉬운 정도
Zustand &gt; Recoil = Context API &gt; MobX &gt; Redux</li>
<li>미들웨어 및 고급 기능 지원
Redux &gt; MobX &gt; Recoil &gt; Zustand = Context API</li>
<li>중첩된 객체 또는 복잡한 상태 구조 관리
MobX &gt; Recoil &gt; Redux = Zustand &gt; Context API</li>
<li>함수형 및 클래스형 컴포넌트 모두 지원
MobX &gt; Recoil &gt; Zustand &gt; Context API &gt; Redux</li>
<li>동기 및 비동기 업데이트 지원
Recoil &gt; Zustand &gt; Redux &gt; MobX &gt; Context API</li>
<li>성능
Recoil &gt; Zustand &gt; MobX &gt; Redux &gt; Context API</li>
<li>비동기 처리 지원
Recoil &gt; Zustand &gt; Redux &gt; MobX &gt; Context API</li>
<li>기존 프로젝트와의 호환성
Redux &gt; MobX &gt; Zustand &gt; Recoil &gt; Context API</li>
</ul>
<h2 id="최신-라이브러리-톺아보기">최신 라이브러리 톺아보기</h2>
<p>Recoil과 Jotai는 Context와 Provider, 그리고 훅을 기반으로 가능한 작은 상태를 효율적으로 관리하는데 초점을 맞춘다. 
Zustand는 리덕스와 비슷하게 하나의 큰 스토어를 기반으로 상태를 관리하는 라이브러리로 하나의 큰 스토어가 클로저를 기반으로 생성되어 이 스토어의 상태가 변경되면 상태를 구독하고 있는 컴포넌트에 전파해 리렌더링을 알리는 방식이다.</p>
<h4 id="recoil-1">Recoil</h4>
<p>리액트에서 훅의 개념으로 상태 관리를 시작한 최초의 라이브러리 중 하나
최소 상태 개념인 Atom을 처음 리액트 생태계에 선보임
메타 팀에서 주도적으로 개발
selector 등 다양한 비동기 작업을 지원하는 API를 제공하여 미들웨어 불필요
그러나 아직 실험 단계로, 안정성, 성능, 사용성 보장 불가능</p>
<h4 id="jotai">Jotai</h4>
<p>Recoil의 atom 모델에 영감을 받아 만들어진 상태 관리 라이브러리
작은 단위의 상태를 위로 전파할 수 있는 구조인 bottom-up 상향식 접근법을 취함
리액트 Context의 불필요한 리렌더링 문제를 해결하고자 설계되었으며 메모이제이션이나 최적화를 거치지 않아도 리렌더링이 발생되지 않도록 설계됨.</p>
<h4 id="zustand-1">Zustand</h4>
<p>리덕스에 영감을 받아 하나의 스토어를 중앙 집중형으로 활용해 이 스토어 내부에서 상태를 관리함</p>
<h2 id="결론">결론</h2>
<p>리덕스가 리액트 상태 관리를 지배하다가, 최근에는 여러가지 라이브러리들이 등장했다. 각 상태 관리 라이브러리가 상태를 관리하는 방식에는 조금씩 차이가 있지만, 리액트에서 리렌더링을 일으키기 위한 방식은 제한적이기 때문에 어떠한 방식으로 상태를 관리하든지 간에 리렌더링을 만드는 방법은 거의 동일하다. 따라서 각 라이브러리별로 특징을 잘 파악하고, 현재 애플리케이션이 상황과 철학에 맞는 상태 관리 라이브러리를 적절하게 사용하여 효율적인 애플리케이션을 만들자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[CRA없이 리액트 프로젝트 직접 구성하기]]></title>
            <link>https://velog.io/@a-honey/CRA%EC%97%86%EC%9D%B4-%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%A7%81%EC%A0%91-%EA%B5%AC%EC%84%B1%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@a-honey/CRA%EC%97%86%EC%9D%B4-%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%A7%81%EC%A0%91-%EA%B5%AC%EC%84%B1%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 12 Nov 2023 06:07:41 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/a-honey/post/eccdb9f7-cce0-47b4-95ab-ead1e6e86355/image.png" alt=""></p>
<h1 id="webpack과-vite">Webpack과 vite</h1>
<p>&#39;create-react-app&#39; 하나면 리액트 프로젝트 뚝딱이었지만, 최근에는 vite를 사용한다는 말이 들려서 webpack, babel 등등 이들이 그래서 뭔 역할을 하는지 궁금해졌다. 일단 대략적인 차이점은 webpack은 빌드를 하면서 모든 모듈을 하나의 번들로 제공하고, vite는 개발 서버에서 모듈을 실시간으로 번들링하고, 프로덕션 빌드 시에는 더 작은 코드 스플릿 번들을 생성하여 빠른 반응성을 제공한다는 점. 한 모듈 리플레이스먼트를 지원하고, esbuild를 사용한다고 하는데 webpack도 떼지못한 내가 알아보기엔 외계어다..</p>
<h1 id="react-직접-구성하기">React 직접 구성하기</h1>
<p>일단 webpack의 역할부터 체감해야한다.</p>
<h4 id="1-packagejson-파일-생성">1. package.json 파일 생성</h4>
<p><code>npm init -y</code></p>
<p>노드 프로젝트를 시작.</p>
<h4 id="2-babel-설치">2. Babel 설치</h4>
<p><code>npm install --save-dev @babel/core @babel/preset-env @babel/preset-react babel-loader</code></p>
<pre><code>// .babelrc
{
  &quot;presets&quot;: [&quot;@babel/preset-env&quot;, &quot;@babel/preset-react&quot;]
}</code></pre><p>지바스크립트의 서로 다른 문법간의 오류를 하나로 통합해줄 바벨의 모듈을 설치
JSX와 ES
 이상의 문법을 다른 브라우저가 이해할 수 있도록 ES5 문법으로 변환시켜줌
 빌드 시에만 필요하기 때문에 devDependencies로 설치</p>
<ul>
<li>@babel/core: 바벨의 핵심 기능을 담당</li>
<li>@babel/preset-react: 리액트의 JSX를 자바스크립트 코드로 변환</li>
<li>@babel/preset-env: ES6 이상의 문법을 ES5로 변환해주는 프리셋</li>
</ul>
<h4 id="3-webpack-설치">3. Webpack 설치</h4>
<p><code>npm install --save-dev webpack webpack-cli webpack-dev-server html-webpack-plugin css-loader style-loader</code></p>
<pre><code>// webpack.config.js
const HtmlWebpackPlugin = require(&quot;html-webpack-plugin&quot;);
const path = require(&quot;path&quot;);

module.exports = {
  entry: &quot;./src/index.tsx&quot;,
  output: {
    filename: &quot;main.js&quot;,
    path: path.resolve(__dirname, &quot;dist&quot;),
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: { loader: &quot;babel-loader&quot; },
      },
      { test: /\.css$/, use: [&quot;style-loader&quot;, &quot;css-loader&quot;] },
    ],
  },
  resolve: {
    extensions: [&quot;.js&quot;, &quot;.jsx&quot;],
  },
  plugins: [new HtmlWebpackPlugin({ template: &quot;./public/index.html&quot; })],
  devServer: {
    static: {
      directory: path.join(__dirname, &quot;dist&quot;),
    },
    hot: true,
    open: true,
  },
};</code></pre><ul>
<li>webpack-dev-server: 개발 모드에 필요한 서버를 구동해줌. 컴퓨터의 메모리를 빌려 웹팩을 작동시키고 임시 서버를 띄움</li>
<li>css-loader, style-loader: css 문법을 자바스크립트로 변환해주는 역할 css-loader가 변환한 파일을 index.html의 &#39;<style>&#39; 태그에 넣는 역할</li>
<li>html-webpack-plugin: html 파일에 번들링된 리액트 코드를 삽입해줌. dist 폴더에 번들링된 파일을 옮겨주는 역할</li>
<li>clean-webpack-plugin: 번들링이 완료될 때마다 이전의 번들링 결과를 제거해주는 역할</li>
</ul>
<h3 id="4-기타-파일-작성">4. 기타 파일 작성</h3>
<pre><code>my-app/
  ├── public/
      ├── index.html
  ├── src/
      ├── App.js
      ├── index.js
  ├── .babelrc
  ├── package.json
  ├── webpack.config.js</code></pre><pre><code>// public/index.html
&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
  &lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot;&gt;
    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;
    &lt;title&gt;My Custom React App&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;div id=&quot;root&quot;&gt;&lt;/div&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre><pre><code>// src/index.js
import React from &#39;react&#39;;
import ReactDOM from &#39;react-dom&#39;;
import App from &#39;./App&#39;;

ReactDOM.render(&lt;App /&gt;, document.getElementById(&#39;root&#39;));</code></pre><pre><code>// src/App.js
import React from &#39;react&#39;;

const App = () =&gt; {
  return &lt;div&gt;Hello, word!&lt;/div&gt;;
};
export default App;</code></pre><h4 id="5-스크립트-등록">5. 스크립트 등록</h4>
<pre><code>  &quot;scripts&quot;: {
  &quot;start&quot;: &quot;webpack serve --progress --mode development&quot;,
  &quot;build&quot;: &quot;webpack&quot;
} </code></pre><p><code>npm start</code></p>
<h1 id="vite">Vite</h1>
<p>  Go언어로 작성된 ESbuild를 번들러로 사용하여, 사전 번들링이 필요한 코드에만 적용됨. 소스코드에는 영향을 받지 않고 브라우저에서 구동시 불필요한 자원을 사용하지 않음(그래서 빠르다.)</p>
<hr>
<p>그치만 아직 CRA가 오랫동안 사용된만큼 검증된 모듈과 안정적인 절차를 가지고 있다. 아직 vite의 기술의 일부는 불안정성을 가지고 있음. So, 아직은 가벼운 사이드 혹은 토이 프로젝트에 vite를 유용하게 사용하고, 정규 프로젝트는 CRA를 사용하는 편.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[엘리스 AI 8기 회고]]></title>
            <link>https://velog.io/@a-honey/%EC%97%98%EB%A6%AC%EC%8A%A4-AI-8%EA%B8%B0-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@a-honey/%EC%97%98%EB%A6%AC%EC%8A%A4-AI-8%EA%B8%B0-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Tue, 07 Nov 2023 14:06:36 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/a-honey/post/99b1f480-f12b-45d9-9ed1-747f34eebc41/image.png" alt=""></p>
<p>나는 3월에 본격적으로 공부를 시작했고, 4월에 지원해서 5월에 엘리스 AI 8기를 시작했다. 3월 초까지는 자바스크립트랑 자바랑 뭔 차인지도 모르고 그냥 프론트 공부해야지 하면서 자바 책을 샀고<del>(이건 내가 생각해도 최악임)</del> 기초도 없으면서 스프링 강의를 듣다가, 파이썬 강의를 듣다가 여기저기 OT만 듣고 다른거 찾아다니고 그랬다. 그러다가 코드잇에서 분야별 로드맵을 보고 3월 19일부터 한달간 코드잇을 끊어놓고 실습없이 이론강의만 냅다 2배속으로 달렸다. 초반에 열정이 강한 편인 점과, 일단 길이 보여야 제대로 시작하는 편이라는 걸 이미 고딩때 많이 겪었고 현우진으로 익히고 유튜브로 다져진 2배속 듣기 왕이었기 때문에 한달동안 기초적인 프론트, 백엔드는 훑을 수 있었다. 필기만 약 60개인걸보면 한 강의 40개는 들은거같다. 한 2~3주차쯤에 커리큘럼이 있으니까 뭔가 걱정없이 진도만 뺄 수 있길래, 온라인 부트캠프를 찾아보다가 엘리스에 지원하게 되었다. 사실 처음부터 프론트를 생각하고 있었기 때문에 SW 트랙과 AI 트랙에서 고민을 좀 했는데, 나는 빽빽한 강의 커리큘럼보다 꾸준한 자극과 가이드가 필요했기 때문에 6개월 과정인 AI 트랙을 선택했다. 6개월만에 AI까지 어떻게 다 배우냐고 비추천하는 글들도 많았는데, 받아들이기 나름인 것 같다. AI로 취업을 하겠다는 좀 어렵고 본인 노력이 더 많이 필요한게 사실이지만, AI를 찍먹해보겠다는 마인드는 뭐 나쁘지 않다. 사실 나는 AI 강의 안듣다가 마지막에 이미지 처리에 호기심이 생겨서 나중에 플러터를 배우겠다고 결심하기도 했다. 오히려 AI에 대해 가능성을 열게 되어서 만족스러운 선택이었다.</p>
<p><img src="https://velog.velcdn.com/images/a-honey/post/04c8f30b-9cdc-4444-8659-25441f287245/image.png" alt=""></p>
<p>사실 난 실시간 강의를 열심히 듣지 않았다. 첫주에 HTML/CSS 강의를 들으면서 강의는 너무 훌륭하지만 초보 대상이다보니 이미 훑으면서 아는 내용이어서 집중력이 떨어졌다. 그래서 커리큘럼만 맞춰가자는 마음으로 실시간 강의 시간에 모던 자바스크립트 딥다이브를 읽었다. 강의만 급하게 나가느라 많이 부족했던 기초를 쌓을 수 있었고, 엘리스의 온라인 강의와 주간 테스트를 통해서 내 실력을 판단할 수 있었다. 그러다가 리액트를 배우길래 리액트를 다루는 기술이라는 책을 보면서 프로젝트 구조를 익힐 수 있었고, 혼자서 국립중앙박물관 클론 코딩을 했고 무한 슬라이드를 구현하려고 밤새가면서 재미를 느꼈다. 이때 대충 프론트 프로젝트가 돌아가는 방식을 익힐 수 있어서 프로젝트 1차는 나름 혼자서 애써서 완성할 수 있었다. 헤더에 토큰 넣을때 띄어쓰기 하나 더 들어가서 나혼자 개고생했던거 아직도 잊지 못한다..</p>
<p> 백엔드와 데이터베이스는 수업을 들었었던 것 같은데, 그래서 기억이 안난다. 열심히 들은건 기억안나고 딴짓한것만 기억남..ㅎ 이때 Next로 보노보노 PPT를 닮은 색감 쨍쨍한 웹사이트도 만들었다. 그렇게 2차 프로젝트를 시작했고, 여기서도 프론트는 나 혼자였다. 2차에서는 처음부터 끝까지 내가 참여했고, 처음에 생각한대로 결과물이 나와서 만족스러웠다. 갈등이 있긴 했지만 프론트를 내 힘으로 다 짤 수 있었고, 잘 짜서 좋았다. 이때서야 진짜로 프론트엔드 개발자가 되겠다고 마음을 잡았던 것 같다. 그렇게 AI는 실시간 강의도, 온라인 강의도 듣지 않았다.. 2차 프로젝트 백엔드분과 웹소켓을 구현했고, 혼자서 뷰 프로젝트를 진행했고, 내 디자인으로 충격받은 웹사이트도 탬플릿 사용해서 다시 만들었다. 그러다가 자연어/이미지 처리 결정할 때쯤 이미지 처리팀에 들어갔고, 어쨌든 3차가 우려되어 실시간 강의에 참여했다가 openCV의 매력을 느낄 수 있었다. 하지만 막주차라 그대로 안녕..</p>
<p><img src="https://velog.velcdn.com/images/a-honey/post/7f8b94ca-ffdf-4ef1-a297-fc8a8a465586/image.png" alt=""></p>
<p> 3차 프로젝트에서도 역시나 프론트는 없었다. 왜 다들 프론트의 멋짐을 모르는 걸까! 배우는 입장에서 혼자하면 좋긴 한데, 뭔가 프론트를 안하는 마음이 궁금하다. 웹사이트로 그림그리는거 너무 재밌지 않나. 아무튼 한분이 같이 프론트해주시긴 했지만 바쁘셔서 그냥 협업보단 분업이었다. 딱히 안하겠다는 사람붙잡고 싶지도 않아서 그냥 했다. 이번 프로젝트 나혼자 십만줄 넘었다. 즐거운 코딩 journey :d 3차 프로젝트를 하면서 react-query를 이해할 수 있었다. 그래도 6개월동안 형식적으로 신입 프론트엔드 개발자에게 요구하는 스택은 다 쌓을 수 있었다. 사실 지금 취업을 하면 실력이 확실히 늘 수 있을것같은데, 업계 상황도 그렇게 좋지 않고 내년에 졸업 안하면 진짜 2년차에 발목잡을게 뻔해서 다시 학교를 갈 거같다. 벌써 괴로움.. <del>아무도 나한테 말 안걸어줬으면 좋겠다. 교수님도</del></p>
<hr>
<h2 id="엘리스-좋았던-점">엘리스 좋았던 점</h2>
<p>내가 엘리스로 결정한 이유는 크게 두가지였다.</p>
<ul>
<li>온라인 강의로 진행됨</li>
<li>성수 오프라인 강의실을 자유롭게 사용할 수 있음</li>
</ul>
<p>우당탕탕 한달이었지만 어쨌든 기본적인 기초는 알고 있었기 때문에, 온라인 강의에 집중하지 않을 것을 예상하고 있었다. 그래서 딴 짓을 해도 들키지 않아야했고, 코로나 학번으로 3학년때 처음 학교 1년 갔다가 그대로 휴학길에 올랐기 때문에 대면이 무서웠다. 그럼에도 언제든지 집을 벗어날 수 있는 오프라인 강의실이 있는게 좋았다. 실제로 1차 프로젝트 이후에는 집에 있으면 안하게 되니까 계속 나갔다. 심지어 첫날엔가 주먹밥과 오렌지 주스를 주셔서 나한테 성수 오프라인 강의실이 굉장히 &#39;좋은 곳&#39;으로 인식되는 바람에 그냥 아무생각없이 계속 나갔다. 진짜 이거 지금생각하면 너무 어이없는 마인드인데, 친구들이 아무도 의심안하고 그저 나답다고 했다. 도대체 얘네한테 나는 몰까..? 일단 출발하는게 힘들지 앉고 나면 밤 열시까지 풀집중할 수 있어서 좋다. 그리고 매니저님이 계시는게 생각보다 너무 좋았다. 오프라인 강의실을 나가기 시작하면서 조금씩 만나게 되었는데 그때마다 응원많이 해주셨다. 내가 뚝딱이여서 죄송할뿐,, 결석 19일했지만 일단 어떻게든 수료하게 해주신다고 하는 매니저님,,</p>
<ul>
<li>오피스아워</li>
</ul>
<p>그리고 오피스아워가 큰 도움이 됐다. 사실 1차, 2차에서는 거의 질문 빌런이었는데 답변 다해주시고 이해시켜주시고 큰 도움을 받았다. 지금 생각해보면 황당한 짓도 많이 했는데, 친절하게 받아주셨다. 한 프로젝트에 상태관리 라이브러리 아는거 다 설치해서 쓰는 나를 보면서 무슨 생각을 하셨을까 싶음. 그덕에 프론트 돌아가는 원리를 그래도 이해할 수 있었다. 3차에서는 그 이해를 바탕으로 개선점에 대해서 도움을 많이 받을 수 있었다. 프로젝트 폴더 구조를 어떻게 짜면 좋을지, 타입을 왜 쓰는지 등등. 디자인 패턴도 하나 소개해주셨다. 이때 사실 혼자 PMS로 이 다음엔 뭘해야하나 좀 막막해서 우울했는데, 이런 디자인 패턴을 보면서 좀 나아갈 방향을 잡을 수 있었다. 사실 1차, 2차에서 혼자서 질문으로 한시간 뚝딱이었기 때문에 3차에서도 일단 질문을 몰아쳐서 했는데, 댓글 답변 공격으로 받아쳐주셨다. 2주차까지 질문 공격으로 맞대응하다가 3주차쯤에 질문거리가 생각안나서 결국 내가 지고 말았다. 아무튼,, 오피스아워짱이다.</p>
<ul>
<li>3번의 프로젝트</li>
</ul>
<p>부담스러워서 도망치고 싶었던 적도 있고, 갈등도 조금 있었지만, 어쨌든 다같이 으쌰으쌰해서 만드는 프로젝트는 재밌었다. 다같이 밤새고 있으면 뭔가 든든하고 같이 하고 있다는게 느껴지면 더 열심히하고 싶은 마음이 커졌다. 같이 포스팅 스터디하시는 분들은 얼굴을 봐서 뭔가 더 편한 느낌이 들기도 한다. 다른 기수보면서 부러우면서도 음 저럴시간에 공부하는게 낫지 않나 왜 친목하지라는 마음이 조금 있었는데, 친목을 하면서 더 돈독해지는게 확실히 있는거같다. 그러려면 사회성 스킬을 길러야한다..주륵..</p>
<h2 id="부트캠프-고민한다면">부트캠프 고민한다면</h2>
<p>일단 내가 제일 잘한게 한달동안 강의 빡세게 듣고 엘리스 참여한거다. 내가 실시간 강의를 열심히 듣지 않긴 했지만, 일단 틀어놓으면 부족한 부분이나 대충하고 넘어갔던 키워드들이 들린다. 그럼 그땐 강의를 듣고, 아는 부분이면 다시 내 공부를 할 수 있다. 그리고 일단 기초가 없으면 이해안가는 본인이 제일 힘들다. 이렇게 했어도 처음에 프로젝트 셋팅하라고 하면 멘붕오는데, 다 처음이면 진짜 포기하고 싶어진다. 최대한 이론 공부를 많이 해서 여유있게 공부하길 바란다. 그리고 본인의 분야를 정하고 오면 좋을 것같다. 6개월동안 다른 분야의 진도를 나가더라도 계속 혼자 프론트에 발을 걸치고 있었기 때문에 3차에서는 꽤나 완성도 있는 프론트 퀄리티가 나왔다. 6개월만에 프론트, 백, AI를 다 하는건 불가능하지만 한 분야를 6개월동안 끌고 가는 동시에 여러 분야를 찍먹해보는건 좋은 경험인 것 같다. 그리고 가장 중요한건, 부트캠프든 책이든 강의든 &#39;수단&#39;이라는 걸 알아야한다. 국비지원, 부트캠프, 독학 장단점 비교하는 걸 나도 진짜 많이 보고 걱정도 많이 했는데 그걸로 절대 결정 안 난다. 같은 기수 안에서도 직장 병행하느라 크게 참여 못하는 사람/ 개발에 관심 크지 않은 사람/ 전공한 사람 등등 상황 다 다르고 그냥 본인 하기에 달렸다. 그래서 그냥 본인이 여기서 얻어가고 싶은 것을 확고하게 정하고 목표만 바라보면 좋을것같다. </p>
<h2 id="나-이제-뭐하지">나 이제 뭐하지</h2>
<p>개발이란건 할껀 많은데 뭘 해야할지 몰라서 생기는 막막함이 늘 있다. 지금이 그 상태. nest, graphQL을 좀 건들여볼까, Next를 더 써볼까, 자바 공부 조금 해서 코드 철학을 알아볼까, 자바스크립트 이론을 다시 공부할까. 코딩테스트를 준비할까. 마지막 프로젝트 내내 고민하고 코치님한테도 DM으로 물어보고 했는데 결국 답은 없는거같다. 사실 나도 고민해봤자 그때그때 내 맘대로 할거 다 안다. 그냥 막막하다고 찡찡거리면서 아무것도 안 하고싶은거지 뭐. 걍 막막해지면 오은영 박사님 빙의해야됨 
다 울었니? 이제 할 일을 하자.
<img src="https://velog.velcdn.com/images/a-honey/post/2ed29e92-277a-40d0-9f92-a66ebe5c1eca/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Emotiary 회고]]></title>
            <link>https://velog.io/@a-honey/Emotiary-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@a-honey/Emotiary-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Fri, 03 Nov 2023 13:58:06 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/a-honey/post/4ca7bb8e-742f-4b4a-bd0f-be1924dd8ee1/image.png" alt=""></p>
<p>이번 프로젝트 목표: react-query 정복
한 것: 타스한테 제발 에러나지 말라고 빌기</p>
<hr>
<p>사실 그렇게 애착이 가는 프로젝트는 아니였다. <del>근데 혼자 코드 122,427 ++, 53,746 --..</del> 일단 AI가 핵심이다보니 메인 서비스에서 프론트가 할 수 있는게 없었다. 프론트단의 기능같은 경우에도 그냥 친구 요청, 수락, 거절/ 모든 유저 조회 및 탭별 조회/ 채팅기능 정도였는데, 이미 2차 프로젝트에서 경험한 것들이어서 엄청나게 신선하지 않았다. 특히 채팅은 프로젝트 끝나고 진행하면서 엄청나게 신기하고 재밌었는데, 이번에는 그냥 대충 예상이 가니까 그냥 컴포넌트 몇개 더 만드는 수준이었다. 그럼에도 이 프로젝트가 나한테 의미가 큰건, 아는 기능이다보니 개선을 많이 할 수 있었고 그에 대한 고민을 많이 했다. 특히 지난 프로젝트에서는 모르는 것에 대해 질문하기 바빴는데, 이번에는 프로젝트 구조나 네이밍 등 개선할 방안에 대해 많이 알아볼 수 있었다. 앞으로 뭘 해야하는가에 대한 고민도 컸고. 약간 2차에서 마음껏 펼쳐보고, 여러개 이미지, 프로필 이미지나 데이터를 초기화하는 등 3차에서 좋게 다듬은 느낌이다.</p>
<h2 id="react-query를-써보자">React-query를 써보자</h2>
<p>지난 프로젝트에서는 레이아웃 단계에서 모달로 글을 작성하는 바람에, 글 목록 데이터가 담겨있는 컴포넌트의 업데이트 함수를 끌고 올 수가 없었다. 어쩌면 전역상태관리를 통하거나, 커스텀 이벤트를 통해 가능할 수 있었겠지만, 마감직전이어서 구현하지는 못했다. 그러다가 차라리 모든 데이터를 저장할 수 있으면 좋겠다고 생각할 수 있었고, 그래서 react-query를 사용하기로 했다. 어쨌든 캐싱을 하면 뭐가 됐든 성능은 좋아지겠다 싶어서 결정했고, 이 리액트쿼리=캐싱이라는 잘못된 생각이 계속 나를 잡았지만 확실히 써보면서 익숙해지면서 좋은 걸 느꼈다. 생각보다 캐싱 외에도 편리함이 컸다. 로딩상태를 만들어주지 않아도 되는거나, 윈도우 포커스했을때 바로 리페치해주는게 UX적으로 좋은 것 같다. 특히 무효화 시점에 대해서 queryClient를 무조건 컴포넌트에서 실행하고 useMutation에 전달해야한다고 생각한거나, 내가 무효화시켜놓고 왜 다 무효화되는건가에 대한 의문으로 고생을 했는데, 코드를 하나씩 읽어나가면서 무효화 시점을 직접 생각해서 지정하고 컴포넌트 내 언마운트시점으로 바꿔서 오히려 좋게 할 수 있었다. 사실 아직 모르는 옵션들도 많이 있는듯한데, 그건 차차 불편함을 겪으면서 알아볼거다. 단점은 뭔가 api 요청의 통제권이 나에게서 사라진 느낌?</p>
<h2 id="구조-네이밍은-귀찮아도-생각하고-가자">구조, 네이밍은 귀찮아도 생각하고 가자</h2>
<p>useQuery같은 경우는 /src/get에 다 넣었고, useMutation은 어떻게 할까 생각하던 중, src/mutation에 다 넣었다. 당연히 post, put, delete를 전부 다 넣었는데, 이게 점점 뒤로 갈수록 많아져서 힘들었다. 중반 즈음에 결국 구분이 필요하다고 생각해서 /src에 get, post, put, delete 함수를 넣었다. 사실 1차나 2차때는 구조에 대한 생각을 전혀 안했는데, 3~4주차에 1주차의 내가 구조를 어떻게 잡았는지 기억이 안나서 그냥 무한 토글을 했다. 무한 토글의 늪에 빠지고 나니까 프로젝트 구조를 처음부터 잘 잡고 가야한다는걸 알아버렸다.. 특히 src/pages에 페이지별로 나누고 그안에 컴포넌트, 스타일, 테스트 다 집어넣고 페이지명.컴포넌트명.tsx로 파일이름을 설정해보니 찾기가 훨씬 편했다. 인생이 둔감한 나한테 너무 어렵지만 불편함을 계속 인지하고 해결해야할거같다.. 사실 지금 이거 때문에 디자인 패턴에 관심이 가기 시작했다. 코치님이 컴포넌트 컴포넌트 디자인 패턴을 알려주셨는데, 이제 웹 화면을 할 수 있는 단계 이후에는 코드에 철학을 담는게 실력이 되는 것 같다. 내가 recoil보다 redux를 좋아하는 것처럼, 누군가는 철학이 담긴 내 코드를 더 좋아하겠구나 싶다. 하지만, 디자인 패턴은 일단 자바가 진입장벽이다.. 코드 철학에 대한건 다 자바라서 눈물난다.</p>
<h2 id="완벽한-서비스에-대한-집착을-버리자">완벽한 서비스에 대한 집착을 버리자</h2>
<p>서비스를 만들겠다고 기획을 들어가게 되면 여러가지 기능을 생각하게 되고, 비밀번호 변경/ 탈퇴 등 보수적인 기능들도 당연히 넣게 된다. 하지만 사실 그게 없다고 해도 괜찮은 거같다. 오히려 메인 서비스를 흐리게 된다. 사실 여기에 대해서는 의문이긴 하다. 애초에 내가 감당할 수 있는만큼 기능을 넣었고, 사실 다 감당했으니까 별문제 없는거 아닌가 싶다가도, 기능을 많이 넣은만큼 내 시선을 벗어난 api가 생겨버리니까 문제였다. 백엔드 한분한분같은 경우는 라우터 완성하면 된거지만, 프론트 입장에서는 전체 백엔드분들이 끝나야 api 하나 하나를 확인을 할 수 있어서 어려웠다. 메인 기능 api가 완성되니까 댓글 api는 거의 붙여만 놓은 수준이다. 특히 put, delete같은 경우는 생각을 안하면 영원히 안하게 되니까 지금도 내가 뭘 안했는지 잘 모르겠다. 2차때 이걸 느껴놓고 인정안하고 갔는데, 뼈져리게 체감했다. 뒤늦게 애자일 방법론에 대해 알아가면서 결국에 완벽한 서비스는 없고 시간을 들여서 완벽한 서비스로 나아가야한다는 걸알았다. 결국에 무한 개선이 답일 뿐이다.</p>
<h2 id="나도-백을-알아야한다">나도 백을 알아야한다</h2>
<p>res.data가 객체 안에 객체 안에 배열안에 내가 원하는 데이터가 있길래, 그냥 내가 원하는 데이터만 주면 안되나 하는 생각이 항상 있었다. 이런 점들은 GraphQL적으로 생각해서 그런거같다. 그리고 내가 db에 어디에 있고, 누구와 관계가 연결되어있는지 알아서 api 요청할때 어디의 어떤 요청을 주라고 말할 수 있어야하는 것 같다. 그리고 id로 조인하고 조인해서 두개의 테이블로 가져오는게 생각보다 딜레이가 있었다. 말은 하지 않았지만, 이런 경우에는 그냥 api를 하나 더 만들어서 요청을 비동기로 하는게 나았을거같다. 이런 점들이 프론트가 백을 알아야하는 이유다. 애초에 SDD했다면 이런 일이 없었기 때문에 데이터베이스 공부는 해야한다. 뭐 그냥 어쨌든 나는 백에서 주는 데이터를 요리하는 입장이기 때문에, 재료에 대해서는 알아야한다. 하지만 스프링은 싫으니까 QL해야지. 그리고 웹소켓하면서 느낀건데, 더 깊게 안 배우는건 상관없지만 어쨌든 다룰 줄은 알아야한다. 솔직히 QL도 그렇고 흥미로운 기능들은 백과 프론트의 연결에서 나오는것같다. 네트워크 지식도 필요하겠지만,, 프론트만 하는것도 아직 벅차지만,,</p>
<h2 id="방법론도-배우자">방법론도 배우자</h2>
<p>매일 11시에 스크럼을 진행했는데, 스크럼에 참여하면서도 이걸 왜하는지 이해가 안갔다. 사실 실시간으로 하고 있는걸 매번 커밋으로 남기다 보니, 굳이 말로 해야나 의문이 들고 커밋을 안 남겼는데 왜 했다고 하는건가 싶었다. 여지껏 했다, 하겠다 해놓고 결국 안한분들이 많았기 때문에 안 보이면 안 믿게 되는 내 문제도 있었다. 애초에 모든 api가 나를 향하고 있으니까, 나는 진행상황을 다 알고 있어서 나혼자 공유의 필요성을 못느낀걸수도 있다. 그러다가 진짜 애자일 스크럼을 진행하는 이유와 방식을 알아보면서 스크럼의 필요성을 알게되었다. 여지껏 내가 한건 그냥 어제 놀지않았음을 증명하는 수단이었다. 스프린트와 백로그, 그에 대한 관리자가 없었기 때문에 그럴 수밖에 없었다. 목표가 없다보니 진행상황 공유에서만 그쳤고, 그 다음 방향에 대한 이야기를 할 시간이 없었다. 저번주엔가 개발자들 사이에선 PM이 베짱이로 보인다는 글을 보고 웃었는데, 기획자는 연결을 위해 꼭 필요한 존재였다.. 원래는 이번 프로젝트 후기도 애자일 방법론을 배워서 애자일 방식의 회고를 진행하고 싶었는데, 각잡고 책읽을 시간이 없어서 포기했다. 언젠간 꼭,,?</p>
<h2 id="emotiaryemotion--diary">Emotiary(Emotion + Diary)</h2>
<p>🔗 레포: <a href="https://github.com/a-honey/emotiary">https://github.com/a-honey/emotiary</a>
🔗 위키: <a href="https://github.com/a-honey/emotiary/wiki">https://github.com/a-honey/emotiary/wiki</a></p>
<p><img src="https://velog.velcdn.com/images/a-honey/post/a86aaecf-047f-4cf1-a07b-748e02745d27/image.gif" alt=""></p>
]]></description>
        </item>
    </channel>
</rss>