<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Delight</title>
    <link>https://dreamyard.tistory.com/</link>
    <description>한발 한발 나아가는 Delight(기쁨)
개발일지는 이곳에서 &amp;rarr; https://velog.io/@jaidy/posts</description>
    <language>ko</language>
    <pubDate>Sun, 14 Jun 2026 00:38:20 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>DREYA</managingEditor>
    <image>
      <title>Delight</title>
      <url>https://tistory1.daumcdn.net/tistory/6017397/attach/fd4a3704ce74448d903ffa141fb781a1</url>
      <link>https://dreamyard.tistory.com</link>
    </image>
    <item>
      <title>[TypeScript ] 비동기 호출 - API 에러 핸들링</title>
      <link>https://dreamyard.tistory.com/entry/Typescript-%EB%B9%84%EB%8F%99%EA%B8%B0-%ED%98%B8%EC%B6%9C-API-%EC%97%90%EB%9F%AC-%ED%95%B8%EB%93%A4%EB%A7%81</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;비동기 호출을 하다보면 다양한 상태코드에 따라 401(인증되지 않은 사용자), 404(존재하지 않는 리소스), 500(서버 내부 에러) 혹은 CORS 에러와 같은 다양한 에러가 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입 스크립트에선 이러한 비동기 API 에러를 어떻게 처리 및 명시할 수 있는지 6가지 정리해보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 타입 가드 활용하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입 가드를 활용하면 서버 에러를 명시적으로 확인할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 명시적으로 표시하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 서버에서 전달하는 공통 에러 객체에 대해 타입을 정의할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// 서버에서 전달하는 공통 에러 객체 타입 정의
interface ErrorResponse {
 status: string;
 errorCode: string;
 errorMessage: string;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) Axios 라이브러리의 isAxiosError&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ErrorResponse 인터페이스를 사용해 처리해야할 Axios 에러 형태는 AxiosError로 표현되며, 아래와 같이 명시할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입 가드 정의 시에는 parameterName is Type 형태의 타입 명제를 정의해주는 것이 좋고, parameterName은 타입 가드 &lt;b&gt;함수의 시그니처*&lt;/b&gt;에 포함된 매개변수여아 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;*함수 시그니처 :&lt;/b&gt; 함수 이름, 매개변수의 타입, 반환 타입을 정의하는 부분&lt;/p&gt;
&lt;pre class=&quot;xquery&quot;&gt;&lt;code&gt;function isServerError(error: unknown): error is AxiosError&amp;lt;ErrorResponse&amp;gt; {
 return axios.isAxiosError(error);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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;pre class=&quot;javascript&quot;&gt;&lt;code&gt;  // 실제 사용 예시
async function deleteUser(id: string) {
 try {
   await axios.delete(`/api/users/${id}`);
   alert(&quot;삭제되었습니다.&quot;);
 } catch (error: unknown) {
   // 타입가드로 서버 에러임을 확인할 수 있습니다.
          //  
   if (isServerError(error) &amp;amp;&amp;amp; error.response?.data.errorMessage) {
     alert(error.response.data.errorMessage);
     return;
   }
   // 그 외 에러 처리
   alert(&quot;알 수 없는 에러가 발생했습니다.&quot;);
 }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 에러 서브클래싱하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서브 클래싱(Subclassing)이란 부모 클래스를 확장해서 새로운 하위 자식 클래스를 만드는 과정으로 자식 클래스는 부모 클래스으 모든 속성과 메서드를 상속받아 사용할 수 있고, 추가적인 속성과 메서드 정의가 가능합니다.&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;p data-ke-size=&quot;size16&quot;&gt;1) 에러 발생 시 코드상에서 어떤 에러인지 바로 확인 가능&lt;br /&gt;2) 에러 인스턴스가 무엇인지에 따라 에러 처리 방식을 다르게 할 수 있음&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;장점 1) 에러 발생 시 코드상에서 어떤 에러인지 바로 확인 가능&lt;/h3&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;  // 1. 기본 에러 클래스 생성
class AppError extends Error {
 constructor(message: string) {
   super(message);
   this.name = 'AppError'; // ⭐️ 에러 이름 지정도 가능해요 !
 }
}

// 2. 구체적인 에러 클래스들
class NetworkError extends AppError {
 constructor(message: string = '네트워크 연결에 실패했습니다.') {
   super(message);
   this.name = 'NetworkError';
 }
}

class AuthError extends AppError {
 constructor(message: string = '인증에 실패했습니다.') {
   super(message);
   this.name = 'AuthError';
 }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;사용 예시&lt;/h4&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;async function fetchUserData(userId: string) {
 try {
   const response = await axios.get(`/api/users/${userId}`);
   return response.data;
 } catch (error) {
   if (axios.isAxiosError(error)) {
     switch (error.response?.status) {
       case 401:
         throw new AuthError('로그인이 필요합니다.'); //  에러 종류 바로 확인 가능
       case 404:
         throw new AppError('사용자를 찾을 수 없습니다.');
       case 500:
         throw new AppError('서버 오류가 발생했습니다.');
     }
   }
   throw new NetworkError();//  
 }
}


// 에러 처리
async function handleUserData(userId: string) {
  try {
    const userData = await fetchUserData(userId);
    console.log(userData);
  } catch (error) {
    if (error instanceof AuthError) {
      // 로그인 페이지로 리다이렉트
      redirect('/login');
    } else if (error instanceof NetworkError) {
      // 재시도 로직
      retryFetch(userId);
    } else {
      // 기본 에러 처리
      showErrorMessage(error.message);
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;장점 2) 에러 인스턴스가 무엇인지에 따라 에러 처리 방식을 다르게 할 수 있음&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 타입 가드문을 통해 코드상에서 인스턴스에 따른 에러를 다르게 처리할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;async function handleUserData(userId: string) {
 try {
   const userData = await fetchUserData(userId);
   console.log(userData);
 } catch (error) {
   //   인스턴스에 따라 에러 처리도 가능하고, 어떤 에러인지도 확인할 수 있습니다.
   if (error instanceof AuthError) {
     // 로그인 페이지로 리다이렉트
     redirect('/login');
   } else if (error instanceof NetworkError) {
     // 재시도 로직
     retryFetch(userId);
   } else {
     // 기본 에러 처리
     showErrorMessage(error.message);
   }
 }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Axios 인터셉터를 활용한 에러처리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Axios 같은 페칭 라이브러리는 인터셉터 기능을 제공하는데, 이를 통해 HTTP 요청이나 응답을 가로채서 HTTP 에러에 일관된 처리를 적용할 수 있습니다.&lt;br /&gt;아래 토큰 추가 및 갱신처리와 관련된 예시 코드를 전반적으로 살펴보며 확인해보겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본 설정&lt;/h3&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;// 1. API 기본 설정과 타입 정의
interface ErrorResponse {
 status: string;
 code: string;
 message: string;
}

const api = axios.create({
 baseURL: 'https://api.example.com',
 timeout: 5000,
 headers: {
   'Content-Type': 'application/json'
 }
});

// 2. 응답에 대한 에러 핸들러 함수 정의
const errorHandler = (error: AxiosError&amp;lt;ErrorResponse&amp;gt;): Promise&amp;lt;Error&amp;gt; =&amp;gt; {
 const { response } = error;

 //   상태 코드별로 일관된 에러 처리가 가능합니다. 
 switch (response?.status) {
   case 401: {
     // 인증 에러 처리
     const currentUrl = window.location.href;
     window.location.href = `/login?redirect=${currentUrl}`;
     return Promise.reject(new Error('인증이 필요합니다.'));
   }

   case 403: {
     // 권한 부족 에러 처리
     alert('접근 권한이 없습니다.');
     window.location.href = '/home';
     return Promise.reject(new Error('권한이 없습니다.'));
   }

   case 404: {
     return Promise.reject(new Error('요청한 리소스를 찾을 수 없습니다.'));
   }

   case 500: {
     return Promise.reject(new Error('서버 오류가 발생했습니다.'));
   }

   default: {
     return Promise.reject(new Error('알 수 없는 에러가 발생했습니다.'));
   }
 }
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;요청과 응답에 대한 인터셉터 설정&lt;/h3&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;// 요청 인터셉터 설정
api.interceptors.request.use(
 (config) =&amp;gt; {
   // 토큰 추가
   const token = localStorage.getItem('token');
   if (token) {
     config.headers.Authorization = `Bearer ${token}`;
   }
   return config;
 },
 (error) =&amp;gt; {
   return Promise.reject(error);
 }
);

// 응답 인터셉터 설정
api.interceptors.response.use(
 (response) =&amp;gt; response,  // 성공 응답은 그대로 반환
 async (error) =&amp;gt; {
   // 토큰 만료 체크 및 갱신 처리
   if (axios.isAxiosError(error) &amp;amp;&amp;amp; 
       error.response?.status === 401 &amp;amp;&amp;amp; 
       error.config) {
     try {
       // 토큰 갱신 시도
       const newToken = await refreshToken();
       localStorage.setItem('token', newToken);

       // 실패했던 요청 재시도
       const config = error.config;
       config.headers.Authorization = `Bearer ${newToken}`;
       return api.request(config);
     } catch (e) {
                  //   앞서 정의해준 응답에 대한 에러 핸들러 함수
       return errorHandler(error);
     }
   }         //  
   return errorHandler(error);
 }
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;API 사용 예시&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// API 사용 예시
async function fetchUserData(userId: string) {
 try {
   const response = await api.get(`/users/${userId}`);
   return response.data;
 } catch (error) {
   //   인터셉터에서 처리되지 않은 에러 처리
   console.error('API 호출 중 에러 발생:', error);
   throw error;
 }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 에러 바운더리를 활용한 에러처리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;에러 바운더리&lt;/b&gt;는 React 16에서 도입된 기능으로 &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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  언제사용할까?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러가 발생한 컴포넌트 대신에 에러 처리를 하거나, 커스텀 에러 페이지와 같이 예상하지 못한 에러를 공통으로 처리할 때 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본적인 에어 바운더리 구현 : 에러 발생 시, 커스텀 에러페이지 return&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// ErrorBoundary.tsx

class ErrorBoundary extends React.Component&amp;lt;
  { children: React.ReactNode }, //   ErrorBoundaryProps 
  { hasError: boolean; error: Error | null }  //   ErrorBoundaryState
&amp;gt; {
  constructor(props: { children: React.ReactNode }) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  //   에러 발생시 호출되는 메서드
  static getDerivedStateFromError(error: Error) {
    return { hasError: true, error };
  }

  //   에러 발생시 부가적인 작업
  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    // 에러 로깅, 분석 도구에 보고 등
    console.error('Error:', error);
    console.error('Error Info:', errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return (
        &amp;lt;div className=&quot;error-ui&quot;&amp;gt;
          &amp;lt;h1&amp;gt;문제가 발생했습니다.  &amp;lt;/h1&amp;gt;
          &amp;lt;p&amp;gt;{this.state.error?.message}&amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
      );
    }

    return this.props.children;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용 예시&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// 전역 설정
// App.tsx 
function App() {
  return (
    &amp;lt;ErrorBoundary&amp;gt;
      &amp;lt;div className=&quot;app&quot;&amp;gt;
        &amp;lt;Header /&amp;gt;
        &amp;lt;Main /&amp;gt;
        &amp;lt;Footer /&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/ErrorBoundary&amp;gt;
  );
}

// 특정 컴포넌트에만 적용
function UserProfile() {
  return (
    &amp;lt;ErrorBoundary&amp;gt;
      &amp;lt;UserData /&amp;gt;
    &amp;lt;/ErrorBoundary&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;에러 바운더리 정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 에러 바운더리를 이용하면, 에러가 발생해도 전체 앱이 중단되지 않고, 커스텀 에러 페이지와 같은 대체 UI가 보여지도록 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. react-query를 활용한 에러처리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;react-query 또는 swr과 같은 페칭 라이브러리를 사용하면 요청에 대한 상태를 반환 받을 수 있어 요청 상태를 확인하기 쉽습니다.&lt;br /&gt;상대적으로 많이 쓰는 &lt;b&gt;react-query&lt;/b&gt;로 &lt;b&gt;사용자 프로필 구현 시 발생할 수 있는 에러 처리&lt;/b&gt;를 구현한 코드를 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;기본 설정 코드&lt;/h4&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;//   필요한 타입 정의
interface User {
  id: number;
  name: string;
  email: string;
}

interface ErrorResponse {
  message: string;
  code: string;
}

//   API 함수 정의
const fetchUser = async (id: number): Promise&amp;lt;User&amp;gt; =&amp;gt; {
  const response = await axios.get(`/api/users/${id}`);
  return response.data;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본적인 react-query 사용과 에러 처리&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// userId를 props로 받아 해당 사용자의 정보를 조회하고 표시

function UserProfile({ userId }: { userId: number }) {

 // User: 응답 데이터 타입, Error: 에러 타입
 const {
   data, // API 호출 성공 시 받아오는 데이터
   isLoading, // 데이터 로딩 중인지 여부 (true/false)
   error, // 에러 발생 시 에러 객체
   isError // 에러 발생 여부 (true/false)
 } = useQuery&amp;lt;User, Error&amp;gt;( //   React Query의 useQuery 훅 사용
   // User = 'user'와 userId로 구성된 고유한 쿼리 키
   // 이 키로 데이터를 캐싱하고 관리
   ['user', userId],

   () =&amp;gt; fetchUser(userId),// 실제 API 호출을 수행하는 함수 - userId를 인자로 받아 사용자 정보를 가져옴

   // useQuery 옵션 설정
   {
     retry: 3, // API 호출 실패 시 최대 3번까지 재시도
     retryDelay: 1000, // 재시도 사이의 대기 시간 (1초)
     // 에러 발생 시 실행될 콜백 함수
     onError: (error) =&amp;gt; {
       console.error('사용자 정보 로딩 실패:', error);
     }
   }
 );  useQuery fin 

 // 로딩 중일 때 표시할 UI
 if (isLoading) {
   return &amp;lt;div&amp;gt;로딩 중...&amp;lt;/div&amp;gt;;
 }

 // 에러 발생 시 표시할 UI
 // 새로고침 버튼을 통해 페이지 리로드 가능
 if (isError) {
   return (
     &amp;lt;div&amp;gt;
       &amp;lt;p&amp;gt;에러: {error.message}&amp;lt;/p&amp;gt;
       &amp;lt;button onClick={() =&amp;gt; window.location.reload()}&amp;gt;
         새로고침
       &amp;lt;/button&amp;gt;
     &amp;lt;/div&amp;gt;
   );
 }

 // 데이터 로드 성공 시 사용자 정보 표시
 // data가 없으면 null 반환
 return data ? (
   &amp;lt;div&amp;gt;
     &amp;lt;h1&amp;gt;{data.name}의 프로필&amp;lt;/h1&amp;gt;
     &amp;lt;p&amp;gt;이메일: {data.email}&amp;lt;/p&amp;gt;
   &amp;lt;/div&amp;gt;
 ) : null;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1740296311097&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// userId를 props로 받아 해당 사용자의 정보를 조회하고 표시

function UserProfile({ userId }: { userId: number }) {

 // User: 응답 데이터 타입, Error: 에러 타입
 const {
   data, 
   isLoading, 
   error,
   isError
 } = useQuery&amp;lt;User, Error&amp;gt;( //   React Query의 useQuery 훅 사용
   ['user', userId],
   () =&amp;gt; fetchUser(userId),
   {
     retry: 3, 
     retryDelay: 1000, 
     onError: (error) =&amp;gt; {
       console.error('사용자 정보 로딩 실패:', error);
     }
   }
 ); 

 // 로딩 중일 때 표시할 UI
 if (isLoading) {
   return &amp;lt;div&amp;gt;로딩 중...&amp;lt;/div&amp;gt;;
 }

 // 에러 발생 시 표시할 UI
 if (isError) {
   return (
     &amp;lt;div&amp;gt;
       &amp;lt;p&amp;gt;에러: {error.message}&amp;lt;/p&amp;gt;
       &amp;lt;button onClick={() =&amp;gt; window.location.reload()}&amp;gt;
         새로고침
       &amp;lt;/button&amp;gt;
     &amp;lt;/div&amp;gt;
   );
 }

 // 데이터 로드 성공 시 사용자 정보 표시
 return data ? (
   &amp;lt;div&amp;gt;
     &amp;lt;h1&amp;gt;{data.name}의 프로필&amp;lt;/h1&amp;gt;
     &amp;lt;p&amp;gt;이메일: {data.email}&amp;lt;/p&amp;gt;
   &amp;lt;/div&amp;gt;
 ) : null;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 상태 관리 라이브러리(Redux)에서의 에러 처리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태 관리 라이브러리 Redux를 사용하면 비동기 작업의 상태를 체계적으로 관리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Redux의 슬라이스 생성을 통해 이용한 '컴포넌트 마운트 시 자동으로 사용자 데이터를 로드하는 기능 구현' 코드&lt;/b&gt;를 예시로 살펴보겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;초기 세팅 코드&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;기본 타입 및 상태 정의&lt;/h4&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// types.ts
interface User {
  id: number;
  name: string;
}

interface UserState {
  data: User | null;
  loading: boolean;
  error: string | null;
}

// API 에러 타입
interface ApiError {
  message: string;
  status: number;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;API 호출 및 에러 처리&lt;/h4&gt;
&lt;pre class=&quot;qml&quot;&gt;&lt;code&gt;// api.ts
const api = axios.create({
  baseURL: 'https://api.example.com'
});

export const fetchUser = async (id: number) =&amp;gt; {
  try {
    const response = await api.get(`/users/${id}`);
    return response.data;
  } catch (error) {
    if (axios.isAxiosError(error)) {
      throw new Error(error.response?.data?.message || '사용자 정보를 불러오는데 실패했습니다.');
    }
    throw error;
  }
};​&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Redux를 이용한 설정&lt;/h3&gt;
&lt;pre class=&quot;pf&quot;&gt;&lt;code&gt;// userSlice.ts

import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

//   비동기 액션 생성
// createAsyncThunk는 비동기 작업을 위한 액션 생성자를 만듭니다.
export const fetchUserById = createAsyncThunk(
  'user/fetchById',  // 액션 타입의 접두사

  // 실제 비동기 작업을 수행하는 함수
  async (id: number, { rejectWithValue }) =&amp;gt; {
    try {
      const user = await fetchUser(id);
      return user; 
    } catch (error) {
      return rejectWithValue(error.message);
    }
  }
);

//   리듀서 설정
const userSlice = createSlice({
  name: 'user',

  // 초기 상태 설정
  initialState: {
    data: null,
    loading: false,
    error: null
  } as UserState,


  //1️⃣ 일반 리듀서
  reducers: {
    // 에러 상태를 초기화하는 리듀서
    clearError: (state) =&amp;gt; {
      state.error = null;
    }
  },

  // 2️⃣ 비동기 액션을 처리하는 리듀서
  extraReducers: (builder) =&amp;gt; {
    builder
      // 요청 시작
      .addCase(fetchUserById.pending, (state) =&amp;gt; {
        state.loading = true;   // 로딩 시작
        state.error = null;     // 에러 초기화
      })
      // 요청 성공
      .addCase(fetchUserById.fulfilled, (state, action) =&amp;gt; {
        state.loading = false;  // 로딩 완료
        state.data = action.payload;  // 데이터 저장
      })
      // 요청 실패
      .addCase(fetchUserById.rejected, (state, action) =&amp;gt; {
        state.loading = false;  // 로딩 완료
        state.error = action.payload as string;  // 에러 메시지 저장
      });
  }
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;일반 리듀서와 비동기 리듀서 (참고 1)&lt;/h4&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;reducers&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;일반적인 동기 작업을 처리하며, 단순 상태 업데이트 및 직접 액션을 디스패치하여 호출합니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;extraReducers&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;createAsyncThunk로 생성된 비동기 액션을 처리하고, 다른 슬라이스의 액션에 응답합니다.&lt;br /&gt;복잡한 상태 변화 처리나 여러 액션 타입에 대한 공통 처리에 적합합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Redux reducer와 React useReducer (참고 2)&lt;/h4&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;기본 구조 비교&lt;/h4&gt;
&lt;pre class=&quot;pf&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;// React useReducer
const [state, dispatch] = useReducer(reducer, initialState);
// state: 현재 상태 / dispatch: 액션을 발생시키는 함수
// reducer: 상태를 변경하는 함수 / initialState: 초기 상태

// Redux reducer
const store = createStore(reducer, initialState);&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;실제 코드 비교&lt;/h4&gt;
&lt;pre class=&quot;pf&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;//   React useReducer
function todoReducer(state, action) {
  switch (action.type) {
    case 'ADD_TODO':
      return [...state, action.payload];
    default:
      return state;
  }
}

function TodoComponent() {
  const [todos, dispatch] = useReducer(todoReducer, []);
  // 컴포넌트 내부에서만 사용 가능
}

//   Redux reducer
const todoReducer = createSlice({
  name: 'todos',
  initialState: [],
  reducers: {
    addTodo: (state, action) =&amp;gt; {
      state.push(action.payload);
    }
  }
});

// 어떤 컴포넌트에서든 접근 가능
function AnyComponent() {
  const dispatch = useDispatch();
  const todos = useSelector(state =&amp;gt; state.todos);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;주요 차이점&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;범위&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;useReducer: 컴포넌트 로컬 상태 관리&lt;/li&gt;
&lt;li&gt;Redux: 전역 상태 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;상태 접근&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;useReducer: 해당 컴포넌트와 자식 컴포넌트만&lt;/li&gt;
&lt;li&gt;Redux: 애플리케이션 어디서든 접근 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;미들웨어&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;useReducer: 미들웨어 개념 없음&lt;/li&gt;
&lt;li&gt;Redux: 미들웨어를 통한 부가 기능 (로깅, 비동기 처리 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;개발자 도구&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;useReducer: 기본 React 개발자 도구&lt;/li&gt;
&lt;li&gt;Redux: Redux DevTools를 통한 상태 추적&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;간단한 상태 관리: useReducer&lt;/li&gt;
&lt;li&gt;복잡하고 전역적인 상태 관리: Redux&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용 예시&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// UserProfile.tsx

function UserProfile() {
  //   Redux - dispatch 훅 사용
  const dispatch = useAppDispatch();
  // Redux의 액션을 발생시키는 함수를 가져옵니다. 
  // - 이를 통해 fetchUserById 같은 액션을 실행할 수 있어요.  로 표시된 부분

  //   Redux - selector 훅 사용
  const { data, loading, error } = useAppSelector(state =&amp;gt; state.user);//   error?
  // Redux 스토어의 user 상태를 가져오는 훅입니다. 
  // - data: 사용자 정보
  // - loading: 로딩 상태
  // - error: 에러 상태

  // 컴포넌트 마운트 시 데이터 로드
  useEffect(() =&amp;gt; {
    // async/await를 사용하지 않고 Promise 체이닝으로 처리
    dispatch(fetchUserById(1)) // 
      .unwrap()  // 성공/실패 여부를 Promise로 변환
      .catch((error) =&amp;gt; {
        // 추가적인 에러 처리 (로깅, 분석 등)
        console.error('사용자 정보 로딩 실패:', error); //   error?
      });
  }, [dispatch]);

  // 재시도 핸들러
  const handleRetry = () =&amp;gt; {
    dispatch(fetchUserById(1));
  };

  // 로딩 중 UI
  if (loading) {
    return &amp;lt;div&amp;gt;데이터를 불러오는 중입니다...&amp;lt;/div&amp;gt;;
  }

  // 에러 발생 시 UI
  if (error) {
    return (
      &amp;lt;div className=&quot;error-container&quot;&amp;gt;
        &amp;lt;p&amp;gt;에러가 발생했습니다: {error}&amp;lt;/p&amp;gt;
        &amp;lt;button onClick={handleRetry}&amp;gt;
          다시 시도하기
        &amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
    );
  }

  // 정상적인 데이터 표시 UI
  return data ? (
    &amp;lt;div className=&quot;user-profile&quot;&amp;gt;
      &amp;lt;h1&amp;gt;{data.name}님의 프로필&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;이메일: {data.email}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  ) : null;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  error 전달의 과정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;표시된 error는 어떤 과정을 거쳐 전달되고, 반영될까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 문장으로 정리하면 'API 호출 실패' &amp;rarr; 'catch 블록' &amp;rarr; 'Redux 상태 업데이트' &amp;rarr; 'useAppSelector'로 가져오는 순서로 전달됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좀 더 자세히 알아보면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) API 호출이 실패하면 catch 블록에서 에러를 잡습니다.&lt;br /&gt;2) 이 에러는 Redux의 createAsyncThunk에서 rejectWithValue로 처리되어 리듀서로 전달됩니다:&lt;/p&gt;
&lt;pre class=&quot;pf&quot;&gt;&lt;code&gt;extraReducers: (builder) =&amp;gt; {
  builder
    .addCase(fetchUserById.rejected, (state, action) =&amp;gt; {
      state.error = action.payload; //   여기서 Redux 상태의 error가 설정됨
    })
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) 리듀서에서 설정된 error가 다시 useAppSelector를 통해 컴포넌트로 전달됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Redux 와 react-query&lt;/h4&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Redux&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전역 상태 관리&lt;/li&gt;
&lt;li&gt;클라이언트 상태 관리 (예: UI 상태, 사용자 설정)&lt;/li&gt;
&lt;li&gt;복잡한 상태 로직 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;React Query&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버 상태 관리&lt;/li&gt;
&lt;li&gt;데이터 페칭, 캐싱, 동기화&lt;/li&gt;
&lt;li&gt;서버 데이터 업데이트&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;항목별 상세 비교&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;데이터 관리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Redux&lt;/b&gt;: 수동적인 데이터 관리, 직접 상태 업데이트&lt;/li&gt;
&lt;li&gt;&lt;b&gt;React Query&lt;/b&gt;: 자동 캐싱, 자동 백그라운드 업데이트&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;코드 복잡도&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Redux&lt;/b&gt;: 보일러플레이트 코드 많음 (액션, 리듀서 등)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;React Query&lt;/b&gt;: 간단한 훅 기반 API&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;성능&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Redux&lt;/b&gt;: 수동 최적화 필요&lt;/li&gt;
&lt;li&gt;&lt;b&gt;React Query&lt;/b&gt;: 자동 최적화 (캐싱, 중복 요청 방지)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;기능&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Redux&lt;/b&gt;: 전역 상태 관리에 중점&lt;/li&gt;
&lt;li&gt;&lt;b&gt;React Query&lt;/b&gt;: 서버 상태 관리, 캐싱, 재시도, 폴링 등 추가 기능&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/TypeScript</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/35</guid>
      <comments>https://dreamyard.tistory.com/entry/Typescript-%EB%B9%84%EB%8F%99%EA%B8%B0-%ED%98%B8%EC%B6%9C-API-%EC%97%90%EB%9F%AC-%ED%95%B8%EB%93%A4%EB%A7%81#entry35comment</comments>
      <pubDate>Mon, 17 Mar 2025 03:40:12 +0900</pubDate>
    </item>
    <item>
      <title>[TypeScript] 자바스크립트의 런타임과 타입스크립트의 컴파일</title>
      <link>https://dreamyard.tistory.com/entry/Typescript-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%EB%9F%B0%ED%83%80%EC%9E%84%EA%B3%BC-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%EC%BB%B4%ED%8C%8C%EC%9D%BC</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;자바스크립트의 유연함이 때로는 장점이 되기도 하지만, 종종 예상치 못한 런타임 오류의 원인이 되기도 합니다. 타입스크립트는 이러한 자바스크립트의 한계를 극복하기 위해 만들어졌습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정적 타입 시스템을 통해 코드 실행 전 많은 오류를 미리 잡고, 더 안전하고 유지보수하기 쉬운 코드를 작성할 수 있게 해줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 브라우저와 Node.js는 여전히 자바스크립트만 이해할 수 있는데, 타입스크립트 코드는 어떻게 동작하는 걸까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트 컴파일러의 내부 동작 원리를 살펴보고, 소스 코드가 어떻게 검사되고 변환되는지, 컴파일 과정의 각 단계를 살펴보며, 타입스크립트의 안전성과 한계도 정리해보았습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 자바스크립트의 런타임과 타입스크립트의 컴파일&lt;/h2&gt;
&lt;pre id=&quot;code_1741897072436&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//   타입스크립트에서는 컴파일 시점에 타입 검사가 이루어집니다
function add(a: number, b: number) {
  return a + b;
}

// ✅ 올바른 타입 사용
add(10, 20);     // 결과: 30

// ❌ 컴파일 오류 발생
// add(10, &quot;20&quot;);  // 에러: Argument of type 'string' is not assignable to parameter of type 'number'

//   런타임 오류 예방 사례 1: undefined 속성 접근
function accessProperty(obj: { name?: string }) {
  // 타입스크립트가 없다면 런타임에 오류 발생 가능
  // 타입스크립트는 컴파일 시점에 이런 오류를 잡아냅니다
  return obj.name?.toUpperCase();  // 옵셔널 체이닝으로 안전하게 접근
}

//   런타임 오류 예방 사례 2: 배열 접근
function getFirstItem(arr: string[] | null) {
  // 타입스크립트가 강제하는 null 체크
  if (arr === null) {
    return &quot;배열이 없습니다&quot;;
  }
  
  return arr.length &amp;gt; 0 ? arr[0] : &quot;빈 배열입니다&quot;;
}

//   런타임 오류 예방 사례 3: 스코프 문제
function testScope() {
  const localVar = &quot;이 변수는 함수 내부에서만 접근 가능&quot;;
  
  // 블록 스코프를 벗어난 변수 사용 시 컴파일 오류
  // return innerVar; // 에러: Cannot find name 'innerVar'
  
  if (true) {
    const innerVar = &quot;블록 스코프 변수&quot;;
    return innerVar; // 이건 정상 작동
  }
  
  return localVar;
}

//   타입스크립트는 오류를 미리 발견하여 안정적인 코드 작성을 도와줍니다&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 런타임과 컴파일타임&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;(1) 고수준 언어와 저수준 언어&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래밍 언어는 크게 두 가지로 구분할 수 있습니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;고수준 언어&lt;/b&gt;  &amp;zwj; : 사람이 이해하기 쉬운 형식 (예: JavaScript, TypeScript, Python)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;저수준 언어&lt;/b&gt;  : 컴퓨터가 이해하기 쉬운 형식 (예: 어셈블리어, 기계어)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고수준 언어로 작성된 코드는 컴퓨터가 실행하기 위해 저수준 언어로 변환되는 과정이 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;(2) 컴파일타임 vs 런타임&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;컴파일타임(Compile Time)&lt;/b&gt; ⚙️&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;소스코드가 기계어 또는 중간 코드로 변환되는 시점&lt;/li&gt;
&lt;li&gt;코드의 문법, 타입 검사 등이 이루어짐&lt;/li&gt;
&lt;li&gt;오류가 발견되면 프로그램이 실행되기 전에 알려줌&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;런타임(Runtime)&lt;/b&gt;  &amp;zwj;♂️&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로그램이 실제로 실행되는 시점&lt;/li&gt;
&lt;li&gt;사용자 입력 처리, 메모리 할당, 연산 등이 발생&lt;/li&gt;
&lt;li&gt;예상치 못한 상황(null 참조, 잘못된 입력 등)으로 오류 발생 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 자바스크립트 런타임&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;b&gt;대표적인 예 )&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;웹 브라우저 (Chrome, Firefox, Safari 등)&lt;/li&gt;
&lt;li&gt;Node.js&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;주요 구성 요소&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;자바스크립트 엔진&lt;/b&gt;: 코드를 해석하고 실행 (예: V8, SpiderMonkey)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;웹 API&lt;/b&gt;: DOM, AJAX, setTimeout 등의 기능 제공&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이벤트 루프, 콜백 큐&lt;/b&gt;: 비동기 작업을 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;런타임 오류의 예시&lt;/h4&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// 예시 1: undefined 속성 접근
let foo;
foo.bar; // TypeError: Cannot read properties of undefined (reading 'bar')

// 예시 2: null 객체 속성 접근
const testArr = null;
if (testArr.length === 0) { 
  console.log(&quot;zero length&quot;);
} // TypeError: Cannot read properties of null (reading 'length')

// 예시 3: 스코프 문제
function testFn() { 
  const foo = &quot;bar&quot;;
} 
console.log(foo); // ReferenceError: foo is not defined
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;참고&lt;/b&gt;: 자바스크립트는 흔히 인터프리터 언어로 알려져 있지만, 실제로는 V8 같은 현대 엔진이 내부적으로 JIT(Just-In-Time) 컴파일하여 성능을 최적화합니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) 타입스크립트의 컴파일&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트는 소스코드를 자바스크립트로 변환합니다. 이 과정은 기존의 컴파일과 약간 다릅니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;  고수준 언어(TypeScript) &amp;rarr; 또 다른 고수준 언어(JavaScript)로 변환&lt;/li&gt;
&lt;li&gt;  이러한 변환을 &lt;b&gt;트랜스파일(Transpile)&lt;/b&gt; 또는 &lt;b&gt;소스 대 소스 컴파일&lt;/b&gt;이라고도 함&lt;/li&gt;
&lt;li&gt;  tsc라는 컴파일러를 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;타입스크립트의 가장 큰 장점: 컴파일 타임 타입 체크&lt;/h4&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;function add(a: number, b: number) { 
  return a + b;
}

add(10, 20);       // ✅ 정상 동작
add(10, &quot;20&quot;);     // ❌ 컴파일 오류 발생: 'string' 타입은 'number' 타입에 할당할 수 없습니다.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;런타임 오류를 방지하는 타입스크립트&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트는 앞서 본 자바스크립트 런타임 오류를 컴파일 시점에 미리 잡아낼 수 있습니다:&lt;/p&gt;
&lt;pre class=&quot;qml&quot;&gt;&lt;code&gt;// undefined 속성 접근 방지
let foo: { bar?: string };
foo.bar;  // ❌ 오류: 'foo'가 초기화되기 전에 사용되었습니다.

// null 체크 강제
const testArr: string[] | null = null;
if (testArr?.length === 0) {  // 옵셔널 체이닝으로 안전하게 접근
  console.log(&quot;zero length&quot;);
}

// 스코프 문제 감지
function testFn() { 
  const foo = &quot;bar&quot;;
} 
console.log(foo);  // ❌ 오류: 'foo'가 선언되기 전에 사용되었습니다.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  타입스크립트를 사용해야 하는 이유&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;안전성&lt;/b&gt;  ️: 컴파일 타임에 많은 오류를 잡아내어 런타임 오류 감소&lt;/li&gt;
&lt;li&gt;&lt;b&gt;가독성&lt;/b&gt;  : 코드의 의도를 명확히 표현할 수 있는 타입 시스템&lt;/li&gt;
&lt;li&gt;&lt;b&gt;생산성&lt;/b&gt; ⚡: 자동 완성, 리팩토링 지원 등 개발 도구의 향상된 지원&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유지보수성&lt;/b&gt;  : 타입 정보를 통해 코드 변경 시 영향 범위 파악 용이&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 타입스크립트 컴파일러&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트는 자바스크립트의 슈퍼셋(확장)으로, 개발 단계에서 강력한 타입 체크를 제공하지만 결국 브라우저나 Node.js에서 실행되기 위해서는 자바스크립트로 변환되어야 합니다. 이 과정을 담당하는 것이 바로 &lt;b&gt;타입스크립트 컴파일러(tsc)&lt;/b&gt; 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트 컴파일러가 소스코드를 컴파일하고, 프로그램 실행되기까지의 과정은 아래와 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1 &amp;nbsp;tsc&amp;nbsp;명령어를&amp;nbsp;실행하여&amp;nbsp;프로그램&amp;nbsp;객체가&amp;nbsp;컴파일&amp;nbsp;과정을&amp;nbsp;시작한다. &lt;br /&gt;2 &amp;nbsp;스캐너는&amp;nbsp;소스&amp;nbsp;파일을&amp;nbsp;토큰&amp;nbsp;단위로&amp;nbsp;분리한다. &lt;br /&gt;3 &amp;nbsp;파서는&amp;nbsp;토큰을&amp;nbsp;이용하여&amp;nbsp;AST를&amp;nbsp;생성한다. &lt;br /&gt;4 &amp;nbsp;바인더는&amp;nbsp;AST의&amp;nbsp;각&amp;nbsp;노드에&amp;nbsp;대응하는&amp;nbsp;심볼을&amp;nbsp;생성한다.&amp;nbsp;심볼은&amp;nbsp;선언된&amp;nbsp;타입의&amp;nbsp;노드&amp;nbsp;정보를&amp;nbsp;담고&amp;nbsp;있다. &lt;br /&gt;5 &amp;nbsp;체커는&amp;nbsp;AST를&amp;nbsp;탐색하면서&amp;nbsp;심볼&amp;nbsp;정보를&amp;nbsp;활용하여&amp;nbsp;타입&amp;nbsp;검사를&amp;nbsp;수행한다. &lt;br /&gt;6 &amp;nbsp;타입&amp;nbsp;검사&amp;nbsp;결과&amp;nbsp;에러가&amp;nbsp;없다면&amp;nbsp;이미터를&amp;nbsp;사용해서&amp;nbsp;자바스크립트&amp;nbsp;소스&amp;nbsp;파일로&amp;nbsp;변환한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 코드 검사기로서의 타입스크립트 컴파일러&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트 컴파일러의 첫 번째 중요한 역할은 &lt;b&gt;코드 검사&lt;/b&gt;입니다. 이는 코드를 실행하기 전에 타입 오류를 찾아내는 것을 의미합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;예시: 런타임 에러를 컴파일 타임에 잡아내기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;자바스크립트에서는&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;const developer = {
  work() {
    console.log(&quot;working...&quot;);
  },
};

developer.work(); // 정상 작동: &quot;working...&quot; 출력
developer.sleep(); //   런타임 에러: TypeError: developer.sleep is not a function
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;타입스크립트에서는&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;const developer = {
  work() {
    console.log(&quot;working...&quot;);
  },
};

developer.work(); // ✅ 정상 작동
developer.sleep(); // ❌ 컴파일 에러: Property 'sleep' does not exist on type '{ work(): void; }'
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트 컴파일러는 tsc binder를 사용하여 타입 검사를 수행하며, 이 과정에서 타입스크립트 AST(Abstract Syntax Tree)를 분석합니다. 타입 검사 후에는 이 AST를 자바스크립트 코드로 변환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 코드 변환기로서의 타입스크립트 컴파일러&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트 컴파일러의 두 번째 주요 역할은 &lt;b&gt;코드 변환&lt;/b&gt;입니다. 타입스크립트 코드는 브라우저나 Node.js와 같은 자바스크립트 런타임에서 직접 실행될 수 없습니다. 따라서 컴파일러는 타입스크립트 코드를 표준 자바스크립트로 변환(트랜스파일)합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;예시: 타입스크립트에서 자바스크립트로의 변환&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;타입스크립트&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;type Fruit = &quot;banana&quot; | &quot;watermelon&quot; | &quot;orange&quot; | &quot;apple&quot; | &quot;kiwi&quot; | &quot;mango&quot;;
const fruitBox: Fruit[] = [&quot;banana&quot;, &quot;apple&quot;, &quot;mango&quot;];

const welcome = (name: string) =&amp;gt; {
  console.log(`hi! ${name} :)`);
};
&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;컴파일 후 자바스크립트 코드(ES5 기준)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;&quot;use strict&quot;;
var fruitBox = [&quot;banana&quot;, &quot;apple&quot;, &quot;mango&quot;];
var welcome = function (name) {
  console.log(&quot;hi! &quot;.concat(name, &quot; :)&quot;));
};
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;주목할 점&lt;/b&gt;: 컴파일 후에는 모든 타입 정보(type Fruit, : Fruit[], : string)가 제거되었습니다.&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;컴파일러의 target 옵션을 통해 어떤 버전의 자바스크립트로 변환할지 지정할 수 있습니다(예: ES5, ES6, ES2020 등).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;중요한 특성: 타입 에러가 있어도 컴파일은 진행됨&lt;/h4&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;xquery&quot;&gt;&lt;code&gt;const name: string = &quot;zig&quot;;
const age: number = &quot;zig&quot;; // ❌ 타입 에러: Type 'string' is not assignable to type 'number'
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 타입 에러가 있지만, 다음과 같이 자바스크립트로 컴파일됩니다:&lt;/p&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;const name = &quot;zig&quot;;
const age = &quot;zig&quot;; // 타입 정보가 제거되어 에러가 없어짐
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ &lt;b&gt;주의&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) 런타임에서의 제약: 타입 정보 손실&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트의 타입 시스템은 &lt;b&gt;컴파일 타임&lt;/b&gt;에만 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드가 자바스크립트로 컴파일되면 모든 타입 정보가 사라집니다. 이는 런타임에서 타입 정보를 사용할 수 없다는 것을 의미합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;예시: 런타임에서 타입 사용 시 문제&lt;/h4&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;interface Square {
  width: number;
}

interface Rectangle extends Square {
  height: number;
}

type Shape = Square | Rectangle;

function calculateArea(shape: Shape) {
  // ❌ 오류: 'Rectangle'은 타입이므로 런타임에서 값으로 사용할 수 없음
  if (shape instanceof Rectangle) {
    return shape.width * shape.height;
  } else {
    return shape.width * shape.width;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; ️ &lt;b&gt;해결책&lt;/b&gt;: 대신 '속성 체크'나 '타입 가드'를 사용해야 합니다. 타입스크립트의 인터페이스, 타입 별칭, 제네릭 등은 모두 컴파일 타임에만 존재하는 개념이므로, 런타임에서는 이를 직접 참조할 수 없습니다. 대신 자바스크립트의 기본 연산자와 패턴을 활용해 비슷한 기능을 구현합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;function calculateArea(shape: Shape) {
  // ✅ 'height' 속성의 존재 여부로 타입 구분
  if ('height' in shape) {
    // 타입스크립트는 이 블록 내에서 shape를 Rectangle로 인식
    return shape.width * shape.height;
  } else {
    return shape.width * shape.width;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;TSC vs Babel 차이&lt;/h4&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트 컴파일러(tsc)와 Babel은 모두 최신 코드를 이전 버전의 자바스크립트로 변환한다는 점에서 유사하지만, 아래 표와 같이 다른 점이 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;특징&amp;nbsp;&lt;/td&gt;
&lt;td&gt;타입스크립트&amp;nbsp; 컴파일러(tsc)&lt;/td&gt;
&lt;td&gt;Babel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;타입 검사&lt;/td&gt;
&lt;td&gt;✅ 수행&lt;/td&gt;
&lt;td&gt;❌ 수행하지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;코드 변환&lt;/td&gt;
&lt;td&gt;✅ 수행&lt;/td&gt;
&lt;td&gt;✅ 수행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;주요 목적&lt;/td&gt;
&lt;td&gt;타입 검사 + 코드 변환&lt;/td&gt;
&lt;td&gt;최신 자바스크립트를 구 버전으로 변환&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote style=&quot;color: #666666;&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p style=&quot;color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;참고&lt;/b&gt;: 실제 프로젝트에서는 Babel은 코드 변환을, TypeScript는 타입 검사만을 담당하도록 설정하는 경우도 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  타입스크립트 컴파일러의 두 가지 핵심 역할&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 두 역할을 통해 타입스크립트는 개발 단계에서의 안전성을 제공하면서도, 모든 자바스크립트 런타임 환경과의 호환성을 유지할 수 있습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;타입 검사&lt;/b&gt;: 코드의 타입 오류를 컴파일 타임에 발견&lt;/li&gt;
&lt;li&gt;&lt;b&gt;코드 변환&lt;/b&gt;: 타입스크립트 코드를 자바스크립트로 트랜스파일&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  개발 Tip&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;tsconfig.json의 noEmitOnError 옵션을 true로 설정하면, 타입 에러가 있을 때 자바스크립트 파일을 생성하지 않도록 할 수 있습니다.&lt;/li&gt;
&lt;li&gt;개발 단계에서는 --watch 플래그를 사용하여 파일 변경 시 자동으로 컴파일되도록 설정하면 효율적입니다.&lt;/li&gt;
&lt;li&gt;런타임에 타입 검사가 필요한 경우, 별도의 런타임 타입 검사 라이브러리(예: io-ts, zod)를 고려해볼 수 있습니다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 타입스크립트 컴파일러의 구조와 동작 원리&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 타입스크립트 컴파일러의 구조&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트는 개발자가 작성한 타입스크립트 코드를 브라우저나 Node.js가 이해할 수 있는 자바스크립트로 변환해야 합니다. 이 변환 과정을 담당하는 것이 &lt;b&gt;타입스크립트 컴파일러&lt;/b&gt;입니다. 컴파일러는 단순히 코드를 변환하는 것뿐만 아니라 타입 검사, 문법 오류 확인 등 다양한 작업을 수행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 컴파일러의 다섯 단계&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;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;1. 스캐너(Scanner)&lt;/td&gt;
&lt;td&gt;소스코드를 토큰으로 분리&lt;/td&gt;
&lt;td&gt;토큰(Token)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. 파서(Parser)&lt;/td&gt;
&lt;td&gt;토큰을 기반으로 구문 분석&lt;/td&gt;
&lt;td&gt;AST(추상 구문 트리)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. 바인더(Binder)&lt;/td&gt;
&lt;td&gt;타입 정보를 AST에 연결&lt;/td&gt;
&lt;td&gt;심볼(Symbol)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4. 체커(Checker)&lt;/td&gt;
&lt;td&gt;타입 검사 수행&lt;/td&gt;
&lt;td&gt;진단 정보(오류)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5. 이미터(Emitter)&lt;/td&gt;
&lt;td&gt;최종 JavaScript 코드 생성&lt;/td&gt;
&lt;td&gt;.js 파일&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;[ 1단계 ] 프로그램(Program)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일 과정은 tsc 명령어가 실행되면서 시작됩니다. 이 시점에서 &lt;b&gt;프로그램 객체&lt;/b&gt;가 생성되며, 다음 작업을 수행합니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;tsconfig.json 파일에서 컴파일 옵션 로드&lt;/li&gt;
&lt;li&gt;컴파일할 소스 파일 및 관련 파일 식별&lt;/li&gt;
&lt;li&gt;전체 컴파일 과정 조정&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;axapta&quot;&gt;&lt;code&gt;#  ️ 터미널에서 컴파일 실행
tsc index.ts  # 특정 파일 컴파일
# 또는
tsc           # tsconfig.json 설정에 따라 컴파일
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;[ 2 단계 ] 스캐너(Scanner)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스캐너는 타입스크립트 소스코드를 토큰(Token)이라는 최소 의미 단위로 분해합니다. 이 과정을 어휘 분석(Lexical Analysis)이라고 합니다.&lt;/p&gt;
&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;ebnf&quot;&gt;&lt;code&gt;const woowa = &quot;bros&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스캐너는 다음과 같은 토큰으로 분해합니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ConstKeyword (const)&lt;/li&gt;
&lt;li&gt;WhitespaceTrivia (공백)&lt;/li&gt;
&lt;li&gt;Identifier (woowa)&lt;/li&gt;
&lt;li&gt;WhitespaceTrivia (공백)&lt;/li&gt;
&lt;li&gt;EqualsToken (=)&lt;/li&gt;
&lt;li&gt;WhitespaceTrivia (공백)&lt;/li&gt;
&lt;li&gt;StringLiteral (&quot;bros&quot;)&lt;/li&gt;
&lt;li&gt;SemicolonToken (;)&lt;/li&gt;
&lt;/ul&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;a href=&quot;https://github.com/microsoft/TypeScript/blob/3a8c6307473a5350284699fbf0adc32a6b169a39/src/compiler/types.ts#L21&quot;&gt;SyntaxKind&lt;/a&gt; 변수에서 확인할 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;[ 3단계 ] 파서(Parser)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파서는 스캐너가 생성한 토큰을 받아 추상 구문 트리(AST: Abstract Syntax Tree)를 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AST는 소스코드의 구조를 트리 형태로 표현한 자료구조입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스캐너가 '무엇이 있는지'를 파악했다면, 파서는 '어떤 의미인지'를 파악합니다&lt;/li&gt;
&lt;li&gt;예를 들어, 괄호(())가 함수 호출인지, 그룹화 연산자인지 결정합니다&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;//   예제 코드
function normalFunction() { 
  console.log(&quot;normalFunction&quot;);
}
normalFunction();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드의 AST는 대략 다음과 같은 구조를 가집니다:&lt;/p&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;FunctionDeclaration
├── Identifier (normalFunction)
├── Empty Parameters
└── Block
    └── ExpressionStatement
        └── CallExpression (console.log)
            ├── PropertyAccessExpression
            │   ├── Identifier (console)
            │   └── Identifier (log)
            └── StringLiteral (&quot;normalFunction&quot;)

ExpressionStatement
└── CallExpression (normalFunction)
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;도구 팁&lt;/b&gt;: &lt;a href=&quot;https://ts-ast-viewer.com/&quot;&gt;TypeScript AST Viewer&lt;/a&gt;를 사용하면 코드의 AST 구조를 시각적으로 확인할 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;[ 4단계 ] 바인더(Binder)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바인더의 주요 역할은 파서가 생성한 AST 노드들을 의미적으로 연결하고, 타입 검사를 위한 심볼(Symbol)을 생성하는 것입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;심볼&lt;/b&gt;은 코드에서 선언된 식별자(변수, 함수, 클래스 등)에 대한 정보를 담고 있습니다&lt;/li&gt;
&lt;li&gt;각 심볼은 다음과 같은 정보를 포함합니다:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;flags: 심볼의 종류(변수, 함수, 클래스 등)&lt;/li&gt;
&lt;li&gt;escapedName: 심볼의 이름&lt;/li&gt;
&lt;li&gt;declarations: 해당 심볼과 관련된 선언 노드 목록&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;//   심볼 인터페이스 일부 (타입스크립트 소스 코드에서)
export interface Symbol {
  flags: SymbolFlags;       // 심볼 종류 식별자
  escapedName: string;      // 심볼 이름
  declarations?: Declaration[]; // 관련 선언 노드
  // 이하 생략...
}
&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;심볼 플래그는 AST에서 선언된 타입의 노드 정보를 저장하는 식별자입니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;//   심볼 플래그 예시 (일부)
export const enum SymbolFlags {
  None = 0,
  FunctionScopedVariable = 1 &amp;lt;&amp;lt; 0,  // var 변수 또는 매개변수
  BlockScopedVariable = 1 &amp;lt;&amp;lt; 1,     // let이나 const 변수
  Property = 1 &amp;lt;&amp;lt; 2,                // 속성 또는 enum 멤버
  Function = 1 &amp;lt;&amp;lt; 4,                // 함수
  Class = 1 &amp;lt;&amp;lt; 5,                   // 클래스
  Interface = 1 &amp;lt;&amp;lt; 6,               // 인터페이스
  // 이하 생략...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;[ 5단계 ] 체커(Checker)와 이미터(Emitter)&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;체커(Checker)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;체커는 앞서 생성된 AST와 심볼을 바탕으로 &lt;b&gt;타입 검사&lt;/b&gt;를 수행합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;타입스크립트 컴파일러에서 가장 큰 부분을 차지합니다(약 2.7MB)&lt;/li&gt;
&lt;li&gt;AST 노드를 순회하면서 각 노드에 대한 타입을 분석하고 검증합니다&lt;/li&gt;
&lt;li&gt;타입 오류가 발견되면 진단 정보(에러 메시지)를 생성합니다&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;xquery&quot;&gt;&lt;code&gt;//   타입 오류 예시
let name: string = &quot;Woowa&quot;;
name = 42;  // ❌ 오류: Type 'number' is not assignable to type 'string'
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;이미터(Emitter)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미터는 최종적으로 타입스크립트 코드를 &lt;b&gt;자바스크립트 코드&lt;/b&gt;로 변환합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바스크립트 파일(.js)과 타입 선언 파일(.d.ts)을 생성합니다&lt;/li&gt;
&lt;li&gt;이미터는 체커의 타입 검사 결과를 활용합니다&lt;/li&gt;
&lt;li&gt;타입 정보는 제거되고 실행 가능한 자바스크립트 코드만 남습니다&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;arcade&quot;&gt;&lt;code&gt;//   타입스크립트 원본 코드
function greet(name: string): string {
  return `Hello, ${name}!`;
}

//   이미터가 생성한 자바스크립트 코드
function greet(name) {
  return &quot;Hello, &quot; + name + &quot;!&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;컴파일 과정 요약&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트 컴파일러의 전체 과정을 요약하면 다음과 같습니다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;프로그램 객체 생성&lt;/b&gt;: tsc 명령어 실행으로 컴파일러 시작&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스캐너&lt;/b&gt;: 소스코드를 토큰으로 분리 (어휘 분석)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;파서&lt;/b&gt;: 토큰을 기반으로 AST 생성 (구문 분석)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;바인더&lt;/b&gt;: AST 노드와 심볼 연결, 타입 정보 준비&lt;/li&gt;
&lt;li&gt;&lt;b&gt;체커&lt;/b&gt;: AST와 심볼을 기반으로 타입 검사 수행&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이미터&lt;/b&gt;: 타입 정보가 제거된 자바스크립트 코드 생성&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt; 개발 Tip&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;타입 오류 이해하기&lt;/b&gt;: 컴파일러 에러 메시지의 원인은 주로 체커 단계에서 발생합니다. 에러 메시지를 이해하면 문제 해결이 쉬워집니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;컴파일 성능 최적화&lt;/b&gt;: 대규모 프로젝트에서는 증분 컴파일을 활용하여 성능을 향상시킬 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AST 탐색 도구 활용&lt;/b&gt;: &lt;a href=&quot;https://ts-ast-viewer.com/&quot;&gt;TypeScript AST Viewer&lt;/a&gt;를 사용하면 코드의 구조를 더 잘 이해할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;타입스크립트 컴파일러 API&lt;/b&gt;: 타입스크립트는 컴파일러 API를 제공하여 자체 도구를 개발할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/TypeScript</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/43</guid>
      <comments>https://dreamyard.tistory.com/entry/Typescript-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%EB%9F%B0%ED%83%80%EC%9E%84%EA%B3%BC-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%EC%BB%B4%ED%8C%8C%EC%9D%BC#entry43comment</comments>
      <pubDate>Sun, 16 Mar 2025 16:39:46 +0900</pubDate>
    </item>
    <item>
      <title>[TypeScript] 타입 활용하기</title>
      <link>https://dreamyard.tistory.com/entry/Typescript-%ED%83%80%EC%9E%85-%ED%99%9C%EC%9A%A9%ED%95%98%EA%B8%B0</link>
      <description>&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;이번 글에서는 Typescript의 타입을 활용하는 방법들을 정리해보았습니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;1) 조건부 타입&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입에서 조건에 따라 다른 타입을 반환해야 할 때 사용합니다. 타입스크립트의 조건부 타입은 자바스크립트의 삼항 연산자와 동일한 Condition ? A : B 형태를 가집니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(1) extends와 제네릭을 활용한 조건부 타입&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;extends는 타입 확장 뿐 아니라 조건부 타입 설정에서도 사용되고, 제네릭 타입에서는 한정자 역할로도 사용됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드들은 결제 수단과 관련된 타입입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741892534647&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 결제 수단 관련 타입 정의
interface Bank {
  financialCode: string;
  name: string;
  // 기타 은행 관련 속성
}

interface Card {
  financialCode: string;
  name: string;
  addCardType: boolean; // 카드사 앱으로 등록 가능 여부
  // 기타 카드 관련 속성
}

// 제네릭과 extends를 활용한 조건부 타입
type PayMethod&amp;lt;T&amp;gt; = T extends Card 
  ? { type: 'card'; provider: string; data: T }
  : T extends Bank
    ? { type: 'bank'; accountNumber: string; data: T }
    : never;

// 타입 적용 예시
type CardPayMethodType = PayMethod&amp;lt;Card&amp;gt;; 
// { type: 'card'; provider: string; data: Card }

type BankPayMethodType = PayMethod&amp;lt;Bank&amp;gt;; 
// { type: 'bank'; accountNumber: string; data: Bank }&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;br /&gt;Bank는 계좌를 이용한 결제 수단이며 고유코드인 financialCode, name 등을 가지고 있습니다. Card 타입과 다른점은 addCardType으로 카드사 앱을 사용해서 카드 정보를 등록할 수 있는지 구별해주는 속성있다는 점 입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PayMethod 타입은 제네릭 타입으로 extends를 사용한 조건부 타입입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PayMethod 타입을 사용해서 CardPayMethodType과 BankPayMethodType을 도출할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(2) 조건부 타입이 필요한 경우 예시 보기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드는 react-query를 활용한 예시로 계좌, 카드, 앱 카드 등 3가지 결제 수단 정보를 가져오는 API가 있고 각 API는 계좌, 카드, 앱 카드의 결제 수단 정보를 배열 형태로 반환한다고 가정하겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3가지 API의 엔드포인트가 비슷하므로 서버 응답을 처리하는 공통 함수를 생성한 뒤, 해당 함수에 타입을 전달해 타입별로 처리하는 로직 코드를 살펴보며, 조건부 타입이 필요한 경우를 살펴보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741892605095&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// API 호출 함수 (결제 수단 정보를 가져오는 API)
async function fetchPaymentMethods&amp;lt;T&amp;gt;(
  endpoint: string
): Promise&amp;lt;T[]&amp;gt; {
  const response = await fetch(`/api/payment/${endpoint}`);
  return response.json();
}

// 각 결제 수단 타입별 API 호출 함수
function useBankPaymentMethods() {
  return useQuery&amp;lt;Bank[]&amp;gt;('banks', () =&amp;gt; 
    fetchPaymentMethods&amp;lt;Bank&amp;gt;('banks')
  );
}

function useCardPaymentMethods() {
  return useQuery&amp;lt;Card[]&amp;gt;('cards', () =&amp;gt; 
    fetchPaymentMethods&amp;lt;Card&amp;gt;('cards')
  );
}

function useAppCardPaymentMethods() {
  return useQuery&amp;lt;Card[]&amp;gt;('appCards', () =&amp;gt; 
    fetchPaymentMethods&amp;lt;Card&amp;gt;('app-cards')
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(3) infer를 활용해서 타입 추론하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;extends를 사용할 땐 infer라는 키워드도 사용할 수 있습니다. 이를 적용할 경우 extends로 조건을 서술하고 infer로 타입을 추론하는 구조로 작성됩니다. 예시를 살펴보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1741892614567&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Promise 타입에서 내부 타입을 추출하는 유틸리티 타입
type UnpackPromise&amp;lt;T&amp;gt; = T extends Promise&amp;lt;infer K&amp;gt; ? K : T;

// 배열의 경우 요소 타입에 적용
type UnpackArrayPromise&amp;lt;T&amp;gt; = T extends (infer U)[]
  ? UnpackPromise&amp;lt;U&amp;gt;[]
  : UnpackPromise&amp;lt;T&amp;gt;;

// 사용 예시
const cardPromise = Promise.resolve({ cardNumber: '1234-5678', name: 'John' });
type CardType = UnpackPromise&amp;lt;typeof cardPromise&amp;gt;; // { cardNumber: string; name: string; }

const promises = [Promise.resolve(&quot;Mark&quot;), Promise.resolve(38)];
type PromiseResults = UnpackArrayPromise&amp;lt;typeof promises&amp;gt;; // (string | number)[]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UnpackPromise 타입은 Promise 안에 담긴 실제 데이터 타입을 꺼내주는 역할을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Promise&amp;lt;string&amp;gt;이면 &amp;rarr; string으로 변환하고, Promise가 아닌 타입은 &amp;rarr; 원래 타입(T) 그대로 유지합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741882777322&quot; class=&quot;typescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;// 예시
const promises = [Promise.resolve(&quot;Mark&quot;), Promise.resolve(38)];
type Expected = UnpackPromise&amp;lt;typeof promises&amp;gt;; // string | number&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Promise&amp;lt;infer K&amp;gt; 는 Promise의 반환 값을 추론해 해당 타입을 K로 한다는 의미입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;extends와 infer, 제네릭을 활용하면 타입을 조건에 따라 더 세밀하게 사용할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라우팅 타입 문제를 infer를 활용해 해결한 사례를 보며 자세히 살펴보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741882704706&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 라우팅 정의를 위한 타입
interface RouteBase {
  name: string;
  path: string;
  component: ComponentType;
}

export interface RouteItem {
  name: string;
  path: string;
  component?: ComponentType;
  pages?: RouteBase[];
}

// 실제 라우트 정의
export const routes: RouteItem[] = [
  { 
    name: &quot;기기 내역 관리&quot;, 
    path: &quot;/device-history&quot;, 
    component: DeviceHistoryPage,
  },
  { 
    name: &quot;헬멧 인증 관리&quot;, 
    path: &quot;/helmet-certification&quot;, 
    component: HelmetCertificationPage,
  },
  // ... 기타 라우트들
];

// 메뉴 정의를 위한 타입
export interface SubMenu {
  name: string;
  path: string;
}

export interface MainMenu {
  name: string;
  path?: string;
  subMenus?: SubMenu[];
}

export type MenuItem = MainMenu | SubMenu;

// 실제 메뉴 정의
export const menuList: MenuItem[] = [
  {
    name: &quot;계정 관리&quot;,
    subMenus: [
      { name: &quot;기기 내역 관리&quot;, path: &quot;/device-history&quot; },
      { name: &quot;헬멧 인증 관리&quot;, path: &quot;/helmet-certification&quot; },
    ],
  },
  { name: &quot;운행 관리&quot;, path: &quot;/operation&quot; },
  // ... 기타 메뉴들
];&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드의 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;문제&lt;/b&gt;&lt;/span&gt;는 크게 &lt;b&gt;2가지&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;1. name 속성이 모두 단순 string 타입으로 정의되어 있어서 오타가 있어도 타입 체크에서 감지되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예: &quot;기기 내역 관리&quot;를 &quot;기기 관리&quot;로 잘못 작성해도 타입 오류가 발생하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.&amp;nbsp;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;라우팅(routes)과 메뉴(menuList)의 name 값이 정확히 일치해야 권한 확인이 제대로 작동합니다. &lt;/span&gt;하지만 이 일치 여부가 타입 시스템에 의해 강제되지 않아 런타임 오류가 발생할 가능성이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 infer를 활용해 개선해보면,&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741882794924&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//   1. 메뉴 리스트를 불변 객체로 정의
export interface MainMenu {
 name: string;
 path?: string;
 subMenus?: ReadonlyArray&amp;lt;SubMenu&amp;gt;; // ✨ ReadonlyArray로 변경 (불변성 강화)
}

export interface SubMenu {
 name: string;
 path: string;
}

export type MenuItem = MainMenu | SubMenu; //   메인메뉴 또는 서브메뉴를 의미

//   as const를 사용해 불변 객체로 만듦 (이게 핵심!)
// as const 는 객체를 읽기 전용으로 만들고, 그 안의 모든 값을 리터럴 타입으로 변환
// 예: &quot;기기 내역 관리&quot;는 단순 string이 아닌, 정확히 &quot;기기 내역 관리&quot; 리터럴 타입이 됩니다.
export const menuList = [
 {
   name: &quot;계정 관리&quot;, //   이 name은 단순 그룹명
   subMenus: [
     { name: &quot;기기 내역 관리&quot;, path: &quot;/device-history&quot; }, // ⭐ 실제 권한명
     { name: &quot;헬멧 인증 관리&quot;, path: &quot;/helmet-certification&quot; }, // ⭐ 실제 권한명
   ],
 },
 { name: &quot;운행 관리&quot;, path: &quot;/operation&quot; }, // ⭐ 실제 권한명
 // ... 기타 메뉴들
] as const; //   이 as const가 모든 문자열을 정확한 리터럴 타입으로 만들어줌

//   2. 권한 이름을 추출하는 타입 정의 ( &amp;zwj;♂️ 마법의 타입 추론)
type UnpackMenuNames&amp;lt;T extends ReadonlyArray&amp;lt;MenuItem&amp;gt;&amp;gt; = 
 T extends ReadonlyArray&amp;lt;infer U&amp;gt; //   배열에서 각 요소 타입 U로 추출
   ? U extends MainMenu //   메인 메뉴인가?
     ? U[&quot;subMenus&quot;] extends infer V //   메인 메뉴면 -&amp;gt; 서브메뉴가 있는지 확인
       ? V extends ReadonlyArray&amp;lt;SubMenu&amp;gt;  //   서브 메뉴가 있는가?
         ? UnpackMenuNames&amp;lt;V&amp;gt; //   서브메뉴가 있으면 -&amp;gt; UnpackMenuNames&amp;lt;V&amp;gt; 서브메뉴로 다시 처리 (재귀)
         : U[&quot;name&quot;] // ✅ 서브메뉴 없으면 -&amp;gt; U[&quot;name&quot;] (직접 권한으로 사용)
       : never
     : U extends SubMenu 
       ? U[&quot;name&quot;] // ✅ 서브메뉴면 -&amp;gt; U[&quot;name&quot;] (직접 권한으로 사용)
       : never
   : never;

//   3. 메뉴에서 추출한 권한 이름 타입
export type PermissionNames = UnpackMenuNames&amp;lt;typeof menuList&amp;gt;;
//   결과: &quot;기기 내역 관리&quot; | &quot;헬멧 인증 관리&quot; | &quot;운행 관리&quot;

//   4. 라우팅 타입을 권한 이름 타입과 연결
interface RouteBase {
 name: PermissionNames; //   string에서 PermissionNames로 변경 (타입 안전성 강화!)
 path: string;
 component: ComponentType;
}

export type RouteItem = 
 | { //   페이지 그룹 (폴더 같은 개념)
     name: string; //   페이지 그룹명은 여전히 string (권한 검사 대상이 아님)
     path: string;
     component?: ComponentType;
     pages: RouteBase[]; //   하위 페이지들은 권한 검사 대상
   }
 | { //   단일 페이지
     name: PermissionNames; //   단일 페이지는 권한 검사 대상
     path: string;
     component?: ComponentType;
   };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;혹시 너도? 나도?! 이해가 어려웠던 코드 정리&lt;/h4&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1741883505626&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;U[&quot;subMenus&quot;] extends infer V&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;[ 설명 ]&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;U의 subMenus 속성이 있든 없든, 그 타입을 V라고 부르자라는 의미로 볼 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;V는 &quot;subMenus의 타입이 무엇이든 간에 그것&quot;을 나타냅니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MainMenu 타입에서 subMenus는 ReadonlyArray&amp;lt;SubMenu&amp;gt; | undefined가 될 수 있습니다 (optional이니까).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드는 subMenus의 실제 타입을 V라는 변수에 담아두고, 다음 조건에서 사용합니다.&lt;/p&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 extends ReadonlyArray&amp;lt;SubMenu&amp;gt;은 &quot;V가 SubMenu의 배열이면&quot;이라는 의미입니다. 즉, subMenus가 실제로 존재하는지 검사하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단히 정리하면:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;infer V로 subMenus의 타입을 V에 담고&lt;/li&gt;
&lt;li&gt;V가 SubMenu 배열이면 (즉, subMenus가 존재하면) &amp;rarr; 서브메뉴들에서 권한을 추출&lt;/li&gt;
&lt;li&gt;그렇지 않으면 (subMenus가 없으면) &amp;rarr; 메인 메뉴의 name을 권한으로 사용&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size20&quot;&gt;as const와 ReadonlyArray를 함께 사용한 이유?&lt;/h4&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예제 코드에서 as const와 ReadonlyArray를 함께 사용한 이유는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;&lt;u&gt;메뉴 및 권한 구조를 타입 시스템 수준에서 안전하게 관리&lt;/u&gt;할 수 있으며, 타입스크립트 컴파일러가 오타나 불일치를 감지할 수 있게 되기 때문입니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;ReadonlyArray&amp;lt;SubMenu&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MainMenu 인터페이스에서 subMenus 속성을 읽기 전용으로 만들어 불변성 보장하고, 한 번 정의된 메뉴 구조가 런타임에 변경되지 않도록 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;as const&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;menuList 전체를 불변 객체로 만들어 모든 문자열이 리터럴 타입으로 유지되도록 하고,&amp;nbsp;이를 통해 타입 추출 시 정확한 문자열 리터럴 타입을 얻을 수 있게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예: &quot;기기 내역 관리&quot; (문자열 리터럴 타입) vs string (일반 문자열 타입)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;각각의 개념과 기능&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;as const 타입 단언&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;as const는 타입스크립트에서 값을 &quot;리터럴 타입&quot;으로 변환해주는 특별한 타입 단언(type assertion)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 값을 불변(readonly)으로 만듭니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체의 모든 속성이 불변(readonly)이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열이 불변(readonly) 튜플이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 값의 타입을 최대한 구체적인 리터럴 타입으로 변환합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;string &amp;rarr; 정확한 문자열 리터럴(예: &quot;기기 내역 관리&quot;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;number &amp;rarr; 정확한 숫자 리터럴(예: 42)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 &amp;rarr; 모든 속성이 리터럴 타입인 읽기 전용 객체&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;ReadonlyArray&amp;lt;T&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;ReadonlyArray&amp;lt;T&amp;gt;는 타입스크립트에서 제공하는 기본 제네릭 타입으로, 요소를 추가, 제거 또는 변경할 수 없는 배열을 정의합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 배열 내용을 변경할 수 없음&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;push(), pop(), shift(), unshift(), splice() 등의 메서드 사용 불가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열 요소를 직접 수정(arr[0] = newValue) 불가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 읽기 전용 조작만 허용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;map(), filter(), reduce() 등의 비파괴적 메서드는 사용 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;length 속성 등 읽기는 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;2) 템플릿 리터럴 타입 활용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입 스크립트에서는 유니온 타입을 활용해 변수 타입을 특정 문자열로 지정할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트 4.1부터 이를 확장하는 방법인 템플릿 리터럴 타입이 지원되기 시작했고, 이는 &lt;u&gt;자바스크립트의 템플릿 리터럴 문법을 사용해 특정 문자열에 대한 타입을 선언할 수 있는 기능&lt;/u&gt;입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HeaderTag 타입은 템플릿 리터럴 타입을 사용해 아래와 같이 선언할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741884263607&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 적용 전
type HeaderTag = &quot;h1&quot; | &quot;h2&quot; | &quot;h3&quot;| &quot;h4&quot; | &quot;h5&quot;; 

// 적용 후 
type HeadingNumber = 1 | 2 | 3 | 4 | 5;
type HeaderTag = `h${HeadingNumber}`;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;템플릿 리터럴을 사용함으로써 더욱 읽기 쉬운 코드 작성이 가능하고, 코드를 재사용하고 수정하는데 용이한 타입을 선언할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Q. 그럼 최대로 추론할 수 있는 경우의 수는?&lt;/h4&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;타입스크립트 컴파일러가 유니온을 추론하는데 시간이 오래 걸리면 비효율적이므로 타입스크립트가 타입을 추론하지 않고 에러를 내뱉을 때가 있습니다. 이 땐, 템플릿 리터럴 타입에 사용된 유니온 조합의 경우의 수가 너무 많지 않게 적절히 나누어 타입을 정의하는 것이 좋습니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;타입스크립트는 템플릿 리터럴 타입에서 &lt;u&gt;약 10,000개의 경우의 수까지 추론&lt;/u&gt;할 수 있습니다. 이 한계를 초과하면 &quot;Type instantiation is excessively deep and possibly infinite.(타입 인스턴스화가 너무 깊거나 무한할 수 있습니다.)&quot;라는 오류 메시지가 표시됩니다. 예를 들어, 각각 10개 항목을 가진 유니온 타입 4개를 조합하면 10^4 = 10,000개의 경우의 수가 생기는데, 이는 한계에 도달합니다. 실무에서는 경우의 수가 100개 이하가 되도록 유니온 타입을 분리하는 것이 좋습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;적용 예시&lt;/h4&gt;
&lt;pre id=&quot;code_1741892666333&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 다양한 헤더 태그 타입 정의
type HeadingNumber = 1 | 2 | 3 | 4 | 5;
type HeaderTag = `h${HeadingNumber}`;

// 버튼 크기 타입 정의
type Size = 'small' | 'medium' | 'large';
type ButtonSize = `btn-${Size}`;

// 색상 변형 타입 정의
type Color = 'primary' | 'secondary' | 'success' | 'danger';
type ButtonVariant = `btn-${Color}` | `btn-outline-${Color}`;

// 조합된 버튼 클래스 타입
type ButtonClass = `${ButtonSize} ${ButtonVariant}`;

// 사용 예시
function createHeader(tag: HeaderTag, content: string) {
  const element = document.createElement(tag);
  element.textContent = content;
  return element;
}

// 유효한 호출
createHeader('h1', '제목');
createHeader('h3', '소제목');

// 타입 오류 발생
// createHeader('h6', '잘못된 태그'); // Error: 'h6' is not assignable to type 'HeaderTag'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;3) 커스텀 유틸리티 타입 활용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트로 프로젝트를 진행하다보면, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;타입을 정확하게 설정해야만 해당 컴포넌트, 함수의 안정성과 사용성을 높일 수 있지만&lt;span&gt; &lt;/span&gt;&lt;/span&gt;표현하기 힘든 타입을 마주할 때가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴 땐, 유틸리티 타입을 활용한 커스텀 유틸리티 타입을 제작해서 사용하면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(1) 유틸리티 함수를 활용해 styled-componets의 중복 타입 선언 피하기 : Pick, Omit&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제와 해결 과정을 살펴보면서 유틸리티 함수를 활용한 사례를 짚어보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Props 타입과 styled-componets 타입의 중복 선언 및 문제점&lt;/h4&gt;
&lt;pre id=&quot;code_1741892709876&quot; class=&quot;maxima&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;// 문제가 있는 방식: 중복 타입 선언
interface ButtonProps {
  fontSize?: string;
  backgroundColor?: string;
  color?: string;
  disabled?: boolean;
  onClick: () =&amp;gt; void;
}

// styled-components에서 또 다시 동일한 타입을 선언
const StyledButton = styled.button&amp;lt;{
  fontSize?: string;
  backgroundColor?: string;
  color?: string;
  disabled?: boolean;
}&amp;gt;` 
  font-size: ${({ fontSize }) =&amp;gt; fontSize || '16px'};
  background-color: ${({ backgroundColor }) =&amp;gt; backgroundColor || '#ffffff'};
  color: ${({ color }) =&amp;gt; color || '#000000'};
  opacity: ${({ disabled }) =&amp;gt; disabled ? 0.5 : 1};
`;

const Button: React.FC&amp;lt;ButtonProps&amp;gt; = (props) =&amp;gt; {
  return &amp;lt;StyledButton {...props}&amp;gt;{props.children}&amp;lt;/StyledButton&amp;gt;;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Pick, Omit 으로 개선하기&lt;/h4&gt;
&lt;pre id=&quot;code_1741892720153&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Pick, Omit을 활용한 개선 버전
interface ButtonProps {
  fontSize?: string;
  backgroundColor?: string;
  color?: string;
  disabled?: boolean;
  onClick: () =&amp;gt; void;
}

//   필요한 타입만 Pick으로 선택  
type StyledButtonProps = Pick&amp;lt;ButtonProps, 'fontSize' | 'backgroundColor' | 'color' | 'disabled'&amp;gt;;

//   또는 Omit으로 필요 없는 타입 제외  
// type StyledButtonProps = Omit&amp;lt;ButtonProps, 'onClick'&amp;gt;;

const StyledButton = styled.button&amp;lt;StyledButtonProps&amp;gt;` 
  font-size: ${({ fontSize }) =&amp;gt; fontSize || '16px'};
  background-color: ${({ backgroundColor }) =&amp;gt; backgroundColor || '#ffffff'};
  color: ${({ color }) =&amp;gt; color || '#000000'};
  opacity: ${({ disabled }) =&amp;gt; disabled ? 0.5 : 1};
`;

const Button: React.FC&amp;lt;ButtonProps&amp;gt; = (props) =&amp;gt; {
  return &amp;lt;StyledButton {...props}&amp;gt;{props.children}&amp;lt;/StyledButton&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;이처럼 Pick이나 Omit 유틸리티 타입을 활용해 props에서 필요한 부분만 선택해 styled-components의 컴포넌트 타입을 정의하면 중복된 코드를 작성하지 않아도 되고 유지보수를 더욱 편리하게 할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;자세히 알고가기 : Pick과 Omit&amp;nbsp;&lt;/h4&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pick 과 Omit은 각각 아래의 경우에 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;Pick&lt;/b&gt;: 필요한 속성이 소수일 때 (몇 개만 선택)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Omit&lt;/b&gt;: 제외할 속성이 소수일 때 (몇 개만 제외)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Pick&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pick&amp;lt;T,&amp;nbsp;K&amp;gt;:&amp;nbsp;T&amp;nbsp;타입에서&amp;nbsp;K로&amp;nbsp;지정된&amp;nbsp;속성만&amp;nbsp;선택하여&amp;nbsp;새로운&amp;nbsp;타입을&amp;nbsp;구성합니다. 마치 객체에서 필요한 속성만 '골라내는' 느낌입니다. &lt;br /&gt;예:&amp;nbsp;Pick&amp;lt;User,&amp;nbsp;'id'&amp;nbsp;|&amp;nbsp;'name'&amp;gt;&amp;nbsp;-&amp;nbsp;User&amp;nbsp;타입에서&amp;nbsp;id와&amp;nbsp;name&amp;nbsp;속성만&amp;nbsp;가진&amp;nbsp;새&amp;nbsp;타입&amp;nbsp;생성&lt;/p&gt;
&lt;pre id=&quot;code_1741892760811&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 예시: User 타입에서 id와 name만 필요한 경우
interface User {
  id: number;
  name: string;
  email: string;
  password: string;
}

// User에서 id와 name만 가진 타입 생성
type UserProfile = Pick&amp;lt;User, 'id' | 'name'&amp;gt;;
// 결과: { id: number; name: string; }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Omit&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Omit&amp;lt;T,&amp;nbsp;K&amp;gt;:&amp;nbsp;T&amp;nbsp;타입에서&amp;nbsp;K로&amp;nbsp;지정된&amp;nbsp;속성을&amp;nbsp;제외한&amp;nbsp;나머지로&amp;nbsp;새로운&amp;nbsp;타입을&amp;nbsp;구성합니다. 마치 객체에서 특정 속성을 '잘라내는' 느낌입니다. &lt;br /&gt;예:&amp;nbsp;Omit&amp;lt;User,&amp;nbsp;'password'&amp;gt;&amp;nbsp;-&amp;nbsp;User&amp;nbsp;타입에서&amp;nbsp;password를&amp;nbsp;제외한&amp;nbsp;모든&amp;nbsp;속성을&amp;nbsp;가진&amp;nbsp;새&amp;nbsp;타입&amp;nbsp;생성&lt;/p&gt;
&lt;pre id=&quot;code_1741892785170&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 예시: User 타입에서 password는 제외하고 싶은 경우
interface User {
  id: number;
  name: string;
  email: string;
  password: string;
}

// User에서 password를 제외한 타입 생성
type SafeUser = Omit&amp;lt;User, 'password'&amp;gt;;
// 결과: { id: number; name: string; email: string; }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(2) PickOne 유틸리티 함수&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트에는 서로 다른 2개 이상의 객체를 유니온 타입으로 받을 때, 아래 경우와 같이 타입 검사가 제대로 진행되지 않을 수도 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741886877853&quot; class=&quot;typescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;//   두 가지 객체 타입 정의
type Card = { card: string };
type Account = { account: string };

//   문제 상황: Card 또는 Account 중 하나만 받기 원함
function withdraw(type: Card | Account) { /* ... */ }

// ❌ 문제: 두 속성을 모두 포함한 객체도 타입 검사를 통과함
withdraw({ card: &quot;hyundai&quot;, account: &quot;hana&quot; }); // 에러가 발생해야 하지만 발생하지 않음!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;왜 타입 에러가 발생하지 않을까요? &lt;/b&gt;그 이유는 집합의 관점에서 Card | Account는 &lt;b&gt;합집합&lt;/b&gt;이기 때문입니다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;{ card: string }도 허용 ✅&lt;/li&gt;
&lt;li&gt;{ account: string }도 허용 ✅&lt;/li&gt;
&lt;li&gt;{ card: string, account: string }도 허용 ✅ (에러가 발생하지 않는 주원인)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 크게 2가지 방식으로 에러를 관리할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;[ 방식 1 ] 식별 가능한 유니온 (Discriminated Unions)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;식별 가능한 유니온을 사용해, type 값을 식별자로 구분 가능하게 만드는 방법 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 식별자를 일일이 추가해야 한다는 번거로움이 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1741886934882&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//   type 속성을 추가하여 구분 가능하게 만듦
type Card = {
  type: &quot;card&quot;;  //   식별자
  card: string;
};

type Account = {
  type: &quot;account&quot;;  //   식별자
  account: string;
};

function withdraw(type: Card | Account) { /* ... */ }

// ✅ 정확한 타입만 전달 가능
withdraw({ type: &quot;card&quot;, card: &quot;hyundai&quot; });
withdraw({ type: &quot;account&quot;, account: &quot;hana&quot; });&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;[ 방식 2 ] PickOne 유틸리티 타입&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드처럼 여러 속성 중 &lt;b&gt;딱 하나의 속성만&lt;/b&gt; 허용하는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;PickOne &lt;/span&gt;타입을 만들어 사용하는 방법 입니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741886993080&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//   최종 PickOne 타입 (두 가지 타입의 교차 타입)
type PickOne&amp;lt;T&amp;gt; = One&amp;lt;T&amp;gt; &amp;amp; ExcludeOne&amp;lt;T&amp;gt;;

//   Part 1: One&amp;lt;T&amp;gt; - 객체의 키-값 쌍 중 하나를 선택
type One&amp;lt;T&amp;gt; = { [P in keyof T]: Record&amp;lt;P, T[P]&amp;gt; }[keyof T];

//   Part 2: ExcludeOne&amp;lt;T&amp;gt; - 선택된 키 외에 다른 키를 undefined로 설정
type ExcludeOne&amp;lt;T&amp;gt; = { 
  [P in keyof T]: Partial&amp;lt;Record&amp;lt;Exclude&amp;lt;keyof T, P&amp;gt;, undefined&amp;gt;&amp;gt; 
}[keyof T];&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;코드별로 이해하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Part 1. One&amp;lt;T&amp;gt;&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741887397491&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type One&amp;lt;T&amp;gt; = { [P in keyof T]: Record&amp;lt;P, T[P]&amp;gt; }[keyof T];
//           ① 매핑된 타입  ② 레코드 생성   ③ 인덱싱&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ [P in keyof T]: T의 각 속성 키를 순회합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ Record&amp;lt;P, T[P]&amp;gt;: 키 P와 원래 값 T[P]를 가진 객체 타입을 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣ [keyof T]: 만들어진 객체 타입에서 T의 키로 접근합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741887417199&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//   예시: Card 타입에 One 적용
type Card = { card: string };

//   One&amp;lt;Card&amp;gt;의 단계별 변환:
// type One&amp;lt;T&amp;gt; = { [P in keyof T]: Record&amp;lt;P, T[P]&amp;gt; }[keyof T];
// 1️⃣ { [P in keyof Card]: Record&amp;lt;P, Card[P]&amp;gt; }
// 2️⃣ { card: Record&amp;lt;'card', string&amp;gt; }
// 3️⃣ { card: { card: string } }[keyof Card]
// 4️⃣ { card: string }

const one: One&amp;lt;Card&amp;gt; = { card: &quot;hyundai&quot; }; // ✅&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Part2. ExcludeOne&amp;lt;T&amp;gt;&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741887582530&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type ExcludeOne&amp;lt;T&amp;gt; = { 
  [P in keyof T]: Partial&amp;lt;Record&amp;lt;Exclude&amp;lt;keyof T, P&amp;gt;, undefined&amp;gt;&amp;gt; 
}[keyof T];
//               ① 매핑   ②옵셔널 ③레코드 ④키 제외    ⑤인덱싱&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ [P in keyof T]: T의 각 속성 키를 순회합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ Exclude&amp;lt;keyof T, P&amp;gt;: 현재 키 P를 제외한 모든 키를 선택합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣ Record&amp;lt;..., undefined&amp;gt;: 선택된 키들의 값을 undefined로 설정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4️⃣ Partial&amp;lt;...&amp;gt;: 모든 속성을 선택적(옵셔널)으로 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5️⃣ [keyof T]: 만들어진 객체 타입에서 T의 키로 접근합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741887618103&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//   예시: Card 타입에 ExcludeOne 적용
type Card = { card: string, other: number };

//   ExcludeOne&amp;lt;Card&amp;gt;의 단계별 변환 (card 키 기준):
// type ExcludeOne&amp;lt;T&amp;gt; = {[P in keyof T]: Partial&amp;lt;Record&amp;lt;Exclude&amp;lt;keyof T, P&amp;gt;, undefined&amp;gt;&amp;gt;}[keyof T];
// 1️⃣ Exclude&amp;lt;keyof Card, 'card'&amp;gt; = 'other'
// 2️⃣ Record&amp;lt;'other', undefined&amp;gt; = { other: undefined }
// 3️⃣ Partial&amp;lt;{ other: undefined }&amp;gt; = { other?: undefined }
// 4️⃣ 최종 결과 = { card: string, other?: undefined }

//   결과적으로 other 속성이 있어도 그 값이 undefined인 경우만 허용&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt; PickOne 적용 결과&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이렇게 PickOne 타입을 아래처럼 사용하면 여러 객체 타입 중 정확히 하나만 선택되도록 강제할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1741887747283&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type Card = { card: string };
type Account = { account: string };

//   PickOne을 사용한 함수 정의
function withdraw(type: PickOne&amp;lt;Card &amp;amp; Account&amp;gt;) { /* ... */ }

// ✅ 정상 동작: 하나의 속성만 있는 객체
withdraw({ card: &quot;hyundai&quot; });
withdraw({ account: &quot;hana&quot; });

// ❌ 타입 에러: 두 속성 모두 있는 객체
withdraw({ card: &quot;hyundai&quot;, account: &quot;hana&quot; }); // 타입 에러 발생&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;(2) NonNullable 유틸리티 함수&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트에서 null이나 undefined를 처리하기 위해 매번 if문으로 체크하는 것은 번거롭습니다. 이는 아래처럼 NonNullable 이라는 유틸리티 타입을 사용해 타입 가드 함수를 만들어 사용함으로써 해결할 수 있습니다. &amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식은 특히 API 응답이나 비동기 작업에서 null 값이 섞여 있을 때 유용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 여러 API 호출을 병렬로 처리할 때 일부 호출이 실패해도 전체 로직이 중단되지 않도록 해야 합니다. 이 예시에서는 이런 상황을 NonNullable 타입 가드로 해결하는 방법을 자세히 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;NonNullable&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; 유틸리티 타입&amp;nbsp;&lt;/h4&gt;
&lt;pre id=&quot;code_1741895643159&quot; class=&quot;excel&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//   타입스크립트 내장 유틸리티 타입
type NonNullable&amp;lt;T&amp;gt; = T extends null | undefined ? never : T;
//   T가 null이나 undefined면 never 반환, 아니면 T 그대로 반환&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;NonNullable 타입 가드 함수 구현&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입 가드 함수 구현은 아래와 같이 해볼 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741889807037&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//   null/undefined 체크 함수 (타입 가드)
function NonNullable&amp;lt;T&amp;gt;(value: T): value is NonNullable&amp;lt;T&amp;gt; {
  return value !== null &amp;amp;&amp;amp; value !== undefined;
}
//   is 키워드: 함수 반환값이 true이면 타입이 NonNullable&amp;lt;T&amp;gt;로 좁혀짐&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;실제 활용 예시: Promise.all에서 활용하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;예시 코드&amp;nbsp;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; API 요청 함수 (에러 시 null 반환)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입 시그니처가 Promise&amp;lt;AdCampaign[] | null&amp;gt;로 선언되어 있어, API 호출이 성공하면 광고 목록을, 실패하면 null을 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오류가 발생하더라도 애플리케이션이 중단되지 않고 계속 실행되도록 하는 방어적 프로그래밍 기법입니다. null로 에러를 처리하는 것은 이후 처리 과정에서 에러를 구분할 수 있게 해주지만, 타입 안전성을 위한 추가 작업이 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741889843974&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class AdCampaignAPI {
  static async operating(shopNo: number): Promise&amp;lt;AdCampaign[]&amp;gt; {
    try {
      //   API 호출
      return await fetch(`/ad/shopNumber=${shopNo}`);
    } catch (error) {
      // ⚠️ 에러 발생 시 null 반환
      return null;
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;여러 상점의 광고 정보 가져오기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Promise.all은 모든 프로미스가 해결될 때까지 기다린 후 결과 배열을 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 API 호출은 AdCampaign[] 또는 null을 반환할 수 있으므로, 결과 배열의 타입은 Array&amp;lt;AdCampaign[] | null&amp;gt;이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 타입은 &quot;각 요소가 광고 목록이거나 null일 수 있는 배열&quot;을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 상태에서 배열을 순회하면 null 항목으로 인한 런타임 오류가 발생할 위험이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741889874788&quot; class=&quot;groovy&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;//   상점 목록
const shopList = [
  { shopNo: 100, category: &quot;chicken&quot; },
  { shopNo: 101, category: &quot;pizza&quot; },
  { shopNo: 102, category: &quot;noodle&quot; },
];

//   모든 상점의 광고 정보 요청
const shopAdCampaignList = await Promise.all(
  shopList.map((shop) =&amp;gt; AdCampaignAPI.operating(shop.shopNo))
);
//   타입: Array&amp;lt;AdCampaign[] | null&amp;gt; (null 포함 가능성)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 필터링 방식은 조건에 맞는 요소만 포함한 새 배열을 반환합니다. !!shop은 shop이 null이 아닐 때만 true가 되므로, null 값은 필터링됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;하지만 타입스크립트는 이 필터링을 인식하지 못합니다.&lt;/b&gt;&amp;nbsp;반환 타입은 여전히 Array&amp;lt;AdCampaign[] | null&amp;gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 타입스크립트가 일반 JavaScript 함수의 복잡한 필터링 로직을 완전히 이해하지 못하기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 타입 시스템은 여전히 배열 요소에 null이 있을 수 있다고 가정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741889891473&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ❌ 문제가 있는 방식
const filteredAds = shopAdCampaignList.filter((shop) =&amp;gt; !!shop);
//   타입: Array&amp;lt;AdCampaign[] | null&amp;gt; (여전히 null 가능성 있음)&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[ 개선 ] NonNullable 함수로 필터링 (타입 안전)&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741889915931&quot; class=&quot;typescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;//   타입 가드 함수 정의
function NonNullable&amp;lt;T&amp;gt;(value: T): value is NonNullable&amp;lt;T&amp;gt; {
  return value !== null &amp;amp;&amp;amp; value !== undefined;
}

// ✅ NonNullable 함수로 필터링
const shopAds = shopAdCampaignList.filter(NonNullable);
//   타입: Array&amp;lt;AdCampaign[]&amp;gt; (null 가능성 제거됨)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입 가드 함수 NonNullable은 값이 null 또는 undefined가 아닌지 확인합니다. value is NonNullable&amp;lt;T&amp;gt; 구문은 TypeScript에게 &quot;이 함수가 true를 반환하면 값이 NonNullable&amp;lt;T&amp;gt; 타입임을 보장한다&quot;고 알려줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 TypeScript는 filter 후에 배열에서 null과 undefined가 제거되었다는 것을 이해할 수 있습니다. 결과적으로 shopAds의 타입은 Array&amp;lt;AdCampaign[]&amp;gt;으로 정확하게 추론됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;NonNullable 사용 장점 정리&lt;/h4&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;NonNullable을 사용하면 컴파일러가 null이 제거되었음을 인식하여 타입 오류를 더 정확히 감지합니다.&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매번 if (value !== null &amp;amp;&amp;amp; value !== undefined) 검사를 작성할 필요가 없어 코드가 긴결해지고, 추후 코드를 수정할 때 null 체크를 누락할 위험이 줄어듭니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741896742098&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ❌ NonNullable 없이 사용하면
shopAdCampaignList.forEach(ads =&amp;gt; {
  if (ads) {  // null 체크 필요
    ads.forEach(ad =&amp;gt; console.log(ad.title));
  }
});

// ✅ NonNullable 사용 후
shopAds.forEach(ads =&amp;gt; {
  // null 체크 불필요! 타입스크립트가 ads가 항상 AdCampaign[]임을 알고 있음
  ads.forEach(ad =&amp;gt; console.log(ad.title));
});&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;4) 불변 객체 타입으로 활용&amp;nbsp;&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체의 키를 문자열로 다룰 때 발생할 수 있는 문제를 살펴보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1741890352685&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//   색상 정보를 담은 객체
const colors = { 
  red: &quot;#F45452&quot;, 
  green: &quot;#0C952A&quot;, 
  blue: &quot;#1A7CFF&quot;, 
};

// ❌ 문제가 있는 접근 방식: string 타입 사용
const getColorHex = (key: string) =&amp;gt; colors[key];
//   반환 타입: any (타입 안전성 없음)
//   &quot;purple&quot; 같은 존재하지 않는 키도 전달 가능&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드의 문제는 getColorHex 함수가 any 타입을 반환한다는 점과 &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;존재하지 않는 키를 전달해도 컴파일 시 오류가 발생하지 않는 다는 것입니다. (더불어 자동 완성 지원이 되지 않습니다.)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴 땐&lt;u&gt;, keyof와 as const로 객체를 불변으로 만들어 타입 안전성을 확보&lt;/u&gt;할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;[ step 1 ]&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;nbsp;as const로 객체를 불변으로 만들기&lt;/h4&gt;
&lt;pre id=&quot;code_1741890460736&quot; class=&quot;typescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;// ✅ as const로 객체를 불변으로 선언
const colors = { 
  red: &quot;#F45452&quot;, 
  green: &quot;#0C952A&quot;, 
  blue: &quot;#1A7CFF&quot;, 
} as const;
//   각 값이 정확한 문자열 리터럴 타입으로 변환됨&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;[ step 2 ]&lt;span&gt;&amp;nbsp;&lt;/span&gt;keyof와 typeof로 객체 키 타입 추출하기&lt;/h4&gt;
&lt;pre id=&quot;code_1741890472363&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ✅ 객체 키 타입 추출
type ColorKey = keyof typeof colors;
//   결과: &quot;red&quot; | &quot;green&quot; | &quot;blue&quot;

//   타입 안전한 함수 구현
const getColorHex = (key: ColorKey) =&amp;gt; colors[key];
//   반환 타입: &quot;#F45452&quot; | &quot;#0C952A&quot; | &quot;#1A7CFF&quot; (정확한 타입)&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;const vs as const의 차이점 정리&lt;/h4&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;const vs as const의 차이점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;const와 as const는 다른 목적으로 사용됩니다:&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;const (JavaScript의 상수 선언)&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1741891316451&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const colors = { red: &quot;#F45452&quot;, green: &quot;#0C952A&quot; }; // 타입: { red: string; green: string; }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;const는 &lt;b&gt;변수 자체&lt;/b&gt;가 재할당되지 않도록 합니다.&lt;/li&gt;
&lt;li&gt;그러나 객체의 &lt;b&gt;내부 속성은 여전히 변경 가능&lt;/b&gt;합니다 (colors.red = &quot;새로운색상&quot; 가능)&lt;/li&gt;
&lt;li&gt;타입스크립트는 값을 &lt;b&gt;넓은 타입&lt;/b&gt;(string, number 등)으로 추론합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;as const (TypeScript의 타입 단언)&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1741891323748&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const colors = { red: &quot;#F45452&quot;, green: &quot;#0C952A&quot; } as const;
// 타입: { readonly red: &quot;#F45452&quot;; readonly green: &quot;#0C952A&quot;; }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;as const는 객체 전체를 **깊은 수준까지 불변(readonly)**으로 만듭니다.&lt;/li&gt;
&lt;li&gt;객체의 &lt;b&gt;내부 속성도 변경 불가능&lt;/b&gt;해집니다 (colors.red = &quot;새로운색상&quot; 불가능)&lt;/li&gt;
&lt;li&gt;타입스크립트는 값을 &lt;b&gt;정확한 리터럴 타입&lt;/b&gt;으로 추론합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;리터럴 타입 vs string 타입의 차이&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1741891399738&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// string 타입
let color1: string = &quot;red&quot;;
color1 = &quot;anything&quot;; // ✅ OK

// 리터럴 타입
let color2: &quot;red&quot; = &quot;red&quot;;
color2 = &quot;blue&quot;; // ❌ 오류: &quot;blue&quot; 타입은 &quot;red&quot; 타입에 할당할 수 없습니다&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;string 타입&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 문자열 값을 허용합니다.&lt;/li&gt;
&lt;li&gt;어떤 문자열이든 할당 가능합니다.&lt;/li&gt;
&lt;li&gt;타입 안전성이 낮습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;리터럴 타입&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정확히 지정된 값만 허용합니다.&lt;/li&gt;
&lt;li&gt;다른 값은 할당할 수 없습니다.&lt;/li&gt;
&lt;li&gt;타입 안전성이 높습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;객체에서 리터럴 타입의 의미&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1741891419644&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// as const 없음
const theme = { fontSize: { small: &quot;12px&quot; } };
theme.fontSize.small = &quot;14px&quot;; // ✅ OK
// 타입: { fontSize: { small: string } }

// as const 있음
const themeConst = { fontSize: { small: &quot;12px&quot; } } as const;
themeConst.fontSize.small = &quot;14px&quot;; // ❌ 오류: readonly 속성을 할당할 수 없음
// 타입: { readonly fontSize: { readonly small: &quot;12px&quot; } }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;5) Record 원시 타입 키 개선하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트에서 객체의 키-값 관계를 정의할 때 Record&amp;lt;K, V&amp;gt; 타입을 자주 사용합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 키 타입을 string이나 number 같은 원시 타입으로 지정하면 타입 안전성이 저하된다는 문제가 있습니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741891554479&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//   문제가 있는 방식: 키 타입이 너무 광범위함
type Category = string; //   모든 문자열이 허용됨

interface Food {
  name: string;
  // ... 기타 속성들
}

//   음식 카테고리별 목록
const foodByCategory: Record&amp;lt;Category, Food[]&amp;gt; = {
  한식: [{ name: &quot;제육덮밥&quot; }, { name: &quot;뚝배기 불고기&quot; }],
  일식: [{ name: &quot;초밥&quot; }, { name: &quot;텐동&quot; }],
};&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1741891585040&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ❌ 존재하지 않는 키에 접근해도 타입 오류가 없음
foodByCategory[&quot;양식&quot;]; //   타입: Food[] (실제로는 undefined)

// ❌ 런타임 오류 발생!
foodByCategory[&quot;양식&quot;].map((food) =&amp;gt; console.log(food.name));
//   Uncaught TypeError: Cannot read properties of undefined (reading 'map')&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 2가지 방법으로 개선해볼 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;[ 방법 1 ] 옵셔널 체이닝 사용하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 해결책으로 옵셔널 체이닝(?.)을 사용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1741891621445&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ✅ 옵셔널 체이닝으로 런타임 오류 방지
foodByCategory[&quot;양식&quot;]?.map((food) =&amp;gt; console.log(food.name));
//   undefined일 경우 아무 동작 없이 넘어감&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옵셔널 체이닝(?.)은 객체 속성에 안전하게 접근하는 방법으로, &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;객체가 null 또는 undefined이면 &amp;rarr; 즉시 undefined 를 반환하고,&amp;nbsp;&lt;/span&gt;객체가 존재하면 &amp;rarr; 정상적으로 속성에 접근합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 이 방식은 아래와 같은 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/span&gt;이 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) 개발자가 옵셔널 체이닝을 잊어버리더라도 타입 시스템이 오류를 표시하지 않습니다. &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이 경우, 타입스크립트는 foodByCategory[&quot;양식&quot;]의 타입을 Food[]로 추론합니다. (실제론 undefined)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2)&amp;nbsp;모든 접근마다 개발자가 옵셔널 체이닝을 기억해서 사용해야 하고,&amp;nbsp;한 번이라도 빼먹으면 런타임 오류 발생.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3) 코드만 봤을 때 해당 키가 존재하지 않을 수 있다는 의도가 타입에 표현되지 않습니다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;[ 방법 2 ]&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;nbsp;PartialRecord 타입 정의하기&lt;/h3&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;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;b&gt;PartialRecord&lt;/b&gt;&lt;span&gt; 을 사용하는 것입니다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1741891746531&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//   부분적인 레코드 타입 정의
type PartialRecord&amp;lt;K extends string | number | symbol, T&amp;gt; = {
  [P in K]?: T;
};

//   개선된 방식
const foodByCategory: PartialRecord&amp;lt;Category, Food[]&amp;gt; = {
  한식: [{ name: &quot;제육덮밥&quot; }, { name: &quot;뚝배기 불고기&quot; }],
  일식: [{ name: &quot;초밥&quot; }, { name: &quot;텐동&quot; }],
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;개선함으로써 얻을 수 있는 효과&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1741891774137&quot; class=&quot;javascript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;//   이제 타입이 Food[] | undefined로 정확하게 추론됨
foodByCategory[&quot;양식&quot;];

// ❌ 타입 오류가 발생 (컴파일 시 문제 감지)
foodByCategory[&quot;양식&quot;].map((food) =&amp;gt; console.log(food.name));
//   &quot;Object is possibly 'undefined'&quot; 오류 발생

// ✅ 옵셔널 체이닝 사용 필요성을 타입 시스템이 알려줌
foodByCategory[&quot;양식&quot;]?.map((food) =&amp;gt; console.log(food.name)); // OK&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;이렇게 PartialRecord 사용을 하면, 없는 키에 접근할 때 undefined 가능성을 타입 시스템이 인식하기 때문에 런타임 전에 오류를 발견할 수 있으며, 옵셔널 체이닝과 같은 안전한 방법으로 사용하도록 유도합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법은 외부 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;u&gt;PartialRecord 를 사용해야하는 이유&lt;/u&gt;를 정리해볼 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) 개발자가 옵셔널 체이닝을 빼먹으면 컴파일러가 즉시 오류 표시함으로써 런타임 오류를 미리 방지할 수 있습니다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2)&amp;nbsp; &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;타입 자체가 &quot;이 객체의 키는 존재하지 않을 수 있다&quot;는 정보를 담고 있기 때문에 의도 전달이 명확해집니다.&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3)&amp;nbsp;프로젝트 전체에서 일관된 타입 체크가 가능합니다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체적으로 요약하면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옵셔널 체이닝만 사용하는 것은 &quot;개발자가 항상 기억해야 하는 방어적 코딩&quot; 방식이고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PartialRecord를 사용하는 것은 &quot;타입 시스템이 자동으로 체크해주는 예방적 코딩&quot; 방식이라고 볼 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/TypeScript</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/42</guid>
      <comments>https://dreamyard.tistory.com/entry/Typescript-%ED%83%80%EC%9E%85-%ED%99%9C%EC%9A%A9%ED%95%98%EA%B8%B0#entry42comment</comments>
      <pubDate>Fri, 14 Mar 2025 08:04:01 +0900</pubDate>
    </item>
    <item>
      <title>[Typescript] 타입 확장/좁히기</title>
      <link>https://dreamyard.tistory.com/entry/Typescript-%ED%83%80%EC%9E%85-%ED%99%95%EC%9E%A5%EC%A2%81%ED%9E%88%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 Typescript의 타입 확장과 타입 좁히기에 대해 정리해보았습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 타입 확장하기&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입 확장의 가장 큰 장점은 기존 타입을 바탕으로 타입을 확장함으로서 불필요한 코드 중복을 줄일 수 있다는 점입니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;interface와 type 키워드로 정의된 타입은 extends, 교차 타입, 유니온 타입을 사용해 타입을 확장합니다. extends, 교차 타입, 유니온 타입 간의 차이를 파악하고 언제 사용하면 좋은지 정리해보았습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) extends&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;extends를 활용하면 기존 타입을 확장해 새로운 타입을 정의할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예시 코드를 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;예시 코드&lt;/h4&gt;
&lt;pre id=&quot;code_1741630362762&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 기본 장바구니 아이템 타입
interface BaseCartItem {
  id: string;      // 상품 고유 ID
  name: string;    // 상품명
  price: number;   // 상품 가격
}

// BaseCartItem을 확장한 CartItem 타입
interface CartItem extends BaseCartItem {
  quantity: number;     //   수량 정보 추가
  options?: string[];   //   옵션 정보 (선택사항)
  discountRate?: number; //   할인율 (선택사항)
}

// 사용 예시
const myItem: CartItem = {
  id: &quot;item-1&quot;,
  name: &quot;티셔츠&quot;,
  price: 20000,
  quantity: 2,
  options: [&quot;색상: 블랙&quot;, &quot;사이즈: L&quot;]
}; // ✅ BaseCartItem의 모든 속성 + CartItem의 추가 속성 포함&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 유니온 타입&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2개 이상의 타입을 조합하여 사용하는 방법 입니다. 집합 관점에서 합집합으로 해석할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741625038238&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type MyUnion = A | B;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 의미를 해석하면, MyUnion은 타입 A와 B의 합집합으로, A의 모든 원소는 집합 MyUnion의 원소이며, B의 모든 원소 역시 MyUnion의 원소로, A와 B타입의 모든 값이 MyUnion타입의 값이 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, 주의해야할 점은 유니온 타입으로 선언된 값은 유니온 타입에 포함된 모든 타입이 공통으로 갖고 있는 속성에만 접근할 수 있습니다. 타입 스크립트의 타입을 속성의 집합이 아니라 값이 집합이라고 생각해야 좀 더 이해하기 쉽습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741630375774&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 요리 단계 타입
interface CookingStep {
  orderId: string;  // 주문 ID
  time: number;     // 요리 시간(분)
  price: number;    // 요리 가격
  chef: string;     // 요리사 이름
}

// 배달 단계 타입
interface DeliveryStep {
  orderId: string;   // 주문 ID
  time: number;      // 배달 소요 시간(분)
  distance: number;  // 배달 거리(km)
  deliveryPerson: string; // 배달원 이름
}

// 유니온 타입 정의 - CookingStep 또는 DeliveryStep 중 하나
type Step = CookingStep | DeliveryStep;

// 사용 예시
function processStep(step: Step) {
  //   공통 속성에만 직접 접근 가능
  console.log(`주문번호: ${step.orderId}, 소요시간: ${step.time}`);
  
  //   Error: chef 속성은 CookingStep에만 있어 직접 접근 불가
  // console.log(step.chef);
  
  // ✅ 타입 가드를 사용하여 특정 타입 속성에 접근
  if ('chef' in step) {
    //  &amp;zwj;  CookingStep인 경우
    console.log(`요리사: ${step.chef}, 가격: ${step.price}`);
  } else {
    //   DeliveryStep인 경우
    console.log(`배달원: ${step.deliveryPerson}, 거리: ${step.distance}km`);
  }
}&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;즉, step이라는 유니온 타입은 CookingStep 또는 DeliveryStep 타입에 해당할 뿐이지만 CookingStep이면서 DeleiveryStep인 것은 아니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) 교차 타입&lt;/h3&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;pre id=&quot;code_1741630400578&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 요리 단계 타입
interface CookingStep {
  orderId: string;  // 주문 ID
  time: number;     // 요리 시간(분)
  price: number;    // 요리 가격
}

// 배달 단계 타입
interface DeliveryStep {
  orderId: string;   // 주문 ID
  time: number;      // 배달 소요 시간(분)
  distance: number;  // 배달 거리(km)
}

// 교차 타입 정의 - CookingStep과 DeliveryStep의 모든 속성 포함
type BaedalProgress = CookingStep &amp;amp; DeliveryStep;

// 사용 예시
const order: BaedalProgress = {
  orderId: &quot;order-123&quot;,  
  time: 60,              // ⏱️ 총 소요 시간(분)
  price: 15000,          //   요리 가격
  distance: 3.2          //  ️ 배달 거리(km)
};

// ✅ BaedalProgress는 CookingStep과 DeliveryStep의 모든 속성을 가집니다
console.log(`주문번호: ${order.orderId}`);
console.log(`소요시간: ${order.time}`);
console.log(`가격: ${order.price}`);
console.log(`거리: ${order.distance}`);&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;BaedalProgress는 CookingStep과 DeliveryStep 타입을 합쳐 모든 속성을 가진 단일 타입이 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741625440674&quot; class=&quot;typescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;type MyIntersection = A &amp;amp; B;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유니온은 합집합의 개념이라고 했지만, 교차 타입은 교집합의 개념과 비슷합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MyIntersection 타입의 모든 값은 A 타입의 값이며, 동시에 B타입의 값입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;집합 관점에서 해석하면 집합 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;MyIntersection&lt;span&gt; 의 모든 원사는 집합 A의 원소이자 B의 원소입니다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;( BeadalProgress 교차 타입은 CookingStep이 가진 속성(orderId, time, price)과 DeliveryStep이 가진 속성(orderId, time, distance)를 모두 만족하는 값의 집합이라고 해석할 수 있습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 타입에 공통된 속성이 없더라도, 두 타입의 속성을 모두 포함한 타입이 됩니다. 타입 자체가 속성이 아닌 값의 집합으로 해석되기 때문입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 교차 타입 사용 시, 타입이 서로 호환되지 않는 경우도 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741630420392&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 문자열 ID 타입
type IdType = {
  id: string;  // 문자열 타입의 id
};

// 숫자 타입
type Numeric = {
  id: number;  // 숫자 타입의 id
};

// 호환되지 않는 교차 타입 - id가 string과 number를 동시에 만족할 수 없음
type Universal = IdType &amp;amp; Numeric;

// ⚠️ 시도하면 에러 발생
const value: Universal = {
  id: &quot;abc123&quot; //   에러! string과 number 타입을 동시에 만족할 수 없음
};

//   Universal 타입의 id는 사실상 never 타입이 됨
// string과 number의 교집합은 없기 때문에 어떤 값도 할당 불가&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Universal은 IdType과 Numeric의 교차타입이므로, 두 타입을 모두 만족하는 경우에만 만족기 때문에 Universal의 타입은 never가 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4) extends와 교차 타입&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;extends 키워드를 사용해 교차 타입을 작성할 수도 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;교차 타입을 사용할 경우 각각의 타입은 type 키워드로 선언되어야 합니다. 왜냐하면, 유니온 타입과 교차 타입을 사용한 새로운 타입은 오직 type 키워드로만 선언할 수 있기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 주의할 점은 extends 키워드를 사용한 타입이 교차 타입과 100% 상응하지는 않는 다는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드 예시를 통해 살펴보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741631356814&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// DeliveryTip 타입 정의
interface DeliveryTip {
  tip: number; //   배달팁은 숫자 타입
}

// ❌ Error: 같은 이름의 속성 tip이 서로 호환되지 않음
interface Filter extends DeliveryTip {
  tip: string; //   number와 호환되지 않는 string 타입으로 재정의 시도
}&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;DeliveryTip 타입은 number 타입의 tip 속성을 가지고 있고, DeliveryTip을 extends로 확장한 Filter 타입에 string 타입의 속성 tip을 선언하면 타입이 호환되지 않는다는 에러가 발생합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇담 아래와 코드를 작성했을 땐 어떻게 될까요?&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741631370757&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// DeliveryTip 타입 정의
interface DeliveryTip {
  tip: number; //   배달팁은 숫자 타입
}

// ✅ 컴파일 에러 없음 (하지만 tip의 타입은 never가 됨)
type Filter = DeliveryTip &amp;amp; {
  tip: string; //   number와 string은 호환되지 않지만 컴파일 시점에는 에러 없음
};

// 실제 사용 시에는 never 타입이 되어 어떤 값도 할당 불가
const myFilter: Filter = {
  tip: &quot;무료&quot; //   실제로는 에러! tip은 never 타입이 됨
};

//   교차 타입에서는 타입 선언 시점에 에러를 발생시키지 않고
// 사용 시점에 해당 속성을 never 타입으로 만들어 어떤 값도 할당할 수 없게 함&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;extends를 &amp;amp;로만 바꿨을 뿐인데 에러가 발생하지 않습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 tip 속성의 타입은 number도 아니고 string 도 아닌 never가 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;type 키워드는 교차 타입으로 선언 시, 새롭게 추가되는 속성에 대해 미리 알 수 없기 때문에 선언 시 에러가 발생하지 않습니다. 하지만, tip 이라는 같은 속성에 대해 서로 호환되지 않는 타입이 선언되면서 never가 된 것 입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 타입 좁히기&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입 좁히기란 변수 또는 표현식의 타입 범위를 더 작은 범위로 좁혀나가는 과정을 의미합니다. 타입 좁히기를 통해 더 정확하고 명시적인 타입 추론이 가능하고, 복잡한 타입을 작은 범위로 축소해 타입 안정성을 높일 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 타입 가드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트에서의 분기 처리는 조건과 타입 가드를 활용해 변수나 표현식의 타입 범위를 좁혀 다양한 상황에 따라 다른 동작을 수행하는 것을 의미합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입 가드는 런타임에 조건문을 사용해 타입을 검사하고, 타입 범위를 좁혀줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 타입을 할당할 수 있는 스코프에서 특정 타입을 조건으로 만들어 분기 처리하고 싶을 때, 여러 타입을 할당할 수 있다는 것은 변수가 유니온 또는 any 타입 등과 같이 조건으로 검사하려는 타입보다 넓은 범위의 타입을 가진다는 것을 의미합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ 스코프(scope)란? 타입스크립트에서의 스코프는 변수와 함수 등의 식별자가 유효한 범위를 나타냅니다. 즉, 변수와 함수를 선언하거나 사용할 수 있는 영역을 의미합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 인자의 타입이 A 또는 B일 때를 구분해 로직을 처리한다면, if 문을 사용하면 될 것 같지만 컴파일 시 타입 정보가 모두 제거되 런타임에는 존재하지 않기 때문에 이는 불가한 방법입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;(0) 타입 가드의 구분&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 특정 문맥 안에서 타입 스크립트가 해당 변수를 타입 A로 추론하도록 유도하면서 런타임에도 유효하도록 하는 방법이 필요한데, 이 때 사용하는 방법이 타입 가드 입니다. 타입 가드에는 크게 &lt;u&gt;자바스크립트 연산자를 사용한 타입 가드&lt;/u&gt;와 &lt;u&gt;사용자 정의 타입 가드&lt;/u&gt;로 구분할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;(1) 자바스크립트 연산자를 사용한 타입 가드&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;typeof, instanceof, in과 같은 연산자 사용하며,&amp;nbsp;제어문으로 특정 타입 값을 가질 수 밖에 없는 상황을 유도해 타입을 좁히는 방식 입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;(2) 사용자 정의 타입 가드&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 직접 어떤 타입으로 값을 좁힐지 직접 지정하는 방식 입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;(1) 원시 타입 추론 할 땐 typeof 활용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;typeof 연산자는 이용하면 원시 타입에 대해 추론할 수 있습니다. 하지만, 자바스크립트 타입 시스템에만 대응할 수 있으며, 자바스크립트 동작 방식으로 인해 null과 배열 타입이 object 타입으로 판별되는 등 복잡한 타입 검증에는 한계가 있으므로 원시 타입을 좁히는 용도로만 사용하는 것이 권장됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;typeof 연산자를 사용해 검사할 수 있는 타입 목록&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;-&amp;nbsp;&quot;string&quot;:&amp;nbsp;문자열&amp;nbsp;타입&amp;nbsp;(예:&amp;nbsp;typeof&amp;nbsp;&quot;hello&quot;&amp;nbsp;===&amp;nbsp;&quot;string&quot;) &lt;br /&gt;-&amp;nbsp;&quot;number&quot;:&amp;nbsp;숫자&amp;nbsp;타입&amp;nbsp;(예:&amp;nbsp;typeof&amp;nbsp;42&amp;nbsp;===&amp;nbsp;&quot;number&quot;) &lt;br /&gt;-&amp;nbsp;&quot;boolean&quot;:&amp;nbsp;불리언&amp;nbsp;타입&amp;nbsp;(예:&amp;nbsp;typeof&amp;nbsp;true&amp;nbsp;===&amp;nbsp;&quot;boolean&quot;) &lt;br /&gt;-&amp;nbsp;&quot;undefined&quot;:&amp;nbsp;undefined&amp;nbsp;타입&amp;nbsp;(예:&amp;nbsp;typeof&amp;nbsp;undefined&amp;nbsp;===&amp;nbsp;&quot;undefined&quot;) &lt;br /&gt;-&amp;nbsp;&quot;object&quot;:&amp;nbsp;객체&amp;nbsp;타입&amp;nbsp;(null,&amp;nbsp;배열,&amp;nbsp;일반&amp;nbsp;객체&amp;nbsp;포함)&amp;nbsp;(예:&amp;nbsp;typeof&amp;nbsp;{}&amp;nbsp;===&amp;nbsp;&quot;object&quot;) &lt;br /&gt;-&amp;nbsp;&quot;function&quot;:&amp;nbsp;함수&amp;nbsp;타입&amp;nbsp;(예:&amp;nbsp;typeof&amp;nbsp;(()&amp;nbsp;=&amp;gt;&amp;nbsp;{})&amp;nbsp;===&amp;nbsp;&quot;function&quot;) &lt;br /&gt;-&amp;nbsp;&quot;symbol&quot;:&amp;nbsp;심볼&amp;nbsp;타입&amp;nbsp;(예:&amp;nbsp;typeof&amp;nbsp;Symbol()&amp;nbsp;===&amp;nbsp;&quot;symbol&quot;) &lt;br /&gt;-&amp;nbsp;&quot;bigint&quot;:&amp;nbsp;BigInt&amp;nbsp;타입&amp;nbsp;(ES2020&amp;nbsp;이상)&amp;nbsp;(예:&amp;nbsp;typeof&amp;nbsp;BigInt(123)&amp;nbsp;===&amp;nbsp;&quot;bigint&quot;)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;(2) 인스턴스화된 객체 타입을 판별할 땐 intanceof 활용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;instanceof 연산자는 인스턴스화된 객체 타입을 판별하는 타입 가드로 사용할 수 있습니다. A instanceof B의 형태로 A에는 타입 검사할 대상 변수, B에는 특정 객체의 생성자가 들어갑니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A의 프로토타입 체인에 생성자 B가 존재한다면 true, 그렇지 않으면 false를 반환합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예시에서는 HTMLInputElement에 존재하는 blur 메서드를 사용하기 위해서 event.target이 HTMLInputElement의 인스턴스인지 검사한 후 분기처리하는 로직을 구현한 코드입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741631464964&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//   폼 제출 이벤트 핸들러
function handleSubmit(event: Event) {
  event.preventDefault(); //   기본 동작 방지
  
  //   event.target이 HTMLInputElement인지 확인
  if (event.target instanceof HTMLInputElement) {
    // ✅ HTMLInputElement로 타입이 좁혀짐
    event.target.blur(); // ⚡ input 요소에만 있는 blur 메서드 사용 가능
    console.log(event.target.value); //   input 값 접근 가능
  }
  
  // ⚠️ 타입 가드 없이 사용하면 컴파일 에러 발생
  // event.target.blur(); //   에러: 'target'의 타입이 'EventTarget'이므로 'blur' 속성이 없음
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;(3) 객체의 속성이 있는지 없는지 구분할 땐 in 활용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;in 연산자는 객체에 속성이 있는지 확인한 후 true 또는 false를 반환합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A in B 형태로 사용되는데 A라는 속성이 B 객체에 존재하는지 검사합니다. 프로토타입 체인으로 접근할 수 있는 속성이라면 전부 true를 반환하며, 속성에 undefined가 할당되어도 속성 자체가 있는지 없는지 여부만 판단합니다.(delete 연산을 통해 객체 내부에서 해당 속성을 제거해야 false 반환)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741631486047&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//   사용자 타입 정의
interface User {
  id: number;
  name: string;
  email: string;
}

//   회사 타입 정의
interface Company {
  id: number;
  name: string;
  address: string;
}

//   유저 또는 회사 정보를 출력하는 함수
function printInfo(entity: User | Company) {
  console.log(`ID: ${entity.id}`);
  console.log(`Name: ${entity.name}`);
  
  //   'email' 속성이 있는지 확인하여 User 타입인지 체크
  if ('email' in entity) {
    // ✅ User 타입으로 좁혀짐
    console.log(`Email: ${entity.email}`);
  }
  
  //   'address' 속성이 있는지 확인하여 Company 타입인지 체크
  if ('address' in entity) {
    // ✅ Company 타입으로 좁혀짐
    console.log(`Address: ${entity.address}`);
  }
}&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;자바스크립트의 in 연산자는 런타임 값만 검사하는 반면, 타입스크립트에서는 객체 타입에 속성이 존재하는지 여부를 검사합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드 처럼 여러 객체 타입을 유티온 타입으로 가질 때 in 연산자를 사용해 속성의 유무에 따라 조건 분기를 할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;(4) 사용자 정의 타입 가드 만들 땐 is 활용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 타입 가드 함수를 만들 땐, 반환 타입이 타입 명제인 함수를 정의해 사용할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입 명제는 A is B 형식으로 작성되는데, A는 매개변수 이름 B는 타입 입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ 타입 명제 (type predicates) : 함수의 반환 타입에 대한 타입 가드를 수행하기 위해 사용되는 특별한 형태의 함수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741631496240&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//   응답 데이터 타입 정의
interface SuccessResponse {
  status: 'success';
  data: any;
}

interface ErrorResponse {
  status: 'error';
  message: string;
}

//   API 응답 타입 (성공 또는 에러)
type ApiResponse = SuccessResponse | ErrorResponse;

//   성공 응답인지 확인하는 사용자 정의 타입 가드
function isSuccessResponse(response: ApiResponse): response is SuccessResponse {
  return response.status === 'success';
}

//   API 응답 처리 함수
function handleResponse(response: ApiResponse) {
  // ✅ 사용자 정의 타입 가드로 응답 타입 좁히기
  if (isSuccessResponse(response)) {
    //   SuccessResponse 타입으로 좁혀짐
    console.log(`성공! 데이터:`, response.data);
  } else {
    // ❌ ErrorResponse 타입으로 좁혀짐
    console.error(`에러 발생: ${response.message}`);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;좀 더 쉽게 이해하기&lt;/h4&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  과일 분류하기&lt;/p&gt;
&lt;pre id=&quot;code_1741632022845&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 가능한 과일 코드 목록
type FruitCode = &quot;APPLE&quot; | &quot;BANANA&quot; | &quot;ORANGE&quot;;

// 과일 코드별 한글 이름
const FruitNameMap = {
  &quot;APPLE&quot;: &quot;사과&quot;,
  &quot;BANANA&quot;: &quot;바나나&quot;, 
  &quot;ORANGE&quot;: &quot;오렌지&quot;
};

// 허용된 과일 코드 목록
const allowedFruitCodes = [&quot;APPLE&quot;, &quot;BANANA&quot;, &quot;ORANGE&quot;];&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ is를 사용하지 않는 방식 (문제 발생)&lt;/p&gt;
&lt;pre id=&quot;code_1741632034538&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ❌ boolean 반환 - 타입스크립트는 내부 로직을 이해하지 못함
const isFruitCode = (code: string): boolean =&amp;gt; {
  return allowedFruitCodes.includes(code);
};

function getFruitNames(codes: string[]): string[] {
  const fruitNames: string[] = [];
  
  codes.forEach(code =&amp;gt; {
    if (isFruitCode(code)) {
      //   에러! 타입스크립트는 여전히 code를 string으로만 인식
      // &quot;string&quot; 타입은 &quot;APPLE&quot; | &quot;BANANA&quot; | &quot;ORANGE&quot; 인덱스로 사용할 수 없음
      fruitNames.push(FruitNameMap[code]); 
    }
  });
  
  return fruitNames;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ is를 사용하는 방식 (문제 해결)&lt;/p&gt;
&lt;pre id=&quot;code_1741632045833&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ✅ 타입 가드 함수: 문자열이 FruitCode인지 확인
const isFruitCode = (code: string): code is FruitCode =&amp;gt; {
  return allowedFruitCodes.includes(code);
};

function getFruitNames(codes: string[]): string[] {
  const fruitNames: string[] = [];
  
  codes.forEach(code =&amp;gt; {
    if (isFruitCode(code)) {
      // ✅ 이제 타입스크립트는 code가 FruitCode 타입임을 알게 됨
      // if 블록 안에서 code는 &quot;APPLE&quot; | &quot;BANANA&quot; | &quot;ORANGE&quot; 타입으로 좁혀짐
      fruitNames.push(FruitNameMap[code]); // 에러 없음!
    }
  });
  
  return fruitNames;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  쉽게 풀어서 설명하면&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;타입스크립트의 한계&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;타입스크립트는 includes() 같은 함수의 내부 로직을 보고 타입을 추론하지 못해요.&lt;/li&gt;
&lt;li&gt;그냥 true/false를 반환한다는 것만 알 뿐, 그게 타입에 어떤 의미인지 모릅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;is의 역할&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;code is FruitCode는 &quot;이 함수가 true를 반환하면 code는 FruitCode 타입이다&quot;라고 타입스크립트에게 직접 알려주는 것입니다.&lt;/li&gt;
&lt;li&gt;이것은 마치 &quot;신분증 검사관&quot;이 &quot;이 사람이 성인이 맞습니다&quot;라고 보증해주는 것과 같아요.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;결과적으로&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;is를 사용하면 if문 안에서 타입스크립트가 변수의 타입을 더 구체적으로 좁힐 수 있게 됩니다.&lt;/li&gt;
&lt;li&gt;이를 통해 FruitNameMap[code]와 같은 코드가 타입 오류 없이 안전하게 작동합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1741632068865&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (isDestinationCode(str)) {
  // ✅ is 덕분에 str은 여기서 DestinationCode 타입으로 좁혀짐
  destinationNames.push(DestinationNameSet[str]);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;is는 &quot;이 조건이 참이면, 이 변수는 이 타입이 확실하다&quot;고 타입스크립트에게 알려주는 특별한 표시라고 생각하시면 됩니다!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 식별할 수 있는 유니온&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;종종 태그된 유니온으로도 불리는 식별할 수 있는 유니온은 타입 좁히기에 널리 사용되는 방식입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;왜 필요할까?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 에러 타입을 정의했다고 할 때,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741631511606&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//   네트워크 에러 타입
interface NetworkError {
  errorCode: 500;  //   HTTP 상태 코드
  message: string;
  timeout: number;
}

//   인증 에러 타입
interface AuthError {
  errorCode: 401;  //   HTTP 상태 코드
  message: string;
  token: string;
}

//   권한 에러 타입
interface PermissionError {
  errorCode: 403;  //   HTTP 상태 코드
  message: string;
  resource: string;
}

//   에러 타입 유니온
type AppError = NetworkError | AuthError | PermissionError;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 에러 타입의 유니온 타입을 원소로 하는 배열을 정의하면 아래와 같을 것입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1741631521943&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//   에러 배열 선언 (유니온 타입으로 각 에러 타입을 모두 담을 수 있음)
const errorArr: AppError[] = [
  {
    errorCode: 500,
    message: &quot;서버 연결 오류&quot;,
    timeout: 3000
  },
  {
    errorCode: 401,
    message: &quot;인증 토큰이 만료되었습니다&quot;,
    token: &quot;expired_token_123&quot;
  },
  {
    errorCode: 403,
    message: &quot;접근 권한이 없습니다&quot;,
    resource: &quot;/admin/users&quot;
  }
];&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유니온 타입의 원소를 같은 배열을 정의함으로써 다양한 여러 객체를 관리할 수 있게 되었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 여기서 발생합니다. 여기서 각 에러 타입에 해당되는 필드를 여러개 섞어 가지는 객체에 대해서는 타입 에러를 뱉어야 할테지만, 아래 코드를 작성했을 때 자바스크립트는 덕 타이핑 언어이므로 별도의 타입 에러를 뱉지 않게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;덕 타이핑 ?&lt;/b&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;덕 타이핑(Duck Typing)은 프로그래밍에서 객체의 타입을 판단하는 방식 중 하나입니다. 이 개념은 &quot;만약 어떤 새가 오리처럼 걷고, 오리처럼 꽥꽥거리면, 그것은 오리일 것이다&quot;라는 표현에서 유래했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;덕 타이핑의 핵심 원리는 객체가 특정 클래스의 인스턴스인지 확인하는 대신, 객체가 필요한 메소드와 속성을 가지고 있는지만 확인한다는 것입니다. 즉, 객체의 실제 유형보다는 객체의 행동(메소드와 속성)에 초점을 맞춥니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741631530994&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//   덕 타이핑으로 인한 문제 - 타입스크립트는 구조적 타이핑을 따름
const mixedError = {
  errorCode: 500,
  message: &quot;혼합된 에러&quot;,
  timeout: 1000,      // NetworkError의 속성
  token: &quot;abc123&quot;,    // AuthError의 속성
  resource: &quot;/users&quot;  // PermissionError의 속성
};

// ⚠️ 모든 필드가 있기 때문에 에러 없이 배열에 추가됨
errorArr.push(mixedError);  //   이게 어떤 에러 타입인지 명확하지 않음&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되면 앞으로 개발 과정에서 의미를 알 수 없는 무수한 에러 객체가 생겨날 위험성이 커지게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 에러 타입을 구분할 방법이 필요하고, 각 타입이 비슷한 구조를 가지면서 서로 호환되지 않도록 만들어 주기 위해 타입들이 서로 포함관계를 가지지 않도록 정의해야 합니다. 이 때 사용하는 것이 식별할 수 있는 유니온 입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;사용해보자&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;식별할 수 있는 유니온이란 타입 간의 구조 호환을 막고자 타입마다 구분할 수 있는 판별자를 달아 주어 포함관계를 제거하는 것 입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;판별자의 개념으로 errorType이라는 필드를 새로 정의해보겠습니다. 각 에러 타입마다 이 필드에 대해 다른 값을 가지도록 하여 판별자를 달아주면 포함관계를 벗어나게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741631556531&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//   네트워크 에러 타입 (판별자 추가)
interface NetworkError {
  errorType: &quot;NetworkError&quot;;  //  ️ 판별자 필드 추가
  errorCode: 500;
  message: string;
  timeout: number;
}

//   인증 에러 타입 (판별자 추가)
interface AuthError {
  errorType: &quot;AuthError&quot;;  //  ️ 판별자 필드 추가
  errorCode: 401;
  message: string;
  token: string;
}

//   권한 에러 타입 (판별자 추가)
interface PermissionError {
  errorType: &quot;PermissionError&quot;;  //  ️ 판별자 필드 추가
  errorCode: 403;
  message: string;
  resource: string;
}

//   판별자가 있는 에러 타입 유니온
type AppError = NetworkError | AuthError | PermissionError;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741631573394&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//   판별자가 있는 에러 객체 배열
const errorArr: AppError[] = [
  {
    errorType: &quot;NetworkError&quot;,
    errorCode: 500,
    message: &quot;서버 연결 오류&quot;,
    timeout: 3000
  },
  {
    errorType: &quot;AuthError&quot;,
    errorCode: 401,
    message: &quot;인증 토큰이 만료되었습니다&quot;,
    token: &quot;expired_token_123&quot;
  },
  {
    errorType: &quot;PermissionError&quot;,
    errorCode: 403,
    message: &quot;접근 권한이 없습니다&quot;,
    resource: &quot;/admin/users&quot;
  }
];

// ❌ 이제 혼합된 에러는 컴파일 에러 발생
const mixedError = {
  errorType: &quot;NetworkError&quot;,  //  ️ NetworkError로 명시했지만
  errorCode: 500,
  message: &quot;혼합된 에러&quot;,
  timeout: 1000,
  token: &quot;abc123&quot;,    // ⚠️ AuthError의 속성인 token이 있어서 타입 에러
  resource: &quot;/users&quot;  // ⚠️ PermissionError의 속성인 resource가 있어서 타입 에러
};

//   컴파일 에러: 'NetworkError' 타입에 'token'과 'resource' 속성이 없음
// errorArr.push(mixedError);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Q. 질문 : 판별자로 errorType 말고 errorCode로 지정은 안되는걸까?&lt;/h4&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A. 판별자로 errorCode도 사용할 수 있지만 아래 사항들을 고려해야 합니다.&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;유닛 타입 조건&lt;/b&gt;: errorCode 값이 각 타입마다 고유하고 유닛 타입(리터럴 타입)이어야 제대로 작동합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;의미적 명확성&lt;/b&gt;: errorType은 직관적으로 &quot;이 에러의 종류&quot;를 나타내므로 코드 가독성이 높아집니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;확장성&lt;/b&gt;: 향후 같은 errorCode를 가진 다른 종류의 에러가 필요할 수 있어, errorType과 같은 별도 판별자를 두는 것이 더 유연합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;식별할 수 있는 유니온의 판별자 선정 시 주의할 점&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;식별할 수 있는 유니온의 판별자는 유닛 타입으로 선언되어야 정상적으로 동작합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유닛 타입이란 다른 타입으로 더이상 쪼개지지 않고 오직 하나의 정확한 값을 가지는 타입을 의미합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예) null, undefined, 리터럴 타입, true, 1 등(여러 타입을 할당받을 수 있는 void, string, number는 유닛 타입이 아님)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 깃 허브의 이슈 탭을 보면, 식별할 수 있는 유니온 판별자로 사용할 수 있는 타입이 아래와 같이 정의되어 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1. 리터럴 타입이어야 한다.&lt;br /&gt;2. 판별자로 선정한 값에 적어도 하나 이상의 유닛 타입이 포함되어야 하며, 인스턴스화 할 수 있는 타입은 포함되지 않아야 한다.&amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741631652262&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//   유니온 타입 정의
type FormValue = 
  | { value: 'a'; answer: true } 
  | { value: string; answer: false } 
  | { value: number; answer: true };

//   함수로 타입 좁히기 테스트
function processForm(form: FormValue) {
  //   'value' 속성으로 타입 좁히기 시도
  if (form.value === 'a') {
    // ✅ 첫 번째 타입으로 좁혀짐
    console.log('첫 번째 타입', form.answer); // form.answer는 true
  }
  
  //   'answer' 속성으로 타입 좁히기 시도
  if (form.answer === false) {
    // ✅ 두 번째 타입으로 좁혀짐
    console.log('두 번째 타입', form.value); // form.value는 string
  } else {
    // ✅ 첫 번째 또는 세 번째 타입으로 좁혀짐 (answer가 true인 것들)
    console.log('첫 번째나 세 번째 타입', form.value); // form.value는 'a' | number
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드에서 판별자가 value일 때, 판별자로 선정한 값 중 'a'만 유일하게 유닛 타입이므로, 해당 타입만 좁혀지는 것을 확인 할 수 있습니다. 반면 판별자가 answer일 경우에는 판별자가 모두 유닛 타입이므로 타입이 정상적으로 좁혀집니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 정확한 타입 분기 유지하기 - Exhaustiveness Checking&amp;nbsp;&lt;/h2&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Exhaustiveness Checking은 모든 케이스에 대해 철저하게 타입 검사하는 것을 의미하고, 타입 좁히기에 사용되는 패러다임 중 하나 입니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;모든 케이스에 대해 분기 처리를 해야만 유지보수 측면에서 안전하다고 생각되는 상황이 생길 경우, 이를 이용해 모든 케이스에 대한 타입 검사를 강제할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예시 코드로 자세히 살펴보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741631662043&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//   도형 종류 유니온 타입
type Shape = 
  | { kind: 'circle'; radius: number }
  | { kind: 'rectangle'; width: number; height: number }
  | { kind: 'triangle'; base: number; height: number };

//   도형의 넓이를 계산하는 함수
function calculateArea(shape: Shape): number {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2;
    case 'rectangle':
      return shape.width * shape.height;
    case 'triangle':
      return (shape.base * shape.height) / 2;
    default:
      // ✅ Exhaustiveness Checking
      // 모든 케이스를 처리했다면 이 코드는 실행되지 않음
      return exhaustiveCheck(shape);
  }
}

//   Exhaustiveness Checking 함수
function exhaustiveCheck(value: never): never {
  throw new Error(`예상치 못한 타입: ${JSON.stringify(value)}`);
}

//   만약 새로운 도형 타입이 추가된다면?
// 예: type Shape = ... | { kind: 'square'; side: number }
// 이 경우 calculateArea 함수를 수정하지 않으면 컴파일 에러 발생!
// never 타입에 square 타입이 할당될 수 없기 때문입니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 모든 케이스에 대한 타입 분기 처리를 해주지 않았을 때 컴파일타입 에러가 발생하게 하는 것을 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Exhaustiveness Checking이라고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;exhaustiveCheck 함수는 매개변수로 never 타입을 선언하고 있으므로, 어떤 값도 매개변수로 받을 수 없고 만약 값이 들어온다면 에러를 내뱉습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 함수를 타입 처리 조건문의 마지막 else 문제 사용하면 앞의 조건 문에서 모든 타입에 대한 분기 처리를 강제할 수 있습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/TypeScript</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/41</guid>
      <comments>https://dreamyard.tistory.com/entry/Typescript-%ED%83%80%EC%9E%85-%ED%99%95%EC%9E%A5%EC%A2%81%ED%9E%88%EA%B8%B0#entry41comment</comments>
      <pubDate>Tue, 11 Mar 2025 18:46:42 +0900</pubDate>
    </item>
    <item>
      <title>[웹 최적화] 브라우저 렌더링과 렌더링 최적화</title>
      <link>https://dreamyard.tistory.com/entry/%EC%9B%B9-%EC%B5%9C%EC%A0%81%ED%99%94-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EB%A0%8C%EB%8D%94%EB%A7%81%EA%B3%BC-%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%B5%9C%EC%A0%81%ED%99%94</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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;0. 브라우저 렌더링&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저 렌더링, 다양한 웹 리소스를 최적화 하는 방법을 이해하여 잘 읽힐 뿐 아니라 빠른 웹을 구현해야 합니다.&lt;br /&gt;마크업 작성 시, 불필요한 태그는 제거하고 필요한 태그만 사용해 간단 명료하게 구조화하는 것이 중요합니다.&lt;br /&gt;구조가 복잡하고 태그의 깊이가 깊어질수록 DOM 트리를 생성하는 비용이 많이 듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 브라우저의 구조&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조 그림과 함께 각 구조에 대해 설명&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 사용자 인터페이스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 페이지 표시 영역을 제외한 주소표시줄, 이전/다음 버튼, 새로고침 버튼 등 나머지 부분에 해당됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 브라우저 엔진&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 인터페이스와 렌더링 엔진을 연결하며, 사용자가 주소창에 웹 주소 입력 시 네트워킹을 통해 해당 웹사이트 리소스를 요청하고 렌더링 엔진을 통해 요청한 페이지가 나타나도록 제어합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) 렌더링 엔진&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 페이지를 표시하며, HTML, CSS를 파싱하여 화면에 렌더링하는 역할을 합니다.&lt;br /&gt;브라우저마자 렌더링 엔진이 다릅니다. (Blink : 크롬, Edge / Webkit : 사파리 / Gecko : 파이어폭스)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4) 네트워킹&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저 주소창에 주소 입력 하는 경우, 웹 페이지에서 링크가 있는 요소(메뉴,버튼 등)를 선택한 경우, 이전/다음/새로고침 버튼을 선택한 경우, 스크립트에서 서버 리소스를 요청한 경우와 같은 &lt;u&gt;사용자 액션이 발생한 경우 웹페이지 요청이 발생&lt;/u&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹페이지 요청 발생 시,&lt;u&gt; 네트워킹이 요청된 웹사이트 도메인 주소를 IP 주소로 변환해주는 DNS(도메인 네임 시스템)&lt;/u&gt;을 통해 실제 웹페이지를 제공하는 &lt;u&gt;웹 서버의 IP주소를 찾고 웹 서버에 요청된 페이지를 보내달라는 신호&lt;/u&gt;를 보냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 요청 받은&lt;u&gt; 웹서버가 페이지에 대한 리소스(HTML, CSS, JS, image 등)을 사용자 브라우저로 전송&lt;/u&gt;하고 네트워킹은 클라이언트와 서버 간 데이터 통신을 담당합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전송받은 웹페이지 리소스를 &lt;u&gt;렌더링 엔진, 자바스크립트 인터프리터로 파싱하고 최종적으로 웹페이지&lt;/u&gt;를 표시합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5) 자바스크립트 인터프리터&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터프리너란 프로그래밍 언어를 한 줄씩 읽어서 실행하는 프로그램으로, 자바스크립트 인터프리터의 경우 런터임 환경에서 기계어를 한 줄씩 번영해 실행합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 브라우저는 자바스크립트 엔진을 내장하고 있고, 크롬의 경우 구글의 V8 엔진을 사용합니다. Node.js의 경우 V8엔진을 사용하며 브라우저가 아닌 곳에서도 자바스크립트를 사용할 수 있도록 만들어졌습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;작동 방식&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;렌더링 엔진에서 웹 서버로부터 받은 HTML 파일을 읽다 &amp;lt;script&amp;gt;를 만나면 HTML 파싱 작업을 일시 중단하고 자바스크립트 인터프리터가 해당 script를 해석, 실행 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6) UI 백엔드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콤보 박스, 체크 박스, 텍스트 박스 등의 UI 위젯을 그리며, 브라우저별로 내장된 스타일의 UI 위젯을 사용합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7) 데이터 스토리지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저 자체에 데이터를 저장할 수 있는 곳으로, 브라우저 개발자 도구의 애플리케이션 탭을 통해 쿠키, 로컬 스토리지, 세션 스토리지, IndexedDB, Web SQL, 캐시 스토리지 등의 데이터 스토리지를 확인할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 렌더링 과정&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(1) 웹 접속 요청&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 웹 주소 입력 및 웹 사이트 접속을 요청합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(2) 웹 서버의 HTMP 파일 전송&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 서버는 사용자, 즉 클라이언트가 요청한 웹사이트의 실제 페이지인 HTML 파일을 전송합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(3) DOM 트리 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저 렌더링 엔진이 HTML 파일을 파싱하며 DOM 트리를 생성 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(4) CSSOM 트리 생성&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML 파싱 중 CSS 인식 시 렌더링 잠시 중단 (HTML 파싱을 계속 진행) 하고 CSS 파싱 후 CSSOM 트리를 생성합니다. 이 때문에 CSS 를 렌더링 차단 리소스 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;(render block resource) &lt;/span&gt;라고도 합니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;DOM 트리와 CSSOM 트리의 변환 과정&amp;nbsp;&lt;/h4&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bytes -&amp;gt; Characters -&amp;gt; Tokens -&amp;gt; Nodes -&amp;gt; CSSOM&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(5) 자바스크립트 컴파일레이션&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계속해서 HTML 파싱하다 script 파일 인식 시, HTML 파싱을 잠시 중단 후 &lt;u&gt;자바스크립트 인터프리터가 자바스크립트를 해석하고 실행 (컴파일레이션) 합니다. &lt;/u&gt;자바스크립트를 파서 차단 리소스 (parser blocking resource) 라고도 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ 자바스크립트 파일은 렌더링 엔진이 아닌, 자바스크립트 인터프리터에 의해 해석되고 실행됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;컴파일의 3단계&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 코드를 의미 있는 조각으로 나누는 렉싱/토크나이징 (이 때 스코프가 결정 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 코드를 트리 구조로 나타내는 &lt;u&gt;추상 구문 트리(abstract syntax tree, AST)로 만드는 파싱&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) AST 트리를 바탕으로 바이트 코드로 변환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;AST란?&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래밍 언어 문법에 따라 소스 코드 구조를 표시하는 계층적인 프로그램 표현으로, AST Explorer 에서 AST가 어떤 모양으로 생성되는지 확인 가능 합니다.(JSON 형태로도 확인 가능합니다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;a title=&quot;AST Explorer 바로가기&quot; href=&quot;https://astexplorer.net/&quot;&gt; AST Explorer 바로가기  &lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;AST가 만들어 지는 과정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트 코드는 컴파일러의 lexcial analysis (어휘 분석)과 systax analysis (구문 분석)을 통해 AST로 변환 됩니다. 어휘분석기가 정의된 규칙에 따라 공백, 주석을 제거하며 코드를 읽고 토큰 목록으로 분할하고, 분할된 토큰 목록은 구문 분석기(parser)가 코드 구문을 검증하고 트리 구조로 변환하게 됩니다. (구문 오류 시 에러 표시)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(6) 렌더 트리 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 HTML을 파싱해 DOM트리와 CSSOM 트리를 합쳐 렌더트리를 생성합니다. DOM 트리에는 display:none; 된 HTML 요소가 존재하나, 렌더 트리에는 존재하지 않게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(7) 레이아웃 단계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레이아웃 단계에서 렌더 트리 화면 배치를 위한 절대적인 픽셀 값이 계산 됩니다. CSS에서 크기나 위치를 %로 정의했어도 모두 절대적인 픽셀로 계산되며, 레이아웃 성능은 DOM에 영향을 받으므로, 불필요한 HTML 요소를 제거하는 것이 중요합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(8) 페인팅 단계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페인팅 단계에서 화면에 렌더트리의 각 노드를 화면에 실제 픽셀로 변환하게 됩니다. 픽셀 변환 결과를 바탕으로 여러개의 레이어 ( 렌더링 시 페인트할 대상 영역을 나눈 것)가 생성 되며, 레이어는 개발자 도구 Layers 탭에서 확인 가능합니다. 페인팅 단계에서는 페인팅 레코드(렌더링 순서를 기록한 정보)가 생성됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(9) 컴포지션 단계&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페인트 단계에서 생성된 레이어를 페인트 레코드 순서에 맞게 브라우저에 픽셀로 그리고, 나누었던 레이어들을 합성해 최종화면을 사용자에게 보여줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(10) 리플로우 &amp;amp; 리페인트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 웹페이지에 처음 접속하면 렌더링 과정을 거쳐 화면에 모든 요소가 그려지는데, 이후 사용자의 액션에 따라 발생하는 이벤트로 새로운 기존 요소에 변경이 발생될 때 리플로우나 리페인트가 이루어지게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;리플로우의 경우 이미 생성된 DOM 요소에 대한 수치가 변경되는 경우 영향을 받는 노드들(자신, 부모, 자식 등)의 너비, 높이, 위치 등과 같은 레이아웃 수치를 재계산해 렌더 트리 생성과 레이아웃 과정을 다시 수행하는 것&lt;/u&gt;을 의미합니다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;리플로우가 일어나는 대표 속성&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;width, height, margin, padding, border, display, position, top, left, right, bottom, float, overflow, font-size, font-family, font-weight, line-height, text-align, vertical-align, white-space, min-height, min-width, max-height, max-width, flex, grid, transform&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;리페인트는 이러한 리플로우의 결과를 화면에 그리기 위해서 일어나는 과정&lt;/u&gt;을 의미합니다. 리플로우 발생 시 반드시 리페인트가 발생됩니다. 이 과정은 개발자도구 Performance 탭 Event Log에서 확인할 수 있습니다. 레이아웃에 영향을 미치지 않는 단순한 색상 변경에 대해선 리페인트만 수행됩니다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;리페인트가 일어나는 대표 속성&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;color, background, background-color, background-image, border-color, border-style, visibility, outline, opacity, box-shadow, text-shadow, filter&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 렌더링 최적화&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CRP를 최적화하면 최초 렌더링 시간이 단축되고, 초당 60프레임으로 리플로우, 리페인트되도록 보장해 버벅거림을 방지할 수 있습니다. 웹 성능에는 서버 요청 및 응답, 로딩, 스크립팅, 렌더링, 레이아웃, 페인팅과 관련됩니다. 이와 관련해 렌더링 최적화 방법을 w정리해보았습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ CRP(Critical Rendering Path) : 브라우저가 HTML,CSS, JS를 스크린의 픽셀로 변환하는 순서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) HTML 마크업 최적화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;불필요한 Wrapper 요소를 제거하고 &lt;/span&gt;HTML 태그의 중첩을 최소화하여 단순하게 구성해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 사용되지 않는 CSS 스타일 선언 제거&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크롬 개발자 도구에서 CSS Overview 탭에서 Capture Overview 버튼을 클릭하면 현재 웹페이지에 대한 HTML 요소, 색상, 폰트. 미디어쿼리 등의 정보를 확인할 수 있습니다.&amp;nbsp;이중 Unused declarations 메뉴를 통해 정의는 했으나 사용되지 않은 CSS 선언을 확인할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) CSS 파싱 시점 분리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSS는 렌더링 차단 리소스로, CSS를 파싱해 CSSOM을 만드는 동안에 렌더링이 일어나지 않게 합니다. CSS 삽입 시 사용하는 link태그에는 media 라는 속성이 있는데, CSS파일을 어떤 조건에서 적용할지 결정합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;속성값이 print일 경우 사용자가 웹 페이지 인쇄를 위한 프린트 버튼을 클릭했을 때만 적용되는 것임을 의미합니다. 이는 곧 최초 렌더링 시 페이지 렌더링을 차단하지 않는 CSS가 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 외에도 CSS 삽입 시, 프린트, 스마트폰 크기, 태블릿 크기, 가로 모드, 세로 모드 등 media 속성을 적절하게 사용해 초기 렌더링 속도를 높일 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741623416246&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;!-- 모든 환경에서 사용되는 기본 스타일 --&amp;gt;
&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;base.css&quot;&amp;gt;

&amp;lt;!-- 인쇄 시에만 적용되는 스타일 --&amp;gt;
&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;print.css&quot; media=&quot;print&quot;&amp;gt;

&amp;lt;!-- 스마트폰 크기에서 적용되는 스타일 --&amp;gt;
&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;mobile.css&quot; media=&quot;screen and (max-width: 767px)&quot;&amp;gt;

&amp;lt;!-- 태블릿 크기에서 적용되는 스타일 --&amp;gt;
&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;tablet.css&quot; media=&quot;screen and (min-width: 768px) and (max-width: 1023px)&quot;&amp;gt;

&amp;lt;!-- 가로 모드에서만 적용되는 스타일 --&amp;gt;
&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;landscape.css&quot; media=&quot;screen and (orientation: landscape)&quot;&amp;gt;

&amp;lt;!-- 세로 모드에서만 적용되는 스타일 --&amp;gt;
&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;portrait.css&quot; media=&quot;screen and (orientation: portrait)&quot;&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;크롬 개발자 도구 Performance insights 탭에서 현재 페이지의 렌더링 차단 요청이 몇 번 일어나고, 어떤 리소스를 요청할 때 일어나는지, DOM이 로드되는데 걸리는 시간을 확인할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4) 파서 차단 자바스크립트 삭제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트는 파서 차단 리소스로, 브라우저가 HTML 마크업을 파싱해 DOM을 빌드해야하는데, 이를 처리하는 동안 파서에서는 자바스크립트를 발견할 때마다 파싱을 중지하고 자바스크립트를 실행합니다. 그 후 HTML을 계속 파싱합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML 페이지에 삽입된 외부 JS는 파서에 리소스가 다운로드 될 때까지 기다려야 하고, 외부 리소스 수만큼 네트워크 요청에 대한 왕복 시간이 소요되기 때문에 첫 페이지 렌더링 시간이 지연될 수 있습니다. 따라서, &lt;u&gt;첫 페이지 렌더링 시 필요하지 않은 자바스크립트는 비동기로 가져오거나, 첫 렌더링 완료 시까지 대기시켰다가 가져오도록 처리하는 것이 좋습니다.&amp;nbsp;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 렌더링 시 스크립트 크기가 작고 여러 페이지에 반복적으로 사용되지 않을 땐, body 태그 앞쪽에 script 태그를 추가해도 되지만, 자바스크립트로 인해 파서가 차단되지 않도록 하려면 삽입되는 외부 스크립트 파일에 아래와 같이 async 속성을 사용해 비동기처리되도록 하는 것이 좋습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741615631507&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script async src=&quot;script.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 비동기 스크립트는 코드 나열 순으로 실행된다는 보장이 없으므로, 실행 순서가 중요한 스크립트의 경우엔 사용해선 안됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5) HTTP 요청 최소화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 페이지 자원 중 CSS, JS, image 파일은 삽입 되는 수 만큼 네트워크 요청이 일어나 응답 시간에 영향을 미칩니다. 이러한 HTTP 요청을 최소화 하는 것만으로도 성능 개선이 가능합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;방법 1) 내부 스타일 시트 사용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;style 규칙이 적을 경우, &amp;lt;link&amp;gt; 태그로 외부 스타일 시트를 사용하는 대신 &amp;lt;style&amp;gt; 태그 사용하기&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;방법 2 ) 외부 CSS와 JS 파일 결합하기&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹팩 (webpack)을 사용하면 각각의 CSS와 JS 파일을 압축하고 여러 파일을 결합해서 하나의 파일로 생성할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;웹팩 기본 설정 예시&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741623572202&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// webpack.config.js 예시
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
  optimization: {
    minimize: true,
    minimizer: [new TerserPlugin()],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'styles.css',
    }),
  ],
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
        },
      },
    ],
  },
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;방법 3) 이미지 스프라이트 사용&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지 스프라이트는 웹페이지에 사용하는 다수의 이미지를 한 장의 이미지로 만들고, 스타일 시트에서 background-position 속성을 설정해 필요한 부분의 이미지만 보여주는 기법 입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741623459057&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* 스프라이트 이미지 예시 */
.icon {
  background-image: url('sprites.png');
  width: 24px;
  height: 24px;
  display: inline-block;
}

.icon-home {
  background-position: 0 0;
}

.icon-search {
  background-position: -24px 0;
}

.icon-settings {
  background-position: -48px 0;
}

.icon-user {
  background-position: -72px 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1741623469698&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!-- HTML에서 사용 예시 --&amp;gt;
&amp;lt;a href=&quot;#&quot;&amp;gt;&amp;lt;span class=&quot;icon icon-home&quot;&amp;gt;&amp;lt;/span&amp;gt; 홈&amp;lt;/a&amp;gt;
&amp;lt;a href=&quot;#&quot;&amp;gt;&amp;lt;span class=&quot;icon icon-search&quot;&amp;gt;&amp;lt;/span&amp;gt; 검색&amp;lt;/a&amp;gt;
&amp;lt;a href=&quot;#&quot;&amp;gt;&amp;lt;span class=&quot;icon icon-settings&quot;&amp;gt;&amp;lt;/span&amp;gt; 설정&amp;lt;/a&amp;gt;
&amp;lt;a href=&quot;#&quot;&amp;gt;&amp;lt;span class=&quot;icon icon-user&quot;&amp;gt;&amp;lt;/span&amp;gt; 사용자&amp;lt;/a&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;방법 4) gzip 압축을 이용해 파일 크기 최소화&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 서버에서 파일을 압축하는 대표적인 인코딩 방식으로는 gzip이 있으며, gzip으로 압축 전송 시 파일 크기를 평균 70% 줄일 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;gzip 압축 서버 설정 예시 (Apache)&lt;/h4&gt;
&lt;pre id=&quot;code_1741623723559&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Apache .htaccess 파일 예시
&amp;lt;IfModule mod_deflate.c&amp;gt;
  # 압축할 파일 유형 지정
  AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript application/javascript application/json application/xml
  
  # 이미 압축된 콘텐츠는 다시 압축하지 않음
  SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png|zip|gz|bz2|rar)$ no-gzip dont-vary
  
  # 프록시 서버가 압축된 콘텐츠를 캐시하도록 설정
  Header append Vary User-Agent env=!dont-vary
&amp;lt;/IfModule&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Nginx gzip 설정 예시&lt;/h4&gt;
&lt;pre id=&quot;code_1741623738807&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Nginx 설정 파일 예시
server {
    listen 80;
    server_name example.com;
    
    # gzip 활성화
    gzip on;
    gzip_disable &quot;msie6&quot;;
    
    # 압축 레벨 설정 (1-9, 높을수록 더 많이 압축되지만 CPU 사용량 증가)
    gzip_comp_level 6;
    
    # 압축할 최소 파일 크기 (1k 이상인 파일만 압축)
    gzip_min_length 1000;
    
    # 압축할 파일 유형
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;방법 5) CRP 성능 측정&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자 도구의 성능 탭을 통해 CPR 성능을 측정해봅니다. 아래 관련 용어를 정리해보았으니 참고해서 보시기 바랍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Send Request : index.html 요청&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Parse HTML &amp;amp; Send Request : HTML을 파싱해서 DOM 트리 생성. style.css와 main.js 요청&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Parse Stylesheet : style.css 에 대한 CSSOM 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. Evaluate Script : main.js 해석&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. Layout : 뷰포트 기준으로 레이아웃 계산&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. Paint : 픽셀로 페인팅&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;더 알고 가기&amp;nbsp;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;가상 DOM과 리액트, 뷰&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DOM 변경이 일어날 때마다 브라우저는 렌더링 과정을 거치므로, 복잡한 화면에서 DOM 변경 발생 시 화면을 다시 그리는 과정에서 시간이 소요되거나 버벅거림이 발생할 수 있습니다. 이를 보완하기 위해 나온 것이 가상 DOM (virtual DOM) 입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저 렌더링 과정에서 가장 많은 비용이 드는 단계는 레이아웃 단계와 페인팅 단계이며, 최초 렌더링 이후 자바스크립트로 DOM을 조작하면 리플로우와 리페인트가 수행됩니다. 변경 사항이 많을 수록 많은 리플로우와 리페인트가 수행되고 화면이 느려지게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상 DOM은 메모리에 실제 DOM과 동일한 DOM 구조를 만들어서 사용하는 방식으로 가상 DOM에서는 실제 렌더링이 일어나지 않아 DOM 조작이 필요한 경우 일단 가상 DOM에 반영하고 변경 사항을 묶어 실제 DOM에 반영함으로써 DOM 조작 연산 비용을 훨씬 적게 줄일 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;가상 DOM 작동 방식&lt;/h4&gt;
&lt;pre id=&quot;code_1741623639559&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;실제 DOM 조작 과정:
1. 변경 필요 &amp;rarr; 2. DOM 직접 수정 &amp;rarr; 3. 리플로우 발생 &amp;rarr; 4. 리페인트 발생

가상 DOM 조작 과정:
1. 변경 필요 &amp;rarr; 2. 가상 DOM에 변경 적용 &amp;rarr; 3. 실제 DOM과 비교(Diffing) &amp;rarr; 
4. 변경된 부분만 실제 DOM에 한 번에 적용 &amp;rarr; 5. 최소한의 리플로우와 리페인트 발생&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;리액트와 뷰 가상 DOM 간단 비교&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 가상 DOM을 사용하는 대표적인 프론트엔드 개발 도구로 리액트와 뷰가 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741623662907&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;리액트(React)의 가상 DOM:
- JSX를 사용하여 UI 컴포넌트 정의
- setState나 hooks를 통한 상태 변경 시 가상 DOM 업데이트
- Reconciliation 알고리즘으로 실제 DOM과 비교하여 차이점만 업데이트
- 단방향 데이터 흐름 (부모에서 자식으로만 데이터 전달)

뷰(Vue)의 가상 DOM:
- Template 또는 JSX로 UI 컴포넌트 정의 가능
- 반응형 시스템으로 데이터 변경 감지 및 가상 DOM 업데이트
- 양방향 바인딩 지원 (v-model)
- 더 작은 크기와 더 빠른 초기 렌더링 성능&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/웹 최적화</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/40</guid>
      <comments>https://dreamyard.tistory.com/entry/%EC%9B%B9-%EC%B5%9C%EC%A0%81%ED%99%94-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EB%A0%8C%EB%8D%94%EB%A7%81%EA%B3%BC-%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%B5%9C%EC%A0%81%ED%99%94#entry40comment</comments>
      <pubDate>Tue, 11 Mar 2025 09:00:50 +0900</pubDate>
    </item>
    <item>
      <title>[웹 최적화] 웹 리소스와 이미지, 동영상, 검색 최적화</title>
      <link>https://dreamyard.tistory.com/entry/%EC%9B%B9-%EC%B5%9C%EC%A0%81%ED%99%94-%EC%9B%B9-%EB%A6%AC%EC%86%8C%EC%8A%A4%EC%99%80-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%8F%99%EC%98%81%EC%83%81-%EA%B2%80%EC%83%89-%EC%B5%9C%EC%A0%81%ED%99%94</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 &lt;u&gt;웹 리소스(CSS,JS, 외부 라이브러리 등)와 이미지, 동영상, 오디오, 검색에 이르기 까지 전반적인 웹 최적화 방법&lt;/u&gt;에 대해 정리해보았습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;웹 리소스 최적화&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 개발 시 사용되는 CSS, JS를 비롯한 외부 오픈소스 라이브러리들의 로드 시점을 잘 분산하면 웹페이지 로드 시간을 최적화 할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. preload&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;preload는 현재 페이지에서 필요한 리소스를 미리 가져오게 할 수 있는 속성값 입니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741621990891&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;link rel=&quot;preload&quot; href=&quot;styles.css&quot; as=&quot;style&quot;&amp;gt;
&amp;lt;link rel=&quot;preload&quot; href=&quot;main.js&quot; as=&quot;script&quot;&amp;gt;
&amp;lt;link rel=&quot;preload&quot; href=&quot;font.woff2&quot; as=&quot;font&quot; crossorigin&amp;gt;
&amp;lt;link rel=&quot;preload&quot; href=&quot;hero-image.jpg&quot; as=&quot;image&quot;&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;반드시 as 속성으로 브라우저에 리소스 유형을 알려줘야 하며, href 속성에 정의되어 있는 리소스 유형 또한 맞게 설정되어야 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;preload는 중복 선언 시, 선언된 수만큼 리소스를 가져오기 때문에 중복에 유의해야 합니다. preload를 통해 가져와진 리소스가 현 페이지에서 3초 이내에 사용되지 않는다면 console 창에 경고 메시지가 출력되므로, 페이지 로드 시 바로 사용되지 않는 리소스는 preload로 가져올 필요가 없습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 페이지에 바로 적용되어야 하는 폰트, CSS, 페이지 렌더링에 관여하는 JS가 이에 해당됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. preconnect&amp;nbsp;&lt;/h3&gt;
&lt;pre id=&quot;code_1741622001608&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;link rel=&quot;preconnect&quot; href=&quot;https://fonts.googleapis.com&quot;&amp;gt;
&amp;lt;link rel=&quot;preconnect&quot; href=&quot;https://cdn.example.com&quot; crossorigin&amp;gt;
&amp;lt;link rel=&quot;preconnect&quot; href=&quot;https://api.example.com&quot;&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;preconnect는 외부 도메인의 리소스를 참고하는 것이 있다면 브라우저에게 알려 미리 외부 도메인과 연결할 수 있게 합니다. 이를 통해 DNS, TCP, TLS 등과 같이 연결에 필요한 시간을 절약할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. prefetch&lt;/h3&gt;
&lt;pre id=&quot;code_1741622008464&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;link rel=&quot;prefetch&quot; href=&quot;next-page.html&quot;&amp;gt;
&amp;lt;link rel=&quot;prefetch&quot; href=&quot;product-details.js&quot;&amp;gt;
&amp;lt;link rel=&quot;prefetch&quot; href=&quot;secondary-styles.css&quot;&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미래에 사용될 것으로 예상되는 리소스를 prefetch 로 미리 가져와 브라우저 캐시에 저장할 수 있습니다. prefetch를 통해 가져온 리소스는 당장 사용되는 것이 아니므로, 브라우저 렌더링에 영향을 미치지 않습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;prefetch로 너무 많은 리소스를 가져오면 네트워크 통신이 많이 일어나 페이지 렌더링이 오래 걸릴 수 있으므로, 적절하게 사용해야 하며, 쇼핑몰에서 원하는 제품을 클릭해서 제품 상세페이지로 이동하는 예상되는 사용자 행동에 대해 관련되는 리소스를 미리 가져올 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. loading = lazy&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지 내 삽입된 img 또는 iframe 태그의 loading 속성으로 필요한 시점에 콘텐츠가 로드되게 할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 특정 페이지가 세로로 여러번 스크롤하며 봐야할 정도로 콘텐츠 양이 많다면, img 태그의 loading 속성을 사용해 이미지를 사용자가 스크롤을 내려 해당 이미지 페이지가 노출되는 순간 로드함으로써 웹 속도를 개선할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741622017283&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!-- 이미지 지연 로딩 --&amp;gt;
&amp;lt;img src=&quot;image.jpg&quot; alt=&quot;설명&quot; loading=&quot;lazy&quot;&amp;gt;

&amp;lt;!-- iframe 지연 로딩 --&amp;gt;
&amp;lt;iframe src=&quot;embedded-content.html&quot; loading=&quot;lazy&quot;&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;이미지 최적화&amp;nbsp;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 권장되는 크기와 비율로 사용하기&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 사이트 내에서 이미지는 크게 배경 이미지, 히어로 이미지, 배너 이미지, 블로그 이미지, 로고, 파비콘 으로 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 이미지 유형별로 권장되는 크기와 비율은 아래와 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;이미지 유형별 권장되는 크기와 비율, 형식&lt;/h4&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;1920x1080px&lt;/td&gt;
&lt;td&gt;16:9&lt;/td&gt;
&lt;td&gt;JPEG, WebP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;히어로 이미지&lt;/td&gt;
&lt;td&gt;1600x900px&lt;/td&gt;
&lt;td&gt;16:9&lt;/td&gt;
&lt;td&gt;JPEG, WebP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;배너 이미지&lt;/td&gt;
&lt;td&gt;1200x300px&lt;/td&gt;
&lt;td&gt;4:1&lt;/td&gt;
&lt;td&gt;JPEG, PNG&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;블로그 이미지&lt;/td&gt;
&lt;td&gt;800x450px&lt;/td&gt;
&lt;td&gt;16:9&lt;/td&gt;
&lt;td&gt;JPEG, WebP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;로고&lt;/td&gt;
&lt;td&gt;200x200px 또는 250x100px&lt;/td&gt;
&lt;td&gt;1:1 또는 5:2&lt;/td&gt;
&lt;td&gt;PNG (투명)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;파비콘&lt;/td&gt;
&lt;td&gt;32x32px, 16x16px&lt;/td&gt;
&lt;td&gt;1:1&lt;/td&gt;
&lt;td&gt;ICO, PNG&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;로고의 경우 1:1 비율 또는 5:2 비율을 사용하며 투명도를 지원하는 PNG 형식 사용이 권장되며 최대 높이 100px 를 넘지 않는 것이 좋습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) img 태그의 srcset, sizes 속성 사용&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;img 태그에는 이미지 경로 지정을 위한 src 속성 외에도 여러 이미지를 한 번에 설정할 수 있는 srcset 속성이 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;srcset 속성은 sizes 속성을 함께 사용해 브라우저 너비에 따라 적절한 크기로 이미지가 보여지도록 설정해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1741622085737&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;img 
  src=&quot;image-800w.jpg&quot; 
  srcset=&quot;image-480w.jpg 480w, 
          image-800w.jpg 800w, 
          image-1200w.jpg 1200w&quot;
  sizes=&quot;(max-width: 600px) 480px,
         (max-width: 900px) 800px,
         1200px&quot;
  alt=&quot;반응형 이미지 예시&quot;&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) picture 태그&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;picture&amp;gt;는 미디어 쿼리나 자바스크립트 없이 반응형 웹 이미지를 표현할 수 있게 해줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;picture 태그는 img 태그 src 리소스에 대해 다중 이미지 리소스를 정의할 수 있도록 해주는 img 태그에 대한 컨테이너 태그 입니다. 따라서, 하나의 img 태그와 0개 이상의 source 태그를 포함합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;media 속성&lt;/h4&gt;
&lt;pre id=&quot;code_1741622106556&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;picture&amp;gt;
  &amp;lt;source media=&quot;(min-width: 1200px)&quot; srcset=&quot;large.jpg&quot;&amp;gt;
  &amp;lt;source media=&quot;(min-width: 768px)&quot; srcset=&quot;medium.jpg&quot;&amp;gt;
  &amp;lt;source media=&quot;(min-width: 480px)&quot; srcset=&quot;small.jpg&quot;&amp;gt;
  &amp;lt;img src=&quot;fallback.jpg&quot; alt=&quot;반응형 이미지&quot;&amp;gt;
&amp;lt;/picture&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;type 속성&lt;/h4&gt;
&lt;pre id=&quot;code_1741622116883&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;picture&amp;gt;
  &amp;lt;source type=&quot;image/webp&quot; srcset=&quot;image.webp&quot;&amp;gt;
  &amp;lt;source type=&quot;image/jpeg&quot; srcset=&quot;image.jpg&quot;&amp;gt;
  &amp;lt;img src=&quot;image.jpg&quot; alt=&quot;최적화된 이미지 형식&quot;&amp;gt;
&amp;lt;/picture&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;srcset 속성과 sizes 속성&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;img 태그에서의 속성 사용법과 동일합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4) map 태그&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 이미지에 여러 링크를 넣어야 할 때 사용하는 태그로, 하나의 이미지를 영역별로 구분하고 각 영역에 하이퍼 링크를 적용할 수 있게 해줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지와 연결된 &amp;lt;map&amp;gt; 태그를 작성하고, 자식 요소인 &amp;lt;area&amp;gt;는 이미지 안의 클릭 가능한 영역 지정을 위해 사용됩니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741622143951&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;img src=&quot;navigation.jpg&quot; alt=&quot;사이트 내비게이션&quot; usemap=&quot;#navigationmap&quot;&amp;gt;

&amp;lt;map name=&quot;navigationmap&quot;&amp;gt;
  &amp;lt;area shape=&quot;rect&quot; coords=&quot;0,0,100,100&quot; href=&quot;home.html&quot; alt=&quot;홈으로&quot;&amp;gt;
  &amp;lt;area shape=&quot;circle&quot; coords=&quot;150,150,50&quot; href=&quot;about.html&quot; alt=&quot;소개&quot;&amp;gt;
  &amp;lt;area shape=&quot;poly&quot; coords=&quot;250,150,300,200,250,250,200,200&quot; href=&quot;contact.html&quot; alt=&quot;연락처&quot;&amp;gt;
&amp;lt;/map&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;영역의 shape 별 coords 지정 방법은 아래와 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1741622152088&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!-- 사각형(rect): 좌상단 x,y 및 우하단 x,y 좌표 --&amp;gt;
&amp;lt;area shape=&quot;rect&quot; coords=&quot;x1,y1,x2,y2&quot; href=&quot;link.html&quot; alt=&quot;사각형 영역&quot;&amp;gt;

&amp;lt;!-- 원형(circle): 중심점 x,y 및 반지름 r --&amp;gt;
&amp;lt;area shape=&quot;circle&quot; coords=&quot;x,y,r&quot; href=&quot;link.html&quot; alt=&quot;원형 영역&quot;&amp;gt;

&amp;lt;!-- 다각형(poly): 각 꼭지점의 x,y 좌표들을 순서대로 나열 --&amp;gt;
&amp;lt;area shape=&quot;poly&quot; coords=&quot;x1,y1,x2,y2,x3,y3,...,xn,yn&quot; href=&quot;link.html&quot; alt=&quot;다각형 영역&quot;&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;Image Map Generator를 사용하면, 이미지 안에 특정 위치에 대한 좌표 뿐 아니라 &amp;lt;map&amp;gt; 태그를 자동으로 생성할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;a title=&quot;image map generator &quot; href=&quot;https://www.image-map.net/&quot;&gt; Image Map Generator 바로가기 &lt;/a&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: center;&quot; href=&quot;https://www.image-map.net/&quot;&gt; &lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;map 태그를 사용하면 한 장의 이미지에 여러 링크를 적용할 수 있지만, 이미지 크기가 고정된 경우에만 사용할 수 있다는 단점이 있습니다. 반응형웹 구현시에는 미디어 쿼리 설정에 따라 여러 개의 map 태그를 준비해야 하거나 고정된 이미지 크기일 때만 사용해야 한다는 점에 주의해야 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;웹 비디오와 오디오 최적화&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비디오와 오디오를 최적화 하기 위해선 preload 속성을 반드시 고려해야 합니다. 사용자가 반드시 비디오나 오디오를 사용할 것이라는 확신이 없다면 preload의 속성값을 auto로 사용해서는 안됩니다. 따라서, preload에 대해선 실제 사용자의 웹 사이트 사용을 모니터링 하고 이에 맞게 적절한 속성값 (auto, metadata, none)을 설정해야 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;웹 공유 최적화&amp;nbsp;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;오픈 그래프 프로토콜&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오픈 그래프 프로토콜은 콘텐츠 공유 시 콘텐츠에 대한 풍부한 정보를 제공하는데 도움을 줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오픈 그래프 프로토콜은 HTML 문서의 &amp;lt;head&amp;gt; 태그 안에 &amp;lt;meta&amp;gt; 태그를 사용해 정의할 수 있으며, 공유하는 콘텐츠의 제목, 설명, 대표 이미지, 크기 등의 정보를 정의해 사용자 방문을 유도하는 UI를 제공할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;반드시 정의해야하는 4가지 기본 프로퍼티 속성 값&amp;nbsp;&lt;/h4&gt;
&lt;pre id=&quot;code_1741622196826&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;meta property=&quot;og:title&quot; content=&quot;페이지 제목&quot;&amp;gt; &amp;lt;!-- 공유될 콘텐츠 제목 --&amp;gt;
&amp;lt;meta property=&quot;og:type&quot; content=&quot;website&quot;&amp;gt; &amp;lt;!-- 콘텐츠 유형 (website, article, book 등) --&amp;gt;
&amp;lt;meta property=&quot;og:url&quot; content=&quot;https://www.example.com/page&quot;&amp;gt; &amp;lt;!-- 콘텐츠 표준 URL --&amp;gt;
&amp;lt;meta property=&quot;og:image&quot; content=&quot;https://www.example.com/image.jpg&quot;&amp;gt; &amp;lt;!-- 대표 이미지 URL --&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;선택적인 속성 값&lt;/h4&gt;
&lt;pre id=&quot;code_1741622205617&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;meta property=&quot;og:description&quot; content=&quot;페이지에 대한 간략한 설명&quot;&amp;gt;
&amp;lt;meta property=&quot;og:site_name&quot; content=&quot;사이트 이름&quot;&amp;gt;
&amp;lt;meta property=&quot;og:locale&quot; content=&quot;ko_KR&quot;&amp;gt; &amp;lt;!-- 리소스 언어 --&amp;gt;
&amp;lt;meta property=&quot;og:locale:alternate&quot; content=&quot;en_US&quot;&amp;gt; &amp;lt;!-- 대체 언어 --&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;og:image, og:video, og:audio의 구조화된 속성&lt;/h4&gt;
&lt;pre id=&quot;code_1741622261881&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!-- 이미지 관련 오픈 그래프 태그 --&amp;gt;
&amp;lt;meta property=&quot;og:image&quot; content=&quot;https://example.com/image.jpg&quot;&amp;gt; &amp;lt;!-- 공유 시 표시될 이미지의 URL --&amp;gt;
&amp;lt;meta property=&quot;og:image:secure_url&quot; content=&quot;https://secure.example.com/image.jpg&quot;&amp;gt; &amp;lt;!-- HTTPS 프로토콜을 사용하는 이미지 URL --&amp;gt;
&amp;lt;meta property=&quot;og:image:type&quot; content=&quot;image/jpeg&quot;&amp;gt; &amp;lt;!-- 이미지의 MIME 타입 지정 (jpeg, png, gif 등) --&amp;gt;
&amp;lt;meta property=&quot;og:image:width&quot; content=&quot;1200&quot;&amp;gt; &amp;lt;!-- 이미지의 너비 (픽셀 단위) - 페이스북 권장 크기는 1200x630 --&amp;gt;
&amp;lt;meta property=&quot;og:image:height&quot; content=&quot;630&quot;&amp;gt; &amp;lt;!-- 이미지의 높이 (픽셀 단위) --&amp;gt;
&amp;lt;meta property=&quot;og:image:alt&quot; content=&quot;이미지 설명&quot;&amp;gt; &amp;lt;!-- 이미지에 대한 대체 텍스트 (접근성 향상) --&amp;gt;

&amp;lt;!-- 비디오 관련 오픈 그래프 태그 --&amp;gt;
&amp;lt;meta property=&quot;og:video&quot; content=&quot;https://example.com/video.mp4&quot;&amp;gt; &amp;lt;!-- 공유 시 표시될 비디오의 URL --&amp;gt;
&amp;lt;meta property=&quot;og:video:secure_url&quot; content=&quot;https://secure.example.com/video.mp4&quot;&amp;gt; &amp;lt;!-- HTTPS 프로토콜을 사용하는 비디오 URL --&amp;gt;
&amp;lt;meta property=&quot;og:video:type&quot; content=&quot;video/mp4&quot;&amp;gt; &amp;lt;!-- 비디오의 MIME 타입 지정 (mp4, webm 등) --&amp;gt;
&amp;lt;meta property=&quot;og:video:width&quot; content=&quot;1280&quot;&amp;gt; &amp;lt;!-- 비디오 플레이어의 너비 (픽셀 단위) --&amp;gt;
&amp;lt;meta property=&quot;og:video:height&quot; content=&quot;720&quot;&amp;gt; &amp;lt;!-- 비디오 플레이어의 높이 (픽셀 단위) --&amp;gt;

&amp;lt;!-- 오디오 관련 오픈 그래프 태그 --&amp;gt;
&amp;lt;meta property=&quot;og:audio&quot; content=&quot;https://example.com/audio.mp3&quot;&amp;gt; &amp;lt;!-- 공유 시 표시될 오디오의 URL --&amp;gt;
&amp;lt;meta property=&quot;og:audio:secure_url&quot; content=&quot;https://secure.example.com/audio.mp3&quot;&amp;gt; &amp;lt;!-- HTTPS 프로토콜을 사용하는 오디오 URL --&amp;gt;
&amp;lt;meta property=&quot;og:audio:type&quot; content=&quot;audio/mpeg&quot;&amp;gt; &amp;lt;!-- 오디오의 MIME 타입 지정 (mpeg, wav, ogg 등) --&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;og:type 의 공유 콘텐츠 타입 설정 속성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;og:type 속성을 사용하면 공유되는 콘텐츠가 어떤 타입인지 정의할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741622269734&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!-- 일반 웹사이트 타입 - 가장 기본적인 타입으로 일반 웹페이지에 사용 --&amp;gt;
&amp;lt;meta property=&quot;og:type&quot; content=&quot;website&quot;&amp;gt;

&amp;lt;!-- 기사 타입 - 뉴스, 블로그 포스트 등 기사 형식의 콘텐츠에 사용 --&amp;gt;
&amp;lt;meta property=&quot;og:type&quot; content=&quot;article&quot;&amp;gt;
&amp;lt;meta property=&quot;article:published_time&quot; content=&quot;2023-01-01T00:00:00+00:00&quot;&amp;gt; &amp;lt;!-- 기사 발행 시간 (ISO 8601 형식) --&amp;gt;
&amp;lt;meta property=&quot;article:author&quot; content=&quot;저자 이름&quot;&amp;gt; &amp;lt;!-- 기사 작성자 정보 --&amp;gt;
&amp;lt;meta property=&quot;article:section&quot; content=&quot;기술&quot;&amp;gt; &amp;lt;!-- 기사가 속한 섹션/카테고리 --&amp;gt;
&amp;lt;meta property=&quot;article:tag&quot; content=&quot;웹 최적화&quot;&amp;gt; &amp;lt;!-- 기사와 관련된 태그/키워드 --&amp;gt;

&amp;lt;!-- 도서 타입 - 책, 출판물에 관한 정보를 표시할 때 사용 --&amp;gt;
&amp;lt;meta property=&quot;og:type&quot; content=&quot;book&quot;&amp;gt;
&amp;lt;meta property=&quot;book:author&quot; content=&quot;저자 이름&quot;&amp;gt; &amp;lt;!-- 책의 저자 정보 --&amp;gt;
&amp;lt;meta property=&quot;book:isbn&quot; content=&quot;978-3-16-148410-0&quot;&amp;gt; &amp;lt;!-- 책의 국제 표준 도서 번호 --&amp;gt;
&amp;lt;meta property=&quot;book:release_date&quot; content=&quot;2023-01-01&quot;&amp;gt; &amp;lt;!-- 책의 출간일 --&amp;gt;

&amp;lt;!-- 프로필 타입 - 사용자, 인물 등의 프로필 페이지에 사용 --&amp;gt;
&amp;lt;meta property=&quot;og:type&quot; content=&quot;profile&quot;&amp;gt;
&amp;lt;meta property=&quot;profile:first_name&quot; content=&quot;홍&quot;&amp;gt; &amp;lt;!-- 프로필 사용자의 이름 --&amp;gt;
&amp;lt;meta property=&quot;profile:last_name&quot; content=&quot;길동&quot;&amp;gt; &amp;lt;!-- 프로필 사용자의 성 --&amp;gt;
&amp;lt;meta property=&quot;profile:username&quot; content=&quot;honggildong&quot;&amp;gt; &amp;lt;!-- 프로필 사용자의 사용자명 --&amp;gt;

&amp;lt;!-- 비디오 관련 타입 - 영화, TV 프로그램, 에피소드 등에 사용 --&amp;gt;
&amp;lt;meta property=&quot;og:type&quot; content=&quot;video.movie&quot;&amp;gt; &amp;lt;!-- 영화 타입 --&amp;gt;
&amp;lt;meta property=&quot;og:type&quot; content=&quot;video.episode&quot;&amp;gt; &amp;lt;!-- TV 에피소드, 웹 시리즈 등의 단일 에피소드 --&amp;gt;
&amp;lt;meta property=&quot;og:type&quot; content=&quot;video.tv_show&quot;&amp;gt; &amp;lt;!-- TV 프로그램, 웹 시리즈 등의 전체 쇼 --&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;콘텐츠 공유 시 유의할 사항&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오픈 그래프 프로토콜로 소셜 미디어에 한 번 공유된 정보는 해당 소셜 미디어에 기록됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약, 개발자가 HTML &amp;lt;meta&amp;gt;태그에 의해 실시간으로 반영하는 오픈 그래프 프로토콜 설정을 확인하기 위해서는 각 소셜 미디어에서 제공하는 공유 디버거 페이지에서 캐시 초기화 또는 다시 스크랩을 해야합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;웹 검색 최적화&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글은 구글 검색 엔진만을 위한 meta 태그를 제공합니다. meta 태그는 검색 엔진과 웹페이지에 대한 추가 정보를 제공하는 데 사용되는 HTML 태그입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;meta 태그 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 엔진 크롤링과 색인 생성을 위한 meta 태그에는 &amp;lt;meta name=&quot;robots&quot;&amp;gt;와 &amp;lt;meta name=&quot;googlebot&quot;&amp;gt;이 있습니다. 전자는 모든 검색 엔진에 적용되지만, 후자는 구글 검색 엔진에만 적용됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;구글에서 지원하는 로봇 메타 태그에 적용할 수 있는 지시어&lt;/h4&gt;
&lt;pre id=&quot;code_1741622250293&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;meta name=&quot;robots&quot; content=&quot;noindex&quot;&amp;gt; &amp;lt;!-- 페이지 색인 생성 금지 --&amp;gt;
&amp;lt;meta name=&quot;robots&quot; content=&quot;nofollow&quot;&amp;gt; &amp;lt;!-- 페이지 내 링크 추적 금지 --&amp;gt;
&amp;lt;meta name=&quot;robots&quot; content=&quot;noarchive&quot;&amp;gt; &amp;lt;!-- 페이지 캐시 저장 금지 --&amp;gt;
&amp;lt;meta name=&quot;robots&quot; content=&quot;nosnippet&quot;&amp;gt; &amp;lt;!-- 검색 결과에 스니펫 표시 금지 --&amp;gt;
&amp;lt;meta name=&quot;robots&quot; content=&quot;notranslate&quot;&amp;gt; &amp;lt;!-- 번역 제안 금지 --&amp;gt;
&amp;lt;meta name=&quot;robots&quot; content=&quot;noimageindex&quot;&amp;gt; &amp;lt;!-- 이미지 색인 생성 금지 --&amp;gt;
&amp;lt;meta name=&quot;robots&quot; content=&quot;max-snippet:-1&quot;&amp;gt; &amp;lt;!-- 스니펫 최대 길이 제한 (-1은 제한 없음) --&amp;gt;
&amp;lt;meta name=&quot;robots&quot; content=&quot;max-image-preview:large&quot;&amp;gt; &amp;lt;!-- 이미지 미리보기 크기 (none/standard/large) --&amp;gt;
&amp;lt;meta name=&quot;robots&quot; content=&quot;max-video-preview:-1&quot;&amp;gt; &amp;lt;!-- 비디오 미리보기 최대 길이 (초 단위, -1은 제한 없음) --&amp;gt;
&amp;lt;meta name=&quot;robots&quot; content=&quot;unavailable_after: 2023-12-31&quot;&amp;gt; &amp;lt;!-- 특정 날짜 이후 색인 생성 중단 --&amp;gt;

&amp;lt;!-- 구글봇 전용 --&amp;gt;
&amp;lt;meta name=&quot;googlebot&quot; content=&quot;noindex, nofollow&quot;&amp;gt; &amp;lt;!-- 구글봇에게만 적용되는 설정 --&amp;gt;
&amp;lt;meta name=&quot;googlebot-news&quot; content=&quot;noindex&quot;&amp;gt; &amp;lt;!-- 구글 뉴스봇에게만 적용되는 설정 --&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/웹 최적화</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/39</guid>
      <comments>https://dreamyard.tistory.com/entry/%EC%9B%B9-%EC%B5%9C%EC%A0%81%ED%99%94-%EC%9B%B9-%EB%A6%AC%EC%86%8C%EC%8A%A4%EC%99%80-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%8F%99%EC%98%81%EC%83%81-%EA%B2%80%EC%83%89-%EC%B5%9C%EC%A0%81%ED%99%94#entry39comment</comments>
      <pubDate>Tue, 11 Mar 2025 01:06:27 +0900</pubDate>
    </item>
    <item>
      <title>[React] React 상태의 정의 및 분류, 상태 관리 잘하는 방법</title>
      <link>https://dreamyard.tistory.com/entry/React-React-%EC%83%81%ED%83%9C%EC%9D%98-%EC%A0%95%EC%9D%98-%EB%B0%8F-%EB%B6%84%EB%A5%98-%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC-%EC%9E%98%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95</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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1️⃣ 리액트의 상태&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;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&quot; 렌더링 결과에 영향을 주는 정보를 담은 순수 자바스크립트 객체&quot;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2️⃣ 상태의 분류&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 지역 상태 (Local State)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트 내부에서 사용되는 상태입니다. 체크 박스의 체크 여부, 폼의 입력값 등에 해당되며, 주로 useState 훅을 많이 사용합니다. 경우에 따라서는 useReducer를 사용하기도 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 전역 상태 (Global State)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱 전체에서 공유되는 상태를 의미합니다. 여러 개의 컴포넌트가 전역 상태를 사용할 수 있으며 상태가 변경되면 컴포넌트들도 업데이트 됩니다. Prop drilling 문제를 피하고자 지역 상태를 해당 컴포넌트들 사이의 전역 상태로 공유할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Prop drilling이란?&amp;nbsp;&lt;/h4&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;props를 통해 데이터를 전달하는 과정에서 중간 컴포넌트는 해당 데이터가 필요하지 않음에도 자식 컴포넌트에 전달하기 위해 props를 전달해야하는 과정을 의미합니다. 컴포넌트 수가 많을수록 Prop drilling으로 인해 코드가 훨씬 복잡해질 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) 서버 상태 (Server State)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자의 정보, 글 목록 등 외부 서버에 저장해야 하는 상태들을 의미합니다. UI 상태와 결합해 관리하게 되며 로딩 여부나 에러 상태 등이 포함됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 상태는 지역 상태 혹은 전역 상태와 동일한 방법으로 관리되며, 최근에는 react-query, SWR 과 같은 외부 라이브러리를 사용하여 관리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3️⃣상태 관리를 잘 하는 방법&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;즉, 가능하면 Stateless 컴포넌트를 활용하는게 좋습니다.&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4️⃣상태를 정의할 때 고려해야할 2가지&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 값을 상태로 정의할 때는 다음 2가지 사항을 고려해야 합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. 시간이 지나도 변하지 않는다면 상태가 아니다.&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. 파생된 값은 상태가 아니다.&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 시간이 지나도 변하지 않는다면 상태가 아니다. &amp;rarr; 객체 참조 동일성 유지&lt;/h3&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;컴포넌트 마운트 시에만 스토어 객체 인스턴스를 생성하고, 컴포넌트 언마운트까지 해당 참조가 변하지 않는다고 가정해보겠습니다. 이를 단순히 상수 변수에 저장하여 사용할 수도 있지만, 이 방식은 렌더링 될 때마다 새로운 객체 인스턴스가 생성되기 때문에 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;u&gt; 컴포넌트 라이프사이클 내에서 마운트 될 때 인스턴스가 생성되고, 렌더링 될 때마다 동일한 객체 참조가 유지되도록 구현해주어야 합니다.&amp;nbsp;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼, 어떤 방식으로 구현할 수 있고 어떤 방법을 사용하는게 가장 나을까요? 아래 정리해보았습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;방법 1. useMemo를 이용한 메모이제이션&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useMemo를 활용하면 컴포넌트가 마운트 될 때만 객체 인스턴스를 생성하고 이후 렌더링에서는 이전 인스턴스를 재활용할 수 있도록 구현 가능합니다. 즉, 객체 참조의 동일성 유지를 위해 널리 사용되는 방법 중 하나 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;const store = useMemo(() =&amp;gt; new Store(), []);
&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;하지만 공식 문서를 살펴보면, useMemo를 통한 메모이제이션은 의미상으로 보장된 것이 아니므로, 오로지 성능 향상을 위해서만 사용해야한다고 되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 리액트에선 메모리 확보를 위해 이전 메모이제이션 데이터가 삭제될 수 있다고 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 useMemo를 사용한다면, useMemo 없이도 코드가 올바르게 작동하도록 작성한 후, 추후 성능 개선을 위해 useMemo를 추가하는 것이 적절한 접근 방식 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;방법 2. useState의 초깃값만 지정하는 방법&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useState의 초깃값만 지정함으로써 모든 렌더링 과정에서 객체 참조를 동일하게 유지할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 useState(new Store())와 같이 사용하면 객체 인스턴스가 실제로 사용되지 않더라도 렌더링 마다 생성되어 초깃값 설정에는 큰 비용이 소요될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 useState(()=&amp;gt; new Store())와 같이 초깃값을 계산하는 콜백을 지정하는 방식을 사용해야 합니다. (지연 초기화 방식)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;const [store] = useState(() =&amp;gt; new Store());
&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;하지만 useState 사용은 기술적으론 잘 동작하더라도 의미론적으로는 좋은 방법이 아닙니다. 원래는 상태를 시간이 지나면서 변화되어 렌더링에 영향을 주는 데이터로 정의했지만, 현재의 목적은 모든 렌더링 과정에서 객체의 참조를 동일하게 유지하고자 하는 것이기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;방법 3. useRef 이용하기 (권장)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서에 따르면 useRef가 동일한 객체 참조를 유지하려는 목적으로 사용하기에는 가장 적합한 훅 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useRef의 인자로 직접 new Store()을 사용하면 useState와 마찬가지로 렌더링마다 불필요한 인스턴스가 생성되므로 아래와 같이 작성되어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;llvm&quot;&gt;&lt;code&gt;const store = useRef&amp;lt;Store&amp;gt;(null);

if(!store.current) {
	store.current = new Store();
}
&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;useRef는 기술적으로 useState({children : initialValue})[0]과 동일하다고 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 상태라고 하는 것은 렌더링에 영향을 주며 변화하는 값을 의미하므로, 의미론적으로는 객체 참조 동일성 유지를 위해 useState에 초깃값만 할당하는 것은 적절하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가독성 등의 이유료 팀 내에서 합의된 컨벤션으로 지정된 경우가 아니라면, 동일 객체 참조를 할 땐 useRef를 사용할 것을 권장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// useRef 사용 예시 - 구체적인 스토어 객체
type Store = {
  data: any[];
  addItem: (item: any) =&amp;gt; void;
  removeItem: (id: number) =&amp;gt; void;
};

const Component = () =&amp;gt; {
  //   useRef를 통해 객체 참조 유지
  const storeRef = useRef&amp;lt;Store | null&amp;gt;(null);
  
  // 최초 렌더링 시 한 번만 Store 인스턴스 생성
  if (storeRef.current === null) {
    storeRef.current = {
      data: [],
      addItem: (item) =&amp;gt; {
        storeRef.current!.data.push(item);
        console.log('Item added', storeRef.current!.data);
      },
      removeItem: (id) =&amp;gt; {
        storeRef.current!.data = storeRef.current!.data.filter(
          item =&amp;gt; item.id !== id
        );
        console.log('Item removed', storeRef.current!.data);
      }
    };
  }
  
  // 렌더링마다 동일한 Store 인스턴스 참조
  const store = storeRef.current;
  
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; store.addItem({ id: Date.now(), name: 'New item' })}&amp;gt;
        아이템 추가
      &amp;lt;/button&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; store.removeItem(store.data[0]?.id)}&amp;gt;
        첫 번째 아이템 삭제
      &amp;lt;/button&amp;gt;
      &amp;lt;pre&amp;gt;{JSON.stringify(store.data, null, 2)}&amp;lt;/pre&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 파생된 값은 상태가 아니다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좀 더 자세히 말하면, props거나 기존 상태에서 계산될 수 있는 값은 상태가 아닙니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1. 부모에게서 props로 전달받으면 상태가 아닙니다.&lt;br /&gt;2. 기존 상태에서 계산할 수 있는 값은 상태가 아닙니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 값에서 파생된 값을 상태로 관리하게 되면 기존 출처와는 다른 새로운 출처에서 관리하게 되는 것이므로 해당 데이터의 정확성과 일관성을 보장하기 어렵습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 문장의 의미를 예시 코드를 보며 좀 더 자세히 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 부모에게서 props로 전달받으면 상태가 아닙니다.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 컴포넌트가 있다고 가정해볼게요. 초기 이메일 값을 부모 컴포넌트로부터 받아 input value로 렌더링하고 이후에는 사용자가 입력한 값을 input 태그의 value로 렌더링합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;//   부모로부터 initialEmail을 props로 받아서 사용하는 예시
type UserEmailProps = {
  initialEmail: string;
};

const UserEmail = ({ initialEmail }: UserEmailProps) =&amp;gt; {
  //   부모로부터 받은 initialEmail을 useState의 초깃값으로 설정
  const [email, setEmail] = useState(initialEmail);

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;label htmlFor=&quot;email&quot;&amp;gt;이메일:&amp;lt;/label&amp;gt;
      &amp;lt;input
        id=&quot;email&quot;
        type=&quot;email&quot;
        value={email}
        onChange={(e) =&amp;gt; setEmail(e.target.value)}
      /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

//   사용 예시
const ParentComponent = () =&amp;gt; {
  const [userEmail, setUserEmail] = useState(&quot;user@example.com&quot;);
  
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setUserEmail(&quot;new@example.com&quot;)}&amp;gt;
        이메일 변경
      &amp;lt;/button&amp;gt;
      &amp;lt;UserEmail initialEmail={userEmail} /&amp;gt;
      {/* 
          버튼 클릭 시 userEmail이 변경되어도 
        UserEmail 컴포넌트의 input 값은 변경되지 않음 
      */}
    &amp;lt;/div&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;위 컴포넌트에서 전달받은 initialEmail prop 값이 변경되어도 input 태그의 value는 변경되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useState의 초깃값으로 설정한 값은 컴포넌트가 마운트될 때 한 번만 email 상태의 값으로 설정되며 이후에는 독자적으로 관리됩니다.&lt;/p&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와 상태를 동기화하기 위해 useEffect를 사용한 해결책을 떠올릴 수도 있지만, 좋은 방법은 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 값을 변경한 뒤에 initialEmail prop이 변경된다면 input 태그의 value는 어떻게 설정될까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴 때는 사용자의 입력을 무시하고 부모 컴포넌트로부터 전달된 initialEmail props의 값을 value로 설정할 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useEffect를 사용한 동기화 작업은 리액트 외부 데이터와 동기화할 때만 사용해야하며, 내부에 존재하는 데이터를 동기화하는 데는 사용하면 안됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부에 존재하는 상태를 useEffect로 동기화하면 추적하기 어려운 오류가 발생할 수 있기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;const [email, setEmail] = useState(initialEmail);

useEffect(() =&amp;gt; {
  setEmail(initialEmail);
}, [initialEmail]);
&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;현재 email 상태에 대한 출처는 prop 받는 initialEmail과 useState로 생성한 email state 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 해결을 위해서는 두 출처의 데이터를 동기화하기보다 단일 출처로부터 데이터를 사용하도록 변경해주어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때, 일반적으로 리액트에서는 상위 컴포넌트에서 상태를 관리하도록 해주는 상태 끌어올리기(Lifting State Up) 기법을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UserEmail에서 관리하던 상태를 부모 컴포넌트로 옮겨서 email 데이터의 출처를 props 하나로 통일할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;이처럼 두 컴포넌트에서 동일한 데이터를 상태로 갖고 있을 경우엔 두 컴포넌트 간의 상태를 동기화하는 방법이 아닌, 가까운 공통 부모 컴포넌트로 상태를 끌어올려 SSOT를 지킬 수 있도록 해야 합니다.&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;※ SSOT(Single Source of Truth) : 어떠한 데이터도 단 하나의 출처에서 생성하고 수정해야 한다는 원칙을 의미하는 방법론.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 기존 상태에서 계산할 수 있는 값은 상태가 아닙니다.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드는 아이템 목록과 선택된 아이템 목록을 가진 코드 입니다. 이 코드는 아이템 목록 변경 시마다 선택된 아이템 목록을 가져오기 위해 useEffect로 동기화 작업을 하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;xquery&quot;&gt;&lt;code&gt;//   잘못된 사용 예시: 파생 데이터를 별도의 상태로 관리
type Item = {
  id: number;
  name: string;
  isSelected: boolean;
};

const ItemList = () =&amp;gt; {
  const [items, setItems] = useState&amp;lt;Item[]&amp;gt;([
    { id: 1, name: &quot;아이템 1&quot;, isSelected: false },
    { id: 2, name: &quot;아이템 2&quot;, isSelected: true },
    { id: 3, name: &quot;아이템 3&quot;, isSelected: false },
  ]);
  
  //   items로부터 계산 가능한 값을 별도 상태로 관리하면 동기화 문제 발생
  const [selectedItems, setSelectedItems] = useState&amp;lt;Item[]&amp;gt;([]);
  
  //   useEffect로 동기화 시도 (좋지 않은 방법)
  useEffect(() =&amp;gt; {
    setSelectedItems(items.filter(item =&amp;gt; item.isSelected));
  }, [items]);
  
  const toggleSelection = (id: number) =&amp;gt; {
    setItems(
      items.map(item =&amp;gt; 
        item.id === id ? { ...item, isSelected: !item.isSelected } : item
      )
    );
    //   여기서 items가 변경되면, useEffect가 실행되어 selectedItems 상태도 변경됨
    // 이로 인해 2번의 렌더링이 발생
  };
  
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h3&amp;gt;전체 아이템 ({items.length})&amp;lt;/h3&amp;gt;
      &amp;lt;ul&amp;gt;
        {items.map(item =&amp;gt; (
          &amp;lt;li 
            key={item.id}
            style={{ 
              cursor: 'pointer', 
              fontWeight: item.isSelected ? 'bold' : 'normal' 
            }}
            onClick={() =&amp;gt; toggleSelection(item.id)}
          &amp;gt;
            {item.name} {item.isSelected ? '✓' : ''}
          &amp;lt;/li&amp;gt;
        ))}
      &amp;lt;/ul&amp;gt;
      
      &amp;lt;h3&amp;gt;선택된 아이템 ({selectedItems.length})&amp;lt;/h3&amp;gt;
      &amp;lt;ul&amp;gt;
        {selectedItems.map(item =&amp;gt; (
          &amp;lt;li key={item.id}&amp;gt;{item.name}&amp;lt;/li&amp;gt;
        ))}
      &amp;lt;/ul&amp;gt;
    &amp;lt;/div&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;이 코드의 가장 큰 문제는 items와 selectedItems가 동기화되지 않을 수 있다는 것 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드와 같이 아주 간단한 로직을 담고 있다면 괜찮지만, 여러 상태가 복잡하게 얽힐 경우 흐름 파악이 어렵고, 의도치 않게 동기화 과정이 누락될 수도 있습니다. 또한 setSelectedItem을 사용하여 items에서 가져온 데이터가 아닌 임의의 데이터 셋을 설정하는 것도 가능하므로 오류 발생 가능성도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;items와 seletedItems 라는 2가지 상태를 유지하면서 useEffect로 동기화하는 과정을 거치면 selectedItems 값을 얻기위해 2번의 렌더링이 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면, 새로운 상태로 정의함으로써 단일 출처가 아닌 여러 출처를 가지게 되었고, 이에 따라 동기화 문제가 발생하게 된다는 것입니다. 따라서, 하나의 출처로 합치는 방향으로 코드를 수정해야합니다. 아래 그 방법을 알아보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;계산된 값을 자바스크립트 변수로 담기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태로 정의하지 않고, 계산된 값을 자바스크립트 변수로 담으면 items가 변경될 때마다 컴포넌트가 새로 렌더링되고, 매번 렌더링될 때마다 selectedItems를 다시 계산하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 단일 출처를 가지면서 원하는 동작을 수행하게 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;const [items, setItems] = useState&amp;lt;Item[]&amp;gt;([]);
const selectedItems = items.filter((item) =&amp;gt; item.isSelected);
&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;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;size18&quot;&gt;&lt;b&gt;계산 비용이 크다면? useMemo&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때는 useMemo를 사용해 items이 변경될 때만 계산을 수행하고 결과를 메모이제이션 함으로써 성능을 개선할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;const [items, setItems] = useState&amp;lt;Item[]&amp;gt;([]);
const selectedItems = useMemo(() =&amp;gt; veryExpensiveCalculation(items), [items]);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5️⃣ useState 대신 useReducer가 권장되는 경우&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태관리 시, 어떨 때 useState를 사용하고 useReducer를 사용해야할까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useState 대신 useReducer 사용이 권장되는 경우는 크게 2가지 입니다. 각각의 경우를 예시 코드를 통해 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;1. 다수의 하위 필드를 포함하고 있는 복잡한 상태 로직을 다룰 때&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;2. 다음 상태가 이전 상태에 의존적일 때&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 다수의 하위 필드를 포함하고 있는 복잡한 상태 로직을 다룰 때&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 리뷰 리스트를 필터링하여 보여주기 위한 쿼리를 상태로 저장해야한다면, 이러한 쿼리는 검색 날짜 범위, 리뷰 점수, 키워드 등 많은 하위 필드를 가지게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지네이션을 고려한다면, 페이지, 사이즈 등의 필드도 추가될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;//   복잡한 상태 구조: 리뷰 필터링 쿼리
type ReviewQuery = {
  keyword: string;
  minScore: number;
  maxScore: number;
  startDate: string | null; // ISO 형식 '2023-01-01'
  endDate: string | null;
  page: number;
  size: number;
};

// useState로 구현한 경우 (권장하지 않음)
const ReviewListWithState = () =&amp;gt; {
  const [query, setQuery] = useState&amp;lt;ReviewQuery&amp;gt;({
    keyword: '',
    minScore: 0,
    maxScore: 5,
    startDate: null,
    endDate: null,
    page: 0,
    size: 10
  });

  //   페이지 변경 시 - 기존 상태를 복사하고 page만 수정
  const handlePageChange = (newPage: number) =&amp;gt; {
    setQuery({
      ...query,
      page: newPage
    });
  };

  //   페이지 크기 변경 시 - 기존 상태를 복사하고 size를 수정하며, page는 0으로 초기화
  const handleSizeChange = (newSize: number) =&amp;gt; {
    setQuery({
      ...query,
      size: newSize,
      page: 0 //   size가 변경되면 page를 0으로 초기화해야 함 (비즈니스 로직)
    });
  };

  //   키워드 변경 시 - 복잡한 업데이트 로직
  const handleKeywordChange = (newKeyword: string) =&amp;gt; {
    setQuery({
      ...query,
      keyword: newKeyword,
      page: 0 //   검색어가 변경되면 page를 0으로 초기화해야 함 (비즈니스 로직)
    });
  };

  // 다른 상태 변경 함수들...

  return (
    &amp;lt;div&amp;gt;
      {/* UI 구현 */}
    &amp;lt;/div&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;이러한 데이터 구조를 useState로 다루면, 상태를 업데이트할 때마다 잠재적인 오류 가능성이 증가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지 값만 업데이트하고 싶어도 우선 전체 데이터를 가지고 온 다음 페이지값을 덮어쓰게 되므로, 사이즈나 필터 같은 다른 필드가 수정될 수 있어 의도치 않은 오류가 발생할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 '사이즈 필드를 업데이트 할 때는 페이지 필드를 0으로 설정해야 한다.' 등의 특정한 업데이트 규칙이 있다면 useState 만으로는 한계가 있습니다. 이럴 경우 useReducer를 사용하는 것이 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;useReducer 적용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useReducer는 무엇을 변경할지와 어떻게 변경할지를 분리해, dispatch를 통해 어떤 작업을 할지 액션으로 넘기고, reducer 함수 내에서 상태를 업데이트하는 방식을 정의합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 복잡한 상태 로직을 숨기고 안전성을 높일 수 있습니다. 아래는 앞에서 본 리뷰 쿼리 대해 useReducer를 적용한 코드입니다.&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;//   useReducer로 개선한 버전
// 액션 타입 정의
type ReviewQueryAction =
  | { type: 'SET_KEYWORD'; payload: string }
  | { type: 'SET_SCORE_RANGE'; payload: { min: number; max: number } }
  | { type: 'SET_DATE_RANGE'; payload: { start: string | null; end: string | null } }
  | { type: 'SET_PAGE'; payload: number }
  | { type: 'SET_SIZE'; payload: number }
  | { type: 'RESET_FILTERS' };

// 리듀서 함수
const reviewQueryReducer = (state: ReviewQuery, action: ReviewQueryAction): ReviewQuery =&amp;gt; {
  switch (action.type) {
    case 'SET_KEYWORD':
      return {
        ...state,
        keyword: action.payload,
        page: 0 //   검색어 변경 시 페이지 초기화 로직 포함
      };
    case 'SET_SCORE_RANGE':
      return {
        ...state,
        minScore: action.payload.min,
        maxScore: action.payload.max,
        page:
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 다음 상태가 이전 상태에 의존적일 때&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;boolean 상태를 토글하는 액션만 사용하는 경우에도 useState 대신 useReducer를 사용하곤 합니다. 이유가 뭘까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useState와 달리 useReducer는 상태 업데이트 로직을 분리할 수 있고, 이전 상태에 의존하는 로직을 더 안전하게 처리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useState를 사용하여 이전 상태에 의존하는 업데이트를 할 때는 함수형 업데이트를 사용해야 하는데, 이것을 실수로 놓치면 버그가 발생할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// useState로 토글 구현 (함수형 업데이트 사용)
const [isOpen, setIsOpen] = useState(false);

// 올바른 방법
const toggle = () =&amp;gt; setIsOpen(prev =&amp;gt; !prev);

// 잘못된 방법 (비동기 업데이트 시 문제 발생 가능)
const toggleIncorrect = () =&amp;gt; setIsOpen(!isOpen);
&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;useReducer를 사용하면 이런 문제를 방지할 수 있습니다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;pf&quot;&gt;&lt;code&gt;// useReducer로 토글 구현
const [isOpen, dispatch] = useReducer(state =&amp;gt; !state, false);

// 항상 이전 상태를 기반으로 업데이트됨
const toggle = () =&amp;gt; dispatch();
&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;또한 useReducer는 복잡한 상태 로직을 테스트하기 쉽게 만들고, 컴포넌트와 분리된 순수 함수로 상태 변환 로직을 관리할 수 있게 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6️⃣ 전역 상태 관리와 상태 관리 라이브러리&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 컨텍스트 API + 'useState 또는 useReducer'&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨텍스트 API는 다른 컴포넌트들과 데이터를 쉽게 공유하기 위한 목적으로 사용되는 API이며, Prop Drilling과 같은 문제를 해결하기 위한 도구로 활용됩니다. UI 테마 정보나 &lt;u&gt;로케일 데이터*&lt;/u&gt;와 같이 전역적으로 제공해야 할 때도 유용하게 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;로케일 데이터란?&amp;nbsp;&lt;/h4&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로케일 데이터는 애플리케이션의 지역화(localization)와 국제화(internationalization)를 위한 정보를 의미합니다. 이는 다음과 같은 요소들을 포함합니다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;언어 설정&lt;/b&gt; - 사용자 인터페이스에 표시될 텍스트의 언어(예: 한국어, 영어, 일본어 등)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;날짜 및 시간 형식&lt;/b&gt; - 지역에 따라 다른 날짜 표시 방식(예: MM/DD/YYYY, DD/MM/YYYY, YYYY-MM-DD)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;숫자 형식&lt;/b&gt; - 천 단위 구분자, 소수점 표시(예: 1,000.00 vs 1.000,00)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;통화 형식&lt;/b&gt; - 통화 기호 및 위치(예: ₩1,000, $1,000, 1.000&amp;euro;)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;측정 단위&lt;/b&gt; - 미터법 또는 영국식 단위계 등&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 또는 앱 개발에서 로케일 데이터는 일반적으로 전역 상태로 관리되며, React에서는 컨텍스트 API를 사용하여 이 데이터를 애플리케이션 전체에 제공하는 경우가 많습니다. 이렇게 하면 사용자가 언어나 지역 설정을 변경했을 때 모든 컴포넌트가 일관되게 업데이트될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 국제화 라이브러리인 i18next나 react-intl 등을 사용할 때 로케일 데이터를 컨텍스트를 통해 전달하는 패턴이 흔히 사용됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨텍스트 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;예시 코드를 살펴보겠습니다. TabGroup 컴포넌트와 Tab 컴포넌트에 type이라는 prop을 전달할 경우, Tab Group 컴포넌트에만 이 prop을 전달하고 Tab 컴포넌트의 구현 내에서도 사용할 수 있게 하려면 어떻게 해야 할까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;// TabContext.js
import { createContext, useContext } from 'react';

//   컨텍스트 API: 컴포넌트 간 데이터 공유를 위한 기본 생성
export const TabContext = createContext(null); // ⭐ 새로운 컨텍스트 객체 생성

// ⚙️ 커스텀 훅: 컨텍스트를 쉽게 사용하기 위한 래퍼
export const useTabContext = () =&amp;gt; {
  const context = useContext(TabContext); //   React의 useContext 훅을 사용하여 컨텍스트 값 접근
  if (!context) {
    //   에러 처리: 컨텍스트가 Provider 외부에서 사용될 경우
    throw new Error('useTabContext must be used within a TabProvider');
  }
  return context;
};&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;아래와 같이 상위 컴포넌트 구현 부에 컨텍스트 프로바이더(Context Provider)를 넣어주고, 하위 컴포넌트에서 해당 컨텍스트를 구독하여 데이터를 읽어오는 방식으로 구현할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;// TabGroup.js
import { TabContext } from './TabContext';

//   상위 컴포넌트: 컨텍스트 프로바이더를 포함하는 부모 컴포넌트
export function TabGroup({ children, type }) {
  return (
    //   컨텍스트 공유: Provider를 통해 type 값을 하위 컴포넌트에 전달
    &amp;lt;TabContext.Provider value={{ type }}&amp;gt; // ⭐ 프로바이더로 값 제공
      &amp;lt;div className={`tab-group tab-group-${type}`}&amp;gt;
        {children}
      &amp;lt;/div&amp;gt;
    &amp;lt;/TabContext.Provider&amp;gt;
  );
}

// Tab.js
import { useTabContext } from './TabContext';

//   하위 컴포넌트: 컨텍스트를 구독하여 값을 사용
export function Tab({ children }) {
  const { type } = useTabContext(); //   커스텀 훅을 통해 상위에서 제공된 type 값 접근
  
  return (
    &amp;lt;div className={`tab tab-${type}`}&amp;gt; //   상위에서 받은 type 값으로 스타일 적용
      {children}
    &amp;lt;/div&amp;gt;
  );
}

// 사용 예시
function App() {
  return (
    //   실제 사용: 상위 컴포넌트에만 prop 전달, 하위는 컨텍스트로 접근
    &amp;lt;TabGroup type=&quot;primary&quot;&amp;gt; // ⭐ type prop은 상위 컴포넌트에만 전달
      &amp;lt;Tab&amp;gt;Tab 1&amp;lt;/Tab&amp;gt; //   Tab에는 직접 prop을 전달하지 않아도 됨
      &amp;lt;Tab&amp;gt;Tab 2&amp;lt;/Tab&amp;gt;
    &amp;lt;/TabGroup&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;유틸리티 함수를 정의하여 더 간단한 코드로 컨텍스트와 훅을 생성할 수도 있습니다. 아래와 같이 createContextHook이라는 유틸리티 함수를 정의해서 자주 사용되는 프로바이더와 해당 컨텍스트를 사용하는 훅을 간편하게 생성할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;// createContextHook.js
import { createContext, useContext } from 'react';

//  ️ 유틸리티 함수: 컨텍스트와 훅을 한 번에 생성
export function createContextHook(name) {
  const Context = createContext(null); // ⭐ 새로운 컨텍스트 객체 생성
  
  const useContextHook = () =&amp;gt; {
    const context = useContext(Context); //   컨텍스트 값 접근
    if (!context) {
      //   에러 처리
      throw new Error(`use${name}Context must be used within a ${name}Provider`);
    }
    return context;
  };
  
  //   배열 형태로 반환하여 구조분해할당 가능
  return [Context, useContextHook];
}

// 사용 예시
//   간결한 방식: 한 줄로 컨텍스트와 훅 생성
const [TabContext, useTabContext] = createContextHook('Tab'); // ⭐ 유틸리티 함수 사용

// ✨ 컨텍스트 프로바이더 컴포넌트 생성
export function TabProvider({ children, value }) {
  return &amp;lt;TabContext.Provider value={value}&amp;gt;{children}&amp;lt;/TabContext.Provider&amp;gt;;
}

export { useTabContext };&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;컨텍스트 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;하지만 아래 코드처럼 useState나 useReducer와 같은 지역 상태를 관리하기 위한 API와 결합하여 여러 컴포넌트 사이에서 상태를 공유하기 위한 방법으로 사용되기도 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;// ThemeContext.js
import { createContext, useContext, useState } from 'react';

const ThemeContext = createContext(null); // ⭐ 테마를 위한 컨텍스트 생성

//   컨텍스트 API + useState 결합: 상태 관리와 공유를 함께
export function ThemeProvider({ children }) {
  //   useState: 로컬 상태 관리를 위한 훅 사용
  const [theme, setTheme] = useState('light'); // ⭐ 상태 관리 추가
  
  //   토글 함수: 상태 업데이트를 위한 함수 정의
  const toggleTheme = () =&amp;gt; {
    setTheme(prevTheme =&amp;gt; (prevTheme === 'light' ? 'dark' : 'light')); //   함수형 업데이트
  };
  
  //   컨텍스트에 제공할 값 패키징
  const value = {
    theme,
    toggleTheme // ⚡ 상태와 상태 변경 함수를 모두 제공
  };
  
  return (
    //   컨텍스트 제공: 상태와 상태 변경 함수를 모두 포함
    &amp;lt;ThemeContext.Provider value={value}&amp;gt;
      {children}
    &amp;lt;/ThemeContext.Provider&amp;gt;
  );
}

// ⚙️ 커스텀 훅: 테마 컨텍스트 접근
export const useTheme = () =&amp;gt; {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within a ThemeProvider');
  }
  return context;
};

// 사용 예시
function App() {
  return (
    //   전역 테마 제공
    &amp;lt;ThemeProvider&amp;gt;
      &amp;lt;ThemedComponent /&amp;gt;
    &amp;lt;/ThemeProvider&amp;gt;
  );
}

//   테마를 사용하는 컴포넌트
function ThemedComponent() {
  //   컨텍스트를 통해 테마 상태와 변경 함수에 접근
  const { theme, toggleTheme } = useTheme(); // ⭐ 커스텀 훅으로 상태와 함수에 접근
  
  return (
    &amp;lt;div className={`themed-component ${theme}`}&amp;gt; //   테마 적용
      &amp;lt;p&amp;gt;Current theme: {theme}&amp;lt;/p&amp;gt;
      &amp;lt;button onClick={toggleTheme}&amp;gt;Toggle Theme&amp;lt;/button&amp;gt; //   테마 토글 이벤트
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 컨텍스트 API를 사용하여 전역 상태 관리를 하는 것은 대규모이거나 성능이 중요한 애플리케이션에서는 권장되지 않는 방법입니다. 왜냐하면, 컨텍스트 프로바이더의 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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 외부 상태 관리 라이브러리 (Redux, MobX, Recoil, Zustand 등)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부 상태 관리 라이브러리는 복잡한 상태 관리를 위한 더 강력한 도구를 제공합니다. 이러한 라이브러리들은 React의 Context API보다 더 효율적인 렌더링 최적화, 개발자 도구, 미들웨어 지원 등의 기능을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Redux&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redux는 예측 가능한 상태 컨테이너로, 단일 스토어와 리듀서 패턴을 사용합니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// Redux 스토어 설정
import { createStore } from 'redux';

// 리듀서
const counterReducer = (state = { count: 0 }, action) =&amp;gt; {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    case 'DECREMENT':
      return { count: state.count - 1 };
    default:
      return state;
  }
};

// 스토어 생성
const store = createStore(counterReducer);

// 컴포넌트에서 사용
import { Provider, useSelector, useDispatch } from 'react-redux';

function App() {
  return (
    &amp;lt;Provider store={store}&amp;gt;
      &amp;lt;Counter /&amp;gt;
    &amp;lt;/Provider&amp;gt;
  );
}

function Counter() {
  const count = useSelector(state =&amp;gt; state.count);
  const dispatch = useDispatch();
  
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;p&amp;gt;Count: {count}&amp;lt;/p&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; dispatch({ type: 'INCREMENT' })}&amp;gt;+&amp;lt;/button&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; dispatch({ type: 'DECREMENT' })}&amp;gt;-&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;MobX&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MobX는 반응형 프로그래밍 접근법을 사용하는 상태 관리 라이브러리입니다.&lt;/p&gt;
&lt;pre style=&quot;background-color: #f8f8f8; color: #383a42;&quot;&gt;&lt;code&gt;import { makeAutoObservable } from 'mobx';
import { observer } from 'mobx-react-lite';

// 스토어 클래스
class CounterStore {
  count = 0;
  
  constructor() {
    makeAutoObservable(this);
  }
  
  increment() {
    this.count += 1;
  }
  
  decrement() {
    this.count -= 1;
  }
}

const counterStore = new CounterStore();

// 컴포넌트에서 사용 (observer HOC로 감싸기)
const Counter = observer(() =&amp;gt; {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;p&amp;gt;Count: {counterStore.count}&amp;lt;/p&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; counterStore.increment()}&amp;gt;+&amp;lt;/button&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; counterStore.decrement()}&amp;gt;-&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Recoil&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Recoil은 React를 위해 설계된 상태 관리 라이브러리로, 원자(atoms)와 선택자(selectors) 개념을 사용합니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { RecoilRoot, atom, useRecoilState } from 'recoil';

// 원자(atom) 정의
const counterState = atom({
  key: 'counterState',
  default: 0
});

// 컴포넌트에서 사용
function App() {
  return (
    &amp;lt;RecoilRoot&amp;gt;
      &amp;lt;Counter /&amp;gt;
    &amp;lt;/RecoilRoot&amp;gt;
  );
}

function Counter() {
  const [count, setCount] = useRecoilState(counterState);
  
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;p&amp;gt;Count: {count}&amp;lt;/p&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setCount(count + 1)}&amp;gt;+&amp;lt;/button&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setCount(count - 1)}&amp;gt;-&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;Zustand&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Zustand는 간결한 API를 가진 경량 상태 관리 라이브러리입니다.&lt;/p&gt;
&lt;pre style=&quot;background-color: #f8f8f8; color: #383a42;&quot;&gt;&lt;code&gt;import create from 'zustand';

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

// 컴포넌트에서 사용
function Counter() {
  const { count, increment, decrement } = useStore();
  
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;p&amp;gt;Count: {count}&amp;lt;/p&amp;gt;
      &amp;lt;button onClick={increment}&amp;gt;+&amp;lt;/button&amp;gt;
      &amp;lt;button onClick={decrement}&amp;gt;-&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) Context API vs 외부 라이브러리 비교&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특징 Context API 외부 상태 관리 라이브러리&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;낮음 (React 내장)&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;없음&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;중간~많음 (Redux &amp;gt; MobX/Recoil &amp;gt; Zustand)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;확장성&lt;/td&gt;
&lt;td&gt;제한적&lt;/td&gt;
&lt;td&gt;높음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;외부 상태 관리 라이브러리가 더 적합한 경우&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;복잡한 상태 로직을 처리할 때&lt;/li&gt;
&lt;li&gt;많은 컴포넌트가 동일한 상태에 접근해야 할 때&lt;/li&gt;
&lt;li&gt;성능 최적화가 중요할 때&lt;/li&gt;
&lt;li&gt;시간 여행 디버깅과 같은 고급 개발자 도구가 필요할 때&lt;/li&gt;
&lt;li&gt;로깅, 지속성, 비동기 작업과 같은 미들웨어 기능이 필요할 때&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Context API가 더 적합한 경우&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;간단한 전역 상태를 관리할 때&lt;/li&gt;
&lt;li&gt;컴포넌트 트리 깊숙한 곳에 props를 전달하는 것을 피하고 싶을 때&lt;/li&gt;
&lt;li&gt;추가 라이브러리 없이 React만으로 해결하고 싶을 때&lt;/li&gt;
&lt;li&gt;앱의 규모가 작거나 상태 변경이 자주 일어나지 않을 때&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근에는 Redux의 복잡성을 줄인 Redux Toolkit과 같은 도구나, Zustand와 같은 간결한 API를 가진 라이브러리가 인기를 얻고 있어 외부 라이브러리 사용의 진입 장벽이 낮아지고 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/React</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/38</guid>
      <comments>https://dreamyard.tistory.com/entry/React-React-%EC%83%81%ED%83%9C%EC%9D%98-%EC%A0%95%EC%9D%98-%EB%B0%8F-%EB%B6%84%EB%A5%98-%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC-%EC%9E%98%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95#entry38comment</comments>
      <pubDate>Tue, 4 Mar 2025 00:42:36 +0900</pubDate>
    </item>
    <item>
      <title>[TypeScript] TypeScript와 React Hook (2) - useRef, custom Hook (커스텀 훅)</title>
      <link>https://dreamyard.tistory.com/entry/React-useRef-custom-Hook-%EC%BB%A4%EC%8A%A4%ED%85%80-%ED%9B%85</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;React useRef 훅와 필요에 따라 설정해 사용하는 custom Hook에 대해 정리해보았습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1️⃣ useRef&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트 어플리케이션에서 &amp;lt;input /&amp;gt; 요소에 포커스를 설정하거나 특정 컴포넌트의 위치로 스크롤 하는 등 &lt;u&gt;DOM을 직접 선택해야하는 경우 사용&lt;/u&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;● useRef의 타입 정의&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입 정의를 보면 useRef는 MutableRefObejct 또는 RefObejct를 반환합니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;// useRef의 타입 정의 3가지
function useRef&amp;lt;T&amp;gt;(initialValue: T): MutableRefObject&amp;lt;T&amp;gt;;  //   일반적인 값을 저장할 때
function useRef&amp;lt;T&amp;gt;(initialValue: T|null): RefObject&amp;lt;T&amp;gt;;    //   DOM 요소를 참조할 때
function useRef&amp;lt;T = undefined&amp;gt;(): MutableRefObject&amp;lt;T|undefined&amp;gt;;  //   초기값 없이 사용할 때&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;○ MutableRefObejct&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;current의 값을 변경할 수 있습니다.&lt;/u&gt; 만일 useRef의 제네릭에 HTMLInputElement | null 타입을 넣어주었다면, 해당 useRef는 &lt;u&gt;첫번째 타입 정의&lt;/u&gt;를 따를 것이고, 이 경우, MutableObject의 current는 변경할 수 있는 값이 되어 ref.current의 값이 바뀌는 사이드 이펙트가 발생할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;○ RefObject&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;current의 값을 변경할 수 없습니다. (readonly)&lt;/u&gt; useRef의 제네릭으로 HTMLInputElement를 넣고, 초기 인자에 null을 넣을 경우 useRef의 &lt;u&gt;두번째 타입 정의&lt;/u&gt;를 따르게 되는데, 이때는 RefObejct를 반환하여 ref.current의 값을 임의로 변경할 수 없게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;○&amp;nbsp;MutableRefObject vs RefObject 차이점&lt;/h4&gt;
&lt;pre id=&quot;code_1740935239090&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// MutableRefObject - current 값을 변경할 수 있음
const countRef = useRef&amp;lt;number&amp;gt;(0);
countRef.current = 1;  // ✅ 가능: current 값 변경 가능

// RefObject - current 값이 readonly
const inputRef = useRef&amp;lt;HTMLInputElement&amp;gt;(null);
inputRef.current = someElement;  // ❌ 오류: current는 readonly&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2️⃣ forwardRef : 자식 요소에 ref 전달하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트 컴포넌트에서 &lt;u&gt;ref를 prop으로 전달하기 위해서는 forwardRef를 사용&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;※ ref 라는 속성의 이름은 리액트에서 DOM 요소에 접근 이라는 특수한 목적을 사용되므로, props를 넘겨주는 방식으로는 자식요소에 전달될 수 없습니다. ref라는 이름 말고 inputRef 등의 이름을 사용하면 forwardRef를 사용하지 않아도 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;●&amp;nbsp;forwardRef의 타입정의&lt;/h3&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;// forwardRef의 타입 정의
function forwardRef&amp;lt;T, P = {}&amp;gt;(
  render: ForwardRefRenderFunction&amp;lt;T, P&amp;gt;  //   콜백 함수 (컴포넌트)
): ForwardRefExoticComponent&amp;lt;PropsWithoutRef&amp;lt;P&amp;gt; &amp;amp; RefAttributes&amp;lt;T&amp;gt;&amp;gt;;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;●&amp;nbsp;ForwardRefRenderFunction의 타입 정의&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;forwardRef에 인자로 넘겨주는 콜백함수입니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;// ForwardRefRenderFunction 의 타입 정의
interface ForwardRefRenderFunction&amp;lt;T, P = {}&amp;gt; {
  (props: P, ref: ForwardedRef&amp;lt;T&amp;gt;): ReactElement | null;  //   props와 ref를 매개변수로 받음
  displayName?: string;
  defaultProps?: never;
  propTypes?: never;
}&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;ForwardRefRenderFunction는 2개의 타입 매개변수 T와 P를 받고, P는 일반적인 리액트 컴포넌트에서 자식 컴포넌트로 넘겨주는 props 타입을, T는 ref로 전달하려는 요소의 타입을 나타냅니다. &lt;u&gt;이 부분에서 주목할 점은 ref의 타입이 T를 래핑한 형태인 ForwardRef&amp;lt;T&amp;gt;라는 것입니다.&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;// ForwardedRef&amp;lt;T&amp;gt;의 타입 정의
type ForwardedRef&amp;lt;T&amp;gt; = 
  | ((instance: T | null) =&amp;gt; void)  //   콜백 함수
  | MutableRefObject&amp;lt;T | null&amp;gt;      //   useRef로 생성된 객체
  | null;                           //   ref가 없을 경우&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;useRef의 반환타입이 MutableRefObejct&amp;lt;T&amp;gt; 또는 RefObject&amp;lt;T&amp;gt;인 반면에, &lt;u&gt;ForwardedRef에는 오직 MutableRefObject만 들어올 수 있습니다.&lt;/u&gt; MutableRefObejct가 RefObject보다 더 넓은 범위 타입을 가지므로, 부모 컴포넌트에서 ref를 어떻게 선언했는지와 관계없이 자식 컴포넌트가 해당 ref를 수용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;●&amp;nbsp;useImperativeHandle&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ForwardRefRenderFunction과 함께 사용할 수 있는 훅으로, 부모 컴포넌트에서 ref를 통해 자식 컴포넌트에서 정의한 커스터마이징된 메서드를 호출할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;○&amp;nbsp;자식 컴포넌트에서 설정하는 코드 예시&lt;/h4&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;// 자식 컴포넌트에서 노출할 메서드 타입 정의
interface CreateFormHandle {
  submit: () =&amp;gt; void;
  reset: () =&amp;gt; void;
}

// 자식 컴포넌트
const CreateForm = forwardRef&amp;lt;CreateFormHandle, CreateFormProps&amp;gt;((props, ref) =&amp;gt; {
  //   제네릭: &amp;lt;노출할 메서드 타입, props 타입&amp;gt;
  const { onSubmit } = props;
  const [formData, setFormData] = useState({
    name: '',
    email: '',
  });

  // 폼 제출 처리 함수
  const handleSubmit = useCallback(() =&amp;gt; {
    onSubmit(formData);
  }, [formData, onSubmit]);

  // 폼 초기화 함수
  const handleReset = useCallback(() =&amp;gt; {
    setFormData({ name: '', email: '' });
  }, []);

  // ref를 통해 부모에게 노출할 메서드 정의
  useImperativeHandle(ref, () =&amp;gt; ({
    submit: handleSubmit,  //   외부에서 호출할 메서드
    reset: handleReset,    //   외부에서 호출할 메서드
  }));

  return (
    &amp;lt;form&amp;gt;
      &amp;lt;input
        type=&quot;text&quot;
        value={formData.name}
        onChange={(e) =&amp;gt; setFormData({ ...formData, name: e.target.value })}
        placeholder=&quot;이름&quot;
      /&amp;gt;
      &amp;lt;input
        type=&quot;email&quot;
        value={formData.email}
        onChange={(e) =&amp;gt; setFormData({ ...formData, email: e.target.value })}
        placeholder=&quot;이메일&quot;
      /&amp;gt;
    &amp;lt;/form&amp;gt;
  );
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;○&amp;nbsp;부모 컴포넌트에서 사용하는 코드 예시&lt;/h4&gt;
&lt;pre id=&quot;code_1740935742446&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 부모 컴포넌트에서 사용
const ParentComponent = () =&amp;gt; {
  const formRef = useRef&amp;lt;CreateFormHandle&amp;gt;(null);  //   자식 컴포넌트의 메서드 타입을 지정
  
  const handleSaveClick = () =&amp;gt; {
    // 자식 컴포넌트의 submit 메서드 호출
    formRef.current?.submit();  //   옵셔널 체이닝으로 안전하게 호출
  };
  
  const handleResetClick = () =&amp;gt; {
    // 자식 컴포넌트의 reset 메서드 호출
    formRef.current?.reset();  //   옵셔널 체이닝으로 안전하게 호출
  };
  
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;CreateForm ref={formRef} onSubmit={(data) =&amp;gt; console.log(data)} /&amp;gt;
      &amp;lt;button onClick={handleSaveClick}&amp;gt;저장&amp;lt;/button&amp;gt;
      &amp;lt;button onClick={handleResetClick}&amp;gt;초기화&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예시처럼 자식 컴포넌트에서는 ref와 정의된 CreateFormHandle을 통해 부모 컴포넌트에서 호출할 수 있는 함수를 생성하고, 부모 컴포넌트에서는 다음처럼 current.submit()를 사용하여 자식 컴포넌트의 특정 메서드를 실행할 수 있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3️⃣ useRef의 여러가지 특성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useRef는 자식 컴포넌트를 저장하는 변수로 사용하는 것 뿐 아니라 아래와 같은 방식으로도 사용할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;useRef로 관리되는 변수는 값이 바뀌어도 컴포넌트 리렌더링이 발생하지 않는데, 이 특성을 이용해 상태가 변경되도 불필요한 리렌더링을 피할 수 있습니다.&lt;/li&gt;
&lt;li&gt;리액트 컴포넌트 상태는 상태 변경 함수를 호출하고 렌더링된 이후에 업데이트된 상태를 조회할 수 있습니다. 반면 useRef로 관리되는 변수는 값 설정 후 즉시 조회할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;●&amp;nbsp;예시 코드&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아래 코드는 자동 재생 기능이 있는 이미지 슬라이더(carousel) 컴포넌트 입니다. 여기서 isAutoPlayPause는 현재 자동 재생이 일시 정지되었는지 확인하는 ref 입니다. 이 변수는 렌더링에 영향을 미치지 않으며, 값이 변경되더라도 다시 렌더링을 기다릴 필요 없이 사용할 수 있어야 합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;isAutoPlayPause.current에 null이 아닌 값을 할당해서 마치 변수처럼 활용할 수도 있습니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;import React, { useRef, useState, useEffect } from 'react';

const AutoPlaySlider = () =&amp;gt; {
  const [currentSlide, setCurrentSlide] = useState(0);
  const [isPlaying, setIsPlaying] = useState(true);
  
  //   리렌더링을 발생시키지 않는 값을 저장하는 ref
  const isAutoPlayPause = useRef&amp;lt;boolean&amp;gt;(false);
  
  //   타이머 ID를 저장하는 ref - 이것도 UI에 직접 영향을 주지 않는 값
  const timerRef = useRef&amp;lt;NodeJS.Timeout | null&amp;gt;(null);
  
  // 슬라이드를 다음으로 넘기는 함수
  const nextSlide = () =&amp;gt; {
    setCurrentSlide((prev) =&amp;gt; (prev + 1) % 5); // 총 5장의 슬라이드가 있다고 가정
  };
  
  // 자동 재생 시작
  const startAutoPlay = () =&amp;gt; {
    //   기존 타이머가 있으면 제거 (ref.current 직접 접근)
    if (timerRef.current) clearInterval(timerRef.current);
    
    //   새 타이머 ID를 ref에 저장 (UI와 무관한 값)
    timerRef.current = setInterval(() =&amp;gt; {
      //   ref 값을 조건으로 사용 (상태 업데이트 없이 즉시 최신 값 참조)
      if (!isAutoPlayPause.current) {
        nextSlide();
      }
    }, 3000);
  };
  
  // 마우스 오버 시 자동 재생 일시 정지
  const handleMouseEnter = () =&amp;gt; {
    //   setState 없이 값을 즉시 변경 (리렌더링 발생 안 함)
    isAutoPlayPause.current = true;
    
    //   변경 후 즉시 새 값에 접근 가능 (setState는 비동기적으로 업데이트됨)
    console.log('자동 재생 일시 정지:', isAutoPlayPause.current); // true 바로 출력
  };
  
  // 마우스 아웃 시 자동 재생 재개
  const handleMouseLeave = () =&amp;gt; {
    //   다시 값을 즉시 변경 (리렌더링 없음)
    isAutoPlayPause.current = false;
    console.log('자동 재생 재개:', isAutoPlayPause.current); // false 바로 출력
  };
  
  // 재생/일시정지 토글
  const togglePlay = () =&amp;gt; {
    //   이것은 상태 변경 - 리렌더링 발생
    setIsPlaying((prev) =&amp;gt; !prev);
  };
  
  // isPlaying 상태가 변경될 때 자동 재생 시작 또는 중지
  useEffect(() =&amp;gt; {
    if (isPlaying) {
      startAutoPlay();
    } else {
      //   ref에 저장된 타이머 ID에 접근해서 정리
      if (timerRef.current) {
        clearInterval(timerRef.current);
        timerRef.current = null;
      }
    }
    
    // 컴포넌트 언마운트 시 타이머 정리
    return () =&amp;gt; {
      //   cleanup 함수에서도 ref의 최신 값에 접근 가능
      if (timerRef.current) {
        clearInterval(timerRef.current);
      }
    };
  }, [isPlaying]); //   isPlaying이 변경될 때만 실행
  
  return (
    &amp;lt;div 
      className=&quot;slider-container&quot;
      onMouseEnter={handleMouseEnter}
      onMouseLeave={handleMouseLeave}
    &amp;gt;
      &amp;lt;div className=&quot;slides&quot;&amp;gt;
        {/* 슬라이드 내용 */}
        &amp;lt;div className=&quot;slide&quot; style={{ transform: `translateX(-${currentSlide * 100}%)` }}&amp;gt;
          {[0, 1, 2, 3, 4].map((index) =&amp;gt; (
            &amp;lt;div key={index} className=&quot;slide-item&quot;&amp;gt;
              슬라이드 {index + 1}
            &amp;lt;/div&amp;gt;
          ))}
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
      
      &amp;lt;button onClick={togglePlay}&amp;gt;
        {isPlaying ? '일시정지' : '재생'} {/*   isPlaying은 상태로 UI에 표시 */}
      &amp;lt;/button&amp;gt;
      
      &amp;lt;div className=&quot;indicators&quot;&amp;gt;
        {[0, 1, 2, 3, 4].map((index) =&amp;gt; (
          &amp;lt;button 
            key={index}
            className={currentSlide === index ? 'active' : ''}
            onClick={() =&amp;gt; setCurrentSlide(index)}
          /&amp;gt;
        ))}
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default AutoPlaySlider;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4️⃣ 리액트 훅의 규칙&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트 훅은 호출 순서에 의존하기 때문에, 리액트 훅을 안전하기 사용하기 위해서는 아래 2가지 규칙을 지켜야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트의 모든 컴포넌트 렌더링에서 훅의 순서가 항상 동일하게 유지되어야 하며, 이를 통해 항상 동일한 컴포넌트 렌더링이 보장 됩니다. 이 규칙들을 준수함으로써 컴포넌트의 모든 상태 관련 로직을 좀 더 명확히 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. 훅은 항상 최상위 레벨에서 호출되어야 합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건문, 반복문, 중첩함수, 클래스 등의 내부에서는 훅을 호출하지 않아야 하며, 반환문으로 함수 컴포넌트가 종료되거나 조건문 또는 변수에 따라 반복문 등으로 훅의 호출여부가 결정되어서는 안됩니다. 이를 유의해야 useState나 useEffect가 여러번 호출되더라도 훅의 상태를 올바르게 유지할 수 있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. 훅은 항상 함수 컴포넌트나 커스텀 훅 등의 리액트 컴포넌트 내에서 호출되어야 합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5️⃣ 커스텀 훅&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트에서는 사용자 정의 훅 생성도 가능합니다. 커스텀 훅은 리액트 컴포넌트 내에서만 사용할 수 있으며, 이름은 반드시 use로 시작되어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;●&amp;nbsp;useInput&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 일반적인 커스텀 훅인 useInput을 살펴보겠습니다. useInput은 인자로 받은 초깃값을 useState로 관리하며, 해당 값을 수정할 수 있는 onChange함수를 input 값과 함께 반환하는 훅입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;useInput 예시 코드&lt;/h4&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// useInput 예시 코드
import { useState, useCallback } from 'react';

function useInput(initialValue) {
  const [value, setValue] = useState(initialValue);
  
  const onChange = useCallback((e) =&amp;gt; {
    setValue(e.target.value);
  }, []);
  
  const reset = useCallback(() =&amp;gt; {
    setValue(initialValue);
  }, [initialValue]);
  
  return { value, onChange, reset };
}

export default useInput;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;useInput 사용 예시 코드&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드에서 {...name} 과 같이 스프레드 연산자로 name의 모든 속성을 전달하고 있지만, HTML input 요소가 인식하는 속성들만 실제로 적용되고 그 외 속성(reset)은 무시됩니다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;import React from 'react';
import useInput from './useInput';

function SignupForm() {
  const name = useInput('');
  const email = useInput('');
  const password = useInput('');
  
  const handleSubmit = (e) =&amp;gt; {
    e.preventDefault();
    
    console.log({
      name: name.value,
      email: email.value,
      password: password.value
    });
    
    // 폼 초기화
    name.reset();
    email.reset();
    password.reset();
  };
  
  return (
    &amp;lt;form onSubmit={handleSubmit}&amp;gt;
      &amp;lt;div&amp;gt;
        &amp;lt;label&amp;gt;이름&amp;lt;/label&amp;gt;
        &amp;lt;input type=&quot;text&quot; {...name} placeholder=&quot;이름을 입력하세요&quot; /&amp;gt;
      &amp;lt;/div&amp;gt;
      
      &amp;lt;div&amp;gt;
        &amp;lt;label&amp;gt;이메일&amp;lt;/label&amp;gt;
        &amp;lt;input type=&quot;email&quot; {...email} placeholder=&quot;이메일을 입력하세요&quot; /&amp;gt;
      &amp;lt;/div&amp;gt;
      
      &amp;lt;div&amp;gt;
        &amp;lt;label&amp;gt;비밀번호&amp;lt;/label&amp;gt;
        &amp;lt;input type=&quot;password&quot; {...password} placeholder=&quot;비밀번호를 입력하세요&quot; /&amp;gt;
      &amp;lt;/div&amp;gt;
      
      &amp;lt;button type=&quot;submit&quot;&amp;gt;가입하기&amp;lt;/button&amp;gt;
    &amp;lt;/form&amp;gt;
  );
}

export default SignupForm;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;●&amp;nbsp;타입스크립트로 커스텀 훅 강화하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useInput 예시 코드 파일의 확장자를 .ts로 변환하면 아래와 같은 컴파일 에러가 뜹니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;// 기존 코드에 대해 .ts 변환 시 뜨는 오류 코드 
function useInput(initialValue) {
                  ^^^^^^^^^^^^ Parameter 'initialValue' implicitly has an 'any' type.

const onChange = useCallback((e) =&amp;gt; {
                             ^^^ Parameter 'e' implicitly has an 'any' type.
&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;컴파일 오류의 원인은 useInput 함수의 인자로 넣어준 initialValue와 onChange 함수의 인자로 넣어준 e의 타입이 지정되지 않았기 때문에 발생하는 에러로 두 군데 타입을 명시적으로 정의하면 해결 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// 해결한 코드
import { useState, useCallback, ChangeEvent } from 'react';

function useInput&amp;lt;T&amp;gt;(initialValue: T) {
  const [value, setValue] = useState&amp;lt;T&amp;gt;(initialValue);
  
  const onChange = useCallback((e: ChangeEvent&amp;lt;HTMLInputElement&amp;gt;) =&amp;gt; {
    setValue(e.target.value as unknown as T);
  }, []);
  
  const reset = useCallback(() =&amp;gt; {
    setValue(initialValue);
  }, [initialValue]);
  
  return { value, onChange, reset };
}

export default useInput;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/TypeScript</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/37</guid>
      <comments>https://dreamyard.tistory.com/entry/React-useRef-custom-Hook-%EC%BB%A4%EC%8A%A4%ED%85%80-%ED%9B%85#entry37comment</comments>
      <pubDate>Mon, 3 Mar 2025 02:27:33 +0900</pubDate>
    </item>
    <item>
      <title>[TypeScript] TypeScript와 React Hook (1) - useState, useEffect,  useLayoutEffect, useMemo, useCallback</title>
      <link>https://dreamyard.tistory.com/entry/TypeScript-TypeScript%EC%99%80-React-Hook-1-useState-useEffect-useLayoutEffect-useMemo-useCallback</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;TypeScript의 개념과 접목해 React Hook에 대해 정리해보겠습니다. 먼저, React Hook은 왜 생겨나게 된 것일까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론부터 말씀드리면, &lt;u&gt;리액트 16.8부터는&lt;span&gt; &lt;/span&gt;&lt;/u&gt;&lt;u&gt;기존 클래스 컴포넌트의 단점을 해결&lt;/u&gt;하기 위해 탄생했습니다. 기존 클래스 컴포넌트는 아래와 같은 단점을 가집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1) 컴포넌트 간 상태 로직 재사용이 어렵다
2) 생명주기 메서드에 서로 관련없는 로직들이 얽혀 코드의 복잡성이 증가된다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예시 코드를 보며 클래스 컴포넌트의 단점을 좀 더 자세히 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;// 기존 클래스 컴포넌트의 예시
class UserDashboard extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      userData: null,
      notifications: [],
      themeMode: 'light'
    };
  }

  componentDidMount() { //  
    // 사용자 데이터 로딩
    this.fetchUserData();
    // 알림 설정
    this.setupNotifications();
    // 테마 설정
    this.setupTheme();
  }

  componentWillUnmount() {  //  
    // 여러 정리 작업들이 한 곳에 모여있음
    this.clearNotifications();
    this.removeThemeListener();
    this.disconnectUserSession();
  }

  // 메서드들이 여러 곳에 흩어져 있음
  fetchUserData() { ... }
  setupNotifications() { ... }
  setupTheme() { ... }

  render() { ... }
}&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;기존의 클래스 컴포넌트에서는 componentDidMount, componentWillUnmount, componentDidUpdate와 같이 하나의 생명주기 함수에서만 상태 업데이트에 따른 로직을 실행시킬 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 코드는 관련 없는 로직들이 한 생명주기 메서드에 섞여 있어야 했고, 로직 재사용이 어려우며, 코드가 길어지면 길어질 수록 유지보수가 어렵다는 단점이 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 상태를 하나의 함수 내에서 처리하다보니 관심사가 뒤섞이게 되어 상태에 따른 테스트나 잘못 발생한 사이드 이펙트의 디버깅이 어려워졌습니다. 이러한 점은 프로젝트의 규모가 커질수록 불편해졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;componentWillUnmount에선 componentDidMount에서 정의한 컴포넌트가 Dom에서 해제될 때 실행되어야 할 여러 사이 이펙트 함수를 호출하는데, 이는 여러 문제를 야기할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, componentWillUnmount에서 실행되어야 할 코드가 하나 빠졌다면 componentDidMount와 비교해가며 어떤 함수가 빠졌는지 비교하면서 찾아야 하는 상황이 발생할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; 함수 컴포넌트에 리액트 훅을 적용한다면?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 리액트 훅의 도입으로 함수 컴포넌트에서도 클래스 컴포넌트와 같이 컴포넌트 생명주기에 맞춰 로직을 실행할 수 있게 되었고, &lt;u&gt;코드를 재사용하거나 작은 단위로 분할해 테스트하는 것이 용이&lt;/u&gt;해졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞의 코드를 훅을 적용해 정리한 코드를 보며 좀 더 자세히 비교해볼게요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function UserDashboard() {
  // 각각의 관심사별로 상태와 효과를 분리

  // 사용자 데이터 관련
  const [userData, setUserData] = useState(null);
  useEffect(() =&amp;gt; {
    const fetchData = async () =&amp;gt; {
      const data = await fetchUserData();
      setUserData(data);
    };
    fetchData();
  }, []);

  // 알림 관련
  const [notifications, setNotifications] = useState([]);
  useEffect(() =&amp;gt; {
    const notification = setupNotifications();
    return () =&amp;gt; clearNotifications(notification);
  }, []);

  // 테마 관련
  const [themeMode, setThemeMode] = useState('light');
  useEffect(() =&amp;gt; {
    const theme = setupTheme();
    return () =&amp;gt; removeThemeListener(theme);
  }, []);

  return (...);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1️⃣ useState&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트 상태 관리를 위한 훅 입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;➖타입 정의&lt;/h3&gt;
&lt;pre class=&quot;openscad&quot;&gt;&lt;code&gt;function useState&amp;lt;S&amp;gt;(
  initialState: S | (() =&amp;gt; S)
): [S, Dispatch&amp;lt;SetStateAction&amp;lt;S&amp;gt;&amp;gt;];
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;S: 상태의 타입을 나타내는 제네릭&lt;/li&gt;
&lt;li&gt;initialState: 초기 상태값 또는 초기 상태를 반환하는 함수&lt;/li&gt;
&lt;li&gt;반환값: [상태값, 상태 업데이트 함수]를 포함하는 튜플&lt;/li&gt;
&lt;li&gt;Dispatch&amp;lt;SetStateAction&amp;lt;S&amp;gt;&amp;gt;: 상태 업데이트 함수의 타입&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;➖useState에 TypeScript를 사용하면 좋은 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TypeScript를 useState를 적용하면 아래와 같이 &lt;u&gt;필수 데이터의 누락을 방지해 실수를 방지&lt;/u&gt;하고, &lt;u&gt;잘못된 타입의 데이터가 입력되는 것을 컴파일 단계에서 방지&lt;/u&gt;함으로서 타입 안정성을 확보할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;xquery&quot;&gt;&lt;code&gt;interface User {
  name: string;
  age: number;
  email: string;
}

const [user, setUser] = useState&amp;lt;User&amp;gt;({
  name: '',
  age: 0,
  email: ''
});

// ❌ 아래 코드는 컴파일 에러 발생
setUser({
  name: 'John'  // Property 'age' is missing
                // Property 'email' is missing
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2️⃣ useEffect&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;렌더링 이후 어떤 일을 수행해야하는 지 알려주기 위해 useEffect&lt;/u&gt;를 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;➖타입 정의&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useEffect의 콜백함수에는 &lt;u&gt;비동기 함수가 들어갈 수 없습니다.&lt;/u&gt; useEffect에서 비동기함수를 호출할 수 있다면 &lt;b&gt;경쟁 상태*&lt;/b&gt;를 불러일으킬 수 있기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;경쟁상태(Race Condition)&lt;/b&gt;란?&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;멀티스레딩 환경에서 동시에 여러 프로세스 또는 스레드가 공유된 자원에 접근하려고 할 때 발생할 수 있는 문제로, &lt;u&gt;실행 순서나 타이밍 예측이 불가해져 프로그램 동작이 원치않는 방향&lt;/u&gt;으로 흘러갈 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;function useEffect(
  effect: () =&amp;gt; void | (() =&amp;gt; void | undefined),
  deps?: ReadonlyArray&amp;lt;any&amp;gt;
): void;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;effect: 실행할 효과 함수&lt;/li&gt;
&lt;li&gt;deps: 의존성 배열 (선택적)&lt;/li&gt;
&lt;li&gt;ReadonlyArray&amp;lt;any&amp;gt;: 읽기 전용 배열 타입&lt;/li&gt;
&lt;li&gt;반환값: void (없음)&lt;/li&gt;
&lt;li&gt;클린업 함수를 반환할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;➖useEffect 실행 조건&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useEffect의 &lt;u&gt;두번째 인자인 deps는 옵셔널하게 제공되고, deps 배열의 원소가 변경되면 실행한다&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;deps에 숫자, 문자열 같은 타입스크립트의 기본 자료형이 아닌 객체나 배열을 넣을 때는 주의해야하는데,useEffect는 deps가 변경되었는지를 얕은 비교로만 판단하므로, 실제 객체 값이 바뀌지 않았더라도 &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;이를 위해선 실제 사용하는 값을 useEffect의 deps에 사용해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;➖deps가 빈 배열일 땐 ...&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useEffect는 Destructor를 반환하는데, 이것은 컴포넌트가 언마운트 될 때 실행되는 함수입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, &lt;u&gt;deps가 빈 배열이라면 useEffect의 콜백함수는 컴포넌트가 처음 렌더링 될 때만 실행&lt;/u&gt;되며 &lt;u&gt;Destructor (클린업 함수라고도 함)는 컴포넌트가 마운트 해제될 때 실행&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;하지만, deps 배열이 존재한다면 &lt;u&gt;배열의 값이 변경될 때마다 Destructor가 실행&lt;/u&gt;됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;개념 정리 ( 얕은 비교, 클린업 함수)&lt;/h4&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;얕은 비교 (shallow compare)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체나 배열과 같은 복합 데이터 타입 값을 비교할 때, 내부의 각 요소나 속성을 재귀적으로 비교하지 않고, 해당 값들의 참조나 기본적인 타입 값만을 간단하게 비교하는 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ 이러한 특징은 useMemo나 useCallback과 같은 다른 훅에서도 동일하게 적용됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;클린업 함수&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;컴포넌트 해제 전에 정리 작업을 수행하기 위한 함수 입니다.&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;➖사용 예시&lt;/h3&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;function DataFetcher() {
  const [data, setData] = useState&amp;lt;string[]&amp;gt;([]);

  // 1. 기본적인 데이터 fetching
  useEffect(() =&amp;gt; {
    const fetchData = async () =&amp;gt; {  // 별도의 async 함수 선언
      try {
        const response = await fetch('api/data');
        const json = await response.json();
        setData(json);
      } catch (error) {
        console.error('데이터 로딩 실패:', error);
      }
    };

    fetchData();  // 함수 호출

    
    return () =&amp;gt; {//   클린업 함수 
      // 정리 작업 수행
      setData([]); // 데이터 초기화
    };
  }, []); //   빈 deps 배열 = 마운트 시 1회만 실행

  // 2. deps 배열 사용 시 주의사항
  const [user, setUser] = useState({ id: 1, name: 'John' });

  //   잘못된 사용 - 객체를 직접 deps에 넣음
  useEffect(() =&amp;gt; {
    console.log(user);
  }, [user]); // user 객체의 참조가 변경될 때마다 실행

  //   올바른 사용 - 실제 사용하는 값만 deps에 넣음
  useEffect(() =&amp;gt; {
    console.log(user.name);
  }, [user.name]); // name이 변경될 때만 실행

  return &amp;lt;div&amp;gt;{/* 렌더링 로직 */}&amp;lt;/div&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3️⃣ useLayoutEffect&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useLayoutEffect의 타입 정의는 &lt;u&gt;useEffect와 동일하고, 역할의 차이&lt;/u&gt;만 있습니다.&amp;nbsp; 좀 더 정확히는 콜백 함수의 실행 시점이 다릅니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;➖타입 정의&lt;/h3&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;// useLayoutEffect 타입 정의
function useLayoutEffect(
 effect: () =&amp;gt; void | (() =&amp;gt; void),
 deps?: ReadonlyArray&amp;lt;any&amp;gt;
): void;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;useEffect와 동일한 타입 구조&lt;/li&gt;
&lt;li&gt;실행 시점만 다름 &lt;u&gt;(DOM 업데이트 직후, 브라우저 페인팅 전)&lt;/u&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useEffect는 앞서 살펴본 componentDidUpdate와 같은 기존 생명주기 함수와는 다르게, &lt;u&gt;레이아웃 배치와 화면 렌더링이 모두 완료된 이후에 실행&lt;/u&gt;됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;⭐ 꼭 알아두기 ! useEffect와 useLayoutEffect의 실행순서&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) React가 가상 DOM을 업데이트&lt;br /&gt;2) DOM이 업데이트됨&lt;br /&gt;3) useLayoutEffect 실행 (동기적)&lt;br /&gt;4) 브라우저 화면 그리기&lt;br /&gt;5) useEffect 실행 (비동기적)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;➖사용 예시&lt;/h3&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;function NameDisplay({ userId }: { userId: string }) {
  const [name, setName] = useState('');

  // ❌ useEffect 사용 시
  // 처음에 빈 이름이 표시되었다가 데이터를 가져온 후 업데이트됨
  useEffect(() =&amp;gt; {
    const fetchName = async () =&amp;gt; {
      const data = await fetchUserName(userId);
      setName(data.name);
    };
    fetchName();
  }, [userId]);

  // ✅ useLayoutEffect 사용 시
  // DOM에 반영되기 전에 데이터를 가져와서 빈 이름이 표시되는 것을 방지
  useLayoutEffect(() =&amp;gt; {
    const fetchName = async () =&amp;gt; {
      const data = await fetchUserName(userId);
      setName(data.name);
    };
    fetchName();
  }, [userId]);

  return (
    &amp;lt;div className=&quot;name-container&quot;&amp;gt;
      &amp;lt;h2&amp;gt;{name || 'Loading...'}&amp;lt;/h2&amp;gt;
    &amp;lt;/div&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;하지만, useLayoutEffect 를 사용하면 화면에 해당 &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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4️⃣ useMemo와 useCallback&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 훅 모두 &lt;u&gt;이전에 생성된 값 또는 함수를 기억하며, 동일한 값과 함수를 반복해서 생성하지 않도록 해주는 훅&lt;/u&gt;입니다.&lt;br /&gt;어떤 값을 계산하는데 오래걸릴 때나 렌더링이 자주 발생하는 form에서 유용하게 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;➖타입 정의&lt;/h3&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;Copyfunction useMemo&amp;lt;T&amp;gt;(
  factory: () =&amp;gt; T,
  deps: DependencyList
): T;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;T: 메모이제이션할 값의 타입을 나타내는 제네릭&lt;/li&gt;
&lt;li&gt;factory: 계산할 값을 반환하는 함수&lt;/li&gt;
&lt;li&gt;DependencyList: 의존성 배열 (ReadonlyArray&amp;lt;any&amp;gt;의 별칭)&lt;/li&gt;
&lt;li&gt;반환값: 메모이제이션된 값&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1740339495555&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function useCallback&amp;lt;T extends Function&amp;gt;(
  callback: T,
  deps: DependencyList
): T;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;T extends Function: 함수 타입을 나타내는 제네릭&lt;/li&gt;
&lt;li&gt;callback: 메모이제이션할 콜백 함수&lt;/li&gt;
&lt;li&gt;DependencyList: 의존성 배열&lt;/li&gt;
&lt;li&gt;반환값: 메모이제이션된 콜백 함수&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 훅 모두 useEffect와 유사하게 deps 배열을 가지고 있으며, 이 의존성이 변경되면 값을 다시 계산합니다. 얕은 비교를 수행하기 때문에 deps 배열이 변경되지 않았는데도 다시 계산되지 않도록 주의해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로, 의존성 배열이 비어있다면 최초 마운트 시점에 동작합니다. 이렇게 되면, 최초 마운트 시점에 값과 함수가 생성되어 메모리에 저장되고, 이후 렌더링 부터는 동일한 값과 함수가 참조되고 언마운트 시점까지 이를 유지합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 값과 함수를 useMemo와 useCallback을 사용해 과도하게 메모이제이션 하면, 이는 메모리 사용량 증가를 초래해 컴포넌트의 성능 향상을 저해할 수 있습니다. 아래 예시 코드를 통해 자세히 살펴 보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: right;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;※ 메모이제이션 :&lt;/b&gt; &lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;이전에 계산된 값을 저장함으로써 같은 입력에 대한 연산을 다시 수행하지 않도록 최적화 하는 기술&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;➖예시 코드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 훅의 사용 예시와 잘못된, 올바른 사용 예시를 함께 살펴보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function ExpensiveCalculator({ numbers, onResult }) {
  // 1. useMemo 사용 예시
  const sum = useMemo(() =&amp;gt; {
    console.log(&quot;Calculating sum...&quot;); // 디버깅용
    return numbers.reduce((acc, num) =&amp;gt; acc + num, 0);
  }, [numbers]); // numbers가 변경될 때만 재계산

  // 2. useCallback 사용 예시
  const handleClick = useCallback(() =&amp;gt; {
    onResult(sum);
  }, [sum, onResult]); // sum이나 onResult가 변경될 때만 함수 재생성

  // 3. 잘못된 사용 예시
  const badExample = useMemo(() =&amp;gt; {
    return &quot;Hello&quot;; // 단순 문자열은 메모이제이션이 불필요
  }, []);

  // 4. 올바른 사용 예시
  const expensiveValue = useMemo(() =&amp;gt; {
    let result = 0;
    for (let i = 0; i &amp;lt; 1000000; i++) {
      result += complexCalculation(i);
    }
    return result;
  }, []); // 복잡한 계산은 메모이제이션이 유용

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;Sum: {sum}&amp;lt;/h2&amp;gt;
      &amp;lt;button onClick={handleClick}&amp;gt;Send Result&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/TypeScript</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/36</guid>
      <comments>https://dreamyard.tistory.com/entry/TypeScript-TypeScript%EC%99%80-React-Hook-1-useState-useEffect-useLayoutEffect-useMemo-useCallback#entry36comment</comments>
      <pubDate>Mon, 24 Feb 2025 04:53:15 +0900</pubDate>
    </item>
    <item>
      <title>[TypeScript] React에서 JSX를 TSX로 변환할 때 알아야 할 핵심 이론들</title>
      <link>https://dreamyard.tistory.com/entry/Typescript-React%EC%97%90%EC%84%9C-JSX%EB%A5%BC-TSX%EB%A1%9C-%EB%B3%80%ED%99%98%ED%95%A0-%EB%95%8C-%EC%95%8C%EC%95%84%EC%95%BC-%ED%95%A0-%ED%95%B5%EC%8B%AC-%EC%9D%B4%EB%A1%A0%EB%93%A4</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;React JSX (JavaScript XML)를 React TSX (TypeScript XML)로 변환해야 할 때, 알아야할 React의 핵심이론들을 정리해보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 Button 컴포넌트의 JSX 버전을 TSX로 변환할 때도, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;아래와 같이&lt;span&gt; 여러 개념에 의한 변환이 필요합니다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1738178980610&quot; class=&quot;typescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;// JSX 버전
const Button = (props) =&amp;gt; {
  return &amp;lt;button onClick={props.onClick}&amp;gt;클릭&amp;lt;/button&amp;gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1738179026820&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// TSX 버전
// 1. Props 타입 정의 - React.ComponentPropsWithoutRef 활용
type ButtonProps = React.ComponentPropsWithoutRef&amp;lt;&quot;button&quot;&amp;gt; &amp;amp; {
  customProp?: string;
}

// 2. FC vs 일반 함수 선언 - 함수형 컴포넌트 타입 활용
const Button: React.FC&amp;lt;ButtonProps&amp;gt; = (props) =&amp;gt; {
  return &amp;lt;button onClick={props.onClick}&amp;gt;클릭&amp;lt;/button&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;JSX를 TSX로 변환하다보면 발생하는 주요 경우들을 정리하면, 아래와 같은 경우들이 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 함수형 컴포넌트의 타입 지정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 컴포넌트 Props의 타입 지정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- HTML 엘리먼트의 기본 속성들을 타입으로 가져와야 할 때 ( 관련 개념 : ComponentPropsWithoutRef) &lt;br /&gt;- children prop을 다룰 때 ( &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;관련 개념 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; ReactNode 타입 등) &lt;br /&gt;3) 이벤트 핸들러 타입을 지정할 때 ( &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;관련 개념 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; ChangeEventHandler&amp;nbsp;등) &lt;br /&gt;4) 재사용 가능한 제네릭 컴포넌트를 만들 때 ( &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;관련 개념 :&lt;span&gt; (예) &lt;/span&gt;&lt;/span&gt;Select&amp;nbsp;컴포넌트)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5) 타입 유틸리티 활용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각의 개념에 대해서 좀 더 상세히 정리하고 설명해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1) 함수형 컴포넌트의 타입 지정&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React.FC와 React.VFC는 리액트에서 함수 컴포넌트의 타입 지정을 위해 제공되는 타입 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;React.FC(FunctionComponent)&lt;/span&gt; &lt;br /&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;암묵적으로 children을 포함하고 있으므로, children을 해당 컴포넌트에서 사용하지 않더라도 children props를 허용&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;React.VFC(VoidFunctionComponent)&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;children props가 필요하지 않은 컴포넌트의 더 정확한 타입 지정을 위해 사용&lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React.FC 등장 이후 @types/react 16.9.4 버전에서 React.VFC 타입이 추가되었지만, 리액트 v18로 넘어오면서 React.VFC가 삭제(deprecated)되고 React.FC에서 children이 사라졌습니다. &lt;b&gt;&lt;u&gt;따라서, 현재는 React.FC를 사용하되 children을 명시적으로 지정하는 방식 또는 props 타입과 반환 타입을 직접 지정하는 형태로 타이핑 해주어야 합니다.&amp;nbsp;&lt;/u&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;React.FC vs 직접 타입 지정&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;최근에는 직접 타입 지정이 좀 더 명시적이고 명확하며, 반환타입 지정에 대한 유연성이 좋기 때문에 좀 더 선호되고 있다고 합니다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;두 방식 코드 비교해보기 &lt;/b&gt;&lt;/h4&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1738175367710&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// React.FC 사용
interface MyComponentProps {
  name: string;
  age: number;
}

const MyComponent: React.FC&amp;lt;MyComponentProps&amp;gt; = ({ name, age }) =&amp;gt; {
  return (
    &amp;lt;div&amp;gt;
      {name} is {age} years old
    &amp;lt;/div&amp;gt;
  );
};


// =========================================================================

// 직접 타입 지정
interface MyComponentProps {
  name: string;
  age: number;
}

const MyComponent = ({ name, age }: MyComponentProps): JSX.Element =&amp;gt; {
  return (
    &amp;lt;div&amp;gt;
      {name} is {age} years old
    &amp;lt;/div&amp;gt;
  );
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2) Props의 타입 지정과 관련된 주요 개념&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) children prop의 타입 지정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;children은 createElement 함수에서 특별히 처리되는 prop 입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;PropsWithChildren&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React에서 제공하는 타입 유틸리티로, 기존 props 타입에 children 속성을 추가해주는 역할을 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1738176530651&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface BoxProps {
  color: string;
  width: number;
}

// BoxProps와 children을 함께 사용
type Props = PropsWithChildren&amp;lt;BoxProps&amp;gt;;

const Box = ({ children, color, width }: Props) =&amp;gt; {
  return (
    &amp;lt;div style={{ color, width }}&amp;gt;
      {children}
    &amp;lt;/div&amp;gt;
  );
};

// 사용
&amp;lt;Box color=&quot;red&quot; width={100}&amp;gt;
  &amp;lt;span&amp;gt;자식 요소&amp;lt;/span&amp;gt;
&amp;lt;/Box&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;@types/react 내부엔 PropsWithChildren이 아래와 같이 정의되어 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1738171391767&quot; class=&quot;typescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;type PropsWithChildren&amp;lt;P = unknown&amp;gt; = P &amp;amp; {
    children?: ReactNode | undefined;
};&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;ReactNode에는 ReactElement, boolean, number 등 여러 타입을 포함하고 있기 때문에 구체적인 타이핑을 위해선 children에 대해서만 따로 타이핑을 해주어야 합니다. 방법은 아래 코드처럼 다른 prop 타입 지정과 동일한 방식으로 할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1738171755161&quot; class=&quot;elm&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;// example 1 
type WelcomeProps = { 
	children: &amp;ldquo;천생연분&amp;rdquo; | &amp;ldquo;더 귀한 분&amp;rdquo; | &amp;ldquo;귀한 분&amp;rdquo; | &amp;ldquo;고마운 분&amp;rdquo;;
};
// example 2 
type WelcomeProps = { 
	children: string;
};
// example 3 
type WelcomeProps = { 
	children: ReactElement;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 함수 컴포넌트의 반환 타입 : React.ReactElement / JSX.Element / React.ReactNode&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 3가지 타입은 함수 컴포넌트의 반환 타입으로, 각각의 타입이 children으로 허용하는 값의 범위가 다르기 때문에 children prop과 연관되어 중요한 개념입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서는 대부분의 경우 가장 유연한 React.ReactNode를 사용하며, 특&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;정 타입의 컴포넌트만 자식으로 받고 싶을 땐 React.ReactElement 사용하고, &lt;/span&gt;정확한 타입 제한이 필요한 경우에는 더 구체적인 타입을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSX.Element의 경우 너무 제한적인 반환 타입 범위와 children prop 타입 안전성이 낮아 현재로서는 잘 사용되지 않는 편이라고 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;React.ReactElement&lt;/h4&gt;
&lt;pre id=&quot;code_1738174625635&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ReactElement의 정의
interface ReactElement&amp;lt;P = any, T extends string | JSXElementConstructor&amp;lt;any&amp;gt; = string | JSXElementConstructor&amp;lt;any&amp;gt;&amp;gt; {
  type: T;
  props: P;
  key: Key | null;
}&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;리액트 컴포넌트를 객체 형태로 저장하기 위한 포맷으로, JSX의 createElement 메서드 호출로 생성된 리액트 엘리먼트를 나타내는 타입 입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JSX.Element 대신 ReactElement를 사용하면, 아래 코드와 같이 원하는 컴포넌트의 props를 ReactElement의 제네릭으로 지정해줄 수 있습니다. 이 방식을 사용하면 Props의 타입 안정성이 확보되고, IDE의 자동완성도 지원되어 보다 편리한 코드 작성이 가능합니다.&amp;nbsp;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1738174793699&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1. JSX.Element 사용
interface Props {
  icon: JSX.Element;
}

const Component = ({ icon }: Props) =&amp;gt; {
  // ❌ icon.props의 타입이 'any'
  const iconSize = icon.props.size;  // 타입 안정성 없음
  return &amp;lt;div&amp;gt;{icon}&amp;lt;/div&amp;gt;;
};

// 2. ReactElement 사용 - 제네릭으로 props 타입 지정
interface IconProps {
  size: number;
  color: string;
}

interface Props {
  icon: React.ReactElement&amp;lt;IconProps&amp;gt;;
}

const Component = ({ icon }: Props) =&amp;gt; {
  // ✅ icon.props의 타입이 IconProps
  const iconSize = icon.props.size;  // 타입 추론 가능
  const iconColor = icon.props.color;  // 자동완성 지원
  return &amp;lt;div&amp;gt;{icon}&amp;lt;/div&amp;gt;;
};

// 사용
&amp;lt;Component icon={&amp;lt;Icon size={24} color=&quot;red&quot; /&amp;gt;} /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;JSX.Element&lt;/h4&gt;
&lt;pre id=&quot;code_1738174589733&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// JSX.Element의 정의
declare global {
  namespace JSX {
    interface Element extends React.ReactElement&amp;lt;any, any&amp;gt; { }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;ReactElement의 특정 타입으로, props와 타입 필드를 any 타입으로 가지는 &lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt; ReactElement 입니다. 때문에 &lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt; ReactElement를 prop으로 전달받아서 &lt;/span&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;render props 패턴*&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;으로 컴포넌트를 구현할 때 유용합니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;render props 패턴은 컴포넌트 간에 값을 공유하기 위해 함수를 props로 전달하는 기법입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1738173852744&quot; class=&quot;gml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;// 1. 기본적인 render props 패턴
interface MouseTrackerProps {
  render: (position: { x: number; y: number }) =&amp;gt; JSX.Element;
}

const MouseTracker = ({ render }: MouseTrackerProps) =&amp;gt; {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  const handleMouseMove = (event: React.MouseEvent) =&amp;gt; {
    setPosition({
      x: event.clientX,
      y: event.clientY
    });
  };

  return (
    &amp;lt;div onMouseMove={handleMouseMove}&amp;gt;
      {render(position)}
    &amp;lt;/div&amp;gt;
  );
};

// 사용 예시
&amp;lt;MouseTracker 
  render={({ x, y }) =&amp;gt; (
    &amp;lt;div&amp;gt;
      마우스 위치 - x: {x}, y: {y}
    &amp;lt;/div&amp;gt;
  )}
/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;React.ReactNode&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ReactElement 외에도 boolean, string, number 등의 여러 타입 포함. 리액트의 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;render 함수*&lt;/b&gt;&lt;/span&gt;가 반환할 수 있는 모든 형태를 담고 있다고 볼 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, &lt;u&gt;&lt;b&gt;어떤 타입이든 children prop으로 지정할 수 있게 하고 싶다면 ReactNode 타입으로 children을 선언해주면 됩니다.&amp;nbsp;&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1738173356556&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ReactNode로 children을 정의하면 다음 모든 경우가 가능합니다
interface ComponentProps {
  children: React.ReactNode;
}

const Component = ({ children }: ComponentProps) =&amp;gt; {
  return &amp;lt;div&amp;gt;{children}&amp;lt;/div&amp;gt;;
};

// 이렇게 다양한 타입을 children으로 전달할 수 있습니다
&amp;lt;Component&amp;gt;Hello World&amp;lt;/Component&amp;gt;              // string
&amp;lt;Component&amp;gt;{42}&amp;lt;/Component&amp;gt;                     // number
&amp;lt;Component&amp;gt;{null}&amp;lt;/Component&amp;gt;                   // null
&amp;lt;Component&amp;gt;{undefined}&amp;lt;/Component&amp;gt;              // undefined
&amp;lt;Component&amp;gt;&amp;lt;div&amp;gt;JSX Element&amp;lt;/div&amp;gt;&amp;lt;/Component&amp;gt;   // JSX
&amp;lt;Component&amp;gt;{true}&amp;lt;/Component&amp;gt;                   // boolean
&amp;lt;Component&amp;gt;{[1, 2, 3]}&amp;lt;/Component&amp;gt;             // array&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;※ 참고로 ReactNode에 포함된 타입 중,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;현재 ReactText, ReactChild, ReactFragment는 deprecated 되었다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(deprecated : 아직 사용은 가능하나, 사용하지 않는 것이 권장되며, 추후 버전에서 완전히 제거될 수 있는 기능)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트의 내장 타입에 해당하는 PropsWithChildren 타입도 ReactNode 타입으로 children을 선언하고 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1738173396082&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type PropsWithChildren&amp;lt;P = unknown&amp;gt; = P &amp;amp; { children?: ReactNode | undefined;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;※ render 함수란 ?&lt;/b&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스 컴포넌트에서 UI를 정의하는 필수 메서드 입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 함수형 컴포넌트가 주로 사용되기 때문에, render 메서드를 직접 사용하는 경우는 감소하고 있다고 합니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1738172712287&quot; class=&quot;scala&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;class MyComponent extends React.Component {
  render() {
    return (
      &amp;lt;div&amp;gt;
        클래스 컴포넌트의 UI는 render 메서드에서 정의됩니다
      &amp;lt;/div&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;함수형 컴포넌트에서는 함수 자체가 render 역할을 하므로 불필요합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1738172770892&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function MyComponent() {
  return (
    &amp;lt;div&amp;gt;
      함수형 컴포넌트는 함수 자체가 render 역할을 합니다
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) HTML 기본 속성 타입 활용하기 : ComponentPropsWithoutRef&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트에서 HTML 태그의 속성 타입을 활용하는 대표적인 방법은 DetailedHTMLProps와 ComponentPropsWithoutRef 타입을 활용하는 것입니다. 하지만, DetailedHTMLProps는 ref속성을 포함하고 있기 때문에, 이 타입을 사용하게 되면 실제로는 동작하지 않는 ref를 받도록 타입이 지정되면서 예기치 않은 에러가 발생할 확률이 높아진다는 단점이 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 상황에 따라서 DetailedHTMLProps를 활용하는 것도 좋지만, HTML 속성을 확장하는 props 설계 시에는 ComponentPropsWithoutRef를 사용하고, ref 속성은 필요에 따라 forwardRef 를 사용해 전달하는 것이 안전합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1738177343870&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ⚠️ 잠재적 문제가 있는 방식
type ButtonProps = React.DetailedHTMLProps
  React.ButtonHTMLAttributes&amp;lt;HTMLButtonElement&amp;gt;,
  HTMLButtonElement
&amp;gt;;

const Button = (props: ButtonProps) =&amp;gt; {
  return &amp;lt;button {...props} /&amp;gt;;
};

// 사용시
&amp;lt;Button ref={someRef} /&amp;gt;  // 타입상으로는 허용되지만 실제로는 ref가 동작하지 않음


// ✅ 안전한 방식
type ButtonProps = React.ComponentPropsWithoutRef&amp;lt;&quot;button&quot;&amp;gt;;

const Button = (props: ButtonProps) =&amp;gt; {
  return &amp;lt;button {...props} /&amp;gt;;
};

// ref가 필요한 경우 forwardRef 사용
const ButtonWithRef = forwardRef&amp;lt;HTMLButtonElement, ButtonProps&amp;gt;((props, ref) =&amp;gt; {
  return &amp;lt;button ref={ref} {...props} /&amp;gt;;
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3) 이벤트 핸들러 타입&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;React에서 이벤트 핸들러 타입은 사용자의 상호작용(클릭, 입력 등)을 처리하는 함수의 타입을 정의하는 방법 입니다. React.ChangeEventHandler, React.MouseEventHandler 등과 같은 타입을 사용하여 이벤트 함수의 타입 안전성을 보장하고, 특히 e.target의 타입을 올바르게 추론할 수 있게 해줍니다. &amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(1) 기본적인 주요 이벤트 핸들러 타입&lt;/h3&gt;
&lt;pre id=&quot;code_1738180771389&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 기본적인 주요 이벤트 핸들러 타입
// 1. Change 이벤트
type ChangeEventHandler = React.ChangeEventHandler&amp;lt;HTMLInputElement&amp;gt;;
// 2. Click 이벤트
type ClickEventHandler = React.MouseEventHandler&amp;lt;HTMLButtonElement&amp;gt;;
// 3. Form 이벤트
type SubmitEventHandler = React.FormEventHandler&amp;lt;HTMLFormElement&amp;gt;;
// 4. Focus 이벤트
type FocusEventHandler = React.FocusEventHandler&amp;lt;HTMLInputElement&amp;gt;;

// 예시 몇 가지 살펴보기
// 1. onChange 이벤트
interface InputProps {
  value: string;
  onChange: React.ChangeEventHandler&amp;lt;HTMLInputElement&amp;gt;;
}

const Input = ({ value, onChange }: InputProps) =&amp;gt; {
  return &amp;lt;input value={value} onChange={onChange} /&amp;gt;;
};

// 사용 예시
&amp;lt;Input 
  value=&quot;hello&quot;
  onChange={(e) =&amp;gt; {
    // e.target.value는 자동으로 string으로 추론됨
    console.log(e.target.value);
  }}
/&amp;gt;

// 2. onClick 이벤트
interface ButtonProps {
  onClick: React.MouseEventHandler&amp;lt;HTMLButtonElement&amp;gt;;
}

const Button = ({ onClick }: ButtonProps) =&amp;gt; {
  return &amp;lt;button onClick={onClick}&amp;gt;클릭&amp;lt;/button&amp;gt;;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(2) 커스텀 이벤트 핸들러&lt;/h3&gt;
&lt;pre id=&quot;code_1738180851071&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 제네릭을 활용한 커스텀 이벤트 핸들러
type CustomChangeHandler&amp;lt;T&amp;gt; = (value: T) =&amp;gt; void;

interface SelectProps&amp;lt;T&amp;gt; {
  value: T;
  onChange: CustomChangeHandler&amp;lt;T&amp;gt;;
}

const Select = &amp;lt;T,&amp;gt;({ value, onChange }: SelectProps&amp;lt;T&amp;gt;) =&amp;gt; {
  // 구현...
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4) 제네릭을 활용한 재사용 가능한 컴포넌트 타입&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트를 정의할 때 구체적인 타입 대신 제네릭(타입 변수)를 사용함으로써, 컴포넌트를 사용하는 시점에 실제 타입을 결정할 수 있습니다. 이에 따라 &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;하나의 컴포넌트 타입으로 다양한 타입의 데이터를 처리하며 타입 안정성과 동시에 컴포넌트 타입의 재사용성을 높일 수 있습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주로 리스트나 Select 컴포넌트 등에 활용되는데 그 예시 코드는 아래와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예시 1) 다양한 데이터 타입에 적용 가능한 리스트 컴포넌트 구현&amp;nbsp;&lt;/h3&gt;
&lt;pre id=&quot;code_1738181742060&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 제네릭 컴포넌트의 상세한 예시
interface DataListProps&amp;lt;T&amp;gt; {
  // T는 어떤 타입이든 될 수 있는 제네릭 타입
  items: T[];                                  // 데이터 배열
  renderItem: (item: T) =&amp;gt; React.ReactNode;    // 각 항목을 렌더링하는 함수
  keyExtractor: (item: T) =&amp;gt; string | number;  // 각 항목의 고유 키를 추출하는 함수
  onItemClick?: (item: T) =&amp;gt; void;            // 항목 클릭 핸들러 (선택적)
  listTitle?: string;                         // 리스트 제목 (선택적)
}

// 제네릭 컴포넌트 구현
const DataList = &amp;lt;T,&amp;gt;({ 
  items, 
  renderItem, 
  keyExtractor,
  onItemClick,
  listTitle 
}: DataListProps&amp;lt;T&amp;gt;) =&amp;gt; {
  return (
    &amp;lt;div className=&quot;data-list&quot;&amp;gt;
      {/* 제목이 있는 경우에만 렌더링 */}
      {listTitle &amp;amp;&amp;amp; &amp;lt;h2&amp;gt;{listTitle}&amp;lt;/h2&amp;gt;}
      
      {/* 데이터 리스트 렌더링 */}
      &amp;lt;ul&amp;gt;
        {items.map(item =&amp;gt; (
          &amp;lt;li 
            key={keyExtractor(item)}
            onClick={() =&amp;gt; onItemClick?.(item)}  // 선택적 체이닝으로 안전하게 호출
          &amp;gt;
            {renderItem(item)}
          &amp;lt;/li&amp;gt;
        ))}
      &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

// 사용 예시 1: 문자열 리스트
const StringList = () =&amp;gt; {
  const strings = [&quot;apple&quot;, &quot;banana&quot;, &quot;orange&quot;];
  
  return (
    &amp;lt;DataList&amp;lt;string&amp;gt;
      items={strings}
      renderItem={(item) =&amp;gt; &amp;lt;span&amp;gt;{item}&amp;lt;/span&amp;gt;}
      keyExtractor={(item) =&amp;gt; item}
      listTitle=&quot;과일 목록&quot;
    /&amp;gt;
  );
};

// 사용 예시 2: 사용자 객체 리스트
interface User {
  id: number;
  name: string;
  email: string;
}

const UserList = () =&amp;gt; {
  const users: User[] = [
    { id: 1, name: &quot;John&quot;, email: &quot;john@example.com&quot; },
    { id: 2, name: &quot;Jane&quot;, email: &quot;jane@example.com&quot; }
  ];
  
  const handleUserClick = (user: User) =&amp;gt; {
    console.log(`Selected user: ${user.name}`);
  };
  
  return (
    &amp;lt;DataList&amp;lt;User&amp;gt;
      items={users}
      renderItem={(user) =&amp;gt; (
        &amp;lt;div className=&quot;user-card&quot;&amp;gt;
          &amp;lt;h3&amp;gt;{user.name}&amp;lt;/h3&amp;gt;
          &amp;lt;p&amp;gt;{user.email}&amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
      )}
      keyExtractor={(user) =&amp;gt; user.id}
      onItemClick={handleUserClick}
      listTitle=&quot;사용자 목록&quot;
    /&amp;gt;
  );
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예시 2) select 컴포넌트 구현&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ Record는 TypeScript의 유틸리티 타입으로, 키-값 쌍의 타입을 정의할 때 사용합니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1738180970896&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface SelectProps&amp;lt;T extends Record&amp;lt;string, string&amp;gt;&amp;gt; {
  options: T;
  value?: keyof T;
  onChange?: (value: keyof T) =&amp;gt; void;
  placeholder?: string;
}

const Select = &amp;lt;T extends Record&amp;lt;string, string&amp;gt;&amp;gt;({ 
  options, 
  value, 
  onChange,
  placeholder 
}: SelectProps&amp;lt;T&amp;gt;) =&amp;gt; {
  return (
    &amp;lt;select 
      value={value as string}
      onChange={e =&amp;gt; {
        const selectedKey = Object.entries(options)
          .find(([_, val]) =&amp;gt; val === e.target.value)?.[0];
        onChange?.(selectedKey);
      }}
    &amp;gt;
      {placeholder &amp;amp;&amp;amp; (
        &amp;lt;option value=&quot;&quot;&amp;gt;{placeholder}&amp;lt;/option&amp;gt;
      )}
      {Object.entries(options).map(([key, value]) =&amp;gt; (
        &amp;lt;option key={key} value={value}&amp;gt;
          {value}
        &amp;lt;/option&amp;gt;
      ))}
    &amp;lt;/select&amp;gt;
  );
};

// 사용 예시
const fruits = {
  apple: &quot;사과&quot;,
  banana: &quot;바나나&quot;,
  orange: &quot;오렌지&quot;
} as const;

function App() {
  const [selectedFruit, setSelectedFruit] = useState&amp;lt;keyof typeof fruits&amp;gt;();

  return (
    &amp;lt;Select
      options={fruits}
      value={selectedFruit}
      onChange={setSelectedFruit}
      placeholder=&quot;과일을 선택하세요&quot;
    /&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5) 주요 타입 유틸리티&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) Record&amp;lt;K,T&amp;gt;&lt;/b&gt;: 객체의 키-값 쌍의 타입을 정의할 때 사용하는 유틸리티 타입입니다. K는 키의 타입, T는 값의 타입을 나타냅니다.&lt;/p&gt;
&lt;pre id=&quot;code_1738182264974&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const fruits: Record&amp;lt;string, number&amp;gt; = {
  apple: 1,
  banana: 2
  // 모든 키는 string, 모든 값은 number
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) keyof typeof&lt;/b&gt;: 객체의 키들을 유니온 타입으로 추출합니다. 객체의 타입을 그 객체의 키들의 유니온으로 변환합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1738182287932&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 객체의 키들을 유니온 타입으로 추출
const theme = {
  color: 'red',
  size: 'small'
};

type ThemeKeys = keyof typeof theme;
// 결과: 'color' | 'size'&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;3) Partial&amp;lt;T&amp;gt;&lt;/b&gt;: 타입 T의 모든 속성을 선택적(optional)으로 만드는 유틸리티 타입입니다. 기존 타입의 모든 필드가 optional이 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1738182313423&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface User {
  name: string;
  age: number;
}

type PartialUser = Partial&amp;lt;User&amp;gt;;
// 결과: { name?: string; age?: number; }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4) Pick&amp;lt;T,K&amp;gt;&lt;/b&gt;: 타입 T에서 특정 속성 K만을 선택하여 새로운 타입을 만드는 유틸리티 타입입니다. 원하는 속성만 뽑아서 사용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1738182366530&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface User {
  name: string;
  age: number;
  email: string;
}

type UserBasicInfo = Pick&amp;lt;User, 'name' | 'age'&amp;gt;;
// 결과: { name: string; age: number; }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/TypeScript</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/34</guid>
      <comments>https://dreamyard.tistory.com/entry/Typescript-React%EC%97%90%EC%84%9C-JSX%EB%A5%BC-TSX%EB%A1%9C-%EB%B3%80%ED%99%98%ED%95%A0-%EB%95%8C-%EC%95%8C%EC%95%84%EC%95%BC-%ED%95%A0-%ED%95%B5%EC%8B%AC-%EC%9D%B4%EB%A1%A0%EB%93%A4#entry34comment</comments>
      <pubDate>Thu, 30 Jan 2025 05:39:49 +0900</pubDate>
    </item>
    <item>
      <title>[TypeScript] 타입 스크립트의 타입 총정리</title>
      <link>https://dreamyard.tistory.com/entry/TypeScript-%ED%83%80%EC%9E%85-%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%ED%83%80%EC%9E%85-%EC%B4%9D%EC%A0%95%EB%A6%AC</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;타입 스크립트에서 사용되는 데이터 타입들을 원시타입, 객체타입부터 타입스크립트에서만 제공되는 고급 타입까지 총 정리해보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전반적인 타입스크립트의 계층 구조는 아래 사진을 참고해보시기 바랍니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;443&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Dc10P/btsLYotYXS3/I4bTaFzosKEKx8LYAGqRw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Dc10P/btsLYotYXS3/I4bTaFzosKEKx8LYAGqRw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Dc10P/btsLYotYXS3/I4bTaFzosKEKx8LYAGqRw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDc10P%2FbtsLYotYXS3%2FI4bTaFzosKEKx8LYAGqRw1%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;1280&quot; height=&quot;443&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;443&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 원시타입&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;number, string, boolean은 가장 대표적인 원시타입으로, 가장 많이 사용되고 사용자에 따라 다르게 사용될 여지가 적지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;null 이나 undefined는 tsconfig 옵션이나 사용자의 취향에 따라서 다르게 사용될 여지가 많습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) boolean&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;true와 false 값만 할당할 수 있는 타입.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1737658962669&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// boolean
let bool1 : boolean = true;
let bool2 : boolean = false;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) undefined&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의되지 않았다는 의미의 타입. 초기화되어 있지 않거나 존재하지 않음을 나타냅니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1737658983129&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// undefined 타입
let unde1: undefined = undefined;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) null&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에서 보통 빈 값을 할당해야 할 때 사용합니다. 자바스크립트에서 값이 없음을 나타날 때 null 과 undefined를 혼용하지만, 엄연히 따로 존재하는 원시값이므로 서로의 타입에 할당할 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;null과 undefined는 타입가드나 !(null이 아니라는 의미의 단언, 단언하고자 하는 변수 or 인수명 가장 오른쪽에 붙여 사용)를 통해 특정 상황에서 null과 undefined가 되는 경우를 걸러내기도 합니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1737658974638&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// null
let null1: null = null;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;null을 number 타입에 할당 가능할까 ?&lt;/h4&gt;
&lt;pre id=&quot;code_1737659063619&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let numA = null;
let numA: number = null;  // ❌&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;null은 number에 포함되는 값이 아니므로, 오류가 발생합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4) number&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트 숫자에 해당하는 모든 원시값을 할당할 수 있습니다. 정수, 부동소수점수를 구분하지 않으므로 모든 number 타입에 할당할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1737658923534&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// number
let num1: number = 123;
let num2: number = -123;
let num3: number = 0.123;
let num4: number = -0.123;
let num5: number = Infinity;
let num6: number = -Infinity;
let num7: number = NaN;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5) bigInt&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ES2020에서 새롭게 도입된 데이터 타입으로 타입스크립트 3.2버전부터 사용할 수 있습니다. 전보다 더 큰 수를 처리할 수 있도록 만들어진 타입입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6) string&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열을 할당할 수 있는 타입으로, 공백도 포함합니다. 작은 따옴표, 큰 따옴표로 둘러싸서 입력하며, 백틱(`)으로 감싼 문자열 내부에 변수값을 포함할 수 있는 템플릿 리터럴 문법도 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1737658954036&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// string
let str1: string = &quot;hello&quot;;
let str2: string = 'hello';
let str3: string = `hello`;
let str4: string = `hello ${str1}`;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7) symbol&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ES2015에서 도입된 타입이며, 어떤 값과도 중복되지 않는 유일한 값을 생성할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트에는 symbol타입과 더불어 const 선언에서만 사용할 수 있는 unique symbol 타입이라는 symbol의 하위타입도 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1737650976123&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const MOVIE_TITLE = Symbol(&quot;title&quot;);
const MUSIC_TITLE = Symbol(&quot;title&quot;);
console.log(MOVIE_TITLE === MUSIC_TITLE) // false&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;※ 원시 값과 원시 래퍼 객체&amp;nbsp;&lt;/h3&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;자바스크립트에서는 내장 타입을 파스킬 표기법으로 표기했지만, 타입 스크립트는 이를 구분하고자 소문자로 표기합니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;타입을 파스칼표기법으로 표기하면 자바스크립트에서는 이를 원시 레퍼 객체라고 부르는데, null과 undefined를 제외한 모든 원시값은 해당 원시값을 래핑한 객체를 가집니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;1) 원시값&amp;nbsp;&lt;/h4&gt;
&lt;pre id=&quot;code_1737658322382&quot; class=&quot;dart&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const str = &quot;hello&quot;;              // string 원시값
const num = 123;                  // number 원시값
const bool = true;                // boolean 원시값&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;2) 원시 래퍼 객체&lt;/h4&gt;
&lt;pre id=&quot;code_1737658322383&quot; class=&quot;javascript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const strObj = new String(&quot;hello&quot;);    // String 객체
const numObj = new Number(123);        // Number 객체
const boolObj = new Boolean(true);     // Boolean 객체&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;실제 개발 시에는 래퍼 객체를 직접 생성하는 것은 성능과 코드의 명확성, 예측 불가한 동작 방지, 메모리의 효율성을 위해 권장되지 않습니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 객체 타입&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 소개한 7가지 원시타입에 해당되지 않는 값은 모두 객체 타입으로 분류될 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트는 다양한 형태를 가지는 객체마다 개별적으로 타입 지정이 가능합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) object &lt;span style=&quot;color: #ef5369;&quot;&gt;가급적 사용 지양&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;object 타입은 any 타입과 유사하게 객체에 해당하는 모든 타입 값(객체, 배열, 정규 표현식, 함수, 클래스 등)을 유동적으로 할당할 수 있어 정적 타이핑의 의미가 크게 퇴색되기 때문에 가급적 사용하지 않는 것이 권장됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) {} (중괄호)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에서 객체 리터럴 방식으로 객체를 생성할 때 사용됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트에서 객체 타이핑 시에도 사용할 수 있는데, 중괄호 안에 객체의 속성 타입을 지정해주는 식으로 사용됩니다. {}만 사용할 경우 빈 객체임을 의미하며, {} 타입으로 지정된 객체에는 어떤 값도 속성으로 할당할 수 없습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) array&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트의 배열은 하나의 타입 값만 가질 수 있다는 점에서 자바스크립트 배열보다 조금 더 엄격합니다. 자바스크립트와 마찬가지로 원소 개수는 타입에 영향을 주지 않습니다. 선언 방법으로는 [] (대괄호)를 사용해서 선언하는 방법이나, Array 키워드로 선언하는 2가지 방식이 있습니다. (두 방식을 결과적으로 같기 때문에 둘 중 편한 것을 선택해 사용하면 됩니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4) type과 interface&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트 object 타입은 실무에서는 잘 사용하지 않으며, type과 interface 가 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;흔히 객체를 타이핑하기 위해 자주 사용됩니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5) function&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에서 함수를 function이라는 별도 타입으로 분류하듯, 타입스크립트에서는 함수를 별도 함수 타입으로 지정할 수 있습니다. 단, 아래 두 가지 사항을 주의해야 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 타입스크립트는 function 이라는 키워드 자체를 타입으로 사용하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 타입스크립트에서는 함수의 매개변수도 별도 타입으로 지정해야합니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1737652354296&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function add(a:number, b:number): number {
	return a + b; 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면, 함수 자체의 타입은 호출 시그니처를 정의하는 방식으로 지정하면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트에서 함수 자체 타입을 명시할 땐 화살표 함수 방식으로만 호출 시그니처를 정의합니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1737652433413&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type add = (a:number, b: number) =&amp;gt; number;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 고급 타입&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고급 타입은 타입스크립트에만 존재하는 키워드지만, 그 개념은 자바스크립트에서 기인했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) any 타입&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에 존재하는 모든 값을 오류 없이 받을 수 있는 타입입니다. 타입스크립트는 동적 타이핑 특징을 가진 자바스크립트에 정적 타이핑을 적용하는 것이 주된 목적인 만큼, &lt;b&gt;any를 사용하는 것은 지양하는 것이 좋습니다.&amp;nbsp;&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;tsconfig 에서 noImplicityAny 옵션을 true로 설정하면, 타입이 명시되지 않은 변수의 암묵적인 any에 대한 경고를 발생시킬 수 있습니다. 다만, 상황에 따라 any타입을 어쩔 수 없이 사용해야할 때도 있는데 아래 3가지에 해당됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(1) 개발 단계에서 추후 값이 변경될 가능성이 있거나, 세부 항목에 대한&lt;b&gt; 타입이 확정되지 않은 경우 임시로 값을 지정해야 할 때&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2) API 요청 및 응답 처리, 콜백 함수 전달, 타입이 잘 정제되지 않아 타입 파악이 힘든 외부 라이브러리 등을 사용할 때 &lt;b&gt;어떤 값을 받아올지 또는 넘겨줄지 정할 수 없을 때&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(3) 외부 라이브러리나 웹 API 요청에 따라 다양한 값을 반환하는 API(대표적으로 Fetch API) 처럼, &lt;b&gt;값을 예측할 수 없을 때 암묵적으로 사용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) unknown 타입&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;any와 유사하게 모든 타입 값이 할당될 수 있지만, any를 제외한 다른 타입으로 선언된 변수에는 unknown 타입 값을 할당할 수 없습니다. unknown은 어떤 연산에도, 어떤 메서드에서도 사용할 수 없고, 값을 저장하는 행위만 할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;any타입과 비슷하지만, unknown 타입은 any타입으로 임시적으로 오류를 피한 뒤 특정타입을 수정해야하는 것을 깜빡하는 상황들을 보완하기 위해 등장한 타입입니다. 타입 검사를 강제하고 타입이 식별된 후 사용할 수 있기 때문에 any 보다 더 안전합니다. 따라서, 데이터 구조 파악이 힘들 경우 any 대신 unknown으로 대체해서 사용하는 방법이 권장됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 타입 좁히기를 통해 해당 값의 타입이 number 값임을 보장해주면 곱셈 연산 수행이 가능합니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1737659772889&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (typeof unknownVar === &quot;number&quot;) {
	// 이 조건이 참이된다면 unknownVar는 number 타입으로 볼 수 있음
  unknownVar * 2;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) void&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에서는 함수에서 명시적인 반환문이 없다면, 기본적으로 undefined가 반환됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 타입스크립트에서는 void 타입이 사용됩니다. 변수에도 할당할 수 있긴 하지만, 함수가 아닌 값에 대해서는 대부분 무의미 합니다. void 타입으로 지정된 변수는 null 이나 undefined만 할당 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ 함수 내부에 별도 반환문이 없다면 타입 스크립트 컴파일러가 알아서 함수 타입을 추론해주기 때문에 void 타입은 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;함수 자체를 다른 함수의 인자로 전달하는 경우가 아니라면,&lt;span&gt; 잘 명시하지 않는 경향이 있습니다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1737660665781&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 컴파일러가 자동으로 void로 추론
function log(message: string) {
    console.log(message);
}

// 명시적으로 void 선언이 유용한 경우
type LogFunction = (message: string) =&amp;gt; void;
const logger: LogFunction = (message) =&amp;gt; {
    console.log(message);
    return true; // void 타입이므로 반환값은 무시됨
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;4) never&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;b&gt;함수와 관련되어 많이 사용되는 타입으로, 값을 반환할 수 없는 타입&lt;/b&gt;을 의미합니다. 모든 타입의 하위 타입이며, 자신을 제외한 그 어떤 타입도 never 타입에는 할당될 수 없습니다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;값을 반환할 수 없는 경우는 크게 2가지로 구분됩니다. (반환하지 않는 것과 (void), 반환할 수 없는 것 (never))&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;1) 에러를 던지는 경우 &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;2) 무한히 함수가 실행되는 경우&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1737660701511&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 에러를 던지는 함수
function throwError(message: string): never {
    throw new Error(message);
}

// 무한 루프 함수
function infiniteLoop(): never {
    while (true) {
        // ...
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5) Array 타입&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트는 정적 타입의 특성을 살려 명시적인 타입을 선언해 해당 타입의 원소를 관리하는 것을 강제합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열 타입의 선언 방식은 다른 정적 언어와 유사하며, 자료형 + [] 형식을 사용해 선언합니다. 또는 Array 키워드를 사용해 제네릭이라는 문법을 사용해 선언 할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 아래와 같은 2가지 방식이 가능하며 선언 형식만 다른 것이기 때문에 혼용해서 사용해도 큰 문제는 없습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1737654367121&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const array: number[] = [1,2,3]; // 숫자에 해당하는 원소만 허용
const array: Array&amp;lt;number&amp;gt; = [1,2,3]; // 숫자에 해당하는 원소만 허용&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;튜플&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 더해, 배열로부터 파생된 튜플이라는 타입은 대괄호 안에 타입을 직접 기술해 선언하며, 대괄호 안의 타입 개수가 곧 튜플이 가질 수 있는 원소의 개수를 의미합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트에서의 배열은 사전에 허용하지 않는 타입이 서로 섞이는 것을 방지함으로써 타입 안정성을 제공하며, 튜플은 이에 길이 제한까지 더해진 타입으로 원소 개수와 타입까지 보장합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6) enum 타입&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;열거형이라고도 부르며, 타입스크립트에서 지원하는 특수한 타입입니다.&lt;b&gt; enum은 주로 문자열 상수 생성에 사용&lt;/b&gt;됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;열거형은 모든 멤버에 일일히 값을 할당할 수도 있으나, 직접 할당하지 않아도 값 할당이 누락된 멤버들에 대해 숫자 1을 기준으로 1씩 늘려가며 값을 자동으로 할당해 줍니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1737654935921&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum ProgrammingLanguage { 
	Typescript, // 0 
    Javascript, // 1
    Java, // 2
    Python, // 3
    Kotlin, // 4
    Rust, // 5
    Go, // 6
}

ProgrammingLanguage.Typescript; // 0 
ProgrammingLanguage[&quot;Go&quot;]; // 6 
ProgrammingLanguage[2]; // Java (역방향 접근도 가능)

enum ProgrammingLanguage { 
	Typescript = &quot;Type&quot;,
    Javascript = &quot;Java&quot;,
    Java = 300,
    Python = 400,
    Kotlin, // 401
    Rust, // 402
    Go, // 403
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;주로 문자열 상수 생성 시 사용되는 enum&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;enum은 관련이 높은 멤버를 모아 문자열 상수처럼 사용하고자 할 때 유용하게 쓸 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;enum은 그 자체로 변수 타입으로 설정 가능한데, enum이 타입으로 설정된 변수는 enum이 가지는 모든 멤버를 값으로 받을 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드가 그 예시로, itemStatus는 ItemStatusType 열거형을 타입으로 가지는데, &lt;b&gt;itemStatus가 문자열 타입으로 지정된 경우와 비교했을 때 아래와 같은 이점&lt;/b&gt;이 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) 타입 안정성 :&lt;/b&gt; checkItemAvailable는 ItemStatusType에 명시되지 않은 다른 문자열은 인자로 받을 수 없게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) 명확한 의미전달 &amp;amp; 높은 응집력 :&lt;/b&gt; ItemStatusType이 다루는 값이 무엇인지 명확하고, 아이템 상태에 대한 값을 모아놓은 것으로 응집력이 좋아집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3) 가독성 :&lt;/b&gt; ItemStatusType 내부 멤버를 통해 어떤 상태를 나타내는지 쉽게 이해할 수 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt; &lt;b&gt; enum을 통해 문자열 상수를 설정해 사용한 경우&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1737655879341&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum ItemStatusType {
DELIVERY_HOLD = &quot;DELIVERY_HOLD&quot;, // 배송 보류 
DELIVERY_READY = &quot;DELIVERY_READY&quot;, // 배송 준비
DELIVERING = &quot;DELIVERING&quot;, // 배송 중
DELIVERED = &quot;DELIVERED&quot;, // 배송 완료 
}

const checkItemAvailable = (itemStatus : ItemStatusType) =&amp;gt; {
	switch(itemStatus) {
    case ItemStatusType.DELIVERY_HOLD:
    case ItemStatusType.DELIVERY_READY:    
    case ItemStatusType.DELIVERING:
    return false;
    case ItemStatusType.DELIVERED:    
    default : 
    return true;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt; &lt;b&gt;[참고] enum 없이 일반 문자열로 설정해 사용한 경우&lt;/b&gt;&lt;/h4&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1737658611277&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 문자열 리터럴 타입을 사용하는 경우
type ItemStatus = 'DELIVERY_HOLD' | 'DELIVERY_READY' | 'DELIVERING' | 'DELIVERED';

// 또는 그냥 string 타입을 사용하는 경우
const checkItemAvailable = (itemStatus: string) =&amp;gt; {
    switch(itemStatus) {
        case 'DELIVERY_HOLD':
        case 'DELIVERY_READY':    
        case 'DELIVERING':
            return false;
        case 'DELIVERED':    
        default: 
            return true;
    }
}

// 이런 실수들이 가능해집니다:
checkItemAvailable('DELIVRY_HOLD');  // 오타지만 타입 에러가 발생하지 않음
checkItemAvailable('SHIPPED');       // 정의되지 않은 상태지만 허용됨
checkItemAvailable('');              // 빈 문자열도 허용됨&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;⚠️단, 열거형(enum) 사용 시 주의점 !&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;숫자로만 이루어져있거나, 자동으로 추론된 열거형은 역방향 접근이 가능하기 때문에 안전성이 떨어진다는 단점이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 동작을 막기 위해 const enum으로 선언하는 방법이 있습니다. 하지만, 이 방법으로도 숫자 상수 방식으로 선언된 경우에 대해서 이 이외의 값을 할당하거나 접근하려고 할 때 방지할 수 없습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;따라서 const enum과 함께 문자열 상수 방식을 사용해 역방향 접근과 의도치 않은 값의 할당이나 접근을 방지하는 것이 비교적 안전합니다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1737657176445&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const enum NUMBER {
	ONE = 1,
	TWO = 2,
}
const myNumber : NUMBER = 100; // NUMBER enum 에 정의되지 않은 값이지만, 
// 숫자형 enum은 컴파일 시점에 값이 인라인 처리되어 타입오류가 발생하지 않을 수 있다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;더 알아보기 ! enum과 const enum의 컴파일 차이&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;enum&amp;nbsp;&lt;/h4&gt;
&lt;pre id=&quot;code_1737659515556&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 일반 enum 컴파일 전 
enum NORMAL_NUMBER {
    ONE = 1,
    TWO = 2
}
const normalValue = NORMAL_NUMBER.ONE;

// 컴파일 결과
var NORMAL_NUMBER;
(function (NORMAL_NUMBER) {
    NORMAL_NUMBER[NORMAL_NUMBER[&quot;ONE&quot;] = 1] = &quot;ONE&quot;;
    NORMAL_NUMBER[NORMAL_NUMBER[&quot;TWO&quot;] = 2] = &quot;TWO&quot;;
})(NORMAL_NUMBER || (NORMAL_NUMBER = {}));
const normalValue = NORMAL_NUMBER.ONE;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;const enum&lt;/h4&gt;
&lt;pre id=&quot;code_1737659462830&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// const enum 컴파일 전 
const enum NUMBER {
    ONE = 1,
    TWO = 2
}

const value = NUMBER.ONE;


// 컴파일 결과
const value = 1;  // NUMBER.ONE이 직접 1로 대체됨&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;const enum은 실제 런타임에 enum 객체가 존재하지 않습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/TypeScript</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/33</guid>
      <comments>https://dreamyard.tistory.com/entry/TypeScript-%ED%83%80%EC%9E%85-%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%ED%83%80%EC%9E%85-%EC%B4%9D%EC%A0%95%EB%A6%AC#entry33comment</comments>
      <pubDate>Fri, 24 Jan 2025 07:05:18 +0900</pubDate>
    </item>
    <item>
      <title>[TypeScript] 기존 React JavaScript 를 React TypeScript로 변환 하기 / React(TypeScript) Template 간단 설치 방법</title>
      <link>https://dreamyard.tistory.com/entry/TypeScript-%EA%B8%B0%EC%A1%B4-React-JavaScript-%EB%A5%BC-React-TypeScript%EB%A1%9C-%EB%B3%80%ED%99%98-%ED%95%98%EA%B8%B0-ReactTypeScript-Template-%EA%B0%84%EB%8B%A8-%EC%84%A4%EC%B9%98-%EB%B0%A9%EB%B2%95</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 javascript로 작성된 React 코드를 TypeScript로 변환하는 방법을 정리해보았습니다. 마지막에는 처음부터 React Typescript 로 프로젝트를 설정해 시작할 수 있는 Template 설치 방법도 정리해놓았으니 함께 참고해보시기 바랍니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  변환하는 순서&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 기존의 React javascript 파일을 React tyescript로 변경할 때는 아래와 같은 절차를 따르게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 리액트 앱을 위한 타입 정보들을 제공하는 패키지 설치 (4가지)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) tsconfig.json 파일 root 경로에 추가해 컴파일러 옵션 설정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) 모든 js 파일을 jsx로 변경&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) 개발 파일 확장자를 하나씩 tsx 로 변경&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5) tsx로 변경해줌으로써 발생하는 오류 해결&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  진행해보기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 리액트 앱을 위한 타입 정보들을 제공하는 패키지 설치&amp;nbsp;&lt;/h3&gt;
&lt;pre id=&quot;code_1737293442095&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm i @types/node @types/react @types/react-dom @types/jest&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드 terminal에 입력해 설치 후, package.json 에서 파일 설치 여부 확인&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;698&quot; data-origin-height=&quot;618&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDAj7X/btsLS3W4qsR/3YHSNBpJKABgJQ2WQcUio1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDAj7X/btsLS3W4qsR/3YHSNBpJKABgJQ2WQcUio1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDAj7X/btsLS3W4qsR/3YHSNBpJKABgJQ2WQcUio1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDAj7X%2FbtsLS3W4qsR%2F3YHSNBpJKABgJQ2WQcUio1%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;360&quot; height=&quot;319&quot; data-origin-width=&quot;698&quot; data-origin-height=&quot;618&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) tsconfig.json 파일 root 경로에 추가해 컴파일러 옵션 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;target의 경우 리액트가 어디서든 잘 동작할 수 있도록 하기 위해&amp;nbsp; ESNext가 아닌 ES5로 설정해줍니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1737293707520&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;compilerOptions&quot;: {
    &quot;target&quot;: &quot;ES5&quot;, 
    &quot;module&quot;: &quot;CommonJS&quot;,
    &quot;strict&quot;: true,
    &quot;allowJs&quot;: true,
    &quot;esModuleInterop&quot;: true,
    //default로 내보낸 값이 없더라도 default를 설정한 양식으로 import를 할 수 있도록 도와줌
    // 일반적으로 true 값으로 설정 후 작업한다. 
    // react 외부 패키지를 불러올 때, default export 되지 않는 패키지가 있을 가능성을 염두해서 설정해줌
    &quot;jsx&quot;: &quot;react-jsx&quot;
    // typescript는 기본적으로 jsx 문법을 해석할 수 없기 때문에, 설정해준다. 
  },
  &quot;include&quot;: [&quot;src&quot;]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;설정값에 대한 자세한 설명&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;이 외 설정값에 대한 이유와 자세한 기능이 궁금하시다면 아래 내용을 참고해보시기 바랍니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1737295890894&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;compilerOptions&quot;: {
    &quot;target&quot;: &quot;ES5&quot;,
    // JavaScript 코드가 변환될 ECMAScript 버전을 지정
    // ES5는 대부분의 브라우저에서 지원되는 안정적인 버전
    // 다른 옵션: ES6/ES2015, ES2016, ES2017, ES2018, ES2019, ES2020, ESNext

    &quot;module&quot;: &quot;CommonJS&quot;,
    // 모듈 시스템을 지정
    // CommonJS는 Node.js에서 사용하는 표준 모듈 시스템
    // 다른 옵션: ESNext, AMD, UMD, System, ES6/ES2015

    &quot;strict&quot;: true,
    // 모든 엄격한 타입-체킹 옵션을 활성화
    // 포함되는 옵션들: noImplicitAny, strictNullChecks, strictFunctionTypes, 
    // strictBindCallApply, strictPropertyInitialization, noImplicitThis, alwaysStrict

    &quot;allowJs&quot;: true,
    // TypeScript 프로젝트 내에서 JavaScript 파일의 사용을 허용
    // .js와 .jsx 파일을 TypeScript 프로젝트에 포함시킬 수 있음
    // 점진적인 마이그레이션에 유용

    &quot;esModuleInterop&quot;: true,
    // CommonJS 모듈을 ES6 모듈 방식으로 import 할 수 있게 해줌
    // 예: import React from 'react' 
    // (esModuleInterop이 false라면 import * as React from 'react' 형식을 사용해야 함)

    &quot;jsx&quot;: &quot;react-jsx&quot;
    // JSX 코드의 변환 방식을 지정
    // &quot;react-jsx&quot;: 새로운 JSX 변환, React 17 이상에서 사용
    // 다른 옵션: &quot;preserve&quot;, &quot;react&quot;, &quot;react-native&quot;
  },

  &quot;include&quot;: [&quot;src&quot;]
  // TypeScript 컴파일러가 분석할 디렉토리를 지정
  // src 폴더 내의 모든 TypeScript/JavaScript 파일이 컴파일 대상이 됨
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) 모든 js 파일을 jsx로 변경&amp;nbsp;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1677&quot; data-origin-height=&quot;506&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cIAWbX/btsLTrQ27nX/3AKgd079b9MLxNgizU1Zq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cIAWbX/btsLTrQ27nX/3AKgd079b9MLxNgizU1Zq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cIAWbX/btsLTrQ27nX/3AKgd079b9MLxNgizU1Zq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcIAWbX%2FbtsLTrQ27nX%2F3AKgd079b9MLxNgizU1Zq1%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;1677&quot; height=&quot;506&quot; data-origin-width=&quot;1677&quot; data-origin-height=&quot;506&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;'~~.js 파일은 입력 파일을 덮어쓰므로 쓸 수 없습니다.' 라는 위 사진과 같은 오류가 tsconfig.json 파일에서 발생한다면. App.js 나 index.js와 같은 파일의 확장자를 jsx로 수정해야 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;TypeScript는 기본적으로 js 파일로 부터 jsx 문법을 해석할 수 없기 때문입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4) 파일 확장자 tsx 로 변경&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;js로 만들어진 파일을 ts로 변경할 때에는 한번에 많은 오류가 뜰 수 있기 때문에 가능한 한 파일씩 확장자를 tsx로 변경하는 것이 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, App.jsx나 index.jsx 파일을 하나씩 순차적으로 tsx로 변경해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5) tsx로 변경해줌으로써 발생하는 오류 해결&lt;/h3&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;tsx로 변경 시, 아래와 같은 오류가 뜨게 되는데, 이 원인은 document.getElementById(&lt;/span&gt;&lt;span style=&quot;color: #a31515;&quot;&gt;&quot;root&quot;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;)의 값이 null 값을 내보낼 수도 있는 값인데, creatRoot라는 메서드는 null 값을 받지 않기 때문에 발생하는 오류입니다.&amp;nbsp;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1231&quot; data-origin-height=&quot;391&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UTHQ2/btsLSPrlE34/zidGI0iXKVfRjmACQvi84K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UTHQ2/btsLSPrlE34/zidGI0iXKVfRjmACQvi84K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UTHQ2/btsLSPrlE34/zidGI0iXKVfRjmACQvi84K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUTHQ2%2FbtsLSPrlE34%2FzidGI0iXKVfRjmACQvi84K%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;1231&quot; height=&quot;391&quot; data-origin-width=&quot;1231&quot; data-origin-height=&quot;391&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;해결방법 1) not null(!)설정을 해줌으로써 해당 값이 null 값이 아님을 단언&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1090&quot; data-origin-height=&quot;85&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/H7NPJ/btsLSzoLN6p/4NshL3Xl1xeH4mkK0FkgfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/H7NPJ/btsLSzoLN6p/4NshL3Xl1xeH4mkK0FkgfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/H7NPJ/btsLSzoLN6p/4NshL3Xl1xeH4mkK0FkgfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FH7NPJ%2FbtsLSzoLN6p%2F4NshL3Xl1xeH4mkK0FkgfK%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;1090&quot; height=&quot;85&quot; data-origin-width=&quot;1090&quot; data-origin-height=&quot;85&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;해결방법 2 ★추천) 좀 더 직관적으로 단언해주는 방법&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1275&quot; data-origin-height=&quot;74&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oYGQE/btsLSz3koCQ/XgsnKz1XrkPJlzjNKhC4z1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oYGQE/btsLSz3koCQ/XgsnKz1XrkPJlzjNKhC4z1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oYGQE/btsLSz3koCQ/XgsnKz1XrkPJlzjNKhC4z1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoYGQE%2FbtsLSz3koCQ%2FXgsnKz1XrkPJlzjNKhC4z1%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;1275&quot; height=&quot;74&quot; data-origin-width=&quot;1275&quot; data-origin-height=&quot;74&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  React(TypeScript) Template 설치&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음부터 React TypeScript로 프로젝트를 진행하고자 한다면, 아예 시작때부터 React TypeScript버전으로 Template을 설치하면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세한 내용은 아래 Create React App docs에도 나와있으니 참고해보시기 바랍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1;&quot; href=&quot;https://create-react-app.dev/docs/adding-typescript&quot;&gt; &lt;/a&gt;&lt;a title=&quot;타입스크립트 템플릿 설치 관련 docs 링크&quot; href=&quot;https://create-react-app.dev/docs/adding-typescript&quot;&gt;Adding TypeScript  &lt;/a&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 아래와 같은 코드를 terminal에 입력합니다.&amp;nbsp;&lt;/h3&gt;
&lt;pre id=&quot;code_1737295107608&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npx create-react-app . --template typescript&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 입력 후 자동으로 TypeScript가 적용된 react 기본 템플릿이 형성됩니다.&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tsconfig.json, pakage.json, index.tsx, App.tsx와 같은 파일들을 살펴보면, 위에서 설명드렸던 javascript -&amp;gt; TypeScript변환을 해주며 해주었던 설정들이 그대로 적용되어 있는 것을 확인해보실 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/TypeScript</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/32</guid>
      <comments>https://dreamyard.tistory.com/entry/TypeScript-%EA%B8%B0%EC%A1%B4-React-JavaScript-%EB%A5%BC-React-TypeScript%EB%A1%9C-%EB%B3%80%ED%99%98-%ED%95%98%EA%B8%B0-ReactTypeScript-Template-%EA%B0%84%EB%8B%A8-%EC%84%A4%EC%B9%98-%EB%B0%A9%EB%B2%95#entry32comment</comments>
      <pubDate>Sun, 19 Jan 2025 23:13:58 +0900</pubDate>
    </item>
    <item>
      <title>[React] React로 todoList 만들기 / Reducer 훅 적용하기</title>
      <link>https://dreamyard.tistory.com/entry/React-React%EB%A1%9C-todoList-%EB%A7%8C%EB%93%A4%EA%B8%B0-Reducer-%ED%9B%85-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;React로 todoList 만들어보며 사용한 주요 개념들을 정리해보았습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;519&quot; data-origin-height=&quot;601&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bk7tXU/btsLJlkWPwa/kpPiDTzCa3Ja1kg5K4g7NK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bk7tXU/btsLJlkWPwa/kpPiDTzCa3Ja1kg5K4g7NK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bk7tXU/btsLJlkWPwa/kpPiDTzCa3Ja1kg5K4g7NK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbk7tXU%2FbtsLJlkWPwa%2FkpPiDTzCa3Ja1kg5K4g7NK%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;519&quot; height=&quot;601&quot; data-origin-width=&quot;519&quot; data-origin-height=&quot;601&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1736610015456&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import &quot;./App.css&quot;;
import Header from &quot;./component/Header&quot;;
import TodoEditor from &quot;./component/TodoEditor&quot;;
import TodoList from &quot;./component/TodoList&quot;;
import { useState, useRef } from &quot;react&quot;;
function App() {
  const idRef = useRef(3);
  const mockTodo = [
    {
      id: 0,
      isDone: false,
      content: &quot;React 공부하기&quot;,
      createDate: new Date().getTime(),
    },
    {
      id: 1,
      isDone: false,
      content: &quot;빨래 널기&quot;,
      createDate: new Date().getTime(),
    },
    {
      id: 2,
      isDone: false,
      content: &quot;노래 연습&quot;,
      createDate: new Date().getTime(),
    },
  ];
  const [todo, setTodo] = useState(mockTodo);
  const onCreate = (content) =&amp;gt; {
    const newItem = {
      id: idRef.current,
      content,
      isDone: false,
      createdDate: new Date().getTime(),
    };
    setTodo([newItem, ...todo]);
    idRef.current += 1;
  };
  const onUpdate = (targetId) =&amp;gt; {
    setTodo(
      todo.map((todo) =&amp;gt;
        todo.id === targetId ? { ...todo, isDone: !todo.isDone } : todo
      )
    );
  };

  const onDelete = (targetId) =&amp;gt; {
    setTodo(todo.filter((it) =&amp;gt; it.id !== targetId));
  };

  return (
    &amp;lt;div className=&quot;App&quot;&amp;gt;
      &amp;lt;Header /&amp;gt;
      &amp;lt;TodoEditor onCreate={onCreate} /&amp;gt;
      &amp;lt;TodoList todo={todo} onUpdate={onUpdate} onDelete={onDelete} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Create(todoList 추가)&lt;/h2&gt;
&lt;pre id=&quot;code_1736610084179&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  const onCreate = (content) =&amp;gt; {
    const newItem = {
      id: idRef.current,
      content,
      isDone: false,
      createdDate: new Date().getTime(),
    };
    setTodo([newItem, ...todo]);
    idRef.current += 1;
  };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Read(TodoList 조회, 렌더링)&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;prop하여 자식 컴포넌트로 todo(할일 리스트를 담은 객체 배열)데이터를 내려 전달하고, onCreate, onUpdate, onDelete 함수를 전달해 자식 컴포넌트의 이벤트에 따라 해당 함수들이 실행되도록 했습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1736610176827&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// App.jsx 

  return (
    &amp;lt;div className=&quot;App&quot;&amp;gt;
      &amp;lt;Header /&amp;gt;
      &amp;lt;TodoEditor onCreate={onCreate} /&amp;gt;
      &amp;lt;TodoList todo={todo} onUpdate={onUpdate} onDelete={onDelete} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1736610189269&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// TodoList.jsx

import &quot;./TodoList.css&quot;;
import TodoItem from &quot;./TodoItem&quot;;
import { useState } from &quot;react&quot;;

const TodoList = ({ todo, onUpdate, onDelete }) =&amp;gt; {
  const [search, setSearch] = useState(&quot;&quot;);
  const onChangeSearch = (e) =&amp;gt; {
    setSearch(e.target.value);
  };
  const getSearchResult = () =&amp;gt; {
    return search === &quot;&quot;
      ? todo
      : todo.filter((it) =&amp;gt;
          it.content.toLowerCase().includes(search.toLowerCase())
        );
  };

  return (
    &amp;lt;div className=&quot;TodoList&quot;&amp;gt;
      &amp;lt;h3&amp;gt;Todo List &amp;lt;/h3&amp;gt;
      &amp;lt;input
        value={search}
        className=&quot;searchbar&quot;
        onChange={onChangeSearch}
        type=&quot;text&quot;
        placeholder=&quot;검색어를 입력하세요&quot;
      /&amp;gt;
      &amp;lt;div className=&quot;list__wrap&quot;&amp;gt;
        {getSearchResult().map((it) =&amp;gt; (
          &amp;lt;TodoItem
            onDelete={onDelete}
            key={it.id}
            {...it}
            onUpdate={onUpdate}
          /&amp;gt;
        ))}
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};
export default TodoList;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1736658917201&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// TodoItem.jsx

import &quot;./TodoItem.css&quot;;
import { useEffect } from &quot;react&quot;;

const TodoItem = ({ id, content, isDone, createdDate, onUpdate, onDelete }) =&amp;gt; {
  useEffect(() =&amp;gt; {
    console.log(&quot;content&quot;, content);
  }, [content]);
  const onChangeCheckbox = () =&amp;gt; {
    onUpdate(id);
  };
  const onClickDeleteButton = () =&amp;gt; {
    onDelete(id);
  };
  return (
    &amp;lt;div className=&quot;TodoItem&quot;&amp;gt;
      &amp;lt;div&amp;gt;
        &amp;lt;input onChange={onChangeCheckbox} checked={isDone} type=&quot;checkbox&quot; /&amp;gt;
        &amp;lt;div className={`list__title ${isDone ? &quot;done&quot; : &quot;&quot;}`}&amp;gt;{content}&amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;div&amp;gt;
        &amp;lt;span&amp;gt;{new Date(createdDate).toLocaleDateString()}&amp;lt;/span&amp;gt;
        &amp;lt;div className=&quot;btn__col&quot;&amp;gt;
          &amp;lt;button onClick={onClickDeleteButton}&amp;gt;X&amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};
export default TodoItem;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Update(업데이트 = 완료/미완료 체크)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;map을 이용해 onUpdate 실행 시, 해당 targetId 에 해당되는 todo 요소의 isDone(checkbox의 Value)이 토글되도록 하여, TodoItem 별로 완료/미완료 상태가 업데이트 되도록 했습니다..&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1736610108669&quot; class=&quot;coffeescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;  const onUpdate = (targetId) =&amp;gt; {
    setTodo(
      todo.map((todo) =&amp;gt;
        todo.id === targetId ? { ...todo, isDone: !todo.isDone } : todo
      )
    );
  };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Delete(삭제)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;filter를 사용하여 해당 id를 제외한 나머지 todo 내부 요소들을 반환하도록 했습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1736610151280&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  const onDelete = (targetId) =&amp;gt; {
    setTodo(todo.filter((it) =&amp;gt; it.id !== targetId));
  };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;부가기능 (검색)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;search라는 useState 변수를 설정하고, search 값의 변경에 따라 getSearchResult가 실행되도록 하여, search 값이 유효할 경우&amp;nbsp; content에 search가 포함된&amp;nbsp; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;todo 요소들을 필터링 하도록 하여 구현하였습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 시, 대소문자 구분을 없애기 위해 todo 요소들의 내용(content)과 검색어(search)를 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;toLowerCase()로 변환하여 비교하도록 하였습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1736666089582&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import &quot;./TodoList.css&quot;;
import TodoItem from &quot;./TodoItem&quot;;
import { useState } from &quot;react&quot;;

const TodoList = ({ todo, onUpdate, onDelete }) =&amp;gt; {
  const [search, setSearch] = useState(&quot;&quot;);
  const onChangeSearch = (e) =&amp;gt; {
    setSearch(e.target.value);
  };
  const getSearchResult = () =&amp;gt; {
    return search === &quot;&quot;
      ? todo
      : todo.filter((it) =&amp;gt;
          it.content.toLowerCase().includes(search.toLowerCase())
        );
  };

  return (
    &amp;lt;div className=&quot;TodoList&quot;&amp;gt;
      &amp;lt;h3&amp;gt;Todo List &amp;lt;/h3&amp;gt;
      &amp;lt;input
        value={search}
        className=&quot;searchbar&quot;
        onChange={onChangeSearch}
        type=&quot;text&quot;
        placeholder=&quot;검색어를 입력하세요&quot;
      /&amp;gt;
      &amp;lt;div className=&quot;list__wrap&quot;&amp;gt;
        {getSearchResult().map((it) =&amp;gt; (
          &amp;lt;TodoItem
            onDelete={onDelete}
            key={it.id}
            {...it}
            onUpdate={onUpdate}
          /&amp;gt;
        ))}
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};
export default TodoList;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;TodoList 코드 가독성 향상 시키기 : useState를 대체할 수 있는 useReducer&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useReducer란 컴포넌트에 새로운 State를 생성하는 React Hook 으로 모든 useState는 useReducer로 대체가 가능하며, useReducer를 이용하면 컴포넌트 내부에서만 관리하던 상태 관리 코드를 컴포넌트 외부로 분리하여 보다 깔끔한 코드 작성이 가능합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ 현재 사진 상으로는 코드가 그렇게 깔끔해보이나? 큰 차이가 없어 보일 수 있지만, 만약 더 큰 규모의 프로젝트에서 각 함수의 내부 실행 코드가 복잡해질 경우에, 내부 실행 코드를 컴포넌트 외부로 아예 정리할 수 있다는 점에서 reducer를 활용하는 것이 훨씬 가독성에 도움이 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csSgWI/btsLJBA8dLB/e7T7YWkpbgnGCfdNNNEh0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csSgWI/btsLJBA8dLB/e7T7YWkpbgnGCfdNNNEh0K/img.png&quot; data-origin-width=&quot;1061&quot; data-origin-height=&quot;1304&quot; data-is-animation=&quot;false&quot; style=&quot;width: 47.9561%; margin-right: 10px;&quot; data-widthpercent=&quot;48.52&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csSgWI/btsLJBA8dLB/e7T7YWkpbgnGCfdNNNEh0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcsSgWI%2FbtsLJBA8dLB%2Fe7T7YWkpbgnGCfdNNNEh0K%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;1061&quot; height=&quot;1304&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lV9EZ/btsLLDKVrlp/7GZrh3lyEQpMMnuqOSmrQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lV9EZ/btsLLDKVrlp/7GZrh3lyEQpMMnuqOSmrQK/img.png&quot; data-origin-width=&quot;1206&quot; data-origin-height=&quot;1397&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;51.48&quot; data-filename=&quot;blob&quot; style=&quot;width: 50.8811%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lV9EZ/btsLLDKVrlp/7GZrh3lyEQpMMnuqOSmrQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlV9EZ%2FbtsLLDKVrlp%2F7GZrh3lyEQpMMnuqOSmrQK%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;1206&quot; height=&quot;1397&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;TodoList에 UseReducer 적용 전(왼쪽) / 후(오른쪽) 코드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 컴포넌트의 주된 역할은 UI를 렌더링하는 것인데, useState를 사용하여 상태관리를 하게되면, State 변수는 컴포넌트 내부에서만 접근이 가능하므로 컴포넌트 내부의 코드가 길고 복잡해질 수 밖에 없습니다. 따라서, 컴포넌트에서 렌더링 하는 UI가 무엇인지 한눈에 파악하기 어렵게 되고, 코드 가독성이 저하됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 컴포넌트 외부에 상태 관리 코드를 정리할 필요를 느끼게 되는데, 이를 가능하게 하는 것이 useReducer라는 훅 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;useReducer 사용법&lt;/h3&gt;
&lt;pre id=&quot;code_1736660438226&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  const [state, dispatch] = useReducer(reducer, 0);
  //const [state 변수, 상태 변화 촉발 함수] = 생성자(상태변화 함수, 초깃값)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Counter에 적용한 useReducer 적용 예시&lt;/h3&gt;
&lt;iframe src=&quot;https://codesandbox.io/embed/yy23x5?view=editor+%2B+preview&amp;module=%2Fsrc%2Fcomponents%2FCounterUseReducer.js&quot;
     style=&quot;width:100%; height: 500px; border:0; border-radius: 4px; overflow:hidden;&quot;
     title=&quot;useReducer&quot;
     allow=&quot;accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking&quot;
     sandbox=&quot;allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts&quot;
   &gt;&lt;/iframe&gt;
&lt;pre id=&quot;code_1736660548978&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useReducer } from &quot;react&quot;;

// reducer : 변환기
// =&amp;gt; 상태를 실제로 변환시키는 변환기 역할할
function reducer(state, action) {
  console.log(state, action);
  switch (action.type) {
    case &quot;INCREASE&quot;:
      return state + action.data;
    case &quot;DECREASE&quot;:
      return state - action.data;
    default:
      return state;
  }
}

const Exam = () =&amp;gt; {
  // dispatch : 발송하다. 급송하다.
  // =&amp;gt; 상태 변화가 있어야한다는 사실을 알리는, 발송하는 함수
  const [state, dispatch] = useReducer(reducer, 0);

  const onClickPlus = () =&amp;gt; {
    // 인수 : 상태가 어떻게 변화되길 원하는지
    // =&amp;gt; 액션 객체
    dispatch({
      type: &quot;INCREASE&quot;,
      data: 1,
    });
  };

  const onClickMinus = () =&amp;gt; {
    // 인수 : 상태가 어떻게 변화되길 원하는지
    // =&amp;gt; 액션 객체
    dispatch({
      type: &quot;DECREASE&quot;,
      data: 1,
    });
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;{state}&amp;lt;/h1&amp;gt;
      &amp;lt;button onClick={onClickPlus}&amp;gt;+&amp;lt;/button&amp;gt;
      &amp;lt;button onClick={onClickMinus}&amp;gt;-&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default Exam;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;useReducer를 적용한 TodoList의 App.jsx&amp;nbsp;&lt;/h2&gt;
&lt;pre id=&quot;code_1736611739101&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import &quot;./App.css&quot;;
import Header from &quot;./component/Header&quot;;
import TodoEditor from &quot;./component/TodoEditor&quot;;
import TodoList from &quot;./component/TodoList&quot;;
import Exam from &quot;./component/Exam&quot;;
import { useState, useRef, useReducer } from &quot;react&quot;;

const mockTodo = [
  {
    id: 0,
    isDone: false,
    content: &quot;React 공부하기&quot;,
    date: new Date().getTime(),
  },
  {
    id: 1,
    isDone: false,
    content: &quot;빨래 널기&quot;,
    date: new Date().getTime(),
  },
  {
    id: 2,
    isDone: false,
    content: &quot;노래 연습&quot;,
    date: new Date().getTime(),
  },
];

function reducer(state, action) {
  switch (action.type) {
    case &quot;CREATE&quot;:
      return [action.data, ...state];
    case &quot;UPDATE&quot;:
      return state.map((item) =&amp;gt;
        item.id === action.targetId ? { ...item, isDone: !item.isDone } : item
      );
    case &quot;DELETE&quot;:
      return state.filter((item) =&amp;gt; item.id !== action.targetId);
    default:
      return state;
  }
}

function App() {
  const [todo, dispatch] = useReducer(reducer, mockTodo);
  const idRef = useRef(3);

  // const [todo, setTodo] = useState(mockTodo);
  const onCreate = (content) =&amp;gt; {
    dispatch({
      type: &quot;CREATE&quot;,
      data: {
        id: idRef.current++,
        isDone: false,
        content: content,
        date: new Date().getTime(),
      },
    });
  };

  const onUpdate = (targetId) =&amp;gt; {
    dispatch({
      type: &quot;UPDATE&quot;,
      targetId: targetId,
    });
  };

  const onDelete = (targetId) =&amp;gt; {
    dispatch({
      type: &quot;DELETE&quot;,
      targetId: targetId,
    });
  };

  return (
    &amp;lt;div className=&quot;App&quot;&amp;gt;
      &amp;lt;Header /&amp;gt;
      &amp;lt;TodoEditor onCreate={onCreate} /&amp;gt;
      &amp;lt;TodoList todo={todo} onUpdate={onUpdate} onDelete={onDelete} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt; 그럼, useReducer는 어떨 때 사용하는 것이 좋을까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;counter 앱처럼 굉장히 간단한 상태변화만 있다면 useState를 사용하면 되지만, todo처럼 배열 안에 복잡한 객체를 관리하는 경우에는 useReducer가 일반적으로 사용된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Q&amp;amp;A&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 아래 코드에서 onDelete를 onClick에 직접 연결하지 않고, 중간 매개 함수를 만드는 이유?&lt;/h3&gt;
&lt;pre id=&quot;code_1736508802888&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import &quot;./TodoItem.css&quot;;
import { useEffect } from &quot;react&quot;;

const TodoItem = ({ id, content, isDone, createdDate, onUpdate, onDelete }) =&amp;gt; {
  useEffect(() =&amp;gt; {
    console.log(&quot;content&quot;, content);
  }, [content]);
  const onChangeCheckbox = () =&amp;gt; {
    onUpdate(id);
  };
  const onClickDeleteButton = () =&amp;gt; {
    onDelete(id);
  };
  return (
    &amp;lt;div className=&quot;TodoItem&quot;&amp;gt;
      &amp;lt;div&amp;gt;
        &amp;lt;input onChange={onChangeCheckbox} checked={isDone} type=&quot;checkbox&quot; /&amp;gt;
        &amp;lt;div className=&quot;listtitle&quot;&amp;gt;{content}&amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;span&amp;gt;{new Date(createdDate).toLocaleDateString()}&amp;lt;/span&amp;gt;
      &amp;lt;div className=&quot;btncol&quot;&amp;gt;
        &amp;lt;button onClick={onClickDeleteButton}&amp;gt;X&amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};
export default TodoItem;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;코드가 간단한 경우에는 아래 코드와 같이 직접 연결해도 좋지만,&amp;nbsp;나중에 로직이 복잡해질 것을 대비해서 분리하는 것이 좋습니다. 특히 컴포넌트가 커지거나 여러 곳에서 같은 삭제 로직을 재사용해야 할 때 유용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1736609799112&quot; class=&quot;xml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;&amp;lt;button onClick={() =&amp;gt; onDelete(id)}&amp;gt;X&amp;lt;/button&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;이유 1 ) 디버깅의 용이성&lt;/h4&gt;
&lt;pre id=&quot;code_1736508821204&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const onClickDeleteButton = () =&amp;gt; {
  // 여기에 디버깅용 console.log를 추가하기 쉽습니다
  console.log(`Deleting item with id: ${id}`);
  onDelete(id);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;이유 2) 기능 확장성&lt;/h4&gt;
&lt;pre id=&quot;code_1736508833204&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const onClickDeleteButton = () =&amp;gt; {
  // 삭제 전 추가 로직을 넣기 쉽습니다
  if(window.confirm(&quot;정말 삭제하시겠습니까?&quot;)) {
    onDelete(id);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;이유 3) 코드 가독성&lt;/h4&gt;
&lt;pre id=&quot;code_1736508844417&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 이것보다는:
&amp;lt;button onClick={() =&amp;gt; onDelete(id)}&amp;gt;X&amp;lt;/button&amp;gt;

// 이게 더 명확합니다:
&amp;lt;button onClick={onClickDeleteButton}&amp;gt;X&amp;lt;/button&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;※ 단, 이렇게 사용하는 것엔 유의하자 !&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/h4&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;onClick={onDelete(id)}이런 코드는 렌더링 시점에 즉시 함수를 실행해버리기 때문에 주의해야된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1736659875847&quot; class=&quot;hsp&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 이렇게 작성하면:
&amp;lt;button onClick={onDelete(id)}&amp;gt;X&amp;lt;/button&amp;gt;

// 컴포넌트가 렌더링될 때마다 onDelete(id)가 실행됩니다
// 버튼을 클릭하지 않았는데도 삭제가 발생합니다!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올바른 방법은&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1736659883000&quot; class=&quot;hsp&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// 방법 1: 화살표 함수로 감싸기
&amp;lt;button onClick={() =&amp;gt; onDelete(id)}&amp;gt;X&amp;lt;/button&amp;gt;

// 방법 2: 별도의 핸들러 함수 만들기
const onClickDeleteButton = () =&amp;gt; onDelete(id);
&amp;lt;button onClick={onClickDeleteButton}&amp;gt;X&amp;lt;/button&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;실제 테스트를 통해 확인해보기&lt;/p&gt;
&lt;pre id=&quot;code_1736659890479&quot; class=&quot;javascript&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// 이렇게 테스트해보세요:
const TestComponent = () =&amp;gt; {
  const handleDelete = (id) =&amp;gt; {
    console.log(`Deleting ${id}`);
  };

  console.log(&quot;컴포넌트 렌더링&quot;);
  
  return (
    &amp;lt;div&amp;gt;
      {/* 잘못된 방법 - 렌더링할 때마다 즉시 실행됨 */}
      &amp;lt;button onClick={handleDelete(1)}&amp;gt;잘못된 방법&amp;lt;/button&amp;gt;
      
      {/* 올바른 방법 - 클릭할 때만 실행됨 */}
      &amp;lt;button onClick={() =&amp;gt; handleDelete(1)}&amp;gt;올바른 방법&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;2) &lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;map으로 요소 return 시&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; 유의점&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;(1) =&amp;gt; 뒤에 소괄호를 사용하는 경우&amp;nbsp;&lt;/h4&gt;
&lt;pre id=&quot;code_1736661012383&quot; class=&quot;clojure&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;{getSearchResult().map((it) =&amp;gt; (
    &amp;lt;TodoItem key={it.id} {...it} /&amp;gt;
))}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;화살표 함수의 암시적 반환(implicit return) 사용&lt;/li&gt;
&lt;li&gt;소괄호 () 안의 표현식이 자동으로 반환됨&lt;/li&gt;
&lt;li&gt;간단한 반환값에 적합&lt;/li&gt;
&lt;li&gt;더 간결한 문법&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;(2) =&amp;gt; 뒤에 중괄호와 return 사용하는 경우&amp;nbsp;&lt;/h4&gt;
&lt;pre id=&quot;code_1736661012384&quot; class=&quot;clojure&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;{getSearchResult().map((it) =&amp;gt; {
    return &amp;lt;TodoItem key={it.id} {...it} /&amp;gt;
})}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;명시적 return문 사용&lt;/li&gt;
&lt;li&gt;중괄호 {} 사용으로 함수 본문 블록을 생성&lt;/li&gt;
&lt;li&gt;반환 전에 추가 로직을 넣을 수 있음&lt;/li&gt;
&lt;li&gt;여러 줄의 로직이 필요할 때 적합&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;(3) 정리&lt;/h4&gt;
&lt;pre id=&quot;code_1736661012384&quot; class=&quot;livescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// 1. 단순 반환시 첫 번째 방식이 깔끔
{items.map(it =&amp;gt; (
    &amp;lt;TodoItem key={it.id} {...it} /&amp;gt;
))}

// 2. 추가 로직이 필요할 때는 두 번째 방식
{items.map(it =&amp;gt; {
    const updatedProps = processProps(it);
    console.log(it);
    return &amp;lt;TodoItem key={it.id} {...updatedProps} /&amp;gt;;
})}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/React</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/31</guid>
      <comments>https://dreamyard.tistory.com/entry/React-React%EB%A1%9C-todoList-%EB%A7%8C%EB%93%A4%EA%B8%B0-Reducer-%ED%9B%85-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0#entry31comment</comments>
      <pubDate>Sun, 12 Jan 2025 15:48:41 +0900</pubDate>
    </item>
    <item>
      <title>[Vue.js] console.log 일괄 제거하기</title>
      <link>https://dreamyard.tistory.com/entry/Vuejs-consolelog-%EC%9D%BC%EA%B4%84-%EC%A0%9C%EA%B1%B0%ED%95%98%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;수많은 디버깅을 하다보면, console.log를 미처 지우지 못해서 console.log 창이 지저분해지기도 하고, 디버깅의 편리를 위해 남겨두고 싶기도 한데요. 일일히 지우기도 번거로운 것이 사실입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드 시에는 불필요한 수많은 console log들을 일괄적으로 없앨 수 있는 방법 정리해보았습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;vite.config.js 설정&amp;nbsp;&lt;/h2&gt;
&lt;pre id=&quot;code_1736264996126&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default defineConfig({
  build: {
    minify: 'terser',
    terserOptions: {
      compress: {
        drop_console: true,
        drop_debugger: true
      },
      format: {
        comments: false
      }
    }
  }
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;terser 사용을 위한 플러그인 설치&lt;/h2&gt;
&lt;pre id=&quot;code_1736265123862&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install terser -D&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;terser 란 ?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Terser는 JavaScript 코드를 최적화하고 압축(minify)하는 도구로&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주요 기능으로는 아래와 같은 기능들이 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 불필요한 공백, 주석 제거&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 변수명 축소&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) 디버깅 코드 제거 (console.log 등)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/Vue.js</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/30</guid>
      <comments>https://dreamyard.tistory.com/entry/Vuejs-consolelog-%EC%9D%BC%EA%B4%84-%EC%A0%9C%EA%B1%B0%ED%95%98%EA%B8%B0#entry30comment</comments>
      <pubDate>Wed, 8 Jan 2025 00:54:45 +0900</pubDate>
    </item>
    <item>
      <title>[React] 리액트 훅 useRef / useEffect 기본 개념 / useRef useEffect 사용 상의 차이</title>
      <link>https://dreamyard.tistory.com/entry/React-%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%9B%85-useRef-useEffect-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90-useRef-useEffect-%EC%82%AC%EC%9A%A9-%EC%83%81%EC%9D%98-%EC%B0%A8%EC%9D%B4</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;리액트의 자주 사용하는 훅인 useRef 와 useEffect에 대해 정리해보았습니다. useRef는 리액트의 DOM 요소들을 직접 조작하고자 할 때 사용하며, useEffect의 경우 마운트, 업데이트, 언마운트와 같은 리액트의 라이프 사이클을 조절하기 위해 사용합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useEffect의 경우 이번 시간에는 간단한 사용법까지만 알아보고, 다음 글에 이어서 라이프 사이클을 제어하는 방법을 상세히 알아보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;useRef&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트의 Ref(Reference)를 이용하면 DOM 요소들을 직접 조작할 수 있습니다. 마치 실제 DOM의 getElementById()와 비슷한 역할을 한다고 볼 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useRef를 이용해 특정 요소에 focus를 주는 jsx 코드를 구현해보았습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 먼저 useRef를 import 해준 후&amp;nbsp;생성해줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2)&amp;nbsp; focusInput 함수 내부에 inputRef.current.focus() 를 이용해 함수 실행 시 inputRef.current(현재 inputRef가 가리키는 대상)에 focus를 주도록 설정합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) 포커스 주기 버튼 클릭(onClick) 시, focusInput 함수가 실행되도록 합니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1735716195580&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useRef } from 'react';

function MyComponent() {
  // 1. ref 생성
  const inputRef = useRef(null);
  
  // 2. 포커스를 주는 함수
  const focusInput = () =&amp;gt; {
    // ref.current로 DOM 요소에 접근
    inputRef.current.focus();
  };

  return (
    &amp;lt;div&amp;gt;
      {/* 3. ref 속성에 연결 */}
      &amp;lt;input ref={inputRef} type=&quot;text&quot; /&amp;gt;
      &amp;lt;button onClick={focusInput}&amp;gt;포커스 주기&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ 주석 정리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 코드 주석: 한줄 //, 여러줄 /**/&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React JSX 코드 주석 : 여러줄 {/* */}&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실제 사용 예시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드처럼 이미지 업로드 시, 파일 입력창을 숨기고, 별도로 배치한 버튼 클릭 시, 파일 입력창이 클릭되도록 할 때 적용할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1735717081516&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function ImageUploader() {
  const fileInputRef = useRef(null);
  
  const handleUploadClick = () =&amp;gt; {
    // 숨겨진 파일 입력창 클릭 효과
    fileInputRef.current.click();
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;input 
        type=&quot;file&quot;
        ref={fileInputRef}
        style={{ display: 'none' }}
        onChange={(e) =&amp;gt; {
          // 파일 처리 로직
        }}
      /&amp;gt;
      &amp;lt;button onClick={handleUploadClick}&amp;gt;
        이미지 업로드
      &amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;ref 값은 state 처럼 변경되어도 컴포넌트가 재랜더링 되지 않기 때문에, ref는 꼭 필요한 경우에만 사용하고 렌더링에 필요한 값은 state를 사용하는 것이 좋습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1735717783664&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function WrongUsage() {
  const inputRef = useRef(null);
  
  // ❌ 잘못된 사용: 렌더링 중에 ref.current 사용
  return &amp;lt;div&amp;gt;{inputRef.current.value}&amp;lt;/div&amp;gt;;  
  
  // ✅ 올바른 사용: 이벤트 핸들러나 useEffect 안에서 사용
  useEffect(() =&amp;gt; {
    console.log(inputRef.current.value);
  });
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;※ 리액트 훅 (React Hook)이란?&amp;nbsp;&lt;/h4&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트 훅은 2018년도에 처음 발표된 기능으로, 함수로 만든 리액트 컴포넌트에서 클래스로 만든 리액트 컴포넌트의 기능을 이용하도록 도와주는 함수들입니다.&amp;nbsp;useState, useRef, useEffect 모두 리액트 훅으로, 리액트 훅은 항상 이름 앞에 use가 붙습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;State, Ref, Effect 모두 함수형 컴포넌트에서는 원래 사용할 수 없었던 기능이었지만, 훅 기능을 이용하면 사용할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 훅이 없던 시절에는 대부분의 컴포넌트를 클래스로 만들어 사용했었는데, 작성해야할 코드가 너무 많고, 문법이 간결하지 못해 불편했다고 합니다. 이를 개선하고자 만든 것이 훅이고, 명칭 또한 낚아채듯 클래스로 만든 기능을 가져와 사용한다고 해서 붙여진 이름이라고 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;▼ 리액트 개발팀이 처음으로 훅을 소개한 영상 ▼&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=dpw9EHDh2bM&quot;&gt;React Today and Tomorrow and 90% Cleaner React With Hooks&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;▼ 더 많은 리액트 훅 보기 ▼&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://legacy.reactjs.org/docs/hooks-reference.html&quot;&gt;Hooks API Reference &amp;ndash; React&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt; useState 공부 시작 전에 알고 가기 &amp;gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;리액트 컴포넌트의 라이프 사이클&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;리액트 컴포넌트의 태어나고, 사라지는 생애 주기를 라이프 사이클이라고 하고 3단계로 구분 합니다. 크게 마운트, 업데이트, 언마운트로 아래와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Mount (마운트) : 컴포넌트를 페이지에 처음 렌더링 할 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Update (업데이트) : State 나 Props의 값이 바뀌거나 부모 컴포넌트가 리렌더해서 자신도 리렌더될 때&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Unmount (언마운트) : 더이상 페이지에 컴포넌트를 렌더링 하지 않을 때&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 라이프 사이클을 통해&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 렌더링 시 특정 동작을 하도록 만들거나, 업데이트 시 적절한지 검사하거나, 페이지에서 사라질 때 메모리를 정리하는 등 여러 가지 유용한 작업을 할 수 있습니다. 이러한 작업들을 라이프 사이클 제어라고 하며, 리액트 훅 중 하나인 useEffect를 이용해 이 사이클을 제어할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;useEffect&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;어떤 값이 변경될 때마다 특정 코드를 실행하는 리액트 훅으로, &quot;특정 값을 검사한다.&quot;라고 표현합니다. useEffect를 이용하면 컴포넌트의 State 값이 바뀔 때마다 변경된 값을 콘솔에 출력하게 할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(1) 함수 useEffect를 사용하기 위해 react 라이브러리에서 import 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2) useEffect를 호출하고, 첫 번째 인수로는 콜백 함수를, 두번째 인수로는 배열(의존성 배열, Dependency Array, deps)을 전달합니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1735704745486&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;useEffect(callback, [deps])
 // 콜백 함수, 의존성 배열&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt; 의존성 배열의 요소 값이 변경되면 첫 번째 인수로 전달된 콜백 함수가 실행됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단일 요소 및 여러 개의 요소 검사&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;해당 state 변수를 deps 배열에 넣으면 됩니다. 여러 개일 경우도 deps 배열 안에 해당 state 변수들을 나열하여 넣으면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1735709113813&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useEffect, useState } from &quot;react&quot;;

export default function App() {
	const [ count, setCount ] = useState(0);
	const [ text, setText ] = useState(&quot;&quot;);
    // 중략 ...
    
    
    // 아래 두 경우 중 하나 선택 
    // (1) 단일 요소만 검사할 경우 
   useEffect(() =&amp;gt; {
   	console.log(&quot;count 업데이트: &quot;, count);
   }, [count]);
    
    
    // (2) 여러 개의 요소를 검사할 경우
   useEffect(() =&amp;gt; {
   	console.log(&quot;count 업데이트: &quot;, count);
    console.log(&quot;text 업데이트: &quot;, text);
   }, [count, text]);
        
        
    return (
    // 중략 ... 
	
    );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;잠깐!! useState와 useEffect 비교해보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;위에서 설명했듯 useEffect는 특정 State 변수가 변경될 때마다 그 변경을 감지하고 특정 함수를 실행되게 할 수 있습니다. 그런데 돌이켜보면 우리가 앞서 배웠던 useState 함수도 잘 이용하면 state 변수값의 변경에 따라 특정 동작을 수행할 수 있었는데요.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 리액트 훅의 코드를 비교해보며 사용방법과 기능상의 차이를 알아보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 두 사진의 코드 모두 count라는 상태 변수의 변화를 감지하고 console.log 가 실행되도록 구현된 코드입니다. 아래 사진의 코드를 통해 간단히 사용 방법의 차이를 체크해보시고 아래 차이점을 비교해보시기 바랍니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zaPne/btsLBZOYnMs/LFCAyrfRKWzkUlkW5sMluK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zaPne/btsLBZOYnMs/LFCAyrfRKWzkUlkW5sMluK/img.png&quot; data-origin-width=&quot;860&quot; data-origin-height=&quot;524&quot; data-is-animation=&quot;false&quot; style=&quot;width: 51.5333%; margin-right: 10px;&quot; data-widthpercent=&quot;52.14&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zaPne/btsLBZOYnMs/LFCAyrfRKWzkUlkW5sMluK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzaPne%2FbtsLBZOYnMs%2FLFCAyrfRKWzkUlkW5sMluK%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;860&quot; height=&quot;524&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WOHlR/btsLDl4msY7/C070HCg0RzmKHzeOnznnIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WOHlR/btsLDl4msY7/C070HCg0RzmKHzeOnznnIk/img.png&quot; data-origin-width=&quot;693&quot; data-origin-height=&quot;460&quot; data-is-animation=&quot;false&quot; style=&quot;width: 47.3039%;&quot; data-widthpercent=&quot;47.86&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WOHlR/btsLDl4msY7/C070HCg0RzmKHzeOnznnIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWOHlR%2FbtsLDl4msY7%2FC070HCg0RzmKHzeOnznnIk%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;693&quot; height=&quot;460&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;차이점 1 : 실행 시점&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useState 설정을 통해 우리는 특정 state 변수 값의 변경에 따라 컴포넌트가 리렌더링 되도록 할 수 있습니다. useEffect의 경우 해당 state 변수값의 변경으로 인한 리렌더링이 완료된 후에 실행됩니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1735721849096&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function ExampleComponent() {
  const [value, setValue] = useState(0);

  // useState: 상태 변경 함수 호출 시점에 실행
  const handleChange = (newValue) =&amp;gt; {
    setValue(newValue);  // 상태 변경
    performSideEffect(); // 즉시 실행
  };

  // useEffect: 렌더링 완료 후 실행
  useEffect(() =&amp;gt; {
    performSideEffect(); // 렌더링 후 실행
  }, [value]);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;차이점 2 : 비동기 작업 처리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useState의 값의 set함수를 통해 해당 State 값을 변경하더라도, set함수 자체가 비동기적으로 실행되고, React의 상태 업데이트는 현재 함수 실행이 끝난 후 일괄처리 되기 때문에 State변수 값이 업데이트 되지 않고 나머지 코드가 실행됩니다. 따라서, 동시적으로 여러 작업이 진행되는 비동기 작업 처리가 어렵습니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1735721870620&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function UserProfile() {
  const [userId, setUserId] = useState(1);

  const handleUserChange = async (newId) =&amp;gt; {
    setUserId(newId);  // 1️⃣ 상태 업데이트
    
    // 2️⃣ 이 시점에서 userId는 아직 이전 값
    const userData = await fetchUserData(userId); // 문제: 이전 userId로 요청됨
    console.log('현재 userId:', userId); // 이전 값이 출력됨
  };

  return (
    &amp;lt;button onClick={() =&amp;gt; handleUserChange(2)}&amp;gt;
      유저 변경
    &amp;lt;/button&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 useEffect의 경우 데이터의 변경 모두 적용된 후 코드들이 실행되기 때문에 async를 적용한 비동기적 함수를 통한 비동기적 작업에도 적합합니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1735723172541&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function UserProfile() {
  const [userId, setUserId] = useState(1);
  const [userData, setUserData] = useState(null);
  
  console.log('1. 컴포넌트 렌더링 - userId:', userId);

  useEffect(() =&amp;gt; {
    console.log('3. useEffect 실행 - userId:', userId);
    
    const fetchUser = async () =&amp;gt; {
      console.log('4. 데이터 요청 시작 - userId:', userId);
      const data = await fetchUserData(userId);
      console.log('5. 데이터 수신 완료:', data);
      setUserData(data);
    };

    fetchUser();
  }, [userId]);

  const handleUserChange = (newId) =&amp;gt; {
    console.log('2. 상태 업데이트 시작');
    setUserId(newId);
    console.log('userId 직후:', userId); // 아직 이전 값
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; handleUserChange(2)}&amp;gt;
        ID {userId}의 유저 데이터 불러오기
      &amp;lt;/button&amp;gt;
      &amp;lt;div&amp;gt;
        {userData ? (
          &amp;lt;div&amp;gt;이름: {userData.name}&amp;lt;/div&amp;gt;
        ) : (
          &amp;lt;div&amp;gt;로딩 중...&amp;lt;/div&amp;gt;
        )}
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;차이점 3 : DOM&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useEffect는 DOM 업데이트 후 실행되기 때문에 콜백함수 내에서 DOM 요소 내부의 변경이 업데이트 된 후, 접근이 가능합니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1735721900658&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function TextInput() {
  const [text, setText] = useState('');

  // useState: DOM 업데이트 전에 실행되므로 부적절
  const handleTextChange = (newText) =&amp;gt; {
    setText(newText);
    // DOM 요소의 크기를 측정하려고 하면 이전 상태의 크기를 얻게 됨
  };

  // useEffect: DOM 업데이트 후 실행되므로 적절
  useEffect(() =&amp;gt; {
    // 텍스트가 변경된 후 DOM 요소의 실제 크기를 얻을 수 있음
    const element = document.querySelector('.text-input');
    console.log('입력창 높이:', element.scrollHeight);
  }, [text]);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 글에서는 useEffect를 이용해 리액트의 라이프 사이클을 제어하는 방법을 상세히 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/React</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/29</guid>
      <comments>https://dreamyard.tistory.com/entry/React-%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%9B%85-useRef-useEffect-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90-useRef-useEffect-%EC%82%AC%EC%9A%A9-%EC%83%81%EC%9D%98-%EC%B0%A8%EC%9D%B4#entry29comment</comments>
      <pubDate>Wed, 1 Jan 2025 18:28:01 +0900</pubDate>
    </item>
    <item>
      <title>[React] State 이해 및 사용법</title>
      <link>https://dreamyard.tistory.com/entry/React-State-%EC%9D%B4%ED%95%B4-%EB%B0%8F-%EC%82%AC%EC%9A%A9%EB%B2%95</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;State는 상태라는 뜻으로, 어떤 사물의 형편이나 모양을 의미합니다. 전구의 On/Off 상태와 유사하게 리액트의 컴포넌트도 State(상태)따라 다른 결과를 렌더링 할 수 있습니다. 리액트에서 사용되는 State에 대해 좀 더 이해하면서, 사용하는 방법도 함께 정리해보았습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;컴포넌트와 State(상태)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React의 컴포넌트는 사용자의 행위나 시간 변동에 따라 값이 변하는 동적인 컴포넌트로 만들 수 도 있습니다. 이를 위해 사용되는 기능이 바로 React의 State 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;State의 기본 사용법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;State를 사용하려면 먼저 useState라는 함수를 통해 State를 생성해주어야 하는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 생성해주면 되고, 각각은 [ State 변수, set 함수 ] = 생성자(초기값) 을 의미합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1735190134588&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const [light, setLight] = useState('off'); 
// [ State 변수, set 함수 ] = 생성자(초기값)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 한번 풀어서 설명하면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;light는 State 변수로, 현재 상태 값을 저장하고 있는 변수 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setLight는 set 함수로, State 변수의 값을 변경하는 함수입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useState를 호출 할 때, ()안에 특정값을 넣어주게 되면, 해당값이 State변수의 초깃값이 됩니다. (현재 light의 값은 'off')&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;State로 간단한 Counter 구현해보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 useState import를 해준 뒤, Body 컴포넌트에서 useState 함수로 초깃값은 0으로 설정, 구조분해할당으로 각각 count(State 변수)와 setCount(set 함수)를 설정해주었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후, button이 클릭될 때마다 count의 값이 올라가도록 설정했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, console.log(&quot;Update!&quot;)를 통해 버튼 클릭 시마다, 콘솔에 Update! 출력을 확인하여,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변경되는 count State 값이 변할 때마다 해당 State가 포함된 컴포넌트가 다시 렌더링 것을 확인할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 컴포넌트는 자신이 관리하는 State값이 변할 때마다 자동으로 다시 호출되는데, 이를 '리렌더' 또는 '리렌더링' 이라고 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;iframe src=&quot;https://codesandbox.io/embed/kgxx7f?view=editor+%2B+preview&amp;module=%2Fsrc%2Fcomponents%2FCounter.js&amp;expanddevtools=1&quot;
     style=&quot;width:100%; height: 500px; border:0; border-radius: 4px; overflow:hidden;&quot;
     title=&quot;one-vite&quot;
     allow=&quot;accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking&quot;
     sandbox=&quot;allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts&quot;
   &gt;&lt;/iframe&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;State로 사용자 입력 관리&amp;nbsp;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;input&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;text라는 이름으로 컴포넌트의 상태 관리 값을 설정해준 후,이를 input 태그의 onChange를 이용하여 사용자가 텍스트 입력 시 마다 해당 값이 text에 저장되도록 handleOnChange 함수를 설정해주었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이 코드에서도 State변수인 text 값의 변경 시마다 해당 컴포넌트가 자동으로 리렌더되어 현재의 State 값이 다시 렌더링되어 보여지게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;iframe src=&quot;https://codesandbox.io/embed/kgxx7f?view=editor+%2B+preview&amp;module=%2Fsrc%2Fcomponents%2FInput.js&amp;expanddevtools=1&quot;
     style=&quot;width:100%; height: 500px; border:0; border-radius: 4px; overflow:hidden;&quot;
     title=&quot;one-vite&quot;
     allow=&quot;accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking&quot;
     sandbox=&quot;allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts&quot;
   &gt;&lt;/iframe&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;select&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Select 1&lt;/h4&gt;
&lt;iframe src=&quot;https://codesandbox.io/embed/kgxx7f?view=editor+%2B+preview&amp;module=%2Fsrc%2Fcomponents%2FSelect.js&amp;expanddevtools=1&quot;
     style=&quot;width:100%; height: 500px; border:0; border-radius: 4px; overflow:hidden;&quot;
     title=&quot;one-vite&quot;
     allow=&quot;accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking&quot;
     sandbox=&quot;allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts&quot;
   &gt;&lt;/iframe&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Select1에서 사용자가 옵션을 변경하면, onChage 이벤트가 발생 하고, 이 때 e.target.value에는 현재 사용자가 선택한 option값이 저장됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Select 2&lt;/h4&gt;
&lt;iframe src=&quot;https://codesandbox.io/embed/kgxx7f?view=editor+%2B+preview&amp;module=%2Fsrc%2Fcomponents%2FSelect2.js&amp;expanddevtools=1&quot;
     style=&quot;width:100%; height: 500px; border:0; border-radius: 4px; overflow:hidden;&quot;
     title=&quot;one-vite&quot;
     allow=&quot;accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking&quot;
     sandbox=&quot;allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts&quot;
   &gt;&lt;/iframe&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Select2의 코드의 경우 참고로 알아두시면 좋을 것 같습니다. select에서 사용자가 옵션을 변경하면, onChage 이벤트가 발생하여 값이 저장되는 것은 같지만,&amp;nbsp; e.target.value에는 현재 사용자가 선택한 key 속성이 저장됩니다. 예를 들어, 사용자가 2번 옵션을 선택했다면, Key값이 저장되어 e.target.value값은 &quot;2번&quot;이 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 key를 설정하는 경우는 추후 아래와 같이 옵션을 배열로 관리할 경우, 필요하게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1735272516251&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const options = [&quot;1번&quot;, &quot;2번&quot;, &quot;3번&quot;];

// ... 중략 ...

&amp;lt;select value={option} onChange={handleOnChange}&amp;gt;
  {options.map((opt) =&amp;gt; (
    &amp;lt;option key={opt}&amp;gt;{opt}&amp;lt;/option&amp;gt;
  ))}
&amp;lt;/select&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;textarea&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;textarea 태그 역시, onChage 이벤트를 통해 e.target.value의 값을 전달해 text(State 변수)의 값을 변경합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;iframe src=&quot;https://codesandbox.io/embed/kgxx7f?view=editor+%2B+preview&amp;module=%2Fsrc%2Fcomponents%2FTextarea.js&quot;
     style=&quot;width:100%; height: 500px; border:0; border-radius: 4px; overflow:hidden;&quot;
     title=&quot;one-vite&quot;
     allow=&quot;accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking&quot;
     sandbox=&quot;allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts&quot;
   &gt;&lt;/iframe&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;여러 개 입력 받기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;입력 값을 객체형태, 즉 여러 개의 프로퍼티값을 입력 받을 경우를 구현해보겠습니다. 이 코드에서 주목해야될 부분은 객체 형태로 설졍되는 State와 setState입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;iframe src=&quot;https://codesandbox.io/embed/kgxx7f?view=editor+%2B+preview&amp;module=%2Fsrc%2Fcomponents%2FMultiInput.js&quot;
     style=&quot;width:100%; height: 500px; border:0; border-radius: 4px; overflow:hidden;&quot;
     title=&quot;one-vite&quot;
     allow=&quot;accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking&quot;
     sandbox=&quot;allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts&quot;
   &gt;&lt;/iframe&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;State에서 초깃값 설정 시, 내부를 객체형태로 각각의 프로퍼티와 초깃값을 설정해줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후 setState 함수는 ...state를 통해서 새로운 객체를 생성해 전달하고, name 속성 (e.tarrget.name)을 key로 하여 입력 폼에 입력한 값을 value로 저장하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좀 더 자세히 설명하면, e.target.name은 현재 이벤트가 발생한 요소의 name 속성으로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 성별 입력 select 태그에서 onChange 이벤트 발생 시 e.target.name은 gender가 되는데, 이를 통해 state의 3가지 프로퍼티 중, 현재 이벤트가 발생한 요소인 gender 프로퍼티의 value&amp;nbsp;&amp;nbsp;값이 변경되게 되는 것 입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/React</category>
      <category>state</category>
      <category>useState</category>
      <category>리액트</category>
      <category>리액트state</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/28</guid>
      <comments>https://dreamyard.tistory.com/entry/React-State-%EC%9D%B4%ED%95%B4-%EB%B0%8F-%EC%82%AC%EC%9A%A9%EB%B2%95#entry28comment</comments>
      <pubDate>Fri, 27 Dec 2024 13:17:48 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스, Java] 두 개 뽑아서 더하기</title>
      <link>https://dreamyard.tistory.com/entry/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%91%90-%EA%B0%9C-%EB%BD%91%EC%95%84%EC%84%9C-%EB%8D%94%ED%95%98%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제&amp;nbsp;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;설명&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정수&amp;nbsp;배열&amp;nbsp;numbers가&amp;nbsp;주어집니다.&amp;nbsp;numbers에서&amp;nbsp;서로&amp;nbsp;다른&amp;nbsp;인덱스에&amp;nbsp;있는&amp;nbsp;두&amp;nbsp;개의&amp;nbsp;수를&amp;nbsp;뽑아&amp;nbsp;더해서&amp;nbsp;만들&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;모든&amp;nbsp;수를&amp;nbsp;배열에&amp;nbsp;오름차순으로&amp;nbsp;담아&amp;nbsp;return&amp;nbsp;하도록&amp;nbsp;solution&amp;nbsp;함수를&amp;nbsp;완성해주세요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;제한사항&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;numbers의&amp;nbsp;길이는&amp;nbsp;2&amp;nbsp;이상&amp;nbsp;100&amp;nbsp;이하입니다. &lt;br /&gt;numbers의&amp;nbsp;모든&amp;nbsp;수는&amp;nbsp;0&amp;nbsp;이상&amp;nbsp;100&amp;nbsp;이하입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;풀이&amp;nbsp;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;풀이 1&lt;/h3&gt;
&lt;pre id=&quot;code_1734939015647&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;



class Solution {
    public int[] solution(int[] numbers) {
        ArrayList&amp;lt;Integer&amp;gt; result = new ArrayList&amp;lt;&amp;gt;();
        
        for(int i = 0; i &amp;lt; numbers.length; i++){
            for(int j = i+1; j &amp;lt; numbers.length; j++){
                int sum  = numbers[i] + numbers[j];
                if(!result.contains(sum)) result.add(sum);
            }
        }
    
        Collections.sort(result);
        int[] arr = new int[result.size()];
        for(int i = 0; i &amp;lt; result.size(); i++){
            arr[i] = result.get(i);
        }
        
        return arr;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;요약&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- ArrayList를 활용해서 ArrayList의 contains를 이용해서 중복 숫자 배제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- for문에서 j=i+1 부터 시작하여 자기자신끼리 더하는 경우 없도록 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Collections.sort를 활용해 오름차순 정렬 후 ArrayList를 배열로 변환 후 return.&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;시간 복잡도 분석&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;n = numbers의 크기, k = result의 크기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) for문 외부 내부 루프 n*(n-1) : O( n&amp;sup2; -n)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) ArrayList.contains() 연산&amp;nbsp; : O(n)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) add() 연산 : O(1)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--- 이 부분 시간 복잡도 : &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;O(n&amp;sup3;)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) Collections.sort() : O(k log k)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5) 배열로 변환하는 부분 : O(k)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종 시간 복잡도 : O(n&amp;sup3;) + O(k log k) = O(n&amp;sup3;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;풀이 2&lt;/h2&gt;
&lt;pre id=&quot;code_1734938970698&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;


class Solution {
    public int[] solution(int[] numbers) {
        HashSet&amp;lt;Integer&amp;gt; set = new HashSet&amp;lt;&amp;gt;();
        
        for(int i = 0; i &amp;lt; numbers.length; i++){
            for(int j = i+1; j &amp;lt; numbers.length; j++){
                int sum  = numbers[i] + numbers[j];
                set.add(sum);
            }
        }
    
        
        return set.stream().sorted().mapToInt(Integer::intValue).toArray();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;요약&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- HashSet을 사용하여 요소 추가 시 중복 배제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- return 시, HashSet을 스트림으로 변환 -&amp;gt; sorted()로 스트림 요소들을 오름차순 정렬 -&amp;gt; mapToInt로 Stream&amp;lt;Integer&amp;gt;를 IntStream으로 변환. Integer::intValue 로 Integer 객체를 기본형 int로 변환 ( x -&amp;gt; x.intValue()) -&amp;gt; toArray()를 통해 intStream을 int[] 배열로 변환&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;시간 복잡도 분석&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;n = numbers의 크기, k = result의 크기&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1) for문 외부 내부 루프 n*(n-1) : O( n&amp;sup2; -n)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;3) Hashset add() 연산 : O(1)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;-- 이 부분 시간 복잡도 : n&amp;sup2;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;4) stream 생성 : O(1)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;5) sorted(): O(k log k)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;6) mapToInt(): O(k)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;7) toArray(): O(k)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;-- 최종 시간 복잡도 :&lt;span&gt; O(n&amp;sup2;) + O(k log k) = O(n&amp;sup2;) &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀이 1( n&amp;sup3; )보다 풀이 2( n&amp;sup2; )가 시간복잡도로 봤을 때 더 효율적이다.&amp;nbsp;&lt;/p&gt;</description>
      <category>Algorithm</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/27</guid>
      <comments>https://dreamyard.tistory.com/entry/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%91%90-%EA%B0%9C-%EB%BD%91%EC%95%84%EC%84%9C-%EB%8D%94%ED%95%98%EA%B8%B0#entry27comment</comments>
      <pubDate>Mon, 23 Dec 2024 17:05:21 +0900</pubDate>
    </item>
    <item>
      <title>리액트 시작과 실행, 작동 원리</title>
      <link>https://dreamyard.tistory.com/entry/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%8B%9C%EC%9E%91%EA%B3%BC-%EC%8B%A4%ED%96%89-%EC%9E%91%EB%8F%99-%EC%9B%90%EB%A6%AC</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;리액트는 페이스북, 인스타그램, 넷플릭스 등을 비롯한 유명 서비스들에 사용된 기술로 많이 사용되고 있는 프론트엔드 기술입니다. 2013년 페이스북 팀이 개발한 오픈소스로, 서비스의 변화가 쉽고 사용자와 상호작용이 원활하도록 할 수 있는 기술 개발을 목적으로 만들어졌습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이러한 React 사용을 위해 로컬 환경(Visual studio 이용)에서 리액트 프로젝트를 시작하는 방법을 설명하고, 그 작동 원리를 이해해보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;리액트앱 생성하기 Create React App&amp;nbsp;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트를 생성할 폴더를 Visual Studio 로 열고, 터미널을 열어 아래 코드를 입력합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 만일 Need to install the following packages: ... 라는 메세지가 뜰 경우 y를 입력하면 됩니다.&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;297&quot; data-origin-height=&quot;68&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bon67c/btsLn62t0RV/kcfuecXOW6MF33l5WKtz3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bon67c/btsLn62t0RV/kcfuecXOW6MF33l5WKtz3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bon67c/btsLn62t0RV/kcfuecXOW6MF33l5WKtz3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbon67c%2FbtsLn62t0RV%2FkcfuecXOW6MF33l5WKtz3K%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;297&quot; height=&quot;68&quot; data-origin-width=&quot;297&quot; data-origin-height=&quot;68&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1734633577187&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npx create-react-app .&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ npx = Node Package Execute (노드 패키지 실행)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ . 은 현재의 폴더를 의미합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치에는 최대 5분정도 소요되니 이점 참고해서 진행해주세요. 설치가 완료되면 터미널에 Success! Created ... 이런 식의 문구가 뜹니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;의존성 충돌 오류가 났을 경우&lt;/h3&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;저같은 경우는 초기에 설치를 하다, 아래와 같은 오류가 떴는데,&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;npm&amp;nbsp;error&amp;nbsp;code&amp;nbsp;ERESOLVE&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;npm&amp;nbsp;error&amp;nbsp;ERESOLVE&amp;nbsp;unable&amp;nbsp;to&amp;nbsp;resolve&amp;nbsp;dependency&amp;nbsp;tree&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;찾아보니 &lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;의존성 충돌에 의한 오류였습니다.&lt;/span&gt; 프로젝트는 리액트의 최신버전 19.0.0을 사용하려하는데, @testing-library/react가 React 18.0.0 버전을 peer dependency로 요구해서 그런 거라고 하더라고요. 그래서 package.json 파일에서 react 버전을 18 버전으로 낮춰 설정하여 해결했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;비슷한 오류가 뜨신다면 참고해보셔도 될 것 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734634826251&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;dependencies&quot;: {
    &quot;cra-template&quot;: &quot;1.2.0&quot;,
    &quot;react&quot;: &quot;^18.2.0&quot;,
    &quot;react-dom&quot;: &quot;^18.2.0&quot;,
    &quot;react-scripts&quot;: &quot;5.0.1&quot;,
    &quot;web-vitals&quot;: &quot;^2.1.4&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;생성한 리액트앱 실행하기&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트앱 생성을 완료했다면 아래 코드를 터미널에 입력해 앱을 실행합니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734635094338&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm run start&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;404&quot; data-origin-height=&quot;163&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Hn4Aj/btsLodggMET/rsrZwQngT2e5dfS9BkQ1OK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Hn4Aj/btsLodggMET/rsrZwQngT2e5dfS9BkQ1OK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Hn4Aj/btsLodggMET/rsrZwQngT2e5dfS9BkQ1OK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHn4Aj%2FbtsLodggMET%2FrsrZwQngT2e5dfS9BkQ1OK%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;404&quot; height=&quot;163&quot; data-origin-width=&quot;404&quot; data-origin-height=&quot;163&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터미널에 위와 같은 문구가 뜨며 실행이 정상적으로 수행되면, http://localhost:3000/ 주소로 창의 띄워지며 아래와 같은 기본 리액트 화면이 보여집니다.&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;809&quot; data-origin-height=&quot;714&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAq8ga/btsLov8wtt9/7QYkfv3ORxpx5OntKYHzok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAq8ga/btsLov8wtt9/7QYkfv3ORxpx5OntKYHzok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAq8ga/btsLov8wtt9/7QYkfv3ORxpx5OntKYHzok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAq8ga%2FbtsLov8wtt9%2F7QYkfv3ORxpx5OntKYHzok%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;578&quot; height=&quot;510&quot; data-origin-width=&quot;809&quot; data-origin-height=&quot;714&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트 초기 화면에는 Edit src/App.js and save to reload 라는 문구가 뜨는데, src 폴더의 App.js를 수정하고 저장해서 다시 로드하라는 의미입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;App.js 파일을 열어보면 아래와 같이 구성되어있고, App 내부의 요소들을 수정해서 다시 로드하면 반영되는 것을 확인할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734635785069&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import logo from './logo.svg';
import './App.css';

function App() {
  return (
    &amp;lt;div className=&quot;App&quot;&amp;gt;
      &amp;lt;header className=&quot;App-header&quot;&amp;gt;
        &amp;lt;img src={logo} className=&quot;App-logo&quot; alt=&quot;logo&quot; /&amp;gt;
        &amp;lt;p&amp;gt;
          Edit &amp;lt;code&amp;gt;src/App.js&amp;lt;/code&amp;gt; and save to reload.
        &amp;lt;/p&amp;gt;
        &amp;lt;a
          className=&quot;App-link&quot;
          href=&quot;https://reactjs.org&quot;
          target=&quot;_blank&quot;
          rel=&quot;noopener noreferrer&quot;
        &amp;gt;
          Learn React
        &amp;lt;/a&amp;gt;
      &amp;lt;/header&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;리액트의 페이지 렌더링 원리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 사용자가 주소 입력을 통해 요청하면, 리액트 앱 서버는 웹 페이지 파일인 public폴더의 index.html을 보내게 됩니다. 그런데 이 index.html 파일 의 body 내부를 보면 딱히 보여질만한 게 없습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734636023180&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; ...
 
 &amp;lt;body&amp;gt;
    &amp;lt;noscript&amp;gt;You need to enable JavaScript to run this app.&amp;lt;/noscript&amp;gt;
    &amp;lt;div id=&quot;root&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the &amp;lt;body&amp;gt; tag.

      To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    --&amp;gt;
  &amp;lt;/body&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;개발자 도구를 살펴보면 단서를 찾을 수 있습니다. bundle.js 는 src 폴더 내부의 index.js와 이 파일이 불러오는 모든 모듈을 하나로 묶어놓은 파일입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;633&quot; data-origin-height=&quot;119&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0ERcl/btsLpHOffq3/mYKehrCSE1x12jefZc804k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0ERcl/btsLpHOffq3/mYKehrCSE1x12jefZc804k/img.png&quot; data-alt=&quot;개발자 도구 Elements 탭에서 확인&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0ERcl/btsLpHOffq3/mYKehrCSE1x12jefZc804k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0ERcl%2FbtsLpHOffq3%2FmYKehrCSE1x12jefZc804k%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;633&quot; height=&quot;119&quot; data-origin-width=&quot;633&quot; data-origin-height=&quot;119&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;개발자 도구 Elements 탭에서 확인&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;bundle.js&amp;nbsp; 더 알아보기&lt;/h3&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;bundle.js는 웹팩(Webpack)이라는 모듈 번들러에 의해 생성되는 파일로, React 프로젝트의 모든 JavaScript 파일들(컴포넌트, 유틸리티 함수 등)과 import된 모듈들을 하나의 파일로 묶어줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;묶어주는 목적은 아래와 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1) 하나의 파일만 요청하게 함으로써 네트워크 요청 시, 성능 향상&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2) 번들링 과정에서 코드가 압축하고, 불필요한 코드를 제거해 코드 최적화&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;3) 최신 Javascript 문법을 브라우저가 이해할 수 있는 코드를 변환함으로써 브라우저와의 호환성 향상&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;index.js 파일을 살펴보면, 아래와 같이 되어있고&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734636362054&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  &amp;lt;React.StrictMode&amp;gt;
    &amp;lt;App /&amp;gt;
  &amp;lt;/React.StrictMode&amp;gt;
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();&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;리액트의 App 컴포넌트를 import 해서 사용하고 있으며, ReactDOM.createRoot라는 인수로 전달한 요소를 리액트 앱의 루트 컴포넌트로 만들어 반환하는 메서드가 사용되고 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후, root.render가 되면 내부적으로 전달된 리액트 컴포넌트가 돔에 추가되어 렌더링 되어 웹 페이지에 보여지게 되는 원리입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 정리하면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) index.html이 클라이언트 요청에 의해 반환되면 index.html은 bundle.js를 실행시켜 index.js에서 작성된 코드가 실행되게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) index.js 에서는 ReactDOM.createRoot 메서드를 통해 루트 컴포넌트가 지정되게 되고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) render를 통해 루트 컴포넌트를 비롯한 자식 컴포넌트까지 모두 렌더링되며 웹페이지에 보여지게 되는 것 입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;※ 주요 용어 정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dom(Document Object Model) : 문서 객체 모델&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;렌더링 (Rendering) : 브라우저가 웹의 3가지 언어 HTML, CSS, 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;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/React</category>
      <category>리액트</category>
      <category>리액트시작하기</category>
      <category>리액트앱생성</category>
      <category>리액트앱실행</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/26</guid>
      <comments>https://dreamyard.tistory.com/entry/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%8B%9C%EC%9E%91%EA%B3%BC-%EC%8B%A4%ED%96%89-%EC%9E%91%EB%8F%99-%EC%9B%90%EB%A6%AC#entry26comment</comments>
      <pubDate>Fri, 20 Dec 2024 04:51:13 +0900</pubDate>
    </item>
    <item>
      <title>[Javascript] 동기 비동기의 개념과 Promise, async/await</title>
      <link>https://dreamyard.tistory.com/entry/Javascript-%EB%8F%99%EA%B8%B0-%EB%B9%84%EB%8F%99%EA%B8%B0%EC%9D%98-%EA%B0%9C%EB%85%90%EA%B3%BC-Promise-asyncawait</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Promise,async,await.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oJOZU/btsLiT2Gk4p/A9Qa9IwURURkzJkKnKrRxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oJOZU/btsLiT2Gk4p/A9Qa9IwURURkzJkKnKrRxK/img.png&quot; data-alt=&quot;[Javascript] 동기 비동기의 개념과 Promise, async/await&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oJOZU/btsLiT2Gk4p/A9Qa9IwURURkzJkKnKrRxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoJOZU%2FbtsLiT2Gk4p%2FA9Qa9IwURURkzJkKnKrRxK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;500&quot; data-filename=&quot;Promise,async,await.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[Javascript] 동기 비동기의 개념과 Promise, async/await&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Javascript 비동기 프로그래밍의 핵심 개념인 Promise, async/await 에 대해 정리해보고자 합니다. 설명에 앞서 먼저 동기와 비동기에 대해 간단히 정리하자면, &lt;b&gt;&lt;u&gt;동기 처리(Synchronous)&lt;/u&gt;&lt;/b&gt;란 &lt;u&gt;코드가 작성된 순서로 실행되며, 각 작업이 완료될 때까지 다음 작업이 기다리는 방식&lt;/u&gt;을 의미합니다. 반대로 &lt;u&gt;&lt;b&gt;비동기 처리(Asynchronous)&lt;/b&gt;&lt;/u&gt;란 &lt;u&gt;현재의 작업 완료를 기다리지 않고 다음 작업을 실행하는 방식&lt;/u&gt;입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좀 더 쉽게 표현하면,&lt;b&gt; 동기 처리는 줄 서서 기다리기, 비동기 처리는 주문하고 진동벨 받기&lt;/b&gt;에 비유할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면,&lt;b&gt; Javascript 에서 비동기 처리를 돕는 Promise, async/await는 왜 만들어진 것일까요?&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;복잡한 비동기 코드를 더 쉽고 깔끔하게&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비동기 처리는 &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;대기 시간동안 다른 작업이 가능하다는 점에서 사용자의 대기 시간을 줄이고 사용자 경험을 향상시킵니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 코드 실행 순서를 예측하기 어려울 수 있고, 콜백 지옥 등과 같은 복잡한 코드가 생길 수 있다는 단점이 있죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시 코드를 들어 설명해보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;대표적인 비동기 처리 메서드인 setTimeout 을 실행하는 workA, workB, workC 함수를 정의하고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1734191924947&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const workA = (value, callback) =&amp;gt; {
  setTimeout(() =&amp;gt; {
    callback(value + 5);
  }, 5000); // 5초 설정
};

const workB = (value, callback) =&amp;gt; {
  setTimeout(() =&amp;gt; {
    callback(value - 3);
  }, 3000); // 3초 설정
};

const workC = (value, callback) =&amp;gt; {
  setTimeout(() =&amp;gt; {
    callback(value + 10);
  }, 10000); // 10초 설정
};&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;이 코드들을 A,B,C 순으로 순차적으로 실행하기 위해 아래와 같이 코드를 작성해보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734192162196&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;workA(10, (resA) =&amp;gt; {
  console.log(`1. ${resA}`);
  workB(resA, (resB) =&amp;gt; {
    console.log(`2. ${resB}`);
    workC(resB, (resC) =&amp;gt; {
      console.log(`3. ${resC}`);
    });
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;결과&amp;nbsp;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;86&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjCwXB/btsLkqY0xCn/si8rrnGUkwt1jOHK2FTld1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjCwXB/btsLkqY0xCn/si8rrnGUkwt1jOHK2FTld1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjCwXB/btsLkqY0xCn/si8rrnGUkwt1jOHK2FTld1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjCwXB%2FbtsLkqY0xCn%2Fsi8rrnGUkwt1jOHK2FTld1%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;642&quot; height=&quot;86&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;86&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;workA, workB, workC를 순차적으로 계산하기 위해, CallBack 함수 내부에 또 다른 CallBack 함수를 넘기며 실행되도록 한 코드입니다. 이 코드는 각 함수의 실행 순서를 알기엔 쉽지만, 각각 어떻게 실행되는지 보기에는 가독성이 매우 떨어집니다. (이렇게 꺽쇠 모양으로 복잡하게 생긴 코드를 흔히 콜백 지옥이라고 합니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;u&gt;이러한 코드를 더 깔끔하고 가독성 좋게 작성할 수 있도록 해주는 것이 Promise, 더 나아가 async/await 입니다.&amp;nbsp;&lt;/u&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;비동기 작업을 더 편리하게 해주는 자바스크립트 내장 객체, Promise&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 먼저 Promise 객체부터 자세히 알아보겠습니다. Promise 객체는 자바스크립트의 비동기 작업을 더 편리하게 처리할 수 있도록 해주는 자바스크립트의 내장객체로 아래와 같은 방법으로 생성자를 사용해 생성 가능합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734192786026&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const executor = (resolve, reject) =&amp;gt; {
 // 실행 내용
}
const promise = new Promise(executor);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;promise 객체를 생성할 때는 위 코드와 같은 executor(실행자) 함수가 반드시 인수로 들어가야 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;executor의 인수로 들어가는 resolve와 reject는 자바스크립트에서 자체적으로 제공하는 콜백함수로, executor의 비동기 처리 성공시에는 resolve가, 실패시에는 reject가 호출됩니다. 비동기 처리는 항상 성공 or 실패 둘 중 하나이기 때문에, resolve와 reject 중 둘 중 하나는 반드시 호출 되어야 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, Promise 객체는 아래 사진처럼 state와 result라는 2가지 프로퍼티를 가지고, resolve와 reject의 호출에 따라 내부 프로퍼티의 상태가 변화합니다. 한 번 변경 처리가 끝난 Promise 객체에 resolve나 reject를 호출할 경우에는 무시됩니다.&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;1016&quot; data-origin-height=&quot;558&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbxzqh/btsLjTggJ41/78kYg5NVa1P02SNCzKi1E0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbxzqh/btsLjTggJ41/78kYg5NVa1P02SNCzKi1E0/img.png&quot; data-alt=&quot;출처 : 자바스크립트 중급 강좌 #16 프로미스(Promise) - 코딩앙마&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbxzqh/btsLjTggJ41/78kYg5NVa1P02SNCzKi1E0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbbxzqh%2FbtsLjTggJ41%2F78kYg5NVa1P02SNCzKi1E0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;665&quot; height=&quot;365&quot; data-origin-width=&quot;1016&quot; data-origin-height=&quot;558&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : 자바스크립트 중급 강좌 #16 프로미스(Promise) - 코딩앙마&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;resolve와 reject의 동작 방식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 resolve가 실행되면 promise의 프로퍼티가 변화하는데, state는 pending -&amp;gt; fulfilled로, result는 undefined에서 넘겨받는 value 값으로 변화합니다. reject가 실행될 경우도 위의 그림에서 본 값과 같이 변화됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, resolve와 reject냐에 따라 then, catch, finally 메서드를 통해 다음 실행할 코드를 설정할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;아래 코드를 통해 resolve와 reject의 동작 방식을 살펴보겠습니다. &lt;span&gt;&lt;/span&gt;&lt;/span&gt;먼저 아래와 같은 코드에서 resolve에 &quot;성공&quot;이라는 인수를 넣어 그 아래에서 실행해보겠습니다. 콘솔 결과를 보면, resolve에 넣은 인수는 then, reject에 넣은 인수는 catch를 통해 실행되며, finally는 resolve냐 reject냐에 무관하게 무조건 실행되는 것을 확인할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734193842294&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const executor = (resolve, reject) =&amp;gt; {
  setTimeout(() =&amp;gt; {
    resolve(&quot;성공&quot;);
  }, 3000);
};

const promise = new Promise(executor);
promise
  .then((res) =&amp;gt; {
    console.log(&quot;then -&amp;gt; &quot;, res);
  })
  .catch((rej) =&amp;gt; {
    console.log(&quot;catch -&amp;gt; &quot;, rej);
  })
  .finally(() =&amp;gt; {
    console.log(&quot;무조건 실행&quot;);
  });&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;639&quot; data-origin-height=&quot;65&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/szrpE/btsLinJ5qmv/xYhO2ZkBZ5K594pur4cEek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/szrpE/btsLinJ5qmv/xYhO2ZkBZ5K594pur4cEek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/szrpE/btsLinJ5qmv/xYhO2ZkBZ5K594pur4cEek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FszrpE%2FbtsLinJ5qmv%2FxYhO2ZkBZ5K594pur4cEek%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;639&quot; height=&quot;65&quot; data-origin-width=&quot;639&quot; data-origin-height=&quot;65&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734193872158&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const executor = (resolve, reject) =&amp;gt; {
  setTimeout(() =&amp;gt; {
    reject(&quot;실패&quot;);
  }, 3000);
};

const promise = new Promise(executor);
promise
  .then((res) =&amp;gt; {
    console.log(&quot;then -&amp;gt; &quot;, res);
  })
  .catch((rej) =&amp;gt; {
    console.log(&quot;catch -&amp;gt; &quot;, rej);
  })
  .finally(() =&amp;gt; {
    console.log(&quot;무조건 실행&quot;);
  });&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;71&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/C0Rpt/btsLiLjojuG/KK0ukUra6q3aTTD1tyamk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/C0Rpt/btsLiLjojuG/KK0ukUra6q3aTTD1tyamk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/C0Rpt/btsLiLjojuG/KK0ukUra6q3aTTD1tyamk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FC0Rpt%2FbtsLiLjojuG%2FKK0ukUra6q3aTTD1tyamk0%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;648&quot; height=&quot;71&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;71&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Promise 적용하기&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 지금 까지 배운 Promise를 처음에 보았던 workA, workB, workC 코드에 적용해보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;적용 전&lt;/h4&gt;
&lt;pre id=&quot;code_1734196479140&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const workA = (value, callback) =&amp;gt; {
  setTimeout(() =&amp;gt; {
    callback(value + 5);
  }, 5000); // 5초 설정
};

const workB = (value, callback) =&amp;gt; {
  setTimeout(() =&amp;gt; {
    callback(value - 3);
  }, 3000); // 3초 설정
};

const workC = (value, callback) =&amp;gt; {
  setTimeout(() =&amp;gt; {
    callback(value + 10);
  }, 10000); // 10초 설정
};

workA(10, (resA) =&amp;gt; {
  console.log(`1. ${resA}`);
  workB(resA, (resB) =&amp;gt; {
    console.log(`2. ${resB}`);
    workC(resB, (resC) =&amp;gt; {
      console.log(`3. ${resC}`);
    });
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;결과&amp;nbsp;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;86&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjCwXB/btsLkqY0xCn/si8rrnGUkwt1jOHK2FTld1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjCwXB/btsLkqY0xCn/si8rrnGUkwt1jOHK2FTld1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjCwXB/btsLkqY0xCn/si8rrnGUkwt1jOHK2FTld1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjCwXB%2FbtsLkqY0xCn%2Fsi8rrnGUkwt1jOHK2FTld1%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;642&quot; height=&quot;86&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;86&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;적용 후 (Promise Chaining)&lt;/h4&gt;
&lt;pre id=&quot;code_1734196367016&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const workA = (value) =&amp;gt; {
  const promise = new Promise((resolve, reject) =&amp;gt; {
    setTimeout(() =&amp;gt; {
      resolve(value + 5);
    }, 5000);
  });
  return promise;
};

const workB = (value) =&amp;gt; {
  const promise = new Promise((resolve, reject) =&amp;gt; {
    setTimeout(() =&amp;gt; {
      resolve(value - 3);
    }, 3000);
  });
  return promise;
};

const workC = (value) =&amp;gt; {
  const promise = new Promise((resolve, reject) =&amp;gt; {
    setTimeout(() =&amp;gt; {
      resolve(value + 10);
    }, 10000);
  });
  return promise;
};

workA(10)
  .then((resA) =&amp;gt; {
    console.log(`1. ${resA}`);
    return workB(resA);
  })
  .then((resB) =&amp;gt; {
    console.log(`2. ${resB}`);
    return workC(resB);
  })
  .then((resC) =&amp;gt; {
    console.log(`3. ${resC}`);
  });&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;결과&amp;nbsp;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;86&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjCwXB/btsLkqY0xCn/si8rrnGUkwt1jOHK2FTld1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjCwXB/btsLkqY0xCn/si8rrnGUkwt1jOHK2FTld1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjCwXB/btsLkqY0xCn/si8rrnGUkwt1jOHK2FTld1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjCwXB%2FbtsLkqY0xCn%2Fsi8rrnGUkwt1jOHK2FTld1%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;642&quot; height=&quot;86&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;86&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각의 함수는 promise 객체를 반환하기 때문에 .then 메서드를 마치 체인을 엮듯 연속적으로 사용할 수 있습니다. 이러한 사용 방법을 &lt;u&gt;'프로미스 체이닝'&lt;/u&gt;이라고 부릅니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 순서도 한눈에 보이고, 적용 전 코드의 콜백 지옥 모양과 다르게 코드의 가독성이 개선된 것을 확인할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Promise 객체를 더 쉽게 사용할 수 있게 해주는 async/await&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 멈추지 않고, Promise 객체를 더 쉽고 직관적으로 사용하게 해주는 것이 async/await 입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;async와 await를 적용해 아래 Promise와 then이 적용된 코드를 좀 더 개선해보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734197210769&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const delay = (ms) =&amp;gt; {
  return new Promise((resolve) =&amp;gt; {
    setTimeout(() =&amp;gt; {
      resolve();
    }, ms);
  });
};

const start = () =&amp;gt; {
  delay(2000).then(() =&amp;gt; {
    console.log(&quot;대기&quot;);
  });
};

start();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;결과&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;637&quot; data-origin-height=&quot;32&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmSdfV/btsLjR3P7ZG/PFLEpPcDT3IIn00ixPZVp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmSdfV/btsLjR3P7ZG/PFLEpPcDT3IIn00ixPZVp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmSdfV/btsLjR3P7ZG/PFLEpPcDT3IIn00ixPZVp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmSdfV%2FbtsLjR3P7ZG%2FPFLEpPcDT3IIn00ixPZVp0%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;637&quot; height=&quot;32&quot; data-origin-width=&quot;637&quot; data-origin-height=&quot;32&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프로미스를 반환해, async&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;start 함수에 먼저 async를 설정해주겠습니다. async 키워드를 특정 함수의 = 오른편에 입력해준 뒤, start 함수 위에 커서를 올리면 아래와 같이 Promise를 반환하는 함수라는 설명이 뜨는 것을 확인할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;이렇게 async를 설정해준 함수는 자동으로 promise를 반환하는 비동기처리 함수가 됩니다.&amp;nbsp;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;295&quot; data-origin-height=&quot;167&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dnX3b0/btsLjj7Ifhc/t80Um2QQ4M1kKm1rcwUrP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dnX3b0/btsLjj7Ifhc/t80Um2QQ4M1kKm1rcwUrP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dnX3b0/btsLjj7Ifhc/t80Um2QQ4M1kKm1rcwUrP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdnX3b0%2FbtsLjj7Ifhc%2Ft80Um2QQ4M1kKm1rcwUrP1%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;295&quot; height=&quot;167&quot; data-origin-width=&quot;295&quot; data-origin-height=&quot;167&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이렇게 async를 설정한 start 함수는 아래와 같이 설정해줄 수 있고, 이 때 start 함수에서 return 해주는 값은 프로미스 객체의 resolve의 결과값으로 전달되는 것이기 때문에 아래 사진의 코드처럼 then으로 처리가 가능합니다.&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;205&quot; data-origin-height=&quot;166&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mTWE5/btsLiXDOezP/Eyl62K8kkX8wkArFqbJs2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mTWE5/btsLiXDOezP/Eyl62K8kkX8wkArFqbJs2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mTWE5/btsLiXDOezP/Eyl62K8kkX8wkArFqbJs2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmTWE5%2FbtsLiXDOezP%2FEyl62K8kkX8wkArFqbJs2k%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;205&quot; height=&quot;166&quot; data-origin-width=&quot;205&quot; data-origin-height=&quot;166&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;끝날 때까지 기다려, await&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;await는 미리 정의된 특정 함수의 이름 바로 앞에 작성될 수 있는데,&amp;nbsp; async 로 설정된 함수의 내부에서만 사용 가능합니다. await는 기다리다라는 의미인 만큼, &lt;u&gt;설정된 함수(promise)가 종료될 때까지 그 다음 코드들을 실행되지 않습니다. &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;따라서, 아래 코드에서는 await 설정 전에는 console에 바로 &quot;대기&quot; 가 출력되었지만, await 설정 후에는 2초 후 출력됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;372&quot; data-origin-height=&quot;240&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFKs1M/btsLjK4MDgC/wBVZ5dzi84BWSNJWxxvK9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFKs1M/btsLjK4MDgC/wBVZ5dzi84BWSNJWxxvK9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFKs1M/btsLjK4MDgC/wBVZ5dzi84BWSNJWxxvK9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFKs1M%2FbtsLjK4MDgC%2FwBVZ5dzi84BWSNJWxxvK9K%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;372&quot; height=&quot;240&quot; data-origin-width=&quot;372&quot; data-origin-height=&quot;240&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;async/await 적용된 코드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래가 최종 수정된 코드 입니다. 가독성이 개선되고, 코드가 좀 더 심플해진 것을 확인하실 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734198097573&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const delay = (ms) =&amp;gt; {
  return new Promise((resolve) =&amp;gt; {
    setTimeout(() =&amp;gt; {
      resolve();
    }, ms);
  });
};

const start = async () =&amp;gt; {
  await delay(2000); // delay 실행 완료 후, 아래 코드 실행
  console.log(&quot;대기&quot;);
};

start();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;그럼, 코드 실행 실패(오류) 시에는?&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 Promise에서 처리하던 then과 catch와 같은 오류 처리를 async, await에서는 어떻게 할까요?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 아래와 같이 try-catch 문을 이용하면 됩니다. try에는 실행할 코드를,&amp;nbsp; catch에는 실행한 코드 오류 시 실행할 코드를 작성해주면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734198316543&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const delay = (ms) =&amp;gt; {
  return new Promise((resolve) =&amp;gt; {
    setTimeout(() =&amp;gt; {
      resolve();
    }, ms);
  });
};

const start = async () =&amp;gt; {
  try { // 실행할 코드 작성
    await delay(2000); 
    console.log(&quot;대기&quot;);
  } catch (error) { // 실행 코드에서 오류 발생 시 실행할 코드 작성
    console.log(error);
  }
};

start();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&amp;nbsp;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Promise&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 생성 시 실행자 함수가 반드시 인수로 전달되어야 하며, state와 result라는 프로퍼티를 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 프로퍼티는 resolve 또는 reject에 의해 각각 다르게 상태 변화하며, resolve 값은 then, reject 값은 catch에 의해 다음 처리 될 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;async&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 함수 표현식에서 '=' 오른쪽에 기입되며, async가 설정된 함수는 Promise 객체를 반환한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;await&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;- 함수명 왼쪽에 기입되며, async가 정의된 &lt;span&gt;함수 내부에서 사용되며, 적용된 메서드가 끝날 때까지 다음 코드가 실행되지 않는다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/JavaScript</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/25</guid>
      <comments>https://dreamyard.tistory.com/entry/Javascript-%EB%8F%99%EA%B8%B0-%EB%B9%84%EB%8F%99%EA%B8%B0%EC%9D%98-%EA%B0%9C%EB%85%90%EA%B3%BC-Promise-asyncawait#entry25comment</comments>
      <pubDate>Sun, 15 Dec 2024 03:06:02 +0900</pubDate>
    </item>
    <item>
      <title>HTML, CSS 에서의 문자셋 설정 charset, UTF-8</title>
      <link>https://dreamyard.tistory.com/entry/HTML-CSS-%EC%97%90%EC%84%9C%EC%9D%98-%EB%AC%B8%EC%9E%90%EC%85%8B-%EC%84%A4%EC%A0%95-charset-UTF-8</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Charset (Character set, 문자셋)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;웹 브라우저 또는 HTML 문서가 어떤 문자셋&lt;/span&gt;&lt;/b&gt;으로 되어있는지 명시해놓은 속성.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;encoding(인코딩)을 어떤 종류의 문자셋으로 해줄지, 즉 인코딩하는 방식을 정하는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;※ encoding(인코딩) : 사람의 언어를 컴퓨터가 읽을 수 있는 부호로 전환해주는 것이다. 반대로 decoding(디코딩)은 컴퓨터의 언어를 사람의 언어로 전화해주는 것을 의미한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Charset의 종류&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. ASCII(아스키 코드)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;가장 처음 만들어진 문자셋&lt;/b&gt;&lt;/span&gt;으로 &lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;127개의 영문자와 숫자&lt;/b&gt;&lt;/span&gt;로만 이루어져있다. 한글을 지원하지 않기 때문에 깨져서 나타난다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. UNICODE(유니코드)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;아스키 코드의 단점을 극복하기 위해 만들어진 코드 체계&lt;/b&gt;&lt;/span&gt;로 각 나라별 언어를 모두 표현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. UTF-8&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;유니코드를 사용하는 인코딩 방식 중 하나&lt;/span&gt;&lt;/b&gt;로, 전 세계 언어를 모두 표현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유니코드 자체로도 충분히 한글이 지원되지만, 영어의 경우 유니코드 사용 시 메모리 소요가 2배이상되므로 이를 해결하기 위해 만들어진 코드 체계이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UTF-8 유니코드는 아스키코드와 영문 영역에서 100% 호환되므로, UTF-8로 이루어진 문서가 영문과 숫자로만 되어있다면 아스키코드나 다름 없다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;HTML과 CSS에서 UTF-8 설정&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;HTML4 :&lt;/b&gt; &amp;lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html;charset=UTF-8&quot;&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;HTML5:&lt;/b&gt; &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CSS :&lt;/b&gt; @charset &quot;UTF-8&quot;;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목을 입력해주세요__복사본-002.png&quot; data-origin-width=&quot;533&quot; data-origin-height=&quot;533&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8MouJ/btsnGOP9U9N/Vt0wKzzoAgbi30xDx16GzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8MouJ/btsnGOP9U9N/Vt0wKzzoAgbi30xDx16GzK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8MouJ/btsnGOP9U9N/Vt0wKzzoAgbi30xDx16GzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8MouJ%2FbtsnGOP9U9N%2FVt0wKzzoAgbi30xDx16GzK%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; alt=&quot;HTML&amp;amp;#44; CSS 에서의 문자셋 설정 charset&amp;amp;#44; UTF-8&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;300&quot; data-filename=&quot;제목을 입력해주세요__복사본-002.png&quot; data-origin-width=&quot;533&quot; data-origin-height=&quot;533&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/HTML</category>
      <category>ascii</category>
      <category>Charset</category>
      <category>Unicode</category>
      <category>UTF-8</category>
      <category>디코딩</category>
      <category>아스키</category>
      <category>유니코드</category>
      <category>인코딩</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/23</guid>
      <comments>https://dreamyard.tistory.com/entry/HTML-CSS-%EC%97%90%EC%84%9C%EC%9D%98-%EB%AC%B8%EC%9E%90%EC%85%8B-%EC%84%A4%EC%A0%95-charset-UTF-8#entry23comment</comments>
      <pubDate>Sun, 16 Jul 2023 02:26:15 +0900</pubDate>
    </item>
    <item>
      <title>HTML form 관련 태그 / button태그와 input type=button 의 차이</title>
      <link>https://dreamyard.tistory.com/entry/HTML-form-%EA%B4%80%EB%A0%A8-%ED%83%9C%EA%B7%B8-button%ED%83%9C%EA%B7%B8%EC%99%80-input-typebutton-%EC%9D%98-%EC%B0%A8%EC%9D%B4</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;script async src=&quot;https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8062209632442181&quot;
     crossorigin=&quot;anonymous&quot;&gt;&lt;/script&gt;
&lt;!-- [디스플레이,사각형, 반응형] 최상단 광고 --&gt;
&lt;ins class=&quot;adsbygoogle&quot;
     style=&quot;display:block&quot;
     data-ad-client=&quot;ca-pub-8062209632442181&quot;
     data-ad-slot=&quot;6459821048&quot;
     data-ad-format=&quot;auto&quot;
     data-full-width-responsive=&quot;true&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목을 입력해주세요__복사본-001 (3).png&quot; data-origin-width=&quot;533&quot; data-origin-height=&quot;533&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lJi2j/btsmrCdm7ut/9FZSqUZNfCDH8EVkf0awT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lJi2j/btsmrCdm7ut/9FZSqUZNfCDH8EVkf0awT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lJi2j/btsmrCdm7ut/9FZSqUZNfCDH8EVkf0awT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlJi2j%2FbtsmrCdm7ut%2F9FZSqUZNfCDH8EVkf0awT0%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; alt=&quot;HTML form 태그&quot; loading=&quot;lazy&quot; width=&quot;533&quot; height=&quot;533&quot; data-filename=&quot;제목을 입력해주세요__복사본-001 (3).png&quot; data-origin-width=&quot;533&quot; data-origin-height=&quot;533&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;form&amp;nbsp;태그&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블록레벨&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;온라인 서식(검색&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;로그인&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;회원가입 등)에서 입력한 값들을 처리하는 프로그램으로 전송할 때 사용하는 태그&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;사용예) &amp;lt;form&amp;gt;&amp;lt;/form&amp;gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;속성&lt;span&gt; : action, method&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;1) action : &lt;/span&gt;서식에서 작성한 값을 처리하는 프로그램의 주소&lt;span&gt;(URL)&lt;/span&gt;를 지정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;2) method : &lt;/span&gt;서식의 값들을 서버 프로그램에 어떤 방식으로 전달할 것인지 정의&lt;span&gt;. &lt;/span&gt;방식은&lt;span&gt; get(&lt;/span&gt;검색&lt;span&gt;), post(&lt;/span&gt;로그인&lt;span&gt;,&lt;/span&gt;회원가입 등 중요한 정보 전송&lt;span&gt;)&lt;/span&gt;방식 사용&lt;span&gt;. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퍼블리싱 시, 개발자와의 원활한 협업을 위해 만들어놓는 것이 좋다&lt;span&gt;. &amp;lt;form action=&quot;&quot; method=&quot;&quot; &amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;fieldset 태그&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블록 레벨 요소.&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;양식 요소들을 그룹화 할 때 사용&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(&lt;/span&gt;가로로 묶음)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식요소로&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;lt;legend&amp;gt;&amp;lt;/legend&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;사용하여&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;fieldset&lt;/span&gt;의 제목을 정의 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;사용예) &amp;lt;fieldset&amp;gt;&amp;lt;/fieldset&amp;gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;코딩 &lt;/span&gt;예시)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;lt;body&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&amp;lt;form action=&quot;#&quot;method=&quot;post&quot;&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&amp;lt;fieldset&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&amp;lt;legend&amp;gt;&lt;/span&gt;로그인&lt;span&gt;&amp;lt;/legend&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&amp;lt;p&amp;gt;ID : &amp;lt;input type=&quot;text&quot;&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&amp;lt;p&amp;gt;PW : &amp;lt;input type=&quot;password&quot;&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&amp;lt;/fieldset&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;script async src=&quot;https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8062209632442181&quot;
     crossorigin=&quot;anonymous&quot;&gt;&lt;/script&gt;
&lt;!-- 사각형, 반응형, 중간광고 --&gt;
&lt;ins class=&quot;adsbygoogle&quot;
     style=&quot;display:block&quot;
     data-ad-client=&quot;ca-pub-8062209632442181&quot;
     data-ad-slot=&quot;9351322667&quot;
     data-ad-format=&quot;auto&quot;
     data-full-width-responsive=&quot;true&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;input태그&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인라인블럭&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;사용예 ) &amp;lt;input&amp;gt;&amp;lt;/input&amp;gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;속성 : type, id, placeholder&lt;span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;속성 type&lt;/span&gt;의 속성값&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- text : &lt;/span&gt;한줄짜리 글 입력 상자&lt;span&gt;. maxlength &lt;/span&gt;속성으로 최대 글자 수 제한 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- password : &lt;/span&gt;비밀번호 입력 상자&lt;span&gt;. &lt;/span&gt;입력 내용&lt;span&gt; *&lt;/span&gt;로 표시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- radio : &lt;/span&gt;라디오 버튼&lt;span&gt;, &lt;/span&gt;여러 개 중 하나만 선택&lt;span&gt; (&lt;/span&gt;요소의&lt;span&gt; name&lt;/span&gt;값이 같도록 설정해야 함&lt;span&gt;). checked &lt;/span&gt;속성 정의 시 해당&lt;span&gt; radio&lt;/span&gt;가 기본으로 체크되어 표시됨&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- checkbox : &lt;/span&gt;체크 박스&lt;span&gt;, &lt;/span&gt;다중 선택 가능&lt;span&gt;. checked &lt;/span&gt;속성 정의 시 해당&lt;span&gt; checkbox&lt;/span&gt;가 기본으로 체크되어 표시됨&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- reset : &lt;/span&gt;초기화 버튼&lt;span&gt;, value &lt;/span&gt;속성으로 버튼에 표시되는 텍스트 지정&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;- submit (데이터 전송 기능&lt;span style=&quot;text-align: start;&quot;&gt;) :&amp;nbsp;&lt;/span&gt;전송 버튼&lt;span style=&quot;text-align: start;&quot;&gt;, value&amp;nbsp;&lt;/span&gt;속성으로 버튼에 표시되는 텍스트 지정.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;- button(데이터 전송 기능) : 범용 버튼, value 속성으로 버튼에 표시되는 텍스트 지정.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;- image(데이터 전송 기능) : 이미지 버튼, src, alt 속성 반드시 정의&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- file : &lt;/span&gt;첨부 파일&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- hidden : &lt;/span&gt;사용자에게 노출할 필요 없는 데이터 지정&lt;span&gt;. &lt;/span&gt;화면에 표시되지 않음&lt;span&gt; (&lt;/span&gt;프로그래머 사용&lt;span&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- search : &lt;/span&gt;검색 입력 상자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- email : &lt;/span&gt;이메일 입력 상자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- tel : &lt;/span&gt;전화번호 입력 상자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- color : &lt;/span&gt;컬러 입력 상자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- number : &lt;/span&gt;숫자 입력 상자&lt;span&gt;. &lt;/span&gt;숫자의 최솟값&lt;span&gt;, &lt;/span&gt;최댓값을&lt;span&gt; min, max &lt;/span&gt;속성으로 지정 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- range : &lt;/span&gt;슬라이드 바&lt;span&gt;, &lt;/span&gt;최솟값&lt;span&gt;, &lt;/span&gt;최댓값을&lt;span&gt; min, max &lt;/span&gt;속성으로 지정 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;textarea태그&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인라인 블럭&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;여러줄 텍스트 입력 상자 태그. 주로 사이트 약관같은 것에 사용.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;사용예 )&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&amp;lt;textarea&amp;gt;&amp;lt;/textarea&amp;gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;속성&lt;span&gt; : rows, cols &lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;rows : 텍스트 입력 상자의 행 개수 지정&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;cols : 텍스트 입력 상자의 열 개수 지정&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;script async src=&quot;https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8062209632442181&quot;
     crossorigin=&quot;anonymous&quot;&gt;&lt;/script&gt;
&lt;!-- 사각형, 반응형, 중간광고 --&gt;
&lt;ins class=&quot;adsbygoogle&quot;
     style=&quot;display:block&quot;
     data-ad-client=&quot;ca-pub-8062209632442181&quot;
     data-ad-slot=&quot;9351322667&quot;
     data-ad-format=&quot;auto&quot;
     data-full-width-responsive=&quot;true&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;select태그&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인라인 블럭&lt;span&gt;, &lt;/span&gt;선택 목록 상자 태그&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용예 ) &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;lt;select&amp;gt;&amp;lt;/select&amp;gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;속성&lt;span&gt; : &amp;lt;select name=&quot;&quot; id=&quot;&quot;&amp;gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;lt;option&amp;gt;&amp;lt;/option&amp;gt;&lt;/span&gt;태그만 자식으로 올 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;lt;option&amp;gt;&lt;/span&gt;태그에&lt;span&gt; selected &lt;/span&gt;속성 정의 되면 해당&lt;span&gt; option&lt;/span&gt;이 기본값으로 설정됨 (&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;selected&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;사용 잘 안하기도 함 - h)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;option&lt;/span&gt;의 속성은&lt;span&gt; value. &amp;lt;option value=&quot;&quot;&amp;gt;&amp;lt;/option&amp;gt;. value값은 개발자가 설정함.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ 선택버튼&lt;span&gt;(radio, checkbox)&lt;/span&gt;은&lt;span&gt; checked &lt;/span&gt;인점 구별&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;button태그&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;form&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;태그 안에서 작동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;사용예 )&lt;span&gt; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;&amp;lt;button&amp;gt;&amp;lt;/button&amp;gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;속성&lt;span&gt;&amp;nbsp; : type = &lt;/span&gt;속성값&lt;span&gt; submit(default&lt;/span&gt;값&lt;span&gt;), button, reset &lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;button : &lt;/span&gt;자체로는 아무런 이벤트가 없고&lt;span&gt; click &lt;/span&gt;이벤트와 연결시켜서 자바 스크립트를 활용하는 방법을 많이 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;submit : &lt;/span&gt;폼을 제출하는 이벤트를 발생시킨다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;reset : &lt;/span&gt;폼 안에 작성된 내용을 초기화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;※ button태그와 input type=button 의 차이&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;1) button&lt;/span&gt;은 스스로 닫지 않는 태그&lt;span&gt;, input&lt;/span&gt;은 스스로 닫는 태그&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;2) button&lt;/span&gt;은 하위 태그 추가 가능&lt;span&gt;, input&lt;/span&gt;은 불가&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;3)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;이미지 삽입 시에도&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;button&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;태그는&lt;span&gt;img&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;태그로 해결&lt;span&gt;. input&lt;/span&gt;은&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;img&lt;/span&gt;불가&lt;span&gt;(css&lt;/span&gt;의&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;background-image&lt;/span&gt;사용&lt;span&gt;).&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이미지는&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;img&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;태그를 사용하는 것이 검색 엔진 최적화 등에 도움이 된다는 사실이 있다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;함께 참고하면 좋은 링크 : &lt;/span&gt;&lt;a href=&quot;https://cocoder16.tistory.com/18&quot;&gt;https://cocoder16.tistory.com/18&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;script async src=&quot;https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8062209632442181&quot;
     crossorigin=&quot;anonymous&quot;&gt;&lt;/script&gt;
&lt;!-- 사각형, 반응형, 중간광고 --&gt;
&lt;ins class=&quot;adsbygoogle&quot;
     style=&quot;display:block&quot;
     data-ad-client=&quot;ca-pub-8062209632442181&quot;
     data-ad-slot=&quot;9351322667&quot;
     data-ad-format=&quot;auto&quot;
     data-full-width-responsive=&quot;true&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;label태그&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인라인 요소.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 입력 태그&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;lt;input&amp;gt;, &amp;lt;textarea&amp;gt;, &amp;lt;select&amp;gt;&lt;/span&gt;들에 대한 제목을 정의하는 태그.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;lt;label&amp;gt;&lt;/span&gt;태그를 사용자 입력 태그와 제목으로 연결하기 위해선&lt;span&gt;,&amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;lt;label&amp;gt;&lt;/span&gt;태그의&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;for&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;속성 값과 사용자 입력 태그의&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;id&lt;/span&gt;속성값을 동일하게 정의해야 한다&lt;span&gt;. &lt;/span&gt;예시&lt;span&gt;) &amp;lt;label for=&quot;id&quot;&amp;gt;&lt;/span&gt;아이디&lt;span&gt;&amp;lt;/label&amp;gt; &amp;lt;input type=&quot;text&quot;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;id=&quot;id&quot;&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;label&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;태그의 텍스트를 클릭하면 연결된 사용자 입력 요소로 포커스가 이동됨.&amp;nbsp;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;label&amp;gt;&lt;/span&gt;태그가 끝나면&lt;span&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;한줄 띄어진다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;사용예 )&amp;lt;label&amp;gt;&amp;lt;/label&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;속성&lt;span&gt; &lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;placeholder : &lt;/span&gt;입력 내용에 대한 힌트를 제공&lt;span&gt;. &lt;/span&gt;내용이 비어있는 상태에서 입력한 속성값이 표시됨&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시&lt;span&gt;) &amp;lt;p&amp;gt;&amp;lt;label for=&quot;id&quot;&amp;gt;&lt;/span&gt;아이디&lt;span&gt;&amp;lt;/label&amp;gt;&amp;lt;input type=&quot;text&quot; id=&quot;id&quot; placeholder=&quot;&lt;/span&gt;아이디&lt;span&gt;&quot; title=&quot;&lt;/span&gt;아이디입력&lt;span&gt;&quot; /&amp;lt;p&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;readonly : &lt;/span&gt;입력된 내용을 수정할 수 없도록 만들어주는 속성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;script async src=&quot;https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8062209632442181&quot;
     crossorigin=&quot;anonymous&quot;&gt;&lt;/script&gt;
&lt;!-- [디스플레이,사각형, 반응형] 최하단 광고 --&gt;
&lt;ins class=&quot;adsbygoogle&quot;
     style=&quot;display:block&quot;
     data-ad-client=&quot;ca-pub-8062209632442181&quot;
     data-ad-slot=&quot;3530922293&quot;
     data-ad-format=&quot;auto&quot;
     data-full-width-responsive=&quot;true&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/HTML</category>
      <category>Button</category>
      <category>fieldset</category>
      <category>form</category>
      <category>html</category>
      <category>input</category>
      <category>label</category>
      <category>select</category>
      <category>textarea</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/22</guid>
      <comments>https://dreamyard.tistory.com/entry/HTML-form-%EA%B4%80%EB%A0%A8-%ED%83%9C%EA%B7%B8-button%ED%83%9C%EA%B7%B8%EC%99%80-input-typebutton-%EC%9D%98-%EC%B0%A8%EC%9D%B4#entry22comment</comments>
      <pubDate>Wed, 5 Jul 2023 00:09:01 +0900</pubDate>
    </item>
    <item>
      <title>웹 디자인 및 프로그래밍 시 자주 활용되는 주요 플러그인 사이트 5</title>
      <link>https://dreamyard.tistory.com/entry/%EC%9B%B9-%EB%94%94%EC%9E%90%EC%9D%B8-%EB%B0%8F-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EC%8B%9C-%EC%9E%90%EC%A3%BC-%ED%99%9C%EC%9A%A9%EB%90%98%EB%8A%94-%EC%A3%BC%EC%9A%94-%ED%94%8C%EB%9F%AC%EA%B7%B8%EC%9D%B8-%EC%82%AC%EC%9D%B4%ED%8A%B8-5</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;웹 디자인 및 프로그래밍 시 자주 활용되는 주요 플러그인 사이트 5가지 &lt;/b&gt;&lt;/span&gt;를 모아보았습니다 :)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;1. GSAP&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Greensock 에서 만든 애니메이션 플랫폼으로 jQuery에 비해 20배 빠른 성능을 보여 줍니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;다양한 웹 애니메이션을 구현할 수 있는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;JavaScript&amp;nbsp;&lt;/span&gt;로 구성된 애니메이션 라이브러리 입니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;250&quot;&gt;&lt;a href=&quot;https://greensock.com/gsap/&quot; target=&quot;&quot; title=&quot;GSAP&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6Usy8/btslJ3vKsQL/holsFBvPqEYWRwso42jOJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6Usy8%2FbtslJ3vKsQL%2FholsFBvPqEYWRwso42jOJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;250&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;250&quot;/&gt;&lt;/a&gt;&lt;figcaption&gt;GSAP 이미지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;2. Fullpage&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;웹 사이트의 전체 화면을 스크롤 형태로 만들 수 있는 JavaScript 라이브러리 입니다. 웹 사이트 구역 안에 수직 방향으로 스크롤 되거나 수평 방향으로 슬라이드를 넣을 수 있습니다. (수평 방향 스크롤 형태는 현재 유료 입니다.)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;250&quot;&gt;&lt;a href=&quot;https://alvarotrigo.com/fullPage/ko/&quot; target=&quot;&quot; title=&quot;fullPage&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/djDUkK/btslLhGPnMS/MhXYThKaY4IJhCtukUyo70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdjDUkK%2FbtslLhGPnMS%2FMhXYThKaY4IJhCtukUyo70%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;250&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;250&quot;/&gt;&lt;/a&gt;&lt;figcaption&gt;Fullpage 이미지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;3. AOS&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;스크롤 에니메이션 라이브러리 입니다. 스크롤을 내리며 박스가 나타나는 애니메이션을 지정할 수 있습니다. 사이트 전체가 예시로 이루어져 있어 보기 편리하고 적용하기가 쉽습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;3.png&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;250&quot;&gt;&lt;a href=&quot;https://michalsnik.github.io/aos/&quot; target=&quot;&quot; title=&quot;AOS&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQ0pRB/btslImbCbAX/fMasxDRu7CEWgKWGffAh31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQ0pRB%2FbtslImbCbAX%2FfMasxDRu7CEWgKWGffAh31%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;250&quot; data-filename=&quot;3.png&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;250&quot;/&gt;&lt;/a&gt;&lt;figcaption&gt;AOS 이미지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;4. Three.js&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;웹 페이지에 3D 객체를 쉽게 렌더링 할 수 있도록 도와주는 JavaScript 라이브러리 입니다. 렌더링 뿐만 아니라 인터렉션 적인 부분도 3D 형태로 표현 가능합니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;4.png&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;250&quot;&gt;&lt;a href=&quot;https://threejs.org/&quot; target=&quot;&quot; title=&quot;threejs&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kwJC3/btslNln5ZUM/O1sBX2GpX0nK0twx1jlve1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkwJC3%2FbtslNln5ZUM%2FO1sBX2GpX0nK0twx1jlve1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;250&quot; data-filename=&quot;4.png&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;250&quot;/&gt;&lt;/a&gt;&lt;figcaption&gt;Three.js 이미지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;5. Swiper&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;슬라이드 라이브러리로 웹 사이트에 사용되는 다양한 슬라이드와 관련된 동작을 쉽게 구현할 수 있습니다. CSS를 통해 원하는 형태로 커스텀 가능합니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;5.png&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;250&quot;&gt;&lt;a href=&quot;https://swiperjs.com/&quot; target=&quot;&quot; title=&quot;swiperjs&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckujLC/btslKkxjYXG/vFDq3AeEjpl73nYZ6J5pek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FckujLC%2FbtslKkxjYXG%2FvFDq3AeEjpl73nYZ6J5pek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;250&quot; data-filename=&quot;5.png&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;250&quot;/&gt;&lt;/a&gt;&lt;figcaption&gt;Swiper 이미지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목을 입력해주세요__복사본-001 (3).png&quot; data-origin-width=&quot;533&quot; data-origin-height=&quot;533&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dgZFrl/btslLFHnyBb/wzJHF4ee95zl8g3yoQV1S1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dgZFrl/btslLFHnyBb/wzJHF4ee95zl8g3yoQV1S1/img.png&quot; data-alt=&quot;웹 프로그래밍에 사용하는 플러그인 사이트 5&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dgZFrl/btslLFHnyBb/wzJHF4ee95zl8g3yoQV1S1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdgZFrl%2FbtslLFHnyBb%2FwzJHF4ee95zl8g3yoQV1S1%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;300&quot; height=&quot;300&quot; data-filename=&quot;제목을 입력해주세요__복사본-001 (3).png&quot; data-origin-width=&quot;533&quot; data-origin-height=&quot;533&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;웹 프로그래밍에 사용하는 플러그인 사이트 5&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Development/HTML</category>
      <category>AoS</category>
      <category>fullpage</category>
      <category>gsap</category>
      <category>Plugin</category>
      <category>swiper</category>
      <category>three.js</category>
      <category>프로그래밍</category>
      <category>플러그인</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/21</guid>
      <comments>https://dreamyard.tistory.com/entry/%EC%9B%B9-%EB%94%94%EC%9E%90%EC%9D%B8-%EB%B0%8F-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EC%8B%9C-%EC%9E%90%EC%A3%BC-%ED%99%9C%EC%9A%A9%EB%90%98%EB%8A%94-%EC%A3%BC%EC%9A%94-%ED%94%8C%EB%9F%AC%EA%B7%B8%EC%9D%B8-%EC%82%AC%EC%9D%B4%ED%8A%B8-5#entry21comment</comments>
      <pubDate>Wed, 28 Jun 2023 22:23:19 +0900</pubDate>
    </item>
    <item>
      <title>Table 태그</title>
      <link>https://dreamyard.tistory.com/entry/Table-%ED%83%9C%EA%B7%B8</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목을 입력해주세요__복사본-001 (2).png&quot; data-origin-width=&quot;533&quot; data-origin-height=&quot;533&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PxoXL/btskIdGwfxT/VthPqBOimNMvmoLFYri8SK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PxoXL/btskIdGwfxT/VthPqBOimNMvmoLFYri8SK/img.png&quot; data-alt=&quot;HTML table 태그&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PxoXL/btskIdGwfxT/VthPqBOimNMvmoLFYri8SK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPxoXL%2FbtskIdGwfxT%2FVthPqBOimNMvmoLFYri8SK%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; alt=&quot;HTML table 태그&quot; loading=&quot;lazy&quot; width=&quot;533&quot; height=&quot;533&quot; data-filename=&quot;제목을 입력해주세요__복사본-001 (2).png&quot; data-origin-width=&quot;533&quot; data-origin-height=&quot;533&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;HTML table 태그&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #303030;&quot;&gt;1. Table 태그 : &lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #303030;&quot;&gt;명확한 표의 형식에만&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #303030;&quot;&gt;사용하는 태그&lt;/span&gt;&lt;span style=&quot;color: #303030;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #303030;&quot;&gt;사용 예시 )&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #303030;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #303030; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;lt;table&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;color: #303030;&quot;&gt;&amp;nbsp; &amp;lt;caption&amp;gt; 선물용과 가정용 상품 구성&amp;lt;/caption&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #303030;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #303030; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp; &amp;lt;thead&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #303030; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;lt;tr&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #303030; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;th&amp;gt;용도&amp;lt;/th&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #303030; text-align: start; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;th&amp;gt;중량&amp;lt;/th&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #303030; text-align: start; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;th&amp;gt;개수&amp;lt;/th&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #303030; text-align: start; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;th&amp;gt;가격&amp;lt;/th&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #303030; text-align: start; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;lt;/tr&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #303030; text-align: start; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp; &amp;lt;/thead&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #303030; text-align: start; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp; &amp;lt;tbody&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #303030; text-align: start; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;lt;tr&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #303030; text-align: start; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;td&amp;gt;선물용&amp;lt;/td&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #303030; text-align: start; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;td&amp;gt;3kg&amp;lt;/td&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #303030; text-align: start; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;td&amp;gt;11~16과&amp;lt;/td&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #303030; text-align: start; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;td&amp;gt;35,000원&amp;lt;/td&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #303030; text-align: start; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;lt;/tr&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #303030; text-align: start; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;lt;tr&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #303030; text-align: start; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;td&amp;gt;가정용&amp;lt;/td&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #303030; text-align: start; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;td&amp;gt;4kg&amp;lt;/td&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #303030; text-align: start; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;td&amp;gt;11~16과&amp;lt;/td&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #303030; text-align: start; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;td&amp;gt;21,000원&amp;lt;/td&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #303030; text-align: start; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;lt;/tr&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #303030; text-align: start; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp; &amp;lt;/tbody&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #303030; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #303030;&quot;&gt;2. caption&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;color: #303030;&quot;&gt;&amp;lt;caption&amp;gt;&amp;lt;/caption&amp;gt; : &lt;/span&gt;&lt;span style=&quot;color: #303030;&quot;&gt;표 제목 , 위쪽 중앙에 표시, 생략가능&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #303030;&quot;&gt;3. scope&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #303030; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;lt;scope&amp;gt;&amp;lt;/scope&amp;gt; : 열 제목, 행 제목 셀&amp;lt;th&amp;gt;에 정의하는 속성&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #303030; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;행 제목 : &amp;lt;th scope=&quot;row&quot;&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #303030; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;열 제목 : &amp;lt;th scope=&quot;col&quot;&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #303030; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;낭독기에선 scope가 정의된 셀과 함께 td를 읽어준다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;4. &lt;span style=&quot;color: #303030;&quot;&gt;&amp;lt;tr&amp;gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #303030; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;행을 만드는 태그&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #303030;&quot;&gt;5. &lt;span style=&quot;color: #303030;&quot;&gt;&amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #303030; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;행안의 셀을 만드는 태그&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #303030; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;colspan=&quot;n&quot; 속성&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #303030;&quot;&gt;6. &lt;span style=&quot;color: #303030;&quot;&gt;&amp;lt;th&amp;gt;&amp;lt;/th&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #303030; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;제목 행에 셀을 만드는 태그&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #303030; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;내용이 진하게 표시, 중앙 배열&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #303030;&quot;&gt;7. 속성 &lt;span style=&quot;color: #303030; text-align: start;&quot;&gt;rowspan, &lt;span style=&quot;color: #303030; text-align: start;&quot;&gt;colspan&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #303030; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;속성 rowspan&lt;span style=&quot;color: #303030; text-align: start;&quot;&gt;=&quot;n&quot;&amp;nbsp;&lt;/span&gt; &lt;span style=&quot;color: #303030; text-align: start;&quot;&gt;아래와 같이&amp;nbsp;&lt;/span&gt;행을 합칠 때 사용&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 31.3953%; height: 51px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 34px;&quot; rowspan=&quot;2&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #303030; letter-spacing: 0px; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;속성 colspan=&quot;n&quot; 아래와 같이 열을 합칠 때 사용&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 31.3953%; height: 51px;&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: 66.6666%;&quot; colspan=&quot;2&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;사용 예시 )&amp;nbsp; &lt;/b&gt;&amp;lt;td rowspan=&quot;합칠 셀의 개수&quot;&amp;gt;셀의 내용&amp;lt;/td&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;8. 열을 묶는 태그&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;color: #303030;&quot;&gt;&amp;lt;colgroup&amp;gt;, &amp;lt;col&amp;gt; :&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #303030;&quot;&gt;배경, 너비 등을 특정 열에&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #303030;&quot;&gt;지정할 때 사용&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #303030;&quot;&gt;사용예시 )&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #303030; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;lt;table&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #303030; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp; &amp;lt;caption&amp;gt;선물용과 가정용 상품 구성&amp;lt;/caption&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #303030; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp; &amp;lt;colgroup&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #303030; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;lt;col style=&quot;background-color:#eee;&quot;&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #303030; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;lt;col&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #303030; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;lt;col span=&quot;2&quot; style=&quot;width:150px;&quot;&amp;gt; // 스타일 속성이 같은 세번째, 네번째 열을 묶어서 너비 지정&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #303030; text-align: start; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp; &amp;lt;colgroup&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #303030; text-align: start; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp; &amp;lt;thead&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #303030; text-align: start; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;(... 이하 생략)&lt;/span&gt;&lt;/p&gt;</description>
      <category>Development/HTML</category>
      <category>colgroup</category>
      <category>table</category>
      <category>TABLE태그</category>
      <category>table태그기본</category>
      <category>TD</category>
      <category>TR</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/20</guid>
      <comments>https://dreamyard.tistory.com/entry/Table-%ED%83%9C%EA%B7%B8#entry20comment</comments>
      <pubDate>Wed, 21 Jun 2023 00:17:02 +0900</pubDate>
    </item>
    <item>
      <title>HTML의 기본 구조와 개념</title>
      <link>https://dreamyard.tistory.com/entry/HTML%EC%9D%98-%EA%B8%B0%EB%B3%B8-%EA%B5%AC%EC%A1%B0%EC%99%80-%EA%B0%9C%EB%85%90</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목을 입력해주세요__복사본-001 (1).png&quot; data-origin-width=&quot;533&quot; data-origin-height=&quot;533&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buombC/btskKDcPFBR/DJTpxcvS9n20Hw6BjJ0hG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buombC/btskKDcPFBR/DJTpxcvS9n20Hw6BjJ0hG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buombC/btskKDcPFBR/DJTpxcvS9n20Hw6BjJ0hG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuombC%2FbtskKDcPFBR%2FDJTpxcvS9n20Hw6BjJ0hG1%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;533&quot; height=&quot;533&quot; data-filename=&quot;제목을 입력해주세요__복사본-001 (1).png&quot; data-origin-width=&quot;533&quot; data-origin-height=&quot;533&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #303030;&quot;&gt;1. HTML의 기본 구조 &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #303030;&quot;&gt;&amp;lt;html&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #303030;&quot;&gt;&amp;nbsp; &amp;lt;head&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #303030;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #303030;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;lt;meta&amp;gt; //&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #303030;&quot;&gt;웹브라우저에는 보이지 않지만 웹문서와 관련된 정보를 지정할 때 사용.&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #303030;&quot;&gt;한글로된 내용을 표시할 때는 UTF-8이라는 문자 세트를 사용한다고 웹브라우저에 알려주어야 한다. &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt; 하지 않으면 한글이 깨질수도 있다.&lt;/span&gt;&lt;span style=&quot;color: #303030;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #303030;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;lt;title&amp;gt;웹문서의 제목 입력&amp;lt;/title&amp;gt; // &lt;/span&gt;&lt;span style=&quot;color: #303030;&quot;&gt;웹 브라우저 제목 표시줄에 표시&lt;/span&gt;&lt;span style=&quot;color: #303030;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #303030; text-align: start;&quot;&gt;&amp;nbsp; &amp;lt;/head&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #303030; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #303030;&quot;&gt;&amp;nbsp; &amp;lt;body&amp;gt; // &lt;span style=&quot;color: #303030; text-align: start;&quot;&gt;실제 웹 브라우저 화면에서 보여주는 내용 작성&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #303030;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;header&amp;gt;&amp;lt;/header&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #303030; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt; &amp;lt;main&amp;gt;&amp;lt;/main&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #303030; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;footer&amp;gt;&amp;lt;/footer&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #303030;&quot;&gt;&amp;nbsp; &amp;lt;/body&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #303030;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #303030; text-align: start;&quot;&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 이외 개념&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #303030;&quot;&gt;&lt;b&gt;1) HTML 기본구조 자동입력하기 :&lt;/b&gt; 첫 줄에 ! 입력 후, Tab 또는 Enter, 입력 후 lang=&quot;en&quot;을 lang=&quot;ko&quot;로 변경&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #303030;&quot;&gt;&lt;b&gt;2) 웹 문서의 영역 :&lt;/b&gt; 헤더, 메인, 푸터, 사이드바&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #303030;&quot;&gt;&lt;b&gt;3) 경로 입력 시,&lt;/b&gt; 절대경로, 상대경로 구분 유의&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;절대경로 : 속성값이 항상 같은 경로&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;ex) &lt;a href=&quot;https://naver.com&quot;&gt;https://naver.com&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;상대경로 : 현재 작성하는 문서의 위치를 기준으로 함. 문서의 위치에 따라 바뀜. &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;ex) ./sub/sub2.html, ../index.html&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;※ 경로 표기법&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;/ : 최상위 경로&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;./ : 현재 폴더 위치 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;../ : 상위 폴더 위치&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #303030;&quot;&gt;4) 코딩의 대전제 :&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #303030;&quot;&gt; 2개 이상의 요소는 하나로 묶는다. 단일이어도 특정 스타일을 적용할 경우도 div로 묶는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #303030;&quot;&gt; 텍스트 요소는 별 의미없이 묶을 땐 &amp;lt;span&amp;gt;을 사용. &amp;lt;strong&amp;gt;강조 &amp;lt;em&amp;gt; 특별한 의미일 경우 묶는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #303030;&quot;&gt;5) 태그 구분 block, inline-block, inline&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #303030;&quot;&gt;block : h1~h6, p, adress, ol, ul, li, dl, table(테이블로 따로 분류되기도 함), form, fieldset, div, header, section, footer, nav, article, aside&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #303030;&quot;&gt;inline-block : &amp;lt;img&amp;gt;(홑태그), &amp;lt;input&amp;gt;(홑태그), textarea, select&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #303030;&quot;&gt;inline (text) : a, em, strong, mark, b, small, sub, sup, label, span, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #303030;&quot;&gt;table : table (블럭같이 행동함)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #303030;&quot;&gt;6) 부모 자식 상속 관계와 예외&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #303030;&quot;&gt;- 부모가 블럭이면 누구든 자식으로 받을 수 있다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #303030;&quot;&gt;- 부모가 인라인이면 인라인만 자식으로 받을 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #303030;&quot;&gt;&amp;nbsp;- &lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;단, 몇몇 블럭은 자식으로 블럭을 둘 수 없다.&lt;/b&gt; &lt;/span&gt;아래와 같이 주&lt;/span&gt;&lt;span style=&quot;color: #303030;&quot;&gt;로 텍스트 위주로 사용되는 태그들이 그렇다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #303030;&quot;&gt;&amp;nbsp; &amp;nbsp;&amp;lt;h1&amp;gt;~&amp;lt;h6&amp;gt;, &amp;lt;p&amp;gt;, &amp;lt;address&amp;gt;, &amp;lt;dt&amp;gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>Development/HTML</category>
      <category>html</category>
      <category>HTML기본구조</category>
      <category>블럭</category>
      <category>인라인</category>
      <category>인라인블럭</category>
      <category>테이블</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/19</guid>
      <comments>https://dreamyard.tistory.com/entry/HTML%EC%9D%98-%EA%B8%B0%EB%B3%B8-%EA%B5%AC%EC%A1%B0%EC%99%80-%EA%B0%9C%EB%85%90#entry19comment</comments>
      <pubDate>Tue, 20 Jun 2023 23:59:46 +0900</pubDate>
    </item>
    <item>
      <title>jQuery CDN 방식으로 연결하기</title>
      <link>https://dreamyard.tistory.com/entry/jQuery-CDN-%EB%B0%A9%EC%8B%9D%EC%9C%BC%EB%A1%9C-%EC%97%B0%EA%B2%B0%ED%95%98%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;jQuery를 CDN 방식으로 연결하는 방법을 알아보겠습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. jQuery 사이트에 접속해서 오른쪽 상단의 주황박스&lt;u&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt; '&lt;/b&gt;&lt;/span&gt;&lt;/u&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; color: #333333; text-align: start;&quot;&gt;&lt;u&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;Download jQuery'를 클릭&lt;/b&gt;&lt;/span&gt;&lt;/u&gt;해줍니다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;※ 아래 이미지 클릭 시, 바로 이동합니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1596&quot; data-origin-height=&quot;813&quot;&gt;&lt;a href=&quot;https://jquery.com/&quot; target=&quot;&quot; title=&quot;jQuery&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCydgC/btr9GamQBgC/S1fNieOIWTBkKtRfeXN3UK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCydgC%2Fbtr9GamQBgC%2FS1fNieOIWTBkKtRfeXN3UK%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;1596&quot; height=&quot;813&quot; data-origin-width=&quot;1596&quot; data-origin-height=&quot;813&quot;/&gt;&lt;/a&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2.&amp;nbsp; 아래와 같은 페이지로 넘어가는데 스크롤을 아래로 내리며 &lt;u&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;'linkUsing jQuery with a CDN'&lt;/b&gt;&lt;/span&gt;&lt;/u&gt; 제목의 구문부분을 찾아줍니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1387&quot; data-origin-height=&quot;790&quot;&gt;&lt;a href=&quot;https://jquery.com/download/&quot; target=&quot;&quot; title=&quot;jQuery 다운로드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KoIrf/btr9P46pxVK/hSYYzor7LTKKxYxgGjDnGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKoIrf%2Fbtr9P46pxVK%2FhSYYzor7LTKKxYxgGjDnGK%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;1387&quot; height=&quot;790&quot; data-origin-width=&quot;1387&quot; data-origin-height=&quot;790&quot;/&gt;&lt;/a&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3. 아래와 같은 구문에서 원하는 CDN을 클릭해줍니다. 예시로 &lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;google CDN&lt;/b&gt;&lt;/span&gt;을 클릭하여 들어가보겠습니다. 아래 초록 화살표 링크를 클릭하면, &lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;'Google Hosted Libraries'&lt;/b&gt;&lt;/span&gt;로 이동합니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1046&quot; data-origin-height=&quot;727&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KmrSu/btr9N9G580w/LVCyPYpK0bfrgqjqaohR0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KmrSu/btr9N9G580w/LVCyPYpK0bfrgqjqaohR0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KmrSu/btr9N9G580w/LVCyPYpK0bfrgqjqaohR0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKmrSu%2Fbtr9N9G580w%2FLVCyPYpK0bfrgqjqaohR0K%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;1046&quot; height=&quot;727&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1046&quot; data-origin-height=&quot;727&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4. 아래 스니펫 리스트 중, &lt;u&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;원하는 것을 복사해서 html 'head' 부분에 붙여넣어줍니다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;864&quot; data-origin-height=&quot;546&quot;&gt;&lt;a href=&quot;https://developers.google.com/speed/libraries?hl=ko#jquery&quot; target=&quot;&quot; title=&quot;google hosted libraries&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YnNo0/btr9DHywPs0/bA5WE9ts7bKVBcjAU00fO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYnNo0%2Fbtr9DHywPs0%2FbA5WE9ts7bKVBcjAU00fO1%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;864&quot; height=&quot;546&quot; data-origin-width=&quot;864&quot; data-origin-height=&quot;546&quot;/&gt;&lt;/a&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;jQuey.png&quot; data-origin-width=&quot;533&quot; data-origin-height=&quot;533&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckYOvd/btr9PcXSUKX/q0FzGn3zarLMs1R3VsSTqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckYOvd/btr9PcXSUKX/q0FzGn3zarLMs1R3VsSTqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckYOvd/btr9PcXSUKX/q0FzGn3zarLMs1R3VsSTqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FckYOvd%2Fbtr9PcXSUKX%2Fq0FzGn3zarLMs1R3VsSTqk%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; alt=&quot;jQuery CDN 방식 연결&quot; loading=&quot;lazy&quot; width=&quot;238&quot; height=&quot;238&quot; data-filename=&quot;jQuey.png&quot; data-origin-width=&quot;533&quot; data-origin-height=&quot;533&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/JavaScript</category>
      <category>CDN</category>
      <category>jquery</category>
      <category>연결하기</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/18</guid>
      <comments>https://dreamyard.tistory.com/entry/jQuery-CDN-%EB%B0%A9%EC%8B%9D%EC%9C%BC%EB%A1%9C-%EC%97%B0%EA%B2%B0%ED%95%98%EA%B8%B0#entry18comment</comments>
      <pubDate>Wed, 12 Apr 2023 19:59:58 +0900</pubDate>
    </item>
    <item>
      <title>무료 아이콘 사이트 정리 5</title>
      <link>https://dreamyard.tistory.com/entry/%EB%AC%B4%EB%A3%8C-%EC%95%84%EC%9D%B4%EC%BD%98-%EC%82%AC%EC%9D%B4%ED%8A%B8-%EC%A0%95%EB%A6%AC-5</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; color: #666666;&quot;&gt;무료 아이콘 사이트를 정리해보았습니다. 아이콘은 종종 웹디자인, 홍보 인쇄물 등 다양한 용도로 사용되는데요.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; color: #666666;&quot;&gt;아래 사이트 리스트 참고하셔서 원하시는 아이콘을 찾는데 도움이되셨으면 좋겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; color: #666666;&quot;&gt;&lt;span style=&quot;background-color: #ceefdf;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;사이트별로 무료 사용의 경우 저작권을 표기해야하는 등 별도로 요구되는 사항이 있으니, 사용 전 미리 확인 후 준수하시며 사용&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;하시는 것을 권장 드립니다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;u&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;무료 아이콘 사이트 정리 5 &lt;/span&gt;&lt;/b&gt;&lt;/u&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; color: #409d00;&quot;&gt;※ 사이트 사진을 클릭하시면, 해당 사이트로 바로 이동됩니다. ※&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;1. Google Fonts Icon &lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; color: #666666;&quot;&gt;구글 Fonts에서 &lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;무료로 벡터아이콘들을 제공&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;하는 사이트입니다. svg, png 형식 모두 다운받을 수 있어요.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-03-13 013702.png&quot; data-origin-width=&quot;510&quot; data-origin-height=&quot;260&quot;&gt;&lt;a href=&quot;https://fonts.google.com/icons&quot; target=&quot;&quot; title=&quot;google fonts icon&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lNBoK/btr3pssfPY4/VrJJlYValH3tZcSHZbOUA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlNBoK%2Fbtr3pssfPY4%2FVrJJlYValH3tZcSHZbOUA1%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;510&quot; height=&quot;260&quot; data-filename=&quot;스크린샷 2023-03-13 013702.png&quot; data-origin-width=&quot;510&quot; data-origin-height=&quot;260&quot;/&gt;&lt;/a&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;2. Freepik&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;전세계적으로 유명한 벡터사이트입니다. 비트맵 이미지도 지원합니다.&lt;/span&gt; &lt;span style=&quot;color: #000000; background-color: #ceefdf;&quot;&gt;&lt;b&gt;무료로 이용할 경우 저작권 표시가 필수&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #666666;&quot;&gt;인 사이트입니다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;웹용으로 사용할 경우,&lt;b&gt;&lt;span style=&quot;color: #409d00;&quot;&gt; Designed by Freepik&lt;/span&gt;&lt;/b&gt; 또는&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt; www.freepik.com&lt;/b&gt;&lt;/span&gt; 표기 후 링크를 걸어야 합니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; color: #666666;&quot;&gt;프린트용일 경우 Designed by Freepik 표기 또는 출처 표기가 어려울 경우엔 크레딧 페이지 또는 참고 문헌에 표기해야 합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-03-13 013926.png&quot; data-origin-width=&quot;510&quot; data-origin-height=&quot;276&quot;&gt;&lt;a href=&quot;https://www.freepik.com/ &quot; target=&quot;&quot; title=&quot;Freepik&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0p71i/btr3c3nlct6/lnyKoSzBg9tNviTPrrRX0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0p71i%2Fbtr3c3nlct6%2FlnyKoSzBg9tNviTPrrRX0k%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;510&quot; height=&quot;276&quot; data-filename=&quot;스크린샷 2023-03-13 013926.png&quot; data-origin-width=&quot;510&quot; data-origin-height=&quot;276&quot;/&gt;&lt;/a&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;
&lt;script async src=&quot;https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8062209632442181&quot;
     crossorigin=&quot;anonymous&quot;&gt;&lt;/script&gt;
&lt;!-- 디스플레이광고 반응형 --&gt;
&lt;ins class=&quot;adsbygoogle&quot;
     style=&quot;display:block&quot;
     data-ad-client=&quot;ca-pub-8062209632442181&quot;
     data-ad-slot=&quot;3926068492&quot;
     data-ad-format=&quot;auto&quot;
     data-full-width-responsive=&quot;true&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;3. XEIcon&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; color: #666666;&quot;&gt;네이버가 인수하여 관리하고 있는 아이콘 사이트입니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-03-13 013911.png&quot; data-origin-width=&quot;510&quot; data-origin-height=&quot;390&quot;&gt;&lt;a href=&quot;http://xpressengine.github.io/XEIcon/&quot; target=&quot;&quot; title=&quot;XEicon&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctNQOM/btr3ofNgyhf/fPkiLJob8lNGTjIKoY6PQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FctNQOM%2Fbtr3ofNgyhf%2FfPkiLJob8lNGTjIKoY6PQK%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;510&quot; height=&quot;390&quot; data-filename=&quot;스크린샷 2023-03-13 013911.png&quot; data-origin-width=&quot;510&quot; data-origin-height=&quot;390&quot;/&gt;&lt;/a&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;4. Fontawesome&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #666666;&quot;&gt;유료 및 무료 아이콘을 제공합니다. 무료의 아이콘은 Free 카테고리에 분류되어 있으며 약 2000가지 아이콘을 제공합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-03-13 013901.png&quot; data-origin-width=&quot;510&quot; data-origin-height=&quot;375&quot;&gt;&lt;a href=&quot;https://fontawesome.com/icons&quot; target=&quot;&quot; title=&quot;Fontawesome&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Nl0Oj/btr3eytxCoF/kypkKSEkgSkUx0bGWrqcmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNl0Oj%2Fbtr3eytxCoF%2FkypkKSEkgSkUx0bGWrqcmK%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;510&quot; height=&quot;375&quot; data-filename=&quot;스크린샷 2023-03-13 013901.png&quot; data-origin-width=&quot;510&quot; data-origin-height=&quot;375&quot;/&gt;&lt;/a&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;5. Flaticon&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; color: #666666;&quot;&gt;유료 및 무료 아이콘을 제공하며, &lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ceefdf;&quot;&gt;무료 버전으로 사용할 경우 출처 표기 필수&lt;/span&gt;&lt;/b&gt;&lt;/span&gt; 입니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-03-13 014139.png&quot; data-origin-width=&quot;510&quot; data-origin-height=&quot;233&quot;&gt;&lt;a href=&quot;https://www.flaticon.com/&quot; target=&quot;&quot; title=&quot;flaticon&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgz3NY/btr3vPHqXPx/Hz9Atq98AIklmCbUOlRvQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbgz3NY%2Fbtr3vPHqXPx%2FHz9Atq98AIklmCbUOlRvQ0%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;510&quot; height=&quot;233&quot; data-filename=&quot;스크린샷 2023-03-13 014139.png&quot; data-origin-width=&quot;510&quot; data-origin-height=&quot;233&quot;/&gt;&lt;/a&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;무료아이콘.png&quot; data-origin-width=&quot;533&quot; data-origin-height=&quot;533&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OmtE7/btr3dE8E9If/GUYZKYCfi9hqEK73pH1p31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OmtE7/btr3dE8E9If/GUYZKYCfi9hqEK73pH1p31/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OmtE7/btr3dE8E9If/GUYZKYCfi9hqEK73pH1p31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOmtE7%2Fbtr3dE8E9If%2FGUYZKYCfi9hqEK73pH1p31%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;300&quot; height=&quot;300&quot; data-filename=&quot;무료아이콘.png&quot; data-origin-width=&quot;533&quot; data-origin-height=&quot;533&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Design</category>
      <category>무료아이콘</category>
      <category>무료아이콘사이트</category>
      <category>웹디자인</category>
      <category>포스터디자인</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/17</guid>
      <comments>https://dreamyard.tistory.com/entry/%EB%AC%B4%EB%A3%8C-%EC%95%84%EC%9D%B4%EC%BD%98-%EC%82%AC%EC%9D%B4%ED%8A%B8-%EC%A0%95%EB%A6%AC-5#entry17comment</comments>
      <pubDate>Mon, 13 Mar 2023 08:03:28 +0900</pubDate>
    </item>
    <item>
      <title>사람들은 심리적으로 익숙한 디자인을 좋아한다.</title>
      <link>https://dreamyard.tistory.com/entry/%EC%82%AC%EB%9E%8C%EB%93%A4%EC%9D%80-%EC%8B%AC%EB%A6%AC%EC%A0%81%EC%9C%BC%EB%A1%9C-%EC%9D%B5%EC%88%99%ED%95%9C-%EB%94%94%EC%9E%90%EC%9D%B8%EC%9D%84-%EC%A2%8B%EC%95%84%ED%95%9C%EB%8B%A4</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Jakob's Law에 따르면 사람들은 접속하는 여러 사이트가 같은 방식으로 작동하길 바란다고 합니다. 같은 방식으로 작동할수록, 처음 접하는 사이트더라도 사람들은 익숙함을 느끼고 그만큼 새로운 사이트에도 금방 적응할 수 있다고 합니다. 여러 인터페이스를 익히는 데 드는 시간이 줄어들수록, 사용자의 정신적인 에너지 소모가 감소합니다. 따라서, 목표 달성에 쓸 에너지는 늘어나고 사용자가 원하는 목표를 달성할 확률도 늘어납니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;15. 디자인 심리학.jpg&quot; data-origin-width=&quot;295&quot; data-origin-height=&quot;295&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TodLV/btr1CNeEs0Z/XrVttordavZCDgOilsOzx1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TodLV/btr1CNeEs0Z/XrVttordavZCDgOilsOzx1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TodLV/btr1CNeEs0Z/XrVttordavZCDgOilsOzx1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTodLV%2Fbtr1CNeEs0Z%2FXrVttordavZCDgOilsOzx1%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; alt=&quot;디자인 심리학&amp;amp;#44; 사람들은 심리적으로 익숙한 디자인을 좋아한다.&quot; loading=&quot;lazy&quot; width=&quot;295&quot; height=&quot;295&quot; data-filename=&quot;15. 디자인 심리학.jpg&quot; data-origin-width=&quot;295&quot; data-origin-height=&quot;295&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jakob's Law, 사람들은 심리적으로 익숙한 디자인을 좋아한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jakob's Law는 2000년에 Jakob Nielson이 2000년에 제창한 것으로, '사용자는 다른 웹사이트를 통해 축적된 경험을 바탕으로 디자인 관례에 대한 기대치를 형성하는 경향을 보인다.'라는 내용의 법칙입니다. Jakob's Law는 디자이너들에게 일반적인 디자인 관습을 따를 것을 권장합니다. 그래야 사용자가 불편을 느끼지 않고, 사이트나 제품에 더 집중할 수 있다는 것입니다. 사람들은 기존의 축적된 경험을 바탕으로 '이렇게 작동해야 한다.'라고 예상하고 기대하고 있기 때문에, 이를 저버리지 않아야 한다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 Jakob's Law는 심리학에서 Mental Model이라는 개념과 직접적인 연관이 있습니다. Mental Model이란, 우리가 어떤 시스템의 작동방식에 대해 알고 있다고 생각하는 것을 가리키는 말입니다. 우리는 경험을 통해 시스템 작동 방식에 대한 모델을 만들고, 이를 시스템과 비슷한 낯선 상황에 적용합니다. 만약, 제품이나 서비스가 사용자의 Mental Model에 따라 디자인된다면, 그만큼 좋은 사용자 경험이 만들어집니다. 디자이너들은 이러한 목표를 달성하기 위해서 사용자를 인터뷰하거나, 페르소나를 설정하는 등 다양한 방법을 동원합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페르소나는 대상 사용자의 특정 부분 집합을 대표하는 가상의 인물입니다. 디자이너가 사용하는 기법의 하나인 페르소나는 사용자의 현실적인 니즈를 바탕으로 디자인 결정을 내리도록 도와주는 틀이 되어 줍니다. 사용자를 명확히 해두지 않으면 디자이너마다 사용자에 대한 해석이 달라지기 때문에 업무 프로세스가 더욱 어렵고 복잡해집니다. 페르소나에는 일반적으로 사진, 이름, 나이 직업 등과 같은 정보, 사용자의 행동과 관련되는 세부 정보, 통찰이 포함됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 사용했던 익숙한 제품이 갑자기 변하면 Mental Model Discordance가 일어나게 됩니다. 디자인과 Mental Model이 어긋나면서, 사용자가 해당 제품이나 서비스를 인지하고 이해하는 속도에 악영향을 미치게 됩니다. 그 대표적인 예로 스냅챗의 2018년의 redesign을 들 수 있습니다. 당시의 스냅챗은 기존의 스토리 기능과 채팅 기능을 합쳐버리는 등 대대적인 정비를 했습니다. 이는 광고주에게 새로운 모습을 보여주고, 소비자에게 맞춤형 광고를 제공하기 위해 진행한 것이었습니다. 이에 불만을 품은 기존의 사용자들은 트위터에 반감을 표하거나, 경쟁사인 인스타그램으로 넘어가 버려 사용자 수가 크게 줄었다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 사용자가 무조건 redesign을 싫어하는 것은 아닙니다. 2017년, 구글이 제안한 새로운 유튜브 디자인은 오히려 사용자들에게 환영받았다고 합니다. 구글은 사용자에게 유튜브의 새로운 디자인을 강요하지 않고, 새로운 Material Design의 UI에 서서히 익숙하게 만들었다고 합니다. 또한, 사용자가 새로운 디자인에 익숙해지는 동안, 구글에 피드백을 보내거나 이전 디자인으로 돌아갈 수 있는 선택권을 주어 Mental Model Discordance로 인한 부작용을 줄일 수 있었다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mental Model은 디지털 디자인뿐만 아니라, 자동차 같은 제품에도 적용됩니다. 2020년 메르세데스 벤츠 EQC 400 프로토타입은 Door Panel의 좌석 제어부 버튼이 실제 좌석과 비슷한 모양으로 만들어져 있습니다. 덕분에 사용자는 좌석의 각 부분을 움직이기 위해 어떤 버튼을 눌러야 하는지 쉽게 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Psychology</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/16</guid>
      <comments>https://dreamyard.tistory.com/entry/%EC%82%AC%EB%9E%8C%EB%93%A4%EC%9D%80-%EC%8B%AC%EB%A6%AC%EC%A0%81%EC%9C%BC%EB%A1%9C-%EC%9D%B5%EC%88%99%ED%95%9C-%EB%94%94%EC%9E%90%EC%9D%B8%EC%9D%84-%EC%A2%8B%EC%95%84%ED%95%9C%EB%8B%A4#entry16comment</comments>
      <pubDate>Thu, 2 Mar 2023 21:20:18 +0900</pubDate>
    </item>
    <item>
      <title>상호성의 원칙에 따르는 보답과 양보의 심리학</title>
      <link>https://dreamyard.tistory.com/entry/%EC%83%81%ED%98%B8%EC%84%B1%EC%9D%98-%EC%9B%90%EC%B9%99%EC%97%90-%EB%94%B0%EB%A5%B4%EB%8A%94-%EB%B3%B4%EB%8B%B5%EA%B3%BC-%EC%96%91%EB%B3%B4%EC%9D%98-%EC%8B%AC%EB%A6%AC%ED%95%99</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;'뿌리면 뿌리는 대로 거둔다'라는 상호성의 원칙에 따라 우리는 다른 사람에게 뭔가를 받으면 그에 맞는 보답을 해야 한다고 생각합니다. 우리는 누군가의 호의를 호의로 갚으려 합니다. 비교적 작고 소소한 호의는 시간이 지날수록 기억에서 잊히지만, 기억에 남을 만한 큰 선물을 받았을 때는 보답에 대한 의무감이 상당히 오래갑니다. 이러한 의무감은 때론 문화적 차이와 지리적 거리를 뛰어넘어 국가 간의 관계에까지 영향을 미치기도 합니다. 그 예시로 1985년 에티오피아와 멕시코 사이에 있었던 일을 들어볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1985년 에티오피아는 수년에 걸친 가뭄과 내전으로 매우 궁핍하고 혼란한 상태였습니다. 많은 국민들이 기아와 질병으로 죽어 나가는 상황이었습니다. 하지만, 그런 상황 속에서도 같은 해 지진이 발생한 멕시코에 에티오피아의 적십자 회원들은 5000달러를 보냈다고 합니다. 에티오피아가 이런 놀라운 행동을 한 이유는 1935년 에티오피아가 이탈리아에 침공당했을 때 멕시코로부터 도움을 받은 적이 있기 때문이었다고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;14. 상호성의 원칙.jpg&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IA2RN/btr1dcMNKtN/jZR0JVgZWcfa4L1HseG7Qk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IA2RN/btr1dcMNKtN/jZR0JVgZWcfa4L1HseG7Qk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IA2RN/btr1dcMNKtN/jZR0JVgZWcfa4L1HseG7Qk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIA2RN%2Fbtr1dcMNKtN%2FjZR0JVgZWcfa4L1HseG7Qk%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; alt=&quot;상호성의 원칙&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;300&quot; data-filename=&quot;14. 상호성의 원칙.jpg&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;우리는 상호성의 원칙에 따라 보답하려 한다.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 사회는 상호성의 원칙에 따라서 오랜 시간 서로 이득을 주고받아온 만큼, 사회 구성원으로서 어릴 때부터 상호성의 원칙을 신뢰하고 따르도록 교육받아 왔습니다. 받기만 하고 내주지 않는 사람은 배은망덕한, 파렴치한이라는 수식어가 붙으며 사회적인 비난을 받곤 합니다. 우리는 이런 사람이 되지 않게 지속해서 노력하게 되고, 때론 이를 이용해 부당한 이득을 취하려는 사람들도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 사람 간의 상호성은 타인의 동의를 얻어내는 데 효과적인 도구로써 이용될 수 있습니다. 단번에 거절당할 법한 요청에도 '네'라는 응답을 끌어내기도 합니다. 상대에게 작은 호의라도 베푸는 것은 상대로부터 원하는 결과를 얻어낼 확률을 높일 수 있습니다. 어떤 제품이나 서비스를 구매하는 데도, 선물을 받은 고객들이 그렇지 않은 고객에 비해 제품과 서비스를 구매할 확률이 높다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화장품 가게에서 주는 공짜 샘플, 마트의 시식코너 모두 상호성의 원칙을 이용한 마케팅 방법의 하나입니다. 공짜 샘플을 나눠주고, 공짜 시식을 권함으로써 사람들에게 부채 의식과 의무감을 심어주는 것입니다. 부채 의식과 의무감은 사람들에게 별로 좋아하지 않는 제품이더라도 구매할 확률을 높여줍니다. 세계적인 방문판매 회사 암웨이(Amway)사에서도 이 전략을 사용했었습니다. 암웨이는 여러 제품을 특별 제작한 상자나 비닐봉지 등에 담아 고객에게 한번 사용해 보라고 권하며 24~72시간 맡겨두는 방식으로 고객들에게 공짜 샘플을 제공했습니다. 이러한 방법은 고객에게 부채 의식과 의무감을 심어주며 구매율을 효과적으로 높여주었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 상대의 호의에 대해 그와 유사한 정도와 방법으로 호의에 보답하려 합니다. 하지만, 때론 작은 호의로부터 시작된 의무감이 훨씬 더 큰 보답을 끌어내는 것처럼 불공평한 교환을 일으키기도 합니다. 처음 호의를 제공하는 사람은 마음만 먹는다면 호의에 대한 보답의 형태까지 결정할 수 있는 만큼, 부당한 이득을 취하려는 사람들은 얼마든지 이런 불공평함을 이용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;상호성의 원칙은 양보에도 작용한다.&amp;nbsp;&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 만약 처음부터 상대에게 요구하는 것이 터무니없이 극단적이라면 그 효과는 떨어집니다. 상대에게 지나치게 비현실적이고 무리한 요구는 양보하더라도 진실해 보이지 않고, 상대의 양보를 끌어낼 확률도 매우 낮다고 합니다. 따라서, 협상에서 좋은 결과를 내는 방법은 첫 번째 요구를 적당히 무리한 것으로 제안한 후, 상대와 양보를 주고받다가 내가 원하는 방향으로 상대를 끌어내는 것이라 볼 수 있습니다.&lt;/p&gt;</description>
      <category>Psychology</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/15</guid>
      <comments>https://dreamyard.tistory.com/entry/%EC%83%81%ED%98%B8%EC%84%B1%EC%9D%98-%EC%9B%90%EC%B9%99%EC%97%90-%EB%94%B0%EB%A5%B4%EB%8A%94-%EB%B3%B4%EB%8B%B5%EA%B3%BC-%EC%96%91%EB%B3%B4%EC%9D%98-%EC%8B%AC%EB%A6%AC%ED%95%99#entry15comment</comments>
      <pubDate>Wed, 1 Mar 2023 17:57:25 +0900</pubDate>
    </item>
    <item>
      <title>우리는 의사 결정 중에 무의식적으로 지름길을 찾으려 한다</title>
      <link>https://dreamyard.tistory.com/entry/%EC%9A%B0%EB%A6%AC%EB%8A%94-%EC%9D%98%EC%82%AC-%EA%B2%B0%EC%A0%95-%EC%A4%91%EC%97%90-%EB%AC%B4%EC%9D%98%EC%8B%9D%EC%A0%81%EC%9C%BC%EB%A1%9C-%EC%A7%80%EB%A6%84%EA%B8%B8%EC%9D%84-%EC%B0%BE%EC%9C%BC%EB%A0%A4-%ED%95%9C%EB%8B%A4</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 물건을 구매할 때 무조건 싼 물건만을 구입하지 않습니다. 비슷해 보이더라도 고가의 물건을 선택해 버릴 때도 있는데요. 사람들 마음속에는 싼 게 비지떡, 즉 비싼 것이 좋은 것이라는 인식이 있기 때문입니다. 예를 들어, 소중한 연인에게 선물할 목걸이를 사러 온 손님에게 처음부터 500달러의 목걸이를 250달러로 제안하자, 손님은 목걸이를 매우 맘에 들어 하다가도 구매를 망설였다고 합니다. 소중한 사람에게 비싸고, 좋은 선물을 사주고 싶었던 것이죠. 반면, 500달러의 목걸이를 계산 직전 250달러로 할인해주자 매우 기뻐하며 사 갔다고 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 방법을 관광지에서도 많이 사용됩니다. 관광 시즌이 되면, 판매가 부진한 재고상품에 대해 높은 가격을 매겨 판매하기도 합니다. 만약에 이런 방식이 효과가 떨어진다면 가격 할인을 홍보하여 할인 상품을 찾아다니는 사람들을 대상으로 원래 가격으로 팔면 된다고 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;13. 설득결정.jpg&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yb5kq/btr0HR3SzGe/wnfsbeF9jpLRlF0mjhOJF0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yb5kq/btr0HR3SzGe/wnfsbeF9jpLRlF0mjhOJF0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yb5kq/btr0HR3SzGe/wnfsbeF9jpLRlF0mjhOJF0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fyb5kq%2Fbtr0HR3SzGe%2FwnfsbeF9jpLRlF0mjhOJF0%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; alt=&quot;의사 결정 중의 무의식적인 지름길&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;500&quot; data-filename=&quot;13. 설득결정.jpg&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사람들은 무의식적으로 지름길을 찾아 결정한다.&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람들은 기존의 여러 학습된 인식의 영향을 받아 자동적이고 상투적인 행동을 할 때가 많습니다. 이는 현대 사회에서 복잡한 세상에서 사는 우리에게 &lt;span&gt;가장 효과적이고 필요한 행동 방식이기도 합니다. 복잡한 세상에서 &lt;/span&gt;빠른 의사결정을 내리기 위해선 지름길이 필요합니다. 살면서 경험하고 결정하는 모든 것을 철저히 분석하며 사는 것은 매우 피곤하고, 거의 불가능에 가깝습니다. 따라서, 우리는 사회적으로 알려진 고정관념이나 경험을 통해 알게 된 법칙 등을 통해 대상을 분류하고, 대상에 따라서는 고민의 과정을 생략하며 자동으로 반응하기도 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 방식의 행동이 항상 효과를 발휘하는 것은 아니지만, 별다른 대안이 없는 만큼 그 부정확성을 감수할 수밖에 없습니다. 감수하지 않고 이런저런 고민을 하는 것이 오히려 우리를 더 지치게 하고, 비효율적으로 만들지도 모릅니다. 점점 더 복잡하고 단계가 많아지는 현대 사회의 여러 징후를 보면 앞으로 점점 더 이렇게 지름길을 찾으려는 경향이 강해질지도 모릅니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;심리학자들은 여러 연구를 통해 일상에서 우리가 판단을 내릴 때 사용하는 여러 지름길을 밝혀냈습니다. 그중 모든 경우를 고려하는 대신 경험을 통해 발견한 편리한 기준에 따라 일부만 고려해서 문제를 해결하려는 방법인 판단의 휴리스틱스가 지금 얘기한 방법과 유사한 방법입니다. 대부분의 경우 효과를 발휘하지만 잘못될 경우 치명적일 가능성도 있는 방법입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 특정 분야의 전문가나 권위자의 말을 맹목적으로 믿는 경향이 있습니다. 전문가의 주장을 따져보고 검토해보기보다는 주장과는 상관없이 전문가라는 지위 자체에 설득이 되는 것입니다. 마치 버튼을 누르면 자동으로 작동하는 자동 반응이나 다름이 없는 방식입니다. 이러한 자동 반응과는 반대로 모든 정보를 철저히 분석한 후 반응하는 것을 통제반응이라고 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람들은 정보를 분석할 의지와 능력이 있을 때 통제 반응을 더 잘 보인다고 합니다. 고민해야 할 주제가 복잡하고, 시간도 없고, 피곤하기까지 하면 하나하나 분석하며 사고할 여유가 생기지 않습니다. 이런 경우에는 나와 관련된 중요한 일이더라도 통제반응보다는 지름길을 선택하고자 자동 반응을 보일 확률이 높습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 자동 반응은 무의식적으로 일어나기 때문에 스스로 알아차리기가 어렵습니다. 하지만, 자동 반응에 대해 잘 모른다면 작동 방식을 잘 알고 있는 누군가에게 함부로 휘둘릴 가능성이 있기 때문에 알아둘 필요가 있습니다. 이러한 자동 반응의 유발 요인을 모방해 자동 반응을 끌어냄으로써 부당한 이득을 취하려는 사람들도 있습니다. 사람들의 자동 행동은 대부분 학습을 통한 심리 원칙이나 고정관념에서 비롯됩니다. 이런 원칙과 고정관념을 파악하고 있는 사람들은 자동 반응을 끌어내 설득의 무기로 사용하기도 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그들이 사용하는 무기 중 하나인 대조의 원리는 두 개의 대상을 제시할 때 두 대상 간의 차이가 심할수록 그 차이가 실제보다 더 크게 다가온다는 원리입니다. 가벼운 물체를 들었다가 무거운 물체를 들면, 무거운 물체만 들어봤을 때보다 더 무겁게 느껴집니다. 의류 판매점에서는 판매원에게 고객에게 가장 비싼 제품부터 권하라고 가르친다고 합니다. 그 이유는 처음부터 비싼 물건을 내놓으면 그 뒤에 권해지는 물건들이 상대적으로 좀 더 저렴해 보이는 효과가 있기 때문입니다.&amp;nbsp;&lt;/p&gt;</description>
      <category>Psychology</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/14</guid>
      <comments>https://dreamyard.tistory.com/entry/%EC%9A%B0%EB%A6%AC%EB%8A%94-%EC%9D%98%EC%82%AC-%EA%B2%B0%EC%A0%95-%EC%A4%91%EC%97%90-%EB%AC%B4%EC%9D%98%EC%8B%9D%EC%A0%81%EC%9C%BC%EB%A1%9C-%EC%A7%80%EB%A6%84%EA%B8%B8%EC%9D%84-%EC%B0%BE%EC%9C%BC%EB%A0%A4-%ED%95%9C%EB%8B%A4#entry14comment</comments>
      <pubDate>Sun, 26 Feb 2023 23:40:59 +0900</pubDate>
    </item>
    <item>
      <title>색채의 수반 감정과 심리적 연상 작용</title>
      <link>https://dreamyard.tistory.com/entry/%EC%83%89%EC%B1%84%EC%9D%98-%EC%88%98%EB%B0%98-%EA%B0%90%EC%A0%95%EA%B3%BC-%EC%8B%AC%EB%A6%AC%EC%A0%81-%EC%97%B0%EC%83%81-%EC%9E%91%EC%9A%A9</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 색마다 다른 느낌을 받고, 이러한 색의 느낌은 색을 보는 우리의 심리에 많은 영향을 미칩니다. 그림이나 사진을 통해 우리의 심리를 표현하고자 한다면 무엇보다 색마다는 느낌을 알고 색을 잘 활용하는 것이 중요합니다. 이번 포스팅에서는 여러 가지 색으로부터 느껴지는 수반 감정과 심리적 연상 작용에 대해 알아보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;12. 색의 수반 감정.jpg&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dTtJdq/btr0wYaSubK/kkcRbmDMlOzkkaLC5VO2vk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dTtJdq/btr0wYaSubK/kkcRbmDMlOzkkaLC5VO2vk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dTtJdq/btr0wYaSubK/kkcRbmDMlOzkkaLC5VO2vk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdTtJdq%2Fbtr0wYaSubK%2FkkcRbmDMlOzkkaLC5VO2vk%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; alt=&quot;색의 수반 감정&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;500&quot; data-filename=&quot;12. 색의 수반 감정.jpg&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;색의 수반 감정과 심리적 연상&amp;nbsp;&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맛과 관련된 색의 수반 감정을 먼저 살펴보겠습니다. 예를 들어, 우리는 파란 계열의 음식을 보면 식욕이 떨어집니다. 이는 파란색과 같이 차가운 느낌을 주는 한색은 차갑고, 쓴맛을 연상시키기 때문입니다. 반대로 난색은 따뜻하고, 자극적인 느낌을 주어 우리가 식욕을 느끼게 합니다. 맥도널드, 버거킹과 같은 패스트푸드 음식점의 인테리어나 디자인에 난색을 많이 사용하는 이유도 이와 관련되어 있습니다. 초록색은 떫음과 시큼함을 연상시키고, 분홍색은 달콤한 느낌을 연상시킵니다. 갈색과 같은 코코아색이나 진한 자주색 느낌의 와인색은 깊고 진한 맛을 연상시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;냄새와 관련된 색의 수반 감정은 만화를 통해 쉽게 알 수 있습니다. 만화에선 짙은 초록색이나 갈색같이 어두운 계열의 색으로 방귀, 입 냄새와 같이 지독한 냄새를 표현합니다. 반대로 향수 냄새와 같이 좋은 냄새는 분홍색, 노란색과 같이 밝은색으로 표현하는 것을 볼 수 있습니다. 이는 밝고 화사한 색은 향수처럼 기분 좋고 향긋한 냄새를 연상시키는 반면, 어둡고 진한 색은 악취나 지독한 냄새를 연상시키기 때문입니다. 주황색과 같은 오렌지를 연상시키는 색은 톡 쏘는 듯한 냄새를 느껴지게 합니다. 자색과 라일락색과 같은 색은 향기롭고 은은한 향을 연상시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 밝고 채도가 강한 색에서 높고 가벼운 음을 연상합니다. 반면 탁한 느낌의 색은 둔탁하고 무거운 소리나 마찰음을 연상시킵니다. 순색에 가까운 선명한 색은 예리하고 날카로운 음을 연상 킵니다. 우리가 하는 대화의 느낌도 색을 통해 표현할 수 있습니다. 밝은 난색은 따뜻한 대화를 연상시키고, 한색 계통의 푸른색으로부터는 냉정하고, 차가운 대화를 연상시킵니다. 선명하고 높은 채도의 색은 명확하고, 똑똑한 말투를 연상시킵니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;촉감과 관련되는 색의 수반 감정은 우리 주변의 물건들을 통해 쉽게 연상해 볼 수 있습니다. 고명도, 고채도의 색은 미끄러운 느낌과 평활함, 광택감을 줍니다. 명도가 낮은 색으로부터는 젖은 듯한 윤택 감이 잘 느껴집니다. 따뜻하고 환한 파스텔톤의 색은 부드러운 느낌을 줍니다. 짙은 중성 느낌의 색은 끈끈한 느낌을 줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 색을 지각할 때 우리의 경험과 심리적인 작용에 의해 색과 관련된 물건, 느낌 등을 떠올리게 되는 데 이를 색의 연상이라고 합니다. 색의 연상은 크게 추상적 연상과 구체적 연상으로 나눌 수 있습니다. 추상적 연상은 빨간색을 보고 사랑, 열정과 같은 것을 떠올리는 것이고, 구체적 연상은 초록색을 보고 산, 나무 같은 대상을 연상하는 것입니다. 색의 연상은 국가, 문화, 성별, 계절, 직업 등에 따라 다르게 나타납니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 색은 우리의 심리에 각각 다른 심리적인 느낌을 연상시킵니다. 빨강은 정열, 열정, 위험을 연상시킵니다. 주황은 기쁨, 활력, 만족, 혐오를 연상시킵니다. 노랑은 명랑함, 쾌활함, 신성함, 경박함을 연상시키고, 녹색은 평화, 이상, 휴식을 의미합니다. 파랑은 젊음과 시원함, 신비함을 의미하고, 보라는 고귀함과 우아함, 신비를 연상시킵니다. 흰색은 순결함, 평화, 소박함을 연상시키고, 검은색은 죽음, 허무, 공포, 불안의 느낌을 줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 각각의 색은 우리의 심리에 다른 영향을 미치는데, 이를 이용해 우리의 몸과 마음을 치료하기도 합니다. 이러한 치료 방법을 Color Therapy라고 합니다. 예를 들어, 파랑은 우리의 눈의 피로를 풀어주고, 마음을 진정시켜 주는 효과가 있습니다. 주황색은 무기력한 상태일 때 활력을 불어넣어 주고, 녹색은 피로 해소에 도움이 된다고 합니다.&amp;nbsp;&lt;/p&gt;</description>
      <category>Psychology</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/13</guid>
      <comments>https://dreamyard.tistory.com/entry/%EC%83%89%EC%B1%84%EC%9D%98-%EC%88%98%EB%B0%98-%EA%B0%90%EC%A0%95%EA%B3%BC-%EC%8B%AC%EB%A6%AC%EC%A0%81-%EC%97%B0%EC%83%81-%EC%9E%91%EC%9A%A9#entry13comment</comments>
      <pubDate>Thu, 23 Feb 2023 22:44:04 +0900</pubDate>
    </item>
    <item>
      <title>분노의 심리학, 우리는 왜 화를 낼까</title>
      <link>https://dreamyard.tistory.com/entry/%EB%B6%84%EB%85%B8%EC%9D%98-%EC%8B%AC%EB%A6%AC%ED%95%99-%EC%9A%B0%EB%A6%AC%EB%8A%94-%EC%99%9C-%ED%99%94%EB%A5%BC-%EB%82%BC%EA%B9%8C</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;사회생활을 하면서 우리는 다양한 분노의 상황을 직면하곤 하는데요. 급격한 분노 표출은 일시적인 쾌락을 가져오기도 하지만, 결과적으론 타인뿐 아니라 나의 몸과 마음에도 안 좋은 결과를 불러올 때가 많습니다. &lt;span&gt;이번 포스팅에서는 분노와 관련되어 알아 두면 좋은 심리학적인 경향 몇 가지를 정리해 보았습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;11. 분노의 심리.jpg&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;375&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bt8tFe/btr0r6MdbCc/kNl1XOWvjGNpYRc3F3gkb1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bt8tFe/btr0r6MdbCc/kNl1XOWvjGNpYRc3F3gkb1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bt8tFe/btr0r6MdbCc/kNl1XOWvjGNpYRc3F3gkb1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbt8tFe%2Fbtr0r6MdbCc%2FkNl1XOWvjGNpYRc3F3gkb1%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; alt=&quot;분노의 심리학&amp;amp;#44; 우리는 왜 화를 낼까&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;375&quot; data-filename=&quot;11. 분노의 심리.jpg&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;375&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;분노의 심리학, 우리가 화를 내는 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람들은 분노 표출의 결과가 좋지 않을 수도 있다는 점을 알면서도 분노를 표출하기도 합니다. 이는 좋지 않은 결과가 예상되더라도 당장의 분노를 표출하는 것은 우리의 뇌에 쾌감을 주기 때문인데요. 마치, 우리가 다이어트할 때 음식의 유혹을 참기 힘든 것과 같습니다. 때론 사람 중에는 사소한 모욕과 부정적인 대우에도 민감하게 반응하며 어떻게든 트집거리를 만들어 앙갚음하려는 사람들도 있습니다. 특히 종교와 같이 스스로 굉장히 신뢰감을 가지고 있는 대상일수록 사람들은 매우 민감하게 반응합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람들은 당장 분노를 표출하지 못하더라도, 분노의 대상에 대해 복수를 꿈꾸며 어떻게 하면 되갚아줄지를 궁리하기도 합니다. 복수를 꿈꿀 때도 분노와 마찬가지로 우리 뇌에 쾌감을 준다고 합니다. 이는 복수를 준비하며, 복수한 결과를 상상하며 쾌락을 느끼기 때문이라고 합니다. 따라서, 복수를 실현하기 위해 들인 시간과 노력이 더 많을수록 그만큼 복수에 대한 즐거움이 커집니다. 사람들은 복수를 위해 자신의 시간과 비용을 아낌없이 투자하기도 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복수에 대한 심리는 성별에 따라서도 다른 경향을 보입니다. 런던대학교의 Tania Singer 연구팀의 실험에 따르면, 남성이 여성보다 복수심이 더 강하다고 합니다. 자신이 싫어하는 사람이 신체적으로 고통스러워하는 장면을 볼 때 남성은 통쾌함을 느끼지만, 여성은 동정심을 느낀다고 합니다. 반면, 자신이 좋아하거나 정직하다고 생각하는 사람이 고통스러워하는 장면에서는 남성과 여성 모두 동정심을 느꼈다고 합니다. 남성은 여성보다 원한이 생기면 반드시 되갚고자 하는 경향이 있고, 상대를 쉽게 용서하지 못하는 편이라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분노 조절 능력은 어릴 적 경험과도 관련이 있습니다. 어릴 때 반복적이고 지속해서 학대 경험이 있거나 폭력적인 환경에 노출된 사람일수록 폭력적인 성향이 강합니다. 특히 또래 친구들에게 거부당한 경험이 있거나, 오랜 시간 부모와의 갈등을 겪어왔던 사람은 자신이 싫어하는 일에 쉽게 화를 내고 타인을 괴롭힐 가능성이 높다고 합니다. 반면, 부모에게 엄격한 교육을 받아온 사람일수록 상대의 분노로 인한 폭력의 피해자가 되기 쉽습니다. 부모에 의해 여러 규제와 압박을 당할수록 아이의 자기 가치감과 안전감이 떨어지게 되고, 이는 무조건 참는 습관을 형성되게 만듭니다. 타인이 나를 함부로 대하더라도 마냥 참는 사람이 되어버리는 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 사람은 분노를 느낄 때마다 참기를 반복하기도 합니다. 특히 직장생활에서 이러한 모습이 많이 보이는데, 이는 일자리에 대한 불안감과 실업에 대한 우려 때문입니다. 이러한 상태에 있는 사람들은 오랜 시간 긴장과 불안 상태에 놓여있습니다. 실업을 한 사람들보다도 몸과 마음에 더 큰 스트레스를 받고, 많은 건강 문제를 겪게 된다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분노를 표출하는 것도 나에게 좋지 않은 결과를 줄 수 있지만, 이를 마냥 참기만 하는 것도 내 건강과 마음에 좋은 방법이 아닙니다. 따라서 마냥 참는 방법보다는 나를 위한 여러 방법을 강구해 보는 것이 좋습니다. 분노를 느낄 때 한바탕 욕을 하면서 잠시 속을 시원하게 하는 것도 방법입니다. 한 심리학 연구에 따르면 고통을 느낄 때 욕을 한마디 내뱉으면 아니면, 진통을 줄여준다고 합니다. 이 효과를 평소에 욕을 하던 사람이면, 효과가 떨어진다고 합니다. 이 외에도 명상을 통해 나의 주의력을 분산시킴으로써 분노로부터 잠시라도 멀어지는 방법도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 화가 났을 때, 상대에게 바로 사과한다면 분노를 줄일 수 있습니다. 사과 한마디는 상대의 마음도 어느 정도 누그러뜨리기 때문에 상대가 나를 공격할 가능성도 줄어듭니다. 사과 한마디가 이미 상해버린 상대의 마음을 완전히 풀어주진 못하더라도 서로의 분노를 줄이는 데 도움을 줍니다. 타인과 나 사이에 다툼이 있었다면, 한 사람의 만족을 위한 보복을 꿈꾸기보단 분노로 인한 충동적인 행동을 자제하면서 먼저 사과를 하는 것이 서로에게 도움이 됩니다.&lt;/p&gt;</description>
      <category>Psychology</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/12</guid>
      <comments>https://dreamyard.tistory.com/entry/%EB%B6%84%EB%85%B8%EC%9D%98-%EC%8B%AC%EB%A6%AC%ED%95%99-%EC%9A%B0%EB%A6%AC%EB%8A%94-%EC%99%9C-%ED%99%94%EB%A5%BC-%EB%82%BC%EA%B9%8C#entry12comment</comments>
      <pubDate>Thu, 23 Feb 2023 02:02:48 +0900</pubDate>
    </item>
    <item>
      <title>직장에서 알면 좋은 다양한 심리학의 경향 6가지</title>
      <link>https://dreamyard.tistory.com/entry/%EC%A7%81%EC%9E%A5%EC%97%90%EC%84%9C-%EC%95%8C%EB%A9%B4-%EC%A2%8B%EC%9D%80-%EB%8B%A4%EC%96%91%ED%95%9C-%EC%8B%AC%EB%A6%AC%ED%95%99%EC%9D%98-%EA%B2%BD%ED%96%A5-6%EA%B0%80%EC%A7%80</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;수많은 직장인이 회사라는 하나의 조직 내에서 다양한 의사소통과 협력을 하며 업무를 합니다. 각자 다른 특성과 스타일의 사람들이 모여 서로 원활히 소통하며 업무를 해내는 것은 절대 쉽지 않은 일인데요. 직장 내 조직 생활에서 알아두고, 참고하면 좋은 6가지 심리학의 경향을 정리해 보았습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;10. 직장에서 알면 좋은 심리학.jpg&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMLjhD/btr0ikRpRtE/YeR8gYurTA358AEhHU8irk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMLjhD/btr0ikRpRtE/YeR8gYurTA358AEhHU8irk/img.jpg&quot; data-alt=&quot;직장에서 알면 좋은 다양한 심리학의 경향 6가지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMLjhD/btr0ikRpRtE/YeR8gYurTA358AEhHU8irk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMLjhD%2Fbtr0ikRpRtE%2FYeR8gYurTA358AEhHU8irk%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; alt=&quot;직장에서 알면 좋은 다양한 심리학의 경향 6가지&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;500&quot; data-filename=&quot;10. 직장에서 알면 좋은 심리학.jpg&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;직장에서 알면 좋은 다양한 심리학의 경향 6가지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;직장에서 알면 좋은 다양한 심리학의 경향&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람들은 목소리가 더 낮은 사람을 관리자로서 선호하는 경향이 있습니다.&amp;nbsp;사람들은 다양한 목소리를 가지고 있고, 목소리의 높낮이 또한 다릅니다. 한 연구 결과에 따르면, 사람들은 낮은 목소리를 더 선호하는 경향이 있다고 합니다. 따라서 직장에서도 높은 직급의 여성 관리자를 뽑을 때, 목소리가 낮은 여성을 보다 적합한 인물로 평가한다고 합니다. 반대로 남성 관리자를 뽑을 남성의 경우는 목소리가 낮은 남성을 지지하지만, 여성의 경우는 목소리에 신경 쓰지 않는 편이라고 합니다.&amp;nbsp;테스토스테론이라는 우리 몸의 호르몬은 매력이나 리더십과 관련이 있는데, 대체로 목소리 톤이 낮은 사람은 테스토스테론 수치가 높습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;키가 크거나 좋은 체격일수록 평가에서 비교적 유리한 경향이 있다고 합니다.&amp;nbsp;외적으로 보이는 우리 몸의 체격은 승진과 경쟁을 하는 데 중요한 요소로 작용합니다. 옛날부터 체격은 곧 권력과 힘의 상징이었고, 체격이 클수록 진화적인 관점에서 더 유리한 위치를 차지하는 경향이 있었습니다. 이는 현대사회에서도 마찬가지로 큰 키와 탄탄한 몸과 같이 유리한 체격을 가진 사람일수록 사회적으로 더 뛰어난 평가를 받을 확률이 높다고 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관용을 베풀수록 조직 내에서 성공할 확률이 높습니다.&amp;nbsp;한 심리학자의 연구에 따르면, 타인에게 박복하게 굴지 않고 관용과 너그러움을 베푸는 사람일수록 조직 안에서 많은 사람의 도움을 받으며, 사회적으로도 성공할 확률이 높아진다고 합니다. 또한, 능력이 없는 사장일수록 직원들을 질타하고 괴롭힌다고 합니다. 안정적이고, 능력이 있는 사장일수록 직원들에게 더 우호적인 태도를 보여준다고 합니다. 스스로 능력이 없거나 권력이 없다고 느낄수록 직원들에게 독설하거나, 그릇된 처세를 하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업무 형태에 따라 심리적으로 적합한 사무실 구조가 다릅니다. 공동 프로젝트를 함께 진행하는 상황에서는 서로 독립된 공간에서 일하는 것보다 상대가 시야에서 보이는 시각적인 접촉이 이루어지는 공간에서 함께 일하는 것이 좋다고 합니다. 협력해서 이뤄나가야 하는 일을 각자의 방에서 진행하게 된다면, 아무리 옆 방에서 협업자가 함께 일하고 있다는 것을 인식하고 있더라도 업무에 대한 집중도나 노력의 정도가 저하된다고 합니다. 반대로 주된 업무가 보고서 작성이나 정보 조사 등의 협업보단 집중력이 중요시되는 일이라면, 너무 개방적인 사무실은 오히려 독이 됩니다. 개방적인 구조는 타인의 시선을 의식하기 쉽고, 때론 감시받는 기분을 느끼게 해서 집중에 필요한 개인의 안정감을 저하하기 때문입니다. 사무실의 구조 외에도 일하는 장소의 밝기도 업무 효율에 영향을 미칩니다. 사람은 어두운 장소에서 일할수록 자기 행동이 잘 보이지 않는다고 생각하여, 이기적이고 불성실한 업무 태도를 보일 수 있다고 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스스로 우수하게 느껴지더라도, 타인의 생각은 다를 수 있습니다. 오히려 팀 내에서 자신이 우수하다고 생각하는 경향이 강한 사람일수록, 실제로는 평범하거나 심할 경우에는 함께 일하기 싫은 사람으로 평가된다고 합니다. 자신이 우수하다는 생각이 강할수록, 팀 내에서 일하며 타인과 자신을 비교하며 알게 된 자기 능력 수준을 과장하는 경향이 있기 때문입니다. 자신보다 더 많은 일을 한 동료가 있더라도 바로 보지 못하고, 자신이 한 일과 노력만 기억하는 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;남성은 성공에 대한 집중을, 여성을 협력을 잘하는 경향이 있습니다.&amp;nbsp;남성은 일을 성공적으로 이끌기 위해 더 집중하고, 욕망을 보이는 한편, 여성은 협력을 중시하며 일을 해내고자 하는 경향이 있다고 합니다. 협력적인 관계를 중요시하는 여성은 자신의 지위에 대해 위기감을 느끼면, 그만큼 협력하려는 의지가 강해지는 특성이 있습니다. 자신과 지내던 사람이 자신을 배신하고 떠난다거나, 다른 사람이 빼앗아 갈지도 모른다는 것을 항상 염두에 두기 때문에 친한 사람 외에는 타인을 배척하는 경향을 보이기도 합니다. 이는 관계적인 문제로도 이어져 때론 업무 효율의 저하를 초래하기도 합니다. 모든 여성이 그렇다는 것은 아니지만, 남성보다 여성이 이러한 경향이 더 강한 편이라고 합니다.&lt;/p&gt;</description>
      <category>Psychology</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/11</guid>
      <comments>https://dreamyard.tistory.com/entry/%EC%A7%81%EC%9E%A5%EC%97%90%EC%84%9C-%EC%95%8C%EB%A9%B4-%EC%A2%8B%EC%9D%80-%EB%8B%A4%EC%96%91%ED%95%9C-%EC%8B%AC%EB%A6%AC%ED%95%99%EC%9D%98-%EA%B2%BD%ED%96%A5-6%EA%B0%80%EC%A7%80#entry11comment</comments>
      <pubDate>Wed, 22 Feb 2023 01:33:31 +0900</pubDate>
    </item>
    <item>
      <title>우리의 생각과 결정에 영향을 주는 무의식</title>
      <link>https://dreamyard.tistory.com/entry/%EC%9A%B0%EB%A6%AC%EC%9D%98-%EC%83%9D%EA%B0%81%EA%B3%BC-%EA%B2%B0%EC%A0%95%EC%97%90-%EC%98%81%ED%96%A5%EC%9D%84-%EC%A3%BC%EB%8A%94-%EB%AC%B4%EC%9D%98%EC%8B%9D</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;많은 사람이 목표를 달성하기 위해 의식적으로 여러 노력을 기울이지만, 이러한 의식적인 노력에도 불구하고 우리는 자기도 모르게 무의식적으로 행동할 때가 많습니다. 연구에 따르면, 아무리 의식적인 행동이더라도 우리의 행동과 결정은 무의식의 영향을 받게 된다고 합니다. 또한 무의식에 의한 결정이 의식적으로 고민 끝에 내린 결정보다 더 좋은 결과를 가져올 때도 있다고 합니다. &lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;9. 무의식과 결정.jpg&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oLr2e/btrZ7GUAuRE/8r4fbJFKDwQkmdY0BJoBu1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oLr2e/btrZ7GUAuRE/8r4fbJFKDwQkmdY0BJoBu1/img.jpg&quot; data-alt=&quot;우리의 생각과 결정에 영향을 주는 무의식&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oLr2e/btrZ7GUAuRE/8r4fbJFKDwQkmdY0BJoBu1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoLr2e%2FbtrZ7GUAuRE%2F8r4fbJFKDwQkmdY0BJoBu1%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; alt=&quot;우리의 생각과 결정에 영향을 주는 무의식&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;500&quot; data-filename=&quot;9. 무의식과 결정.jpg&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;우리의 생각과 결정에 영향을 주는 무의식&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;편견과 무의식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 가지고 있는 편견도 무의식의 영향을 받습니다. 사람은 누구나 공정하고 정직한 사람이 되고 싶어 하지만, 우리는 상점을 이용할 때 사은품이나 포인트 혜택이 있는 곳을 더 선호하게 되는 경향이 있습니다. 스포츠 경기의 심판도 주최 측에 유리한 판단을 내릴 가능성이 높다고 합니다. 아무리 공정한 심판이라 해도 주최 측의 선수를 무의식적으로 편애하게 되기 때문입니다.&lt;br /&gt;&lt;br /&gt;편견을 없애기 어려운 이유는 상대적으로 사람들이 자신의 견해를 바꾸기보단 유지하려고 하기 때문입니다. 이러한 경향이 강한 고지식한 사람일수록 주변 사람들의 노력에도 자기 생각과 견해를 고수하려 합니다. 한 연구에 따르면, 이런 유형의 사람들은 자기 생각과 다른 정보에 관심이 많고, 자기 생각을 억지로라도 합리화하려 노력하는 경향이 있다고 합니다. 또한, 사람들은 분노, 긴장, 초조함을 느낄 때 편견이 더 강해지는 경향이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;감각과 무의식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예일 대학교의 심리학자 John Bargh의 논문에 따르면, 어떤 결정을 내리기 전 무거운 물건을 들면, 그 결정에 대해 더 중요하게 느껴진다고 합니다. 그는 또한 단단한 물건은 사람을 융통성 없게 만든다고 했습니다. 예를 들어, 딱딱한 의자보단 푹신한 의자에 앉아 있을 때 보다 더 유연한 협상이 가능하다는 의미입니다. 이렇게 무의식적으로 느끼게 되는 신체의 감각도 우리의 생각과 결정에 영향을 미칠 수 있습니다.&lt;br /&gt;&lt;br /&gt;말이나 글에서 느껴지는 무의식적인 느낌도 우리에게 영향을 미칩니다. 한 연구에 따르면, 여러 의미의 단어들을 모니터로 의식적으로 알아차리기 힘든 속도로 깜빡거리며 보여준 뒤 맞추게 했는데, 긍정적인 의미의 단어에 노출된 그룹이 더 즐겁고, 열심히 퍼즐 맞추기에 참여했다고 합니다. 이렇게 의식적으로 알아차리기 어려울 정도의 짧은 순간의 자극을 주어 사람들에게 영향을 미치는 효과를 서브리미널 효과(subliminal effect)라고 부릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;때로는 무의식에 의한 결정이 더 옳을 때도 있다.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람들은 중요한 일을 결정해야 할 때 힘들어하며,결정으로 인해 초래될 결과에 대한 두려움으로 인해 결정을 수없이 망설입니다. 하지만, 한 심리학 연구에 따르면 결정을 내리기 전에 고민을 오래 한다고 해서 반드시 만족스러운 결과를 얻는 것은 아니라고 합니다. 복잡한 결정일수록 다양한 요소를 고려해야 하므로 오래 고민하면 할수록 에너지 소모가 커지고, 그만큼 판단력도 둔해집니다. 정작 중요한 요소들은 배제한 채 단기적이고 쉬운 측면만 고려하게 되어 최선의 선택하기가 더더욱 어려워집니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때로는 무의식에 의해 직관적으로 한 결정이 더 도움이 될 때도 있습니다. 미국 노스웨스턴대학교의 심리학과 Ken Paler 교수는 실험자들을 두 그룹으로 나눠 모니터에 빠르게 지나가는 12가지 색의 변화를 관찰하게 했다고 합니다. 한 그룹에는 색의 변화를 집중해서 보도록 하고, 다른 그룹은 주의력을 분산시키며 모니터를 보게 한 뒤, 이전에 보여줬던 그림들을 선택하게 했다고 합니다. 그 결과 주의력을 분산시켰던 그룹이 더 정확한 선택 했다고 합니다. 이는 다양한 경험과 기억에 의해 만들어진 무의식에 따르는 직관적인 결정이 때로는 옳을 수도 있다는 것을 보여줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의식적인 생각이 항상 옳지 않다는 것을 보여주는 사례도 있습니다. 1995년 독일의 한 작가는 어린 시절 나치의 만행을 직접 목격하고 그 실화를 책으로 썼다고 합니다. 그 작품은 큰 호평을 받고 높이 평가되었지만, 그로부터 몇 년 뒤 그 작가는 책의 내용이 잘못되었다며 고백했다고 합니다. 이성적으로 서술된 작품이었음에도 불구하고, 기억 왜곡으로 인해 잘못된 판단을 내린 것입니다. &lt;/p&gt;</description>
      <category>Psychology</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/10</guid>
      <comments>https://dreamyard.tistory.com/entry/%EC%9A%B0%EB%A6%AC%EC%9D%98-%EC%83%9D%EA%B0%81%EA%B3%BC-%EA%B2%B0%EC%A0%95%EC%97%90-%EC%98%81%ED%96%A5%EC%9D%84-%EC%A3%BC%EB%8A%94-%EB%AC%B4%EC%9D%98%EC%8B%9D#entry10comment</comments>
      <pubDate>Mon, 20 Feb 2023 14:42:04 +0900</pubDate>
    </item>
    <item>
      <title>매슬로의 5단계 인간 욕구 이론</title>
      <link>https://dreamyard.tistory.com/entry/%EB%A7%A4%EC%8A%AC%EB%A1%9C%EC%9D%98-5%EB%8B%A8%EA%B3%84-%EC%9D%B8%EA%B0%84-%EC%9A%95%EA%B5%AC-%EC%9D%B4%EB%A1%A0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;에이브러햄 매슬로(Abraham Maslow)는 인본주의 심리학의 대표적인 인물입니다. 1943년 매슬로는 인간의 욕구에 대한 '매슬로의 인간 욕구 5단계 이론(Maslow's hierarchy of needs)'을 제안했습니다. 이 이론에서 매슬로는 인간의 욕구를 생리적 욕구, 안전의 욕구, 애정과 공감의 욕구, 존경의 욕구, 자아실현의 욕구 이렇게 총 5단계로 나누었습니다. 이번 글에서 각각의 욕구에 대해 알아보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;8. 매슬로우의 욕구 이론.jpg&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DAtbz/btrZIsjxkNf/aJIImqKk97TLDwjMVgF931/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DAtbz/btrZIsjxkNf/aJIImqKk97TLDwjMVgF931/img.jpg&quot; data-alt=&quot;매슬로의 5단계 인간 욕구 이론&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DAtbz/btrZIsjxkNf/aJIImqKk97TLDwjMVgF931/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDAtbz%2FbtrZIsjxkNf%2FaJIImqKk97TLDwjMVgF931%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; alt=&quot;매슬로의 5단계 인간 욕구 이론&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;500&quot; data-filename=&quot;8. 매슬로우의 욕구 이론.jpg&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;매슬로의 5단계 인간 욕구 이론&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;매슬로의 5단계 욕구 이론&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 단계에 해당하는 생리적 욕구는 음식을 먹고 싶어 하는 욕구, 물을 마시고 싶어 하는 욕구, 공기를 통해 숨 쉬고자 하는 욕구, 잠자고자 하는 욕구, 성에 대한 욕구 등에 대한 것으로 사람의 욕구 중에 가장 강력합니다. 이 욕구는 우리가 살기 위해 가장 필수적인 욕구로 이 욕구가 충족되지 못하면 우리가 생존하고 살아가는 데 큰 영향을 미칩니다. 매슬로의 욕구 이론은 생리적 욕구라는 기초가 충족되지 못하면 나머지 욕구도 존재할 수 없다는 입장을 가집니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 층에 해당하는 안전의 욕구는 안정감을 느끼며, 두려움과 초조함을 없애고자 하는 욕구입니다. 사람들은 위험에 대비해 각종 보험에 가입하며, 안정적인 직업을 갖기 위해 노력합니다. 이렇듯 사람이라면 누구나 안전을 추구하고자 하는 욕구를 가지고 있습니다.&amp;nbsp;세 번째 층인 애정과 공감의 욕구는 친구를 사귀거나 어느 한 단체에 소속되고자 하는 욕구로 사람들과 관계를 맺고자 하는 욕구입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네 번째 층인 존경의 욕구는 명예욕, 권력욕 등과 관련됩니다. 사람들로부터 주목받고, 인정받고 싶어 하는 욕구입니다. 존경의 욕구를 충족시킴으로써 우리는 활력과 자신감을 얻게 됩니다. 가장 꼭대기 층인 자아실현 욕구는 자기 잠재력을 개발하여 자신이 원하는 사람이 되는 것입니다. 자아실현 욕구는 본능에 의해 압도되는 경우가 많습니다. 다른 욕구에 비해 연약한 편인 자아실현의 욕구는 습관, 문화적인 압력 등에 쉽게 압도되곤 합니다. 다시 말해, 자아실현의 욕구는 생리적인 욕구에 부딪히면 그만큼 포기하기 쉬운 욕구입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상황과 사람에 따라 욕구의 순서가 다를 순 있지만, 매슬로의 욕구 이론에서 단계가 낮은 욕구일수록 우리에게 미치는 영향력은 매우 큽니다. 단계가 높이 올라감에 따라 그 욕구의 크기는 점점 약해집니다. 따라서, 우리가 가장 높은 단계인 존경과 자아실현의 욕구를 달성하기 위해서는 아래 단계를 단단히 쌓아가는 과정이 필요합니다. 지금 당장의 생계를 유지하기 어렵거나, 두려움에 떨고 있는 사람은 자아실현의 욕구를 추구하기 어렵습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;매슬로 욕구 이론의 한계&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이론적으로 보면, 피라미드 구조는 아래에서부터 하나씩 층층이 쌓아 올리는 구조이지만, 그러한 과정이 반드시 절대적인 것은 아닙니다. 상황에 따라 욕구의 일부만 충족되어도 다음 단계의 욕구가 일어나기도 합니다. 또는 몇 가지의 욕구를 한 번에 충족시킬 수 있는 행동을 하기도 합니다. 예를 들어, 자동차를 사는 행위는 몸에 대한 안전과 편안함이라는 생리적 욕구와 자아실현의 욕구가 함께 충족될 수 있습니다. 어떤 사람들은 자신의 꿈을 위해 생리적인 욕구를 포기함으로써 욕구 단계를 뛰어넘는 갖은 노력을 이어가기도 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매슬로는 죽기 전에 자신의 욕구 피라미드에 대한 한계를 인정하며, 피라미드가 뒤집어진 것이 옳았다고 말했다고 합니다. 그의 말대로 옛날에 비해 많은 것들이 풍족해진 현대 사회에서는 자아실현의 욕구가 우세해 보이기도 합니다. 어떤 것이 더 우세한가에 대한 논란이 많은 이론이지만, 매슬로의 인간 욕구 5단계 이론은 우리가 무엇을 위해 살아가는지, 어떻게 살아갈 것인지에 대해 자신에게 고민할 계기가 되어준다는 점에서 또 다른 의미를 가집니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기에 제안된 매슬로의 욕구 이론은 5단계였지만, 이후 매슬로에 의해 1단계가 추가되고 그의 제자들에 의해서 2단계가 추가되면서, 8단계로 확장되었습니다. 추가된 욕구는 자아 초월 욕구, 심미적 욕구, 인지적 욕구입니다. 자아 초월 욕구는 자아를 더 초월하여 다른 것을 만들어내고자 하는 욕구입니다. 심미적 욕구는 자신이 생활하고 사용하는 도구에 대한 것에 대해 심미적인 디자인을 추구하는 욕구입니다. 인지적 욕구는 배우고, 경험하고, 이해하고자 하는 욕구입니다.&amp;nbsp;&lt;/p&gt;</description>
      <category>Psychology</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/9</guid>
      <comments>https://dreamyard.tistory.com/entry/%EB%A7%A4%EC%8A%AC%EB%A1%9C%EC%9D%98-5%EB%8B%A8%EA%B3%84-%EC%9D%B8%EA%B0%84-%EC%9A%95%EA%B5%AC-%EC%9D%B4%EB%A1%A0#entry9comment</comments>
      <pubDate>Sat, 18 Feb 2023 00:06:20 +0900</pubDate>
    </item>
    <item>
      <title>심리학에서 말하는 우리의 의식, 전의식, 잠재의식</title>
      <link>https://dreamyard.tistory.com/entry/%EC%8B%AC%EB%A6%AC%ED%95%99%EC%97%90%EC%84%9C-%EB%A7%90%ED%95%98%EB%8A%94-%EC%9A%B0%EB%A6%AC%EC%9D%98-%EC%9D%98%EC%8B%9D-%EC%A0%84%EC%9D%98%EC%8B%9D-%EC%9E%A0%EC%9E%AC%EC%9D%98%EC%8B%9D</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;프로이트는 인간의 심리를 의식, 전의식, 잠재의식이라는 세 부분으로 나누었습니다. 의식이란 사람들이 인식하는 모든 사상과 감정, 지각을 말합니다. 우리가 현재 느끼는 것과 생각하는 것 모두 의식의 범위 안에 있습니다. 전의식은 필요할 때마다 언제든 불러올 수 있는 자신의 기억과 생각을 의미합니다. 어릴 때 가장 친했던 친구가 누구인지, 어제 점심으로 무엇을 먹었는지 등과 같은 정보들은 모두 전의식 속에 저장되어 있습니다. 잠재의식은 어느 순간 나타났다가도 사라지기도 하는 신비의 영역입니다. 잠재의식은 심오하고 예측할 수 없는 부분으로 인간 심리 가운데 가장 주된 부분이라고 볼 수 있습니다. 세 가지 의식을 비율로 따지면, 의식과 전의식의 비율은 5%에 불과하고 나머지 95%가 모두 잠재의식이라고 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;7. 의식과 무의식.jpg&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqHCcx/btrZz1fbFy0/mhCEchiMTBxDBaKlVet6hK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqHCcx/btrZz1fbFy0/mhCEchiMTBxDBaKlVet6hK/img.jpg&quot; data-alt=&quot;심리학에서 말하는 우리의 의식, 전의식, 잠재의식&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqHCcx/btrZz1fbFy0/mhCEchiMTBxDBaKlVet6hK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqHCcx%2FbtrZz1fbFy0%2FmhCEchiMTBxDBaKlVet6hK%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; alt=&quot;심리학에서 말하는 우리의 의식&amp;amp;#44; 전의식&amp;amp;#44; 잠재의식&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;500&quot; data-filename=&quot;7. 의식과 무의식.jpg&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;심리학에서 말하는 우리의 의식, 전의식, 잠재의식&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;잠재의식과 의식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;겉으로 드러나는 우리의 삶의 모습은 모두 잠재의식에서 나온다고 볼 수 있습니다. 지금의 나를 만들고 행동하게 하는 것은 잠재의식의 영향이 큽니다. 처음엔 영향이 미미하더라도 우리의 삶 속에 조금씩 녹아들며 현실 생활 속의 나를 점차 잠재의식과 가깝게 만들어갑니다. 잠재의식 속에는 의식과 전의식에는 없는 훨씬 많은 것들이 숨겨져 있습니다. 심지어는 도덕적 윤리를 배반하거나 비이성적인 행동들도 잠재되어 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의식은 마주하고 싶지 않거나 감당하기 힘든 일들을 모두 잠재의식 속으로 넣어버리기도 합니다. 그렇게 함으로써 아무런 문제가 없는 듯한 잔잔함을 유지합니다. 하지만, 잠재의식이 이러한 것들을 감당하는 데에도 한계가 있습니다. 잠재의식 속에 부정적이고 암울한 것들이 너무 많이 쌓이다 보면 의식의 영역까지 영향을 주게 되는데, 이럴 경우 심리적인 질병으로까지 이어질 수 있습니다. 즉, 이러한 관점에서 심리적 질병의 근본 원인은 모두 잠재의식으로부터 나오는 것이라고 볼 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잠재의식은 때론 의식이 하는 역할처럼 감각기관으로부터 받은 정보를 해석하거나 수정하기도 합니다. 우리의 망막에는 맹점이 있는데, 어떤 물체의 모습이 맹점에 닿으면 우리는 그 물체를 눈앞에 빤히 두고도 볼 수 없는 경우가 발생합니다. 하지만, 보통의 상태에서는 맹점을 느낄 수 없는데 이 이유는 대뇌가 맹점 주위에서 얻은 데이터를 근거로 맹점으로 인해 보지 못하는 부분을 채워 넣기 때문입니다. 맹점을 메꾸기 위해서 우리의 눈동자는 매초 단위로 아주 미세하게 움직이는데 이를 미소안운동(microsaccades)이라고 합니다. 누군가와 대화할 때도 우리의 시선은 상대의 얼굴 안에서 이리저리 쉴 새 없이 움직입니다. 눈동자를 제어하는 6개의 근육에 의해 일어나는 이 운동은 매우 미세하고 빠르기 때문에 특수 기기의 도움 없이는 측정하기가 힘들 정도라고 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;잠재의식과 의식의 차이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잠재의식은 감각기관이 전달하는 정보를 해석하고 수정하고 보완하는 역할을 합니다. 우리가 듣는 소리, 보는 빛, 느끼는 감촉 등 모두 잠재의식을 거쳐 우리에게 인식됩니다. 만약 이런 역할을 하는 잠재의식이 사라진다면, 우리에게 세상은 뒤죽박죽으로 보일지도 모릅니다. 의식은 잠재의식을 기반으로 활동하기 때문에 잠재의식이 사라진다면, 우리는 아무것도 의식할 수 없게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잠재의식은 의식이 알아차리기도 전에 정보를 먼저 예측하며 탐지합니다. 만약, 받아들인 정보에 의해 위험하다는 판단이 들면 즉각적으로 두려움을 느끼는 것도 잠재의식입니다. 잠재의식은 순간적으로 이상을 감지하고 빠르게 반응합니다. 이에 반해, 의식은 잠재의식보다는 느리지만 주변 상황을 꼼꼼하게 분석하기 때문에 종종 잠재의식으로 인한 착오를 바로잡기도 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잠재의식은 받아들인 정보에 대해 즉각적인 반응을 보이며, 현실에 보다 충실합니다. 반대로 의식은 미래에 대한 계획을 세울 줄 압니다. 의식은 미래에 이루고 싶은 목표를 위해 좀 더 많은 시간을 투자할 것을 권유하지만, 잠재의식은 우리가 하고 싶은 대로 휴식을 취하고, 사람들과 어울리도록 유혹합니다. 우리는 의식과 잠재의식의 끊임없는 충돌 속에서 살아가고 있는 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 의식적으로 신경 쓰지 않아도 자연스럽게 되는 행동들이 있습니다. 자전거를 타면서 노래를 듣더라도 우리 몸은 자연스럽게 중심을 잡으려 하고, 머릿속을 스쳐 가는 이런저런 생각들도 의식적으로 하기보다는 자연스럽게 떠오르는 경우가 많습니다. 이러한 것들은 잠재의식에 의한 것으로, 잠재의식을 통해 자동으로 이루어지는 것들에 대해서 우리는 크게 힘이 든다거나 불편함을 잘 느끼지 못합니다. 이에 반해 의식은 수동적으로 상황을 일일이 분석하고 판단해서 이루어집니다.&amp;nbsp;&lt;/p&gt;</description>
      <category>Psychology</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/8</guid>
      <comments>https://dreamyard.tistory.com/entry/%EC%8B%AC%EB%A6%AC%ED%95%99%EC%97%90%EC%84%9C-%EB%A7%90%ED%95%98%EB%8A%94-%EC%9A%B0%EB%A6%AC%EC%9D%98-%EC%9D%98%EC%8B%9D-%EC%A0%84%EC%9D%98%EC%8B%9D-%EC%9E%A0%EC%9E%AC%EC%9D%98%EC%8B%9D#entry8comment</comments>
      <pubDate>Fri, 17 Feb 2023 01:50:44 +0900</pubDate>
    </item>
    <item>
      <title>다른 사람의 호감을 얻는 법</title>
      <link>https://dreamyard.tistory.com/entry/%EB%8B%A4%EB%A5%B8-%EC%82%AC%EB%9E%8C%EC%9D%98-%ED%98%B8%EA%B0%90%EC%9D%84-%EC%96%BB%EB%8A%94-%EB%B2%95</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;사람들의 호감을 얻는다는 것은 그만큼 사회에 소속되어 사람들의 관계망 속에서 서로에게 줄 수 있는 혜택을 공유할 수 있다는 것을 의미합니다. 사람들의 호감을 사면 살수록, 나를 위해 특별히 더 애써주고 혹시나 실수하더라도 도와주거나 눈감아주려는 사람들이 늘어나게 됩니다. 사람들로부터 호감을 얻는 것은 사회생활에 있어서 꽤 유용한 일 중 하나입니다.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;6. 호감얻기.jpg&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dpgw9k/btrZsGH8sXF/1WkwDNjbyJ5dSwyyrk5vyK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dpgw9k/btrZsGH8sXF/1WkwDNjbyJ5dSwyyrk5vyK/img.jpg&quot; data-alt=&quot;다른 사람의 호감을 얻는 법&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dpgw9k/btrZsGH8sXF/1WkwDNjbyJ5dSwyyrk5vyK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdpgw9k%2FbtrZsGH8sXF%2F1WkwDNjbyJ5dSwyyrk5vyK%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; alt=&quot;다른 사람의 호감을 얻는 법&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;500&quot; data-filename=&quot;6. 호감얻기.jpg&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;다른 사람의 호감을 얻는 법&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;칭찬과 따라 하기는 호감을 얻기 좋은 방법이다. &lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;칭찬은 다른 사람들에게 호감을 얻을 수 있는 효과적인 방법입니다. 칭찬은 과하면 때론 '아부'라는 표현으로 불리기도 합니다. 상사에게 잘 보이고 싶다면, 직접적인 칭찬도 좋지만 직장 동료나 주변 사람들에게 살짝 흘리는 것도 좋은 방법입니다. 제삼자를 통해 들리는 칭찬은 상대에게 보다 진실성 있게 느껴지기 때문입니다. 또는 조언을 청하는 것도 그 사람을 존중한다고 느끼게 하기 때문에 좋은 방법입니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;우리는 자기를 좋아했으면 하는 사람이 있으면, 그 사람의 행동을 무의식적으로 따라 하기도 합니다. 그 사람이 코를 만지면 코를 만지기도, 다리를 꼬고 있으면 따라서 다리를 꼬기도 합니다. 실제 한 연구에 따르면, 상대가 자기 행동을 은근히 따라 하면 그 사람에게 은연중에 더 호감을 갖게 된다고 합니다. 심지어 이런 방법은 중요한 협상이나 회의에서도 보다 더 나에게 유리한 쪽으로 결과를 내는 데 도움을 준다고 합니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 다른 사람들의 호감을 얻고 싶어 자신의 의견을 그들에 맞게 조정하기도 합니다. 사람들은 자신과 비슷한 사람을 좋아하고, 다른 사람을 싫어하는 경향이 있습니다. 조금이라도 더 자신과 음식 취향이 비슷하고, 생각도 비슷하고, 옷도 비슷하게 입는 사람을 좋아합니다. 우리 자신도 이 점을 알고 있기 때문에, 우리는 때론 상대의 호감을 얻고자 입는 옷 스타일, 의견, 심지어는 주량까지 조절하며 상대와 비슷해지기 위해 노력하기도 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;외모와 겸손이 호감에 미치는 영향&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매력적인 외모도 상대의 호감을 얻는 데 영향을 미칩니다. 통계적으로도 외모가 매력적일수록 사람들의 신뢰를 얻거나, 직장을 쉽게 구하거나, 임금을 더 많이 받는 경향이 있다고 합니다. 많은 사람이 머리 관리, 장신구, 옷, 끊임없는 다이어트 등을 통해 외모의 단점을 감추고 더 매력적인 사람이 되기 위해 노력합니다. 성형 수술, 피부 관리 등 외모를 가꾸는 데에 아낌없이 자산을 투자하기도 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상대를 향해 미소 짓는 행동 또한 호감을 얻는 데 도움을 줍니다. 자연스러운 미소는 미소를 짓는 사람이 보다 더 사회성과 붙임성이 있어 보이게 합니다. 인간 관계론에서 카네기는 미소는 상대에게 당신을 좋아하고, 당신 덕에 행복하고, 당신을 만나 기쁘다는 의미를 담는다며 미소의 중요성을 강조했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 시험에서 1등을 하거나 합격했다면, 주변에 알릴 자랑거리이지만, 이는 다른 이들의 호감을 얻는 데는 도움이 되지 못합니다. 일반적으로 사람들은 자신의 성공을 자랑하기보다는 내세우지 않는 사람을 좋아한다고 합니다. 반대로 지나친 겸손은 자존감이 낮거나, 자기에 대해 잘 모르는 사람으로 비칠 수도 있습니다. 따라서, 적정하고 진정성 있는 겸손은 사람들의 호감을 얻는 데 도움을 줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;권력이 있는 사람일수록 타인의 호감을 얻으려는 경향이 약하다.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사회적인 권력이 있는 사람들일수록 영향력을 발휘하거나 원하는 것을 얻을 수 있는 여러 수단을 가지고 있기 때문에 사람들의 호감을 얻으려는 경향이 약해집니다. 또한, 영향력을 지닌 사람일수록 직원이나 자신보다 아랫사람에게 칭찬하더라도 아부 떠는 것으로 받아들여질 가능성이 작아 아랫사람이 윗사람에 하는 것보다 보다 쉽게 호감을 얻어낼 수 있습니다. 하지만, 아랫사람의 의견에 맞게 자신의 의견을 조정하는 행동은 자신의 지위를 위협할 수도 있어 이런 방법으로 호감을 얻는 일은 드문 경향이 있다고 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;상충하는 양쪽의 호감을 얻어야 할 때는 어떡해야 할까&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상충하는 의견을 내놓는 양쪽 사이에서 모두 호감을 얻어내야 하는 상황은 한 사람의 호감을 얻는 것보다 훨씬 더 어렵습니다. 그런데도, 맞지 않는 두 친구 사이나 직장 상사와 동료 사이 등 우리는 종종 양쪽의 호감을 얻어야 하는 입장에 놓이곤 합니다. 상충하는 양쪽의 호감을 모두 얻기 위해서는 어떻게 해야 할까요? 이런 경우에는 완화된 자기 제시를 통해 양쪽의 의견에 절묘하게 중립적인 의견을 내는 방법이 있습니다. 하지만, 자칫 양쪽 모두의 미움을 살까 두렵다면 양쪽에 각각 다른 입장으로 대응해 보는 것도 방법입니다.&amp;nbsp;&lt;/p&gt;</description>
      <category>Psychology</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/7</guid>
      <comments>https://dreamyard.tistory.com/entry/%EB%8B%A4%EB%A5%B8-%EC%82%AC%EB%9E%8C%EC%9D%98-%ED%98%B8%EA%B0%90%EC%9D%84-%EC%96%BB%EB%8A%94-%EB%B2%95#entry7comment</comments>
      <pubDate>Wed, 15 Feb 2023 23:33:37 +0900</pubDate>
    </item>
    <item>
      <title>내 맘대로 되지 않는 나의 이미지, 자기 제시(self-presentation)의 실패</title>
      <link>https://dreamyard.tistory.com/entry/%EC%9E%90%EA%B8%B0-%EC%A0%9C%EC%8B%9C%EC%9D%98-%EC%8B%A4%ED%8C%A8%EC%99%80-%EA%B1%B0%EC%A7%93%EB%A7%90-%ED%83%90%EC%A7%80%EA%B8%B0%EC%97%90-%EB%8C%80%ED%95%9C-%EC%A7%84%EC%8B%A4</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;앞에서 말했듯 자기 제시(self-presentation)는 타인에게 형성되는 나의 인상을 통제하려는 과정입니다. 소개팅이라던가 원하는 상대와의 첫 데이트 만남에서 우리는 좋은 첫인상을 남기려고 노력합니다. 하지만 종종 다르게 원치 않는 상황과 상대의 반응으로 인해, 원하는 방향으로의 자기 제시(self-presentation)를 끌어내지 못하는 경우도 있습니다. 상대에게 자신이 원하는 인상을 심어주기 위해선 특별한 노력과 집중력이 필요하지만, 그 노력과는 별개로 오히려 원치 않는 인상을 남겨버릴 때도 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;5. 자기 제시의 실패, 거짓말 탐지기.jpg&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ba3sNg/btrY1mCVEPA/8elVmccjxcK6RGUiv7ggrk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ba3sNg/btrY1mCVEPA/8elVmccjxcK6RGUiv7ggrk/img.jpg&quot; data-alt=&quot;자기 제시의 실패와 거짓말 탐지기에 대한 진실&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ba3sNg/btrY1mCVEPA/8elVmccjxcK6RGUiv7ggrk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fba3sNg%2FbtrY1mCVEPA%2F8elVmccjxcK6RGUiv7ggrk%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; alt=&quot;자기 제시의 실패와 거짓말 탐지기에 대한 진실&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;500&quot; data-filename=&quot;5. 자기 제시의 실패, 거짓말 탐지기.jpg&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;자기 제시의 실패와 거짓말 탐지기에 대한 진실&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;자기 제시(self-presentation) 실패의 대가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원하고 계획했던 자기 제시(self-presentation)의 방향에 많은 것들이 걸려있을수록 그 실패의 대가는 큽니다. 심리적 대가인 창피함과 자존감 하락과 더불어 실직이나 실연처럼 여실히 느껴지는 큰 대가를 치르기도 한합니다. 이러한 대가를 어느 정도 알고 짐작하기 때문에 우리는 자기 제시에 대해 두려움을 느끼기도 합니다. 자기 제시의 실패에 대한 불안감은 사회적으로 흔한 현상이지만, 정도가 심해지면 상황을 무조건 회피하려 하거나 도망치려 하기도 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때론 상대에게 부정적인 인상을 심어주는 자기 제시(self-presentation)를 하는 경우도 있습니다. 소개팅에서 맘에 들지 않는 상대를 만났을 때, 대화를 짧게 끊어버리거나 시선을 회피하는 등의 태도를 취하기도 합니다. 하지만,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;사람들은 대개 정직하고 믿을 만한 사람으로 보이고 싶어 하며, 일관성 있고 예측할 수 있는 안정적인 사람으로 이미지를 남기고 싶어 합니다. 대외적으로&lt;/span&gt;&lt;span&gt; 심어두면 유용한 이미지로는 호감 가는 사람, 유능한 사람, 지위와 권력이 있는 사람이 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;거짓된 자기 제시(self-presentation)의 대가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순한 노력만으로 원하는 인상을 남기기 어려울 것 같다는 판단이 들 땐, 심지어 우리는 가짜로 자신을 연기하며 자기 제시(self-presentation)를 하려고 하기도 합니다. 상사의 말에 진심과 다른 격한 반응을 보이기도 하고, 맘에 들지 않는 생일 선물을 받더라도 상대를 위해 기뻐하는 척을 하는 것처럼요. 이렇게 꾸며낸 자기 제시는 때론 상대에게 '그런 척을 한다.'라는 느낌을 주어 정직하지 못한 사람, 위선적인 사람이라는 인상을 심어 주기도 합니다. 이러한 이미지 손상이 사회적인 낙인으로까지 이어지면 기피의 대상이 되고 사회적으로 고립되는 상황까지 이어지기도 합니다. 터무니없고 과장된 자기 제시(self-presentation)일수록 크고 장기적인 대가를 치르게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거짓된 자기 제시(self-presentation)는 자신뿐 아니라 상대와 주변 사람들에게 큰 피해가 되기도 합니다. 연인에 대한 거짓된 행동은 상대에게 큰 상처가 되기도 하고, 함께 일한 직장동료들이나 상사에게도 심리적일 뿐만 아니라 업무적인 피해를 주는 경우도 있습니다. 심할 경우, 비단 내 주변뿐 아니라 사회적으로 심각한 범죄 사건으로까지 이어지기도 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;우리는 거짓된 자기 제시(self-presentation)를 탐지할 수 있을까&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거짓말탐지기는 피부에서 일어나는 전기전도적인 변화, 혈압, 심박수, 호흡으로 나타나는 신체의 각성도를 측정하고 기록합니다. 이러한 기록을 바탕으로 질문을 듣고 답하면서 일어나는 상대의 신체 각성도를 분석하여 이루어집니다. 예를 들어, 용의자에게 진실을 말할 가능성이 큰 범죄와는 상관없는 질문을 했을 때보다 범죄와 관련된 행동에 대해 질문을 했을 때 각성도가 증가하는지를 보는 방식으로 이루어집니다. &amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 거짓말 탐지기가 기록하는 요소 중, 심박수와 피부에서 일어나는 전기 변화는 거짓말 판별에 정확한 척도가 되지 못합니다. 범죄 사실과는 별개로 질문에 따라 용의자가 단순한 분노를 느끼거나 공포를 느끼기도 하기 때문입니다. 결과적으로 보면, 거짓말탐지기의 정확도는 25%부터 90%까지 매우 불규칙적이며, 결백한 사람을 유죄로 판별해 버릴 가능성이 더 높다고 합니다. 또한, 검사를 받는 사람이 거짓말 탐지기를 믿지 않을수록 신체적인 반응 정도도 약해지기 때문에 정확도가 더 떨어질 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거짓말 탐지기 말고도, 뇌파를 이용하거나, 얼굴의 발열 패턴을 기록하는 열화상 처리 기술을 이용하거나, 핵자기공명장치인 fMRI를 통해 뇌의 활성화 패턴을 추적하려 하는 등 거짓말을 측정하려는 많은 시도가 있었지만, 지금까지 실제로 유의미하게 거짓말을 판별해 내는 것으로 입증된 기술은 없다고 합니다.&amp;nbsp;&lt;/p&gt;</description>
      <category>Psychology</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/6</guid>
      <comments>https://dreamyard.tistory.com/entry/%EC%9E%90%EA%B8%B0-%EC%A0%9C%EC%8B%9C%EC%9D%98-%EC%8B%A4%ED%8C%A8%EC%99%80-%EA%B1%B0%EC%A7%93%EB%A7%90-%ED%83%90%EC%A7%80%EA%B8%B0%EC%97%90-%EB%8C%80%ED%95%9C-%EC%A7%84%EC%8B%A4#entry6comment</comments>
      <pubDate>Sun, 12 Feb 2023 14:24:29 +0900</pubDate>
    </item>
    <item>
      <title>상대로부터 필요한 것을 얻기 위한 노력, 자기 제시(self-presentation)란?</title>
      <link>https://dreamyard.tistory.com/entry/%EC%83%81%EB%8C%80%EB%A1%9C%EB%B6%80%ED%84%B0-%ED%95%84%EC%9A%94%ED%95%9C-%EA%B2%83%EC%9D%84-%EC%96%BB%EA%B8%B0-%EC%9C%84%ED%95%9C-%EB%85%B8%EB%A0%A5-%EC%9E%90%EA%B8%B0-%EC%A0%9C%EC%8B%9Cself-presentation%EB%9E%80</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 사람들에게 전달하고 싶은 나의 이미지에 따라 어떤 옷을 입을지, 카카오톡이나 인스타에 어떤 프로필 사진을 올릴지 등을 고민합니다. 많은 사람이 옷을 사거나, 자기 자신을 꾸미는데 많은 시간을 들이는 이유도 자신의 외관이 다른 사람들의 자신을 보는 시선에 영향을 준다는 것을 알고 있기 때문입니다. 이러한 것을 알고 우리는 사람들에게 원하는 이미지를 심어주고자 여러 가지 시도와 함께 타인에게 형성되는 나의 인상을 통제하려는 과정을 겪게 되는데 이를 사회심리학에선 &lt;span&gt;자기 제시(self-presentation)라고 합니다. 우&lt;/span&gt;리는 이러한 자기 제시(self-presentation)를 겪으며 사회에서 사람들과 여러 상호작용을 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;5. 자기 제시.jpg&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bozl6l/btrYSOgqVB2/Qht8z8PaekY6a29cZt9yF0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bozl6l/btrYSOgqVB2/Qht8z8PaekY6a29cZt9yF0/img.jpg&quot; data-alt=&quot;상대로부터 필요한 것을 얻기 위한 노력, 자기 제시(self-presentation)란&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bozl6l/btrYSOgqVB2/Qht8z8PaekY6a29cZt9yF0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbozl6l%2FbtrYSOgqVB2%2FQht8z8PaekY6a29cZt9yF0%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; alt=&quot;상대로부터 필요한 것을 얻기 위한 노력&amp;amp;#44; 자기 제시(self-presentation)란&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;500&quot; data-filename=&quot;5. 자기 제시.jpg&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;상대로부터 필요한 것을 얻기 위한 노력, 자기 제시(self-presentation)란&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;자기 제시(self-presentation)는 우리에게 어떤 영향을 줄까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 무언가를 타인에게서 얻고자 할 때, 상대를 더 의식하게 됩니다. 자신에게 가치 있는 것을 얻기 위해 상대에게 자기 능력과 장점을 어필하며 자기 제시(self-presentation)를 합니다. 우리가 원하거나 필요로 하는 것을 다른 사람들이 가지고 있을 때, 우리는 그것을 얻기 위해 상대를 설득해야 합니다. 면접관, 데이트하고 싶은 이성처럼 강한 어필을 해야 할 상대를 만날 때도 있습니다. 우리는 이런 상황에서 우리의 장점과 능력을 최대한 상대에게 보여주고자 최선을 다해 노력하곤 합니다. 때로는 상황에 따라 자신의 약점을 보여주거나, 좋은 점을 축소할 때도 있기도 하고요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 우리는 타인의 시선을 의식하는 과정을 통해 자아상을 그려 나가기도 합니다. 누군가 내가 하는 말에 잘 웃어준다면, 내가 생각하는 내가 '유머 감각이 있는 사람'으로 느껴지기도 합니다. 그리고 한편으론, 우리는 내가 생각하는 나의 모습을 있는 그대로 좋게 봐주는 사람들에게서 편안함을 느끼기도 합니다.&amp;nbsp;우리는 타인뿐 아니라, 자기 자신에게도 자기 제시(self-presentation)하며 자아상을 바꾸어 나가기도 합니다. 내가 원하는 방향으로 자기 제시(self-presentation)를 통해 내가 재미있는 사람이라는 자아상이 강해지면 그만큼 사람들을 웃기는 것에 대한 동기가 생기고, 그런 행동을 더 많이 하며 자아상을 강화해 갑니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 방향의 자기 제시(self-presentation) 방식은 우리가 사회에서 어떻게 대우받기를 원하는지 사람들에게 인식시키는 데에도 도움을 줍니다. 사회적인 만남이 더욱 순조롭고 내가 원하는 대로 이루어질 수 있도록 하는 긍정적인 작용을 하기도 합니다. 결과적으로, 자기 제시(self-presentation)는 자신에게 중요하고 필요한 것들을 얻도록 도와주고, 자신만의 자기 정체성을 형성하고 유도하도록 도와주며, 사회적 만남을 더욱 순조롭게 해 줄 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;우리는 어떨 때, 더 자기 제시(self-presentation)를 하게 될까?&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람들은 자신이 여러 사람의 시선 앞에 있다는 것을 인식할 때 자기 제시(self-presentation)를 할 가능성이 높습니다. 사진 촬영을 위해 사진사 앞에서 포즈를 취할 때, 여러 사람 앞에서 말을 하거나 강연할 때 등과 같은 상황에서 자신의 외관을 평소보다 더 신경 쓰고, 예절을 지키며 자기 제시(self-presentation)를하게 됩니다. 반대로 우리는 사람들이 없을 때도 자신을 타인의 시선으로 바라볼 때도 많습니다. 이런 현상을 심리학에선 조명 효과(spotlight effect)라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사람마다 다른 자기 제시(self-presentation) 동기의 정도&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자신이 눈에 띄는 것이 상대적으로 민감한 사람들도 있습니다. 타인이 자신에게 신경을 쓰고 있다고 믿는 것을 공적 자기의식(public self-consciousness)은 사람마다 다른데, 공적 자기의식이 높은 사람은 다른 사람들이 자신을 어떻게 보는지에 대해 민감하게 반응하고, 거부당하는 것에 부정적으로 반응하며 자기 외모와 평판에 더 집중한다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 관찰자들이 자신의 목표에 영향을 미칠 때, 그 목표가 자신에게 중요한 만큼 더 전략적으로 자기 제시(self-presentation) 합니다. 또는 관찰자가 이미 받은 인상이 내가 심어주고자 하는 인상과 다를 때 더더욱 전략적으로 행동하게 됩니다. 예를 들어, 직장 상사가 나를 지켜볼 때 더욱 호감 가는 사람으로 보이려 행동을 조심하고, 때론 상사가 듣기 좋은 말을 일부러 하기도 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자기 제시(self-presentation)에 대한 동기 부여 강도는 사람마다 다릅니다. 계속해서 자신의 대외적인 이미지에 신경 쓰며 상황에 따라 행동을 조정하는 경향인 자기 감시(self-monitoring)가 높은 사람은 타인이 있든 없든 자신의 이미지를 관리하려고 합니다. 그런 만큼 그들은 다른 사람들이 원하는 것을 가늠하고 그들의 요구에 맞게 행동을 바꾸는 데 능숙합니다. 상대의 행동도 잘 모방하고, 타인의 표정을 금세 읽어 언제 자신을 조종하려 드는지 감지해 내곤 합니다.&lt;/p&gt;</description>
      <category>Psychology</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/5</guid>
      <comments>https://dreamyard.tistory.com/entry/%EC%83%81%EB%8C%80%EB%A1%9C%EB%B6%80%ED%84%B0-%ED%95%84%EC%9A%94%ED%95%9C-%EA%B2%83%EC%9D%84-%EC%96%BB%EA%B8%B0-%EC%9C%84%ED%95%9C-%EB%85%B8%EB%A0%A5-%EC%9E%90%EA%B8%B0-%EC%A0%9C%EC%8B%9Cself-presentation%EB%9E%80#entry5comment</comments>
      <pubDate>Sat, 11 Feb 2023 16:17:37 +0900</pubDate>
    </item>
    <item>
      <title>담배를 피우면 아이디어가 잘 떠오른다? 좋은 아이디어를 내기 위해 알아두면 좋은 것 3</title>
      <link>https://dreamyard.tistory.com/entry/%EB%8B%B4%EB%B0%B0%EB%A5%BC-%ED%94%BC%EC%9A%B0%EB%A9%B4-%EC%95%84%EC%9D%B4%EB%94%94%EC%96%B4%EA%B0%80-%EC%9E%98-%EB%96%A0%EC%98%A4%EB%A5%B8%EB%8B%A4-%EC%A2%8B%EC%9D%80-%EC%95%84%EC%9D%B4%EB%94%94%EC%96%B4%EB%A5%BC-%EB%82%B4%EA%B8%B0-%EC%9C%84%ED%95%B4-%EC%95%8C%EC%95%84%EB%91%90%EB%A9%B4-%EC%A2%8B%EC%9D%80-%EA%B2%83-3</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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;4. 좋은 아이디어.jpg&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YM8yl/btrYRfMI8hb/omToNexRWyJAFAuDB2GnF0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YM8yl/btrYRfMI8hb/omToNexRWyJAFAuDB2GnF0/img.jpg&quot; data-alt=&quot;담배를 피우면 아이디어가 잘 떠오른다? 좋은 아이디어를 내기 위해 알아두면 좋은 것 3&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YM8yl/btrYRfMI8hb/omToNexRWyJAFAuDB2GnF0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYM8yl%2FbtrYRfMI8hb%2FomToNexRWyJAFAuDB2GnF0%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; alt=&quot;담배를 피우면 아이디어가 잘 떠오른다? 좋은 아이디어를 내기 위해 알아두면 좋은 것 3&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;500&quot; data-filename=&quot;4. 좋은 아이디어.jpg&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;담배를 피우면 아이디어가 잘 떠오른다? 좋은 아이디어를 내기 위해 알아두면 좋은 것 3&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;담배를 피우면, 아이디어가 잘 떠오른다?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'담배를 피우면 머리가 맑아지고, 일의 효율성이 높아진다.'라는 말이 있는데, 이는 우리 뇌에 대한 일시적인 속임수에 불과합니다. 담배를 피우게 되면 니코틴은 폐를 통해 흡수되어 뇌에 도달하여 면 뇌의 니코틴 수용체에 결합하게 됩니다. 이러한 결합은 뇌에 '아세틸콜린'이라는 물질이 충분하다는 착각을 일으키게 해서 아세틸콜린을 더 이상 생성하지 않게 하고, 그 결과, 아세틸콜린이 부족해지는 상태가 됩니다. 점점 이런 상태가 지속되면 뇌에서는 점점 '아세틸콜린'을 분비하지 않게 되고 이에 따라 우리 몸은 니코틴을 계속해서 찾게 되는 니코틴 의존증에 걸리게 되는 것입니다. (아세틸콜린은 우리가 안정 상태일 때 뇌 내에서 분비되는 물질입니다. 뇌에 작용하여 발상력과 기억력을 높여주는 것으로 알려져 있습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;담배를 피워 머리가 맑아진다고 느끼는 것은 니코틴에 의한 일시적인 효과에 불과합니다. 니코틴은 30분 만에 절반으로 줄어들며, 금세 몸은 아세틸콜린이 부족하다고 느끼게 되고 이에 따라 우리 몸과 마음이 불안정하고 불안한 상태가 되는 것입니다. 일시적으로 아이디어가 잘 떠오르는 상태가 될진 몰라도, 점차 우리 뇌는 니코틴 없이는 맑아질 수 없는 상태가 되어 더 많은 아이디어를 떠올릴 수많은 기회와 시간을 놓쳐버리게 될지도 모릅니다.&amp;nbsp; &amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;뇌에서 발생하는 시터파(Theta Welle)는 발상력과 기억력을 높여준다.&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 심적으로 편안한 상태에 있게 되면 알파파라는 뇌파가 나옵니다. 하지만, 알파파 말고도 시터파(Theta Welle)라는 뇌파도 나온다는 것을 들어보신 적 있으실까요? 시터파(Theta Welle)는 우리가 잠이 들기 직전 졸고 있는 상태, 낮잠, 또는 깊은 명상 상태일 때 나오는 뇌파입니다. 알파파의 주파수가 9~12Hz라면, 시터파(Theta&amp;nbsp;Welle)의 주파수는 알파파보다 조금 느린 4~7Hz인데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;시터파(Theta Welle)는 평소 뇌의 해마로부터 발생하는데, 우리 몸에서 생성되는 '아세틸콜린'은 우리 뇌의 해마에 작용하여, 더 많은 시터파(Theta&amp;nbsp;Welle)가 생성되게 합니다. &lt;/span&gt;이렇게 시터파(Theta Welle)가 더 많이 발생하면 뇌의 시냅스가 쉽게 연결될 수 있는 상태가 되어 기억력이 좋아질 뿐만 아니라 아이디어에 대한 발상력도 올라가게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시터파(Theta&amp;nbsp;Welle)는 낯선 길을 가보거나, 새로운 일에 도전할 때 또는 새로운 자극을 받았을 때 가장 활발하게 나옵니다. 평소 다니지 않던 낯선 길로 산책을 해보는 것도 좋은 방법입니다. 동네에 새로운 가게가 생겼다면, 자주 가던 식당에 새로운 메뉴가 생겼다면, 새로운 가게에 구경을 가보고 새로운 음식을 맛보아 보세요. 모두 아세틸콜린 분비가 촉진되며 시터파(Theta&amp;nbsp;Welle)의 발생이 활발해지는 방법입니다. 따라서, 기발한 아이디어는 회의실에서 나오는 것이 아니라, 시터파(Theta Welle)가 더 잘 발생하는 샤워 중이나 침대에 누워있을 때 같은 편안한 상태에서 떠오를 가능성이 더 높습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 시터파(Theta Welle)는 얕은 수면 상태인 렘수면 상태일 때도 활발히 발생됩니다. 이는 아세틸콜린이 활성화되기 때문이기도 한데요. 렘수면 상태일 때 때론 우리는 비현실적이고 기상천외한 꿈을 꾸기도 합니다. 벤젠 고리를 발견한 독일의 화학자 케쿨레는 원 모양으로 자기 꼬리를 물고 있는 뱀인 우로보로스를 꿈에서 보고, 벤젠 고리를 생각해 냈다고 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;레시틴이 풍부한 식사는 아이디어 발상에 도움을 준다.&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레시틴은 달걀노른자, 대두, 간, 땅콩 등에 풍부하게 함유되어 있습니다. 이러한 레시틴은 아세틸콜린의 주된 원료입니다. 앞에서 말씀드렸듯, 아세틸콜린을 우리 뇌의 기억력과 발상력을 높여주는 역할을 합니다. 식사를 통해 레시틴을 충분히 섭취할수록 아세틸콜린이 활성화에 도움이 되고, 이에 따라 우리의 뇌는 기발한 아이디어를 내기에 더 좋은 상태가 되는 것입니다. 또한, 레시틴은 다른 성분들에 비해 뇌로 잘 전달된다는 특성이 있습니다. 레시틴은 자체적인 유화작용에 의해 콜레스테롤과 지방 성분을 분해하여 동맥경화와 지방간을 예방하는 데에도 도움을 줍니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;</description>
      <category>Psychology</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/4</guid>
      <comments>https://dreamyard.tistory.com/entry/%EB%8B%B4%EB%B0%B0%EB%A5%BC-%ED%94%BC%EC%9A%B0%EB%A9%B4-%EC%95%84%EC%9D%B4%EB%94%94%EC%96%B4%EA%B0%80-%EC%9E%98-%EB%96%A0%EC%98%A4%EB%A5%B8%EB%8B%A4-%EC%A2%8B%EC%9D%80-%EC%95%84%EC%9D%B4%EB%94%94%EC%96%B4%EB%A5%BC-%EB%82%B4%EA%B8%B0-%EC%9C%84%ED%95%B4-%EC%95%8C%EC%95%84%EB%91%90%EB%A9%B4-%EC%A2%8B%EC%9D%80-%EA%B2%83-3#entry4comment</comments>
      <pubDate>Sat, 11 Feb 2023 08:00:07 +0900</pubDate>
    </item>
    <item>
      <title>심리학(Psychology)의 의미와 역사</title>
      <link>https://dreamyard.tistory.com/entry/%EC%8B%AC%EB%A6%AC%ED%95%99Psychology%EC%9D%98-%EC%9D%98%EB%AF%B8%EC%99%80-%EC%97%AD%EC%82%AC</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;심리학(&lt;span style=&quot;background-color: #ffffff; color: #373a3c;&quot;&gt;Psychology)&lt;/span&gt;은 인간의 심리적 과정과 행동, 서로에 대한 상호작용을 연구하는 경험 과학입니다. 심리학은 지각, 인지, 주의, 정서, 지능, 성격, 대인관계 등과 같은 인간 심리에 관한 것들을 연구합니다. 심리학은 철학부터 행동경제학, 사회과학, 자연과학, 컴퓨터과학, 인공지능에 이르기까지 매우 폭넓은 주제를 다룹니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;3. 심리학의 역사.jpg&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nIkdZ/btrYBWrtdVT/QF7UELH5M4N4YqpyJISYU1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nIkdZ/btrYBWrtdVT/QF7UELH5M4N4YqpyJISYU1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nIkdZ/btrYBWrtdVT/QF7UELH5M4N4YqpyJISYU1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnIkdZ%2FbtrYBWrtdVT%2FQF7UELH5M4N4YqpyJISYU1%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; alt=&quot;심리학의 의미와 역사&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;300&quot; data-filename=&quot;3. 심리학의 역사.jpg&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;철학에서부터 시작된 심리학&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;심리학의 역사는 철학에서부터 시작되었습니다. 이후, 해부학과 의학이 발전하면서 사람의 감정과 생각은 어디에 존재하는지에 대한 논의가 이루어졌습니다. 토머스 홉스는 신체는 마음과 같은 곳에서 나오기 때문에 몸이 행하는 것은 곧 마음이라고 하였습니다. 르네 데카르트는 마음인 영혼과 신체는 근본이 다르며, 영혼은 물질적인 것으로 이루어질 수 없는 것이라고 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후, 제대로 독립된 학문으로서 자리 잡기 시작한 것은 19세기 생리학자들에 의해서였습니다. 이들은 경험적이고 실험적인 방법으로 심리학을 연구했습니다. 오스트리아 의사인 Galton은 정신적인 능력이 뇌의 크기가 클수록 증가한다는 것을 발견하였으며, 행복, 기억, 계산 등과 관련된 정신적인 능력이 뇌의 특정 부분에 있다는 이론을 발전시켰습니다. 그는 뇌와 마음이 연결되어 있다고 생각하였습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현대적인 심리학의 시조라 할 수 있는 기능주의와 구조주의의 발전은 Wilhelm Wundt와 William James가 각각 독일의 라이프치히 대학과 하버드 대학에 심리학 실험실을 설치한 것이 계기가 되었습니다. Wilhelm Wundt에 의한 초기 심리학은 구조주의라 불립니다. 그는 마음이 어떤 요소로 구성되어 있는지 구조적으로 바라보며 자신을 관찰하는 방법인 '내성법'으로 심리학을 연구하였습니다. 반면 William James의 심리학은 기능주의로 불리며, 마음을 구조로 보기보다는 마음이 어떻게 기능하는지를 중점으로 두었습니다. 이 기능주의 심리학에서 논해진 주제들은 인지심리학에서도 논의되는 경우가 많습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;행동주의는 20세기 초중반에 크게 유행하였는데, 어떻게 마음속 생각과 감정이 연이어서 다른 생각과 감정으로 이어지는지를 주로 연구하였습니다. 이때 자극-반응이라는 개념이 생겨 알려지면서 행동주의가 대세로 떠오르게 되었습니다. 왓슨, 파블로프, 스키너도 행동주의의 대표적인 심리학자라고 할 수 있으며, 이들은 특히 학습심리학에 대한 여러 업적을 남겼습니다.&amp;nbsp;행동주의의 주된 모토는 '동일한 자극에 대해선 동일한 행동이 유발된다.'는 것이었는데, 전혀 배운 적이 없던 행동을 해내는 사례들이 발견되기 시작하며, 행동주의에 반하며 인지 혁명이 일어나게 됩니다. 이렇게 20세기 중후반에 일어난 인지 혁명으로 심리학은 행동주의가 소홀히 했던 '마음'에 좀 더 초점을 두게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;20세기 후반에는 fMRI 등 첨단 연구 장비들이 등장함에 따라, 뇌와 마음을 수학적으로 분석하는 신경과학이 부상하게 되었습니다. fMRI로 뇌의 활성도를 측정함으로써 마음과 연관된 뇌의 기능을 연구할 수 있게 되었습니다. 20세기 후반에 이르러 심리학은 심리학이 미국 심리학회(American Psychological Association, APA)가 임상가들에 의해 주도되었습니다. 기초과학적인 성격을 잃는 것을 우려한 일부 학자들에 의해 심리과학협회(Association for Psychological Science, APS)가 창립되면서 기초 분야와 응용 분야로 양립된 체제로 존재하고 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;심리학의 기초 분야에는 실험심리학, 수리심리학, 생물심리학, 신경심리학, 지각심리학, 인지심리학, 성격심리학, 사회심리학, 발달심리학, 진화심리학, 긍정심리학이 있습니다. 이러한 기초 과학적 심리학들을 토대로 다양한 분야에서 심리학이 접목되어 많은 응용 분야가 탄생하게 되었습니다. 분야 이름에 '심리학'만 붙여도 된다는 말이 떠오를 정도로 정말 다양한 분야가 있고, 계속해서 생기고 있습니다. 응용 분야에는 임상심리학, 이상심리학, 조직심리학, 소비자 심리학, 광고심리학, 마케팅심리학, 행동경제학, 투자심리학, 교육심리학, 범죄심리학, 심리언어학 등이 있습니다.&amp;nbsp;&lt;/p&gt;</description>
      <category>Psychology</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/3</guid>
      <comments>https://dreamyard.tistory.com/entry/%EC%8B%AC%EB%A6%AC%ED%95%99Psychology%EC%9D%98-%EC%9D%98%EB%AF%B8%EC%99%80-%EC%97%AD%EC%82%AC#entry3comment</comments>
      <pubDate>Thu, 9 Feb 2023 12:04:38 +0900</pubDate>
    </item>
    <item>
      <title>멀티태스킹(Multi-Tasking)은 허상이다.</title>
      <link>https://dreamyard.tistory.com/entry/%EB%A9%80%ED%8B%B0%ED%83%9C%EC%8A%A4%ED%82%B9Multi-Tasking%EC%9D%80-%ED%97%88%EC%83%81%EC%9D%B4%EB%8B%A4</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;거의 모든 사람이 멀티태스킹(Multi-Tasking)이 효과적이고, 효율적이라고 생각합니다. 일을 잘 해내기 위해 멀티태스킹(Multi-Tasking)은 업무에 있어서 꼭 필요한 능력으로 여겨지기도 합니다. 실제로 사회생활을 하면서도 한 번에 여러 가지 일을 잘 해내는 사람들을 보기도 합니다. 1960년대 이후부터 쓰이기 시작한 '멀티태스킹(Multi-Tasking)'이라는 용어는 하나의 삶의 방식처럼 여겨져 오기도 했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2. 멀티태스킹.jpg&quot; data-origin-width=&quot;560&quot; data-origin-height=&quot;560&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ez2kRT/btrYvnbfB0X/JCoV35Q5MS2tritEf01DKK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ez2kRT/btrYvnbfB0X/JCoV35Q5MS2tritEf01DKK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ez2kRT/btrYvnbfB0X/JCoV35Q5MS2tritEf01DKK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fez2kRT%2FbtrYvnbfB0X%2FJCoV35Q5MS2tritEf01DKK%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; alt=&quot;멀티태스킹&quot; loading=&quot;lazy&quot; width=&quot;560&quot; height=&quot;560&quot; data-filename=&quot;2. 멀티태스킹.jpg&quot; data-origin-width=&quot;560&quot; data-origin-height=&quot;560&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;멀티태스킹(Multi-Tasking)의 실상&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;하지만, 많은 일을 동시다발적으로 하는 듯한 컴퓨터도 자세히 들여다보면 한 번에 단 하나의 코드를 처리하는 것부터 시작합니다. CPU라는 중앙처리장치를 중심으로 여러 가지 일을 각각 번갈아 가면서 빠르게 처리하기 때문에 마치 동시다발적으로 모든 일을 하는 것처럼 보이는 것입니다. 우리는 실제로도 모든 일을 해낼 수 있다는 희망을 가지고 몇 가지씩의 일을 행하곤 합니다. 하지만, 실제로 사람이 이렇게 여러 일을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;번갈아 가며&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;처리한다고 하면 컴퓨터와는 다르게 여러 문제가 발생할 가능성이 매우 높아집니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티태스킹(Multi-Tasking)의 실상은 작업 전환입니다. 우리가 텔레비전을 보면서 빨래를 갤 수 있고, 노래를 들으며 설거지를 할 수 있는 것은 그 작업들이 단순해서 빠른 작업 전환이 이루어질 수 있어 가능한 일입니다. 그러나 직장에서 보는 업무와 같이 집중력을 필요로 하는 작업들은 한 번 흐름이 끊기게 되면, 다시 시작하기는 쉽지 않습니다. 중단되었던 바로 그 부분부터 다시 더듬어 찾아가느라 그에 필요한 별도의 시간과 에너지를 쓰게 됩니다. 작업 전환에는 정도의 차이는 있겠지만, 언제나 어느 정도의 대가가 따르게 됩니다. 따라서 멀티 태스킹은 일의 효율성을 증가시켜 시간을 절약시켜 준다기보다는 시간과 에너지를 낭비하는 일에 가깝습니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘날 일하는 사무실에서조차도, 우리는 하나의 업무를 수행하는 와중에도 다른 여러 가지 일도 하기를 강요받습니다. 하나의 프로젝트에 집중하는 중에도 다른 업무를 요청하는 이메일도 계속 들어오고, 핸드폰이나 사무실 전화로 전화가 오기도 하고, 때론 다른 직원들이 지나가며 이것저것 물어보거나 업무적인 요청을 하기도 합니다. 그럴수록 집중이 되기보단, 업무는 엉망이 되고 계속 흐름을 끊기게 됩니다. 연구 결과에 따르면 직장인들은 평균 11분마다 한 번 타인의 방해를 받고, 일과 중 3분의 1을 집중력을 되찾는 데 사용한다고 합니다. 하루에 평균적으로 한 시간에 37번이나 모니터 화면을 바꾸며, 이메일을 확인하거나 문서 작업을 한다고 합니다. 이렇게 우리는 멀티태스킹(Multi-Tasking)이라는 작업 전환을 하느라 매일 평균적으로 28퍼센트의 근로 시간을 낭비하고 있다고 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호흡과 같은 신체적인 활동은 두뇌에서 집중력이 필요한 부분과는 별개인 곳에서 조종하기 때문에 우리가 업무를 하더라도 영향을 주지 않습니다. 우리의 집중력은 전전두엽에서 발생하는데, 이는 한정적이라 우리가 두 가지 일을 한다면 집중력도 나뉘거나 충돌할 수밖에 없습니다. 각각의 일에 집중력이 분산되는 만큼 실수가 일어날 확률도 높아집니다. 2009년 뉴욕 타임스 기자인 매트 릭텔은 운전 중 문자나 통화를 하는 것이 얼마나 위험한지에 대한 '운전 중 한눈팔기의 위험성'이라는 연작 기사로 퓰리처상을 받았다고 합니다. 그에 따르면, 운전 중에 하는 통화는 전체 집중력을 40%나 잡아먹게 되는데 이는 거의 음주운전을 하는 것과 같은 수준의 영향이라고 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 오늘 출근 버스의 버스 기사, 나를 치료하는 의사, 함께 일하는 동료에게도 업무에 집중하기를 바라고, 요구합니다. 실상 우리 자신에게는 멀티태스킹(Multi-Tasking)이라는 모순적인 잣대를 나 자신에게 들이밀며, 그들에게 요구하는 만큼조차도 나에게 필요한 집중력을 끌어내지 못하고 있는 건지도 모릅니다. 멀티태스킹(Multi-Tasking)을 하느라 낭비되고 있는 우리의 시간과 에너지를 다시 돌아보는 시간을 가져보는 것은 어떨까요?&lt;/p&gt;</description>
      <category>Psychology</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/2</guid>
      <comments>https://dreamyard.tistory.com/entry/%EB%A9%80%ED%8B%B0%ED%83%9C%EC%8A%A4%ED%82%B9Multi-Tasking%EC%9D%80-%ED%97%88%EC%83%81%EC%9D%B4%EB%8B%A4#entry2comment</comments>
      <pubDate>Wed, 8 Feb 2023 11:06:38 +0900</pubDate>
    </item>
    <item>
      <title>당신의 운 나쁜 하루를 위한 심리학, 리프레이밍(Reframing)</title>
      <link>https://dreamyard.tistory.com/entry/%EB%8B%B9%EC%8B%A0%EC%9D%98-%EC%9A%B4-%EB%82%98%EC%81%9C-%ED%95%98%EB%A3%A8%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%8B%AC%EB%A6%AC%ED%95%99-%EB%A6%AC%ED%94%84%EB%A0%88%EC%9D%B4%EB%B0%8DReframing</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;하루하루 살다 보면 유독 운이 좋지 않은 하루가 있습니다. 이런 날엔 버스를 코앞에서 놓쳤거나, 비 오는 날에 지나가는 차에 빗물을 잔뜩 맞거나 등등 드라마나 영화에서 보던 운 안 좋은 상황들이 연이어 일어나기도 합니다. 이럴 땐 리프레이밍(Reframing)을 통해 하루를 다시 돌아보는 것은 어떨까요?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1. 리프레이밍.jpg&quot; data-origin-width=&quot;533&quot; data-origin-height=&quot;533&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPh0Hj/btrYpmrGZtS/kTJ0fSXr4XdimEuEkLGBj1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPh0Hj/btrYpmrGZtS/kTJ0fSXr4XdimEuEkLGBj1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPh0Hj/btrYpmrGZtS/kTJ0fSXr4XdimEuEkLGBj1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPh0Hj%2FbtrYpmrGZtS%2FkTJ0fSXr4XdimEuEkLGBj1%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; alt=&quot;리프레이밍(Reframing)&quot; loading=&quot;lazy&quot; width=&quot;533&quot; height=&quot;533&quot; data-filename=&quot;1. 리프레이밍.jpg&quot; data-origin-width=&quot;533&quot; data-origin-height=&quot;533&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;생각 틀을 바꿔주는 리프레이밍(Reframing)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프레임(Frame)이란 사고방식이나 느끼는 방식의 틀을 의미합니다. 따라서, 리프레이밍(Reframing)이란 틀을 새롭게 한다는 뜻으로, 기존에 벌어진 사건을 생각의 틀을 바꾸어 다른 관점에서 바라보고, 새로운 의미를 부여하는 것을 말합니다. 별볼일 없는 그림도 액자를 바꾸면 달라져 보이는 것처럼, 리프레이밍(Reframing),즉 생각의 틀 바꾸기는 우리가 일상에서 마주치는 사건과 상황을 보다 유연하게 겪어낼 수 있도록 도와줄 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리프레이밍(Reframing)을 통해 현실에서 이미 벌어진 사건을 바꿀 수는 없지만, 적어도 그것을 보는 우리의 생각과 느낌을 보다 긍정적으로 바꾸어 줄 수 있습니다. 더 나아가 이런저런 사건에 끌려다니는 인생을 살 것이냐, 아니면 내 힘으로 인생을 살아낼 것이냐의 주체성의 차이를 만들어 낼 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, 주의해야 할 것이 있습니다. 리프레이밍(Reframing)은 무조건적인 긍정과 낙관으로 모든 일들을 멋지게 꾸며내라는 의미가 아닙니다. 벌어진 일에 대해 부정적일지라도 자신의 느낌과 감정을 충분히 느끼고, 이런 감정도 나 자신의 일부임을 먼저 인정하는 과정이 먼저 이루어져야 합니다. 그런 다음 그 감정을 리프레이밍(Reframing)하며 긍정적인 방향으로 바꾸어나가는 겁니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;작은 것부터 시작해보는 리프레이밍(Reframing) 습관&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 모든 일들을 그렇게 리프레이밍(Reframing)하는 것은 쉬운 일은 아닙니다. 리프레이밍(Reframing)에 익숙해지기 위해선 작은 것부터 시작해 보는 연습이 필요합니다. 나 자신에게 실망할 일을 겪는다면, 그래서 '나는 할 수 없어.'라는 말이 계속해서 떠오르고 우울감이 느껴진다면, 그럴 땐 '아직'이라는 단어를 붙여보세요. '나는 아직 할 수 없어.'라고 말입니다. 화가 나거나 슬픈 일을 겪어 분노와 슬픔을 여실히 느끼면서도 나에게 질문을 던져보세요. '지금 이 상황이 내게 무엇을 말해주려는 걸까? 숨어있는 기회는 뭘까? 무엇을 배울 수 있을까?' 이러한 리프레이밍(Reframing) 질문 하나가 우리가 상황을 바라보는 관점을 바꿔줄 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리프레이밍(Reframing) 과정은 비단 우리의 마음뿐 아니라, 일상에서 겪는 여러 문제를 해결하는 데도 도움을 줍니다. 우리가 어떤 문제를 맞닥뜨렸을 때, 그 문제로부터 유래된 감정들로부터 보다 빠르게 빠져나와 문제를 직면하고 해결하도록 돕습니다. 문제를 잘 해결하는 사람들의 가장 기본적인 특성도 '리프레이밍(Reframing)'에 능하다는 것입니다. 그들은 어려운 상황에 직면했을 때 운명으로 받아들이며 체념하거나 주변을 원망하는 것이 아니라, 더 좋은 방법이 있을 것이라고 믿습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로마의 스토아 철학자인 에픽테토스도 이런 말을 했습니다. '우리를 불안하게 만드는 것은 사물이나 사건이 아니다. 그것을 바라보는 우리의 생각이 불안의 원인이다.' 하루하루 수많은 일들을 겪고, 여러 감정과 스트레스를 느끼고 견디며 살아가는 나를 위해 따뜻한 차 한잔과 잠시 하루를 돌아보며, 오늘의 리프레이밍(Reframing) 한 가지 실천해 보는 것을 권해드립니다.&lt;/p&gt;</description>
      <category>Psychology</category>
      <category>Reframing</category>
      <category>리프레이밍</category>
      <category>심리학</category>
      <category>지친나를위한심리학</category>
      <author>DREYA</author>
      <guid isPermaLink="true">https://dreamyard.tistory.com/1</guid>
      <comments>https://dreamyard.tistory.com/entry/%EB%8B%B9%EC%8B%A0%EC%9D%98-%EC%9A%B4-%EB%82%98%EC%81%9C-%ED%95%98%EB%A3%A8%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%8B%AC%EB%A6%AC%ED%95%99-%EB%A6%AC%ED%94%84%EB%A0%88%EC%9D%B4%EB%B0%8DReframing#entry1comment</comments>
      <pubDate>Wed, 8 Feb 2023 00:53:17 +0900</pubDate>
    </item>
  </channel>
</rss>