<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>soo-ni.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Sun, 20 Jul 2025 07:42:16 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>soo-ni.log</title>
            <url>https://velog.velcdn.com/images/soo-ni/profile/0279409e-75fc-4307-9a1d-d3ef595bed72/social_profile.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. soo-ni.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/soo-ni" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[NgZone 사례]]></title>
            <link>https://velog.io/@soo-ni/NgZone-%EC%82%AC%EB%A1%80</link>
            <guid>https://velog.io/@soo-ni/NgZone-%EC%82%AC%EB%A1%80</guid>
            <pubDate>Sun, 20 Jul 2025 07:42:16 GMT</pubDate>
            <description><![CDATA[<h2 id="1-ngzonerun">#1. NgZone.run()</h2>
<p>Angular 는 Zone.js 로 모든 비동기 작업을 감싸고, 비동기 완료 시 자동으로 Change Detection 을 트리거한다.</p>
<p>하지만 외부 라이브러리나 DOM 이벤트 콜백은 Angular Zone 외부에서 실행되기 때문에, 뷰 업데이트가 자동으로 일어나지 않는다.</p>
<p>이 때, <code>ngZone.run()</code> 을 호출해 Angular Zone 안으로 복귀시키면 변경된 데이터를 UI에 반영할 수 있다.</p>
<h3 id="3rd-party-이벤트-예제">3rd-party 이벤트 예제</h3>
<pre><code class="language-ts">someJsLib.onEvent(payload =&gt; {
  // 이 콜백은 Angular Zone 밖
  this.ngZone.run(() =&gt; {
    this.data = payload;
    // run() 내부라서 CD 자동 실행
  });
});</code></pre>
<ol>
<li>외부 콜백 실행</li>
<li><code>ngZone.run()</code> 진입</li>
<li><code>this.data</code> 변경</li>
<li>Change Detection </li>
<li>뷰 업데이트</li>
</ol>
<h3 id="그-외-사용할-수-있는-곳">그 외 사용할 수 있는 곳</h3>
<ul>
<li><p><strong>WebSocket 메시지 수신</strong>: 서버 푸시 데이터를 UI에 반영</p>
</li>
<li><p><strong>3rd‑party 콜백</strong>: 차트 라이브러리, 맵 API 이벤트</p>
</li>
<li><p><strong>FileReader</strong>, <strong>IndexedDB</strong> 콜백 등</p>
<ul>
<li>단, <strong><code>run()</code>을 남발하면</strong> 매번 Change Detection이 일어나 성능 저하로 이어질 수 있으니, 
이벤트 빈도가 높지 않은 곳에만 사용하거나, 꼭 필요한 부분만 감싸야한다.</li>
</ul>
</li>
</ul>
<h2 id="2-runoutsideangular">#2. runOutsideAngular()</h2>
<p>Angular Zone 안에서는 모든 이벤트가 CD를 트리거하기 때문에, </p>
<p>특히 <strong>스크롤</strong>, <strong>mousemove</strong>, <strong>resize</strong> 같은 <strong>반복 빈도 높은 이벤트</strong>를 처리하면 성능에 큰 부담이 된다. </p>
<p>이럴 때 <code>runOutsideAngular()</code> 를 사용해 Zone을 벗어나 이벤트를 등록하면, 불필요한 CD를 피할 수 있다.</p>
<h3 id="scroll-최적화-예제">scroll 최적화 예제</h3>
<pre><code class="language-ts">this.ngZone.runOutsideAngular(() =&gt; {
  fromEvent(window,&#39;scroll&#39;).pipe(throttleTime(100))
    .subscribe(() =&gt; {
      const y = window.scrollY;
      if (y &gt; 200) {
        this.ngZone.run(() =&gt; this.scrollY = y);
      }
    });
});</code></pre>
<ol>
<li><code>runOutsideAngular()</code> 로 모든 스크롤 이벤트가 Zone 밖에서 실행 -&gt; CD 미발생</li>
<li><code>throttleTime(100)</code>으로 이벤트 빈도 제어</li>
<li>조건 통과 시 <code>(y &gt; 200)</code> 에만 <code>run()</code> 호출해 UI 업데이트</li>
</ol>
<h3 id="mousemove-최적화-예제">mousemove 최적화 예제</h3>
<pre><code class="language-ts">this.ngZone.runOutsideAngular(() =&gt; {
  fromEvent(window, &#39;mousemove&#39;)
    .pipe(throttleTime(200))
    .subscribe((e: MouseEvent) =&gt; {
      const coords = { x: e.clientX, y: e.clientY };
      // 마우스가 화면 중앙 근처일 때만 UI 업데이트
      if (coords.x &gt; window.innerWidth/2) {
        this.ngZone.run(() =&gt; {
          this.cursorPos = coords;
        });
      }
    });
});</code></pre>
<ol>
<li><strong>Zone 밖</strong>에서 스트림 생성</li>
<li><strong>RxJS 연산자</strong>로 빈도 제어 (<code>throttleTime</code>/<code>debounceTime</code>)</li>
<li><strong>조건</strong>(혹은 결과) 만족 시에만</li>
<li><strong>Zone 안</strong>으로 복귀해 UI 업데이트 (<code>ngZone.run()</code>)</li>
</ol>
<h3 id="왜-이렇게-처리를-할까🤔">왜 이렇게 처리를 할까🤔?</h3>
<ul>
<li><p>매 프레임마다(60fps) 발생하는 이벤트를 Zone 안에서 처리하면 매번 CD → 성능 저하</p>
</li>
<li><p>Zone 밖에서 <strong>빈도 제어</strong> 후, 정말 필요한 순간에만 Zone 안으로 복귀</p>
</li>
</ul>
<p>=&gt;효과:  <strong>Raw scroll</strong> 처리: 30fps 미만으로 뚝뚝 끊김, <strong>throttleTime+runOutside/run</strong> 적용: 55–60fps 유지</p>
<h4 id="참고-throttletime-vs-debouncetime">(참고) throttleTime vs. debounceTime</h4>
<p><code>throttleTime</code>, <code>debounceTime</code> 선택의 기준</p>
<ul>
<li><code>throttleTime(ms)</code>: 최초 이벤트 방출 후, 지정한 ms 동안은 이후 이벤트 무시 스크롤 / scroll, mousemove 같은 빈번한 이벤트 제어</li>
<li><code>debounceTime(ms)</code>: 마지막 이벤트가 발생하고 지정한 ms 동안 추가 이벤트 없을 때 최종 이벤트만 방출 / 검색어 입력 후 최종 키 입력 시점 처리 (검색 API 호출)</li>
</ul>
<pre><code class="language-ts">// 1) throttleTime: 스크롤 위치 감지
this.ngZone.runOutsideAngular(() =&gt; {
  fromEvent(window, &#39;scroll&#39;)
    .pipe(throttleTime(100))
    .subscribe(() =&gt; {
      // 초당 최대 10번(1000/100)만 동작
      this.ngZone.run(() =&gt; this.scrollY = window.scrollY);
    });
});

// 2) debounceTime: 검색어 입력
fromEvent(searchInput.nativeElement, &#39;keyup&#39;)
  .pipe(
    map((e: any) =&gt; e.target.value),
    debounceTime(300)      // 마지막 입력 후 300ms 동안 입력 없을 때만
  )
  .subscribe(query =&gt; {
    this.searchService.search(query).subscribe(results =&gt; this.items = results);
  });
</code></pre>
<p>=&gt; <strong>throttleTime</strong>은 “지속적으로 많은 이벤트 중 <strong>간헐적으로</strong> 한 번만” 처리,</p>
<p><strong>debounceTime</strong>은 “마지막 이벤트가 <strong>완전히 멈춘 뒤</strong>에 한 번만” 처리</p>
<h2 id="3-onmicrotaskempty-vs-onstable">#3. onMicrotaskEmpty vs. onStable</h2>
<h3 id="zone-이벤트-흐름">Zone 이벤트 흐름</h3>
<p>Angular(Zone.js)는 <strong>microtask</strong>와  <strong>macrotask</strong> 두 가지 큐를 관리한다.</p>
<ul>
<li><strong>microtask</strong>: Promise 콜백, <code>queueMicrotask</code> 등 → 우선 순위가 높아 macrotask 직후에 실행</li>
<li><strong>macrotask</strong>: <code>setTimeout</code>, <code>setInterval</code>, DOM 이벤트, XHR 등</li>
</ul>
<h4 id="zone-이벤트-실행-순서">Zone 이벤트 실행 순서</h4>
<ol>
<li><strong>하나의 macrotask 실행</strong><ul>
<li>예: <code>setTimeout</code> 콜백, I/O 이벤트, DOM 이벤트, <code>setInterval</code> 등</li>
</ul>
</li>
<li><strong>해당 macrotask 내에서 스케줄된 모든 microtask 처리</strong><ul>
<li>예: <code>Promise.then</code>, <code>queueMicrotask</code>, <code>MutationObserver</code> 콜백 등</li>
</ul>
</li>
<li><strong>렌더링(Optional)</strong><ul>
<li>브라우저가 필요하다면 화면을 갱신</li>
</ul>
</li>
<li><strong>다음 macrotask 로 이동</strong></li>
</ol>
<p>=&gt; <strong>“macrotask → microtask → (렌더링) → 다음 macrotask”</strong> 순서로 동작</p>
<h3 id="onmicrotaskempty">onMicrotaskEmpty</h3>
<p><code>onMicrotaskEmpty</code> 는 모든 macrotask 가 끝난 직후, 그리고 그 사이에 발생한 모든 microtask 도 완료된 시점에 발생한다.</p>
<p>Angular 내부 로직 중 “Promise 기반 초기화”가 끝난 바로 다음 순간에 특정 코드를 실행하고 싶을 때, 
예: 서비스 초기화(<code>init().then()</code>), 라우터 가드 <code>canActivate()</code> Promise 처리 직후 같은 때 사용 가능하다.</p>
<pre><code class="language-ts">this.ngZone.onMicrotaskEmpty.pipe(take(1)).subscribe(() =&gt; {
  console.log(&#39;모든 Promise 콜백 처리 완료 직후&#39;);
});</code></pre>
<blockquote>
<p>pipe(take(1)): 스트림에서 <strong>첫 번째(next) 알림</strong>을 받은 즉시 그 값을 구독자에게 전달하고 <strong>메모리 누수 없이</strong> 자동으로 구독이 해제 (unsubscribe)</p>
</blockquote>
<h3 id="onstable">onStable</h3>
<p><code>onStable</code> 은 모든 macrotask + microtask 가 완전히 끝나고, Zone 이 &quot;완전히 유휴(idle)&quot; 상태가 된 뒤에 발생한다.</p>
<p>앱 초기 렌더링 + 비동기 데이터 바인딩이 모두 끝난 뒤에 실행할 로직,
ex. Analytics 초기화: 모든 컴포넌트가 로딩된 뒤 스크립트 로드 / Layout 측정: ViewChild,ElementRef 로 DOM 크기를 정확히 측정 할 때 사용가능하다.</p>
<pre><code class="language-ts">this.ngZone.onStable.pipe(take(1)).subscribe(() =&gt; {
  console.log(&#39;모든 비동기 작업(타이머, HTTP, 이벤트) 종료 후&#39;);
});</code></pre>
<h3 id="analytics--layout-측정-예제">Analytics &amp; layout 측정 예제</h3>
<pre><code class="language-ts">import { Component, NgZone, AfterViewInit, ViewChild, ElementRef } from &#39;@angular/core&#39;;
import { take } from &#39;rxjs/operators&#39;;

// (가상의) Analytics, Chart 라이브러리 인터페이스
declare const Analytics: { init: () =&gt; void };
declare const ChartLib: { create: (el: HTMLElement, w: number, h: number) =&gt; void };

@Component({
  selector: &#39;app-analytics-layout-demo&#39;,
  template: `
    &lt;div #chartContainer class=&quot;chart-container&quot;&gt;
      &lt;!-- 차트가 여기 렌더링됩니다 --&gt;
    &lt;/div&gt;
  `
})
export class AnalyticsLayoutDemoComponent implements AfterViewInit {
  @ViewChild(&#39;chartContainer&#39;, { static: true })
  chartContainer!: ElementRef&lt;HTMLElement&gt;;

  constructor(private ngZone: NgZone) {}

  ngAfterViewInit() {
    // 1) Change Detection + Promise 등 microtask 직후 → Analytics 초기화
    this.ngZone.onMicrotaskEmpty
      .pipe(take(1))
      .subscribe(() =&gt; {
        console.log(&#39;onMicrotaskEmpty: Analytics.init() 호출&#39;);
        Analytics.init();
      });

    // 2) setTimeout, HTTP 등 macrotask까지 모두 완료된 뒤 → Layout 측정 및 차트 생성
    this.ngZone.onStable
      .pipe(take(1))
      .subscribe(() =&gt; {
        const el = this.chartContainer.nativeElement;
        const { width, height } = el.getBoundingClientRect();
        console.log(`onStable: 차트 크기 ${width}x${height}로 초기화`);
        ChartLib.create(el, width, height);
      });

    // (옵션) 강제로 macrotask 하나 삽입해 보기
    setTimeout(() =&gt; {
      console.log(&#39;0ms 타이머 완료 (macrotask 예시)&#39;);
    }, 0);
  }
}</code></pre>
<ol>
<li><strong><code>ngAfterViewInit</code></strong> 진입</li>
<li>Angular가 뷰 초기화 → Change Detection 수행</li>
<li><strong>Promise 콜백, <code>queueMicrotask</code> 등 마이크로태스크</strong>가 처리된 후 → <strong><code>onMicrotaskEmpty</code></strong> 이벤트 발생 → <code>Analytics.init()</code></li>
<li><strong><code>setTimeout</code>, HTTP 요청, 기타 매크로태스크</strong>가 모두 끝난 후 → <strong><code>onStable</code></strong> 이벤트 발생 → <code>getBoundingClientRect()</code>로 정확한 컨테이너 크기 측정 후 차트 렌더링</li>
</ol>
<h4 id="실행-순서">실행 순서</h4>
<pre><code>onMicrotaskEmpty: Analytics.init() 호출
0ms 타이머 완료 (macrotask 예시)
onStable: 차트 크기 800x400로 초기화</code></pre><p><strong>onMicrotaskEmpty: Analytics.init() 호출</strong>
 – 뷰 초기화 + Change Detection 후, 마이크로태스크 큐가 비워지는 시점에 바로 발생한다.</p>
<p><strong>0ms 타이머 완료 (macrotask 예시)</strong>
 – <code>setTimeout(..., 0)</code> 매크로태스크가 실행된 직후 콘솔에 찍힌다.</p>
<p><strong>onStable: 차트 크기 {width}x{height}로 초기화</strong>
 – 모든 macrotask(여기서는 타이머)와 남은 microtask가 끝나고 Zone이 완전 유휴가 된 뒤에 발생하며, <code>getBoundingClientRect()</code>로 읽어온 실제 컨테이너 크기가 찍힌다.</p>
<h2 id="4-settimeout-의-zone-내부-vs-외부-차이">#4. setTimeout 의 Zone 내부 vs. 외부 차이</h2>
<p>Zone.js는 브라우저의 <strong>macrotask API</strong> (<code>setTimeout</code>, <code>setInterval</code>, DOM 이벤트, XHR 등)를 후킹(patch) 한다.</p>
<p>각 타이머 호출 시 “현재 Zone” 정보를 함께 저장 → 콜백 실행 시 자동으로 그 Zone으로 복귀한다.</p>
<h3 id="angular-zone-내부-run">Angular Zone 내부 (run())</h3>
<pre><code class="language-ts">// Angular Zone 내부
setTimeout(() =&gt; {
  this.msg1 = &#39;Zone 내부 setTimeout 완료&#39;;
  // 이 시점에 Change Detection(CD) 자동으로 실행
}, 500);</code></pre>
<ul>
<li><p>동작: </p>
<ol>
<li>타이머 예약 시 Zone 컨텍스트 기록</li>
<li>500ms 후 콜백 실행 → Zone.js가 자동으로 <code>ngZone.run()</code> 효과를 주어 CD 트리거</li>
<li>뷰가 즉시 갱신</li>
</ol>
</li>
<li><p>장점: 코드가 간결</p>
</li>
<li><p>주의: 폴링(polling) 등 반복 타이머가 많으면 CD 부담 증가</p>
</li>
</ul>
<h3 id="angular-zone-외부-runoutsideangular-타이머">Angular Zone 외부 (runOutsideAngular()) 타이머</h3>
<pre><code class="language-ts">this.ngZone.runOutsideAngular(() =&gt; {
  setTimeout(() =&gt; {
    this.msg2 = &#39;Zone 외부 setTimeout 완료&#39;;
    // 아직 CD가 일어나지 않았다!
    // 강제 복귀 필요:
    this.ngZone.run(() =&gt; {
      this.msg2 += &#39; → run() 복귀 후 반영&#39;;
    });
  }, 1000);
});</code></pre>
<ul>
<li>동작:<ol>
<li><code>runOutsideAngular()</code>로 Zone 바깥에서 타이머 예약 → 콜백도 Zone 바깥에서 실행</li>
<li>첫 번째 <code>this.msg2</code> 변경 시 CD 미발생 → 뷰 미반영</li>
<li><code>ngZone.run()</code> 호출 → CD 트리거 → 뷰에 최종 문자열 반영</li>
</ol>
</li>
<li>장점: 불필요한 CD를 피할 수 있어, 반복 타이머나 폴링 시 성능 유리</li>
<li>주의: UI 반영 시점을 명확하게 관리 필요</li>
</ul>
<h3 id="실시간-알림-폴링-예제">실시간 알림 폴링 예제</h3>
<pre><code class="language-ts">ngOnInit() {
  this.ngZone.runOutsideAngular(() =&gt; {
    this.pollSub = timer(0, 5000).subscribe(() =&gt; {
      this.notificationService.getNewCount()
        .subscribe(count =&gt; {
          if (count &gt; this.lastCount) {
            // 변화가 있을 때만 Zone 복귀
            this.ngZone.run(() =&gt; {
              this.newCount = count;
            });
          }
          this.lastCount = count;
        });
    });
  });
}

ngOnDestroy() {
  this.pollSub.unsubscribe();
}</code></pre>
<ul>
<li>폴링 성능<ul>
<li>매 5초마다 HTTP 요청 → 콜백 내부에서만 UI 반영</li>
<li>불필요한 CD(false positive) 제거 → 전체 앱 반응성 향상</li>
</ul>
</li>
</ul>
<h4 id="요약--tip">요약 &amp; tip</h4>
<ul>
<li><strong>runOutsideAngular()</strong> + <strong>조건부 run()</strong> 조합으로,<ul>
<li><strong>불필요한 CD 제거</strong></li>
<li><strong>필요할 때만 UI 업데이트</strong></li>
</ul>
</li>
<li><strong>사용처</strong>: 폴링, 애니메이션, 마우스/키보드 이벤트 처리 등</li>
<li><strong>리소스 해제</strong>: 구독/타이머 해제 반드시 (<code>unsubscribe()</code>, <code>clearInterval</code>)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Redis-cluster 구성 (feat. Spring)]]></title>
            <link>https://velog.io/@soo-ni/redis-cluster-%EA%B5%AC%EC%84%B1</link>
            <guid>https://velog.io/@soo-ni/redis-cluster-%EA%B5%AC%EC%84%B1</guid>
            <pubDate>Mon, 22 Apr 2024 02:17:12 GMT</pubDate>
            <description><![CDATA[<h2 id="1-redis">#1. Redis</h2>
<h3 id="redis-설치-windows">Redis 설치 (Windows)</h3>
<p>Windows에서 redis 다운로드 받기 <a href="https://github.com/MicrosoftArchive/redis/releases">MS 공식 github</a>에서 2016년 버전이 끝이다.
최대한 최근 버전이 필요해서 서치하던 중 <a href="https://github.com/tporadowski/redis/releases/tag/v5.0.14.1">링크</a>  에서 새로운 윈도우 버전을 다운로드 받을 수 있다.</p>
<p>msi 로 설치 &gt; <code>C:Program Files/Redis</code> 폴더에서 Redis가 설치된 것을 확인할 수 있다.</p>
<h3 id="cluster-구성">Cluster 구성</h3>
<p>가장 기본으로 설치되는 포트 6379이 아닌 최소 구성 조건인 master node 3개 <code>127.0.0.1:7000</code>, <code>127.0.0.1:7001</code>, <code>127.0.0.1:7002</code> 로 구성한다.</p>
<h4 id="각-노드-폴더-생성">각 노드 폴더 생성</h4>
<pre><code class="language-shell"># C:Program Files/Redis
$ mkdir 7000
$ mkdir 7001
$ mkdir 7002</code></pre>
<h4 id="conf-file-생성">conf file 생성</h4>
<p><code>C:Program Files/Redis/redis.7000.conf, redis.7001.conf, redis.7002.conf</code> 생성
각 포트에 맞게 아래 conf file 내용 변경 필요</p>
<pre><code class="language-conf">port 7000
daemonize yes
logfile ./7000/log_7000.txt
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 3000
appendonly yes
dir ./7000</code></pre>
<h4 id="redis-실행">redis 실행</h4>
<pre><code class="language-shell">$ redis-server redis.7000.conf
$ redis-server redis.7001.conf
$ redis-server redis.7002.conf</code></pre>
<h4 id="cluster-시작">cluster 시작</h4>
<pre><code class="language-shell">$ redis-cli --cluster create 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 --cluster-replicas 1</code></pre>
<p>cluster info 확인</p>
<pre><code class="language-shell">$ redis-cli -c -p 7000
127.0.0.1:7000&gt; cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:3
cluster_size:3
cluster_current_epoch:3
cluster_my_epoch:1
cluster_stats_messages_ping_sent:3847
cluster_stats_messages_pong_sent:3855
cluster_stats_messages_sent:7702
cluster_stats_messages_ping_received:3853
cluster_stats_messages_pong_received:3847
cluster_stats_messages_meet_received:2
cluster_stats_messages_received:7702</code></pre>
<h4 id="추가-service-로-관리">추가) service 로 관리</h4>
<p>service 등록해서 사용</p>
<p>등록 및 실행</p>
<pre><code class="language-shell">$ cd Redis # redis 위치하는 폴더
$ redis-server --service-install redis.7000.conf --service-name redis7000
$ redis-server --service-install redis.7001.conf --service-name redis7001
$ redis-server --service-install redis.7002.conf --service-name redis7002

$ redis-server --service-start --service-name redis7000
[19584] 29 Apr 11:28:09.104 # Redis service successfully started.
$ redis-server --service-start --service-name redis7001
[13072] 29 Apr 11:28:13.634 # Redis service successfully started.
$ redis-server --service-start --service-name redis7002
[20996] 29 Apr 11:28:17.145 # Redis service successfully started.</code></pre>
<p>종료 및 해지</p>
<pre><code class="language-shell">$ redis-server --service-stop --service-name redis7000
$ redis-server --service-stop --service-name redis7001
$ redis-server --service-stop --service-name redis7002

$ redis-server --service-uninstall --service-name redis7000
$ redis-server --service-uninstall --service-name redis7001
$ redis-server --service-uninstall --service-name redis7002</code></pre>
<h2 id="2-spring">#2. Spring</h2>
<h3 id="redis-설정">Redis 설정</h3>
<p>⭐<strong>주의</strong>⭐ node 주소는 현재 ip 그대로 적으면 오류날 수 도 있다. (<code>127.0.0.1:7000</code> 으로 적어보기)</p>
<h4 id="redisconfigjava">RedisConfig.java</h4>
<pre><code class="language-java">@EnableCaching
@Configuration
@RequiredArgsConstructor
@Slf4j
public class RedisConfig implements CachingConfigurer {
    private final AppProperties appProperties;

    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {
        Set&lt;String&gt; nodes = new HashSet&lt;&gt;();
        for (String node : appProperties.getRedisClusterNodes()) {
            try {
                nodes.add(node.trim());
            } catch (RuntimeException ex) {
                throw new IllegalStateException(&quot;Invalid redis sentinel property nodes {} : {}&quot; + node, ex);
            }
        }

        RedisClusterConfiguration clusterConfiguration = new RedisClusterConfiguration(nodes);
        clusterConfiguration.setMaxRedirects(5);
        LettuceClientConfiguration clientConfiguration = LettuceClientConfiguration.builder()
                .commandTimeout(Duration.ofMinutes(1))
                .readFrom(ReadFrom.REPLICA_PREFERRED)
                .build();

        return new LettuceConnectionFactory(clusterConfiguration,clientConfiguration);
    }

    private ObjectMapper objectMapper() {
        PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator
                .builder()
                .allowIfSubType(Object.class)
                .build();

        return new ObjectMapper()
                .findAndRegisterModules()
                .enable(SerializationFeature.INDENT_OUTPUT)
                .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
                .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false)
                .registerModule(new JavaTimeModule())
                .activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.NON_FINAL);
    }

    @Bean
    public RedisTemplate&lt;String,Object&gt; redisTemplate() {
        final RedisTemplate&lt;String,Object&gt; redisTemplate = new RedisTemplate&lt;&gt;();
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer(objectMapper()));
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(RedisSerializer.java());
        redisTemplate.setConnectionFactory(redisConnectionFactory());
        return redisTemplate;
    }


    @Bean
    public RedisCacheConfiguration cacheConfiguration() {
        return RedisCacheConfiguration.defaultCacheConfig()
                .disableCachingNullValues()
                .computePrefixWith(cacheName -&gt; &quot;prefix::&quot; + cacheName + &quot;::&quot;)
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(
                        new GenericJackson2JsonRedisSerializer(objectMapper())
                ));
    }

    @Override
    @Bean
    public CacheManager cacheManager() {
        return RedisCacheManager.builder(this.redisConnectionFactory())
                .cacheDefaults(this.cacheConfiguration())
                .build();
    }

    @Override
    public CacheErrorHandler errorHandler() {
        return new CacheErrorHandler() {
            @Override
            public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
                log.warn(exception.getMessage(), exception);
            }

            @Override
            public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {
                log.warn(exception.getMessage(), exception);
            }

            @Override
            public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
                log.warn(exception.getMessage(), exception);
            }

            @Override
            public void handleCacheClearError(RuntimeException exception, Cache cache) {
                log.warn(exception.getMessage(), exception);
            }
        };
    }
}
</code></pre>
<h4 id="service-구현">Service 구현</h4>
<pre><code class="language-java">@Service
@Slf4j
@RequiredArgsConstructor
public class TestService {

    @Cacheable(value=&quot;CACHE_NAME&quot;, key=&quot;#testType&quot;)
    public String getTestType(TestType testType) {
        return testMapper.selectTestType(testType); // testTypeResult
    }
}</code></pre>
<h4 id="redis-저장-확인">redis 저장 확인</h4>
<pre><code class="language-shell">$ redis-cli -c -p 7000
127.0.0.1:7000&gt; get prefix::CACHE_NAME::testType # cacheConfiguration perfix 확인
testTypeResult #저장된값</code></pre>
<h3 id="참고">참고</h3>
<ul>
<li><a href="https://analog-green.tistory.com/545">NoSQL:레디스 zip버전 윈도우 로컬서버</a></li>
<li><a href="https://steady-hello.tistory.com/128">[Redis] redis cluster 간단하게 구성해보기</a></li>
<li><a href="https://velog.io/@yeonjoo913/Redis-Spring-3.x-Redis-%EC%A0%81%EC%9A%A9%EA%B8%B0AOP-ubfq0m3g">[Redis] Spring 3.x Redis 적용기(AOP)</a></li>
<li><a href="http://redisgate.jp/redis/introduction/win_start.php">레디스 서버 시작하고 사용하기</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[v-html 사용]]></title>
            <link>https://velog.io/@soo-ni/v-html-%EC%82%AC%EC%9A%A9</link>
            <guid>https://velog.io/@soo-ni/v-html-%EC%82%AC%EC%9A%A9</guid>
            <pubDate>Thu, 18 Jan 2024 07:09:53 GMT</pubDate>
            <description><![CDATA[<h2 id="xss">XSS</h2>
<p><a href="https://developer.mozilla.org/ko/docs/Glossary/Cross-site_scripting">크로스 사이트 스크립팅(XSS)</a>은 가장 흔한 <a href="https://owasp.org/www-project-top-ten/2017/A7_2017-Cross-Site_Scripting_(XSS).html">웹 앱 취약점</a> 중 하나로 공격자가 웹사이트에 악성 클라이언트 사이드 코드를 삽입할 수 있도록 하는 보안 취약점 공격이다.</p>
<h2 id="v-html">v-html</h2>
<p>HTML 코드를 사이트에 렌더링 하기 위해서는 어떻게 해야할까? Vue.js 의 경우, <code>v-text</code>나 <code>{{}}</code>는 있는 그대로 문자열을 렌더링하기 때문에 HTML 코드 형태로 렌더링하기 위해서는 <code>v-html</code>을 사용해야 한다.</p>
<p>그러나 <code>v-html</code>은 <code>innerHTML</code> 기반이기 때문에 <strong>XSS 위협</strong>이 있다. </p>
<p><a href="https://v2.ko.vuejs.org/v2/guide/syntax.html">vue.js 공식 문서</a>에도 v-html을 사용하기 위해서는 신뢰할 수 있는 콘텐츠에서만 사용하라는 안내문구가 있으며,
<img src="https://velog.velcdn.com/images/soo-ni/post/6a876904-289f-4a17-b0a4-36a6f5350335/image.png" alt=""></p>
<p>eslint를 적용한 IntelliJ에서도 경고를 보여주는걸 확인할 수 있다.
<img src="https://velog.velcdn.com/images/soo-ni/post/78b40d93-cdd1-4ade-9f3f-2bd70da61d89/image.png" alt=""></p>
<p>물론 innerHTML 기반이기 때문에 <code>&lt;script&gt;&lt;/script&gt;</code> 와 같은 누구나 알 수 있는 코드는 삭제되지만 이외의 <code>&lt;img srcset=&quot;,,,,,x&quot; onerror=&quot;alert(1)&quot;&gt;</code> 와 같은 코드는 바로 실행되는것을 알 수 있다.</p>
<h2 id="해결">해결</h2>
<p>html을 보여주지 않을 수 없으니 그렇다면 어떻게 적용해야할까?</p>
<p>해결 방법을 찾아보니 <a href="https://www.npmjs.com/package/vue-dompurify-html">v-dompurify-html</a>, <a href="https://www.npmjs.com/package/sanitize-html">santize-html</a> 와 같은 라이브러리로 쉽게 해결할 수 있었다. 그 중 2024년 1월 기준 가장 많이 사용하고 있는 dompurify 를 적용한 v-dompurify를 사용해서 v-html 필터링을 적용했다.
<img src="https://velog.velcdn.com/images/soo-ni/post/7ded5253-770f-4839-93dc-08326dbced20/image.png" alt=""></p>
<h3 id="적용-방법">적용 방법</h3>
<ol>
<li>설치<pre><code class="language-shell">// vue 2 이하
npm install vue-dompurify-html@vue-legacy
</code></pre>
</li>
</ol>
<p>// vue 3 이상
npm install vue-dompurify-html</p>
<pre><code>
2. 적용
```ts
// main.ts
import Vue from &#39;vue&#39;;
import App from &#39;./App.vue&#39;
import VueDOMPurifyHTML from &#39;vue-dompurify-html&#39;;

Vue.use(VueDOMPurifyHTML);

new Vue({
  render: h =&gt; h(App),
}).$mount(&#39;#app&#39;)</code></pre><ol start="3">
<li>테스트
<a href="https://html5sec.org/">HTML5 Security Cheatsheet</a>에 있는 테스트가 다 통과하는지 확인한다.</li>
</ol>
<h3 id="참고">참고</h3>
<ul>
<li><a href="https://velog.io/@skyepodium/vue-v-html%EC%9D%80-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B3%A0-%EC%8B%B6%EC%A7%80%EB%A7%8C-%ED%81%AC%EB%A1%9C%EC%8A%A4-%EC%82%AC%EC%9D%B4%ED%8A%B8-%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8C%85%EC%9D%80-%ED%94%BC%ED%95%98%EA%B3%A0-%EC%8B%B6%EC%96%B4">[vue] v-html은 사용하고 싶지만, 크로스 사이트 스크립팅은 피하고 싶어</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[테스트 도입] 1. FE Test]]></title>
            <link>https://velog.io/@soo-ni/FE-Test</link>
            <guid>https://velog.io/@soo-ni/FE-Test</guid>
            <pubDate>Thu, 09 Nov 2023 23:29:30 GMT</pubDate>
            <description><![CDATA[<h2 id="프론트엔드에서-테스트를-해야하는-이유">프론트엔드에서 테스트를 해야하는 이유</h2>
<p>예전에는 프론트엔드가 복잡하지 않아 테스트를 잘 진행하지 않았다. 
그러나 프론트엔드의 <strong>규모가 커지고</strong>, <strong>백엔드의 다양한 기능들이 프론트엔드로 옮겨오면서</strong> 관리할 필요성이 생기기 시작했다.
프론트엔드 관리와, 코드 퀄리티가 중요해지면서 프론트엔드 테스트도 필요하게 되었다.</p>
<p>이러한 테스트는 <strong>코드가 의도한대로 동작하는 것을 보장</strong>해준다. 
즉, 개발자가 스스로 테스트하는 것이 아닌 자동화시켜 놓치는 부분 없이 의도한 기능대로 동작한다는 것을 보장한다.</p>
<p>유지보수 시 코드를 수정하다보면 <strong>사이드이팩트</strong>를 알 수 없다. <strong>때문에 개발자들은 점점 기존에 동작하고 있는 코드를 수정하기 꺼려하고, 기존의 코드를 리팩토링해서 사용하기 보다는 중복된 기능을 가진 코드를 생성하는 것을 선호한다.</strong>
프로젝트에는 중복되는 코드와 나쁜 코드가 점점 쌓이고 결국 프로젝트는 손쓸 수 없는 상태가 된다.</p>
<p>프로젝트가 더 복잡해지기 전에 <strong>테스트</strong>를 도입하자!</p>
<h2 id="테스트-종류">테스트 종류</h2>
<p>프론트엔드의 테스트 종류로는 유닛테스트, 통합테스트, E2E 테스트가 있다.</p>
<p>간단하게 정리하자면 아래와 같다.</p>
<ul>
<li><strong>Unit test (단위테스트)</strong>: 함수 하나, 컴포넌트 하나와 같이 코드의 작은 부분을 테스트</li>
<li><strong>Integration test (통합테스트)</strong>: 서로 다른 시스템들의 상호작용이 잘 이뤄지는지 테스트</li>
<li><strong>E2E test (종단 간 테스트)</strong>: 사용자와 어플리케이션의 상호작용이 잘 이뤄지는지 테스트</li>
</ul>
<p>예를 들자면 한 게임에서 유저가 몬스터를 잡는다는 상황을 가정한다.</p>
<blockquote>
<ul>
<li>유저가 몬스터에게 달려가는 Move 함수</li>
</ul>
</blockquote>
<ul>
<li>유저가 몬스터를 때리는 Attack 함수</li>
<li>몬스터를 잡고 난 후 전리품을 수집하는 Gather 함수</li>
</ul>
<p><code>유닛테스트</code> 는 각 함수에 이런저런 입력 값을 줘봐서 잘 되는지 테스트하는 것이고,
<code>통합테스트</code> 는 유저가 몬스터를 잡고 전리품을 수집할 때 실제로 DB에 잘 저장됬는지 테스트하는 것,
<code>e2e 테스트</code> 는 실제 유저가 되어 이 모든 일련의 과정이 정상적으로 돌아가는지 테스트해 보는 것이다.</p>
<h3 id="그래서-어떤테스트를-해야하는가-🤔">그래서 어떤테스트를 해야하는가? 🤔</h3>
<p>이 테스트를 모두 수행하는지에 대한 대답은 <code>그렇다.</code> 이다.
테스트를 관리하는 데 사용되는 수많은 이론 중 하나인 테스트 피라미드 그림을 참고해보면 단계별 테스트의 비중과 비용을 알 수 있다.  (실제로는 모래시계형처럼 통합테스트가 제일 적은 경우도 발생한다.)</p>
<p><img src="https://velog.velcdn.com/images/soo-ni/post/5c8a6fea-c76a-4837-9afc-a63fa1490b8f/image.png" alt=""></p>
<h3 id="테스트-도입">테스트 도입</h3>
<p>회사에서 프로젝트 성격에 맞춰 테스트 도입해보기로 결정했고 FE 프레임워크가 적용되지 않은 레거시 프로젝트와 프레임워크가 적용된 최근 프로젝트에 테스트 도입에 대해 포스트해보려고 한다.</p>
<h3 id="참고">참고</h3>
<ul>
<li><a href="https://velog.io/@couchcoding/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%96%B4%EB%96%BB%EA%B2%8C-%ED%95%B4%EC%95%BC%ED%95%A0%EA%B9%8Cwith-react-testing-library">프론트엔드 테스트 해야할까? - (1)</a></li>
<li><a href="https://velog.io/@ongsim123/TIL-Unit-test-Integration-test-e2e-test-%EA%B7%B8%EB%A6%AC%EA%B3%A0-TDD">[TIL] Unit test, Integration test, e2e test 그리고 TDD</a></li>
<li><a href="https://fe-developers.kakaoent.com/2023/230209-e2e/">E2E 테스트 도입 경험기</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[테스트 도입 - 레거시 프로젝트] 2-3. FE E2E TestFE Cypress Report]]></title>
            <link>https://velog.io/@soo-ni/FE-Cypress-Report</link>
            <guid>https://velog.io/@soo-ni/FE-Cypress-Report</guid>
            <pubDate>Fri, 13 Oct 2023 07:52:37 GMT</pubDate>
            <description><![CDATA[<p>Cypress 결과를 한번에 보기</p>
<h2 id="reporter-설치">Reporter 설치</h2>
<h3 id="library-설치">library 설치</h3>
<pre><code class="language-shell">npm install -D mochawesome
npm install -D cypress-mochawesome-reporter</code></pre>
<h3 id="cypressconfigjs">cypress.config.js</h3>
<p>report 관련 설정</p>
<pre><code class="language-js">const { defineConfig } = require(&#39;cypress&#39;);

module.exports = defineConfig({
  reporter: &#39;cypress-mochawesome-reporter&#39;,
  video: false,
  reporterOptions: {
    charts: true,
    reportPageTitle: &#39;Cypress Inline Reporter&#39;,
    embeddedScreenshots: true,
    inlineAssets: true, //Adds the asserts inline
  },
  e2e: {
    experimentalStudio: true,
    setupNodeEvents(on, config) {
      require(&#39;cypress-mochawesome-reporter/plugin&#39;)(on);
    },
  },
});</code></pre>
<h3 id="실행">실행</h3>
<pre><code class="language-shell">npx cypress run --e2e</code></pre>
<h2 id="report-결과">Report 결과</h2>
<h3 id="terminal">terminal</h3>
<p><img src="https://velog.velcdn.com/images/soo-ni/post/45f6ddf6-e2c1-4885-97fb-ff5a0a9fae0e/image.png" alt=""></p>
<h3 id="reports-폴더">reports 폴더</h3>
<p>video 가 true인 경우에는 video 생성 (false로 하는 경우 기존 video가 사라지니 주의)</p>
<p><img src="https://velog.velcdn.com/images/soo-ni/post/96463c21-5abe-4416-81b4-5ece07af1b0f/image.png" alt=""></p>
<p>index.html 내부 상세하게 기재</p>
<p><img src="https://velog.velcdn.com/images/soo-ni/post/237b921a-66a3-4df7-93b7-056cbbe08b9d/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[테스트 도입 - 레거시 프로젝트] 2-1. FE Unit Test]]></title>
            <link>https://velog.io/@soo-ni/FE-Unit-Test</link>
            <guid>https://velog.io/@soo-ni/FE-Unit-Test</guid>
            <pubDate>Fri, 13 Oct 2023 01:00:17 GMT</pubDate>
            <description><![CDATA[<p>회사 프로젝트에서는 기존 레거시 프로젝트에 webpack, babel을 적용한 환경으로 vue나 react같은 프레임워크를 사용하지 않고 jsp를 사용하고 있다. 
unit test와 integration test, e2e test 모두를 도입하려고 했지만 코드상 <strong>unit test</strong>와 <strong>e2e test</strong>만을 진행하기로 결정했다.
unit test는 공통으로 쓰이는 함수들을 체크하기 위해 사용하고, e2e 테스트는 의도한 대로 코드가 잘 동작하는지 확인하기 위해 진행한다.</p>
<h2 id="unit-test">Unit Test?</h2>
<p>단위테스트란 최소 단위의 함수, 컴포넌트 등을 테스트한다.</p>
<h3 id="unit-test-vs-e2e-test">Unit Test vs. E2E Test</h3>
<h4 id="unit-test-1">Unit Test</h4>
<ul>
<li>주어진 함수, 클래스 또는 컴포저블에 제공된 입력 정보가 의도하는 출력 또는 side-effect를 생성하는지 확인</li>
<li>테스트가 빠르게 실행됨</li>
<li>정확한 문제 식별 가능</li>
<li>그러나, 단위 테스트를 통과했음에도 불구하고 전체 어플리케이션이 여전히 작동하지 않을 수 있음</li>
<li>Tool: <a href="https://4sii.tistory.com/291">Jest</a></li>
</ul>
<h4 id="e2e-test">E2E Test</h4>
<ul>
<li>여러 페이지에 걸쳐 있는 기능을 확인하고, 프로덕션으로 빌드되는 Vue 앱처럼 실제 네트워크 요청</li>
<li>한 번에 많은 항목 테스트 가능</li>
<li>실행 속도가 느림 (최대 1시간 이상 걸릴 수 있음)</li>
<li>실패 원인을 정확히 찾아낼 수 없음</li>
<li>외부 서비스나 API에 의존하는 E2E 테스트는 어플리케이션 자체에는 문제가 없지만 서비스가 중단되어 실패할 수 있음</li>
<li>Tool: Cypress, Nightwatch</li>
</ul>
<h2 id="unit-test-tools">Unit Test Tools</h2>
<p>javascript 테스트 중 인기 있는 라이브러리는 <a href="">Jest</a>와 <a href="">Jasmine</a>이다. </p>
<h2 id="jest-사용">Jest 사용</h2>
<h3 id="library-설치">library 설치</h3>
<pre><code class="language-shell">npm install -D jest
npm install -D jest-environment-jsdom
npm install -D eslint-plugin-jest</code></pre>
<h3 id="packagejson">package.json</h3>
<pre><code class="language-json">script: {
  &#39;test&#39;: &#39;jest&#39;
}</code></pre>
<p>###</p>
<h3 id="😢-오류-해결">😢 오류 해결</h3>
<h4 id="import-오류">import 오류</h4>
<p><code>Jest encountered an unexpected token</code> 오류 발생</p>
<p><code>import</code> 자체를 읽지 못하는 이슈로, babel-jest로 javascript code transformation 필요. babel-jest 설치 및 설정이 필요하다.</p>
<p><strong>해결방법</strong></p>
<ol>
<li>babel-jest 설치</li>
</ol>
<pre><code class="language-shell">npm install -D babel-jest</code></pre>
<ol start="2">
<li>jest.config.js 옵션 추가<pre><code class="language-javascript">const config = {
transform: {
 &#39;\\.js$&#39;: &#39;&lt;rootDir&gt;/node_modules/babel-jest&#39;,
},
}</code></pre>
</li>
</ol>
<h4 id="module-import-오류">module import 오류</h4>
<p><code>Cannot find module &#39;@/common&#39; from &#39;__test__/common.test.js&#39;</code> 오류 발생</p>
<p><code>import { isNullString } from &#39;@/common&#39;;</code> 라인에서 발생하는 오류로, <code>@</code> alias 선언이 안돼있어 module import 시 파일 경로를 찾지 못해 발생하는 오류</p>
<p><strong>해결방법</strong></p>
<ol>
<li>jest.config.js 옵션 추가<pre><code class="language-javascript">const config = {
moduleNameMapper: {
 &#39;\\.(css|less|scss)$&#39;: &#39;&lt;rootDir&gt;/&lt;파일 경로&gt;/styleMock.js&#39;,
}
}</code></pre>
</li>
</ol>
<h4 id="library-내부-css-오류">Library 내부 css 오류</h4>
<p><code>SyntaxError: Unexpected token .</code> 오류 발생</p>
<p><code>import &#39;./stype.css&#39;;</code> 라인에서 발생하는 오류로, 사용하고 있는 library 내부에서 css를 import해서 사용하고 있을 때 발생하는 오류</p>
<p><strong>해결방법</strong></p>
<ol>
<li>styleMock.js 파일 생성</li>
</ol>
<pre><code class="language-javascript">export default {};</code></pre>
<ol start="2">
<li>jest.config.js 옵션 추가</li>
</ol>
<pre><code class="language-javascript">const config = {
  moduleNameMapper: {
    &#39;\\.(css|less|scss)$&#39;: &#39;&lt;rootDir&gt;/&lt;파일 경로&gt;/styleMock.js&#39;,
  }
}</code></pre>
<h4 id="jquery-ui-jquery-선언-오류">jquery-ui jQuery 선언 오류</h4>
<p>jquery-ui library 사용중 <code>ReferenceError: jQuery is not defined</code> 오류 발생</p>
<p>jquery-ui node_modules/dist/jquery-ui.js 내부 jQuery 선언이 돼있지 않아 발생하는 오류로, jest 실행 전 환경설정 파일 읽어들일 수 있도록 수정</p>
<p><strong>해결방법</strong></p>
<ol>
<li>test-env.js 파일 생성</li>
</ol>
<pre><code class="language-js">import $ from &#39;jquery&#39;;
global.$ = global.jQuery = $;</code></pre>
<ol start="2">
<li>jest.config.js 옵션 추가</li>
</ol>
<pre><code class="language-javascript">const config = {
  setupFiles: [&#39;&lt;rootDir&gt;/&lt;파일 경로&gt;/test-env.js&#39;],
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[테스트 도입 - 레거시 프로젝트] 2-2. FE E2E Test]]></title>
            <link>https://velog.io/@soo-ni/FE-E2E-Test</link>
            <guid>https://velog.io/@soo-ni/FE-E2E-Test</guid>
            <pubDate>Tue, 10 Oct 2023 06:52:52 GMT</pubDate>
            <description><![CDATA[<h2 id="e2e-test">E2E Test?</h2>
<p>E2E는 End to End 의 약자로 어플리케이션의 흐름을 처음부터 끝까지 테스트하는 것을 의미한다. API 연동도 테스트 항목에 포함되므로 일반적으로 목(Mock) 서버를 사용하지 않으며 최대한 실제 시스템을 사용하는 <strong>사용자 관점</strong>에서 시뮬레이션 한다.</p>
<h3 id="unit-test-vs-e2e-test">Unit Test vs. E2E Test</h3>
<h4 id="unit-test">Unit Test</h4>
<ul>
<li>주어진 함수, 클래스 또는 컴포저블에 제공된 입력 정보가 의도하는 출력 또는 side-effect를 생성하는지 확인</li>
<li>테스트가 빠르게 실행됨</li>
<li>정확한 문제 식별 가능</li>
<li>그러나, 단위 테스트를 통과했음에도 불구하고 전체 어플리케이션이 여전히 작동하지 않을 수 있음</li>
<li>Tool: <a href="https://4sii.tistory.com/291">Jest</a></li>
</ul>
<h4 id="e2e-test-1">E2E Test</h4>
<ul>
<li>여러 페이지에 걸쳐 있는 기능을 확인하고, 프로덕션으로 빌드되는 Vue 앱처럼 실제 네트워크 요청</li>
<li>한 번에 많은 항목 테스트 가능</li>
<li>실행 속도가 느림 (최대 1시간 이상 걸릴 수 있음)</li>
<li>실패 원인을 정확히 찾아낼 수 없음</li>
<li>외부 서비스나 API에 의존하는 E2E 테스트는 어플리케이션 자체에는 문제가 없지만 서비스가 중단되어 실패할 수 있음</li>
<li>Tool: Cypress, Nightwatch</li>
</ul>
<blockquote>
<p><strong>참고</strong>
<a href="https://ko.vuejs.org/guide/scaling-up/testing.html">Vue &gt; 테스트</a>에서는 왜 테스트를 해야하고, 어떤 테스트 유형이 있는지 자세히 작성되어 있다.</p>
</blockquote>
<h2 id="e2e-test-tools">E2E Test Tools</h2>
<p>테스트를 위한 툴로는 Cypress, Nightwatch, Playwright, Puppeteer, Selenium 등이 있다. <a href="https://npmtrends.com/cypress-vs-nightwatch-vs-playwright-vs-puppeteer-vs-selenium-vs-testcafe">npm trends</a>에 따르면 2023년 10월 기준으로 1년간 cypress가 가장 많이 다운로드 됐다.</p>
<p><img src="https://velog.velcdn.com/images/soo-ni/post/9e67a367-7644-414c-843e-1b79fc7cf276/image.png" alt=""></p>
<p>일반적으로 <a href="https://nightwatchjs.org/">Nightwatch</a>와 <a href="https://www.cypress.io/">Cypress</a>를 가장 많이 사용한다.</p>
<p>공식 Vue 팀이 추천하는 E2E Test뿐만 아니라 컴포넌트 테스트도 지원하는 Cypress 를 적용해보자. (<code>Chronium 브라우저와 Firefox만 지원</code>)</p>
<h2 id="cypress-적용">Cypress 적용</h2>
<pre><code class="language-shell">npm install -D cypress
npm install -D eslint-plugin-cypress</code></pre>
<p>.eslintrc.js</p>
<pre><code class="language-json">extends: [&#39;plugin:cypress/recommended&#39;]</code></pre>
<p>package.json</p>
<pre><code class="language-json">&quot;scripts&quot;: {
  &quot;test&quot;: &quot;cypress open&quot;,
},</code></pre>
<p>cypress 실행 시 아래와 같은 화면에서 continue 후 chrome 으로 실행 </p>
<pre><code class="language-shell">npm test</code></pre>
<p><img src="https://velog.velcdn.com/images/soo-ni/post/ff56206b-a6ae-4afc-9e0a-b1d26f7dd3c0/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/soo-ni/post/e130d87b-32f4-4d29-916b-5d83a753a5f3/image.png" alt=""></p>
<p>cypress 관련 폴더 생성 및 cypress UI
<img src="https://velog.velcdn.com/images/soo-ni/post/3425e507-20d9-4848-bdf0-67cbf265fa59/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/soo-ni/post/91f3151f-1245-4f2e-bedd-52aa6e2ff814/image.png" alt=""></p>
<h2 id="cypress-studio">Cypress Studio</h2>
<p>cypress를 사용하며 테스트 코드를 모두 작성할 수 있지만 cypress studio를 이용해 손쉽게 테스트 코드를 작성할 수 있다.</p>
<h3 id="cypress-studio-활성화">Cypress Studio 활성화</h3>
<ol>
<li>cypress open &gt; settings &gt; Project settings
Project settings에서 확인해보면 <code>experimentalStudio</code> 라는 옵션이 있다. 현재는 disabled 상태로 활성화시켜보자.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/soo-ni/post/50e9c362-a3e6-4fb1-b9b1-94f2a6764a06/image.png" alt=""></p>
<ol start="2">
<li>cypress.config.js 수정</li>
</ol>
<p><code>experimentalStudio: true</code> 를 cypress.config.js에 추가하면 cypress 창이 새로 열리게된다. 이후 Project settings를 보면 활성화 상태인 것을 확인할 수 있다. </p>
<p><img src="https://velog.velcdn.com/images/soo-ni/post/9e8308d1-bfaf-4c7c-ae27-2a7ed03a2563/image.png" alt=""></p>
<h3 id="cypress-studio-사용">Cypress Studio 사용</h3>
<ol>
<li>example.studio.cy.js 파일 생성 후 수정</li>
</ol>
<p><img src="https://velog.velcdn.com/images/soo-ni/post/b9197083-dbe8-4995-b0ac-5fefe7b0444a/image.png" alt=""></p>
<ol start="2">
<li>원하는 테스트 동작 입력</li>
</ol>
<p><img src="https://velog.velcdn.com/images/soo-ni/post/1737deeb-634a-451f-98b9-49e37ae46784/image.gif" alt=""></p>
<ol start="3">
<li>코드 확인</li>
</ol>
<p>코드가 알아서 생성된다.</p>
<p><img src="https://velog.velcdn.com/images/soo-ni/post/ccad53c3-d651-432e-bdfb-c00219d9dc7b/image.png" alt=""></p>
<h2 id="with-cicd-tools">with CI/CD Tools</h2>
<p>Cypress를 사용 시 CI/CD Tools 에서 자동으로 테스트할 수 있다. 현재 회사에서 사용하고 있는  Teamcity에서 script 내부에 cypress test 실행하는 코드를 작성한 다음 build 하게 되면 자동으로 테스트 가능하다. 이 때, cypress version이 특정돼있는 docker 선택해야한다.</p>
<pre><code class="language-shell">npm install cypress
npm run test</code></pre>
<h2 id="논의-사항">논의 사항</h2>
<ul>
<li>mock 서버를 만들기보다는 실 서버에서 테스트해야한다.
=&gt; test 서버는 어디로?</li>
<li>전체를 테스트해야하므로 범위를 지정해야한다</li>
</ul>
<h3 id="참고">참고</h3>
<ul>
<li><a href="https://fe-developers.kakaoent.com/2023/230209-e2e/">E2E 테스트 도입 경험기</a></li>
<li><a href="https://tech.kakao.com/2022/02/25/angular-e2e-testing-2/">FE개발자의 성장 스토리 12 : Angular E2E 테스팅 경험기</a></li>
<li><a href="https://vuejsdevelopers.com/2019/04/01/vue-testing-unit-vs-e2e/">Unit vs E2E Testing for Vue.js</a></li>
<li><a href="https://kailash-pathak.medium.com/how-to-set-up-and-run-cypress-test-cases-in-ci-cd-teamcity-18e1c03a438b">How to Set up And Run Cypress Test Cases in CI/CD TeamCity?</a></li>
<li><a href="https://blog.coderifleman.com/2016/06/17/e2e-test-and-nightwatch/">E2E 테스트와 나이트왓치</a></li>
<li><a href="https://jforj.tistory.com/324">[React] Cypress로 E2E테스트하기</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Intellij Git bash 연결]]></title>
            <link>https://velog.io/@soo-ni/Intellij-Git-bash-%EC%97%B0%EA%B2%B0</link>
            <guid>https://velog.io/@soo-ni/Intellij-Git-bash-%EC%97%B0%EA%B2%B0</guid>
            <pubDate>Mon, 18 Sep 2023 00:05:32 GMT</pubDate>
            <description><![CDATA[<p>로컬에서 shell file 실행 시 window 환경이어서 shell script실행이 안되는 문제 발생</p>
<p>=&gt; terminal 환경을 git bash로 바꾸자!</p>
<h2 id="settings-변경">Settings 변경</h2>
<ol>
<li>Settings &gt; terminal 검색</li>
<li>Tools &gt; Terminal</li>
<li>Application Settings &gt; Shell path</li>
<li><code>&quot;C:\Program Files\Git\bin\sh.exe&quot; -login -i</code> 입력</li>
<li>IntelliJ restart</li>
</ol>
<p><img src="https://velog.velcdn.com/images/soo-ni/post/1d38c83e-ca55-4629-ab39-bca2bf257f45/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[StructuredClone]]></title>
            <link>https://velog.io/@soo-ni/StructuredClone</link>
            <guid>https://velog.io/@soo-ni/StructuredClone</guid>
            <pubDate>Fri, 11 Aug 2023 00:32:13 GMT</pubDate>
            <description><![CDATA[<h2 id="structuredclone-global-function">structuredClone() global function</h2>
<p>Javascript에서 객체의 깊은 복사를 위해 여러 방식을 사용했다. Javascript는 이제 <code>structuredClone()</code> 이라는 깊은 복사를 위한 전역 메소드를 지원한다.</p>
<p>전역 <code>structuredClone()</code> 메소드는 깊은 복사를 생성한다.</p>
<p><code>structuredClone()</code>의 기능을 설명하기 전 얕은 복사와 깊은 복사에 대해 간단하게 설명한다.</p>
<h2 id="얕은-복사">얕은 복사</h2>
<p>Javascript에서 값을 복사하는 경우 거의 항상 얕은(shallow) 복사이다. 중첩된 객체의 경우 복사된 객체를 변경하면 원본에도 영향을 주게 된다.</p>
<h3 id="arrayprototypeslice">Array.prototype.slice</h3>
<p>Array.prototype.slice 는 얕은 복사를 수행하기 때문에, 모든 값을 독립적으로 복사할 수 없다.</p>
<pre><code class="language-js">const arr = [1, 2, [3, 4]];
const copied = arr.slice();

isSameObject(arr, copied); // true

copied[2].push(5);
isSameObject(arr, copied); // true</code></pre>
<h3 id="spread-operator">Spread Operator</h3>
<p>펼침 연산자를 통해서도 값의 복사를 수행할 수 있다. 아래 예시와 같이 객체가 이터러블임이 확인이되면 for-loop 과 같은 단순한 반복문을 통해 모든 요소를 하나씩 옮겨 담는다.</p>
<pre><code class="language-js">const b = [1, 2, 3];
const a = [ ...b ];

// 위를 실행했을 때, 내부적으로 일어나는 일
var a = [];
for (var i = 0; i &lt; b.length; i += 1) {
  a.push(b[i]);
}</code></pre>
<p>이러한 이유로 펼침 연산자 역시 중첩 구조를 복사하지 못한다.</p>
<pre><code class="language-js">const arr = [1, 2, [3, 4]];
const copied = [ ...arr ];

isSameObject(arr, copied); // true

copied[2].push(5);
isSameObject(arr, copied); // true</code></pre>
<h3 id="objectassign">Object.assign</h3>
<p>Object.assign의 경우에도 마찬가지로 중첩 구조를 복사하지 못한다.</p>
<pre><code class="language-js">const arr = [1, 2, [3, 4]];
const copied = Object.assign([], arr);

isSameObject(arr, copied); // true

copied[2].push(5);
isSameObject(arr, copied); // true</code></pre>
<h2 id="깊은-복사">깊은 복사</h2>
<p>깊은(deep) 복사는 얕은 복사의 반댓말로, 복사된 객체를 변경해도 원본에 영향을 주지 않는다. 깊은 복사도 객체의 속성을 하나씩 복사하지만, 다른 객체에 대한 참조를 찾으면 재귀적으로 해당 객체의 복사본도 생성한다.</p>
<h3 id="jsonparsejsonstringify">JSON.parse(JSON.stringify)</h3>
<p>JSON.stringify은 입력 값으로 넘어온 데이터를 문자로 변형을 시켜주는 메소드이고, JSON.parse 는 JSON.stringify로 변형된 문자를 다시 원래 객체로 되돌려주는 역할을 한다.</p>
<pre><code class="language-js">const arr = [1, 2, [3, 4]];
const copied = JSON.parse(JSON.stringify(arr));

isSameObject(arr, copied); // true

copied[2].push(5);
isSameObject(arr, copied); // false</code></pre>
<p>이는 객체 순환을 통해 값을 옮겨담는 과정이 아닌, 문자열로 변경 후 그것을 다시 해석해 원본 객체로 변경하는 과정에서 자바스크립트의 문자열(string) 이라고 불리는 데이터 타입이 immutable primitive type, 즉 불변성의 형질을 띠는 원시 타입이기 때문이다.</p>
<p>이 패턴은 몇가지 단점이 있는데 다음과 같다.</p>
<ol>
<li>재귀 데이터 구조: 재귀적인 데이터 구조로 <code>JSON.stringify()</code>를 사용하면 에러가 발생한다.</li>
<li>내장 함수 유형: Map, Set, Date, RegExp, ArrayBuffer를 포함한 값이 있을 경우 에러가 발생한다.</li>
<li>함수: 함수를 폐기한다.</li>
</ol>
<h2 id="기능-및-제한-사항">기능 및 제한 사항</h2>
<p><code>structuredClone()</code>은 구조적 복제는 순환되는 데이터 구조를 처리할 수 있고, 내장 데이터 타입을 지원할 수 있으며 일반적으로 더 강력하고 더 빠르다.</p>
<h3 id="기능">기능</h3>
<p><code>structuredClone()</code>은 깊은 복사를 하는 데 사용될 수 있으며 아래와 같이 <strong>순환 참조</strong>도 지원한다.</p>
<pre><code class="language-js">// 값과 스스로를 순환 참조하는 객체 생성
const original = { name: &quot;MDN&quot; };
original.itself = original;

// 복제
const clone = structuredClone(original);

console.assert(clone !== original); // 동일하지 않은 객체 (같지 않은 동일성)
console.assert(clone.name === &quot;MDN&quot;); // 같은 값을 가집니다.
console.assert(clone.itself === clone); // 순환 참조가 보존됩니다.</code></pre>
<p><code>structuredClone()</code>를 사용해 객체를 복제하는 경우, 각 객체에 대한 변경은 다른 객체에 영향을 주지 않는다.</p>
<pre><code class="language-js">const mushrooms1 = {
  amanita: [&quot;muscaria&quot;, &quot;virosa&quot;],
};

const mushrooms2 = structuredClone(mushrooms1);

mushrooms2.amanita.push(&quot;pantherina&quot;);
mushrooms1.amanita.pop();

console.log(mushrooms2.amanita); // [&quot;muscaria&quot;, &quot;virosa&quot;, &quot;pantherina&quot;]
console.log(mushrooms1.amanita); // [&quot;muscaria&quot;]
</code></pre>
<h3 id="제한-사항">제한 사항</h3>
<ol>
<li>프로토타입: 객체의 프로토타입 체인을 폐기한다.</li>
<li>함수: 객체에 함수가 있는 경우 폐기된다.</li>
<li>복제 불가: <code>Error</code>와 <code>DOM 노드</code>는 복제가 불가능하다.</li>
</ol>
<h2 id="성능">성능</h2>
<p><a href="https://www.measurethat.net/Benchmarks/Show/18967/0/structuredclone-test#latest_results_block">성능 테스트 결과</a>에 따르면 <code>structuredClone()</code>이 <code>JSON.parse(JSON.stringify)</code> 보다 빠르다는것을 알 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/soo-ni/post/7c7a2b78-bfe4-4a30-919f-6a444043e0b1/image.png" alt=""></p>
<h2 id="브라우저-호환성">브라우저 호환성</h2>
<p>Chrome, Edge 등 브라우저에서 모두 사용 가능하다.</p>
<p><img src="https://velog.velcdn.com/images/soo-ni/post/e118262d-8e69-4838-a068-ba7f6058d6ef/image.png" alt=""></p>
<h2 id="결론">결론</h2>
<p>Javascript에서 깊은 복사본을 만들어야 하는 경우 다른 라이브러리를 사용하지 않고 자체적으로 제공하는 <code>structuredClone()</code>를 사용하면 될 것 같다.</p>
<h3 id="참고">참고</h3>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/structuredClone">structuredClone() global function</a></li>
<li><a href="https://velog.io/@seonja/StructuredClone%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-JavaScript%EC%97%90%EC%84%9C-%EA%B9%8A%EC%9D%80-%EB%B3%B5%EC%82%AC#%EC%96%95%EC%9D%80-%EB%B3%B5%EC%82%AC">StructuredClone에 대해 꼭 알아두세요! JavaScript에서 깊은 복사가 가능해져요.</a></li>
<li><a href="https://medium.com/watcha/%EA%B9%8A%EC%9D%80-%EB%B3%B5%EC%82%AC%EC%99%80-%EC%96%95%EC%9D%80-%EB%B3%B5%EC%82%AC%EC%97%90-%EB%8C%80%ED%95%9C-%EC%8B%AC%EB%8F%84%EC%9E%88%EB%8A%94-%EC%9D%B4%EC%95%BC%EA%B8%B0-2f7d797e008a">WATCHA - 깊은 복사 얕은 복사</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[TypeScript]]></title>
            <link>https://velog.io/@soo-ni/TypeScript</link>
            <guid>https://velog.io/@soo-ni/TypeScript</guid>
            <pubDate>Fri, 14 Jul 2023 00:15:34 GMT</pubDate>
            <description><![CDATA[<h2 id="💻-typescript란">💻 TypeScript란?</h2>
<p>TypeScript란 Microsoft에서 개발 및 유지 관리 하는 오픈소스 프로그래밍 언어이다.</p>
<p>TypeScript는 ES5의 Superset이므로 기존의 자바스크립트(ES5) 문법을 그대로 사용할 수 있다. 쉽게 말해 JavaScript에 <code>타입</code>을 부여한 언어이다.
<img src="https://blog.hax0r.info/assets/images/posts/2020-12-15-from-the-grave-to-the-cradle-with-typescript/typescript-superset.png"></p>
<h3 id="javascript-vs-typescript">JavaScript vs. TypeScript</h3>
<ul>
<li>JavaScript<ul>
<li><code>동적 타이핑 (Dynamic Typing)</code></li>
<li>변수나 반환값의 타입을 사전에 지정하지 않는다.</li>
<li>타입 결정 이후에도 같은 변수에 여러 타입의 값을 교차하여 할당 가능하다.</li>
<li><code>런타임</code> 시 에러를 발생시킨다.</li>
</ul>
</li>
<li>TypeScript<ul>
<li><code>정적 타이핑 (Static Typing)</code></li>
<li>타입을 명시적으로 선언한다.</li>
<li>타입이 선언된 이후에는 변경할 수 없다.</li>
<li><code>컴파일</code> 시 에러를 발생시킨다.</li>
<li>JavaScript와 달리 브라우저에서 실행하려면 compile해야한다.</li>
</ul>
</li>
</ul>
<h2 id="🤔-typescript를-쓰는-이유">🤔 TypeScript를 쓰는 이유?</h2>
<p>크게 에러의 사전 방지 및 코드 가이드 및 자동완성을 통한 개발 생산성 향상을 위해 사용한다.</p>
<h3 id="에러의-사전-방지">에러의 사전 방지</h3>
<pre><code class="language-javascript">// js
function sum(a, b) {
    return a + b;
}</code></pre>
<pre><code class="language-typescript">// ts
function sum(a: number, b:number) {
    return a + b;    
}</code></pre>
<pre><code class="language-javascript">sum(10, 20);    // ex1
sum(&#39;10&#39;, &#39;20&#39;);    // ex2</code></pre>
<p><code>sum</code> 함수를 이용하여 ex1과 같이 숫자 10과 20을 더하게 되면 js, ts 모두 20을 얻을 수 있다.</p>
<p>ex2와 같이 숫자 대신 문자열을 더하게 될 경우 js는 <code>1020</code> 이라는 결과를 반환한다. 그러나, ts의 경우 컴파일 단계에서 오류를 발생시켜 의도하지 않은 코드의 동작을 예방할 수 있다.</p>
<h3 id="코드-자동-완성과-가이드">코드 자동 완성과 가이드</h3>
<p>TypeScript를 사용하게되면 <code>Visual Studio Code</code> 와 같은 개발 툴의 기능을 최대로 활용할 수 있다.</p>
<p>JavaScript로 코드를 작성하게 되면 작성 시점에 변수의 타입을 모르기 때문에 API를 사용할 수 없다. 그러나 TypeScript로 코드를 작성하게 되면 타입이 지정되어 있기 때문에 해당 타입에 대한 API를 미리 보기로 볼 수 있고 빠르고 정확하게 작성할 수 있다.</p>
<h2 id="📑-기본-타입">📑 기본 타입</h2>
<p>JavaScript에서 추가된 데이터 타입이 있다.</p>
<ul>
<li>tuple</li>
<li>enum</li>
<li>any</li>
<li>void</li>
<li>never</li>
</ul>
<h3 id="boolean">Boolean</h3>
<pre><code class="language-typescript">let isTrue: boolean = false;</code></pre>
<h3 id="number">Number</h3>
<pre><code class="language-typescript">let num: number = 6;</code></pre>
<h3 id="string">String</h3>
<pre><code class="language-typescript">let name: string = &quot;typescript&quot;;</code></pre>
<h3 id="array">Array</h3>
<pre><code class="language-typescript">let arr: number[] = [1, 2, 3];    // []
let arr: Array&lt;number&gt; = [1, 2, 3];    // 제네릭 배열 타입</code></pre>
<h3 id="tuple">Tuple</h3>
<p>요소의 타입과 개수가 고정된 배열을 표현 가능하다.</p>
<pre><code class="language-typescript">let arr: [string, number];
arr = [&quot;hello&quot;, 1];

console.log(arr[0].substring(1));    // 정해진 인덱스에 위치한 요소에 접근 가능</code></pre>
<h3 id="enum">Enum</h3>
<p>특정 값이 어떤 <code>Color</code> enum멤버와 매칭되는지를 알고 싶을 때, 이 값을 이용하면 일치하는 enum 멤버의 이름을 알아낼 수 있다.</p>
<pre><code class="language-typescript">enum Color {Red, Green, Blue}
let c: Color = Color.Green;</code></pre>
<h3 id="any">Any</h3>
<p>알지 못하는 타입을 표현해야 할 경우 타입 검사를 하지 않고, 그 값들이 컴파일 시간에 검사를 통과시킬 때 사용한다.</p>
<pre><code class="language-typescript">let notSure: any = 4;
notSure = &quot;maybe a string instead&quot;;
notSure = false; // success</code></pre>
<p>또한, any 타입은 타입의 일부만 알고 전체는 알지 못할 때 사용한다. 예를 들어, 여러 다른 타입이 섞인 배열을 다룰 수 있다.</p>
<pre><code class="language-typescript">let list: any[] = [1, true, &quot;free&quot;];

list[1] = 100;</code></pre>
<h3 id="void">Void</h3>
<p>변수에는 <code>undefined</code>와 <code>null</code>만 할당하고, 함수에는 반환 값을 설정할 수 없는 타입이다. 보통 함수에서 반환 값이 없을 때 반환 타입을 표현하기 위해 쓰인다.</p>
<pre><code class="language-typescript">let unuseful: void = undefined;
function warnUser(): void {
    console.log(&quot;This is my warning message&quot;);
}</code></pre>
<h3 id="null-and-undefined">Null and Undefined</h3>
<pre><code class="language-typescript">// 이 밖에 이 변수들에 할당할 수 있는 값이 없다!
let u: undefined = undefined;
let n: null = null;</code></pre>
<p><strong>기본적으로 <code>null</code> 과 <code>undefined</code>는 다른 모든 타입의 하위 타입이다.</strong></p>
<p>하지만, <code>--strictNullChecks</code>를 사용하면, <code>null</code>과 <code>undefined</code>는 오직 <code>any</code>와 각자 자신들 타입에만 할당 가능하다.</p>
<h3 id="never">Never</h3>
<p>함수의 끝에 절대 도달하지 않는다는 의미를 지닌 타입이다.</p>
<pre><code class="language-typescript">// 이 함수는 절대 함수의 끝까지 실행되지 않는다는 의미
function neverEnd(): never {
  while (true) {

  }
}</code></pre>
<h3 id="object">Object</h3>
<p><code>object</code>는 원시 타입(Primitive Type)이 아닌 타입을 나타낸다.</p>
<h3 id="변수-선언-시-let을-사용하는-이유">변수 선언 시 let을 사용하는 이유</h3>
<p>값이 변하지 않는 변수는 <code>const</code>를, 값이 변하는 변수는 <code>let</code>을 사용하여 선언한다. <code>var</code>는 절대로 사용하지 않도록 한다.</p>
<blockquote>
<ul>
<li>let: 중복 선언 불가능 &gt; 블럭 내부의 변수는 외부에서 사용 불가능</li>
<li>var: 중복 선언 가능 &gt; 블럭 내부의 변수를 외부에서 사용 가능</li>
</ul>
</blockquote>
<pre><code class="language-javascript">var foo = 123;
console.log(foo); //123

{
    var foo=456;  // 중복선언이 가능, 위에 선언된 애를 블럭내에서도 참조가 되버림
}
console.log(foo); //456 , 블럭밖에서도 참조가 됩니다. 

let foo2_ = 789;
console.log(foo2_); //789
{
    let foo2_:number = 456; 
    // 중복선언이 불가능. 같은 변수를 두번 선언 할 수 없음. 
    // /위에걸 쓰는게 아니고 블럭내의 변수하나를 새로 만드는것
    let bar:number = 456;
    console.log(foo2_);
    console.log(bar); //456
}
foo2_ = 567;
console.log(foo2_); //789</code></pre>
<h2 id="⚙-typescript-개발-환경-설정">⚙ TypeScript 개발 환경 설정</h2>
<ol>
<li>TypeScript 컴파일러 설치</li>
</ol>
<pre><code class="language-shell">$ npm install -g typescript</code></pre>
<ol start="2">
<li>ts 파일 작성 (test.ts)</li>
</ol>
<pre><code class="language-typescript">let helloWorld = &quot;Hello World!!&quot;;
const user = {
    name: &quot;TypeScript&quot;,
    id: 0,
};</code></pre>
<ol start="3">
<li>ts 파일 컴파일</li>
</ol>
<pre><code class="language-shell">$ tsc test.ts</code></pre>
<ol start="4">
<li>JavaScript 코드로 변환 (test.js)</li>
</ol>
<pre><code class="language-javascript">var helloWorld = &quot;Hello World!!&quot;;
var user = {
    name: &quot;TypeScript&quot;,
    id: 0,
};</code></pre>
<ul>
<li><code>tsconfig.json</code>에서 컴파일 옵션 수정이 가능하다.</li>
</ul>
<h2 id="📚-참고">📚 참고</h2>
<ul>
<li><a href="https://typescript-kr.github.io/">타입스크립트 - 번역본</a></li>
<li><a href="https://blog.hax0r.info/2019-03-12/typescript-in-fastcampus/">이제는 타입스크립트를 배워야합니다. (to 자바스크립트 개발자)</a></li>
<li><a href="https://blog.hax0r.info/2020-12-15/from-the-grave-to-the-cradle-with-typescript/">Typescript 와 함께 요람에서 무덤까지 1부</a></li>
<li><a href="https://poiemaweb.com/typescript-introduction">TypeScript의 소개와 개발 환경 구축</a></li>
<li><a href="https://nashorn.tistory.com/entry/Typescript%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-Nodejs-API-%EC%84%9C%EB%B2%84-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D">Typescript를 이용한 Node.js API 서버 프로그래밍</a></li>
<li><a href="https://react.vlpt.us/using-typescript/">8장. 리액트 프로젝트에서 타입스크립트 사용하기</a></li>
<li><a href="https://codingmoondoll.tistory.com/entry/%EC%B8%84%EB%9D%BC%EC%9D%B4-%EC%B8%84%EB%9D%BC%EC%9D%B4-%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EB%B6%80%EC%A0%9C-%EB%82%98%EB%A7%8C-%EA%B3%A0%ED%86%B5-%EB%B0%9B%EC%9D%84-%EC%88%98%EB%8A%94-%EC%97%86%EC%A7%80">리액트 + 타입스크립트</a></li>
<li><a href="https://joshua1988.github.io/vue-camp/ts/with-vue.html">뷰에 타입스크립트 적용하기</a></li>
<li><a href="https://velog.io/@hopsprings2/TypeScript-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%99%98%EA%B2%BD%EC%84%A4%EC%A0%95">TS 환경설정</a></li>
<li><a href="https://joshua1988.github.io/ts/guide/basic-types.html#tuple">타입스크립트 핸드북</a></li>
<li><a href="https://geonlee.tistory.com/214">컴파일옵션 정리</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[프론트엔드의 흐름]]></title>
            <link>https://velog.io/@soo-ni/Why-Vue</link>
            <guid>https://velog.io/@soo-ni/Why-Vue</guid>
            <pubDate>Fri, 16 Jun 2023 02:01:12 GMT</pubDate>
            <description><![CDATA[<p><a href="https://survey.stackoverflow.co/2023/">Stackoverflow Ranking (2023)</a>에서 프레임워크 점유율을 살펴보면 React, Angular, Vue, Svelte 순서로 많이 점유하고 있다.</p>
<p><img src="https://velog.velcdn.com/images/soo-ni/post/aee6c854-92d3-4cff-b54b-ce1a2c9aced9/image.png" alt=""></p>
<p>지금은 이런 프론트엔드 프레임워크가 익숙하지만 예전부터 지금까지 어떤식으로 프론트엔드를 개발해왔는지 간략하게 적어본다.</p>
<h2 id="초기-프론트엔드">초기 프론트엔드</h2>
<h3 id="html-css">HTML, CSS</h3>
<p><img src="https://velog.velcdn.com/images/soo-ni/post/2c55f2c5-be37-466b-a2fb-1033e71dc3fe/image.png" alt=""></p>
<p>HTML 파일을 만들어서 수정하면 되는 아주 기본적인 방법이다. <code>&lt;style&gt;</code>, <code>&lt;link&gt;</code> 태그 사용을 통해 CSS를 적용한다.</p>
<h2 id="jsp-jquery">JSP, jQuery</h2>
<p><img src="https://velog.velcdn.com/images/soo-ni/post/311967ae-8f04-46d2-94dc-c4bf35039672/image.png" alt="">
JSP (Java Server Pages)는 Server Side 스크립트 언어로 페이지에 동적 데이터를 반영할 수 있지만, 새로 필요한 정보가 있는 경우 <strong>새로고침</strong>이나 <strong>페이지 이동</strong>을 통해 서버에서 페이지를 다시 읽어야하는 불편함이 있다.</p>
<p><code>ajax</code> 가 나오면서 <strong>페이지 이동 없이</strong> 서버에 요청하고 받을 수 있게 되었고 변경된 데이터를 <strong>jQuery</strong>를 이용해 <strong>DOM을 간편하게 변경</strong>할 수 있다.</p>
<h2 id="angular-js">Angular JS</h2>
<p><img src="https://velog.velcdn.com/images/soo-ni/post/af3d6d49-e472-46c6-a423-38ff20eb1955/image.png" alt=""></p>
<p>Angular JS는 구글에서 등장한 프레임워크이다.</p>
<p>View와 Model간 <strong>양방향 바인딩</strong>을 통해 jQuery로 DOM 조작을 하는 불편함에서 벗어날 수 있었다. 데이터를 $scope에 저장한 다음 template에 Angular 문법을 사용하면 화면을 자동으로 갱신해준다.</p>
<p>그러나 주기적으로 $scope의 데이터의 변경을 확인하는 Digest Loop/Cycle 방식으로 프로젝트의 규모가 커질수록 굉장히 느려지고 데이터의 흐름이 모호해지기 시작했다.</p>
<p>이러한 문제로 Angular JS는 2.0으로 버전을 올리면서 Angular로 구분되었고 기존 1.0과 호환이 되지 않는다.</p>
<blockquote>
<p>Angular JS 이후로 프론트앤드에서 고려해야하는 사항</p>
</blockquote>
<ol>
<li>새로운 데이터 변경 감지 방법 필요</li>
<li>데이터 변경이 감지되었을 때 뷰를 빠르고 효율적으로 그리기</li>
<li><ol>
<li>2.가 효율적이기 위해 웹페이지의 구조를 더 작은 단위로 만들기</li>
</ol>
</li>
</ol>
<h2 id="react">React</h2>
<p><img src="https://velog.velcdn.com/images/soo-ni/post/5d4bd037-7c01-4d95-ad73-9e8821bd2192/image.png" alt=""></p>
<p>React는 모든 화면을 <strong>컴포넌트</strong>라 불리는 단위로 쪼개고, 이 컴포넌트를 재사용해서 만드는 <strong>Component-based Development</strong>를 적용했다. <em>(3. 웹페이지 구조를 더 작은 단위로 만들기)</em></p>
<p>그렇다면 컴포넌트간 데이터 흐름은 어떻게 될까? React는 부모 컴포넌트에서 자식 컴포넌트로 <strong>prop</strong>을 통해 데이터를 전달하는 것만 가능하고, 컴포넌트 내부에서는 스스로 변경하는 <strong>state</strong>를 통해 데이터 변화를 감지한다. prop이 변경되면 prop을 전달한 부모 컴포넌트가 자식 컴포넌트에게 render를 알려주고 state가 변경되면 setState 메소드가 스스로 render를 실행한다. <em>(1. 새로운 데이터 변경 감지 방법이 필요)</em></p>
<p>prop, state가 변경될때마다 뷰를 통으로 그리는 경우 성능은 어떻게 될까? 이를 해결하기 위해  <strong>Virtual DOM</strong>이란 개념이 나온다. Virtual DOM이란 DOM을 복사한 데이터 구조로, 실제로 DOM을 조작하지 않고 Virtual DOM을 그린 다음 현재 Virtual DOM과 메모리상의 데이터를 비교해서 실제 변경사항만 DOM에 반영한다. <em>(2. 데이터 변경이 감지되었을 때 뷰를 빠르고 효율적으로 그리기)</em></p>
<pre><code class="language-js">// JSX

class HelloMessage extends React.Component {
  render() {
    return &lt;div&gt;Hello {this.props.name}&lt;/div&gt;;
  }
}
ReactDOM.render(&lt;HelloMessage name=&quot;John&quot; /&gt;, mountNode)

// 일반 JavaScript로 컴파일하면?
class HelloMessage extends React.Component {
  render() {
    return React.createElement(
      &quot;div&quot;,
      null,
      &quot;Hello &quot;,
      this.props.name
    );
  }
}

ReactDOM.render(React.createElement(HelloMessage, { name: &quot;John&quot; }), mountNode);</code></pre>
<blockquote>
<p><strong>단방향 바인딩 vs. 양방향 바인딩 ?</strong>
<a href="https://adjh54.tistory.com/49">링크</a> 참고</p>
</blockquote>
<h2 id="vue">Vue</h2>
<p><img src="https://velog.velcdn.com/images/soo-ni/post/06524a32-984c-4bf4-a951-f0d668338ab6/image.png" alt=""></p>
<p>Angular JS에서 편리하게 사용하던 템플릿을 React에서는 바로 뷰를 그리는 JSX를 채택해 러닝커브가 높았다면 Vue는 <em>(1. 새로운 데이터 변경 감지 방법이 필요)</em> <em>(2. 데이터 변경이 감지되었을 때 뷰를 빠르고 효율적으로 그리기)</em> 에 <strong>템플릿</strong>을 추가했다.</p>
<pre><code class="language-vue">&lt;template&gt;
  &lt;button @click=&quot;count++&quot;&gt;Count is: {{ count }}&lt;/button&gt;
&lt;/template&gt;

&lt;script&gt;
export default {
  data() {
    return {
      count: 0
    }
  }
}
&lt;/script&gt;

&lt;style scoped&gt;
button {
  font-weight: bold;
}
&lt;/style&gt;</code></pre>
<h2 id="svelte">Svelte</h2>
<p><img src="https://velog.velcdn.com/images/soo-ni/post/55c17aa7-9dce-44c9-a667-9b36c3ab2e8c/image.png" alt=""></p>
<p><a href="https://svelte.dev/tutorial/basics">Svelte 튜토리얼</a>에서 보게되면 <code>.svelte</code> 파일에 svelte 문법에 따라 코드를 작성하면, svelte가 컴파일해 JS와 CSS output을 만들어준다.</p>
<p>jQuery처럼 개발자가 데이터 변경을 감시하고 갱신의 효율을 따지지 않는 것처럼 svelte도 svelte 컴파일러가 대신 DOM을 조작하는 코드를 작업해준다.</p>
<h2 id="🤔-그래서-why-vue">🤔 그래서 Why Vue?</h2>
<h3 id="react를-사용하지-않는-이유는-뭔가요-🙄">React를 사용하지 않는 이유는 뭔가요? 🙄</h3>
<p>현재 가장 많이 사용하고 있는 React를 사용하지 않는 이유는 뭔가요? 에 대한 대답은 Vue의 러닝커브가 낮아서이다. React는 프레임워크라기 보다는 &#39;라이브러리&#39; 라고 부를정도로 다양한 라이브러리가 있고, 그만큼 자유도가 높다. 자유도가 높다는 말은 좋은 말이지만 한편으로 익숙하지 않은 사람이 시작하기에는 러닝 커브가 높다. Vue는 template화 돼있어서 처음 접하는 사람도 익숙하게 사용할 수 있는 장점이 있다.</p>
<p>어떤 프레임워크를 사용하는지에 대한 답은 없지만 현재 진행하고 있는 프로젝트에 맞게 프레임워크를 선택해서 사용하면 된다.</p>
<h3 id="참고">참고</h3>
<ul>
<li><a href="https://velog.io/@minsangk/%EC%A7%A7%EA%B2%8C-%EC%8D%A8%EB%B3%B4%EB%8A%94-%EC%9B%B9-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C%EC%9D%98-%EC%97%AD%EC%82%AC">짧게 써보는 웹 프론트엔드의 역사</a></li>
<li><a href="https://ssocoit.tistory.com/266">Angular, React, Vue는 어떻게 등장하게 되었을까?</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Javascript debug with Intellij]]></title>
            <link>https://velog.io/@soo-ni/Javascript-debug-with-Intellij</link>
            <guid>https://velog.io/@soo-ni/Javascript-debug-with-Intellij</guid>
            <pubDate>Thu, 01 Jun 2023 23:39:12 GMT</pubDate>
            <description><![CDATA[<p>회사에서 Spring + JSP 프로젝트를 진행하다 javascript에서 es6 문법을 사용할 수 있도록 최근에 webpack 도입했다.</p>
<p>Intellij 에 분명히 javascript debugger가 있는데 어떻게 사용하는지 제대로 알지 못하고 1. <code>debugger;</code> 2. <code>console.log()</code> 3. chrome devtools &gt; sources 를 사용중이었다. Intellj로 어떻게 디버깅하는지 열심히 구글링을 해도 몰랐는데 드디어 해결 완료.</p>
<p>Chrome과 Intellij를 이용해 디버깅하는 방법은 다음과 같다. (Intellij 2021.1.3 기준)</p>
<h2 id="webpackconfigjs">webpack.config.js</h2>
<p>개발 환경에서 디버깅 진행</p>
<pre><code class="language-javascript">module.export = {
  mode: &#39;development&#39;,
  devtool: &#39;inline-source-map&#39;,
}</code></pre>
<h2 id="intellij-debugger">Intellij debugger</h2>
<h3 id="plugin-설치">Plugin 설치</h3>
<p>아래 2가지 plugin이 설치돼있어야 한다.</p>
<ul>
<li>JavaScript and TypeScript</li>
<li>JavaScript Debugger</li>
</ul>
<h3 id="debugger-setting">debugger setting</h3>
<ol>
<li><p>Run &gt; Edit Configurations...
<img src="https://velog.velcdn.com/images/soo-ni/post/84cb14b7-6093-424d-a0d0-a3e40111f75b/image.png" alt=""></p>
</li>
<li><p>+ JavaScript Debug
<img src="https://velog.velcdn.com/images/soo-ni/post/85093746-961c-41fb-b02b-1c8b8ac93d57/image.png" alt=""></p>
</li>
<li><p>기본 설정 세팅
Name, URL, Browser를 다음과 같이 설정한다.
<img src="https://velog.velcdn.com/images/soo-ni/post/cb5585ec-edbf-4846-b46e-4a154ac73d06/image.png" alt=""></p>
</li>
<li><p>🌟 Chrome 세팅 🌟
가장 중요한 Chrome 세팅. Chrome에서 자체적으로 막고있어서 제대로 안됐었다.
Chrome 세팅에 <code>--remote-allow-origins=*</code> 를 추가한다.</p>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/soo-ni/post/a86f4408-6027-4cea-b601-7cf6ca558b51/image.png" alt="">
<img src="https://velog.velcdn.com/images/soo-ni/post/8e4af3ec-929b-4f45-bc88-6bacef278517/image.png" alt="">
<img src="https://velog.velcdn.com/images/soo-ni/post/0ac8a15d-e807-49c8-a5ef-a478d4bb7f29/image.png" alt=""></p>
<ol start="5">
<li>(선택) javascript debug 실행 전 서버 실행
debug 실행 전 webpack 실행을 위해 Before launch에 npm 관련 설정을 추가한다.
package.json을 선택하면 자동으로 scripts가 입력돼 선택 가능하다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/soo-ni/post/2b7f763f-fac8-47bb-90f6-a957482383d7/image.png" alt="">
<img src="https://velog.velcdn.com/images/soo-ni/post/18a94c35-700f-40cf-9c6c-cb29b0c00ba5/image.png" alt="">
<img src="https://velog.velcdn.com/images/soo-ni/post/f8ff8a3f-1894-42da-af84-6e6d1c5087c3/image.png" alt=""></p>
<h3 id="run-debug-🐞-">run debug 🐞 ~!</h3>
<ol>
<li>설정한 javascript debug 선택 후 디버그 모드로 실행한다.
<img src="https://velog.velcdn.com/images/soo-ni/post/1a186ac6-8b61-432f-b111-69b91a926995/image.png" alt=""></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Proxy in Javascript]]></title>
            <link>https://velog.io/@soo-ni/Proxy-in-Javascript</link>
            <guid>https://velog.io/@soo-ni/Proxy-in-Javascript</guid>
            <pubDate>Fri, 12 May 2023 04:28:10 GMT</pubDate>
            <description><![CDATA[<h2 id="proxy">Proxy</h2>
<p><code>Proxy</code> 객체를 사용하면 한 객체에 대한 기본 작업을 가로채고 재정의하는 프록시를 만들 수 있다.</p>
<p>프록시 객체를 사용하면 속성 가져오기, 설정 및 정의와 같은 기본 객체 작업을 <strong>재정의</strong> 할 수 있다. 프록시 객체는 일반적으로 속성 액세스를 기록하고, 입력의 유효성을 검사하고, 형식을 지정하거나, 삭제하는 데 사용된다. </p>
<p>즉, 프록시 객체는 대상 객체를 <strong>트랩</strong>을 통해 기본 명령을 재정의해서 사용한다.</p>
<h3 id="매개변수">매개변수</h3>
<ul>
<li>target: 프록시할 원본 객체</li>
<li>handler: 가로채는 작업과 가로채는 작업을 재정의하는 방법을 정의하는 객체</li>
</ul>
<h3 id="사용-방법">사용 방법</h3>
<p>대상 객체인 target 객체를 만들고, <code>new Proxy()</code> 로 프록시 객체를 생성한다.</p>
<pre><code class="language-js">const target = {
  message1: &quot;hello&quot;,
  message2: &quot;everyone&quot;
};

const handler1 = {};

const proxy1 = new Proxy(target, handler1);</code></pre>
<p>프록시 객체인 <code>proxy1</code> 의 모양은 다음과 같다.</p>
<pre><code>Proxy {}
  &gt; [[Handler]]: Object
  &gt; [[Target]]: Object
  &gt; [[IsRevoked]]: false</code></pre><p>핸들러에서 아무런 작업을 하지 않기 때문에 원래 대상처럼 작동한다.</p>
<pre><code class="language-js">console.log(proxy1.message1); // hello
console.log(proxy1.message2); // everyone</code></pre>
<p>프록시를 커스텀하기 위해서 handler를 정의한다. (트랩 함수 추가)</p>
<pre><code class="language-js">const target = {
  message1: &quot;hello&quot;,
  message2: &quot;everyone&quot;
};

const handler2 = {
  get(target, prop, receiver) {
    return &quot;world&quot;;
  }
};

const proxy2 = new Proxy(target, handler2);</code></pre>
<p>handler에서 get하는 동시에 <code>world</code>를 return하게 했으므로 기존 값이 아닌 <code>world</code>가 출력된다.</p>
<pre><code class="language-js">console.log(proxy2.message1); // world
console.log(proxy2.message2); // world</code></pre>
<p>Reflect 를 이용해 다음과 같이 재정의할 수 있다.</p>
<pre><code class="language-js">const target = {
  message1: &quot;hello&quot;,
  message2: &quot;everyone&quot;
};

const handler3 = {
  get(target, prop, receiver) {
    if (prop === &quot;message2&quot;) {
      return &quot;world&quot;;
    }
    return Reflect.get(...arguments); // target[propertyKey] 값을 반환, target[prop] 값이 undefined가 아닌 TypeError 발생
  },
};

const proxy3 = new Proxy(target, handler3);

console.log(proxy3.message1); // hello
console.log(proxy3.message2); // world</code></pre>
<blockquote>
<p><strong>Reflect</strong> 🤔 ?
하나의 네임스페이스에 모인 API로 <code>Object</code> 보다 더 깔끔하게 구현 가능하다.
ESLint Rule Preset 사용 시 no-prototype-builtins 규칙으로 인해 <code>obj.hasOwnProperty</code> 사용을 못하고 <code>Object.prototype.hasOwnProperty.call</code> 를 사용해야하는데, <code>Relect</code>를 사용한다면 깔끔하게 코드 작성 가능하다.
<code>Object.prototype.hasOwnProperty.call(obj, &#39;prop&#39;);</code> -&gt; <code>Reflect.has(obj, &#39;prop&#39;);</code></p>
</blockquote>
<h2 id="proxy-in-vue">Proxy in Vue</h2>
<p><a href="https://velog.io/@soo-ni/Composition-API#-2-%EB%B0%98%EC%9D%91%ED%98%95-%EB%8D%B0%EC%9D%B4%ED%84%B0-reactive-ref">Composition API - 반응형 데이터</a> 에서 &#39;reactive는 프록시 객체를 반환한다.&#39; 에서의 프록시 객체가 이때까지 살펴봤던 프록시 객체이다.</p>
<p>@vue &gt; reactivity.cjs.js 파일에서 reactive와 ref 코드를 살펴보자.</p>
<h3 id="reactive">reactive</h3>
<p><code>createReactiveObject</code> 에서 살펴볼 수 있듯이 <code>new Proxy</code> 로 프록시 객체를 반환하는 것을 볼 수 있다.</p>
<pre><code class="language-js">function reactive(target) {
    // if trying to observe a readonly proxy, return the readonly version.
    if (isReadonly(target)) {
        return target;
    }
    return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap);
}

function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers, proxyMap) {
    if (!shared.isObject(target)) {
        {
            console.warn(`value cannot be made reactive: ${String(target)}`);
        }
        return target;
    }
    // target is already a Proxy, return it.
    // exception: calling readonly() on a reactive object
    if (target[&quot;__v_raw&quot; /* ReactiveFlags.RAW */] &amp;&amp;
        !(isReadonly &amp;&amp; target[&quot;__v_isReactive&quot; /* ReactiveFlags.IS_REACTIVE */])) {
        return target;
    }
    // target already has corresponding Proxy
    const existingProxy = proxyMap.get(target);
    if (existingProxy) {
        return existingProxy;
    }
    // only specific value types can be observed.
    const targetType = getTargetType(target);
    if (targetType === 0 /* TargetType.INVALID */) {
        return target;
    }
    const proxy = new Proxy(target, targetType === 2 /* TargetType.COLLECTION */ ? collectionHandlers : baseHandlers);
    proxyMap.set(target, proxy);
    return proxy;
}</code></pre>
<p>간단히 pseudo-code 로 나타내면 다음과 같다.</p>
<pre><code class="language-js">function reactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      track(target, key)
      return target[key]
    },
    set(target, key, value) {
      target[key] = value
      trigger(target, key)
    }
  })
}</code></pre>
<h3 id="ref">ref</h3>
<p><code>reactive</code> 와 다르게 프록시 객체를 반환하지 않고 <code>getter/setter</code> 를 사용하는 것을 볼 수 있다.</p>
<pre><code class="language-js">function ref(value) {
    return createRef(value, false);
}

function createRef(rawValue, shallow) {
    if (isRef(rawValue)) {
        return rawValue;
    }
    return new RefImpl(rawValue, shallow);
}

class RefImpl {
    constructor(value, __v_isShallow) {
        this.__v_isShallow = __v_isShallow;
        this.dep = undefined;
        this.__v_isRef = true;
        this._rawValue = __v_isShallow ? value : toRaw(value);
        this._value = __v_isShallow ? value : toReactive(value);
    }
    get value() {
        trackRefValue(this);
        return this._value;
    }
    set value(newVal) {
        const useDirectValue = this.__v_isShallow || isShallow(newVal) || isReadonly(newVal);
        newVal = useDirectValue ? newVal : toRaw(newVal);
        if (shared.hasChanged(newVal, this._rawValue)) {
            this._rawValue = newVal;
            this._value = useDirectValue ? newVal : toReactive(newVal);
            triggerRefValue(this, newVal);
        }
    }
}</code></pre>
<p>간단히 pseudo-code 로 나타내면 다음과 같다.</p>
<pre><code class="language-js">function ref(value) {
  const refObject = {
    get value() {
      track(refObject, &#39;value&#39;)
      return value
    },
    set value(newValue) {
      value = newValue
      trigger(refObject, &#39;value&#39;)
    }
  }
  return refObject
}</code></pre>
<h3 id="📚-참고">📚 참고</h3>
<ul>
<li><a href="https://ko.javascript.info/proxy">Javascript</a></li>
<li><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Proxy">MDN web docs - proxy</a></li>
<li><a href="https://ui.toast.com/posts/ko_20210413">JavaScript Proxy. 근데 이제 Reflect를 곁들인</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Vue3]]></title>
            <link>https://velog.io/@soo-ni/Vue2-Vue3</link>
            <guid>https://velog.io/@soo-ni/Vue2-Vue3</guid>
            <pubDate>Thu, 27 Apr 2023 17:04:36 GMT</pubDate>
            <description><![CDATA[<p>Vue2에서 이미 Composition API를 사용하고 있어 큰 차이점은 느끼지 못했지만 간단히 Vue2와 Vue3의 차이점을 정리해본다.</p>
<h2 id="주목할만한-새로운-기능">주목할만한 새로운 기능</h2>
<p><a href="https://v3-migration.vuejs.org/breaking-changes/">많은 변경들</a> 중 Vue3에서 주목해야할 새로운 기능은 아래와 같다.</p>
<ul>
<li>Composition API</li>
<li><code>&lt;script setup&gt;</code></li>
<li>Fragments</li>
<li>Emits Component Option</li>
<li><code>v-bind</code> in <code>&lt;style&gt;</code></li>
<li><code>key</code> usage on <code>v-for</code>, <code>v-if</code></li>
</ul>
<p>Composition API, <code>&lt;script setup&gt;</code>, Emits Component Option, <code>v-bind</code> in <code>&lt;style&gt;</code> 은 Vue 2.7 에서도 사용가능하다.</p>
<h3 id="composition-api">Composition API</h3>
<p>Vue2에서는 <code>data, computed, methods, created, watch</code>를 모두 하나씩 생성해야 했지만 Vue3에서는 Composition API를 통해 setup 함수 안에서 모두 생성할 수 있다. 이 때, setup 함수 안에서 생성한 data는 <code>this</code>로 접근하지 않는다. </p>
<p>Composition API를 사용하는 가장 큰 장점은 목적에 맞는 코드를 모듈화해 유연하고 확장이 가능하다.</p>
<p>자세한 내용은 <a href="https://velog.io/@soo-ni/Composition-API">링크</a> 에서 확인 가능하다.</p>
<h3 id="script-setup">&lt;script setup&gt;</h3>
<p>Composition API를 사용하게되면서 SFC(Single-File Components) 내에서 사용가능하며 다음과 같은 이점이 있다.</p>
<ul>
<li>더 적은 상용구로 간결한 코드 작성</li>
<li>typescript 사용 시 props와 emit 선언</li>
<li>런타임 성능 향상 (템플릿이 <code>&lt;script setup&gt;</code>과 같은 scope에 있는 render 함수로 컴파일 되어 proxy가 불필요)</li>
<li>IDE 타입 추론 성능 향상</li>
</ul>
<h4 id="top-level-bindings-are-exposed-to-template">Top-level bindings are exposed to template</h4>
<p>기존 setup() 함수 내에서 return해서 사용했던 것과 다르게 <code>&lt;script setup&gt;</code> 사용 시 return 없이 사용 가능하다.</p>
<pre><code class="language-vue">&lt;script setup&gt;
import { ref } from &#39;vue&#39;

// variable
const count = ref(0)
const msg = &#39;Hello!&#39;

// functions
function log() {
  console.log(msg)
}
&lt;/script&gt;

&lt;template&gt;
  &lt;button @click=&quot;log&quot;&gt;{{ msg }}&lt;/button&gt;
  &lt;button @click=&quot;count++&quot;&gt;{{ count }}&lt;/button&gt;
&lt;/template&gt;</code></pre>
<p>import 사용도 마찬가지이다.</p>
<pre><code class="language-vue">&lt;script setup&gt;
import { capitalize } from &#39;./helpers&#39;
&lt;/script&gt;

&lt;template&gt;
  &lt;div&gt;{{ capitalize(&#39;hello&#39;) }}&lt;/div&gt;
&lt;/template&gt;</code></pre>
<h4 id="using-components">Using Components</h4>
<p>component를 바로 사용할 수 있다.</p>
<pre><code class="language-vue">&lt;script setup&gt;
import MyComponent from &#39;./MyComponent.vue&#39;
&lt;/script&gt;

&lt;template&gt;
  &lt;MyComponent /&gt;
&lt;/template&gt;</code></pre>
<h4 id="defineprops--defineemits">defineProps() &amp; defineEmits()</h4>
<p><code>props</code>와 <code>emits</code>를 defineProps()와 defineEmits()로 선언 가능하다.</p>
<pre><code class="language-vue">&lt;script setup&gt;
const props = defineProps({
  foo: String
})

const emit = defineEmits([&#39;change&#39;, &#39;delete&#39;])
// setup code
&lt;/script&gt;</code></pre>
<h4 id="usage-alongside-normal-script">Usage alongside normal &lt;script&gt;</h4>
<p>일반 <code>&lt;setup&gt;</code>과 함께 사용 가능하다.</p>
<pre><code class="language-vue">&lt;script&gt;
// normal &lt;script&gt;, executed in module scope (only once)
runSideEffectOnce()

// declare additional options
export default {
  inheritAttrs: false,
  customOptions: {}
}
&lt;/script&gt;

&lt;script setup&gt;
// executed in setup() scope (for each instance)
&lt;/script&gt;</code></pre>
<p>더 많은 설정은 <a href="https://vuejs.org/api/sfc-script-setup.html#script-setup">공식 문서</a>에서 확인 가능하다.</p>
<h3 id="fragments">Fragments</h3>
<p>Vue2에서는 단일 루트 노드만을 지원했지만 Vue3는 다중 루트 컴포넌트를 지원한다. 그러나 공식적으로는 단일 루트를 지향하는것이 좋다 한다.</p>
<pre><code class="language-vue">// vue 2
&lt;template&gt;
  &lt;!-- OK --&gt;
  &lt;div&gt;&lt;/div&gt;
&lt;/template&gt;
&lt;template&gt;
  &lt;!-- ERROR --&gt;
  &lt;div&gt;&lt;/div&gt;
  &lt;div&gt;&lt;/div&gt;
&lt;/template&gt;

// vue 3
&lt;template&gt;
  &lt;!-- OK --&gt;
  &lt;div&gt;&lt;/div&gt;
  &lt;div&gt;&lt;/div&gt;
&lt;/template&gt;</code></pre>
<h3 id="emits-component-option">Emits Component Option</h3>
<p>Vue2에서는 별도로 emit을 지정하는 부분이 없었지만 Vue3 에서는 props와 마찬가지로 emit을 정의할 수 있다. array 형식으로 정의하거나 object 형식으로 선언할 수 있다.</p>
<pre><code class="language-vue">// vue 3
// array 형식
&lt;script&gt;
export default {
  emits: [&#39;check&#39;, &#39;submit&#39;],
  methods: {
    submitMethod() {
      this.$emit(&#39;submit&#39;, { email, password });
    }
  }
}
&lt;/script&gt;

// object 형식
&lt;script&gt;
export default {
  emits: {
    click: null,    // validation이 필요 없는 경우
    submit: (payload) =&gt; {    // validation이 필요한 경우
      if (payload.email &amp;&amp; payload.password) {
        return true;
      } else {
        console.warn(`Invalid submit event payload!`);
        return false;
      }
    }
  },
  methods: {
    submitMethod() {
      this.$emit(&#39;submit&#39;, { email, password });
    }
  }
}
&lt;/script&gt;</code></pre>
<h3 id="v-bind-in-style">v-bind in &lt;style&gt;</h3>
<p>v-bind로 template에서 정의한 변수를 style에서 사용 가능하다.</p>
<pre><code class="language-vue">// vue 3
&lt;script setup&gt;
const theme = {
  color: &#39;red&#39;
}
&lt;/script&gt;

&lt;template&gt;
  &lt;p&gt;hello&lt;/p&gt;
&lt;/template&gt;

&lt;style scoped&gt;
p {
  color: v-bind(&#39;theme.color&#39;);
}
&lt;/style&gt;</code></pre>
<h3 id="key-usage-on-v-for-v-if">key usage on v-for, v-if</h3>
<p>Vue3에서는 key를 자동으로 생성해주고 있으므로 직접 key 설정하는것을 권장하지 않는다.</p>
<h4 id="v-if">v-if</h4>
<pre><code class="language-vue">// vue 2
&lt;div v-if=&quot;condition&quot; key=&quot;yes&quot;&gt;Yes&lt;/div&gt;
&lt;div v-else key=&quot;no&quot;&gt;No&lt;/div&gt;

// vue 3
&lt;div v-if=&quot;condition&quot;&gt;Yes&lt;/div&gt;
&lt;div v-else&gt;No&lt;/div&gt;</code></pre>
<p>Vue2에서 <code>&lt;template&gt;</code> 태그에는 <code>key</code>를 사용할 수 없어 child component에 설정해야 했지만 Vue3에서는 <code>&lt;template&gt;</code> 태그에도 <code>key</code> 를 설정할 수 있다.</p>
<pre><code class="language-vue">// vue 2
&lt;template v-for=&quot;item in list&quot;&gt;
  &lt;div :key=&quot;&#39;heading-&#39; + item.id&quot;&gt;...&lt;/div&gt;
  &lt;span :key=&quot;&#39;content-&#39; + item.id&quot;&gt;...&lt;/span&gt;
&lt;/template&gt;

// vue 3
&lt;template v-for=&quot;item in list&quot; :key=&quot;item.id&quot;&gt;
  &lt;div&gt;...&lt;/div&gt;
  &lt;span&gt;...&lt;/span&gt;
&lt;/template&gt;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Composition-API]]></title>
            <link>https://velog.io/@soo-ni/Composition-API</link>
            <guid>https://velog.io/@soo-ni/Composition-API</guid>
            <pubDate>Thu, 27 Apr 2023 16:34:15 GMT</pubDate>
            <description><![CDATA[<p><code>composition api</code> 는 대규모 프로젝트에서도 <strong>코드의 재사용성을 증가와 로직에 대한 관심사를 모으기 위해 새롭게 추가된 API</strong>이다. namespace 충돌을 방지할 수 있고 코드 로직에 대한 관심사가 한군데에 집중되게 되어 가독성 면에서 큰 이점이 존재한다. 특히 코드의 재사용성과 타입 추론이 크게 개선되었다.</p>
<h2 id="✨composition-api-배경">✨Composition-API 배경</h2>
<p>기존의 vue2 버전에서는 직관적인 확인이 어렵고 재사용성이 떨어진다. 이러한 문제를 해결하기 위해 composition API를 사용한다.</p>
<h3 id="기존-vue-2">기존 Vue 2</h3>
<pre><code class="language-vue">&lt;template&gt;
    &lt;div&gt;
        &lt;h1&gt;{{ countWithUnit }}&lt;/h1&gt;
        &lt;button @click=&quot;increase&quot;&gt;Increase&lt;/button&gt;
        &lt;button @click=&quot;decrease&quot;&gt;Decrease&lt;/button&gt;
    &lt;/div&gt;
&lt;/template&gt;

&lt;script&gt;
export default {
    data() {
        return {
            count: 0
        }
    },
    computed: {
        countWithUnit() {
            return this.count + &#39;입니다&#39;
        }        
    },
    methods: {
        increase() {
            ++this.count
        },
        decrease() {
            --this.count
        }
    }
}
&lt;/script&gt;</code></pre>
<ul>
<li>같은 기능임에도 불구하고 data, computed, methods에 코드가 흩어져있다. </li>
</ul>
<h3 id="composition-api-사용">Composition-API 사용</h3>
<pre><code class="language-vue">&lt;template&gt;
    &lt;div&gt;
        &lt;h1&gt;{{ countWithUnit }}&lt;/h1&gt;
        &lt;button @click=&quot;increase&quot;&gt;Increase&lt;/button&gt;
        &lt;button @click=&quot;decrease&quot;&gt;Decrease&lt;/button&gt;
    &lt;/div&gt;
&lt;/template&gt;

&lt;script&gt;
import { ref, computed } from &#39;@vue/composition-api&#39;

const useCount = () =&gt; {
    const count = ref(0)
    const countWithUnit = computed(() =&gt; count.value + &#39;입니다&#39;)
    const increase = () =&gt; ++count.value
    const decrease = () =&gt; --count.value

       return { countWithUnit, increase, decrease }
}

export default {
    setup() {
        const { countWithUnit, increase, decrease } = countWithUnit()

        return {
            countWithUnit,
            increase,
            decrease
        }
    }
}
&lt;/script&gt;</code></pre>
<ul>
<li>count의 기능에 대한 값과 로직 값이 모두 useCount 함수 내에 모여있다.</li>
<li>React의 Hooks와 유사하다.</li>
<li>목적에 맞는 코드를 모듈화하여 유연하고 확장이 가능하다.</li>
</ul>
<h2 id="📌-1-lifecycle">📌 #1. LifeCycle</h2>
<h3 id="기존-vue2">기존 Vue2</h3>
<pre><code class="language-vue">&lt;script&gt;
export default {
    mounted() {

    },
    updated() {

    },
    destroyed() {

    },
}
&lt;/script&gt;</code></pre>
<h3 id="composition-api">Composition-API</h3>
<pre><code class="language-vue">&lt;script&gt;
import {
    onMounted,
    onUpdated,
    onUnmounted
} from &#39;@vue/composition-api

const funcion1 = () =&gt; {
    onMounted(() =&gt; {
        // mounted1
    })
}

const funcion2 = () =&gt; {
    onMounted(() =&gt; {
        // mounted2
    })
}

export default {
    setup() {
        function1()    // mounted1
        function2()    // mounted2

        onUpdated(() =&gt; {
            // updated
        })

        destroyed(() =&gt; {
            // unmounted (destroyed)
        })
    }
}
&lt;/script&gt;</code></pre>
<ul>
<li>기존에는 컴포넌트당 <strong>하나</strong>의 생명주기 훅을 사용했지만, Composition-API를 사용할 경우 <strong>여러개</strong>를 사용할 수 있다.</li>
</ul>
<h3 id="lifecycle">LifeCycle</h3>
<table>
<thead>
<tr>
<th>기존 Vue 2</th>
<th>Composition-API</th>
</tr>
</thead>
<tbody><tr>
<td>beforeCreate</td>
<td>setup</td>
</tr>
<tr>
<td>created</td>
<td>setup</td>
</tr>
<tr>
<td>beforeMount</td>
<td>onBeforeMount</td>
</tr>
<tr>
<td>mounted</td>
<td>onMounted</td>
</tr>
<tr>
<td>beforeUpdated</td>
<td>onBeforeUpdated</td>
</tr>
<tr>
<td>updated</td>
<td>onUpdated</td>
</tr>
<tr>
<td>beforeDestroyed</td>
<td>onBeforeUnmount</td>
</tr>
<tr>
<td>destroyed</td>
<td>onUnmounted</td>
</tr>
<tr>
<td>errorCaptured</td>
<td>onErrorCaptured</td>
</tr>
</tbody></table>
<h2 id="📌-2-반응형-데이터-reactive-ref">📌 #2. 반응형 데이터 (reactive, ref)</h2>
<p>반응형 데이터는 값이 변경됨에 따라 이를 감지하고 해당 값에 종속된 작업(Side Effect)이 수행된다.</p>
<h3 id="기존-vue-2-1">기존 Vue 2</h3>
<p>data() 내부에 선언을 통해 반응형 데이터를 만든다.</p>
<pre><code class="language-vue">&lt;template&gt;
    &lt;div&gt;
        {{ message }}        
    &lt;/div&gt;
&lt;/template&gt;

&lt;script&gt;
export default {
    data() {
        return {
            message: &#39;Hello World!!&#39;
        }
    }
}

&lt;/script&gt;</code></pre>
<h3 id="composition-api-1">Composition-API</h3>
<p>Composition-API의 경우, <strong>2가지 유형(reactive, ref)</strong>으로 반응형 데이터를 만든다.</p>
<pre><code class="language-vue">&lt;script&gt;
import { reactive, ref, computed } from &#39;@vue/composition-api

export default {
    setup() {
        // 1. reactive
        const reactiveValue = reactive({ name: &#39;sooni&#39;})
        reactiveVlaue.name    // sooni

        // 2. ref
        const refValue = ref(10)
        refValue.value    // 10
    }
}
&lt;/script&gt;</code></pre>
<h4 id="reactive">reactive</h4>
<ul>
<li>오직 <strong>객체</strong>만 받는다.</li>
<li>reactive는 인자로 받은 객체와 <strong>완전히 동일한 프록시 객체를 반환</strong>한다.</li>
<li>원본 객체에 접근하는 것과 동일하게 값에 접근 가능하다.</li>
<li>reactive를 통해 생성된 객체는 모두 <strong>깊은(Deep) 감지</strong>를 수행한다.</li>
</ul>
<h4 id="ref">ref</h4>
<ul>
<li>모든 원시타입(Primitive) 값을 포함한 여러가지 타입의 값을 받는다.</li>
<li>value 속성을 통해 접근 및 변경할 수 있다.</li>
</ul>
<blockquote>
<p>ref 객체는 원본 값을 value라는 속성에 담아두고 변경을 감시하는 객체이며, reactive는 원본 객체 자체에 변경을 감지하는 옵저버를 추가하여 그대로 반환한 값이다.</p>
</blockquote>
<h3 id="isref-torefs">isRef, toRefs</h3>
<p>어떤 유형의 값인지 확인하기 위한 <strong>isRef</strong>, reactive 값을 ref 값으로 변환하는 toRefs가 존재한다.</p>
<h4 id="isref">isRef</h4>
<p>ref값과 reactive값에 따라 다르게 처리해야 할 경우 사용한다.</p>
<pre><code class="language-vue">&lt;script&gt;
import { reactive, ref, isRef } from &#39;@vue/composition-api

export default {
    setup() {
        const reactiveValue = reactive({ name: &#39;sooni&#39; })
        const refValue = ref(10)

        const printValue = obj =&gt; {
            console.log(isRef(obj) ? obj.value : obj)
        }
    }
}
&lt;/script&gt;</code></pre>
<ul>
<li>ref 값의 경우 <strong>value</strong> 속성을 통해 데이터에 접근하므로 ref인 경우 obj.value를 출력하고, ref가 아닌 경우(reactive) obj를 그대로 출력하도록 구현된 모습이다.</li>
</ul>
<h4 id="torefs">toRefs</h4>
<pre><code class="language-vue">&lt;script&gt;
import { reactive, ref, isRef } from &#39;@vue/composition-api

const useMousePoint = () =&gt; {
    const pos = reactive({ x: 0, y: 0 })
    return toRefs(pos)    // convert to ref
}

export default {
    setup() {
        const { x, y } = useMousePosition()

        return {
            x,
            y
        }
    }
}
&lt;/script&gt;</code></pre>
<ul>
<li>useMousePoint에 있는 pos 객체의 각각의 값을 사용하고 싶은 경우, reactive를 toRefs를 통해 ref 값으로 변환한다.</li>
</ul>
<h2 id="📌-3-computed">📌 #3. Computed</h2>
<pre><code class="language-vue">&lt;template&gt;
    &lt;div&gt;
        &lt;h1&gt;{{ value2x }}&lt;/h1&gt;
    &lt;/div&gt;
&lt;/template&gt;

&lt;script&gt;
import { ref, computed } from &#39;@vue/composition-api&#39;

export default {
    setup() {
        const myValue = ref(10)
        const value2x = computed(() =&gt; myValue * 2)

        return {
            value2x
        }
    }
}
&lt;/script&gt;</code></pre>
<ul>
<li>getter 함수가 반환하는 값에 대해 <strong>변경 불가능한 반응형 데이터</strong>를 반환한다.</li>
</ul>
<h2 id="참고">참고</h2>
<ul>
<li><a href="https://geundung.dev/102">[Vue 3] Composition API 살펴보기</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[vue3 vite vue-router reload 404 (3)]]></title>
            <link>https://velog.io/@soo-ni/vue3-vite-vue-router-reload-404-3</link>
            <guid>https://velog.io/@soo-ni/vue3-vite-vue-router-reload-404-3</guid>
            <pubDate>Fri, 21 Apr 2023 04:08:45 GMT</pubDate>
            <description><![CDATA[<p>tomcat 설정을 직접 바꾸지 않고 하는 방법</p>
<p>/public/META-INF/context.xml</p>
<pre><code class="language-xml">&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;!DOCTYPE xml&gt;
&lt;Context docBase=&quot;/usr/local/apache/webapps/{name}&quot; path=&quot;/{path}&quot; reloadable=&quot;true&quot;&gt;
    &lt;Valve className=&quot;org.apache.catalina.valves.rewrite.RewriteValve&quot; /&gt;
    &lt;WatchedResource&gt;WEB-INF/rewrite.config&lt;/WatchedResource&gt;
&lt;/Context&gt;
</code></pre>
<p>/public/WEB-INF/rewrite.config</p>
<pre><code class="language-config">RewriteRule ^(.*?)\.(css|images|js)$ - [L]
RewriteRule ^(.*)$ /index.html</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[vue3 vite vue-router reload 404 (2)]]></title>
            <link>https://velog.io/@soo-ni/vue3-vite-vue-router-reload-404-2</link>
            <guid>https://velog.io/@soo-ni/vue3-vite-vue-router-reload-404-2</guid>
            <pubDate>Mon, 10 Apr 2023 07:44:28 GMT</pubDate>
            <description><![CDATA[<p><a href="https://velog.io/@soo-ni/vue3-vite-vue-router-reload-404">이전글</a>에서 web.xml로 해결했다면 근본적인 해결방법을 포스팅한다.</p>
<p>당연히 Apache라고 생각했던 나를 반성하며 tomcat에서 vue-router 적용하는 방법으로 다른 글 포스팅을 통해 따라했다.</p>
<h2 id="tomcat에-적용하기">tomcat에 적용하기</h2>
<p>tomcat-server-9.0.73</p>
<p>#1. server.xml 파일 수정
<code>&lt;Valve className=&quot;org.apache.catalina.valves.rewrite.RewriteValve&quot; /&gt;</code> 추가</p>
<pre><code class="language-xml">      &lt;Host name=&quot;localhost&quot;  appBase=&quot;webapps&quot;
            unpackWARs=&quot;true&quot; autoDeploy=&quot;true&quot;&gt;

        &lt;!-- SingleSignOn valve, share authentication between web applications
             Documentation at: /docs/config/valve.html --&gt;
        &lt;!--
        &lt;Valve className=&quot;org.apache.catalina.authenticator.SingleSignOn&quot; /&gt;
        --&gt;
        &lt;Valve className=&quot;org.apache.catalina.valves.rewrite.RewriteValve&quot; /&gt;

        &lt;!-- Access log processes all example.
             Documentation at: /docs/config/valve.html
             Note: The pattern used is equivalent to using pattern=&quot;common&quot; --&gt;
        &lt;Valve className=&quot;org.apache.catalina.valves.AccessLogValve&quot; directory=&quot;logs&quot;
               prefix=&quot;localhost_access_log&quot; suffix=&quot;.txt&quot;
               pattern=&quot;%h %l %u %t &amp;quot;%r&amp;quot; %s %b&quot; /&gt;

      &lt;/Host&gt;</code></pre>
<p>#2. rewrite.config 파일 만들기
${tomcat_home}/conf/Catalina/localhost/rewrite.config</p>
<pre><code class="language-conf">RewriteCond %{REQUEST_PATH} !-f
RewriteCond %{REQUEST_PATH} !-d
RewriteCond %{REQUEST_URI} !^/images/(.*)$
RewriteCond %{REQUEST_URI} !^/css/(.*)$
RewriteCond %{REQUEST_URI} !^/js/(.*)$
RewriteCond %{REQUEST_URI} !^/favicon.ico
RewriteRule ^/${context}/(.*) /${context}/index.html</code></pre>
<p>context에 내가 설정한 context 이름을 설정해주면 된다.
tomcat에 여러 앱을 배포한다면..? 모든 경로를 다 설정해줘야 하는건가?
애플리케이션경로/WEB-INF/rewrite.config 는 제대로 동작하지 않아서 일단 임시로 이렇게 적용했다.
web.xml 설정해주는 것과 다르게 콘솔창에서 404 에러가 뜨지 않는다.</p>
<h3 id="참고">참고</h3>
<ul>
<li><a href="https://jiurinie.tistory.com/105">https://jiurinie.tistory.com/105</a></li>
<li><a href="https://datajoy.tistory.com/116">https://datajoy.tistory.com/116</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[vue3 vite vue-router reload 404]]></title>
            <link>https://velog.io/@soo-ni/vue3-vite-vue-router-reload-404</link>
            <guid>https://velog.io/@soo-ni/vue3-vite-vue-router-reload-404</guid>
            <pubDate>Sun, 09 Apr 2023 14:03:44 GMT</pubDate>
            <description><![CDATA[<ul>
<li>vue@3</li>
<li>vite@4</li>
<li>vue-router@4</li>
</ul>
<p><code>npm run dev</code>에서는 멀쩡하게 잘 동작하던 router가 deploy만 하면 제대로 동작하지 않는 경우에 임시방편</p>
<p>분명히 router &gt; redirect까지 잘 되는데! 새로고침을 하게되면 index.html을 제대로 못띄워서발생하는 문제인듯 (SPA)</p>
<p><a href="https://router.vuejs.org/guide/essentials/history-mode.html#apache">vue-router</a>에서는 Apache를 사용하는 경우 httpd.conf 를 수정하라 하는데 tomcat을 사용하고 있어서 임시 방편으로 pulic 폴더에 WEB-INF 폴더를 만들고 web.xml을 아래와 같이 만든다.</p>
<p>/public/WEB-INF/web.xml</p>
<pre><code class="language-xml">&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;web-app xmlns=&quot;http://java.sun.com/xml/ns/javaee&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; xsi:schemaLocation=&quot;http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd&quot; version=&quot;3.0&quot; metadata-complete=&quot;true&quot;&gt;
    &lt;display-name&gt;vue-vite project&lt;/display-name&gt;
    &lt;description&gt;vue-vite project&lt;/description&gt;
    &lt;error-page&gt;
        &lt;error-code&gt;404&lt;/error-code&gt;
        &lt;location&gt;/&lt;/location&gt;
    &lt;/error-page&gt;
&lt;/web-app&gt;</code></pre>
<p>그리고 router는 다음과 같이 설정해준다. 웬만하면 404 페이지는 따로 만들어주자.</p>
<p>/src/router/index.ts</p>
<pre><code class="language-typescript">import { createRouter, createWebHistory } from &#39;vue-router&#39;;

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: &#39;/&#39;,
      redirect: &#39;b&#39;,
      component: () =&gt; import(&#39;@/layouts/AView.vue&#39;),
      children: [
        {
          name: &#39;b&#39;,
          path: &#39;b&#39;,
          component: () =&gt; import(&#39;@/views/BView.vue&#39;),
        },
      ],
    },
    {
      path: &#39;/:pathMatch(.*)*&#39;,
      name: &#39;not-found&#39;,
      component: () =&gt; import(&#39;@/views/NotFoundView.vue&#39;),
    },
  ],
});

export default router;</code></pre>
<p>그 후 <code>npm run build</code>하고 deploy 시 새로고침을 해도 제대로 동작하는 것을 볼 수 있다. 물론 console에는 404에러가 뜬다..
=&gt; <a href="https://velog.io/@soo-ni/vue3-vite-vue-router-reload-404-2">다른 방법</a>으로 해결</p>
<p>(404 error, when you reload in a project using vue3, vite, and vue-router)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Vite]]></title>
            <link>https://velog.io/@soo-ni/Webpack-vs.-Vite</link>
            <guid>https://velog.io/@soo-ni/Webpack-vs.-Vite</guid>
            <pubDate>Sun, 09 Apr 2023 13:18:41 GMT</pubDate>
            <description><![CDATA[<p>초기 자바스크립트는 <code>모듈</code>이 없는 채로 만들어졌다. 아주 간단한 동작만 하기 위한 목적으로 만들어졌기 때문에 파일을 여러 개로 만들어서 개발할 수 없었다.</p>
<p>이후에 node가 만들어지고 서버에서 js를 사용할 수 있게 되면서 require 함수, module.exports를 쓰는 <code>CommonJS</code> 방식의 모듈이 만들어졌다.</p>
<p>서버 프로그래밍은 코드를 미리 만들고 파일에서 코드를 동적으로 불러오는 데 제한이 없지만, 브라우저는 js를 저장하지 못하고 매번 불러와야했다. CommonJS와 다르게 비동기가 가능한 <code>Asynchronous Module Definition (AMD)</code>라는 방식으로 브라우저에서 돌아가는 모듈을 만들었다. </p>
<p>하지만 이 방식은 여러 파일을 로딩해야했고, js 파일을 동시에 여러개 호출 시 속도 문제 발생 및 특정 js 파일의 로딩이 지연되면 전체가 늦어지는 문제가 있어 역시 널리 쓰이지 않게 됐다.</p>
<blockquote>
<p>참고<br>CommonJS와 AMD의 JavaScript 모듈화: <a href="https://d2.naver.com/helloworld/12864">https://d2.naver.com/helloworld/12864</a> </p>
</blockquote>
<h2 id="번들">번들</h2>
<h3 id="여러-개-파일을-비동기로-로딩하는-것이-문제라면-그냥-처음부터-파일을-하나로-합쳐서-전달하는건-🤔">여러 개 파일을 비동기로 로딩하는 것이 문제라면 그냥 처음부터 파일을 하나로 합쳐서 전달하는건 🤔?!</h3>
<p>결국 사람들은 여러개의 파일을 나눠서 개발할 수 있도록 모든 파일을 하나로 합친 하나의 <code>번들(Bundle)</code>을 만들었다.</p>
<h3 id="번들링">번들링</h3>
<p>번들링(Bundling)이란 웹 어플리케이션을 제공하기 위해 분리된 모듈을 묶어주는 작업으로, 하나의 번들을 만드는 작업을 의미한다.</p>
<h4 id="app">app</h4>
<pre><code class="language-javascript">// app.js
import { add } from &#39;./common.js&#39;;

console.log(add(1, 2)); // 3

// constant.js
export const add = (a, b) =&gt; {
   return a + b;
}</code></pre>
<h4 id="bundle">bundle</h4>
<pre><code class="language-javascript">function add(a, b) {
  return a + b;
}

console.log(add(1, 2)); // 3</code></pre>
<h3 id="번들러">번들러</h3>
<p>번들러는 번들링을 작업하는 도구로 대표적으로 Webpack, Rollup, Parcel, Browserify 등이 있다.</p>
<p>번들러는 서로 의존성이 있는 여러 JS 파일(모듈)들을 하나의 번들 파일로 묶어주는 역할을 한다. 이 때, 꼭 JS 파일만 가능한 것이 아닌 웹팩의 로더(Loader)를 통해 다양한 타입의 파일들도 번들링 가능하다.</p>
<p>개발자가 직접  모든 JS 파일의 변수를 확인해 중복 선언을 피해야했던 것과 다르게, 번들러를 통해 모듈 단위 개발이 편리해졌다.</p>
<p>그러나, 수천개의 js 모듈이 있는 프로젝트라면 Webpack을 이용해 개발 서버를 실행할 때 굉장히 긴 시간을 기다릴 수 있다 🤯</p>
<h2 id="vite">Vite</h2>
<p>Vite는 Vue.js 팀이 개발한 웹 개발용 빌드 도구이다. 프랑스어로 <code>매우 빠르다</code> 를 의미한다.</p>
<h3 id="그래서-왜-vite를-사용해야하는가">그래서 왜 Vite를 사용해야하는가</h3>
<h4 id="지루할-정도로-길었던-서버-구동">지루할 정도로 길었던 서버 구동</h4>
<p>개발 서버 실행 시 전체 파일을 처음부터 번들링하는 <strong>콜드 스타트</strong> 방식과 다르게 vite는 <strong>dependencies</strong>, <strong>source code</strong> 두가지 카테고리로 나누어 개발 서버를 시작하게 한다.</p>
<p>Vite는 사전 번들링과 ESM(ECMAScript Modules) 기반의 번들링을 통해 빠른 개발을 제공한다. </p>
<p>Webpack개발 서버 실행 시 전체 파일을 처음부터 번들링하지만, vite는 설치와 동시에 수정이 잘 일어나지 않는 의존성 패키지(컴포넌트 라이브러리와 같은 모듈)들을 <strong><a href="https://www.peterkimzz.com/extremely-faster-esbuild-than-webpack/">Esbuild</a></strong>를 사용해 <strong>사전 번들링</strong> 해두고, 수정이 잦은 내부 소스 코드는 브라우저에서 <strong>Native ESM</strong>을 이용해 을 이용해 소스 코드를 제공하도록 하고 있다. 즉, 브라우저가 곧 번들러라는 의미이다. 따라서 조건부 동적 import 이후의 코드는 현재 화면에서 실제로 사용이 되어야만 처리가 된다.</p>
<p><img src="https://velog.velcdn.com/images/soo-ni/post/c032cd60-7d9d-4d8b-909d-1f42e128fe50/image.png" alt=""></p>
<div style="text-align: center;">Webpack 번들링 과정</div>

<p><img src="https://velog.velcdn.com/images/soo-ni/post/9e34c300-5c80-4c1d-b801-486c5a7c77a3/image.png" alt=""></p>
<div style="text-align: center;">Vite 번들링 과정</div>

<blockquote>
<p><strong>ESM (ECMAScript Modules)</strong>
<a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Modules">자바스크립트 네이티브 모듈</a>, 브라우저 자체에서 모듈 기능을 지원하기 위한 스펙이다. import, export를 이용해서 특별한 라이브러리 없이 js 코드를 모듈화해서 작성하면 브라우저가 이해할 수 있게됐다.</p>
</blockquote>
<blockquote>
<p><strong>Esbuild</strong>
go 언어로 작성된 <strong>자바스크립트를 위한 번들러</strong>. 타입스크립트의 타입 체킹이나 프론트엔드 언어 (Vue, Angular) 지원, 핫 모듈 리로딩을 포함한 개발 서버 오픈 등 번들링과 관계 없는 기능들은 일체 없다. 기존 빌드 속도보다 100배는 빠르지만 esbuild를 사용하지 않는 이유이다.</p>
</blockquote>
<h4 id="느린-소스코드-갱신">느린 소스코드 갱신</h4>
<p>기존 번들러 기반 개발 진행 시, 소스 코드 업데이트를 하면 번들링 과정을 다시 거쳐야했다. 이를 위해 <strong>HMR(Hot Module Replacement)</strong>라는 대안이 나왔으나 완전하게 시간을 개선할 순 없었다.</p>
<p>Vite는 ESM을 이용해서 HMR을 지원한다. 어떤 모듈이 수정되면 vite는 수정된 모듈과 관련된 부분만 교체하고, 브라우저에서 해당 모듈을 요청하면 교체된 모듈을 전달한다.</p>
<p>그러나, vite는 개발 환경에서는 빠른 속도를 가진 Esbuild를 사용하지만 빌드 과정에서는 코드 스플리팅(Code-splitting)과 CSS 관련 처리가 미비해 Rollup 번들러를 사용한다. 같은 모듈을 import하는 것을 매번 요청해서 가져오면 비효율적이고, 최적의 퍼포먼스를 내기 위해서는 번들링하는 게 좋기 때문이다.</p>
<pre><code class="language-typescript">// vite.config.ts
rollupOptions: {
  input: {
    main: resolve(__dirname, &#39;./index.html&#39;),
  },
  output: {
    manualChunks: {
      // Split external library from transpiled code.
      vue: [&#39;vue&#39;, &#39;vue-router&#39;, &#39;pinia&#39;],
      vuetify: [&#39;vuetify&#39;, &#39;vuetify/components&#39;, &#39;vuetify/directives&#39;],
    },
  },
},</code></pre>
<blockquote>
<p><strong>HMR (Hot Module Replacement)</strong>
브라우저를 새로 고치지 않아도 웹팩으로 빌드한 결과물이 웹 애플리케이션에 실시간으로 반영될 수 있게 도와주는 설정이다.</p>
</blockquote>
<blockquote>
<p><strong>Code splitting</strong>
번들한 여러 코드 혹은 컴포넌트를 분리하는 것으로, 필요에 따라 특정한 컴포넌트만 로딩하거나 병렬로 로딩할 수 있다.
webpack에서는 <code>const Home = () =&gt; import(/* webpackChunkName: &quot;home&quot; */ &#39;./Home.vue&#39;);</code> 처럼 <code>webpackChunkName</code>를 지정하면 해당 이름으로 파일이 분리되어 빌드된다.</p>
</blockquote>
<h3 id="그래서-vite">그래서 vite?</h3>
<p>항상 모든 기술을 선택하는 데 있어서 무조건적으로 좋은건 없다 생각한다. 이번 프로젝트는 최신 기술을 사용해보는 데 의의를 두기 위해 vite를 선택했다. Webpack과 어떤 점이 다른지 개발 속도가 향상됐다는데 얼마나 향상됐길래 <code>vite</code>라는 이름을 사용하는지 궁금해서 사용했다.</p>
<p>build가 훨씬 빠른 장점이 있고, 기존 프로젝트에서 webpack을 vite로 migration할 수 있는지 여부는 확인해보면 좋을 것 같다.</p>
<h2 id="참고">참고</h2>
<ul>
<li><a href="https://humanwater.tistory.com/2">https://humanwater.tistory.com/2</a></li>
<li><a href="https://tech.wonderwall.kr/articles/Vite/">https://tech.wonderwall.kr/articles/Vite/</a></li>
<li><a href="https://tech.cloudmt.co.kr/2023/02/23/build-fast-webpack-to-vite-migratiokn/">https://tech.cloudmt.co.kr/2023/02/23/build-fast-webpack-to-vite-migratiokn/</a></li>
<li><a href="https://vitejs-kr.github.io/guide/why.html">https://vitejs-kr.github.io/guide/why.html</a></li>
<li><a href="https://yozm.wishket.com/magazine/detail/1620/">https://yozm.wishket.com/magazine/detail/1620/</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[vue3 + vite + vuetify3 + typescript + pinia]]></title>
            <link>https://velog.io/@soo-ni/vue3-vite-vuetify3-pinia</link>
            <guid>https://velog.io/@soo-ni/vue3-vite-vuetify3-pinia</guid>
            <pubDate>Sun, 09 Apr 2023 12:34:19 GMT</pubDate>
            <description><![CDATA[<p>간단한 프로젝트를 만들일이 생겨 평소에 하고싶었던 기술들을 사용해본 과정을 적어본다.</p>
<h2 id="🔨사용한-기술">🔨사용한 기술</h2>
<ol>
<li>Vue3</li>
<li>Vite</li>
<li>Vuetify3</li>
<li>typescript</li>
<li>pinia</li>
</ol>
<p>react로 개발해보고 싶었지만 시간이 부족해서 기존에도 사용했던 vue를 사용했다. 
너무 느렸던.. Webpack을 대신해 속도가 엄청 빨라졌다는 Vite도 이번에 사용해봤다. 확실히 속도면에서 엄청나게 향상된 점을 알 수 있다.
매번 릴리즈 될 것이라는 Vuetify3도 사용해보고 기존에 사용하고 있는 typescript, pinia까지 적용했다.</p>
<h2 id="📌아마도-목차">📌아마도 목차</h2>
<ol>
<li>Webpack vs. Vite
: Webpack대신 사용한 Vite란?</li>
<li>Vue3
: Vue2와의 차이</li>
<li>Vuetify3
: Vuetify2와의 차이</li>
<li>typescript
: javascript와의 차이 (기존에도 typescript 사용중)</li>
<li>pinia
: vuex와의 차이 (기존에도 pinia 사용중)</li>
<li>자잘한 이슈들
: 🤦...</li>
</ol>
]]></description>
        </item>
    </channel>
</rss>