<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>sujeong_dev.log</title>
        <link>https://velog.io/</link>
        <description>Front-End Developer</description>
        <lastBuildDate>Thu, 07 Mar 2024 01:50:06 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>sujeong_dev.log</title>
            <url>https://velog.velcdn.com/images/sujeong_dev/profile/4641cde3-388b-4f69-8292-ee193d5c86cc/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. sujeong_dev.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/sujeong_dev" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[every()는 왜 빈배열에 true를 반환하는 것인가..]]></title>
            <link>https://velog.io/@sujeong_dev/every%EB%8A%94-%EC%99%9C-%EB%B9%88%EB%B0%B0%EC%97%B4%EC%97%90-true%EB%A5%BC-%EB%B0%98%ED%99%98%ED%95%98%EB%8A%94-%EA%B2%83%EC%9D%B8%EA%B0%80</link>
            <guid>https://velog.io/@sujeong_dev/every%EB%8A%94-%EC%99%9C-%EB%B9%88%EB%B0%B0%EC%97%B4%EC%97%90-true%EB%A5%BC-%EB%B0%98%ED%99%98%ED%95%98%EB%8A%94-%EA%B2%83%EC%9D%B8%EA%B0%80</guid>
            <pubDate>Thu, 07 Mar 2024 01:50:06 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>const allQueriesResolved = results.every(result =&gt; !result.isPending &amp;&amp; !result.isLoading);</p>
</blockquote>
<p>react-query의 useQueries를 통해 API 통신 결과를 받아와서 상태가 pending이나 loading이 아닌 경우들을 파악하려고 했는데 통신을 하지 않은 상태에서도 <code>allQueriesResolved</code>변수가 true라서 원인을 찾던 도중.....every는 빈배열일 때 콜백함수에 관계없이 true를 반환한다는 것이었다..</p>
<p>...........왜?</p>
<p>당연히 <code>results</code>가 빈 배열일 때 콜백함수 조건을 만족하지 않으니까 every가 false일 줄 알았지....너무나도 삽질이었다..</p>
<p>검색해보니 너무나도 자세히 설명되어있는 포스트를 찾았다.</p>
<blockquote>
<p><a href="https://velog.io/@sehyunny/why-does-every-return-true-for-empty-array">(번역) 이상한 자바스크립트: 왜 every()는 빈 배열에 true를 반환할까?</a></p>
</blockquote>
<p>전체 양화사...공허참...주어진 조건을 만족시킬 수 없는 경우 공집합은 참을 의미한다.
<img src="https://velog.velcdn.com/images/sujeong_dev/post/5ceab522-870f-4120-b83e-8129e0eaf053/image.png" alt=""></p>
<p>빈배열이라면 길이가 0 이기 때문에 5번 과정이 생략되어 콜백함수를 호출할 수 없게되고 true를 return한다..</p>
<hr>
<pre><code>return results.length === 0 ? false : allQueriesResolved;</code></pre><p>결국 every로 배열 요소들의 조건을 파악하기 전에 해당 배열이 빈배열인지 먼저 확인해야한다!
삽질했지만 절대 안까먹을 것 같다...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[CORS 에러 핸들링 😎]]></title>
            <link>https://velog.io/@sujeong_dev/CORS-%EC%97%90%EB%9F%AC-%ED%95%B8%EB%93%A4%EB%A7%81</link>
            <guid>https://velog.io/@sujeong_dev/CORS-%EC%97%90%EB%9F%AC-%ED%95%B8%EB%93%A4%EB%A7%81</guid>
            <pubDate>Thu, 07 Mar 2024 01:08:23 GMT</pubDate>
            <description><![CDATA[<p>지독히도 잊고있던 CORS에러를 공휴일 조회 API를 연동하면서 다시 한 번 마주쳤다...
모처럼 또다시 삽질한 김에 CORS에러에 대해서 정리를 해보도록 하겠다...!</p>
<h1 id="📌-먼저-cors란-무엇인가">📌 먼저, CORS란 무엇인가</h1>
<blockquote>
<p>CORS(Cross-Origin Resource Sharing) : 교차 출처 리소스 공유</p>
</blockquote>
<p>우선 브라우저는 동일출처정책(SOP)에 따라 동일한 출처에서만 리소스를 공유할 수 있게 되어있다.
그런데 만약 <a href="https://www.naver.com">https://www.naver.com</a> 에서 <a href="https://www.google.com">https://www.google.com</a> , <a href="http://www.naver.com">http://www.naver.com</a> 에 있는 리소스를 공유하려고 할 때 출처(Origin), 즉 protocol, host, port가 다른 곳에서 리소스를 요청하게 된다.</p>
<p>교차 출처에서 리소스를 요청하게 된다면 응답받는 origin의 서버단에서는 200 ok를 던져주게 된다.
<strong>서버단이 아니라 브라우저에서 판단</strong>하기 때문에 브라우저에서 <em>&#39;엇, origin이 다르네?&#39;</em> 하고 CORS 정책을 확인하고 위반한 요청일 경우 접근을 막아버리는 것이다.</p>
<p>이것이 우리가 익히 아는 CORS 에러이다.</p>
<hr>
<h1 id="📌-어떻게-해결할-수-있는가">📌 어떻게 해결할 수 있는가</h1>
<h2 id="✔️-백엔드단에서-해결하기">✔️ 백엔드단에서 해결하기</h2>
<h3 id="백엔드-개발자에게-부탁하기">백엔드 개발자에게 부탁하기..</h3>
<p>서버에서 Access-Control-Allow-Origin 헤더에 요청하는 주소를 정확히 명시해 <em>&#39;해당 주소는 접근을 허용한다&#39;</em> 라고 브라우저 쪽으로 알려주면 된다.</p>
<blockquote>
<p>Access-Control-Allow-Origin: * 로 어떤 주소든 접근을 허용하겠다고 명시해도되지만 보안상의 이슈가 있어 지양하는 것이 좋다.</p>
</blockquote>
<h2 id="✔️-프론트단에서-직접-해결하기">✔️ 프론트단에서 직접 해결하기</h2>
<h3 id="인증-정보로-쿠키를-사용한다면-">인증 정보로 쿠키를 사용한다면 ?</h3>
<p>CORS는 기본적으로 쿠키의 사용을 막고있기 때문에 쿠키 허용 설정을 안해주었을 경우에도 CORS에러가 발생할 수 있다. 인증 정보로 쿠키를 사용한다면 요청, 응답 시 credentials 부분을 true로 설정해주어야 한다.</p>
<h4 id="요청클라이언트">요청(클라이언트)</h4>
<p>fetch 메소드 인 경우</p>
<pre><code>fetch(url, {
  credentials: &#39;include&#39;
}) </code></pre><p>axios 라이브러리를 사용하는 경우</p>
<pre><code>axios.post(
    &#39;https://example.com:1234&#39;, 
    { withCredentials: true }
).then(response =&gt; { 
})</code></pre><h4 id="응답서버">응답(서버)</h4>
<pre><code>// node, express 기준
const cors = require(&#39;cors&#39;);

const app = express();

app.use(cors({
    credential: &#39;true&#39;
}));</code></pre><h3 id="프록시-설정하기">프록시 설정하기</h3>
<pre><code>// package.json 파일
{ ... 
&quot;proxy&quot; : &quot;우회할 API 주소&quot;
}</code></pre><p>CRA로 설정한 프로젝트에서는 package.json에 해당 방식으로 proxy를 설정할 수 있다.
우회할 API 주소가 여러개라면 http-proxy-middleware 라이브러리를 사용해서 프록시를 설정할 수도 있다.</p>
<blockquote>
<p>http-proxy-middleware 라이브러리는 로컬환경에서만 잘 돌아간다...</p>
</blockquote>
<hr>
<p>잊을만하면 괴롭히는 CORS에러... 하나씩 시도하면서 해결하면 될것이다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[MSW(Mock Service Worker)를 이용한 API mocking]]></title>
            <link>https://velog.io/@sujeong_dev/MSWMock-Service-Worker%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-API-mocking</link>
            <guid>https://velog.io/@sujeong_dev/MSWMock-Service-Worker%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-API-mocking</guid>
            <pubDate>Tue, 14 Nov 2023 08:21:42 GMT</pubDate>
            <description><![CDATA[<p>프론트 개발을 하던 와중에 백엔드 API 개발속도와 맞지 않아 동시에 개발이 진행되게 되어 /public경로에 실제 API response의 json key-value값을 맞춰서 mock data를 만들어 mocking하는 식으로 UI를 만들었다.
그러던 중 MSW라는 API mocking하는 라이브러리를 알게되었다.</p>
<h1 id="mswmock-service-worker란">MSW(Mock Service Worker)란?</h1>
<blockquote>
<p>서비스 워커(Service Worker)를 사용하여 HTTP 네트워크 호출을 가로채는 API 모킹(mocking) 라이브러리</p>
</blockquote>
<p>백엔드에서 받는 API인 척 실제 네트워크가 이루어지는 것 처럼 프론트엔드의 응답값을 주는 라이브러리이다.</p>
<p>실제 네트워크단에서 일어나기 때문에 axios의 호출하는 엔드포인트나 REST API를 실제 API로 바꿔기기 쉬우며 에러처리나 예외처리를 똑같이 수행할 수 있다는 점이 굉장히 매력적이다.</p>
<h1 id="msw-사용법">MSW 사용법</h1>
<h2 id="1-라이브러리-설치하기">1. 라이브러리 설치하기</h2>
<p>저는 yarn을 사용하므로 yarn 커맨드로 설치</p>
<pre><code>yarn add msw --dev</code></pre><h2 id="2-service-worker-등록">2. Service worker 등록</h2>
<p>아래 명령어를 통해 msw라이브러리를 실행시켜 <code>public</code> 폴더 위치에 worker를 등록한다.</p>
<pre><code>npx msw init public/ --save</code></pre><h2 id="3-worker-설정">3. worker 설정</h2>
<pre><code>// src/mocks/browser.ts

import { handlers } from &quot;@mock/handlers/handlers&quot;;
import { setupWorker } from &quot;msw&quot;;

export const worker = setupWorker(...handlers);</code></pre><p>worker 인스턴스를 생성하고, 요청 핸들러를 정의한다.</p>
<h2 id="4-worker-실행">4. worker 실행</h2>
<pre><code>// src/main.ts
import initMockAPI from &#39;@mock/index.ts&#39;;
import ReactDOM from &#39;react-dom/client&#39;;
import { RecoilRoot } from &#39;recoil&#39;;
import App from &#39;./App.tsx&#39;;

initMockAPI();

ReactDOM.createRoot(document.getElementById(&#39;root&#39;)!).render(
  &lt;RecoilRoot&gt;
    &lt;QueryClientProvider client={queryClient}&gt;
      &lt;App /&gt;
    &lt;/QueryClientProvider&gt;
  &lt;/RecoilRoot&gt;
)</code></pre><pre><code>// src/mocks/index.ts
const initMockAPI = async (): Promise&lt;void&gt; =&gt; {
  if (import.meta.env.VITE_REACT_ENV === &#39;2&#39;) {
    const { worker } = await import(&quot;@mock/browser&quot;);
    worker.start();
  }
};

export default initMockAPI;</code></pre><p>가장 최상단 루트파일인 <code>main.ts</code>에 worker 실행 코드를 추가한다.
(나는 개발환경에서만 worker를 실행하기 위해 환경변수를 통해 실행을 제어했다.)</p>
<p>이제 <code>yarn dev</code>로 어플리케이션을 다시 실행시키면 콘솔에서 메세지가 뜨면서 활성화가 된다 !
<img src="https://velog.velcdn.com/images/sujeong_dev/post/778558f1-b3ac-42b2-abe6-a5df7d05ddf6/image.png" alt=""></p>
<h2 id="5-요청-핸들러-작성">5. 요청 핸들러 작성</h2>
<p>그럼 이제 네트워크 호출을 가로챌 준비는 끝났고 어떤 요청을 했을 때 어떤 응답값을 받아야하는지에 대한 정보를 나타내는 요청 핸들러를 작성해주어야한다.</p>
<pre><code>// src/mocks/handlers/handlers.ts

export const handlers = [
  rest.get(&#39;/result&#39;, (req, res, ctx) =&gt; {
    const DataEnc = req.url.searchParams.get(&#39;DataEnc&#39;);
    const pageNum = req.url.searchParams.get(&#39;pageNum&#39;);

    if (!DataEnc) {
      return res(ctx.status(301, &#39;dataenc값 없음&#39;));
    }
    if (pageNum === &#39;0&#39;) {
      return res(ctx.json(pageData_0));
    } else if (pageNum === &#39;1&#39;) {
      return res(ctx.json(pageData_1));
    } else if (pageNum === &#39;2&#39;) {
      return res(ctx.json(pageData_2));
    } else if (pageNum === &#39;3&#39;) {
      return res(ctx.json(pageData_3));
    } else if (pageNum === &#39;4&#39;) {
      return res(ctx.json(pageData_4));
    } else if (pageNum === &#39;5&#39;) {
      return res(ctx.json(pageData_5));
    } else if (pageNum === &#39;6&#39;) {
      return res(ctx.json(pageData_6));
    } else {
      return res(ctx.json(pageData_7));
    }
  })
]</code></pre><p><code>rest</code>객체를 사용하여 get, post, put, delete 등 API에 해당하는 http메소드들을 불러올 수 있다. </p>
<h3 id="응답-해석기">응답 해석기</h3>
<p><code>req</code>는 요청정보, <code>req</code>는 응답 정보, <code>ctx</code>는 status code, headers, body 등 응답값들을 지정한다.</p>
<h3 id="query-parameter">query parameter</h3>
<p><code>req.url.searchParams.get</code>를 통해 URLsearchparams로 넘겨준 querystring을 가져올 수 있고 파라미터별 값 분기처리를 통해 로직을 구현한다.</p>
<hr>
<p>postman을 통해 API response값의 key와 value의 형식만 지정해주면 되기 때문에 이제는 더이상 API에 구애받지 않고 프론트엔드 개발을 할 수 있었다 !!
mock data보다 초기 코드작성이 많고 진입장벽은 높았지만 host를 변경해줘야하거나 상태코드에 따른 로직을 구현할 수 없었던 단점을 보완할 수 있어서 너무 좋았고 실제 API통신과 흡사해서 UI 구현하는데 너무 편리했다 ✨</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[window.performance 이벤트미발생 오류 해결(react-router-dom useNavigate() / location.place() 차이)]]></title>
            <link>https://velog.io/@sujeong_dev/window.performance-%EC%9D%B4%EB%B2%A4%ED%8A%B8%EB%AF%B8%EB%B0%9C%EC%83%9D-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0</link>
            <guid>https://velog.io/@sujeong_dev/window.performance-%EC%9D%B4%EB%B2%A4%ED%8A%B8%EB%AF%B8%EB%B0%9C%EC%83%9D-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0</guid>
            <pubDate>Sat, 14 Oct 2023 07:10:10 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/sujeong_dev/post/5d5f43b4-1603-41fd-893a-49dd159810c0/image.png" alt=""></p>
<p>window.performance.getEntriesByType(&quot;navigation&quot;)[0].type의 &#39;back_forward&#39;값으로 뒤로가기 접근 감지로직을 만들었는데 해당 이벤트가 발생하지 않았다..
console.log를 찍어도 해당 인터페이스의 객체 자체가 찍히지 않는 것이었다.
페이지 이동 시에 문제가 있는 것 같아 이것저것 시도해보았다.</p>
<h2 id="의심-1-새로고침">의심 1. 새로고침?</h2>
<p>페이지 이동 시 새로고침이 안되어서 그런지 첫번째 의심을 해봤다.
useNavigation으로 이동을 하고 있었는데 새로고침을 해주기 위해 <strong>replace: true</strong> 옵션을 주었다.
새로고침은 되었으나 이것 역시 원인이 아니었다.</p>
<h2 id="의심-2-페이지-이동-자체의-문제">의심 2. 페이지 이동 자체의 문제</h2>
<p>navigate의 문제가 있는게 아닐까 하여 location.replace()로 변경해주었다.</p>
<p>드디어 console.log에 객체가 떴다 !! 😊
<img src="https://velog.velcdn.com/images/sujeong_dev/post/3f83b3a9-7492-4821-a6a9-08881ab922ae/image.png" alt=""></p>
<hr>
<h2 id="그렇다면-usenavigate--locationplace는-어떤-차이점이-있는걸까">그렇다면 useNavigate() / location.place()는 어떤 차이점이 있는걸까?</h2>
<h3 id="1-usenavigate">1. useNavigate()</h3>
<p>React-Router v6에서 제공하는 navigate훅으로 React앱 내부의 navigate, 이동 히스토리를 관리할 수 있다. 
따라서 useNavigate를 사용해 페이지를 이동하는 것은 React앱 내부에서의 이동일 뿐 실제 크롬, 사파리 브라우저의 히스토리에는 영향을 주지 않는다.</p>
<h3 id="2-locationplace">2. location.place()</h3>
<p>location.place는 자바스크립트 내장 함수로 실제 브라우저의 navigate, 이동 히스토리를 관리할 수 있다. location.href와는 달리 페이지 이동 시에 새로고침까지 이루어지는 함수이다.</p>
<blockquote>
<p>💡 useNavigation은 React Router를 통해 React내의 history를 조작하는 것 뿐 실제 브라우저의 history에는 관여하지 않아 window.performance이벤트가 발생하지 않았다 !!</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[CS] 빅오표기법]]></title>
            <link>https://velog.io/@sujeong_dev/%EB%B9%85%EC%98%A4%ED%91%9C%EA%B8%B0%EB%B2%95</link>
            <guid>https://velog.io/@sujeong_dev/%EB%B9%85%EC%98%A4%ED%91%9C%EA%B8%B0%EB%B2%95</guid>
            <pubDate>Sat, 14 Oct 2023 06:39:32 GMT</pubDate>
            <description><![CDATA[<p>빅오표기법</p>
<p>시간복잡도를 나타내는 표기법 중 하나로 &#39;O(입력)&#39;으로 표기합니다.</p>
<p>빅오로 표현할 수 있는 시간복잡도의 코드는 아래와 같습니다.</p>
<h1 id="1-o1">1. O(1)</h1>
<blockquote>
<p>&#39;일정한 복잡도&#39;라고 하며, 입력값의 크기와 관계없이 즉시 출력값을 얻어낼 수 있는 시간복잡도 입니다.</p>
</blockquote>
<pre><code>function O_1_algorithm(arr, index) {
  console.log(&#39;o1 알고리즘!&#39;);
}</code></pre><p>코드와 같이 함수 파라미터를 통한 입력값을 통해 바로 출력값을 얻었을 때 시간복잡도가 O(1)입니다.</p>
<h1 id="2-on">2. O(n)</h1>
<blockquote>
<p>&#39;선형 복잡도&#39;라고 하며, 입력값이 증가함에 따라 시간 또한 같은 비율로 증가하는 것을 의미하는 시간복잡도 입니다.</p>
</blockquote>
<pre><code>function O_n_algorithm(n) {
  for (let i = 0; i &lt; n; i++) {
    console.log(i)
  }
}</code></pre><p>코드와 같이 n=1이면 i=0일 때 1초 실행시간, i=1일때 1초 실행시간, 이렇게 n이 1씩 증가할 때마다 실행시간이 1초씩 증가합니다.</p>
<p>즉, 입력값이 증가함에 따라 시간 또한 같은 비율로 늘어나는 시간복잡도가 O(n)입니다.</p>
<h1 id="3-on2">3. O(n^2)</h1>
<blockquote>
<p>&#39;2차 복잡도&#39;라고 하며, 입력값이 증가함에 따라 시간이 n의 제곱수 비율로 증가하는 것을 의미하는 시간복잡도 입니다.</p>
</blockquote>
<p>입력값이 1일 때 1초의 실행시간이 걸리던 알고리즘에 3일 때 9초가 걸리는 것을 의미합니다.</p>
<pre><code>function O_n2_algorithm(n) {
  for (let i = 0; i &lt; n; i++) {
    for (let j = 0; j &lt; n; j++) {
      console.log(i, j)
    }
  }
}</code></pre><p>n=3이라고 한다면 i=0,1,2 j=0,1,2 총 9회의 for문이 돌아가고 총 실행시간이 9초이다.</p>
<p>이처럼 입력값이 증가함에 따라 시간이 제곱으로 증가하는 시간복잡도가 O(n^2)입니다.</p>
<h1 id="4-o2n">4. O(2^n)</h1>
<blockquote>
<p>&#39;기하급수적 복잡도&#39;라고 하며, 가장 느린 시간 복잡도를 가집니다.</p>
</blockquote>
<p>이 시간복잡도의 가장 대표적인 알고리즘은 피보나치 수열입니다.</p>
<pre><code>function O_2n_algorithm(n){
  if(n &lt;= 0) {
      return 0;
  } else if(n === 1) {
      return 1;
  }
      return O_2n_algorithm(n-1) + O_2n_algorithm(n-2);
}</code></pre><p>즉 함수를 호출할 때마다 바로 전 숫자와 그 전 숫자를 알아야 숫자를 더하면서 앞으로 나올 숫자를 파악할 수 있습니다. 이렇게 매번 함수가 호출될 때마다 두 번씩 호출합니다. 이걸 트리의 높이만큼 반복합니다.</p>
<h1 id="5-olog-n">5. O(log n)</h1>
<blockquote>
<p>해당 시간복잡도는 한번 처리할 때마다 탐색량이 절반씩 줄어듭니다.</p>
</blockquote>
<pre><code>let arr = [];

function O_logn_algorithm(k, s, e){
  for(let i = s; i &lt;= e; i++){
    arr.push(i);
    let m = (s+e)/2;

    if(arr[m] === k){
      console.log(m) 
    } else if(arr[m]&gt; k){
      return log(k, s, m-1);
    } else{
      return log(k, m+1,e)
    }
  }
}</code></pre><p>해당 시간복잡도의 가장 대표적인 알고리즘은 이진 탐색입니다. 배열에서 특정 숫자를 찾을 때, 이진 탐색을 이용한다면 배열 가운데 값과 키값을 비교하고 가운데 값이 키값보다 작다면 탐색하지 않습니다. 그럼 다시 또 중간값을 설정하고 비교합니다.</p>
<h1 id="6-on-log-n">6. O(n log n)</h1>
<blockquote>
<p>해당 시간복잡도는 퀵 정렬, 병합 정렬과 같은 분할 정복 정렬 알고리즘의 실행 시간입니다.</p>
</blockquote>
<pre><code>function O_nlogn_algorithm(arr) {
  if (arr.length &lt;= 1) return arr;

  const pivot = arr[0];
  const left = [];
  const right = [];

  for (let i = 1; i &lt; arr.length; i++) {
    if (arr[i] &lt;= pivot) left.push(arr[i]);
    else right.push(arr[i]);
  }

  const lSorted = quickSort(left);
  const rSorted = quickSort(right);
  return [...lSorted, pivot, ...rSorted];
};</code></pre><p>이전 문제에 있었던 퀵 정렬을 예로 들어보겠습니다. 기준값이 되는 피벗 포인트를 설정하고 해당 값보다 작은 배열, 큰 배열이 나뉘고 또 그 배열안에서 기준값을 정하고 해당 값보다 작은 배열, 큰배열 이렇게 분할 정복으로 계속 나뉘게 됩니다.</p>
<p>이 때의 시간복잡도가 O(n log n)입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[HTTP 499 Error]]></title>
            <link>https://velog.io/@sujeong_dev/HTTP-499-Error</link>
            <guid>https://velog.io/@sujeong_dev/HTTP-499-Error</guid>
            <pubDate>Sat, 14 Oct 2023 06:31:41 GMT</pubDate>
            <description><![CDATA[<p>postman에서 잘되던 api가 배포했더니 오류가 났다....!
이건 듣도보도 못한 HTTP status 499....</p>
<blockquote>
<p>해당 에러는 Nginx 자체 클라이언트 쪽 에러로 서버가 응답을 보내기도 전에 클라이언트쪽에서 연결을 끊어버린 상태다</p>
</blockquote>
<p>아니 나는 끊은 적이 없는데 왜...? 🥲</p>
<p>원인을 찾다보니 React-Query useMutation의 mutateAsync로 비동기처리를 하고 있었는데 axios의 timeout시간에 딱 걸려버린 것이다.
해당 api가 1.5초 정도 걸렸는데 1초로 해줬으니 axios에서 응답을 받기도 전에 끊어버린 것이다.</p>
<blockquote>
<p>✏️ timeout시간을 1초에서 3초로 늘려줬더니 해결 !</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[FE] Github Action을 이용하여 AWS S3 CD 구축하기 - 에러핸들링]]></title>
            <link>https://velog.io/@sujeong_dev/FE-Github-Action%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-AWS-S3-CD-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0-%EC%97%90%EB%9F%AC%ED%95%B8%EB%93%A4%EB%A7%81</link>
            <guid>https://velog.io/@sujeong_dev/FE-Github-Action%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-AWS-S3-CD-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0-%EC%97%90%EB%9F%AC%ED%95%B8%EB%93%A4%EB%A7%81</guid>
            <pubDate>Wed, 05 Apr 2023 15:42:20 GMT</pubDate>
            <description><![CDATA[<p>Github Action에 AWS S3 배포를 구축하면서 두가지의 에러를 마주쳤다...
역시 안마주치길 바랬지만...ㅎ 피할 수 없다면 즐겨라
끝내 해결해서 초록빛을 보았다 😎 나의 에러 해결방법을 소개하겠습니다.</p>
<h2 id="1-react-scripts-not-found-에러🥲">1. &#39;react-scripts: not found&#39; 에러🥲</h2>
<p><img src="https://velog.velcdn.com/images/sujeong_dev/post/e0d6ff43-b0c6-4973-8c1f-b20044554c71/image.png" alt=""></p>
<p>역시 한번에 될리가 없지.....뭐가 문제니ㅠㅠ
react-script를 찾을 수가 없다는 에러다...왜? why?
이런 바보... npm install을 하지 않아서 package.json내의 파일들을 다운로드 하지 못해 react-script를 알지 못하는거였다.
<strong>workflow에 run:npm install</strong> 추가해주면 된다 😎
그러나 찾아보니 npm install 대신 <code>npm ci</code>를 추천해주더라.
빌드 시에 package-lock.json을 수정하지 않고 정확한 버전의 패키지들을 설치해 node_modules에 적재하기 때문에 버전이 동일하다는 이유에서다!</p>
<h2 id="2-react-18에서-testing-libraryreact-hooks-not-support">2. React 18에서 @testing-library/react-hooks not support?!</h2>
<p><img src="https://velog.velcdn.com/images/sujeong_dev/post/170e270e-4509-473d-90eb-62aa27936940/image.png" alt=""></p>
<p>npm ci로 바꾸기 전 npm install을 추가했음에도 불구하고 에러가 났다..
@testing-library/react-hooks에서 해당 @types/react를 찾을 수 없다는거 였는데 package-lock.json을 보니 16, 17버전 의존성만 있고 18이 없었다..!!!
<a href="https://github.com/testing-library/react-hooks-testing-library">공식 github</a>을 가서 봤더니 Readme에 React 18에서는 @testing-library/react에서 한번에 지원하고 있으니 react-hooks는 Uninstall해도 된다고 나와있었다.(이것이 Readme의 중요성인가..)
삭제하고 다시 Install하니 정상적으로 되었다 😊</p>
<h2 id="3-eslint-warn-규칙때문에-build-fail-에러🥲">3. eslint warn 규칙때문에 build fail 에러🥲</h2>
<p><img src="https://velog.velcdn.com/images/sujeong_dev/post/4bf2a1ee-4ecc-4d08-b8d5-111096c465c4/image.png" alt=""></p>
<p>이젠 정말 다 된줄 알았어...ㅎ
협업을 위해 설정한 eslint룰 중에 warn으로 지정해준 것들 때문에 build가 fail난다..
어차피 우리는 eslint error로 지정해둔 규칙들 중 하나라도 어기면 push를 못하게하는 husky를 지정해놨기때문에 여기서는 eslint를 무시하도록 하기로 했다.
<strong>CI가 false일 때 npm run build</strong>를 해주게끔 workflow를 수정해주면 된다!
흠 근데 env에 CI=false로 하면 안되고 npm run build 옆에 지정해주어야 된다...왜일까</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[FE] Github Action을 이용하여 AWS S3 CD 구축하기]]></title>
            <link>https://velog.io/@sujeong_dev/FE-Github-Action%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-AWS-S3-CD</link>
            <guid>https://velog.io/@sujeong_dev/FE-Github-Action%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-AWS-S3-CD</guid>
            <pubDate>Wed, 05 Apr 2023 10:21:39 GMT</pubDate>
            <description><![CDATA[<p>프로젝트를 진행하면서 개발은 오케이..어느정도 해왔다. 그렇지만 내가 이렇게 개발한다한들 실제 사용자들이 내 프로젝트가 있는지 없는지도 모른다면? 말짱 꽝이다..
그렇기때문에 배포는 필수적인것이다 ✨</p>
<h2 id="1-aws-s3">1. AWS S3</h2>
<p>배포를 도와주는 많은 툴들이 있지만 그 중에 보편적으로 사용하는 AWS의 S3를 활용해보자.
먼저 AWS란 현재 가장 보편적으로 사용되는 클라우드 컴퓨팅 서비스로 그 중 S3는 특정 파일을 저장해 인터넷상에서 접근할 수 있도록 해주는 서비스이다. </p>
<blockquote>
<p>CRA로 React환경을 구축하고 빌드하면 build파일들이 생성되고 이를 브라우저에서 접근해서 실행하면 CSR의 특징으로 S3서비스를 통해 배포할 수 있다.</p>
</blockquote>
<h2 id="2-aws-s3로-배포해보기">2. AWS S3로 배포해보기</h2>
<h3 id="2-1-aws-계정-생성">2-1. AWS 계정 생성</h3>
<p>배포를 하려면 일단 <strong>AWS 계정을 먼저 생성한다 !</strong>
<del>신용카드 등록 해야해서 유료인가하지만 프로젝트 배포정도는 무료이고 인증용으로 돈을 가져갔다가 다시준다 ㅎ</del>
<img src="https://velog.velcdn.com/images/sujeong_dev/post/8ae0ea5b-e20a-4249-8004-0eb6a649e2d9/image.png" alt=""></p>
<p>그 후 콘솔에서 S3에 접속한다.
<img src="https://velog.velcdn.com/images/sujeong_dev/post/c9de3e3e-95a6-4f17-907d-f508fd16376d/image.png" alt=""></p>
<h3 id="2-2-버킷-생성">2-2. 버킷 생성</h3>
<p><img src="https://velog.velcdn.com/images/sujeong_dev/post/a52c56b7-57ee-41eb-b2ed-c64eaac14c63/image.png" alt=""></p>
<p>버킷 이름을 설정하고 버킷을 저장할 AWS리전을 선택한다.
보통의 토이 프로젝트 같은 소규모 같은 부분은 우리나라 한정에서만 접근하면 되니까 전세계에 있는 AWS 서버 중 서울에 있는 곳으로 지정해준다.</p>
<p><img src="https://velog.velcdn.com/images/sujeong_dev/post/06676c13-4407-474b-9200-d3747bf3f19c/image.png" alt=""></p>
<p>그 뒤로 우리는 해당 배포 URL을 알고있는 누구나 접근을 허용해야하므로 기본적으로 설정되어있는 엑세스차단 체크를 풀어준다. <code>퍼블릭 상태가 될 수 있음을 알고 있습니다</code>에는 체크표시 !</p>
<h3 id="2-3-배포할-파일들을-업로드">2-3. 배포할 파일들을 업로드</h3>
<p><img src="https://velog.velcdn.com/images/sujeong_dev/post/d26dfa5a-3988-4209-905a-25d4151c2248/image.png" alt=""></p>
<p>만들어진 객체에 배포할 파일들을 업로드해준다. 업로드 버튼을 눌러서해도 되고 drag &amp; drop으로 해도된다. 여기에 업로드 할 파일들은 이전에 말했듯이 <strong>빌드하여 생성된 파일</strong>들을 업로드 해준다.</p>
<blockquote>
<p>업로드 할 파일 객체들에 index.html이 보여야한다 ! (그 위 상위폴더들 xxx)</p>
</blockquote>
<h3 id="2-4-웹-사이트-호스팅-활성화">2-4. 웹 사이트 호스팅 활성화</h3>
<p><img src="https://velog.velcdn.com/images/sujeong_dev/post/eb6b3f8a-16ab-4224-b99a-32325ff987cb/image.png" alt=""></p>
<p>그 후 속성 탭에 가서 <code>정적 웹 사이트 호스팅</code>을 설정해준다.
활성화 한 후 기본 페이지, 즉 /경로로 들어왔을 경우 보이는 기본페이지다. CSR 기반의 React프로젝트이니까 index.html로 지정해준다. 오류가 발생했을 때 예외페이지도 설정해줄 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/sujeong_dev/post/ae5894d7-e60e-4628-959c-74025b0df4bf/image.png" alt=""></p>
<p>따단! 활성화를 시키면 배포된 URL을 보내준다!!
이제 한번 웹사이트를 접근해보자!!</p>
<p><img src="https://velog.velcdn.com/images/sujeong_dev/post/cd45e26b-e77f-418c-9211-1dd46fcabd60/image.png" alt=""></p>
<p>🥲...끝날때까지 끝난게 아니었다...왜 안보이니....
403 error는 권한없음 에러! <code>나 너 누군지는 알겠어(인증 ok) 그치만 너는 여기에 접근할 권한이 없단다(인가 fail)</code> 요런 말이다.
그렇다면 허락을 해주면 되겠지?</p>
<h3 id="2-5-버켓-정책-설정">2-5. 버켓 정책 설정</h3>
<p><img src="https://velog.velcdn.com/images/sujeong_dev/post/3acfc523-5e13-4db0-a961-11a706237126/image.png" alt=""></p>
<p><code>해당하는 Resource, 즉 해당 버킷 ARN에 대해 GetObject(객체를 가져오는 행동)을 허락하겠다.</code>라는 의미의 정책을 설정해주었다.
(Sid는 해당 정책에 대한 unique한 id로 무슨 역할을 하는 정책인지에 대한 것들이 드러나게끔 설정해준다.)</p>
<p><img src="https://velog.velcdn.com/images/sujeong_dev/post/b9b0ac22-19b5-4488-a440-4e1d8f089b2a/image.png" alt=""></p>
<p>이렇게하면 드디어 웹사이트에 페이지가 등장!!!!
이제 아까 받은 URL로 우리나라 어디에서 접근하든 빠르게 내 프로젝트를 확인할 수 있다 😎</p>
<p>실무에서도 이렇게 하는지는 잘 모르겠다.(<del>엑세스 차단을 해제해도 문제가 없을까..?</del>)
일단 이렇게하면 AWS S3에 배포는 성공적으로 끝마쳤다.
그렇다면 해당 프로젝트가 수정될때마다 AWS 사이트에 들어가서 버킷에 들어가서 다시 파일들을 업로드하는 이런 귀찮은 짓을 해야할까?ㅠㅠㅠ</p>
<p>역시 개발자는 자동화...배포를 자동화하여 지속적인 배포(CD)를 구축해보자!</p>
<h2 id="3-github-action을-이용한-지속적인-배포">3. Github Action을 이용한 지속적인 배포</h2>
<p><img src="https://velog.velcdn.com/images/sujeong_dev/post/b8fab0a2-4906-4ee0-a036-3ee0c0d08c5b/image.png" alt=""></p>
<h6 id="출처-github-actions-공식문서httpsdocsgithubcomenactionslearn-github-actionsunderstanding-github-actions">출처: GitHub Actions 공식문서(<a href="https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions">https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions</a>)</h6>
<p>github action은 이러한 과정을 거치는데 이 과정을 <strong>workflow</strong>라고 한다.
push, pull request open, issue open등의 특정한 활동을 <strong>Event</strong>라고 부르며 Event가 발생했을 때 Runner가 동작한다. <strong>Runner</strong>는 workflow를 실행할 서버를 의미하며 직접 컴퓨터를 관리할 필요없이 가상의 Runner에서 workflow를 실행시킬 수 있다.
하나의 Runner에는 여러개의 Job이 존재한다. Job에는 자주 사용되는 기능들을 모아둔 <strong>Action</strong>을 등록할 수 있다. 설정파일에서 <code>use</code> 키워드와 함께 사용할 수 있으며 브랜치로 체크아웃하고, 환경을 설정하는 등 복잡하지만 자주 사용되는 과정들을 미리 정의해두고 편리하게 활용할 수 있으며 GitHub Marketplace에서 Action들을 검색하고 활용할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/sujeong_dev/post/a51beae7-b245-405f-bd46-6bac22a9ce1e/image.png" alt=""></p>
<p>repository를 생성하면 이렇게 Action탭에 workflow를 만들게끔 되어있다.
내 스스로 세팅할 수도 있고 이미 많은 템플릿들도 있어서 선택해서 만들어주면 된다.ㅎㅎ
나는 직접 만들어서 세팅을 해보도록 하겠다.</p>
<p><img src="https://velog.velcdn.com/images/sujeong_dev/post/ed66c9ab-cc4d-4ec6-9def-d04a34d72922/image.png" alt=""></p>
<p>S3 sync를 맞춰주는 action을 marketplace의 가장 많이 설치된 버전으로 가져와서 등록해보았다.<del>(역시 자동화 좋아하는 개발자들..ㅎ)</del>
해당 workflow는 <code>main브랜치에 push가될 때 npm run build를 실행하고 기존에 버킷에 있던 파일들은 delete한 후 build파일에 있는 파일들을 해당 리전에 있는 해당 버킷에 업로드하겠다</code>라는 의미이다.
(workflow만드는건 <a href="https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions">공식문서</a>에 잘 나와있으니 이것도 참고하기!)</p>
<p>workflow파일은 YMAL형태의 .yml파일로 저장되어야하며 <code>프로젝트파일/.github/workflows/</code>경로안에 위치해있어야한다. env에 보이는 환경파일들은 노출이되면 안되기 때문에 github action에 등록한 후 환경변수를 불러오는 형식으로 불러와준다.</p>
<blockquote>
<p>⚠️ 환경변수를 설정하면 다시 들어가도 보여주지 않기 때문에 잘 기억을 해둔다!
(accesskey도 마찬가지로 secret accesskey는 다시 알려주지 않는다)</p>
</blockquote>
<h6 id="aws-환경변수-설정하는-곳-↓">AWS 환경변수 설정하는 곳 ↓</h6>
<p><img src="https://velog.velcdn.com/images/sujeong_dev/post/66d69364-46d5-4a1f-becf-44749a8b2088/image.png" alt=""></p>
<h6 id="aws-access-key-발급하는-곳-↓">AWS access key 발급하는 곳 ↓</h6>
<p><img src="https://velog.velcdn.com/images/sujeong_dev/post/5660c1ca-115f-42a0-8c6a-3adf129b7aa8/image.png" alt=""></p>
<p>자 이제 해당 repository main 브랜치에 push를 해서 돌려보자!</p>
<h2 id="번외-에러-핸들링">번외. 에러 핸들링</h2>
<p><img src="https://velog.velcdn.com/images/sujeong_dev/post/e0d6ff43-b0c6-4973-8c1f-b20044554c71/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/sujeong_dev/post/4bf2a1ee-4ecc-4d08-b8d5-111096c465c4/image.png" alt=""></p>
<p>역시나 마주한 에러들.... 에러 핸들링은 따로 정리해두겠습니다!!</p>
<h2 id="마침내-성공-😂">마침내 성공!! 😂</h2>
<p><img src="https://velog.velcdn.com/images/sujeong_dev/post/9c0f1a1d-2004-4372-8071-6f5b3ec01e60/image.png" alt=""></p>
<p>드디어.....드디어..초록빛...!!!
성공적으로 배포를 마쳤습니다!🙏</p>
<hr>
<p>프론트엔드가 배포까지 알아야하냐구요?
네. 그렇습니다.
프론트엔드도 우리가 만드는 서비스에 책임을 가지고 있어야 합니다.
개발 프로세스를 알고 문제가 생겼을 때 트러블슈팅을 하기 위해 배포를 알아야합니다.💪🏻</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Project] 원티드 프리온보딩 인턴십 회고]]></title>
            <link>https://velog.io/@sujeong_dev/Project-%EC%9B%90%ED%8B%B0%EB%93%9C-%ED%94%84%EB%A6%AC%EC%98%A8%EB%B3%B4%EB%94%A9-%EC%9D%B8%ED%84%B4%EC%8B%AD-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@sujeong_dev/Project-%EC%9B%90%ED%8B%B0%EB%93%9C-%ED%94%84%EB%A6%AC%EC%98%A8%EB%B3%B4%EB%94%A9-%EC%9D%B8%ED%84%B4%EC%8B%AD-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Tue, 04 Apr 2023 06:07:18 GMT</pubDate>
            <description><![CDATA[<p>2023년 3월 !!
한달 불태웠던 원티드 프리온보딩 인턴십.
React 상태관리나 관심사의 분리에 대해 더 알 수 있었던 시간이 었고 막연히 Typescript공부만 하고 있었는데 실질적인 프로젝트를 통해 어떻게 React에서 사용할 수 있었는지 알 수 있었다.
한주한주 주어지는 기업과제를 하던 새벽 너무 힘들었던 적도 많고 내가 되게 얕게 공부하고 있었다는 무지함을 문득 느끼게 되는 순간들이었지만 지금와서야 생각해보면 성장하는 시간들이었고 해냈다는 뿌듯함이 더 크게 느껴진다 !</p>
<h1 id="🥰-좋았던-점liked">🥰 좋았던 점(Liked)</h1>
<p>인턴십을 신청할 때 냈던 에세이에서 내가 여기서 얻어가고자 하는 점이 두가지가 있었다.</p>
<blockquote>
<p>프리온보딩 인턴십에 참여하여 이루고 싶은게 2가지가 있다.</p>
</blockquote>
<ol>
<li>역량 향상</li>
<li>공유 문화의 경험
실제 기업의 프로덕트를 접해보고 기업 과제를 해결해 봄으로써 실전 역량을 강화시키고 채용과정에서의 기술 과제와 면접에 대한 대비를 할 것이다.
또한 나는 개발자로서 중요한 것이 개발자 간의 공유문화라고 생각한다. 공유를 통해 넓은 인사이트를 가지고 싶기 때문에 많은 동료들과 협업하여 다양한 인사이트의 피드백을 기대한다.
그렇게 되면 스스로의 코드 습관을 알고 나도 피드백을 줌으로써 다른 개발자의 코드를 분석하는 역량을 키우고 싶다.</li>
</ol>
<p>두개 다 얻었다!! 기술적인 부분, 커뮤니케이션적인 부분에서의 역량을 향상할 수 있었고 코드리뷰를 통한 공유 문화를 경험해 볼 수 있었다.</p>
<p><strong>몹 프로그래밍?</strong></p>
<ul>
<li>몹 프로그래밍....페어 프로그래밍도 아니고 처음 듣는 단어였는데 둘이 아닌 여러명의 개발자들끼리 하나의 디바이스로 같이 프로그래밍을 진행하는 것을 말한다. </li>
<li>우리는 서로 다른 곳에서 있는 9명의 사람들끼리 같이 프로그래밍을 진행했다. 처음엔 각자 개발해보고 assignment끼리 묶어서 다같이 디스코드에 모여서 best practice를 도출하는 방향으로 개발을 진행했다. 원래 사공이 많으면 배가 산으로 간다는 말이 있지만...이번에는 이런저런 아이디어들을 많이 엿볼 수 있었고 고집부리는 팀원이 없어서 좋은 부분만 경험할 수 있었던 것 같다!</li>
</ul>
<p><strong>코드 리뷰</strong></p>
<ul>
<li>팀원들의 실력이 워낙 훌륭해서 배운 점이 많았다. 각자 feature브랜치에서 작업한 후 PR을 올려서 코드리뷰를 진행했다. 조금 더 관심사의 분리를 진행했으면 좋았을 부분, 왜 이렇게 했는지 등 서로 공유했던 부분이 내가 대답하고 물어보면서 한번 더 정리할 수 있었고 내가 미처 생각하지 못한 부분도 발견할 수 있어서 한뼘 성장할 수 있었다.</li>
<li>github에 issue을 등록하고 commit이랑 PR에 <code>[#이슈번호]</code> 이렇게 태그를 달아서 이슈별로 구분해서 commit과 PR을 볼 수 있어서 너무 좋았다. </li>
</ul>
<p><strong>husky 도입</strong></p>
<ul>
<li>프로젝트하면서 prettier나 eslint는 사용해봤지만 husky는 처음 시도해보았다. commit전에 컨벤션을 맞췄는지 확인해볼 수 있는 기능으로 warning은 경고창만 error는 아예 push가 안되게끔 할 수도 있었고 팀프로젝트하면서 되게 유용한 기능이었다. </li>
</ul>
<hr>
<h1 id="✏️-배웠던-점learned">✏️ 배웠던 점(Learned)</h1>
<p>양질의 세션과 프로젝트를 진행하면서 기술적인 부분에서 배운 점이 되게 많았다.
해당 내용들을 하나씩 정리해야겠다.</p>
<blockquote>
<p>✍🏻 세션 내용 
Github Action을 이용한 AWS S3 CD
useCallback / useMemo / Context API를 이용한 컴포넌트 렌더링 최적화
의존성 역전을 통해 소프트웨어 유연하게 만들기
TypeScript로 안전하고 표현력이 높은 애플리케이션 작성하는법
Redux의 개념과 미들웨어의 원리 이해하기
Jest와 RTL을 이용해 리액트 컴포넌트 테스트하기
클로저 / 스코프 &amp; 호이스팅의 원리를 실행 컨텍스트와 연관지어 이해하기</p>
</blockquote>
<hr>
<h1 id="🥲-부족했던-점leaked">🥲 부족했던 점(Leaked)</h1>
<p><strong>정보의 홍수..</strong></p>
<ul>
<li>최대한 그 주 세션시간에 배운 내용을 적용하고 싶었지만 알찬 세션 내용으로 정리하고 그 내용을 적용하기까지란 빠듯한 데드라인이었다..차근차근 블로그에 정리해봐야겠다..!</li>
</ul>
<hr>
<h1 id="🙏-바라는-점longed-for">🙏 바라는 점(Longed for)</h1>
<p><strong>지금처럼 우직한 궁둥이힘</strong></p>
<ul>
<li>기술적인 부분에서 참 많이 배웠다는 생각이 든다. 오히려 짧은 기간으로 굉장히 몰입할 수 있었고 이렇게 궁둥이를 오래 붙히고 있던 적이 얼마만인가 싶었다.
앞으로 지금처럼 빡 몰입해서 <strong>꼭 좋은 곳에 취업하고 싶다 💪🏻</strong></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Project] STAWEFOLIO 회고록]]></title>
            <link>https://velog.io/@sujeong_dev/Project-STAWEFOLIO-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@sujeong_dev/Project-STAWEFOLIO-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Fri, 17 Mar 2023 17:34:30 GMT</pubDate>
            <description><![CDATA[<p>드디어 위코드의 마지막 프로젝트,
STAWEFOLIO를 통해 동기들과의 마지막을 마무리 지었다 !</p>
<h1 id="🥰-좋았던-점liked">🥰 좋았던 점(Liked)</h1>
<p><strong>Notion &amp; Trello &amp; Gitbook</strong></p>
<ul>
<li>이번에는 두번째라 그런지 Notion에 Plan을 계획하는데 익숙해졌고 티켓도 마크업, 스타일링, 기능구현으로 나누어 성취감도 얻고 진척도 파악도 빠르게 할 수 있었다.</li>
<li>Notion으로 Sprint별 standup meeting기록과 Daily plan을 계획하였고 Trello로 전체적인 개발일정을 관리하였다.</li>
<li>Gitbook을 통해 API 문서도 그때그때 타협한 내용으로 수정되어 확인할 수 있었다.</li>
</ul>
<blockquote>
<p><strong>Planning Meeting</strong>
하나의 Sprint가 시작되는 매주 월요일마다 Trello를 사용하여 해당 Sprint때의 목표를 설정하였다.
<strong>Daily Standup Meeting</strong>
아침 10시마다 전날의 개발진척도와 오늘의 목표, blocker들을 공유하였다.
<strong>Retrospective Meeting</strong>
최종발표 날 두번의 Sprint동안의 KPT회고를 통한 회고를 진행하였다.</p>
</blockquote>
<p><strong>standup meeting 10분컷</strong></p>
<ul>
<li>standup meeting은 말그대로 서서하는 미팅으로 서서해도 괜찮을만큼 짧게 끝내자는 취지이다. </li>
<li>저번에는 <del>진짜 sitdown으로 바꾸자고</del>(ㅎ)할만큼 많은 이야기들을 했었는데 이번에는 미팅 10분전 각자 노션에 진척도, daily todo plan을 얘기해주면서 10분 내로 끝이 날 수 있었다.</li>
</ul>
<p><strong>기술 공유</strong></p>
<ul>
<li>GNB에 모달창을 띄워 가고싶은 지역을 선택하는 UI에서 react-modal 라이브러리 사용법을 팀원에게 공유해주었다! <del>물론 나의 datepicker가 시급했지만..ㅎ</del></li>
<li>CSS를 통해 만들고 있던 팀원에게 공유해주었고 덕분에 시간이 단축이 많이되어 크게는 우리 팀의 개발진척도에도 많은 영향을 줄 수 있었다.</li>
</ul>
<hr>
<h1 id="✏️-배웠던-점learned">✏️ 배웠던 점(Learned)</h1>
<h2 id="공식문서의-중요성">공식문서의 중요성</h2>
<ul>
<li>죽음의 date picker 라이브러리 사용 시 처음에는 잘 만들어진 예시와 스택오버플로우를 잔뜩 찾아다녔다...그치만 역시 튜닝의 근본은 순정이라고 돌고돌아 공식문서에 답이 있었다.</li>
<li>요즘에는 한국어도 많이 제공해주고 데모영상 및 코드박스로 직접 코드를 수정해보면서까지 배울 수 있다.</li>
</ul>
<h2 id="forwardref를-통해-dom요소에-접근">forwardRef를 통해 DOM요소에 접근</h2>
<p>datepicker라이브러리는 input창에 클릭 시 calendar가 띄워지는 형식이다. 이 때의 Input창을 사용자가 직접 custom할 수도 있는데 그 때 forwardRef를 통해 DOM요소에 접근할 수 있다.
<code>ref</code>props는 map의 key Prop처럼 특수한 목적으로 사용되고 그 때 forwardRef를 통해 외부에서 받은 ref prop으로 부모 컴포넌트에서 해당 컴포넌트 엘리먼트에 직접 접근할 수 있다. </p>
<p>👇 /src/components/DayPicker/CustomInput.js</p>
<pre><code class="language-jsx">const CustomInput = forwardRef(({ value, onClick }, ref) =&gt; {
  //...codes
  return (
    &lt;InputButton onClick={onClick} ref={ref}&gt;
      {value === &#39;&#39;
        ? checkInDate.length === 0 &amp;&amp; checkOutDate.length === 0
          ? &#39;날짜를 입력해주세요.&#39;
          : checkInDate + &#39; - &#39; + checkOutDate
        : value + &#39; | &#39; + days + &#39;박&#39;}
    &lt;/InputButton&gt;
  );
});</code></pre>
<h2 id="custom-hook을-통해-조금-더-정확한-관심사의-분리">custom hook을 통해 조금 더 정확한 관심사의 분리</h2>
<p>인원별 필터링을 구현할 때 인원을 조절하는 훅을 만들어 인원수를 조절하는 기능만 담당하도록 구현하였다. 그럼 인원수를 조절하는 방식이나 값을 변경해야 하는 경우가생기면 해당 훅에서만 변경해주면 된다.</p>
<p>👇 /src/pages/RoomList/DropDown/People.js</p>
<pre><code class="language-jsx">const useCounter = () =&gt; {
  const [searchParams] = useSearchParams();
  const [quantity, setQuantity] = useState(
    searchParams.getAll(&#39;numberOfGuests&#39;).length === 0
      ? 0
      : searchParams.getAll(&#39;numberOfGuests&#39;)
  );

  const plusQuantity = () =&gt; {
    setQuantity(quantity &gt; 9 ? 10 : quantity + 1);
  };

  const minusQuantity = () =&gt; {
    setQuantity(quantity &lt; 1 ? 0 : quantity - 1);
  };

  return { quantity, plusQuantity, minusQuantity };
};
</code></pre>
<blockquote>
<p>ㄴ💡 <strong>더 자세한 코드들이 궁금하시다면?</strong></p>
</blockquote>
<ul>
<li><a href="https://github.com/sujeong-dev/39-2nd-stawefolio-frontend">github repository 바로가기</a></li>
</ul>
<hr>
<h1 id="🥲-부족했던-점leaked">🥲 부족했던 점(Leaked)</h1>
<p><strong>상태관리의 부재</strong></p>
<ul>
<li>물론 엄청난 props drillling은 발생하지 않았지만 날짜를 관리할 때 props로 내려주는게 아닌 전역상태관리 라이브러리를 썼으면 어땠을까하는 아쉬움이 들기도 한다.</li>
</ul>
<p><strong>데드라인에 쫓기기</strong></p>
<ul>
<li>이건 사실 모든일에 마찬가지일 것이다. 데드라인에 쫓겨 기능을 구현하는데에만 급급했었다. </li>
<li>물론 모든 코드는 작성하는 순간 레거시가 되버린다. </li>
<li>그렇지만 클린코드를 작성해야한다고 계속 신경을 쓰고 머릿속에 있던 걸 바로 치지말고 한번 더 생각해보고 치는 습관을 들이자.</li>
</ul>
<hr>
<h1 id="🙏-바라는-점longed-for">🙏 바라는 점(Longed for)</h1>
<p>조금 더 사용자의 입장에서 생각해보자.
사용자는 내가 코드를 어떻게 치는지, 클린코드인지 관심도 없고 알 수도 없다. 그냥 UI가 직관적이고 편리하고 좋으면 끝인거다.
사용자 입장에서 이 페이지에 이렇게하면 더 편리할까?라고 생각을 해보자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Project] WCF SHOP 회고록]]></title>
            <link>https://velog.io/@sujeong_dev/Project-WCF-SHOP</link>
            <guid>https://velog.io/@sujeong_dev/Project-WCF-SHOP</guid>
            <pubDate>Wed, 21 Dec 2022 15:40:06 GMT</pubDate>
            <description><![CDATA[<p>대장정의 1차 프로젝트가 끝이났따✌️
다사다난하고 우당탕탕이었지만 좋은 팀원들과 함께 기획부터 회고까지 무사히 끝마칠 수 있었다 :)
<br/></p>
<h1 id="🥰-좋았던-점liked">🥰 좋았던 점(Liked)</h1>
<p>우리 팀원 분위기가 너무 좋았다.... 진짜 여기서 2달전에 만났던 사람들 맞나 싶을정도로 프로젝트 하는동안 같이 울고 웃었던 기억들이 많다..!
나의 리드하에 팀원들의 코로나 격리로 원격으로 소통하고 애자일 스크럼에 맞게 1주단위의 Sprint로 팀 프로젝트를 진행했다.
Notion을 통해 daily standup metting기록과 Plan을 계획했고 Trello를 통해 전반적인 스프린트 개발일정을 공유하여 진척도를 조절하였다.</p>
<p><strong>Scrum Process</strong>
<code>애자일방법론</code>에 따라 Weekly Sprint로 총 2번의 Sprint를 진행하였다.
<img src="https://velog.velcdn.com/images/sujeong_dev/post/cb9569d7-85d1-45f4-a98e-399cbf2b7893/image.png" alt="">
<img src="https://velog.velcdn.com/images/sujeong_dev/post/e1ac2209-77eb-43e3-9e8a-a41c8398251d/image.png" alt=""></p>
<blockquote>
<p><strong>Planning Meeting</strong>
하나의 Sprint가 시작되는 매주 월요일마다 Trello를 사용하여 해당 Sprint때의 목표를 설정하였다.
<strong>Daily Standup Meeting</strong>
아침 10시마다 전날의 개발진척도와 오늘의 목표, blocker들을 공유하였다.
<strong>Retrospective Meeting</strong>
최종발표 날 두번의 Sprint동안의 KPT회고를 통한 회고를 진행하였다.</p>
</blockquote>
<p><strong>Gitbook</strong></p>
<p><img src="https://velog.velcdn.com/images/seoya_lee/post/1fd78929-681d-4117-8bbe-68bc337ac680/image.png" alt="">
gitbook을 통해 백엔드와의 소통도 원만하게 진행되었다.
백엔드, 프론트엔드 페이지 담당자끼리 API Data 형식이나 무엇을 넘겨줄건지 고민하면서 미리 협의해두었다.
그럼에도 중간에 필요하거나 수정할 부분이 생기면 그때그때 소통하여 조율하여 개발을 진행하면서 큰 변화가 없이 진행할 수 있었다.</p>
<hr>
<h1 id="✏️-배웠던-점learned">✏️ 배웠던 점(Learned)</h1>
<h2 id="탭-별-조건부-렌더링"><strong>탭 별 조건부 렌더링</strong></h2>
<p><img src="https://velog.velcdn.com/images/sujeong_dev/post/30261ee7-6244-4b83-8ccb-2ab67f538670/image.gif" alt=""></p>
<p>리스트페이지에서 처음에는 브랜드를 클릭했을 경우 Brand 컴포넌트를 return하게끔 switch case로 구현했었다. 멘토님의 피드백으로 객체를 사용해서 클릭한 탭의 id를 저장해서 해당하는 id의 content로 등록되어있는 컴포넌트를 불러오게끔 조건부 렌더링을 리팩토링 하였다.</p>
<p><strong>👇 ProductList.js</strong></p>
<pre><code class="language-jsx">const TABS = [
    {
      id: 0,
      title: &#39;브랜드&#39;,
      content: &lt;Brand /&gt;,
    },
    {
      id: 1,
      title: &#39;가격&#39;,
      content: &lt;Price /&gt;,
    },
    {
      id: 2,
      title: &#39;사이즈&#39;,
      content: &lt;Size /&gt;,
    },
  ];

            &lt;div className=&quot;left-filter&quot;&gt;
              &lt;ul className=&quot;filter-list&quot;&gt;
                {TABS.map(filter =&gt; {
                  const isCurrent = currentTab === filter.id;
                  return (
                    &lt;li key={filter.id}&gt;
                      &lt;button
                        onClick={() =&gt;
                          setCurrentTab(isCurrent ? &#39;&#39; : filter.id)
                        }
                        className={isCurrent ? &#39;current&#39; : &#39;&#39;}
                      &gt;
                        {filter.title}
                      &lt;/button&gt;
                    &lt;/li&gt;
                  );
                })}
              &lt;/ul&gt;
            &lt;/div&gt;
          {TABS.find(({ id }) =&gt; id === currentTab)?.content}
</code></pre>
<blockquote>
<ul>
<li><code>TAB</code>객체를 활용하여 id, content를 매칭하여 조건부 렌더링 구현</li>
</ul>
</blockquote>
<ul>
<li><code>map</code>을 돌려 <code>title</code>을 불러와 필터링 버튼을 생성하고 click시 현재 tab의 id값을 설정한다.</li>
<li>현재 tab의 id를 <code>TAB</code>객체에서 찾아 해당하는 id의 content인 컴포넌트를 불러온다.</li>
<li><code>옵셔널체이닝</code>을 통해 <code>TAB</code>객체에서 현재 tab의 id가 없는 경우, 즉 tab이 다 닫혀있는 경우 content를 불러올 수 없기 때문에 예외처리를 해주었다.</li>
<li><ul>
<li>현재 열려있는 tab을 한번 더 click시 현재 tab의 id와 <code>TAB</code>객체의 id가 같은 지 판별하여 현재 tab의 id를 reset하여 toggle기능 구현</li>
</ul>
</li>
</ul>
<h2 id="querystring"><strong>querystring</strong></h2>
<p>브랜드, 가격, 사이즈의 정보를 state에 저장하였더니 새로고침이나 재접속 시 체크한 필터값이 날아가는 이슈가 발생했다. 
검색 결과 상태로 저장하지 않고 url에 저장하는 querystring으로 리팩토링 하였다 !</p>
<p><strong>👇 Brand.js</strong></p>
<pre><code class="language-jsx">const [searchParams, setSearchParams] = useSearchParams();

  //TODO: 브랜드 검색기능
  const searchBrand = e =&gt; {
    const filterBrand = BRAND.filter(brand =&gt;
      brand.name.includes(e.target.value)
    );
    setFilterList(filterBrand);
  };

  //TODO: querystring 생성
  const prevQuery = searchParams.getAll(&#39;brandId&#39;);
  const handleCheckbox = e =&gt; {
    const { checked, value } = e.target;
    if (checked) {
      searchParams.append(&#39;brandId&#39;, value);
      setSearchParams(searchParams);
    } else {
      searchParams.delete(&#39;brandId&#39;);
      prevQuery
        .filter(query =&gt; query !== value)
        .forEach(query =&gt; searchParams.append(&#39;brandId&#39;, query));
      setSearchParams(searchParams);
    }
  };</code></pre>
<blockquote>
<ul>
<li>checkbox에 check가 되었으면 searchparams에 append()를 통해 추가한다.</li>
</ul>
</blockquote>
<ul>
<li>check가 해제되었으면 해당 param key의 value들을 모두 삭제하고 이전에 모든 value들을 담아놓은 <code>prevQuery</code>변수에서 e.target.value인 것들을 제외한 value들을 다시 searchparams에 담는다.</li>
</ul>
<h2 id="path-parameter를-통한-동적라우팅"><strong>path parameter를 통한 동적라우팅</strong></h2>
<p><img src="https://velog.velcdn.com/images/sujeong_dev/post/666a5342-2b4b-4058-a104-f79b03164847/image.gif" alt="">
상세페이지를 하나씩 다 만들어야하나 고민하다가 동적라우팅을 알게 되었다!</p>
<p><strong>👇 Product.js</strong></p>
<pre><code class="language-jsx">    &lt;li&gt;
      &lt;Link to={`/product-detail/${id}`}&gt;
        &lt;img src={imgurl} alt=&quot;상품이미지&quot; /&gt;
        &lt;div className=&quot;like&quot; /&gt;
        &lt;div className=&quot;info&quot;&gt;
          &lt;span className=&quot;brand&quot;&gt;{brand}&lt;/span&gt;
          &lt;span className=&quot;name&quot;&gt;{name}&lt;/span&gt;
          &lt;span className=&quot;price&quot;&gt;{Number(price).toLocaleString()}&lt;/span&gt;
          &lt;span className=&quot;heart&quot;&gt;
            &lt;i className=&quot;fa-regular fa-heart&quot; /&gt;
            &lt;em&gt;999+&lt;/em&gt;
          &lt;/span&gt;
        &lt;/div&gt;
      &lt;/Link&gt;
    &lt;/li&gt;</code></pre>
<p><strong>👇 Router.js</strong></p>
<pre><code class="language-jsx">    &lt;BrowserRouter&gt;
      &lt;Nav /&gt;
      &lt;Routes&gt;
        &lt;Route path=&quot;/&quot; element={&lt;Main /&gt;} /&gt;
        &lt;Route path=&quot;/product-detail/:productId&quot; element={&lt;ProductDetail /&gt;} /&gt;
        &lt;Route path=&quot;/product-list&quot; element={&lt;ProductList /&gt;} /&gt;
      &lt;/Routes&gt;
      &lt;Footer /&gt;
    &lt;/BrowserRouter&gt;</code></pre>
<blockquote>
<ul>
<li><code>Router.js</code>의 path prop에 <code>:productId</code>로 이름을 지정한다.</li>
</ul>
</blockquote>
<ul>
<li>상품리스트페이지에서 어떠한 품목을 선택해도 상품 상세페이지로 이동하게된다.</li>
</ul>
<p>💡 <strong>더 자세한 코드들이 궁금하시다면?</strong></p>
<ul>
<li><a href="https://github.com/sujeong-dev/39-1st-WCF-frontend">github repository 바로가기</a></li>
</ul>
<hr>
<h1 id="🥲-부족했던-점leaked">🥲 부족했던 점(Leaked)</h1>
<p>첫 프로젝트였던만큼 부족한 점이 많을 수 밖에 없었던 것 같다.</p>
<p><strong>기획의 부실</strong></p>
<ul>
<li>프로젝트를 본격적으로 들어가기 전 기획회의를 시작할 때 이것저것 하고 싶은게 너무 많았다.
이것만큼은 꼭 하자던 동기들의 옷 데이터를 수집하여 이미지를 대체하자는 기획빼고는 다른건 실행할 수 없었다...</li>
</ul>
<p><strong>프론트엔드끼리의 부실</strong></p>
<ul>
<li>컨벤션이 잘 지켜지지 못했다. 나만 잘한다고 되는게 아니었고 다른 페이지의 부분을 commit만으로 무엇을 진행했는지 파악하면 소스를 다 뒤집어까지 않았어도 될텐데..아쉽다.</li>
<li>결제 페이지를 해내지 못했다. 장바구니 기능이 발표 당일 새벽 끝마쳐지지 못해서 결제 페이지를 만들었지만 사용할 수 없었다.</li>
</ul>
<p><strong>백엔드끼리의 부실</strong></p>
<ul>
<li>백엔드 개발자 인원이 2명이었는데 한 명이 일신상의 이유(?)로 팀원 1명이 독박이 씌워져버린 상황이 되었다...그러한 상황은 오롯이 프론트엔드의 피해로까지 이어졌고 시간과 경험이 절대적으로 부족하였다..</li>
</ul>
<p>*<em>시간의 부실 !!!! *</em>
역시 데드라인은 죽음뿐...버릴건 버리고 일정관리의 중요성을 느꼈다..</p>
<hr>
<h1 id="🙏-바라는-점longed-for">🙏 바라는 점(Longed for)</h1>
<p><strong>Trello 티켓을 세분화 하여 진행하기</strong></p>
<ul>
<li>페이지를 하나의 티켓으로 했더니 Done 카테고리에 위치해있는게 아무것도 없어서 성취감도 없고 진척도 파악에도 어려움을 겪었다. 한 페이지의 조금 더 세분화하여 진행해보자.
<strong>프론트엔드끼리 지식 및 인사이트 공유하기</strong></li>
<li>그래도 우리 같은 프론트엔드 개발자끼리 사이트도 공유하고 이럴 땐 이런 기술을 사용하면 더 좋을 것 같다라는 PR리뷰도 적극적으로 해보자.</li>
</ul>
<hr>
<h1 id="📌-느낀-점">📌 느낀 점</h1>
<p>코드를 지울 줄 아는 개발자가 되자..!
이번에 리팩토링을 하면서 다 지우고 처음부터 다시 짠 코드도 있었고 부분적으로 다시 수정한 코드도 있었다. 내가 짠 코드가 정답이 아니다. 물론 코드에 정답은 없다고는 하지만 내가 짠 코드에 고집을 부려서는 안된다.
리팩토링이던 협업 내에 컨벤션이 수정되었던 내 코드에 집착하지말고 과감히 지울 줄 아는 개발자가 되어야겠다고 생각했다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[CS] 운영서버 - 온프레미스 vs 클라우드]]></title>
            <link>https://velog.io/@sujeong_dev/CS-%EC%9A%B4%EC%98%81%EC%84%9C%EB%B2%84</link>
            <guid>https://velog.io/@sujeong_dev/CS-%EC%9A%B4%EC%98%81%EC%84%9C%EB%B2%84</guid>
            <pubDate>Thu, 24 Nov 2022 01:51:12 GMT</pubDate>
            <description><![CDATA[<h2 id="1-운영-서버란">1. 운영 서버란?</h2>
<p>개발이나 테스트 목적이 아닌 실제 사용자들을 대상으로 서비스하는 서버</p>
<h3 id="1-1-단일-서버">1-1. 단일 서버</h3>
<p>가장 단순하면서 기본적이고, 구축하기 간단한 서버구조
전체 서비스의 장애 발생 가능성 높음
서버 자원을 효율적으로 사용하기 어려움
보안성이 떨어짐
scale-out 확장 방식이 어려움</p>
<h3 id="1-2-애플리케이션과-데이터베이스-서버-분리-구조">1-2. 애플리케이션과 데이터베이스 서버 분리 구조</h3>
<p>애플리케이션과 데이터베이스를 각각의 서버로 구성
두 서버가 다른 자원을 사용
두개의 서버를 관리하므로 구성은 다소 복잡
애플리케이션과 DB서버 사이의 지연시간과 네트워크 보안고려</p>
<h3 id="1-3-서버-단위의-로드-밸런서">1-3. 서버 단위의 로드 밸런서</h3>
<p>서버의 로드밸런서를 추가하여 부하분산시켜 트래픽 과부화를 방지</p>
<h3 id="1-4-서버-내-앱-단위의-로드-밸런서">1-4. 서버 내 앱 단위의 로드 밸런서</h3>
<p>서버 내 앱 단위의 로드밸런서가 추가된 구조
기존 애플리케이션 서버 안에 똑같은 애플리케이션을 여러 프로세스로 만들어 실행
하나의 서버에서 여러 요청을 동시에 처리
서버자원을 효율적으로 사용</p>
<h2 id="2-온프레미스">2. 온프레미스</h2>
<p>IT 서비스를 기업이 자체적으로 보유한 물리적인 서버에 직접 설치 및 운영
인프라를 물리적으로 직접 구축하여 운영
서비스에 필요한 시스템을 구축하기 위해 기업이 직접 하드웨어를 구입
필요한 자원을 예측하여 물리적인 구성을 설계하는데 예측과 실제가 달라 이로 인해 불필요한 비용이 발생할 수 있음</p>
<h2 id="3-클라우드-컴퓨팅">3. 클라우드 컴퓨팅</h2>
<p>it 리소스를 인터넷을 통해 온디맨드로 제공하고 사용한 만큼만 비용을 지불하는 것</p>
<blockquote>
<p><code>## 클라우드 컴퓨팅 특징</code></p>
</blockquote>
<ul>
<li>요청하는 즉시 인터넷을 통해 온디맨드로 컴퓨팅 자원을 제공</li>
<li>원하는 시간동안 원하는 만큼 컴퓨팅 자원을 사용가능</li>
<li>scale-up, scale-out이 자유롭게 가능</li>
<li>서비스에 따라 리소스의 타입을 변경 또는 사이즈 변경을 손쉽게 컨트롤</li>
<li>다양한 리전을 통한 글로벌 확장이 용이</li>
<li>downtime이 적은 고가용성을 보장</li>
</ul>
<blockquote>
<p><code>## 클라우드 컴퓨팅 유형</code></p>
</blockquote>
<ol>
<li>IaaS(서비스형 인프라스트럭쳐)
클라우드 컴퓨팅의 가장 기본적인 계층
ex) AWS Elastic Compute Cloud(EC2)</li>
<li>PaaS(서비스형 플랫폼)
여러 개발환경을 미리 구축하고 그것을 서비스 형태로 제공
ex) AWS Elastic Beanstalk, Heroku, Redhat OpenShift</li>
<li>SaaS(서비스형 소프트웨어)
기본적인 클라우드 인프라와 소프트웨어를 사용자에게 함께 제공
ex) Google Drice, iCloud, Slack</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] 조건부 렌더링]]></title>
            <link>https://velog.io/@sujeong_dev/React-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EC%9E%AC%EC%82%AC%EC%9A%A9</link>
            <guid>https://velog.io/@sujeong_dev/React-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EC%9E%AC%EC%82%AC%EC%9A%A9</guid>
            <pubDate>Sun, 20 Nov 2022 13:38:22 GMT</pubDate>
            <description><![CDATA[<h2 id="1-컴포넌트-분리">1. 컴포넌트 분리</h2>
<p>코드의 가독성과 유지보수성을 향상시켜 코드 퀄리티를 올리기 위해 컴포넌트 분리를 적용</p>
<blockquote>
<ul>
<li>view와 로직을 분리</li>
</ul>
</blockquote>
<ul>
<li>state에 따라서 분리
가독성이 떨어지지 않는지 재사용 될 수 있는지에 초점!</li>
</ul>
<h2 id="2-조건부렌더링-구현">2. 조건부렌더링 구현</h2>
<p><img src="https://velog.velcdn.com/images/sujeong_dev/post/574d2838-22bc-4d56-8f3a-661d84bdc2e8/image.png" alt=""><img src="https://velog.velcdn.com/images/sujeong_dev/post/75f02fa2-cdc4-4581-876e-24603ab895a5/image.png" alt=""></p>
<pre><code class="language-jsx">import Brand from &#39;./Brand/Brand&#39;;
import Price from &#39;./Price/Price&#39;;

const TABS = [
    {
      id: 0,
      title: &#39;브랜드&#39;,
      content: (
        &lt;Brand
          setSelectedAllFilter={setSelectedFilter}
          selectedFilter={selectedBrand}
        /&gt;
      ),
    },
    {
      id: 1,
      title: &#39;가격&#39;,
      content: (
        &lt;Price
          setSelectedAllFilter={setSelectedFilter}
          selectedFilter={selectedPrice}
        /&gt;
      ),
    },
  ];</code></pre>
<pre><code class="language-jsx">&lt;div className=&quot;left-filter&quot;&gt;
              &lt;ul className=&quot;filter-list&quot;&gt;
                {TABS.map(filter =&gt; {
                  const isCurrent = currentTab === filter.id;

                  return (
                    &lt;li key={filter.id}&gt;
                      &lt;button
                        onClick={() =&gt;
                          setCurrentTab(isCurrent ? &#39;&#39; : filter.id)
                        }
                        className={isCurrent ? &#39;current&#39; : &#39;&#39;}
                      &gt;
                        {filter.title}
                      &lt;/button&gt;
                    &lt;/li&gt;
                  );
                })}
              &lt;/ul&gt;
            &lt;/div&gt;
{TABS.find(({ id }) =&gt; id === currentTab)?.content}</code></pre>
<blockquote>
<ol>
<li>해당 버튼을 클릭했을 때 그것에 맞는 panel을 보여준다.</li>
<li>panel에 보여줄 화면을 컴포넌트화 시킨다.</li>
<li>탭을 눌렀을 때 변경되는 currentId값에 따라 컴포넌트가 보여질 수 있도록 객체에 컴포넌트를 할당하고 객체가 조건문의 역할을 수행할 수 있도록 구현한다.</li>
</ol>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[CS] Session vs Cookie vs Token]]></title>
            <link>https://velog.io/@sujeong_dev/CS-Session-vs-Cookie-vs-Token</link>
            <guid>https://velog.io/@sujeong_dev/CS-Session-vs-Cookie-vs-Token</guid>
            <pubDate>Sun, 13 Nov 2022 14:12:37 GMT</pubDate>
            <description><![CDATA[<h2 id="1-session">1. Session</h2>
<ul>
<li>동일한 클라이언트가 브라우저를 통해 웹 서버에 접속한 시점으로부터 브라우저를 종료하여 연결을 끝내는 시점 동안에 들어오는 일련의 Request를 하나로 보고, 그 상태를 일정하게 유지하여 클라이언트와 웸 서버가 논리적으로 연결된 상태</li>
<li>서버는 session에 대한 정보를 저장하고 클라이언트에게는 session을 구분할 수 있는 id를 부여</li>
<li>request를 보낼 때 해당 session id를 부여</li>
</ul>
<blockquote>
<h4 id="session의-장점">Session의 장점</h4>
<ul>
<li>sessionID자체에는 유의미한 개인정보를 담고 있지 않다. </li>
<li>서버에서 정보를 관리하기때문에 데이터 손상우려가 적다</li>
<li>서버에서 상태를 유지하고 있어 로그인 여부확인이 쉽다.<h4 id="session의-단점">Session의 단점</h4>
</li>
<li>사용자 수가 증가할수록 서버에 부하가 증가한다.</li>
<li>서버의 양을 늘리면 관리가 어려워진다.</li>
<li>중복 로그인이 가능해진다.</li>
</ul>
</blockquote>
<hr>
<h2 id="2-cookie">2. Cookie</h2>
<ul>
<li>클라이언트의 컴퓨터에 저장되는 데이터 파일</li>
<li>이름, 값, 만료날짜, 시간, 경로정보 등으로 구성</li>
<li>도메인당 20개를 가질 수 있고 1개당 4kbyte 이하</li>
<li>클라이언트에서 서버에 http request에 저장된 cookie를 함께 전달</li>
</ul>
<blockquote>
<h4 id="cookie의-장점">Cookie의 장점</h4>
<ul>
<li>서버의 자원을 사용하지 않는다.</li>
<li>쿠키도 만료기간이 있지만 파일로 저장되기 때문에 브라우저를 종료해도 정보가 유지<h4 id="cookie의-단점">Cookie의 단점</h4>
</li>
<li>클라이언트 로컬에 저장되기 때문에 변질되거나 request에서 스니핑 당할 우려가 있어서 보안에 취약</li>
<li>웹 브라우저마다 지원 형태가 다름</li>
<li>사용자가 보안상의 문제로 거부할 경우 사용 불가능</li>
</ul>
</blockquote>
<hr>
<h2 id="3-token">3. Token</h2>
<p>제한된 리소스에 대해 일정 기간 동안 접근할 수 있는 권한을 캡슐화 및 접근할 수 있는 리소스의 범위와 접근 가능한 기간을 통제</p>
<blockquote>
<h4 id="token의-장점">Token의 장점</h4>
<ul>
<li>token을 클라이언트측에서 저장하여 서버의 메모리 부담이 적고 서버증량에 용이하다.</li>
<li>모바일과 브라우저의 멀티환경에서 사용이 용이하다.</li>
<li>만료 시간을 짧게 설정하여 안정성을 높일수 있다.<h4 id="token의-단점">Token의 단점</h4>
</li>
<li>서버에서 사용자의 상태를 저장하고 있지 않기때문에 로그인 여부확인 등의 제재를 가하기가 어렵다. </li>
<li>사용자가 임의로 토큰을 수정하거나 변경하면 서버에서 확인이 어렵다.</li>
<li>Payload 부분에 사용자 식별을 위한 여러 정보들이 포함 되어 있어 Session Id의 길이보다 길어져 HTTP request 전송 데이터의 크기가 증가</li>
</ul>
</blockquote>
<hr>
<h2 id="💡-세션-대신-쿠키를-사용하는-이유">💡 세션 대신 쿠키를 사용하는 이유</h2>
<p>세션이 보안성이 더 높고 데이터 손상우려가 더 적지만 쿠키를 사용하는 이유는 세션은 어쨌든 서버에서 관리하기 때문에 서버 자원의 한계를 느낄 수 있고 속도가 느려질 수 있다. 따라서 세션과 쿠키의 적절한 사용으로 보안성과 속도 둘 다 효율적으로 사용해야한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[회고] 일일회고 - 221110]]></title>
            <link>https://velog.io/@sujeong_dev/%ED%9A%8C%EA%B3%A0-%EC%9D%BC%EC%9D%BC%ED%9A%8C%EA%B3%A0-221110</link>
            <guid>https://velog.io/@sujeong_dev/%ED%9A%8C%EA%B3%A0-%EC%9D%BC%EC%9D%BC%ED%9A%8C%EA%B3%A0-221110</guid>
            <pubDate>Thu, 10 Nov 2022 10:42:28 GMT</pubDate>
            <description><![CDATA[<h2 id="fact">FACT</h2>
<ul>
<li>RESTful API 설계방법(uniform interface)</li>
<li>인스타그램 리액트 클론코딩<ul>
<li>fetch함수를 통한 서버와의 통신</li>
</ul>
</li>
</ul>
<hr>
<h2 id="feeling">FEELING</h2>
<ul>
<li>fetch를 통해 백엔드와의 통신을 처음 시도했다. json형태로 주고받았고 처음으로 RESTful API를 접해보니 신기했다. response로 토큰을 받았고 로컬 스토리지에 저장하려고 했는데 네트워크 패널을 보니까 {object object}형식으로 나왔다...이것도 JSON.stringify로 json화 시켜주었더니 제대로 찍혔다.</li>
</ul>
<hr>
<h2 id="finding">FINDING</h2>
<ul>
<li>RESTful API 설계 원칙<ul>
<li>URL, HTTP method, Status Code를 통해 인터페이스를 구현</li>
</ul>
</li>
<li>fetch()</li>
</ul>
<hr>
<h2 id="future-action">FUTURE ACTION</h2>
<ul>
<li>피드 좋아요, 검색기능 구현</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[회고] 일일회고 - 221109]]></title>
            <link>https://velog.io/@sujeong_dev/%ED%9A%8C%EA%B3%A0-%EC%9D%BC%EC%9D%BC%ED%9A%8C%EA%B3%A0-221109</link>
            <guid>https://velog.io/@sujeong_dev/%ED%9A%8C%EA%B3%A0-%EC%9D%BC%EC%9D%BC%ED%9A%8C%EA%B3%A0-221109</guid>
            <pubDate>Thu, 10 Nov 2022 10:42:13 GMT</pubDate>
            <description><![CDATA[<h2 id="fact">FACT</h2>
<ul>
<li>인스타그램 리액트 클론코딩<pre><code>  - 피드 컴포넌트 분리
  - 피드 별 댓글 등록/삭제 기능 구현</code></pre></li>
</ul>
<hr>
<h2 id="feeling">FEELING</h2>
<ul>
<li>피드 별로 댓글이 다르게 달려야하는데 한개의 피드에 댓글을 등록하면 해당 피드에만 등록되어야하는데 모든 피드에 등록이 되어서 당황했다. 원인은 피드를 컴포넌트로 분리시켰더니 해결되었다. 휴우</li>
<li>댓글에 달고 index를 key값으로 받아 해당 index로 댓글 삭제 기능을 구현했다. 댓글 컴포넌트에 해당 함수를 props로 넘겨줬더니 댓글을 넣자마자 바로 삭제되었다. 원인은 함수를 불러올 때 해당 함수 내용을 실행되는 것이었고 고차함수를 통해서 해결했다. 함수를 호출 시 함수내용을 실행시키는 것이 아니고 해당 내용을 return시키게 끔 해주었다.</li>
</ul>
<hr>
<h2 id="finding">FINDING</h2>
<ul>
<li>고차함수</li>
<li>구조분해할당 값의 변수생성하기</li>
</ul>
<hr>
<h2 id="future-action">FUTURE ACTION</h2>
<ul>
<li>fetch를 통한 서버와의 통신</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[회고] 일일회고 - 221108]]></title>
            <link>https://velog.io/@sujeong_dev/%ED%9A%8C%EA%B3%A0-%EC%9D%BC%EC%9D%BC%ED%9A%8C%EA%B3%A0-221108</link>
            <guid>https://velog.io/@sujeong_dev/%ED%9A%8C%EA%B3%A0-%EC%9D%BC%EC%9D%BC%ED%9A%8C%EA%B3%A0-221108</guid>
            <pubDate>Tue, 08 Nov 2022 11:09:07 GMT</pubDate>
            <description><![CDATA[<h2 id="fact">FACT</h2>
<ul>
<li>인스타그램 리액트 클론코딩<ul>
<li>상수 데이터로 footer section 수정</li>
<li>mock data로 피드 구현</li>
</ul>
</li>
</ul>
<hr>
<h2 id="feeling">FEELING</h2>
<ul>
<li>footer부분을 상수 데이터로 footer의 리스트들을 정리해서 map으로 구현하는 쪽으로 수정했다. 확실히 map으로 돌리니까 수월하긴 했는데 그럼 모든 리스트들은 다 map을 돌리는게 효율적인가에 대한 의문이 들었다.</li>
<li>백엔드와의 통신하기 전 mock data를 만들어 미리 유저정보, 이미지들을 불러와서 피드를 구현했다. 데이터객체를 만들어서 fetch를 통해 response를 받아서 map으로 뿌려줬다. 피드는 만든 mock data의 수만큼 나오지만 모든 피드에 댓글이 동일하게 올라가는 이슈가 발생한다....흐음</li>
</ul>
<hr>
<h2 id="finding">FINDING</h2>
<ul>
<li>fetch()</li>
<li>mock data</li>
</ul>
<hr>
<h2 id="future-action">FUTURE ACTION</h2>
<ul>
<li>mock data를 활용하여 각 피드별로 댓글 구현하기</li>
<li>refactoring</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] useEffect]]></title>
            <link>https://velog.io/@sujeong_dev/React-useEffect</link>
            <guid>https://velog.io/@sujeong_dev/React-useEffect</guid>
            <pubDate>Mon, 07 Nov 2022 08:21:39 GMT</pubDate>
            <description><![CDATA[<h2 id="1-useeffect란">1. useEffect란?</h2>
<p>React에서 side effect를 편리하고 안전하게 발생시킬 수 있게 도와주는 hook</p>
<blockquote>
<p><code>useEffect(콜백 함수, 의존성 배열)</code></p>
</blockquote>
<ul>
<li>콜백함수 : 특정한 side effect를 수행</li>
<li>의존성 배열 : 렌더링 시 useEffect를 실행할 조건</li>
</ul>
<hr>
<h2 id="2-의존성-배열에-따른-useeffect-발생">2. 의존성 배열에 따른 useEffect 발생</h2>
<pre><code class="language-js">import { useEffect } from &#39;react&#39;

// 사용법
useEffect(콜백 함수, 의존성 배열);

// 1번
useEffect(() =&gt; {
  // side effect
});

// 2번
useEffect(() =&gt; {
  // side effect
}, [value]);

// 3번
useEffect(() =&gt; {
  // side effect
}, [value1, value2]);</code></pre>
<blockquote>
<ol>
<li>의존성 배열이 전달되지 않았으므로 매 렌더링마다 side effect가 실행된다<ol start="2">
<li>첫 번째 렌더링 이후에 side effect를 실행하고 그 이후에는 value 값이 변했을 때만 실행한다.</li>
<li>첫 번째 렌더링 이후에 side effect를 실행하고 그 이후에는 value1, value2 중 하나라도 변하면 side effect를 실행한다.</li>
</ol>
</li>
</ol>
</blockquote>
<hr>
<h2 id="3-컴포넌트-렌더링에-따른-useeffect-렌더링-과정">3. 컴포넌트 렌더링에 따른 useEffect 렌더링 과정</h2>
<blockquote>
<ol>
<li>컴포넌트가 최초로 렌더링 -&gt;     <code>mount</code></li>
<li>useEffect의 콜백함수 실행</li>
<li>컴포넌트의 state, props가 업데이트 되었을 경우 리렌더링 발생 -&gt; <code>updated</code></li>
<li>useEffect의 의존성 배열 확인</li>
</ol>
</blockquote>
<ul>
<li>의존성배열 값이 없는 경우 : 최초 렌더링 시에만 호출하므로 콜백함수가 실행되지 않는다.</li>
<li>의존성배열 값이 변경된 경우 : 콜백함수를 호출한다.</li>
<li>의존성배열 값이 변경되지 않은 경우 : 콜백함수 호출하지 않는다.</li>
</ul>
<ol start="5">
<li>컴포넌트 간 이동하거나 필요없으면 화면에서 사라짐 -&gt; <code>unmount</code></li>
</ol>
<hr>
<h2 id="4-clean-up">4. clean up</h2>
<p>컴포넌트가 unmount 시에도 해당 콜백함수가 계속 실행되고 있다면 효율성이 떨어지기 때문에 clean up을 통해 종료시켜줘야한다.</p>
<pre><code class="language-jsx">useEffect(() =&gt; {
    // 콜백함수 실행
    // ...
    // clean up을 하는 함수를 return
    return;
})</code></pre>
<blockquote>
<h3 id="clean-up을-호출하는-경우">clean up을 호출하는 경우</h3>
</blockquote>
<ol>
<li>다음 side effect를 발생시키기 전</li>
<li>컴포넌트가 unmount 되었을 때</li>
</ol>
<hr>
<h2 id="5-clean-up-호출순서">5. clean up 호출순서</h2>
<pre><code class="language-jsx">import { useState, useEffect } from &#39;react&#39;
import { useNavigate } from &quot;react-router-dom&quot;;


const App = () =&gt; {
    const [count, setCount] = useState(0);
    const navigate = useNavigate();

    useEffect(() =&gt; {
    console.log(&quot;test1&quot;);                             // 1

    return () =&gt; {
      console.log(&quot;test2&quot;);                           // 2
    };
  }, [count]);

    console.log(&quot;test3&quot;);                               // 3

    return (
        &lt;&gt;
            &lt;button onClick={()=&gt;{setCount(count + 1)}}&gt;숫자 증가&lt;/button&gt;
            &lt;button onClick={()=&gt;{navigate(&#39;/main&#39;)}}&gt;메인 페이지 이동&lt;/button&gt;
        &lt;/&gt;
    );
};

export default App;</code></pre>
<h4 id="app-컴포넌트가-마운트-된-순간부터-숫자-증가-버튼을-한번-누르고-메인-페이지-이동-버튼을-누를때-까지-console에-출력되는-메세지의-순서는">App 컴포넌트가 마운트 된 순간부터 ‘숫자 증가’ 버튼을 한번 누르고 ‘메인 페이지 이동’ 버튼을 누를때 까지 console에 출력되는 메세지의 순서는?</h4>
<blockquote>
<ol>
<li>컴포넌트 최초 mount시 <code>&#39;test3&#39; &#39;test1&#39;</code> 가 차례로 출력</li>
<li>&#39;숫자 증가&#39;버튼 클릭 시 setCount()에 의해  의존성 배열에 값인 count state가 변화했으므로 콜백 함수가 실행, <code>&#39;test3&#39; &#39;test1&#39;</code> 가 차례로 출력</li>
<li>&#39;메인 페이지 이동&#39;버튼 클릭 시 unmount되어 컴포넌트가 사라졌으므로 clean up을 호출하여 <code>&#39;test2&#39;</code>가 출력</li>
</ol>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[회고] 일일회고 - 221104]]></title>
            <link>https://velog.io/@sujeong_dev/%ED%9A%8C%EA%B3%A0-%EC%9D%BC%EC%9D%BC%ED%9A%8C%EA%B3%A0-221104</link>
            <guid>https://velog.io/@sujeong_dev/%ED%9A%8C%EA%B3%A0-%EC%9D%BC%EC%9D%BC%ED%9A%8C%EA%B3%A0-221104</guid>
            <pubDate>Sun, 06 Nov 2022 11:10:10 GMT</pubDate>
            <description><![CDATA[<h2 id="fact">FACT</h2>
<ul>
<li>인스타그램 리액트 클론코딩<ul>
<li>댓글 컴포넌트화</li>
</ul>
</li>
<li>코드 카타<ul>
<li>strs은 단어가 담긴 배열입니다. 공통된 시작 단어(prefix)를 반환해주세요. 예를 들어
<code>strs = [&#39;start&#39;, &#39;stair&#39;, &#39;step&#39;]
return은 &#39;st&#39;</code></li>
</ul>
</li>
</ul>
<hr>
<h2 id="feeling">FEELING</h2>
<ul>
<li>인스타그램 클론코딩 댓글창 부분을 컴포넌트화 시켰다. 댓글 컴포넌트를 하나 만들어서 부모가 되는 main.js에서 map으로 돌리는 <code>&lt;li&gt;</code> 들의 key값과, 실질적인 댓글 부분인 input의 값을 props로 넘겨주고 댓글 컴포넌트에서 한개의 <code>&lt;li&gt;</code>에 key값과 댓글 input값을 받아서 뿌려주는 형식이다. 이렇게 하니 자바스크립트로 <code>createElement()</code>를 활용해 하나하나 태그를 만들어서 했을 때보다 훨씬 간편하고 편리했고 리액트내에서도 관리하기가 수월했다.</li>
<li>코드카타 부분에서 공통으로 시작되는 단어를 for문을 활용해 각 index마다 확인해보고 같으면 반환할 단어변수에 넣어주면 될 것이라고 단순하게 생각했었는데 이게 단어마다 길이도 다르고 prefix가 없는 경우도 있어서 알고리즘 짜기가 생각보다 복잡해서 힘들었다.</li>
</ul>
<hr>
<h2 id="finding">FINDING</h2>
<ul>
<li>map함수 적용 시 key값을 부여해야하는 이유</li>
</ul>
<hr>
<h2 id="future-action">FUTURE ACTION</h2>
<ul>
<li>스토리 부분 및 메인 페이지의 컴포넌트화</li>
<li>실질적인 백엔드와의 통신</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] state]]></title>
            <link>https://velog.io/@sujeong_dev/React-state</link>
            <guid>https://velog.io/@sujeong_dev/React-state</guid>
            <pubDate>Sun, 06 Nov 2022 06:54:09 GMT</pubDate>
            <description><![CDATA[<h2 id="1-state란">1. state란?</h2>
<p>컴포넌트 내부에서 컴포넌트의 상태값 -&gt; 해당 컴포넌트가 UI에 보여줄 정보를 결정할 때 사용할 수 있는 상태값</p>
<hr>
<h2 id="2-state-선언-및-적용">2. state 선언 및 적용</h2>
<pre><code class="language-jsx">import React, { useState } from &#39;react&#39;;

  const [value, setValue] = useState({
    id: &#39;&#39;,
    pw: &#39;&#39;,
  });

  const saveUserValue = e =&gt; {
    const name = e.target.name;
    const targetValues = { ...value, [name]: e.target.value };
    setValue(targetValues);
  };</code></pre>
<blockquote>
<ul>
<li><code>useState()</code>를 사용해서 state를 관리한다.<ul>
<li>첫번째 요소 : 동적으로 관리하고자 하는 상태값</li>
<li>두번째 요소 : 상태값을 업데이트하는 함수</li>
<li>useState()에 인자는 state의 초기값</li>
</ul>
</li>
</ul>
</blockquote>
<ul>
<li>결국 <code>value = { id: &#39;&#39;, pw:&#39;&#39; }</code> 이고 해당 객체에 변화를 주고 싶으면 setValue()로 관리하게 되는 것이다.</li>
<li><code>saveUserValue()</code> 함수 호출 시 spread문법으로 기존 value의 값들을 가져온 후 해당 value객체에 해당하는 키의 값을 setValue()를 통해서 e.target.value로 업데이트 시킨다.</li>
<li>setState function으로 해당 state를 업데이트하지 않고 단순 할당으로는 바뀐 값을 기준으로 화면이 다시 그려지지 않는다. setState function을 통해 state의 값을 변경해야만 값의 업데이트와 리렌더링의 효과를 볼 수 있다.</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>