<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>코드노트</title>
    <link>https://codeno-te.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Fri, 8 May 2026 07:14:01 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>코드노트</managingEditor>
    <image>
      <title>코드노트</title>
      <url>https://tistory1.daumcdn.net/tistory/5276815/attach/020c087e332445f384fa4784eff6f7d0</url>
      <link>https://codeno-te.tistory.com</link>
    </image>
    <item>
      <title>DP활용 문제, UglyNumbers 레벨3 풀이</title>
      <link>https://codeno-te.tistory.com/254</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;문제&lt;/b&gt;&lt;/h4&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;심술쟁이 수는 2,3,5의 곱으로 만들 수 있는 수이다. 다음과 같은 순서의 수가 11개의 심술쟁이 수이다.&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #eeeeee;&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p style=&quot;color: #333333;&quot; data-ke-size=&quot;size16&quot;&gt;1,2,3,4,5,6,8,9,10,12,15,....&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 수는 1로 시작하도록 한다. 입력은 받지 않고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;lt;number&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;에 1500번째 심술쟁이 수가 출력되게 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;출력&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1773234997249&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;859963392&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 풀려면 우선 DP를 알고 넘어가야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DP(Dynamic Programming)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 작은 문제의 답을 저장해두고, 그걸 재사용해서 큰 문제를 푸는 방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 가장 큰 특징은 한번 계산한 작은 문제는 다시 계산하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 매번 처음부터 계산하지 않고 이전에 구한 결과를 배열 같은 데 저장해두고 꺼내 쓴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시를 볼때 피보나치로 보면 쉽게 이해할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1773235158546&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;f(1) = 1
f(2) = 1
f(3) = f(2) + f(1)
f(4) = f(3) + f(2)
...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- f(5)를 구하려면 f(4), f(3)이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 그런데 f(4), f(3)도 이미 전에 구했으면 또 계산할 필요가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 배열에 저장한다.&lt;/p&gt;
&lt;pre id=&quot;code_1773235394380&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dp[1] = 1
dp[2] = 1
dp[3] = dp[2] + dp[1]
dp[4] = dp[3] + dp[2]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸로 DP를 이해할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제를 보고 1차원적으로 이렇게 생각할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1500번째의 숫자를 찾는거니깐 그럼 2,3,5를 while로 순회하면서 배열에 하나씩 넣고 1500번째 때 꺼내면 되지 않을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그것도 맞다. 브루트포스로 진행한다고하면 모든 경우의 수를 하나하나 시도해볼 수 있기도 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 만약 이렇게 동작시킨다면? 1부터 약 8억 5천만까지 검사해야 답을 구할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1773230558693&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function factor235(n) {
    while (n % 2 === 0) n /= 2
    while (n % 3 === 0) n /= 3
    while (n % 5 === 0) n /= 5

    return n
}

function uglyNumbers(n) {
    const arr = [1]
    let cnt = 1
    while (arr.length !== n + 1) {
        const isUgly = factor235(cnt) === 1
        if (isUgly) {
            arr.push(cnt)
        }
        cnt++
    }
    return arr.slice(-1)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;&lt;b&gt;그럼 DP방식으로 진행한다면?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ugly Number의 예시를 보면&lt;/p&gt;
&lt;pre id=&quot;code_1773239891555&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;1
2
3
4
5
6
8
9
10
12
15
...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1 다음에 오는 수부터 확인해보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이미 구한 수들에 2, 3, 5를 곱한 후보로 들어가게 해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 다음 숫자의 후보는&lt;br /&gt;1에 2,3,5를 곱한 [2, 3, 5] 중 하나이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 작은 2&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1773239991499&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ugly = [1]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;배열로 관리를 하게 된다면 1을 하나 가지고 있고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 수의 후보인&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1 * 2 = 2&lt;/li&gt;
&lt;li&gt;1 * 3 = 3&lt;/li&gt;
&lt;li&gt;1 * 5 = 5&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 2, 3, 5를 곱한 수 중 제일 작은 2를 추가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼&lt;/p&gt;
&lt;pre id=&quot;code_1773240068831&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ugly = [1, 2]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 다음 수의 후보는&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;2 * 2 = 4&lt;/li&gt;
&lt;li&gt;1 * 3 = 3&lt;/li&gt;
&lt;li&gt;1 * 5 = 5&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4, 3, 5 호부중에서 제일 작은 3을 추가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 계속 가게 된다고 했을 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포인터가 3개가 필요하다&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;pointer2 : 2를 곱할 차례인 ugly의 배열 index 위치&lt;/li&gt;
&lt;li&gt;pointer3 : 3를 곱할 차례인 ugly의 배열 index 위치&lt;/li&gt;
&lt;li&gt;pointer5 : 5를 곱할 차례인 ugly의 배열 index 위치&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;이제 이 포인터로 따라가보자&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1773240299477&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const ugly = [1]
let pointer2 = 0
let pointer3 = 0
let pointer5 = 0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기값부터 시작&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1단계&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773240336433&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ugly[pointer2] * 2 // 1 * 2 = 2
ugly[pointer3] * 3 // 1 * 3 = 3
ugly[pointer5] * 5 // 1 * 5 = 5&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 여기서 가장 작은값은 2&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- ugly배열에 2를 추가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- pointer2는 +1을 시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2단계&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773240479929&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ugly[pointer2] * 2 // 2 * 2 = 4
ugly[pointer3] * 3 // 1 * 3 = 3
ugly[pointer5] * 5 // 1 * 5 = 5&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 여기서 가작 작은 값은 3&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- ugly배열에 3을 추가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;pointer3은 +1을 시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3단계&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773240525165&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ugly[pointer2] * 2 // 2 * 2 = 4
ugly[pointer3] * 3 // 2 * 3 = 6
ugly[pointer5] * 5 // 1 * 5 = 5&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 여기서 가작 작은 값은 4&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- ugly배열에 4을 추가&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;pointer2은 +1을 시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4단계&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773240564871&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ugly[pointer2] * 2 // 3 * 2 = 6
ugly[pointer3] * 3 // 2 * 3 = 6
ugly[pointer5] * 5 // 1 * 5 = 5&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 여기서 가작 작은 값은 5&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- ugly배열에 5을 추가&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;pointer5은 +1을 시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;5단계 중요&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773240628927&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ugly[pointer2] * 2 // 3 * 2 = 6
ugly[pointer3] * 3 // 2 * 3 = 6
ugly[pointer5] * 5 // 2 * 5 = 10&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 여기서 가작 작은 값은 6&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- ugly배열에 6을 추가&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;pointer2, pointer3이 같은 6을 가지고 있다. 지금까지 1개의 작은수만 있다보니 하나의 포인터만 증가시켰지만 2개의 포인터를 각각 증가시켜줘야한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;전체 흐름을 정리해보자&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ugly 배열에서 1부터 시작한다.&lt;/li&gt;
&lt;li&gt;계산을 진행하는 방법은 3개의 포인트를 가지고 있다.(n2, n3, n5)&lt;br /&gt;1부터 시작하여 2, 3, 5를 곱한 수의 가장 작은 값을 ugly 배열에 추가한다.&lt;/li&gt;
&lt;li&gt;그 값을 만든 포인터(index)는 1추가 시킨다. (가장 작은 값을 구해야하기에 같은 후보를 만들지 않도록 증가 시키며 계산)&lt;/li&gt;
&lt;li&gt;ugly 길이가 n이 될때까지 반복한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지의 예시처럼 순회를 하며 값을 저장하고 포인터를 변경해가면서 n번째까지 while문으로 순회하면 우리가 찾고자하는 1500번 만 진행하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1773240823060&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function uglyNumber(n) {
    const ugly = [1];

    let p2 = 0;
    let p3 = 0;
    let p5 = 0;

    while (ugly.length &amp;lt; n) {
        const next2 = ugly[p2] * 2;
        const next3 = ugly[p3] * 3;
        const next5 = ugly[p5] * 5;

        const next = Math.min(next2, next3, next5);

        ugly.push(next);

        if (next === next2) p2++;
        if (next === next3) p3++;
        if (next === next5) p5++;
    }

    return ugly[n - 1];
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Code note/자바스크립트 알고리즘 문제풀이</category>
      <category>알고리즘</category>
      <category>자바스크립트</category>
      <category>코딩테스트</category>
      <author>코드노트</author>
      <guid isPermaLink="true">https://codeno-te.tistory.com/254</guid>
      <comments>https://codeno-te.tistory.com/254#entry254comment</comments>
      <pubDate>Wed, 11 Mar 2026 23:56:58 +0900</pubDate>
    </item>
    <item>
      <title>Happy Number 레벨2 문제풀이</title>
      <link>https://codeno-te.tistory.com/253</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제&lt;/b&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;양의 정수 S0 의 각 아라비아 숫자들의 제곱의 합으로 양의 정수 S1을 만든다고 하자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;동일한 방법이라면, S1으로 S2를 만들 수 있고, 이 후로도 계속 만들 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;만약 어떤 i(i &amp;ge; 1)에 대해서 Si = 1이라면, 최초의 S0를 Happy Number라고 부른다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Happy Number가 아닌 수를 Unhappy Number라고 부른다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어, 7에서 시작하게 되면 다음과 같은 일련의 순서를 가지게 되며&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;7, 49(=7^2), 97(=4^2+9^2), 130(=9^2+7^2), 10(=1^2+3^2), 1(=1^2),&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서 7은 즐거운 수이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 4는&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;4, 16(4^2), 37(1^2+6^2), 58(3^2+7^2), 89(5^2+8^2), 145(8^2+9^2),&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;42(1^2+4^2+5^2), 20(4^2+2^2), 4(2^2)의 순서로 반복되므로 Unhappy Number이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;br /&gt;입력&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #fafafa; color: #333333;&quot; data-text-less=&quot;닫기&quot; data-text-more=&quot;더보기&quot; data-ke-type=&quot;moreLess&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;첫 라인은 인풋 케이스의 수 n이 주어지며 이후 n라인의 케이스가 주어진다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;각 테스트 케이스는 한 개의 양의 정수 N으로 구성되며 N은 10^9 보다 작다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;br /&gt;출력&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #fafafa; color: #333333;&quot; data-text-less=&quot;닫기&quot; data-text-more=&quot;더보기&quot; data-ke-type=&quot;moreLess&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;출력은 주어진 수 N이 Happy Number인지 Unhappy Number인지 여부에 따라 다음과 같이 출력한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;N이 Happy Number라면 &amp;ldquo;Case #p: N is a Happy number.&amp;rdquo;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;N이 Unhappy Number라면 &amp;ldquo;Case #p: N is an Unhappy number.&amp;rdquo;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;p는 1부터 시작하는 케이스의 번호이며 각각의 케이스는 한 줄에 결과를 표시한다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;input / output&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773213677314&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;3 //  3 is an Unhappy number.
7 // 7 is a Happy number.
4 // 4 is an Unhappy number.
13 // 7 is a Happy number.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;채점&amp;nbsp;기준&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;작성한 프로그램은 각각의 테스트케이스에 대해서 올바른 결과를 출력하여야 한다.&lt;/li&gt;
&lt;li&gt;입력 후 결과 출력까지 걸리는 시간이 빠르면 빠를수록 좋다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;(* 이번 문제는 정확한 결과만큼이나 속도도 중요합니다)&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;프로그램에서 사용한 자료구조 및 알고리즘이 적절하여야 한다.&lt;/li&gt;
&lt;li&gt;그 외 일반적인 코드 구조, 스타일, 에러/예외 처리 등이 적절할수록 좋다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;UVA 10591 문제이고, 이스트소프트 개발직군 샘플문제로 공개된 자료를 가져왔습니다. 채점기준을 준수한다면 난이도는 좀 더 상승될 것 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;채점기준에서 속도가 중요하다 했는데, 대부분의 수들이 길지 않은 싸이클로 반복하다보니 처리시간은 순식간입니다. 수십, 수백만개의 Case에 대해 처리시간을 체크해봐야 차이가 날것 같습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;풀이&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773213796876&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function solution(n) {
    const list = [0,1,4,9,16,25,36,49,64,81]
    let res = n
    const s = new Set([n])

    while(true) {
        let arr = String(res).split(&quot;&quot;)
        res = arr.reduce((a, c) =&amp;gt; a + list[c], 0)

        if (res === 1) {
            return `${n} is a Happy number.`
        }
        
        if (s.has(res)) {
            return `${n} is an Unhappy number.`
        }


        s.add(res)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 문제를 정리해보면 각 숫자들을 한자리수로 확인해서 제곱값들을 더해가면서 값이 1이 되는지 여부를 판단하면된다. 그 외 1이되지 않거나 반복이 된다면 Unhappy number이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 처음에 접근할때 Set을 사용하지 않고 array로 진행했다. includes로 여부를 확인하면서 순회하도록 했는데 채점기준에서 조금 더 속도를 빠르게 하기위해 Set으로 변경했다.&lt;br /&gt;- 여기서 list또한 객체로 만들어서 각 키값으로 0~9까지 접근하게 했지만 어차피 인덱스로 접근하는거라 각 수의 제곱 또한 배열로 관리하도록 하였다.&lt;/p&gt;</description>
      <category>Code note/자바스크립트 알고리즘 문제풀이</category>
      <author>코드노트</author>
      <guid isPermaLink="true">https://codeno-te.tistory.com/253</guid>
      <comments>https://codeno-te.tistory.com/253#entry253comment</comments>
      <pubDate>Wed, 11 Mar 2026 16:43:12 +0900</pubDate>
    </item>
    <item>
      <title>React의 useTransition 가이드 정리, 동작 흐름 확인해보기</title>
      <link>https://codeno-te.tistory.com/252</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;useTransition이란?&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;useTranstion은 React18에서 도입된 Concurrent Rendering 기능의 일부로, UI 반응성과 성능을 개선하기 위해 &lt;br /&gt;&quot;덜 중요한 작업을 늦춰서 처리&quot; 하는 훅이다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 빠르게 반응을 체감해야하는 작업이 있다면 가벼운 작업과 무거운 작업을 분리 할 수 있으며, UI의 지연(Lag)을 줄이는데 효과가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 가벼운 작업 예시 : 버튼 클릭, 입력 필드 타이핑 등&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 무거운 작업 예시 : 목록 필터링, 차트 업데이트 등&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;언제 사용할까?&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;대량 데이터 필터링, 렌더링, 정렬&lt;/li&gt;
&lt;li&gt;애니메이션, 차트, 목록 등 복잡한 UI 업데이트&lt;/li&gt;
&lt;li&gt;사용자의 입력 반응은 빠르게 결과는 나중에 처리하고 싶을 때&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용하지 않아도 되는 경우&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;상태 업데이트가 가벼운 경우&lt;/li&gt;
&lt;li&gt;비동기 fetch 작업 위주로 구성될 때&lt;/li&gt;
&lt;li&gt;전체 앱이 단순한 상태 흐름을 가질 때&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용법은 간단하다.&lt;/p&gt;
&lt;pre id=&quot;code_1745139802441&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const [isPending, startTransition] = useTransition();

startTransition(() =&amp;gt; {
  // 우선순위가 낮은 상태 업데이트
  setSomeState(data);
});&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;isPending : 현재 transiton중인 상태를 나타낸다. ( true / false )&lt;/li&gt;
&lt;li&gt;startTransition : 작업을 시작하는 함수&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;예제 실시간 검색 필터링&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745140291792&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState, useTransition } from 'react';

const largeList = [...Array(10000)].map((_, i) =&amp;gt; ({
  id: i,
  label: `Item ${i}`,
}));

function SearchInput() {
  const [input, setInput] = useState('');
  const [filteredList, setFilteredList] = useState([]);
  const [isPending, startTransition] = useTransition();

  const handleChange = (e) =&amp;gt; {
    const keyword = e.target.value;
    setInput(keyword);

    startTransition(() =&amp;gt; {
      const filtered = largeList.filter(item =&amp;gt;
        item.label.toLowerCase().includes(keyword.toLowerCase())
      );
      setFilteredList(filtered);
    });
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;input value={input} onChange={handleChange} /&amp;gt;
      {isPending &amp;amp;&amp;amp; &amp;lt;p&amp;gt;검색 중...&amp;lt;/p&amp;gt;}
      &amp;lt;ul&amp;gt;
        {filteredList.map(item =&amp;gt; &amp;lt;li key={item.id}&amp;gt;{item.label}&amp;lt;/li&amp;gt;)}
      &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;검색어가 입력될 때마다&amp;nbsp; handleChange가 실행되지만 setInput으로 input상태를 업데이트 한다.&lt;/li&gt;
&lt;li&gt;startTansition을 통해서 입력 반영 이후에 필터링은 나중에 처리되도록 진행한다.&lt;/li&gt;
&lt;li&gt;이때 isPending 값을 통해서 검색중이라는 UI를 유저에게 보여지게 할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;React Scheduler&lt;/b&gt;가 &lt;u&gt;&lt;b&gt;직접 우선순위를 조절하고 렌더링 시점까지 관리&lt;/b&gt;&lt;/u&gt;된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 &lt;b&gt;debounce를 사용해도 되는거 아니야?&lt;/b&gt; 라고 할 수 있지만&lt;/p&gt;
&lt;pre id=&quot;code_1745142540475&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState, useMemo, useEffect } from 'react';
import debounce from 'lodash.debounce';

function SearchDebounced({ data }) {
  const [input, setInput] = useState('');
  const [filtered, setFiltered] = useState([]);

  const debouncedSearch = useMemo(() =&amp;gt;
    debounce((keyword) =&amp;gt; {
      const result = data.filter(item =&amp;gt;
        item.label.toLowerCase().includes(keyword.toLowerCase())
      );
      setFiltered(result);
    }, 300), [data]
  );

  useEffect(() =&amp;gt; {
    debouncedSearch(input);
  }, [input, debouncedSearch]);

  return (
    &amp;lt;&amp;gt;
      &amp;lt;input value={input} onChange={(e) =&amp;gt; setInput(e.target.value)} /&amp;gt;
      &amp;lt;ul&amp;gt;
        {filtered.map((item) =&amp;gt; &amp;lt;li key={item.id}&amp;gt;{item.label}&amp;lt;/li&amp;gt;)}
      &amp;lt;/ul&amp;gt;
    &amp;lt;/&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이렇게 debounce를 사용하게 되면 300ms동안 입력이 멈추면 그때 filter로직이 실행되게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 필터 함수 호출 횟수가 줄어들기 때문에 성능에는 좋지만 입력 즉시 반응하지 않기 때문에 UX에서는 느린걸 느낄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 그 외에도 로딩 상태 추가하기 위해서는 상태하나를 더 추가해야하는 번거로움이 필요하다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;그럼 debounce말고 무조건적인 useTransition이 좋은걸까?&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그건 또 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 외부 api를 줄여야하는 경우가 있다면?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-&amp;gt; 검색시마다 api요청을 진행하게 된다면 debounce를 사용해서 &lt;u&gt;api호출 횟수를 줄이는게&lt;/u&gt; 바람직 하다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 말하지만 &lt;u&gt;&lt;b&gt;useTransition은 단순하게 지연을 시키는 것이 아니라&lt;/b&gt;&lt;/u&gt; React 내부에서 상태 업데이트의 우선순위를 낮춰서 UI가 끊기지 않게 해주는 스케줄링 기술이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그렇다면 코드레벨을 통해서 useTransition의 동작 흐름을 살펴 보자!&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1745142905253&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const [isPending, startTransition] = useTransition();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- useTransition의 리턴하는 구조이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1745142934155&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 내부 구조 유추 형태 (단순화)
function useTransition() {
  const [isPending, setPending] = useState(false);

  function startTransition(callback) {
    setPending(true);
    
    // 실제 업데이트 예약
    unstable_runWithPriority(LowPriority, () =&amp;gt; {
      callback();
      setPending(false);
    });
  }

  return [isPending, startTransition];
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 내부적으로 실행되는 코드를 단순하게 살펴보면 React는 내부적으로 state의 queue 상태를 가진 객체를 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 실제로는 react-reconciler&lt;span&gt;, &lt;/span&gt;scheduler&lt;span&gt;, &lt;/span&gt;react-dom 패키지가 함께 작동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1745143007368&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// scheduler 구현 일부를 단순화한 예
unstable_runWithPriority(LowPriority, () =&amp;gt; {
  // &amp;rarr; 이 안에 있는 state 업데이트는 낮은 우선순위로 처리됨
  setFilteredList(filteredList);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- startTransition으로 감싸면 React는 그 안의 state update를 &lt;b&gt;&quot;low priority update&quot;&lt;/b&gt;로 등록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이렇게 진행되면 즉시 렌더링 되는게 아니라 긴급(urgent)한 작업들이 먼저 끝나기를 기다리게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 여기서 핵심은 Scheduler가 이 업데이트를 바로 처리하지 않는 다는 것이다. React 18을 보면 새롭게 도입된 &lt;b&gt;Concurrent Mode&lt;/b&gt;의 핵심 기능 중 하나이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;React의 작업 우선 순위&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1745143198790&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 내부 우선순위 enum
export const ImmediatePriority = 1;
export const UserBlockingPriority = 2;
export const NormalPriority = 3;
export const LowPriority = 4;
export const IdlePriority = 5;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 보통 setState는 Normal ~ UserBlocking 수준으로 바로 처리되지만, startTransition()안에 있는 작업은 &lt;b&gt;LowPriority&lt;/b&gt;로 들어간다.&lt;/p&gt;
&lt;pre id=&quot;code_1745143352492&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function flushWork(hasTimeRemaining, currentTime) {
  // 시간이 남을 때만 low priority 작업 실행
  if (hasTimeRemaining || currentTime &amp;gt; deadline) {
    // do work
  } else {
    // 중단하고 urgent 작업 대기
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 React 렌더링 중간에 지금 여유가 있는지 확인하고 Idle time이나 frame time이 안남았을때까지 기다렸다가 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;동작 순서&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;[사용자&amp;nbsp;입력]&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;darr;&lt;br /&gt;(setInput)&amp;nbsp;&amp;nbsp;--&amp;gt;&amp;nbsp;High&amp;nbsp;Priority&amp;nbsp;&amp;rarr;&amp;nbsp;즉시&amp;nbsp;렌더링&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;darr;&lt;br /&gt;(startTransition&amp;nbsp;안의&amp;nbsp;setFilteredList)&amp;nbsp;&amp;rarr;&amp;nbsp;Low&amp;nbsp;Priority&amp;nbsp;&amp;rarr;&amp;nbsp;나중에&amp;nbsp;렌더링&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;debounce, useTransition 차이점 정리&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;&lt;span&gt;항목&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;&lt;span&gt;debounce&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;&lt;span&gt;useTransition&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;&lt;span&gt;실행 타이밍&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;delay 이후 1번 실행&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;상태는 즉시 반영, 렌더링은 나중에&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;&lt;span&gt;목적&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;함수 호출/요청 빈도 줄이기&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;렌더링 병목 분산 및 반응성 개선&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;&lt;span&gt;사용 범위&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;네트워크 요청 최적화, 단순 함수 제어&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;React 렌더링 우선순위 스케줄링&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;&lt;span&gt;UX에 미치는 영향&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;입력에 약간의 지연 있음&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;입력은 즉시 반영됨&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;&lt;span&gt;내부 메커니즘&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;setTimeout&lt;span&gt;, &lt;/span&gt;clearTimeout&lt;span&gt; 기반&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;React Scheduler + Fiber 기반&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이제 UI적으로 왜 유리한지 이해 되는것 같다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;입력 값 변화는 즉시 화면에 반영된다!&lt;/li&gt;
&lt;li&gt;그러나 filteredList는 나중에 반영되기 때문에 렌더링 비용이 분산된다!&lt;/li&gt;
&lt;li&gt;렌더링 프레임을 블로킹하지 않고 부드러운 사용자 경험을 제공할 수 있다!&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;마무리 해보자면&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;useTransition은&lt;b&gt; React 내부의 Scheduler와 Fiber구조를 활용해 렌더링 우선순위를 조절하는 비동기 렌더링 기술&lt;/b&gt;이다.&lt;/li&gt;
&lt;li&gt;startTransition 내부의 업데이트는 낮은 우선순위로 처리되어, 사용자 입력에 대한 UI응답을 빠르게 하고, 무거운 렌더링은 나중에 처리한다.&lt;/li&gt;
&lt;li&gt;debounce와 달리, &lt;b&gt;호출 타이밍 제어가 아니라 렌더링 제어에 특화된 방식&lt;/b&gt;이다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Code note/리액트</category>
      <category>Hook</category>
      <category>react</category>
      <category>react hook</category>
      <category>react scheduler</category>
      <category>useTransition</category>
      <author>코드노트</author>
      <guid isPermaLink="true">https://codeno-te.tistory.com/252</guid>
      <comments>https://codeno-te.tistory.com/252#entry252comment</comments>
      <pubDate>Sun, 20 Apr 2025 19:27:47 +0900</pubDate>
    </item>
    <item>
      <title>ethers.js, Web3Provider와 JsonProvider의 차이점 및 사용법 정리</title>
      <link>https://codeno-te.tistory.com/251</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;ethers.js&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이더리움 블록체인과 상호작용 하기 위한 javascript 라이브러리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 가볍고 모듈화가 되어있어 Node.js 브라우저 환경에서 모두 사용 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Provider, Signer, Contract 등 유틸리티를 제공하여 블록체인 애플리캐이션 개발을 단순화할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- DApp 개발 시 Ethereum 네트워크와 상호작용하려면 ethers.js 라이브러리의 Provider를 사용해야 한다.&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Provider의 개념과 역할&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Provider는 이더리움 네트워크와 데이터를 교환화기 위한 인터페이스이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;역할: 네트워크 상태 조회, 블록 정보 가져오기, 트랜잭션 확인 등&lt;/li&gt;
&lt;li&gt;서명이나 전송은 직접 처리하지 않는다.&lt;/li&gt;
&lt;li&gt;ethers.js에서 여러 종류의 Provider를 지원하며, 가장 일반적으로 사용되는 두 가지가 Web3Provider와 JsonRpcProvider이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Web3Provider&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1735006908913&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;new ethers.providers.Web3Provider&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;브라우저 환경에서 사용되는 Ethereum 지갑(MetaMask 등)의 window.ethereum 객체를 래핑하여 네트워크와 상호작용할 수 있게 도와주는 Provider이다.&lt;/li&gt;
&lt;li&gt;Web3지갑이 제공하는 JSON-RPC 인터페이스를 통해 네트워크와 통신한다.&lt;/li&gt;
&lt;li&gt;사용자의 서명 및 트랜잭션 전송을 지갑이 처리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;사용자 지갑과 연동&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;브라우저 지갑을 통해 사용자의 계정 정보를 가져오거나, 트랜잭션에 서명할 수 있다.&lt;/li&gt;
&lt;li&gt;사용자는 트랜잭션을 전송하기 전에 MetaMask팝업을 통해 직접 승인 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;네트워크 설정&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지갑에서 선택된 네트워크를 사용한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex) MetaMask에서 Rinkeby 네트워크로 설정하면, Web3Provider도 동일한 네트워크를 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사용 사례&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자의 지갑을 통해 트랜잭션을 서명하거나, 지갑 주소를 가져오는 작업을 실행한다.&lt;/li&gt;
&lt;li&gt;사용자가 메타마스크와 같은 지갑을 사용할 때 적합하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;생성 및 사용 방법&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1734998064877&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { ethers } from &quot;ethers&quot;;

// 브라우저의 Ethereum 객체(MetaMask 등)를 사용해 Web3Provider 생성
const provider = new ethers.providers.Web3Provider(window.ethereum);

// 사용자의 지갑과 연결된 계정 가져오기
const signer = provider.getSigner();
const address = await signer.getAddress();
console.log(&quot;사용자 주소:&quot;, address);

// 트랜잭션 서명 및 전송
const tx = await signer.sendTransaction({
    to: &quot;0xRecipientAddress&quot;,
    value: ethers.utils.parseEther(&quot;0.1&quot;),
});
console.log(&quot;트랜잭션 전송:&quot;, tx.hash);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 사용자 지갑을 통해 트랜잭션을 쉽게 서명할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 사용자가 네트워크를 직접 선택하고 관리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- MetaMask와 같은 브라우저 지갑이 설치되어 있어야만 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 지갑 팝업에서 사용자가 직접 서명을 승인해야 하기 때문에 자동화 작업에는 적합하지 않다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;JsonRpcProvider&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1735007306648&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;new ethers.providers.JsonRpcProvider&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;동작방식&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JsonRpcProvider는 특정 RPC URL을 사용해 네트워크와 직접 통신한다.&lt;/li&gt;
&lt;li&gt;Infura, Alchemy, 또는 개인 노드를 통해서 네트워크에 연결한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;직접 RPC url 설정&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 RPC 엔드포인트를 통해 네트워크에 직접 연결한다.&lt;/li&gt;
&lt;li&gt;MetaMask와 같은 브라우저 지갑 없이도 사용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사용 사례&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;네트워크 데이터를 읽는 작업 (ex: 계정 잔액 조회, 블록 정보 가져오기)&lt;/li&gt;
&lt;li&gt;트랜잭션 서명은 프론트엔드에서 직접 수행할 수 없으므로, 보통 데이터 조회 용도로 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;생성 및 사용 방법&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1735007575929&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { ethers } from &quot;ethers&quot;;

// RPC 서버 URL을 사용해 JsonRpcProvider 생성
const provider = new ethers.providers.JsonRpcProvider(&quot;https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID&quot;);

// 최신 블록 번호 가져오기
const blockNumber = await provider.getBlockNumber();
console.log(&quot;최신 블록 번호:&quot;, blockNumber);

// 특정 주소의 잔액 가져오기
const balance = await provider.getBalance(&quot;0xYourAddress&quot;);
console.log(&quot;잔액:&quot;, ethers.utils.formatEther(balance));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- MetaMask가 설치되어 있지 않아도 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 네트워크 데이터를 읽는 작업에 적합하며, 빠르고 효율적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 트랜잭션을 서명하거나 사용자의 계정을 가져오는 작업은 수행할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- RPC url(API 키 포함)이 노출되면 악용될 위험이 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Web3Provider와 JsonRpcProvider 차이점&lt;/b&gt;&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 114px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;&lt;b&gt; Web3Provider&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;&lt;b&gt;JsonRpcProvider&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;&lt;b&gt;지갑 연동&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;&lt;span&gt;사용자의 브라우저 지갑(MetaMask 등)과 연동&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;&lt;span&gt;지갑 없이 RPC 서버와 직접 통신&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;&lt;b&gt;트랜잭션 서명&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;&lt;span&gt;사용자 지갑에서 직접 서명 요청&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;&lt;span&gt;서명 작업 불가능 (백엔드 또는 별도 서명 필요)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;&lt;b&gt;네트워크 설정&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;&lt;span&gt;MetaMask의 네트워크 설정을 따름&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;&lt;span&gt;개발자가 지정한 RPC URL 사용&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;&lt;b&gt;사용 사례&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;지갑에서 자동 관리&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;&lt;span&gt;네트워크 데이터 읽기 (잔액, 블록 정보 등)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;&lt;b&gt;의존성&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;&lt;span&gt;트랜잭션 서명, 사용자의 계정 정보 조회&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px;&quot;&gt;&lt;span&gt;별도 의존성 없음 (RPC 서버만 필요)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;상황에 맞는 Provider 선택 기준&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Web3Provider를 사용하는 경우 - &lt;b&gt;사용자의 서명이 필요한 작업&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- MetaMask와 같은 브라우저 지갑이 설치되어 있는 사용자를 대상으로 하는 DApp&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 사용자가 트랜잭션을 직접 서명해야하는 경우 *ex) 토큰 전송 및 NFT 구매&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. JsonRpcProvider를 사용하는 경우 - &lt;b&gt;데이터를 읽기만 하는 작업&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 사용자가 MetaMask를 설치하지 않아도 데이터를 읽을 수 있는 기능이 필요할 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 네트워크 정보를 조회하거나 블록체인 상태를 보여주는 애플리케이션 *ex) 토큰 가격 조회 및 특정 주소의 잔액 또는 트랜잭션 기록 표시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Code note/web3</category>
      <author>코드노트</author>
      <guid isPermaLink="true">https://codeno-te.tistory.com/251</guid>
      <comments>https://codeno-te.tistory.com/251#entry251comment</comments>
      <pubDate>Tue, 24 Dec 2024 11:46:20 +0900</pubDate>
    </item>
    <item>
      <title>wagmi web3 지갑 연동 라이브러리 정리</title>
      <link>https://codeno-te.tistory.com/250</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Wagmi&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- react, typescript를 지원하는 라이브러리로 이더리움 어플리케이션을 구축할 때 지갑 연결 및 체인 데이터 관리에 있어서 직관적이고 효율적으로 처리할 수 있도록 도와주는 라이브러리이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;주요 특징&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- React Hook 기반 설계를 통해 리액트에서 쉽게 사용 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- typescript로 구현되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 다양한 지갑 지원 ( MetaMask, WalletConnect, Coinbase Wallet 등 여러 지갑 연동 )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 다양한 체인 지원 ( 이더리움 외에도 Polygon, Arbitrum 등 다양한 체인 지원 )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Type-safe API 지원&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Type-safe API&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;abi 기반으로 스마트 계약 함수 호출&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1733889785042&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useContractRead, useContractWrite } from 'wagmi';
import { abi } from './MyContractABI'; // ABI JSON 파일

const contractConfig = {
  address: '0xYourContractAddress',
  abi: abi,
};

const MyComponent = () =&amp;gt; {
  // 타입 안전하게 함수 호출
  const { data, isError } = useContractRead({
    ...contractConfig,
    functionName: 'balanceOf',
    args: ['0xUserAddress'], // 타입에 맞는 파라미터 자동 체크
  });

  const { write } = useContractWrite({
    ...contractConfig,
    functionName: 'transfer',
    args: ['0xRecipientAddress', BigInt(1000)], // 타입 안전
  });

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;p&amp;gt;Balance: {data?.toString()}&amp;lt;/p&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; write()}&amp;gt;Transfer&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- useContractRead, useContractWrite를 사용하여 스마트 계약과 상호작용할 때 타입 안전성을 제공한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;u&gt;체인 연결 타입 강제&lt;/u&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1733890397339&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useSwitchNetwork } from 'wagmi';

const SwitchNetworkButton = () =&amp;gt; {
  const { switchNetwork, chain } = useSwitchNetwork();

  return (
    &amp;lt;button onClick={() =&amp;gt; switchNetwork?.(1)}&amp;gt;
      Switch to {chain?.name ?? 'Ethereum'}
    &amp;lt;/button&amp;gt;
  );
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- useSwitchNetwork를 사용하여 사용자가 특정 네트워크로 강제할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 숫자 형식의 체인 ID만 허용하며, 잘못된 형식의 데이터는 컴파일 타임에 에러를 발생 시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Wagmi React Hook&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;useAccount&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1733892004709&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useAccount } from 'wagmi';

const AccountInfo = () =&amp;gt; {
  const { address, isConnected } = useAccount();

  return isConnected ? &amp;lt;p&amp;gt;Address: {address}&amp;lt;/p&amp;gt; : &amp;lt;p&amp;gt;Not Connected&amp;lt;/p&amp;gt;;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 현재 연결된 계정 정보를 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- address: 연결된 지갑 주소&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- isConnected: 연결 여부&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;useConnect&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1733892077624&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useConnect } from 'wagmi';

const ConnectWallet = () =&amp;gt; {
  const { connect, connectors } = useConnect();

  return (
    &amp;lt;div&amp;gt;
      {connectors.map((connector) =&amp;gt; (
        &amp;lt;button key={connector.id} onClick={() =&amp;gt; connect(connector)}&amp;gt;
          Connect {connector.name}
        &amp;lt;/button&amp;gt;
      ))}
    &amp;lt;/div&amp;gt;
  );
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 사용자가 지갑을 연결할 수 있도록 도와준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- connect: 지갑 연결 함수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- connectors: 사용 가능한 지갑 목록&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- error: 연결 시 발생한 에러&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;useDiconnect&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1733892085475&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useDisconnect } from 'wagmi';

const DisconnectWallet = () =&amp;gt; {
  const { disconnect } = useDisconnect();

  return &amp;lt;button onClick={disconnect}&amp;gt;Disconnect&amp;lt;/button&amp;gt;;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 현재 연결된 지갑을 해제한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- disconnect: 연결 해제 함수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;useSignMessage&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1733892231917&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useSignMessage } from 'wagmi';

const SignMessage = () =&amp;gt; {
  const { signMessage, data } = useSignMessage();

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; signMessage({ message: 'Hello, Ethereum!' })}&amp;gt;
        Sign Message
      &amp;lt;/button&amp;gt;
      {data &amp;amp;&amp;amp; &amp;lt;p&amp;gt;Signed Message: {data}&amp;lt;/p&amp;gt;}
    &amp;lt;/div&amp;gt;
  );
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 사용자의 메시지 서명을 요청하고, 결과를 반환다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- signMessage: 서명 요청 함수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- data: 서명 결과&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- isLoading: 서명 처리 상태&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;useChainId&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1733892274368&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useChainId } from 'wagmi';

const NetworkInfo = () =&amp;gt; {
  const chainId = useChainId();

  return &amp;lt;p&amp;gt;Connected to Chain ID: {chainId}&amp;lt;/p&amp;gt;;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 현재 연결된 네트워크의 체인 ID를 반환 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- chainId: 연결된 네트워크의 체인 ID&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;useConnectors&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1733892318875&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useConnectors } from 'wagmi';

const WalletList = () =&amp;gt; {
  const { connectors } = useConnectors();

  return (
    &amp;lt;ul&amp;gt;
      {connectors.map((connector) =&amp;gt; (
        &amp;lt;li key={connector.id}&amp;gt;{connector.name}&amp;lt;/li&amp;gt;
      ))}
    &amp;lt;/ul&amp;gt;
  );
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 사용 가능한 지갑 커넥터 목록을 반환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- connectors : 지갑 커넥터 배열&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Wagmi 사용 흐름도&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지갑 연결 : useConnect로 사용자가 원하는 지갑을 연결 한다.&lt;/li&gt;
&lt;li&gt;계정 상태 확인 : useAccount로 현재 연결된 지갑 정보를 가져온다.&lt;/li&gt;
&lt;li&gt;네트워크 확인: useChainId로 사용자가 올바른 체인에 연결되었는지 확인한다.&lt;/li&gt;
&lt;li&gt;메시지 서명: useSignMessage로 데이터를 서명받아 인증 과정을 처리한다.&lt;/li&gt;
&lt;li&gt;연결 해제: useDisconnect로 연결을 해제한다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Code note/web3</category>
      <category>wagmi</category>
      <category>Web3</category>
      <category>이더리움</category>
      <author>코드노트</author>
      <guid isPermaLink="true">https://codeno-te.tistory.com/250</guid>
      <comments>https://codeno-te.tistory.com/250#entry250comment</comments>
      <pubDate>Wed, 11 Dec 2024 13:54:41 +0900</pubDate>
    </item>
    <item>
      <title>tailwindCSS addvariant으로 커스텀 변형 feat.hover 모바일 제외</title>
      <link>https://codeno-te.tistory.com/249</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;반응형 디자인을 하면서 생긴 문제를 해결하면서 알게된 tailwindCSS의 addvariant를 정리하려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 사내 프로젝트에서는 네이티브로 작업을 하지 않고 있다. 그렇다보니 반응형으로 작업을 진행하면서 발생한 이슈이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 프로젝트에서도 hover를 줄텐데 모바일에서는 hover를 할 수 없기에 제외시키는 코드를 주어야했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 hover를 제외시키지 않는다면 터치가 되었을때 hover에 주었던 css값들이 그대로 남아있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;mobile hover before.gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;422&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xx5Q1/btsKDPf57lB/D6haxAeiNGEYhkrUi06W71/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xx5Q1/btsKDPf57lB/D6haxAeiNGEYhkrUi06W71/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xx5Q1/btsKDPf57lB/D6haxAeiNGEYhkrUi06W71/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/xx5Q1/btsKDPf57lB/D6haxAeiNGEYhkrUi06W71/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;422&quot; data-filename=&quot;mobile hover before.gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;422&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 이미지를 보게 되면 hover로 주었던 배경색이 그대로 남게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하기위해서는 css 미디어쿼리를 사용해서 hover를 지원하고 있는 기기에서만 css가 적용되도록 지정할 수 있었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;screens raw&lt;/b&gt;&lt;/h4&gt;
&lt;figure id=&quot;og_1731367792504&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Tailwind Hover 모바일에선 제외하기&quot; data-og-description=&quot;CSS에서 hover라는 Pseudo-classes를 사용하면 편하게 hover 효과를 구현할 수 있다. 그러나 여기엔 문제가 하나 있는데, 그것은 바로,, 모바일 기기에서는 hover라고 부를 효과가 없으며, 그대로 적용할 &quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@sangpok/Tailwind-Hover-%EB%AA%A8%EB%B0%94%EC%9D%BC%EC%97%90%EC%84%A0-%EC%A0%9C%EC%99%B8%ED%95%98%EA%B8%B0&quot; data-og-url=&quot;https://velog.io/@sangpok/Tailwind-Hover-모바일에선-제외하기&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bK4oHw/hyXwkmblt1/SwKiJ7nghZ2oOs9C8WPJfK/img.gif?width=582&amp;amp;height=352&amp;amp;face=0_0_582_352,https://scrap.kakaocdn.net/dn/i4DaY/hyXwtDsyRu/pPTcF8Gx1dhyOmOrPlktk1/img.gif?width=582&amp;amp;height=352&amp;amp;face=0_0_582_352,https://scrap.kakaocdn.net/dn/pVwT4/hyXwp8TnMm/PKnGKJMiO0gSei42TT5KUk/img.png?width=932&amp;amp;height=572&amp;amp;face=0_0_932_572&quot;&gt;&lt;a href=&quot;https://velog.io/@sangpok/Tailwind-Hover-%EB%AA%A8%EB%B0%94%EC%9D%BC%EC%97%90%EC%84%A0-%EC%A0%9C%EC%99%B8%ED%95%98%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@sangpok/Tailwind-Hover-%EB%AA%A8%EB%B0%94%EC%9D%BC%EC%97%90%EC%84%A0-%EC%A0%9C%EC%99%B8%ED%95%98%EA%B8%B0&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bK4oHw/hyXwkmblt1/SwKiJ7nghZ2oOs9C8WPJfK/img.gif?width=582&amp;amp;height=352&amp;amp;face=0_0_582_352,https://scrap.kakaocdn.net/dn/i4DaY/hyXwtDsyRu/pPTcF8Gx1dhyOmOrPlktk1/img.gif?width=582&amp;amp;height=352&amp;amp;face=0_0_582_352,https://scrap.kakaocdn.net/dn/pVwT4/hyXwp8TnMm/PKnGKJMiO0gSei42TT5KUk/img.png?width=932&amp;amp;height=572&amp;amp;face=0_0_932_572');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Tailwind Hover 모바일에선 제외하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;CSS에서 hover라는 Pseudo-classes를 사용하면 편하게 hover 효과를 구현할 수 있다. 그러나 여기엔 문제가 하나 있는데, 그것은 바로,, 모바일 기기에서는 hover라고 부를 효과가 없으며, 그대로 적용할&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 구글링을 하면서 알게된 방법을 사용해저 지정을 하려고 했다.&lt;/p&gt;
&lt;pre class=&quot;css&quot; style=&quot;color: #000000; text-align: left;&quot;&gt;&lt;code&gt;     screens: {
        pointerhover: {
          raw: &quot;(hover: hover) and (pointer: fine)&quot;,
        },
      },&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;raw&lt;/b&gt;: tailwind CSS 설정 파일에서 사용자 정의 미디어 쿼리를 추가할 때 사용되는 특수한 속성이다. raw는 픽셀 기반이 아니기 때문에 미디어 쿼리를 직접 작성할 수 있다.&lt;br /&gt;-  &lt;b&gt;fine&lt;/b&gt;: 정교한 포인팅 장치를 의미하며 마우스와 같은 정확한 포인터를 가진 장치에서 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 외에도 coarse: 손가락으로 조작하는 환경, none: 존재하지 않음 등 속성이 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;근데 문제가 또 발생...&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;./tailwind.config.ts 에서 extend를 추가하여야하는데 screens에 적용하니 hover는 진행되지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;screens에 넣어서그런지 기존 반응형 브레이크 포인트가 적용되지 않았다... sm, md, lg, xl ... 등&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관련 에러가 나만 생기는건가 싶어서 다른 글들을 찾아보니&lt;/p&gt;
&lt;figure id=&quot;og_1731367918917&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;Adding non-pixel media queries to screens config prevents auto-generated max-* classes for the rest of the breakpoints &amp;middot; Issue &quot; data-og-description=&quot;What version of Tailwind CSS are you using? v3.3.3 What build tool (or framework if it abstracts the build tool) are you using? Next.js 13.5.4 What version of Node.js are you using? v18.16.0 What b...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/tailwindlabs/tailwindcss/issues/13022&quot; data-og-url=&quot;https://github.com/tailwindlabs/tailwindcss/issues/13022&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bkVP4e/hyXwhiIJDW/XkITOc0MVMCdZNpxJD4Gw1/img.png?width=1200&amp;amp;height=600&amp;amp;face=971_118_1070_226,https://scrap.kakaocdn.net/dn/bF1eL4/hyXwt4uTNI/af2jNlhMpbNuNyKC74xb11/img.png?width=1200&amp;amp;height=600&amp;amp;face=971_118_1070_226&quot;&gt;&lt;a href=&quot;https://github.com/tailwindlabs/tailwindcss/issues/13022&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/tailwindlabs/tailwindcss/issues/13022&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bkVP4e/hyXwhiIJDW/XkITOc0MVMCdZNpxJD4Gw1/img.png?width=1200&amp;amp;height=600&amp;amp;face=971_118_1070_226,https://scrap.kakaocdn.net/dn/bF1eL4/hyXwt4uTNI/af2jNlhMpbNuNyKC74xb11/img.png?width=1200&amp;amp;height=600&amp;amp;face=971_118_1070_226');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Adding non-pixel media queries to screens config prevents auto-generated max-* classes for the rest of the breakpoints &amp;middot; Issue&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;What version of Tailwind CSS are you using? v3.3.3 What build tool (or framework if it abstracts the build tool) are you using? Next.js 13.5.4 What version of Node.js are you using? v18.16.0 What b...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나와 같은 에러를 만나는 사람들이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tailwindCSS에서 theme/extend/screens에서 작성하고 사용하기 위해서는&lt;/p&gt;
&lt;pre id=&quot;code_1731368315216&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const defaultTheme = require('tailwindcss/defaultTheme')

module.exports = {
  theme: {
    extend: {
      screens: {
        'max-sm': { raw: `not all and (min-width: ${defaultTheme.screens.sm})` },
        'max-md': { raw: `not all and (min-width: ${defaultTheme.screens.md})` },
        'max-lg': { raw: `not all and (min-width: ${defaultTheme.screens.lg})` },
        'max-xl': { raw: `not all and (min-width: ${defaultTheme.screens.xl})` },
        'max-2xl': { raw: `not all and (min-width: ${defaultTheme.screens['2xl']})` },

        // You can now have your custom media queries and still use max-* ranges.
        pointerhover: {
          raw: &quot;(hover: hover) and (pointer: fine)&quot;,
        },
      },
    },
  },
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이런식으로 각 해당 브레이크 포인트를 다시 생성해주고 사용한다면 잘 적용된다..!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 그치만 &lt;u&gt;불필요하게 다시 브레이크 포인트를 생성해주는것보다 다른 방법&lt;/u&gt;이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2937;&quot;&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #1f2937;&quot;&gt;addVariant&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;figure id=&quot;og_1731369163841&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Plugins - Tailwind CSS&quot; data-og-description=&quot;Extending Tailwind with reusable third-party plugins.&quot; data-og-host=&quot;tailwindcss.com&quot; data-og-source-url=&quot;https://tailwindcss.com/docs/plugins#static-variants&quot; data-og-url=&quot;https://tailwindcss.com/docs/plugins&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/f9mfk/hyXwq7NFkv/WwIogwUSe419R4pSLyulU1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/X2pG5/hyXwvuu8mN/I4N2E89IP0kL65YeWDkWN1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/dKr6oq/hyXwrejDgf/5tAPKrG1Dojp510K7TkOU1/img.png?width=2880&amp;amp;height=1232&amp;amp;face=0_0_2880_1232&quot;&gt;&lt;a href=&quot;https://tailwindcss.com/docs/plugins#static-variants&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://tailwindcss.com/docs/plugins#static-variants&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/f9mfk/hyXwq7NFkv/WwIogwUSe419R4pSLyulU1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/X2pG5/hyXwvuu8mN/I4N2E89IP0kL65YeWDkWN1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/dKr6oq/hyXwrejDgf/5tAPKrG1Dojp510K7TkOU1/img.png?width=2880&amp;amp;height=1232&amp;amp;face=0_0_2880_1232');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Plugins - Tailwind CSS&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Extending Tailwind with reusable third-party plugins.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;tailwindcss.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1731369131805&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  plugins: [
    function ({ addVariant }: { addVariant: Function }) {
      addVariant(&quot;pointerhover&quot;, &quot;@media (hover: hover) and (pointer: fine)&quot;);
    },
  ],&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- plugins 옵션을 활용해서 새로운 variant를 추가할 수 있다. 기존의 유틸리티를 같이 사용하면서 addVariant함수를 사용해 새로운 ,클래스를 추가해서 사용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 미디어쿼리 코드들을 작성할때 screens를 사용하면서 기존 브레이크포인트를 모두 재작성하는것 보다 조금은 더 간편하게 정의할 수 있는 방법이 아닐까 싶다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Code note/CSS</category>
      <category>addvariant</category>
      <category>mobile hover</category>
      <category>Plugins</category>
      <category>Screens</category>
      <category>Tailwind</category>
      <category>tailwind mobile hover</category>
      <author>코드노트</author>
      <guid isPermaLink="true">https://codeno-te.tistory.com/249</guid>
      <comments>https://codeno-te.tistory.com/249#entry249comment</comments>
      <pubDate>Tue, 12 Nov 2024 09:05:32 +0900</pubDate>
    </item>
    <item>
      <title>Race Condition 정리 및 해결방법</title>
      <link>https://codeno-te.tistory.com/248</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Race Condition?&lt;/b&gt;&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;- 두 개 이상의 작업이 동시에 실행되거나 예상치 못한 순서로 완료될 때 발생하는 문제점이다.&lt;br&gt;리액트를 사용하면서 한번쯤은 만나본 문제이기도 하다. 이로 인해서 데이터 상태가 예측할 수 없는 상태로 변경될 수 있다.&lt;br&gt;특히 비동기 코드에서는 필수적으로 생각하고 코드를 작성해야한다!&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot;&gt;&lt;blockquote data-ke-style=&quot;style1&quot;&gt; &lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;그럼 Race Condition는 왜 일어나며 어떤 문제점을 가지고 있을까?&lt;/b&gt;&lt;/span&gt;&lt;/span&gt; 
 &lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style4&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;br&gt;&lt;br&gt;&lt;/span&gt; 
&lt;/blockquote&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;u&gt;&lt;b&gt;여러 비동기 작업이 동시에 실행되면서 서로 경쟁하는 상황이 발생&lt;/b&gt;&lt;/u&gt;할 수 있다. 비동기 작업이 순차적으로 이뤄지지 않고 병렬적으로 실행되기 때문에 동시에 업데이트가 된다면 예상치 못한 상태 변경이 발생할 수 있다.&lt;br&gt;- 리엑트를 사용할 때 컴포넌트가 비동기적으로 데이터를 가져오고 동시에 setState를 통해 상태를 업데이트를 할때 주로 발생한다.&lt;br&gt;- 어? 나는 이런적이 없는데? 아니 코드를 100번, 1,000번 실행했을 때 실행 결과가 같아야하지 않을까? 단 한번이라도 Race Condition가 발생한다면 그건 꼭 해결해야하는 문제이다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot;&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;useEffect – React&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;The library for web and native user interfaces&quot; data-og-host=&quot;react.dev&quot; data-og-source-url=&quot;https://react.dev/reference/react/useEffect#fetching-data-with-effects&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/i0jgy/hyWVQ6Rf2I/isKXntoA0av6LrrYqagli1/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567,https://scrap.kakaocdn.net/dn/7X1zz/hyWVVtxFEk/72jqqePyMlJlZkMx6sWzHK/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567&quot; data-og-url=&quot;https://react.dev/reference/react/useEffect&quot;&gt;&lt;a href=&quot;https://react.dev/reference/react/useEffect&quot; target=&quot;_blank&quot; data-source-url=&quot;https://react.dev/reference/react/useEffect#fetching-data-with-effects&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/i0jgy/hyWVQ6Rf2I/isKXntoA0av6LrrYqagli1/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567,https://scrap.kakaocdn.net/dn/7X1zz/hyWVVtxFEk/72jqqePyMlJlZkMx6sWzHK/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;useEffect – React&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;The library for web and native user interfaces&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;react.dev&lt;/p&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;- react 공식 dev에서도 이야기를 하고 있는것을 볼 수 있다. 참고!&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot;&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;React에서의 Race Condition 예시&lt;/b&gt;&lt;/h4&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;import React, { useState, useEffect } from 'react';

function UserProfile({ userId }) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const [userData, setUserData] = useState(null);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;useEffect(() =&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;async function fetchData() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const response = await fetch(`https://api.example.com/user/${userId}`);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const data = await response.json();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;setUserData(data);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fetchData();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}, [userId]);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return (
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;div&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{userData ? &amp;lt;h1&amp;gt;{userData.name}&amp;lt;/h1&amp;gt; : &amp;lt;p&amp;gt;Loading...&amp;lt;/p&amp;gt;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/div&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;);
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;- 이 코드는 userId를 props로 받아서 userId가 변경될때마다 fetch로 api요청 후 data를 setUserData로 상태를 변경시켜준다.&lt;br&gt;- 여기서 문제 userId가 계속해서 바뀌고 이전 요청이 나중에 도착하여 잘못된 userData로 변경될 수 있다.&lt;br&gt;- 이 문제는 then을 사용해도되지 않나요? 라고도 하지만 계속해서 userId가 바뀐다면 비동기로 진행된 fetch는 언제 data를 가져와서 어떤 데이터가 set함수에 적용될지 완벽하게 예상할 수 없다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Race Condition 해결 방법&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. Cleanup 함수 사용 (useEffect)&lt;/b&gt;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;import React, { useState, useEffect } from 'react';

function UserProfile({ userId }) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const [userData, setUserData] = useState(null);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;useEffect(() =&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let isMounted = true;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;async function fetchData() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const response = await fetch(`https://api.example.com/user/${userId}`);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const data = await response.json();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (isMounted) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;setUserData(data);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fetchData();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return () =&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;isMounted = false;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;};
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}, [userId]);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return (
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;div&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{userData ? &amp;lt;h1&amp;gt;{userData.name}&amp;lt;/h1&amp;gt; : &amp;lt;p&amp;gt;Loading...&amp;lt;/p&amp;gt;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/div&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;);
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;- isMounted 변수를 통해서 컴포넌트가 마운트되어있는지 확인한다.&lt;br&gt;- fetchData함수로 비동기 작업이 완료된 후 isMounted가 true일때만 set함수로 userData를 업데이트 한다.&lt;br&gt;- 이후 isMounted를 false로 처리하여 컴포넌트가 언마운트 상태에서는 set함수를 호출하지 않게하여 race condition로 인한 불필요한 상태 업데이트를 방지할 수 있다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot;&gt;&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. AbortController 사용&lt;/b&gt;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;import React, { useState, useEffect } from 'react';

function UserProfile({ userId }) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const [userData, setUserData] = useState(null);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;useEffect(() =&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const controller = new AbortController();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const signal = controller.signal;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;async function fetchData() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const response = await fetch(`https://api.example.com/user/${userId}`, { signal });
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const data = await response.json();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;setUserData(data);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} catch (error) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (error.name === 'AbortError') {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;console.log('Fetch aborted');
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} else {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;console.error('Fetch error:', error);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fetchData();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return () =&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;controller.abort();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;};
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}, [userId]);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return (
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;div&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{userData ? &amp;lt;h1&amp;gt;{userData.name}&amp;lt;/h1&amp;gt; : &amp;lt;p&amp;gt;Loading...&amp;lt;/p&amp;gt;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/div&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;);
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;- AbortController는 비동기 요청을 취소시킬 수 있다. 네트워크 요청을 명시적으로 중단시켜 Race Condition를 방지할 수 있다.&lt;br&gt;- signal 객체는 fetch 요청에 전달되고 요청이 중단될 때 이 신호를 감지할 수 있다. fetch 함수에 signal을 전달하여 요청이 중단될 수 있도록 설정한다. 요청이 만약 성곡적으로 완료되면 set함수에 상태를 업데이트하고 요청이 중단되면 따로 에러 처리!&lt;br&gt;- cleanup함수는 컴포넌트가 언마운트 되거나 userId가 변경될때 호출 되는데 이때 controller.abort를 호출하여 현재 진행중인 요청을 취소할 수 있다.&lt;br&gt;- 만약 응답이 도착하기 전에 컴포넌트가 언마운트 되거나 userId가 변경된 이후에도 비동기 작업이 상태를 업데이트 하지 않도록 한다.&lt;br&gt;- AbortController를 사용하여 안전하게 관리할 수 있으며, isMounted와 같이 따로 변수를 통해 플래그를 사용하지 않고도 race condition를 해결할 수 있다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;AbortController - Web API | MDN&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;AbortController 인터페이스는 하나 이상의 웹 요청을 취소할 수 있게 해준다.&quot; data-og-host=&quot;developer.mozilla.org&quot; data-og-source-url=&quot;https://developer.mozilla.org/ko/docs/Web/API/AbortController&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/daTA5B/hyWV6aOhSF/5R3o6WxVfYXgVKrpMnbjZ0/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080&quot; data-og-url=&quot;https://developer.mozilla.org/ko/docs/Web/API/AbortController&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/API/AbortController&quot; target=&quot;_blank&quot; data-source-url=&quot;https://developer.mozilla.org/ko/docs/Web/API/AbortController&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/daTA5B/hyWV6aOhSF/5R3o6WxVfYXgVKrpMnbjZ0/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;AbortController - Web API | MDN&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;AbortController 인터페이스는 하나 이상의 웹 요청을 취소할 수 있게 해준다.&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;developer.mozilla.org&lt;/p&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot;&gt;&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. Suspense 사용&lt;/b&gt;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;import React, { Suspense } from 'react';

// 비동기 데이터를 처리하는 함수
function fetchData() {
&amp;nbsp;&amp;nbsp;return new Promise((resolve) =&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;setTimeout(() =&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resolve('Data loaded');
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}, 2000);
&amp;nbsp;&amp;nbsp;});
}

// 데이터를 감싸는 함수
function wrapPromise(promise) {
&amp;nbsp;&amp;nbsp;let status = 'pending';
&amp;nbsp;&amp;nbsp;let result;

&amp;nbsp;&amp;nbsp;let suspender = promise.then(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;(r) =&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;status = 'success';
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result = r;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;(e) =&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;status = 'error';
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result = e;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;);

&amp;nbsp;&amp;nbsp;return {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;read() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (status === 'pending') {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw suspender;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} else if (status === 'error') {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw result;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} else if (status === 'success') {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return result;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;};
}

const resource = wrapPromise(fetchData());

// 비동기 데이터를 표시하는 컴포넌트
function DataComponent() {
&amp;nbsp;&amp;nbsp;const data = resource.read();
&amp;nbsp;&amp;nbsp;return &amp;lt;div&amp;gt;{data}&amp;lt;/div&amp;gt;;
}

// 앱 컴포넌트
export default function App() {
&amp;nbsp;&amp;nbsp;return (
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;div&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;h1&amp;gt;Suspense Example&amp;lt;/h1&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;Suspense fallback={&amp;lt;p&amp;gt;Loading...&amp;lt;/p&amp;gt;}&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;DataComponent /&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/Suspense&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/div&amp;gt;
&amp;nbsp;&amp;nbsp;);
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;- 비동기 요청이 완료되기 전에 컴포넌트는 로딩 상태를 표시한다.&lt;br&gt;- 데이터를 받기 전까지 fallback UI를 보여주며 데이터를 기다린다.&lt;br&gt;- 데이터가 성공적으로 로드되면 로딩 상태를 종료하고 데이터가 준비된 상태로 컴포넌트를 렌더링 한다.&lt;br&gt;- Suspense는 Concurrent Mode와 함께 사용되며, 여러 비동기 작업이 동시에 실행되더라도 상태를 일관되게 유지 한다. 데이터가 완료되기 전까지는 로딩 상태를 보여주고 데이터가 준비되면 그때 최신상태로 업데이트 한다.&lt;br&gt;- 데이터 요청이 중복되거나 Race Condition이 발생한다면, 마지막으로 완료된 요청의 결과만 실제로 적용된다.&lt;br&gt;- Suspense가 최신 상태의 데이터를 항상 렌더링하기 때문에 자연스럽게 해결된다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Suspense를 사용해서 Race Condition, Waterfall 문제 해결하기&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;React에서 Suspense가 트렌드가 된 건 아무래도 React 18버전부터 아닐까 생각됩니다. 이번시간에는 Suspense를 사용해서 Race Condition, Waterfall 문제를 해결하는 방법에 대해 알아보도록 하겠습니다.&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@ckstn0777/Suspense%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%B4%EC%84%9C-Race-Condition-Waterfall-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bB4NOQ/hyWV18oa5i/kT0igG7nD2pZWKrmPAOSY1/img.png?width=686&amp;amp;height=380&amp;amp;face=0_0_686_380,https://scrap.kakaocdn.net/dn/UJVAu/hyWV0hlkWs/ZLohMzWdgRk5hqohjPV8V1/img.png?width=686&amp;amp;height=380&amp;amp;face=0_0_686_380,https://scrap.kakaocdn.net/dn/mjwzu/hyWV3SE7ZQ/gTgwakGy3I6Ath9lu39K60/img.jpg?width=640&amp;amp;height=960&amp;amp;face=0_0_640_960&quot; data-og-url=&quot;https://velog.io/@ckstn0777/Suspense를-사용해서-Race-Condition-Waterfall-문제-해결하기&quot;&gt;&lt;a href=&quot;https://velog.io/@ckstn0777/Suspense를-사용해서-Race-Condition-Waterfall-문제-해결하기&quot; target=&quot;_blank&quot; data-source-url=&quot;https://velog.io/@ckstn0777/Suspense%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%B4%EC%84%9C-Race-Condition-Waterfall-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bB4NOQ/hyWV18oa5i/kT0igG7nD2pZWKrmPAOSY1/img.png?width=686&amp;amp;height=380&amp;amp;face=0_0_686_380,https://scrap.kakaocdn.net/dn/UJVAu/hyWV0hlkWs/ZLohMzWdgRk5hqohjPV8V1/img.png?width=686&amp;amp;height=380&amp;amp;face=0_0_686_380,https://scrap.kakaocdn.net/dn/mjwzu/hyWV3SE7ZQ/gTgwakGy3I6Ath9lu39K60/img.jpg?width=640&amp;amp;height=960&amp;amp;face=0_0_640_960')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;Suspense를 사용해서 Race Condition, Waterfall 문제 해결하기&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;React에서 Suspense가 트렌드가 된 건 아무래도 React 18버전부터 아닐까 생각됩니다. 이번시간에는 Suspense를 사용해서 Race Condition, Waterfall 문제를 해결하는 방법에 대해 알아보도록 하겠습니다.&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;velog.io&lt;/p&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;React Query와 함께 Concurrent UI Pattern을 도입하는 방법 | 카카오페이 기술 블로그&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;카카오페이에서 React Query를 활용하여 Concurrent UI Pattern을 도입한 사례에 대해 소개합니다. 이 글은 연작 중 2편에 해당합니다. 1편: 카카오페이 프론트엔드 개발자들이 React Query를 선택한 이유, 2&quot; data-og-host=&quot;tech.kakaopay.com&quot; data-og-source-url=&quot;https://tech.kakaopay.com/post/react-query-2/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Thyz9/hyWV5JIzlJ/ojCc03VjqUieJ2NRZLbL2K/img.png?width=480&amp;amp;height=319&amp;amp;face=0_0_480_319,https://scrap.kakaocdn.net/dn/ccgE6W/hyWV1HjjRG/4Kchy6jnPPod3aDJYgwYF1/img.png?width=480&amp;amp;height=319&amp;amp;face=0_0_480_319,https://scrap.kakaocdn.net/dn/mDfvY/hyWVZW1gMb/HkmKN5vDnf8eLmqkjfwjx1/img.jpg?width=790&amp;amp;height=1183&amp;amp;face=0_0_790_1183&quot; data-og-url=&quot;https://tech.kakaopay.com/post/react-query-2/&quot;&gt;&lt;a href=&quot;https://tech.kakaopay.com/post/react-query-2/&quot; target=&quot;_blank&quot; data-source-url=&quot;https://tech.kakaopay.com/post/react-query-2/&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Thyz9/hyWV5JIzlJ/ojCc03VjqUieJ2NRZLbL2K/img.png?width=480&amp;amp;height=319&amp;amp;face=0_0_480_319,https://scrap.kakaocdn.net/dn/ccgE6W/hyWV1HjjRG/4Kchy6jnPPod3aDJYgwYF1/img.png?width=480&amp;amp;height=319&amp;amp;face=0_0_480_319,https://scrap.kakaocdn.net/dn/mDfvY/hyWVZW1gMb/HkmKN5vDnf8eLmqkjfwjx1/img.jpg?width=790&amp;amp;height=1183&amp;amp;face=0_0_790_1183')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;React Query와 함께 Concurrent UI Pattern을 도입하는 방법 | 카카오페이 기술 블로그&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;카카오페이에서 React Query를 활용하여 Concurrent UI Pattern을 도입한 사례에 대해 소개합니다. 이 글은 연작 중 2편에 해당합니다. 1편: 카카오페이 프론트엔드 개발자들이 React Query를 선택한 이유, 2&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;tech.kakaopay.com&lt;/p&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Code note/Error문제해결</category>
      <category>abortcontroller</category>
      <category>RaceCondition</category>
      <category>react</category>
      <category>Suspense</category>
      <author>코드노트</author>
      <guid isPermaLink="true">https://codeno-te.tistory.com/248</guid>
      <comments>https://codeno-te.tistory.com/248#entry248comment</comments>
      <pubDate>Thu, 29 Aug 2024 17:17:27 +0900</pubDate>
    </item>
    <item>
      <title>Next.js 13^ Jest 설정하기, 에러 해결</title>
      <link>https://codeno-te.tistory.com/247</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;next.js 에서 jest를 설정하려면 바로 시작할 수 있는 방법도 있지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;next를 설치하고 jest를 추가하는 방법을 기록해보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방법은 어렵지 않고 config, setup ... 등 세팅값만 맞춰주면 바로 jest를 실행하고 개발이 가능했다.&lt;/p&gt;
&lt;figure id=&quot;og_1723436128375&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Testing: Jest | Next.js&quot; data-og-description=&quot;Learn how to set up Jest with Next.js for Unit Testing and Snapshot Testing.&quot; data-og-host=&quot;nextjs.org&quot; data-og-source-url=&quot;https://nextjs.org/docs/app/building-your-application/testing/jest&quot; data-og-url=&quot;https://nextjs.org/docs/app/building-your-application/testing/jest&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cKrc51/hyWKG5w2Cm/YaMpHzfwRWPUsMBfzk8zi1/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441,https://scrap.kakaocdn.net/dn/cB8LrX/hyWKvpowJb/ijDHcZS006ux85KKQ72zRK/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441&quot;&gt;&lt;a href=&quot;https://nextjs.org/docs/app/building-your-application/testing/jest&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://nextjs.org/docs/app/building-your-application/testing/jest&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cKrc51/hyWKG5w2Cm/YaMpHzfwRWPUsMBfzk8zi1/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441,https://scrap.kakaocdn.net/dn/cB8LrX/hyWKvpowJb/ijDHcZS006ux85KKQ72zRK/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Testing: Jest | Next.js&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Learn how to set up Jest with Next.js for Unit Testing and Snapshot Testing.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;nextjs.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Quickstart&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;bash&quot; style=&quot;color: #000000; text-align: left;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;$npx create-next-app --example with-jest with-jest-app

$yarn create next-app --example with-jest with-jest-app

$npm create next-app --example with-jest with-jest-app&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 간단한 명령어로 설치가능하지만 설치후에 기존에 Next를 설치하는것과 다르게 다른 설정값들을 정하는게 없었고, tailwindCSS 또한 따로 설치해줘야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 폴더구조 또한 src가 없어서 새로 폴더구조를 조금 정리해줘야했다. 이러한 설정들이 귀찮다보니 나는 기존 next를 설치하고 Babel을 사용해서 Jest구조를 새로 정리하는 방법으로 진행했다! 오히려 뭔가 더 좋은것 같음&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Next 프로젝트에 Jest 추가&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- next.js 12 이후 Jest에 대한 기본 구성이 내장되어있다고 한다. 이제 아래 추가 명령어를 통해 설치할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; style=&quot;color: #000000; text-align: left;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;$yarn add --dev jest jest-environment-jsdom @testing-library/react @testing-library/jest-dom

$npm install --save-dev jest jest-environment-jsdom @testing-library/react @testing-library/jest-dom&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치 후 아래 jest.config.js 파일과 jest.setup.js 파일을 생성해줘야한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;./jest.config.js&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1724049063424&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;module.exports = {
  collectCoverage: true,
  // on node 14.x coverage provider v8 offers good speed and more or less good report
  coverageProvider: &quot;v8&quot;,
  collectCoverageFrom: [
    &quot;**/*.{js,jsx,ts,tsx}&quot;,
    &quot;!**/*.d.ts&quot;,
    &quot;!**/node_modules/**&quot;,
    &quot;!&amp;lt;rootDir&amp;gt;/out/**&quot;,
    &quot;!&amp;lt;rootDir&amp;gt;/.next/**&quot;,
    &quot;!&amp;lt;rootDir&amp;gt;/*.config.js&quot;,
    &quot;!&amp;lt;rootDir&amp;gt;/coverage/**&quot;,
  ],
  moduleNameMapper: {
    // Handle CSS imports (with CSS modules)
    // https://jestjs.io/docs/webpack#mocking-css-modules
    &quot;^.+\\.module\\.(css|sass|scss)$&quot;: &quot;identity-obj-proxy&quot;,

    // Handle CSS imports (without CSS modules)
    &quot;^.+\\.(css|sass|scss)$&quot;: &quot;&amp;lt;rootDir&amp;gt;/__mocks__/styleMock.js&quot;,

    // Handle image imports
    // https://jestjs.io/docs/webpack#handling-static-assets
    &quot;^.+\\.(png|jpg|jpeg|gif|webp|avif|ico|bmp|svg)$/i&quot;: `&amp;lt;rootDir&amp;gt;/__mocks__/fileMock.js`,

    // Handle module aliases
    &quot;^@/components/(.*)$&quot;: &quot;&amp;lt;rootDir&amp;gt;/src/components/$1&quot;,
    &quot;^@/(.*)$&quot;: &quot;&amp;lt;rootDir&amp;gt;/src/$1&quot;,
  },
  // Add more setup options before each test is run
  setupFilesAfterEnv: [&quot;&amp;lt;rootDir&amp;gt;/jest.setup.js&quot;],
  testPathIgnorePatterns: [&quot;&amp;lt;rootDir&amp;gt;/node_modules/&quot;, &quot;&amp;lt;rootDir&amp;gt;/.next/&quot;],
  testEnvironment: &quot;jsdom&quot;,
  transform: {
    // Use babel-jest to transpile tests with the next/babel preset
    // https://jestjs.io/docs/configuration#transform-objectstring-pathtotransformer--pathtotransformer-object
    &quot;^.+\\.(js|jsx|ts|tsx)$&quot;: [&quot;babel-jest&quot;, { presets: [&quot;next/babel&quot;] }],
  },
  transformIgnorePatterns: [
    &quot;/node_modules/&quot;,
    &quot;^.+\\.module\\.(css|sass|scss)$&quot;,
  ],
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;./jest.setup.js&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1724049543261&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// version 6 이전
import '@testing-library/jest-dom/extend-expect';

// version 6 이후
import '@testing-library/jest-dom';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Jest version 6 이후 부터는 extend-expoct가 사라졌다고 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;typescript라면&lt;/b&gt; type에러가 발생하기 때문에 따로 설치해줘야함&lt;/p&gt;
&lt;pre id=&quot;code_1724051493207&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm i --save-dev @types/jest&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;만약 'jestmatchers&amp;lt;htmlelement&amp;gt;' 형식에 'tobeinthedocument' 속성이 없습니다.ts(2339)라는 Error가 발생한다면?&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1724051646597&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;compilerOptions&quot;: {
    &quot;lib&quot;: [&quot;dom&quot;, &quot;dom.iterable&quot;, &quot;esnext&quot;],
    &quot;allowJs&quot;: true,
    &quot;skipLibCheck&quot;: true,
    &quot;strict&quot;: true,
    &quot;noEmit&quot;: true,
    &quot;esModuleInterop&quot;: true,
    &quot;module&quot;: &quot;esnext&quot;,
    &quot;moduleResolution&quot;: &quot;bundler&quot;,
    &quot;resolveJsonModule&quot;: true,
    &quot;isolatedModules&quot;: true,
    &quot;jsx&quot;: &quot;preserve&quot;,
    &quot;incremental&quot;: true,
    &quot;plugins&quot;: [
      {
        &quot;name&quot;: &quot;next&quot;
      }
    ],
    &quot;paths&quot;: {
      &quot;@/*&quot;: [&quot;./src/*&quot;]
    }
  },
  &quot;include&quot;: [
    &quot;next-env.d.ts&quot;,
    &quot;**/*.ts&quot;,
    &quot;**/*.tsx&quot;,
    &quot;.next/types/**/*.ts&quot;,
    -----------아래 줄 추가
    // &quot;./jest.setup.js&quot;
  ],
  &quot;exclude&quot;: [&quot;./cypress.config.ts&quot;, &quot;node_modules&quot;, &quot;cypress&quot;, &quot;**/*.cy.tsx&quot;]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- tsconfig.json&lt;span&gt;&amp;nbsp;&lt;/span&gt;파일의&lt;span&gt;&amp;nbsp;&lt;/span&gt;include&lt;span&gt;&amp;nbsp;&lt;/span&gt;옵션에&lt;span&gt;&amp;nbsp;&lt;/span&gt;jest.setup.js&lt;span&gt;&amp;nbsp;&lt;/span&gt;파일을 넣어줘야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1724052142583&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[next.js] 13ver jest setting&quot; data-og-description=&quot;jest setting&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@sssssssssy/jest-setting&quot; data-og-url=&quot;https://velog.io/@sssssssssy/jest-setting&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/h2JXw/hyWOpCEiLS/wUiJxf1Z4AwLPIsSfUOfRK/img.png?width=950&amp;amp;height=500&amp;amp;face=0_0_950_500,https://scrap.kakaocdn.net/dn/ba8mxF/hyWSohKoyl/9uH95OjzvQ8WAVrSpv7O00/img.jpg?width=4440&amp;amp;height=2871&amp;amp;face=0_0_4440_2871,https://scrap.kakaocdn.net/dn/bOJJW5/hyWSd1zkYx/jN6b1IRUQkLQxrxiH633uK/img.png?width=1276&amp;amp;height=216&amp;amp;face=0_0_1276_216&quot;&gt;&lt;a href=&quot;https://velog.io/@sssssssssy/jest-setting&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@sssssssssy/jest-setting&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/h2JXw/hyWOpCEiLS/wUiJxf1Z4AwLPIsSfUOfRK/img.png?width=950&amp;amp;height=500&amp;amp;face=0_0_950_500,https://scrap.kakaocdn.net/dn/ba8mxF/hyWSohKoyl/9uH95OjzvQ8WAVrSpv7O00/img.jpg?width=4440&amp;amp;height=2871&amp;amp;face=0_0_4440_2871,https://scrap.kakaocdn.net/dn/bOJJW5/hyWSd1zkYx/jN6b1IRUQkLQxrxiH633uK/img.png?width=1276&amp;amp;height=216&amp;amp;face=0_0_1276_216');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[next.js] 13ver jest setting&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;jest setting&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1724052170038&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Next.js 환경에서 Jest &amp;amp; React Testing Library 사용하기&quot; data-og-description=&quot;Jest 와 RTL(React Testing Library)는 유닛테스트를 위해 흔히 같이 사용된다.다음과 같이 Next.js 프로젝트 환경에서 Jest를 사용할 수 있는 세 가지 환경 설정 방법이 있다.Nextjs 공식 문서에 나와있는 quick&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@ljw4536/Next.js-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-Jest-React-Testing-Library-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0&quot; data-og-url=&quot;https://velog.io/@ljw4536/Next.js-환경에서-Jest-React-Testing-Library-사용하기&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cJzC6s/hyWOp3FRfy/BVEVRJbdI4R9WNytTddmR0/img.png?width=796&amp;amp;height=416&amp;amp;face=0_0_796_416,https://scrap.kakaocdn.net/dn/czUUhn/hyWSezrfrx/TTni4DjMZJHsxkKKHhZC70/img.png?width=796&amp;amp;height=416&amp;amp;face=0_0_796_416,https://scrap.kakaocdn.net/dn/2tPGu/hyWOh5DwHW/NsGihlRG9QspY57NULwtF1/img.jpg?width=3024&amp;amp;height=4032&amp;amp;face=0_0_3024_4032&quot;&gt;&lt;a href=&quot;https://velog.io/@ljw4536/Next.js-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-Jest-React-Testing-Library-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@ljw4536/Next.js-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-Jest-React-Testing-Library-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cJzC6s/hyWOp3FRfy/BVEVRJbdI4R9WNytTddmR0/img.png?width=796&amp;amp;height=416&amp;amp;face=0_0_796_416,https://scrap.kakaocdn.net/dn/czUUhn/hyWSezrfrx/TTni4DjMZJHsxkKKHhZC70/img.png?width=796&amp;amp;height=416&amp;amp;face=0_0_796_416,https://scrap.kakaocdn.net/dn/2tPGu/hyWOh5DwHW/NsGihlRG9QspY57NULwtF1/img.jpg?width=3024&amp;amp;height=4032&amp;amp;face=0_0_3024_4032');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Next.js 환경에서 Jest &amp;amp; React Testing Library 사용하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Jest 와 RTL(React Testing Library)는 유닛테스트를 위해 흔히 같이 사용된다.다음과 같이 Next.js 프로젝트 환경에서 Jest를 사용할 수 있는 세 가지 환경 설정 방법이 있다.Nextjs 공식 문서에 나와있는 quick&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Code note</category>
      <category>jest</category>
      <category>next jest</category>
      <category>TDD</category>
      <category>테스트코드</category>
      <author>코드노트</author>
      <guid isPermaLink="true">https://codeno-te.tistory.com/247</guid>
      <comments>https://codeno-te.tistory.com/247#entry247comment</comments>
      <pubDate>Mon, 19 Aug 2024 16:23:13 +0900</pubDate>
    </item>
    <item>
      <title>자바스크립트 디자인 패턴 정리</title>
      <link>https://codeno-te.tistory.com/246</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dWrjJS/btsIa30VbYX/jx9UVS2CH3eC1gw5iS0PdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dWrjJS/btsIa30VbYX/jx9UVS2CH3eC1gw5iS0PdK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dWrjJS/btsIa30VbYX/jx9UVS2CH3eC1gw5iS0PdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdWrjJS%2FbtsIa30VbYX%2Fjx9UVS2CH3eC1gw5iS0PdK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;500&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;디자인 패턴은 면접을 볼때 많이 물어보는 질문중 하나였다.&lt;br&gt;어떻게 보면 여러 디자인 패턴이 있지만 처음 디자인 패턴이라는 이야기를 들었을때에는 디자인이라는 단어 때문에&lt;br&gt;UI를 다루는 패턴인가? 라는 생각을 했던것 같다. 비전공자인.. 특히 나는 디자이너로 시작하여 개발자로 이직을 했다보니&lt;br&gt;더 그렇게 생각했었다. 그래서 첫 면접에서 당당히 나는 아토믹 패턴을 구구절절 이야기했었던 기억이 있다...ㅋ&lt;br&gt;&amp;nbsp;&lt;br&gt;그렇게 프론트엔드 개발을 공부하면서 디자인 패턴에 대해서 많이 알게 되었고, 현재 회사에서도 여러 패턴들을 적용하며 코드를 보다보니 내가 몰랐던 패턴들도 같이 정리를 해보려고 한다!&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot;&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;생성 패턴 (Creational)&lt;/b&gt;&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;- 객체의 생성 방식에 중점을 둔다.&lt;br&gt;- 객체 생성 과정에서 복잡성을 줄이고, 코드의 유연성과 재사용성을 높이기 위해 객체 생성 로직을 추상화 한다.&lt;br&gt;- 객체를 생성하거나 초기화하는 방법을 정의하여 객체 생성의 불필요한 복잡성을 제거하고, 객체 생성을 캡슐화 한다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;▶ 생성자 패턴 (Constructor)&lt;/b&gt;&lt;/h4&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;객체를 생성하는 가장 기본적인 방법중에 하나이다. new 키워드를 사용하여 클래스의 인스턴스를 생성한다. 자바스크립트에서는 함수가 생성자의 역할을 할 수 있다. 처음 클래스를 사용할때 알고있는 new 연산자를 통하여 생성하는 패턴이니 간단하다.&lt;/li&gt;&lt;/ul&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;// 생성자 함수 정의
function Person(name, age) {
&amp;nbsp;&amp;nbsp;this.name = name;
&amp;nbsp;&amp;nbsp;this.age = age;
}

// 인스턴스 생성
const person1 = new Person('Alice', 25);
const person2 = new Person('Bob', 30);

console.log(person1.name); // Alice
console.log(person2.age); // 30&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;▶&lt;/b&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;b&gt;팩토리 패턴 (Factory)&lt;/b&gt;&lt;/h4&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;객체 생성 로직을 캡슐화하여 특정 조건에 따라 다른 클래스의 인스턴스를 반환하는 패턴이다. 복잡한 객체 생성 로직을 숨기고, 코드의 유연성과 재사용성을 높인다.&lt;/li&gt;&lt;li&gt;예제 코드만 보게되면 상속을 사용하는건가 라고 생각할 수 있지만 상속을 하지 않더라도 내부에서 조건으로 객체 생성 로직을 캡슐화 하여 객체를 생성하게도 할 수 있다.&lt;b&gt;( 객체 생성 로직을 캡슐화하여 객체 생성방식을 분리하는것 )&lt;/b&gt;&lt;/li&gt;&lt;/ul&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;// Animal 클래스 정의
class Animal {
&amp;nbsp;&amp;nbsp;constructor(name) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.name = name;
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;speak() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;console.log(`${this.name} makes a noise.`);
&amp;nbsp;&amp;nbsp;}
}

// Dog 클래스 정의
class Dog extends Animal {
&amp;nbsp;&amp;nbsp;speak() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;console.log(`${this.name} barks.`);
&amp;nbsp;&amp;nbsp;}
}

// Cat 클래스 정의
class Cat extends Animal {
&amp;nbsp;&amp;nbsp;speak() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;console.log(`${this.name} meows.`);
&amp;nbsp;&amp;nbsp;}
}

// 팩토리 함수 정의
function AnimalFactory(type, name) {
&amp;nbsp;&amp;nbsp;switch (type) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case 'dog':
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return new Dog(name);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case 'cat':
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return new Cat(name);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;default:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return new Animal(name);
&amp;nbsp;&amp;nbsp;}
}

// 인스턴스 생성
const dog = AnimalFactory('dog', 'Rex');
const cat = AnimalFactory('cat', 'Whiskers');

dog.speak(); // Rex barks.
cat.speak(); // Whiskers meows.&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;▶&lt;/b&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;b&gt;싱글톤 패턴 (Singletom)&lt;/b&gt;&lt;/h4&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;특정 클래스의 인스턴스가 하나만 존재하도록 보장하는 패턴이다.&lt;/li&gt;&lt;li&gt;애플리케이션 전체에서 동일한 인스턴스를 공유할 때 유용하다. 모든 곳에서 이 인스턴스에 접근할 수 있도록 하며, 전역상태를 관리하는데 유용할 수 있다.&lt;/li&gt;&lt;/ul&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;const Singleton = (function () {
&amp;nbsp;&amp;nbsp;let instance;

&amp;nbsp;&amp;nbsp;function createInstance() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const object = new Object(&quot;I am the instance&quot;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return object;
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;return {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;getInstance: function () {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (!instance) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;instance = createInstance();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return instance;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;};
})();

const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();

console.log(instance1 === instance2); // true&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot;&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;구조 패턴 (Structural)&lt;/b&gt;&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;- 클래스나 객체들을 더 큰 구조로 조직화하는 패턴이다. 주로 객체 간의 관계를 명확히 하거나 코드의 유연성을 높이는데 사용된다.&lt;br&gt;- 코드의 유연성, 효율성을 높이고 객체 간의 관계를 관리하기 위해 클래스와 객체를 구조적으로 조합한다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;▶&lt;/b&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;b&gt;모듈 패턴 (Module)&lt;/b&gt;&lt;/h4&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;내부 상태를 비공개로 유지하고, 공개 인터페이스만을 제공하여 코드의 구조를 개선하는 패턴이다. 주로 싱글톤과 함께 사용되어 하나의 모듈이 단일 인스턴스만을 가지도록 설계할 수 있다.&lt;/li&gt;&lt;/ul&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;const CounterModule = (function () {
&amp;nbsp;&amp;nbsp;let count = 0;

&amp;nbsp;&amp;nbsp;function increment() { count++; }
&amp;nbsp;&amp;nbsp;function decrement() { count--; }
&amp;nbsp;&amp;nbsp;function getCount() { return count; }
&amp;nbsp;&amp;nbsp;return { increment, decrement, getCount, }; })();

// 사용 예
CounterModule.increment();
CounterModule.increment();
console.log(CounterModule.getCount()); // 2&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;▶&lt;/b&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;b&gt;프록시 패턴 (Proxy)&lt;/b&gt;&lt;/h4&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;다른 객체에 대한 접근을 제어하는 대리자 객체를 제공하여 객체의 접근을 간접적으로 제어하고 보호하는 패턴이다.&lt;/li&gt;&lt;li&gt;접근 제어 및 추가적인 기능 제공, 복잡한 객체 접근 관리 등에 유용하게 사용된다.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;// 원본 객체
class RealSubject {
&amp;nbsp;&amp;nbsp;request() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;console.log('RealSubject handles request.');
&amp;nbsp;&amp;nbsp;}
}

// 프록시 객체
class Proxy {
&amp;nbsp;&amp;nbsp;constructor(realSubject) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.realSubject = realSubject;
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;request() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (this.checkAccess()) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.realSubject.request();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} else {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;console.log('Access denied.');
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;checkAccess() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 복잡한 접근 제어 로직을 여기에 구현
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return true;
&amp;nbsp;&amp;nbsp;}
}

// 사용 예
const realSubject = new RealSubject();
const proxy = new Proxy(realSubject);

proxy.request(); // RealSubject handles request.&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;▶&lt;/b&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;b&gt;데코레이터 패턴 (Decorator)&lt;/b&gt;&lt;/h4&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;객체에 동적으로 기능을 추가할 수 있도록 해주는 구조적 패턴이다.&lt;/li&gt;&lt;li&gt;상속을 사용하지 않고도 객체에 새로운 기능을 추가할 수 있어서 유연성이 높다.&lt;/li&gt;&lt;li&gt;객체 자체를 넘겨 동일한 인스턴스를 참조하여 사용하는게 일반적이다.&lt;/li&gt;&lt;/ul&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;// 기본 컴포넌트
class Coffee {
&amp;nbsp;&amp;nbsp;cost() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return 5;
&amp;nbsp;&amp;nbsp;}
}

// 데코레이터 클래스
class MilkDecorator {
&amp;nbsp;&amp;nbsp;constructor(coffee) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.coffee = coffee;
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;cost() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return this.coffee.cost() + 2;
&amp;nbsp;&amp;nbsp;}
}

// 데코레이터 클래스
class WhipDecorator {
&amp;nbsp;&amp;nbsp;constructor(coffee) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.coffee = coffee;
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;cost() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return this.coffee.cost() + 1;
&amp;nbsp;&amp;nbsp;}
}

// 사용 예
let coffee = new Coffee();
console.log(coffee.cost()); // 5

coffee = new MilkDecorator(coffee);
console.log(coffee.cost()); // 7

coffee = new WhipDecorator(coffee);
console.log(coffee.cost()); // 8&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot;&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;행위 패턴 (Behavioral)&lt;/b&gt;&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;- 객체 간의 커뮤니케이션과 책임을 분산시켜 시스템의 동작을 제어하고 조정한다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;▶&lt;/b&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;b&gt;옵저버 패턴&lt;/b&gt;&lt;/h4&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;객체의 상태 변화를 관찰하는 옵저버 목록을 유지하고, 상태가 변화할 때마다 해당 목록에 있는 모든 옵저버들에게 알림을 보내는 패턴&lt;/li&gt;&lt;li&gt;이 패턴은 일 대 다 의존성을 정의하며 객채 간의 느슨한 결합을 가능하게 한다.&lt;/li&gt;&lt;li&gt;EX) 주식 시장에서 주식 가격이 변동할 때 여러 개의 화면을 업데이트해야 할 때 옵저버 패턴을 사용할 수 있다. 각 화면(옵저버)은 주식 가격 변화를 구독하고 있고, 주식 가격이 변할 때 마다 모든 구독자에게 새로운 가격 정보를 전달한다. 어떠한 값을 관찰하고 있는것을 생각하면 이해하기 쉬울 것 같다.&lt;/li&gt;&lt;/ul&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;// 옵저버 클래스 정의
class Observer {
&amp;nbsp;&amp;nbsp;constructor(name) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.name = name;
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;update(message) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;console.log(`${this.name} received message: ${message}`);
&amp;nbsp;&amp;nbsp;}
}

// 주제(Subject) 클래스 정의
class Subject {
&amp;nbsp;&amp;nbsp;constructor() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.observers = [];
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;// 옵저버 추가
&amp;nbsp;&amp;nbsp;addObserver(observer) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.observers.push(observer);
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;// 상태 변경 시 모든 옵저버에게 알림
&amp;nbsp;&amp;nbsp;notify(message) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.observers.forEach(observer =&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;observer.update(message);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;});
&amp;nbsp;&amp;nbsp;}
}

// 사용 예
const subject = new Subject();

const observer1 = new Observer('Observer 1');
const observer2 = new Observer('Observer 2');

subject.addObserver(observer1);
subject.addObserver(observer2);

subject.notify('Hello World!');&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;▶&lt;/b&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;b&gt;커맨드 패턴 (Commond)&lt;/b&gt;&lt;/h4&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;요청을 객체로 캡슐화하여 요청을 매개변수화하고, 메서드 호출, 큐잉, 로깅 등을 지원하여 실행 취소 기능을 제공하는 패턴&lt;/li&gt;&lt;li&gt;이는 요청을 발신자(클라이언트)와 수신사(실행자) 사이에 분리시키고, 유연성을 높이며 확장성을 제공한다.&lt;/li&gt;&lt;li&gt;EX) 텍스트 에디터에서 '실행', '취소', '수정' 기능을 구현할 때 커맨드 패턴을 많이 사용한다. 각각의 작업은 커맨드 객체로 캡슐화 되어 에디터에서 발행한 모든 작업을 큐에 넣고, 사용자의 요청에 따라 순서대로 실행하거나 실행할 수 있다. 해당 실행 값을 캡슐화하여 관리하고 복잡한 상태관리를 간편하게 사용할 수 있다.&lt;/li&gt;&lt;/ul&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;// 커맨드 인터페이스 정의
class Command {
&amp;nbsp;&amp;nbsp;execute() {}
}

// 커맨드 구현 클래스
class ConcreteCommand extends Command {
&amp;nbsp;&amp;nbsp;constructor(receiver) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;super();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.receiver = receiver;
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;execute() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.receiver.action();
&amp;nbsp;&amp;nbsp;}
}

// 리시버 클래스
class Receiver {
&amp;nbsp;&amp;nbsp;action() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;console.log('Receiver is executing action.');
&amp;nbsp;&amp;nbsp;}
}

// 인보커 클래스
class Invoker {
&amp;nbsp;&amp;nbsp;constructor(command) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.command = command;
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;setCommand(command) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.command = command;
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;executeCommand() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.command.execute();
&amp;nbsp;&amp;nbsp;}
}

// 사용 예
const receiver = new Receiver();
const command = new ConcreteCommand(receiver);
const invoker = new Invoker(command);

invoker.executeCommand();&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Code note/자바스크립트</category>
      <category>데코레이터 패턴</category>
      <category>싱긅톤 패턴</category>
      <category>옵저버 패턴</category>
      <category>커맨드 패턴</category>
      <category>팩토리 패턴</category>
      <category>프록시 패턴</category>
      <author>코드노트</author>
      <guid isPermaLink="true">https://codeno-te.tistory.com/246</guid>
      <comments>https://codeno-te.tistory.com/246#entry246comment</comments>
      <pubDate>Mon, 24 Jun 2024 18:27:35 +0900</pubDate>
    </item>
    <item>
      <title>Stomp.js, SockJS / SockJS-client 정리</title>
      <link>https://codeno-te.tistory.com/245</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;실시간 이벤트처리 및 웹소켓 연결을 하게 되면서 웹소켓을 바로 사용하는게 아니라 StompJS, SockJS를 사용하는 것을 알게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 서비스에서 사용되고 있는 코드들을 해석하면서 꼭 알고 넘어가야하는 것들을 정리 해보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;WebSocket&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- http의 단방향 통신(클라이언트가 서버로 요청을 보내고, 서버에서 응답을 받는)과 다르게 서버와 양방향으로 연결하는 것을 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트가 서버로 요청을 보내게되면 서버의 응답을 받고 끝나는 것이 아닌 연결이 계속해서 유지가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rest API와 WebSocket 관련한 내용들은 따로 정리를 해둔 링크를 참고하면 좋을 것 같다.&lt;/p&gt;
&lt;figure id=&quot;og_1717378486658&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;REST API vs WebSocket 차이점 정리 feat.Polling, LongPolling, Streaming, SSE&quot; data-og-description=&quot;프로젝트에 채팅기능을 만들려고한다. WebSocket을 사용하여 실시간 통신을 하지만 WebSocket 이전의 양방향 통신 Rest 방법도 함께 정리해보려고 한다. Rest는 일반적으로 요청을 하고 서버에서 응답&quot; data-og-host=&quot;codeno-te.tistory.com&quot; data-og-source-url=&quot;https://codeno-te.tistory.com/207&quot; data-og-url=&quot;https://codeno-te.tistory.com/207&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/k6i0T/hyWgYDK0Km/UffbT0V22CokVS61xofc80/img.jpg?width=500&amp;amp;height=500&amp;amp;face=0_0_500_500,https://scrap.kakaocdn.net/dn/IbC7B/hyWdlHorUG/pelPuZoBSf7BI6LLjEPsQk/img.jpg?width=500&amp;amp;height=500&amp;amp;face=0_0_500_500,https://scrap.kakaocdn.net/dn/gD7B0/hyWdoD6ja4/3pB2VhWHW6EMik9FfkW0nk/img.jpg?width=1921&amp;amp;height=1921&amp;amp;face=0_0_1921_1921&quot;&gt;&lt;a href=&quot;https://codeno-te.tistory.com/207&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://codeno-te.tistory.com/207&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/k6i0T/hyWgYDK0Km/UffbT0V22CokVS61xofc80/img.jpg?width=500&amp;amp;height=500&amp;amp;face=0_0_500_500,https://scrap.kakaocdn.net/dn/IbC7B/hyWdlHorUG/pelPuZoBSf7BI6LLjEPsQk/img.jpg?width=500&amp;amp;height=500&amp;amp;face=0_0_500_500,https://scrap.kakaocdn.net/dn/gD7B0/hyWdoD6ja4/3pB2VhWHW6EMik9FfkW0nk/img.jpg?width=1921&amp;amp;height=1921&amp;amp;face=0_0_1921_1921');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;REST API vs WebSocket 차이점 정리 feat.Polling, LongPolling, Streaming, SSE&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;프로젝트에 채팅기능을 만들려고한다. WebSocket을 사용하여 실시간 통신을 하지만 WebSocket 이전의 양방향 통신 Rest 방법도 함께 정리해보려고 한다. Rest는 일반적으로 요청을 하고 서버에서 응답&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;codeno-te.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Stomp.js (Simple/Stream Text Oriented Message Protocol)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 텍스트 기반의 메시징 프로토콜로, 클라이언트와 메시징 브로커 간의 통신을 위한 프로토콜이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Stomp는 메시지 큐 시스템과 함께 사용되어 실시간 메시징 기능을 제공한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;왜 사용할까?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 단순화된 메시징 : STOMP는 텍스트 기반 프로토콜로 메시지를 주고 받는 형식이 간단하고 이해하기 쉽다.&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 브로커 지원 : STOMP는 RabbitMQ, ActiveMQ 등 여러 메시지 브로커와 호환된다. 따라서 이러한 브로커와 통신하기 위해 STOMP.js 를 사용할 수 있다.&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;WebSocket 통합: WebSocket API는 실시간 양방향 통신을 가능하게 하지만, 이를 직접 다루는 것은 복잡하다. STOMP.js는 WebSocket 위에서 동작하며, 메시지 송수신을 보다 간편하게 처리할 수 있게 해준다.&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;구독/발행 : STOMP.js는 Topic과 큐에 메시지를 발행하거나 구독할 수 있는 기능을 제공하여 메시지 기반 애플리케이션을 쉽게 구현할 수 있다.&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;헤더 : 헤더를 통해 인증 처리 로직 또한 구현할 수 있다&lt;/span&gt;.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;* Topic, pub/sub, 메시지 브로커, 큐 시스템   알고 넘어가자!&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Topic&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 메시지가 발행(&lt;b&gt;Pub&lt;/b&gt;lish)되고 구독(&lt;b&gt;Sub&lt;/b&gt;scribe)될 수 있는 대상이다. 메시징 시스템에서 토픽은 특정 주제나 채널을 말하며, 발행된 메시지는 해당 토픽을 구동하는 모든 클라이언트에게 전달된다. 쉽게 말하면 &lt;b&gt;&lt;u&gt;채팅방에 있는 사람들을 채팅방(Topic)에 입장(Subscribe 구독)하게 되면 다른사람들이 보낸 메시지들(Publish 발행)을 볼 수 있게 되는걸 생각&lt;/u&gt;&lt;/b&gt;하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Sub(Subscribe)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 구독(Subscribe)은 클라이언트가 특정 토픽의 메시지를 수신할 수 있도록 하는 작업이다. 클라이언트는 stomp.js의 subscribe 메서드를 사용하여 특정 토픽을 구독한다. 구독한 후에는 해당 토픽으로 발행된 모든 메시지를 실시간으로 수신할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Pub(Publish)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 발행(Publish)은 클라이언트가 특정 Topic으로 메시지를 보내는 작업이다. stomp.js의 send 메서드를 사용해서 메시지를 발행할 수 있다. 발행된 메시지는 해당 토픽을 구독한 모든 클라이언트에게 전달된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;메시지브로커&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 메시지 브로커는 다양한 시스템, 서비스, 애플리케이션 간에 메시지를 전송하는 중간 매개체 역할을 하는 소프트웨어이다. 발신자(생성자)로 부터 메시지를 받아 이를 적절한 수신자(소비자)에게 전달한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 메시지 라우팅 : 메시지를 특정 조건에 따라 적절한 수진자에게 라우팅 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 메시지 변환: 서로 다른 형식의 메시지를 변환하여 호환성을 유지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 내구성: 메시지를 안전하게 저장하고 전달 실패 시 재전송을 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;메시지 큐 시스템&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 메시지 큐 시스템은 메시지를 임시로 저장하는 큐를 사용하여 비동기적으로 통신할 수 있게 해주는 시스템이다. 발신자는 메시지를 큐에 넣고, 수신자는 큐에서 메시지를 가져가 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 비동기 통신 : 발신자와 수신자가 동시에 동작하지 않아도 메시지를 주고 받을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 부하 분산 : 여러 수신자에게 작업을 분산하여 처리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 확장성 : 시스템의 부하에 따라 수신자의 수를 유연하게 조절할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Sock.js / SockJS-Client&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 보통 WebSocket을 사용하게 되면 Sockjs를 사용하는 것을 많이 봤을 것이다. 그 이유는 Sock.js와 SockJS-Client를 통해서 &lt;u&gt;&lt;b&gt;WebSocket이 지원되지 않는 환경에서 실시간 통신을 제공&lt;/b&gt;&lt;/u&gt;할 수 있게 하기 위해서 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 즉 기본적으로는 WebSocket기반으로 실행되지만 만약 웹소켓이 실행되지 않는다면 다른 전송방법으로 자동 폴백을 시킨다. 이를 통해서 통신의 신뢰성을 높일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 다양한 전송방법을 지원하며 브라우저 호환성 문제를 해결한다. 웹 소켓이 지원되지 않는 환경에서도 작동하며, fallback 매커니즘을 통해 여러 전송 방법을 제공한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;예제 코드&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1717382293046&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import SockJS from 'sockjs-client';
import { Client } from '@stomp/stompjs';

// JWT 토큰 예시 (실제 토큰은 서버로부터 받아야 함)
const jwtToken = 'your-jwt-token';

let socket;

// STOMP 클라이언트 생성
const client = new Client({
  webSocketFactory: () =&amp;gt; {
    // SockJS 엔드포인트 설정
    socket = new SockJS('http://your-server/sockjs');
    return socket;
  },
  reconnectDelay: 5000,
  heartbeatIncoming: 4000,
  heartbeatOutgoing: 4000,
});

// 연결 이벤트 핸들러
client.onConnect = (frame) =&amp;gt; {
  console.log('Connected:', frame);

  // 토픽 구독
  const subscription = client.subscribe('/topic/sports', (message) =&amp;gt; {
    console.log('Received message:', message.body);
  });

  // 메시지 발행 (헤더에 인증 정보 추가)
  client.send('/topic/sports', { Authorization: `Bearer ${jwtToken}` }, 'Hello, this is a message!');
};

// 연결 실패 이벤트 핸들러
client.onStompError = (frame) =&amp;gt; {
  console.error('Broker reported error:', frame.headers['message']);
  console.error('Additional details:', frame.body);

  // 실패 시 SockJS 다시 실행
  restartSockJS();
};

// 연결 해제 이벤트 핸들러
client.onDisconnect = (frame) =&amp;gt; {
  console.log('Disconnected:', frame);
  // 연결 해제 시 수행할 추가 작업
};

// 클라이언트 연결
client.activate();

// 임의로 연결 해제를 원할 때 사용할 함수
function disconnect() {
  client.deactivate();
}

// SockJS 다시 실행 함수
function restartSockJS() {
  console.log('Attempting to restart SockJS...');
  socket = new SockJS('http://your-server/sockjs');
  client.webSocketFactory = () =&amp;gt; socket;
  client.activate();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적을 사용될 수 있는 메서드들로 예제를 구현했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;onConnect : 이벤트 핸들러를 설정하여 연결 성공 시&amp;nbsp;&lt;/li&gt;
&lt;li&gt;subscribe : onCunnect 내부에서 특정 주제(Topic)에 대한 메시지 수신 설정&lt;/li&gt;
&lt;li&gt;send: onCunnect 내부에서 특정 메시지 발행&lt;/li&gt;
&lt;li&gt;onStompError: 연결 실패 핸들러 실패시 sockJS 실행하여 대응&lt;/li&gt;
&lt;li&gt;activate: 클라이언트 연결&lt;/li&gt;
&lt;li&gt;deactivate: 이벤트 연결 해제를 원할 때 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹소켓도 이러한 라이브러리를 통해서 구현하면 더 쉽게 구현할 수 있는걸 알게 되었고 기존 현 회사 코드에서도 이러한 로직들을 통해서 진행되고 있다.( 실제 화상에 사용되는 이벤트들은 조금은 다르게 이벤트를 등록하고 받아 사용되지만 기본 로직은 비슷하다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹소켓 채팅 관련하여 솔루션들도 있었는데 샌드버드, 톡플러스 등이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 사이드로 진행하는 곳에서는 톡플러스를 사용해보기로 했는데 톡플러스를 사용해보고 후기도 작성해봐야겠다.&lt;/p&gt;</description>
      <category>Code note/codenote</category>
      <category>sock</category>
      <category>sockjs</category>
      <category>sockjs-client</category>
      <category>STOMP</category>
      <category>stomp.js</category>
      <category>websocket</category>
      <category>웹소켓</category>
      <category>프론트엔드</category>
      <author>코드노트</author>
      <guid isPermaLink="true">https://codeno-te.tistory.com/245</guid>
      <comments>https://codeno-te.tistory.com/245#entry245comment</comments>
      <pubDate>Mon, 3 Jun 2024 13:37:16 +0900</pubDate>
    </item>
  </channel>
</rss>