<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>sg_yksv77.log</title>
        <link>https://velog.io/</link>
        <description>목표 지향을 위해 협업하는 개발자</description>
        <lastBuildDate>Thu, 27 Apr 2023 06:47:43 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>sg_yksv77.log</title>
            <url>https://images.velog.io/images/sg_yksv77/profile/93a2a2c8-227a-4316-8cb4-dc4d6ee69693/KakaoTalk_Photo_2021-12-12-20-30-44.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. sg_yksv77.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/sg_yksv77" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Error] vercel 배포 시 Invalid API key 에러 발생]]></title>
            <link>https://velog.io/@sg_yksv77/TIL-vercel-%EB%B0%B0%ED%8F%AC-%EC%8B%9C-Invalid-API-key-%EC%97%90%EB%9F%AC-%EB%B0%9C%EC%83%9D</link>
            <guid>https://velog.io/@sg_yksv77/TIL-vercel-%EB%B0%B0%ED%8F%AC-%EC%8B%9C-Invalid-API-key-%EC%97%90%EB%9F%AC-%EB%B0%9C%EC%83%9D</guid>
            <pubDate>Thu, 27 Apr 2023 06:47:43 GMT</pubDate>
            <description><![CDATA[<h3 id="💢-문제-발생">💢 문제 발생</h3>
<blockquote>
<p>로컬에서 정상적으로  작동하는 서비스가 vercel 배포시 invelid api key 에러가 발생했다.</p>
</blockquote>
<h3 id="❓왜-그럴까">❓왜 그럴까?</h3>
<p>API KEY를 <code>.env</code> 파일에서 보관하고 <code>gitignore</code> 에 추가했다. 이같은 이유는 보완상의 이유로 github에 공유되는 것을 막기 위해서였다.
그렇다면... github repository와 연결된 vercel 도 <code>.env</code>이 공유되지 않은 것이 아닐까?</p>
<h3 id="🧑🚒-해결방법">🧑‍🚒 해결방법</h3>
<p><a href="https://vercel.com/guides/how-to-add-vercel-environment-variables">How do I add environment variables to my Vercel project?</a></p>
<blockquote>
<h3 id="environment-variables-ui">Environment Variables UI</h3>
<p><img src="https://velog.velcdn.com/images/sg_yksv77/post/c3fa03e3-c6c3-44f0-808d-3dcb632a85a2/image.png" alt="">
You can add Environment Variables to your site through Project Settings. Values are encrypted at rest and permission to view values can be restricted based on your role. You can also choose a specific Git branch to set Environment Variables for with the Preview environment.</p>
</blockquote>
<ol>
<li>Invalid API key 에러 발생 프로젝트 선택</li>
<li>프로젝트 상단, Settings =&gt; Environment Variables</li>
<li><code>.env</code>에서 작성한 API KEY 변수와 API KEY를 각각 name과 value에 입력한다.</li>
<li>배포가 된 상태에서 환경변수 설정을 했으므로, 재배포를 해야한다.
<img src="https://velog.velcdn.com/images/sg_yksv77/post/d2ad502a-bbbd-4b7f-ad4e-584bdffd65da/image.gif" alt=""></li>
</ol>
<h3 id="배운점">배운점</h3>
<ol>
<li><code>gitignore</code>에 추가된 파일은 Github에 올라가지 않는다!!</li>
<li>velcel 배포를 기획했다면, 배포 전, 환경변수 설정을 잊지 말자!</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] React Router Dom 이해]]></title>
            <link>https://velog.io/@sg_yksv77/TIL-React-Router-Dom-%EC%9D%B4%ED%95%B4</link>
            <guid>https://velog.io/@sg_yksv77/TIL-React-Router-Dom-%EC%9D%B4%ED%95%B4</guid>
            <pubDate>Thu, 23 Mar 2023 07:16:02 GMT</pubDate>
            <description><![CDATA[<h2 id="1-react-router-dom-이란">1. React Router Dom 이란?</h2>
<blockquote>
<p>기존 라우팅 아키텍처는 라우팅이 실행 중인 앱 외부의 구성에서 처리되었다.
그러나, React Router DOM은 앱 및 플랫폼의 요구 사항에 따라 컴포넌트 기반 라우팅을 용이하게 한다. 
쉽게 말해, 웹 앱에서 동적 라우팅을 구현 할 수 있게 해주는 라이브러리 이다.</p>
</blockquote>
<h3 id="react-router-dom의-활용-spa">React Router Dom의 활용: SPA</h3>
<p>리액트는 SPA이기 때문에, index.js 템플릿 파일을 가지고 있다. 이 템플릿 파일에 JS를 이용해서 다른 컴포넌트를 템플릿 파일(index.js)에 추가하므로써, 페이지를 변경시키게 된다.</p>
<p>이때, React Router Dom 라이브러리가 새 컴포넌트로 라우팅 및 탐색을 한 뒤 렌더링하도록 도와주게 된다.</p>
<h4 id="react-router--설정하기">React Router  설정하기</h4>
<p> 템플릿 파일(src &gt; index.js)의 루트 구성 요소(App 구성 요소)를 BrowserRouter로 래핑한다.</p>
<blockquote>
<p>BrowserRouter 는 HTML5 History API를 사용하여 UI를 URL과 동기화된 상태로 유지해준다.
ex) URL이 localhost:3000/home 일 때, Home 컴포넌트(UI)를 동기화  상태로 유지하고 싶을 때,    BrowserRouter 가 이를 유지시켜주는 것이다.</p>
</blockquote>
<pre><code class="language-tsx">import React from &#39;react&#39;;
import ReactDOM from &#39;react-dom/client&#39;;
import &#39;./index.css&#39;;
import App from &#39;./App&#39;;
import reportWebVitals from &#39;./reportWebVitals&#39;;
import { BrowserRouter } from &#39;react-router-dom&#39;;

const root = ReactDOM.createRoot(document.getElementById(&#39;root&#39;));
root.render(
  /* &lt;React.StrictMode&gt;
    &lt;App /&gt;
  &lt;/React.StrictMode&gt; */
  &lt;BrowserRouter&gt;
    &lt;App /&gt;
  &lt;/BrowserRouter&gt;
);
reportWebVitals();</code></pre>
<h4 id="여러-컴포넌트-생성-및-라우트-정의하기">여러 컴포넌트 생성 및 라우트 정의하기</h4>
<p><img src="https://velog.velcdn.com/images/sg_yksv77/post/825b4f9d-25d9-4b40-a341-76cee5c978dc/image.png" alt=""></p>
<h4 id="routes">Routes</h4>
<blockquote>
<p>앱에서 생성 될 모든 개별 경로에 대한 컨테이너 및 상위 역할을 담당한다.
Route로 생성된 자식 컴포넌트 중에서 매칭되는 첫번째 Route를 랜더링 한다.</p>
</blockquote>
<h4 id="route">Route</h4>
<blockquote>
<p>Route는 단일 경로를 만드는 데 사용되며, 두 가지 속성을 가진다.</p>
</blockquote>
<ol>
<li>path : 매칭 후 렌더링 할 컴포넌트의 url 경로를 지정한다. 이 경로의 이름은 자유롭게 지정할 수 있다.</li>
<li>element : 경로에 맞게 렌더링 되어야 하는 컴포넌트를 지정한다.</li>
</ol>
<h4 id="link">Link</h4>
<blockquote>
<p>HTML 의 a요소와 유사하다. 
Link 컴포넌트의 to 속성은 이동할 컴포넌트의 경로(path)를 지정한다.</p>
</blockquote>
<pre><code class="language-tsx">function Home() {
    return (
        &lt;div&gt;
            &lt;Link to=&quot;about&quot;&gt;About 이동&lt;/Link&gt;
        &lt;/div&gt;
    )
}</code></pre>
<h4 id="link-컴포넌트-동작-순서">Link 컴포넌트 동작 순서</h4>
<ol>
<li>About 이동 클릭</li>
<li>기존 localhost:3000/home 에서 localhost:3000/about 으로 경로 변경</li>
<li>BrowserRouter 에서 History API를 사용하여 About 컴포넌트(UI)를 about 경로(URL)와 동기화 상태 유지</li>
</ol>
<h3 id="rouet와-link의-차이">Rouet와 Link의 차이</h3>
<p>Route는 요청된 URL 에 따라 매칭된 컴포넌트를 렌더링 시키는 것이라고 이해하면 된다.
그러나, Link 컴포넌트는 지정된 URL로 이동시키는 역할만 수행한다. 즉, Link 태그의 URL로 페이지 주소로 이동 할 뿐, 해당 주소에 맞는 UI(컴포넌트)를 새로 불러오지 않는다.</p>
<blockquote>
<p>Route : URL에 맞게 컴포넌트 렌더링
Link : URL 변경</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[개인프로젝트] 상세 영화 정보 모달 만들기(1)]]></title>
            <link>https://velog.io/@sg_yksv77/%EA%B0%9C%EC%9D%B8%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%AA%A8%EB%8B%AC</link>
            <guid>https://velog.io/@sg_yksv77/%EA%B0%9C%EC%9D%B8%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%AA%A8%EB%8B%AC</guid>
            <pubDate>Wed, 22 Mar 2023 09:47:08 GMT</pubDate>
            <description><![CDATA[<h3 id="영화-상세-정보-모달-생성">영화 상세 정보 모달 생성</h3>
<p>Row 컴포넌트 내에서 영화를 클릭하면 해당 영화의 상세정보 페이지를 모달로 노출시키고자 한다. 이를 위해, 첫번째로 Row 컴포넌트에서 해야할 작업(state, event handler)과 두번째, Row 컴포넌트에서 props로 전달 받은 영화 정보를 토대로 모달 컴포넌트의 구조를 작성 해야하는지 살펴보겠다.</p>
<h3 id="row-컴포넌트-사전-작업">Row 컴포넌트 사전 작업</h3>
<ul>
<li>State 생성<ol>
<li>모달 활성화 유무를 나타내는 state를 준비한다.<pre><code class="language-tsx">const [modalOpen, setModalOpen] = useState(false);</code></pre>
</li>
<li>모달 활성화 state가 변경되면, 모달 컴포넌트에 props로 전달 할 상세 영화 정보 state를 준비한다. 이때, 콘솔로 확인하여 사용할 수 있는 속성들이 무엇이 있는지 확인해야한다.</li>
</ol>
<ul>
<li>backdrop_path, title, overview, name?, release_date?, first_air_date, vote_average 속성을 사용할 것이다.<pre><code class="language-tsx">const [movieSelected, setMovieSelected] = useState({});</code></pre>
<img src="https://velog.velcdn.com/images/sg_yksv77/post/00d955f3-4564-42b8-bc78-1ab167eb024c/image.png" alt=""></li>
</ul>
</li>
<li>Click Event Handler <ol>
<li>RowPosters 디자인 컴포넌트의 img El에서 movieSelected state에 담을 영화 정보는 가지고 있으므로, handler를 img el에 적용시킨다.</li>
<li>해당 이벤트 헨들러는 img El 에 클릭 이벤트가 발생하면, 모달 컴포넌트를 활성화 시키고, 클릭 이벤트가 발생한 img el 의 상세 영화 정보를 state로 관리 시키는 기능을 담당한다.<pre><code class="language-tsx">// src &gt; components &gt; Row.js
const [modalOpen, setModalOpen] = useState(false);
const [movieSelected, setMovieSelected] = useState({});
...
const ModalhadleClick = (movie) =&gt; {
setModalOpen(true);
setMovieSelected(movie);
console.log(&quot;handledMovie&quot;, movie);
};
...
...
&lt;RowPosters id={id}&gt;
   {movies.map((movie) =&gt; (
     &lt;img
       key={movie.id}
       className=&quot;row__poster&quot;
       src={`https://image.tmdb.org/t/p/original/
            ${movie.backdrop_path}`}
       alt={movie.name}
       onClick={() =&gt; ModalhadleClick(movie)}
     /&gt;
   ))}
&lt;/RowPosters&gt;</code></pre>
</li>
</ol>
</li>
</ul>
<h3 id="moviemodal-컴포넌트로-props-전달하기">MovieModal 컴포넌트로 Props 전달하기</h3>
<ol>
<li><p>row컴포넌트 내에 img el를 클릭 했을 때, 다음과 같은 과정이 수행된다.
1) onClick 이벤트 발생하면서, movie state를 인자로 전달받은 ModalhadleClick 함수가 호출된다.
2) ModalhadleClick 함수가 호출되면서, 다음 두 개의 state를 변경시킨다.</p>
<ul>
<li>setModalOpen(true);</li>
<li>setMovieSelected(movie);</li>
</ul>
<p>3) modalOpen state가 true일 때, MovieModal 컴포넌트가 마운팅 되도록 설정한다.</p>
</li>
</ol>
<pre><code class="language-tsx">  {modalOpen &amp;&amp; ( &lt;MovieModal /&gt;)}</code></pre>
<p>  4) MovieModal 컴포넌트에 필요한 아래 두가지 props를 전달한다.
    * 모달을 비활성화 할 것을 감안하여, setModalOpen를 props로 전달한다.<br>    * 모달에서 사용할 movieSelected의 속성을 사용하기 위해, movieSelected의 속성들을 전개연산자를 사용하여 Props로 전달한다.
        backdrop_path, title, overview, name?, release_date?, first_air_date, vote_average 속성을 사용할 계획인다.
    <code>tsx
      {modalOpen &amp;&amp; (
        &lt;MovieModal setModalOpen={setModalOpen} {...movieSelected} /&gt;
      )}</code></p>
<h3 id="moviemodal-컴포넌트-구조">MovieModal 컴포넌트 구조</h3>
<pre><code class="language-tsx">  import React from &quot;react&quot;;
  import { ModalContent, ModalWrapper, Presentation } from &quot;./MovieModal.styled&quot;;

  const MovieModal = ({
    backdrop_path,
    title,
    overview,
    name,
    release_date,
    first_air_date,
    vote_average,
    setModalOpen,
  }) =&gt; {
    return (
      &lt;Presentation role=&quot;presentation&quot;&gt;
        &lt;ModalWrapper&gt;
          &lt;div className=&quot;modal&quot;&gt;
            &lt;span className=&quot;modal__close&quot; onClick={() =&gt; setModalOpen(false)}&gt;
              X
            &lt;/span&gt;

            &lt;img
              className=&quot;modal__poster-img&quot;
              src={`https://image.tmdb.org/t/p/original/${backdrop_path}`}
              alt=&quot;modal-img&quot;
            /&gt;

            &lt;ModalContent&gt;
              &lt;p className=&quot;modal__details&quot;&gt;
                &lt;span className=&quot;modal__user_perc&quot;&gt;100% for you&lt;/span&gt;{&quot; &quot;}
                {/* 개봉일자 혹은 첫 상영일자 */}
                {release_date ? release_date : first_air_date}
              &lt;/p&gt;

              &lt;h2 className=&quot;modal__title&quot;&gt;{title ? title : name}&lt;/h2&gt;
              &lt;p className=&quot;modal__overview&quot;&gt;평점: {vote_average}&lt;/p&gt;
              &lt;p className=&quot;modal__overview&quot;&gt;{overview}&lt;/p&gt;
            &lt;/ModalContent&gt;
          &lt;/div&gt;
        &lt;/ModalWrapper&gt;
      &lt;/Presentation&gt;
    );
  };

  export default MovieModal;
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[개인프로젝트] (에러핸들링) Row Component 
 useEffect 경고( React Hook useEffect has a missing dependency eslintreact-hooks/exhaustive-deps)]]></title>
            <link>https://velog.io/@sg_yksv77/%EA%B0%9C%EC%9D%B8%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-Row-Component</link>
            <guid>https://velog.io/@sg_yksv77/%EA%B0%9C%EC%9D%B8%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-Row-Component</guid>
            <pubDate>Tue, 21 Mar 2023 02:10:56 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/sg_yksv77/post/8c2d7729-6350-4208-b74e-299e8d1bd208/image.png" alt=""></p>
<ol>
<li>태마 별로 영화를 나열 하기 위한 Row Component를 설계한다.
 1) trending Now
 2) Top Rated
 3) Action Movies
 4) Comedy Movies</li>
<li>모든 Row Component 의 UI는 같으므로, 재사용 하도록 만든다.</li>
<li>각 Row Component에 맞는 데이터를 사용하기 위해, props 객체에 title, id, fetchURL 을 전달한다.<pre><code class="language-tsx">// src &gt; App.js
import { Container, GlobalStyle } from &quot;./App.styled.js&quot;;
import Banner from &quot;./components/Banner.js&quot;;
import Nav from &quot;./components/Nav&quot;;
import Category from &quot;./components/Category&quot;;
import Row from &quot;./components/Row&quot;;
import requests from &quot;./api/requests.js&quot;;
function App() {
return (
 &lt;&gt;
   &lt;GlobalStyle /&gt;
   &lt;Container&gt;
     &lt;Nav /&gt;
     &lt;Banner /&gt;
     &lt;Category /&gt;
     &lt;Row 
       title=&quot;Trending Now&quot; 
       id=&quot;TN&quot; fetchURL={requests.fetchTrending} /&gt;
     &lt;Row 
       title=&quot;Top Rated&quot; 
       id=&quot;TR&quot; fetchURL={requests.fetchTopRated} /&gt;
     &lt;Row
       title=&quot;Action Movies&quot;
       id=&quot;AM&quot;
       fetchURL={requests.fetchActionMovies}
     /&gt;
     &lt;Row
       title=&quot;Comedy Movies&quot;
       id=&quot;CM&quot;
       fetchURL={requests.fetchComedyMovies}
     /&gt;
   &lt;/Container&gt;
 &lt;/&gt;
);
}
</code></pre>
</li>
</ol>
<p>export default App;</p>
<pre><code>
### 문제 발생
&gt; React Hook useEffect has a missing dependency: &#39;fecthMovieData&#39;. Either include it or remove the dependency array.

### 원인 확인
&gt; [useEffect가 하는 일은 무엇일까요? useEffect Hook을 이용하여 우리는 React에게 컴포넌트가 렌더링 이후에 어떤 일을 수행해야하는 지를 말합니다. React는 우리가 넘긴 함수를 기억했다가(이 함수를 ‘effect’라고 부릅니다) DOM 업데이트를 수행한 이후에 불러낼 것입니다. ](https://ko.reactjs.org/docs/hooks-effect.html)

### 1. React에서 컴포넌트 렌더링 시 어떤 일을 수행하는가? 
&gt; **컴포넌트가 랜더링 될 때, 리액트는 컴포넌트 내 함수를 기억하고 DOM 없데이트를 수행 한 이후 호출하게 된다.**

* 컴포넌트 랜더링 과정을 이해하지 못했기 때문에 나타난 경고문이다.
이는 불필요한 함수의 재생성으로써, 필요시에만 재생성 될 수 있도록 메모이제이션을 해주면 해결된다.
### 해결 1.
(dependency)으로 설정한다.

1. 컴포넌트가 랜더링 될 때, fetchMovieData의 불필요한 재생성을 막기 위해, useCallbakc의 콜백함수로 fetchMovieData를 전달한다.
2. useCallbakc 이 새로 생성되는 조건은 fetchUrl 가 변경될 때마다 생성되야 하므로, useCallbakc의 dependency 배열에 fetchUrl 를 전달한다.
3. fetchUrl의 변경에 따라, useCallbakc 이 새로 생성되면,  fetchMovieData의 값도 변경된다.
4. fetchMovieData 의 값이 변경될 때마다, useEffect도 변경되어야 하므로, useEffect도 dependency를 fetchMovieData로 정의한다.


* useCallbakc의
```tsx
import React, { useState, useEffect, useCallback } from &quot;react&quot;;
import { axiosInstance } from &quot;../api/axios&quot;;
import requests from &quot;../api/requests&quot;;
const Row = ({ title, id, fetchUrl }) =&gt; {
  const [movies, setMovies] = useState([]);
 // 2) fecthMovieData 변경 -&gt; useEffect 호출
  useEffect(() =&gt; {
    fecthMovieData();
  }, [fecthMovieData]); /* &#39;fecthMovieData&#39; was used before it 
  was defined.eslintno-use-before-define */

 // 1) fetchUrl 변경 -&gt; useCallback() -&gt; fecthMovieData 변경
  const fecthMovieData = useCallback(async () =&gt; {
    const axiosReq = await axiosInstance.get(fetchUrl);
    console.log(&quot;axiosReq&quot;, axiosReq);
    setMovies(axiosReq.data.results);
  }, [fetchUrl]);

  return &lt;div&gt;Row&lt;/div&gt;;
};

export default Row;</code></pre><h3 id="2-함수-표현식이-호이스팅되어-함수가-아닌-변수로-인식-됨">2. 함수 표현식이 호이스팅되어 함수가 아닌, 변수로 인식 됨</h3>
<ul>
<li>useEffect는 컴포넌트가 랜더링 될 때, 수행하는 side-effect 라는 것을 기억해야한다.</li>
<li>이를 이해했다면, 위 코드에서 &quot;&#39;fecthMovieData&#39; was used before it was defined.&quot; 경로를 보내주는지 이해할 수 있다.</li>
<li>위 로직은 컴포넌트가 랜더링 되고 fecthMovieData 함수를 호출하고 있다.</li>
<li>그러나, 호이스팅의 결과로 fecthMovieData 는 함수가 아닌 변수로 인식되어, fecthMovieData 가 정의되기 전에 사용되었다고 경고하는 것이다.<h3 id="해결-2">해결 2.</h3>
</li>
<li>이를 해결하기 위해, 컴포넌트의 랜더링 과정에 맞게, fecthMovieData 함수를 useEffect 상단에 위치 시켜, side-effect를 가능하게 했다.</li>
</ul>
<pre><code class="language-tsx">import React, { useState, useEffect, useCallback } from &quot;react&quot;;
import { axiosInstance } from &quot;../api/axios&quot;;
import requests from &quot;../api/requests&quot;;
const Row = ({ title, id, fetchUrl }) =&gt; {
  const [movies, setMovies] = useState([]);

  const fecthMovieData = useCallback(async () =&gt; {
    const axiosReq = await axiosInstance.get(fetchUrl);
    console.log(&quot;axiosReq&quot;, axiosReq);
    setMovies(axiosReq.data.results);
  }, [fetchUrl]);

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

  return &lt;div&gt;Row&lt;/div&gt;;
};

export default Row;
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[개인프로젝트] Category Component]]></title>
            <link>https://velog.io/@sg_yksv77/%EA%B0%9C%EC%9D%B8%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-Category-Component</link>
            <guid>https://velog.io/@sg_yksv77/%EA%B0%9C%EC%9D%B8%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-Category-Component</guid>
            <pubDate>Thu, 16 Mar 2023 13:32:12 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/sg_yksv77/post/bfc51b46-7b55-4dd2-8806-f0430a59cf9e/image.png" alt=""></p>
<ol>
<li>배너 컴포넌트와 margin-top: 30px</li>
<li>grid로 배치하여, 5개의 컨텐츠가 25px 간격으로 동일한 비율로 공간을 차지하게 한다.</li>
<li>768px 이하로 크기가 줄어들 경우, 1개의 컨텐츠만 공간을 차지하게 한다.</li>
</ol>
<pre><code class="language-tsx">export const Container = styled.div`
  margin-top: 30px;
  padding: 30px 0px 26px;
  display: grid;
  gap: 25px
  grid-template-columns: repeat(5, 1fr);

  @media (max-width: 768px) {
    grid-template-columns: repeat(1, 1fr);
  }
`;</code></pre>
<p><img src="https://velog.velcdn.com/images/sg_yksv77/post/4d68e5dd-72a3-417c-8b9b-a5cd638dd696/image.gif" alt=""></p>
<p>Wrap Styled Component에 스타일링을 해서 크기를 맞춰준다.</p>
<h3 id="wrap-styled-component">Wrap Styled Component</h3>
<ol>
<li><p>Wrap Styled Component는 다음과 같이 구성되어 있다.</p>
<pre><code class="language-tsx">   &lt;Wrap&gt;
     &lt;img src=&quot;/images/viewers-disney.png&quot; alt=&quot;disney&quot; /&gt;
     &lt;video autoPlay loop muted&gt;
       &lt;source src=&quot;/videos/disney.mp4&quot; type=&quot;video/mp4&quot;&gt;&lt;/source&gt;
     &lt;/video&gt;
   &lt;/Wrap&gt;</code></pre>
</li>
<li><p>위와 같이 구성된 Wrap Styled Component는 총 5개로 위치한다.</p>
</li>
<li><p>이때, 5개의 Wrap Styled Component는 Container Styled Component 에 의해, 
같은 비율로 25px 간격으로 Category Component의 공간을 차지하게 된다.</p>
</li>
<li><p>따라서, img element를 </p>
<ol>
<li>부모 컴포넌트인 Wrap component 내에서 img element를 옆으로 배치하기 위해
1) display: block, 2) position: absolute 를 설정한다.</li>
<li>height은 Wrap component의 100% 차지하게 한다.</li>
<li>img 소스는 동일한 크기를 가지므로, object-fit 속성을 사용하여, img 소스의 크기에 맞춰 가득 채운다.</li>
</ol>
</li>
<li><p>video element는</p>
<ol>
<li><p>img element와 동일하게 Wrap component의 100% 크기를 할당한다.</p>
</li>
<li><p>부모 컴포넌트인 Wrap component를 기준으로 position 을 잡는다.</p>
</li>
<li><p>img element와 위치를 맞추기 위해 top: 0</p>
</li>
<li><p>마지막으로, video element의 기본 값은 img element와 겹쳐있되, 투명해야한다.</p>
</li>
<li><p>Wrap component가 hover 되었을 때, video element의 불투명하게 된다.</p>
<p><img src="https://velog.velcdn.com/images/sg_yksv77/post/c9166d73-a955-4ec9-9813-50b60e3cdace/image.gif" alt=""></p>
</li>
</ol>
</li>
</ol>
<h3 id="문제-발생과-해결">문제 발생과 해결</h3>
<ol>
<li>의도대로 디자인이되었다면 위 영상처럼 정상적으로 움직여야 한다.</li>
<li>그러나, 의도대로 되지 않고, 다음과 같은 문제가 발생했다.
<img src="https://velog.velcdn.com/images/sg_yksv77/post/82e1334d-e158-4554-967c-fa099d8b1a4c/image.gif" alt="">1) img element 가 Wrap Styled Component 내 고정되어 있지 않고, 
 App Component 안에 있는 Container Styled Component 에 위치 하고 있음이 확인됐다.
 2) 원인은 img element가 부모 컴포넌트인 Wrap Styled Component 를 기준으로 positions을 잡았으나,
 정작, 부모 컴포넌트인 Wrap Styled Component 에 position 을 실수로 잡지 않았기 때문이었다.
 3) 그렇기 때문에, img element 의 position은 Category Component의 상위 Component인
 Container Styled Component가 기준이 된 것이다.
 4) 따라서, Wrap Styled Component의 position: relative를 추가하여, 이 문제를 해결할 수 있었다.
<img src="https://velog.velcdn.com/images/sg_yksv77/post/bc830d84-d016-4ce9-8cd4-4d0b7249535d/image.gif" alt=""></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[개인프로젝트] 비디오 배너 생성(Iframe 컴포넌트)]]></title>
            <link>https://velog.io/@sg_yksv77/%EA%B0%9C%EC%9D%B8%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%B9%84%EB%94%94%EC%98%A4-%EB%B0%B0%EB%84%88-%EC%83%9D%EC%84%B1</link>
            <guid>https://velog.io/@sg_yksv77/%EA%B0%9C%EC%9D%B8%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%B9%84%EB%94%94%EC%98%A4-%EB%B0%B0%EB%84%88-%EC%83%9D%EC%84%B1</guid>
            <pubDate>Thu, 16 Mar 2023 06:22:28 GMT</pubDate>
            <description><![CDATA[<h3 id="목표">목표</h3>
<ol>
<li>the movie DB API로 받아온 영화 데이터 중 비디오 데이터를 배너 컴포넌트에서 재생하려고 한다.</li>
<li>비디오 데이터가 있을 경우, 배너에 Play 버튼이 활성화 된다.</li>
<li>해당 버튼을 클릭하여, 비디오를 재생한다.</li>
</ol>
<h3 id="방법">방법</h3>
<ol>
<li>Play 버튼을 클릭하면, 기존의 배너 React Element 랜더링하지 않고,
비디오 React Element가 랜더링되도록 조건문을 설정한다.</li>
<li>조건문은 Play 버튼에 클릭이벤트가 발생했는지 여부를 파단한다.</li>
<li>따라서, Play 버튼의 클릭 이벤트 활성화 유무를 상태로 관리해야한다. </li>
</ol>
<h3 id="적용">적용</h3>
<pre><code class="language-tsx">import React, { useEffect, useState } from &quot;react&quot;;
import { fetchData } from &quot;../api/axios&quot;;
import {
  HeaderBanner,
  BannerContents,
  BannerTitle,
  BannerButtons,
  BannerButton,
  BannerDescription,
  BannerFadeBottom,
} from &quot;./Banner.styled&quot;;

const Banner = () =&gt; {
  const [movie, setMovie] = useState([]);
  /* play 버튼 클릭 여부 상태 */
  const [isClicked, setClicked] = useState(false); 
  useEffect(() =&gt; {
    fetchData(setMovie);
  }, []);

  // Description 컴포넌트 내
  // 설명글이 100자 이상이면, 100자 이전 까지 자른 후, 100자부터 &quot;...&quot;로 마무리한다.
  const truncatOverview = (str, n) =&gt; {
    return str?.length &gt; n ? str.substring(0, n) + &quot;...&quot; : str;
  };

  const playHandle = (e) =&gt; setClicked(true);
  /* play 버튼이 클릭되었다면(true) video string react element 랜더링 */
  if (isClicked) {
    console.log(isClicked);
    return &lt;div&gt;video!&lt;/div&gt;;
  }

  return (
    &lt;HeaderBanner
      movie={`https://image.tmdb.org/t/p/original/${movie.backdrop_path}`}
    &gt;
      &lt;BannerContents&gt;
        &lt;BannerTitle&gt;
          {movie.title || movie.name || movie.original_name}
        &lt;/BannerTitle&gt;
        &lt;BannerButtons&gt;
          {movie?.videos?.results[0]?.key &amp;&amp; (
            &lt;BannerButton play onClick={playHandle}&gt;
              Play
            &lt;/BannerButton&gt;
          )}
        &lt;/BannerButtons&gt;
        &lt;BannerDescription&gt;
          {truncatOverview(movie?.overview, 100)}
        &lt;/BannerDescription&gt;
      &lt;/BannerContents&gt;
      &lt;BannerFadeBottom /&gt;
    &lt;/HeaderBanner&gt;
  );
};

export default Banner;</code></pre>
<h3 id="결과1">결과1.</h3>
<p>원하는 결과를 얻을 수 있었다.
이제 video react element를 만들면 된다.
<img src="https://velog.velcdn.com/images/sg_yksv77/post/737a5bcb-cb37-431f-be1d-58781172b2d0/image.gif" alt=""></p>
<h3 id="video-react-element">video react element</h3>
<ol>
<li>styled component를 사용한 UI 생성<ul>
<li>Container<ol>
<li>배치 : 정 가운데로 배치</li>
<li>쌓임순서 : 수직 방향으로 쌓아서, 비디오 -&gt; X 버튼 순으로 쌓이도록 설정<ol start="3">
<li>크기 : 전체 사용<pre><code class="language-tsx">export const Container = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%; /* App.styled.js 의 Container가 main tag이므로 참고 */
height: 100vh; /* App.styled.js 의 Container 참고 */
`;

</code></pre>
</li>
</ol>
</li>
</ol>
</li>
</ul>
</li>
</ol>
<pre><code>* HomContainer 
    1. 크기 : Continer 전체 사용
    ```tsx
    export const HomeContainer = styled.div`
      width: 100%;
      height: 100%;
    `;

* Iframe
  ```tsx
  export const Iframe = styled.iframe`
    width: 100%;
    height: 100%;
    z-index: -1;
    opacity: 0.65;
    border: none;

    &amp;::after {
      content: &quot;&quot;;
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
    }
  `;
    `</code></pre><h3 id="학습--iframe">학습 : Iframe</h3>
<blockquote>
<p>Iframe : The Inline Frame element
<a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe">The &quot;iframe&quot; HTML element represents a nested browsing context, embedding another HTML page into the current one.</a></p>
</blockquote>
<p>Iframe은 inline frame의 약자이며,
효과적으로 다른 HTML페이지를 현재 페이지에 포함시킬 때 사용할 수 있다.
따라서, Iframe 요소를 사용하면, 해당 웹 페이지 안에 어떠한 제한 없이 다른 페이지를 불어와서 삽입 할 수 있다.</p>
<h3 id="iframe-적용하기">Iframe 적용하기</h3>
<ul>
<li>랜더링 할 video는 유튜브를 재생하는 것이므로, Iframe 요소를 사용하는 것이 적절하다.
<img src="https://velog.velcdn.com/images/sg_yksv77/post/cba4cd16-43c8-45fb-905d-dd80d308433b/image.png" alt=""></li>
</ul>
<h3 id="유튜브-embedded">유튜브 embedded</h3>
<p><img src="https://velog.velcdn.com/images/sg_yksv77/post/fb51e863-6635-453e-bd1d-c08c110d365f/image.gif" alt=""></p>
<pre><code class="language-tsx"> &lt;iframe
    width=&quot;560&quot;
    height=&quot;315&quot;
    src=&quot;https://www.youtube.com/embed/k_3E2y5l_rw&quot;
    /* src=&quot;https://www.youtube.com/embed/{videoID}?option&quot; */
    title=&quot;YouTube video player&quot;
    frameborder=&quot;0&quot;
    allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;
    allowfullscreen
  &gt;&lt;/iframe&gt;;</code></pre>
<h3 id="iframe-컴포넌트에-유튜브-연결하기">Iframe 컴포넌트에 유튜브 연결하기</h3>
<ol>
<li>axios 인스턴스(src &gt; api &gt; axios.js &gt; fetchData() )를 통해 전달 받은 랜덤한 영화 데이터의 key를 확인한다.
<img src="https://velog.velcdn.com/images/sg_yksv77/post/35771aa8-cd95-465a-b166-76a5812f58f3/image.png" alt=""></li>
<li>movie state에 axios 인스턴스의 리턴 값이 관리 된다.</li>
<li>따라서, 콘솔 확인 결과, key 값은 movie.videos.results[0].key를 통해 확인할 수 있다.</li>
</ol>
<pre><code class="language-tsx"> if (isClicked) {
    console.log(isClicked);
    return (
      &lt;&gt;
        &lt;Container&gt;
          &lt;HomeContainer&gt;
            &lt;Iframe
              src={`https://www.youtube.com/embed/
                ${movie.videos.results[0].key}?
                controls=0&amp;autoplay=1&amp;loop=1&amp;mute=1&amp;playlist=
                ${movie.videos.results[0].key}`}
              width=&quot;640&quot;
              height=&quot;360&quot;
              frameborder=&quot;0&quot;
              allow=&quot;autoplay; fullscreen&quot;
            &gt;&lt;/Iframe&gt;
          &lt;/HomeContainer&gt;
        &lt;/Container&gt;
        &lt;button onClick={() =&gt; setClicked(false)}&gt;X&lt;/button&gt;
      &lt;/&gt;
    );
  }</code></pre>
<p><img src="https://velog.velcdn.com/images/sg_yksv77/post/204eedb9-4fcd-47bb-b34c-a43d1e371334/image.gif" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[개인프로젝트] styled-component 외부 url : new URL vs props]]></title>
            <link>https://velog.io/@sg_yksv77/%EA%B0%9C%EC%9D%B8%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8</link>
            <guid>https://velog.io/@sg_yksv77/%EA%B0%9C%EC%9D%B8%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8</guid>
            <pubDate>Thu, 16 Mar 2023 01:31:16 GMT</pubDate>
            <description><![CDATA[<ol>
<li>배너 이미지 사용하기</li>
<li>배너 포지션</li>
<li>사이즈</li>
</ol>
<h3 id="문제-발생">문제 발생</h3>
<ol>
<li>styled-component 에서 background-image 에 외부 url을 전달 할 때 다음과 같은 에러가 발생했다.<pre><code class="language-tsx">import styled from &quot;styled-components&quot;;
export const HeaderBanner = styled.header`
background-image: ${(props) =&gt;
 URL(`https:/image.tmdb.org/t/p/original/${props.movie.backdrop_path}`)};
background-position: &quot;top center&quot;;
background-size: cover;
`;</code></pre>
<blockquote>
<p>TypeError: Failed to construct &#39;URL&#39;: Please use the &#39;new&#39; operator, this DOM object constructor cannot be called as a function</p>
</blockquote>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/sg_yksv77/post/e4361595-e282-4db0-a621-099f38d918f6/image.png" alt=""></p>
<h3 id="해결">해결</h3>
<ol>
<li>오류 메세지를 자세하게 읽지 않고, <a href="(https://stackoverflow.com/questions/62643990/typeerror-failed-to-construct-url-please-use-the-new-operator-this-dom-ob)">stackoverflow</a>를 참고하여 답을 찾았다.</li>
<li>그러나, 이미 오류 메세지에서 new 키워드를 사용할 것을 제안하고 있었다.</li>
<li>이번에도, 오류 메세지를 잘 봐야 함을 다시 깨달았다.
<img src="https://velog.velcdn.com/images/sg_yksv77/post/21643a94-1bf8-49a5-a9ac-ce15ffeae195/image.png" alt=""><pre><code class="language-tsx">import styled from &quot;styled-components&quot;;
export const HeaderBanner = styled.header`
background-image: ${(props) =&gt;
 new URL(
   `https:/image.tmdb.org/t/p/original/${props.movie.backdrop_path}`)};
background-position: &quot;top center&quot;;
background-size: cover;
`;
</code></pre>
</li>
</ol>
<pre><code>
### 해결책이 아니었음!
new URL을 사용하면 URL 객체가 생성되므로, 원하는 외부 이미지 url을 가져와 배경이미지로 사용할 수 없었음

### 해결 방법
styled-component 에 props로 외부 이미지 url을 내려 보냄으로써 해결했다.

```tsx
// src &gt; components &gt; Banner.js 
return (
    &lt;HeaderBanner
   movie={`https://image.tmdb.org/t/p/original/${movie.backdrop_path}`}
    &gt;</code></pre><pre><code class="language-tsx">// src &gt; components &gt; Banner.styled.js 
import styled from &quot;styled-components&quot;;

export const HeaderBanner = styled.header`
  background-image: url(${(props) =&gt; props.movie});
  background-position: top center;
  background-size: cover;
`;</code></pre>
<p><img src="https://velog.velcdn.com/images/sg_yksv77/post/0cad79e4-6e5b-465e-bcda-fc83bddb6a67/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[개인프로젝트] API 호출(1) ]]></title>
            <link>https://velog.io/@sg_yksv77/%EA%B0%9C%EC%9D%B8%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-API-%ED%98%B8%EC%B6%9C1</link>
            <guid>https://velog.io/@sg_yksv77/%EA%B0%9C%EC%9D%B8%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-API-%ED%98%B8%EC%B6%9C1</guid>
            <pubDate>Wed, 15 Mar 2023 07:50:40 GMT</pubDate>
            <description><![CDATA[<h3 id="첫번째-목적">첫번째 목적</h3>
<ol>
<li>Banner 컴포넌트에서 배경 이미지를 사용하려고 한다.</li>
<li>사용될 이미지 소스는 The Movie DB API 에서 받은 데이터를 사용할 것이다.</li>
<li>API 요청을 위한 Axios 인스턴스를 생성하여 재사용성을 높이려 한다. </li>
<li>Banner 컴포넌트가 랜더링 될 때, Axios 인스턴스가 실행되어야 한다.</li>
</ol>
<h3 id="방법">방법</h3>
<ol>
<li>useEffect를 사용하여, 컴포넌트가 랜더링 됨과 동시에 aixos 인스턴스를 실행하도록 구현한다.<pre><code class="language-tsx">useEffec(() =&gt; {
/*Axios 인스턴스 : */fetchData() }, [])</code></pre>
</li>
<li>api 폴더 내, axios.js에서 axios 인스턴스를 만들고, 이를 banner 컴포넌트에서 import 하여 사용한다.<pre><code class="language-tsx">// src &gt; api &gt; axios.js 
</code></pre>
</li>
</ol>
<p>import axios from &quot;axios&quot;;
import requests from &quot;./requests&quot;;</p>
<p>export const instance = axios.create({
  baseURL: &quot;<a href="https://api.themoviedb.org/3/&quot;">https://api.themoviedb.org/3/&quot;</a> /* 중복 URL */,
  params: {
    api_key: &quot;a38fa376de46b95eb6b7c9eff2b1116c&quot;,
    language: &quot;ko-KR&quot;,
  },
});</p>
<p>export const fetchData = () =&gt; {
  // 현재 상영 중인 영화 정보 가져오기
  const request = instance.get(requests.fetchNowPlaying);
  // request 확인하기
  console.log(&quot;request :&quot;, request);
};</p>
<pre><code>3. Banner 컴포넌트를 App 컴포넌트에 import 하여 원하는 데이터가 들어왔는지 확인한다.

### 문제: &quot;다시 확인! 비동기 처리의 필요성&quot;
1. App 컴포넌트를 랜더링 하였으나, 콘솔창에는 다음 과 같은 결과가 나왔다.</code></pre><p>request : Promise {<pending>}</p>
<pre><code>### 원인 : 동기적 데이터 흐름
1. 의도는 Axios 인스턴스 요청으로 서버에서 Response가 도착한 뒤 결과값을 콘솔로 확인하려 했다.
2. 그러나, 실제로는 Response가 오지 않은 상태에서 결과 값을 콘솔로 확인 했기 때문에, pending 이 된 것이다.
이는 아래 이미지를 통해 이해할 수 있다. ![](https://velog.velcdn.com/images/sg_yksv77/post/9dd99852-448c-477d-a1f4-00157b618a0e/image.png)

### 해결 : 비동기 적용
1. 위 문제는 Axios 요청과 응답을 동기적으로 진행했기 때문에 발생한 사소한 문제이다.
2. 이를 해결하기 위해, 의도대로 Response 가 도착한 뒤 호출할 수 있게 비동기로 전환하면 간단히 해결할 수 있다.
3. 이때, fetch를 사용해도 되지만, then 체이닝이나, 결과값을 JSON으로 전환해야하는 번거로움이 있으므로, Async &amp; Await를 사용하여 간결하게 작성했다.
```tsx
export const fetchData = async () =&gt; {
  // 현재 상영 중인 영화 정보 가져오기
  const request = await instance.get(requests.fetchNowPlaying);
  // request 확인하기
  console.log(&quot;request :&quot;, request);
};
</code></pre><p><img src="https://velog.velcdn.com/images/sg_yksv77/post/1d18e52a-02fc-4e13-ac70-f4333145da66/image.png" alt=""></p>
<h3 id="두번째-목적">두번째 목적</h3>
<ol>
<li>호출된 영화 정보들 중 랜덤으로 영화 하나의 ID를 가져오려고 한다.</li>
<li>랜덤으로 가져온 영화 ID의 상세 정보(비디오 정보 포함)를 가져온다.</li>
</ol>
<h3 id="방법-1">방법</h3>
<ol>
<li>랜덤한 값의 범위는 전달 받은 영화들의 개수 만큼 설정한다. </li>
<li>이를 구현하기 위해, Math.floor와 Math.random() 을 사용한다.<pre><code class="language-tsx">Math.floor(Math.random() * request.data.results.length)</code></pre>
</li>
<li>위와 같이 얻게 될 랜덤 값을 전달받은 영화 데이터의 인덱스로 사용하여, 원하는 영화 ID를 가져온다. <pre><code class="language-tsx">export const fetchData = async (setState) =&gt; {
// 현재 상영 중인 영화 정보 가져오기
const requestMovieData = 
     await axiosInstance.get(requests.fetchNowPlaying);
// request 확인하기
console.log(&quot;requestMovieData :&quot;, requestMovieData);
const movieId =
 requestMovieData.data.results[
   Math.floor(Math.random() * requestMovieData.data.results.length)
 ].id;
console.log(&quot;movieId :&quot;, movieId);
</code></pre>
</li>
</ol>
<pre><code>![](https://velog.velcdn.com/images/sg_yksv77/post/2039708f-7b00-433f-b770-a754114d3c55/image.png)

4. 랜덤 한 영화 ID를 얻었으므로, 이를 사용하여, 해당 영화 ID의 상세 정보를 받아 온다.
5. 이때, 비디오 정보도 포함해서 받아 와야 한다.
이는 [The Movie API Append To Response](https://developers.themoviedb.org/3/getting-started/append-to-response)를 참고해서 진행했다.
![](https://velog.velcdn.com/images/sg_yksv77/post/07f6ddeb-b018-4df8-bbe4-22e906e6e8d1/image.png)
```tsx
export const fetchData = async (setState) =&gt; {
  // 현재 상영 중인 영화 정보 가져오기
  const requestMovieData = await axiosInstance.get(requests.fetchNowPlaying);
  // request 확인하기
  console.log(&quot;requestMovieData :&quot;, requestMovieData);
  const movieId =
    requestMovieData.data.results[
      Math.floor(Math.random() * requestMovieData.data.results.length)
    ].id;
  console.log(&quot;movieId :&quot;, movieId);

  const { data: movieDetail } = 
        await axiosInstance.get(`movie/${movieId}`, {
    params: { append_to_response: &quot;videos&quot; },
  });
};</code></pre><h3 id="문제-발생--스코프에-막힌-상태-변경">문제 발생 : 스코프에 막힌 상태 변경</h3>
<ol>
<li><p>영화 상세 정보를 가져와 movieDetail 에 담아놓았다.</p>
</li>
<li><p>상세 정보를 가져오는 fetchData 인스턴스를 Banner 컴포넌트에서 사용하여, 영화 정보 상태를 변경하려고 했다.</p>
</li>
<li><p>그러나, 인스턴스 내에서 가져온 영화 상세 정보 데이터는 인스턴스를 import한 Banner 컴포넌트에서 사용하려 할 때, 스코프에 막혀 데이터를 인용할 수 없는 문제가 발생했다.</p>
</li>
<li><p>그런데, 이는 조금만 생각하면 너무 간단하게 풀리는 문제였다.</p>
</li>
<li><p>fetchData 인스턴스의 매개변수로 컴포넌트가 사용할 State를 전달받으면 되는 문제였다.</p>
</li>
<li><p>이렇게하면, 필요한 movieDetail가 스코프 내에서 Banner 컴포넌트의 setMovie에 인용될 수 있게 된다.</p>
<pre><code class="language-tsx">// src &gt; api &gt; axios.js
export const fetchData = async (setState) =&gt; {
// 현재 상영 중인 영화 정보 가져오기
const requestMovieData = 
     await axiosInstance.get(requests.fetchNowPlaying);
// request 확인하기
console.log(&quot;requestMovieData :&quot;, requestMovieData);
const movieId =
 requestMovieData.data.results[
   Math.floor(Math.random() * requestMovieData.data.results.length)
 ].id;
console.log(&quot;movieId :&quot;, movieId);

const { data: movieDetail } = 
     await axiosInstance.get(`movie/${movieId}`, {
 params: { append_to_response: &quot;videos&quot; },
});
setState(movieDetail);
};</code></pre>
<pre><code class="language-tsx">// src &gt; components &gt; Banner.js
import React, { useEffect, useState } from &quot;react&quot;;
import { fetchData } from &quot;../api/axios&quot;;
</code></pre>
</li>
</ol>
<p>const Banner = () =&gt; {
  const [movie, setMovie] = useState([]);
  useEffect(() =&gt; {
    fetchData(setMovie);
  }, []);
  return <div>Banner</div>;
};</p>
<p>export default Banner;</p>
<p>```</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[에러핸들링] styled component 전역 스타일링(Global Style)]]></title>
            <link>https://velog.io/@sg_yksv77/%EC%97%90%EB%9F%AC%ED%95%B8%EB%93%A4%EB%A7%81-styled-component-%EC%A0%84%EC%97%AD-%EC%8A%A4%ED%83%80%EC%9D%BC%EB%A7%81Global-Style</link>
            <guid>https://velog.io/@sg_yksv77/%EC%97%90%EB%9F%AC%ED%95%B8%EB%93%A4%EB%A7%81-styled-component-%EC%A0%84%EC%97%AD-%EC%8A%A4%ED%83%80%EC%9D%BC%EB%A7%81Global-Style</guid>
            <pubDate>Thu, 09 Mar 2023 09:06:16 GMT</pubDate>
            <description><![CDATA[<h2 id="의도">의도</h2>
<pre><code class="language-tsx">import { Container } from &quot;./App.styled.js&quot;;
import Nav from &quot;./components/Nav&quot;;
function App() {
  return (
      &lt;Container&gt;
        &lt;Nav /&gt;
      &lt;/Container&gt;
  );
}

export default App;</code></pre>
<ul>
<li>App Component에 전역 스타일링을 다음 과 같이 하려고 한다.<pre><code class="language-css">body {
  background-color: #040714;
  color: #f9f9f9;
  font-family: Avenir-Roman, sans-serif;
  margin: 0;
  padding: 0;
}
a {
  color: #f9f9f9;
  text-decoration: none;
}</code></pre>
</li>
</ul>
<h3 id="문제-발생">문제 발생</h3>
<ul>
<li>Styled Component 에서 전역 스타일링을 하기 위한 방법으로 새로운 Styled Component A를 만들어 최 상위 컴포넌트로 만들면 되지 않을까 예상했지만, 권장사항이 아니었다.<pre><code class="language-tsx">import { Container } from &quot;./App.styled.js&quot;;
import Nav from &quot;./components/Nav&quot;;
function App() {
return (
  &lt;A&gt;
    &lt;Container&gt;
      &lt;Nav /&gt;
    &lt;/Container&gt;
  &lt;A/&gt;
);
}
</code></pre>
</li>
</ul>
<p>export default App;</p>
<pre><code>
### 문제 해결
1.  컴포넌트 레벨 스타일링 과 애플리케이션 레벨 스타일링 을 구분해야한다.
    * **컴포넌트 레벨 스타일링이란?**
        말 그대로, 개별 컴포넌트 단위로 스타일을 하는 것을 말한다. 
        예를 들어, 위 App Component는 Container, Nav(Nav 컴포넌트 내 NavWrapper, Logo styled component 가 있다.) 컴포넌트는 각각 HTML 엘리먼트를 스타일하고 있다. 
        styled component 는 이렇게 컴포넌트 단위로 적용한 스타일을 외부와 완전히 격리시켜 해당 컴포넌트 내부에서만 유효하도록 제한한다.

     * **애플리케이션 레벨 스타일링이란?**
       간단하게, 모든 컴포넌트에 동일하게 적용되는 스타일을 하는 것을 말한다.
       예를 들어, 브라우저에 상관없이 일괄적인 스타일을 적용하기 위해 사용하는 CSS 정규화나 CSS 초기화를 예로 들수 있다.
       이처럼, 여러 컴포넌트에 걸쳐 통일된 스타일은 Body 엘리먼트에 정의해 사용하기도 한다.

 2. 애플리케이션 레벨 스타일링 방법
 * 애플리케이션 레벨 스타일을 지원하기 위해서 Styled Components는 createGlobalStyle()라는 함수를 제공한다.
 * createGlobalStyle() 함수를 사용하여 의도한 전역 스타일을 적용한다.
```css
import styled, { createGlobalStyle } from &quot;styled-components&quot;;

export const GlobalStyle = createGlobalStyle`
  body {
    background-color: #040714;
    color: #f9f9f9;
    font-family: Avenir-Roman, sans-serif;
    margin: 0;
    padding: 0;
  }
  a {
    color: #f9f9f9;
    text-decoration: none;
  }
`</code></pre><h3 id="주의-사항">주의 사항!</h3>
<ul>
<li>createGlobalStyle() 함수로 생성한 전역 스타일 컴포넌트를 애플리케이션의 최 상위 컴포넌트에 추가해주면 하위 모든 컴포넌트에 스타일이 적용된다.</li>
<li>그러나, 최 상위 컴포넌트로 감싸주면 될 것이라 생각하고, 다음과 같이 작성하였으나, 의도대로 적용되지 않았다.<pre><code class="language-tsx">import { Container, GlobalStyle } from &quot;./App.styled.js&quot;;
import Nav from &quot;./components/Nav&quot;;
function App() {
return (
  &lt;GlobalStyle&gt;
    &lt;Container&gt;
      &lt;Nav /&gt;
    &lt;/Container&gt;
  &lt;/GlobalStyle&gt;
);
}
</code></pre>
</li>
</ul>
<p>export default App;</p>
<pre><code>* 이유는, 전역 스타일 컴포넌트 내부로 하위 컴포넌트를 포함시키는 것이 아니라, 최 상위 컴포넌트로 전역 스타일 컴포넌트를 추가해주기만 하면 됐다.
```tsx
import { Container, GlobalStyle } from &quot;./App.styled.js&quot;;
import Nav from &quot;./components/Nav&quot;;
function App() {
  return (
    &lt;&gt;
      &lt;GlobalStyle /&gt;
      &lt;Container&gt;
        &lt;Nav /&gt;
      &lt;/Container&gt;
    &lt;/&gt;
  );
}

export default App;
</code></pre><p><a href="https://www.daleseo.com/styled-components-global-style/">Styled Components 전역 스타일링 (Global Style)</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] CSS wrapper 추가 학습]]></title>
            <link>https://velog.io/@sg_yksv77/TIL-CSS-wrapper-%EC%B6%94%EA%B0%80-%ED%95%99%EC%8A%B5</link>
            <guid>https://velog.io/@sg_yksv77/TIL-CSS-wrapper-%EC%B6%94%EA%B0%80-%ED%95%99%EC%8A%B5</guid>
            <pubDate>Thu, 09 Mar 2023 07:41:19 GMT</pubDate>
            <description><![CDATA[<p><a href="https://itchallenger.tistory.com/908">[CSS] 레이아웃 Wrapper 만들기</a></p>
<h2 id="1-너비-추가하기">1. 너비 추가하기</h2>
<ul>
<li>wrapper를 구현할 때 가장 먼저 결정할 것은 너비이다. </li>
<li>보통 1000px - 1300px 사이 값이 일반적이다.<h3 id="tip">tip</h3>
화면 크기가 1170px 미만일 경우 가로 스크롤이 발생하므로, max-width로 최대 너비만 사용할 수도 있다.<pre><code class="language-css">.wrapper { max-width : 1170px;}</code></pre>
<h2 id="2-중앙-정렬하기">2. 중앙 정렬하기</h2>
<blockquote>
<p>&#39;margin-left&#39;와 &#39;margin-right&#39;가 모두 &#39;auto&#39;이면 사용되는 값은 동일합니다.
컨테이닝 블록의 가장자리를 기준으로 요소를 가로로 중앙에 배치합니다.
<a href="https://ishadeed.com/article/auto-css/">상업기술-CSS사양 재인용</a></p>
</blockquote>
</li>
</ul>
<pre><code class="language-css">.wrapper {
  max-width: 1170px;
  margin: 0 auto;
}</code></pre>
<h2 id="3-좌우-패딩">3. 좌우 패딩</h2>
<blockquote>
<p><strong>&quot;뷰포트 크기&quot;</strong> 가 <strong>&#39;래퍼의 최대 너비&#39;</strong>보다 작으면,
래퍼의 가장자리가 뷰포트에 고정된다.</p>
</blockquote>
<p>wrapper에 좌우 패딩을 추가하면 얻는 이점은,
wrapper와 뷰포트 가장 자리에 달라붙는 현상을 방지해 줄 수 있다.</p>
<pre><code class="language-css">.wrapper {
  max-width: 1170px;
  margin-left: auto;
  margin-right: auto;
  padding-left: 16px;
  padding-right: 16px;
}</code></pre>
<h2 id="4-display-type">4. Display type</h2>
<blockquote>
<p>관심사 분리에 위배 되므로 권장하지 않는 방법이다.</p>
</blockquote>
<p>wrapper는 기본적으로 블록 수준 요소이다.</p>
<h4 id="문제-발생">문제 발생!</h4>
<p>이때, wrapper 내부의 콘텐츠를 그리드에 배치해야 할 때,
어떻게 디스플레이 타입을 그리드로 변경할 수 있을까?</p>
<h4 id="방법권장-x">방법(권장 X)</h4>
<p>그리드 래퍼가 필요한 경우 래퍼 내부에 다른 div 를 추가하면 된다.</p>
<pre><code class="language-tsx">&lt;div class=&quot;wrapper&quot;&gt;
  &lt;div class=&quot;featured-news&quot;&gt;
    &lt;!-- Elements that needs to be placed in a grid --&gt;
  &lt;/div&gt;
&lt;/div&gt;</code></pre>
<pre><code class="language-css">.featured-news {
  display: grid;
  grid-template-columns: 2fr 1fr;
  grid-gap: 16px;
}</code></pre>
<h2 id="5-전체-화면-섹션-내부의-wrapper">5. 전체 화면 섹션 내부의 wrapper</h2>
<blockquote>
<p>뷰포트 내부 모든 컨텐츠의 너비는 wrapper의 너비에 의해 제한 한다.</p>
</blockquote>
<p>관심사 분리를 적용하기 위해, 뷰포트와 내부 컨텐츠를 분리하여, 너비를 분리 시켜야 한다.
이때, 내부 컨텐츠를 각각 wrapper 로 관리하여, 배경 색이나, 이미지가 뷰포트 전체 너비를 차지하지 않도록 방지할 수 있다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[개인프로젝트] styled Component(css 다지기 1.)]]></title>
            <link>https://velog.io/@sg_yksv77/%EA%B0%9C%EC%9D%B8%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-styled-Componentcss-%EB%8B%A4%EC%A7%80%EA%B8%B0-1</link>
            <guid>https://velog.io/@sg_yksv77/%EA%B0%9C%EC%9D%B8%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-styled-Componentcss-%EB%8B%A4%EC%A7%80%EA%B8%B0-1</guid>
            <pubDate>Wed, 08 Mar 2023 13:50:50 GMT</pubDate>
            <description><![CDATA[<h3 id="네비게이션-컴포넌트-스타일링">네비게이션 컴포넌트 스타일링</h3>
<ol>
<li><p>NavWrapper : Nav 컴포넌트의 최상위 styled component</p>
<ul>
<li><p>semantic tag : nav</p>
</li>
<li><p>position : fiexd 를 작용하여, Nav가 고정되도록 설정</p>
</li>
<li><p>z-index : 3 / 네비게이션 컴포넌트가 최상단으로 고정되도록 설정</p>
</li>
<li><p>display: flex / Flex 아이템이 자신의 width 만큼 공간을 차지한 채, 가로 방향으로 배치</p>
</li>
<li><p>Justify-content : space-between / 3개의 flex item이 가장자리에서 부터 동일한 여유 공간을 가지도록 배치</p>
</li>
<li><p>align-items: center / flex item이 가운데 정렬 될 수 있도록 설정(이후 하위 styled component로 상속 됨)</p>
</li>
<li><p>letter-spacing : 16px / 텍스트 사이 간격 지정</p>
<pre><code class="language-tsx">export const NavWrapper = styled.nav`
  position: fixed;
  z-index: 3;
  top: 0; /* top,left,right =&gt; Nav Component 위치 지정  */
  left: 0;
  right: 0;
  height: 70px;
  background-color: #090b13;
  display: flex; /* flex item 고유 width 만큼 가로로 배치 */
  justify-content: space-between;
  align-items: center;
  padding: 0 36px;
  letter-spacing: 16px;
`;</code></pre>
<blockquote>
<p>기초 중의 기초!!  
block element의 특징 : 좌우공간을 모두 차지함
따라서, NavWrapper 에  width 지정 의미 없음!!!</p>
</blockquote>
</li>
</ul>
</li>
</ol>
<ol start="2">
<li><p>Logo</p>
<ul>
<li><p>semantic tag : a / 로고 클릭시 root 경로로 이동</p>
</li>
<li><p>display: inline-block / 이웃한 flex item 과 같은 줄 위치 + width,height 설정</p>
</li>
<li><p>img tag : Logo 컴포넌트와 동일한 크기로 설정</p>
<pre><code class="language-tsx">  export const Logo = styled.a`
  padding: 0;
  width: 80px;
  margin-top: 4px;
  max-height: 70px;
  font-size: 0;
  display: inline-block; /* 이웃한 flex item과 한 줄에 위치 + width,height 사용 */

  img {
    display: block;
    width: 100%; /* img와 Logo 컴포넌트 크기 일치 */
  }
  `;</code></pre>
</li>
</ul>
</li>
</ol>
<h3 id="네비게이션-컴포넌트-스크롤-구현">네비게이션 컴포넌트 스크롤 구현</h3>
<ol>
<li><p>색상 : 배경색이 #090b13(검은색)이며, 실제 색은 투명하다.</p>
</li>
<li><p>따라서, 일정 구간으로 스크롤 되면, 투명을 유지하다가 다시 배경색으로 덮히는 설정을 하려고 한다.</p>
<h3 id="방법">방법</h3>
<ol>
<li>[상태 설정] show 라는 state를 만들고, show 가 true 일때, #090b13, false 일때 투명(transparent)하게 색을 전환 한다.</li>
<li>[조건 설정] 색이 변경되는 위치 값을 얻기 위해, window.scrollY 를 적용하여 스크롤 위치가 50을 초과한 구간부터 #090b13, 이하 일때, 투명하게 색을 전환하는 조건을 설정한다.</li>
<li>[이벤트 리스너] 설정된 조건은 &quot;scroll&quot; 이벤트가 발생될 때, 호촐되엉 하므로, window 객체에 이벤트를 등록한다. </li>
<li>[useEffect] 설정된 이벤트 리스너는 Nav 컴포넌트가 렌더링 될 때, 한번 발생해야하며, 더 이상 Nav 컴포넌트를 사용하지 않을 때, 해당 이벤트 리스너가 호출되지 않도록, 지워주는 코드가 필요하다.</li>
<li>[색상 변경] show state가 변경됨에 따라, 색상 전환이 필요하다. 이는 styled component에 props로 전달하면 해결된다.</li>
</ol>
</li>
</ol>
<pre><code class="language-tsx">import React, { useEffect, useState } from &quot;react&quot;;
import { Logo, NavWrapper } from &quot;./Nav.styled&quot;;

const Nav = () =&gt; {
  const [show, setShow] = useState(false);

  useEffect(() =&gt; {
    window.addEventListener(&quot;scroll&quot;, () =&gt; {
      if (window.scrollY &gt; 50) {
        setShow(true);
      } else {
        setShow(false);
      }
    });
    return () =&gt; {
      /* Nav 컴포넌트를 사용하지 않을 시, 불필요한 이벤트를 제거해줌 */
      window.removeEventListener(&quot;scroll&quot;, () =&gt; {});
    };
  }, []);

  return (
    &lt;NavWrapper show={show}&gt;
      &lt;Logo&gt;
        &lt;img
          alt=&quot;Disney Plus Logo&quot;
          src=&quot;/images/logo.svg&quot;
          onClick={() =&gt; (window.location.href = &quot;/&quot;)}
        /&gt;
      &lt;/Logo&gt;
    &lt;/NavWrapper&gt;
  );
};

export default Nav;
</code></pre>
<h3 id="추가-학습">추가 학습</h3>
<blockquote>
<p>CSS 클래스 네이밍 container vs wrapper 차이는 무엇인가?</p>
</blockquote>
<ul>
<li>container 는 하나 이상의 요소를 포괄하는 개체를 지칭하는 의미를 지닌다.</li>
<li>wrapper 는 하나의 개체만을 포함하는 것을 뜻한다.</li>
</ul>
<pre><code class="language-html">&lt;ul class=&quot;items-container&quot;&gt;
    &lt;li class=&quot;item-wrapper&quot;&gt;
        &lt;div class=&quot;item&quot;&gt;...&lt;/div&gt;
    &lt;/li&gt;
    &lt;li class=&quot;item-wrapper&quot;&gt;
        &lt;div class=&quot;item&quot;&gt;...&lt;/div&gt;
    &lt;/li&gt;
    &lt;li class=&quot;item-wrapper&quot;&gt;
        &lt;div class=&quot;item&quot;&gt;...&lt;/div&gt;
    &lt;/li&gt;
    &lt;li class=&quot;item-wrapper&quot;&gt;
        &lt;div class=&quot;item&quot;&gt;...&lt;/div&gt;
    &lt;/li&gt;
    &lt;li class=&quot;item-wrapper&quot;&gt;
        &lt;div class=&quot;item&quot;&gt;...&lt;/div&gt;
    &lt;/li&gt;
&lt;/ul&gt;</code></pre>
<p><a href="https://uxdev.org/entry/CSS-%ED%81%B4%EB%9E%98%EC%8A%A4-%EB%84%A4%EC%9D%B4%EB%B0%8D-%EC%8B%9C-container-vs-wrapper-%EC%B0%A8%EC%9D%B4-%EA%B5%AC%EB%B6%84%ED%95%98%EA%B8%B0">CSS 클래스 네이밍 시 container vs wrapper 차이 구분하기</a></p>
<h3 id="css-wrapper에-대한-간단한-설명">[CSS] wrapper에 대한 간단한 설명</h3>
<p><a href="https://itchallenger.tistory.com/908">[CSS] 레이아웃 Wrapper 만들기</a> 를 인용함.</p>
<p><img src="https://velog.velcdn.com/images/sg_yksv77/post/8e7d6bef-498f-41a8-8a73-3c6763fb31aa/image.png" alt=""></p>
<p>위 이미지는 다음 과 같은 코드로 구성되어 있다</p>
<pre><code class="language-html">    &lt;div class=&quot;wrapper&quot;&gt;
      &lt;aside&gt;...&lt;aside/&gt;
      &lt;main&gt;...&lt;main/&gt;
    &lt;div/&gt;</code></pre>
<p> 그런데 이 때, aside와 main 요소를 감싸는 wrapper 요소가 없다면, 
 하위 요소인 aside와 main 요소가 화면 가장자리에 달라붙게 되어(block 요소의 특징이므로), 의도와 다른 결과를 얻게 된다.</p>
<p> 아래 이미지는 하위 요소를 감싸는 상위 요소(여기서는 wrapper)가 없을 경우 요소의 너비가 늘어난 상황을 보여준다.<img src="https://velog.velcdn.com/images/sg_yksv77/post/65becdf2-f600-4534-93f4-8f6be81f9128/image.png" alt=""></p>
<h3 id="정리">정리</h3>
<blockquote>
<p>wrapper는 3가지 이점을  가질 수 있다.</p>
</blockquote>
<ol>
<li>콘텐츠의 가독성을 높여준다.
 a. 텍스트 및 이미지와 같은 콘텐츠가 늘어나서 화면 전체 너비를 채우는 상황을 방지 하여, 원래 의도대로 디자인 할 수 있다.</li>
<li>디자인 요소를 그룹화 하여, 간격을 추가하는데 유용하다.</li>
<li>디자인 요소를 열로 나누는 작업은 래퍼 없이 수행하기 어렵다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[개인프로젝트] 2. Axios 인스턴스 생성 및 요청 보내기]]></title>
            <link>https://velog.io/@sg_yksv77/%EA%B0%9C%EC%9D%B8%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-2.-Axios-%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4-%EC%83%9D%EC%84%B1-%EB%B0%8F-%EC%9A%94%EC%B2%AD-%EB%B3%B4%EB%82%B4%EA%B8%B0</link>
            <guid>https://velog.io/@sg_yksv77/%EA%B0%9C%EC%9D%B8%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-2.-Axios-%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4-%EC%83%9D%EC%84%B1-%EB%B0%8F-%EC%9A%94%EC%B2%AD-%EB%B3%B4%EB%82%B4%EA%B8%B0</guid>
            <pubDate>Mon, 06 Mar 2023 08:23:57 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Axios : 브라우저, node.js를 위한  Promise API를 활용한 HTTP 비동기 통신 라이브러리
쉽게, BE-FE 간 통신을 위한 라이브러리 이다.</p>
</blockquote>
<blockquote>
<p>fetch의 단점은 응답 데이터를 json으로 변경한 뒤 작업을 해야한다. axios는 응답 데이터를 바로 json으로 받아오기 때문에 편리함을 제공한다.</p>
</blockquote>
<h3 id="axios-사용-방법">Axios 사용 방법</h3>
<ul>
<li>axios 모듈 설치 npm install axios --save</li>
<li>사용예시 : axios.get(&quot;thhps://api.themoviedb.org/3/trending/all/week&quot;)</li>
</ul>
<h3 id="axios-인스턴스화-하기">Axios 인스턴스화 하기</h3>
<h3 id="1-인스턴스화-하는-이유는">1. 인스턴스화 하는 이유는?</h3>
<ul>
<li>중복된 부분을 계속 입력하지 않아도 되기 때문이다.</li>
<li>중복 예시
Get Movie By Latest : <a href="https://api.themoviedb.org/3/movie/latest?api_key=">https://api.themoviedb.org/3/movie/latest?api_key=</a>&lt;<api_key>&gt;&amp;language=en-US
Get Movie Detaill : <a href="https://api.themoviedb.org/3/movie/%7Bmovie_id%7D?api_key=">https://api.themoviedb.org/3/movie/{movie_id}?api_key=</a>&lt;<api_key>&gt;&amp;language=en-US<blockquote>
<p>중복 url : <a href="https://api.themoviedb.org/3/">https://api.themoviedb.org/3/</a>
해당 중복 url을 &quot;baseURL&quot;로 따로 관리하는 것이 좋다.</p>
</blockquote>
</li>
</ul>
<h3 id="2-axios-인스턴스-만드는-순서">2. Axios 인스턴스 만드는 순서</h3>
<ol>
<li>인스턴스 생성할 폴더 파일 생성하기
<img src="https://velog.velcdn.com/images/sg_yksv77/post/d1692ddf-6987-46fd-b43e-ef816dab98a1/image.png" alt=""></li>
<li>예시 : <a href="https://api.themoviedb.org/3/movie/latest?api_key=">https://api.themoviedb.org/3/movie/latest?api_key=</a>&lt;<api_key>&gt;&amp;language=en-US</li>
</ol>
<ul>
<li>axios.js<pre><code class="language-tsx">import axios from &quot;axios&quot;
</code></pre>
</li>
</ul>
<p>const instance = axios.create({
    baseURL: &quot;<a href="https://api.themoviedb.org/3/&quot;">https://api.themoviedb.org/3/&quot;</a>, // 중복 URL
      params: {
        api_key: &quot;a38fa376de46b95eb6b7c9e______&quot;,
          language: &quot;ko-KR&quot;
    }
})</p>
<p>export default instance;</p>
<pre><code>*    requests.js
: baseURL 를 제외한 각각의 요청 url path 를 인스턴스 생성
https://developers.themoviedb.org/3/getting-started/introduction 를 참조하여 필요한 path를 인스턴스로 관리하면 된다.
```tsx
const requests = {
  fetchNowPlaying: &quot;movie/now_playing&quot;,
  fetchTrending: &quot;trending/all/week&quot;,
  fetchTopRated: &quot;movie/top_rated&quot;,
  fetchActionMovies: &quot;discover/movie?with_genres=28&quot;,
  fetchComedyMovies: &quot;discover/movie?with_genres=35&quot;,
  fetchHorrorMovies: &quot;discover/movie?with_genres=27&quot;,
  fetchRomanceMovies: &quot;discover/movie?with_genres=10749&quot;,
  fetchDocumentaries: &quot;discover/movie?with_genres=99&quot;,
};

export default requests;
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[개인 프로젝트] 1. the moviedb API 생성하기]]></title>
            <link>https://velog.io/@sg_yksv77/%EA%B0%9C%EC%9D%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-1.-the-moviedb-API-%EC%83%9D%EC%84%B1%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@sg_yksv77/%EA%B0%9C%EC%9D%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-1.-the-moviedb-API-%EC%83%9D%EC%84%B1%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 06 Mar 2023 07:49:15 GMT</pubDate>
            <description><![CDATA[<p>API key를 기반으로 the movie DB API 에 통신을 시도한다. </p>
<h3 id="1-api-key-발급받기">1. API Key 발급받기</h3>
<ol>
<li><a href="https://www.themoviedb.org">https://www.themoviedb.org</a> 로 이동</li>
<li>로그인 후 API_KEY 발급<blockquote>
<p>프로필과 설정 -&gt; 설정 -&gt; API
<img src="https://velog.velcdn.com/images/sg_yksv77/post/9751c2ef-388e-4257-921a-5b5cb5970db8/image.png" alt="">
API 요청 예 를 참고하여 서버 요청을 보내면 된다.
<img src="https://velog.velcdn.com/images/sg_yksv77/post/e43d6c31-bcb3-41c0-b719-fafd8e10031a/image.png" alt=""></p>
</blockquote>
<h3 id="2-text-editor에서-the-moviedb-api-설정">2. Text Editor에서 the MovieDB API 설정</h3>
<blockquote>
<p><a href="https://developers.themoviedb.org/3/getting-started/introduction">https://developers.themoviedb.org/3/getting-started/introduction</a> -&gt; 사용할 요청어 선택 -&gt; 적용
<img src="https://velog.velcdn.com/images/sg_yksv77/post/f5b381e6-55f7-4bd8-90c1-4e6853ef00f3/image.png" alt=""></p>
</blockquote>
</li>
</ol>
<h3 id="주로-사용할-api_url">주로 사용할 API_URL</h3>
<ol>
<li><p>Get Movie By Latest 
:<a href="https://api.themoviedb.org/3/movie/latest?api_key=">https://api.themoviedb.org/3/movie/latest?api_key=</a>&lt;<api_key>&gt;&amp;language=en-US<img src="https://velog.velcdn.com/images/sg_yksv77/post/18c5cdda-9c4b-4c73-8747-daaebf7ff0f8/image.png" alt=""></p>
</li>
<li><p>Get Movie Detaill
<a href="https://api.themoviedb.org/3/movie/%7Bmovie_id%7D?api_key=">https://api.themoviedb.org/3/movie/{movie_id}?api_key=</a>&lt;<api_key>&gt;&amp;language=en-US<img src="https://velog.velcdn.com/images/sg_yksv77/post/47be034c-4ba3-4bc2-8eba-2021aff9a433/image.png" alt=""></p>
</li>
<li><p>Get Movie Reviews
<a href="https://api.themoviedb.org/3/movie/%7Bmovie_id%7D/reviews?api_key=">https://api.themoviedb.org/3/movie/{movie_id}/reviews?api_key=</a>&lt;<api_key>&gt;&amp;language=en-US&amp;page=1
<img src="https://velog.velcdn.com/images/sg_yksv77/post/a8a57cbf-7d38-4329-a7d6-b4d64cd1f295/image.png" alt=""></p>
</li>
<li><p>Get Trending
<a href="https://api.themoviedb.org/3/trending/all/day?api_key=">https://api.themoviedb.org/3/trending/all/day?api_key=</a>&lt;<api_key>&gt;
<img src="https://velog.velcdn.com/images/sg_yksv77/post/5a5c3dd5-2e9c-4cb3-b30b-a0473c664b37/image.png" alt=""></p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] setState(비동기-batch update) vs prevState(Queue)]]></title>
            <link>https://velog.io/@sg_yksv77/React-setState%EB%B9%84%EB%8F%99%EA%B8%B0-batch-update-vs-prevStateQueue</link>
            <guid>https://velog.io/@sg_yksv77/React-setState%EB%B9%84%EB%8F%99%EA%B8%B0-batch-update-vs-prevStateQueue</guid>
            <pubDate>Tue, 21 Feb 2023 06:03:39 GMT</pubDate>
            <description><![CDATA[<h2 id="문제-발생">문제 발생</h2>
<ol>
<li>setState를 사용하여, number state에 1~3를 이어서 더하는 동작을 구현했다.</li>
<li>그러나, 최종 number stat는 6이 아닌, 3이 적용되었다.</li>
</ol>
<pre><code class="language-tsx">const [ number, setNumber ] = useState(0);

const handleClick = (i) {
    setNumber(number + 1);
      setNumber(number + 2);
     setNumber(number + 3);

      console.log(&quot;numberState: &quot;, number)
}</code></pre>
<h2 id="문제-원인">문제 원인</h2>
<blockquote>
<p>setState는 비동기적 처리과정을 따르기 때문이다.
<em>상태 업데이트는 비동기식일 수 있습니다. React는 setState()성능을 위해 여러 호출을 단일 업데이트로 일괄 처리할 수 있습니다 .</em>
<a href="https://ko.reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous">https://ko.reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous</a></p>
</blockquote>
<p><strong>state의 값이 변경되는 시점</strong>은,
setState가 호출되는 시점이 아닌, 해당 코드가 포함된 <strong>함수가 모두 실행된 이후</strong> 변경된다.</p>
<h3 id="왜-state는-즉각-변경되지-않는가">왜, state는 즉각 변경되지 않는가?</h3>
<p>React에서 리렌더링되는 시점은,
state 변경이 대표적인 사례이다.
그렇다면, state가 변경될 때마다 리렌더링 된다면, 매우 비효율적인 성능을 가지게 된다. 
이를 해결하기 위해, 나온 것이 *<em>React batch updating *</em>이다.</p>
<h3 id="react-batch-update">React batch update</h3>
<blockquote>
<p>Batching이란, react가 더 나은 성능 개선을 위해, 여러개의 state 업데이트를 한번의 리렌더링으로 묶어서 진행하는 것을 말한다. 
<a href="https://github.com/reactwg/react-18/discussions/21">https://github.com/reactwg/react-18/discussions/21</a></p>
</blockquote>
<h2 id="문제-해결-prevstate">문제 해결: prevState</h2>
<blockquote>
<p>원래 의도대로, setState를 모두 수행하고 싶다면, Queue를 이용하면 된다!</p>
</blockquote>
<p>setState에 함수를 인자로 전달하는 방법을 사용할 수 있다.
이때, 인자로 전달된 함수는 이전 state 값을 전달 받게 된다.
이후 해당 함수들은 Queue에 저장 되어 순서대로 실행된다.
그 결과, Queue에 저장된 함수들이 동기적으로 작동되면서, 모든 setState 구문이 동작하게 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL.[Ts] TS 상속(1) 클래스 상속]]></title>
            <link>https://velog.io/@sg_yksv77/TIL.Ts-TS-%EC%83%81%EC%86%8D1-%ED%81%B4%EB%9E%98%EC%8A%A4-%EC%83%81%EC%86%8D</link>
            <guid>https://velog.io/@sg_yksv77/TIL.Ts-TS-%EC%83%81%EC%86%8D1-%ED%81%B4%EB%9E%98%EC%8A%A4-%EC%83%81%EC%86%8D</guid>
            <pubDate>Tue, 27 Sep 2022 08:11:55 GMT</pubDate>
            <description><![CDATA[<h3 id="상속-개념">상속 개념</h3>
<p>이전 피드에서 News라는 인터페이스를 기반으로 NewsFeed를 만들고, NewsDetail를 만들고, NewsComment를 만들었습니다.
이런 식으로 공통 요소를 만들고, 공통 요소를 확잘 할 수 있는 개별 요소를 만드는 것이 상속의 개념입니다.</p>
<blockquote>
<p>코드를 작성할 때, 목적에 부합되도록 분류하고, 그 분류 내에서 훨씬 더 의미를 잘 부여할 수 있게, 중복된 코드를 제거한다면, 코드 가독성에 기여할 수 있습니다.
이를 위한 첫번째 방법이 클래스입니다. </p>
</blockquote>
<h3 id="코드-분석">코드 분석</h3>
<pre><code class="language-ts">// 네트워크를 통해서 API를 호출하는 함수 getData
function getData&lt;AjaxResponse&gt;(url: string): AjaxResponse {
  ajax.open(&#39;GET&#39;, url, false);
  ajax.send();

  return JSON.parse(ajax.response);
}

// 뷰와 관련된 업데이트를 처리하는 함수 makeFeeds, 함수 updateView
function makeFeeds(feeds: NewsFeed[]): NewsFeed[] {
  for (let i = 0; i &lt; feeds.length; i++) {
    feeds[i].read = false;
  }

  return feeds;
}

function updateView(html: string): void {
  if (container) {
    container.innerHTML = html;
  } else {
    console.error(&#39;최상위 컨테이너가 없어 UI를 진행하지 못합니다.&#39;);
  }
}
// 메인 뷰를 처리하는 함수 NewsFeed, 함수 NewsDetail
function newsFeed(): void {
  let newsFeed: NewsFeed[] = store.feeds;
  const newsList = [];
  let template = `
    &lt;div class=&quot;bg-gray-600 min-h-screen&quot;&gt;
        ...
    &lt;/div&gt;
    `
  if (newsFeed.length === 0) {
      newsFeed = store.feeds = makeFeeds(getData&lt;NewsFeed[]&gt;(NEWS_URL));
    }
  ...
  updateView(template);
}

function newsDetail(): void {
  const id = location.hash.substring(7);
  const newsContent = getData&lt;NewsDetail&gt;(CONTENT_URL.replace(&#39;@id&#39;, id))
  let template = `...`
  ...
}
</code></pre>
<ol>
<li>네트워크를 통해서 API를 호출하는 함수 getData</li>
<li>뷰와 관련된 업데이트를 처리하는 함수 makeFeeds, 함수 updateView</li>
<li>메인 뷰를 처리하는 함수 NewsFeed, 함수 NewsDetail</li>
</ol>
<p>해당 피드에서는 공통요소로 작용하는 함수 getData에 집중하도록 하겠습니다.</p>
<h3 id="문제-제기">문제 제기</h3>
<p>API를 호출하는 함수 getData를 사용하는 입장인 함수 NewsFeed, 함수 NewsDetail에서 getData라는 명칭만으로는 정확히 어떤 타입의 데이터를 가져오는지 의미를 갖고 있지 않습니다.</p>
<pre><code class="language-ts">function newsFeed(): void {
    newsFeed = store.feeds = makeFeeds(getData&lt;NewsFeed[]&gt;(NEWS_URL));
}

function newsDetail(): void {
  const id = location.hash.substring(7);
  const newsContent = getData&lt;NewsDetail&gt;(CONTENT_URL.replace(&#39;@id&#39;, id))
  }</code></pre>
<p>즉, 공통함수를 사용하는 입장에서 좀 더 의미화 가 필요합니다.</p>
<h3 id="클래스-상속으로-해결">클래스 상속으로 해결</h3>
<ol>
<li>함수 NewsFeed, 함수 NewsDetail 에서 공통요소는, 네트워크를 통해서 API를 호출하는 getDtat 입니다.</li>
<li>따라서, 클래스를 선언하고 getData의 기능을 할당합니다.</li>
<li>이때, API를 호출할 때 필요한 것들은, 함수 getData를 참고하여, url과 XMLHttpRequest의 인스턴스를 만드는 코드가 필요합니다. 이를 생성자에 추가합니다.</li>
<li>이때, class Api는 외부로부터 URL를 받고, 어떤 URL를 갖는 API더라도 호출을 할 수 있어야 합니다. 따라서, 생성자 함수의 인자로 url을 전달합니다.</li>
<li>인스턴스 객체에 접근하는 지시어인 this를 사용하여, 클래스의 내부 요소인 url과 ajax로 접근합니다.</li>
</ol>
<pre><code class="language-ts">class Api {
    url: string;
    ajax: XMLHttpRequest;

    constructor(url: string) {
      this.url = url;
      this.ajax = new XMLHttpRequest();
    }
}</code></pre>
<ol start="6">
<li><p>작성된 공통요소인 Api 클래스를 확장하기 위해, 함수 NewsFeed, 함수 NewsDetail 를 각각 클래스로 만듭니다.</p>
</li>
<li><p>Api 클래스를 확장해서 만든 NewsFeedApi 클래스가 필요한 속성인 함수getData를 선언해줍니다.</p>
</li>
<li><p>동일한 방법으로 NewsDetailApi 클래스를 선언합니다.</p>
<pre><code class="language-ts">class NewsFeedApi extends Api {
getData(): NewsFeed[] {
   ajax.open(&#39;GET&#39;, url, false);
 ajax.send();

 return JSON.parse(ajax.response);
}
}
</code></pre>
</li>
</ol>
<p>class NewsDetailApi extends Api {
  getData(): NewsDetail {
      ajax.open(&#39;GET&#39;, url, false);
    ajax.send();</p>
<pre><code>return JSON.parse(ajax.response);</code></pre><p>  }
}</p>
<pre><code>
9. 확장 된 NewsFeedApi와 NewsDetailApi가 getData의 기능을 공통요소로 가지고 있습니다. 따라서, 이 공통요소를 Api 클래스로 끌어올립니다. 이를 통해 확장 된 NewsFeedApi와 NewsDetailApi에 해당 코드들을 제거할 수 있습니다.
10. 공통요소는 코드의 묶음이므로 함수(getRequest)로 작성해줍니다.
11. 공통요소로 작성된 함수 getRequest의 ajax와 url은 모두 Api 클래스의 내부 요소로 작성되어 있기 때문에 this 로 바꿔줍니다.

```ts
class Api {
  ajax: XMLHttpRequest;
  url: string;

  constructor(url: string) {
    this.ajax = new XMLHttpRequest();
    this.url = url;
  }

  getRequest&lt;AjaxResponse&gt;(): AjaxResponse {
    this.ajax.open(&quot;GET&quot;, this.url, false);
    this.ajax.send();

    return JSON.parse(this.ajax.response);
  }
}</code></pre><ol start="12">
<li>이제 수정된 Api 클래스를 NewsFeedApi와 NewsDetailApi로 확장해줍니다. 이때, 확장된 클래스의 getData에는 상위 클래스인 Api의 getRequest를 호출한 값을 가져오면 됩니다.<pre><code class="language-ts">class NewsFeedApi extends Api {
getData(): NewsFeed[] {
return this.getRequest&lt;NewsFeed[]&gt;();
}
}
</code></pre>
</li>
</ol>
<p>class NewsDetailApi extends Api {
  getData(): NewsDetail {
    return this.getRequest<NewsDetail>();
  }
}</p>
<pre><code>13. 확장된 클래스를 적용합니다. 이때,  api라는 클래스 인스턴스를 만들어주었습니다. 
```ts
function newsFeed(): void {
   const api = new NewsFeedApi(NEWS_URL);
  ...
    newsFeed = store.feeds = makeFeeds(api.getData&lt;NewsFeed[]&gt;(NEWS_URL));
}

function newsDetail(): void {
  const id = location.hash.substring(7);
  const newsContent = getData&lt;NewsDetail&gt;(CONTENT_URL.replace(&#39;@id&#39;, id))
  }</code></pre><h4 id="클래스-메소드-노출-문제-발생">클래스 메소드 노출 문제 발생</h4>
<ul>
<li>Api 가 제공하는 getData 를 기존의 getData로 바꿀 때, getData와 getRequest 라는 두가지 메소드가 등장합니다.</li>
<li>getDtat 메소드는 기존의 getData 함수와 동일하게 바깥쪽에서 API 호출을 위해서 사용하는 용도로 만들어진 메소드입니다. 따라서, </li>
<li>getRequest 메소드는 Api 클래스를 확장한 하위 클래스에서 사용할 용도로 만들어진 메소드입니다.</li>
<li>따라서, getRequest 메소드는 Api 클래스 외부에서 사용되지 않아야 합니다. </li>
<li>그러나, 에디터에서는 이를 그대로 노출하고 있기 때문에 사용될 수 있습니다. 따라서, 에디터에서 이를 노출시키지 않게 해야합니다. </li>
<li>이러한 문제를 해결하기 위해 다음과 같은 방법을 사용하면 됩니다.<img src="https://velog.velcdn.com/images/sg_yksv77/post/3fda5298-06d2-48b4-9ff4-c9bd1af83dc3/image.png" alt=""></li>
</ul>
<h4 id="클래스-메소드-노출-문제-해결">클래스 메소드 노출 문제 해결</h4>
<ol>
<li>타입스크립트에서는 class의 속성과 메소드를 외부로 노출시키지 않는 지시어를 제공합니다.</li>
</ol>
<p>바로, Protected입니다.</p>
<ol start="2">
<li><p>이렇게 속성명 앞에 Protecte를 지정해주면, 해당 속성은 class 외부에서 인스턴스 객체로 등장하지 않게 됩니다.</p>
<pre><code class="language-ts">class Api {
ajax: XMLHttpRequest;
url: string;

constructor(url: string) {
 this.ajax = new XMLHttpRequest();
 this.url = url;
}

protected getRequest&lt;AjaxResponse&gt;(): AjaxResponse {
 this.ajax.open(&quot;GET&quot;, this.url, false);
 this.ajax.send();

 return JSON.parse(this.ajax.response);
}
}</code></pre>
<p><img src="https://velog.velcdn.com/images/sg_yksv77/post/c5440497-174e-47a0-b8ac-ad1994d1eee1/image.png" alt=""></p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Erro & TIL. [TS] 함수 리턴 값이 2가지 이상 일때, Generics 으로 타입 지정하기]]></title>
            <link>https://velog.io/@sg_yksv77/TIL.-TS-%ED%95%A8%EC%88%98-%EB%A6%AC%ED%84%B4-%EA%B0%92%EC%9D%B4-2%EA%B0%80%EC%A7%80-%EC%9D%B4%EC%83%81-%EC%9D%BC%EB%95%8C-Generics-%EC%9C%BC%EB%A1%9C-%ED%83%80%EC%9E%85-%EC%A7%80%EC%A0%95%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@sg_yksv77/TIL.-TS-%ED%95%A8%EC%88%98-%EB%A6%AC%ED%84%B4-%EA%B0%92%EC%9D%B4-2%EA%B0%80%EC%A7%80-%EC%9D%B4%EC%83%81-%EC%9D%BC%EB%95%8C-Generics-%EC%9C%BC%EB%A1%9C-%ED%83%80%EC%9E%85-%EC%A7%80%EC%A0%95%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 22 Sep 2022 10:24:40 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>유니온 타입(Union Type)이란 자바스크립트의 OR 연산자(||)와 같이 A이거나 B이다 라는 의미의 타입입니다. </p>
</blockquote>
<h3 id="상황">상황</h3>
<pre><code class="language-ts">type News = {
  id: number;
  time_ago: string;
  title: string;
  url: string;
  user: string;
  content: string;
};

// Intersection Type

type NewsFeed = News &amp; {
  comments_count: number;
  points: number;
  // optional properties
  read?: boolean;
};

type NewsDetail = News &amp; {
  comments: NewsComment[];
};

// 함수 getData의 반환값을 Union Type으로 지정
function getData(url: string): NewsFeed[] | NeswDetail {
  ajax.open(&quot;GET&quot;, url, false);
  ajax.send();

  return JSON.parse(ajax.response);
}

// 함수 getData가 사용되는 두가지 상황 발생
// 1. NewsFeed[]를 반환하는 함수 newsFeed
// 2. NeswDetail을 반환하는 함수 newsDetail
function newsFeed(): void {
  ...
  if (newsFeed.length === 0) {
    // 불러온 뉴스 데이터에 read 속성을 추가함
    newsFeed = store.feeds = makeFeeds(getData(NEWS_URL));
    console.log(&quot;makeFeeds read =&gt;&quot;, newsFeed);
  }
}

function newsDetail(): void {
  const id = location.hash.substring(7);
  const newsContent = getData&lt;NewsDetail&gt;(CONTENT_URL.replace(&quot;@id&quot;, id));
  ...
}</code></pre>
<ul>
<li>함수 getData는 상황에 따라, 타입 앨리어스로 지정한 NewsFeed[] 타입 혹은 NeswDetail 타입을 반환값으로 갖습니다.</li>
<li>이를 표현하기 위해 함수 getData의 반환값으로 유니온 타입을 선언했습니다.<pre><code class="language-ts">function getData(url: string): NewsFeed[] | NeswDetail {}</code></pre>
</li>
</ul>
<h3 id="의도">의도</h3>
<ul>
<li>NewsFeed[] 타입을 반환값으로 갖는 함수 newsFeed와 NeswDetail 타입을 반환값으로 갖는 함수 newsDetail 등 두가지 상황에서 각각 다른 함수 getData의 반환값이 적용될 것을 예상했습니다.</li>
<li>이유는 유니온 타입(A | B)이 A도 될 수 있고 B도 될수 있는 타입이므로 함수 getData의 반환값 타압이 NewsFeed[]도 되고 NeswDetail도 될 것으로 예상했습니다.</li>
<li>의도되로 된다면, 함수 getData의 반환값을 갖는 변수 newsContent는 title 속성이 존재해야할 것이며, 함수 newsFeed와 함수 newsDetail 모두 상황에 따라, 의도된 값을 가져야 합니다.</li>
</ul>
<h3 id="오류-발생">오류 발생</h3>
<pre><code class="language-ts">function getData(url: string): NewsFeed[] | NewsDetail
&#39;NewsFeed[] | NewsDetail&#39; 형식의 인수는 
&#39;NewsFeed[]&#39; 형식의 매개 변수에 할당될 수 없습니다.
  &#39;NewsDetail&#39; 형식에 &#39;NewsFeed[]&#39; 형식의 
  length, pop, push, concat 외 29개 속성이 없습니다.ts(2345)</code></pre>
<p><img src="https://velog.velcdn.com/images/sg_yksv77/post/67c0beed-ef40-4aa6-a987-5cad1f513993/image.png" alt=""><img src="https://velog.velcdn.com/images/sg_yksv77/post/3a02c318-864c-43b5-9fbe-ff155e71ef70/image.png" alt=""></p>
<h3 id="원인">원인</h3>
<ul>
<li>타입스크립트 관점에서는 함수 getData를 호출하는 시점에 NewsFeed[] 타입이 올지 NewsDetail 타입이 올지 알수 없다고 합니다.<blockquote>
<p>이에, 어느 타입이 들어오든 우류가 나지 않는 방향으로 타입을 추론하게 되기 때문에, NewsFeed[] 과 NewsDetail 두 타입에 공통으로 들어있는 속성인 News 타입 속성만 접근할 수 있습니다.
<a href="https://joshua1988.github.io/ts/guide/operator.html#union-type%EC%9D%84-%EC%93%B8-%EB%95%8C-%EC%A3%BC%EC%9D%98%ED%95%A0-%EC%A0%90">참고 : 타입스크립트 핸드북</a></p>
</blockquote>
</li>
</ul>
<h3 id="해결-방법">해결 방법</h3>
<blockquote>
<p>함수 getData는 리턴 값을 2가지 종류를 갖고 있습니다.
그러나, getData를 사용하는 입장에서는, 어떤 값이 올지 예상할 수 없는 상황입니다.</p>
</blockquote>
<ol>
<li>타입 가드(Type Guard)를 이용하여 타입의 범위를 좁혀줍니다.
 그러나, 이는 좋은 방법이 아닙니다.
 왜냐하면, 함수 getData가 지금은 API 2개만 처리하지만, 추후, API 30개를 처리한다면, 함수 getData를 호출할 때마다, 조건문으로 끝 없이 타입 가드 코드를 작성해야 하기 때문입니다. 
 즉, &quot;리턴 값으로 유니온 타입으로 기술하는 것만으로는 쉽지 않다.&quot;라는 것을 알 수 있습니다.</li>
<li>제네릭을 적용합니다.</li>
</ol>
<h4 id="제네릭-사전적-의미">제네릭 사전적 의미</h4>
<blockquote>
<p>제네릭은 C#, Java 등의 언어에서 재사용성이 높은 컴포넌트를 만들 때 자주 활용되는 특징입니다. 특히, 한가지 타입보다 여러 가지 타입에서 동작하는 컴포넌트를 생성하는데 사용됩니다.
<a href="https://joshua1988.github.io/ts/guide/generics.html">출처 : 타입스크립트 핸드북</a></p>
</blockquote>
<blockquote>
<p>제네릭은 입력이 n개의 유형일 때, 출력도 n개의 유형인 것을 정의하는 것입니다.
여기서 핵심은 입력이 ABCD 유형 중, A가 입력되면 출력도 A로 나가야합니다.</p>
</blockquote>
<h3 id="즉-입출력이-동일한-유형이어야-합니다">즉, 입출력이 동일한 유형이어야 합니다.</h3>
<p>이러한 상황을 제네릭이라는 문법으로 표현할 수 있습니다.</p>
<h3 id="제네릭-적용하기">제네릭 적용하기</h3>
<pre><code class="language-ts">function getData&lt;AjaxRespons&gt;(url: string): AjaxRespons {
  ajax.open(&quot;GET&quot;, url, false);
  ajax.send();

  return JSON.parse(ajax.response);
}</code></pre>
<ol>
<li><p>함수의 이름 바로 뒤에 &quot;T&quot; 라는 코드를 추가하고, 함수의 인자와 반환 값 모두에 &quot;T&quot; 라는 타입을 추가합니다. </p>
</li>
<li><p>이때, &quot;T&quot; 는 통상적인 이름이며, 명시적으로 지정하는 것이 가독성을 높혀 줄 수 있습니다. 여기서는 &quot;T&quot;  대신 &quot;AjaxRespons&quot; 로 작명 했습니다.</p>
</li>
<li><p>또한, 위 코드에서는 반환 값 타입만 지정해줬습니다. 이유는 제네릭의 특성 상, 입출력 타입이 모두 동일하기 때문에, 인자 타입을 생략했습니다.</p>
<pre><code class="language-ts">if (newsFeed.length === 0) {
 // 불러온 뉴스 데이터에 read 속성을 추가함
 newsFeed = store.feeds = makeFeeds(getData&lt;NewsFeed[]&gt;(NEWS_URL));
 console.log(&quot;makeFeeds read =&gt;&quot;, newsFeed);
}

function newsDetail(): void {
   const id = location.hash.substring(7);
   const newsContent = getData&lt;NewsDetail&gt;(CONTENT_URL.replace(&quot;@id&quot;, id));
...
}
</code></pre>
<ol start="4">
<li>제네릭이 적용된 함수 getData를 호출 할 때, 기술된 타입이 그대로 &quot;AjaxRespons 타입&quot; 에 적용되고, 리턴 값을 &quot;AjaxRespons 타입&quot;으로 출력합니다.</li>
<li>즉, 호출하는 쪽에서 타입을 명시해 주면, 그 타입을 적용해서 그대로 getData의 반환 타입으로 사용하겠다는 것이 제네릭입니다.</li>
</ol>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Erro] TS. String.resplace() 매개변수 타입 에러]]></title>
            <link>https://velog.io/@sg_yksv77/Erro-TS.-String.resplace-%EB%A7%A4%EA%B0%9C%EB%B3%80%EC%88%98-%ED%83%80%EC%9E%85-%EC%97%90%EB%9F%AC</link>
            <guid>https://velog.io/@sg_yksv77/Erro-TS.-String.resplace-%EB%A7%A4%EA%B0%9C%EB%B3%80%EC%88%98-%ED%83%80%EC%9E%85-%EC%97%90%EB%9F%AC</guid>
            <pubDate>Thu, 22 Sep 2022 05:58:30 GMT</pubDate>
            <description><![CDATA[<h2 id="typescript---string-replace">TypeScript - String replace()</h2>
<p>해당 메서드는 정규식과 문자열 간의 일치 항목을 찾고 일치하는 하위 문자열을 새 하위 만자열로 바꾸는 메서드이다.</p>
<blockquote>
<p>string.replace(regexp/substr, newSubStr/function[, flags]);</p>
</blockquote>
<ul>
<li><p>regexp - RegExp 객체. 일치는 매개변수 #2의 반환 값으로 대체됩니다.</p>
</li>
<li><p>substr - newSubStr로 대체될 문자열입니다.</p>
</li>
<li><p>newSubStr - 매개변수 #1에서 받은 하위 문자열을 대체하는 문자열입니다.</p>
</li>
<li><p>function - 새로운 부분 문자열을 생성하기 위해 호출되는 함수.</p>
</li>
<li><p>flags - RegExp 플래그의 조합을 포함하는 문자열: g</p>
</li>
</ul>
<p>예시 </p>
<pre><code class="language-ts">var re = /apples/gi; 
var str = &quot;Apples are round, and apples are juicy.&quot;;
var newstr = str.replace(re, &quot;oranges&quot;); 
console.log(newstr)
 =&gt; oranges are round, and oranges are juicy.</code></pre>
<p><a href="https://www.tutorialspoint.com/typescript/typescript_string_replace.htm">출처 : Tutorialspoint</a></p>
<h3 id="상황">상황</h3>
<pre><code class="language-ts">function newsFeed(): void {
  let newsFeed: NewsFeed[] = store.feeds;
  const newsList = [];
  let template = `
  &lt;div class=&quot;bg-gray-600 min-h-screen&quot;&gt;
    ...
          &lt;div class=&quot;items-center justify-end&quot;&gt;
            &lt;a href=&quot;#/page/{{__prev_page__}}&quot; class=&quot;text-gray-500&quot;&gt;
              Previous
            &lt;/a&gt;
            &lt;a href=&quot;#/page/{{__next_page__}}&quot; class=&quot;text-gray-500 ml-4&quot;&gt;
              Next
            &lt;/a&gt;
    ...
    &lt;div class=&quot;p-4 text-2xl text-gray-700&quot;&gt;
      {{__news_feed__}}        
    &lt;/div&gt;
  &lt;/div&gt;
  `;

  template = template.replace(&quot;{{__news_feed__}}&quot;, newsList.join(&quot;&quot;));
  template = template.replace(
    &quot;{{__prev_page__}}&quot;,
    store.currentPage &gt; 1 ? store.currentPage - 1 : 1);
  template = template.replace(&quot;{{__next_page__}}&quot;,
                             store.currentPage + 1);

  updateView(template);
}

</code></pre>
<ol>
<li>문자열로 구성된 html의 태그 중 일부를 마킹</li>
<li>이를 replace 메서드를 사용하여 하위 문자열로 대체하려 함</li>
<li>두번째 매개변수로, 숫자를 전달함</li>
<li>문법 오류 발생</li>
</ol>
<h3 id="의도">의도</h3>
<p>변수 template는 문자열로 구성된 html입니다.
해당 변수는 뉴스 페이지와 개별 뉴스를 모두 담기을 시 복잡도를 예상하여, 뉴스 페이지와 개별 뉴스를 각각 {{<strong>prev_page</strong>}}, {{<strong>next_page</strong>}}로 마킹하여, replace 메서드를 사용하여 하위 문자열로 대체하려는 의도로 작성되었습니다.
따라서, template.replace(str, num)으로 입력하여 이를 해결하려 했습니다.</p>
<h3 id="원인">원인</h3>
<p>그러나, replace 메서드는 문자열을 매개변수로 갖음을 인지하지 못했기에, 문법 오류가 발생한 것입니다.</p>
<h3 id="해결">해결</h3>
<p>먼저, 에디터가 제시한 힌트를 확인했습니다.</p>
<blockquote>
<p>(method) String.replace(searchValue: string | RegExp, replaceValue: string): string (+3 overloads)</p>
</blockquote>
<p>에디터는 replace 메서드가 &quot;두가지 문자열을 받으며 첫번째 매개변수를 두번째 매개변수로 전환해줌&quot;을 제시하고 있습니다.</p>
<p>방법은 단순합니다. 두번째 매개 변수를 문자열로 바꿔주면 됩니다.
삼항연산자를 사용하여 상황에 따라 달라지는 값을 전달했기에, 어떻게 문자열로 전환할지 고민했지만, 최종적으로 전달되는 값은 문자열이나, 숫자나 모두 문자열로 반환되기 때문입니다.</p>
<p>따라서, 삼항연산자의 결과를 String으로 묶는다면 원하는 문법 오류를 해결할 수 있습니다.</p>
<pre><code class="language-ts">  template = template.replace(
    &quot;{{__prev_page__}}&quot;,
    String(store.currentPage &gt; 1 ? store.currentPage - 1 : 1)
  );
  template = template.replace(&quot;{{__next_page__}}&quot;,
                              String(store.currentPage + 1));
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL. [TS] null 타입 지정 시 문제 해결.-타입 가드]]></title>
            <link>https://velog.io/@sg_yksv77/TIL.-TS-null-%ED%83%80%EC%9E%85-%EC%A7%80%EC%A0%95-%EC%8B%9C-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0.-%ED%83%80%EC%9E%85-%EA%B0%80%EB%93%9C</link>
            <guid>https://velog.io/@sg_yksv77/TIL.-TS-null-%ED%83%80%EC%9E%85-%EC%A7%80%EC%A0%95-%EC%8B%9C-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0.-%ED%83%80%EC%9E%85-%EA%B0%80%EB%93%9C</guid>
            <pubDate>Tue, 20 Sep 2022 12:15:53 GMT</pubDate>
            <description><![CDATA[<h2 id="학습-핵심">학습 핵심</h2>
<blockquote>
<p>어떤 유형의 값이 2가지가 들어 올 때, 그 중 1가지가 null인 즉, 데이터가 없는 케이스에서 무작정 데이터가 당연히 있다고 생각하고 속성을 접근했을 경우, *<em>잊지 않고 null을 체크하라! *</em></p>
</blockquote>
<h3 id="작성-코드">작성 코드</h3>
<blockquote>
</blockquote>
<pre><code class="language-ts">const container: HTMLElement | null =
      document.getElementById(&quot;root&quot;);
let template = `
  &lt;div class=&quot;bg-gray-600 min-h-screen&quot;&gt;
    &lt;div class=&quot;p-4 text-2xl text-gray-700&quot;&gt;
      {{__news_feed__}}        
    &lt;/div&gt;
  &lt;/div&gt;
  `;</code></pre>
<p><img src="https://velog.velcdn.com/images/sg_yksv77/post/652ab681-6fba-4f49-8595-7e5759cabe04/image.png" alt=""></p>
<h3 id="코드-설명">코드 설명</h3>
<ul>
<li>변수 container에 두개의 타입(HTMLElement | null)을 지정함</li>
<li>innerHTML에 데이터를 넣는 코드에 문제 발생</li>
</ul>
<h3 id="원인-파악">원인 파악</h3>
<ul>
<li>문제가 발생한 변수 container에 지정한 타입으로 &quot;HTMLElement&quot; 혹은 &quot;null&quot;이 반환되어야 함</li>
<li>따라서 &quot;null&quot; 타입이 지정되어 있기 때문에, 변수 container에 innerHTML이라는 속성이 존재할 수 없음</li>
</ul>
<h3 id="결론">결론</h3>
<ul>
<li>변수 container의 타입으로 null이 지정되었기 때문에, 오류가 발생한 것</li>
</ul>
<h3 id="해결-방법">해결 방법</h3>
<blockquote>
<p>변수 container의 반환값으로 &quot;null&quot;이 아닌 경우에만, &quot;innerHTML&quot;에 접근하라!</p>
</blockquote>
<p>수도코드 : 만약, 변수 container의 값이 null 이 아닌 경우, innerHTML에 접근한다.</p>
<pre><code class="language-ts">if(container != null) {
    container.innerHTML = template;
 } else {
   console.error(
     &quot;최상위 컨테이너(root)가 없어 UI를 진행하지 못합니다.&quot;)
   }</code></pre>
<p>이때, 코드를 축약할 수 있다.
변수 container를 if문 안에 넣었을 때, if 문에서 null 자체를 참, 거짓을 판별하여, null이 있다면, 거짓으로 판정해준다. 
이를 적용하여, 변수 container에 데이터가 들어있다면, 참으로 판정하게 된다.</p>
<pre><code class="language-ts">if(container) {
    container.innerHTML = template;
} else {
   console.error(
     &quot;최상위 컨테이너(root)가 없어 UI를 진행하지 못합니다.&quot;)
  } </code></pre>
<p>만약, 위 코드가 중복된다면, 함수로 업데이트하여 사용하는 것이 좋다.</p>
<pre><code class="language-ts">function updateView(html) {
  if(container) {
    container.innerHTML = html;
} else {
   console.error(
     &quot;최상위 컨테이너(root)가 없어 UI를 진행하지 못합니다.&quot;)
  } 
}

updateView(template)
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[개발 일지  중복확인 axios.get?]]></title>
            <link>https://velog.io/@sg_yksv77/%EA%B0%9C%EB%B0%9C-%EC%9D%BC%EC%A7%80-%EC%A4%91%EB%B3%B5%ED%99%95%EC%9D%B8-axios.get</link>
            <guid>https://velog.io/@sg_yksv77/%EA%B0%9C%EB%B0%9C-%EC%9D%BC%EC%A7%80-%EC%A4%91%EB%B3%B5%ED%99%95%EC%9D%B8-axios.get</guid>
            <pubDate>Fri, 13 May 2022 12:28:07 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-jsx">const [userInfo, setUserInfo] = useState({
    id: &quot;&quot;,
    password: &quot;&quot;,
    rePassword: &quot;&quot;,
    email: &quot;&quot;,
    name: &quot;&quot;,
    birth: &quot;&quot;,
    number: &quot;&quot;,
  });

  const [idErrMsg, setIdErrMsg] = useState(&quot;&quot;); // id 에러 메세지
  const [idCheckMsg, setIdCheckMsg] = useState(&quot;&quot;); // id 사용가능 메세지
</code></pre>
<pre><code class="language-jsx">// 아이디 유효성 검사 후 중복 검사 
const idValidation = (e) =&gt; {
    const regExp = /^[a-z0-9]{4,10}$/;
    if (!regExp.test(e.target.value)) {
      setIdErrMsg(&quot;아이디는 4~10자 영어 소문자, 숫자를 사용하세요.&quot;);
    } else {
      setIdErrMsg(&quot;&quot;);
      axios
        .get(`${process.env.REACT_APP_API_URL}/user/id?id=${id}`)
        .then((res) =&gt; {
          const resMessge = res.data.message;
          if (resMessge === &quot;사용 가능한 아이디입니다.&quot;) {
            setIdErrMsg(&quot;&quot;);
            setIdCheckMsg(&quot;사용 가능한 아이디입니다.&quot;);
          } else if (resMessge === &quot;이미 사용중인 아이디입니다.&quot;) {
            setIdErrMsg(&quot;이미 사용중인 아이디입니다.&quot;);
            setIdCheckMsg(&quot;&quot;);
          }
        });
    }
  };</code></pre>
<p>회원가입 페이지를 구현하던 와중에 문든 의문이 들었습니다.</p>
<p>입력한 아이디에 대한 중복확인을 할때, post가 보내야할지, get으로 보내야할 지 의문이 들더군요.</p>
<p>저는 당연하게 회원가입에 입력된 아이디 정보를 axios.post로 요청을 보낸 뒤, 서버가 post요청으로 들어온 아이디를 DB에서 조회하면 된다고 생각했습니다.</p>
<blockquote>
<p>GET 요청은 조회! POST 요청은 생성!</p>
</blockquote>
<p>post 요청으로 보내야한다고 생각했던 가장 큰 이유는 get요청은 데이터를 전달할 수 없다고 알았기 때문이었죠.</p>
<h3 id="get방식-localhost8888idbananapwdhong">GET방식 :localhost:8888/?id=banana&amp;pwd=hong</h3>
<p>출처: <a href="https://xzio.tistory.com/1753">https://xzio.tistory.com/1753</a> [코딩창고]</p>
<p>GET은 입력된 아이디를 parameter 값으로 url에 담아 서버로 요청을 보낼 수 있습니다. 따라서 GET방식은 숨겨야할 정보를 url로 노출시키는 보안상의 문제를 가집니다.</p>
<p>그런데도, 아이디 중복확인에 GET 방식을 사용한 이유는 무엇일까요?
바로, 중복확인은 조회를 우선으로 두기 때문입니다.
POST 방식은 서버로 리소스를 생성하거나 업데이트하기 위해 데이터를 보낼 때 사용되는 방식입니다. 
따라서 DB에 아이디가 중복되었음을 확인만 하면 되기 때문에,
GET 방식을 사용하여 중복여부만 요청하면 되기 때문이죠.
게다가, 아이디는 노출되어도 보안의 위험이 덜하기 때문에, get으로 하는 것이 유리하다는 결론을 내릴 수 있었습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[개발 일지 5.13 axios.defaults.withCredentials]]></title>
            <link>https://velog.io/@sg_yksv77/%EA%B0%9C%EB%B0%9C-%EC%9D%BC%EC%A7%80-5.13-axios.defaults.withCredentials</link>
            <guid>https://velog.io/@sg_yksv77/%EA%B0%9C%EB%B0%9C-%EC%9D%BC%EC%A7%80-5.13-axios.defaults.withCredentials</guid>
            <pubDate>Fri, 13 May 2022 11:49:52 GMT</pubDate>
            <description><![CDATA[<p>지난 3개월간 처음 마주한 스택들을 습득하고 나름 열심히 공부했다고 생각했습니다. 하지만, 첫번째 프로젝트를 시작한지 5일차를 지나며 이것 하나를 분명하게 알게되었죠.</p>
<blockquote>
<p>헛 공부했구나!</p>
</blockquote>
<p>지난 3개월간 빈칸에 필요한 코드를 작성하며 흐름을 파악하기 바빴으나,
프로젝트는 아무것도 없는 상태에서 하나씩 쌓아가야 한다는 현실에 매 순간이 도전의 연속이었습니다.</p>
<h3 id="1-axiosdefaultswithcredentials--true">1. axios.defaults.withCredentials = true;</h3>
<p>상황
    1. 회원가입에 필요한 모든 유저 정보를 입력 완료
    2. 유저 정보를 axios.post(&quot;/signup&quot;, userInfo)로 요청을 보냄 
    3. 서버에서 req.user 값이 undefined로 나온 상황
    4. 정상적으로 통신했다면, req.user 객체에 정보가 담겨있어야 함</p>
<p> 왜 이런 상황이 발생했는지 알기 위해,
 &quot;axios&quot;로 구글링 해봤습니다.
 링크 :<a href="https://inpa.tistory.com/entry/AXIOS-%F0%9F%93%9A-%EC%84%A4%EC%B9%98-%EC%82%AC%EC%9A%A9">https://inpa.tistory.com/entry/AXIOS-%F0%9F%93%9A-%EC%84%A4%EC%B9%98-%EC%82%AC%EC%9A%A9</a></p>
<p> aixos의 정의와 특징을 다시 정리했습니다.</p>
<h3 id="aixos-왜-쓰는걸까">aixos, 왜 쓰는걸까?</h3>
<pre><code> 간단하게, 백엔드와 프론트엔드가 통신을 쉽게하기 위해 사용하는 비동기 통신 라이브러리 입니다.
그럼, 어떻게 쉽게 통신하게 하는 걸까요?
HTTP 요청과 응답을 하기 위해선 JSON형태로 변경하여 통신해야 합니다.
이미 JS에 fetch api가 있어 백엔드와 프론트엔드의 통신이 이루어지고 있었지만, 매번 통신 할때마다 JSON 형태로 데이터를 수정해야 하는 단점이 있었죠.
axios는 이런 fetch api의 단점을 보완했습니다.
HTTP 요청과 응답을 JSON형태로 자동으로 변경해주기 때문이죠.</code></pre><h3 id="클라이언트---서버-간-통신-방법">클라이언트 - 서버 간 통신 방법</h3>
<p>다시 두번째 상황으로 가보겠습니다. 클라이언트에서 작성된 유저 정보를 서버로 axios.post로 요청을 보낼 때, 출처가 서로 같았는지 확인을 하지 않았음을 알았습니다.</p>
<p>출처(origin)란, 서버의 위치를 의미하는 URL이며, protocol,host,port까지를 의미합니다.</p>
<p>그렇다면, 제가 작업한 클라이언트의 origin과 서버의 origin은 같았을 까요?
달랐습니다. 
웹 통신 정책에는 SOP(same origin policy)와 CORS(Cross Origin Resurce Sharing) 두가지 정책이 있습니다.
SOP은 동일한 출저에서만 리소스 공유가 가능한 정책입니다.
따라서 이번 에러의 원인은 클라이언트와 서버 같 origin이 달랐기 때문에, SOP 정책 위반에 따른 문제였습니다.</p>
<p>이를 해결하는 방법은 굳이 동일한 origin을 설정하지 않아도 되는 CORS 정책으로 해결될 수 있었습니다.</p>
<blockquote>
<p>axios 요청 파라미터 옵션 : withCredentials의 기본값인 fals를 true로 변경!</p>
</blockquote>
<p>axios 요청 파라미터 옵션인 withCredentials의 기본값은 fals로 설정되어 CORS 요청을 허용하지 않게 설정되어 있습니다.
withCredentials의 기본값을 fals에서 ture로 변경하면,
CORS 요청을 허용하게되고, 쿠키값을 전달 할 수 있게 되는 것이죠.</p>
<p>회원가입 페이지에는 axios 요청이 자주 요구되므로,
withCredentials의 기본값을 전역으로 설정하여 편의성을 확보 할 수 있었습니다.</p>
]]></description>
        </item>
    </channel>
</rss>