<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Hello Jay's Programming World!</title>
    <link>https://jayprogram.tistory.com/</link>
    <description>프로그래밍 관련 블로그입니다.</description>
    <language>ko</language>
    <pubDate>Tue, 30 Jun 2026 20:05:12 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>코딩하는 Jay</managingEditor>
    <image>
      <title>Hello Jay's Programming World!</title>
      <url>https://tistory1.daumcdn.net/tistory/2787063/attach/40e11f4f28b842d7be48085d75abb517</url>
      <link>https://jayprogram.tistory.com</link>
    </image>
    <item>
      <title>[React] react-markdown 심화: 수학 수식(KaTeX) &amp;middot; 코드 하이라이트 &amp;middot; 유용한 플러그인 총정리</title>
      <link>https://jayprogram.tistory.com/104</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Gemini_Generated_Image_6tpdtd6tpdtd6tpd.png&quot; data-origin-width=&quot;2752&quot; data-origin-height=&quot;1536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Vfu2l/dJMcafUoUwV/RROenUjRGm0S43pdkn7RY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Vfu2l/dJMcafUoUwV/RROenUjRGm0S43pdkn7RY0/img.png&quot; data-alt=&quot;AI로 생성한 이미지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Vfu2l/dJMcafUoUwV/RROenUjRGm0S43pdkn7RY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVfu2l%2FdJMcafUoUwV%2FRROenUjRGm0S43pdkn7RY0%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;2752&quot; height=&quot;1536&quot; data-filename=&quot;Gemini_Generated_Image_6tpdtd6tpdtd6tpd.png&quot; data-origin-width=&quot;2752&quot; data-origin-height=&quot;1536&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;AI로 생성한 이미지&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;안녕하세요. &lt;b&gt;코딩하는 Jay&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;예전에 [React 환경에서 Markdown 출력하기(react-markdown)] 글을 올렸는데, 꾸준히 찾아주시더라고요. 그래서 오늘은 한 발 더 들어가서, 실제 서비스에서 자주 필요한 &lt;b&gt;① 수학 수식(KaTeX) ② 코드 문법 강조(syntax highlight) ③ 유용한 remark/rehype 플러그인&lt;/b&gt;을 정리해보려고 합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본 글은 &lt;b&gt;react-markdown v9&lt;/b&gt; 기준입니다. (v9에서 API가 일부 바뀌었으니 아래 주의사항도 함께 보세요.)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. remark와 rehype, 뭐가 다른가요?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;react-markdown은 내부적으로 &lt;b&gt;unified&lt;/b&gt; 생태계를 씁니다. 처리 흐름을 알면 플러그인 배치가 쉬워집니다.&lt;/p&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;마크다운 텍스트
  &amp;rarr; (remark) mdast: 마크다운 추상 구문 트리
  &amp;rarr; (rehype) hast: HTML 추상 구문 트리
  &amp;rarr; React 엘리먼트&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;&lt;b&gt;remarkPlugins&lt;/b&gt; = 마크다운 단계에서 동작 (예: &lt;code&gt;$수식$&lt;/code&gt; 문법 인식, GFM 표 파싱)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;rehypePlugins&lt;/b&gt; = HTML 단계에서 동작 (예: 수식 렌더링, 코드 하이라이트)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 &quot;&lt;b&gt;문법을 새로 인식&lt;/b&gt;&quot;하는 건 remark, &quot;&lt;b&gt;HTML로 꾸미는&lt;/b&gt;&quot; 건 rehype라고 보면 됩니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 설치&lt;/h2&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;npm install react-markdown remark-gfm remark-math rehype-katex rehype-highlight katex highlight.js&lt;/code&gt;&lt;/pre&gt;
&lt;table data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;패키지&lt;/td&gt;
&lt;td&gt;역할&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;remark-gfm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;GFM 지원 (표, 체크박스, 취소선, 자동 링크)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;remark-math&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;$...$&lt;/code&gt; / &lt;code&gt;$$...$$&lt;/code&gt; 수식 문법 인식&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rehype-katex&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;인식된 수식을 KaTeX로 렌더링&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rehype-highlight&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;코드 블록 문법 강조 (highlight.js 기반)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 기본 + GFM (표&amp;middot;체크박스)&lt;/h2&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import Markdown from 'react-markdown'
import remarkGfm from 'remark-gfm'

export default function MarkdownView({ content }: { content: string }) {
  return (
    &amp;lt;div className=&quot;markdown-body&quot;&amp;gt;
      &amp;lt;Markdown remarkPlugins={[remarkGfm]}&amp;gt;{content}&amp;lt;/Markdown&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ &lt;b&gt;v9 주의&lt;/b&gt;: &lt;code&gt;&amp;lt;Markdown&amp;gt;&lt;/code&gt;에 더 이상 &lt;code&gt;className&lt;/code&gt; prop을 직접 못 줍니다. 위처럼 &lt;b&gt;바깥 &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;로 감싸서&lt;/b&gt; 스타일을 적용하세요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 수학 수식 &amp;mdash; KaTeX&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;remark-math&lt;/code&gt;(인식) + &lt;code&gt;rehype-katex&lt;/code&gt;(렌더)를 함께 써야 합니다. 그리고 &lt;b&gt;KaTeX CSS import가 필수&lt;/b&gt;입니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import Markdown from 'react-markdown'
import remarkMath from 'remark-math'
import rehypeKatex from 'rehype-katex'
import 'katex/dist/katex.min.css' // &amp;larr; 이거 빠뜨리면 수식이 깨져 보입니다

export default function MathMarkdown({ content }: { content: string }) {
  return (
    &amp;lt;Markdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex]}&amp;gt;
      {content}
    &amp;lt;/Markdown&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마크다운에서는 이렇게 씁니다.&lt;/p&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;인라인 수식: $E = mc^2$

블록 수식:
$$
a^2 + b^2 = c^2
$$&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  자주 하는 실수 2가지: &lt;b&gt;(1) &lt;code&gt;katex.min.css&lt;/code&gt; import 누락&lt;/b&gt; &amp;rarr; 수식 모양이 다 깨집니다. &lt;b&gt;(2) 본문에 &lt;code&gt;$&lt;/code&gt; 기호(가격 등)가 많을 때&lt;/b&gt; &amp;rarr; 의도치 않게 수식으로 파싱될 수 있으니 &lt;code&gt;\$&lt;/code&gt;로 이스케이프하세요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 코드 문법 강조 (syntax highlight)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;방법 A &amp;mdash; rehype-highlight (가장 간단)&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import Markdown from 'react-markdown'
import rehypeHighlight from 'rehype-highlight'
import 'highlight.js/styles/github-dark.css' // &amp;larr; 테마 CSS 필수

export default function CodeMarkdown({ content }: { content: string }) {
  return (
    &amp;lt;Markdown rehypePlugins={[rehypeHighlight]}&amp;gt;{content}&amp;lt;/Markdown&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;```js&lt;/code&gt; 처럼 fence에 언어를 적으면 자동으로 강조됩니다. &lt;b&gt;테마 CSS만 import하면 끝&lt;/b&gt;이라 가장 빠릅니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;방법 B &amp;mdash; react-syntax-highlighter (커스터마이징&amp;middot;라인 번호)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라인 번호, 특정 줄 강조 등 세밀한 제어가 필요하면 &lt;code&gt;components&lt;/code&gt;로 &lt;code&gt;code&lt;/code&gt;를 직접 교체합니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import Markdown from 'react-markdown'
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism'

export default function CodeMarkdown({ content }: { content: string }) {
  return (
    &amp;lt;Markdown
      components={{
        code({ className, children, ...rest }) {
          const match = /language-(\w+)/.exec(className || '')
          return match ? (
            &amp;lt;SyntaxHighlighter
              {...rest}
              PreTag=&quot;div&quot;
              language={match[1]}
              style={oneDark}
              showLineNumbers
            &amp;gt;
              {String(children).replace(/\n$/, '')}
            &amp;lt;/SyntaxHighlighter&amp;gt;
          ) : (
            &amp;lt;code className={className} {...rest}&amp;gt;
              {children}
            &amp;lt;/code&amp;gt;
          )
        },
      }}
    &amp;gt;
      {content}
    &amp;lt;/Markdown&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;방법 A&lt;/b&gt;: 빠르고 가벼움. 블로그&amp;middot;문서엔 보통 이걸로 충분&lt;/li&gt;
&lt;li&gt;&lt;b&gt;방법 B&lt;/b&gt;: 라인 번호&amp;middot;줄 강조 등 제어 필요할 때&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 다 합치기 + 유용한 플러그인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 서비스에서는 보통 이렇게 묶어 씁니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import Markdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
import remarkMath from 'remark-math'
import rehypeKatex from 'rehype-katex'
import rehypeHighlight from 'rehype-highlight'
import rehypeSlug from 'rehype-slug'
import 'katex/dist/katex.min.css'
import 'highlight.js/styles/github-dark.css'

export default function RichMarkdown({ content }: { content: string }) {
  return (
    &amp;lt;div className=&quot;markdown-body&quot;&amp;gt;
      &amp;lt;Markdown
        remarkPlugins={[remarkGfm, remarkMath]}
        rehypePlugins={[rehypeKatex, rehypeHighlight, rehypeSlug]}
      &amp;gt;
        {content}
      &amp;lt;/Markdown&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알아두면 좋은 플러그인:&lt;/p&gt;
&lt;table style=&quot;height: 100px;&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;플러그인&lt;/td&gt;
&lt;td&gt;역할&lt;/td&gt;
&lt;td&gt;단계&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;code&gt;rehype-slug&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;헤딩에 &lt;code&gt;id&lt;/code&gt; 자동 부여 (목차&amp;middot;앵커용)&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;rehype&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;code&gt;rehype-autolink-headings&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;헤딩에 앵커 링크 추가&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;rehype&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;code&gt;rehype-raw&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;마크다운 안의 &lt;b&gt;생 HTML&lt;/b&gt; 렌더 허용&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;rehype&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;code&gt;rehype-sanitize&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;XSS 방지용 HTML 정제&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;rehype&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 꼭 기억할 주의사항&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;CSS import를 잊지 마세요&lt;/b&gt; &amp;mdash; KaTeX(&lt;code&gt;katex.min.css&lt;/code&gt;), highlight.js(테마 CSS) 둘 다 없으면 &quot;왜 안 꾸며지지?&quot; 하게 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;플러그인 순서&lt;/b&gt; &amp;mdash; &lt;code&gt;remark-math&lt;/code&gt;(remark)가 있어야 &lt;code&gt;rehype-katex&lt;/code&gt;(rehype)가 렌더할 게 생깁니다. remark &amp;rarr; rehype 흐름을 기억하세요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;보안&lt;/b&gt; &amp;mdash; 사용자 입력 마크다운을 그대로 렌더한다면, &lt;code&gt;rehype-raw&lt;/code&gt;로 생 HTML을 허용할 때 &lt;b&gt;반드시 &lt;code&gt;rehype-sanitize&lt;/code&gt;를 함께&lt;/b&gt; 써서 XSS를 막으세요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;v9 변경점&lt;/b&gt; &amp;mdash; &lt;code&gt;&amp;lt;Markdown className&amp;gt;&lt;/code&gt;이 제거됐습니다. 래퍼 div로 감싸세요.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;react-markdown은 플러그인만 잘 조합하면 &lt;b&gt;수식&amp;middot;코드&amp;middot;표&amp;middot;목차&lt;/b&gt;까지 갖춘 꽤 완성도 높은 렌더러가 됩니다. 저도 제 사이드 프로젝트(&lt;a href=&quot;https://jay-project.kr&quot;&gt;jay-project.kr&lt;/a&gt;)의 여러 도구에서 &lt;code&gt;remark + rehype + KaTeX&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;/p&gt;</description>
      <category>React</category>
      <category>KaTeX</category>
      <category>markdown</category>
      <category>REACT</category>
      <category>remark</category>
      <category>리액트</category>
      <category>리액트마크다운</category>
      <category>마크다운</category>
      <category>마크다운렌더링</category>
      <category>코드하이라이트</category>
      <category>프론트엔드</category>
      <author>코딩하는 Jay</author>
      <guid isPermaLink="true">https://jayprogram.tistory.com/104</guid>
      <comments>https://jayprogram.tistory.com/104#entry104comment</comments>
      <pubDate>Thu, 4 Jun 2026 16:25:15 +0900</pubDate>
    </item>
    <item>
      <title>[TypeScript] d.ts 파일과 types.ts 파일의 차이</title>
      <link>https://jayprogram.tistory.com/103</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;다운로드.png&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;1200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bi9YDc/btsLPxp9xSK/LmZ206l2k5Z6UxXuaOE250/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bi9YDc/btsLPxp9xSK/LmZ206l2k5Z6UxXuaOE250/img.png&quot; data-alt=&quot;TypeScript&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bi9YDc/btsLPxp9xSK/LmZ206l2k5Z6UxXuaOE250/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbi9YDc%2FbtsLPxp9xSK%2FLmZ206l2k5Z6UxXuaOE250%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;146&quot; height=&quot;146&quot; data-filename=&quot;다운로드.png&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;1200&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;TypeScript&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;안녕하세요, &lt;b&gt;Jay&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;오늘은 TypeScript에서 자주 사용하는 두 가지 파일인 d.ts 파일과 types.ts 파일의 차이를 알아보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저도 처음 TypeScript를 사용하면서 헷갈렸던 부분 중 하나인데요, 프로젝트를 하다 보면 이 둘을 어떻게 구분하고 사용하는지 감이 안 올 때가 있습니다. 이 글에서는 d.ts 파일과 types.ts 파일의 특징과 용도를 설명해 보겠습니다.&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;1. d.ts 파일이란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;d.ts 파일은 &lt;b&gt;타입 선언 파일(Declaration File)&lt;/b&gt;이라고 합니다.&lt;br /&gt;주로 &lt;b&gt;외부 라이브러리의 타입 정보를 TypeScript에 알려주기 위해&lt;/b&gt; 사용됩니다.&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;예를 들어, 어떤 JavaScript 라이브러리를 사용하는데 해당 라이브러리가 타입 정보를 제공하지 않는다면 d.ts 파일을 만들어서 타입을 직접 정의할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특징:
&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;타입 선언만 포함되며, 컴파일 시 JavaScript로 변환되지 않습니다.&lt;/li&gt;
&lt;li&gt;@types 패키지로 배포되는 타입 정의 파일들이 모두 .d.ts 파일입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// example.d.ts
declare module &quot;my-library&quot; {
  export function doSomething(value: string): void;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 my-library라는 라이브러리가 제공하지 않는 타입을 직접 선언한 예입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. types.ts 파일이란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;types.ts 파일은 &lt;b&gt;프로젝트 내부에서 사용할 타입, 인터페이스 등을 정의하는 파일&lt;/b&gt;입니다.&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;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특징:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반 TypeScript 파일입니다.&lt;/li&gt;
&lt;li&gt;타입 외에도 실행 가능한 코드(함수, 클래스 등)를 포함할 수 있습니다.&lt;/li&gt;
&lt;li&gt;컴파일 시 JavaScript 파일로 변환됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;// types.ts
export interface User {
  id: number;
  name: string;
}

export type Status = &quot;active&quot; | &quot;inactive&quot; | &quot;suspended&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 프로젝트 내부에서 사용할 User 인터페이스와 Status 타입을 정의한 예입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 주요 차이점&lt;/b&gt;&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&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;d.ts 파일&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;types.ts 파일&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;JavaScript 파일로 변환되지 않음&lt;/td&gt;
&lt;td&gt;JavaScript 파일로 변환됨&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;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 언제 어떤 파일을 사용해야 할까요?&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;b&gt;외부 라이브러리 타입 정의&lt;/b&gt;&lt;br /&gt;외부 라이브러리에서 타입을 제공하지 않는 경우 d.ts 파일을 사용합니다.&lt;br /&gt;예: @types 패키지를 설치하거나 직접 선언.&lt;/li&gt;
&lt;li&gt;&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;프로젝트 내부 타입 정의&lt;/b&gt;&lt;br /&gt;프로젝트에서 사용되는 타입을 관리하려면 types.ts 파일을 사용합니다.&lt;br /&gt;예: API 응답 타입, 전역적으로 사용하는 인터페이스 정의 등.&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>JavaScript</category>
      <category>d.ts</category>
      <category>TS</category>
      <category>types.ts</category>
      <category>typeScript</category>
      <category>타입</category>
      <author>코딩하는 Jay</author>
      <guid isPermaLink="true">https://jayprogram.tistory.com/103</guid>
      <comments>https://jayprogram.tistory.com/103#entry103comment</comments>
      <pubDate>Thu, 16 Jan 2025 08:00:11 +0900</pubDate>
    </item>
    <item>
      <title>[Git] git pull, git fetch 명령어 차이</title>
      <link>https://jayprogram.tistory.com/102</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;347&quot; data-origin-height=&quot;145&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vrWMG/btsLOKDq83y/loV3IcjR4d89nDXyZjBZbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vrWMG/btsLOKDq83y/loV3IcjR4d89nDXyZjBZbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vrWMG/btsLOKDq83y/loV3IcjR4d89nDXyZjBZbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvrWMG%2FbtsLOKDq83y%2FloV3IcjR4d89nDXyZjBZbK%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;347&quot; height=&quot;145&quot; data-origin-width=&quot;347&quot; data-origin-height=&quot;145&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;안녕하세요, &lt;b&gt;Jay&lt;/b&gt;입니다.  &lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 Git을 사용하면서 자주 사용하는 명령어인 &lt;b&gt;git pull&lt;/b&gt;과 &lt;b&gt;git fetch&lt;/b&gt;의 차이를 알아보려고 합니다.&lt;br /&gt;두 명령어 모두 원격 저장소(remote repository)와 관련이 있지만, 그 동작 방식에는 명확한 차이가 있습니다.&lt;/p&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;git fetch란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;git fetch는 원격 저장소의 변경사항을 로컬 저장소로 가져옵니다.&lt;br /&gt;하지만 &lt;b&gt;작업 중인 브랜치에는 영향을 주지 않습니다.&lt;/b&gt; 즉, 로컬 브랜치의 상태는 그대로 유지되고, 원격 저장소의 최신 커밋만 확인할 수 있게 됩니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1736917810733&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git fetch&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;사용 예시&lt;/h4&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;li&gt;&lt;b&gt;merge 또는 rebase하기 전에&lt;/b&gt; 최신 상태를 가져와 비교하고 싶을 때 유용합니다.&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;git pull이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;git pull은 git fetch와 git merge를 합친 명령어입니다.&lt;br /&gt;즉, 원격 저장소의 변경 사항을 가져온 뒤, &lt;b&gt;현재 작업 중인 브랜치에 바로 병합(merge)&lt;/b&gt; 합니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1736917882615&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git pull&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;사용 예시&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;로컬 브랜치에 원격 저장소의 최신 변경 사항을 바로 반영&lt;/b&gt;하고 싶을 때 사용합니다.&lt;/li&gt;
&lt;li&gt;협업 프로젝트에서 팀원이 작업한 내용을 빠르게 반영하고 싶을 때 유용합니다.&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;주요 차이점 정리&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&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;git fetch&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;git pull&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&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;/td&gt;
&lt;td&gt;없음&lt;/td&gt;
&lt;td&gt;현재 작업 중인 브랜치가 즉시 업데이트됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;안전성&lt;/td&gt;
&lt;td&gt;원격 변경 사항을 미리 확인 가능 (병합 전 상태 확인 가능)&lt;/td&gt;
&lt;td&gt;충돌 발생 시 바로 해결해야 함&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&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;언제 사용해야 할까?&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;git fetch를 먼저 실행한 후 필요에 따라 병합&lt;/b&gt;하는 것이 더 안전한 방법입니다.&lt;br /&gt;예를 들어, 아래와 같이 진행하면 원격 저장소의 변경 사항을 미리 확인하고 충돌을 방지할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1736917982537&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git fetch 
git merge origin/main&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;git pull은 간단하게 최신 상태를 가져오고 바로 작업하고 싶을 때&lt;/b&gt; 적합합니다.&lt;br /&gt;하지만, 충돌 가능성이 있는 경우 조심해서 사용해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;b&gt;git fetch&lt;/b&gt;: &quot;업데이트 내용을 가져오기만 하고, 잠깐 관찰해볼게요!&quot;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;git pull&lt;/b&gt;: &quot;업데이트 내용 바로 가져오고, 제 브랜치에도 반영할게요!&quot;&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;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://git-scm.com/doc&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://git-scm.com/doc&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1736918029356&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;Git - Documentation&quot; data-og-description=&quot;Documentation Reference The official and comprehensive man pages that are included in the Git package itself. Quick reference guides: GitHub Cheat Sheet | Visual Git Cheat Sheet Book Videos Length: 08:15 Length: 05:06 Length: 05:59 Length: 04:26 See all vi&quot; data-og-host=&quot;git-scm.com&quot; data-og-source-url=&quot;https://git-scm.com/doc&quot; data-og-url=&quot;https://git-scm.com/doc&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://git-scm.com/doc&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://git-scm.com/doc&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&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;Git - Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Documentation Reference The official and comprehensive man pages that are included in the Git package itself. Quick reference guides: GitHub Cheat Sheet | Visual Git Cheat Sheet Book Videos Length: 08:15 Length: 05:06 Length: 05:59 Length: 04:26 See all vi&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;git-scm.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Tool</category>
      <category>Fetch</category>
      <category>git</category>
      <category>Pull</category>
      <category>깃</category>
      <author>코딩하는 Jay</author>
      <guid isPermaLink="true">https://jayprogram.tistory.com/102</guid>
      <comments>https://jayprogram.tistory.com/102#entry102comment</comments>
      <pubDate>Wed, 15 Jan 2025 14:16:17 +0900</pubDate>
    </item>
    <item>
      <title>Error: socket hang up, ECONNRESET 오류</title>
      <link>https://jayprogram.tistory.com/101</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rq2wa/btsLMoUW172/FKZ7uiIe3FpPfr2Hvk7dT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rq2wa/btsLMoUW172/FKZ7uiIe3FpPfr2Hvk7dT1/img.png&quot; data-alt=&quot;본 이미지는 Gemini AI를 활용하여 생성되었습니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rq2wa/btsLMoUW172/FKZ7uiIe3FpPfr2Hvk7dT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Frq2wa%2FbtsLMoUW172%2FFKZ7uiIe3FpPfr2Hvk7dT1%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;512&quot; height=&quot;512&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;512&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;본 이미지는 Gemini AI를 활용하여 생성되었습니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;br /&gt;&lt;br /&gt;안녕하세요, &lt;b&gt;Jay&lt;/b&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&gt;오늘은 &lt;/span&gt;&lt;span&gt;Error: socket hang up&lt;/span&gt;&lt;span&gt;과 &lt;/span&gt;&lt;span&gt;ECONNRESET&lt;/span&gt;&lt;span&gt; 오류에 대해 이야기해보려고 합니다. 이 오류는 클라이언트와 서버 간 연결이 갑작스럽게 종료될 때 발생하는 문제로, 특히 네트워크 요청이나 API 호출 과정에서 자주 나타납니다. 이 글에서는 이 오류의 원인과 해결 방법을 단계별로 알아보겠습니다.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&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;&lt;span&gt;Error: socket hang up&lt;/span&gt;&lt;span&gt;이란?&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Error: socket hang up&lt;/span&gt;&lt;span&gt;은 클라이언트가 서버와 연결을 시도했지만, 그 연결이 예상치 못하게 종료된 상황을 나타냅니다. 이 오류는 주로 다음과 같은 이유로 발생할 수 있습니다:&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;서버 타임아웃&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: 서버가 응답을 처리하는 데 시간이 오래 걸려 연결이 끊긴 경우입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;요청 제한 (Rate Limiting)&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: 서버가 과도한 요청을 감지하고 이를 차단했을 때 발생합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;네트워크 문제&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: 클라이언트와 서버 간 네트워크 연결이 불안정한 경우입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;SSL/TLS 문제&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: SSL/TLS 인증 과정에서 문제가 발생했을 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;서버 과부하&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: 서버가 과부하 상태이거나 설정이 잘못된 경우입니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;해결 방법&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;1. &lt;/span&gt;&lt;span&gt;재시도 로직 구현하기&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;일시적인 네트워크 문제로 인해 발생한 오류는 요청을 재시도함으로써 해결될 수 있습니다. 아래는 JavaScript에서 재시도 로직을 구현한 예제입니다:&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;qml&quot;&gt;&lt;code&gt;const fetchWithRetry = async (url, options, retries = 3) =&amp;gt; {
    try {
        return await fetch(url, options);
    } catch (err) {
        if (retries &amp;gt; 0) {
            return fetchWithRetry(url, options, retries - 1);
        }
        throw err;
    }
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이 코드는 요청 실패 시 최대 3번까지 재시도하도록 설계되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;2. &lt;/span&gt;&lt;span&gt;서버 로그 확인하기&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;서버에 접근할 수 있다면 로그를 확인하여 오류의 원인을 파악하세요. 서버에서 오류 메시지와 원인을 기록하고 있을 가능성이 높습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;3. &lt;/span&gt;&lt;span&gt;타임아웃 시간 늘리기&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;서버 응답 시간이 오래 걸리는 경우 타임아웃 시간을 늘리는 것도 하나의 방법입니다. 아래는 Axios를 사용한 예제입니다:&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const axios = require('axios');
axios.get('https://example.com/api', { timeout: 30000 })
    .then(response =&amp;gt; console.log(response))
    .catch(error =&amp;gt; console.error(error));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;타임아웃 시간을 30초로 설정하여 서버가 느리게 응답해도 오류가 발생하지 않도록 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;4. &lt;/span&gt;&lt;span&gt;네트워크 상태 점검하기&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;클라이언트의 네트워크 연결 상태를 확인하고 안정적인 환경에서 요청을 재시도하세요. 인터넷 연결이 끊기거나 불안정하면 이 오류가 발생할 가능성이 높습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;5. &lt;/span&gt;&lt;span&gt;작은 요청 크기로 테스트하기&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;보내는 데이터 크기가 너무 커서 서버가 이를 처리하지 못했을 수 있습니다. 요청 데이터를 줄여서 테스트해보세요.&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;6. &lt;/span&gt;&lt;span&gt;SSL/TLS 문제 해결하기&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;SSL/TLS 인증서 문제가 원인일 수 있으므로, 인증서가 유효한지 확인하세요. OpenSSL을 사용하여 서버와의 연결 상태를 점검할 수 있습니다:&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;openssl s_client -connect example.com:443&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;추가 디버깅 방법&lt;/span&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-spread=&quot;true&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;Postman 또는 &lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;curl&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;로 요청 테스트하기&lt;/b&gt;&lt;/span&gt;&lt;span&gt;Postman을 사용하여 요청을 보내고 응답을 확인하세요.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;curl -v &lt;a href=&quot;https://example.com/api&quot;&gt;https://example.com/api&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;디버깅 로그 활성화하기&lt;/b&gt;&lt;/span&gt;&lt;span&gt; HTTP 클라이언트의 디버깅 옵션을 활성화하여 요청과 응답의 상세 정보를 확인합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;마무리&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Error: socket hang up&lt;/span&gt;&lt;span&gt; 오류는 다양한 원인으로 발생할 수 있지만, 위에서 소개한 방법들을 활용하면 대부분의 문제를 해결할 수 있습니다. 특히 네트워크 상태를 점검하거나, 재시도 로직을 구현하는 것만으로도 많은 경우에 효과적인 결과를 얻을 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이 글이 여러분의 문제 해결에 도움이 되었길 바랍니다.  &lt;/span&gt;&lt;/p&gt;</description>
      <category>Network</category>
      <author>코딩하는 Jay</author>
      <guid isPermaLink="true">https://jayprogram.tistory.com/101</guid>
      <comments>https://jayprogram.tistory.com/101#entry101comment</comments>
      <pubDate>Mon, 13 Jan 2025 23:01:25 +0900</pubDate>
    </item>
    <item>
      <title>Zustand를 활용한 React 상태 관리</title>
      <link>https://jayprogram.tistory.com/100</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;137&quot; data-origin-height=&quot;21&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dkuSwe/btsLBxYSgt2/cLwzDakKkHf8KEHpQYI0k0/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dkuSwe/btsLBxYSgt2/cLwzDakKkHf8KEHpQYI0k0/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dkuSwe/btsLBxYSgt2/cLwzDakKkHf8KEHpQYI0k0/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdkuSwe%2FbtsLBxYSgt2%2FcLwzDakKkHf8KEHpQYI0k0%2Fimg.webp&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;222&quot; height=&quot;34&quot; data-origin-width=&quot;137&quot; data-origin-height=&quot;21&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;&lt;span&gt;안녕하세요, &lt;b&gt;Jay&lt;/b&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&gt;오늘은 &lt;/span&gt;&lt;span&gt;&lt;b&gt;Zustand&lt;/b&gt;&lt;/span&gt;&lt;span&gt;라는 React 상태 관리 도구를 소개하려고 합니다. Zustand는 경량 상태 관리 라이브러리로, 간단한 API와 강력한 성능을 제공합니다. Redux처럼 강력하면서도 Context API처럼 간결한 사용성을 자랑하기 때문에 많은 React 개발자들에게 사랑받고 있습니다. 이 글에서는 Zustand의 특징과 사용법, 그리고 실제 예제를 통해 Zustand를 활용하는 방법을 자세히 알아보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;Zustand란 무엇인가요?&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Zustand는 독일어로 &quot;상태&quot;를 뜻하며, React 애플리케이션에서 상태 관리를 간단하게 처리할 수 있게 해주는 라이브러리입니다. 다음과 같은 특징을 가지고 있습니다:&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;가벼운 설치 용량&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: 불필요한 의존성이 없고, 코드베이스도 가볍습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;보일러플레이트 제거&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: 복잡한 설정 없이도 간단히 상태를 관리할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;Context API 대체&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: React의 Context API처럼 작동하지만, 더 나은 성능을 제공합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;비동기 작업 지원&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: 비동기 액션도 직관적으로 처리할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;Redux DevTools 호환&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: 디버깅을 쉽게 하기 위해 Redux DevTools와 통합할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;Zustand 설치&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Zustand를 설치하려면 아래 명령어를 실행하세요:&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;yarn add zustand&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;혹은&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;npm install zustand&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;설치 후, Zustand의 &lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt; 함수를 사용하여 스토어를 생성할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;기본 사용법&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Zustand의 기본 사용법은 매우 간단합니다. 아래는 상태를 정의하고 사용하는 간단한 예제입니다:&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { create } from 'zustand';

// Zustand 스토어 생성
const useStore = create((set) =&amp;gt; ({
  count: 0,
  increase: () =&amp;gt; set((state) =&amp;gt; ({ count: state.count + 1 })),
  decrease: () =&amp;gt; set((state) =&amp;gt; ({ count: state.count - 1 })),
}));

function App() {
  const { count, increase, decrease } = useStore();

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;Count: {count}&amp;lt;/h1&amp;gt;
      &amp;lt;button onClick={increase}&amp;gt;Increase&amp;lt;/button&amp;gt;
      &amp;lt;button onClick={decrease}&amp;gt;Decrease&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;코드 설명&lt;/span&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;create&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt; 함수&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: Zustand의 핵심 함수로, 스토어를 생성합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;set&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt; 함수&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: 상태를 업데이트하는 데 사용됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;useStore&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt; 훅&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: 컴포넌트에서 상태와 액션을 가져오는 데 사용됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;Zustand의 주요 기능&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;1. &lt;/span&gt;&lt;span&gt;&lt;b&gt;리액티브 상태 관리&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Zustand는 상태가 변경되면 자동으로 컴포넌트를 다시 렌더링합니다. React의 상태 관리 방식과 비슷하지만, 더 간결한 코드로 이를 구현할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;2. &lt;/span&gt;&lt;span&gt;&lt;b&gt;미들웨어 지원&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Zustand는 미들웨어를 통해 상태를 확장할 수 있습니다. Redux DevTools를 통합하거나 로깅 기능을 추가하는 등 다양한 작업이 가능합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;import { create } from 'zustand';
import { devtools } from 'zustand/middleware';

const useStore = create(
  devtools((set) =&amp;gt; ({
    count: 0,
    increase: () =&amp;gt; set((state) =&amp;gt; ({ count: state.count + 1 })),
  }))
);&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;3. &lt;/span&gt;&lt;span&gt;&lt;b&gt;비동기 액션&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Zustand는 비동기 작업도 간단히 처리할 수 있습니다. 예를 들어, API 호출 결과를 상태에 저장하려면 아래와 같이 작성할 수 있습니다:&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;const useStore = create((set) =&amp;gt; ({
  data: null,
  fetchData: async () =&amp;gt; {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    set({ data });
  },
}));&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;실전 예제: Todo 리스트 구현&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;아래는 Zustand를 활용하여 간단한 Todo 리스트를 구현한 예제입니다:&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { create } from 'zustand';

const useTodoStore = create((set) =&amp;gt; ({
  todos: [],
  addTodo: (todo) =&amp;gt; set((state) =&amp;gt; ({ todos: [...state.todos, { id: Date.now(), ...todo }] })),
  removeTodo: (id) =&amp;gt; set((state) =&amp;gt; ({ todos: state.todos.filter((todo) =&amp;gt; todo.id !== id) })),
}));

function TodoApp() {
  const { todos, addTodo, removeTodo } = useTodoStore();
  const [newTodo, setNewTodo] = useState('');

  const handleAddTodo = () =&amp;gt; {
    if (newTodo.trim()) {
      addTodo({ text: newTodo, completed: false });
      setNewTodo('');
    }
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;Todo List&amp;lt;/h1&amp;gt;
      &amp;lt;input
        type=&quot;text&quot;
        value={newTodo}
        onChange={(e) =&amp;gt; setNewTodo(e.target.value)}
      /&amp;gt;
      &amp;lt;button onClick={handleAddTodo}&amp;gt;Add Todo&amp;lt;/button&amp;gt;
      &amp;lt;ul&amp;gt;
        {todos.map((todo) =&amp;gt; (
          &amp;lt;li key={todo.id}&amp;gt;
            {todo.text} &amp;lt;button onClick={() =&amp;gt; removeTodo(todo.id)}&amp;gt;Remove&amp;lt;/button&amp;gt;
          &amp;lt;/li&amp;gt;
        ))}
      &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default TodoApp;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;특징&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;간결한 상태 정의&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: 상태와 액션을 한 곳에서 관리합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;빠른 업데이트&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: Todo 추가/삭제가 즉각적으로 반영됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;Zustand vs Redux&lt;/span&gt;&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 72.093%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 14.4187%;&quot;&gt;특성&lt;/td&gt;
&lt;td style=&quot;width: 28.372%;&quot;&gt;Zustand&lt;/td&gt;
&lt;td style=&quot;width: 29.0698%;&quot;&gt;Redux&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 14.4187%;&quot;&gt;&lt;span&gt;설정 복잡도&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 28.372%;&quot;&gt;&lt;span&gt;간단함&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 29.0698%;&quot;&gt;&lt;span&gt;비교적 복잡함&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 14.4187%;&quot;&gt;&lt;span&gt;보일러플레이트&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 28.372%;&quot;&gt;&lt;span&gt;거의 없음&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 29.0698%;&quot;&gt;&lt;span&gt;많음&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 14.4187%;&quot;&gt;&lt;span&gt;DevTools 지원&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 28.372%;&quot;&gt;&lt;span&gt;지원 (Redux DevTools와 호환)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 29.0698%;&quot;&gt;&lt;span&gt;기본적으로 지원&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 14.4187%;&quot;&gt;&lt;span&gt;미들웨어 사용&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 28.372%;&quot;&gt;&lt;span&gt;간단하게 통합 가능&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 29.0698%;&quot;&gt;&lt;span&gt;추가 설정 필요&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 14.4187%;&quot;&gt;&lt;span&gt;러닝 커브&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 28.372%;&quot;&gt;&lt;span&gt;낮음&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 29.0698%;&quot;&gt;&lt;span&gt;높음&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;마무리&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Zustand는 간단하면서도 강력한 React 상태 관리 도구입니다. 특히 소규모 프로젝트나 간단한 상태 관리가 필요한 경우에 매우 유용합니다. Redux의 복잡함이나 Context API의 성능 문제로 고민 중이라면 Zustand를 고려해보세요.&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&gt;Zustand를 활용해 더 간결하고 효율적인 React 개발을 경험해보시기 바랍니다.  &lt;/span&gt;&lt;/p&gt;</description>
      <category>React</category>
      <category>REACT</category>
      <category>Zustand</category>
      <category>리액트</category>
      <category>상태</category>
      <category>상태관리</category>
      <author>코딩하는 Jay</author>
      <guid isPermaLink="true">https://jayprogram.tistory.com/100</guid>
      <comments>https://jayprogram.tistory.com/100#entry100comment</comments>
      <pubDate>Tue, 31 Dec 2024 13:26:01 +0900</pubDate>
    </item>
    <item>
      <title>React 환경에서 Markdown 출력하기 (react-markdown)</title>
      <link>https://jayprogram.tistory.com/99</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Markdown-mark.svg.png&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;738&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kPFhy/btsGOKI0Fp0/0q0kaCkKBuYFkB8Zk8iFTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kPFhy/btsGOKI0Fp0/0q0kaCkKBuYFkB8Zk8iFTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kPFhy/btsGOKI0Fp0/0q0kaCkKBuYFkB8Zk8iFTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkPFhy%2FbtsGOKI0Fp0%2F0q0kaCkKBuYFkB8Zk8iFTk%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;445&quot; height=&quot;274&quot; data-filename=&quot;Markdown-mark.svg.png&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;738&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;&amp;nbsp;안녕하세요. Jay 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;오늘은 React-markdown(&lt;a href=&quot;https://github.com/remarkjs/react-markdown&quot;&gt;https://github.com/remarkjs/react-markdown&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;간단한 프로젝트로 openai API 응답을 받아서 화면을 그려주는 프로젝트를 해보게 되었습니다. 해당 API는 SSE(Server-Sent-Event)로 동작하는데, 데이터가 마크다운 형식으로 응답하도록 구성되어 있어서 마크다운 형식을 보여줄 수 있도록 구현하는 것이 필요했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마크다운 형식의 텍스트를 서식이 있는 문서로 보여주는 라이브러리를 찾아봤는데, react-markdown을 찾았고, 괜찮은 라이브러리인 것 같아 한 번 얘기해보겠습니다.&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;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;yarn add react-markdown&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;pre class=&quot;clean&quot;&gt;&lt;code&gt;import ReactMarkdown from &quot;react-markdown&quot;;

const md = `
# This is a H1
## This is a H2
### This is a H3
#### This is a H4
##### This is a H5
###### This is a H6

1. first
2. second
3. third

* 안녕하세요.
* hi
* 하이
`;

function App() {
  return (
    &amp;lt;div className=&quot;App&quot;&amp;gt;
      &amp;lt;ReactMarkdown&amp;gt;{md}&amp;lt;/ReactMarkdown&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력 결과는 아래와 같습니다. 입력된 마크다운 텍스트가 원하는 서식대로 출력된 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-04-15 오후 5.37.03.png&quot; data-origin-width=&quot;576&quot; data-origin-height=&quot;858&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/P1Wtz/btsGPpYUyD1/uke6Srr56M2p7wnlRuiVh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/P1Wtz/btsGPpYUyD1/uke6Srr56M2p7wnlRuiVh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/P1Wtz/btsGPpYUyD1/uke6Srr56M2p7wnlRuiVh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FP1Wtz%2FbtsGPpYUyD1%2Fuke6Srr56M2p7wnlRuiVh0%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;209&quot; height=&quot;311&quot; data-filename=&quot;스크린샷 2024-04-15 오후 5.37.03.png&quot; data-origin-width=&quot;576&quot; data-origin-height=&quot;858&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;h2 data-ke-size=&quot;size26&quot;&gt;Remark&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;ReactMarkdown은 remark plugin을 통해 기본 마크다운 문법 이외에도 다양한 변형 마크다운을 지원합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Github-Flavored Markdown(GFM)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;마크다운의 변형으로 Github-Flavored Markdown이 있습니다. 취소선, 표, 자동 링크 변환, 작업 목록등을 지원합니다. GFM을 문법을 사용하고 싶다면, remark-gfm 패키지를 사용하면 됩니다.&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;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;yarn add remark-gfm&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;실제로 사용하보겠습니다. remark-gfm 패키지를 import하고, remarkPlugins 속성을 이용하여 ReactMarkdown에 적용했습니다. 그리고 작업 목록과 취소선 마크다운을 추가했습니다.&lt;/p&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;import ReactMarkdown from &quot;react-markdown&quot;;
import remarkGfm from &quot;remark-gfm&quot;;

const md = `
# This is a H1
## This is a H2
### This is a H3
#### This is a H4
##### This is a H5
###### This is a H6

1. first
2. second
3. third

* 안녕하세요.
* hi
* 하이

# 할 일
* [x] 개발하기
* [ ] 테스트하기
* [ ] ~~배포하기~~
`;

function App() {
  return (
    &amp;lt;div className=&quot;App&quot;&amp;gt;
      &amp;lt;ReactMarkdown remarkPlugins={[remarkGfm]}&amp;gt;{md}&amp;lt;/ReactMarkdown&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App;

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;결과는 아래와 같습니다. 작업 목록과 취소선도 잘 적용된 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-04-22 오후 5.09.04.png&quot; data-origin-width=&quot;412&quot; data-origin-height=&quot;1148&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buBnkS/btsGO9oitng/MZbqX8hCC0Rt5E5r0YuCT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buBnkS/btsGO9oitng/MZbqX8hCC0Rt5E5r0YuCT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buBnkS/btsGO9oitng/MZbqX8hCC0Rt5E5r0YuCT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuBnkS%2FbtsGO9oitng%2FMZbqX8hCC0Rt5E5r0YuCT0%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;195&quot; height=&quot;543&quot; data-filename=&quot;스크린샷 2024-04-22 오후 5.09.04.png&quot; data-origin-width=&quot;412&quot; data-origin-height=&quot;1148&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;&amp;nbsp;이외에도 다양한 remark plugin들이 존재합니다. 본인의 필요에 따라 다양한 plugin을 import 해서 사용할 수 있습니다.&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;참고:&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/remarkjs/react-markdown&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/remarkjs/react-markdown&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://namu.wiki/w/%EB%A7%88%ED%81%AC%EB%8B%A4%EC%9A%B4#s-4.1&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://namu.wiki/w/%EB%A7%88%ED%81%AC%EB%8B%A4%EC%9A%B4#s-4.1&lt;/a&gt;&lt;/p&gt;</description>
      <category>React</category>
      <category>GFM</category>
      <category>markdown</category>
      <category>REACT</category>
      <category>react-markdown</category>
      <author>코딩하는 Jay</author>
      <guid isPermaLink="true">https://jayprogram.tistory.com/99</guid>
      <comments>https://jayprogram.tistory.com/99#entry99comment</comments>
      <pubDate>Mon, 22 Apr 2024 17:25:35 +0900</pubDate>
    </item>
    <item>
      <title>Next.js v14</title>
      <link>https://jayprogram.tistory.com/98</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-10-31 오후 3.38.08.png&quot; data-origin-width=&quot;210&quot; data-origin-height=&quot;64&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CDfeO/btszyR1JXI6/uaXQNwJkuSb8FvyR9IVCp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CDfeO/btszyR1JXI6/uaXQNwJkuSb8FvyR9IVCp1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CDfeO/btszyR1JXI6/uaXQNwJkuSb8FvyR9IVCp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCDfeO%2FbtszyR1JXI6%2FuaXQNwJkuSb8FvyR9IVCp1%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;210&quot; height=&quot;64&quot; data-filename=&quot;스크린샷 2023-10-31 오후 3.38.08.png&quot; data-origin-width=&quot;210&quot; data-origin-height=&quot;64&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Turbopack&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;53% 더 빠른 로컬 서버 시작&lt;/li&gt;
&lt;li&gt;94% 더 빠른 코드 업데이트시, 새로고침&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Server Actions (Stable)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;caching과 revalidating 통합&lt;/li&gt;
&lt;li&gt;간단한 함수나 기본적인 형태로 동작&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Partial Prerendering (Preview)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;빠른 초기 정적 응답 + 스트리밍 동적 콘텐츠&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Next.js Learn (New)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;App Router, authentication, database등의 무료 코스 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1698734264799&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Next.js 14&quot; data-og-description=&quot;Next.js 14 includes included performance, stability for Server Actions, a new course teaching the App Router, and more.&quot; data-og-host=&quot;nextjs.org&quot; data-og-source-url=&quot;https://nextjs.org/blog/next-14&quot; data-og-url=&quot;https://nextjs.org/blog/next-14&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/beLQNe/hyUkjrthSK/KwOEFJsViimXAn82NiyORk/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080,https://scrap.kakaocdn.net/dn/dPwnqX/hyUnPWuMDQ/lmz2MFXEUrtohKhOHLIA50/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080&quot;&gt;&lt;a href=&quot;https://nextjs.org/blog/next-14&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://nextjs.org/blog/next-14&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/beLQNe/hyUkjrthSK/KwOEFJsViimXAn82NiyORk/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080,https://scrap.kakaocdn.net/dn/dPwnqX/hyUnPWuMDQ/lmz2MFXEUrtohKhOHLIA50/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Next.js 14&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Next.js 14 includes included performance, stability for Server Actions, a new course teaching the App Router, and more.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;nextjs.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>React</category>
      <category>Next.js</category>
      <category>REACT</category>
      <author>코딩하는 Jay</author>
      <guid isPermaLink="true">https://jayprogram.tistory.com/98</guid>
      <comments>https://jayprogram.tistory.com/98#entry98comment</comments>
      <pubDate>Tue, 31 Oct 2023 15:38:33 +0900</pubDate>
    </item>
    <item>
      <title>React v18</title>
      <link>https://jayprogram.tistory.com/97</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-10-30 오후 5.13.59.png&quot; data-origin-width=&quot;306&quot; data-origin-height=&quot;274&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PFdnQ/btszvCQ3g2M/opm6EEsl0TwO7OQHRjsAU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PFdnQ/btszvCQ3g2M/opm6EEsl0TwO7OQHRjsAU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PFdnQ/btszvCQ3g2M/opm6EEsl0TwO7OQHRjsAU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPFdnQ%2FbtszvCQ3g2M%2Fopm6EEsl0TwO7OQHRjsAU0%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;306&quot; height=&quot;274&quot; data-filename=&quot;스크린샷 2023-10-30 오후 5.13.59.png&quot; data-origin-width=&quot;306&quot; data-origin-height=&quot;274&quot;/&gt;&lt;/span&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;new Root API&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이전에는 Root Container에 변화가 없더라도, 새로 랜더링하기 위해 Root를 반드시 체크했어야만 했다.&lt;/li&gt;
&lt;li&gt;위 과정이 무의미하다고 판단하여 새로운 Root API를 생성함.&lt;/li&gt;
&lt;li&gt;매번 Root를 체크하지 않음.&lt;/li&gt;
&lt;li&gt;새로운 Root API에는 Callback이 제거되었는데, hydration, SSR 과 함께 Callback을 사용하면 타이밍이 개발자의 생각과 다르게 작동할 수 있기 때문이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;automatic batching&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;더 나은 랜더링 성능을 위해 state 업데이트를 하나로 그룹화 하는 것을 의미한다.&lt;/li&gt;
&lt;li&gt;모든 state 업데이트는 리액트에서 발생하는 이벤트 내부의 업데이트와 동일한 방식으로 처리하여 랜더링 횟 수를 최소화 함.&lt;/li&gt;
&lt;li&gt;automatic batching을 원하지 않는 경우, ReactDom.flusySync() 함수를 사용하면 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Transition&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;state를 업데이트하는 우선순위를 지정할 수 있음.&lt;/li&gt;
&lt;li&gt;Transition API를 활용하기 위해서는 new Root API를 사용해야함.&lt;/li&gt;
&lt;li&gt;useTransition Hook을 사용하여 Transition을 쉽게 사용할 수 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;isPending
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Transition이 진행중인지 여부를 나타냄&lt;/li&gt;
&lt;li&gt;이를 통해 사용자들에게 화면 로딩 중, 데이터 업데이트 여부를 전달할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;startTransition
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;startTransition 함수 내부에서 state 업데이트를 진행하면, 우선순위 큐 내부에서 뒤로 밀려 업데이트가 진행됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1698653669621&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;React 18 버전의 실상을 파헤치다. - 오픈소스컨설팅 테크블로그 %&quot; data-og-description=&quot;안녕하세요. Playce Dev팀 강동희 입니다. 22년 4월 React 의 버전이 18버전으로 메이저 업그레이드 되었습니다. 버전이 올라가면서 무엇이 어떻게 변화 했는지 집중적으로 살펴봅시다!&quot; data-og-host=&quot;tech.osci.kr&quot; data-og-source-url=&quot;https://tech.osci.kr/react-18v/&quot; data-og-url=&quot;https://tech.osci.kr/react-18v/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bMcY5J/hyUnLsO98U/lkbCffxrvfbXSNSmI0122k/img.png?width=1026&amp;amp;height=478&amp;amp;face=0_0_1026_478,https://scrap.kakaocdn.net/dn/h7NWZ/hyUkfCqG5N/Dc5gbev2iYPBXD8rF25PqK/img.png?width=1026&amp;amp;height=478&amp;amp;face=0_0_1026_478,https://scrap.kakaocdn.net/dn/bLMTyO/hyUnOiMyVO/h22MWdHp7wTrMkqkIO8h50/img.png?width=708&amp;amp;height=683&amp;amp;face=0_0_708_683&quot;&gt;&lt;a href=&quot;https://tech.osci.kr/react-18v/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://tech.osci.kr/react-18v/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bMcY5J/hyUnLsO98U/lkbCffxrvfbXSNSmI0122k/img.png?width=1026&amp;amp;height=478&amp;amp;face=0_0_1026_478,https://scrap.kakaocdn.net/dn/h7NWZ/hyUkfCqG5N/Dc5gbev2iYPBXD8rF25PqK/img.png?width=1026&amp;amp;height=478&amp;amp;face=0_0_1026_478,https://scrap.kakaocdn.net/dn/bLMTyO/hyUnOiMyVO/h22MWdHp7wTrMkqkIO8h50/img.png?width=708&amp;amp;height=683&amp;amp;face=0_0_708_683');&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;React 18 버전의 실상을 파헤치다. - 오픈소스컨설팅 테크블로그 %&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요. Playce Dev팀 강동희 입니다. 22년 4월 React 의 버전이 18버전으로 메이저 업그레이드 되었습니다. 버전이 올라가면서 무엇이 어떻게 변화 했는지 집중적으로 살펴봅시다!&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;tech.osci.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>React</category>
      <category>REACT</category>
      <category>리액트</category>
      <author>코딩하는 Jay</author>
      <guid isPermaLink="true">https://jayprogram.tistory.com/97</guid>
      <comments>https://jayprogram.tistory.com/97#entry97comment</comments>
      <pubDate>Mon, 30 Oct 2023 17:16:07 +0900</pubDate>
    </item>
    <item>
      <title>[React] 이벤트 리스너에서 현재 상태가져오기</title>
      <link>https://jayprogram.tistory.com/96</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;luke-stackpoole-eWqOgJ-lfiI-unsplash.jpg&quot; data-origin-width=&quot;5105&quot; data-origin-height=&quot;6381&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXbZ4D/btskIPLOI1y/0XkshzYcoIAe4fWF6XY1X0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXbZ4D/btskIPLOI1y/0XkshzYcoIAe4fWF6XY1X0/img.jpg&quot; data-alt=&quot;사진: Unsplash 의 Luke Stackpoole&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXbZ4D/btskIPLOI1y/0XkshzYcoIAe4fWF6XY1X0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXbZ4D%2FbtskIPLOI1y%2F0XkshzYcoIAe4fWF6XY1X0%2Fimg.jpg&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;475&quot; height=&quot;594&quot; data-filename=&quot;luke-stackpoole-eWqOgJ-lfiI-unsplash.jpg&quot; data-origin-width=&quot;5105&quot; data-origin-height=&quot;6381&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;사진: Unsplash 의 Luke Stackpoole&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;&amp;nbsp;useRef를 이용해 이 문제를 해결 할 수 있습니다.&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;화면에는 현재 count 상태 값을 출력하고, 버튼을 클릭하면 count 값을 1 증가시킵니다. 증가된 상태 값은 바로 적용되어 화면에 출력됩니다. 브라우저 화면을 더블 클릭하면 count 값을 alert 창에 출력하도록 이벤트 리스터도 추가했습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;import &quot;./App.css&quot;;
import { useEffect, useState } from &quot;react&quot;;

function App() {
  const [count, setCount] = useState(0);

  const incHandler = () =&amp;gt; {
    setCount(count + 1);
  };

  useEffect(() =&amp;gt; {
    window.addEventListener(&quot;dblclick&quot;, () =&amp;gt; {
      alert(&quot;count: &quot; + count);
    });
  }, []);

  return (
    &amp;lt;div className=&quot;App&quot;&amp;gt;
      &amp;lt;header className=&quot;App-header&quot;&amp;gt;
        &amp;lt;p&amp;gt;{count}&amp;lt;/p&amp;gt;
        &amp;lt;button onClick={incHandler}&amp;gt;증가&amp;lt;/button&amp;gt;
      &amp;lt;/header&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;여기서 바로 문제가 재현됩니다. count를 증가시키고 브라우저 화면을 더블클릭하여 count 값을 출력했는데, 현재 count 값과 다른 값이 출력되는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-06-20 오후 5.01.59.png&quot; data-origin-width=&quot;1546&quot; data-origin-height=&quot;1394&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XVaoD/btskKD4x8GS/vkVGSB8BRnpfrjrPym8RS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XVaoD/btskKD4x8GS/vkVGSB8BRnpfrjrPym8RS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XVaoD/btskKD4x8GS/vkVGSB8BRnpfrjrPym8RS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXVaoD%2FbtskKD4x8GS%2FvkVGSB8BRnpfrjrPym8RS1%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;1546&quot; height=&quot;1394&quot; data-filename=&quot;스크린샷 2023-06-20 오후 5.01.59.png&quot; data-origin-width=&quot;1546&quot; data-origin-height=&quot;1394&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이것은 이벤트가 등록되는 시점의 count 값을 복사하여 참조하고 있기 때문에 발생되는 문제입니다. 이를 해결하기 위해서는 useRef를 활용해야합니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;import &quot;./App.css&quot;;
import { useEffect, useRef, useState } from &quot;react&quot;;

function App() {
  const [count, _setCount] = useState(0);
  const countRef = useRef(count);
  const setCount = (value) =&amp;gt; {
    countRef.current = value;
    _setCount(value);
  };

  const incHandler = () =&amp;gt; {
    setCount(count + 1);
  };

  useEffect(() =&amp;gt; {
    window.addEventListener(&quot;dblclick&quot;, () =&amp;gt; {
      alert(&quot;count: &quot; + countRef.current);
    });
  }, []);

  return (
    &amp;lt;div className=&quot;App&quot;&amp;gt;
      &amp;lt;header className=&quot;App-header&quot;&amp;gt;
        &amp;lt;p&amp;gt;{count}&amp;lt;/p&amp;gt;
        &amp;lt;button onClick={incHandler}&amp;gt;증가&amp;lt;/button&amp;gt;
      &amp;lt;/header&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;위와 같이 수정하면 됩니다. useRef 훅을 이용해 countRef 상태를 만들고, setCount할 때, countRef.current와 count 상태를 모두 변경합니다. 이벤트가 등록되는 시점에는 countRef의 참조값이 복사되기 때문에 countRef.current 값을 활용하면 최신의 상태를 유지할 수 있게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-06-20 오후 5.10.48.png&quot; data-origin-width=&quot;1544&quot; data-origin-height=&quot;1402&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEcVqf/btskJi7SJd3/CQHmeDUEdf7uJjYi75SAo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEcVqf/btskJi7SJd3/CQHmeDUEdf7uJjYi75SAo1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEcVqf/btskJi7SJd3/CQHmeDUEdf7uJjYi75SAo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEcVqf%2FbtskJi7SJd3%2FCQHmeDUEdf7uJjYi75SAo1%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;1544&quot; height=&quot;1402&quot; data-filename=&quot;스크린샷 2023-06-20 오후 5.10.48.png&quot; data-origin-width=&quot;1544&quot; data-origin-height=&quot;1402&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샘플 소스: &lt;a href=&quot;https://github.com/hjleesm/test-useref&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/hjleesm/test-useref&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1687249864959&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - hjleesm/test-useref&quot; data-og-description=&quot;Contribute to hjleesm/test-useref development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/hjleesm/test-useref&quot; data-og-url=&quot;https://github.com/hjleesm/test-useref&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dpmvL0/hyS4ztIEE0/MKIt8mP9rcMIkUftVT0Ayk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/hjleesm/test-useref&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/hjleesm/test-useref&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dpmvL0/hyS4ztIEE0/MKIt8mP9rcMIkUftVT0Ayk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&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;GitHub - hjleesm/test-useref&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to hjleesm/test-useref development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고: &lt;a href=&quot;https://medium.com/geographit/accessing-react-state-in-event-listeners-with-usestate-and-useref-hooks-8cceee73c559&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://medium.com/geographit/accessing-react-state-in-event-listeners-with-usestate-and-useref-hooks-8cceee73c559&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1687249901688&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Accessing React State in Event Listeners with useState and useRef hooks&quot; data-og-description=&quot;If you&amp;rsquo;re using React hooks in a component with an event listener, your event listener callback cannot access the latest state. We can&amp;hellip;&quot; data-og-host=&quot;medium.com&quot; data-og-source-url=&quot;https://medium.com/geographit/accessing-react-state-in-event-listeners-with-usestate-and-useref-hooks-8cceee73c559&quot; data-og-url=&quot;https://medium.com/geographit/accessing-react-state-in-event-listeners-with-usestate-and-useref-hooks-8cceee73c559&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Wl1WF/hyS2B7LFfA/VmRxRdVR38fQnV3nD4pmcK/img.jpg?width=1200&amp;amp;height=887&amp;amp;face=0_0_1200_887&quot;&gt;&lt;a href=&quot;https://medium.com/geographit/accessing-react-state-in-event-listeners-with-usestate-and-useref-hooks-8cceee73c559&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://medium.com/geographit/accessing-react-state-in-event-listeners-with-usestate-and-useref-hooks-8cceee73c559&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Wl1WF/hyS2B7LFfA/VmRxRdVR38fQnV3nD4pmcK/img.jpg?width=1200&amp;amp;height=887&amp;amp;face=0_0_1200_887');&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;Accessing React State in Event Listeners with useState and useRef hooks&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;If you&amp;rsquo;re using React hooks in a component with an event listener, your event listener callback cannot access the latest state. We can&amp;hellip;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;medium.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>React</category>
      <category>REACT</category>
      <category>State</category>
      <category>useRef</category>
      <category>useState</category>
      <category>리액트</category>
      <category>상태</category>
      <category>이벤트</category>
      <category>이벤트리스너</category>
      <author>코딩하는 Jay</author>
      <guid isPermaLink="true">https://jayprogram.tistory.com/96</guid>
      <comments>https://jayprogram.tistory.com/96#entry96comment</comments>
      <pubDate>Tue, 20 Jun 2023 17:32:14 +0900</pubDate>
    </item>
    <item>
      <title>[Recoil] Selectors 기본 알아보기</title>
      <link>https://jayprogram.tistory.com/95</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1744&quot; data-origin-height=&quot;1160&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAZSVh/btrXhmTr4fq/ov9LmzQncNjG3rjrYueDm0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAZSVh/btrXhmTr4fq/ov9LmzQncNjG3rjrYueDm0/img.jpg&quot; data-alt=&quot;이 사진을 Unsplash에서 제공해 주신 Alexander Schimmeck @alschim 님께 감사드립니다  &quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAZSVh/btrXhmTr4fq/ov9LmzQncNjG3rjrYueDm0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAZSVh%2FbtrXhmTr4fq%2Fov9LmzQncNjG3rjrYueDm0%2Fimg.jpg&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;1744&quot; height=&quot;1160&quot; data-origin-width=&quot;1744&quot; data-origin-height=&quot;1160&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이 사진을 Unsplash에서 제공해 주신 Alexander Schimmeck @alschim 님께 감사드립니다  &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;selectors는 atoms이나 다른 selectors를 입력으로 받아드리는 순수 함수이다.&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;selectors는 상태를 기반으로 하는 파생 데이터를 계산하는 데 사용된다. 이 말은, 최소한의 상태만 atoms에 저장하고, atoms과 selectors를 조합/가공하여 파생될 수 있는 모든 데이터는 selectors에 명시한 함수를 통해 효율적으로 계산함으로 쓸데 없는 상태의 보존을 방지한다.&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;selector 함수 내부에서 사용하는 atoms 또는 selectors가 업데이트 되면 해당 selector 함수도 다시 실행된다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;const fontSizeLabelState = selector({
  key: 'fontSizeLabelState',
  get: ({get}) =&amp;gt; {
    const fontSize = get(fontSizeState);
    const unit = 'px';

    return `${fontSize}${unit}`;
  },
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 위 fontSizeLabelState selector의 경우, fontSizeState atom과 의존성이 있기 때문에 fontSizeState atom이 업데이트되면 fontSizeLabelState selector 함수도 다시 실행된다.&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;위 코드를 통해 selectors에 대해 좀 더 자세히 살펴보자.&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;selectors는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;selector&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수를 사용해 정의한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;get&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;속성은 계산될 함수다. get 함수는 get이라는 파라매터를 받는데, get 파라매터를 통해 atoms나 다른 selectors를 사용할 수 있다. get 함수를 통해 의존성이 생기면, 의존성을 갖는 atoms나 selectors가 업데이트 되었을 때, 이 함수도 다시 실행된다.&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;이렇게 정의한 selectors는 어떻게 사용할 수 있을까?&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function FontButton() {
  const [fontSize, setFontSize] = useRecoilState(fontSizeState);
  const fontSizeLabel = useRecoilValue(fontSizeLabelState);

  return (
    &amp;lt;&amp;gt;
      &amp;lt;div&amp;gt;Current font size: ${fontSizeLabel}&amp;lt;/div&amp;gt;

      &amp;lt;button onClick={setFontSize(fontSize + 1)} style={{fontSize}}&amp;gt;
        Click to Enlarge
      &amp;lt;/button&amp;gt;
    &amp;lt;/&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;selectors는 atoms와 동일한 방법으로 사용할 수 있다. fontSizeLabelState selector의 경우, get 함수만 정의하여 읽기만 가능한 selector라고 할 수 있다. 읽기만 가능하기 때문에 혼란을 줄이기 위해, useRecoilValue()를 사용해 해당 selector를 사용하고 있다.(만약, selector에 set 함수도 정의했다면, useRecoilState()를 사용해 읽고 쓸 수 도 있다!)&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;만약, 위 함수에서 setFontSize() 함수를 통해 fontSizeState의 상태가 변화하면 fontSizeState atom과 해당 atom에 의존성을 갖는 fontSizeLabelState selector가 모두 다시 렌더링된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고:&lt;/p&gt;
&lt;figure id=&quot;og_1674791681252&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;주요 개념 | Recoil&quot; data-og-description=&quot;개요&quot; data-og-host=&quot;recoiljs.org&quot; data-og-source-url=&quot;https://recoiljs.org/ko/docs/introduction/core-concepts/&quot; data-og-url=&quot;https://recoiljs.org/ko/docs/introduction/core-concepts&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/1RdfE/hyRpLpxfTk/Ehu7dPVAOTanPzV3leCHt0/img.png?width=1420&amp;amp;height=646&amp;amp;face=0_0_1420_646,https://scrap.kakaocdn.net/dn/dk9mOf/hyRpGPiclP/BfvnuOpl0g4TPVCTzQfkIk/img.png?width=1420&amp;amp;height=646&amp;amp;face=0_0_1420_646&quot;&gt;&lt;a href=&quot;https://recoiljs.org/ko/docs/introduction/core-concepts/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://recoiljs.org/ko/docs/introduction/core-concepts/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/1RdfE/hyRpLpxfTk/Ehu7dPVAOTanPzV3leCHt0/img.png?width=1420&amp;amp;height=646&amp;amp;face=0_0_1420_646,https://scrap.kakaocdn.net/dn/dk9mOf/hyRpGPiclP/BfvnuOpl0g4TPVCTzQfkIk/img.png?width=1420&amp;amp;height=646&amp;amp;face=0_0_1420_646');&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;주요 개념 | Recoil&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;개요&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;recoiljs.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이&amp;nbsp;사진을&amp;nbsp;Unsplash에서&amp;nbsp;제공해&amp;nbsp;주신&amp;nbsp;Alexander&amp;nbsp;Schimmeck&amp;nbsp;@alschim&amp;nbsp;님께&amp;nbsp;감사드립니다&amp;nbsp; &lt;/p&gt;</description>
      <category>React</category>
      <category>ATOM</category>
      <category>REACT</category>
      <category>recoil</category>
      <category>Selector</category>
      <category>리액트</category>
      <category>상태관리</category>
      <author>코딩하는 Jay</author>
      <guid isPermaLink="true">https://jayprogram.tistory.com/95</guid>
      <comments>https://jayprogram.tistory.com/95#entry95comment</comments>
      <pubDate>Fri, 27 Jan 2023 12:59:57 +0900</pubDate>
    </item>
  </channel>
</rss>