<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>journey-dev</title>
    <link>https://journey-dev.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Mon, 15 Jun 2026 08:55:30 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>journey-dev</managingEditor>
    <image>
      <title>journey-dev</title>
      <url>https://tistory1.daumcdn.net/tistory/6247544/attach/ba1877998d3c4c2ebf105f802f0e6c0d</url>
      <link>https://journey-dev.tistory.com</link>
    </image>
    <item>
      <title>Webpack 설정으로 알게된 publicPath, historyApiFallback</title>
      <link>https://journey-dev.tistory.com/197</link>
      <description>&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;웹팩 설정을 하며 배운것들이 너무너무 많다.&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;그 과정에서 특히 기억하고 싶은 publicPath와 historyApiFallback에 대해 기록해보려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;✅&amp;nbsp;publicPath에 대해&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`publicPath`는&amp;nbsp;&amp;nbsp;브라우저가 번들링된 파일들(JavaScript, CSS, 이미지 등)을 어디서 찾아야 하는지 알려주는 기본 경로이다.&lt;br /&gt;publicPath: '/'는 브라우저에게 모든 에셋을 서버의 루트 경로에서 찾아주라고 지시하는 것이다.&lt;br /&gt;&lt;br /&gt;SPA에서는 일반적으로 `publicPath: '/'`를 사용하게 된다.&lt;br /&gt;SPA는 클라이언트 측에서 라우팅을 처리하지만, 모든 라우트에서 하나의 동일한 HTML, JS파일을 사용하기 때문이다.&amp;nbsp;&lt;br /&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;요청할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;것이다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;840&quot; data-origin-height=&quot;364&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B3Tum/btsML2wWOoY/ryUwUn9WQypttqXMpEB2C1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B3Tum/btsML2wWOoY/ryUwUn9WQypttqXMpEB2C1/img.png&quot; data-alt=&quot;webpack.config.js에서의 publicPath&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B3Tum/btsML2wWOoY/ryUwUn9WQypttqXMpEB2C1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB3Tum%2FbtsML2wWOoY%2FryUwUn9WQypttqXMpEB2C1%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;489&quot; height=&quot;212&quot; data-origin-width=&quot;840&quot; data-origin-height=&quot;364&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;webpack.config.js에서의 publicPath&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;✔️ 문제) publicPath 설정이 없을 때 발생하는 오류&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 웹팩의 output 설정에서 publicPath를 지정하지 않고 프로젝트를 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 결과,&amp;nbsp; 특정&amp;nbsp;경로에서 새로고침을 할 때 &lt;b&gt;404 오류&lt;/b&gt;가 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, /movie/123 경로에서 새로고침을 하자 아래와 같은 오류가 발생한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1456&quot; data-origin-height=&quot;434&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pdcak/btsMK43jjx0/FBkrjrqkOGCgBuElpPS1bk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pdcak/btsMK43jjx0/FBkrjrqkOGCgBuElpPS1bk/img.png&quot; data-alt=&quot;&amp;quot;bundle.js&amp;quot;가 상대경로로 붙여있음&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pdcak/btsMK43jjx0/FBkrjrqkOGCgBuElpPS1bk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpdcak%2FbtsMK43jjx0%2FFBkrjrqkOGCgBuElpPS1bk%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;712&quot; height=&quot;212&quot; data-origin-width=&quot;1456&quot; data-origin-height=&quot;434&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;&quot;bundle.js&quot;가 상대경로로 붙여있음&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1380&quot; data-origin-height=&quot;354&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RRUWx/btsMK43jjn7/ym4pgerzvkSuA987I1SNRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RRUWx/btsMK43jjn7/ym4pgerzvkSuA987I1SNRK/img.png&quot; data-alt=&quot;bundle.js 파일 경로가 상대경로로 되있어, /bundle.js가 아니라 /movie/bundle.js로 찾게됨.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RRUWx/btsMK43jjn7/ym4pgerzvkSuA987I1SNRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRRUWx%2FbtsMK43jjn7%2Fym4pgerzvkSuA987I1SNRK%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;716&quot; height=&quot;184&quot; data-origin-width=&quot;1380&quot; data-origin-height=&quot;354&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;bundle.js 파일 경로가 상대경로로 되있어, /bundle.js가 아니라 /movie/bundle.js로 찾게됨.&lt;/figcaption&gt;
&lt;/figure&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;publicPath가 설정되지 않으면, 기본값은 상대경로(./ 또는 &quot;&quot;)이고, 이로 인해, bundle.js의 경로가 상대경로로 지정되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에 브라우저는 http://localhost:3000/movie/bundle.js 경로에서 bundle.js를 찾으려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;루트 경로의 movie폴더에서 bundle.js파일을 찾는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 웹팩이 빌드한 bundle.js는 dist 폴더 내에 위치하기 때문에 잘못된 경로 요청을 하게되어 404 오류가 발생한다.&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;h4 data-ke-size=&quot;size20&quot;&gt;✔️ 해결) publicPath : &quot;/&quot; 설정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하기 위해 publicPath : &quot;/&quot; 설정을 추가하여 모든 에셋의 경로가 루트 경로('/')를 기준으로 로드되도록 처리해주었다.&lt;br /&gt;모든&amp;nbsp;에셋의&amp;nbsp;경로가&amp;nbsp;루트&amp;nbsp;경로를&amp;nbsp;기준으로&amp;nbsp;하는&amp;nbsp;절대&amp;nbsp;경로가&amp;nbsp;되는&amp;nbsp;것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;※ publicPath: '/'에서 말하는 &quot;루트 경로&quot;란?&lt;br /&gt;웹팩이 빌드를 후 생성된 정적 파일들이 위치한 dist폴더를 만한다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;- 빌드 전&lt;/b&gt;: app/src, app/public, webpack.config.js 등&lt;br /&gt;&lt;b&gt;&lt;b&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;빌드 후&lt;/b&gt;: app/dist/bundle.js, app/dist/index.html, app/dist/other-assets/...&lt;br /&gt;&lt;br /&gt;publicPath: '/' 설정을하면, 개발서버는 dist폴더를 정적 파일을 서빙하는 루트 폴더로 설정한다.&lt;br /&gt;즉, 웹팩에서 설정한 &quot;publicPath : /&quot;는 개발서버가 dist 폴더를 루트로 인식하고, 그 안에 있는 모든 에셋을 로드하라는 지시인 것이다!&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 `/movie/123` 페이지에서도 브라우저는 항상 루트 경로에서 에셋을 찾으므로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;http://localhost:3000/bundle.js로 정확하게 파일을 요청하게 된다.&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-origin-width=&quot;890&quot; data-origin-height=&quot;434&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/I6Kel/btsMLt22lrS/VEn88VKHVQuTwJEAffVi50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/I6Kel/btsMLt22lrS/VEn88VKHVQuTwJEAffVi50/img.png&quot; data-alt=&quot;/절대경로로 루트 경로에서 시작되도록 처리&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/I6Kel/btsMLt22lrS/VEn88VKHVQuTwJEAffVi50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FI6Kel%2FbtsMLt22lrS%2FVEn88VKHVQuTwJEAffVi50%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;569&quot; height=&quot;277&quot; data-origin-width=&quot;890&quot; data-origin-height=&quot;434&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;/절대경로로 루트 경로에서 시작되도록 처리&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1460&quot; data-origin-height=&quot;350&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwmefX/btsMMeqmCx8/O4qzjDH7TPRrDEfsZMPXA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwmefX/btsMMeqmCx8/O4qzjDH7TPRrDEfsZMPXA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwmefX/btsMMeqmCx8/O4qzjDH7TPRrDEfsZMPXA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwmefX%2FbtsMMeqmCx8%2FO4qzjDH7TPRrDEfsZMPXA0%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;626&quot; height=&quot;150&quot; data-origin-width=&quot;1460&quot; data-origin-height=&quot;350&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; 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;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;✅ historyApiFallback 에 대해&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 spa의 특성으로 인해 publicPath 설정이 필요했는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;historyApiFallback 설정도 spa의 특성과도 연관이 있다는 것을 알게되었다.&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;✔️ historyApiFallback란?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;SPA에서 존재하지 않는 URL로 접근시, 404 대신 index.html을 제공하여, &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;클라이언트 사이드 라우팅이 정상적으로 작동하게 해주는 설정이다.&lt;/span&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;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&quot;/movie/123&quot;과 같은 경로는 React Router같은 클라이언트 사이드 라우팅 경로고, 실제 서버에선 이 경로로 파일이 없다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;때문에 브라우저가 서버에 &quot;/movie/123&quot;경로를 요청하면, 서버는 404 에러를 일으키게 된다.&lt;br /&gt;이때 historyApiFallback를 통해 404에러시 자동으로 index.html로 리다이렉트 시켜주는 것이다.&lt;br /&gt;그러면&amp;nbsp;React앱이&amp;nbsp;다시&amp;nbsp;로드되고,&amp;nbsp;React&amp;nbsp;Router를&amp;nbsp;통해&amp;nbsp;현재&amp;nbsp;URL에&amp;nbsp;맞는&amp;nbsp;컴포넌트가&amp;nbsp;렌더링된다.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&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;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;✔️&amp;nbsp;historyApiFallback와&amp;nbsp;publicPath&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;이렇게 historyApiFallback와 publicPath를 제대로 세팅해야 SPA 앱이 올바르게 리소스를 가져올 수 있게 된다.&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;publicPath:&amp;nbsp;'/'&amp;nbsp;:&amp;nbsp;모든&amp;nbsp;에셋(JS,&amp;nbsp;CSS,&amp;nbsp;Html,&amp;nbsp;등)을&amp;nbsp;루트&amp;nbsp;경로&amp;nbsp;기준으로&amp;nbsp;로드&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;historyApiFallback:&amp;nbsp;true&amp;nbsp;:&amp;nbsp;없는&amp;nbsp;경로&amp;nbsp;요청&amp;nbsp;시&amp;nbsp;index.html로&amp;nbsp;폴백&amp;nbsp;처리&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;1.&amp;nbsp;사용자:&amp;nbsp;'/movie/123'&amp;nbsp;새로고침&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;2.&amp;nbsp;서버:&amp;nbsp;이&amp;nbsp;경로는&amp;nbsp;없는데?&amp;nbsp;-&amp;gt;&amp;nbsp;historyApiFallback&amp;nbsp;발동&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;3.&amp;nbsp;서버:&amp;nbsp;index.html&amp;nbsp;반환&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;4.&amp;nbsp;브라우저:&amp;nbsp;index.html&amp;nbsp;로드&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;5.&amp;nbsp;index.html:&amp;nbsp;publicPath='/'에&amp;nbsp;의해&amp;nbsp;루트&amp;nbsp;경로에서&amp;nbsp;모든&amp;nbsp;에셋&amp;nbsp;로드&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;6.&amp;nbsp;React&amp;nbsp;Router:&amp;nbsp;'/movie/123'&amp;nbsp;URL을&amp;nbsp;보고&amp;nbsp;해당&amp;nbsp;컴포넌트&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;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;✅&lt;span&gt;&amp;nbsp; 느낀점&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹팩이 단순히 번들러 역할을 넘어서, 파일 경로 문제까지도 해결할 수 있다는 것을 알게되었다.&lt;br /&gt;특히 spa는 클라이언트 측 라우팅을 사용하다보니, 서버에서 리소스 로딩 방식을 직접 명시적으로 설정해줘야 함을 알게되었다!&lt;br /&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;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;a href=&quot;https://webpack.kr/configuration/output/#outputpublicpath&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://webpack.kr/configuration/output/#outputpublicpath&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://webpack.kr/configuration/dev-server/#devserverhistoryapifallback&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://webpack.kr/configuration/dev-server/#devserverhistoryapifallback&lt;/a&gt;&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;</description>
      <category>프론트엔드</category>
      <author>journey-dev</author>
      <guid isPermaLink="true">https://journey-dev.tistory.com/197</guid>
      <comments>https://journey-dev.tistory.com/197#entry197comment</comments>
      <pubDate>Sun, 16 Mar 2025 02:10:40 +0900</pubDate>
    </item>
    <item>
      <title>클로저를 활용해 useState Hook 직접 구현해보기</title>
      <link>https://journey-dev.tistory.com/190</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅&amp;nbsp;들어가며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;javascript deep dive를 읽으며 js코어 개념을 익히곤했다.&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;br /&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;그런데 이미 리액트 useState가 클로저를 활용한 개념이라고 한다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 글은 JSConf 의 useState관련 영상을 보며 공부한 내용이다.(&lt;a href=&quot;https://www.youtube.com/watch?v=KJP1E-Y-xyo&quot;&gt;※ 영상&lt;/a&gt;)&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅&amp;nbsp;useState와 클로저&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1️⃣ 클로저란 무엇인가?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로저(Closure)는 내부함수가 외부 함수의 변수에 접근할 수 있도록 해주는 JavaScript 기능이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로져를 통해 함수가 &amp;ldquo;privte&amp;rdquo;한 변수를 갖을 수 있게 해준다. (변수 은닉화)&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;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;function getAdd() {
  let foo = 1; // 클로저로 접근가능
  return function () {
    foo += 1;
    return foo;
  };
}

const add = getAdd();
console.log(add()); // 2
console.log(add()); // 3
console.log(add()); // 4&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getAdd 함수는 내부에 foo라는 변수를 가지고 있고, 반환되는 함수는 foo에 접근하고 수정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;foo는 add 함수가 호출될 때마다 갱신된다.&lt;/p&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;b&gt;2️⃣ 클로저 개념을 활용한 리액트 useState 만들기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트의 useState 는 내부적으로 클로저를 사용하여 상태를 관리를 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 원리를 알아보기 위해 직접 useState를 만들며 알아보겠다.&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2-1. 클로저를 사용하지 않은 useState 예시&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;function useState(initVal) {
  let _val = initVal;
  const state = _val; // 클로저 사용X
  const setState = newVal =&amp;gt; {
    _val = newVal;
  };
  return [state, setState];
}

const [count, setCount] = useState(1);
console.log(count); // 1
setCount(2);
console.log(count); // 1 (변경되지 않음)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setState(2)를 실행해도, count는 여전히 1로 고정된 값이 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setState는 _val을 업데이트하는 함수이지만, state는 useState가 처음 실행될 때 _val의 값을 그대로 가져와 &lt;b&gt;복사된 값&lt;/b&gt;을 유지하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;state는 클로저를 사용하지 않았기 때문에, useState 함수 실행이 끝난 후 상태 값이 가비지 컬렉션에 의해 삭제된다.&lt;/p&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;b&gt;2-2. 클로저를 사용한 useState 구현&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function useState(initVal) {
  let _val = initVal;
  const state = () =&amp;gt; _val; // 클로저를 사용하여 _val을 동적으로 반환
  const setState = newVal =&amp;gt; {
    _val = newVal;
  };
  return [state, setState];
}

const [count, setCount] = useState(1);
console.log(count()); // 1
setCount(2);
console.log(count()); // 2 (변경됨)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로저를 사용하면 state는 최신 값을 동적으로 가져올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부 함수인 useState가 종료된 후에도, 내부 함수인 state와 setState는 외부 함수의 실행 컨텍스트에 있는 변수 _val을 계속 참조할 수 있다. 때문에 _val은 가비지 컬렉션의 대상이 되지 않고, 계속해서 상태 값으로 유지된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 state는 _val를 클로저로 참조하고 있기 때문에 state()를 호출하면 최신 값을 동적으로 가져올 수 있는 것이다.&lt;/p&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;b&gt;2-3. 컴포넌트에서 useState Hook 사용하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트 컴포넌트는 여러 useState를 배열에 저장하고, 이를 통해 여러 상태를 관리하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에서 각 useState 호출은 클로저로 관리되는 상태 값들을 지속적으로 참조하게 된다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const React = (function () {
  let hooks = [];
  let idx = 0;

  function useState(initVal) {
    const _idx = idx;
    const state = hooks[idx] || initVal;
    const setState = newVal =&amp;gt; {
      hooks[_idx] = newVal;
    };
    idx++; // 인덱스를 증가시켜서 다음 훅을 처리
    return [state, setState];
  }

  function render(Component) {
    idx = 0;
    const C = Component();
    C.render();
    return C;
  }

  return { useState, render };
})();

function Component() {
  const [count, setCount] = React.useState(1);
  const [text, setText] = React.useState('apple');

  return {
    render: () =&amp;gt; console.log({ count, text }),
    click: () =&amp;gt; setCount(count + 1),
    type: (word) =&amp;gt; setText(word),
  }
}

var App = React.render(Component); // { count: 1, text: &quot;apple&quot; }
App.click();
var App = React.render(Component); // { count: 2, text: &quot;apple&quot; }
App.type('pear');
var App = React.render(Component); // { count: 2, text: &quot;pear&quot; }
&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;b&gt;1) hooks, idx , _idx는 클로저로 작동된다.&lt;br /&gt;&lt;/b&gt;이&amp;nbsp;값들은&amp;nbsp;외부&amp;nbsp;함수(React&amp;nbsp;즉시실행함수)에서&amp;nbsp;정의되었고,&amp;nbsp;내부&amp;nbsp;함수인&amp;nbsp;useState와&amp;nbsp;render에서&amp;nbsp;참조하고&amp;nbsp;있다.&lt;br /&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;유지된다.&lt;br /&gt;클로저&amp;nbsp;덕분에&amp;nbsp;외부함수(React&amp;nbsp;즉시실행함수)가&amp;nbsp;실행이&amp;nbsp;끝나도,&amp;nbsp;hooks와&amp;nbsp;idx는&amp;nbsp;가비지&amp;nbsp;컬렉팅되지&amp;nbsp;않고,&amp;nbsp;상태&amp;nbsp;값들을&amp;nbsp;계속&amp;nbsp;유지할&amp;nbsp;수&amp;nbsp;있다.&lt;br /&gt;때문에 내부함수(useState , render)도 이 값에 계속 접근할 수 있는 것이다.&lt;b&gt;&lt;br /&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;b&gt;2) 클로저를 활용한 setState 인덱스 고정&lt;br /&gt;&lt;/b&gt;setState가 호출될 때마다 인덱스가 변동되는 문제를 해결하기 위해 &lt;b&gt;클로저를&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 이용해&amp;nbsp;&lt;/span&gt;&lt;b&gt;state 할당 당시의 인덱스&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;를 고정한다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;count는 0인 idx를 갖고, text는 1인 idx를 갖게된다.&lt;br /&gt;hooks 배열은 [count, text] 가 저장되고 각 hook은 고정된 idx를 통해 접근할 수 있는 것이다.&lt;br /&gt;이런 방식으로 각 상태가 의도한 배열 위치에 정상적으로 저장되고 업데이트 되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ Hooks 규칙에 대해&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서 React 컴포넌트와 Hooks를 직접 만들면서 왜 hooks를 배열로 만들어야 하는지와 hook규칙의 이유에 대해 알 수 있었다.&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. hooks 배열과 인덱스 관리를 통해 hook이 실행된다.&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 &amp;ldquo;클로저를 활용한&amp;nbsp;setState&amp;nbsp;인덱스 고정&amp;rdquo; 에서 말한 것 처럼 hook은 &amp;ldquo;&lt;u&gt;고정된 idx&lt;/u&gt;&amp;rdquo;를 가지고 이 인덱스를 통해 상태 값을 관리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나아가 React는 하나의 렌더링 사이클 동안 호출된 모든 hooks를 &amp;ldquo;&lt;u&gt;배열&lt;/u&gt;&amp;rdquo;에 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 hooks 배열에서 상태의 저장 위치가 고정된 idx를 통해 보장되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를통해 hooks가 여러개 있을시 &lt;u&gt;hook의 실행 순서를 보장&lt;/u&gt;하고, 여러 상태 관리를 순서대로 저장할 수 있다.&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;2. Hooks 규칙이 있는 이유 : hook 호출 순서 보장 위해&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function MyComponent() {
  const handleClick = () =&amp;gt; {
    const [state, setState] = React.useState(0); // ❌ React Hook 규칙 위반
    console.log(state);
  };

  return &amp;lt;button onClick={handleClick}&amp;gt;Click Me&amp;lt;/button&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트는 Hooks는 항상 컴포넌트의 최상위에서만 호출되어야 하는 규칙이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이유는 렌더링될 때마다 Hook의 호출 순서를 보장하기 위해서다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;렌더링이 일어날 때마다 Hooks가 일정한 순서로 호출되어야만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 Hook이 배열에 저장된 상태값을 올바르게 참조하고, 상태를 업데이트할 수 있게된다.&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;lt;이 hook의 규칙을 어긴다면?&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 이 규칙을 어기고 조건문이나 반복문 안에서 Hook을 호출한다면, 매 렌더링마다 Hook의 호출 순서가 바뀔 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게되면 hooks 배열에 상태 값이 저장되는 순서가 깨지므로, hook의 실행 순서가 달라지게 될 것이고&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;예를들면, 위의 &amp;lsquo;컴포넌트에서 useState hook 사용하기&amp;rsquo; 예제에서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hooks 배열은 [count, text] 순서로 저장되어 count와 text를 각각 고유한 인덱스 0, 1에 할당한 상태이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 만약 count 상태의 useState hook의 호출이 조건부로 실행이 된다면 이 고유한 idx값이 틀어지게된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;text useState의 idx가 0이될때도 1이 될때도 있게되어 hook의 호출 순서가 깨진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에 hooks 배열에 올바른 상태를 참조하지 못하거나 상태를 제대로 업데이트 하지 못하게 되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(text 상태를 참조했는데 idx 0인 count값이 참조될 수도 있고, setText로 상태 업데이트시 count값을 업데이트 해버리는 오류 발생!)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;✅ 마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useState와 클로저의 원리에 대해 배우며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트에서 빈번히 사용되는 개념에 이러한 js코어 원리가 담겨있다는 것을 알고 코어 개념의 활용을 알 수 있었다.&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;또한 hook의 규칙의 의미까지 알게되었다.&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;나아가 hook이 배열에 일일히 다 기억되고 있다는 것도 흥미로웠다.&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>프론트엔드/React</category>
      <author>journey-dev</author>
      <guid isPermaLink="true">https://journey-dev.tistory.com/190</guid>
      <comments>https://journey-dev.tistory.com/190#entry190comment</comments>
      <pubDate>Sun, 2 Mar 2025 19:15:58 +0900</pubDate>
    </item>
    <item>
      <title>Controlled vs Uncontrolled (input요소 value와 defaultValue 차이)</title>
      <link>https://journey-dev.tistory.com/188</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;React에서 input 요소를 사용할 때 value와 defaultValue는 어떤 차이가 있을지 의문이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 보기엔, defaultValue는 초기값 설정해주는 것 같은데 이런건 또 언제 써야하는걸까?&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;br /&gt;&lt;b&gt;Controlled Component(제어 컴포넌트)&lt;/b&gt; 와 &lt;b&gt;Uncontrolled Component(비제어 컴포넌트)&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;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;요약&amp;gt;&lt;br /&gt;리액트에서 input과 같은 폼 요소를 다룰때,&lt;br /&gt;&lt;br /&gt;Controlled 컴포넌트는 value에 state를 넣어 상태를 관리한다. &lt;br /&gt;상태가 업데이트될 때마다 폼 전체가 리렌더링된다.&lt;span style=&quot;color: #333333; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;Uncontrolled 컴포넌트는 defaultValue로 값을 설정하고, ref를 사용해 DOM에 직접 접근하여 값을 관리한다.&lt;br /&gt;상태를 사용하지 않아서 리렌더링 횟수를 줄여 성능을 개선할 수 있다.&lt;span style=&quot;color: #333333; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;✅ Controlled vs Uncontrolled&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React에서는 폼 요소의 상태를 관리하는 두 가지 방식이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;state와 통합하는 방식인 Controlled Component 와 DOM을 직접 제어하는 방식인 Uncontrolled Component 이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1️⃣ Controlled Component (제어 컴포넌트)&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;input 초기값을 &lt;u&gt;value&lt;/u&gt;로 설정하고,&lt;/li&gt;
&lt;li&gt;폼 요소(input)의 값(value)을 &lt;u&gt;state&lt;/u&gt;를 통해 관리하는 방식이다.&lt;/li&gt;
&lt;li&gt;onChange 이벤트 핸들러를 통해 입력값을 업데이트된다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;장점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상태 변경에 따라 UI가 업데이트되어 , 상태와 입력값이 &lt;u&gt;동기화&lt;/u&gt;된다.&lt;/li&gt;
&lt;li&gt;state만 관리하면 되서 폼 데이터를 다루기 쉽다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;단점 : 상태 업데이트에 따라 &lt;u&gt;리렌더링이&lt;/u&gt; 발생해서 성능 문제생길 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { useState } from 'react';

function ControlledInput() {
  const [text, setText] = useState(''); // React state로 값 관리하고

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;input
        type=&quot;text&quot;
        value={text}  // state가 input 값이 됨.
        onChange={event =&amp;gt; setText(event.target.value)} // 변경 시 state 업데이트
      /&amp;gt;
      &amp;lt;p&amp;gt;입력된 값: {text}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2️⃣ Uncontrolled Component (비제어 컴포넌트)&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;input 초기값을 &lt;u&gt;defaultValue&lt;/u&gt;로 설정하고,&lt;/li&gt;
&lt;li&gt;폼 요소의 값을 &lt;u&gt;useRef&lt;/u&gt;로 사용해 DOM에 직접 접근하여 값을 가져오는 방식이다.&lt;/li&gt;
&lt;li&gt;값을 React가 관리하지 않고, 값의 변경을 계속 관찰하지 않는다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;장점 : 상태를 따로 관리하지 않아도 되므로, 리렌더링으로 인한 &lt;u&gt;성능&lt;/u&gt; 문제 발생하지 않는다.&lt;/li&gt;
&lt;li&gt;단점 : React와 DOM 간의 &lt;u&gt;동기화가&lt;/u&gt; 번거로워 값을 추적하기 어렵다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { useRef } from 'react';

function UncontrolledInput() {
  const inputRef = useRef(null); // useRef로 input 요소 참조

  const handleClick = () =&amp;gt; {
    console.log(inputRef.current.value); // input값을 직접 DOM에서 가져온다!
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;input type=&quot;text&quot; ref={inputRef} defaultValue=&quot;초기값&quot; /&amp;gt;
      &amp;lt;button onClick={handleClick}&amp;gt;값 출력&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;✅ 각각 언제 사용해야 할까?&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1️⃣&amp;nbsp;Controlled Component를 사용해야 하는 경우&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;입력값을 &lt;u&gt;실시간으로&lt;/u&gt; 관리하고 싶을때 (ex. 검색창, 실시간 폼 입력).&amp;nbsp;&lt;/li&gt;
&lt;li&gt;React 상태를 기반으로 동작하는 UI가 필요할 때 (ex. 입력값을 기반으로 다른 UI 변경).&lt;/li&gt;
&lt;li&gt;동일한 입력값을 다른 컴포넌트에서도 사용해야 할 때.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2️⃣&amp;nbsp;Uncontrolled Component를 사용해야 하는 경우&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;u&gt;성능이&lt;/u&gt; 중요해서 state 업데이트를 최소화하고 싶을 때.&lt;/li&gt;
&lt;li&gt;입력값 변경이 &lt;u&gt;자주 발생하지 않을때&lt;/u&gt;&lt;/li&gt;
&lt;li&gt;폼 데이터를 즉각적으로 저장할 필요가 없을 때&lt;/li&gt;
&lt;li&gt;사용자가 입력한 값을 한 번만 가져오면 되는 경우 (ex. 폼 제출 시).&lt;/li&gt;
&lt;li&gt;외부 라이브러리와의 호환성이 필요할 때 (ex. jQuery, D3.js 등과 함께 사용할 경우).&lt;/li&gt;
&lt;/ol&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;ldquo;react-hook-form&amp;rdquo;는 uncontrolled component 기반의 라이브러리다! &lt;b&gt; &lt;/b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://react-hook-form.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;react-hook-form 사이트&lt;/a&gt;에서, uncontrolled 기반인 폼과, controlled인 폼을 비교해볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;controlled 컴포넌트는 input창 입력시마다 리렌더링되지만 uncontrolled는 그렇지 않다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image (3).png&quot; data-origin-width=&quot;2312&quot; data-origin-height=&quot;1206&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m5KF2/btsMjMPrxNN/FnrV8183Mm7G9KUe6kkBr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m5KF2/btsMjMPrxNN/FnrV8183Mm7G9KUe6kkBr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m5KF2/btsMjMPrxNN/FnrV8183Mm7G9KUe6kkBr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm5KF2%2FbtsMjMPrxNN%2FFnrV8183Mm7G9KUe6kkBr0%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;563&quot; height=&quot;294&quot; data-filename=&quot;image (3).png&quot; data-origin-width=&quot;2312&quot; data-origin-height=&quot;1206&quot;/&gt;&lt;/span&gt;&lt;/figure&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;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;폼 구현시 흔히 써왔던 react-hook-form에 이런 내용이 있는줄은 몰랐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;input요소 하나 입력시 폼 전체의 input요소가 리렌더링 되지 않는 것이 uncontrolled 컴포넌트 방식이기 때문이라니!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 몰랐다면 나는 그냥 라이브러리가 알아서 잘 구현됐다보다 라고 생각하고 편하게 쓰기만 했을것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;defaultValue도 단순히 초기값을 위한게 아니라는 것을 알게되어 유익한 시간이였다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프론트엔드/React</category>
      <author>journey-dev</author>
      <guid isPermaLink="true">https://journey-dev.tistory.com/188</guid>
      <comments>https://journey-dev.tistory.com/188#entry188comment</comments>
      <pubDate>Sun, 16 Feb 2025 19:57:31 +0900</pubDate>
    </item>
    <item>
      <title>requestAnimationFrame를 활용해 애니메이션 최적화 해보자</title>
      <link>https://journey-dev.tistory.com/186</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. requestAnimationFrame (RAF)란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RequestAnimationFrame 은 브라우저 내장 API이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저 렌더링 주기(보통 60FPS)에 맞춰 콜백을 실행한다.&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;때문에 애니메이션 구현시, setInterval 대신 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;requestAnimationFrame을 더 많이 활용한다.&lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. requestAnimationFrame 동작원리&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;브라우저는 다음 렌더링 시점에 애니메이션을 예약하고 '&lt;u&gt;매 프레임마다 자동으로 호출&lt;/u&gt;'한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저의 리페인트 주기(60FPS) 에 맞춰 실행되는 것이다.&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;즉, 브라우저가 1초당 출력할 수 있는 프레임 수(fps) 기준으로 requestAnimationFrame(callback)을 사용하면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;그&amp;nbsp;프레임 간격마다 애니메이션이 반복 실행된다.&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;※ Frame 이란?&lt;br /&gt;우리가 보는 영상이나 애니메이션은 정지된 이미지가 연속적으로 빠르게 바뀌는 것이다.&lt;br /&gt;이 낱장의 정지된 이미지 하나를 프레임(Frame) 이라고 한다.&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;※ fps란? (Frames&amp;nbsp;Per&amp;nbsp;Second)&lt;br /&gt;초당 프레임 수를 의미하며, 60fps는 1초 동안 60장의 화면을 그린다는 뜻이다.&lt;br /&gt;일반적인 브라우저는 60FPS(1초당 60번 갱신) 을 목표로 한다.&lt;br /&gt;1프레임 간격은 약 16.67ms(1초 &amp;divide; 60) 이다.&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. requestAnimationFrame (RAF)와 태스크 큐&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;비동기 실행을 위한 콜백함수들은 태스크 큐에 대기하며,&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;태스크 큐엔 1.마이크로 태스크 큐 2.매크로 태스크 큐가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 &lt;u&gt;requestAnimationFrame가 이 두 태스크 큐 사이에 위치&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;✅&lt;/b&gt; 태스크 큐의 작업 순서&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.&amp;nbsp;&lt;b&gt;마이크로 태스트 큐&lt;/b&gt;&amp;nbsp;: Promise 후속처리 메서드 콜백함수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.&amp;nbsp;&lt;b&gt;requestAnimationFrame&lt;/b&gt;&amp;nbsp;: requestAnimationFrame 처럼 브라우저 렌더링 관련된 콜백함수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.&amp;nbsp;&lt;b&gt;매크로 태스크 큐&lt;/b&gt;&amp;nbsp;: setTimeout 등 그 외 나머지 비동기 콜백함수&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt; console.log(&quot;Start&quot;); // 1

// 마이크로 태스크: Promise
Promise.resolve().then(() =&amp;gt; {
  console.log(&quot;Microtask 1: Promise then&quot;); // 3
}).then(() =&amp;gt; {
  console.log(&quot;Microtask 2: Chained Promise then&quot;); // 4
});

// 애니메이션 프레임: requestAnimationFrame
requestAnimationFrame(() =&amp;gt; {
  console.log(&quot;Animation Frame: requestAnimationFrame callback&quot;); // 5
});

// 매크로 태스크: setTimeout
setTimeout(() =&amp;gt; {
  console.log(&quot;Macrotask: setTimeout callback&quot;); // 6
}, 0);

console.log(&quot;End&quot;); // 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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. requestAnimationFrame 사용&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1738501132896&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;requestAnimationFrame(callback);&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;callback : 다음 프레임이 렌더링될 때 실행할 함수. 리페인트 될 때 호출된다.&lt;/li&gt;
&lt;li&gt;반환값 : 고유한 id를 반환한다. &lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;이 값을 window.cancelAnimationFrame(id) 에 전달해 애니메이션을 취소할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;✅&lt;/b&gt;&amp;nbsp;사용코드 예제&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;requestAnimationFrame()은 한 번만 실행되므로 반복하려면 재귀적으로 호출해야 한다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;let rafY = 0;
let rafId

// 일정한 간격으로 애니메이션 발생
 function animateRAF(timestamp) { 
    rafY += 2; 
    rafBox.style.transform = `translateY(${rafY}px)`;
    rafId = requestAnimationFrame(animateRAF);
  }
  
document.getElementById('start').addEventListener('click', () =&amp;gt; {
	  rafId = requestAnimationFrame(animateRAF); // 애니메이션 발생
});

document.getElementById('stop').addEventListener('click', () =&amp;gt; {
	  cancelAnimationFrame(rafId); // 애니메이션 취소
});
&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;b&gt;✅ 활용 &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;CSS 애니메이션 대신 JavaScript 으로 애니메이션 구현시&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Canvas 에서 애니메이션 구현시 자주쓰인다. (ex. Canvas , WebGL 렌더링)&lt;/li&gt;
&lt;li&gt;부드러운 스크롤 효과 (ex. Parallax, Scroll Animation)&lt;/li&gt;
&lt;/ol&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. 왜 setInterval 대신 requestAnimationFrame을 써야 하나? (RAF 특징)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;✅&lt;span&gt;&amp;nbsp;&lt;/span&gt;실행 주기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. setInterval은 &lt;br /&gt;&lt;u&gt;고정된&amp;nbsp;시간&amp;nbsp;간격&lt;/u&gt;(setInterval(()&amp;rArr;{},&amp;nbsp;&lt;u&gt;16ms&lt;/u&gt;))으로&amp;nbsp;코드를&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;2. requestAnimationFrame은 &lt;br /&gt;&lt;u&gt;브라우저의&amp;nbsp;화면&amp;nbsp;프레임&amp;nbsp;주기&lt;/u&gt;(일반적으로&amp;nbsp;60&lt;u&gt;FPS&lt;/u&gt;, 약 16.67ms)에 맞춰 실행된다. 브라우저의 리프레시 속도에 맞춰 실행되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;rarr;&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&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;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;리소스 효율성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. setInterval은 불필요한 CPU 사용을 초래할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;탭이&amp;nbsp;비활성화되거나, 창이 최소화 되었을&amp;nbsp;때도&amp;nbsp;계속&amp;nbsp;실행된다.&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;p data-ke-size=&quot;size16&quot;&gt;2. requestAnimationFrame은 &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;비활성화된 상태에서는 &lt;u&gt;자동으로 멈추게된다&lt;/u&gt;. &lt;br /&gt;&lt;b&gt;&amp;rarr;&lt;/b&gt; 불필요한 연산을 줄여 CPU 사용을 절약할 수 있다.&amp;nbsp;(리소스 낭비 방지)&lt;/span&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;b&gt;✅&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;타이밍 정확도&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. setInterval은 타이밍이 정확하지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setInterval(()&amp;rArr;{} , 16ms) 는 사실 고정된 시간 간격(16ms)로 정확히 실행되지 않는다.&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;이때 CPU사용량이 많아지거나, 작업에 부하가 있다면 setInterval의 콜백은 16ms보다 더 대기하게 되서 늦게 실행될 수 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;rarr;&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&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;p data-ke-size=&quot;size16&quot;&gt;2. requestAnimationFrame은 브라우저 렌더링 사이클과 정확히 동기화되서 &lt;u&gt;프레임 간 일관성&lt;/u&gt;이 유지된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;브라우저 리페인트 주기(60FPS, 약16.67ms)마다 requestAnimationFrame의 callback이 자동으로 실행된다.&lt;/li&gt;
&lt;li&gt;CPU가 부하가 많더라도, 브라우저가 알아서 조정해서 다음 프레임을 실행할 타이밍을 맞추게된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;rarr;&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. &lt;span data-token-index=&quot;0&quot;&gt;구현예제 - &lt;/span&gt;&lt;span data-token-index=&quot;0&quot;&gt;setInterval VS requestAnimationFrame&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;iframe src=&quot;https://stackblitz.com/edit/vitejs-vite-dh9bavwu?embed=1&amp;amp;file=index.html&quot; width=&quot;100%&quot; height=&quot;500px&quot;&gt;&lt;/iframe&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;requestAnimationFrame은 일정한 속도로 부드럽게 이동하지만, setInterval은 확연히 끊기는 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금은 간단한 예시지만, 좀 더 복잡한 인터렉션이 발생하고 CPU에 부하가 높아지면 setInterval 은 확연히 더 느려질 것이다.&lt;/p&gt;</description>
      <category>프론트엔드</category>
      <author>journey-dev</author>
      <guid isPermaLink="true">https://journey-dev.tistory.com/186</guid>
      <comments>https://journey-dev.tistory.com/186#entry186comment</comments>
      <pubDate>Sun, 2 Feb 2025 22:17:29 +0900</pubDate>
    </item>
    <item>
      <title>타입스크립트의 필요성에 대해</title>
      <link>https://journey-dev.tistory.com/175</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;✅&amp;nbsp;들어가며&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;취업 준비 시절, 나는 타입스크립트까지 제대로 공부하지 못한 채 취업에 성공했다. &lt;br /&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;
&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;br /&gt;만약 처음부터 타입 가드를 적용해 &lt;u&gt;undefined.length&lt;/u&gt; 같은 상황을 처리했다면,&amp;nbsp;&lt;br /&gt;&lt;u&gt;TypeError: Cannot read property of undefined&lt;/u&gt; 이런 런타임 오류가 발생하는 문제를 예방할 수 있었을 것이다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 나는 타입스크립트를 잘 활용하기 위해 &lt;a href=&quot;https://github.com/type-challenges/type-challenges&quot;&gt;&lt;b&gt;TypeScript Challenge&lt;/b&gt;&lt;/a&gt;를 풀며 하나씩 학습을 이어가고 있다. &lt;br /&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span&gt;✅&lt;/span&gt;&amp;nbsp;TypeScript와 Javascript&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TypeScript는 JavaScript의 &lt;b&gt;Superset(상위 확장)이다.&lt;br /&gt;&lt;/b&gt;기존 JavaScript 코드를 그대로 사용하면서,&amp;nbsp;JavaScript에 정적 타입 시스템을 덧붙여 개발자 경험을 향상시킨 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image (1).png&quot; data-origin-width=&quot;445&quot; data-origin-height=&quot;445&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4f9Jy/btsLEsb5tTn/BmHAwdmgkDp5Rb4bNd8Jhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4f9Jy/btsLEsb5tTn/BmHAwdmgkDp5Rb4bNd8Jhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4f9Jy/btsLEsb5tTn/BmHAwdmgkDp5Rb4bNd8Jhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4f9Jy%2FbtsLEsb5tTn%2FBmHAwdmgkDp5Rb4bNd8Jhk%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;268&quot; height=&quot;268&quot; data-filename=&quot;image (1).png&quot; data-origin-width=&quot;445&quot; data-origin-height=&quot;445&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;일단 ts와 js의 차이를 알기 위한 배경지식으로 &quot;인터프리터와 컴파일러&quot;를 이해해야 한다.&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;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;인터프리터&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;컴파일러&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;작동 방식&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;명령어 단위로 번역하며 즉시 실행&lt;/td&gt;
&lt;td&gt;전체 코드를 한 번에 번역 후 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;오류 발견 시점&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;실행 중 (런타임)&lt;/td&gt;
&lt;td&gt;컴파일 시 (실행 전)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;- 코드 수정 후 즉시 실행 가능&lt;br /&gt;- 빠른 피드백 가능&lt;/td&gt;
&lt;td&gt;- 실행 속도가 빠름&lt;br /&gt;- 사전 오류 검출 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;- 실행 속도가 느림&lt;br /&gt;- 런타임 오류 발생 가능&lt;/td&gt;
&lt;td&gt;- 초기 컴파일 시간 소요&lt;br /&gt;- 잦은 수정 시 비효율적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;대표 언어&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;JavaScript, Python 등&lt;/td&gt;
&lt;td&gt;TypeScript, C, Java 등&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;size18&quot;&gt;&lt;b&gt;인터프리터 (Interpreter)&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;/li&gt;
&lt;li&gt;컴파일러와 달리 전체 코드를 한번에 번역하지 않고, 소스코드 명령어 한줄마다 기계어로 번역하여 바로 실행되는 것이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;장점 &lt;/b&gt;: 번역과 실행이 동시에 이뤄져 즉각적인 결과 확인 가능 (실행 즉시 번역됨)(잦은 코드 수정시 유리함).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단점1 &lt;/b&gt;: 한번에 한 문장씩 번역후 실행시키기 때문에 실행 속도가 느리다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단점2&lt;/b&gt; : 코드 실행 중에만 오류를 발견할 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;컴파일러 (Compiler)&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;변환(컴파일) 단계에서 문법,타입 오류 확인하며 변환된 파일이 실행된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;장점1 &lt;/b&gt;: 실행 전에 모든 오류 검출 &lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(코드 컴파일 하는 과정에서 오류 발생하게된다)&lt;/span&gt;,&lt;/li&gt;
&lt;li&gt;&lt;b&gt;장점2&lt;/b&gt; : 빠른 실행 속도 (처음에 한번에 기계어로 컴파일 해놔서 실행이 빠르다)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단점&lt;/b&gt;: 초기 번역 시간이 길고 잦은 수정에 비효율적.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(참조 : &lt;a style=&quot;color: #0070d1; text-align: start;&quot; href=&quot;https://velog.io/@jhur98/%EC%BB%B4%ED%8C%8C%EC%9D%BC%EB%9F%ACcompiler%EC%99%80-%EC%9D%B8%ED%84%B0%ED%94%84%EB%A6%AC%ED%84%B0interpreter%EC%9D%98-%EC%B0%A8%EC%9D%B4&quot;&gt;컴파일러(compiler)와 인터프리터(interpreter)의 차이&lt;/a&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt; 인터프리터&amp;nbsp;언어&amp;nbsp;vs&amp;nbsp;&amp;nbsp;컴파일러&amp;nbsp;언어&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;1. JavaScript (인터프리터 언어)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JavaScript는 인터프리터 방식으로 실행되기 때문에, 코드를 실행하는 과정에서 오류를 발견한다.&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;span data-token-index=&quot;0&quot;&gt;2. TypeScript (컴파일 언어)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TypeScript는 컴파일러를 통해 JavaScript로 변환되기 전에 코드의 문법과 타입을 검증한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일 단계에서 오류를 미리 발견하므로, 런타임에서 발생할 수 있는 문제를 줄일 수 있게된다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt; 동적&amp;nbsp;타입&amp;nbsp;언어&amp;nbsp;vs&amp;nbsp;정적&amp;nbsp;타입&amp;nbsp;언어&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;1. JavaScript &lt;b&gt;(동적 타입 언어)&lt;/b&gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;1) 타입 결정 시점 : 런타임시. 변수에 어떤 데이터가 들어갈지는 실행 중에 확인된다.&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;2) 안정성 : 낮음 (런타임 오류 가능)&lt;br /&gt;개발 단계에서 오류를 확인하기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램&amp;nbsp;실행&amp;nbsp;중에야&amp;nbsp;오류를&amp;nbsp;발견할&amp;nbsp;수&amp;nbsp;있어&amp;nbsp;사용자에게&amp;nbsp;영향을&amp;nbsp;미칠&amp;nbsp;수&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;3) 타입 변경 : 코드 실행시 자유로운 타입 변경 허용&lt;/p&gt;
&lt;pre id=&quot;code_1736082639176&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function sum(a, b) {
  return a + b;
}
sum(1, 2);       // 3
sum(&quot;x&quot;, &quot;y&quot;);   // 'xy' (문자열 결합)&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;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;2. TypeScript&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;(정적 타입 언어)&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;1) 타입 결정 시점 : 컴파일시. 변수의 타입이 런타임 이전의 컴파일 단계에서 결정된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;2) 안정성 : 높은 (컴파일시 오류 검출)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;3) 타입 변경 : 타입 변경시 오류 발생&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1736086144224&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function sum(a: number, b: number): number {
  return a + b;
}
sum(&quot;x&quot;, &quot;y&quot;);
// error TS2345: Argument of type '&quot;x&quot;' is not assignable to parameter of type 'number'.
// 컴파일 에러: 'string' 타입은 'number' 타입에 할당할 수 없음&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span&gt;✅&lt;/span&gt; TypeScript를 사용하는 이유&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. &lt;b&gt;런타임 에러 감소로 안정성 강화&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JavaScript는 동적 타입 언어로, 실행 중 오류가 발생할 가능성이 크다.&lt;br /&gt;이로인해 사용자에게 그 오류가 그대로 노출될 위험이 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1736082879800&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function greetUser(userName) {
  console.log(&quot;Hello, &quot; + userName.toUpperCase() + &quot;!&quot;);
}

// 올바른 호출
greetUser(&quot;Alice&quot;); // 출력: Hello, ALICE!

// 잘못된 호출 - userName에 undefined가 전달됨
greetUser(); // 런타임 오류 발생: TypeError: Cannot read properties of undefined (reading 'toUpperCase')

// 잘못된 호출 - userName에 숫자가 전달됨
greetUser(123); // 런타임 오류 발생: TypeError: userName.toUpperCase is not a function&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;반면, TypeScript는 컴파일 단계에서 오류를 발견해 실행 전 수정할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1736082901807&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function add(a: number, b: number): number {
  return a + b;
}

add(10, &quot;20&quot;); // 컴파일 에러 발생: '20'은 string 타입&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. &lt;b&gt;개발자 경험(DX) 향상&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TypeScript는 IDE의 자동완성, 타입 추론, 코드 탐색 기능을 강화하여 개발 생산성을 높인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 파라미터의 형태나 타입을 vscode에서 즉시  확인할 수 있어 코드 작성 속도가 빨라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 이 점이 개발하면서 정말 편했던 것 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1736082944953&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const user = {
  name: &quot;Alice&quot;,
  age: 25,
};

console.log(user.); // 자동완성을 통해 'name'과 'age'를 빠르게 확인 가능&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. &lt;b&gt;유지보수성 증가&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 타입을 명확히 선언하여, 의도치 않게 타입을 변경하는 문제를 방지할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 디버깅을 수월하게 할 수 있다 : 개발시 vscode에서 오류를 바로 알려주기 떄문에 문제를 빠르게 파악할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 협업시 인터페이스를 정의하는데 도움이 되기도 한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1736083564230&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface User {
  id: number;
  name: string;
  email?: string; // 선택적 속성
}

function createUser(user: User): void {
  console.log(user);
}

createUser({ id: 1, name: &quot;John&quot; }); // 올바른 호출
createUser({ id: 2 }); // 컴파일 에러 발생: 'name' 속성 누락
createUser({ id: &quot;3&quot; }); // 컴파일 에러 발생: 'string' 형식은 'number' 형식에 할당할 수 없습니다.&lt;/code&gt;&lt;/pre&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span&gt;✅&lt;/span&gt; 타입스크립트 컴파일러 동작원리&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;npx&amp;nbsp;tsc&amp;nbsp;note.ts&lt;/u&gt;를&amp;nbsp;실행하면&amp;nbsp;note.ts&amp;nbsp;파일이&amp;nbsp;note.js로&amp;nbsp;변환이&amp;nbsp;된다.&lt;br /&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;TypeScript의 컴파일러(tsc)는 고급 프로그래밍 언어인 TypeScript 코드를 브라우저나 Node.js에서 실행 가능한 JavaScript 코드로 변환하는 역할을 한다. 이 과정은 크게 4단계로 이루어진다.&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;1. 파일 로드 및 파싱&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TypeScript 컴파일러는 프로젝트의 설정 파일(tsconfig.json)을 읽어들인 뒤, 컴파일 대상 파일을 식별한다.&lt;br /&gt;그런 다음, TypeScript 파일(.ts, .tsx)을 로드하고 각 파일을 파싱하여 추상 구문 트리(AST, Abstract Syntax Tree)를 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 작성한 코드는 컴파일러에게는 단순한 텍스트에 불과하기 때문에 AST로 파싱하는 것이다.&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;AST는 우리가 작성한 코드를 &lt;b&gt;컴파일러가 이해할 수 있는 형태로 구조화한 트리&lt;/b&gt;이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AST는 이후 과정에서 타입 검사 및 변환 작업에 활용된다.&lt;/p&gt;
&lt;pre id=&quot;code_1736083714737&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1. typescript코드
const add = (a: number, b: number): number =&amp;gt; {
  return a + b;
};

// 2. 위 코드를 컴파일러가 읽으면, 다음과 같은 AST를 생성한다.
Program
├── VariableDeclaration
│   ├── Identifier: &quot;add&quot;
│   ├── ArrowFunctionExpression
│       ├── Parameters
│       │   ├── Identifier: &quot;a&quot; (type: number)
│       │   ├── Identifier: &quot;b&quot; (type: number)
│       ├── ReturnType: number
│       ├── Body
│           ├── ReturnStatement
│               ├── BinaryExpression
│                   ├── Identifier: &quot;a&quot;
│                   ├── Operator: &quot;+&quot;
│                   ├── Identifier: &quot;b&quot;&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;size18&quot;&gt;&lt;b&gt;2. 타입 검사&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TypeScript의 핵심 기능인 &lt;b&gt;정적 타입 검사&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;가 이 단계에서 이루어진다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일러는 생성된 AST를 기반으로 각 변수, 함수, 객체 등의 타입을 확인하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 타입 오류가 발견되면 &lt;b&gt;컴파일을 중단하&lt;/b&gt;고 해당 &lt;b&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;3. js코드 생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입 검사가 완료된 후, TypeScript 컴파일러는 AST를 변환하여 &lt;b&gt;JavaScript 코드로 출력&lt;/b&gt;한다.&lt;br /&gt;TypeScript 코드에서 작성한 타입 정보는 JavaScript 코드에는 포함되지 않고, 실행 가능한 &lt;b&gt;순수 JavaScrip&lt;span data-token-index=&quot;0&quot;&gt;t 코드&lt;/span&gt;&lt;/b&gt;&lt;span data-token-index=&quot;0&quot;&gt;만 생성된다.&lt;/span&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;- 컴파일러 옵션(target)에 따라 출력되는 JavaScript 코드의 표준(ES5, ES6 등)이 결정된다.&lt;/p&gt;
&lt;pre id=&quot;code_1736083957962&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// tsconfig.json
{&quot;target&quot;: &quot;es2016&quot;, ...}&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;size18&quot;&gt;&lt;b&gt;4. 출력과 번들링&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;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://develog-yeon.tistory.com/35&quot;&gt;https://develog-yeon.tistory.com/35&lt;/a&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;✅&amp;nbsp;결론&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트를 사용해보니, &amp;ldquo;한 번 써보면 다시는 안 쓸 수 없다&amp;rdquo;는 말이 뭔지 확 와닿았다.&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;지난 ts없이 개발하던 회사 프로젝트에서는 &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;변수의 타입이 확실치 않아 불안감이 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹여 라이브러리가 숫자를 문자열로 응답하지는 않을까 싶어 typeof로 확인해야 했고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 데이터 값이 undefined일 때 가드 처리를 빼먹지는 않았을까 하는 걱정이 항상 따라다녔다.&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;p data-ke-size=&quot;size16&quot;&gt;또한 런타임 에러를 미리 방지하며 개발을 한다는 점에 안도감도 들었다.&lt;/p&gt;
&lt;div&gt;
&lt;div data-message-model-slug=&quot;gpt-4o&quot; data-message-id=&quot;6f90ab00-8b19-4efe-bb34-0d56a7954a9a&quot; data-message-author-role=&quot;assistant&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트를 사용하지 않았던 과거의 경험 덕분에, 지금은 TS의 필요성과 강력함을 더욱 체감하고 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>프론트엔드/Typescript</category>
      <author>journey-dev</author>
      <guid isPermaLink="true">https://journey-dev.tistory.com/175</guid>
      <comments>https://journey-dev.tistory.com/175#entry175comment</comments>
      <pubDate>Sun, 5 Jan 2025 21:41:51 +0900</pubDate>
    </item>
    <item>
      <title>Custom React 구현하기</title>
      <link>https://journey-dev.tistory.com/174</link>
      <description>&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;&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;p data-ke-size=&quot;size16&quot;&gt;Fiber 노드 객체의 속성과 뎁스가 너무 많아 코드를 파악하기 어려웠다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 문제를 해결하기 위해 각 노드 객체의 관계를 그림으로 직접 그려보며 구조를 하나씩 이해해나갔다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;react1.png&quot; data-origin-width=&quot;1274&quot; data-origin-height=&quot;1892&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ea47K0/btsLrkTBGFo/kZWKRhL66jaLOnC8jnAzG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ea47K0/btsLrkTBGFo/kZWKRhL66jaLOnC8jnAzG0/img.png&quot; data-alt=&quot;거의 그림 그리면서 코드 파악했다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ea47K0/btsLrkTBGFo/kZWKRhL66jaLOnC8jnAzG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fea47K0%2FbtsLrkTBGFo%2FkZWKRhL66jaLOnC8jnAzG0%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;323&quot; height=&quot;480&quot; data-filename=&quot;react1.png&quot; data-origin-width=&quot;1274&quot; data-origin-height=&quot;1892&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;거의 그림 그리면서 코드 파악했다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://pomb.us/build-your-own-react/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;튜토리얼 사이트&amp;nbsp;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/pomber/didact&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;전체코드&lt;/a&gt;&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;/p&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;b&gt;1. &lt;span data-token-index=&quot;1&quot;&gt;Fiber란?&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1) 정의&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fiber는 각 DOM 노드(element)마다 생성되는 객체로, Fiber Tree를 구성하는 작업 단위이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fiber Tree는 렌더링 프로세스를 관리하기 위한 구조인데, &lt;br /&gt;이를 기반으로 react는 DOM Tree를 업데이트를 효율적으로 관리하고 최적화한다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Fiber로 작업 단위를 나누는 이유는?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래에 설명할 동시성 렌더링(Concurrent Rendering)을 하기 위해서다. &lt;br /&gt;Fiber Tree 생성 작업을 한 번에 처리하지 않고, 우선순위가 높은 작업이 있다면 Fiber 생성을 중단 했다가 &lt;br /&gt;다시 idle 상태에서 이어서 처리하기 위해서이다.&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) Fiber의 구조&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;const fiber = {
  type: &quot;div&quot;,
  props: {
    id: &quot;root&quot;,
    className: &quot;container&quot;,
    children: [],
  },
  parent: null,
  sibling: null,
  child: null,
  dom: null,
  alternate: null,
  effectTag: &quot;&quot; 
};
&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;size18&quot;&gt;&lt;b&gt;3) Fiber Tree 예제&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function App() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;
        &amp;lt;p&amp;gt;p&amp;lt;/p&amp;gt;
        &amp;lt;a&amp;gt;a&amp;lt;/a&amp;gt;
      &amp;lt;/h1&amp;gt;
      &amp;lt;h2&amp;gt;&amp;lt;/h2&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

const container = document.getElementById(&quot;root&quot;);
const element = &amp;lt;App /&amp;gt;;

Didact.render(element, container);

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드의 Fiber Tree는 아래와 같이 구성된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;react2.png&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;1040&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JlNrF/btsLs2YyagW/rmNbPTiURhn8Zramwt8KBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JlNrF/btsLs2YyagW/rmNbPTiURhn8Zramwt8KBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JlNrF/btsLs2YyagW/rmNbPTiURhn8Zramwt8KBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJlNrF%2FbtsLs2YyagW%2FrmNbPTiURhn8Zramwt8KBK%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;391&quot; height=&quot;472&quot; data-filename=&quot;react2.png&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;1040&quot;/&gt;&lt;/span&gt;&lt;/figure&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;h1 노드의 fiber를 출력해보면 실제 이런식으로 구성되어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;h1 노드의 Fiber는 부모, 자식, 형제 관계가 객체 형태로 모두 연결되있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;react3.png&quot; data-origin-width=&quot;1198&quot; data-origin-height=&quot;532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chBfta/btsLqDTFPem/ArTv42flgTDUlhIJqTs1p0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chBfta/btsLqDTFPem/ArTv42flgTDUlhIJqTs1p0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chBfta/btsLqDTFPem/ArTv42flgTDUlhIJqTs1p0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchBfta%2FbtsLqDTFPem%2FArTv42flgTDUlhIJqTs1p0%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;493&quot; height=&quot;219&quot; data-filename=&quot;react3.png&quot; data-origin-width=&quot;1198&quot; data-origin-height=&quot;532&quot;/&gt;&lt;/span&gt;&lt;/figure&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;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. &lt;span data-token-index=&quot;1&quot;&gt;동시성 렌더링 (Concurrent Rendering)&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1) 정의&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시성 렌더링은 Fiber 조작(생성, 삭제, 수정)을 한 번에 처리하지 않고, &lt;b&gt;idle 상태&lt;/b&gt;에서 작업을 나누어 처리하며 &lt;br /&gt;우선순위가 높은 작업이 즉시 처리되도록 설계된 React 렌더링 모델이다. &lt;br /&gt;이를 통해 더 중요한 작업(ex: 사용자 인터랙션)을 우선 처리하여 사용자 경험을 개선할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(React 18 버전부터 도입된 기능)&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2) 특징&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;작업 단위 쪼개기: Fiber Tree를 만드는 과정을 나누어 처리한다. 그 나눈 작업 단위를 Fiber 라고 한다.&lt;/li&gt;
&lt;li&gt;Idle 상태 활용: Fiber 조작 과정은 idle 상태(유휴 상태)일 때 진행된다.&lt;/li&gt;
&lt;li&gt;작업 우선순위 설정: 더 중요한 작업(예: 사용자 입력)은 우선 처리한다. (이땐 idle 상태가 아니게됨)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;react4.png&quot; data-origin-width=&quot;844&quot; data-origin-height=&quot;242&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOtOGr/btsLrgjkhuv/HXv1XWdUzqQU2PYjc7AfEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOtOGr/btsLrgjkhuv/HXv1XWdUzqQU2PYjc7AfEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOtOGr/btsLrgjkhuv/HXv1XWdUzqQU2PYjc7AfEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOtOGr%2FbtsLrgjkhuv%2FHXv1XWdUzqQU2PYjc7AfEK%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;513&quot; height=&quot;147&quot; data-filename=&quot;react4.png&quot; data-origin-width=&quot;844&quot; data-origin-height=&quot;242&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3) 필요성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;동시성 렌더링이 없으면?&lt;br /&gt;&lt;/b&gt;fber 생성 과정이 중단되지 않고 한번에 진행된다.&lt;b&gt;&lt;br /&gt;&lt;/b&gt;그래서 도중 사용자 인터렉션이 발생한면 사용자 인터렉션 작업은 뒤로 밀리게된다.&lt;br /&gt;&amp;rarr; 결과적으로 사용자 경험이 저하된다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;동시성 렌더링이 있으면?&lt;br /&gt;&lt;/b&gt;fber 생성 작업이 나뉘어 idle 상태에서 처리되므로, 사용자 입력과 같은 우선순위가 높은 작업이 즉시 처리된다.&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&amp;rarr; 결과적으로 사용자 경험이 개선된다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4) 구현&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;튜토리얼에서는 window.requestIdleCallback을 사용해 idle 상태를 감지하고, 이 상태에서 Fiber 생성 작업을 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 이는 학습을 위한 구현이고, 실제 React는 자체적으로 스케줄링 알고리즘을 구현했다. (&lt;a href=&quot;https://github.com/facebook/react/blob/5309f102854475030fb91ab732141411b49c1126/packages/scheduler/src/forks/Scheduler.js#L209&quot;&gt;React 코드 참고&lt;/a&gt;) &lt;br /&gt;React는 Scheduler 패키지를 사용하여 우선순위와 작업 시간을 관리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(requestIdleCallback는 사파리에서 지원하지 않는다고 한다.)&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. Commit 단계&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커밋 단계에서는 생성된 Fiber Tree를 기반으로 DOM Tree에 최종 렌더링 작업 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fiber 속성인 effectTag를 통해 DOM 노드의 생성, 수정, 삭제를 결정한다. 이 작업은 한 번에 처리된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;react5.png&quot; data-origin-width=&quot;1254&quot; data-origin-height=&quot;722&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sQymA/btsLqX5lR8a/vLChkCdv801QuddOFrVnIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sQymA/btsLqX5lR8a/vLChkCdv801QuddOFrVnIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sQymA/btsLqX5lR8a/vLChkCdv801QuddOFrVnIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsQymA%2FbtsLqX5lR8a%2FvLChkCdv801QuddOFrVnIk%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;539&quot; height=&quot;310&quot; data-filename=&quot;react5.png&quot; data-origin-width=&quot;1254&quot; data-origin-height=&quot;722&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;4. 재조정 (Reconciliation)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1) 정의&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재조정은 리렌더링시, 가상 DOM과 실제 DOM을 비교하여 변경된 부분만 업데이트하는 과정이다. &lt;br /&gt;이를 통해 불필요한 DOM 업데이트를 줄이고 성능을 최적화할 수 있다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2) 구현&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;이전 Fiber와 비교하여 변경된 부분만 새롭게 생성된 Fiber로 대체합니다.&lt;/li&gt;
&lt;li&gt;Fiber Tree를 새로 구성한 뒤&lt;/li&gt;
&lt;li&gt;똑같이 Commit 단계에서 변경된 부분만 실제 DOM에 반영하게 된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&amp;lt;마치며&amp;gt;&lt;/b&gt;&lt;/h3&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;Deep Dive라는게 이런거구나 라는 생각도 든다.&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;
&lt;p data-ke-size=&quot;size16&quot;&gt;참조사이트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@mask_vvs/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%8F%99%EC%8B%9C%EC%84%B1%EC%9D%B4%EB%9E%80&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;리액트 동시성이란?&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@jangws/React-Fiber&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;React Fiber&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://dev.to/qhphan/build-your-own-react-fiber-tree-2kij&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;React Fiber Tree&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://medium.com/@sht02048/%EC%83%9D%EA%B0%81%EB%B3%B4%EB%8B%A4-%EB%8D%94-%EC%84%AC%EC%84%B8%ED%95%9C-%EB%A6%AC%EC%95%A1%ED%8A%B8-%20 %EB%A0%8C%EB%8D%94%EB%A7%81-%EA%B3%BC%EC%A0%95-feat-%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%8C%8C%EC%9D%B4%EB%B2%84-44075084381a&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;생각보다 더 섬세한 리액트 렌더링 과정&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@jinyoung234/Render-and-commit&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Render-and-commit&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://react-ko.dev/learn/render-and-commit&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;render-and-commit2&lt;/a&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>프론트엔드/React</category>
      <author>journey-dev</author>
      <guid isPermaLink="true">https://journey-dev.tistory.com/174</guid>
      <comments>https://journey-dev.tistory.com/174#entry174comment</comments>
      <pubDate>Sun, 22 Dec 2024 18:09:09 +0900</pubDate>
    </item>
    <item>
      <title>동기 VS 비동기, Promise와 async/await</title>
      <link>https://journey-dev.tistory.com/172</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 JS코어를 다시 공부 하고있다.&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;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;h2 data-ke-size=&quot;size26&quot;&gt;✅ 동기 , 비동기&lt;/h2&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;자바스크립트는 &amp;ldquo;싱글 스레드&amp;rdquo; 방식이기 때문에, 한번에 하나의 태스크만 처리할 수 있다.&lt;br /&gt;이렇게 하나의 작업이 끝날 때까지 다음 작업이 대기하는 방식이 동기 처리이다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// taskOne의 처리가 끝나기 전 까진 taskTwo는 대기하게 된다. 
function taskOne() {
  for (let i = 0; i &amp;lt; 1000000000; i++) {} // 시간이 걸리는 작업
  console.log(&quot;taskOne&quot;);
}

function taskTwo() {
  console.log(&quot;tasktwo&quot;);
}

taskOne();
taskTwo();&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;size18&quot;&gt;&lt;b&gt;비동기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 현재 실행 중인 작업이 종료되지 않은 상태라도, 다음 작업을 곧바로 실행할 수 있다.&lt;br /&gt;이것이 비동기 처리.&lt;br /&gt;&lt;br /&gt;비동기 처리 요청을 보낸 후 응답 여부와 상관없이 다음 태스크를 병렬적으로 수행할 수 있는 방식이다.&lt;br /&gt;이 방식은 http 요청, 타이머 설정, 이벤트 핸들러 등 시간이 오래 걸리는 작업을 처리할 때 유용하다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// taskOne을 먼저 호출했지만 setTimeout은 taskTwo가 실행된 이후에 실행된다.
// 비동기 콜백 함수(setTimeout)는 작업이 바로 완료되지 않고, 이후에 실행될 수 있다.
function taskOne() {
  setTimeout(() =&amp;gt; { 
    console.log(&quot;taskOne&quot;);
  }, 1000);
}

function taskTwo() {
  console.log(&quot;tasktwo&quot;);
}

taskOne();
taskTwo();&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;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;✅&lt;span&gt;&amp;nbsp;&lt;/span&gt;비동기 처리는 어떻게 가능한 것인가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 비동기 처리는 브라우저에서 실행되는 1)이벤트 루프와 2)태스크 큐를 통해 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비동기 함수는 바로 실행되는게 아니라 먼저 태스크 큐에 등록된다.&lt;br /&gt;&lt;br /&gt;이후, 이벤트 루프가 콜 스택에 실행 중인 작업이 없음을 감지하면, 큐에 있는 작업을 콜 스택으로 이동시킨다.&lt;br /&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;br /&gt;&lt;a href=&quot;https://dev.to/lydiahallie/javascript-visualized-promises-async-await-5gke&quot;&gt;(※ 그림 출처)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8Gt6v/btsKUmRh8in/oWkKd1poEl7nuDqUT4P0wk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8Gt6v/btsKUmRh8in/oWkKd1poEl7nuDqUT4P0wk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8Gt6v/btsKUmRh8in/oWkKd1poEl7nuDqUT4P0wk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8Gt6v%2FbtsKUmRh8in%2FoWkKd1poEl7nuDqUT4P0wk%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;1000&quot; height=&quot;420&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;420&quot;/&gt;&lt;/span&gt;&lt;/figure&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;1. 자바스크립트 엔진 구성 : Heap과 Call Stack&lt;br /&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; font-size: 16px; letter-spacing: 0px;&quot;&gt;Heap&lt;/span&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;/li&gt;
&lt;li&gt;call stack에서 함수가 실행될 때, 실행 중 필요한 객체는 Heap에서 참조하게 되는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Call Stack&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;함수가 실행될 때 Call Stack에 쌓이고, 실행이 완료되면 Call Stack에서 제거된다.&lt;/li&gt;
&lt;li&gt;자바스크립트는 싱글 스레드로 동작하므로 한 번에 하나의 작업만 이 스택에서 실행되는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. Call Stack에서 함수 실행&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;greet() 함수 호출하면 &amp;rarr; greet함수가 Call Stack에 들어와 실행 &amp;rarr; 실행 완료 후 &amp;rarr; Call Stack에서 제거된다.&lt;br /&gt;이후, respond() 함수 호출되면 &amp;rarr; Call Stack에 들어와 실행 &amp;rarr; setTimeout이 실행된다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. Web API로 비동기 작업 위임&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setTimeout은 브라우저에서 제공하는 Web API로 타이머 작업을 백그라운드에서 처리하도록 넘겨진다.&lt;br /&gt;여기서 타이머(1000ms)가 완료될 때까지 Call Stack은 중단되지 않고, 그 사이에 다른 작업들을 계속 처리할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1732437862141&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;console.log(&quot;Start&quot;); 

setTimeout(() =&amp;gt; { 
	console.log(&quot;This is delayed&quot;); // 비동기 콜백함수는 web api로 위임되고, 
}, 1000); 

console.log(&quot;End&quot;); // 1000ms 후에 실행되는 비동기 작업을 기다리지 않고, 다음 작업을 바로 실행한다.&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;size18&quot;&gt;&lt;b&gt;4. Queue에 작업 추가&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타이머가 완료되면, setTimeout 콜백 함수(() =&amp;gt; { return &quot;Hey!&quot; })가 Queue로 이동한다.&lt;br /&gt;&lt;br /&gt;Queue는 두가지로 나뉜다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;마이크로 태스크 큐: 프로미스의 후속 처리 메서드(then, catch, finally)에 사용된 &quot;콜백 함수&quot;가 저장.&lt;/li&gt;
&lt;li&gt;매크로 태스크 큐: setTimeout과 같은 타이머의 &quot;콜백 함수&quot;나 이벤트 핸들러가 저장됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;5. Event Loop (이벤트 루프)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Queue는 이벤트 루프에 의해 관리된다.&lt;br /&gt;이벤트 루프는 Call Stack이 비어 있는지 확인한다.&lt;br /&gt;Call Stack이 비어 있으면, Queue에 있는 작업을 Call Stack으로 가져와 실행하게 된다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;6. 콜백 함수 실행&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setTimeout의 콜백 함수가 Call Stack으로 이동해 실행된다.&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 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;✅&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;JS에서 비동기 처리 다루는 방식&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1️⃣ 콜백 함수&lt;/b&gt;&lt;/h3&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;콜백 헬&lt;/b&gt;&lt;br /&gt;비동기 작업을 순차적으로 처리하기 위해, 콜백 함수 안에 다음에 실행할 콜백함수를 넣게되는데&lt;br /&gt;이렇게 콜백함수가 계속 중첩되면서 코드의 뎁스가 깊어져 코드가 복잡해지는 현상이다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// script1로드 후에, script2 로드, script3 로드하는 비동기 작업 연결

loadScript('/my/script1.js', (script) =&amp;gt; {
  loadScript('/my/script2.js', (script) =&amp;gt; {
    loadScript('/my/script3.js', (script) =&amp;gt; {
      // 세 스크립트 로딩이 끝난 후 실행됨
    });
  })
});&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;&lt;b&gt;콜백 헬은 왜 문제되는가?&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;가독성 문제&lt;/li&gt;
&lt;/ol&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;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;2&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;에러 처리의 어려움&lt;br /&gt;동기 코드에서처럼 try-catch 문으로 에러 처리할 수 없다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1732439868565&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;   try {
     setTimeout(() =&amp;gt; {throw new Error(&quot;Error!&quot;);}, 1000);
   } catch (error) {
     console.error(error);
   }&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;setTimeout의 콜백함수가 실행될 시점에는, 이미 try{..}catch{..} 부분은 동기 실행으로 모두 실행이 종료된 상태다.&lt;br /&gt;이후 비동기 실행만 덩그러니 실행되는 꼴이므로, 에러 객체는 catch로 받을 수 없게된다.&lt;/p&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;b&gt;2️⃣ Promise&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;  Promise란?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비동기 작업의 성공 또는 실패를 처리하고, 후속 작업을 체계적으로 연결할 수 있는 JavaScript의 내장 객체&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Promise로 인해 비동기 작업이 여러개가 중첩되도 콜백 지옥 해결할 수 있게되었다.&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;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;b&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;Promise 객체&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;function myPromise() {
  return new Promise((resolve, reject) =&amp;gt; {
      const data = fetch();

      if (data) {
        resolve(data);
      } else {
        reject(&quot;Error&quot;);
      }
  });
}

myPromise()
  .then((value) =&amp;gt; {
    console.log(&quot;Success:&quot;, value); // &quot;Success: data값&quot;
  })
  .catch((error) =&amp;gt; {
    console.error(&quot;Error:&quot;, error); // &quot;Error: Error&quot;
  })
  .finally(() =&amp;gt; {
    console.log(&quot;Operation complete&quot;);
  });&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;Promise 생성자에는 executor라는 콜백 함수를 인자로 받는다.&lt;br /&gt;그리고 executor 함수는 &lt;code&gt;resolve&lt;/code&gt; 및 &lt;code&gt;reject&lt;/code&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;code&gt;resolve(value)&lt;/code&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;/li&gt;
&lt;li&gt;resolve의 매개변수 value는 성공시 전달할 값이다.&lt;/li&gt;
&lt;li&gt;이 값은 .then() 메서드의 첫 번째 콜백함수로 전달되어 후속 작업에 사용된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;reject(error)&lt;/code&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;/li&gt;
&lt;li&gt;reject의 매개변수 error는 실패 시 전달할 값이다.&lt;/li&gt;
&lt;li&gt;이 값은 .catch() 메서드의 첫 번째 콜백함수로 전달되어 에러 처리에 사용된다.&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;size18&quot;&gt;&lt;b&gt;&lt;b&gt; &lt;/b&gt;&amp;nbsp;Promise 상태&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. pending (대기중)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Promise 객체는 resolve 또는 reject가 호출될 때까지 기다리게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 상태에서는 then,catch가 실행되지 않는다.&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;2. fulfilled (성공)&lt;/b&gt;&lt;br /&gt;&lt;code&gt;resolve&lt;/code&gt;가 호출되면, Promise는 fulfilled(성공) 상태로 바뀌고, &lt;code&gt;.then()&lt;/code&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;3. rejected (실패)&lt;/b&gt;&lt;br /&gt;&lt;code&gt;reject&lt;/code&gt;가 호출되면, Promise는 rejected(실패) 상태로 바뀌고, &lt;code&gt;.catch()&lt;/code&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;Promise는 상태가 한 번 결정(settled)되면 더 이상 변경되지 않는다.&lt;br /&gt;때문에 fulfilled, rejected 상태를 settled 상태라고도 표현한다.&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;size18&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt; &lt;/b&gt;&lt;/b&gt;후속 처리 메서드&amp;nbsp;&lt;br /&gt;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. &lt;code&gt;then(onFulfilled, onRejected)&lt;/code&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;resolve&lt;/code&gt;로 반환된 값은 &lt;code&gt;.then()&lt;/code&gt;의 첫 번째 콜백 함수의 매개변수로 전달된다.&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&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;.then()은 새로운 Promise 객체를 반환한다. 때문에 또 then()을 이어서 프로미스 체이닝이 가능하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;첫번째 인자 (onFulfilled)&lt;br /&gt;: Promise가 수행될 때 호출되는 Function으로, 이행 값(fulfillment value) 하나를 인수로 받는다.&lt;/li&gt;
&lt;li&gt;&amp;nbsp;두번째 인자 (onRejected)&lt;br /&gt;: Promise가 거부될 때 호출되는 Function으로, 거부 이유(rejection reason) 하나를 인수로 받는다.&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;2. catch&lt;/b&gt;&lt;br /&gt;&lt;code&gt;reject&lt;/code&gt;로 반환된 값은 &lt;code&gt;.catch()&lt;/code&gt;의 첫 번째 콜백 함수의 매개변수로 전달된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로미스 체이닝 : &lt;code&gt;catch()&lt;/code&gt;는 새로운 Promise 객체를 반환한다. 때문에 또 then()을 이어서 프로미스 체이닝이 가능하다.&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;3. finally&lt;/b&gt;&lt;br /&gt;resolve와 reject의 결과와 관계없이 항상 실행된다.&lt;br /&gt;주로 로딩 스피너 제거와 같은 정리 작업에 사용된다.&lt;/p&gt;
&lt;pre id=&quot;code_1732438984993&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;myPromise()
.then((value) =&amp;gt; console.log(&quot;Success:&quot;, value))
.catch((error) =&amp;gt; console.error(&quot;Error:&quot;, error))
.finally(() =&amp;gt; console.log(&quot;Cleanup complete&quot;));&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;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;  Promise 한계&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;then 메서드로 프로미스 체이닝하여 비동기 처리 흐름을 이어나갈 수 있지만,&lt;br /&gt;then이 여러개 중첩되어 여전히 콜백 헬과 비슷한 문제 발생하게 된다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;fetch('https://example.com/api')
  .then(response =&amp;gt; response.json())
  .then(data =&amp;gt; fetch(`https://example.com/api/${data.id}`))
  .then(response =&amp;gt; response.json())
  .then(data =&amp;gt; fetch(`https://example.com/api/${data.id}/details`))
  .then(response =&amp;gt; response.json())
  .then(data =&amp;gt; console.log(data))
  .catch(error =&amp;gt; console.error(error)); &lt;/code&gt;&lt;/pre&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;b&gt;3️⃣ async-await&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 promise체이닝의 한계를 해결하기 위해 async-await를 활용할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가독성 향상&lt;br /&gt;: async-await을 사용하면 비동기 코드가 마치 동기 코드처럼 작성되어, 코드 흐름을 쉽게 이해할 수 있다.&lt;br /&gt;: 각 비동기 작업을 기다리는 동안 코드가 일시 중지되고 순차적으로 실행되기 때문에 코드가 직관적이고 읽기 쉬워진다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;간단한 코드&lt;br /&gt;: then, catch, finally 등의 후속 처리 메서드를 사용하지 않고도, 동기 실행 코드처럼 간결하게 작성할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// await를 통해 프로미스가 처리될 때까지 기다리며, 이후 코드를 순차적으로 실행하게 된다.

  async function getData() {
    const response = await fetch('https://example.com/api');
    const data = await response.json();
    const response2 = await fetch(`https://example.com/api/${data.id}`);
    const data2 = await response2.json();
    const response3 = await fetch(`https://example.com/api/${data.id}/details`);
    const data3 = await response3.json();
    console.log(data3);
  } 

  getData();&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;size18&quot;&gt;&lt;b&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;b&gt;async await 사용법&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;async&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 용도 : await를 사용하고자 하는 함수에 붙인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 반환값 : async 함수는 항상 Promise를 반환한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;반환값이 있다면 자동으로 반환값이 resolve된 프로미스를 반환한다. (프로미스 안에 값이 resolve된 꼴)&lt;/li&gt;
&lt;li&gt;반환값이 없으면 undefined를 resolve하는 프로미스를 반환한다.&lt;code&gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1732439428885&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;async function example() {
return 42; // 자동으로 resolve된 Promise(42)를 반환
}

example().then(value =&amp;gt; console.log(value)); // 출력: 42&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;size18&quot;&gt;&lt;b&gt;await&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 용도 : promise 작업이 settled (resolved, rejected) 될 때까지 다음 코드 실행을 일시 중지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 사용 위치 : async함수 내부에, Promise 인스턴스 앞에 붙임.&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;Promise가 resolved 상태일 경우, 프로미스가 resolve한 처리 &amp;ldquo;결과 값&amp;rdquo;만을 반환한다.&lt;/li&gt;
&lt;li&gt;Promise가 rejected 되면, await는 에러를 throw 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) 순차적 실행 보장 : await를 활용하면 비동기 작업을 순차적으로 처리할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;async function fetchTodo(){
    const res = await fetch(&quot;https://url&quot;); // fetch는 프로미스를 반환. await를 통해 프로미스의 처리결과 값만 반환
    const todo = await res.json(); // json()도 프로미스를 반환. await를 통해 프로미스의 처리 결과 값만 반환
    console.log(todo)
} &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;size18&quot;&gt;&lt;b&gt;&lt;b&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;async/await 에서의 에러처리&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. try&amp;hellip;catch문 사용한 에러처리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;try...catch를 사용하면 코드 블록 내에서 발생한 모든 예외를 catch에서 한 번에 처리 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 에러와 다른 예외적인 상황(JSON 파싱 오류, 런타임 오류 등)을 모두 catch에서 처리할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;async function fetchTodo() {
  try {
    const res = await fetch(&quot;https://url&quot;); // http 에러
    const todo = await res.json(); // 런타임 에러
  } catch (err) {
    console.error(&quot;Error occurred:&quot;, err); // 모두 잡을 수 있음
  }
}&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;b&gt;2. catch 메서드 사용한 에러처리&lt;/b&gt;&lt;br /&gt;async 함수에서 try...catch 문을 사용하지 않으면, 예외가 발생할 경우 자동으로 rejected 상태의 프로미스가 반환된다.&lt;br /&gt;따라서 then과 catch 메서드를 통해도 후속 처리가 가능하다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const foo = async function fetchTodo() {
  const res = await fetch(&quot;https://url&quot;);
  const todo = await res.json();  
};

foo()
  .then(() =&amp;gt; console.log(&quot;success&quot;))
  .catch(() =&amp;gt; console.log(&quot;err&quot;));&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;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;p data-ke-size=&quot;size16&quot;&gt;참고 문서&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;https://www.lydiahallie.com/blog/event-loop&quot;&gt;https://www.lydiahallie.com/blog/event-loop&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1732449874855&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;JavaScript Visualized - Event Loop, Web APIs, (Micro)task Queue&quot; data-og-description=&quot;Learn how the browser event loop, task queue, microtask queue, and Web APIs work together to enable non-blocking, asynchronous JavaScript.&quot; data-og-host=&quot;www.lydiahallie.com&quot; data-og-source-url=&quot;https://www.lydiahallie.com/blog/event-loop&quot; data-og-url=&quot;https://lydiahallie.framer.website/blog/event-loop&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/baohVB/hyXDaiXvVH/Q43kvPxZvL87hM91ohUF2K/img.png?width=3456&amp;amp;height=1812&amp;amp;face=0_0_3456_1812,https://scrap.kakaocdn.net/dn/bVVq9U/hyXDapJdV4/jl4EZJpHjGHaZuO3kw6t71/img.png?width=3456&amp;amp;height=1812&amp;amp;face=0_0_3456_1812,https://scrap.kakaocdn.net/dn/bCxqbZ/hyXDdtfTQ5/6lcimkWfZNUSBxQOi8rWm0/img.jpg?width=1670&amp;amp;height=1670&amp;amp;face=594_414_1248_1128&quot;&gt;&lt;a href=&quot;https://www.lydiahallie.com/blog/event-loop&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.lydiahallie.com/blog/event-loop&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/baohVB/hyXDaiXvVH/Q43kvPxZvL87hM91ohUF2K/img.png?width=3456&amp;amp;height=1812&amp;amp;face=0_0_3456_1812,https://scrap.kakaocdn.net/dn/bVVq9U/hyXDapJdV4/jl4EZJpHjGHaZuO3kw6t71/img.png?width=3456&amp;amp;height=1812&amp;amp;face=0_0_3456_1812,https://scrap.kakaocdn.net/dn/bCxqbZ/hyXDdtfTQ5/6lcimkWfZNUSBxQOi8rWm0/img.jpg?width=1670&amp;amp;height=1670&amp;amp;face=594_414_1248_1128');&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;JavaScript Visualized - Event Loop, Web APIs, (Micro)task Queue&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Learn how the browser event loop, task queue, microtask queue, and Web APIs work together to enable non-blocking, asynchronous JavaScript.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.lydiahallie.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.lydiahallie.com/blog/promise-execution&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.lydiahallie.com/blog/promise-execution&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1732449874799&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;JavaScript Visualized - Promise Execution&quot; data-og-description=&quot;This guide covers some of the inner workings of Promises, exploring how they leverage concepts like the Microtask Queue and Event Loop to enable non-blocking async code. Follow along with easy-to-understand examples and visualizations.&quot; data-og-host=&quot;www.lydiahallie.com&quot; data-og-source-url=&quot;https://www.lydiahallie.com/blog/promise-execution&quot; data-og-url=&quot;https://lydiahallie.framer.website/blog/promise-execution&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/0Zapm/hyXDhblgma/PQHtwfKkkvnP4DsJXX4KU1/img.png?width=3456&amp;amp;height=1944&amp;amp;face=0_0_3456_1944,https://scrap.kakaocdn.net/dn/Ije22/hyXDjtsVNI/2v4jfK3kIB58QiFzZERuEK/img.png?width=3456&amp;amp;height=1944&amp;amp;face=0_0_3456_1944,https://scrap.kakaocdn.net/dn/GG8E8/hyXDj1gJXy/eKsXe33qUv1AUUf3OI9pjk/img.jpg?width=1670&amp;amp;height=1670&amp;amp;face=594_414_1248_1128&quot;&gt;&lt;a href=&quot;https://www.lydiahallie.com/blog/promise-execution&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.lydiahallie.com/blog/promise-execution&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/0Zapm/hyXDhblgma/PQHtwfKkkvnP4DsJXX4KU1/img.png?width=3456&amp;amp;height=1944&amp;amp;face=0_0_3456_1944,https://scrap.kakaocdn.net/dn/Ije22/hyXDjtsVNI/2v4jfK3kIB58QiFzZERuEK/img.png?width=3456&amp;amp;height=1944&amp;amp;face=0_0_3456_1944,https://scrap.kakaocdn.net/dn/GG8E8/hyXDj1gJXy/eKsXe33qUv1AUUf3OI9pjk/img.jpg?width=1670&amp;amp;height=1670&amp;amp;face=594_414_1248_1128');&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;JavaScript Visualized - Promise Execution&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;This guide covers some of the inner workings of Promises, exploring how they leverage concepts like the Microtask Queue and Event Loop to enable non-blocking async code. Follow along with easy-to-understand examples and visualizations.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.lydiahallie.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;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/JS-%F0%9F%93%9A-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%BD%9C%EB%B0%B1-%ED%95%A8%EC%88%98&quot;&gt;https://inpa.tistory.com/entry/JS- -자바스크립트-콜백-함수&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/JS-%F0%9F%93%9A-%EB%B9%84%EB%8F%99%EA%B8%B0%EC%B2%98%EB%A6%AC-Promise&quot;&gt;https://inpa.tistory.com/entry/JS- -비동기처리-Promise&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/JS-%F0%9F%93%9A-%EB%B9%84%EB%8F%99%EA%B8%B0%EC%B2%98%EB%A6%AC-async-await&quot;&gt;https://inpa.tistory.com/entry/JS- -비동기처리-async-await&lt;/a&gt;&lt;/p&gt;</description>
      <category>프론트엔드/JS</category>
      <author>journey-dev</author>
      <guid isPermaLink="true">https://journey-dev.tistory.com/172</guid>
      <comments>https://journey-dev.tistory.com/172#entry172comment</comments>
      <pubDate>Sun, 24 Nov 2024 17:34:55 +0900</pubDate>
    </item>
    <item>
      <title>React-Query 사용기</title>
      <link>https://journey-dev.tistory.com/163</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 사이드 프로젝트를 하면서 React-Query를 접하게 되었습니다.&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;React-Query를 사용하며 배우고 느꼈던 것들을 정리해보겠습니다.&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;style7&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;✅ React-Query란 ?&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;서버 상태 관리를 위한 라이브러리.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;데이터 패칭, 동기화, 캐싱 등 기능을 제공해 개발자가 서버 데이터를 손쉽게 관리할 수 있다.&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;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;✅&lt;span&gt;&amp;nbsp;효용&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;효용1.&amp;nbsp; 상태 관리의 단순화&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;1) React-Query 없이 관리할 땐&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&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;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;useEffect와 useState를 사용해 직접 서버 데이터를 저장하고, &lt;/span&gt;&lt;/span&gt;&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;loading과 error 상태는 클라이언트에서 관리해야 한다.&lt;/span&gt;&lt;/span&gt;&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;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;아래의 코드 예시처럼, 데이터 페칭시 관리해야 할 클라이언트 상태가 필요하게 되어&lt;span&gt;&amp;nbsp;&lt;/span&gt;state 수도 많아진다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1729914774965&quot; class=&quot;javascript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;const Component = () =&amp;gt; {
  const [data, setData] = useState(null);  // 서버 데이터 저장
  const [loading, setLoading] = useState(false); // 로딩 상태 저장
  const [error, setError] = useState(null);  // 에러 상태 저장
 
  // 컴포넌트가 마운트될 때 처음 데이터 패칭
  useEffect(async() =&amp;gt; {
    try {
      setLoading(true);  // 로딩 상태 활성화
      const response = await axios.get('/api/...');  
      setData(response.data);  // 데이터 저장
      setError(null);  // 에러 상태 초기화
    } catch (err) {
      setError(err);  // 에러 상태 저장
    } finally {
      setLoading(false);  // 로딩 상태 비활성화
    }
  }, []);  
  
  return &amp;lt;&amp;gt;&amp;lt;/&amp;gt;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;2) React-Query를 사용하면&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;React Query는 비동기 통신에 필요한 상태(isError, error, isLoading 등..)을 내부적으로 관리해주기 때문에,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;개발자가 별도로 클라이언트 상태를 직접 관리할 필요가 없어진다.&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;a style=&quot;color: #0070d1;&quot; href=&quot;https://tanstack.com/query/latest/docs/framework/react/reference/useQuery&quot;&gt;※ useQuery return값 참고&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;lt;동일한 코드를 useQuery로 패칭하는 예제 코드&amp;gt;&lt;br /&gt;코드가 현저히 감소함을 볼 수 있다&lt;/p&gt;
&lt;pre id=&quot;code_1729914774967&quot; class=&quot;javascript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;const fetchData = async () =&amp;gt; {
  const { data } = await axios.get('/api/...');
  return data;
};

const Component = () =&amp;gt; {
  const { data, error, isLoading, refetch } = useQuery('fetchData', fetchData);   // useQuery 훅을 사용하여 데이터 패칭

  return (&amp;lt;&amp;gt;&amp;lt;/&amp;gt;);
};&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;효용2.&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&amp;nbsp;서버 상태와 클라이언트 상태의 명확한 구분&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h2&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;뿐만 아니라 서버 상태와 클라이언트 상태를 명확히 구분할 수 있게된다.&lt;/span&gt;&lt;/span&gt;&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;React Query는 서버 상태만을 처리하고, 클라이언트 상태는 useState나 상태관리 라이브러리로 구분하여 관리할 수 있다.&lt;/span&gt;&lt;/span&gt;&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;이를 통해 UI관련된 상태값 정도만 클라이언트가 갖고 있게 된다.&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&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;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;효용3. &lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&amp;nbsp;&lt;/b&gt;key를 통한 데이터 업데이트&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) React-Query 미사용시 : &lt;b&gt;수동 데이터 관리&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;&lt;b&gt;&lt;b&gt;&lt;span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예로들면, 상품 상세 페이지에서 데이터를 수정하여 서버에 PUT요청을 보내 수정 완료되고 나면,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;수정된 데이터를 새로 불러오기 위해 상품상세에 대한 GET 요청을 다시 실행해야 한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;또한 상품 리스트 항목도 데이터 최신화를 위해 다시 GET요청을 보내야한다.&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;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 데이터 최신화를 위해 연관된 데이터를 직접 수동으로 일일히 관리해줘야 한다.&amp;nbsp;&amp;nbsp;&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;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2)React-Query를 사용시 : &lt;b&gt;자동 데이터 관리&lt;/b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;React Query를 사용하면, 상품 상세 페이지 정보를 수정한 후 product와 관련된 쿼리 키를 무효화하기만 하면&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;관련된 모든 데이터 쿼리(get:상품 상세, getMany:상품 리스트)가 자동으로 다시 페칭된다.&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;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;데이터를 업데이트한 후 특정 쿼리 키를 무효화하여, 연관된 데이터들이 자동으로 다시 페칭되어 최신 상태로 유지되는 것이다.&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;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 쿼리키만 잘 관리하면, 직접 데이터를 업데이트 하는 코드를 줄일 수 있다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1556&quot; data-origin-height=&quot;932&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eeWG8O/btsJntYrFZG/D0wCkoPIjZ9L3oAiK6PoIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eeWG8O/btsJntYrFZG/D0wCkoPIjZ9L3oAiK6PoIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eeWG8O/btsJntYrFZG/D0wCkoPIjZ9L3oAiK6PoIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeeWG8O%2FbtsJntYrFZG%2FD0wCkoPIjZ9L3oAiK6PoIk%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;735&quot; height=&quot;440&quot; data-origin-width=&quot;1556&quot; data-origin-height=&quot;932&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;&amp;nbsp;&lt;br /&gt;&lt;/span&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;효용4. 캐싱 기능을 사용할 수 있다.&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) staleTime (기본값 0)&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;staleTime은 데이터를 얼마나 오래 &quot;fresh&quot; 한 상태로 유지할지에 대한 시간이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 시간동안은 데이터가 fresh한 상태로 간주되서 네트워크 요청을 새로 하지 않고 캐싱된 데이터를 사용한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 시간이 지나면 데이터는 &quot;stale&quot; 상태가 된다.&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;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;시간이 지난 후 화면 출력시, stale된 캐시 데이터를 일단 사용하여 화면에 출력해주고&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이후 새로운 api호출을 통해 데이터를 refetch 시킨다.&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;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이러한 캐싱 기능을 이용해&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;업데이트가 자주 필요 없는 데이터는 staleTime을 설정하여 해당 시간동안 네트워크 요청을 줄일 수 있게된다.&lt;/p&gt;
&lt;pre id=&quot;code_1729914774969&quot; class=&quot;javascript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// 1. 쿼리 클라이언트에 설정
new QueryClient({
  defaultOptions: {
    queries: { 
       staleTime: 0 // 쿼리가 최신 상태에서 더 이상 최신이 아닌 상태로 전환하는 시간
    }
  }
})

// 2. api마다 각각 설정
// 유저 데이터 쿼리 (1분 동안 stale 상태로 유지)
  const { data: userData } = useQuery('user', fetchUserData, {
    staleTime: 60000, // 1분
  });

  // 상품 데이터 쿼리 (5분 동안 stale 상태로 유지)
  const { data: productData } = useQuery('products', fetchProductData, {
    staleTime: 300000, // 5분
  });&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) cachetime (gc time)&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;cachetime 은 데이터가 메모리에 유지될 시간을 의미한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1) staleTime은 설정 시간이 지나도 데이터를 삭제하지는 않고, stale된 데이터가 그대로 남아있는 반면&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2) cachetime은 설정된 시간이 지나면, 메모리에서 데이터를 아예 삭제하게 된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 화면에 일단 빈 데이터가 출력되고 &amp;gt; 쿼리를 사용되는 컴포넌트가 마운트되서 &amp;gt; 쿼리가 다시 사용될때 &amp;gt; api를 새로 호출하여 데이터 refetch 하게 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1729917429891&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 예: 유저 데이터 쿼리 - 5분 동안 캐시에 유지
const { data: userData } = useQuery('user', fetchUserData, {
  staleTime: 60000, // 1분 동안 fresh
  cacheTime: 300000 // 5분 후 메모리에서 삭제
});&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;✅&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;배운점&lt;/span&gt;&lt;/h2&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;1. 쿼리&lt;span&gt;&amp;nbsp;&lt;/span&gt;키 관리 관리의 중요성&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;리액트 쿼리를 사용하면서, 쿼리 키 관리가 중요하다는 것을 느꼈다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;쿼리 키를 어떻게 구조화하느냐에 따라 개발 효율이 크게 달라졌다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;쿼리 키 관리 방식이 코드의 일관성과 유지보수성에 큰 영향을 끼쳤다.&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;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1️⃣ 문제&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 쿼리 키를 직접 입력하는 방식으로 구현했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예를들어, 상품 목록 페이지를 구성할 때 페이지마다 쿼리 키를 일일히 지정해줬다.&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;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;직접 입력하다보니 규칙이 없어 쿼리 키 형식을 일관되게 유지하는 것이 어렵고,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;프로젝트가 커지면 쿼리 키가 다양해지면서 유지보수 하기도 어려워진다.&lt;/p&gt;
&lt;pre id=&quot;code_1729914774971&quot; class=&quot;coffeescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// 상품 목록 
const { data } = useQuery([&quot;product&quot;, &quot;getMany&quot;, &quot;1&quot;], () =&amp;gt; fetchProductList(1)); // 1페이지
const { data } = useQuery([&quot;product&quot;, &quot;getMany&quot;, &quot;2&quot;], () =&amp;gt; fetchProductList(2)); // 2페이지
// 쿼리키 [&quot;product&quot;, &quot;getMany&quot;, &quot;3,4,5...&quot;]

// 상품 상세
const { data } = useQuery([&quot;product&quot;, &quot;get&quot;, &quot;detailId1&quot;];, () =&amp;gt; fetchProductDetail(&quot;detailId1&quot;));
const { data } = useQuery([&quot;product&quot;, &quot;get&quot;, &quot;detailId2&quot;];, () =&amp;gt; fetchProductDetail(&quot;detailId2&quot;));
// 쿼리키 [&quot;product&quot;, &quot;get&quot;, &quot;detailId3,4,5,..&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;2️⃣&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;해결 - &lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Query Key 팩토리 함수 사용&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Query Key 팩토리 함수를 사용하면 위 문제를 해결할 수 있다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;(※ Query Key 팩토리 함수 -&amp;nbsp;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://velog.io/@bo-like-chicken/Query-Keys%EB%A5%BC-%EA%B4%80%EB%A6%AC%ED%95%98%EB%8A%94-%EA%B8%B0%EC%A4%80%EA%B3%BC-%EB%B0%A9%EB%B2%95&quot;&gt;참고1,&lt;/a&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://velog.io/@rgfdds98/React-Query-queryKeys&quot;&gt;참고2,&lt;/a&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://yogjin.tistory.com/121&quot;&gt;참고3&lt;/a&gt;)&lt;/span&gt;&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;u&gt;일관된 쿼리키 형태를 유지&lt;/u&gt; 할 수 있게 해준다.&lt;/span&gt;&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;&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;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;lt;코드 예제&amp;gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아래의 코드에서 ProductQuery는 쿼리 키를 생성하는 팩토리 역할을 한다.&lt;br /&gt;&quot;getMany와 get&quot; 메서드를 통해 &quot;상품 목록과 상품 상세&quot;의 쿼리 키를 일관성 있게 관리할 수 있게 되었다.&amp;nbsp;&lt;br /&gt;prodQuery나&amp;nbsp;id를&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;있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1470&quot; data-origin-height=&quot;994&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beKPyd/btsJoD76pjV/KMGlsQjtx2C3A1f2MFGE40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beKPyd/btsJoD76pjV/KMGlsQjtx2C3A1f2MFGE40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beKPyd/btsJoD76pjV/KMGlsQjtx2C3A1f2MFGE40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeKPyd%2FbtsJoD76pjV%2FKMGlsQjtx2C3A1f2MFGE40%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;628&quot; height=&quot;425&quot; data-origin-width=&quot;1470&quot; data-origin-height=&quot;994&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;lt;쿼리키 결과&amp;gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;: 팩토리를 통해 생성된 쿼리 키는 다음과 같이 구조화되어 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1729914774973&quot; class=&quot;json&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// 상품 목록 쿼리키
[&quot;product&quot;,&quot;getMany&quot;,{&quot;include&quot;:2,&quot;page&quot;:1,&quot;size&quot;:3,&quot;sort&quot;:&quot;-createdAt&quot;}]
[&quot;product&quot;,&quot;getMany&quot;,{&quot;include&quot;:2,&quot;page&quot;:2,&quot;size&quot;:3,&quot;sort&quot;:&quot;-createdAt&quot;}]

// 상품 상세 쿼리키
[&quot;product&quot;,&quot;get&quot;,&quot;66d429ff444bc2d5e2334aee&quot;]
[&quot;product&quot;,&quot;get&quot;,&quot;66d42a06444bc2d5e2334afa&quot;]
[&quot;product&quot;,&quot;get&quot;,&quot;66d42fe8444bc2d5e2334bb0&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;2.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;쿼리&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;키 무효화 관리&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1️⃣ 문제&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위에서 쿼리키를 수동으로 작성하고 관리하는 것이 비효율적이라고 했듯이,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;쿼리키를 무효화 할 때도 일일히 처리하는 것은 좋지 않은 방법이라 생각됐다.&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;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어, 상품 리스트 쿼리 키가 페이지별로 생성되는 구조에서&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;상품 리스트 3페이지에서 항목 하나를 삭제 한 후, 3 페이지 쿼리키를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;removeQueries를 사용하여 직접 제거를 했더니&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;그럼 그 뒤에있는 4,5.. 페이지도 다시 refresh&amp;nbsp; 시켜줘야 하는 상황이 생겼다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;이렇게 연관된 쿼리키를 수동으로 직접 관리하기 어렵다.&lt;/span&gt;&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;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2️⃣&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;해결&lt;br /&gt;&lt;/b&gt;&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;pre id=&quot;code_1729914774975&quot; class=&quot;javascript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;client.invalidateQueries({ queryKey: ProductQuery.all })

const ProductQuery = {
  all: [&quot;product&quot;] as const,
  getMany: (prodQuery: ProductQueryParams) =&amp;gt; [...ProductQuery.all, &quot;getMany&quot;, prodQuery] as const,
  get: (id: string) =&amp;gt; [...ProductQuery.all, &quot;get&quot;, id] as const,
};

const CategoryQuery = {
  all: [&quot;category&quot;] as const,
  getMany: (categoryQuery: CateQueryParams) =&amp;gt; [...CategoryQuery.all, &quot;getMany&quot;, categoryQuery] as const,
  get: (id: string) =&amp;gt; [...CategoryQuery.all, &quot;get&quot;, id] as const,
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;상품(목록,상세) 관련 쿼리 키가 한 곳에서 집중되어 관리되어&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;상품 목록을 업데이트(추가,삭제,수정) 후 상품 관련 쿼리키를 한번에 invalidate 시킬 수 있다.&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;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;ProductQuery.all을 상품 관련된 쿼리키 생성 함수인 getMany, get에 공통으로 적용해주어&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;ProductQuery.all 만 invalid하면 연관된 getMany, get도 한번에 invalid 시킬 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 그냥 쿼리 키만 잘 관리해주면 된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;일일히 상품 목록 api를 다시 불러오고, 관련된 상품 상세 등 데이터를 수동으로 refetch 하지 않아도 된다.&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;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;✅&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt; 느낀점&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용해보니 &quot;이 좋은걸 왜 이제 알았을까&quot; 하는 생각이 들었고, 아직 장점이 더 많은 것 같다고 생각된다.&lt;br /&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=MArE6Hy371c&quot;&gt;https://www.youtube.com/watch?v=MArE6Hy371c&lt;/a&gt;&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;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;#fake-store&lt;/p&gt;</description>
      <category>프론트엔드</category>
      <author>journey-dev</author>
      <guid isPermaLink="true">https://journey-dev.tistory.com/163</guid>
      <comments>https://journey-dev.tistory.com/163#entry163comment</comments>
      <pubDate>Sun, 20 Oct 2024 23:39:21 +0900</pubDate>
    </item>
    <item>
      <title>FTP와 SFTP에 대해, RSA에 대해</title>
      <link>https://journey-dev.tistory.com/154</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://journey-dev.tistory.com/92&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt; ftp 자동배포를 시도&lt;/a&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;누군가 FTP가 뭐에요?라고 묻는다면, 나는 &quot;파일 전송 프로토콜&quot;이요 라고 밖에 말하지 못하겠다.&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;&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;# FTP와&amp;nbsp; SFTP&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;FTP(File Transfer Protocol)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;서버와 클라이언트 간에 파일을 전송하기 위한 네트워크 프로토콜&lt;/span&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;특징1. 전송방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 파일을 암호화하지 않은 상태로 전송함.&lt;br /&gt;: 전송 데이터가 암호화되지 않고&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt; &lt;/span&gt;평문으로 전송해되기 때문에 전송된 파일, 사용자이름, 비밀번호가 노출될 위험 있음. -&amp;gt; 보안 취약&lt;/span&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;span style=&quot;color: #333333; text-align: start;&quot;&gt;특징2. 보안이 취약함&amp;nbsp;&lt;/span&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;SFTP(SSH File Transfer Protocol)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ftp 연결에 보안을 강화한 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;특징1. 전송방식&lt;br /&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;특징2. 보안 강화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 102px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;특징&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;FTP&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;SFTP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;보안&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;암호화X&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;암호화O (SSH 기반)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;기본 포트&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;21&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;전송 방식&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;평문 전송&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;암호화하여 전송&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;인증 방식&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;사용자 이름, 비밀번호&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;사용자 이름, 비밀번호 or SSH 키&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;사용&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;보안이 필요없는 파일 전송시&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&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;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;느낀점&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사내에서 ftp 인증 방식으로 개발서버는 텍스트 비밀번호를, 스테이징 서버를 ssh키를 사용하고 있는데&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;p data-ke-size=&quot;size16&quot;&gt;스테이징 서버는 aws를 사용하고 있어 보안을 강화하기 위해 ssh키를 사용한 것 이였다.&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;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;※ 참조 사이트&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://terms.naver.com/entry.naver?docId=3574148&amp;amp;cid=59088&amp;amp;categoryId=59096&quot;&gt;https://terms.naver.com/entry.naver?docId=3574148&amp;amp;cid=59088&amp;amp;categoryId=59096&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/WEB-%F0%9F%97%84%EF%B8%8F-FTP-SFTP-%EB%9E%80&quot;&gt;https://inpa.tistory.com/entry/WEB-%F0%9F%97%84%EF%B8%8F-FTP-SFTP-%EB%9E%80&lt;/a&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 style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;# rsa 암호화 및 디지털 서명&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 암호화&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;rsa는 암호화 알고리즘 중 하나.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;2개의 키(공개키 public key, 개인키 private key)를 이용한 비대칭 암호화다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;공개키로 데이터를 암호화하고, 개인키로 이를 복화하하는 방식으로 데이터를 보호한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;오직 그 개인키를 가진 사람만 이를 해독할 수 있게되는 것이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 서명(디지털 서명)&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;개인키를 갖은 사람만이 공개키로 암호화된 것을 해독할 수 있으니, 해독을 했다면 신분이 인증되는 것이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;1. 보안이 뛰어남&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;2. 속도는 느림 : 대용량 데이터를 암호화하는 경우 속도가 느려짐&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;활용&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;1. SSH에서 사용자 인증시 사용&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;2. 전자 상거래, 공인인증서 등 신분 증명시&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;&lt;/span&gt;※ 참조 사이트&lt;br /&gt;&lt;a href=&quot;https://velog.io/@480/RSA-%EC%95%94%ED%98%B8%ED%99%94-3%EB%B6%84-%EB%A7%8C%EC%97%90-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0&quot;&gt;https://velog.io/@480/RSA-%EC%95%94%ED%98%B8%ED%99%94-3%EB%B6%84-%EB%A7%8C%EC%97%90-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프론트엔드</category>
      <author>journey-dev</author>
      <guid isPermaLink="true">https://journey-dev.tistory.com/154</guid>
      <comments>https://journey-dev.tistory.com/154#entry154comment</comments>
      <pubDate>Fri, 4 Oct 2024 00:13:36 +0900</pubDate>
    </item>
    <item>
      <title>css scope, scope 무시하기</title>
      <link>https://journey-dev.tistory.com/148</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;css scope&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 css는 전역으로 적용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 css scoping을 하면, 해당 컴포넌트 내에서만 한정적으로 적용할 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;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-origin-width=&quot;1732&quot; data-origin-height=&quot;810&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HfgmZ/btsJHW7dwta/o5uAePErfyyo1X9ik9wXok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HfgmZ/btsJHW7dwta/o5uAePErfyyo1X9ik9wXok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HfgmZ/btsJHW7dwta/o5uAePErfyyo1X9ik9wXok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHfgmZ%2FbtsJHW7dwta%2Fo5uAePErfyyo1X9ik9wXok%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;1732&quot; height=&quot;810&quot; data-origin-width=&quot;1732&quot; data-origin-height=&quot;810&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cKW5ua/btsJGNDrTky/roVisB8Sxk1lRCsQ0XHI61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cKW5ua/btsJGNDrTky/roVisB8Sxk1lRCsQ0XHI61/img.png&quot; data-origin-width=&quot;1642&quot; data-origin-height=&quot;790&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;62.45&quot; style=&quot;width: 61.7192%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cKW5ua/btsJGNDrTky/roVisB8Sxk1lRCsQ0XHI61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcKW5ua%2FbtsJGNDrTky%2FroVisB8Sxk1lRCsQ0XHI61%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;1642&quot; height=&quot;790&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Kw6Rn/btsJIiCgzub/56heInmj640b339XPorQ9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Kw6Rn/btsJIiCgzub/56heInmj640b339XPorQ9k/img.png&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;656&quot; data-is-animation=&quot;false&quot; style=&quot;width: 37.118%;&quot; data-widthpercent=&quot;37.55&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Kw6Rn/btsJIiCgzub/56heInmj640b339XPorQ9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKw6Rn%2FbtsJIiCgzub%2F56heInmj640b339XPorQ9k%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;820&quot; height=&quot;656&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;스코핑 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;css scoping 적용하면 컴포넌트별로 랜덤한 접두사가 붙는다.&lt;/ul&gt;
&lt;/ul&gt;
&lt;/ul&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;html 요소에는 고유한 랜덤 접두사가 추가되고, &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;css에는 접두사가 포함된 셀렉터로 변환된다.&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;만약, 두 컴포넌트에서 동일한 클래스명을 쓰더라도 서로 다른 접두사가 추가되어 서로 충돌나지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;scope 예외&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 외부 UI라이브러리를 쓰다보면 scoping을 무시해야 할 때가 있다.&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;그럴 경우 vue에서는 ::v-deep 또는 :global 선택자를, vite에선 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;:global 선택자를 사용하여&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;css scoping을 무시할 수 있다.&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-origin-width=&quot;1646&quot; data-origin-height=&quot;906&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sCskK/btsJIrMqsdq/ScSK4VUy0GixpZ2El87kak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sCskK/btsJIrMqsdq/ScSK4VUy0GixpZ2El87kak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sCskK/btsJIrMqsdq/ScSK4VUy0GixpZ2El87kak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsCskK%2FbtsJIrMqsdq%2FScSK4VUy0GixpZ2El87kak%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;1646&quot; height=&quot;906&quot; data-origin-width=&quot;1646&quot; data-origin-height=&quot;906&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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-origin-width=&quot;1804&quot; data-origin-height=&quot;822&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dYg6zJ/btsJIjnzDiL/kie6S1vykUKQPbIam9bqHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dYg6zJ/btsJIjnzDiL/kie6S1vykUKQPbIam9bqHk/img.png&quot; data-alt=&quot;스코핑 예외 처리 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dYg6zJ/btsJIjnzDiL/kie6S1vykUKQPbIam9bqHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdYg6zJ%2FbtsJIjnzDiL%2Fkie6S1vykUKQPbIam9bqHk%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;1804&quot; height=&quot;822&quot; data-origin-width=&quot;1804&quot; data-origin-height=&quot;822&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;스코핑 예외 처리 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.children은 이제 스코핑이 무시되어 랜덤한 접두사 같은것이 붙지 않고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 순수 css처럼 되어 전역에 미치게 된다.&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: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Vue (프레임워크) &lt;br /&gt;: 뷰 프레임워크가 기본적으로 제공한다. vue-loader를 통해 css module 기능을 사용하게 된다.&lt;/li&gt;
&lt;li&gt;React (라이브러리) &lt;br /&gt;: vite 같은 번들러를 따로 설치해야 한다. vite에서 css module 기능을 제공해준다.&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;br /&gt;&lt;a href=&quot;https://github.com/css-modules/css-modules/blob/master/docs/composition.md#exceptions&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/css-modules/css-modules/blob/master/docs/composition.md#exceptions&lt;/a&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;a href=&quot;https://stackoverflow.com/questions/43613619/what-does-global-colon-global-do&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://stackoverflow.com/questions/43613619/what-does-global-colon-global-do&lt;/a&gt;&lt;/p&gt;</description>
      <category>프론트엔드</category>
      <author>journey-dev</author>
      <guid isPermaLink="true">https://journey-dev.tistory.com/148</guid>
      <comments>https://journey-dev.tistory.com/148#entry148comment</comments>
      <pubDate>Sun, 22 Sep 2024 23:33:43 +0900</pubDate>
    </item>
  </channel>
</rss>