<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Code log</title>
    <link>https://daniellee09.tistory.com/</link>
    <description>개발일지</description>
    <language>ko</language>
    <pubDate>Mon, 5 Oct 2026 13:11:09 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>danieLee</managingEditor>
    <image>
      <title>Code log</title>
      <url>https://tistory1.daumcdn.net/tistory/7466409/attach/6dc7fff659f24d68accdf03c193e5444</url>
      <link>https://daniellee09.tistory.com</link>
    </image>
    <item>
      <title>[AI] LangChain이란?</title>
      <link>https://daniellee09.tistory.com/84</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;생성형 AI를 활용한 서비스가 많아지면서 단순히 GPT와 대화하는 것을 넘어, 문서를 검색하거나 외부 API를 호출하고 데이터베이스와 연결하는 형태의 AI 서비스가 늘어나고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 LLM을 실제 애플리케이션에 연결할 때 자주 사용되는 프레임워크가 바로 &lt;b&gt;LangChain&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;오늘은 이 LangChain이 어떤 것인지 간단히 알아보도록 하자.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c68f56/dJMcah63yGm/LnIZeCvqLLfo0hRjkxoauk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c68f56/dJMcah63yGm/LnIZeCvqLLfo0hRjkxoauk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c68f56/dJMcah63yGm/LnIZeCvqLLfo0hRjkxoauk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc68f56%2FdJMcah63yGm%2FLnIZeCvqLLfo0hRjkxoauk%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;506&quot; height=&quot;506&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&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;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;LangChain이란?&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdWlJu/dJMcadDhBpq/LAlgLnZA24KEqVUCbOzhTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdWlJu/dJMcadDhBpq/LAlgLnZA24KEqVUCbOzhTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdWlJu/dJMcadDhBpq/LAlgLnZA24KEqVUCbOzhTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdWlJu%2FdJMcadDhBpq%2FLAlgLnZA24KEqVUCbOzhTk%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;689&quot; height=&quot;459&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LangChain은 &lt;b&gt;LLM을 활용한 애플리케이션을 쉽게 개발할 수 있도록 도와주는 오픈소스 프레임워크&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenAI의 GPT나 Anthropic의 Claude처럼 직접 언어 모델을 제공하는 것은 아니다. 대신 다양한 LLM과 프롬프트, 문서, 데이터베이스, 외부 API 등을 하나의 흐름으로 연결하는 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 단순한 LLM 서비스는 다음과 같은 구조를 가진다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;사용자 질문
    &amp;darr;
LLM
    &amp;darr;
답변
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 실제 서비스를 만들다 보면 LLM만 호출해서는 해결하기 어려운 경우가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 사내 규정 문서를 기반으로 답변하는 AI 서비스를 만든다고 생각해보자.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;사용자 질문
    &amp;darr;
관련 문서 검색
    &amp;darr;
필요한 내용 추출
    &amp;darr;
프롬프트 구성
    &amp;darr;
LLM 호출
    &amp;darr;
최종 답변
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LangChain은 이러한 여러 단계를 연결하고 관리할 수 있도록 도와준다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 이름이 LangChain일까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LangChain이라는 이름은 크게 두 단어로 나눌 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Lang&lt;/b&gt; : Language Model&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Chain&lt;/b&gt; : 여러 작업을 연결한 흐름&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 언어 모델을 중심으로 여러 작업을 체인처럼 연결한다는 의미다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 흐름을 하나의 Chain으로 구성할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;문서 검색 &amp;rarr; 프롬프트 생성 &amp;rarr; LLM 호출 &amp;rarr; 결과 반환
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자는 각각의 기능을 따로 구현하는 것보다 LangChain이 제공하는 구성 요소를 이용해 전체 흐름을 보다 체계적으로 만들 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;LangChain의 주요 구성 요소&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LangChain을 이해하려면 몇 가지 핵심 개념을 알아두는 것이 좋다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Model&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GPT, Claude와 같은 LLM을 호출하는 부분이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LangChain 자체가 AI 모델은 아니기 때문에 실제 답변을 생성하려면 외부 LLM을 연결해야 한다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;LangChain
    &amp;darr;
OpenAI GPT
Anthropic Claude
기타 LLM
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LangChain은 여러 모델을 비슷한 방식으로 연결할 수 있도록 인터페이스를 제공한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Prompt&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Prompt는 LLM에게 전달하는 명령이나 질문의 형식을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같이 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;너는 금융 상담 AI다.

다음 자료를 참고해서 질문에 답하라.

자료:
{context}

질문:
{question}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 {context}와 {question}에 실제 데이터를 넣어 최종 프롬프트를 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 프롬프트를 템플릿 형태로 관리할 수 있다는 것도 LangChain의 특징 중 하나다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Document와 Retriever&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM이 외부 문서를 참고하도록 만들 때 사용되는 요소다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 PDF나 사내 문서에서 사용자의 질문과 관련된 부분을 찾아야 한다면 Retriever를 이용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;사용자 질문
    &amp;darr;
Retriever
    &amp;darr;
관련 문서 검색
    &amp;darr;
LLM
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조는 RAG를 구현할 때 특히 많이 사용된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Tool&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tool은 LLM이 외부 기능을 사용할 수 있도록 연결하는 기능이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 기능을 Tool로 만들 수 있다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;날씨 API
계산기
데이터베이스 조회
웹 검색
사내 API
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM이 단순히 자신의 학습 데이터만을 이용하는 것이 아니라 실제 외부 시스템과 상호작용할 수 있게 되는 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Chain&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Chain은 여러 작업을 정해진 순서로 연결한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 Chain을 구성할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;사용자 질문
    &amp;darr;
문서 검색
    &amp;darr;
프롬프트 생성
    &amp;darr;
LLM 호출
    &amp;darr;
답변 반환
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 단계가 미리 정해진 순서대로 실행된다는 것이 특징이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Agent&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Agent는 Chain보다 한 단계 더 발전된 개념이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Chain은 개발자가 미리 처리 순서를 정하지만, Agent는 &lt;b&gt;LLM이 현재 상황을 보고 어떤 Tool을 사용할지 판단할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 사용자가 다음과 같이 질문했다고 하자.&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;서울의 현재 날씨를 확인해서
비가 오면 우산을 가져가야 하는지 알려줘.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Agent는 다음과 같이 동작할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;사용자 질문
    &amp;darr;
LLM 판단
    &amp;darr;
날씨 정보가 필요함
    &amp;darr;
Weather Tool 호출
    &amp;darr;
결과 확인
    &amp;darr;
최종 답변 생성
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉,&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;Chain = 정해진 순서대로 실행

Agent = LLM이 다음 행동을 판단
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이라고 이해하면 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;LangChain과 RAG&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LangChain을 공부할 때 가장 자주 등장하는 개념 중 하나가 &lt;b&gt;RAG(Retrieval-Augmented Generation)&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG는 LLM이 외부 문서를 검색한 뒤 그 정보를 참고하여 답변하도록 만드는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 구조는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;문서
 &amp;darr;
텍스트 분할
 &amp;darr;
Embedding
 &amp;darr;
Vector DB
 &amp;darr;
Retriever
 &amp;darr;
관련 문서 검색
 &amp;darr;
LLM
 &amp;darr;
답변
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 회사의 규정 PDF를 Vector DB에 저장해 두고 사용자가 질문하면 질문과 관련된 문서를 검색해 LLM에게 전달할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 LLM이 학습하지 않은 최신 정보나 내부 데이터도 활용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LangChain은 이러한 RAG 구조를 구현하는 데 필요한 여러 기능을 제공하기 때문에 RAG 개발에서 자주 사용된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;LangChain과 Agent&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근에는 LangChain을 단순한 RAG 프레임워크보다 &lt;b&gt;AI Agent 개발을 위한 도구&lt;/b&gt;로 사용하는 경우도 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 LLM은 질문에 답변을 생성하는 것이 주된 역할이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 Agent는 상황에 따라 외부 도구를 사용하며 작업을 수행할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;사용자
  &amp;darr;
Agent
  &amp;darr;
LLM
 ├─ 웹 검색
 ├─ 데이터베이스
 ├─ 계산기
 └─ 외부 API
  &amp;darr;
최종 결과
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 구조를 사용하면 단순한 챗봇을 넘어 여러 작업을 수행하는 AI 시스템을 만들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복잡한 Agent의 상태 관리나 분기, 반복 처리가 필요한 경우에는 LangChain 생태계의 &lt;b&gt;LangGraph&lt;/b&gt;도 함께 사용된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;LangChain은 무료일까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LangChain 자체는 오픈소스 프레임워크이기 때문에 직접 설치해서 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 LangChain과 연결하는 모든 서비스가 무료인 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 서비스에는 별도의 비용이 발생할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;OpenAI API
Claude API
Vector Database
Cloud 서비스
외부 API
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;LangChain = 애플리케이션을 구성하는 프레임워크

LLM API = 실제 AI 모델을 사용하는 서비스
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로 구분해서 이해해야 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;LangChain과 Spring AI의 차이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java와 Spring을 사용해본 개발자라면 LangChain과 Spring AI를 비교하면 이해하기 쉽다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt; LangChain &lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt; Spring AI &lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python / JavaScript 중심&lt;/td&gt;
&lt;td&gt;Java / Spring 중심&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM 애플리케이션 개발 프레임워크&lt;/td&gt;
&lt;td&gt;Spring 기반 AI 개발 프레임워크&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prompt&lt;/td&gt;
&lt;td&gt;Prompt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retriever&lt;/td&gt;
&lt;td&gt;VectorStore / Retrieval&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tool&lt;/td&gt;
&lt;td&gt;Tool / Function Calling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent&lt;/td&gt;
&lt;td&gt;Agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;다양한 LLM 연결&lt;/td&gt;
&lt;td&gt;다양한 LLM 연결&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 프레임워크 모두 LLM과 외부 시스템을 연결해 실제 AI 서비스를 만드는 것이 목적이라는 점에서는 비슷하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 LangChain은 Python 기반 AI 생태계에서 많이 사용되고, Spring AI는 Spring 애플리케이션에 AI 기능을 자연스럽게 통합하는 데 초점을 두고 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LangChain은 GPT 같은 LLM 자체가 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;LLM과 프롬프트, 문서, 데이터베이스, 외부 API, Tool 등을 연결해 하나의 AI 애플리케이션을 구성할 수 있도록 도와주는 오픈소스 프레임워크&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체적인 구조를 간단하게 표현하면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;                ┌─ 문서 / Vector DB
                │
사용자 &amp;rarr; LangChain ─ LLM
                │
                ├─ 외부 API
                │
                └─ Tool
                     &amp;darr;
                   답변
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LangChain을 공부한다면 다음 순서로 개념을 익히는 것이 좋다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;LangChain
   &amp;darr;
Prompt / Model
   &amp;darr;
Embedding
   &amp;darr;
Vector DB
   &amp;darr;
RAG
   &amp;darr;
Retriever
   &amp;darr;
Tool
   &amp;darr;
Agent
   &amp;darr;
LangGraph
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;LLM을 다른 데이터와 기능에 연결해주는 프레임워크&lt;/b&gt;라는 핵심 개념부터 잡는 것이 중요하다.&lt;/p&gt;</description>
      <category>개발/AI</category>
      <category>Ai</category>
      <category>LangChain</category>
      <category>llm</category>
      <category>개념</category>
      <category>개발</category>
      <category>랭체인</category>
      <category>프레임워크</category>
      <author>danieLee</author>
      <guid isPermaLink="true">https://daniellee09.tistory.com/84</guid>
      <comments>https://daniellee09.tistory.com/84#entry84comment</comments>
      <pubDate>Sun, 13 Sep 2026 15:26:14 +0900</pubDate>
    </item>
    <item>
      <title>[AI] RAG란? - 검색 증강 생성의 개념과 동작 원리 이해하기</title>
      <link>https://daniellee09.tistory.com/83</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;ChatGPT와 같은 LLM은 방대한 데이터를 학습하기 때문에 다양한 질문에 답할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 LLM이라고 해서 모든 정보를 알고 있는 것은 아니다.&lt;/p&gt;
&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 class=&quot;erlang&quot;&gt;&lt;code&gt;우리 회사의 휴가 규정은 어떻게 돼?

지난주에 변경된 서비스 정책을 알려줘.

사내 개발 가이드에서 API Naming Rule을 찾아줘.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 정보는 LLM이 학습할 때 존재하지 않았거나, 외부에 공개되지 않은 정보일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 LLM은 답을 정확히 모르는 상황에서도 그럴듯한 내용을 만들어낼 수 있다.&lt;/p&gt;
&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 class=&quot;properties&quot;&gt;&lt;code&gt;Knowledge Cutoff
&amp;rarr; 학습 이후의 최신 정보를 모름

Lack of Context
&amp;rarr; 회사 내부 문서나 특정 도메인 정보를 모름

Hallucination
&amp;rarr; 사실이 아닌 내용을 그럴듯하게 생성할 수 있음&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 LLM과 실제 데이터 사이의 간극을 줄이기 위해 사용하는 대표적인 방법이 &lt;b&gt;RAG&lt;/b&gt;다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1254&quot; data-origin-height=&quot;1254&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6N1ej/dJMcagmqSep/CEC1ynu9HXU0x3kPcWPFFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6N1ej/dJMcagmqSep/CEC1ynu9HXU0x3kPcWPFFK/img.png&quot; data-alt=&quot;RAG&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6N1ej/dJMcagmqSep/CEC1ynu9HXU0x3kPcWPFFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6N1ej%2FdJMcagmqSep%2FCEC1ynu9HXU0x3kPcWPFFK%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;579&quot; height=&quot;579&quot; data-origin-width=&quot;1254&quot; data-origin-height=&quot;1254&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;RAG&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;RAG란?&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/diJvIw/dJMb991ZCQQ/5lAQiHX0wIWieKalzJRkEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/diJvIw/dJMb991ZCQQ/5lAQiHX0wIWieKalzJRkEk/img.png&quot; data-alt=&quot;RAG 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/diJvIw/dJMb991ZCQQ/5lAQiHX0wIWieKalzJRkEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdiJvIw%2FdJMb991ZCQQ%2F5lAQiHX0wIWieKalzJRkEk%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;715&quot; height=&quot;536&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;RAG 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG는 다음의 약자다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;Retrieval-Augmented Generation

Retrieval
&amp;rarr; 검색

Augmented
&amp;rarr; 증강, 보강

Generation
&amp;rarr; 생성&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 문장으로 정리하면 다음과 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;RAG는 사용자의 질문과 관련된 외부 정보를 먼저 검색한 뒤, 검색된 정보를 LLM에게 함께 제공하여 답변을 생성하는 방식이다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체적인 구조는 매우 단순하다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;사용자 질문
     │
     ▼
관련 정보 검색
Retrieval
     │
     ▼
검색 결과를 질문에 추가
Augmentation
     │
     ▼
LLM
     │
     ▼
답변 생성
Generation&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 LLM에게 바로 질문하지 않고, &lt;b&gt;답변에 필요한 자료를 먼저 찾아준 다음 질문과 함께 전달한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG의 Runtime 과정 역시 Retrieve &amp;rarr; Augment &amp;rarr; Generate 흐름으로 구성된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;RAG를 쉽게 이해해보자&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG를 &lt;b&gt;오픈북 시험&lt;/b&gt;에 비유해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 LLM은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;시험 문제
   &amp;darr;
학생의 기억
   &amp;darr;
답변&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학생이 &lt;u&gt;예전에 공부한 내용만&lt;/u&gt; 가지고 답을 작성하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 RAG는 오픈북 시험과 비슷하다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;시험 문제
   &amp;darr;
책에서 관련 내용 검색
   &amp;darr;
관련 페이지 확인
   &amp;darr;
문제 + 참고 자료
   &amp;darr;
답변 작성&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM이 모든 내용을 기억하고 있을 필요가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요한 순간에 &lt;u&gt;외부 데이터&lt;/u&gt;에서 관련 정보를 찾아서 LLM에게 제공하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 RAG에서 LLM의 역할과 검색 시스템의 역할은 다르다.&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;검색 시스템
&amp;rarr; 필요한 지식을 찾아준다.

LLM
&amp;rarr; 찾아온 지식을 이용해서 답변을 만든다.&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;RAG에서 가장 중요한 개념&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG를 이해하려면 다음 요소들의 관계를 먼저 이해해야 한다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;Document

Chunk

Embedding

Vector

Vector DB

Similarity Search

Prompt Context

LLM&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체적으로 연결하면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;gauss&quot;&gt;&lt;code&gt;Document
   │
   ▼
Chunk
   │
   ▼
Embedding
   │
   ▼
Vector
   │
   ▼
Vector DB


사용자 질문
   │
   ▼
Embedding
   │
   ▼
Query Vector
   │
   ▼
Vector DB 검색
   │
   ▼
관련 Chunk
   │
   ▼
Prompt Context
   │
   ▼
LLM&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 RAG는 &lt;b&gt;문서를 잘 저장하고, 질문과 관련된 문서를 잘 찾아서, LLM에게 잘 전달하는 기술&lt;/b&gt;이라고 볼 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;RAG는 크게 두 과정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG는 하나의 과정처럼 보이지만 실제로는 크게 두 단계로 나누어 생각할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;gradle&quot;&gt;&lt;code&gt;RAG

├─ Offline
│
│  문서를 미리 가공하고 저장
│
└─ Runtime

   질문이 들어왔을 때
   관련 문서를 검색하고 답변 생성&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자료에서도 외부 데이터를 DocumentReader &amp;rarr; DocumentTransformer &amp;rarr; DocumentWriter로 처리하는 Offline 과정과, 실제 질문에 대해 Retrieve &amp;rarr; Augment &amp;rarr; Generate하는 Runtime 과정을 구분하고 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Offline 과정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Offline은 &lt;b&gt;RAG가 사용할 지식을 미리 준비하는 과정&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 회사 문서가 있다고 해보자.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;사내규정.pdf
개발가이드.pdf
보안정책.docx
서비스매뉴얼.txt&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 파일을 그대로 검색하는 것이 아니라 RAG가 검색하기 좋은 형태로 가공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 과정은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;gauss&quot;&gt;&lt;code&gt;원본 Document
      │
      ▼
텍스트 추출
      │
      ▼
문서 분할
Chunking
      │
      ▼
Embedding
      │
      ▼
Vector
      │
      ▼
Vector DB 저장&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정은 일반적으로 다음과 같은 ETL 형태로 볼 수 있다.&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;Extract
&amp;rarr; 데이터를 읽는다.

Transform
&amp;rarr; 검색하기 좋은 형태로 가공한다.

Load
&amp;rarr; Vector DB에 저장한다.&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Document&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG에서 가장 처음 다루게 되는 것은 &lt;b&gt;Document&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Document는 말 그대로 검색에 사용할 &lt;u&gt;원본 정보&lt;/u&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 것들이 모두 Document의 원천이 될 수 있다.&lt;/p&gt;
&lt;pre class=&quot;dns&quot;&gt;&lt;code&gt;PDF
TXT
DOCX
HTML
JSON

웹페이지
사내 Wiki
DB 데이터
업무 매뉴얼
정책 문서&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 원본 파일 하나가 반드시 검색 단위 하나가 되는 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 경우 문서를 &lt;u&gt;더 작은 단위&lt;/u&gt;로 분리해서 사용한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Chunk란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Chunk는 큰 Document를 검색하기 좋은 작은 단위로 나눈 &lt;b&gt;문서 조각&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;100페이지짜리 회사 규정 문서가 있다고 해보자.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;회사규정.pdf

├─ 근무 규정
├─ 휴가 규정
├─ 경조사 규정
├─ 출장 규정
├─ 보안 규정
└─ 복리후생 규정&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문서 전체를 하나의 검색 단위로 사용하면 문제가 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 다음과 같이 질문했다고 해보자.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;경조사 휴가는 며칠이야?&lt;/code&gt;&lt;/pre&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;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;경조사 질문
    &amp;darr;
100페이지 전체
    &amp;darr;
LLM&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;그래서 문서를 작은 Chunk로 나눈다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;회사규정.pdf

        &amp;darr; Chunking

Chunk 1
&amp;rarr; 근무시간 규정

Chunk 2
&amp;rarr; 연차 규정

Chunk 3
&amp;rarr; 경조사 휴가 규정

Chunk 4
&amp;rarr; 출장 규정

Chunk 5
&amp;rarr; 보안 규정&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 경조사 휴가에 대해 질문하면:&lt;/p&gt;
&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;&quot;경조사 휴가는 며칠이야?&quot;

          &amp;darr;

      Chunk 검색

          &amp;darr;

Chunk 3
&quot;경조사 휴가 규정 ...&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처럼 필요한 부분만 가져올 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 Chunk가 중요한가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG에서는 Chunk 크기가 검색 품질에 큰 영향을 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너무 큰 Chunk를 사용하면:&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;Chunk가 너무 큼

&amp;rarr; 여러 주제가 한 Chunk에 섞임
&amp;rarr; Embedding 의미가 모호해짐
&amp;rarr; 관련 없는 정보까지 LLM에 전달&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 너무 작은 Chunk를 사용하면:&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;Chunk가 너무 작음

&amp;rarr; 문맥이 끊어짐
&amp;rarr; 필요한 정보가 여러 Chunk로 분산
&amp;rarr; 검색 결과 하나만으로 의미를 이해하기 어려움&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음 문장을 생각해보자.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;직원은 입사 1년 이후 15일의 연차를 사용할 수 있다.
미사용 연차의 처리 기준은 별도 규정을 따른다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너무 잘게 나누면:&lt;/p&gt;
&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;Chunk 1
&quot;직원은 입사 1년 이후&quot;

Chunk 2
&quot;15일의 연차를 사용할 수 있다.&quot;

Chunk 3
&quot;미사용 연차의 처리 기준은&quot;

Chunk 4
&quot;별도 규정을 따른다.&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처럼 문맥이 깨질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Chunk는 단순히 작게 만드는 것이 아니라 &lt;b&gt;질문에 답할 수 있을 정도의 의미 있는 정보 단위&lt;/b&gt;로 만드는 것이 중요하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Embedding&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Chunk를 만들었다면 다음으로 필요한 것이 &lt;b&gt;Embedding&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Embedding은 텍스트의 의미를 &lt;u&gt;숫자로 이루어진 Vector로 변환&lt;/u&gt;하는 과정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;&quot;고양이가 뛰고 있다&quot;

        &amp;darr;

  Embedding Model

        &amp;darr;

[0.12, 0.41, -0.31, 0.72, ...]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 문장도 같은 방식으로 Vector가 된다.&lt;/p&gt;
&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;&quot;고양이가 달린다&quot;

        &amp;darr;

[0.11, 0.39, -0.30, 0.70, ...]&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;따라서 Embedding Model이 잘 학습되어 있다면 두 Vector 역시 &lt;u&gt;서로 비슷하게&lt;/u&gt; 만들어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로:&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;&quot;Spring Boot 서버를 실행한다.&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;는 의미가 전혀 다르기 때문에 다른 위치의 Vector가 생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념적으로 표현하면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;Vector Space


 ● 고양이가 달린다
   ● 고양이가 뛰고 있다



                         ● Spring Boot 서버 실행&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 &lt;b&gt;의미가 비슷한 데이터가 Vector 공간에서도 가까워지도록 만드는 것&lt;/b&gt;이 Embedding의 핵심이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 그냥 키워드 검색을 하지 않을까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 검색은 동일한 단어가 있는지를 중심으로 검색할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 사용자가 다음과 같이 검색한다.&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;&quot;휴가 일수&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 문서에는 이렇게 적혀 있을 수도 있다.&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;&quot;근로자는 연간 15일의 연차를 사용할 수 있다.&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;휴가 일수라는 단어가 정확히 존재하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 의미적으로는 매우 관련이 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;휴가 일수

&amp;asymp; 연간 연차 15일&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Embedding을 이용하면 단순한 문자열 일치보다 &lt;b&gt;의미적 유사성&lt;/b&gt;을 이용해 검색할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것을 &lt;b&gt;Semantic Search&lt;/b&gt;라고 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Vector DB&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Embedding으로 만든 Vector를 저장하는 곳이 &lt;b&gt;Vector Database&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 Database가 다음과 같은 검색에 강하다면:&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;WHERE name = 'Kim'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vector DB는 다음과 같은 질문에 강하다.&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;&quot;이 문장과 의미가 가장 비슷한 문서를 찾아줘.&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 검색 기준이 다음과 같이 달라진다.&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;일반 DB

값이 같은가?
      &amp;darr;
Exact Match


Vector DB

의미가 비슷한가?
      &amp;darr;
Similarity Search&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG에서 Vector Store는 Document를 Embedding하여 저장하고, 질문을 이용해 유사한 Document를 다시 검색하는 역할을 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Offline 과정에서 실제로 저장되는 것&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음 Chunk가 있다고 해보자.&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;&quot;대한민국의 영토는 한반도와 그 부속도서로 한다.&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Embedding을 수행한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Embedding Model

        &amp;darr;

[0.13, -0.42, 0.81, 0.22, ...]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Vector DB에는 개념적으로 다음과 같은 정보가 저장된다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Document

content
&amp;rarr; 대한민국의 영토는 한반도와 그 부속도서로 한다.

metadata
&amp;rarr; article: 3

embedding
&amp;rarr; [0.13, -0.42, 0.81, ...]&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;여기까지가 RAG의 &lt;b&gt;지식 준비 과정&lt;/b&gt;이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Runtime 과정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 사용자가 실제 질문을 한다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;대한민국의 영토는 어디까지야?&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기부터 Runtime RAG가 시작된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 흐름은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;사용자 질문

     &amp;darr;

Query Embedding

     &amp;darr;

Query Vector

     &amp;darr;

Vector DB

     &amp;darr;

Similarity Search

     &amp;darr;

관련 Chunk 검색

     &amp;darr;

Prompt에 추가

     &amp;darr;

LLM

     &amp;darr;

최종 답변&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나씩 보면 이해하기 쉽다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 사용자의 질문을 Vector로 만든다&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저장된 Document만 Vector로 만드는 것이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색할 질문 역시 &lt;b&gt;같은 Embedding Model&lt;/b&gt;을 이용해 Vector로 변환한다.&lt;/p&gt;
&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;질문

&quot;대한민국의 영토는 어디까지야?&quot;

        &amp;darr;

Embedding Model

        &amp;darr;

Query Vector

[0.15, -0.40, 0.78, ...]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 Query Vector와 Vector DB에 저장된 Vector들을 비교한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. Similarity Search&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vector DB에서는 Query Vector와 가장 비슷한 Vector를 찾는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 결과가 나왔다고 해보자.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;질문 Vector

      &amp;darr;

Vector DB

      &amp;darr;

Document A
Similarity = 0.93

Document B
Similarity = 0.82

Document C
Similarity = 0.57

Document D
Similarity = 0.21&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;자료에서는 VectorStoreDocumentRetriever가 이러한 VectorStore 기반 유사도 검색을 수행하는 Retrieval 역할을 담당한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Top-K&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG에서는 일반적으로 문서 하나만 가져오지 않는다.&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;Top-K&lt;/b&gt;라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;Top-K = 3&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이면:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;검색 결과

1위 0.93  &amp;larr; 사용
2위 0.88  &amp;larr; 사용
3위 0.82  &amp;larr; 사용
4위 0.71
5위 0.65&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상위 3개의 Document를 가져온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Query

  &amp;darr;

Vector Search

  &amp;darr;

Top-K

  ├─ Chunk A
  ├─ Chunk B
  └─ Chunk C&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;K가 너무 작으면 필요한 정보를 놓칠 수 있고, 너무 크면 불필요한 문서가 함께 들어올 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Similarity Threshold&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 하나 중요한 개념이 &lt;b&gt;Similarity Threshold&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Top-K만 사용하면 문제가 하나 있다.&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;pre class=&quot;1c&quot;&gt;&lt;code&gt;질문

&quot;회사 주차장 이용 규정 알려줘&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 Vector DB에는 주차장 관련 내용이 하나도 없다고 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 Top-3를 강제로 가져오면:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1위 0.34
2위 0.29
3위 0.23&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같이 별로 관련 없는 문서가 선택될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 최소 유사도를 설정한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Similarity Threshold = 0.7&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;0.92 &amp;rarr; 사용
0.83 &amp;rarr; 사용
0.75 &amp;rarr; 사용
0.63 &amp;rarr; 제외
0.41 &amp;rarr; 제외&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처럼 일정 수준 이상의 문서만 사용한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Retrieval&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지가 RAG의 첫 번째 단계인 &lt;b&gt;Retrieval&lt;/b&gt;이다.&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Question
   │
   ▼
Embedding
   │
   ▼
Vector Search
   │
   ▼
Top-K / Threshold
   │
   ▼
관련 Chunk&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Retrieval의 목적은 단순하다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용자의 질문에 답하기 위해 필요한 정보를 외부 데이터에서 찾아오는 것&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Augmentation&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 문서를 찾는 것만으로 끝나지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색된 내용을 LLM에게 전달해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정이 &lt;b&gt;Augmentation&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;pre class=&quot;erlang&quot;&gt;&lt;code&gt;대한민국의 영토는 한반도와 그 부속도서로 한다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 정보를 사용자의 질문과 함께 Prompt에 넣는다.&lt;/p&gt;
&lt;pre class=&quot;prolog&quot;&gt;&lt;code&gt;아래 정보를 참고하여 질문에 답하세요.

[Context]

대한민국의 영토는
한반도와 그 부속도서로 한다.

[Question]

대한민국의 영토는 어디까지인가?&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 Prompt가:&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;Question&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이었다면 RAG에서는:&lt;/p&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;Context
+
Question&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;자료에서도 검색된 Document를 사용자 Query와 결합하여 최종 Prompt를 만드는 것을 Generation 단계의 증강 과정으로 설명한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Context란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG에서 매우 자주 등장하는 단어가 &lt;b&gt;Context&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Context는 LLM에게 제공하는 &lt;b&gt;참고 정보&lt;/b&gt;라고 보면 된다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;Context
&amp;rarr; LLM이 답변할 때 참고해야 하는 외부 정보&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;사용자 질문

&quot;경조사 휴가가 며칠이야?&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Context가 없으면:&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;LLM

&amp;rarr; 자신의 기존 지식으로 답변&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG에서는:&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;Context

&quot;회사 규정 제12조:
직계가족의 경조사 발생 시
최대 5일의 특별휴가를 제공한다.&quot;

           +

Question

&quot;경조사 휴가가 며칠이야?&quot;

           &amp;darr;

          LLM&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;LLM 입장에서는 검색을 직접 수행했다기보다 &lt;b&gt;질문을 받았는데 참고 자료까지 같이 받은 것&lt;/b&gt;에 가깝다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Generation&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막 단계가 &lt;b&gt;Generation&lt;/b&gt;이다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;Context
+
Question

     &amp;darr;

    LLM

     &amp;darr;

Answer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM은 검색된 데이터를 바탕으로 자연어 답변을 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;Context

&quot;연차는 입사 1년 이후 15일이 부여된다.&quot;


Question

&quot;1년 이상 근무하면 연차가 몇 개야?&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM은 이를 이용해서:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;입사 후 1년 이상 근무한 직원에게는
15일의 연차가 부여된다.&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;즉 RAG에서 LLM은 검색 엔진이 아니다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;Retriever
&amp;rarr; 정보를 검색

LLM
&amp;rarr; 검색된 정보를 이해하고 답변 생성&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역할이 나누어져 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;RAG의 전체 원리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 내용을 하나로 연결하면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;                 [ Offline ]


             원본 Document
                   │
                   ▼
               Chunking
                   │
                   ▼
             Embedding Model
                   │
                   ▼
                Vector
                   │
                   ▼
               Vector DB


────────────────────────────────


                 [ Runtime ]


             User Question
                   │
                   ▼
             Embedding Model
                   │
                   ▼
              Query Vector
                   │
                   ▼
               Vector DB
                   │
                   ▼
           Similarity Search
                   │
              Retrieval
                   ▼
             Related Chunks
                   │
              Augmentation
                   ▼
           Context + Question
                   │
                   ▼
                  LLM
                   │
              Generation
                   ▼
                Answer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조가 가장 기본적인 RAG다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;RAG의 핵심은 사실 LLM보다 Retrieval&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG에서는 검색 결과가 매우 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무리 좋은 LLM을 사용하더라도 검색에서 잘못된 Document를 가져오면 제대로 답변하기 어렵다.&lt;/p&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;좋은 Retrieval

정확한 Document
      &amp;darr;
좋은 Context
      &amp;darr;
좋은 Answer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로:&lt;/p&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;나쁜 Retrieval

관련 없는 Document
       &amp;darr;
나쁜 Context
       &amp;darr;
잘못된 Answer&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;따라서 RAG 품질을 개선할 때는 단순히 LLM만 좋은 모델로 바꾸는 것이 아니라 다음 요소들을 함께 고려해야 한다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;Embedding Model

Chunk 크기

Chunk 분할 방식

검색 방식

Top-K

Similarity Threshold

Metadata Filtering

Prompt 구성&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Hallucination이 사라지는 것은 아니다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG의 대표적인 목적 중 하나는 LLM에게 근거를 제공하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 RAG를 사용했다고 해서 항상 정확한 답변이 만들어지는 것은 아니다.&lt;/p&gt;
&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 class=&quot;mipsasm&quot;&gt;&lt;code&gt;잘못된 Retrieval
      &amp;darr;
잘못된 Context
      &amp;darr;
LLM
      &amp;darr;
잘못된 답변&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;또 제대로 된 문서를 검색했어도 Prompt가 좋지 않다면 LLM이 외부 지식을 섞어서 답할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 RAG Prompt에는 다음과 같은 규칙을 넣기도 한다.&lt;/p&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;제공된 Context만 이용하여 답변하세요.

Context에 답이 없다면
답을 찾을 수 없다고 응답하세요.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자료에서도 검색 결과가 없을 경우 별도의 응답을 사용하거나, 제공된 Context만을 사용하도록 Prompt를 구성하는 방법을 다룬다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본 RAG의 한계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 설명한 구조를 흔히 가장 기본적인 RAG 형태로 볼 수 있다.&lt;/p&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;Question
   &amp;darr;
Vector Search
   &amp;darr;
Context
   &amp;darr;
LLM&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 실제 사용자가 항상 검색하기 좋은 질문을 입력하는 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;아 그 저번에 얘기했던 거 있잖아
그거 기간 얼마나 돼?&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문장만 Vector Search하면 무엇을 의미하는지 알기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 다음과 같이 필요 이상으로 장황한 질문도 있을 수 있다.&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;제가 지금 프로젝트를 하다가 문득 궁금해졌는데요.
Spring AI에서 RAG를 쓰고 있는데 혹시 검색 정확도를
높일 수 있는 방법이 있나 해서 질문드립니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색에 필요한 핵심은 사실:&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;RAG 검색 정확도 향상 방법&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 문제를 개선하기 위해 &lt;b&gt;Advanced RAG&lt;/b&gt;에서는 검색 전후에 추가 처리를 수행한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Pre-Retrieval&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pre-Retrieval은 실제 검색을 하기 전에 사용자의 Query를 가공하는 단계다.&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;User Query
    │
    ▼
Pre-Retrieval
    │
    ▼
검색에 최적화된 Query&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 방법은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Query Rewrite
&amp;rarr; 불필요한 표현을 제거하고 질문을 다시 작성

Query Compression
&amp;rarr; 이전 대화까지 참고해 독립적인 질문으로 변경

Multi Query
&amp;rarr; 하나의 질문을 여러 형태의 검색 Query로 확장

Translation
&amp;rarr; 검색에 적합한 언어로 변환&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;원본

&quot;오늘 날씨도 좋고 갑자기 궁금해졌는데
Spring AI RAG 검색 속도 올리는 방법이 있을까?&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음처럼 바꿀 수 있다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;Spring AI RAG 검색 성능 최적화 방법&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 검색 품질을 높일 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Multi Query&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 질문을 여러 형태로 바꾸어 검색하는 방법도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;원본 Query

&quot;RAG 성능 개선 방법&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;을 다음처럼 확장한다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;Query 1
RAG 검색 정확도 향상 방법

Query 2
Embedding 기반 RAG 성능 개선 방법

Query 3
Vector DB Retrieval 최적화 방법&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후 각 Query로 문서를 검색한다.&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;             Original Query
                   │
                   ▼
             Query Expander
                   │
        ┌──────────┼──────────┐
        ▼          ▼          ▼

      Query 1    Query 2    Query 3

        │          │          │
        └──────────┼──────────┘
                   ▼

               Retrieval&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 범위를 넓혀 관련 정보를 찾을 가능성을 높이는 방식이다. 자료에서도 MultiQueryExpander의 목적을 단일 질문을 다양한 질문으로 확장해 더 많은 관련 문서를 찾는 것으로 설명한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Post-Retrieval&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 결과를 그대로 LLM에게 전달할 필요도 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색된 문서를 다시 가공하는 과정을 &lt;b&gt;Post-Retrieval&lt;/b&gt;이라고 한다.&lt;/p&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;Vector Search

     &amp;darr;

Documents

     &amp;darr;

Post-Retrieval

     &amp;darr;

정제된 Documents&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로 다음과 같은 작업이 있다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;Filtering
&amp;rarr; 관련 없는 문서 제거

Reranking
&amp;rarr; 질문과 관련성이 높은 순서로 다시 정렬

Deduplication
&amp;rarr; 중복 Document 제거

Compression
&amp;rarr; Document에서 필요한 부분만 추출 또는 요약&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자료에서도 Post-Retrieval의 역할로 재정렬, 관련 없는 문서 제거, 압축 등을 설명한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본 RAG와 Advanced RAG&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 두 구조를 비교하면 다음과 같다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본 RAG&lt;/h3&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;Question
   │
   ▼
Retrieval
   │
   ▼
Context
   │
   ▼
LLM&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Advanced RAG&lt;/h3&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;Question
   │
   ▼
Pre-Retrieval
Query Rewrite / Expansion
   │
   ▼
Retrieval
Vector Search
   │
   ▼
Post-Retrieval
Filtering / Reranking
   │
   ▼
Augmentation
Context + Query
   │
   ▼
Generation
LLM
   │
   ▼
Answer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 RAG가 발전할수록 단순히 &lt;b&gt;Vector DB에서 몇 개 가져와 LLM에게 넣는 구조&lt;/b&gt;를 넘어, 검색 품질 자체를 개선하는 방향으로 확장된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;RAG를 사용할 때 중요한 포인트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG를 이해할 때 다음 네 가지를 기억하면 된다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. 무엇을 저장할 것인가?

Document / Data


2. 어떻게 나눌 것인가?

Chunking


3. 어떻게 찾을 것인가?

Embedding / Retrieval


4. 어떻게 LLM에게 전달할 것인가?

Augmentation / Prompt&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 RAG 성능은 이 네 과정의 결과가 합쳐져 만들어진다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;RAG 전체 구조 다시 보기&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;                         RAG


              ┌─────────────────────┐
              │      Offline        │
              └─────────────────────┘

                  Original Data
                       │
                       ▼
                    Extract
                       │
                       ▼
                    Document
                       │
                       ▼
                    Chunking
                       │
                       ▼
                 Embedding Model
                       │
                       ▼
                     Vector
                       │
                       ▼
                   Vector DB


════════════════════════════════════════════════


              ┌─────────────────────┐
              │       Runtime       │
              └─────────────────────┘

                  User Question
                       │
                       ▼
                   Embedding
                       │
                       ▼
                  Query Vector
                       │
                       ▼
                   Vector DB
                       │
                  Retrieval
                       ▼
                Related Documents
                       │
                  Augmentation
                       ▼
                 Context + Query
                       │
                       ▼
                      LLM
                       │
                   Generation
                       ▼
                     Answer&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG는 &lt;b&gt;Retrieval-Augmented Generation&lt;/b&gt;, 즉 &lt;b&gt;검색 증강 생성&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM에게 질문을 바로 전달하는 대신, 질문과 관련된 정보를 외부 데이터에서 먼저 검색하여 함께 전달한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 세 단계다.&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;Retrieval

질문과 관련된 정보를 검색한다.


Augmentation

검색된 정보를
Prompt의 Context로 추가한다.


Generation

LLM이 질문과 Context를 이용하여
최종 답변을 생성한다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 실제 RAG는 그 전에 데이터를 준비하는 과정까지 포함해서 이해해야 한다.&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;문서
 &amp;darr;
Chunking
 &amp;darr;
Embedding
 &amp;darr;
Vector DB&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 질문이 들어오면:&lt;/p&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;질문
 &amp;darr;
Embedding
 &amp;darr;
Similarity Search
 &amp;darr;
관련 Chunk
 &amp;darr;
Context
 &amp;darr;
LLM
 &amp;darr;
답변&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;따라서 RAG에서 가장 중요한 것은 단순히 LLM에게 문서를 전달하는 것이 아니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용자의 질문에 필요한 정보를 얼마나 정확하게 찾아서, 적절한 Context로 LLM에게 전달하느냐가 RAG의 핵심이다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이 관점에서 보면 RAG는 단순한 LLM + Vector DB가 아니라,&lt;/p&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;문서 준비
+
Chunking
+
Embedding
+
Retrieval
+
Context 구성
+
Generation&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 모두 연결된 하나의 &lt;b&gt;정보 검색 + 생성 파이프라인&lt;/b&gt;이라고 이해하는 것이 가장 정확하다.&lt;/p&gt;</description>
      <category>개발/AI</category>
      <category>Ai</category>
      <category>context</category>
      <category>embedding</category>
      <category>llm</category>
      <category>rag</category>
      <category>Retrieval</category>
      <category>vector</category>
      <category>개발</category>
      <category>래그</category>
      <author>danieLee</author>
      <guid isPermaLink="true">https://daniellee09.tistory.com/83</guid>
      <comments>https://daniellee09.tistory.com/83#entry83comment</comments>
      <pubDate>Mon, 31 Aug 2026 13:26:09 +0900</pubDate>
    </item>
    <item>
      <title>[Spring AI] Spring AI란? 개념부터 전체 구조까지 이해하기</title>
      <link>https://daniellee09.tistory.com/82</link>
      <description>&lt;p data-end=&quot;344&quot; data-start=&quot;251&quot; data-ke-size=&quot;size16&quot;&gt;최근 LLM을 활용한 애플리케이션이 빠르게 늘어나면서 기존 웹 서비스에서도 ChatGPT, Claude, Gemini 같은 AI 모델을 사용하는 경우가 많아지고 있다.&lt;/p&gt;
&lt;p data-end=&quot;344&quot; data-start=&quot;251&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;373&quot; data-start=&quot;346&quot; data-ke-size=&quot;size16&quot;&gt;예를 들자면 다음과 같은 기능을 생각해볼 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;AI 챗봇
문서 기반 질의응답
사내 데이터 검색
코드 분석
문서 요약
AI Agent
외부 API 자동 호출&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;508&quot; data-start=&quot;449&quot; data-ke-size=&quot;size16&quot;&gt;Spring Boot 애플리케이션에서도 OpenAI API를 직접 호출하면 이러한 기능을 구현할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;522&quot; data-start=&quot;510&quot; data-ke-size=&quot;size16&quot;&gt;구조만 보면 간단하다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;Spring Application
        │
        │ HTTP Request
        ▼
     OpenAI API
        │
        │ Response
        ▼
       LLM&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;718&quot; data-start=&quot;665&quot; data-ke-size=&quot;size16&quot;&gt;하지만 실제 서비스를 만들기 시작하면 단순히 HTTP 요청 한 번 보내는 것으로 끝나지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;829&quot; data-start=&quot;720&quot; data-ke-size=&quot;size16&quot;&gt;OpenAI뿐만 아니라 Claude나 Gemini 같은 다른 모델을 사용해야 할 수도 있고, 대화 기록을 관리하거나 사내 문서를 검색하고, AI가 외부 API를 호출하도록 만들어야 할 수도 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Prompt 관리
Streaming 응답
모델 변경
대화 Memory
Structured Output
RAG
Vector DB
Tool Calling
MCP
Observability&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;997&quot; data-start=&quot;946&quot; data-ke-size=&quot;size16&quot;&gt;이러한 AI 애플리케이션의 공통 기능을 매번 직접 구현하면 코드와 구조가 빠르게 복잡해진다.&lt;/p&gt;
&lt;p data-end=&quot;1047&quot; data-start=&quot;999&quot; data-ke-size=&quot;size16&quot;&gt;이 문제를 Spring 방식으로 해결하기 위해 등장한 것이 &lt;b&gt;Spring AI&lt;/b&gt;다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biNaee/dJMcahFB6MN/DuLAc1v4zPXkQAcKR8lmEk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biNaee/dJMcahFB6MN/DuLAc1v4zPXkQAcKR8lmEk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biNaee/dJMcahFB6MN/DuLAc1v4zPXkQAcKR8lmEk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiNaee%2FdJMcahFB6MN%2FDuLAc1v4zPXkQAcKR8lmEk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;542&quot; height=&quot;361&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;400&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-end=&quot;1052&quot; data-start=&quot;1049&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;1068&quot; data-start=&quot;1054&quot; data-section-id=&quot;11023yb&quot; data-ke-size=&quot;size26&quot;&gt;Spring AI란?&lt;/h2&gt;
&lt;p data-end=&quot;1147&quot; data-start=&quot;1070&quot; data-ke-size=&quot;size16&quot;&gt;Spring AI는 &lt;b&gt;Spring 애플리케이션에서 생성형 AI 기능을 쉽게 사용할 수 있도록 지원하는 AI 애플리케이션 프레임워크&lt;/b&gt;다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;456&quot; data-origin-height=&quot;301&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/diF0yP/dJMcahZT3Mr/3kNvOEs9nqzEqQFFUlSYCk/tfile.svg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/diF0yP/dJMcahZT3Mr/3kNvOEs9nqzEqQFFUlSYCk/tfile.svg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/diF0yP/dJMcahZT3Mr/3kNvOEs9nqzEqQFFUlSYCk/tfile.svg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdiF0yP%2FdJMcahZT3Mr%2F3kNvOEs9nqzEqQFFUlSYCk%2Ftfile.svg&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;456&quot; height=&quot;301&quot; data-origin-width=&quot;456&quot; data-origin-height=&quot;301&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;1302&quot; data-start=&quot;1149&quot; data-ke-size=&quot;size16&quot;&gt;LLM뿐만 아니라 Embedding Model, Vector Database, RAG, Tool Calling, MCP 등의 기능을 Spring의 방식으로 사용할 수 있도록 여러 추상화와 API를 제공한다.&lt;/p&gt;
&lt;p data-end=&quot;1323&quot; data-start=&quot;1304&quot; data-ke-size=&quot;size16&quot;&gt;한 문장으로 정리하면 다음과 같다.&lt;/p&gt;
&lt;blockquote data-end=&quot;1394&quot; data-start=&quot;1325&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;1394&quot; data-start=&quot;1327&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Spring AI는 AI 모델과 기업의 데이터 및 서비스를 Spring 애플리케이션에 연결하기 위한 프레임워크다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;1506&quot; data-start=&quot;1396&quot; data-ke-size=&quot;size16&quot;&gt;Spring AI 공식 문서에서도 핵심 문제를 &lt;b&gt;기업의 Data와 API를 AI Model에 연결하는 것&lt;/b&gt;으로 설명하고 있다. &lt;span data-content-reference-end=&quot;1484&quot; data-content-reference-start=&quot;1460&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;1549&quot; data-start=&quot;1508&quot; data-ke-size=&quot;size16&quot;&gt;중요한 점은 &lt;b&gt;Spring AI 자체가 AI 모델은 아니라는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;Spring AI &amp;ne; ChatGPT
Spring AI &amp;ne; LLM
Spring AI &amp;ne; AI 모델&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1714&quot; data-start=&quot;1618&quot; data-ke-size=&quot;size16&quot;&gt;Spring AI는 OpenAI, Anthropic, Google 등의 AI 모델을 &lt;b&gt;Spring 애플리케이션에서 편리하게 사용할 수 있도록 연결해주는 계층&lt;/b&gt;에 가깝다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;Spring Application
        │
        ▼
    Spring AI
        │
        ▼
   AI Provider
        │
        ▼
       LLM&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1879&quot; data-start=&quot;1848&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 실제 모델은 다음과 같은 것들이 될 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;autoit&quot;&gt;&lt;code&gt;OpenAI
&amp;rarr; GPT

Anthropic
&amp;rarr; Claude

Google
&amp;rarr; Gemini

Ollama
&amp;rarr; Local LLM&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2045&quot; data-start=&quot;1964&quot; data-ke-size=&quot;size16&quot;&gt;Spring AI는 이 모델들과 애플리케이션 사이에서 요청과 응답을 중재한다.&lt;/p&gt;
&lt;hr data-end=&quot;2050&quot; data-start=&quot;2047&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h1 data-end=&quot;2072&quot; data-start=&quot;2052&quot; data-section-id=&quot;19gw0b4&quot;&gt;왜 Spring AI가 필요한가?&lt;/h1&gt;
&lt;p data-end=&quot;2110&quot; data-start=&quot;2074&quot; data-ke-size=&quot;size16&quot;&gt;사실 Spring AI가 없어도 LLM API를 호출할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;2149&quot; data-start=&quot;2112&quot; data-ke-size=&quot;size16&quot;&gt;Spring에는 이미 다음과 같은 HTTP Client가 존재한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;RestClient
WebClient
RestTemplate&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2238&quot; data-start=&quot;2198&quot; data-ke-size=&quot;size16&quot;&gt;따라서 OpenAI API를 직접 호출하는 코드도 충분히 만들 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;2262&quot; data-start=&quot;2240&quot; data-ke-size=&quot;size16&quot;&gt;문제는 애플리케이션이 커질수록 나타난다.&lt;/p&gt;
&lt;p data-end=&quot;2293&quot; data-start=&quot;2264&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 처음에는 OpenAI만 사용했다고 해보자.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;Application
     │
     ▼
 OpenAI API&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2370&quot; data-start=&quot;2346&quot; data-ke-size=&quot;size16&quot;&gt;그런데 이후 Claude도 사용해야 한다면?&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;Application
   │
   ├── OpenAI API
   │
   └── Anthropic API&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2485&quot; data-start=&quot;2446&quot; data-ke-size=&quot;size16&quot;&gt;각 AI 제공업체마다 API의 요청 구조와 응답 구조가 다를 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;OpenAI

{
  &quot;model&quot;: &quot;...&quot;,
  &quot;messages&quot;: [...]
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;Claude

{
  &quot;model&quot;: &quot;...&quot;,
  &quot;system&quot;: &quot;...&quot;,
  &quot;messages&quot;: [...]
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2673&quot; data-start=&quot;2632&quot; data-ke-size=&quot;size16&quot;&gt;인증 방법이나 Streaming 방식, 모델 옵션 등도 서로 차이가 있다.&lt;/p&gt;
&lt;p data-end=&quot;2704&quot; data-start=&quot;2675&quot; data-ke-size=&quot;size16&quot;&gt;결국 개발자는 모델마다 별도의 코드를 작성해야 한다.&lt;/p&gt;
&lt;hr data-end=&quot;2709&quot; data-start=&quot;2706&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h1 data-end=&quot;2723&quot; data-start=&quot;2711&quot; data-section-id=&quot;l9ga1m&quot;&gt;기존 방식의 문제점&lt;/h1&gt;
&lt;p data-end=&quot;2774&quot; data-start=&quot;2725&quot; data-ke-size=&quot;size16&quot;&gt;Spring 애플리케이션에서 LLM API를 직접 사용하면 여러 문제가 발생할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;2827&quot; data-start=&quot;2776&quot; data-ke-size=&quot;size16&quot;&gt;대표적으로 다음과 같다.&lt;/p&gt;
&lt;h3 data-end=&quot;2840&quot; data-start=&quot;2829&quot; data-section-id=&quot;1yypvmm&quot; data-ke-size=&quot;size23&quot;&gt;API 파편화&lt;/h3&gt;
&lt;p data-end=&quot;2905&quot; data-start=&quot;2842&quot; data-ke-size=&quot;size16&quot;&gt;OpenAI, Anthropic, Bedrock, Gemini 등 AI Provider마다 API 구조가 다르다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;vbscript&quot;&gt;&lt;code&gt;OpenAI API
     │
     ├─ Request 형식
     └─ Response 형식

Claude API
     │
     ├─ Request 형식
     └─ Response 형식&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3070&quot; data-start=&quot;3035&quot; data-ke-size=&quot;size16&quot;&gt;모델을 변경하면 관련 코드도 함께 변경해야 할 가능성이 커진다.&lt;/p&gt;
&lt;hr data-end=&quot;3075&quot; data-start=&quot;3072&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;3088&quot; data-start=&quot;3077&quot; data-section-id=&quot;3pkwf&quot; data-ke-size=&quot;size23&quot;&gt;반복되는 구현&lt;/h3&gt;
&lt;p data-end=&quot;3121&quot; data-start=&quot;3090&quot; data-ke-size=&quot;size16&quot;&gt;AI 애플리케이션에서는 반복적으로 필요한 기능들이 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;Prompt 구성
Streaming
Retry
Timeout
응답 Parsing
Token 사용량
Logging&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3234&quot; data-start=&quot;3199&quot; data-ke-size=&quot;size16&quot;&gt;이러한 기능을 프로젝트마다 직접 구현하면 중복 코드가 늘어난다.&lt;/p&gt;
&lt;hr data-end=&quot;3239&quot; data-start=&quot;3236&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;3261&quot; data-start=&quot;3241&quot; data-section-id=&quot;x7fmoe&quot; data-ke-size=&quot;size23&quot;&gt;AI 패턴을 직접 구현해야 함&lt;/h3&gt;
&lt;p data-end=&quot;3297&quot; data-start=&quot;3263&quot; data-ke-size=&quot;size16&quot;&gt;서비스가 조금 복잡해지면 단순한 질문과 답변만으로는 부족하다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;이전 대화 기억
&amp;rarr; Memory

사내 문서 검색
&amp;rarr; RAG

외부 API 실행
&amp;rarr; Tool Calling

외부 도구 연결
&amp;rarr; MCP&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3466&quot; data-start=&quot;3388&quot; data-ke-size=&quot;size16&quot;&gt;이러한 구조까지 모두 직접 설계하면 AI 관련 코드가 상당히 복잡해진다.&lt;/p&gt;
&lt;p data-end=&quot;3546&quot; data-start=&quot;3468&quot; data-ke-size=&quot;size16&quot;&gt;Spring AI는 이러한 문제를 &lt;b&gt;Spring의 추상화, DI, Auto Configuration 방식으로 해결하려는 프레임워크&lt;/b&gt;다.&lt;/p&gt;
&lt;hr data-end=&quot;3551&quot; data-start=&quot;3548&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h1 data-end=&quot;3576&quot; data-start=&quot;3553&quot; data-section-id=&quot;pynmgq&quot;&gt;Spring의 추상화를 AI에 적용한다&lt;/h1&gt;
&lt;p data-end=&quot;3618&quot; data-start=&quot;3578&quot; data-ke-size=&quot;size16&quot;&gt;Spring AI를 이해할 때 가장 중요한 개념이 바로 &lt;b&gt;추상화&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-end=&quot;3659&quot; data-start=&quot;3620&quot; data-ke-size=&quot;size16&quot;&gt;Spring 개발을 해봤다면 Spring Data를 떠올려볼 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;3732&quot; data-start=&quot;3661&quot; data-ke-size=&quot;size16&quot;&gt;Spring Data에서는 사용하는 데이터베이스가 달라져도 공통된 방식으로 데이터를 다룰 수 있도록 여러 인터페이스를 제공한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;             Repository

                 │
       ┌─────────┼─────────┐
       ▼         ▼         ▼

      JPA      MongoDB    Redis&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3906&quot; data-start=&quot;3882&quot; data-ke-size=&quot;size16&quot;&gt;Spring AI도 비슷한 구조를 사용한다.&lt;/p&gt;
&lt;p data-end=&quot;3956&quot; data-start=&quot;3908&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 다양한 AI 모델을 ChatModel이라는 공통 인터페이스로 추상화한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;               ChatModel

                  │
       ┌──────────┼──────────┐
       ▼          ▼          ▼

     OpenAI    Anthropic    Gemini&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4223&quot; data-start=&quot;4115&quot; data-ke-size=&quot;size16&quot;&gt;개발자는 각 Provider의 HTTP API를 매번 직접 처리하기보다는 Spring AI가 제공하는 공통 API를 사용한다.&lt;/p&gt;
&lt;p data-end=&quot;4353&quot; data-start=&quot;4225&quot; data-ke-size=&quot;size16&quot;&gt;공식 문서 역시 Spring AI가 AI Provider에 대한 공통 Model API를 제공해 구현체를 비교적 쉽게 교체할 수 있도록 설계되어 있다고 설명한다. &lt;span data-content-reference-end=&quot;4287&quot; data-content-reference-start=&quot;4263&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr data-end=&quot;4358&quot; data-start=&quot;4355&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h1 data-end=&quot;4378&quot; data-start=&quot;4360&quot; data-section-id=&quot;lmdddf&quot;&gt;Spring AI의 기본 구조&lt;/h1&gt;
&lt;p data-end=&quot;4407&quot; data-start=&quot;4380&quot; data-ke-size=&quot;size16&quot;&gt;전체 구조를 단순화하면 다음과 같이 볼 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;                Application

                     │
                     ▼

                 ChatClient
                     │
                     ▼

                  Prompt
                     │
                     ▼

                 ChatModel
                     │
                     ▼

                AI Provider

           OpenAI / Claude / Gemini
                     │
                     ▼

                    LLM&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4878&quot; data-start=&quot;4855&quot; data-ke-size=&quot;size16&quot;&gt;여기에서 중요한 개념들이 하나씩 등장한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;ChatClient
&amp;rarr; LLM을 편리하게 호출하기 위한 고수준 API

Prompt
&amp;rarr; AI 모델에 전달할 요청 정보

ChatModel
&amp;rarr; 다양한 Chat AI Model을 추상화한 인터페이스

ChatResponse
&amp;rarr; AI 모델의 응답을 Spring AI 형식으로 표현한 객체&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;5095&quot; data-start=&quot;5051&quot; data-ke-size=&quot;size16&quot;&gt;앞으로 Spring AI를 공부할 때 이 구조를 먼저 기억해두면 이해하기 쉽다.&lt;/p&gt;
&lt;hr data-end=&quot;5100&quot; data-start=&quot;5097&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h1 data-end=&quot;5113&quot; data-start=&quot;5102&quot; data-section-id=&quot;1geyt93&quot;&gt;ChatModel&lt;/h1&gt;
&lt;p data-end=&quot;5218&quot; data-start=&quot;5115&quot; data-ke-size=&quot;size16&quot;&gt;ChatModel은 다양한 LLM의 요청과 응답을 &lt;b&gt;공통된 방식으로 사용할 수 있도록 추상화한 인터페이스&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-end=&quot;5253&quot; data-start=&quot;5220&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 애플리케이션 입장에서는 다음처럼 생각할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;            ChatModel

               │
     ┌─────────┼─────────┐
     ▼         ▼         ▼

   OpenAI    Claude     Gemini&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;5451&quot; data-start=&quot;5394&quot; data-ke-size=&quot;size16&quot;&gt;각 Provider가 내부적으로 사용하는 API는 다르지만 Spring AI가 중간에서 이를 변환한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;Spring AI Prompt
       │
       ▼
    ChatModel
       │
       ▼
Provider Native Request
       │
       ▼
      LLM&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;5628&quot; data-start=&quot;5585&quot; data-ke-size=&quot;size16&quot;&gt;그리고 LLM에서 받은 응답도 Spring AI의 공통 응답 형태로 변환한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;LLM Response
     │
     ▼
Provider Native Response
     │
     ▼
Spring AI
     │
     ▼
ChatResponse&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;5806&quot; data-start=&quot;5746&quot; data-ke-size=&quot;size16&quot;&gt;따라서 애플리케이션 코드가 특정 AI Provider의 API 구조에 지나치게 종속되는 것을 줄일 수 있다.&lt;/p&gt;
&lt;hr data-end=&quot;5811&quot; data-start=&quot;5808&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h1 data-end=&quot;5825&quot; data-start=&quot;5813&quot; data-section-id=&quot;v8l9gh&quot;&gt;ChatClient&lt;/h1&gt;
&lt;p data-end=&quot;5891&quot; data-start=&quot;5827&quot; data-ke-size=&quot;size16&quot;&gt;실제 애플리케이션에서는 ChatModel을 직접 사용하는 것보다 ChatClient를 사용하는 경우가 많다.&lt;/p&gt;
&lt;p data-end=&quot;5999&quot; data-start=&quot;5893&quot; data-ke-size=&quot;size16&quot;&gt;ChatClient는 ChatModel을 보다 편리하게 사용할 수 있도록 만든 &lt;b&gt;고수준 Fluent API&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-end=&quot;6023&quot; data-start=&quot;6001&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같이 사용할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;String result = chatClient
    .prompt()
    .user(&quot;Spring AI란 무엇인가?&quot;)
    .call()
    .content();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;6166&quot; data-start=&quot;6137&quot; data-ke-size=&quot;size16&quot;&gt;코드를 읽어보면 동작을 거의 그대로 이해할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;prompt()
&amp;rarr; AI 요청 생성

user()
&amp;rarr; 사용자 질문 전달

call()
&amp;rarr; AI 모델 호출

content()
&amp;rarr; 응답 내용 추출&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;6276&quot; data-start=&quot;6262&quot; data-ke-size=&quot;size16&quot;&gt;전체 흐름은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;사용자 질문
    │
    ▼
ChatClient
    │
    ▼
ChatModel
    │
    ▼
   LLM
    │
    ▼
ChatResponse
    │
    ▼
응답 Content&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;6484&quot; data-start=&quot;6410&quot; data-ke-size=&quot;size16&quot;&gt;Spring AI에서는 이러한 Fluent API를 통해 AI 호출 코드를 Spring 개발자가 익숙하게 사용할 수 있도록 구성한다.&lt;/p&gt;
&lt;p data-end=&quot;6612&quot; data-start=&quot;6486&quot; data-ke-size=&quot;size16&quot;&gt;공식 문서에서도 ChatClient를 WebClient, RestClient와 유사한 방식으로 사용할 수 있는 Fluent API로 설명하고 있다. &lt;span data-content-reference-end=&quot;6521&quot; data-content-reference-start=&quot;6497&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr data-end=&quot;6617&quot; data-start=&quot;6614&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h1 data-end=&quot;6644&quot; data-start=&quot;6619&quot; data-section-id=&quot;sk0368&quot;&gt;Spring AI는 어떤 기능을 제공할까?&lt;/h1&gt;
&lt;p data-end=&quot;6688&quot; data-start=&quot;6646&quot; data-ke-size=&quot;size16&quot;&gt;Spring AI는 단순히 LLM API를 호출하는 기능만 제공하지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;6765&quot; data-start=&quot;6690&quot; data-ke-size=&quot;size16&quot;&gt;AI 애플리케이션을 만들 때 필요한 여러 기능을 함께 제공한다.&lt;/p&gt;
&lt;p data-end=&quot;6783&quot; data-start=&quot;6767&quot; data-ke-size=&quot;size16&quot;&gt;대표적인 기능은 다음과 같다.&lt;/p&gt;
&lt;h3 data-end=&quot;6798&quot; data-start=&quot;6785&quot; data-section-id=&quot;r7tv1t&quot; data-ke-size=&quot;size23&quot;&gt;Model API&lt;/h3&gt;
&lt;p data-end=&quot;6833&quot; data-start=&quot;6800&quot; data-ke-size=&quot;size16&quot;&gt;다양한 AI Model을 공통 인터페이스로 사용할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;Chat Model
Embedding Model
Image Model&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;7003&quot; data-start=&quot;6887&quot; data-ke-size=&quot;size16&quot;&gt;현재 공식 문서에서는 Chat, Embedding뿐만 아니라 이미지 생성, 음성 인식, 음성 생성 등 다양한 모델 유형을 지원하고 있다. &lt;span data-content-reference-end=&quot;6907&quot; data-content-reference-start=&quot;6865&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr data-end=&quot;7008&quot; data-start=&quot;7005&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;7020&quot; data-start=&quot;7010&quot; data-section-id=&quot;t0usjm&quot; data-ke-size=&quot;size23&quot;&gt;Prompt&lt;/h3&gt;
&lt;p data-end=&quot;7042&quot; data-start=&quot;7022&quot; data-ke-size=&quot;size16&quot;&gt;LLM에게 전달하는 입력을 관리한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;System Message
User Message
Assistant Message
ChatOptions&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;7146&quot; data-start=&quot;7115&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 정보를 AI에게 전달할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;너는 Spring 전문가다.
        &amp;darr;
System Message

Spring AI가 무엇인지 설명해줘.
        &amp;darr;
User Message&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;7251&quot; data-start=&quot;7248&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;7274&quot; data-start=&quot;7253&quot; data-section-id=&quot;1ukxfyg&quot; data-ke-size=&quot;size23&quot;&gt;Structured Output&lt;/h3&gt;
&lt;p data-end=&quot;7325&quot; data-start=&quot;7276&quot; data-ke-size=&quot;size16&quot;&gt;LLM의 자유로운 문자열 응답을 Java 객체와 같은 정형화된 데이터로 변환할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;LLM

&quot;홍길동의 나이는 30살입니다.&quot;

        &amp;darr;

Structured Output

{
  &quot;name&quot;: &quot;홍길동&quot;,
  &quot;age&quot;: 30
}

        &amp;darr;

Java Object&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;7492&quot; data-start=&quot;7452&quot; data-ke-size=&quot;size16&quot;&gt;즉 AI 응답을 다른 애플리케이션 로직에서 사용하기 쉽게 만들 수 있다.&lt;/p&gt;
&lt;hr data-end=&quot;7497&quot; data-start=&quot;7494&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;7510&quot; data-start=&quot;7499&quot; data-section-id=&quot;13j6ij6&quot; data-ke-size=&quot;size23&quot;&gt;Advisor&lt;/h3&gt;
&lt;p data-end=&quot;7545&quot; data-start=&quot;7512&quot; data-ke-size=&quot;size16&quot;&gt;LLM 요청과 응답 사이에 공통 기능을 끼워 넣을 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;vbscript&quot;&gt;&lt;code&gt;Request
   │
   ▼
Advisor
   │
   ▼
LLM
   │
   ▼
Advisor
   │
   ▼
Response&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;7664&quot; data-start=&quot;7637&quot; data-ke-size=&quot;size16&quot;&gt;이를 이용해 다음과 같은 기능을 구현할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;Logging
Memory
RAG
요청 가공
응답 가공&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;7805&quot; data-start=&quot;7710&quot; data-ke-size=&quot;size16&quot;&gt;Spring AI에서는 반복되는 생성형 AI 패턴을 Advisor라는 확장 메커니즘으로 캡슐화한다.&lt;/p&gt;
&lt;hr data-end=&quot;7810&quot; data-start=&quot;7807&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;7839&quot; data-start=&quot;7812&quot; data-section-id=&quot;9teamr&quot; data-ke-size=&quot;size23&quot;&gt;Embedding과 Vector Store&lt;/h3&gt;
&lt;p data-end=&quot;7876&quot; data-start=&quot;7841&quot; data-ke-size=&quot;size16&quot;&gt;문장이나 문서의 의미를 Vector로 변환하고 저장할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;&quot;Spring AI는 AI 통합 프레임워크다&quot;

            &amp;darr;

      Embedding Model

            &amp;darr;

[0.12, -0.45, 0.88, ...]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;8045&quot; data-start=&quot;7996&quot; data-ke-size=&quot;size16&quot;&gt;이 벡터를 Vector DB에 저장하면 &lt;b&gt;의미적으로 비슷한 문서를 검색&lt;/b&gt;할 수 있다.&lt;/p&gt;
&lt;hr data-end=&quot;8050&quot; data-start=&quot;8047&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;8059&quot; data-start=&quot;8052&quot; data-section-id=&quot;1xxfl6a&quot; data-ke-size=&quot;size23&quot;&gt;RAG&lt;/h3&gt;
&lt;p data-end=&quot;8107&quot; data-start=&quot;8061&quot; data-ke-size=&quot;size16&quot;&gt;RAG는 외부 문서를 검색하여 그 정보를 LLM의 Prompt에 추가하는 방식이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;사용자 질문
     │
     ▼
Vector DB 검색
     │
     ▼
관련 문서
     │
     ▼
Prompt에 추가
     │
     ▼
    LLM
     │
     ▼
문서 기반 답변&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;8292&quot; data-start=&quot;8246&quot; data-ke-size=&quot;size16&quot;&gt;이를 이용하면 사내 문서나 서비스 데이터를 기반으로 답변하는 AI를 만들 수 있다.&lt;/p&gt;
&lt;hr data-end=&quot;8297&quot; data-start=&quot;8294&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;8314&quot; data-start=&quot;8299&quot; data-section-id=&quot;70y6gp&quot; data-ke-size=&quot;size23&quot;&gt;Chat Memory&lt;/h3&gt;
&lt;p data-end=&quot;8343&quot; data-start=&quot;8316&quot; data-ke-size=&quot;size16&quot;&gt;기본적으로 LLM 호출은 각각 독립적인 요청이다.&lt;/p&gt;
&lt;p data-end=&quot;8385&quot; data-start=&quot;8345&quot; data-ke-size=&quot;size16&quot;&gt;따라서 이전 대화를 기억하려면 애플리케이션이 대화 이력을 관리해야 한다.&lt;/p&gt;
&lt;p data-end=&quot;8428&quot; data-start=&quot;8387&quot; data-ke-size=&quot;size16&quot;&gt;Spring AI에서는 이를 위한 ChatMemory 기능을 제공한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;사용자
&quot;내 이름은 철수야&quot;
      │
      ▼
 ChatMemory
      │
      ▼
사용자
&quot;내 이름이 뭐였지?&quot;
      │
      ▼
이전 대화와 함께 LLM 호출&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;8556&quot; data-start=&quot;8553&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;8574&quot; data-start=&quot;8558&quot; data-section-id=&quot;5no1zw&quot; data-ke-size=&quot;size23&quot;&gt;Tool Calling&lt;/h3&gt;
&lt;p data-end=&quot;8608&quot; data-start=&quot;8576&quot; data-ke-size=&quot;size16&quot;&gt;LLM이 외부 시스템의 기능을 사용하도록 만들 수도 있다.&lt;/p&gt;
&lt;p data-end=&quot;8638&quot; data-start=&quot;8610&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 사용자가 다음과 같이 질문했다고 해보자.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;&quot;서울의 현재 날씨 알려줘&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;8697&quot; data-start=&quot;8669&quot; data-ke-size=&quot;size16&quot;&gt;LLM 자체에는 실시간 날씨 정보가 없을 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;8730&quot; data-start=&quot;8699&quot; data-ke-size=&quot;size16&quot;&gt;이때 Weather API를 Tool로 등록할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;사용자

&quot;서울 날씨 알려줘&quot;

      &amp;darr;

     LLM

&quot;Weather Tool이 필요하다&quot;

      &amp;darr;

Spring AI

      &amp;darr;

Weather API

      &amp;darr;

현재 날씨

      &amp;darr;

     LLM

      &amp;darr;

&quot;서울의 현재 기온은 ...입니다.&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;9029&quot; data-start=&quot;8911&quot; data-ke-size=&quot;size16&quot;&gt;Tool Calling은 AI가 단순히 텍스트를 생성하는 것을 넘어 외부 데이터를 조회하거나 실제 작업을 수행하도록 만드는 중요한 기반이다. &lt;span data-content-reference-end=&quot;8913&quot; data-content-reference-start=&quot;8888&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr data-end=&quot;9034&quot; data-start=&quot;9031&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;9043&quot; data-start=&quot;9036&quot; data-section-id=&quot;1xxfgoo&quot; data-ke-size=&quot;size23&quot;&gt;MCP&lt;/h3&gt;
&lt;p data-end=&quot;9123&quot; data-start=&quot;9045&quot; data-ke-size=&quot;size16&quot;&gt;MCP(Model Context Protocol)는 AI 애플리케이션과 외부 Tool 및 Resource를 연결하기 위한 표준 프로토콜이다.&lt;/p&gt;
&lt;p data-end=&quot;9208&quot; data-start=&quot;9125&quot; data-ke-size=&quot;size16&quot;&gt;Spring AI는 MCP Client와 MCP Server를 모두 지원한다. &lt;span data-content-reference-end=&quot;9095&quot; data-content-reference-start=&quot;9053&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;9224&quot; data-start=&quot;9210&quot; data-ke-size=&quot;size16&quot;&gt;개념적으로는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;AI Application
      │
      ▼
  MCP Client
      │
      ▼
  MCP Server
      │
      ├─ Tool
      ├─ Resource
      └─ 외부 시스템&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;9398&quot; data-start=&quot;9368&quot; data-ke-size=&quot;size16&quot;&gt;이 부분은 이후 별도의 글에서 자세히 살펴볼 예정이다.&lt;/p&gt;
&lt;hr data-end=&quot;9403&quot; data-start=&quot;9400&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h1 data-end=&quot;9435&quot; data-start=&quot;9405&quot; data-section-id=&quot;totqxd&quot;&gt;결국 Spring AI가 해결하려는 것은 무엇일까?&lt;/h1&gt;
&lt;p data-end=&quot;9469&quot; data-start=&quot;9437&quot; data-ke-size=&quot;size16&quot;&gt;Spring AI의 핵심을 다시 단순하게 생각해보면 된다.&lt;/p&gt;
&lt;p data-end=&quot;9515&quot; data-start=&quot;9471&quot; data-ke-size=&quot;size16&quot;&gt;기업의 기존 Spring 애플리케이션에는 이미 수많은 데이터와 기능이 존재한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Database

Service

Repository

REST API

Kafka

Batch

사내 문서&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;9615&quot; data-start=&quot;9591&quot; data-ke-size=&quot;size16&quot;&gt;그리고 외부에는 AI Model이 존재한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;autoit&quot;&gt;&lt;code&gt;GPT

Claude

Gemini

Local LLM&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;9805&quot; data-start=&quot;9661&quot; data-ke-size=&quot;size16&quot;&gt;Spring AI의 중요한 역할은 이 둘을 연결하는 것이다.&lt;/p&gt;
&lt;p data-end=&quot;9805&quot; data-start=&quot;9661&quot; data-ke-size=&quot;size16&quot;&gt;Spring AI의 핵심 설계 철학을 &lt;b&gt;기업 DATA와 기업 API를 AI 모델에 연결하는 것&lt;/b&gt;으로 정리한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;기존 Spring Application

 ┌────────────────────────────┐
 │                            │
 │  Service       Database    │
 │                            │
 │  Repository    REST API    │
 │                            │
 │  사내 문서      업무 시스템   │
 │                            │
 └─────────────┬──────────────┘
               │
               ▼

           Spring AI

               │
               ▼

           AI Model

     GPT / Claude / Gemini&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;10402&quot; data-start=&quot;10265&quot; data-ke-size=&quot;size16&quot;&gt;즉 기존 서비스를 버리고 AI 전용 애플리케이션을 새로 만드는 것이 아니라, &lt;b&gt;기존 Spring 생태계 위에 AI 기능을 확장하는 것&lt;/b&gt;이 Spring AI의 큰 목적이다.&lt;/p&gt;
&lt;hr data-end=&quot;10407&quot; data-start=&quot;10404&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h1 data-end=&quot;10430&quot; data-start=&quot;10409&quot; data-section-id=&quot;dhg9y&quot;&gt;Spring 개발자 입장에서의 장점&lt;/h1&gt;
&lt;p data-end=&quot;10511&quot; data-start=&quot;10432&quot; data-ke-size=&quot;size16&quot;&gt;기존 Spring 개발자에게 Spring AI가 특히 유용한 이유는 이미 알고 있는 Spring 개발 방식을 그대로 활용할 수 있기 때문이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;Spring IoC / DI
Spring Boot
Auto Configuration
Spring Security
Spring Data
Micrometer
MVC / WebFlux&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;10665&quot; data-start=&quot;10626&quot; data-ke-size=&quot;size16&quot;&gt;AI 관련 컴포넌트 역시 Bean으로 등록하고 DI를 이용할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
public class AiService {

    private final ChatClient chatClient;

    public AiService(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;10929&quot; data-start=&quot;10859&quot; data-ke-size=&quot;size16&quot;&gt;즉 완전히 새로운 개발 생태계를 배우는 것이 아니라 &lt;b&gt;기존 Spring 애플리케이션 안으로 AI 기능을 가져오는 형태&lt;/b&gt;다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;기존 Spring 개발 방식

Controller
Service
Repository
DI
Configuration

        +

AI

ChatClient
RAG
Memory
Tool
MCP

        &amp;darr;

Spring AI Application&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;11254&quot; data-start=&quot;11089&quot; data-ke-size=&quot;size16&quot;&gt;Spring Security를 이용한 인증&amp;middot;인가, 기존 도메인 Service와 Repository, 운영 환경과 Observability 등을 AI 기능과 함께 활용할 수 있다는 것도 Spring 기반 AI 개발의 장점이다.&lt;/p&gt;
&lt;hr data-end=&quot;11259&quot; data-start=&quot;11256&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h1 data-end=&quot;11279&quot; data-start=&quot;11261&quot; data-section-id=&quot;1870jmb&quot;&gt;Spring AI의 전체 그림&lt;/h1&gt;
&lt;p data-end=&quot;11315&quot; data-start=&quot;11281&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 살펴본 내용을 하나의 그림으로 정리하면 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;                        Spring Application

                                │
                                ▼

                            ChatClient
                                │
              ┌─────────────────┼─────────────────┐
              │                 │                 │
              ▼                 ▼                 ▼

           Prompt            Advisor            Tool

              │             Memory / RAG          │
              │                                   │
              └─────────────────┬─────────────────┘
                                │
                                ▼

                            ChatModel
                                │
              ┌─────────────────┼─────────────────┐
              ▼                 ▼                 ▼

            OpenAI          Anthropic           Gemini
              │                 │                 │
              └─────────────────┼─────────────────┘
                                │
                                ▼

                               LLM


                         별도의 데이터 영역

              Document
                  │
                  ▼
           Embedding Model
                  │
                  ▼
             Vector Store
                  │
                  ▼
                 RAG
                  │
                  └──────────&amp;rarr; ChatClient&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;12753&quot; data-start=&quot;12710&quot; data-ke-size=&quot;size16&quot;&gt;처음 보면 구성 요소가 많아 보이지만 앞으로 하나씩 나누어 보면 어렵지 않다.&lt;/p&gt;
&lt;p data-end=&quot;12778&quot; data-start=&quot;12755&quot; data-ke-size=&quot;size16&quot;&gt;가장 먼저 이해해야 할 것은 다음 정도다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;ChatClient
&amp;rarr; AI와 대화하기 위한 고수준 API

ChatModel
&amp;rarr; AI Model을 추상화한 인터페이스

Prompt
&amp;rarr; LLM에 전달하는 요청

Advisor
&amp;rarr; 요청/응답 과정에 기능 추가

Embedding
&amp;rarr; 데이터를 의미 기반 Vector로 변환

Vector Store
&amp;rarr; Vector 저장 및 검색

RAG
&amp;rarr; 외부 지식을 검색해서 Prompt에 추가

Memory
&amp;rarr; 대화 이력 관리

Tool
&amp;rarr; 외부 기능 실행

MCP
&amp;rarr; 외부 Tool/Resource 연결 표준&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;13075&quot; data-start=&quot;13072&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h1 data-end=&quot;13098&quot; data-start=&quot;13077&quot; data-section-id=&quot;wg4u1a&quot;&gt;Spring AI와 AI Agent&lt;/h1&gt;
&lt;p data-end=&quot;13150&quot; data-start=&quot;13100&quot; data-ke-size=&quot;size16&quot;&gt;Spring AI를 공부하다 보면 최종적으로 &lt;b&gt;AI Agent&lt;/b&gt;라는 개념으로 이어진다.&lt;/p&gt;
&lt;p data-end=&quot;13185&quot; data-start=&quot;13152&quot; data-ke-size=&quot;size16&quot;&gt;기존 LLM 애플리케이션이 단순히 질문에 답하는 구조였다면,&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;사용자 질문
    &amp;darr;
   LLM
    &amp;darr;
텍스트 답변&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;13282&quot; data-start=&quot;13233&quot; data-ke-size=&quot;size16&quot;&gt;Agent는 사용자의 목표를 이해하고 필요한 데이터를 찾거나 Tool을 실행할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;13290&quot; data-start=&quot;13284&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;사용자

&quot;내일 비가 오면 회의 장소를 실내로 변경해줘.&quot;

              &amp;darr;

            Agent

              &amp;darr;

      내일 날씨 확인
              │
              ▼
         Weather Tool
              │
              ▼
          비 여부 판단
              │
              ▼
        Calendar Tool
              │
              ▼
        회의 장소 변경
              │
              ▼
          Email Tool
              │
              ▼
       참석자에게 알림&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;13820&quot; data-start=&quot;13715&quot; data-ke-size=&quot;size16&quot;&gt;즉 AI가 단순한 &lt;b&gt;글쓰기 도구&lt;/b&gt;를 넘어 실제 시스템과 연결되어 &lt;b&gt;일을 수행하는 시스템&lt;/b&gt;으로 발전하는 것이다.&lt;/p&gt;
&lt;p data-end=&quot;13909&quot; data-start=&quot;13822&quot; data-ke-size=&quot;size16&quot;&gt;Spring AI는 이러한 애플리케이션을 만들기 위해 필요한 Model, RAG, Memory, Tool Calling, MCP 등의 기반 기능을 제공한다.&lt;/p&gt;
&lt;hr data-end=&quot;13914&quot; data-start=&quot;13911&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h1 data-end=&quot;13920&quot; data-start=&quot;13916&quot; data-section-id=&quot;yim43j&quot;&gt;정리&lt;/h1&gt;
&lt;p data-end=&quot;13992&quot; data-start=&quot;13922&quot; data-ke-size=&quot;size16&quot;&gt;Spring AI는 &lt;b&gt;Spring Framework 기반 애플리케이션에서 생성형 AI 기능을 개발하기 위한 프레임워크&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-end=&quot;14085&quot; data-start=&quot;13994&quot; data-ke-size=&quot;size16&quot;&gt;단순히 OpenAI API를 호출하는 라이브러리가 아니라 다양한 AI Provider와 AI 애플리케이션 패턴을 Spring 방식으로 사용할 수 있도록 추상화한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Spring AI

├─ Model
│   └─ 다양한 AI Model 추상화
│
├─ ChatClient
│   └─ LLM 호출을 위한 Fluent API
│
├─ Prompt
│   └─ LLM 입력 관리
│
├─ Structured Output
│   └─ AI 응답 구조화
│
├─ Advisor
│   └─ 요청/응답 기능 확장
│
├─ Embedding / Vector Store
│   └─ 의미 기반 데이터 검색
│
├─ RAG
│   └─ 외부 지식 활용
│
├─ Chat Memory
│   └─ 대화 기억
│
├─ Tool Calling
│   └─ 외부 기능 실행
│
└─ MCP
    └─ 외부 Tool과 Resource 연결&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;14494&quot; data-start=&quot;14466&quot; data-ke-size=&quot;size16&quot;&gt;그리고 이 모든 기능의 중심에는 다음 목적이 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;기업 Data
기업 API
기존 업무 시스템

      │
      ▼

   Spring AI

      │
      ▼

   AI Model&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;14614&quot; data-start=&quot;14595&quot; data-ke-size=&quot;size16&quot;&gt;한 문장으로 정리하면 다음과 같다.&lt;/p&gt;
&lt;blockquote data-end=&quot;14721&quot; data-start=&quot;14616&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;14721&quot; data-start=&quot;14618&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Spring AI는 기존 Spring 애플리케이션의 데이터와 기능을 AI 모델과 연결하여 생성형 AI 애플리케이션을 만들 수 있도록 도와주는 Spring 기반 AI 프레임워크다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;14825&quot; data-start=&quot;14723&quot; data-ke-size=&quot;size16&quot;&gt;Spring이 데이터베이스, 웹, 보안 등의 기술을 Spring 방식으로 추상화해왔던 것처럼, Spring AI는 그 방식을 &lt;b&gt;AI 개발 영역까지 확장한 것&lt;/b&gt;이라고 이해하면 된다.&lt;/p&gt;</description>
      <category>개발/Spring</category>
      <category>Ai</category>
      <category>java</category>
      <category>spring</category>
      <category>spring ai</category>
      <category>개념</category>
      <category>개발</category>
      <category>생성형</category>
      <category>스프링</category>
      <category>스프링ai</category>
      <category>프레임워크</category>
      <author>danieLee</author>
      <guid isPermaLink="true">https://daniellee09.tistory.com/82</guid>
      <comments>https://daniellee09.tistory.com/82#entry82comment</comments>
      <pubDate>Fri, 28 Aug 2026 17:06:54 +0900</pubDate>
    </item>
    <item>
      <title>[JavaScript][Vue.js] Pinia와 Store 정리 - 전역 상태 관리 이해하기</title>
      <link>https://daniellee09.tistory.com/81</link>
      <description>&lt;h1&gt;&lt;span style=&quot;color: #333333; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; font-size: 16px; letter-spacing: 0px;&quot;&gt;Vue 애플리케이션이 커지면 여러 컴포넌트가 같은 데이터를 함께 사용해야 하는 경우가 많아진다.&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 데이터가 있다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;로그인 사용자 정보
장바구니
알림 개수
다크모드 설정
날씨 단위
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 데이터를 부모에서 자식으로 계속 Props로 전달하면 구조가 복잡해질 수 있다.&lt;/p&gt;
&lt;pre class=&quot;gauss&quot;&gt;&lt;code&gt;App
 &amp;darr;
Layout
 &amp;darr;
Header
 &amp;darr;
Menu
 &amp;darr;
UserInfo
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중간 컴포넌트가 데이터를 사용하지 않더라도 계속 전달해야 하는 문제가 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴 때 사용하는 것이 &lt;b&gt;Pinia&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pinia는 Vue 애플리케이션에서 여러 컴포넌트가 함께 사용하는 데이터를 중앙에서 관리하기 위한 &lt;b&gt;전역 상태 관리 라이브러리&lt;/b&gt;다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Pinia란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pinia는 여러 컴포넌트가 같은 상태를 공유할 수 있도록 &lt;b&gt;Store&lt;/b&gt;라는 중앙 저장소를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;Component A ─┐
             │
Component B ─┼──&amp;rarr; Store
             │
Component C ─┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 컴포넌트가 서로 직접 데이터를 전달하지 않고 같은 Store를 바라보는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 로그인 정보를 Store에 저장하면 다음과 같이 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;gauss&quot;&gt;&lt;code&gt;Header
&amp;rarr; 로그인 사용자 이름 표시

MyPage
&amp;rarr; 사용자 정보 표시

Navigation Guard
&amp;rarr; 로그인 여부 확인
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모두 같은 로그인 상태를 공유할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Store란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Store는 여러 컴포넌트가 함께 사용할 &lt;b&gt;상태와 로직을 보관하는 저장소&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 로그인 Store라면 다음과 같은 데이터를 관리할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;authStore

user
token
isLoggedIn
login()
logout()
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 Store에는 단순한 데이터만 들어가는 것이 아니라, 데이터를 가공하거나 변경하는 로직도 함께 들어간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pinia Store의 핵심은 크게 세 가지로 나눌 수 있다.&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;State
Getters
Actions
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue의 Composition API와 연결하면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;Pinia         Vue

State    &amp;rarr;    ref / reactive
Getters  &amp;rarr;    computed
Actions  &amp;rarr;    function
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;State&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;State는 Store가 가지고 있는 &lt;b&gt;원본 데이터&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 카운터 Store라면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const count = ref(0)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 count가 State다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인 Store라면 다음과 같은 값들이 State가 될 수 있다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;const user = ref(null)
const token = ref(null)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;State는 애플리케이션에서 여러 컴포넌트가 공유하는 원본 상태다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;Getters&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Getters는 &lt;u&gt;State를 기반으로 계산한 값&lt;/u&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue의 computed()와 같은 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const count = ref(10)

const doubleCount = computed(() =&amp;gt; {
  return count.value * 2
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;count
&amp;rarr; State

doubleCount
&amp;rarr; Getter
&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;로그인 여부도 Getter로 만들 수 있다.&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;const isLoggedIn = computed(() =&amp;gt; {
  return token.value !== null
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 Getter는 원본 상태를 직접 저장하는 것이 아니라 &lt;b&gt;State를 이용해 만들어진 계산 결과&lt;/b&gt;다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;Actions&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Actions는 State를 변경하거나 특정 로직을 실행하는 함수다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;const increment = () =&amp;gt; {
  count.value++
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 함수가 Action이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인 Store라면 다음처럼 작성할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;const login = (userData, authToken) =&amp;gt; {
  user.value = userData
  token.value = authToken
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그아웃은 다음과 같이 만들 수 있다.&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;const logout = () =&amp;gt; {
  user.value = null
  token.value = null
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Action은 Store의 State를 변경하거나 서버 통신 같은 로직을 처리하는 함수다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;Pinia Store 만들기&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 카운터 Store를 만든다고 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일 구조는 다음과 같이 만들 수 있다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;src/
└─ stores/
   └─ counter.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Store 코드는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useCounterStore = defineStore('counter', () =&amp;gt; {

  const count = ref(0)

  const doubleCount = computed(() =&amp;gt; {
    return count.value * 2
  })

  const increment = () =&amp;gt; {
    count.value++
  }

  return {
    count,
    doubleCount,
    increment,
  }
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조를 정리하면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;useCounterStore

├─ count
│   &amp;rarr; State
│
├─ doubleCount
│   &amp;rarr; Getter
│
└─ increment()
    &amp;rarr; Action
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;defineStore()는 Pinia Store를 만드는 함수다.&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;defineStore('counter', () =&amp;gt; {
  ...
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 값인 'counter'는 Store를 구분하는 고유한 이름이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;컴포넌트에서 Store 사용하기&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Store를 사용할 컴포넌트에서는 먼저 import한다.&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
import { useCounterStore } from '@/stores/counter'

const counterStore = useCounterStore()
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Template에서 State, Getter, Action을 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;p&amp;gt;
    Count: {{ counterStore.count }}
  &amp;lt;/p&amp;gt;

  &amp;lt;p&amp;gt;
    Double: {{ counterStore.doubleCount }}
  &amp;lt;/p&amp;gt;

  &amp;lt;button @click=&quot;counterStore.increment&quot;&amp;gt;
    증가
  &amp;lt;/button&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동작 구조는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;Component
   &amp;darr;
useCounterStore()
   &amp;darr;
Store 접근
   &amp;darr;
State 읽기
Getter 사용
Action 실행
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;main.js에서 Pinia 등록하기&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pinia를 사용하려면 Vue 애플리케이션에 먼저 등록해야 한다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { createApp } from 'vue'
import { createPinia } from 'pinia'

import App from './App.vue'

const app = createApp(App)

app.use(createPinia())

app.mount('#app')
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서:&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;createPinia()
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;는 Pinia 인스턴스를 만들고,&lt;/p&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;app.use()
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 통해 Vue 애플리케이션에 등록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;createPinia()
      &amp;darr;
Pinia 생성
      &amp;darr;
app.use()
      &amp;darr;
Vue Application에 등록
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;날씨 프로젝트에 적용하기&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 날씨 프로젝트에서 섭씨와 화씨 설정을 여러 화면에서 함께 사용한다고 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 Store를 만들 수 있다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;configStore

unit
&amp;rarr; celsius / fahrenheit

unitSymbol
&amp;rarr; ℃ / ℉

toggleUnit()
&amp;rarr; 단위 변경
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드는 다음과 같이 구성할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useConfigStore = defineStore('config', () =&amp;gt; {

  const unit = ref('celsius')

  const unitSymbol = computed(() =&amp;gt; {
    return unit.value === 'celsius'
      ? '℃'
      : '℉'
  })

  const toggleUnit = () =&amp;gt; {
    unit.value =
      unit.value === 'celsius'
        ? 'fahrenheit'
        : 'celsius'
  }

  return {
    unit,
    unitSymbol,
    toggleUnit,
  }
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 역할은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;unit
&amp;rarr; State

unitSymbol
&amp;rarr; Getter

toggleUnit()
&amp;rarr; Action
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 Store를 여러 컴포넌트에서 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;             configStore
                 │
       ┌─────────┼─────────┐
       ▼         ▼         ▼

UnitToggler  HomeView   DetailView
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 UnitToggler에서:&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;configStore.toggleUnit()
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;을 실행하면 Store의 값이 변경된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 HomeView, DetailView도 같은 Store를 사용하고 있다면 해당 값이 함께 반영된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;Props와 Pinia 차이&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Props는 부모와 자식 관계에서 데이터를 전달할 때 사용한다.&lt;/p&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;Parent
   &amp;darr; Props
Child
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pinia는 컴포넌트의 부모&amp;middot;자식 관계와 상관없이 공통 상태를 공유할 때 사용한다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;Component A
     │
     ▼
    Store
     ▲
     │
Component B
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비교하면 다음과 같다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 86.1628%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.6279%;&quot;&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 32.856%;&quot;&gt;&lt;b&gt;Props&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 36.5626%;&quot;&gt;&lt;b&gt;Pinia&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.6279%;&quot;&gt;범위&lt;/td&gt;
&lt;td style=&quot;width: 32.856%;&quot;&gt;부모 &amp;rarr; 자식&lt;/td&gt;
&lt;td style=&quot;width: 36.5626%;&quot;&gt;애플리케이션 전역&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.6279%;&quot;&gt;목적&lt;/td&gt;
&lt;td style=&quot;width: 32.856%;&quot;&gt;컴포넌트 데이터 전달&lt;/td&gt;
&lt;td style=&quot;width: 36.5626%;&quot;&gt;전역 상태 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.6279%;&quot;&gt;관계&lt;/td&gt;
&lt;td style=&quot;width: 32.856%;&quot;&gt;부모&amp;middot;자식 구조 필요&lt;/td&gt;
&lt;td style=&quot;width: 36.5626%;&quot;&gt;관계 없어도 됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.6279%;&quot;&gt;대표 예&lt;/td&gt;
&lt;td style=&quot;width: 32.856%;&quot;&gt;WeatherCard 데이터&lt;/td&gt;
&lt;td style=&quot;width: 36.5626%;&quot;&gt;로그인, 장바구니, 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 단순히 자식에게 필요한 데이터를 전달한다면 Props를 사용하고, 여러 페이지나 여러 컴포넌트에서 같은 데이터를 공유해야 한다면 Pinia를 사용하는 구조다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;provide / inject와 Pinia 차이&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;provide / inject 역시 깊은 컴포넌트로 데이터를 전달할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 범위가 다르다.&lt;/p&gt;
&lt;pre class=&quot;irpf90&quot;&gt;&lt;code&gt;provide / inject
&amp;rarr; 특정 컴포넌트 트리 안에서 공유

Pinia
&amp;rarr; 애플리케이션 전체에서 공유
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 특정 Form 내부 설정값을 여러 하위 컴포넌트가 사용하는 경우에는 provide / inject를 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 다음과 같은 데이터는 Pinia가 적합하다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;로그인 상태
사용자 정보
장바구니
전역 테마
알림 상태
공통 설정값
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;Pinia의 전체 구조&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pinia 구조를 한 번에 보면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;                  Pinia

                   │
                 Store
                   │
        ┌──────────┼──────────┐
        │          │          │
        ▼          ▼          ▼

      State      Getter      Action

      ref()     computed()   function

        │          │          │
        └──────────┴──────────┘
                   │
                   ▼

              Components
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트들은 각각 Store를 불러와 같은 상태를 공유한다.&lt;/p&gt;
&lt;pre class=&quot;vhdl&quot;&gt;&lt;code&gt;          Component A
              │
              ▼
          ┌─────────┐
          │  Store  │
          └─────────┘
          ▲    ▲    ▲
          │    │    │
 Component B   │  Component C
               │
          Component D
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조 덕분에 컴포넌트 간에 Props를 여러 단계로 전달하지 않고도 공통 상태를 사용할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;정리&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pinia는 Vue 애플리케이션의 &lt;b&gt;전역 상태 관리 라이브러리&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Store는 Pinia 안에서 실제 데이터를 관리하는 저장소다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Store의 핵심은 다음 세 가지다.&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;State
&amp;rarr; 원본 상태
&amp;rarr; ref / reactive

Getters
&amp;rarr; State를 이용한 계산값
&amp;rarr; computed

Actions
&amp;rarr; State 변경 및 로직 실행
&amp;rarr; function
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue Composition API와 연결하면 다음처럼 이해할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;Vue Component

ref()
computed()
function()

      &amp;darr;

Pinia Store로 이동

      &amp;darr;

State
Getter
Action
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 기존에는 각 컴포넌트가 자기 상태를 가지고 있었다면, 여러 컴포넌트가 공통으로 사용해야 하는 상태를 &lt;b&gt;Store로 끌어올려 중앙에서 관리하는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 문장으로 정리하면 다음과 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Pinia는 Vue 애플리케이션의 여러 컴포넌트가 함께 사용할 상태를 중앙에서 관리하는 도구이며, Store는 그 안에서 State, Getter, Action을 관리하는 전역 저장소다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>개발/JavaScript</category>
      <category>JavaScript</category>
      <category>js</category>
      <category>pinia</category>
      <category>store</category>
      <category>vue.js</category>
      <category>개발</category>
      <category>스토어</category>
      <category>컴포턴넌트</category>
      <category>프론트</category>
      <category>피니아</category>
      <author>danieLee</author>
      <guid isPermaLink="true">https://daniellee09.tistory.com/81</guid>
      <comments>https://daniellee09.tistory.com/81#entry81comment</comments>
      <pubDate>Wed, 26 Aug 2026 16:57:58 +0900</pubDate>
    </item>
    <item>
      <title>[JavaScript][Vue.js] Slot 정리 - 부모가 자식 컴포넌트에 UI를 주입하는 방법</title>
      <link>https://daniellee09.tistory.com/80</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Vue에서 컴포넌트를 만들다 보면 &lt;b&gt;겉모양은 같지만 내부 내용만 바뀌는 UI&lt;/b&gt;를 만들고 싶은 경우가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 카드 컴포넌트를 생각해보자.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;┌──────────────────────┐
│                      │
│        내용           │
│                      │
└──────────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카드의 외곽 디자인은 같지만 내부에는 상황에 따라 서로 다른 내용을 넣고 싶을 수 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;카드 1
&amp;rarr; 날씨 정보

카드 2
&amp;rarr; 사용자 정보

카드 3
&amp;rarr; 공지사항
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴 때 사용하는 것이 &lt;b&gt;Slot&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Slot은 쉽게 말하면,&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식 컴포넌트가 미리 비워둔 영역에 부모 컴포넌트가 원하는 HTML이나 컴포넌트를 넣을 수 있게 하는 기능이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Slot의 기본 구조&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 자식 컴포넌트에서 &amp;lt;slot&amp;gt;을 선언한다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;!-- BaseCard.vue --&amp;gt;
&amp;lt;template&amp;gt;
  &amp;lt;div class=&quot;card&quot;&amp;gt;
    &amp;lt;slot&amp;gt;&amp;lt;/slot&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서&lt;/p&gt;
&lt;pre class=&quot;apache&quot;&gt;&lt;code&gt;&amp;lt;slot&amp;gt;&amp;lt;/slot&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부분이 부모가 내용을 넣을 자리다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모 컴포넌트에서는 다음과 같이 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;BaseCard&amp;gt;
  &amp;lt;h2&amp;gt;공지사항&amp;lt;/h2&amp;gt;
  &amp;lt;p&amp;gt;새로운 공지가 있습니다.&amp;lt;/p&amp;gt;
&amp;lt;/BaseCard&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 부모가 작성한 내용이 자식 컴포넌트의 &amp;lt;slot&amp;gt; 위치에 들어간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념적으로는 다음과 같이 렌더링된다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;&amp;lt;div class=&quot;card&quot;&amp;gt;
  &amp;lt;h2&amp;gt;공지사항&amp;lt;/h2&amp;gt;
  &amp;lt;p&amp;gt;새로운 공지가 있습니다.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 흐름은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;Parent Component

&amp;lt;BaseCard&amp;gt;
  &amp;lt;h2&amp;gt;공지사항&amp;lt;/h2&amp;gt;
&amp;lt;/BaseCard&amp;gt;

        &amp;darr;

Child Component

&amp;lt;div class=&quot;card&quot;&amp;gt;
  &amp;lt;slot /&amp;gt;
&amp;lt;/div&amp;gt;

        &amp;darr;

실제 화면

&amp;lt;div class=&quot;card&quot;&amp;gt;
  &amp;lt;h2&amp;gt;공지사항&amp;lt;/h2&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Slot과 Props의 차이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Props와 Slot은 둘 다 부모에서 자식으로 &lt;u&gt;무언가를 전달한다&lt;/u&gt;는 점에서는 비슷하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 전달하는 &lt;u&gt;대상&lt;/u&gt;이 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Props는 &lt;b&gt;데이터를 전달&lt;/b&gt;한다.&lt;/p&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt;&amp;lt;WeatherCard
  :city-name=&quot;cityName&quot;
  :temperature=&quot;25&quot;
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 전달하는 것은 다음과 같은 값이다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;&quot;서울&quot;
25
true
객체
배열
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 Slot은 &lt;b&gt;HTML 구조나 UI 자체를 전달&lt;/b&gt;한다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;BaseCard&amp;gt;
  &amp;lt;h2&amp;gt;서울 날씨&amp;lt;/h2&amp;gt;
  &amp;lt;p&amp;gt;현재 기온 25도&amp;lt;/p&amp;gt;
  &amp;lt;button&amp;gt;상세보기&amp;lt;/button&amp;gt;
&amp;lt;/BaseCard&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 차이를 정리하면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;julia&quot;&gt;&lt;code&gt;Props
&amp;rarr; 데이터 전달

Slot
&amp;rarr; HTML / UI 전달
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 Slot을 사용할까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Slot의 가장 큰 장점은 &lt;b&gt;레이아웃을 재사용할 수 있다는 점&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 카드 디자인이 다음과 같이 동일하다고 해보자.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&quot;card&quot;&amp;gt;
    ...
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카드마다 내용을 직접 작성하면 비슷한 구조를 계속 반복해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Slot을 사용하면 카드의 공통 디자인은 하나만 만들고 &lt;u&gt;내부 내용만 바꿀 수 있다.&lt;/u&gt;&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;BaseCard&amp;gt;
  &amp;lt;h2&amp;gt;사용자 정보&amp;lt;/h2&amp;gt;
  &amp;lt;p&amp;gt;홍길동&amp;lt;/p&amp;gt;
&amp;lt;/BaseCard&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;BaseCard&amp;gt;
  &amp;lt;h2&amp;gt;날씨 정보&amp;lt;/h2&amp;gt;
  &amp;lt;p&amp;gt;서울 25도&amp;lt;/p&amp;gt;
&amp;lt;/BaseCard&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;BaseCard&amp;gt;
  &amp;lt;h2&amp;gt;공지사항&amp;lt;/h2&amp;gt;
  &amp;lt;p&amp;gt;서버 점검 안내&amp;lt;/p&amp;gt;
&amp;lt;/BaseCard&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 BaseCard를 계속 재사용하면서 내용만 바꾸는 것이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기본 Slot&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 기본적인 Slot은 이름이 없는 Slot이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식:&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;!-- BaseCard.vue --&amp;gt;
&amp;lt;template&amp;gt;
  &amp;lt;div class=&quot;card&quot;&amp;gt;
    &amp;lt;slot&amp;gt;&amp;lt;/slot&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모:&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;BaseCard&amp;gt;
  &amp;lt;h2&amp;gt;Vue Slot&amp;lt;/h2&amp;gt;
  &amp;lt;p&amp;gt;Slot 기본 예제입니다.&amp;lt;/p&amp;gt;
&amp;lt;/BaseCard&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모가 전달한 모든 내용이 자식의 &amp;lt;slot&amp;gt; 위치에 삽입된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Slot의 기본값&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모가 아무 내용도 전달하지 않는 경우를 대비해 기본 내용을 지정할 수도 있다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&quot;card&quot;&amp;gt;
    &amp;lt;slot&amp;gt;
      &amp;lt;p&amp;gt;기본 콘텐츠입니다.&amp;lt;/p&amp;gt;
    &amp;lt;/slot&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모가 다음처럼 사용하면&lt;/p&gt;
&lt;pre class=&quot;apache&quot;&gt;&lt;code&gt;&amp;lt;BaseCard /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면에는&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;기본 콘텐츠입니다.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가 표시된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 부모가 내용을 전달하면&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;BaseCard&amp;gt;
  &amp;lt;h2&amp;gt;새로운 내용&amp;lt;/h2&amp;gt;
&amp;lt;/BaseCard&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본값 대신 부모가 전달한 내용이 표시된다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;부모 콘텐츠 있음
&amp;rarr; 부모 콘텐츠 사용

부모 콘텐츠 없음
&amp;rarr; slot 기본값 사용
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;Named Slot&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 컴포넌트에 여러 개의 내용을 각각 다른 위치에 넣고 싶다면 &lt;b&gt;Named Slot&lt;/b&gt;을 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 카드가 다음 구조라고 해보자.&lt;/p&gt;
&lt;pre class=&quot;gauss&quot;&gt;&lt;code&gt;┌──────────────────────┐
│ Header               │
├──────────────────────┤
│ Main Content         │
├──────────────────────┤
│ Footer               │
└──────────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식 컴포넌트에서 각 Slot에 이름을 지정한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&quot;card&quot;&amp;gt;

    &amp;lt;header&amp;gt;
      &amp;lt;slot name=&quot;header&quot;&amp;gt;&amp;lt;/slot&amp;gt;
    &amp;lt;/header&amp;gt;

    &amp;lt;main&amp;gt;
      &amp;lt;slot&amp;gt;&amp;lt;/slot&amp;gt;
    &amp;lt;/main&amp;gt;

    &amp;lt;footer&amp;gt;
      &amp;lt;slot name=&quot;footer&quot;&amp;gt;&amp;lt;/slot&amp;gt;
    &amp;lt;/footer&amp;gt;

  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모에서는 각 이름에 맞는 내용을 전달한다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;BaseCard&amp;gt;

  &amp;lt;template #header&amp;gt;
    &amp;lt;h2&amp;gt;게시글 제목&amp;lt;/h2&amp;gt;
  &amp;lt;/template&amp;gt;

  &amp;lt;p&amp;gt;게시글 내용입니다.&amp;lt;/p&amp;gt;

  &amp;lt;template #footer&amp;gt;
    &amp;lt;button&amp;gt;확인&amp;lt;/button&amp;gt;
  &amp;lt;/template&amp;gt;

&amp;lt;/BaseCard&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서&lt;/p&gt;
&lt;pre class=&quot;vala&quot;&gt;&lt;code&gt;#header
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;는&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;v-slot:header
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의 축약형이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마찬가지로&lt;/p&gt;
&lt;pre class=&quot;vala&quot;&gt;&lt;code&gt;#footer
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;는&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;v-slot:footer
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 줄여 쓴 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;#header
&amp;rarr; header slot

#footer
&amp;rarr; footer slot

이름이 없는 영역
&amp;rarr; 기본 slot
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;Scoped Slot&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 Slot은 부모가 자식에게 UI를 전달하는 구조다.&lt;/p&gt;
&lt;pre class=&quot;julia&quot;&gt;&lt;code&gt;Parent
   &amp;darr;
HTML / UI
   &amp;darr;
Child
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Scoped Slot은 여기서 조금 더 나아가 &lt;b&gt;자식 컴포넌트가 가진 데이터를 부모가 받아서 화면을 구성할 수 있게 한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 자식이 사용자 데이터를 가지고 있다고 해보자.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
const userName = '홍길동'
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;slot :name=&quot;userName&quot;&amp;gt;&amp;lt;/slot&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식은 Slot을 통해 userName을 전달한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모에서는 이를 받을 수 있다.&lt;/p&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;&amp;lt;ChildComponent v-slot=&quot;slotData&quot;&amp;gt;
  &amp;lt;p&amp;gt;사용자 이름: {{ slotData.name }}&amp;lt;/p&amp;gt;
&amp;lt;/ChildComponent&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흐름은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;Child Component

userName = &quot;홍길동&quot;

      &amp;darr;

slot에 데이터 전달

      &amp;darr;

Parent Component

slotData.name

      &amp;darr;

홍길동 출력
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 Scoped Slot은 &lt;b&gt;자식이 데이터를 제공하고, 부모가 그 데이터를 이용해 어떤 HTML을 그릴지 결정하는 방식&lt;/b&gt;이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Props, Emits, Slot 비교&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue 컴포넌트 간 통신을 정리하면 다음과 같이 구분할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;Props

Parent
   &amp;darr;
Child

데이터 전달
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;Emits

Child
   &amp;darr;
Parent

이벤트 전달
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;julia&quot;&gt;&lt;code&gt;Slot

Parent
   &amp;darr;
Child의 빈 영역

HTML / UI 전달
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Scoped Slot

Child
   &amp;darr;
Parent

Slot용 데이터 전달
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단히 비교하면 다음과 같다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 87.5581%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 28.9535%;&quot;&gt;&lt;b&gt;기능&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 26.0465%;&quot;&gt;&lt;b&gt;방향&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 32.4419%;&quot;&gt;&lt;b&gt;전달하는 것&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 28.9535%;&quot;&gt;Props&lt;/td&gt;
&lt;td style=&quot;width: 26.0465%;&quot;&gt;부모 &amp;rarr; 자식&lt;/td&gt;
&lt;td style=&quot;width: 32.4419%;&quot;&gt;데이터&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 28.9535%;&quot;&gt;Emits&lt;/td&gt;
&lt;td style=&quot;width: 26.0465%;&quot;&gt;자식 &amp;rarr; 부모&lt;/td&gt;
&lt;td style=&quot;width: 32.4419%;&quot;&gt;이벤트, Payload&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 28.9535%;&quot;&gt;Slot&lt;/td&gt;
&lt;td style=&quot;width: 26.0465%;&quot;&gt;부모 &amp;rarr; 자식&lt;/td&gt;
&lt;td style=&quot;width: 32.4419%;&quot;&gt;HTML, UI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 28.9535%;&quot;&gt;Scoped Slot&lt;/td&gt;
&lt;td style=&quot;width: 26.0465%;&quot;&gt;자식 &amp;rarr; 부모&lt;/td&gt;
&lt;td style=&quot;width: 32.4419%;&quot;&gt;Slot에서 사용할 데이터&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;어떤 상황에서 Slot을 사용할까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Slot은 &lt;b&gt;외곽 구조는 같고 내부 내용만 달라지는 컴포넌트&lt;/b&gt;에 적합하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로 다음과 같은 UI가 있다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;Card
Modal
Dialog
Layout
Panel
Popup
Form Wrapper
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 Modal 컴포넌트를 만들 수 있다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&quot;modal&quot;&amp;gt;
    &amp;lt;div class=&quot;modal-content&quot;&amp;gt;
      &amp;lt;slot&amp;gt;&amp;lt;/slot&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용할 때는 내용만 바꾼다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;BaseModal&amp;gt;
  &amp;lt;h2&amp;gt;삭제 확인&amp;lt;/h2&amp;gt;
  &amp;lt;p&amp;gt;정말 삭제하시겠습니까?&amp;lt;/p&amp;gt;
&amp;lt;/BaseModal&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 다른 곳에서는&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;BaseModal&amp;gt;
  &amp;lt;h2&amp;gt;로그인&amp;lt;/h2&amp;gt;
  &amp;lt;LoginForm /&amp;gt;
&amp;lt;/BaseModal&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처럼 같은 Modal 디자인을 재사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 Slot을 이용하면 컴포넌트가 특정 내용에 &lt;u&gt;종속되지 않고 훨씬 유연해진다.&lt;/u&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;Props와 Slot을 같이 사용할 수도 있다&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Props와 Slot은 서로 대체하는 관계가 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 컴포넌트에서 동시에 사용할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 카드의 상태는 Props로 받고, 카드 내부 내용은 Slot으로 받을 수 있다.&lt;/p&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;&amp;lt;!-- BaseCard.vue --&amp;gt;

&amp;lt;script setup&amp;gt;
defineProps({
  title: String
})
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;div class=&quot;card&quot;&amp;gt;
    &amp;lt;h2&amp;gt;{{ title }}&amp;lt;/h2&amp;gt;

    &amp;lt;slot&amp;gt;&amp;lt;/slot&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모:&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;BaseCard title=&quot;사용자 정보&quot;&amp;gt;
  &amp;lt;p&amp;gt;이름: 홍길동&amp;lt;/p&amp;gt;
  &amp;lt;button&amp;gt;수정&amp;lt;/button&amp;gt;
&amp;lt;/BaseCard&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조를 보면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;title
&amp;rarr; Props

&amp;lt;p&amp;gt;, &amp;lt;button&amp;gt;
&amp;rarr; Slot
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 방식으로 데이터와 UI를 각각 적절한 방식으로 전달할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;전체 구조 정리&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Slot의 기본적인 구조를 하나로 정리하면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;        Parent Component

        &amp;lt;BaseCard&amp;gt;
          원하는 HTML
        &amp;lt;/BaseCard&amp;gt;

               │
               │ UI 전달
               ▼

         Child Component

       ┌─────────────────┐
       │                 │
       │     &amp;lt;slot&amp;gt;      │
       │                 │
       └─────────────────┘

               &amp;darr;

         실제 화면 출력
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Named Slot을 사용하면 여러 위치를 나눌 수도 있다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;          Parent

   #header   기본   #footer
       │      │       │
       ▼      ▼       ▼

┌─────────────────────────┐
│       header slot       │
├─────────────────────────┤
│       default slot      │
├─────────────────────────┤
│       footer slot       │
└─────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;정리&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Slot은 &lt;b&gt;자식 컴포넌트 내부에 부모가 원하는 HTML이나 UI를 넣을 수 있게 하는 기능&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 기본적인 형태는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;apache&quot;&gt;&lt;code&gt;&amp;lt;slot&amp;gt;&amp;lt;/slot&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모에서는&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;BaseCard&amp;gt;
  &amp;lt;h2&amp;gt;내용&amp;lt;/h2&amp;gt;
&amp;lt;/BaseCard&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처럼 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능별로 보면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Default Slot
&amp;rarr; 하나의 기본 영역에 콘텐츠 삽입

Named Slot
&amp;rarr; 여러 위치를 이름으로 구분

Scoped Slot
&amp;rarr; 자식의 데이터를 부모가 받아 UI 구성
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Props와 비교하면 차이는 더욱 명확하다.&lt;/p&gt;
&lt;pre class=&quot;julia&quot;&gt;&lt;code&gt;Props
&amp;rarr; 데이터를 전달

Slot
&amp;rarr; HTML / UI를 전달
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 한 문장으로 정리하면 다음과 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Slot은 재사용 가능한 컴포넌트의 특정 영역을 비워두고, 부모 컴포넌트가 그 영역에 원하는 HTML이나 컴포넌트를 주입할 수 있게 하는 Vue의 UI 조합 기능이다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>개발/JavaScript</category>
      <category>js</category>
      <category>named</category>
      <category>Scoped</category>
      <category>Slot</category>
      <category>vue</category>
      <category>vue.js</category>
      <category>개발</category>
      <category>뷰제이에스</category>
      <category>슬롯</category>
      <category>프론트</category>
      <author>danieLee</author>
      <guid isPermaLink="true">https://daniellee09.tistory.com/80</guid>
      <comments>https://daniellee09.tistory.com/80#entry80comment</comments>
      <pubDate>Wed, 26 Aug 2026 13:27:28 +0900</pubDate>
    </item>
    <item>
      <title>[JavaScript][Vue.js] provide / inject 정리 - 깊은 컴포넌트 간 데이터 전달</title>
      <link>https://daniellee09.tistory.com/79</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Props / Emits 는 이전 글 참조&lt;/p&gt;
&lt;figure id=&quot;og_1787709549787&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[JavaScript][Vue.js] Props와 Emits 정리 - 부모&amp;middot;자식 컴포넌트 통신&quot; data-og-description=&quot;Vue는 화면을 여러 개의 컴포넌트로 나누어 구성한다.예를 들어 하나의 페이지가 다음과 같이 구성되어 있다고 해보자.ParentComponent ├─ HeaderComponent ├─ SearchBar ├─ WeatherCard └─ FooterComponent&quot; data-og-host=&quot;daniellee09.tistory.com&quot; data-og-source-url=&quot;https://daniellee09.tistory.com/78&quot; data-og-url=&quot;https://daniellee09.tistory.com/78&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/hG6Tp/dJMb82MUXAr/i0pkbhNYal7uEhDdazEKK1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/s1SZA/dJMb9aKWDSJ/f8iELCFATZCv1PZ5cInOcK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://daniellee09.tistory.com/78&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://daniellee09.tistory.com/78&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/hG6Tp/dJMb82MUXAr/i0pkbhNYal7uEhDdazEKK1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/s1SZA/dJMb9aKWDSJ/f8iELCFATZCv1PZ5cInOcK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[JavaScript][Vue.js] Props와 Emits 정리 - 부모&amp;middot;자식 컴포넌트 통신&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Vue는 화면을 여러 개의 컴포넌트로 나누어 구성한다.예를 들어 하나의 페이지가 다음과 같이 구성되어 있다고 해보자.ParentComponent ├─ HeaderComponent ├─ SearchBar ├─ WeatherCard └─ FooterComponent&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;daniellee09.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue에서는 부모와 자식 컴포넌트 사이의 기본적인 데이터 전달 방식으로 Props와 Emits를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 컴포넌트 계층이 &lt;u&gt;깊어지면&lt;/u&gt; 단순한 Props 전달만으로는 구조가 복잡해질 수 있다.&lt;/p&gt;
&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 class=&quot;lasso&quot;&gt;&lt;code&gt;GrandParent
   &amp;darr;
Parent
   &amp;darr;
Child
   &amp;darr;
GrandChild
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GrandParent가 가진 데이터를 GrandChild에서 사용해야 한다면, Props만으로는 중간 컴포넌트들을 계속 거쳐야 한다.&lt;/p&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;GrandParent
   &amp;darr; Props
Parent
   &amp;darr; Props
Child
   &amp;darr; Props
GrandChild
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 Parent나 Child는 실제로 그 데이터를 사용하지 않더라도, 단순히 아래 컴포넌트로 전달하기 위해 Props를 받아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 현상을 &lt;b&gt;Props Drilling&lt;/b&gt;이라고 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Props Drilling&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Props Drilling은 데이터를 필요로 하지 않는 중간 컴포넌트까지 계속 Props를 전달해야 하는 상황을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 최상위 컴포넌트에서 테마 정보를 가지고 있다고 해보자.&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;const theme = ref('dark')
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 이 데이터를 사용하는 것은 가장 아래의 MenuItem 컴포넌트뿐인데, 구조가 다음과 같다면&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;App
 │
 └─ Layout
      │
      └─ Sidebar
           │
           └─ Menu
                │
                └─ MenuItem
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Props 방식에서는 다음처럼 전달해야 한다.&lt;/p&gt;
&lt;pre class=&quot;xl&quot;&gt;&lt;code&gt;App
 │ theme
 &amp;darr;
Layout
 │ theme
 &amp;darr;
Sidebar
 │ theme
 &amp;darr;
Menu
 │ theme
 &amp;darr;
MenuItem
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중간 컴포넌트들은 theme을 사용하지 않더라도 계속 전달해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 구조가 커질수록 Props 관리가 번거로워질 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;provide / inject란?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;provide / inject는 상위 컴포넌트의 데이터를 &lt;b&gt;중간 컴포넌트를 거치지 않고 깊은 하위 컴포넌트에서 직접 사용할 수 있게 하는 기능&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역할은 다음과 같이 나뉜다.&lt;/p&gt;
&lt;pre class=&quot;gradle&quot;&gt;&lt;code&gt;provide
&amp;rarr; 상위 컴포넌트가 데이터 제공

inject
&amp;rarr; 하위 컴포넌트가 데이터 사용
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조를 보면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;GrandParent
   │
   │ provide
   │
   ├─────────────────┐
   │                 │
Parent               │
   │                 │
Child                │
   │                 │
GrandChild ◀─────────┘
           inject
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중간에 있는 Parent, Child는 데이터를 전달할 필요가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 provide / inject의 핵심은 &lt;b&gt;컴포넌트 계층을 건너뛰어 데이터를 전달할 수 있다는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;provide()&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상위 컴포넌트에서는 provide()를 이용해 데이터를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 형태는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;provide('key', value)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 테마 데이터를 전달하고 싶다면 다음과 같이 작성할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
import { ref, provide } from 'vue'

const theme = ref('dark')

provide('theme', theme)
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;provide('theme', theme)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;은&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;theme이라는 이름으로 이 데이터를 하위 컴포넌트들에게 제공한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;는 의미다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 인자는 데이터를 찾기 위한 key이고, 두 번째 인자는 실제로 전달할 값이다.&lt;/p&gt;
&lt;pre class=&quot;xquery&quot;&gt;&lt;code&gt;provide(
  'theme',
  theme
)

   │
   ├─ key
   └─ value
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;inject()&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하위 컴포넌트에서는 inject()를 이용해 상위 컴포넌트가 제공한 데이터를 가져온다.&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
import { inject } from 'vue'

const theme = inject('theme')
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;p&amp;gt;현재 테마: {{ theme }}&amp;lt;/p&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;inject()의 key는 &lt;u&gt;provide()에서 사용한 key와 같아야 한다.&lt;/u&gt;&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;provide('theme', theme)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;와&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;inject('theme')
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 서로 연결된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 구조는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;상위 Component

provide('theme', theme)

          &amp;darr;

중간 Component

          &amp;darr;

하위 Component

inject('theme')
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;전체 예제&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최상위 컴포넌트에서 사용자 이름을 제공하고, 가장 아래에 있는 컴포넌트에서 해당 값을 사용하는 구조를 만들어보자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;GrandParent.vue&lt;/h3&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
import { ref, provide } from 'vue'
import ParentComponent from './ParentComponent.vue'

const userName = ref('홍길동')

provide('userName', userName)
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;ParentComponent /&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ParentComponent.vue&lt;/h3&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
import ChildComponent from './ChildComponent.vue'
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;ChildComponent /&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ParentComponent에서는 userName과 관련된 코드를 &lt;u&gt;작성하지 않는다.&lt;/u&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ChildComponent.vue&lt;/h3&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
import { inject } from 'vue'

const userName = inject('userName')
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;p&amp;gt;사용자: {{ userName }}&amp;lt;/p&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 흐름은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;GrandParent

userName = '홍길동'

provide('userName', userName)

        │
        │
        ▼

ParentComponent

        │
        │ 별도 전달 없음
        ▼

ChildComponent

inject('userName')

        &amp;darr;

홍길동
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중간 컴포넌트가 데이터를 &lt;u&gt;전달할 필요가 없다&lt;/u&gt;는 것이 가장 큰 특징이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;반응형 데이터도 전달할 수 있다&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;provide()를 통해 ref나 reactive 같은 반응형 데이터를 전달할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 상위 컴포넌트에서 다음과 같이 선언했다고 해보자.&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
import { ref, provide } from 'vue'

const theme = ref('dark')

provide('theme', theme)
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하위에서는 그대로 받을 수 있다.&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
import { inject } from 'vue'

const theme = inject('theme')
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상위 컴포넌트에서 값을 변경하면&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;theme.value = 'light'
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하위 컴포넌트에도 변경 사항이 반영된다.&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;상위

theme = dark

    &amp;darr;

provide

    &amp;darr;

inject

    &amp;darr;

하위

theme = dark
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 상위에서&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;dark
 &amp;darr;
light
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로 변경하면 하위에서도 같은 반응형 데이터를 참조하고 있기 때문에 화면이 함께 변경된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;기본값 지정&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;inject()를 사용할 때 해당 key를 제공하는 상위 컴포넌트가 없을 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 기본값을 지정할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;const theme = inject('theme', 'light')
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동작은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;xl&quot;&gt;&lt;code&gt;상위에서 theme 제공
&amp;rarr; 제공된 값 사용

상위에서 theme 제공 안 함
&amp;rarr; 'light' 사용
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트를 독립적으로 사용할 가능성이 있다면 기본값을 지정하는 방식이 도움이 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;Props와 provide / inject 차이&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Props와 provide / inject는 모두 컴포넌트 간 데이터 전달에 사용하지만 목적이 다르다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;구분&lt;/td&gt;
&lt;td&gt;Props&lt;/td&gt;
&lt;td&gt;provide / inject&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;기본 방향&lt;/td&gt;
&lt;td&gt;부모 &amp;rarr; 자식&lt;/td&gt;
&lt;td&gt;조상 &amp;rarr; 하위&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;중간 컴포넌트&lt;/td&gt;
&lt;td&gt;계속 전달 필요&lt;/td&gt;
&lt;td&gt;건너뛸 수 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;데이터 흐름 확인&lt;/td&gt;
&lt;td&gt;명확함&lt;/td&gt;
&lt;td&gt;상대적으로 숨겨짐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;사용 목적&lt;/td&gt;
&lt;td&gt;일반적인 부모&amp;middot;자식 통신&lt;/td&gt;
&lt;td&gt;깊은 계층의 공통 데이터 전달&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Props는 다음처럼 바로 연결된 관계에 적합하다.&lt;/p&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;Parent
   &amp;darr; Props
Child
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 provide / inject는 다음처럼 계층이 깊은 경우에 유용하다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;GrandParent
   │
   ├───────────────┐
   &amp;darr;               │
Parent             │
   &amp;darr;               │
Child              │
   &amp;darr;               │
GrandChild ◀───────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 모든 Props를 provide / inject로 바꾸는 것이 아니라, 각각의 목적에 맞게 사용하는 것이 중요하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;provide / inject와 Pinia의 차이&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;provide / inject와 Pinia는 모두 여러 컴포넌트에서 데이터를 사용할 수 있게 하지만 범위와 역할이 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;provide / inject는 특정 컴포넌트 계층을 기준으로 동작한다.&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;App
 │
 └─ Layout
      │
      └─ Sidebar
           │
           └─ Menu
&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;Pinia는 별도의 전역 Store를 만들어 &lt;u&gt;컴포넌트 계층과 관계없이 데이터를 공유&lt;/u&gt;한다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;Component A
      │
      │
      ▼
    Store
      ▲
      │
Component B
      │
Component C
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순하게 구분하면 다음과 같다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 79.7674%; height: 93px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px; width: 10.814%;&quot;&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px; width: 33.2558%;&quot;&gt;&lt;b&gt;provide / inject&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px; width: 35.5814%;&quot;&gt;&lt;b&gt;Pinia&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px; width: 10.814%;&quot;&gt;범위&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 33.2558%;&quot;&gt;특정 컴포넌트 트리&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 35.5814%;&quot;&gt;애플리케이션 전체&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px; width: 10.814%;&quot;&gt;목적&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 33.2558%;&quot;&gt;계층 간 데이터 전달&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 35.5814%;&quot;&gt;전역 상태 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px; width: 10.814%;&quot;&gt;구조&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 33.2558%;&quot;&gt;조상 &amp;rarr; 하위&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 35.5814%;&quot;&gt;Store 중심&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px; width: 10.814%;&quot;&gt;예&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 33.2558%;&quot;&gt;테마, 언어, 폼 설정&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 35.5814%;&quot;&gt;로그인, 장바구니, 전역 사용자 상태&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어&lt;/p&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;특정 화면 내부 테마
특정 Form의 설정
공통 UI 옵션
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등은 provide / inject로 처리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;로그인 상태
사용자 정보
장바구니
전역 알림
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 애플리케이션 전체 상태는 Pinia와 같은 상태 관리 도구가 적합하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;provide / inject 전체 흐름&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 구조를 하나로 보면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;               GrandParent

        const theme = ref('dark')

       provide('theme', theme)

                  │
                  │
        ┌─────────┴─────────┐
        │                   │
        ▼                   │
      Parent                │
        │                   │
        ▼                   │
       Child                │
        │                   │
        ▼                   │
    GrandChild ◀────────────┘

       inject('theme')
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중간 컴포넌트는 해당 데이터의 존재를 &lt;b&gt;&lt;i&gt;&lt;u&gt;몰라도 된다.&lt;/u&gt;&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 Props Drilling을 줄일 수 있는 이유다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;정리&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue의 일반적인 부모&amp;middot;자식 데이터 전달은 Props를 사용한다.&lt;/p&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;Parent
   &amp;darr; Props
Child
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 컴포넌트 계층이 깊어지면 Props를 여러 단계에 걸쳐 전달해야 하는 Props Drilling 문제가 생길 수 있다.&lt;/p&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;GrandParent
   &amp;darr;
Parent
   &amp;darr;
Child
   &amp;darr;
GrandChild
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;provide / inject를 사용하면 중간 단계를 건너뛸 수 있다.&lt;/p&gt;
&lt;pre class=&quot;vhdl&quot;&gt;&lt;code&gt;상위 Component
      │
      │ provide
      ▼
      &amp;middot;
      &amp;middot;
      &amp;middot;
      ▼
하위 Component
      │
      └─ inject
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 역할은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;provide()
&amp;rarr; 상위 컴포넌트에서 데이터 제공

inject()
&amp;rarr; 하위 컴포넌트에서 데이터 사용
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 한 문장으로 정리하면 다음과 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;provide / inject는 상위 컴포넌트가 데이터를 제공하고, 깊은 하위 컴포넌트가 중간 컴포넌트를 거치지 않고 직접 받아 사용할 수 있게 하는 Vue의 의존성 주입 방식이다.&lt;/b&gt;&lt;/p&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;Props는 일반적인 부모&amp;middot;자식 통신에 사용하고, provide / inject는 깊은 컴포넌트 계층에서 공통 데이터를 전달해야 할 때 사용하는 방식으로 구분하면 된다.&lt;/p&gt;</description>
      <category>개발/JavaScript</category>
      <category>Component</category>
      <category>emits</category>
      <category>Inject</category>
      <category>JavaScript</category>
      <category>js</category>
      <category>Props</category>
      <category>provide</category>
      <category>vue</category>
      <category>vue.js</category>
      <category>프론트</category>
      <author>danieLee</author>
      <guid isPermaLink="true">https://daniellee09.tistory.com/79</guid>
      <comments>https://daniellee09.tistory.com/79#entry79comment</comments>
      <pubDate>Wed, 26 Aug 2026 11:03:34 +0900</pubDate>
    </item>
    <item>
      <title>[JavaScript][Vue.js] Props와 Emits 정리 - 부모&amp;middot;자식 컴포넌트 통신</title>
      <link>https://daniellee09.tistory.com/78</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Vue는 화면을 여러 개의 컴포넌트로 나누어 구성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 하나의 페이지가 다음과 같이 구성되어 있다고 해보자.&lt;/p&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;ParentComponent
   ├─ HeaderComponent
   ├─ SearchBar
   ├─ WeatherCard
   └─ FooterComponent
&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;Vue에서는 이때 기본적으로 다음 두 가지 방식을 사용한다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;Props
&amp;rarr; 부모 &amp;rarr; 자식

Emits
&amp;rarr; 자식 &amp;rarr; 부모
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉,&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터는 부모에서 자식으로 내려가고, 이벤트는 자식에서 부모로 올라간다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조를 보통 &lt;b&gt;Props Down, Events Up&lt;/b&gt;이라고 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Props란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Props는 &lt;b&gt;부모 컴포넌트가 자식 컴포넌트에게 데이터를 전달하는 방법&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 부모 컴포넌트가 도시 이름을 가지고 있다고 해보자.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
import WeatherCard from './WeatherCard.vue'

const cityName = '서울'
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;WeatherCard :city-name=&quot;cityName&quot; /&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서&lt;/p&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt;:city-name=&quot;cityName&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;은 부모의 cityName 데이터를 자식 컴포넌트에 전달한다는 의미다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식 컴포넌트에서는 defineProps()를 이용해 받을 데이터를 정의한다.&lt;/p&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
defineProps({
  cityName: String
})
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;p&amp;gt;{{ cityName }}&amp;lt;/p&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 흐름은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;Parent Component
      │
      │ Props
      ▼
Child Component

cityName = &quot;서울&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 Props는 &lt;b&gt;부모가 가진 데이터를 자식에게 내려주는 통로&lt;/b&gt;다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;defineProps()&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Props를 받을 때 사용하는 함수가 defineProps()다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 간단하게는 다음처럼 작성할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;defineProps({
  cityName: String,
  temperature: Number
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금 더 세밀하게 지정할 수도 있다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;defineProps({
  cityName: {
    type: String,
    required: true
  },

  temperature: {
    type: Number,
    default: 20
  }
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 각각 다음 의미를 가진다.&lt;/p&gt;
&lt;pre class=&quot;autoit&quot;&gt;&lt;code&gt;cityName
&amp;rarr; String
&amp;rarr; 반드시 전달 필요

temperature
&amp;rarr; Number
&amp;rarr; 전달되지 않으면 기본값 20
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Props는 단순히 데이터를 받는 것뿐 아니라, 어떤 타입의 값을 받을지 규격을 정할 수도 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Props는 읽기 전용이다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모에게 받은 Props는 자식 컴포넌트에서 직접 수정하면 안 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 코드는 잘못된 방식이다.&lt;/p&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;const props = defineProps({
  count: Number
})

props.count = 100
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Props는 부모가 소유한 데이터이기 때문이다.&lt;/p&gt;
&lt;pre class=&quot;haskell&quot;&gt;&lt;code&gt;Parent
  │
  │ owns data
  ▼
Child
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식 컴포넌트는 전달받은 값을 읽을 수는 있지만, 부모의 원본 상태를 &lt;u&gt;직접 바꾸는 역할을 하지 않는다.&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Props는 다음과 같이 기억하면 된다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;Props
&amp;rarr; 부모 &amp;rarr; 자식
&amp;rarr; 데이터 전달
&amp;rarr; 읽기 전용
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식에서 부모 상태를 변경하고 싶다면 직접 수정하는 대신 &lt;u&gt;이벤트를 발생&lt;/u&gt;시켜 부모에게 변경을 요청해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 사용하는 것이 &lt;b&gt;Emits&lt;/b&gt;다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;Emits란?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Emits는 &lt;b&gt;자식 컴포넌트에서 발생한 일을 부모 컴포넌트에게 전달하는 방식&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 WeatherCard를 클릭했을 때 선택된 도시 이름을 부모에게 전달한다고 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식 컴포넌트에서는 먼저 사용할 이벤트를 정의한다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
const emit = defineEmits(['select-city'])
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 실제 이벤트를 발생시킨다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;emit('select-city', '서울')
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;select-city
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;는 이벤트 이름이고,&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;서울
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;은 부모에게 함께 전달할 데이터다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식 컴포넌트를 전체로 보면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
const emit = defineEmits(['select-city'])

const handleClick = () =&amp;gt; {
  emit('select-city', '서울')
}
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;button @click=&quot;handleClick&quot;&amp;gt;
    도시 선택
  &amp;lt;/button&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모 컴포넌트에서는 @를 사용해 이벤트를 받는다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
import WeatherCard from './WeatherCard.vue'

const handleSelectCity = (city) =&amp;gt; {
  console.log(city)
}
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;WeatherCard @select-city=&quot;handleSelectCity&quot; /&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흐름은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;vhdl&quot;&gt;&lt;code&gt;Child Component
      │
      │ emit
      ▼
Parent Component

select-city
+
&quot;서울&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 Emits는 &lt;b&gt;자식이 부모에게 이벤트를 발생시키고, 필요한 데이터를 함께 전달하는 통로&lt;/b&gt;다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;defineEmits()&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;defineEmits()는 자식 컴포넌트가 어떤 이벤트를 발생시킬지 정의하는 함수다.&lt;/p&gt;
&lt;pre class=&quot;sml&quot;&gt;&lt;code&gt;const emit = defineEmits([
  'select-city',
  'delete-item'
])
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 이벤트를 발생시킬 때는 다음과 같이 사용한다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;emit('select-city', cityName)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조를 보면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;defineEmits()
&amp;rarr; 사용할 이벤트 정의

emit()
&amp;rarr; 실제 이벤트 발생
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;Props와 Emits를 함께 사용하기&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Props와 Emits는 따로 떨어진 기능이라기보다 부모와 자식 컴포넌트 사이의 &lt;u&gt;하나의 통신 구조&lt;/u&gt;로 이해하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 부모가 자식에게 도시 정보를 전달하고, 자식이 클릭되면 다시 부모에게 선택된 도시를 알려준다고 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;부모 컴포넌트:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
import { ref } from 'vue'
import WeatherCard from './WeatherCard.vue'

const selectedCity = ref('선택된 도시 없음')

const handleSelectCity = (cityName) =&amp;gt; {
  selectedCity.value = cityName
}
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;p&amp;gt;선택된 도시: {{ selectedCity }}&amp;lt;/p&amp;gt;

  &amp;lt;WeatherCard
    city-name=&quot;서울&quot;
    status=&quot;맑음&quot;
    @select-city=&quot;handleSelectCity&quot;
  /&amp;gt;
&amp;lt;/template&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;b&gt;자식 컴포넌트:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
const props = defineProps({
  cityName: String,
  status: String
})

const emit = defineEmits(['select-city'])

const handleClick = () =&amp;gt; {
  emit('select-city', props.cityName)
}
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;div @click=&quot;handleClick&quot;&amp;gt;
    &amp;lt;h3&amp;gt;{{ cityName }}&amp;lt;/h3&amp;gt;
    &amp;lt;p&amp;gt;{{ status }}&amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 흐름은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;Parent
  │
  │ Props
  │
  ├─ cityName = 서울
  └─ status = 맑음
  │
  ▼
WeatherCard
  │
  │ 사용자 클릭
  │
  │ emit('select-city', '서울')
  ▼
Parent
  │
  └─ selectedCity = 서울
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모가 데이터를 내려주고,&lt;/p&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;Parent
   &amp;darr;
Props
   &amp;darr;
Child
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식은 발생한 이벤트를 다시 부모에게 알린다.&lt;/p&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;Child
   &amp;darr;
Emits
   &amp;darr;
Parent
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두 방향을 합치면 Vue의 기본적인 부모&amp;middot;자식 통신 구조가 완성된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;:와 @의 의미&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모 컴포넌트에서 자식 컴포넌트를 사용할 때 Props와 Emits는 보통 다음 형태로 함께 나타난다.&lt;/p&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt;&amp;lt;WeatherCard
  :city-name=&quot;cityName&quot;
  @select-city=&quot;handleSelectCity&quot;
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 :는 v-bind의 축약형이다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;:
&amp;rarr; v-bind
&amp;rarr; 자식에게 데이터 전달
&amp;rarr; Props
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@는 v-on의 축약형이다.&lt;/p&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;@
&amp;rarr; v-on
&amp;rarr; 자식 이벤트 수신
&amp;rarr; Emits
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 다음 코드는&lt;/p&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt;&amp;lt;ChildComponent
  :data=&quot;value&quot;
  @update=&quot;handleUpdate&quot;
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 해석할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;:data=&quot;value&quot;
&amp;rarr; 부모의 value를 자식에게 전달

@update=&quot;handleUpdate&quot;
&amp;rarr; 자식이 update 이벤트를 발생시키면
   부모의 handleUpdate 실행
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;Payload&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;emit()을 사용할 때 이벤트와 함께 부모에게 데이터를 전달할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 데이터를 &lt;b&gt;Payload&lt;/b&gt;라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;emit('select-city', '서울')
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 '서울'이 Payload다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모에서는 이벤트 핸들러의 매개변수로 받을 수 있다.&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;const handleSelectCity = (cityName) =&amp;gt; {
  console.log(cityName)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 데이터를 객체로 묶어 전달하는 것도 가능하다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;emit('select-city', {
  id: 1,
  name: '서울',
  temperature: 28
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모에서는 다음처럼 처리한다.&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;const handleSelectCity = (city) =&amp;gt; {
  console.log(city.id)
  console.log(city.name)
  console.log(city.temperature)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 Emits는 단순한 신호뿐 아니라 필요한 데이터까지 함께 전달할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;왜 자식이 Props를 직접 수정하면 안 될까?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Props를 읽기 전용으로 두는 이유는 데이터의 흐름을 &lt;u&gt;명확하게 만들기 위해서다.&lt;/u&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;pre class=&quot;properties&quot;&gt;&lt;code&gt;Parent
  &amp;darr;
Child A
  &amp;darr;
Child B
&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;Vue에서는 이를 다음과 같이 단순화한다.&lt;/p&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;상태를 가진 곳
&amp;rarr; Parent

데이터 전달
&amp;rarr; Props

변경 요청
&amp;rarr; Emits

실제 상태 변경
&amp;rarr; Parent
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 &lt;b&gt;데이터의 소유권은 부모에게 두고, 자식은 변경이 필요하다는 이벤트만 전달하는 방식&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 단방향 흐름 덕분에 상태 변화를 추적하기 쉬워진다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;컴포넌트 통신 전체 구조&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Props와 Emits를 하나의 그림으로 정리하면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;              Parent Component
                    │
                    │ Props
                    │
                    ▼
              Child Component
                    │
                    │ Emits
                    │
                    ▼
              Parent Component
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좀 더 구체적으로 표현하면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt;         Parent State

        cityName = 서울
              │
              │ :city-name
              ▼
        ┌─────────────┐
        │ WeatherCard │
        └─────────────┘
              │
              │ 사용자 클릭
              │
              │ emit
              ▼
       @select-city
              │
              ▼
      handleSelectCity()
              │
              ▼
       Parent State 변경
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조가 Vue 컴포넌트 통신의 가장 기본적인 형태다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;정리&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Props와 Emits는 Vue에서 부모와 자식 컴포넌트가 통신하는 기본 방식이다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 70.3486%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.3624%;&quot;&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 21.5796%;&quot;&gt;&lt;b&gt;Props&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 23.7154%;&quot;&gt;&lt;b&gt;Emits&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.3624%;&quot;&gt;방향&lt;/td&gt;
&lt;td style=&quot;width: 21.5796%;&quot;&gt;부모 &amp;rarr; 자식&lt;/td&gt;
&lt;td style=&quot;width: 23.7154%;&quot;&gt;자식 &amp;rarr; 부모&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.3624%;&quot;&gt;목적&lt;/td&gt;
&lt;td style=&quot;width: 21.5796%;&quot;&gt;데이터 전달&lt;/td&gt;
&lt;td style=&quot;width: 23.7154%;&quot;&gt;이벤트 전달&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.3624%;&quot;&gt;정의&lt;/td&gt;
&lt;td style=&quot;width: 21.5796%;&quot;&gt;defineProps()&lt;/td&gt;
&lt;td style=&quot;width: 23.7154%;&quot;&gt;defineEmits()&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.3624%;&quot;&gt;부모 Template&lt;/td&gt;
&lt;td style=&quot;width: 21.5796%;&quot;&gt;:prop=&quot;value&quot;&lt;/td&gt;
&lt;td style=&quot;width: 23.7154%;&quot;&gt;@event=&quot;handler&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.3624%;&quot;&gt;데이터 변경&lt;/td&gt;
&lt;td style=&quot;width: 21.5796%;&quot;&gt;자식 직접 수정 X&lt;/td&gt;
&lt;td style=&quot;width: 23.7154%;&quot;&gt;부모에게 변경 요청&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.3624%;&quot;&gt;전달 데이터&lt;/td&gt;
&lt;td style=&quot;width: 21.5796%;&quot;&gt;상태 값&lt;/td&gt;
&lt;td style=&quot;width: 23.7154%;&quot;&gt;Payload 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 핵심인 구조는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Props Down
Events Up
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉,&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;부모
 │
 │ Props
 ▼
자식
 │
 │ Emits
 ▼
부모
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue에서는 부모가 상태를 관리하고 자식에게 필요한 데이터를 &lt;u&gt;Props로 전달&lt;/u&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식 컴포넌트에서 상태 변경이 필요하면 부모의 데이터를 직접 수정하는 대신 &lt;u&gt;Emits를 통해 이벤트를 발생&lt;/u&gt;시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 Props와 Emits는 &lt;b&gt;컴포넌트 사이의 데이터 흐름을 한 방향으로 명확하게 유지하기 위한 Vue의 기본 통신 구조&lt;/b&gt;라고 정리할 수 있다.&lt;/p&gt;</description>
      <category>개발/JavaScript</category>
      <category>Component</category>
      <category>emits</category>
      <category>JavaScript</category>
      <category>js</category>
      <category>Props</category>
      <category>vue.js</category>
      <category>개발</category>
      <category>뷰</category>
      <category>뷰제이에스</category>
      <category>프론트</category>
      <author>danieLee</author>
      <guid isPermaLink="true">https://daniellee09.tistory.com/78</guid>
      <comments>https://daniellee09.tistory.com/78#entry78comment</comments>
      <pubDate>Wed, 26 Aug 2026 10:49:35 +0900</pubDate>
    </item>
    <item>
      <title>[JavaScript][Vue.js] Component Lifecycle과 Lifecycle Hook 정리</title>
      <link>https://daniellee09.tistory.com/77</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Vue의 컴포넌트는 단순히 화면에 나타나기만 하는 것이 아니라, 생성되고 화면에 붙고, 데이터 변화에 따라 갱신되고, 마지막에는 화면에서 제거되는 과정을 거친다.&lt;/p&gt;
&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;Component Lifecycle&lt;/b&gt;이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 정리하면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;생성
 &amp;darr;
마운트
 &amp;darr;
업데이트
 &amp;darr;
언마운트
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Vue는 각 단계마다 특정 시점에 원하는 코드를 실행할 수 있도록 &lt;b&gt;Lifecycle Hook&lt;/b&gt;을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로 다음과 같은 함수들이 있다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;onMounted()
onUpdated()
onUnmounted()
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, Lifecycle은 컴포넌트의 전체 생명주기이고, Lifecycle Hook은 그 생명주기의 특정 시점에 실행되는 함수라고 보면 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Component Lifecycle이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트는 화면에 등장하기 전부터 내부적으로 여러 단계를 거친다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 Vue 컴포넌트가 있다고 해보자.&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
import { ref } from 'vue'

const count = ref(0)
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;button @click=&quot;count++&quot;&amp;gt;
    {{ count }}
  &amp;lt;/button&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 컴포넌트가 실행되면 내부적으로 다음과 같은 흐름을 거친다.&lt;/p&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;컴포넌트 생성
      &amp;darr;
반응형 데이터 준비
      &amp;darr;
DOM에 연결
      &amp;darr;
화면 표시
      &amp;darr;
count 변경
      &amp;darr;
화면 업데이트
      &amp;darr;
컴포넌트 제거
&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;Vue에서는 이 과정을 크게 다음 네 단계로 나눌 수 있다.&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;Creation
Mounting
Updating
Unmounting
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;생성 단계 Creation&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성 단계는 컴포넌트가 메모리상에 만들어지는 시점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 단계에서는 &amp;lt;script setup&amp;gt; 내부의 코드가 실행되고, ref, reactive, computed, watch 같은 반응형 데이터와 로직이 준비된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
import { ref } from 'vue'

const count = ref(0)

console.log('컴포넌트 생성')
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 시점에는 컴포넌트가 아직 실제 브라우저 DOM에 완전히 붙기 전이다.&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;pre class=&quot;&quot;&gt;&lt;code&gt;컴포넌트 메모리 생성
       &amp;darr;
반응형 상태 초기화
       &amp;darr;
함수와 계산 로직 준비
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마운트 단계 Mounting&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마운트는 컴포넌트가 실제 브라우저 DOM에 연결되는 단계다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &amp;lt;template&amp;gt;에 작성한 div, button, input 같은 요소가 실제 화면에 만들어진 상태다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 시점에 사용하는 대표적인 Lifecycle Hook이 onMounted()다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
import { onMounted } from 'vue'

onMounted(() =&amp;gt; {
  console.log('컴포넌트가 DOM에 연결됨')
})
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;onMounted()는 컴포넌트가 실제 화면에 붙은 뒤 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흐름을 보면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;컴포넌트 생성
      &amp;darr;
Virtual DOM 구성
      &amp;darr;
Real DOM에 연결
      &amp;darr;
onMounted()
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 시점에는 실제 DOM 요소가 존재하므로 DOM 접근이 가능하다.&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;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
import { ref, onMounted } from 'vue'
import axios from 'axios'

const users = ref([])

onMounted(async () =&amp;gt; {
  const response = await axios.get('/api/users')

  users.value = response.data
})
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 흐름은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;컴포넌트 생성
      &amp;darr;
DOM에 연결
      &amp;darr;
onMounted()
      &amp;darr;
API 요청
      &amp;darr;
데이터 저장
      &amp;darr;
화면 갱신
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;업데이트 단계 Updating&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue에서 ref나 reactive 같은 반응형 데이터가 변경되면 화면도 함께 변경된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 코드가 있다고 해보자.&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
import { ref } from 'vue'

const count = ref(0)
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;button @click=&quot;count++&quot;&amp;gt;
    {{ count }}
  &amp;lt;/button&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼을 누르면 count 값이 변경된다.&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;count = 0
   &amp;darr;
버튼 클릭
   &amp;darr;
count = 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue는 이 변화를 감지하고 화면을 다시 갱신한다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;반응형 데이터 변경
        &amp;darr;
Vue가 변경 감지
        &amp;darr;
Virtual DOM 갱신
        &amp;darr;
변경된 부분 확인
        &amp;darr;
Real DOM 반영
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업데이트가 완료된 이후 실행되는 Hook이 onUpdated()다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
import { onUpdated } from 'vue'

onUpdated(() =&amp;gt; {
  console.log('화면 업데이트 완료')
})
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 데이터 변경 이후 DOM 상태를 확인해야 하는 경우 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 onUpdated()는 컴포넌트가 업데이트될 때마다 실행될 수 있기 때문에 무거운 작업을 넣는 것은 피하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 onUpdated() 내부에서 또 다른 반응형 데이터를 계속 변경하면 반복적인 업데이트가 발생할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;언마운트 단계 Unmounting&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언마운트는 컴포넌트가 화면에서 제거되는 단계다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 부모 컴포넌트가 있다고 해보자.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;ChildComponent v-if=&quot;isVisible&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;isVisible이 false가 되면 ChildComponent는 화면에서 제거된다.&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;isVisible = true
      &amp;darr;
ChildComponent 존재

isVisible = false
      &amp;darr;
ChildComponent 제거
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 시점에 사용하는 대표적인 Hook이 onUnmounted()다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
import { onUnmounted } from 'vue'

onUnmounted(() =&amp;gt; {
  console.log('컴포넌트 제거 완료')
})
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언마운트 단계에서 중요한 것은 &lt;b&gt;Cleanup 작업&lt;/b&gt;이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 Cleanup이 필요할까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트가 사라졌다고 해서 모든 JavaScript 작업이 자동으로 종료되는 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 컴포넌트에서 타이머를 실행했다고 해보자.&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;const timerId = setInterval(() =&amp;gt; {
  console.log('실행 중')
}, 1000)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트가 화면에서 제거되어도 setInterval()은 계속 실행될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 그대로 두면 불필요한 연산이 계속 발생하고 &lt;u&gt;메모리 누수&lt;/u&gt;의 원인이 될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 컴포넌트가 제거될 때 타이머도 함께 정리한다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
import { onMounted, onUnmounted } from 'vue'

let timerId

onMounted(() =&amp;gt; {
  timerId = setInterval(() =&amp;gt; {
    console.log('실행 중')
  }, 1000)
})

onUnmounted(() =&amp;gt; {
  clearInterval(timerId)
})
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 흐름은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;컴포넌트 생성
      &amp;darr;
onMounted()
      &amp;darr;
setInterval 시작
      &amp;darr;
컴포넌트 사용
      &amp;darr;
컴포넌트 제거
      &amp;darr;
onUnmounted()
      &amp;darr;
clearInterval()
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타이머뿐만 아니라 전역 이벤트 리스너 같은 작업도 같은 방식으로 정리할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Before Hook도 있다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue에는 각 주요 단계가 실행되기 직전 시점을 다룰 수 있는 Hook도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;onBeforeMount()
onBeforeUpdate()
onBeforeUnmount()
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 흐름을 보면 이해하기 쉽다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;컴포넌트 생성
      &amp;darr;
onBeforeMount()
      &amp;darr;
DOM에 연결
      &amp;darr;
onMounted()
      &amp;darr;
데이터 변경
      &amp;darr;
onBeforeUpdate()
      &amp;darr;
DOM 업데이트
      &amp;darr;
onUpdated()
      &amp;darr;
컴포넌트 제거 시작
      &amp;darr;
onBeforeUnmount()
      &amp;darr;
DOM에서 제거
      &amp;darr;
onUnmounted()
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;주요 Lifecycle Hook 정리&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Lifecycle Hook&lt;/td&gt;
&lt;td&gt;실행 시점&lt;/td&gt;
&lt;td&gt;주요 용도&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;onBeforeMount()&lt;/td&gt;
&lt;td&gt;DOM에 연결되기 직전&lt;/td&gt;
&lt;td&gt;마운트 전 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;onMounted()&lt;/td&gt;
&lt;td&gt;DOM에 연결된 후&lt;/td&gt;
&lt;td&gt;API 호출, DOM 접근&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;onBeforeUpdate()&lt;/td&gt;
&lt;td&gt;DOM 업데이트 직전&lt;/td&gt;
&lt;td&gt;업데이트 전 상태 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;onUpdated()&lt;/td&gt;
&lt;td&gt;DOM 업데이트 완료 후&lt;/td&gt;
&lt;td&gt;변경된 DOM 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;onBeforeUnmount()&lt;/td&gt;
&lt;td&gt;컴포넌트 제거 직전&lt;/td&gt;
&lt;td&gt;제거 전 정리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;onUnmounted()&lt;/td&gt;
&lt;td&gt;컴포넌트 제거 후&lt;/td&gt;
&lt;td&gt;타이머, 이벤트 정리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 다음 세 가지를 중심으로 이해하면 된다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;onMounted()
&amp;rarr; 컴포넌트가 화면에 붙은 뒤

onUpdated()
&amp;rarr; 화면이 업데이트된 뒤

onUnmounted()
&amp;rarr; 컴포넌트가 제거된 뒤
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;onMounted와 API 호출&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lifecycle Hook 중 onMounted()는 초기 데이터 조회와 연결되는 경우가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 날씨 데이터를 가져오는 컴포넌트를 만든다고 해보자.&lt;/p&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
import { ref, onMounted } from 'vue'
import axios from 'axios'

const weather = ref(null)

onMounted(async () =&amp;gt; {
  const response = await axios.get('/api/weather')

  weather.value = response.data
})
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;div v-if=&quot;weather&quot;&amp;gt;
    {{ weather.temperature }}
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흐름은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;Weather Component 생성
          &amp;darr;
DOM Mount
          &amp;darr;
onMounted()
          &amp;darr;
Backend API 호출
          &amp;darr;
JSON 응답
          &amp;darr;
weather 변경
          &amp;darr;
화면 업데이트
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조는 Lifecycle과 Vue의 반응형 데이터가 어떻게 연결되는지를 보여준다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;전체 Lifecycle 흐름&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Component Lifecycle 전체를 하나로 정리하면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;        Component 생성
               &amp;darr;
        &amp;lt;script setup&amp;gt;
               &amp;darr;
       onBeforeMount()
               &amp;darr;
          DOM Mount
               &amp;darr;
          onMounted()
               &amp;darr;
        ┌─────────────┐
        │             │
        │ 데이터 변경 │
        │             │
        └──────┬──────┘
               &amp;darr;
      onBeforeUpdate()
               &amp;darr;
         DOM Update
               &amp;darr;
          onUpdated()
               &amp;darr;
       데이터가 변경되면
       위 과정 반복
               &amp;darr;
     onBeforeUnmount()
               &amp;darr;
        DOM에서 제거
               &amp;darr;
        onUnmounted()
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 컴포넌트는 단순히 한 번 렌더링되는 것이 아니라, 상태 변화에 따라 여러 번 업데이트되다가 필요 없어지면 제거된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Component Lifecycle은 &lt;b&gt;Vue 컴포넌트가 생성되고 제거될 때까지 거치는 전체 과정&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크게 네 단계로 구분할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;Creation
   &amp;darr;
Mounting
   &amp;darr;
Updating
   &amp;darr;
Unmounting
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Vue는 각 단계에 맞춰 코드를 실행할 수 있도록 Lifecycle Hook을 제공한다.&lt;/p&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;Mount
&amp;rarr; onMounted()

Update
&amp;rarr; onUpdated()

Unmount
&amp;rarr; onUnmounted()
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 중요한 흐름은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;컴포넌트 생성
      &amp;darr;
화면에 연결
      &amp;darr;
초기 데이터 요청
      &amp;darr;
사용자의 입력
      &amp;darr;
반응형 데이터 변경
      &amp;darr;
화면 업데이트
      &amp;darr;
컴포넌트 제거
      &amp;darr;
타이머 / 이벤트 정리
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 문장으로 정리하면 다음과 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Component Lifecycle은 Vue 컴포넌트가 생성, 마운트, 업데이트, 언마운트되는 전체 생명주기이며, Lifecycle Hook은 각 시점에 원하는 로직을 실행할 수 있도록 제공되는 함수다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/JavaScript</category>
      <category>Component</category>
      <category>JavaScript</category>
      <category>js</category>
      <category>Lifecycle</category>
      <category>vue.js</category>
      <category>개발</category>
      <category>뷰</category>
      <category>뷰제이에스</category>
      <category>자바스크립트</category>
      <category>프론트</category>
      <author>danieLee</author>
      <guid isPermaLink="true">https://daniellee09.tistory.com/77</guid>
      <comments>https://daniellee09.tistory.com/77#entry77comment</comments>
      <pubDate>Wed, 26 Aug 2026 10:37:30 +0900</pubDate>
    </item>
    <item>
      <title>[JavaScript][Vue.js] Composition API란? - Options API와의 차이부터 ref, computed, watch까지</title>
      <link>https://daniellee09.tistory.com/76</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Vue 3에서 컴포넌트의 로직을 작성하는 대표적인 방식이 &lt;b&gt;Composition API&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Composition API는 컴포넌트에서 사용하는 &lt;b&gt;데이터, 함수, 계산된 값, 감시 로직 등을 하나의 기능 단위로 묶어서 작성하는 방식&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue 2에서는 주로 Options API를 사용했지만, Vue 3에서는 Composition API와 &amp;lt;script setup&amp;gt; 방식이 기본적인 작성 방식으로 사용된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Composition API란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Composition은 말 그대로 여러 로직을 &lt;b&gt;조합(Composition)&lt;/b&gt;한다는 의미다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 &lt;u&gt;카운터 기능&lt;/u&gt;을 만든다고 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카운터에는 최소한 다음과 같은 요소가 필요하다.&lt;/p&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;count 데이터
증가 함수
감소 함수
count를 이용한 계산값
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Composition API에서는 이와 관련된 코드를 한곳에 모아 작성할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
import { ref, computed } from 'vue'

const count = ref(0)

const increment = () =&amp;gt; {
  count.value++
}

const decrement = () =&amp;gt; {
  count.value--
}

const doubleCount = computed(() =&amp;gt; {
  return count.value * 2
})
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;p&amp;gt;현재 값: {{ count }}&amp;lt;/p&amp;gt;
    &amp;lt;p&amp;gt;2배 값: {{ doubleCount }}&amp;lt;/p&amp;gt;

    &amp;lt;button @click=&quot;increment&quot;&amp;gt;증가&amp;lt;/button&amp;gt;
    &amp;lt;button @click=&quot;decrement&quot;&amp;gt;감소&amp;lt;/button&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카운터와 관련된 상태와 함수가 하나의 영역에 모여 있다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;[카운터 기능]

count
increment()
decrement()
doubleCount
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 &lt;b&gt;기능과 관련된 로직을 하나의 묶음으로 구성하는 것&lt;/b&gt;이 Composition API의 핵심이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기존 Options API와 무엇이 다른가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue 2에서는 주로 &lt;b&gt;Options API&lt;/b&gt;를 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Options API는 컴포넌트의 코드를 역할별로 나눈다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;export default {
  data() {
    return {
      count: 0
    }
  },

  computed: {
    doubleCount() {
      return this.count * 2
    }
  },

  methods: {
    increment() {
      this.count++
    }
  },

  watch: {
    count(newValue) {
      console.log(newValue)
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조를 보면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;haskell&quot;&gt;&lt;code&gt;data
&amp;rarr; 데이터

computed
&amp;rarr; 계산된 값

methods
&amp;rarr; 함수

watch
&amp;rarr; 데이터 감시
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;기능이 아니라 역할을 기준으로 코드를 나누는 방식&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트의 규모가 작다면 구조가 명확하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 하나의 기능이 커지면 관련 코드가 여러 영역에 흩어질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 로그인 기능을 만든다고 해보자.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;data
 └─ user

computed
 └─ isLoggedIn

methods
 ├─ login()
 └─ logout()

watch
 └─ user 감시
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인이라는 하나의 기능을 수정하기 위해 data, computed, methods, watch 영역을 각각 확인해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Composition API는 이를 기능 중심으로 모은다.&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;const user = ref(null)

const isLoggedIn = computed(() =&amp;gt; {
  return user.value !== null
})

const login = () =&amp;gt; {
  user.value = {
    name: 'Kim'
  }
}

const logout = () =&amp;gt; {
  user.value = null
}

watch(user, () =&amp;gt; {
  console.log('로그인 상태 변경')
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조를 보면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;[로그인 기능]

user
isLoggedIn
login()
logout()
watch()
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자료에서도 Options API는 data, methods, computed와 같은 정해진 옵션에 코드를 나누는 방식이고, Composition 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;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 88.2558%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.907%;&quot;&gt;&lt;b&gt;구분&amp;nbsp;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 31.6279%;&quot;&gt;&lt;b&gt;Options API&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 28.6047%;&quot;&gt;&lt;b&gt;Composition API&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.907%;&quot;&gt;코드 구성 기준&lt;/td&gt;
&lt;td style=&quot;width: 31.6279%;&quot;&gt;역할별&lt;/td&gt;
&lt;td style=&quot;width: 28.6047%;&quot;&gt;기능별&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.907%;&quot;&gt;상태 선언&lt;/td&gt;
&lt;td style=&quot;width: 31.6279%;&quot;&gt;data()&lt;/td&gt;
&lt;td style=&quot;width: 28.6047%;&quot;&gt;ref(), reactive()&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.907%;&quot;&gt;함수&lt;/td&gt;
&lt;td style=&quot;width: 31.6279%;&quot;&gt;methods&lt;/td&gt;
&lt;td style=&quot;width: 28.6047%;&quot;&gt;일반 JavaScript 함수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.907%;&quot;&gt;계산값&lt;/td&gt;
&lt;td style=&quot;width: 31.6279%;&quot;&gt;computed 옵션&lt;/td&gt;
&lt;td style=&quot;width: 28.6047%;&quot;&gt;computed()&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.907%;&quot;&gt;감시&lt;/td&gt;
&lt;td style=&quot;width: 31.6279%;&quot;&gt;watch 옵션&lt;/td&gt;
&lt;td style=&quot;width: 28.6047%;&quot;&gt;watch()&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.907%;&quot;&gt;작성 방식&lt;/td&gt;
&lt;td style=&quot;width: 31.6279%;&quot;&gt;&amp;lt;script&amp;gt;&lt;/td&gt;
&lt;td style=&quot;width: 28.6047%;&quot;&gt;주로 &amp;lt;script setup&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.907%;&quot;&gt;Vue 기준&lt;/td&gt;
&lt;td style=&quot;width: 31.6279%;&quot;&gt;Vue 2 중심&lt;/td&gt;
&lt;td style=&quot;width: 28.6047%;&quot;&gt;Vue 3 표준&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;&amp;lt;script setup&amp;gt;은 무엇인가?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Composition API는 Vue 3.2부터 &amp;lt;script setup&amp;gt; 안에서 간결하게 작성할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue의 Single File Component는 기본적으로 다음 구조를 가진다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
  // JavaScript
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;!-- HTML --&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;style scoped&amp;gt;
  /* CSS */
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 영역의 역할은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
&amp;rarr; 데이터
&amp;rarr; 함수
&amp;rarr; 비즈니스 로직

&amp;lt;template&amp;gt;
&amp;rarr; 화면 구조

&amp;lt;style&amp;gt;
&amp;rarr; 디자인
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자료에서도 Vue 3의 SFC는 &amp;lt;script setup&amp;gt;, &amp;lt;template&amp;gt;, &amp;lt;style&amp;gt;의 구조로 구성되며, &amp;lt;script setup&amp;gt;에서 데이터와 함수 등의 기능 로직을 작성한다고 설명한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 간단한 카운터는 다음과 같이 작성할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
import { ref } from 'vue'

const count = ref(0)

const increment = () =&amp;gt; {
  count.value++
}
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;button @click=&quot;increment&quot;&amp;gt;
    {{ count }}
  &amp;lt;/button&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Options API처럼&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;export default {
  data() {},
  methods: {}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조를 만들 필요 없이 일반 JavaScript처럼 변수와 함수를 선언한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;Composition API의 핵심 함수&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Composition API는 Vue에서 제공하는 여러 함수를 조합해서 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 기능은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;ref()
reactive()
computed()
watch()
watchEffect()

onMounted()
onUpdated()
onUnmounted()
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자료에서도 Vue 3 내장 기능을 Reactive State, Computed &amp;amp; Watchers, Lifecycle Hooks 등으로 구분하고 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ref()&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ref()는 값을 &lt;b&gt;반응형 상태(Reactive State)&lt;/b&gt;로 만드는 함수다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;import { ref } from 'vue'

const count = ref(0)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 JavaScript 변수와 달리 ref()로 만든 데이터는 값이 변경되면 Vue가 그 변화를 감지하고 화면을 업데이트한다.&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
import { ref } from 'vue'

const count = ref(0)

const increment = () =&amp;gt; {
  count.value++
}
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;p&amp;gt;{{ count }}&amp;lt;/p&amp;gt;

  &amp;lt;button @click=&quot;increment&quot;&amp;gt;
    증가
  &amp;lt;/button&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;script setup&amp;gt;에서는 다음처럼 .value를 사용한다.&lt;/p&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;count.value++
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 &amp;lt;template&amp;gt;에서는 .value가 필요 없다.&lt;/p&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;&amp;lt;p&amp;gt;{{ count }}&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;

count.value


&amp;lt;template&amp;gt;

count
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자료에서도 ref()는 원시 타입뿐 아니라 배열과 객체도 반응형 상태로 만들 수 있으며, script에서는 .value, template에서는 .value 없이 사용한다고 설명한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;reactive()&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;reactive() 역시 반응형 데이터를 만드는 함수다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주로 객체나 배열과 같은 참조형 데이터를 다룬다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;import { reactive } from 'vue'

const user = reactive({
  name: '홍길동',
  age: 20
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ref()와 달리 .value를 사용하지 않는다.&lt;/p&gt;
&lt;pre class=&quot;brainfuck&quot;&gt;&lt;code&gt;user.age++
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Template에서도 동일하다.&lt;/p&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;&amp;lt;p&amp;gt;{{ user.name }}&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;{{ user.age }}&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 reactive() 객체 자체를 새로운 객체로 재할당하면 반응성 연결이 끊어질 수 있다는 점을 주의해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 방식은 피해야 한다.&lt;/p&gt;
&lt;pre class=&quot;pf&quot;&gt;&lt;code&gt;let state = reactive({
  count: 0
})

state = {
  count: 10
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신 내부 속성을 변경한다.&lt;/p&gt;
&lt;pre class=&quot;pf&quot;&gt;&lt;code&gt;state.count = 10
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는 객체와 배열까지 ref()로 통일해서 사용하는 방식도 가능하다.&lt;/p&gt;
&lt;pre class=&quot;pf&quot;&gt;&lt;code&gt;const state = ref({
  count: 0
})

state.value.count = 10
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;computed()&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;computed()는 기존 반응형 데이터를 이용해서 &lt;b&gt;새로운 값을 계산할 때&lt;/b&gt; 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 상품 가격과 수량이 있다고 해보자.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const price = ref(10000)
const quantity = ref(3)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;총 가격은 다음과 같이 계산할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;const totalPrice = computed(() =&amp;gt; {
  return price.value * quantity.value
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Template에서는 다음과 같이 사용한다.&lt;/p&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;&amp;lt;p&amp;gt;총 가격: {{ totalPrice }}원&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;price
   +
quantity
   &amp;darr;
computed
   &amp;darr;
totalPrice
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;price 또는 quantity가 변경되면 totalPrice도 자동으로 다시 계산된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;computed()의 중요한 특징은 &lt;b&gt;캐싱(Caching)&lt;/b&gt;이다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const doubleCount = computed(() =&amp;gt; {
  return count.value * 2
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;count 값이 바뀌지 않았다면 이전 계산 결과를 재사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 함수가 컴포넌트가 다시 렌더링될 때마다 재실행되는 반면, computed()는 의존하는 반응형 데이터가 변경됐을 때만 다시 계산된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;값을 만들어서 화면에 보여주고 싶다
&amp;rarr; computed
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라고 생각하면 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;watch()&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;watch()는 특정 반응형 데이터가 변경되는 것을 감시하고, 변경되었을 때 특정 작업을 수행한다.&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;import { ref, watch } from 'vue'

const city = ref('서울')

watch(city, (newValue, oldValue) =&amp;gt; {
  console.log(oldValue)
  console.log(newValue)
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;city가&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;서울
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에서&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;수원
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;으로 변경되면 watch()의 callback이 실행된다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;city 변경
   &amp;darr;
watch 감지
   &amp;darr;
callback 실행
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;watch()는 다음과 같은 상황에서 활용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;검색어 변경
&amp;rarr; API 다시 호출

선택 도시 변경
&amp;rarr; 날씨 데이터 다시 조회

로그인 상태 변경
&amp;rarr; 사용자 정보 저장

설정값 변경
&amp;rarr; LocalStorage 저장
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;watch()를 반응형 데이터의 변경을 감지한 뒤 서버 통신이나 데이터 저장 같은 후속 로직을 실행하기 위한 함수로 설명할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;watchEffect()&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;watchEffect() 역시 데이터를 감시하지만 watch()와 차이가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;watch()는 감시할 변수를 직접 지정한다.&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;watch(city, () =&amp;gt; {

})
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 watchEffect()는 내부에서 사용하는 반응형 데이터를 Vue가 자동으로 추적한다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;watchEffect(() =&amp;gt; {
  console.log(city.value)
  console.log(age.value)
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue가 내부 코드를 확인한 뒤&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;city
age
&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;또한 watchEffect()는 컴포넌트가 생성될 때 최초 한 번 즉시 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;watchEffect()는 감시 대상을 별도로 지정하지 않고 함수 내부에서 접근한 반응형 데이터를 자동으로 추적하며, 최초 실행 시에도 즉시 실행된다.&lt;/p&gt;
&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;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;watch()&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;watchEffect()&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;감시 대상&lt;/td&gt;
&lt;td&gt;직접 지정&lt;/td&gt;
&lt;td&gt;자동 추적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;이전 값&lt;/td&gt;
&lt;td&gt;사용 가능&lt;/td&gt;
&lt;td&gt;사용 불가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;현재 값&lt;/td&gt;
&lt;td&gt;사용 가능&lt;/td&gt;
&lt;td&gt;내부에서 직접 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;최초 실행&lt;/td&gt;
&lt;td&gt;기본적으로 X&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;Lifecycle Hook&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트에도 생성부터 제거까지의 생명주기가 있다.&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;생성
 &amp;darr;
Mount
 &amp;darr;
Update
 &amp;darr;
Unmount
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Composition API에서는 이 시점에 맞춰 특정 함수를 실행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로 다음 세 가지가 있다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;onMounted()
onUpdated()
onUnmounted()
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;onMounted()&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트가 실제 DOM에 연결된 이후 실행된다.&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;import { onMounted } from 'vue'

onMounted(() =&amp;gt; {
  console.log('화면에 컴포넌트가 생성됨')
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기 API 요청 등을 처리할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;onMounted(async () =&amp;gt; {
  const response = await axios.get('/api/users')
})
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;onUpdated()&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반응형 데이터가 변경되고 화면이 다시 그려진 이후 실행된다.&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;onUpdated(() =&amp;gt; {
  console.log('화면 업데이트 완료')
})
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;onUnmounted()&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트가 화면에서 제거된 이후 실행된다.&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;onUnmounted(() =&amp;gt; {
  console.log('컴포넌트 제거 완료')
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타이머나 이벤트 리스너를 정리할 때 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;onUnmounted(() =&amp;gt; {
  clearInterval(timerId)
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mount 시점은 초기 DOM 조작과 API 호출, Unmount 시점은 타이머 및 이벤트 리스너 정리 등에 활용하는 단계이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;하나의 기능으로 묶어보면&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Composition API의 장점은 각각의 함수 자체보다 &lt;b&gt;이 함수들을 하나의 기능 단위로 조합할 수 있다는 점&lt;/b&gt;에 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 상품 검색 기능을 만든다고 해보자.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
import { ref, computed, watch } from 'vue'

const searchKeyword = ref('')

const products = ref([
  { id: 1, name: '노트북' },
  { id: 2, name: '키보드' },
  { id: 3, name: '마우스' }
])

const filteredProducts = computed(() =&amp;gt; {
  return products.value.filter((product) =&amp;gt;
    product.name.includes(searchKeyword.value)
  )
})

watch(searchKeyword, (newKeyword) =&amp;gt; {
  console.log('검색어 변경:', newKeyword)
})

const clearSearch = () =&amp;gt; {
  searchKeyword.value = ''
}
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 기능과 관련된 코드가 한곳에 정리된다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;[상품 검색 기능]

searchKeyword
products
filteredProducts
watch()
clearSearch()
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 Composition API가 말하는 &lt;b&gt;Composition&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 기능에 필요한 상태와 로직을 하나의 묶음으로 구성하는 것이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;Composable&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Composition API의 기능 묶음은 별도의 JavaScript 함수로 분리할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 &lt;b&gt;Composable&lt;/b&gt;이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 여러 컴포넌트에서 공통으로 사용하는 카운터 기능이 있다고 해보자.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// useCounter.js

import { ref } from 'vue'

export function useCounter() {
  const count = ref(0)

  const increment = () =&amp;gt; {
    count.value++
  }

  const decrement = () =&amp;gt; {
    count.value--
  }

  return {
    count,
    increment,
    decrement
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트에서는 필요한 기능을 가져온다.&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
import { useCounter } from './useCounter'

const {
  count,
  increment,
  decrement
} = useCounter()
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 기능을 별도의 JavaScript 모듈로 분리했기 때문에 여러 컴포넌트에서 재사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;useCounter()

        &amp;darr;

Component A
Component B
Component C
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Composable을 Vue의 반응형 상태와 로직을 묶어서 재사용할 수 있도록 만든 함수로 설명한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;Composition API 전체 구조&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 살펴본 내용을 하나로 정리하면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;            Composition API

                  │
       ┌──────────┼──────────┐
       │          │          │
       &amp;darr;          &amp;darr;          &amp;darr;

   Reactive    Computed    Watcher
    State

       │          │          │
       &amp;darr;          &amp;darr;          &amp;darr;

    ref()      computed()   watch()
  reactive()              watchEffect()

                  │
                  &amp;darr;

          Lifecycle Hooks

         onMounted()
         onUpdated()
         onUnmounted()

                  │
                  &amp;darr;

             Component
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 Composition API는 특정 함수 하나를 의미하는 것이 아니다.&lt;/p&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;ref
+
reactive
+
computed
+
watch
+
Lifecycle Hook
+
일반 JavaScript 함수
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 기능들을 조합해서 &lt;b&gt;하나의 컴포넌트 로직을 구성하는 방식 전체&lt;/b&gt;를 의미한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;정리&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Options API와 Composition API의 가장 중요한 차이는 &lt;b&gt;코드를 어떤 기준으로 구성하느냐&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Options API는 역할을 기준으로 나눈다.&lt;/p&gt;
&lt;pre class=&quot;haskell&quot;&gt;&lt;code&gt;data
methods
computed
watch
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Composition API는 기능을 기준으로 묶는다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;로그인 기능

user
isLoggedIn
login()
logout()
watch()
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Vue 3에서는 다음과 같은 형태로 구성한다.&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
import {
  ref,
  computed,
  watch,
  onMounted
} from 'vue'

const data = ref(...)

const result = computed(() =&amp;gt; {
  ...
})

watch(data, () =&amp;gt; {
  ...
})

const functionName = () =&amp;gt; {
  ...
}

onMounted(() =&amp;gt; {
  ...
})
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심을 한 문장으로 정리하면 다음과 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Composition API는 Vue 컴포넌트에서 사용하는 상태, 함수, 계산 로직, 감시 로직 등을 기능 단위로 조합해서 작성하는 Vue 3의 코드 구성 방식이다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Composition API를 이해한 뒤에는 ref(), reactive(), computed(), watch()를 각각 구체적으로 정리하면 Vue의 반응형 데이터 처리 구조를 이해하기 쉬워진다.&lt;/p&gt;</description>
      <category>개발/JavaScript</category>
      <category>API</category>
      <category>composition</category>
      <category>JavaScript</category>
      <category>js</category>
      <category>vue</category>
      <category>vue.js</category>
      <category>개발</category>
      <category>뷰</category>
      <category>자바스크립트</category>
      <category>프론트</category>
      <author>danieLee</author>
      <guid isPermaLink="true">https://daniellee09.tistory.com/76</guid>
      <comments>https://daniellee09.tistory.com/76#entry76comment</comments>
      <pubDate>Wed, 26 Aug 2026 10:29:39 +0900</pubDate>
    </item>
    <item>
      <title>[JavaScript][Vue.js] Vue Directive 문법 정리 - v-bind부터 v-if, v-for까지</title>
      <link>https://daniellee09.tistory.com/75</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Vue에서 &amp;lt;template&amp;gt;을 작성하다 보면 일반 HTML에서는 볼 수 없었던 속성들이 등장한다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;v-html
v-bind
v-if
v-show
v-for
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 v-로 시작하는 Vue 전용 특수 속성을 &lt;b&gt;Directive(디렉티브)&lt;/b&gt;라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Directive를 사용하면 HTML 태그와 JavaScript 데이터를 연결하여 화면의 내용, 속성, 스타일, 조건, 반복 등을 동적으로 제어할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue의 Directive에서 특히 중요한 점은 다음과 같은 코드에서&lt;/p&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt;&amp;lt;img :src=&quot;imageUrl&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;imageUrl&quot;이 단순한 문자열이 아니라 &lt;b&gt;JavaScript 표현식이 실행되는 영역&lt;/b&gt;이라는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 Vue의 Template은 단순한 HTML이 아니라 JavaScript 데이터와 연결되어 동적으로 변경되는 화면이라고 볼 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;데이터를 화면에 출력하는 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue에서 데이터를 화면에 출력하는 가장 기본적인 방법은 {{ }}를 사용하는 것이다.&lt;/p&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
const message = 'Hello Vue'
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;p&amp;gt;{{ message }}&amp;lt;/p&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면에는 다음과 같이 출력된다.&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;Hello Vue
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 &lt;b&gt;Text Interpolation(텍스트 보간법)&lt;/b&gt;이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 문자열 안에 HTML 코드가 들어 있다면 상황이 조금 달라진다.&lt;/p&gt;
&lt;pre class=&quot;vbnet&quot;&gt;&lt;code&gt;const message = '&amp;lt;strong&amp;gt;Hello Vue&amp;lt;/strong&amp;gt;'
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 보간법으로 출력하면&lt;/p&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;&amp;lt;p&amp;gt;{{ message }}&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면에는 HTML이 적용되는 것이 아니라 문자열 그대로 출력된다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;&amp;lt;strong&amp;gt;Hello Vue&amp;lt;/strong&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;v-html&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열을 실제 HTML Element로 해석해서 출력하고 싶다면 v-html을 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
const rawHtmlData =
  '이 글자는 &amp;lt;span style=&quot;color:red; font-weight:bold;&quot;&amp;gt;빨간색 굵은 글자&amp;lt;/span&amp;gt;이다.'
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;p v-html=&quot;rawHtmlData&quot;&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;v-html은 JavaScript의 innerHTML과 유사하게 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념적으로 보면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;haskell&quot;&gt;&lt;code&gt;{{ data }}
    &amp;darr;
문자열 그대로 출력

v-html=&quot;data&quot;
    &amp;darr;
HTML로 해석하여 출력
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 v-html을 사용할 때는 &lt;b&gt;XSS(Cross-Site Scripting)&lt;/b&gt;에 주의해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 입력한 값을 그대로 v-html에 넣으면 해당 문자열이 HTML로 해석되기 때문에 악의적인 코드가 브라우저에서 실행될 위험이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게시판이나 입력창에 악성 코드가 삽입되고, 다른 사용자가 이를 조회할 때 실행되는 상황을 XSS의 대표적인 위험이다.&lt;/p&gt;
&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 class=&quot;clojure&quot;&gt;&lt;code&gt;{{ data }}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 사용하고, &lt;b&gt;신뢰할 수 있는 HTML을 반드시 렌더링해야 할 때만 v-html을 사용하는 것이 좋다.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;v-text&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;v-text도 데이터를 텍스트로 출력하는 Directive다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
const content = '안녕하세요! Vue 강의입니다.'
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;p v-text=&quot;content&quot;&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 일반 보간법과 거의 같은 역할을 한다.&lt;/p&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;&amp;lt;p&amp;gt;{{ content }}&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자료에서도 v-text는 텍스트 내용을 채워 넣으며, 일반적인 경우에는 v-text보다 {{ }}를 주로 사용하는 방식으로 설명하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 세 가지를 비교하면 다음과 같다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 67.093%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 32.4419%;&quot;&gt;&lt;b&gt;문법&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 34.5349%;&quot;&gt;&lt;b&gt;역할&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 32.4419%;&quot;&gt;{{ data }}&lt;/td&gt;
&lt;td style=&quot;width: 34.5349%;&quot;&gt;데이터를 텍스트로 출력&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 32.4419%;&quot;&gt;v-text=&quot;data&quot;&lt;/td&gt;
&lt;td style=&quot;width: 34.5349%;&quot;&gt;데이터를 텍스트로 출력&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 32.4419%;&quot;&gt;v-html=&quot;data&quot;&lt;/td&gt;
&lt;td style=&quot;width: 34.5349%;&quot;&gt;데이터를 HTML로 해석하여 출력&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;HTML 속성과 Vue 데이터를 연결하는 v-bind&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue에서 가장 많이 사용하는 Directive 중 하나가 v-bind다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;v-bind는 &lt;b&gt;HTML의 Attribute와 JavaScript 데이터를 동적으로 연결하는 기능&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 일반 HTML에서는 이미지 주소를 다음과 같이 고정한다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;&amp;lt;img src=&quot;/images/logo.png&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue에서는 이미지 주소를 변수로 관리할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;vbnet&quot;&gt;&lt;code&gt;
const logoImgSrc = '&amp;lt;a href=https://vuejs.org/images/logo.png'&amp;gt;https://vuejs.org/images/logo.png'&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue에서 v-bind는 매우 자주 사용되기 때문에 일반적으로 다음과 같이 :로 축약한다.&lt;/p&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt;&amp;lt;img :src=&quot;logoImgSrc&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;v-bind:src=&quot;logoImgSrc&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;와&lt;/p&gt;
&lt;pre class=&quot;clojure&quot;&gt;&lt;code&gt;:src=&quot;logoImgSrc&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;는 같은 의미다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;href, src, disabled 등 다양한 HTML 속성과 연결할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;a :href=&quot;dynamicUrl&quot;&amp;gt;사이트 이동&amp;lt;/a&amp;gt;

&amp;lt;img :src=&quot;logoImgSrc&quot;&amp;gt;

&amp;lt;button :disabled=&quot;isButtonDisabled&quot;&amp;gt;
  버튼
&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체적인 구조는 다음과 같이 이해하면 된다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;JavaScript 데이터
        &amp;darr;
      v-bind
        &amp;darr;
HTML Attribute
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;class도 동적으로 변경할 수 있다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;v-bind는 단순히 src, href와 같은 속성만 연결하는 것이 아니다.&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;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;p :class=&quot;{ 'text-danger': isWarning }&quot;&amp;gt;
  경고 메시지
&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;isWarning이&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;true
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이면 text-danger 클래스가 적용되고,&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;false
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이면 클래스가 제거된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;isWarning = true
        &amp;darr;
class=&quot;text-danger&quot;

isWarning = false
        &amp;darr;
클래스 제거
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue에서는 여러 개의 클래스를 배열 형태로 조합하는 것도 가능하다.&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;&amp;lt;div
  :class=&quot;[
    themeClass,
    isWarning ? 'border-red' : 'border-gray'
  ]&quot;
&amp;gt;
  내용
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;:class&lt;/b&gt;가 객체와 배열 형식을 지원하며, 객체에서는 Boolean 조건으로 클래스 포함 여부를 정하고 배열에서는 여러 클래스를 조합할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;style도 동적으로 변경할 수 있다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인라인 스타일 역시 v-bind로 연결할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
const textColor = 'purple'
const fontSize = 20
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;p :style=&quot;{
    color: textColor,
    fontSize: fontSize + 'px'
  }&quot;&amp;gt;
    Hello Vue
  &amp;lt;/p&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue에서는 style 객체의 CSS 속성명을 일반적으로 camelCase로 작성한다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;backgroundColor
fontSize
fontWeight
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 스타일 객체를 합칠 수도 있다.&lt;/p&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;&amp;lt;div :style=&quot;[baseStyle, borderStyle]&quot;&amp;gt;
  Box
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;:style은 객체와 배열을 지원하며, 실시간으로 색상이나 크기 같은 CSS 값을 변경할 때 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 대부분의 일반적인 디자인 변경은 :style보다 :class를 사용하는 것이 관리하기 편하다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;:class
&amp;rarr; 미리 만들어놓은 CSS 디자인을 조건에 따라 적용

:style
&amp;rarr; 색상, 크기 등의 값을 직접 동적으로 변경
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다크모드나 경고 상태처럼 이미 정의한 디자인을 전환할 때는 :class가 적합하고, 프로그레스바 너비처럼 숫자가 계속 변하는 경우에는 :style이 적합하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Vue 3.4의 v-bind 축약 문법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue 3.4부터는 HTML 속성명과 JavaScript 변수명이 같다면 더 짧게 작성할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는&lt;/p&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt;&amp;lt;img :src=&quot;src&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라고 작성했다면,&lt;/p&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt;&amp;lt;img :src&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처럼 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어&lt;/p&gt;
&lt;pre class=&quot;vbnet&quot;&gt;&lt;code&gt;
const id = 'user-profile-card'
const src = '&amp;lt;a href=https://vuejs.org/images/logo.png'&amp;gt;https://vuejs.org/images/logo.png'&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&lt;img /&gt;&lt;/div&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;pre class=&quot;clojure&quot;&gt;&lt;code&gt;:src=&quot;src&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에서 이름이 동일하기 때문에&lt;/p&gt;
&lt;pre class=&quot;clojure&quot;&gt;&lt;code&gt;:src
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만 남긴 것이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;조건에 따라 화면을 바꾸는 v-if와 v-show&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 화면에서는 로그인 여부나 데이터 상태에 따라 특정 요소를 보여주거나 숨겨야 하는 경우가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue에서는 이를 위해 v-if를 사용한다.&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
import { ref } from 'vue'

const isLogged = ref(false)
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;p v-if=&quot;isLogged&quot;&amp;gt;
    환영합니다.
  &amp;lt;/p&amp;gt;

  &amp;lt;p v-else&amp;gt;
    로그인이 필요합니다.
  &amp;lt;/p&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;isLogged 값에 따라서 두 요소 중 하나가 렌더링된다.&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;isLogged = true

&amp;rarr; 환영합니다.


isLogged = false

&amp;rarr; 로그인이 필요합니다.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JavaScript의 if / else if / else처럼 여러 조건도 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;&amp;lt;div v-if=&quot;score &amp;gt;= 90&quot;&amp;gt;
  A 학점
&amp;lt;/div&amp;gt;

&amp;lt;div v-else-if=&quot;score &amp;gt;= 80&quot;&amp;gt;
  B 학점
&amp;lt;/div&amp;gt;

&amp;lt;div v-else-if=&quot;score &amp;gt;= 70&quot;&amp;gt;
  C 학점
&amp;lt;/div&amp;gt;

&amp;lt;div v-else&amp;gt;
  F 학점
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;v-if, v-else-if, v-else는 조건에 따라 &lt;b&gt;실제 DOM 요소를 생성하거나 제거하는 방식&lt;/b&gt;으로 동작한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;v-show는 무엇이 다를까&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;v-show도 조건에 따라 화면을 보여주거나 숨긴다.&lt;/p&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;&amp;lt;div v-show=&quot;isVisible&quot;&amp;gt;
  화면에 표시할 영역
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 v-if와 동작 방식이 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;v-if는 조건이 false이면 실제 DOM에서 요소를 제거한다.&lt;/p&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;v-if = false

DOM에서 제거
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 v-show는 DOM 요소를 그대로 유지한 상태에서 CSS를 사용한다.&lt;/p&gt;
&lt;pre class=&quot;scss&quot;&gt;&lt;code&gt;display: none;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉&lt;/p&gt;
&lt;pre class=&quot;maxima&quot;&gt;&lt;code&gt;v-show = false

DOM에는 존재
&amp;darr;
display: none
&amp;darr;
화면에서만 숨김
&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;table style=&quot;letter-spacing: 0px; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt; 구분 &lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt; v-if &lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt; v-show &lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;false일 때&lt;/td&gt;
&lt;td&gt;DOM에서 제거&lt;/td&gt;
&lt;td&gt;DOM 유지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;동작 방식&lt;/td&gt;
&lt;td&gt;DOM 생성/삭제&lt;/td&gt;
&lt;td&gt;display 변경&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;초기 렌더링 비용&lt;/td&gt;
&lt;td&gt;낮음&lt;/td&gt;
&lt;td&gt;상대적으로 높음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;반복적인 화면 전환&lt;/td&gt;
&lt;td&gt;상대적으로 부담&lt;/td&gt;
&lt;td&gt;유리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v-else 사용&lt;/td&gt;
&lt;td&gt;가능&lt;/td&gt;
&lt;td&gt;불가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 화면이 거의 변경되지 않는 경우라면 v-if를,&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;로그인 권한
관리자 메뉴
특정 데이터 존재 여부
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처럼 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 자주 열고 닫는 UI라면 v-show가 적합하다.&lt;/p&gt;
&lt;pre class=&quot;arcade&quot;&gt;&lt;code&gt;Modal
Tab
Accordion
Dropdown
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이와 같은 기준으로 v-if와 v-show를 구분하고 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;반복되는 화면을 만드는 v-for&lt;/h1&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;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;const fruits = ['사과', '바나나', '딸기']
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML을 직접 작성한다면&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;li&amp;gt;사과&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;바나나&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;딸기&amp;lt;/li&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처럼 각각 만들어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue에서는 v-for를 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;&amp;lt;li
  v-for=&quot;fruit in fruits&quot;
  :key=&quot;fruit&quot;
&amp;gt;
  {{ fruit }}
&amp;lt;/li&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열의 index도 함께 받을 수 있다.&lt;/p&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;&amp;lt;li
  v-for=&quot;(fruit, index) in fruits&quot;
  :key=&quot;index&quot;
&amp;gt;
  {{ index + 1 }}번 과일: {{ fruit }}
&amp;lt;/li&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조를 보면&lt;/p&gt;
&lt;pre class=&quot;xquery&quot;&gt;&lt;code&gt;fruits

['사과', '바나나', '딸기']

          &amp;darr;

        v-for

          &amp;darr;

&amp;lt;li&amp;gt;사과&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;바나나&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;딸기&amp;lt;/li&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 역시 반복할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;const user = {
  name: '홍길동',
  age: 25,
  role: '개발자'
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;&amp;lt;li
  v-for=&quot;(value, key) in user&quot;
  :key=&quot;key&quot;
&amp;gt;
  {{ key }} : {{ value }}
&amp;lt;/li&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 실제 개발에서 가장 자주 만나는 형태는 &lt;b&gt;객체를 가진 배열&lt;/b&gt;이다.&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;const items = [
  { id: 'prod_101', name: '아이폰' },
  { id: 'prod_102', name: '갤럭시' }
]
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;&amp;lt;li
  v-for=&quot;item in items&quot;
  :key=&quot;item.id&quot;
&amp;gt;
  {{ item.name }}
&amp;lt;/li&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;v-for를 사용할 때 Vue가 각각의 요소를 고유하게 구분할 수 있도록 :key를 함께 지정하는 것을 중요한 규칙으로 설명한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 일반적으로&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;v-for=&quot;item in items&quot;
:key=&quot;item.id&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 하나의 세트처럼 기억하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 고유한 id가 있다면 index보다 해당 id를 key로 사용하는 방식이 적절하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;알아두면 좋은 기타 Directive&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞에서 본 Directive들은 Vue 개발에서 매우 자주 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 다음 Directive들은 사용 빈도는 상대적으로 낮지만 Vue의 렌더링 원리를 이해하는 데 도움이 된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;v-pre&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;v-pre는 Vue에게&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 영역은 Vue 문법으로 해석하지 말고 그대로 보여줘.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라고 알려주는 Directive다.&lt;/p&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
const message = '안녕하세요!'
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;p&amp;gt;{{ message }}&amp;lt;/p&amp;gt;

  &amp;lt;p v-pre&amp;gt;
    {{ message }}
  &amp;lt;/p&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째는&lt;/p&gt;
&lt;pre class=&quot;erlang-repl&quot;&gt;&lt;code&gt;안녕하세요!
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가 출력되지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;v-pre가 적용된 두 번째는&lt;/p&gt;
&lt;pre class=&quot;clojure&quot;&gt;&lt;code&gt;{{ message }}
&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;Vue의 Template Compiler가 해당 영역을 해석하지 않는 것이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;v-cloak&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue가 완전히 로딩되기 전에 화면이 먼저 나타나면 아주 잠깐&lt;/p&gt;
&lt;pre class=&quot;clojure&quot;&gt;&lt;code&gt;{{ message }}
&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;이를 방지하기 위한 Directive가 v-cloak이다.&lt;/p&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;&amp;lt;div v-cloak&amp;gt;
  {{ message }}
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 CSS와 함께 사용한다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;[v-cloak] {
  display: none !important;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue의 렌더링이 끝날 때까지 화면을 숨겼다가, 준비가 완료되면 표시하는 방식이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;v-once&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue의 반응형 데이터는 값이 변경되면 화면도 다시 갱신된다.&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;이럴 때 v-once를 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;&amp;lt;p v-once&amp;gt;
  {{ count }}
&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;count가 처음에 1이었다면 이 영역은 최초 렌더링 이후 count가 변경돼도 계속 1을 유지한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;최초 count = 1
       &amp;darr;
v-once 렌더링
       &amp;darr;
화면 : 1
       &amp;darr;
count = 2
       &amp;darr;
화면 : 여전히 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 최초 한 번만 렌더링하고 이후의 반응형 업데이트를 받지 않는다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;v-memo&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;v-memo는 조금 더 선택적인 렌더링 최적화 기능이다.&lt;/p&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;&amp;lt;div v-memo=&quot;[name]&quot;&amp;gt;
  &amp;lt;p&amp;gt;이름: {{ name }}&amp;lt;/p&amp;gt;
  &amp;lt;p&amp;gt;나이: {{ age }}&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 name을 감시 대상으로 지정했다.&lt;/p&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;name 변경
   &amp;darr;
해당 영역 다시 렌더링

age만 변경
   &amp;darr;
기존 렌더링 결과 재사용
&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;일반적인 작은 화면에서는 자주 사용할 필요가 없지만, 렌더링 비용이 큰 UI를 최적화해야 할 때 사용할 수 있는 Directive다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;Vue Directive 전체 흐름 정리&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 살펴본 Directive를 역할별로 묶으면 생각보다 간단하다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;Vue Directive
│
├─ 콘텐츠 출력
│   ├─ v-html
│   └─ v-text
│
├─ 데이터 연결
│   └─ v-bind (:)
│       ├─ Attribute
│       ├─ Class
│       └─ Style
│
├─ 조건
│   ├─ v-if
│   └─ v-show
│
├─ 반복
│   └─ v-for
│
└─ 렌더링 제어
    ├─ v-pre
    ├─ v-cloak
    ├─ v-once
    └─ v-memo
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 중 처음 Vue를 공부할 때 가장 중요하게 볼 것은 다음 정도다.&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;{{ }}
&amp;rarr; 화면에 데이터 출력

:
&amp;rarr; v-bind
&amp;rarr; HTML 속성에 데이터 연결

v-if
&amp;rarr; 조건에 따라 DOM 생성/삭제

v-show
&amp;rarr; 조건에 따라 표시/숨김

v-for
&amp;rarr; 배열이나 객체 반복 출력
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 다음 단계에서 여기에&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;@click
v-model
&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;그러면 Vue Template의 기본적인 화면 제어 문법은 거의 완성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 Vue 코드를 보면 다음과 같은 형태가 매우 자주 등장한다.&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
import { ref } from 'vue'

const isLoggedIn = ref(true)

const users = ref([
  { id: 1, name: 'Kim' },
  { id: 2, name: 'Lee' },
  { id: 3, name: 'Park' }
])
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;div v-if=&quot;isLoggedIn&quot;&amp;gt;

    &amp;lt;h2&amp;gt;사용자 목록&amp;lt;/h2&amp;gt;

    &amp;lt;ul&amp;gt;
      &amp;lt;li
        v-for=&quot;user in users&quot;
        :key=&quot;user.id&quot;
      &amp;gt;
        {{ user.name }}
      &amp;lt;/li&amp;gt;
    &amp;lt;/ul&amp;gt;

  &amp;lt;/div&amp;gt;

  &amp;lt;p v-else&amp;gt;
    로그인이 필요합니다.
  &amp;lt;/p&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 여러 문법이 섞여 있어 복잡해 보이지만 하나씩 해석하면 단순하다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;v-if
&amp;rarr; 로그인 상태 확인

v-for
&amp;rarr; users 반복

:key
&amp;rarr; 각각의 사용자 식별

{{ user.name }}
&amp;rarr; 사용자 이름 출력
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 Vue의 Template 문법은 &lt;b&gt;JavaScript 데이터와 HTML 화면을 연결하기 위한 문법&lt;/b&gt;이라고 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue에서는 개발자가 Real DOM을 직접 찾아서 수정하기보다,&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;데이터를 선언하고
      &amp;darr;
Directive로 HTML과 연결하고
      &amp;darr;
데이터를 변경하면
      &amp;darr;
Vue가 화면을 갱신한다
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라는 흐름으로 개발하게 된다.&lt;/p&gt;</description>
      <category>개발/JavaScript</category>
      <category>JavaScript</category>
      <category>js</category>
      <category>v</category>
      <category>V-Class</category>
      <category>vue</category>
      <category>vue.js</category>
      <category>문법</category>
      <category>뷰제이에스</category>
      <category>자바스크립트</category>
      <category>프론트</category>
      <author>danieLee</author>
      <guid isPermaLink="true">https://daniellee09.tistory.com/75</guid>
      <comments>https://daniellee09.tistory.com/75#entry75comment</comments>
      <pubDate>Mon, 24 Aug 2026 22:16:45 +0900</pubDate>
    </item>
  </channel>
</rss>