<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>sohee-k.log</title>
        <link>https://velog.io/</link>
        <description>Web Frontend Developer</description>
        <lastBuildDate>Sun, 16 Jan 2022 06:25:03 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>sohee-k.log</title>
            <url>https://images.velog.io/images/sohee-k/profile/c7fb1c6b-77bf-4f7a-9a8a-6dc04b687355/KakaoTalk_20220107_180659463.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. sohee-k.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/sohee-k" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[React TypeScript 환경에서 Swiper 사용하기(image slider library)]]></title>
            <link>https://velog.io/@sohee-k/React-TypeScript-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-Swiper-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0image-slider-library</link>
            <guid>https://velog.io/@sohee-k/React-TypeScript-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-Swiper-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0image-slider-library</guid>
            <pubDate>Sun, 16 Jan 2022 06:25:03 GMT</pubDate>
            <description><![CDATA[<p>앱잼 기간<code>22.01.02 - 01.21</code> 동안 소품샵 리뷰 및 지도 서비스 <strong>소담</strong>을 개발하고 있습니다. 
각종 리뷰와 소품샵 정보를 보여주는 만큼 이미지 슬라이더가 필수적으로 사용되는데, 적절한 슬라이더 라이브러리를 찾던 중 <strong>Swiper</strong>를 사용하기로 결정했습니다.</p>
<p><a href="https://swiperjs.com/swiper-api">Swiper API 공식문서 바로가기</a></p>
<p><img src="https://images.velog.io/images/sohee-k/post/94031249-5433-43f9-8e4d-bf271b3f7bf2/20220116_040451.png" alt="주간 HOT 소품샵">기능은 다음과 같습니다.</p>
<ol>
<li>좌우 버튼을 누르면 이미지가 한 칸씩 이동한다</li>
<li>이미지를 더 이상 넘길 수 없는 경우, 좌우 버튼이 사라집니다</li>
</ol>
<p>크게 어려운 기능은 아니지만, 기존의 라이브러리 스타일을 그대로 쓰는 게 아니라 어느 정도 커스텀이 필요합니다.</p>
<h1 id="1-초기-세팅하기">1. 초기 세팅하기</h1>
<hr>
<h2 id="설치">설치</h2>
<blockquote>
<p>yarn add <a href="mailto:swiper@6.8.4">swiper@6.8.4</a></p>
</blockquote>
<p>그냥 <code>yarn add swiper</code>로 설치하면 최신버전으로 설치되는데, 최신버전에서는 swiper 모듈을 찾을 수 없다는 에러가 발생합니다. 버전을 낮춰서 설치하면 정상적으로 사용할 수 있습니다.</p>
<h2 id="css-import">css import</h2>
<blockquote>
<p>import &#39;swiper/swiper.min.css&#39;;</p>
</blockquote>
<p>따로 스타일을 커스텀할 예정이라 최소한의 css만 가져왔습니다. 기존 라이브러리의 다양한 스타일을 사용한다면 <code>swiper/swiper.bundle.min.css</code> 까지 import 하면 됩니다. 여기에는 <code>Navigation</code>, <code>Pagination</code> 등 요소의 모든 스타일이 포함되어 있습니다.</p>
<h2 id="슬라이더-뼈대-만들기">슬라이더 뼈대 만들기</h2>
<pre><code>import SwiperCore, { Navigation, Scrollbar } from &#39;swiper&#39;;
import { Swiper, SwiperSlide } from &#39;swiper/react&#39;;

function MainSlider(cardList) {
    SwiperCore.use([Navigation, Scrollbar]);

    return (
        &lt;Swiper&gt;
           {cardList.map(card =&gt; &lt;SwiperSlide key={card.id}&gt;{card}&lt;/SwiperSlide&gt;)}
        &lt;/Swiper&gt;
    );
}

export default MainSlider;</code></pre><ul>
<li><code>SwiperCore.use()</code> 안에 슬라이더에서 사용할 기능을 배열로 추가합니다. <strong>Navigation</strong>(좌우 화살표)와 <strong>Scrollbar</strong>(스크롤바)만 가져왔는데, 필요하다면 Pagination도 추가해 줍니다.</li>
<li><code>&lt;Swiper&gt;</code>는 이미지 슬라이더의 최상단 컴포넌트입니다. 내부에 <code>&lt;SwiperSlide&gt;</code>를 원하는 만큼 추가하면 이미지 슬라이더를 매우 쉽게 만들 수 있습니다.</li>
</ul>
<p>여기까지 작성하면 기본적인 이미지 슬라이더를 만들 수 있습니다.</p>
<h1 id="2-슬라이더-커스텀하기">2. 슬라이더 커스텀하기</h1>
<hr>
<h2 id="여러가지-속성-추가하기">여러가지 속성 추가하기</h2>
<pre><code>const settings = {
    spaceBetween: // px 단위 간격
        navigation: { // 좌우 버튼 커스텀 
            prevEl: // 이전 버튼 Ref 또는 className
            nextEl: // 다음 버튼 Ref 또는 className
        },
        scrollbar: { // 스크롤바 커스텀
            draggable: // 드래그 가능 여부
                el: // 스크롤바 Ref 또는 className
        },
        slidesPerView: // 한 화면에 보이는 슬라이드 수
        onBeforeInit: // 이벤트 핸들러
};
...
return (
    &lt;Swiper {...settings}&gt;
        ...
    &lt;/Swiper&gt;
);</code></pre><p><code>&lt;Swiper&gt;</code>에 직접 속성을 줄 수 있지만, 깔끔한 코드를 위해서 속성을 따로 분리하여 작성했습니다.</p>
<ul>
<li><code>spaceBetween</code>: 각 슬라이드 사이의 간격으로, 숫자만 입력합니다. 이 때의 단위는 px입니다.</li>
<li><code>navigation</code>: 좌우 버튼을 커스텀할 수 있습니다. <code>prevEl</code>과 <code>nextEl</code>을 따로 설정하지 않으면 기본 값이 들어갑니다. 커스텀 할 경우, className 또는 ref.current를 입력합니다. 이 부분에 대해서는 아래에서 자세히 다룰 예정입니다.</li>
<li><code>scrollbar</code>: 스크롤바를 커스텀할 수 있습니다. <code>draggable</code>을 true로 설정하면 슬라이더를 드래그할 수 있습니다. <code>el</code>을 null로 설정하면 스크롤바가 보이지 않습니다.</li>
<li><code>slidesPerView</code>: 한 화면에 보이는 슬라이드 수를 설정할 수 있습니다.</li>
<li>그 외: <code>onBeforeInit</code>, <code>onDragStart</code>, <code>onClick</code> 등 이벤트 핸들러</li>
</ul>
<p>속성을 따로 부여하지 않는 기본 슬라이더의 모습은 다음과 같습니다.
<img src="https://images.velog.io/images/sohee-k/post/664f10e0-a88e-4b10-a55d-b0c8398f0127/2.png" alt="example1">기본값은 좌우 버튼도 없고, 한 화면에 슬라이드 하나만 보이고, 드래그로 슬라이드를 넘길 수 있는 <del>아주 별로인</del> 기본적인 슬라이더입니다.</p>
<p>아래와 같은 속성을 부여하면 가장 기본적인 형태의 슬라이더가 완성됩니다. 라이브러리의 navigation 스타일을 사용하기 위해 별도의 css로 import 합니다.</p>
<pre><code>import &#39;swiper/components/navigation/navigation.min.css&#39;;

const settings = {
    spaceBetween: 20,
        navigation: {},
        scrollbar: { draggable: true, el: null },
        slidesPerView: 3,
};</code></pre><p><img src="https://images.velog.io/images/sohee-k/post/cbcb376f-d744-4319-b032-8bc02559edc3/1.png" alt="example2">좌우 버튼도 생기고, 한 화면에 슬라이드가 3개씩 보이고, 드래그로 슬라이드를 넘길 수 있고, 슬라이드를 더 넘길 수 없으면 좌우 버튼이 비활성화 됩니다.</p>
<h2 id="추가-커스텀">추가 커스텀</h2>
<p>위의 뼈대에 스타일을 전혀 주지 않으면 슬라이더가 위의 화면처럼 나오지 않습니다. className으로 직접 접근하면 더 세세한 커스텀이 가능합니다. 현재 <code>styled-components</code>를 사용하고 있기 때문에, <code>&lt;StyledRoot&gt;</code> 에서 css 스타일을 추가했습니다.</p>
<pre><code>import styled from &#39;styled-components&#39;;
...
function MainSlider(cardList) {
    ...
    return (
          &lt;StyledRoot&gt;
            &lt;Swiper {...settings}&gt;
                ...
            &lt;/Swiper&gt;
          &lt;/StyledRoot&gt;
        );
}
...
const StyledRoot = styled.div`
    .swiper {
            &amp;-wrapper,
            &amp;-container {
                  width: 62rem;
                  margin: 0;
            }
            &amp;-container {
                  margin: 0 3.2rem;
            }
            &amp;-button-disabled {
                  visibility: hidden;
            }
      }
`;</code></pre><p>이렇게 <code>.swiper-wrapper</code>, <code>.swiper-container</code>, <code>.swiper-button-disabled</code>의 스타일을 변경하면 다음과 같이 보여집니다.
<img src="https://images.velog.io/images/sohee-k/post/74f2087f-5141-4fa1-ab4d-b869e8ce8073/3.png" alt="example3"><code>width</code>와 <code>margin</code>은 슬라이드의 크기에 맞춰서 변경하면 됩니다. </p>
<p><code>.swiper-button-disabled</code>에 <code>visibility: hidden;</code> 속성을 준 것은 더 이상 슬라이드를 넘길 수 없는 경우에 버튼을 숨기기 위함입니다. 
<code>display: none;</code> 속성을 주면 추후에 버튼을 커스텀 했을 때 슬라이더가 빈 공간을 메꾸면서 움직일 수 있기 때문에 <code>visibility</code> 속성을 사용했습니다.</p>
<h2 id="navigation-커스텀">navigation 커스텀</h2>
<p>Swiper를 커스텀하면서 제일 어려웠던 부분이 바로 <strong>navigation</strong>(좌우 화살표) <strong>커스텀</strong>이었습니다. 기존의 navigation은 슬라이더 안쪽에 있기 때문에 이걸 슬라이더 밖으로 단순히 이동시켜서는 <strong>화면에 보이지 않게 됩니다</strong>.</p>
<p>따라서 슬라이더 밖에 navigation 버튼을 두고 싶다면 추가적인 커스텀이 필요합니다. 커스텀하는 방법에는 크게 2가지가 있습니다.</p>
<ol>
<li>className 사용하기</li>
<li>useRef 사용하기</li>
</ol>
<blockquote>
<p>className은 재사용되는 컴포넌트에는 적절하지 않은 방법입니다.</p>
</blockquote>
<p>모든 슬라이더의 navigation 버튼에 같은 className이 적용되기 때문에, 버튼 하나를 누르면 모든 슬라이더가 동시에 움직이는 참사가 발생하게 됩니다. 따라서 <strong>useRef</strong>를 사용하는 것을 매우 권장드립니다.</p>
<pre><code>import { useRef } from &#39;react&#39;;

function MainSlider(cardList) {
    const prevRef = useRef(null);
    const nextRef = useRef(null);

    const settings = {
        spaceBetween: 20,
            navigation: {
                    prevEl: prevRef.current, // 이전 버튼
                        nextEl: nextRef.current, // 다음 버튼
                },
            scrollbar: { draggable: true, el: null },
            slidesPerView: 3,
                onBeforeInit: (swiper) =&gt; { // 초기 설정
                    swiper.params.navigation.prevEl = prevRef.current;
                      swiper.params.navigation.nextEl = nextRef.current;
                        swiper.navigation.update();
                },
        };

        return (
          &lt;StyledRoot&gt;
              &lt;StyledButton ref={prevRef}&gt;
                  이전 버튼(img 태그 등등)
              &lt;/StyledButton&gt;
              &lt;Swiper {...swiperSetting}&gt;
                      {cardList.map((card) =&gt; (
                          &lt;SwiperSlide key={card.key}&gt;{card}&lt;/SwiperSlide&gt;
                      ))}
              &lt;/Swiper&gt;
              &lt;StyledButton ref={nextRef}&gt;
                  다음 버튼(img 태그 등등)
              &lt;/StyledButton&gt;
          &lt;/StyledRoot&gt;
    );
};</code></pre><ul>
<li><code>settings.navigation</code>의 <code>prevEl</code>과 <code>nextEl</code>에 각각 ref.current를 설정해 주세요.</li>
<li><code>settings.onBeforeInit</code>에 초기설정 코드를 추가해 주세요. <code>onBeforeInit</code> 대신에 <code>onInit</code>을 사용해도 됩니다. navigation 객체 내용을 변경했으므로 최신 상태로 업데이트해야 합니다.</li>
<li>커스텀 버튼에 ref를 설정해 주세요. onClick 이벤트가 발생하기 때문에 <code>&lt;button&gt;</code> 태그를 사용하는 것이 좋습니다.</li>
</ul>
<p>이렇게 하면 잘 동작할 것 같지만, 이대로는 반드시 문제가 발생합니다 ⚠</p>
<h3 id="useref와-useeffect">useRef와 useEffect</h3>
<p>useRef는 React에서 DOM 요소를 저장하기 위해 종종 사용됩니다. 이 때, 초기값으로는 보통 <code>null</code>을 넣고, 추후에 DOM 트리가 생성되면 <code>HTMLElement</code> 요소값이 ref에 저장됩니다.</p>
<p>React의 구조상, 모든 JavaScript 파일을 한꺼번에 불러온 후 화면에 DOM 요소들을 paint합니다. 즉, useRef에 값이 제대로 들어가기 전에 settings 값이 설정될 수 있습니다.</p>
<pre><code>const settings = {
        spaceBetween: 20,
            navigation: {
                    prevEl: prevRef.current, // 이전 버튼
                        nextEl: nextRef.current, // 다음 버튼
                },
            scrollbar: { draggable: true, el: null },
            slidesPerView: 3,
                onBeforeInit: (swiper) =&gt; { // 초기 설정
                    swiper.params.navigation.prevEl = prevRef.current;
                      swiper.params.navigation.nextEl = nextRef.current;
                        swiper.navigation.update();
                },
        };</code></pre><p>여기서 <code>prevRef.current</code>와 <code>nextRef.current</code> 값이 <code>null</code>이 될 가능성이 매우 높습니다. 둘 중 하나는 <code>HTMLElement</code>이고 하나는 <code>null</code>이 될 수도 있습니다. 정확한 값이 보장되지 않는 것이죠.</p>
<p>따라서 <strong>useEffect</strong>를 사용하여 컴포넌트가 마운트될 때 settings 값을 정해줘야 안정적으로 DOM 요소를 불러올 수 있습니다. 그리고 settings는 <strong>useState</strong>를 사용해서 상태관리 했습니다.</p>
<pre><code>const [swiperSetting, setSwiperSetting] = useState(null);

useEffect(() =&gt; {
    if (!swiperSetting) {
             const settings = { ... };
             setSwiperSetting(settings);
         }
}, [swiperSetting]);
...
return (
    &lt;StyledRoot&gt;
            {swiperSetting &amp;&amp; (
              &lt;Swiper {...settings}&gt;
                  ...
              &lt;/Swiper&gt;
            )}
         &lt;/StyledRoot&gt;

);</code></pre><p><code>useEffect</code> 내부에서 <code>swiperSetting</code> 값이 <code>null</code>이라면 <code>setSwiperSetting(settings)</code>를 통해서 값을 넣어줍니다. 컴포넌트가 최초로 마운트될 때 한 번 실행됩니다.
그리고 <code>swiperSetting</code> 값이 있을 때 <code>&lt;Swiper&gt;</code> 컴포넌트가 화면에 보여져야 정상적인 동작을 할 수 있기 때문에 return 부분에 추가적인 분기처리를 해줍니다.</p>
<h1 id="3-typescript-적용하기">3. TypeScript 적용하기</h1>
<hr>
<p>위의 코드를 모두 모아서 TypeScript를 적용하면 다음과 같습니다. Next를 사용했기 때문에 <code>&lt;img&gt;</code> 대신에 <code>&lt;Image&gt;</code>를 사용했습니다.</p>
<pre><code>import &#39;swiper/swiper.min.css&#39;;

import Image from &#39;next/image&#39;;
import { ReactElement, useEffect, useRef, useState } from &#39;react&#39;;
import styled from &#39;styled-components&#39;;
import SwiperCore, { Navigation, Scrollbar } from &#39;swiper&#39;;
import { Swiper, SwiperSlide } from &#39;swiper/react&#39;;

interface MainSliderProps {
  cardList: ReactElement[]; // 슬라이드에는 컴포넌트가 들어갑니다
  slidesPerView: 3 | 4; // 한 번에 보이는 카드 수
}

function MainSlider(props: MainSliderProps) {
  const { cardList, slidesPerView } = props;

  SwiperCore.use([Navigation, Scrollbar]);

  const prevRef = useRef&lt;HTMLButtonElement&gt;(null);
  const nextRef = useRef&lt;HTMLButtonElement&gt;(null);
  const [swiperSetting, setSwiperSetting] = useState&lt;Swiper | null&gt;(null);

  useEffect(() =&gt; {
    if (!swiperSetting) {
      setSwiperSetting({
        spaceBetween: 24,
        navigation: {
          prevEl: prevRef.current, // 이전 버튼
          nextEl: nextRef.current, // 다음 버튼
        },
        scrollbar: { draggable: true, el: null },
        slidesPerView,
        onBeforeInit: (swiper: SwiperCore) =&gt; {
          if (typeof swiper.params.navigation !== &#39;boolean&#39;) {
            if (swiper.params.navigation) {
              swiper.params.navigation.prevEl = prevRef.current;
              swiper.params.navigation.nextEl = nextRef.current;
            }
          }
          swiper.navigation.update();
        },
      });
    }
  }, [swiperSetting, slidesPerView]);

  return (
    &lt;StyledRoot&gt;
        &lt;StyledButton ref={prevRef}&gt;
          &lt;Image src={&#39;/assets/ic_prev.svg&#39;} width={12} height={24} alt=&quot;prev&quot; /&gt;
        &lt;/StyledButton&gt;
        {swiperSetting &amp;&amp; (
          &lt;Swiper {...swiperSetting}&gt;
            {cardList.map((card) =&gt; (
              &lt;SwiperSlide key={card.key}&gt;{card}&lt;/SwiperSlide&gt;
            ))}
          &lt;/Swiper&gt;
        )}
        &lt;StyledButton ref={nextRef}&gt;
          &lt;Image src={&#39;/assets/ic_next.svg&#39;} width={12} height={24} alt=&quot;next&quot; /&gt;
        &lt;/StyledButton&gt;
    &lt;/StyledRoot&gt;
  );
}

const StyledRoot = styled.div`
  width: 128.8rem;
  display: flex;
  justify-content: center;
  align-items: center;
  margin-left: -8.8rem;
  button {
    padding: 0;
    background: none;
    border: none;
  }
  .swiper {
    &amp;-wrapper,
    &amp;-container {
      width: 120rem;
      margin: 0;
    }
    &amp;-container {
      margin: 0 3.2rem;
    }
    &amp;-button-disabled {
      visibility: hidden;
    }
  }
`;

export default MainSlider;</code></pre><p>그래서 완성된 결과는 다음과 같습니다.
<img src="https://images.velog.io/images/sohee-k/post/8c82e2b2-0c0f-4626-8b54-806d353c70c9/sodam-slider.gif" alt="slider"></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[졸업예정자의 2021년 회고]]></title>
            <link>https://velog.io/@sohee-k/%EC%A1%B8%EC%97%85%EC%98%88%EC%A0%95%EC%9E%90%EC%9D%98-2021%EB%85%84-%ED%9A%8C%EA%B3%A0-8t3e8a9p</link>
            <guid>https://velog.io/@sohee-k/%EC%A1%B8%EC%97%85%EC%98%88%EC%A0%95%EC%9E%90%EC%9D%98-2021%EB%85%84-%ED%9A%8C%EA%B3%A0-8t3e8a9p</guid>
            <pubDate>Fri, 07 Jan 2022 12:33:31 GMT</pubDate>
            <description><![CDATA[<p>2022년 새해 목표 중 하나인 <strong>&#39;주 2회 이상 벨로그 포스팅 작성하기&#39;</strong>를 위해 벨로그를 개설했다. 사실 작년부터 만들고 싶었는데, 파트장에 졸업준비에 너무 바빠서 2021년은 정신이 없었다. (사실 핑계임)</p>
<p>앞으로는 </p>
<ol>
<li>TIL</li>
<li>프론트엔드 개발지식 정리</li>
<li>완료한/진행중인 프로젝트에 대한 회고</li>
<li>알고리즘 문제풀이</li>
<li>취준 일기
등을 기록할 예정이다.</li>
</ol>
<p>그 전에, 본격적으로 웹 개발을 시작한 2021년에 대한 회고를 먼저 진행할까 한다.</p>
<h1 id="프로젝트">프로젝트</h1>
<hr>
<h2 id="sqoop2012--2104">sqoop(20.12 ~ 21.04)</h2>
<p><img src="https://images.velog.io/images/sohee-k/post/f0e509a2-2342-468e-9286-3dca3bb51077/KakaoTalk_20210307_221229231.png" alt="sqoop"><a href="https://sqoop.co.kr">sqoop 서비스 바로가기🍨</a></p>
<p>처음으로 기획/디자인/웹 프론트엔드/서버 파트로 분화된 팀 단위의 협업이자, 릴리즈까지 경험한, 나에게는 정말 의미있는 서비스다. 
2020년 하반기에 SOPT 27기 웹 파트에 들어와서 처음으로 React를 배웠는데, 부족한 실력임에도 리드개발자를 맡았다. 그래서 다양한 기술스택을 사용하지는 못했고, React와 Redux 정도만 사용했다. 그리고 첫 팀 단위 프로젝트이기 때문에 작성한 코드를 이제와서 보면 정말 엉망이고 고칠 게 많이 보인다. 그 만큼 내가 성장했다는 이야기겠지만, 누가 코드를 뜯어볼까봐 살짝 두렵다...😅</p>
<p>내가 사용하고 싶은 서비스여서 그런지 더 애착을 가지고 개발했다. 실질적인 개발기간은 <code>20.12.28 ~ 21.01.16</code>인데, 이 때 대부분의 기능이 완성되었다. 이후에는 QA 과정과 3순위 기능 개발이 이루어졌다. 사실 2월 안에 마무리 할 수 있었는데, 재학 중에 파트장까지 맡느라 sqoop에 신경 쓸 시간이 절대적으로 부족했다.</p>
<p>가장 아쉬웠던 건, 코드리뷰가 원활하지 않았기 때문에 더 나은 코드를 위한 고민이 부족했다는 점이다. 이 때는 코드리뷰의 중요성도 알지 못했고 그저 개발하는 데만 급급했기 때문에, 나보다 실력이 더 나은 리드개발자가 있었다면 성능적으로 더 우수한 서비스가 되지 않았을까 싶다.</p>
<h2 id="oo7d2103--2106">OO7D(21.03 ~ 21.06)</h2>
<p><strong>한 줄 소개</strong>: <code>날씨와 선호에 따른 옷 추천 서비스</code>
이중전공인 융복합소프트웨어전공 종합설계(캡스톤디자인) 프로젝트다. 개발자 5명으로 팀이 구성되었기 때문에, 그나마 프로젝트 경험이 있는 내가 PM + 디자인 + 웹 프론트엔드 개발까지 맡았다. 졸업하기 위해 정말 최선을 다 했다.</p>
<p>일반적인 웹 서비스였다면 완성도를 더 높일 수 있었는데, 교수님이 머신러닝 관련 기능을 꼭! 넣으라고 하셔서 기능 관련 회의가 길어지는 바람에 정작 개발에 투자할 시간은 부족했던 것 같다. 그래도 Python Tensorflow를 통해 머신러닝을 경험해볼 수 있어서 새로운 도전이었다. 프론트엔드는 마찬가지로 React로 개발했다.</p>
<p>머신러닝과 프론트엔드 개발이 거의 완료되어서 서비스를 릴리즈하고 싶었으나, 안타깝게도 서버 개발이 이루어지지 못했다. 이 서비스는 회사를 다니면서라도 추후에 리팩토링 해보고 싶다.</p>
<h2 id="ice-breaker2109--2112">Ice Breaker(21.09 ~ 21.12)</h2>
<p><strong>한 줄 소개</strong>: <code>비대면 모임을 위한 아이스브레이킹 가이드</code>
소프트웨어공학 팀 프로젝트다. 종합설계는 4학년 과목이어서 그나마 각자의 분야가 존재했는데(웹, 서버, 머신러닝 등), 소프트웨어공학은 3학년 과목이어서 5명 중에 3명이 개발 경험 자체가 전무했다. 다행히 한 분이 HTML+CSS+JS를 하실 줄 알아서 웹 개발을 맡기고, 나는 PM + 서버 개발을 담당했다.</p>
<p>이 수업은 서비스를 개발하는 것보다 <strong>개발 프로세스</strong>를 실제 개발과정에 적용해보는 것이 핵심이다. 따라서 서비스 계획, 요구분석, 설계에 가장 많은 시간을 들였고, 개발 후 테스팅까지 진행하면서 전체 개발 프로세스를 경험하는 것에 만족했다. </p>
<p>생각보다 기획자가 고려해야 하는 사항이 많고, 다양한 방법론이 있다는 것을 알게 되었다. 기획이 정말 쉬운 일이 아닌 것 같다. 그런 의미에서, 기획자와 디자이너가 존재하는 팀에서 개발에만 집중할 수 있는 것이 얼마나 감사한 일인지 알게 되었다.</p>
<p>이 서비스는 서버 개발 및 배포까지 완료되었지만, 프론트엔드는 모든 기능을 구현하지 못했다. 2022년 2월 이후로 React로 다시 개발할 예정이다.</p>
<h2 id="그-외의-사이드-프로젝트">그 외의 사이드 프로젝트</h2>
<ul>
<li>Diary App (SOPT 28기 웹파트 세미나 프로젝트)</li>
<li>Velog Clone (SOPT 29기 웹파트 세미나 프로젝트)</li>
<li>Naver Webtoon Clone (SOPT 29기 웹파트 합동세미나 프로젝트)</li>
<li>melting (SOPT 29기 해커톤 프로젝트 -&gt; 추후 리팩토링 예정)
<del>나열해보니까 다 동아리 활동이네...</del></br>

</li>
</ul>
<h1 id="인턴-및-외주">인턴 및 외주</h1>
<hr>
<h2 id="샤인소프트2107--2108">샤인소프트(21.07 ~ 21.08)</h2>
<p>이전까지는 &quot;내가 과연 개발인턴을 할 수 있을까?&quot; 라는 생각이 들 정도로 개발에 대한 자신감이 많이 부족했다. 하지만 인턴은 직무 경험을 쌓을 수 있을 뿐만 아니라, 내 진로에 대해 더 깊이 고민할 수 있는 계기가 될 것이라고 생각했다. 그래서 현장실습 공고가 뜬 것을 보고 &#39;웹 개발&#39; 직무가 있는 회사에 무작정 지원했다. 그리고 덜컥 합격했다.</p>
<p>앞 뒤 안 재고 지원했더니 출퇴근이 무려 <strong>왕복 5시간</strong>이나 걸렸다. 그래도 2달밖에 안 되는 데다가, 회사 직원분들이 좋고, 내가 얻어가는 경험들이 많을 것 같아서 이왕 이렇게 된 거 열심히 다녔다.</p>
<p>규모가 작은 회사이다보니, 정말 이것저것 많이 가르쳐주셨고, 머신러닝, 백엔드, 프론트엔드 모두 다 경험해봤다. 이전까지 백엔드는 node.js 코드만 읽을 수 있는 정도였는데, spring boot를 사용해서 서버의 처음부터 끝까지 개발했다. 서버에 머신러닝 모델을 불러오기 위해서 DJL(Deep Java Library)를 사용했고, 최종적으로 GUI를 구현하기 위해 Vue를 사용했다.</p>
<p>spring boot, DJL, Vue 모두 회사에서 처음 배웠는데, 나름대로 성과를 인정받아서 정말 뿌듯했다. 회사에서 잘 가르쳐주신 덕분도 있겠지만, 나 스스로도 많이 노력했다. 그리고 이 때 배운 서버개발을 지금까지도 유용하게 활용하고 있어서 조만간 다시 인사드리러 찾아봬야 할 것 같다.</p>
<h2 id="nc-soft-마이크로페이지2109">NC SOFT 마이크로페이지(21.09)</h2>
<p><img src="https://images.velog.io/images/sohee-k/post/42f2951b-ccfc-4f82-8baa-06828b9029d1/20220102_122513.png" alt="NC SOFT">정말 감사하게도 NC SOFT의 2021년 하반기 공개채용 웹페이지를 개발할 기회를 얻게 되었다. 평소에 정말 관심 있는 회사이고, 올해 본격적으로 취준하면서 지원하고 싶은 회사이기 때문에 안 할 이유가 없었다.</p>
<p>빠듯한 일정과 지속적으로 추가되는 변경사항 속에서 개발하는 것은 쉽지 않았다. 하지만 발등에 불이 떨어지니까 자연스럽게 개발 속도가 빨라질 수밖에 없었다. 내가 이렇게 코드를 빨리 칠 수 있었나? 싶을 정도...</p>
<p>누구나 알 법한 큰 기업의 웹페이지를 개발하게 되다니 정말 영광이었고, 특히 SNS 광고에 내가 개발한 마이크로페이지의 링크가 걸리는 경험은 정말 짜릿했다.</p>
<p>하지만 디자인 기한도 빠듯했는지, 모바일웹 디자인이 없어서 내가 알아서 반응형을 구현해야 했고, 여러가지 애니메이션 효과도 온전히 나에게 맡겨졌기 때문에 디테일한 완성도는 조금 부족하지 않았나 싶다.
<br/></p>
<h1 id="개발-동아리-sopt">개발 동아리 (SOPT)</h1>
<hr>
<p><img src="https://images.velog.io/images/sohee-k/post/c1dca768-e8c2-4117-8b3d-ea2896de9c69/27%EB%8C%80.jpeg" alt="">나의 대학생활에서 SOPT는 정말 빼놓고 말할 수 없다. 3학년 마치고 진로에 대한 고민으로 무작정 휴학한 후, 혼자 개발공부를 이어가던 나에게 한 줄기의 빛과 같았다. 웹 파트에 들어와서 진로를 웹 프론트엔드 개발자로 정했고, 팀 단위의 제대로 된 협업을 경험했고, 좋은 동료들을 만났으며, 개발한 서비스를 출시하여 고객들을 만났다.</p>
<p>27기, 28기, 29기 웹 파트에서 활동하면서, 개발자로서 정말 많이 성장했다. 대부분의 개발 경험은 여기에서 나왔다.</p>
<ul>
<li>27기: 본격적인 웹 개발과 협업을 배웠다. JavaScript, React, Redux를 배웠고, 깃허브를 통해 협업하고 기획자/디자이너/서버 개발자와 소통하는 방법을 알게 되었다.</li>
<li>28기: 웹 파트장으로서, 내가 알고 있는 웹 개발 지식을 공유했다. 내가 정확히 알고 있는 지식과 애매하게 알고 있는 지식을 구분하게 되었고, 더 나은 코드를 위해 고민하기 시작했다. 200명 규모의 동아리를 운영하기 위해서는 신경써야 할 일이 정말 많았다. 리크루팅, 커리큘럼 구성, 세미나, 행사 기획 및 진행 등등...</li>
<li>29기: 다양한 스터디에 참여하면서 심화적인 웹 개발 지식(TypeScript, Next, React Query, SWR, GraphQL 등)을 배웠다. OB로서 파트원들을 이끌어나가는 역할에 집중했다.</li>
</ul>
<h1 id="개발-스터디">개발 스터디</h1>
<hr>
<h2 id="알고리즘-스터디-상반기">알고리즘 스터디 (상반기)</h2>
<p>코딩 테스트를 보는 기업이 많다 보니, 알고리즘 공부의 필요성을 느껴서 참여했다. 평소에 백준을 C++로 풀고 있어서 C++ 알고리즘반에 참여했는데, 언어를 떠나서 내가 알고리즘 실력이 부족하다는 사실을 정말 많이 느꼈다. 매주 3개의 알고리즘 문제가 과제로 주어졌는데, 문제 하나 푸는데 3시간이 걸렸다😥 (물론 어려운 문제이긴 했다...)</p>
<p>코테 문제를 풀기 위해 필요한 기본적인 지식들을 공부하고, 관련된 문제들을 풀어볼 수 있어서 좋았다. Bruteforce부터 DFS까지 안 다룬 것이 없고, 일정이 빡빡하긴 했지만 꾸준히 문제를 풀 수 있어서 좋았다!</p>
<p><strong>다룬 내용</strong></p>
<ul>
<li>Bruteforce, Backtracking</li>
<li>Greedy</li>
<li>DP</li>
<li>Sort, Binary Search</li>
<li>BFS, DFS</li>
</ul>
<h2 id="웹-심화-스터디-하반기">웹 심화 스터디 (하반기)</h2>
<p>웹 프론트엔드 분야는 정말 폭넓고 빠르게 변하기 때문에 배워야 할 지식이 넘쳐난다. 웹 파트 세미나에서 다루지 않는 심화적인 지식들을 공부하기 위해 참여했다. 매주 스터디원들이 돌아가면서 한 가지 주제에 대해 발표하는 방식으로 진행했다. </p>
<p>나는 React Query와 GraphQL + Apollo를 담당했다. 이 정도면 주니어 개발자로서 웬만한 내용은 다 다루지 않았을까 싶을 정도로 많은 걸 배웠다. 물론 이론을 실전에 적용하는 것은 다른 문제다.</p>
<p><strong>다룬 내용</strong></p>
<ul>
<li>ESLint + Prettier + StyleLint 초기세팅</li>
<li>Recoil</li>
<li>Babel + Webpack</li>
<li>Next.js</li>
<li>SWR</li>
<li>React Query</li>
<li>Unit Test + CI/CD 구축</li>
<li>Storybook</li>
<li>GraphQL + Apollo</li>
<li>Redux + Redux Toolkit</li>
</ul>
<h2 id="typescript-스터디-하반기">TypeScript 스터디 (하반기)</h2>
<p>JavaScript로 개발하다보면 &quot;대체 왜 null이 나오는 거지?&quot;의 경험을 한 번쯤 해봤을 것이다(일단 나는 그렇다😗). JS 문법 특성상 변수의 타입이 동적인데, 이는 예상치 못한 오류를 낳는다. 따라서 조금 더 깔끔하고 안전한 코드를 작성하기 위해 TypeScript를 배웠다.</p>
<p>사실 TS로 프로젝트를 개발한 경험이 없기 때문에 막연한 두려움이 있었는데(코드를 뜯어봤는데 뭔가 어려워보였다), 웹 프론트엔드 개발자로서 한 단계 성장하기 위해서는 TS가 필수라고 생각했다. TS 스터디는 두 팀으로 나누어져서 격주로 발표를 진행했다.</p>
<p><strong>다룬 내용</strong></p>
<ul>
<li>환경 설정, 기본 타입, 타입 추론</li>
<li>Enum, Union &amp; Intersection, Type Assertion, Type Narrowing, Type Guard</li>
<li>Generic, Utility Type</li>
<li>Function, Class, Template Literal Type</li>
<li>React Hooks, Props, Event</li>
<li>Conditional Type, Type Inference</li>
<li>세미나 과제 TS로 변환 (GitHub Profile Finder, Velog Clone)</li>
</ul>
<h2 id="javascript-알고리즘-스터디-하반기">JavaScript 알고리즘 스터디 (하반기)</h2>
<p>이미 상반기에 알고리즘 스터디에 참여했지만, 막상 웹 프론트엔드 개발자를 준비하다보니 C++보다는 JS 문법이 나에게 더 익숙했다. 그리고 웹 개발자를 뽑을 때 JS로 코딩 테스트를 보기도 해서, 이번에는 JS로 알고리즘 문제를 풀어보고 싶었다. 그래서 스터디를 만들었다.</p>
<p>크게 <code>자료구조</code>, <code>알고리즘</code>, <code>JS 심화문법</code>으로 카테고리를 분류하고, 각 영역별로 다뤄야 할 지식들을 리스트업했다. 코딩 테스트 문제를 풀기 위한 웬만한 지식은 대부분 다뤘다. 그리고 매주 각자 공부하고 싶은 주제를 선택해서 해당 주제에 대한 간단한 발표를 준비했다. 해당 주제와 관련된 <code>알고리즘 문제</code>도 소개하여 함께 풀어보는 시간을 가졌다.</p>
<p>다룬 내용이 워낙 많다보니 전부 소개하기는 어렵지만, 주로 프로그래머스 Level 2~3 정도의 수준을 다뤘다.
<br/></p>
<h1 id="마치며">마치며...</h1>
<hr>
<p>벨로그 첫 글은 가볍게 쓰려고 했는데, 회고를 진행하다보니 쓸 내용이 점점 많아져서 이렇게 길어져버렸다. 2021년은 나름대로 알차게 보낸 것 같아서 정말 뿌듯하다😁 학업과 병행하다보니 개발에 온전히 집중할 수 없었지만, 바쁜 와중에 최선을 다 했다고 생각한다. 2022년은 정말 개발과 취준에만 집중할 생각이다. 올해도 화이팅!!!</p>
]]></description>
        </item>
    </channel>
</rss>