<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>momoco-git.log</title>
        <link>https://velog.io/</link>
        <description>2022.08 개발자 시작</description>
        <lastBuildDate>Tue, 15 Aug 2023 09:57:53 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>momoco-git.log</title>
            <url>https://velog.velcdn.com/images/momoco-git/profile/511316bb-8bfd-49db-996e-b029d4fbd79a/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. momoco-git.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/momoco-git" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[ohs]]></title>
            <link>https://velog.io/@momoco-git/ohs</link>
            <guid>https://velog.io/@momoco-git/ohs</guid>
            <pubDate>Tue, 15 Aug 2023 09:57:53 GMT</pubDate>
            <description><![CDATA[<p>sudo apt-get install -y binutils compat-libcap1 compat-libstdc++ gcc gcc-c++ glibc glibc-devel ksh libaio libaio-devel libgcc libstdc++ libstdc++-devel libXi libXtst make sysstat unzip xorg-x11-utils</p>
<p> ./fmw_12.2.1.4.0_ohs_linux64.bin -silent -ignoreSysPrereqs  -invPtrLoc /u01/oracle/oraInventory/oraInst.loc ORACLE_HOME=/u01/oracle/ohs DECLINE_SECURITY_UPDATES=true INSTALL_TYPE=&quot;Standalone HTTP Server (Managed independently of WebLogic server)&quot;</p>
<p> inventory_loc=/path/to/oraInventory
inst_group=oinstall</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[알고리즘 초반]]></title>
            <link>https://velog.io/@momoco-git/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%B4%88%EB%B0%98</link>
            <guid>https://velog.io/@momoco-git/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%B4%88%EB%B0%98</guid>
            <pubDate>Fri, 11 Aug 2023 16:32:44 GMT</pubDate>
            <description><![CDATA[<p>분할과 정복 패턴
<img src="https://velog.velcdn.com/images/momoco-git/post/00280b8e-0326-4050-bd83-2dce31444580/image.png" alt=""></p>
<p>빈도수 세기 패턴
<img src="https://velog.velcdn.com/images/momoco-git/post/d614be73-8543-4caa-871e-3495f4186ea1/image.png" alt=""></p>
<p>다중 포인터 패턴
<img src="https://velog.velcdn.com/images/momoco-git/post/0080bb7b-40b5-408c-b3fa-e3cc6cad0d8d/image.png" alt=""></p>
<p>고유값 세기
<img src="https://velog.velcdn.com/images/momoco-git/post/3095260d-7f84-48fc-9f42-3e640038d2a2/image.png" alt=""></p>
<p>기준점 간 이동 배열 패턴
<img src="https://velog.velcdn.com/images/momoco-git/post/0f3acf86-d18b-4e04-8e42-1dca6dd01c04/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[softeer 성적평가]]></title>
            <link>https://velog.io/@momoco-git/softeer-%EC%84%B1%EC%A0%81%ED%8F%89%EA%B0%80-8jssnr42</link>
            <guid>https://velog.io/@momoco-git/softeer-%EC%84%B1%EC%A0%81%ED%8F%89%EA%B0%80-8jssnr42</guid>
            <pubDate>Fri, 04 Aug 2023 03:36:11 GMT</pubDate>
            <description><![CDATA[<p>문제
현주는 N명의 인원이 참여하는 프로그래밍 스터디 그룹을 이끌고 있다.</p>
<p>현주는 스터디를 위해 대회를 세 개 개최하였고, 모든 구성원이 각 대회에 참여하였다. 참가자는 각 대회에서 0 이상 1,000 이하의 정수인 점수를 얻는다. 한 대회에서 둘 이상의 참가자가 동점이 나오는 경우도 있을 수 있다.</p>
<p>현주는 각 대회별 등수 및 최종 등수를 매기고 싶다. 등수는 가장 점수가 높은 사람부터 1등, 2등, ···, N등의 순서대로 붙는다. 만일 동점이 있을 경우 가능한 높은 (등수의 수가 작은) 등수를 부여한다. 즉, 점수가 내림차순으로 10,7,6,6,4의 순서일 경우, 6점을 받은 두 사람은 공동 3등이 되고, 그 다음 순서인 4점을 받은 사람은 5등이 된다. 이 규칙을 다르게 표현하면 다음과 같다: 각 사람마다 “나보다 점수가 큰 사람”의 수를 세어 1을 더한 것이 자신의 등수가 된다. 대회별 등수는 각 대회에서 얻은 점수를 기준으로 하며 최종 등수는 세 대회의 점수의 합을 기준으로 한다.</p>
<p>각 참가자의 대회별 등수 및 최종 등수를 출력하는 프로그램을 작성하시오.</p>
<p>제약조건
3 ≤ N ≤ 100,000</p>
<p>입력형식
첫째 줄에 참가자의 수를 나타내는 정수 N이 주어진다.
이어 세 개의 줄에 각 대회의 결과를 나타내는 N개의 정수가 주어진다. 이중 i번째 정수는 그 대회에서 i번째 사람이 얻은 점수를 의미한다.</p>
<p>출력형식
첫 세 개의 줄에는 각 참가자의 대회별 등수를 출력한다. 즉 이중 c번째 줄의 i번째 정수는 c번째 대회에서의 i번째 사람의 등수를 의미한다.
이어 새로운 줄에 같은 형식으로 각 참가자의 최종 등수를 출력한다.</p>
<p>입력예제1
3 40 80 70 50 10 20 100 70 30
출력예제1
3 1 2 1 3 2 1 2 3 1 2 3</p>
<pre><code>const readline = require(&quot;readline&quot;);

// 입출력을 위한 인터페이스 객체 생성
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});
// * 배열의 합 구하기
let input = [];

rl.on(&quot;line&quot;, (line) =&gt; {
  input.push(line.split(&quot; &quot;).map((el) =&gt; parseInt(el)));

  if (input.length &gt;= 4) {
    rl.close();
  }
});
rl.on(&quot;close&quot;, () =&gt; {
  const N = input[0][0]; // 참가자 수
  const competitions = input.slice(1); // 각 대회별 점수

  const final_scores = new Array(N).fill(0); // 최종 점수 계산을 위한 배열

  // 각 대회별 등수 계산
  for (const competition of competitions) {
    const rankMap = new Map();
    const sortedScores = [...competition].sort((a, b) =&gt; b - a);
    let rank = 1;
    let sameRankCount = 0; // 동일한 점수의 참가자 수를 추적하는 변수

    for (let i = 0; i &lt; N; i++) {
      if (i &gt; 0 &amp;&amp; sortedScores[i] === sortedScores[i - 1]) {
        sameRankCount += 1;
      } else {
        rank += sameRankCount; // 동일한 점수의 참가자 수만큼 등수 증가
        sameRankCount = 0; // 동일한 점수의 참가자 수 초기화
        rank = i + 1;
      }
      rankMap.set(sortedScores[i], rank);
    }

    const ranks = competition.map((score) =&gt; rankMap.get(score));
    console.log(ranks.join(&quot; &quot;));
    for (let i = 0; i &lt; N; i++) {
      final_scores[i] += competition[i];
    }
  }

  const final_ranks = final_scores
    .map((score, idx) =&gt; [score, idx])
    .sort((a, b) =&gt; b[0] - a[0]);
  const final_order = Array(N).fill(0);
  let rank = 1;
  let sameRankCount = 0;

  for (let i = 0; i &lt; N; i++) {
    if (i &gt; 0 &amp;&amp; final_ranks[i][0] === final_ranks[i - 1][0]) {
      sameRankCount += 1;
    } else {
      rank += sameRankCount;
      sameRankCount = 0;
      rank = i + 1;
    }
    final_order[final_ranks[i][1]] = rank;
  }

  console.log(final_order.join(&quot; &quot;));
  process.exit();
});</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[softeer 바이러스 문제]]></title>
            <link>https://velog.io/@momoco-git/softeer-%EB%B0%94%EC%9D%B4%EB%9F%AC%EC%8A%A4-%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@momoco-git/softeer-%EB%B0%94%EC%9D%B4%EB%9F%AC%EC%8A%A4-%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Fri, 04 Aug 2023 03:33:29 GMT</pubDate>
            <description><![CDATA[<p>문제
바이러스가 숙주의 몸속에서 1초당 P배씩 증가한다.</p>
<p>처음에 바이러스 K마리가 있었다면 N초 후에는 총 몇 마리의 바이러스로 불어날까? N초 동안 죽는 바이러스는 없다고 가정한다.</p>
<p>제약조건
1 ≤ K ≤ 108인 정수</p>
<p>1 ≤ P ≤ 108인 정수</p>
<p>1 ≤ N ≤ 106인 정수</p>
<p>입력형식
첫 번째 줄에 처음 바이러스의 수 K, 증가율 P, 총 시간 N(초)이 주어진다.</p>
<p>출력형식
최종 바이러스 개수를 1000000007로 나눈 나머지를 출력하라.</p>
<p>입력예제1
2 3 2
출력예제1
18</p>
<p>처음 풀이</p>
<pre><code>
const readline = require(&quot;readline&quot;);

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});
// 1초당 P배 씩  K마리가 N초후 몇마리일까?
let K; // k마리
let P; // 배속
let N; // 초후

rl.on(&quot;line&quot;, (line) =&gt; {
  [K, P, N] = line.split(&quot; &quot;).map(Number);
  rl.close();
});

rl.on(&quot;close&quot;, () =&gt; {
  let anser = P ** N * K;
  console.log(anser % 1000000007);
});</code></pre><p>여기서 readline 자바스크립트 입출력표준방식인데 프로그래머스에서는 리턴값이 정답이지만 백준이나 softeer에서는 입출력방식으로 정답을 올려야한다.</p>
<p>처음엔 위의 코드와 같이 답을 올렸는데
간단한 문제라고 생각을 했고 쉽게 풀렸다 하지만
정답이 아니였고 왜 틑렸나 생각해보니 </p>
<pre><code>let [K, P, N] = line.split(&quot; &quot;).map(BigInt); // BigInt로 변환

  let answer = K;
  for (let i = 0; i &lt; Number(N); i++) { // N은 BigInt이므로 순회를 위해 Number로 변환
    answer = (answer * P) % 1000000007n; // 모듈로 연산에 BigInt 리터럴 사용
  }
  console.log(answer.toString()); // BigInt를 문자열로 변환하여 출력</code></pre><p>오버플로우를 막기위해 BigInt를 사용해주고 
직접적으로 제곱을 하기보다는 반복문을 통해 계산을 해주는것이 더 안정적이였다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[추억 점수 level01]]></title>
            <link>https://velog.io/@momoco-git/%EC%B6%94%EC%96%B5-%EC%A0%90%EC%88%98-level01</link>
            <guid>https://velog.io/@momoco-git/%EC%B6%94%EC%96%B5-%EC%A0%90%EC%88%98-level01</guid>
            <pubDate>Mon, 03 Apr 2023 06:11:08 GMT</pubDate>
            <description><![CDATA[<p>오래만의 코딩테스트</p>
<blockquote>
<p>그리워하는 사람의 이름을 담은 문자열 배열 name, 각 사람별 그리움 점수를 담은 정수 배열 yearning, 각 사진에 찍힌 인물의 이름을 담은 이차원 문자열 배열 photo가 매개변수로 주어질 때, 사진들의 추억 점수를 photo에 주어진 순서대로 배열에 담아 return하는 solution 함수를 완성해주세요.
예시</p>
</blockquote>
<pre><code>const name = [&#39;may&#39;, &#39;kein&#39;, &#39;kain&#39;, &#39;radi&#39;];
const yearning = [5, 10, 1, 3];
const photo = [
  [&#39;may&#39;, &#39;kein&#39;, &#39;kain&#39;, &#39;radi&#39;],
  [&#39;may&#39;, &#39;kein&#39;, &#39;brin&#39;, &#39;deny&#39;],
  [&#39;kon&#39;, &#39;kain&#39;, &#39;may&#39;, &#39;coni&#39;],
];
solution(name, yearning, photo) == [19,15,6];</code></pre><blockquote>
<h2 id="문제풀이-흐름">문제풀이 흐름</h2>
</blockquote>
<ol>
<li>photo에 담긴 이차원 배열을 이중 for문 또는 이중 map을 통해 세부 값에 접근하다.</li>
<li>세부값이 name에 들어있는 값과 같을때 그 값의 index값을 통해 점수를 구한다.</li>
<li>각 이차원배열을 한번씩 돌때마다 총합을 구해 정답 배열에 push 한다.</li>
</ol>
<blockquote>
<h2 id="풀이코드">풀이코드</h2>
</blockquote>
<pre><code>function solution(name, yearning, photo) {
  let answer = [];
  photo.map((detail, idx) =&gt; {
    let sum = 0;
    detail.forEach((content) =&gt; {
      if (name.includes(content)) {
        sum += yearning[name.indexOf(content)];
      }
    });
    answer.push(sum);
  });
  return answer;
}</code></pre><blockquote>
<h2 id="다른-사람풀이">다른 사람풀이</h2>
</blockquote>
<ul>
<li>풀이 과정에서 2번의 풀이과정을 나는 name 배열 안에 content를 가질때 그 인덱스의 점수를 더하는 방식으로 해결 했다</li>
<li>다른 사람의 풀이 과정은<pre><code>Map 자료구조를 통해 해결을 했다
const score = new Map()
name.forEach((n,idx)=&gt;{score.set(n,yearning[idx])})
photo.map(detail =&gt; detail.reduce((a,name)=&gt;a + (score.get(name)||0),0))</code></pre>맵에 이름당 점수를 가지는 객체를 만들고 photo에서 배열마다 그이름이 들어가있는 점수를 꺼내어 점수를 reduce를 통해 계산을 하였다.
오랜만에 코딩테스트를 다시 푸니 재미가 있었고, 잘 쓰지않고 있던 함수를 다시금 생각해보는 시간이 되었다.
이제 시간이 된다면 매일 한문제씩 풀어가도록 해보겠다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[3월 31 코드 수정]]></title>
            <link>https://velog.io/@momoco-git/3%EC%9B%94-31-%EC%BD%94%EB%93%9C-%EC%88%98%EC%A0%95</link>
            <guid>https://velog.io/@momoco-git/3%EC%9B%94-31-%EC%BD%94%EB%93%9C-%EC%88%98%EC%A0%95</guid>
            <pubDate>Fri, 31 Mar 2023 04:00:04 GMT</pubDate>
            <description><![CDATA[<ol>
<li>방문자는 로그인 필요가 없으니 관리자 페이지 따로 나눠서 로그인 자체를 없이 사용하도록 하기</li>
<li>전체적인 css 손보기</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Map 이용하여 tab 기능 만들기]]></title>
            <link>https://velog.io/@momoco-git/Map-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-tab-%EA%B8%B0%EB%8A%A5-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@momoco-git/Map-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-tab-%EA%B8%B0%EB%8A%A5-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Mon, 27 Mar 2023 14:46:04 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/momoco-git/post/f9cb0ebd-30d6-4f81-b221-1b65eff6d456/image.gif" alt=""></p>
<blockquote>
</blockquote>
<pre><code>const tabArray = [
    {
      key: &quot;house&quot;,
      content: &lt;PostItemList register={register} errors={errors} /&gt;,
    },
    {
      key: &quot;factory&quot;,
      content: &lt;div /&gt;,
    },
  ];</code></pre><pre><code>&lt;form onSubmit={handleSubmit(onSubmit)}&gt;
        &lt;&gt;
          &lt;span&gt;매물 종류 : &lt;/span&gt;
          &lt;select
            {...register(&quot;itemType&quot;)}
            onChange={e =&gt; {
              setTabIndex(parseInt(e.currentTarget.value));
            }}
          &gt;
            &lt;option value={0}&gt;주택&lt;/option&gt;
            &lt;option value={1}&gt;공장&lt;/option&gt;
          &lt;/select&gt;
        &lt;/&gt;
        &lt;section&gt;{newTabArr.get(tabIndex)}&lt;/section&gt;
        &lt;input type=&quot;submit&quot;&gt;&lt;/input&gt;
      &lt;/form&gt;</code></pre><p>value를 0과 1로 설정해서 tabArray[tabIndex]를 사용하여 해당 index에 해당하는 content를 불러온다.
위 방법으로 tab기능을 작성하게 되면 input의 register에 key값이 0과 1로 들어가게되고 이를 케이스를분류해서 데이터를 입력해도 tab의 종류가 많이 질수록 코드가 길어질것이다</p>
<blockquote>
<p>다음 방법은 예전에 코딩테스트하면서 풀었던 일정 신고횟수 이상 밴하기때 풀었던 방법중에 new Map 이라는 객체 구조를 사용하는것이다
Map의 경우 객체와 다르게 key 값으로 객체가 들어가도 되며 1과 &quot;1&quot;처럼 타입도 지정할 수 있다.
이를 사용하여 select의 value값을 tab의 key 값으로 사용하면서  itemTypeinput의 value 값으로 사용할 수 있게된다.</p>
</blockquote>
<pre><code> const newTabArr = new Map([
    [&quot;house&quot;, &lt;PostItemList register={register} errors={errors} /&gt;],
    [&quot;factory&quot;, &lt;div &gt;빈칸&lt;/div&gt;],
  ]);</code></pre><p>newTabArr에서 값을 꺼내올때 newTabArr[key]를 사용해도 되지만 공식문서에서는 Map.get을 권장한다.</p>
<pre><code>&lt;form onSubmit={handleSubmit(onSubmit)}&gt;
        &lt;select
          {...register(&quot;itemType&quot;)}
          onChange={e =&gt; {
            setTabIndex(parseInt(e.currentTarget.value));
          }}
        &gt;
          &lt;option value={0}&gt;주택&lt;/option&gt;
          &lt;option value={1}&gt;공장&lt;/option&gt;
        &lt;/select&gt;
        &lt;section&gt;{newTabArr.get(tabIndex)}&lt;/section&gt;
        &lt;input type=&quot;submit&quot;&gt;&lt;/input&gt;
      &lt;/form&gt;</code></pre><blockquote>
<p>위와같이 작성을 하고보니 탭기능을 만들었을때 인풋컴포넌트를 여러개 만들어야하게 된다.
그래서 최종적으로는 select를</p>
</blockquote>
<pre><code>            &lt;option value=&quot;monthly/House&quot;&gt;집/월세 &lt;/option&gt;
            &lt;option value=&quot;jense/House&quot;&gt;집/전세&lt;/option&gt;
            &lt;option value=&quot;sale/House&quot;&gt;집/매매&lt;/option&gt;
            &lt;option value=&quot;monthly/Mart&quot;&gt;상가/기타&lt;/option&gt;
            &lt;option value=&quot;sale/Factory&quot;&gt;공장-창고/매매&lt;/option&gt;
            &lt;option value=&quot;monthly/Factory&quot;&gt;공장-창고/임대&lt;/option&gt;
            &lt;option value=&quot;sale/Land&quot;&gt;토지/매대&lt;/option&gt;
            &lt;option value=&quot;monthly/Land&quot;&gt;토지/임대&lt;/option&gt;</code></pre><pre><code>const sellType = tabIndex.split(&#39;/&#39;)[0];
  const postType1 = tabIndex.split(&#39;/&#39;)[1];</code></pre><p>value값을 나누어 앞부분은 판매방식 뒷부분은 종류로 정해서 </p>
<pre><code>{sellType === &#39;monthly&#39; &amp;&amp; (
        &lt;&gt;
         ...인풋
        &lt;/&gt;
      )}</code></pre><p>해당값에 해당하는 인풋태크만 보여지게 만들었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ApolloClient SSR]]></title>
            <link>https://velog.io/@momoco-git/ApolloClient-SSR</link>
            <guid>https://velog.io/@momoco-git/ApolloClient-SSR</guid>
            <pubDate>Thu, 23 Mar 2023 14:54:06 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>rest api 같은경우 데이터를 getStaticprops 나 use()를 사용한다면
apollo를 사용하는경우는 어떻게 하는가
처음에는 </p>
</blockquote>
<pre><code>export const client = new ApolloClient({
  link: authLink.concat(httpLink),
  cache: new InMemoryCache(),
  ssrMode: true,
});
useQuery(GET_CLUSTER_DATA,{ssr=true})</code></pre><p>이런식으로 클라이언트에 ssr을 설정값을 주면 끝나는줄 알았다.
<img src="https://velog.velcdn.com/images/momoco-git/post/25b9af62-424c-4fd0-be19-59b4a73ef621/image.png" alt="">
메인페이지에서 데이터를 가져와보니 값이 들어오지 않았다
이후 검색을 통해 getDataFromTree를 사용하는 경우에 ssrmode를 사용하는것으로 생각되고 이방법은 추후에 더 자료를 찾아보고 정리하겠다</p>
<blockquote>
<p>다른 방법으로 nextjs에서 알려주는 방법이 있다 
<img src="https://velog.velcdn.com/images/2ast/post/0d325371-148d-448d-9150-42bc76ffc481/image.png" alt="">
위와같이 처음에 클라이언트에서 요청을 보내는것이 아니라 처음에 서버에서 캐시로 데이터를 캐싱해서 사용하는 방법이다
<img src="https://velog.velcdn.com/images/momoco-git/post/8e0f7e40-fba1-4a43-b52e-da3f905822d6/image.png" alt="">
<img src="https://velog.velcdn.com/images/momoco-git/post/aa2942ba-e522-46a7-bc93-ac99c312a5c4/image.png" alt=""></p>
</blockquote>
<blockquote>
<p>위의 방법은 다른 분의 블로그에서 참고하여 작성한 코드인데 
다음과 같은경우에는 deepmerge와 lodash를 사용하여 여러개의 캐시값을 초기에 저장하는 방식이다</p>
<p>하지만 현재 내가 사용하는 방법에서는 여러개의 캐시값을 가져올필요가 없고 클러스터에 쓰일 데이터만 가져오는 캐싱만 하면 되었다</p>
<blockquote>
<pre><code>export async function getStaticProps() {
  const apolloClient = initializeApollo();
  await apolloClient.query({
    query: GET_CLUSTER_DATA,
  });
  return {
    props: {
      initialApolloState: apolloClient.cache.extract(),
    },
  };
}</code></pre></blockquote>
</blockquote>
<pre><code></code></pre><p>export function initializeApollo(
  initialState: NormalizedCacheObject | null = null
) {
  const _apolloClient = apolloClient ?? createApolloClient();
  if (initialState) {
    _apolloClient.cache.restore(initialState);
  }
  if (typeof window === &quot;undefined&quot;) return _apolloClient;
  if (!apolloClient) apolloClient = _apolloClient;
  return _apolloClient;
}
export function useApollo(pageProps: any) {
  const store = useMemo(() =&gt; initializeApollo(pageProps), [pageProps]);
  return store;
}</p>
<pre><code></code></pre><p>export default function App({ Component, pageProps }: AppProps) {
  const client = useApollo(pageProps.initialApolloState);
  return (
    <ApolloProvider client={client}></p>
<pre><code>위와 같은 방식으로 구현을 할수 가 있다.
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Debounce 화면안에 보여지는 매물만 리스트목록에 추가하기]]></title>
            <link>https://velog.io/@momoco-git/Debounce-%ED%99%94%EB%A9%B4%EC%95%88%EC%97%90-%EB%B3%B4%EC%97%AC%EC%A7%80%EB%8A%94-%EB%A7%A4%EB%AC%BC%EB%A7%8C-%EB%A6%AC%EC%8A%A4%ED%8A%B8%EB%AA%A9%EB%A1%9D%EC%97%90-%EC%B6%94%EA%B0%80%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@momoco-git/Debounce-%ED%99%94%EB%A9%B4%EC%95%88%EC%97%90-%EB%B3%B4%EC%97%AC%EC%A7%80%EB%8A%94-%EB%A7%A4%EB%AC%BC%EB%A7%8C-%EB%A6%AC%EC%8A%A4%ED%8A%B8%EB%AA%A9%EB%A1%9D%EC%97%90-%EC%B6%94%EA%B0%80%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 23 Mar 2023 01:00:02 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p> 맵의 경계값에 따라 해당안에 있는 좌표만 반환하여 보여주는 함수</p>
</blockquote>
<pre><code>const bounds = () =&gt; {
    if (!map &amp;&amp; !mapState) return;
    const bounds = new kakao.maps.LatLngBounds(mapState?.sw, mapState?.ne!);
    const filterdata = clusterData?.allpost?.posts.filter((p: any) =&gt; {
      const contain = bounds.contain(
        new kakao.maps.LatLng(p.itemGeoLocation.lat, p.itemGeoLocation.lng),
      );
      return contain;
    });
    setSelectedData(filterdata);
  };</code></pre><blockquote>
<p>맵 화면 확대/축소/이동 수많은 이벤트 발생
이를 위해  debounce나 throttle을 사용하여 조절</p>
</blockquote>
<ul>
<li>debounce는 특정 이벤트가 끝난 후 설정한 값을 실행 시킴</li>
<li>throttle은 설정한 시간 값마다만 이벤트를 실행을 시킨다
화면이동 하면서 계속 매물 리스트가 변화하는것 보다
화면이동이 끝났을때 매물만 보여지는것이 더 깔끔해보여 
Debounce를 사용하여 구현하였다.<pre><code>useEffect(() =&gt; {
  const debounce = setTimeout(() =&gt; {
    return bounds();
  }, 400);
  return () =&gt; clearTimeout(debounce);
}, [mapState]);</code></pre></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[s3 image upload ]]></title>
            <link>https://velog.io/@momoco-git/s3-image-upload</link>
            <guid>https://velog.io/@momoco-git/s3-image-upload</guid>
            <pubDate>Sun, 12 Mar 2023 08:05:20 GMT</pubDate>
            <description><![CDATA[<h2 id="s3에-사진-업로드하기">s3에 사진 업로드하기</h2>
<blockquote>
<p>서버와 데이터 통신을 Apollo와 Graphql을 사용하게 되었다.
파일을 서버에 보내고 nodejs에서 multer를 사용해 파일을 받고 이후 S3에 파일을 올려도 되지만 혼자 코드를 작성하다보니 rest api보다는 graphql을 사용하여 서버에서 코드 작성보다 클라이언트에서 코드 작성을 더 늘리기로 결정 했다.</p>
</blockquote>
<blockquote>
<p>사용한 라이브러리는 @aws-sdk/client-s3, @aws-sdk/s3-request-presigner
두개 라이브러리를 사용하였다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/momoco-git/post/b0b31cb6-25e5-4fbe-bdb1-cbe64aad5af9/image.png" alt="">
<img src="https://velog.velcdn.com/images/momoco-git/post/3a91ef1a-1cc2-4a8a-b1aa-7786e07f3b54/image.png" alt="">
다음과 같이 처음에 바로 S3에 데이터를 보내는 방식을 사용했는데 데이터는 들어가지만 파일이 깨지는듯 하였다.
<img src="https://velog.velcdn.com/images/momoco-git/post/ca87e202-a40b-4571-8688-ff28d8c2ef70/image.png" alt=""></p>
<blockquote>
<p>구글링해본결과 
@aws-sdk/s3-request-presigner를 사용하여 먼저 S3에 데이터가 들어갈 자리를 먼저 만들고
후에 url을 받아서 해당 주소로 파일을 보내면 데이터가 입력되는 방식이었다.
<img src="https://velog.velcdn.com/images/momoco-git/post/cdf394cd-9d5c-4370-9051-2311746effaa/image.png" alt=""></p>
</blockquote>
<blockquote>
<h3 id="파일-name-한글일-경우">파일 name 한글일 경우</h3>
<p>다른 문제로 파일이름을 한글로 했을때 발생하였다.
<img src="https://velog.velcdn.com/images/momoco-git/post/1ed35640-1d96-493e-ad11-1b91d8ee500c/image.png" alt="">
<img src="https://velog.velcdn.com/images/momoco-git/post/a0d12cdb-2746-489d-811d-d7e30cd3064b/image.png" alt="">
<img src="https://velog.velcdn.com/images/momoco-git/post/ccca57ba-a444-42df-8505-383216095a09/image.png" alt="">
위와같이 url에 파일의 이름이 들어가야하지만 해당 주소로 들어가도 이미지가 나오지 않았다.
그래서 파일이름을 영어로 사용하도록 정했다.</p>
</blockquote>
<blockquote>
<h3 id="detailimg-빈--입력-오류">detailImg 빈 [] 입력 오류</h3>
</blockquote>
<pre><code>export const S3UpLoadFiles = async (detailFile?: File[]) =&gt; {
  let S3FileURLs: string[] = [];
   try {
    detailFile?.forEach(async file =&gt; {
       const url = await S3UpLoadFile(file).then(res =&gt; {
         S3FileURLs.push(res!);
      });
    });    
     return S3FileURLs;
   } catch (err) {
    console.log(&quot;Error&quot;, err);
   }
 };</code></pre><p>처음에 위와 같이 S3UpLoadFile, S3UpLoadFiles 두 함수를 사용해서 파일을 S3에 저장하고 url를 가져오는 방식이었는데
S3UpLoadFiles을 사용할때 
<img src="https://velog.velcdn.com/images/momoco-git/post/079b2e41-9645-47b7-93db-6de04c675825/image.png" alt="">
PostInputData를 console찍어볼때는 분명 [사진url]이 들어가 있는데
서버에 데이터 들어가는것을 확인해 보면 [] 값이 들어가있지 않았다
그래서 조금 변형해서 S3UpLoadFiles을 사용하지 않고 for을 돌려서 코드를 작성해보니 문제없이 잘 데이터가 들어가졌다.
둘 방식에 어떤 차이점이 있었는지 비교분석을 다음기회로 </p>
<pre><code>const titleS3URL = titleFile &amp;&amp; (await S3UpLoadFile(titleFile));
    let detailS3URL: string[] = [];
    if (detailFile) {
      for (let i = 0; i &lt; detailFile.length; i++) {
        const url = await S3UpLoadFile(detailFile[i]);
        detailS3URL.push(url!);
      }
    }
    const PostInputData = {
      ...data,
      itemAddress: kakaoAddress,
      itemTitleimg: titleS3URL,
      itemDetailimg: detailS3URL,
    };
    const Geo = position;
    CreatPost({ variables: { postInput: PostInputData, geo: Geo } });</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[fileReader 여러개 파일 올리기]]></title>
            <link>https://velog.io/@momoco-git/fileReader-%EC%97%AC%EB%9F%AC%EA%B0%9C-%ED%8C%8C%EC%9D%BC-%EC%98%AC%EB%A6%AC%EA%B8%B0</link>
            <guid>https://velog.io/@momoco-git/fileReader-%EC%97%AC%EB%9F%AC%EA%B0%9C-%ED%8C%8C%EC%9D%BC-%EC%98%AC%EB%A6%AC%EA%B8%B0</guid>
            <pubDate>Mon, 06 Mar 2023 12:34:36 GMT</pubDate>
            <description><![CDATA[<p>사진을 올렸을때 미리보기 이미지를 만들기위해 FileReader를 사용하게 되었다.
하지만 multiple input을 사용했을때 다음과 오류가 발생했다.</p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/momoco-git/post/3e5c6a3d-50ff-4e84-952b-41ae52befe8a/image.png" alt="">
오류를 확인해보니 for문으로 돌면서 동일한 FileReader를 사용하다보니 오류가 발생하게 되었다.</p>
</blockquote>
<blockquote>
</blockquote>
<pre><code> for (let i = 0; i &lt; theFile.length; i++) {
        const compressedFile = await imageCompression(theFile[i], option);
        fileArr.push(compressedFile);
        const reader = new FileReader();
        reader.onload = () =&gt; {
          fileUrl.push(reader.result as string);
          setDetailImg([...fileUrl]);
        };
        reader.readAsDataURL(theFile[i]);
      }</code></pre><p>이와 같이 for문안에 fileReader를 넣어주었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[react-hook-form 사용하기]]></title>
            <link>https://velog.io/@momoco-git/react-hook-form-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@momoco-git/react-hook-form-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 05 Mar 2023 12:53:06 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><img src="https://velog.velcdn.com/images/momoco-git/post/19142a79-2c3b-4861-aa17-5e30b20fa139/image.png" alt=""></p>
</blockquote>
<p>위와 같이 많은 input을 사용해서 데이터값을 처리 해야할때 
하나씩 state를 만들고 onchage함수를 이용해서 값을 가져와서 쓰면 코드가 얼마나 길어질까</p>
<p>코드량을 줄이고 여러 편의 기능을 쓸 수 있도록 해주는 라이브러리가
react-hook-form이다.</p>
<blockquote>
<pre><code>const {
    register,setError,handleSubmit,formState: { errors },
  } = useForm&lt;postType&gt;();</code></pre></blockquote>
<pre><code>- register는 아래 사진과 같이 input태크에 속성으로 입력해주면 첫번째인수를 key값으로 가지는 데이터를 얻을 수 있다.
추가적으로 required등으로 validator기능도 활용할 수 있다
- useForm에는 데이터타입을 넣어주면 register나 setError, setValue같은 함수에서 쉽게 데이터 타입을 사용할 수 있다
![](https://velog.velcdn.com/images/momoco-git/post/d14b890e-e7c4-478f-ac81-58356585021a/image.png)

  &gt;![](https://velog.velcdn.com/images/momoco-git/post/be4c9384-83d0-4477-ba8a-6c9b32aaa1e4/image.png)

  위와같이 커스텀한 input을 사용할려고 했을때 아래와같은 오류가 나타났다.
&gt;![](https://velog.velcdn.com/images/momoco-git/post/01f6d950-fa63-4052-a12b-ae6de4de0cee/image.png)
오류를 읽어 보니 useForm의 register는 ref를 통해 값을 읽어오는것 같다 그래서 하위 컴포넌트에 forwardRef를 사용하지 않으면 오류가 발생했다.</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[KakaoMapAPI 에러모음]]></title>
            <link>https://velog.io/@momoco-git/NextJs-KakaoMapAPI-%EC%97%90%EB%9F%AC%EB%AA%A8%EC%9D%8C</link>
            <guid>https://velog.io/@momoco-git/NextJs-KakaoMapAPI-%EC%97%90%EB%9F%AC%EB%AA%A8%EC%9D%8C</guid>
            <pubDate>Fri, 24 Feb 2023 08:04:54 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>kakaoMAP API를 사용해서 웹을 만드는 중에 발생한 오류를 모아 보았다.</p>
</blockquote>
<blockquote>
<p><img src="https://velog.velcdn.com/images/momoco-git/post/436b212f-aed0-4675-8e30-4b68d3f3a526/image.png" alt="">
kakaomap을 가져오는 일은 공식문서에도 있고 react-kakao-maps-sdk의 문서에도 확인 할 수 있어 편하게 가져왔다.</p>
</blockquote>
<hr>
<p>javascript키를 가져와서 env에 넣고 맵을 가져오니 </p>
<p><img src="https://velog.velcdn.com/images/momoco-git/post/951aaa1b-f9d8-4848-a392-835a6975af3d/image.png" alt="">
appkey가 잘보였다. 하지만 카카오개발자 사이트에서 내가 지정한 도메인만 접속할 수 있으니 appKey가 노출되도 문제가 없을것 같다.</p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/momoco-git/post/569e5f1f-e2cd-4941-aab5-6553909986b0/image.png" alt="">
여기서 추가로 화면사이즈 조절바와 스카이뷰변경을 추가로 넣었는데</p>
</blockquote>
<pre><code> &lt;ZoomControl position={kakao?.maps?.ControlPosition?.BOTTOMLEFT} /&gt;
 &lt;MapTypeControl position={kakao?.maps?.ControlPosition?.TOPRIGHT} /&gt;</code></pre><p>넣고나서 저장하니 잘 나왔다.
하지만 새로고침을 하니 새로운 문제가 발생 했는데
<img src="https://velog.velcdn.com/images/momoco-git/post/2ac32672-2661-410b-96a8-9af7b272bfba/image.png" alt="">
kakao를 읽을 수 가 없다는것 
곰곰히 생각해보니 문제는 _document에서 Script에서 데이터를 가져오는 것보다 앞서서 문제가 발생한다 생각을 했다.
맵을 가져오는것은 SSR을 따른다고 해도 추가적으로 가져오는 유틸버튼은 굳이 SSR로 가져오지않고 CSR로 해도 괜찮을 것이라 생각이들어 유틸버튼 전부를 KakaoMapUtil 컴포넌트에 넣어주고</p>
<pre><code> const KakaomapUtil = dynamic(() =&gt; import(&#39;@components/KakaomapUtil&#39;), {
    ssr: false,
  });</code></pre><p>이렇게 동적으로 넣어주니 새로고침해도 kakao를 찾지 못하는 문제가 해결이 되었다</p>
<p><img src="https://velog.velcdn.com/images/momoco-git/post/d2446e4f-5bbb-474c-aa4e-08d871702cbb/image.png" alt=""></p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/momoco-git/post/658d0094-3637-41d3-ae2e-94e9e9f35550/image.gif" alt="">
화면이 움직일 때마다 util이 깜빡이는 현상이 생겨버렸다.</p>
</blockquote>
<blockquote>
<pre><code>const KakaoMapUtil = dynamic(() =&gt; import(&#39;@components/KakaomapUtil&#39;), {
    ssr: false,
  });</code></pre></blockquote>
<pre><code>해결방법으로 상태에따라 다시 그려지고 있는것 같아 dynamic을 통해서 그려지는게 아니라
Kakaomap이 불러와지고 나서 onCreate로 map이 불러와질때 map이 있는 겨우에만 렌더링 되게만들었다.
깜빡임 해결</code></pre><p> &lt;Kakomap
              center={}
              level={9}
              isPanto={true}
              onCreate={setMap}</p>
<blockquote>
</blockquote>
<p>const KakaoMapUtils = () =&gt; {
    if (!map) return null;
    return <KakaoMapUtil></KakaoMapUtil>;
  };</p>
<pre><code></code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[real-estate-Han 프로젝트]]></title>
            <link>https://velog.io/@momoco-git/apollo</link>
            <guid>https://velog.io/@momoco-git/apollo</guid>
            <pubDate>Tue, 10 Jan 2023 13:12:37 GMT</pubDate>
            <description><![CDATA[<p>이번에 nextjs랑 nodejs express 등 이것저것 공부하고 있는 와중에 공인중개사하고 있는 지인에게서 사이트하나 만들어 달라고 2월 초중순에 요청이 들어왔다.
<img src="https://velog.velcdn.com/images/momoco-git/post/a5019fe0-9ec8-4acb-abad-6c7f8de9dc7f/image.png" alt=""></p>
<h2 id="nextjs">NextJS</h2>
<ul>
<li>부동산 프로젝트를 하기위해 react를 사용할 수 있었는데 SEO에서 좀 더 좋게 만들어서 다른 사람들이 사이트를 더 찾아 올 수 있도록 만들기 위해 선택했다</li>
</ul>
<h2 id="storybook">storybook</h2>
<ul>
<li>원래는 다음 프로젝트에 storybook을 사용해서 컴포넌트들을 미리 그려보고 나중에 다른 사람들과 협업을 위해 배웠지만 이번 프로젝트에서는 혼자 프론트 백 다 하기 때문에 크케 필요하지 않다고 생각이 들어 제외 했다.</li>
</ul>
<h2 id="tdd-feat-jest">TDD (feat. Jest)</h2>
<blockquote>
<p>TDD의 장점 </p>
</blockquote>
<ul>
<li>객체 지향적인 코드 개발 
TDD는 코드의 재사용 보장을 명시하므로 TDD를 통한 소프트웨어 개발 시 기능별로 모듈화가 이루어진다. 이는 의존성과 종속성이 낮은 모듈로 조합된 소프트웨어 개발을 가능하게 하며, 필요에 따라 모듈을 추가하거나 제거해도 소프트웨어 전체 구조에 영향을 미치지 않게 된다.</li>
<li>설계 수정시간의 단축 
테스트코드를 먼저 작성하기 때문에 최초 설계안을 만족하게 하며 입출력 구조와 기능의 정의를 명확하게 하게 되므로 설계의 구조적 문제를 바로 찾아낼 수 있다.</li>
<li>유지보수(리팩토링)의 용이성 
기본적으로 단위 테스트 기반의 테스트 코드를 작성하기 때문에 추후 문제가 발생하였을 때 각각의 모듈별로 테스트를 진행해보면 문제의 지점을 쉽게 찾을 수 있다.</li>
<li>테스트 문서의 대체 가능 
대부분의 개발 프로젝트에서 테스트를 진행하는 경우 단순 통합 테스트에 지나지 않는다. TDD를 하게 될 때 테스팅을 자동화시킴과 동시에 더욱 정확한 테스트 근거를 산출해 정의서를 작성할 수 있다.</li>
</ul>
<p>TDD에 이러한 장점들이 있다고 하고 test를 해보고 싶기도 해서 이번 프로젝트는 TDD를 통해 먼저 테스트코드 작성 후 그거에 맞는 코드를 작성하기로 했다.</p>
<h2 id="css-with-emotion">CSS with emotion</h2>
<p>CSS는 styled-components 보다 emotion이 그나마 용량이 조금 더 작고 둘다 사용법에 있어서도 큰차이가 없어 SSR에서 별도 설정이 필요가 없는 emotiond을 선택하였다.</p>
<h2 id="eslint와-prettier">eslint와 prettier</h2>
<ul>
<li>간단하게 air-bnb eslint를 설정해서 작성을 해보았다.</li>
</ul>
<h2 id="state관리">State관리</h2>
<ul>
<li>지금까지 state 관리를 위해 
apollo와 graphql 조합으로 만들어 보려했지만 
부동산 등록할때 사진 파일이 많이 들어가 react-query RESTapi 조합으로 만들기로 했고
이렇게 서버상태관리는 정해졌고  클라이언트 상태관리는 
recoil은 Nextjs랑 쓰기에는 오류가 자주나서 건너뛰고 
redux도 너무 다른것에 비해 무거워서 
간단하게 하기위해 Zustand를 사용하기로 정했다.</li>
</ul>
<h2 id="mongodb-mongoose">MongoDB mongoose</h2>
<p>데이터 참조해서 이것 저것 관계를 지어서 만들려면 보통 mySQL을 사용해서 하겠지만 
아직 부동산 매물 정보에 크게 어떤정보가 들어가는지 잘 모르고 중간에 추가적으로 데이터 붙일 수 도 있고 MongoDB도 관계형을 만들 수 있어 사용하기로 정했다.</p>
<h2 id="nodejs-nextjs-api">nodejs nextjs-api</h2>
<p>nextjs에 있는 api 기능을 쓸 수 있지만 개발을 좀더 빨리 하기 위해 기존에 알고 있던 nodejs express를 사용해서 서버를 만들기로 결정 했다.</p>
<h2 id="s3-버킷">S3 버킷</h2>
<p>추후에 배포를 vercel로 할 예정인데 기본적인 공간만으로 부동산 사진을 다 담으면 요금이 좀 나올것 같고해서 
저장공간에 요금을 줄이기 위해 S3버킷을 사용해서 사진을 저장하기로 정했다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.Js 12]]></title>
            <link>https://velog.io/@momoco-git/123</link>
            <guid>https://velog.io/@momoco-git/123</guid>
            <pubDate>Mon, 07 Nov 2022 05:58:05 GMT</pubDate>
            <description><![CDATA[<h1 id="nextjs-12">Next.JS 12</h1>
<p>최근에 nextJs 버젼이 13으로 업그레이드 되었는데
일단 지금은 12버젼을 쓰고 있고 13에서도 12버젼을 사용할 수 있기때문에 12버젼을 먼저 공부하고 
다음에 13버젼이 12랑 어떤 점에서 다른지 적어보겠다.</p>
<h2 id="nextjs-css-적용">NextJS CSS 적용</h2>
<blockquote>
<pre><code>function Home() {
  const variable = &quot;red&quot;;
  return (
    &lt;div className=&quot;title&quot;&gt;
      &lt;h1&gt;Home&lt;/h1&gt;
      &lt;style jsx&gt;
        {`
          h1 {
            color: ${variable};
          }
        `}
      &lt;/style&gt;
    &lt;/div&gt;
  );
}</code></pre></blockquote>
<pre><code>style 태그를 사용하용해서 jsx문법을 쓰고 global을 쓴다면 css를 글로벌하게 적용할 수 있다 하지만 다른 페이지 로 간다면 css가 없기 때문에 _app.tsx안에 적용해야한다

## _app.tsx
&gt;렌더링 하는 값은 모든 페이지에 영향을 주기 때문에 전체적인 레이아웃을 잡거나 헤더나 풋터를 적용할때 사용한다.</code></pre><p>import &#39;../styles/globals.css&#39;
import type { AppProps } from &#39;next/app&#39;
function MyApp({ Component, pageProps }: AppProps) {
  return &lt;Component {...pageProps} /&gt;
}
export default MyApp</p>
<pre><code>
## HEAD
&gt;```
&lt;Head&gt;&lt;/Head&gt;</code></pre><p>일반적인 html 파일의 head와 같이 메타 태그를 넣을 수 있고 Head부분을 바꿔 줄 수있는 태그이다.</p>
<h2 id="페이지-이동">페이지 이동</h2>
<blockquote>
<pre><code>import Link from &quot;next/link&quot;;
const Index = () =&gt; (
  &lt;div&gt;
    &lt;Link href=&quot;/blog&quot;&gt;
      &lt;a&gt;Blog&lt;/a&gt;
    &lt;/Link&gt;
    // 동적 link시 [] 사용
    &lt;Link href=&quot;/blog/[address]&quot;&gt;
      &lt;a&gt;Blog&lt;/a&gt;
    &lt;/Link&gt;
  &lt;/div&gt;
);</code></pre></blockquote>
<p>```
a태그를 사용하지 않고 Link태그를 사용하여 이동 한다.</p>
<h2 id="동적라우팅">동적라우팅</h2>
<blockquote>
<p><img src="https://velog.velcdn.com/images/momoco-git/post/d43eff8c-4426-4f38-ba90-a7a6f1b1c715/image.png" alt="">
page 폴더안에 라우팅할 이름을 만들어주면 된다. 폴더만들고 그 안에 index.txs를 만들면 기본 파일이 된다.
동적으로 라우팅이 하고 싶다면 [쓰고 싶은이름].tsx로 작성하면 된다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[forb js=> ts로 변경해보기]]></title>
            <link>https://velog.io/@momoco-git/forb-js-ts%EB%A1%9C-%EB%B3%80%EA%B2%BD%ED%95%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@momoco-git/forb-js-ts%EB%A1%9C-%EB%B3%80%EA%B2%BD%ED%95%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Sat, 29 Oct 2022 11:01:14 GMT</pubDate>
            <description><![CDATA[<p>기존에 있던 ForB 프로젝트를 Js에서 Ts로 migration 도전을 해본다</p>
<h2 id="tsconfigjson">tsconfig.json</h2>
<blockquote>
<pre><code>{
  &quot;compilerOptions&quot;: {
    &quot;target&quot;: &quot;es6&quot;,
    &quot;lib&quot;: [&quot;dom&quot;, &quot;dom.iterable&quot;, &quot;esnext&quot;],
    &quot;allowJs&quot;: true,
    &quot;checkJs&quot;: true,
    &quot;skipLibCheck&quot;: true,
    &quot;esModuleInterop&quot;: true,
    &quot;allowSyntheticDefaultImports&quot;: true,
    &quot;strict&quot;: true,
    // &quot;noImplicitAny&quot;: false,
    &quot;forceConsistentCasingInFileNames&quot;: true,
    &quot;noFallthroughCasesInSwitch&quot;: true,
    &quot;module&quot;: &quot;esnext&quot;,
    &quot;moduleResolution&quot;: &quot;node&quot;,
    &quot;resolveJsonModule&quot;: true,
    &quot;isolatedModules&quot;: true,
    &quot;noEmit&quot;: true,
    &quot;jsx&quot;: &quot;react-jsx&quot;
  },
  &quot;include&quot;: [&quot;src&quot;],
  &quot;exclude&quot;: [&quot;node_modules&quot;, &quot;**/*.(spec|test).ts&quot;]
}</code></pre></blockquote>
<p>```
CRA ts버젼의 기본 tsconfig에서 추가적으로 js에서 migration 해온것이라
일단 JS가 허용되게 allowJs와 checkJs를 추가하였다.
처음에 수정할때는 strict 모드를 끄고 시작을 할려했지만 어느 부분을 수정해야할지 표시가 안나와서 다시 키고 오류나는 부분을 전부 고치기로 했다.</p>
<h3 id="jsx--tsx로-변경">jsx-&gt; tsx로 변경</h3>
<blockquote>
<p><img src="https://velog.velcdn.com/images/momoco-git/post/fa58e94c-4acc-43f7-afdb-bdcc0d97414c/image.png" alt="">
기존에 jsx로 있던 파일을 tsx로 변경해주었다.</p>
</blockquote>
<h2 id="react-responsive">react-responsive</h2>
<blockquote>
<p><img src="https://velog.velcdn.com/images/momoco-git/post/4b333650-f927-401b-a302-23651f06026a/image.png" alt="">
기존의 모바일 뷰와 pc 뷰를 컴포넌트 형태로 나누어 구분을 해주었다면
ts버젼으로 바꾸고 나서는
<img src="https://velog.velcdn.com/images/momoco-git/post/ecc69bcc-674a-4e69-b8a3-44bdc2a29f12/image.png" alt=""></p>
</blockquote>
<p>값을 boolean 값으로 변경해서 삼항연산자를 이용해 구분해 주었다.</p>
<h2 id="redux-store">redux store</h2>
<blockquote>
<p><img src="https://velog.velcdn.com/images/momoco-git/post/0e5d2c6d-ce7d-4f11-8cd1-37726729798f/image.png" alt="">
state와 dispatch에 type을 정해주어서
<img src="https://velog.velcdn.com/images/momoco-git/post/75f13801-c068-4ce6-b554-ab4fccc526d4/image.png" alt="">
useSelector 부분에 타입으로 지정해주었고 나머지 redux toolkit
<a href="https://redux-toolkit.js.org/usage/usage-with-typescript">https://redux-toolkit.js.org/usage/usage-with-typescript</a> 을 참고해서 수정하였다.</p>
</blockquote>
<h2 id="forwardref">ForwardRef</h2>
<blockquote>
<p><img src="https://velog.velcdn.com/images/momoco-git/post/0ebbcbf3-9af8-47e1-8d63-f3bb63460a62/image.png" alt="">
무한스크롤 구현을 위해 ref를 props로 넘길때 쓴 forwardRef를 변경할때 Ref는 어떤 태크를 줬는지에 따라 HTML태크Element로 수정하면 되고 
props는 interface로 타입을 지정해주었다.</p>
</blockquote>
<h2 id="커스텀-훅">커스텀 훅</h2>
<blockquote>
<p><img src="https://velog.velcdn.com/images/momoco-git/post/05b56f9e-a81f-480d-9134-237962e6f04a/image.png" alt="">
initalForm은 특정 name에 값을 담는 배열이기 때문에 
[key:string]: string| number로 타입을 주었고
event는 onChange를 정해서 change이벤트에 HTML input과 textarea에 사용할것이기에 줄다 타입을 주었다.</p>
</blockquote>
<h2 id="styled-components">styled-components</h2>
<blockquote>
<p><img src="https://velog.velcdn.com/images/momoco-git/post/7ffda4e2-b1bc-4a3f-afef-888691480dfa/image.png" alt="">
styled-component에 props를 주려면 똑같이 타입을 지정해줘야한다. 
지금 thumbnail처럼 하나라면 직접적어도 되지만 많아지면 interface로 만들어 지정해주면 되겠다.</p>
</blockquote>
<h2 id="image-import">image import</h2>
<blockquote>
<p><img src="https://velog.velcdn.com/images/momoco-git/post/038e9360-c8ac-439f-bca3-dceba9878bbc/image.png" alt="">
png랑 jpg 이미지 import 해올때 .png&#39; 모듈 또는 해당 형식 선언을 찾을 수 없습니다. 이런 에러가 나타나서 확장자명을 지정해주니 에러가 사라졌다.</p>
</blockquote>
<p>이외에도 코드들을 수정해 나가는 중이다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[타입스크립트를 배워보자]]></title>
            <link>https://velog.io/@momoco-git/%EC%A0%9C%EB%AA%A9</link>
            <guid>https://velog.io/@momoco-git/%EC%A0%9C%EB%AA%A9</guid>
            <pubDate>Fri, 21 Oct 2022 12:44:14 GMT</pubDate>
            <description><![CDATA[<p>강의는 코딩애플의 타입스크립트를 수강하였다.</p>
<p>간단한 변수 옆에 타입을 적는건 아주 간단하다 </p>
<p>그 이후꺼를 배워보자</p>
<h3 id="지켜야할-narrowing-문법">지켜야할 Narrowing 문법</h3>
<pre><code>function coding(x :number | string){
  if (typeof x === &#39;number&#39;) {
    return x + 1
  } 
  else if (typeof x === &#39;string&#39;) {
    return x + 1
  }
  else {
    return 0
  }
}</code></pre><h3 id="type-assertion">Type Assertion</h3>
<pre><code>function coding(x :number | string){ 
    return (x as number) + 1 
}</code></pre><blockquote>
<p>타입 정의가 너무 길면 Type Aliases (별칭)</p>
</blockquote>
<pre><code>type Animal = string | number | undefined;
type 사람 = {
  name : string,
  age : number,
}</code></pre><blockquote>
<p>readonly 키워드는 속성 왼쪽에 붙일 수 있으며
특정 속성을 변경불가능하게 잠궈줍니다. </p>
</blockquote>
<pre><code>type Girlfriend = {
  readonly name : string,
}</code></pre><blockquote>
<p>속성 몇개가 선택사항이라면 </p>
</blockquote>
<pre><code>type Square = {
  color? : string,
  width : number,
}</code></pre><blockquote>
<p>object에 지정한 타입의 경우 합치기도 가능합니다. </p>
</blockquote>
<pre><code>type PositionX = { x: number };
type PositionY = { y: number };
type XandY = PositionX &amp; PositionY</code></pre><p>Type alias &amp; { name : string } 이런 것도 가능합니다. </p>
<h3 id="literal-types">literal types</h3>
<p>특정 글자나 숫자만 가질 수 있게 제한을 두는 타입을 literal type 이라고 부릅니다.</p>
<pre><code>function 함수(a : &#39;hello&#39;) : 1 | 0 | -1 {
  return 1 
}</code></pre><blockquote>
<pre><code>var 자료 = {
  name : &#39;kim&#39;
} as const;
function 내함수(a : &#39;kim&#39;) {
}</code></pre></blockquote>
<pre><code>as const는 효과가 2개인데
1. 타입을 object의 value로 바꿔줍니다. (타입을 &#39;kim&#39;으로 바꿔줍니다)
2. object안에 있는 모든 속성을 readonly로 바꿔줍니다 (변경하면 에러나게)

### 함수 타입 type alias</code></pre><p>type NumOut = (x : number, y : number ) =&gt; number ;</p>
<pre><code></code></pre><p>type NumOut = (x : number, y : number ) =&gt; number 
let ABC :NumOut = function(x,y){
  return x + y
}</p>
<pre><code>&gt;method에 타입지정</code></pre><p>type Member = {
  name : string,
  age : number,
  plusOne : ( x :number ) =&gt; number,
  changeName : () =&gt; void
}</p>
<pre><code>
HTML 찾고 변경해보기 </code></pre><p>{
    &quot;compilerOptions&quot;: {
        &quot;target&quot;: &quot;ES5&quot;,
        &quot;module&quot;: &quot;commonjs&quot;,
        &quot;strictNullChecks&quot;: true
    }
} </p>
<pre><code></code></pre><p>let 제목 = document.querySelector(&#39;#title&#39;);
제목.innerHTML = &#39;반갑소&#39;</p>
<p>=
let 제목 = document.querySelector(&#39;#title&#39;);
if (제목 != null) {
  제목.innerHTML = &#39;반갑소&#39;
}</p>
<pre><code>&gt;다른방법</code></pre><p>let 제목 = document.querySelector(&#39;#title&#39;);
if (제목 instanceof HTMLElement) {
  제목.innerHTML = &#39;반갑소&#39;
}</p>
<pre><code>instanceof 라는 연산자를 쓰는 것인데 우측에 HTMLElement 입력하면 그 타입인지 체크해줍니다.</code></pre><p>let 제목 = document.querySelector(&#39;#title&#39;);
if (제목?.innerHTML != undefined) {
  제목.innerHTML = &#39;반갑소&#39;
}</p>
<pre><code>
html 태그 종류별로 정확한 타입명칭이 있습니다.

a 태그는 HTMLAnchorElement

img 태그는 HTMLImageElement

h4 태그는 HTMLHeadingElement

&gt;Object에 쓸 수 있는 interface 문법
interface 문법을 쓰시면 object 자료형의 타입을 보다 편리하게 지정가능합니다.</code></pre><p>interface Square { 
  color :string, 
  width :number, 
} </p>
<pre><code>
extends 문법은 interface 여기에 복사해달라는 뜻입니다. </code></pre><p>interface Student {
  name :string,
}
interface Teacher extends Student {
  age :number
}</p>
<pre><code>
type 키워드와의 차이점
type alias와 interface는 거의 똑같은 기능을 제공합니다.
 차이점은 extends 문법이 약간 다르다 이런건데</code></pre><p>type Animal = { 
  name :string 
} 
type Cat = Animal &amp; { legs: number }</p>
<pre><code>type alias의 경우 extends는 안되고 &amp; 기호를 쓰면 object 두개를 합칠 수 있습니다.
&amp; 기호 쓰는걸 intersection이라고 부르는데 extends 와 유사하게 사용가능합니다. 

(주의) extends 쓸 때 타입끼리 중복속성이 발견될 경우 에러로 혼내주는데 &amp; 쓰면 때에 따라 아닐 수도 있습니다.
</code></pre><p>interface Animal { 
  name :string 
} 
interface Animal { 
  legs :number 
}</p>
<p>```
interface의 경우 타입이름 중복선언을 허용해주며 중복시 extends 한 것이랑 동일하게 동작합니다. </p>
<p>이러면 Animal 타입은 name, legs 속성을 가질 수 있습니다. </p>
<p>(장점) type 선언을 자주 쓰는 외부 라이브러리 이용시 type 선언을 내가 덮어쓰기, override 하기 편리합니다. type의 경우 중복선언을 허용하지 않습니다. 에러남</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ForB 프로젝트 -2]]></title>
            <link>https://velog.io/@momoco-git/ForB-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-2</link>
            <guid>https://velog.io/@momoco-git/ForB-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-2</guid>
            <pubDate>Wed, 19 Oct 2022 07:40:45 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>게시글 추가하는 모달창이다
<img src="https://velog.velcdn.com/images/momoco-git/post/04eca0af-c1c1-4318-9511-fc24865a1b48/image.png" alt="">
<img src="https://velog.velcdn.com/images/momoco-git/post/e94c3833-eb06-4be8-934c-547498926d36/image.png" alt=""></p>
</blockquote>
<p>데이터를 보낼때 사진이 없으면 서버쪽에서 null 값으로 처리해서 썸네일 데이터가 null일시 기본사진을 보여주고 있다.
하지만 이미지 파일을 첨부하지 않으면 서버에서 오류가 나서 값을 저장하지 못해서</p>
<pre><code>const formData = new FormData();
    if (!fileZero) {
      const noimg = new Blob([JSON.stringify(fileZero)], {
        type: &quot;application/json&quot;,
      });
      formData.append(&quot;file&quot;, noimg);
    } else {
      formData.append(&quot;file&quot;, fileZero);
    }</code></pre><p>formData에 file키에 noimg 라는 null 이 담긴 데이터를 보내주어야했다.
이전에 다른 백분이랑은 했을때는 데이터가 없으면 없는데로 처리 해줬지만 이렇게 사람마다 처리 방법이 다르니 그에 맞춰서 진행을 했다.</p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/momoco-git/post/0532006a-1211-41a4-bda7-d5918a7716ab/image.png" alt="">
그리고 사용자가 처음 이용할때 가이드를 해주기 위해 만들었는데
settimeout을 사용하여 만들어 보았다.</p>
</blockquote>
<pre><code> const nextTip = () =&gt; {
    let currenttip = tips.find(x =&gt; x.id === tipNum);
    setCurrentTip(currenttip);
  };
  useEffect(() =&gt; {
    let timer = setTimeout(() =&gt; {
      if (tipNum + 1 &gt;= 6) {
        setTipNum(1);
        nextTip();
      } else {
        setTipNum(tipNum + 1);
        nextTip();
      }
    }, 4000);
    return () =&gt; clearTimeout(timer);
  }, [tipNum]);
  return &lt;SpanStyled&gt;{currentTip?.tip}&lt;/SpanStyled&gt;;</code></pre><blockquote>
<p> <img src="https://velog.velcdn.com/images/momoco-git/post/b1803450-3228-47d4-9d7a-65ea776e28fc/image.png" alt="">
 게시판페이지는 페이지네이션으로 구성을 해보았다.
 아래 페이지네이션은 전체 글갯수에 맞춰 구성이 변화 한다.
 한페이지당 6개씩 나눠 페이지를 구성하고 5개가 넘어가면 &gt;&gt;눌러 다음 페이지를 불러올 수 있다.</p>
</blockquote>
<pre><code>const Pagenation = props =&gt; {
  const postCount =
    props.postCount % 6 === 0
      ? parseInt(props.postCount / 6)
      : parseInt(props.postCount / 6) + 1;
  const [numberList, setnumberList] = useState([1, 2, 3]);
  const [page, setPage] = useState(1);
  const rowsPerPage = 5;
  const startNum = (page - 1) * rowsPerPage + 1;
  const endNumber =
    startNum + rowsPerPage - 1 &gt;= postCount
      ? postCount
      : startNum + rowsPerPage - 1;
  let numList = [];
  const setlist = () =&gt; {
    for (let i = startNum; i &lt;= endNumber; i++) {
      numList.push(i);
    }
    setnumberList(numList);
  };
  useEffect(() =&gt; {
    setlist();
  }, [page, postCount]);</code></pre><p><img src="https://velog.velcdn.com/images/momoco-git/post/b80be33e-c3d5-4356-9aba-6427b950b7cb/image.png" alt="">
글 작성-수정페이지 입니다
오른쪽이 미리보기는 MarkdownPreview 라이브러리를 사용하여 어떻게 작성하고있는지 벨로그 처럼 확인할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ForB 프로젝트 -1]]></title>
            <link>https://velog.io/@momoco-git/ForB-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-1</link>
            <guid>https://velog.io/@momoco-git/ForB-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-1</guid>
            <pubDate>Tue, 18 Oct 2022 08:10:00 GMT</pubDate>
            <description><![CDATA[<h1 id="실전프로젝트">실전프로젝트</h1>
<p><a herf="https://github.com/ForB-project/ForB-FE">프론트 깃허브</a></p>
<h2 id="forb">ForB</h2>
<p>처음 개발자에 입문하는 사람이 프론트를 고를지 백엔드를 고를지 고민을 하는데 조금이나마 도움이 되고자 만들게 되었고
심리테스트만 있으면 스코프가 너무 작아 간단한 로드맵과 정보, 게시판을 추가하기로 하였다.
팀원은 4명으로 프론트 2명 백엔드 2명이다</p>
<p>프로젝트 구조이다
<img src="https://velog.velcdn.com/images/momoco-git/post/99a00f4a-33cf-4d20-b25c-6ad73461205c/image.png" alt=""></p>
<p>프론트에서 쓰인 라이브러리이다</p>
<pre><code>    &quot;axios&quot;: &quot;^0.27.2&quot;,
    &quot;cross-env&quot;: &quot;^7.0.3&quot;,
    &quot;dompurify&quot;: &quot;^2.4.0&quot;,
    &quot;react&quot;: &quot;^18.2.0&quot;,
    &quot;react-dom&quot;: &quot;^18.2.0&quot;,
    &quot;react-icons&quot;: &quot;^4.4.0&quot;,
    &quot;react-intersection-observer&quot;: &quot;^9.4.0&quot;,
    &quot;react-query&quot;: &quot;^3.39.2&quot;,
    &quot;react-redux&quot;: &quot;^8.0.2&quot;,
    &quot;react-responsive&quot;: &quot;^9.0.0&quot;,
    &quot;react-router-dom&quot;: &quot;^6.3.0&quot;,
    &quot;react-scripts&quot;: &quot;5.0.1&quot;,
    &quot;sockjs-client&quot;: &quot;^1.6.1&quot;,
    &quot;styled-components&quot;: &quot;^5.3.5&quot;,</code></pre><blockquote>
<p>css는 항상 styled-component를 사용하는데 tailwind도 사용해봤지만 가독성이 너무 떨어져 팀원과 같이 할때는 유지 보수가 너무 불편하여 항상 styledComponent를 애용하고 있다.</p>
</blockquote>
<blockquote>
<p>axios도 api를 한꺼번에 관리할수 있게 instance를 만들고 intercepter로 request에 토큰 헤더에 담아서 보낼수 있어 편리해서 항상 사용하고 있다.</p>
</blockquote>
<blockquote>
<p>리액트 쿼리의 경우 초반에는 리덕스툴킷을 사용했는데 리액트쿼리를 공부하고 사용하다 보니 너무나도 코드량이 줄어들고 재렌더링 시키기 쉽고 여러 기능이 많아 리덕스 보다는 리액트 쿼리를 사용하는 편이다.
무한스크롤구현에 있어 infiniteQuery와 intersection-observer를 사용하니 구현하기 간편하였다.</p>
</blockquote>
<blockquote>
<p>리덕스는 리액트 쿼리를 잘 사용하지못하는 팀원이 사용했지만 담당하는 파트가 코드체험과 퀴즈부분이여서 리액트 쿼리를 써서 하는것보다 리덕스를 사용것이 좋아 보았다. 따로 데이터 받아올 일이 크게 없고 데이터 받아와서 가공하는 편이 많아 보여 리액트쿼리를 썼다면 할수는 있겠지만 리덕스도 괜찮아 보였다.</p>
</blockquote>
<blockquote>
<p>react-responsive는 나중에 다른조 코드를 리뷰하다가 보게되었는데 
화면 사이즈를 정해놓고 모바일뷰일때랑 웹뷰일때를 나눠서 보여줄수 있어서 현재 웹에서만 보여주고 모바일같이 화면이 작아질때 반응형 웹이 아니다 보니 화면이 작아지면 아예 웹크기를 키워달라고 뷰를 보여주었다.
<img src="https://velog.velcdn.com/images/momoco-git/post/3c903df4-8994-4b80-9e4d-db7e5910d060/image.png" alt=""></p>
</blockquote>
<p>일단 내가 맡은 파트는 로그인, 메인페이지, 로드맵페이지, 게시판페이지,상세페이지, 마이페이지, 사이드바, 글작성-수정페이지, 댓글 기능 이다</p>
<p>로그인은 소셜로그인으로 카카오와 구글로그인을 진행을 했다.
처음 소셜로그인을 진행하다 보니 공부를 하고 시작을 했는데 서버사이드와 클라이언트 사이드가 있었는데 인가코드만 받아서 서버에 넘겨주고 대부분의 처리를 서버에서 하는 서버사이드 형식으로 진행을 하는데 백쪽에서 
Bad Response가 나와서 이틀동안 해결을 못하고 있었다.
계속 에러가 나오다보니 백에서도 자기쪽 잘못이 아닌거 같다. 라고해서 같이 수정해볼려고 코드를 비교해보니</p>
<blockquote>
<pre><code>`https://accounts.google.com/o/oauth2/v2/auth?
client_id=${GoogleClientId}&amp;redirect_uri=
${process.env.REACT_APP_REDIRECT_GOOGLE}
&amp;response_type=code&amp;scope=https://www.googleapis.com/auth/userinfo.profile 
https://www.googleapis.com/auth/userinfo.email 
openid`;</code></pre></blockquote>
<pre><code>이것이 구글로 로그인 하는 URL인데 구글콘솔에서도 요구하는 스코프가 유저 정보랑 이메일 그리고 openid인데 백에서 인가코드 받아서 토큰을 받아올려고 할때 스코프를 잘못지정하여 유저 정보만 가져와서 BadResponse가 나왔던것이였다.

로그인을 해결하고 메인페이지를 완성을 하게 되었다
![](https://velog.velcdn.com/images/momoco-git/post/da52fb82-6ea6-4ddb-8bb8-27ac2112c9d3/image.png)

그리고 사이드 바이다
&gt; 로그인전
![](https://velog.velcdn.com/images/momoco-git/post/ba3dbc57-1c51-4b96-ace1-7555f5751f67/image.png)


&gt;로그인 후
![](https://velog.velcdn.com/images/momoco-git/post/baf4ca0b-8687-4ae4-b585-ca471b7534c3/image.png)

사이드바에서는 현재 페이지를 알수있게 카테고리가 굵게 표시가 된다.</code></pre><p> <NavLink end to="/" activeclassname="active">홈</NavLink></p>
<pre><code>NavLink를 사용한것인데 /일때 원래 exact 없이 어떻게 홈으로 보내나 싶었는데 구글링해보니 end 를 사용하면 같은 기능을 수행할 수있었다.

테스트결과페이지는 특별한거 없이 테스트 결과를 받아와서 api요청 보내 response로 받아온데이터를 보여주기만 하면 되었다.

&gt; ![](https://velog.velcdn.com/images/momoco-git/post/b675c8a3-dcc8-4e5f-b596-3291c97ad3f3/image.png) 
로드맵페이지
간단한 로드맵이 제공이 되고 해당하는 카테고리를 클릭하면 해당하는 게시글을 무한스크롤로 볼수 있다.


로드맵 컨텐츠를 가져오는 api는 </code></pre><p>  getContent: (data, pageParam, getSort) =&gt;
    api.get(
      <code>/api/roadmap/${data.title}/${data.id}?page=${pageParam}&amp;size=7&amp;sortBy=${getSort}</code>
    ),</p>
<pre><code>이런형식으로 스텍과 카테고리의 아이디값 그리고 pageParam와 정렬값이 있어야한다.</code></pre><p>  const [choseCategory, setChoseCategory] = useState({
    id: 1,
    title: &quot;html&quot;,
  });</p>
<pre><code>기본값으로 html과 id 1을 가지고 시작한다. 이후 카테고리를 클릭하면 변경되는 식이다.
&gt;무한스크롤의 infiniteQuery  </code></pre><p>const getContent = async (data, pageParam, getSort) =&gt; {
    const res = await RoadmapAPI.getContent(data, pageParam, getSort);
    return {
      result: res.data.data,
      nextPage: pageParam + 1,
      isLast: res.data.data[0].contentList.length === 7 ? false : true,
    };
  };
  const infiniteQuery = useInfiniteQuery(
    [&quot;contentList&quot;, choseCategory, getSort],
    ({ pageParam = 1 }) =&gt; getContent(choseCategory, pageParam, getSort),
    {
      getNextPageParam: (lastPage, pages) =&gt; {
        //hasNextPage 대용
        if (!lastPage.isLast) {
          return lastPage.nextPage;
        } else {
          return undefined;
        }
      },
      refetchInterval: 1000,
    }
  );</p>
<pre><code>원래 정석이라고 생각하는 부분은 서버 쪽에서 데이터에 isLast같은 경우도 데이터로 담겨서 해당 page의 데이터를 가져오면 그 이후에 데이터가 없으면 true값을 넘겨줘야한다고 생각을 했다 .
하지만 백에서 그렇게 보내기 힘들다고 하니 알아서 구현을 해보았다.
limit가 7이니 마지막 페이지는 7개가 딱떨어지거나 그 이하로 가져오게 되니 그때 isLast를 true로 받았다 이때 딱 7개일경우는 어쩔 수 없이 한번 더 요청을 하게된다.
nextPage의 경우도 마찬가지로 백에서 데이터 넘길때 설정해줘야한다고 생각하는데 못해주기 때문에 +1씩해주는것이로 대체하였다.
또 hasNextPage의 경우에도 없기때문에 isLast로 대체하였다.

&gt;refetchInterval은 1초마다 데이터를 새로 받아오기는 하지만 리액트 쿼리가 데이터를 최신화 할려고 쓰는만큼 요청이 많다고 할수 있지만 그만큼 데이터가 최신화 된다는것이다.
그리고 이렇게 데이터를 최신화 할려면 리덕스를 택해서 구현할려고 하면 Websoket이나 SSE를 사용하여 실시간으로 데이터 변화를 받아 올 수 있겠지만 코드도 길어지고 보일러플레이트도 생각보다 많아져 리액트쿼리를 쓴다면 Websoket과 SSE 없이 사용하면 된다고 생각이든다.

저번에도 무한스크롤을 구현했지만 미완성이라고 생각했던부분이 데이터 삭제 수정후 재렌더링 문제때문이였다.
일반적으로 useQuery를 사용하여 무한스크롤을 구현하게 되면 page별로 데이터를 받아오기때문에 무한스크롤 보다는 페이지네이션에 가깝게 된다고 생각이 들어서 useMutation으로 바로 재렌더링 시킬 수 있게 쿼리키값도 생각보다 설정하기 쉬운 infiniteQuery를 사용하니 pageParam의 따른 데이터가 따로 분류된게 아니라 page별로 분류가 되어 한 쿼리키에 담기니 수정해여 재렌더링 시키기가 쉬웠다.

&gt;intersection-observer 부분 </code></pre><p>useEffect(() =&gt; {
    if (inView) {
      infiniteQuery.fetchNextPage();
    }
  }, [inView]);</p>
<pre><code></code></pre><p>{infiniteQuery.data?.pages.map((x, idx) =&gt; {
 return (
  &lt;React.Fragment key={idx}&gt;
    {x?.result[0]?.contentList.map((y, keys) =&gt; 
    {if (keys % 7 === 6) {return (
     <RoadmapContent ref={ref} key={y.id}
                  querykey={choseCategory} data={y} />);
     } else { return (
    <RoadmapContent
      key={y.id} querykey={choseCategory} data={y}/>);}
     })}
   &lt;/React.Fragment&gt;
     );
    })}</p>
<p>```
inView를 사용하여 7번째마다 ref를 넘겨서 7번째 게시물이 보일때 마다  다음페이지를 가져오도록 설정하였다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[트위터클론코딩3]]></title>
            <link>https://velog.io/@momoco-git/%ED%8A%B8%EC%9C%84%ED%84%B0%ED%81%B4%EB%A1%A0%EC%BD%94%EB%94%A93</link>
            <guid>https://velog.io/@momoco-git/%ED%8A%B8%EC%9C%84%ED%84%B0%ED%81%B4%EB%A1%A0%EC%BD%94%EB%94%A93</guid>
            <pubDate>Fri, 14 Oct 2022 14:35:48 GMT</pubDate>
            <description><![CDATA[<p>그외의 부분들을 리뷰해본다.
리액트 쿼리를 가져와서 그저 보여주는것은 따로 설명하지 않고
클론코딩하면서 추가적으로 새로해본것만 추가해보겠다.</p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/momoco-git/post/b5b5445d-229e-47b9-bba6-12fc10454baa/image.png" alt=""></p>
</blockquote>
<blockquote>
<p><img src="https://velog.velcdn.com/images/momoco-git/post/bdc2e506-02af-41f1-8dc1-efb0bf1ef902/image.png" alt="">
위의 첫번때 input창에서 다음 input 창의로 만들어 보았는데 다른 materialUI 같은데서 가져오면 간단하게 구현할 수 있으나 styled-component만 이용해서 구현 하기로 했다.</p>
</blockquote>
<blockquote>
<blockquote>
<p>여기서 구현하는 방법은 </p>
</blockquote>
</blockquote>
<pre><code>const IdInput = styled.input`
  margin: 20px 0 0 0;
  padding: 25px 0 5px 5px;
  border: 1px solid rgb(214, 218, 227);
  border-radius: 5px;
  background-color: transparent;
  font-size: 1rem;
  line-height: 24px;
  width: 100%;
  &amp;:disabled {
    background-color: rgb(210, 210, 210, 0.3);
  }
  &amp;::placeholder {
    color: transparent;
  }
  &amp;:not(:placeholder-shown) {
    outline: none;
    + span {
      position: absolute;
      top: 30%;
      left: 3%;
      pointer-events: none;
      font-size: 0.8rem;
      transition: all 0.2s ease;
      -webkit-transition: all 0.2s ease;
      -moz-transition: all 0.2s ease;
      -o-transition: all 0.2s ease;
    }
  }
  &amp;:focus {
    outline: none;
    border: 2px solid #1d9bf0;
    + span {
      position: absolute;
      top: 30%;
      left: 3%;
      color: #1d9bf0;
      pointer-events: none;
      font-size: 0.8rem;
      transition: all 0.2s ease;
      -webkit-transition: all 0.2s ease;
      -moz-transition: all 0.2s ease;
      -o-transition: all 0.2s ease;
    }
  }
`;</code></pre><p>생각보다 간단하였다. placeholder을 쓰지않고 span이나 label을 사용하여 input과 span을 감싸는 div를 만들어 position을 relative로 하고 span의 position을 absolute로 하여 forcus 되었을때 글자가 입력되어있을때의 위치를 조정해주면 되었다.</p>
<p><img src="https://velog.velcdn.com/images/momoco-git/post/0ee47ed1-d3e2-484e-9a29-d6ef18a50d72/image.png" alt="">
탭을 변경해주는 부분은 탭을 눌렀을때 불러오는 데이터를 바꾸는 방법인데 다른팀원분과 같이 만들어 보았다</p>
<pre><code>const [tabIndex, setTabIndex] = useState(0);
const tabArray = [
    {
      key: &quot;tweets&quot;,
      tab: (
        &lt;div
          className={tabIndex === 0 ? &quot;select&quot; : &quot;&quot;}
          onClick={() =&gt; setTabIndex(0)}
        &gt;
          Tweets
        &lt;/div&gt;
      ),
      content: &lt;Tweets userId={profile?.userId} tweets={myTweets} /&gt;,
    },
    {
      key: &quot;likes&quot;,
      tab: (
        &lt;div
          className={tabIndex === 1 ? &quot;select&quot; : &quot;&quot;}
          onClick={() =&gt; setTabIndex(1)}
        &gt;
          Likes
        &lt;/div&gt;
      ),
      content: &lt;Likes memberId={memberId} /&gt;,
    },
  ];
  // view 부분
  &lt;StyledTab&gt;
            {tabArray.map((item) =&gt; {
              return (
                &lt;div key={item.key} className=&quot;tab&quot;&gt;
                  {item.tab}
                &lt;/div&gt;
              );
            })}
          &lt;/StyledTab&gt;
          {tabArray[tabIndex].content}
        &lt;/StyledTabDiv&gt;</code></pre><p>해당 탭을 클릭시 다른걸로 갈아끼우는 방식이였다. 원래는 탭 클릭시 내용만 바뀌게 만들생각이였다.
나의 트윗이 되면 useQuery로 가져온 myTweets이 보여지고 좋아요이면 멤버아이디를 props로 내려 Likes 컴포넌트에서 해당 아이디로 다시 데이터를 가져오는 방식이였다.</p>
]]></description>
        </item>
    </channel>
</rss>