<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>(this) =&gt; yjin</title>
        <link>https://velog.io/</link>
        <description>기억은 한계가 있지만, 기록은 한계가 없다.</description>
        <lastBuildDate>Thu, 04 Jan 2024 04:53:48 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>(this) =&gt; yjin</title>
            <url>https://velog.velcdn.com/images/thisisyjin/profile/029be9d0-1be8-4e6a-96ed-a45da7774a5c/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. (this) =&gt; yjin. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/thisisyjin" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[240104 TIL]]></title>
            <link>https://velog.io/@thisisyjin/230104-TIL</link>
            <guid>https://velog.io/@thisisyjin/230104-TIL</guid>
            <pubDate>Thu, 04 Jan 2024 04:53:48 GMT</pubDate>
            <description><![CDATA[<h1 id="nextjs">NEXT.JS</h1>
<h2 id="learned">Learned</h2>
<ol>
<li>Why Next.js?</li>
<li>File-based Routing</li>
<li>Page Pre-rendering</li>
<li>Data Fetching</li>
<li>Adding API</li>
</ol>
<hr>
<h2 id="nextjs란">Next.js란?</h2>
<ul>
<li>생산용 리액트 프레임워크.</li>
<li>대규모 양산형 React 앱을 구출할 수 있는 많은 기능 포함</li>
<li>React &#39;풀스택&#39; 프레임워크</li>
</ul>
<blockquote>
<p>React는 [라이브러리] 이다!</p>
</blockquote>
<ul>
<li>리액트는 UI에 집중한 &#39;라이브러리&#39;이고,</li>
<li>프레임워크는 하나에 초점을 맞추지 않고 여러가지를 다룸</li>
<li>파일 구성 및 코드 작성 방법과 명확한 규칙이 정해져 있음.</li>
</ul>
<hr>
<h2 id="nextjs의-주요-기능">Next.js의 주요 기능</h2>
<h3 id="1-서버-사이드-렌더링-seo-향상">1. 서버 사이드 렌더링 (SEO 향상)</h3>
<ul>
<li>일반 React로 개발한 SPA는 클라이언트 사이드 렌더링.</li>
<li>사용자가 사이트를 방문했을 때 서버가 클라이언트로 보내는 index.html 파일을 상당히 비어있음.<ul>
<li>데이터를 받아오는 동안 화면 깜뻑임 현상이 발생할 수 있음</li>
<li>사용자 경험이 좋지 않을 수 있음.</li>
</ul>
</li>
<li>검색 엔진으로 찾아야 하는 공개 페이지의 경우, 검색 엔진 크롤러가 index.html을 검색하기 때문에 검색 엔진 최적화(SEO) 방면에서 문제가 될 수 있다.</li>
</ul>
<ul>
<li>물론 기본 React에서도 SSR을 구현할 수 있는 메서드가 존재함.</li>
<li>그러나 Next.js를 사용하면 훨씬 더 쉽게 구현할 수 있음.</li>
<li>서버 사이드 렌더링이 내장되어 있기 때문에 자동으로 모든 페이지를 SSR 처리함.</li>
</ul>
<ul>
<li>초기 렌더링 시에는 pre-render 페이지를 보여주어 SEO향상.</li>
<li>즉, Next.js는 CSR과 SSR을 적절히 혼합할 수 있음.</li>
</ul>
<h3 id="2-file-based-routing">2. File-based Routing</h3>
<ul>
<li>파일 기반 라우팅.</li>
<li>기존 React에서는 React-router을 사용하여 라우팅을 구현하였음.</li>
<li>Next.js 에서는 파일과 폴더를 이용하여 페이지, 라우트를 정의할 수 있음.</li>
</ul>
<ul>
<li><p><code>/pages</code> 라는 폴더의 파일을 추가하여 라우팅 가능.</p>
<ul>
<li>단, 반드시 폴더명을 <code>/pages</code>로 작성해야 함!</li>
</ul>
</li>
<li><p>추가 코드를 쓸 필요가 없어지고, 작업량을 줄일 수 있게 됨.</p>
</li>
<li><p>중첩 라우팅, 동적 라우팅 등의 추가적인 기능도 기본으로 지원함.</p>
</li>
</ul>
<h3 id="3-full-stack-app-build">3. Full-stack App Build</h3>
<ul>
<li>SSR을 위해서라면 독립적인 백엔드 코드도 존재해야 함.</li>
<li>Next.js에서는 React 프로젝트에 백엔드 API를 추가할 수 있음 (Node.js)</li>
<li>데이터베이스, 인증 등 다양한 작업을 할 수 있음. </li>
</ul>
<hr>
<h2 id="practice-project">Practice Project</h2>
<h3 id="project-생성">Project 생성</h3>
<pre><code class="language-bash">$ npx create-next-app</code></pre>
<ul>
<li>CRA로 생성한 기본 리액트 앱은 /public에 <code>index.html</code> 파일이 존재하는데,</li>
<li>next.js 앱 안에는 <code>index.html</code> 파일이 존재하지 않음.<ul>
<li>pre-rendering 기능이 내장되어 있기 때문</li>
</ul>
</li>
</ul>
<blockquote>
<p>[참고] Next 버전 업에 따른 폴더 구조 변경</p>
</blockquote>
<ul>
<li>기존 폴더 구조와 변경되어 /pages 나 /style 등이 생성되지 않음.</li>
<li>위 명령을 통해 보일러플레이트를 생성할 때, 모든 옵션에 No를 체크하면 된다!</li>
</ul>
<p><img src="https://velog.velcdn.com/images/thisisyjin/post/1d3a7075-a4bc-4163-b4d5-cdcc0e5d87fd/image.png" alt=""> <img src="https://velog.velcdn.com/images/thisisyjin/post/4a275851-6dad-427e-9359-377d2e39abfd/image.png" alt=""></p>
<h3 id="pages-폴더">/pages 폴더</h3>
<ul>
<li><code>_app.js</code> 제외하고 전부 삭제.</li>
<li>총 세 개의 페이지를 생성하려면, /pages 폴더에 세 개의 파일 생성 필요.</li>
</ul>
<ol>
<li>index.js</li>
</ol>
<ul>
<li>루트(/) 페이지 </li>
</ul>
<ol start="2">
<li>news.js</li>
</ol>
<ul>
<li>/news 페이지</li>
</ul>
<h3 id="중첩-라우팅">중첩 라우팅</h3>
<p>위 방식처럼 /pages 폴더에 직접 파일을 생성해도 됨.
그 외에도 중첩 라우팅 기능을 제공함.</p>
<p>예&gt;</p>
<ul>
<li><code>/news</code>와 <code>/news/about</code> 에 해당하는 라우팅을 설정하고 싶다면?</li>
<li>아래와 같이 /pages에 <code>/news</code> 폴더를 생성한 후, <code>index.js</code>와 <code>about.js</code> 파일을 생성하면 된다.
<img src="https://velog.velcdn.com/images/thisisyjin/post/cae62ec3-d840-47cb-b356-e3b07b097e2e/image.png" alt=""></li>
</ul>
<h3 id="동적-라우팅">동적 라우팅</h3>
<ul>
<li><code>params</code>가 포함된 동적 라우팅이 필요한 경우</li>
<li>Dynamic Routing</li>
</ul>
<p><a href="https://nextjs.org/docs/pages/building-your-application/routing/dynamic-routes">공식 문서 - Dynamic Routing</a></p>
<ul>
<li>Next.js에서 사용하는 구문 사용</li>
<li>파일명에 대괄호(<code>[]</code>)를 넣어주고, 변수명을 입력하면 된다!</li>
<li>params 값을 가져오기 위해 <code>next/router</code>의 <code>useRouter</code> Hook을 사용한다.</li>
</ul>
<pre><code class="language-jsx">import { useRouter } from &#39;next/router&#39;;

const DetailPage = () =&gt; {
  const router = useRouter();
  return &lt;h1&gt;Detail Page - {router.query.newsId}&lt;/h1&gt;;
};

export default DetailPage;</code></pre>
<blockquote>
<h3 id="userouter">useRouter</h3>
</blockquote>
<ul>
<li><p>useRouter이 리턴하는 router 객체의 구조를 살펴보면 다음과 같다.</p>
<pre><code class="language-js">{
  &quot;pathname&quot;: &quot;/news/[newsId]&quot;,
  &quot;route&quot;: &quot;/news/[newsId]&quot;,
  &quot;query&quot;: {
      &quot;newsId&quot;: &quot;1234567&quot;
  },
  &quot;asPath&quot;: &quot;/news/1234567&quot;,
  &quot;components&quot;: {
      &quot;/news/[newsId]&quot;: {
          &quot;initial&quot;: true,
          &quot;props&quot;: {
              &quot;pageProps&quot;: {}
          }
      },
      &quot;/_app&quot;: {
          &quot;styleSheets&quot;: []
      }
  },
  &quot;isFallback&quot;: false,
  &quot;basePath&quot;: &quot;&quot;,
  &quot;isReady&quot;: true,
  &quot;isPreview&quot;: false,
  &quot;isLocaleDomain&quot;: false,
  &quot;events&quot;: {}
}</code></pre>
</li>
<li><p>중첩 + 동적 라우팅도 가능하다.</p>
<ul>
<li>예&gt; {hostURL}/{userName}/{postName}
<img src="https://velog.velcdn.com/images/thisisyjin/post/a6693df0-064d-49e6-85fd-7a7d8722b7a6/image.png" alt=""></li>
</ul>
</li>
<li><p>위와 같이 폴더명을 대괄호로 감싸주고, 파일명도 대괄호로 감싸주면 된다.</p>
</li>
<li><p><code>/thisisyjin/12345</code> 에서 router 객체는 다음과 같다.</p>
</li>
<li><blockquote>
<p>router.query에서 두 개의 params를 모두 확인할 수 있다!</p>
</blockquote>
<pre><code class="language-js">{
   &quot;pathname&quot;: &quot;/[userName]/[postName]&quot;,
   &quot;route&quot;: &quot;/[userName]/[postName]&quot;,
   &quot;query&quot;: {
       &quot;userName&quot;: &quot;thisisyjin&quot;,
       &quot;postName&quot;: &quot;1234&quot;
   },
   &quot;asPath&quot;: &quot;/thisisyjin/1234&quot;,
   &quot;components&quot;: {
       &quot;/[userName]/[postName]&quot;: {
           &quot;initial&quot;: true,
           &quot;props&quot;: {
               &quot;pageProps&quot;: {}
           }
       },
       &quot;/_app&quot;: {
           &quot;styleSheets&quot;: []
       }
   },
   &quot;isFallback&quot;: false,
   &quot;basePath&quot;: &quot;&quot;,
   &quot;isReady&quot;: true,
   &quot;isPreview&quot;: false,
   &quot;isLocaleDomain&quot;: false,
   &quot;events&quot;: {}
}</code></pre>
</li>
</ul>
<h3 id="페이지-간-연결-link">페이지 간 연결 (Link)</h3>
<ul>
<li>다른 페이지로 이동하는 링크</li>
</ul>
<ol>
<li>a 태그 (href) - SPA 구현 X</li>
<li>Link 컴포넌트 - SPA 구현 가능</li>
</ol>
<blockquote>
<h3 id="link">Link</h3>
</blockquote>
<pre><code class="language-jsx">import { Link } from &#39;next/link&#39;;

const Homepage = () =&gt; {
  return (
    &lt;Link href=&quot;/&quot;&gt;Home&lt;/Link&gt;
    &lt;Link href=&quot;/about&quot;&gt;About&lt;/Link&gt;
    &lt;Link href=&quot;/post/${encodeURIComponent(postId)}&quot;&gt;My Post&lt;/Link&gt;&#39;
    &lt;!-- alternative --&gt;
    &lt;Link href={{
    pathname: &#39;post/[postId]&#39;,
    query: { postId: postId }
        }}
    &gt;
      My Post
    &lt;/Link&gt;
)
}</code></pre>
<ul>
<li>Dynamic Routing의 경우에는, 두 가지 방법을 통해 링크 가능.</li>
</ul>
<ol>
<li>string literal 이용 (<code>encodeURIComponent()</code>)</li>
<li>URL object 이용<pre><code class="language-jsx">&lt;Link href={{
 pathname: &#39;/about/[id]&#39;,
 query: { id: post.id }
}}
&gt;
Link Page
&lt;/Link&gt;</code></pre>
</li>
</ol>
<blockquote>
<p>주의 - React Router의 <code>Link</code>와 다름.</p>
</blockquote>
<ul>
<li>Link to=&#39;/path&#39;와 다르게</li>
<li>Link href=&quot;/path&quot;로 작성해주어야 함.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Node Version Update 시 에러]]></title>
            <link>https://velog.io/@thisisyjin/Node-Version-Update-%EC%8B%9C-%EC%97%90%EB%9F%AC</link>
            <guid>https://velog.io/@thisisyjin/Node-Version-Update-%EC%8B%9C-%EC%97%90%EB%9F%AC</guid>
            <pubDate>Thu, 04 Jan 2024 03:49:34 GMT</pubDate>
            <description><![CDATA[<h2 id="node-version-update-시-에러">Node Version Update 시 에러</h2>
<p>Node.js 세팅 중 노드 버전 업데이트가 필요하여 업데이트 진행 도중
에러가 발생하여 기록한다.</p>
<pre><code class="language-bash"># node version 확인
$ node -v

# npm cache 삭제
$ npm cache clean -f

# 전역으로 n 설치
$ npm install -g n

# lts 버전으로 변경
$ n lts</code></pre>
<h3 id="npm-cache-clean--f">npm cache clean -f</h3>
<pre><code>using --force Recommended protections disabled.</code></pre><ul>
<li>cache 폴더 내용 확인 후, 무결성을 확인하여 해결하는 명령어인
<code>npm cache verify</code> 를 대신 사용해야 한다.</li>
</ul>
<h3 id="n-lts">n lts</h3>
<ul>
<li>installed와 active의 버전이 다름.</li>
<li><code>node -v</code> 해보니 active의 버전이 나옴. </li>
<li>즉, 두 경로가 달라 노드 버전이 변경되지 않았음.</li>
</ul>
<pre><code class="language-bash">$ ln -sf {installed 경로} {active 경로}</code></pre>
<h3 id="ln-명령어">ln 명령어</h3>
<ul>
<li>Symbolic Links</li>
<li>파일 간의 링크를 만드는 명령줄 유틸리티.</li>
<li>즉, active 경로에 installed 경로를 링크하여 활성화함.</li>
</ul>
<pre><code class="language-bash">$ node -v
# v20.10.0</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[240103 TIL (2)]]></title>
            <link>https://velog.io/@thisisyjin/240103-TIL-2</link>
            <guid>https://velog.io/@thisisyjin/240103-TIL-2</guid>
            <pubDate>Wed, 03 Jan 2024 05:44:22 GMT</pubDate>
            <description><![CDATA[<h1 id="redux-advanced">Redux Advanced</h1>
<hr>
<h2 id="비동기">비동기</h2>
<h3 id="부수-효과side-effect">부수 효과(Side-Effect)</h3>
<ol>
<li>컴포넌트 안에서 처리 - <code>useEffect</code>를 사용</li>
<li>action creator 안에서 처리 - redux middleware</li>
</ol>
<hr>
<h1 id="practice-project">Practice Project</h1>
<h2 id="ui-store">ui-store</h2>
<h3 id="store-생성">store 생성</h3>
<ol>
<li>store/index.js<pre><code class="language-jsx">import { configureStore } from &quot;@reduxjs/toolkit&quot;;
import uiSlice from &quot;./ui-slice&quot;;
import cartSlice from &quot;./cart-slice&quot;;
</code></pre>
</li>
</ol>
<p>const store = configureStore({
  reducer: {
    ui: uiSlice.reducer,
    cart: cartSlice.reducer,
  },
});</p>
<p>export default store;</p>
<pre><code>
2. store/ui-slice.js
``` jsx
import { createSlice } from &quot;@reduxjs/toolkit&quot;;

const initialState = {
  cartIsVisible: false,
};

const uiSlice = createSlice({
  name: &quot;ui&quot;,
  initialState,
  reducers: {
    toggle(state) {
      state.cartIsVisible = !state.cartIsVisible;
    },
  },
});

export const uiActions = uiSlice.actions;
export default uiSlice;
</code></pre><ol start="3">
<li>store/cart-slice.js</li>
</ol>
<h3 id="store-연결-provider">store 연결 (Provider)</h3>
<ul>
<li>index.js</li>
</ul>
<pre><code class="language-jsx">import ReactDOM from &quot;react-dom/client&quot;;
import { Provider } from &quot;react-redux&quot;;
import store from &quot;./store&quot;;

import &quot;./index.css&quot;;
import App from &quot;./App&quot;;

const root = ReactDOM.createRoot(document.getElementById(&quot;root&quot;));
root.render(
  &lt;Provider store={store}&gt;
    &lt;App /&gt;
  &lt;/Provider&gt;
);
</code></pre>
<h3 id="usedispatch-ui">useDispatch (UI)</h3>
<ul>
<li>버튼 클릭 시 ui 토글 (dispatch)<pre><code class="language-jsx">// components/CartButton.jsx
</code></pre>
</li>
</ul>
<p>import classes from &quot;./CartButton.module.css&quot;;
import { useDispatch } from &quot;react-redux&quot;;
import { uiActions } from &quot;../../store/ui-slice&quot;;</p>
<p>const CartButton = (props) =&gt; {
  const dispatch = useDispatch();
  const toggleCartHandler = () =&gt; {
    dispatch(uiActions.toggle());
  };</p>
<p>  return (
    <button className={classes.button} onClick={toggleCartHandler}>
      <span>My Cart</span>
      <span className={classes.badge}>1</span>
    </button>
  );
};</p>
<p>export default CartButton;</p>
<pre><code>

### useSelector (UI)
- App.js 에서 useSelector로 상태 가져와서 조건에 따라 Cart 보여주기

``` jsx
// App.js
import Cart from &quot;./components/Cart/Cart&quot;;
import Layout from &quot;./components/Layout/Layout&quot;;
import Products from &quot;./components/Shop/Products&quot;;
import { useSelector } from &quot;react-redux&quot;;

function App() {
  const isVisibleCart = useSelector((state) =&gt; state.ui.cartIsVisible);

  return (
    &lt;Layout&gt;
      {isVisibleCart &amp;&amp; &lt;Cart /&gt;}
      &lt;Products /&gt;
    &lt;/Layout&gt;
  );
}

export default App;</code></pre><hr>
<h2 id="cart-slice">cart-slice</h2>
<h3 id="store-생성-1">store 생성</h3>
<ul>
<li>item 객체 구조<pre><code class="language-js">{
id,
price,
totalPrice,
name 
}</code></pre>
</li>
</ul>
<ol>
<li><p>addItemToCart</p>
</li>
<li><p>removeItemFromCart</p>
<pre><code class="language-jsx">// store/cart-slice.js
import { createSlice } from &quot;@reduxjs/toolkit&quot;;
</code></pre>
</li>
</ol>
<p>const initialState = {
  items: [],
  totalQuantity: 0,
};
const cartSlice = createSlice({
  name: &quot;cart&quot;,
  initialState,
  reducers: {
    addItemToCart(state, action) {
      const newItem = action.payload;
      const existingItem = state.items.find((item) =&gt; item.id === newItem.id);</p>
<pre><code>  if (!existingItem) {
    state.items.push({
      itemId: newItem.id,
      price: newItem.price,
      quantity: 1,
      totalPrice: newItem.price,
      name: newItem.name,
    });
  } else {
    existingItem.quantity++;
    existingItem.totalPrice += newItem.price;
  }
},
removeItemFromCart(state, action) {
  const id = action.payload;
  const existingItem = state.item.find((item) =&gt; item.id === id);
  if (existingItem.quantity === 1) {
    state.items = state.items.filter((item) =&gt; item.id !== id);
  } else {
    existingItem.quantity--;
    existingItem.totalPrice -= existingItem.price;
  }
},</code></pre><p>  },
});</p>
<p>export const cartActions = cartSlice.actions;
export default cartSlice;</p>
<pre><code>

### Action Dispatch

- 컴포넌트 수정 (Products.jsx)
``` jsx
import ProductItem from &quot;./ProductItem&quot;;
import classes from &quot;./Products.module.css&quot;;

const DUMMY_PRODUCTS = [
  { id: &quot;p1&quot;, price: 6, title: &quot;book1&quot;, description: &quot;english book&quot; },
  { id: &quot;p2&quot;, price: 5, title: &quot;book2&quot;, description: &quot;korean book&quot; },
];

const Products = (props) =&gt; {
  return (
    &lt;section className={classes.products}&gt;
      &lt;h2&gt;Buy your favorite products&lt;/h2&gt;
      &lt;ul&gt;
        {DUMMY_PRODUCTS.map((product) =&gt; (
          &lt;ProductItem
            key={product.id}
            title={product.title}
            price={product.price}
            description={product.description}
          /&gt;
        ))}
      &lt;/ul&gt;
    &lt;/section&gt;
  );
};

export default Products;</code></pre><pre><code class="language-jsx">// ProductItem.jsx

import Card from &quot;../UI/Card&quot;;
import classes from &quot;./ProductItem.module.css&quot;;

import { useDispatch } from &quot;react-redux&quot;;
import { cartActions } from &quot;../../store/cart-slice&quot;;

const ProductItem = (props) =&gt; {
  const { title, price, description, id } = props;

  const dispatch = useDispatch();
  const addToCartHandler = () =&gt; {
    dispatch(
      cartActions.addItemToCart({
        id,
        title,
        price,
      })
    );
  };

  return (
    &lt;li className={classes.item}&gt;
      &lt;Card&gt;
        &lt;header&gt;
          &lt;h3&gt;{title}&lt;/h3&gt;
          &lt;div className={classes.price}&gt;${price.toFixed(2)}&lt;/div&gt;
        &lt;/header&gt;
        &lt;p&gt;{description}&lt;/p&gt;
        &lt;div className={classes.actions}&gt;
          &lt;button onClick={addToCartHandler}&gt;Add to Cart&lt;/button&gt;
        &lt;/div&gt;
      &lt;/Card&gt;
    &lt;/li&gt;
  );
};

export default ProductItem;</code></pre>
<h3 id="reducer-수정">reducer 수정</h3>
<pre><code class="language-js">// cart-slice.js

import { createSlice } from &quot;@reduxjs/toolkit&quot;;

const initialState = {
  items: [],
  totalQuantity: 0,
};

const cartSlice = createSlice({
  name: &quot;cart&quot;,
  initialState,
  reducers: {
    addItemToCart(state, action) {
      const newItem = action.payload;
      const existingItem = state.items.find((item) =&gt; item.id === newItem.id);
      state.totalQuantity++;

      if (!existingItem) {
        state.items.push({
          itemId: newItem.id,
          price: newItem.price,
          quantity: 1,
          totalPrice: newItem.price,
          name: newItem.name,
        });
      } else {
        existingItem.quantity++;
        existingItem.totalPrice += newItem.price;
      }
    },
    removeItemFromCart(state, action) {
      const id = action.payload;
      const existingItem = state.item.find((item) =&gt; item.id === id);
      state.totalQuantity--;
      if (existingItem.quantity === 1) {
        state.items = state.items.filter((item) =&gt; item.id !== id);
      } else {
        existingItem.quantity--;
        existingItem.totalPrice -= existingItem.price;
      }
    },
  },
});

export const cartActions = cartSlice.actions;
export default cartSlice;

</code></pre>
<h3 id="useselector">useSelector</h3>
<pre><code class="language-jsx">// CartButton.jsx
import classes from &quot;./CartButton.module.css&quot;;
import { useDispatch, useSelctor } from &quot;react-redux&quot;;
import { uiActions } from &quot;../../store/ui-slice&quot;;

const CartButton = (props) =&gt; {
  const dispatch = useDispatch();
  const cartQuantity = useSelctor((state) =&gt; state.cart.totalQuantity);

  const toggleCartHandler = () =&gt; {
    dispatch(uiActions.toggle());
  };

  return (
    &lt;button className={classes.button} onClick={toggleCartHandler}&gt;
      &lt;span&gt;My Cart&lt;/span&gt;
      &lt;span className={classes.badge}&gt;{cartQuantity}&lt;/span&gt;
    &lt;/button&gt;
  );
};

export default CartButton;</code></pre>
<pre><code class="language-jsx">// Cart.jsx

import Card from &quot;../UI/Card&quot;;
import classes from &quot;./Cart.module.css&quot;;
import CartItem from &quot;./CartItem&quot;;

import { useSelector } from &quot;react-redux&quot;;

const Cart = (props) =&gt; {
  const cartList = useSelector((state) =&gt; state.cart.items);
  return (
    &lt;Card className={classes.cart}&gt;
      &lt;h2&gt;Your Shopping Cart&lt;/h2&gt;
      &lt;ul&gt;
        {cartList.map((cartItem) =&gt; (
          &lt;CartItem
            key={cartItem.id}
            item={{
              title: cartItem.title,
              quantity: cartItem.quantity,
              total: cartItem.totalPrice,
              price: cartItem.price,
            }}
          /&gt;
        ))}
      &lt;/ul&gt;
    &lt;/Card&gt;
  );
};

export default Cart;
</code></pre>
<h3 id="action-dispatch-cart">Action Dispatch (cart)</h3>
<pre><code class="language-jsx">import classes from &quot;./CartItem.module.css&quot;;
import { useDispatch } from &quot;react-redux&quot;;
import { cartActions } from &quot;../../store/cart-slice&quot;;

const CartItem = (props) =&gt; {
  const { title, quantity, total, price, id } = props.item;
  const dispatch = useDispatch();

  const removeItemHandler = () =&gt; {
    dispatch(
      cartActions.removeItemFromCart({
        id,
        title,
        price,
      })
    );
  };

  return (
    &lt;li className={classes.item}&gt;
      &lt;header&gt;
        &lt;h3&gt;{title}&lt;/h3&gt;
        &lt;div className={classes.price}&gt;
          ${total.toFixed(2)}{&quot; &quot;}
          &lt;span className={classes.itemprice}&gt;(${price.toFixed(2)}/item)&lt;/span&gt;
        &lt;/div&gt;
      &lt;/header&gt;
      &lt;div className={classes.details}&gt;
        &lt;div className={classes.quantity}&gt;
          x &lt;span&gt;{quantity}&lt;/span&gt;
        &lt;/div&gt;
        &lt;div className={classes.actions}&gt;
          &lt;button onClick={removeItemHandler}&gt;-&lt;/button&gt;
          &lt;button&gt;+&lt;/button&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/li&gt;
  );
};

export default CartItem;
</code></pre>
<hr>
<h2 id="비동기-코드">비동기 코드</h2>
<ul>
<li>리듀서 함수는 부수효과가 없는 순수 함수여야 함.</li>
<li>즉, 부수효과를 생성하거나 비동기 코드는 리듀서 함수에 포함될 수 없음.</li>
<li>즉, HTTP Request 또한 리듀서 내부에서 실행될 수 없음</li>
</ul>
<ul>
<li>따라서 비동기 코드는 아래 두 방법으로 처리 가능.</li>
</ul>
<ol>
<li>컴포넌트 내부 - useEffect 사용<ul>
<li>컴포넌트 내부에서는 절대 redux state를 변경해서는 안됨. </li>
<li>(리듀서 함수 내부에서처럼 작성하면 X)</li>
</ul>
</li>
<li>액션 생성자 내부</li>
</ol>
<h3 id="logic의-위치">Logic의 위치</h3>
<ol>
<li>동기, 부수효과 없는 코드</li>
</ol>
<ul>
<li>Reducer 사용 O</li>
<li>액션 생성자 or 컴포넌트 사용 X</li>
</ul>
<ol start="2">
<li>비동기, 부수효과 있는 코드</li>
</ol>
<ul>
<li>액션 생성자 or 컴포넌트 사용 O</li>
<li>Reducer 절대 사용 X ❗️❗️</li>
</ul>
<h3 id="redux--useeffect">Redux + useEffect</h3>
<ul>
<li>컴포넌트 내에서 useEffect를 사용.</li>
</ul>
<pre><code class="language-jsx">// App.js
import Cart from &quot;./components/Cart/Cart&quot;;
import Layout from &quot;./components/Layout/Layout&quot;;
import Products from &quot;./components/Shop/Products&quot;;
import { useSelector } from &quot;react-redux&quot;;
import { useEffect } from &quot;react&quot;;

function App() {
  const isVisibleCart = useSelector((state) =&gt; state.ui.cartIsVisible);
  const cart = useSelector((state) =&gt; state.cart);

  useEffect(() =&gt; {
    fetch(&quot;backendURl&quot;, {
      method: &quot;PUT&quot;,
      body: JSON.stringify(cart);
    });
  }, [cart]);

  return (
    &lt;Layout&gt;
      {isVisibleCart &amp;&amp; &lt;Cart /&gt;}
      &lt;Products /&gt;
    &lt;/Layout&gt;
  );
}

export default App;
</code></pre>
<p>위 코드대로라면, useEffect는 초기에 실행되기 때문에
cart가 빈 값으로 덮어쓰기 될 수 있음.</p>
<h3 id="appjs-수정">App.js 수정</h3>
<pre><code class="language-jsx">import Cart from &quot;./components/Cart/Cart&quot;;
import Layout from &quot;./components/Layout/Layout&quot;;
import Products from &quot;./components/Shop/Products&quot;;
import { useSelector } from &quot;react-redux&quot;;
import { useEffect } from &quot;react&quot;;

function App() {
  const isVisibleCart = useSelector((state) =&gt; state.ui.cartIsVisible);
  const cart = useSelector((state) =&gt; state.cart);

  useEffect(() =&gt; {
    const sendCartData = async () =&gt; {
      await response = fetch(&quot;backendURl&quot;, {
         method: &quot;PUT&quot;,
         body: JSON.stringify(cart);
       });

        if (!response.ok) {
          throw new Error(&#39;Send Data Failed&#39;)
        }
       const responseData = await response.json();
     };
  }, [cart]);

  return (
    &lt;Layout&gt;
      {isVisibleCart &amp;&amp; &lt;Cart /&gt;}
      &lt;Products /&gt;
    &lt;/Layout&gt;
  );
}

export default App;
</code></pre>
<h3 id="ui-slice-수정">ui-slice 수정</h3>
<pre><code class="language-jsx">import { createSlice } from &quot;@reduxjs/toolkit&quot;;

const initialState = {
  cartIsVisible: false,
  notification: null,
};

const uiSlice = createSlice({
  name: &quot;ui&quot;,
  initialState,
  reducers: {
    toggle(state) {
      state.cartIsVisible = !state.cartIsVisible;
    },
    showNotification(state, action) {
      state.notification = {
        status: action.payload.status,
        title: action.payload.title,
        message: action.payload.message,
      };
    },
  },
});

export const uiActions = uiSlice.actions;
export default uiSlice;
</code></pre>
<h3 id="appjs-수정-dispatch">App.js 수정 (dispatch)</h3>
<pre><code class="language-jsx">import Cart from &quot;./components/Cart/Cart&quot;;
import Layout from &quot;./components/Layout/Layout&quot;;
import Products from &quot;./components/Shop/Products&quot;;
import { useSelector, useDispatch } from &quot;react-redux&quot;;
import { useEffect } from &quot;react&quot;;
import { uiActions } from &#39;./store/ui-slice&#39;;

function App() {
  const dispatch = useDispatch();
  const isVisibleCart = useSelector((state) =&gt; state.ui.cartIsVisible);
  const cart = useSelector((state) =&gt; state.cart);

  useEffect(() =&gt; {
    const sendCartData = async () =&gt; {
      dispatch(uiActions.showNotification({
        status: &#39;pending&#39;,
        title; &#39;Sending ...&#39;,
        message: &#39;Sending Cart Data&#39;
      }));
      await response = fetch(&quot;backendURl&quot;, {
        method: &quot;PUT&quot;,
        body: JSON.stringify(cart);
      });
      if (!response.ok) {
        dispatch(uiActions.showNotification({
          status: &#39;error&#39;,
          title; &#39;Error&#39;,
          message: &#39;Failed Sending Cart Data&#39;
        }));
      }
      const responseData = await response.json();
      dispatch(uiActions.showNotification({
        status: &#39;Success&#39;,
        title; &#39;Success&#39;,
        message: &#39;Success Sending Cart Data&#39;
      }));
    };

    sendCartData().catch(error =&gt; {
      dispatch(uiActions.showNotification({
        status: &#39;error&#39;,
        title; &#39;Error&#39;,
        message: &#39;Failed Sending Cart Data&#39;
      }));
    })
  }, [cart, dispatch]);

  return (
    &lt;Layout&gt;
      {isVisibleCart &amp;&amp; &lt;Cart /&gt;}
      &lt;Products /&gt;
    &lt;/Layout&gt;
  );
}

export default App;</code></pre>
<h3 id="useeffect가-처음에-실행되지-않도록-설정">useEffect가 처음에 실행되지 않도록 설정</h3>
<p>App.js 수정</p>
<ul>
<li>컴포넌트 외부에 isInitial 함수 선언 (let)</li>
<li>useEffect 에서 함수 실행 전에 if-return 을 통해 첫 실행 시 fetch하지 않도록 분기</li>
</ul>
<pre><code class="language-jsx">import Cart from &quot;./components/Cart/Cart&quot;;
import Layout from &quot;./components/Layout/Layout&quot;;
import Products from &quot;./components/Shop/Products&quot;;
import { useSelector, useDispatch } from &quot;react-redux&quot;;
import { useEffect } from &quot;react&quot;;
import { uiActions } from &#39;./store/ui-slice&#39;;

let isInitial = true;

function App() {
  const dispatch = useDispatch();
  const isVisibleCart = useSelector((state) =&gt; state.ui.cartIsVisible);
  const cart = useSelector((state) =&gt; state.cart);

  useEffect(() =&gt; {
    const sendCartData = async () =&gt; {
      dispatch(uiActions.showNotification({
        status: &#39;pending&#39;,
        title; &#39;Sending ...&#39;,
        message: &#39;Sending Cart Data&#39;
      }));
      await response = fetch(&quot;backendURl&quot;, {
        method: &quot;PUT&quot;,
        body: JSON.stringify(cart);
      });
      if (!response.ok) {
        dispatch(uiActions.showNotification({
          status: &#39;error&#39;,
          title; &#39;Error&#39;,
          message: &#39;Failed Sending Cart Data&#39;
        }));
      }
      const responseData = await response.json();
      dispatch(uiActions.showNotification({
        status: &#39;Success&#39;,
        title; &#39;Success&#39;,
        message: &#39;Success Sending Cart Data&#39;
      }));
    };

    if (isInitial) {
      isInitial = false;
      return;
    }

    sendCartData().catch(error =&gt; {
      dispatch(uiActions.showNotification({
        status: &#39;error&#39;,
        title; &#39;Error&#39;,
        message: &#39;Failed Sending Cart Data&#39;
      }));
    })
  }, [cart]);

  return (
    &lt;Layout&gt;
      {isVisibleCart &amp;&amp; &lt;Cart /&gt;}
      &lt;Products /&gt;
    &lt;/Layout&gt;
  );
}

export default App;</code></pre>
<hr>
<h2 id="액션-생성자-thunk">액션 생성자 Thunk</h2>
<ul>
<li>action creator을 활용할 수 있다.</li>
<li>action creator = <code>uiActions.showNotification</code> 과 같은 메서드.</li>
<li>디스패치할 액션을 생성하는 함수.</li>
</ul>
<h3 id="thunk">Thunk</h3>
<ul>
<li>다른 작업이 완료될 때 까지 작업을 지연시키는 함수</li>
</ul>
<pre><code class="language-jsx">// store/cart-slice.js

const sendCartData = (cartData) =&gt; {
  return (dispatch) =&gt; {
    dispatch(
      uiActions.showNotification({
        status: &#39;pending&#39;,
        title: &#39;Sending...&#39;,
        message: &#39;Sending cart data&#39;s
      })
    )
  }
  const sendCartData = async () =&gt; {
    dispatch(uiActions.showNotification({
      status: &#39;pending&#39;,
      title; &#39;Sending ...&#39;,
      message: &#39;Sending Cart Data&#39;
    }));
    await response = fetch(&quot;backendURl&quot;, {
      method: &quot;PUT&quot;,
      body: JSON.stringify(cart);
    });
    if (!response.ok) {
      dispatch(uiActions.showNotification({
        status: &#39;error&#39;,
        title; &#39;Error&#39;,
        message: &#39;Failed Sending Cart Data&#39;
      }));
    }
    const responseData = await response.json();
    dispatch(uiActions.showNotification({
      status: &#39;Success&#39;,
      title; &#39;Success&#39;,
      message: &#39;Success Sending Cart Data&#39;
    }));
  };

  await sendCartData();
};</code></pre>
<hr>
<h2 id="redux-devtools">Redux-Devtools</h2>
<ul>
<li>redux-toolkit을 사용하는 경우 바로 사용 가능.</li>
<li>redux를 사용하는 경우 바로 사용 가능</li>
</ul>
<pre><code class="language-bash">$ npm install @redux-devtools/extension</code></pre>
<pre><code class="language-js">const store = createStore(
  reducer,
  composeWithDevTools(
    applyMiddleware(...middleware),
    // other store enhancers if any
  ),
);</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[240103 TIL]]></title>
            <link>https://velog.io/@thisisyjin/240103-TIL</link>
            <guid>https://velog.io/@thisisyjin/240103-TIL</guid>
            <pubDate>Wed, 03 Jan 2024 03:29:59 GMT</pubDate>
            <description><![CDATA[<h1 id="redux">Redux</h1>
<h3 id="storegetstate">store.getState();</h3>
<ul>
<li>store.subscribe()</li>
</ul>
<pre><code class="language-jsx">import { createStore } from &#39;redux&#39;;

const store = createStore(counterReducer);
const counterSubscriber = () =&gt; {
  const latestState = store.getState();
}

store.subscribe(counterSubscriber);</code></pre>
<h3 id="reducer">Reducer</h3>
<ul>
<li>initialState를 지정해주어야 함.</li>
<li>state는 처음에 지정되지 않기 때문</li>
</ul>
<pre><code class="language-jsx">// Error - State is undefinced.
const counterReducer = (state, action) =&gt; {
  return {
    counter: state.counter + 1;
  }
}</code></pre>
<pre><code class="language-jsx">// InitialState 지정 필요
const counterReducer = (state = { counter : 0 }, action) =&gt; {
  return {
    counter: state.counter + 1;
  }
}</code></pre>
<h3 id="storedispatch">store.dispatch</h3>
<ul>
<li>액션 객체를 디스패치하여 state.counter을 변환시킬 수 있음.<pre><code class="language-jsx">store.dispatch({ type: &#39;increment&#39; });</code></pre>
</li>
</ul>
<h3 id="reducer-수정">reducer 수정</h3>
<ul>
<li><p>특정 action.type에 따라 다른 로직을 수행하도록 변경</p>
<pre><code class="language-js">const counterReducer = (state = { counter : 0 }, action) =&gt; {
if (action.type === &#39;increment&#39;) {
    return {
    counter: state.counter + 1;
  }

if (action.type === &#39;decrement&#39;) {
     return {
     counter: state.counter - 1;
   }
 }
}</code></pre>
</li>
</ul>
<hr>
<h2 id="practice-project">Practice Project</h2>
<h3 id="npm-install">npm install</h3>
<ul>
<li>redux</li>
<li>react-redux</li>
</ul>
<h3 id="프로젝트-생성">프로젝트 생성</h3>
<p>/src/store/index.js</p>
<pre><code class="language-js">import { createStore } from &quot;redux&quot;;

const counterReducer = (state = { counter: 0 }, action) =&gt; {
  if (action.type === &quot;increment&quot;) {
    return { counter: state.counter + 1 };
  }
  if (action.type === &quot;decrement&quot;) {
    return { counter: state.counter - 1 };
  }
  return state;
};

const store = createStore(counterReducer);

export default store;
</code></pre>
<h3 id="store-provide">store Provide</h3>
<ul>
<li>앱 전체를 렌더링한 index.js 파일에 가서 App 컴포넌트를 감싸줌</li>
<li><code>react-redux</code>에서 <code>Provider</code> 컴포넌트를 임포트함.</li>
</ul>
<pre><code class="language-jsx">import React from &quot;react&quot;;
import ReactDOM from &quot;react-dom/client&quot;;
import { Provider } from &quot;react-redux&quot;;
import store from &quot;./store&quot;;

import &quot;./index.css&quot;;
import App from &quot;./App&quot;;

const root = ReactDOM.createRoot(document.getElementById(&quot;root&quot;));
root.render(
  &lt;Provider store={store}&gt;
    &lt;App /&gt;
  &lt;/Provider&gt;
);
</code></pre>
<h3 id="컴포넌트에서-redux-데이터-사용">컴포넌트에서 redux 데이터 사용</h3>
<ul>
<li><code>react-redux</code>의 useSelector 사용.</li>
</ul>
<pre><code class="language-jsx">import classes from &#39;./Counter.module.css&#39;;
import { useSelector } from &#39;react-redux&#39;

const Counter = () =&gt; {
  const counter = useSelector((state) =&gt; state.counter);
  const toggleCounterHandler = () =&gt; {};

  return (
    &lt;main className={classes.counter}&gt;
      &lt;h1&gt;Redux Counter&lt;/h1&gt;
      &lt;div className={classes.value}&gt;{counter}&lt;/div&gt;
      &lt;button onClick={toggleCounterHandler}&gt;Toggle Counter&lt;/button&gt;
    &lt;/main&gt;
  );
};

export default Counter;</code></pre>
<h3 id="컴포넌트에서-액션-디스패치하기">컴포넌트에서 액션 디스패치하기</h3>
<ul>
<li>예&gt; 버튼을 클릭하면 <code>{type: increment}</code> 액션이 디스패치 되도록<pre><code class="language-jsx">import classes from &quot;./Counter.module.css&quot;;
import { useSelector, useDispatch } from &quot;react-redux&quot;;
</code></pre>
</li>
</ul>
<p>const Counter = () =&gt; {
  const dispatch = useDispatch();
  const counter = useSelector((state) =&gt; state.counter);</p>
<p>  const incrementHandler = () =&gt; {
    dispatch({ type: &quot;increment&quot; });
  };</p>
<p>  const decrementHandler = () =&gt; {
    dispatch({ type: &quot;decrement&quot; });
  };</p>
<p>  const toggleCounterHandler = () =&gt; {};</p>
<p>  return (
    <main className={classes.counter}>
      <h1>Redux Counter</h1>
      <div className={classes.value}>{counter}</div>
      <div>
        <button onClick={incrementHandler}>Increment</button>
        <button onClick={decrementHandler}>Decrement</button>
      </div>
      <button onClick={toggleCounterHandler}>Toggle Counter</button>
    </main>
  );
};</p>
<p>export default Counter;</p>
<pre><code>
### ⚠️ [참고] 클래스 컴포넌트의 경우 -     `connect`
- useSelector, useDispatch 등 `Hooks`는 사용 불가함.
- 대신 `connect`를 사용 가능! (두 Hook의 기능을 대체)
  - HOC (Higher-Ordered Component)
  - `connect(mapStateToProps, mapDispatchToProps)(Component)`
  - 두 개의 params를 받음. (1. useSelector 역할의 mapStateToProps / 2. dispatch 역할의 mapDispatchToProps) 

``` jsx
import { connect } &#39;react-redux&#39;;
class Counter extends React.Component {
  // mapDispatchToProps 
  incrementHandler() {
    this.props.increment();
  }
  decrementHandler() {
    this.props.decrement();
  }
  toogleCountHandler() {}

  render() {
    return (
      &lt;main className={classes.counter}&gt;
        &lt;h1&gt;Redux Counter&lt;/h1&gt;
        &lt;div className={classes.value}&gt;{counter}&lt;/div&gt;
        &lt;div&gt;
          &lt;button onClick={this.incrementHandler}&gt;Increment&lt;/button&gt;
          &lt;button onClick={this.decrementHandler}&gt;Decrement&lt;/button&gt;
        &lt;/div&gt;
        &lt;button onClick={this.toggleCounterHandler}&gt;Toggle Counter&lt;/button&gt;
      &lt;/main&gt;
    )
  }
}

const mapStateToProps = state =&gt; {
  return {
    counter: state.counter
  };
}

const mapDispatchToProps = dispatch =&gt; {
  return {
    increment: () =&gt; dispatch({type: &#39;increment&#39;}),
    decrement: () =&gt; dispatch({type: &#39;decrement&#39;});
  }
};

export default connect(mapStateToProps, mapDispatchToProps)(Counter);</code></pre><hr>
<h2 id="payload-사용">Payload 사용</h2>
<ul>
<li>액션을 디스패치할 때, payload 라는 값을 보내줄 수 있다.</li>
<li>리듀서 함수에서는 넘겨받은 payload값을 사용하여 state 변경 로직에 사용 가능하다.</li>
</ul>
<pre><code class="language-jsx">// store/index.js
import { createStore } from &quot;redux&quot;;

const counterReducer = (state = { counter: 0 }, action) =&gt; {
  if (action.type === &quot;increment&quot;) {
    return { counter: state.counter + 1 };
  }
  // ✅ Payload 사용
  if (action.type === &quot;increase&quot;) {
    return { counter: state.counter + action.value };
  }
  if (action.type === &quot;decrement&quot;) {
    return { counter: state.counter - 1 };
  }
  return state;
};

const store = createStore(counterReducer);

export default store;
</code></pre>
<pre><code class="language-js">// At Component
const increaseHandler = () =&gt; {
  dispatch({ type: &quot;increase&quot;, amount: 5 });
};</code></pre>
<hr>
<h2 id="카운터-toggle-추가하기">카운터 Toggle 추가하기</h2>
<ul>
<li>toggle 버튼을 클릭하면 카운터가 숨겨지는 기능 추가</li>
<li>원래는 state로 진행하지만, redux를 이용한 실습 해보기.</li>
<li>기존 <code>counterReducer</code>의 initialState 구조를 변경함.</li>
</ul>
<pre><code class="language-jsx">import { createStore } from &quot;redux&quot;;

const initialState = { counter: 0, showCounter: true };

const counterReducer = (state = initialState, action) =&gt; {
  if (action.type === &quot;increment&quot;) {
    return { ...state, counter: state.counter + 1 };
  }
  if (action.type === &quot;increase&quot;) {
    return { ...state, counter: state.counter + action.value };
  }
  if (action.type === &quot;decrement&quot;) {
    return { ...state, counter: state.counter - 1 };
  }
  // Toggle 추가
  if (action.type === &quot;toggle&quot;) {
    return {
      ...state,
      showCounter: !state.showCounter,
    };
  }
  return state;
};

const store = createStore(counterReducer);

export default store;
</code></pre>
<pre><code class="language-jsx">// Counter.jsx

import classes from &quot;./Counter.module.css&quot;;
import { useSelector, useDispatch } from &quot;react-redux&quot;;

const Counter = () =&gt; {
  const dispatch = useDispatch();
  const counter = useSelector((state) =&gt; state.counter);
  const show = useSelector((state) =&gt; state.showCounter);

  const incrementHandler = () =&gt; {
    dispatch({ type: &quot;increment&quot; });
  };

  const increaseHandler = () =&gt; {
    dispatch({ type: &quot;increase&quot;, amount: 5 });
  };

  const decrementHandler = () =&gt; {
    dispatch({ type: &quot;decrement&quot; });
  };

  const toggleCounterHandler = () =&gt; {};

  return (
    &lt;main className={classes.counter}&gt;
      {show &amp;&amp; (
        &lt;div className=&quot;counter&quot;&gt;
          &lt;h1&gt;Redux Counter&lt;/h1&gt;
          &lt;div className={classes.value}&gt;{counter}&lt;/div&gt;
          &lt;div&gt;
            &lt;button onClick={incrementHandler}&gt;Increment&lt;/button&gt;
            &lt;button onClick={decrementHandler}&gt;Decrement&lt;/button&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      )}
      &lt;button onClick={toggleCounterHandler}&gt;Toggle Counter&lt;/button&gt;
    &lt;/main&gt;
  );
};

export default Counter;</code></pre>
<h3 id="state-올바르게-사용하기">State 올바르게 사용하기</h3>
<ul>
<li>State를 관리할 때, 불변성이 보장되어야 함.</li>
<li>기존 state를 변형시키지 않고, ...(spread)를 통해 사본을 통해 변경해야 함.</li>
</ul>
<hr>
<h2 id="redux-toolkit">Redux Toolkit</h2>
<pre><code class="language-bash">$ npm install @reduxjs/toolkit</code></pre>
<h3 id="createslice">createSlice()</h3>
<ul>
<li>redux-toolkit에서 사용할 수 있는 강력한 기능.</li>
<li>createReducer()도 있지만, createSlice의 기능이 더 강력하다.</li>
</ul>
<ul>
<li>name(식별자)을 작성해준다.<ul>
<li><code>${name}Slice</code>와 같이 사용하면 된다.</li>
</ul>
</li>
<li>initialState를 지정해준다.</li>
<li>reducers 객체에는 함수가 들어가는데, 이 때는 if나 switch를 통한 action.type의 분기가 필요없어진다.</li>
<li>또한, reducer 함수 내에서 state를 직접 변환시켜줄 수 있다. 
(물론, 코드가 동작할 때 진짜로 state가 변형되지는 않는다.)</li>
</ul>
<pre><code class="language-jsx">import { createSlice } from &#39;@reduxjs/toolkit&#39;;

createSlice({
  name: &quot;counter&quot;,
  initialState,
  reducers: {
    increment(state) {
      state.counter++;
    },
    decrement(state) {
      state.counter--;
    },
    increase(state, action) {
      state.counter += action.amount;
    },
    toggleCounter(state) {
      state.showCounter = !state.showCounter;
    },
  },
});</code></pre>
<h3 id="createstore에-slice-연결">createStore에 slice 연결</h3>
<ul>
<li>우선, createStore()에 해당 슬라이스의 reducer을 연결시켜준다.</li>
<li>주의할 것은, <code>reducers</code>가 아닌 <code>reducer</code>을 넣어줘야 한다.</li>
<li>이 것은 하나의 큰 리듀서 함수를 의미한다.</li>
</ul>
<pre><code class="language-jsx">import { createStore } from &quot;redux&quot;;
import { createSlice } from &quot;@reduxjs/toolkit&quot;;

const initialState = { counter: 0, showCounter: true };

createSlice({
  name: &quot;counter&quot;,
  initialState,
  reducers: {
    increment(state) {
      state.counter++;
    },
    decrement(state) {
      state.counter--;
    },
    increase(state, action) {
      state.counter += action.amount;
    },
    toggleCounter(state) {
      state.showCounter = !state.showCounter;
    },
  },
});

const store = createStore(counterSlice.reducer);

export default store;</code></pre>
<ul>
<li>그러나, 지금 여러개의 리듀서 함수들이 있기 때문에 <code>combineStore</code>을 해줘야 함.</li>
</ul>
<blockquote>
<h3 id="참고-combinereducers">[참고] combineReducers</h3>
</blockquote>
<ul>
<li>redux 라이브러리의 기능으로, 여러 리듀서를 하나의 루트 리듀서로 합쳐줌.</li>
<li><a href="https://ko.redux.js.org/api/combinereducers/">참고 문서</a></li>
</ul>
<ul>
<li>기존 redux의 <code>createStore</code>을 이용하는 경우에는 위와 같이 combineReducers를 해주어야 하는 불편함이 있었음.</li>
<li>그러나, redux-toolkit의 <code>configureStore</code>을 사용해주면 따로 combineReducers를 사용할 필요 X.</li>
</ul>
<h3 id="configurestore">ConfigureStore</h3>
<ul>
<li><a href="https://redux-toolkit.js.org/api/configureStore">참고 문서</a>에 의하면</li>
<li>Combining the slice reducers into the root reducer 기능이 있다.</li>
</ul>
<pre><code class="language-jsx">// (common) slice가 여러개인 경우, reducer.counter 안에 넣어줘야 함.
const store = configureStore({
  reducer: { counter: counterSlice.reducer }
});</code></pre>
<ul>
<li>useReducer을 사용한 경우, 아래와 같이 사용함.<pre><code class="language-jsx">const store = configureStore({
reducer: {
  todos: todosReducer,
  auth: authReducer
}
});</code></pre>
</li>
</ul>
<h3 id="state-연결하기">state 연결하기</h3>
<pre><code class="language-jsx">import { createSlice, configureStore } from &quot;@reduxjs/toolkit&quot;;

const initialState = { counter: 0, showCounter: true };

createSlice({
  name: &quot;counter&quot;,
  initialState,
  reducers: {
    increment(state) {
      state.counter++;
    },
    decrement(state) {
      state.counter--;
    },
    increase(state, action) {
      state.counter += action.payload;
    },
    toggleCounter(state) {
      state.showCounter = !state.showCounter;
    },
  },
});

const store = configureStore({
  reducer: counterSlice.reducer,
});

export default store;
</code></pre>
<hr>
<h2 id="redux-toolkit-마이그레이션">Redux-toolkit 마이그레이션</h2>
<h3 id="액션-생성자">액션 생성자</h3>
<ul>
<li>액션 type에 해당하는 것을 전달하기 위해선 <code>${name}Slice.actions.${reducerName}</code>을 호출하면 된다.</li>
<li>즉, 아래 메서드를 호출하면 액션 객체가 생성되므로, &#39;액션 생성자&#39; 라고 한다.</li>
<li>직접 액션을 생성할 필요가 없고, actions를 사용하면 됨.</li>
</ul>
<pre><code class="language-jsx">counterSlice.actions.increase();
// return { type: &#39;some identifier&#39; } </code></pre>
<ul>
<li>액션 객체를 생성할 필요 X</li>
<li>고유 type명 생각할 필요 X</li>
</ul>
<h3 id="actions-export">actions export</h3>
<pre><code class="language-jsx">export const counterActions = counterSlice.actions;
</code></pre>
<ul>
<li>해당 슬라이스의 actions를 export 하고,</li>
<li>액션을 디스패치 하고 싶은 곳에서 import해서 메서드명을 붙여 사용하면 된다.
예&gt;<pre><code class="language-jsx">import { counterActions } from &quot;../store/index&quot;;
</code></pre>
</li>
</ul>
<p>...</p>
<p>const incrementHandler = () =&gt; {
  dispatch(counterActions.increment());
};</p>
<p>const decrementHandler = () =&gt; {
  dispatch(counterActions.decrement());
};</p>
<p>const increaseHandler = () =&gt; {
  dispatch(counterActions.increase(5)); // action.payload
};</p>
<pre><code>
&gt; [주의] action.payload
- 이전에는 아래와 같이 payload 필드의 이름을 지정할 수 있었지만,
- redux-toolkit에서는 액션을 자동으로 생성하고, 추가 데이터는 payload라는 이름으로 받아오기 때문에 임의로 필드명을 수정할 수 없다.

``` jsx
// 1. 기존 방식
const increaseHandler = () =&gt; {
  dispatch({ type: &quot;increase&quot;, amount: 5 });
};

// 2. Redux-Toolkit (Slice)
const incrementHandler = () =&gt; {
  dispatch(counterActions.increase(5));
};</code></pre><pre><code class="language-jsx">// 1. 기존 방식
if (action.type === &quot;increase&quot;) {
  return { counter: state.counter + action.amount };
}

// 2. Reduxt-Toolkit
increase(state, action) {
  state.counter += action.payload;
},</code></pre>
<hr>
<h2 id="다중-slice-작업">다중 Slice 작업</h2>
<ol>
<li>컴포넌트 추가<pre><code class="language-jsx">// App.js
import Counter from &quot;./components/Counter&quot;;
import Header from &quot;./components/Header&quot;;
import Auth from &quot;./components/Auth&quot;;
import { Fragment } from &quot;react&quot;;
</code></pre>
</li>
</ol>
<p>function App() {
  return (
    <Fragment>
      <Header />
      <Auth />
      <Counter />
    </Fragment>
  );
}</p>
<p>export default App;</p>
<pre><code>

2. store/index.js 수정

- 기존 상태
``` jsx
import { createSlice, configureStore } from &quot;@reduxjs/toolkit&quot;;

const initialState = { counter: 0, showCounter: true };

const counterSlice = createSlice({
  name: &quot;counter&quot;,
  initialState,
  reducers: {
    increment(state) {
      state.counter++;
    },
    decrement(state) {
      state.counter--;
    },
    increase(state, action) {
      state.counter += action.payload;
    },
    toggleCounter(state) {
      state.showCounter = !state.showCounter;
    },
  },
});

const store = configureStore({
  reducer: { counter: counterSlice.reducer },
});

export const counterActions = counterSlice.actions;

export default store;
</code></pre><ul>
<li>initialAuthState 추가 (initialState 분리)</li>
<li>authSlice 생성</li>
<li>configureStore 변경</li>
</ul>
<pre><code class="language-jsx">import { createSlice, configureStore } from &quot;@reduxjs/toolkit&quot;;

const initialCounterState = { counter: 0, showCounter: true };
const initialAuthState = {
  isAuthenticated: false,
};

const counterSlice = createSlice({
  name: &quot;counter&quot;,
  initialState: initialCounterState,
  reducers: {
    increment(state) {
      state.counter++;
    },
    decrement(state) {
      state.counter--;
    },
    increase(state, action) {
      state.counter += action.payload;
    },
    toggleCounter(state) {
      state.showCounter = !state.showCounter;
    },
  },
});

const authSlice = createSlice({
  name: &quot;auth&quot;,
  initialState: initialAuthState,
  reducers: {
    login(state) {
      state.isAuthenticated = true;
    },
    logout(state) {
      state.isAuthenticated = false;
    },
  },
});

const store = configureStore({
  reducer: { counter: counterSlice.reducer, auth: authSlice.reducer },
});

export const counterActions = counterSlice.actions;
export const authActions = authSlice.actions;

export default store;
</code></pre>
<ul>
<li>리듀서가 여러개이기 때문에, 값을 읽어 들일 때에도 코드 변경 필요.</li>
</ul>
<pre><code class="language-jsx">// 기존 코드
const counter = useSelector(state =&gt; state.counter);

// 변경 코드
const counter = useSelector({counter} =&gt; counter.counter);</code></pre>
<h2 id="action-dispatch">Action Dispatch</h2>
<pre><code class="language-jsx">// Auth.jsx
import classes from &quot;./Auth.module.css&quot;;
import { useDispatch } from &quot;react-redux&quot;;
import { authActions } from &quot;../store/index&quot;;

const Auth = () =&gt; {
  const dispatch = useDispatch();

  const handleLogin = () =&gt; {
    dispatch(authActions.login());
  };

  return (
    &lt;main className={classes.auth}&gt;
      &lt;section&gt;
        &lt;form&gt;
          &lt;div className={classes.control}&gt;
            &lt;label htmlFor=&quot;email&quot;&gt;Email&lt;/label&gt;
            &lt;input type=&quot;email&quot; id=&quot;email&quot; /&gt;
          &lt;/div&gt;
          &lt;div className={classes.control}&gt;
            &lt;label htmlFor=&quot;password&quot;&gt;Password&lt;/label&gt;
            &lt;input type=&quot;password&quot; id=&quot;password&quot; /&gt;
          &lt;/div&gt;
          &lt;button onClick={handleLogin}&gt;Login&lt;/button&gt;
        &lt;/form&gt;
      &lt;/section&gt;
    &lt;/main&gt;
  );
};

export default Auth;
</code></pre>
<pre><code class="language-jsx">// Header.jsx
import classes from &quot;./Header.module.css&quot;;
import { useDispatch } from &quot;react-redux&quot;;
import { authActions } from &quot;../store/index&quot;;

const Header = () =&gt; {
  const dispatch = useDispatch();
  const handleLogout = () =&gt; {
    dispatch(authActions.logout());
  }

  return (
    &lt;header className={classes.header}&gt;
      &lt;h1&gt;Redux Auth&lt;/h1&gt;
      &lt;nav&gt;
        &lt;ul&gt;
          &lt;li&gt;
            &lt;a href=&quot;/&quot;&gt;My Products&lt;/a&gt;
          &lt;/li&gt;
          &lt;li&gt;
            &lt;a href=&quot;/&quot;&gt;My Sales&lt;/a&gt;
          &lt;/li&gt;
          &lt;li&gt;
            &lt;button onClick={handleLogout}&gt;Logout&lt;/button&gt;
          &lt;/li&gt;
        &lt;/ul&gt;
      &lt;/nav&gt;
    &lt;/header&gt;
  );
};

export default Header;
</code></pre>
<h2 id="useselector">useSelector</h2>
<pre><code class="language-jsx">// App.js
import { useSelector } from &quot;react-redux&quot;;

import Counter from &quot;./components/Counter&quot;;
import Header from &quot;./components/Header&quot;;
import Auth from &quot;./components/Auth&quot;;
import UserProfile from &quot;./components/UserProfile&quot;;
import { Fragment } from &quot;react&quot;;

function App() {
  const isLogined = useSelector((state) =&gt; state.auth.isAuthenticated);

  return (
    &lt;Fragment&gt;
      &lt;Header /&gt;
      {isLogined ? &lt;UserProfile /&gt; : &lt;Auth /&gt;}
      &lt;Counter /&gt;
    &lt;/Fragment&gt;
  );
}

export default App;
</code></pre>
<pre><code class="language-jsx">// Header.jsx

import classes from &quot;./Header.module.css&quot;;
import { useDispatch, useSelector } from &quot;react-redux&quot;;
import { authActions } from &quot;../store/index&quot;;

const Header = () =&gt; {
  const dispatch = useDispatch();
  const isLogined = useSelector((state) =&gt; state.auth.isAuthenticated);

  const handleLogout = () =&gt; {
    dispatch(authActions.logout());
  };

  return (
    &lt;header className={classes.header}&gt;
      &lt;h1&gt;Redux Auth&lt;/h1&gt;
      {isLogined &amp;&amp; (
        &lt;nav&gt;
          &lt;ul&gt;
            &lt;li&gt;
              &lt;a href=&quot;/&quot;&gt;My Products&lt;/a&gt;
            &lt;/li&gt;
            &lt;li&gt;
              &lt;a href=&quot;/&quot;&gt;My Sales&lt;/a&gt;
            &lt;/li&gt;
            &lt;li&gt;
              &lt;button onClick={handleLogout}&gt;Logout&lt;/button&gt;
            &lt;/li&gt;
          &lt;/ul&gt;
        &lt;/nav&gt;
      )}
    &lt;/header&gt;
  );
};

export default Header;
</code></pre>
<hr>
<h2 id="코드-분할">코드 분할</h2>
<p>/store/index.js 안에 다 작성되어있는 코드를 분할해보자.</p>
<ul>
<li>/store/counter.js</li>
<li>/store/auth.js</li>
</ul>
<pre><code class="language-jsx">// store/index.js
import { configureStore } from &quot;@reduxjs/toolkit&quot;;
import counterSlice from &quot;./counter&quot;;
import authSlice from &quot;./auth&quot;;

const store = configureStore({
  reducer: { counter: counterSlice.reducer, auth: authSlice.reducer },
});

export default store;</code></pre>
<pre><code class="language-jsx">// store/counter.js
import { createSlice } from &quot;@reduxjs/toolkit&quot;;

const initialCounterState = { counter: 0, showCounter: true };

const counterSlice = createSlice({
  name: &quot;counter&quot;,
  initialState: initialCounterState,
  reducers: {
    increment(state) {
      state.counter++;
    },
    decrement(state) {
      state.counter--;
    },
    increase(state, action) {
      state.counter += action.payload;
    },
    toggleCounter(state) {
      state.showCounter = !state.showCounter;
    },
  },
});

export const counterActions = counterSlice.actions;
export default counterSlice;
</code></pre>
<pre><code class="language-jsx">// store/auth.js
import { createSlice } from &quot;@reduxjs/toolkit&quot;;

const initialAuthState = {
  isAuthenticated: false,
};

const authSlice = createSlice({
  name: &quot;auth&quot;,
  initialState: initialAuthState,
  reducers: {
    login(state) {
      state.isAuthenticated = true;
    },
    logout(state) {
      state.isAuthenticated = false;
    },
  },
});

export const authActions = authSlice.actions;
export default authSlice;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[240102 TIL]]></title>
            <link>https://velog.io/@thisisyjin/240102-TIL</link>
            <guid>https://velog.io/@thisisyjin/240102-TIL</guid>
            <pubDate>Wed, 03 Jan 2024 01:03:11 GMT</pubDate>
            <description><![CDATA[<h1 id="react-----typescript">React    | TypeScript</h1>
<h2 id="props-type">Props Type</h2>
<h3 id="primitive-type">Primitive Type</h3>
<pre><code class="language-tsx">type AppProps = {
  title: string;
  id: number;
  disabled: boolean;
}

const App = (props: AppProps) =&gt; {
  ...
}</code></pre>
<h3 id="union-string-literal">Union (String Literal)</h3>
<pre><code class="language-tsx">type AppProps = {
  status: &quot;success&quot; | &quot;failure&quot;;
  userType: &quot;user&quot; | &quot;admin&quot; | &quot;guest&quot;;
}

const App = (props: AppProps) =&gt; {
  ...
}</code></pre>
<h3 id="object">Object</h3>
<pre><code class="language-tsx">type AppProps = {
  obj: {
    id: string,
    title: string
  };
  dict: {
    [key: string]: MyType;
  };
}

const App = (props: AppProps) =&gt; {
  ...
}</code></pre>
<blockquote>
<h3 id="✅-dict">✅ Dict</h3>
<ul>
<li>Dict는 사전형 타입으로, key-value 쌍을 이루는 객체이다.</li>
<li>TypeScript에서는 <code>Record&lt;string, MyType&gt;</code>과 같이 작성 가능함.</li>
</ul>
</blockquote>
<h3 id="eventhanlder-function">EventHanlder Function</h3>
<pre><code class="language-tsx">type AppProps = {
  onClick: () =&gt; void;
  onChange: (id: number) =&gt; void;
  onChange: (event: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; void;
  onClick(event: React.MouseEvent&lt;HTMLButtonElement&gt;): void; 
}

const App = (props: AppProps) =&gt; {
  ...
}</code></pre>
<blockquote>
<h3 id="함수-type-표현">함수 Type 표현</h3>
</blockquote>
<ul>
<li>호출 시그니처: <code>(arg: number): void</code></li>
<li>타입 표현식: <code>(arg: number) =&gt; void</code></li>
<li>제네릭 함수:<pre><code class="language-tsx">function returnValue&lt;Type&gt;(arr: &lt;Type&gt;): Type {
  return arr;
}</code></pre>
</li>
</ul>
<h3 id="setstate-function">setState Function</h3>
<ul>
<li>부모 컴포넌트의 setState Function을 자식 컴포넌트에 <code>props</code>로 전달할 때</li>
</ul>
<p>예시&gt;</p>
<pre><code class="language-tsx">// 부모 컴포넌트
const ParentComponent = () =&gt; {
  const [userName, setUserName] = useState(&#39;&#39;);

  return (
    &lt;ChildComponent setUserName={setUserName} /&gt;
  )  
}</code></pre>
<pre><code class="language-tsx">// 자식 컴포넌트
type Props = {
  setUserName: React.Dispatch&lt;React.SetStateAction&lt;string&gt;&gt;;
  // string 대신 다른 type을 대입하면 OK
}

const ChildComponent = (props: Props) =&gt; {
  const changeState = (e) =&gt; {
    setUserName(e.target.value);
  }

  return (
    &lt;input
       ...
       onChange={changeState}
      /&gt;
  )
}</code></pre>
<h3 id="props">Props</h3>
<pre><code class="language-tsx">interface AppProps {
  children?: React.ReactNode;
  childrenElement: React.JSX.Element;
  style?: React.CSSProperties;
  onChange?: React.FormEventHandler&lt;HTMLInputElement&gt;;
  props: Props &amp; React.ComponentPropsWithoutRef&lt;&quot;button&quot;&gt;; // not forwards ref
}</code></pre>
<hr>
<h3 id="reactreactnode">React.ReactNode</h3>
<ul>
<li>대부분의 JSX 컴포넌트, 요소를 포함하는 상위 개념.</li>
</ul>
<pre><code class="language-tsx">type ReactNode = ReactElement | string | number | ReactFragment | ReactPortal | boolean | null | undefined;</code></pre>
<ul>
<li>ReactElement, ReactChild, JSX.Element 등을 포함.</li>
<li>ReactFragment, ReactPortal 등을 포함.</li>
<li>string, number, boolean, null, undefined 등 원시 타입을 포함.</li>
</ul>
<h3 id="reactreactelement">React.ReactElement</h3>
<ul>
<li>createElement 함수를 통해 생성된 객체에 지정.</li>
<li>원시타입을 포함하지 않음.</li>
<li>완성된 JSX 요소, 즉 <code>컴포넌트</code>만을 허용. (type, props, key 존재)</li>
</ul>
<h3 id="class-vs-functional-component">Class vs. Functional Component</h3>
<h4 id="클래스형-컴포넌트">클래스형 컴포넌트</h4>
<ul>
<li>React.ReactNode Type을 기본으로 리턴함.</li>
</ul>
<h4 id="함수형-컴포넌트">함수형 컴포넌트</h4>
<ul>
<li>React.ReactElement Interface를 기본으로 리턴함.</li>
</ul>
<h3 id="reactreactchild">React.ReactChild</h3>
<ul>
<li>ReactElement, ReactText(string, number)을 포함한다.</li>
</ul>
<h3 id="reactjsxelement">React.JSX.Element</h3>
<ul>
<li><code>ReactElement</code>를 상속받은 Interface.</li>
</ul>
<h3 id="keypoint">KeyPoint</h3>
<ul>
<li>컴포넌트를 자식(props)으로 받아 사용하는 경우, 정확한 Interface인 <code>ReactElement</code>를 이용하자.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[231231 TIL (2)]]></title>
            <link>https://velog.io/@thisisyjin/231231-TIL-2</link>
            <guid>https://velog.io/@thisisyjin/231231-TIL-2</guid>
            <pubDate>Sun, 31 Dec 2023 10:24:30 GMT</pubDate>
            <description><![CDATA[<h1 id="typescript">TypeScript</h1>
<blockquote>
<p><strong>✅ TypeScript Review</strong></p>
</blockquote>
<ul>
<li>이해가 더 필요한 부분 복습 + 정리</li>
</ul>
<blockquote>
</blockquote>
<ul>
<li><a href="https://www.typescriptlang.org/">참고 문서 1 - TypeScript</a></li>
<li><a href="https://react-typescript-cheatsheet.netlify.app/docs/basic/setup">참고 문서 2 - React TypeScript Cheatsheet</a> </li>
</ul>
<h2 id="generic">Generic</h2>
<ul>
<li>입력 값이 출력 값의 타입과 관련이 있거나</li>
<li>두 입력값의 타입이 서로 관련이 있는 형태의 함수</li>
</ul>
<ul>
<li>제네릭 문법은 <strong>두 값 사이의 상관관계를 표현</strong>하기 위해서 사용됨.</li>
</ul>
<pre><code class="language-ts">function firstElement&lt;Type&gt;(arr: Type[]): Type | undefined {
  return arr[0];
}</code></pre>
<p>-&gt; Type에 무엇이 들어가든 &#39;동일하다&#39; 는 의미로 적용됨.
-&gt; Type에 number을 넣어주면 <code>arr: number[]</code>이고, firstElement함수는 <code>number | undefined</code>을 리턴함.</p>
<pre><code class="language-ts">const s = firstElement([&quot;a&quot;, &quot;b&quot;, &quot;c&quot;]);</code></pre>
<p>여기서 Type은 number가 되므로, Number를 리턴함.</p>
<h3 id="inference-예제">Inference 예제</h3>
<pre><code class="language-ts">function map&lt;Input, Output&gt;(arr: Input[], func: (arg: Input) =&gt; Output): Output[] {
  return arr.map(func);
}

// 매개변수 &#39;n&#39;의 타입은 &#39;string&#39; 입니다.
// &#39;parsed&#39;는 number[] 타입을 하고 있습니다.
const parsed = map([&quot;1&quot;, &quot;2&quot;, &quot;3&quot;], (n) =&gt; parseInt(n));</code></pre>
<hr>
<h2 id="typescript-with-react">TypeScript with React</h2>
<h3 id="starter-kits">Starter Kits</h3>
<pre><code class="language-jsx">$ npx create-react-app --template typescript</code></pre>
<ul>
<li><a href="https://react-typescript-cheatsheet.netlify.app/docs/basic/setup#react-and-typescript-starter-kits">주요 Framework와 함께 사용</a></li>
</ul>
<h3 id="try-online">Try Online</h3>
<ul>
<li><a href="https://www.typescriptlang.org/play?target=8&amp;jsx=4#code/JYWwDg9gTgLgBAbzgVwM4FMDKMCGN0A0KGAogGZnoDG8AvnGVBCHAORTo42sDcAsAChB6AB6RYcKhAB2qeAGEIyafihwAvHAAUASg0A+RILiSZcuAG0pymEQwxFNgLobiWXPi0AGHfyECTNHRyShotXQMjAJM4ABMIKmQQdBUAOhhgGAAbdFcAAwBNJUks4CoAa3RYuAASBGsVegzk1Dy-E1pfQWM4DhhkKGltHpMAHn0RmNGwfSLkErLK6vqlRrhm9FRRgHoZybGAI2QYGBk4GXlSivUECPVDe0cVLQb4AGo4AEYdWgnomJil0WcGS+zgOyOJxkfwBOxhcC6AlogiAA">TypeScript Playground</a></li>
<li><a href="https://codesandbox.io/p/devbox/react-vite-9qputt">CodeSandBox</a></li>
</ul>
<h3 id="prop-types">Prop Types</h3>
<pre><code class="language-tsx">type AppProps = {
  // Primitive Type
  message: string;
  count: number;
  disabled: boolean;
  // array
  names: string[];
  // string literals
  status: &quot;success&quot; | &quot;failure&quot;;
  // object
  obj: {
    id: string;
    title: string;
  };
  objArr: {
    id: string;
    title: string;
  }[];
  obj: object;
  // dict (key-value) 
  dict1: {
    [key: string]: MyType;
  };
  dict2: Record&lt;string, MyType&gt;;
  // Event handler Function
  onClick: () =&gt; void;
  onChange: (id: number) =&gt; void;
  onChange: (event: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; void;
  onClick(event: React.MouseEvent&lt;HTMLButtonElement&gt;): void;
  optional?: SomeType;
  setState: React.Dispatch&lt;React.SetStateAction&lt;number&gt;&gt;;
};</code></pre>
<h3 id="그-외-유용한-prop-type">그 외 유용한 Prop Type</h3>
<pre><code class="language-tsx">interface AppProps {
  children?: React.ReactNode;
  childrenElement: React.JSX.Element;
  style?: React.CSSProperties;
  onChange?: React.FormEventHandler&lt;HTMLInputElement&gt;;
  props: Props &amp; React.ComponentPropsWithoutRef&lt;&quot;button&quot;&gt;; // forward X
  props2: Props &amp; React.ComponentPropsWithRef&lt;MyButtonWithForwardRef&gt;; // forward
}</code></pre>
<h3 id="event-hanlder">Event Hanlder</h3>
<p><a href="https://react-typescript-cheatsheet.netlify.app/docs/advanced/patterns_by_usecase/#wrappingmirroring">참고 문서</a></p>
<pre><code class="language-tsx">type UserTextEvent = {
  type: &quot;TextEvent&quot;;
  value: string;
  target: HTMLInputElement;
};
type UserMouseEvent = {
  type: &quot;MouseEvent&quot;;
  value: [number, number];
  target: HTMLElement;
};
type UserEvent = UserTextEvent | UserMouseEvent;
function handle(event: UserEvent) {
  if (event.type === &quot;TextEvent&quot;) {
    event.value; // string
    event.target; // HTMLInputElement
    return;
  }
  event.value; // [number, number]
  event.target; // HTMLElement
}</code></pre>
<hr>
<h2 id="type-vs-interface">Type vs. Interface</h2>
<ul>
<li>Type이 필요할 때 까지는 <code>Interface</code>를 써라.</li>
<li>라이브러리나 써드파티 사용 시 interface를 쓰면 일부 정의를 추가해야할 때, <code>extend</code>를 통해 확장 가능.</li>
</ul>
<ul>
<li><a href="https://react-typescript-cheatsheet.netlify.app/docs/basic/getting-started/basic_type_example#useful-table-for-types-vs-interfaces">참고 - Type vs. Interface</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[231231 TIL]]></title>
            <link>https://velog.io/@thisisyjin/231231-TIL</link>
            <guid>https://velog.io/@thisisyjin/231231-TIL</guid>
            <pubDate>Sun, 31 Dec 2023 07:22:45 GMT</pubDate>
            <description><![CDATA[<h1 id="redux">Redux</h1>
<h2 id="redux-1">Redux</h2>
<ul>
<li>Cross-Component or App-Wide State를 관리하는 관리 라이브러리.</li>
<li>useState와 useReducer로 관리하는 방식 대신 사용함.</li>
</ul>
<h3 id="local-state">Local State</h3>
<ul>
<li>Single Component에서 유효한 state</li>
<li><code>useState</code>, <code>useReducer</code>을 통해 관리</li>
</ul>
<h3 id="cross-component-state">Cross-Component State</h3>
<ul>
<li>Multiple Component에 유효한 state</li>
<li>예&gt; Modal close, open State 등</li>
<li><code>prop drilling</code> 이 필요</li>
</ul>
<h3 id="app-wide-state">App-Wide State</h3>
<ul>
<li>전체 앱에서 유효한 state</li>
<li>예&gt; 사용자 인증 등</li>
<li><code>prop drilling</code> 이 필요</li>
</ul>
<p>Cross-Component or App-Wide State를 관리하기 위해 사용되는 방식인
prop drilling 은 복잡할 수 있음.
-&gt; 이에 대한 해결책: <code>Context API</code> 또는 <code>Redux</code>를 사용하자!</p>
<hr>
<h2 id="redux-vs-context-api">Redux vs. Context API</h2>
<ul>
<li>context API를 사용하면 context와 contextProvider을 중심으로 상태관리를 쉽게 할 수 있음.</li>
<li>🧐 그렇다면, 왜 Redux를 사용하는 걸까?</li>
</ul>
<h3 id="redux-context의-잠재적-단점">Redux context의 잠재적 단점</h3>
<ol>
<li>설정과 관리가 어려워질 수 있다. (복잡성)</li>
</ol>
<ul>
<li>대형 어플리케이션일수록 복잡성을 띈다.</li>
<li>Provider의 중첩 등 상태가 방대해지게 된다.</li>
</ul>
<ol start="2">
<li>성능 문제가 발생할 수 있다.</li>
</ol>
<ul>
<li>고빈도의 변경은 성능을 저하시킬 수 있다.</li>
<li>Context는 상황에 따라 성능 면에서 좋지 않을 수 있다.</li>
</ul>
<hr>
<h2 id="redux-작동-방식">Redux 작동 방식</h2>
<ol>
<li>한 애플리케이션당 하나의 스토어만 가진다.</li>
</ol>
<ul>
<li>상태를 저장하는 스토어(store) = 저장소</li>
<li>저장소에 데이터를 저장해서 컴포넌트에서 사용 가능.</li>
<li>컴포넌트에서 이벤트를 감지하여 UI 업데이트 가능.</li>
<li>즉, 구독(Subscription)하여 데이터가 변경될 때 마다 스토어가 컴포넌트에 알려준다.</li>
</ul>
<ol start="2">
<li>컴포넌트가 직접 상태를 변환하지 않는다.</li>
</ol>
<ul>
<li>리듀서(reducer) 함수를 사용하여 상태를 업데이트한다.</li>
<li>여기서, reducer은 useReducer Hook에서와는 의미가 다르다.</li>
<li>데이터 변환을 &#39;트리거&#39; 하기 위해서 컴포넌트가 액션을 Dispatch함.<ul>
<li><code>Action</code>은 &#39;객체&#39;, <code>Dispatch</code>는 &#39;함수&#39;</li>
<li>즉, 컴포넌트는 특정 이벤트를 감지한 후 <code>Reducer</code>가 수행하도록 하기 위해 액션 객체를 디스패치함.</li>
<li>리덕스는 &#39;액션&#39;을 리듀서 함수로 전달하고, 원하는 작업에 대한 설명을 전달받아 실행하게 됨.</li>
<li>리듀서 함수가 실행되면, 상태가 변경되고, store를 구독중이였던 해당 컴포넌트의 UI를 업데이트 시킴.</li>
</ul>
</li>
</ul>
<hr>
<h2 id="redux-사용-예제">Redux 사용 예제</h2>
<pre><code class="language-jsx">import { createStore } from &#39;redux&#39;;

const store = createStore(counterReducer);

const counterReducer = (state, action) =&gt; {
  return {
    counter: state.counter + 1
  }
};</code></pre>
<h3 id="createstore">createStore</h3>
<ul>
<li>Redux에서는 redux-toolkit의 configStore의 사용을 권장함.</li>
<li>state</li>
</ul>
<h3 id="reducer">Reducer</h3>
<ul>
<li>2개의 파라미터를 가짐.</li>
</ul>
<ol>
<li>기존 State</li>
<li>발송된 action</li>
</ol>
<ul>
<li><p>항상 새로운 state를 반환해야 함. (업데이트)</p>
</li>
<li><p>단, 어떤 부수 효과(Side Effects)도 없어야 함!</p>
</li>
</ul>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[231229 TIL]]></title>
            <link>https://velog.io/@thisisyjin/231229-TIL</link>
            <guid>https://velog.io/@thisisyjin/231229-TIL</guid>
            <pubDate>Fri, 29 Dec 2023 07:13:09 GMT</pubDate>
            <description><![CDATA[<h2 id="refs--portals">Refs &amp; Portals</h2>
<h3 id="practice-usestate-ver">Practice (useState ver.)</h3>
<pre><code class="language-jsx">import { useState } from &quot;react&quot;;

export default function Player() {

  const [enteredPlayerName, setEnteredPlayerName] = useState(&#39;&#39;);
  const [submitted, setSubmitted] = useState(false);
  const handleChange = (e) =&gt; {
    setEnteredPlayerName(e.target.value)
  }

  const handleClick = () =&gt; {
    setSubmitted(true);
  }

  return (
    &lt;section id=&quot;player&quot;&gt;
      &lt;h2&gt;Welcome {submitted ? enteredPlayerName : &#39;unknown entity&#39;}&lt;/h2&gt;
      &lt;p&gt;
        &lt;input type=&quot;text&quot; value={enteredPlayerName} onChange={handleChange}/&gt;
        &lt;button onClick={handleClick}&gt;Set Name&lt;/button&gt;
      &lt;/p&gt;
    &lt;/section&gt;
  );
}
</code></pre>
<ul>
<li>위 코드에서의 문제점은, 클릭을 하고 나면 onChange 할 때마다 enteredPlayerName이 변하게 되고,
화면에서 리렌더링 된다는 점이다.</li>
</ul>
<h3 id="practice-ref-ver">Practice (Ref ver.)</h3>
<ul>
<li>Refs는 참조값을 의미.</li>
<li>그러나, 리액트가 특별한 방법으로 다루는 값.</li>
<li><code>useRef</code> Hook을 이용하여 생성 가능.</li>
</ul>
<p>Ref는 여러 기능을 하지만, 가장 많이 사용되는 용법은 JSX와 연결(DOM)하는 기능이다.</p>
<pre><code class="language-jsx">const playerName = useRef();

...

&lt;input ref={playerName} /&gt;</code></pre>
<ul>
<li>위 practice 코드를 ref를 이용한 코드로 변경해보자.</li>
</ul>
<pre><code class="language-jsx">import { useState, useRef } from &quot;react&quot;;

export default function Player() {
  const playerName = useRef();

  const [enteredPlayerName, setEnteredPlayerName] = useState(&#39;&#39;);

  const handleClick = () =&gt; {
    setEnteredPlayerName(playerName.current.value);
  }

  return (
    &lt;section id=&quot;player&quot;&gt;
      &lt;h2&gt;Welcome {enteredPlayerName ? enteredPlayerName : &#39;unknown entity&#39;}&lt;/h2&gt;
      &lt;p&gt;
        &lt;input ref={playerName} type=&quot;text&quot; /&gt;
        &lt;button onClick={handleClick}&gt;Set Name&lt;/button&gt;
      &lt;/p&gt;
    &lt;/section&gt;
  );
}</code></pre>
<blockquote>
<p>[주의] useRef 사용 시 주의할 점</p>
</blockquote>
<ul>
<li>만약, 위 코드에서 input의 값을 비우고 싶다면 어떻게 해야할까?</li>
<li><code>playerName.current.value = &#39;&#39;;</code>와 같이 DOM을 직접 조작하게 된다면, 
React의 &#39;선언형&#39; 코드 규칙에 위반된다.</li>
<li>즉, 경우에 따라 state 대신 ref로 값을 읽어들이는 것은 괜찮지만, DOM을 직접 조작해서는 안된다. (state를 사용해야 함)</li>
</ul>
<h3 id="-연산자"><code>??</code> 연산자</h3>
<ul>
<li><code>enteredPlayerName ? enteredPlayerName : &#39;unknown entity&#39;</code> 라는 코드를 줄여서 표현 가능.</li>
<li>널 병합 연산자 (<code>??</code>)</li>
</ul>
<ul>
<li>이는 왼쪽 피연산자가 null 또는 undefined 뿐만 아니라 falsy 값에 해당할 경우 오른쪽 피연산자를 반환하는 논리 연산자 OR (||)이 대조된다. </li>
<li><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing">참고 문서 - Nullish Coalescing Operator</a></li>
</ul>
<h3 id="ref">Ref</h3>
<ul>
<li>Ref의 값과 state의 가장 큰 차이점은 동작 방식이다.</li>
<li>state가 변화하면 화면이 리렌더링되지만,</li>
<li>ref 값이 변화하면 화면이 리렌더링되지 않는다.</li>
<li>즉, ref가 변화하면 컴포넌트 함수가 재실행되지 않는다!</li>
</ul>
<ul>
<li>state가 변화하면 (setState 함수에 의해서만 변경됨) -&gt; 컴포넌트 함수가 재실행됨 -&gt; render, 즉 JSX return문이 다시 실행됨</li>
<li>ref가 변화하면 -&gt; 컴포넌트 함수가 재실행되지 않음.</li>
</ul>
<blockquote>
<p>✅ <strong>State vs. Ref</strong></p>
<ul>
<li>UI에 바로 적용되어야 하는 값들을 <code>state</code>로 사용해야 함.</li>
<li>시스템 내부에서만 보이는 값이거나, UI에 바로 적용되어선 안되는 값은 <code>ref</code>로 사용해야 함.</li>
<li>단, DOM에 직접적인 접근이 필요한 경우에는 ref를 사용하면 안된다.</li>
</ul>
</blockquote>
<hr>
<h3 id="과제---time-challenge">과제 - Time Challenge</h3>
<ol>
<li><p>src/components/TimeChallenge.jsx 생성</p>
<pre><code class="language-jsx">export default function TimerChallenge ({ title, targetTime }) {

 return (
     &lt;section className=&quot;challenge&quot;&gt;
         &lt;h2&gt;{title}&lt;/h2&gt;
         &lt;p className=&quot;challenge-time&quot;&gt;
           {targetTime} Second{targetTime &gt; 1 ? &#39;s&#39; : &#39;&#39;}
         &lt;/p&gt;
         &lt;p&gt;
             &lt;button&gt;
                 Start Challenge
             &lt;/button&gt;
         &lt;/p&gt;
         &lt;p className=&quot;active&quot;&gt;
             Time is running... / Timer inactive
         &lt;/p&gt;
     &lt;/section&gt;
 )
}</code></pre>
</li>
<li><p>App.jsx 수정</p>
<pre><code class="language-jsx">import Player from &#39;./components/Player.jsx&#39;;
import TimerChallenge from &#39;./components/TimerChallenge.jsx&#39;;
</code></pre>
</li>
</ol>
<p>function App() {
  return (
    &lt;&gt;
      <Player />
      <div id="challenges">
        <TimerChallenge title="Easy" targetTime={1}/>
        <TimerChallenge title="Not Easy" targetTime={5}/>
        <TimerChallenge title="Getting Tough" targetTime={10}/>
        <TimerChallenge title="Pros Only" targetTime={15}/>
      </div>
    &lt;/&gt;
  );
}</p>
<p>export default App;</p>
<pre><code>
---

### Ref 사용 - 변수

- setTimeout 함수를 변수(let)로 지정해서 쓰는 경우
- 타이머가 정상적으로 작동하지 않음.
- state가 변하면 -&gt; 컴포넌트 함수가 재실행 되고 -&gt; `timer` 변수도 재할당되기 때문.
- 이럴 때, state 대신 ref를 사용하여 함수의 재실행을 막을 수 있음.

``` jsx
// 일반 변수를 사용한 경우 
import { useState } from &quot;react&quot;;

export default function TimerChallenge ({ title, targetTime }) {
    const [timerStarted, setTimerStarted] = useState(false);
    const [timerExpired, setTimerExpired] = useState(false);

    let timer;

    const handleStart = () =&gt; {
      timer = setTimeout(() =&gt; {
        setTimerExpired(true);
      }, targetTime * 1000);

      setTimerExpired(false);
      setTimerStarted(true);

    }

    const handleStop = () =&gt; {
        setTimerStarted(false); 
        clearTimeout(timer);
    }

    return (
        &lt;section className=&quot;challenge&quot;&gt;
            &lt;h2&gt;{title}&lt;/h2&gt;
            {
                timerExpired &amp;&amp; &lt;p&gt;You Lost!&lt;/p&gt;
            }
            &lt;p className=&quot;challenge-time&quot;&gt;
              {targetTime} Second{targetTime &gt; 1 ? &#39;s&#39; : &#39;&#39;}
            &lt;/p&gt;
            &lt;p&gt;
                &lt;button onClick={timerStarted ? handleStop : handleStart}&gt;
                    {timerStarted ? &#39;Stop Challenge&#39; : &#39;Start Challenge&#39;}
                &lt;/button&gt;
            &lt;/p&gt;
            &lt;p className={timerStarted ? &#39;active&#39; : undefined}&gt;
                {timerStarted ? &#39;Time is running...&#39; : &#39;Timer inactive&#39;}
            &lt;/p&gt;
        &lt;/section&gt;
    )
}</code></pre><h3 id="ref를-변수로서-사용">Ref를 변수로서 사용</h3>
<ul>
<li>timer을 useRef로 선언 후, 변수처럼 사용.</li>
<li>각 컴포넌트에 의존하는 ref를 가지게 됨.</li>
<li>state는 컴포넌트 함수가 재실행되면 값이 유실되지만, ref는 컴포넌트 함수가 재실행되어도 변함 없음.</li>
<li>ref를 변화시켜도 컴포넌트 함수의 재실행이 되지 않음.</li>
</ul>
<pre><code class="language-jsx">import { useState, useRef } from &quot;react&quot;;

export default function TimerChallenge ({ title, targetTime }) {
    const timer = useRef();
    const [timerStarted, setTimerStarted] = useState(false);
    const [timerExpired, setTimerExpired] = useState(false);

    const handleStart = () =&gt; {
      timer.current = setTimeout(() =&gt; {
        setTimerExpired(true);
      }, targetTime * 1000);

      setTimerExpired(false);
      setTimerStarted(true);
    }

    const handleStop = () =&gt; {
        setTimerStarted(false); 
        clearTimeout(timer.current);
    }

    return (
        &lt;section className=&quot;challenge&quot;&gt;
            &lt;h2&gt;{title}&lt;/h2&gt;
            {
                timerExpired &amp;&amp; &lt;p&gt;You Lost!&lt;/p&gt;
            }
            &lt;p className=&quot;challenge-time&quot;&gt;
              {targetTime} Second{targetTime &gt; 1 ? &#39;s&#39; : &#39;&#39;}
            &lt;/p&gt;
            &lt;p&gt;
                &lt;button onClick={timerStarted ? handleStop : handleStart}&gt;
                    {timerStarted ? &#39;Stop Challenge&#39; : &#39;Start Challenge&#39;}
                &lt;/button&gt;
            &lt;/p&gt;
            &lt;p className={timerStarted ? &#39;active&#39; : undefined}&gt;
                {timerStarted ? &#39;Time is running...&#39; : &#39;Timer inactive&#39;}
            &lt;/p&gt;
        &lt;/section&gt;
    )
}</code></pre>
<hr>
<h2 id="modal">Modal</h2>
<ul>
<li>결과 모달 Component 생성</li>
</ul>
<pre><code class="language-jsx">export default function ResultModal({result, targetTime}) {
  return &lt;dialog className=&quot;result-modal&quot;&gt;
    &lt;h2&gt;You {result}&lt;/h2&gt;
    &lt;p&gt;
        The target time was &lt;strong&gt;{targetTime}&lt;/strong&gt; seconds.
    &lt;/p&gt;
    &lt;p&gt;
        You stopped the timer with &lt;strong&gt;X seconds&lt;/strong&gt; left.
    &lt;/p&gt;
    &lt;form method=&quot;dialog&quot;&gt;
        &lt;button&gt;Close&lt;/button&gt;
    &lt;/form&gt;
  &lt;/dialog&gt;

}</code></pre>
<blockquote>
<p>HTML dialog Element</p>
</blockquote>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog">참고 문서 - MDN</a></li>
<li><code>open</code> 어트리뷰트를 지정해주어야 보이게 된다.</li>
</ul>
<pre><code class="language-jsx">// TimerChallenge.jsx
import { useState, useRef } from &quot;react&quot;;
import ResultModal from &quot;./ResultModal&quot;;

export default function TimerChallenge ({ title, targetTime }) {
    const timer = useRef();
    const [timerStarted, setTimerStarted] = useState(false);
    const [timerExpired, setTimerExpired] = useState(false);

    const handleStart = () =&gt; {
      timer.current = setTimeout(() =&gt; {
        setTimerExpired(true);
      }, targetTime * 1000);

      setTimerExpired(false);
      setTimerStarted(true);
    }

    const handleStop = () =&gt; {
        setTimerStarted(false); 
        clearTimeout(timer.current);
    }

    return (
        &lt;&gt;
            {timerExpired &amp;&amp; &lt;ResultModal targetTime={targetTime} result=&quot;lost&quot;/&gt;}
            &lt;section className=&quot;challenge&quot;&gt;
                &lt;h2&gt;{title}&lt;/h2&gt;
                &lt;p className=&quot;challenge-time&quot;&gt;
                {targetTime} Second{targetTime &gt; 1 ? &#39;s&#39; : &#39;&#39;}
                &lt;/p&gt;
                &lt;p&gt;
                    &lt;button onClick={timerStarted ? handleStop : handleStart}&gt;
                        {timerStarted ? &#39;Stop Challenge&#39; : &#39;Start Challenge&#39;}
                    &lt;/button&gt;
                &lt;/p&gt;
                &lt;p className={timerStarted ? &#39;active&#39; : undefined}&gt;
                    {timerStarted ? &#39;Time is running...&#39; : &#39;Timer inactive&#39;}
                &lt;/p&gt;
            &lt;/section&gt;
        &lt;/&gt;
    )
}</code></pre>
<pre><code class="language-jsx">// ResultModal.jsx
export default function ResultModal({result, targetTime}) {
  return &lt;dialog className=&quot;result-modal&quot; open&gt;
    &lt;h2&gt;You {result}&lt;/h2&gt;
    &lt;p&gt;
        The target time was &lt;strong&gt;{targetTime} seconds.&lt;/strong&gt; 
    &lt;/p&gt;
    &lt;p&gt;
        You stopped the timer with &lt;strong&gt;X seconds&lt;/strong&gt; left.
    &lt;/p&gt;
    &lt;form method=&quot;dialog&quot;&gt;
        &lt;button&gt;Close&lt;/button&gt;
    &lt;/form&gt;
  &lt;/dialog&gt;

}</code></pre>
<h3 id="ref-사용">Ref 사용</h3>
<ul>
<li>dialog 태그의 <code>backdrop</code> 어트리뷰트를 사용하기 위해서는 위와 같이 open인 상태에서는 접근 X.</li>
<li>이 때, <code>ref</code>를 이용해서 dialog DOM에 접근해서 사용 가능함.</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/showModal">showModal Method</a>를 사용하여 보이게 함.</li>
<li>showModal로 dialog를 열여주면, backdrop(내장 기능) 활용 가능.</li>
</ul>
<ul>
<li>아래와 같이 props로 ref를 다른 컴포넌트에 전달하는 것은 불가능</li>
</ul>
<pre><code class="language-jsx">import { useState, useRef } from &quot;react&quot;;
import ResultModal from &quot;./ResultModal&quot;;

export default function TimerChallenge ({ title, targetTime }) {
    const timer = useRef();
    const dialog = useRef();
    const [timerStarted, setTimerStarted] = useState(false);
    const [timerExpired, setTimerExpired] = useState(false);

    const handleStart = () =&gt; {
      timer.current = setTimeout(() =&gt; {
        setTimerExpired(true);
        dialog.current.showModal(); // ✅ dialog.showModal Method
      }, targetTime * 1000);

      setTimerExpired(false);
      setTimerStarted(true);
    }

    const handleStop = () =&gt; {
        setTimerStarted(false); 
        clearTimeout(timer.current);
    }

    return (
        &lt;&gt;
            {timerExpired &amp;&amp; &lt;ResultModal ref={dialog} targetTime={targetTime} result=&quot;lost&quot;/&gt;}
            &lt;section className=&quot;challenge&quot;&gt;
                &lt;h2&gt;{title}&lt;/h2&gt;
                &lt;p className=&quot;challenge-time&quot;&gt;
                {targetTime} Second{targetTime &gt; 1 ? &#39;s&#39; : &#39;&#39;}
                &lt;/p&gt;
                &lt;p&gt;
                    &lt;button onClick={timerStarted ? handleStop : handleStart}&gt;
                        {timerStarted ? &#39;Stop Challenge&#39; : &#39;Start Challenge&#39;}
                    &lt;/button&gt;
                &lt;/p&gt;
                &lt;p className={timerStarted ? &#39;active&#39; : undefined}&gt;
                    {timerStarted ? &#39;Time is running...&#39; : &#39;Timer inactive&#39;}
                &lt;/p&gt;
            &lt;/section&gt;
        &lt;/&gt;
    )
}</code></pre>
<h3 id="forwardref">forwardRef</h3>
<ul>
<li>컴포넌트 함수를 감싸는 함수.</li>
<li>자식 컴포넌트에 ref를 넘겨주려면, 자식 컴포넌트를 forwardRef()로 감싸줘야 사용 가능함.</li>
<li>함수의 두번째 인자로 <code>ref</code>를 받음. (부모에서 ref로 넘겨준 값)</li>
</ul>
<pre><code class="language-jsx">import { forwardRef } from &quot;react&quot;;

const ResultModal = React.forwardRef(({result, targetTime}, ref) =&gt; {
  return &lt;dialog ref={ref} className=&quot;result-modal&quot;&gt;
    &lt;h2&gt;You {result}&lt;/h2&gt;
    &lt;p&gt;
        The target time was &lt;strong&gt;{targetTime} seconds.&lt;/strong&gt; 
    &lt;/p&gt;
    &lt;p&gt;
        You stopped the timer with &lt;strong&gt;X seconds&lt;/strong&gt; left.
    &lt;/p&gt;
    &lt;form method=&quot;dialog&quot;&gt;
        &lt;button&gt;Close&lt;/button&gt;
    &lt;/form&gt;
  &lt;/dialog&gt;
})

export default ResultModal;</code></pre>
<hr>
<h3 id="useimperativehandle-hook"><code>useImperativeHandle</code> Hook</h3>
<blockquote>
<p>useImperativeHandle</p>
</blockquote>
<ul>
<li>child component의 상태 변경을 parent component에서 하는 경우</li>
<li>child component의 핸들러를 parent component에서 호출해야 하는 경우</li>
<li>자식 컴포넌트에서는 React.forwardRef로 부모 컴포넌트로부터 ref를 전달받아야 함.</li>
<li><blockquote>
<p><a href="https://velog.io/@jay/useImperativeHandle-%EB%96%A0%EB%A8%B9%EC%97%AC%EB%93%9C%EB%A6%BD%EB%8B%88%EB%8B%A4">참고 문서</a></p>
</blockquote>
</li>
</ul>
<ul>
<li>프로퍼티, 메서드 정의</li>
<li>해당 컴포넌트에서 바깥으로 이동</li>
<li>보통은 props를 사용하는 경우가 많지만, 컴포넌트를 안전하고 재사용성있게 만들기 위해 사용.</li>
</ul>
<pre><code class="language-jsx">// ResultModal.jsx (Child Component)
import {forwardRef, useRef, useImperativeHandle} from &quot;react&quot;;

const ResultModal = React.forwardRef(({result, targetTime}, ref) =&gt; {
  const dialog = useRef();

  // ✅ useImperativeHandle 
  useImperativeHandle(ref, () =&gt; {
    return {
        open() {
            dialog.current.showModal();
        }
    };
  });

  return &lt;dialog ref={ref} className=&quot;result-modal&quot;&gt;
    &lt;h2&gt;You {result}&lt;/h2&gt;
    &lt;p&gt;
        The target time was &lt;strong&gt;{targetTime} seconds.&lt;/strong&gt; 
    &lt;/p&gt;
    &lt;p&gt;
        You stopped the timer with &lt;strong&gt;X seconds&lt;/strong&gt; left.
    &lt;/p&gt;
    &lt;form method=&quot;dialog&quot;&gt;
        &lt;button&gt;Close&lt;/button&gt;
    &lt;/form&gt;
  &lt;/dialog&gt;
})

export default ResultModal;</code></pre>
<p>자식 컴포넌트에서 <code>useImperativeHandle(ref, () =&gt; {})</code> 를 해주면
부모 컴포넌트에서 해당 객체를 참고하여 자식 컴포넌트의 상태를 변경할 수 있음.</p>
<pre><code class="language-jsx">// TimerChallenge.jsx (Parent Component)

...

// 기존에는 dialog.current.showModal()로 사용했음
dialog.current.open();</code></pre>
<hr>
<h2 id="practice-project-고도화">Practice Project 고도화</h2>
<ul>
<li>성공 시 점수 계산해서 보여주기 (남은 시간 대비)</li>
<li>setInterval 함수를 사용해야 함.</li>
</ul>
<pre><code class="language-jsx">// TimerChallenge.jsx
import { useState, useRef } from &quot;react&quot;;
import ResultModal from &quot;./ResultModal&quot;;

export default function TimerChallenge ({ title, targetTime }) {
    const dialog = useRef();

    const [timeRemaining, setTimeRemaining] = useState(targetTime * 1000);

    const timerIsActive = timeRemaining &gt; 0 &amp;&amp; timeRemaining &lt; targetTime * 1000; 

    if (timeRemaining &lt;= 0) { // 시간 초과 시 
        clearInterval(timer.current);
        setTimeRemaining(targetTime * 1000);
        dialog.current.open();
    }

    const handleStart = () =&gt; {
      timer.current = setInterval(() =&gt; {
        setTimeRemaining(prevTimeRemaining =&gt; prevTimeRemaining - 10);
      }, 10);
    }

    const handleStop = () =&gt; {
        setTimerStarted(false); 
        clearInterval(timer.current);
    }

    return (
        &lt;&gt;
            &lt;ResultModal ref={dialog} targetTime={targetTime} result=&quot;lost&quot;/&gt;
            &lt;section className=&quot;challenge&quot;&gt;
                &lt;h2&gt;{title}&lt;/h2&gt;
                &lt;p className=&quot;challenge-time&quot;&gt;
                {targetTime} Second{targetTime &gt; 1 ? &#39;s&#39; : &#39;&#39;}
                &lt;/p&gt;
                &lt;p&gt;
                    &lt;button onClick={timerIsActive ? handleStop : handleStart}&gt;
                        {timerIsActive ? &#39;Stop Challenge&#39; : &#39;Start Challenge&#39;}
                    &lt;/button&gt;
                &lt;/p&gt;
                &lt;p className={timerIsActive ? &#39;active&#39; : undefined}&gt;
                    {timerIsActive ? &#39;Time is running...&#39; : &#39;Timer inactive&#39;}
                &lt;/p&gt;
            &lt;/section&gt;
        &lt;/&gt;
    )
}</code></pre>
<pre><code class="language-jsx">// ResultModal.jsx
import {forwardRef, useRef, useImperativeHandle} from &quot;react&quot;;

const ResultModal = React.forwardRef(({ targetTime, timeRemaining }, ref) =&gt; {
  const dialog = useRef();
  const userLost = timeRemaining &lt;= 0; 
  const formattedRemainingTime = (timeRemaining / 1000).toFixed(2);

  useImperativeHandle(ref, () =&gt; {
    return {
        open() {
            dialog.current.showModal();
        }
    };
  });

  return &lt;dialog ref={ref} className=&quot;result-modal&quot;&gt;
    {userLost &amp;&amp; &lt;h2&gt;You Lost&lt;/h2&gt;}
    &lt;p&gt;
        The target time was &lt;strong&gt;{targetTime} seconds.&lt;/strong&gt; 
    &lt;/p&gt;
    &lt;p&gt;
        You stopped the timer with &lt;strong&gt;{formattedRemainingTime} seconds&lt;/strong&gt; left.
    &lt;/p&gt;
    &lt;form method=&quot;dialog&quot;&gt;
        &lt;button&gt;Close&lt;/button&gt;
    &lt;/form&gt;
  &lt;/dialog&gt;
})

export default ResultModal;</code></pre>
<h3 id="error">Error</h3>
<ul>
<li>지금 코드에서는 시간이 초과되면, clearInterval 하고나서 timeRemaining을 리셋시킨다.</li>
<li>그래서 lost 모달이 뜨지 않고, 1초가 남았다는 모달이 뜨게 된다.</li>
<li>모달의 Close를 눌렀을 때 시간이 리셋되도록 수정해야 한다.</li>
</ul>
<pre><code class="language-jsx">if (timeRemaining &lt;= 0) {
  clearInterval(timer.current);
  setTimeRemaining(targetTime * 1000); // Error Logic! (리셋을 여가서 해주면 X)
  dialog.current.open();
}</code></pre>
<pre><code class="language-jsx">const handleReset = () =&gt; {
  setTimeRemaining(targetTime * 1000); 
}

...


  &lt;ResultModal 
    ref={dialog} 
    targetTime={targetTime} 
    timeRemaining={timeRemaining} 
    onReset={handleReset}
    /&gt;
</code></pre>
<pre><code class="language-jsx">import {forwardRef, useRef, useImperativeHandle} from &quot;react&quot;;

const ResultModal = React.forwardRef(({ targetTime, timeRemaining, onReset }, ref) =&gt; {
  const dialog = useRef();
  const userLost = timeRemaining &lt;= 0; 
  const formattedRemainingTime = (timeRemaining / 1000).toFixed(2);

  useImperativeHandle(ref, () =&gt; {
    return {
        open() {
            dialog.current.showModal();
        }
    };
  });

  return &lt;dialog ref={ref} className=&quot;result-modal&quot;&gt;
    {userLost &amp;&amp; &lt;h2&gt;You Lost&lt;/h2&gt;}
    &lt;p&gt;
        The target time was &lt;strong&gt;{targetTime} seconds.&lt;/strong&gt; 
    &lt;/p&gt;
    &lt;p&gt;
        You stopped the timer with &lt;strong&gt;{formattedRemainingTime} seconds&lt;/strong&gt; left.
    &lt;/p&gt;
    &lt;form method=&quot;dialog&quot; onSubmit={onReset}&gt;
        &lt;button&gt;Close&lt;/button&gt;
    &lt;/form&gt;
  &lt;/dialog&gt;
})

export default ResultModal;</code></pre>
<h3 id="점수-계산">점수 계산</h3>
<ul>
<li>남은 시간이 적을수록 높은 점수가 계산되도록 고도화.</li>
</ul>
<pre><code class="language-jsx">// ResultModal.jsx

import {forwardRef, useRef, useImperativeHandle} from &quot;react&quot;;

const ResultModal = React.forwardRef(({ targetTime, timeRemaining, onReset }, ref) =&gt; {
  const dialog = useRef();
  const userLost = timeRemaining &lt;= 0; 
  const formattedRemainingTime = (timeRemaining / 1000).toFixed(2);
  const score = Math.round((1 - timeRemaining / (targetTime * 1000)) * 100);

  useImperativeHandle(ref, () =&gt; {
    return {
        open() {
            dialog.current.showModal();
        }
    };
  });

  return &lt;dialog ref={ref} className=&quot;result-modal&quot;&gt;
    {userLost &amp;&amp; &lt;h2&gt;You Lost&lt;/h2&gt;}
    {!userLost &amp;&amp; &lt;h2&gt;Your Score: {score}&lt;/h2&gt;}
    &lt;p&gt;
        The target time was &lt;strong&gt;{targetTime} seconds.&lt;/strong&gt; 
    &lt;/p&gt;
    &lt;p&gt;
        You stopped the timer with &lt;strong&gt;{formattedRemainingTime} seconds&lt;/strong&gt; left.
    &lt;/p&gt;
    &lt;form method=&quot;dialog&quot; onSubmit={onReset}&gt;
        &lt;button&gt;Close&lt;/button&gt;
    &lt;/form&gt;
  &lt;/dialog&gt;
})

export default ResultModal;</code></pre>
<h3 id="dialog-모달을-esc로-닫기">dialog 모달을 ESC로 닫기</h3>
<ul>
<li>버튼으로 닫으면 onReset이 트리거됨.</li>
<li>그러나, 버튼이 아닌 ESC 키로 닫을 때도 <code>onReset</code>이 트리거되도록 설정해야 함.</li>
<li>dialog Element의 onClose에 <code>onReset</code>을 바인딩해주면 됨.<pre><code class="language-jsx">&lt;dialog ref={dialog} onClose={onReset}&gt;
... 
&lt;/dialog&gt;</code></pre>
</li>
</ul>
<hr>
<h2 id="portals">Portals</h2>
<ul>
<li>Modal은 마크업상 최상단에 App과 별개로 있어야 함.</li>
<li>JSX 를 <code>createPortal</code>로 감싸서 두번째 인자로 HTML Element를 넣어주면 됨.</li>
</ul>
<h3 id="createportal">createPortal</h3>
<ul>
<li>react가 아닌 react-dom의 메서드임.</li>
<li>두 번째 인자로 HTML Element를 넘겨받음. (index.html 내에 존재하는 요소여야 함.)</li>
</ul>
<pre><code class="language-jsx">import { forwardRef, useRef, useImperativeHandle } from &quot;react&quot;;
import { createPortal } from &#39;react-dom&#39;;

const ResultModal = React.forwardRef(({ targetTime, timeRemaining, onReset }, ref) =&gt; {

  ...

  return createPortal(&lt;dialog ref={ref} className=&quot;result-modal&quot; onClose={onReset}&gt;
    {userLost &amp;&amp; &lt;h2&gt;You Lost&lt;/h2&gt;}
    {!userLost &amp;&amp; &lt;h2&gt;Your Score: {score}&lt;/h2&gt;}
    &lt;p&gt;
        The target time was &lt;strong&gt;{targetTime} seconds.&lt;/strong&gt; 
    &lt;/p&gt;
    &lt;p&gt;
        You stopped the timer with &lt;strong&gt;{formattedRemainingTime} seconds&lt;/strong&gt; left.
    &lt;/p&gt;
    &lt;form method=&quot;dialog&quot; onSubmit={onReset}&gt;
        &lt;button&gt;Close&lt;/button&gt;
    &lt;/form&gt;
  &lt;/dialog&gt;, document.getElementById(&#39;modal&#39;))
})

export default ResultModal;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[231224 TIL]]></title>
            <link>https://velog.io/@thisisyjin/231224-TIL</link>
            <guid>https://velog.io/@thisisyjin/231224-TIL</guid>
            <pubDate>Thu, 28 Dec 2023 13:18:43 GMT</pubDate>
            <description><![CDATA[<h2 id="ch-7-list-rendering">CH 7. List Rendering</h2>
<h3 id="연습-데이터-목록-렌더링하기"><strong>연습: 데이터 목록 렌더링하기</strong></h3>
<pre><code class="language-jsx">import React from &#39;react&#39;;

import Todo from &#39;./Todo&#39;;
import &#39;./styles.css&#39;;

const DUMMY_TODOS = [
    &#39;Learn React&#39;,
    &#39;Practice React&#39;,
    &#39;Profit!&#39;
];

// don&#39;t change the Component name &quot;App&quot;
export default function App() {
    return (
        &lt;ul&gt;
          Todo: Output todos
        &lt;/ul&gt;
    );
}</code></pre>
<p>“할 일 목록” 웹 앱을 만든다고 해봅시다. 여러분이 해야 할 작업은 <strong>더미 할 일 항목의 목록을 동적으로 출력</strong>하는 것입니다. 이 작업에 대해 <code>Todo</code> 컴포넌트가 준비되어 있지만, 할 일 텍스트를 <strong>수신하고 출력</strong>하기 위해서는 여전히 코드를 좀 더 추가해야 합니다.</p>
<p>더 정확히 말하면, <code>App</code>컴포넌트에서 제공된 <code>DUMMY_TODOS</code> 배열(변경해서는 안 됩니다!)을 JSX 요소 목록(정확히 말하면 <code>&lt;Todo&gt;</code> 요소)으로 변환해야 합니다. 모든 할 일 컴포넌트 항목은 <code>text</code>라는 prop을 통해 할 일 텍스트를 수신하고 출력해야 합니다.</p>
<blockquote>
<p><strong>내 코드 (PASS)</strong></p>
</blockquote>
<pre><code class="language-jsx">// App.js
import React from &#39;react&#39;;

import Todo from &#39;./Todo&#39;;
import &#39;./styles.css&#39;;

const DUMMY_TODOS = [
    &#39;Learn React&#39;,
    &#39;Practice React&#39;,
    &#39;Profit!&#39;
];

// don&#39;t change the Component name &quot;App&quot;
export default function App() {
    return (
        &lt;ul&gt;
          {DUMMY_TODOS.map((todo) =&gt; (&lt;Todo text={todo}/&gt;))}
        &lt;/ul&gt;
    );
}

// Todo.js
import React from &#39;react&#39;;

export default function Todo({text}) {
    return &lt;li&gt;{text}&lt;/li&gt;;
}</code></pre>
<hr>
<h3 id="함수형-setstate">함수형 setState</h3>
<ul>
<li>배열인 state를 업데이트할 때, 함수형 setState 사용</li>
</ul>
<pre><code class="language-jsx">setUserList(prevState =&gt; ([...prevState, userData]);</code></pre>
<ul>
<li>함수형 setState를 통해 prevState를 기본으로 사용할 수 있다.</li>
<li>이렇게 하는 것이 직접 state를 setState 안에서 사용하는 것 보다 안전하다.</li>
</ul>
<hr>
<h3 id="keys">keys</h3>
<ul>
<li>list로 반환하는 경우 (map 메서드 사용 시)</li>
<li>만약 array가 변경된 경우, 렌더링 성능 측면에서 Key가 필요. (warning message)</li>
<li>새로운 아이템이 추가돤 경우 리액트에서는 아이템을 어떻게 정렬할지 알 수 없음. (다 비슷해보이기 때문)<ul>
<li>따라서 <code>key</code>를 지정해서 고유한 인덱스 역할을 할 수 있게 해줘야 함.</li>
</ul>
</li>
<li>만약 리스트에 고유 id값이 있다면 그것을 사용하고, 그렇지 않다면 index를 사용할 것. (권장 X)</li>
</ul>
<hr>
<h3 id="과제-년도-필터링">[과제] 년도 필터링</h3>
<ul>
<li>처음엔 state를 하나 추가하는 방법을 생각했지만, 굳이 state로 관리하지 않아도</li>
<li>기존 state(productList)에서 filter 메서드만 해주면 되기 때문에 변수로 선언하여 props로 전달함.</li>
</ul>
<pre><code class="language-jsx">const App = () =&gt; {
  const [productList, setProductList] = useState([]);
  const [filteredYear, setFilteredYear] = useState(2020); // default;

  const onChangeFilter = (selectedOption) =&gt; {
    setFilteredYear(selectedOption);
    filteredProductList(
  }

  const filteredProductList = productList.filter((product) =&gt; product.date.getFullYear().toString() === filteredYear);

  return (
    &lt;YearFilter filteredYear={filteredYear} onChangeFilter={onChangeFilter} /&gt;
    &lt;ProductList productList={filteredProductList} filteredYear={filteredYear}/&gt;
  );
}</code></pre>
<pre><code class="language-jsx">const YearFilter = ({filteredYear, onChangeFilter}) =&gt; {

  const handleChange = (e) =&gt; {
    onChangeFilter(e.target.value);
  }

  return (
    &lt;select value={filteredYear} onChange={handleChange}&gt;
            ...
        &lt;/select&gt;
  );
}</code></pre>
<pre><code class="language-jsx">const ProductList = ({productList}) =&gt; {

  return (
    {
            productList.map((product) =&gt; (
                &lt;div key={product.id}&gt;
                    &lt;div&gt;{product.title}&lt;/div&gt;
                    &lt;div&gt;{product.price}&lt;/div&gt;
                    &lt;div&gt;{product.date}&lt;/div&gt;
            &lt;/div&gt;
            ));
    }
  );
}</code></pre>
<hr>
<h3 id="jsx-변수-저장">JSX 변수 저장</h3>
<blockquote>
<p><strong>방법 1</strong></p>
</blockquote>
<ul>
<li>JSX 코드가 복잡해질 수 있음.</li>
</ul>
<pre><code class="language-jsx">const Component = () =&gt; {

...

return (
      {filteredData.length === 0 &amp;&amp; &lt;p&gt;No Data Found.&lt;/p&gt;}
        {filteredData.length &gt; 0 &amp;&amp; filteredData.map((data) =&gt; (...))}
    )
}</code></pre>
<blockquote>
<p><strong>방법 2</strong></p>
</blockquote>
<ul>
<li>JSX를 let 변수로 저장해서 조건문으로 재할당 가능하게</li>
<li>이 방법으로 작성 시 가독성 좋게 표현 가능하다. (취향대로 하면 OK)</li>
</ul>
<pre><code class="language-jsx">const Component = () =&gt; {

...

let dataContent = &lt;p&gt;No Data Found.&lt;/p&gt;;
if (filteredData.length &gt; 0) {
  dataContent = filteredData.map((data) =&gt; (...));
}

return (
      {dataContent}
    )
}</code></pre>
<hr>
<h3 id="연습-조건에-따라-콘텐츠-출력하기"><strong>연습: 조건에 따라 콘텐츠 출력하기</strong></h3>
<p>사용자가 위험한 작업을 수행하려고 할 때 <strong>경고를 표시</strong>하는 웹 앱을 만들고 있다고 가정해 보겠습니다.</p>
<p>따라서, 여러분이 해야 할 작업은 사용자가 특정 버튼을 <strong>클릭</strong>하면 <strong>조건에 따라</strong> 경고 상자를 표시하는 것입니다. 경고 대화 상자 안에는 사용자가 경고를 해제(즉, 화면에서 경고 상자를 제거)할 수 있는 다른 버튼이 있습니다.</p>
<p><code>&lt;button&gt;</code>을 클릭하지 않은 경우, 완성된 앱에는 이 UI가 표시되어야 합니다:</p>
<p>!<a href="https://img-b.udemycdn.com/redactor/raw/coding_exercise_instructions/2023-01-25_19-46-44-9e8772f6a8078c9d8f887588a31cdce2.png">https://img-b.udemycdn.com/redactor/raw/coding_exercise_instructions/2023-01-25_19-46-44-9e8772f6a8078c9d8f887588a31cdce2.png</a></p>
<p>버튼을 클릭한 경우에는 이 UI가 표시됩니다:</p>
<p>!<a href="https://img-b.udemycdn.com/redactor/raw/coding_exercise_instructions/2023-01-25_19-46-45-ea9890f0b7de91072bbb89b9cf4585bd.png">https://img-b.udemycdn.com/redactor/raw/coding_exercise_instructions/2023-01-25_19-46-45-ea9890f0b7de91072bbb89b9cf4585bd.png</a></p>
<p>“진행” 버튼을 클릭하면 경고 상자가 다시 삭제되어야 합니다:</p>
<p>!<a href="https://img-b.udemycdn.com/redactor/raw/coding_exercise_instructions/2023-01-25_19-46-45-422a664c8ab05139df000f6945d859ff.png">https://img-b.udemycdn.com/redactor/raw/coding_exercise_instructions/2023-01-25_19-46-45-422a664c8ab05139df000f6945d859ff.png</a></p>
<p>이 작업에서는, 시작 코드에 있는 두 <code>&lt;button&gt;</code>요소의 클릭에 모두 반응해야 합니다. 두 번째 버튼은 <code>id=&quot;alert&quot;</code>가 있는 <code>&lt;div&gt;</code> 외부에 <code>&lt;div id=&quot;alert&quot;&gt;</code>(및 모든 콘텐츠)를 표시해야 합니다. <div> 내부의 버튼은 다시 숨겨야 합니다(즉, DOM에서 제거해야 합니다).</p>
<p>삼항 표현식을 사용할지, 조건에 따라 표시되는 JSX 코드를 변수에 저장할지는 사용자가 결정할 수 있습니다.</p>
<p><em>중요. Udemy 코드 편집기에서는 <code>useState()</code>를 쓰면 에러가 생길 수 있으므로 <code>useState()</code>가 아니라 <code>React.useState()</code>을 사용해야 합니다!</em></p>
<blockquote>
<p><strong>내 코드 (PASS)</strong></p>
</blockquote>
<pre><code class="language-jsx">import React from &#39;react&#39;;

// don&#39;t change the Component name &quot;App&quot;
export default function App() {
    const [isAlertOpen, SetIsAlertOpen] = React.useState(false);

    const openAlert = () =&gt; {
        SetIsAlertOpen(true);
    }
    const closeAlert = () =&gt; {
        SetIsAlertOpen(false);
    }
    return (
      &lt;div&gt;
      {
          isAlertOpen &amp;&amp; ( 
          &lt;div id=&quot;alert&quot;&gt;
          &lt;h2&gt;Are you sure?&lt;/h2&gt;
          &lt;p&gt;These changes can&#39;t be reverted!&lt;/p&gt;
          &lt;button onClick={closeAlert}&gt;Proceed&lt;/button&gt;
        &lt;/div&gt;
        )
      }
        &lt;button onClick={openAlert}&gt;Delete&lt;/button&gt;
      &lt;/div&gt;    
    );
}</code></pre>
<hr>
<h3 id="과제---조건부-컨텐츠">과제 - 조건부 컨텐츠</h3>
<pre><code class="language-jsx">const dataPoints = [
    {
      id: 1,
      value: 293103,
    },
    {
      id: 2,
      value: 23525434,
    },
    {
      id: 3,
      value: 123232,
    },
    {
      id: 4,
      value: 999999999,
    }
];

const dataPointValues = dataPoints.map(dataPoint =&gt; dataPoint.value);
const maxValue = Math.max(...dataPointValues);</code></pre>
<hr>
<h3 id="동적-스타일링">동적 스타일링</h3>
<pre><code class="language-jsx">let barFillHeight = value / maxValue * 100 + &#39;%&#39;;

...

&lt;div style={{height: barFillHeight}}&gt;&lt;/div&gt;</code></pre>
<ul>
<li>나는 평소 styled-components에서 props로 넘겨줬었다.</li>
<li>기본 style 어트리뷰트로도 가능하다.</li>
</ul>
<hr>
<h3 id="연도별-데이터-월별로-정렬">연도별 데이터 월별로 정렬</h3>
<pre><code class="language-jsx">const chartDataPoints = [
  {label: &#39;Jan&#39;, value: 0},
  {label: &#39;Feb&#39;, value: 0},
  {label: &#39;Mar&#39;, value: 0},
  {label: &#39;Apr&#39;, value: 0},
  {label: &#39;May&#39;, value: 0},
  {label: &#39;Jun&#39;, value: 0},
  {label: &#39;Jul&#39;, value: 0}
  {label: &#39;Aug&#39;, value: 0},
  {label: &#39;Sep&#39;, value: 0},
  {label: &#39;Oct&#39;, value: 0},
  {label: &#39;Nov&#39;, value: 0},
  {label: &#39;Dev&#39;, value: 0}  
];

// productDatas = [{date: ..., value: ... , ...}, {date: ..., value: ... , ...}, ... ]
for (const productData in productDatas) {
  const month = productData.date.getMonth();
  chartDataPoints[month].value += productData.value; // 합계
}</code></pre>
<h2 id="ch-8-react-styling">CH 8. React Styling</h2>
<h3 id="동적-인라인-스타일">동적 인라인 스타일</h3>
<pre><code class="language-jsx">const [isActive, setIsActive] = useState(false);

...
&lt;div style={{color: isActive ? &#39;red&#39; : &#39;black&#39;}}&gt;&lt;/div&gt;</code></pre>
<h3 id="연습-동적-스타일"><strong>연습: 동적 스타일</strong></h3>
<p>여러분이 해야 할 작업은 제공된 리액트 앱의 <code>&lt;p&gt;Style me&lt;/p&gt;</code> 요소에 스타일(<code>color: red</code>)을 <strong>동적으로</strong> 적용하는 것입니다.</p>
<p><code>&lt;button&gt;</code>이 처음 클릭될 때 스타일은 <strong>인라인 스타일</strong>(즉, <code>style</code>속성/prop을 통해) 적용되어야 합니다. 버튼을 다시 클릭하면, 스타일은 초기 스타일인 <code>color: white</code>로 다시 전환되어야 합니다.</p>
<p>버튼이 이 두 스타일 사이를 전환하는지 확인합니다(<code>color: white</code> &lt;=&gt; <code>color: red</code>).</p>
<p>버튼을 클릭하기 전, 완성된 앱의 모습은 다음과 같습니다:</p>
<p>!<a href="https://img-b.udemycdn.com/redactor/raw/coding_exercise_instructions/2023-01-25_20-03-23-02fdb3ce682409032876037dd28b863f.png">https://img-b.udemycdn.com/redactor/raw/coding_exercise_instructions/2023-01-25_20-03-23-02fdb3ce682409032876037dd28b863f.png</a></p>
<p>버튼을 클릭한 후의 모습은 다음과 같습니다:</p>
<p>!<a href="https://img-b.udemycdn.com/redactor/raw/coding_exercise_instructions/2023-01-25_20-03-24-7c8e4ce80ee28148c63cfe980f251d9d.png">https://img-b.udemycdn.com/redactor/raw/coding_exercise_instructions/2023-01-25_20-03-24-7c8e4ce80ee28148c63cfe980f251d9d.png</a></p>
<p>버튼이 다시 클릭되었을 때의 모습은 다음과 같습니다:</p>
<p>!<a href="https://img-b.udemycdn.com/redactor/raw/coding_exercise_instructions/2023-01-25_20-03-24-eb267922d64394c0023ce989aa377ec2.png">https://img-b.udemycdn.com/redactor/raw/coding_exercise_instructions/2023-01-25_20-03-24-eb267922d64394c0023ce989aa377ec2.png</a></p>
<blockquote>
<p><strong>내 코드 (PASS)</strong></p>
</blockquote>
<pre><code class="language-jsx">import React from &#39;react&#39;;

// don&#39;t change the Component name &quot;App&quot;
export default function App() {
    const [isToggle, setIsToggle] = React.useState(false);
    const handleClick = () =&gt; {
        setIsToggle(prev =&gt; !prev);
    }
    return (
        &lt;div&gt;
            &lt;p style={{color: isToggle ? &#39;red&#39; : &#39;white&#39;}}&gt;Style me!&lt;/p&gt;
            &lt;button onClick={handleClick}&gt;Toggle style&lt;/button&gt;
        &lt;/div&gt;
    );
}</code></pre>
<hr>
<h3 id="연습-동적-css-클래스"><strong>연습: 동적 CSS 클래스</strong></h3>
<p>여러분이 해야 할 작업은 제공된 리액트 앱의 <code>&lt;p&gt;Style me&lt;/p&gt;</code> 요소에 <strong>CSS 클래스</strong>(<code>active</code>)를 <strong>동적으로</strong> 적용하는 것입니다. 이 작업은 이전 코딩 연습과 매우 유사하지만, 인라인 스타일이 아닌 CSS 클래스를 동적으로 설정합니다.</p>
<p>버튼을 처음 클릭할 때 스타일은 <strong>CSS 클래스</strong>로(즉, <code>className</code>속성/prop을 통해) 적용되어야 합니다. <code>&lt;button&gt;</code>을 다시 클릭하면 모든 CSS 클래스가 <code>&lt;p&gt;</code> 요소에서 제거되어야 합니다(이 상태도 초기 상태여야 합니다).</p>
<p>버튼이 이 두 가지 스타일 사이를 전환하는지 확인합니다(CSS 클래스 없음 &lt;=&gt; <code>active</code>CSS 클래스).</p>
<p>버튼을 클릭하기 전의 완성된 앱의 모습은 다음과 같습니다:</p>
<p>!<a href="https://img-b.udemycdn.com/redactor/raw/coding_exercise_instructions/2023-01-25_20-18-40-a84ea52cbc1fe11c38ab9fec8263bfe4.png">https://img-b.udemycdn.com/redactor/raw/coding_exercise_instructions/2023-01-25_20-18-40-a84ea52cbc1fe11c38ab9fec8263bfe4.png</a></p>
<p>버튼을 클릭한 후의 모습은 다음과 같습니다:</p>
<p>!<a href="https://img-b.udemycdn.com/redactor/raw/coding_exercise_instructions/2023-01-25_20-18-41-d38af590ee33c2badd29e3ec5e1f2965.png">https://img-b.udemycdn.com/redactor/raw/coding_exercise_instructions/2023-01-25_20-18-41-d38af590ee33c2badd29e3ec5e1f2965.png</a></p>
<p>버튼을 다시 클릭했을 때의 모습은 다음과 같습니다:</p>
<p>!<a href="https://img-b.udemycdn.com/redactor/raw/coding_exercise_instructions/2023-01-25_20-18-41-0a0b6c139e2be5bbb63d032e210309d1.png">https://img-b.udemycdn.com/redactor/raw/coding_exercise_instructions/2023-01-25_20-18-41-0a0b6c139e2be5bbb63d032e210309d1.png</a></p>
<blockquote>
<p><strong>내 코드 (PASS)</strong></p>
</blockquote>
<pre><code class="language-jsx">import React from &#39;react&#39;;

// don&#39;t change the Component name &quot;App&quot;
export default function App() {
    const [isActive, setIsActive] = React.useState(false)
    const handleClick = () =&gt; {
        setIsActive(prev =&gt; !prev);
    }
    return (
        &lt;div&gt;
            &lt;p className={isActive &amp;&amp; &#39;active&#39;}&gt;Style me!&lt;/p&gt;
            &lt;button onClick={handleClick}&gt;Toggle style&lt;/button&gt;
        &lt;/div&gt;
    );
}</code></pre>
<hr>
<h3 id="styled-components">Styled-Components</h3>
<ol>
<li>동적 Props</li>
</ol>
<pre><code class="language-jsx">
const StyledButton = styled.button`
  color: ${props =&gt; props.active ? &#39;red&#39; : &#39;black&#39;}
`

&lt;StyledButton active={isActive}&gt;버튼&lt;/StyledButton&gt;</code></pre>
<ol>
<li>Media Query</li>
</ol>
<pre><code class="language-jsx">const Button = styled.button`
  @media (min-width: 768px) {
    width: auto;
  }
`;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[231223 TIL]]></title>
            <link>https://velog.io/@thisisyjin/231223-TIL</link>
            <guid>https://velog.io/@thisisyjin/231223-TIL</guid>
            <pubDate>Thu, 28 Dec 2023 13:18:11 GMT</pubDate>
            <description><![CDATA[<h1 id="231223-til">231223 TIL</h1>
<h2 id="section-6-state-event-object">Section 6. State, Event Object</h2>
<h3 id="1-공유-handler-함수">1. 공유 handler 함수</h3>
<ul>
<li>여러 Input의 changeHandler 함수 생성 방법</li>
</ul>
<pre><code class="language-jsx">import { useState } from &quot;react&quot;;

export default function App() {
  const [values, setValues] = useState({ userName: &quot;&quot;, id: &quot;&quot;, age: &quot;&quot; });
  const handleChange = (e) =&gt; {
    const { value, name } = e.target;
    setValues({ ...values, [name]: value });
  };

  const handleSubmit = (e) =&gt; {
    e.preventDefault();
    console.log(values);
  };
  return (
    &lt;div className=&quot;App&quot;&gt;
      &lt;form onSubmit={handleSubmit}&gt;
        &lt;input type=&quot;text&quot; name=&quot;userName&quot; onChange={handleChange} /&gt;
        &lt;input type=&quot;text&quot; name=&quot;id&quot; onChange={handleChange} /&gt;
        &lt;input type=&quot;text&quot; name=&quot;age&quot; onChange={handleChange} /&gt;
        &lt;button&gt;Submit&lt;/button&gt;
      &lt;/form&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<pre><code>import { useState } from &quot;react&quot;;

export default function App() {
  const [values, setValues] = useState({ userName: &quot;&quot;, id: &quot;&quot;, age: &quot;&quot; });
  const handleChange = (value, name) =&gt; {
    setValues((prev) =&gt; ({ ...prev, [name]: value }));
  };

  const handleSubmit = (e) =&gt; {
    e.preventDefault();
    console.log(values);
  };

  return (
    &lt;div className=&quot;App&quot;&gt;
      &lt;input type=&quot;text&quot; onChange={(e) =&gt; handleChange(e.target.value, &quot;userName&quot;)} /&gt;
      &lt;input type=&quot;text&quot; onChange={(e) =&gt; handleChange(e.target.value, &quot;id&quot;)} /&gt;
      &lt;input type=&quot;text&quot; onChange={(e) =&gt; handleChange(e.target.value, &quot;age&quot;)} /&gt;
      &lt;button type=&quot;submit&quot; onClick={handleSubmit}&gt;
        Submit
      &lt;/button&gt;
    &lt;/div&gt;
  );
}</code></pre><hr>
<h3 id="2-자식-부모-컴포넌트-통신">2. 자식-부모 컴포넌트 통신</h3>
<ul>
<li>부모에서 자식에게 <code>state</code>를 <code>props</code>로 전달.</li>
<li>부모 → 자식</li>
</ul>
<pre><code class="language-jsx">// Parent Component
...
const saveDataHandler = (enteredData) =&gt; {
  const data = {
    ...enteredData,
    sequence: Math.random().toString()
  };
  console.log(data);
}</code></pre>
<hr>
<h3 id="3-파생계산된-state">3. 파생/계산된 State</h3>
<ul>
<li>만약 selectValue라는 state가 바뀔 때 마다 description이 바뀌는 경우라면?</li>
<li>description을 state로 관리하기보다는 일반 값으로 변경하고 할당(=) 해주자.<ul>
<li>selectValue state가 변경 → 리렌더링 → description도 변경됨.</li>
</ul>
</li>
<li>이와 같은 방법이 권장된다.</li>
</ul>
<hr>
<h3 id="4-component">4. Component</h3>
<ul>
<li>Stateful Component<ul>
<li>내부 state가 존재</li>
</ul>
</li>
<li>Stateless Component<ul>
<li>내부 state는 없음. (props는 존재할 수 있음)</li>
</ul>
</li>
</ul>
<hr>
<h3 id="5-event-handler">5. Event Handler</h3>
<ul>
<li><p>리액트에서 수동으로 이벤트 리스너(addEventListener)을 추가할 경우,</p>
<p>  이는 명령형 코드이기에 React 컴포넌트 외부의 일부 함수를 트리거하므로, 컴포넌트의 state와 상호작용 X</p>
</li>
<li><p>&lt;중요&gt; 리액트는 선언형 코드이다!</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[231220 TIL]]></title>
            <link>https://velog.io/@thisisyjin/231220-TIL</link>
            <guid>https://velog.io/@thisisyjin/231220-TIL</guid>
            <pubDate>Wed, 20 Dec 2023 05:45:23 GMT</pubDate>
            <description><![CDATA[<h1 id="react-project">React Project</h1>
<h2 id="firebase-연결">Firebase 연결</h2>
<ol>
<li>파이어베이스 어플리케이션 생성</li>
<li>firebase.js 생성 후 SDK 등록</li>
</ol>
<hr>
<h2 id="인증auth-기능-구현">인증(Auth) 기능 구현</h2>
<ul>
<li>Google, Facebook, Apple, Twitter, Github, MS 등 많은 로그인 방법이 존재.</li>
<li>이번에는 Password Authentication 을 사용.<ul>
<li><code>createUserWithEmailAndPassword</code></li>
</ul>
</li>
</ul>
<h3 id="회원가입">회원가입</h3>
<pre><code class="language-jsx">// SignUp.jsx
import { getAuth } from &#39;firebase/auth&#39;;

const SignUp = () =&gt; {
  const navigate = useNavigate();
  const [firebaseError, setFirebaseError] = useState(&quot;&quot;);

  const auth = getAuth();
  const handleSignupAndLogin = (email, password) =&gt; {
    createUserWithEmailAndPassword(auth, email, password)
    .then((user) =&gt; {
      // 리덕스 스토어에 저장

      navigate(&#39;/&#39;);
    })
    .catch(error =&gt; {
      return error &amp;&amp; setFirebaseError(&quot;이메일 또는 패스워드가 일치하지 않습니다.&quot;);
    })
  }

  return (
    &lt;Form
      title={&quot;가입하기&quot;}
      getDataForm={handleSignupAndLogin}
      firebaseError={firebaseError}
      /&gt;
  )

}  </code></pre>
<pre><code class="language-jsx">// Form.jsx

const Form = ({title, getDataForm, firebaseError}) =&gt; {
  const { register, handleSubmit, formState:{ errors }, reset } = useForm({
    mode: &#39;onChange&#39;
  });

  const onSubmit = ({email, password}) =&gt; {
    getDataForm(email, password);
    reset(); // input 비워줌
  }
}

...

{firebaseError &amp;&amp; &lt;span&gt;{firebaseError}&lt;/span&gt;}
</code></pre>
<h3 id="로그인">로그인</h3>
<pre><code class="language-jsx">const SignIn = () =&gt; {
  const navigate = useNavigate();
  const [firebaseError, setFirebaseError] = useState(&quot;&quot;);

  const auth = getAuth();
  const handleLogin = (email, password) =&gt; {
    signInWithEmailAndPassword(auth, email, password)
    .then(user =&gt; {
      navigate(&#39;/&#39;)
    })
    .catch(error =&gt; {
      return error &amp;&amp; setFirebaseError(&quot;이메일 또는 패스워드가 일치하지 않습니다.&quot;);
    })
  }

    return (
    &lt;Form
      title={&quot;로그인&quot;}
      getDataForm={handleLogin}
      firebaseError={firebaseError}
      /&gt;
  )
}</code></pre>
<hr>
<h2 id="redux-setting">Redux Setting</h2>
<ol>
<li><p>store에 slice.js 파일 생성
/store/user/user.slice.js,
/store/order/order.slice.js,
/store/categories/categories.slice.js,
/store/cart/cart.slice.js,
/store/products/product.slice.js,
/store/products/products.slice.js 파일 생성.</p>
</li>
<li><p>메인 스토어 - index.js 생성</p>
<pre><code class="language-jsx">// store/index.js
export const store = configureStore({
reducer: {
 userSlice,
 ... // 추후 추가
}
});</code></pre>
</li>
</ol>
<h3 id="userslice">userSlice</h3>
<ul>
<li>user.slice.js</li>
</ul>
<pre><code class="language-jsx">// store/user/user.slice.js

const initialState = localStorage.getItem(&quot;user&quot;) 
? JSON.parse(localStorage.getItem(&quot;user&quot;) || &quot;&quot;) 
: {
  email: &quot;&quot;,
  token: &quot;&quot;,
  id: &quot;&quot;
};

export const userSlice = createSlice({
  name: &quot;user&quot;,
  initialState,
  reducers: {
    setUser: (state, action) =&gt; {
      state.email = action.payload.email;
      state.token = action.payload.token;
      state.id = action.payload.id;

      localStorage.setItem(&quot;user&quot;, JSON.stringify(state));
    },
    removeUser: (state) =&gt; {
      state.email = &quot;&quot;;
      state.token = &quot;&quot;;
      state.id = &quot;&quot;;

      localStorage.setItem(&quot;user&quot;, JSON.stringify(state));
    }
  }
});

// 액션 생성자 (slice.actions) -&gt; dispatch(actionName)
export const { setUser, removeUser } = userSlice.actions;  
export default userSlice.reducer;</code></pre>
<h3 id="store-등록">Store 등록</h3>
<pre><code class="language-jsx">// main.jsx

import { Provider } from &#39;react-redux&#39;;
import { store } from &#39;./store&#39;;

... 

&lt;Provider store={store}&gt;
  &lt;App /&gt;
&lt;/Provider&gt;
</code></pre>
<hr>
<h3 id="dispatch">Dispatch</h3>
<ul>
<li>SignIn, Login 할 때, (setUser) 액션 디스패치 하고 navigate 하도록 </li>
</ul>
<pre><code class="language-jsx">// SignUp.jsx

import { setUser } from &#39;../../store/user/user.slice&#39;;
import { getAuth } from &#39;firebase/auth&#39;;

const SignUp = () =&gt; {
  const dispatch = useDispatch();
  const navigate = useNavigate();
  const [firebaseError, setFirebaseError] = useState(&quot;&quot;);

  const auth = getAuth();
  const handleSignupAndLogin = (email, password) =&gt; {
    createUserWithEmailAndPassword(auth, email, password)
    .then((userCredential) =&gt; {
      // 리덕스 스토어에 저장
      dispatch(setUser({
        email: userCredential.user.email,
        token: userCredential.user.refreshToken,
        id: userCredential.user.uid
      }))
      navigate(&#39;/&#39;);
    })
    .catch(error =&gt; {
      return error &amp;&amp; setFirebaseError(&quot;이메일 또는 패스워드가 일치하지 않습니다.&quot;);
    })
  }

  return (
    &lt;Form
      title={&quot;가입하기&quot;}
      getDataForm={handleSignupAndLogin}
      firebaseError={firebaseError}
      /&gt;
  );
}  </code></pre>
<hr>
<h3 id="폴더-구조">폴더 구조</h3>
<p>지금까지 나는 한 페이지를 구성하는 컴포넌트들은 Components 폴더에 넣었는데,
여기서는 pages/HomePage 폴더 안에 폴더를 추가해서 하는 방식이다.</p>
<p>추가로, 페이지 컴포넌트 자체도 폴더로 구분해서 index.jsx로 생성하는 것이 차이.</p>
<p><img src="https://velog.velcdn.com/images/thisisyjin/post/8b687a2f-4b67-4691-97a4-8f80b508e120/image.png" alt=""></p>
<hr>
<h3 id="useselector-usedispatch">useSelector, useDispatch</h3>
<pre><code class="language-jsx">const category = useSelector((state) =&gt; state.categoriesSlice); 
// state에는 여러 slice들이 다 들어있음. (userSlice, categoriesSlice, ...)</code></pre>
<pre><code class="language-jsx">const dispatch = useDispatch();

const getActiveCategory = () =&gt; {
  dispatch(setActiveCategory(name)); // action.payload로 바로 전달함 
}</code></pre>
<hr>
<h2 id="custom-hook-redux">Custom Hook (Redux)</h2>
<ul>
<li>추후 TypeScript 적용 시 수정 예정</li>
</ul>
<pre><code class="language-jsx">import { useDispatch } form &#39;react-redux&#39;;

const useAppDispatch = () = useDispatch();
const useAppSelector = useSelector;</code></pre>
<hr>
<h3 id="fake-store-api">Fake Store API</h3>
<p><a href="https://fakestoreapi.com/">https://fakestoreapi.com/</a></p>
<ul>
<li>상품 정보, 카테고리, 정렬 등</li>
<li>장바구니 등</li>
</ul>
<ul>
<li>CardList
Redux Action: fetchProducts (API 호출해서 정보 가져옴) -&gt; ProductsSlice</li>
</ul>
<h3 id="extrareducers">extraReducers</h3>
<ul>
<li>reducers 는 액션함수를 생성함과 동시에 해당 액션함수에 대응하는 역할을 함.</li>
<li>extraReducers 는 사용자가 slice reducer 내에서 액션함수에 접근할 수 있게하지만,</li>
<li>extraReducers 내에서 <strong>액션함수를 생성하지 않는다</strong>는 점이 reducers 프로퍼티와의 가장 큰 차이임.</li>
</ul>
<ul>
<li>Promise의 진행 상태에 따라서 리듀서를 실행할 수 있음.</li>
<li><blockquote>
<p>결론: slice reducer 에 맵핑된 내부 액션함수가 아닌, 외부의 액션을 참조하기 위해 사용.</p>
</blockquote>
</li>
</ul>
<pre><code class="language-jsx">import { createAsyncThunk, createSlice } from &quot;@reduxjs/toolkit&quot;;
import axios from &quot;axios&quot;;

export const fetchProducts = createAsyncThunk(
    &quot;products/fetchProducts&quot;,
    async (category: string, thunkAPI) =&gt; {
        try {
            let response;
            if (category) {
                response = await axios.get&lt;IProduct[]&gt;(`https://fakestoreapi.com/products/category/${category}`);
            } else {
                response = await axios.get&lt;IProduct[]&gt;(&quot;https://fakestoreapi.com/products&quot;);
            }

            return response.data; //payload
        } catch (error) {
            return thunkAPI.rejectWithValue(&quot;Error loading products&quot;);
        }

    }
)

...

const initialState: ProductsType = {
    products: [],
    isLoading: false,
    error: &quot;&quot;,
}

export const productsSlice = createSlice({
    name: &#39;products&#39;,
    initialState,
    reducers: {},
    extraReducers: (builder) =&gt; {
        builder
            .addCase(fetchProducts.pending, (state) =&gt; {
                state.isLoading = true;
            })
            .addCase(fetchProducts.fulfilled, (state, action) =&gt; {
                state.isLoading = false;
                state.products = action.payload;
            })
            .addCase(fetchProducts.rejected, (state, action) =&gt; {
                state.isLoading = false;
                state.error = action.payload as string;
            })
    }
})


export default productsSlice.reducer;</code></pre>
<ul>
<li>상태에 따라 (pending, fulfulled, rejected) state를 업데이트 할 수 있음.</li>
<li><a href="https://redux-toolkit.js.org/usage/usage-with-typescript#type-safety-with-extrareducers">참고 문서</a> </li>
</ul>
<hr>
<h3 id="react-loading-skeleton">React Loading Skeleton</h3>
<ul>
<li>react-loading-skeleton 라이브러리</li>
</ul>
<pre><code class="language-jsx">import &quot;react-loading-skeleton/dist/skeleton.css&quot;; 
import Skeleton from &#39;react-loading-skeleton&#39;;

...

{ isLoading &amp;&amp; &lt;Skeleton height={350} /&gt; }
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[231219 TIL]]></title>
            <link>https://velog.io/@thisisyjin/231219-TIL</link>
            <guid>https://velog.io/@thisisyjin/231219-TIL</guid>
            <pubDate>Tue, 19 Dec 2023 07:59:45 GMT</pubDate>
            <description><![CDATA[<h1 id="react-project">React Project</h1>
<blockquote>
<ul>
<li>쇼핑몰 사이트 프로젝트</li>
</ul>
</blockquote>
<h3 id="주요-기능">주요 기능</h3>
<ol>
<li>Firebase 로그인</li>
<li>장바구니</li>
<li>마이페이지 (주문 히스토리)</li>
<li>결제</li>
<li>로그아웃</li>
</ol>
<hr>
<h3 id="프로젝트-생성-vite">프로젝트 생성 (vite)</h3>
<pre><code class="language-bash">$ npm init vite

$ npm install</code></pre>
<h3 id="필요-패키지">필요 패키지</h3>
<ul>
<li>axios</li>
<li>react-router-dom</li>
<li>react-redux</li>
<li>@reduxjs/toolkit</li>
<li>sass</li>
<li>react-icons</li>
<li>react-loading-skeleton</li>
</ul>
<h3 id="폴더-구조">폴더 구조</h3>
<pre><code>src
├─ assets
├─ components
├─ pages
├─ hooks
└─ store</code></pre><hr>
<h2 id="router-설정">Router 설정</h2>
<pre><code class="language-jsx">import &#39;./App.css&#39;;
import { BrowserRouter, Route, Routes } from &#39;react-router-dom&#39;;
import Layout from &#39;./components/layout/Layout&#39;;
import HomePage from &#39;./pages/HomePage&#39;;
import DetailPage from &#39;./pages/DetailPage&#39;;
import CartPage from &#39;./pages/CartPage&#39;;
import LoginPage from &#39;./pages/LoginPage&#39;;
import RegisterPage from &#39;./pages/RegisterPage&#39;;
import OrderPage from &#39;./pages/OrderPage&#39;;
import NotFoundPage from &#39;./pages/NotFoundPage&#39;;

const App = () =&gt; {
  return (
    &lt;BrowserRouter&gt;
      &lt;Routes&gt;
        &lt;Route path=&quot;/&quot; element={&lt;Layout /&gt;}&gt;
          &lt;Route index element={&lt;HomePage /&gt;} /&gt;
          &lt;Route path=&quot;product/:id&quot; element={&lt;DetailPage /&gt;} /&gt;
          &lt;Route path=&quot;cart&quot; element={&lt;CartPage /&gt;} /&gt;
          &lt;Route path=&quot;login&quot; element={&lt;LoginPage /&gt;} /&gt;
          &lt;Route path=&quot;register&quot; element={&lt;RegisterPage /&gt;} /&gt;
          &lt;Route path=&quot;order&quot; element={&lt;OrderPage /&gt;} /&gt;
          &lt;Route path=&quot;*&quot; element={&lt;NotFoundPage /&gt;} /&gt;
        &lt;/Route&gt;
      &lt;/Routes&gt;
    &lt;/BrowserRouter&gt;
  );
};

export default App;</code></pre>
<ul>
<li>공통 레이아웃으로 사용되는 Layout 컴포넌트에는 반드시 <code>Outlet</code>을 넣어줘야 함.
(children을 보여주기 위해)</li>
</ul>
<hr>
<h3 id="sass">SASS</h3>
<ul>
<li>스타일링 관리에 용이한 CSS 확장 언어.</li>
<li>SASS -&gt; (전처리) -&gt; CSS -&gt; 웹사이트</li>
</ul>
<hr>
<h2 id="layout">Layout</h2>
<pre><code class="language-jsx">import styles from &#39;./Layout.module.scss&#39;;
import { Outlet } from &#39;react-router-dom&#39;;
import Header from &#39;../header/Header&#39;;
import Footer from &#39;../footer/Footer&#39;;

const Layout = () =&gt; {
  return (
    &lt;div className={styles.layout}&gt;
      &lt;Header /&gt;
      &lt;Outlet /&gt;
      &lt;Footer /&gt;
    &lt;/div&gt;
  );
};

export default Layout;</code></pre>
<h2 id="header">Header</h2>
<pre><code class="language-jsx">import styles from &#39;./Header.module.scss&#39;;
import { Link } from &#39;react-router-dom&#39;;
import Nav from &#39;./nav/Nav&#39;;

const Header = () =&gt; {
  return (
    &lt;div className={styles.header}&gt;
      &lt;div className=&quot;container&quot;&gt;
        &lt;div className={styles.header_wrapper}&gt;
          &lt;div className={styles.header_logo}&gt;
            &lt;Link to={&#39;/&#39;}&gt;
              &lt;h2&gt;Shop&lt;/h2&gt;
            &lt;/Link&gt;
          &lt;/div&gt;
          &lt;Nav /&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
};

export default Header;
</code></pre>
<hr>
<h2 id="react-hook-form">React Hook Form</h2>
<ul>
<li><a href="https://react-hook-form.com/">https://react-hook-form.com/</a></li>
</ul>
<ul>
<li>리렌더링 최소화</li>
<li>빠른 속도</li>
<li>최소한의 코드로 높은 효율</li>
</ul>
<h3 id="사용-예제">사용 예제</h3>
<ol>
<li>register = input에 이름 등록함. <code>{...register(&quot;name&quot;, validCheckObject)}</code></li>
<li>handleSubmit = form onSubmit에 등록</li>
<li>formState.errors = 에러 (유효성 검사) -&gt; errors.{name} 과 같이 나타남</li>
</ol>
<pre><code class="language-jsx">const { register, handleSubmit, formState: {errors}, reset } = useForm();

const onSubmit = ({email, password}) =&gt; {
  console.log(email, password);
}

// 유효성 검사
const userEmail = {
  required: &quot;필수 값입니다.&quot;,
  minLength: {
    value: 4,
    message: &quot;최소 4자입니다.&quot;
  },
  pattern: {
    value: /^~~~~~~$/gm,
    message: &#39;최소 8자, 영문 1자, 숫자 1자.&#39;
  }
}

const userPassword = {
   // 동일
}

&lt;form onSubmit={handleSubmit}&gt;
  &lt;input {...register(&quot;email&quot;, userEmail} /&gt;
  &lt;span&gt;{errors?.email &amp;&amp; errors.email.message}&lt;/span&gt;
  &lt;input {...register(&quot;password&quot;, userPassword} /&gt;
  &lt;span&gt;{errors.password &amp;&amp; errors.password.message}&lt;/span&gt;
&lt;/form&gt;
</code></pre>
<ul>
<li>블러 이벤트 발생 시 유효성 검사 체크하기<pre><code class="language-jsx">const { register, handleSubmit, formState: {errors}, reset } = useForm({
mode: &#39;onBlur&#39;
});</code></pre>
</li>
<li><blockquote>
<p>onBlur, onChange, onSubmit 3개가 있다. (기본값: onSubmit)</p>
</blockquote>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Programmers] [JavaScript] 최빈값 구하기]]></title>
            <link>https://velog.io/@thisisyjin/Programmers-JavaScript-%EC%B5%9C%EB%B9%88%EA%B0%92-%EA%B5%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@thisisyjin/Programmers-JavaScript-%EC%B5%9C%EB%B9%88%EA%B0%92-%EA%B5%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 19 Dec 2023 01:15:55 GMT</pubDate>
            <description><![CDATA[<h2 id="최빈값-구하기">최빈값 구하기</h2>
<hr>
<h3 id="문제-설명">문제 설명</h3>
<p>최빈값은 주어진 값 중에서 가장 자주 나오는 값을 의미합니다. 정수 배열 <code>array</code>가 매개변수로 주어질 때, 최빈값을 return 하도록 solution 함수를 완성해보세요. 최빈값이 여러 개면 -1을 return 합니다.</p>
<hr>
<h3 id="제한사항">제한사항</h3>
<ul>
<li>0 &lt; array의 길이 &lt; 100</li>
<li>0 ≤ array의 원소 &lt; 1000</li>
</ul>
<hr>
<h3 id="입출력-예">입출력 예</h3>
<p>입출력 예 #1</p>
<p>[1, 2, 3, 3, 3, 4]에서 1은 1개 2는 1개 3은 3개 4는 1개로 최빈값은 3입니다.</p>
<p>입출력 예 #2</p>
<p>[1, 1, 2, 2]에서 1은 2개 2는 2개로 최빈값이 1, 2입니다. 최빈값이 여러 개이므로 -1을 return 합니다.</p>
<p>입출력 예 #3</p>
<p>[1]에는 1만 있으므로 최빈값은 1입니다.</p>
<hr>
<h2 id="내-코드">내 코드</h2>
<ul>
<li>객체를 Map 처럼 사용하여 카운트</li>
<li>forEach 함수를 활용하여 모든 Key값을 0으로 초기화</li>
<li>Object.keys, values를 활용하여 배열로 구현</li>
</ul>
<pre><code class="language-js">function solution(array) {   
  let mapObj = {};
  array.forEach(number =&gt; {
    mapObj[number] = 0
  });

  array.forEach(number =&gt; {
    mapObj[number]++
  });

  const keyArr = Object.keys(mapObj);
  const valueArr = Object.values(mapObj)
  const maxValue = Math.max(...valueArr); 

  const count = valueArr.reduce((cnt, el) =&gt; cnt + (maxValue === el), 0)

  if (count &gt; 1) {
    return -1;
  } else {
    return parseInt(keyArr[valueArr.indexOf(maxValue)]);
  }
}</code></pre>
<hr>
<h2 id="실행-결과">실행 결과</h2>
<pre><code>테스트 1 〉    통과 (0.10ms, 33.4MB)
테스트 2 〉    통과 (0.20ms, 33.5MB)
테스트 3 〉    통과 (0.21ms, 33.4MB)
테스트 4 〉    통과 (0.20ms, 33.5MB)
테스트 5 〉    통과 (0.19ms, 33.5MB)
테스트 6 〉    통과 (0.18ms, 33.4MB)
테스트 7 〉    통과 (0.11ms, 33.4MB)
테스트 8 〉    통과 (0.17ms, 33.3MB)
테스트 9 〉    통과 (0.11ms, 33.5MB)
테스트 10 〉    통과 (0.10ms, 33.5MB)
테스트 11 〉    통과 (0.16ms, 33.5MB)
테스트 12 〉    통과 (0.19ms, 33.5MB)
테스트 13 〉    통과 (0.19ms, 33.5MB)
테스트 14 〉    통과 (0.28ms, 33.4MB)
테스트 15 〉    통과 (0.26ms, 33.4MB)
테스트 16 〉    통과 (0.22ms, 33.6MB)</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[231218 TIL (2)]]></title>
            <link>https://velog.io/@thisisyjin/231218-TIL-2</link>
            <guid>https://velog.io/@thisisyjin/231218-TIL-2</guid>
            <pubDate>Mon, 18 Dec 2023 13:46:05 GMT</pubDate>
            <description><![CDATA[<h1 id="redux">Redux</h1>
<ul>
<li>JavaScript를 위한 상태관리 라이브러리.</li>
</ul>
<h2 id="middleware-없이-카운터-제작">Middleware 없이 카운터 제작</h2>
<h3 id="프로젝트-생성">프로젝트 생성</h3>
<pre><code class="language-bash">$ npx create-react-app react-redux --template typescript</code></pre>
<pre><code class="language-bash">$ npm install redux --save</code></pre>
<h3 id="apptsx-수정">App.tsx 수정</h3>
<pre><code class="language-tsx">import React from &#39;react&#39;;
import logo from &#39;./logo.svg&#39;;
import &#39;./App.css&#39;;

function App() {
  return (
    &lt;div className=&quot;App&quot;&gt;
      Clicked: times
      &lt;button&gt;+&lt;/button&gt;
      &lt;button&gt;-&lt;/button&gt;
    &lt;/div&gt;
  );
}

export default App;</code></pre>
<h3 id="리듀서-작성">리듀서 작성</h3>
<p>src/reducers/index.tsx 생성</p>
<pre><code class="language-tsx">const counter = (state = 0, action: { type: string }) =&gt; {
    switch (action.type) {
        case &quot;INCREMENT&quot;:
            return state + 1;
        case &quot;DECREMENT&quot;:
            return state - 1;
        default:
            break;
    }
}

export default counter;</code></pre>
<hr>
<h2 id="createstore">CreateStore</h2>
<ul>
<li>앱의 전체 상태 트리를 보유하는 Redux 스토어 생성</li>
<li>하나의 앱에는 하나의 스토어만 존재해야 함.</li>
</ul>
<blockquote>
<p>store.getState()</p>
</blockquote>
<ul>
<li>애플리케이션의 현재 상태 트리를 반환합니다. 스토어의 리듀서가 마지막으로 반환한 값과 동일합니다.</li>
</ul>
<pre><code class="language-tsx">// index.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 counter from &#39;./reducers&#39;;
import { createStore } from &#39;redux&#39;;

const store = createStore(counter);

const root = ReactDOM.createRoot(
  document.getElementById(&#39;root&#39;) as HTMLElement
);
root.render(
  &lt;React.StrictMode&gt;
    &lt;App
      value={store.getState()}
      onIncrement={() =&gt; store.dispatch({ type: &#39;INCREMENT&#39; })}
      onDecrement={() =&gt; store.dispatch({ type: &#39;DECREMENT&#39; })}
    /&gt;
  &lt;/React.StrictMode&gt;
);
</code></pre>
<p>App 컴포넌트의 props로 해당 값들을 보내줌.</p>
<pre><code class="language-tsx">// App.tsx

import React from &#39;react&#39;;
import logo from &#39;./logo.svg&#39;;
import &#39;./App.css&#39;;

type Props = {
  value: number;
  onIncrement: () =&gt; void;
  onDecrement: () =&gt; void;
};

function App({ value, onIncrement, onDecrement }: Props) {
  return (
    &lt;div className=&quot;App&quot;&gt;
      Clicked: {value} times
      &lt;button onClick={onIncrement}&gt;+&lt;/button&gt;
      &lt;button onClick={onDecrement}&gt;-&lt;/button&gt;
    &lt;/div&gt;
  );
}

export default App;
</code></pre>
<ul>
<li>버튼 클릭 시, 해당 액션이 디스패치됨.</li>
<li>액션이 디스패치 되면, 리듀서 함수에 의해 action.type 에 맞게 state를 리턴함.</li>
</ul>
<h2 id="subscribe">Subscribe</h2>
<ul>
<li>store.subscribe()</li>
<li>render 함수를 선언하고, subscribe 함수의 params로 넣어줌.</li>
</ul>
<pre><code class="language-tsx">// App.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 counter from &#39;./reducers&#39;;
import { createStore } from &#39;redux&#39;;

const store = createStore(counter);

const render = () =&gt; {
  const root = ReactDOM.createRoot(
    document.getElementById(&#39;root&#39;) as HTMLElement
  );
  root.render(
    &lt;React.StrictMode&gt;
      &lt;App
        value={store.getState()}
        onIncrement={() =&gt; store.dispatch({ type: &#39;INCREMENT&#39; })}
        onDecrement={() =&gt; store.dispatch({ type: &#39;DECREMENT&#39; })}
      /&gt;
    &lt;/React.StrictMode&gt;
  );
};

render();
store.subscribe(render);</code></pre>
<hr>
<h2 id="combinereducer">combineReducer</h2>
<ul>
<li>하나의 리듀서(Root Reducer)을 생성해야 함.</li>
<li>여러개이 리듀서가 있는 경우, 하나의 리듀서로 합쳐주기 위해 combineReducer 메서드 사용.</li>
</ul>
<ul>
<li>리듀서 함수를 하나 더 생성해보자.</li>
<li>Todo를 추가하는 todos 리듀서 함수.<pre><code class="language-tsx">// reducers/todos.tsx
enum ActionType {
ADD_TODO = &#39;ADD_TODO&#39;,
DELETE_TODO = &#39;DELETE_TODO&#39;,
}
</code></pre>
</li>
</ul>
<p>interface Action {
  type: ActionType;
  text: string;
}</p>
<p>const todos = (state = [], action: Action) =&gt; {
  // Action.text 값을 받아옴 (Payload)
  switch (action.type) {
    case &#39;ADD_TODO&#39;:
      return [...state, action.text];
    default:
      return state;
  }
};</p>
<p>export default todos;</p>
<pre><code>
&gt; [참고] TypeScript의 enum
- Union(|)을 이용해도 좋음.
- enum을 사용해 리터럴의 타입과 값에 이름을 붙여서 코드의 가독성을 크게 높일 수 있음.
- enum은 객체이다. (단, 객체와 달리 enum의 속성은 변경할 수 없음.)

``` tsx
// reducers/index.tsx
import { combineReducers } from &#39;redux&#39;;
import counter from &#39;./counter&#39;;
import todos from &#39;./todos&#39;;

const rootReducer = combineReducers({
  counter,
  todos,
});

export default rootReducer;</code></pre><h3 id="createstore에-루트-리듀서-대체">createStore에 루트 리듀서 대체</h3>
<pre><code class="language-tsx">// index.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 { createStore } from &#39;redux&#39;;
import rootReducer from &#39;./reducers&#39;;

const store = createStore(rootReducer);

const render = () =&gt; {
  const root = ReactDOM.createRoot(
    document.getElementById(&#39;root&#39;) as HTMLElement
  );
  root.render(
    &lt;React.StrictMode&gt;
      &lt;App
        value={store.getState()}
        onIncrement={() =&gt; store.dispatch({ type: &#39;INCREMENT&#39; })}
        onDecrement={() =&gt; store.dispatch({ type: &#39;DECREMENT&#39; })}
      /&gt;
    &lt;/React.StrictMode&gt;
  );
};

render();
store.subscribe(render);</code></pre>
<hr>
<h2 id="redux-provider">Redux Provider</h2>
<ul>
<li>react-redux 라이브러리 설치 필요</li>
</ul>
<ol>
<li>index.tsx 수정</li>
</ol>
<ul>
<li>기존 store.subscribe(render)을 지우고</li>
<li>Provider 컴포넌트로 감싸줌.<pre><code class="language-tsx">// index.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 { createStore } from &#39;redux&#39;;
import rootReducer from &#39;./reducers&#39;;
import { Provider } from &#39;react-redux&#39;;
</code></pre>
</li>
</ul>
<p>const store = createStore(rootReducer);</p>
<p>const root = ReactDOM.createRoot(
  document.getElementById(&#39;root&#39;) as HTMLElement
);
root.render(
  &lt;React.StrictMode&gt;
    <Provider store={store}>
      &lt;App
        onIncrement={() =&gt; store.dispatch({ type: &#39;INCREMENT&#39; })}
        onDecrement={() =&gt; store.dispatch({ type: &#39;DECREMENT&#39; })}
      /&gt;
    </Provider>
  &lt;/React.StrictMode&gt;
);</p>
<pre><code>
2. App.tsx 수정
- 폼 추가 (todos)
- handleChange, addTodo 함수 작성 

&gt; 참고 - event 객체 
- `e: React.ChangeEvent&lt;HTMLInputElement&gt;`
- `e: FormEvent&lt;HTMLFormElement&gt;`

``` tsx
// App.tsx
import React, { ChangeEvent, FormEvent, useState } from &#39;react&#39;;
import logo from &#39;./logo.svg&#39;;
import &#39;./App.css&#39;;

type Props = {
  onIncrement: () =&gt; void;
  onDecrement: () =&gt; void;
};

function App({ onIncrement, onDecrement }: Props) {
  const [todoValue, setTodoValue] = useState(&#39;&#39;);

  const addTodo = (e: FormEvent&lt;HTMLFormElement&gt;) =&gt; {
    e.preventDefault();
    setTodoValue(&#39;&#39;);
  };

  const handleChange = (e: ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
    setTodoValue(e.target.value);
  };

  return (
    &lt;div className=&quot;App&quot;&gt;
      Clicked: times
      &lt;button onClick={onIncrement}&gt;+&lt;/button&gt;
      &lt;button onClick={onDecrement}&gt;-&lt;/button&gt;
      &lt;form onSubmit={addTodo}&gt;
        &lt;input type=&quot;text&quot; value={todoValue} onChange={handleChange} /&gt;
        &lt;input type=&quot;submit&quot; /&gt;
      &lt;/form&gt;
    &lt;/div&gt;
  );
}

export default App;
</code></pre><hr>
<h2 id="useselector--usedispatch">useSelector / useDispatch</h2>
<ul>
<li>useSelector = 스토어의 값 가져옴</li>
<li>useDispatch = 디스패치 함수 가져옴 (액션 디스패치)</li>
</ul>
<pre><code class="language-tsx">// App.tsx
const counter = useSelector((state) =&gt; state.counter);
// Error: &#39;state&#39;은(는) &#39;unknown&#39; 형식입니다.ts(18046)
</code></pre>
<ul>
<li>state가 unknown 타입임.</li>
<li><blockquote>
<p>rootReducer에서 타입을 생성해주면 해결됨.</p>
</blockquote>
</li>
<li><blockquote>
<p>즉, <code>state:RootState</code>와 같이 타입 지정을 해줘야 함.</p>
</blockquote>
</li>
</ul>
<blockquote>
<p>[참고] ReturnType</p>
</blockquote>
<ul>
<li><code>ReturnType&lt;T&gt;</code></li>
<li>함수 T의 반환 타입으로 정의한다. Parameters와 대칭되는 형태임.</li>
<li>참고 링크: <a href="https://typescript-kr.github.io/pages/utility-types.html#returntypet">https://typescript-kr.github.io/pages/utility-types.html#returntypet</a></li>
</ul>
<pre><code class="language-tsx">// reducer/index.tsx

export type RootState = ReturnType&lt;typeof rootReducer&gt;;
// rootReducer가 반환하는 타입. (ReturnType)                         </code></pre>
<pre><code class="language-tsx">// App.tsx
import { RootState } from &#39;./reducers&#39;;

const todos = useSelector((state: RootState) =&gt; state.todos);
const counter = useSelector((state: RootState) =&gt; state.counter);
</code></pre>
<p><img src="https://velog.velcdn.com/images/thisisyjin/post/d4b4ef37-5244-427d-88a4-1f46ca735908/image.png" alt=""></p>
<hr>
<h2 id="redux-middleware">Redux Middleware</h2>
<h3 id="로깅-미들웨어">로깅 미들웨어</h3>
<pre><code class="language-jsx">const loggerMiddleware = (store) =&gt; (next) =&gt; (action) =&gt; {
  console.log(store, action);
  next(action)
}

const middleware = applyMiddleware(loggerMiddleware);

const store = createStore(rootReducer, middleware); // 수정
</code></pre>
<hr>
<h2 id="redux-thunk">Redux Thunk</h2>
<ul>
<li>thunk는 &#39;일부 지연된 작업을 수행하는 코드 조각&#39;을 의미.</li>
<li>즉, 비동기 작업을 해야하는 경우 사용함. (서버에 요청을 보내서 데이터를 가져올 때)</li>
</ul>
<h3 id="posts-리듀서-생성">posts 리듀서 생성</h3>
<pre><code class="language-tsx">enum ActionType {
  FETCH_POSTS = &#39;FETCH_POSTS&#39;,
  DELETE_POSTS = &#39;DELETE_POSTS&#39;,
}

interface Post {
  userId: number;
  id: number;
  title: string;
}

interface Action {
  type: ActionType;
  payload: Post[];
}

const posts = (state = [], action: Action) =&gt; {
  switch (action.type) {
    case &#39;FETCH_POSTS&#39;:
      return [...state, ...action.payload];
    default:
      return state;
  }
};

export default posts;
</code></pre>
<ul>
<li>App.tsx에 미들웨어 부분 추가<pre><code class="language-tsx">// App.tsx
</code></pre>
</li>
</ul>
<p>useEffect(() =&gt; {
  dispatch(fetchPosts());
}, [dispatch]);</p>
<p>const fetchPosts = (): any =&gt; {
  return async function fetchPostsThunk(dispatch: any, getState: any) {
    const response = await axios.get(
      &#39;<a href="https://jsonplaceholder.typicode.com/posts&#39;">https://jsonplaceholder.typicode.com/posts&#39;</a>
    );
    dispatch({ type: &#39;FETCH_POSTS&#39;, text: todoValue });
  };
};</p>
<pre><code>
### Redux-thunk 적용

- Action은 객체여야 하는데, dispatch() 안에 함수가 들어가는 경우 에러가 발생한다.
- 즉, 비동기 함수 등을 디스패치 하려면 redux-thunk 같은 미들웨어 사용이 필요하다.

``` tsx
// index.tsx
import thunk from &#39;redux-thunk&#39;;

const middleware = applayMiddleware(thunk, loggerMiddleware); // thunk를 추가</code></pre><pre><code class="language-tsx">// App.tsx
const posts: Post[] = useSelector((state: RootState) =&gt; state.posts);
...

{posts.map((post, index) =&gt; &lt;li key={index}&gt;{post.title}&lt;/li&gt;}</code></pre>
<h3 id="폴더-정리">폴더 정리</h3>
<ul>
<li>실제로 Redux를 사용하는 경우, 한 곳에 코드를 모두 적지 않고</li>
<li>actions 라는 폴더에 액션 파일을 저장함.</li>
</ul>
<pre><code class="language-tsx">// actions/posts.tsx

import axios from &#39;axios&#39;;

const fetchPosts = (): any =&gt; {
  return async function fetchPostsThunk(dispatch: any, getState: any) {
    const response = await axios.get(
      &#39;https://jsonplaceholder.typicode.com/posts&#39;
    );
    dispatch({ type: &#39;FETCH_POSTS&#39;, text: response.data });
  };
};

export default fetchPosts;
</code></pre>
<h3 id="리팩토링">리팩토링</h3>
<pre><code class="language-tsx">import axios from &#39;axios&#39;;

export const fetchPosts = () =&gt; async (dispatch: any, getState: any) =&gt; {
  const response = await axios.get(
    &#39;https://jsonplaceholder.typicode.com/posts&#39;
  );
  dispatch({ type: &#39;FETCH_POSTS&#39;, text: response.data });
};
</code></pre>
<hr>
<h2 id="redux-toolkit">Redux Toolkit</h2>
<h3 id="프로젝트-생성-1">프로젝트 생성</h3>
<pre><code class="language-bash">$ npx create-react-app my-app --template redux-typescript </code></pre>
<ul>
<li>기본으로 @reduxjs/toolkit과 react-redux가 install 되어있음.</li>
</ul>
<blockquote>
<p>Redux Toolkit </p>
</blockquote>
<p><strong>1. React에 Redux 스토어 제공</strong></p>
<ul>
<li>Provider 컴포넌트<blockquote>
</blockquote>
</li>
<li><em>2. Redux State Slice 생성*</em></li>
<li>createSlice를 통해 슬라이스 생성</li>
<li>Initial State가 무엇인지, Slice 이름이 무엇인지, 어떻게 변경되는지를 선언.</li>
<li>Reducer 함수에서 변경 로직을 작성 가능함.</li>
<li><blockquote>
<p>immer 라이브러리를 쓰기 때문에 이전처럼 불변성때문에 머리아플 일 없다!</p>
</blockquote>
</li>
<li><em>3. Store에 Slice Reducer 생성*</em></li>
<li>슬라이스에서 리듀서 함수를 가져와서 스토어에 추가 (<code>configureStore</code>)</li>
<li>리듀서 Params 내부에 필드 정의하여 스토어에 리듀서 함수를 사용하여 state를 업데이트하도록.<blockquote>
</blockquote>
</li>
<li><em>4. Redux State 및 Actions 사용*</em></li>
<li>useSelector, useDispatch 사용</li>
</ul>
<hr>
<h2 id="redux-toolkit-apis">Redux Toolkit APIs</h2>
<h3 id="configurestore">configureStore</h3>
<ul>
<li>기존 createStore을 대체함.</li>
<li>참고 문서 : <a href="https://redux-toolkit.js.org/api/configureStore">https://redux-toolkit.js.org/api/configureStore</a></li>
</ul>
<h3 id="createaction">createAction</h3>
<ul>
<li>기존에는 액션을 생성할 때, 액션 타입과 생성자 함수를 분리하여 선언했었음.<pre><code class="language-tsx">const INCREMENT = &#39;counter/increment&#39;;  // type
</code></pre>
</li>
</ul>
<p>function increment(amount: number) {  // 생성자 함수
  return {
    type: INCREMENT,
    payload: amount
  }
}</p>
<p>const action = increment(10); // action.payload로 10을 넘겨줌</p>
<pre><code>- createAction을 사용하면 한 번에 처리 가능.
- type만 넣으면 자동으로 해당 type을 가진 action creater 함수를 생성함.
(생성된 함수를 호출 시 인수를 넣어준다면, payload로 들어가게 됨.)

``` tsx
import { createAction } from &#39;@reduxjs/toolkit&#39;;

const increment = createAction&lt;number&gt;(&#39;counter/increment&#39;);  // type, 생성자 함수
const action = increment(10);</code></pre><h3 id="createreducer">createReducer</h3>
<ul>
<li>switch 문을 통해 구현했던 reducer 함수를 좀 더 간편하게 구현 가능.</li>
<li>immer 라이브러리를 내장하여 불변성 유지됨.</li>
</ul>
<pre><code class="language-tsx">const counterReducer = createReducer(initialState, (builder) =&gt; {
  builder
    .addCase(increment, (state, action) =&gt; {
      state.value++
    })
    .addCase(decrement, (state, action) =&gt; {
      state.value--
    })
    .addCase(incrementByAmount, (state, action) =&gt; {
      state.value += action.payload
    })
})</code></pre>
<blockquote>
<p>[참고] builder은 무엇인가?</p>
</blockquote>
<ul>
<li>createReducer에서 액션 객체를 처리하기 위해 case reducer을 정의하는 방법이 두가지가 있다.</li>
</ul>
<ol>
<li>빌더 콜백 (Builder Callback)</li>
<li>맵 객체 (Map Object) 표기법.</li>
</ol>
<ul>
<li>동일하지만, 빌더 콜백이 TypeScript와의 호환성 때문에 더 선호된다.</li>
<li><code>builder.addCase(type, 생성자)</code> - 액션 타입과 정확히 맵핑되는 케이스 리듀서 추가</li>
<li>그 외에도 addMatcher (주어진 패턴과 일치하는지 체크 후 처리), addDefaultCase 등이 있다.</li>
</ul>
<h3 id="prepare-콜백-함수">Prepare 콜백 함수</h3>
<ul>
<li>액션 Contents 커스텀 가능.</li>
<li>예&gt; payload에 사용자 정의 값 추가</li>
</ul>
<pre><code class="language-tsx">import { createAction, nanoid } from &#39;@reduxjs/toolkit&#39;

const addTodo = createAction(&#39;todos/add&#39;, function prepate(text) {
  return {
    payload: {
      text,
      id: nanoid(),
      createdAt: new Date().toISOString()
    }
  }
})

console.log(addTodo(&#39;abc&#39;));

// { type: &#39;todos/add&#39;, payload: {text: &#39;abc&#39;, id: &#39;342413fgkskso&#39;, createdAt: &#39;2019-10-03~..&#39;}</code></pre>
<h3 id="createslice">createSlice</h3>
<ul>
<li>Redux Logic 작성</li>
<li>CreateAction + createReducer을 포함함.</li>
<li>InitialState, Slice 이름을 받아 -&gt; 액션 생성자와 타입을 자동으로 생성하는 함수.</li>
</ul>
<pre><code class="language-tsx">import { createSlice } from &#39;@reduxjs/toolkit&#39;;

const initialState = { value: 0 };

const counterSlice = createSlice({
  name: &#39;counter&#39;,
  initialState,
  reducers: {     // case reducer 함수들의 객체 
    increment(state) {   // type: counter/increment
      state.value++
    },
    decrement(state) {   // type: counter/decrement
      state.value--
    },
    incrementByAmount(state, action) {   // type: counter/incrementByAmount
      state.value += action.payload
    }
  }
})</code></pre>
<h3 id="extrareducers">extraReducers</h3>
<ul>
<li>createSlice가 생성한 action type 외의 다른 액션 타입에 응답 가능</li>
<li>즉, 외부 액션을 참조하기 위한 것.</li>
</ul>
<h3 id="createasyncthunk">createAsyncThunk</h3>
<ul>
<li>createAction의 비동기 버전.</li>
<li>createAction 에서는 type, action을 인수로 넣어주었지만,</li>
<li>createAsyncThunk 에서는 type, payloadCreator, options 순으로 넣어준다.</li>
</ul>
<pre><code class="language-jsx">function createAsyncThunk(type, payloadCreator, options)</code></pre>
<ul>
<li>여기서 type은 user/requestStatus 타입 -&gt; 액션 타입: pending, fulfilled, rejected</li>
<li>payloadCreator은 Promise를 반환하는 콜백함수.</li>
<li><blockquote>
<p>예&gt; 비동기 요청을 보낸 후 response.data를 리턴</p>
</blockquote>
</li>
</ul>
<blockquote>
<p><a href="https://redux-toolkit.js.org/api/createAsyncThunk">참고 문서</a></p>
</blockquote>
<h3 id="cancel">cancel</h3>
<ul>
<li><p>useEffect return 부분에 (컴포넌트 언마운트 시) <code>promise.abort()</code>를 실행하여</p>
</li>
<li><p>비동기 요청 도중 취소되도록 구현 가능</p>
</li>
<li><p>thunkName/rejected가 디스패치됨 (createAsyncThunk 참고)</p>
</li>
<li><p>abort 되었을 때, request도 취소되도록 하려면?</p>
</li>
<li><blockquote>
<p>new AbortController()</p>
</blockquote>
</li>
</ul>
<h3 id="abortcontrollersignal">abortController.signal</h3>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/AbortController/signal">mdn 문서</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[231218 TIL]]></title>
            <link>https://velog.io/@thisisyjin/231218-TIL</link>
            <guid>https://velog.io/@thisisyjin/231218-TIL</guid>
            <pubDate>Mon, 18 Dec 2023 05:47:00 GMT</pubDate>
            <description><![CDATA[<h1 id="reactjs">React.js</h1>
<h2 id="주문-사이트-project">주문 사이트 Project</h2>
<ul>
<li>백엔드 서버 run 시켜주고, Type 컴포넌트를 보면 axios.get으로 백엔드 API를 호출하고 있음.</li>
<li>orderType은 products, options 중 하나 (Orderpage/index.jsx 참고)</li>
<li>&#39;products&#39;이면 Products 컴포넌트를 보여주고, &#39;options&#39;면 Options 컴포넌트를 보여줌.</li>
</ul>
<pre><code class="language-jsx">import { useEffect, useState } from &#39;react&#39;;
import axios from &#39;axios&#39;;
import Products from &#39;./Products&#39;;
import Options from &#39;./Options&#39;;
import ErrorBanner from &#39;./ErrorBanner&#39;;

const Type = ({ orderType }) =&gt; {
  const [items, setItems] = useState([]);
  const [error, setError] = useState(false);

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

  const loadItems = async (orderType) =&gt; {
    try {
      const response = await axios.get(`http://localhost:4000/${orderType}`);
      setItems(response.data);
      console.log(response.data);
    } catch (error) {
      console.error(error);
    }
  };

  const ItemComponents = orderType === &#39;products&#39; ? Products : Options;

  const optionItems = items.map((item) =&gt; (
    &lt;ItemComponents
      key={item.name}
      name={item.name}
      imagePath={item.imagePath}
    /&gt;
  ));
  return (
    &lt;&gt;
      {optionItems}
      {error &amp;&amp; &lt;ErrorBanner message=&quot;에러가 발생하였습니다.&quot; /&gt;}
    &lt;/&gt;
  );
};

export default Type;</code></pre>
<ul>
<li>console.log(response.data)를 찍어보면 배열이 반환됨.</li>
<li>이제 Options 컴포넌트를 체크박스 + 라벨(name)로 수정해주면 됨.</li>
</ul>
<pre><code class="language-jsx">const Options = ({ name }) =&gt; {
  return (
    &lt;form&gt;
      &lt;input type=&quot;checkbox&quot; id={`${name} option`} /&gt;
      &lt;label htmlFor={`${name} option`}&gt;{name}&lt;/label&gt;
    &lt;/form&gt;
  );
};

export default Options;
</code></pre>
<blockquote>
<p>지금까지의 결과물 
<img src="https://velog.velcdn.com/images/thisisyjin/post/03e1f95c-3587-4178-9f2d-90cc0faf3f3f/image.png" alt=""></p>
</blockquote>
<hr>
<h2 id="context-api-사용">Context API 사용</h2>
<ul>
<li>여행 상품 수량을 올릴수록, 옵션을 추가할수록 priceState가 수정되어야 함.</li>
<li>변경된 priceState를 전달하기 위해서는 컴포넌트 여러개를 타고 불필요한 코드 반복이 됨.</li>
<li>이럴 때 Context API를 사용하자!</li>
</ul>
<ol>
<li>createContext 를 사용하여 컨텍스트 생성</li>
<li>사용할 부분에 (App 컴포넌트)를 컨텍스트의 Provider로 감싸줌</li>
<li>useContext로 value 가져와서 사용</li>
</ol>
<p> /src/context 폴더 생성 후, OrderContext.js 생성
 (createContext)</p>
<pre><code class="language-js">import { createContext } from &#39;react&#39;;

const OrderContext = createContext();</code></pre>
<p>index.js 수정 (Provider로 감싸줌)</p>
<pre><code class="language-js">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 Ordercontext from &#39;./context/OrderContext&#39;;

const root = ReactDOM.createRoot(document.getElementById(&#39;root&#39;));
root.render(
  &lt;React.StrictMode&gt;
    &lt;OrderContext.Provider&gt;
      &lt;App /&gt;
    &lt;/OrderContext.Provider&gt;
  &lt;/React.StrictMode&gt;
);
</code></pre>
<ul>
<li>OrderContext.js에서 아예 Provider 컴포넌트를 생성해서 임포트하기.<pre><code class="language-jsx">// OrderContext.js
import { createContext } from &#39;react&#39;;
</code></pre>
</li>
</ul>
<p>const OrderContext = createContext();</p>
<p>export function OrderContextProvider(props) {
  return &lt;OrderContext.Provider value&gt;{props.children}&lt;/OrderContext.Provider&gt;;
}</p>
<pre><code>
-&gt; props를 spread 연산자로 간단하게 표현 가능
``` jsx
return &lt;OrderContext.Provider value&gt;{props.children}&lt;/OrderContext.Provider&gt;;
// 같은 컴포넌트이다.
return &lt;OrderContext.Provider value {...props} /&gt;;</code></pre><ul>
<li>index.js에서는 OrderContextProvider를 불러와서 감싸주면 됨. (children으로 App이 들어감)<pre><code class="language-jsx">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 { OrderContextProvider } from &#39;./context/OrderContext&#39;;
</code></pre>
</li>
</ul>
<p>const root = ReactDOM.createRoot(document.getElementById(&#39;root&#39;));
root.render(
  &lt;React.StrictMode&gt;
    <OrderContextProvider>
      <App />
    </OrderContextProvider>
  &lt;/React.StrictMode&gt;
);</p>
<pre><code>
---

### Map 객체
- key - value 쌍으로 값을 저장.
- Map 객체에서 업데이트를 할 때는 `set` 메서드를 사용함.


- products, options에 대한 Map 객체를 생성하기.
- context의 값(value)을 설정할 때, Products, Options에 대한 값 모두를 가지고 있어야 함.
- 따라서 Map 객체를 이용하여 객체에 저장. (initialState)

``` js
// OrderContext.js
import { createContext, useMemo, useState } from &#39;react&#39;;

const OrderContext = createContext();

export function OrderContextProvider(props) {
  const [orderCounts, setOrderCounts] = useState({
    products: new Map(),
    options: new Map(),
  });

  const value = useMemo(() =&gt; {
    return [{ ...orderCounts }];
  }, [orderCounts]); // 리렌더링 최적화를 위해

  return &lt;OrderContext.Provider value={value} {...props} /&gt;;
}</code></pre><h3 id="state-업데이트-함수-작성">State 업데이트 함수 작성</h3>
<ul>
<li>orderCount를 업데이트하는 함수.<pre><code class="language-jsx">// OrderContext.js
import { createContext, useMemo, useState } from &#39;react&#39;;
</code></pre>
</li>
</ul>
<p>const OrderContext = createContext();</p>
<p>export function OrderContextProvider(props) {
  const [orderCounts, setOrderCounts] = useState({
    products: new Map(),
    options: new Map(),
  });</p>
<p>  const value = useMemo(() =&gt; {
    function updateItemCount(itemName, newItemCount, orderType) {
      const newOrderCounts = { ...orderCounts }; // 불변성을 위해 복사해둠
      const orderCountsMap = orderCounts[orderType]; // products, options
      orderCountsMap.set(itemName, parseInt(newItemCount)); // Map set
      setOrderCounts(newOrderCounts); // 
    }</p>
<pre><code>return [{ ...orderCounts }, updateItemCount];</code></pre><p>  }, [orderCounts]);</p>
<p>  return &lt;OrderContext.Provider value={value} {...props} /&gt;;
}</p>
<pre><code>
---

### 총 합계 업데이트 함수 작성

``` jsx
// OrderContext.js

const [totals, setTotals] = useState({
  products: 0,
  options: 0,
  total: 0
})

useEffect(() =&gt; {
  const productsTotal = calculateSubtotal(&quot;products&quot;, orderCounts);
  const optionsTotal = calculateSubtotal(&quot;options&quot;, orderCounts);
  const total = productsTotal + optionsTotal;
  setTotals({
    products: productsTotal,
    options: optionsTotal,
    total: total,
  });
}, [orderCounts]);</code></pre><pre><code class="language-jsx">const pricePerItem = {
  products: 1000,
  options: 500,
};

function calculateSubtotal(orderType, orderCounts) {
  let optionCount = 0;
  for (const count of orderCounts[orderType].values()) {
    // Object.prototype.values()는 객체를 반환한다.
    optionCount += count;
  }
  return optionCount * pricePerItem[orderType];
}</code></pre>
<blockquote>
<p>[참고] </p>
</blockquote>
<ul>
<li><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/for...of">for ... of 문</a></li>
<li>반복 가능 객체에 대해 반복 (개별 속성값에 대해 실행)</li>
</ul>
<hr>
<h3 id="context-value-가져오기">Context Value 가져오기</h3>
<ul>
<li>위에서 value 리턴을 배열로 하고있으므로 구조분해 할당으로 분리.</li>
</ul>
<p>type.jsx</p>
<pre><code class="language-jsx">import { useEffect, useState } from &#39;react&#39;;
import axios from &#39;axios&#39;;
import Products from &#39;./Products&#39;;
import Options from &#39;./Options&#39;;
import ErrorBanner from &#39;./ErrorBanner&#39;;
import { useContext } from &#39;react&#39;;
import { OrderContext } from &#39;../context/OrderContext&#39;;

const Type = ({ orderType }) =&gt; {
  const [items, setItems] = useState([]);
  const [error, setError] = useState(false);
  // useContext로 가져옴
  const [orderData, updateItemCount] = useContext(OrderContext);
  console.log(orderData, updateItemCount);

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

  const loadItems = async (orderType) =&gt; {
    try {
      const response = await axios.get(`http://localhost:4000/${orderType}`);
      setItems(response.data);
      console.log(response.data);
    } catch (error) {
      console.error(error);
      setError(true);
    }
  };

  const ItemComponents = orderType === &#39;products&#39; ? Products : Options;

  const optionItems = items.map((item) =&gt; (
    &lt;ItemComponents
      key={item.name}
      name={item.name}
      imagePath={item.imagePath}
    /&gt;
  ));
  return (
    &lt;&gt;
      {optionItems}
      {error &amp;&amp; &lt;ErrorBanner message=&quot;에러가 발생하였습니다.&quot; /&gt;}
    &lt;/&gt;
  );
};

export default Type;
</code></pre>
<hr>
<h2 id="총-가격total-보여주기">총 가격(total) 보여주기</h2>
<ul>
<li>여행 가격이 변하는 경우</li>
</ul>
<ol>
<li>상품의 개수가 변할 때</li>
<li>옵션의 체크가 변할 때</li>
</ol>
<p>-&gt; OrderCounts가 변경됨</p>
<pre><code class="language-jsx">// Type.jsx
...

&lt;ItemComponents
  key={item.name}
  name={item.name}
  imagePath={item.imagePath}
  updateItemCount={(itemName, newItemCount) =&gt; updateItemCount(itemName, newItemCount, orderType)}
  /&gt;</code></pre>
<p>-&gt; ItemComponents는 &#39;Products&#39; 또는 &#39;Options&#39; 이므로 각 컴포넌트에 props로 업데이트 함수를 넣어줌.</p>
<ol>
<li><p>Products 수정</p>
<pre><code class="language-jsx">// Products.jsx
const Products = ({ name, imagePath, updateItemCount }) =&gt; {
console.log(&#39;products : &#39;, name, imagePath);

// itemName은 name, newItemCount는 e.target.value
const handleChange = (e) =&gt; {
 const currentValue = e.target.value;
 updateItemCount(name, currentValue);
}

return (
 &lt;div style={{ textAlign: &#39;center&#39; }}&gt;
   &lt;img
     src={`http://localhost:4000/${imagePath}`}
     alt={`${name} product`}
     style={{ width: &#39;75%&#39; }}
   /&gt;
   &lt;form style={{ marginTop: &#39;10px&#39; }}&gt;
     &lt;label htmlFor=&quot;&quot; style={{ textAlign: &#39;right&#39; }}&gt;
       {name}
     &lt;/label&gt;
     &lt;input
       type=&quot;number&quot;
       name=&quot;quantity&quot;
       min=&quot;0&quot;
       defaultValue={0}
       style={{ marginLeft: &#39;70x&#39; }}
       onChange={handleChange}
     /&gt;
   &lt;/form&gt;
 &lt;/div&gt;
);
};
</code></pre>
</li>
</ol>
<p>export default Products;</p>
<pre><code>
2. Options 수정
- 체크박스의 경우, e.target.checked가 true면 1이고, false면 0이다.
``` jsx
// Options.jsx
const Options = ({ name, updateItemCount }) =&gt; {
  return (
    &lt;form&gt;
      &lt;input
        type=&quot;checkbox&quot;
        id={`${name} option`}
        onChange={(e) =&gt; {
          updateItemCount(name, e.target.checked ? 1 : 0);
        }}
      /&gt;
      &lt;label htmlFor={`${name} option`}&gt;{name}&lt;/label&gt;
    &lt;/form&gt;
  );
};

export default Options;
</code></pre><hr>
<h3 id="총-합계-표시">총 합계 표시</h3>
<pre><code class="language-jsx">// Type.jsx

return (
  &lt;div&gt;
    &lt;h2&gt;주문 종류&lt;/h2&gt;
    &lt;p&gt;하나의 가격&lt;/p&gt;
    &lt;p&gt;총 가격: {orderData.totals[orderType]}&lt;/p&gt;
    {optionItems}
    {error &amp;&amp; &lt;ErrorBanner message=&quot;에러가 발생하였습니다.&quot; /&gt;}
  &lt;/div&gt;
);</code></pre>
<ul>
<li>총 합계가 잘 변하는 것을 볼 수 있다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/thisisyjin/post/f13265b8-7d79-44e3-94ee-4f2a13c942d2/image.png" alt=""></p>
<hr>
<h2 id="step을-이용한-페이지-전환">step을 이용한 페이지 전환</h2>
<ul>
<li>Order Page = step 0</li>
<li>Summary Page = step 1</li>
<li>Complete Page = step 2</li>
</ul>
<pre><code class="language-jsx">// App.js
import OrderPage from &#39;./pages/OrderPage&#39;;
import SummaryPage from &#39;./pages/SummaryPage&#39;;
import CompletePage from &#39;./pages/CompletePage&#39;;
import { useState } from &#39;react&#39;;

function App() {
  const [step, setStep] = useState(0);
  return (
    &lt;div style={{ padding: &#39;4rem&#39; }}&gt;
      {step === 0 &amp;&amp; &lt;OrderPage setStep={setStep} /&gt;}
      {step === 1 &amp;&amp; &lt;SummaryPage setStep={setStep} /&gt;}
      {step === 2 &amp;&amp; &lt;CompletePage setStep={setStep} /&gt;}
    &lt;/div&gt;
  );
}

export default App;</code></pre>
<pre><code class="language-jsx">// OrderPage/index.js
import { useContext } from &#39;react&#39;;
import Type from &#39;../../components/Type&#39;;
import { OrderContext } from &#39;../../context/OrderContext&#39;;

const OrderPage = ({ setStep }) =&gt; {
  const [orderData] = useContext(OrderContext);

  return (
    &lt;div&gt;
      &lt;h1&gt;Travel Products&lt;/h1&gt;
      &lt;div style={{ display: &#39;flex&#39; }}&gt;
        &lt;Type orderType=&quot;products&quot; /&gt;
      &lt;/div&gt;
      &lt;div style={{ display: &#39;flex&#39;, marginTop: 20 }}&gt;
        &lt;div style={{ width: &#39;50%&#39; }}&gt;
          &lt;Type orderType=&quot;options&quot; /&gt;
        &lt;/div&gt;
        &lt;div style={{ width: &#39;50%&#39; }}&gt;
          &lt;h2&gt;Total Price: {orderData.totals.total}&lt;/h2&gt; &lt;br /&gt;
          &lt;button onClick={() =&gt; setStep(1)}&gt;주문&lt;/button&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
};

export default OrderPage;
</code></pre>
<pre><code class="language-jsx">// SummaryPage/index.js
import { useState } from &#39;react&#39;;

const SummaryPage = ({ setStep }) =&gt; {
  const [checked, setChecked] = useState(false);

  return (
    &lt;div&gt;
      &lt;form&gt;
        &lt;input
          type=&quot;checkbox&quot;
          checked={checked}
          onChange={(e) =&gt; setChecked(e.target.checked)}
          id=&quot;confirm-checkbox&quot;
        /&gt;
        &lt;label htmlFor=&quot;confirm-checkbox&quot;&gt;주문하려는 것을 확인하셨나요?&lt;/label&gt;
        &lt;br /&gt;
        &lt;button disabled={!checked} type=&quot;submit&quot; onClick={() =&gt; setStep(2)}&gt;
          주문 확인
        &lt;/button&gt;
      &lt;/form&gt;
    &lt;/div&gt;
  );
};

export default SummaryPage;</code></pre>
<blockquote>
<p>step이라는 state를 통해 페이지 전환이 가능하다!</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/thisisyjin/post/61dce6a0-9d4b-4d3c-b341-dcd9079744f3/image.gif" alt=""></p>
<hr>
<h2 id="summary-page-구현">Summary Page 구현</h2>
<ul>
<li>해당 페이지 UI 구현하기
<img src="https://velog.velcdn.com/images/thisisyjin/post/bcefb62e-f2c7-4931-b4ff-de5e1b33f763/image.png" alt=""></li>
</ul>
<blockquote>
<p>Array.form</p>
</blockquote>
<ul>
<li>Map 객체를 배열로 만들어주기</li>
<li>배열로 만들어준 후, Array.prototype.map 매서드로 보여줌</li>
</ul>
<blockquote>
<p>⚠️ 참고 - 배열 구조분해 할당 시</p>
<ul>
<li>만약 첫 번째 인자만 가져오고 싶다면?</li>
</ul>
</blockquote>
<pre><code class="language-js">const [first] = someArray; //  이렇게 하면 됨.
const [first, _] = someArray;  // 이런식으로 안해도 됨.</code></pre>
<pre><code class="language-jsx">// SummaryPage/index.js

import { useContext } from &#39;react&#39;;
import { useState } from &#39;react&#39;;
import { OrderContext } from &#39;../../context/OrderContext&#39;;

const SummaryPage = ({ setStep }) =&gt; {
  const [checked, setChecked] = useState(false);
  const [orderDetails] = useContext(OrderContext);

  const productArray = Array.from(orderDetails.products);
  const productList = productArray.map(([key, value]) =&gt; (
    &lt;li key={key}&gt;
      {value} {key}
    &lt;/li&gt;
  ));

  return (
    &lt;div&gt;
      &lt;h1&gt;주문 확인&lt;/h1&gt;
      &lt;h2&gt;여행 상품: {orderDetails.totals.products}&lt;/h2&gt;
      &lt;ul&gt;{productList}&lt;/ul&gt;
      &lt;form&gt;
        &lt;input
          type=&quot;checkbox&quot;
          checked={checked}
          onChange={(e) =&gt; setChecked(e.target.checked)}
          id=&quot;confirm-checkbox&quot;
        /&gt;
        &lt;label htmlFor=&quot;confirm-checkbox&quot;&gt;주문하려는 것을 확인하셨나요?&lt;/label&gt;
        &lt;br /&gt;
        &lt;button disabled={!checked} type=&quot;submit&quot; onClick={() =&gt; setStep(2)}&gt;
          주문 확인
        &lt;/button&gt;
      &lt;/form&gt;
    &lt;/div&gt;
  );
};

export default SummaryPage;</code></pre>
<ul>
<li>Map 객체를 배열로 바꿔준 것.</li>
<li><code>Array.from(mapObject)</code> -&gt; <code>[[key, value], [key, value], [key, value]..]</code> 로 변환</li>
</ul>
<pre><code class="language-js">// Map
new Map([
    [
        &quot;America&quot;,
        2
    ],
    [
        &quot;England&quot;,
        1
    ],
    [
        &quot;Germany&quot;,
        2
    ],
    [
        &quot;Portland&quot;,
        1
    ]
])

// Array
[
    [
        &quot;America&quot;,
        2
    ],
    [
        &quot;England&quot;,
        1
    ],
    [
        &quot;Germany&quot;,
        2
    ],
    [
        &quot;Portland&quot;,
        1
    ]
]</code></pre>
<h3 id="옵션-상품">옵션 상품</h3>
<ul>
<li>옵션 선택을 하지 않은 경우, 아예 해당 영역을 숨김 처리<pre><code class="language-jsx">// SummatyPage/index.js
import { useContext } from &#39;react&#39;;
import { useState } from &#39;react&#39;;
import { OrderContext } from &#39;../../context/OrderContext&#39;;
</code></pre>
</li>
</ul>
<p>const SummaryPage = ({ setStep }) =&gt; {
  const [checked, setChecked] = useState(false);
  const [orderDetails] = useContext(OrderContext);</p>
<p>  // Map 객체는 length가 아닌 size
  const hasOptions = orderDetails.options.size &gt; 0;
  console.log(hasOptions);
  let optionsDisplay = null;</p>
<p>  if (hasOptions) {
    const optionsArray = Array.from(orderDetails.options.keys());
    const optionList = optionsArray.map((key) =&gt; <li key={key}>{key}</li>);
    optionsDisplay = (
      &lt;&gt;
        <h2>옵션: {orderDetails.totals.options}</h2>
        <ul>{optionList}</ul>
      &lt;/&gt;
    );
  }</p>
<p>  const productArray = Array.from(orderDetails.products);
  const productList = productArray.map(([key, value]) =&gt; (
    <li key={key}>
      {value} {key}
    </li>
  ));</p>
<p>  return (
    <div>
      <h1>주문 확인</h1>
      <h2>여행 상품: {orderDetails.totals.products}</h2>
      <ul>{productList}</ul>
      {optionsDisplay}
      <form>
        &lt;input
          type=&quot;checkbox&quot;
          checked={checked}
          onChange={(e) =&gt; setChecked(e.target.checked)}
          id=&quot;confirm-checkbox&quot;
        /&gt;
        <label htmlFor="confirm-checkbox">주문하려는 것을 확인하셨나요?</label>
        <br />
        &lt;button disabled={!checked} type=&quot;submit&quot; onClick={() =&gt; setStep(2)}&gt;
          주문 확인
        </button>
      </form>
    </div>
  );
};</p>
<p>export default SummaryPage;</p>
<pre><code>
---

## Complete Page 구현

- 주문 완료 페이지 (완료 확인)
- 해당 주문에 대한 POST 요청이 필요함.


- order Number을 랜덤으로 지정 (response로 보내줌)
``` jsx
import axios from &#39;axios&#39;;
import { useContext } from &#39;react&#39;;
import { useEffect } from &#39;react&#39;;
import { OrderContext } from &#39;../../context/OrderContext&#39;;

const CompletePage = () =&gt; {
  const [orderData] = useContext(OrderContext);

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

  const orderCompleted = async (orderData) =&gt; {
    try {
      const response = await axios.post(
        &#39;http://localhost:4000/order&#39;,
        orderData
      );
      console.log(response);
    } catch (error) {
      console.error(error);
    }
  };

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

export default CompletePage;</code></pre><ul>
<li>response는 다음과 같다.
<img src="https://velog.velcdn.com/images/thisisyjin/post/6f5ab81a-f4e7-45ce-afdc-b597f5c3fd29/image.png" alt=""></li>
</ul>
<pre><code class="language-jsx">import axios from &#39;axios&#39;;
import { useContext, useState } from &#39;react&#39;;
import { useEffect } from &#39;react&#39;;
import { OrderContext } from &#39;../../context/OrderContext&#39;;

const CompletePage = ({ setStep }) =&gt; {
  const [orderHistory, setOrderHistory] = useState([]);
  const [loading, setLoading] = useState(true);
  const [orderData] = useContext(OrderContext);

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

  const orderCompleted = async (orderData) =&gt; {
    try {
      const response = await axios.post(
        &#39;http://localhost:4000/order&#39;,
        orderData
      );
      setOrderHistory(response.data);
      setLoading(false);
    } catch (error) {
      console.error(error);
    }
  };

  const orderTable = orderHistory.map((item, key) =&gt; (
    &lt;tr key={item.orderNumber}&gt;
      &lt;td&gt;{item.orderNumber}&lt;/td&gt;
      &lt;td&gt;{item.price}&lt;/td&gt;
    &lt;/tr&gt;
  ));

  if (loading) {
    return &lt;div&gt;LOADING...&lt;/div&gt;;
  } else
    return (
      &lt;div style={{ textAlign: &#39;center&#39; }}&gt;
        &lt;h2&gt;주문이 성공했습니다.&lt;/h2&gt;
        &lt;h3&gt;지금까지 모든 주문&lt;/h3&gt;
        &lt;table&gt;
          &lt;tbody&gt;
            &lt;tr&gt;
              &lt;th&gt;number&lt;/th&gt;
              &lt;th&gt;price&lt;/th&gt;
            &lt;/tr&gt;
            {orderTable}
          &lt;/tbody&gt;
        &lt;/table&gt;
        &lt;br /&gt;
        &lt;button onClick={() =&gt; setStep(0)}&gt;첫 페이지로&lt;/button&gt;
      &lt;/div&gt;
    );
};

export default CompletePage;</code></pre>
<hr>
<h3 id="오류-해결">오류 해결</h3>
<ul>
<li><p>발견 에러
옵션에서 체크 후 해제 시, 다음 Summary 페이지에 나타나는 에러가 발견됨.</p>
</li>
<li><p>원인
화면단에 보여줄 때, options.keys로 다 보여줬는데,
Map 객체에서 한 번 체크한 이상 무조건 key-value가 남기 때문에
값이 1인 항목만 보여주어야 함.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/thisisyjin/post/6269e511-7bb2-40c5-863c-2ec6a5e62390/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Programmers] [JavaScript] 분수의 덧셈]]></title>
            <link>https://velog.io/@thisisyjin/Programmers-JavaScript-%EB%B6%84%EC%88%98%EC%9D%98-%EB%8D%A7%EC%85%88</link>
            <guid>https://velog.io/@thisisyjin/Programmers-JavaScript-%EB%B6%84%EC%88%98%EC%9D%98-%EB%8D%A7%EC%85%88</guid>
            <pubDate>Mon, 18 Dec 2023 01:16:56 GMT</pubDate>
            <description><![CDATA[<h2 id="분수의-덧셈">분수의 덧셈</h2>
<hr>
<h3 id="문제-설명">문제 설명</h3>
<p>첫 번째 분수의 분자와 분모를 뜻하는 numer1, denom1, 두 번째 분수의 분자와 분모를 뜻하는 numer2, denom2가 매개변수로 주어집니다. 두 분수를 더한 값을 기약 분수로 나타냈을 때 분자와 분모를 순서대로 담은 배열을 return 하도록 solution 함수를 완성해보세요.</p>
<hr>
<h3 id="제한-사항">제한 사항</h3>
<p>0 &lt;numer1, denom1, numer2, denom2 &lt; 1,000</p>
<hr>
<h3 id="입출력-예">입출력 예</h3>
<table>
<thead>
<tr>
<th>numer1</th>
<th>denom1</th>
<th>numer2</th>
<th>denom2</th>
<th>result</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>[5, 4]</td>
</tr>
<tr>
<td>9</td>
<td>2</td>
<td>1</td>
<td>3</td>
<td>[29, 6]</td>
</tr>
</tbody></table>
<hr>
<h3 id="입출력-예-1">입출력 예 #1</h3>
<ul>
<li>1 / 2 + 3 / 4 = 5 / 4입니다. 따라서 [5, 4]를 return 합니다.</li>
</ul>
<h3 id="입출력-예-2">입출력 예 #2</h3>
<ul>
<li>9 / 2 + 1 / 3 = 29 / 6입니다. 따라서 [29, 6]을 return 합니다.</li>
</ul>
<hr>
<h2 id="내-코드">내 코드</h2>
<pre><code class="language-js">function solution(denum1, num1, denum2, num2) {
    const denum = denum1 * num2 + denum2 * num1;
    const num = num1 * num2;

    const gcd = (a, b) =&gt; b === 0 ? a : gcd(b, a % b)

    let least = gcd(denum, num)
    let answer = [denum / least , num/least]
    return answer
}</code></pre>
<hr>
<h2 id="풀이">풀이</h2>
<ul>
<li><p>처음에는
1 / 2 + 3 / 4 = 1.25 이므로
(소수) 합계를 먼저 구한 후 소수 -&gt; 기약분수로 변환하는 방식을 생각해보았다.
<code>1.25 = 1 + 0.1(1/10) * 2 + 0.01(1/100) * 5</code> 와 같이 쪼개는 방법도 생각해보았는데. 결국 약분해야 하므로 PASS</p>
</li>
<li><p>그 다음으로는 분자, 분모를 구하고
두 수의 최대공약수를 구해서 나누는 방법을 생각했다.
<code>denum = denum1 * num2 + denum2 * num1</code> , <code>num = num1 * num2</code> 이고
gcd 함수를 생성해서 만약 b가 0 (즉, a%b가 0이면) 최대공약수는 a가 되고,
그렇지 않다면 계속해서 그 나머지와 나누어 나머지를 구하는 것을 반복하는 함수이다.</p>
</li>
<li><p>참고 : 유클리드 호제법 (Euclidean Algorithm)
<img src="https://velog.velcdn.com/images/thisisyjin/post/fc6c9752-0e45-4d9d-b4e0-5a74a7fcd979/image.png" alt=""></p>
</li>
</ul>
<hr>
<h2 id="참고-유클리드-호제법">[참고] 유클리드 호제법</h2>
<ul>
<li><p>유클리드 호제법(- 互除法, Euclidean Algorithm)은 2개의 자연수 또는 정식(整式)의 최대공약수(Greatest Common Divisor)를 구하는 알고리즘의 하나이다.</p>
</li>
<li><p>호제법이란 말은 <strong>두 수가 서로(互) 상대방 수를 나누어(除)서 결국 원하는 수를 얻는 알고리즘</strong>을 나타낸다.</p>
</li>
<li><p>2개의 자연수(또는 정식) a, b에 대해서 a를 b로 나눈 나머지를 r이라 하면(단, a&gt;b), a와 b의 최대공약수는 b와 r의 최대공약수와 같다.
이 성질에 따라, b를 r로 나눈 나머지 r&#39;를 구하고, 다시 r을 r&#39;로 나눈 나머지를 구하는 과정을 반복하여 나머지가 0이 되었을 때 나누는 수가 a와 b의 최대공약수이다.</p>
</li>
<li><p>이는 명시적으로 기술된 가장 오래된 알고리즘으로서도 알려져 있으며, 기원전 300년경에 쓰인 유클리드의 《원론》 제7권, 명제 1부터 3까지에 해당한다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[231122 TIL]]></title>
            <link>https://velog.io/@thisisyjin/231122-TIL-4fhcgx5f</link>
            <guid>https://velog.io/@thisisyjin/231122-TIL-4fhcgx5f</guid>
            <pubDate>Tue, 21 Nov 2023 14:34:31 GMT</pubDate>
            <description><![CDATA[<h2 id="context-api">Context API</h2>
<h3 id="state-관리-방법">state 관리 방법</h3>
<ol>
<li>state, props 이용 (컴포넌트 간 통신)</li>
<li>React Context 이용</li>
<li>MobX</li>
<li>Redux</li>
<li>Recoil 등 ...</li>
</ol>
<h3 id="react-context">React Context</h3>
<ul>
<li>Component 트리의 깊이와 관계 없이 Props를 전달하지 않고도 state 사용 가능.</li>
<li>전역 데이터를 관리하는데 사용됨.</li>
</ul>
<hr>
<h3 id="reactcreatecontext">React.createContext</h3>
<ul>
<li>context 객체 생성</li>
<li>Provider에서 현재 context 값을 읽음</li>
</ul>
<pre><code class="language-js">const MyContext = React.createContext(defaultValue);</code></pre>
<ul>
<li>Provider &lt;-&gt; Consumer 구조</li>
</ul>
<hr>
<h3 id="contextprovider">Context.Provider</h3>
<pre><code class="language-jsx">&lt;MyContext.Provider value={ }&gt;
  &lt;AComponent /&gt;
  &lt;BComponent /&gt;
  &lt;CComponent /&gt;
&lt;/MyContext.Provider&gt;</code></pre>
<ul>
<li>A, B, C 컴포넌트 모두 다 컨텍스트를 구독 중이다.</li>
<li>컨텍스트 값이 바뀌면, 컴포넌트를 다시 렌더링한다.</li>
</ul>
<blockquote>
<p>[참고] 변경 사항은 <code>Object.is</code>와 동일한 알고리즘을 사용하여 이전 값과 비교하여 결정됨.</p>
</blockquote>
<hr>
<h3 id="usecontext">useContext</h3>
<pre><code class="language-jsx">const value = useContext(MyContext);</code></pre>
<p>사용 예시</p>
<pre><code class="language-jsx">const themes = {
  light: {
    foreground: &quot;#000000&quot;,
    background: &quot;#eeeeee&quot;
  },
  dark: {
    foreground: &quot;#ffffff&quot;,
    background: &quot;#222222&quot;
  }
};

const ThemeContext = React.createContext(themes.light); // initialState 지정하여 생성

const App = () =&gt; {
  return (
    &lt;ThemeContext.Provider value={themes.dark}&gt; 
      &lt;Toolbar /&gt;
    &lt;/ThemeContext.Provider&gt;
  );
}

const Toolbar = () =&gt; {
  return (
    &lt;div&gt;
      &lt;ThemedButton/&gt;
    &lt;/div&gt;
  );
}

const ThemedButton = () =&gt; {
  const theme = useContext(ThemeContext); // themes.dark
  return (
    &lt;button style={{background: theme.background, color: theme.foreground}}&gt;
      style button
    &lt;/button&gt;)
}</code></pre>
<ul>
<li>위 코드에서 InitialValue는 <code>themes.light</code>지만,
Provider가 value props로 내려주고 있는 값이 <code>themes.dark</code>이므로</li>
<li>useContext(ThemeContext)는 <code>themes.dark</code>가 됨.</li>
</ul>
<hr>
<h2 id="사이드-프로젝트">사이드 프로젝트</h2>
<h3 id="클라이언트">클라이언트</h3>
<ul>
<li>Pages</li>
</ul>
<ol>
<li>주문 페이지</li>
<li>요약 페이지</li>
<li>완료 페이지</li>
</ol>
<ul>
<li>Components</li>
</ul>
<h3 id="백엔드">백엔드</h3>
<ul>
<li>node.js</li>
</ul>
<h3 id="create-react-app-프로젝트-생성">create react app (프로젝트 생성)</h3>
<ul>
<li>현재 폴더에 생성하려면?<pre><code class="language-bash">$ npx create-react-app ./</code></pre>
</li>
</ul>
<hr>
<h3 id="pages-생성">pages 생성</h3>
<p>/src/pages에 아래와 같이 각 페이지 폴더와 index.js 생성</p>
<p><img src="https://velog.velcdn.com/images/thisisyjin/post/2e3f8bf3-5cc5-4c15-8f73-f11b8d44f6ac/image.png" alt=""></p>
<pre><code class="language-jsx">const SummaryPage = () =&gt; {
  return &lt;div&gt;SummaryPage&lt;/div&gt;;
};

export default SummaryPage;</code></pre>
<hr>
<h3 id="summary-page-생성">Summary Page 생성</h3>
<blockquote>
<p>구현해야 할 기능</p>
</blockquote>
<ul>
<li>주문 확인 체크박스 체크해야 [주문 확인] 버튼 클릭 가능.</li>
<li>form을 이용하여 구현 (input checkbox)</li>
</ul>
<ol>
<li><code>checked</code> state를 관리</li>
<li>input onClick 핸들러 (e.target.checked로 설정)</li>
<li>button disabled (checked가 false면 disabled)</li>
</ol>
<pre><code class="language-jsx">import { useState } from &#39;react&#39;;

const SummaryPage = () =&gt; {
  const [checked, setChecked] = useState(false);

  return (
    &lt;div&gt;
      &lt;form&gt;
        &lt;input
          type=&quot;checkbox&quot;
          checked={checked}
          onClick={(e) =&gt; setChecked(e.target.checked)}
          id=&quot;confirm-checkbox&quot;
        /&gt;{&#39; &#39;}
        &lt;label htmlFor=&quot;confirm-checkbox&quot;&gt;주문하려는 것을 확인하셨나요?&lt;/label&gt;
        &lt;br /&gt;
        &lt;button disabled={!checked} type=&quot;submit&quot;&gt;
          주문 확인
        &lt;/button&gt;
      &lt;/form&gt;
    &lt;/div&gt;
  );
};

export default SummaryPage;</code></pre>
<hr>
<h3 id="주문-페이지-ui-생성">주문 페이지 UI 생성</h3>
<ul>
<li>OrderPage에 들어갈 컴포넌트인 Type 컴포넌트 생성.</li>
<li>/src/components/Type.jsx</li>
</ul>
<p>App &gt; OrderPage &gt; Type </p>
<ul>
<li>Type에서는 <code>OrderType</code> 이라는 props를 받아서 구분함</li>
</ul>
<pre><code class="language-jsx">const Type = ({ orderType }) =&gt; {
  return (
    &lt;&gt;
      &lt;h2&gt;주문 종류&lt;/h2&gt;
      &lt;p&gt; 하나의 가격&lt;/p&gt;
      &lt;p&gt;총 가격:&lt;/p&gt;
      &lt;div
        style={{
          display: &#39;flex&#39;,
          flexDirection: orderType === &#39;options&#39; &amp;&amp; &#39;column&#39;,
        }}
      &gt;&lt;/div&gt;
    &lt;/&gt;
  );
};

export default Type;
</code></pre>
<pre><code class="language-jsx">const OrderPage = () =&gt; {
  return (
    &lt;div&gt;
      &lt;h1&gt;Travel Products&lt;/h1&gt;
      &lt;div&gt;
        &lt;Type orderType=&quot;products&quot; /&gt;
      &lt;/div&gt;
      &lt;div style={{ display: &#39;flex&#39;, marginTop: 20 }}&gt;
        &lt;div style={{ width: &#39;50%&#39; }}&gt;
          &lt;Type orderType=&quot;options&quot; /&gt;
        &lt;/div&gt;
        &lt;div style={{ width: &#39;50%&#39; }}&gt;
          &lt;h2&gt;Total Price&lt;/h2&gt; &lt;br /&gt;
          &lt;button&gt;주문&lt;/button&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
};

export default OrderPage;
</code></pre>
<hr>
<h3 id="백엔드-코드-연결">백엔드 코드 연결</h3>
<ul>
<li>상품 이미지 가져오기</li>
<li>API 요청 (axios)</li>
</ul>
<ul>
<li>Type 컴포넌트에서 orderType은 &#39;products&#39; 또는 &#39;options&#39; 인데,
orderType에 따라 api를 다르게 요청함.</li>
<li>items 라는 state에 api response.data를 저장함.</li>
</ul>
<pre><code class="language-jsx">import { useEffect, useState } from &#39;react&#39;;
import axios from &#39;axios&#39;;
import Products from &#39;./Products&#39;;
import Options from &#39;./Options&#39;;

const Type = ({ orderType }) =&gt; {
  const [items, setItems] = useState([]);
  console.log(orderType);

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

  const loadItems = async (orderType) =&gt; {
    try {
      const response = await axios.get(`http://localhost:4000/${orderType}`);
      setItems(response.data);
    } catch (error) {
      console.error(error);
    }
  };

  const ItemComponents = orderType === &#39;products&#39; ? Products : Options;

  const optionItems = items.map((item) =&gt; (
    &lt;ItemComponents
      key={item.name}
      name={item.name}
      imagePath={item.imagePath}
    /&gt;
  ));
  return &lt;&gt;{optionItems}&lt;/&gt;;
};

export default Type;</code></pre>
<p><img src="https://velog.velcdn.com/images/thisisyjin/post/f4fb7137-5554-4059-9baa-b2ea7303cd10/image.png" alt=""></p>
<hr>
<h3 id="products-ui-작성">Products UI 작성</h3>
<ul>
<li>ItemComponents(=Products)의 props 확인<pre><code class="language-jsx">const Products = ({ name, imagePath }) =&gt; {
console.log(&#39;products : &#39;, name, imagePath);
return &lt;&gt;Products&lt;/&gt;;
};
</code></pre>
</li>
</ul>
<p>export default Products;</p>
<pre><code>
### 백엔드(서버) 이미지 접근
- 예&gt; /images/america.jpeg 의 경우
- {백엔드 URL}/images/america.jpeg
- 즉, localhost:4000/images/america.jpeg 로 접근하면 됨.


``` jsx
const Products = ({ name, imagePath }) =&gt; {
  console.log(&#39;products : &#39;, name, imagePath);
  return (
    &lt;div style={{ textAlign: &#39;center&#39; }}&gt;
      &lt;img
        src={`http://localhost:4000/${imagePath}`}
        alt={`${name} product`}
        style={{ width: &#39;75%&#39; }}
      /&gt;
    &lt;/div&gt;
  );
};

export default Products;</code></pre><hr>
<h3 id="api-에러-처리">API 에러 처리</h3>
<ul>
<li>ErrorBanner 컴포넌트 생성<pre><code class="language-jsx">const ErrorBanner = ({ message }) =&gt; {
let errorMessage = message || &#39;에러입니다.&#39;;
return &lt;div style={{ backgroundColor: &#39;red&#39; }}&gt;{errorMessage}&lt;/div&gt;;
};
</code></pre>
</li>
</ul>
<p>export default ErrorBanner;</p>
<pre><code>

- Type 컴포넌트 수정
``` jsx
import { useEffect, useState } from &#39;react&#39;;
import axios from &#39;axios&#39;;
import Products from &#39;./Products&#39;;
import Options from &#39;./Options&#39;;
import Error from &#39;./Error&#39;;

const Type = ({ orderType }) =&gt; {
  const [items, setItems] = useState([]);
  const [error, setError] = useState(false);

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

  const loadItems = async (orderType) =&gt; {
    try {
      const response = await axios.get(`http://localhost:4000/${orderType}`);
      setItems(response.data);
    } catch (error) {
      setError(true); // 에러 처리
    }
  };

  const ItemComponents = orderType === &#39;products&#39; ? Products : Options;

  const optionItems = items.map((item) =&gt; (
    &lt;ItemComponents
      key={item.name}
      name={item.name}
      imagePath={item.imagePath}
    /&gt;
  ));
  return (
    &lt;&gt;
      {optionItems}
      {error &amp;&amp; &lt;Error /&gt;}
    &lt;/&gt;
  );
};

export default Type;</code></pre><hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[231121 TIL]]></title>
            <link>https://velog.io/@thisisyjin/231121-TIL</link>
            <guid>https://velog.io/@thisisyjin/231121-TIL</guid>
            <pubDate>Mon, 20 Nov 2023 15:09:01 GMT</pubDate>
            <description><![CDATA[<h2 id="reactjs--ts">React.js + TS</h2>
<blockquote>
<ul>
<li>React 프로젝트 -&gt; TS + React로 변경</li>
</ul>
</blockquote>
<h3 id="dependencies-설치">Dependencies 설치</h3>
<pre><code class="language-bash">$ npm install -D typescript @types/react @types/react-dom</code></pre>
<h3 id="그-외">그 외</h3>
<ul>
<li>vite.config.js -&gt; ts로 수정</li>
<li>tsconfig.node.json 파일 생성 </li>
<li>src/vite-env.d.ts</li>
</ul>
<blockquote>
<p>참고 - vite 프로젝트 생성 시 Typescript 선택 시 자동으로 생성됨</p>
</blockquote>
<ul>
<li><p>index.html에서 main.jsx -&gt; main.tsx로 수정</p>
</li>
<li><p>main.jsx -&gt; main.tsx 수정</p>
<pre><code class="language-ts">ReactDOM.createRoot(document.getElementById(&#39;root&#39;) as HTMLElement).render(&lt;App /&gt;);</code></pre>
</li>
<li><p>tsconfig.json에서 allow.js을 true로 수정
자바스크립트 파일을 사용할 수 있게 해줄 것인지 설정</p>
</li>
</ul>
<hr>
<h3 id="types-설정">types 설정</h3>
<p>/src/types 폴더 생성해서 타입들만 모아놓기 (.ts)</p>
<pre><code class="language-ts">export interface PokemonData {
  count: number;
  next: string | null;
  previous: string | null;
  results: PokemonNameAndUrl[];
}

export interface PokemonNameAndUrl {
  name: string;
  url: string;
}</code></pre>
<hr>
<ol>
<li><code>State</code>에 타입 지정하기<pre><code class="language-tsx">const [allPokemons, setAllPokemons] = useState([]); // error</code></pre>
</li>
</ol>
<pre><code class="language-tsx">const [allPokemons, setAllPokemons] = &lt;PokemonNameAndUrl[]&gt;useState([]); // OK</code></pre>
<ul>
<li>allPokemons라는 state가 PokemonNameAndUrl이라는 타입을 가진다.</li>
</ul>
<ol start="2">
<li><p>axios.get으로 가져오는 정보에 타입 지정하기</p>
<pre><code class="language-tsx">const response = await axios.get&lt;PokemonData&gt;(url);</code></pre>
</li>
<li><p>함수 <code>params</code>에 타입 지정하기</p>
<pre><code class="language-tsx">const filterDisplayedPokemonData = (
allPokemonsData: PokemonNameAndUrl[],
displayedPokemons: PokemonNameAndUrl[] = []
) =&gt; {
...
} </code></pre>
</li>
<li><p>return 부분 <code>map params</code>에 타입 지정하기</p>
<pre><code class="language-tsx">{displayedPokemons.map(({url, name}: PokemonNameAndUrl) =&gt; ( ... ))}</code></pre>
</li>
<li><p><code>setState</code>에 타입 지정하기</p>
<pre><code class="language-tsx">setState: React.Dispatch&lt;React.SetStateAction&lt;number&gt;&gt;;</code></pre>
</li>
</ol>
<p>그 외 React에서 어떻게 TypeScript를 사용하는지는
<a href="https://react-typescript-cheatsheet.netlify.app/">React-TypeScript-CheatSheet</a>에 잘 나와있으니 참조!</p>
<hr>
<h3 id="컴포넌트-props에-타입-지정하는-컨벤션">컴포넌트 props에 타입 지정하는 컨벤션</h3>
<pre><code class="language-tsx">interface ComponentProps {
  props1: string[],
  props2: React.Dispatch&lt;React.SetStateAction&lt;string&gt;&gt;
}

const Component = ({ props1, props2 }: ComponentProps) =&gt; {
  ...
}</code></pre>
<hr>
<h3 id="이벤트-객체의-경우">이벤트 객체의 경우</h3>
<pre><code class="language-tsx">const handleSubmit = (e: React.FormEvent&lt;HTMLFormElement&gt;) =&gt; {
  ...
}</code></pre>
<hr>
<h3 id="keyof-typeof">keyof typeof</h3>
<pre><code class="language-tsx">const newSprites = {...sprites};

(Object.keys(newSprites) as (keyof typeof newSprites)[]).forEach((key =&gt; {
  ...
})</code></pre>
<ul>
<li>newSprites는 객체이다.</li>
<li>keyof는 타입의 키를 가져오는 것 이므로, 객체에 바로 사용 불가함.</li>
<li>따라서 typeof 를 한 번 해주고나서 keyof를 해줘야 함.</li>
</ul>
<pre><code class="language-ts">type Point = {x: number, y:number};
type P = keyof Point; // &quot;x&quot; | &quot;y&quot;</code></pre>
<blockquote>
<p>✅ <strong>타입 단언 (as)</strong></p>
</blockquote>
<ul>
<li>TypeScript보다 어떤 값의 타입을 보다 명확하게 알고 있을 때 활용</li>
<li>타입 선언(:type)과는 다름.</li>
<li>즉, 타입 단언은 강제로 타입을 지정했으니 체크를 하지 않음.</li>
</ul>
<hr>
<h3 id="tool-quicktype">[Tool] quickType</h3>
<ul>
<li>데이터를 붙여넣으면 자동으로 Type을 바꿔주는 사이트</li>
<li><a href="https://quicktype.io/">quickType 바로가기</a></li>
</ul>
<hr>
<h3 id="await">await</h3>
<ul>
<li>await은 Promise를 반환</li>
<li>그 타입을 &lt;&gt; 안에 적어주면 됨 (string)<pre><code class="language-tsx">const getDescription = async (id: number): Promise&lt;string&gt; =&gt; {
...
return descriptions;
}</code></pre>
</li>
</ul>
<hr>
<h3 id="useref">useRef</h3>
<ol>
<li>변수로 사용하는 경우</li>
</ol>
<ul>
<li>초기 값을 제네릭 타입과 같은 타입으로 넣어주기.</li>
</ul>
<pre><code class="language-ts">const varRef = useRef&lt;number&gt;(0);</code></pre>
<ol start="2">
<li>DOM을 조작하기 위해 사용하는 경우</li>
</ol>
<ul>
<li>초기 값(HTMLElement)을 넣어주기<pre><code class="language-tsx">const ref = useRef&lt;HTMLDivElement&gt;(null);
const inputRef = useRef&lt;HTMLInputElement&gt;(null);
</code></pre>
</li>
</ul>
<p>...</p>
<p>return(
  <div ref={ref}></div>
)</p>
<pre><code>
- 위에서는 ref가 null이 될 수 있다.
- 타입 가드(type guard)를 해줘야 함.

``` tsx
if (ref.current) {  // type guard
  ref.current.style.width = &#39;100%&#39;
}</code></pre><hr>
<h3 id="usestate">useState</h3>
<ul>
<li>useState값이 객체거나 Null일때<pre><code class="language-tsx">const initialUserData = localStorage.getItem(&#39;userData&#39;) ? JSON.parse(localStorage.getItem(&#39;userData&#39;)) : null;
const [userData, setUserData] = useState&lt;User | null&gt;(initialUserData);</code></pre>
</li>
</ul>
<hr>
<h3 id="dts-선언-파일-생성">.d.ts 선언 파일 생성</h3>
<blockquote>
<p>uuid </p>
</blockquote>
<ul>
<li>유니크한 아이디를 생성하는 모듈.</li>
</ul>
<pre><code class="language-bash">$ npm install uuid</code></pre>
<ul>
<li><p>app.tsx에서 임포트 시 선언 파일을 찾을 수 없다는 에러 발견됨.</p>
<pre><code class="language-tsx">import { v4 } from &#39;uuid&#39;;</code></pre>
</li>
<li><p>이와 같이 js로 작성된 모듈을 사용할 때, 타입이 지정되지 않은 경우</p>
<ul>
<li><code>.d.ts</code> 선언 파일을 생성해야 함.</li>
</ul>
</li>
</ul>
<h3 id="uuiddts-파일-생성">uuid.d.ts 파일 생성</h3>
<pre><code class="language-ts">declare module &#39;uuid&#39; {
  const v4: () =&gt; string;

  export { v4 };
}</code></pre>
<ul>
<li>파일 명을 uuid.d.ts가 아닌 main.d.ts 등으로 변경하면 다시 에러가 남.<ul>
<li>자동으로 찾아주지 않기 때문에 Reference 태그를 사용해야함.</li>
</ul>
</li>
</ul>
<h3 id="-refrence-path"><code>/// &lt;refrence path=&quot;&quot;/&gt;</code></h3>
<ul>
<li>파일의 최상단에 위와 같이 작성하면 해결됨.</li>
<li>즉, 선언 파일(.d.ts)명이 모듈의 이름과 다른 경우에는 위와 같이 레퍼런스 태그를 사용해야 함.</li>
</ul>
<pre><code class="language-tsx">/// &lt;reference path=&quot;./main.d.ts&quot; /&gt;</code></pre>
<h3 id="권장-방법">권장 방법</h3>
<ul>
<li>위와 같이 직접 .d.ts 파일을 생성하는 것 보다는</li>
<li>타입 선언 모듈을 설치해서 쓰는 것이 좋다!</li>
</ul>
<pre><code class="language-bash">$ npm install -D @types/{모듈명}</code></pre>
<blockquote>
<p>참고 - npm install uuid &amp;&amp; npm install -D @types/uuid 둘 다 해야 함.</p>
</blockquote>
<hr>
<h3 id="localstorage-wrapper-생성">LocalStorage Wrapper 생성</h3>
<ul>
<li>localStorage 값을 가져올 때 Wrapper를 이용해보자.</li>
</ul>
<p>/src/utils/storage.ts 파일 생성</p>
<pre><code class="language-ts">const storage = {
  set: (key: string, value: any) =&gt; {
    localStorage.setItem(key, JSON.stringify(value));
  }, 

  get: &lt;T&gt;(key: string, defaultValue?: T): T =&gt; {
    const value = localStorage.getItem(key);
    return (value ? JSON.parse(value) : defaultValue) as T;
  },

  remove: (key: string) =&gt; {
    localStorage.removeItem(key)
  }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[231120 TIL]]></title>
            <link>https://velog.io/@thisisyjin/231120-TIL</link>
            <guid>https://velog.io/@thisisyjin/231120-TIL</guid>
            <pubDate>Mon, 20 Nov 2023 13:29:11 GMT</pubDate>
            <description><![CDATA[<h2 id="reactjs">React.js</h2>
<blockquote>
<ol>
<li>Firebase</li>
<li>React Project</li>
</ol>
</blockquote>
<h3 id="firebase란">Firebase란?</h3>
<ul>
<li>Google 로그인 구현 가능</li>
<li>데이터베이스</li>
<li>스토리지</li>
<li>푸시 알림</li>
<li>배포 등</li>
</ul>
<p>앱을 만들 때 필요한 여러가지 기능을 편리하게 구현 가눙.
Firebase 하나로 백엔드 부분을 대체 가능.</p>
<h3 id="firebase와-어플리케이션-연결">Firebase와 어플리케이션 연결</h3>
<ol>
<li>Firebase 사이트로 이동
<a href="https://firebase.google.com/?hl=ko">https://firebase.google.com/?hl=ko</a></li>
</ol>
<ul>
<li>로그인 후, Go to console 클릭.</li>
<li>프로젝트 생성</li>
<li>생성 후, &#39;웹&#39; 클릭</li>
</ul>
<ol start="2">
<li>Add Firebase</li>
</ol>
<ul>
<li><p>App nickname 등록</p>
</li>
<li><p>firebase SDK 넣기 (npm 또는 script 태그)</p>
<pre><code class="language-bash">$ npm install firebase</code></pre>
</li>
<li><p>/src/firebase.js 파일 생성 후 아래 코드 붙여넣기</p>
<pre><code class="language-js">// Import the functions you need from the SDKs you need
import { initializeApp } from &quot;firebase/app&quot;;
// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries
</code></pre>
</li>
</ul>
<p>// Your web app&#39;s Firebase configuration
const firebaseConfig = {
  apiKey: &quot;AIzaSyC63UwfU5W3xQfqOR9CVObrdwtNQNe4Gak&quot;,
  authDomain: &quot;react-firebase-30fd5.firebaseapp.com&quot;,
  projectId: &quot;react-firebase-30fd5&quot;,
  storageBucket: &quot;react-firebase-30fd5.appspot.com&quot;,
  messagingSenderId: &quot;1055997579956&quot;,
  appId: &quot;1:1055997579956:web:fece8bcb3636855b037de1&quot;
};</p>
<p>// Initialize Firebase
const app = initializeApp(firebaseConfig);</p>
<pre><code>
3. Authentication에서 Google Sign in 메서드 허용
- Authentication &gt; Sign-in method 에서 Google 항목을 Enabled로 변경

![](https://velog.velcdn.com/images/thisisyjin/post/b1c6099a-f8d9-450c-926f-f73621bd1a85/image.png)

![](https://velog.velcdn.com/images/thisisyjin/post/a064b8c0-e379-48f3-b1d3-96baa5ba431b/image.png)



--- 
### 로그인 페이지 생성
- 네비게이션 바 생성 (공통 부분)
- Outlet 사용 (React-Router-Dom 중첩 라우팅)

``` jsx
&lt;Route path=&quot;/&quot; element={&lt;Layout /&gt;}&gt;
    &lt;Route index element={&lt;MainPage /&gt;} /&gt;
    &lt;Route path=&quot;/login&quot; element={&lt;LoginPage /&gt;} /&gt;
&lt;/Route&gt;</code></pre><pre><code class="language-jsx">const Layout = () =&gt; {
  return (
    &lt;&gt;
      &lt;div&gt;&lt;/div&gt;
      &lt;Outlet /&gt;
    &lt;/&gt;
  )
}</code></pre>
<p>예&gt; localhost:3000/login 에서는
Layout이 보이고, LoginPage가 보임.</p>
<hr>
<h3 id="styled-component">Styled-Component</h3>
<ul>
<li>CSS in JS</li>
<li>리액트의 컴포넌트 맞춤 스타일링을 위한 방식</li>
<li>스타일링 시 컴포넌트를 생성하고, <code>props</code> 를 사용할 수 있음<pre><code class="language-js">const Button = styled.button`
color: ${(props =&gt; props.primary ? &quot;red&quot; : &quot;white&quot;};
`;</code></pre>
</li>
<li>부모 스타일을 확장 가능 (<code>styled()</code>)<pre><code class="language-js">const Button = styled.button`
// styling
`;
</code></pre>
</li>
</ul>
<p>const BlueButton = styled(Button)<code>color: blue;</code>;</p>
<pre><code>
### 스크롤 시 색상 변화되는 상단 네비게이션 구현
- scroll 이벤트 리스너 이용

``` js
const [show, setShow] = useState(false);

const listener = () =&gt; {
    if (window.scrollY &gt; 50) {
      setShow(true);
    } else {
      setShow(false);
    }
}
useEffect(() =&gt; {
  window.addEventListener(&#39;scroll&#39;, listner);
  return () =&gt; {
    window.removeEventListener(&#39;scroll&#39;, listner);
  }
}, []);</code></pre><hr>
<h3 id="firebase를-이용한-로그인-구현">Firebase를 이용한 로그인 구현</h3>
<ul>
<li>로그인 버튼은 path가 <code>/login</code> 일 때만 보이도록 설정</li>
</ul>
<pre><code class="language-jsx">const { pathname } = useLocation(); // &#39;/login&#39;


...
// return
{pathname === &#39;/login&#39; ? (&lt;LoginButton /&gt;) : null}</code></pre>
<ul>
<li>signinwithpopup 이용
<a href="https://firebase.google.com/docs/auth/web/google-signin?hl=ko">https://firebase.google.com/docs/auth/web/google-signin?hl=ko</a></li>
</ul>
<pre><code class="language-jsx">import { getAuth, signInWithPopup, GoogleAuthProvider } from &#39;firebase/auth&#39;;</code></pre>
<ol>
<li>getAuth(app)</li>
</ol>
<ul>
<li>여기서 app은 firebase.js 생성할 때 initializeApp 으로 생성해준 앱 인스턴스임.<pre><code class="language-jsx">import app from &#39;../firebase&#39;;
</code></pre>
</li>
</ul>
<p>const auth = getAuth(app);</p>
<pre><code>
2. Provider 인스턴스 생성
``` jsx
const provider = new GoogleAuthProvider();</code></pre><ol start="3">
<li>SignInWithPopup 호출<pre><code class="language-jsx">signInWithPopup(auth, provider);</code></pre>
</li>
</ol>
<ul>
<li>로그인 버튼 클릭 시 함수 생성</li>
</ul>
<pre><code class="language-js">const handleAuth = () =&gt; {
  signInWithPopup(auth, provider)
    .then(result =&gt; {
      console.log(result);
    })
    .catch(error =&gt; {
      console.error(error);
    })
}</code></pre>
<hr>
<h3 id="로그인-상태-체크">로그인 상태 체크</h3>
<p>&#39;firebase/auth&#39;의 <code>onAuthStateChanged</code> 메서드 사용</p>
<ul>
<li>유저의 상태가 변경될 때 호출된다.</li>
</ul>
<pre><code class="language-jsx">import { getAuth, onAuthStateChanged } from &quot;firebase/auth&quot;;

const auth = getAuth(app); // auth 인스턴스

onAuthStateChanged(auth, (user) =&gt; {
  if (user) {
    // User Signed in
    const uid = user.uid;
  } else {
    // User Signed out
  }
});</code></pre>
<ul>
<li>이를 이용해서 로그인 상태를 체크하고, 리다리엑트 가능함.</li>
</ul>
<pre><code class="language-jsx">import { useNavigate } from &#39;react-router-dom&#39;;

const navigate = useNavigate();

useEffect(() =&gt; {
  const unsubscribe = onAuthStateChanged(auth, (user) =&gt; {
    if (!user) {
      navigate(&#39;/login&#39;);
    } else if (user &amp;&amp; pathname === &#39;/login&#39;) {
      // 로그인 상태인데 로그인 페이지로 가려고 할 경우
      navigate(&#39;/&#39;);
    }
  });

  // return으로 정리해줘야함
  return () =&gt; {
    unsubscribe(); 
  }
}, []);</code></pre>
<hr>
<h3 id="로그아웃-기능-구현">로그아웃 기능 구현</h3>
<ul>
<li>유저 정보 저장</li>
</ul>
<pre><code class="language-jsx">const [userData, setUserData] = useState({})

const handleAuth = () =&gt; {
  signInWithPopup(auth, provider)
  .then(result =&gt; {
    setUserData(result.user);
  })
  .catch(error =&gt; {
    console.error(error)
  });
}</code></pre>
<p>userData.photoURL을 이용하여 유저 프로필 이미지 보여주기 가능.</p>
<ul>
<li>로그아웃 메서드 (signOut)<pre><code class="language-jsx">import { signOut } from &#39;firebase/auth&#39;;
</code></pre>
</li>
</ul>
<p>const handleLogout = () =&gt; {
  signOut(auth).then(() =&gt; {
    setUserData({});
  })
  .catch(error =&gt; {
    console.error(error); 
  });
}</p>
<pre><code>

### 데이터 유지하기
- 리프레시 했을 때 userData가 날아가지 않도록 해야 함
- 리프레시 -&gt; state가 초기화 됨 -&gt; 데이터 날아감


- localStorage 이용하여 유저 데이터를 저장
- `result.user`은 객체이기 때문에, localStorage에 저장할 때는
`JSON.stringify()`를 통해 스트링화
- 다시 값을 불러올 때에는 `JSON.parse()` 를 이용


---

### Firebase로 앱 배포

1. Git 원격 브랜치에 Push하기
2. firebase CLI 로그인
- firebase-tools 설치 (전역)
``` bash
$ npm install -g firebase-tools</code></pre><ul>
<li>firebase CLI를 이용하여 로그인<pre><code class="language-bash">$ firebase login</code></pre>
</li>
</ul>
<ol start="3">
<li><p>앱 빌드 </p>
<pre><code class="language-bash">$ npm run build</code></pre>
</li>
<li><p>Firebase 시작 </p>
<pre><code class="language-bash">$ firebase init</code></pre>
</li>
<li><p>hosting 기능 선택 후 스페이스바 + 엔터 후
Existing Project 선택 후 엔터</p>
</li>
<li><p>퍼블릭 디렉토리 선택 </p>
</li>
</ol>
<ul>
<li>CRA의 경우, /build 폴더</li>
<li>vite의 경우, /dist 폴더</li>
</ul>
<p>-&gt; <code>dist</code> 입력 후 엔터</p>
<ol start="7">
<li>SPA 여부 -&gt; y</li>
<li>github -&gt; y</li>
<li>overwrite -&gt; n</li>
<li>Authorize Firebase 버튼 클릭 (github 레파지토리 접근)</li>
<li>set up repository -&gt; username/repository 형식으로 작성</li>
<li>배포 전 항상 build? -&gt; y</li>
<li>빌드 시 어떤 script? -&gt;  npm install &amp;&amp; npm run build</li>
<li>어떤 브랜치 -&gt; main</li>
</ol>
<p>위와 같은 과정을 통해 배포 완료.
다시 한 번 소스코드를 git에 push.</p>
<blockquote>
<p>github &gt; Actions 탭 확인해보면
build_and_deploy진행 과정 볼 수 있음.</p>
</blockquote>
<ul>
<li>배포 확인
firebase console에서 Deployed 상태 클릭 -&gt; 도메인 클릭하면 끝.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Programmers] [JavaScript] 콜라 문제]]></title>
            <link>https://velog.io/@thisisyjin/Programmers-JavaScript-%EC%BD%9C%EB%9D%BC-%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@thisisyjin/Programmers-JavaScript-%EC%BD%9C%EB%9D%BC-%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Wed, 15 Nov 2023 06:56:00 GMT</pubDate>
            <description><![CDATA[<h2 id="콜라-문제">콜라 문제</h2>
<hr>
<h3 id="문제-설명">문제 설명</h3>
<p>오래전 유행했던 콜라 문제가 있습니다. 콜라 문제의 지문은 다음과 같습니다.</p>
<blockquote>
<p>정답은 아무에게도 말하지 마세요.</p>
<p>콜라 빈 병 2개를 가져다주면 콜라 1병을 주는 마트가 있다. 빈 병 20개를 가져다주면 몇 병을 받을 수 있는가?</p>
<p>단, 보유 중인 빈 병이 2개 미만이면, 콜라를 받을 수 없다.</p>
</blockquote>
<p>문제를 풀던 상빈이는 콜라 문제의 완벽한 해답을 찾았습니다. 상빈이가 푼 방법은 아래 그림과 같습니다. 우선 콜라 빈 병 20병을 가져가서 10병을 받습니다. 받은 10병을 모두 마신 뒤, 가져가서 5병을 받습니다. 5병 중 4병을 모두 마신 뒤 가져가서 2병을 받고, 또 2병을 모두 마신 뒤 가져가서 1병을 받습니다. 받은 1병과 5병을 받았을 때 남은 1병을 모두 마신 뒤 가져가면 1병을 또 받을 수 있습니다. 이 경우 상빈이는 총 10 + 5 + 2 + 1 + 1 = 19병의 콜라를 받을 수 있습니다.</p>
<p>문제를 열심히 풀던 상빈이는 일반화된 콜라 문제를 생각했습니다. 이 문제는 빈 병 a개를 가져다주면 콜라 b병을 주는 마트가 있을 때, 빈 병 n개를 가져다주면 몇 병을 받을 수 있는지 계산하는 문제입니다. 기존 콜라 문제와 마찬가지로, 보유 중인 빈 병이 a개 미만이면, 추가적으로 빈 병을 받을 순 없습니다. 상빈이는 열심히 고심했지만, 일반화된 콜라 문제의 답을 찾을 수 없었습니다. 상빈이를 도와, 일반화된 콜라 문제를 해결하는 프로그램을 만들어 주세요.</p>
<p>콜라를 받기 위해 마트에 주어야 하는 병 수 a, 빈 병 a개를 가져다 주면 마트가 주는 콜라 병 수 b, 상빈이가 가지고 있는 빈 병의 개수 n이 매개변수로 주어집니다. 상빈이가 받을 수 있는 콜라의 병 수를 return 하도록 solution 함수를 작성해주세요.</p>
<hr>
<h3 id="제한사항">제한사항</h3>
<p>1 ≤ b &lt; a ≤ n ≤ 1,000,000
정답은 항상 int 범위를 넘지 않게 주어집니다.</p>
<hr>
<h3 id="입출력-예">입출력 예</h3>
<blockquote>
<p>입출력 예 #1</p>
</blockquote>
<p>a=2, b=1, n=20
result = 19</p>
<hr>
<h3 id="의사-코드">의사 코드</h3>
<ol>
<li>남은 병 수 / a가 나누어 떨어지는 경우</li>
</ol>
<ul>
<li>남은 병 수 = 남은 병 수 / a (몫)</li>
<li>받은 병 수 = 몫 * b</li>
</ul>
<ol start="2">
<li>남은 병 수 / a가 나누어 떨어지지 않는 경우</li>
</ol>
<ul>
<li>남은 병 수 = 남은 병 수 - 준 병 수 + 받은 병 수 </li>
<li>받은 병 수 = 몫(내림) * b</li>
</ul>
<h3 id="내-코드">내 코드</h3>
<pre><code class="language-js">function solution(a, b, n) {
  let answer = 0;
  let rest = n;

  while (rest &gt;= a) {
    if (rest % a === 0) {
      rest = (rest / a) * b;
      answer += rest;
    } else {
      answer += Math.floor(rest / a) * b;
      rest = rest - a * Math.floor(rest / a) + Math.floor(rest / a) * b;
    }
  }
  return answer;
}</code></pre>
<hr>
<h3 id="실행-결과">실행 결과</h3>
<p>테스트 1 〉    통과 (0.14ms, 33.4MB)
테스트 2 〉    통과 (0.05ms, 33.5MB)
테스트 3 〉    통과 (0.16ms, 33.4MB)
테스트 4 〉    통과 (0.07ms, 33.4MB)
테스트 5 〉    통과 (0.08ms, 33.4MB)
테스트 6 〉    통과 (0.05ms, 33.4MB)
테스트 7 〉    통과 (0.07ms, 33.5MB)
테스트 8 〉    통과 (0.05ms, 33.4MB)
테스트 9 〉    통과 (0.13ms, 33.4MB)
테스트 10 〉    통과 (0.05ms, 33.4MB)
테스트 11 〉    통과 (0.05ms, 33.4MB)
테스트 12 〉    통과 (0.46ms, 33.6MB)
테스트 13 〉    통과 (0.05ms, 33.4MB)
테스트 14 〉    통과 (0.05ms, 33.4MB)</p>
<hr>
<h3 id="다른-코드">다른 코드</h3>
<p>좀 더 간단하게 할 수 없나? 라는 생각에 다른 코드를 작성해보았다.</p>
<pre><code class="language-js">function solution(a, b, n) {
  // 나눠떨어지면 남은 수 = (n/a) / 얻은 수 += (n/a) * b
  // 나눠떨어지지 않으면 남은 수 = n % a + Math.floor(n/a) * b / 얻은 수 += Math.floor(n/a) * b
  let answer = 0;

  while (n &gt;= a) {
    answer =
      n % a === 0 ? answer + (n / a) * b : answer + Math.floor(n / a) * b;
    console.log(n, answer);
    n = n % a === 0 ? n / a : (n % a) + Math.floor(n / a) * b;
  }
  return answer;
}</code></pre>
<p>결국 똑같은 코드인 듯 하다</p>
]]></description>
        </item>
    </channel>
</rss>