<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>random-olive.log</title>
        <link>https://velog.io/</link>
        <description>Doubts kills more dreams than failure ever will</description>
        <lastBuildDate>Thu, 30 Mar 2023 07:12:13 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>random-olive.log</title>
            <url>https://velog.velcdn.com/images/random-olive/profile/628ae33b-fa81-4aa9-8836-2f63fb548adc/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. random-olive.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/random-olive" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[34. route path 다중 적용 v6]]></title>
            <link>https://velog.io/@random-olive/34.-route-path-%EB%8B%A4%EC%A4%91-%EC%A0%81%EC%9A%A9-v6</link>
            <guid>https://velog.io/@random-olive/34.-route-path-%EB%8B%A4%EC%A4%91-%EC%A0%81%EC%9A%A9-v6</guid>
            <pubDate>Thu, 30 Mar 2023 07:12:13 GMT</pubDate>
            <description><![CDATA[<ul>
<li>다음과 같이 다중 route path에 대해 같은 element로 접근시키려고 했는데<pre><code>//App.tsx
&lt;Route
path={[&#39;/&#39;,&#39;/about&#39;]}
  element={&lt;ContentsPage ./props.. /&gt;}
/&gt;</code></pre></li>
<li>v5까지는 support 했지만 v6부터는 지원되지 않는다고 해서 <a href="https://stackoverflow.com/questions/70228810/having-multiple-paths-to-the-same-component-in-react-router-dom-v6">map으로 처리했다.</a><pre><code>{[PATH.HOUSE_WORK, PATH.TIPS].map((path, i) =&gt; {
return (
  &lt;Route path={path} key={i}
    element={&lt;ContentsPage ./props.. /&gt;}
  /&gt;
);
})}
</code></pre></li>
</ul>
<pre><code>
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[33. 다국어 지원 (i18n) + 에러 해결]]></title>
            <link>https://velog.io/@random-olive/33.-%EB%8B%A4%EA%B5%AD%EC%96%B4-%EC%A7%80%EC%9B%90-i18n</link>
            <guid>https://velog.io/@random-olive/33.-%EB%8B%A4%EA%B5%AD%EC%96%B4-%EC%A7%80%EC%9B%90-i18n</guid>
            <pubDate>Sun, 26 Mar 2023 07:21:11 GMT</pubDate>
            <description><![CDATA[<ul>
<li>다국어지원을 하기 위해 <a href="https://react.i18next.com/">i18n</a>을 적용했다.</li>
</ul>
<h3 id="1-설치">1. 설치</h3>
<pre><code>npm i i18next react-i18next @types/i18next @types/react-i18next</code></pre><h3 id="2-설정">2. 설정</h3>
<h3 id="3-menutext-내용-i18njson으로-옮기기">3. menuText 내용 i18n.json으로 옮기기</h3>
<pre><code>//locales/en/main.json
{
  &quot;test&quot;: &quot;Helo&quot;,
  &quot;mainMenu&quot;: [
    {
      &quot;title&quot;: &quot;main&quot;,
      &quot;href&quot;: &quot;/home&quot;,
      &quot;list&quot;: [
        { &quot;title&quot;: &quot;🐰&quot;, &quot;href&quot;: &quot;/housework&quot; },
        { &quot;title&quot;: &quot;♥️🐇&quot;, &quot;href&quot;: &quot;/wandb&quot; },
        { &quot;title&quot;: &quot;recipe&quot;, &quot;href&quot;: &quot;/address&quot; },
        { &quot;title&quot;: &quot;hobby&quot;, &quot;href&quot;: &quot;/address&quot; },
        { &quot;title&quot;: &quot;party&quot;, &quot;href&quot;: &quot;/address&quot; },
        { &quot;title&quot;: &quot;travel&quot;, &quot;href&quot;: &quot;/address&quot; },
        { &quot;title&quot;: &quot;community&quot;, &quot;href&quot;: &quot;/address&quot; },
        { &quot;title&quot;: &quot;store&quot;, &quot;href&quot;: &quot;/address&quot; }
      ]
    }
  ]
}
</code></pre><ul>
<li>menuText로 지정했던 메뉴 이름들을 다국어지원이 가능하게 옮기려고 한다.<pre><code>//Layouts.tsx
export const MenuBar = ({ selected, setSelected }: BarProp) =&gt; {
return (
  &lt;&gt;
    {mainMenu[0].list.map((el, idx) =&gt; (
        &lt;Main key={idx} idx={idx}&gt;
        ...
      &lt;/Main&gt;
    ))} 
  &lt;/&gt;
);
};</code></pre>를<pre><code>//Layouts.tsx
import { useTranslation } from &#39;react-i18next&#39;;
</code></pre></li>
</ul>
<p>export const MenuBar = ({ selected, setSelected }: BarProp) =&gt; {
  const {t} = useTranslation();
  return (
    &lt;&gt;
      {t(&#39;main:mainMenu&#39;)[0].list.map((el, idx) =&gt; (
          <Main key={idx} idx={idx}>
          ...
        </Main>
      ))} 
    &lt;/&gt;
  );
};</p>
<pre><code>단순히 바꿔서 써보았더니 
🟥 Property &#39;list&#39; does not exist on type &#39;string&#39;이라는 에러가 나온다: t(&#39;main:mainMenu&#39;) 부분이 string으로 나타나서 사용할 수 없다는 것 같아 해당 값을 화면에 출력해보니
![](https://velog.velcdn.com/images/random-olive/post/0a8ed8b2-60f7-4a73-a5dd-27d06a9bfded/image.png)

🟥 값은 key &#39;mainMenu (ko-KR)&#39; returned an object instead of string.이라 나오고, 
콘솔로그에는 accessing an object 라는 메세지가 나온다.
[공식문서](https://www.i18next.com/translation-function/objects-and-arrays)를 참조 후, 다음과 같이 옵션을 추가했다.</code></pre><p>t(&#39;main:mainMenu&#39;,{ returnObjects: true })</p>
<pre><code>이번에는 콘솔로그에는 정상적으로 object가 찍히는데 화면에서 출력하려고 했을 때는 다음과 같은 에러가 발생했다.
🟥 Objects are not valid as a React child ...
![](https://velog.velcdn.com/images/random-olive/post/cee4ecce-7d0e-48bb-bb8e-4df773016ec4/image.png)
</code></pre><p>{JSON.stringify(t(&#39;main:mainMenu&#39;, { returnObjects: true }))}</p>
<pre><code>[객체를 stringify해서](https://itprogramming119.tistory.com/entry/React-Objects-are-not-valid-as-a-React-child-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95) 렌더링하니까 성공적으로 출력되었다. 
하지만 string이라서 그 다음 코드(객체의 map 활용)를 제대로 활용할 수 없어서, object로 출력하는 JSON.parse()를 사용했다.</code></pre><p>{JSON.parse(JSON.stringify(t(&#39;main:mainMenu&#39;, { returnObjects: true })))}</p>
<pre><code>* 즉, JSON을 객체로 온전히 활용하려면 우선 JSON.stringify로 string화 시켜준 후, 다시 객체로 변형하는 JSON.parse 순으로 진행하면 된다. 

* stringify하지 않고 바로 JSON.parse만 한다면 
🟥 Uncaught SyntaxError: &quot;[object Object]&quot; is not valid JSON
콘솔로그 에러를 볼 수 있다.

* stringify하고 JSON.parse를 하지 않고 객체 활용을 한다면 
또다시 🟥 Property &#39;list&#39; does not exist on type &#39;string&#39;. 에러를 만난다.

* 결과적으로 다음과 같이 파싱 후, 변수로 바꿔치기해서 기존 결과를 유지하게 되었다.</code></pre><p>//MenuBar.tsx
import { useTranslation } from &#39;react-i18next&#39;;</p>
<p>export const MenuBar = ({ selected, setSelected }: BarProp) =&gt; {
  const { t } = useTranslation();
  const objectParsed = JSON.parse(
    JSON.stringify(t(&#39;main:mainMenu&#39;, { returnObjects: true }))
  );</p>
<p>  return (
    &lt;&gt;
      <Horizontal margin={DEFAULT.MENU_MARGIN}>
        {objectParsed.list.map((el: any, idx: number) =&gt; (
          <Main key={idx} idx={idx}>
            {el.title}
            ... (기존 코드)
          </Main>
        ))}
      </Horizontal>
    &lt;/&gt;
  );
};</p>
<pre><code>-----
text.ts -&gt; i18n.json 으로 변환하며 느낀 점
* 기존 문제가 모두 해결된 후에 해당 변환을 진행하는 게 편하다.
* 메뉴 타입 지정에 에러가 있었다: 필요하지 않은 메뉴(x 항목)가 메뉴에 들어가게 되었는데, 이를 삭제하면 타입 에러가 발생했다.
* json 파일로 변환 후에는 언어 개수에 따라 해당 자료를 지우거나 추가했어야 하므로, 해당 문제를 해결하기 위해 기존의 ts 파일을 남겨둔 채, 해결 후 ts 파일을 지우기로 했다.

🟥 콘솔에러 : i18next::translator: missingKey en-US basic cycle cycle
```
//발생 코드
const cycleMenu = JSON.parse(
    JSON.stringify(t(&#39;basic:cycle&#39;, { returnObjects: true }))
  );
```
* en-US 모드일 때 basic.json에서 해당 key (basic)을 찾을 수 없다고 나오는 에러
* 파일 이름을 item.json으로 변경한 것이 원인이었다: translation할 파일 이름이 없거나, 그에 맞는 key 값이 없으면 같은 에러를 발생시킨다.

* 코드를 다음과 같이 수정하니까 잘 되었다.
//발생 코드
const cycleMenu = JSON.parse(
    JSON.stringify(t(&#39;item:cycle&#39;, { returnObjects: true }))
  );
```


</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[32. 타입에러 해결하기 : does not exist on type {}...]]></title>
            <link>https://velog.io/@random-olive/32.-%ED%83%80%EC%9E%85%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0-does-not-exist-on-type-</link>
            <guid>https://velog.io/@random-olive/32.-%ED%83%80%EC%9E%85%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0-does-not-exist-on-type-</guid>
            <pubDate>Fri, 24 Mar 2023 11:08:55 GMT</pubDate>
            <description><![CDATA[<ul>
<li><pre><code>TS2339: Property &#39;list1&#39; does not exist on type &#39;{ title: string; href: string; list1: { item1: string; }[]; list2: { item2: string; }[]; } | { title: string; href: string; }&#39;.
Property &#39;list1&#39; does not exist on type &#39;{ title: string; href: string; }&#39;.
  68 |               setSelected({
  69 |                 menu: el.title,
&gt; 70 |                 list1: el.list1,
     |                           ^^^^^
  71 |                 // list2: el.list2,
  72 |                 idx1: idx,
  73 |               });</code></pre></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[31. 스크롤 이벤트 (window.scrollTo)]]></title>
            <link>https://velog.io/@random-olive/31.-%EC%8A%A4%ED%81%AC%EB%A1%A4-%EC%9D%B4%EB%B2%A4%ED%8A%B8-window.scrollTo</link>
            <guid>https://velog.io/@random-olive/31.-%EC%8A%A4%ED%81%AC%EB%A1%A4-%EC%9D%B4%EB%B2%A4%ED%8A%B8-window.scrollTo</guid>
            <pubDate>Thu, 23 Mar 2023 09:59:19 GMT</pubDate>
            <description><![CDATA[<h3 id="스크롤-이벤트--windowscroll">스크롤 이벤트 : window.scroll</h3>
<ul>
<li>sticky 버튼을 클릭하면 화면의 최상단 / 최하단으로 스크롤</li>
<li>각각의 Icon에 다음의 클릭 이벤트를 넣었다.</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/scroll">window.scroll</a>을 사용해서 구현했다.<pre><code>//Layouts.tsx
&lt;StickyIcon&gt;
      &lt;UpIcon
        onClick={() =&gt; {
          window.scrollTo({ top: 0, behavior: &#39;smooth&#39; });
        }}
      /&gt;
      &lt;DownIcon
        onClick={() =&gt; {
           window.scrollTo({ top: document.body.scrollHeight, behavior: &#39;smooth&#39; });
        }}
      /&gt;
</code></pre></li>
</ul>
</StickyIcon>
```



<h3 id="끝까지-스크롤--scrollheight">끝까지 스크롤 : scrollHeight</h3>
<ul>
<li>컨텐츠 끝까지 스크롤하기 위해서는 <a href="https://developer.mozilla.org/ko/docs/Web/API/Element/scrollHeight">scrollHeight</a> 속성을 이용했다. 해당 속성은 요소 콘텐츠의 총 높이를 나타내며, 바깥으로 넘쳐서 보이지 않는 콘텐츠도 포함한다.</li>
</ul>
<h3 id="window-객체-속성-정리">window 객체 속성 정리</h3>
<h4 id="화면-사이즈">화면 사이즈</h4>
<ul>
<li><p>window size</p>
<ul>
<li><p>screen : 모니터 사이즈</p>
<ul>
<li>window.screen.width , window.screen.height</li>
</ul>
</li>
<li><p>outer : 브라우저 사이즈 </p>
<ul>
<li>window.outerWidth, window. outerHeight</li>
</ul>
</li>
<li><p>inner : 브라우저 사이즈 (화면 부분만 + 스크롤바 포함)</p>
<ul>
<li>window.innerWidth, window. innerHeight</li>
</ul>
</li>
</ul>
</li>
<li><p>document size</p>
<ul>
<li>documentElement : 브라우저 사이즈 (화면 부분만 + 스크롤바 제외)<ul>
<li>document.documentElement.clientWidth, document.documentElement.clientHeight</li>
</ul>
</li>
</ul>
</li>
</ul>
<h4 id="좌표">좌표</h4>
<ul>
<li>clientX, clienY : (브라우저창 기준) xy 좌표</li>
<li>pageX, pageY : (웹 문서 기준) xy 좌표</li>
<li>screenX, screenY : (화면 모니터 기준) xy 좌표</li>
</ul>
<h4 id="scroll">scroll</h4>
<ul>
<li>window.scrollTo() : 해당 좌표로 이동</li>
<li>window.scrollBy() : 해당 좌표만큼 document 창을 이동</li>
<li>element.scrollIntoView() : 해당 기능을 호출한 element가 있는 좌표로 이동</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[30. 화면 고정 버튼 만들기 (position : fixed, absolute)]]></title>
            <link>https://velog.io/@random-olive/30.-%ED%99%94%EB%A9%B4-%EA%B3%A0%EC%A0%95-%EB%B2%84%ED%8A%BC-%EB%A7%8C%EB%93%A4%EA%B8%B0-position-fixed-absolute</link>
            <guid>https://velog.io/@random-olive/30.-%ED%99%94%EB%A9%B4-%EA%B3%A0%EC%A0%95-%EB%B2%84%ED%8A%BC-%EB%A7%8C%EB%93%A4%EA%B8%B0-position-fixed-absolute</guid>
            <pubDate>Sat, 18 Mar 2023 13:25:16 GMT</pubDate>
            <description><![CDATA[<ul>
<li>화면에 항상 고정해있는 버튼을 만든다.</li>
<li>메인 버튼 : 클릭시 help 기능</li>
<li>서브 버튼 : 클릭시 Home / End 스크롤</li>
</ul>
<p>-&gt; 설계 단계에서는 없었던 거라 이미지를 새로 그리기 애매해서 기존에 있던 이미지를 활용
-&gt; 메인 버튼을 기준으로 서브 버튼의 위치를 고정시킬 예정</p>
<pre><code>//Layouts.tsx
type BackType = {
  top?: string;
  left?: string;
  background?: string;
  width?: string;
  height?: string;
};


const TitleIcon = styled.div&lt;BackType&gt;`
  background: url(${iconPath});
  position: absolute;
  top: -17px;
  left: 5px;
  width: ${(props) =&gt; props.width};
  height: ${(props) =&gt; props.height};
`;

//메인 버튼
export const StickyIcon = styled(TitleIcon)`
  position: fixed;     (1)
  top: 85%;
  left: 85%;
  z-index: 1000;
  width: 58px;
  height: 59px;
  cursor: pointer;
  background-position: 195px 59px;
  margin: 0 0 70px 0;
  @media screen and (max-height: 584px) {
    top: 83%;
  }
`;

//서브 버튼 
export const UpIcon = styled(StickyIcon)`
  position: absolute;     (2)
  top: 9px;
  left: 65px;
  width: 15px;
  height: 15px;
  background-position: 80px 36px;
  transform: rotate(0.5turn);
  @media screen and (max-width: 584px) {
    top: 13px;
    left: -20px;
  }
`;

export const DownIcon = styled(UpIcon)`
  top: 35px;
  transform: rotate(0turn);
`;</code></pre><ul>
<li>(1) position :fixed로 고정시킨다.</li>
<li>(2) 부모 StickyIcon를 기준으로(position : absolute) px 단위로 고정 위치를 줬다.<pre><code>//Layouts.tsx
</code></pre></li>
</ul>
<p>//변경 전
export const BasicLayout = ({ windowSize }: any) =&gt; {
  return (
    &lt;&gt;
      ...
      <Outlet />
      <StickyIcon />
      <Footer />
    &lt;/&gt;
  );
};</p>
<p>//변경 후
export const BasicLayout = ({ windowSize }: any) =&gt; {
  return (
    &lt;&gt;
      ...
      <Outlet />
      <StickyIcon>    (3)
        <UpIcon />
        <DownIcon />
      </StickyIcon>
      <Footer />
    &lt;/&gt;
  );
};</p>
<pre><code>* (1) StickyIcon을 부모로 하는 자식 UpIcon, DownIcon 컴포넌트는 position:absolute를 이용해 부모 기준으로 위치를 고정했다.</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[29. setstate 함수 + manifest 에러]]></title>
            <link>https://velog.io/@random-olive/29.-setstate-%ED%95%A8%EC%88%98</link>
            <guid>https://velog.io/@random-olive/29.-setstate-%ED%95%A8%EC%88%98</guid>
            <pubDate>Thu, 09 Mar 2023 14:18:59 GMT</pubDate>
            <description><![CDATA[<h3 id="1-setstate-함수">1. setstate 함수</h3>
<ul>
<li>메뉴를 클릭하면 그에 맞는 카테고리 버튼이 나온 채 유지되어야 하는데,
메뉴를 클릭하면 활성화되었던 카테고리가 해제되었다.</li>
</ul>
<pre><code>//MenuBar.tsx
&lt;Sub2
  onClick={() =&gt; {
  setSelected({idx3: idx });
  }}
  key={idx}
&gt;
{el.item1}
&lt;/Sub2&gt;</code></pre><ul>
<li>따로 지정했던 setSelected에 ...selected를 지정하니까 해당 문제가 해결되었다.<pre><code>&lt;Sub2
onClick={() =&gt; {
setSelected({ ...selected, idx3: idx });
}}
key={idx}
&gt;</code></pre></li>
</ul>
<h3 id="2-manifest-line-1-column1-syntax-error">2. manifest: line: 1, column:1, syntax error</h3>
<pre><code>//index.html
&lt;link rel=&quot;manifest&quot; href=&quot;%PUBLIC_URL%/manifest.json&quot; /&gt;
를
&lt;link rel=&quot;/manifest&quot; href=&quot;%PUBLIC_URL%/manifest.json&quot; /&gt;
로 변경하니까 해당 에러가 사라졌다.

</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[28. Cannot find file: '.tsx' does not match the corresponding name on disk: '...경로']]></title>
            <link>https://velog.io/@random-olive/28.-Cannot-find-file-.tsx-does-not-match-the-corresponding-name-on-disk-...%EA%B2%BD%EB%A1%9C</link>
            <guid>https://velog.io/@random-olive/28.-Cannot-find-file-.tsx-does-not-match-the-corresponding-name-on-disk-...%EA%B2%BD%EB%A1%9C</guid>
            <pubDate>Tue, 07 Mar 2023 07:38:04 GMT</pubDate>
            <description><![CDATA[<p>🟥 Cannot find file: &#39;.tsx&#39; does not match the corresponding name on disk: &#39;...경로&#39;</p>
<p>-&gt; branch를 합치고나서 pull 을 땡겨왔는데 VSCode 자동완성 때문에 오류가 발생했다.
-&gt; 경로 철자가 틀린 곳이 없는지 확인하기
-&gt; Menubar.tsx로 작성되어있던 파일의 이름을 MenuBar.tsx로 변경했다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[27. type 입력 리팩토링]]></title>
            <link>https://velog.io/@random-olive/28.-type-%EC%9E%85%EB%A0%A5-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81</link>
            <guid>https://velog.io/@random-olive/28.-type-%EC%9E%85%EB%A0%A5-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81</guid>
            <pubDate>Tue, 28 Feb 2023 08:28:13 GMT</pubDate>
            <description><![CDATA[<ul>
<li>외부 prop을 받는 경우 그에 대한 type을 지정해야 하는데,
그동안은 아래와 같이 지정하던 것을 그 다음 방식으로 변경했다.</li>
<li>코드 길이가 줄었고, 덜 헷갈리게 되었다.</li>
</ul>
<pre><code>//Layouts.tsx
interface LayoutProps {
  children?: React.ReactNode;
  item?: any;
}

export const ContentsLayout: React.FC&lt;LayoutProps&gt; = ({ item }) =&gt; {
  return (
    &lt;&gt;
      ...
      &lt;h1&gt;{item.name}&lt;/h1&gt;
      ...
    &lt;/&gt;
  );
};</code></pre><pre><code>//Layouts.tsx
interface LayoutProps {
  children?: React.ReactNode;
  item?: any;
}

export const ContentsLayout = ({ item }: LayoutProps) =&gt; {
  return (
    &lt;&gt;
      ...
      &lt;h1&gt;{item.name}&lt;/h1&gt;
      ...
    &lt;/&gt;
  );
};</code></pre><p><a href="https://bobbyhadz.com/blog/typescript-parameter-implicitly-has-an-any-type">🟥 Parameter &#39;X&#39; implicitly has an &#39;any&#39; type in TypeScript.</a></p>
<pre><code>//MenuBar.tsx
{itemList.map((el, idx)=&gt;(
&lt;div&gt;메뉴&lt;/div&gt;))}

=&gt; 
{itemList.map((el:any, idx:any)=&gt;(
&lt;div&gt;메뉴&lt;/div&gt;))}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[26. Contents page- submenu Layout 추가]]></title>
            <link>https://velog.io/@random-olive/26.-Contents-page-submenu-Layout-%EC%B6%94%EA%B0%80</link>
            <guid>https://velog.io/@random-olive/26.-Contents-page-submenu-Layout-%EC%B6%94%EA%B0%80</guid>
            <pubDate>Wed, 22 Feb 2023 07:57:11 GMT</pubDate>
            <description><![CDATA[<ul>
<li>밖에서 컴포넌트만 다르고 전체 구조는 같은 Layout을 추가했다.</li>
<li>Outlet을 사용하지 않고 props.children을 사용했다.</li>
</ul>
<pre><code>//layouts.tsx
interface LayoutProps {
children: React.ReactNode;                  (1) 
}

export const SubMenuLayout: React.FC&lt;LayoutProps&gt; = 
(props: LayoutProps) =&gt; {                    (2)
  return (
    &lt;&gt;
      &lt;SubMenuContainer&gt;
        &lt;div className=&#39;part&#39;&gt;{props.children}&lt;/div&gt;  (3)
      &lt;/SubMenuContainer&gt;
    &lt;/&gt;
  );
};</code></pre><ul>
<li>(1),(2) : 외부에서 prop이 들어오는데, children이 들어오고, 컴포넌트가 들어오니까 React.ReactNode로 지정한다.</li>
<li>(3) : 컴포넌트가 들어오는 부분</li>
</ul>
<br/>

<ul>
<li>컴포넌트는 다음과 같이 받아준다.<pre><code>//ContentsPage.tsx
const ContentsPage = () =&gt; {
return (
  &lt;&gt;
    {subMenuList.map((el, idx) =&gt; (  (4)
      &lt;SubMenuLayout key={idx}&gt;{el}&lt;/SubMenuLayout&gt;
    ))} (5)
    &lt;AdvSection /&gt;
    &lt;ContentSection&gt;&lt;/ContentSection&gt;
  &lt;/&gt;
);
};
</code></pre></li>
</ul>
<p>export default ContentsPage;</p>
<pre><code>* (4), (5) submenu에 들어가는 메뉴항목의 컴포넌트 array를 상수로 만들어 map을 활용해 나열했다.

* 완성
![](https://velog.velcdn.com/images/random-olive/post/6e146e69-0c88-4643-bec9-fbefaeb77b42/image.png)</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[+ JS Deep Dive: 40. 이벤트 & debounce]]></title>
            <link>https://velog.io/@random-olive/JS-Deep-Dive-40.-%EC%9D%B4%EB%B2%A4%ED%8A%B8-debounce</link>
            <guid>https://velog.io/@random-olive/JS-Deep-Dive-40.-%EC%9D%B4%EB%B2%A4%ED%8A%B8-debounce</guid>
            <pubDate>Mon, 20 Feb 2023 07:20:36 GMT</pubDate>
            <description><![CDATA[<h3 id="1-이벤트-드리븐-프로그래밍">1. 이벤트 드리븐 프로그래밍</h3>
<ul>
<li><p>event-driven programming : 프로그램의 흐름을 이벤트 중심으로 제어하는 프로그래밍 방식 </p>
</li>
<li><p>사용자와의 상호작용에 따라 (버틀 클릭, 키보드 입력, 마우스 이동 등) 함수를 호출하여 어떤 처리를 하고 싶을 때, 보통 사용자가 행동을 &quot;언제&quot; 할지 몰라서 함수를 &quot;언제&quot; 호출해야되는지 알 수 없다.</p>
</li>
<li><p>하지만 다행히도 브라우저는 이런 특정 사건을 <strong>감지</strong>하여 이벤트 event를 발생시킬 수 있기에 <strong>해당하는 타입의 이벤트가 발생했을 때 호출될 함수<em>(이벤트 핸들러)</em></strong>를 <strong>브라우저에게 *<em>알려 *</em>호출을 위임<em>(이벤트 핸들러 등록)</em></strong>한다.</p>
</li>
</ul>
<br />
<br />
<br />


<h3 id="2-이벤트-핸들러-등록-방법">2. 이벤트 핸들러 등록 방법</h3>
<h4 id="1-이벤트-핸들러-attribute">1) 이벤트 핸들러 attribute</h4>
<pre><code>* on이벤트타입 = 함수 호출문 (사실 함수 몸체 자체를 의미함)

&lt;body&gt;
  &lt;button onclick=&quot;handleClick&quot;&gt;Click&lt;/button&gt;
  &lt;script&gt;
  function handleClick(){
    console.log(1)
  }
  &lt;/script&gt;
&lt;/body&gt;</code></pre><p>-&gt; 이벤트 핸들러에 인수를 전달하기 위해 함수 참조문이 아닌 함수 호출문을 전달하게 된다.</p>
<ul>
<li><p>결국 DOM 노드 객체의 이벤트 핸들러 프로퍼티로 변환되므로 결과적으로 이벤트 핸들러 프로퍼티 방식과 동일하다고 볼 수 있다.</p>
</li>
<li><p>관심사 분리 측면에서 더이상 사용하지 않는 것이 좋지만 CBD(Component Based Development) 방식의 Angular/React/Svelte/Vue.js 같은 프레임워크/라이브러리에서는 이 방식으로 처리한다. CBD에서는 HTML, CSS, JS를 관심사가 다른 개별적인 요소로 보지 않고, 뷰를 구성하기 위한 구성요소로 보기 때문에 관심사가 다르다고 생각하지 않는다.</p>
</li>
</ul>
<h4 id="2-이벤트-핸들러-property">2) 이벤트 핸들러 property</h4>
<ul>
<li>Window, Document, HTMLElement 타입의 DOM 객체는 이벤트에 대응하는 이벤트 핸들러 프로퍼티를 가지고 있다. 프로퍼티의 키는 &#39;on+이벤트 타입&#39;으로 이루어져 있으며, 프로퍼티에 함수를 바인딩하면 이벤트 핸들러가 등록된다.</li>
<li>attribute 방식과 달리 함수 참조를 할당한다.</li>
<li>관심사 분리 측면에서는 좋지만 하나의 프로퍼티에 하나의 이벤트 핸들러만 바인딩 가능하다.
```</li>
<li>이벤트타깃. on이벤트타입 = 이벤트 핸들러 (함수)</li>
</ul>
<body>
  <button>click</button>
  <script>
    const $button = document.querySelector('button');
    $button.onclick = function (){console.log(1)}; <- 다음 핸들러에 의해 재할당되었기 때문에 이 부분은 실행되지 않는다.
    $button.onclick = function (){console.log(2)};
    $button.onclick = null; <- 이벤트 핸들러 제거
  </script>
</body>
```

<h4 id="3-이벤트-핸들러-addeventlistener">3) 이벤트 핸들러 addEventListener</h4>
<ul>
<li>앞의 두 방식(DOM level 0)과 달리 DOM level 2에서 도입된 이 방식은 마지막 매개변수에는 이벤트를 캐치할 이벤트 전파 단계(capturing/bubbling)를 지정한다.</li>
<li>마지막 매개변수가 true일 경우 캡쳐링 단계에서 이벤트를 캡쳐하고, 그 외에는 버블링 단계에서 이벤트를 캐치한다. </li>
<li>하나 이상의 핸들러를 등록 가능하며, 호출시 등록된 순서대로 호출된다.</li>
<li>등록한 이벤트 핸들러는 removeEventListener로 삭제 가능하며, 모든 매개변수가 같아야 제대로 삭제된다.</li>
<li>무명의 이벤트 핸들러는 삭제할 수 없으니, 등록시에 기명 핸들러로 등록하도록 한다.</li>
<li>기명 함수를 이벤트 핸들러로 등록할 수 없다면, 함수 자신을 가리키는 arguments.callee를 사용할 수도 있으나, 이는 코드 최적화를 방해하므로 가급적 사용하지 말고 핸들러들의 참조를 꼭 저장해두도록 하자.</li>
</ul>
<pre><code>* 이벤트타깃.addEventListener(이벤트타입, 이벤트핸들러[, capture 사용여부])

&lt;body&gt;
  &lt;button&gt;click&lt;/button&gt;
  &lt;script&gt;
    const $button = document.querySelector(&#39;button&#39;);
    $button.addListener(&#39;click&#39;,function(){console.log(1)}, true)
    $button.addListener(&#39;click&#39;,function(){console.log(2)}, true)
    $button.removeListener(&#39;click&#39;,function(){console.log(1)},false) //마지막 매개변수 다름-&gt;실패
    $button.removeListener(&#39;click&#39;,()=&gt;console.log(1),true) //무명 핸들러-&gt;실패
    $button.removeListener(&#39;click&#39;,function(){console.log(1)}, true) //제거 성공
  &lt;/script&gt;
&lt;/body&gt;
</code></pre><br />
<br />

<h3 id="3-이벤트-타입--이벤트의-종류를-나타내는-문자열-및-예시"><a href="https://developer.mozilla.org/en-US/docs/Web/Events">3. 이벤트 타입 : 이벤트의 종류를 나타내는 문자열</a> 및 예시</h3>
<ul>
<li>마우스, 키보드, 포커스, 폼, 값 변경, DOM 뮤테이션, 뷰, 리소스 이벤트</li>
<li>예시 : click, dbclick, mousedown, mouseup, mousemove, mouseenter, mouseleave, keydown, keyup, focus, blur, submit, reset, input, change, readystatechange, DOMContentLoaded,  resize, scroll, load, unload, abort, error 외</li>
</ul>
<br />
<br />
<br />


<h3 id="4-이벤트-객체">4. 이벤트 객체</h3>
<ul>
<li>이벤트가 발생하면 이벤트에 관련한 정보를 담고 있는 이벤트 객체가 동적으로 생성되고, 핸들러의 첫밴째 인수로 전달된다. 이벤트 객체는 다음과 같은 <a href="https://velog.io/@thumb_hyeok/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%EC%9D%B4%EB%B2%A4%ED%8A%B8">상속 구조</a>를 가지며, Event를 포함한 그 이하는 모두 생성자 함수이다. 
<img src="https://velog.velcdn.com/images/random-olive/post/dd783ad2-eced-4d9f-a193-6c89119e3d87/image.png" alt=""></li>
</ul>
<ul>
<li>공통 프로퍼티 : type, target, currentTarget, eventPhase, bubbles, cancelable, defaultPrevented, isTrusted, timeStamp</li>
<li>다양한 타입의 객체 프로퍼티 : screenX, clientX, pageX, offesetX, altKey, metakey, <a href="https://www.toptal.com/developers/keycode">key</a> ...</li>
</ul>
<br />
<br />
<br />


<h3 id="5-이벤트-전파-propagation">5. 이벤트 전파 propagation</h3>
<ul>
<li>DOM 트리 상에 존재하는 DOM 요소 노드에서 발생한 이벤트는 DOM 트리를 통해 전파되며, 다음과 같이 3 단계로 전파된다.</li>
</ul>
<ol>
<li>캡쳐링 단계 capturing phase : 이벤트가 상위 → 하위 요소 방향으로 전파</li>
<li>타겟 단계 : 이벤트가 이벤트 타깃에 도달</li>
<li>버블링 단계 : 이벤트가 하위 → 상위 요소 방향으로 전파</li>
</ol>
<pre><code>&lt;body&gt;
  &lt;p&gt;이벤트 &lt;button&gt;버튼&lt;/button&gt;&lt;/p&gt;
  &lt;script&gt;
    document.body.addEventListener(&#39;click&#39;,()=&gt;{console.log(&#39;버블링&#39;)}) - (3)
    document.querySelector(&#39;p&#39;).addEventListener(&#39;click&#39;,()=&gt;{console.log(&#39;캡쳐링&#39;)})  - (1)
    document.querySelector(&#39;button&#39;).addEventListener(&#39;click&#39;,()=&gt;{console.log(&#39;타겟&#39;)}) - (2)

&lt;/body&gt;</code></pre><ul>
<li>위의 코드의 경우, button 요소에서 클릭 이벤트가 발생하면 순차적으로 캡처링 , 타겟, 버블링 단계를 캐치하는 이벤트 핸들러가 호출된다.<pre><code>//console.log 결과
캡처링
타겟
버블링</code></pre><br />
<br />
<br />


</li>
</ul>
<h3 id="6-이벤트-위임-delegation">6. 이벤트 위임 delegation</h3>
<ul>
<li><p>여러 개의 하위 DOM 요소에 각각 이벤트 핸들러를 등록하는 대신 _<strong>하나의 상위 DOM 요소에 이벤트를 등록</strong>_하는 방법 (상위 요소가 하위 요소의 이벤트를 캐치 가능)</p>
</li>
<li><p>예를 들어, 모든 li 요소가 100개 일 때 모두 클릭 이벤트에 반응하도록 핸들러를 등록하려면 원래는 100개 등록해야 하는데, 이는 성능 저하와 유지보수에 적합하지 않다.</p>
</li>
<li><p>하지만 위임을 한다면 상위 1개의 요소에만 등록해도 같은 효과를 낸다.</p>
</li>
<li><p>주의할 점 : 실제 이벤트 타깃은 다를 수도 있으니 검사할 필요가 있다.</p>
</li>
<li><blockquote>
<p>Element.prototype.matches 메서드로 특정 노드를 탐색 가능한지 확인한다.</p>
</blockquote>
<pre><code>&lt;style&gt;
 #fruits {
    display: flex;
    list-style-type: none;
    padding: 0;
 }

 #fruits .active {
   color:red;
   text-decoration:underline;
 }
&lt;/style&gt;
</code></pre></li>
</ul>
<body>
  <nav>
    <ul id="fruits">
      <li id ="apple" class="active">Apple</li>
      <li id ="banana">Banana</li>
      <li id ="orange">Orange</li>
    </ul>
  </nav>
...

  <script>
...
//위임 전
    document.getElementById('apple').onclick=activate;
    document.getElementById('banana').onclick=activate;
    document.getElementById('orange').onclick=activate;

//위임 후 (1)
    $fruits.onclick = activate;

</script>
</body>

<pre><code>* (1) : 이 경우 이벤트 객체의 currentTarget 프로퍼티는 $fruit 요소를 가리키지만 이벤트 객체의 target 프로퍼티는 실제로 이벤트를 발생시킨 DOM 요소를 가리킬 수 있다.

&lt;br/&gt;
&lt;br/&gt;
&lt;br/&gt;

### 7. DOM 요소의 기본 동작 조작
#### 1) DOM 요소의 기본 동작 중단 : preventDefault
#### 2) 이벤트 전파 방지 : stopPropagation
* 예시: 상위 DOM에 위임시키고 하위 요소 이벤트 발생시 해당 요소에게 **바인딩된 이벤트 핸들러만** 실행시키고 싶을 때 (다른 요소에서 캐치되어서 실행되지 못하게)</code></pre><p>e.preventDefault();
e.stopPropagation();</p>
<p>```
<br/>
<br/>
<br/></p>
<h3 id="8-이벤트-핸들러-내부의-this-이벤트-핸들러에-인수-전달">8. 이벤트 핸들러 내부의 this, 이벤트 핸들러에 인수 전달</h3>
<h3 id="9-커스텀-이벤트">9. 커스텀 이벤트</h3>
<h3 id="10-debounce--throttle"><a href="https://webclub.tistory.com/607">10. debounce &amp; throttle</a></h3>
<ul>
<li><p>DOM 이벤트를 제어하는 방법</p>
</li>
<li><p>사용 이유 : 사용자가 스크롤과 같은 수많은 이벤트를 발생시켰을 때 이에 대한 무수한 callback이 발생하고, 이는 큰 리소스 소모로 이어짐. 이러한 과도한 실행횟수를 줄여 성능 문제를 해결하기 위함</p>
</li>
<li><p>사용 사례</p>
<ul>
<li>사용자가 창 크기 조정을 멈출 때까지 기다렸다가 resize</li>
<li>사용자가 키보드 입력을 중지할 때까지 stop</li>
<li>페이지의 스크롤 위치를 측정하고 일정 간격으로만 응답</li>
</ul>
</li>
<li><p>Debounce : 연이어 호출되는 함수들을 그룹화해 하나의 이벤트만 발생시킴</p>
<ul>
<li>활용 사례 : resize, keyboard</li>
</ul>
</li>
<li><p>Throttle : 이벤트를 일정 주기마다 발생하도록 함</p>
<ul>
<li>사례 : scroll (infinite scroll)</li>
<li>무한 스크롤 : 사용자가 footer까지 스크롤하면 추가 컨텐츠를 요청하는 원리. 디바운싱을 적용하면 footer까지 다 가서야 컨텐츠를 요청하기 때문에 그 전에 요청하기 위해 스로틀을 사용한다.</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[25. 화면 사이즈 바뀔 때마다 화면 새로고침]]></title>
            <link>https://velog.io/@random-olive/25.-%ED%99%94%EB%A9%B4-%EC%82%AC%EC%9D%B4%EC%A6%88-%EB%B0%94%EB%80%94-%EB%95%8C%EB%A7%88%EB%8B%A4-%ED%99%94%EB%A9%B4-%EC%83%88%EB%A1%9C%EA%B3%A0%EC%B9%A8</link>
            <guid>https://velog.io/@random-olive/25.-%ED%99%94%EB%A9%B4-%EC%82%AC%EC%9D%B4%EC%A6%88-%EB%B0%94%EB%80%94-%EB%95%8C%EB%A7%88%EB%8B%A4-%ED%99%94%EB%A9%B4-%EC%83%88%EB%A1%9C%EA%B3%A0%EC%B9%A8</guid>
            <pubDate>Fri, 17 Feb 2023 09:15:38 GMT</pubDate>
            <description><![CDATA[<ul>
<li>media query를 사용해서 데스크톱 사이즈와 모바일 사이즈로 반응형 웹사이트를 만들었는데, 뼈대는 지정한 픽셀에 따라 바뀌었으나 메뉴는 props를 초반에 받아서 그리다보니까 새로고침을 해야 제대로 변경되었다.</li>
<li>이에 화면 사이즈가 변경되면 이를 감지하고, 자동으로 화면을 새로고침하는 로직을 넣으려고 한다. App component에서 window.outerWidth + useState를 사용해 상태로 만들고 useEffect를 활용하면 더 쉽고 간편하게 구현할 수도 있으나 
많은 컴포넌트들에 모두 props로 내려줘야 할 것이기 때문에 상태관리 라이브러리를 설치 후에 그런 방법을 적용하려고 한다.</li>
<li>우선은 간단하게 window.addEventListner, window.removeEventListner를 사용해서 해당 기능을 구현했다.</li>
</ul>
<h3 id="1-width를-상태로-설정--windoweventlistener"><a href="https://db2dev.tistory.com/entry/React-resize-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%8B%A4%EB%A3%A8%EA%B8%B0">1. width를 상태로 설정 + window.eventListener</a></h3>
<pre><code>//App.tsx
function App() {
  const [windowSize, setWindowSize] = useState&lt;number&gt;(window.outerWidth); (1)
  const handleResize = () =&gt; {setWindowSize(window.outerWidth);
  }; (2)

  useEffect(() =&gt; {
    window.addEventListener(&#39;resize&#39;, handleResize); (3)
    return () =&gt; {
      window.removeEventListener(&#39;resize&#39;,handleResize) (4)
    }
  }, []); (5)

  return (
    &lt;&gt;
      &lt;div className=&#39;App&#39;&gt;{windowSize}
        &lt;Suspense fallback={&lt;LoadingBunny /&gt;}&gt;
          &lt;Routes&gt;
            &lt;Route element={&lt;BasicLayout /&gt;}&gt;
              &lt;Route path={PATH.MAIN} element={&lt;LandingPage /&gt;} /&gt;
              {/* &lt;Route path={PATH.CONTENTS} element={&lt;Contents /&gt;} /&gt; */}
            &lt;/Route&gt;
          &lt;/Routes&gt;
        &lt;/Suspense&gt;
      &lt;/div&gt;
    &lt;/&gt;
  );
}

export default App;</code></pre><ul>
<li>(1) : width에 따라 화면 변경을 감지 : window.outerWidth를 초기 state로 지정하고, 해당 값은 number type으로 설정해준다.</li>
<li>(2) : handleResize 함수는 현재의 window.outerWidth를 받아서 windowSize 상태를 변경시키는 함수이다.</li>
<li>(3), (4) : resize에 대한 이벤트는 window 객체에서 가능하다. 각각 &#39;resize&#39;에 대한 이벤트 리스너를 add/remove하고 이벤트가 발생했을 때 호출될 함수는 handleResize로 한다.</li>
<li>(5) : 빈 배열을 종속성 배열로 넣으면 컴포넌트가 마운트/언마운트 될때만 렌더링한다. 이를 이용해 컴포넌트가 마운트될 때 리스너를 add하고 언마운트될 때 remove하게 한다.</li>
</ul>
<h3 id="2-debounce">2. debounce</h3>
<ul>
<li>scroll, resize 등 이벤트 발생요청이 무수히 많아지면 성능 저하가 날 수 있다. 이를 방지하기 위해 debounce나 throttle 방법을 사용하는데 많은 이벤트를 그룹화했다가 동작이 끝나면 마지막 이벤트를 감지하는 debounce 기법을 사용해 성능 저하를 줄이려고 한다. 우선은 lodash 라이브러리를 이용해 구현한 후, <a href="https://www.mrlatte.net/code/2020/12/15/lodash-debounce">리팩토링시 clearTimeout, setTimeout을 이용하려고 한다.</a></li>
</ul>
<pre><code>//lodash(순수 JS로 작성된 유틸리티를 제공하는 라이브러리) 설치
npm i --save @types/lodash</code></pre><pre><code>//App.tsx
const handleResize = () =&gt; {
  setWindowSize(window.outerWidth);
};

-&gt; 많은 요청이 발생하는 부분을 debounce 콜백으로 처리하고, 시간은 1000ms로 지정한다.

const handleResize = debounce(() =&gt; {
  setWindowSize(window.outerWidth);
}, 1000);</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[24. 메뉴 드롭다운 구현 + useState + onClick]]></title>
            <link>https://velog.io/@random-olive/20.-%EB%A9%94%EB%89%B4-%EB%93%9C%EB%A1%AD%EB%8B%A4%EC%9A%B4-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@random-olive/20.-%EB%A9%94%EB%89%B4-%EB%93%9C%EB%A1%AD%EB%8B%A4%EC%9A%B4-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Wed, 15 Feb 2023 09:37:21 GMT</pubDate>
            <description><![CDATA[<ul>
<li><p>width: 768px 이상일 때는 hover하면 나타나는 메뉴를 나오게 하고
<img src="https://velog.velcdn.com/images/random-olive/post/df2ffd93-8442-4d28-b19b-a03d67c2ce49/image.png" alt=""></p>
</li>
<li><p>그 이하 (mobile size)일 때는 로고를 클릭하면 드롭다운 메뉴가 나오게 하려고 한다.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/random-olive/post/68643518-7dd6-4617-8da7-33bd1df51ce7/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/random-olive/post/1e116cc3-6b59-44d1-86b1-3c275f3446db/image.png" alt=""></p>
<h3 id="1-styled-component로-간단히-드롭다운-구현">1. <a href="https://programming-oddments.tistory.com/177">styled-component로 간단히 드롭다운 구현</a></h3>
<ul>
<li>원하는 위치에서 dropdown되게 하려면 position:absolute를 사용해야 하는데, absolute는 부모의 position을 기준으로 위치를 선정하는 prop으로, 부모의 position이 relative면 그걸 토대로 위치를 선정한다. 부모의 position이 설정되어 있지 않으면 화면 왼쪽 상단을 기준으로 위치를 잡기 시작한다.</li>
</ul>
<pre><code>&lt;DropDown&gt;         (1) 드롭다운 메뉴의 기준점: relative
  &lt;MenuBar&gt;        (2) 메뉴 리스트를 한 번에 담음 : absolute
      &lt;ul&gt;         (3) 메뉴 리스트
        &lt;li&gt;1&lt;/li&gt;
        &lt;li&gt;2&lt;/li&gt;
      &lt;/ul&gt;
  &lt;/MenuBar&gt;
&lt;/DropDown&gt;</code></pre><pre><code>const DropDown = styled.button`
  position: relative;          (1)
  width: 100px;
`;

const MenuBar = styled.div`
  position: absolute;          (2)
  display:none;                (4)
  ${DropDown}:active &amp; {       (5)
    display: block;
  }
  ${DropDown}:focus &amp; {        (5)
    display: block;
  }
`;</code></pre><ul>
<li>DropDown은 button으로 만들어 클릭에 따라 active한지 체크하게 한다.</li>
<li>(4) : 메뉴 리스트는 처음에 display:none으로 보이지 않다가,</li>
<li>(5) : DropDown 컴포넌트가 활성화되면 display:block으로 보여주기 시작한다. ${컴포넌트} : {} 문법은 아주 간편했다. <pre><code>// 하지만 styled-component로 작성된 형태는 가능하지만
const DropDown = styled.div`
...
`
//다음과 같은 형태는 불가능했다.
</code></pre></li>
</ul>
<p>const DropDown = () =&gt; {
return (
...)
}</p>
<pre><code>
### 2. useState를 활용한 드롭다운
* 복잡한 컴포넌트를 드롭다운으로 활용하기 위해서 다음과 같이 코드를 작성했다.</code></pre><p>//Layouts.tsx
export const BasicLayout = () =&gt; {
  return (
    &lt;&gt;
      <DropdownHeader />
      {window.outerWidth &lt; 768 ? &#39;&#39; : <MenuBar />}
      <Outlet />
      <Footer />
    &lt;/&gt;
  );
};</p>
<pre><code>* 768px 이상이면 horizontal 메뉴바를 띄운다.
* 드롭다운 메뉴바인 DropdownHeader 컴포넌트는 그 위에 항상 띄운다.</code></pre><p>//LogoAndSearch.tsx
export const DropdownHeader = () =&gt; {
  return (
    <Vertical>
      <LogoContainer>
        &lt;HomeLogo
          margin={window.outerWidth &lt; 768 ? &#39;0&#39; : RESPONSIVE.HEADER_MARGIN}
        /&gt;
        {window.outerWidth &lt; 768 ? <DropdownBar display='block' /> : &#39;&#39;}
      </LogoContainer>
      <Searchbar />
    </Vertical>
  );
};</p>
<pre><code>* 드롭다운 메뉴바는 LogoContainer로 이루어져 있는데, 이 안에는 HomeLogo와 DropdownBar로 이루어져 있다.
* HomeLogo의 위치를 relative로 한다.</code></pre><p>//MenuBar.tsx
export const DropdownBar: React.FC<BarProp> = ({ display }) =&gt; {
  return (
    <Vertical>
      <MenuContainer display={display}>
        {mainMenu[0].list.map((el, idx) =&gt; (
          <Main key={idx}>{el.title}</Main>
        ))}
      </MenuContainer>
    </Vertical>
  );
};</p>
<pre><code>* DropdownBar 내부의 MenuContainer 컴포넌트의 위치를 absolute로 한다. Main 컴포넌트는 메뉴블럭 하나하나를 뜻하는데 만약 Main의 위치를 absolute로 한다면 모든 메뉴가 한 곳에 겹쳐 나오기 때문에 Main들을 묶어서 absolute prop을 가져줄 MenuContainer 컴포넌트가 필요한 것이다.

* 이제 클릭할 때마다 useState로 상태에 따라 display의 prop을 block / none으로 결정하면 드롭다운 메뉴가 활성화된다.

### 3. useState + onClick type</code></pre><p>//LogoAndSearch.tsx
import {useState} from &#39;react&#39;;</p>
<p>interface ClickProp {
  onClick?: () =&gt; void; (1)
}</p>
<p>export const DropdownHeader: React.FC<ClickProp> = () =&gt; { (1)
  const [menuActive, setMenuActive] = useState<boolean>(false);  (2)
  const toggle = () =&gt; {setMenuActive(!menuActive)}; (3)</p>
<p>  return (
    <Vertical>
      <LogoContainer onClick={toggle}> (4)
        &lt;HomeLogo
          margin={window.outerWidth &lt; 768 ? &#39;0&#39; : RESPONSIVE.HEADER_MARGIN}
        /&gt;
        {window.outerWidth &lt; 768 ? (
          &lt;DropdownBar display={menuActive ? &#39;block&#39; : &#39;none&#39;} /&gt; 
        ) : (
          &#39;&#39;
        )} (5)
      </LogoContainer>
      <Searchbar />
    </Vertical>
  );
};</p>
<pre><code>* (1) : onClick prop을 사용하기 위해서 interface나 type을 설정한다.
만약 설정하지 않는다면 🟥&quot;type {onClick:()=&gt;void} is not assignable&quot; 과 같은 에러가 발생한다. TS에서는 함수 실행 후 별도의 return이 없으면 undefined 대신 void라는 데이터 타입을 반환한다. 그렇기 때문에 void를 type으로 설정해준다.
* (2) : menuActive 자체는 false / true로 boolean이므로 타입을 제네릭으로 지정해준다.
* (3), (4) : LogoContainer 컴포넌트를 클릭하면 set함수가 menuActive를 토글시킨다. 
* (5) : 드롭다운 바 자체는 menuActive가 되어있으면 &#39;block&#39;을 prop으로 받아 화면에 나타내고, 아닐 경우는 &#39;none&#39;을 prop으로 받아 화면에서 사라진다.</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[23. 반응형 화면 media query + 조건의 상수화]]></title>
            <link>https://velog.io/@random-olive/23.-%EB%B0%98%EC%9D%91%ED%98%95-%ED%99%94%EB%A9%B4-media-query</link>
            <guid>https://velog.io/@random-olive/23.-%EB%B0%98%EC%9D%91%ED%98%95-%ED%99%94%EB%A9%B4-media-query</guid>
            <pubDate>Sun, 12 Feb 2023 13:46:22 GMT</pubDate>
            <description><![CDATA[<h3 id="1-반응형-화면-구현하기--media-query-사용">1. <a href="https://blogpack.tistory.com/823">반응형 화면 구현하기 : media query 사용</a></h3>
<ul>
<li><p>media query를 활용해서 디바이스 사이즈에 따라 화면이 다르게 보이게 설정한다.
<img src="https://velog.velcdn.com/images/random-olive/post/63d9c8c4-24e8-4661-ab1d-23cc9baa6b8b/image.png" alt=""></p>
</li>
<li><p>기준은 최소 반응형 레이아웃으로, break point를 각각 다음과 같이 했다.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/random-olive/post/c45473bd-ccb1-429c-b203-f27400930fdf/image.png" alt=""></p>
<h3 id="2-코드-예시">2. 코드 예시</h3>
<pre><code>//media query를 작성
/* 데스크탑 */

@media screen and (max-width:1023px) {
/* 타블렛 */
}

@media screen and (max-width:767px) {
/* 모바일 */
}
</code></pre><br />
<br />

<h3 id="3-media-query의-좋은-점-😀">3. media query의 좋은 점 😀</h3>
<ul>
<li>media query 없이 구현할 때는 이렇게 외부에서 props 조건으로 반응형 화면을 구현했다. 파일 수정시마다 두 파일 다 계속 코드를 수정해야해서 디버깅하는데 시간이 오래 걸렸다. 하지만 그냥 media query를 사용하면 더 적은 파일만 수정하면 됐다.</li>
</ul>
<p>🟥 props만으로 구현</p>
<pre><code>//LogoAndSearch.tsx
export const SmallHeader = () =&gt; {
  return (
    &lt;Vertical&gt;
      &lt;LogoSlideStyle hoverBack={window.outerWidth &lt; 768 ? &#39;#c5f4c8&#39; : &#39;none&#39;}&gt;  (1)
        &lt;HomeLogo /&gt;
      &lt;/LogoSlideStyle&gt;
      &lt;Searchbar /&gt;
    &lt;/Vertical&gt;
  );
};</code></pre><ul>
<li>(1) 모바일 사이즈일 경우만 hover color를 &#39;#c5f4c8&#39;로 준다.<pre><code>//ChangingStyle.tsx
import styled from &#39;styled-components&#39;;
</code></pre></li>
</ul>
<p>type StyleProps = { (2)
  hoverBack: string;
};</p>
<p>export const LogoSlideStyle = styled.div<StyleProps>` (3)
  display: flex;
  justify-content: center;</p>
<p>  width: 100%;
  height: 100%;
  background: white;
  border: 5px solid ${(props) =&gt; props.hoverBack}; (4)
  &amp;:hover {
    background: ${(props) =&gt; props.hoverBack || &#39;none&#39;};
  } (5)
`;</p>
<pre><code>* (2),(3) : props를 사용하기 위해 type(generic) 지정
* (4) : 모바일 사이즈일 경우만 border 생성
* (5) : 모바일 사이즈일 경우만 hover 색상 변경

✅ media query를 사용</code></pre><p>//LogoAndSearch.tsx
export const SmallHeader = () =&gt; {
  return (
    <Vertical>
      <LogoSlideStyle> (1) 
        &lt;HomeLogo
          margin={window.outerWidth &lt; 768 ? &#39;0&#39; : RESPONSIVE.HEADER_MARGIN}
        /&gt;
      </LogoSlideStyle>
      <Searchbar />
    </Vertical>
  );
};</p>
<pre><code>* (1) props를 주지 않는다.</code></pre><p>//ChangingStyle.tsx
import styled from &#39;styled-components&#39;;
import { RESPONSIVE } from &#39;constants/style&#39;;</p>
<p>export const LogoSlideStyle = styled.div`
  display: flex;
  justify-content: center;</p>
<p>  width: 100%;
  height: 100%;
  background: white;</p>
<p>  @media screen and (max-width: ${RESPONSIVE.SMALL_PX}) { (2)
    width: 97vw;
    border: 5px solid #c5f4c8; (3)
    &amp;:hover { (4)
      background: #c5f4c8;
    }
  }
`;</p>
<pre><code>* (2) media query를 이용
* (3), (4) : 앞서 구현한 border, hover 내용을 한번에 관리

### 4. 조건의 상수화
* 화면 사이즈에 따라 코드가 반복되는 부분이 있어 조건을 상수화 해서 사용했는데, 잘 되었다. 사실 잘 안되는 부분도 있었는데 그런 경우, 코드가 문제기보다는 Error에는 잡히지 않지만 syntax적으로 맞지 않거나, 해당 조건이 해당되지 않아 변화 반영이 안된거였고, 혹은 새로고침을 하지 않아 변화가 반영되지 않은 경우였다.</code></pre><p>//Section.tsx
 &lt;img
     alt=&#39;advertise&#39;
    style={{ border: &#39;1px solid blue&#39; }}
    src={img}
    width={CONDITION.SMALL ? &#39;100%&#39; : SECTION.ADV_WIDTH} (1)
    height={SECTION.ADV_HEIGHT}
  /&gt;</p>
<pre><code></code></pre><p>//styleConstants.ts
export const CONDITION = {
  SMALL: &#39;window.outerWidth &lt; 768 ? &#39;,
};</p>
<pre><code></code></pre><p>//LogoAndSearch.tsx
export const SmallHeader = () =&gt; {
  return (
    <Vertical>
      <LogoSlideStyle>
        &lt;HomeLogo
          margin={window.outerWidth &lt; 768 ? &#39;0&#39; : RESPONSIVE.HEADER_MARGIN}
        /&gt; (2)
      </LogoSlideStyle>
      <Searchbar />
    </Vertical>
  );
};</p>
<pre><code>* (1): 태그에 props에 넘기는 경우 정상 작동함
* (2): 다만 컴포넌트에 props로 넘기는 경우 제대로 되지 않았다..
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[22. 인라인 스타일]]></title>
            <link>https://velog.io/@random-olive/22.-%EC%9D%B8%EB%9D%BC%EC%9D%B8-%EC%8A%A4%ED%83%80%EC%9D%BC</link>
            <guid>https://velog.io/@random-olive/22.-%EC%9D%B8%EB%9D%BC%EC%9D%B8-%EC%8A%A4%ED%83%80%EC%9D%BC</guid>
            <pubDate>Fri, 10 Feb 2023 09:55:35 GMT</pubDate>
            <description><![CDATA[<ul>
<li>광고란에 이미지 사이즈가 틀어지지 않았는지 확인하기 위해 일시적으로 인라인 스타일을 적용해보았다.</li>
</ul>
<pre><code>//Section.tsx
import { ReactNode } from &#39;react&#39;;
.
.
.

interface Props {
  img?: string;
  body?: ReactNode;
}

export const AdvSection: React.FC&lt;Props&gt; = ({ img }) =&gt; {
  return (
    &lt;&gt;
      &lt;AdvSectionFrame&gt;
        &lt;img
          alt=&#39;advertise&#39;
          style={{ border: &#39;1px solid blue&#39; }}
          src={img}
          width={SECTION.ADV_WIDTH}
          height={SECTION.ADV_HEIGHT}
        /&gt;
      &lt;/AdvSectionFrame&gt;
    &lt;/&gt;
  );
};</code></pre><ul>
<li>인라인 스타일</li>
<li><blockquote>
<p>태그 props에 자바스크립트 객체 형식으로 직접 넣는 스타일 방식</p>
</blockquote>
<pre><code></code></pre></li>
</ul>
<pre><code>* CSS, styled-components는 세미콜론(;)으로 항목들을 구분함에 반해
인라인스타일은 컴마(,)로 구분하고, 양쪽에 따옴표(&#39;&#39;)를 넣어줘야 한다 + 중괄호로 묶어서 넣는다.</code></pre><p>1) CSS, Styled-components
display: flex;
border: 1px solid blue;</p>
<p>2) Inline style</p>
<div 
 style = {
 {display:'flex', border: '1px solid blue'}
 }>
</div>

<hr>
<p>스타일을 바로 넣지 않고 상수를 사용해서 넣을 수도 있다.</p>
<div style = {makeBlue}></div>

<p>const makeBlue = {
display:&#39;flex&#39;, 
border: &#39;1px solid blue&#39;
}</p>
<pre><code>

</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[21. TS에서 컴포넌트 props 사용하기]]></title>
            <link>https://velog.io/@random-olive/21.-TS%EC%97%90%EC%84%9C-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-props-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@random-olive/21.-TS%EC%97%90%EC%84%9C-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-props-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 10 Feb 2023 09:49:29 GMT</pubDate>
            <description><![CDATA[<ul>
<li>랜딩페이지에서 섹션 사이에 이미지가 들어가는 파트를 고정하고, 외부 props로부터 쉽게 교체하고 싶었다.</li>
</ul>
<pre><code>//LandingPage.tsx

import {HotSection, AdvSection, MoreSection} from &#39;components/moleculres/Section&#39;;
import advPath from &#39;../assets/icons.png&#39;;

const LandingPage = () =&gt; {
  return (
    &lt;&gt;
      &lt;HotSection&gt;&lt;/HotSection&gt;
      &lt;AdvSection img={advPath} /&gt;
      &lt;MoreSection&gt;&lt;/MoreSection&gt;
    &lt;/&gt;
  );
};</code></pre><ul>
<li><p>평소 React만 쓸 때처럼 타입을 지정하지 않으면 다음과 같은 에러가 발생한다.</p>
</li>
<li><p>🟥 Binding element &#39;img&#39; implicitly has an &#39;any&#39; type.
<img src="https://velog.velcdn.com/images/random-olive/post/4d3ecb30-1a17-4673-9dd5-b9e776a472ed/image.png" alt=""></p>
</li>
<li><p>컴포넌트에 타입을 지정해주면 된다.</p>
<pre><code>//Section.tsx
import {ReactNode} from &#39;react&#39;;
.
.
.
</code></pre></li>
</ul>
<p>interface Props {
  img?: string;
  body?: ReactNode; (1)
}</p>
<p>export const AdvSection: React.FC<Props> = ({ img }) =&gt; { (2)
  return (
    &lt;&gt;
      <AdvSectionFrame>{img}</AdvSectionFrame>
    &lt;/&gt;
  );
};</p>
<p>export const MoreSection = ({body}) =&gt; {
  return (
    &lt;&gt;
      <MoreSectionFrame>
        <MoreTitle />
        {body}
      </MoreSectionFrame>
    &lt;/&gt;
  );
};</p>
<pre><code>* (1) props에 컴포넌트를 넣고 싶을 때 type에 ReactNode를 지정한다.
* (2) 제네릭 형태로 타입을 넣는다.</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[20. Layout, Dynamic loading (lazy, suspense)]]></title>
            <link>https://velog.io/@random-olive/20.-Layout-Dynamic-loading-lazy-suspense</link>
            <guid>https://velog.io/@random-olive/20.-Layout-Dynamic-loading-lazy-suspense</guid>
            <pubDate>Fri, 10 Feb 2023 07:02:09 GMT</pubDate>
            <description><![CDATA[<p>아이콘과 전체적인 페이지 색감 등 테스트를 마쳤으니 바로 페이지를 완성하고싶지만,
경험상 Layout 구조를 만든 후 페이지를 구현하는 것이 재사용과 관리 측면에서 좋았어서
다음의 순서로 개발을 진행한다.</p>
<ul>
<li>Layout 완성</li>
<li>dynamic import + 로딩바 구현</li>
<li>컴포넌트 + 랜딩페이지 제작 + 서브메뉴바</li>
<li>컴포넌트 + 컨텐츠 페이지 제작</li>
</ul>
<h3 id="1-layout--template">1. Layout &amp; Template</h3>
<ul>
<li>현재 나의 프로젝트 디렉토리 구성은 다음과 같은 <a href="https://fe-developers.kakaoent.com/2022/220505-how-page-part-use-atomic-design-system/">Atomic Design Pattern</a>을 따르고 있다.</li>
<li>템플릿 폴더에 Layout(페이지의 뼈대)을 지정해놓고, 필요할 때 쓰려고 한다.<pre><code>atoms: 단일 컴포넌트 (ex: 검색바, 검색 버튼)
molecules: atom의 묶음 (검색바 + 검색버튼)
organisms: molecule의 묶음 -&gt; 구역 (검색창 구역)
templates: 컨텐츠가 채워지지 않은 skeleton
pages: 컨텐츠로 채워진 templates</code></pre></li>
</ul>
<h3 id="2-layout--routes">2. Layout &amp; Routes</h3>
<ul>
<li><p>하려는 것 : 우선 Basic Layout을 생성하려고 한다. Basic Layout은 랜딩페이지 및 다른 페이지에 쓰이는 기본 뼈대로, 헤더, 메뉴바, (가변 요소), 푸터 구조를 고정시켜둔다. 페이지마다 해당 레이아웃을 불러오고, 가변 요소만 다르게 하면 매번 페이지 제작시에 같은 컴포넌트를 불러와서 뼈대를 제작할 필요가 없어 재사용성을 높이고 코드 중복을 줄일 수 있다. </p>
</li>
<li><p>Layout을 제작 후, 가변 요소는 react-router-dom 패키지의 &quot;Outlet&quot;이라는 요소로 넣을 수 있다.</p>
</li>
<li><p>웹페이지 경로에 따른 보이는 화면 구현은 react-router-dom 패키지의 &#39;BrowserRouter, Routes, Route&#39;로 구현할 수 있다.</p>
</li>
<li><p>우선, react-router-dom을 설치한다.</p>
<pre><code>npm i react-router-dom</code></pre></li>
<li><p>Header + 메뉴바 + Footer를 Basic Layout으로 하고, 내부를 조금씩 변화시켜줄 것이다. 이럴 경우 Outlet을 사용하여 다음과 같이 짤 수 있다.</p>
<pre><code>//Layouts.tsx
import {Outlet} from &#39;react-router-dom;
</code></pre></li>
</ul>
<p>import Header from &#39;components/molecules/Header&#39;;
import Menu from &#39;components/molecules/Menu&#39;;
import Footer from &#39;components/molecules/Footer&#39;;</p>
<p>export const Basic = () =&gt; {
    return (
        &lt;&gt;
        <Header />
        <Menu />
        <Outlet />     (1)
        <Footer />
        &lt;/&gt;
    )
  );
};</p>
<pre><code>* (1) Outlet부분이 변화되는 부분이고, 나머지가 고정되는 컴포넌트 모음이다.
* Outlet은 Routes, Route와 함께 활용할 것이고, 이를 에러없이 구현하기 위해서는 BrowserRouter로 App 컴포넌트 전체를 감쌀 필요가 있다.</code></pre><p>//root폴더의 index.tsx
import React from &#39;react&#39;;
import ReactDOM from &#39;react-dom/client&#39;;
import &#39;./index.css&#39;;
import App from &#39;./App&#39;;</p>
<p>import { ThemeProvider } from &#39;styled-components&#39;;
import { myTheme } from &#39;styles/theme/DefaultTheme&#39;;
import { BrowserRouter } from &#39;react-router-dom&#39;;</p>
<p>const root = ReactDOM.createRoot(
  document.getElementById(&#39;root&#39;) as HTMLElement
);</p>
<p>root.render(
  &lt;React.StrictMode&gt;
    <ThemeProvider theme={myTheme}>
      <BrowserRouter> (1)
        <App />
      </BrowserRouter>
    </ThemeProvider>
  &lt;/React.StrictMode&gt;
);</p>
<pre><code>* (1) BrowserRouter 태그로 App 컴포넌트를 감쌌다.
* 만든 Outlet과 Routes, Route는 다음과 같이 활용한다.</code></pre><p>//App.tsx
import {Basic} from &#39;components/templates/Layouts&#39;;
import PATH from &#39;constants/routePath&#39;;
import LandingPage from &#39;pages/LandingPage&#39;;</p>
<p>function App() {
  return (
    &lt;&gt;
      <div className='App'>
          <Routes> (1)
            &lt;Route element={<Basic />}&gt;  (2)
              &lt;Route path={PATH.MAIN} element={<LandingPage />} /&gt; (3)
            </Route>
          </Routes>
      </div>
    &lt;/&gt;
  );
}</p>
<p>export default App;</p>
<pre><code>* (1) Route는 Routes로 묶어야 문제 없이 구현된다. 
Layout을 사용하기 위해서 Route를 이중구조로 한다.
* (2) 부모 쪽 Route에 element로 들어가는 부분이 고정되는 컴포넌트다. Basic 컴포넌트에서 헤더-메뉴-Outlet-푸터 구조를 만들었으니 Basic 컴포넌트를 넣어준다.
* (3) 자식 쪽 Route에 element로 들어가는 부분이 Outlet에 들어가는 부분이다. 경로 &#39;/&#39;와 랜딩페이지에 대한 element 설정을 해준다.

### 3. Dynamic import (lazy, suspense)
* (static import)</code></pre><p>import Component from &#39;./Component&#39;;</p>
<pre><code>가장 상위에서 import 구문을 사용해서 불러오는 방식.
앱의 빌드 타임에 &#39;사용하지 않는 컴포넌트까지&#39; 한꺼번에 로딩해 초기 렌더링 지연시간이 길어질 수 있다.

* (dynamic import) 
React.lazy(), Suspense를 사용해서 불러오는 방식. 
런타임에 &#39;사용하는 모듈만&#39;을 로딩해 초기 렌더링 지연시간을 줄인다.
다만, 페이지를 이동하는 과정마다 로딩 화면이 보여질 수 있어 서비스에 따라서 적용여부를 결정하는 것이 좋다.</code></pre><p>//App.tsx
import {Suspense, lazy} from &#39;react&#39;;
import { Routes, Route } from &#39;react-router-dom&#39;;
import { Basic } from &#39;components/templates/Layouts&#39;;
import PATH from &#39;constants/routePath&#39;;
import { LoadingBunny } from &#39;components/atoms/Loading&#39;;</p>
<p>import LandingPage from &#39;pages/LandingPage&#39;;를 다음과 같이 수정
const LandingPage = lazy(() =&gt; import(&#39;pages/LandingPage&#39;)); (2)</p>
<p>function App() {
  return (
    &lt;&gt;
      <div className='App'>
        &lt;Suspense fallback={<LoadingBunny />}&gt; (1)
          <Routes>
            &lt;Route element={<Basic />}&gt;
              &lt;Route path={PATH.MAIN} element={<LandingPage />} /&gt; (2)
            </Route>
          </Routes>
        </Suspense>
      </div>
    &lt;/&gt;
  );
}</p>
<p>export default App;</p>
<pre><code>* (1) Suspense의 fallback prop은 컴포넌트가 로드될 때까지 로딩화면으로 보여줄 React 엘리먼트를 받아들인다.
* (2) React.lazy()로 감싼 컴포넌트는 단독으로 쓰일 수 없고, React.suspense의 컴포넌트 하위에서 렌더링해야 한다.


</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[19. Footer 하단 고정 + TS & Styled-Components props 변수 사용 & div 겹치기]]></title>
            <link>https://velog.io/@random-olive/20.-Footer-%ED%95%98%EB%8B%A8%EC%97%90-%EA%B3%A0%EC%A0%95</link>
            <guid>https://velog.io/@random-olive/20.-Footer-%ED%95%98%EB%8B%A8%EC%97%90-%EA%B3%A0%EC%A0%95</guid>
            <pubDate>Thu, 09 Feb 2023 04:12:06 GMT</pubDate>
            <description><![CDATA[<h3 id="1-footer를-body-컨텐츠-크기에-상관없이-하단에-고정">1. Footer를 body 컨텐츠 크기에 상관없이 하단에 고정</h3>
<pre><code>position:fixed;
bottom:0;
left: 0;
right: 0;</code></pre><h3 id="2-ts--styled-components-props-변수-사용">2. TS + Styled-Components props 변수 사용</h3>
<ul>
<li>컴포넌트의 기본 margin값을 상수로부터 불러오고,
일부 수정할 필요가 있을 때 밖에서 props로 변경하기를 원할 경우 다음과 같이 작성한다.<pre><code>//Bindings.ts
type BindingType = {
margin?: string; (1) 
};
</code></pre></li>
</ul>
<p>export const Horizontal = styled.div<BindingType><code>(2) 
.
.
.
margin : ${(props)=&gt; props.margin || DEFAULT.MARGIN} (3)</code></p>
<pre><code>* (1) type을 명시하고, ?를 넣어 값을 넣지 않아도 에러가 발생하지 않게 한다.
* (2) type을 제네릭으로 적용한다.
* (3) 밖에서 margin 값을 입력하지 않으면 DEFAULT.MARGIN값을 적용한다.</code></pre><p>//Menubar.tsx
export const Menubar = () =&gt; {
.
.
return (
    &lt;&gt;
        &lt;Horizontal margin={&#39;0&#39;}&gt; ... </Horizontal> (4)
    &lt;/&gt;
  )</p>
<p>} </p>
<pre><code>* (4) DEFAULT.MARGIN값이 아닌 다른 margin 값을 주고싶을 때 props로 적용해주면 된다.


### 3. div 겹쳐서 표현하기
* ![](https://velog.velcdn.com/images/random-olive/post/e6afb45c-ee61-4fd6-8e2c-1dbddefa5850/image.png)

그림과 같이 section frame에 HOT 아이콘을 겹쳐서 컴포넌트로 만들고 싶었다.
겹치기 위해서는 position: absolute를 사용해야 하는데, 부모 컴포넌트를 지정하는 것이 중요해서 다음과 구조로 컴포넌트 구조를 만들었다. 
(부모 컴포넌트를 position:relative로 지정하지 않으면, body 전체를 기준으로 position:absolute된 자식이 움직여서 뷰사이즈가 달라질 때마다 자식의 위치가 변경된다.)
</code></pre><p>//Section.tsx
export const HotSection = () =&gt; {
    return (
      &lt;&gt;
      <HotSectionFrame>     --&gt; (1)
        <HotTitle />        --&gt; (2)
      </HotSectionFrame>
      &lt;/&gt;
    )}</p>
<p>```</p>
<ul>
<li>(1) 밑에 깔리는 section frame : 부모 컴포넌트</li>
<li>(2) 위에 올라가는 HOT 아이콘 : 자식 컴포넌트</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[18. 서브메뉴 hover effect]]></title>
            <link>https://velog.io/@random-olive/19.-%EC%84%9C%EB%B8%8C%EB%A9%94%EB%89%B4-hover-effect</link>
            <guid>https://velog.io/@random-olive/19.-%EC%84%9C%EB%B8%8C%EB%A9%94%EB%89%B4-hover-effect</guid>
            <pubDate>Wed, 08 Feb 2023 08:43:57 GMT</pubDate>
            <description><![CDATA[<h3 id="1-서브메뉴-생성">1. 서브메뉴 생성</h3>
<ul>
<li><p>메뉴에 마우스를 호버하면 서브메뉴가 나오고, 밑줄이 가운데에서 밖으로 나가는 애니메이션을 추가했다.</p>
</li>
<li><p>서브메뉴는 앞의 두 개의 메인메뉴에서만 적용될 예정이어서, 해당 서브메뉴의 이름들을 constants/data.ts 의 menuData에 list 형태로 저장한 후, map을 사용해서 구현했다.</p>
</li>
<li><p>Type &#39;{title: string; href: string;}&#39; is not assignable to type &#39;ReactNode&#39; :<br>🟥 서브메뉴에서 el.title을 불러와야 하는데 el만을 불러왔을 때, 나왔던 에러.</p>
</li>
</ul>
<h3 id="2-서브메뉴-로직-리팩터링">2. 서브메뉴 로직 리팩터링</h3>
<ul>
<li>앞의 두 서브메뉴만 hover했을 때 나타나게 해야했다 : 단순하게 아래와 같이 우선 작성한 다음, <pre><code>//Menubar.tsx
</code></pre></li>
</ul>
<p>{idx === 0 &amp;&amp; subMenu[0].list.map((el, i) =&gt; <div key={i}>{el.title}</div>)}
{idx === 1 &amp;&amp; subMenu[1].list.map((el, i) =&gt; <div key={i}>{el.title}</div>)} </p>
<pre><code>* subMenu를 data.ts 파일에 입력하면, Menubar 컴포넌트에서 subMenuIdx가 담긴 배열을 받아서 다음과 같이 반복 없이 구현하도록 하였다.</code></pre><p>//Menubar.tsx
const subMenuIdx = Array.from(Array(subMenu.length),(_,i)=&gt;i);</p>
<p>return (
    &lt;&gt;
        .
        .
        .
        {subMenuIdx.map((i) =&gt; idx === i &amp;&amp; subMenu[i].list
        .map((menu, key) =&gt; 
        (<div key={key}>{menu.title}</div>)
        ))}
    &lt;/&gt;
)</p>
<pre><code></code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[17. 이미지 제작 및 스프라이트 기법 적용 ]]></title>
            <link>https://velog.io/@random-olive/17.-%EC%98%A4%EB%8A%98-%ED%95%A0-%EC%9D%BC</link>
            <guid>https://velog.io/@random-olive/17.-%EC%98%A4%EB%8A%98-%ED%95%A0-%EC%9D%BC</guid>
            <pubDate>Wed, 01 Feb 2023 02:43:30 GMT</pubDate>
            <description><![CDATA[<h3 id="1-아이콘-이미지-제작">1. 아이콘 이미지 제작</h3>
<ul>
<li>Photoshop으로 웹페이지 내에 쓰일 아이콘과 로고 등을 제작했다.<ul>
<li>(로딩 img는 웹페이지 기능이 어느정도 완료된 후 추가작업할 예정 (gif))</li>
</ul>
</li>
</ul>
<h3 id="2-이미지-스프라이트-기법--styled-components">2. 이미지 스프라이트 기법 (+ Styled-Components)</h3>
<ul>
<li>이미지 스프라이트 기법 : 브라우저 이미지 최적화할 때 사용하는 기법. 
페이지 로딩시 이미지를 개별적으로 로딩하면 서버 요청 수가 많아져서 로딩시간이 늘어난다.
이를 개선하기 위해 하나의 이미지 파일을 제작 후 background, background-position, width, height 
속성을 사용해 필요한 부분에 필요 아이콘만 보이게 한다. 
사용자경험 개선 뿐만 아니라 수많은 이미지를 하나로 관리해 관리적 측면에서도 유용하다.<pre><code>//App.tsx
import styled from &#39;styled-components&#39;;
import iconPath from &#39;./assets/icons.png&#39;;
</code></pre></li>
</ul>
<p>function App() {
    return (
        &lt;&gt;
            <div className='App'>
            .
            .
            .
                <Test /> &lt;-테스트할 이미지
            </div>
        &lt;/&gt;</p>
<pre><code>       );</code></pre><p>}</p>
<p>const Test = styled.div<code>background: url(${iconPath});
width: 150px;
height: 80px;
background-position: -10px 20px;</code></p>
<pre><code>-&gt; &quot;img&quot;태그에서 적용하면 태두리가 가는 선으로 같이 나타나서, 
&quot;div&quot; 태그에 일단 테스트해보았다.

### 3. 레이아웃 및 Template 제작
* App.tsx에 이미지 파일, 컴포넌트 등을 모두 넣고, 전체적인 디자인을 테스트해봤는데
전체적인 색감이나 컴포넌트 크기 등 더이상 크게 수정하거나 픽스할 것이 없다고 판단해
페이지를 정리한 후 레이아웃을 생성했다.
</code></pre><ul>
<li>필요한 페이지</li>
<li>랜딩페이지</li>
<li>메뉴 페이지</li>
<li>컨텐츠 페이지</li>
<li>회원가입, 로그인</li>
<li>검색결과 페이지</li>
<li>관리자 페이지<pre><code></code></pre></li>
</ul>
<h3 id="4lazy-suspense-적용">4.Lazy, Suspense 적용</h3>
<ul>
<li>Lazy, Suspense 등을 적용해 로딩바 미리 도입</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[16. Firebase cloud 클라이언트 액세스 만료 문제]]></title>
            <link>https://velog.io/@random-olive/16.-Firebase-cloud-%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8-%EC%95%A1%EC%84%B8%EC%8A%A4-%EB%A7%8C%EB%A3%8C-%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@random-olive/16.-Firebase-cloud-%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8-%EC%95%A1%EC%84%B8%EC%8A%A4-%EB%A7%8C%EB%A3%8C-%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Wed, 01 Feb 2023 01:37:50 GMT</pubDate>
            <description><![CDATA[<ul>
<li>금일 오전 메일이 와 있었다.</li>
</ul>
<p><strong>[Firebase] Cloud Firestore 데이터베이스에 대한 클라이언트 액세스가 3일 후에 만료됩니다</strong></p>
<p>테스트 모드에서 개발을 시작하도록 선택했기 때문에 Cloud Firestore 데이터베이스가 인터넷에 완전히 공개됩니다. 공격자에게 취약한 앱이므로 첫 30일이 지나면 요청 허용을 중지하도록 Firestore 보안 규칙이 구성되었습니다.</p>
<p>3일 후에 Firestore 데이터베이스에 대한 모든 클라이언트 요청이 거부됩니다. 그전까지 데이터를 적절히 보호하면서 앱이 작동할 수 있는 강력한 보안 규칙을 작성하세요. 분석은 매일 실행됩니다. 지난 24시간 이내에 규칙을 수정했다면 변경사항이 반영되지 않을 수 있습니다.</p>
<ul>
<li><p>해결</p>
<ul>
<li><p>&#39;규칙 수정&#39; 클릭</p>
<pre><code>rules_version = &#39;2&#39;;
service cloud.firestore {

match /databases/{database}/documents {
  match /{document=**} {
    allow read, write: if
      request.time &lt; timestamp.date(2023, 2, 4);
  }
}  
}</code></pre></li>
<li><p>request.time &lt; timestamp.date(2023, 2, 4)&gt; 에서
2023 -&gt; 2024로 변경시켜 준 수 &#39;게시&#39; 버튼을 눌러 변경사항을 적용한다.</p>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>