<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>mhlog</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Tue, 11 Jul 2023 06:52:23 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>mhlog</title>
            <url>https://velog.velcdn.com/images/mh-lee/profile/a9514aa3-78ce-4f4a-8f47-5966498e9bf0/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. mhlog. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/mh-lee" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Redux] Redux-Thunk로 프로미스 다루기]]></title>
            <link>https://velog.io/@mh-lee/Redux-Redux-Saga%EB%A1%9C-%ED%94%84%EB%A1%9C%EB%AF%B8%EC%8A%A4-%EB%8B%A4%EB%A3%A8%EA%B8%B0</link>
            <guid>https://velog.io/@mh-lee/Redux-Redux-Saga%EB%A1%9C-%ED%94%84%EB%A1%9C%EB%AF%B8%EC%8A%A4-%EB%8B%A4%EB%A3%A8%EA%B8%B0</guid>
            <pubDate>Tue, 11 Jul 2023 06:52:23 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Velopert님의 블로그를 보면서 공부한 내용을 정리한 글입니다. <a href="https://react.vlpt.us/redux-middleware/04-redux-thunk.html">Redux-Thunk</a></p>
</blockquote>
<blockquote>
<p>지난 포스팅에서는 setTimeout을 이용한 간단한 비동기 처리를 구현하였지만, 이번 포스팅에서는 실제 API의 요청으로 비동기 처리를 하는 작업에 대해 공부한 내용을 포스팅 하려고 한다.</p>
</blockquote>
<h1 id="1-json-server로-api-세팅">1. json-server로 API 세팅</h1>
<p>보통은 express나 spring을 통해 backend 서버를 구현하지만, 연습의 용도이기 때문에 json-server로 간단한 웹 서버를 구현해보려고 한다.</p>
<p>root 디렉토리에 data.json파일을 만들고 다음과 같이 저장한다.</p>
<pre><code class="language-json">// src/data.json
{
  &quot;posts&quot;: [
    {
      &quot;id&quot;: 1,
      &quot;title&quot;: &quot;리덕스 미들웨어를 배워봅시다&quot;,
      &quot;body&quot;: &quot;리덕스 미들웨어를 직접 만들어보면 이해하기 쉽죠.&quot;
    },
    {
      &quot;id&quot;: 2,
      &quot;title&quot;: &quot;redux-thunk를 사용해봅시다&quot;,
      &quot;body&quot;: &quot;redux-thunk를 사용해서 비동기 작업을 처리해봅시다!&quot;
    },
    {
      &quot;id&quot;: 3,
      &quot;title&quot;: &quot;redux-saga도 사용해봅시다&quot;,
      &quot;body&quot;: &quot;나중엔 redux-saga를 사용해서 비동기 작업을 처리하는 방법도 배워볼 거예요.&quot;
    }
  ]
}</code></pre>
<pre><code class="language-bash">npx json-server ./data.json --port 4000</code></pre>
<p>이제 data.json을 기반으로 4000번 포트에 가상 웹서버가 열릴 것이다. </p>
<p><img src="https://velog.velcdn.com/images/mh-lee/post/2ea3b9a1-1437-4a13-8cc5-7c98e8d87005/image.png" alt=""></p>
<h1 id="2-thunk로-promise-처리-reducer-생성">2. thunk로 promise 처리 (Reducer 생성)</h1>
<p>우선 클라이언트에서 서버로부터 API 요청을 보내야하니 axios를 설치한다</p>
<pre><code>npm i axios</code></pre><pre><code class="language-js">// api/posts.js
import axios from &#39;axios&#39;;

export const getPosts = async () =&gt; {
  const response = await axios.get(&#39;http://localhost:4000/posts&#39;);
  return response.data;
};

export const getPostById = async id =&gt; {
  const response = await axios.get(`http://localhost:4000/posts/${id}`);
  return response.data;
};</code></pre>
<p>API 요청을 하는 함수를 따로 작성하였으니 액션 타입을 선언하고, 액션 생성함수를 만든 뒤, posts라는 리듀서를 생생해보겠다.</p>
<p>프로미스를 다루는 리덕스 모듈을 다룰 땐 다음과 같은 사항을 고려해야 한다.</p>
<ol>
<li>프로미스가 시작, 성공, 실패했을때 다른 액션을 디스패치해야한다.</li>
<li>각 프로미스마다 thunk 함수를 만들어주어야 한다.</li>
<li>리듀서에서 액션에 따라 로딩중, 결과, 에러 상태를 변경해주어야 한다.</li>
</ol>
<pre><code class="language-jsx">// modules/posts.js
import * as postsAPI from &#39;../api/posts&#39;; // api/posts 안의 함수 모두 불러오기

/* 액션 타입 */

// 포스트 여러개 조회하기
const GET_POSTS = &#39;GET_POSTS&#39;; // 요청 시작
const GET_POSTS_SUCCESS = &#39;GET_POSTS_SUCCESS&#39;; // 요청 성공
const GET_POSTS_ERROR = &#39;GET_POSTS_ERROR&#39;; // 요청 실패

// 포스트 하나 조회하기
const GET_POST = &#39;GET_POST&#39;;
const GET_POST_SUCCESS = &#39;GET_POST_SUCCESS&#39;;
const GET_POST_ERROR = &#39;GET_POST_ERROR&#39;;

// thunk 를 사용 할 때, 꼭 모든 액션들에 대하여 액션 생성함수를 만들 필요는 없습니다.
// 그냥 thunk 함수에서 바로 액션 객체를 만들어주어도 괜찮습니다.

export const getPosts = () =&gt; async dispatch =&gt; {
  dispatch({ type: GET_POSTS }); // 요청이 시작됨
  try {
    const posts = await postsAPI.getPosts(); // API 호출
    dispatch({ type: GET_POSTS_SUCCESS, posts }); // 성공
  } catch (e) {
    dispatch({ type: GET_POSTS_ERROR, error: e }); // 실패
  }
};

// thunk 함수에서도 파라미터를 받아와서 사용 할 수 있습니다.
export const getPost = id =&gt; async dispatch =&gt; {
  dispatch({ type: GET_POST }); // 요청이 시작됨
  try {
    const post = await postsAPI.getPostById(id); // API 호출
    dispatch({ type: GET_POST_SUCCESS, post }); // 성공
  } catch (e) {
    dispatch({ type: GET_POST_ERROR, error: e }); // 실패
  }
};

const initialState = {
  posts: {
    loading: false,
    data: null,
    error: null
  },
  post: {
    loading: false,
    data: null,
    error: null
  }
};

export default function posts(state = initialState, action) {
  switch (action.type) {
    case GET_POSTS:
      return {
        ...state,
        posts: {
          loading: true,
          data: null,
          error: null
        }
      };
    case GET_POSTS_SUCCESS:
      return {
        ...state,
        posts: {
          loading: true,
          data: action.posts,
          error: null
        }
      };
    case GET_POSTS_ERROR:
      return {
        ...state,
        posts: {
          loading: true,
          data: null,
          error: action.error
        }
      };
    case GET_POST:
      return {
        ...state,
        post: {
          loading: true,
          data: null,
          error: null
        }
      };
    case GET_POST_SUCCESS:
      return {
        ...state,
        post: {
          loading: true,
          data: action.post,
          error: null
        }
      };
    case GET_POST_ERROR:
      return {
        ...state,
        post: {
          loading: true,
          data: null,
          error: action.error
        }
      };
    default:
      return state;
  }
}</code></pre>
<blockquote>
<p>액션 생성자 함수는 dispatch를 인자로 받아 사용하며, 비동기 작업을 수행한 후에 원하는 타이밍에 dispatch 할 수 있다.</p>
</blockquote>
<p>리듀서 함수 작성이 끝났으면 root 리듀서로 등록해준다.</p>
<pre><code class="language-jsx">// modules/index.js
import { combineReducers } from &#39;redux&#39;;
import counter from &#39;./counter&#39;;
import posts from &#39;./posts&#39;;

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

export default rootReducer;</code></pre>
<h1 id="3-client에서-보여주기">3. client에서 보여주기</h1>
<pre><code class="language-jsx">// components/PostList.js
import React from &#39;react&#39;;

function PostList({ posts }) {
  return (
    &lt;ul&gt;
      {posts.map(post =&gt; (
        &lt;li key={post.id}&gt;
          {post.title}
        &lt;/li&gt;
      ))}
    &lt;/ul&gt;
  );
}

export default PostList;

// containers/PostListContainer.js
import React, { useEffect } from &#39;react&#39;;
import { useSelector, useDispatch } from &#39;react-redux&#39;;
import PostList from &#39;../components/PostList&#39;;
import { getPosts } from &#39;../modules/posts&#39;;

function PostListContainer() {
  const { data, loading, error } = useSelector(state =&gt; state.posts.posts);
  const dispatch = useDispatch();

  // 컴포넌트 마운트 후 포스트 목록 요청
  useEffect(() =&gt; {
    dispatch(getPosts());
  }, [dispatch]);

  if (loading) return &lt;div&gt;로딩중...&lt;/div&gt;;
  if (error) return &lt;div&gt;에러 발생!&lt;/div&gt;;
  if (!data) return null;
  return &lt;PostList posts={data} /&gt;;
}

export default PostListContainer;</code></pre>
<blockquote>
<p>이전에 initialState로 loading, data, error 객체를 등록해주었기 때문에 client에서도 selector를 이용해 data, loading, error를 받고 그에 따라서 UI를 다르게 보여준다. 포스트 목록 요청은 컴포넌트가 처음 마운트 될 때 dispatch함수를 통해서 요청을 해준다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/mh-lee/post/a390ef2e-dd89-40ec-901b-d029836a52b4/image.png" alt=""></p>
<p>logger로 action이 올바르게 처리 된 것을 확인할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Redux] Redux-thunk로 비동기 처리]]></title>
            <link>https://velog.io/@mh-lee/Redux-Redux-thunk%EB%A1%9C-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%B2%98%EB%A6%AC</link>
            <guid>https://velog.io/@mh-lee/Redux-Redux-thunk%EB%A1%9C-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%B2%98%EB%A6%AC</guid>
            <pubDate>Mon, 10 Jul 2023 10:34:13 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Velopert님의 블로그를 보면서 공부한 내용을 정리한 글입니다. <a href="https://react.vlpt.us/redux-middleware/04-redux-thunk.html">Redux-Thunk</a></p>
</blockquote>
<h1 id="1-redux-thunk를-쓰는-이유">1. Redux-Thunk를 쓰는 이유</h1>
<blockquote>
<p>이전에는 Redux로 전역 상태 관리를 하는 방법을 알아보았다. Redux로 전역 관리를 하는 패턴을 요약하자면 액션 타입 선언 -&gt; 액션 생성함수(필수는 아님) -&gt; 리듀서 생성하는 형태이다. </p>
</blockquote>
<p>Redux에서 액션 생성함수에서는 일반적인 액션 객체밖에 사용할 수 없다. <strong>(Redux에서 액션은 순수한 객체로 이루어져 있어야 하기 때문에 비동기 작업을 처리할 때에는 Redux의 기본 동작과 맞지 않다.)</strong> 그런데 Redux-Thunk를 사용하면 액션 생성 함수에서 일반적인 액션 객체뿐만 아니라, 함수를 반환하여 비동기 작업을 수행할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/mh-lee/post/bf5cbc47-b6ad-47c2-926c-6cafa930e814/image.png" alt=""></p>
<p>Redux-Thunk는 Redux의 미들웨어 중 하나이다. 비동기 작업을 처리하는 액션 생성자 함수를 반환하는 대신, 함수를 반환하여 Redux-Thunk 미들웨어에 의해 처리된다. 이 함수를 dispath를 인자로 받아서 사용할 수 있으며, 비동기 작업을 수행한 후에 원하는 타이밍에 액션을 디스패치 할 수 있다.</p>
<h1 id="2-redux-thunk">2. Redux-Thunk</h1>
<p>코드로 살펴보면 다음과 같다.</p>
<pre><code class="language-bash">npm install redux-thunk</code></pre>
<pre><code class="language-jsx">import React from &#39;react&#39;;
import ReactDOM from &#39;react-dom/client&#39;;
import App from &#39;./App&#39;;
import reportWebVitals from &#39;./reportWebVitals&#39;;


import { applyMiddleware, legacy_createStore as createStore } from &#39;redux&#39;;
import { Provider } from &#39;react-redux&#39;;
import rootReducer from &#39;./modules&#39;;
import logger from &#39;redux-logger&#39;;

import { composeWithDevTools } from &#39;redux-devtools-extension&#39;;

import ReduxThunk from &quot;redux-thunk&quot;;

import {
  BrowserRouter,
} from &quot;react-router-dom&quot;;

// logger를 사용하려면 logger를 제일 마지막에 두어야함.
const store = createStore(
  rootReducer,
  composeWithDevTools(
    applyMiddleware(
      ReduxThunk, 
      logger
    ))
);

const root = ReactDOM.createRoot(document.getElementById(&#39;root&#39;));
root.render(
  &lt;React.StrictMode&gt;
    &lt;BrowserRouter &gt;
      &lt;Provider store={store}&gt;
        &lt;App /&gt;
      &lt;/Provider&gt;
    &lt;/BrowserRouter&gt;
  &lt;/React.StrictMode&gt;
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();</code></pre>
<p>간단한 delay를 이용한 비동기 작업을 Thunk로 처리해보았다.</p>
<pre><code class="language-jsx">// 액션 타입
const INCREASE = &#39;INCREASE&#39;;
const DECREASE = &#39;DECREASE&#39;;

// 액션 생성 함수
export const increase = () =&gt; ({ type: INCREASE });
export const decrease = () =&gt; ({ type: DECREASE });


// 위에서 언급했듯이 dispatch를 인자로 받고, getState로 현재 상태를 조회도 할 수 있다.
// getState를 쓰지 않는다면 굳이 파라미터로 받아올 필요 없습니다.
export const increaseAsync = () =&gt; dispatch =&gt; {
  setTimeout(() =&gt; dispatch(increase()), 1000);
};
export const decreaseAsync = () =&gt; dispatch =&gt; {
  setTimeout(() =&gt; dispatch(decrease()), 1000);
};

// 초깃값 (초기 상태)
const initialState = 0;

export default function counter(state = initialState, action) {
  switch (action.type) {
    case INCREASE:
      return state + 1;
    case DECREASE:
      return state - 1;
    default:
      return state;
  }
}</code></pre>
<pre><code class="language-jsx">// store 생성
// modules/index.js
import { combineReducers } from &#39;redux&#39;;
import counter from &#39;./counter&#39;;

const rootReducer = combineReducers({ counter });

export default rootReducer;</code></pre>
<p>이제 dispatch로 액션을 발생시키는 코드는 다음과 같다.</p>
<pre><code class="language-jsx">import React from &#39;react&#39;;
import Counter from &#39;../components/Counter&#39;;
import { useSelector, useDispatch } from &#39;react-redux&#39;;
import { increaseAsync, decreaseAsync } from &#39;../modules/counter&#39;;

function CounterContainer() {
  const number = useSelector(state =&gt; state.counter);
  const dispatch = useDispatch();

  const onIncrease = () =&gt; {
    dispatch(increaseAsync());
  };
  const onDecrease = () =&gt; {
    dispatch(decreaseAsync());
  };

  return (
    &lt;Counter number={number} onIncrease={onIncrease} onDecrease={onDecrease} /&gt;
  );
}

export default CounterContainer;</code></pre>
<p><img src="https://velog.velcdn.com/images/mh-lee/post/64f4006a-06e7-469e-af33-d27736f5e259/image.png" alt=""></p>
<p>확인해보면 +1버튼을 눌렀을때 1초가 딜레이되는 것을 확인할 수 있다. redux-logger를 함께 사용하면 console로 샅태 변화를 바로 확인할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Redux] Redux로 전역 상태 관리하기 (feat. RTK(Redux Toolkit) ]]></title>
            <link>https://velog.io/@mh-lee/Redux-Redux%EB%A1%9C-%EC%A0%84%EC%97%AD-%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@mh-lee/Redux-Redux%EB%A1%9C-%EC%A0%84%EC%97%AD-%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 08 Jul 2023 10:21:30 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Redux 공식문서에서는 현재 Redux 로직을 작성하고 있다면 RTK(Redux Toolkit)을 이용하여 코드를 작성하는 것을 추천하고 있다. 하지만 RTK를 배우기 전에 Redux의 개념을 복습하고, RTK와 어떠한 차이가 있는지 몸소 느끼는 것이 좋을 것이라 생각해 Redux와 RTK 모두 다루어볼 생각이다. <a href="https://react.vlpt.us/redux/01-keywords.html">벨로퍼트님의 블로그</a>와 <a href="https://ko.redux.js.org/">Redux 공식문서</a>를 참고하였습니다.</p>
</blockquote>
<h1 id="요즘-트렌드는-rtk">요즘 트렌드는 RTK?</h1>
<p>자세한 내용은 <a href="https://ko.redux.js.org/introduction/why-rtk-is-redux-today">Redux 공식문서(Redux Toolkit이 오늘날 Redux를 사용하는 방법인 이유)</a>을 참고하면 된다. 간단히 요약하자면</p>
<p>Redux를 사용하기 위해서는 다음과 같은 동작들이 보통 포함되게 된다.</p>
<ul>
<li>액션 객체를 생성하는 액션 생성자</li>
<li>부수 효과를 가능하게 하는 미들웨어</li>
<li>부수 효과를 가진 동기 또는 비동기 로직을 포함하는 Thunk 함수</li>
<li>ID로 항목 조회를 가능하게 하는 정규화된 상태</li>
</ul>
<p>등등.. 상태관리를 위해서 부수적으로 해줘야할 작업들이 너무 많다는 것이다. 이러한 장황하고 반복적인 코드를 줄이기 위해서 나온 것이 RTK(Redux Toolkit)이다.</p>
<h1 id="redux에서-사용되는-키워드">Redux에서 사용되는 키워드</h1>
<h3 id="액션-action">액션 (Action)</h3>
<ul>
<li>상태에 변화가 필요할 때 발생 (객체로 표현)</li>
<li>type을 필수로 그외의 값들은 개발자 마음대로 생성</li>
</ul>
<h3 id="액션-생성함수-action-creator">액션 생성함수 (Action Creator)</h3>
<ul>
<li>컴포넌트에서 더욱 쉽게 액션을 발생시키기 위함.</li>
<li>필수는 아님.</li>
</ul>
<h3 id="리듀서-reducer">리듀서 (Reducer)</h3>
<ul>
<li>변화를 일으키는 함수</li>
<li>현재의 상태와 액션을 참조하여 새로운 상태를 반환</li>
</ul>
<h3 id="스토어-store">스토어 (Store)</h3>
<ul>
<li>한 애플리케이션 당 하나의 스토어</li>
<li>현재의 앱 상태와 리듀서, 내장함수 포함</li>
</ul>
<h3 id="디스패치-dispatch">디스패치 (dispatch)</h3>
<ul>
<li>스토어의 내장 함수</li>
<li>액션을 발생시키는 것</li>
</ul>
<h3 id="구독-subscribe">구독 (subscribe)</h3>
<ul>
<li>스토어의 내장함수</li>
<li>subscribe 함수에 특정 함수를 전달해주면, 액션이 디스패치 되었을 때 마다 전달해준 함수가 호출 (리액트에서는 connect 함수 또는 useSelector Hook 을 사용)</li>
</ul>
<h1 id="store-생성하기">Store 생성하기</h1>
<blockquote>
<p>용어만 처음 들었을 때 이해하기는 쉽지 않다. 간단한 Counter 예제로 위에서 설명한 용어와 함께 접목해서 이해하면 훨씬 이해가 쉬울 것이다.</p>
</blockquote>
<p>우선 설치부터.</p>
<pre><code class="language-bash">npm i redux
yarn add redux</code></pre>
<p>Redux를 사용할 때 여러가지 패턴이 있지만, Reducer와 Action 관련 코드들을 하나의 파일에 몰아서 작성하는 패턴인 Duck 패턴을 이용해서 예시를 들도록 하겠다.</p>
<pre><code class="language-typescript">// src/modules/counter.ts
/* 액션 타입 만들기 */
// Ducks 패턴을 따를땐 액션의 이름에 접두사를 넣어주세요.
// 이렇게 하면 다른 모듈과 액션 이름이 중복되는 것을 방지 할 수 있습니다.
/* 
  ** 여기서 as const라는 키워드를 사용하지 않으면 ReturnType을 사용하게 됐을 때 
  ** type의 타입이 무조건 string으로 처리되기 때문에 
  ** Reducer를 제대로 구현할 수 없습니다. 
*/
const SET_DIFF = &#39;counter/SET_DIFF&#39; as const;
const INCREASE = &#39;counter/INCREASE&#39; as const;
const DECREASE = &#39;counter/DECREASE&#39; as const;

/* 액션 생성함수 만들기 */
// 액션 생성함수를 만들고 export 키워드를 사용해서 내보내주세요.
export const setDiff = (diff: any) =&gt; ({ type: SET_DIFF, diff });
export const increase = () =&gt; ({ type: INCREASE });
export const decrease = () =&gt; ({ type: DECREASE });

/* 초기 상태 선언 */
const initialState = {
  number: 0,
  diff: 1
};

// ReturnType 은 함수에서 반환하는 타입을 가져올 수 있게 해주는 유틸 타입입니다.
type CounterAction = 
  | ReturnType&lt;typeof increase&gt;
  | ReturnType&lt;typeof decrease&gt;
  | ReturnType&lt;typeof setDiff&gt;;


export default function counter(state = initialState, action: CounterAction) {
  switch (action.type) {
    case SET_DIFF: 
      return {
        ...state,
        diff: action.diff
      };
    case INCREASE: 
      return {
        ...state,
        number: state.number + state.diff
      };
    case DECREASE:
      return {
        ...state,
        number: state.number - state.diff
      };
    default: 
      return state;
  }
};</code></pre>
<p>여기서 바로 아래에 createStore를 이용하여 store를 생성할 수도 있지만<strong>(아까 언급했듯이 하나의 어플리케이션에서는 하나의 스토어를 사용)</strong> 보통 프로젝트에서는 여러 Reducer가 필요하기 때문에 여러 Reducer를 합칠 수 있는 combineReducers라는 함수를 이용해서 rootReducer를 생성한 후 root 디렉토리의 index.tsx에서 store를 생성하는 방식을 많이 사용한다.</p>
<pre><code class="language-typescript">// modules/index.ts
import { combineReducers } from &#39;redux&#39;;
import counter from &#39;./counter&#39;;

const rootReducer = combineReducers({
  counter,
  // 다른 Reducer
});

export default rootReducer;</code></pre>
<p>root 디렉토리의 index.tsx에서 store를 생성하려면 react-redux라는 라이브러리를 이용해야한다.</p>
<pre><code class="language-bash">npm i react-redux @redux-devtools/extension
yarn add react-redux @redux-devtools/extension</code></pre>
<p>그 후 Provider라는 컴포넌트를 불러와서 index.tsx에 다음과 같이 저장한다.</p>
<pre><code class="language-typescript">import React from &#39;react&#39;;
import ReactDOM from &#39;react-dom/client&#39;;
import &#39;./index.css&#39;;
import App from &#39;./App&#39;;
import reportWebVitals from &#39;./reportWebVitals&#39;;
import { legacy_createStore as createStore } from &#39;redux&#39;

import rootReducer from &#39;./modules&#39;;
import { Provider } from &#39;react-redux&#39;;
import { composeWithDevTools } from &#39;@redux-devtools/extension&#39;;

const store = createStore(rootReducer, composeWithDevTools());
// console.log(store.getState());

const root = ReactDOM.createRoot(
  document.getElementById(&#39;root&#39;) as HTMLElement
);
root.render(
  &lt;React.StrictMode&gt;
    &lt;Provider store={store}&gt;
      &lt;App /&gt;
    &lt;/Provider&gt;
  &lt;/React.StrictMode&gt;
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();</code></pre>
<p>chrome 검사창에서 Redux를 검사하고 싶으면 @redux-devtools/extension를 설치하고 createStore 옆에 composeWithDevTools 함수를 호출하면 된다.</p>
<p>App 을 감싸게 되면 우리가 렌더링하는 그 어떤 컴포넌트던지 리덕스 스토어에 접근 할 수 있다.</p>
<h1 id="컴포넌트에서-리덕스-스토어에-접근하기">컴포넌트에서 리덕스 스토어에 접근하기</h1>
<h3 id="프리젠테이션-컴포넌트-만들기">프리젠테이션 컴포넌트 만들기</h3>
<p>프리젠테이셔널 컴포넌트란, 리덕스 스토어에 직접적으로 접근하지 않고 필요한 값 또는 함수를 props로만 받아와서 사용하는 컴포넌트이다.</p>
<pre><code class="language-typescript">// src/components/Counter.tsx
import React from &quot;react&quot;;

interface CounterProps {
  number: number;
  diff: number;
  onIncrease: () =&gt; void;
  onDecrease: () =&gt; void;
  onSetDiff: (n: number) =&gt; void;
}

function Counter({
  number,
  diff,
  onIncrease,
  onDecrease,
  onSetDiff
}: CounterProps) {
  const onChange = (e: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
    // e.target.value 의 타입은 문자열이기 때문에 숫자로 변환해주어야 합니다.
    onSetDiff(parseInt(e.target.value, 10));
  };

  return (
    &lt;div&gt;
      &lt;h1&gt;{number}&lt;/h1&gt;
      &lt;div&gt;
        &lt;input type=&quot;number&quot; value={diff} min=&quot;1&quot; onChange={onChange} /&gt;
        &lt;button onClick={onIncrease}&gt;+&lt;/button&gt;
        &lt;button onClick={onDecrease}&gt;-&lt;/button&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
}

export default Counter;</code></pre>
<p>프리젠테이셔널 컴포넌트에선 주로 이렇게 UI를 선언하는 것에 집중하며, 필요한 값들이나 함수는 props 로 받아와서 사용하는 형태로 구현한다.</p>
<h3 id="컨테이너-컴포넌트-만들기">컨테이너 컴포넌트 만들기</h3>
<p>컨테이너 컴포넌트란, 리덕스 스토어의 상태를 조회하거나, 액션을 디스패치 할 수 있는 컴포넌트를 의미한다. 그리고, HTML 태그들을 사용하지 않고 다른 프리젠테이셔널 컴포넌트들을 불러와서 사용한다.</p>
<pre><code class="language-typescript">// src/container/CounterContainer.tsx
import React from &#39;react&#39;;
import { useSelector, useDispatch } from &#39;react-redux&#39;;
import Counter from &#39;../components/Counter&#39;;
import { increase, decrease, setDiff } from &#39;../modules/counter&#39;;

function CounterContainer() {
  // useSelector는 리덕스 스토어의 상태를 조회하는 Hook입니다.
  // state의 값은 store.getState() 함수를 호출했을 때 나타나는 결과물과 동일합니다.
  const number = useSelector(state =&gt; state.counter.number);
  const diff = useSelector(state =&gt; state.counter.diff);

  // useDispatch 는 리덕스 스토어의 dispatch 를 함수에서 사용 할 수 있게 해주는 Hook 입니다.
  const dispatch = useDispatch();
  // 각 액션들을 디스패치하는 함수들을 만드세요
  const onIncrease = () =&gt; dispatch(increase());
  const onDecrease = () =&gt; dispatch(decrease());
  const onSetDiff = (diff: number) =&gt; dispatch(setDiff(diff));
  return (
    &lt;Counter
      // 상태와
      number={number}
      diff={diff}
      // 액션을 디스패치 하는 함수들을 props로 넣어줍니다.
      onIncrease={onIncrease}
      onDecrease={onDecrease}
      onSetDiff={onSetDiff}
    /&gt;
  );
}

export default CounterContainer;</code></pre>
<p>이제 App.tsx에서 CounterContainer를 렌더링해서 카운터가 잘 동작하는지 확인한다.</p>
<p><img src="https://velog.velcdn.com/images/mh-lee/post/e67d2173-084d-4897-804b-1861868f3750/image.png" alt=""></p>
<p>크롬 개발자 도구 중 Redux를 이용하여 State의 변화 등을 관찰할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JEST] Mock Function은 무엇일까?]]></title>
            <link>https://velog.io/@mh-lee/JEST-Mock-Function%EC%9D%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C</link>
            <guid>https://velog.io/@mh-lee/JEST-Mock-Function%EC%9D%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C</guid>
            <pubDate>Sun, 21 May 2023 09:17:51 GMT</pubDate>
            <description><![CDATA[<h1 id="0-jest에서-mock이란">0. Jest에서 Mock이란?</h1>
<p>Jest에서 &quot;mock&quot;은 테스트 중에 특정 객체나 함수의 동작을 흉내내거나 대체하는 기능을 의미한다. 이를 통해 테스트 시나리오에 필요한 가짜 동작을 정의하거나, 의존성을 가진 모듈을 격리하여 테스트할 수 있다. 즉, 테스트하기 위해서 흉내만 내는 함수라고 생각할 수 있겠다.</p>
<p>만약 UserDB에 접근해서 userList를 select해오는 작업이 필요하다고 가정해보자. 이를 직접 구현하려고 한다면 다음과 같은 문제점들이 발생한다. </p>
<ul>
<li>구현하기 위해 작성해야될 코드가 많아진다. 테스트를 위한 코드보다 길어질 수 있다. </li>
<li>외부 요인에 영향을 받는다. (네트워크 환경, DB 상태 등)</li>
<li><strong>“테스트는 deterministic 해야한다.&quot;</strong> 라는 원칙에 위배됨. 왜냐하면 단위 테스트가 단독으로 고립되어 있지 않고, 외부 환경에 의존하기 때문이다.</li>
</ul>
<p>테스트에서는 같은 코드는 동일한 결과를 내는 것이 중요하기 때문에 mock function을 쓰는 것이 효율적일 것이다.</p>
<h1 id="1-jestfn-사용법">1. jest.fn() 사용법</h1>
<pre><code class="language-javascript">// mock function
const mockFn = jest.fn();

function forEachAdd1(arr) {
  arr.forEach(num =&gt; {
    // fn(num+1)
    mockFn(num+1);
  })
}
forEachAdd1([10, 20, 30]);

test(&quot;함수 호출은 3번 됩니다.&quot;, () =&gt; {
  console.log(mockFn.mock.calls);
  expect(mockFn.mock.calls.length).toBe(3);
})
test(&quot;전달된 값은 11, 21, 31입니다.&quot;, () =&gt; {
  expect(mockFn.mock.calls[0][0]).toBe(11);
  expect(mockFn.mock.calls[1][0]).toBe(21);
  expect(mockFn.mock.calls[2][0]).toBe(31);
})</code></pre>
<p><img src="https://velog.velcdn.com/images/mh-lee/post/9d749a94-25e4-4f54-b625-d16c640c5628/image.png" alt=""></p>
<blockquote>
<p>각 배열에서 forEach 문을 돌아서 배열의 모든 요소에 1을 추가하는 함수를 구현해본다고 하자. fn이란 함수를 따로 정의하여 1을 증가시킬 수도 있지만, mock Function을 이용하여 함수를 따로 정의하지 않고 구현한 모습이다. 위 사진에서 console로 확인할 수 있듯이 mockFn.mock.call에는 현재까지 호출되어 저장된 값이 배열로 저장된다.</p>
</blockquote>
<p>다음은 mock.results에 대해서 알아보자.</p>
<pre><code class="language-javascript">// mock function
const mockFn = jest.fn(num =&gt; num + 1);

mockFn(10);
mockFn(20);
mockFn(30);

test(&quot;함수 호출은 3번 됩니다.&quot;, () =&gt; {
  console.log(mockFn.mock.results);
  expect(mockFn.mock.results.length).toBe(3);
})</code></pre>
<p>처음 구현한 함수에서 방식을 배열을 받지 않고 값을 받는 형태로 변경한 모습이다. results에는 다음과 같은 값들이 들어간다.</p>
<p><img src="https://velog.velcdn.com/images/mh-lee/post/a4308a97-5c0a-47e3-a479-81d05656871d/image.png" alt=""></p>
<pre><code class="language-javascript">const mockFn = jest.fn();
mockFn
  .mockReturnValueOnce(10)
  .mockReturnValueOnce(20)
  .mockReturnValueOnce(30)
  .mockReturnValue(40)

mockFn();
mockFn();
mockFn();
mockFn();

test(&quot;함수 호출은 4번 됩니다.&quot;, () =&gt; {
  console.log(mockFn.mock.results);
  expect(mockFn.mock.results.length).toBe(4);
})</code></pre>
<p>mockReturnValue(리턴 값) 함수를 이용해서 가짜 함수가 어떤 값을 리턴해야할지 설정해줄 수 있다.</p>
<pre><code class="language-javascript">const mockFn = jest.fn();

mockFn
  .mockResolvedValue({ name: &quot;kim&quot;})

test(&quot;받아온 이름은 Kim&quot;, () =&gt; {
  mockFn().then(res =&gt; {
    expect(res.name).toBe(&quot;kim&quot;)
  })
})</code></pre>
<p>또한, mockResolvedValue를 통해서 Promise처럼 동작하는 가짜 비동기 함수를 만들 수도 있다.</p>
<h1 id="2-jestspyon-사용법">2. jest.spyOn() 사용법</h1>
<p>mocking에는 spyOn이라는 함수를 사용할 수 있다. 어떤 객체에 속한 함수의 구현을 가짜로 대체하지 않고, 해당 함수의 호출 여부와 어떻게 호출되었는지만을 알아내야 할 때가 있다. 이럴 때, Jest에서 제공하는 jest.spyOn(object, methodName) 함수를 이용하면 된다.</p>
<pre><code class="language-javascript">// fn.js
const fn = {
  add: (a, b) =&gt; a + b,
};

// fn.test.js
const fn = require(&#39;./fn&#39;);
const spyFn = jest.spyOn(fn, &quot;add&quot;);

const result = fn.add(2, 3);
expect(spyFn).toBeCalledTimes(1);
expect(spyFn).toBeCalledWith(2, 3);
expect(result).toBe(5);</code></pre>
<blockquote>
<p>jest.spyOn() 함수를 이용해서 fn 객체의 add라는 함수에 스파이를 붙였습니다. 따라서 add 함수를 호출 후에 호출 횟수와 어떤 인자가 넘어갔는지 감증할 수 있다. 하지만 가짜 함수로 대체한 것은 아니기 때문에 결과 값은 원래 구현대로 2와 3의 합인 5가 되는 것을 알 수 있다.</p>
</blockquote>
<h1 id="3-jestmock-사용법">3. jest.mock() 사용법</h1>
<p>만약 DB에 접속해서 user의 가입 정보를 저장하는 함수가 있다고 가정해보자. test를 하기 위해서 createUser 함수를 쓰는 경우, 실제로 함수를 호출하게 되면 DB에 다시 접속해서 저장된 user의 값을 지워주어야 한다. 이럴때 사용하면 좋은 것이 jest.mock()를 이용한 module mocking이다. </p>
<pre><code class="language-javascript">const fn = require(&#39;./fn&#39;);

jest.mock(&quot;./fn&quot;);
fn.createUser.mockReturnValue({ name: &quot;Mike&quot;});

test(&quot;유저를 만든다&quot;, () =&gt; {
  const user = fn.createUser(&quot;Mike&quot;);
  expect(user.name).toBe(&quot;Mike&quot;);
})</code></pre>
<p>위와 같이 사용하면 실제로 user의 정보를 DB에 저장하지 않고 Test를 수행할 수 있다.</p>
<h1 id="4-jest에서-사용하는-유용한-matcher">4. jest에서 사용하는 유용한 Matcher</h1>
<blockquote>
<ul>
<li>toBeCalled(): 한번이라도 호출되었으면 통과됨.</li>
</ul>
</blockquote>
<ul>
<li>toBeCalledTimes(): 정확히 몇번 호출되었는지를 확인하고 맞으면 통과됨.</li>
<li>toBeCalledWith(): 인수로 어떤 값들을 받았는지 체크하고 인수로 받은 값을 받은 적이 있다면 통과됨.</li>
<li>lastCalledWith(): 마지막으로 실행된 인수를 체크하고 맞으면 통과됨.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JEST] Jest로 비동기 코드 테스트 & 테스트 전후 작업]]></title>
            <link>https://velog.io/@mh-lee/JEST-Jest%EB%A1%9C-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%BD%94%EB%93%9C-%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%A0%84%ED%9B%84-%EC%9E%91%EC%97%85</link>
            <guid>https://velog.io/@mh-lee/JEST-Jest%EB%A1%9C-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%BD%94%EB%93%9C-%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%A0%84%ED%9B%84-%EC%9E%91%EC%97%85</guid>
            <pubDate>Sun, 21 May 2023 05:48:52 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Javascript로 코드를 작성하다보면 비동기 통신을 할 때가 굉장히 많다. Jest로 비동기 통신을 어떻게 테스트 할 수 있는 지 알아보자. 추가로 describe 문법과 테스트 전후에 해줄 수 있는 헬퍼에 대해서 알아보도록 하겠다.</p>
</blockquote>
<h1 id="1-jest로-callback-함수-테스트">1. Jest로 Callback 함수 테스트</h1>
<p>다음과 같은 비동기 함수가 있다고 해보겠다.</p>
<pre><code class="language-javascript">// fn.js
const fn = {
  add: (num1, num2) =&gt; num1 + num2,
  getName: (callback) =&gt; {
    const name = &quot;Mike&quot;;
    setTimeout(() =&gt; {
      callback(name);
    }, 3000)
  }
};

module.exports = fn;</code></pre>
<p>위의 함수를 테스트하는 코드는 다음과 같이 작성할 수 있다.</p>
<pre><code class="language-javascript">// fn.test.js
const fn = require(&#39;./fn&#39;);

test(&#39;3초후 받아온 이름은 Mike&#39;, () =&gt; {
  function callback(name) {
    expect(name).toBe(&quot;Mike&quot;);
  }
  fn.getName(callback);
})</code></pre>
<p>우리는 위의 함수를 Jest Runner로 실행시키면 대략 3초가 걸리고 Test가 통과할 것이라 예측할 수 있다. </p>
<p><img src="https://velog.velcdn.com/images/mh-lee/post/5c7c1737-4fcc-4fc6-9772-9e184fe66cfa/image.png" alt=""></p>
<p>1초만에 Test가 끝난 것을 확인할 수 있다. toBe문을 Mike가 아닌 다른 이름으로 바꾸면 실패를 예측하였는데 이 또한 성공한 것을 확인할 수 있었다. </p>
<blockquote>
<p>이는 Jest Runner가 비동기임을 예측하지 못하고 callback 함수를 호출시키지 않는다고 유추해 볼 수 있다. 해결 방법은 test의 callback 함수에 done을 넣어서 명시적으로 비동기 코드임을 Jest Runner에게 알리는 방법이 있다. done이 호출되기 전까지 Jest는 테스트를 끝내지 않기 떄문에 callback 함수가 호출될 때까지 기다린 뒤 테스트를 마칠 수 있다. 수정된 코드는 다음과 같다.</p>
</blockquote>
<pre><code class="language-javascript">// fn.test.js
const fn = require(&#39;./fn&#39;);

test(&#39;3초후 받아온 이름은 Mike&#39;, (done) =&gt; {
  function callback(name) {
    expect(name).toBe(&quot;Mike&quot;);
    done();
  }
  fn.getName(callback);
})</code></pre>
<p><img src="https://velog.velcdn.com/images/mh-lee/post/bd167cf6-2287-4a7f-b05d-ec1f05a59410/image.png" alt="">
실행 결과 우리가 예상했던 것과 같이 대략 3초가 걸려 Test가 끝나는 것을 확인할 수 있다. </p>
<p>만약 done을 인자로 주고, done을 호출하지 않으면 5초 동안 기다린 뒤 응답이 없으면 실패로 간주한다.</p>
<h1 id="2-jest로-promise-테스트">2. Jest로 Promise 테스트</h1>
<pre><code class="language-javascript">// fn.js
getAge: () =&gt; {
    const age = 30;
    return new Promise((res, rej) =&gt; {
      setTimeout(() =&gt; {
        res(age);
      }, 3000);
    })
  }</code></pre>
<p>위의 fn 객체에 setTimeout으로 3초를 기다렸다가 Promise로 resolve된 나이를 반환하는 함수를 추가하였다. 위 함수에 대해서 test를 작성하면 다음과 같다.</p>
<pre><code class="language-javascript">// fn.test.js
const fn = require(&#39;./fn&#39;);

test(&#39;3초후 받아온 나이는 30&#39;, () =&gt; {
  fn.getAge().then(age =&gt; {
    expect(age).toBe(30);
  })
})</code></pre>
<p><img src="https://velog.velcdn.com/images/mh-lee/post/1dd54358-ff05-453e-8dec-56d8d915a7fc/image.png" alt=""></p>
<p>실행시키면 callback 함수와 같이 우리가 예상했던 대로 3초후에 실행이 되어야 하는데 그렇지 않은 것을 확인할 수 있다. toBe안의 인자를 30이 아닌 다른값으로 바꾸어 보아도 Test가 통과하는 것을 확인할 수 있다.</p>
<blockquote>
<p>해결 방법은 return 문만 추가해주면 원했던 바와 같이 테스트가 수행된다. 테스트 함수가 Promise를 리턴하면 Jest Runner는 리턴된 Promise가 resolve될 때까지 기다려주기 때문이다. 수정된 코드는 다음과 같다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/mh-lee/post/0dab01dc-fd2c-419f-a6af-f355bf51c270/image.png" alt="">
예상과 같이 3초 이상의 시간이 걸려 test가 통과된 것을 확인할 수 있다.</p>
<blockquote>
<p>다른 해결 방법으로는 resolves와 rejects 같은 Matcher를 사용할 수도 있다.</p>
</blockquote>
<pre><code class="language-javascript">// fn.test.js
const fn = require(&#39;./fn&#39;);

test(&#39;3초후 받아온 나이는 30&#39;, () =&gt; {
  return expect(fn.getAge()).resolves.toBe(30)
});</code></pre>
<p>Matcher를 사용하니 훨씬 더 코드가 간결해진 것을 확인할 수 있다. reject에 대한 Matcher는 rejects를 사용하면 된다. rejects에는 toBe보다는 toMatch를 통해서 error 메세지를 비교해주면 될 것이다.</p>
<blockquote>
<p>마지막 방법으로는 async, await를 사용하여 Promise를 처리하는 것이다. 개인적으로 async, await가 익숙해서 그런지 가장 간편해보이는 코드였다.</p>
</blockquote>
<pre><code class="language-javascript">test(&#39;3초후 받아온 나이는 30&#39;, async () =&gt; {
  const age = await fn.getAge();
  expect(age).toBe(30);
});</code></pre>
<p>위와 같이 작성하거나 resolves Matcher를 사용하면</p>
<pre><code class="language-javascript">test(&#39;3초후 받아온 나이는 30&#39;, async () =&gt; {
  await expect(fn.getAge()).resolves.toBe(30);
});</code></pre>
<p>위와 같이 사용할 수 있다.</p>
<h1 id="3-test-전-후에-쓰는-helper">3. Test 전 후에 쓰는 Helper</h1>
<blockquote>
<ol>
<li>beforeEach(fn, timeout): 이 파일의 각 테스트가 실행되기 전에 함수를 실행</li>
<li>afterEach(fn, timeout): 이 파일의 각 테스트가 완료된 후 함수를 실행</li>
<li>beforeAll(fn, timeout): 이 파일의 첫번째 테스트가 실행되기 전에 함수를 실행</li>
<li>afterAll(fn, timeout): 이 파일의 마지막 테스트가 실행되기 전에 함수를 실행</li>
</ol>
</blockquote>
<p><strong>Helper 사용 예시</strong></p>
<ul>
<li>만약 Database에서 전 후에 UserDB에 접속하여 정보를 가져오고 정보를 가져온 후에는 DB의 접속을 끊어주는 Test를 작성한다고 가정해보자. 이럴 떄 사용하면 좋은 것이 afterEach와 같은 Helper이다.</li>
</ul>
<pre><code class="language-javascript">// fn.js
const fn = {
  add: (num1, num2) =&gt; num1 + num2,
  connectUserDb: () =&gt; {
    return new Promise(res =&gt; {
      setTimeout(() =&gt; {
        res({
          name: &quot;Mike&quot;,
          age: 30,
          gender: &quot;male&quot;
        })
      }, 500)
    })
  },
  disconnectUserDb: () =&gt; {
    return new Promise(res =&gt; {
      setTimeout(() =&gt; {
        res();
      }, 500)
    })
  }
};

module.exports = fn;</code></pre>
<pre><code class="language-javascript">// fn.test.js
const fn = require(&#39;./fn&#39;);

let user;
beforeAll(async () =&gt; {
  user = await fn.connectUserDb();
})
afterAll(async () =&gt; {
  await fn.disconnectUserDb();
})

test(&quot;이름은 Mike&quot;, () =&gt; {
  expect(user.name).toBe(&quot;Mike&quot;);
})
test(&quot;나이는 30&quot;, () =&gt; {
  expect(user.age).toBe(30);
})
test(&quot;성별은 남성&quot;, () =&gt; {
  expect(user.gender).toBe(&quot;male&quot;);
})
</code></pre>
<p><img src="https://velog.velcdn.com/images/mh-lee/post/e439bb24-0100-4ce2-99c8-5a483ea43dcf/image.png" alt=""></p>
<p>예상과 같이 앞 뒤로 약 0.5초씩 setTimeout이 실행되어 1초 이상의 시간이 걸린 것을 확인할 수 있다.</p>
<h1 id="4-describe-it">4. Describe, it()</h1>
<blockquote>
<p>테스트 파일에 많은 수의 테스트 함수가 작성되어 있는 경우, 연관된 테스트 함수들끼리 그룹화해놓으면 코드를 읽기가 좋다. 다음과 같이 Jest의 describe() 함수를 통해 여러 개의 테스트 함수를 묶는 것이 가능하다. 이떄 test 대신 it을 사용해도 된다. 두 함수의 기능은 완전히 동일하다. 기존 많이 사용되었던 Mocha나 Jasmin 같은 테스트 라이브러리에서 함수명을 it()을 사용하였기 때문에, Jest에서도 it()을 test() 함수의 별칭으로 제공해주고 있다.</p>
</blockquote>
<pre><code class="language-javascript">const fn = require(&#39;./fn&#39;);

describe(&quot;User DB 관련 작업&quot;, () =&gt; {
  let user;
  beforeAll(async () =&gt; {
    user = await fn.connectUserDb();
  })
  afterAll(async () =&gt; {
    await fn.disconnectUserDb();
  })

  it(&quot;이름은 Mike&quot;, () =&gt; {
    expect(user.name).toBe(&quot;Mike&quot;);
  })
  it(&quot;나이는 30&quot;, () =&gt; {
    expect(user.age).toBe(30);
  })
  it(&quot;성별은 남성&quot;, () =&gt; {
    expect(user.gender).toBe(&quot;male&quot;);
  })
})</code></pre>
<h1 id="reference">Reference</h1>
<p><a href="https://www.daleseo.com/jest-before-after/">Jest로 테스트 전/후 처리하기</a>
<a href="https://www.youtube.com/watch?v=TRZ2XdmctSQ&amp;list=PLZKTXPmaJk8L1xCg_1cRjL5huINlP2JKt&amp;index=4">비동기 코드 테스트 - 자바스크립트 테스트 프레임워크</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JEST] JEST 설치 & 자주 쓰는 Matcher]]></title>
            <link>https://velog.io/@mh-lee/JEST-JEST-%EC%84%A4%EC%B9%98-%EC%9E%90%EC%A3%BC-%EC%93%B0%EB%8A%94-Matcher</link>
            <guid>https://velog.io/@mh-lee/JEST-JEST-%EC%84%A4%EC%B9%98-%EC%9E%90%EC%A3%BC-%EC%93%B0%EB%8A%94-Matcher</guid>
            <pubDate>Sat, 20 May 2023 16:36:53 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>사이드 프로젝트를 하면서 React Testing-Library를 사용해보려고 한다. React Testing-Library는 Jest 기반으로 가장 많이 사용하기 때문에 Jest의 기초를 배워보고 내 사이드 프로젝트에 어떻게 적용했는지를 글로 작성하려고 한다.</p>
</blockquote>
<h1 id="0-jest란">0. Jest란?</h1>
<ul>
<li>Jest는 React를 만든 Facebook에서 선보인 Testing 도구이다. </li>
<li>Jest는 Zero Config의 특징을 가지고 있어서 별도의 설정 없이 바로 작업하는 것을 목표로 한다.</li>
<li>현재 가장 많이 사용되고 있는 Testing 도구이다.</li>
</ul>
<h1 id="1-js에서-jest-설치">1. JS에서 Jest 설치</h1>
<pre><code>mkdir jestTest
npm init
npm install jest -save-dev

// package.json
&quot;script&quot;: {
  &quot;test&quot;: &quot;jest&quot;
}</code></pre><p>파일명에 xxx.test.js로 저장하고 터미널에 npm start 명령어를 입력하게 되면 프로젝트 내에 모든 테스트 파일을 찾아서 테스트를 수행한다. 만약 선택한 파일만 테스트를 수행하고 싶다면</p>
<h1 id="2-jest의-기본적인-패턴">2. Jest의 기본적인 패턴</h1>
<p>숫자를 2개 받아서 두 숫자를 더해주는 함수가 있다고 가정해보자. 다음과 같이 테스트를 작성할 수 있다.</p>
<pre><code class="language-javascript">const fn = require(&#39;./fn&#39;);

test(&#39;1은 1이야.&#39;, () =&gt; {
  expect(1).toBe(1);
})

test(&#39;2 더하기 3은 5야.&#39;, () =&gt; {
  expect(fn.add(2, 3)).toBe(5);
})

test(&#39;3 더하기 3은 5야.&#39;, () =&gt; {
  expect(fn.add(3, 3)).toBe(5);
})</code></pre>
<p>위와 같이 2개의 성공 case와 1개의 실패 case로 test를 작성할 수 있다. 결과는 다음과 같다. toBe로 기대되는 결과값을 적어주면 된다. </p>
<p><img src="https://velog.velcdn.com/images/mh-lee/post/a89a6861-0193-4e94-ab36-7c4425f93092/image.png" alt=""></p>
<p>예상 했던 대로 1개의 실패가 발생한 것을 확인할 수 있다. toBe 앞에 not을 붙이면 기대되는 결과값이 아닌 값을 넣었을 때 pass한다.</p>
<pre><code class="language-javascript">test(&quot;테스트 설명&quot;, () =&gt; {
  expect(&quot;검증 대상&quot;).toXxx(&quot;기대 결과&quot;);
});</code></pre>
<p>Jest는 기본적으로 위와 같은 패턴을 띄고 있는 것을 확인할 수 있다. toXXX 부분에서 사용하는 함수를 Matcher라고 한다. 위의 예시에서 사용된 toBe는 숫자나 문자등 기본 타입을 비교할 때 사용한다. 이 부분에는 여러가지 Matcher가 올 수 있다.</p>
<h1 id="3-자주-사용되는-matcher">3. 자주 사용되는 Matcher</h1>
<blockquote>
<ol>
<li>toBe(): 예측 값이 Primitive Value일 때 사용된다. 해당 값이 일치하면 pass를 반환한다. </li>
<li>toBeCloseTo(): 예측 값이 소수점일 때는(JS는 부동소수점을 이용하기 때문에 toBe로 비교하면 같은 값이어도 통과되지 않기 때문에) toBeCloseTo를 이용해야함.</li>
<li>toEqual(): 객체, 배열을 비교할 때. 해당 값 포함하면 pass를 반환한다.</li>
<li>toStrictEqual(): toEqual에서 더 엄격한 비교를 할 때 사용한다. 객체의 모든 property 값이 같아야 pass를 반환한다.</li>
<li>toBeNull(): Null인 경우 통과된다.</li>
<li>toBeUndefined(): undefined인 경우 통과된다.</li>
<li>toBeDefined(): defined인 경우 통과된다.</li>
<li>toBeTruthy(), toBeFalsy(): toBeTruthy()는 검증 대상이 true로 간주되면 테스트 통과이고, toBeFalsy()는 반대로 false로 간주되는 경우 테스트가 통과된다.</li>
<li>toBeGreaterThan(): 입력한 값보다 크면 통과된다.</li>
<li>toBeGreaterThanOrEqual(): 입력한 값보다 크거나 같으면 통과된다.</li>
<li>toBeGreaterThan(): 입력한 값보다 작으면 통과된다.</li>
<li>toBeGreaterThanOrEqual(): 입력한 값보다 작거나 같으면 통과된다.</li>
<li>toMatch(): 정규식 기반의 테스트가 필요할 때, toMatch() 함수를 사용하면된다.</li>
<li>toContain(): 배열에서 특정 요소가 있는지 확인하고 싶을때 사용한다. 특정 요소가 있다면 통과된다.</li>
<li>toThrow(): 예외 발생 여부를 테스트해야할 때는 toThrow() 함수를 사용하면 된다. toThrow() 함수는 인자도 받는데 문자열을 넘기면 예외 메시지를 비교하고 정규식을 넘기면 정규식 체크를 해준다.</li>
</ol>
</blockquote>
<p>toThrow를 사용할 때는 다른 Matcher들과 다르게 expect() 함수에 넘기는 검증 대상을 함수로 한 번 감싸주어야 한다. 그렇지 않으면 예외 발생 여부를 체크하는 것이 아니라, 테스트 실행 도중 정말 그 예외가 발생하기 때문에 그 테스트는 항상 실패하게 된다. 다음과 arrow function을 이용하여 감싸주면 된다.</p>
<pre><code class="language-javascript">// 무조건 실패하는 코드
expect(getUser(-1)).toThrow();
// 올바른 코드
expect(() =&gt; getUser(-1)).toThrow();</code></pre>
<h1 id="4-마치며">4. 마치며</h1>
<p>이외에도 다양한 matcher가 존재한다. matcher에 대해 더 알고 싶다면 <a href="https://jestjs.io/docs/expect">Jest 공식문서</a>를 참고하면 될 것이다.</p>
<blockquote>
<p>참고한 문서
<a href="https://www.youtube.com/watch?v=_36vt4fBjOQ&amp;list=PLZKTXPmaJk8L1xCg_1cRjL5huINlP2JKt&amp;index=2">코딩 앙마 유튜브 강의</a>
<a href="https://www.daleseo.com/jest-basic/">Jest로 기본적인 테스트 작성하기</a>
<a href="https://jestjs.io/docs/expect">Jest 공식문서</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[MongoDB에서 Collection을 자동으로 생성하기]]></title>
            <link>https://velog.io/@mh-lee/MongoDB%EC%97%90%EC%84%9C-Collection%EC%9D%84-%EC%9E%90%EB%8F%99%EC%9C%BC%EB%A1%9C-%EC%83%9D%EC%84%B1%ED%95%B4%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@mh-lee/MongoDB%EC%97%90%EC%84%9C-Collection%EC%9D%84-%EC%9E%90%EB%8F%99%EC%9C%BC%EB%A1%9C-%EC%83%9D%EC%84%B1%ED%95%B4%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Sun, 14 May 2023 12:38:51 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Nodejs 환경에서 유저가 회원가입을 하면 유저의 _id 값을 이용하여 Collection을 자동으로 생성하는 방법을 알아보자. 구글에 나와있는 방법은 버전이 맞지 않아서 잘 동작하지 않는 것 같아 공식 문서를 참고하였다.</p>
</blockquote>
<p><a href="https://www.mongodb.com/docs/manual/reference/method/db.createCollection/">MongoDB 공식문서 (createCollection)</a></p>
<p><img src="https://velog.velcdn.com/images/mh-lee/post/337020f7-de95-415a-9407-69a420a898b7/image.png" alt=""></p>
<p>우선 회원 가입이 완료되면 userInfo Collection에 다음과 같은 Field들이 저장된다. 여기서 MongoDB에서 자동으로 생성해주는 _id 값을 이용하여 Collection을 생성하려면 다음과 같이 코드를 작성하면 된다.</p>
<pre><code class="language-javascript">...생략

connectToMongo();
  try {
    const db = getDB();
    const collection = db.collection(&#39;userInfo&#39;);
    const isEmailExist = await collection.findOne({ email: enteredValue.email });
    if(isEmailExist) throw new Error(&quot;Email Already Exist!&quot;);
    const { hashedPassword, salt } = await createHashedPassword(enteredValue.password);

    const insertDoc = {
      name: enteredValue.name,
      email: enteredValue.email,
      password: hashedPassword,
      salt: salt
    }

    const result = await collection.insertOne(insertDoc);
    const insertedId = result.insertedId.toString();
    const userCollection = await db.createCollection(insertedId);

    console.log(`Collection Inserted! Collection Name: ${userCollection.collectionName}`);

...생략</code></pre>
<p>우선 userInfo Collection에 접근해서 유저가 입력한 이메일이 DB에 저장된 값들과 중복이 아닌지 확인하고, 중복이 아니라면 crypto 방식을 이용하여 입력한 비밀번호를 암호화한다.</p>
<p>collection.insertOne은 Promise를 반환하기 때문에 async await 문법을 이용하였다. result의 insertedId 객체는 DB에 저장되는 _id값을 반환한다. 이 값은 ObjectId(&#39;xxx&#39;)와 같은 형태로 저장되기 때문에 이를 string으로 반환하려면 toString 함수를 사용하면 된다. </p>
<p>이제 저장할 Collection의 이름을 받아왔으니 createCollection으로 Collection을 생성해주면 된다. createCollection도 Promise를 반환하기 때문에 async await 문법을 이용하였다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Recoil과 Recoil Selector를 이용하여 비동기 처리하기]]></title>
            <link>https://velog.io/@mh-lee/Recoil%EA%B3%BC-Recoil-Selector%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@mh-lee/Recoil%EA%B3%BC-Recoil-Selector%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 05 May 2023 07:58:04 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이 문서는 <a href="https://recoiljs.org/ko/">Recoil 공식문서</a>를 참고하여 제가 이해한 방식으로 작성한 글입니다. 틀린 부분이 있으면 알려주시면 감사하겠습니다!</p>
</blockquote>
<h1 id="0-recoil의-필요성">0. Recoil의 필요성</h1>
<p>Recoil팀에서 말하는 React 자체에 내장된 상태 관리 기능의 한계점은 다음과 같다.</p>
<ul>
<li>컴포넌트의 상태는 공통된 상위요소까지 끌어올려야만 공유가 될 수 있으며, 이 과정에서 거대한 트리가 다시 렌더링 되는 효과를 야기하기도 한다.</li>
</ul>
<blockquote>
<p>React에서 컴포넌트는 상태를 가지고 있을 수 있으며, 이 상태가 변경되면 해당 컴포넌트와 그 자식 컴포넌트들이 다시 렌더링 된다. 그러나, 컴포넌트들 사이에 상태를 공유하고 싶을 때는 해당 상태를 공통된 상위 요소까지 끌어올려야 한다. 이를 Lifting Up이라고 한다. 상태를 끌어올리는 과정에서 문제가 발생할 수 있는데, 상태를 끌어올린 상위 요소가 다시 렌더링되면 그 아래에 있는 모든 자식 요소들도 함께 다시 렌더링되어야 한다. 이는 컴포넌트 트리 전체의 재렌더링을 유발할 수 있으며, 큰 규모의 애플리케이션에서는 성능 이슈를 야기할 수 있다. 이런 현상을 &quot;거대한 트리가 다시 렌더링되는 효과&quot;라고 말하는 것이다.</p>
</blockquote>
<ul>
<li>Context는 단일 값만 저장할 수 있으며, 자체 소비자(consumer)를 가지는 여러 값의 집합을 담을 수는 없다.</li>
</ul>
<blockquote>
<p>이는 React에서 기본적으로 제공하는 Context API의 한계에 대한 언급이다. Context API를 사용하면 Provider와 Consumer를 통해 데이터를 전달하고, 컴포넌트 트리 내에서 필요한 곳에서 해당 데이터를 사용할 수 있다. 그러나 Context API는 단일 값만 저장할 수 있는 한계가 있다. 즉, Context는 단일 값 또는 객체를 저장할 수 있지만, 여러 값의 집합을 저장하기 위해 별도의 자체 소비자를 가지는 것은 불가능하다. 따라서 여러 값들을 컨텍스트에 저장하고 싶은 경우, 단일 값으로 묶거나 객체 안에 넣어야 하는 번거로움이 있을 수 있다.</p>
</blockquote>
<h1 id="1-atoms">1. Atoms</h1>
<blockquote>
<p>Atoms는 상태의 단위이며, 업데이트와 구독이 가능하다. atom이 업데이트되면 각각 구독된 컴포넌트는 새로운 값을 반영하여 다시 렌더링 된다. atoms는 런타임에서 생성될 수도 있다. Atoms는 React의 로컬 컴포넌트의 상태 대신 사용할 수 있다. 동일한 atom이 여러 컴포넌트에서 사용되는 경우 모든 컴포넌트는 상태를 공유한다.</p>
</blockquote>
<p>Recoil에서는 크게 Atoms와 Selector로 이루어진다. 먼저, Atoms은 React에서 한번이라도 상태관리를 해보았다면 매우 쉽게 파악할 수 있을 것이다. 사용 예시는 다음과 같다.</p>
<pre><code class="language-javascript">// recoil/fontSizeState.ts
const fontSizeState = atom({
  key: &#39;fontSizeState&#39;,
  default: 14,
});

export default fontSizeState;</code></pre>
<p>Atoms에는 디버깅과 지속성 등을 위해서 사용되는 Key가 필요하다. 이 Key는 전역적으로 고유해야한다. 또한, default로 기본값을 관리한다. 이는 React 컴포넌트의 상태와 매우 유사하다고 볼 수 있다. 이렇게 선언한 State는 컴포넌트에서 다음과 같이 사용될 수 있다.</p>
<pre><code class="language-javascript">// components/FontButton.tsx
function FontButton() {
  const [fontSize, setFontSize] = useRecoilState(fontSizeState);
  return (
    &lt;button onClick={() =&gt; setFontSize((size) =&gt; size + 1)} style={{fontSize}}&gt;
      Click to Enlarge
    &lt;/button&gt;
  );
}</code></pre>
<p>이는 React의 useState와 매우 유사한 것을 볼 수 있다. Recoil에서 사용하는 Hook은 다음과 같다.</p>
<blockquote>
<ul>
<li>useRecoilState(state): 첫 요소가 상태의 값이며, 두번째 요소가 호출되었을 때 주어진 값을 업데이트하는 setter 함수를 리턴.</li>
</ul>
</blockquote>
<ul>
<li>useRecoilValue(state): 주어진 Recoil 상태의 값을 리턴.</li>
<li>useSetRecoilState(state): 쓰기 가능한 Recoil 상태의 값을 업데이트하기 위한 setter 함수를 리턴.</li>
<li>useResetRecoilState(state): 주어진 상태를 default 값으로 리셋하는 함수를 리턴.</li>
</ul>
<h1 id="2-selector">2. Selector</h1>
<h2 id="synchronous">Synchronous</h2>
<blockquote>
<p>Selector는 파생된 상태(derived state)의 일부를 나타낸다. 파생된 상태를 어떤 방법으로든 주어진 상태를 수정하는 순수 함수에 전달된 상태의 결과물로 생각할 수 있다. -공식문서</p>
</blockquote>
<p>여기서 &quot;파생된 상태&quot;란, 기존 상태(State)를 기반으로 계산되고 생성되는 상태를 말한다. 즉, Selector는 기존 상태를 입력으로 받아 순수 함수를 통해 파생된 상태를 반환하는 것을 의미한다. (<strong>순수함수란</strong>, 같은 입력이 들어오면, 해당 입력에 대한 출력은 항상 같은 함수라는 뜻)</p>
<pre><code class="language-javascript">const todoListFilterState = atom({
  key: &#39;todoListFilterState&#39;,
  default: &#39;Show All&#39;,
});

const filteredTodoListState = selector({
  key: &#39;filteredTodoListState&#39;,
  get: ({get}) =&gt; {
    const filter = get(todoListFilterState);
    const list = get(todoListState);

    switch (filter) {
      case &#39;Show Completed&#39;:
        return list.filter((item) =&gt; item.isComplete);
      case &#39;Show Uncompleted&#39;:
        return list.filter((item) =&gt; !item.isComplete);
      default:
        return list;
    }
  },
});</code></pre>
<p>위의 코드를 보면 Selector는 get으로 todoListState와 todoListFilterState를 구독한다. 구독한 todoListState, todoListFilterState 둘 중 하나라도 Update되면 자동으로 todoList가 업데이트 되는 구조이다.</p>
<pre><code class="language-javascript">function TodoList() {
  // changed from todoListState to filteredTodoListState
  const todoList = useRecoilValue(filteredTodoListState);

  return (
    &lt;&gt;
      &lt;TodoListStats /&gt;
      &lt;TodoListFilters /&gt;
      &lt;TodoItemCreator /&gt;

      {todoList.map((todoItem) =&gt; (
        &lt;TodoItem item={todoItem} key={todoItem.id} /&gt;
      ))}
    &lt;/&gt;
  );
}</code></pre>
<p>useRecoilValue를 통해서 fliter된 todoList에 접근 할 수 있고, 하위 컴포넌트에서 todoListState, todoListFilterState 둘 중 하나라도 Update되면 자동으로 todoList가 업데이트 되는 구조이다.</p>
<h2 id="asynchronous-비동기처리">Asynchronous (비동기처리)</h2>
<blockquote>
<p>Selector에서 비동기처리를 하려면 Suspense와 Loadable을 이용하여 비동기 데이터가 도착하기 이전의 Fallback (Loading창)을 보여주어야한다. 이에 대해서는 추후에 글을 쓰도록 하고 이번 포스팅에서는 Selector로 비동기처리를 하는 방법에 대해서만 알아보도록 하겠다.</p>
</blockquote>
<p>위에서 언급하지는 않았지만 위에서 처리한 작업은 동기적으로 Selector를 사용한 것이다. Atoms를 이용해서 서버로부터 데이터를 받아와서 받아온 data를 Atoms에 저장하는 방법도 가능하겠지만, Selector는 이러한 작업을 매우 간단하게 해준다. </p>
<pre><code class="language-javascript">const currentUserNameQuery = selector({
  key: &#39;CurrentUserName&#39;,
  get: async ({get}) =&gt; {
    const response = await myDBQuery({
      userID: get(currentUserIDState),
    });
    return response.name;
  },
});

function CurrentUserInfo() {
  const userName = useRecoilValue(currentUserNameQuery);
  return &lt;div&gt;{userName}&lt;/div&gt;;
}</code></pre>
<p>위와 같이 DB로 부터 User의 이름을 비동기적으로 받아와야한다고 할 때 get을 통해서 currentUserIDState를 구독하고 async await를 이용해서 DB로 부터 유저의 이름을 받아온 뒤 return 한다.</p>
<p>지금까지는 get함수만 소개를 했지만 Selector는 set함수도 가질 수 있다. </p>
<p>공식문서에서 소개된 Selector의 구조는 다음과 같은데, </p>
<pre><code class="language-javascript">function selector&lt;T&gt;({
  key: string,
  get: ({
    get: GetRecoilValue
  }) =&gt; T | Promise&lt;T&gt; | RecoilValue&lt;T&gt;,
  set?: (
    {
      get: GetRecoilValue,
      set: SetRecoilState,
      reset: ResetRecoilState,
    },
    newValue: T | DefaultValue,
  ) =&gt; void,
  dangerouslyAllowMutability?: boolean,
})</code></pre>
<p>set함수는 writeable 한 state 값을 변경할 수 있는 함수를 return 하는 곳. 여기서 주의할 점은, 자기 자신 selector를 set 하려고 하면, 스스로를 해당 set function에서 set 하는 것이므로 무한루프가 돌게 되니 반드시 다른 selector와 atom을 set 하는 로직을 구성하여야 한다. 또한 애초에 selector는 read-only 한 return 값(RecoilValue)만 가지기 때문에 set으로는 writeable 한 atom 의 RecoilState 만 설정할 수 있다. </p>
<h1 id="3-프로젝트에서-selector로-구현한-비동기처리">3. 프로젝트에서 Selector로 구현한 비동기처리.</h1>
<pre><code class="language-javascript">export default selector&lt;TResponseData&gt;({
  key: &#39;initialOrderState&#39;,
  get: async ({ get }) =&gt; {
    // queryData가 수정될때마다 아래 코드를 실행
    const queryData = get(QueryDataState);
    if (
      queryData === undefined ||
      window.location.pathname !== `/${QUIZ_PAGENAME}`
    )
      return undefined;

    const { amount, team } = queryData;
    const response = await axios({
      url: `${process.env.REACT_APP_SERVER_URL}/quiz/get`,
      method: &quot;GET&quot;,
      params: {
        amount: amount,
        team: team,
      }
    })

    if(response.error) {
      throw response.error
    }
    ... 생략

    return response.data;
  },
  set: ({ get, set }) =&gt; {
    const amount = get(QuizNumbersState);
    const team = get(QuizTeamState);

    set(QueryDataState, { amount, team });
    set(QuizNumbersState, DEFAULT_NUMBERS);
  },
});</code></pre>
<p>set 속성에서는 get을 통해서 QuizNumberState와 QuizTeamState를 구독한 뒤, set 함수를 통해서 두 State가 Update 될 때마다 QueryDataState가 Update된다.</p>
<p>그런데, get 속성에서 queryData를 get함수를 통해서 구독하고 있기 때문에 QueryDataState가 Update될 때마다 get 속성에 있는 함수가 실행되는 것을 확인할 수 있다.</p>
<p>axios로 서버와 통신을 해주었는데, 만약 서버가 죽어서 response에 error를 담아서 온다면 에러를 던지게 된다. 여기서 던진 에러는 React ErrorBoundary(보통 React Suspense와 함께 사용)를 통해서 잡을 수 있게 된다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React - nodejs Multer를 이용하여 이미지 파일 올리기]]></title>
            <link>https://velog.io/@mh-lee/React-nodejs-Multer%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-%EC%9D%B4%EB%AF%B8%EC%A7%80-%ED%8C%8C%EC%9D%BC-%EC%98%AC%EB%A6%AC%EA%B8%B0</link>
            <guid>https://velog.io/@mh-lee/React-nodejs-Multer%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-%EC%9D%B4%EB%AF%B8%EC%A7%80-%ED%8C%8C%EC%9D%BC-%EC%98%AC%EB%A6%AC%EA%B8%B0</guid>
            <pubDate>Sat, 29 Apr 2023 13:10:03 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>사이드 프로젝트를 진행하던 중 nodejs단 서버로 이미지 파일을 전송하는 기능을 구현해야 했다. 구글링을 통해 찾아보니 multer라는 라이브러리를 이용하여 이미지 파일을 쉽게 주고 받을 수 있다고 한다. multer 라이브러리 사용법에 대해서 자세히 알아보자.</p>
</blockquote>
<h1 id="이미지를-관리하는-여러-방법">이미지를 관리하는 여러 방법</h1>
<p>웹 사이트를 운영하면 이미지를 업로드하고 보여줄 일이 자주 생기게 된다. 이미지를 관리하는 방법에는 크게 3가지가 있다.</p>
<ul>
<li>server단에서 폴더를 하나 만들어서 (보통 public 폴더로 생성)하여 저장하는 방법</li>
<li>Amazon 같은곳에서 하드를 구매해서 거기에 저장 (Amazon S3 방식)</li>
<li>DB에 직접 저장하는 방식</li>
</ul>
<p>그런데 DB에 직접 저장하는 방식은 느리고, 비싸기때문에 보통 1번과 2번 방식을 많이 사용한다. 저장할 이미지가 매우 많다면 내 하드에 저장하는 1번 방식은 무리가 있기 때문에 2번 방식을 사용하고 저장할 이미지가 내 하드에서 커버 가능하다면 1번 방식을 사용한다. 그리고 이미지를 누가, 어디에, 어떤 이름으로 업로드 했는지와 같은 메타 정보들을 DB에 저장하는 것이 일종의 웹개발 관습이다.</p>
<p>사이드 프로젝트에서는 1번 방법을 대부분 사용하므로 하드에 저장하는 방식으로 살펴보도록 하겠다.</p>
<h1 id="설치">설치</h1>
<pre><code>yarn add multer
npm install multer</code></pre><p>우선 multer 라이브러리를 설치하여 준다.</p>
<h1 id="client-단에서-server로-이미지-보내기">Client 단에서 Server로 이미지 보내기</h1>
<p>우선 Client 단에서는 Input 태그 중 type이 file로 이미지를 입력받는다. 여러장 입력하는 것도 가능해야하니 multiple 속성을 켜주고, 이미지의 확장자를 jpg, jpeg, png로 제한한다.</p>
<pre><code>&lt;Label&gt;
        사진 등록
        &lt;Input
          type=&quot;file&quot;
          name=&quot;imageFile&quot;
          accept=&quot;image/jpg, image/jpeg, image/png&quot;
          multiple
          onChange={handleChange}
        /&gt;
&lt;/Label&gt;</code></pre><p>onChange로 이미지가 등록될때마다 state로 관리하였다. </p>
<p>이제 Form이 Submit 되면 server측으로 저장할 이미지를 POST 요청 날려주면 된다. 여기서의 Form은 enctype(보내는 파일의 인코딩형식)이 multiplart/form-data로 적어주어야한다. 이미지 전송시에는 인코딩 형식을 multipart/form-data로 설정해주어야한다. 안적어주면 default값인 application/x-www-form-urlencoded라는 base64 형식으로 인코딩 되어서 전달되게 되는데 용량이 조금 증가한다고 한다. 추가로 multer를 사용할 수 없다! multer를 사용할 것이라면 인코딩 형식을 multipart/form-data로 설정해야한다.</p>
<p>요청 날릴때 data는 FormData로 작성해야 한다.</p>
<pre><code class="language-javascript">const formData = new FormData();

...생략 (다른 정보들도 FormData에 담아서 전송)
if(imageFile) {
    for (let i = 0; i &lt; values.imageFile.length; i++) {
          formData.append(&quot;files&quot;, values.imageFile[i]);
    }
}
axios({
  method: &quot;POST&quot;,
  url: POST 요청을 날릴 ServerURL,
  headers: {
     &quot;Content-Type&quot;: &quot;multipart/form-data&quot;
  },
  data: formData
 })
   .then((result) =&gt; {
     // 성공시 수행할 코드
   })
   .catch((error) =&gt; {
     // 실패시 수행할 코드
   })</code></pre>
<p>imageFile이 여러개일 수 있으니 조건문으로 imageFile이 있는지 확인하고 반복문으로 formData에 저장하였다.</p>
<h1 id="server">Server</h1>
<h2 id="multer-기본-설정">multer 기본 설정</h2>
<p>다음은 Client단에서 보내온 데이터를 multer를 통해서 서버에서 처리하는 로직이다. 우선 multer를 사용하기 위해서는 다음과 같은 코드를 써줘야한다. </p>
<pre><code class="language-javascript">let multer = require(&#39;multer&#39;);
var storage = multer.diskStorage({
  destination: function (req, file, callback) {
    callback(null, &#39;./public/image&#39;)
  },
  filename: function (req, file, callback) {
    // 한글 이름으로 된 파일이름이 깨지지 않게 하기 위한 코드
    file.originalname = Buffer.from(file.originalname, &#39;latin1&#39;).toString(&#39;utf8&#39;)
    let fileName = Date.now() + file.originalname;
    callback(null, fileName)
  }
})
var upload = multer({ storage: storage })</code></pre>
<ul>
<li>diskStorage라는 함수를 쓰면 업로드된 파일을 하드에 저장할 수 있다. memoryStorage라고 쓰면 하드 말고 램에 저장할 수 있다 (휘발성)</li>
<li>destination: 업로드된 파일을 하드 어떤 경로로 저장할지 정하는 부분이다.</li>
<li>filename: 파일의 이름을 정하는 부분이다. file.originalname이 원본 파일명이라는 뜻이고, 앞에 날짜를 붙이는 등 다양하게 커스텀할 수 있다. 이는 사이트마다 다르게 때문에 입맛에 맞게 설정하면 될것같다.</li>
</ul>
<h2 id="server-api-설정">server API 설정</h2>
<p>이제 multer를 사용할 준비가 되었으니 client에서 POST 요청을 날릴때 실행해줄 로직을 다음과 같이 구현하면 된다.</p>
<pre><code class="language-javascript">Router.post(&#39;/&#39;, upload.array(&quot;files&quot;, 5), (req, res) =&gt; {
  try {
    console.log(req.files);
    const insertDoc = {
      filename: [],
      path: [],
      SavedTime: [],
    }
    req.files.map((file, index) =&gt; {
      insertDoc.filename[index] = file.filename;
      insertDoc.path[index] = file.path;
      insertDoc.SavedTime[index] = new Date();
    })
    // DB에 저장하는 코드
    collection.insertOne(insertDoc);
    res.status(200).json({ message: &quot;DB insertSuccess !&quot;});
  } catch (error) {
    res.status(500).json(error);
  }
})
</code></pre>
<ul>
<li>upload.single은 파일 하나만 받는 것이고, 우리는 여러 이미지를 받아야 하므로 upload.array함수로 첫번째 인자에는 fieldname을 써주고 두번 째 인자에는 최대로 받을 파일의 갯수를 지정해준다.</li>
<li>file들에 대한 정보는 req.files에 담아서 온다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/mh-lee/post/48c658e9-96a4-4439-bc06-7901821e79c5/image.png" alt=""></p>
<p>req.files에는 다음과 같은 값들이 담겨져온다. 파일의 경로와 현재시간 저장한 filename을 DB에 저장해주었다. </p>
<p><img src="https://velog.velcdn.com/images/mh-lee/post/52485054-f6ec-4b33-baf7-5eb1c6455ea4/image.png" alt=""></p>
<p>DB에 잘 저장된 것을 확인할 수 있다.</p>
<h1 id="결론">결론</h1>
<p>multer에 대해서 알아보았다. 다음 기회에는 multer로 Amazon S3에 이미지를 저장하는 방법에 대해서 구현해보고 싶다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React + Express로 JWT 방식 회원기능 구현하기 (2)]]></title>
            <link>https://velog.io/@mh-lee/React-Express%EB%A1%9C-JWT-%EB%B0%A9%EC%8B%9D-%ED%9A%8C%EC%9B%90%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0-1-3lqwwqyf</link>
            <guid>https://velog.io/@mh-lee/React-Express%EB%A1%9C-JWT-%EB%B0%A9%EC%8B%9D-%ED%9A%8C%EC%9B%90%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0-1-3lqwwqyf</guid>
            <pubDate>Mon, 24 Apr 2023 08:18:01 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>지난 글에서 JWT 개념에 대해서 이해하였으니 이번 글에서는 React / TS 환경에서 JWT를 어떻게 구현하였는지를 포스팅하려 한다.</p>
</blockquote>
<h1 id="jwt-token-발행">JWT Token 발행</h1>
<p>사용자가 로그인을 하여 서버로 Client단에서 입력한 아이디와 비밀번호를 실어서 POST 요청을 하면 DB에 저장된 회원정보와 Client단에서 보내온 request.body의 값이 같다면 Token을 하나 만들어서 고객 브라우저의 쿠키에 저장한다. 다음은 위 기능을 구현하는 코드이다.</p>
<blockquote>
<p>주의해야할 점은 Client단에서는 POST 요청을 할 때 withCredentials Option을 키고, 서버에서 쿠키를 주고 받을 수 있게 미들웨어를 추가해야한다. 이는 구글링해보면 빠르게 알 수 있으므로 생략하도록 하겠다. 또한, JWT는 decoding이 쉽기 때문에 secretKey를 길고 때려맞추기 어렵게 작성하여 .env파일로 따로 보관해야한다.</p>
</blockquote>
<pre><code class="language-javascript">// routers/auth.js
const express = require(&#39;express&#39;);
const Router = express.Router();
const { login } = require(&#39;../controller/auth&#39;);

Router.post(&#39;/login&#39;, login);

// controller/auth.js
const login = async (req, res) =&gt; {
  try {
    // DB에 저장된 이메일이 없는 경우 status 402를 보냄.
    if (!await isemailExist(req.body.email)) throw 402;

    // DB에 해당 이메일이 있다면 비밀번호 검사를 수행함.
    const userInfo = await foundUserInfo(req.body.email);
    const isCorrectPW = await verifyPassword(req.body.password, userInfo.salt, userInfo.password);
    // 유저의 비밀번호가 맞지 않는 경우
    if (!isCorrectPW) throw 403;
    try {
      // 비밀번호까지 맞다면 access Token 발급
      const accessToken = jwt.sign({
        email: userInfo.email,
        name: userInfo.name
      }, process.env.ACCESS_TOKEN_SECRET, {
        expiresIn: &#39;60m&#39;,
        issuer: &#39;Lee&#39;
      })
      // token을 쿠키에 담아서 전송
      res.cookie(&#39;accessToken&#39;, accessToken, {
        secure: false,
        // JS에서 쿠키 접근이 불가능하게 하기 위함.
        httpOnly: true
      })
      res.status(200).json({
        name: userInfo.name,
        message: &#39;로그인 성공하였습니다.&#39;
      });
    } catch (error) {
      res.status(500).json(error);
    }
  } catch (errorcode) {
    if (errorcode === 402) {
      // 유저가 입력한 이메일이 DB에 없는 경우
      res.status(errorcode).json({ message: &#39;해당 Email이 존재하지 않습니다.&#39; });
    } else if (errorcode === 403) {
      // 유저가 입력한 비밀번호가 일치하지 않는 경우
      res.status(errorcode).json({ message: &#39;비밀번호가 맞지 않습니다&#39; })
    } else {
      res.status(500).json(error)
    }

  }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/mh-lee/post/ec6876ed-2aaf-49e7-8574-57f34565f29c/image.png" alt=""></p>
<p>올바른 ID와 비밀번호로 POST 요청을 하니 다음과 같이 accessToken이 발행된 것을 확인할 수 있다.</p>
<h1 id="jwt-token-유효성-검사">JWT Token 유효성 검사</h1>
<blockquote>
<p>JWT Token의 유효성을 검사하는 부분에서 고민이 많았다. 처음 한 생각은 페이지 들어갈 때 한번만 로그인 여부를 검사하는 API에 GET 요청을 날려서 로그인 되었는지 여부와 user의 이름을 state로 관리하는 방식으로 생각하였는데, 이 방식으로 구현을 하게 되면 페이지에 머물다가 JWT Token이 만료가 되면 재로그인을 요구하는 alert창을 띄울 수가 없다. 생각해낸 해결 방법으로는 setInterval 함수를 사용해서 10분 (혹은 더 짧게)마다 로그인이 유효한지 서버에 요청을 보내는 방법이다. customHook으로 useIslogin 함수를 만들어서 구현하였다.</p>
</blockquote>
<p>우선 로그인이 성공적으로 진행되었는지를 GET요청으로 확인할 수 있는 서버의 API는 다음과 같이 구현하였다.</p>
<pre><code class="language-javascript">
// routers/auth.js
Router.get(&#39;/islogin&#39;, loginSuccess);

// controller/auth.js
const loginSuccess = async (req, res) =&gt; {
  try {
    const token = req.cookies.accessToken;
    // token이 존재하지 않음, 즉 유저가 로그인하지 않음
    if(!token) throw 400;
    const data = jwt.verify(token, process.env.ACCESS_TOKEN_SECRET);

    const userData = await foundUserInfo(data.email);
    const { password, salt, ...others } = userData;
    res.status(200).json({
      name: others.name
    });
  } catch (error) {
    if(error === 400) {
      res.status(400).json(error);
    } else {
      res.status(500).json(error);
    }
  }
}</code></pre>
<p>다음은 Client단에서 CustomHook을 이용하여 10분을 주기로 서버에 GET 요청을 보내는 코드이다.</p>
<pre><code class="language-javascript">import { useEffect, useState } from &quot;react&quot;;
import axios from &quot;axios&quot;;

export default function useIsLogin() {
  const [islogin, setIsLogin] = useState(false);
  const [userName, setUserName] = useState(&#39;&#39;);
  const [status, setStatus] = useState(0);

  useEffect(() =&gt; {
    checkLoginStatus();
    // Check login every 10 minutes.
    const intervlId = setInterval(checkLoginStatus, 60 * 1000 * 10);
    return () =&gt; clearInterval(intervlId);
  }, [])


  const checkLoginStatus = () =&gt; {
    try {
      axios({
        url: &quot;http://localhost:8080/auth/islogin&quot;,
        method: &quot;GET&quot;,
        withCredentials: true
      })
        .then((response) =&gt; {
          setIsLogin(true);
          setStatus(200);
          setUserName(response.data.name);
        })
        .catch((error) =&gt; {
          if (error.response.status === 400) {
            // User does not login
            setStatus(400);
            setIsLogin(false);
            setUserName(&#39;&#39;);
          } else {
            // token expired
            setIsLogin(false);
            setUserName(&#39;&#39;);
            setStatus(500);
          }
        })
    } catch (error) {
      console.log(error)
    }
  }

  console.log(islogin, userName, status);
  return {
    islogin,
    userName,
    status
  };
}</code></pre>
<h1 id="결론">결론</h1>
<p>간단한 회원기능이지만 DB에 저장부터 해서 Token 발행 등 각잡고 구현하려고 하니 고려해야할 점이 많았다. 이번에는 accessToken의 시간을 짧게 두는 방식으로 구현하였지만, 다음 사이드 프로젝트에서는 refreshToken을 이용하여 구현해볼 예정이다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React + Express로 JWT 방식 회원기능 구현하기 (1)]]></title>
            <link>https://velog.io/@mh-lee/React-Express%EB%A1%9C-JWT-%EB%B0%A9%EC%8B%9D-%ED%9A%8C%EC%9B%90%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0-1</link>
            <guid>https://velog.io/@mh-lee/React-Express%EB%A1%9C-JWT-%EB%B0%A9%EC%8B%9D-%ED%9A%8C%EC%9B%90%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0-1</guid>
            <pubDate>Sat, 22 Apr 2023 19:44:53 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이번 포스팅에서는 Session과 JWT의 차이점, 장단점에 대해서 포스팅하고, 다음 포스팅에서 내가 이해한 React + Express로 JWT를 구현하는 방법에 작성하려고 한다 </p>
</blockquote>
<h1 id="session-vs-jwt">Session VS JWT</h1>
<h2 id="session-based-authentication">Session-based Authentication</h2>
<ul>
<li><p>세션은 브라우저와 웹 서버가 연결되어 브라우저가 종료될 때까지의 시점이다. 브라우저를 통해 서버에 접속할 때 서버는 세션 id를 쿠키에 담아 되돌려주고, 사용자는 세션 id를 담은 쿠키인 세션 쿠키를 이 후 요청부터 계속해서 함께 전달함으로 서버가 클라이언트를 식별할 수 있게 하는 기술이 세션 기반 인증 방식인데 보통 세션이라고 한다.</p>
</li>
<li><p>클라이언트가 HTTP요청을 보낼 때 쿠키에 세션 id가 없다면 서버는 세션 id를 생성 해 쿠키에 담아서 돌려준다. 다시 HTTP 재요청을 할 때 쿠키를 보내기 때문에 서버에서는 세션 id를 통해 클라이언트를 식별할 수 있는 것이다.</p>
</li>
</ul>
<p>전체적인 Flow는 다음과 같다.</p>
<blockquote>
<ol>
<li>로그인시 제출한 아이디, 비밀번호가 DB에 저장된 회원정보와 맞다면 세션스토어에 Session을 하나 만들어서 저장한다. (여기서 세션스토어는 DB가 될 수도 있고 서버 메모리가 될 수도 있음) Session은 쉽게 말해서 Client가 언제 어디서 로그인 했는지에 대한 정보를 담은 자료이다.</li>
<li>로그인 한 유저마다 각각 유니크한 세션아이디를 발급한다. </li>
<li>발급한 세션아이디는 쿠키에 담아서 고객 브라우저에 전송해준다. 세션아이디는 고객과 서버 둘다 보관한다. (쿠키란 브라우저에 마련되어 있는 작은 문자데이터 저장공간이다. 여기에 세션아이디가 abc123 이렇게 기록이 된다.)</li>
<li>이제 Client가 myPage와 같은 로그인이 필요한 페이지를 접속하고자 한다면 페이지를 보여주기 전에 로그인을 하였는지를 확인하는 미들웨어를 실행합니다.</li>
<li>고객이 페이지를 요청할 때 마다 자동으로 쿠키가 서버로 전송되게 되는데, 서버는 쿠키에 기록된 세션아이디를 세션스토어(서버메모리 or DB)에 저장되어 있던 세션아이디와 비교해서 있으면 마이페이지를 보여준다.
<img src="https://velog.velcdn.com/images/mh-lee/post/8836bbd1-5d30-45bc-adba-38fbb1bb1bfb/image.png" alt=""></li>
</ol>
</blockquote>
<h3 id="장점">장점</h3>
<ul>
<li>서버에서 클라이언트의 상태를 유지하고 있으므로, 사용자의 로그인 여부 확인이 용이하고, 경우에 따라서 강제 로그아웃 등의 제재를 가할 수 있다.</li>
<li>클라이언트가 임의로 정보를 변경시키더라도 서버에서 클라이언트의 상태 정보를 가지고 있으므로 상대적으로 안전하다.</li>
</ul>
<h3 id="단점">단점</h3>
<ul>
<li>서버에서 클라이언트의 상태를 모두 유지하고 있어야 하므로, 클라이언트 수에 따른 메모리나 DB 부하가 심하다.</li>
<li>사용자가 많아지는 경우 로드 밸런싱을 통한 서버확장을 이용해야 하는데 이때 세션관리가 여려워진다.</li>
<li>웹 브라우저에서 세션 관리에 사용하는 쿠키는 단일 도메인 및 서브 도메인에서만 작동하도록 설계되어 CORS 방식(여러 도메인에 request를 보내는 브라우저)을 사용할 때 쿠키 및 세션 관리가 어렵다.</li>
<li>멀티 디바이스 환경(모바일, 브라우저 공동 사용 등)에서 로그인 시 중복 로그인 처리가 되지 않는 등의 신경 써줘야 할 부분들이 생긴다.</li>
</ul>
<h2 id="json-web-token-jwt">JSON Web Token (JWT)</h2>
<ul>
<li>토큰 방식은 세션데이터를 서버에 저장하지 않고, 마이페이지를 열람할 수 있는 열쇠(토큰)을 사용자에게 쥐어주는 것이다.</li>
<li>따라서 토큰에는 session 방식보다 더 많은 정보가 들어간다. (이름, 아이디, 유효기간 등)
<img src="https://velog.velcdn.com/images/mh-lee/post/4fc3cd5a-eb34-42fe-9c9a-e6bf32c43276/image.png" alt=""></li>
<li>JWT 토큰에는 Header, Payload, Signature 구조로 이루어져 있다. JWT는 암호화되지 않아서 쉽게 Decoding이 가능하기 때문에 Payload(Data)에는 최소한의 정보만 담아야 한다. </li>
</ul>
<p>전체적인 Flow는 다음과 같다.</p>
<blockquote>
<ol>
<li>로그인시 제출한 아이디와 비밀번호가 DB에 저장된 회원정보와 맞다면 서버는 Token을 하나 만들어서 고객 브라우저로 보낸다. </li>
<li>클라이언트가 마이페이지를 요청하면 서버는 마이페이지를 보여주기위해서 토큰 검사를 수행한다. 고객이 마이페이지 요청시 함께 보낸 Token이 적법한지 등을 검사한다. 유통기한이 지나지는 않았는지, Signature는 올바르게 되어 있는지 등의 검사를 거친 후 이상이 없으면 마이페이지를 보여준다.
<img src="https://velog.velcdn.com/images/mh-lee/post/4846e460-64f1-4fdf-91e4-e076abc5089d/image.png" alt=""></li>
</ol>
</blockquote>
<h3 id="장점-1">장점</h3>
<ul>
<li><strong>stateless</strong>하다. 서버측 부하를 낮출 수 있고 독립적이기 때문에 능률적으로 접근 권한관리를 할 수 있고 분산/클라우드 기반 인프라 스트럭처에 잘 대응할 수 있다.</li>
<li>별도의 인증 저장소가 필요하지 않다. 인증서버와 db에 의존하지 않아도 된다.</li>
</ul>
<h3 id="단점-1">단점</h3>
<ul>
<li>JWT는 Decoding이 매우 쉽다. 민감한 유저의 정보는 집어넣지 않는다. 최소한의 정보만 넣어야함.</li>
<li>Signature의 SecretKey를 때려 맞추기 매우 쉽다. 이를 brute force attack이라 한다. 따라서 SecretKey를 매우 길고 보안성이 뛰어나게 설정해야한다. 보안성을 더욱 더 신경쓰고 싶으면 생성용키와 검증용키를 사용하여 운영한다. (JWT 라이브러리 이용)</li>
<li>JWT가 수명이 길어서 제 3자에게 탈취당할 경우가 발생할 수 있다. 그런데 JWT는 서버가 발급하고 나면 JWT를 관리하지 않기 때문에 유효기간이 끝날때 까지 해커는 계속 사용이 가능하다. 이를 보완한 방법 중 하나로 access token &amp; refresh token 방식이 있는데 두개의 토큰 모두 JWT이다.</li>
</ul>
<h3 id="refreshtoken--accesstoken">RefreshToken &amp; AccessToken</h3>
<p>access token &amp; refresh token 방식은 토큰들에 유효기간을 주어서 보안을 강화시킨 것이다. access token은 유효기간이 짧은 토큰이고, refresh token은 access token보다 유효기간이 긴 토큰이다.</p>
<ul>
<li>사용자가 로그인을 하면 서버로부터 access token, refresh token 2개의 토큰을 받는다. 이때 서버는 refresh token을 안전한 저장소에 저장한다.</li>
<li>서버로부터 받은 access token의 유효 기간이 지나지 않은 경우 사용자가 어떤 요청을 보낼 때 access token을 함께 보내고 서버는 유효한 지 확인 후 응답을 보낸다.</li>
<li>서버로부터 받은 access token의 유효 기간이 지난 경우 사용자는 refresh token과 함께 요청을 보내고, 저장소에 저장되어 있던 refresh token과 비교한 후에 유효하다면 새로운 access token과 응답을 함께 보낸다.</li>
</ul>
<h1 id="참고">참고</h1>
<blockquote>
<p><a href="https://millo-l.github.io/Session-%EA%B8%B0%EB%B0%98-%EC%9D%B8%EC%A6%9D%EB%B0%A9%EC%8B%9D/">https://millo-l.github.io/Session-%EA%B8%B0%EB%B0%98-%EC%9D%B8%EC%A6%9D%EB%B0%A9%EC%8B%9D/</a>
<a href="https://80000coding.oopy.io/1f213f10-185c-4b4e-8372-119402fecdd0">https://80000coding.oopy.io/1f213f10-185c-4b4e-8372-119402fecdd0</a>
<a href="https://velog.io/@gotaek/%EC%84%B8%EC%85%98Session%EA%B3%BC-JWT">https://velog.io/@gotaek/%EC%84%B8%EC%85%98Session%EA%B3%BC-JWT</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[React의 디자인 패턴 중 아토믹 디자인 패턴을 알아보자]]></title>
            <link>https://velog.io/@mh-lee/React%EC%9D%98-%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-%EC%A4%91-%EC%95%84%ED%86%A0%EB%AF%B9-%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4%EC%9D%84-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@mh-lee/React%EC%9D%98-%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-%EC%A4%91-%EC%95%84%ED%86%A0%EB%AF%B9-%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4%EC%9D%84-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Sat, 22 Apr 2023 10:17:20 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>React의 여러 디자인 패턴 중 아토믹 패턴에 대해서 알아보자.</p>
</blockquote>
<h1 id="0-아토믹-디자인-패턴이란">0. 아토믹 디자인 패턴이란?</h1>
<p><img src="https://velog.velcdn.com/images/mh-lee/post/02165a3b-11fe-47c1-b8da-e4408e0ded03/image.png" alt=""></p>
<ul>
<li>가장 작은 컴포넌트 단위를 Atoms로 설정하여 이를 바탕으로 상위 컴포넌트를 만들어 코드 재사용을 최대화하는 방법론</li>
<li>상위 컴포넌트는 순서대로 분자(Molecules), 유기체(Organisms), 템플릿(Templates)으로 가며 최종적으로 페이지(Pages)를 관리<h2 id="원자-atoms">원자 (Atoms)</h2>
</li>
<li>디자인과 기능의 최소단위이다.</li>
<li>상위 컴포넌트에서 재사용이 가능해야 하기 때문에 가장 작고, 미니멀하게 제작한다.</li>
<li>Label, Text, Container, Button, Icon등이 대표적인 예시<h2 id="분자-molecules">분자 (Molecules)</h2>
</li>
<li>원자보다 한 단계 위의 컴포넌트이다.</li>
<li>2개 이상의 Atom들로 구성한다.</li>
<li>Input forms, Navigation, Card 등이 대표적인 예시이다.<h2 id="유기체-organism">유기체 (Organism)</h2>
</li>
<li>분자들을 결합하여 유기체를 형성한다.</li>
<li>입력폼과 함께 헤더를 감싸거나, 여러 카드를 관리하는 Grid등을 예로 들수 있다.</li>
<li>프로젝트에 따라 유기체의 사이즈가 다르다.<h2 id="템플릿-template">템플릿 (Template)</h2>
</li>
<li>템플릿은 여러 유기체가 모여있고, 페이지보다는 낮은 단위이다.</li>
<li>Grid들을 묶은 하나의 컴포넌트 등을 예시로 들수 있다.</li>
</ul>
<h2 id="페이지-page">페이지 (Page)</h2>
<ul>
<li>유저가 볼 수 있는 실제 컨텐츠를 담고 있음.</li>
<li>여러 template들이 모아져있는 구조.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/mh-lee/post/e50a1f1a-b2b7-4c94-b33b-1a869ff17346/image.png" alt=""></p>
<h1 id="1-아토믹-디자인의-장단점">1. 아토믹 디자인의 장단점</h1>
<h2 id="장점">장점.</h2>
<ul>
<li>컴포넌트 재사용성이 극대화된다.</li>
<li>애플리케이션과 분리하여 컴포넌트를 개발하고, 테스트할 수 있다. (단위 테스트에 유리)</li>
<li>Storybook과 같은 라이브러리를 이용하면 더욱 더 시너지를 발휘할 수 있다.</li>
</ul>
<h2 id="단점">단점.</h2>
<ul>
<li><p>초기 구현이 어려울 수 있다. 작은 모듈로 분해하고 계층 구조로 구성하는 과정은 초기에는 더 많은 시간과 노력을 필요로 할 수 있다.</p>
</li>
<li><p>중복 코드가 발생할 수 있다. 작은 모듈로 분해하다 보면, 비슷한 기능을 하는 컴포넌트가 여러 개 만들어질 수 있다. 이 경우 중복 코드가 발생할 가능성이 있다.</p>
</li>
<li><p>복잡한 구조를 가지고 있어서 다른 개발자들과의 협업이 어려울 수 있다.</p>
</li>
<li><p>디자인 시스템에 대한 이해가 필요하다. 아토믹 디자인 패턴은 디자인 시스템과 밀접한 관련이 있다. 이러한 디자인 시스템에 대한 이해가 부족하면, 이 패턴을 사용하는 것이 어려울 수 있다.</p>
</li>
<li><p>유연성이 부족할 수 있다. 작은 모듈로 분해하다 보면, 컴포넌트 간의 결합도가 높아질 수 있습니다. 이 경우, 유연성이 부족해질 수 있다.</p>
</li>
</ul>
<h1 id="2-내-프로젝트에서-적용한-atomic-design">2. 내 프로젝트에서 적용한 Atomic Design</h1>
<blockquote>
<p>Atoms, Moleclues, Organism, Template, Page로 모두 나누기에는 명확한 기준도 존재하지 않고, 어떻게 나누어야할지 감이 안올 것 같아서 Template을 제외한 4가지로 분류하였다.</p>
</blockquote>
<ul>
<li>Atoms<ul>
<li>div, button과 같은 가장 작은 단위의 컴포넌트들 위주로 구성하였다.</li>
</ul>
</li>
<li>Molecules<ul>
<li>2개 이상의 원자로 구성되고, Form, Content, Modal같은 재사용이 가능한 컴포넌트로 구성하였다.</li>
</ul>
</li>
<li>Organism<ul>
<li>재사용이 불가능한 컴포넌트들.</li>
<li>유기체가 모여서 Page를 이룬다.</li>
</ul>
</li>
<li>Page<ul>
<li>Organism이 모여서 만든다.</li>
<li>완성된 하나의 페이지.</li>
</ul>
</li>
</ul>
<h1 id="결론">결론</h1>
<p>아토믹 디자인 패턴은 UI를 재사용 가능한 작은 모듈로 분해하고 이를 계층 구조로 구성하는 방식으로 UI를 구성하는 패턴이다. 이는 UI를 더 쉽게 구현하고 재사용성과 확장성을 높일 수 있으나 여러 단점들이 존재한다. 진행하려는 프로젝트의 규모나 목적에 맞게 사용하면 좋을 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Express에 MongoDB 연결하기]]></title>
            <link>https://velog.io/@mh-lee/Express%EC%97%90-MongoDB-%EC%97%B0%EA%B2%B0%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@mh-lee/Express%EC%97%90-MongoDB-%EC%97%B0%EA%B2%B0%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 19 Apr 2023 17:05:42 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Node.js (Express)에 MongoDB 연결하는 방법을 알아보자. 공식문서를 참고하여 최신버전인 5.2버전에서 잘 적용된다.</p>
</blockquote>
<h1 id="mongodb-계정-세팅">MongoDB 계정 세팅</h1>
<ul>
<li>우선 MongoDB 홈페이지에 접속해서 계정을 만들고, database를 만든 뒤에 Collection까지 생성한다.
<img src="https://velog.velcdn.com/images/mh-lee/post/5a5c4fc3-70eb-4f98-877a-59b7bf21dcb8/image.png" alt=""></li>
<li>MongoDB에서 database와 collection은 위와 같다. database는 하나의 폴더, collection은 하나의 엑셀파일이라고 생각하면 편하다. collection에는 회원가입시 저장할 유저의 정보 등을 저장한다.
<img src="https://velog.velcdn.com/images/mh-lee/post/26226fc8-4bb3-4e4d-95e3-ce4651dd9b38/image.png" alt=""></li>
<li>connect -&gt; Drivers에서 Nodejs를 선택한 뒤 mogodb 접속 url을 가져오면 된다.</li>
</ul>
<h1 id="server">server</h1>
<ul>
<li>현재 폴더 구조는 다음과 같다.
<img src="https://velog.velcdn.com/images/mh-lee/post/df4b00ec-278e-409c-a136-f1ac7bfd5d56/image.png" alt=""></li>
<li>server.js 파일에 MongoDB를 연결하는 코드를 넣을 수도 있겠지만 모듈화를 위해서 database 폴더에 나누어서 진행하였다.<pre><code class="language-javascript">// database.js
require(&#39;dotenv&#39;).config();
const { MongoClient } = require(&#39;mongodb&#39;);
const MONGO_URI = process.env.MONGO_URI;
</code></pre>
</li>
</ul>
<p>const client = new MongoClient(MONGO_URI);
const connectMongoDB = async () =&gt; {
    try {
        const database = client.db(&#39;yummytrip&#39;);
        const post = database.collection(&#39;post&#39;);
          post.insertOne({ name: &#39;testtest&#39; });
        console.log(&#39;MonogoDB connected!&#39;);
    } finally {
        await client.close();
    }
}
module.exports = connectMongoDB;</p>
<p>// server.js
const connectMongoDB = require(&#39;./database/database&#39;)
...생략</p>
<p>connectMongoDB().catch((error) =&gt; console.log(error));</p>
<pre><code>- 테스트로 post라는 collection에 값을 저장하는 코드를 추가하여 실행시키니 다음과 같이 잘 저장된 것을 확인할 수 있었다.
![](https://velog.velcdn.com/images/mh-lee/post/91b22fbd-c13f-4abe-b357-b874b2f87b3e/image.png)
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[React-CRA + TypeScript의 폴더 구조를 알아보자]]></title>
            <link>https://velog.io/@mh-lee/React-CRA-TypeScript%EC%9D%98-%ED%8F%B4%EB%8D%94-%EA%B5%AC%EC%A1%B0%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@mh-lee/React-CRA-TypeScript%EC%9D%98-%ED%8F%B4%EB%8D%94-%EA%B5%AC%EC%A1%B0%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Sat, 15 Apr 2023 13:11:46 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>사이드 프로젝트를 할 때 create react-app으로 프로젝트를 시작하곤 하는데 최근에 TypeScript를 배우고 나서 React에 TS를 얹어서 프로젝트를 생성하고 나니 src내에 모르는 파일이 너무 많았다... 이참에 폴더 구조를 한번 정리해보려고 한다.</p>
</blockquote>
<h2 id="전체적인-폴더-구조">전체적인 폴더 구조</h2>
<p>전체적인 폴더 구조는 다음과 같다.
<img src="https://velog.velcdn.com/images/mh-lee/post/dd2a3d2c-d4c4-4272-a336-354ec3520614/image.png" alt=""></p>
<pre><code>└── node_modules/
└── public/
    ├── favicon.ico
    ├── index.html
    ├── logo192.png
    ├── logo512.png
    ├── manifest.json
    ├── robots.txt
└── src/
    ├── App.css
    ├── App.test.tsx
    ├── App.tsx
    ├── index.css
    ├── index.tsx
    ├── logo.svg
    ├── react-app-env.d.ts
    ├── reportWebVitals.ts
    ├── setupTests.ts
└── package.json
└── tsconfig.json
└── .gitignore
└── yarn.lock
</code></pre><h3 id="1-node_modules">1. node_modules</h3>
<ul>
<li>CRA를 설치하면 기본적으로 <strong>webpack, babel</strong> 등 여러 패키지가 자동으로 추가된다. 이러한 패키지를 모아놓은 곳이 node_modules이다.</li>
<li>보통 .gitignore 파일에 node_modules 폴더명이 적혀있기 때문에 Github 저장소에서는 보이지 않는 것이 특징이다.<h3 id="2-public">2. public</h3>
</li>
<li>React 프로젝트의 <strong>정적(Static) 파일</strong>들이 저장된 폴더이다. 여기서 정적 파일이란 이미지, CSS, HTML, JS와 같이 해당 내용이 고정되며, 응답할 때 별도 처리 없이 파일 내용을 그대로 보여주면 되는 파일을 말한다.</li>
<li>React는 직접 Routing을 담당하는 SPA이기 때문에 모든 페이지가 Public 폴더의 index.html로 모여 처리된다.<ul>
<li><code>favicon.ico</code> : 기본적으로 CRA로 프로젝트를 생성하면 브라우저 탭에 나타나는 아이콘이다.</li>
<li><code>index.html</code> : 브라우저에 나타내는 HTML 파일, 브라우저의 제목 등을 수정할 수 있다.</li>
<li><code>manifest.json</code> : PWA(Progressive Web Apps)에 필수로 포함돼야하는 파일이다.
<img src="https://velog.velcdn.com/images/mh-lee/post/c00565e9-5d02-4870-b31e-aded787e34d9/image.png" alt=""><ul>
<li><code>short_name</code>: 사용자 홈 화면에서 아이콘 이름으로 사용한다.</li>
<li><code>name</code>: 웹앱 설치 배너에 사용한다.</li>
<li><code>icons</code>: 홈 화면에 추가할 때 사용할 이미지, <code>favicon.ico</code> <code>logo192.png</code> 등의 파일이 여기에 사용된다.</li>
<li><code>name</code>: 웹앱 설치 배너에 사용한다.</li>
<li><code>start_url</code>: 웹앱 실행시 시작되는 주소</li>
<li><code>display</code>: 디스플레이 유형(fullscreen, standalone, brower)</li>
<li><code>theme_color</code>: 상단 툴바 색상</li>
<li><code>background_color</code>: 화면 배경 색상 </li>
</ul>
</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>Progressive Web App</strong>란? 웹사이트를 안드로이드/iOS 모바일 앱처럼 사용할 수 있게 만드는 일종의 웹개발 기술이다. </p>
</blockquote>
<ul>
<li><code>robot.txt</code>: 웹사이트에 웹 크롤러와 같은 로봇들의 접근을 제어하기 위한 규약<h3 id="3-src">3. src</h3>
</li>
<li>실제 React를 사용하여 개발을 할 때 사용하는 폴더</li>
<li><code>App.css, index.css</code>: App.tsx, index.tsx안의 컴포넌트들에 대한 CSS파일</li>
<li><code>App.test.tsx</code>: DOM을 테스트하기 위한 도구. React Testing Library나 JEST등 테스트 라이브러리를 사용한다. TDD(테스트 주도 개발)과 테스트 케이스를 작성할 때 사용</li>
<li><code>App.tsx</code>: CRA가 제공해주는 기본 예제가 들어있는 파일. </li>
<li><code>index.tsx</code>: App.tsx에 모인 컴포넌트들을 public/index.html과 연결해주는 파일</li>
<li><code>logo.svg</code>: App.tsx의 기본 예제에 사용되는 로고 (필요없음)</li>
<li><code>react-app-env.ts</code>: TypeScript 타입 선언을 참조한다. bmp, gif, jpg, png 등의 리소스 파일 가져오기에 대한 지원을 추가한다. 또한, <code>module.css(scss)</code> 확장자를 가진 css 모듈 가져오기에 대한 지원을 추가한다.</li>
<li><code>reportWebVitals.ts</code>: React 프로젝트의 성능을 측정하기 위한 파일. <code>index.tsx</code>에 <code>reportWebVistals(console.log)</code> 등으로 사용할 수 있다. </li>
<li><code>setupTest.ts</code>: React 프로젝트에서 테스트를 실행하기 위한 설정 파일</li>
</ul>
<pre><code class="language-typescript">// 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 reportWebVitals from &#39;./reportWebVitals&#39;;

const root = ReactDOM.createRoot(
  document.getElementById(&#39;root&#39;) as HTMLElement
);
root.render(
  &lt;React.StrictMode&gt;
    &lt;App /&gt;
  &lt;/React.StrictMode&gt;
);

reportWebVitals();</code></pre>
<blockquote>
<p><strong>React</strong>는 순수 자바스크립트이고, 이 자바스크립트를 이용해 컴포넌트를 만들어간다. 실제로 브라우저가 이해할 수 있는 것은 <strong>html, css, JavaScrip</strong>t 뿐이며, 이것들만 이용할 수 있다. 
그래서 위의 코드와 같은 식은 바벨을 이용해서 순수 자바스크립트로 변환이 된다! 그리고 나서 우리가 만든 컴포넌트를 html과 연결하는 작업을 해줘야하는데 이것을 해주는 것이 바로 <strong>ReactDOM</strong>이다. 그래서 사용자에게 궁극적으로 배포되어지는 것은 <strong>index.html</strong>이다.</p>
</blockquote>
<p><code>index.html</code> 파일을 확인해보면 다음과 같이 <strong>id</strong>가 <strong>root</strong>인 <strong>div태그</strong>가 존재하는 것을 확인할 수 있다.</p>
<pre><code class="language-html">// index.html
...생략
  &lt;/head&gt;
  &lt;body&gt;
    &lt;noscript&gt;You need to enable JavaScript to run this app.&lt;/noscript&gt;
    &lt;div id=&quot;root&quot;&gt;&lt;/div&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre>
<p>이제 다시 <span style="color: red;"><code>index.tsx</code></span>를 확인하면 이해가 쉬울 것이다. <strong>ReactDOM</strong>으로 id가 root인 요소를 가져와서 render함수로 <span style="color: red;"><code>App.tsx</code></span>의 컴포넌트와 연결을 해주는 것이다 !!</p>
<h3 id="4-root">4. root</h3>
<ul>
<li><code>.gitignore</code>: Github, GitLab 등 레포지토리에 저장되지 않길 원하는 파일을 지정할 수 있다.</li>
<li><code>package.json</code>: 설치한 패키지들의 정보가 담긴 파일. node_module을 통째로 올리고 복사하는 것은 비효율적이기 때문에 package.json에 설치한 패키지들의 정보를 기록하고 다른 사람이 레포지토리를 통해 설치할 때 이 파일에 근거해 패키지를 설치한다.</li>
<li><code>yarn.lock</code>: <strong>npm</strong>으로 설치하면 <code>package-lock.json</code>이 만들어진다. 이 파일이 생성된 시점의 의존성 트리에 대한 정보를 가지고 있다.</li>
<li><code>tsconfig.json</code>: 타입스크립트를 컴파일하는데 필요한 루트 파일과 컴파일 옵션을 지정하는 파일이다.</li>
</ul>
<blockquote>
<p><strong>느낀점👋 !</strong>
그동안 무지성으로 파일들을 삭제했었는데 이제 어떠한 역할을 하는지에 대략적으로 알게 되었으니 좀 더 똑똑하게 지울 수 있게 되었다..ㅎㅎ  Testing Library에 사용되는 .test파일들이 흥미로웠다. 어떻게 사용하는지 자세하게 알아봐야겠다.
<strong>참고자료</strong>
<a href="https://sangseophwang.tistory.com/89">https://sangseophwang.tistory.com/89</a>
<a href="https://velog.io/@khw970421/React-%ED%8C%8C%EC%9D%BC%EB%93%A4%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C-index.js-App.js-index.html">https://velog.io/@khw970421/React-%ED%8C%8C%EC%9D%BC%EB%93%A4%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C-index.js-App.js-index.html</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[React에 Express 연동하는 방법]]></title>
            <link>https://velog.io/@mh-lee/React-TypeScript-Express-%ED%99%98%EA%B2%BD-%EC%B4%88%EA%B8%B0-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@mh-lee/React-TypeScript-Express-%ED%99%98%EA%B2%BD-%EC%B4%88%EA%B8%B0-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Tue, 11 Apr 2023 14:18:37 GMT</pubDate>
            <description><![CDATA[<h1 id="intro">Intro</h1>
<blockquote>
<p><span style="color: red;"><code>React</code></span>로 사이드 프로젝트를 진행하려는 와중에 <strong>server</strong>로 <span style="color: red;"><code>node+express</code></span>를 사용하려고 한다. 좋은 포스팅들이 많았지만 왜 이런식으로 구현해야하는지 이해가 잘 되지 않아서 찾아보면서 내가 이해한 방법으로 포스팅하려고 한다.</p>
</blockquote>
<h1 id="서버와-react의-존재-이유">서버와 React의 존재 이유</h1>
<h2 id="서버란">서버란?</h2>
<ul>
<li><span style="color: red;"><code>서버</code></span>는 클라이언트에게 여러 가지 서비스를 제공하는 것을 뜻한다.</li>
<li>평소에 우리는 웹 브라우저를 사용해 웹 사이트에 액세스하고 있을 것이다. 이때 웹 브라우저가 &#39;<strong>클라이언트</strong>&#39;이며, 웹 사이트의 콘텐츠가 있는 컴퓨터가 &#39;<strong>서버</strong>&#39;이다. </li>
<li>그렇다면, <strong>웹서버</strong>는 유저가 웹 문서를 요청하면 웹 문서를 보내주는 프로그램을 웹서버라고 한다.<h2 id="react의-존재-이유">React의 존재 이유</h2>
</li>
<li><span style="color: red;"><code>React</code></span>는 <span style="color: red;"><code>HTML</code></span>을 예쁘게 만들어주는 툴입니다.</li>
<li>앱처럼 부드럽게 동작하는 <strong>SPA(Single Page Application)</strong>을 만들 때 사용합니다.</li>
</ul>
<blockquote>
<p><span style="color: red;"><code>React</code></span>는 yarn run build라는 명령어를 사용하면 하나의 <span style="color: red;"><code>HTML</code></span> 파일을 만들어준다. 
따라서, 어떤 서버를 사용하건 간에 유저가 메인 페이지로 접속하면 <strong>React</strong>로 만든 <strong>HTML</strong> 파일을 보내주면 연동이 끝입니다.</p>
</blockquote>
<h1 id="server와-client를-설치">server와 client를 설치</h1>
<h2 id="server-설치-node--express">server 설치 (node + express)</h2>
<pre><code class="language-javascript">// 프로젝트 폴더에 server 폴더 만들기
mkdir server

// terminal에 node+express에 필요한 패키지 설치
cd server
yarn init -y
yarn add express

// server 폴더로 이동하서 server.js 파일 생성
// server.js
const express = require(&#39;express&#39;);
const path = require(&#39;path&#39;);
const app = express();

app.listen(8080, function () {
  console.log(&#39;listening on 8080&#39;)
});

// 미리보기 띄우기
nodemon server.js</code></pre>
<h2 id="client-설치-cra">client 설치 (CRA)</h2>
<pre><code>// 프로젝트 폴더로 이동해서 React 설치
yarn create react-app client --template typescript</code></pre><p>설치가 완료 되었으면 다음과 같은 폴더 구조가 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/mh-lee/post/c54b4ac1-e4f6-44d8-b6c2-e63217d9e7d2/image.png" alt=""></p>
<h1 id="react에-express-연동">React에 Express 연동</h1>
<p>리액트로 개발을 다 마친 후 <code>yarn run build</code>를 하면 build라는 폴더가 생기고 안에 html css js파일이 생성됩니다. React는 SPA이기 때문에 기본적으로 html 파일이 하나만 있습니다. 따라서 server.js 파일에 유저가 메인페이지로 접속하면 React로 build한 index.html 파일을 보내주기만 하면 React와 Express 연동 끝입니다.</p>
<pre><code class="language-javascript">// server.js
const express = require(&#39;express&#39;);
const app = express();
const path = require(&#39;path&#39;);

app.listen(8080, function() {
    console.log(&#39;listening on 8080&#39;);
})
app.use(express.static(path.join(__dirname, &#39;../client/build&#39;)));
app.get(&#39;/&#39;, function(req, res) {
    res.sendFile(path.join(__dirname, &#39;../client/build/index.html&#39;));
})</code></pre>
<p>express.static을 쓰면 특정 폴더 안의 파일들을 static 파일로 유저에게 잘 보내줄 수 있습니다. 이제 localhost:8080 으로 접속하면 리액트 프로젝트가 나옵니다. 끄읏!
그런데 리액트는 SPA라 리액트 상에서 라우팅을 담당하는 경우가 대부분입니다. 근데 react에서 /test라는 route를 생성하고 localhost:8080/test로 이동하면 에러가 발생합니다. </p>
<h1 id="궁금했던-점-😒">궁금했던 점 😒</h1>
<h2 id="build-작업">build 작업</h2>
<blockquote>
<p>server에서 build 된 html 파일을 보여주기 때문에 리액트 프로젝트 코드 수정할 때마다 build 작업을 해야될까? 그럴 필요는 없다. 나중에 사이트를 aws, heroku 등에 배포할 때 한 번만 해주면 된다. 평소에 개발할 때는 리액트도 localhost로 미리보기 띄워놓고, 서버도 localhost로 미리보기 띄워놓고 개발 진행하면 별 문제 없다.</p>
</blockquote>
<p>리액트와 서버를 동시에 띄우려면 어떻게 해야될까?</p>
<h3 id="concurrently">concurrently</h3>
<pre><code>// root 디렉토리(프로젝트)로 이동해서 터미널에 설치 
yarn add global concurrently

// root 디렉토리에 package.json
{
  &quot;scripts&quot;: {
    &quot;server&quot;: &quot;cd server &amp;&amp; nodemon server&quot;,
    &quot;client&quot;: &quot;cd client &amp;&amp; npm start&quot;,
    &quot;start&quot;: &quot;concurrently --kill-others-on-fail \&quot;npm run server\&quot; \&quot;npm run client\&quot;&quot;
  },
    &quot;devDependencies&quot;: {
    &quot;nodemon&quot;: &quot;^2.0.7&quot;
  },
  &quot;dependencies&quot;: {
    &quot;concurrently&quot;: &quot;^8.0.1&quot;
  }
}</code></pre><p>스크립트 부분만 변경해주면 된다.</p>
<h2 id="라우팅">라우팅</h2>
<blockquote>
<p>리액트는 SPA라 리액트 상에서 라우팅을 담당하는 경우가 대부분입니다. 근데 react에서 /test라는 route를 생성하고 localhost:8080/test로 이동하면 에러가 발생합니다. 왜냐하면 브라우저 URL 창에 입력하는건 서버에게 요청하는 것이지 리액트 라우터에게 요청하는 것이 아니기  때문입니다. 따라서 리액트가 라우팅하게 권한을 넘기고 싶으면 server.js에 다음과 같은 코드를 작성하면 됩니다. </p>
</blockquote>
<pre><code>// 최하단에 위치 (server.js)
app.get(&#39;*&#39;, function(req, res) {
    res.sendFile(path.join(__dirname, &#39;../client/build/index.html&#39;));
})</code></pre><p>유저가 URL란에 아무거나 입력하면 리액트 프로젝트를 보내줘 라우팅하는 방식이다. 가장 하단에 놓아야 잘 동작합니다.</p>
<h2 id="react에서-db-데이터-보여주고-싶으면">React에서 DB 데이터 보여주고 싶으면?</h2>
<p>리액트는 보통 client-side-rendering을 합니다. 그래서 DB에 있는 상품목록을 가져와서 리액트에서 보여주고 싶으면 다음과 같은 방식으로 구현한다.</p>
<ol>
<li>서버는 누군가 /product로 GET 요청을 하면 DB에서 데이터를 꺼내서 보여주라고 API를 설계해놓는다.</li>
<li>리액트는 상품목록을 보여주고 싶을 땐 서버 /product 주소로 GET 요청을 날리면 된다.</li>
<li>받아와진 데이터를 가지고 html에 넣는 방식으로 개발한다.</li>
</ol>
<p>리액트에서 서버와의 통신은 거의 <code>ajax</code>로 진행한다. POST 요청, 로그인해서 세션만들기 이런것도 보통 <code>ajax</code> 쓴다.</p>
<pre><code>yarn add cors</code></pre><p>쓰기 전에 <code>cors</code> 패키지 설치, <code>cors</code>는 다른 도메인주소끼리 ajax 요청 주고 받을 때 필요하다.</p>
<pre><code class="language-javascript">// server.js
// 상단에 있어야함.
app.use(express.json());
var cors = require(&#39;cors&#39;);
app.use(cors());

app.get(&#39;/product&#39;, (req, res) =&gt; {
      // DB에서 받아와서 이런식으로 함
    res.json({name: &#39;black shoes&#39;});
})
</code></pre>
<h1 id="결론">결론</h1>
<blockquote>
<p>react와 node 서버 간에 데이터를 주고 받기 위해서는 프록시 모듈이 필요하다. 프록시 모듈의 사용법에 대해서는 추가적인 포스팅으로 다루어 보도록 하겠다. 이제 react + express 연동도 했으니 DB쪽으로 연결만 한 뒤 사이드 프로젝트에 들어가기만 하면 되겠다..! 😊</p>
</blockquote>
<h1 id="참고자료">참고자료</h1>
<p><a href="https://create-react-app.dev/docs/proxying-api-requests-in-development/">https://create-react-app.dev/docs/proxying-api-requests-in-development/</a>
<a href="https://velog.io/@autumndr3ams/210802-React-Node.jsexpress-%EC%97%B0%EA%B2%B0%ED%95%98%EA%B8%B0#nodejs-%EC%84%9C%EB%B2%84-%EA%B5%AC%EC%B6%95">https://velog.io/@autumndr3ams/210802-React-Node.jsexpress-%EC%97%B0%EA%B2%B0%ED%95%98%EA%B8%B0#nodejs-%EC%84%9C%EB%B2%84-%EA%B5%AC%EC%B6%95</a>
<a href="https://blog.naver.com/sejun3278/221569640649">https://blog.naver.com/sejun3278/221569640649</a></p>
]]></description>
        </item>
    </channel>
</rss>