<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>e7e</title>
    <link>https://e-7-e.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Thu, 25 Jun 2026 08:44:31 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>e7e</managingEditor>
    <image>
      <title>e7e</title>
      <url>https://tistory1.daumcdn.net/tistory/5336194/attach/6fa12594f4704d26aa850bc8f5cac692</url>
      <link>https://e-7-e.tistory.com</link>
    </image>
    <item>
      <title>React + Tailwindcss + Shadcn(UI)</title>
      <link>https://e-7-e.tistory.com/328</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;headless ui&lt;/b&gt; 컴포넌트 템플릿 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;shadn ui&lt;/b&gt;&lt;/span&gt;가 인기가 있다고 하닝.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;css 게임 체인저 &lt;b&gt;tailwindcss 와 함께 쓰면 찰떡 궁합&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;headless 컴포넌트란 기능만 로직만 되어 있고, css가 안되어 있는 걸 말한당.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화장하지 않은 그녀라 말해도 될깡?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의외로 headless 란 말은 많이 사용되는데.. 암튼 아래 참조당. 느끼미 왔당&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;headless server&amp;nbsp; =&amp;gt;&amp;nbsp; &lt;/b&gt;모니터 없는 서버&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;headless jdk&amp;nbsp; &amp;nbsp; &amp;nbsp;=&amp;gt; &lt;/b&gt;GUI&amp;nbsp; 라이브러리 뺀 jdk&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;headless os&amp;nbsp; &amp;nbsp; &amp;nbsp;=&amp;gt; &lt;/b&gt;GUI 없이 터미널만 있는 OS&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;vite에서의 설치는 아래 링크가 공식 참조당.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ui.shadcn.com/docs/installation/vite&quot;&gt;https://ui.shadcn.com/docs/installation/vite&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;암튼 요즘엔 눈으로 보고, 손으로 안 만지면 느끼미가 오지 않으닝, 일단 GO당&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;vscode로&amp;nbsp; 새로 폴더(이름 소문자롱) 만들어 열공, 터미널에서 vite 프로젝트 생성&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;npx create-vite@latest .&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&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;react_vite.png&quot; data-origin-width=&quot;701&quot; data-origin-height=&quot;743&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dgl1ZK/dJMcagj1XPq/TP4HvjZWNgSmqIpxBxCm21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dgl1ZK/dJMcagj1XPq/TP4HvjZWNgSmqIpxBxCm21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dgl1ZK/dJMcagj1XPq/TP4HvjZWNgSmqIpxBxCm21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdgl1ZK%2FdJMcagj1XPq%2FTP4HvjZWNgSmqIpxBxCm21%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;701&quot; height=&quot;743&quot; data-filename=&quot;react_vite.png&quot; data-origin-width=&quot;701&quot; data-origin-height=&quot;743&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Tailwindcss를 설치하장.&amp;nbsp; 괘니 터미널 1개 더 열장.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[ &lt;b&gt;path&lt;/b&gt;는 경로 alias(별명) 설정에 필요해서 그냥 넣었당 ]&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;npm install tailwindcss&amp;nbsp; @tailwindcss/vite&lt;br /&gt;npm install path&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;index.css &lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;을 아래처럼 수정&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769581481756&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@import &quot;tailwindcss&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;tailwindcss 적용 및 @ 글자에 alias 적용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;vite.config.js&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769581875163&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
import path from 'path'


// https://vite.dev/config/
export default defineConfig({
  plugins: [react(),tailwindcss()],
  resolve: {
    alias: {
      &quot;@&quot;:path.resolve(__dirname,&quot;./src&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt; &lt;b&gt;jsconfig.json&lt;/b&gt; &lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769582057356&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;compilerOptions&quot;: {
    &quot;baseUrl&quot;: &quot;.&quot;,
    &quot;paths&quot;: {
      &quot;@/*&quot;: [&quot;./src/*&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;shadcn 적용을 위한 초기화 작업&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;npx shadcn@latest init&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&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;shadn1.png&quot; data-origin-width=&quot;895&quot; data-origin-height=&quot;468&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8NQSk/dJMcafSX6uA/RjF1m26KX56PzeyYiIvMak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8NQSk/dJMcafSX6uA/RjF1m26KX56PzeyYiIvMak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8NQSk/dJMcafSX6uA/RjF1m26KX56PzeyYiIvMak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8NQSk%2FdJMcafSX6uA%2FRjF1m26KX56PzeyYiIvMak%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;895&quot; height=&quot;468&quot; data-filename=&quot;shadn1.png&quot; data-origin-width=&quot;895&quot; data-origin-height=&quot;468&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;요 시점에 index.css 파일을 열어보면, 자동으로 많은 내용이 추가되었음을 안당.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;src 디렉토리 아래에&amp;nbsp; lib 폴더와 그 안에 utils.js 파일도 자동 생성됨을 안당.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;src 바깥에는 components.json 파일도 만들어졌음을 안당.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;shadcn/ui&lt;/b&gt; 와 &lt;b&gt;tailwindcss를 같이&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;shadcn 제공 Button&lt;/b&gt; 컴포넌트 1개 추가해서 사용해 보도록 하장. 터미널에 아래 명령&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;npx shadcn@latest add button&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;src 폴더 아래 자동으로 components/ui 폴더 생기고, 그안에 button.jsx가 있음을 안당.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[ 당연히 button.jsx 파일을 열어보는 건 본능적으로 금상첨화임을 느낀당 ]&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: #f89009;&quot;&gt;&lt;b&gt;App.jsx&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769585972044&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from &quot;react&quot;;
import { Button } from &quot;./components/ui/button&quot;;

function App() {
  const [btnName, setBtnName] = useState(&quot;E7E&quot;);
  const handleClick = (e) =&amp;gt; {
    setBtnName(btnName === &quot;E7E&quot; ? &quot;로또1등당첨&quot; : &quot;E7E&quot;);
  };

  return (
    &amp;lt;div className=&quot;w-1/2 mx-auto mt-10&quot;&amp;gt;
      &amp;lt;h1 className=&quot;text-5xl text-center rounded-full bg-violet-700 text-white p-5&quot;&amp;gt;
        Shadn/UI + Tailwindcss
      &amp;lt;/h1&amp;gt;
      &amp;lt;Button
        onClick={handleClick}
        className={
          &quot;text-white text-7xl mt-20   h-50  w-full   hover:animate-bounce hover:bg-pink-900&quot;
        }
      &amp;gt;
        {btnName}
      &amp;lt;/Button&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;shadn2.png&quot; data-origin-width=&quot;1250&quot; data-origin-height=&quot;607&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/L0gqB/dJMcabJO0Vv/zN2vHDsi3stb5Un5Avzd40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/L0gqB/dJMcabJO0Vv/zN2vHDsi3stb5Un5Avzd40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/L0gqB/dJMcabJO0Vv/zN2vHDsi3stb5Un5Avzd40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FL0gqB%2FdJMcabJO0Vv%2FzN2vHDsi3stb5Un5Avzd40%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;1250&quot; height=&quot;607&quot; data-filename=&quot;shadn2.png&quot; data-origin-width=&quot;1250&quot; data-origin-height=&quot;607&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;Button 1개 해 본걸로는 명암(밝고 또 어두움) 내밀기가&amp;nbsp; 그림자 질 수 있으니&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dialog 컴포넌트를 1개 더 해 보도록 하장.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ui.shadcn.com/docs/components/radix/dialog&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ui.shadcn.com/docs/components/radix/dialog&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;먼저 dialog 컴포넌트를 추가하장.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;npx shadcn@latest add dialog&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;components 폴더에 dialog.jsx가 추가됨을 안당. 심지어 이제 느낌도 온당. 아~항&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;App.jsx&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769588789341&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Button } from &quot;./components/ui/button&quot;;
import {
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from &quot;./components/ui/dialog&quot;;

function App() {
  return (
    &amp;lt;div className=&quot;w-1/2 mx-auto mt-5&quot;&amp;gt;
      &amp;lt;h1 className=&quot;text-4xl text-center rounded-full bg-violet-700 text-white p-5&quot;&amp;gt;
        Shadn/UI + Tailwindcss
      &amp;lt;/h1&amp;gt;
      &amp;lt;hr className=&quot;my-2&quot; /&amp;gt;
      &amp;lt;Dialog&amp;gt;
        &amp;lt;DialogTrigger className=&quot;text-4xl bg-pink-700 text-white rounded-2xl  ml-15 w-2/3&quot;&amp;gt;
          크을릭 해보장
        &amp;lt;/DialogTrigger&amp;gt;
        &amp;lt;DialogContent&amp;gt;
          &amp;lt;DialogHeader&amp;gt;
            &amp;lt;DialogTitle
              className={
                &quot;text-4xl text-indigo-700 text-center bg-gray-400 rounded-2xl  my-4&quot;
              }
            &amp;gt;
              그게 정말이닝?
            &amp;lt;/DialogTitle&amp;gt;
            &amp;lt;DialogDescription
              className={&quot;text-2xl text-gray-600 font-extrabold&quot;}
            &amp;gt;
              응 덩말롱 Headless 랭
            &amp;lt;/DialogDescription&amp;gt;
          &amp;lt;/DialogHeader&amp;gt;
          &amp;lt;div className=&quot;flex h-100&quot;&amp;gt;
            &amp;lt;div className=&quot;flex-1 self-center&quot;&amp;gt;
              &amp;lt;img
                className=&quot;rounded-full&quot;
                src=&quot;https://100k-faces.vercel.app/api/random-image&quot;
              /&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div className=&quot;flex-1 self-center &quot;&amp;gt;
              &amp;lt;p&amp;gt;
                당연하징... &amp;lt;br /&amp;gt;
                God 수저는 내 스타일이 아니얌 &amp;lt;br /&amp;gt;
                허지만... 그렇지만...
                &amp;lt;br /&amp;gt;
                그래도 로또는 1등 하고싶당. Oh My God!
              &amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/div&amp;gt;
          &amp;lt;DialogFooter className=&quot;sm:justify-end&quot;&amp;gt;
            &amp;lt;DialogClose asChild&amp;gt;
              &amp;lt;Button type=&quot;button&quot;&amp;gt;다드불어&amp;lt;/Button&amp;gt;
            &amp;lt;/DialogClose&amp;gt;
          &amp;lt;/DialogFooter&amp;gt;
        &amp;lt;/DialogContent&amp;gt;
      &amp;lt;/Dialog&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;shadn3.png&quot; data-origin-width=&quot;1013&quot; data-origin-height=&quot;828&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dAdSMS/dJMcachC1i0/czOoPyfkabC5TbKCBK1vwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dAdSMS/dJMcachC1i0/czOoPyfkabC5TbKCBK1vwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dAdSMS/dJMcachC1i0/czOoPyfkabC5TbKCBK1vwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdAdSMS%2FdJMcachC1i0%2FczOoPyfkabC5TbKCBK1vwK%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;1013&quot; height=&quot;828&quot; data-filename=&quot;shadn3.png&quot; data-origin-width=&quot;1013&quot; data-origin-height=&quot;828&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;style3&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;/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;&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;특별한 계기나 강력한 의지의 노력이 더해지지 않는다면&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;정작 자신은 단 한번도 올리지 않은 사람들...&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;바로 그들을 심판하러 A.I는 세상의 문턱이 되었다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=DYgE3SGPEqk&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=DYgE3SGPEqk&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=DYgE3SGPEqk&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bEAbSm/dJMb8Weuf8M/7Xfg6nAKihYh1VQRmAMKXk/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=414_150_828_600,https://scrap.kakaocdn.net/dn/NDSZX/dJMb8Weuf8L/oQz1TZZ65ZZpkRWZaejkfK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=414_150_828_600&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;i-dle (아이들) &quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/DYgE3SGPEqk&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&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>shadn</category>
      <category>shadn/ui</category>
      <category>tailwindcss</category>
      <category>리액트</category>
      <author>e7e</author>
      <guid isPermaLink="true">https://e-7-e.tistory.com/328</guid>
      <comments>https://e-7-e.tistory.com/328#entry328comment</comments>
      <pubDate>Wed, 28 Jan 2026 18:51:55 +0900</pubDate>
    </item>
    <item>
      <title>vitest(테스트 프레임워크) 초우 간단 사용법</title>
      <link>https://e-7-e.tistory.com/325</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;당연할꺼당.&amp;nbsp; React에도 테스트가 존재하련당.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근에 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;vitest&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://vitest.dev/guide/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://vitest.dev/guide/&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;vite&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;&amp;nbsp;vitest와 함께 필요한 걸 설치( -D 옵션 알디용!)&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;npm i -D &lt;span style=&quot;color: #8a3db6;&quot;&gt;vitest&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;npm i -D &lt;span style=&quot;color: #8a3db6;&quot;&gt;happy-dom&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;npm i -D &lt;span style=&quot;color: #8a3db6;&quot;&gt;@testing-library/react&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;npm i -D &lt;span style=&quot;color: #8a3db6;&quot;&gt;@testing-library/jest-dom&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;happy-dom&lt;/b&gt; &lt;/span&gt;은&amp;nbsp;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt; jest-dom&lt;/b&gt;&lt;/span&gt; 보다 심플한 상황에서 쉽게 쓸 수 있는&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DOM(Document Object Model) 이당.(지나치면 본 기억이 남는당)&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;src 폴더&lt;/b&gt;에 간단히 아래와 같은 초 간단 파일을 맹글장.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[ 단순히&amp;nbsp; 2개 매개변수 받아서 합을 return 하는 함수당. 그렇당 ]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;sum.js&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768533690923&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export function sum(a, b) {
  return a + b;
}&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;위 sum.js의 sum 함수를 테스트 하는 소스(단위 테스트)를 아래 처럼 맹글장.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[ 보통 파일명.test.확장자,&amp;nbsp; 파일명.spec.확장자 식으로 맹근당. ]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;sum.test.js&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768533713507&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { describe, expect, it, test } from &quot;vitest&quot;;
import { sum } from &quot;./sum&quot;;

// 간단
test(&quot;1 + 2는 3이어야짐&quot;, () =&amp;gt; {
  expect(sum(1, 2)).toBe(3);
});

// it는 test의 alias
it(&quot;2 +2 는 4인가?&quot;, () =&amp;gt; {
  expect(sum(2, 2)).toBe(4);
});

// it는 보통 describe랑(묶음 표현) 잘 사용
describe(&quot;머라할거얌&quot;, () =&amp;gt; {
  it(&quot;2 +2 는 4인가?&quot;, () =&amp;gt; {
    expect(sum(2, 6)).toBe(8);
  });

  test(&quot;3 +3 는 6인가?&quot;, () =&amp;gt; {
    expect(sum(3, 3)).toBe(6);
  });
});&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;npx vitest&lt;/b&gt;&lt;/span&gt;라 쓰고 엔터를 치면 아래와 같은 결과를 보게 된당.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;vitest1.png&quot; data-origin-width=&quot;845&quot; data-origin-height=&quot;564&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNVnpz/dJMcaioA31L/WK9EJHjqNoKZlt1wwzCA4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNVnpz/dJMcaioA31L/WK9EJHjqNoKZlt1wwzCA4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNVnpz/dJMcaioA31L/WK9EJHjqNoKZlt1wwzCA4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNVnpz%2FdJMcaioA31L%2FWK9EJHjqNoKZlt1wwzCA4k%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;845&quot; height=&quot;564&quot; data-filename=&quot;vitest1.png&quot; data-origin-width=&quot;845&quot; data-origin-height=&quot;564&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;느끼미가 화악하고 순식간에 눈 앞 3cm&amp;nbsp; 거리에 다가서지만 의뭉도 남는당.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;describe&lt;/b&gt;&lt;/span&gt;는 그냥 &lt;b&gt;테스트를 설명하는 문자을 남기기 위해 감싸는 함수&lt;/b&gt;라고 생각하공&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;test&lt;/b&gt;&lt;/span&gt;와&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt; it&lt;/b&gt;&lt;/span&gt;는 정확히 같은 의미로&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt; it&lt;/b&gt;&lt;/span&gt;&lt;b&gt;는&lt;/b&gt; &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;test&lt;/b&gt;&lt;/span&gt;&lt;b&gt;의&lt;/b&gt; &lt;b&gt;alias당&lt;/b&gt;,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;describe&lt;/b&gt;&lt;/span&gt; 안 쓸 때는 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;test&lt;/b&gt;&lt;/span&gt;라 쓰고,&lt;span style=&quot;color: #f89009;&quot;&gt; &lt;b&gt;describe&lt;/b&gt;&lt;/span&gt; 안에 쓸 때는 &lt;b&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;it&lt;/span&gt;&lt;/b&gt;를 주로 쓰는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는 나역시 모른당. 짐작하는 바가 있지만 지금은 침묵이 가치 있을 듯.. ㅋ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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; 형식(세팅)이란 걸&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;먼저 npx vitest로 써도 되지만 npm test로 쓰겡 package.json에 아래 내용 추가하장.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;package.json&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768533858403&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;scripts&quot;: {
    &quot;test&quot;: &quot;vitest&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;vite.config.js&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768534625817&quot; class=&quot;dts&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;/// &amp;lt;reference types=&quot;vitest/config&quot; /&amp;gt;
import { defineConfig } from &quot;vite&quot;;
import react from &quot;@vitejs/plugin-react&quot;;

// https://vite.dev/config/
export default defineConfig({
  plugins: [react()],
  test: {
    environment: &quot;happy-dom&quot;,
    setupFiles: [&quot;./src/tests/setup.js&quot;],
    include: [&quot;src/**/*.{test,spec}.{js,jsx}&quot;],
  },
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;include 부분은 본인이 한번 추론해서 의미해석 하는 시간을 갖장.&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;위 파일에 정의한 setup.js를&amp;nbsp; src 아래 tests 폴더 만든 후 아래처럼 만들장.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[ 개별 테스트 실행 후 초기화를 하란 의미당. 그 의미당 ]&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;setup.js&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768534663011&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { afterEach } from &quot;vitest&quot;;
import { cleanup } from &quot;@testing-library/react&quot;;
import &quot;@testing-library/jest-dom/vitest&quot;;

afterEach(() =&amp;gt; {
  cleanup();
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 터미널에 npm test라고 입력후 엔터를 때려보면 같은 결과가 보일거시당.&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;React는 UI 라이브러리다&lt;/b&gt;. 그렇다면 UI 를 테스트 해야 한당.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DOM 동작과 상태를 테스트 해야 할꺼당. 그럴꺼당.&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;아래 처럼 App.jsx를 만들어 보장. (data-testid에 괘니 주목)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;App.jsx&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768558950410&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from &quot;react&quot;;

function App() {
  const [insa, setInsa] = useState(&quot;안농&quot;);
  const handleClick = (e) =&amp;gt; {
    setInsa(insa === &quot;안농&quot; ? &quot;나야E7E&quot; : &quot;안농&quot;);
  };

  return (
    &amp;lt;&amp;gt;
      &amp;lt;h1 data-testid=&quot;insa&quot;&amp;gt;{insa}&amp;lt;/h1&amp;gt;
      &amp;lt;button data-testid=&quot;btn&quot; onClick={handleClick}&amp;gt;
        눌러방
      &amp;lt;/button&amp;gt;
    &amp;lt;/&amp;gt;
  );
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼을 처음 누르면 h1 내용이 안농 에서 나야E7E로 바뀌어야만 한당.(기억!)&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;App.jsx의 동작을 테스트하기 위한 App.test.jsx를 아래처럼 맹글장.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;App.test.jsx&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768558970553&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { fireEvent, render, screen, waitFor } from &quot;@testing-library/react&quot;;
import { describe, it, expect } from &quot;vitest&quot;;
import App from &quot;./App&quot;;

describe(&quot;App Test&quot;, () =&amp;gt; {
  it(&quot;Basic Start&quot;, () =&amp;gt; {
    render(&amp;lt;App /&amp;gt;);
    const insa = screen.getByTestId(&quot;insa&quot;);

    expect(insa).toBeInTheDocument();
    expect(insa).toHaveTextContent(&quot;안농&quot;);
    expect(insa.tagName).toBe(&quot;H1&quot;);
  });

  it(&quot;Click Test&quot;, async () =&amp;gt; {
    render(&amp;lt;App /&amp;gt;);
    const btn = screen.getByTestId(&quot;btn&quot;);
    // fireEvent.change(input, {target: {value:&quot;만세&quot;}})
    fireEvent.click(btn);

    await waitFor(() =&amp;gt; {
      const insa = screen.getByTestId(&quot;insa&quot;);

      expect(insa).toBeInTheDocument();
      expect(insa).toHaveTextContent(&quot;나야E7E&quot;);
      expect(insa.tagName).toBe(&quot;H1&quot;);
    });
  });
});&lt;/code&gt;&lt;/pre&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;vitest2.png&quot; data-origin-width=&quot;547&quot; data-origin-height=&quot;365&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9c7tX/dJMcaiPCOAJ/43GY9KAYZjbGG2tV5eSUhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9c7tX/dJMcaiPCOAJ/43GY9KAYZjbGG2tV5eSUhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9c7tX/dJMcaiPCOAJ/43GY9KAYZjbGG2tV5eSUhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9c7tX%2FdJMcaiPCOAJ%2F43GY9KAYZjbGG2tV5eSUhK%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;547&quot; height=&quot;365&quot; data-filename=&quot;vitest2.png&quot; data-origin-width=&quot;547&quot; data-origin-height=&quot;365&quot;/&gt;&lt;/span&gt;&lt;/figure&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;screen&lt;/b&gt;&lt;/span&gt;은 화면이당. &lt;b&gt;document로 보면 조금 미심쩍고 UI 컴포넌트(DOM)로 보는 것이 타당&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;fireEvent&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;는 이벤트를 발생시킨당&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;await waitFor&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;는 이벤트 핸들러가 끝나기를 기다렸당.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;expect&lt;/b&gt; &lt;/span&gt;사용법은 이미 추론해서 파악했으리라 믿는당. 이걸로 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;pass/fail이 결정&lt;/b&gt;&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;터미널에 q를 입력해서&amp;nbsp; test watch 모드를&amp;nbsp; 종료하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 npm start 입력 후 엔터를 친다면, 아래 처럼&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 sum 테스트와 App 테스트가 합쳐진 결과를 얻게 될 꺼이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;vitest3.png&quot; data-origin-width=&quot;643&quot; data-origin-height=&quot;465&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dltqCR/dJMcaac2F3L/8iKXmTupZHUqoTbV5tfXXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dltqCR/dJMcaac2F3L/8iKXmTupZHUqoTbV5tfXXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dltqCR/dJMcaac2F3L/8iKXmTupZHUqoTbV5tfXXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdltqCR%2FdJMcaac2F3L%2F8iKXmTupZHUqoTbV5tfXXk%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;643&quot; height=&quot;465&quot; data-filename=&quot;vitest3.png&quot; data-origin-width=&quot;643&quot; data-origin-height=&quot;465&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일부러 fail이 나오는 상황을 만들어 보는 것도 분명 도우미 될꺼이당.&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;이거 언제 써용???? ~~&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;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약에 말야 당신이 감수성 눈물 넘치는 개발자라면 말야...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;if문을 보고는 머리에.., 만약에 말야, 응 있지 만약에 말야...&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;else if문을 보고는 머리에 또.., 만약에 말야, 이것도... 만약인데 말야...&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;else 문을 보고는 머리에 또, 미안한데 저기 마지막으로 만약인데 말야....&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; if문이 만약 틀려서&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;p 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;또 만약 이런 사람들이 만약 엄청나게 많다면.....&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&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;a href=&quot;https://www.youtube.com/watch?v=6FiUjgp1LfY&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=6FiUjgp1LfY&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=6FiUjgp1LfY&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/QFwg7/dJMb9lL6jei/8sglHs4l5sSiQKjYW97kcK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=348_304_598_400,https://scrap.kakaocdn.net/dn/tPDte/dJMb9kl7xZQ/x1LIjpK3m6tksoloKzkEu1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=348_304_598_400&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;[만약에 우리] 사랑은 봄비처럼... 이별은 겨울비처럼&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/6FiUjgp1LfY&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>React</category>
      <category>happy-dom</category>
      <category>jest-dom</category>
      <category>React</category>
      <category>test</category>
      <category>vitest</category>
      <category>리액트</category>
      <category>테스트</category>
      <author>e7e</author>
      <guid isPermaLink="true">https://e-7-e.tistory.com/325</guid>
      <comments>https://e-7-e.tistory.com/325#entry325comment</comments>
      <pubDate>Tue, 27 Jan 2026 21:43:00 +0900</pubDate>
    </item>
    <item>
      <title>React(리액트) 티키타카 39 커스텀 훅(Custom Hook)</title>
      <link>https://e-7-e.tistory.com/327</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;참으로 끔찍하겡 달가운 이야기당.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;커스텀 훅&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;그렇당 잘 써야 할 꺼이당.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;컴포넌트 내부 소스를 간단히 하기 위해서도, 더불어 가독성을 올려드리고,&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;재 사용 가능하게 맹글어서, 이 컴포넌트 저 컴포넌트에서도 널리 재활용&lt;/b&gt;&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;&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;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;Custom Hook을 만드는 건 너무나 간단하지만&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;좋은 재사용 유용한 Custom Hook을 만드는 일은 그리&amp;nbsp;간단하지는 않당.&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;개인적인 추천은 컴포넌트를 만들다 보면, 컴포넌트 내부(특히 JSX 리턴 앞부분)의&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;별도 파일로 빼내보면 은근 슬쩍 반복 되는 게 보일 수 있는데, 바로 이때~~&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;SPA(Single Page Application)의 경우, 비동기 AJAX의 사용이 대표적으로&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vite를 이용하여 React 프로젝트를 만드는 건 그냥 알아서 맡기겠당.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[ 참고로 난 재미를 위해 tailwindcss도 설치하고야 말았당. ]&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;그럼 목적지를 ajax(여기선 fetch)를&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;Idol.jsx&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768809128404&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const imgServer = &quot;https://newsimg.sedaily.com&quot;;

function Idol({ name, role, imgURL }) {
  return (
    &amp;lt;div className=&quot;w-full text-center border-8 border-pink-500 rounded-2xl&quot;&amp;gt;
      &amp;lt;h1 className=&quot;text-4xl mt-2 mb-2 font-extrabold&quot;&amp;gt;{name}&amp;lt;/h1&amp;gt;
      &amp;lt;h2 className=&quot;text-2xl mb-2 text-blue-600 font-bold bg-gray-300&quot;&amp;gt;
        {role}
      &amp;lt;/h2&amp;gt;
      &amp;lt;img
        src={`${imgServer}${imgURL}`}
        className=&quot;w-full h-120 rounded-[50%]&quot;
      /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default Idol;&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;IdolList.jsx&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768809157004&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useEffect, useState } from &quot;react&quot;;
import Idol from &quot;./Idol&quot;;

const e7eURL = &quot;https://my-json-server.typicode.com/jangmk/kpop/agents&quot;;

function IdolList() {
  const [idols, setIdols] = useState([]);

  useEffect(() =&amp;gt; {
    let totalIdols = [];
    const getIdols = async () =&amp;gt; {
      const response = await fetch(e7eURL);
      const data = await response.json();

      // 데이터 축출
      data.forEach((ent) =&amp;gt; {
        ent.groups.forEach((group) =&amp;gt; {
          totalIdols = [...totalIdols, ...group.members];
        });
      });

      console.log(&quot;체에킁: &quot;, totalIdols);
      setIdols(totalIdols);
    };
    getIdols();
  }, []);

  return (
    &amp;lt;div className=&quot;w-[80%] pt-40  mx-auto grid grid-cols-4 gap-4 &quot;&amp;gt;
      {idols.map((idol) =&amp;gt; (
        &amp;lt;Idol key={idol.name} {...idol} /&amp;gt;
      ))}
    &amp;lt;/div&amp;gt;
  );
}

export default IdolList;&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;App.jsx&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768809186612&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import IdolList from &quot;./IdolList&quot;;

function App() {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;h1 className=&quot;fixed w-full text-center text-8xl bg-black text-white p-5 mb-3 &quot;&amp;gt;
        지원 더 없나용
      &amp;lt;/h1&amp;gt;
      &amp;lt;IdolList /&amp;gt;
    &amp;lt;/&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;custom1.png&quot; data-origin-width=&quot;1523&quot; data-origin-height=&quot;487&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cx0i0x/dJMcaaximuZ/IQFgs33ehjmKe4dJBE63FK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cx0i0x/dJMcaaximuZ/IQFgs33ehjmKe4dJBE63FK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cx0i0x/dJMcaaximuZ/IQFgs33ehjmKe4dJBE63FK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcx0i0x%2FdJMcaaximuZ%2FIQFgs33ehjmKe4dJBE63FK%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;1523&quot; height=&quot;487&quot; data-filename=&quot;custom1.png&quot; data-origin-width=&quot;1523&quot; data-origin-height=&quot;487&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IdolList.jsx에서 useEffect Hook에 사용한 fetch당.&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;비동기 AJAX를 가마니 쓰고 앉아서&amp;nbsp; 쪼금 가마니 생각해 보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과가 나온 성공 케이스 (이때 결과를 data라 하장)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러가 발생한 실패 케이스 (이때 결과를 error라 하장)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 아직 성공/실패를 알수없는 진행 케이스(이때를 pending이라 하장)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 3가지를 기본적으로 담아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 담아야 한다는 말은 해당 코드가 반복될거예요와 같은 의미당.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바뀌는 건 url 일 꺼시당.&amp;nbsp; (메소드 부분은 일단 머리에서 제외, 상황 간단히)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[ 사실 Promise는 이 3가지 상태를 기준으로 만들어졌당 ]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 맹글어서 적용해 보장.~~&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;useFetcher&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768814419075&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useEffect, useState } from &quot;react&quot;;

// 대표적 커스텀 훅(AJAX)
function useFetcher(url) {
  // 3가지 상태를 위한 상태변수, Re-Redering을 발생시키기 위함
  const [data, setData] = useState(null); // 결과가 있는지 없는지
  const [pending, setPending] = useState(true); // 실행중인지 끝났는지
  const [error, setError] = useState(null); // 에러가 있는지 없는지

  useEffect(() =&amp;gt; {
    const fetchData = async () =&amp;gt; {
      try {
        setPending(true); // 아작스 들어가기 전에 실행중 표시, Re-Rendering
        const response = await fetch(url);
        if (!response.ok)
          throw new Error(`Http Error! Status: ${response.status}`); //  강제 Error발생 -&amp;gt; catch로 이동
        const result = await response.json();
        setData(result);
      } catch (error) {
        setError(error.message); // Re-Rendering
      } finally {
        setPending(false); // 그냥 초기화 그래도 Re-Rendering 발생
      }
    };

    fetchData(); // 호출

    // 클리어 함수 (unmount 되었을 때 실행)
    return () =&amp;gt; {
      // 혹 필요한 작업이 있다면
    };
  }, [url]); // url이 바뀌면 실행됨

  return { data, pending, error }; // 이 훅을 가져다 쓸 때, 꺼내 쓸 수 있도록
}

export default useFetcher;&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;span style=&quot;color: #f89009;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;바뀐&lt;/span&gt;&lt;b&gt; IdolList.jsx&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768814925747&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useEffect, useState } from &quot;react&quot;;
import Idol from &quot;./Idol&quot;;
import useFetcher from &quot;./useFetcher&quot;;

const e7eURL = &quot;https://my-json-server.typicode.com/jangmk/kpop/agents&quot;;

function IdolList() {
  const { data, pending, error } = useFetcher(e7eURL);

  if (pending) return &amp;lt;h1 className=&quot;text-4xl&quot;&amp;gt;땀시만 기다려주삼&amp;lt;/h1&amp;gt;;
  if (error) return &amp;lt;h1&amp;gt;{error}&amp;lt;/h1&amp;gt;;

  let totalIdols = [];
  data.forEach((ent) =&amp;gt; {
    ent.groups.forEach((group) =&amp;gt; {
      totalIdols = [...totalIdols, ...group.members];
    });
  });

  return (
    &amp;lt;div className=&quot;w-[80%] pt-40  mx-auto grid grid-cols-4 gap-4 &quot;&amp;gt;
      {totalIdols.map((idol) =&amp;gt; (
        &amp;lt;Idol key={idol.name} {...idol} /&amp;gt;
      ))}
    &amp;lt;/div&amp;gt;
  );
}

export default IdolList;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는 분명 같지 않지 아니하지 않을 수 없지 않음을 본능이 느낀당..&lt;/p&gt;
&lt;p data-ke-size=&quot;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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;react-query&lt;/b&gt;&lt;/span&gt;란 걸 만들었당.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연히 그러할 거시당. React에도 역사란 게 있으닝..&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 훌륭한 React-Query의 사용법이당.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[분명 맘에 들꺼이당. useEffect가 눈에 보이지 않으닝 좋을 수 밖에~~ (&amp;gt;&amp;lt;)]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://e-7-e.tistory.com/307&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2025.04.08 - [React] - React(리액트) 티키타카 34 (@tanstack/react-query)&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;분명 천천히 꾸준히 React를 사용한다면 당신도 이런 걸 만들 생각에&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&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;인공지능이란 이름으로 더 많은 정보를 바탕으로&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;p data-ke-size=&quot;size16&quot;&gt;그 누군가는 직업상 평가 문제를 내어야 하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A.I를 이용하여 문제를 만들었당.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저 누군가는 상황상 평가를 받아야 하는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI에게 문제의 답을 물어 제출하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 누군가는 저 누군가에 대한 채점을&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A.I에게 맡겼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;점수가 맘에 안 들었던 저 누군가는 항의 서신을&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A.I에게 작성하게 하였고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 누군가는 그 회신을 다시 A.I에게 맡겼다.&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;그저 평균 점수가 높았졌네 감탄과&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 100점이 아니야 하는 한탄이&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;바라보는 3D 위치에 따라서 매우 긍정적으로도&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;&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;50% 수준에서 정확합니다.&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;95% 수준에서 정확합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;이거다 싶지만 틀릴까봐 5% 변명 남겨둡니다. ~~&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;99.9999% 수준에서 정확합니다.&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;100% 그건 나의 바람이었나?&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://www.youtube.com/watch?v=gpdIcAOYdxY&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=gpdIcAOYdxY&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=gpdIcAOYdxY&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/kByjv/dJMb8863uUX/JDiCLYD0WdqDG5KxPdjZD0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=234_62_1012_392,https://scrap.kakaocdn.net/dn/bPo1uD/dJMb8YXF2qV/rimQkKcL5ck5Q1Qo4fIK4k/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=234_62_1012_392&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;정수라(ジョンスラ)X전유진(チョンユジン) - 바람이었나(&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/gpdIcAOYdxY&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&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;/p&gt;</description>
      <category>React</category>
      <category>Custom Hook</category>
      <category>React</category>
      <category>tailwindcss</category>
      <category>리액트</category>
      <category>커스텀 훅</category>
      <author>e7e</author>
      <guid isPermaLink="true">https://e-7-e.tistory.com/327</guid>
      <comments>https://e-7-e.tistory.com/327#entry327comment</comments>
      <pubDate>Mon, 19 Jan 2026 15:06:53 +0900</pubDate>
    </item>
    <item>
      <title>Spring Boot Swagger3 후다닥 쾌속 느끼랑</title>
      <link>https://e-7-e.tistory.com/324</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;Restful, Rest Api&lt;/b&gt;&lt;/span&gt; 하면 따라다니는 용어가 있는데,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;OAS&lt;/b&gt;&lt;/span&gt;&amp;nbsp; &amp;nbsp; =&amp;gt;&amp;nbsp; &lt;b&gt;O&lt;/b&gt;pen &lt;b&gt;A&lt;/b&gt;PI &lt;b&gt;S&lt;/b&gt;pecfication&amp;nbsp;&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;gt;&amp;nbsp; 그저 Rest 관련 표준을 정하고 있구나 생각하면 된당.&amp;nbsp;&amp;nbsp;&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;gt; 요것땜시 open-api 란 글자들도 자주 누네띤당.&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: #f89009;&quot;&gt;&lt;b&gt;HATEOAS&lt;/b&gt;&lt;/span&gt;&amp;nbsp; =&amp;gt; &lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;b&gt;H&lt;/b&gt;ypermedia &lt;b&gt;A&lt;/b&gt;s &lt;b&gt;T&lt;/b&gt;he &lt;b&gt;E&lt;/b&gt;ngine &lt;b&gt;O&lt;/b&gt;f &lt;b&gt;A&lt;/b&gt;pplication State&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;=&amp;gt;&amp;nbsp; &amp;nbsp;자원(Resource)에 접근하면, 해당 자원 관련 처리를&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&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; 할 수 있는 추가 API 링크를 받을 수 있는 형식!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&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;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&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; get으로&amp;nbsp; /api/idol/3&amp;nbsp; &amp;nbsp;아이디 3번인 idol 정보를 요청했는데&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&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; idol3번을 지우는 link와 수정할 수 있는&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&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;link 정보도 같이 오는 것이당. (와아 넘 쉽당!~~ ㅋㅋ)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1b711d;&quot;&gt;&lt;b&gt;상식이닝 그냥 머리에 담아서 누가 얘기하면 아는 척 있어보이장!&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp;&lt;b&gt;Restful&lt;/b&gt;로 만들면 &lt;b&gt;그 사용법을 시각적으로., 테스트도 거저 쉽게&lt;/b&gt; 하고프당&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;b&gt;이럴 때&lt;/b&gt; 사용하는 것이 &lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;Swagger&lt;/b&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: #ffffff; color: #333333; text-align: start;&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;먼저 스프링 부트 프로젝트를 1개 아래처럼 새로 맹글장.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;STS5(난 최신을 마구 좋아함)에서 Spring Starter Project&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;( Gradle 안 써본 그 분들을 위해 Maven아니공 &lt;b&gt;괘닝 Gradle롱&lt;/b&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;swag-proj.png&quot; data-origin-width=&quot;546&quot; data-origin-height=&quot;339&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgnH32/dJMcaacPx1A/6nBkHjKHKuPpj6hm7P55FK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgnH32/dJMcaacPx1A/6nBkHjKHKuPpj6hm7P55FK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgnH32/dJMcaacPx1A/6nBkHjKHKuPpj6hm7P55FK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgnH32%2FdJMcaacPx1A%2F6nBkHjKHKuPpj6hm7P55FK%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;546&quot; height=&quot;339&quot; data-filename=&quot;swag-proj.png&quot; data-origin-width=&quot;546&quot; data-origin-height=&quot;339&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;Dependencies 선택은 Spring Web, Lombok&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 그냥 매번 넣는 Spring Boot DevTools 와 Spring Boot Actuator을 하고서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;Swagger를 쓰기 위해&amp;nbsp; 최신&amp;nbsp; springdoc-openapi-starter-webmvc-ui:3.0.0&lt;/b&gt;&lt;/span&gt; 를&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;build.gradle 파일에 직접 넣으면 아래와 같을지어당.&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;build.gradle&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1766580235368&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;plugins {
	id 'java'
	id 'org.springframework.boot' version '4.0.1'
	id 'io.spring.dependency-management' version '1.1.7'
}

group = 'com.e7e'
version = '0.0.1-SNAPSHOT'
description = 'Demo project for Spring Boot'

java {
	toolchain {
		languageVersion = JavaLanguageVersion.of(21)
	}
}

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-actuator'
	implementation 'org.springframework.boot:spring-boot-starter-webmvc'
	implementation(&quot;org.springdoc:springdoc-openapi-starter-webmvc-ui:3.0.0&quot;)
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-actuator-test'
	testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}


tasks.named('test') {
	useJUnitPlatform()
}&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;build.gradle 파일을 수정하면 마우스 오른쪽 눌렁, 아래와 같은 Refresh를 잊지 말장.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;buildGradle.png&quot; data-origin-width=&quot;524&quot; data-origin-height=&quot;157&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dG3dw5/dJMcabCNSP8/09btOulwNlGMVpqWFRBx80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dG3dw5/dJMcabCNSP8/09btOulwNlGMVpqWFRBx80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dG3dw5/dJMcabCNSP8/09btOulwNlGMVpqWFRBx80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdG3dw5%2FdJMcabCNSP8%2F09btOulwNlGMVpqWFRBx80%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;524&quot; height=&quot;157&quot; data-filename=&quot;buildGradle.png&quot; data-origin-width=&quot;524&quot; data-origin-height=&quot;157&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: #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;참~ 미리 1개만 준비하장.&amp;nbsp; 시작페이지가 없어서 안된다고 오해할 그들을 위한 준비당&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;src/main/resources 아래 static 폴더에 index.html을 아래처럼 미리 맹글장.&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: #f89009;&quot;&gt;&lt;b&gt;index.html&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1766638297573&quot; class=&quot;xml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
&amp;lt;title&amp;gt;Insert title here&amp;lt;/title&amp;gt;
&amp;lt;style&amp;gt;
#wrapper {
    margin: 20px auto;
    padding: 10px;
	width:60%;
    border: 10px groove gold;
    text-align:center;
}

a {
	font-size:2em;
	display: block;
	width:100%;
}
&amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;div id=&quot;wrapper&quot; &amp;gt;
&amp;lt;h1&amp;gt;E7E 노파심에 여기에 링크를....&amp;lt;/h1&amp;gt;
&amp;lt;a href=&quot;/swagger-ui/index.html&quot;&amp;gt;Swagger-UI 이동&amp;lt;/a&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&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;application.properties 파일을 아래 처럼 맹글장.&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;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&lt;b&gt;springdoc.swagger-ui.enabled=&lt;/b&gt;&lt;span style=&quot;color: #2aa198;&quot;&gt;&lt;b&gt;true&lt;/b&gt;&amp;nbsp; &amp;nbsp;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;요줄이 있어야 화면에 swagger가 나온당.&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;application.properties &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1766580061856&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring.application.name=swagger3
logging.level.com.e7e.swagger3=debug
# 아래 2개는 그냥 내가 보고 싶어서 넣음
logging.level.org.springframework.web=debug
logging.level.org.springframework.webmvc=debug

# Swagger Spring UI 세팅
springdoc.packages-to-scan=com.e7e.swagger3		
springdoc.default-consumes-media-type=application/json
springdoc.default-produces-media-type=application/json
springdoc.cache.disabled=true
# 아래 default값은 enabled
# springdoc.api-docs.enabled=false
# 아래 default값은 /v3/api-docs/
# springdoc.api-docs.path=/api-docs/json
springdoc.api-docs.groups.enabled=true
springdoc.swagger-ui.enabled=true
#defaut /swagger-ui/index.html
#springdoc.swagger-ui.path=/e7e-ui.html
springdoc.swagger-ui.tags-sorter=alpha
# alpha, default, method
springdoc.swagger-ui.operations-sorter=method&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용하려면 아래와 같은 설정이 그냥 필요하당. 그렇당.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;눈이 흐르면 느끼미도 따라 흐른당.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;SwaggerConfig.java&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1766580025600&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.e7e.swagger3.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;

@Configuration
public class SwaggerConfig {

    @Bean
    OpenAPI openAPI() {
        Info info = new Info()
                .title(&quot;Swagger로 해보는API Document 연습&quot;)
                .version(&quot;v1.0.0&quot;)
                .description(&quot;Swagger 연습용 API 명세&quot;);
        // OpenAPI 생성
        return new OpenAPI()
                .components(new Components())
                .info(info);
    }
}&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;스키마로 쓸 VO 1개 만들장. (&lt;b&gt;나중에 결과 실행화면과 매칭시켜서 봐야한당&lt;/b&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;BdVO.java&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1766580127368&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.e7e.swagger3.vo;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class BdVO {

    @Schema(description = &quot;bd 고유키&quot;, example = &quot;272&quot;)
    private int seq;

    @Schema( description = &quot;bd 제목&quot;, example = &quot;제목 쓰삼&quot;)
    private String bdTitle;

    @Schema( description = &quot;bd 내용&quot;, example = &quot;Swagger 재밌넹&quot;)
    private String bdContent;

    @Schema( description = &quot;bd 작성자명&quot;, example = &quot;경미니&quot;)
    private String userName;

}&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;초간단 Restful 컨트롤러&lt;/b&gt; 1개 만들장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(get 방식 조회 2개, post , put, delete 각 1개)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;BdController.java&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1766580162584&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.e7e.swagger3.controller;

import java.util.ArrayList;
import java.util.List;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.e7e.swagger3.vo.BdVO;

import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Tag(name = &quot;boards&quot;, description = &quot;Board CRUD API EndPoint&quot;)
@RestController
public class BdController {

	// DB 대신 그냥 static 으로 연습
	private static List&amp;lt;BdVO&amp;gt; bdLists = new ArrayList&amp;lt;&amp;gt;();

	// 생성 후 초기화
	@PostConstruct
	void init() {
		log.debug(&quot;그냥 실행 여부 눈으로 확인 - 디버깅용&quot;);

		List&amp;lt;String&amp;gt; names = List.of(&quot;경미닝&quot;, 
				                     &quot;수미닝&quot;, 
				                     &quot;지워닝&quot;, 
				                     &quot;선주닝&quot;, 
				                     &quot;미서닝&quot;);
		List&amp;lt;String&amp;gt; titles = List.of(&quot;넘 자랑하고팡&quot;, 
				                      &quot;난 이미 아이돌&quot;, 
				                      &quot;Support 매니아&quot;, 
				                      &quot;당당한 갑옷이요?&quot;, 
				                      &quot;웃음 에너지&quot;);
		List&amp;lt;String&amp;gt; conts = List.of(&quot;최고 인사고과 평점 S용&quot;, 
				                     &quot;왜 절 캐스팅하지 않아용?&quot;, 
				                     &quot;이름 자체가 Support&quot;, 
				                     &quot;건강하면 그렇게 예뻐보여용&quot;, 
				                     &quot;인생은 웃어야 맛이죵 그죵?&quot;);

		BdVO bdVO = null;
				
		for (int i = 0; i &amp;lt; names.size(); i++) {
			bdVO = new BdVO();
			bdVO.setSeq(i);
			bdVO.setUserName(names.get(i));
			bdVO.setBdTitle(titles.get(i));
			bdVO.setBdContent(conts.get(i));
			bdLists.add(bdVO);
		}
	}

	/* Restful API GET 메서드 */
	@GetMapping(&quot;/boards&quot;)
	@Operation(summary = &quot;전체 조회&quot;, description = &quot;board 전체 조회&quot;)
	@ApiResponse(responseCode = &quot;200&quot;, description = &quot;성공&quot;, content = @Content(schema = @Schema(implementation = BdVO.class)))
	public ResponseEntity&amp;lt;List&amp;lt;BdVO&amp;gt;&amp;gt; boardList() {
		return ResponseEntity.ok(bdLists);
	}

	/* Restful API GET 메서드 */
	@GetMapping(&quot;/boards/{seq}&quot;)
//감추고 싶다면	@Hidden   
	@Operation(summary = &quot;seq로 조회&quot;, description = &quot;board by seq&quot;)
	@ApiResponse(responseCode = &quot;200&quot;, description = &quot;성공&quot;, content = @Content(schema = @Schema(implementation = BdVO.class)))
	public ResponseEntity&amp;lt;BdVO&amp;gt; boardByUserId(
			@Parameter(name = &quot;seq&quot;, description = &quot;seq지용&quot;) @PathVariable(value = &quot;seq&quot;) int seq) {
		log.debug(&quot;체킁: {}&quot;, seq);
		List&amp;lt;BdVO&amp;gt; schList = bdLists.stream().filter(bd -&amp;gt; bd.getSeq() == seq).toList();
		BdVO bdVO = schList.size() &amp;gt; 0 ? schList.get(0) : null;

		return ResponseEntity.ok(bdVO);
	}

	/* Restful API POST 메서드 */
	@PostMapping(&quot;/boards&quot;)
	@Operation(summary = &quot;게시글 등록&quot;, description = &quot;게시글을 등록합니다.&quot;)
	@ApiResponse(responseCode = &quot;200&quot;, description = &quot;등록 성공&quot;)
	public ResponseEntity&amp;lt;BdVO&amp;gt; createBoard(@RequestBody @Schema(implementation = BdVO.class) BdVO bdVO) {
		int maxSeq = 0;

		for (BdVO bdVO2 : bdLists) {
			if (bdVO2.getSeq() &amp;gt; maxSeq) {
				maxSeq = bdVO2.getSeq();
			}
		}
		bdVO.setSeq(maxSeq + 1);
		bdLists.add(bdVO);

		return ResponseEntity.ok(bdVO);
	}

	/* Restful API PUT 메서드 */
	@PutMapping(&quot;/boards/{seq}&quot;)
	@Operation(summary = &quot;Board Update&quot;, description = &quot;Board Update By seq&quot;, responses = {
			@ApiResponse(responseCode = &quot;200&quot;, description = &quot;떵공&quot;),
			@ApiResponse(responseCode = &quot;400&quot;, description = &quot;잘못된 요청&quot;),
			@ApiResponse(responseCode = &quot;404&quot;, description = &quot;못찾겠다&quot;),
			@ApiResponse(responseCode = &quot;500&quot;, description = &quot;서버가 미안&quot;) }, parameters = {
					@Parameter(name = &quot;seq&quot;, description = &quot;Board seq&quot;, required = true, example = &quot;1&quot;) })
	public ResponseEntity&amp;lt;BdVO&amp;gt; updateBoard(@PathVariable(value = &quot;seq&quot;) int seq,
			@RequestBody @Schema(implementation = BdVO.class) BdVO bdVO) {

		for (BdVO bdVO1 : bdLists) {
			if (bdVO1.getSeq() == seq) {
				bdVO1.setBdContent(bdVO.getBdContent());
				bdVO1.setBdTitle(bdVO.getBdTitle());
				bdVO1.setUserName(bdVO.getUserName());
			}
		}

		bdVO.setSeq(seq);
		return ResponseEntity.ok(bdVO);
	}

	/* Restful API DELETE 메서드 */
	@DeleteMapping(&quot;/boards/{seq}&quot;)
	@Operation(summary = &quot;Board Delete&quot;, description = &quot;Delete Board By seq&quot;)
	public ResponseEntity&amp;lt;BdVO&amp;gt; deleteBoard(@Parameter(name = &quot;seq&quot;) @PathVariable(value = &quot;seq&quot;) int seq) {

		log.debug(&quot;delete {}&quot;, seq);
		List&amp;lt;BdVO&amp;gt; schList = bdLists.stream().filter(bd -&amp;gt; bd.getSeq() == seq).toList();

		// iterator 대신에
		bdLists.removeIf(bd -&amp;gt; bd.getSeq() == seq);

		BdVO bdVO = schList.size() &amp;gt; 0 ? schList.get(0) : null;

		return ResponseEntity.ok(bdVO);

	}
}&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;http://localhost:8080/swagger-ui/index.html&lt;/b&gt;&lt;/span&gt;&amp;nbsp; 를 확인해 보면 당근 아래와 같당.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각의 메소드를 눌러보면 Try it out ㅓ버튼이 있으니, 눌러서 실제 실행도 해보장.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;swagger3.png&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;961&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biy7kC/dJMcajt1kT6/RaKWU5A6mvK9UBVJkPJOD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biy7kC/dJMcajt1kT6/RaKWU5A6mvK9UBVJkPJOD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biy7kC/dJMcajt1kT6/RaKWU5A6mvK9UBVJkPJOD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbiy7kC%2FdJMcajt1kT6%2FRaKWU5A6mvK9UBVJkPJOD1%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;922&quot; height=&quot;961&quot; data-filename=&quot;swagger3.png&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;961&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;swagget.png&quot; data-origin-width=&quot;1099&quot; data-origin-height=&quot;930&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cskNzb/dJMcafFdpDp/kmhVWQnt6KXk4HPGtCl9gK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cskNzb/dJMcafFdpDp/kmhVWQnt6KXk4HPGtCl9gK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cskNzb/dJMcafFdpDp/kmhVWQnt6KXk4HPGtCl9gK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcskNzb%2FdJMcafFdpDp%2FkmhVWQnt6KXk4HPGtCl9gK%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;1099&quot; height=&quot;930&quot; data-filename=&quot;swagget.png&quot; data-origin-width=&quot;1099&quot; data-origin-height=&quot;930&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;실행 결과와 컨트롤러/VO 소스를 맵핑시켜 보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;@Tag&lt;/b&gt;&lt;/span&gt; / &lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&lt;span style=&quot;color: #646464;&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;@Operation&lt;/b&gt;&lt;/span&gt; / &lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;@&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #0066cc;&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;ApiResponse&lt;/b&gt;&lt;/span&gt; / &lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;@&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #0066cc;&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;Hidden&lt;/b&gt;&lt;/span&gt; / &lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&lt;span style=&quot;color: #646464;&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;@Content&lt;/b&gt;&lt;/span&gt; / &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;@Parameter&lt;/b&gt;&lt;/span&gt; / &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;@Schema&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&lt;span style=&quot;color: #646464;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&lt;span style=&quot;color: #0066cc;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&lt;span style=&quot;color: #0066cc;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&lt;span style=&quot;color: #646464;&quot;&gt;어노테이션들의 의미가 느끼미와 붙어서 순간 뇌에 아하! 하공~ 박히공 만당.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&lt;span style=&quot;color: #646464;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&lt;span style=&quot;color: #0066cc;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&lt;span style=&quot;color: #0066cc;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&lt;span style=&quot;color: #646464;&quot;&gt;억지로 외우지 말고, 그저 필요한 부분만 복사/붙여넣기 식으로 사용하면 &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&lt;span style=&quot;color: #646464;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&lt;span style=&quot;color: #0066cc;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&lt;span style=&quot;color: #0066cc;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&lt;span style=&quot;color: #646464;&quot;&gt;저절로 급하지 않게 충분한 속도로 익숙해 질 것이당. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt; http://localhost:8080/v3/api-docs&amp;nbsp; &lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&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;p&gt;&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/WtXAO/dJMcacBISTD/HIZmNuMZDKggIFskeJkKzK/swagger3.zip?attach=1&amp;amp;knm=tfile.zip&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;swagger3.zip&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.07MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;STS에서 이상한 현상을 1개 발견&lt;/b&gt;하였다.~ 눈치 못챌뻔~~&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;webmvc-ui&amp;nbsp; 라이브러리를 넣고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gradle에서 Refresh Gradle Project를 하면&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;resources 아래 &lt;b&gt;templates 폴더가 패키지 형태로 바뀌어&lt;/b&gt; 버린당.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 폴더형태로 바꾸면 index.html이 실행이 안된당.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼가 있구낭 하고, 스프링 web과 webmvc 패키지에 debug 로깅레벨을&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;&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;아래는 난 Swagger에 중독되었어 생각이 든다면 가보길 추천한당.(그닥 재미는 없을거당)&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;Swagger UI&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;a href=&quot;https://swagger.io/swagger-ui/)&quot;&gt;https://swagger.io/swagger-ui/)&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;b&gt;Swagger Editor&lt;/b&gt; (&lt;a href=&quot;https://editor.swagger.io/)&quot;&gt;https://editor.swagger.io/)&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;b&gt;Swagger Codegen&lt;/b&gt; (&lt;a href=&quot;https://github.com/swagger-api/swagger-codegen/tree/3.0.0&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/swagger-api/swagger-codegen/tree/3.0.0&lt;/a&gt;&lt;a href=&quot;https://github.com/swagger-api/swagger-codegen)&quot;&gt;)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의및 관심&amp;nbsp; 2025-12-25 기준 issues 가 3.1K나 됨.. 기다릴 줄도 알아야...&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;style3&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;친구집에 갔다가&amp;nbsp; 반갑게 &quot;하이&quot; 하는 그 아들에게 손가락 총으로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;빵!&quot; 하니 물끄러미 쳐다봐서, 멋쩍은 나머지 다시 &quot;빵!&quot; 했다.&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;/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;&quot;아더씨 호빵!&quot; ~~~&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;분명 아주 오래전 옛날엔 총에 빵을 넣어 쐈을거당.&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;&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://www.youtube.com/watch?v=m6ftHZi9qTI&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=m6ftHZi9qTI&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=m6ftHZi9qTI&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bW77Fd/hyZQFtRrwB/56sdLarLMUfQAqkzuAqYz1/img.jpg?width=480&amp;amp;height=360&amp;amp;face=64_123_422_209,https://scrap.kakaocdn.net/dn/bJQIp5/hyZPKv2HnM/Pzs7LkWvfFS28wyJKMmV20/img.jpg?width=480&amp;amp;height=360&amp;amp;face=64_123_422_209&quot; data-video-width=&quot;480&quot; data-video-height=&quot;360&quot; data-video-origin-width=&quot;480&quot; data-video-origin-height=&quot;360&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;[M/V] NORAZO(노라조) - Bread(빵)&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/m6ftHZi9qTI&quot; width=&quot;480&quot; height=&quot;360&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;</description>
      <category>스프링부트</category>
      <category>Example</category>
      <category>simple</category>
      <category>spring boot</category>
      <category>Swagger</category>
      <category>간단</category>
      <category>스와거</category>
      <category>스프링부트</category>
      <category>예제</category>
      <author>e7e</author>
      <guid isPermaLink="true">https://e-7-e.tistory.com/324</guid>
      <comments>https://e-7-e.tistory.com/324#entry324comment</comments>
      <pubDate>Thu, 25 Dec 2025 15:01:49 +0900</pubDate>
    </item>
    <item>
      <title>Annotation(어노테이션)을 이용한 AOP</title>
      <link>https://e-7-e.tistory.com/323</link>
      <description>&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;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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;AOP (Aspect Of Point)&amp;nbsp; 참 좋은 기능&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;b&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;성능 평가&lt;/b&gt;를 위해 &lt;b&gt;특정 컨트롤러 메소드들의 실행 시간 측정&lt;/b&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;실행 시간 측정이 필요한 메소드들에 &lt;span style=&quot;color: #8a3db6;&quot;&gt;@TimeCheck&lt;/span&gt; 어노테이션을 붙여서&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;해당 어노테이션이 붙은&amp;nbsp; 메소드들에만 적용되는 Aspect를&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;만들어 log 출력&lt;/b&gt;&lt;/span&gt;을 하면 될꺼이당.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[ &lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;일일이 메소드마다 필요한 코드를 붙이는 바보같은 행동을 막기 위해 AOP는 탄생했다&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;aop.png&quot; data-origin-width=&quot;424&quot; data-origin-height=&quot;170&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/be7Cp8/dJMcafkR0VC/BvFL8kvnvJYPr1EnPbtwLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/be7Cp8/dJMcafkR0VC/BvFL8kvnvJYPr1EnPbtwLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/be7Cp8/dJMcafkR0VC/BvFL8kvnvJYPr1EnPbtwLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbe7Cp8%2FdJMcafkR0VC%2FBvFL8kvnvJYPr1EnPbtwLK%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;424&quot; height=&quot;170&quot; data-filename=&quot;aop.png&quot; data-origin-width=&quot;424&quot; data-origin-height=&quot;170&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 style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;pom.xml&lt;/b&gt;&lt;/span&gt;에&amp;nbsp; &lt;b&gt;aop&lt;/b&gt; 추가하장&lt;/p&gt;
&lt;pre id=&quot;code_1766383459913&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;		&amp;lt;dependency&amp;gt;
			&amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
			&amp;lt;artifactId&amp;gt;spring-boot-starter-aop&amp;lt;/artifactId&amp;gt;
			&amp;lt;version&amp;gt;4.0.0-M2&amp;lt;/version&amp;gt;
		&amp;lt;/dependency&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;method가 적용 타겟&lt;/b&gt;이당)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;TimeCheck.java&amp;nbsp; &lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;(package는 &lt;span style=&quot;color: #8a3db6;&quot;&gt;com.e7e.annotaion&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1766129617995&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TimeCheck {}&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;위 @TimeCheck 어노테이션이 붙은 메소드에 동작할 Aspect를 만들장.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;TimeCheckAspect.java &lt;/b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;(package는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;com.e7e.aspect&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1766130249923&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Slf4j
@Aspect
@Component
public class TimeCheckAspect {

	// 쭈의 어노테이션명은 동일한 패키지가 아니면 풀패키지 경로가 필요
	@Around(&quot;@annotation(com.e7e.annotation.TimeCheck)&quot;)
	public Object logTime(ProceedingJoinPoint joinPoint) throws Throwable {
		log.debug(&quot;항상 눈으로 확인 {}&quot;,joinPoint);
		long start = System.nanoTime();
		Object proceed = joinPoint.proceed();  // 실행 호출
		long runTime = System.nanoTime() - start;
		log.debug(joinPoint.getSignature().getName() + &quot; 실행시간=&amp;gt; &quot; + runTime + &quot; ns&quot;);
		return proceed;
	}
}&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;이제 그저 테스트 해볼 초 심플 Controller를 맹글장.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;AopTestController.java &lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;(package는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;com.e7e.controller&lt;/span&gt;)&lt;/span&gt; &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1766130900083&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Slf4j
@RestController
@RequestMapping(&quot;/aop&quot;)
public class AopTestController {

	@TimeCheck    // 요게 붙었으닝, AOP 동작 join point
	@GetMapping(&quot;/test&quot;)
	public String testAop() {
		log.debug(&quot;요청이 왔어용&quot;);
		int sum = 0;
		for(int i=1; i&amp;lt;=99999;i++) {
		   	sum +=i;
		}
		log.debug(&quot;합 구했어용 {}&quot;,sum);
		return  &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;size16&quot;&gt;서버를 실행 시키고, 브라우져에서 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;http://너의서버명:포트번호/aop/test&lt;/b&gt; &lt;/span&gt;를 엔터를 꽝 치면&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분명 아래와 같은 내용이 보일 것이당.&amp;nbsp; 그냥 그렇당.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #6164c6;&quot;&gt;항상 눈으로 확인 &lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;execution(String com.e7e.controller.AopTestController.testAop())&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #6164c6;&quot;&gt;요청이 왔어용&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #6164c6;&quot;&gt;합 구했어용 704982704&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #6164c6;&quot;&gt;testAop 실행시간=&amp;gt; 1021200 ns&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 벌써 끄시당.~~&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;앗! 잠깐!&lt;/b&gt;&lt;/span&gt;&amp;nbsp; &lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #d4d4d4; color: #000000;&quot;&gt;TimeCheckAspect&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;에서&amp;nbsp; Advice를 지정하기 위해 사용한&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@Around 어노테이션에서 TimeCheck &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;어노테이션명을 패키지명 포함 고정문자열로 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;하는 것은 &lt;/span&gt;지금 시대는 별로 좋은 방법이 아니다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;아래 처럼 쓰면&lt;/b&gt; &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;변수처럼 동작&lt;/b&gt;&lt;/span&gt;하게 된당.&amp;nbsp; &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;분명 더 좋을 꺼시당&lt;/b&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(메소드 매개변수에 &lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #d4d4d4; color: #646464;&quot;&gt;TimeCheck&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #6a3e3e;&quot;&gt;timeCheck 가 있음에 주목하장)&lt;/span&gt;&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;TimeCheckAspect.java &lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;(수정본)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1766131637820&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Slf4j
@Aspect
@Component
public class TimeCheckAspect {

	@Around(&quot;@annotation(timeCheck)&quot;) // timeCheck가 변수로 동작
	public Object logTime(ProceedingJoinPoint joinPoint,TimeCheck timeCheck) throws Throwable {
		log.debug(&quot;항상 눈으로 확인 {}&quot;,joinPoint);
		log.debug(&quot;변수로 받아용 {}&quot;,timeCheck);
		long start = System.nanoTime();
		Object proceed = joinPoint.proceed();  // 실행 호출
		long runTime = System.nanoTime() - start;
		log.debug(joinPoint.getSignature().getName() + &quot; 실행시간=&amp;gt; &quot; + runTime + &quot; ns&quot;);
		return proceed;
	}
}&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;결과는 같을 것이고, console 출력엔 아래 내용 더불어 나올거시당.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #6164c6;&quot;&gt;변수로 받아용 @com.e7e.rest.annotation.TimeCheck()&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@PathVariable 식으로 받아들이면 Very Good이당.&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;style3&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;/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;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;&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;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=v-yI9x4IpZs&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=v-yI9x4IpZs&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=v-yI9x4IpZs&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/rRXTu/hyZOE4EtdP/ckJnoqbYf9gISU0JK2R0UK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/Em6KJ/hyZPuU8YCR/tQ5WwKwFzTzIho80W9Adw0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;[LIVE CLIP] 한없이 아름다운 너를 만나 - 솔지&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/v-yI9x4IpZs&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>스프링부트</category>
      <category>AOP annotation</category>
      <category>aop에 어노테이션 적용</category>
      <category>spring boot</category>
      <category>스프링 부트</category>
      <author>e7e</author>
      <guid isPermaLink="true">https://e-7-e.tistory.com/323</guid>
      <comments>https://e-7-e.tistory.com/323#entry323comment</comments>
      <pubDate>Fri, 19 Dec 2025 18:38:38 +0900</pubDate>
    </item>
    <item>
      <title>Spring Security  session(jsp) + jwt(react)</title>
      <link>https://e-7-e.tistory.com/320</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;미쳐 몰랐다. 귀차니즘에 근거한 외면에 뿌리를 둔 나의 불찰이당.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jsp(thymeleaf도)의 security 설정과&amp;nbsp; react jwt security 설정을&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;그냥 security도 애매모호 한데, jwt까지 붙이라닝 아마도 힘들었으리라.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 요즘 같은 A.I 시대엔 정확히 원하는 대로 동작하는 코드는&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;/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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;일반적인&amp;nbsp; JSP를 사용하는 경우의 Spring Security&lt;/b&gt;&lt;/span&gt;를 담고 있고&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://e-7-e.tistory.com/215&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2024.03.11 - [스프링] - spring boot 3 security 그냥 한번 해보고 한번 더 해보면 잘 될꺼얼!&lt;/a&gt;&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;JWT를 사용해야 하는 경우의 Spring Security&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;를 담고 있다&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://e-7-e.tistory.com/277&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2025.01.07 - [스프링부트] - spring boot3 security jwt 적용 1 (B/E)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;곧 프론트 프레임워크를 사용하는 경우에 필요하당. (물론 JWK란걸 활용해도 된당)&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;DB뿐만 아니라, 거의 대부분의 구성(인프라)가 같거나 비슷하다는 걸.&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;2개를 교묘히 합치는 방법이 존재하는데, 그것은&amp;nbsp; 멀티&amp;nbsp; securiyFilterChain 설정이당.&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;SecurityConfig.java&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1762933822423&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//.... 생략

@Slf4j
@Configuration
@EnableWebSecurity(debug = false)
@EnableMethodSecurity  						// @preAuthorize/@postAuthorize 사용
public class SecurityConfig {
 
@Autowired
	private DataSource dataSource; // application.properties에 설정한 spring.datasource D.I

	@Order(1)  // 순서가 중요하다. 범위가 작은 걸 먼저 설정해야 한당.
	@Bean 
	SecurityFilterChain filterChain2(HttpSecurity http) throws Exception {
		log.debug(&quot;JWT 시큐리티 설정&quot;);
		
		http.securityMatcher(&quot;/rct/**&quot;)
		    .csrf(csrf-&amp;gt;csrf.disable())
		    .cors(cors -&amp;gt; cors.configurationSource(corsConfigurationSource()))
		    .sessionManagement(sess -&amp;gt; sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
		    .authorizeHttpRequests(auth -&amp;gt; auth.requestMatchers(&quot;/rct/e7e&quot;).hasRole(&quot;CEO&quot;)
		    		                           .requestMatchers(&quot;/rct/login&quot;).permitAll()
		    		                           .requestMatchers(&quot;/rct/refresh&quot;).permitAll()
		    		                           .anyRequest().authenticated())
			.formLogin(form-&amp;gt; form.loginPage(&quot;/rct/login&quot;)
					              .successHandler(jwtLoginSuccessHandler())
					              .failureHandler(jwtLoginFailureHandler()))
			.exceptionHandling(ex -&amp;gt; ex.accessDeniedHandler(jwtAccessDeniedHandler()));
		
		http.addFilterBefore(new JwtAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class);
		
		return http.build();
	}

	@Order(2)
	@Bean
	protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
		log.debug(&quot;JSP 시큐리티 설정&quot;);
		return http.httpBasic(hbasic -&amp;gt; hbasic.disable())
				.headers(config -&amp;gt; config.frameOptions(customizer -&amp;gt; customizer.sameOrigin()))
				.authorizeHttpRequests(
						authz -&amp;gt; authz.dispatcherTypeMatchers(DispatcherType.FORWARD, DispatcherType.ASYNC).permitAll() // forward 허가
								.requestMatchers(&quot;/&quot;,&quot;/login&quot;, &quot;/error&quot;).permitAll()
								.requestMatchers(&quot;/css/**&quot;, &quot;/js/**&quot;, &quot;/img/**&quot;, &quot;/favicon.ico&quot;).permitAll()
								.requestMatchers(&quot;/ceo/**&quot;).hasRole(&quot;CEO&quot;)
								.requestMatchers(&quot;/manager/**&quot;).hasAnyRole(&quot;CEO&quot;,&quot;MANAGER&quot;)
								.anyRequest().authenticated())
				.formLogin(form -&amp;gt; form.loginPage(&quot;/login&quot;)
						               .successHandler(customLoginSuccessHandler())
						               .failureHandler(customLoginFailureHandler()))
				.sessionManagement(session -&amp;gt; session.maximumSessions(1))
				.exceptionHandling(ex -&amp;gt; ex.accessDeniedHandler(customAccessDeniedHandler()))
				.logout(Customizer.withDefaults()).rememberMe(config -&amp;gt; config.key(&quot;e7eKey&quot;).tokenValiditySeconds(86400)
						.tokenRepository(persistentTokenRepository()))
				.build();
	}
 	
  // .... 생략
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2개의 SecurityFilterChain을 리턴하는 메소드가 존재하고,&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt; &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;@Order(1) 어노테이션이 붙은 게 &lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;React용이고, &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;@Order(2) 가 붙은 게 JSP용&lt;/b&gt;&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;p data-ke-size=&quot;size16&quot;&gt;주석을 달아놓긴 했는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;Order 넘버가 중요한데,&amp;nbsp; 숫자가 작은 것에 동작 범위가 작은 것을&amp;nbsp; &lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;지정해야 한당&lt;/b&gt;&lt;/span&gt;.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나의 경우&amp;nbsp; react에서 사용할 url, 곧 jwt를 쓸 거는 모두 url 에 /rct를&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;붙이도록 하여, 첫번째 SecurityFilterChain이 동작하도록 (결국 /rct/** 에만 동작),&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 외의 경우는 두번째 SecurityFilterChain이 동작하도록 하였당.&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; 로그인 successHandler, failureHandler,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;accessDeniedHandler는 처리 방식이 달라야 해서 따로 클래스 파일을 분리하였다.&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개의 글 모두 읽은 사람에게는 그저 코드를 확인하는&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;[ DB는 준비가 되어 있어야 동작확인이 가능함을 잊지 말장 ]&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/bFAoYA/dJMcagqjBB0/oNj28w3vsr3bfSeufCO5w1/secsample.zip?attach=1&amp;amp;knm=tfile.zip&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;secsample.zip&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.08MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://e-7-e.tistory.com/215&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2024.03.11 - [스프링] - spring boot 3 security 그냥 한번 해보고 한번 더 해보면 잘 될꺼얼!&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글에서 되던 동작과&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;jsp_login.png&quot; data-origin-width=&quot;713&quot; data-origin-height=&quot;493&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bozOkf/dJMcadG8f8X/AG73dLO6hrLqg9raKfrhCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bozOkf/dJMcadG8f8X/AG73dLO6hrLqg9raKfrhCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bozOkf/dJMcadG8f8X/AG73dLO6hrLqg9raKfrhCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbozOkf%2FdJMcadG8f8X%2FAG73dLO6hrLqg9raKfrhCK%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;713&quot; height=&quot;493&quot; data-filename=&quot;jsp_login.png&quot; data-origin-width=&quot;713&quot; data-origin-height=&quot;493&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;jsp_login2.png&quot; data-origin-width=&quot;917&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cO6YC6/dJMcabWQPuI/E2GosVasK3RaW3iUTcpBhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cO6YC6/dJMcabWQPuI/E2GosVasK3RaW3iUTcpBhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cO6YC6/dJMcabWQPuI/E2GosVasK3RaW3iUTcpBhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcO6YC6%2FdJMcabWQPuI%2FE2GosVasK3RaW3iUTcpBhK%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;917&quot; height=&quot;800&quot; data-filename=&quot;jsp_login2.png&quot; data-origin-width=&quot;917&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&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;a href=&quot;https://e-7-e.tistory.com/277&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2025.01.07 - [스프링부트] - spring boot3 security jwt 적용 1 (B/E)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글에서 되던 동작도 postman이나 boomerang을 이용하면&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT 로그인만 확인할 수 있게 초우 간단 React를 할 수없이 만들어보장.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vscode의 Open Foler로 새폴더를 만들어 열고, 터미널을 열어 아래 명령어를 치장&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;npx create-vite@latest .&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;npm i&amp;nbsp; axios sweetalert2&amp;nbsp; &amp;nbsp;sweetalert2-react-content&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sweetalert2는 그냥 재미로 넣었어용&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;초 간단이긴 하지만 너무 못생기면 그러하니 아래처럼 혼을 갈아넣은 css&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: #f89009;&quot;&gt;&lt;b&gt;index.css&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1763016384749&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#root {
    width: 450px;
    margin:30px auto;
    border:10px solid pink;
    border-radius: 10px;
    text-align: center;
    padding-bottom: 20px;
}

h1 {
     background-color: black;
     color:yellowgreen;
}

h3 {
     color:goldenrod;
}

h4 {
    color: rgb(197, 5, 133);
}

h4 &amp;gt; div {
    color:rgb(5, 90, 97);
}

#memId {
    display: inline;
    color:blue;
}

span {
    display: inline-block;
    width: 50px;
    text-align: center;
    font-weight: bolder;
    color: cadetblue;
}

button {
    margin-top: 20px;
    font-size: 1.2em;
    border-radius: 3px;
    border: transparent;
    background-color: blueviolet;
    color:white;
}&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;LoginForm.jsx&amp;nbsp; &lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;는 간단한 로그인 form으로 jwt받아서 localStorage에 저장했쪄&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1763016435925&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import axios from &quot;axios&quot;;
import { useRef } from &quot;react&quot;;
import Swal from &quot;sweetalert2&quot;;
import withReactContent from &quot;sweetalert2-react-content&quot;;

const rctURL = &quot;http://localhost:8080/rct&quot;;

function LoginForm({ setIsLogin }) {
  const mkForm = useRef(null);

  const handleSubmit = (e) =&amp;gt; {
    e.preventDefault();

    axios
      .post(`${rctURL}/login`, null, {
        params: {
          username: mkForm.current.username.value,
          password: mkForm.current.password.value,
        },
      })
      .then((rslt) =&amp;gt; {
        console.log(rslt.data);
        if (!rslt.data.Error) {
          localStorage.setItem(&quot;loginInfo&quot;, JSON.stringify(rslt.data));
          setIsLogin(true);
        } else {
          withReactContent(Swal).fire(&quot;머냥 아이디? 암호? 아님 둘다&quot;);
          mkForm.current.username.focus();
        }
      });
  };

  return (
    &amp;lt;&amp;gt;
      &amp;lt;h4&amp;gt;단순 로그인 체크만 테스트&amp;lt;/h4&amp;gt;
      &amp;lt;div style={{ display: &quot;flex&quot;, justifyContent: &quot;center&quot; }}&amp;gt;
        &amp;lt;form ref={mkForm} onSubmit={handleSubmit}&amp;gt;
          &amp;lt;div&amp;gt;
            &amp;lt;span&amp;gt;아 디&amp;lt;/span&amp;gt;
            &amp;lt;input type=&quot;text&quot; name=&quot;username&quot; autoFocus defaultValue={&quot;e7e&quot;} /&amp;gt;
          &amp;lt;/div&amp;gt;
          &amp;lt;div&amp;gt;
            &amp;lt;span&amp;gt;암 호&amp;lt;/span&amp;gt;
            &amp;lt;input type=&quot;password&quot; name=&quot;password&quot; defaultValue={&quot;e7e&quot;} /&amp;gt;
          &amp;lt;/div&amp;gt;
          &amp;lt;div&amp;gt;
            &amp;lt;button&amp;gt;로그잉&amp;lt;/button&amp;gt;
          &amp;lt;/div&amp;gt;
        &amp;lt;/form&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/&amp;gt;
  );
}

export default LoginForm;&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;Profile.jsx &lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;는 로그인 성공하면 localStorage 정보 읽어 사용자 정보 뿌리는 애&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1763016573605&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useEffect, useState } from &quot;react&quot;;

function Profile({ setIsLogin }) {
  const [userInfo, setUserInfo] = useState(null);

  useEffect(() =&amp;gt; {
    const loginInfo = JSON.parse(localStorage.getItem(&quot;loginInfo&quot;));
    console.log(&quot;체킁:&quot;, loginInfo);
    setUserInfo(loginInfo);
  }, []);

  const handleClick = () =&amp;gt; {
    if (localStorage.getItem(&quot;loginInfo&quot;)) {
      localStorage.removeItem(&quot;loginInfo&quot;);
      setIsLogin(false);
    }
  };

  return (
    &amp;lt;&amp;gt;
      {userInfo ? (
        &amp;lt;div&amp;gt;
          &amp;lt;h3&amp;gt;
            &amp;lt;span id=&quot;memId&quot;&amp;gt;{userInfo.memName}&amp;lt;/span&amp;gt; 님의 프로필
          &amp;lt;/h3&amp;gt;
          &amp;lt;img
            width={150}
            height={150}
            src={`https://api.dicebear.com/9.x/avataaars/svg?seed=${userInfo.memName}`}
          /&amp;gt;
          &amp;lt;h4&amp;gt;
            권한은:
            &amp;lt;br /&amp;gt;
            {userInfo.authList.map((auth) =&amp;gt; (
              &amp;lt;div key={auth.authName}&amp;gt;{auth.authName}&amp;lt;/div&amp;gt;
            ))}
          &amp;lt;/h4&amp;gt;
        &amp;lt;/div&amp;gt;
      ) : null}
      &amp;lt;button onClick={handleClick}&amp;gt;로그아웃&amp;lt;/button&amp;gt;
    &amp;lt;/&amp;gt;
  );
}

export default Profile;&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;App.jsx &lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;에 2개를 넣어요 isLogin 상태 변수로 둘 중에 누가 화면에 등장할지?&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1763016605534&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from &quot;react&quot;;
import LoginForm from &quot;./LoginForm&quot;;
import Profile from &quot;./Profile&quot;;

function App() {
  const [isLogin, setIsLogin] = useState(false);

  return (
    &amp;lt;&amp;gt;
      &amp;lt;h1&amp;gt;MK JWT사용&amp;lt;/h1&amp;gt;
      {!isLogin ? (
        &amp;lt;LoginForm setIsLogin={setIsLogin} /&amp;gt;
      ) : (
        &amp;lt;Profile setIsLogin={setIsLogin} /&amp;gt;
      )}
    &amp;lt;/&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;rct-jwt1.png&quot; data-origin-width=&quot;946&quot; data-origin-height=&quot;683&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdhNHH/dJMcacg9xsl/a5A1XiQJTjKvRioLQTIkw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdhNHH/dJMcacg9xsl/a5A1XiQJTjKvRioLQTIkw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdhNHH/dJMcacg9xsl/a5A1XiQJTjKvRioLQTIkw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdhNHH%2FdJMcacg9xsl%2Fa5A1XiQJTjKvRioLQTIkw0%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;946&quot; height=&quot;683&quot; data-filename=&quot;rct-jwt1.png&quot; data-origin-width=&quot;946&quot; data-origin-height=&quot;683&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;rct-jwt2.png&quot; data-origin-width=&quot;1207&quot; data-origin-height=&quot;767&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwJ1Wp/dJMcaiPc1cd/6xgLxSh1ufDtTkVYE9gz61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwJ1Wp/dJMcaiPc1cd/6xgLxSh1ufDtTkVYE9gz61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwJ1Wp/dJMcaiPc1cd/6xgLxSh1ufDtTkVYE9gz61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwJ1Wp%2FdJMcaiPc1cd%2F6xgLxSh1ufDtTkVYE9gz61%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;1207&quot; height=&quot;767&quot; data-filename=&quot;rct-jwt2.png&quot; data-origin-width=&quot;1207&quot; data-origin-height=&quot;767&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;css&lt;/b&gt;에 잠시 시간을 투자하닝.. 그래도 &lt;b&gt;그나마 낫당&lt;/b&gt;.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소스만 바라는 사람도 있당.&amp;nbsp; 당근 초간단이라 MIT 라이센스당.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/eeP1Ul/dJMcagX96v9/F6hol8CVowzgCXR3EMOlKK/rct-jwt.zip?attach=1&amp;amp;knm=tfile.zip&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;rct-jwt.zip&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.03MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 JWT( &lt;span style=&quot;background-color: #ffffff; color: #0a0a0a; text-align: start;&quot;&gt;JSON Web Token) 도 있고 JWK( JSON Web Key, private/public 비대칭 키) &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0a0a0a; text-align: start;&quot;&gt;있으며,&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #0a0a0a; text-align: start;&quot;&gt;token을 브라우져 저장공간(localStorage등)에 저장해서 쓰는 방식&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0a0a0a; text-align: start;&quot;&gt;그냥 변수에 저장해서(in-memory) 사용하는 방식, Back-End 단에서&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0a0a0a; text-align: start;&quot;&gt;Cookie에 담아서 보내는 방식등 이 역시도 다양하당.(그냥 그렇당)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0a0a0a; text-align: start;&quot;&gt;모든 걸 구지 다 알려고 할 필요는 없다. 개념만 잘 잡고 사용하면 그냥 훌륭하당.&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;style3&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;p data-ke-size=&quot;size16&quot;&gt;천재라 불리며 칭찬의 비행기를 타고 세상의 집요한 감시를 받으며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세상이 준비한 그 길을 마냥 생각없이 시키는 대로 따라간 아이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멍청이라 불리며 잔혹한 내팽기침에 세상이 외면한 자유를 얻어&lt;/p&gt;
&lt;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;&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;&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;내가 그리도 찾아 헤매던 50cm 바로 그 자!&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=Ux_Vjw3AFGw&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=Ux_Vjw3AFGw&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=Ux_Vjw3AFGw&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/sNsWZ/hyZNjFW9fl/LI7EyvkkuYwkeY8n8c89Bk/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=410_152_744_516,https://scrap.kakaocdn.net/dn/cbng03/hyZNo8jwOb/At6mfOrA34ed64jABpHP0K/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=410_152_744_516&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;그토록 원했던 자유  , RUMI, JINWOO - Free (KPOP DEMON HUNTERS) [가사 해석]&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/Ux_Vjw3AFGw&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>스프링</category>
      <category>jsp + react 스프링 시큐리티</category>
      <category>spring security jsp + jwt</category>
      <category>스프링 시큐리티 JSP + React</category>
      <category>스프링 시큐리티 session + jwt</category>
      <author>e7e</author>
      <guid isPermaLink="true">https://e-7-e.tistory.com/320</guid>
      <comments>https://e-7-e.tistory.com/320#entry320comment</comments>
      <pubDate>Thu, 13 Nov 2025 16:19:41 +0900</pubDate>
    </item>
    <item>
      <title>React Spinner(react-loader-spinner)</title>
      <link>https://e-7-e.tistory.com/319</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;리액트 하다보면 꽤나 자주&amp;nbsp; &lt;b&gt;로딩 Spinner&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;사람인지라 곧 제대로 된 있어보이는 Spinner를 넣지 못한게&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;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://mhnpd.github.io/react-loader-spinner/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://mhnpd.github.io/react-loader-spinner/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꽤나 따양하고 땀찍한 9개 Spinner가&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;&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;당연 폴더를 1개 만들공, vscode로 해당 폴더를 열공, 터미널에&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;npx create-vite@latest .&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력하고 선택은 react , javascript로 하공, 영문자 o도 미리 입력한다.&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;Spinner 패키지를 아래 명령어로 설치하장.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;npm i&amp;nbsp; react-loader-spinner&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;App.jsx &lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;를 아래 코드로 복사/붙여넣기 해보면 바로 사용법 인지당.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1762431077928&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Audio, DNA, Grid, Hearts, ThreeCircles } from &quot;react-loader-spinner&quot;;

function App() {

  return (
    &amp;lt;&amp;gt;
      &amp;lt;h1
        style={{
          textAlign: &quot;center&quot;,
          backgroundColor: &quot;black&quot;,
          color: &quot;yellow&quot;,
        }}
      &amp;gt;
        MK 데뷔 추카 다양한 Spinner
      &amp;lt;/h1&amp;gt;
      &amp;lt;Audio color=&quot;magenta&quot; /&amp;gt;
      &amp;lt;DNA /&amp;gt;
      &amp;lt;ThreeCircles /&amp;gt;
      &amp;lt;Discuss /&amp;gt;
      &amp;lt;Hearts color=&quot;pink&quot; /&amp;gt;
      &amp;lt;Grid color=&quot;blue&quot; /&amp;gt;
      &amp;lt;Hourglass /&amp;gt;
    &amp;lt;/&amp;gt;
  );
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면을 보고 나면 머얌 넘 쉽잖아 느끼미가 찾아온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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://mhnpd.github.io/react-loader-spinner/docs/intro&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://mhnpd.github.io/react-loader-spinner/docs/intro&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 패키지에서 제공되는 Spinner의 종류가&amp;nbsp; 많은데 한개만 골라서 쓰기에는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맘이 아까우니, 그저&amp;nbsp; &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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;spinAttrs.js&amp;nbsp; &lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;는 제공되는 모든 Spinner의 디폴트 속성값을 담고 있는 파일이당.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;[&amp;nbsp; 잔머리를 조금 써서&amp;nbsp; 짐작하는 것 보다는 쉽게 만들었다. 일단 필요하당 ] &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1762441326064&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const spinAttrs = {
  Audio: {
    height: 100,
    width: 100,
    color: &quot;#4fa94d&quot;,
    ariaLabel: &quot;audio-loading&quot;,
    wrapperStyle: {},
    wrapperClass: &quot;&quot;,
    visible: true,
  },
  BallTriangle: {
    height: 100,
    width: 100,
    radius: 5,
    color: &quot;#4fa94d&quot;,
    ariaLabel: &quot;ball-triangle-loading&quot;,
    wrapperClass: &quot;&quot;,
    wrapperStyle: {},
    visible: true,
  },
  Bars: {
    height: 80,
    width: 80,
    color: &quot;#4fa94d&quot;,
    ariaLabel: &quot;bars-loading&quot;,
    wrapperStyle: {},
    wrapperClass: &quot;&quot;,
    visible: true,
  },
  Blocks: {
    visible: true,
    width: 80,
    height: 80,
    wrapperClass: &quot;&quot;,
    wrapperStyle: {},
    ariaLabel: &quot;blocks-loading&quot;,
  },
  Circles: {
    height: 80,
    width: 80,
    color: &quot;#4fa94d&quot;,
    colors: [&quot;#4fa94d&quot;],
    gradientType: &quot;&quot;,
    gradientAngle: 0,
    ariaLabel: &quot;circles-loading&quot;,
    wrapperStyle: {},
    wrapperClass: &quot;&quot;,
    visible: true,
  },
  CirclesWithBar: {
    wrapperStyle: {},
    visible: true,
    wrapperClass: &quot;&quot;,
    height: 100,
    width: 100,
    color: &quot;#4fa94d&quot;,
    outerCircleColor: &quot;#4fa94d&quot;,
    innerCircleColor: &quot;#4fa94d&quot;,
    barColor: &quot;#4fa94d&quot;,
    ariaLabel: &quot;circles-with-bar-loading&quot;,
  },
  CircularProgress: {
    height: 100,
    width: 100,
    color: &quot;#4fa94d&quot;,
    secondaryColor: &quot;#4fa94d&quot;,
    ariaLabel: &quot;circular-progress-loading&quot;,
    wrapperStyle: {},
    wrapperClass: &quot;&quot;,
    visible: true,
    strokeWidth: 2,
    animationDuration: 1,
  },
  ColorRing: {
    visible: true,
    width: 80,
    height: 80,
    colors: [&quot;#e15b64&quot;, &quot;#f47e60&quot;, &quot;#f8b26a&quot;, &quot;#abbd81&quot;, &quot;#849b87&quot;],
    wrapperClass: &quot;&quot;,
    wrapperStyle: {},
    ariaLabel: &quot;color-ring-loading&quot;,
  },
  Comment: {
    visible: true,
    width: 80,
    height: 80,
    backgroundColor: &quot;#ff6d00&quot;,
    color: &quot;#fff&quot;,
    wrapperClass: &quot;&quot;,
    wrapperStyle: {},
    ariaLabel: &quot;comment-loading&quot;,
  },
  DNA: {
    visible: true,
    width: 80,
    height: 80,
    wrapperClass: &quot;&quot;,
    wrapperStyle: {},
    ariaLabel: &quot;dna-loading&quot;,
    dnaColorOne: &quot;rgba(233, 12, 89, 0.51)&quot;,
  },
  Discuss: {
    visible: true,
    width: 80,
    height: 80,
    wrapperClass: &quot;&quot;,
    wrapperStyle: {},
    ariaLabel: &quot;discuss-loading&quot;,
    colors: [&quot;#ff727d&quot;, &quot;#ff727d&quot;],
  },
  FallingLines: {
    color: &quot;#4fa94d&quot;,
    width: 100,
    visible: true,
  },
  FidgetSpinner: {
    width: 80,
    height: 80,
    backgroundColor: &quot;#4fa94d&quot;,
    ballColors: [&quot;#fc636b&quot;, &quot;#6a67ce&quot;, &quot;#ffb900&quot;],
    wrapperClass: &quot;&quot;,
    wrapperStyle: {},
    ariaLabel: &quot;fidget-spinner-loader&quot;,
    visible: true,
  },
  Grid: {
    height: 80,
    width: 80,
    radius: 12.5,
    color: &quot;#4fa94d&quot;,
    ariaLabel: &quot;grid-loading&quot;,
    wrapperStyle: {},
    wrapperClass: &quot;&quot;,
    visible: true,
  },
  Hearts: {
    height: 80,
    width: 80,
    color: &quot;#4fa94d&quot;,
    ariaLabel: &quot;hearts-loading&quot;,
    wrapperStyle: {},
    wrapperClass: &quot;&quot;,
    visible: true,
  },
  Hourglass: {
    visible: true,
    width: 80,
    height: 80,
    wrapperClass: &quot;&quot;,
    wrapperStyle: {},
    ariaLabel: &quot;hourglass-loading&quot;,
    colors: [&quot;#306cce&quot;, &quot;#72a1ed&quot;],
  },
  InfinitySpin: {
    color: &quot;#4fa94d&quot;,
    width: 200,
  },
  LineWave: {
    wrapperStyle: {},
    visible: true,
    wrapperClass: &quot;&quot;,
    height: 100,
    width: 100,
    color: &quot;#4fa94d&quot;,
    ariaLabel: &quot;line-wave-loading&quot;,
    firstLineColor: &quot;#4fa94d&quot;,
    middleLineColor: &quot;#4fa94d&quot;,
    lastLineColor: &quot;#4fa94d&quot;,
  },
  MagnifyingGlass: {
    visible: true,
    height: 80,
    width: 80,
    wrapperClass: &quot;&quot;,
    wrapperStyle: {},
    ariaLabel: &quot;magnifying-glass-loading&quot;,
    glassColor: &quot;#c0efff&quot;,
    color: &quot;#e15b64&quot;,
  },
  MutatingDots: {
    height: 90,
    width: 80,
    radius: 12.5,
    color: &quot;#4fa94d&quot;,
    secondaryColor: &quot;#4fa94d&quot;,
    ariaLabel: &quot;mutating-dots-loading&quot;,
    wrapperStyle: {},
    wrapperClass: &quot;&quot;,
    visible: true,
  },
  Oval: {
    height: 80,
    width: 80,
    color: &quot;#4fa94d&quot;,
    secondaryColor: &quot;#4fa94d&quot;,
    ariaLabel: &quot;oval-loading&quot;,
    wrapperStyle: {},
    wrapperClass: &quot;&quot;,
    visible: true,
    strokeWidth: 2,
    strokeWidthSecondary: 2,
    animationDuration: 1,
  },
  ProgressBar: {
    visible: true,
    height: 80,
    width: 80,
    wrapperClass: &quot;&quot;,
    wrapperStyle: {},
    ariaLabel: &quot;progress-bar-loading&quot;,
    borderColor: &quot;#F4442E&quot;,
    barColor: &quot;#51E5FF&quot;,
  },
  Puff: {
    height: 80,
    width: 80,
    radius: 1,
    color: &quot;#4fa94d&quot;,
    ariaLabel: &quot;puff-loading&quot;,
    wrapperStyle: {},
    wrapperClass: &quot;&quot;,
    visible: true,
  },
  Radio: {
    visible: true,
    height: 80,
    width: 80,
    wrapperClass: &quot;&quot;,
    wrapperStyle: {},
    ariaLabel: &quot;radio-loading&quot;,
    colors: [&quot;#1B5299&quot;, &quot;#EF8354&quot;, &quot;#DB5461&quot;],
  },
  RevolvingDot: {
    radius: 45,
    strokeWidth: 5,
    color: &quot;#4fa94d&quot;,
    secondaryColor: &quot;r2&quot;,
    ariaLabel: &quot;revolving-dot-loading&quot;,
    wrapperStyle: {},
    wrapperClass: &quot;&quot;,
    visible: true,
  },
  Rings: {
    height: 80,
    width: 80,
    radius: 6,
    color: &quot;#4fa94d&quot;,
    ariaLabel: &quot;rings-loading&quot;,
    wrapperStyle: {},
    wrapperClass: &quot;&quot;,
    visible: true,
  },
  RotatingLines: {
    height: 96,
    width: 96,
    color: &quot;#4fa94d&quot;,
    strokeWidth: 5,
    animationDuration: 0.75,
    strokeColor: &quot;#4fa94d&quot;,
    visible: true,
    ariaLabel: &quot;rotating-lines-loading&quot;,
    wrapperStyle: {},
    wrapperClass: &quot;&quot;,
  },
  RotatingSquare: {
    wrapperClass: &quot;&quot;,
    color: &quot;#4fa94d&quot;,
    height: 100,
    width: 100,
    strokeWidth: 4,
    ariaLabel: &quot;rotating-square-loading&quot;,
    wrapperStyle: {},
    visible: true,
  },
  RotatingTriangles: {
    visible: true,
    height: 80,
    width: 80,
    wrapperClass: &quot;&quot;,
    wrapperStyle: {},
    ariaLabel: &quot;rotating-triangle-loading&quot;,
    colors: [&quot;#1B5299&quot;, &quot;#EF8354&quot;, &quot;#DB5461&quot;],
  },
  TailSpin: {
    height: 80,
    width: 80,
    strokeWidth: 2,
    radius: 1,
    color: &quot;#4fa94d&quot;,
    ariaLabel: &quot;tail-spin-loading&quot;,
    wrapperStyle: {},
    wrapperClass: &quot;&quot;,
    visible: true,
  },
  ThreeCircles: {
    wrapperStyle: {},
    visible: true,
    wrapperClass: &quot;&quot;,
    height: 100,
    width: 100,
    color: &quot;#4fa94d&quot;,
    ariaLabel: &quot;three-circles-loading&quot;,
    outerCircleColor: &quot;#4fa94d&quot;,
    innerCircleColor: &quot;#4fa94d&quot;,
    middleCircleColor: &quot;#4fa94d&quot;,
  },
  ThreeDots: {
    height: 80,
    width: 80,
    radius: 9,
    color: &quot;#4fa94d&quot;,
    ariaLabel: &quot;three-dots-loading&quot;,
    wrapperStyle: {},
    wrapperClass: &quot;&quot;,
    visible: true,
  },
  Triangle: {
    height: 80,
    width: 80,
    color: &quot;#4fa94d&quot;,
    ariaLabel: &quot;triangle-loading&quot;,
    wrapperStyle: {},
    wrapperClass: &quot;&quot;,
    visible: true,
  },
  Vortex: {
    visible: true,
    height: 80,
    width: 80,
    ariaLabel: &quot;vortex-loading&quot;,
    wrapperStyle: {},
    wrapperClass: &quot;&quot;,
    colors: [&quot;#1B5299&quot;, &quot;#EF8354&quot;, &quot;#DB5461&quot;, &quot;#1B5299&quot;, &quot;#EF8354&quot;, &quot;#DB5461&quot;],
  },
  Watch: {
    height: 80,
    width: 80,
    radius: 48,
    color: &quot;#4fa94d&quot;,
    ariaLabel: &quot;watch-loading&quot;,
    wrapperStyle: {},
    wrapperClass: &quot;&quot;,
    visible: true,
  },
};&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;RanSpinner.jsx&amp;nbsp; &lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;파일 요게 핵심 파일이당.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1762440294254&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import * as Spinners from &quot;react-loader-spinner&quot;;
import { spinAttrs } from &quot;./spinAttrs&quot;;

/*
console.log(&quot;좋은 습관 체킁: &quot;,
  Spinners.Audio.toString().substring(1, Spinners.Audio.toString().indexOf(&quot;)&quot;))
);

//지원되는 Spinner 이름을 출력하기 위한 코드, key값을 확인하고자 함
let fullString = &quot;{&quot;;
Object.keys(Spinners).forEach((key) =&amp;gt; {
  fullString += `&quot;${key}&quot;:${Spinners[key]
    .toString()
    .substring(1, Spinners[key].toString().indexOf(&quot;)&quot;))},`;
});
fullString += &quot;}&quot;;
console.log(fullString);
*/


function RanSpinner({ width = 80, height = 80 }) {
  // 이게 곧 함수
  const spinName = rName();
  const SpinnerComp = Spinners[spinName];
  const attrs = spinAttrs[spinName];

  if (attrs.width) attrs.width = width;
  if (attrs.height) attrs.width = height;
  attrsColor(attrs);

  if (SpinnerComp) {
    return &amp;lt;SpinnerComp {...attrs} /&amp;gt;; // 함수 호출
  }

  // 이건 그냥 참공 &amp;lt;DNA {...attrs} /&amp;gt; 와 동일한 호출
  return &amp;lt;Spinners.DNA /&amp;gt;;
}

// 제공되는 Spinner 이름
const sNames = [
  &quot;Audio&quot;,
  &quot;BallTriangle&quot;,
  &quot;Bars&quot;,
  &quot;Blocks&quot;,
  &quot;Circles&quot;,
  &quot;CirclesWithBar&quot;,
  &quot;CircularProgress&quot;,
  &quot;ColorRing&quot;,
  &quot;Comment&quot;,
  &quot;DNA&quot;,
  &quot;Discuss&quot;,
  &quot;FallingLines&quot;,
  &quot;FidgetSpinner&quot;,
  &quot;Grid&quot;,
  &quot;Hearts&quot;,
  &quot;Hourglass&quot;,
  &quot;InfinitySpin&quot;,
  &quot;LineWave&quot;,
  &quot;MagnifyingGlass&quot;,
  &quot;MutatingDots&quot;,
  &quot;Oval&quot;,
  &quot;ProgressBar&quot;,
  &quot;Puff&quot;,
  &quot;Radio&quot;,
  &quot;RevolvingDot&quot;,
  &quot;Rings&quot;,
  &quot;RotatingLines&quot;,
  &quot;RotatingSquare&quot;,
  &quot;RotatingTriangles&quot;,
  &quot;TailSpin&quot;,
  &quot;ThreeCircles&quot;,
  &quot;ThreeDots&quot;,
  &quot;Triangle&quot;,
  &quot;Vortex&quot;,
  &quot;Watch&quot;,
];

// 랜덤 Spinner 선택
const rName = () =&amp;gt; {
  return sNames[Math.floor(Math.random() * sNames.length)];
};

// 랜덤 16진수 칼라
const rHexColor = () =&amp;gt; {
  let hex = ((Math.random() * 0x1000000) | 0).toString(16);
  return &quot;#&quot; + hex.padEnd(6, &quot;0&quot;);
};

// color관련 속성 모두 random 칼러롱
const colorKeys = [
  &quot;color&quot;, &quot;secondaryColor&quot;, &quot;outerCircleColor&quot;,
  &quot;innerCircleColor&quot;, &quot;middleCircleColor&quot;, &quot;barColor&quot;,
  &quot;backgroundColor&quot;, &quot;firstLineColor&quot;, &quot;middleLineColor&quot;,
  &quot;lastLineColor&quot;, &quot;glassColor&quot;, &quot;strokeColor&quot;
];

const attrsColor = (attrs) =&amp;gt; {
  colorKeys.forEach(key =&amp;gt; {
    if (attrs[key]) {
      if (key == &quot;ballColors&quot; || key == &quot;colors&quot;)
        attrs[key] = [rHexColor(), rHexColor(), rHexColor(), rHexColor(), rHexColor(), rHexColor()];
      else attrs[key] = rHexColor();
    }
  })

}

export default RanSpinner;&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;App.jsx &lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;를 아래 처럼 고치면 화면을 새로고침 할때 마다 랜덤 확인이 된당.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1762440510833&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import RanSpinner from &quot;./RanSpinner&quot;;

function App() {

  return (
    &amp;lt;&amp;gt;
      &amp;lt;h1 style={h1Style} &amp;gt;
        MK 데뷔 추카 다양한 Spinner
      &amp;lt;/h1&amp;gt;
      &amp;lt;RanSpinner width={100} height={100} /&amp;gt;
      &amp;lt;RanSpinner /&amp;gt;
      &amp;lt;RanSpinner width={100} height={100} /&amp;gt;
    &amp;lt;/&amp;gt;
  );
}

// H1 스타일
const h1Style = {
  textAlign: &quot;center&quot;,
  backgroundColor: &quot;black&quot;,
  color: &quot;yellow&quot;,
  borderRadius: 12
}
export default App;&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;RanSpinner.jsx&lt;/b&gt; &lt;/span&gt;소스에는 width와 height는&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Props로 넘겨 받을 수 있도록 했고 만약 설정하지 않았다면&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;defalut로 80, 80 값을 가지도록 하였당.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;추가적으로 재밌게 하기 위해 color를 지정하는 속성에는 모두 랜덤 값을 넣었다.&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: #f89009;&quot;&gt; &lt;b&gt;RanSpinner.jsx&lt;span style=&quot;text-align: start; color: #000000;&quot;&gt; 가 훔쳐가야 할&amp;nbsp; 유일한 소스당.&lt;/span&gt;&lt;/b&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;이제 덤으로 실제 더미 데이터와 AJAX 페이크 코드를&amp;nbsp; 살짝 넣어서&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;진실로 Spinner로 동작하는 모습을 확인하는 시간을 가지자.&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: #f89009;&quot;&gt;&lt;b&gt;fake.js&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1762441647016&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 그냥 가짜 데이터
const data = [
  { id: 1, name: &quot;시현&quot;, feature: &quot;다이어트&quot; },
  { id: 2, name: &quot;혜선&quot;, feature: &quot;딴소리&quot; },
  { id: 3, name: &quot;민경&quot;, feature: &quot;기침감기&quot; },
  { id: 4, name: &quot;승아&quot;, feature: &quot;목소리&quot; },
  { id: 5, name: &quot;예원&quot;, feature: &quot;패션츄리닝&quot; },
  { id: 6, name: &quot;경민&quot;, feature: &quot;말괄량이&quot; },
  { id: 7, name: &quot;선주&quot;, feature: &quot;이모티콘&quot; },
  { id: 8, name: &quot;윤정&quot;, feature: &quot;황금구두&quot; },
  { id: 9, name: &quot;현정&quot;, feature: &quot;댄스머신&quot; },
  { id: 10, name: &quot;이서&quot;, feature: &quot;이태원천재&quot; },
  { id: 11, name: &quot;조이&quot;, feature: &quot;동물농장&quot; },
  { id: 12, name: &quot;진영&quot;, feature: &quot;잘가라아&quot; }
];

// AJAX Fake
export const getData = async () =&amp;gt; {
  return new Promise((resolve) =&amp;gt; {
    const ranTime = Math.round(Math.random() * 2000) + 1000;
    setTimeout(() =&amp;gt; {
      const newData = [...data];
      Array.from({ length: 8 }).forEach(() =&amp;gt; {
        newData.splice(Math.floor(Math.random() * newData.length), 1);
      })
      resolve(newData);
    }, ranTime);
  });
};&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;Hero.jsx&amp;nbsp; &lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;는 Spinner 이후에 화면엥 나올 UI 컴포넌트&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1762442763965&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const avar = &quot;https://api.dicebear.com/9.x/big-smile/svg?seed=&quot;;
function Hero({ name, feature }) {
  return (
    &amp;lt;div style={flexColStyle}&amp;gt;
      &amp;lt;img width={100} height={100} src={`${avar}${name}`} alt={name} /&amp;gt;
      &amp;lt;h3 style={nameStyle}&amp;gt;{name}&amp;lt;/h3&amp;gt;
      &amp;lt;h4 style={featureStyle}&amp;gt;{feature}&amp;lt;/h4&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

// 그냥 스타일 
const flexColStyle = {
  display: &quot;inline-flex&quot;,
  flexDirection: &quot;column&quot;,
  justifyContent: &quot;center&quot;,
  alignItems: &quot;center&quot;
}

const nameStyle = {
  color: &quot;yellowgreen&quot;,
  backgroundColor: &quot;black&quot;,
  width: &quot;80%&quot;,
  textAlign: &quot;center&quot;,
  borderRadius: 10,
}

const featureStyle = {
  borderBottom: `2px solid magenta`,
  color: &quot;blue&quot;,
  width: &quot;80%&quot;,
  textAlign: &quot;center&quot;,
  fontWeight: &quot;bolder&quot;
}


export default Hero&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;App.jsx&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1762442798910&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useEffect, useState } from &quot;react&quot;;
import { getData } from &quot;./fake&quot;;
import RanSpinner from &quot;./RanSpinner&quot;;
import Hero from &quot;./Hero&quot;;

function App() {
  const [heros, setHeros] = useState([]);
  const [again, setAgain] = useState(true);

  useEffect(() =&amp;gt; {
    if (!again) setHeros([]);
    else {
      getData().then((result) =&amp;gt; {
        //console.log(&quot;result&quot;, result);
        setHeros([...result]);
      });
    }

    const timerId = setTimeout(() =&amp;gt; {
      setAgain(!again)
    }, 5000);

    return () =&amp;gt; {
      //console.log(&quot;unmount&quot;);
      clearTimeout(timerId);
    }
  }, [again]);

  return (
    &amp;lt;&amp;gt;
      &amp;lt;h1 style={h1Style} &amp;gt;
        MK 데뷔 추카 다양한 Spinner
      &amp;lt;/h1&amp;gt;
      {heros.length ? (
        &amp;lt;div style={flexStyle}&amp;gt;
          {heros.map((hero) =&amp;gt; (
            &amp;lt;Hero key={hero.id} {...hero} /&amp;gt;
          ))
          }
        &amp;lt;/div&amp;gt;
      ) : (
        &amp;lt;div style={flexStyle}&amp;gt;
          &amp;lt;RanSpinner width={100} height={100} /&amp;gt;
          &amp;lt;RanSpinner width={100} height={100} /&amp;gt;
          &amp;lt;RanSpinner width={100} height={100} /&amp;gt;
          &amp;lt;RanSpinner width={100} height={100} /&amp;gt;
        &amp;lt;/div&amp;gt;
      )}
    &amp;lt;/&amp;gt;
  );
}

// H1 스타일
const h1Style = {
  textAlign: &quot;center&quot;,
  backgroundColor: &quot;black&quot;,
  color: &quot;yellow&quot;,
  borderRadius: 12
}
// flex 스타일
const flexStyle = {
  display: &quot;flex&quot;,
  justifyContent: &quot;space-evenly&quot;,
  alignItems: &quot;center&quot;
}

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;데이터 오기 전 Spinner&amp;nbsp; 동작 화면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[ 똑같은 Spinner가 나오는 지루함에서 벗어나게 되었당.. ㅋㅋ]&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;spinners.png&quot; data-origin-width=&quot;1034&quot; data-origin-height=&quot;470&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/suJAH/dJMcafSrTgU/gMKCElYdKqgP6dwIn94If1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/suJAH/dJMcafSrTgU/gMKCElYdKqgP6dwIn94If1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/suJAH/dJMcafSrTgU/gMKCElYdKqgP6dwIn94If1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsuJAH%2FdJMcafSrTgU%2FgMKCElYdKqgP6dwIn94If1%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;1034&quot; height=&quot;470&quot; data-filename=&quot;spinners.png&quot; data-origin-width=&quot;1034&quot; data-origin-height=&quot;470&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;Data가 도착하면 Spinner는 사라지고, 데이터가 보인당.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[ 랜덤데이터 중 랜덤으로 4개만 나오게 했으닝, 어쩌면 이름에 불만이 있을수도...]&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;afterspin.png&quot; data-origin-width=&quot;1034&quot; data-origin-height=&quot;653&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4CS0K/dJMcagX7Qvo/TGEbAnCsQAzjbvQYJEhpg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4CS0K/dJMcagX7Qvo/TGEbAnCsQAzjbvQYJEhpg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4CS0K/dJMcagX7Qvo/TGEbAnCsQAzjbvQYJEhpg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4CS0K%2FdJMcagX7Qvo%2FTGEbAnCsQAzjbvQYJEhpg0%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;1034&quot; height=&quot;653&quot; data-filename=&quot;afterspin.png&quot; data-origin-width=&quot;1034&quot; data-origin-height=&quot;653&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;노파심에 전체 소스를 아래 zip 파일로 올린당.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[&amp;nbsp; npm i 가 필요함을 잊지말장 ]&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/tQPNq/dJMcacg7oSM/CGOI34zM17uL2dqD7Y4GV0/simple-spin.zip?attach=1&amp;amp;knm=tfile.zip&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;simple-spin.zip&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.03MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&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; 만약 시간이 있어&amp;nbsp; 소스를 여유롭게 본다면, 결단코&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 누군가는 전두엽의 자극으로 산문 코딩을 시 코딩으로&amp;nbsp; 바꿀 테크닉을 얻으리랑.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한때 김다미가 조이서로 출연하여 맘껏 좋아햇던&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빛 발랄 드라마 이태원 클라스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거기서 만났던 감정의 이음새들을 흔들었던 말&lt;/p&gt;
&lt;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;감동 자극이지만 주제와 상황에 따라 뭐래?가 될 수 있다&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;그런 Spinner가 더 아기자기하고 예쁠 수 있다는 거당.&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;납득 시키고 픈 건 나만의 Spinner를&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;뱅뱅 스피너!&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;a href=&quot;https://www.youtube.com/watch?v=KceYK_5TY18&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=KceYK_5TY18&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=KceYK_5TY18&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bgajiS/hyZM8YtIUg/qco34Ho7LQBQUoi2Kpmtok/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=78_138_1196_476,https://scrap.kakaocdn.net/dn/1WfDG/hyZNeqSJBQ/bhpBUTDGERK67XhAqOyyi0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=78_138_1196_476&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;[MV] Sondia - &quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/KceYK_5TY18&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>React</category>
      <category>React Spinner</category>
      <category>react-loader-spinner</category>
      <category>랜덤 스피너</category>
      <category>리액트 Spinner</category>
      <category>리액트 스피너</category>
      <author>e7e</author>
      <guid isPermaLink="true">https://e-7-e.tistory.com/319</guid>
      <comments>https://e-7-e.tistory.com/319#entry319comment</comments>
      <pubDate>Fri, 7 Nov 2025 14:41:58 +0900</pubDate>
    </item>
    <item>
      <title>React(리액트) 티키타카 18 ( rc-tree 트리 컴포넌트)</title>
      <link>https://e-7-e.tistory.com/264</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;아니 또 누군가는 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;Tree 컴포넌트&lt;/b&gt;&lt;/span&gt;를 쓰고 싶단다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.jstree.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;jstree&lt;/a&gt;는 jquery가 필요한 jquery 플러그인이닝, React를 쓴다면 과감없잉 버릴깡?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[ jquery 4가 발표되었당!~ 무엇이 바뀌었을깡??? ]&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;rc-tree란 게 있당. 중국말 설명이 중국산인가 보당.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 npmjs에 가면 &lt;b&gt;api가 테이블로 정리&lt;/b&gt; 되어 있당.&amp;nbsp; 지금은 대략 눈치껏 확인 정도만...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/rc-tree&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.npmjs.com/package/rc-tree&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;b&gt;아래 링크&lt;/b&gt; 가면, &lt;b&gt;실행 예제와 소스&lt;/b&gt;를 볼 수 있어, 어쩔 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;또 올 수 밖에&lt;/b&gt;&lt;/span&gt; 없낭? 하는 느낌이당.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://tree-react-component.vercel.app/demo/draggable&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://tree-react-component.vercel.app/demo/draggable&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;시작하면&amp;nbsp; 절반&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;vite로 필요한 프로젝트 맹글었다 치공, &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;설치&lt;/b&gt;&lt;/span&gt; 들어가장.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;npm i rc-tree&lt;br /&gt;npm i tailwindcss @tailwindcss/vite&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;vite.config.js&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769083015708&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { defineConfig } from &quot;vite&quot;;
import react from &quot;@vitejs/plugin-react&quot;;
import tailwindcss from &quot;@tailwindcss/vite&quot;;

// https://vite.dev/config/
export default defineConfig({
  plugins: [react(), tailwindcss()],
});&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;index.css&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769083039405&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@import &quot;tailwindcss&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;size16&quot;&gt;실행 모습을 보려면 어쩔! 더미 데이터가 필요하당.~~ ㅠㅠ&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;key, title, children 이 기본속성 값&lt;/b&gt;&lt;/span&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;sampleData.js&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1735105199464&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const treeData = [
  {
    key: &quot;E7E&quot;,
    title: &quot;DDIT 배움 즐김사&quot;,
    children: [
      {
        key: &quot;F1&quot;,
        title: &quot;F1 GIRLS&quot;,
        children: [
          { key: &quot;f1-1&quot;, title: &quot;경미닝&quot; },
          { key: &quot;f1-2&quot;, title: &quot;선주닝&quot; },
          { key: &quot;f1-3&quot;, title: &quot;세여닝&quot; },
          { key: &quot;f1-4&quot;, title: &quot;지워닝&quot; },
          { key: &quot;f1-5&quot;, title: &quot;수미닝&quot; },
          { key: &quot;f1-6&quot;, title: &quot;해프닝&quot; },
        ],
      },
    ],
  },
  {
    key: &quot;SM&quot;,
    title: &quot;SM 엔터테인먼트&quot;,
    children: [
      {
        key: &quot;aespa&quot;,
        title: &quot;에스파&quot;,
        children: [
          { key: &quot;aespa-1&quot;, title: &quot;닝닝&quot; },
          { key: &quot;aespa-2&quot;, title: &quot;카리나&quot; },
          { key: &quot;aespa-3&quot;, title: &quot;지젤&quot; },
          { key: &quot;aespa-4&quot;, title: &quot;윈터&quot; },
        ],
      },
      {
        key: &quot;redvelvet&quot;,
        title: &quot;레드벨벳&quot;,
        children: [
          { key: &quot;redvel-1&quot;, title: &quot;조이&quot; },
          { key: &quot;redvel-2&quot;, title: &quot;슬기&quot; },
          { key: &quot;redvel-3&quot;, title: &quot;아이린&quot; },
          { key: &quot;redvel-4&quot;, title: &quot;웬디&quot; },
          { key: &quot;redvel-5&quot;, title: &quot;메리&quot; },
        ],
      },
    ],
  },
  {
    key: &quot;YG&quot;,
    title: &quot;YG 엔터테인먼트&quot;,
    children: [
      {
        key: &quot;blackpink&quot;,
        title: &quot;블랙핑크&quot;,
        children: [
          { key: &quot;blink-1&quot;, title: &quot;로제&quot; },
          { key: &quot;blink-2&quot;, title: &quot;제니&quot; },
          { key: &quot;blink-3&quot;, title: &quot;리사&quot; },
          { key: &quot;blink-4&quot;, title: &quot;지수&quot; },
        ],
      },
    ],
  },
  {
    key: &quot;JYP&quot;,
    title: &quot;JYP 엔터테인먼트&quot;,
    children: [
      {
        key: &quot;itzy&quot;,
        title: &quot;있지&quot;,
        children: [
          { key: &quot;itzy-1&quot;, title: &quot;예지&quot; },
          { key: &quot;itzy-2&quot;, title: &quot;채령&quot; },
          { key: &quot;itzy-3&quot;, title: &quot;류진&quot; },
          { key: &quot;itzy-4&quot;, title: &quot;리아&quot; },
          { key: &quot;itzy-5&quot;, title: &quot;유나&quot; },
        ],
      },
    ],
  },
  {
    key: &quot;STAR&quot;,
    title: &quot;스타쉽 엔터테인먼트&quot;,
    children: [
      {
        key: &quot;ive&quot;,
        title: &quot;IVE&quot;,
        children: [
          { key: &quot;ive-1&quot;, title: &quot;안유진&quot; },
          { key: &quot;ive-2&quot;, title: &quot;가을&quot; },
          { key: &quot;ive-3&quot;, title: &quot;레이&quot; },
          { key: &quot;ive-4&quot;, title: &quot;장원영&quot; },
          { key: &quot;ive-5&quot;, title: &quot;리즈&quot; },
          { key: &quot;ive-6&quot;, title: &quot;이서&quot; },
        ],
      },
    ],
  },
];

export default treeData;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 이와 비슷한 Tree 컴포넌트를 사용해 본 사람이라면&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;데이터 구조에 parent를 사용하는가?, children을 사용하는가?에 따라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장단점이 많이 다르당.(언제 한번 느껴보길 바란당)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나중에 Backend 서버에서 이런 형태의 데이터를 직접 가져오든가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아니면 받은 데이터를 이런 형태가 되도록 Transform 하는&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;기본 Tree를 맹글어&lt;/b&gt;&lt;/span&gt;보당.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제공 되는 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;Tree 컴포넌트의 props를 잘 눈길&lt;/b&gt;&lt;/span&gt;에 발자국 찍듯이 힘을 빼고 본당.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;아이콘 이미지는 귀간지러서 api를 사용&lt;/b&gt;해서 넣었당.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;그래야 소스 가져간 사람이 귀안케 이미지&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;color: #f89009;&quot;&gt;&lt;b&gt;select 이벤트도 onSelect&lt;/b&gt; &lt;/span&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;RcTree.jsx&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1730903814056&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Tree from &quot;rc-tree&quot;;
import &quot;rc-tree/assets/index.css&quot;;
import treeData from &quot;./sampleData&quot;;

function RcTree() {

  // 그냥 아이콘 잼나겡
  const handleIcon = (obj) =&amp;gt; {
    //console.log(&quot;체킁&quot;,obj)
    let imgURL = &quot;&quot;;
    const hypCnt = obj.pos.split(&quot;-&quot;).length;

    hypCnt == 2 &amp;amp;&amp;amp;
      (imgURL = `https://dummyimage.com/160x160/000/fff.png&amp;amp;text=${obj.data.key}`);
    hypCnt == 3 &amp;amp;&amp;amp;
      (imgURL = `https://dummyimage.com/160x160/000/ff0.png&amp;amp;text=${obj.data.key}`);
    hypCnt == 4 &amp;amp;&amp;amp;
      (imgURL = `https://api.dicebear.com/9.x/adventurer/svg?seed=${obj.title}`);

    return &amp;lt;img src={imgURL} width={16} height={16} /&amp;gt;;
  };

  const handleSelect = (skey, info) =&amp;gt; {
    console.log(&quot;체킁 1: &quot;, skey, info.node.title);
    console.log(&quot;체킁 2: &quot;, info);
  };

  return (
    &amp;lt;Tree
      treeData={treeData}
      showIcon
      icon={handleIcon}
      selectable
      expandAction={&quot;click&quot;}
      onSelect={handleSelect}
      // 은근 Zoom 테크니크!
      style={{ transform: &quot;scale(2)&quot;, transformOrigin: &quot;left top&quot; }}
    /&amp;gt;
  );
}

export default RcTree;&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;pre id=&quot;code_1762227403510&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const hypCnt = obj.pos.split(&quot;-&quot;).length;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rc-tree는 tree node에&amp;nbsp; pos란 속성을 가질 수도 있는데, sampleData.js를 보장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최상위 Root가&amp;nbsp; 0번 (여기선 treeData 배열 자체) 이 되고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음 배열의 첫번째 key E7E가&amp;nbsp; 0-0, 두번째 SM이 0-1&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SM의 children 첫번째 aespa가&amp;nbsp; 0-1-0, 두번째가 0-1-1 식의&amp;nbsp; 값을 가지게 된당.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[ 사실 이 부분은 sampleData에 type등 본인이 원하는 이름으로 추가 속성을 부여한다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;App.jsx&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1735111186666&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import RcTree from &quot;./RcTree&quot;;

function App() {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;h1 className=&quot;bg-violet-800 text-white text-center font-extrabold p-2 text-5xl&quot;&amp;gt;
        RC-TREE 해보아용
      &amp;lt;/h1&amp;gt;
      &amp;lt;RcTree /&amp;gt;
    &amp;lt;/&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 data-ke-size=&quot;size16&quot;&gt;음... 먼가 써볼까 하는 마음이 들었지만, RC 카를 사면 더 재밌을 꺼&amp;nbsp; 같당.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;rc-tree1.png&quot; data-origin-width=&quot;1271&quot; data-origin-height=&quot;889&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBRBid/dJMcagqKjXu/tA9KsiA2OH5Yh5qIKmmp7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBRBid/dJMcagqKjXu/tA9KsiA2OH5Yh5qIKmmp7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBRBid/dJMcagqKjXu/tA9KsiA2OH5Yh5qIKmmp7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBRBid%2FdJMcagqKjXu%2FtA9KsiA2OH5Yh5qIKmmp7K%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;1271&quot; height=&quot;889&quot; data-filename=&quot;rc-tree1.png&quot; data-origin-width=&quot;1271&quot; data-origin-height=&quot;889&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;/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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Tree 컴포넌트를 사용하다보면 열린 Tree Node가 많으면&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;선택한 Node만 열리고, 나머지는 닫히도록 해보장&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;RC-TREE에선 정말 쉽당.(문서에 설명이 없어, 실행 결과에서 추론해야 한당.)&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: #f89009;&quot;&gt;&lt;b&gt;RcTree.jsx&amp;nbsp; &lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;(주석을 다라 다라 달았당)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769067832012&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Tree from &quot;rc-tree&quot;;
import &quot;rc-tree/assets/index.css&quot;;
import treeData from &quot;./sampleData&quot;;
import { useState } from &quot;react&quot;;

function RcTree() {
  const handleIcon = (obj) =&amp;gt; {
    //console.log(&quot;체킁&quot;,obj)
    let imgURL = &quot;&quot;;
    const hypCnt = obj.pos.split(&quot;-&quot;).length;

    hypCnt == 2 &amp;amp;&amp;amp;
      (imgURL = `https://dummyimage.com/160x160/000/fff.png&amp;amp;text=${obj.data.key}`);
    hypCnt == 3 &amp;amp;&amp;amp;
      (imgURL = `https://dummyimage.com/160x160/000/ff0.png&amp;amp;text=${obj.data.key}`);
    hypCnt == 4 &amp;amp;&amp;amp;
      (imgURL = `https://api.dicebear.com/9.x/adventurer/svg?seed=${obj.title}`);

    return &amp;lt;img src={imgURL} width={16} height={16} /&amp;gt;;
  };

  const handleSelect = (skey, info) =&amp;gt; {
    //console.log(&quot;체킁 1: &quot;, skey, info.node.title);
    //console.log(&quot;체킁 2: &quot;, info);
  };

  // 선택된 Node만 열리게 하기 위한 코드
  const [expandedKeys, setExpandedKeys] = useState([]);
  // 콜백형식으로 필요한 값들을 매개변수로 자동으로 넣어준당.
  const handleExpand = (expandedKeys, { _expanded, node, _nativeEvent }) =&amp;gt; {
    node.expanded ? setExpandedKeys(expandedKeys) : setExpandedKeys([node.key]);
  };

  return (
    &amp;lt;Tree
      treeData={treeData}
      showIcon
      icon={handleIcon}
      selectable
      expandAction={&quot;click&quot;}
      onSelect={handleSelect}
      // 선택된 Node만 열리기 위한 부분
      autoExpandParent
      expandedKeys={expandedKeys}
      onExpand={handleExpand}
      // 은근 Zoom 테크니크!
      style={{ transform: &quot;scale(2)&quot;, transformOrigin: &quot;left top&quot; }}
    /&amp;gt;
  );
}

export default RcTree;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는 실행해 보면 바로 각막에 결과가 새겨질지경이당.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;뽀인또는&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;Tree 컴포넌트&lt;/b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;에 에 추가된 아래&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #f89009; text-align: start;&quot;&gt;&lt;b&gt;속성(expanedKeys가 핵심&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;)과&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769082447028&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;        autoExpandParent
        expandedKeys={expandedKeys}
        onExpand={handleExpand}&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;pre id=&quot;code_1769082492309&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  // 선택된 Node만 열리게 하기 위한 코드
  const [expandedKeys, setExpandedKeys] = useState([]);
  // 콜백형식으로 필요한 값들을 매개변수로 자동으로 넣어준당.
  const handleExpand = (expandedKeys, { _expanded, node, _nativeEvent }) =&amp;gt; {
    node.expanded ? setExpandedKeys(expandedKeys) : setExpandedKeys([node.key]);
  };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요기까지만 해도 만족이야 하겠지만, 포만감은 없을거당.&lt;/p&gt;
&lt;p data-ke-size=&quot;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;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;npm i react-router&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라우터 설정 해볼까낭! 초간단 설정!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;main.jsx&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1735116403395&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { createRoot } from &quot;react-dom/client&quot;;
import App from &quot;./App.jsx&quot;;
import &quot;./index.css&quot;;
import { createBrowserRouter, RouterProvider } from &quot;react-router&quot;;
import Idol from &quot;./Idol.jsx&quot;;

const router = createBrowserRouter([
  {
    path: &quot;&quot;,
    element: &amp;lt;App /&amp;gt;,
    children: [
      {
        index: true,
        element: &amp;lt;Idol /&amp;gt;,
      },
      {
        path: &quot;/:name&quot;,
        element: &amp;lt;Idol /&amp;gt;,
      },
    ],
  },
]);

createRoot(document.getElementById(&quot;root&quot;)).render(
  &amp;lt;RouterProvider router={router} /&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;Idol 컴포넌트가 나올 자리 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;Outlet 설정&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;useNavigate&amp;nbsp; 훅 이용, 값 전달&lt;/b&gt;&lt;/span&gt;도 해봄&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선택여부와 children여부를 이용,&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt; leaf인 경우만 navigate&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 style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;App.jsx&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769082566709&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Outlet } from &quot;react-router&quot;;
import RcTree from &quot;./RcTree&quot;;

function App() {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;h1 className=&quot;mt-5 bg-violet-800 text-white text-center font-extrabold p-2 text-5xl&quot;&amp;gt;
        RC-TREE 해보아용
      &amp;lt;/h1&amp;gt;
      &amp;lt;div className=&quot;flex border-2 border-violet-600 h-full min-w-200&quot;&amp;gt;
        &amp;lt;div className=&quot;flex-1&quot;&amp;gt;
          &amp;lt;RcTree /&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div className=&quot;flex-1 h-full border border-violet-500 z-10&quot;&amp;gt;
          &amp;lt;Outlet /&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;RcTree.jsx&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1735116454595&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Tree from &quot;rc-tree&quot;;
import &quot;rc-tree/assets/index.css&quot;;
import treeData from &quot;./sampleData&quot;;
import { useState } from &quot;react&quot;;
import { useNavigate } from &quot;react-router&quot;;

function RcTree() {
  const handleIcon = (obj) =&amp;gt; {
    //console.log(&quot;체킁&quot;,obj)
    let imgURL = &quot;&quot;;
    const hypCnt = obj.pos.split(&quot;-&quot;).length;

    hypCnt == 2 &amp;amp;&amp;amp;
      (imgURL = `https://dummyimage.com/160x160/000/fff.png&amp;amp;text=${obj.data.key}`);
    hypCnt == 3 &amp;amp;&amp;amp;
      (imgURL = `https://dummyimage.com/160x160/000/ff0.png&amp;amp;text=${obj.data.key}`);
    hypCnt == 4 &amp;amp;&amp;amp;
      (imgURL = `https://api.dicebear.com/9.x/adventurer/svg?seed=${obj.title}`);

    return &amp;lt;img src={imgURL} width={16} height={16} /&amp;gt;;
  };

  const navigate = useNavigate();
  const handleSelect = (skey, info) =&amp;gt; {
    // console.log(&quot;1: &quot;, skey, info.node.title);
    //console.log(&quot;2: &quot;, info.node);
    const selNode = info.node;

    if (!selNode.selected &amp;amp;&amp;amp; !selNode.children) {
      navigate(`/${selNode.title}`, { state: selNode });
    }
  };

  // 선택된 Node만 열리게 하기 위한 코드
  const [expandedKeys, setExpandedKeys] = useState([]);
  // 콜백형식으로 필요한 값들을 매개변수로 자동으로 넣어준당.
  const handleExpand = (expandedKeys, { _expanded, node, _nativeEvent }) =&amp;gt; {
    node.expanded ? setExpandedKeys(expandedKeys) : setExpandedKeys([node.key]);
  };

  return (
    &amp;lt;Tree
      treeData={treeData}
      showIcon
      icon={handleIcon}
      selectable
      expandAction={&quot;click&quot;}
      onSelect={handleSelect}
      // 선택된 Node만 열리기 위한 부분
      autoExpandParent
      expandedKeys={expandedKeys}
      onExpand={handleExpand}
      // 은근 Zoom 테크니크!
      style={{ transform: &quot;scale(2)&quot;, transformOrigin: &quot;left top&quot; }}
    /&amp;gt;
  );
}

export default RcTree;&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;span style=&quot;color: #456771;&quot;&gt;&lt;b&gt;useLocation 훅 이용, 넘겨 받은 node값 활용&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;Idol.jsx&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1735116482547&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useLocation } from &quot;react-router&quot;;

function Idol() {
  const location = useLocation();
  const { title = &quot;누구닝&quot;, key = &quot;&quot; } = { ...location.state };
  console.log(&quot;체킁2: &quot;, key);

  return (
    &amp;lt;div className=&quot;h-full  flex flex-col border-10 border-pink-300 justify-start items-center&quot;&amp;gt;
      {/* 랜덤 얼굴 이미지 API https://100k-faces.vercel.app/api/random-image */}
      {key === &quot;&quot; ? (
        &amp;lt;img
          className=&quot;rounded-2xl&quot;
          src={
            &quot;https://pimg.mk.co.kr/news/cms/202411/21/news-p.v1.20241121.21f41559c9be4ce88ced8a655f969ae6_P1.jpg&quot;
          }
        /&amp;gt;
      ) : (
        &amp;lt;img
          src={`https://100k-faces.vercel.app/api/random-image?seed=${key}`}
        /&amp;gt;
      )}
      &amp;lt;h1 className=&quot;text-8xl my-4&quot;&amp;gt;
        &amp;lt;span&amp;gt;{title}&amp;lt;/span&amp;gt;
      &amp;lt;/h1&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default Idol;&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;rc-tree2.png&quot; data-origin-width=&quot;1274&quot; data-origin-height=&quot;982&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8Pwu4/dJMcabJL3Me/Zs422bKwTq2Nb9koZcYfMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8Pwu4/dJMcabJL3Me/Zs422bKwTq2Nb9koZcYfMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8Pwu4/dJMcabJL3Me/Zs422bKwTq2Nb9koZcYfMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8Pwu4%2FdJMcabJL3Me%2FZs422bKwTq2Nb9koZcYfMK%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;1274&quot; height=&quot;982&quot; data-filename=&quot;rc-tree2.png&quot; data-origin-width=&quot;1274&quot; data-origin-height=&quot;982&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본인이 한번 맹글어 보는 수고를 해보장. 어쩌면 생각보다 기쁠거시당.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;npmjs에서 검색하면 rc-tree-select도 있당. (나중에 써 보아야겠당!)&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;아! 그리고 이런 외부 라이브러리는 Typescript로 한번 더 해보면 좋은데...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Typescript 사용 패턴 연습이 Typescript가 낯선자에게 꽤나 도우미 된당.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@types/rc-tree 형식으로&amp;nbsp; @types/패키지명의 패키지도 대부분 지원된단 사실도...&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로 맹근 파일을 첨부해 본당&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/bezFsJ/dJMcahbEbOZ/l771tWK3UfsQfXRY6qVxVk/type-rctree.zip?attach=1&amp;amp;knm=tfile.zip&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;type-rctree.zip&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.04MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&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;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;npm i &amp;amp;&amp;amp; npm run dev&amp;nbsp;&lt;/b&gt;&lt;/blockquote&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;rctree3.png&quot; data-origin-width=&quot;1182&quot; data-origin-height=&quot;746&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cU2haR/dJMcagDOjvP/bkKgrzCSN4fJQMV7yqrNX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cU2haR/dJMcagDOjvP/bkKgrzCSN4fJQMV7yqrNX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cU2haR/dJMcagDOjvP/bkKgrzCSN4fJQMV7yqrNX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcU2haR%2FdJMcagDOjvP%2FbkKgrzCSN4fJQMV7yqrNX0%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;1182&quot; height=&quot;746&quot; data-filename=&quot;rctree3.png&quot; data-origin-width=&quot;1182&quot; data-origin-height=&quot;746&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;style3&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; 센스&amp;nbsp; 초능력은&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;말과 상황에 대처하는 센스는 소심하였당.&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;찌질한 말을 훅 뱉지 않았다면...&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;하여간&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;a href=&quot;https://www.youtube.com/watch?v=HzOjwL7IP_o&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=HzOjwL7IP_o&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=HzOjwL7IP_o&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/EhIGY/hyXOqACeGa/MCqcAniCJNYt8TIczjB431/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=420_88_1204_366,https://scrap.kakaocdn.net/dn/jiMps/hyXSswLdBm/PNGo9cVS6k1u22wdIR4umK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=420_88_1204_366&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;(MV)오마이걸(OH MY GIRL)_Dun Dun Dance&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/HzOjwL7IP_o&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;</description>
      <category>React</category>
      <category>Example</category>
      <category>MUI</category>
      <category>rc-tree</category>
      <category>React</category>
      <category>useLocation</category>
      <category>Vite</category>
      <category>예제</category>
      <author>e7e</author>
      <guid isPermaLink="true">https://e-7-e.tistory.com/264</guid>
      <comments>https://e-7-e.tistory.com/264#entry264comment</comments>
      <pubDate>Tue, 4 Nov 2025 12:19:33 +0900</pubDate>
    </item>
    <item>
      <title>React(리액트) 티키타카 33  SVAR Gantt(업그레이드)</title>
      <link>https://e-7-e.tistory.com/305</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;드디어 svar gantt가&amp;nbsp; 쓸만하게 업그레이드 되었당.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #456771;&quot;&gt;(패키지이름도&amp;nbsp; wx-react-gantt에서 @svar-ui/react-gantt로 살짜기 바뀌었당)&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; 유료인 어쩔수 없는 상황에서 svar gantt는&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;/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://docs.svar.dev/react/gantt/getting_started/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.svar.dev/react/gantt/getting_started/&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당근 먼저 vite로 React/Javascript 프로젝트를 아무 빈 폴더에 셋업한당.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;npx create-vite@latest .&lt;/b&gt;&lt;/span&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; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로&amp;nbsp; 설치하장&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;npm i @svar-ui/react-gantt&amp;nbsp; &amp;nbsp; ## SVAR React Gantt&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;npm i axios&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대략 필요없는 파일은 나름 정리하고 App.jsx를 아래처럼 바꿔보장&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: #f89009;&quot;&gt;&lt;b&gt;App,jsx&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1761955934199&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Gantt, WillowDark } from &quot;@svar-ui/react-gantt&quot;;
import &quot;@svar-ui/react-gantt/all.css&quot;;

function App() {

  return (
    &amp;lt;&amp;gt;
      &amp;lt;h1&amp;gt;
        SVAR GANTT 이제는 쓸만하당
      &amp;lt;/h1&amp;gt;
      &amp;lt;WillowDark&amp;gt;
        &amp;lt;Gantt /&amp;gt;
      &amp;lt;/WillowDark&amp;gt;
    &amp;lt;/&amp;gt;
  );
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 화면을 확인하면 당연 데이터 없는 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;Gantt&lt;/b&gt;&lt;/span&gt;가 눈에 보인당.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;WillowDark&lt;/b&gt;&lt;/span&gt;는 제공되는 테마당. &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;Willow&lt;/b&gt;&lt;/span&gt;랑 2개가 제공되는데&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당근 Dark가 붙었으니가 어두운 테마, 안 붙은 건 밝은 테마당&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;이 시대가 원하는 옳고 그름을 떠난&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; 화면이 영어로 나오는 부분을 한글로 나오도록(localizaion) 위해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래&amp;nbsp; &amp;nbsp;ko.js파일을 src 폴더에 맹글장. (귀찮으면 다운받는당)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/bv5ilZ/dJMcab3zamh/YCdrF4yo5MAkPyp0IE9ks0/ko.js?attach=1&amp;amp;knm=tfile.js&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;ko.js&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.00MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&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 style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;ko.js&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1761956862381&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const ko = {
  //calendar
  calendar: {
    monthFull: [
      &quot; 1월&quot;,
      &quot; 2월&quot;,
      &quot; 3월&quot;,
      &quot; 4월&quot;,
      &quot; 5월&quot;,
      &quot; 6월&quot;,
      &quot; 7월&quot;,
      &quot; 8월&quot;,
      &quot; 9월&quot;,
      &quot;10월&quot;,
      &quot;11월&quot;,
      &quot;12월&quot;,
    ],
    monthShort: [
      &quot; 1월&quot;,
      &quot; 2월&quot;,
      &quot; 3월&quot;,
      &quot; 4월&quot;,
      &quot; 5월&quot;,
      &quot; 6월&quot;,
      &quot; 7월&quot;,
      &quot; 8월&quot;,
      &quot; 9월&quot;,
      &quot;10월&quot;,
      &quot;11월&quot;,
      &quot;12월&quot;,
    ],

    dayFull: [
      &quot;일요일&quot;,
      &quot;월요일&quot;,
      &quot;화요일&quot;,
      &quot;수요일&quot;,
      &quot;목요일&quot;,
      &quot;금요일&quot;,
      &quot;토요일&quot;,
    ],

    dayShort: [
      &quot;일요일&quot;,
      &quot;월요일&quot;,
      &quot;화요일&quot;,
      &quot;수요일&quot;,
      &quot;목요일&quot;,
      &quot;금요일&quot;,
      &quot;토요일&quot;,
    ],
    hours: &quot;시&quot;,
    minutes: &quot;분&quot;,
    done: &quot;완료&quot;,
    clear: &quot;클리어&quot;,
    today: &quot;오늘&quot;,
    am: [&quot;오전&quot;, &quot;오전&quot;],
    pm: [&quot;오후&quot;, &quot;오후&quot;],

    weekStart: 7,
    clockFormat: 24,
  },

  //core
  core: {
    ok: &quot;확인&quot;,
    cancel: &quot;취소&quot;,
    select: &quot;선택&quot;,
    &quot;No data&quot;: &quot;데어터 없음&quot;,
  },

  formats: {
    dateFormat: &quot;%Y-%m-%d&quot;,
    timeFormat: &quot;%H:%i&quot;,
  },

  lang: &quot;ko-KR&quot;,

  //Gantt
  gantt: {
    // Header / sidebar
    &quot;Task name&quot;: &quot;작업명&quot;,
    &quot;Start date&quot;: &quot;시작일&quot;,
    Duration: &quot;기간&quot;,
    Task: &quot;작업&quot;,
    Milestone: &quot;이정표&quot;,
    &quot;Summary task&quot;: &quot;작업 써머리&quot;,

    // Sidebar
    Save: &quot;저장&quot;,
    Delete: &quot;삭제&quot;,
    Name: &quot;이름&quot;,
    Description: &quot;설명&quot;,
    &quot;Select type&quot;: &quot;타입 선택&quot;,
    Type: &quot;타입&quot;,
    &quot;End date&quot;: &quot;종료일&quot;,
    Progress: &quot;진행율&quot;,
    Predecessors: &quot;이전 작업&quot;,
    Successors: &quot;이후 작업&quot;,
    &quot;Add task name&quot;: &quot;작업명 추가&quot;,
    &quot;Add description&quot;: &quot;설명 추가&quot;,
    &quot;Select link type&quot;: &quot;링크 타입 선택&quot;,
    &quot;End-to-start&quot;: &quot;끝과 시작&quot;,
    &quot;Start-to-start&quot;: &quot;시작과 시작&quot;,
    &quot;End-to-end&quot;: &quot;끝과 끝&quot;,
    &quot;Start-to-end&quot;: &quot;시작과 끝&quot;,

    // Context menu / toolbar
    Add: &quot;추가&quot;,
    &quot;Child task&quot;: &quot;하위..&quot;,
    &quot;Task above&quot;: &quot;위에..&quot;,
    &quot;Task below&quot;: &quot;아래..&quot;,
    &quot;Convert to&quot;: &quot;변환 to&quot;,
    Edit: &quot;수정&quot;,
    Cut: &quot;잘라내기&quot;,
    Copy: &quot;복사&quot;,
    Paste: &quot;붙여넣기&quot;,
    Move: &quot;이동&quot;,
    Up: &quot;위로&quot;,
    Down: &quot;아래로&quot;,
    Indent: &quot;안으로&quot;,
    Outdent: &quot;밖으로&quot;,
    &quot;Split task&quot;: &quot;작업 분리&quot;,

    // Toolbar
    &quot;New task&quot;: &quot;새 작업&quot;,
    &quot;Move up&quot;: &quot;위로 이동&quot;,
    &quot;Move down&quot;: &quot;아래도 이동&quot;,
  },
};

export default ko;&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;(역시 귀찮다면 다운받아서 src 폴더에 버려버리장.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/xRMCt/dJMcaiuQZot/t6kP4VCoU1gy3cwFwVwJpk/dummyData.js?attach=1&amp;amp;knm=tfile.js&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;dummyData.js&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.00MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&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 style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;dummyData.js&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1761957775346&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 작업
export const tasks = [
  {
    id: 20,
    text: &quot;회의록 작성&quot;,
    start: &quot;2025-11-11&quot;,
    end: &quot;2025-11-12&quot;,
    duration: 1,
    progress: 0,
    type: &quot;task&quot;,
  },
  {
    id: 47,
    text: &quot;REACT 프로젝트&quot;,
    start: &quot;2025-11-01&quot;,
    end: &quot;2025-11-20&quot;,
    duration: 20,
    progress: 5,
    parent: 0,
    type: &quot;summary&quot;,
    open: true,
  },
  {
    id: 22,
    text: &quot;라우터/레이아웃 설정&quot;,
    start: &quot;2025-11-03&quot;,
    end: &quot;2025-11-07&quot;,
    duration: 5,
    progress: 30,
    parent: 47,
    type: &quot;task&quot;,

  },
  {
    id: 21,
    text: &quot;신규 프로젝트 설계&quot;,
    start: &quot;2025-12-10&quot;,
    end: &quot;2025-12-13&quot;,
    duration: 3,
    progress: 0,
    type: &quot;task&quot;,
  },
  {
    id: 31,
    text: &quot;금수저 프로젝트&quot;,
    start: &quot;2025-12-20&quot;,
    end: &quot;2025-12-30&quot;,
    duration: 3,
    progress: 0,
    type: &quot;task&quot;,
  },

];
// 앞뒤 연결  e2e는 end to end의 의미
export const links = [{ id: 1, source: 20, target: 21, type: &quot;e2e&quot; }];
// 시간 스케일
export const scales = [
  { unit: &quot;month&quot;, step: 1, format: &quot;yyyy-MM&quot; },
  { unit: &quot;day&quot;, step: 1, format: &quot;d&quot; },
];

export const markers = [
  {
    start: new Date().setDate(new Date().getDate() + 2),
    text: &quot;모레&quot;,
  },
  {
    start: new Date().setDate(new Date().getDate() + 7),
    text: &quot;7일후&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;대략 준비가 되었으니, 핵심 바로 가져다 쓸 Base 샘플 코드를 맹글어보장&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/r54kA/dJMb99YY0UQ/AmKq1XmqmvoWv6aPTvvlh0/BaseGantt.jsx?attach=1&amp;amp;knm=tfile.jsx&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;BaseGantt.jsx&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.00MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&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 style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;BaseGantt.jsx&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1761966251899&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Locale } from &quot;@svar-ui/react-core&quot;;
import &quot;@svar-ui/react-gantt/all.css&quot;;
import {
  ContextMenu,
  Editor,
  Fullscreen,
  Gantt,
  Toolbar,
  Tooltip,
  WillowDark,
} from &quot;@svar-ui/react-gantt&quot;;
import ko from &quot;./ko&quot;;
import { tasks, links, scales, markers } from &quot;./dummyData&quot;;
import { useState } from &quot;react&quot;;

function BaseGantt() {
  const [api, setApi] = useState(null);

  const init = (api) =&amp;gt; {
    setApi(api);
  };

  return (
    &amp;lt;&amp;gt;
      &amp;lt;ContextMenu api={api}&amp;gt;
        &amp;lt;Fullscreen hotkey=&quot;ctrl+shift+f&quot;&amp;gt;
          &amp;lt;WillowDark&amp;gt;
            &amp;lt;Locale words={{ ...ko }}&amp;gt;
              &amp;lt;Tooltip api={api} /&amp;gt;
              &amp;lt;Toolbar api={api} /&amp;gt;
              &amp;lt;Gantt
                tasks={tasks}
                scales={scales}
                links={links}
                markers={markers}
                zoom
                init={init}
              /&amp;gt;
              {api &amp;amp;&amp;amp; &amp;lt;Editor api={api} /&amp;gt;}
            &amp;lt;/Locale&amp;gt;
          &amp;lt;/WillowDark&amp;gt;
        &amp;lt;/Fullscreen&amp;gt;
      &amp;lt;/ContextMenu&amp;gt;
    &amp;lt;/&amp;gt;
  );
}

export default BaseGantt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 이 소스에 svar gantt가 제공하는 기본 컴포넌트 구조가 모두 담겨있다고&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;ContextMenu&lt;/b&gt;&lt;/span&gt; 컴포넌트로 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;Gantt&lt;/b&gt;&lt;/span&gt;를 둘러싸면 마우스 오른쪽 컨텍스가 제공되고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;Fullscreen&lt;/b&gt;&lt;/span&gt; 컴포넌트로 둘러싸면 화면에&amp;nbsp; Fullscreen 전환 버튼이 나타나고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;Toollbar&lt;/b&gt;&lt;/span&gt;로 둘러싸면 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;Gantt&lt;/b&gt; &lt;/span&gt;위에 툴바가 나타나고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;Tooltip&lt;/b&gt;&lt;/span&gt;을 넣으면 tooltip이 지원되공&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;Locale&lt;/b&gt;&lt;/span&gt;로 둘러싸면 localization이 되는 식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트 프레임워크에서 바라는 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;컴포넌트 구조화&lt;/b&gt;&lt;/span&gt;가 잘 되어있는 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;모습&lt;/b&gt;&lt;/span&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;App.jsx&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1761966463556&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import BaseGantt from &quot;./BaseGantt&quot;;

function App() {

  return (
    &amp;lt;&amp;gt;
      &amp;lt;h1 style={titleStyle}&amp;gt;
        그냥 여기서 띠작할께용
      &amp;lt;/h1&amp;gt;
      &amp;lt;BaseGantt /&amp;gt;
    &amp;lt;/&amp;gt;
  );
}

// 그냥 스타일 정의 객체
const titleStyle = {
  textAlign: &quot;center&quot;,
  backgroundColor: &quot;blueviolet&quot;,
  color: &quot;white&quot;,
  marginBottom: 0,
}

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;gantt1.png&quot; data-origin-width=&quot;1272&quot; data-origin-height=&quot;552&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4ij5f/dJMcai9rYtz/YEafpxb4OjgINM5qBLVFPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4ij5f/dJMcai9rYtz/YEafpxb4OjgINM5qBLVFPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4ij5f/dJMcai9rYtz/YEafpxb4OjgINM5qBLVFPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4ij5f%2FdJMcai9rYtz%2FYEafpxb4OjgINM5qBLVFPk%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;1272&quot; height=&quot;552&quot; data-filename=&quot;gantt1.png&quot; data-origin-width=&quot;1272&quot; data-origin-height=&quot;552&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;새작업 버튼을 누르면 New Task가 추가되고 그걸 더블클릭하거낭&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;gantt2.png&quot; data-origin-width=&quot;1272&quot; data-origin-height=&quot;552&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxis1e/dJMb99YYoD4/if8qMsXQpszlD3KzQJXNf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxis1e/dJMb99YYoD4/if8qMsXQpszlD3KzQJXNf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxis1e/dJMb99YYoD4/if8qMsXQpszlD3KzQJXNf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbxis1e%2FdJMb99YYoD4%2Fif8qMsXQpszlD3KzQJXNf0%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;1272&quot; height=&quot;552&quot; data-filename=&quot;gantt2.png&quot; data-origin-width=&quot;1272&quot; data-origin-height=&quot;552&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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;gantt3.png&quot; data-origin-width=&quot;1272&quot; data-origin-height=&quot;552&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnYfMz/dJMcain4O45/jBKKmft9y1iFtj7zzw5C90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnYfMz/dJMcain4O45/jBKKmft9y1iFtj7zzw5C90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnYfMz/dJMcain4O45/jBKKmft9y1iFtj7zzw5C90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnYfMz%2FdJMcain4O45%2FjBKKmft9y1iFtj7zzw5C90%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;1272&quot; height=&quot;552&quot; data-filename=&quot;gantt3.png&quot; data-origin-width=&quot;1272&quot; data-origin-height=&quot;552&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;b&gt;Restful 테스트&lt;/b&gt;를 해보기 위해 아래 json-server를 구성한 폴더 압축 파일을 다운받자&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/cAAurP/dJMcaiuQZLA/kWT1gEHiwceQW75ttxv53k/json-gantt.zip?attach=1&amp;amp;knm=tfile.zip&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;json-gantt.zip&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.01MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&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; vscode로 폴더를 열어 터미널에 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;npm start&lt;/b&gt;&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;p data-ke-size=&quot;size16&quot;&gt;아래 파일은 axios를 이용하여 위 json-server에서 데이터를 가져와서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gantt 를그리는 샘플 소스를 포함하고 있다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/v2Vk2/dJMcacVHRAe/4FSTiy6vqGwsVugwSF6wUk/mk-gantt.zip?attach=1&amp;amp;knm=tfile.zip&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;mk-gantt.zip&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.04MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&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;압축 풀고, vscode로 열어서 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;npm i&amp;nbsp; 후 npm run dev&lt;/b&gt; &lt;/span&gt;하면 볼 수 있당.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;gantt-rest.png&quot; data-origin-width=&quot;1519&quot; data-origin-height=&quot;704&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPcJoG/dJMcae63IhV/rOqFQlcRErOgITYM9FTd00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPcJoG/dJMcae63IhV/rOqFQlcRErOgITYM9FTd00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPcJoG/dJMcae63IhV/rOqFQlcRErOgITYM9FTd00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPcJoG%2FdJMcae63IhV%2FrOqFQlcRErOgITYM9FTd00%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;1519&quot; height=&quot;704&quot; data-filename=&quot;gantt-rest.png&quot; data-origin-width=&quot;1519&quot; data-origin-height=&quot;704&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; 작업(task)에 대해서만 추가/수정/삭제 가 잘 동작함을&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인할 수 있고, 스타일도 살짝 넣었당(gantt.css 파일 참조).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나머진(link 등등) 아직 하지 않았당.&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;b&gt;JSON 서버는 id 속성을 무조건 문자열 처리해 버리고,&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SVAR GANTT는 문서에는 id가 number | string 으로 되어 있지만&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실제 number로 넘겨주지 않으면 에러가 발생하여 땜방이당&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring등으로 실제 Back-End를 구축한다면 간단히 해결될 문제당.&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;개인적인 결론은 svar gantt가 위와 같은 기본 기능이 다 장착되어 있는&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;Slider의 동작은 마구 이상한데... 그 원인을 내가 찾고 말았당.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;controlled Component 방식으로 사용한 것이 원인이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;uncontrolled 방식으로 사용하면&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; 기존 api.intercept가 나열된 소스 뒤에 추가 하면 된당.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(역시 흐름을 생각하니, 해결책이 보인당. 마구 기쁘당!~~ ㅋㅋ)&lt;/p&gt;
&lt;pre id=&quot;code_1762154562295&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    api.intercept(&quot;scroll-chart&quot;, (e) =&amp;gt; {
      console.log(&quot;check e&quot;, e);
      return false;  // 뒤에 따라 오는 처리를 하지 않겠음
    });&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;style3&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;&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;&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;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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=KNtJGQkC-WI&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=KNtJGQkC-WI&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=KNtJGQkC-WI&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/cBw1Kb/hyYxCS9MYL/cmwBNQV6dKVo0IulD0hWDk/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=540_164_720_360,https://scrap.kakaocdn.net/dn/c9WcPE/hyYyRPQPvu/zAKVLBo1bxYlRHA8soSLoK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=540_164_720_360&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;Ariana Grande - we can&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/KNtJGQkC-WI&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;</description>
      <category>React</category>
      <category>React</category>
      <category>react gantt</category>
      <category>svar gannt</category>
      <category>간트</category>
      <category>간트 차트</category>
      <category>리액트</category>
      <category>무료 gannt</category>
      <category>무료 간트</category>
      <category>사용법</category>
      <author>e7e</author>
      <guid isPermaLink="true">https://e-7-e.tistory.com/305</guid>
      <comments>https://e-7-e.tistory.com/305#entry305comment</comments>
      <pubDate>Mon, 3 Nov 2025 12:31:06 +0900</pubDate>
    </item>
    <item>
      <title>React With TypeScript 체킁1</title>
      <link>https://e-7-e.tistory.com/318</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;아무래도 때가 된 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 부터는 TypeScript로 리액트를 해보장.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Node&lt;/b&gt;와 &lt;b&gt;vscode&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 style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;TypeScript가 대략 먼지는?&lt;/b&gt;&lt;/span&gt; 아래 글을 읽고 오장 (싫다면 그러렴)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://e-7-e.tistory.com/278&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2025.01.13 - [자바스크립트] - TypeScript(타입스크립트) 넌 머냥?&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;b&gt;실무는 실체적 느끼미가 중요&lt;/b&gt;하당)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나의 경우 react-type1(꼭 소문자로)으로 새 폴더를 만들어서 열었당.&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;Terminal -&amp;gt; New Teriminal을 열고는&amp;nbsp; 아래 명령어를 치장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(Powershell 이 문제를 일으키면 그냥 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;cmd&lt;/b&gt;&lt;/span&gt;라고 치고 다시 한당)&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;npx create-vite@latest .&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&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;step1.png&quot; data-origin-width=&quot;523&quot; data-origin-height=&quot;474&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFxcw9/dJMb9PGooSD/QeRfMWt7uSKTxyeKlGC5i0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFxcw9/dJMb9PGooSD/QeRfMWt7uSKTxyeKlGC5i0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFxcw9/dJMb9PGooSD/QeRfMWt7uSKTxyeKlGC5i0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFxcw9%2FdJMb9PGooSD%2FQeRfMWt7uSKTxyeKlGC5i0%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;523&quot; height=&quot;474&quot; data-filename=&quot;step1.png&quot; data-origin-width=&quot;523&quot; data-origin-height=&quot;474&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;b&gt;rolldown-vite&lt;/b&gt;는 Experimental(실험적) 이라고 나오는 데, vite에서 적용예정으로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;rust 언어로 만들어진 새로운 bundler&lt;/b&gt;&lt;/span&gt;(여러 흩어진 파일을 하나로 묶어주는)로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 성능이 기존 bundler 보다 참으로 월등하다고 전해진당&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;step2.png&quot; data-origin-width=&quot;713&quot; data-origin-height=&quot;620&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cQ52jq/dJMb9NhvhLa/SeLqZ3EL2gYxPZd8I405T1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cQ52jq/dJMb9NhvhLa/SeLqZ3EL2gYxPZd8I405T1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cQ52jq/dJMb9NhvhLa/SeLqZ3EL2gYxPZd8I405T1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcQ52jq%2FdJMb9NhvhLa%2FSeLqZ3EL2gYxPZd8I405T1%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;713&quot; height=&quot;620&quot; data-filename=&quot;step2.png&quot; data-origin-width=&quot;713&quot; data-origin-height=&quot;620&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영문자 o를 치고 엔터를 치면 브라우져가 열릴거시당. (이것은 이미 아는 거당)&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와 .tsx가 누네띤당)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tsconfig.json 파일도 누네띠는데 열어보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tsconfig.app.json과 tsconfig.node.json이 그냥 이해된당&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;filelist.png&quot; data-origin-width=&quot;497&quot; data-origin-height=&quot;777&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5BXWW/dJMb84DubBM/DWJ1IRzFvDvpzxqtD5kqJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5BXWW/dJMb84DubBM/DWJ1IRzFvDvpzxqtD5kqJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5BXWW/dJMb84DubBM/DWJ1IRzFvDvpzxqtD5kqJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5BXWW%2FdJMb84DubBM%2FDWJ1IRzFvDvpzxqtD5kqJK%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;497&quot; height=&quot;777&quot; data-filename=&quot;filelist.png&quot; data-origin-width=&quot;497&quot; data-origin-height=&quot;777&quot;/&gt;&lt;/span&gt;&lt;/figure&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;assets 폴더와 App.css 파일은 삭제, index.css 파일은 내용은 지우고 껍데기만 남긴당.&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: #f89009;&quot;&gt;&lt;b&gt;main.tsx &lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;는 아래처럼 바꾼당.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1761046221496&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { createRoot } from &quot;react-dom/client&quot;;
import &quot;./index.css&quot;;
import App from &quot;./App.tsx&quot;;

// 아래 라인에 보이는 !는 null 아님을 보증한다는 의미당  희안?
createRoot(document.getElementById(&quot;root&quot;)!).render(&amp;lt;App /&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;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;App.tsx &lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;는 아래처럼 바꾼당.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1761046367159&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function App() {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;h1&amp;gt;너 TypeScript랑 친해?&amp;lt;/h1&amp;gt;
    &amp;lt;/&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;result1.png&quot; data-origin-width=&quot;636&quot; data-origin-height=&quot;287&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9ega5/dJMb89dIVdB/BXZSBTVd6wheThXTCL66Ik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9ega5/dJMb89dIVdB/BXZSBTVd6wheThXTCL66Ik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9ega5/dJMb89dIVdB/BXZSBTVd6wheThXTCL66Ik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9ega5%2FdJMb89dIVdB%2FBXZSBTVd6wheThXTCL66Ik%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;636&quot; height=&quot;287&quot; data-filename=&quot;result1.png&quot; data-origin-width=&quot;636&quot; data-origin-height=&quot;287&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;이제 어느정도 준비가 된 것 같지만, 기본 alert는 예쁘지 않으닝&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본으로 sweetAlert2도 미리 준비하도록 하장. 터미널에 아래처럼 입력한당.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;npm i sweetalert2&amp;nbsp; sweetalert2-react-content&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;E7ESwal.tsx &lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;을&amp;nbsp; src 폴더에 아래내용으로 만들장&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1761049355925&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import withReactContent from &quot;sweetalert2-react-content&quot;;
import Swal from &quot;sweetalert2&quot;;
 
const E7ESwal = withReactContent(Swal);
export default E7ESwal;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;준비 끝 (준비 작업도 참 길당)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 은근 꽤나 많이 자주 질문 받는 &lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;form안에 submit 버튼이 여러개 있을 때&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;어느 submit버튼을 눌렀는지를 알아내는 걸&lt;/b&gt; &lt;/span&gt;해보장.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(typescript를 쓰면 추가적인 질문이 발생하게 된당.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;FormSample.tsx &lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;를 src 폴더에 아래처럼 맹글장&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1761050037876&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import type { FormEvent } from &quot;react&quot;;
import E7ESwal from &quot;./E7ESwal&quot;;

function FormSample() {
  const handleSubmit = (e: FormEvent&amp;lt;HTMLFormElement&amp;gt;) =&amp;gt; {
    e.preventDefault();
    const nativeEvent = e.nativeEvent as SubmitEvent; // 형변환을 해야 submitter가 나옴!
    console.log(nativeEvent.submitter);               // 어느 버튼이 submit을 발생시켰는지 안당.
    const clickedButton = nativeEvent.submitter;
    console.log(clickedButton?.innerHTML);            // ? 알디용
    if (clickedButton?.innerHTML === &quot;눌러방&quot;) {      // innerText를 써도 된당.
      E7ESwal.fire(&quot;눌러방 눌렀네엥&quot;);
    } else {
      E7ESwal.fire(&quot;누를깡 눌렀구망&quot;);
    }
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;form onSubmit={handleSubmit}&amp;gt;
        &amp;lt;button&amp;gt;눌러방&amp;lt;/button&amp;gt;
        &amp;lt;br /&amp;gt;
        &amp;lt;button&amp;gt;누를깡&amp;lt;/button&amp;gt;
        &amp;lt;br /&amp;gt;
      &amp;lt;/form&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default FormSample;&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;뽀인트는 form이벤트의 &lt;b&gt;submitter에 어느 버튼이 submit을 발생시켰는지가 담겼는데&lt;/b&gt;..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Typescript에선 아래처럼 형변환을 해줘야 submitter를 읽을 수 있당.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;const nativeEvent = e.nativeEvent as SubmitEvent&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;e는 진짜 이벤트를 품고 있는 가짜 이벤트고(멋진 말로 syntheticeven라 한당)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;e.nativeEvent가 진짜 이벤트당!&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: #f89009;&quot;&gt;&lt;b&gt;App.tsx &lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;를 아래처럼 수정하장&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1761050062948&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import FormSample from &quot;./FormSample&quot;;

function App() {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;h1&amp;gt;너 TypeScript랑 친해?&amp;lt;/h1&amp;gt;
      &amp;lt;FormSample /&amp;gt;
    &amp;lt;/&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 React에선 거의 사용되진 않지만(왜?) 일반 html이나 jsp라면&amp;nbsp; submit 버튼의&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;form관련 속성을 이용해서 쉽게 버튼 구분 필요없이 원하는 action을 취할 수 있당.&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;formtest.htm&lt;/b&gt;&lt;/span&gt;l을 public 폴더에 아래처럼 맹글고&lt;/p&gt;
&lt;pre id=&quot;code_1761096908189&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
    &amp;lt;title&amp;gt;Document&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;form&amp;gt;
    &amp;lt;input type=&quot;text&quot; name=&quot;myname&quot; value=&quot;E7E&quot;&amp;gt;&amp;lt;br/&amp;gt;
    &amp;lt;button formaction=&quot;km.html&quot;&amp;gt;경미닝&amp;lt;/button&amp;gt;&amp;lt;br/&amp;gt;
    &amp;lt;button formaction=&quot;mk.html&quot;&amp;gt;밍겨닝&amp;lt;/button&amp;gt;&amp;lt;br/&amp;gt;
    &amp;lt;button formaction=&quot;yw.html&quot;&amp;gt;예워닝&amp;lt;/button&amp;gt;&amp;lt;br/&amp;gt;
    &amp;lt;button formaction=&quot;ksj.html&quot;&amp;gt;금수저닝&amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;    
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;주소 표시줄에 http://localhost:5173/formtest.html 를 입력해서 결과를 확인해 보장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(port 번호는 본인에 맞게, 나의 경우 5173 이당.)&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;useRef Hook을 이용&lt;/b&gt;해서도 submit 버튼을 구분하는 걸 한번 해보장.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;FormSample.tsx&lt;/b&gt;&lt;/span&gt; 를 아래 처럼 고쳐보장.&lt;/p&gt;
&lt;pre id=&quot;code_1761090334502&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useRef, type FormEvent } from &quot;react&quot;;
import E7ESwal from &quot;./E7ESwal&quot;;

function FormSample() {
  const myForm = useRef(null);

  const handleSubmit = (e: FormEvent&amp;lt;HTMLFormElement&amp;gt;) =&amp;gt; {
    e.preventDefault();
    const nativeEvent = e.nativeEvent as SubmitEvent; // 형변환을 해야 submitter가 나옴!
    console.log(nativeEvent.submitter);
    const clickedButton = nativeEvent.submitter!;
    const eForm = myForm.current!;

    if (clickedButton !== eForm[&quot;ksj&quot;]) {
      E7ESwal.fire(&quot;눌러방 눌렀네엥&quot;);
    } else {
      E7ESwal.fire(&quot;누를깡 눌렀구망&quot;);
    }
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;form ref={myForm} onSubmit={handleSubmit}&amp;gt;
        &amp;lt;button name=&quot;smk&quot;&amp;gt;눌러방&amp;lt;/button&amp;gt;
        &amp;lt;br /&amp;gt;
        &amp;lt;button name=&quot;ksj&quot;&amp;gt;누를깡&amp;lt;/button&amp;gt;
        &amp;lt;br /&amp;gt;
      &amp;lt;/form&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default FormSample;&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;result2.png&quot; data-origin-width=&quot;665&quot; data-origin-height=&quot;589&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d3Zr6a/dJMb9QrLlFC/s3WeQWaVEr9V629zReP2lK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d3Zr6a/dJMb9QrLlFC/s3WeQWaVEr9V629zReP2lK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d3Zr6a/dJMb9QrLlFC/s3WeQWaVEr9V629zReP2lK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd3Zr6a%2FdJMb9QrLlFC%2Fs3WeQWaVEr9V629zReP2lK%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;665&quot; height=&quot;589&quot; data-filename=&quot;result2.png&quot; data-origin-width=&quot;665&quot; data-origin-height=&quot;589&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;form하면 거져 생각나는 라이브러리 react-hook-form을 한번&amp;nbsp; 사용해보도록 하장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a title=&quot;react-hook-form&quot; href=&quot;https://www.npmjs.com/package/react-hook-form&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.npmjs.com/package/react-hook-form&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;먼저 지레 설치지 않도록 설칭, Terminal에&amp;nbsp; 아래처럼 입력&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;npm i react-hook-form&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;HookFormSample.tsx&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 를 src 폴더에 맹글장.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1761094064935&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useForm } from &quot;react-hook-form&quot;;

function HookFormSample() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm();

  return (
    &amp;lt;div style={{ border: &quot;5px groove blue&quot;, marginBottom: 10 }}&amp;gt;
      &amp;lt;form onSubmit={handleSubmit((data) =&amp;gt; console.log(data))}&amp;gt;
        이름:
        &amp;lt;input
          autoComplete={&quot;off&quot;}
          {...register(&quot;name&quot;, { required: true, minLength: 2 })}
        /&amp;gt;
        &amp;lt;br /&amp;gt;
        나이:
        &amp;lt;input
          {...register(&quot;age&quot;, {
            required: true,
            pattern: /\d{2}/,
            min: 20,
            max: 99,
          })}
        /&amp;gt;
        &amp;lt;br /&amp;gt;
        별명:
        &amp;lt;input {...register(&quot;alias&quot;)} /&amp;gt;
        &amp;lt;br /&amp;gt;
        {errors.name &amp;amp;&amp;amp; &amp;lt;p style={{ color: &quot;red&quot; }}&amp;gt; 이름 제대로 써달라공&amp;lt;/p&amp;gt;}
        {errors.age &amp;amp;&amp;amp; &amp;lt;p style={{ color: &quot;red&quot; }}&amp;gt; 이거 나이 맞엉?&amp;lt;/p&amp;gt;}
        &amp;lt;button style={{ color: &quot;magenta&quot; }}&amp;gt;체킁&amp;lt;/button&amp;gt;
      &amp;lt;/form&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default HookFormSample;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;뽀인또는 useForm 훅&lt;/b&gt;&lt;/span&gt;인디, 개인적인 생각에도 아주 잘 만들었당.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자들의 form 사용 패턴을 잘 분석했다는 느끼미가 다가서는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java Spring을&amp;nbsp; 해본 사람이라면 &lt;b&gt;Spring 서버 태그인 &amp;lt;form:form&amp;gt;&amp;nbsp; 태그와&lt;/b&gt;&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;useForm 훅 함수가 return 해주는 register, handleSubmit, formState 3개만&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용하면 쉽게 form에 유효성 검사와 최종 form이 가진 값들을 덩어리로&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용&lt;/b&gt;할 수 있당.&amp;nbsp; formState에서는 erros 객체만 구조분해롱 꺼냈당.&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;App.tsx&lt;/b&gt;&lt;/span&gt; 를 아래처럼 수정하장.&lt;/p&gt;
&lt;pre id=&quot;code_1761094209215&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import FormSample from &quot;./FormSample&quot;;
import HookFormSample from &quot;./HookFormSample&quot;;

function App() {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;h1&amp;gt;너 TypeScript랑 친해?&amp;lt;/h1&amp;gt;
      &amp;lt;HookFormSample /&amp;gt;
      &amp;lt;FormSample /&amp;gt;
    &amp;lt;/&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;result3.png&quot; data-origin-width=&quot;918&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/befPcG/dJMb9cuKGzI/rZHvhhXbnrRpIVCA6YquH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/befPcG/dJMb9cuKGzI/rZHvhhXbnrRpIVCA6YquH0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/befPcG/dJMb9cuKGzI/rZHvhhXbnrRpIVCA6YquH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbefPcG%2FdJMb9cuKGzI%2FrZHvhhXbnrRpIVCA6YquH0%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;918&quot; height=&quot;640&quot; data-filename=&quot;result3.png&quot; data-origin-width=&quot;918&quot; data-origin-height=&quot;640&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TypeScript를 쓴다&lt;/b&gt;고 해서 엄청나게 많은 코드 변화를 감당해야 하는 게 아니라는 것!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그저 lint(훈수꾼)이 밑줄 그어주는 부분에만 &lt;b&gt;조금씩 대응해 주면 된다&lt;/b&gt;는 것!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그저 사용하다 보면 자연스럽게 TypeScript 스타일도 품을 수 있게 될꺼이당.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;천천히&lt;/b&gt; 그저 가면 된당.&amp;nbsp;&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;style3&quot; /&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;&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;p data-ke-size=&quot;size16&quot;&gt;도박이 해석하는 인간은, 사람은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;승리, 약탈, 소유, 우위,지배를 재밌다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반복되는 로직!, 반복에 반복인 코딩과 도박하자.&lt;/p&gt;
&lt;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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성의없이 의무로 반복된 kiss는&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;식지않는 kiss, sunkiss를 기다린당.&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://www.youtube.com/watch?v=5PS2cJsSJrI&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=5PS2cJsSJrI&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=5PS2cJsSJrI&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/kb2cy/hyZL8xhHbV/7KVX8RbgtlthvJ3KeTL0EK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=312_168_576_456,https://scrap.kakaocdn.net/dn/vb3TM/hyZL6M0fvm/O0RXp05v8VaiAytTXkkKhk/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=312_168_576_456&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;WENDY (웬디) &quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/5PS2cJsSJrI&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>React</category>
      <category>form</category>
      <category>React</category>
      <category>submit</category>
      <category>submit 여러개</category>
      <category>submitter</category>
      <category>Typescript</category>
      <category>리액트</category>
      <category>타입스크립트</category>
      <author>e7e</author>
      <guid isPermaLink="true">https://e-7-e.tistory.com/318</guid>
      <comments>https://e-7-e.tistory.com/318#entry318comment</comments>
      <pubDate>Tue, 21 Oct 2025 20:03:14 +0900</pubDate>
    </item>
  </channel>
</rss>