<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>cracked_egg</title>
        <link>https://velog.io/</link>
        <description>프론트엔드 지식으로 가득찰 때까지</description>
        <lastBuildDate>Tue, 05 Mar 2024 11:14:36 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>cracked_egg</title>
            <url>https://velog.velcdn.com/images/gaebar_top/profile/92c5a1ea-67d9-4c28-8388-e7d840e7aad6/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. cracked_egg. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/gaebar_top" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[위클리 페이퍼 (16)]]></title>
            <link>https://velog.io/@gaebar_top/%EC%9C%84%ED%81%B4%EB%A6%AC-%ED%8E%98%EC%9D%B4%ED%8D%BC-16</link>
            <guid>https://velog.io/@gaebar_top/%EC%9C%84%ED%81%B4%EB%A6%AC-%ED%8E%98%EC%9D%B4%ED%8D%BC-16</guid>
            <pubDate>Tue, 05 Mar 2024 11:14:36 GMT</pubDate>
            <description><![CDATA[<h1 id="16주차-위클리-페이퍼">16주차 위클리 페이퍼</h1>
<h2 id="q1-authorization-code를-활용하는-구글-소셜-로그인을-실행하기까지-유저-프론트엔드-백엔드-openid-connect-프로바이더-사이에-어떤-과정을-거치는지-설명해-주세요">Q1) Authorization Code를 활용하는 구글 소셜 로그인을 실행하기까지 유저, 프론트엔드, 백엔드, OpenID Connect 프로바이더 사이에 어떤 과정을 거치는지 설명해 주세요.</h2>
<ol>
<li>유저는 서비스에 접근해 구글 간편 로그인 버튼을 클릭한다.</li>
<li>프론트엔드에서는 간편 로그인 요청을 하게 된다.</li>
<li>OpenID Connect 프로바이더는 로그인 페이지를 제공하고, 간편 로그인 이메일과 비밀번호를 요청한다.</li>
<li>유저는 요청받은 이메일과 비밀번호를 입력하여 본인임을 인증한다.</li>
<li>OpenID Connect 프로바이더는 프론트엔드에 Authroization Code를 발급한다.</li>
<li>프론트엔드는 받은 Authorization Code를 백엔드에 전송한다.</li>
<li>백엔드는 Authorization Code와 Client ID와 Cliend Secret를 통해 인증절차를 확인한다.</li>
<li>OpenID Connect 프로바이더에서 백에늗에 Access Token과 ID Token을 발급한다.</li>
<li>백엔드에서 프론트엔드로 Session ID 또는 Token을 전달한다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[React 유저 기능]]></title>
            <link>https://velog.io/@gaebar_top/React-%EC%9C%A0%EC%A0%80-%EA%B8%B0%EB%8A%A5</link>
            <guid>https://velog.io/@gaebar_top/React-%EC%9C%A0%EC%A0%80-%EA%B8%B0%EB%8A%A5</guid>
            <pubDate>Tue, 05 Mar 2024 10:05:53 GMT</pubDate>
            <description><![CDATA[<h1 id="react-유저-기능-구현">React 유저 기능 구현</h1>
<h2 id="1-request에서-쿠키-사용하기">1. request에서 쿠키 사용하기</h2>
<h3 id="1-origin이란">(1) Origin이란?</h3>
<p><code>Origin</code>이란 쉽게 말해 request를 보내는 사이트의 도메인이다. <code>https://localhost:3000</code>에서 <code>https://도메인</code>으로 request를 보내는 경우, 서로 다른 Origin이라는 의미에서 Cross Origin이라고 표현한다. 이런 경우 여러 가지 보안 문제가 발생할 수 있기 때문에 주의해야 한다. CORS(Cross-Origin Resource Sharing)는 웹 개발에서 자주 겪기도 하고 중요한 문제이다.</p>
<h3 id="2-credential이란">(2) Credential이란?</h3>
<p>웹 개발에서 <code>Credential</code>이라고 하면 유저를 증명할 수 있는 정보들을 말한다. 예를 들어 아이디와 비밀번호라던지 서버에서 발급받은 토큰 같은 것들을 말한다. request를 보내는 상황에서는 주로 쿠키를 의미한다.</p>
<h4 id="1-axios에서-cfredential-사용하기">1. Axios에서 Cfredential 사용하기</h4>
<p>Axios에서는 <code>withCredentials</code>라는 옵션을 불린형으로 지정할 수 있다. 이 값을 <code>true</code>로 설정해야만 Cross Origin에 쿠키를 보내거나 받을 수 있다. 참고로 이건 <code>fetch()</code> 함수에서 <code>credentials: &#39;incldue&#39;</code>를 설정하는 것과 같다.</p>
<pre><code class="language-js">axios.post(
  &#39;/auth/login&#39;,
  { email: &#39;sunny@sundaymorning.kr&#39;, password: &#39;t3st!&#39; },
  { withCredentials: true },
);</code></pre>
<h4 id="2-fetch-함수에서-credential-사용하기">2. fetch() 함수에서 Credential 사용하기</h4>
<p><code>fetch()</code> 함수에서 request를 보낼 때 쿠키를 사용하려면 적절한 <code>credentials</code> 옵션을 설정해 주어야 한다.</p>
<ul>
<li><code>omit</code>: 쿠키를 사용하지 않는다. request를 보낼 때도 쿠키를 사용하지 않고, response로 <code>Set-Cookie</code> 헤더를 받았을 때에도 쿠키를 저장하지 않는다.</li>
<li><code>&#39;same-origin</code>: 아무 옵션을 지정하지 않았을 때 기본 값이다. 같은 Origin인 경우에만 쿠키를 사용하겠다는 옵션이다. Origin은 쉽게 말해서 사이트의 도메인이라고 할 수 있다. 프론트엔드 사이트 주소와 request를 보낼 백엔드 서버의 주소가 다르다면 Cross Origin이라고 이해하면 된다.</li>
<li><code>&#39;include&#39;</code>: 이 옵션을 사용하면 Cross Origin인 경우에도 쿠키를 사용한다.</li>
</ul>
<pre><code>fetch(&#39;https://주소/api/link-service/auth/login&#39;, {
  method: &#39;POST&#39;,
  headers: { &#39;Content-Type&#39;: &#39;application/json&#39; },
  body: JSON.stringify({ email: &#39;sunny@sundaymorning.kr&#39;, password: &#39;t3st!&#39; }),
  credentials: &#39;include&#39;
});</code></pre><p>CORS에서 쿠키를 사용하려면 <code>credentials: &#39;include&#39;</code>를 설정해야 한다.</p>
<hr>

<h2 id="2-쿠키가-제대로-저장되지-않을-때">2. 쿠키가 제대로 저장되지 않을 때</h2>
<h3 id="1-response-헤더의-set-cookie-확인하기">(1) response 헤더의 Set-Cookie 확인하기</h3>
<p>개발자 도구의 Network 탭에서 response 헤더를 확인해 보면 된다. Headers라는 탭에서 Response Headers 안에 있는 <code>Set-Cookie</code> 값을 확인하면 된다.</p>
<p>다른 도메인을 가진 백엔드 서버에서 <code>SameSite=Stirct</code>라는 옵션으로 쿠키를 만든 경우를 가정하자. request를 보내는 쪽은 도메인이 <code>localhost</code>인데, 받는 쪽의 도메인이 달라서 쿠키가 저장되지 않은 경우이다. 이럴 때 개발자 도구에서 <code>SameSite=Strict</code> 옵션이지만 도메인이 다른(크로스 사이트) response이기 때문에 쿠키를 저장하지 않았다는 경고 표시를 해준다.</p>
<pre><code>This attempt to set a cookie via a Set-Cookie header was blocked because it had the &quot;SameSite=Strict&quot; attribute but came from a cross-site response which was not the response to a top-level navigation.</code></pre><p>참고로 response로 받은 <code>Set-Cookie</code> 헤더는 오른쪽 상단에 있는 <code>Cookies</code> 탭을 사용하면 표 형태로 좀 더 편하게 확인할 수 있다.</p>
<h3 id="2-samesite-옵션">(2) SameSite 옵션</h3>
<p><code>SameSite</code>는 request를 보내는 쪽의 도메인과 request를 받는 쪽의 도메인이 일치하는지 확인하고 쿠키의 사용을 허용하는 옵션이다. 이런 옵션은 백엔드 쪽에서 설정할 수 있다.</p>
<p><code>SameSite=None</code>이라는 옵션을 사용하면 request를 보내는 쪽과 받는 쪽의 도메인이 다르더라도 쿠키를 저장할 수 있다. <code>SameSite=Strict</code>라는 옵션은 반드시 request를 보내는 쪽과 받는 쪽이 같은 도메인이어야 쿠키를 저장하고 사용할 수 있게 한다.</p>
<hr>

<h2 id="3-항상-access-token-사용하기">3. 항상 Access Token 사용하기</h2>
<pre><code class="language-js">const instance = axios.create({
  baseURL: &#39;http://localhost:3000/api/&#39;,
  withCredentials: true,
});</code></pre>
<hr>

<h2 id="4-컨텍스트로-유저-데이터-관리하기">4. 컨텍스트로 유저 데이터 관리하기</h2>
<pre><code class="language-js">const AuthContext = createContext({
  user: null,
  isPending: false,
  login: () =&gt; {},
  logout: () =&gt; {},
  updateMe: () =&gt; {},
});

export function AuthProvider({ children }) {
  const [values, setValues] = useState({
    user: null,
    isPending: true,
  });

  async function getMe() {
    setValues((prevValues) =&gt; ({
      ...prevValues,
      isPending: true,
    }));
    let nextUser;
    try {
      const res = await axios.get(&#39;/users/me&#39;);
      nextUser = res.data;
    } finally {
      setValues((prevValues) =&gt; ({
        ...prevValues,
        user: nextUser,
        isPending: false,
      }));
    }
  }

  async function login({ email, password }) {
    await axios.post(&#39;/auth/login&#39;, { email, password });
    await getMe();
  }

  async function logout() {
    await axios.delete(&#39;/auth/logout&#39;);
    setValues((prevValues) =&gt; ({
      ...prevValues,
      user: null,
    }));
  }

  async function updateMe(formData) {
    const res = await axios.patch(&#39;/users/me&#39;, formData);
    const nextUser = res.data;
    setValues((prevValues) =&gt; ({
      ...prevValues,
      user: nextUser,
    }));
  }

  useEffect(() =&gt; {
    getMe();
  }, []);

  return (
    &lt;AuthContext.Provider
      value={{
        user: values.user,
        isPending: values.isPending,
        login,
        logout,
        updateMe,
      }}
    &gt;
      {children}
    &lt;/AuthContext.Provider&gt;
  );
}


export function useAuth(required) {
  const context = useContext(AuthContext);
  const navigate = useNavigate();

  if (!context) {
    throw new Error(&#39;반드시 AuthProvider 안에서 사용해야 합니다.&#39;);
  }

  useEffect(() =&gt; {
    if (required &amp;&amp; !context.user &amp;&amp; !context.isPending) {
      navigate(&#39;/login&#39;);
    }
  }, [context.user, context.isPending, navigate, required]);

  return context;
}</code></pre>
<hr>

<h2 id="5-로그인-상태에-따라-리다이렉트하기">5. 로그인 상태에 따라 리다이렉트하기</h2>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/63f2d4b1-a0c2-4b0c-a486-b61f8f97d907/image.png" alt="">
<img src="https://velog.velcdn.com/images/gaebar_top/post/caae1761-c8fc-4db2-8d78-13ca51f061ad/image.png" alt=""></p>
</blockquote>
<hr>

<h2 id="6-refresh-token-활용하기">6. Refresh Token 활용하기</h2>
<pre><code class="language-js">import axios from &#39;axios&#39;;

const instance = axios.create({
  baseURL: &#39;http://localhost:3000/api/&#39;,
  withCredentials: true,
});

instance.interceptors.response.use(res =&gt; res, async (error) =&gt; {
  const originalRequest = error.config;
  if (error.response?.status === 401 &amp;&amp; !originalRequest._retry) {
    await instance.post(&#39;/auth/token/refresh&#39;, undefined, { _retry: true });
    originalRequest._retry = true;
    return instance(originalRequest);
  }
  return Promise.reject(error);
});

export default instance;</code></pre>
<hr>

<h2 id="7-워크플로우">7. 워크플로우</h2>
<h3 id="1-회원가입">(1) 회원가입</h3>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/371c1ae5-1b91-4a92-bf10-7519d1f5af03/image.png" alt=""></p>
</blockquote>
<h3 id="2-로그인">(2) 로그인</h3>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/edd60093-f366-42f6-8090-8628392728a4/image.png" alt=""></p>
</blockquote>
<h3 id="3-유저-데이터-가져오기">(3) 유저 데이터 가져오기</h3>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/2aef50df-a892-4d02-8926-814adfe72a51/image.png" alt=""></p>
</blockquote>
<h3 id="4-토큰-갱신하기">(4) 토큰 갱신하기</h3>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/2dc10238-45a7-4ab9-bff9-731ee54a1f34/image.png" alt=""></p>
</blockquote>
<h3 id="5-로그아웃">(5) 로그아웃</h3>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/4e742f85-a374-45c2-b16f-908895639b86/image.png" alt=""></p>
</blockquote>
<h3 id="6-구글-로그인">(6) 구글 로그인</h3>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/1738a627-ed87-4546-9577-dc44f1b0219a/image.png" alt=""></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[위클리 페이퍼 (15)]]></title>
            <link>https://velog.io/@gaebar_top/%EC%9C%84%ED%81%B4%EB%A6%AC-%ED%8E%98%EC%9D%B4%ED%8D%BC-15</link>
            <guid>https://velog.io/@gaebar_top/%EC%9C%84%ED%81%B4%EB%A6%AC-%ED%8E%98%EC%9D%B4%ED%8D%BC-15</guid>
            <pubDate>Mon, 26 Feb 2024 04:23:43 GMT</pubDate>
            <description><![CDATA[<h1 id="15주차-위클리-페이퍼">15주차 위클리 페이퍼</h1>
<h2 id="q1-세션-기반-인증과-토큰-기반-인증을-비교해서-설명해-주세요">Q1) 세션 기반 인증과 토큰 기반 인증을 비교해서 설명해 주세요.</h2>
<h3 id="1-인증과-인가">(1) 인증과 인가</h3>
<p>인증(Authentication)은 쉽게 말하자면, 로그인과 같은 개념이다. 클라이언트가 자신이라고 주장하고 있는 사용자가 맞는지를 검증하는 과정이다.</p>
<p>인가(Autorization)는 인증 작업 이후에 행해지는 작업으로, 인증된 사용자에 대한 자원에 대한 접근 확인 절차를 의미한다.</p>
<h3 id="2-http의-비상태성stateless">(2) HTTP의 비상태성(Stateless)</h3>
<p>HTTP는 비상태성이라는 특성을 가진다. 서버는 클라이언트의 상태를 저장하지 않으며, 따라서 이전 요청과 다음 요청의 맥락이 이어지지 않는다. HTTP는 바로 직전에 발생한 통신을 기억하지 못하기 때문에 HTTP 단독으로 요청한 클라이언트가 이전에 이미 인증과정을 거쳤는지 알 방법이 없다.</p>
<p>그렇다면 사용자의 아이디와 비밀번호를 브라우저에 그대로 저장해놓고, 매 요청마다 함께 그 정보를 보내는 방법은 전송 데이터가 커져 비효율적인 뿐더러 클라이언트에 민감한 데이터가 그대로 저장되어 보안에도 취약하다. 서버 입장에서도 매 작업마다 데이터베이스를 조회하고, 인증과정을 거치는 것이 비효율적일 것이다.</p>
<h3 id="3-세션-기반-인증">(3) 세션 기반 인증</h3>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/8ea3673d-9465-4d09-9419-5a8a0415f6a6/image.png" alt=""></p>
</blockquote>
<p>세션 기반 인증는 사용자의 인증 정보가 서버의 세션 저장소에 저장되는 방식이다. 사용자가 로그인을 하면, 해당 인증 정보를 서버의 세션 저장소에 저장하고, 사용자에게는 저장된 세션 정보의 식별자인 ID를 발급한다. 발급된 ID는 브라우저에 쿠키 형태로 저장되지만, 실제 인증 정보는 서버에 저장되어 있다. </p>
<p>브라우저는 인증 절차를 마친 이후의 요청마다 HTTP Cookie 헤더에 ID를 함께 서버로 전송한다. 서버는 요청을 전달받고, ID에 해당하는 세션 정보가 세션 저장소에 존재한다면 해당 사용자를 인증된 사용자로 판단한다.</p>
<h3 id="4-토큰-기반-인증">(4) 토큰 기반 인증</h3>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/e7131118-b64f-4784-bdaa-5868193b9432/image.png" alt=""></p>
</blockquote>
<p>세션 기반 인증이 인증 정보를 서버에 저장하는 방식이라면 토큰 기반 인증은 인증 정보를 클라이언트가 직접 들고 있는 방식이다. 이때 인증 정보가 토큰의 형태로 브라우저의 로컬 스토리지에 저장된다. 토큰의 종류에 따라 다르겠지만, 대표적인 토큰인 JWT의 경우 디지털 서명이 존재해 토큰의 내용이 위변조되었는지 서버측에서 확인할 수 있다.</p>
<p>토큰 기반 인증에서는 사용자가 가지고 있는 토큰을 HTTP의 Authorization 헤더에 실어 보낸다. 이 헤더를 수신한 서버는 토큰이 위변조되었거나, 만료 기간이 지나지 않은지 등을 확인한 후 토큰에 담겨있는 사용자 인증 정보를 확인해 사용자를 인가한다.</p>
<h3 id="4-세션-기반-인증-vs-토큰-기반-인증">(4) 세션 기반 인증 vs 토큰 기반 인증</h3>
<h4 id="1-사이즈">1. 사이즈</h4>
<p>세션의 경우 Cookie 헤더에 ID만 보내면 되기 때문에 트래픽을 적게 사용한다. 하지만 JWT는 사용자 인증 정보와 토큰의 발급시각, 만료기간, 토큰의 ID 등 담겨있는 정보가 세션 ID에 비해 비대하므로 세션 방식보다 훨씬 더 많은 네트워크 트래픽을 사용한다.</p>
<h4 id="2-안정성과-보안문제">2. 안정성과 보안문제</h4>
<p>세션의 경우 모든 인증 정보를 서버에서 관리하기 때문에 보안 측면에서 조금 더 유리하다. 세션 ID가 해커에게 탈취된다고 해도, 서버측에서 해당 헤션을 무효 처리하면 된다. 토큰은 서버가 트래킹하지 않고, 클라이언트가 모든 인증 정보를 가지고 있다. 따라서 토큰이 한번 해커에게 탈취되면 해당 토큰이 만료되기 전까지는 속수무책으로 피해를 입을 수 밖에 없다.</p>
<p>JWT 특성상 토큰에 실린 Payload가 별도로 암호화 되어있지 않으므로, 누구나 내용을 확인할 수 있다. 따라서 Payload에 민감한 데이터는 실을 수 없다. 즉, Payload에 실을 수 있는 데이터가 제한된다. 하지만 세션과 같은 경우에는 모든 데이터가 서버에 저장되기 때문에 아무나 함부로 열람을 할 수 없으므로 저장할 수 있는 데이터에 제한이 없다.</p>
<h4 id="3-확장성">3. 확장성</h4>
<p>일반적으로 웹 애플리케이션의 서버 확장 방식은 수평 확장을 사용한다. 즉, 한대가 아닌 여러대의 서버가 요청을 처리하게 된다. 이때 별도의 작업을 해주지 않는다면, 세션 기반 인증 방식은 세션 불일치 문제를 겪게 된다. 이를 해결하기 위해 Sticky Session, Session Clustering, 세션 스토리지 외부 분리 등의 작업을 해주어야 한다.</p>
<p>하지만, 토큰 기반 인증 방식의 경우 서버가 직접 인증 방식을 저장하지 않고, 클라이언트가 저장하는 방식을 취하기 때문에 이런 세션 불일치 문제로부터 자유롭다. 이런 특징으로 토큰 기반 인증 방식은 HTTP의 비상태성을 그대로 활용하 수 있고, 높은 확장성을 가질 수 있다.</p>
<h4 id="4-서버의-부담">4. 서버의 부담</h4>
<p>세션 기반 인증 방식은 서비스가 세션 데이터를 직접 저장하고 관리한다. 따라서 세션 데이터의 양이 많아지면 많아질수록 서버의 부담이 증가한다. 반면에 토큰 기반 인증 방식은 서버가 인증 데이터를 가지고 있는 대신, 클라이언트가 인증 데이터를 직접 가지고 있다. 따라서 유저의 수가 얼마나 되던 서버의 부담이 증가하지 않는다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[유저 기능 원리 (2)]]></title>
            <link>https://velog.io/@gaebar_top/%EC%9C%A0%EC%A0%80-%EA%B8%B0%EB%8A%A5-%EC%9B%90%EB%A6%AC-2</link>
            <guid>https://velog.io/@gaebar_top/%EC%9C%A0%EC%A0%80-%EA%B8%B0%EB%8A%A5-%EC%9B%90%EB%A6%AC-2</guid>
            <pubDate>Sun, 25 Feb 2024 11:42:07 GMT</pubDate>
            <description><![CDATA[<h1 id="위임">위임</h1>
<h2 id="1-접근-권한-위임과-oauth">1. 접근 권한 위임과 OAuth</h2>
<p>접근 권한 위임이란, 한 서비스가 다른 서비스에 있는 보호된 리소스에 대한 접근 권한을 위임하거나 받는 기능을 의미한다.</p>
<p>OAuth(Open AUthorization)란, 개방형 접근 권한 위임 표준을 뜻한다. 민감한 인증 정보를 제3자에게 넘겨주지 않으면서 제3자가 보호된 리소스에 접근할 수 있는 절차라고 생각하면 된다.</p>
<hr>

<h2 id="2-oauth-주체와-설정">2. OAuth 주체와 설정</h2>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/f28c5f33-8b10-43da-9263-9de1e9801533/image.png" alt=""></p>
</blockquote>
<ul>
<li>인가 서버<ul>
<li>Client ID와 Secret</li>
<li>Scope : 인가 서버로부터 넘겨받으려는 권한의 범위</li>
</ul>
</li>
</ul>
<hr>

<h2 id="3-oauth-워크플로우">3. OAuth 워크플로우</h2>
<p>OAuth 워크플로우란, 접근 권한을 특정 방식으로 주고 받기 위해서 각 주체들이 해야되는 작업들과 그 순서를 의미한다.</p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/a197f9bd-b4d0-41c0-b482-18c357e1335b/image.png" alt=""></p>
</blockquote>
<ul>
<li>Authorization Code : 유저 본인이 권한 위임을 원한다는 걸 확인시켜주는 데이터</li>
</ul>
<hr>

<h2 id="4-openid-connect">4. OpenID Connect</h2>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/bcfe23ec-8f74-437a-add2-b79ece0c417d/image.png" alt="">
<img src="https://velog.velcdn.com/images/gaebar_top/post/c1cc4699-d220-4101-be9b-f260949389ff/image.png" alt="">
<img src="https://velog.velcdn.com/images/gaebar_top/post/7433fe68-bb01-42b3-9ce1-c2799ed4c7b7/image.png" alt=""></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[유저 기능 원리 (1)]]></title>
            <link>https://velog.io/@gaebar_top/%EC%9C%A0%EC%A0%80-%EA%B8%B0%EB%8A%A5</link>
            <guid>https://velog.io/@gaebar_top/%EC%9C%A0%EC%A0%80-%EA%B8%B0%EB%8A%A5</guid>
            <pubDate>Sun, 25 Feb 2024 07:24:12 GMT</pubDate>
            <description><![CDATA[<h1 id="인증과-인가">인증과 인가</h1>
<h2 id="1-쿠키-인증">1. 쿠키 인증</h2>
<p>쿠키란, 서버 response나, 클라이언트 코드에 따라 브라우저에 저장되는 작은 단위의 문자열 파일들을 의미한다.</p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/03ca1f66-83b0-4e2c-bb71-e22ed07c12c9/image.png" alt=""></p>
</blockquote>
<p>쿠키 저장과 전송 기능은 브라우저가 자동으로 실행해 주기 때문에 조금 더 편리하게 인증 구현이 가능하다.</p>
<hr>

<h2 id="2-쿠키-보안">2. 쿠키 보안</h2>
<p>쿠키는 유저 인증뿐만 아니라, 브라우저 이용자에 대한 개인화된 기능과 데이터 제공 수단으로 사용할 수 있다. 로그인을 하지 않아도 검색 기록이 저장되거나, 쇼핑 카트를 사용할 수 있다거나, 한 번 설정한 라이트와 다크 테마도 유지되는 경우들이 쿠키를 통해 이루어지는 것들이다.</p>
<p>하지만 브라우저 사용자가 아닌 다른 사람이 쿠키를 가로채거나 여러 방법들로 악용한다면, 보안 문제가 생길 수 있다. 특히 인증 관련 쿠키가 악용된다면 큰 문제로 이어질 수 있다.</p>
<p>(response 쿠키 예시)</p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/ea1f03b3-3157-454d-8665-ba52da46e029/image.png" alt=""></p>
</blockquote>
<h3 id="1-secure">(1) Secure</h3>
<p>서버에서 response를 보낼 때 이 설정을 추가해주면 HTTP보다 보안에 강한 HTTPS를 사용할 때만 클라이언트에서 서버로 쿠키가 보내진다. HTTPS를 사용하면 항상 request와 response가 암호화되기 때문에 누군가 중간에 request를 가로챘을 때 정보 유출을 줄일 수 있다.</p>
<p>Secure 설정은 response Set-Cookie 헤더에 이름-값 쌍 뒤 <code>;</code>과 <code>Secure</code> 키워드를 사용해서 적용할 수 있다.</p>
<pre><code>Set-Cookie: cookie_name=cookie_value; Secure;</code></pre><h3 id="2-httponly">(2) HttpOnly</h3>
<p>이 설정을 추가하면 클라이언트가 자바스크립트 코드로 해당 쿠키에 접근할 수 없게 된다. 자바스크립트 코드로는 가지고 올 수 없고, 그냥 쿠키를 설정한 웹 사이트에 request로 보낼 수만 있는 것이다. 코드로 쿠키에 접근할 수 없으면, 악의적 클라이언트가 개인 정보에 직접 접근하는 걸 막을 수 있다. 하지만 때에 따라서 코드로 저장한 쿠키에 접근하고 싶을 수 있으니까, 필요에 따라 설정하면 된다.</p>
<p>HttpOnly 설정은 response Set-Cookie 헤더에 이름-값 쌍 뒤 <code>;</code>과 <code>HttpOnly</code> 키워드를 사용해서 적용할 수 있다.</p>
<pre><code>Set-Cookie: cookie_name=cookie_value; Secure; HttpOnly;</code></pre><h3 id="3-samesite">(3) SameSite</h3>
<p>이 설정은 Cross Site request forgery의 약자, CSRF(XSRF)라는 공격을 예방할 수 있는 설정이다. CSRF는 일반 사이트 A와 악의적 사이트 B가 있을 때, B 웹 페이지에서 브라우저에 저장된 쿠키를 가지고 사이트 A 서버로 request를 보내는 공격이다</p>
<p>SameSite를 Strict로 하면 다른 도메인에서 request를 보낼 때 쿠키가 가는 걸 아예 방지할 수 있다. 이해하기 쉽게 말하자면 request를 보내는 클라이언트와 이걸 받는 서버의 도메인이 서로 같을 때만 쿠키가 간다.</p>
<p>하지만 URL을 직접 쳐서 사이트를 방문하기도 하지만, 이메일이나, 메세지, 심지어 다른 페이지에 있는 링크를 통해서도 페이지를 방문한다. 이때도 쿠키가 가지 않는 문제가 생긴다. 그래서 URL에 직접 링크를 치면 저장되어있던 쿠키가 가지만, 친구가 메세지로 보낸 링크를 통해서 페이지를 방문할 때는 쿠키가 가지 않아서 다시 로그인을 해야 되는 것이다. 물론 보안을 위해서 이렇게 되길 원하면 상관없지만, 인증 정보처럼 민감한 정보가 아닐 때는 관한 조치일 수 있다.</p>
<p>이때 사용하는 설정이 Lax이다. Lax는 영어로 &#39;느슨한&#39;이라는 뜻이다. 이 설정을 하면 링크를 통해 사이트를 직접 방문할 때는 쿠키가 보내진다.</p>
<p>SameSite 설정을 None으로 하면 아무런 제한 없이 브라우저에서 보내는 모든 request에 쿠키가 붙어서 간다. SameSite 설정을 None으로 할 때에는 보안 문제 때문에 항상 Secure 설정을 추가해야 한다. 추가하지 않으면 특정 브라우저들은 보안이 취약하다고 판단하고 쿠키를 저장하지 않는다.</p>
<p>SameSite 설정은 response Set-Cookie 헤더에 이름-값 쌍 뒤 <code>;</code>과 <code>SameSite</code> 키워드, 그리고 원하는 쿠키 사용 범위 키워드, None, Lax, Strict를 사용해서 적용할 수 있다.</p>
<pre><code>Set-Cookie: cookie_name=cookie_value; Secure; HttpOnly; SameSite=Lax;</code></pre><hr>

<h2 id="3-authorization-헤더-인증">3. Authorization 헤더 인증</h2>
<ul>
<li><p>장점
request에 인증서를 붙일지 안 붙일지 선택 가능하다. 또한 서로 다른 루트 도메인 사이에서 인증이 가능하다.</p>
</li>
<li><p>유의해야될 점
비밀번호와 같이 민감한 정보는 쿠키나 로컬 스토리지에 절대 저장하지 않는다.</p>
</li>
</ul>
<hr>

<h2 id="4-세션-기반-인증">4. 세션 기반 인증</h2>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/636e816e-e151-495e-acfb-98ded35a1ec8/image.png" alt=""></p>
</blockquote>
<p>세션이란, 서버가 저장하는 사이트 방문자들에 대한 기록을 의미한다.</p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/e04615c1-f04a-4af1-a257-0ba4446ea68f/image.png" alt=""></p>
</blockquote>
<hr>

<h2 id="5-토큰-기반-인증">5. 토큰 기반 인증</h2>
<p>인증 토큰이란, 유저에 대한 정보를 암호화한 문자열을 의미한다. 접근 토큰이라고 부르기도 한다.</p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/6a3b6ac9-6052-4bd6-ae38-b866402144d3/image.png" alt=""></p>
</blockquote>
<p>최근에는 JWT(JSON Web Token) 형식을 많이 사용한다.</p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/a5ccf906-a6d1-4a05-8e7e-f7e50b0df345/image.png" alt=""></p>
</blockquote>
<hr>

<h2 id="6-인코딩과-base64url">6. 인코딩과 Base64URL</h2>
<h3 id="1-인코딩">(1) 인코딩</h3>
<p>HTTP 헤더 이름과 값들은 효율성과 안정성을 위해 미리 정해진 256개의 문자들만 사용해야 한다. 이 문자들은 ASII라는 표준에 포함된 문자들이다. 이 표준을 따르면 실제로는 0과 1로 저장되는 데이터가 여러 컴퓨터에서 다르게 해석되는 걸 방지할 수 있다.</p>
<p>근데 세상에는 ASCII에 포함되지 않는 문자들도 많이 있다. 특히 인증 관련 데이터처럼 유저가 직접 정한 값들은 정해진 256개의 문자들만 사용한다고 가정할 수 없다. 이런 걸 헤더 값으로 사용하고 싶을 때는 직접 ASCII에 포함된 문자로 바꿔서 사용해야 한다.</p>
<p>데이터를 여러 곳에서 쉽고 안정적이게 사용하기 위해 통일된 형식으로 바꾸는 걸 &#39;인코딩&#39;이라고 부른다. 읽는 사람이 무슨 말인지 알 수 없게 만드는 &#39;암호화&#39;와는 조금 다른 개념이다. 여러 인코딩 방식들이 있지만 웹에서는 base64url이 많이 사용된다.</p>
<h3 id="2-base64-인코딩">(2) Base64 인코딩</h3>
<p>Base64는 데이터를 0과 1로 표현하고 이걸 6자리씩 끊어서 ASCII에 포함된 64문자 중 하나로 바꿔주는 인코딩 방식이다. 예를 들어 <code>000000</code>는 <code>A</code>, <code>101000</code>는 <code>o</code> 이런 식으로 <code>000000</code>부터 <code>111111</code>까지 각 숫자에 해당하는 문자로 바꾸는 것이다. 참고로 ASCII에는 256개의 문자가 있지만, 이 중 64개로만 사용한다. 64개의 문자열은 모든 영문 대소문자, 모든 숫자, 그리고 <code>+</code>와 <code>/</code>로 이루어져 있다. 자릿수가 부족할 때는 <code>=</code> 문자로 채워넣는다.</p>
<pre><code>À È Ì Ò Ù Ỳ Ǹ Ẁ &lt;=&gt; w4Agw4ggw4wgw5Igw5kg4buyIMe4IOG6gA==
한글도 인코딩할 수 있어요 &lt;=&gt; 7ZWc6riA64-EIOyduOy9lOuUqe2VoCDsiJgg7J6I7Ja07JqU</code></pre><p>반대로 오른쪽에 데이터가 있고 이게 base64 방식으로 인코딩됐다는 걸 알면 누구나 손쉽고 빠르게 원래의 문자열로 바꿀 수 있다. 인코딩된 데이터를 원래 형태로 바꾸는 걸 디코딩이라고 한다.</p>
<h3 id="3-base64url-인코딩">(3) Base64URL 인코딩</h3>
<p>Base64URL은 Base64와 거의 똑같은 인코딩 방식이다. <code>+</code>와 <code>/</code> 문자들은 URL에 사용될 때 특정 의미를 갖기 때문에 이 두 문자 대신 의미가 없는 <code>-</code>과 <code>_</code>를 사용한다. 웹에서 두 문자에 의미가 더 부여돼 해석될 가능성을 줄여서 조금 더 안정하게 사용할 수 있다.</p>
<hr>

<h2 id="7-기본-인증basic-authentication">7. 기본 인증(Basic Authentication)</h2>
<h3 id="1-기본-인증">(1) 기본 인증</h3>
<p>세션과 토큰 기반 인증, 이 두 인증 방식은 문자열 형식의 &#39;인증서&#39;같은 개념을 사용하는 것인데, 사실 인증을 할 때는 &#39;인증서&#39;의 개념을 아예 사용하지 않아도 되고, 그냥 온전히 이메일과 비밀번호만 사용할 수도 있다. 바로 기본 인증(Basic Authentication)이라는 방식을 사용하면 된다.</p>
<p>기본 인증의 request의 Authorization 헤더를 사용한다. 토큰 인증을 할 때 Authorization 헤더 뒤에 Bearer 또는 Token 그리고 뒤에 토큰을 붙였던 것과 비슷하게 Authorization 헤더 뒤에 Basic, 그리고 이메일과 비밀번호를 <code>:</code>로 이어서 붙여주면 된다. (<code>Authorization: Basic email:password</code>)</p>
<p>참고로 토큰 기반 인증과 기본 인증에서 Bearer/Token 또는 Basic을 붙여주는 건 서버에서 뒤에 붙이는 인증 정보의 종류를 서버에 알려주기 위한 방법이다. Authorization 헤더 인증을 사용한다면 꼭 추가해야 한다.</p>
<p><code>username:password</code> 이 부분은 서버로 보내기 전에 base64url 인코딩한다. 이 후 인코딩 결과로 나온 문자열을 이용해 <code>Authorization: Basic &quot;문자열&quot;</code> 형태로 작성한다. 이렇게 한 후 request를 보내면, 서버가 Baisc을 통해서 뒤에 있는 정보가 기본 인증이란 걸 파악하고, 이걸 디코딩해서 유저를 인증한다.</p>
<h3 id="2-기본-인증의-단점">(2) 기본 인증의 단점</h3>
<p>기본 인증은 유저를 인증하는 자체에 있어서는 보족함이 없지만, 보안 문제로 인해 요즘에는 거의 사용하지 않는다.</p>
<p>이메일과 비밀번호를 누군가에게 노출됐을 때 세션 id나 토큰보다 훨씬 더 악용할 수 있는 여지가 많다. 모든 request에 이런 민감한 데이터를 보내면 악의를 갖는 공격자가 중간에서 가로챌 위험이 커진다. 또, 로그인 상태를 유지하기 위해서는 클라이언트가 이메일과 비밀번호를 어딘가에 저장해놓고, 가지고 와서 request에 부붙여야 되는데, 이렇게 민감한 정보를 브라우저에 저장해놓는 것도 마찬가지로 노출될 위험이 있기 때문에 안전하지 않다.</p>
<hr>

<h2 id="8-refresh-토큰">8. Refresh 토큰</h2>
<p>Access 토큰은 갖고 있는 유저에게 특정 권한을 주기 위한 목적으로 사용한다. 사실 아무런 안전장치 없이 단독으로 사용하면 안정성 문제가 생길 수도 있다. 특히 토큰의 만료 기간을 길게 잡으면 토큰을 누군가 가로챘을 때 더 오랫동안 특정 권한을 갖는 유저 행세를 할 수 있다. 그렇다고 해서 만료 기간을 짧게 잡으면 이메일과 비밀번호로 인증을 너무 자주 해야해서 귀찮고 위험해질 수 있다. 이 문제를 어느 정도 해소하기 위해서 때때로 refresh 토큰이란 걸 같이 사용한다.</p>
<p>refresh 토큰은 access 토큰이 만료됐을 때, 이메일 비밀번호를 사용하지 않고 access 토큰을 새롭게 발급받는데 사용되는 토큰이다. 먼저 유저가 로그인을 하기 위해 request로 서버에 이메일과 비밀번호를 보내고, 서버가 이걸 확인하면, 서버는 클라이언트에서 access와 refresh, 두 가지의 토큰을 보내줄 수 있다.</p>
<p>Access 토큰이 소유자가 특정 권한을 가질 수 있게 하는 토큰이라면, refresh 토큰은 이메일과 비밀번호를 사용하지 않고 새로운 access 토큰을 발급받을 수 있게 하는 토큰이다. Access 토큰을 사용하다가 만료가 돼서 더 이상 request를 인증할 수 없게 되는 경우일 때, 클라이언트는 access 토큰을 새롭게 발급받는 URL에 새로운 GET request를 보낸다. 이 때 body에 refresh 토큰을 함께 보내게 되는 것이다. 그럼 서버는 refresh 토큰이 유요한 걸 확인한 후, 새로운 access 토큰을 발급한 후, response로 클라이언트에게 돌려준다.</p>
<p>일반적으로 refresh 토큰이 access 토큰보다 만료 기간이 더 길다. access 토큰의 만료 기간이 짧기 때문에 서버로 토큰을 보낼 때 누군가 이 정보를 가로챈다고 해도 최대 10분 정도까지만 다른 유저 행세를 할 수 있다.</p>
<p>refresh 토큰은 access 토큰이 만료됐을 때만 보내면 되기 때문에 훨씬 더 적게 보내도 되고 그만큼 누가 빼돌릴 확률이 줄어든다. 클라이언트가 이 두 토큰을 저장하고 있으면, refresh 토큰이 만료할 때까지 특정 권한을 갖는다는 걸 증명할 수 있는 것이다. 그렇기 때문에 이메일 비밀번호를 받지 않아도 계속 로그인 상태를 유지할 수 있어 편리하고, 이메일과 비밀번호라는 개인 정보를 request로 최대한 적은 횟수로 보내도 되기 때문에 더 위험부담을 줄일 수 있다.</p>
<hr>

<h2 id="9-jwtjson-web-token">9. JWT(JSON Web Token)</h2>
<p>JWT는 JSON 형식의 데이터를 문자열로 인코딩한 토큰을 의미한다.</p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/6e02adf1-6989-4abc-8dec-69acada3fc73/image.png" alt=""></p>
</blockquote>
<ul>
<li><p>Header
토큰 자체에 대한 데이터를 저장한다.</p>
</li>
<li><p>PayLoad
토큰이 실질적으로 저장하려는 정보가 담겨있다. 저장하고 싶은 데이터의 종류에는 제한이 없다. 다만, 최대한 짧게 작성하는 것이 좋다. 여기에는 공식적으로 사용하는 이름이 있기 때문에 최대한 활용하는 것을 추천한다. (exp, iat, jti 등)</p>
</li>
<li><p>Signature
토큰을 믿을 수 있는지 확인하기 위한 데이터를 저장한다. (Header + PayLoad + 시크릿 키)</p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/bc32559d-fe2f-455b-b9da-df4a2add3820/image.png" alt=""></p>
</blockquote>
</li>
</ul>
<p>단, Header와 PayLoad 부분은 단순히 인코딩된 것이기 때문에 아무나 볼 수 있다는 점도 주의해서 사용해야 한다.</p>
<hr>

<h2 id="10-세션-vs-토큰-기반-인증">10. 세션 vs 토큰 기반 인증</h2>
<h3 id="1-효율성">(1) 효율성</h3>
<p>토큰 기반 인증의 첫 번째 장점은 효율성이다. 세션 기반 인증을 사용하면 서버는 항상 로그인 세션 정보를 저장하며, 매 request의 유저가 누구인지를 비교해야 한다. 그리고 이걸 하기 위해서는 용량과 시간이 소비된다. 로그인한 유저가 엄청 많거나 특정 시간에 몰리게 되면 서버의 request 처리 속도가 느려질 수 있다. 반면 토큰 기반 인증은 어딘가 저장한 데이터와 비교하는 게 아니라 토큰 자체 내용을 해석하기만 하면 되기 때문에 더 효율적으로 작동할 수 있다.</p>
<h3 id="2-유연성">(2) 유연성</h3>
<p>토큰 기반 인증은 세션 기반 인증보다 조금 더 유연하게 사용될 수 있다. 토큰을 발행하는 방법이 똑같고, 시크릿 키만 있으면, 발행을 한 곳과 확인을 하는 곳이 달라도 된다.</p>
<p>예를 들어 같은 유저 데이터베이스를 사용하는 여러 서비스들이 있고, 이 사이트들이 같은 방식과 키를 사용해서 토큰을 발행한다면, 한 사이트에서 제공한 토큰을 가지고 있으면, 다른 서비스가 그걸 해석해서 유저를 파악할 수 있다. 요즘은 크고 복잡한 웹 애플리케이션들을 더 작은 내용을 담당하는 작은 부분들로 나눠서 개발하는 경우 많기 때문에, 토클을 사용하는 게 더 유연하다.</p>
<h3 id="3-restful-api">(3) RESTful API</h3>
<p>세션 정보와 같이 서버가 &quot;상태&quot; 정보를, 예를 들어 유저가 로그인을 했는지 안 했는지 저장하고 있을 때, stateful하다고 표현한다. REST에 부합하기 위해서는 서버가 상태 정보를 저장하지 않는, stateless한 특성이 있어야 한다. 서버는 클라이언트에서 보내는 정보만으로 충분히 상태를 파악할 수 있어야 한다. 이 기준에서 살펴보면 RESTful한 API 서버를 만들고 있다면 세션 기반 인증보다 토큰 기반 인증이 더 어울린다.</p>
<h3 id="4-무효화">(4) 무효화</h3>
<p>세션 기반 인증의 장점 중 하나는 서버에서 세션 데이터를 따로 관리를 하기 때문에 특정 세션을 손쉽게 무효화할 수 있다는 점이다. 그냥 세션을 관리해서 상태를 만료로 바꾸거나 마료일을 지금 당장으로 해버리면 된다. 하지만 토큰 기반 인증을 사용하면 따로 서버가 상태 정보를 저장하지 않기 때문에 특정 토큰을 무효화하는게 더 복잡해진다. 그리고 이건 꽤 큰 문제들로 이어질 수 있다. 예를 들어 금융 서비스에서 누군가 유저 토큰을 가로챘는데 이걸 바로 무효화할 수 없다면 돈을 뺏기는 문제가 생길 수도 있다. 물론 이중 비밀번호나, OTP라든지 이걸 방지하기 위한 방법들이 있지만, 세션 기반 인증을 사용하면 한 가지 안전장치를 더 손쉽게 사용할 수 있다.</p>
<hr>

<h2 id="11-인가--authorization">11. 인가 : Authorization</h2>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/c8e233e0-ffa2-4630-ad08-ba12a78d4150/image.png" alt=""></p>
</blockquote>
<hr>

<h2 id="12-authorization-헤더를-인증에-사용하는-이유">12. Authorization 헤더를 인증에 사용하는 이유</h2>
<p>request를 보낸 유저가 &quot;누군인지&quot; 파악하는 인증과 request가 &quot;특정 작업을 요청할 권한이 있는지&quot; 파악하는 인가는 서로 다른 두 기능이다. 하지만 request의 Authorization(인가) 헤더를 이용하는 유저 인증 방법을 이런 식으로 사용했다.</p>
<pre><code>DELETE http://www.example.com/api/some_resource/1/
Authorization: Bearer &lt;token&gt;</code></pre><p><a href="https://rfc-editor.org/rfc/rfc7235#section-4.2">공식 문서</a>에 따르면 Authorization은 인가가 아닌 인증 데이터를 보내기 위한 헤더라고 정의되어 있다. 공식 문서는 약 20년 전 작성된 것으로, 유저 기능의 두 용어를 구별해서 사용하는 요즘 기준으로 생각하면 이름을 잘 못 지었다고 할 수 있다. 하지만 왜 헷갈리는 이름 실수를 바로 잡지 않는것인가?</p>
<p>이미 Authorization 헤더를 너무 널리 사용하기 때문에 이름을 바꾸면 많은 것들을 망가트릴 수 있거나, 두 개의 기준이 섞여 사용돼서 혼란을 일으킬 수 있기 때문이라고 생각한다. 이유가 어찌 됐던, Authorization 헤더는 인가가 아닌 인증을 위한 헤더라는 점을 이해하고 사용하면 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[위클리 페이퍼 (14)]]></title>
            <link>https://velog.io/@gaebar_top/%EC%9C%84%ED%81%B4%EB%A6%AC-%ED%8E%98%EC%9D%B4%ED%8D%BC-14</link>
            <guid>https://velog.io/@gaebar_top/%EC%9C%84%ED%81%B4%EB%A6%AC-%ED%8E%98%EC%9D%B4%ED%8D%BC-14</guid>
            <pubDate>Wed, 21 Feb 2024 07:49:12 GMT</pubDate>
            <description><![CDATA[<h1 id="14주차-위클리-페이퍼">14주차 위클리 페이퍼</h1>
<h2 id="q1-cors-에러에-대해-설명하고-어떻게-해결하면-될지-설명해-주세요">Q1) CORS 에러에 대해 설명하고, 어떻게 해결하면 될지 설명해 주세요.</h2>
<p>CORS는 Cross-Origin Resource Sharing의 약자이다. 우선 CORS를 이해하기 위해서 몇가지 개념을 먼저 알고 있어야 한다.</p>
<h3 id="1-origin출처이란">(1) Origin(출처)이란?</h3>
<p>어떤 사이트를 접속할 때 인터넷 주소창에 URL이라는 문자열을 입력해 접근하게 된다. </p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/54b5557d-a7cf-4415-9c48-bc2dac22294b/image.png" alt=""></p>
</blockquote>
<p>즉, 출처(Origin)라는 것은 Protocol과 Host, Port까지 모두 합친 URL을 의미한다고 생각하면 된다. 자바스크립트로 간단하게 현재 사이트의 Origin을 알아낼 수도 있다.</p>
<pre><code>console.log(location.origin)</code></pre><h3 id="2-동일-출처-정책same-origin-policy">(2) 동일 출처 정책(Same-Origin Policy)</h3>
<p>SOP(Same Origin Policy) 정책은 말 그대로 동일한 출처에 대한 정책을 의미한다. 이 SOP 정책은 &#39;동일한 출처에서만 리소스를 공유할 수 있다&#39;라는 규칙을 가지고 있다. 즉, 동일 출처(Same-Origin) 서버에 있는 리소스는 자유롭게 가져와 사용할 수 있지만, 다른 출처(Cross-Origin) 서버에 있는 이미지나 영상 같은 리소스는 가져와 사용하지 못한다.</p>
<p>보통 다음과 같은 이유로 인해 동일 출처 정책이 필요하다.</p>
<ol>
<li>사용자가 악성 사이트에 접속한다.</li>
<li>이때 해커가 몰래 심어놓은 악의적인 자바스크립트가 실행되어, 사용자가 모르는 사이에 어느 포털 사이트에 요청을 보낸다.</li>
<li>그럼 포털 사이트에서 해당 브라우저의 쿠키를 이용하여 로그인을 하는 등 상호작용에 따른 개인 정보를 응답값으로 받은 뒤, 사이트에서 해커 서버로 개인 정보를 보낸다.</li>
<li>이외에도 사용자가 접속중인 내부망의 IP와 Port를 가져오거나, 사용자 브라우저를 Proxy처럼 악용할 수 있다.</li>
</ol>
<p>이러한 경우를 방지하기 위해, SOP 정책으로 동일하지 않는 다른 출처의 스크립트가 실행되지 않도록 브라우저에서 사전에 방지하는 것이다.</p>
<h3 id="3-corscross-origin-resource-sharing-동작-원리">(3) CORS(Cross-Origin Resource Sharing) 동작 원리</h3>
<p>클라이어트에서 HTTP 요청의 헤더에 Origin을 담아 전달한다. 기본적으로 웹은 HTTP 프로토콜을 이용하여 서버에 요청을 보내게 되는데, 이 때 브라우저는 요청 헤더에 Origin이라는 필드에 출처를 함께 담아 보내게 된다.</p>
<p>그 다음으로 서버는 응답헤더에 Access-Control-Allwo-Origin을 담아 클라이언트로 전달한다. 이 후 서버가 이 요청에 대한 응답을 할 때 응답 헤더에 Access-Control-Allow-Origin이라는 필드를 추가하고 값으로 &#39;해당 리소스를 접근하는 것이 허용된 출처 url&#39;을 내려보낸다.</p>
<p>이후 응답을 받은 브라우저는 보냈던 요청의 Origin과 서버가 보내준 응답의 Access-Control-Allow-Origin을 비교해본 후 차단할지 말지를 결정한다. 만약 유효하지 않다면 그 응답을 사용하지 않고 버린다.(CORS 에러) 유효하다면 다른 출처의 리소스를 문제없이 가져오게 된다.</p>
<h3 id="4-해결-방법">(4) 해결 방법</h3>
<ul>
<li><p>Chrome에서는 CORS 문제를 해결하기 위한 확장 프로그램을 제공해준다. </p>
<blockquote>
<p><a href="https://chromewebstore.google.com/detail/allow-cors-access-control/lhobafahddgcelffkeicbaginigeejlf">Allow CORS: Access-Control-Allow-Origin</a></p>
</blockquote>
</li>
<li><p>프록시 사이트를 이용한다. 프론트에서 직접 서버에 리소스를 요청했더니 서버에서 따로 설정을 안해줘서 CORS 에러가 뜬다면, 모든 출처를 허용한 서버 대리점을 통해 요청을 하면 되는 것이다.</p>
<blockquote>
<p><a href="https://cors-anywhere.herokuapp.com/corsdemo">heroku 프록시 서버</a>
<a href="https://github.com/raravel/cors-proxy">cors-proxy-app-raravel</a>
<a href="https://cors.sh/">cors.sh</a></p>
</blockquote>
</li>
<li><p>직접 서버에서 HTTP 헤더 설정을 통해 출처를 허용하게 설정하는 것이 가장 정석과 같은 방식이다. 서버의 종류도 노드 서버, 스프링 서버, 아파티 서버 등 여러가지가 있다. 각 서버의 문법에 맞게 HTTP 헤더를 추가해주면 된다.</p>
</li>
</ul>
<hr>

<h2 id="q2-seo가-무엇인지-설명하고-개선을-위해-어떤-작업을-할-수-있을지-설명해-주세요">Q2) SEO가 무엇인지 설명하고, 개선을 위해 어떤 작업을 할 수 있을지 설명해 주세요.</h2>
<h3 id="1-검색엔전-최적화seo">(1) 검색엔전 최적화(SEO)</h3>
<p>검색엔진 최적화(SEO: Search Engin Optimization)는 한 마디로 내가 만든 웹 사이트를 구글이나 네이버와 같은 검색결과 상단에 노출시킬 수 있도록 최적화해주는 방법이다. SEO를 잘 설계하면 검색엔진 검색결과 상단에 노출되어 무료 트래픽을 얻을 수 있게 된다. 기본적인 작업 방식은 특정 검색어를 웹 페이지에 적절하게 배치하고 다른 웹 페이지에서 링크가 많이 연결되도록 하는 것이다.</p>
<h3 id="2-seo-개선-방법">(2) SEO 개선 방법</h3>
<ul>
<li><p>구체적인 페이지 제목
HTML 문서의 헤더에 들어가는 페이지 제목은 구체적이고 간결하게 구성해, 검색 결과 화면에서 텍스트가 잘리지 않도록 해야 한다. 유인 키워드 반복, 반복적이고 틀에 박힌 제목 등은 지양하고 제목의 시작이나 끝에 사이트 일므을 포함하고 나머지 제목은 (<code>-</code>, <code>:</code>, <code>|</code>) 등으로 연결한다.</p>
</li>
<li><p>메타 태그 활용
구글 등 메타 태그 정보를 검색 알고리즘 평가 대상에서 제외하는 검색엔진이 증가한다고 하지만, 메타 정보는 검색엔진의 검색 결과에도 표시되고 있기 때문에 되도록 포함시키는 것이 좋다.</p>
</li>
<li><p>이미지에 alt 속성 기재
alt 속성이란, 이미지가 로딩되지 못했을 때 대신 표시되는 텍스트이다. alt 속성을 붙이면 HTML 코딩과 유용성 측면에서도 좋고, 시각장애인용 스크린리더가 사용될 때 이미지 대신 alt 속성 값을 읽어 대략 어떤 이미지인지 파악할 수 있도록 도움을 준다. 검색엔진 또한 이미지를 발견하면 alt 속성 안의 텍스트를 종해 인덱싱 작업을 하기 때문에 SEO에도 좋다.</p>
</li>
<li><p>이미지 map에 중요한 링크 사용 피하기
이미지 맵은 <code>&lt;map&gt;</code>, <code>&lt;area&gt;</code> 태그를 이용해 한 장의 사진에 여러 개의 링크를 설치하는 것이다. 이미지 맵은 검색엔진이 링크를 따라 이동할 때 방해가 될 수 있으므로, 중요한 링크 설치는 피하는게 좋다.</p>
</li>
<li><p>anchor 태그를 활용한 적절한 키워드 배치
앵커 텍스트란, 홈페이지에 삽입되는 링크 위에 있는 설명 문구를 의미한다. 앵커 태그란, 서로 다른 페이지 사이를 이동하거나 페이지 내부에서 특정한 위치로 이동할 때 사용한다. 주요한 키워드를 앵커 태그 및 앵커 텍스트에 삽입하면 SEO에 도움이 된다.</p>
</li>
<li><p>폰트 로딩 최적화
<code>@import</code>는 폰트를 불러오는데 브라우저 로딩을 잠시 멈춰 세운다. 따라서 <code>link</code> 혹은 <code>@font-face</code>를 통한 웹 폰트 로딩을 사용하는 것을 권장한다. </p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js Api]]></title>
            <link>https://velog.io/@gaebar_top/Next.js-Api</link>
            <guid>https://velog.io/@gaebar_top/Next.js-Api</guid>
            <pubDate>Wed, 21 Feb 2024 05:24:48 GMT</pubDate>
            <description><![CDATA[<h1 id="nextjs-api-만들기">Next.js Api 만들기</h1>
<h2 id="1-api-라우트-만들기">1. API 라우트 만들기</h2>
<pre><code class="language-jsx">// endpoint
// api/short-links/[id].jsx
export default function handler(request, response) { // (req, res)
  response.send(&quot;API&quot;);
}</code></pre>
<pre><code class="language-http">// request.http
GET http://localhost:3000/api/short-links/123</code></pre>
<hr>

<h2 id="2-request-다루기">2. request 다루기</h2>
<pre><code class="language-jsx">export default function handler(req, res) {
  res.send(req.query); // req.body, req.cookies, req.method
}</code></pre>
<hr>

<h2 id="3-response-다루기">3. response 다루기</h2>
<pre><code class="language-jsx">export default function handler(req, res) {
  switch (req.method) {
    case &#39;POST&#39;:
      res.status(201).send({ // method chaining
        title: &#39;위키피디아 Next.js&#39;,
        url: &#39;https://en.wikipedia.org/wiki/Next.js&#39;,
      });
      break;

    case &#39;GET&#39;:
      res.send([
        {
          id: &#39;abc&#39;,
          title: &#39;위키피디아 Next.js&#39;,
          url: &#39;https://en.wikipedia.org/wiki/Next.js&#39;,
        },
        {
          id: &#39;def&#39;,
          title: &#39;자유게시판&#39;,
          url: &#39;https://codeit.kr/commnuity/general&#39;,
        },
        {
          id: &#39;ghi&#39;,
          title: &#39;질문답변&#39;,
          url: &#39;https://www.codeit.kr/commnuity/questions&#39;,
        },
      ]);

    default:
      res.status(404).send();
  }
}</code></pre>
<hr>

<h2 id="4-api-라우팅-정리">4. API 라우팅 정리</h2>
<h3 id="1-request-핸들러-함수">(1) request 핸들러 함수</h3>
<p><code>/api/short-links</code>로 들어오는 request를 처리하려면 <code>/pages/api/shore-links.jsx</code> 또는 <code>/pages/api/short-links/index.jsx</code> 경로로 파일을 만들고 아래처럼 함수를 default export하면 된다.</p>
<pre><code class="language-jsx">export default async function handler(req, res) {
  ...
}</code></pre>
<h3 id="2-request-객체">(2) request 객체</h3>
<table>
  <thead>
    <th>프로퍼티</th>
    <th>타입</th>
    <th>설명</th>
  </thead>
  <tbody>
    <tr>
      <th>method</th>
      <th>문자열</th>
      <th>request로 들어온 HTTP 메소드 값</th>
    </tr>
        <tr>
      <th>query</th>
      <th>객체</th>
      <th>쿼리 스트링이나 Next.js에서 사용하는 Params 값이 들어 있는 객체</th>
    </tr>
        <tr>
      <th>body</th>
      <th>자유로움</th>
      <th>request의 바디 값</th>
    </tr>
        <tr>
      <th>cookie</th>
      <th>객체</th>
      <th>request의 쿠키가 key/value로 들어 있는 객체</th>
    </tr>
  </tbody>
</table>

<h3 id="3-response-객체">(3) response 객체</h3>
<p>함수 체이닝 방식으로 사용하기 때문에, <code>res.status(201).send()</code>처럼 함수를 이어서 사용할 수 있다.</p>
<table>
  <thead>
    <th>프로퍼티</th>
    <th>타입</th>
    <th>설명</th>
  </thead>
  <tbody>
    <tr>
      <th>status()</th>
      <th>함수</th>
      <th>response로 보낼 HTTP 상태 코드를 지정</th>
    </tr>
      <tr>
        <th>send()</th>
        <th>함수</th>
        <th>response로 보낼 바디를 전달</th>
      </tr>
  </tbody>
</table>

<h3 id="4-참고-자료">(4) 참고 자료</h3>
<blockquote>
<p><a href="https://nextjs.org/docs/pages/building-your-application/routing/api-routes">API Routes - Request Helpers</a>
<a href="https://nextjs.org/docs/pages/building-your-application/routing/api-routes">API Routes - Response Helpers</a></p>
</blockquote>
<hr>

<h2 id="5-mongodb-atlas와-mongoose">5. MongoDB Atlas와 Mongoose</h2>
<p>MongoDB Atlas는 MongoDB를 만드는 회사에서 운영하는 클라우드 서비스이다. 가입만하면 간편하게 클라우드로 MongoDB를 이용할 수 있다.</p>
<p>Mongoose라는 라이브러리는 쉽고 편리하기 때문에 MongoDB를 사용하는 자바스크립트 개발자들이 가장 많이 사용하는 라이브러리이다.</p>
<pre><code>npm install mongoose</code></pre><hr>

<h2 id="6-nextjs에서-환경-변수-사용하기">6. Next.js에서 환경 변수 사용하기</h2>
<h3 id="1-환경-변수environment-variable">(1) 환경 변수(Environment Variable)</h3>
<p>환경 변수는 프로그램에서 실행 환경에 따라 다른 값을 지정할 수 있는 변수이다. 꼭 웹 서비스가 아니더라도 사용하는 데이크톱 프로그램이나 터니멀에서도 이런 환경 변수를 많이 활용한다.</p>
<p>꼭 서버와 데이터베이스를 여러 개 쓰는 경우가 아니더라도 데이터베이스 주소와 같은 값을 소스코드에 그대로 쓰는 것은 위험하다. 반드시 환경 변수로 사용하는 것이 좋다. Node.js 환경에서는 이런 환경 변수들을 <code>process.env</code>라는 객체를 통해서 참조할 수 있다.</p>
<h3 id="2-nextjs에서-환경-변수-추가하기">(2) Next.js에서 환경 변수 추가하기</h3>
<p>Next.js에서는 기본적으로 <a href="https://www.npmjs.com/package/dotenv">dotenv</a>라는 파이브러리를 지원한다. 이 라이브러리는 <code>.env</code> 같은 이름의 파일에서 환경 변수들을 저장해 두면, Node.js 프로젝트를 실행할 때 환경 변수로 지정해 주는 라이브러리이다. 이 때 주의할 점은 <code>.env</code> 파일 같은 건 소스 코드에 포함시키면 안 된다는 것이다.</p>
<p>Next.js 프로젝트에서는 기본적으로 dotenv 설정이 되어 있어서, <code>.env.local</code> 같은 파일을 추가하면 손쉽게 환경 변수를 추가할 수 있다.</p>
<pre><code>// .env.local
MONGODB_URI=mongodb+srv://admin:blahblah@.clusterName.blahblah.mongodb.net/databaseName?retryWrites=true&amp;w=majority</code></pre><p>이렇게 추가한 값을 <code>process.env.MONGODB_URI</code>로 참조할 수 있다.</p>
<pre><code class="language-jsx">// pages/api/short-links/index.jsx
export default function handler(req, res) {
  const DB_URI = process.env.MONGODB_URI;
  // 데이터베이스 접속 ...
}</code></pre>
<h3 id="3-nextjs에서-사용하는-특별한-환경-변수">(3) Next.js에서 사용하는 특별한 환경 변수</h3>
<p>위에서 추가한 데이터베이스 주소는 유저의 아이디와 비밀번호를 포함한 아주 민감한 정보이다. 이러한 환경 변수가 웹 사이트에 노출되는 것을 막기 위해서 Next.js에서는 클라이언트 사이드에서 사용하는 환경 벼수에 특별한 접두사(prefix)를 사용한다. <code>NEXT_PUBLIC_</code>이라고 이름을 붙이면 이 환경 변수는 클라이언트 사이드에서도 사용할 수 있다. 예를 들어 클라이언트 사이드에서 현재 사이트의 호스트 주소를 저장해 두고 참조하고 싶다면 <code>NEXT_PUBLIC_HOST</code>라는 이름으로 사용하면 된다.</p>
<pre><code>MONGODB_URI=mongodb+srv://admin:blahblah@cluster0.blahblah.mongodb.net/databaseName?retryWrites=true&amp;w=majority
NEXT_PUBLIC_HOST=http://localhost:3000</code></pre><pre><code class="language-jsx">export default Home() {
  // 페이지 컴포넌트에서는 아래와 같이 사용
  return (
    &lt;&gt;호스트 주소: {process.env.NEXT_PUBLIC_HOST}&lt;/&gt;
  );</code></pre>
<hr>

<h2 id="7-데이터베이스-연동하기">7. 데이터베이스 연동하기</h2>
<pre><code class="language-jsx">//db/dbConnect.jsx
import mongoose from &quot;mongoose&quot;;
declare global {
  var mongoose: any; // This must be a `var` and not a `let / const`
}

const MONGODB_URI = process.env.MONGODB_URI!;

if (!MONGODB_URI) {
  throw new Error(
    &quot;Please define the MONGODB_URI environment variable inside .env.local&quot;,
  );
}

let cached = global.mongoose;

if (!cached) {
  cached = global.mongoose = { conn: null, promise: null };
}

async function dbConnect() {
  if (cached.conn) {
    return cached.conn;
  }
  if (!cached.promise) {
    const opts = {
      bufferCommands: false,
    };
    cached.promise = mongoose.connect(MONGODB_URI, opts).then((mongoose) =&gt; {
      return mongoose;
    });
  }
  try {
    cached.conn = await cached.promise;
  } catch (e) {
    cached.promise = null;
    throw e;
  }

  return cached.conn;
}

export default dbConnect;</code></pre>
<p><code>.env.local</code> 파일도 지정해 주어야 한다.</p>
<hr>

<h2 id="8-모델-만들기">8. 모델 만들기</h2>
<pre><code class="language-jsx">// db/models/ShortLinks.jsx
import mongoose from &#39;mongoose&#39;;

const shortLinkSchema = new mongoose.Schema( // 모델의 구조와 프로퍼티를 정하는 것
  {
    title: { type: String, default: &#39;&#39; },
    url: { type: String, default: &#39;&#39; },
    shortUrl: { type: String, default: &#39;&#39; },
  }, {
    timestamps: true,
  }
);

const ShortLink = mongoose.models[&#39;ShortLink&#39;] || mongoose.model(&#39;ShortLink&#39;, shortLinkSchema)

export deafult ShortLink;</code></pre>
<hr>

<h2 id="9-도큐먼트로-생성-조회하기">9. 도큐먼트로 생성, 조회하기</h2>
<pre><code class="language-jsx">// api/short-links/index.jsx
export default async function handler(req, res) {
  await dbConnect();

  switch (req.method) {
    case &#39;POST&#39;:
      const newShortLink = await ShortLink.create(req.body);
      res.status(201).send(newShortLink);
      break;

    case &#39;GET&#39;:
      const shortLinks = await ShortLink.find();
      res.send(shortLinks)
      break;

    default:
      res.status(404).send();
  }
}

// /api/short-links/[id].jsx
export default async function handler(req, res) {
  await dbConnect();
  const { id } = req.qeury;

  switch(req.method) {
    ...

    case &#39;GET&#39;:
      const shortLink = await ShortLink.findById(id);
      res.send(shortLink);
      break;
    ...
}</code></pre>
<hr>

<h2 id="10-도큐먼트-수정-삭제하기">10. 도큐먼트 수정, 삭제하기</h2>
<pre><code class="language-jsx">// [id].jsx
export default async function handler(req, res) {
  await dbConnect();
  const { id } = req.qeury;

  switch(req.method) {
    case &#39;PATCH&#39;:
      const updatedShortLink = await ShortLink.findByIdAndUpdate(id, req.body, {
      new: true,});
      res.send(updatedShortLink);
    ...

    case &#39;DELETE&#39;:
      await ShortLink.findByIdAndUpdate(id);
      res.status(204).send();
      break;
  }
}</code></pre>
<hr>

<h2 id="11-mongoose-문법-정리">11. Mongoose 문법 정리</h2>
<h3 id="1-데이터베이스-연동하기">(1) 데이터베이스 연동하기</h3>
<p>가장 먼저 <code>mongoose.connect()</code> 함수를 사용해소 커넥션을 만들고 사용한다. 이때 Next.js 환경에서 커넥션을 불필요하게 여러 개 만들 수 있기 때문에, 캐싱 기법을 사용한다. 복잡할 수 있기 때문에 공식 레포지토리에 있는 코드(<a href="https://github.com/vercel/next.js/tree/canary/examples/with-mongodb-mongoose">next.js/examples/with-mongodb-monggose</a>)를 참고해서 구현하는 걸 권장한다.</p>
<p>연동이 잘 되었는지 확인하려면 <code>mongoose.connection.readyState</code>라는 값으로 확인할 수 있다. 문서에 따르면 접속한 상태에서는 1이라는 값이 출력된다.</p>
<pre><code class="language-jsx">import mongoose from &#39;mongoose&#39;;
// ...
await dbConnect();
console.log(mongoose.connection.readyState);</code></pre>
<h3 id="2-모델-만들기">(2) 모델 만들기</h3>
<p><code>mongoose.Schema()</code>를 사용해서 스키마를 생성한다. 스키마는 모델이 어떤 속성을 가질지 정하는 용도이다. <code>mongoose.model()</code>을 사용해서 모델을 생성한다. 이때 모델의 이름을 첫 번째 아규먼트로 넘겨주는데, 이 이름은 <code>mongoose.models[ ... ]</code>로 참조할 수 있기 때문에 잘 지정했는지 확인해야 한다.</p>
<p>모듈 파일을 import할 때마다 모델을 생성하는 일이 일어나지 않도록 <code>mongoose.models[&#39;ShortLink&#39; || mongoose.model(&#39;ShortLink&#39;, shortLinkSchema)</code>처럼 작성해 둔 부분도 확인해보면 된다.</p>
<pre><code class="language-jsx">import mongoose from &#39;mongoose&#39;;

const shortLinkSchema = new mongoose.Schema(
  {
    title: { type: String, default: &#39;&#39; },
    url: { type: String, default: &#39;&#39; },
    shortUrl: { type: String, default: &#39;&#39; },
  },
  {
    timestamps: true,
  }
);

const ShortLink =
  mongoose.models[&#39;ShortLink&#39;] || mongoose.model(&#39;ShortLink&#39;, shortLinkSchema);

export default ShortLink;</code></pre>
<h3 id="3-모델-다루기">(3) 모델 다루기</h3>
<h4 id="1-생성--modelcreate">1. 생성 : Model.create()</h4>
<p>아규먼트로 전달한 값으로 도큐먼트를 생성한다.</p>
<pre><code class="language-jsx">const newShortLink = await ShortLink.create({
  title: &#39;코드잇 커뮤니티&#39;,
  url: &#39;https://www.codeit.kr/community/general&#39;,
});</code></pre>
<h4 id="2-여러-개-조회--modelfind">2. 여러 개 조회 : Model.find()</h4>
<p>조건에 맞는 모든 도큐먼트를 조회한다. 이때 조건으로 쓰이는 객체는 MongoDB의 문법을 따른다. 간단하게는 속성과 값을 key와 value로 하는 객체를 넣어줄 수 있다.</p>
<pre><code class="language-jsx">const shortLinks = await ShortLink.find(); // 모든 도큐먼트 조회

const filteredShortLinks = await ShortLink.find({ shortUrl: &#39;c0d317&#39; }) // shortUrl 값이 &#39;c0d317&#39;인 모든 도큐먼트 조회</code></pre>
<h4 id="3-아이디로-하나만-조회--modelfindbyid">3. 아이디로 하나만 조회 : Model.findById()</h4>
<p>아규먼트로 넘겨준 아이디에 해당하는 도큐먼트를 조회한다.</p>
<pre><code class="language-jsx">const shortLink = await ShortLink.findById(&#39;n3x7j5&#39;);</code></pre>
<h4 id="4-아이디로-업데이트하기--modelfindbyidandupdate">4. 아이디로 업데이트하기 : Model.findByIdAndUpdate()</h4>
<p>첫 번째 아규먼트로 넘겨준 아이디에 해당하는 도큐먼트를 업데이트한다. 두 번째 아규먼트로는 업데이트할 값을 넘겨준다.</p>
<pre><code class="language-jsx">const updatedShortLink = await ShortLink.findByIdAndUpdate(&#39;n3x7j5&#39;, { ... });</code></pre>
<h4 id="5-아이디로-삭제하기--modelfindbyidanddelete">5. 아이디로 삭제하기 : Model.findByIdAndDelete()</h4>
<p>아규먼트로 넘겨준 아이디에 해당하는 도큐먼트를 삭제한다.</p>
<pre><code class="language-jsx">await ShortLink.findByIdAndDelete(&#39;n3x7j5&#39;);</code></pre>
<h3 id="4-그-외에-자주-사용하는-함수">(4) 그 외에 자주 사용하는 함수</h3>
<h4 id="1-조건으로-하나만-조회">1. 조건으로 하나만 조회</h4>
<p>아규먼트로 조건을 넘겨주고 해당하는 도큐먼트를 하나만 조회한다.</p>
<pre><code class="language-jsx">const shortLink = await ShortLink.findOne({ shortUrl: &#39;c0d317&#39; });</code></pre>
<h4 id="2-조건으로-업데이트하기">2. 조건으로 업데이트하기</h4>
<p>첫 번째 아규먼트로 넘겨준 조건에 해당하는 도큐먼트를 업데이트한다. 두 번째 아규먼트로는 업데이트할 값을 넘겨준다.</p>
<pre><code class="language-jsx">await ShortLink.updateOne({ _id: &#39;n3x7j5&#39; }, { ... }); // 업데이트만 하고 업데이트 된 값을 리턴하지는 않음

const updatedShortLink = await ShortLink.findOneAndUpdate({ _id: &#39;n3x7j5&#39; }, { ... });</code></pre>
<h4 id="3-조건으로-삭제하기">3. 조건으로 삭제하기</h4>
<p>아규먼트로 넘겨준 조건에 해당하는 도큐먼트를 삭제한다.</p>
<pre><code class="language-jsx">await ShortLink.deleteOne({ _id: &#39;n3x7j5&#39; }, { ... }); // 삭제만 하고  기존 값을 리턴하지는 않음

const deletedShortLink = await ShortLink.findOneAndDelete({ _id: &#39;n3x7j5&#39; }, { ... });</code></pre>
<hr>

<h2 id="12-주소-단축-기능-구현">12. 주소 단축 기능 구현</h2>
<pre><code class="language-jsx">// pages/index.jsx
...
async function handleCreate(e) {
  e.prevendDefault();
  const res = await axios.post(&#39;/short-link/&#39;, { title: url, url });
  const newShortLink = res.data;
  const newShortUrl = newShortLink.shortUrl;
  setShortUrl(newShortUrl);
}
...

// short-links/new.js
export default function ShortLinkCreatePage() {
  const router = useRouter();

  async function handleSubmit(values) {
    await axios.post(&#39;/short-links/&#39;, values);
    router.push(&#39;/short-links/&#39;);
  }
  ...
}</code></pre>
<hr>

<h2 id="13-짧은-주소-리다이렉트하기">13. 짧은 주소 리다이렉트하기</h2>
<pre><code class="language-jsx">// [shortUrl].jsx
export async function getServerSideProps(context) {
  const { shortUrl } = context.query;
  await dbConnect();
  const shortLink = await ShortLink.findOne({ shortUrl });
  if (shortLink) {
    return {
      redirect: {
        destination: shortLink.url,
        permanent: false,
      },
    };
  }

  return {
    notFound: true,
  };
}

export default function ShortUrlPage() {
  return null;
}</code></pre>
<hr>

<h2 id="14-짧은-주소-목록-보여주기">14. 짧은 주소 목록 보여주기</h2>
<pre><code class="language-jsx">// short-links/index.jsx
export async function getServerSideProps() {
  await dbConnect();
  const shortLinks = awiat ShortLink.find();
  return {
    props: {
      shortLinks: JSON.parse(JSON.stringify(shortLinks)),
    }
  }
}

export default function ShortLinkListPage({ shortLinks }) {
  return (
    ...
  )
}</code></pre>
<hr>

<h2 id="15-짧은-주소-수정-삭제하기">15. 짧은 주소 수정, 삭제하기</h2>
<pre><code class="language-jsx">// [id].jsx
export async function getServerSideProps(context) {
  const { id } = context.query;
  await dbConnect();
  const shortLink = await ShortLink.findById(id);
  if (shortLink) {
    return {
      props: {
        shortLink: JSON.parse(JSON.stringify(shortLink)),
      },
    };
  }
  return {
    notFound: true,
  };
}

export default function ShortLinkEditPage({ shortLink }) {
  const router = useRouter();
  const { id } = router.query;

  async function handleSubmit(values) {
    await axios.patch(`/short-links/${id}`, values);
    router.push(&quot;/short-links/&quot;);
  }
...
}

// index.jsx
export default function ShortLinkListPage({ shortLinks: initialShortLinks }) {
  const [shortLinks, setShortLinks] = useState(initialShortLinks);

  async function handleDelete(id) {
    await axios.delete(`/short-links/${id}`);
    setShortLinks((prevShortLinks) =&gt;
      prevShortLinks.filter((shortLink) =&gt; shortLink._id !== id)
    );
  }
...</code></pre>
<hr>

<h2 id="16-vercel에서-환경-변수-설정하기">16. Vercel에서 환경 변수 설정하기</h2>
<ol>
<li>프로젝트 화면에서 Settings 메뉴로 이동한다.</li>
<li>Environment Variables 메뉴로 들어간다.</li>
<li>Vercel에서 사용할 환경 변수를 추가할 수 있다. (<code>NEXT_PUBLIC_BASE_URL</code> 환경 변수는 추가하고, <code>MONGOOB_URI</code>는 따로 지정하지 않는다.)</li>
</ol>
<hr>

<h2 id="17-vercel과-mongodb-atlas-연동하기">17. Vercel과 MongoDB Atlas 연동하기</h2>
<ol>
<li>Vercel 메인 화면에서 Integrations 메뉴로 들어간다.</li>
<li>마켓 플레이스에서 MongoDB Atlas를 선택하고, Add Integration을 클릭해서 연동한다.</li>
<li>어떤 프로젝트에 적용할 지 물어보면, 모든 프로젝트에 적용하기를 한다.</li>
<li>우선 모든 권한을 허용해 준다.</li>
<li>MongoDB Atlas 쪽에서 권한을 확인한다.</li>
<li>Vercel 프로젝트와 연결한 클러스터를 물어본다. 둘을 연결해 준다. 그리고 허용할 IP 목록에 Vercel을 위해서 <code>0.0.0.0/0</code>을 허용한다는 것을 이해했다고 체크한다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[이력서 작성 가이드]]></title>
            <link>https://velog.io/@gaebar_top/%EC%9D%B4%EB%A0%A5%EC%84%9C-%EC%9E%91%EC%84%B1-%EA%B0%80%EC%9D%B4%EB%93%9C</link>
            <guid>https://velog.io/@gaebar_top/%EC%9D%B4%EB%A0%A5%EC%84%9C-%EC%9E%91%EC%84%B1-%EA%B0%80%EC%9D%B4%EB%93%9C</guid>
            <pubDate>Fri, 16 Feb 2024 06:56:46 GMT</pubDate>
            <description><![CDATA[<h1 id="이력서-작성-가이드">이력서 작성 가이드</h1>
<h2 id="1-이력서에-관해서">1. 이력서에 관해서</h2>
<h3 id="q1-이력서는-어떤-기능을-하는-문서인가">Q1) 이력서는 어떤 기능을 하는 문서인가?</h3>
<ul>
<li>나를 설명하는 문서</li>
<li>회사가 &#39;나&#39;라는 구성원을 어떻게 사용해야 하는지 설명하는 내용이 필요하다.</li>
</ul>
<h3 id="q2-훌륭한-설명서이력서-조건은">Q2) 훌륭한 설명서(이력서) 조건은?</h3>
<ul>
<li>읽는 사람을 배려한</li>
<li>이해하기 쉬원</li>
<li>만나보고 싶은</li>
</ul>
<hr>

<h2 id="2-훌륭한-이력서의-조건">2. 훌륭한 이력서의 조건</h2>
<h3 id="1-출발점은-자신이-아니라-읽는-사람이어야-한다">(1) 출발점은 자신이 아니라 읽는 사람이어야 한다.</h3>
<p>이력서를 읽는 사람이 누구인지 알고, 그들을 이해하는 것이 이력서를 작성하기 전에 가장 먼저 준비되어야 한다.</p>
<ul>
<li>상대방이 누구인지 알기(채용담당자, 직무전문가)</li>
<li>상대방이 사용하는 언어로 표현하기(홈페이지, 채용공고)</li>
<li>상대방을 이해하고 공감하기(커리어 SNS, 인적 네트워크)</li>
</ul>
<h3 id="2-보기에-편안하고-이해하기-쉬운-내용이어야-한다">(2) 보기에 편안하고, 이해하기 쉬운 내용이어야 한다.</h3>
<p>보기 좋은 떡이 맛도 좋다고 한다. 이력서를 보는 사람의 눈이 편안하고, 쉽게 이해가 되며, 흥미를 유발할 수 있어야 한다.</p>
<ul>
<li>이력서 페이지 구성(가독성)</li>
<li>이력서 디자인(회사 브랜드 이미지 커스텀)</li>
<li>이력서 파일 형식(PDF/PPT -&gt; 노션)</li>
</ul>
<h3 id="3-메시지가-명확하고-구체적이어야-한다">(3) 메시지가 명확하고 구체적이어야 한다.</h3>
<p>입사 지원하는 회사에 전달하고 싶은 메세지가 있다면, 의미부여를 명확히 해야 한다. 전달하고 싶은 메시지를 구체적으로 설명해야 읽는 사람에게 잘 전달이 된다.</p>
<ul>
<li>육하원칙(누가 언제 어디서 무엇을 어떻게 왜)</li>
<li>숫자 표현(객관적 성과)</li>
<li>비교(배운점, 느낀점)<hr>

</li>
</ul>
<h2 id="3-본격적으로-이력서를-작성하기-전에-차별화-전략-이-두가지-내용을-고민해야-한다">3. 본격적으로 이력서를 작성하기 전에 <code>#차별화 #전략</code> 이 두가지 내용을 고민해야 한다.</h2>
<h3 id="q1-여러분이-갖고-있는-차별된-경험과-역량은-무엇인가">Q1) 여러분이 갖고 있는 차별된 경험과 역량은 무엇인가?</h3>
<h3 id="q2-이력서에-여러분이-갖고-있는-차별된-경험과-역량을-돋보이게-보여줄-전략은-무엇인가">Q2) 이력서에 여러분이 갖고 있는 차별된 경험과 역량을 돋보이게 보여줄 전략은 무엇인가?</h3>
<hr>

<h2 id="4-이력서-작성">4. 이력서 작성</h2>
<ul>
<li>이력서 디자인과 항목 구성은 각자 갖고 있는 강점이 돋보이도록 만들기</li>
<li>이력서를 보는 사람이 느끼게 만들고 싶은 이야기 전체를 관통하는 일관된 메세지</li>
<li>채용하는 회사는 기술만 뛰어난 인재보다 협업과 문제해결능력 있는 인재를 원한다.</li>
<li>이력서 분량을 예측 가능하도록 글자수와 읽는데 소요되는 시간을 명시하라.</li>
<li>Career Social Network LinkedIn 프로필을 업데이트해 보자.</li>
<li>다룰 수 있는 협업 도구 나열은 생략해도 괜찮다.</li>
</ul>
<h3 id="1-메리트merit와-베네핏benefit-차이">(1) 메리트(Merit)와 베네핏(Benefit) 차이</h3>
<p>&#39;나&#39;라는 입사 지원자가 기능적으로 뛰어나다는 내용은 어필이 되지 않는다. &#39;나&#39;라는 인재를 영입하여 얻을 수 있는 결과가 무엇인지 어필해야 한다.</p>
<ul>
<li>메리트(Merit) : 상품의 가격을 결정하는 요인 / 상품이 좋은 이유</li>
<li>베네핏(Benefit) : 상품을 통해 고객이 얻을 수 있는 혜택 / 상품을 구매하면 얻는 결과</li>
</ul>
<h3 id="2-이력서-작성-내용을-보고-오해가-없도록-상태값을-정직하게-공유해야-한다">(2) 이력서 작성 내용을 보고 오해가 없도록 상태값을 정직하게 공유해야 한다.</h3>
<ul>
<li>학력 정보(대학원 이후 정보만 이력하는 경우)</li>
<li>경험 기간(시작일만 있고, 종료일은 없는 경우)</li>
</ul>
<h3 id="3-프로필-사진에도-입사-지원-기업에-따른-전략이-필요하다">(3) 프로필 사진에도 입사 지원 기업에 따른 전략이 필요하다.</h3>
<ul>
<li>프로필 사진, 이렇게 준비해보자.<ul>
<li>대기업 : 정장을 입은 증명 사진 느낌</li>
<li>중견기업 : 세미 캐쥬얼을 입은 여권 사진 느낌</li>
<li>스타트업 : 편한 복장으로 스마트폰 촬영한 셀카 느낌</li>
</ul>
</li>
</ul>
<h3 id="4-기업에서-요구하지-않는-개인-정보는-작성하지-않아도-된다">(4) 기업에서 요구하지 않는 개인 정보는 작성하지 않아도 된다.</h3>
<ul>
<li>대표적인 불필요한 정보<ul>
<li>부모님을 포함한 가족 신상</li>
<li>이전 직장 연봉 정보와 퇴직 사유</li>
<li>모든 아르바이트, 대외 활동, 취득한 자격증 등</li>
</ul>
</li>
</ul>
<h3 id="5-개인-기술-블로그-이렇게-작성해-보자">(5) 개인 기술 블로그 이렇게 작성해 보자.</h3>
<ul>
<li>기술적인 깊이를 보여주는 포스팅<ul>
<li>많이 쓰는 것보다 품격 있는 글 2개 정도</li>
<li>일상적인 내용과 감정, 부정적인 내용 작성 금지</li>
<li>Insight 위주로 간단 명료하게 작성</li>
</ul>
</li>
</ul>
<h3 id="6-github-등록보다-잘-관리된-내용이-중요하다">(6) GitHub 등록보다 잘 관리된 내용이 중요하다.</h3>
<ul>
<li>GitHub 내용을 만들 때<ul>
<li>프로젝트 구조, 개발 구현 이미지, 프로젝트 종류, 사용 기술 설명</li>
<li>단순히 알고리즘 공부한 내용을 작성하지 않기</li>
<li>다른 사람 것을 퍼온 내용을 게시하지 않기</li>
</ul>
</li>
</ul>
<hr>

<h2 id="5-훌륭한-프로젝트-조건">5. 훌륭한 프로젝트 조건</h2>
<h3 id="1-프로젝트를-설명하는-구성은-개요와-사용-기술-기여-정도를-포함하는-것이-좋다">(1) 프로젝트를 설명하는 구성은 개요와 사용 기술, 기여 정도를 포함하는 것이 좋다.</h3>
<ul>
<li>개요는 프로젝트 시작 배경과 문제 인식, 해결 방법에 대한 소개를 작성한다.</li>
<li>사용 기술은 언어, 프레임워크, 라이브러리 구별하여 작성한다.</li>
<li>팀 프로젝트인 경우 작성자의 기여 정도를 백분율로 설명하면 좋다.</li>
</ul>
<h3 id="2-실제-사용자가-있는-것과-현재-배포되어-있는-상태가-중요하다">(2) 실제 사용자가 있는 것과 현재 배포되어 있는 상태가 중요하다.</h3>
<ul>
<li>프로젝트로 만든 웹 페이지를 실제 사용해 본 사람이 있어야 한다. 실제 사용자가 없는 내용은 인위적인 느낌을 전달한다.</li>
<li>현재 배포되어 있는 상태라면 좋다. 개발한 내용을 실시간으로 볼 수 있어야 한다.</li>
</ul>
<h3 id="3-프로젝트-경험을-통해-배우고-느낀-점과-같은-성장-포인트를-작성하면-좋다">(3) 프로젝트 경험을 통해 배우고, 느낀 점과 같은 성장 포인트를 작성하면 좋다.</h3>
<p>개인적인 생각을 이야기할 때, 논리적인 근거를 바탕으로 주장하는 것이 좋다</p>
<hr>

<h2 id="6-훌륭한-자기소개서-조건">6. 훌륭한 자기소개서 조건</h2>
<h3 id="1-입사-지원-동기와-본인-스토리가-중요하다">(1) 입사 지원 동기와 본인 스토리가 중요하다.</h3>
<p>입사 지원 동기는 회사와 산업에 대한 관심의 표현이다. 입사 지원하는 직무를 잘 할 수 있는 준비와 가능성을 본인 스토리로 증명하면 좋다.</p>
<ul>
<li><p>매력적인 스토리텔링
왜 입사 지원한 회사여야 하는가, 왜 직무를 잘 할 수 있는가, 왜 나를 채용해야 하는가</p>
</li>
<li><p>형식적인 지원 동기
어떤 회사에 지원해도 이상하지 않은 내용은, 어떤 회사도 합격하기 어렵다.</p>
</li>
<li><p>한 줄 카피라이팅
메시지 전달이 모호한 카피라이팅은 감점 요인이다.</p>
</li>
</ul>
<h3 id="2-그-외">(2) 그 외</h3>
<ul>
<li>입사 지원하는 직무와 관련 있는 인턴과 동아리 활동을 설명해라.</li>
<li>여러분의 지난 과거 동안 만든 경험과 역량이 가치있는 성과임을 기억해라.(떳떳하거나 뻔뻔할 정도로 자신감 찾기!)</li>
<li>이력서 작성 완료 후 오타와 맞춤법 확인은 필수다.</li>
<li>입사 지원 서류 파일 형식은 노션보다 PDF 파일 형식이 더 좋다.</li>
<li>입사 지원서 제출 전 헷갈리는 내용은 반드시 채용담당자에게 문의해라.</li>
<li>스스로, 그리고 주변 사람들에게 이력서 점수를 피드백 받아보라.</li>
<li>범용 입사 지원 서류로 진정성을 갖춘 내용이 필요하다. 논리 만큼이나 진심도 통할 수 있다.<hr>

</li>
</ul>
<h2 id="7-서류-제출-후">7. 서류 제출 후</h2>
<h3 id="1-서류-전형-결과-막상-합격해-보니-포지션에-대한-마음이-식었을-때">(1) 서류 전형 결과 막상 합격해 보니 포지션에 대한 마음이 식었을 때</h3>
<ul>
<li>가급적 채용 전형을 중단하지 마시고 면접에 참여해라.</li>
<li>채용 전형을 중단할 때, 최대한 정중한 메시지로 거절해라.</li>
<li>대한민국 직상 세계는 대단히 좁다. 언제 어떻게 만날지 모른다는 것을 기억해라.</li>
</ul>
<h3 id="2-입사-지원-결과-불합격을-대하는-담담한-마음이-필요하다">(2) 입사 지원 결과 불합격을 대하는 담담한 마음이 필요하다.</h3>
<ul>
<li>중요한 건 꺽이지 않는 마음이다.</li>
</ul>
<h3 id="3-경제-불황으로-고물가-시대가-되자-못난이-사과가-상품화-되어-인기를-끌고-있다">(3) 경제 불황으로 고물가 시대가 되자 못난이 사과가 상품화 되어 인기를 끌고 있다.</h3>
<ul>
<li>처음에는 누구나 지식과 기술이 부족하다.</li>
<li>하지만 개발자가 되고자 하는 열정과 흥미는 부족하지 않아야 한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[위클리 페이퍼 (13)]]></title>
            <link>https://velog.io/@gaebar_top/%EC%9C%84%ED%81%B4%EB%A6%AC-%ED%8E%98%EC%9D%B4%ED%8D%BC-13</link>
            <guid>https://velog.io/@gaebar_top/%EC%9C%84%ED%81%B4%EB%A6%AC-%ED%8E%98%EC%9D%B4%ED%8D%BC-13</guid>
            <pubDate>Fri, 16 Feb 2024 05:28:49 GMT</pubDate>
            <description><![CDATA[<h1 id="13주차-위클리-페이퍼">13주차 위클리 페이퍼</h1>
<h2 id="q1-리액트만-사용할-때와-비교해-nextjs를-사용하는-이유에-대해-설명해-주세요">Q1) 리액트만 사용할 때와 비교해 Next.js를 사용하는 이유에 대해 설명해 주세요.</h2>
<h3 id="1-리액트의-단점">(1) 리액트의 단점</h3>
<p>리액트는 기본적으로 CSR(Client-side Rendering)에서만 작동한다는 단점이 있다. 사용자의 웹 브라우저에서만 실행되기 때문에 리액트를 사용한 웹 애플리케이션은 검색엔진 최적화(SEO)의 효과를 거의 볼 수 없으며, 첫 화면에 웹 애플리케이션을 제대로 표시하기 위해 애플리케이션 실행 초기에 성능 부담이 생긴다. 웹 앱을 완전히 표시하려면 브라우저가 전체 웹 애플리케이션 번들을 다운로드한 다음, 그 내용을 분석하고 코드를 실행해서 결과를 얻어야 하기 때문에, 아주 큰 웹 애플리케이션에서는 첫 화면을 표시하기까지 수 초가 소요되기도 한다.</p>
<h3 id="2-nextjs의-등장">(2) Next.js의 등장</h3>
<p>Next.js는 리액트를 위해 만든 오픈소스 자바스크립트 웹 프레임워크로, 리액트에는 없는 SSR(서버사이드 렌더링), SSG(정적 사이트 생서), ISR(증분 정적 재생성)과 같은 다양하고 풍부한 기능을 제공한다. 이 외에도 타입스크립트에 대한 기본 지원, 자동 polyfill 적용, 이미지 최적화, 웹 애플리케이션의 국제화 지원, 성능 분석 등 다양한 기능 또한 제공해 주고 있다.</p>
<p>Next.js와 비슷한 프레임워크로는 Gatsby, Razzle, Nuxt.js, Angular Universal 등이 있다. 하지만 이러한 프레임워크들이 존재하지만, 굳이 Next.js를 사용하는 이유는 Next.js가 제공하는 뛰어난 기능들 때문이다.</p>
<hr>

<h2 id="q2-nextjs에서-ssr을-실행하는-과정과-hydration에-대해-설명해-주세요">Q2) Next.js에서 SSR을 실행하는 과정과 hydration에 대해 설명해 주세요.</h2>
<h3 id="1-ssr-실행과정">(1) SSR 실행과정</h3>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/99ae372f-070a-42c1-a07f-9ad4c0520610/image.png" alt=""></p>
</blockquote>
<p>서버사이드 렌더링에서의 렌더링은 HTML 파일 내에 내용이 있느냐 없느냐를 의미한다. 쉽게 생각해서 내용이 있으면, 렌더링이 된 것이다. 브라우저는 렌더링이 가능한 HTML을 받아 렌더링한 후 JS 파일을 다운받는다. 이렇게 브라우저가 해당 파일을 실행시키면, 페이지의 상호작용까지도 가능해지게 된다.</p>
<p>Next.js는 모든 페이지를 미리 렌더링(pre-rendering)한다. Next.js에서 미리 렌더링하는 방식은 SSG와 SSR로 나뉜다.</p>
<ul>
<li><p>SSG(Static-site Generation)
빌드 타임에 HTML이 생성되어 매 요청마다 이를 재사용한다. 즉, 빌드 시점 이후에 서버에 따로 request를 보내지 않는다.</p>
</li>
<li><p>SSR(Server-Side Rendering)
매 request마다, 즉 웹 사이트의 페이지를 접속하거나 페이지를 새로고침할 때마다 HTML을 생성한다. SSR 방식에서는 클라이언트의 각 request마다 서버가 해당 페이지의 데이터와 리소스를 가져와서 HTML을 동적으로 생성한다.</p>
</li>
</ul>
<h3 id="2-hydration">(2) Hydration</h3>
<p>Next.js는 서버에서 HTML을 문자열로 가져온 후에, 클라이언트에서 버서로부터 보내준 HTML을 <code>render()</code>, <code>hydrate()</code>하여 브라우저에 렌더링하는 과정을 hydration이라고 부른다.</p>
<p>리액트는 클라이언트 렌더링만 있어, 유저에게 보여줄 HTML, CSS 그리고 JS 모두 <code>render()</code> 함수를 이용해 생성하여, 모든 리소스를 한번에 렌더링한다.</p>
<p>반면, Next.js는 서버에서 보여줄 HTML을 미리 렌더링하여 가져오기 때문에 <code>render()</code> 함수로 HTML 뼈대만 렌더링하고, <code>hydrate()</code>를 통해 서버에서 받아온 HTML에 유저가 상호작용할 수 있는 이벤트 리스너(JS)를 연결하는 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js 시작하기 (3)]]></title>
            <link>https://velog.io/@gaebar_top/Next.js-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-3</link>
            <guid>https://velog.io/@gaebar_top/Next.js-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-3</guid>
            <pubDate>Thu, 15 Feb 2024 12:12:01 GMT</pubDate>
            <description><![CDATA[<h1 id="프리렌더링pre-rendering">프리렌더링(Pre-rendering)</h1>
<h2 id="1-프리렌더링이란">1. 프리렌더링이란?</h2>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/21669630-f5fb-41a3-bfae-59395124afb2/image.png" alt=""></p>
</blockquote>
<h3 id="정적-생성--빌드할-때-렌더링">정적 생성 : 빌드할 때 렌더링</h3>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/7336c66f-8721-447e-adaa-fbc7a7d3b2ca/image.png" alt=""></p>
</blockquote>
<p>Hydration : 이미 렌더링된 HTML과 리액트와 연결하는 것을 의미한다.</p>
<h3 id="서버사이드-렌더링server-side-rendering">서버사이드 렌더링(Server-side rendering)</h3>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/c227cf9e-4049-4ac7-8cbc-8bff02d178e6/image.png" alt=""></p>
</blockquote>
<p>즉, 프리렌더링은 웹 페이지가 렌더링되기 이전의 렌더링하는 것을 의미하며, 크게 정적 생성과 서버사이드 렌더링으로 나뉜다.</p>
<ul>
<li>정적 생성 : 빌드할 때 렌더링</li>
<li>서버사이드 렌더링 : request가 들어오면 렌더링</li>
</ul>
<p>프리렌더링을 하면 좋은 점은 초기 로딩이 빨라지며, 검색엔진 최적화(SEO)가 된다.</p>
<hr>

<h2 id="2-정적-생성-i">2. 정적 생성 I</h2>
<p>Next.js에서는 기본적으로 정적 생성을 한다. 이 때 데이터를 가져와서 사용하고 싶을 때는 <code>getStaticProps</code>를 사용해서 사용하면 된다.</p>
<pre><code class="language-jsx">export async function getStaticProps() {
  const res = await axios.get(&#39;/products&#39;);
  const products = res.data.results;

  return {
    props: {
      products
    }
  }
}

export default function Hom({ products }) {
  return (
    ...
  )
}</code></pre>
<hr>

<h2 id="3-정적-생성-ii">3. 정적 생성 II</h2>
<pre><code class="language-jsx">// [id].js
export async function getStaticPaths() {
  const res = await axios.get(&#39;.products/&#39;);
  const products = res.data.results;
  const paths = rpoducts.map((product) =&gt; ({
    params: { id: String(product.id) },
  }));

  return {
    paths,
    fallback: true,
  }
}

export async function getStaticProps(context) {
  const productId = context.params[&#39;id&#39;];
  let product;
  try {
    const res = await axios.get(`/products/${productId}`);
    product = res.data;
  } catch {
    return {
      notFound: true,
    };
  }

  return {
    props: {
      product,
    }
  }
}

export default function Product({ product }) {
  ...

  if (!product) return (
    &lt;div className={styles.loading}&gt;
      &lt;Spinner /&gt;
    &lt;/div&gt;
  );
  ...
}</code></pre>
<hr>

<h2 id="4-서버사이드-렌더링">4. 서버사이드 렌더링</h2>
<pre><code class="language-jsx">export async function getServerSideProps(context) {
  const q = context.query[&#39;q&#39;];

  const res = await axios.get(`/products/?q=${q}`);
  const products = res.data.results ?? [];

  return {
    props: {
      products,
      q,
    },
  }
}

export default function Search({ q, products }) {
  ...
}</code></pre>
<hr>

<h2 id="5-클라이언트에서-데이터-주고-받기">5. 클라이언트에서 데이터 주고 받기</h2>
<pre><code class="language-jsx">...

export default function Product({ product, sizeReviews: initialSizeReviews }) {
  const [sizeReviews, setrSizeReviews] = useState(initialSizeReviews);
  const [formValue, setFormValue] = useState({
    size: &#39;M&#39;,
    sex: &#39;male&#39;,
    height: 173,
    fit: &#39;good&#39;,
  });

  async function handleSubmit(e) {
    e.preventDefault();
    const sizeReview = {
      ...formValue,
      productId: product.id,
    };
    const res = await axios.post(&#39;/size_reveiws/&#39;, sizeReview);
    const newSizeReview = res.data;
    setSizeReveiws((prevSizeReviews) =&gt; [
      newSizeReview,
      ...prevSizeReviews,
    ]);
  };

  async function handleInputChange(e) {
    const { name, value } = e.target;
    handleChange(name, value);
  }

  async function handleChange(name, value) {
    setFormValue({
      ...formValue,
      [name]: value,
    });
  }

  ...
}</code></pre>
<hr>

<h2 id="6-정적-생성-vs-ssr">6. 정적 생성 vs SSR</h2>
<p>프리렌더링을 하면 검색엔진 최적화(SEO)에 도움이 되고, 페이지의 로딩 속도가 빠르다는 장점이 있다. 정적 생성과 서버사이드 렌더링 두 가지 선택이 있는데, 이 두가지를 언제 어떻게 사용하면 좋을까?</p>
<h3 id="1-홈페이지">(1) 홈페이지</h3>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/fa96be55-96e5-4eca-84c4-a94eccb162b7/image.png" alt=""></p>
</blockquote>
<p>홈페이지에서는 상품 데이터가 자주 업데이트되지 않는다고 가정했기 때문에, 정적 생성을 했었다. <code>getStaticProps()</code> 함수를 구현하고 데이터를 불러와서 정적 생성을 하도록 구현했다. 만약에 관리자가 상품을 자주 업데이트하거나, 항상 최신의 상품을 보여줘야 한다면 서버사이드 렌더링을 선택했을 것이다.</p>
<h3 id="2-검색-페이지">(2) 검색 페이지</h3>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/df635186-1efd-435e-9ccf-76747d78be4c/image.png" alt=""></p>
</blockquote>
<p>검색 페이지에서는 주소에 있는 쿼리스트링 값에 따라서 다른 검색 결과를 보여줘야 하기 때문에, 서버사이드 렌더링을 했었다. <code>getServerSideProps()</code> 함수를 구현해서 쿼리스트링에 따라 다른 데이터를 가지고 서버사이드 렌더링을 하도록 구현했다.</p>
<h3 id="3-상품-상세-페이지">(3) 상품 상세 페이지</h3>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/a4df80d8-ffcf-4b68-b724-abe1bced2061/image.png" alt=""></p>
</blockquote>
<p>상품 상세 페이지에서는 항상 최신의 리뷰 데이터를 보여주기 위해서, 서버사이드 렌더링을 했었다. <code>getServerSideProps()</code> 함수를 구현해서 아이디 값에 따라 다른 데이터를 가지고 서버사이드 렌더링을 하도록 구현했다. request가 들어올 때마다 매번 리뷰 데이터를 불러와서 렌더링하기 때문에, 항상 최신의 리뷰 데이터로 프리렌더링할 수 있다.</p>
<h3 id="4-설정-페이지">(4) 설정 페이지</h3>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/07fef404-8c71-43ba-a3a9-67e5672ed5ba/image.png" alt=""></p>
</blockquote>
<p>설정 페이지에서는 딱히 데이터를 사용하지 않았기 때문에, Next.js에서 기본적으로 정적 생성을 하고 있었다.</p>
<h3 id="5-서버사이드-렌더링을-고려하면-좋은-경우">(5) 서버사이드 렌더링을 고려하면 좋은 경우</h3>
<ul>
<li>항상 최신 데이터를 보여줘야 하는 경우</li>
<li>데이터가 자주 바뀌는 경우</li>
<li>request의 데이터를 사용해야 하는 경우 (헤더, 쿼리스트링, 쿠키 등)</li>
</ul>
<p>그 외에 특별한 이유가 없다면 되도록 정적 생성을 하는 걸 권장한다.</p>
<hr>

<h2 id="7-프리렌더링-정리">7. 프리렌더링 정리</h2>
<h3 id="1-프리렌더링pre-rendering">(1) 프리렌더링(Pre-rendering)</h3>
<p>웹 브라우저가 페이지를 로딩하기 이전에 렌더링하는 걸 의미한다. 크게 정적 생성(Static Generation)과 서버사이드 렌더링(Server-side Rendering)으로 나뉜다. Next.js에서는 기본적으로 모든 페이지를 정적 생성한다.</p>
<h3 id="2-정적-생성static-generation">(2) 정적 생성(Static Generation)</h3>
<p>프로젝트를 빌드하는 시점에 미리 HTML을 렌더링하는 걸 의미한다.</p>
<h4 id="1-getstaticprops-함수">1. getStaticProps() 함수</h4>
<p>정적 생성할 때 필요한 데이터를 받아와서 렌더링하고 싶다면 <code>getStaticProps()</code> 함수를 구현하고 export하면 된다. 객체의 <code>props</code> 프로퍼티로 넘겨줄 Props 객체를 지정하고, 이것을 페이지 컴포넌트에서 사용할 수 있다.</p>
<pre><code class="language-jsx">export async function getStaticProps() {
  const res = await axios(&#39;/products/&#39;);
  const products = res.data;

  return {
    props: {
      products,
    },
  };
}

export default function Home({ products }) {
  return (
    &lt;ProductList products={products} /&gt;
  );
}</code></pre>
<h4 id="2-getstaticpaths-함수">2. getStaticPaths() 함수</h4>
<p><code>[id].js</code>와 같이 다이나믹 라우팅을 하는 페이지를 정적 생성을 할 때에는 어떤 페이지를 정적 생성할지 지정해줘야 한다. <code>getStaticPaths()</code> 라는 함수를 구현하고 export해서 정해줄 수 있다.</p>
<p><code>getStaticPaths()</code> 함수에서는 리턴 값으로 객체를 리턴하는데, <code>paths</code>라는 배열에서 각 페이지에 해당하는 정보를 넘겨줄 수 있다. 예를 들어 <code>id</code> 값이 <code>&#39;1&#39;</code>인 페이지를 정적 생성하려면 <code>{ params: { id: &#39;1&#39; } }</code>과 같이 쓸 수 있다.</p>
<p><code>fallback</code>이라는 속성을 사용해서 정적 생성되지 않은 페이지를 처리해 줄 것인지 지정할 수 있다. <code>fallback: true</code>라고하면 생성되지 않은 페이지를 접속했을 때 <code>getStaticProps()</code> 함수를 실행해 페이지를 만들어서 보여준다.</p>
<pre><code class="language-jsx">export async function getStaticPaths() {
  return {
    paths: [
      { params: { id: &#39;1&#39; }},
      { params: { id: &#39;2&#39; }},
    ],
    fallback: true,
  };
}</code></pre>
<p><code>getStaticProps()</code> 함수에서는 <code>context</code> 파라미터를 사용해서 필요한 Params(<code>context.params</code>) 값이나 쿼리스트링(<code>context.query</code>) 값을 참조할 수 있다.</p>
<p>그리고 <code>fallback: true</code>라고 지정했다면, 필요한 데이터가 존재하지 않을 수 있기 때문에 적절한 예외처리를 해야 한다. <code>{ notFound: true }</code>를 리턴하면 데이터를 찾을 수 없을 때 404 페이지로 이동시킬 수 있다.</p>
<pre><code class="language-jsx">export async function getStaticProps(context) {
  const productId = context.params[&#39;id&#39;];

  let product;

  try {
    const res = await axios(`/products/${productId}`);
    product = res.data;
  } catch {
    return {
      notFound: true,
    };
  }

  return {
    props: {
      product,
    },
  };
}</code></pre>
<p>만약 <code>fallback: true</code>를 설정했다면 <code>getStaticProps()</code>를 실행하는 동안 보여줄 로딩 페이지를 구현해야 한다. 페이지 컴포넌트에서 필요한 데이터가 존재하지 않을 때를 처리해 주면 된다.</p>
<pre><code class="language-jsx">export default function Product({ product }) {
  if (!product) {
    return &lt;&gt;로딩 중 ...&lt;/&gt;
  }
  return &lt;&gt;상품 이름: {product.name}&lt;/&gt;;
}</code></pre>
<h3 id="3-서버사이드-렌더링server-side-rendering">(3) 서버사이드 렌더링(Server-side Rendering)</h3>
<p>Next.js 서버에 request가 도착할 때마다 페이지를 렌더링해서 보내주는 방식이다. <code>getServerSideProps()</code> 함수를 구현하고 export하면 된다. 이때 리턴 값으로는 객체를 리턴한다. 정적 생성때와 마찬가지로 <code>props</code> 프로퍼티로 Props 객체를 넘겨주면 페이지 컴포넌트에서 받아서 사용할 수 있다.</p>
<pre><code class="language-jsx">export async function getServerSideProps() {
  const res = await axios(&#39;/products/&#39;);
  const products = res.data;

  return {
    props: {
      products,
    },
  };
}

export default function Home({ products }) {
  return (
    &lt;ProductList products={products} /&gt;
  );
}</code></pre>
<hr>

<h2 id="8-app-router">8. App Router</h2>
<h3 id="1-app-router란">(1) App Router란?</h3>
<p>Next.js 13.4 이후 버전부터는 새로운 방식의 라우팅을 지원하기 시작했다. App Router라는 것이다. 이전까지 배웠던 것은 Pages Router라고 부른다.</p>
<p>App Router의 가장 큰 차이는 <code>/pages</code> 폴더가 아닌 <code>/app</code> 폴더에 페이지 컴포넌트들을 추가한다는 것이다. 그리고 이 페이지 컴포넌트들은 기본적으로 리액트 서버 컴포넌트(React Server Compoenent)이다. 기존에 사용하던 리액트 컴포넌트와는 조금 다른 컴포넌트이다. 간단히 설명해서 서버에서만 렌더링되는 컴포넌트이다. 그 밖에도 라우팅의 여러 기능들이 달라졌다. 공통된 레이아웃을 적용하는 방식이나, 메타데이터를 사용하는 방식, 그리고 데이터를 받아오는 방식 등이 달라졌다.</p>
<h3 id="2-react-server-componentrsc란">(2) React Server Component(RSC)란?</h3>
<p>리액트 서버 컴포넌트는 서버에서만 렌더링하는 컴포넌트이다. 기존에 사용하던 컴포넌트 방식은 서버 컴포넌트와 구분하기 위해서 리액트 클라이언트 컴포넌트라고 부르기도 한다.</p>
<p>리액트 서버 컴포넌트와 리액트 클라이언트 컴포넌트의 문법에서 가장 큰 차이는 데이터를 가져오는 방식이다. 클라이언트에서 request를 보내서 데이터를 받아오거나, Next.js에서 프리렌더링을 한다면 데이터를 Props로 내려받았다. 리액트 서버 컴포넌트를 사용하면 컴포넌트를 async/await 함수로 만들 수 있고, 함수 최상위(top-level)에서 await로 데이터를 가져올 수 있다.</p>
<p>async/await를 사용해서 컴포넌트 함수를 작성하기 때문에 훨씬 직과적인 문법으로 컴포넌트를 개발할 수 있다. 서버에서 모든 데이터를 가져온 다음 렌더링까지 해서 보내주기 때문에 서버와 클라이언트가 여러번 request를 주고받을 때보다 빠르게 페이지를 보여줄 수 있다. 게다가 서버 컴포넌트 렌더링에 필요한 자바스크립트는 서버에서만 실행하기 때문에 클라이언트가 다운로드해야 할 자바스크립트 코드의 양도 줄어든다.</p>
<pre><code class="language-jsx">async function getData() {
  const res = await fetch(&#39;https://api.example.com/...&#39;);
  return res.json();
}

export default async function Page() {
  const data = await getData();

  return &lt;main&gt; ... &lt;/main&gt;;
}</code></pre>
<h3 id="3-참고자료">(3) 참고자료</h3>
<blockquote>
<p><a href="https://www.youtube.com/watch?v=DrxiNfbr63s">Introducing Next.js App Router - Vercel on Youtube</a>
<a href="https://react.dev/blog/2023/03/22/react-labs-what-we-have-been-working-on-march-2023#react-server-components">React Labs: What We&#39;ve Been Working On - March 2023 - React</a>
<a href="https://nextjs.org/docs/app/building-your-application/rendering#server-components">Server Components - Next.js</a>
<a href="https://react.dev/blog/2020/12/21/data-fetching-with-react-server-components">Introducing Zero - Bundle-Size React Server Components</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js 시작하기 (2)]]></title>
            <link>https://velog.io/@gaebar_top/Next.js-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-2</link>
            <guid>https://velog.io/@gaebar_top/Next.js-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-2</guid>
            <pubDate>Wed, 14 Feb 2024 11:49:08 GMT</pubDate>
            <description><![CDATA[<h1 id="nextjs를-이용한-사이트-완성하기">Next.js를 이용한 사이트 완성하기</h1>
<h2 id="1-image-컴포넌트">1. Image 컴포넌트</h2>
<pre><code class="language-jsx">import Image from &#39;next/image&#39;;

export default function Test() {
  return (
    &lt;&gt;
      &lt;Image
        src=&quot;/images/product.jpeg&quot;
        width=&quot;400&quot;
        height=&quot;400&quot;
        alt=&quot;상품 이미지&quot;
      /&gt;
    &lt;/&gt;
  );
}</code></pre>
<hr>

<h2 id="2-image-컴포넌트-사용법">2. Image 컴포넌트 사용법</h2>
<pre><code class="language-jsx">import Image from &#39;next/image&#39;;

export default function Test() {
  return (
    &lt;&gt;
      &lt;div style={{
          position: &#39;relative&#39;,
          width: &#39;50%&#39;,
          height: &#39;200px&#39;,
        }}&gt;
        &lt;Image
          fill
          src=&quot;/images/product.jpeg&quot;
          alt=&quot;상품 이미지&quot;
          style={{
            objectFit: &#39;cover&#39;,
          }}
        /&gt;
      &lt;/div&gt;
    &lt;/&gt;
  );
}

// next.config.js
...
    },
    images: {
      remotePatterns: [
        {
          protocol: &#39;https&#39;,
          hostname: &#39;example.com&#39;, // 설정해주어야 한다
          port: &#39;&#39;,
          pathname: &#39;/account123/**&#39;, // 설정해주어야 한다
        },
      ],
    },
...</code></pre>
<p><code>public</code> 폴더가 아닌 외부의 이미지를 사용할 때는 <code>next.config.js</code>를 설정해 주어야 한다.</p>
<hr>

<h2 id="3-head-컴포넌트">3. Head 컴포넌트</h2>
<pre><code class="language-jsx">// 페이지 제목 및 아이콘 설정(모든 페이지에 동일하게 적용시킬 때는 _app.js에서 작성)
import Head from &#39;next/head&#39;;
...

export default function App({ Component, pageProps }) {
  return (
    &lt;&gt;
      &lt;Head&gt;
        &lt;title&gt;제목&lt;/title&gt;
        &lt;link rel=&quot;icon&quot; href=&quot;/favicon.ico&quot; /&gt; // public 폴더 내 파일
      &lt;/Head&gt;
      &lt;ThemeProvider&gt;
        &lt;Header /&gt;
        &lt;Container&gt;
          &lt;Component {...pageProps} /&gt;
        &lt;/Container&gt;
      &lt;/ThemeProvider&gt;
    &lt;/&gt;
  );
}</code></pre>
<hr>

<h2 id="4-구글-폰트-적용하기">4. 구글 폰트 적용하기</h2>
<p>Next.js에서는 구글 폰트를 위한 기능도 제공한다. 구글 폰트를 편하게 쓰는 것뿐만 아니라 여러 가지 최적화도 함께 제공한다. 예를 들어 Noto Sans KR이라는 폰트를 적용해 본다고 가정하자. <code>_app.js</code> 파일에서 적용을 할 때, <code>@next/font/google</code>이라는 패키지에서 <code>Noto_Sans_KR</code>을 임포트하고 객체를 만든다.</p>
<pre><code class="language-jsx">import { Noto_Sans_KR } from &#39;@next/font/google&#39;;

const notoSansKR = Noto_Sans_KR({
  weight: [&#39;400&#39;, &#39;700&#39;],
  subsets: [],
});</code></pre>
<p><code>weight</code> 프로퍼티에는 사용할 폰트 굵기를 적는다. 굵기 400, 700을 갖는 폰트를 쓴다. 주의할 점은 숫자형태가 아닌 문자열(<code>&#39;400&#39;</code>, <code>&#39;700&#39;</code> 같은 형태)라는 것이다.</p>
<p><code>subsets</code>는 빈 배열로 했는데, 서브셋이라는 건 폰트에서 영문, 한글 이런 식으로 사용할 글자들만 골라서 사용할 때 쓰는 건데, 일단 전부 다 사용하는 걸로 설정했다. 만약에 영문만 사용하는 폰트라면 <code>[&#39;latin&#39;]</code>과 같이 작성하면 된다.</p>
<p>폰트를 적용하려면 <code>notoSansKR</code> 객체의 <code>className</code> 프로퍼티를 사용할 수 있는데, 이 클래스가 적용된 요소 안에서는 폰트를 적용하게 된다.</p>
<pre><code class="language-jsx">&lt;main className={notoSansKR.className}&gt;
  ...
&lt;/main&gt;</code></pre>
<p>아니면 Next.js에서 제공하는 <code>Head</code> 컴포넌트를 활용해서 전역 스타일로 적용할 수도 있다.</p>
<pre><code class="language-jsx">&lt;Head&gt;
  &lt;style&gt;{`
    html {
      font-family: ${notoSansKR.style.fontFamily}, sans-serif;
    }
  `}&lt;/style&gt;
&lt;/Head&gt;</code></pre>
<p>폰트를 적용하고 개발자 도구를 열어서 Network 탭의 세부 탭인 Font 탭을 살표보면 구글 사이트가 아니라, 해당 사이트에서 폰트 파일을 받아오는 것을 알 수 있다. 그래서 초기 로딩 속도가 훨씬 빠르다.</p>
<hr>

<h2 id="5-빌드하기와-실행하기">5. 빌드하기와 실행하기</h2>
<p>리액트 코드를 브라우저가 실행할 수 있게 변환해야 하며, 서버 실행에 필요한 코드들을 생성해야 한다.</p>
<pre><code>npm run build
npm run start (npm start)</code></pre><blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/a38276a2-cdd7-481a-91e1-5365cec10649/image.png" alt=""></p>
</blockquote>
<hr>

<h2 id="6-vercel-배포하기">6. Vercel 배포하기</h2>
<ol>
<li><a href="https://vercel.com/">Vercel</a> 홈페이지로 가서 Sign up 버튼을 누르고 GitHug으로 가입한다.</li>
<li>가입 후 팝업이 뜨면 Install 버튼을 눌러 설치한다.</li>
<li>만들어놓은 Next.js 프로젝트 레포지토리를 임포트한다. 여러가지 설정을 할 수 있지만 우선 Deploy를 눌러 배포한다.</li>
<li>Continue to Dashboard를 눌러 대시보드로 이동한다.</li>
<li>DOMAINS에 적혀 있는 주소로 접속하면 배포된 앱을 확인할 수 있다.</li>
<li>오른쪽 위에 <code>View Build Logs</code> 버튼을 눌러 배포된 과정을 살펴본다. Deployment Status &gt; Building을 보면 터미널 기록 같은 것을 볼 수 있다. 서버에서 어떤 실행을 했는지 보여준다.</li>
<li>새로 배포를 하려면 메인 브랜치에 코드를 푸시하기만 하면 된다.<hr>

</li>
</ol>
<h2 id="7-정리">7. 정리</h2>
<h3 id="1-image-컴포넌트-1">(1) <code>&lt;Image&gt;</code> 컴포넌트</h3>
<p>Next.js에서는 이미지를 사용할 때 Next.js 서버를 한 번 거쳐서 이미지 최적화를 한 다음 사용할 수 있도록 해주고 있다. 그래서 되도록이면 <code>&lt;img&gt;</code> 태그보다는 <code>&lt;Image&gt;</code> 컴포넌트를 사용하는걸 권장한다. <code>&lt;Image&gt;</code> 컴포넌트는 <code>next/image</code>에서 불러와서 사용한다. 이때 반드시 크기가 있어야 한다. <code>width</code>와 <code>height</code> 값을 지정하거나 또는 <code>fill</code>이라는 Prop을 사용할 수 있다.</p>
<h4 id="1-width와-height를-사용하는-경우">1. width와 height를 사용하는 경우</h4>
<p><code>&lt;img&gt;</code> 태그와 마찬가지로 너비와 높이를 지정해 준다.</p>
<pre><code class="language-jsx">import Image from &#39;next/image&#39;;

export default function Page() {
  return (
    &lt;&gt;
      &lt;Image
        src=&quot;/images/product.jpeg&quot;
        width={300}
        height={300}
        alt=&quot;상품 이미지&quot;
      /&gt;
    &lt;/&gt;
  );
}</code></pre>
<h4 id="2-fill을-사용하는-경우">2. fill을 사용하는 경우</h4>
<p>이미지 크기를 유연하게 지정해야 할 때는 <code>fill</code>을 사용한다. 이때 부모 요소에서 <code>position: relative</code>와 같이 포지셔닝해야 한다. <code>fill</code>을 사용할 경우 <code>absolute</code> 포지션으로 배치되기 때문에, &quot;가장 가까운 포지셔닝이 된&quot; 조상 요소에 꽉 차도록 이미지가 배치된다. 만약 이미지의 비율이 깨지는 것을 막고 싶다면 <code>object-fit: cover</code> 속성으로 이미지를 크롤하면 된다.</p>
<pre><code class="language-jsx">import Image from &#39;next/image&#39;;

export default function Page() {
  return (
    &lt;&gt;
     &lt;div
        style={{
          position: &#39;relative&#39;,
          width: &#39;100%&#39;,
          height: &#39;300px&#39;,
        }}
      &gt;
        &lt;Image
          src=&quot;/images/product.jpeg&quot;
          fill
          alt=&quot;상품 이미지&quot;
          style={{
            objectFit: &#39;cover&#39;,
          }}
        /&gt;
      &lt;/div&gt;
    &lt;/&gt;
  );
}</code></pre>
<h3 id="2-head-컴포넌트">(2) <code>&lt;Head&gt;</code> 컴포넌트</h3>
<p><code>next/head</code>에서 불러와서 <code>&lt;Head&gt;</code> 컴포넌트 안에 <code>&lt;head&gt;</code> 태그에 넣고 싶은 코드를 작성하면 된다.</p>
<pre><code class="language-jsx">import Head from &#39;next/head&#39;;

export default function Page() {
  return (
    &lt;&gt;
      &lt;Head&gt;
        &lt;title&gt;페이지 제목&lt;/title&gt;
        &lt;link rel=&quot;icon&quot; href=&quot;/favicon.ico&quot; /&gt;
      &lt;/Head&gt;
      ...
    &lt;/&gt;
  );
}</code></pre>
<p>만약 사이트 전체에 공통적으로 적용하고 싶다면, <code>/pages/_app.js</code> 파일에서 <code>&lt;Head&gt;</code> 컴포넌트를 활용하면 된다.</p>
<pre><code class="language-jsx">import Head from &#39;next/head&#39;;

export default function App({ Component, pageProps }) {
  return (
    &lt;&gt;
      &lt;Head&gt;
        &lt;title&gt;사이트 기본 제목&lt;/title&gt;
        &lt;link rel=&quot;icon&quot; href=&quot;/favicon.ico&quot; /&gt;
      &lt;/Head&gt;
      &lt;Component {...pageProps} /&gt;
    &lt;/&gt;
  );
}</code></pre>
<h3 id="3-개발모드-빌드-실행-그리고-배포">(3) 개발모드, 빌드, 실행 그리고 배포</h3>
<h4 id="1-개발-서버-켜기">1. 개발 서버 켜기</h4>
<p>개발 모드이기 때문에 새로고침 없이도 수정 사항을 그때그때 확인할 수 있다.</p>
<pre><code>npm run dev</code></pre><h4 id="2-빌드하기">2. 빌드하기</h4>
<p>Next.js 프로젝트를 배포하려면 우선 실행 가능한 형태로 코드를 변환한 다음에, 서버에서 실행을 해야 하는데, 이러한 과정을 &quot;빌드&quot;라고 한다.</p>
<pre><code>npm run build</code></pre><p><code>.next</code> 폴더에 실행에 필요한 파일들이 생성된다.</p>
<h4 id="3-실행하기">3. 실행하기</h4>
<pre><code>npm run start</code></pre><h4 id="4-배포하기">4. 배포하기</h4>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js 시작하기 (1)]]></title>
            <link>https://velog.io/@gaebar_top/Next.js-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-1</link>
            <guid>https://velog.io/@gaebar_top/Next.js-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-1</guid>
            <pubDate>Wed, 14 Feb 2024 08:16:55 GMT</pubDate>
            <description><![CDATA[<h1 id="라우팅">라우팅</h1>
<h2 id="1-파일시스템-기반-라우팅이란">1. 파일시스템 기반 라우팅이란?</h2>
<p>라우팅이란, 어떤 주소에 어떤 페이지를 보여줄지 정하는 것을 의미한다. 파일시스템 기반 라우팅은 파일의 경로가 주소에 매칭되는 라우팅 방식이다.</p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/976378bd-cf28-4e28-99f8-40f0ecdbd7df/image.png" alt=""></p>
</blockquote>
<hr>

<h2 id="2-페이지-나누기">2. 페이지 나누기</h2>
<p><code>pages</code> 폴더 안의 파일들이 곧 페이지가 된다. <code>pages</code> 폴더명은 Next.js에서 지정한 폴더명이기 때문에 변경해서는 안된다. 다이나믹 라우팅을 사용하고 싶을 때는 파일명(폴더명)에 <code>[id].js</code> 형태로 작성하면 된다.</p>
<hr>

<h2 id="3-link-컴포넌트">3. Link 컴포넌트</h2>
<pre><code class="language-jsx">import Link from &#39;next/link&#39;;

export default function Home() {
  return (
    &lt;&gt;
      &lt;ul&gt;
        &lt;li&gt;
          &lt;Link href=&quot;/products/1&quot;&gt;첫 번째 상품&lt;/Link&gt;
        &lt;/li&gt;
        &lt;li&gt;
          &lt;Link href=&quot;/products/2&quot;&gt;두 번째 상품&lt;/Link&gt;
        &lt;/li&gt;
        &lt;li&gt;
          &lt;Link href=&quot;/products/3&quot;&gt;세 번째 상품&lt;/Link&gt;
        &lt;/li&gt;
        &lt;li&gt;
          &lt;Link href=&quot;https://naver.com&quot;&gt;네이버&lt;/Link&gt;
        &lt;/li&gt;
      &lt;/ul&gt;
    &lt;/&gt;
  )
}</code></pre>
<hr>

<h2 id="4-userouter--쿼리-사용하기4">4. useRouter : 쿼리 사용하기4</h2>
<pre><code class="language-jsx">// [id].js
import { useRouter } from &#39;next/router&#39;;

export default function Product() {
  const router = useRouter();
  // 파일명의 id
  const { id } = router.query;

  return &lt;div&gt;Product 페이지&lt;/div&gt;;
}

// search.js
import { useRouter } from &#39;next/router&#39;;

export default function Search() {
  const router = useRouter();
  // /q?=값
  const { q } = router.query;

  return (
    &lt;div&gt;
      &lt;h1&gt;Search 페이지&lt;/h1&gt;
      &lt;h2&gt;{q} 검색 결과&lt;/h2&gt;
    &lt;/div&gt;
  );
}</code></pre>
<hr>

<h2 id="5-userouter--페이지-이동하기">5. useRouter : 페이지 이동하기</h2>
<pre><code class="language-jsx">// pages 폴더 밖에 폴더 생성
// components/SearchForm.js
import ( useRouter } from &#39;next/router&#39;;
import { useState } from &#39;react&#39;;

export default function SearchForm({ initialValue = &#39;&#39; }) {
  const router = useRouter();
  const [value, setValue] = useState(&#39;&#39;);

  function handleChange(e) {
    setValue(e.target.value);
  };

  function handleSubmit(e) {
    e.preventDefault();
    if (!value) {
      router.push(&#39;/&#39;);
      return;
    }

    router.push(`/search?q=${value}`);
  };

  return (
    &lt;form onSubmit={handleSubmit}&gt;
      &lt;input name=&#39;q&#39; value={value} onChange={handleChange} /&gt;
      &lt;button&gt;검색&lt;/button&gt;
    &lt;/form&gt;
  );
}</code></pre>
<hr>

<h2 id="6-api-연동하기">6. API 연동하기</h2>
<pre><code class="language-jsx">import { useEffect, useState } from &#39;react&#39;;
import MovieList from &#39;@/components/MovieList&#39;;
import SearchForm from &#39;@/components/SearchForm&#39;;
import styles from &#39;@/styles/Home.module.css&#39;;
import Header from &#39;@/components/Header&#39;;
import Container from &#39;@/components/Container&#39;;
import axios from &#39;@/lib/axios&#39;;

export default function Home() {
  const [movies, setMovies] = useState([]);

  async function getMovies() {
    const res = await axios.get(&#39;/movies/&#39;);
    const movies = res.data.results ?? [];
    setMovies(movies);
  }

  useEffect(() =&gt; {
    getMovies();
  }, []);

  return (
    &lt;&gt;
      &lt;Header /&gt;
      &lt;Container page&gt;
        &lt;SearchForm /&gt;
        &lt;MovieList className={styles.movieList} movies={movies} /&gt;
      &lt;/Container&gt;
    &lt;/&gt;
  );
}</code></pre>
<hr>

<h2 id="7-리다이렉트">7. 리다이렉트</h2>
<p><code>next.config.js</code> 파일은 Next.js 서버를 설정하는 파일이다.</p>
<pre><code class="language-jsx">/** @type {import(&#39;next&#39;).NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  async redirects() {
    return [
      {
        source: &quot;/products/:id&quot;, // 바꾸기 전 주소
        destination: &quot;/items/:id&quot;, // 바뀐 주소
        permanent: true, // 리다이렉트를 저장할 지 결정
      },
    ]
  },
}

module.exports = nextConfig;</code></pre>
<hr>

<h2 id="8-커스텀-404-페이지">8. 커스텀 404 페이지</h2>
<pre><code class="language-jsx">import ButtonLink from &quot;@/components/ButtonLink&quot;;
import Container from &quot;@/components/Container&quot;;
import Header from &quot;@/components/Header&quot;;
import styles from &quot;@/styles/NotFound.module.css&quot;;

export default function NotFound() {
  return (
    &lt;&gt;
      &lt;Header /&gt;
      &lt;Container&gt;
        &lt;div className={styles.notFound}&gt;
          &lt;div className={styles.content}&gt;
            찾을 수 없는 페이지입니다.
            &lt;br /&gt;
            요청하신 페이지가 사라졌거나, 잘못된 경로를 이용하셨어요 :)
          &lt;/div&gt;
          &lt;ButtonLink className={styles.button} href=&quot;/&quot;&gt;
            홈으로 이동
          &lt;/ButtonLink&gt;
        &lt;/div&gt;
      &lt;/Container&gt;
    &lt;/&gt;
  );
}</code></pre>
<hr>

<h2 id="9-커스텀-app과-document">9. 커스텀 App과 Document</h2>
<pre><code class="language-jsx">// _app.js 파일은 공통된 레이아웃을 구현할 때 작성하면 된다.
import Container from &quot;@/components/Container&quot;;
import Header from &quot;@/components/Header&quot;;
import &quot;@/styles/global.css&quot;;

export default function App({ Component, pageProps }) {
  return (
    &lt;&gt;
      &lt;Header /&gt;
      &lt;Container&gt;
        &lt;Component {...pageProps} /&gt;
      &lt;/Container&gt;
    &lt;/&gt;
  );
}</code></pre>
<hr>

<h2 id="10-context-활용하기">10. Context 활용하기</h2>
<pre><code class="language-jsx">import { createContext, useContext, useEffect, useState } from &#39;react&#39;;

export const ThemeContext = createContext();

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState(&#39;dark&#39;);

  useEffect(() =&gt; {
    document.body.classList.add(theme);

    return () =&gt; {
      document.body.classList.remove(theme);
    }
  }, [theme]);

  return (
    &lt;ThemeContext.Provider value={{ theme, setTheme }}&gt;
      {children}
    &lt;/ThemeContext.Provider&gt;
  );
}

export function useTheme() {
  const themeContext = useContext(ThemeContext);
  if (!themeContext) {
    throw new Error(&#39;ThemeContext 안에서 써야 합니다&#39;);
  }

  return themeContext;
}</code></pre>
<hr>

<h2 id="11-api-라우팅">11. API 라우팅</h2>
<p>Next.js에서는 페이지를 만드는 것처럼 간단하게 백엔드 API를 만들 수 있다. 사실상 작은 Node.js 서버를 구현할 수 있는 것이다. 우선 <code>/pages</code> 폴더 아래에 <code>/api</code>라는 폴더를 만들고, 특별한 형태의 자바스크립트 파일을 추가하면 된다.</p>
<pre><code class="language-jsx">// /pages/api/cart/js
let cart = [];

export default function handler(req, res) {
  if (req.method === &#39;GET&#39;) {
    return res.status(200).json(cart);
  } else if (req.method === &#39;PUT&#39;) {
    cart = req.body;
    return res.status(200).json(cart);
  } else {
    return res.sendStatus(404);
  }
}</code></pre>
<p>default export로 request 객체(<code>req</code>)와 response 객체(<code>res</code>)를 파라미터로 받는 함수를 만들면 된다. request 객체와 response 객체는 Node.js의 request 객체와 response 객체이다.</p>
<p>GET request를 보냈을 때 <code>cart</code> 배열을 response로 보내 주고, PUT request를 보냈을 때 <code>cart</code> 배열을 수정하는 코드이다. 이 API의 주소는 Next.js에서 페이지를 만들었을 때의 주소와 마찬가지이다. <code>/api/cart.js</code>라는 경로이니까 <code>/api/cart</code>라는 주소로 request를 보내면 파일에 있는 핸들러 함수를 실행해서 response를 보내주는 형태이다.</p>
<p>웹 브라우저에서 <code>http://localhost:3000/api/cart</code>라는 주소로 접속하거나, API 테스트를 해보면 아래와 같은 JSON 데이터가 response로 전달될 것이다.</p>
<pre><code>GET http://localhost:3000/api/cart
Content-Type: application/json</code></pre><p>GET request를 보내고 받은 response 예시</p>
<pre><code>[]</code></pre><p>PUT request 보낼 때</p>
<pre><code>PUT http://localhost:3000/api/cart
Content-Type: application/json

[1, 2, 3]</code></pre><p>PUT request를 보내고 받은 response 예시</p>
<pre><code>[1, 2, 3]</code></pre><p>requset 객체를 활용하면 requset의 헤더나 쿠키 같은 값을 사용해서 다양한 동작을 하도록 만들 수 있다. 더 궁금한 점이 있다면 <a href="https://nextjs.org/docs/pages/building-your-application/routing/api-routes">Next.js - API Routes</a>를 참고해 보면 좋다.</p>
<hr>

<h2 id="12-라우팅-정리">12. 라우팅 정리</h2>
<h3 id="1-link-컴포넌트">(1) <code>&lt;Link&gt;</code> 컴포넌트</h3>
<p>Next.js에서는 링크를 연결하는데 <code>&lt;a&gt;</code> 태그 대신에 <code>&lt;Link&gt;</code> 컴포넌트를 사용한다. <code>&lt;a&gt;</code> 태그를 사용하면 페이지를 이동할 때 페이지 전체를 다시 로딩하기 때문에 속도가 느리고, 빈 화면이 잠깐 보이면서 깜빡거림이 생기지만, <code>&lt;Link&gt;</code> 컴포넌트는 Next.js에서 내부적으로 여러 가지 최적화를 해주기 때문에 빠르고 부드러운 페이지 전환이 가능하다.</p>
<pre><code class="language-jsx">import Link from &#39;next/link&#39;;

export default Page() {
  return &lt;Link href=&quot;/&quot;&gt;홈페이지로 이동&lt;/Link&gt;;
}</code></pre>
<h3 id="2-userouter-hook">(2) useRouter() Hook</h3>
<h4 id="1-쿼리-사용하기">1. 쿼리 사용하기</h4>
<p><code>router.query</code> 값을 사용하면 페이지 주소에서 Params 값이나 쿼리스트링 값을 참조할 수 있다. 예를 들면 <code>pages/products/[id].js</code> 페이지에서 <code>router.query[&#39;id&#39;]</code> 값으로 Params <code>id</code>에 해당하는 값을 가져올 수 있다.</p>
<pre><code class="language-jsx">import { useRouter } from &#39;next/router&#39;;

export default function Product() {
  const router = useRouter();
  const id = router.query[&#39;id&#39;];

  return &lt;&gt;Product #{id} 페이지&lt;/&gt;;
}</code></pre>
<p><code>/search?q=티셔츠</code>와 같은 주소로 들어왔을 때, <code>router.query[&#39;q&#39;]</code> 값으로 쿼리스트링 <code>q</code>에 해당하는 값을 가져올 수도 있다.</p>
<pre><code class="language-jsx">import { useRouter } from &#39;next/router&#39;;

export default function Search() {
  const router = useRouter();
  const q = router.query[&#39;q&#39;];

  return &lt;&gt;{q} 검색 결과&lt;/&gt;;
}</code></pre>
<h4 id="2-페이지-이동하기">2. 페이지 이동하기</h4>
<p><code>router.push()</code> 함수를 사용하면 코드로 페이지를 이동할 수 있다.</p>
<pre><code class="language-jsx">import { useState } from &#39;react&#39;;
import { useRouter } from &#39;next/router&#39;;

export default function SearchForm() {
  const [value, setValue] = useState();
  const router = useRouter();

  function handleChange(e) {
    setValue(e.target.value);
  }

  function handleSubmit(e) {
    e.preventDefault();
    if (!value) {
      return router.push(&#39;/&#39;);
    }
    return router.push(`/search?q=${value}`);
  }

  return (
    &lt;form onSubmit={handleSubmit}&gt;
      &lt;input name=&quot;q&quot; value={value} onChange={handleChange} /&gt;
      &lt;button&gt;검색&lt;/button&gt;
    &lt;/form&gt;
  );
}</code></pre>
<h3 id="3-리다이렉트">(3) 리다이렉트</h3>
<p><code>next.config.js</code> 파일을 수정하면 특정 주소에 대해서 리다이렉트할 주소를 지정할 수 있다. 예를 들어 <code>/products:id</code>라는 주소로 들어오면 <code>/items/:id</code>라는 주소로 이동시켜 줄 수 있다.</p>
<p>이때 <code>permanent</code>라는 속성으로 상태 코드를 정할 수 있다. <code>permanent: false</code>로 하면 307 Temporary Redirect를 하고, <code>permanent: true</code>로 하면 브라우저에 리다이렉트 정보를 저장하는 308 Permanent Redirect를 할 수 있다.</p>
<pre><code class="language-jsx">
/** @type {import(&#39;next&#39;).NextConfig} */
const nextConfig = {
  async redirects() {
    return [
      {
        source: &#39;/products/:id&#39;,
        destination: &#39;/items/:id&#39;,
        permanent: true,
      },
    ];
  },
}

module.exports = nextConfig;</code></pre>
<h3 id="4-커스텀-404-페이지">(4) 커스텀 404 페이지</h3>
<p>존재하지 않는 주소로 들어올 경우에 Next.js에서는 기본적으로 404 페이지를 보여준다. 원하는 404 페이지를 보여주려면, <code>pages.NotFound.js</code> 파일을 만들고 일반적인 페이지처럼 구현하면 된다.</p>
<h3 id="5-커스텀-app">(5) 커스텀 App</h3>
<p>모든 페이지에 공통적으로 코드를 적용하고 싶다면 커스텀 <code>App</code> 컴포넌트를 수정하면 된다. <code>pages/_app.js</code> 파일에 있는 컴포넌트이다. 이 컴포넌트에 사이트 전체에서 보여줄 컴포넌트나 전체적으로 적용할 리액트 컨텍스트를 적용할 수 있다. 그리고 사이트 전체에 적용할 CSS 파일도 여기서 임포트할 수 있다.</p>
<p>커스텀 <code>App</code> 컴포넌트의 Props는 <code>Component</code>와 <code>pageProps</code>가 있다. 우리가 만든 페이지들이 <code>Component</code> Prop으로 전달되고 여기에 내부적으로 필요한 Props는 <code>pageProps</code>라는 값으로 전달된다.</p>
<pre><code class="language-jsx">import Header from &#39;@/components/Header&#39;;
import { ThemeProvider } from &#39;@/lib/ThemeContext&#39;;
import &#39;@/styles/globals.css&#39;;

export default function App({ Component, pageProps }) {
  return (
    &lt;ThemeProvider&gt;
      &lt;Header /&gt;
      &lt;Component {...pageProps} /&gt;
    &lt;/ThemeProvider&gt;
  );
}</code></pre>
<h3 id="6-커스텀-document">(6) 커스텀 Document</h3>
<p><code>pages/_document.js</code> 파일에 있는 <code>Document</code> 컴포넌트는 HTML 코드의 뼈대를 수정하는 용도로 사용한다. 코드는 React 컴포넌트이지만 일반적인 컴포넌트처럼 동작하지 않기 때문에 <code>useState</code>나 <code>useEffect</code>처럼 브라우저에서 실행이 필요한 기능들은 사용할 수 없다.</p>
<pre><code class="language-jsx">import { Html, Head, Main, NextScript } from &#39;next/document&#39;

export default function Document() {
  return (
    &lt;Html lang=&quot;ko&quot;&gt;
      &lt;Head /&gt;
      &lt;body&gt;
        &lt;Main /&gt;
        &lt;NextScript /&gt;
      &lt;/body&gt;
    &lt;/Html&gt;
  )
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[위클리 패이퍼 (12)]]></title>
            <link>https://velog.io/@gaebar_top/%EC%9C%84%ED%81%B4%EB%A6%AC-%ED%8C%A8%EC%9D%B4%ED%8D%BC-12</link>
            <guid>https://velog.io/@gaebar_top/%EC%9C%84%ED%81%B4%EB%A6%AC-%ED%8C%A8%EC%9D%B4%ED%8D%BC-12</guid>
            <pubDate>Sun, 11 Feb 2024 05:45:32 GMT</pubDate>
            <description><![CDATA[<h1 id="12주차-위클리-페이퍼">12주차 위클리 페이퍼</h1>
<h2 id="q1-javascript만-사용하는-것과-비교해-typescript를-사용하는-이유에-대해-설명해-주세요">Q1) JavaScript만 사용하는 것과 비교해 TypeScript를 사용하는 이유에 대해 설명해 주세요.</h2>
<h3 id="1-타입스크립트란">(1) 타입스크립트란?</h3>
<p>타입스크립트는 2012년 마이크로소프트에서 자바스크립트 기반의 정적 타입 문법을 추가한 &#39;프로그래밍 언어&#39;이다. 타입스크립트는 개발자가 가장 좋아하고 관심을 가지고 있는 프로그래밍 순위 5위에 오르기도 했다. 타입스크립트는 자바스크립트의 슈퍼 셋, 즉 상위 확장자로 자바스크립트 엔진을 사용하며 자신이 원하는 변수의 타입을 정의하고 프로그래밍을 하면 자바스크립트로 컴파일되어 실행할 수 있다. 타입스크립트는 자바스크립트와 달리 브라우저에서 실행하려면 파일을 한번 변환해 주어야 하는데 이러한 변환 과정을 컴파일이라고 부른다.</p>
<h3 id="2-타입스크립트를-사용하는-이유">(2) 타입스크립트를 사용하는 이유</h3>
<p>우선 타입스크립트를 사용하는 가장 큰 이유는 언어에서 알 수 있듯이 타입 때문이다. 타입이란, 한글로 자료형이라고 볼 수 있다. 어떠한 변수가 숫자인지, 문자인지 불린형인지에 관한 추상 형태를 의미힌다. 타입스크립트의 정적 타입 기반, 즉 컴파일을 하는 과정에서 타입을 결정하게 된다. 타입스크립트는 컴파일 과정에서 타입을 지정하기 때문에 컴파일 에러를 예방할 수 있을 뿐만 아니라, 손쉬운 디버깅이 가능해진다.</p>
<p>또한 타입스크립트는 높은 생산성을 제공한다. 자바스크립트로 코드를 작성할 때, 객체의 필드나 함수의 매개변수로 들어오는 값이 무엇인지 알기 위해 여러 파일을 살펴야 했지만 타입스크립트를 사용한다면 변수의 이름뿐만 아니라 그 데이터의 자료형까지 알 수 있게 된다.</p>
<h2 id="q2-typescript의-동작-원리에-대해-설명해-주세요">Q2) TypeScript의 동작 원리에 대해 설명해 주세요.</h2>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/7ee5fde8-8817-4388-863a-99d3ba28371d/image.png" alt=""></p>
</blockquote>
<p>작성한 타입스크립트 코드는 타입스크립트 컴파일러(tsc)를 통해 파싱하여 타입스크립트 AST 코드로 변환된다. AST는 Abstract Syntax tree의 약자로, 추상화 문법트리라는 의미를 갖는다. 프로그래밍 언어(고급언어)를 컴파일러를 통해 파싱하여서 AST의 구조형태의 코드로 만들어지게 된다. 그 이후 타입 검사기(Typechecker)를 통해 파싱된 타입스크립트 AST 코드의 타입을 체크하고, 타입스크립트 AST 코드를 자바스크립트 코드로 변환한다. 여기까지의 과정들은 타입스크립트 컴파일러(tsc)에 의해 수행된다. 이후로 자바스크립트 코드를 자바스크립트 AST 코드로 파싱하고 자바스크립트 AST를 바이트 코드로 변환한다. 이 때 런타임(runtime)이라는 실행환경에서 바이트 코드를 실행한다. 여기까지의 과정은 자바스크립트 런타인에 의해 수행된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React에 TypeScript 적용하기(2)]]></title>
            <link>https://velog.io/@gaebar_top/React%EC%97%90-TypeScript-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B02</link>
            <guid>https://velog.io/@gaebar_top/React%EC%97%90-TypeScript-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B02</guid>
            <pubDate>Wed, 07 Feb 2024 07:48:47 GMT</pubDate>
            <description><![CDATA[<h1 id="react에-typescript-적용하기">React에 TypeScript 적용하기</h1>
<h2 id="1-react와-typescript">1. React와 TypeScript</h2>
<p><code>tsconfig.json</code> 파일에서 <code>compilerOptions</code>에서 <code>jsx</code> 옵션은 타입스크립트에서 jsx 문법으로 작성된 파일들을 변환할 때 어떤 형태로 변환할지 정하는 옵션이다.</p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/a2e42706-7562-4407-987f-1c5f20caa3b9/image.png" alt=""></p>
</blockquote>
<p>즉, 타입스크립트에서 리액트를 사용할 때 트랜스파일링 과정이 필요하며, 타입스크립트 컴파일러가 JSX를 JS로 바꿀지, JSX를 그대로 둘지는 <code>tsconfig.json</code> 파일에서 설정할 수 있다.</p>
<hr>

<h2 id="2-html-dom">2. HTML DOM</h2>
<pre><code class="language-ts">const usernameInput = document.getElementById(&#39;username&#39;) as HTMLInputElement;
const submitButton = document.getElementById(&#39;submit&#39;) as HTMLButtonElement;

usernameInput.focus();
submitButton.addEventListener(&#39;click&#39;, handleClick);

function handleClick(e: MouseEvent) {
  e.preventDefault();
  const message = `${usernameInput.value}님 반갑습니다!`;
  alert(message);
}</code></pre>
<hr>

<h2 id="3-컴포넌트">3. 컴포넌트</h2>
<pre><code class="language-ts">// Button.tsx
import styles from &#39;./Button.module.css&#39;;

interface Props {
  className?: string;
  id?: string;
  children?: ReactNode;
  onClick: any;
}

export default function Button({ className = &#39;&#39;, id, children, ...rest }: Props) {
  const classNames = `${styles.button} ${className}`;
  return (
    &lt;button className={classNames} id={id} onClick={onClick}&gt;
      {children}
    &lt;/button&gt;
  );
}

// Input.tsx
import styles from &#39;./Input.module.css&#39;;

interface Props extends InputHTMLAttributes&lt;HTMLInputElement&gt; {}

export default function Input({ className = &#39;&#39;, ...rest }: Props) {
  const classNames = `${styles.input} ${className}`;
  return &lt;input className={classNames} {...rest} /&gt;;
}</code></pre>
<hr>

<h2 id="4-hook">4. Hook</h2>
<pre><code class="language-ts">const formRef = useRef&lt;HTMLFormElement&gt;(null);</code></pre>
<p>useState의 경우에는 타입 추론이 일반적으로 잘 되지만, 그렇지 않을 경우에는 타입을 지정해 주어야 한다.</p>
<hr>

<h2 id="5-이벤트-핸들러">5. 이벤트 핸들러</h2>
<pre><code class="language-ts">function handleChange(e: ChangeEvent&lt;HTMLInputElement&gt;) {
  const { name, value } = e.target;
  const nextValues = {
    ...values,
    [name]: value,
  };
  setValues(nextValues);
}

function handleClick(e: MouseEvent&lt;HTMLButtonElement&gt;) { // SyntheticEvent
  e.preventDefault();
  const message = `${values.username}님 환영합니다`;
  alert(message);
}

// Button.tsx
...
interface Props {
  className?: string;
  id?: string;
  children?: ReactNode;
  onClick: (e: MouseEvent) =&gt; void; // MouseEventHandler&lt;HTMLButtonElement&gt;;
}
...</code></pre>
<p>이벤트를 구체적으로 지정하지 않아도 될 때는 <code>SyntheticEvent</code>를 지정해 주어도 된다.</p>
<hr>

<h2 id="6-context">6. Context</h2>
<pre><code class="language-ts">// translate.tsx
import { createContext, useContext, useState } from &#39;react&#39;;

type Locale = &#39;ko&#39; | &#39;en&#39;;

interface LocaleContextValue {
  locale: Locale;
  setLocale: (value: Locale) =&gt; void;
}

const LocaleContext = createContext&lt;LocaleContextValue&gt;({
  locale: &#39;ko&#39;,
  setLocale: () =&gt; {},
} as any);

export function LocaleContextProvider({ children }: {children: ReactNode;}) {
  const [locale, setLocale] = useState&lt;Locale&gt;(&#39;ko&#39;);

  return (
    &lt;LocaleContext.Provider
      value={{
        locale,
        setLocale,
      }}
    &gt;
      {children}
    &lt;/LocaleContext.Provider&gt;
  );
}

const dict = {
  ko: {
    signin: &#39;로그인&#39;,
    username: &#39;아이디&#39;,
    &#39;email or phone number&#39;: &#39;Email 또는 전화번호&#39;,
    password: &#39;비밀번호&#39;,
    &#39;forgot your password?&#39;: &#39;비밀번호를 잊으셨나요?&#39;,
    &#39;new user?&#39;: &#39;회원이 아니신가요?&#39;,
    signup: &#39;가입하기&#39;,
  },
  en: {
    signin: &#39;Signin&#39;,
    username: &#39;Username&#39;,
    &#39;email or phone number&#39;: &#39;Email or phone number&#39;,
    password: &#39;Password&#39;,
    &#39;forgot your password?&#39;: &#39;Forgot your password?&#39;,
    &#39;new user?&#39;: &#39;New user?&#39;,
    signup: &#39;Signup&#39;,
  },
};

function useLocale() {
  const { locale } = useContext(LocaleContext);
  return locale;
}

export function useSetLocale() {
  const { setLocale } = useContext(LocaleContext);
  return setLocale;
}

export function useTranslate(): (key: string) =&gt; string {
  const locale = useLocale();
  const t = (key: keyof typeof dict[Locale]) =&gt; dict[locale][key];
  return t;
}</code></pre>
<hr>

<h2 id="7-타입스크립트에서-파일을-inport하는-경우">7. 타입스크립트에서 파일을 inport하는 경우</h2>
<h3 id="1-css-파일을-위한-타입들">(1) CSS 파일을 위한 타입들</h3>
<p>리액트에서 사용하는 CSS 파일은 사실 리액트에서 처리하는게 아니라 번들러에서 처리해준다. 코드에 import 문법이 있으면 그걸 가지고 파일을 불러와서 따로 처리를 해주는 식이다. 실제로 브라우저에서 실행될 때는 이런 CSS를 불러오는 import 문법은 다른 코드로 변환된다.</p>
<p>그런데 타입스크립트에서 CSS를 import하는 문법을 쓰려면 문제가 생긴다. 자바스크립트 파일도 아니고, 타입스크립트 파일도 아니기 때문에 블러온 변수의 타입을 알 수 없기 때문이다. 그래서 이런 경우엔 <code>d.ts</code> 파일을 만들어서 타입을 정의해 준다.</p>
<p><code>declare module</code>이라는 문법은 모듈의 타입을 직접 정의하는 문법이다. 이 문법을 사용하면 아래와 같은 코드에 있는 <code>styles</code> 객체의 타입이 무엇인지 정의할 수 있게 된다.</p>
<pre><code class="language-ts">import styles from &#39;./Button.module.css&#39;;

function Button({ children }) {
  // 실제로 사용하는 경우
  return &lt;button className={styles.Button}&gt;{ children }&lt;/button&gt;;
}</code></pre>
<p>Create React App에서는 <code>module.css</code> 파일을 사용하면 클래스 이름을 <code>styles</code> 객체로 참조할 수 있다. <code>styles</code>는 문자열을 속성 값으로 하는 객체이다. 그래서 타입스크립트 프로젝트에서는 아래처럼 모듈의 타입을 정의해 주고 있다.</p>
<pre><code class="language-ts">declare module &#39;*.module.css&#39; {
  const classes: { readonly [key: string]: string };
  export default classes;
}

declare module &#39;*.module.scss&#39; {
  const classes: { readonly [key: string]: string };
  export default classes;
}

declare module &#39;*.module.sass&#39; {
  const classes: { readonly [key: string]: string };
  export default classes;
}</code></pre>
<p>default export가 <code>classes</code>라는 이름으로 되어 있는데 이게 곧 <code>styles</code>라고 사용하던 거라고 생각하면 된다. 그 타입은 <code>{ [key: string]: string }</code>처럼 지정이 되어 있다.아래쪽의 <code>*.module.scss</code>라던지 <code>*.module.sass</code>는 Sass라는 CSS 확장 언어의 타입들이다.</p>
<p>즉, 실제 CSS 파일의 처리는 번들러에서 해주지만, 타입스크립트에서는 CSS 모듈의 타입을 추론할 수 없기 때문에 타입 정의를 직접 해줘야 한다. 이런 타입 정의만 있으면 무조건 CSS 파일을 쓸 수 있는 건 아니고, 번들러의 설정이 필요하다. 이 부분은 Create React App이나 Vite, Next.js 같은 것들이 대신 해주고 있다.</p>
<h3 id="2-이미지-파일을-위한-타입들">(2) 이미지 파일을 위한 타입들</h3>
<p>마찬가지로 이미지 파일을 리액트 프로젝트에서 사용할 수 있게 해주는 것도 번들러이다. 하지만 타입스크립트에서는 import문에 타입이 필요하기 때문에 <code>declare module</code>이라는 문법으로 타입을 지정해주고 있다. Create React App에서는 이미지 파일들을 import하면 이미지 주소를 문자열로 쓸 수 있다.</p>
<pre><code class="language-ts">declare module &#39;*.bmp&#39; {
  const src: string;
  export default src;
}

declare module &#39;*.gif&#39; {
  const src: string;
  export default src;
}

declare module &#39;*.jpg&#39; {
  const src: string;
  export default src;
}

declare module &#39;*.jpeg&#39; {
  const src: string;
  export default src;
}

declare module &#39;*.png&#39; {
  const src: string;
  export default src;
}
...</code></pre>
<p>참고로 항상 이렇게 되는 것은 아니다. Next.js에서는 이미지를 불러오면 객체 타입이다. 그래서 Next.js에서는 아래처럼 내부적으로 정의한 <code>StaticImageData</code>라는 타입으로 정의하고 있다. 실제로는 import했을 때 처리하는 건 Next.js에서 내부적으로 구현한 기능들이다.</p>
<pre><code class="language-ts">declare module &#39;*.jpg&#39; {
  const content: import(&#39;../dist/shared/lib/image-external&#39;).StaticImageData

  export default content
}

declare module &#39;*.jpeg&#39; {
  const content: import(&#39;../dist/shared/lib/image-external&#39;).StaticImageData

  export default content
}</code></pre>
<hr>

<h2 id="8-react-타입-정리-노트">8. React 타입 정리 노트</h2>
<h3 id="1-html-타입">(1) HTML 타입</h3>
<h4 id="1-htmlelement-삽입">1. HTMLElement 삽입</h4>
<p><code>HTML&lt;태그이름&gt;Element</code>라는 이름으로 DOM 노드에 대한 타입을 사용할 수 있다.</p>
<pre><code class="language-ts">const usernameInput = document.getElementById(&#39;username&#39;) as HTMLInputElement;
const submitButton = document.getElementById(&#39;submit&#39;) as HTMLButtonElement;</code></pre>
<h4 id="2-이벤트-타입">2. 이벤트 타입</h4>
<p>기본적으로 <code>Event</code>라는 타입을 쓸 수 있고, 구체적으로 <code>-Event</code>로 끝나는 타입을 활용하면 된다. 예를 들어 <code>oninput</code> 이벤틍체 대한 타입은 <code>InputEvent</code>이고, <code>onclick</code> 이벤트에 대한 타입은 <code>MouseEvent</code>이다.</p>
<pre><code class="language-ts">submitButton.addEventListener(&#39;click&#39;, handleClick);

function handleClick(e: MouseEvent) {
  e.preventDefault();
  const message = `${usernameInput.value}님 반갑습니다!`;
  alert(message);
}</code></pre>
<h3 id="2-react-타입">(2) React 타입</h3>
<h4 id="1-props">1. Props</h4>
<p>인터페이스를 사용해서 타입을 지정해 준다. <code>children</code>의 경우 <code>ReactNode</code>라는 타입을 사용한다.</p>
<pre><code class="language-ts">import { MouseEvent, ReactNode } from &#39;react&#39;;
import styles from &#39;./Button.module.css&#39;;

interface Props {
  className?: string;
  id?: string;
  children?: ReactNode;
  onClick: (e: MouseEvent&lt;HTMLButtonElement&gt;) =&gt; void;
}

const Button = ({ className = &#39;&#39;, id, children, onClick }: Props) =&gt; {
  const classNames = `${styles.button} ${className}`;
  return (
    &lt;button className={classNames} id={id} onClick={onClick}&gt;
      {children}
    &lt;/button&gt;
  );
}

export default Button;</code></pre>
<p>HTML 기본 Props를 타입으로 정의하고 싶다면, <code>태그이름HTMLAttributes&lt;노드타입&gt;</code> 형태의 타입을 상속해서 활용할 수 있다. 예를 들어 인풋 노드의 Props를 사용하고 싶다면 <code>InputHTMLAttributes&lt;HTMLInputElement&gt;</code>와 같이 쓸 수 있다.</p>
<pre><code class="language-ts">import { InputHTMLAttributes } from &#39;react&#39;;
import styles from &#39;./Input.module.css&#39;;

interface Props extends InputHTMLAttributes&lt;HTMLInputElement&gt; {
}

export default function Input({ className = &#39;&#39;, ...rest }: Props) {
  const classNames = `${styles.input} ${className}`;
  return &lt;input className={classNames} {...rest} /&gt;;
}</code></pre>
<h4 id="2-hook">2. Hook</h4>
<p><code>useState()</code>의 경우 초깃값만 잘 지정하면 타입이 잘 추론된다. 기본값에서 타입이 추론되지 않으면 제네릭으로 타입을 지정해준다. 특히 빈 배열을 사용할 때 주의하면 된다.</p>
<pre><code class="language-ts">const names = useState&lt;string[]&gt;([]);</code></pre>
<p><code>useRef()</code>의 경우 대상이 되는 DOM 노드의 타입을 제네릭으로 지정하고, 초깃값으로 <code>null</code>을 지정해주면, <code>ref</code> Props로 내려줄 때 타입 오류가 나지 않는다.</p>
<pre><code class="language-ts">const formRef = useRef&lt;HTMLFormElement&gt;(null);</code></pre>
<h4 id="3-이벤트-핸들러">3. 이벤트 핸들러</h4>
<p>HTML 이벤트 타입과 마찬가지로 <code>ChangeEvent</code>, <code>MouseEvent</code> 같이 <code>-Event</code>로 끝나는 타입을 사용한다. 제네릭으로 DOM 노드 타입을 지정해 주면 이벤트 타겟의 타입을 지정할 수 있다. 이때 주의할 점은 순수 HTML 자바스크립트에서 사용하는 <code>MouseEvent</code>가 아니라 <code>react</code> 패키지에서 불러와서 사용하는 <code>MouseEvent</code> 타입이라는 점이다. 어떤 이벤트인지 타입을 구체적으로 지정할 필요가 없는 경우라면, <code>SyntheticEvent</code>라는 타입을 사용하면 된다.</p>
<pre><code class="language-ts">import { ChangeEvent, MouseEvent, useEffect, useRef, useState } from &#39;react&#39;;

// ...

function handleChange(e: ChangeEvent&lt;HTMLInputElement&gt;) {
    const { name, value } = e.target;
    const nextValues = {
      ...values,
      [name]: value,
    };
    setValues(nextValues);
  }

  function handleClick(e: MouseEvent&lt;HTMLButtonElement&gt;) {
  // function handleClick(e: SyntheticEvent)처럼 쓸 수도 있음
    e.preventDefault();

    const message = `${values.username}님 환영합니다`;
    alert(message);
  }</code></pre>
<h4 id="4-context">4. Context</h4>
<p>컨텍스트의 경우 컨텍스트 값의 타입을 제네릭으로 잘 지정해주면 된다. 이때 초깃값도 올바로 지정하는 것을 잊지말아야 한다.</p>
<pre><code class="language-ts">type Locale = &#39;ko&#39; | &#39;en&#39;;
interface LocaleContextValue {
  locale: Locale;
  setLocale: (value: Locale) =&gt; void;
}

const LocaleContext = createContext&lt;LocaleContextValue&gt;({
  locale: &#39;ko&#39;,
  setLocale: () =&gt; {},
});</code></pre>
<hr>

<h2 id="9-pages-router-타입">9. Pages Router 타입</h2>
<h3 id="1-커스텀-app">(1) 커스텀 App</h3>
<p><code>_app.tsx</code> 파일에서 웹사이트 전체에 공통적으로 렌더링되는 <code>App</code>이라는 컴포넌트를 만든다. 이 컴포넌트의 Props 형태는 정해져 있다. <code>AppProps</code>라는 타입을 <code>next/app</code> 패키지에서 불러와서 사용하면 된다.</p>
<pre><code class="language-ts">// _app.tsx
import Head from &#39;next/head&#39;;
import { AppProps } from &#39;next/app&#39;;
import Header from &#39;@/components/Header&#39;;
import &#39;@/styles/global.css&#39;;

export default function App ({ Component, pageProps }: AppProps) {
  return (
    &lt;&gt;
      &lt;Head&gt;
        &lt;title&gt;Codeitmall&lt;/title&gt;
        &lt;link rel=&quot;icon&quot; href=&quot;/favicon.ico&quot; /&gt;
      &lt;/Head&gt;
      &lt;Header /&gt;
      &lt;Component {...pageProps} /&gt;
    &lt;/&gt;
  );
}</code></pre>
<h3 id="2-프리-렌더링">(2) 프리 렌더링</h3>
<h4 id="1-정적-생성">1. 정적 생성</h4>
<p>Next.js에선 빌드 시점에 리액트 코드를 미리 렌더링해 둘 수 있다. 이런 걸 정적 생성(Static Generation)이라고 한다.</p>
<pre><code class="language-ts">import Image from &#39;next/image&#39;;

export async function getStaticPaths () {
  const res = await fetch(&#39;https://learn.codeit.kr/api/codeitmall/products/&#39;);
  const data = await res.json();
  const products = data.results;
  const paths = products.map((product) =&gt; ({
    params: { id: String(product.id) },
  }));

  return {
    paths,
    fallback: true,
  };
};

export async function getStaticProps(context) {
  const productId = context.params [&#39;id&#39;];

  let product;
  try {
    const res = await fetch(`https://learn.codeit.kr/api/codeitmall/products/${productId}`);
    const data = await res.json();
    product = data;
  } catch {
    return {
      notFound: true,
    };
  }

  return {
    props: {
      product,
    },
  };
}

export default function ProductPage({ product }) {
  return (
    &lt;div&gt;
      &lt;h1&gt;{product.name}&lt;/h1&gt;
      &lt;Image
        src={product.imgUrl}
        width=&quot;480&quot;
        height=&quot;480&quot;
        alt={product.name}
      /&gt;
    &lt;/div&gt;
  );
}</code></pre>
<p><code>getStaticPaths()</code>라는 함수에선 <code>https://learn.codeit.kr/api/codeitmall/products/</code>라는 API 주소에 request를 보내서 상품 목록 데이터를 받는다. 이 데이터로 아이디 값들을 모아서 <code>params</code>값들을 만든다. 이 값을 바탕으로 <code>getStaticProps()</code> 함수에서는 <code>context</code> 값을 활용해 request를 보내서 상품 데이터를 받아온다. 이걸 <code>product</code>라는 이름의 Prop으로 내려주고 있다. Next.js에선 기본적으로 화살표 함수로 만든 다음 아래와 같이 <code>GetStaticPaths</code>, <code>GetStaticProps</code> 타입을 지정하는 걸 권장한다.</p>
<pre><code class="language-ts">import { GetStaticPaths, GetStaticProps } from &#39;next&#39;;
// ...

export const getStaticPaths: GetStaticPaths = async () =&gt; {
  // ...

  return {
    paths,
    fallback: true,
  };
};

export const getStaticProps: GetStaticProps = async (context) =&gt; {
  // ...

  return {
    props: {
      product,
    },
  };
}</code></pre>
<p>그 다음으로 페이지 타입을 정의해 보도록 하겠다. 우선 일반적인 리액트 컴포넌트의 Props 타입을 정의하듯이 <code>Props</code>를 정의하고, 이걸 <code>getStaticProps()</code> 함수의 제네릭으로 지정해 준다.</p>
<pre><code class="language-ts">interface Props {
  product: Product;
}

export const getStaticProps: GetStaticProps&lt;Props&gt; = async (context) =&gt; {
  // ...
  return {
    props: {
      product,
    },
  };
};

export default function ProductPage({ product }: Props) {
  return (
    &lt;div&gt;
      &lt;h1&gt;{product.name}&lt;/h1&gt;
      &lt;Image
        src={product.imgUrl}
        width=&quot;480&quot;
        height=&quot;480&quot;
        alt={product.name}
      /&gt;
    &lt;/div&gt;
  );
}</code></pre>
<h4 id="2-서버사이드-렌더링">2. 서버사이드 렌더링</h4>
<p>Next.js 서버에 request가 들어올 때마다 렌더링을 해서 보내주는 서버사이드 렌더링의 경우에도 비슷한 방식으로 해주면 된다. 앞에서 보았단 예시에서 서버사이드 렌더링으로 바꾼 코드이다.</p>
<pre><code class="language-ts">import Image from &#39;next/image&#39;;

export async function getServerSideProps(context) {
  const productId = context.params[&#39;id&#39;];

  let product;
  try {
    const res = await fetch(`https://learn.codeit.kr/api/codeitmall/products/${productId}`);
    const data = await res.json();
    product = data;
  } catch {
    return {
      notFound: true,
    };
  }

  return {
    props: {
      product,
    },
  };
}

export default function ProductPage({ product }) {
  return (
    &lt;div&gt;
      &lt;h1&gt;{product.name}&lt;/h1&gt;
      &lt;Image
        src={product.imgUrl}
        width=&quot;480&quot;
        height=&quot;480&quot;
        alt={product.name}
      /&gt;
    &lt;/div&gt;
  );
}</code></pre>
<p><code>getServerSideProps()</code> 함수의 타입은 화살표 함수로 만든 다음에 <code>GetServerSideProps</code>로 지정하면 되고, 마찬가지로 <code>Props</code> 타입도 정의한 다음, 아래처럼 제네릭으로 지정하고, 페이지 컴포넌트에도 정의하면 된다.</p>
<pre><code class="language-ts">interface Props {
  product: Product;
}

export const getServerSideProps: GetServerSideProps&lt;Props&gt; = async (context) =&gt; {
  // ...
  return {
    props: {
      product,
    },
  };
};

export default function ProductPage({ product }: Props) {
  return (
    &lt;div&gt;
      &lt;h1&gt;{product.name}&lt;/h1&gt;
      &lt;Image
        src={product.imgUrl}
        width=&quot;480&quot;
        height=&quot;480&quot;
        alt={product.name}
      /&gt;
    &lt;/div&gt;
  );
}</code></pre>
<h3 id="3-api-라우트">(3) API 라우트</h3>
<p>API 라우트의 타입을 살펴보면 아래와 같이 request와 response에 타입을 지정하면 된다.</p>
<pre><code class="language-ts">import type { NextApiRequest, NextApiResponse } from &#39;next&#39;

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  // ...
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[React에 TypeScript 적용하기(1)]]></title>
            <link>https://velog.io/@gaebar_top/React%EC%97%90-TypeScript-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B01</link>
            <guid>https://velog.io/@gaebar_top/React%EC%97%90-TypeScript-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B01</guid>
            <pubDate>Mon, 05 Feb 2024 11:21:40 GMT</pubDate>
            <description><![CDATA[<h1 id="타입스크립트-세팅하기">타입스크립트 세팅하기</h1>
<h2 id="1-create-react-app-프로젝트">1. Create React App 프로젝트</h2>
<h3 id="1-typescript로-프로젝트-생성하기">(1) TypeScript로 프로젝트 생성하기</h3>
<p>Create React App에서는 &quot;템플릿&quot;이라는 게 있다. 템플릿은 만들 프로젝트의 형태들을 미리 세팅해 놓은 틀이라고 할 수 있다. 여러 가지 템플릿들이 있는데, 타입스크립트 템플릿을 선택해서 프로젝트를 생성하도록 하겠다. 참고로 공식적으로 제공하는 타입스크립트 템플릿 말고도 다른 개발자들이 만든 템플릿을 <a href="https://www.npmjs.com/search?q=cra-template-*">NPM 사이트</a>에서 찾아볼 수 있다.</p>
<h4 id="1-터미널에-익숙하지-않은-경우">1. 터미널에 익숙하지 않은 경우</h4>
<p>프로젝트를 위한 폴더를 하나 만들어라. 그리고 그 폴더를 VSCode에서 열고, 터미널을 연다. 터미널에서 <code>npx create-react-app . --template typescript</code>을 입력한다. 마침표(<code>.</code>)는 현재 폴더라는 의미이다. VSCode에서는 기본적으로 터미널을 열면 현재 열린 폴더에서 터미널을 열어주기 때문에, 마침표를 입력하면 현재 폴더, 즉 VSCode가 열어 놓은 폴더에 프로젝트를 생성하게 된다. <code>--template</code>은 유닉스 커맨드에서 옵션을 사용하는 부분이다. <code>template</code>라는 옵션으로 <code>typescript</code>를 사용하겠다는 의미이다. 잘 생성했으면 <code>tsconfig.json</code> 파일과 함께 프로젝트가 생성된다.</p>
<h4 id="2-터미널에-익숙한-경우">2. 터미널에 익숙한 경우</h4>
<pre><code>npx create-react-app 원하는_폴더_이름 --template typescript</code></pre><h3 id="2-javascript-프로젝트를-typescript로-마이그레이션-하기">(2) JavaScript 프로젝트를 TypeScript로 마이그레이션 하기</h3>
<p>IT 분야에서 데이터나 소프트웨어 같은 걸 한 시스템에서 다른 시스템으로 옮기는 걸 마이그레이션(Migration)이라고 부른다. Create React App으로 이미 생성한 자바스크립트 프로젝트가 있다면, 프로젝트를 마이그레이션을 하는 가장 쉬운 방법은 타입스크립트 템플릿으로 새로운 프로젝트를 생성한 다음, 기존 소스코드 파일들을 복사해 오는 것이다.</p>
<h4 id="1-새로운-프로젝트를-타입스크립트-템플릿으로-생성하기">1. 새로운 프로젝트를 타입스크립트 템플릿으로 생성하기</h4>
<p>위의 프로젝트 생성하는 방법을 참고하여 원하는 위치에 프로젝트를 생성해라. 필요한 파일을 복하는 용도이기 때문에 위치는 크게 상관없다.</p>
<h4 id="2-기존-소스코드-파일-복사하기">2. 기존 소스코드 파일 복사하기</h4>
<p>새롭게 생성한 프로젝트의 <code>src</code> 폴더에서 아래 파일들을 제외하고 모두 지워라.</p>
<pre><code>src/react-app-env.d.ts
src/reportWebVitals.ts
src/setupTests.ts</code></pre><p>만약 성능 측정 기능(<code>reportWebVitals.ts</code> 파일) 그리고 테스트 기능(<code>setupTests.ts</code> 파일)을 사용하지 않는다면 이것도 지워도 괜찮다. 하지만 <code>src/react-app-env.d.ts</code> 파일은 반드시 남겨두어야 한다. 이 파일은 Create React App에서 미리 세팅해 높은 타입들을 불러오는 파일이기 때문이다.</p>
<p>기존 프로젝트의 소스코드에서 <code>src</code> 폴더에 있는 파일들을 복사해서 새로운 프로젝트의 <code>src</code> 폴더로 붙여 넣는다. 마찬가지로 새로운 프로젝트의 <code>public</code> 폴더에 있는 파일들을 지우고, 기존 프로젝트의 코드도 옮겨준다.</p>
<h4 id="3-파일-확장자-바꾸기">3. 파일 확장자 바꾸기</h4>
<p><code>src</code> 폴더에 있는 자바스크립트 파일의 확장자를 타입스크립트로 바꾸면 된다. JSX 문법이 있다면 <code>.tsx</code>로, 일반적인 파일이라면 <code>.ts</code>로 바꿔주면 된다.</p>
<h4 id="4-내가-쓴-코드가-바로-실행이-안-된다">4. 내가 쓴 코드가 바로 실행이 안 된다</h4>
<p>기존 자바스크립트 파일을 타입스크립트 파일로 바꿨지만 아직 자바스크립트 문법으로 작성된 코드라서 당장은 타입 오류가 날 수 있다.</p>
<hr>

<h2 id="2-vite-프로젝트">2. Vite 프로젝트</h2>
<p>2023년을 기준으로 요즘은 React 개발자들도 Vite라는 프로젝트 생성 도구를 많이 사용한다. Create React App과 비교했을 때 좀 더 라이트한 기능의 프로젝트를 만들어주고, 빌드 속도가 조금 더 빠르다고 알려져 있다. 그리고 리액트 프로젝트 말고도 다양한 프로젝트를 생성하는 데 사용할 수 있다는 장점도 있다.</p>
<h3 id="1-typescript로-프로젝트-생성하기-1">(1) TypeScript로 프로젝트 생성하기</h3>
<p>Vite에도 템플릿이 있다. 리액트뿐만 아니라 Vue, Svelte 등 여러 프로젝트의 템플릿을 제공한다. 그중에서도 타입스크립트를 위한 템플릿을 선택해 프로젝트를 생성할 것이다. 기본적으로 제공하는 템플릿은 <a href="https://vitejs.dev/guide/#trying-vite-online">공식 문서</a>에서 확인할 수 있고, 개발자들이 만든 템플릿은 <a href="https://github.com/vitejs/awesome-vite#templates">awesome-vite</a>라는 레포지토리에서 확인할 수 있다.</p>
<h4 id="1-터미널에-익숙하지-않은-경우-1">1. 터미널에 익숙하지 않은 경우</h4>
<p>프로젝트를 위한 폴더를 하나 만들어라. 그리고 그 폴더를 VSCode에서 열고, 터미널을 연다. 터미널에서 <code>npx create-vite-app . --template react-ts</code>을 입력한다. 마침표(<code>.</code>)는 현재 폴더라는 의미이다. VSCode에서는 기본적으로 터미널을 열면 현재 열린 폴더에서 터미널을 열어주기 때문에, 마침표를 입력하면 현재 폴더, 즉 VSCode가 열어 놓은 폴더에 프로젝트를 생성하게 된다. <code>--template</code>은 유닉스 커맨드에서 옵션을 사용하는 부분이다. <code>template</code>라는 옵션으로 <code>react-ts</code>를 사용하겠다는 의미이다. 잘 생성했으면 <code>tsconfig.json</code> 파일과 함께 프로젝트가 생성된다.</p>
<p>Vite는 Create React App과 달리 패키지를 직접 설치해 주어야 한다. <code>npm install</code>을 통해 패키지를 설치하면 된다. 제대로 패키지를 설치했으면 <code>node_modules</code> 폴더가 생겼을 것이다.</p>
<h4 id="2-터미널에-익숙한-경우-1">2. 터미널에 익숙한 경우</h4>
<pre><code>npx create-vite-app 원하는_폴더_이름 --template react-ts
npm install</code></pre><h3 id="2-javascript-프로젝트를-typescript로-마이그레이션-하기-1">(2) JavaScript 프로젝트를 TypeScript로 마이그레이션 하기</h3>
<p>IT 분야에서 데이터나 소프트웨어 같은 걸 한 시스템에서 다른 시스템으로 옮기는 걸 마이그레이션(Migration)이라고 부른다. Vite로로 이미 생성한 자바스크립트 프로젝트가 있다면, 프로젝트를 마이그레이션을 하는 가장 쉬운 방법은 타입스크립트 템플릿으로 새로운 프로젝트를 생성한 다음, 기존 소스코드 파일들을 복사해 오는 것이다.</p>
<h4 id="1-새로운-프로젝트를-타입스크립트-템플릿으로-생성하기-1">1. 새로운 프로젝트를 타입스크립트 템플릿으로 생성하기</h4>
<p>위의 프로젝트 생성하는 방법을 참고하여 원하는 위치에 프로젝트를 생성해라. 필요한 파일을 복하는 용도이기 때문에 위치는 크게 상관없다.</p>
<h4 id="2-기존-소스코드-파일-복사하기-1">2. 기존 소스코드 파일 복사하기</h4>
<p>새롭게 생성한 프로젝트의 <code>src</code> 폴더에서 <code>src/react-app-env.d.ts</code> 파일을 제외하고 모두 지우면 된다. <code>src/react-app-env.d.ts</code> 파일은 반드시 남겨두어야 한다. 이 파일에는 Vite 프로젝트에서 미리 세팅해 높은 타입 정의가 적혀있다.</p>
<p>기존 프로젝트의 소스코드에서 <code>src</code> 폴더에 있는 파일들을 복사해서 새로운 프로젝트의 <code>src</code> 폴더로 붙여 넣는다. 마찬가지로 기존 프로젝트이 소스코드에서 <code>index.html</code> 파일과 <code>index.html</code>에서 사용하는 <code>favicon.ico</code> 같은 파일들이 있다면 같이 복사해서 새로운 프로젝트로 옮겨준다.</p>
<h4 id="3-파일-확장자-바꾸기-1">3. 파일 확장자 바꾸기</h4>
<p><code>src</code> 폴더에 있는 자바스크립트 파일의 확장자를 타입스크립트로 바꾸면 된다. JSX 문법이 있다면 <code>.tsx</code>로, 일반적인 파일이라면 <code>.ts</code>로 바꿔주면 된다.</p>
<h4 id="4-내가-쓴-코드가-바로-실행이-안-된다-1">4. 내가 쓴 코드가 바로 실행이 안 된다</h4>
<p>기존 자바스크립트 파일을 타입스크립트 파일로 바꿨지만 아직 자바스크립트 문법으로 작성된 코드라서 당장은 타입 오류가 날 수 있다.</p>
<hr>

<h2 id="3-nextjs-프로젝트">3. Next.js 프로젝트</h2>
<h3 id="1-typescript로-프로젝트-생성하기-2">(1) TypeScript로 프로젝트 생성하기</h3>
<h4 id="1-터미널에-익숙하지-않은-경우-2">1. 터미널에 익숙하지 않은 경우</h4>
<pre><code>npx create-next-app .</code></pre><p>타입스크립트를 사용할 거냐옥 물어보는데, Yes를 선택하고 엔터를 입력하고, 프로젝트 생성을 진행하면 된다.</p>
<h4 id="2-터미널에-익숙한-경우-2">2. 터미널에 익숙한 경우</h4>
<pre><code>npx create-next-app 원하는_폴더_이름</code></pre><h3 id="2-프로젝트를-typescript로-마이그레이션-하기">(2) 프로젝트를 TypeScript로 마이그레이션 하기</h3>
<h4 id="1-파일-확장자-바꾸기">1. 파일 확장자 바꾸기</h4>
<p><code>src</code> 폴더에 있는 자바스크립트 파일의 확장자를 타입스크립트로 바꾸면 된다. JSX 문법이 있다면 <code>.tsx</code>로, 일반적인 파일이라면 <code>.ts</code>로 바꿔주면 된다.</p>
<h4 id="2-개발모드-실행하기">2. 개발모드 실행하기</h4>
<p>터미널에서 <code>npm run dev</code>를 입력해 Next.js 개발모드를 실행한다. 한 번 실행하면 타입스크립트 파일을 인식해 알아서 <code>tsconfig.json</code> 같은 필요한 파일들을 생성한다.</p>
<h4 id="3-내가-쓴-코드가-바로-실행이-안-된다">3. 내가 쓴 코드가 바로 실행이 안 된다</h4>
<p>기존 자바스크립트 파일을 타입스크립트 파일로 바꿨지만 아직 자바스크립트 문법으로 작성된 코드라서 당장은 타입 오류가 날 수 있다.</p>
<hr>

<h2 id="4-기존-패키지에서-타입을-찾을-수-없을-때">4. 기존 패키지에서 타입을 찾을 수 없을 때</h2>
<p>기존에 프로젝트에서 리액트 말고도 다른 패키지를 쓴 게 있다면, 추가로 타입을 설치해줘야 할 수도 있다.</p>
<h3 id="1-types-타입-설치하기">(1) @types 타입 설치하기</h3>
<p>사용하던 패키지 중에 타입스크립트 파일로 바꾸고 나면 가끔 아래 이미지처럼 import 구문에서 주의 표시가 나오는 경우가 있다.</p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/b5a2fc9e-5dcd-42a4-91a4-926e571b6e82/image.png" alt=""></p>
</blockquote>
<p>에러 메시지를 읽어보면 타입 정의 파일이 없다고 한다. <code>@types/react-modal</code>이라는 패키지를 설치하라고 가이드하고 있다. 이 때 타입스크립트는 개발할 때만 사용하는 거니까 <code>--save-dev</code> 옵션으로 설치해야 한다는 점도 주의해야 한다.</p>
<pre><code>npm install --save-dev @types/패키지_이름</code></pre><p>참고로 모든 패키지를 이렇게 설치해야 하는 건 아니다. 어떤 패키지들은 <code>@types</code> 패키지를 사용하고 어떤 패키지는 사용하지 않아도 된다. 타입을 찾을 수 없다고 오류가 난다면 <code>@types/</code>를 패키지 이름 앞에 붙여서 설치해주면 된다. <code>@types</code>를 사용하는 이유가 궁금하면 <a href="https://www.codeit.kr/tutorials/91/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%97%90%EC%84%9C%20%40types%20%ED%8C%A8%ED%82%A4%EC%A7%80%EB%A5%BC%20%EC%93%B0%EB%8A%94%20%EC%9D%B4%EC%9C%A0">타입스크립트에서 @types 패키지를 사용하는 이유</a>에서 더 자세히 알아보면 좋다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[타입스크립트 기본기]]></title>
            <link>https://velog.io/@gaebar_top/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EA%B8%B0%EB%B3%B8%EA%B8%B0</link>
            <guid>https://velog.io/@gaebar_top/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EA%B8%B0%EB%B3%B8%EA%B8%B0</guid>
            <pubDate>Mon, 05 Feb 2024 04:55:36 GMT</pubDate>
            <description><![CDATA[<h1 id="typescript-시작하기">TypeScript 시작하기</h1>
<h2 id="1-typescript-프로젝트-세팅">1. TypeScript 프로젝트 세팅</h2>
<pre><code>npm init
npm install --save-dev typescript
npm tsc --init</code></pre><p><code>package.json</code> 파일에서 scripts에 <code>&quot;build&quot;: &quot;tsc&quot;</code> 추가한다. 그리고 ts 파일 하나를 만든 후, <code>npm run build</code>를 입력하여 js 파일이 생성된 것을 확인할 수 있다. <code>use strict</code>는 조금 더 엄격한 기준으로 자바스크립트를 사용한다는 의미를 담고 있다.</p>
<p><code>node 파일명.js</code> 명령어도 자주 사용하게 되기 때문에, scripts에 <code>&quot;start&quot;: &quot;node 파일명.js&quot;</code>도 추가해주면, 단순히 <code>npm start</code>로 실행할 수 있게 된다.</p>
<hr>

<h2 id="2-typescript가-실행되는-과정">2. TypeScript가 실행되는 과정</h2>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/47529b5a-7951-415b-bb15-7ec72adf526d/image.png" alt=""></p>
<ul>
<li><p>컴파일러 : 컴파일을 하는 프로그램을 의미한다. 컴파일이란, 한 프로그래밍에서 다른 프로그래밍 언어로 번역하는 것을 의미한다. 웹 개발에서는 트랜스파일(Transpile)이라고 한다.
<img src="https://velog.velcdn.com/images/gaebar_top/post/09de2d48-b2c9-422e-b44b-e6f4c6376f58/image.png" alt=""></p>
</li>
<li><p>타입스크립트 컴파일러(TSC) : 타입스크립트 코드를 자바스크립트 코드로 트랜스파일 해 주는 프로그램을 의미한다. 이 외에도 &#39;타입 검사&#39;를 하게 된다.</p>
<hr>

</li>
</ul>
<h2 id="3-타입을-정하는-법">3. 타입을 정하는 법</h2>
<pre><code class="language-ts">let size = 100; // let size: number
size = &#39;L&#39; // Error!

let size: number = 100; // 타입 지정
let size: number; // 바로 타입을 지정하지 않을 때
let size = 105;</code></pre>
<hr>

<h2 id="4-타입-오류-이해하기">4. 타입 오류 이해하기</h2>
<pre><code class="language-ts">// example1
const product = {
  id: &#39;c001&#39;,
  name: &#39;라이트 윈드 브레이커&#39;,
  price: 129000,
};

// ...

product.price = &#39;139000원&#39;; // 오류

// ...

const salePrice = product.price * 0.9;
console.log(`할인 가격: ${salePrice}`);

// example2
let product = {
  id: &#39;c001&#39;,
  name: &#39;라이트 윈드 브레이커&#39;,
  price: 129000,
  sizes: [&#39;M&#39;, &#39;L&#39;, &#39;XL&#39;],
};

// ...

const newProduct = {
  id: &#39;c002&#39;,
  name: &#39;다크 윈드 브레이커&#39;,
  price: 139000,
  sizes: [90, 95, 100, 105, 100],
};

// ...

product = newProduct; // 오류(밖에서 안으로 오류를 해석하면 된다)</code></pre>
<hr>

<h2 id="5-기본형">5. 기본형</h2>
<pre><code class="language-ts">let itemName: string = &#39;안녕&#39;;
let itemPrice: number = 129000;
let membersOnly: boolean = true;
let owner: undefined = undefined;
let seller: null = null;
// undefined와 null은 서로 다른 타입이다.</code></pre>
<hr>

<h2 id="6-배열과-튜플">6. 배열과 튜플</h2>
<pre><code class="language-ts">// 배열
const cart: string[] = [];
cart.push(&#39;c001&#39;);
cart.push(&#39;c002&#39;);

const carts: string[][] = [
  [&#39;c001&#39;, &#39;c002&#39;],
  [&#39;c003&#39;],
];

// 튜플
let mySize: [number, number] = [167, 28];
mySize = [167, 28, 255]; // 오류
mySize = [255]; // 오류
mySize = []; // 오류
mySize = [167, &#39;28&#39;]; // 오류</code></pre>
<hr>

<h2 id="7-객체-타입">7. 객체 타입</h2>
<pre><code class="language-ts">let product = {
  id: string;
  name: string;
  price: number;
  membersOnly?: boolean; // Optional Property
  sizes: string[];
} = {
  id: &#39;c001&#39;,
  name: &#39;코드잇 블랙 후디&#39;,
  price: 129000,
  sizes: [&#39;M&#39;, &#39;L&#39;, &#39;XL&#39;],
}

if (product.membersOnly) {
  console.log(&#39;회원 전용 상품&#39;);
} else {
  console.log(&#39;일반 상품&#39;);
}

let field = &#39;field name&#39;;
let obj = {
  [field]: &#39;field value&#39;,
};

let stock: {
  [id: string]: number;
} = {
  c001: 3,
  c002: 2,
  c003: 0
};</code></pre>
<hr>

<h2 id="8-any">8. any</h2>
<pre><code class="language-ts">const product: any = {
  id: &#39;c001&#39;,
  name: &#39;블랙 후디&#39;,
  price: 129000,
  sizes: [&#39;M&#39;, &#39;L&#39;, &#39;XL&#39;],
};

console.log(product.reviews[2]);

// any는 되도록 사용하지 않아야 하지만 JSON으로 파싱할 때처럼 사용해야 할 때도 있다. 하지만 이 때도 되도록 자료형을 지정해주는 것도 좋다.
// 방법1
const parsedProduct: {
  name: string;
  price: number;
} = JSON.parse(
  &#39;{ &quot;name&quot;: &quot;토트백&quot;, &quot;price&quot;: 12000 }&#39;
);

// 방법2
const parsedProduct = JSON.parse(
  &#39;{ &quot;name&quot;: &quot;토트백&quot;, &quot;price&quot;: 12000 }&#39;
) as {
  name: string;
  price: number;
};

// 방법3 (자주 사용하는 방법은 아니다)
const parsedProduct: &lt;{
  name: string;
  price: number;
}&gt;JSON.parse(
  &#39;{ &quot;name&quot;: &quot;토트백&quot;, &quot;price&quot;: 12000 }&#39;
);</code></pre>
<hr>

<h2 id="9-함수에-타입-정의하기">9. 함수에 타입 정의하기</h2>
<pre><code class="language-ts">const mall: {
  stock: { [id: string]: number };
  cart: string[];
  addToCart: (id: string, quantity?: number) =&gt; boolean;
  addManyToCart: {...ids: string[]) =&gt; void; // 비워있음을 의미한다.
} = {
  stock: {
      c001: 3,
      c002: 1,
  },
  cart: [],
  addToCart,
};

function addToCart(id: string, quantity?: number) {
  if (!quantity) {
    quantity = 1;
  )

  if (stock[id] &lt; quantity) {
    return false;
  }

  stock[id] -= quantity;
  for (let i = 0; i &lt; quantity; i++) {
    cart.push(id);
  }

  return true;
}

function addManyToCart(...ids: string[]) {
  for (const id of ids) {
    addToCart(id);
  }
};

console.log(stock, cart);
const result1 = addToCart(&#39;c001&#39;, 1);
console.log(`결과1: ${result1}`);
console.log(stock, cart);
const result2 = addToCart(&#39;c002&#39;, 2);
console.log(`결과2: ${result2}`);
console.log(stock, cart);</code></pre>
<hr>

<h2 id="10-기본-문법-정리">10. 기본 문법 정리</h2>
<h3 id="1-배열과-튜플">(1) 배열과 튜플</h3>
<p>배열 타입을 만들려면 타입을 적고 <code>[]</code>를 붙인다. 만약에 배열을 만들고 싶다면 배열 타입 뒤에 <code>[]</code>를 붙이면 된다. 튜플은 개수랑 순서가 정해져 있는 배열이다. <code>[]</code> 안에 순서대로 타입을 쉼표로 구분해서 쓰면 된다.</p>
<pre><code class="language-ts">// 배열
const cart: string[] = [];
cart.push(&#39;c001&#39;);
cart.push(&#39;c002&#39;);

// 배열의 배열
const carts: string[][] = [
  [&#39;c001&#39;, &#39;c002&#39;],
  [&#39;c003&#39;],
];

// 튜플
let mySize: [number, number, string] = [175, 30, &#39;L&#39;];</code></pre>
<h3 id="2-객체-타입">(2) 객체 타입</h3>
<p><code>{}</code> 안에다가 프로퍼티 이름을 쓰고 콜론 다음에 타입을 쓴다. 각 프로퍼티는 세미콜론으로 구분한다. 필수가 아닌 프로퍼티는 프로퍼티 이름 뒤에 물음표를 붙인다.</p>
<pre><code class="language-ts">let product: {
  id: string;
  name: string;
  price: number;
  membersOnly?: boolean; // 필수가 아닌 프로퍼티
  sizes: string[];
} = {
  id: &#39;c001&#39;,
  name: &#39;코드잇 블랙 후디&#39;,
  price: 129000,
  sizes: [&#39;M&#39;, &#39;L&#39;, &#39;XL&#39;],
};

if (product.membersOnly) {
  console.log(&#39;회원 전용 상품&#39;);
} else {
  console.log(&#39;일반 상품&#39;);
}</code></pre>
<p>프로퍼티의 개수를 정하지 않고, 프로퍼티 값의 타입을 정하고 싶다면 아래와 같은 문법을 활용하면 된다.</p>
<pre><code class="language-ts">let stock: { [id: string]: number } = {
  c001: 3,
  c002: 0,
  c003: 2,
};</code></pre>
<h3 id="3-any-타입">(3) any 타입</h3>
<p>자바스크립트를 사용할 때와 마찬가지로 자유롭게 쓸 수 있는 타입이다. 되도록이면 <code>any</code> 타입으로 지정하지 않는 것을 권장한다. 어쩔 수 없이 <code>any</code> 타입을 사용하는 경우 <code>as</code> 키워드를 써서 타입을 지정하거나, 콜론으로 타입을 지정할 수 있다.</p>
<pre><code class="language-ts">const parsedProduct = JSON.parse(&#39;{ &quot;name&quot;: &quot;토트백&quot;, &quot;price&quot;: 12000 }&#39;) as { name: string; price: number };

const parsedProduct: { name: string; price: number } = JSON.parse(&#39;{ &quot;name&quot;: &quot;토트백&quot;, &quot;price&quot;: 12000 }&#39;);</code></pre>
<h3 id="4-함수-타입">(4) 함수 타입</h3>
<p>리턴 타입을 지정하는 경우에는 다음과 같이 작성하면 된다.</p>
<pre><code class="language-ts">function addToCart(id: string, quanity: number): boolean {
    if (어떤 조건) {
     return false;
  }

  return true;
}</code></pre>
<p>리턴 타입을 미리 주지 않고 리턴 값으로부터 추론하게 할 수도 있다.</p>
<pre><code class="language-ts">function addToCart(id: string, quanity: number) {
    if (어떤 조건) {
     return false;
  }

  return true;
}</code></pre>
<p>함수를 값으로 사용하는 경우 화살표 함수처럼 작성하면 된다.</p>
<pre><code class="language-ts">(id: string, quanity: number) =&gt; boolean</code></pre>
<p>Rest 파라미터로 배열 타입을 쓴다. 값을 리턴하지 않느 ㄴ경우 리턴 타입을 <code>void</code>로 할 수 있다.</p>
<pre><code class="language-ts">(...ids: string[]) =&gt; void;</code></pre>
<hr>

<h2 id="11-enum열거형">11. Enum(열거형)</h2>
<pre><code class="language-ts">enum Size {
  S = &#39;S&#39;,
  M = &#39;M&#39;,
  L = &#39;L&#39;,
  XL = &#39;XL&#39;,
}

let product: {
  id: string;
  name: string;
  price: number;
  membersOnly?: boolean;
  sizes: Size[];
} = {
  id: &#39;c001&#39;,
  name: &#39;블랙 후디&#39;,
  price: 129000,
  sizes: [Size.M, Sime.L],
};</code></pre>
<hr>

<h2 id="12-interface">12. Interface</h2>
<pre><code class="language-ts">enum Size {
  S = &#39;S&#39;,
  M = &#39;M&#39;,
  L = &#39;L&#39;,
  XL = &#39;XL&#39;,
}

interface Product {
  id: string;
  name: string;
  price: number;
  membersOnly?: boolean;
}

// 상속 가능
interface ClothingProduct extends Product {
  sizes: Size[];
}

const product1: ClothingProduct = {
  id: &#39;c001&#39;,
  name: &#39;블랙 후드 집업&#39;,
  price: 129000,
  membersOnly: true,
  sizes: [Size.M, Size.L],
};

const product2: Product {
  id: &#39;d001&#39;,
  name: &#39;텀블러&#39;,
  price: 25000,
};

interface PrintProductFunction {
  (product: Product): void;
}

const printProduct: PrintProductFunction = (product) =&gt; {
  console.log(`${product.name}의 가격은 ${product.price}원입니다.`)
}

printProduct(product1);
printProduct(product2);</code></pre>
<hr>

<h2 id="13-리터럴-타입">13. 리터럴 타입</h2>
<pre><code class="language-ts">let productName1 = &#39;블랙 후드&#39;;
const productName2 = &#39;텀블러&#39;;

let small = 95;
const large = 100;

function printSize(size: number) {
  console.log(`${size} 사이즈입니다.`);
}

printSize(small); // 오류
printSize(large); // 100</code></pre>
<hr>

<h2 id="14-타입-별칭">14. 타입 별칭</h2>
<pre><code class="language-ts">
type Cart = string[];
type CartResultCallback = (result: boolean) =&gt; void;

const cart: Cart = [
  &#39;c001&#39;,
  &#39;c001&#39;,
  &#39;c002&#39;,
];

interface User {
  username: string;
  email: string;
  cart: string[];
}

const user: User = {
  username: &#39;hello&#39;,
  email: &#39;typescript@hello.kr&#39;,
  cart,
}</code></pre>
<hr>

<h2 id="15-union-타입">15. Union 타입</h2>
<pre><code class="language-ts">enum ClothingSize {
  S = &#39;S&#39;,
  M = &#39;M&#39;,
  L = &#39;L&#39;,
  XL = &#39;XL&#39;,
}

interface Product {
  id: string;
  name: string;
  price: number;
  membersOnly?: boolean;
}

interface ClothingProduct extends Product {
  sizes: ClothingSize[];
  color: string;
}

interface ShoeProduct extends Product {
  sizes: number[];
  handmade: boolean;
}

// Union
function printSizes(product: ClothingProduct | ShoeProduct) {
  const availableSizes = product.sizes.join(&#39;, &#39;);
  console.log(`구매 가능한 사이즈는 다음과 같습니다: ${availableSizes}`);

  if (&#39;color&#39; in product) {
    console.log(`색상: ${product.color}`);
  )

  if (&#39;handmade&#39; in product) {
    console.log(
      product.handmade
          ? &#39;이 상품은 장인이 직접 만듭니다.&#39;
          : &#39;이 상품은 공장에서 만들어졌습니다.&#39;
    );
  }
}

const product1: ClothingProduct = {
  id: &#39;c001&#39;,
  name: &#39;코드잇 블랙 후드 집업&#39;,
  price: 129000,
  membersOnly: true,
  sizes: [ClothingSize.M, ClothingSize.L],
  color: &#39;black&#39;,
};

const product2: ShoeProduct = {
  id: &#39;s001&#39;,
  name: &#39;코드잇 스니커즈&#39;,
  price: 69000,
  membersOnly: false,
  sizes: [220, 230, 240, 260, 280],
  handmade: false,
};

printSizes(product1);
printSizes(product2);</code></pre>
<hr>

<h2 id="16-intersection-타입">16. Intersection 타입</h2>
<pre><code class="language-ts">interface Id {
  id: string;
}

interface Timestamp {
  createdAt: Date;
  updatedAt: Date;
}

type Product = Id &amp; {
  name: string;
  price: number;
  membersOnly?: boolean;
}

type User = Id &amp; Timestamp &amp; {
  username: string;
  email: string;
}

type Review = Id &amp; Timestamp &amp; {
  productId: string;
  userId: string;
  content: string;
}

const product: Product = {
  id: &#39;c001&#39;,
  name: &#39;코드잇 블랙 후드티&#39;,
  price: 129000,
}

const user: User = {
  id: &#39;user0001&#39;,
  username: &#39;codeit&#39;,
  email: &#39;typescript@codeit.kr&#39;,
  createdAt: new Date(),
  updatedAt: new Date(),
}

const review: Review = {
  id: &#39;review001&#39;,
  userId: user.id,
  productId: product.id,
  content: &#39;아주 좋음&#39;,
  createdAt: new Date(),
  updatedAt: new Date(),
}</code></pre>
<hr>

<h2 id="17-intersection하면-합쳐지는-이유">17. Intersection하면 합쳐지는 이유</h2>
<h3 id="1-structural-subtyping">(1) Structural Subtyping</h3>
<p>타입스크립트에서 타입은 Structural Subtyping이라는 규칙을 따른다. 쉽게 말해 구조가 같으면 같은 타입이라고 판단하는 것이다. 예를 들어 <code>a</code>라는 프로퍼티를 갖는 타입 <code>A</code>가 있다고 하고, 이 타입의 <code>a</code> 프로퍼티를 출력하는 <code>printA()</code>라는 함수가 있다고 가정하자. 이 함수를 아래와 같이 <code>{ a: &#39;codeit&#39; }</code>, <code>{ a: &#39;codeit&#39;, b: 42 }</code>, <code>{ a: &#39;codeit&#39;, b: 42, c: true }</code>라는 객체로 실행해도 모두 올바른 타입이다. 세 객체 모두에 <code>a</code>라는 프로퍼티가 있기 때문에 타입 <code>A</code>라고 판단하는 것이다. 이런 걸 Structural Subtyping, Structural Type System이라고 부른다.</p>
<pre><code class="language-ts">interface A {
  a: string;
}

interface B {
  b: number;
}

function printA(arg: A) {
  console.log(arg.a);
}

const x = { a: &#39;codeit&#39; };
const y = { b: 42 };
const z = { a: &#39;codeit&#39;, b: 42 };
const w = { a: &#39;codeit&#39;, b: 42, c: true };

printA(x);
printA(y); // 잘못된 타입
printA(z);
printA(w);</code></pre>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/ec14cbf2-41d3-46a3-9167-daa5bed63cbc/image.png" alt=""></p>
<h3 id="2-union-타입-살펴보기">(2) Union 타입 살펴보기</h3>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/e3f36904-b1cc-459a-95ad-d1eb47197314/image.png" alt=""></p>
<p>코드를 확인해 봐도 <code>pirntAUnionB()</code>라는 함수에 모두 타입 오류 없이 사용할 수 있다는 것을 알 수 있다. 참고로 함수 안에서 <code>if</code>문으로 <code>in</code> 키워드를 사용해서 해당하는 프로퍼티가 존재하는지 확인해 봤는데, 이런 식으로 타입의 범위를 좁힐 수도 있다. 타입스크립트에서는 이런 걸 Type Narrowing이라고 표현한다.</p>
<pre><code class="language-ts">interface A {
  a: string;
}

interface B {
  b: number;
}

function printAUnionB(arg: A | B) {
  // 여기서는 타입 A | B

    if (&#39;a&#39; in arg) {
    // 여기 안에서는 타입 A
    console.log(arg.a);
  }

    if (&#39;b&#39; in arg) {
    // 여기 안에서는 타입 B
    console.log(arg.b); // VS Code에서 arg에 마우스를 호버해 보세요.
  }
}

const x = { a: &#39;codeit&#39; };
const y = { b: 42 };
const z = { a: &#39;codeit&#39;, b: 42 };
const w = { a: &#39;codeit&#39;, b: 42, c: true };

printAUnionB(x);
printAUnionB(y);
printAUnionB(z);
printAUnionB(w);</code></pre>
<h3 id="3-intersection-타입-살펴보기">(3) Intersection 타입 살펴보기</h3>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/23b023d3-03fc-431d-ad42-41e8aba695f1/image.png" alt=""></p>
<pre><code class="language-ts">interface A {
  a: string;
}

interface B {
  b: number;
}

function printAIntersectionB(arg: A &amp; B) {
  console.log(arg.a);
  console.log(arg.b);
}

const x = { a: &#39;codeit&#39; };
const y = { b: 42 };
const z = { a: &#39;codeit&#39;, b: 42 };
const w = { a: &#39;codeit&#39;, b: 42, c: true };

printAIntersectionB(x); // 타입 오류
printAIntersectionB(y); // 타입 오류
printAIntersectionB(z);
printAIntersectionB(w);</code></pre>
<h3 id="4-정리">(4) 정리</h3>
<p>간단하게 <code>A | B</code>라고 하면 &quot;A 타입이거나 B 타입이다&quot;, <code>A &amp; B</code>라고 하면 &quot;A 타입이랑 B 타입을 합친 것이다&quot;라고 생각하면 대부분의 경우 문제없이 사용할 수 있다.</p>
<hr>

<h2 id="18-keyof와-typeof-연산자">18. keyof와 typeof 연산자</h2>
<pre><code class="language-ts">interface Product {
  id: string;
  name: string;
  price: number;
  salePrice: number; // 추가
  membersOnly?: boolean;
}

// type ProductProperty = keyof Product;

const productTableKeys: (keyof Product)[] = [&#39;name&#39;, &#39;price&#39;, &#39;salePrices&#39;, &#39;membersOnly&#39;];

const product: Product = {
  id: &#39;c001&#39;,
  name: &#39;블랙 후드 집업&#39;,
  price: 129000,
  salePrice: 90000,
  membersOnly: true,
};

for (let key of productTableKeys) {
  console.log(`${key} | ${product[key]}`);
}</code></pre>
<hr>

<h2 id="19-타입-별칭은-언제-쓰면-좋을까">19. 타입 별칭은 언제 쓰면 좋을까</h2>
<h3 id="1-enum과-타입-별칭">(1) Enum과 타입 별칭</h3>
<h4 id="1-enum을-사용하는-경우-권장">1. Enum을 사용하는 경우 (권장)</h4>
<pre><code class="language-ts">enum UserType {
  Admin = &#39;admin&#39;,
  User = &#39;user&#39;,
  Guest = &#39;guest&#39;,
}

const role = UserType.Admin;
console.log(role === UserType.Guest);</code></pre>
<h4 id="2-타입-별칭과-union을-사용한-경우">2. 타입 별칭과 Union을 사용한 경우</h4>
<pre><code class="language-ts">type UserType = &#39;admin&#39; | &#39;user&#39; | &#39;guest&#39;

const role: UserType = &#39;admin&#39;;
console.log(role === &#39;guest&#39;);</code></pre>
<h4 id="3-javascript로-트랜스파일링-했을-때">3. JavaScript로 트랜스파일링 했을 때</h4>
<pre><code class="language-js">&quot;use strict&quot;;
var UserType;
(function (UserType) {
    UserType[&quot;Admin&quot;] = &quot;admin&quot;;
    UserType[&quot;User&quot;] = &quot;user&quot;;
    UserType[&quot;Guest&quot;] = &quot;guest&quot;;
})(UserType || (UserType = {}));
const role = UserType.Admin;
console.log(role === UserType.Guest);</code></pre>
<p>Enum은 별도의 자바스크립트 객체를 만들어서 그 객체를 사용한다. <code>UserType</code>은 자바스크립트에서 아래와 같은 객체인 것이다.</p>
<pre><code class="language-js">{ Admin: &#39;admin&#39;, User: &#39;user&#39;, Guest: &#39;guest&#39; }</code></pre>
<p>예를 들어 가능한 <code>UserType</code> 값들을 모두 활용해서 어떤 동작을 구현하고 싶다면 Enum을 써서 <code>Object.keys()</code>라는 함수를 사용해 볼 수 있다.</p>
<pre><code class="language-js">console.log(Object.keys(UserType)); // [&#39;Admin&#39;, &#39;User&#39;, &#39;Guest&#39;]</code></pre>
<p>반면에 타입 별칭은 타입스크립트에서만 의미 있는 코드이다. 그래서 Enum과 달리 자바스크립트로 트랜스파일 했을 때 추가로 객체 같은 걸 만들지 않고 단순히 값만 사용하는 코드가 만들어진다.</p>
<pre><code class="language-js">&quot;use strict&quot;;
const role = &#39;admin&#39;;
console.log(role === &#39;guest&#39;);</code></pre>
<h4 id="4-어떤-걸-써야-할까">4. 어떤 걸 써야 할까</h4>
<p>대부분의 경우 Enum 또는 타입 별칭을 모두 사용할 수 있다. 하지만 되도록 Enum의 목적에 맞는 경우라면 Enum 문법을 사용하는 걸 권장한다.</p>
<h3 id="2-interface와-타입-별칭">(2) Interface와 타입 별칭</h3>
<h3 id="1-interface를-사용한-경우-권장">1. Interface를 사용한 경우 (권장)</h3>
<pre><code class="language-ts">interface Entity {
  id: string;
  createdAt: Date;
  updatedAt: Date;
}

interface User extends Entity {
  username: string;
  email: string;
}</code></pre>
<h4 id="2-타입-별칭을-사용한-경우">2. 타입 별칭을 사용한 경우</h4>
<pre><code class="language-ts">type Entity = {
  id: string;
  createdAt: Date;
  updatedAt: Date;
}

type User = Entity &amp; {
  username: string;
  email: string;
}</code></pre>
<p>Inteface의 상속과 Intersection의 가장 큰 차이점은 Intersection은 두 가지 이상의 타입을 한 번에 합칠 수 있다는 것이다. 이것도 Interface로 아주 불가능하지는 않다.</p>
<pre><code class="language-ts">interface Entity {
  id: string;
}

interface TimestampEntity extends Entity {
  createdAt: Date;
  updatedAt: Date;
}

interface User extends TimestampEntity {
  username: string;
  email: string;
}</code></pre>
<pre><code class="language-ts">type Id = {
  id: string;
}

type Entity = {
  createdAt: Date;
  updatedAt: Date;
}

type User = Id &amp; Entity &amp; {
  username: string;
  email: string;
}</code></pre>
<h4 id="3-어떤-걸-써야-할까">3. 어떤 걸 써야 할까</h4>
<p>대부분의 경우 Inteface 또는 타입 별칭을 모두 사용할 수 있다. 하지만 되도록 Inteface의 목적에 맞는 경우라면 Enum 문법을 사용하는 걸 권장한다.</p>
<h3 id="3-타입-별칭은-언제-쓰면-좋을까">(3) 타입 별칭은 언제 쓰면 좋을까</h3>
<p>타입 별칭은 타입에 &#39;이름&#39;을 정하는 문법이다. 복잡한 타입을 만들고, 그 타입을 여러 곳에서 활용할 때 사용하면 된다. 예를 들어 아래와 같이 복잡한 타입을 만들고 여러 고셍서 재활용할 수 있다.</p>
<pre><code class="language-ts">type Point = [number, number];
type SearchQuery = string | string[];
type Result = SuccessResult | FailedResult;
type Coupon = 
  | PromotionCoupon
  | EmployeeCoupon
  | WelcomCoupon
  | RewardCoupon
  ;</code></pre>
<hr>

<h2 id="20-타입들-문법-정리">20. 타입들 문법 정리</h2>
<h3 id="1-리터럴-타입">(1) 리터럴 타입</h3>
<p>특정한 숫자나 문자열 같이 변수의 값을 타입으로 하는 타입이다. 각 리터럴 타입들은 <code>string</code>이나 <code>number</code> 같은 더 큰 타입에 포함된다.</p>
<pre><code class="language-ts">const name = &#39;hello&#39;; // &#39;hello&#39; 이라는 리터럴 타입
const rank = 1; // 1 이라는 리터럴 타입</code></pre>
<h3 id="2-타입-별칭">(2) 타입 별칭</h3>
<p>복잡한 타입에 이름을 붙이고 재사용하고 싶을 때 사용한다.</p>
<pre><code class="language-ts">type Point = [number, number];
type SearchQuery = string | string[];
type Result = SuccessResult | FailedResult;
type Coupon = 
  | PromotionCoupon
  | EmployeeCoupon
  | WelcomCoupon
  | RewardCoupon
  ;   </code></pre>
<h3 id="3-union">(3) Union</h3>
<p><code>A</code> 이거나 또는 <code>B</code>인 경우를 타입으로 만들고 싶을 때</p>
<pre><code class="language-ts">ClothingProduct | ShoeProduct</code></pre>
<h3 id="4-intersection">(4) Intersection</h3>
<p><code>A</code>와 <code>B</code>의 성질을 모두 갖는 타입을 만들고 싶을 때</p>
<pre><code class="language-ts">interface Entity {
  id: string;
  createdAt: Date;
  updatedAt: Date;
}

type Product = Entity &amp; {
  name: string;
  price: number;
  membersOnly?: boolean;
}</code></pre>
<p>하지만 보통 이럴 때는 <code>interface</code>와 상속을 사용하는걸 권장한다.</p>
<pre><code class="language-ts">interface Entity {
  id: string;
  createdAt: Date;
  updatedAt: Date;
}

interface Product extends Entity {
  name: string;
  price: number;
  membersOnly?: boolean;
}</code></pre>
<h3 id="4-keyof-연산자">(4) keyof 연산자</h3>
<p>객체 타입에서 프로퍼티 이름들을 모아서 Union한 타입으로 만들고 싶을 때 사용한다.</p>
<pre><code class="language-ts">interface Product {
  id: string;
  name: string;
  price: number;
  membersOnly?: boolean;
}

type ProductProperty = keyof Product; // &#39;id&#39; | &#39;name&#39; | &#39;price&#39; | &#39;membersOnly&#39;;</code></pre>
<h3 id="5-typeof-연산자">(5) typeof 연산자</h3>
<p>자바스크립트 코드에서 사용하면 결괏값이 문자열이지만, 타입스크립트 코드에서 쓸 때는 결과 값은 타입스크립트의 타입이다.</p>
<pre><code class="language-ts">const product: Product = {
  id: &#39;c001&#39;,
  name: &#39;블랙 후드 집업&#39;,
  price: 129000,
  salePrice: 98000,
  membersOnly: true,
};

console.log(typeof product); // 문자열 &#39;object&#39;

const product2: typeof product = { // 타입스크립트의 Product 타입
  id: &#39;g001&#39;,
  name: &#39;텀블러&#39;,
  price: 25000,
  salePrice: 19000,
  membersOnly: false,
};</code></pre>
<hr>

<h2 id="21-제네릭">21. 제네릭</h2>
<pre><code class="language-ts">const shoeSizes: number[] = [230, 250, 280];
shoeSizes.map((num) =&gt; {

});

const clothingSizes: string[] = [&#39;M&#39;, &#39;L&#39;, &#39;XL&#39;];
clothingSizes.map((names) =&gt; {

});

function printArray&lt;T&gt;(items: T[]) {
  for (const item of items) {
    console.log(item);
  }
}

printArray(shoeSizes);
printArray(clothingSizes);</code></pre>
<hr>

<h2 id="22-알아두면-유용한-제네릭-타입들">22. 알아두면 유용한 제네릭 타입들</h2>
<h3 id="1-javascript-기능들">(1) JavaScript 기능들</h3>
<h4 id="1-queryselector-함수">1. querySelector() 함수</h4>
<p>기본적으로 어떤 DOM 노드가 리턴될지 모르기 때문에 <code>HTMLElement</code>라는 일반적인 타입으로 정의된다. 하지만 타입을 확신할 수 있는 경우에는 아래 코드처럼 직접 제네릭 타입을 정의해 주면 된다.</p>
<pre><code class="language-ts">const elem = document.querySelector&lt;HTMLInputElement&gt;(&#39;input.username&#39;);</code></pre>
<h4 id="2-map">2. Map</h4>
<p>key와 value를 갖는 자료구조이다. 타입 파라미터로 key와 value의 타입을 정의하고 사용할 수 있다.</p>
<pre><code class="language-ts">const productMap = new Map&lt;string, Product&gt;();
productMap.set(product1.id, product1);
productMap.set(product2.id, product2);</code></pre>
<h4 id="3-set">3. Set</h4>
<p>배열과 비슷하지만 중복된 요소를 추가할 수 없는, 수학에서 집합에 해당하는 자료구조이다. 타입 파라미터로 요소의 타입을 정의하고 사용할 수 있다.</p>
<pre><code class="language-ts">const productSet = new Set&lt;Product&gt;();
productSet.add(product1);
productSet.add(product2);</code></pre>
<h3 id="2-유용한-타입들">(2) 유용한 타입들</h3>
<h4 id="1-key와-value-정하기-record">1. key와 value 정하기: Record</h4>
<p>객체에 key와 value 타입을 정해놓고 싶을 때 사용한다. <code>Map</code>과 비슷하지만 차이점은 순수한 객체에 타입만 추가한다는 점이다.</p>
<pre><code class="language-ts">const productMap: Record&lt;string, Product&gt; = {}
productMap[&#39;c001&#39;] = product1;
productMap[&#39;c002&#39;] = product2;</code></pre>
<h4 id="2-객체-프로퍼티-고르기-pick">2. 객체 프로퍼티 고르기: Pick</h4>
<pre><code class="language-ts">interface Product {
  id: string;
  name: string;
  price: number;
  membersOnly?: boolean;
}

type ProductInfo = Pick&lt;Product, &#39;name&#39; | &#39;price&#39;&gt;;</code></pre>
<p><code>Pick</code>으로 만든 타입은 아래와 같다. <code>name</code> 프로퍼티와 <code>price</code> 프로퍼티만 골라서 타입을 만들었다.</p>
<pre><code class="language-ts">type ProductInfo = {
    name: string;
    price: number;
} </code></pre>
<h4 id="3-객체의-프로퍼티-생략하기-omit">3. 객체의 프로퍼티 생략하기: Omit</h4>
<pre><code class="language-ts">interface Product {
  id: string;
  name: string;
  price: number;
  membersOnly?: boolean;
}

type ProductInfo = Omit&lt;Product, &#39;id&#39; | &#39;membersOnly&#39;&gt;;</code></pre>
<p><code>Omit</code>으로 만든 타입은 아래와 같다. <code>id</code>와 <code>membersOnly</code>를 제외하고 타입을 만들었다.</p>
<pre><code class="language-ts">type ProductInfo = {
    name: string;
    price: number;
} </code></pre>
<h4 id="4-union-제거하기-exclude">4. Union 제거하기: Exclude</h4>
<p>Union을 사용해서 <code>PromotionCoupon</code> 또는 <code>EmployeeCoupon</code> 또는 <code>WelcomeCoupon</code> 또는 <code>RewardCoupon</code>인 타입을 <code>Coupon</code>이라고 했다. 여기서 <code>EmployCoupon</code>에 해당하는 것만 제거를 하고 싶을 때 <code>Exclude</code>를 사용할 수 있다.</p>
<pre><code class="language-ts">type Coupon = 
  | PromotionCoupon
  | EmployeeCoupon
  | WelcomCoupon
  | RewardCoupon
  ;

type InternalCoupon = EmployeeCoupon;
type PublicCoupon = Exclude&lt;Coupon, InternalCoupon&gt;;
// type PublicCoupon = PromotionCoupon | WelcomCoupon | RewardCoupon</code></pre>
<h4 id="5-함수-파라미터-타입-가져오기-parameters">5. 함수 파라미터 타입 가져오기: Parameters</h4>
<p>함수 파라미터들의 타입을 함수의 타입을 통해 정의할 수 있다. 만약 함수의 타입이 아니라, 선언된 함수라면 <code>typeof</code> 연산자로 함수의 타입으로 만들어서 사용하면 된다.</p>
<pre><code class="language-ts">function addToCart(id: string, quantity: number = 1): boolean {
  // ...
  return true;
}

type AddToCartParameters = Parameters&lt;typeof addToCart&gt;;
// type AddToCartParameters = [id: string, quantity: number | undefined]</code></pre>
<h4 id="6-함수-리턴-타입-가져오기-returntype">6. 함수 리턴 타입 가져오기: ReturnType</h4>
<p><code>Parameters</code>와 마찬가지로 함수의 리턴 타입을 가져온다.</p>
<pre><code class="language-ts">function addToCart(id: string, quantity: number = 1): boolean {
  // ...
  return true;
}

type AddToCartResult = ReturnType&lt;typeof addToCart&gt;;
// type AddToCartResult = boolean</code></pre>
<hr>

<h2 id="22-자주-사용하는-옵션">22. 자주 사용하는 옵션</h2>
<p>앞에서 프로젝트를 만들 때 <code>tsc --init</code> 명령어로 <code>tsconfig.json</code> 파일을 생성했다. 이 파일에서 사용할 수 있는 여러 옵션들에 대해 자세히 살펴보도록 하겠다.</p>
<pre><code class="language-json">{
  &quot;compilerOptions&quot;: {
    &quot;target&quot;: &quot;es2016&quot;,                                  /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
    &quot;module&quot;: &quot;commonjs&quot;,                                /* Specify what module code is generated. */
    &quot;esModuleInterop&quot;: true,                             /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables &#39;allowSyntheticDefaultImports&#39; for type compatibility. */
    &quot;forceConsistentCasingInFileNames&quot;: true,            /* Ensure that casing is correct in imports. */
    &quot;strict&quot;: true,                                      /* Enable all strict type-checking options. */
    &quot;skipLibCheck&quot;: true                                 /* Skip type checking all .d.ts files. */
  }
}</code></pre>
<p><code>compilerOptions</code>에서 사용할 수 있는 옵션 중에서 중요한 것들에 대해 살펴보도록 하겠다.</p>
<h3 id="1-target--어떤-ecmascript-버전으로-변환할지">(1) target : 어떤 ECMAScript 버전으로 변환할지</h3>
<p>타입스크립트 코드는 빌드해서 자바스크립트로 변환한다. 이때 변환할 자바스크립트의 버전을 정할 수 있는데, 만약에 프로젝트가 과거 자바스크립트 표준만 지원하는 브라우저까지 지원해야 한다면, 여기서 오래된 버전을 설정할 수 있다. 2023년 기준으로 <code>tsconfig.json</code>을 생성했을 때, 기본적으로 ES7(ES2016)으로 변환하도록 되어있다.</p>
<p>코드를 실행할 브라우저나 Node.js 환경에 따라 선택하면 되는데, 브라우저의 경우 ES5는 거의 모든 브라우저에서 지원하고, ES6는 인터넷 익스플로러를 제외하면 대부분의 브라우저에서 지원한다. Node.js의 경우 v14 이후 버전을 쓰고 있다면 ES5, ES6을 대부분 지원한다. 잘 모르겠다면 일단은 ES6로 설정해 두고 쓰는 걸 추천한다.</p>
<h3 id="2-module--어떤-방식으로-모듈을-만들지">(2) module : 어떤 방식으로 모듈을 만들지</h3>
<p>자바스크립트 모듈에는 크게 두 가지 방식이 있는데, ES6부터 도입된 import/export 문법을 사용하는 ESM(ECMAScript Module) 방식이 있고, Node.js 같은 데서 기본적으로 사용하는 CJS(CommonJS) 방식이 있다.</p>
<p>이 옵션에서는 자바스크립트 코드로 트랜스파일할 때 어떤 모듈 문법으로 변환할지 선택할 수 있다. ESM을 쓰려면 <code>es6</code>, <code>es2020</code>과 같이 <code>es</code>로 시작하는 값을 쓰면 되고, CJS를 쓰려면 <code>commonjs</code>라고 쓰면 된다. 보통 Node.js 환경에서는 CJS를 사용하고, 프론트엔드 개발을 할 때는 보통 번들러에서 모듈을 알아서 처리해 주기 때문에 ESM, CJS 상관없이 쓸 수 있다. 어떤 걸 선택할지 모르겠다면 <code>commonjs</code>로 설정하는 걸 추천한다.</p>
<h3 id="3-esmoduleinterop--es-모듈을-안전하게-사용">(3) esModuleInterop : ES 모듈을 안전하게 사용</h3>
<p>ESM 문법에서 <code>import * as moment from &#39;moment</code>라든가 <code>import moment from &#39;moment&#39;</code>라는 문법은 다르다. 이 옵션을 <code>false</code>로하면 CJS로 변환했을 때 두 코드는 같은 형태의 코드 <code>const moment = require(&#39;moment&#39;)</code>로 변환된다. 안전하게 모듈을 사용하려면 <code>esModuleInterop</code> 옵션은 <code>true</code>로 해놓는 것을 추천한다.</p>
<h3 id="4-forceconsistentcasinginfilenames--파일의-대소문자-구분하기">(4) forceConsistentCasingInFileNames : 파일의 대소문자 구분하기</h3>
<p><code>main.ts</code>와 <code>Main.ts</code>는 macOS에서는 다르게 취급하고, Windows에서는 동일하게 취급한다. 어떤 환경에서 개발하더라도 반드시 대소문자구분을 명확하게 하겠다는 옵션이다. 이 옵션도 반드시 <code>true</code>로 해놓는 것을 추천한다.</p>
<h3 id="5-strict--엄격한-규칙들-켜기">(5) strict : 엄격한 규칙들 켜기</h3>
<h4 id="1-noimplicitany">1. noImplicitAny</h4>
<p>아무 타입을 직접 정의하지 않고, 타입 추론도 되지 않는 상태를 implicit any라고 하는데, 쉽게 말해 기존 자바스크립트 코드처럼 타입 없이 사용하는 걸 의미한다. 새로 시작하는 타입스크립트 프로젝트라면 반드시 켜는 걸 추천한다. 아래처럼 설정하면 <code>strict</code> 규칙들을 한꺼번에 설정하지만, <code>noImplicitAny</code>는 설정하지 않을 수도 있다.</p>
<pre><code>&quot;strict&quot;: true,
&quot;noImplicitAny&quot;: false,</code></pre><h4 id="2-strictnullchecks">2. strictNullChecks</h4>
<p><code>null</code>이 될 가능성이 있다면, 이런 경우를 반드시 처리하도록 하는 옵션이다. 이것도 되도록이면 켜 놓는것을 추천한다. 예를 들어 아래와 같이 strict null check를 할 때 오류가 발생한다. <code>num</code> 변수가 <code>null</code>이 될 수도 있기 때문이다. 반면에 <code>strictNullChecks</code>가 꺼져있다면 타입 오류가 나지 않는다. 대신 실행하는 도중에 <code>num</code> 변수에 <code>null</code> 값이 들어간다면 런타임 오루가 발생할 것이다.</p>
<pre><code class="language-ts">let num: number | null;
// ...
num -= 1;
~~~</code></pre>
<p>되도록이면 <code>strictNullChecks</code>를 키고, 반드시 null check를 하는 걸 추천한다.</p>
<pre><code class="language-ts">let num: number | null;
// ...
if (num !== null) {
  num -= 1;
}</code></pre>
<h3 id="6-skiplibcheck--설치한-패키지의-타이-검사하지-않기">(6) skipLibCheck : 설치한 패키지의 타이 검사하지 않기</h3>
<p><code>node_modules</code> 폴더에 설치된 패키지들의 타입 검사를 하지 않는 옵션이다. 패키지 개발 과정에서 대부분 타입 검사가 이뤄지기 때문에, 중복으로 하지 않아도 된다. 그래서 이 옵션을 사용하는 걸 추천한다.</p>
<h3 id="7-rootdir--최상위-폴더">(7) rootDir : 최상위 폴더</h3>
<p>타입스크립트 컴파일러가 처리할 타입스크립트 파일들의 최상위 폴더를 정하는 옵션이다. 기본 값으로는 처리하는 파일들의 경로를 종합해서 최상위 폴더를 정한다. 만약 <code>tsconfig.json</code> 파일과 같은 폴더에 있는 <code>src</code> 폴더를 최상위 폴더로 정하고 싶다면 아래와 같이 작성하면 된다.</p>
<pre><code>{  
  &quot;compilerOptions&quot;: {
    &quot;rootDir&quot;: &quot;src&quot;,
  }
}</code></pre><h3 id="8-outdir--자바스크립트-파일을-생성할-폴더">(8) outDir : 자바스크립트 파일을 생성할 폴더</h3>
<p><code>outDir</code>에 지정된 폴더 안에다가 <code>rootDir</code>의 디렉터리 구조에 따라서 자바스크립트 파일을 만든다. 값을 지정하지 않으면 소스코드 파일과 같은 폴더에 자바스크립트 파일을 만든다.</p>
<pre><code>{  
  &quot;compilerOptions&quot;: {
    &quot;outDir&quot;: &quot;dist&quot;,
  }
}</code></pre><h3 id="9-resolvejsonmodule--json-파일-임포트하기">(9) resolveJsonModule : JSON 파일 임포트하기</h3>
<p><code>.json</code> 파일을 임포트해서 사용하고 싶다면 이 옵션을 켜야 한다.</p>
<pre><code class="language-ts">import data from &#39;data.json&#39;;
// ...</code></pre>
<h3 id="10-include와-exclude">(10) include와 exclude</h3>
<p><code>tsc</code>로 트랜스파일할 때 포함할 경로(<code>include</code>)와 포함하지 않을 경로(<code>exclude</code>)를 정해줄 수 있다. 배열로 경로 패턴을 적어주면 된다. <code>**/*</code>라는 코드는 아래의 모든 폴더, 모든 파일을 의미한다. 참고로 이런 패턴을 <a href="https://ko.wikipedia.org/wiki/%EA%B8%80%EB%A1%9C%EB%B8%8C_(%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D)">Glob 패턴</a>이라고 한다.</p>
<pre><code>{
  &quot;compilerOptions&quot;: {
    &quot;target&quot;: &quot;es2016&quot;,                                  /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
    &quot;module&quot;: &quot;commonjs&quot;,                                /* Specify what module code is generated. */
    &quot;esModuleInterop&quot;: true,                             /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables &#39;allowSyntheticDefaultImports&#39; for type compatibility. */
    &quot;forceConsistentCasingInFileNames&quot;: true,            /* Ensure that casing is correct in imports. */
    &quot;strict&quot;: true,                                      /* Enable all strict type-checking options. */
    &quot;skipLibCheck&quot;: true                                 /* Skip type checking all .d.ts files. */
  }
  &quot;include&quot;: [&quot;src/**/*&quot;, &quot;tests/**/*&quot;],
  &quot;exclude&quot;: [&quot;bin/**/*&quot;],
}</code></pre><hr>

<h2 id="23-tsconfigjson-파일-불러오기">23. tsconfig.json 파일 불러오기</h2>
<p><code>tsconfig.json</code> 파일을 매번 설정하는 건 귀찮기도 하고, 만약에 여러 프로젝트를 개발한다면 항상 똑같은 설정을 쓰고 싶을 것이다. 이럴 때 매번 복사/붙여넣기 하는 건 유지 보수하는데 좋지 않다. 이럴 때 사용할 수 있는 옵션이 바로 <code>extends</code>이다. 아래와 같이 <code>extends</code> 옵션으로 원하는 설정 파일의 경로를 적어주면 <code>tsconfig.json</code> 파일을 불러올 수 있다. 추가로 옵션을 사용해서 덮어쓸 수도 있다.</p>
<pre><code class="language-json">{
  &quot;extends&quot;: &quot;&lt;설정 파일 경로&gt;&quot;
}</code></pre>
<h3 id="1-tsconfigbases-예시">(1) tsconfig/bases 예시</h3>
<p>예를 들어 권장하는 <code>tsconfig.json</code> 설정들을 모아놓은 <a href="https://github.com/tsconfig/bases">tsconfig/bases</a> 리포지토리에 있는 설정 파일을 패키지로 설치한 다음, 불러와서 사용해 보도록 하겠다.</p>
<h4 id="1-패키지-설치하기">1. 패키지 설치하기</h4>
<pre><code>npm install --save-dev @tsconfig/recommended</code></pre><p>타입스크립트 패키지는 개발 환경에서만 사용하기 때문에, <code>--save-dev</code> 옵션으로 설치해야 한다.</p>
<h4 id="2-extends-설정하기">2. extends 설정하기</h4>
<p><code>tsconfig.json</code> 파일의 맨 위에 <code>extends</code> 속성을 추가해 준다. 기존에 사용하던 옵션은 지워준다.</p>
<pre><code>{
  &quot;extends&quot;: &quot;@tsconfig/recommended/tsconfig.json&quot;,
  &quot;compilerOptions&quot;: {
  }
}</code></pre><h4 id="3-옵션-덮어쓰기">3. 옵션 덮어쓰기</h4>
<p>bases/recommended.json 파일을 살펴보면 아래와 같은 옵션을 사용하고 있다.</p>
<pre><code>{
  &quot;compilerOptions&quot;: {
    &quot;target&quot;: &quot;ES2015&quot;,
    &quot;module&quot;: &quot;commonjs&quot;,
    &quot;strict&quot;: true,
    &quot;esModuleInterop&quot;: true,
    &quot;skipLibCheck&quot;: true,
    &quot;forceConsistentCasingInFileNames&quot;: true
  },
  &quot;$schema&quot;: &quot;https://json.schemastore.org/tsconfig&quot;,
  &quot;display&quot;: &quot;Recommended&quot;
}</code></pre><p><code>target</code>을 <code>ES2016</code>으로 사용하고 싶어서 이 옵션을 덮어쓰도록 하겠다.</p>
<pre><code>{
  &quot;extends&quot;: &quot;@tsconfig/recommended/tsconfig.json&quot;,
  &quot;compilerOptions&quot;: {
    &quot;target&quot;: &quot;ES2016&quot;
  }
}</code></pre><p>이런 식으로 <code>extends</code> 옵션을 사용하면 패키지로 설치한 <code>tsconfig.json</code> 파일을 불러올 수도 있고, 직접 만든 <code>tsconfig.json</code> 파일을 불러올 수도 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React 컴포넌트 설계]]></title>
            <link>https://velog.io/@gaebar_top/React-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EC%84%A4%EA%B3%84</link>
            <guid>https://velog.io/@gaebar_top/React-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EC%84%A4%EA%B3%84</guid>
            <pubDate>Sat, 13 Jan 2024 07:17:40 GMT</pubDate>
            <description><![CDATA[<h1 id="react-컴포넌트를-만들-때-마주하는-어려움">React 컴포넌트를 만들 때 마주하는 어려움</h1>
<h2 id="1-react-컴포넌트를-만드는-예시">1. React 컴포넌트를 만드는 예시</h2>
<h3 id="1-리액트-컴포넌트를-만들-때-고려하는-것">(1) 리액트 컴포넌트를 만들 때 고려하는 것</h3>
<p>프론트엔드 개발자가 리액트를 활용해서 컴포넌트를 만들 때 고려하는 것들이 있다. 재사용성이 좋은가, 쉽게 유지보수 가능한가, 충분히 빠르게 동작하는가 등이 있다. 이번에는 재사용성, 유지보수, 코드 가독성과 같은 개발자 생산성 관점에서 어떻게 리액트 컴포넌트를 만드는 게 좋을지 배워보도록 한다.</p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/f8717f93-f862-454a-ba5d-22dcf08e3a04/image.png" alt=""></p>
</blockquote>
<p>리액트르 막 배운 경우에는 쉽게 하나의 페이지 컴포넌트로 만들 수 있다.</p>
<pre><code># 폴더 구조

pages
    FolderPage.jsx
styles
    FolderPage.css</code></pre><pre><code class="language-jsx">// pages/FolderPage.jsx
import &#39;styles/FolderPage.css&#39;
...
import axios from &#39;axios&#39;

const FolderPage = () =&gt; {
  const [
    linkValue,
    setLinkValue,
  ] = useState(&quot;&quot;);
  const [
    searchValue,
    setSearchValue,
  ] = useState(&quot;&quot;);

  const [
    user,
    setUser,
  ] = useState({ profileImage: &quot;&quot;, email: &quot;&quot; });
  const [
    folders,
    setFolders,
  ] = useState([]);
  const [
    links,
    setLinks,
  ] = useState([]);

  const handleLinkValueChange = (event) =&gt; setLinkValue(event.target.value);
  const handleSearchValueChange = (event) =&gt; setSearchValue(event.target.value);

  const handleAddLinkClick = () =&gt; axios.post(&quot;/links&quot;, { link: linkValue });
  const handleAddFolderClick = () =&gt; openFolderModal();
  const handleShareClick = () =&gt; openShareModal();
  const handleRenameClick = () =&gt; openRenameModal();
  const handleDeleteClick = () =&gt; openDeleteModal();

  useEffect(() =&gt; {
    const fetchUser = async () =&gt; {
      const response = await axios.get(&quot;/user&quot;);
      const { data } = await response.json();

      setUser(data);
    };

    const fetchFolders = async () =&gt; {
      const response = await axios.get(&quot;/folders&quot;);
      const { data } = await response.json();

      setFolders(data);
    };

    const fetchLinks = async () =&gt; {
      const response = await axios.get(`/links`);
      const { data } = await response.json();

      setLinks(data);
    };

    fetchUser();
    fetchFolders();
    fetchLinks();
  }, []);

  return (
    &lt;div className=&quot;page&quot;&gt;
      &lt;header&gt;
        &lt;nav&gt;
          &lt;img src=&quot;logo.png&quot; /&gt;
      &lt;div className=&quot;profile&quot;&gt;
        &lt;img src={user.profileImage} /&gt;
        &lt;span&gt;{user.email}&lt;/span&gt;
      &lt;/div&gt;
        &lt;/nav&gt;
        &lt;div className=&quot;addLinkBox&quot;&gt;
          &lt;div className=&quot;addLink&quot;&gt;
            &lt;img src=&quot;link.png&quot; /&gt;
            &lt;input
              className=&quot;addLinkInput&quot;
              placeholder=&quot;링크를 추가해 보세요&quot;
              value={linkValue}
              onChange={handleLinkValueChange}
            /&gt;
            &lt;button
              className=&quot;addLinkButton&quot; 
              onClick={handleAddLinkClick}
            &gt;
              &lt;span&gt;추가하기&lt;/span&gt;
            &lt;/button&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/header&gt;
      &lt;div className=&quot;search&quot;&gt;
        &lt;img src=&quot;search.png&quot; /&gt;
        &lt;input
          className=&quot;searchInput&quot;
          placeholder=&quot;제목을 검색해 보세요&quot;
          value={searchValue}
          onChange={handleSearchValueChange}
        /&gt;
      &lt;/div&gt;
      &lt;div className=&quot;contentBox&quot;&gt;
        &lt;div className=&quot;folderList&quot;&gt;
          {folders.map((folder) =&gt; (
            &lt;Link
              key={folder.id}
              className={`folderItem ${folder.id === id ? &quot;selected&quot; : &quot;&quot;}`}
              href={`/folder/${folder.id}`}
            &gt;
              &lt;span&gt;{folder.name}&lt;/span&gt;
            &lt;/Link&gt;
          ))}
        &lt;/div&gt;
        &lt;button className=&quot;addFolderButton&quot; onClick={handleAddFolderClick}&gt;
          폴더 추가
          &lt;img src=&quot;plus.png&quot; /&gt;
        &lt;/button&gt;
        &lt;h2&gt;{folders.find((folder) =&gt; folder.id === id)?.name}&lt;/h2&gt;
        &lt;button className=&quot;shareButton&quot; onClick={handleShareClick}&gt;
          &lt;img src=&quot;share.png&quot; /&gt;
          공유
        &lt;/button&gt;
        &lt;button className=&quot;renameButton&quot; onClick={handleRenameClick}&gt;
          &lt;img src=&quot;pencil.png&quot; /&gt;
          이름 변경
        &lt;/button&gt;
        &lt;button className=&quot;deleteButton&quot; onClick={handleDeleteClick}&gt;
          &lt;img src=&quot;trashcan.png&quot; /&gt;
          삭제
        &lt;/button&gt;
        {links.map((link) =&gt; (
          &lt;Link key={link.id} href={link.url}&gt;
            &lt;div className=&quot;linkItem&quot;&gt;
              &lt;img src={link.imageSource} /&gt;
              {/* 카드에 들어갈 내용 ... */}
            &lt;/div&gt;
          &lt;/Link&gt;
        ))}
      &lt;/div&gt;
      &lt;footer&gt;
        {/* 푸터에 들어갈 내용 ... */}
      &lt;/footer&gt;
    &lt;/div&gt;
  );
};

export default FolderPage;</code></pre>
<p>위 코드의 경우 코드 양이 매우 많이 들어간다. 이로 인해 해당 코드가 어떤 기능을 하는지 빠르게 파악하기 어렵다.</p>
<h3 id="2-추가적인-작업-요청이-발생한-경우">(2) 추가적인 작업 요청이 발생한 경우</h3>
<p>앞의 페이지 작업이 끝난 뒤 <code>/shared</code> 페이지도 만들어 달라는 요청이 있다고 가정해보자.</p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/f54fca66-0b32-4212-a8e8-6b54455fc9b3/image.png" alt=""></p>
</blockquote>
<p><code>/folder</code> 페이지와 동일하게 사용하는 컴포넌트가 많이 보인다. 이미 만들었던 코드를 다시 활용하기 위해 크게 두 가지 선택지에 놓이게 된다.</p>
<ol>
<li><code>/folder</code> 페이지에서 반복되는 부분을 복사해서 <code>/shared</code> 페이지로 붙여 넣기</li>
<li><code>/folder</code> 페이지에서 컴포넌트를 잘 분리해서 새로운 컴포넌트를 만들기</li>
</ol>
<p>1번의 선택은 두 페이지에서 공통적으로 사용하는 영역에 변경이 있는 경우, 변경해야 하는 부분을 찾고 수정하는 작업을 두 번 반복해야 한다. 또한 둘 중 하나의 페이지에는 수정을 누락할 위험도 높다.</p>
<p>2번 선택지로 결정하고 분리를 위해 <code>/folder</code> 페이지에서 재사용 가능한 컴포넌트를 분리해서 새로운 컴포넌트(nav 컴포넌트, 링크 검색 컴포넌트, 링크 카드 리스트 등)를 만든다. <code>/folder</code> 페이지에서 분리한 새로운 컴포넌트를 사용하도록 수정하고 <code>/shared</code> 페이지에는 분리한 새로운 컴포넌트를 사용한다.</p>
<p>2번 선택지의 수정 및 추가 작업을 하면서 다음과 같은 문제를 느껴야 한다. <code>/shared</code> 페이지 만드는 작업을 해야 하는데, 왜 <code>/folder</code> 페이지를 수정하고 있는가?</p>
<p>살펴본 예시와 같이 페이지 전체를 하나의 컴포넌트로 만들어서 발생하는 문제가 아니라도, 개발자가 모든 경우의 수를 미리 예측하고 컴포넌트를 분리할 수는 없기 때문에 유사한 상황을 종종 마주하게 된다.</p>
<hr>

<h1 id="관심사의-분리-separation-of-concerns">관심사의 분리 (Separation of Concerns)</h1>
<h2 id="1-관심사의-분리">1. 관심사의 분리</h2>
<p>관심사의 분리란 컴퓨터 프로그램을 관심사 별로 구별해서 분리하는 설계 원칙이다.</p>
<p>분리를 하려면 경계를 세워야 하는데 경계란 주어진 책임을 설명하는 논리적이거나 물리적인 제한을 의미한다. 소스 구성에 대한 프로젝트, 폴더 구조를 포함하기도 한다.</p>
<p>관심사 분리의 목표는 시스템을 분리되지 않는 파트들로 조각내는 것이 아니라 시스템을 반복하지 않고 각각의 책임을 가진 요소들로 구성하는 것에 있다. 앞으로 책임이란 단어를 자주보게 되는데 책임은 특정 코드가 수행해야 하는 동작이라고 생각하면 된다. 관심사의 분리를 잘하면 아래와 같은 효과를 얻을 수 있다.</p>
<ul>
<li>컴포넌트의 불필요한 반복이 없어지고, 책임이 단일화되어 전체 시스템을 유지보수하기 쉽게 만든다.</li>
<li>시스템 전체가 유지보수성이 올라가 더 안정적이게 된다.</li>
<li>각각의 컴포넌트가 단일 책이으로 자신의 관심사만 집중해서 확장 가능성을 만든다.</li>
<li>고유한 책임들이 잘 나누어지면 팀 간의 필요한 협력을 최소화시키고, 각 팀이 그들의 책임과 핵심 경쟁력에 집중할 수 있게 해 준다. 이를 통해 전체 비즈니스 목표 달성에 도움을 준다.</li>
</ul>
<h2 id="2-관심사의-분리---수평적인-분리">2. 관심사의 분리 - 수평적인 분리</h2>
<p>수평적인 관심사는 애플리케이션 내에 동일한 역항르 수행하는 기능의 논리적 단위로 분리한다. 마치 건물의 층이 나누어져 있는 모습과 유사하고, 크게 아래와 같이 세 개의 레이어로 나누어 볼 수 있다.</p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/4fdfb337-bcaa-4f05-b4ab-08e0bc800e06/image.png" alt=""></p>
</blockquote>
<ul>
<li>Presentation Layer: 보이는 요소인 HTML, CSS, UI에 필요한 상태관리로 이루어진 UI 컴포넌트</li>
<li>Business Layer: 비즈니스 로직과 정책 등에 관련된 컴포넌트</li>
<li>Resource Access Layer: 유저에게 제공할 데이터를 받기 위해 백엔드 서버에 요청, 로컬 스토리지 접근, 외부 api 서버 요청 등 외부 정보 접근과 관련된 훅 또는 함수</li>
</ul>
<p>이렇게 레이어를 분리하면 UI의 스타일에 문제가 있을 때 Presentation Layer만 보면 되고, 요청한 데이터를 받지 못하는 문제가 있으면 Resource Access Layer만 보면 문제를 확인할 수 있다. 또한 비즈니스의 정책 변경이 있다면 해당하는 Business Layer만 찾아서 수정하면 된다. 이처럼 수평적인 분리를 통해 문제를 빠르게 발견해 고칠 수 있고, 변경이 필요한 경우 빠르고 정확하게 반영할 수 있어 유지보수가 쉬워진다. 그리고 반복해서 필요한 UI, 비즈니스 규칙, 데이터 요청 등을 재사용하기 쉬워진다.</p>
<h2 id="3-관심사의-분리---수직적인-분리">3. 관심사의 분리 - 수직적인 분리</h2>
<p>수직적인 관심사는 애플리케이션의 동일한 도메인(비즈니스 관심사)을 모듈로 묶어서 분리한다.</p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/70ace613-6dd1-4fa7-be63-04272504249b/image.png" alt=""></p>
</blockquote>
<p>이러한 분리는 각 도메인의 책임을 분명하게 해 준다. 또한 서로 다른 개발조직이 각각의 모듈에 집중할 수 있어 관리하기 편하게 해 준다.</p>
<p>예시로 보았던 <code>/folder</code> 페이지 수직적 관심사를 살펴보면 아래와 같이 나누어 볼 수 있다.</p>
<pre><code>user
folder
link
nav
footer</code></pre><p>지금까지 수평적 분리와 수직적 분리를 살펴보았는데, 둘 중 하나를 선택해야 할 필요는 없고, 필요에 따라 이 둘을 함께 적용할 수도 있다.</p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/a3868ac9-6ad6-424e-9d90-abe89c346fd4/image.png" alt=""></p>
</blockquote>
<hr>

<h1 id="react-컴포넌트에-관심사의-분리-적용하기">React 컴포넌트에 관심사의 분리 적용하기</h1>
<h2 id="1-react-컴포넌트에-관심사의-분리-적용">1. React 컴포넌트에 관심사의 분리 적용</h2>
<h3 id="1-수평적-분리">(1) 수평적 분리</h3>
<h4 id="1-presentational--container-패턴">1. Presentational &amp; Container 패턴</h4>
<p>Presentational and Container(또는 Smart and Dumb) 패턴을 활용해 Presentational Layer를 분리할 수 있다. 해당 패턴에 대해 간략히 알아보면, Presentational 컴포넌트는 사용자가 보고, 조작하는 UI 컴포넌트이다. UI만을 위한 상태를 제외하고는 상태를 가지지 않고 Container 컴포넌트가 내려준 props를 통해 조작된다. Container 컴포넌트는 데이터를 받거나 비즈니스 로직을 설정할 수 있고, UI 컴포넌트를 포함할 수 있고, UI 컴포넌트에 props를 전달해 UI를 조작한다.</p>
<h4 id="2-데이터-접근-분리">2. 데이터 접근 분리</h4>
<p>Container 컴포넌트에 데이터를 받아오는 부분이 있는 경우가 많은데, 이는 분리할 수 있다. 이때 리액트의 커스텀 훅을 활용하면, 데이터 접근 로직만 분리해서 재사용도 가능하다.</p>
<h4 id="3-유틸리티-함수-분리">3. 유틸리티 함수 분리</h4>
<p>유틸리티 함수란 계산과 처리를 대신하는 일반 함수를 말한다. UI 컴포넌트, 데이터 접근을 위한 훅, 비즈니스 로직 등을 만들다보면 유틸리티 성격의 함수들을 만들어 사용하게 되는ㄷ 이런 함수들도 분리할 수 있다. 분리하게 되면 재사용할 수도 있고, 분리된 환경에서 유틸리티 함수만 테스트하기도 쉬워진다. 지금까지 크게 네 가지로 분리해 봤고, 분리한 관심사가 잘 드러나도록 이름을 붙인다.</p>
<ul>
<li>UI 컴포넌트 -&gt; ui</li>
<li>데이터 접근 -&gt; data-access</li>
<li>데이터 접근을 제외한 Container 컴포넌트 -&gt; feature</li>
<li>유틸리티 함수 -&gt; util</li>
</ul>
<p>위 관심사들을 분리했지만, 의존성을 아무렇게나 가져가면 분리한 의미가 없어지게 된다.</p>
<p>ui 컴포넌트에서 특정 data-access 훅을 <code>import</code>해서 사용한다면 ui 컴포넌트는 data-access 훅에 의존하는 컴포넌트가 된다. 이렇게 되면 ui 컴포넌트에 다른 data-access 사용이 필요한 경우 사용할 수 없게 된다. ui 컴포펀트가 다른 ui 컴포넌트를 의존하는 경우에는, 그 자체로 새로운 ui 컴포넌트가 되고 다른 도메인에서 사용하는데 문제될 것이 없다. data-access를 의존하면 안 되는 같은 이유로 ui 컴포넌트는 feature 컴포넌트를 의존해서 안된다.</p>
<p>feature 컴포넌트에서는 data-access를 활용해서 데이터를 가져오고 이를 활용해 ui 컴포넌트의 props로 전달해야 한다. 또한, feature 컴포넌트는 다른 feature 컴포넌트를 사용할 수도 있다.</p>
<p>이러한 관계들을 반영해 아래와 같이 의존성을 정리해 볼 수 있다.</p>
<ul>
<li>ui: ui, uitl에 의존할 수 있다.</li>
<li>data-acess: data-access, util에 의존할 수 있다.</li>
<li>feature: feature, ui, data-access, util 모두에 의존할 수 있다.</li>
<li>util: util끼리만 의존할 수 있다.</li>
</ul>
<h3 id="2-수직적-분리">(2) 수직적 분리</h3>
<p>수직적 분리는 서비스에 따라 다른데, 비즈니스 도메인에 따라 나누면 좋다. 앞서 살펴본 예시의 경우 user, folder, link로 나눠볼 수 있고, nav, footer의 경우 여러 페이지에서 공유하는 사용이 필요해 sharing이라는 관심사로 묶어볼 수 있다.</p>
<h2 id="2-예시에-관심사-분리-적용">2. 예시에 관심사 분리 적용</h2>
<pre><code># 폴더 구조

page-layout
    FolderLayout
        FolderLayout.jsx
        FolderLayout.module.css
    SharedLayout
        SharedLayout.jsx
        SharedLayout.module.css

pages
    FolderPage.jsx
    SharedPage.jsx

src
    user
        data-access-user
            useCurrentUser.js
            useGetUser.js
        ui-profile-badge
            ProfileBadge.jsx
            ProfileBadge.module.css
    folder
        data-access-folder
            useAddFolder.js
            useDeleteFolder.js
            useGetFolders.js
            useRenameFolder.js
        ui-folder-info
            FolderInfo.jsx
        feature-folder-tool-bar
            FolderToolBar.jsx
            FolderToolBar.module.css
        ui-folder-button
            FolderButton.jsx
            FolderButton.module.css
        ui-folder-list
            FolderList.jsx
            FolderList.module.css
        ui-icon-and-text-button
            IconAndTextButton.jsx
            IconAndTextButton.module.css
    link
        data-access-link
            useAddLink.js
            useGetLinks.js
        feature-link-form
            LinkForm.jsx
        feature-link-search
            LinkSearch.jsx
            useLinkSearch.js
        ui-link-form
            LinkForm.jsx
            LinkForm.module.css
        ui-card-list
            CardList.jsx
            CardList.module.css
        ui-card
            Card.jsx
            Card.module.css
        ui-search-bar
            SearchBar.jsx
            SearchBar.module.css
        util-map
            mapLinks.js
        util-filter
            filterLinkSearch.js
    sharing
        footer
            ui-footer
                Footer.jsx
        nav
            feature-nav
                Nav.jsx
            ui-top-nav
                TopNav.jsx
                TopNav.module.css
            ui-side-nav
                SideNav.jsx
                SideNav.module.css
        modal
            feature-modal
                Modal.jsx
                useModal.js
            ui-modal
                Modal.jsx
                Modal.module.css
        ui
            ui-cta-button
                CtaButton.jsx
                CtaButton.module.css
            ...
        util
            ...
        ...</code></pre><pre><code class="language-jsx">// pages/FolderPage.jsx
import { FolderLayout } from &#39;layouts/FolderLayout&#39;
import { Nav } from &#39;src/sharing/feature-nav&#39;
...
import { Footer } from &#39;src/sharing/ui-footer&#39;

const FolderPage = () =&gt; {
    const { links } = useGetLinks(folderId);
    const { searchInput, setSearchInput, filteredLinks } = useLinkSearch(links);
    const handleSearchInputChange = (event) =&gt; setSearchInput(event.target.searchInput);

  return (
        &lt;FolderLayout
            nav={&lt;Nav /&gt;}
            addLink={&lt;AddLink /&gt;}
            linkSearch={
                &lt;LinkSearch
                    value={searchInput}
                    onChange={handleSearchInputChange}
                /&gt;
            }
            folderToolBar={&lt;FolderToolBar /&gt;}
            cardList={&lt;CardList links={filteredLinks} /&gt;}
            footer={&lt;Footer /&gt;}
        /&gt;
    )
};

export default FolderPage;</code></pre>
<p>폴더 레이아웃 컴포넌트를 사용해서 props로 사용할 컴포넌트를 설정한다.</p>
<pre><code class="language-jsx">// page-layout/FolderLayout.jsx
export const FolderLayout = ({
    nav,
  addLink,
  linkSearch,
  folderToolBar,
  cardList,
  footer
}) =&gt; {
  return (
    &lt;div className=&quot;folderPage&quot;&gt;
      &lt;div className=&quot;nav&quot;&gt;{nav}&lt;/div&gt;
      &lt;div className=&quot;addLink&quot;&gt;{addLink}&lt;/div&gt;
      &lt;div className=&quot;linkSearch&quot;&gt;{linkSearch}&lt;/div&gt;
      &lt;div className=&quot;folderToolBar&quot;&gt;{folderToolBar}&lt;/div&gt;
      &lt;div className=&quot;cardList&quot;&gt;{cardList}&lt;/div&gt;
      &lt;div className=&quot;footer&quot;&gt;{footer}&lt;/div&gt;
    &lt;/div&gt;
    );
};</code></pre>
<p>폴더 레이아웃 컴포넌트는 폴더 페이지에 여러 컴포넌트들의 배치 스타일링을 담당한다.</p>
<pre><code class="language-jsx">// src/link/ui-card-list/CardList.jsx
export const CardList = ({ links }) =&gt; {
    return (
        &lt;div className=&quot;cardList&quot;&gt;
            {links.map((link) =&gt; &lt;LinkItem key={link.id} {...link} /&gt;)
        &lt;/div&gt;
    );
};</code></pre>
<p>links 데이터를 props로 받아 LinkItem들을 보여준다.</p>
<pre><code class="language-jsx">// pages/SharedPage.jsx
import { SharingLayout } from &#39;layouts/sharing/SharingLayout&#39;
import { Nav } from &#39;src/shared/nav/feature-nav&#39;
...
import { Footer } from &#39;src/shared/footer/feature-footer&#39;

const FolderPage = () =&gt; {
    const { links } = useGetLinks(id);
    const { searchInput, setSearchInput, filteredLinks } = useLinkSearch(links);
    const handleSearchInputChange = (event) =&gt; setSearchInput(event.target.searchInput);

  return (
        &lt;FolderLayout
            nav={&lt;Nav /&gt;}
            folderInfo={&lt;FolderInfo /&gt;}
            linkSearch={
                &lt;LinkSearch
                    value={searchInput}
                    onChange={handleSearchInputChange}
                /&gt;
            }
            cardList={&lt;cardList links={filteredLinks} /&gt;}
            footer={&lt;Footer /&gt;}
        /&gt;
    )
};

export default FolderPage;</code></pre>
<p><code>Nav</code>, <code>LinkSearch</code>, <code>CardList</code>, <code>Footer</code> 컴포넌트를 재사용하고, <code>useLinkSearch</code>도 재사용해서 만든다. LinkItem으로 만든 링크 내용이 담긴 카드 형태의 컴포넌트가 다른 도메인에서 사용할 수 있다면 컴포넌트 이름을 Card로 바꾸고 상위 폴더인 shared/ui로 옮겨 줄 수 있다.</p>
<h2 id="3-관심사의-분리-적용-전-생각해-볼-것">3. 관심사의 분리 적용 전 생각해 볼 것</h2>
<h3 id="1-trade-off">(1) Trade-off</h3>
<p>제일 처음에 살펴본 페이지 컴포넌트와 관심사 분리를 적용한 페이지 컴포넌트를 비교할 때 관심사 분리를 적용한 컴포넌트가 가진 장점들이 있다. 하지만 이러한 장점을 위해서 어떤 구조로 만들어야 할지 고민해야 하고, 각각을 분리하는 과정에서 더 많은 코드를 작성해야 하고, 컴포넌트 이름을 정해줘야 할 것도 더 많아진다. 즉, 개발자의 리소스가 더 많이 들어간다.</p>
<p>언제 어느 정도의 관심사 분리를 할 지는 프로젝트의 규모, 프로젝트의 성격, 피쳐의 생명주기 등을 고려해서 정하면 된다.</p>
<ul>
<li>규모가 크고 현재 서비스 중인 프로젝트라면 비즈니스 규칙 변경, UI 추가/수정, 오류 해결 등 유지보수가 중요하다. 또한 이미 만들어진 컴포넌트, 함수, 비즈니스 로직 등을 재사용해야 하는 경우도 많다. 이런 경우 당장은 조금 더 많은 시간이 들 수 있지만, 관심사 분리를 잘하면 장기적으로 개발 생산성이 크게 높아진다.</li>
<li>디자인 요소는 크게 중요하지 않고 기능 자체가 빠르게 필요한 관리자용 프로젝트라면 UI 라이브러리를 활용해 UI 컴포넌트 개발의 관심사는 외부에 의존하고 데이터 접근, 비즈니스 로직 등에 집중하는 것도 고려할 수 있다.</li>
<li>잠깐 사용하고 사라질 이벤트 페이지를 만들거나 재사용 가능성이 없는 컴포넌트를 만들어야 할 경우 많은 리소스를 사용하기보다 최소한의 리소스를 사용해 빠르게 만드는 게 좋을 수 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[포매터와 린터]]></title>
            <link>https://velog.io/@gaebar_top/%ED%8F%AC%EB%A7%A4%ED%84%B0%EC%99%80-%EB%A6%B0%ED%84%B0</link>
            <guid>https://velog.io/@gaebar_top/%ED%8F%AC%EB%A7%A4%ED%84%B0%EC%99%80-%EB%A6%B0%ED%84%B0</guid>
            <pubDate>Sat, 13 Jan 2024 06:11:57 GMT</pubDate>
            <description><![CDATA[<h1 id="코드-스타일이-중요한-이유">코드 스타일이 중요한 이유</h1>
<h2 id="1-코드-스타일이-중요한-이유">1. 코드 스타일이 중요한 이유</h2>
<p>여러 명의 개발자들이 같이 작업을 할 때 똑같은 함수를 작성하고 동작도 똑같이 되지만, 코드는 서로 다르게 작성될 때가 대부분이다. 한 연구 결과에 따르면 소프트웨어 개발자들이 약 60%의 업무시간을 코드를 읽고 이해하는데 사용한다고 한다. 마치 한 사람이 작성한 것처럼 일관된 스타일로 코드를 작성하는 건 효율적인 협업을 위해서 필수적이다. 가독성이 좋은 코드는 읽는데 작은 노력과 적은 시간이 들기 때문이다.</p>
<h2 id="2-코딩-컨벤션">2. 코딩 컨벤션</h2>
<p>수많은 기업과 프로젝트들이 코딩 컨벤션(Coding Convention)을 정해놓고 사용한다. 개발자들이 논의를 통해 어떤 스타일로 코드를 작성할지 약속해 놓은 규칙, 메뉴얼이라고 생각하면 된다.</p>
<blockquote>
<p><a href="https://standardjs.com/rules.html">JavaScript Standard Style</a>
<a href="https://google.github.io/styleguide/jsguide.html">Goolge JavaScript Style Guide</a>
<a href="https://github.com/airbnb/javascript">GitHub - airbnb/javascript: JavaScript Style Guide</a>
<a href="https://ui.toast.com/fe-guide/ko_CODING-CONVENTION">코딩컨벤션</a></p>
</blockquote>
<h2 id="3-포메터와-린터">3. 포메터와 린터</h2>
<p>실제 코딩 커벤션 문서를 보면 알겠지만, 항목도 너무 많고 다 외워서 사용하기도 어렵다. 그리고 외우는 사람이 있더라도 실수를 하기 마련이다. 이걸 지켰는지 확인하면서 일일이 신경쓰는 것도 비효율적이다. 그래서 개발자들은 이것도 대신해 주는 개발 도구를 사용한다. 바로 포매터(Formatter)와 린터(Linter)이다.</p>
<p>규칙을 미래 정해두면, 포매터는 코드 스타일을 검수/수정(포매팅)해 주고, 린터는 코드의 구조를 검사해서 잘못 작성된 코드가 없는지 확인(정적 분석: Static Analysis)해 준다. 포매터와 린터에 규칙만 잘 정해두면 마치 한 사람이 작성한 코드처럼 만들 수 있다.</p>
<h2 id="4-함께-사용하는-자동화-도구들">4. 함께 사용하는 자동화 도구들</h2>
<p>근데 포매터와 린터도 사람이 매번 실행하려니 비효율적이다. 검사는 걸 잊어버릴 때도 있는데, 이것마저도 대신해 주는 자동화 도구들을 사용한다.</p>
<h3 id="1-husky">(1) Husky</h3>
<p>최근에는 자바스크립트로 개발하는 경우에 husky라고 하는 패키지를 사용해서 포매터와 린터를 자동으로 실행하는 경우가 많다. husky를 사용하면 커밋을 하기 전이나 푸시를 하기 전에 포매터와 린터를 실행해서 자동으로 코드를 검사하도록 할 수 있다. 참고로 Husky는 Git에서 제공하는 깃 훅(Git Hooks)이라는 기능을 쉽게 사용할 수 있도록 해주는 패키지이기 때문에, 꼭 husky를 사용하지 않더라도 Git의 기능으로 이런 자동화를 할 수 있다.</p>
<h3 id="2-github-action">(2) Github Action</h3>
<p>자동화 스크립트를 본인의 컴퓨터에서만 실행하는 게 아니라, 깃허브 같은 원격 저장소에서도 실행할 수 있다. 린터를 실행하거나 하는 일도 깃허브에서 실행할 수 있다. 최근에는 깃허브에서 기본적으로 제공하는 Github Action이라는 걸 많이 사용한다.</p>
<hr>

<h1 id="editor-config">Editor Config</h1>
<h2 id="1-editor-config">1. Editor Config</h2>
<blockquote>
<p><a href="https://editorconfig.org/">Editor Config</a></p>
</blockquote>
<p>VSCode에서는 환경 설정을 하면 원하는 코드 스타일을 지키도록 할 수 있다. 예를 들어 들여쓰기를 스페이스 2칸으로 하고, 따움표는 반드시 홑따움표만 쓰도록 할 수 있다.</p>
<p>그런데 사람들이 전부 똑같이 VSCode를 사용하지는 않느다. WebStorm을 쓰는 사람도 있고, Sublime Text를 쓰는 사람들도 있다. 이 모든 사람들에게 설정을 공통적으로 적용할 수 있는 방법이 있는데, 바로 Editor Config이다.</p>
<p>Editor Config는 에디터의 설정을 코드로 할 수 있다. 예를 들어 <code>.editorconfig</code> 파일을 만들고 프로젝트 최상위 폴더에 배치해 두면, 기본적인 에디터 설정을 할 수 있다. 이렇게 하면 프로젝트 소스코드만 다운 받으면 별도로 에디터 설정을 할 필요없이 기본적인 코드 스타일을 설정할 수 있다는 장점이 있다.</p>
<pre><code># .editorconfig

root = true

[*]
indent_style = space          # 들여쓰기에 스페이스 사용
indent_size = 2               # 들여쓰기 크기는 스페이스 2개로 쓰기
charset = utf-8               # 파일 인코딩은 utf-8
insert_final_newline = true   # 파일 맨 마지막에 빈 줄 추가하기</code></pre><hr>

<h1 id="prettier">Prettier</h1>
<h2 id="1-패키지로-설정하기">1. 패키지로 설정하기</h2>
<p>프로젝트에 <code>devDependencies</code>로 <code>prettier</code>라는 패키지를 설치한다.</p>
<pre><code>npm install --save-dev prettier</code></pre><p>그리고 <code>.prettierrc.json</code>라는 파일을 만들어라. 이 파일은 Prettier에서 사용할 규칙을 적은 설정 파일이다. 예를 들어 &quot;매번 마지막에 콤마를 추가하기.&quot;, &quot;들여쓰기 크기는 2&quot;, &quot;세미콜론은 쓰지 않기.&quot;, &quot;홑따움표만 쓰기&quot;를 설정할 수 있다. 이런 규칙의 목록은 <a href="https://prettier.io/docs/en/options.html">Prettier 공식 문서의 Options</a>에서 확인할 수 있다.</p>
<pre><code>{
  &quot;trailingComma&quot;: &quot;es5&quot;,
  &quot;tabWidth&quot;: 2,
  &quot;semi&quot;: false,
  &quot;singleQuote&quot;: true
}</code></pre><p><code>prettier</code> 패키지를 직접 실행할 수도 있지만, NPM 스크립트를 통해 사용하는 걸 권장한다. 예를 들어 <code>prettier</code>라는 스크립트에 <code>prettier --write .</code>라고 설정을 한다고 가정하자. <code>--write</code>는 파일을 덮어써서 수정하겠다는 의미이고, <code>src/**/*.js</code>는 <code>src</code> 디렉토리 아래에 있는 모든 자바스크립트 파일을 의미한다.</p>
<pre><code># package.json

{
  &quot;name&quot;: &quot;formatter-and-lint&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;description&quot;: &quot;&quot;,
  &quot;main&quot;: &quot;index.js&quot;,
  &quot;scripts&quot;: {
    &quot;prettier&quot;: &quot;prettier --write src/**/*.js&quot;
  },
  &quot;author&quot;: &quot;&quot;,
  &quot;license&quot;: &quot;ISC&quot;,
  &quot;devDependencies&quot;: {
    &quot;prettier&quot;: &quot;^2.8.8&quot;
  }
}</code></pre><p>예를 들어 <code>src/test.js</code>라는 파일에 &#39;섭씨-화씨 변환 코드&#39;를 저장하고 <code>npm run prettier</code>를 실행하면, Prettier는 미리 정한 규칙에 따라 파일을 수정하게 된다.</p>
<pre><code># src/test.js

function c2f(value){return value*9/5+32}</code></pre><h2 id="2-vscode-확장-프로그램으로-사용하기">2. VSCode 확장 프로그램으로 사용하기</h2>
<p>VSCode에서는 Prettier를 VSCode 에디터 안에서 실행할 수 있다. Extension 메뉴에서 &quot;prettier&quot;를 검색하고 설치하면 된다.</p>
<h2 id="3-참고하면-좋은-자료">3. 참고하면 좋은 자료</h2>
<blockquote>
<p><a href="https://www.daleseo.com/js-prettier/">[자바스크립트] Prettier로 코딩 스타일 통일하기</a></p>
</blockquote>
<hr>

<h1 id="eslint">ESLint</h1>
<h2 id="1-reactcreate-react-app에서-사용하기">1. React(Create React App)에서 사용하기</h2>
<p>프로젝트 폴더에서 터미널을 열고, ESLint에서 제공하는 도구를 사용해서 초기화를 한다.</p>
<pre><code>npm init @eslint/config</code></pre><p>만약에 이 명령어를 처음 실행했다면 <code>@eslint/create-config</code>라는 패키지를 설치할 거냐고 물어보는데, <code>y</code>를 입력해준다.</p>
<pre><code>Need to install the following packages:
  @eslint/create-config@0.4.3
Ok to proceed? (y)</code></pre><p>설치 목적을 선택한다.</p>
<pre><code>? How would you like to use ESLint? … 
❯ To check syntax and find problems
  To check syntax, find problems, and enforce code style
  To check syntax only</code></pre><p>모듈 문법은 import/export를 위주로 쓴다면 그렇게 설정하라.</p>
<pre><code>? What type of modules does your project use? … 
❯ JavaScript modules (import/export)
  CommonJS (require/exports)
  None of these</code></pre><p>그 외에 필요한 설정을 한다. 성공적으로 설정을 완료했다면 <code>package.json</code>의 <code>devDependencies</code>에 두 패키지가 추가되어 있을 것이다.</p>
<pre><code>&quot;eslint&quot;: &quot;^8.39.0&quot;
&quot;eslint-plugin-react&quot;: &quot;^7.32.2&quot;</code></pre><p>NPM 스크립트를 추가하라.</p>
<pre><code>{
  ...
  &quot;script&quot;: {
    &quot;lint&quot;: &quot;eslint src/**/*.js&quot;,
    ...
  }
}</code></pre><p><code>src</code> 폴더 아래에 있는 모든 자바스크립트 파일에 <code>eslint</code>를 실행하도록 한다. <code>npm run lint</code>를 실행하면 <code>eslint</code>가 실행된다. <code>.eslintrc.js</code>라는 파일이 만들어져 있는데, 이 파일은 eslint에 적용할 규칙을 코드로 작성한 파일이다. 이걸 수정하면 적용할 규칙을 바꿀 수 있다.</p>
<pre><code>module.exports = {
  env: {
    browser: true,
    es2021: true,
  },
  extends: [&#39;eslint:recommended&#39;, &#39;plugin:react/recommended&#39;],
  overrides: [],
  parserOptions: {
    ecmaVersion: &#39;latest&#39;,
    sourceType: &#39;module&#39;,
  },
  plugins: [&#39;react&#39;],
  rules: {},</code></pre><h2 id="2-nextjs에서-사용하기">2. Next.js에서 사용하기</h2>
<p>Next.js에서는 기본적으로 ESLint를 별도의 설정 없이 사용할 수 있다. <code>.eslintrc.js</code> 파일을 만들고 <code>npm run lint</code>를 실행하면 된다.</p>
<blockquote>
<p><a href="https://nextjs.org/docs/pages/building-your-application/configuring/eslint">Basic Features: ESLint | Next.js</a></p>
</blockquote>
<h2 id="3-vscode에서-확장-프로그램으로-사용하기">3. VSCode에서 확장 프로그램으로 사용하기</h2>
<p>확장 프로그램을 설치하고 <code>.eslintrc.js</code> 같을 설정 파일을 만들어 두면 VSCode에서 바로바로 린트 결과를 확인할 수 있다. 확장 프로그램 탭에서 &quot;eslint&quot;를 검색해 설치하면 된다.</p>
<p>소스 코드 파일에서 왼쪽 아래의 아이콘을 클릭하거나, 소스 코드에 생긴 빨간 줄에 마우스 커서를 올려 보면 ESLint 오류를 확인할 수 있다.</p>
<h2 id="4-자주-쓰이는-설정들">4. 자주 쓰이는 설정들</h2>
<p>우선 규칙을 추가하는 법부터 배우도록 하자. <code>rules</code>라는 프로퍼티에서 객체를 만들고, 그 객체에서는 각 프로퍼티의 이름으로 규칙의 이름을 쓴 다음 규칙을 설정할지 말지를 결정한다. 어떤 규칙을 쓸 수 있는지는 <a href="https://eslint.org/docs/latest/rules/">Rules Reference</a>에서 확인해 볼 수 있다.</p>
<p>예를 들어 세미콜론을 쓸 건지 정하는 규칙(<a href="https://eslint.org/docs/latest/rules/semi">공식 문서</a>)은 <code>semi</code>이다. 공식 문서에 따르면 <code>always</code>라는 값을 쓰면 항상 세미콜론을 쓰고, <code>never</code>라는 값을 쓰면 항상 세미콜론을 생략한다. 이런 식으로 <code>rules</code>에다가 규칙을 설정하면 된다.</p>
<pre><code># .eslintrc.js

module.exports = {
  env: {
    browser: true,
    es2021: true,
  },
  extends: [
    &#39;eslint:recommended&#39;,
    &#39;plugin:react/recommended&#39;,
  ],
  overrides: [],
  parserOptions: {
    ecmaVersion: &#39;latest&#39;,
    sourceType: &#39;module&#39;,
  },
  plugins: [&#39;react&#39;],
  rules: {
    semi: [&#39;error&#39;, &#39;never&#39;],
  },
};</code></pre><p>만약 리액트 버전 17 이상을 쓰고 있다면 매번 <code>import React from &#39;react&#39;;</code>라는 코드를 쓸 필요가 없다. 그래서 이것과 관련된 코드를 <code>eslint</code>가 검사하지 않도록 <code>react/jsx-runtime</code>이라는 설정을 상속해 주어야 한다.(<a href="https://www.npmjs.com/package/eslint-plugin-react">링크</a>) 쉽게 말해 다른 <code>eslintrc</code> 파일을 현재 <code>eslintrc</code> 파일에서 불러와서 쓰는 거라고 생각하면 된다.</p>
<pre><code>module.exports = {
  env: {
    browser: true,
    es2021: true,
  },
  extends: [
    &#39;eslint:recommended&#39;,
    &#39;plugin:react/recommended&#39;,
    **&#39;plugin:react/jsx-runtime&#39;,**
  ],
  overrides: [],
  parserOptions: {
    ecmaVersion: &#39;latest&#39;,
    sourceType: &#39;module&#39;,
  },
  plugins: [&#39;react&#39;],
  rules: {},
};</code></pre><p>그 밖에도 많은 사람들이 규칙을 하나하나 정의하기 보다는 다른 사람들이 만들어 놓은 규칙에 일부만 수정해서 사용하는 경우가 많다. 그 중에서도 가장 많이 사용하는 규칙 중 하나는 에어비앤비에서 만든 규칙들이다.</p>
<blockquote>
<p><a href="https://www.npmjs.com/package/eslint-config-airbnb">npm: eslint-config-airbnb</a></p>
</blockquote>
<p><code>eslint-config-airbnb</code>라는 패키지는 앞에서 살펴 본 <code>plugin:react/jsw-runtime</code>이랑 마찬가지로 미리 만들어 놓은 <code>eslintrc</code> 파일이다. 이 패키지를 설치한 다음 프로젝트에서 불러와서 사용할 수 있다. 패키지 공식 문서의 설명에 따라 <code>eslint-config-airbnb</code> 패키지를 설치하고, <code>extends</code>에 추가하면 된다.</p>
<pre><code>module.exports = {
  env: {
    browser: true,
    es2021: true,
  },
  extends: [
    &#39;eslint:recommended&#39;,
    &#39;plugin:react/recommended&#39;,
    &#39;plugin:react/jsx-runtime&#39;,
    **&#39;airbnb&#39;,
    &#39;airbnb/hooks&#39;,**
  ],
  overrides: [],
  parserOptions: {
    ecmaVersion: &#39;latest&#39;,
    sourceType: &#39;module&#39;,
  },
  plugins: [&#39;react&#39;],
  rules: {},
};</code></pre><p>참고로 <code>airbnb</code>는 에어비앤비의 코딩 컨벤션을 <code>eslint</code> 규칙으로 만들어 둔 것이다. 그리고 <code>airbnb/hooks</code>는 리액트 훅을 사용하는 규칙을 정해놓은 것이다.</p>
<h2 id="5-팁1-prettier와-eslint-충돌없이-사용하기">5. 팁1) Prettier와 ESLint 충돌없이 사용하기</h2>
<p>포매터와 린터를 함께 쓰다보면 포매터와 린터의 규칙이 조금씩 달라서 충돌이 발생할 때가 있다. 이럴 때는 린터에서 포매터의 설정들을 포함하도록 설정해 주면 된다.</p>
<p>우선 <code>eslint-config-prettier</code> 패키지를 설치한다. 이 패키지는 ESLint의 규칙 중에서 Prettier와 충돌의 여지가 있는 규칙들을 꺼주는 역할을 한다.</p>
<pre><code>npm install --save-dev eslint-config-prettier</code></pre><p>그리고 <code>.eslintrc</code>에 <code>prettier</code>을 추가하면 된다.</p>
<pre><code>module.exports = {
  env: {
    browser: true,
    es2021: true,
  },
  extends: [
    &#39;eslint:recommended&#39;,
    &#39;plugin:react/recommended&#39;,
    &#39;plugin:react/jsx-runtime&#39;,
    &#39;airbnb&#39;,
    &#39;airbnb/hooks&#39;,
    **&#39;prettier&#39;,**
  ],
  overrides: [],
  parserOptions: {
    ecmaVersion: &#39;latest&#39;,
    sourceType: &#39;module&#39;,
  },
  plugins: [&#39;react&#39;],
  rules: {},
};</code></pre><h2 id="6-팁2-typescript와-eslint-사용하기">6. 팁2) TypeScript와 ESLint 사용하기</h2>
<p>최신 버전에서는 ESLint 안에서 타입스크립트를 지원한다. 마찬가지로 <a href="https://typescript-eslint.io/getting-started/">공식 홈페이지 설명</a>을 참고해 TypeScript와 관련된 패키지들을 설치하고, <code>.eslintrc</code>를 수정하면 된다.</p>
<blockquote>
<p><a href="https://typescript-eslint.io/">typescript-eslint</a></p>
</blockquote>
<h2 id="7-참고하면-좋은-자료">7. 참고하면 좋은 자료</h2>
<blockquote>
<p><a href="https://blog.pumpkin-raccoon.com/74">JS코드를 깔끔하게 해주는 ESLint 알아보기! (적용방법과 실상세 옵션)</a></p>
</blockquote>
<hr>

<h1 id="stylelint">Stylelint</h1>
<h2 id="1-stylelint">1. Stylelint</h2>
<p>앞에서 살표 본 ESLint가 자바스크립트의 구문을 분석해 검사하는 도구라면, CSS를 위한 린터인 Stylelint도 있다.</p>
<blockquote>
<p><a href="https://stylelint.io/">Home | Stylelint</a></p>
</blockquote>
<p>참고로 일반적인 CSS 문법이 아니라, SCSS 같은 프리프로세서 문법이라던지 Styled-Components 같은 CSS-in-JS 라이브러리에서도 Stylelint를 위한 플러그인을 만들어서 제공하는 경우도 있으니 참고해 보면 좋을 것 같다.</p>
<blockquote>
<p><a href="https://www.npmjs.com/package/stylelint-config-standard-scss">npm: stylelint-config-standard-scss</a>
<a href="https://styled-components.com/docs/tooling#stylelint">styled-components: Tooling</a></p>
</blockquote>
<hr>

<h1 id="husky">Husky</h1>
<h2 id="1-husky-1">1. Husky</h2>
<blockquote>
<p><a href="https://www.npmjs.com/package/husky">npm: huksy</a></p>
</blockquote>
<p>앞에서 잠깐 소개했듯이, 깃 훅(Git Hooks) 기능을 쉽게 쓰도록 도와주는 NPM 패키지이다.</p>
<p>패키지를 설치하고 공식 문서의 설명처럼 <code>prepare</code>라는 NPM 스크립트로 <code>husky install</code>을 추가한 다음에 한 번 실행하면 된다. (참고로 여기서 첫 번째 줄은 <code>package.json</code> 파일에서 <code>scripts</code> 안에다가 <code>prepare: &quot;husky install&quot;</code>을 추가하는 명령어이다.)</p>
<pre><code>npm pkg set scripts.prepare=&quot;husky install&quot;
npm run prepare</code></pre><p>이제 pre-commit 훅을 추가해 본다. <code>npm run lint</code> 명령어를 추가해 본다.</p>
<pre><code>npx husky add .husky/pre-commit &quot;npm run lint&quot;</code></pre><p>코드를 수정하고 커밋을 해 보면, <code>npm run lint</code>를 먼저 실행하고, 만약에 린트가 깨진다면 커밋이 생성되지 않는다. 이런 식으로 husky를 이용하면 깃 훅을 활용해 여러가지 명령어들을 실행해 볼 수 있다.</p>
<blockquote>
<p><a href="https://blog.pumpkin-raccoon.com/85">husky: git hook을 통한 테스트 및 린트 자동화</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[위클리 페이퍼 (8)]]></title>
            <link>https://velog.io/@gaebar_top/%EC%9C%84%ED%81%B4%EB%A6%AC-%ED%8E%98%EC%9D%B4%ED%8D%BC-8</link>
            <guid>https://velog.io/@gaebar_top/%EC%9C%84%ED%81%B4%EB%A6%AC-%ED%8E%98%EC%9D%B4%ED%8D%BC-8</guid>
            <pubDate>Tue, 09 Jan 2024 10:35:23 GMT</pubDate>
            <description><![CDATA[<h1 id="8주차-위클리-페이퍼">8주차 위클리 페이퍼</h1>
<h2 id="q1-본인이-생각하는-css-in-js의-장점과-단점을-설명해-주세요">Q1) 본인이 생각하는 CSS-in-JS의 장점과 단점을 설명해 주세요.</h2>
<p>CSS-in-JS란 자바스크립트 또는 타입스크립트 코드에서 직접 CSS를 작성하여 스타일링 하는 방법을 말한다.</p>
<ol>
<li>장점</li>
</ol>
<ul>
<li><p>지역 스코프 스타일 : 일반 CSS를 작성할 때는 실수로 의도한 것보다 더 광범위하게 스타일을 적용하기가 쉽다. CSS-in-JS는 기본적으로 스타일을 지역 스코프로 지정하기 때문에 이러한 문제를 해결할 수 있다.</p>
</li>
<li><p>자바스크립트 변수를 style에 사용할 수 있다. CSS-in-JS를 사용하면 스타일 규칙을 작성할 때 자바스크립트 변수를 참조할 수 있다.</p>
</li>
<li><p>CSS의 컴포넌트화로 스타일 시트의 파일을 유지보수 할 필요가 없다. CSS 모델을 문서 레벨이 아닌 컴포넌트 레벨로 추상화한다. (모듈성)</p>
</li>
<li><p>CSS-in-JS는 자바스크립트 환경을 최대한 활용할 수 있다.</p>
</li>
<li><p>자바스크립트와 CSS 사이의 상수와 함수를 쉽게 공유할 수 있다.</p>
</li>
<li><p>현재 사용중인 스타일만 DOM에 포함된다.</p>
</li>
<li><p>CSS-in-JS는 짧은 길이의 유니크한 클래스를 자동으로 생성하기 때문에 코드 경량화의 장점이 있다.</p>
</li>
</ul>
<ol start="2">
<li>단점</li>
</ol>
<ul>
<li><p>CSS-in-JS는 런타임 오버헤드를 더한다. 컴포넌트가 렌더링 될 때 CSS-in-JS 라이브러리는 document에 삽입할 수 있는 일반 CSS로 스타일을 직렬화해야 한다.</p>
</li>
<li><p>CSS-in-JS는 번들 크기를 늘린다. 사이트를 방문하는 각 사용자는 CSS-in-JS 라이브러리용 자바스크립트를 다운로드 받아야 한다.</p>
</li>
<li><p>CSS-in-jS는 React DevTools를 어지럽힌다.</p>
</li>
<li><p>인터랙션한 페이지일 경우 CSS 파일을 따로 관리하는 방법에 비해 느린 성능을 보여줄 수 있다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Styled Components (1)]]></title>
            <link>https://velog.io/@gaebar_top/Styled-Components-1</link>
            <guid>https://velog.io/@gaebar_top/Styled-Components-1</guid>
            <pubDate>Tue, 09 Jan 2024 10:27:40 GMT</pubDate>
            <description><![CDATA[<h1 id="styled-components-시작하기">Styled Components 시작하기</h1>
<h2 id="1-styled-components">1. Styled Components</h2>
<p>리액트에서 CSS를 좀 더 편리하게 적용하기 위해서 사용하는 기술이 바로 Styled Components이다.</p>
<pre><code class="language-jsx">import styled from &#39;styled-components&#39;;

const Button = styled.button`
  background-color: #ededed;
  border: none;
  border-radius: 8px;
`;

function App() {
  return (
    &lt;div&gt;
      &lt;h1&gt;안녕 Styled Components!&lt;/h1&gt;
      &lt;Button&gt;확인&lt;/Button&gt;
    &lt;/div&gt;
  );
}

export default App;</code></pre>
<p>위 코드는 Styled Components로 Button이라는 컴포넌트를 만든 예시이다. 일반적인 방법과 다르게 CSS 코드로 컴포넌트가 만들어진 부분이 Styled Components가 적용된 부분이다. 리액트는 JSX라는 문법으로 태그를 작성해 태그에 해당하는 컴포넌트를 만든다. 그렇게 만든 컴포넌트에 스타일을 적용하려면 해당 태그를 타겟한 CSS 코드를 따로 작성해야 했다.</p>
<p>하지만 Styled Components는 컴포넌트를 만들면서 바로 해당 컴포넌트의 스타일을 작성한다. 마치 JSX로 컴포넌트를 만드는 것처럼 리액트스럽게 CSS를 쓰는 방식이다. 이렇게 컴포넌트 중심으로 스타일을 지정하는 방식은 편리할 뿐만 아니라, 개발 속도도 빨라진다.</p>
<hr>

<h2 id="2-기존-방식의-문제점">2. 기존 방식의 문제점</h2>
<h3 id="1-css-클래스-이름이-겹치는-문제">(1) CSS 클래스 이름이 겹치는 문제</h3>
<pre><code class="language-js">// App.js
import Dashboard from &#39;./Dashboard&#39;;
import App.css;

function App() {
   return (
     &lt;div className=&quot;container&quot;&gt;
       &lt;Dashboard&gt; ... &lt;/Dashboard&gt;
     &lt;/div&gt;
   );
}

export default App

// Dashboard.js
import Dashboard.css;

function Dashboard({ children }) {
   return (
     &lt;div className=&quot;container&quot;&gt; 
       {children}
     &lt;/div&gt;
   );
}

export default Dashboard;</code></pre>
<pre><code class="language-css">/* App.css */
.container {
  background-color: #000000;
}

/* Dashboard.css */
.container {
  font-size: 16px;
}</code></pre>
<p><code>App.js</code>에 <code>App</code>이라는 컴포넌트를 만들고 클래스 이름을 <code>.container</code>로 지었다. <code>Dashboard.js</code>에는 <code>Dashboard</code>라는 컴포넌트를 만들고, 해당 컴포넌트에서의 클래스 이름도 <code>.container</code>로 지었다. 그렇게 만들어진 <code>Dashboard</code> 컴포넌트는 <code>App</code> 컴포넌트 안에 배치했다.</p>
<p>하지만 위 코드는 의도와는 다르게 동작한다. 두 컴포넌트 모두에서 <code>App.css</code>에 있는 스타일과 <code>Dashboard.css</code>에 있는 스타일이 모두 적용된다. 이는 사용된 클래스 이름이 전역적인 특성을 가지기 때문이다. 참고로, 클래스 이름이 전역적이라는 건 한 컴포넌트에서 사용한 클래스 이름을 다른 모든 컴포넌트에서도 사용할 수 있다는 뜻이다.</p>
<p>클래스 이름은 모든 곳에서 사용할 수 있기 때문에, <code>import</code> 해오지 않은 CSS 파일에서도 같은 클래스 이름이 사용된 부분이 있으면 그곳의 스타일이 함께 적용된다. 이렇게 의도하지 않은 방식으로 스타일이 적용되는 걸 막기 위해 클래스 이름은 겹치지 않도로고 조심해야 한다.</p>
<pre><code class="language-jsx">const StyledApp = styled.div`
  background-color: #000000;
`;

const Dashboard = styled.div`
  font-size: 16px;
`;

function App() {
  return (
    &lt;StyledApp&gt;
      &lt;Dashboard&gt; ... &lt;/Dashboard&gt;
    &lt;/StyledApp&gt;
  );
}</code></pre>
<p>Styled Components는 이 문제를 쉽게 해결한다. 클래스 이름을 아예 쓰지 않는 것이다. CSS 코드로 React 컴포넌트를 바로 만드니까 애초에 클래스 이름이 겹칠 일이 없다.</p>
<h3 id="2-재사용하는-css-코드를-관리하기-어렵다">(2) 재사용하는 CSS 코드를 관리하기 어렵다</h3>
<p>사이트를 만들다 보면 자주 쓰는 CSS 코드가 생기기 마련이다. 예를 들면 그림자 같은 게 있다. 그림자는 다양한 곳에서 자수 쓰지만 스타일의 종류는 몇 가지로 정해져 있다. 이렇게 자주 재사용되는 스타일은 각 종류 별로 클래스를 만들어 놓고 여러 컴포넌트에서 가져다 쓰면 편리하다. 하지만 CSS만으로는 재사용되는 코드를 잘 관리하는게 어렵다.</p>
<pre><code class="language-js">// App.js
import Dashboard from &#39;./Dashboard&#39;;
import Card from &#39;./Card&#39;;
import App.css;

function App() {
   return (
     &lt;div className=&quot;container&quot;&gt;
       &lt;Dashboard&gt;
         &lt;Card&gt; ... &lt;/Card&gt;
       &lt;/Dashboard&gt;
     &lt;/div&gt;
   );
}

export default App

// Card.js
import Card.css;

function Card({ children }) {
  return (
    &lt;div className=&quot;container shadow20&quot;&gt;
    &lt;/div&gt;
  );
}

export default Card;</code></pre>
<pre><code class="language-css">/* App.css */
.shadow20 {
  box-shadow: 0 10px 15px rgba(0, 0, 0, 0.2);
}

.shadow40 {
  box-shadow: 0 10px 15px rgba(0, 0, 0, 0.4);
}</code></pre>
<p><code>App</code> 컴포넌트 안에 <code>Dashboard</code>를 배치하고, 그 안에 <code>Card</code> 컴포넌트를 배치하는 코드이다. <code>App.css</code> 파일에 <code>.shadow20</code>이라는 클래스를 만들어 두고 이걸 <code>Card</code> 컴포넌트에서 사용했다. (클래스 이름은 전역적이기 때문에 이렇게 사용 가능하다.)</p>
<p>여기서 문제는 <code>App.css</code> 코드에 정의된 <code>shadow20</code>만을 보고는 어디에 사용되는 스타일인지 알기 어렵다. JavaScript와 달리 CSS 코드는 VSCode 같은 코드 에디터에서 추적하기 어렵기 때문에 직접 테스트로 하나하나 검색을 해야 한다. 스타일이 재사용 되는 곳이 점점 더 많아질수록 코드를 유지 보수 할 때 관리가 더 힘들어진다.</p>
<pre><code class="language-jsx">// shadow.js
import { css } from &#39;styled-components&#39;;

const shadow20 = css`
  box-shadow: 0 10px 15px rgba(0, 0, 0, 0.2);
`;

const shadow40 = css`
  box-shadow: 0 10px 15px rgba(0, 0, 0, 0.4);
`;

// Card.js

import { shadow20 } from &#39;../shadows&#39;;

const Card = styled.div`
  ${shadow20}
  ...(다른 CSS 코드)
`;

export default Card;</code></pre>
<p>Syled Components에서는 스타일 재사용이 필요한 상황에서 클래스가 아니라 JavaScript 변수를 만든다. 예시 코드에서 <code>shadow20</code>은 JavaScript 변수이다. JavaScript라서 언제 어디서 쓰고 있는지 에디터를 통해 확인하기 쉽고, 이름을 바꾸거나 삭제를 하는 것도 코드 에디터를 통해 쉽게 할 수 있다.</p>
<hr>

<h2 id="3-hello-styled">3. Hello Styled</h2>
<h3 id="1-설치">(1) 설치</h3>
<pre><code>npm init react-app 프로젝트명
npm install styled-components</code></pre><p><code>package.json</code> 파일에 아래와 같이 추가되었을 것이다. 숫자 <code>5</code>는 버전을 의미한다.</p>
<pre><code class="language-json">{
  ...
  &quot;dependencies&quot;: {
      ...
      &quot;styled-components&quot;: &quot;^5.3.5&quot;
    },
}</code></pre>
<h3 id="2-hello-styled">(2) Hello Styled!</h3>
<pre><code class="language-js">// Button.js
import styled from &#39;styled-components&#39;;

const Button = styled.button`
  background-color: #6750a4;
  border: none;
  color: #ffffff;
  padding: 16px;
`;

export default Button;


// App.js
import Button from &#39;./Button&#39;;

function App() {
  return (
    &lt;div&gt;
      &lt;Button&gt;Hello Styled!&lt;/Button&gt;
    &lt;/div&gt;
  );
}

export default App;</code></pre>
<h3 id="3-코드-살펴보기">(3) 코드 살펴보기</h3>
<h4 id="1-styled-불러오기">1. <code>styled</code> 불러오기</h4>
<pre><code class="language-js">import styled from &#39;styled-components&#39;;</code></pre>
<p><code>styled-components</code>의 default import로 <code>styled</code>를 가져오면 된다. 대부분의 작업은 <code>styled</code> 함수를 사용한다.</p>
<h4 id="2-컴포넌트-만들기">2. 컴포넌트 만들기</h4>
<p>Styled Components에서는 클래스 대신에 컴포넌트를 만든다.</p>
<pre><code class="language-js">const Button = styled.button`
  background-color: #6750a4;
  border: none;
  color: #ffffff;
  padding: 16px;
`;</code></pre>
<p><code>styled.tagname</code>의 <code>tagname</code> 부분에는 스타일을 적용할 HTML 태그 이름을 작성한다. 그리고 바로 뒤에 템플릿 리터럴 문법으로 CSS 코드를 작성한다. 태그 함수라는 걸 사용한 건데, 뒤에서 자세히 설명하도록 하겠다.</p>
<h4 id="3-컴포넌트-사용하기">3. 컴포넌트 사용하기</h4>
<pre><code class="language-js">&lt;Button&gt;Hello Styled!&lt;/Button&gt;</code></pre>
<p><code>styled.tagname</code>으로 만든 컴포넌트는 일반적인 리액트 컴포넌트처럼 JSX로 사용하면 된다.</p>
<hr>

<h2 id="4-nesting-문법">4. Nesting 문법</h2>
<h3 id="1--선택자">(1) <code>&amp;</code> 선택자</h3>
<p><code>&amp;</code> 선택자를 사용해서 앞에서 만든 버튼 컴포넌트를 호버하거나 클릭했을 때 배경색이 바뀌도록 만들어 보겠다.</p>
<pre><code class="language-js">// Button.js
import styled from &#39;styled-components&#39;;

const Button = styled.button`
  background-color: #6750a4;
  border: none;
  color: #ffffff;
  padding: 16px;

  &amp;:hover,
  &amp;:active {
    background-color: #463770;
  }
`;

export default Button;</code></pre>
<p>Nesting에서 <code>&amp;</code>는 부모 선택자를 의미한다. 위 코드에서는 버튼 컴포넌트의 클래스를 뜻한다. 기존 CSS 코드로 표현해 본다면, 버튼 컴포넌트가 <code>.Button</code>이라는 클래스 일므을 쓸 때 <code>&amp;:hover</code>는 <code>.Button:hover</code>와 같은 의미이다.</p>
<pre><code class="language-css">.Button {
  background-color: #6750a4;
  border: none;
  color: #ffffff;
  padding: 16px;
}

.Button:hover,
.Button:active {
  background-color: #463770;
}</code></pre>
<h3 id="2-컴포넌트-선택자">(2) 컴포넌트 선택자</h3>
<p>Styled Components에선 클래스 이름을 사용하지 않는데, 컴포넌트 안에 있는 또 다른 컴포넌트를 선택하고 싶을 때는 어떻게 해야 하는가?</p>
<p>버튼 안에 아이콘을 배치하는 상황을 가정하여, 버튼 텍스트 왼쪽에 아이콘을 배치하고 그 사이에 마진을 4px만큼 주려고 한다.</p>
<p>Styled Components로 <code>Icon</code>과 <code>StyledButton</code> 컴포넌트를 각각 만들고, <code>StyledButton</code> 안에 <code>Icon</code>을 배치한다. 이 때 <code>StyledButton</code> 컴포넌트 안에서 <code>Icon</code> 컴포넌트를 선택해 별도로 <code>margin-right: 4px</code>라는 속성을 지정한다. 이럴 경우, 컴포넌트를 선택자로 쓰고 싶을 때는 <code>${Icon}</code> 같이 컴포넌트 자체를 템플릿 리터럴 안에 넣어주면 된다.</p>
<pre><code class="language-js">import styled from &#39;styled-components&#39;;
import nailImg from &#39;./nail.png&#39;;

const Icon = styled.img`
  width: 16px;
  height: 16px;
`;

const StyledButton = styled.button`
  background-color: #6750a4;
  border: none;
  color: #ffffff;
  padding: 16px;

  &amp; ${Icon} {
    margin-right: 4px;
  }

  &amp;:hover,
  &amp;:active {
    background-color: #463770;
  }
`;

function Button({ children, ...buttonProps }) {
  return (
    &lt;StyledButton {...buttonProps}&gt;
      &lt;Icon src={nailImg} alt=&quot;nail icon&quot; /&gt;
      {children}
    &lt;/StyledButton&gt;
  );
}

export default Button;</code></pre>
<p>자손 결합자(Descendant Combinator)로 쓴 <code>&amp; ${Icon} { ... }</code> 부분을 기존 CSS로 표현해 본다면 다음과 같이 나타낼 수 있다. 버튼 안에 있는 태그 중에 <code>Icon</code> 컴포넌트에 해당하는 태그를 찾아서 스타일을 적용한 것이다.</p>
<pre><code class="language-css">.StyledButton {
  ...
}

.StyledButton .Icon {
  margin-right: 4px;
}</code></pre>
<p>특히, <code>&amp;</code>와 자손 결합자를 사용하는 경우에는 <code>&amp;</code>를 생략할 수 있다. 즉 <code>${Icon}</code>만 써도 똑같이 동작한다. 보통 간편하게 많이 쓰니까, 자손 결합자로 Nesting 할 때는 아래처럼 쓰는 걸 권장한다.</p>
<pre><code class="language-js"> const StyledButton = styled.button`
  background-color: #6750a4;
  border: none;
  color: #ffffff;
  padding: 16px;

  ${Icon} {
    margin-right: 4px;
  }

  &amp;:hover,
  &amp;:active {
    background-color: #463770;
  }
`;</code></pre>
<p>참고로 Nesting은 여러 겹으로 할 수도 있다.</p>
<pre><code class="language-js">const StyledButton = styled.button`
  ...
  &amp;:hover,
  &amp;:active {
    background-color: #7760b4;

    ${Icon} {
      opacity: 0.2;
    }
  }
`;</code></pre>
<p><code>&amp;:hover, &amp;:active { ... }</code> 안에 있는 <code>${Icon}</code> 선택자를 CSS 코드로 표현해 보면 다음과 같을 것이다.</p>
<pre><code class="language-css">.StyledButton:hover .Icon,
.StyledButton:active .Icon {
  opacity: 0.5;
}</code></pre>
<hr>

<h2 id="5-다이나믹-스타일링">5. 다이나믹 스타일링</h2>
<p>크기를 조절하는 <code>size</code>, 둥근 모양을 지정하는 <code>round</code>라는 Prop을 추가해 버튼 컴포넌트의 크기와 모양을 조절해 보도록 하겠다.</p>
<pre><code class="language-js">// Button.js
import styled from &#39;styled-components&#39;;

const SIZES = {
  large: 24,
  medium: 20,
  small: 16,
};

const Button = styled.button`
  background-color: #6750a4;
  border: none;
  border-radius: ${({ round }) =&gt; round ? `9999px` : `3px`};
  color: #ffffff;
  font-size: ${({ size }) =&gt; SIZES[size] ?? SIZES[&#39;medium&#39;]}px;
  padding: 16px;

  &amp;:hover,
  &amp;:active {
    background-color: #463770;
  }
`;

export default Button;

// App.js
import styled from &#39;styled-components&#39;;
import Button from &#39;./Button&#39;;

const Container = styled.div`
  ${Button} {
    margin: 10px;
  }
`;

function App() {
  return (
    &lt;Container&gt;
      &lt;h1&gt;기본 버튼&lt;/h1&gt;
      &lt;Button size=&quot;small&quot;&gt;small&lt;/Button&gt;
      &lt;Button size=&quot;medium&quot;&gt;medium&lt;/Button&gt;
      &lt;Button size=&quot;large&quot;&gt;large&lt;/Button&gt;
      &lt;h1&gt;둥근 버튼&lt;/h1&gt;
      &lt;Button size=&quot;small&quot; round&gt;
        round small
      &lt;/Button&gt;
      &lt;Button size=&quot;medium&quot; round&gt;
        round medium
      &lt;/Button&gt;
      &lt;Button size=&quot;large&quot; round&gt;
        round large
      &lt;/Button&gt;
    &lt;/Container&gt;
  );
}

export default App;</code></pre>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gaebar_top/post/679d2c89-c5fa-4f59-b08a-427e22b3a1f7/image.png" alt=""></p>
</blockquote>
<p>템플릿 리터럴 안에는 달러와 중괄호(<code>${ ... }</code>)를 사용해서 JavaScript 코드를 집어넣을 수 있다. 이런 걸 표현 삽입법(Expression Interpolation)이라고 부른다. 표현식 삽입법을 사용하면 Styled Components에서 Prop에 따라 컴포넌트의 스타일을 다르게 보여줄 수 있다. JSX에서 Prop이나 State에 따라 HTML 태그를 다르게 보여주는 것과 비슷하다.</p>
<h3 id="1----안에-값변수-사용하기">(1) ${ ... } 안에 값(변수) 사용하기</h3>
<p>가장 기본적인 사용법은 JavaScript 변수를 그대로 넣는 방식이다.</p>
<pre><code class="language-js">const a = 1;
const b = 2;
const str = `${a} 더하기 ${b}는 ${a + b} 입니다.`;</code></pre>
<p><code>${SIZES[&#39;medium&#39;]}</code> 부분은 숫자 20을 뜻하기 때문에, <code>font-size: ${SIZES[&#39;medium&#39;]}px;</code>는 <code>font-size: 20px;</code>란 코드가 된다.</p>
<pre><code class="language-js">const SIZES = {
  large: 24,
  medium: 20,
  small: 16
};

const Button = styled.button`
  ...
  font-size: ${SIZES[&#39;medium&#39;]}px;
`;</code></pre>
<h3 id="2----안에-함수-사용하기">(2) ${ ... } 안에 함수 사용하기</h3>
<p>Prop에 따라 스타일을 다르게 적용하는 함수를 넣으려고 한다. 함수의 파라미터로는 Props를 받고, 리턴 값으로는 스타일 코드를 리턴하면 된다. 참고로 이건 템플릿 리터럴의 기능이 아니라 Styled Components가 내부적으로 처리해 주는 것이다.</p>
<pre><code class="language-jsx">const SIZES = {
  large: 24,
  medium: 20,
  small: 16
};

const Button = styled.button`
  ...
  font-size: ${(props) =&gt; SIZES[props.size]}px;
`;

// 구조 분해(Destructuring)
  font-size: ${({ size }) =&gt; SIZES[size]}px;</code></pre>
<p><code>size</code> Prop이 값이 없거나 잘못된 값일 경우 어떻게 되는가? Styled Components에서는 <code>undefined</code> 값은 빈 문자열로 처리해 주기 때문에 <code>font-size: px</code> 같은 잘못된 CSS 코드가 된다. 그래서 가능하면 기본 값을 정해주는 게 좋다. 여러 가지 방법이 있겠지만, 널 병합 연산자(Nullish coalescing opeator)를 사용할 수 있다.</p>
<pre><code class="language-jsx">font-size: ${({ size }) =&gt; SIZES[size] ?? SIZES[&#39;medium&#39;]}px;</code></pre>
<h4 id="1-논리-연산자-사용하기">1. 논리 연산자 사용하기</h4>
<p>함수를 사용할 때 많이 사용하는 패턴 중 하나는 논리 연산자를 사용하는 것이다. 예를 들어, <code>round</code>라는 Prop이 참일 때 컴포넌트의 모서리를 둥글게 만드는 것이다.</p>
<pre><code class="language-jsx">const Button = styled.button`
  ...
  ${({ round }) =&gt; round &amp;&amp; `
      border-radius: 9999px;
    `}
`;</code></pre>
<p><code>round</code> 값이 참이면 그 뒤에 값까지 계산하기 때문에 <code>border-radius: 9999px</code>이라는 문자열이 리턴돼서 적용된다. 반대로, <code>round</code> 값이 거짓이면 그냥 <code>false</code>가 리턴돼서 아무런 값도 적용되지 않는다. 리액트에서 JSX로 조건부 렌더링 하는 것과 비슷하다.</p>
<h4 id="2-삼항-연산자-사용하기">2. 삼항 연산자 사용하기</h4>
<p>마찬가지로 자주 쓰는 패턴이다. <code>round</code>가 참이면 완전히 둥근 모서리를 보여주고, 거짓이면 <code>3px</code> 정도로 살짝 부드럽게 깎인 모서리를 보여주고 싶을 때 삼항 연산자로 쓸 수 있다.</p>
<pre><code class="language-jsx">border-radius: ${({ round }) =&gt; round ? `9999px` : `3px`};</code></pre>
<hr>

<h2 id="6-스타일-재사용--상속">6. 스타일 재사용 : 상속</h2>
<h3 id="1-styled-함수">(1) styled() 함수</h3>
<p>Styled Components로 만들어진 컴포넌트를 상속하려면 <code>styled()</code> 함수를 사용하면 된다.</p>
<pre><code class="language-js">// Button.js
import styled from &#39;styled-components&#39;;

const SIZES = {
  large: 24,
  medium: 20,
  small: 16,
};

const Button = styled.button`
  background-color: #6750a4;
  border: none;
  color: #ffffff;
  font-size: ${({ size }) =&gt; SIZES[size] ?? SIZES[&#39;medium&#39;]}px;
  padding: 16px;

  ${({ round }) =&gt;
    round
      ? `
      border-radius: 9999px;
    `
      : `
      border-radius: 3px;
    `}

  &amp;:hover,
  &amp;:active {
    background-color: #463770;
  }
`;

export default Button;

// App.js
import styled from &#39;styled-components&#39;;
import Button from &#39;./Button&#39;;

const SubmitButton = styled(Button)`
  background-color: #de117d;
  display: block;
  margin: 0 auto;
  width: 200px;

  &amp;:hover {
    background-color: #f5070f;
  }
`;

function App() {
  return (
    &lt;div&gt;
      &lt;SubmitButton&gt;계속하기&lt;/SubmitButton&gt;
    &lt;/div&gt;
  );
}

export default App;</code></pre>
<p><code>Button</code> 컴포넌트의 스타일을 상속해서 새로운 버튼 <code>SubmitButton</code>을 만들고, <code>App</code> 컴포넌트 안에 <code>SubmitButton</code>을 배치하는 상황이다. 코드를 보면 <code>SubmitButton</code> 컴포넌트를 만들 때 <code>styled(Button)</code>이라고 작성했다. 이렇게 하면 <code>SubmitButton</code>이 <code>Button</code>의 스타일을 상속받게 된다. <code>Button</code> 컴포넌트에 <code>SubmitButton</code>의 스타일이 상속됐기 때문에, 마찬가지로 글씨는 흰색으로 보이고 있다.</p>
<p>상속이라는 단어가 어렵게 느껴질 수도 있다. 다른 컴포넌트의 스타일을 가져와서 원하는 대로 사용할 수 있는 것이라고 이해하면 된다. <code>SubmitButton</code>도 <code>Button</code>의 스타일 전부를 상속받고, 몇 가지 스타일만 추가해 원하는 컴포넌트를 만들고 있다.</p>
<h3 id="2-jsx로-직접-만든-컴포넌트에-styled-사용하기">(2) JSX로 직접 만든 컴포넌트에 styled() 사용하기</h3>
<p><code>styled.tagname</code>으로 만든 컴포넌트는 바로 <code>styled()</code> 함수를 사용할 수 있지만, 그렇지 않은 컴포넌트는 따로 처리가 필요하다. 약관을 보여주는 <code>TermsOfService</code>라는 컴포넌트를 만든다고 가정하자.</p>
<pre><code class="language-js">// TermsOfService.js
function TermsOfService() {
  return (
    &lt;div&gt;
      &lt;h1&gt;㈜코드잇 서비스 이용약관&lt;/h1&gt;
      &lt;p&gt;
        환영합니다.
        &lt;br /&gt;
        Codeit이 제공하는 서비스를 이용해주셔서 감사합니다. 서비스를
        이용하시거나 회원으로 가입하실 경우 본 약관에 동의하시게 되므로, 잠시
        시간을 내셔서 주의 깊게 살펴봐 주시기 바랍니다.
      &lt;/p&gt;
      &lt;h2&gt;제 1 조 (목적)&lt;/h2&gt;
      &lt;p&gt;
        본 약관은 ㈜코드잇이 운영하는 기밀문서 관리 프로그램인 Codeit에서
        제공하는 서비스를 이용함에 있어 이용자의 권리, 의무 및 책임사항을
        규정함을 목적으로 합니다.
      &lt;/p&gt;
    &lt;/div&gt;
  );
}

export default TermsOfService;

// App.js
import styled from &#39;styled-components&#39;;
import Button from &#39;./Button&#39;;
import TermsOfService from &#39;./TermsOfService&#39;;

const StyledTermsOfService = styled(TermsOfService)`
  background-color: #ededed;
  border-radius: 8px;
  padding: 16px;
  margin: 40px auto;
  width: 400px;
`;

const SubmitButton = styled(Button)`
  background-color: #de117d;
  display: block;
  margin: 0 auto;
  width: 200px;

  &amp;:hover {
    background-color: #f5070f;
  }
`;

function App() {
  return (
    &lt;div&gt;
      &lt;StyledTermsOfService /&gt;
      &lt;SubmitButton&gt;계속하기&lt;/SubmitButton&gt;
    &lt;/div&gt;
  );
}

export default App;</code></pre>
<p><code>styled()</code>로 지정한 스타일이 적용되지 않는다. <code>StyledTermsOfService</code>에 지정한 배경색이랑 너비가 적용이 되지 않았다. Styled Components는 내부적으로 <code>className</code>을 따로 생성한다. 그리고, 자체적으로 생성된 <code>className</code>이 있는 부분에 <code>styled()</code> 함수의 스타일이 입혀진다.</p>
<p>그런데, JSX 문법으로 직접 만든 컴포넌트는 <code>styled()</code> 함수가 적용될 <code>className</code>에 대한 정보가 없다. <code>styled()</code> 함수에서 지정한 스타일이 입혀질 부분이 어딘지 알 수 없어 스타일이 적용되지 않는 것이다. 이렇게, Styled Components를 사용하지 않고 직접 만든 컴포넌트는 <code>className</code> 값을 Prop으로 따로 내려줘야 <code>styled()</code> 함수를 사용할 수 있다.</p>
<pre><code class="language-js">// TermsOfService.js
function TermsOfService({ className }) {
  return (
    &lt;div className={className}&gt;
      ...
    &lt;/div&gt;
  );
}</code></pre>
<p>직접 만든 컴포넌트에 <code>className</code> prop을 따로 내려주는 건 <code>styled()</code> 함수가 적용될 부분의 <code>className</code>을 별도로 정해주는 거라고 이해하면 된다. 위 코드의 경우엔, <code>&lt;div&gt;</code> 태그에 <code>className</code>을 내려줬기 때문에 <code>styled(TermsOfService)</code>에서 작성한 코드는 <code>TermsOfService</code> 안에 있는 <code>&lt;div&gt;</code> 태그에 적용된다.</p>
<p>정리하자면, 스타일 상속을 하려면 <code>styled()</code> 함수를 사용하면 되는데, <code>styled.tagname</code>으로 만든 컴포넌트는 <code>styled()</code> 함수로 바로 상속하면 되고, Styled Components를 사용하지 않고 직접 만든 컴포넌트에는 클래스 이름을 내려준 후에 <code>styled()</code> 함수로 상속해야 한다.</p>
<hr>

<h2 id="7-스타일-재사용--css-함수">7. 스타일 재사용 : css 함수</h2>
<p>가끔 중복되는 CSS 코드들을 변수처럼 저장해서 여러 번 다시 사용하고 싶을 때 사용되는 <code>css</code> 함수에 대해 배워보도록 하겠다. <code>Button</code> 컴포넌트와 <code>Input</code> 컴포넌트에 같은 글자 크기를 갖도록 하는 상황을 가정하자. <code>size</code>라는 Prop으로 <code>small</code>, <code>medium</code>, <code>large</code> 각각에 지정된 크기를 전달하면 16, 20, 24 픽셀로 글자 크기를 지정하려 한다.</p>
<pre><code class="language-js">import styled, { css } from &#39;styled-components&#39;;

const SIZES = {
  large: 24,
  medium: 20,
  small: 16
};

const fontSize = css`
  font-size: ${({ size }) =&gt; SIZES[size] ?? SIZES[&#39;medium&#39;]}px;
`;

const Button = styled.button`
  ...
  ${fontSize}
`;

const Input = styled.input`
  ...
  ${fontSize}
`;</code></pre>
<p>일반적인 템플릿 리터럴을 쓰는 게 아니라 <code>css</code>라는 태그 함수를 붙여서 쓴다. Props를 받아서 사용하는 함수가 들어있기 때문에 반드시 <code>css</code> 함수를 사용해야 한다.</p>
<pre><code class="language-js">const boxShadow = `
  box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
`;</code></pre>
<p>함수를 삽입하지 않는 단순한 문자열이라면 일반적인 템플릿 리터럴을 써도 된다.</p>
<pre><code class="language-js">const boxShadow = css`
  box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
`;</code></pre>
<p>하지만, 이런 경우에도 항상 <code>css</code> 함수를 사용하도록 습관화하는 것을 권장한다.</p>
]]></description>
        </item>
    </channel>
</rss>