<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jj_24.log</title>
        <link>https://velog.io/</link>
        <description>take your time</description>
        <lastBuildDate>Sat, 05 Mar 2022 14:20:24 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>jj_24.log</title>
            <url>https://images.velog.io/images/jj_24/profile/307fd6a5-7475-4a14-9fea-2ebf98b603a1/KakaoTalk_20211229_152616054_08.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. jj_24.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jj_24" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[스크롤 메뉴는 어떻게 개발하나요?]]></title>
            <link>https://velog.io/@jj_24/%EC%8A%A4%ED%81%AC%EB%A1%A4-%EB%A9%94%EB%89%B4%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EA%B0%9C%EB%B0%9C%ED%95%98%EB%82%98%EC%9A%94</link>
            <guid>https://velog.io/@jj_24/%EC%8A%A4%ED%81%AC%EB%A1%A4-%EB%A9%94%EB%89%B4%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EA%B0%9C%EB%B0%9C%ED%95%98%EB%82%98%EC%9A%94</guid>
            <pubDate>Sat, 05 Mar 2022 14:20:24 GMT</pubDate>
            <description><![CDATA[<p>며칠 전에 웹 서비스를 이용하다가 페이지 상단의 네비게이션을 눌렀는데, 브라우저의 스크롤이 변경되며 화면이 이동하는 기능을 봤었습니다. 이러한 기능을 어떻게 만들지 궁금증이 들었는데요.</p>
<p>이번 글에서는 위의 기능을 저는 어떻게 개발했는지 얘기해보겠습니다.</p>
<hr>
<p>먼저 다음의 두가지에 대해 생각해 볼 필요가 있었습니다.</p>
<ul>
<li>화면의 위치를 어떻게 구할 것인가?</li>
<li>지정된 위치로 어떻게 스크롤을 할 것인가?</li>
</ul>
<h3 id="1화면의-위치를-어떻게-구할-것인가">1.화면의 위치를 어떻게 구할 것인가?</h3>
<p>페이지가 시작하는 가장 상단 부분에서 현재 보여질 화면이 시작하는 부분까지의 거리를 구해야 했습니다. 글 하단의 참고자료를 통해서 offsetTop을 사용해서 원하는 거리를 구할 수 있는 것을 알게 되었습니다.</p>
<p>Main.tsx</p>
<pre><code class="language-javascript">...

function Main() {
    const [sectionHeight, setSectionHeight] = useState&lt;number[]&gt;([]); 

      ...

    setSectionHieght(
        sectionRef.map((ref) =&gt; {
            if (ref.current) {
                return ref.current.offsetTop;
            }

            return 0;
        })  
    )

    ...

    return (
        ...

          &lt;section ref={sectionRef[0]}&gt;
              ...
        &lt;/section&gt;
          &lt;section ref={sectionRef[1]}&gt;
              ...
        &lt;/section&gt;

          ...
    )
}

...</code></pre>
<h3 id="2지정된-위치로-어떻게-스크롤을-할-것인가">2.지정된 위치로 어떻게 스크롤을 할 것인가?</h3>
<p>scrollTo 메소드를 사용하면 옵션 값으로 들어온 top 값만큼 스크롤을 이동시킬 수 있습니다.</p>
<p>ScrollNav.tsx</p>
<pre><code class="language-javascript">...

interface ScrollNavProps {
    sectionHeight: number[]; 
}

function ScrollNav(props: ScrollNavProps) {
  const handleClick = (e: React.MouseEvent) =&gt; {
    e.preventDefault();

    window.scrollTo({
      top: props.sectionHeight[
          Number(e.currentTarget.getAttribute(&#39;data-index&#39;))  
      ],
      behavior: &#39;smooth&#39;
    });
  };

  return (
      ...

    &lt;li&gt;
      &lt;a href=&quot;&quot; data-index={0} onClick={handleClick}&gt;
          section1
      &lt;/a&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;a href=&quot;&quot; data-index={1} onClick={handleClick}&gt;
          section2
      &lt;/a&gt;
    &lt;/li&gt;

    ...
  )
}

...</code></pre>
<hr>
<p>브라우저에서는 다음과 같이 보여집니다.
<img src="https://images.velog.io/images/jj_24/post/70f98d79-6b1a-418d-8a16-3625bbfa493b/uniformbridge_copy%20-%20Chrome%202022-03-05%2022-58-08.gif" alt=""></p>
<p>참고자료</p>
<p><a href="https://ko.javascript.info/size-and-scroll">https://ko.javascript.info/size-and-scroll</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[배너는 어떻게 개발하나요? - 2]]></title>
            <link>https://velog.io/@jj_24/%EB%B0%B0%EB%84%88%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EA%B0%9C%EB%B0%9C%ED%95%98%EB%82%98%EC%9A%94-Carousel</link>
            <guid>https://velog.io/@jj_24/%EB%B0%B0%EB%84%88%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EA%B0%9C%EB%B0%9C%ED%95%98%EB%82%98%EC%9A%94-Carousel</guid>
            <pubDate>Wed, 23 Feb 2022 20:22:42 GMT</pubDate>
            <description><![CDATA[<p><a href="https://velog.io/@jj_24/%EB%B0%B0%EB%84%88%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EA%B0%9C%EB%B0%9C%ED%95%98%EB%82%98%EC%9A%94">배너는 어떻게 개발하나요?</a> 에서 배너 개발에 대해 얘기했었는데요. 최근에 웹 서비스를 이용하다가 해당 글에서 얘기한 배너와는 다른 방식으로 동작하는 배너를 봤었습니다. 
<img src="https://images.velog.io/images/jj_24/post/46f129d1-4f14-44f7-9e5c-5649e1ed21b1/image.png" alt="">
좌측에서 우측으로 이동하는 방식의 배너인데요. 특이한 점은 현재 배너 이미지 좌우로 이전 배너 이미지의 일부와 다음 배너 이미지의 일부가 노출되고 있었습니다.</p>
<p>이번 글에서는 위와 같은 배너를 제가 어떻게 개발했는지 얘기해볼게요.</p>
<hr>
<p>먼저 다음의 다섯가지에 대해 생각해 볼 필요가 있었습니다.</p>
<ul>
<li>여러개의 배너 이미지를 어떻게 일렬로 위치시킬 것인가?</li>
<li>일렬로 위치시킨 배너를 어떻게 일부만 노출시킬 것인가?</li>
<li>배너 이미지를 어떻게 이동시킬 것인가?</li>
<li>마지막 배너 이미지와 첫번째 배너 이미지를 어떻게 연결할 것인가?</li>
<li>이전 배너 이미지 일부와 다음 배너 이미지 일부를 어떻게 노출시킬 것인가?</li>
</ul>
<h3 id="1-여러개의-배너-이미지를-어떻게-일렬로-위치시킬-것인가">1. 여러개의 배너 이미지를 어떻게 일렬로 위치시킬 것인가?</h3>
<p>배너 이미지를 감싸는 부모 엘리먼트에 &#39;display: flex&#39;와 &#39;width: fit-content&#39;를 사용했습니다. 그리고 배너 이미지에 해당하는 엘리먼트에 동일한 width 값을 주었습니다.
<img src="https://images.velog.io/images/jj_24/post/ce27b298-200e-4ad3-ae98-c1e7e333a09f/image.png" alt=""></p>
<h3 id="2-일렬로-위치시킨-배너를-어떻게-일부만-노출시킬-것인가">2. 일렬로 위치시킨 배너를 어떻게 일부만 노출시킬 것인가?</h3>
<p>1번에서 &#39;display: flex&#39;를 적용한 엘리먼트(배너 이미지를 감싸는 부모 엘리먼트)의 부모 엘리먼트에 &#39;overflow-x: hidden&#39;을 사용했습니다.
<img src="https://images.velog.io/images/jj_24/post/f94aea96-e5ff-44f9-86ef-ac7a111fcb23/image.png" alt=""></p>
<h3 id="3-배너-이미지를-어떻게-이동시킬-것인가">3. 배너 이미지를 어떻게 이동시킬 것인가?</h3>
<p>배너 이미지를 감싸고 있는 부모 엘리먼트에 &#39;transform: translateX(-1  * (100 / 배너 이미지 개수 *  현재 노출시킬 배너 이미지 인덱스) %)&#39; 와 &#39;transition: 0.5s&#39;를 사용해서 배너가 좌측에서 우측으로 이동하는 것처럼 보이게 했습니다.
<img src="https://images.velog.io/images/jj_24/post/82750968-b748-4499-b5c1-a85cc6d2d3f1/image.png" alt=""></p>
<h3 id="4-마지막-배너-이미지와-첫번째-배너-이미지를-어떻게-연결할-것인가">4. 마지막 배너 이미지와 첫번째 배너 이미지를 어떻게 연결할 것인가?</h3>
<p><img src="https://images.velog.io/images/jj_24/post/f0cf1514-3e84-4f09-ad50-d3b4666be9ce/uniformbridge_copy%20-%20Chrome%202022-02-26%2016-19-35.gif" alt="">
위의 화면 처럼 1번 이미지 -&gt; 2번 이미지 -&gt; 3번 이미지 -&gt; 2번이 노출되고 1번 이미지와 같은 상황이 발생하지 않기 위해서 첫번째 배너 이미지와 마지막 배너 이미지가 연결된 것처럼 만들어야 합니다. 이를 위해서 마지막 배너 이미지 다음에 첫번째 배너 이미지를 추가해주고 첫번째 배너 이미지 이전에 마지막 배너 이미지를 추가했습니다.
<img src="https://images.velog.io/images/jj_24/post/83a2b044-95e3-4a6f-955e-7e31fe9d0e3b/image.png" alt="">
배너가 우측으로 이동할 때 인덱스 번호가 4인 이미지의 노출시간이 지나면 인덱스 번호 1번인 이미지를 노출시킵니다. 이 때 transition을 0s로 변경해서 사용자에게 3번 이미지와 2번 이미지가 노출되지 않도록 했습니다. 그 다음 transition을 다시 0.5s로 변경하고 배너 이미지 노출시간을 0으로 변경해서 인덱스 번호가 2번인 이미지로 곧 바로 이동하게 했습니다. 
사용자가 버튼을 눌러서 배너가 좌측으로 이동할 때는 인덱스 번호가 0인 이미지의 노출시간이 지나면 인덱스 번호 3번 이미지를 노출시킵니다. 이 때도 transition을 0s로 변경해서 사용자에게 1번 이미지와 2번 이미지가 노출되지 않도록 했습니다. 그 다음 transition을 다시 0.5s로 변경하고 배너 이미지 노출시간을 0으로 변경해서 인덱스 번호가 2번인 이미지로 곧 바로 이동하게 했습니다.
이 때 주의할 점은 인덱스 번호 1번인 이미지와 인덱스 번호 3번인 이미지를 노출시킬 때 이미지의 이동 방향을 구분하고 있어야 배너가 좌측으로 이동할 때와 우측으로 이동할 때의 이미지 노출 시간과 transition 값을 변경해서 배너가 정상적으로 작동할 수 있습니다.
<img src="https://images.velog.io/images/jj_24/post/94129f0b-0400-40e3-a66e-d663a846012b/uniformbridge_copy%20-%20Chrome%202022-02-26%2018-09-46.gif" alt=""></p>
<h3 id="5이전-배너-이미지-일부와-다음-배너-이미지-일부를-어떻게-노출시킬-것인가">5.이전 배너 이미지 일부와 다음 배너 이미지 일부를 어떻게 노출시킬 것인가?</h3>
<p>배너 이미지를 감싸고 있는 부모 엘리먼트의 &#39;transform&#39;을 &#39; translateX(-1  * ((100 / 배너 이미지 개수) * 0.9 + (100 / 배너 이미지 개수) *  현재 노출시킬 배너 이미지 인덱스) %)&#39; 로 변경해서 이전 배너 이미지 일부와 다음 배너 이미지 일부가 노출되도록 했습니다.
<img src="https://images.velog.io/images/jj_24/post/c7caa22a-3654-4645-89a7-1a8dc4c1e60b/image.png" alt="">
그리고 인덱스 번호 0번인 이미지가 노출될 때와 인덱스 번호 4번인 이미지가 노출될 때 이전 배너 이미지 일부와 다음 배너 이미지 일부가 노출될 수 있도록 맨 앞과 맨 뒤에 배너 이미지를 추가했습니다.
<img src="https://images.velog.io/images/jj_24/post/eae7fbf2-3c79-46bf-8d85-893a18972474/image.png" alt=""></p>
<hr>
<p>브라우저에서는 다음과 같이 보여집니다.
<img src="https://images.velog.io/images/jj_24/post/a08d246f-7d5f-40f9-b520-8865c2258d5d/uniformbridge_copy%20-%20Chrome%202022-02-26%2019-28-47.gif" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[배너는 어떻게 개발하나요?]]></title>
            <link>https://velog.io/@jj_24/%EB%B0%B0%EB%84%88%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EA%B0%9C%EB%B0%9C%ED%95%98%EB%82%98%EC%9A%94</link>
            <guid>https://velog.io/@jj_24/%EB%B0%B0%EB%84%88%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EA%B0%9C%EB%B0%9C%ED%95%98%EB%82%98%EC%9A%94</guid>
            <pubDate>Sat, 12 Feb 2022 23:46:49 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/jj_24/post/54145cee-aba8-44ed-bc55-e4e6654a7451/%EB%B0%B0%EB%84%882.png" alt=""></p>
<p>평소처럼 웹 서비스를 이용하는데 문득 &#39;메인 페이지의 배너는 어떻게 개발하는 걸까&#39; 궁금증이 들었어요.</p>
<p>이번 글에서는 제가 배너를 개발한 방식에 대해 얘기해볼게요.</p>
<hr>
<p>배너를 개발하기 위해서 먼저 다음 두가지에 대해 생각해볼 필요가 있었습니다.</p>
<ul>
<li>여러개의 배너 이미지 중 어떻게 하나의 배너 이미지만 노출시킬 것인가?</li>
<li>배너 이미지를 자동으로 변경하려면 어떻게 해야 하는가?</li>
</ul>
<h3 id="1-여러개의-배너-이미지-중-어떻게-하나의-배너-이미지만-노출시킬-것인가">1. 여러개의 배너 이미지 중 어떻게 하나의 배너 이미지만 노출시킬 것인가?</h3>
<p>이를 위해서 &#39;position: absolute&#39;를 사용해서 배너 이미지들을 같은 지점에 위치시켰습니다. 그리고 &#39;display: none&#39;, &#39;display: block&#39;을 사용해서 하나의 배너 이미지만 노출시키고 다른 배너 이미지들은 노출되지 않도록 했습니다.</p>
<p>Banner.tsx</p>
<pre><code class="language-javascript">...

const bannerImage = [&#39;https://.../1.jpg&#39;, &#39;https://.../2.jpg&#39;, &#39;https://.../3.jpg&#39;]; // 이미지 url을 갖고있는 배열

const [bannerIndex, setBannerIndex] = useState&lt;number&gt;(0); //화면에 보여지는 배너 이미지의 인덱스를 갖는 state

...

{bannerImage.map((image, index) =&gt; {
    return (
    &lt;div
        className={`banner__image${
            bannerIndex === index ? &quot; active&quot; : &quot;&quot;
        }`}
        key={index}
    &gt;
        &lt;a href=&quot;&quot; style={{ background: `url(${image})` }}&gt;&lt;/a&gt;
    &lt;/div&gt;      
    );
})}

...

}</code></pre>
<p>Banner.scss</p>
<pre><code class="language-css">...

 .banner__image {
    position: absolute;
    width: 100%;
    height: 100%;
    display: none;
    animation: bannerOpacity 0.7s;

    a {
      display: block;
      height: 100%;
    }

    .active {
      display: block;
      }
  }

  @keyframes bannerOpacity {
    0% {
      opacity: 0;
    }

    100% {
      opacity: 1;
    }
  }
...
</code></pre>
<p>사용자에게 노출시킬 배너 이미지를 정하기 위해서 bannerIndex라는 state를 사용했습니다. 배너 이미지의 index가 bannerIndex와 동일할 경우 className에 active를 추가해서 &#39;display: block&#39;이 적용되도록 했습니다.</p>
<h3 id="2-배너-이미지를-자동으로-변경하려면-어떻게-해야-하는가">2. 배너 이미지를 자동으로 변경하려면 어떻게 해야 하는가?</h3>
<p>배너 이미지를 자동으로 변경하기 위해서 setInterval을 사용했습니다.</p>
<p>주의할 점은 react에서는 setInterval을 다음과 같은 코드만으로 사용할 경우</p>
<pre><code class="language-javascript">setInterval(() =&gt; setBannerIndex((bannerIndex + 1) % bannerImage.length), 3000);</code></pre>
<p>랜더링이 될 때 마다 새로운 interval이 생성되기 때문에 문제가 발생합니다. 그래서 unmount가 될 때 clearInterval을 호출해서 현재 실행 중인 interval을 제거해야 합니다. 이를 위해서 useEffect를 사용했습니다. </p>
<pre><code class="language-javascript">useEffect(() =&gt; {
    const id = setInterval(() =&gt; setBannerIndex((bannerIndex + 1) % bannerImage.length), 3000)

    return () =&gt; clearInterval(id);
}, [bannerIndex]);</code></pre>
<hr>
<p>브라우저에서는 다음과 같이 보여집니다.
<img src="https://images.velog.io/images/jj_24/post/99209201-d6b2-4566-9c65-3799c5993fd1/banner.gif" alt=""></p>
<p>전체 코드는 다음과 같습니다.
Banner.tsx</p>
<pre><code class="language-javascript">import * as React from &quot;react&quot;;
import { useState, useEffect } from &quot;react&quot;;
import &quot;../styles/Banner.scss&quot;;

function Banner() {
  const bannerImage = [
    &quot;https://edit-edition.com/images/m-1.jpg&quot;,
    &quot;https://edit-edition.com/web/upload/NNEditor/20211110/03_shop1_201805.jpg&quot;,
    &quot;https://edit-edition.com/web/upload/NNEditor/20211110/12_shop1_201806.jpg&quot;,
  ];

  const [bannerIndex, setBannerIndex] = useState&lt;number&gt;(0);
  const [bannerChangeFlag, setBannerChangeFlag] = useState&lt;boolean&gt;(true);

  const handleLeftBtnClick = (e: React.MouseEvent) =&gt; {
    setBannerIndex(
      bannerIndex === 0
        ? bannerImage.length - 1
        : (bannerIndex - 1) % bannerImage.length,
    );
  };

  const handleRightBtnClick = (e: React.MouseEvent) =&gt; {
    setBannerIndex((bannerIndex + 1) % bannerImage.length);
  };

  const handleDotClick = (e: React.MouseEvent) =&gt; {
    const index = e.currentTarget.getAttribute(&quot;data-index&quot;);

    setBannerIndex(index ? Number(index) : 0);
  };

  const handleMouseEnter = (e: React.MouseEvent) =&gt; {
    setBannerChangeFlag(false);
  };

  const handleMouseLeave = (e: React.MouseEvent) =&gt; {
    setBannerChangeFlag(true);
  };

  useEffect(() =&gt; {
    let id: NodeJS.Timer;
    if (bannerChangeFlag) {
      id = setInterval(
        () =&gt; setBannerIndex((bannerIndex + 1) % bannerImage.length),
        3000,
      );
      }

    return () =&gt; clearInterval(id);
  }, [bannerIndex, bannerChangeFlag]);

  return (
    &lt;div className=&quot;banner&quot;&gt;
      {bannerImage.map((image, index) =&gt; {
        return (
          &lt;div
            className={`banner__image${
              bannerIndex === index ? &quot; active&quot; : &quot;&quot;
            }`}
            key={index}
            onMouseEnter={handleMouseEnter}
            onMouseLeave={handleMouseLeave}
          &gt;
            &lt;a href=&quot;&quot; style={{ background: `url(${image})` }}&gt;&lt;/a&gt;
          &lt;/div&gt;
        );
      })}
      &lt;div className=&quot;banner__leftBtn&quot; onClick={handleLeftBtnClick}&gt;&lt;/div&gt;
      &lt;div className=&quot;banner__rightBtn&quot; onClick={handleRightBtnClick}&gt;&lt;/div&gt;
      &lt;ul className=&quot;banner__dots&quot;&gt;
        {bannerImage.map((image, index) =&gt; {
          return (
            &lt;li key={index}&gt;
              &lt;button
                className={bannerIndex === index ? &quot;active&quot; : undefined}
                data-index={index}
                onClick={handleDotClick}
              &gt;&lt;/button&gt;
            &lt;/li&gt;
          );
        })}
      &lt;/ul&gt;
    &lt;/div&gt;
  );
}

export default Banner;
</code></pre>
<p>Banner.scss</p>
<pre><code class="language-css">.banner {
  position: relative;
  height: 300px;
  min-width: 1200px;
  padding-left: 70px;

  .banner__image {
    position: absolute;
    width: 100%;
    height: 100%;
    display: none;
    animation: bannerOpacity 0.7s;

    a {
      display: block;
      height: 100%;
    }
  }

  .active {
    display: block;
  }

  .banner__leftBtn {
    position: absolute;
    top: 135px;
    left: 150px;
    cursor: pointer;
  }

  .banner__leftBtn:after {
    content: &quot;&quot;;
    width: 20px;
    height: 20px;
    border-top: 5px solid #d7d7d7;
    border-right: 5px solid #d7d7d7;
    display: inline-block;
    transform: rotate(225deg);
    transition: 0.2s;
  }

  .banner__leftBtn:hover:after {
    border-top: 5px solid #000;
    border-right: 5px solid #000;
  }

  .banner__rightBtn {
    position: absolute;
    top: 135px;
    right: 80px;
    cursor: pointer;
  }

  .banner__rightBtn:after {
    content: &quot;&quot;;
    width: 20px;
    height: 20px;
    border-top: 5px solid #d7d7d7;
    border-right: 5px solid #d7d7d7;
    display: inline-block;
    transform: rotate(45deg);
    transition: 0.2s;
  }

  .banner__rightBtn:hover:after {
    border-top: 5px solid #000;
    border-right: 5px solid #000;
  }

  .banner__dots {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 10px;
    text-align: center;
    padding-left: 70px;

    li {
      display: inline-block;
      margin: 0px 3px;

      button {
        height: 16px;
        border-radius: 8px;
        cursor: pointer;
        background-color: #d7d7d7;
        border-color: #d7d7d7;
      }

      .active {
        background-color: #000;
        border-color: #000;
      }
    }
  }
}

@keyframes bannerOpacity {
  0% {
    opacity: 0;
  }

  100% {
    opacity: 1;
  }
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[함수형 컴포넌트... 함수 선언식? 함수 표현식? React.FC?]]></title>
            <link>https://velog.io/@jj_24/%ED%95%A8%EC%88%98%ED%98%95-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8...-%ED%95%A8%EC%88%98-%EC%84%A0%EC%96%B8%EC%8B%9D-%ED%95%A8%EC%88%98-%ED%91%9C%ED%98%84%EC%8B%9D-React.FC</link>
            <guid>https://velog.io/@jj_24/%ED%95%A8%EC%88%98%ED%98%95-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8...-%ED%95%A8%EC%88%98-%EC%84%A0%EC%96%B8%EC%8B%9D-%ED%95%A8%EC%88%98-%ED%91%9C%ED%98%84%EC%8B%9D-React.FC</guid>
            <pubDate>Sun, 16 Jan 2022 05:12:50 GMT</pubDate>
            <description><![CDATA[<p>TypeScript + React로 함수형 컴포넌트를 개발하는 분들은 한번쯤은 <strong>&#39;컴포넌트를 함수 표현식으로 개발할까? 함수 선언식으로 개발할까?&#39;</strong>, <strong>&#39;타입을 명시하려고 타입스크립트를 사용하는거니까 React.FC 타입을 선언하는게 좋지 않을까?&#39;</strong>, <strong>&#39;React.FC 타입을 선언하는 것과 안하는 것의 차이가 있을까?&#39;</strong> 궁금증을 가져보셨을 건데요. </p>
<p>이번 글에서는 위의 궁금증에 대한 대답을 간략하게 다뤄볼게요 </p>
<hr>
<p>함수형 컴포넌트를 개발할 때 다음과 같이 코드를 작성할 수 있습니다.</p>
<pre><code class="language-javascript">    function Login() {
        return (
            &lt;&gt; .... &lt;/&gt;  
        )
    }

    const Login = () =&gt; {
         return (
            &lt;&gt; .... &lt;/&gt;  
        )
    }</code></pre>
<p>함수 선언식과 <strong>타입을 지정하지 않은</strong> 함수 표현식은 JSX.Element를 반환하는 함수를 타입으로 갖게 됩니다. </p>
<p><img src="https://images.velog.io/images/jj_24/post/7e8bddac-d15e-4135-98d0-3ca6a7908c00/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/jj_24/post/f7e97216-cf3c-4534-92b0-2f81da0bda91/image.png" alt=""></p>
<p>함수 표현식은 다음과 같이 React.FC 타입을 선언할 수도 있습니다. </p>
<pre><code class="language-javascript">   const Login:React.FC = () =&gt; {
         return (
            &lt;&gt; .... &lt;/&gt;
        )
   }
</code></pre>
<p><img src="https://images.velog.io/images/jj_24/post/ab525903-25ba-4709-9fdb-d588ba2a7f7e/image.png" alt=""></p>
<hr>
<p>JSX.Element, JSX.Element가 상속받는 React.ReactElement, React.FC는 다음과 같이 정의되어 있습니다.</p>
<ul>
<li>JSX.Element</li>
</ul>
<blockquote>
<pre><code class="language-javascript">declare global {
    namespace JSX {
       interface Element extends React.ReactElement&lt;any, any&gt; {}</code></pre>
</blockquote>
<ul>
<li>React.ReactElement</li>
</ul>
<blockquote>
<pre><code class="language-javascript">interface ReactElement&lt;P = any, T extends string |
    JSXElementConstructor&lt;any&gt; = string | JSXElementConstructor&lt;any&gt;&gt; {
    type: T;
   props: P;
   key: Key | null;
}</code></pre>
</blockquote>
<ul>
<li>React.FC</li>
</ul>
<blockquote>
<pre><code class="language-javascript">type FC&lt;P = {}&gt; = FunctionComponent&lt;P&gt;;

interface FunctionComponent&lt;P = {}&gt; {
    (props: PropsWithChildren&lt;P&gt;, context?: any): ReactElement&lt;any, any&gt; | null;

   ...

}</code></pre>
</blockquote>
<p><br></br><br>여기서 주목할 점은 JSX.Element가 상속받는 React.ReactElement와 FunctionComponent에 공통으로 들어있는 <strong>props</strong> 속성입니다.</p>
<p><strong>ReactElement는 컴포넌트가 선언될 때의 props interface를 그대로 사용하고, FunctionComponent는 컴포넌트가 선언될 때의 props interface를 PropsWithChildren으로 감싸고 있는 것을 확인할 수 있습니다.</strong>
<br></br><br>PropsWithChildren은 다음과 같이 정의되어 있습니다.</p>
<ul>
<li>PropsWithChildren</li>
</ul>
<blockquote>
<pre><code class="language-javascript">type PropsWithChildren&lt;P&gt; = P &amp; { children?: ReactNode | undefined };
</code></pre>
</blockquote>
<p><br></br> 
<strong>FunctionComponent가 PropsWithChildren을 통해서 props interface에 개발자가 정한 속성 외에 children 속성을 추가하는 것을 확인할 수 있습니다.</strong></p>
<hr>
<p>위의 내용을 확인할 간단한 React 코드입니다.</p>
<p>Main.tsx</p>
<pre><code class="language-javascript">...

import { default as FunctionalComponent } from &#39;./FunctionalComponent&#39;;
import { default as ChildComponent } from &#39;./ChildComponent&#39;;

function Main() {
  return &lt;FunctionalComponent&gt;{ChildComponent}&lt;/FunctionalComponent&gt;
}
</code></pre>
<p>FunctionalComponent.tsx</p>
<pre><code class="language-javascript">...

interface FunctionalComponentProps {
    ... 
}

function FunctionalComponent(props: FunctionalComponentProps) {
  return  (
      ...
  );
}</code></pre>
<p><img src="https://images.velog.io/images/jj_24/post/9e1bbe47-fbb3-47de-ad61-34955c8f5577/image.png" alt=""></p>
<p>FunctionalComponent.tsx (React.FC Type)</p>
<pre><code class="language-javascript">...

interface FunctionalComponentProps {
     ...
}

const FunctionalComponent: React.FC&lt;FunctionalComponentProps&gt; = (props) =&gt; {
  return (
      ...  
  );
};</code></pre>
<p>  <img src="https://images.velog.io/images/jj_24/post/f4bacf50-1720-4299-8a93-9a3e264752c8/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[팝업창은 어떻게 개발하나요?]]></title>
            <link>https://velog.io/@jj_24/%ED%8C%9D%EC%97%85%EC%B0%BD%EC%9D%80-%EC%96%B4%EB%96%BB%EA%B2%8C-%EA%B0%9C%EB%B0%9C%ED%95%98%EB%82%98%EC%9A%94</link>
            <guid>https://velog.io/@jj_24/%ED%8C%9D%EC%97%85%EC%B0%BD%EC%9D%80-%EC%96%B4%EB%96%BB%EA%B2%8C-%EA%B0%9C%EB%B0%9C%ED%95%98%EB%82%98%EC%9A%94</guid>
            <pubDate>Fri, 14 Jan 2022 14:55:46 GMT</pubDate>
            <description><![CDATA[<p>웹 서비스를 이용하다보면 다음과 같은 팝업창을 본 적이 있을거에요</p>
<img src="https://images.velog.io/images/jj_24/post/a1c90ec9-e288-4073-928e-69974200ff86/image.png" width="200px">
<img src="https://images.velog.io/images/jj_24/post/e07b2298-ffde-482f-86d9-39745961524f/image.png" width="450px">

<p>이번 글에서는 팝업을 어떤 식으로 개발하는지 알아볼게요  </p>
<hr>
<p>팝업을 개발하려면 먼저 다음 두가지에 대해 알고 있어야 합니다.</p>
<h2 id="cookie">Cookie</h2>
<ul>
<li>Cookie는 브라우저에 저장되는 데이터입니다. <img src="https://images.velog.io/images/jj_24/post/586cb851-166c-49d4-87c6-0397e1ae0c19/image.png" style="display:inline; width: 200px"></li>
<li>이름, 값, 도메인, 경로, 만료일에 대한 정보를 갖고있습니다.<img src="https://images.velog.io/images/jj_24/post/a729a395-8c91-4e59-9ea5-2885294ea479/image.png" style="display:inline; width: 700px"></li>
<li>만료일이 지나면 자동으로 삭제됩니다.</li>
<li>팝업의 오늘 그만 보기 기능을 구현하는데 사용합니다.<ul>
<li>사용자가 버튼을 눌렀을 때 쿠키를 생성합니다.</li>
<li>해당 페이지를 다시 방문했을 때 쿠키가 존재하는지를 확인하고, 쿠키가 없을 경우에 팝업을 노출시키는 방식으로 개발할 수 있습니다.<br/>

</li>
</ul>
</li>
</ul>
<h2 id="dimmed">Dimmed</h2>
<ul>
<li>팝업을 강조하기 위해서 팝업 외 다른 부분에 반투명 음영을 넣는 것을 딤드라고 합니다.</li>
<li>팝업 외 다른 요소들보다 상위에 위치해야 하므로 z-index 속성을 사용합니다.</li>
<li>불투명도를 설정해야 하므로 opacity 속성을 사용합니다.<pre><code class="language-scss">{
z-index: 3000;
opacity: 0.5;
...
}</code></pre>
</li>
</ul>
<hr>
<p>React를 사용한 간단한 팝업 코드입니다. </p>
<details>
<summary>Main.tsx</summary>

<pre><code class="language-javascript">  import * as React from &quot;react&quot;;
  import { useCookies } from &quot;react-cookie&quot;;
  import { default as Popup } from &quot;../components/Popup&quot;;

  function Main() {
    const [cookies, setCookie] = useCookies([&quot;nonePopup&quot;]);

    return (
      &lt;div
        className=&quot;main&quot;
        style={{
          backgroundImage: &quot;url(https://edit-edition.com/images/mainbanner.jpg)&quot;,
          width: &quot;100%&quot;,
          height: &quot;100%&quot;,
        }}
      &gt;
        {cookies.nonePopup ? null : &lt;Popup setCookie={setCookie}&gt;&lt;/Popup&gt;}
      &lt;/div&gt;
    );
}

export default Main;</code></pre>
</details>

<details>
<summary>Popup.tsx</summary>

<pre><code class="language-javascript">  import * as React from &quot;react&quot;;
  import { useState } from &quot;react&quot;;
  import &quot;../styles/Popup.scss&quot;;

  interface PopupProps {
    setCookie: (
      name: &quot;nonePopup&quot;,
      value: any,
      options?: CookieSetOptions | undefined,
    ) =&gt; void;
  }

  function Popup(props: PopupProps) {
    const [isVisible, setIsVisible] = useState&lt;boolean&gt;(true);

    const handleRightBtnClick = () =&gt; {
      setIsVisible(false);
    };

    const handleLeftBtnClick = () =&gt; {
      const expires = new Date();
      expires.setDate(expires.getDate() + 1);

      props.setCookie(&quot;nonePopup&quot;, true, { expires });
    };

    return (
      &lt;&gt;
        {isVisible ? (
          &lt;div className=&quot;popup&quot;&gt;
            &lt;div className=&quot;popup__dimmed&quot;&gt;&lt;/div&gt;
            &lt;div className=&quot;popup__modal&quot;&gt;
              &lt;div className=&quot;modal__content&quot;&gt;
                &lt;p&gt;내용&lt;/p&gt;
              &lt;/div&gt;
              &lt;div className=&quot;modal__bottom&quot;&gt;
                &lt;div className=&quot;bottom__left-btn&quot; onClick={handleLeftBtnClick}&gt;
                  &lt;p&gt;오늘 그만 보기&lt;/p&gt;
                &lt;/div&gt;
                &lt;div className=&quot;bottom__right-btn&quot; onClick={handleRightBtnClick}&gt;
                  &lt;p&gt;닫기&lt;/p&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        ) : null}
      &lt;/&gt;
    );
  }

  export default Popup;
</code></pre>
</details>


<details>
<summary>Popup.scss</summary>

<pre><code class="language-css">  .popup {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    z-index: 3500;

    .popup__dimmed {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background-color: #000;
      opacity: 0.5;
    }

    .popup__modal {
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      width: 300px;
      height: 450px;
      border: solid 1px;
      border-radius: 20px;
      background-color: #fff;

      .modal__content {
        display: flex;
        justify-content: center;
        align-items: center;
        height: 410px;
      }

      .modal__bottom {
        position: absolute;
        left: 0;
        bottom: 0;
        display: flex;
        height: 40px;
        border-top: solid 1px;
        color: #000;
        cursor: pointer;

        .bottom__left-btn {
          border-right: solid 1px;
          border-bottom-left-radius: 20px;
          width: 150px;
        }

        .bottom__right-btn {
          border-left: solid 1px;
          border-bottom-right-radius: 20px;
          width: 150px;
        }

        .bottom__left-btn p,
        .bottom__right-btn p {
          display: flex;
          justify-content: center;
          align-items: center;
          height: 100%;
        }
      }
    }
  }

  p {
    font-weight: bold;
    font-size: 15px;
  }</code></pre>
</details>

<hr>
<p>  브라우저에서는 다음과 같이 보여집니다.</p>
<ul>
<li><p>팝업이 노출됐을 때 화면
<img src="https://images.velog.io/images/jj_24/post/6a72f91c-4303-45e1-98b9-7c92e8249660/image.png" alt=""></p>
</li>
<li><p>팝업이 닫혔을 때 화면
<img src="https://images.velog.io/images/jj_24/post/32b5e11d-3d96-4d90-8b29-9132a6fd0c8c/image.png" alt=""></p>
</li>
<li><p>오늘 그만 보기 버튼을 눌렀을 때 생성된 쿠키
<img src="https://images.velog.io/images/jj_24/post/55f7d482-2587-41cd-8610-2f74c572061f/image.png" alt=""></p>
</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>