<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>dev__note.log</title>
        <link>https://velog.io/</link>
        <description>개발하며 얻은 인사이트들을 공유합니다.</description>
        <lastBuildDate>Fri, 09 Sep 2022 16:25:49 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>dev__note.log</title>
            <url>https://velog.velcdn.com/images/dev__note/profile/cdf60b53-7705-4cb6-85df-68e9d872debf/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. dev__note.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dev__note" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[비동기로 전환하기 ]]></title>
            <link>https://velog.io/@dev__note/%EB%B9%84%EB%8F%99%EA%B8%B0%EB%A1%9C-%EC%A0%84%ED%99%98%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dev__note/%EB%B9%84%EB%8F%99%EA%B8%B0%EB%A1%9C-%EC%A0%84%ED%99%98%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 09 Sep 2022 16:25:49 GMT</pubDate>
            <description><![CDATA[<h2 id="프롤로그">프롤로그</h2>
<p>비동기로 바꿔보자 XMLHttpRequest 에서 Promise 함수를 이용, 그리고 마지막에는 async-awiat 를 사용하는 비동기 함수를 사용해서 단계별로 비동기 함수로 바꿔 나가는 과정을 기록하고자 한다. 전체 코드보다 api.ts 파일 부분을 주로 기록했다. </p>
<h3 id="1-비동기-옵션---이벤트-핸들러-load">1. 비동기 옵션 - 이벤트 핸들러 &#39;load&#39;</h3>
<p>비동기 코드로 가져올 수 있도록 코드를 바꿔주기 위해 이벤트 핸들러를 사용해보자. return 값을 받아 줄 수 있는 곳이 없으므로 callback 함수를 써서 받아줘야 한다. </p>
<pre><code>export class Api {
  ajax: XMLHttpRequest;
  url: string;

  constructor(url: string) {
    this.ajax = new XMLHttpRequest();
    this.url = url;
  }

  getRequest&lt;AjaxResponse&gt;(cb: (data: AjaxResponse) =&gt; void): void {
    this.ajax.open(&quot;GET&quot;, this.url);
    // 이벤트 리스너를 등록
    this.ajax.addEventListener(&quot;load&quot;, () =&gt; {
      cb(JSON.parse(this.ajax.response) as AjaxResponse);
    });

    this.ajax.send(); //데이터를 가져옴
  }
}

//cb 함수를 받아 넘겨줘야 한다. 실제로 사용하는 부분은 view 쪽
export class NewsFeedApi extends Api {
  constructor(url: string) {
    super(url);
  }
  getData(cb: (data: NewsFeed[]) =&gt; void): void {
    return this.getRequest&lt;NewsFeed[]&gt;(cb);
  }
}

export class NewsDetailApi extends Api {
  constructor(url: string) {
    super(url);
  }

  getData(cb: (data: NewsDetail) =&gt; void): void {
    return this.getRequest&lt;NewsDetail&gt;(cb);
  }
}

</code></pre><p>이제 실제 사용하는 view 부분 
<strong>newsdetail</strong></p>
<pre><code>
render = (id: string): void =&gt; {
    const api = new NewsDetailApi(CONTENT_URL.replace(&quot;@id&quot;, id));

    // const newsDetail: NewsDetail = api.getData();
    //return 으로 인자를 받을 수 없으니 인자에게 함수를 제공한다.
    api.getData((data: NewsDetail) =&gt; {
      const { title, content, comments } = data;

      this.store.makeRead(Number(id));
      this.setTemplateData(&quot;comments&quot;, this.makeComment(comments));
      this.setTemplateData(&quot;currentpage&quot;, String(this.store.currentPage));
      this.setTemplateData(&quot;title&quot;, String(title));
      this.setTemplateData(&quot;content&quot;, String(content));

      this.updateView();
    });
  };</code></pre><p><strong>newsfeed</strong>
detail 부분과 다르게 생성자에서 api 호출하고 있다. render 라고 하는 함수는 라우터가 호출하므로 데이터가 왔을지 안왔을지 보장이 안됨! 그래서 생성자에서 api 호출하는 부분들을 render 로 옮겨와줘야 한다. </p>
<pre><code>
 render = (page: string = &#39;1&#39;): void =&gt; {
    this.store.currentPage = Number(page);

    if (!this.store.hasFeeds) {
      this.api.getDataWithPromise((feeds: NewsFeed[]) =&gt; {
        this.store.setFeeds(feeds);
        this.renderView();
      });
    }

    this.renderView();
  }

// 따로 분리 해 주었다.
  renderView = () =&gt; {
    for(let i = (this.store.currentPage - 1) * 10; i &lt; this.store.currentPage * 10; i++) {
      const { id, title, comments_count, user, points, time_ago, read } = this.store.getFeed(i);

      this.addHtml(`
        &lt;div class=&quot;p-6 ${read ? &#39;bg-red-500&#39; : &#39;bg-white&#39;} mt-6 rounded-lg shadow-md transition-colors duration-500 hover:bg-green-100&quot;&gt;
          &lt;div class=&quot;flex&quot;&gt;
            &lt;div class=&quot;flex-auto&quot;&gt;
              &lt;a href=&quot;#/show/${id}&quot;&gt;${title}&lt;/a&gt;  
            &lt;/div&gt;
            &lt;div class=&quot;text-center text-sm&quot;&gt;
              &lt;div class=&quot;w-10 text-white bg-green-300 rounded-lg px-0 py-2&quot;&gt;${comments_count}&lt;/div&gt;
            &lt;/div&gt;
          &lt;/div&gt;
          &lt;div class=&quot;flex mt-3&quot;&gt;
            &lt;div class=&quot;grid grid-cols-3 text-sm text-gray-500&quot;&gt;
              &lt;div&gt;&lt;i class=&quot;fas fa-user mr-1&quot;&gt;&lt;/i&gt;${user}&lt;/div&gt;
              &lt;div&gt;&lt;i class=&quot;fas fa-heart mr-1&quot;&gt;&lt;/i&gt;${points}&lt;/div&gt;
              &lt;div&gt;&lt;i class=&quot;far fa-clock mr-1&quot;&gt;&lt;/i&gt;${time_ago}&lt;/div&gt;
            &lt;/div&gt;  
          &lt;/div&gt;
        &lt;/div&gt;    
      `);
    }  

    this.setTemplateData(&#39;news_feed&#39;, this.getHtml());
    this.setTemplateData(&#39;prev_page&#39;, String(this.store.prevPage));
    this.setTemplateData(&#39;next_page&#39;, String(this.store.nextPage));

    this.updateView();
  }
</code></pre><h3 id="에러-봉착">에러 봉착!</h3>
<p>왜 id 값을 못 가져오지?? 
api 문제인것 같은데 강사님 코드와 비교해봐도 해결실마리를 찾지 못했다 ㅜㅜ
<img src="https://velog.velcdn.com/images/dev__note/post/16f63685-ecf2-4a6a-bd6f-d754d5637e46/image.png" alt=""></p>
<h3 id="2-promise-버전으로-변경">2. promise 버전으로 변경</h3>
<p>XMLHttpRequest     를 promise 버전으로 바꾸어 보았다. fetch 함수도 사용했는데 차세대 버전이라고 한다. (<strong>둘 차이를 비교!</strong>) </p>
<pre><code>getRequestWithXHR&lt;AjaxResponse&gt;(cb: (data: AjaxResponse) =&gt; void): void {
    this.xhr.open(&quot;GET&quot;, this.url);
    // 이벤트 리스너를 등록
    this.xhr.addEventListener(&quot;load&quot;, () =&gt; {
      cb(JSON.parse(this.xhr.response) as AjaxResponse);
    });

    this.xhr.send(); //데이터를 가져옴
  }

  // callback 지옥을 막기 위해 나온 promise
  getRequestWithPromise&lt;AjaxResponse&gt;(cb: (data: AjaxResponse) =&gt; void): void {
    // 새로운 api, 차세대 api - xhr 을 대응하는 새로운 api. promise 베이스의 api
    //xhr 의 json.parse 는 동기적으로 작동 - 데이터가 커지면 ui 가 멈추게 된다.
    //반면 fetch 는 json 자체를 비동기적으로 객체로 바꾸는 기능을 제공함으로써 해결을 하고 있음
    fetch(this.url)
      .then((response) =&gt; response.json())
      .then(cb)
      .catch(() =&gt; {
        console.error(&quot;데이터를 불러오지 못했습니다.&quot;);
      });
  }</code></pre><h3 id="3-콜백-함수-없는-비동기-코드-작성법">3. 콜백 함수 없는 비동기 코드 작성법</h3>
<p>비동기코드를 어떻게 하면 더 효율적으로 사용 할 수 있을까? 에서 promise 메커니즘을 생각 해 내었고, 이제 더 나아가 async,awiat 를 사용하는 비동기 함수를 이용해서 __ 내부적인 메커니즘은 콜백처럼 작동을 하는 비동기 코드임에도 불구하고, 코드 상으로는 완전한 동기체계로써 보이게 작성을 할 수 있는 문법 체계__ 로 발전되었다고 한다. 비동기 함수 또한 Promise 메커니즘을 바탕으로 작성된 코드여서 Promise 메커니즘에 대한 공부가 필요하다.</p>
<pre><code>import { NEWS_URL, CONTENT_URL } from &quot;../config&quot;;
import { NewsFeed, NewsDetail } from &quot;../types&quot;;

export class Api {
  xhr: XMLHttpRequest;
  url: string;

  constructor(url: string) {
    this.xhr = new XMLHttpRequest();
    this.url = url;
  }

  async request&lt;AjaxResponse&gt;(): Promise&lt;AjaxResponse&gt; {
    const response = await fetch(this.url);
    return (await response.json()) as AjaxResponse;
  }
}


export class NewsFeedApi extends Api {
  constructor() {
    super(NEWS_URL);
  }

  async getData(): Promise&lt;NewsFeed[]&gt; {
    return this.request&lt;NewsFeed[]&gt;();
  }
}

export class NewsDetailApi extends Api {
  constructor(id: string) {
    super(CONTENT_URL.replace(&quot;@id&quot;, id));
  }
  async getData(): Promise&lt;NewsDetail&gt; {
    return this.request&lt;NewsDetail&gt;();
  }
}
</code></pre><h3 id="결과-화면">결과 화면</h3>
<p><img src="https://velog.velcdn.com/images/dev__note/post/2a254194-4b1b-4064-aa14-4c0252f56c09/image.gif" alt=""></p>
<h2 id="에필로그">에필로그</h2>
<p>비동기 함수 부분은 조금 버거웠다. 그래서 Promise 메커니즘, 비동기 함수 부분의 이론을 따로 찾아봤더니 조-금은 이해가 되었다. async-await 패턴도 결국 Promise 메커니즘 기반이라고 하니 잘 익혀 둬야 겠다. 
callback 함수도 잘 몰랐었는데 비동기 함수와 함께 나오는 개념이라는 것을 알게 되었다.중간에 오류가 날때도 있었지만 천천히 보다보면 결국 오타 문제가 많았다. ^^;;... 오타를 내지 않도록 조심해야겠다. <em>허탈..</em> 
꽤 긴 여정이었다. 역시 들을때랑 정리하면서 들을 때랑 집중도가 다르고, 모르는 내용은 두세번 반복하고 이론부분도 보충하니 훨씬 이해도가 높아졌다. 
해커뉴스 클론코딩을 하면서 코드가 무수히 바뀔 수 있다는 것과 어떻게 더 효율적으로 쓰는지 등을 좀 익히게 되었다. 이러한 사이클을 기억하면서 더 연습해봐야겠다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[코드의 구조를 깔끔하게 분리 - 전역상태 관리 ]]></title>
            <link>https://velog.io/@dev__note/%EC%BD%94%EB%93%9C%EC%9D%98-%EA%B5%AC%EC%A1%B0%EB%A5%BC-%EA%B9%94%EB%81%94%ED%95%98%EA%B2%8C-%EB%B6%84%EB%A6%AC-%EC%A0%84%EC%97%AD%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC</link>
            <guid>https://velog.io/@dev__note/%EC%BD%94%EB%93%9C%EC%9D%98-%EA%B5%AC%EC%A1%B0%EB%A5%BC-%EA%B9%94%EB%81%94%ED%95%98%EA%B2%8C-%EB%B6%84%EB%A6%AC-%EC%A0%84%EC%97%AD%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC</guid>
            <pubDate>Tue, 06 Sep 2022 07:18:07 GMT</pubDate>
            <description><![CDATA[<h2 id="프롤로그">프롤로그</h2>
<p>디렉터리를 만들고 적당하게 분산시키고 적절하게 배치함으로써 코드의 구조를 깔끔하게 개선시켜보자 </p>
<h3 id="1-디렉터리">1. 디렉터리</h3>
<p>src : 많이 쓰는 소스코드를 배치하는 용도로 많이 사용한다. 
core : 공통 코드들 (구조가 커져도 공통으로 사용될 코드) 
page : UI 관련
types : interface </p>
<h4 id="파일">파일</h4>
<p>config.ts : 설정파일들 </p>
<h3 id="2-modules">2. modules</h3>
<p>파일을 가지고 오기 위해 모듈이라는 스펙을 사용한다. <code>import</code>, <code>export</code> </p>
<h4 id="indexts">index.ts</h4>
<p>페이지 경로만 기억하고 해당 클래스들을 가지고 오는 방법은 디렉토리 안에 index.ts 파일을 생성 하는 것이다. 
index.ts 에서 해당하는 클래스 파일을 import 해주고 바로 export 해주는 것 </p>
<p>예를들면 이렇다. </p>
<pre><code>export { default as NewsDetailView } from &quot;./news-detail-view&quot;;
export { default as NewsFeedView } from &quot;./news-feed-view&quot;;
</code></pre><p>사용하는 쪽에서는 페이지 하위 디렉토리에 있는 정보에 대해서 기억할 필요가 없음 </p>
<h3 id="2-전역상태-관리">2. 전역상태 관리</h3>
<p>모두가 접근 가능한 전역 공간은 가능하면 쓰지 않는것이 좋다. __window __ 객체는 X </p>
<pre><code>import { NewsStore, NewsFeed } from &quot;./types&quot;;

export class Store implements NewsStore {
  // 외부에서 노출되지 않도록 방어
  private feeds: NewsFeed[];
  private _currentPage: number;

  // 초기화
  constructor() {
    this.feeds = [];
    // 내부에서만 쓰는 경우 _ 를 붙여 이름을 쓴다. 
    this._currentPage = 1;
  }

  // 외부에서 접근 가능하도록 기능을 제공해주자.
  // 내부에서는 함수로 작동하지만 외부에서는 속성처럼 작동하는 getter, setter
  // 내부에서는 함수기 때문에 다른 잘못된 값으로 세팅하거나 혹은 특정한 범위 내의 값으로만 한정시키거나 하는 코드를 삽입해서 방어코드 작성 가능하다.
  get currentPage() {
    return this._currentPage;
  }

  set currentPage(page: number) {
    this._currentPage = page;
  }

  get nextPage(): number {
    return this._currentPage + 1;
  }

  get prevPage(): number {
    return this._currentPage &gt; 1 ? this._currentPage - 1 : 1;
  }

  get numberOfFeed(): number {
    return this.feeds.length;
  }

  get hasFeeds(): boolean {
    return this.feeds.length &gt; 0;
  }

  getFeed(position: number): NewsFeed {
    return this.feeds[position];
  }

  getAllFeeds(): NewsFeed[] {
    return this.feeds;
  }

  setFeeds(feeds: NewsFeed[]): void {
    this.feeds = feeds.map((feed) =&gt; ({
      ...feed,
      read: false,
    }));
  }

  makeRead(id: number): void {
    const feed = this.feeds.find((feed: NewsFeed) =&gt; feed.id === id);

    if (feed) {
      feed.read = true;
    }
  }
}
</code></pre><h2 id="에필로그">에필로그</h2>
<p>비교적 간단한 강의였다. 하지만 .. 사이드 프로젝트 할 때 폴더구조같은게 정말 헷갈렸고 어떤식으로 나눠야 하는지 혼란이었는데 그걸 잡아 주고 이유까지 알게 되서 유용했던 차수 였다. </p>
<p>추가) 뭔가 전역상태에 진짜 저런 사소한 것 까지 함수로 만드나 싶은데 실제코드 짤 때도 저렇게 짜는것일지 궁금해진다. 어쨌든 사용하는 쪽에서는 코드가 깔끔해 지는 것은 맞다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[View class 로 코드 구조 개선 ]]></title>
            <link>https://velog.io/@dev__note/View-class-%EB%A1%9C-%EC%BD%94%EB%93%9C-%EA%B5%AC%EC%A1%B0-%EA%B0%9C%EC%84%A0</link>
            <guid>https://velog.io/@dev__note/View-class-%EB%A1%9C-%EC%BD%94%EB%93%9C-%EA%B5%AC%EC%A1%B0-%EA%B0%9C%EC%84%A0</guid>
            <pubDate>Tue, 06 Sep 2022 04:53:57 GMT</pubDate>
            <description><![CDATA[<h2 id="프롤로그">프롤로그</h2>
<p>앞서 배웠던 class 를 view 클래스로 공통으로 빼야 한다. <code>공통된 목적을 추출</code> 하는게 우선이다. </p>
<h3 id="1-공통된-목적을-추출">1. 공통된 목적을 추출</h3>
<p>NewsFeed 나 NewsDetail 의 함수는 UI 를 업데이트 하는 함수이고 그 외에 makeFeed, updateView 등의 함수들은 UI를 업데이트 함에 있어 보조적인 기능을 업데이트 하는 함수이다. </p>
<ul>
<li><code>constructor</code> : 인스턴스를 처음 만들때에만 필요한 코드를 남겨두고 나머지 코드들은 해당하는 목적의 메소드들로 분류해놓으면 좋다. </li>
<li>상위 클래스로 뽑아 낼 수 있는 것을 많이 뽑아 낼 수 있으면 좋다. (공통된 요소들을 찾아내기) </li>
</ul>
<h3 id="2-ui-를-담당하는-view">2. UI 를 담당하는 View</h3>
<p>NewsFeed 와 NewsDetail에 있는 공통된 부분을 View Class 로 빼자.
주석으로 소소팁(?)을 적어 놓았다. </p>
<pre><code>
abstract class View {
  private template: string;
  private renderTemplate: string;
  private container: HTMLElement;
  private htmlList: string[];

  constructor(containerId: string, template: string) {
    // 루트 엘리먼트 그리는 부분 
    const containerElement = document.getElementById(containerId);
    // 오류 처리 
    if (!containerElement) {
      throw &quot;최상위 컨테이너가 없어 UI를 진행하지 못합니다.&quot;;
    }


    this.container = containerElement;
    this.template = template;
    this.renderTemplate = template;
    this.htmlList = [];
  }

  // 두 군데 모두 사용되는 함수 - container 정보를 가지고 있다. 
  protected updateView(): void {
    this.container.innerHTML = this.renderTemplate;
    this.renderTemplate = this.template;
  }

  // 상위클래스에서 (하위클래스에서 직접하지않고) 기능을 제공한다는 컨셉!! 
  // 기존에는 htmllist 배열에 데이터 푸시 (기능만 외부에 노출시키는 방법이 좋음 - 그래서 상위 클래스에 넣어놓음.)
  protected addHtml(htmlString: string): void {
    this.htmlList.push(htmlString);
  }

  // 기존에 template 원본은 유지하고있어야 계속된 update에서도 새로운 데이터로 업데이트 할 수 잇다.
  protected getHtml(): string {
    const snapshot = this.htmlList.join(&quot;&quot;);
    this.clearHtmlList();
    return snapshot;
  }

  protected setTemplateData(key: string, value: string): void {
    this.renderTemplate = this.renderTemplate.replace(`{{__${key}__}}`, value);
  }

  // 데이터를 지우는 부분도 따로 빼주자. 
  private clearHtmlList(): void {
    this.htmlList = [];
  }

// View 클래스를 가지고 있으면 render 를 실행시켜
  abstract render(): void;
}</code></pre><h4 id="newsfeedview">NewsFeedView</h4>
<pre><code>
class NewsFeedView extends View {
  private api: NewsFeedApi;
  private feeds: NewsFeed[];

  // 루트인자를 상위로 부터 받으면 훨씬 더 유연성이 커진다. 
  constructor(containerId: string) {
    let  : string = `
      &lt;div class=&quot;bg-gray-600 min-h-screen&quot;&gt;
        &lt;div class=&quot;bg-white text-xl&quot;&gt;
          &lt;div class=&quot;mx-auto px-4&quot;&gt;
            &lt;div class=&quot;flex justify-between items-center py-6&quot;&gt;
              &lt;div class=&quot;flex justify-start&quot;&gt;
                &lt;h1 class=&quot;font-extrabold&quot;&gt;Hacker News&lt;/h1&gt;
              &lt;/div&gt;
              &lt;div class=&quot;items-center justify-end&quot;&gt;
                &lt;a href=&quot;#/page/{{__prev_page__}}&quot; class=&quot;text-gray-500&quot;&gt;
                  Previous
                &lt;/a&gt;
                &lt;a href=&quot;#/page/{{__next_page__}}&quot; class=&quot;text-gray-500 ml-4&quot;&gt;
                  Next
                &lt;/a&gt;
              &lt;/div&gt;
            &lt;/div&gt; 
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;p-4 text-2xl text-gray-700&quot;&gt;
          {{__news_feed__}}        
        &lt;/div&gt;
      &lt;/div&gt;
    `;

    super(containerId, template);

    this.api = new NewsFeedApi(NEWS_URL);
    this.feeds = store.feeds;

    if (this.feeds.length === 0) {
      this.feeds = store.feeds = this.api.getData();
      this.makeFeeds();
    }
  }

  render(): void {
    store.currentPage = Number(location.hash.substr(7) || 1);

    for (
      let i = (store.currentPage - 1) * 10;
      i &lt; store.currentPage * 10;
      i++
    ) {
      // 구조분해할당 
      const { id, title, comments_count, user, points, time_ago, read } =
        this.feeds[i];
        // 기능만 보여줄 수 있게
      this.addHtml(`
        &lt;div class=&quot;p-6 ${
          read ? &quot;bg-red-500&quot; : &quot;bg-white&quot;
        } mt-6 rounded-lg shadow-md transition-colors duration-500 hover:bg-green-100&quot;&gt;
          &lt;div class=&quot;flex&quot;&gt;
            &lt;div class=&quot;flex-auto&quot;&gt;
              &lt;a href=&quot;#/show/${id}&quot;&gt;${title}&lt;/a&gt;  
            &lt;/div&gt;
            &lt;div class=&quot;text-center text-sm&quot;&gt;
              &lt;div class=&quot;w-10 text-white bg-green-300 rounded-lg px-0 py-2&quot;&gt;${comments_count}&lt;/div&gt;
            &lt;/div&gt;
          &lt;/div&gt;
          &lt;div class=&quot;flex mt-3&quot;&gt;
            &lt;div class=&quot;grid grid-cols-3 text-sm text-gray-500&quot;&gt;
              &lt;div&gt;&lt;i class=&quot;fas fa-user mr-1&quot;&gt;&lt;/i&gt;${user}&lt;/div&gt;
              &lt;div&gt;&lt;i class=&quot;fas fa-heart mr-1&quot;&gt;&lt;/i&gt;${points}&lt;/div&gt;
              &lt;div&gt;&lt;i class=&quot;far fa-clock mr-1&quot;&gt;&lt;/i&gt;${time_ago}&lt;/div&gt;
            &lt;/div&gt;  
          &lt;/div&gt;
        &lt;/div&gt;    
      `);
    }
    // 데이터 대체 하는 부분도 클래스에서 상속받아서 사용하면 좋다. 
    this.setTemplateData(&quot;news_feed&quot;, this.getHtml());
    this.setTemplateData(
      &quot;prev_page&quot;,
      String(store.currentPage &gt; 1 ? store.currentPage - 1 : 1)
    );
    this.setTemplateData(&quot;next_page&quot;, String(store.currentPage + 1));

    this.updateView();
  }

  private makeFeeds(): void {
    for (let i = 0; i &lt; this.feeds.length; i++) {
      this.feeds[i].read = false;
    }
  }
}</code></pre><h4 id="newsdetailview">NewsDetailView</h4>
<pre><code>
class NewsDetailView extends View {
  constructor(containerId: string) {
    let template = `
      &lt;div class=&quot;bg-gray-600 min-h-screen pb-8&quot;&gt;
        &lt;div class=&quot;bg-white text-xl&quot;&gt;
          &lt;div class=&quot;mx-auto px-4&quot;&gt;
            &lt;div class=&quot;flex justify-between items-center py-6&quot;&gt;
              &lt;div class=&quot;flex justify-start&quot;&gt;
                &lt;h1 class=&quot;font-extrabold&quot;&gt;Hacker News&lt;/h1&gt;
              &lt;/div&gt;
              &lt;div class=&quot;items-center justify-end&quot;&gt;
                &lt;a href=&quot;#/page/{{__currentPage__}}&quot; class=&quot;text-gray-500&quot;&gt;
                  &lt;i class=&quot;fa fa-times&quot;&gt;&lt;/i&gt;
                &lt;/a&gt;
              &lt;/div&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;

        &lt;div class=&quot;h-full border rounded-xl bg-white m-6 p-4 &quot;&gt;
          &lt;h2&gt;{{__title__}}&lt;/h2&gt;
          &lt;div class=&quot;text-gray-400 h-20&quot;&gt;
            {{__content__}}
          &lt;/div&gt;

          {{__comments__}}

        &lt;/div&gt;
      &lt;/div&gt;
    `;

    super(containerId, template);
  }

  render() {
    const id = location.hash.substr(7);
    const api = new NewsDetailApi(CONTENT_URL.replace(&quot;@id&quot;, id));
    const newsDetail: NewsDetail = api.getData();

    for (let i = 0; i &lt; store.feeds.length; i++) {
      if (store.feeds[i].id === Number(id)) {
        store.feeds[i].read = true;
        break;
      }
    }

    this.setTemplateData(&quot;comments&quot;, this.makeComment(newsDetail.comments));
    this.setTemplateData(&quot;currentPage&quot;, String(store.currentPage));
    this.setTemplateData(&quot;title&quot;, newsDetail.title);
    this.setTemplateData(&quot;content&quot;, newsDetail.content);

    this.updateView();
  }

  makeComment(comments: NewsComment[]): string {
    for (let i = 0; i &lt; comments.length; i++) {
      const comment: NewsComment = comments[i];

      this.addHtml(`
        &lt;div style=&quot;padding-left: ${comment.level * 40}px;&quot; class=&quot;mt-4&quot;&gt;
          &lt;div class=&quot;text-gray-400&quot;&gt;
            &lt;i class=&quot;fa fa-sort-up mr-2&quot;&gt;&lt;/i&gt;
            &lt;strong&gt;${comment.user}&lt;/strong&gt; ${comment.time_ago}
          &lt;/div&gt;
          &lt;p class=&quot;text-gray-700&quot;&gt;${comment.content}&lt;/p&gt;
        &lt;/div&gt;      
      `);

      if (comment.comments.length &gt; 0) {
        this.addHtml(this.makeComment(comment.comments));
      }
    }

    return this.getHtml();
  }
}</code></pre><h3 id="3-router-와-그-외">3. Router 와 그 외</h3>
<pre><code>
// 기능을 제공하고 그 기능을 이용햏서 바깥쪽에서 특정한 hash 값이 되면 어떤 페이지로 이동하게끔 하는 것이 설정하는데 가장 좋은 모양 
class Router {
  routeTable: RouteInfo[];
  defaultRoute: RouteInfo | null;

  constructor() {
    window.addEventListener(&quot;hashchange&quot;, this.route.bind(this));

    this.routeTable = [];
    this.defaultRoute = null;
  }

  setDefaultPage(page: View): void {
    this.defaultRoute = { path: &quot;&quot;, page };
  }

  addRoutePath(path: string, page: View): void {
    this.routeTable.push({ path, page });
  }

  route() {
    const routePath = location.hash;

    if (routePath === &quot;&quot; &amp;&amp; this.defaultRoute) {
      this.defaultRoute.page.render();
    }

    for (const routeInfo of this.routeTable) {
      if (routePath.indexOf(routeInfo.path) &gt;= 0) {
        routeInfo.page.render();
        break;
      }
    }
  }
}


// 인스턴스 생성 
const router: Router = new Router();
const newsFeedView = new NewsFeedView(&quot;root&quot;);
const newsDetailView = new NewsDetailView(&quot;root&quot;);

router.setDefaultPage(newsFeedView); 

router.addRoutePath(&quot;/page/&quot;, newsFeedView);
router.addRoutePath(&quot;/show/&quot;, newsDetailView);

router.route();
</code></pre><h2 id="에필로그">에필로그</h2>
<p>클래스 부분을 바꾸는 일을 조금 헷갈렸다. 그래도 두세번 들으니까 조금 알겠긴 한데 이런 작업들은 실제로 내가 생각하고 하나하나 코드를 구현해 봐야지 점점 실력이 늘 것 같다. 그래도 이해의 범위가 좀 깊어졌다. 좋은 일이다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[타입과 인터페이스 | 상속과 믹스인 ]]></title>
            <link>https://velog.io/@dev__note/%ED%83%80%EC%9E%85%EA%B3%BC-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4-%EC%83%81%EC%86%8D%EA%B3%BC-%EB%AF%B9%EC%8A%A4%EC%9D%B8</link>
            <guid>https://velog.io/@dev__note/%ED%83%80%EC%9E%85%EA%B3%BC-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4-%EC%83%81%EC%86%8D%EA%B3%BC-%EB%AF%B9%EC%8A%A4%EC%9D%B8</guid>
            <pubDate>Tue, 06 Sep 2022 03:08:46 GMT</pubDate>
            <description><![CDATA[<h2 id="프롤로그">프롤로그</h2>
<p><code>타입</code>과 <code>인터페이스</code>의 차이는 아주 ~ 조금 ~ 차이가 난다고 한다. 코드는 일관성이 중요하기 때문에 어떤 미세한 기능 때문에 그것을 써야 하는 경우가 있다면 그 코드로 써야 겠지만 대부분은 취향의 차이라고 한다.</p>
<p><code>타입</code>을 인터페이스로 바꿔보려고 한다.</p>
<h3 id="1-타입-알리아스와-인터페이스의-차이">1. 타입 알리아스와 인터페이스의 차이</h3>
<blockquote>
<p>타입을 결합시키거나 조합시키는 차이</p>
</blockquote>
<blockquote>
<p>인터페이스는 타입 알리아스의 유니언 (a | b) 타입을 지원하지 않는다. </p>
</blockquote>
<blockquote>
<p>인터페이스가 좀 더 코드를 읽는다는 느낌이 있다. (표현 기법의 차이)</p>
</blockquote>
<h3 id="2-인터페이스로-변환">2. 인터페이스로 변환</h3>
<p>코드를 보자. 앞에 <code>interface</code> 를 바꾸고 <code>=</code> 을 없앴다.
그리고 확장할 때는 <code>&amp;&amp;</code> 를 쓰는게 아니라 <code>extends</code> 를 써준다.</p>
<pre><code>interface Store {
  currentPage: number;
  feeds: NewsFeed[];
}

interface News {
  readonly id: number;
  readonly time_ago: string;
  readonly title: string;
  readonly url: string;
  readonly user: string;
  readonly content: string;
}

interface NewsFeed extends News {
  readonly comments_count: number;
  readonly points: number;
  read?: boolean;
}

interface NewsDetail extends News {
  readonly comments: NewsComment[];
}

interface NewsComment extends News {
  readonly comments: NewsComment[];
  readonly level: number;
}
</code></pre><h3 id="3-상속과-믹스인">3. 상속과 믹스인</h3>
<blockquote>
<p><strong>상속</strong> 이란 공통요소를 만들어 놓고, 공통 요소를 확장 할 수 있는 개별 요소를 만들게 되는 식. 이 관계를 상속이라고 한다. </p>
</blockquote>
<p>상속을 다루는 메커니즘은 2가지가 있다.</p>
<ul>
<li>클래스</li>
<li>믹스인 </li>
</ul>
<h4 id="-우선-api-코드를-class-로-다뤄보자">‣ 우선 API 코드를 class 로 다뤄보자</h4>
<pre><code>//class 를 만들고 한번 초기화의 과정을 해줘야 한다. constructor 가 초기화를 담당 
class Api {
  ajax: XMLHttpRequest;
  url: string;

  constructor(url: string) {
    this.ajax = new XMLHttpRequest();
    this.url = url;
  }

// api 를 요청하는 공통적인 부분
  getRequest&lt;AjaxResponse&gt;(): AjaxResponse {
    this.ajax.open(&#39;GET&#39;, this.url, false);
    this.ajax.send();

    return JSON.parse(this.ajax.response);
  }
}

// 타입을 다르게 해서 가져오는 부분. api 클래스에서 상속을 받았다.
class NewsFeedApi extends Api {
  getData(): NewsFeed[] {
    return this.getRequest&lt;NewsFeed[]&gt;();
  }
}

class NewsDetailApi extends Api {
  getData(): NewsDetail {
    return this.getRequest&lt;NewsDetail&gt;();
  }
}</code></pre><p>class 는 목적을 위한 구조를 가지게 된다. 목적을 위한 구조를 가지게 되면 좋은 점은 코드가 나중에 더 많은 구조를 가지게 될 때 초기의 복잡도는 유지하면서 바깥쪽에서는 그 단순함을 꾸준히 유지 할 수 있다는 장점이 있다. </p>
<p>코드베이스가 작을 때는 변경된 코드가 너무 복잡해서 더 나빠진 것 같은데 라는 생각이 들 수 있지만 코드가 점점 커질 수록 코드의 장점이 극대화 될 것이다. </p>
<p>사용하는 쪽을 보자 </p>
<pre><code>function newsFeed(): void {
  //인스턴스 생성 필수 
  let api = new NewsFeedApi(NEWS_URL);
  let newsFeed: NewsFeed[] = store.feeds;
  const newsList: string[] = [];

 // .. 코드 중략 ... 

//api.getData() 로 url 을 넘겨줄필요도 없고, 타입을 지정할 필요도 없다. 
  if (newsFeed.length === 0) {
    newsFeed = store.feeds = makeFeeds(api.getData());
  }

  // .. 코드 중략 .. 



  template = template.replace(&#39;{{__news_feed__}}&#39;, newsList.join(&#39;&#39;));
  template = template.replace(&#39;{{__prev_page__}}&#39;, String(store.currentPage &gt; 1 ? store.currentPage - 1 : 1));
  template = template.replace(&#39;{{__next_page__}}&#39;, String(store.currentPage + 1));

  updateView(template);
}</code></pre><blockquote>
<p><strong>믹스인</strong> 이라고 하는 기법은 class 를 사용해서 상속을 구현하지만 class의 extends라는 기법을 사용하지 않고 class 를 마치 함수처럼, 단독의 객체 처럼 바라보면서 필요한 class를 합성해서 새로운 기능으로 확장해나가는 기법이다. <strong>믹스인은 class 자체를 훨씬 더 독립적인 주체로 바라본다.</strong> </p>
<blockquote>
<p>굳이 extends 기능이 있는데 왜 쓰는가? </p>
</blockquote>
</blockquote>
<ol>
<li>기존의 extends 라고 하는 방식의 상속 방법은 코드에 적시 되어야 하는 상속 방법이다. 상속의 관계를 바꾸고 싶으면 코드 자체를 바꿔야 한다는 뜻 (관계를 유연하게 가져갈 수 없다.) </li>
<li>JS 와 TS 의 extends 문법은 다중 상속을 지원하지 않는다.(여러개의 상위 클래스를 상속받고 싶으면 불가능하다는 이야기) </li>
</ol>
<h4 id="-class-를-mixin-으로-바꿔보자">‣ class 를 mixin 으로 바꿔보자</h4>
<pre><code>//targetClass 로 제공된 class 에다가 baseClass 들로 제공된 n 개의 클래스 기능들을 합성시키는 역할의 함수 

function applyApiMixins(targetClass: any, baseClasses: any[]) : void {
  baseClasses.forEach(baseClasses =&gt;{
    Object.getOwnPropertyNames(baseClass.prototype).forEach(name =&gt;{
      const descriptor = Object.getOwnPropertyDescriptor(baseClass.prototype, name);

      if(descriptor) {
        Object.defineProperty(targetClass.prototype, name, descriptor)
      }
    })
  })
}

class Api {

  getRequest&lt;AjaxResponse&gt;(url: string): AjaxResponse {
    const ajax = new XMLHttpRequest();
    ajax.open(&#39;GET&#39;, url, false);
    ajax.send();

    return JSON.parse(ajax.response);
  }
}

class NewsFeedApi {
  getData(): NewsFeed[] {
    return this.getRequest&lt;NewsFeed[]&gt;(NEWS_URL));
  }
}

class NewsDetailApi {
  getData(id:string): NewsDetail {
    return this.getRequest&lt;NewsDetail&gt;(CONTENT_URL.replace(&#39;@id&#39;, id));
  }
}

// 타입스크립트 컴파일러한테 두개가 합성 될거라고 알려줘야 한다. 
interface NewsFeedApi extends Api {};
interface NewsDetailApi extends Api {};

// 믹스인 사용하는 쪽 
applyApiMixins(NewsFeedApi, [Api]);
applyApiMixins(NewsDetailApi, [Api]);
</code></pre><h3 id="4-이론-클래스">4. 이론) 클래스</h3>
<p>클래스는 자바스크립트에서 <strong>객체를 만드는 가장 정교한 방법</strong>을 제공한다.
클래스는 아직 객체가 만들어 지지 않은 상태. 객체가 만들어 지면 어떻게 만들어지고 어떤 기능을 가질거야 라고 하는 일종의 설계도에 가깝다. 그래서 실제 객체로 아직 형상화 되어 있지는 않다. </p>
<p>그렇다면 실제객체로 형상화 되려면 어떻게 해야하는가? 
인스턴스라고 하는 것을 만들어야 하고 그렇게 만들어진 인스턴스는 실제로 객체인데 클래스의 설계도대로 <code>현실화 된 객체</code>라고 하는 뜻이다. </p>
<ul>
<li><p><code>static</code> 이라는 것은 인스턴스 객체에 포함되지 않는 정말 정적으로 shpae class 에 연결되어 있는 속성이라는 뜻 (굳이 인스턴스 객체마다 데이터 혹은 메소드를 넣을 필요가 없는 경우, 정확히는 인스턴스들끼리 관계가 없이 shape class 라고하는 특정 클래스가 포함하고 있는 데이터나 메소드인 경우에 정적으로 만들면 효과적으로 사용하는 경우가 있다.) </p>
</li>
<li><p>상속받은 하위클래스에서는 반드시 construcotor에 <code>super()</code>라고 써서 부모클래스를 초기화 시켜줘야한다. </p>
</li>
<li><p><code>readonly</code> 는 인스턴스객체가 만들어진 이후에 인스턴스 객체 외부에서 이 name 자체의 값을 바꿀 수 없는 속성을 말한다. </p>
</li>
<li><p>내부속성을 보호하기 위한 방법은 두가지가 있다. 하나는 <code>private</code> 속성을 사용하는 것. 보이지 않게 만들고 바깥쪽에서 사용하는 것은 getter 혹은 setter 라는 것을 사용해서 따로 제공하는 방법 | 또는 readonly </p>
</li>
<li><p><code>abstract</code>는 추상 메소드이다. 추상 클래스일 경우에 사용하는 기능. 이 클래스를 상속받은 하위클래스 한테 반드시 실체적인 코드를 구현해라 하고 지시하는 역할. 그래서 추상클래스의 추상메소드가 있는 경우에는 반드시 상속받으면서 해당하는 메소드를 실체화된 코드로 구현해야 한다. </p>
</li>
<li><p>interface 를 클래스와 연결지으면 클래스의 설계를 제한하는 용도로 사용하게 된다. class MyContainer <code>implements</code> containers {...} 설계도로써 컨테이너라는 인터페이스를 사용할거야! 라고 하면 implements 의 키워드를 사용해야 한다. </p>
</li>
<li><p><code>public</code> : 인스턴스객체 그대로 드러나서 사용할 수 있는 것 </p>
</li>
<li><p><code>private</code> : class 안에서만 통용되는 것 (상속받은 하위클래스 혹은 부모클래스에서도 접근이 안되는 방식) </p>
</li>
<li><p><code>protected</code> : 외부에는 공개되지 않지만 내부에서는 자식 클래스 즉, 확장된 클래스 접근할 수 있는 요소 (private 보다 범위가 넓다) </p>
</li>
</ul>
<h3 id="인스턴스">인스턴스</h3>
<p>구체적으로 현실화 된 객체를 <strong>인스턴스</strong> 라고 한다. </p>
<h4 id="함수로-만드는-방법">함수로 만드는 방법</h4>
<p>암묵적인 방식이 존재 </p>
<p>new 연산자를 함수 호출 앞에 쓰게 되면 암묵적인 매카니즘이 작동하게 됨 </p>
<ol>
<li>빈객체를 하나를 만듦 (인스턴스객체) </li>
<li>CartV1 한테 그 빈 객체를   전달해줌 ( 그 빈객체를 가르키는 지시어가 this) </li>
<li>함수를 new 연산자로 만들고 났을 때 인스턴스 객체를 만들고 나서 함수가 종료되면, 자동으로 this 객체를 return 하게 되어 있다.  </li>
<li>내부적으로는 또 암묵적 매카니즘이 작동하는데, 만들어진 빈 this 객체에 <code>prototype</code> 속성을 할당한다. </li>
</ol>
<p>객체는 모두 <code>__proto__</code>라는 속성을 가지고 있고, 
함수는 <code>prototype</code>이라는 속성을 기본적으로 가지고 있다.</p>
<h4 id="클래스">클래스</h4>
<p>직관적이고 하나로 묶여있어 보기 편하다.</p>
<h2 id="에필로그">에필로그</h2>
<p>점점 고급문법이 나오면서 난이도가 높아져 간다. 클래스는 강의를 들으면서 많이 접했는데 완벽하게 이해가 가지 않았었다. 다시들으면서 이론도 같이 접하니까 이해가 확실히 간다. 함수에서 쓴 <code>prototype</code> 또한 마찬가지
깊게 듣고 이해하려고 하니 이해가 간다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[코드 migration 하기(JS를 TS 로)]]></title>
            <link>https://velog.io/@dev__note/%EC%BD%94%EB%93%9C-migration-%ED%95%98%EA%B8%B0JS%EB%A5%BC-TS-%EB%A1%9C</link>
            <guid>https://velog.io/@dev__note/%EC%BD%94%EB%93%9C-migration-%ED%95%98%EA%B8%B0JS%EB%A5%BC-TS-%EB%A1%9C</guid>
            <pubDate>Fri, 02 Sep 2022 12:04:59 GMT</pubDate>
            <description><![CDATA[<h2 id="프롤로그">프롤로그</h2>
<p>이때까지 js 만들었던 hacker news 를 typescript 로 변환하고자 한다.
어떤 부분들이 바뀌는 지 어떻게 바뀌는지 흐름을 따라 가 보면 좋을 듯 하다. </p>
<h3 id="1-환경-설정-설명">1. 환경 설정 설명</h3>
<h4 id="간단한-컴파일-옵션들---tsconfigjson">간단한 컴파일 옵션들 - tsconfig.json</h4>
<pre><code>{
  &quot;compilerOptions&quot;: {
    &quot;strict&quot;: true, // 본격적으로 타입스크립트로 변환하겠다라고 했을 때 엄격모드로 설정 해 놓으면 조금 더 세부적으로 변환 가능하다. 
    &quot;target&quot;: &quot;ES5&quot;, // 어떤 문법을 사용할 것인지 (js 에 사용될 문법 체계를 어떤 버전을 쓸 것이냐) 
    &quot;module&quot;: &quot;CommonJS&quot;,
    &quot;alwaysStrict&quot;: true,
    &quot;noImplicitAny&quot;: true, //any 타입을 쓰지 못하도록 한다. (타입을 명확하게 할 수 있도록 한다.) 
    &quot;noImplicitThis&quot;: true,
    &quot;sourceMap&quot;: true, //개발환경과 소스코드를 일치 시킨다. (관리자도구에서 ts 파일을 볼 수 있다.) 
    &quot;downlevelIteration&quot;: true
  } 
}</code></pre><h3 id="2-변수에-타입-변경하기">2. 변수에 타입 변경하기</h3>
<pre><code>
//타입 알리아스 
type Store = {
  currentPage: number;
  feeds: NewsFeed[]; //NewsFeed 유형의 데이터가 들어가는 배열
}

type NewsFeed = {
  id: number;
  comments_count: number;
  url: string;
  user: string;
  time_ago: string;
  points: number;
  title: string;
  read?: boolean; //optional 한 속성 
}

const container: HTMLElement | null = document.getElementById(&#39;root&#39;);
const ajax: XMLHttpRequest = new XMLHttpRequest();
const NEWS_URL = &#39;https://api.hnpwa.com/v0/news/1.json&#39;;
const CONTENT_URL = &#39;https://api.hnpwa.com/v0/item/@id.json&#39;;
const store: Store = {
  currentPage: 1,
  feeds: [],
};
</code></pre><blockquote>
<p><strong>타입 추론</strong> : 누가 봐도 당연한 값들은 typescript 에서 알아서 타입 추론을 해 준다. 따로 타입 지정을 할 필요가 없다. 예를 들면 for 문 </p>
</blockquote>
<h3 id="3-타입-가드">3. 타입 가드</h3>
<pre><code>const container: HTMLElement | null = document.getElementById(&#39;root&#39;);</code></pre><p>container 같은 경우 타입이 2가지이다. 그래서 이러한 부분들을 따로 타입에 대한 조건을 달아 줘야 한다. <strong>타입 가드</strong> 
만약에 innerHTML 에 null 이라는 타입이 들어오면 오류가 나게 되니 미리 방지해줘야 한다는 말이다.</p>
<p>중복되는 부분이 있어서 함수로 만들었다. 이와 같이 null 을 체크하는 부분을 <strong>타입 가드</strong>한다 라고 한다. </p>
<pre><code>function updateView(html) {
  if (container) {
    container.innerHTML = html;
  } else {
    console.error(&#39;최상위 컨테이너가 없어 UI를 진행하지 못합니다.&#39;);
  }
}</code></pre><blockquote>
<p><strong>타입가드</strong> 는 타입스크립트 내에서 어떤 변수가 2개이상의 타입을 갖게 되는 경우가 있을 때, 코드상에서 a 라는 타입이 들어왔을 때 작동 될 수 없는 코드에 대해서 경고를 해 주거나, 혹은 그것을 원천적으로 막을 수 있는 코드 테크닉 혹은 코딩 방식을 타입가드라고 한다.</p>
</blockquote>
<h3 id="4-함수의-규격-작성하기">4. 함수의 규격 작성하기</h3>
<h4 id="타입-알리아스의-공통-속성-사용">타입 알리아스의 공통 속성 사용</h4>
<pre><code>
// 공통 속성 
type News = {
  id: number;
  time_ago: string;
  title: string;
  url: string;
  user: string;
  content: string;
}

// news 에 대한 공통 속성을 사용하는 방법을 제공한다 &amp;을 이용
type NewsFeed = News &amp; {
  comments_count: number;
  points: number;
  read?: boolean;
}


type NewsDetail =  News &amp; {
  comments: NewsComment[];
}

type NewsComment = News &amp; {
  comments: NewsComment[];
  level: number;
}</code></pre><h4 id="제네릭으로-표현하기">제네릭으로 표현하기</h4>
<pre><code>// | 타입으로 타입을 여러개 정의 할 수 있으나 타입이 많아지면 곤란할 뿐더러 getData 를 사용하는 입장에서도 어떤 데이터를 사용할 지 모호한 상황이 오게 된다.
//  그래서 제네릭 기능 :: 입력이 n 개의 유형일 때 출력도 n 개의 유형인 것을 정의하는 것
//  &lt;T&gt; 이러한 타입이라는 입력이 들어오면 T 로 나온다. 
function getData&lt;AjaxResponse&gt;(url: string): AjaxResponse {
  ajax.open(&#39;GET&#39;, url, false);
  ajax.send();

  return JSON.parse(ajax.response);
}

// !! 이렇게 사용한다. 같은 getData 함수이지만 입력이 NEWsFeed 가 들어오면 NewsFeed 로 출력

if (newsFeed.length === 0) {
    newsFeed = store.feeds = makeFeeds(getData&lt;NewsFeed[]&gt;(NEWS_URL));
  }

  //NewsDetail 이 들어오면 NewsDetail 로 출력 
  const newsContent = getData&lt;NewsDetail&gt;(CONTENT_URL.replace(&#39;@id&#39;, id))
</code></pre><blockquote>
<p><strong>제네릭</strong>
확정되지 않은 T 라는 값인데, 인자값에 타입이 들어오면 타입으로 T 를 쓸꺼야. 반환값으로도 T 로 쓸 거야. 
타입을 호출 순간에 확정하고, 확정 함으로써 그 뒤로 확정되는 범위를 확대해서 타입스크립트의 장점을 누릴 수 있는 기능! (객체를 쓸 때 진가가 발휘 된다) </p>
</blockquote>
<h3 id="5-전체-코드">5. 전체 코드</h3>
<pre><code>
type Store = {
  currentPage: number;
  feeds: NewsFeed[];
}

type News = {
  id: number;
  time_ago: string;
  title: string;
  url: string;
  user: string;
  content: string;
}

type NewsFeed = News &amp; {
  comments_count: number;
  points: number;
  read?: boolean;
}

type NewsDetail =  News &amp; {
  comments: NewsComment[];
}

type NewsComment = News &amp; {
  comments: NewsComment[];
  level: number;
}

const container: HTMLElement | null = document.getElementById(&#39;root&#39;);
const ajax: XMLHttpRequest = new XMLHttpRequest();
const NEWS_URL = &#39;https://api.hnpwa.com/v0/news/1.json&#39;;
const CONTENT_URL = &#39;https://api.hnpwa.com/v0/item/@id.json&#39;;
const store: Store = {
  currentPage: 1,
  feeds: [],
};

function getData&lt;AjaxResponse&gt;(url: string): AjaxResponse {
  ajax.open(&#39;GET&#39;, url, false);
  ajax.send();

  return JSON.parse(ajax.response);
}

function makeFeeds(feeds: NewsFeed[]): NewsFeed[] {
  for (let i = 0; i &lt; feeds.length; i++) {
    feeds[i].read = false;
  }

  return feeds;
}

function updateView(html: string): void {
  if (container) {
    container.innerHTML = html;
  } else {
    console.error(&#39;최상위 컨테이너가 없어 UI를 진행하지 못합니다.&#39;);
  }
}

function newsFeed(): void {
  let newsFeed: NewsFeed[] = store.feeds;
  const newsList = [];
  let template = `
    &lt;div class=&quot;bg-gray-600 min-h-screen&quot;&gt;
      &lt;div class=&quot;bg-white text-xl&quot;&gt;
        &lt;div class=&quot;mx-auto px-4&quot;&gt;
          &lt;div class=&quot;flex justify-between items-center py-6&quot;&gt;
            &lt;div class=&quot;flex justify-start&quot;&gt;
              &lt;h1 class=&quot;font-extrabold&quot;&gt;Hacker News&lt;/h1&gt;
            &lt;/div&gt;
            &lt;div class=&quot;items-center justify-end&quot;&gt;
              &lt;a href=&quot;#/page/{{__prev_page__}}&quot; class=&quot;text-gray-500&quot;&gt;
                Previous
              &lt;/a&gt;
              &lt;a href=&quot;#/page/{{__next_page__}}&quot; class=&quot;text-gray-500 ml-4&quot;&gt;
                Next
              &lt;/a&gt;
            &lt;/div&gt;
          &lt;/div&gt; 
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;p-4 text-2xl text-gray-700&quot;&gt;
        {{__news_feed__}}        
      &lt;/div&gt;
    &lt;/div&gt;
  `;

  if (newsFeed.length === 0) {
    newsFeed = store.feeds = makeFeeds(getData&lt;NewsFeed[]&gt;(NEWS_URL));
  }

  for(let i = (store.currentPage - 1) * 10; i &lt; store.currentPage * 10; i++) {
    newsList.push(`
      &lt;div class=&quot;p-6 ${newsFeed[i].read ? &#39;bg-red-500&#39; : &#39;bg-white&#39;} mt-6 rounded-lg shadow-md transition-colors duration-500 hover:bg-green-100&quot;&gt;
        &lt;div class=&quot;flex&quot;&gt;
          &lt;div class=&quot;flex-auto&quot;&gt;
            &lt;a href=&quot;#/show/${newsFeed[i].id}&quot;&gt;${newsFeed[i].title}&lt;/a&gt;  
          &lt;/div&gt;
          &lt;div class=&quot;text-center text-sm&quot;&gt;
            &lt;div class=&quot;w-10 text-white bg-green-300 rounded-lg px-0 py-2&quot;&gt;${newsFeed[i].comments_count}&lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;flex mt-3&quot;&gt;
          &lt;div class=&quot;grid grid-cols-3 text-sm text-gray-500&quot;&gt;
            &lt;div&gt;&lt;i class=&quot;fas fa-user mr-1&quot;&gt;&lt;/i&gt;${newsFeed[i].user}&lt;/div&gt;
            &lt;div&gt;&lt;i class=&quot;fas fa-heart mr-1&quot;&gt;&lt;/i&gt;${newsFeed[i].points}&lt;/div&gt;
            &lt;div&gt;&lt;i class=&quot;far fa-clock mr-1&quot;&gt;&lt;/i&gt;${newsFeed[i].time_ago}&lt;/div&gt;
          &lt;/div&gt;  
        &lt;/div&gt;
      &lt;/div&gt;    
    `);
  }

  template = template.replace(&#39;{{__news_feed__}}&#39;, newsList.join(&#39;&#39;));
  template = template.replace(&#39;{{__prev_page__}}&#39;, String(store.currentPage &gt; 1 ? store.currentPage - 1 : 1));
  template = template.replace(&#39;{{__next_page__}}&#39;, String(store.currentPage + 1));

  updateView(template);
}

function newsDetail(): void {
  const id = location.hash.substr(7);
  const newsContent = getData&lt;NewsDetail&gt;(CONTENT_URL.replace(&#39;@id&#39;, id))
  let template = `
    &lt;div class=&quot;bg-gray-600 min-h-screen pb-8&quot;&gt;
      &lt;div class=&quot;bg-white text-xl&quot;&gt;
        &lt;div class=&quot;mx-auto px-4&quot;&gt;
          &lt;div class=&quot;flex justify-between items-center py-6&quot;&gt;
            &lt;div class=&quot;flex justify-start&quot;&gt;
              &lt;h1 class=&quot;font-extrabold&quot;&gt;Hacker News&lt;/h1&gt;
            &lt;/div&gt;
            &lt;div class=&quot;items-center justify-end&quot;&gt;
              &lt;a href=&quot;#/page/${store.currentPage}&quot; class=&quot;text-gray-500&quot;&gt;
                &lt;i class=&quot;fa fa-times&quot;&gt;&lt;/i&gt;
              &lt;/a&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class=&quot;h-full border rounded-xl bg-white m-6 p-4 &quot;&gt;
        &lt;h2&gt;${newsContent.title}&lt;/h2&gt;
        &lt;div class=&quot;text-gray-400 h-20&quot;&gt;
          ${newsContent.content}
        &lt;/div&gt;

        {{__comments__}}

      &lt;/div&gt;
    &lt;/div&gt;
  `;

  for(let i=0; i &lt; store.feeds.length; i++) {
    if (store.feeds[i].id === Number(id)) {
      store.feeds[i].read = true;
      break;
    }
  }

  updateView(template.replace(&#39;{{__comments__}}&#39;, makeComment(newsContent.comments)));
}

function makeComment(comments: NewsComment[]): string {
  const commentString = [];

  for(let i = 0; i &lt; comments.length; i++) {
    const comment: NewsComment = comments[i];

    commentString.push(`
      &lt;div style=&quot;padding-left: ${comment.level * 40}px;&quot; class=&quot;mt-4&quot;&gt;
        &lt;div class=&quot;text-gray-400&quot;&gt;
          &lt;i class=&quot;fa fa-sort-up mr-2&quot;&gt;&lt;/i&gt;
          &lt;strong&gt;${comment.user}&lt;/strong&gt; ${comment.time_ago}
        &lt;/div&gt;
        &lt;p class=&quot;text-gray-700&quot;&gt;${comment.content}&lt;/p&gt;
      &lt;/div&gt;      
    `);

    if (comment.comments.length &gt; 0) {
      commentString.push(makeComment(comment.comments));
    }
  }

  return commentString.join(&#39;&#39;);
}

function router(): void {
  const routePath = location.hash;

  if (routePath === &#39;&#39;) {
    newsFeed();
  } else if (routePath.indexOf(&#39;#/page/&#39;) &gt;= 0) {
    store.currentPage = Number(routePath.substr(7));
    newsFeed();
  } else {
    newsDetail()
  }
}

window.addEventListener(&#39;hashchange&#39;, router);

router();
</code></pre><h2 id="에필로그">에필로그</h2>
<p>뭔가 타입스크립트라고 해서 어렵게 생각했는데 js 를 먼저 작성하고 차례대로 바꾸는 작업을 거치니까 문턱이 낮아진 느낌이다. 물론 고급 기능들을 습득 한 것은 아니지만 점점 알게되고 있는 기분이라 좋게 생각한다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[상태를 가져보자 - 읽은 피드 표시하기 ]]></title>
            <link>https://velog.io/@dev__note/%EC%83%81%ED%83%9C%EB%A5%BC-%EA%B0%80%EC%A0%B8%EB%B3%B4%EC%9E%90-%EC%9D%BD%EC%9D%80-%ED%94%BC%EB%93%9C-%ED%91%9C%EC%8B%9C%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dev__note/%EC%83%81%ED%83%9C%EB%A5%BC-%EA%B0%80%EC%A0%B8%EB%B3%B4%EC%9E%90-%EC%9D%BD%EC%9D%80-%ED%94%BC%EB%93%9C-%ED%91%9C%EC%8B%9C%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 02 Sep 2022 11:11:36 GMT</pubDate>
            <description><![CDATA[<h2 id="프롤로그">프롤로그</h2>
<p>기능요구사항 ) 사용자가 피드를 읽었다가 돌아왔을 때 읽었는지 안 읽었는지 확인이 필요하다. 이때 상태 관리를 어떻게 할지 생각해 보고 구현해 보자</p>
<p>피드를 읽었는지 안 읽었는지 확인 하는 방법에는 두가지가 있다. </p>
<blockquote>
<ol>
<li>피드의 마다 고유의 Id 값을 가지고 읽음 표시 데이터를 만들어서 따로 표시 해 놓는 방식</li>
<li>뉴스피드를 가지고 온 데이터에 새로운 속성 하나를 추가해서 그걸 가지고 확인 하는 방식 -- read </li>
</ol>
</blockquote>
<p>후자의 방식을 선택해 보기로 했다.</p>
<h3 id="1-새로운-상태-추가">1. 새로운 상태 추가</h3>
<p>실제로 피드 데이터를 불러 오게 될 때 목록에는 10개씩 나타 나지만 실제로 모두를 불러들이게 된다. 만약 전체 값을 불러와서 비교를 하게 된다면 세상 비효율적일 수가 없다. 그래서 그런 비효율을 없애기 위해 후자의 방식을 택했다. </p>
<p>글을 한번 읽으면 read 데이터를 가지고 있으면 좋다. 목록에서도 피드에서도 참고해야 하니까 전역 상태값을 하나 추가한다. <code>store</code> 에 <code>feed</code>라는 값을 만들어 배열을 만든다.</p>
<pre><code>const store = {
  currentPage: 1,
  feeds: [],
};</code></pre><p>뉴스 피드에도 다이렉트로 URL 을 불러오는게 아니라 store.feeds 데이터로 교체 해 준다.</p>
<pre><code>let newsFeed = store.feeds;

if (newsFeed.length === 0) {
    newsFeed = store.feeds = makeFeeds(getData(NEWS_URL));
  }
</code></pre><p>그런데 여기서 makeFeeds 는 무엇일까?
데이터를 가지고 와서 read 했는지 안했는지 알아야 하니까 그 부분의 속성도 추가 시켜 주는 함수이다. </p>
<pre><code>function makeFeeds(feeds) {
  for (let i = 0; i &lt; feeds.length; i++) {
    feeds[i].read = false;
  }

  return feeds;
}</code></pre><p>즉, 데이터를 불러와서 makeFeeds 로 가서 read 속성을 추가 해 주고, 그 데이터를 <code>store.feeds</code> 배열에 담아주고 <code>newsfeed</code> 에 다시 담아 질 수 있도록 한다.</p>
<h3 id="2-상태와-ui-연결">2. 상태와 UI 연결</h3>
<p>속성을 만들 었으면 글을 읽고 나왔을 때 색상을 변경해 주는 코드를 작성해 보자. </p>
<p>NewsFeed 목록 부분에서 상태값이 <code>read</code> 면 빨간색 <code>bg-red-500</code> 이고, 아니면 그대로 흰색으로 한다. </p>
<pre><code>for(let i = (store.currentPage - 1) * 10; i &lt; store.currentPage * 10; i++) {
    newsList.push(`
      &lt;div class=&quot;p-6 ${newsFeed[i].read ? &#39;bg-red-500&#39; : &#39;bg-white&#39;} mt-6 rounded-lg shadow-md transition-colors duration-500 hover:bg-green-100&quot;&gt;
        &lt;div class=&quot;flex&quot;&gt;
          &lt;div class=&quot;flex-auto&quot;&gt;
            &lt;a href=&quot;#/show/${newsFeed[i].id}&quot;&gt;${newsFeed[i].title}&lt;/a&gt;  
          &lt;/div&gt;
          &lt;div class=&quot;text-center text-sm&quot;&gt;
            &lt;div class=&quot;w-10 text-white bg-green-300 rounded-lg px-0 py-2&quot;&gt;${newsFeed[i].comments_count}&lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;flex mt-3&quot;&gt;
          &lt;div class=&quot;grid grid-cols-3 text-sm text-gray-500&quot;&gt;
            &lt;div&gt;&lt;i class=&quot;fas fa-user mr-1&quot;&gt;&lt;/i&gt;${newsFeed[i].user}&lt;/div&gt;
            &lt;div&gt;&lt;i class=&quot;fas fa-heart mr-1&quot;&gt;&lt;/i&gt;${newsFeed[i].points}&lt;/div&gt;
            &lt;div&gt;&lt;i class=&quot;far fa-clock mr-1&quot;&gt;&lt;/i&gt;${newsFeed[i].time_ago}&lt;/div&gt;
          &lt;/div&gt;  
        &lt;/div&gt;
      &lt;/div&gt;    
    `);
  }</code></pre><p>read 값으로 바꾸어 주는 코드는 클릭하고 상세페이지로 전환 될 때 발생 된다. NewsDetail 부분이다. 데이터 전체를 돌려서 id 값이 지금 읽고 있는 id 와 같으면 read 로 전환 시키기 </p>
<pre><code>for(let i=0; i &lt; store.feeds.length; i++) {
    if (store.feeds[i].id === Number(id)) {
      store.feeds[i].read = true;
      break;
    }
  }</code></pre><h4 id="읽었을-때-상태">읽었을 때 상태</h4>
<p><img src="https://velog.velcdn.com/images/dev__note/post/21de7c19-b950-400d-a54b-a068e78470de/image.png" alt=""></p>
<h3 id="3-전체-코드">3. 전체 코드</h3>
<pre><code>const container = document.getElementById(&#39;root&#39;);
const ajax = new XMLHttpRequest();
const NEWS_URL = &#39;https://api.hnpwa.com/v0/news/1.json&#39;;
const CONTENT_URL = &#39;https://api.hnpwa.com/v0/item/@id.json&#39;;
const store = {
  currentPage: 1,
  feeds: [],
};

function getData(url) {
  ajax.open(&#39;GET&#39;, url, false);
  ajax.send();

  return JSON.parse(ajax.response);
}

function makeFeeds(feeds) {
  for (let i = 0; i &lt; feeds.length; i++) {
    feeds[i].read = false;
  }

  return feeds;
}

function newsFeed() {
  let newsFeed = store.feeds;
  const newsList = [];
  let template = `
    &lt;div class=&quot;bg-gray-600 min-h-screen&quot;&gt;
      &lt;div class=&quot;bg-white text-xl&quot;&gt;
        &lt;div class=&quot;mx-auto px-4&quot;&gt;
          &lt;div class=&quot;flex justify-between items-center py-6&quot;&gt;
            &lt;div class=&quot;flex justify-start&quot;&gt;
              &lt;h1 class=&quot;font-extrabold&quot;&gt;Hacker News&lt;/h1&gt;
            &lt;/div&gt;
            &lt;div class=&quot;items-center justify-end&quot;&gt;
              &lt;a href=&quot;#/page/{{__prev_page__}}&quot; class=&quot;text-gray-500&quot;&gt;
                Previous
              &lt;/a&gt;
              &lt;a href=&quot;#/page/{{__next_page__}}&quot; class=&quot;text-gray-500 ml-4&quot;&gt;
                Next
              &lt;/a&gt;
            &lt;/div&gt;
          &lt;/div&gt; 
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;p-4 text-2xl text-gray-700&quot;&gt;
        {{__news_feed__}}        
      &lt;/div&gt;
    &lt;/div&gt;
  `;

  if (newsFeed.length === 0) {
    newsFeed = store.feeds = makeFeeds(getData(NEWS_URL));
  }

  for(let i = (store.currentPage - 1) * 10; i &lt; store.currentPage * 10; i++) {
    newsList.push(`
      &lt;div class=&quot;p-6 ${newsFeed[i].read ? &#39;bg-red-500&#39; : &#39;bg-white&#39;} mt-6 rounded-lg shadow-md transition-colors duration-500 hover:bg-green-100&quot;&gt;
        &lt;div class=&quot;flex&quot;&gt;
          &lt;div class=&quot;flex-auto&quot;&gt;
            &lt;a href=&quot;#/show/${newsFeed[i].id}&quot;&gt;${newsFeed[i].title}&lt;/a&gt;  
          &lt;/div&gt;
          &lt;div class=&quot;text-center text-sm&quot;&gt;
            &lt;div class=&quot;w-10 text-white bg-green-300 rounded-lg px-0 py-2&quot;&gt;${newsFeed[i].comments_count}&lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;flex mt-3&quot;&gt;
          &lt;div class=&quot;grid grid-cols-3 text-sm text-gray-500&quot;&gt;
            &lt;div&gt;&lt;i class=&quot;fas fa-user mr-1&quot;&gt;&lt;/i&gt;${newsFeed[i].user}&lt;/div&gt;
            &lt;div&gt;&lt;i class=&quot;fas fa-heart mr-1&quot;&gt;&lt;/i&gt;${newsFeed[i].points}&lt;/div&gt;
            &lt;div&gt;&lt;i class=&quot;far fa-clock mr-1&quot;&gt;&lt;/i&gt;${newsFeed[i].time_ago}&lt;/div&gt;
          &lt;/div&gt;  
        &lt;/div&gt;
      &lt;/div&gt;    
    `);
  }

  template = template.replace(&#39;{{__news_feed__}}&#39;, newsList.join(&#39;&#39;));
  template = template.replace(&#39;{{__prev_page__}}&#39;, store.currentPage &gt; 1 ? store.currentPage - 1 : 1);
  template = template.replace(&#39;{{__next_page__}}&#39;, store.currentPage + 1);

  container.innerHTML = template;
}

function newsDetail() {
  const id = location.hash.substr(7);
  const newsContent = getData(CONTENT_URL.replace(&#39;@id&#39;, id))
  let template = `
    &lt;div class=&quot;bg-gray-600 min-h-screen pb-8&quot;&gt;
      &lt;div class=&quot;bg-white text-xl&quot;&gt;
        &lt;div class=&quot;mx-auto px-4&quot;&gt;
          &lt;div class=&quot;flex justify-between items-center py-6&quot;&gt;
            &lt;div class=&quot;flex justify-start&quot;&gt;
              &lt;h1 class=&quot;font-extrabold&quot;&gt;Hacker News&lt;/h1&gt;
            &lt;/div&gt;
            &lt;div class=&quot;items-center justify-end&quot;&gt;
              &lt;a href=&quot;#/page/${store.currentPage}&quot; class=&quot;text-gray-500&quot;&gt;
                &lt;i class=&quot;fa fa-times&quot;&gt;&lt;/i&gt;
              &lt;/a&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class=&quot;h-full border rounded-xl bg-white m-6 p-4 &quot;&gt;
        &lt;h2&gt;${newsContent.title}&lt;/h2&gt;
        &lt;div class=&quot;text-gray-400 h-20&quot;&gt;
          ${newsContent.content}
        &lt;/div&gt;

        {{__comments__}}

      &lt;/div&gt;
    &lt;/div&gt;
  `;

  for(let i=0; i &lt; store.feeds.length; i++) {
    if (store.feeds[i].id === Number(id)) {
      store.feeds[i].read = true;
      break;
    }
  }

  function makeComment(comments, called = 0) {
    const commentString = [];

    for(let i = 0; i &lt; comments.length; i++) {
      commentString.push(`
        &lt;div style=&quot;padding-left: ${called * 40}px;&quot; class=&quot;mt-4&quot;&gt;
          &lt;div class=&quot;text-gray-400&quot;&gt;
            &lt;i class=&quot;fa fa-sort-up mr-2&quot;&gt;&lt;/i&gt;
            &lt;strong&gt;${comments[i].user}&lt;/strong&gt; ${comments[i].time_ago}
          &lt;/div&gt;
          &lt;p class=&quot;text-gray-700&quot;&gt;${comments[i].content}&lt;/p&gt;
        &lt;/div&gt;      
      `);

      if (comments[i].comments.length &gt; 0) {
        commentString.push(makeComment(comments[i].comments, called + 1));
      }
    }

    return commentString.join(&#39;&#39;);
  }

  container.innerHTML = template.replace(&#39;{{__comments__}}&#39;, makeComment(newsContent.comments));
}

function router() {
  const routePath = location.hash;

  if (routePath === &#39;&#39;) {
    newsFeed();
  } else if (routePath.indexOf(&#39;#/page/&#39;) &gt;= 0) {
    store.currentPage = Number(routePath.substr(7));
    newsFeed();
  } else {
    newsDetail()
  }
}

window.addEventListener(&#39;hashchange&#39;, router);

router();
</code></pre><h2 id="에필로그">에필로그</h2>
<p>기능이 많아지는 것은 규모가 커지면 커질 수록 당연한 일이다. 그러나 코드를 복잡하게 짜서는 안된다. 코드를 복잡하지 않게 프로그래밍 하는 일이 개발자의 역량이라고 할 수 있다.</p>
<p>어떻게 하면 복잡도를 늘리지 않고 규모를 늘릴 수 있을까?가 큰 연구중에 하나라고 한다. </p>
<p>연습이 답이다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[댓글과 대댓글 구현 ]]></title>
            <link>https://velog.io/@dev__note/%EB%8C%93%EA%B8%80%EA%B3%BC-%EB%8C%80%EB%8C%93%EA%B8%80-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@dev__note/%EB%8C%93%EA%B8%80%EA%B3%BC-%EB%8C%80%EB%8C%93%EA%B8%80-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Fri, 02 Sep 2022 10:38:24 GMT</pubDate>
            <description><![CDATA[<h2 id="프롤로그">프롤로그</h2>
<p>상세페이지에서 댓글과 대댓글 부분의 데이터를 가져와서 구현해 보도록 하자.</p>
<h3 id="1-comment-함수-만들기">1. comment 함수 만들기</h3>
<p>상세페이지 부분의 <code>{{__comment__}}</code> 부분을 대체 할 함수를 만들어 보자.</p>
<p>comment를 만들기에 앞서 데이터의 구조가 어떻게 되어있는지를 살펴 보자.
<img src="https://velog.velcdn.com/images/dev__note/post/4d6b3654-1951-4997-bedb-40880b57229f/image.png" alt=""></p>
<p>comments 밑에 comments 
comments 밑에 또 comments ... 
댓글 - 대댓글 -- 대댓글 이런식으로 객체가 넘어온다. 생각해 볼 수 있는 포인트는 여기서 댓글이 각 게시글 당 몇개가 올지는 모른다는 점이다.</p>
<p>다만 ui 를 그릴 때 댓글을 그려주는 구조는 똑같을 테니까 함수로 만들어 놓으면 되겠다 라는 논리가 생긴다. </p>
<pre><code> function makeComment(comments, called = 0) {
    const commentString = [];

    for(let i = 0; i &lt; comments.length; i++) {
      commentString.push(`
        &lt;div style=&quot;padding-left: ${called * 40}px;&quot; class=&quot;mt-4&quot;&gt;
          &lt;div class=&quot;text-gray-400&quot;&gt;
            &lt;i class=&quot;fa fa-sort-up mr-2&quot;&gt;&lt;/i&gt;
            &lt;strong&gt;${comments[i].user}&lt;/strong&gt; ${comments[i].time_ago}
          &lt;/div&gt;
          &lt;p class=&quot;text-gray-700&quot;&gt;${comments[i].content}&lt;/p&gt;
        &lt;/div&gt;      
      `);

      if (comments[i].comments.length &gt; 0) {
        commentString.push(makeComment(comments[i].comments, called + 1));
      }
    }

    return commentString.join(&#39;&#39;);
  }

  container.innerHTML = template.replace(&#39;{{__comments__}}&#39;, makeComment(newsContent.comments));</code></pre><p>makeComment 를 만들어서 commnets 값을 데이터로 받는다. commentString 이라는 빈 배열에 commnets 개수 만큼 값을 넣어주게 된다. </p>
<p>댓글이 몇개 올지 모르니까 그 안에 만약 대댓글이 있으면 commentString 에 다시 자기 자신의 함수를 불러서 넣어주고 대댓글이 없을 때 까지 이를 반복한다. </p>
<p>이것을 재귀함수라고 한다. 갯수가 몇개인지 모르는 상황에서 반복되는 경우가 있을 때 사용한다. </p>
<p>그리고 끝나면 <code>{{__comments__}}</code> 를 대체 시켜준다. </p>
<blockquote>
<p>재귀 함수 - 자기 자신을 호출 </p>
</blockquote>
<p>또 ui 로 봤을 때 대댓글이 달리면 padding-left 값이 40px 씩 들여쓰기를 해 주는데, 이때 called 라는 값을 넘겨주면서 대댓글이 있으면 +1 씩 올려주면서 padding 값을 조절 해준다. </p>
<h2 id="에필로그">에필로그</h2>
<p>js 에서 나오는 함수들은 정말 코드 마일리지가 많이 쌓여야 하는 것 같다. 아무리 개념 공부를 한다고 해서 되는게 아니라 그 상황을 많이 만나서 써보고 익숙해 져야지 내 것이 되는 듯 하다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[템플릿 방식 vs 기존 DOM 방식,  tailwind 스타일링 하기 ]]></title>
            <link>https://velog.io/@dev__note/%ED%85%9C%ED%94%8C%EB%A6%BF-%EB%B0%A9%EC%8B%9D-vs-%EA%B8%B0%EC%A1%B4-DOM-%EB%B0%A9%EC%8B%9D-tailwind-%EC%8A%A4%ED%83%80%EC%9D%BC%EB%A7%81-%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dev__note/%ED%85%9C%ED%94%8C%EB%A6%BF-%EB%B0%A9%EC%8B%9D-vs-%EA%B8%B0%EC%A1%B4-DOM-%EB%B0%A9%EC%8B%9D-tailwind-%EC%8A%A4%ED%83%80%EC%9D%BC%EB%A7%81-%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 02 Sep 2022 10:16:49 GMT</pubDate>
            <description><![CDATA[<h2 id="프롤로그">프롤로그</h2>
<p>마크업을 그릴 때 js 로 하다보니 DOM 을 많이 건드리는 방식을 사용했다. <code>id=root</code> 를 잡아서 <code>createElement</code>를 하고 <code>appendChild</code> 를 하고.. 그런데 이 방식의 단점은 직관적으로 마크업의 구조를 볼 수 없다는 것이다. (개발자 입장에서) </p>
<p>그래서 문자열을 사용하는 방식으로 조금 개선을 했었다. 빽틱<code>(``)</code>을 활용한 템플릿 방식을 사용했는데 이 부분도 li 처럼 반복되는 부분이 있으면 배열을 사용해야 했고, 배열의 사용도 여전히 한계가 존재 했다. </p>
<p>코드가 간단할때야 문제가 없지만 코드가 복잡해 지기 시작하면 코드상 ui 구조 파악이 쉽지 않기 때문이다. 그래서 조금 더 코드를 명확하게 볼 수 있는 템플릿 구조를 사용해 보고자 한다. </p>
<p>템플릿 방식으로 변경을 하고 나서 tailwind 를 이용해서 UI 를 스타일링 하기로 한다. </p>
<h3 id="1-템플릿-방식">1. 템플릿 방식</h3>
<pre><code>function newsFeed() {
  const newsFeed = getData(NEWS_URL);
  const newsList = [];
  let template = `
    &lt;div class=&quot;container mx-auto p-4&quot;&gt;
      &lt;h1&gt;Hacker News&lt;/h1&gt;
      &lt;ul&gt;
        {{__news_feed__}}      
      &lt;/ul&gt;
      &lt;div&gt;
        &lt;a href=&quot;#/page/{{__prev_page__}}&quot;&gt;이전 페이지&lt;/a&gt;
        &lt;a href=&quot;#/page/{{__next_page__}}&quot;&gt;다음 페이지&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  `;

  for(let i = (store.currentPage - 1) * 10; i &lt; store.currentPage * 10; i++) {
    newsList.push(`
      &lt;li&gt;
        &lt;a href=&quot;#/show/${newsFeed[i].id}&quot;&gt;
          ${newsFeed[i].title} (${newsFeed[i].comments_count})
        &lt;/a&gt;
      &lt;/li&gt;
    `);
  }

  template = template.replace(&#39;{{__news_feed__}}&#39;, newsList.join(&#39;&#39;));
  template = template.replace(&#39;{{__prev_page__}}&#39;, store.currentPage &gt; 1 ? store.currentPage - 1 : 1);
  template = template.replace(&#39;{{__next_page__}}&#39;, store.currentPage + 1);

  container.innerHTML = template;
}</code></pre><p>전체 코드를 작성해 놓고 데이터가 들어갈 부분을 <code>{{__news__feed__}}</code> 이런 식으로 작성해서 이후에 <code>replace</code> 해준다. (오호 이 부분은 좀 신선했다.🧐)</p>
<p>템플릿 사용의 장점은 이것이다.</p>
<ul>
<li>코드 상 ui 를 알아보기 쉽다. </li>
<li>어떤 데이터가 들어가는 지 한눈에 파악하기 쉽다.</li>
</ul>
<p>하지만, 이 부분도 여전한 한계가 존재 한다. </p>
<h3 id="2-tailwind-로-ui-스타일링-하기">2. tailwind 로 ui 스타일링 하기</h3>
<p>사용된 스펙</p>
<blockquote>
<p>fontawsome 
tailwind </p>
</blockquote>
<pre><code>function newsFeed() {
  const newsFeed = getData(NEWS_URL);
  const newsList = [];
  let template = `
    &lt;div class=&quot;bg-gray-600 min-h-screen&quot;&gt;
      &lt;div class=&quot;bg-white text-xl&quot;&gt;
        &lt;div class=&quot;mx-auto px-4&quot;&gt;
          &lt;div class=&quot;flex justify-between items-center py-6&quot;&gt;
            &lt;div class=&quot;flex justify-start&quot;&gt;
              &lt;h1 class=&quot;font-extrabold&quot;&gt;Hacker News&lt;/h1&gt;
            &lt;/div&gt;
            &lt;div class=&quot;items-center justify-end&quot;&gt;
              &lt;a href=&quot;#/page/{{__prev_page__}}&quot; class=&quot;text-gray-500&quot;&gt;
                Previous
              &lt;/a&gt;
              &lt;a href=&quot;#/page/{{__next_page__}}&quot; class=&quot;text-gray-500 ml-4&quot;&gt;
                Next
              &lt;/a&gt;
            &lt;/div&gt;
          &lt;/div&gt; 
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;p-4 text-2xl text-gray-700&quot;&gt;
        {{__news_feed__}}        
      &lt;/div&gt;
    &lt;/div&gt;
  `;

    for(let i = (store.currentPage - 1) * 10; i &lt; store.currentPage * 10; i++) {
    newsList.push(`
      &lt;div class=&quot;p-6 bg-white mt-6 rounded-lg shadow-md transition-colors duration-500 hover:bg-green-100&quot;&gt;
        &lt;div class=&quot;flex&quot;&gt;
          &lt;div class=&quot;flex-auto&quot;&gt;
            &lt;a href=&quot;#/show/${newsFeed[i].id}&quot;&gt;${newsFeed[i].title}&lt;/a&gt;  
          &lt;/div&gt;
          &lt;div class=&quot;text-center text-sm&quot;&gt;
            &lt;div class=&quot;w-10 text-white bg-green-300 rounded-lg px-0 py-2&quot;&gt;${newsFeed[i].comments_count}&lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;flex mt-3&quot;&gt;
          &lt;div class=&quot;grid grid-cols-3 text-sm text-gray-500&quot;&gt;
            &lt;div&gt;&lt;i class=&quot;fas fa-user mr-1&quot;&gt;&lt;/i&gt;${newsFeed[i].user}&lt;/div&gt;
            &lt;div&gt;&lt;i class=&quot;fas fa-heart mr-1&quot;&gt;&lt;/i&gt;${newsFeed[i].points}&lt;/div&gt;
            &lt;div&gt;&lt;i class=&quot;far fa-clock mr-1&quot;&gt;&lt;/i&gt;${newsFeed[i].time_ago}&lt;/div&gt;
          &lt;/div&gt;  
        &lt;/div&gt;
      &lt;/div&gt;    
    `);
  }

  template = template.replace(&#39;{{__news_feed__}}&#39;, newsList.join(&#39;&#39;));
  template = template.replace(&#39;{{__prev_page__}}&#39;, store.currentPage &gt; 1 ? store.currentPage - 1 : 1);
  template = template.replace(&#39;{{__next_page__}}&#39;, store.currentPage + 1);

  container.innerHTML = template;
}</code></pre><h4 id="목록-부분-이미지-결과">목록 부분 이미지 결과</h4>
<p><img src="https://velog.velcdn.com/images/dev__note/post/8bded6bc-b0c5-4850-ad41-dcb9ea65daf9/image.png" alt=""></p>
<pre><code>function newsDetail() {
  const id = location.hash.substr(7);
  const newsContent = getData(CONTENT_URL.replace(&#39;@id&#39;, id))
  let template = `
    &lt;div class=&quot;bg-gray-600 min-h-screen pb-8&quot;&gt;
      &lt;div class=&quot;bg-white text-xl&quot;&gt;
        &lt;div class=&quot;mx-auto px-4&quot;&gt;
          &lt;div class=&quot;flex justify-between items-center py-6&quot;&gt;
            &lt;div class=&quot;flex justify-start&quot;&gt;
              &lt;h1 class=&quot;font-extrabold&quot;&gt;Hacker News&lt;/h1&gt;
            &lt;/div&gt;
            &lt;div class=&quot;items-center justify-end&quot;&gt;
              &lt;a href=&quot;#/page/${store.currentPage}&quot; class=&quot;text-gray-500&quot;&gt;
                &lt;i class=&quot;fa fa-times&quot;&gt;&lt;/i&gt;
              &lt;/a&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class=&quot;h-full border rounded-xl bg-white m-6 p-4 &quot;&gt;
        &lt;h2&gt;${newsContent.title}&lt;/h2&gt;
        &lt;div class=&quot;text-gray-400 h-20&quot;&gt;
          ${newsContent.content}
        &lt;/div&gt;

        {{__comments__}}

      &lt;/div&gt;
    &lt;/div&gt;
  `;


}</code></pre><h4 id="상세-페이지-부분">상세 페이지 부분</h4>
<p><img src="https://velog.velcdn.com/images/dev__note/post/2b96a3b7-4b7b-4fb7-a1f9-a5d4cfe2f5fb/image.png" alt=""></p>
<h2 id="에필로그">에필로그</h2>
<p>tailwind 같은 라이브러리는 클래스 명이 정해져 있어 관리자 페이지 같이 깔끔하거나 카드 박스 형태의 디자인이 있을 때에는 사용 하기 좋을 것 같다. 하지만 디자인 작업이 많이 들어갔다면 조금 한계가 있을 듯 하다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[페이징 구현하기]]></title>
            <link>https://velog.io/@dev__note/%ED%8E%98%EC%9D%B4%EC%A7%95-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dev__note/%ED%8E%98%EC%9D%B4%EC%A7%95-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 02 Sep 2022 09:56:13 GMT</pubDate>
            <description><![CDATA[<h2 id="프롤로그">프롤로그</h2>
<p>지난 포스팅까지 두개의 화면을 만들었다.데이터를 불러오게 되면 많은 게시글이 있어 페이지네이션이 생기게 되는데 이번에는 페이징을 구현하고자 한다. </p>
<h3 id="1-페이징-구현하기">1. 페이징 구현하기</h3>
<p>게시글 상세에서 목록보기를 클릭할 때 전에 보고 있던 페이지가 예를들어 2페이지라면 그대로 2페이지로 목록을 보여줘야 사용자가 원할하게 사용 할 것이다. (만약 1페이로 초기화가 된다면 좀 화날지도 😡 ) 그래서 그 페이지를 담는 변수가 필요한 데 여러 군데에서 사용되는 변수는 따로 객체를 만들어 담아 놓는 것이 좋다. (공유되는 자원들은 하나로 묶어놓자) </p>
<pre><code>// 공유되는 자원이란 의미로 store 라고 명명 
const store = {
  currentPage: 1,
};

function newsFeed() {
  const newsFeed = getData(NEWS_URL);
  const newsList = [];

  newsList.push(&#39;&lt;ul&gt;&#39;);

  // 10개씩 게시글이 보여진다고 할 때 리스트가 10개씩 한 묶음으로 만들어 져야 하니까 10을 곱해주었다.
  for(let i = (store.currentPage - 1) * 10; i &lt; store.currentPage * 10; i++) {
    newsList.push(`
        &lt;li&gt;
        &lt;a href=&quot;#/show/${newsFeed[i].id}&quot;&gt;
          ${newsFeed[i].title} (${newsFeed[i].comments_count})
        &lt;/a&gt;
      &lt;/li&gt;
    `);
  }

  newsList.push(&#39;&lt;/ul&gt;&#39;);
  newsList.push(`
  &lt;div&gt;
    &lt;a href=&quot;#/page/${store.currentPage - 1}&quot;&gt;이전 페이지&lt;/a&gt;
    &lt;a href=&quot;#/page/${store.currentPage + 1}&quot;&gt;다음 페이지&lt;/a&gt;
  &lt;/div&gt;
  `);

  container.innerHTML = newsList.join(&#39;&#39;);
}


</code></pre><p>주솟값이 <code>#/page/currentPage</code> , <code>#/show/currentPage</code> 이런식으로 바뀌었다. 그 이유인 즉슨 페이징을 구현하기 전에는 #뒤에 무조건 id 값이 온다고 생각해서 빈칸이면 목록을 구현하고 그렇지 않으면 상세를 보여주기로 했었다. 그런데 페이지네이션을 구현하게 되면 뒤에 무조건 뭔가가 올 테니까 구분할 인자가 필요 했다. </p>
<p>그래서 page 를 보여줄 항목에는 <code>/page/</code> 를 
상세페이지는 <code>/show/</code> 를 오게 처리해주고 주소 url 에 해당 단어가 들어 있으면 <code>indexOf</code> 목록 또는 상세페이지로 처리해 주었다. </p>
<pre><code>function router() {
  const routePath = location.hash;

  if (routePath === &#39;&#39;) {
    newsFeed();
  } else if (routePath.indexOf(&#39;#/page/&#39;) &gt;= 0) {
    store.currentPage = Number(routePath.substr(7));
    newsFeed();
  } else {
    newsDetail()
  }
}</code></pre><h3 id="2-결함-수정">2. 결함 수정</h3>
<p>이전에 작성 해 놓았던 코드를 살짝 수정 해보자</p>
<pre><code>
function newsDetail() {
//substr(1) 이 아니라 이제는 7이 되어야 한다. /page/ 등으로 구분을 해 놓았기 때문! 
  const id = location.hash.substr(7);
  const newsContent = getData(CONTENT_URL.replace(&#39;@id&#39;, id))

  container.innerHTML = `
    &lt;h1&gt;${newsContent.title}&lt;/h1&gt;

    &lt;div&gt;
      &lt;a href=&quot;#/page/${store.currentPage}&quot;&gt;목록으로&lt;/a&gt;
    &lt;/div&gt;
  `;
}
</code></pre><h3 id="3-방어코드-작성">3. 방어코드 작성</h3>
<p>방어코드는 이런 것이다. 예를들면 이전 페이지를 눌렀을 때 currentPage -1 을 했는데 만약 현재페이지가 0 이었다면??? 
url 이 없는 페이지를 나타내게 하면 안되므로 조건문을 걸어서 처리 해 줘야 한다. </p>
<pre><code>newsList.push(`
    &lt;div&gt;
      &lt;a href=&quot;#/page/${store.currentPage &gt; 1 ? store.currentPage - 1 : 1}&quot;&gt;이전 페이지&lt;/a&gt;
      &lt;a href=&quot;#/page/${store.currentPage + 1}&quot;&gt;다음 페이지&lt;/a&gt;
    &lt;/div&gt;
  `);</code></pre><h3 id="4-전체-코드">4. 전체 코드</h3>
<pre><code>const container = document.getElementById(&#39;root&#39;);
const ajax = new XMLHttpRequest();
const content = document.createElement(&#39;div&#39;);
const NEWS_URL = &#39;https://api.hnpwa.com/v0/news/1.json&#39;;
const CONTENT_URL = &#39;https://api.hnpwa.com/v0/item/@id.json&#39;;
const store = {
  currentPage: 1,
};

function getData(url) {
  ajax.open(&#39;GET&#39;, url, false);
  ajax.send();

  return JSON.parse(ajax.response);
}

function newsFeed() {
  const newsFeed = getData(NEWS_URL);
  const newsList = [];

  newsList.push(&#39;&lt;ul&gt;&#39;);

  for(let i = (store.currentPage - 1) * 10; i &lt; store.currentPage * 10; i++) {
    newsList.push(`
      &lt;li&gt;
        &lt;a href=&quot;#/show/${newsFeed[i].id}&quot;&gt;
          ${newsFeed[i].title} (${newsFeed[i].comments_count})
        &lt;/a&gt;
      &lt;/li&gt;
    `);
  }

  newsList.push(&#39;&lt;/ul&gt;&#39;);
  newsList.push(`
    &lt;div&gt;
      &lt;a href=&quot;#/page/${store.currentPage &gt; 1 ? store.currentPage - 1 : 1}&quot;&gt;이전 페이지&lt;/a&gt;
      &lt;a href=&quot;#/page/${store.currentPage + 1}&quot;&gt;다음 페이지&lt;/a&gt;
    &lt;/div&gt;
  `);

  container.innerHTML = newsList.join(&#39;&#39;);
}

function newsDetail() {
  const id = location.hash.substr(7);
  const newsContent = getData(CONTENT_URL.replace(&#39;@id&#39;, id))

  container.innerHTML = `
    &lt;h1&gt;${newsContent.title}&lt;/h1&gt;

    &lt;div&gt;
      &lt;a href=&quot;#/page/${store.currentPage}&quot;&gt;목록으로&lt;/a&gt;
    &lt;/div&gt;
  `;
}

function router() {
  const routePath = location.hash;

  if (routePath === &#39;&#39;) {
    newsFeed();
  } else if (routePath.indexOf(&#39;#/page/&#39;) &gt;= 0) {
    store.currentPage = Number(routePath.substr(7));
    newsFeed();
  } else {
    newsDetail()
  }
}

window.addEventListener(&#39;hashchange&#39;, router);

router();
</code></pre><h2 id="에필로그">에필로그</h2>
<p>이번에 배운 점은 *<em>공유되는 자원은 따로 빼 놓는 게 코드를 효율적으로 관리 하는 것 *</em> 이다. </p>
<p>여담으로 .. 저 store 객체는 나중에 정~~ 말 어려워지는 코드로 변환하고야 마는데 ..ㅜㅜ 사실 store 부분에 이것저것 코드가 추가되고 하면서 따라가기 어려워서 처음으로 돌아오게 되었다. 이 부분은 정말 중요하다! !</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[라우터 - 화면 처리기 만들기 ]]></title>
            <link>https://velog.io/@dev__note/%EB%9D%BC%EC%9A%B0%ED%84%B0-%ED%99%94%EB%A9%B4-%EC%B2%98%EB%A6%AC%EA%B8%B0-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@dev__note/%EB%9D%BC%EC%9A%B0%ED%84%B0-%ED%99%94%EB%A9%B4-%EC%B2%98%EB%A6%AC%EA%B8%B0-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Thu, 01 Sep 2022 08:25:40 GMT</pubDate>
            <description><![CDATA[<h2 id="프롤로그">프롤로그</h2>
<p>이번에는 한 화면 내에서 페이지가 전환 되는 것 처럼 보이게 만들어보려고 한다. (SPA) 이전까지의 코드에서는 <code>appendChild</code> 로 삽입하는 코드만 있었는데 한 화면 내에서 화면전환이 일어나게 보이려면 원래 있던 콘텐츠를 새로운 콘텐츠로 overWrite 해줘야한다. 라우터를 만드는 단계흐름을 살펴보자. </p>
<h3 id="1-구조-구축하기">1. 구조 구축하기</h3>
<p><code>container.innerHTML</code> 에 새롭게 덮여씌여 지는 구조를 만들어 준다. 추가적으로, 코드를 작성하는 사람이 직관적으로 알아 볼 수 있도록 creatElement 를 이용해 구조를 만드는 것이 아니라 문자열을 사용해서 구조를 수정 해주었다. </p>
<h4 id="목록이-나올-부분">목록이 나올 부분</h4>
<pre><code>const newsList = [];

newsList.push(&#39;&lt;ul&gt;&#39;);

for(let i = 0; i &lt; 10; i++) {
  newsList.push(`
    &lt;li&gt;
      &lt;a href=&quot;#${newsFeed[i].id}&quot;&gt;
        ${newsFeed[i].title} (${newsFeed[i].comments_count})
      &lt;/a&gt;
    &lt;/li&gt;
  `);
}

newsList.push(&#39;&lt;/ul&gt;&#39;);

container.innerHTML = newsList.join(&#39;&#39;);</code></pre><p>문자열을 사용해서 만들 때 위와 같이 <code>ul 태그</code>에 <code>li 태그</code> 를 반복해서 넣어야 하는 구조라면 하드코딩으로 하게 되면 비효율적이 되게 된다. 그래서 반복문을 사용해야 하는데 이때 자주 사용하는 것이 배열구조를 많이 사용한다고 한다. </p>
<blockquote>
<ol>
<li>빈 배열을 하나 만들고 </li>
<li><code>&lt;ul&gt;</code> 을 넣는다. </li>
<li>그리고 반복문으로 <code>li</code> 구조를 만들어서 배열에 <code>push</code> 한다.</li>
<li>마지막에 <code>&lt;/ul&gt;</code> 를 <code>push</code> 한다. </li>
<li>그리고 DOM 에 그려 줄 때에는 배열에 담긴 요소들을 하나로 합쳐서 그려줘야 한다. </li>
<li><code>join</code> 을 사용하게 되는데 이때 기본적으로 합칠 때 <code>,</code> 로 합쳐지게 되는데 <code>,</code>는 필요없으니 빈값을 넣어준다. </li>
</ol>
</blockquote>
<h4 id="상세페이지가-나올-부분">상세페이지가 나올 부분</h4>
<pre><code>window.addEventListener(&#39;hashchange&#39;, function() {
  const id = location.hash.substr(1);

  const newsContent = getData(CONTENT_URL.replace(&#39;@id&#39;, id))

  container.innerHTML = `
    &lt;h1&gt;${newsContent.title}&lt;/h1&gt;

    &lt;div&gt;
      &lt;a href=&quot;#&quot;&gt;목록으로&lt;/a&gt;
    &lt;/div&gt;
  `;
});</code></pre><h3 id="2-함수-분리">2. 함수 분리</h3>
<p>가장 간단한 게시판 구조를 생각해보자. 게시글 목록이 나오고, 사용자가 게시글을 클릭하면 상세 게시글을 보게 된다. 그리고 뒤로 돌아가기를 눌렀을 때 다시 목록화면이 나와야 한다. </p>
<p>이렇게 화면을 왔다갔다 할 수 있게 중계(전환)하는 것을 라우터 라고 한다. 라우터를 만들기 위해서는 하나로 묶여 있는 코드 즉, 함수를 실행시켜야 하는데 현재의 코드는 함수로 묶여 있는 코드가 아니다. 그래서 기능별로 함수로 분리를 시키자.</p>
<p>함수는 기능을 알아 볼 수 있도록 직관적으로 작성하는 것이 좋다.</p>
<h4 id="피드목록">피드(목록)</h4>
<pre><code>function newsFeed() {
  const newsFeed = getData(NEWS_URL);
  const newsList = [];

  newsList.push(&#39;&lt;ul&gt;&#39;);

  for(let i = 0; i &lt; 10; i++) {
    newsList.push(`
      &lt;li&gt;
        &lt;a href=&quot;#${newsFeed[i].id}&quot;&gt;
          ${newsFeed[i].title} (${newsFeed[i].comments_count})
        &lt;/a&gt;
      &lt;/li&gt;
    `);
  }

  newsList.push(&#39;&lt;/ul&gt;&#39;);

  container.innerHTML = newsList.join(&#39;&#39;);
}
</code></pre><h4 id="상세">상세</h4>
<p>상세페이지 피드는 이벤트 핸들러에 묶여 있었다. 그래서 함수로 만들고 따로 분리를 시켜 주었다. </p>
<pre><code>function newsDetail() {
  const id = location.hash.substr(1);
  const newsContent = getData(CONTENT_URL.replace(&#39;@id&#39;, id))

  container.innerHTML = `
    &lt;h1&gt;${newsContent.title}&lt;/h1&gt;

    &lt;div&gt;
      &lt;a href=&quot;#&quot;&gt;목록으로&lt;/a&gt;
    &lt;/div&gt;
  `;
}

window.addEventListener(&#39;hashchange&#39;, newsDetail);

</code></pre><h3 id="3-라우터-작성">3. 라우터 작성</h3>
<p>이제 화면 전환을 구현해보자. </p>
<ul>
<li><p>우선 뉴스피드와 뉴스디테일을 함수로 모두 묶어 놨으니 첫 화면에서 한번 쯤은 뉴스피드 함수를 불러줘야 한다. </p>
</li>
<li><p>이벤트가 (hashchage) 일어 났을 때에도 화면 전환이 일어나야 하니까 브라우저 입장에서는 언제 어떤 피드를 불러줘야 하냐도 고려해야한다. </p>
<pre><code>function router() {
const routePath = location.hash;

if (routePath === &#39;&#39;) {
  newsFeed();
} else {
  newsDetail();
}
}
</code></pre></li>
</ul>
<p>window.addEventListener(&#39;hashchange&#39;, router);</p>
<p>router();</p>
<pre><code>`location.hash` 를 잡아서 그 값에 따라 어떤 화면을 보여 줄 지 나뉘었다. 화면 전환이 일어나는 이벤트에는 라우터 함수를 연결해주면 newsDetail 함수로만 고정되지 않게 된다. 


### 4. 전체 코드</code></pre><p>const container = document.getElementById(&#39;root&#39;);
const ajax = new XMLHttpRequest();
const content = document.createElement(&#39;div&#39;);
const NEWS_URL = &#39;<a href="https://api.hnpwa.com/v0/news/1.json&#39;">https://api.hnpwa.com/v0/news/1.json&#39;</a>;
const CONTENT_URL = &#39;<a href="https://api.hnpwa.com/v0/item/@id.json&#39;">https://api.hnpwa.com/v0/item/@id.json&#39;</a>;</p>
<p>function getData(url) {
  ajax.open(&#39;GET&#39;, url, false);
  ajax.send();</p>
<p>  return JSON.parse(ajax.response);
}</p>
<p>function newsFeed() {
  const newsFeed = getData(NEWS_URL);
  const newsList = [];</p>
<p>  newsList.push(&#39;<ul>&#39;);</p>
<p>  for(let i = 0; i &lt; 10; i++) {
    newsList.push(<code>&lt;li&gt;
        &lt;a href=&quot;#${newsFeed[i].id}&quot;&gt;
          ${newsFeed[i].title} (${newsFeed[i].comments_count})
        &lt;/a&gt;
      &lt;/li&gt;</code>);
  }</p>
<p>  newsList.push(&#39;</ul>&#39;);</p>
<p>  container.innerHTML = newsList.join(&#39;&#39;);
}</p>
<p>function newsDetail() {
  const id = location.hash.substr(1);
  const newsContent = getData(CONTENT_URL.replace(&#39;@id&#39;, id))</p>
<p>  container.innerHTML = `
    <h1>${newsContent.title}</h1></p>
<pre><code>&lt;div&gt;
  &lt;a href=&quot;#&quot;&gt;목록으로&lt;/a&gt;
&lt;/div&gt;</code></pre><p>  `;
}</p>
<p>function router() {
  const routePath = location.hash; //화면을 전환하는 기준 값 </p>
<p>  if (routePath === &#39;&#39;) {
    newsFeed();
  } else {
    newsDetail();
  }
}</p>
<p>window.addEventListener(&#39;hashchange&#39;, router);</p>
<p>router();</p>
<pre><code>
## 에필로그 
뭔가 강의를 듣다 보면 처음부터 함수로 작성하는게 아니라 약간 원시적인 코드부터 시작해서 점점 발전하는 코드를 보여준다. 함수를 사용하면 왜 좋은지, 이런 코드 보다 저런 코드는 어떨지 등등의 생각포인트를 알려주는데 좋은 것 같다. 해커뉴스 클론코딩 시리즈 말고 이전에 작성했던 리액트 30 시리즈에서는 사실 너무 깔끔한 코드로 코딩을 하는데 나같은 아직 비기너들은 음 그렇군의 수준이기 때문이다. 뭔가 처음부터 바로 그런 코드를 작성하기 까지는 시간이 꽤 걸리는 데 이때 이런 코드의 흐름을 알고 가면 정말 좋을 것 같다. 결론은 이 강의 좋고 시간 아깝지 않다. !</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[두개의 화면을 가진 웹 앱 ]]></title>
            <link>https://velog.io/@dev__note/%EB%91%90%EA%B0%9C%EC%9D%98-%ED%99%94%EB%A9%B4%EC%9D%84-%EA%B0%80%EC%A7%84-%EC%9B%B9-%EC%95%B1</link>
            <guid>https://velog.io/@dev__note/%EB%91%90%EA%B0%9C%EC%9D%98-%ED%99%94%EB%A9%B4%EC%9D%84-%EA%B0%80%EC%A7%84-%EC%9B%B9-%EC%95%B1</guid>
            <pubDate>Thu, 01 Sep 2022 07:16:36 GMT</pubDate>
            <description><![CDATA[<h2 id="프롤로그">프롤로그</h2>
<p>지난 12줄의 코드에서 점점 더 개선을 하는 작업을 하게 된다. 기본으로 나타났던 목록에서 해당 게시물을 누르면 상세 페이지에 대한 정보를 불러오는 작업까지 작성 해 보자.</p>
<p>나아가서 중복되는 코드는 함수로 묶어주면서 점점 더 반복되는 코드를 없앤다.
<em>(지난 포스팅에 더해져서 이어가는 코드라 순서대로 보는 것을 권장한다!)</em> </p>
<h3 id="1-구분짓는-id값-설정">1. 구분짓는 id값 설정</h3>
<p>리스트를 만들 때 해당 값을 구별 할 수 있는 id 를 a 태그의 href 속성으로 설정 해 주었다. 코드를 보자. </p>
<pre><code>for(let i = 0; i &lt; 10; i++) {
  const div = document.createElement(&#39;div&#39;)

  div.innerHTML = `
    &lt;li&gt;
      &lt;a href=&quot;${newsFeed[i].id}&quot;&gt;
        ${newsFeed[i].title} (${newsFeed[i].comments_count})
      &lt;/a&gt;&quot;
    &lt;/li&gt;
  `;

  ul.appendChild(div.firstElementChild);
}</code></pre><p>데이터를 newsFeed라는 배열 데이터에 담아놓는다. 
for 문으로 객체로 받은 데이터를 돌리면서 title, comments_count, id 를 세팅 해 주고 있다. </p>
<p>객체화 된 데이터를 잠깐 보자면 이렇다. </p>
<p><img src="https://velog.velcdn.com/images/dev__note/post/8f7b575e-595f-4bd7-9c9a-c577a72e68bf/image.png" alt=""></p>
<h3 id="2-해당-게시물의-데이터-받아-오기">2. 해당 게시물의 데이터 받아 오기</h3>
<p>id 값에 따라 달라지는 데이터를 받아 오기 위해 다시 api 를 호출한다. 
id 값에 따라 달라지는 url 주소가 되므로 @id 로 적어 놓고, 이후에 대체하는 식으로 작성해 놓았다. 바로 아래 이벤트에서 다뤄 보자.</p>
<pre><code>const CONTENT_URL = &#39;https://api.hnpwa.com/v0/item/@id.json&#39;;
</code></pre><h3 id="3-아이템-클릭-감지">3. 아이템 클릭 감지</h3>
<p>사용자가 해당 게시물을 클릭했을 때 컴퓨터는 어떻게 감지할까? 답은 윈도우에서 자체 이벤트를 제공해 준다. 그래서 <code>addEventListenr</code> 라는 메서드를 이용해서 클릭을 감지해 주고, 그에 해당하는 데이터를 불러오게 한다. (로직) </p>
<pre><code>window.addEventListener(&#39;hashchange&#39;, function() {
  const id = location.hash.substr(1);

  ajax.open(&#39;GET&#39;, CONTENT_URL.replace(&#39;@id&#39;, id), false);
  ajax.send();

  const newsContent = JSON.parse(ajax.response);
  const title = document.createElement(&#39;h1&#39;);

  title.innerHTML = newsContent.title;

  content.appendChild(title);
});</code></pre><ol>
<li><p><code>hashchange</code> 라는 이벤트를 이용해서 주소값에 #뒤가 바뀌면 #뒤의 id 값을 잡아와서 변수에 담아준다. </p>
</li>
<li><p>@id 를 변수에 담아 놓은 id 로 대체 한다. </p>
</li>
<li><p>이제 url 주소값을 잡았으니 api 호출하고 받아와서 <code>JSON.parse</code>를 통해 객체로 만들어 준다. </p>
</li>
</ol>
<h3 id="중간-전체코드">중간) 전체코드</h3>
<p>전체코드를 잠시 보자</p>
<pre><code>const container = document.getElementById(&#39;root&#39;);
const ajax = new XMLHttpRequest();
const content = document.createElement(&#39;div&#39;);
const NEWS_URL = &#39;https://api.hnpwa.com/v0/news/1.json&#39;;
const CONTENT_URL = &#39;https://api.hnpwa.com/v0/item/@id.json&#39;;

ajax.open(&#39;GET&#39;, NEWS_URL, false);
ajax.send();

const newsFeed = JSON.parse(ajax.response);
const ul = document.createElement(&#39;ul&#39;);

window.addEventListener(&#39;hashchange&#39;, function() {
  const id = location.hash.substr(1);

  ajax.open(&#39;GET&#39;, CONTENT_URL.replace(&#39;@id&#39;, id), false);
  ajax.send();

  const newsContent = JSON.parse(ajax.response);
  const title = document.createElement(&#39;h1&#39;);

  title.innerHTML = newsContent.title;

  content.appendChild(title);
});

for(let i = 0; i &lt; 10; i++) {
  const div = document.createElement(&#39;div&#39;)

  div.innerHTML = `
    &lt;li&gt;
      &lt;a href=&quot;${newsFeed[i].id}&quot;&gt;
        ${newsFeed[i].title} (${newsFeed[i].comments_count})
      &lt;/a&gt;&quot;
    &lt;/li&gt;
  `;

  ul.appendChild(div.firstElementChild);
}

container.appendChild(ul);
container.appendChild(content);
</code></pre><h3 id="4중복-코드-합치기">4.중복 코드 합치기</h3>
<p>강사님은 마무리로 항상 중복코드를 합쳐가며 코드를 깔끔하게 유지하기를 강조하셨다. 뒤로 가면 갈 수록 (나는 안보이거나 생각 못한 ) 중복 코드를 꺼내서 따로 빼고 분리하는 작업을 하시는데 .. wow 이다. 이렇게 코드를 다루는구나 싶었다. </p>
<p>일단 api 를 호출 하는 부분이 계속 반복 되니까 저 부분은 함수로 합치자. 지금은 반복 되는 부분이 api 밖에 없다. </p>
<pre><code>function getData(url) {
  ajax.open(&#39;GET&#39;, url, false);
  ajax.send();

  return JSON.parse(ajax.response);
}
</code></pre><h3 id="5-마무리-전체-코드">5. 마무리 전체 코드</h3>
<pre><code>const container = document.getElementById(&#39;root&#39;);
const ajax = new XMLHttpRequest();
const content = document.createElement(&#39;div&#39;);
const NEWS_URL = &#39;https://api.hnpwa.com/v0/news/1.json&#39;;
const CONTENT_URL = &#39;https://api.hnpwa.com/v0/item/@id.json&#39;;

function getData(url) {
  ajax.open(&#39;GET&#39;, url, false);
  ajax.send();

  return JSON.parse(ajax.response);
}

const newsFeed = getData(NEWS_URL);
const ul = document.createElement(&#39;ul&#39;);

window.addEventListener(&#39;hashchange&#39;, function() {
  const id = location.hash.substr(1);

  const newsContent = getData(CONTENT_URL.replace(&#39;@id&#39;, id))
  const title = document.createElement(&#39;h1&#39;);

  title.innerHTML = newsContent.title;

  content.appendChild(title);
});

for(let i = 0; i &lt; 10; i++) {
  const div = document.createElement(&#39;div&#39;);

  div.innerHTML =  `
    &lt;li&gt;
      &lt;a href=&quot;#${newsFeed[i].id}&quot;&gt;
        ${newsFeed[i].title} (${newsFeed[i].comments_count})
      &lt;/a&gt;
    &lt;/li&gt;
  `;

  ul.appendChild(div.firstElementChild);
}

container.appendChild(ul);
container.appendChild(content);
</code></pre><h2 id="에필로그">에필로그</h2>
<p>화면을 어떻게 전환시키는가를 이해하는것이 해당 어플리케이션을 빠르게 이해 할 수 있다고 말해주셨다. 웹은 url 로만 전환을 시켜왔고, react 를 할 때 spa 를 처음 접해봤는데 이러한 부분도 어플이나 웹앱을 쓸 때 좀 더 유의깊게 봐야 할 포인트인 것 같다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[12줄의 코드로 시작하기 ]]></title>
            <link>https://velog.io/@dev__note/12%EC%A4%84%EC%9D%98-%EC%BD%94%EB%93%9C%EB%A1%9C-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dev__note/12%EC%A4%84%EC%9D%98-%EC%BD%94%EB%93%9C%EB%A1%9C-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 01 Sep 2022 06:34:22 GMT</pubDate>
            <description><![CDATA[<h2 id="프롤로그">프롤로그</h2>
<p>이번에는 김민태의 프론트엔드 아카데미를 들으며 프로그래밍을 익히고 있다. 제 1강 JavaScript &amp; TypeScript Essential 은 HackerNews 라는 사이트를 만들어보면서 코드의 퀄리티를 점차적으로 개선해 나가는 작업을 보여준다. 한번 따라 해봤는데 꽤 어려웠다.🥲 </p>
<p>class 구조와 전역상태 관리 동기에서 비동기 api 처리를 하는 과정에서 결국 막히게 되었다.<del><em>(따라가기 힘들었다는 말)</em></del> 다시 들으면서 코드를 분석해 보기 위한 작업을 시작하려고 한다. 모르는 채로 진도 나가는 것 보다 하나를 더 깊이 파보는 게 더 도움이 될 것 같아 시간이 걸리더라도 천천히 작성해 봐야겠다. </p>
<h3 id="어플리케이션의-본질">어플리케이션의 본질</h3>
<p><code>입력 데이터</code>를 가지고 어떠한 <code>처리과정</code>을 거쳐 <code>출력데이터</code>로 바꾸는 것</p>
<h3 id="개발-환경">개발 환경</h3>
<blockquote>
<p>parcel
javascript</p>
</blockquote>
<h3 id="1-데이터-가져오기">1. 데이터 가져오기</h3>
<pre><code>const ajax = new XMLHttpRequest(); 
const NEWS_URL = &#39;https://api.hnpwa.com/v0/news/1.json&#39;; //데이터를 불러 올 주소 

ajax.open(&#39;GET&#39;, NEWS_URL, false); //false : 동기로 가져오도록 하기 위함
ajax.send();</code></pre><h3 id="2-데이터-처리-객체로-변환">2. 데이터 처리 (객체로 변환)</h3>
<pre><code>const newsFeed = JSON.parse(ajax.response); // 받아 온 데이터를 객체로 변환 -JSON </code></pre><h3 id="3-데이터-출력">3. 데이터 출력</h3>
<pre><code>const ul = document.createElement(&#39;ul&#39;); 

// li 를 반복하며 생성해서 ul 태그 밑으로 넣어준다. 
for(let i = 0; i &lt; 10; i++) {
  const li = document.createElement(&#39;li&#39;);

  li.innerHTML = newsFeed[i].title;

  ul.appendChild(li);
}

document.getElementById(&#39;root&#39;).appendChild(ul); //만든 ul 을 DOM에 그려주기 </code></pre><h3 id="전체-12줄의-코드">전체 12줄의 코드</h3>
<pre><code>const ajax = new XMLHttpRequest(); 
const NEWS_URL = &#39;https://api.hnpwa.com/v0/news/1.json&#39;; //데이터를 불러 올 주소 

ajax.open(&#39;GET&#39;, NEWS_URL, false);
ajax.send();

const newsFeed = JSON.parse(ajax.response); // 받아 온 데이터를 객체로 변환 -JSON 
const ul = document.createElement(&#39;ul&#39;); 

// li 를 반복하며 생성해서 ul 태그 밑으로 넣어준다. 
for(let i = 0; i &lt; 10; i++) {
  const li = document.createElement(&#39;li&#39;);

  li.innerHTML = newsFeed[i].title;

  ul.appendChild(li);
}

document.getElementById(&#39;root&#39;).appendChild(ul); //만든 ul 을 DOM에 그려주기 </code></pre><h2 id="에필로그">에필로그</h2>
<p>어플리케이션의 본질은 데이터를 입력받아 처리하고 출력하는 것이다. 그게 바탕이 된 상태에서 기능을 좀 더 추가하고 디자인을 입히고 하면서 점점 발전 해 나가는 것. </p>
<p>보이는 화면은 그대로여도 어떻게 코드를 가독성있게 만들고 깔끔하게 만드는지에 대해 생각하는 것. </p>
<p>코드 마일리지가 진짜 많이 쌓여야 자유자재로 코드를 현명하게 짤 수 있을 것이라는 생각이든다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[react 30] #3 갤러리 구현하기]]></title>
            <link>https://velog.io/@dev__note/react-30-3-%EA%B0%A4%EB%9F%AC%EB%A6%AC-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dev__note/react-30-3-%EA%B0%A4%EB%9F%AC%EB%A6%AC-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 03 Aug 2022 12:29:08 GMT</pubDate>
            <description><![CDATA[<h2 id="프롤로그">프롤로그</h2>
<p>cra 와 typescript을 사용하여 갤러리를 구현하는 작업을 진행하였다.
패키지 매니저로는 yarn 을 사용했고, 
두가지 방식으로 갤러리에 파일을 추가하는 방식을 정리해 보고자 한다.</p>
<h2 id="cra-개발환경-세팅">CRA 개발환경 세팅</h2>
<p>🗣 next.js 를 설치 한 상태에서 진행하였습니다.</p>
<h3 id="1-yarn-설치">1. yarn 설치</h3>
<pre><code class="language-javascript">npm install -g yarn</code></pre>
<p>yarn 을 설치 하고 yarn start 를 했을 때 오류 
▶︎ couldn&#39;t find a package.json </p>
<p>해당 파일 내에 package.json 파일이 존재하지 않는다! 
이럴때에는 <code>yarn init</code> 으로 package.json 파일을 생성해주면 된다.</p>
<p>몇가지 질문이 나타나는데 그에 대한 대답을 써주면 된다.</p>
<p><img src="https://velog.velcdn.com/images/dev__note/post/f34ffdd7-79cb-44b5-9c19-614350120a85/image.png" alt=""></p>
<pre><code class="language-javascript">{
  &quot;name&quot;: &quot;project30&quot;, //프로젝트 이름 
  &quot;version&quot;: &quot;1.0.0&quot;, // 프로젝트 버전 
  &quot;description&quot;: &quot;practice react&quot;, // 설명
  &quot;main&quot;: &quot;index.js&quot;, // 첫 시작점 위치
  &quot;repository&quot;: &quot;https://github.com/bok-world/project30.git&quot;, // 저장소 위치 
  &quot;author&quot;: &quot;bk &lt;rnjsqhrud10@gmail.com&gt;&quot;, // 작업자 
  &quot;license&quot;: &quot;MIT&quot;, //라인센스 
  &quot;private&quot;: true // 개인 &amp; 공적 
}
</code></pre>
<h3 id="2-폴더-구조">2. 폴더 구조</h3>
<p>cra 를 설치하면 기본으로 설치되는 것들 중 중요한 것들만 말하고자 한다.
처음 리액트를 배울때 저런 폴더구조에 대해서 궁금한게 많았는데 직접 개발환경을 설정하고 설명을 듣다보니 점차 해소가 되고 있는 중이다. </p>
<pre><code class="language-javascript">- node_moudles //node.js 에서 install 된 개발하는데 필요한 소스나 라이브러리
- public //개발된 리액트 코드가 들어가서 보여지게 되는 폴더
    ⌞ index.html
- src // 여기서 주로 코드를 다루게 된다.
    ⌞ components // 컴포넌트에 대한 폴더 (생성) 
    ⌞ App.css
    ⌞ App.test.tsx
    ⌞ index.css
    ⌞ index.tsx
    ⌞ logo.svg
    ⌞ ...
- .gitignore // git에 올릴 때 제외하는 코드들 
- package.json // 어떤 라이브러리들이 install 되어있는지 보여주고 스크립트를 실행 할 수 있게 해주는 부분 
- README.md
- tsconfig.json //타입스크립트의 설정파일들 
- yarn-error.log
- yarn.lock //세부적인 라이브러리의 설정들 </code></pre>
<h2 id="구조와-components">구조와 Components</h2>
<p><img src="https://velog.velcdn.com/images/dev__note/post/4b0481fa-ba30-4037-aee5-d0e1fc1403a7/image.png" alt=""></p>
<blockquote>
<ol>
<li>초기화면은 &quot;이미지가 없습니다. 이미지를 추가해주세요&quot; 라는 글귀와 + 박스 </li>
</ol>
</blockquote>
<p><img src="https://velog.velcdn.com/images/dev__note/post/d639de5f-8fc3-4d5e-8a12-119b21acb4b9/image.png" alt=""></p>
<blockquote>
<ol start="2">
<li>박스를 눌러 파일을 첨부하게 되면 이미지가 하나씩 추가되면서 화면상에 보여진다. 이미지를 추가 해 달라는 글귀는 없어지게 되고, 이미지를 추가 할 때 마다 한줄로 정렬되며 나타난다. </li>
</ol>
</blockquote>
<p><em>따라 할 때는 무심코 하니 쉬워보였지만 
다시 거꾸로 돌아가 어떤식으로 짰구나 라고 돌아보니 
처음에 어떻게 짰을지 막막 했을 것 같다.</em></p>
<p>큰 틀은 이러하다.</p>
<blockquote>
<ul>
<li>초기화면을 만든다.</li>
</ul>
</blockquote>
<ul>
<li>박스를 클릭하면 파일 첨부를 한다. 현재 타겟 데이터를 받는다.</li>
<li>list 배열에 추가 시킨다.</li>
<li>받은 파일 정보를 dataURL 로 변경시켜 </li>
<li>각각의 파일들을 이미지로 보여지게 만든다.</li>
</ul>
<p>컴포넌트를 이용해 구조를 작성했다. </p>
<p>리액트의 장점은 컴포넌트를 사용할수 있다는 점이다.
컴포넌트의 좋은점은 <code>&quot;재사용 가능하다&quot;</code></p>
<h3 id="0-component에-대해">0. Component에 대해</h3>
<p>컴포넌트는 이미 내장되어 있는 태그를 조합하고, 거기에 스타일, 동작까지 입혀서 (문서,스타일,동작,표현)을 <code>한꺼번에 만들고 재활용하는 방식</code> 이라고 정의 할 수 있다.</p>
<pre><code class="language-javascript">&lt;내가지은이름 name =&quot;Mark&quot; /&gt;
&lt;내가지은이름 props={false}&gt;내용&lt;/내가지은이름&gt;

// src,class,name,props 밖에서 넣어주는 데이터
// 문서(HTML), 스타일(css), 동작(JS) 를 합쳐서 내가 만든 일종의 태그</code></pre>
<h3 id="1-초기화면-작성하기">1. 초기화면 작성하기</h3>
<p>html 구조 짜는 것과 비슷하면서도 비슷하지 않았다. 
<em>컴포넌트 활용한다는 것이 가장 큰 차이!</em>
우선 밑에 코드를 보자 </p>
<pre><code class="language-javascript">import React, { useRef, useState, useCallback } from &quot;react&quot;;
import &quot;./App.css&quot;;
import ImageBox from &quot;./components/ImageBox&quot;;

function App() {
  const inpRef = useRef&lt;HTMLInputElement&gt;(null);
  const [imageList, setImageList] = useState&lt;string[]&gt;([]);


  return (
    &lt;div className=&quot;container&quot;&gt;
      &lt;div className={&quot;gallery-box &quot; + (imageList.length &gt; 0 &amp;&amp; &quot;row&quot;)}&gt;
        {imageList.length === 0 &amp;&amp; (
          &lt;div className=&quot;text-center&quot;&gt;
            이미지가 없습니다. &lt;br /&gt; 이미지를 추가해주세요.
          &lt;/div&gt;
        )}

        &lt;div
          className=&quot;plus-box&quot;
        &gt;
          +
          &lt;input
             type=&quot;file&quot;
             ref={inpRef}
          /&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
}

export default App;
</code></pre>
<p>❗️ <strong>알고가면 좋을 내용들</strong> ❗️</p>
<blockquote>
<p>파일 첨부를 id 를 사용해서 label 을 만드는  방법은 react 스럽지 못하다. 리액트에서는 <code>useRef</code> 를 사용한다.</p>
</blockquote>
<p>리액트는 가상 dom 이기 때문에 id 값을 사실상 잡을 수 없다.
훅을 이용해서 useRef 를 만들어주고 input 태그에는 <code>ref={inpRef}</code> 라는 변수(?) 를 지정해 준다.</p>
<h3 id="2-공통구조는-컴포넌트로">2. 공통구조는 컴포넌트로</h3>
<p>이미지 박스는 사실 같은 구조이다. 그 안에 들어가는 src 만 다를뿐이기 때문에 구조를 공통화 시킨다.</p>
<pre><code class="language-javascript">function ImageBox(props: { src: string }) {
  return (
    &lt;div className=&quot;img-box&quot;&gt;
      &lt;img src={props.src} /&gt;
    &lt;/div&gt;
  );
}

export default ImageBox;

</code></pre>
<p>❗️ <strong>알고가면 좋을 내용들</strong> ❗️</p>
<blockquote>
</blockquote>
<ul>
<li>변경되는 부분은 props 로 받을 수 있다. </li>
<li>props는 object 들만 올 수 있다. </li>
<li>이 프로젝트는 ts 기반이기 때문에 타입을 지정해 주었다. </li>
<li>리액트는 js 기반이다. js 에서 class는 예약어이므로 className 로 클래스명을 지정해 준다. </li>
<li>다른 부분에서도 사용할 것이기 때문에 export 를 시켜준다.</li>
</ul>
<h3 id="3-파일-데이터-상태-변경--저장">3. 파일 데이터 상태 변경 &amp; 저장</h3>
<ol>
<li>클릭했을 때 파일 정보를 받아 올것이고 ▸ click 이벤트</li>
<li>파일에 대한 정보는 useState로 변경해준다. ▸ 하나가 아니므로 배열로 저장해 준다.</li>
<li>배열로 저장한 값들 각각을 map 함수를 이용해 컴포넌트로 바꿔준다. ▸ 각각의 배열들은 key 값을 설정 해 준다. </li>
</ol>
<pre><code class="language-javascript">import React, { useRef, useState } from &quot;react&quot;;
import &quot;./App.css&quot;;
import ImageBox from &quot;./components/ImageBox&quot;;

function App() {
  const inpRef = useRef&lt;HTMLInputElement&gt;(null);
  const [imageList, setImageList] = useState&lt;string[]&gt;([]);


  return (
    &lt;div className=&quot;container&quot;&gt;
      &lt;div className={&quot;gallery-box &quot; + (imageList.length &gt; 0 &amp;&amp; &quot;row&quot;)}&gt;
        {imageList.length === 0 &amp;&amp; (
          &lt;div className=&quot;text-center&quot;&gt;
            이미지가 없습니다. &lt;br /&gt; 이미지를 추가해주세요.
          &lt;/div&gt;
        )}

        {imageList.map((el, idx) =&gt; (
          &lt;ImageBox key={el + idx} src={el} /&gt;
        ))}
        &lt;div
          className=&quot;plus-box&quot;
           onClick={() =&gt; {
             inpRef.current?.click();
           }}
        &gt;
          +
          &lt;input
             type=&quot;file&quot;
             ref={inpRef}

             onChange={(event) =&gt; {
               if (event.currentTarget.files?.[0]) {
                 console.log(event.currentTarget.files[0]);
                 const file = event.currentTarget.files[0];

                 const reader = new FileReader();
                 reader.readAsDataURL(file);
                 reader.onloadend = (event) =&gt; {
                   setImageList((prev) =&gt; [
                     ...prev,
                     event.target?.result as string,
                   ]);
                 };
               }
             }}
          /&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
}

export default App;
</code></pre>
<p>❗️ <strong>알고가면 좋을 내용들</strong> ❗️</p>
<blockquote>
<ol>
<li>true &amp;&amp; true ▸ true
true &amp;&amp; 1 ▸ 1 
이란 특성을 이용해서 
삼항연산자를  &amp;&amp; 로써 단축 할 수 있다. </li>
<li>event.currentTarget.files?.[0] 부분도 마찬가지이다. </li>
</ol>
</blockquote>
<h2 id="에필로그">에필로그</h2>
<p>각각의 파일을 배열에 담고, url 로 변경시켜 화면에 나타나게 할 수 있었다. 복잡한 코드를 함축시켜 놓은 느낌이라 정말 간편하다고 느꼈고, 
별도의 화면의 전환 없이 한 화면에서 해결 되는 점이 정말 깔끔하다고 느꼈다.</p>
<p>추가적으로클릭 이벤트 말고도 드래그 드롭으로 <code>react-dropzone</code> 이라는 라이브러리를 설치 해 사용자의 경험을 훨씬 더 개선시켰다. </p>
<p>라이브러리를 사용하니 앞서 사용했던 hook 들이 소용없어졌고 훨씬 더 간편해졌다는 점이다 <del><em>(허무)</em></del> 그래도 저런 라이브러리는 사용하고 적용시키며 원리를 파악해보는 것도 중요하다고 생각한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[react 30] #2 이미지 슬라이드]]></title>
            <link>https://velog.io/@dev__note/react-30-2-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%8A%AC%EB%9D%BC%EC%9D%B4%EB%93%9C</link>
            <guid>https://velog.io/@dev__note/react-30-2-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%8A%AC%EB%9D%BC%EC%9D%B4%EB%93%9C</guid>
            <pubDate>Mon, 01 Aug 2022 09:15:41 GMT</pubDate>
            <description><![CDATA[<h2 id="프롤로그">프롤로그</h2>
<p>이미지 슬라이드를 사용할 때에 실무에서는 주로 라이브러리를 사용하곤 했었다. 이번 강의에서는 자바스크립트 클래스문법으로 구현을 다시 해보는 작업을 가졌다. 역시 라이브러리는 편하지만 직접 작동 방식을 생각하면서 하는게 도움이 훨씬 더 많이 된다. </p>
<h2 id="webpack-개발환경-세팅">webpack 개발환경 세팅</h2>
<p>지난 포스팅 slider 와 같은 개발환경으로 진행하기로 한다.
<a href="https://velog.io/@dev__note/30%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-1-%EA%B0%80%EC%83%81-%ED%82%A4%EB%B3%B4%EB%93%9C-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0">webpack 이용한 개발환경 세팅</a></p>
<p>추가적으로 세팅해야 할 몇몇 사항들이다. </p>
<h3 id="1-폰트-어썸-설치">1. 폰트 어썸 설치</h3>
<pre><code class="language-javascript">npm install --save @fortawesome/fontawesome-free
</code></pre>
<p>style.css 상단에 불러와준다.</p>
<pre><code class="language-javascript">@import url(&#39;~@fortawesome/fontawesome-free/css/all.min.css&#39;);</code></pre>
<h3 id="2-webpack-에서-이미지를-처리하는-방식의-설정">2. webpack 에서 이미지를 처리하는 방식의 설정</h3>
<p>웹 팩에서는 이미지를 처리하는 방식을 따로 설정해 주어야 한다고 한다.
webpack.config.js 파일로 들어간다.</p>
<pre><code class="language-javascript">
module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, &#39;css-loader&#39;],
      },
      //새로 추가하는 부분 - 이미지 처리 방식
      {
        test: /\.jpeg$/, //jpeg파일을 만나면
        type: &#39;asset/inline&#39;, //asset/inline에 있는 파일들을 webpack 에 있는 내장된 로더로 읽어 들이겠다 라는 의미 
      },
    ],
  },</code></pre>
<h2 id="html--css-구조-세팅">html &amp; css 구조 세팅</h2>
<p>코드를 냅다 보여주는 것보다 html 은 사실 박스 구조로 이해를 하는게 더 좋다고 생각하기 때문에 구조를 그려봤다. </p>
<p>웹퍼블리싱 공부를 처음 할때 강사님이 강조했던 것은 코드를 치기 전에 구조를 먼저 생각하라는 점이었다. 박스 구조 짜는 연습을 했던 것이 빠르게 레이아웃을 만드는데 도움이 많이 되었다.</p>
<p><img src="https://velog.velcdn.com/images/dev__note/post/5557f32e-7d18-4e80-a7b3-9b66cd025ce2/image.jpeg" alt="박스구조"></p>
<p>가장 큰 아우터 랩이 slider-wrap 이라고 할 수 있다. 작게 그려놨지만 전체적인 구조를 보면 저렇게 div 로 감싸고 있는 큰 아우터가 존재하고, 그 안에 각각의 요소를 감싸주는 ul 랩과 각각의 요소들인 li 들이 존재한다. 
그리고 화면에 보여지는 것은 파란색부분(가장 큰 아우터 랩) 이 될 것이다. </p>
<p>요소가 나열되어 있어야 슬라이더들이 옆으로 움직일때 자연스러운 움직임이 가능하기 때문이다. 슬라이더들이 나열되고, 그 위에 position 으로 <code>화살표</code>, <code>play 버튼</code>, <code>indicator</code> 들을 위치 시켰다.</p>
<p><img src="https://velog.velcdn.com/images/dev__note/post/a989f998-71d2-4e53-8c7a-8a7dce21339f/image.png" alt="이미지 슬라이드"></p>
<p>위 이미지 처럼 박스구조를 이해하고 나서 코드를 보자!
훨씬 더 이해가 잘 될 것이다.</p>
<pre><code class="language-javascript">
&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;title&gt;&lt;%= htmlWebpackPlugin.options.title %&gt;&lt;/title&gt;
  &lt;/head&gt;

  &lt;body&gt;
    &lt;div class=&quot;slider-wrap&quot; id=&quot;slider-wrap&quot;&gt;
      &lt;ul class=&quot;list slider&quot; id=&quot;slider&quot;&gt;
        &lt;li&gt;
          &lt;img src=&quot;&lt;%= require(&#39;./src/image/red.jpeg&#39;) %&gt;&quot; /&gt;
        &lt;/li&gt;
        &lt;li&gt;
          &lt;img src=&quot;&lt;%= require(&#39;./src/image/orange.jpeg&#39;) %&gt;&quot; /&gt;
        &lt;/li&gt;
        &lt;li&gt;
          &lt;img src=&quot;&lt;%= require(&#39;./src/image/yellow.jpeg&#39;) %&gt;&quot; /&gt;
        &lt;/li&gt;
        &lt;li&gt;
          &lt;img src=&quot;&lt;%= require(&#39;./src/image/green.jpeg&#39;) %&gt;&quot; /&gt;
        &lt;/li&gt;
        &lt;li&gt;
          &lt;img src=&quot;&lt;%= require(&#39;./src/image/blue.jpeg&#39;) %&gt;&quot; /&gt;
        &lt;/li&gt;
        &lt;li&gt;
          &lt;img src=&quot;&lt;%= require(&#39;./src/image/indigo.jpeg&#39;) %&gt;&quot; /&gt;
        &lt;/li&gt;
        &lt;li&gt;
          &lt;img src=&quot;&lt;%= require(&#39;./src/image/violet.jpeg&#39;) %&gt;&quot; /&gt;
        &lt;/li&gt;
      &lt;/ul&gt;

      &lt;div class=&quot;btn next&quot; id=&quot;next&quot;&gt;&lt;i class=&quot;fa fa-arrow-right&quot;&gt;&lt;/i&gt;&lt;/div&gt;
      &lt;div class=&quot;btn previous&quot; id=&quot;previous&quot;&gt;
        &lt;i class=&quot;fa fa-arrow-left&quot;&gt;&lt;/i&gt;
      &lt;/div&gt;

      &lt;div class=&quot;indicator-wrap&quot; id=&quot;indicator-wrap&quot;&gt;
        &lt;ul&gt;
          &lt;!-- &lt;li class=&quot;active&quot;&gt;&lt;/li&gt;
          &lt;li&gt;&lt;/li&gt;
          &lt;li&gt;&lt;/li&gt;
          &lt;li&gt;&lt;/li&gt;
          &lt;li&gt;&lt;/li&gt;
          &lt;li&gt;&lt;/li&gt;
          &lt;li&gt;&lt;/li&gt; --&gt;
        &lt;/ul&gt;
      &lt;/div&gt;

      &lt;div class=&quot;control-wrap play&quot; id=&quot;control-wrap&quot;&gt;
        &lt;i class=&quot;fa fa-pause&quot; id=&quot;pause&quot; data-status=&quot;pause&quot;&gt;&lt;/i&gt;
        &lt;i class=&quot;fa fa-play&quot; id=&quot;play&quot; data-status=&quot;play&quot;&gt;&lt;/i&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/body&gt;
&lt;/html&gt;

</code></pre>
<h2 id="구현해야-할-기능들">구현해야 할 기능들</h2>
<p>기능단위로 나누어서 코드를 구현하였다. 하나하나 다시 살펴보자.</p>
<blockquote>
<ol>
<li>next, prev 버튼 작동 <ol start="2">
<li>인디케이터 </li>
<li>autoplay 기능</li>
</ol>
</li>
</ol>
</blockquote>
<h3 id="1-next-prev-버튼-구현">1. next, prev 버튼 구현</h3>
<p>이미지 슬라이드의 원리는 간단하다. 
next 버튼을 누르면 슬라이드요소들이 모두 한칸씩 왼쪽으로 위치 이동하는 것이다. 그렇게 되면 필요한게 전체 <code>슬라이드의 갯수</code> 와 <code>슬라이드 한개의 길이</code> 이다. </p>
<ul>
<li>assignElement()에 각각의 요소들을 잡아준다.</li>
<li>addEvent() 에는 이벤트를 실행 시켜 주는 함수를 만들어주고, this를 바인딩 시켜준다.</li>
<li>moveToRight(), moveToLeft() 를 만들어 각각 해당 길이 만큼 위치를 이동할 수 있도록 한다. </li>
<li>initSliderNumber(), initSliderWidth(), initSliderListWidth() 는 해당 슬라이드에 대한 전체 길이나 갯수 등을 초기화 하는 함수이다. </li>
</ul>
<pre><code class="language-javascript">export default class ImageSlider {
  #currentPosition = 0;
  #slideNumber = 0;
  #slideWidth = 0;

  sliderWrapEl;
  sliderListEl;
  nextBtnEl;
  previousBtnEl;

  constructor() {
    this.assignElement();
    this.initSliderNumber();
    this.initSliderWidth();
    this.initSLiderListWidth();
    this.addEvent();

  }

  assignElement() {
    this.sliderWrapEl = document.getElementById(&#39;slider-wrap&#39;);
    this.sliderListEl = this.sliderWrapEl.querySelector(&#39;#slider&#39;);
    this.nextBtnEl = this.sliderWrapEl.querySelector(&#39;#next&#39;);
    this.previousBtnEl = this.sliderWrapEl.querySelector(&#39;#previous&#39;);
  }

//슬라이더 갯수 
  initSliderNumber() {
    this.#slideNumber = this.sliderListEl.querySelectorAll(&#39;li&#39;).length;
  }

//슬라이더 가로값
  initSliderWidth() {
    this.#slideWidth = this.sliderListEl.clientWidth;
  }

//전체 감싸고 있는 슬라이더 아우터 가로값
  initSLiderListWidth() {
    this.sliderListEl.style.width = `${this.#slideNumber * this.#slideWidth}px`;
  }

  addEvent() {
    this.nextBtnEl.addEventListener(&#39;click&#39;, this.moveToRight.bind(this));
    this.previousBtnEl.addEventListener(&#39;click&#39;, this.moveToLeft.bind(this));
  }


// 오른쪽 버튼 눌렀을 시, 왼쪽으로 한칸 씩 이동해 준다. 그리고 마지막 슬라이더라면 초기화 위치값을 초기화시켜준다.
  moveToRight() {
    this.#currentPosition += 1;
    if (this.#currentPosition === this.#slideNumber) {
      this.#currentPosition = 0;
    }
    this.sliderListEl.style.left = `-${
      this.#slideWidth * this.#currentPosition
    }px`;
  }

// 왼쪽 버튼 눌렀을 시 오른쪽으로 한칸씩 이동해준다. 그리고 마지막 슬라이더라면 초기화 위치값을 초기화시켜준다.
  moveToLeft() {
    this.#currentPosition -= 1;
    if (this.#currentPosition === -1) {
      this.#currentPosition = this.#slideNumber - 1;
    }
    this.sliderListEl.style.left = `-${
      this.#slideWidth * this.#currentPosition
    }px`;
  }



}
</code></pre>
<h3 id="2-인디케이터">2. 인디케이터</h3>
<p>인디케이터는 지표라고 생각하면 된다. 해당 번호를 누르면 그에 해당하는 슬라이더가 나타 날 수 있게 한다. </p>
<ul>
<li>인디케이터들을 슬라이더갯수에 따라 만들고</li>
<li>활성화 되었을때 어떻게 될지 style을 구현한다.</li>
<li>그리고 클릭 했을때의 이벤트 변화를 구현한다.</li>
</ul>
<pre><code class="language-javascript">
export default class ImageSlider {
  #currentPosition = 0;
  #slideNumber = 0;
  #slideWidth = 0;

  sliderWrapEl;
  sliderListEl;
  nextBtnEl;
  previousBtnEl;

  indeicaterWrapEl;
  controlWrapEl;

  constructor() {
    this.assignElement();
    this.initSliderNumber();
    this.initSliderWidth();
    this.initSLiderListWidth();
    this.addEvent();
    this.createIndicater();
    this.setIndicator();

  }

  assignElement() {
    this.sliderWrapEl = document.getElementById(&#39;slider-wrap&#39;);
    this.sliderListEl = this.sliderWrapEl.querySelector(&#39;#slider&#39;);
    this.nextBtnEl = this.sliderWrapEl.querySelector(&#39;#next&#39;);
    this.previousBtnEl = this.sliderWrapEl.querySelector(&#39;#previous&#39;);
    this.indeicaterWrapEl = this.sliderWrapEl.querySelector(&#39;#indicator-wrap&#39;);
    this.controlWrapEl = this.sliderWrapEl.querySelector(&#39;#control-wrap&#39;);

  }


  initSliderNumber() {
    this.#slideNumber = this.sliderListEl.querySelectorAll(&#39;li&#39;).length;
  }

  initSliderWidth() {
    this.#slideWidth = this.sliderListEl.clientWidth;
  }

  initSLiderListWidth() {
    this.sliderListEl.style.width = `${this.#slideNumber * this.#slideWidth}px`;
  }

  addEvent() {
    this.nextBtnEl.addEventListener(&#39;click&#39;, this.moveToRight.bind(this));
    this.previousBtnEl.addEventListener(&#39;click&#39;, this.moveToLeft.bind(this));
     this.indeicaterWrapEl.addEventListener(
      &#39;click&#39;,
      this.onClickIndicator.bind(this),
    );
  }


// 인디케이터 클릭 시 작동 
onClickIndicator(event) {
    const indexPosition = parseInt(event.target.dataset.index, 10);
    if (Number.isInteger(indexPosition)) {
      this.#currentPosition = indexPosition;
      this.sliderListEl.style.left = `-${
        this.#slideWidth * this.#currentPosition
      }px`;
      this.setIndicator();
    }
    console.log(indexPosition);
  }

  moveToRight() {
    this.#currentPosition += 1;
    if (this.#currentPosition === this.#slideNumber) {
      this.#currentPosition = 0;
    }
    this.sliderListEl.style.left = `-${
      this.#slideWidth * this.#currentPosition
    }px`;
  }

  moveToLeft() {
    this.#currentPosition -= 1;
    if (this.#currentPosition === -1) {
      this.#currentPosition = this.#slideNumber - 1;
    }
    this.sliderListEl.style.left = `-${
      this.#slideWidth * this.#currentPosition
    }px`;
  }


// 인디케이터 생성 - 슬라이더 갯수에 따라 만들어주기
createIndicater() {
    const docFragment = document.createDocumentFragment();
    for (let i = 0; i &lt; this.#slideNumber; i += 1) {
      const li = document.createElement(&#39;li&#39;);
      li.dataset.index = i;
      docFragment.appendChild(li);
      this.indeicaterWrapEl.querySelector(&#39;ul&#39;).appendChild(docFragment);
    }
  }

//인디케이터 세팅 - 활성화 되었을 시 세팅하기
  setIndicator() {
    this.indeicaterWrapEl
      .querySelector(&#39;li.active&#39;)
      ?.classList.remove(&#39;active&#39;);

    this.indeicaterWrapEl
      .querySelector(`ul li:nth-child(${this.#currentPosition + 1})`)
      .classList.add(&#39;active&#39;);
  }



}


</code></pre>
<h3 id="3-자동플레이-버튼-구현">3. 자동플레이 버튼 구현</h3>
<p> 대부분의 슬라이더는 사용자가 건들이지 않아도 자동으로 슬라이드가 진행된다. 만약 사용자가 일시정지 버튼을 누르면 이미지가 멈추고, 다시 재생하면 슬라이드가 재생되야하는데 여기서 생각해야 할 것은</p>
<ul>
<li>setInterval을 시켜줘 무한 재생이지만</li>
<li>일시정지를 누르면 clearInterval 을 시켜준다.</li>
<li>인디케이터도 그에 대응해 같이 따라와야하고, </li>
<li>무한재생 되고 있을 때 인디케이터 버튼을 누르면 clear 를 시켜줘야 한다.</li>
</ul>
<p> 아래는 전체 script이다. </p>
<pre><code class="language-javascript">
export default class ImageSlider {
  #currentPosition = 0;
  #slideNumber = 0;
  #slideWidth = 0;
  #intervalId;
  #autoPlay = true;

  sliderWrapEl;
  sliderListEl;
  nextBtnEl;
  previousBtnEl;
  indeicaterWrapEl;
  controlWrapEl;

  constructor() {
    this.assignElement();
    this.initSliderNumber();
    this.initSliderWidth();
    this.initSLiderListWidth();
    this.addEvent();
    this.createIndicater();
    this.setIndicator();
    this.initAutoPlay();
  }

  assignElement() {
    this.sliderWrapEl = document.getElementById(&#39;slider-wrap&#39;);
    this.sliderListEl = this.sliderWrapEl.querySelector(&#39;#slider&#39;);
    this.nextBtnEl = this.sliderWrapEl.querySelector(&#39;#next&#39;);
    this.previousBtnEl = this.sliderWrapEl.querySelector(&#39;#previous&#39;);
    this.indeicaterWrapEl = this.sliderWrapEl.querySelector(&#39;#indicator-wrap&#39;);
    this.controlWrapEl = this.sliderWrapEl.querySelector(&#39;#control-wrap&#39;);
  }

  initAutoPlay() {
    this.#intervalId = setInterval(this.moveToRight.bind(this), 3000);
  }

  initSliderNumber() {
    this.#slideNumber = this.sliderListEl.querySelectorAll(&#39;li&#39;).length;
  }

  initSliderWidth() {
    this.#slideWidth = this.sliderListEl.clientWidth;
  }

  initSLiderListWidth() {
    this.sliderListEl.style.width = `${this.#slideNumber * this.#slideWidth}px`;
  }

  addEvent() {
    this.nextBtnEl.addEventListener(&#39;click&#39;, this.moveToRight.bind(this));
    this.previousBtnEl.addEventListener(&#39;click&#39;, this.moveToLeft.bind(this));
    this.indeicaterWrapEl.addEventListener(
      &#39;click&#39;,
      this.onClickIndicator.bind(this),
    );
    this.controlWrapEl.addEventListener(&#39;click&#39;, this.togglePlay.bind(this));
  }

// 해당 버튼 플레이 &amp; 일시정지 변경 
  togglePlay(event) {
    if (event.target.dataset.status === &#39;play&#39;) {
      this.#autoPlay = true;
      this.controlWrapEl.classList.add(&#39;play&#39;);
      this.controlWrapEl.classList.remove(&#39;pause&#39;);
      this.initAutoPlay();
    } else if (event.target.dataset.status === &#39;pause&#39;) {
      this.#autoPlay = false;
      this.controlWrapEl.classList.remove(&#39;play&#39;);
      this.controlWrapEl.classList.add(&#39;pause&#39;);
      clearInterval(this.#intervalId);
    }
  }

  onClickIndicator(event) {
    const indexPosition = parseInt(event.target.dataset.index, 10);
    if (Number.isInteger(indexPosition)) {
      this.#currentPosition = indexPosition;
      this.sliderListEl.style.left = `-${
        this.#slideWidth * this.#currentPosition
      }px`;
      this.setIndicator();
    }
    console.log(indexPosition);
  }

  moveToRight() {
    this.#currentPosition += 1;
    if (this.#currentPosition === this.#slideNumber) {
      this.#currentPosition = 0;
    }
    this.sliderListEl.style.left = `-${
      this.#slideWidth * this.#currentPosition
    }px`;

// 자동재생에 대한 조건문을 걸어준다.
    if (this.#autoPlay) {
      clearInterval(this.#intervalId);
      this.#intervalId = setInterval(this.moveToRight.bind(this), 3000);
    }

    this.setIndicator();
  }

  moveToLeft() {
    this.#currentPosition -= 1;
    if (this.#currentPosition === -1) {
      this.#currentPosition = this.#slideNumber - 1;
    }
    this.sliderListEl.style.left = `-${
      this.#slideWidth * this.#currentPosition
    }px`;

//자동재생에 대한 조건문을 걸어준다.
    if (this.#autoPlay) {
      clearInterval(this.#intervalId);
      this.#intervalId = setInterval(this.moveToRight.bind(this), 3000);
    }

    this.setIndicator();
  }

  createIndicater() {
    const docFragment = document.createDocumentFragment();
    for (let i = 0; i &lt; this.#slideNumber; i += 1) {
      const li = document.createElement(&#39;li&#39;);
      li.dataset.index = i;
      docFragment.appendChild(li);
      this.indeicaterWrapEl.querySelector(&#39;ul&#39;).appendChild(docFragment);
    }
  }

  setIndicator() {
    this.indeicaterWrapEl
      .querySelector(&#39;li.active&#39;)
      ?.classList.remove(&#39;active&#39;);

    this.indeicaterWrapEl
      .querySelector(`ul li:nth-child(${this.#currentPosition + 1})`)
      .classList.add(&#39;active&#39;);
  }
}

</code></pre>
<h2 id="에필로그">에필로그</h2>
<p>이미지 슬라이드는 퍼블리싱 공부할때 직접 손코딩도 해보고 기능도 나눠서 생각해보고 했던 기억이 있다. 그래서 그런지 로직이 머릿속에 남아 있다. 확실히 내가 직접 짜보고 생각해보면 그 과정이 머릿속에 더 오래 남는가 보다. </p>
<p>좋았던 부분은 라이브러리나 jqeury 사용을 안했다는 점과 자바스크립트 또한 클래스문법이라는 나에겐 새로운 방식으로 코드를 구현했다는 점이다. 
이번에는 클래스 문법을 조금 더 자세하게 공부하며 분석했다. 클래스 문법이 조금더 직관적인 것 같아 조금 더 익숙해 진다면 실무에서도 적용해 보고 싶다. </p>
<p>아쉬웠던 부분은 이러하다.
슬라이드들이 이동하다가 오른쪽 끝에 다다랐을때 슬라이드들이 와다다다 하며 왼쪽으로 가도록 코드가 짜여있다. 하지만 그 부분은 자연스럽지 못하다고 생각한다. 이동될때 마다 반대쪽 맨 끝에 있는 슬라이드가 바로 바로 위치 이동을 해서 끝에 달라 붙었다면 훨씬 더 매끄러운 움직임이 나타났을 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[react] 회원가입 폼 데이터 기능구현#1 - REST API 개념 익히기]]></title>
            <link>https://velog.io/@dev__note/react-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85-%ED%8F%BC-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EA%B8%B0%EB%8A%A5%EA%B5%AC%ED%98%841-REST-API-%EA%B0%9C%EB%85%90-%EC%9D%B5%ED%9E%88%EA%B8%B0</link>
            <guid>https://velog.io/@dev__note/react-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85-%ED%8F%BC-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EA%B8%B0%EB%8A%A5%EA%B5%AC%ED%98%841-REST-API-%EA%B0%9C%EB%85%90-%EC%9D%B5%ED%9E%88%EA%B8%B0</guid>
            <pubDate>Sat, 09 Jul 2022 13:09:12 GMT</pubDate>
            <description><![CDATA[<h2 id="프롤로그">프롤로그</h2>
<p>그리디브 개발 스터디에서 진행하고 있는 프로그레시브 앱 MVP 구현하기! 
드디어 약 5주차에 걸쳐 회원가입 입력 폼 구현과 디자인 입히기를 마무리 했다.🥳🥳 그리고 이제 데이터를 주고 받을 수 있도록 api 를 붙여보기로 했다. api 를 붙이기 전 REST API 에 대해서 먼저 이론을 익히고 기능을 구현해 보고자 한다. </p>
<h2 id="rest-api-에-대한-이해">REST API 에 대한 이해</h2>
<p><strong>Representational State Transfer API</strong> </p>
<p>REST 를 기반으로 만들어진 API 이다. 
그렇다면 REST는 무엇일까? 
REST와 API 각각의 개념을 알고 REST API를 이해해 보자 🙂</p>
<h3 id="rest">REST</h3>
<p>자원을 이름으로 구분하여 해당 자원의 상태를 주고 받는 모든 것을 의미한다. </p>
<blockquote>
<ol>
<li>HTTP URI(Uniform Resource Identifier)를 통해 자원(Resource)을 명시하고 </li>
<li>HTTP Method(POST, GET, PUT, DELETE)를 통해</li>
<li>해당 자원(URI)에 대한 CRUD Operation을 적용하는 것을 의미함 </li>
</ol>
</blockquote>
<p>💡 <strong><em>CRUD Operation ?</em></strong>
CRUD 는 대부분의 컴퓨터 소프트웨어가 가지는 기본적인 데이터 처리 기능인 <code>Create(생성)</code>, <code>Read(읽기)</code>, <code>Update(갱신)</code>, <code>Delete(삭제)</code> 를 묶어서 일컫는 말로 REST 에서의 CRUD 동작 예시는 다음과 같다. </p>
<blockquote>
<p>Create 데이터 생성 -&gt; POST 
Read 데이터 조회 -&gt; GET
Update 데이터 수정 -&gt; PUT
Delete 데이터 삭제 -&gt; DELETE </p>
</blockquote>
<h3 id="rest-의-구성-요소">REST 의 구성 요소</h3>
<p>REST는 3가지로 구성되어 있다.</p>
<blockquote>
<p>자원(Resource) : HTTP URI
자원에 대한 행위(Verb) : HTTP Method
자원에 대한 행위의 내용 (Representations) : HTTP Message Pay Load</p>
</blockquote>
<p>💡 <em>*<em>자원에 대한 행위의 내용? : HTTP에서의 Representation 에 대하여 *</em></em>
representation 을 조금 더 자세하게 정의하자면 <code>어떤 리소스의 특정 시점의 상태를 반영하고 있는 정보</code>이다. 하나의 representation은 representation data와 representation metadata로 구성된다.</p>
<p>조금 더 깊은 이해가 필요하다면 이 분의 블로그를 자세하게 읽어보자 
representation에 대한 개념을 심도있게 &quot;잘&quot; 설명해주셨다. 
<a href="https://blog.npcode.com/2017/04/03/rest%EC%9D%98-representation%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80/">REST의 representation이란 무엇인가</a></p>
<h3 id="rest-디자인-원칙">REST 디자인 원칙</h3>
<blockquote>
<ol>
<li>균일한 인터페이스</li>
<li>클라이언트-서버 디커플링</li>
<li>Stateless</li>
<li>캐싱 가능성</li>
<li>계층 구조 아키텍처</li>
<li>코드 온디맨드(옵션)</li>
</ol>
</blockquote>
<p>① <strong>균일한 인터페이스</strong></p>
<ul>
<li><code>Identification of resources</code> : 인터페이스는 클라이언트와 서버 간의 상호 작용에 관련된 각 리소스를 고유하게 식별해야 한다.</li>
<li><code>Manipulation of resources through representations</code> : 리소스는 서버 응답에 동일한 표현을 사용해야 합니다. API 소비자는 이러한 표현을 사용하여 서버의 리소스 상태를 수정해야 한다.</li>
<li><code>Self-descriptive messages</code> : 각 리소스 표현에는 메시지 처리 방법을 설명하는 충분한 정보가 있어야 한다. 또한 클라이언트가 리소스에 대해 수행할 수 있는 추가 작업에 대한 정보도 제공해야 한다. </li>
<li><code>Hypermedia as the engine of application state</code> : 클라이언트는 애플리케이션의 초기 URI만 가지고 있어야 한다. 클라이언트 애플리케이션은 하이퍼링크를 사용하여 다른 모든 리소스와 상호 작용을 동적으로 구동해야 한다.</li>
</ul>
<p>② <strong>클라이언트-서버 디커플링</strong></p>
<p>클라이언트와 서버 애플리케이션은 서로 간에 완전히 독립적이어야 한다. 
클라이언트 애플리케이션이 알아야 하는 유일한 정보는 요청된 리소스의 URI이며, 이는 다른 방법으로 서버 애플리케이션과 상호작용할 수 없다. 이와 유사하게, 서버 애플리케이션은 HTTP를 통해 요청된 데이터에 전달하는 것 말고는 클라이언트 애플리케이션을 수정하지 않아야 한다.</p>
<p>③ <strong>Stateless</strong></p>
<p>이는 각 요청에서 이의 처리에 필요한 모든 정보를 포함해야 함을 의미한다. 즉, REST API는 서버측 세션을 필요로 하지 않고, 서버 애플리케이션은 클라이언트 요청과 관련된 데이터를 저장할 수 없다.</p>
<p>④ <strong>캐싱 가능성</strong></p>
<p>가급적이면, 리소스를 클라이언트 또는 서버측에서 캐싱할 수 있어야 한다. 또한 서버 응답에는 전달된 리소스에 대해 캐싱이 허용되는지 여부에 대한 정보도 포함되어야 한다. <em>(이의 목적은 서버측의 확장성 증가와 함께 클라이언트측의 성능 향상을 동시에 얻는 것이다.)</em></p>
<p>⑤ <strong>계층 구조 아키텍처</strong></p>
<p>REST API에서는 호출과 응답이 서로 다른 계층을 통과한다.</p>
<p>⑥ <strong>코드 온디맨드(옵션)</strong></p>
<p>REST API는 일반적으로 정적 리소스를 전송하지만, 특정한 경우에는 응답에 실행 코드(예: Java 애플릿)를 포함할 수도 있다. 이러한 경우, 코드는 요청 시에만 실행되어야 한다.</p>
<h3 id="api">API</h3>
<p>*<em>Application Programming Interface(애플리케이션 프로그램 인터페이스) *</em>
API의 맥락에서 <code>애플리케이션</code>이라는 단어는 고유한 기능을 가진 모든 소프트웨어를 나타낸다. <code>인터페이스</code>는 두 애플리케이션 간의 서비스 계약이라고 할 수 있다. 이 계약은 요청과 응답을 사용하여 두 애플리케이션이 서로 통신하는 방법을 정의한다. </p>
<blockquote>
<p>즉, API는 정의 및 프로토콜 집합을 사용하여 두 소프트웨어 구성 요소가 서로 통신할 수 있게 하는 메커니즘
 🙂 더 쉬운말로, 디바이스가 서로 간에 연결하여 통신할 수 있는 방법을 정의하는 규칙 세트라고 할 수 있다. </p>
</blockquote>
<h3 id="rest-api-란">REST API 란?</h3>
<p><strong>REST(REpresentational State Transfer) 아키텍처 스타일의 디자인 원칙을 준수하는 API</strong></p>
<p>위의 REST의 디자인 설계 원칙을 따르는 API 를 말한다. REST에는 여섯 가지의 기본 원칙이 있고, 이 가이드를 준수한 인터페이스는 RESTful하다고 표현한다. 
 <em>(REST를 만든 로이 필딩은 REST 아키텍쳐 스타일을 따르지 않는 API는 REST API라고 부르지 말아주길 바라고 있다고 한다.)</em></p>
<h2 id="에필로그">에필로그</h2>
<p>API라는 단어는 정말 많이 들어봤는데 정확한 개념을 몰랐었다. REST API에 대한 이론을 찾아보면서 API 란 이런거군, REST 에 대한 개념도 확실히 잡을 수 있었다. REST의 원칙을 정확하게 따르지 않은 API 는 HTTP API 라고 구분해 부르는 것도 알게 되었다. (REST API라는 명칭이 통용적으로 쓰이며 굳어진 듯하나, REST의 아키텍쳐를 정확하게 따른것들과 그렇지 않은 것은 구분하는 것이 좋을 것 같다.) 아마 더 깊은 이론들이 존재하겠지만 우선은 여기정도까지만 알아도 이해하고 구현하는데는 무리 없을 것 같다. 그렇다면 이제 axios 를 활용해서 api 를 회원가입 폼에 붙여보러 가보자! </p>
<h3 id="참고">참고</h3>
<p><a href="https://khj93.tistory.com/entry/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-REST-API%EB%9E%80-REST-RESTful%EC%9D%B4%EB%9E%80">REST API란</a>
<a href="https://blog.npcode.com/2017/04/03/rest%EC%9D%98-representation%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80/">REST의 representation이란 무엇인가</a>
<a href="https://www.ibm.com/kr-ko/cloud/learn/rest-apis">REST 디자인 원칙</a>
<a href="https://restfulapi.net/">What is REST</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[react 30] #1  가상 키보드 구현하기 ]]></title>
            <link>https://velog.io/@dev__note/30%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-1-%EA%B0%80%EC%83%81-%ED%82%A4%EB%B3%B4%EB%93%9C-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dev__note/30%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-1-%EA%B0%80%EC%83%81-%ED%82%A4%EB%B3%B4%EB%93%9C-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 02 Jul 2022 08:34:37 GMT</pubDate>
            <description><![CDATA[<h2 id="프롤로그">프롤로그</h2>
<p>패스트캠퍼스에 [30개 프로젝트로 배우는 프론트엔드] 라는 강의가 있어서 듣기 시작했다. 클론코딩인데 듣고 끝! 하게 되면 사실상 나중에 기억이 하나도 남게 되지 않으니까 1프로젝트 1글 정리를 하고자 한다. 첫번째는 가상 키보드 구현하기이다. </p>
<h2 id="webpack-이용한-개발환경-구축">webpack 이용한 개발환경 구축</h2>
<p>module bundler
웹개발에 필요한 html,css,javascript 등을 하나의 파일 또는 여러개의 파일로 병합하거나 압축해주는 역할을 한다.</p>
<h3 id="1-packagejson-초기화">1. package.json 초기화</h3>
<pre><code class="language-javascript">npm init -y</code></pre>
<h3 id="2-webpack-관련-패키지-설치">2. webpack 관련 패키지 설치</h3>
<pre><code class="language-javascript">npm i -D webpack webpack-cli webpack-dev-server</code></pre>
<p>-D 는 dev dependancies로써 설치를 하겠다는 뜻이고
local 개발이나 test 를 설치하는데에만 쓰이는 패키지를 뜻한다.</p>
<p>반면 dependacies 로 설치를 하게 되면
production 환경에서 필요한 패키지를 뜻한다.</p>
<h3 id="3-src-폴더-생성">3. src 폴더 생성</h3>
<p>개발할 때 필요한 파일들을 생성 해 준다. </p>
<h3 id="4-추가-플러그인-설치">4. 추가 플러그인 설치</h3>
<pre><code class="language-javascript">npm i -D terser-webpack-plugin //압축 플러그인 
npm i -D html-webpack-plugin //html 관련 모듈
npm i -D mini-css-extract-plugin css-loader css-minimizer-webpack-plugin //css 관련 모듈</code></pre>
<h3 id="5-webpackconfigjs-세팅">5. webpack.config.js 세팅</h3>
<p>각각 주석으로 해당에 관한 설명을 적어놨다.</p>
<pre><code class="language-javascript">const path = require(&quot;path&quot;);
const TerserWebpackPlugin = require(&quot;terser-webpack-plugin&quot;);
const HtmlwebpackPlugin = require(&quot;html-webpack-plugin&quot;);
const MiniCssExtractPlugin = require(&quot;mini-css-extract-plugin&quot;);
const CssMinimizerPlugin = require(&quot;css-minimizer-webpack-plugin&quot;);

module.exports = {
  entry: &quot;./src/js/index.js&quot;, //js 파일의 진입점
  output: {
    // 빌드를 했을때 번들파일 관련 속성
    filename: &quot;bundle.js&quot;, // 파일이름 지정(번들들파일)
    path: path.resolve(__dirname, &quot;./dist&quot;), //번들파일이 생성될 경로, path.resolve 메소드를 사용해서 __dirname 을사용해 웹팩이 절대경로를 찾을 수 있도록 해줌
    clean: true, //이미 번들파일이 있다면 다 지우고 다시 만들어주는 속성
  },
  devtool: &quot;source-map&quot;, //build 한 파일과 원본 파일을 연결시켜주는 파일
  mode: &quot;development&quot;, //production 과 devlopment 모드가 있는데 html,css,js 파일을 난독화 기능을 제공하는지에 대한 차이
  devServer:{
    host:&quot;localhost&quot;,
    port:8080,
    open:true, // dev 서버를 열때 새창을 이용해서 열어줘라 
    watchFiles: &quot;index.html&quot; //html 변화 감지를 지켜봄, 변화가 있을때마다 reload
  },
  plugins: [
    new HtmlwebpackPlugin({
      title: &quot;keyboard&quot;, //title
      template: &quot;./index.html&quot;, //lodash 파일 사용할 수 있게 해줌 -&gt; 유틸성 메소드나 템플릿성 메소드를 제공해 주는 라이브러리
      inject: &quot;body&quot;, //js 번들했을때 파일을 body 쪽에 넣어주겠다.
      favicon: &quot;./favicon.ico&quot;,
    }),
    new MiniCssExtractPlugin({
      filename: &quot;style.css&quot;,
    }),
  ],
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, &quot;css-loader&quot;], //css파일을 이런 로더를 사용해서 읽어들이도록 하겠다.
      },
    ],
  },

  optimization: {
    //압축해주는 친구들
    minimizer: [new TerserWebpackPlugin(), new CssMinimizerPlugin()],
  },
};
</code></pre>
<h3 id="6-indexhtml-파일-세팅">6. index.html 파일 세팅</h3>
<p>HtmlwebpackPlugin 플러그인을 설치하고, <code>template: &quot;./index.html&quot;</code> template 를 index.html 로 설정해 주었다. 이렇게 하면 lodash 파일을 사용할 수 있게 해주는데 index.html 파일에 따로 lodash 문법을 사용해서 적어줘야한다. <code>header</code> 안에 적어줬다.</p>
<pre><code class="language-javascript">
  &lt;head&gt;
    &lt;title&gt;&lt;%= htmlWebpackPlugin.options.title %&gt;&lt;/title&gt;
  &lt;/head&gt;</code></pre>
<h3 id="7-eslint--prettier">7. eslint &amp; prettier</h3>
<ul>
<li>eslint 는 js linter 중에 하나로 정적 분석을 통해 문법적 오류를 찾아준다. 간단한 포맷팅 기능도 제공한다. </li>
<li>prettier 는 코드 포맷팅 중에 하나이다. </li>
<li>^ 캐럿 표시는 npm 설치 시 마이너 버전이 업데이트가 되었으면 마이너 버전까지는 업데이트를 허용한다라는 뜻이다. (--save-exact 사용하여 설치) </li>
<li>eslint 가 포맷팅 기능도 제공하다 보니까 prettier 가 formatting 이 겹치는 룰이 있어 충돌이 나게 된다. 그 충돌을 방지하기 위해 <code>eslint-config-prettier</code> 플러그인 추가해 준다. </li>
<li><code>eslint-plugin-prettier</code> 는 eslint에 prettier 플러그인을 추가해 주기 위한 패키지이다.</li>
</ul>
<pre><code class="language-javascript">npm i -D eslint
npm install --save-dev --save-exact prettier 

npm i -D eslint-config-prettier eslint-plugin-prettier</code></pre>
<h4 id="eslintrcjson">.eslintrc.json</h4>
<pre><code class="language-javascript">{
  &quot;env&quot;: {
    &quot;browser&quot;: true,
    &quot;es2021&quot;: true
  },
  &quot;extends&quot;: [&quot;eslint:recommended&quot;, &quot;plugin:prettier/recommended&quot;],
  &quot;parserOptions&quot;: {
    &quot;ecmaVersion&quot;: &quot;latest&quot;,
    &quot;sourceType&quot;: &quot;module&quot;
  },
  &quot;rules&quot;: {
    &quot;prettier/prettier&quot;: &quot;error&quot;
  }
}
</code></pre>
<h4 id="eslintignore">.eslintignore</h4>
<pre><code class="language-javascript">/node_modules
/dis
webpack.config.js
</code></pre>
<h4 id="prettierrcjson">.prettierrc.json</h4>
<p>prettier 홈페이지의 기본 reccommnet 를 가져왔다.</p>
<pre><code class="language-javascript">{
  &quot;arrowParens&quot;: &quot;always&quot;,
  &quot;bracketSameLine&quot;: false,
  &quot;bracketSpacing&quot;: true,
  &quot;embeddedLanguageFormatting&quot;: &quot;auto&quot;,
  &quot;htmlWhitespaceSensitivity&quot;: &quot;css&quot;,
  &quot;insertPragma&quot;: false,
  &quot;jsxSingleQuote&quot;: false,
  &quot;printWidth&quot;: 80,
  &quot;proseWrap&quot;: &quot;preserve&quot;,
  &quot;quoteProps&quot;: &quot;as-needed&quot;,
  &quot;requirePragma&quot;: false,
  &quot;semi&quot;: true,
  &quot;singleQuote&quot;: false,
  &quot;tabWidth&quot;: 2,
  &quot;trailingComma&quot;: &quot;es5&quot;,
  &quot;useTabs&quot;: false,
  &quot;vueIndentScriptAndStyle&quot;: false
}
</code></pre>
<h4 id="prettierignore">.prettierignore</h4>
<pre><code class="language-javascript">/node_modules
/dis
webpack.config.js
</code></pre>
<h3 id="8-최종-packagejson">8. 최종 package.json</h3>
<pre><code class="language-javascript">{
  &quot;name&quot;: &quot;playground&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;description&quot;: &quot;&quot;,
  &quot;main&quot;: &quot;index.js&quot;,
  &quot;scripts&quot;: {
    &quot;build&quot;: &quot;webpack --mode=production&quot;,
    &quot;dev&quot;: &quot;webpack-dev-server&quot;
  },
  &quot;keywords&quot;: [],
  &quot;author&quot;: &quot;&quot;,
  &quot;license&quot;: &quot;ISC&quot;,
  &quot;devDependencies&quot;: {
    &quot;css-loader&quot;: &quot;^6.5.1&quot;,
    &quot;css-minimizer-webpack-plugin&quot;: &quot;^3.3.1&quot;,
    &quot;eslint&quot;: &quot;^8.18.0&quot;,
    &quot;eslint-config-prettier&quot;: &quot;^8.3.0&quot;,
    &quot;eslint-plugin-prettier&quot;: &quot;^4.0.0&quot;,
    &quot;html-webpack-plugin&quot;: &quot;^5.5.0&quot;,
    &quot;mini-css-extract-plugin&quot;: &quot;^2.4.6&quot;,
    &quot;prettier&quot;: &quot;2.5.1&quot;,
    &quot;terser-webpack-plugin&quot;: &quot;^5.3.0&quot;,
    &quot;webpack&quot;: &quot;^5.65.0&quot;,
    &quot;webpack-cli&quot;: &quot;^4.9.1&quot;,
    &quot;webpack-dev-server&quot;: &quot;^4.7.2&quot;
  }
}
</code></pre>
<h2 id="html--css-작성">html &amp; css 작성</h2>
<p><img src="https://velog.velcdn.com/images/dev__note/post/d675114a-3127-4189-bc9f-66c8369ce4e1/image.png" alt=""></p>
<h4 id="html-body-에-들어-갈-내용">html <code>&lt;body&gt;</code> 에 들어 갈 내용</h4>
<pre><code class="language-javascript">&lt;body&gt;
    &lt;div class=&quot;container&quot; id=&quot;container&quot;&gt;
        &lt;div class=&quot;menu&quot;&gt;
            &lt;label class=&quot;switch&quot;&gt;
                &lt;input id=&quot;switch&quot; type=&quot;checkbox&quot;&gt;
                &lt;span class=&quot;slider&quot;&gt;&lt;/span&gt;
            &lt;/label&gt;
            &lt;div class=&quot;select-box&quot;&gt;
                &lt;label for=&quot;font&quot;&gt;Font:&lt;/label&gt;
                &lt;select id=&quot;font&quot;&gt;
                    &lt;option value=&quot;&quot; disabled selected&gt;Choose Font&lt;/option&gt;
                    &lt;option value=&quot;Comic Sans MS, Comic Sans, cursive&quot;&gt;Font 1&lt;/option&gt;
                    &lt;option value=&quot;Arial Narrow, sans-serif&quot;&gt;Font 2&lt;/option&gt;
                    &lt;option value=&quot;Chalkduster, fantasy&quot;&gt;Font 3&lt;/option&gt;
                &lt;/select&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;input-group&quot; id=&quot;input-group&quot;&gt;
            &lt;input id=&quot;input&quot; class=&quot;input&quot; type=&quot;text&quot; autocomplete=&quot;off&quot;&gt;
            &lt;div class=&quot;error-message&quot;&gt;한글 입력 불가&lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;keyboard&quot; id=&quot;keyboard&quot;&gt;
            &lt;div class=&quot;row&quot;&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;Backquote&quot; data-val=&quot;`&quot;&gt;
                    &lt;span class=&quot;two-value&quot;&gt;~&lt;/span&gt;
                    &lt;span class=&quot;two-value&quot;&gt;`&lt;/span&gt;
                &lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;Digit1&quot; data-val=&quot;1&quot;&gt;
                    &lt;span class=&quot;two-value&quot;&gt;!&lt;/span&gt;
                    &lt;span class=&quot;two-value&quot;&gt;1&lt;/span&gt;
                &lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;Digit2&quot; data-val=&quot;2&quot;&gt;
                    &lt;span class=&quot;two-value&quot;&gt;@&lt;/span&gt;
                    &lt;span class=&quot;two-value&quot;&gt;2&lt;/span&gt;
                &lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;Digit3&quot; data-val=&quot;3&quot;&gt;
                    &lt;span class=&quot;two-value&quot;&gt;#&lt;/span&gt;
                    &lt;span class=&quot;two-value&quot;&gt;3&lt;/span&gt;
                &lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;Digit4&quot; data-val=&quot;4&quot;&gt;
                    &lt;span class=&quot;two-value&quot;&gt;$&lt;/span&gt;
                    &lt;span class=&quot;two-value&quot;&gt;4&lt;/span&gt;
                &lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;Digit5&quot; data-val=&quot;5&quot;&gt;
                    &lt;span class=&quot;two-value&quot;&gt;%&lt;/span&gt;
                    &lt;span class=&quot;two-value&quot;&gt;5&lt;/span&gt;
                &lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;Digit6&quot; data-val=&quot;6&quot;&gt;
                    &lt;span class=&quot;two-value&quot;&gt;^&lt;/span&gt;
                    &lt;span class=&quot;two-value&quot;&gt;6&lt;/span&gt;
                &lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;Digit7&quot; data-val=&quot;7&quot;&gt;
                    &lt;span class=&quot;two-value&quot;&gt;&amp;&lt;/span&gt;
                    &lt;span class=&quot;two-value&quot;&gt;7&lt;/span&gt;
                &lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;Digit8&quot; data-val=&quot;8&quot;&gt;
                    &lt;span class=&quot;two-value&quot;&gt;*&lt;/span&gt;
                    &lt;span class=&quot;two-value&quot;&gt;8&lt;/span&gt;
                &lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;Digit9&quot; data-val=&quot;9&quot;&gt;
                    &lt;span class=&quot;two-value&quot;&gt;(&lt;/span&gt;
                    &lt;span class=&quot;two-value&quot;&gt;9&lt;/span&gt;
                &lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;Digit0&quot; data-val=&quot;0&quot;&gt;
                    &lt;span class=&quot;two-value&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;two-value&quot;&gt;0&lt;/span&gt;
                &lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;Minus&quot; data-val=&quot;-&quot;&gt;
                    &lt;span class=&quot;two-value&quot;&gt;_&lt;/span&gt;
                    &lt;span class=&quot;two-value&quot;&gt;-&lt;/span&gt;
                &lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;Equal&quot; data-val=&quot;=&quot;&gt;
                    &lt;span class=&quot;two-value&quot;&gt;+&lt;/span&gt;
                    &lt;span class=&quot;two-value&quot;&gt;=&lt;/span&gt;
                &lt;/div&gt;
                &lt;div class=&quot;key back-space-key&quot; data-code=&quot;Backspace&quot; data-val=&quot;Backspace&quot;&gt;
                    Backspace
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;row&quot;&gt;
                &lt;div class=&quot;key tab-key&quot;&gt;Tab&lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;KeyQ&quot; data-val=&quot;q&quot;&gt;Q&lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;KeyW&quot; data-val=&quot;w&quot;&gt;W&lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;KeyE&quot; data-val=&quot;e&quot;&gt;E&lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;KeyR&quot; data-val=&quot;r&quot;&gt;R&lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;KeyT&quot; data-val=&quot;t&quot;&gt;T&lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;KeyY&quot; data-val=&quot;y&quot;&gt;Y&lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;KeyU&quot; data-val=&quot;u&quot;&gt;U&lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;KeyI&quot; data-val=&quot;i&quot;&gt;I&lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;KeyO&quot; data-val=&quot;o&quot;&gt;O&lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;KeyP&quot; data-val=&quot;p&quot;&gt;P&lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;BracketLeft&quot; data-val=&quot;[&quot;&gt;
                    &lt;span class=&quot;two-value&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;two-value&quot;&gt;[&lt;/span&gt;
                &lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;BracketRight&quot; data-val=&quot;]&quot;&gt;
                    &lt;span class=&quot;two-value&quot;&gt;}&lt;/span&gt;
                    &lt;span class=&quot;two-value&quot;&gt;]&lt;/span&gt;
                &lt;/div&gt;
                &lt;div class=&quot;key back-slash-key&quot; data-code=&quot;Backslash&quot; data-val=&quot;\&quot;&gt;
                    &lt;span class=&quot;two-value&quot;&gt;|&lt;/span&gt;
                    &lt;span class=&quot;two-value&quot;&gt;\&lt;/span&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;row&quot;&gt;
                &lt;div class=&quot;key caps-lock-key&quot;&gt;CapsLock&lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;KeyA&quot; data-val=&quot;a&quot;&gt;A&lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;KeyS&quot; data-val=&quot;s&quot;&gt;S&lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;KeyD&quot; data-val=&quot;d&quot;&gt;D&lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;KeyF&quot; data-val=&quot;f&quot;&gt;F&lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;KeyG&quot; data-val=&quot;g&quot;&gt;G&lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;KeyH&quot; data-val=&quot;h&quot;&gt;H&lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;KeyJ&quot; data-val=&quot;j&quot;&gt;J&lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;KeyK&quot; data-val=&quot;k&quot;&gt;K&lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;KeyL&quot; data-val=&quot;l&quot;&gt;L&lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;Semicolon&quot; data-val=&quot;;&quot;&gt;
                    &lt;span class=&quot;two-value&quot;&gt;:&lt;/span&gt;
                    &lt;span class=&quot;two-value&quot;&gt;;&lt;/span&gt;
                &lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;Quote&quot; data-val=&quot;&#39;&quot;&gt;
                    &lt;span class=&quot;two-value&quot;&gt;&quot;&lt;/span&gt;
                    &lt;span class=&quot;two-value&quot;&gt;&#39;&lt;/span&gt;
                &lt;/div&gt;
                &lt;div class=&quot;key enter-key&quot; data-code=&quot;Enter&quot;&gt;Enter&lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;row&quot;&gt;
                &lt;div class=&quot;key left-shift-key&quot; data-code=&quot;ShiftLeft&quot;&gt;Shift&lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;KeyZ&quot; data-val=&quot;z&quot;&gt;Z&lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;KeyX&quot; data-val=&quot;x&quot;&gt;X&lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;KeyC&quot; data-val=&quot;c&quot;&gt;C&lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;KeyV&quot; data-val=&quot;v&quot;&gt;V&lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;KeyB&quot; data-val=&quot;b&quot;&gt;B&lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;KeyN&quot; data-val=&quot;n&quot;&gt;N&lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;KeyM&quot; data-val=&quot;m&quot;&gt;M&lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;Comma&quot; data-val=&quot;,&quot;&gt;
                    &lt;span class=&quot;two-value&quot;&gt;
                        &amp;lt;
                    &lt;/span&gt;
                    &lt;span class=&quot;two-value&quot;&gt;,&lt;/span&gt;
                &lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;Period&quot; data-val=&quot;.&quot;&gt;
                    &lt;span class=&quot;two-value&quot;&gt;
                        &amp;gt;
                    &lt;/span&gt;
                    &lt;span class=&quot;two-value&quot;&gt;.&lt;/span&gt;
                &lt;/div&gt;
                &lt;div class=&quot;key&quot; data-code=&quot;Slash&quot; data-val=&quot;/&quot;&gt;
                    &lt;span class=&quot;two-value&quot;&gt;?&lt;/span&gt;
                    &lt;span class=&quot;two-value&quot;&gt;/&lt;/span&gt;
                &lt;/div&gt;
                &lt;div class=&quot;key right-shift-key&quot; data-code=&quot;ShiftRight&quot;&gt;Shift&lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;row&quot;&gt;
                &lt;div class=&quot;key fn-key&quot;&gt;Ctrl&lt;/div&gt;
                &lt;div class=&quot;key fn-key&quot;&gt;-&lt;/div&gt;
                &lt;div class=&quot;key fn-key&quot;&gt;Alt&lt;/div&gt;
                &lt;div class=&quot;key space-key&quot; data-code=&quot;Space&quot; data-val=&quot;Space&quot;&gt;Space&lt;/div&gt;
                &lt;div class=&quot;key fn-key&quot;&gt;Alt&lt;/div&gt;
                &lt;div class=&quot;key fn-key&quot;&gt;Fn&lt;/div&gt;
                &lt;div class=&quot;key fn-key&quot;&gt;-&lt;/div&gt;
                &lt;div class=&quot;key fn-key&quot;&gt;Ctrl&lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;

    &lt;/div&gt;
&lt;/body&gt;
</code></pre>
<h4 id="css-파일">css 파일</h4>
<pre><code class="language-javascript">* {
  user-select: none;
  outline: none;
}

html[theme=&quot;dark-mode&quot;] {
  /* ! */
  filter: invert(100%) hue-rotate(180deg);
}

body {
  background-color: white;
}

.container {
  width: 1050px;
  margin: auto;
}

.keyboard {
  background-color: gray;
  color: gray;
  width: 1050px;
  border-radius: 4px;
}

.row {
  /* ! */
  display: flex;
}

.key {
  width: 60px;
  height: 60px;
  margin: 5px;
  border-radius: 4px;
  background-color: white;
  cursor: pointer;
  /* ! */
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  transition: 0.2s;
  /* ! */
}

.key:hover {
  background-color: lightgray;
}

.key.active {
  background-color: #333;
  color: #fff;
}

.key .two-value {
  width: 100%;
  text-align: center;
}

.fn-key {
  width: 80px;
}

.space-key {
  width: 420px;
}

.back-space-key {
  width: 130px;
}

.tab-key {
  width: 95px;
}

.back-slash-key {
  width: 95px;
}

.caps-lock-key {
  width: 110px;
}

.left-shift-key {
  width: 145px;
}

.enter-key {
  width: 150px;
}

.right-shift-key {
  width: 185px;
}

.menu {
  /* ! */
  display: flex;
}

.switch {
  position: relative;
  width: 60px;
  height: 34px;
}

.switch input {
  display: none;
}

.slider {
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  position: absolute;
  cursor: pointer;
  background-color: gray;
  border-radius: 34px;
  transition: 0.4s;
}

/* ! */
.slider::before {
  position: absolute;
  content: &quot;&quot;;
  height: 26px;
  width: 26px;
  left: 4px;
  bottom: 4px;
  background-color: white;
  transition: 0.5s;
  border-radius: 50%;
}

input:checked + .slider {
  background-color: black;
}

input:checked + .slider::before {
  /* ! */
  transform: translateX(26px);
}

.select-box {
  position: relative;
  margin-left: 60px;
  height: 34px;
}

.select-box select {
  /* ! */
  font-size: 0.9rem;
  /* ! */
  padding: 2px 5px;
  height: 34px;
  width: 200px;
}

.input-group {
  margin: 100px 0px;
}

.input {
  border: none;
  border-bottom: 2px solid lightgrey;
  width: 1050px;
  height: 50px;
  font-size: 30px;
  text-align: center;
  display: block;
}

.error-message {
  color: #cc0033;
  font-size: 30px;
  line-height: 30px;
  margin-top: 10px;
  text-align: center;
}

.input-group .error-message {
  display: none;
}

.error input {
  border-bottom: 2px solid red;
}

.error .error-message {
  display: block;
}
</code></pre>
<h2 id="이벤트-스크립트">이벤트 스크립트</h2>
<p>javascript 클래스로 만들어주었다. </p>
<blockquote>
<p>이벤트 목록 </p>
</blockquote>
<ol>
<li>다크 모드 테마 </li>
<li>폰트 변경 </li>
<li>키보드로 작성 가능 (keydown, keyup) </li>
<li>화면 가상 키보드 클릭 시 작성 가능 (mousedown, mouseup)  </li>
</ol>
<h3 id="private-class-field">private class field</h3>
<p>class 의 속성(property)들은 기본적으로 public 하며 class 외부에서 읽히고 수정될 수 있다. 하지만, ES2019 에서는 해쉬 <code># prefix</code> 를 추가해 private class 필드를 선언할 수 있게 되었다.</p>
<pre><code class="language-javascript">export class Keyboard {
  #swichEl;
  #fontSelectEl;
  #containerEl;
  #keyboardEl;
  #inputGroupEl;
  #inputEl;
  #keyPress = false;
  #mouseDown = false;
  constructor() {
    this.#assignElement();
    this.#addEvent();
  }
  #assignElement() {
    this.#containerEl = document.getElementById(&quot;container&quot;);
    this.#swichEl = this.#containerEl.querySelector(&quot;#switch&quot;);
    this.#fontSelectEl = this.#containerEl.querySelector(&quot;#font&quot;);
    this.#keyboardEl = this.#containerEl.querySelector(&quot;#keyboard&quot;);
    this.#inputGroupEl = this.#containerEl.querySelector(&quot;#input-group&quot;);
    this.#inputEl = this.#inputGroupEl.querySelector(&quot;#input&quot;);
  }

  #addEvent() {
    this.#swichEl.addEventListener(&quot;change&quot;, this.#onChageTheme);
    this.#fontSelectEl.addEventListener(&quot;change&quot;, this.#onChageFont);
    document.addEventListener(&quot;keydown&quot;, this.#onKeyDown.bind(this));
    document.addEventListener(&quot;keyup&quot;, this.#onKeyUp.bind(this));
    this.#inputEl.addEventListener(&quot;input&quot;, this.#onInput);
    this.#keyboardEl.addEventListener(
      &quot;mousedown&quot;,
      this.#onMouseDown.bind(this)
    );
    document.addEventListener(&quot;mouseup&quot;, this.#onMouseUp.bind(this));
  }

  #onMouseUp(event) {
    if (this.#keyPress) return;
    this.#mouseDown = true;
    const keyEl = event.target.closest(&quot;div.key&quot;);
    const isActive = !!keyEl?.classList.contains(&quot;active&quot;);
    const val = keyEl?.dataset.val;

    if (isActive &amp;&amp; !!val &amp;&amp; val !== &quot;Space&quot; &amp;&amp; val !== &quot;Backspace&quot;) {
      this.#inputEl.value += val;
    }
    if (isActive &amp;&amp; val === &quot;Space&quot;) {
      this.#inputEl.value += &quot; &quot;;
    }
    if (isActive &amp;&amp; val === &quot;Backspace&quot;) {
      this.#inputEl.value = this.#inputEl.value.slice(0, -1);
    }

    this.#keyboardEl.querySelector(&quot;.active&quot;)?.classList.remove(&quot;active&quot;);
  }
  #onMouseDown(event) {
    if (this.#keyPress) return;
    this.#mouseDown = true;
    event.target.closest(&quot;div.key&quot;)?.classList.add(&quot;active&quot;);
  }
  #onInput(event) {
    event.target.value = event.target.value.replace(/[ㄱ-ㅎ|ㅏ-ㅣ|가-힣]/, &quot;&quot;);
  }
  #onKeyDown(event) {
    if (this.#mouseDown) return;
    this.#keyPress = true;
    this.#keyboardEl
      .querySelector(`[data-code=${event.code}]`)
      ?.classList.add(&quot;active&quot;);
    this.#inputGroupEl.classList.toggle(
      &quot;error&quot;,
      /[ㄱ-ㅎ|ㅏ-ㅣ|가-힣]/.test(event.key)
    );
  }
  #onKeyUp(event) {
    if (this.#mouseDown) return;
    this.#keyPress = false;
    this.#keyboardEl
      .querySelector(`[data-code=${event.code}]`)
      ?.classList.remove(&quot;active&quot;);
  }

  #onChageTheme(event) {
    document.documentElement.setAttribute(
      &quot;theme&quot;,
      event.target.checked ? &quot;dark-mode&quot; : &quot;&quot;
    );
  }

  #onChageFont(event) {
    document.body.style.fontFamily = event.target.value;
  }
}
</code></pre>
<h2 id="따로-공부가-필요한-내용들">따로 공부가 필요한 내용들</h2>
<p>강의를 들으며 알고는 있었지만 정확하게 개념이 부족한 부분들은 따로 공부가 필요했다. 아래의 개념들은 이론노트 블로그에 따로 정리 해 두겠다. 필요한 분들 (미래의 나포함) 은 링크를 타고 가서 보면 좋을 것 같다. </p>
<blockquote>
<ol>
<li>bind()</li>
<li>this </li>
</ol>
</blockquote>
<h2 id="에필로그">에필로그</h2>
<p>물론 혼자 공부하고 혼자 개발 해 보는 것도 중요하지만 강의를 듣게 되면 좋은 점 중에 하나는 나보다 더 잘하는 사람들의 코드를 보며 &#39;저렇게 코드를 짜는구나&#39;라는 방향성이 생긴다. 자바스크립트를 클래스로 사용해서 짜본 적이 없는데 새로운 개념도 얻게 되는 이점이 있다. 물론 모르는 개념들은 따로 찾아보고 정리해야 베스트이지만 말이다. </p>
<p>강의듣고 따라치는 것은 쉽지만 사실상 정리하려고 보면 엄두가 나지 않는다. 지금 이 글 정리도 2주나 지나서야 작성을 완료했다.🤣 하지만 글로 정리해 두면 확실히 내것이 되는 것이 있다. </p>
<p>일전에 퍼블리싱을 배울때에도 오프라인에서 강의를 듣고 직접 손으로 손코딩을 해가며 정리를 했던 기억이 있는데 시간이 오래걸려 비효율적인 것 같았지만서도 퍼블리싱 실무를 할때에는 확실히 그때의 손코딩하며 익혔던 개념들을 떠올리면서 작업을 하게 된다. 정리의 힘을 누구보다 잘 깨닫고 느껴 아주 운이 좋다고 생각한다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[react] 회원가입 form 꾸미기 #1 -
material Icon 을 Props 로 전달받기]]></title>
            <link>https://velog.io/@dev__note/react-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85-form-%EA%BE%B8%EB%AF%B8%EA%B8%B0-1-material-Icon-%EC%9D%84-Props-%EB%A1%9C-%EC%A0%84%EB%8B%AC%EB%B0%9B%EA%B8%B0</link>
            <guid>https://velog.io/@dev__note/react-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85-form-%EA%BE%B8%EB%AF%B8%EA%B8%B0-1-material-Icon-%EC%9D%84-Props-%EB%A1%9C-%EC%A0%84%EB%8B%AC%EB%B0%9B%EA%B8%B0</guid>
            <pubDate>Sun, 26 Jun 2022 15:35:07 GMT</pubDate>
            <description><![CDATA[<h2 id="프롤로그">프롤로그</h2>
<p>본격 날것의 디자인에서 css 를 입히는 중이다. emotion 을 사용하고 material icon 을이용해 회원가입 폼을 꾸미려고 한다. 
material icon 또한 npm 을 이용해 설치를 해주었다. 아이콘은 컴포넌트 형태로 가져 올 수 있어 편했다. (따로 이미지 작업을 하지 않아도 되고, 이미지형태로 불러오지 않아도 된다:-0)</p>
<h2 id="스타일-입힌-화면">스타일 입힌 화면</h2>
<p>  <img src="https://velog.velcdn.com/images/dev__note/post/63c05ed8-023b-4c5e-b78b-1dd99902b955/image.png" alt=""></p>
<h2 id="emotion-사용한-styling">emotion 사용한 styling</h2>
<p>지난 포스팅에서와 같이 emotion 으로 css 를 입혀 주었다. 클래스를 지정하고, 그에 맞는 스타일을 입혀주었다. 
정보를 입력하는 textField 영역은 컴포넌트로 만들었기 때문에 하나하나 클래스를 부여하는게 아니라 그 하나의 컴포넌트만 클래스를 주고 css 를 꾸며 주면 됐었다. </p>
<h3 id="클래스-부여">클래스 부여</h3>
<pre><code class="language-javascript"> &lt;div css={[pageCover]}&gt;
        &lt;h1 css={pageTitle}&gt;Sign Up&lt;/h1&gt;
        &lt;span css={[spanStyle]} /&gt;
      &lt;/div&gt;

      &lt;form onSubmit={handleSubmit(submitForm)} css={[formWrapper]}&gt;
        &lt;TextField
          icon={DraftsOutlinedIcon}
          text={&quot;Email&quot;}
          name={&quot;email&quot;}
          inputType={&quot;email&quot;}
          errorMsg={errors.email &amp;&amp; &quot;이메일 형식이 맞지 않습니다.&quot;}
          register={register}
        /&gt;

        &lt;button type=&quot;submit&quot; css={button}&gt;
          회원가입
        &lt;/button&gt;
      &lt;/form&gt;</code></pre>
<h3 id="-으로-감싼--css-style-부여">`` 으로 감싼  css style 부여</h3>
<pre><code class="language-javascript">const pageCover = css`
  position: relative;
  padding: 30px 0;
`;
const spanStyle = css`
  width: 80px;
  height: 80px;
  background: #696f64;
  border-radius: 50%;
  position: absolute;
  top: 50%;
  transform: translate(-20%, -80%);
  opacity: 0.7;
  z-index: -1;
`;
const pageTitle = css`
  font-size: 7.5vw;
  font-weight: 800;
  padding: 1.5vw;
  padding-left: 25px;
`;

const formWrapper = css`
  /* width: 500px; */
  margin: 0 auto;
  padding: 1.5vw;
  background: #696f64;
  color: #fff;
  border-radius: 15px 15px 0 0;
`;

const button = css`
  color: #3a3a3a;
  background-color: #fff;
  opacity: 0.8;
  border-radius: 5px;
  outline: none;
  appearance: none;
  border: none;
  padding: 13px;
  margin: 5px 0;
  font-size: 2vw;
  color: #2a4337;
  font-weight: 600;
  font-size: inherit;
  width: 100%;
`;</code></pre>
<h2 id="material-icon">material Icon</h2>
<p>이제 textField 부분도 스타일링을 해 줄 차례이다. 하나의 단위 컴포넌트만 css 를 입히는 것은 사실 쉬운 일이었다. </p>
<p>그러나 아이콘을 넣으려고 하니까 컴포넌트를 공통으로 사용해서 어떻게 각각 다르게 넘겨주지?? 라는 의문이 들었다. </p>
<p>일단 설치해주자</p>
<p><a href="https://mui.com/material-ui/icons/">머터리얼 아이콘</a></p>
<pre><code class="language-javascript">npm install @mui/icons-material
</code></pre>
<p>머터리얼 아이콘은 svg icon 컴포넌트로 받아 올 수 있다. 홈페이지에서 원하는 아이콘을 고른 후 import 시켜주면 끝이다. </p>
<p>하나의 아이콘을 따로 사용할 때에는 문제 없지만,
*<em>공통 컴포넌트를 쓰고 있는데 각각 다른 아이콘만 사용하고 싶은 경우에는 이 svg icon 도 props 로 넘겨 줘야 했다! *</em></p>
<h2 id="icon-컴포넌트-props-넘기기">Icon 컴포넌트 props 넘기기</h2>
<h3 id="1-svgicon-import-시키기">1. SvgIcon import 시키기</h3>
<pre><code class="language-javascript">import SvgIcon from &quot;@mui/material/SvgIcon&quot;;
import { SvgIconComponent } from &quot;@mui/icons-material&quot;;</code></pre>
<h3 id="2-컴포넌트-추가--props-설정">2. 컴포넌트 추가 &amp; props 설정</h3>
<p><code>&lt;SvgIcon component={icon} inheritViewBox /&gt;</code> 컴포넌트를 추가해 주고 icon 을 props 로 받아왔다. </p>
<pre><code class="language-javascript">  return (
    &lt;div css={[fieldWrapper]}&gt;
      &lt;div css={[title]}&gt;
        &lt;i&gt;
          &lt;SvgIcon component={icon} inheritViewBox /&gt;
        &lt;/i&gt;
        &lt;label htmlFor={name} css={[label]}&gt;
          {text}
        &lt;/label&gt;
      &lt;/div&gt;

      &lt;input type={inputType} {...register(name)} css={[inputStyle]} /&gt;
      {errorMsg &amp;&amp; &lt;span css={[errorMsgStyle]}&gt;{errorMsg}&lt;/span&gt;}
    &lt;/div&gt;
  );</code></pre>
<h3 id="3-타입-설정">3. 타입 설정</h3>
<p>타입스크립트를 사용하면 타입도 지정해 줘야한다. 처음에 컴포넌트를 string 으로 지정해줬는데 오류가 났다. 컴포넌트를 받는 것이니 <code>SvgIconComponent</code> 타입을 써줘야 한다. 오류 메세지 타고 따라가니 타입설정이 나왔다. </p>
<pre><code class="language-javascript">interface IProps {
  icon: SvgIconComponent;
  text: string;
  inputType?: string;
  name: Path&lt;ISignUpForm&gt;;
  register: UseFormRegister&lt;ISignUpForm&gt;;
  errorMsg?: string;
}</code></pre>
<h3 id="4-props-원하는-아이콘-보내주기">4. props 원하는 아이콘 보내주기</h3>
<p>원하는 아이콘을 홈페이지에서 설정해서 받아온다. import 로 꼭 받아와줘야 한다. (import 는 해당 컴포넌트를 props 로 보내주는 곳에 써줘야 한다.) 그리고 컴포넌트를 props 로 보내준다. 이때 &quot;&quot; 로 감쌀 필요는 없다. </p>
<pre><code class="language-javascript">&lt;TextField
          icon={DraftsOutlinedIcon}
          text={&quot;Email&quot;}
          name={&quot;email&quot;}
          inputType={&quot;email&quot;}
          errorMsg={errors.email &amp;&amp; &quot;이메일 형식이 맞지 않습니다.&quot;}
          register={register}
        /&gt;</code></pre>
<h2 id="결과">결과</h2>
<p>각가의 아이콘이 잘 가져 와졌다. 컴포넌트도 props 로 보내 줄 수 있어 정말 간편하고 코드가 깔끔했다. 전과 후를 살펴보자! 😊</p>
<p><img src="https://velog.velcdn.com/images/dev__note/post/c80a14cd-280f-4da2-b9ea-9030cfa1b623/image.png" alt=""></p>
<h2 id="에필로그">에필로그</h2>
<p>컴포넌트로 props 를 보내주고 공통화 시킨다는 것은 정말 효율적이고 깔끔한 코드가 완성 되는 것 같다. 아주 편리하다. css styling 을 해놓으니까 일단 마음도 편안해 졌다. 이번주에는 강점인 css 를 다뤄서 재미있기도 했다. 동시에 api 도 붙여야 하는데 이론 공부를 좀 하고 실제로 구현 해 볼 것 같다. 일주일에 하나씩 뭔가가 완성되니까 아주 좋다.🔥</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[react] 스플래시 화면 꾸미기 #1 - swiper 와 emotion 활용 with next.js]]></title>
            <link>https://velog.io/@dev__note/react-%EC%8A%A4%ED%94%8C%EB%9E%98%EC%8B%9C-%ED%99%94%EB%A9%B4-%EA%BE%B8%EB%AF%B8%EA%B8%B0-1-swiper-%EC%99%80-emotion-%ED%99%9C%EC%9A%A9-with-next.js</link>
            <guid>https://velog.io/@dev__note/react-%EC%8A%A4%ED%94%8C%EB%9E%98%EC%8B%9C-%ED%99%94%EB%A9%B4-%EA%BE%B8%EB%AF%B8%EA%B8%B0-1-swiper-%EC%99%80-emotion-%ED%99%9C%EC%9A%A9-with-next.js</guid>
            <pubDate>Mon, 20 Jun 2022 12:22:43 GMT</pubDate>
            <description><![CDATA[<h2 id="프롤로그">프롤로그</h2>
<p>progresive웹 앱으로 기록 앱을 만들기로 한 [그리디브] 팀원들. 약 3주차에 걸쳐 나는 회원가입 폼을 완성해 내었다. 그리고 부여 받은 할일은 style 을 입히는 작업이다. 에이전시에서 퍼블리싱 작업을 하고 있는 나는 html, css, js, jquery 로 주로 작업을 해왔는데 React 를 배우며 다양한 style 을 입히는 라이브러리와 방법들을 접해왔다. css 가 기초적 기반이라 어려움은 없었지만 사용방법이 달라 생소한 부분들이 많았다. 회원가입 폼을 styling 하기에 앞서 모바일의 첫 화면이 될 스플래쉬 화면을 먼저 꾸며 보기로 했다. </p>
<h2 id="스플래쉬-모바일-화면">스플래쉬 모바일 화면</h2>
<p><img src="https://velog.velcdn.com/images/dev__note/post/2ac51451-d01e-4b3f-968f-18fce0cd6449/image.png" alt=""></p>
<h2 id="스플래시-화면">스플래시 화면?</h2>
<p>모바일 앱을 시작할 때 첫인상과 같은 역할을 하는 표지 화면을 본 적이 있다. 우리는 그것을 스플래시 화면이라고 부른다. 로딩바가 진행 되면서 아이콘을 보여주는 어플도 있고 여러 페이지로 모바일 앱의 설명을 담아서 시작 가이드의 느낌을 주는 앱도 있다. 잘 꾸며진 스플래시 화면은 모바일 어플의 정체성을 만들고 좋은 첫인상을 심어줘서 앱을 더 궁금하게 만들기도 한다. </p>
<p>스플래시 화면에 대해 간략하게 설명하자면 이렇다고 한다. 
출처는 <a href="https://maily.so/tipster/posts/770ae91d">팁스터</a></p>
<blockquote>
<p>스플래시 스크린의 시작은 애플로, 더 나은 사용자 경험을 위해 앱이 빠르게 반응하는 것처럼 보이도록 Placeholder Image를 요구했어요. 기능적으로 이는 앱 아이콘을 탭 하고, 앱이 실제로 실행되는 사이의 짧은 간격을 메우는데 중요한 역할을 하게 되었습니다. 이제 스플래시는 기능적 측면보다 브랜딩 측면에서 더 자주 사용되고 있는데요. 반드시 거쳐가는 시간이자 화면이기 때문입니다.</p>
</blockquote>
<p>기능적인 부분도 있지만 브랜딩 측면에서 더욱 자주사용되고 중요한 부분. 나도 애니메이션을 사용한 스플래시 화면을 본 적이 있었는데 한눈에 어떤 앱인지 알 수 있었다. </p>
<h2 id="기능적인-부분-설명">기능적인 부분 설명</h2>
<p>화려한 애니메이션이 있으면 좋겠지만 우선 사용자가 직접 넘기면서 간략한 설명을 인지 할 수 있도록 4페이지의 화면을 구성했다. (중간에 겹치는 부분은 생략하기로 했다.) 그리고 마지막 화면에는 로그인 페이지를 넣고 누르면 이동 할 수 있도록 해주자. </p>
<p><code>swiper</code> 라이브러리르 사용해 슬라이드와 페이지네이션 기능을 넣고, <code>emotion</code> 으로 styling 을 해주기로 하자.</p>
<h2 id="swiper">swiper</h2>
<p>swiper 는 js 사용법과 비슷했다. 오히려 사실 react 가 더 간략해서 허무했을 정도
<a href="https://swiperjs.com/react">swiper 리액트</a>
cdn link 로 불러오지 않고 yarn 으로 설치해 줬다. </p>
<pre><code class="language-javascript">
yarn add swiper</code></pre>
<p>그리고 바로 import 하고 컴포넌트 구조를 만들어 준다. 
길어보이지만 <code>swiper</code> 안에 필요한 <code>swiperSlide</code> 컴포넌트를 만들어 주면  된다. <code>swiper</code> 에는 필요한 옵션들을 <code>props</code> 로 보내주자. 나는 페이지네이션을 쓸거라서 <code>modules</code> 로 <code>pagination</code> 을 따로 설치해주고 불러왔다. (css 도 함께 import 해주어야 한다.) </p>
<pre><code class="language-javascript"> &lt;Swiper
          modules={[Pagination]}
          pagination={{
            type: &quot;fraction&quot;,
            clickable: true,
          }}
          spaceBetween={0}
          slidesPerView={1}
          className=&quot;mySwiper&quot;
          onSlideChange={() =&gt; console.log(&quot;slide change&quot;)}
          onSwiper={(swiper) =&gt; console.log(swiper)}
        &gt;
          &lt;SwiperSlide&gt;
            &lt;main css={[main]}&gt;
              &lt;Image
                src={mainImage01}
                layout=&quot;fill&quot;
                objectFit=&quot;cover&quot;
                quality={100}
              /&gt;
              &lt;h1 css={[title]}&gt;
                DEAR MY
                &lt;br /&gt; YOGA JOURNEY
              &lt;/h1&gt;
              &lt;Image src={mainIcon} css={yogapose01} width={694} height={455} /&gt;
            &lt;/main&gt;
          &lt;/SwiperSlide&gt;
          &lt;SwiperSlide&gt;
            &lt;main css={[main]}&gt;
              &lt;Image
                src={mainImage01}
                layout=&quot;fill&quot;
                objectFit=&quot;cover&quot;
                quality={100}
              /&gt;
              &lt;h1 css={[title]}&gt;
                We practice &lt;br /&gt; Yoga and &lt;br /&gt; Keep a Journal
              &lt;/h1&gt;
              &lt;Image src={mainIcon} css={yogapose02} width={694} height={455} /&gt;
            &lt;/main&gt;
          &lt;/SwiperSlide&gt;
          &lt;SwiperSlide&gt;
            &lt;main css={[main]}&gt;
              &lt;Image
                src={mainImage01}
                layout=&quot;fill&quot;
                objectFit=&quot;cover&quot;
                quality={100}
              /&gt;
              &lt;h1 css={[title]}&gt;
                WE &lt;br /&gt; CONNECTED WITH LIFE &lt;br /&gt; THROUGH THIS
              &lt;/h1&gt;
              &lt;Image
                src={mainIcon02}
                css={yogapose03}
                width={694}
                height={455}
              /&gt;
            &lt;/main&gt;
          &lt;/SwiperSlide&gt;
          &lt;SwiperSlide&gt;
            &lt;main css={[main]}&gt;
              &lt;Image
                src={mainImage01}
                layout=&quot;fill&quot;
                objectFit=&quot;cover&quot;
                quality={100}
              /&gt;
              &lt;h1 css={[title]}&gt;
                DEAR MY &lt;br /&gt; YOGA JOURNEY
              &lt;/h1&gt;
              &lt;div css={[utils]}&gt;
                &lt;div css={utils_tit}&gt;Start your Yoga Life&lt;/div&gt;
                &lt;Button css={button} onClick={() =&gt; linkTo(&quot;/signup&quot;)}&gt;
                  Join Now
                &lt;/Button&gt;
                &lt;Button css={button} onClick={() =&gt; linkTo(&quot;/login&quot;)}&gt;
                  Login
                &lt;/Button&gt;
              &lt;/div&gt;
            &lt;/main&gt;
          &lt;/SwiperSlide&gt;
        &lt;/Swiper&gt;</code></pre>
<h2 id="emotion">emotion</h2>
<p>emotion 으로 styling 을 해주기 위해 emotion 설치 진행해준다. 
<a href="https://emotion.sh/docs/install">Emotion 리액트</a></p>
<pre><code class="language-javascript">yarn add @emotion/react</code></pre>
<p>css 를 그냥 쓰는게 익숙해져있는 나는 className 을 주고 css 를 써도 됐지만 emotion 을 사용해서  (더 리액트 다운 방식으로 🤔) 스타일링을 해주기로 했다. 하지만 bakcground 를 불러오거나 이미지를 불러오는 부분에 있어서 조금 애를 먹었다. </p>
<h3 id="emotion-으로-global-style-지정">emotion 으로 global style 지정</h3>
<p>우선 가장 기초적인 reset css 를 글로벌로 불러오자. 이 emotion 에 들어있는 reset 부분도 따로 설치를 해줬다. 그리고 global 도 꺼내 와준다. </p>
<pre><code class="language-javascript">import emotionReset from &quot;emotion-reset&quot;;
import { Global, css } from &quot;@emotion/react&quot;;</code></pre>
<p>리턴 함수에 <code>&lt;&gt;&lt;/&gt;</code> 감싸주고 하위에 글로벌 컴포넌트로 감싸주었다. 
emotion 은 <code>css``</code> 이런식으로 정의하고 css 속성을 주었다. </p>
<pre><code class="language-javascript"> &lt;Global
        styles={css`
          ${emotionReset}

          body {
            font-family: &quot;Roboto&quot;;
            width: 390px;
            margin: 0 auto;
          }
        `}
      /&gt;</code></pre>
<h3 id="emotion-으로-background-주기">emotion 으로 background 주기</h3>
<p>그리고 이제 배경화면을 줘야하는데 아무리 찾아도 emotion 에서 백그라운드를 어떻게 props 로 보내주는지 모르겠는것이다😨 그러다가 next.js 에서 emotion 을 활용해서 bakcgournd 전체를 주는 방법을 찾았는데 next.js 에서 제공하는 <code>&lt;Image/&gt;</code> 컴포넌트를 활용하는 것이었다. </p>
<p><a href="https://nextjs.org/docs/api-reference/next/image#quality">nextjs 이미지 사용법</a>
<a href="https://github.com/vercel/next.js/blob/canary/examples/image-component/pages/background.js">백그라운드 이미지</a></p>
<p>공식문서에서 제공하는 예제를 따라봤다. 
방법은 이러하다. <code>&lt;Image/&gt;</code> 컴포넌트를 활용 해서 프로퍼티로 그 이미지를 꽉 채워주는것이다. <code>layout=&quot;fill&quot;</code> 속성을 주자! 이 속성은 주로 함께 <code>objectFit</code> 속성과 함께 온다고 한다. <code>quality</code> 는 화질설정! </p>
<p>그리고 그 이미지 컴포넌트는 <code>position: relative</code> 로 떠 있다. <del>근데 진짜 emotion 에서 백그라운드로 이미지를 주는 방법은 없는 것인가?</del> </p>
<pre><code class="language-javascript">&lt;Image
  src={mainImage01}
  layout=&quot;fill&quot;
  objectFit=&quot;cover&quot;
  quality={100}
/&gt;</code></pre>
<p>그리고 nextjs 에서 image 를 불러오는 방식도 따로 존재했다. 
import 로 불러오거나 require 로 직접 써주거나! 
import 로 불러오면 각 이미지의 사용할 이름을 써주고 불러온다.
image 폴더는 public 폴더 안에 넣어 주었다.</p>
<pre><code class="language-javascript">import Humaaans from &quot;../public/img/Humaaans.png&quot;;
import mainImage01 from &quot;../public/img/mainbackground.png&quot;;
import mainIcon from &quot;../public/img/mainIcon.png&quot;;
import mainIcon02 from &quot;../public/img/mainIcon02.png&quot;;

&lt;Image src={mainIcon} css={yogapose01} width={694} height={455} /&gt;
</code></pre>
<h3 id="emotion-으로-css-styling">emotion 으로 css styling</h3>
<p>그리고 emotion 의 방식이 불편하면서도 적응이 되면 편하려나
이렇게 각 클래스를 부여하는 것 처럼 <code>css={classname 명}</code> 지어주고, 각 변수로 할당해 주었다. styling 은 사실 css 방식과 동일</p>
<pre><code class="language-javascript">const main = css`
  height: 100vh;
  width: 100%;
`;

const title = css`
  font-size: 3vw;
  font-weight: 800;
  height: 350px;
  color: #222;
  opacity: 0.9;
  padding: 15vw 1.5vw 5vw;
  box-sizing: border-box;
  letter-spacing: -0.01em;
  font-family: &quot;Roboto Mono&quot;;
`;
const imagebox = css`
  width: 100%;
  background-color: #2a4337;
`;
const yogapose01 = css`
  transform: translateX(10%);
  width: 150% !important;
  height: 455px !important;
  max-width: initial !important;
`;
const yogapose02 = css`
  transform: translateX(-40%);
  width: 150% !important;
  height: 455px !important;
  max-width: initial !important;
`;
const yogapose03 = css`
  width: 40% !important;
  height: 100% !important;
  max-width: initial !important;
  min-width: initial !important;
  transform: translateX(50%);
`;
const utils = css`
  display: flex;
  flex-direction: column;
  position: relative;
  z-index: 9;
  padding: 1.5vw;
`;
const utils_tit = css`
  font-size: 2vw;
  line-height: 1.4;
  color: #fff;
`;
const button = css`
  color: #3a3a3a;
  background-color: #fff;
  opacity: 0.8;
  border-radius: 5px;
  outline: none;
  appearance: none;
  border: none;
  padding: 13px;
  margin: 5px 0;
  font-size: 2vw;
  color: #2a4337;
  font-weight: 600;
`;</code></pre>
<p>각각의 클래스네임을 지정해주고 css 부여 </p>
<h3 id="emotion-을-더-잘-사용하기-위하여">emotion 을 더 잘 사용하기 위하여</h3>
<p>emotion 을 쓰다보니 불편한 점이 하나 있었는데 빽틱을 사용하다 보니까 자동완성이 안되는 것이었다. vs code 를 사용하는 이유중의 하나인것을..<br>근데 바로 vs code 플러그인이 있다는 사실을 듣고 설치를 진행했다. 그리고 아주 편안하고 오타없이 css 작성을 할 수 있었다. </p>
<p><img src="https://velog.velcdn.com/images/dev__note/post/0e7c1b5f-28c5-45a3-a954-7a84cf56bb1a/image.png" alt="styled 플러그인"></p>
<h2 id="결과">결과</h2>
<p><img src="https://velog.velcdn.com/images/dev__note/post/ca0903a3-1ded-4e8f-8cf1-b604a819e636/image.gif" alt=""></p>
<h2 id="에필로그">에필로그</h2>
<p>css 를 작성하는데는 무리가 없었지만 새로운 방식으로 진행을 하다보니 꽤 시간이 걸렸다. 하지만 공식문서를 토대로 찾아보고 어느정도 아는 개념들이 있어 막막하지는 않았다. 하지만 css 한 파일에 코드를 몰아 넣지 않고, 컴포넌트 별로 분리하고 그 안에서 모든 것을 작성하는 방식이 효율적인지는 아직은 모르겠다. <del>기껏 많아 봤자 두페이지 정도 밖에 되지 않기 때문이다.</del>  뭔가 더 작은 컴포넌트 단위로 나누어야 할 것 같다. 폴더 구조도 아직 정확한 개념은 아니어서 아무래도 이번 스터디 때 팀원들에게 도움을 요청 해봐야 겠다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[react] 회원가입 폼 만들기 #3 - react-hook-form 라이브러리]]></title>
            <link>https://velog.io/@dev__note/react-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85-%ED%8F%BC-%EB%A7%8C%EB%93%A4%EA%B8%B0-3-react-hook-form-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC</link>
            <guid>https://velog.io/@dev__note/react-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85-%ED%8F%BC-%EB%A7%8C%EB%93%A4%EA%B8%B0-3-react-hook-form-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC</guid>
            <pubDate>Mon, 13 Jun 2022 14:48:14 GMT</pubDate>
            <description><![CDATA[<h2 id="프롤로그">프롤로그</h2>
<p>react 만으로는 해결하지 못했던 상태관리 공유 + 리랜더링 최적화 를 고민했지만 사실 답을 찾기 어려웠다. (답을 찾으면 다시 글로 정리하겠다) 리액트를 쓰며 똑같은 고민을 한 사람들이 조금 더 편하게 쓰기위해 라이브러리를 만들어 놓지 않았을까? 라이브러리의 작동방식을 파헤쳐보는 것이 결국 리액트에서 막혔던 부분들에 대한 답이 아닐까 싶다. 
우선은 회원가입 폼을 구현하기 위해 react-hook-form 라이브러리르 써보기로 했다. [그리디브] 팀원의 도움을 받아 옆에서 그의 사고흐름을 지켜보기로 했다. </p>
<p>기술 스택은
<code>react</code> <code>next.js</code> <code>typescript</code> <code>emotion</code> </p>
<h2 id="어떻게-적용할-것인가">어떻게 적용할 것인가?</h2>
<p>일단 팀원&lt;야잴&gt;이 알려준 팁은 모르면 구글 검색을 하는데 <strong>하나의 글만 보지말고 최신순으로 그 페이지 내에 있는 글을 모두 보는 것이다.</strong> 하나의 글만 보고 하게 되면 모르는 부분이 있을 뿐더러 오류가 나게 되면 다른 문제로 빠질 경우가 많기 때문에 페이지에 있는 다른 글들을 모두 보는 것을 추천한다고 한다. </p>
<p><strong>날짜도 중요하다.</strong> js 라이브러리들은 update 속도가 빠르기 때문에 최신순을 보는 것이 좋다. </p>
<p><strong>공식문서에서 먼저 보는것이 가장 좋다.</strong> 공식문서는 라이브러리를 만든 사람의 의도가 들어있기 때문에 공식문서 보는 것을 두려워 하지 말자. 다양한 예들도 많기 때문에 내가 구현하고자 하는 기능들과 비슷 한 예들을 찾아보자. </p>
<p><strong>버전확인을 하자</strong> 공식문서를 볼때 docs 에서의 버전확인은 필수사항이다. 내부 설정같은 세밀한 사항들이도 바뀌고, 종속성 있는 버전들도 업그레이드 되기 때문이라고 한다. 버전이 다르면 구현이 안되는 경우도 있다. 버전일치도 확인해 주자.</p>
<h2 id="react-hook-form">react-hook-form</h2>
<p><a href="https://react-hook-form.com/get-started">react-hook-form 공식문서</a></p>
<p>초기에 작성했던 코드와 다르게 뭔가 간단해졌다. 블로그와 공식문서를 따라가며 하나씩 치환작업을 진행했다. </p>
<h3 id="1-각각의-타입을-지정해-주었다">1. 각각의 타입을 지정해 주었다.</h3>
<pre><code class="language-javascript">interface ISignUpForm {
  email: string;
  name: string;
  pw: string;
  checkPw: string;
  phone: string;
  birth: string;
}</code></pre>
<h3 id="2-useform이라는-커스텀-훅">2. useForm()이라는 커스텀 훅</h3>
<p>상태관리는 useForm() 이 담당해 주는 것 같다. form 을 관리하기 위한 커스텀 훅을 react-hook-form 에서 제공한다. <code>등록</code> <code>제출</code> <code>에러처리</code> 를 관리해준다. 여기서 상태가 변하면 resolver 가 yupResolver 를 통해 schema 로 전달해준다.  </p>
<pre><code class="language-javascript">const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm&lt;ISignUpForm&gt;({
    mode: &quot;onChange&quot;,
    reValidateMode: &quot;onChange&quot;,
    resolver: yupResolver(schema),
  });</code></pre>
<h3 id="3-schema-에서-유효성-검사를-진행해-준다">3. schema 에서 유효성 검사를 진행해 준다.</h3>
<p>yup 이라는 라이브러리도 설치해 줘야 한다 .
yup은 정규식을 조금 쉽게 처리 할 수 있게 도와주는 라이브러이다. 
resolver 로 받아온 값들을 schema 에서 받아 유효성 검사를 실시해 준다.
이부분에서는 값 비교도 가능하다. 값을 그대로 가져와서 비밀번호, 비밀번호 확인까지 진행 가능하다. (내가 원하던 부분을 충족시켜줬다.) </p>
<pre><code class="language-javascript">const schema = yup.object().shape({
    email: yup.string().email().required(),
    name: yup.string().min(2).max(10).required(),
    pw: yup.string().matches(regex.pw).required(),
    checkPw: yup
      .string()
      .oneOf([yup.ref(&quot;pw&quot;), null])
      .required(),
    phone: yup.string().matches(regex.phone).required(),
    birth: yup.string().required(),
  });
</code></pre>
<h3 id="4-컴포넌트-분리">4. 컴포넌트 분리</h3>
<p>상태관리를 라이브러리를 써서 해결 되니 component 도 분리가 가능해졌다. 
<code>&lt;TextField&gt;</code> 컴포넌트로 분리 해주고 값들은 props 로 받는다. 각각의 타입도 지정해 준다. 각각의 타입은 코드 v1.1.0 에서 확인하자 :-) </p>
<pre><code class="language-javascript">
const TextField = ({
  text,
  name,
  inputType = &quot;text&quot;,
  register,

  errorMsg,
  ...others
}: IProps) =&gt; {
  console.log(&quot;in TextField&quot;, others);
  return (
    &lt;div css={[fieldWrapper]}&gt;
      &lt;label htmlFor={name}&gt;{text}&lt;/label&gt;
      &lt;input type={inputType} {...register(name)} css={[inputStyle]} /&gt;
      {errorMsg &amp;&amp; &lt;span css={[errorMsgStyle]}&gt;{errorMsg}&lt;/span&gt;}
    &lt;/div&gt;
  );
};</code></pre>
<h2 id="결과">결과</h2>
<p><img src="https://velog.velcdn.com/images/dev__note/post/53ff1485-c110-41ec-bd2d-04106f3d14da/image.gif" alt="form 화면"></p>
<h2 id="코드-v110">코드 v1.1.0</h2>
<p>그래서 전체 코드를 보면 이렇다. emotion 으로 css 도 곁들였다. </p>
<p><strong>signUp.tsx</strong></p>
<pre><code class="language-javascript">import * as yup from &quot;yup&quot;;
import { yupResolver } from &quot;@hookform/resolvers/yup&quot;;
import { useForm, SubmitHandler } from &quot;react-hook-form&quot;;
import { regex } from &quot;@lib/utils&quot;;
import { useEffect, useRef, useState } from &quot;react&quot;;
import { TextField } from &quot;components/molecules&quot;;
import { css } from &quot;@emotion/react&quot;;

interface ISignUpForm {
  email: string;
  name: string;
  pw: string;
  checkPw: string;
  phone: string;
  birth: string;
}

const SignUp = () =&gt; {
  const schema = yup.object().shape({
    email: yup.string().email().required(),
    name: yup.string().min(2).max(10).required(),
    pw: yup.string().matches(regex.pw).required(),
    checkPw: yup
      .string()
      .oneOf([yup.ref(&quot;pw&quot;), null])
      .required(),
    phone: yup.string().matches(regex.phone).required(),
    birth: yup.string().required(),
  });

  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm&lt;ISignUpForm&gt;({
    mode: &quot;onChange&quot;,
    reValidateMode: &quot;onChange&quot;,
    resolver: yupResolver(schema),
  });

  const submitForm: SubmitHandler&lt;ISignUpForm&gt; = (data) =&gt; console.log(data);

  return (
    &lt;form onSubmit={handleSubmit(submitForm)} css={[formWrapper]}&gt;
      &lt;TextField
        text={&quot;이메일&quot;}
        name={&quot;email&quot;}
        inputType={&quot;email&quot;}
        errorMsg={errors.email &amp;&amp; &quot;이메일 형식이 맞지 않습니다.&quot;}
        register={register}
      /&gt;
      &lt;TextField
        text={&quot;닉네임&quot;}
        name={&quot;name&quot;}
        errorMsg={errors.name &amp;&amp; &quot;2글자 이상 입력해주세요.&quot;}
        register={register}
      /&gt;
      &lt;TextField
        text={&quot;비밀번호&quot;}
        name={&quot;pw&quot;}
        errorMsg={
          errors.pw &amp;&amp; &quot;숫자+영문자+특수문자 조합으로 8자리 이상 입력해주세요!&quot;
        }
        register={register}
      /&gt;
      &lt;TextField
        text={&quot;비밀번호 확인&quot;}
        name={&quot;checkPw&quot;}
        errorMsg={errors.checkPw &amp;&amp; &quot;떼잉~ 비밀번호가 똑같지 않아요!&quot;}
        register={register}
      /&gt;
      &lt;TextField
        text={&quot;전화번호&quot;}
        name={&quot;phone&quot;}
        errorMsg={errors.phone &amp;&amp; &quot;올바른 형식이 아닙니다!&quot;}
        register={register}
      /&gt;
      &lt;TextField
        text={&quot;생년월일&quot;}
        name={&quot;birth&quot;}
        inputType=&quot;date&quot;
        errorMsg={errors.birth &amp;&amp; &quot;생년월일을 입력해주세요.&quot;}
        register={register}
      /&gt;
      &lt;button type=&quot;submit&quot;&gt;회원가입&lt;/button&gt;
    &lt;/form&gt;
  );
};

export default SignUp;

const formWrapper = css`
  width: 500px;
  border: 1px solid #ddd;
  border-radius: 10px;
  margin: 0 auto;
  padding: 50px;
`;
</code></pre>
<p><strong>{ TextField } from &quot;components/molecules&quot;</strong></p>
<pre><code class="language-javascript">import { css } from &quot;@emotion/react&quot;;
import styled from &quot;@emotion/styled&quot;;
import React from &quot;react&quot;;
import { Path, UseFormRegister } from &quot;react-hook-form&quot;;
interface TSignUpFieldValues {
  email: string;
  name: string;
  pw: string;
  checkPw: string;
  phone: string;
  birth: string;
}
interface IProps {
  text: string;
  inputType?: string;

  name: Path&lt;TSignUpFieldValues&gt;;
  register: UseFormRegister&lt;TSignUpFieldValues&gt;;
  errorMsg?: string;
}
const TextField = ({
  text,
  name,
  inputType = &quot;text&quot;,
  register,

  errorMsg,
  ...others
}: IProps) =&gt; {
  console.log(&quot;in TextField&quot;, others);
  return (
    &lt;div css={[fieldWrapper]}&gt;
      &lt;label htmlFor={name}&gt;{text}&lt;/label&gt;
      &lt;input type={inputType} {...register(name)} css={[inputStyle]} /&gt;
      {errorMsg &amp;&amp; &lt;span css={[errorMsgStyle]}&gt;{errorMsg}&lt;/span&gt;}
    &lt;/div&gt;
  );
};

export default TextField;

const fieldWrapper = css`
  padding: 10px;
  margin: 10px;
  padding: 10px;
  display: flex;
  align-items: flex-start;
  flex-direction: column;
  text-align: left;
`;

const inputStyle = css`
  outline: none;
  padding: 10px 0px;
  width: 100%;
  border: none;
  border-bottom: 1px solid #ddd;
  margin-bottom: 5px;
`;

const errorMsgStyle = css`
  font-size: 12px;
  color: #f00;
  line-height: 1.4;
`;
</code></pre>
<h2 id="에필로그">에필로그</h2>
<p>react-hook-form 은 form 을 만드는데 최적화 된 라이브러리가 아닐까 생각해보지만 라이브러리를 쓸때마다 제대로 알지 못했다는 느낌이 드는 것은 사실이다. 앞서 말했던 것처럼 이 라이브러리의 작동 방식이 결국 리액트로 풀지 못한 해답일텐데 말이다. 모르는 문제는 검색을 하고 코드를 붙여 넣어보고 하며 뚝딱뚝딱 만들어 해결은 가능하나 원론적인 문제는 해결하지 못하는게 좀 아쉽다. (다시말하지만 해결하면 다시 글을 쓰겠다) </p>
<p>팀원&lt;야잴&gt; 이 해결하는 방식을 옆에서 보는것은 흥미로웠다. 나보다 잘 하는 사람들은 어떤식으로 문제를 해결하는 지 보고 좋은 점은 흡수 해버릴 수 있으니까 말이다. 또한 일주일에 한번씩 (각자 자기가 맡은 역할을 해온 후에) 하는 미팅은 어떻게 코드를 리뷰하고 서로 의사소통을 하는지에 대해서 많이 알 수 있는 시간이다. 주로 나는 듣기만 하지만 연차 쌓인 경력자들을 옆에 두고 함께 프로젝트를 하는 것은 정말이지 운이 좋지 않을 수 없다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[react] 회원가입 폼 만들기 #2 - 반복되는 코드 합치기 그리고 한계 ]]></title>
            <link>https://velog.io/@dev__note/react-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85-%ED%8F%BC-%EB%A7%8C%EB%93%A4%EA%B8%B0-%EB%B0%98%EB%B3%B5%EB%90%98%EB%8A%94-%EC%BD%94%EB%93%9C-%ED%95%A9%EC%B9%98%EA%B8%B0-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%ED%95%9C%EA%B3%84</link>
            <guid>https://velog.io/@dev__note/react-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85-%ED%8F%BC-%EB%A7%8C%EB%93%A4%EA%B8%B0-%EB%B0%98%EB%B3%B5%EB%90%98%EB%8A%94-%EC%BD%94%EB%93%9C-%ED%95%A9%EC%B9%98%EA%B8%B0-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%ED%95%9C%EA%B3%84</guid>
            <pubDate>Mon, 13 Jun 2022 11:41:22 GMT</pubDate>
            <description><![CDATA[<h2 id="프롤로그">프롤로그</h2>
<p>[그리디브] 스터디를 하며 회원가입 폼을 만들어 보기로 했었다. 리액트를 공부하며 회원가입 폼을 완성 했으나 비효율 적이라고 생각이 들었다. </p>
<p>*<em>기능은 잘 작동 하지만 반복되는 코드가 많았다. *</em></p>
<p>팀원들이 코드리뷰를 해주었는데 같은 의견이었다. 반복되는 코드는 최대한 줄이고 하나의 컴포넌트로 합치면 어떨까라는 피드백을 받았다. 그래서 지금 보다 더 간단하게 코드를 수정해 보기로 했다. </p>
<p>그리고 코드를 수정하는데 있어 몇가지 고민이 필요했다. </p>
<h2 id="1-비제어-컴포넌트-vs-제어-컴포넌트">1. 비제어 컴포넌트 vs 제어 컴포넌트</h2>
<p>폼을 만들고 상태 관리를 하는 방식에는 두가지 방식이 있다. 
<strong>비제어 컴포넌트와 제어 컴포넌트</strong></p>
<p>일단 기본적으로 폼 엘리먼트는 비제어 컴포넌트이다. 하지만 폼 엘리먼트의  value를 prop으로 관리하도록 하면 그것이 제어 컴포넌트 방식이다.</p>
<h3 id="비제어-컴포넌트의-플로우">비제어 컴포넌트의 플로우</h3>
<ol>
<li>사용자 입력 이벤트 발생</li>
<li>DOM 엘리먼트의 상태를 직접 변경</li>
</ol>
<h3 id="제어-컴포넌트의-플로우">제어 컴포넌트의 플로우</h3>
<ol>
<li>사용자 입력 이벤트 발생</li>
<li>이벤트 핸들러에서 setState(newState)</li>
<li>수정된 state를 바탕으로 react가 엘리먼트를 리랜더 함</li>
</ol>
<h3 id="비제어와-제어-컴포넌트의-사용">비제어와 제어 컴포넌트의 사용</h3>
<p>그렇다면 언제 비제어 컴포넌트와 제어 컴포넌트를 사용해야 할까? 🤔
리액트 공식문서에서는 대부분의 경우 폼을 구현하는데 제어 컴포넌트를 사용하는게 좋다고 한다.  </p>
<blockquote>
<p>기능 사용에 있어 <strong>실시간 제어 가능 &amp; 유효성 검사</strong>가 있다면 제어 컴포넌트를 쓰자 </p>
</blockquote>
<p>하지만 제어 컴포넌트를 사용하는 것에 대한 대가도 분명 존재 한다. </p>
<blockquote>
<p>Form 을 제어 컴포넌트로 잘 관리하기 위해서는 state 관리 위치에 대한 고민 &amp; 잦은 리랜더링을 방지하기 위한 성능 최적화가 필요하다.</p>
</blockquote>
<h2 id="2-상태관리를-어느-레벨에서-할-것인가">2. 상태관리를 어느 레벨에서 할 것인가?</h2>
<p>이말인 즉슨, 어떤 범위에서 상태를 참조하고 있는가? 이다. 
그리고 세가지의 경우로 나타나진다. </p>
<blockquote>
<ol>
<li>컴포넌트 내에서 local state 를 직접 관리 (singUp) //비효율적 </li>
<li>부모로 부터 props 를 통해 state를 내려받기 ⇒ useRef useState ⇒ Form </li>
<li>contextAPI 또는 reducx 등 전역 상태에서 관리하기 ( 또는 상태관리 라이브러리 등을 통해 전역 state를 받기 )</li>
</ol>
</blockquote>
<p>리액트를 공부하면서 고민하고 채워나갈 부분 중 하나는 상태관리를 어떻게 효율적으로 하는가? 에 대한 답을 찾는 것이라고 생각한다. 다양한 경우가 있는 그 상태에서 어떤 것이 더 나은 결정인가..? </p>
<h3 id="1-첫번째--컴포넌트-내에서-local-상태-관리를-한다면">1) 첫번째 : 컴포넌트 내에서 (local) 상태 관리를 한다면?</h3>
<ul>
<li>간단한 form 의 변화들은 local 에서 상태관리를 해도 무방하다. </li>
<li>간단한 form 들은 전역상태로 관리하게 되면 불필요한 렌더링이 발생하기 때문에 오히려 제어 컴포넌트보다 비제어 컴포넌트가 더 심플하고 성능이 좋을 수 있다. </li>
</ul>
<h3 id="2-두번째--컴포넌트를-나누고-props-를-통해-상태관리">2) 두번째 : 컴포넌트를 나누고 props 를 통해 상태관리</h3>
<ul>
<li>컴포넌트를 나누고 쪼개었더니 값을 공유하기 어려워 졌다.
예를들어, 비밀번호를 입력하고 비밀번호 확인을 하려면 값을 비교해야 한다. 그런데 컴포넌트를 나눴더니 이 값을 어떻게 공유해야 할것인가? 에 대한 물음이 생겼다. </li>
</ul>
<p><em>일단 페이지를 분리한다고 했지만 부족하다 부족해😥</em></p>
<p>** 함수 컴포넌트로 반복되는 부분은 합쳤다. ** 
setState 를 종류와 각 항목별로 써줬다면 지금은 종류별로만 써줘도 충분했다.  </p>
<pre><code class="language-javascript">import React, { useState, useRef } from &quot;react&quot;;

export default function Form(props) {
  const [val, setVal] = useState(&quot;&quot;);
  const [message, setMessage] = useState(&quot;&quot;);
  const [valid, setValid] = useState(false);

  const list = [
    /^[a-zA-z0-9]{4,12}$/, //id
    /^(?=.*[a-zA-Z])(?=.*[!@#$%^*+=-])(?=.*[0-9]).{8,25}$/, //password
    /^[A-Za-z0-9_]+[A-Za-z0-9]*[@]{1}[A-Za-z0-9]+[A-Za-z0-9]*[.]{1}[A-Za-z]{1,3}$/, //email
    /^01([0|1|6|7|8|9])-?([0-9]{3,4})-?([0-9]{4})$/, //phone
  ];

  const onChange = (e) =&gt; {
    const val = e.target.value;
    const name = e.target.name;
    setVal(val);

    // 유효성 검사
    if (name == &quot;id&quot; || name == &quot;email&quot;) {
      const RegExp = list[props.reg];
      const validTest = RegExp.test(val);
      if (!validTest) {
        setMessage(props.errorMsg);
        setValid(false);
      } else {
        setMessage(props.sucessMsg);
        setValid(true);
      }
    }

    // 갯수 검사
    if (name == &quot;name&quot;) {
      if (val.length &lt; 2 || val.length &gt; 5) {
        setMessage(props.errorMsg);
        setValid(false);
      } else {
        setMessage(props.sucessMsg);
        setValid(true);
      }
    }
  };

  return (
    &lt;div className=&quot;form-el&quot; style={{ marginBottom: &quot;10px&quot; }}&gt;
      &lt;label htmlFor={props.htmlFor} style={{ marginRight: &quot;10px&quot; }}&gt;
        {props.text}
      &lt;/label&gt;
      &lt;input id={props.id} name={props.name} value={val} onChange={onChange} /&gt;
      &lt;p className=&quot;message&quot;&gt; {message} &lt;/p&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>** 주석처리 부분을 함수 컴포넌트 페이지로 합쳤다. **
그런데 생각보다 반복되지만 반복되지 않는 부분이 많았다. 비밀번호 확인같은 경우는 변경 값을 저장하고 있어줘야 했는데 함수 컴포넌트로 따로 빼주게 되면 값을 확인 해 줄 수 없었다. 
전화번호 같은경우에는 &#39;-&#39; 생성을 위한 함수가 한번 더 필요했기에 함수 컴포넌트로 합칠 수 없었다. 그렇게 다양한 이유들로 생각보다 코드를 합치지 못했다. </p>
<pre><code class="language-javascript">
import { NextPage } from &quot;next&quot;;
import { useState, useRef, useEffect } from &quot;react&quot;;
import Button from &quot;../components/lib/button&quot;;
import React from &quot;react&quot;;
import Form from &quot;../components/lib/Form&quot;;

const Signup: NextPage = () =&gt; {
  const [password, setPassword] = React.useState(&quot;&quot;);
  const [passwordConfirm, setPasswordConfirm] = React.useState(&quot;&quot;);
  const [phone, setPhone] = React.useState(&quot;&quot;);
  const [birth, setBirth] = React.useState(&quot;&quot;);

  const [passwordMessage, setPasswordMessage] = React.useState(&quot;&quot;);
  const [passwordConfirmMessage, setPasswordConfirmMessage] =
    React.useState(&quot;&quot;);
  const [phoneMessage, setPhoneMessage] = React.useState(&quot;&quot;);

  const [isPassword, setIsPassword] = React.useState(false);
  const [isPasswordConfirm, setIsPasswordConfirm] = React.useState(false);
  const [isPhone, setIsPhone] = React.useState(false);
  const [isBirth, setIsBirth] = React.useState(false);

  const onChangePassword = (e) =&gt; {
    const currentPassword = e.target.value;
    setPassword(currentPassword);

    const passwordRegExp =
      /^(?=.*[a-zA-Z])(?=.*[!@#$%^*+=-])(?=.*[0-9]).{8,25}$/;
    if (!passwordRegExp.test(currentPassword)) {
      setPasswordMessage(
        &quot;숫자+영문자+특수문자 조합으로 8자리 이상 입력해주세요!&quot;
      );
      setIsPassword(false);
    } else {
      setPasswordMessage(&quot;안전한 비밀번호 입니다.&quot;);
      setIsPassword(true);
    }
  };

  const onChangePasswordConfirm = (e) =&gt; {
    const currentPasswordConfirm = e.target.value;
    setPasswordConfirm(currentPasswordConfirm);
    if (password !== currentPasswordConfirm) {
      setPasswordConfirmMessage(&quot;떼잉~ 비밀번호가 똑같지 않아요!&quot;);
      setIsPasswordConfirm(false);
    } else {
      setPasswordConfirmMessage(&quot;똑같은 비밀번호를 입력했습니다.&quot;);
      setIsPasswordConfirm(true);
    }
  };

  const onChangePhone = (getNumber) =&gt; {
    const currentPhone = getNumber;
    setPhone(currentPhone);
    const phoneRegExp = /^01([0|1|6|7|8|9])-?([0-9]{3,4})-?([0-9]{4})$/;

    if (!phoneRegExp.test(currentPhone)) {
      setPhoneMessage(&quot;올바른 형식이 아닙니다!&quot;);
      setIsPhone(false);
    } else {
      setPhoneMessage(&quot;사용 가능한 번호입니다:-)&quot;);
      setIsPhone(true);
    }
  };

  const addHyphen = (e) =&gt; {
    const currentNumber = e.target.value;
    setPhone(currentNumber);
    if (currentNumber.length == 3 || currentNumber.length == 8) {
      setPhone(currentNumber + &quot;-&quot;);
      onChangePhone(currentNumber + &quot;-&quot;);
    } else {
      onChangePhone(currentNumber);
    }
  };

  const onChangeBirth = (e) =&gt; {
    const currentBirth = e.target.value;
    setBirth(currentBirth);
    setIsBirth(true);
  };

  return (
    &lt;div style={{ width: &quot;500px&quot;, margin: &quot;0 auto&quot; }}&gt;
      &lt;h3&gt;Sign Up&lt;/h3&gt;
      &lt;div className=&quot;form&quot;&gt;
        &lt;Form
          htmlFor=&quot;id&quot;
          id=&quot;id&quot;
          name=&quot;id&quot;
          text=&quot;ID&quot;
          sucessMsg=&quot;사용가능한 아이디 입니다.&quot;
          errorMsg=&quot;4-12사이 대소문자 또는 숫자만 입력해 주세요!&quot;
          reg=&quot;0&quot;
        /&gt;

        &lt;Form
          htmlFor=&quot;name&quot;
          id=&quot;name&quot;
          name=&quot;name&quot;
          text=&quot;Nick Name&quot;
          sucessMsg=&quot;사용가능한 닉네임 입니다.&quot;
          errorMsg=&quot;닉네임은 2글자 이상 5글자 이하로 입력해주세요!&quot;
        /&gt;

        {/* &lt;Form
          htmlFor=&quot;password&quot;
          id=&quot;password&quot;
          name=&quot;password&quot;
          text=&quot;Password&quot;
          sucessMsg=&quot;안전한 비밀번호 입니다.&quot;
          errorMsg=&quot;숫자+영문자+특수문자 조합으로 8자리 이상 입력해주세요!&quot;
          reg=&quot;1&quot;
        /&gt;
        &lt;Form
          htmlFor=&quot;passwordConfirm&quot;
          id=&quot;passwordConfirm&quot;
          name=&quot;passwordConfirm&quot;
          text=&quot;passwordConfirm&quot;
          sucessMsg=&quot;떼잉~ 비밀번호가 똑같지 않아요!&quot;
          errorMsg=&quot;똑같은 비밀번호를 입력했습니다.&quot;
        /&gt; */}
        &lt;div className=&quot;form-el&quot;&gt;
          &lt;label htmlFor=&quot;password&quot;&gt;Password&lt;/label&gt; &lt;br /&gt;
          &lt;input
            id=&quot;password&quot;
            name=&quot;password&quot;
            value={password}
            onChange={onChangePassword}
          /&gt;
          &lt;p className=&quot;message&quot;&gt;{passwordMessage}&lt;/p&gt;
        &lt;/div&gt;
        &lt;div className=&quot;form-el&quot;&gt;
          &lt;label htmlFor=&quot;passwordConfirm&quot;&gt;Password Confirm&lt;/label&gt; &lt;br /&gt;
          &lt;input
            id=&quot;passwordConfirm&quot;
            name=&quot;passwordConfirm&quot;
            value={passwordConfirm}
            onChange={onChangePasswordConfirm}
          /&gt;
          &lt;p className=&quot;message&quot;&gt;{passwordConfirmMessage}&lt;/p&gt;
        &lt;/div&gt;
        &lt;Form
          htmlFor=&quot;email&quot;
          id=&quot;email&quot;
          name=&quot;email&quot;
          text=&quot;Email&quot;
          sucessMsg=&quot;사용 가능한 이메일 입니다.&quot;
          errorMsg=&quot;이메일의 형식이 올바르지 않습니다!&quot;
          reg=&quot;2&quot;
        /&gt;

        &lt;div className=&quot;form-el&quot;&gt;
          &lt;label htmlFor=&quot;phone&quot;&gt;Phone&lt;/label&gt; &lt;br /&gt;
          &lt;input id=&quot;phone&quot; name=&quot;phone&quot; value={phone} onChange={addHyphen} /&gt;
          &lt;p className=&quot;message&quot;&gt;{phoneMessage}&lt;/p&gt;
        &lt;/div&gt;

        &lt;div className=&quot;form-el&quot;&gt;
          &lt;label htmlFor=&quot;birth&quot;&gt;Birth&lt;/label&gt; &lt;br /&gt;
          &lt;input
            type=&quot;date&quot;
            id=&quot;birth&quot;
            name=&quot;birth&quot;
            value={birth}
            onChange={onChangeBirth}
          /&gt;
        &lt;/div&gt;
        &lt;br /&gt;
        &lt;br /&gt;

        &lt;button
          type=&quot;submit&quot;
          disabled={
            isPassword == true &amp;&amp;
            isPasswordConfirm == true &amp;&amp;
            isPhone == true &amp;&amp;
            isBirth == true
              ? false
              : true
          }
        &gt;
          Submit
        &lt;/button&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
};

export default Signup;
</code></pre>
<h3 id="3-세번째--전역-상태관리-contextapi">3) 세번째 : 전역 상태관리 contextAPI</h3>
<p>상태를 공유하기 위해 가장 위에 contextAPI 를 써서 전역적으로 상태관리를 해 주기로 했다. 하지만 여기서도 비효율이 나타난다. 전역적으로 상태관리를 하다보니 값이 하나 바뀌게 되면 굳이 리랜더링이 되지 않아도 될 것들 까지 랜더링이 되게 된다. 이렇게 되면 성능상 낭비가 발생한다. </p>
<h2 id="그래서-결론">그래서 결론?</h2>
<p>그래서 결국 세가지 고민과 함께 useRef 또는 useState 모두를 쓰려고 검색을 해봤지만</p>
<ol>
<li>상태를 공유할 수 있어야 한다. </li>
<li>값 변경시 변경된 부분만 리랜더링이 되어야 한다. </li>
</ol>
<p>이 둘을 충족 시킬 수 있는 답은 찾지 못했다. 약 2주동안 같은 고민으로 헤매었고, 결국 라이브러리의 도움을 받기로 한다. 그런데 정말 라이브러리 말고는 답을 찾지 못하는 걸까? 아직 나의 실력으로는 여기까지가 최선이었다는 결론이다. </p>
<p>그래서 스터디원의 도움을 받아 react-hook-form 라이브러리를 써보기로 한다. </p>
<h2 id="에필로그">에필로그</h2>
<p>최적화된 프랙티스는 무엇일까? 약 2-3주 정도의 고민이 있었고 이것저것 시도해도 막히고 고민하는 부분은 같은 지점에서 반복되었다. 강의도 찾아서 들어보고 블로그를 봐도 해결하지 못한 부분이 있었다. 아마 여기서 아는 만큼 보인다를 증명하는 것과 같이 알아도 발굴해 내지 못하는 실력의 한계가 있지 않을까 생각한다. 다른 사람들은 어떻게 문제를 해결할까? </p>
<p>그래서 다음에는 팀원의 도움을 받아 그의 플로우를 온전하게 옆에서 지켜보기로 했다. </p>
]]></description>
        </item>
    </channel>
</rss>