<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jay__ss.log</title>
        <link>https://velog.io/</link>
        <description>😂그냥 직진하는 (예비)개발자</description>
        <lastBuildDate>Sat, 18 May 2024 14:07:56 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>jay__ss.log</title>
            <url>https://images.velog.io/images/jay__ss/profile/5b14eeaf-725c-43b7-8fd2-8450193290dc/brainstorm.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. jay__ss.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jay__ss" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[항해 프론트엔드플러스 후기]]></title>
            <link>https://velog.io/@jay__ss/%ED%95%AD%ED%95%B4-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C%ED%94%8C%EB%9F%AC%EC%8A%A4-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@jay__ss/%ED%95%AD%ED%95%B4-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C%ED%94%8C%EB%9F%AC%EC%8A%A4-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Sat, 18 May 2024 14:07:56 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jay__ss/post/11565e98-8570-409c-9f99-26fa5ed9300a/image.png" alt=""></p>
<blockquote>
<p>항해 플러스 프론트엔드 후기를 작성해보고자 합니다. 약간의 솔직함이 담긴 주관적인 내용이 포함되어있으므로 보시는 분들은 어느정도 정제해서 필요한 부분만 보셔도 좋을 것 같습니다. </p>
</blockquote>
<ol>
<li><p>간단한 자기소개
비전공자 출신으로 국비교육을 통해 개발공부를 시작하였으며, 현재는 SI업체의 프론트엔드 개발자로 재직중이다.(국비교육은 팀스파르타에서 듣지 않았습니다)
약간의솔직함: 솔직히 코딩 교육기관에대한 무조건적인 호감은 없었습니다만, 항해는 나쁘지 않았던것 같습니다.</p>
</li>
<li><p>항해 플러스에 들어오기 전, 프론트엔드 개발자로 일하며 갖고 있던 고민
현재 대한민국 시장에서 프론트엔드 개발자 역량에서 기본이라고 생각되는 Javascript 기본기, Reaact 활용능력이 부족하다고 생각했습니다.(주 스택VueJs)
약간의솔직함: 솔직히 기본기에 대한 탄탐함이 모자르다고 느꼈고, 리액트에 대한 자신감은 0이었습니다.</p>
</li>
<li><p>항해 플러스를 선택하게 된 결정적 계기
비슷한 고민을 가진 동료들은 어떤 고민을 하고있는지를 들으며, 나한테도 적용할만한 내용은 없는지 알 수 있었을 것 같았다.
또한 시니어 레벨급의 멘토진들이 주제를 선정하였기에, 내가 공부할 범위를 확실히 알 수 있어서 좋았고, 과제를 받을 수 있으므로 공부방법에 대한 고민이 줄어들 것이라 생각했다.</p>
</li>
<li><p>항해 플러스 프론트엔드 코스의 장점
첫째로, 아무래도 네트워킹을 무시할 수 없었다. 다른 현업분들의 회사에서의 어려운점도 알 수 있었고, 어떤 고민을 해결하고 싶다라는 구체적인 얘기를 들으면서 나도 이런부분은 채워놓고 있는 것이 좋겠다라는 생각을 하게되었다. 물론 나보다 레벨이 높은사람에게서 배울 수 있는 것이 많겠지만, 동일한 레벨의 분들에게도 영감을 얻는 부분이 많다는 것을 여실히 느꼈습니다.
둘째로는 각 주마다 받게되는 학습 주제였다. 유행에 쫓아가는 주제들로 현혹시켜서 주입시키는 것이 아니라, 실제로 (내가) 선망하는 기업들에서 적용중인 내용들, 해당 기업의 시니어가 생각하는 주니어가 알아야하고 기본이라고 여겨지는 주제들로 선정이 된다. 주제만 정해주지 않으며 기본적인 학습에 필요한 자료들까지 주어지는것이 시간절약에도 도움이 된다.(일과 병행할 때 필수라고 생각한다) 특히 생각나는 주제로는 프론트엔드 TDD였다. 솔직히 화면단 개발자에게 TDD는 좀 안어울린다고 생각하였는데, 실상은 정반대였다. 테스팅 코드를 어느정도 쓸만한 레벨로 작성해둔다면 훨씬 더 프론트엔드 개발자에게도 효율성있게 작업할 수 있다고 생각이 바뀌었다.
약간의솔직함: 이 주제들만 모아놓고 보아도 솔직히 괜찮은 것 같습니다.주제 + 과제 + 남들의 코드 + 답안지 이렇게만 보아도 콘텐츠는 풍부하다고 느껴집니다.</p>
</li>
<li><p>항해 플러스 이전과 이후 어떻게 달라졌나
첫째, 뭘 공부해야할지 알게되었다.
공부해야할 것은 지천에 널렸지만, (내가 가고싶은 기업이 원하는)신입 프론트엔드 개발자가 소양으로 가춰야 할 것은 정해져 있는 것 같습니다.
둘째, 표현이 이상하지만 사내에서의 정치적인 포지셔닝에 대한 스킬을 알게되었다.
코치진분들이나 동료분들에게 얻을 수 있는것은, 프론트엔드 개발자가 가져야할 개발적인 역량뿐만이 아닙니다. 궁금한것은 아무거나 마구 물어보면서 배우게 된 부분입니다.</p>
</li>
<li><p>항해 플러스에 투자한 시간, 돈이 아깝지 않은 이유
10주라는 시간은 생각보다 짧습니다. 시작하기 전에는 10주의 시간이 길다라고 생각했는데, 10주를 학습과 프로젝트와 병행하다보면 금방 지나갑니다. 즉, 몰입도와 응집력이 굉장히 높은 코스라고 생각합니다.
돈을 투자하여서 얻은것은 확실히 다음과 같습니다. &quot;네트워킹&quot;을 얻었다는 것은 생각보다 훨씬 강력한 것 같습니다. 그들에게서 얻는 내용뿐만아니라 같이 함께 힘을 낼 수 있다는 부분에서도 도움이 정말 많이 됩니다.(독려를 받는다는 것이 생각보다 엄청 중요합니다)
한 줄평은, 적절한 시간과 돈의 투자라고 생각하며 인풋과 아웃풋이 비례한다고 생각합니다.</p>
</li>
<li><p>항해 플러스 코스를 고민하고 있는 개발자들에게 한마디
급여로 치자면 1달 분량의 액수를 투자해야 합니다. 그와 더불어 3달이 조금안되는 시간 또한 투자해야 합니다. 위에서 작성했듯이 투자대비 아웃풋은 확실하다고 생각됩니다. 주제도 좋고, 코치진분들도 열정적이시며, 다양한 현업자분들과 만나는 것 또한 인상깊은 활동이 됩니다. 마음의 준비가 되었다면 추천할 수 있을 것 같습니다.
약간의솔직함: 마음의 준비가 된 사람들에게는 추천을 아끼지 않고 싶습니다.</p>
</li>
</ol>
<blockquote>
<p>이외에 궁금한것은 댓글로 연락할만한 수단을 알려주세요.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[mongodb connection error]]></title>
            <link>https://velog.io/@jay__ss/mongodb-connection-error</link>
            <guid>https://velog.io/@jay__ss/mongodb-connection-error</guid>
            <pubDate>Wed, 06 Sep 2023 03:31:39 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>몽고디비와 처음 연결할 때 오류가 발생</p>
</blockquote>
<p>예전에 해놓았는데 오랜만에 연결하려니 에러가 발생(아래와같음)
<code>Error: couldn’t connect to server 127.0.0.1:27017</code></p>
<ol>
<li><p>스택오버플로우와 티스토리 벨로그를 마구 돌아 다닌다</p>
</li>
<li><p><code>find . -name ‘mongod.conf’</code>
로 몽고디비 관련한 파일들이 어디있는지 찾는다</p>
</li>
<li><p><code>vi /opt/homebrew/etc/mongod.conf</code>
를 열어서 문제가 될 코드를 찾는다</p>
</li>
<li><p>해결했다는 코드와 똑같이 되어있는 파일을 보고 좌절한다</p>
</li>
<li><p><code>sudo rm -rf /usr/local/var/mongodb</code>
로 디렉토리를 무자비하게 삭제한다</p>
</li>
<li><p><code>cd /usr/local/var</code>로 간뒤, <code>mkdir mongodb</code>로 새로 만든다</p>
</li>
<li><p>다행히 연결되는 몽고디비를 보며 즐거워하고 벨로그에 정리한다</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[styled-components best practice]]></title>
            <link>https://velog.io/@jay__ss/styled-components-best-practice</link>
            <guid>https://velog.io/@jay__ss/styled-components-best-practice</guid>
            <pubDate>Wed, 16 Aug 2023 08:54:29 GMT</pubDate>
            <description><![CDATA[<p>참고자료
<a href="https://www.robinwieruch.de/styled-components/">스타일드 컴포넌트 베스트 프랙티스</a></p>
<ol>
<li><p>컴포넌트 파일과 스타일 파일을 분리하여 컨트롤 할 수 있다.
엄청 짧은 컴포넌트여서 파일상단에는 컴포넌트를, 하단에는 스타일드 컴포넌트를 배치하는 구조를 가질 수 있지만, 규모가 커지면서 컴포넌트의 개수가 너무 많아지면 혼동이 올 수 있다.</p>
<pre><code class="language-shell">ComponentName
├─index.jsx
└─styles.js</code></pre>
</li>
<li><p>임포트 할때 명명된 애를 가져올수 있지만(<code>import { Headline } from</code>), <code>import * as St</code>, <code>import * as S</code>처럼 컨벤션을 따라 모든걸 가져오는 방법을 택할 수 있다.</p>
</li>
<li><p>모든걸 스타일드 컴포넌트화 할 수 있고,</p>
<pre><code class="language-js">const Section = styled.section`
border-bottom: 1px solid grey;
padding: 20px;
`;
</code></pre>
</li>
</ol>
<p>const Headline = styled.h1<code>color: red;</code>;</p>
<p>const Text = styled.span<code>padding: 10px;</code>;</p>
<p>const Content = ({ title, children }) =&gt; {
  return (
    <Section>
      <Headline>{title}</Headline></p>
<pre><code>  &lt;Text&gt;{children}&lt;/Text&gt;
&lt;/Section&gt;</code></pre><p>  );
};</p>
<pre><code>CSS에 익숙하다면, 아래처럼도 쓸 수 있다.
```js
const Container = styled.section`
  border-bottom: 1px solid grey;
  padding: 20px;

  h1 {
    color: red;
  }

  .text {
    padding: 10px;
  }
`;

const Content = ({ title, children }) =&gt; {
  return (
    &lt;Container&gt;
      &lt;h1&gt;{title}&lt;/h1&gt;

      &lt;span className=&quot;text&quot;&gt;{children}&lt;/span&gt;
    &lt;/Container&gt;
  );
};</code></pre><ol start="4">
<li><p><code>DOM</code>요소에 직접적으로 넘겨줘선 안되는 <code>props</code>는 <code>$</code>를 앞에 붙여준다</p>
</li>
<li><p><code>margin</code>을 위한 컴포넌트를 만들지않고 <code>Spacer</code>라는 컴포넌트 만들어보기(<a href="https://www.joshwcomeau.com/react/modern-spacer-gif/">reference</a>)</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[유데미x스나이퍼팩토리] 10주 완성 프로젝트 캠프 - 디자인 시스템 프로젝트 - Snack Bar 컴포넌트 (3)]]></title>
            <link>https://velog.io/@jay__ss/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-10%EC%A3%BC-%EC%99%84%EC%84%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-%EB%94%94%EC%9E%90%EC%9D%B8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-Snack-Bar-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-3</link>
            <guid>https://velog.io/@jay__ss/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-10%EC%A3%BC-%EC%99%84%EC%84%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-%EB%94%94%EC%9E%90%EC%9D%B8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-Snack-Bar-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-3</guid>
            <pubDate>Wed, 02 Aug 2023 04:57:29 GMT</pubDate>
            <description><![CDATA[<h1 id="첫-구현-코드">첫 구현 코드</h1>
<p><code>setSnackbarMessage</code>로 메세지만 바꿔주면 자동적으로 스낵바가 하단에서 나오는 사용자경험을 가진다</p>
<h2 id="사용부">사용부</h2>
<pre><code class="language-js">import { FoundationStyles, LayoutStyles, TypographyStyles } from &#39;@lib/foundation&#39;;
import { useSnackbar, SnackBar } from &#39;@lib/components/SnackBar&#39;;

const getRandomMsg = () =&gt;
  Array(5)
    .fill(&#39;&#39;)
    .map((_, i) =&gt; `${i + 1}번째 테스트 메세지 입니다`)[Math.floor(Math.random() * 5)];

function App() {
  const [snackbarMessage, setSnackbarMessage] = useSnackbar();

  return (
    &lt;&gt;
      &lt;FoundationStyles /&gt;
      &lt;LayoutStyles size=&#39;tablet&#39; system=&#39;android&#39;&gt;
        &lt;TypographyStyles.Body1&gt;Hello World&lt;/TypographyStyles.Body1&gt;
        &lt;button onClick={() =&gt; setSnackbarMessage(getRandomMsg())}&gt;Open Snackbar&lt;/button&gt;
        &lt;SnackBar message={snackbarMessage} setMessage={setSnackbarMessage} duration={3000} isAppbar={false} /&gt;
      &lt;/LayoutStyles&gt;
    &lt;/&gt;
  );
}

export default App;</code></pre>
<p>특이한 점은 스낵바 컴포넌트를 써야하는 페이지에 스낵바 컴포넌트를 위치시키면서, <code>useSanckbar</code>라는 커스텀 훅을 쓰고 있다는 점이다
그리고 해당 커스텀훅의 리턴값이 스낵바에서 노출시키고 싶은 메세지의 값과, 해당 메세지를 <code>set</code>시키는 <code>setter</code>함수라는 것을 알 수 있다
(일종의 <code>useState</code>의 <code>setXxxx</code>함수)
<code>setSnackbarMessage</code>를 내려준 이유는, 일정시간후에 스낵바 컴포넌트에서 메세지를 빈 문자열 <code>&#39;&#39;</code>로 초기화하여 컴포넌트를 제거해주기 위해서이다</p>
<h2 id="컴포넌트">컴포넌트</h2>
<h3 id="usesnackbarts">useSnackbar.ts</h3>
<pre><code class="language-js">import { useState } from &#39;react&#39;;

type ReturnType = [string, (text: string) =&gt; void];

export const useSnackbar = (): ReturnType =&gt; {
  const [message, setMessage] = useState({ text: &#39;&#39; });

  function setSnackbarMessage(text: string): void {
    setMessage({ text });
  }

  return [message.text, setSnackbarMessage];
};</code></pre>
<p><code>useSnackbar</code>는 그냥 <code>useState</code>를 쓰는것과 비슷하다고 볼 수 있지만 메세지의 타입이 조금 다르다
일반적이라면 <code>string</code>값을 사용할텐데 여기서는 개체안에 <code>text</code>라는 키에 밸류로 저장하고 이를 반환한다</p>
<p>더욱 이상한 점은 불변성을 지키지 않는 형태를 가지는 것이다</p>
<p>이는 내가 의도한 코드인데, 이렇게되면 같은 메세지라 할지라도 다른 상태로 리액트가 인식하여 리렌더링을 유발할 것이다
(예를 들어, 무엇인가를 연속적으로 삭제하여 &#39;삭제되었습니다&#39;라는 알림이 여러번 뜨는것과 같은 상황)</p>
<h3 id="indextsx">index.tsx</h3>
<pre><code class="language-js">export const SnackBar: FC&lt;SnackBarProps&gt; = ({ message, duration, setMessage, isAppbar }) =&gt; {
  useEffect(() =&gt; {
    if (message.length) {
      const timer = setTimeout(() =&gt; {
        setMessage(&#39;&#39;);
      }, duration);

      return () =&gt; {
        clearTimeout(timer);
      };
    }
  }, [message]);

  return (
    &lt;&gt;
      {message.length ? (
        &lt;St.SnackBar key={Math.random()} duration={duration} isAppbar={isAppbar}&gt;
          &lt;Typo.Caption1&gt;{message}&lt;/Typo.Caption1&gt;
        &lt;/St.SnackBar&gt;
      ) : null}
    &lt;/&gt;
  );
};</code></pre>
<p>컴포넌트는 메세지가 빈 문자열이 아니라면 전달받은 <code>duration</code>뒤에 메세지를 빈 문자열로 할당하여 스낵바 컴포넌트를 언마운트 하게된다</p>
<p>또한 <code>useEffect</code>에서 <code>return</code>문을 사용하여 언마운트시에 타이머를 클리어해주는 혹시 모르는상황에도 처리를 해주었다</p>
<h3 id="typets">type.ts</h3>
<p>특별한건 없고 <strong>메세지를 세팅하는 함수의 타입</strong>은 <code>string</code>을 인자로 받아 아무것도 반환하지 않는 <code>void</code>함수로 해주었다</p>
<pre><code class="language-js">type SetMessage = (text: string) =&gt; void;

export type SnackBarProps = {
  message: string;
  duration: number;
  setMessage: SetMessage;
  isAppbar: boolean;
};</code></pre>
<h1 id="구현화면">구현화면</h1>
<p>이렇게 같은 메세지이더라도 렌더링되는 스낵바 컴포넌트를 제작하였다</p>
<p><img src="https://velog.velcdn.com/images/jay__ss/post/c1df3683-9898-4f13-b79b-60af0905c699/image.gif" alt=""></p>
<h1 id="문제점">문제점</h1>
<blockquote>
<p>버튼을 연타하여 5번 스낵바를 오픈했다고 가정해보자
첫번째 스낵바가 오픈이 되면서 컴포넌트 내부에서 타이머가 실행된다
듀레이션이 지나면 메세지를 빈 문자열로 초기화하여 렌더링되던 스낵바가 보이지않게 된다
두번째 오픈이 될 때 해당 첫 타이머가 클리어 되어야 안정적으로 듀레이션동안 알림을 줄 수 있다
문제는 메세지가 바뀜에따라 컴포넌트가 없어졌다가 다시 생기는것이 아니라는 점이다
(없어졌다가 생기는 마법같은 현상이라면 유즈이펙트의 리턴문에서 클리어해주기에 문제가 없다)
하지만 컴포넌트는 그대로 존재하고 메세지만 바뀌기에 첫 타이머의 클리어가 되지않는 문제가 발생한다</p>
</blockquote>
<p>위에서 말했듯이 <code>props</code>로 세터함수를 내려받은 뒤, 타이머를 스낵바 컴포넌트 내부에서 실행시키는 로직은 특수한 상황에서 원하는대로 클리어가 되지않는다</p>
<p>그렇다면 타이머와 클리어 로직은 스낵바 컴포넌트의 외부에서 관리되어야 한다는 의미이고, 이러한 내부 로직을 사용자에게 강제할 수 없으니 <code>useSnackbar</code> 내부로 전부 밀어넣으면 된다고 생각했다</p>
<h2 id="리팩토링-코드">리팩토링 코드</h2>
<h3 id="usesnackbarts-1">useSnackbar.ts</h3>
<p><code>useEffect</code>로 메세지를 감시하고, 클리어 로직을 리턴문에 넣어서 컴포넌트가 사라질 때를 대비하였고, 커런트값을 가진다면(==아직 스낵바가 동작중인데 스낵바가 또 오픈이되는 상황이라면) 클리어 로직을 걸어주었다</p>
<p>이렇게되면 사용부에서는 스낵바의 렌더링 로직은 몰라도되는채로, 듀레이션만 유즈스낵바와 컴포넌트의 프롭스로 동일하게 넣어준다면 원하는 시간에 맞게 스낵바 알림이 뜨는 기능을 구현할 수 있다</p>
<pre><code class="language-js">import { useState, useRef, useEffect } from &#39;react&#39;;

type Timer = ReturnType&lt;typeof setTimeout&gt;;

export const useSnackbar = (ms: number): [string, (text: string) =&gt; void] =&gt; {
  const [message, setMessage] = useState({ text: &#39;&#39; });
  const timer = useRef&lt;Timer&gt;();

  function setSnackbarMessage(text: string): void {
    setMessage({ text });
  }

  useEffect(() =&gt; {
    if (!message.text.length) return;
    if (timer.current) clearTimeout(timer.current);

    timer.current = setTimeout(() =&gt; {
      setSnackbarMessage(&#39;&#39;);
    }, ms);
    return () =&gt; {
      clearTimeout(timer.current);
    };
  }, [message]);

  return [message.text, setSnackbarMessage];
};</code></pre>
<h3 id="indextsx-1">index.tsx</h3>
<p>타이머와 관련된 로직을 전부 제거하고 전달받던 세터함수도 빼주었다</p>
<pre><code class="language-js">import { FC } from &#39;react&#39;;
import { TypographyStyles as Typo } from &#39;@lib/foundation&#39;;
import * as St from &#39;./style&#39;;
import { SnackBarProps } from &#39;./type&#39;;
import { useSnackbar } from &#39;./useSanckbar&#39;;
export { useSnackbar };

export const SnackBar: FC&lt;SnackBarProps&gt; = ({ message, duration, isAppbar, width = &#39;1024px&#39; }) =&gt; {
  return (
    &lt;&gt;
      {message.length ? (
        &lt;St.SnackBar key={Math.random()} duration={duration} isAppbar={isAppbar} width={width}&gt;
          &lt;Typo.Caption1&gt;{message}&lt;/Typo.Caption1&gt;
        &lt;/St.SnackBar&gt;
      ) : null}
    &lt;/&gt;
  );
};</code></pre>
<h3 id="typets-1">type.ts</h3>
<p>너비가 맞지않게 나온다는 의견이 있어, 프롭스로 전달받고 없으면 너비만큼의 기본값을 주는 코드로 변경</p>
<pre><code class="language-js">export type SnackBarProps = {
  message: string;
  duration: number;
  isAppbar: boolean;
  width?: string;
};</code></pre>
<hr>
<p> 본 후기는 유데미-스나이퍼팩토리 10주 완성 프로젝트캠프 학습 일지 후기로 작성 되었습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[유데미x스나이퍼팩토리] 10주 완성 프로젝트 캠프 - 디자인 시스템 프로젝트 (2)]]></title>
            <link>https://velog.io/@jay__ss/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-10%EC%A3%BC-%EC%99%84%EC%84%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-%EB%94%94%EC%9E%90%EC%9D%B8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-2</link>
            <guid>https://velog.io/@jay__ss/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-10%EC%A3%BC-%EC%99%84%EC%84%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-%EB%94%94%EC%9E%90%EC%9D%B8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-2</guid>
            <pubDate>Thu, 20 Jul 2023 11:22:34 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>팀 프로젝트에서 (야매)<code>Typography</code> 시스템을 만들고나서의 회고.</p>
</blockquote>
<h1 id="언어별-폰트를-다르게-렌더한다">언어별 폰트를 다르게 렌더한다</h1>
<blockquote>
<p>디자이너: &quot;한글은 프리텐다드로 해주시고, 나머지는 로보토 폰트로 렌더링 해주세요(개발 부탁해요)&quot;
개발자: &#39;...&#39;</p>
</blockquote>
<p>이제껏 폰트를 깊게 고민하거나, 제대로 된 <code>CSS</code>시스템을 설계해본적이 없는데, 이번 팀 프로젝트에는 두 개의 폰트를 쓰고 있었고 언어별로 다르게 렌더링이 되어야 했다(위와 같은 상황)</p>
<h2 id="font-face의-기본-스펙">@font-face의 기본 스펙</h2>
<pre><code class="language-css">@font-face {
    font-family: &#39;내가 쓸 폰트이름_보통 폰트 홈페이지에 기재되어있다&#39;;
    font-weight: 900;
    font-display: swap;
    src: local(&#39;컴퓨터에 있다면 가져다 쓸 폰트 이름&#39;), 
         url(&#39;../fonts/woff2/Pretendard-Black.woff2&#39;) format(&#39;woff2&#39;), 
         url(&#39;../fonts/woff/Pretendard-Black.woff&#39;) format(&#39;woff&#39;);
    /* 유니코드 레인지로 이 폰트를 지정해줄 범위를 적어준다 */
    unicode-range: U+AC00-D7A3;
}</code></pre>
<blockquote>
<p><strong>쓸만한 범위들</strong>
한글 범위:  U+AC00-D7A3
영어 대문자 범위 :  U+0041-005A
영어 소문자 범위 :   U+0061-007A
숫자 범위 : U+0030-0039
특수 문자: U+0020-002F, U+003A-0040, U+005B-0060, U+007B-007E</p>
</blockquote>
<p>유니코드 레인지는 위의 특수 문자처럼 쉼표로 구분하여 여러가지를 지정할 수 있다</p>
<h1 id="createglobalstyle에-css-넣기">createGlobalStyle에 CSS 넣기</h1>
<blockquote>
<p>&#39;글로벌스타일 내부에서 <code>import</code>하면 끝이지~(아님)&#39;</p>
</blockquote>
<blockquote>
<p>Please do not use @import CSS syntax in createGlobalStyle at this time, as the CSSOM APIs we use in production do not handle it well. Instead, we recommend using a library such as react-helmet to inject a typical <code>&lt;link&gt;</code> meta tag to the stylesheet, or simply embedding it manually in your index.html <code>&lt;head&gt;</code> section for a simpler app.</p>
</blockquote>
<blockquote>
<p><code>import</code>구문 쓰지마라. 우리가 쓰는 프로덕션 과정에서 이거 핸들링하기 어렵다. 이 문구를 보고도 쓰고 싶거든 리액트 헬맷과 같은 라이브러리로 <code>&lt;link&gt;</code>태그 주입해라 아니면 그냥 html에서 link써라...</p>
</blockquote>
<p>그러고 싶지 않은 이유는 단 하나, 고작 폰트 적용시키는데 라이브러리를 또 쓰기는 싫었다</p>
<h2 id="export시키는-createglobalstyle-코드-상단에서-import">export시키는 createGlobalStyle 코드 상단에서 import</h2>
<p>너무 어지러운 찰나에 <code>vite</code> 공식문서를 읽던 중 반가운 부분이 보였다</p>
<blockquote>
<p>페이지 내 CSS 주입 비활성화하기</p>
</blockquote>
<pre><code class="language-js">import &#39;./foo.css&#39; // 페이지에 스타일이 추가됨
import otherStyles from &#39;./bar.css?inline&#39; // 스타일이 추가되지 않음</code></pre>
<p>그렇단 얘기는 <code>css</code>파일은 불러오기만 해도 페이지(혹은 컴포넌트)에 스타일이 주입 된다는 얘긴가?</p>
<blockquote>
<p>그러면 <code>폰트css</code>를 <code>createGlobalStyle</code>코드 상단에서 불러와 글로벌 컴포넌트에 주입시키면 되지 않을까?</p>
</blockquote>
<p>결과적으로는 되더라..</p>
<pre><code class="language-js">// lib/foundation/index.ts
import { createGlobalStyle } from &#39;styled-components&#39;;
import &#39;./Typography/FontFamily/font.css&#39;;

/** Foundation 글로벌 스타일 */
export const FoundationStyles = createGlobalStyle`
  ${ColorStyles}
`;
// App.tsx
import { FoundationStyles, LayoutStyles, TypographyStyles } from &#39;@lib/foundation&#39;;
import &#39;./App.css&#39;;

function App() {
  return (
    &lt;&gt;
      &lt;FoundationStyles /&gt;
      &lt;LayoutStyles size=&#39;tablet&#39; system=&#39;android&#39;&gt;
        &lt;TypographyStyles.Body1 as={&#39;h2&#39;}&gt;Hello World&lt;/TypographyStyles.Body1&gt;
      &lt;/LayoutStyles&gt;
    &lt;/&gt;
  );
}

export default App;</code></pre>
<p>무사히 한글은 프리텐다드 폰트가, 그외에는 로보토가 적용이 된걸 확인했다</p>
<h1 id="git은-항상-복잡하다">git은 항상 복잡하다</h1>
<ol>
<li><code>git remote update</code>
모든 원격 브랜치를 업데이트하여 최신 상태로 갱신한다. 하지만, 로컬에서 변동 사항을 병합하지는 않는다.</li>
<li><code>git fetch</code>
현재 위치하고 있는 원격 브랜치만 업데이트한다. 하지만, 로컬에서 변동 사항을 병합하지는 않는다.
all 옵션을 주면 모든 원격 브랜치를 업데이트할 수 있다.</li>
<li><code>git pull</code>
현재 위치하고 있는 원격 브랜치를 업데이트하고 로컬에서 변동 사항을 병합한다.
로컬 브랜치를 업데이트할 때 사용한다.</li>
</ol>
<h1 id="기본적으로-텍스트-콘텐츠-태그와-스타일을-지정하고-개발자가-원하는-태그를-동적으로-렌더한다">기본적으로 텍스트 콘텐츠 태그와 스타일을 지정하고, 개발자가 원하는 태그를 동적으로 렌더한다</h1>
<blockquote>
<p>왜 이런 고민을 하게 된거지?</p>
</blockquote>
<p>분명 나는 픽셀과 줄높이, 폰트두께를 디자인 시스템에서 정의된 스타일을 지정하고 싶은데 태그가 하나로 고정이 되어있다면 의미있는 <code>html</code>구조가 아니게된다</p>
<p>그렇다고 생성할 때 마다 태그를 입력 받는것은 개발자에게 너무 귀찮은 일이다</p>
<p>어느정도 의미를 고려한 태그를 기본 제공하되, 스타일만 가져다쓰고 태그는 다른 태그를 쓰고 싶을 때는 <code>styled-components</code>의 <code>as</code>라는 <code>prop</code>을 쓰면 된다</p>
<h2 id="styled-components의-기본-스펙">Styled-components의 기본 스펙</h2>
<pre><code class="language-js">import styled from &quot;styled-components&quot;;

const Component = styled.div`
  color: red;
`;

function App() {
  // ...codes...
  return (
      &lt;&gt;
        &lt;Component as=&quot;button&quot; onClick={() =&gt; alert(&#39;It works!&#39;)}&gt;
            Hello World!
        &lt;/Component&gt;        
    &lt;/&gt;
  )
}</code></pre>
<p>분명 스타일드 컴포넌트로 <code>div</code>태그를 생성했지만, 실제로는 <code>as</code>로 넘겨준 값인 <code>button</code>이 렌더링 된다</p>
<blockquote>
<p>출처 - 스타일드 컴포넌트 공식 문서(API reference)
<a href="https://styled-components.com/docs/api#as-polymorphic-prop">styled-components <code>as</code></a></p>
</blockquote>
<hr>
<p>본 후기는 유데미-스나이퍼팩토리 10주 완성 프로젝트캠프 학습 일지 후기로 작성 되었습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[유데미x스나이퍼팩토리] 10주 완성 프로젝트 캠프 - 디자인 시스템 프로젝트 (1)]]></title>
            <link>https://velog.io/@jay__ss/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-10%EC%A3%BC-%EC%99%84%EC%84%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-1%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@jay__ss/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-10%EC%A3%BC-%EC%99%84%EC%84%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-1%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Tue, 18 Jul 2023 05:27:08 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>프로젝트를 진행 할 기업과 과제가 정해졌으니 이제부터는 팀 별로 개발을 시작하면 된다
무작정 개발에 돌입하는 것이 아니라, 형식을 맞추어 작업하면 일관되게 진행할 수 있어 여러가지 이점이 있다</p>
</blockquote>
<h1 id="팀-문화-정하기">팀 문화 정하기</h1>
<p>팀원으로서 여러가지 행동양식등을 정했다
단체활동에 불이익을 주지 않는 범위내에서 지킬 양식들이라 어려운 점은 없었다</p>
<ul>
<li>코어타임 정하기</li>
<li>공지사항에 대한 행동규칙 정하기(수시로 확인 및 확인을 알리는 방식)</li>
<li>회의는 짧게</li>
</ul>
<h1 id="컨벤션-정하기">컨벤션 정하기</h1>
<p>여럿이서 공동의 프로젝트를 진행을 하다보면 작성자에 따라서 코드나 네이밍, 폴더 구조 등에서 불규칙한 부분을 찾기 쉽다
이러한 불규칙을 최대한 줄이고, 타인(팀원)이 보아도 이해가 될 수 있도록 컨벤션(일종의 규칙)을 정하는 것은 매우 중요하다</p>
<p>우리가 정한 컨벤션은 다음과 같다</p>
<ul>
<li>코드 컨벤션</li>
<li>네이밍 컨벤션</li>
<li>디렉토리 구조</li>
</ul>
<h1 id="git-협업-흐름-정하기">Git 협업 흐름 정하기</h1>
<p>최상단 루트에 <code>.github</code> 디렉토리를 생성한다(깃허브 관련 시스템 파일을 넣을 폴더, 앞에 <code>.</code>이 붙었다는건 그런 의미)
해당 디렉토리에 <code>ISSUE_TEMPLATE</code> 디렉토리를 만들고 <code>만드려는제목.md</code> 파일을 만든다
해당 파일에 공동 문서 서식을 마크다운 문법에 맞추어 적는다</p>
<p>일련의 과정을 거치면 자동적으로 해당 레포에 이슈 템플릿이 자동적으로 만들어진다</p>
<p><img src="https://velog.velcdn.com/images/jay__ss/post/cd2b4abe-4df7-4a84-bc45-c44c5658339e/image.png" alt="">
<code>이슈</code>탭에서 <code>New Issue</code>를 생성한다
<img src="https://velog.velcdn.com/images/jay__ss/post/fedeb4ba-7bc3-49ad-b70f-ad9c52708754/image.png" alt="">
화면이 전환되고 만들어놓은 템플릿 리스트들이 보인다(이 화면단에서도 생성 가능)
<img src="https://velog.velcdn.com/images/jay__ss/post/ecc0451f-ea36-4097-8f73-5c20a229b507/image.png" alt="">
마크다운 서식에 맞게 문서를 작성하고, 오른쪽에 할당자, 라벨, 진행상황, 마일스톤을 설정하고 이슈를 생성한다</p>
<h1 id="초기-개발-환경">초기 개발 환경</h1>
<p>Vite + Typescript + React + styled-components로 초기 세팅을 하였고, 필요에 따라 라이브러리 추가 예정이다</p>
<h1 id="figma-분석-및-정리">Figma 분석 및 정리</h1>
<p>주어진 Figma를 보고 만들어야 할 디자인 시스템이 어떤 것인지 파악하고, 분류를 나누어 구현해야 할 리스트를 작성했다</p>
<hr>
<p>본 후기는 유데미-스나이퍼팩토리 10주 완성 프로젝트캠프 학습 일지 후기로 작성 되었습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[유데미x스나이퍼팩토리] 10주 완성 프로젝트 캠프 - React Movie Web(2)]]></title>
            <link>https://velog.io/@jay__ss/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-10%EC%A3%BC-%EC%99%84%EC%84%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-React-Movie-Web2</link>
            <guid>https://velog.io/@jay__ss/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-10%EC%A3%BC-%EC%99%84%EC%84%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-React-Movie-Web2</guid>
            <pubDate>Tue, 04 Jul 2023 14:57:13 GMT</pubDate>
            <description><![CDATA[<h1 id="과제">과제</h1>
<h2 id="요구사항">요구사항</h2>
<blockquote>
<p>무비앱을 리덕스를 사용하여 데이터 흐름 관리해보기</p>
</blockquote>
<h2 id="구조">구조</h2>
<pre><code>movie-app
├─ env
│  └─ .env
├─ src
│  ├─ index.css
│  ├─ main.jsx
│  ├─ App.css
│  ├─ App.jsx
│  ├─ routes.jsx
│  ├─ components
│  │  ├─ Gnb.jsx
│  │  ├─ Pagination.jsx
│  │  └─ movies
│  │     ├─ detail.jsx
│  │     └─ item.jsx
│  ├─ hooks
│  │  ├─ useFetchMovies.js
│  │  └─ usePagination.js
│  ├─ pages
│  │  ├─ index.jsx
│  │  ├─ cart
│  │  └─ movies
│  │     ├─ [id].jsx
│  │     └─ index.jsx
│  └─ store
│     ├─ index.js
│     └─ modules
│        └─ favoriteMovie.js
├─ index.html
├─ .eslintrc.cjs
├─ .gitignore
├─ .prettierrc.json
├─ package-lock.json
├─ package.json
└─ vite.config.js</code></pre><p><code>리덕스</code>와 관련해서 보아야 할 폴더구조는 <code>store</code>이다</p>
<p>당연히 하나의 앱에는 하나의 스토어만이 존재해야 하지만, 데이터의 특성에 따라 나누고 끌어다가 쓸 분리가 필요하다고 생각하여 <code>modules</code>폴더를 두었다
(사실 당장에는 즐겨찾기 하는 저장소만이 필요해서 이런 분리가 반드시 필요했냐고 묻는다면 대답은 <code>No</code>이다_그러나 미래를 위해서)</p>
<p>이 모듈들을 <code>index.js</code>에서 끌어다가 <code>createStore</code>를 해주면 된다</p>
<h2 id="코드">코드</h2>
<pre><code class="language-js">// src/store/index.js
import { configureStore } from &#39;@reduxjs/toolkit&#39;;
import favoriteMovieReducer from &#39;./modules/favoriteMovie&#39;;

const store = configureStore({
    reducer: {
        favoriteMovie: favoriteMovieReducer,
    },
});

export default store;</code></pre>
<p><code>configureStore</code>에는 <code>reducer</code>뿐만 아니라 <code>middleware</code>도 설정 가능하다</p>
<pre><code class="language-js">// src/store/modules/favoriteMovie.js
import { createSlice } from &#39;@reduxjs/toolkit&#39;;

export const favoriteMovie = createSlice({
    // action.type에 아래의 name + &#39;/addFavorite&#39; 요런식으로 붙어서 실행 됨
    name: &#39;favoriteMovie&#39;,
    initialState: {
        list: [],
    },
    reducers: {
        addFavorite: (state, action) =&gt; {
            console.log(action);
            state.list.push(action.payload);
        },
        deleteFavorite: (state, action) =&gt; {
            state.list = state.list.filter(movie =&gt; movie.id !== action.payload);
        },
    },
});

export const { addFavorite, deleteFavorite } = favoriteMovie.actions;

// 여기서의 state.favoriteMovie의 favoriteMovie index에서 createStore에서 설정
export const selectFavoriteMovie = state =&gt; state.favoriteMovie.list;

export default favoriteMovie.reducer;</code></pre>
<p><code>redux-toolkit</code>의 미친점(?)은 <code>createSlice</code>의 내부 코드에 <code>immer</code> 라이브러리를 사용하여서 불변성을 지키면서 코드를 작성하지 않아도 된다</p>
<blockquote>
<p>Remember: reducer functions must always create new state values immutably, by making copies! It&#39;s safe to call mutating functions like Array.push() or modify object fields like state.someField = someValue inside of createSlice(), because it converts those mutations into safe immutable updates internally using the Immer library, but don&#39;t try to mutate any data outside of createSlice!
출처 - 리덕스 공식문서</p>
</blockquote>
<pre><code class="language-js">// src/pages/movies/[id].jsx
import { useParams } from &#39;react-router-dom&#39;;
import { useSelector, useDispatch } from &#39;react-redux&#39;;
import { addFavorite, deleteFavorite, selectFavoriteMovie } from &#39;../../store/modules/favoriteMovie&#39;;
// Hook
import useFetchMovies from &#39;../../hooks/useFetchMovies&#39;;
// Component
import MovieDetail from &#39;../../components/movies/detail&#39;;

const MovieDetailPage = () =&gt; {
    // routes
    const { id } = useParams();
    // redux
    const favoriteList = useSelector(selectFavoriteMovie);
    const dispatch = useDispatch();
    // TODO: delete line when deploy
    console.log(favoriteList);

    const { data, isLoading, error } = useFetchMovies(&#39;/movie_details.json&#39;, `movie_id=${id}`);

    if (isLoading) return &lt;p&gt;Loading...&lt;/p&gt;;
    if (error) console.error(error);

    return (
        &lt;&gt;
            {data ? (
                &lt;&gt;
                    &lt;MovieDetail
                        item={data?.movie}
                        dispatch={dispatch}
                        addFavorite={addFavorite}
                        deleteFavorite={deleteFavorite}
                    /&gt;
                &lt;/&gt;
            ) : null}
        &lt;/&gt;
    );
};

export default MovieDetailPage;</code></pre>
<p><code>selectFavoriteMovie</code>를 모듈에서 가져와서 <code>react-redux</code>의 <code>useSelector</code>에 넣어준다
<code>dispatch</code>를 발생시키려면 <code>react-redux</code>의 <code>useDispatch()</code>를 통해 사용가능하다</p>
<pre><code class="language-js">// src/components/movies/detail.jsx
const MovieDetail = ({ item, dispatch, addFavorite, deleteFavorite }) =&gt; {
    return (
        &lt;div&gt;
            &lt;img src={item.background_image_original} alt={`${item.background_image_original}의배경화면`} /&gt;
            &lt;h3&gt;{item.title}&lt;/h3&gt;
            &lt;p&gt;{item.year}&lt;/p&gt;
            &lt;p&gt;{item.description_full}&lt;/p&gt;
            &lt;p&gt;{item.rating}&lt;/p&gt;
            &lt;p&gt;{item.runtime}&lt;/p&gt;
            &lt;img src={item.large_cover_image} alt={`${item.large_cover_image}의커버이미지`} /&gt;
            &lt;button
                onClick={() =&gt; {
                    dispatch(addFavorite(item));
                }}&gt;
                Add favorite
            &lt;/button&gt;
            &lt;button
                onClick={() =&gt; {
                    dispatch(deleteFavorite(item.id));
                }}&gt;
                Delete favorite
            &lt;/button&gt;
        &lt;/div&gt;
    );
};

export default MovieDetail;</code></pre>
<hr>
<p>본 후기는 유데미-스나이퍼팩토리 10주 완성 프로젝트캠프 학습 일지 후기로 작성 되었습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[유데미x스나이퍼팩토리] 10주 완성 프로젝트 캠프 - React Movie Web]]></title>
            <link>https://velog.io/@jay__ss/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-10%EC%A3%BC-%EC%99%84%EC%84%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-React-Movie-Web</link>
            <guid>https://velog.io/@jay__ss/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-10%EC%A3%BC-%EC%99%84%EC%84%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-React-Movie-Web</guid>
            <pubDate>Sun, 02 Jul 2023 14:34:31 GMT</pubDate>
            <description><![CDATA[<h1 id="과제">과제</h1>
<h2 id="요구사항">요구사항</h2>
<blockquote>
<p>다양한 커스텀 훅(Custom Hook)을 설계, 구현 및 문서화 할 것</p>
</blockquote>
<ol>
<li><code>useFetchMovies</code><ul>
<li>영화 API에 HTTP 요청을 보내고,응답을 받아와서 영화데이터를 처리</li>
<li>반환값으로는 로딩상태,영화데이터,에러 정보등을 포함하는 객체를 반환</li>
</ul>
</li>
<li><code>useFavoriteMovies</code><ul>
<li>사용자가 영화를 즐겨찾기에 추가하거나 제거할 수 있는 함수를 제공</li>
<li>반환값 자유</li>
</ul>
</li>
<li><code>useSearchMovies</code><ul>
<li>사용자가 입력한 검색어로 영화를 검색하고, 검색결과를 처리</li>
<li>반환값 자유</li>
</ul>
</li>
<li><code>usePagination</code><ul>
<li>사용자가 다른페이지로 이동할 수 있도록 페이지번호를 관리</li>
<li>반환값 자유</li>
</ul>
</li>
</ol>
<h2 id="커스텀-훅custom-hook">커스텀 훅(Custom Hook)</h2>
<p>커스텀 훅을 만들기전에 의미를 알아보자</p>
<p>커스텀: 관습이라는 사전적 의미이지만, 여기서는 본인의 상황에 맞는 개인 맞춤 정도로 보면 된다
훅: 함수라고 보면 된다(<code>useXxxx</code>)</p>
<p>즉, 개발하는데에 있어 여기저기서 <strong>공통적으로</strong> 쓰이는 코드를 맞춤으로 설계하여 반복작업을 줄이는 행위이다</p>
<h2 id="코드">코드</h2>
<h3 id="usefetchmovies">useFetchMovies</h3>
<p>보통의 프론트 작업에서 데이터를 가져와야 하는 경우, </p>
<blockquote>
<ol>
<li><code>useEffect</code>에서 빈배열을 두어, 초기렌더링 시 데이터를 요청</li>
<li>응답이 올 때 까지 로딩화면 표시</li>
<li>제대로 데이터를 가져올 경우, 데이터를 렌더링</li>
<li>에러발생의 경우 후속처리</li>
</ol>
</blockquote>
<p>위의 작업을 반복적으로 하게 된다
이와 같은 반복을 줄이기 위해서는 커스텀 훅을 만드는 것이 좋다</p>
<pre><code class="language-js">// src/hook/useFetchMovies.js
import { useEffect, useState } from &quot;react&quot;;

const BASE_URL = &quot;https://yts.mx/api/v2&quot;;

const useFetchMovies = (path, params) =&gt; {
  const [isLoading, setIsLoading] = useState(false);
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() =&gt; {
    setIsLoading(true);
    let url = `${BASE_URL}${path}`;
    if (params) {
      const searchParams = new URLSearchParams(params);
      url += &quot;?&quot; + searchParams.toString();
    }
    fetch(url, { method: &quot;GET&quot; })
      .then((res) =&gt; res.json())
      .then((json) =&gt; {
        // console.log(json.data);
        setData(json.data);
      })
      .catch((err) =&gt; setError(err))
      .finally(() =&gt; {
        setIsLoading(false);
      });
  }, [path, params]);

  return { data, error, isLoading };
};

export default useFetchMovies;</code></pre>
<p><code>path</code>: 베이스 url에다가 요청을 보낼 경로(리스트면 리스트, 디테일이면 디테일)
<code>params</code>: 요청 시, 물음표(?) 뒤에 오는 간단한 정보들을 담는다(보통 어떤 아이디, 갯수 등)</p>
<p>현재는 <code>GET</code>요청에 한해 돌아가는 코드를 구현해보았다
더욱 살을 붙여 다른 요청에도 방어가 가능한 코드로 발전시키면 좋을 것 같다</p>
<pre><code class="language-js">// App.js
const { data, isLoading, error } = useFetchMovies(
  &quot;/list_movies.json&quot;,
  `minimum_rating=7.0&amp;sort_by=year&amp;page=${temp.current}`
);</code></pre>
<h3 id="usepagination">usePagination</h3>
<p>게시판등에서, 총게시물이 너무 많을 경우 infinite scroll방식을 채택할 수 있지만 전통적으로는 페이지네이션을 많이 쓴다</p>
<p>이 기능을 구현하려면, 얼마나 많은 데이터수를 가지고 있는지와 한 화면에 뿌려줄 데이터 갯수를 바탕으로 페이지네이션을 구현해야 한다</p>
<pre><code class="language-js">// src/hook/usePagination.js
import { useState } from &quot;react&quot;;

const usePagination = (totalCount = 1, itemsPerPage = 1) =&gt; {
  const [currentPage, setCurrentPage] = useState(1);
  const maxPage = Math.ceil(totalCount / itemsPerPage);
  const next = () =&gt; {
    setCurrentPage((currentPage) =&gt; Math.min(currentPage + 1, maxPage));
  };
  const prev = () =&gt; {
    setCurrentPage((currentPage) =&gt; Math.max(currentPage - 1, 1));
  };

  const jump = (page) =&gt; {
    const pageNumber = Math.max(1, page);
    setCurrentPage(Math.min(pageNumber, maxPage));
  };
  return { next, prev, jump, currentPage, maxPage };
};
export default usePagination;</code></pre>
<p>총 데이터 갯수를 페이지당 렌더링할 갯수로 나눈 값을 올림 하면 총 페이지의 개수가 된다
또한 페이지 이동의 경우, <code>useState</code>로 선언한 <code>currentPage</code>를 컨트롤 해주면 된다</p>
<pre><code class="language-js">// src/components/Pagination.js
const Pagination = ({ next, prev, jump, currentPage, maxPage }) =&gt; {
  const computedStyle = (page) =&gt; {
    if (page === currentPage) {
      return {
        color: &quot;red&quot;,
      };
    }
  };
  const computedStartPages = () =&gt; {
    if (currentPage &gt; 5) {
      return Math.floor((currentPage - 1) / 5) * 5;
    }
    return 0;
  };

  const computedEndPages = () =&gt; {
    if (currentPage &gt; 5) {
      return (Math.floor((currentPage - 1) / 5) + 1) * 5;
    }
    return 5;
  };

  return (
    &lt;PaginationWrapper&gt;
      &lt;button onClick={() =&gt; prev()}&gt;prev&lt;/button&gt;
      {Array(maxPage)
        .fill()
        .map((_, idx) =&gt; idx + 1)
        .slice(computedStartPages(), computedEndPages())
        .map((v) =&gt; (
          &lt;button style={computedStyle(v)} key={v} onClick={() =&gt; jump(v)}&gt;
            {v}
          &lt;/button&gt;
        ))}
      &lt;button onClick={() =&gt; next()}&gt;next&lt;/button&gt;
    &lt;/PaginationWrapper&gt;
  );
};
export default Pagination;
</code></pre>
<h2 id="회고라기-보다는-반성">회고(라기 보다는 반성)</h2>
<p>현재 익숙하지 않은 리액트의 개발을 하려다 보니 과제에 미흡한 점이 많이 있다
추후에 보강하여 개발을 해두어야겠다</p>
<hr>
<p>본 후기는 유데미-스나이퍼팩토리 10주 완성 프로젝트캠프 학습 일지 후기로 작성 되었습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[유데미x스나이퍼팩토리] 10주 완성 프로젝트 캠프 9일차 - React web toy(1)]]></title>
            <link>https://velog.io/@jay__ss/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-10%EC%A3%BC-%EC%99%84%EC%84%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-9%EC%9D%BC%EC%B0%A8-React-web-toy1</link>
            <guid>https://velog.io/@jay__ss/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-10%EC%A3%BC-%EC%99%84%EC%84%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-9%EC%9D%BC%EC%B0%A8-React-web-toy1</guid>
            <pubDate>Sun, 25 Jun 2023 13:56:19 GMT</pubDate>
            <description><![CDATA[<h1 id="과제">과제</h1>
<h2 id="요구사항">요구사항</h2>
<blockquote>
<p>React를 이용하여 배경이미지 랜덤 변경, 인사, 시계 만들기</p>
</blockquote>
<p>기존의 <code>Javascript</code>를 이용해서 만들었던 프로젝트를, React로 포팅하는 과제이다
아마도 이를 수행하면 리액트의 장점과 왜 쓰는지에 대한 아이디어가 생길 것 같다</p>
<h2 id="코드">코드</h2>
<h3 id="구조">구조</h3>
<p>루트 디렉토리(최상단)에는 <code>index.html</code>이나 환경 설정에 관한 파일들이 몰려있고, 하위에 <code>/env</code>, <code>/src</code>폴더가 위치한다
<code>/env</code>에는 숨기고 싶은 변수들을 담는다(<code>API_KEY</code>와 같은)
<code>/src</code>에는 리액트 파일들(진입점인 <code>main.jsx</code>, 앱의 전체를 담당하는 <code>App.jsx</code>)과 <code>/assets</code>(이미지)들이 들어있다</p>
<pre><code>react
├─ env
│  └─ .env
├─ src
│  ├─ App.jsx
│  ├─ Clock.jsx
│  ├─ Greeting.jsx
│  ├─ TodoList.jsx
│  ├─ Weather.jsx
│  ├─ assets
│  │  ├─ bg-bicycle.jpeg
│  │  ├─ bg-bunny.jpeg
│  │  ├─ bg-forest.jpeg
│  │  ├─ bg-japan.jpeg
│  │  ├─ bg-ocean.jpg
│  │  └─ bg-wall.jpeg
│  ├─ index.css
│  ├─ main.jsx
│  └─ reset.css
├─ index.html
├─ package-lock.json
├─ package.json
├─ .eslintrc.cjs
├─ .gitignore
├─ README.md
└─ vite.config.js
</code></pre><h3 id="배경이미지">배경이미지</h3>
<p><code>imgPath</code>들을 담은 배열을 정적인 변수로 선언하고, 앱에 접근할 때 <code>index</code>를 랜덤으로 뽑아주면 배경이미지가 새로고침(혹은 접속)할 때마다 바뀌게 된다</p>
<p><code>useState</code>를 사용하지 않은 이유는, 앱의 구동중에 해당 상태를 변경할 일이 없을 것 같아서 <code>useRef</code>를 사용했다</p>
<pre><code class="language-js">// App.jsx
import { useRef } from &quot;react&quot;;
import Weather from &quot;./Weather&quot;;
import Clock from &quot;./Clock&quot;;
import Greeting from &quot;./Greeting&quot;;
import TodoList from &quot;./TodoList&quot;;

const IMG_PATHS = [
  &quot;src/assets/bg-bicycle.jpeg&quot;,
  &quot;src/assets/bg-bunny.jpeg&quot;,
  &quot;src/assets/bg-forest.jpeg&quot;,
  &quot;src/assets/bg-japan.jpeg&quot;,
  &quot;src/assets/bg-ocean.jpg&quot;,
  &quot;src/assets/bg-wall.jpeg&quot;,
];

function App() {
  const imgPathIdx = useRef(Math.floor(Math.random() * IMG_PATHS.length));

  return (
    &lt;div
      style={{
        backgroundImage: `linear-gradient(rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0.75)), url(${
          IMG_PATHS[imgPathIdx.current]
        })`,
      }}
    &gt;
      &lt;Weather /&gt;
      &lt;Clock /&gt;
      &lt;Greeting /&gt;
      &lt;TodoList /&gt;
    &lt;/div&gt;
  );
}

export default App;</code></pre>
<h3 id="인사">인사</h3>
<ul>
<li>렌더링 되어지는 <code>userName</code>이라는 변수를 <code>useState</code>로 선언하고 초기값을 로컬스토리지로부터 가져온다</li>
<li>만약 그 값이 없다면 <code>form</code>을 보여주고 값을 입력받는다<ul>
<li>동적으로 <code>className</code>을 붙여주어서 디스플레이 여부를 제어한다</li>
</ul>
</li>
<li><code>userName</code>이 갱신되면 인사말이 보여진다</li>
</ul>
<blockquote>
<p><code>input</code>에서 <code>onChange</code>로 상태를 키보드를 입력할 때마다 갱신해주지 않은 이유는 <code>useState</code>의 상태가 변경되면 그 때마다 리렌더링이 발생하기 때문에 <code>form</code>을 입력하고 전송할때에만 리렌더링을 주고싶어서 <code>input</code>을 <code>useRef</code>로 접근해보았다(순수 <code>Javascript</code>로 <code>DOM</code>에 접근하듯이)</p>
</blockquote>
<pre><code class="language-js">// Greeting.jsx
import { useRef, useState } from &quot;react&quot;;

const Greeting = () =&gt; {
  const [userName, setUserName] = useState(localStorage.getItem(&quot;userName&quot;));
  const userNameInp = useRef(&quot;&quot;);

  const onSubmitUserName = (e) =&gt; {
    e.preventDefault();
    const enteredUserName = userNameInp.current.value.trim();
    console.log(enteredUserName.length);
    if (enteredUserName.length &gt; 1) {
      localStorage.setItem(&quot;userName&quot;, userNameInp.current.value);
      setUserName(enteredUserName);
      userNameInp.current.value = &quot;&quot;;
    } else {
      alert(&quot;두 글자 이상의 성함을 입력해주세요(공백제외)&quot;);
    }
  };

  return (
    &lt;section id=&quot;greeting&quot;&gt;
      &lt;form
        className={&quot;login-box&quot; + (userName ? &quot; hide&quot; : &quot; &quot;)}
        onSubmit={onSubmitUserName}
      &gt;
        &lt;div className=&quot;input-box&quot;&gt;
          &lt;input type=&quot;text&quot; id=&quot;userName&quot; ref={userNameInp} /&gt;
          &lt;label htmlFor=&quot;userName&quot;&gt;이름을 입력해주세요&lt;/label&gt;
        &lt;/div&gt;
      &lt;/form&gt;
      {userName ? &lt;h1&gt;{userName}님 안녕하세요!&lt;/h1&gt; : null}
    &lt;/section&gt;
  );
};
export default Greeting;</code></pre>
<h3 id="시계">시계</h3>
<p><code>now</code>라는 현재 시간을 담을 변수를 <code>useState</code>로 선언하고 1초마다 갱신 해준다
<code>useEffect</code>에서 <code>dep array</code>에 <code>now</code>를 담아서 변할때마다 내부 함수를 실행시킨다
<code>useEffect</code>내부에서는 <code>setInterval</code>로 <code>now</code>를 변동시킨다</p>
<blockquote>
<p><code>useEffect</code> 내부에서의 <code>return</code>이 없다면?
리렌더링 될 때, <code>setInterval</code> 작동을 클리어 시키지 않으므로, <code>setInterval</code>이 n번식 호출된다</p>
</blockquote>
<pre><code class="language-js">// Clock.jsx
import { useEffect, useRef, useState } from &quot;react&quot;;

const Clock = () =&gt; {
  const [now, setNow] = useState(new Date());
  const interval = useRef(null);
  useEffect(() =&gt; {
    // console.log(now);
    interval.current = setInterval(() =&gt; {
      setNow(new Date());
    }, 1000);
    return () =&gt; {
      clearInterval(interval.current);
    };
  }, [now]);
  return (
    &lt;section id=&quot;clock&quot;&gt;
      &lt;h2&gt;{now.toLocaleTimeString()}&lt;/h2&gt;
    &lt;/section&gt;
  );
};

export default Clock;</code></pre>
<h2 id="결과">결과</h2>
<p><img src="https://velog.velcdn.com/images/jay__ss/post/b08d38b9-a234-46d8-9ab2-69d4b2ad3c67/image.gif" alt="">
<img src="https://velog.velcdn.com/images/jay__ss/post/89c97840-e81c-42f6-99ff-807081acdf30/image.gif" alt=""></p>
<hr>
<p>본 후기는 유데미-스나이퍼팩토리 10주 완성 프로젝트캠프 학습 일지 후기로 작성 되었습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[유데미x스나이퍼팩토리] 10주 완성 프로젝트 캠프 8일차 - vanilla JS web toy(완성)]]></title>
            <link>https://velog.io/@jay__ss/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-10%EC%A3%BC-%EC%99%84%EC%84%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-8%EC%9D%BC%EC%B0%A8-vanilla-JS-web-toy%EC%99%84%EC%84%B1</link>
            <guid>https://velog.io/@jay__ss/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-10%EC%A3%BC-%EC%99%84%EC%84%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-8%EC%9D%BC%EC%B0%A8-vanilla-JS-web-toy%EC%99%84%EC%84%B1</guid>
            <pubDate>Thu, 22 Jun 2023 07:01:42 GMT</pubDate>
            <description><![CDATA[<h1 id="과제">과제</h1>
<h2 id="요구사항">요구사항</h2>
<blockquote>
<p>vanilla js로 할 일 목록 만들기
vanilla js로 내 위치의 날씨 만들기</p>
</blockquote>
<h2 id="코드">코드</h2>
<h3 id="전체-html">전체 HTML</h3>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;ko&quot;&gt;
  &lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot; /&gt;
    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot; /&gt;
    &lt;title&gt;Hello World?&lt;/title&gt;
    &lt;link rel=&quot;stylesheet&quot; href=&quot;./style/reset.css&quot; /&gt;
    &lt;link rel=&quot;stylesheet&quot; href=&quot;./js-assignment-css.css&quot; /&gt;
    &lt;script defer src=&quot;./js-assignment-js.js&quot;&gt;&lt;/script&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;div id=&quot;app&quot;&gt;
      &lt;!-- weather --&gt;
      &lt;section id=&quot;weather&quot;&gt;&lt;/section&gt;
      &lt;!-- clock --&gt;
      &lt;section id=&quot;clock&quot;&gt;&lt;/section&gt;
      &lt;!-- greeting --&gt;
      &lt;section id=&quot;greeting&quot; class=&quot;hide&quot;&gt;
        &lt;form class=&quot;login-box&quot;&gt;
          &lt;div class=&quot;input-box&quot;&gt;
            &lt;input type=&quot;text&quot; id=&quot;userName&quot; /&gt;
            &lt;label for=&quot;userName&quot;&gt;이름을 입력해주세요&lt;/label&gt;
          &lt;/div&gt;
        &lt;/form&gt;
      &lt;/section&gt;
      &lt;!-- todo --&gt;
      &lt;section id=&quot;todo&quot;&gt;
        &lt;form class=&quot;todo-box&quot;&gt;
          &lt;div class=&quot;input-box&quot;&gt;
            &lt;input type=&quot;text&quot; id=&quot;todoInp&quot; /&gt;
            &lt;label for=&quot;todoInp&quot;&gt;할 일을 입력해주세요&lt;/label&gt;
          &lt;/div&gt;
        &lt;/form&gt;
        &lt;ul id=&quot;todoList&quot;&gt;&lt;/ul&gt;
      &lt;/section&gt;
    &lt;/div&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p><code>script</code>에 <code>defer</code>속성을 주어, 스크립트의 다운로드와 함께 <code>html</code>렌더 중지를 하지 않고 렌더가 완료되고 스크립트의 실행을 시켜주었다</p>
<h3 id="날씨">날씨</h3>
<p>위치 기반의 날씨를 렌더링 하려면 두 가지의 문제가 있었다</p>
<ul>
<li>내 위치를 알아내야 한다(가져와야한다)</li>
<li>해당 위치의 날씨 정보를 알아내야 한다(가져와야한다)</li>
</ul>
<p>첫번째 문제는 <code>Web api</code>의 <code>navigator</code>(의 <code>geolocation</code>)를 활용하면 된다</p>
<p><code>Web api</code>란 자바스크립트에 내장된 모델은 아니지만, 우리가 현재 자바스크립트를 실행시키는 웹에서 제공되는 <code>api</code>이다</p>
<blockquote>
<p><code>Navigator</code> 인터페이스는 사용자 에이전트의 상태와 신원 정보를 나타내며, 스크립트로 해당 정보를 질의할 때와 애플리케이션을 특정 활동에 등록할 때 사용합니다.
<code>Navigator</code> 객체는 <code>window.navigator</code> 읽기 전용 속성으로 접근할 수 있습니다.
<code>Navigator.geolocation</code> 읽기 전용 속성은 웹에서 장치의 위치를 알아낼 때 사용할 수 있는 <code>Geolocation</code> 객체를 반환합니다. 웹 사이트나 웹 앱은 위치정보를 사용해 결과 화면을 맞춤 설정할 수 있습니다.
<a href="https://developer.mozilla.org/ko/docs/Web/API/Navigator">[출처] MDN(Navigator-Web API)</a></p>
</blockquote>
<p>코드에서 우선 해당브라우저에서 <code>API</code>를 사용할 수 있는지 분기처리하고, 사용할 수 있다면 <code>open api</code>를 활용해서 기능을 구현해주도록 한다</p>
<p>또한 <code>open api</code> 데이터를 가져올 때 생길 에러상황도 구현해준다</p>
<pre><code class="language-js">// weather
const KEY = &quot;cd9fca71fbee843c7a38d104afaf8bf8&quot;;
const $sectionWeather = document.getElementById(&quot;weather&quot;);

const getWeather = async ({ latitude, longitude }) =&gt; {
  try {
    const res = await fetch(
      `https://api.openweathermap.org/data/2.5/weather?lat=${latitude}&amp;lon=${longitude}&amp;appid=${KEY}&amp;units=metric`
    );
    const json = await res.json();
    const { weather, main } = json;

    const $div = document.createElement(&quot;div&quot;);
    $div.classList.add(&quot;wrap-weather&quot;);
    const $span = document.createElement(&quot;span&quot;);
    const $img = document.createElement(&quot;img&quot;);
    $span.textContent = main.temp + &quot;°C&quot;;
    $img.src = `http://openweathermap.org/img/wn/${weather[0].icon}@2x.png`;

    $div.appendChild($img);
    $div.appendChild($span);

    $sectionWeather.appendChild($div);
  } catch (error) {
    const $div = document.createElement(&quot;div&quot;);
    $div.classList.add(&quot;wrap-weather&quot;);
    const $span = document.createElement(&quot;span&quot;);
    $span.textContent = &quot;fail to load weahter&quot;;
    $div.appendChild($span);
    $sectionWeather.appendChild($div);
  }
};
if (&quot;geolocation&quot; in navigator) {
  console.log(&quot;위치정보 사용 가능&quot;);

  navigator.geolocation.getCurrentPosition((position) =&gt; {
    const { latitude, longitude } = position.coords;
    getWeather({ latitude, longitude });
  });
} else {
  console.log(&quot;위치정보 사용 불가능&quot;);
}</code></pre>
<blockquote>
<p><code>openweathermap API KEY</code>는 현재 <code>inactivate</code>해놓았습니다</p>
</blockquote>
<h3 id="할-일-목록">할 일 목록</h3>
<p>투두리스트의 경우엔, <code>form</code>으로 사용자의 입력값을 받아서 <code>todos</code>라는 배열에 투두를 푸쉬하고, 해당 배열을 렌더링해주는 방식으로 구현했다</p>
<p>입력을 마치고 배열에 투두가 잘 들어갔다면, 입력창을 초기화하고 포커싱을 떼주는 기능까지 구현했다</p>
<p>삭제의 경우, 투두에다가 <code>id</code>를 삽입해두었다가 해당 <code>id</code>를 기준으로 배열에서 <code>filter</code>해주는 방식을 사용했다</p>
<p>삭제가 된 todos라는 배열을 다시 렌더링 해주면 삭제기능은 끝이다</p>
<p><code>form</code>의 경우 <code>default</code>로 행동하는 새로고침을 막기위해 이벤트의<code>preventDefault</code>를 활용했다</p>
<pre><code class="language-js">// todo
const $todoList = document.getElementById(&quot;todoList&quot;);
const $todoForm = document.querySelector(&quot;.todo-box&quot;);

let todos = [];

$todoForm.addEventListener(&quot;submit&quot;, (e) =&gt; {
  e.preventDefault();
  const todo = e.target[0].value;
  if (!todo) return;

  todos.push({
    id: todos.length,
    desc: todo,
  });

  renderTodos();

  e.target[0].value = &quot;&quot;;
  document.getElementById(&quot;todoInp&quot;).blur();
});

function renderTodos() {
  $todoList.innerHTML = &quot;&quot;;
  todos.forEach((todo) =&gt; {
    const $li = document.createElement(&quot;li&quot;);
    const $inp = document.createElement(&quot;input&quot;);
    const $span = document.createElement(&quot;span&quot;);
    const $btn = document.createElement(&quot;button&quot;);

    $li.id = todo.id;
    $li.classList.add(&quot;todo&quot;);

    $inp.name = todo.id;
    $inp.id = todo.id;
    $inp.type = &quot;checkbox&quot;;

    $span.textContent = todo.desc;
    $span.htmlFor = todo.id;

    $btn.classList.add(&quot;delete&quot;);
    $btn.textContent = &quot;삭제&quot;;

    $li.appendChild($inp);
    $li.appendChild($span);
    $li.appendChild($btn);

    $btn.addEventListener(&quot;click&quot;, (e) =&gt; {
      deleteTodo(e.target.parentNode.id);
    });

    $todoList.appendChild($li);
  });
}
function deleteTodo(id) {
  todos = todos.filter((todo) =&gt; todo.id != id);
  renderTodos();
}</code></pre>
<h2 id="결과">결과</h2>
<p><img src="https://velog.velcdn.com/images/jay__ss/post/419932da-6302-4dae-aea6-bc506314a878/image.png" alt="">
<img src="https://velog.velcdn.com/images/jay__ss/post/be163f7e-3948-4ba7-b1aa-7b43137c722c/image.png" alt="">
<img src="https://velog.velcdn.com/images/jay__ss/post/10917dc5-c0aa-40ec-9a57-708e0b15dc7a/image.png" alt="">
<img src="https://velog.velcdn.com/images/jay__ss/post/d69d1d8b-506a-42b8-9bae-b68a291186f9/image.png" alt=""></p>
<hr>
<p>본 후기는 유데미-스나이퍼팩토리 10주 완성 프로젝트캠프 학습 일지 후기로 작성 되었습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[유데미x스나이퍼팩토리] 10주 완성 프로젝트 캠프 7일차 - vanilla JS web toy]]></title>
            <link>https://velog.io/@jay__ss/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-10%EC%A3%BC-%EC%99%84%EC%84%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-7%EC%9D%BC%EC%B0%A8-vanilla-JS-web-toy</link>
            <guid>https://velog.io/@jay__ss/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-10%EC%A3%BC-%EC%99%84%EC%84%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-7%EC%9D%BC%EC%B0%A8-vanilla-JS-web-toy</guid>
            <pubDate>Tue, 20 Jun 2023 11:44:10 GMT</pubDate>
            <description><![CDATA[<h1 id="과제">과제</h1>
<h2 id="요구사항">요구사항</h2>
<p>과제의 요구사항은 총 3개로, 하나의 페이지에 구현하는 것이다
추가 요구사항은 목요일 과제로 진행될 예정이다</p>
<ul>
<li>vanilla js로 배경이미지 랜덤 변경</li>
<li>vanilla js로 인사 만들기</li>
<li>vanilla js로 시계 만들기</li>
</ul>
<h2 id="코드">코드</h2>
<h3 id="html">HTML</h3>
<p>앱 전체를 <code>div id=app</code>으로 감싸준 뒤, 구현해야할 기능별로 <code>section</code>을 나눠주었다
사용자의 이름을 입력받을 섹션에는 기본적으로 <code>hide</code>라는 <code>class</code>를 주어 숨김처리하고 있다(추후에 <code>js</code>로 컨트롤하여 이 클래스를 제거해준다)</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;ko&quot;&gt;
  &lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot; /&gt;
    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot; /&gt;
    &lt;title&gt;Hello World?&lt;/title&gt;
    &lt;link rel=&quot;stylesheet&quot; href=&quot;./style/reset.css&quot; /&gt;
    &lt;link rel=&quot;stylesheet&quot; href=&quot;./js-assignment-css.css&quot; /&gt;
    &lt;script defer src=&quot;./js-assignment-js.js&quot;&gt;&lt;/script&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;div id=&quot;app&quot;&gt;
      &lt;!-- weather --&gt;
      &lt;section id=&quot;weather&quot;&gt;&lt;/section&gt;
      &lt;!-- clock --&gt;
      &lt;section id=&quot;clock&quot;&gt;&lt;/section&gt;
      &lt;!-- greeting --&gt;
      &lt;section id=&quot;greeting&quot; class=&quot;hide&quot;&gt;
        &lt;form class=&quot;login-box&quot;&gt;
          &lt;div class=&quot;user-box&quot;&gt;
            &lt;input type=&quot;text&quot; id=&quot;userName&quot; /&gt;
            &lt;label for=&quot;userName&quot;&gt;이름을 입력해주세요&lt;/label&gt;
          &lt;/div&gt;
        &lt;/form&gt;
      &lt;/section&gt;
      &lt;!-- todo --&gt;
      &lt;section id=&quot;todo&quot;&gt;
        &lt;form&gt;
          &lt;!-- &lt;input type=&quot;text&quot; name=&quot;&quot; id=&quot;&quot; /&gt; --&gt;
        &lt;/form&gt;
      &lt;/section&gt;
    &lt;/div&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<h3 id="css">CSS</h3>
<p>전에는 <code>background-image</code>를 쓰지않고 이미지태그 하나, 백그라운드 어두운처리용 <code>div</code>를 썼었는데 그러지 않아도 어둡게 처리할 수 있다</p>
<pre><code class="language-css">background-image: linear-gradient(rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0.75)), url(&quot;IMG_PATH&quot;)</code></pre>
<h3 id="javascript">Javascript</h3>
<p>스크립트는 <code>IIFE</code>로 즉시실행을 해준다(없어도 실행은 되지만 전역변수의 사용을 피하기위해 사용하였다)</p>
<pre><code class="language-js">(() =&gt; {
  console.log(&quot;IIFE&quot;);
  ...
})()</code></pre>
<p>랜덤 배경화면은 준비된 이미지들의 PATH들을 담은 배열과 <code>Math.random()</code>을 활용하여 <code>background</code>스타일의 <code>url</code>을 갈아끼워주는 방식으로 구현했다</p>
<pre><code class="language-js">// random background
const DEFAULT_PATH = &quot;./assets/&quot;;
const imgPaths = [
    &quot;bg-bicycle.jpeg&quot;,
    &quot;bg-bunny.jpeg&quot;,
    &quot;bg-forest.jpeg&quot;,
    &quot;bg-japan.jpeg&quot;,
    &quot;bg-ocean.jpg&quot;,
    &quot;bg-wall.jpeg&quot;,
];
const App = document.getElementById(&quot;app&quot;);
App.style.backgroundImage = `linear-gradient(rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0.75)), url(${DEFAULT_PATH + imgPaths[Math.floor(Math.random() * imgPaths.length)]
})`;</code></pre>
<p>인사의 경우, 로컬스토리지를 활용해 값이 있는 경우와 없는 경우에 렌더링되어야 하는 요소가 다르다
이를 구현하기위해 렌더링을 해주는 함수는 밖으로 빼주었다</p>
<p>분리된 함수를 통해, 값을 가져왔을 때(로컬스토리지로부터) 있다면 인사를 렌더링하고 없다면 <code>form</code>의 <code>hide</code>클래스를 제거하여 유저로부터 이름을 받는다</p>
<pre><code class="language-js">// greeting
let userName = localStorage.getItem(&quot;userName&quot;);
const $loginForm = document.querySelector(&quot;.login-box&quot;);

const showGreeting = () =&gt; {
  $loginForm.remove();
  userName = localStorage.getItem(&quot;userName&quot;);
  const $sectionGreeting = document.getElementById(&quot;greeting&quot;);
  const $h1 = document.createElement(&quot;h1&quot;);
  $h1.textContent = userName + &quot;님 안녕하세요&quot;;
  $sectionGreeting.appendChild($h1);
};

if (userName) showGreeting();
else {
  document.getElementById(&quot;greeting&quot;).classList.remove(&quot;hide&quot;);
  $loginForm.addEventListener(&quot;submit&quot;, (e) =&gt; {
    e.preventDefault();
    const userName = document.getElementById(&quot;userName&quot;).value.trim();
    if (userName) {
      document.getElementById(&quot;userName&quot;).value = &quot;&quot;;
      // blur(): Remove focus from a text input
      document.getElementById(&quot;userName&quot;).blur();
      localStorage.setItem(&quot;userName&quot;, userName);
      showGreeting();
    }
  });
}</code></pre>
<p>시계의 경우 핵심은 1초마다 현재시간을 렌더링해야 하는것이 필수였다
이를 위해 시간을 지정하면 콜백함수를 실행시키는 <code>setInterval</code>를 활용했다
약간의 문제점은 <code>setInterval</code>은 지정한 시간 이후부터 실행이 되기에 약간의 딜레이가 생긴다
이를 해결하기 위해 시간을 출력하는 함수를 분리하고, <code>setInterval</code>이전에 <code>showTime</code>을 한번만 실행해주면 조금 더 부드럽게 출력이 된다</p>
<pre><code class="language-js">// clock
const $sectionClock = document.getElementById(&quot;clock&quot;);
const $h2 = document.createElement(&quot;h2&quot;);
const showTime = () =&gt; {
  const now = new Date();

  const hours = now.getHours();
  const minutes = now.getMinutes();
  const seconds = now.getSeconds();
  const AMPM = hours &gt; 12 ? &quot;PM&quot; : &quot;AM&quot;;

  const addZero = (num) =&gt; {
    let result = String(num);
    return result.length === 1 ? &quot;0&quot; + result : result;
  };

  $h2.textContent = `${AMPM} ${addZero(hours)} : ${addZero(
    minutes
  )} : ${addZero(seconds)}`;

  $sectionClock.appendChild($h2);
};
showTime();
setInterval(showTime, 1000);</code></pre>
<h2 id="결과">결과</h2>
<ul>
<li>새로고침을 할 때마다 무작위 배경화면이 렌더링 된다</li>
<li><code>setInterval</code>을 활용해 <code>1000ms</code>의 시간마다 현재시간을 보여준다</li>
<li><code>localStoarge</code>에 <code>userName</code>이라는 <code>key</code>를 가진 <code>value</code>가 없다면, <code>form</code>의 <code>hide</code>라는 <code>class</code>를 제거하여 폼을 보여준 뒤 입력받을 수 있도록 해주고, 해당 <code>value</code>가 있다면 인사말을 남긴다</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jay__ss/post/56dfef37-c6bf-4239-9fcf-fee60d0402b3/image.png" alt="">
<img src="https://velog.velcdn.com/images/jay__ss/post/cca7d62b-f1c2-4cfe-9edf-dbbca82e6fbf/image.png" alt=""></p>
<hr>
<p>본 후기는 유데미-스나이퍼팩토리 10주 완성 프로젝트캠프 학습 일지 후기로 작성 되었습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[유데미x스나이퍼팩토리] 10주 완성 프로젝트 캠프 6일차 - 반응형 웹]]></title>
            <link>https://velog.io/@jay__ss/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-10%EC%A3%BC-%EC%99%84%EC%84%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-6%EC%9D%BC%EC%B0%A8-%EB%B0%98%EC%9D%91%ED%98%95-%EC%9B%B9</link>
            <guid>https://velog.io/@jay__ss/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-10%EC%A3%BC-%EC%99%84%EC%84%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-6%EC%9D%BC%EC%B0%A8-%EB%B0%98%EC%9D%91%ED%98%95-%EC%9B%B9</guid>
            <pubDate>Sun, 18 Jun 2023 13:17:00 GMT</pubDate>
            <description><![CDATA[<h1 id="과제">과제</h1>
<blockquote>
<p>(구)세이프홈즈 랜딩페이지 반응형 퍼블리싱 - css, js 작성</p>
</blockquote>
<p>이번 과제는 <code>Figma</code>를 보고 랜딩페이지를 반응형으로 만들라는 요구사항이었다
한 페이지를 만드는데 하루를 꼬박 다 썼다
하루에 두개 세개 그 이상의 페이지를 매끄럽게 만들기 위해서는 내가 지금 어렵거나 시간을 많이 들였던 부분을 정리하면 좋을것 같다</p>
<h1 id="사안과-코드">사안과 코드</h1>
<h2 id="screen-reader-only">screen reader only</h2>
<p>페이지를 각 섹션별로 나누고, <code>h2</code>태그로 해당 구역의 의미를 가지는 태그를 삽입했다
그러기 위해서는 스크린에서는 보이지 않는 요소가 반드시 생기고, 웹 접근성을 고려해서 해당 태그를 숨기는 기법을 사용했다</p>
<pre><code class="language-css">.sr-only {
  position: absolute;
  clip: rect(0 0 0 0);
  width: 1px;
  height: 1px;
  margin: -1px;
  overflow: hidden;
}</code></pre>
<p>위의 클래스를 태그에 지정해주면 화면에서는 보이지않고, 스크린 리더는 읽을 수 있도록 코드작성이 가능하다</p>
<h2 id="이상한-가로-스크롤-생김여백-생김">이상한 가로 스크롤 생김(여백 생김)</h2>
<p>라이브 서버를 구동하면서 화면을 체크하다보니 가로여백이 생겼다
바디의 마진이나 패딩은 리셋을 통해 아니라는걸 알았고 원인을 알 수 없지만, 바디의 너비를 100%로 고정하고 x축으로 넘치는 걸 막으면 임시방편으로 해결할 수 있다고 생각해서 적용해 보았다</p>
<pre><code class="language-css">body {
  width: 100%;
  overflow-x: hidden;
}</code></pre>
<p>어느정도 문제는 해결했지만, 가끔씩 여백이 생기는 경우가 발생했는데 새로고침 연타를 통해 해결아닌 해결을 할 수 있었다</p>
<h2 id="디자인시안-분석하기">디자인시안 분석하기</h2>
<blockquote>
<p>&quot;무작정 키보드에 손 올리지 마시고 작성 해보세요&quot;</p>
</blockquote>
<p>강사님의 당부가 떠올라, 랜딩 페이지의 디자인을 분석해서 나름대로 정리해보고 <code>css</code>작성을 해보고자 했다</p>
<p>처음에는 웹 페이지 기준 각 섹션별로 통일된 스타일이 있는지 분석했다
분석해보니, 크게 4가지 구성이 보였다</p>
<p>레이아웃(flex, container), 폰트(사이즈, 굵기, 줄간격, 자간격), 버튼 정도는 스타일을 지정해두고, 해당 선택자를 <code>html</code>에서 클래스만 지정해주면 되겠다고 생각했다(마치 부트스트랩처럼)</p>
<p>그 다음으로는 스크린의 너비에 따른 통일된 변경사항이 있는지 분석했다
예를 들어 <code>.txt-lg</code>는 웹에서는 어떤 스타일, 태블릿에서는 어떤 스타일, 모바일에서는 어떤 스타일 이런 식으로 분석했다</p>
<p>위의 흐름대로 분석을 했더니, 통일된 스타일을 찾을 수 있었고 가끔씩 생기는 특이사항은 선택자로 잡아와서 수정해주면 되었다</p>
<h3 id="container">container</h3>
<pre><code class="language-css">.container {
  width: 976px;
  margin-inline: auto;
}
.container-lg {
  width: 1084px;
  margin-inline: auto;
}
@media (max-width: 1024px) {
  .container {
    width: 634px;
  }
  .container-lg {
    width: 712px;
  }
  ...
}</code></pre>
<p>컨테이너의 너비를 지정해주고 <code>margin-inline: auto;</code>로 중앙정렬을 해준 뒤, <code>break-point</code>별로 크기를 조정해주면 반응형으로 대응이 가능하다</p>
<h3 id="fontweight-color-size-line-height-letter-spacing">font(weight, color, size, line-height, letter-spacing)</h3>
<pre><code class="language-css">/* text size */
.txt-4xl {
  font-size: 48px;
  line-height: 57.6px;
  letter-spacing: -1.8px;
}
@media (max-width: 1024px) {
  .txt-4xl {
    font-size: 32px;
    line-height: 1.2em;
    letter-spacing: -0.9;
  }
  ...
}</code></pre>
<p>픽셀별로 분석해보면, <code>16, 18, 21, 24, 32, 48, 60</code>픽셀을 가진 폰트들이 있었고 많다라고 느꼈지만 처음 세팅시간이 오래걸리지만 페이지가 많았다면 효율적일것이라고 생각했다(그런데 만약, 다른페이지에서는 통일되게 변화를 하지 않는다면 그건 끔찍하다)</p>
<p><code>break-point</code>별로 변동되는 스타일을 분석해서 위와 같이 코드로 작성하면 반응형으로 대응할 수 있었다</p>
<h3 id="btn">btn</h3>
<p>버튼은 크게 두가지 관점으로 봤다
<strong>크기와 버튼 색상</strong></p>
<pre><code class="language-css">/* 버튼 */
.btn-sm {
  padding: 8px 24px;
  border-radius: 4px;
}
.btn-lg {
  padding: 23px 27px;
  border-radius: 8px;
}
.btn-mid {
  padding: 21px 25px;
  border-radius: 8px;
}
.btn-blue {
  background-color: var(--blue-color);
  color: #fff;
}
.btn-white {
  background-color: #fff;
  color: var(--blue-color);
}</code></pre>
<h3 id="flex">flex</h3>
<p>레이아웃 흐름잡기에서 빠질 수 없는 <code>flex</code>를 미리 지정해주었다</p>
<pre><code class="language-css">* flex */
.flex {
  display: flex;
  flex-wrap: wrap;
}
.flex-reverse {
  display: flex;
  flex-wrap: wrap;
  flex-direction: row-reverse;
}
.flex-col {
  display: flex;
  flex-direction: column;
}
.flex-col-center {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}</code></pre>
<h2 id="img의-너비-높이에-관하여">img의 너비 높이에 관하여</h2>
<p>이미지는 너비와 높이의 비율이 중요한 요소이다
하지만 사진이 필요한 곳들은 천차만별이고, &#39;카드&#39;라고 예를 들었을 때 비율이 다른 사진이더라도 같은 사이즈의 공간에 끼워넣어야 할 순간이 생긴다</p>
<p>이럴 땐, 사이즈를 지정하고 <code>object-fit</code>속성을 이용하여 컨트롤해준다
<code>cover</code>로 해주면 비율을 맞춰서 확대?자르기?를 통해 맞춰준다</p>
<pre><code class="language-css">.card .img-card {
  width: 100%;
  height: 233px;
  object-fit: cover;
  border-radius: 8px;
}</code></pre>
<h2 id="클래스의-지정에-관한-고찰">클래스의 지정에 관한 고찰</h2>
<p>이제껏 텍스트, 버튼, 레이아웃(flex, container)를 분석해서 일반화를 시켰다
그러나 모든 구역에서 어느정도의 통일성만 있지 완벽히 동일할 수 없다
가령, 어떤 구역에서는 <code>xl</code>의 사이즈를 가지지만 <code>line-height</code>만 다를 수 있다
이럴 때는 불가피하게 해당 태그를 선택자로 잡아와서 조정해야만 한다
그런데 이때 <code>.불어와야할컨테이너 .불러와야할태그 .txt-xl</code> 이런 식으로 잡아오는 것은 의미적으로 맞지 않다고 생각했다
그래서 해당 태그가 가지는 의미로 가져오고 싶었고, 아래처럼 바꾸어 보았다
<code>.불어와야할컨테이너 .불러와야할태그 .태그가가지는의미적특성</code>
여기서 보통 특성을 가져올 때, <code>tit</code>, <code>desc</code> 정도로 잡았는데 이게 맞는지 의문이 들었다</p>
<p>그러나뎊스를 타고 들어오면서 잡아야 한다는 암묵적인 룰이 있다면 이는 문제가 없을것으로 보였다</p>
<p>풀어서 설명하자면,</p>
<blockquote>
<p>섹션첫번째의 카드의 사이즈가라지인 태그의 <code>line-height</code>를 바꾸겠다</p>
</blockquote>
<p>가 아니라,</p>
<blockquote>
<p>섹션첫번째의 카드의 타이틀의 <code>line-height</code>를 바꾸겠다</p>
</blockquote>
<p>로 바꾸면 의미도 들어맞고, 모듈(야매모듈)로 작성한 스타일도 쓸 수 있다고 생각해서 적용해 보았다</p>
<h1 id="결과">결과</h1>
<p><img src="https://velog.velcdn.com/images/jay__ss/post/317549ec-a6ce-4b60-83c9-bdde911d8b34/image.png" alt="">
<img src="https://velog.velcdn.com/images/jay__ss/post/435eff5b-9598-4b94-8404-d6c056ea42bf/image.png" alt="">
<img src="https://velog.velcdn.com/images/jay__ss/post/19eb41db-2d0b-4b47-a9c7-246943a1ad31/image.png" alt=""></p>
<hr>
<p>본 후기는 유데미-스나이퍼팩토리 10주 완성 프로젝트캠프 학습 일지 후기로 작성 되었습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[유데미x스나이퍼팩토리] 10주 완성 프로젝트 캠프 5일차 - CSS]]></title>
            <link>https://velog.io/@jay__ss/asd</link>
            <guid>https://velog.io/@jay__ss/asd</guid>
            <pubDate>Thu, 15 Jun 2023 05:33:24 GMT</pubDate>
            <description><![CDATA[<h1 id="과제">과제</h1>
<blockquote>
<p>자신만의 스타일로 프로필화면 구현하기
특이사항: 반응형을 고려해서 만들 것</p>
</blockquote>
<h2 id="내가-만들어본-시안">내가 만들어본 시안</h2>
<p><img src="https://velog.velcdn.com/images/jay__ss/post/2b368bcb-7d04-4f04-9bfb-7b6e602ad79f/image.jpeg" alt=""></p>
<p>크게 세구획이 있고, <code>break point</code>에서 아래에 있던 구획이 레이아웃이 바뀌도록 반응형 디자인을 고안했다</p>
<h2 id="코드">코드</h2>
<p>간만에 간단한 디자인 구상부터 제로부터 <code>CSS</code> 작성을 하려니 시간이 오래 걸렸다</p>
<p>구현을 하면서 시간을 많이 썼던 부분을 정리해본다</p>
<h3 id="웹-폰트">웹 폰트</h3>
<p>구글폰트에서 원하는 폰트를 <code>CSS</code>파일에서 직접 <code>import</code>해서 쓸 수 있다</p>
<pre><code class="language-css">@import url(&quot;https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@1,900&amp;display=swap&quot;);

적용하고싶은요소선택 {
  font-family: &quot;Montserrat&quot;, sans-serif;
}</code></pre>
<h3 id="변수처리">변수처리</h3>
<p><code>CSS</code>에서도 변수를 쓸 수 있다는 것은 알고있었지만, 적용을 해본적이 없었다</p>
<pre><code class="language-css">:root {
  --main-black-color: #2a272a;
  --main-theme-color: #0859fa;
  --main-sub-color: #22a4be;
}
적용하고싶은요소선택 {
  color: var(--main-theme-color);
}  </code></pre>
<p><code>root</code>에서 관리할 변수를 작성하고, 하단의 코드부분에서 끌어다 쓸 수 있다</p>
<h3 id="responsive한-스타일">responsive한 스타일</h3>
<p>화면의 <code>viewport</code>를 고려한 반응형 스타일을 작성하는 요소를 몇 개 정리해본다</p>
<ul>
<li>흐린배경처리
요즘 웹에서 많이 보이는 흐린배경처리를 도입해보고자 했다<pre><code class="language-css">nav {
backdrop-filter: blur(10px);
}</code></pre>
실제로 프로필 상단과 하단에 블러처리한 내비게이션과 서머리텍스트를 넣었다</li>
</ul>
<p>구현자체는 <code>CSS</code>의 <code>backdrop-filter</code>를 이용하면 된다</p>
<ul>
<li><p>flex direction
이제는 <code>flex</code>속성을 여러 브라우저에서 사용할 수 있다(+IE의 몰락😀)
구획을 수평 방향으로 나누는 스타일에서 아래로 떨어뜨리고 싶을 때, <code>flex-direction</code>을 <code>row(default)</code>에서 <code>column</code>으로 변경하면 쉽게 바뀐다</p>
<pre><code class="language-css">.wrap-content section {
display: flex;
}
@media (max-width: 800px) {
.wrap-content section {
  flex-direction: column;
}
}</code></pre>
</li>
<li><p><code>vw</code>를 활용한 크기지정
하단에 눈길을 끄는 텍스트를 고정시키고, 해당 <code>font-size</code>를 <code>vw</code>로 지정하면 화면크기에 따라서 크기를 가지게 되어 더욱 반응형스러운 디자인을 가지게 된다</p>
<pre><code class="language-css">.banner-summary {
position: fixed;
bottom: 0;
right: 0;

font-family: &quot;Montserrat&quot;, sans-serif;
font-size: 7vw;
line-height: 0.9em;
letter-spacing: -9px;

backdrop-filter: blur(10px);
width: 100%;
text-align: end;
}</code></pre>
</li>
<li><p>단어에 따라서 떨어뜨리고 싶다
화면을 줄이다보면 글자별로 떨어지는 모습을 볼 수 있다
하지만 사용자가 &quot;벨(다음줄)로그를 읽다보면...&quot;과 &quot;벨로그를(다음줄)읽다보면...&quot; 둘 중 하나를 택한다면 당연히 후자일 것이다
이를 코드로 구현하려면 <code>word-break</code>를 쓰면 된다
```css</p>
</li>
<li><p>{
word-break: keep-all;
}</p>
<pre><code></code></pre></li>
</ul>
<h2 id="결과">결과</h2>
<p><img src="https://velog.velcdn.com/images/jay__ss/post/413edaeb-d067-421b-98ca-931be0087494/image.gif" alt=""></p>
<hr>
<p>본 후기는 유데미-스나이퍼팩토리 10주 완성 프로젝트캠프 학습 일지 후기로 작성 되었습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[유데미x스나이퍼팩토리] 10주 완성 프로젝트 캠프 4일차 - HTML]]></title>
            <link>https://velog.io/@jay__ss/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-10%EC%A3%BC-%EC%99%84%EC%84%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-4%EC%9D%BC%EC%B0%A8-HTML</link>
            <guid>https://velog.io/@jay__ss/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-10%EC%A3%BC-%EC%99%84%EC%84%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-4%EC%9D%BC%EC%B0%A8-HTML</guid>
            <pubDate>Tue, 13 Jun 2023 13:24:26 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>4일차에는 HTML 수업을 진행했다.
기존에 알고있던 내용이 있어서 내가 찾아본 내용위주로 정리하고자 한다.</p>
</blockquote>
<h1 id="html-학습">HTML 학습</h1>
<h2 id="meta-태그">&lt;meta&gt; 태그</h2>
<p><code>meta</code>는 어떤데이터가 있으며, 그 데이터를 설명하는 데이터를 메타데이터라고 한다
태그도 어찌보면 메타데이터라고도 할 수 있다</p>
<pre><code class="language-html">&lt;head&gt;
  &lt;meta charset=&quot;utf-8&quot; /&gt;
  &lt;meta name=&quot;description&quot; content=&quot;생활코딩의 소개자료&quot;&gt;
  &lt;meta name=&quot;kewwords&quot; content=&quot;코딩,coding,프로그래밍,html,css,js&quot;&gt;
  &lt;meta name=&quot;author&quot; content=&quot;egoing&quot;&gt;
  &lt;meta http-equiv=&quot;refresh&quot; content=&quot;30&quot;&gt;
&lt;/head&gt;</code></pre>
<ul>
<li>코드는 결국 문자열이다
이 문자열을 어떤 방식으로 저장하고 읽어올지 <code>charset</code> 속성으로 작성</li>
<li>페이지가 어떤 내용을 담고있는지 <code>description</code> 속성으로 작성</li>
<li>페이지가 담고있는 내용을 단어로 나누어서 <code>kewords</code> 속성에서 콤마로 구분지어 작성</li>
<li>페이지의 저자를 <code>author</code> 속성으로 작성</li>
<li><code>http-equiv=refresh</code>로 몇 초마다 새로고침 할 지 작성</li>
</ul>
<h2 id="a-vs-button-버튼-ui">&lt;a&gt; vs &lt;button&gt; (버튼 UI)</h2>
<p>디자인 시안을 보고 버튼처럼 생겼다고 해서 습관적으로 <code>button</code>태그를 사용하는 것은 위험하다</p>
<h3 id="a"><code>&lt;a&gt;</code></h3>
<p>앵커태그는 &#39;페이지의 이동&#39;을 의미한다
페이지 내부에서 어떤 구획으로의 이동을 의미할 수도 있고, 아예 다른 사이트로의 이동을 의미할 수 있다
그러니 &#39;이동&#39;이 이루어지는 요소라면 앵커태그를 사용해야 한다</p>
<h3 id="button"><code>&lt;button&gt;</code></h3>
<p>버튼태그는 &#39;사용자의 액션&#39;을 의미한다
전송이나 혹은 알림받기 등, 해당 버튼을 클릭하면 어떠한 액션이 발생한다면 버튼태그를 사용해야 한다</p>
<h2 id="open-graph-protocol-og">Open Graph Protocol (:og)</h2>
<p>간혹 우리가 어떤 링크를 카카오톡 등에서 공유할 때, 미리보기 사진이나 제목들을 볼 수 있다
이러한 미리보기 영역까지 고려한다면 훨씬 좋은 사이트가 될 수 있다
이를 나타내기 위해서는 오픈그래프 프로토콜을 이용한다
메타 태그로 작성해야한다</p>
<pre><code class="language-html">&lt;meta property=&quot;og:title&quot; content=&quot;미리보기-제목&quot;&gt;
&lt;meta property=&quot;og:description&quot; content=&quot;미리보기-짧은요약&quot;&gt;
&lt;meta property=&quot;og:image&quot; content=&quot;미리보기-이미지경로&quot;&gt;</code></pre>
<p>위와 같이 메타태그로 <code>property=&quot;og:종류&quot; content=&quot;...&quot;</code>로 작성해주면 우리의 사이트의 미리보기가 완성된다</p>
<h1 id="과제">과제</h1>
<p><a href="https://github.com/yedol1/Lec-Practice/blob/main/personal/%EA%B3%BD%EC%84%B1%EC%9E%AC/index.html">깃허브링크</a></p>
<h2 id="요구사항">요구사항</h2>
<blockquote>
<p><strong>(구)세이프홈즈 랜딩페이지의 Figma 시안을 보고 클론코딩 실시</strong>
특이사항: HTML만을 사용해서 구조만 잡기</p>
</blockquote>
<h2 id="코드">코드</h2>
<ul>
<li><p>h1태그는 한 페이지에 하나만 담아야 한다</p>
</li>
<li><p>의미별로 구획을 나누어 마크업한다</p>
<img src="https://velog.velcdn.com/images/jay__ss/post/3aadf860-7117-47ca-9236-207fd6f291bd/image.png" width="420" />
서비스의 전체 내용을 말해주는 h1 태그는 나중에 숨김 텍스트 처리하여서 스크린 리더만 읽게 해준다
의미를 파악하여 구획을 나눈후, 해당 구회마다 주석을 달아준다
</li>
<li><p>구획별로 제목을 나타내는 heading태그가 있어야 한다(숨김텍스트처리)</p>
<pre><code class="language-html">&lt;section&gt;
&lt;h2&gt;내 집 보증금, 경매로 날아가면 어떻게 하지?&lt;/h2&gt;
...
&lt;/section&gt;</code></pre>
<p>구획별로 heading 태그를 반드시 두고, 리스트의 내용에도 h3를 활용하여 heading 태그를 사용해준다</p>
</li>
<li><p>대체 텍스트를 고려해야 한다</p>
<pre><code class="language-html">&lt;img src=&quot;./assets/logo.svg&quot; alt=&quot;세이프홈즈로고이미지&quot; /&gt;</code></pre>
<p>이미지를 삽입하는 경우 어떠한 이미지를 넣었는지 대체텍스트를 반드시 작성한다</p>
</li>
<li><p>서비스의 내용과 연관없는 의미를 가진 이미지를 pseudo element로 처리한다</p>
</li>
<li><p>수정보완이 필요한 부분에는 주석을 달자
<img src="https://velog.velcdn.com/images/jay__ss/post/9831cd8c-8101-45fa-b515-1cb7c8a5b2de/image.png" alt=""></p>
</li>
</ul>
<pre><code class="language-html">&lt;!-- TODO: 화살표모양(&gt;)는 pseudo el로 처리하기 --&gt;
&lt;a href=&quot;#&quot;&gt;&lt;span&gt;지금 걱정 해결하기&lt;/span&gt;&lt;/a&gt;</code></pre>
<p>위의 예시는 단순히 꺽쇠로도 표현이 가능하지만, 간단하 다른 이미지라고 상상(?)을 하고 굳이 의미가 있는 이미지가 아니므로 슈도 엘리먼트를 활용해보자
또한, 아직 완전한 구현이 되지 않았으므로 코드를 유지 보수하기 쉽게 <code>TODO</code>라는 문구를 붙여서 주석을 작성한다</p>
<ul>
<li>버튼 UI의 의미를 고려하여 태그를 선택한다
위에서 기술한것처럼, 버튼모양을 관습적으로 버튼태그를 쓰지 않도록 유의한다</li>
</ul>
<h2 id="결과">결과</h2>
<img src="https://velog.velcdn.com/images/jay__ss/post/3fada036-5d52-48a1-91bf-87c66d9f0876/image.png" width="420" />

<h1 id="참고자료">참고자료</h1>
<p><a href="https://www.youtube.com/watch?v=zO5zHxX2lNI">생활코딩-meta</a>
<a href="https://www.youtube.com/watch?v=T7h8O7dpJIg">드림코딩-HTML 태그들, 헷갈리는거 정리해 보았다 🥳</a>
<a href="https://ogp.me/">The Open Graph Protocol</a></p>
<hr>
<p>본 후기는 유데미-스나이퍼팩토리 10주 완성 프로젝트캠프 학습 일지 후기로 작성 되었습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[유데미x스나이퍼팩토리] 10주 완성 프로젝트 캠프 3일차 - 서비스기획]]></title>
            <link>https://velog.io/@jay__ss/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-10%EC%A3%BC-%EC%99%84%EC%84%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-3%EC%9D%BC%EC%B0%A8-%EC%84%9C%EB%B9%84%EC%8A%A4%EA%B8%B0%ED%9A%8D</link>
            <guid>https://velog.io/@jay__ss/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-10%EC%A3%BC-%EC%99%84%EC%84%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-3%EC%9D%BC%EC%B0%A8-%EC%84%9C%EB%B9%84%EC%8A%A4%EA%B8%B0%ED%9A%8D</guid>
            <pubDate>Sat, 10 Jun 2023 15:12:43 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>지금까지 기획한 흐름들을 실제로 옮기는 &#39;아이디어 스케치&#39;를 알아보자
과제: 여러분의 플랫폼을 아이디어 스케치로 그려보세요</p>
</blockquote>
<h1 id="수업">수업</h1>
<h2 id="지금까지의-흐름">지금까지의 흐름</h2>
<ul>
<li>내가 불편함을 느끼는 현상 발견</li>
<li>현상을 해결하는 아이디어를 마인드 맵으로 형상화</li>
<li>현상을 겪는 사람과 해결해주는 사람의 액션 정의</li>
<li>유저가 서비스를 이용하는 흐름 정의<h2 id="아이디어-스케치">아이디어 스케치?</h2>
</li>
<li>흩뿌려져 있던 아이디어들, 도표들을 현실의 서비스처럼 실체화해서 현실처럼 그려놓은 것<h2 id="왜">왜?</h2>
</li>
<li>도표보다도 확실하게 서비스의 구조를 실제처럼 파악할 수 있다</li>
<li>전체 스토리를 중요도에 따라 알 수 있다</li>
<li>진행과정의 스토리 흐름을 알 수 있다</li>
</ul>
<h2 id="효과적인-스케치">효과적인 스케치</h2>
<ul>
<li>1장에 1개의 주제만을 담아라</li>
<li>그리는데 3분이상 소요하지마라(흐름을 잡는것이다)</li>
<li>스토리를 그려라</li>
<li>알고있는 개념은 생략하라(내가 알고 있으니 괜찮다)</li>
</ul>
<blockquote>
<p>스케치가 완료되었을 때 가장 중요한 것은 그림이 아니라 머리속의 이미지다</p>
</blockquote>
<h2 id="스케치의-흐름">스케치의 흐름</h2>
<ul>
<li>상품에 들어갈 요소 정의</li>
<li>카피 확장</li>
<li>상품의 스토리 구성 스케치</li>
<li>구성 레벨화</li>
</ul>
<blockquote>
<p>이렇게 완성된 스케치는 스토리보드, 제안서, 시놉시스, 회의, 다이어리 등에 쓰일 수 있다</p>
</blockquote>
<h1 id="과제">과제</h1>
<blockquote>
<p>여러분의 과제를 아이디어 스케치로 그려보세요</p>
</blockquote>
<h2 id="어떻게">어떻게?</h2>
<ul>
<li>메인페이지(검색) → 결과 리스트 렌더 → 상세페이지 렌더 → 참여하기</li>
<li>메인페이지(멘토클릭) → 멘토 상세페이지 렌더 → 멘토링신청</li>
</ul>
<div align="center">
  <img src="https://velog.velcdn.com/images/jay__ss/post/87fa5239-c0b6-4cc1-affc-4052cca84bd9/image.jpeg">
</div>

<hr>
<p>본 후기는 유데미-스나이퍼팩토리 10주 완성 프로젝트캠프 학습 일지 후기로 작성 되었습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[유데미x스나이퍼팩토리] 10주 완성 프로젝트 캠프 2일차 - 서비스기획]]></title>
            <link>https://velog.io/@jay__ss/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-10%EC%A3%BC-%EC%99%84%EC%84%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-2%EC%9D%BC%EC%B0%A8-%EC%84%9C%EB%B9%84%EC%8A%A4%EA%B8%B0%ED%9A%8D</link>
            <guid>https://velog.io/@jay__ss/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-10%EC%A3%BC-%EC%99%84%EC%84%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-2%EC%9D%BC%EC%B0%A8-%EC%84%9C%EB%B9%84%EC%8A%A4%EA%B8%B0%ED%9A%8D</guid>
            <pubDate>Thu, 08 Jun 2023 08:36:00 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>목적</strong>: 서비스 기획 단계에 있어 UML이란 무엇인지 이해하고, 
직접 가상의 모델링을 실습해보자
<strong>TODO</strong>: Activity Diagram을 만들어보세요</p>
</blockquote>
<h1 id="수업">수업</h1>
<h2 id="왜-uml">왜 UML?</h2>
<p>텍스트가 주는 파워는 강하지만 직관적이지 못하다. 하나의 서비스는 덩어리가 너무 크지만, 이런 서비스를 알리는데에 순수 텍스트로만 접근을 한다면 접근성이 매우 떨어질 것이다. 직관적으로 서비스를 잘 이해하려면 UML이 필수적이다.</p>
<p>복잡한 설명서를 1쪽부터 100쪽까지 읽을 것인가, 시스템의 흐름을 적절한 글과 그림으로 풀어낸 한장짜리 설계도를 볼 것인가?</p>
<h2 id="그래서-uml이-무엇">그래서 UML이 무엇?</h2>
<ul>
<li>Unified Modeling Language </li>
<li>복잡한 사람들의 사고와 생각을 표현하는 도구 혹은 방법론이라고 이해할 수 있다.</li>
</ul>
<p>어떻게 개발할 것인지를 나열하는 것이 아닌, 이 시스템의 산출물의 역할을 시각화하여 규정하는 것이다.(말 그대로 시스템을 이해하는 것이지, 시스템을 어떻게 만들지에 대한 설명서가 아니다)</p>
<p><strong>이런 과정을 통해 꼭 필요한 행위를 기반으로 한 객체 지향 모델링이 가능해진다</strong></p>
<blockquote>
<p>💡 모델링을 할 때 단순화, 일반화, 추상화를 통해 모델링을 해야한다.
예를 들어, 유저의 구구절절한 유입 시나리오나 300포인트를 쓰냐 안쓰냐등의 코어하지 않은 부분은 고려하지 않는다.</p>
</blockquote>
<p>무턱대고 모델링을 하지 않고 정제를 통한 모델링을 하므로 내부 구조나 동작의 표현의 자유가 있고, 시스템의 요소들간에 관계를 보기 쉬우며, 설계와 구현간의 일관성을 유지할 수 있다. 무엇이 더 코어한 흐름인지를 나타내는 레벨화도 가능하다.</p>
<h3 id="usecase-diagram">Usecase Diagram</h3>
<ul>
<li>우리 서비스는 ‘무엇’을 제공할 것이고, 그 ‘무엇’을 정의하는 다이어그램이다. </li>
</ul>
<p>인문학적인 소양이 떨어지다보니 이해하기에는 어렵지만, 행동을 하는 <code>Actor</code>들을 기준으로 그들이 하는 행동(단순, 일반, 추상)들과 <code>Actor</code>들간에 관계를 알 수 있다.(오히려 말보다는 그림이 이해하기 쉽다)</p>
<h3 id="activity-diagram">Activity Diagram</h3>
<ul>
<li>서비스 흐름에서 가장 코어한 부분만 처음부터 끝까지 일종의 시나리오를 만드는 것<h2 id="개발자와-uml에-대한-고찰">개발자와 UML에 대한 고찰</h2>
그래서 UML을 학습해서 우리는 어떤 개발자가 되어야 하냐고 묻는다면,</li>
<li>(유저 혹은 관리자)액션, 관계에 대한 설계도를 도식화해서 이런구조로 형성이 되어있다라고 설명할 수 있는</li>
<li>혹은 그런 서비스를 이해한채로 개발하는 개발자가 되어야한다고 생각한다</li>
</ul>
<p>그리고 기획, UML 설계를 무조건적인 수용을 할 것인가? 위험하다. 설계도를 바탕으로 우리도 물음표를 던질 수 있어야한다. 공동의 책임이 있다</p>
<h2 id="더-나아가서는">더 나아가서는?!</h2>
<p>상세한 기획에 있어서는 앞선 도표를 바탕으로 스토리보드를 제작하게 된다. 이 때 우리 서비스의 유저의 상당수가 이러할 것이다라는 기준을 잡은 가상인물인 페르소나가 등장한다. 그들의 공통적인 니즈를 설계해두고, 이를 바탕으로 스토리보드를 제작하게 된다.</p>
<h1 id="과제">과제</h1>
<h2 id="어떻게">어떻게?</h2>
<p>여러가지 행동 흐름(액터별, 상황별)이 나올 수 있겠지만, 프로젝트나 스터디의 멘토링을 받는다는 가장 거대한 스토리 흐름을 다이어그램에 담아보려고 한다.</p>
<h2 id="결과는">결과는?</h2>
<p><img src="https://velog.velcdn.com/images/jay__ss/post/ac7607a3-2562-43aa-94be-4bcddfb7c09b/image.png" alt=""></p>
<hr>
<p>본 후기는 유데미-스나이퍼팩토리 10주 완성 프로젝트캠프 학습 일지 후기로 작성 되었습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[유데미x스나이퍼팩토리] 10주 완성 프로젝트 캠프 1일차 - 서비스기획[과제]]]></title>
            <link>https://velog.io/@jay__ss/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-10%EC%A3%BC-%EC%99%84%EC%84%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-1%EC%9D%BC%EC%B0%A8-%EC%84%9C%EB%B9%84%EC%8A%A4%EA%B8%B0%ED%9A%8D-o4l9tw8n</link>
            <guid>https://velog.io/@jay__ss/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-10%EC%A3%BC-%EC%99%84%EC%84%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-1%EC%9D%BC%EC%B0%A8-%EC%84%9C%EB%B9%84%EC%8A%A4%EA%B8%B0%ED%9A%8D-o4l9tw8n</guid>
            <pubDate>Wed, 07 Jun 2023 01:22:29 GMT</pubDate>
            <description><![CDATA[<h1 id="설계의-시작점">설계의 시작점</h1>
<p>플랫폼 설계의 시작은, 어떠한 주제(내가 서비스하고 싶은 주제)를 두고 이런 서비스가 있다면 이런이런 기능들이 있겠구나 상상하며 키워드들을 나열하고 이러한 키워드들의 집합이 곧 플랫폼의 시작점이 된다.</p>
<h2 id="플랫폼의-차별성">플랫폼의 차별성?!</h2>
<p>쿠팡과 옥션은 이커머스 사이트라는 범주에서는 동일하지만, 우리는 확실히 다르다고 느낀다. 이처럼 플랫폼이 굉장히 새로울 필요는 없지만, 차별화 포인트 하나만 있다면 그것만으로도 훌륭한 플랫폼이 될 것이다.</p>
<h1 id="my-ideation">My Ideation</h1>
<p>현재 내가 개발공부를 하고있기도 하고, 어려움을 겪는 부분들이 많다보니 이러한 문제를 해결하고 싶었다. 스터디나 프로젝트를 통해 성장할 수 있으면서 초심자들이 길을 잃지 않도록 멘토링 서비스를 하게되는 플랫폼을 생각해보았다. 이전에 이미 고민했었던 주제이다 보니 쉽게 할 수 있었다.</p>
<h2 id="잊지말아야-한다">잊지말아야 한다</h2>
<p>기획을 알고모르고, 그 기획을 자세히 아는자와 대충 아는자의 차이는 구현 단계에서 차이가 난다. 그러니 계획단계의 내용들을 잊지말자.</p>
<h2 id="결과">결과</h2>
<p>맥용 프리마인드 설치에 어려움이 있어, 기존에 있던 Figma툴을 활용하여 마인드맵을 작성해보았다.</p>
<p><img src="https://velog.velcdn.com/images/jay__ss/post/6eebbe4b-6a4e-45ae-93ce-e817eefb018e/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[유데미x스나이퍼팩토리] 10주 완성 프로젝트 캠프 1일차 - 서비스기획]]></title>
            <link>https://velog.io/@jay__ss/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-10%EC%A3%BC-%EC%99%84%EC%84%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-1%EC%9D%BC%EC%B0%A8-%EC%84%9C%EB%B9%84%EC%8A%A4%EA%B8%B0%ED%9A%8D</link>
            <guid>https://velog.io/@jay__ss/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-10%EC%A3%BC-%EC%99%84%EC%84%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-1%EC%9D%BC%EC%B0%A8-%EC%84%9C%EB%B9%84%EC%8A%A4%EA%B8%B0%ED%9A%8D</guid>
            <pubDate>Tue, 06 Jun 2023 10:02:15 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jay__ss/post/d30f2b74-5322-453d-8ea1-d1d609c72a87/image.png" alt=""></p>
<h1 id="후기">후기</h1>
<p>본격적인 &#39;개발&#39;을 공부하고 프로젝트를 하기전에, 웹 기획이라는 거대한 뼈대를 바라볼 줄 아는 사람이 되어야 하는 것이 중요하다고 느꼈다. 이전에 &#39;개발자의 품격&#39;을 진행할 때에도, 멘토님은 기획의 중요성을 강조하셨고 이번 유데미 캠프에서도 마찬가지이다.</p>
<p>개발자가 개발을 잘하는 것은 당연한 얘기이다. 그러나 개발자가 기획을 고려하는 것은 차별점임과 동시에 무기가 될 수 있다.</p>
<p>첫 주에 편성된 기획 강의를 개발자가 되더라도 잊지말자!</p>
<h1 id="수업내용">수업내용</h1>
<h2 id="끊임-없이-질문해라">끊임 없이 질문해라</h2>
<ol>
<li>누가 이용해?</li>
<li>어떤걸 제공해?</li>
<li>제공해서 어떤걸 얻어?</li>
<li>그 싸이클은 자동화(일정의 흐름정도로 보임)야?</li>
</ol>
<p>위의 질문을 계속해서 생각하고 던져보면서 플랫폼의 아이덴티티를 잊지말고, 잃지말고, 단단히해라.</p>
<h2 id="플랫폼이란">플랫폼이란?</h2>
<p>정류장과도 같다. 고객은 원하는걸 얻고, 기업은 고객이 원하는 것을 제공하고 이득을 본다</p>
<h2 id="플랫폼의-진화">플랫폼의 진화</h2>
<p>플랫폼의 진화는 그 시대의 디바이스와 연관되어 있다.</p>
<table>
<thead>
<tr>
<th>tv, pc, phone</th>
<th>smart phone, tablet, iptv</th>
<th>smart watch, ai speaker</th>
</tr>
</thead>
<tbody><tr>
<td>정보의 소유</td>
<td>정보의 교류</td>
<td>맞춤형 정보</td>
</tr>
<tr>
<td>관계/수단</td>
<td>개인화</td>
<td>프로세스화</td>
</tr>
</tbody></table>
<p>고객의 “행동”으로 이뤄지는 플랫폼 생태계</p>
<h2 id="그래서-플랫폼의-핵심은">그래서, 플랫폼의 핵심은?</h2>
<p>플랫폼은 시장의 흐름을 보는 것부터 시작되고, 세상의 행동이 인터넷 서비스로 전환되고 있으며, 우리가 중점적으로 관찰해야 하는 것은 “관계”이다.</p>
<p>고객들간의 관계 혹은 고객과 플랫폼과의 관계, 관계를 유지하기 위한 수단으로 우리의 플랫폼을 쓰게끔 하자!</p>
<table>
<thead>
<tr>
<th>관계</th>
<th>수단</th>
<th>플랫폼</th>
</tr>
</thead>
<tbody><tr>
<td>정보를 주고받고 공감하는 관계</td>
<td>정보 공유(다면 플랫폼)</td>
<td>페이스북, 네이버, 챗GPT</td>
</tr>
<tr>
<td>상품을 주고받는 관계</td>
<td>상품 판매(거래 플랫폼)</td>
<td>옥션,11번가,쿠팡</td>
</tr>
<tr>
<td>서비스와 개인을 연결해 주는 관계</td>
<td>GATE 역할(생태계 플랫폼)</td>
<td>앱스토어, 구글플레이 스토어</td>
</tr>
</tbody></table>
<h2 id="설계의-시작점">설계의 시작점</h2>
<p>상상의 키워드들을 나열하고, 그들의 조합이 곧 플랫폼 설계의 시작이다.(마인드맵)</p>
<h2 id="성공한-플랫폼의-특징">성공한 플랫폼의 특징</h2>
<p>문화를 리드하고 사용자의 생활/이용 환경에 변화를 준 플랫폼</p>
<h2 id="how-to-success">How to success?!</h2>
<p>첫 번째 : 기업의 신뢰가 없다면 사람과 사람의 신뢰를 공략하자!
두 번째 : 이용을 하지 않으면 손실을 보는 가치를 부여하자!
세 번째 : 처음부터 비용을 선뜻 지불하는 고객은 없다! 무료와 병행하라!</p>
<h2 id="idea">idea...</h2>
<blockquote>
<p>&quot;아이디어라는 것은 원래 완성 상태로 떠오르지 않습니다. 
오직 실행하는 과정에서만 명료해질 뿐입니다. 
그래서 지금 바로 시작하면 되는 겁니다.&quot;
-마크저커버그-</p>
</blockquote>
<p>일단 서비스의 핵심만을 메이킹하고 배포하자. 그러고 살을 붙여야지 굴러간다.</p>
<h1 id="기억나는점">기억나는점</h1>
<h2 id="ai와-인간">AI와 인간</h2>
<p>빠르게 발전하는 AI시대에서, AI가 이야기하는 것의 “진위여부 판단”만큼은 아직 인간의 역할(혹은 숙제정도)로 남아있다는 점이 인상깊었다. 이 말은 즉, 아직은 AI가 완성형이 아니라는 점에서 인간으로서 한시름 놓을 수 있는(?) 부분이 아닐까 싶지만 역으로 그 부분을 제외하고는 AI가 모든 걸 메이킹 할 수 있다는 점에서 소름이 돋는다.</p>
<h2 id="왜-이렇게-적극적인-거야">왜 이렇게 적극적인 거야!</h2>
<p>왜 이렇게 적극적으로 유저가 서비스를 잘 이용할까라는 질문이 인상깊었다. 우리는 무언가 새로운게 나오면 미친듯이 이용하고 나름의 전문가가 된다.(그런데 왜 그런지는 똑 부러지게 말씀 해주시진 않았다) 강사님의 맥락을 정리해보면, 이러한 트렌드와 변화의 흐름에 빠르게 탑승해서, 그걸 활용할 줄 알아야 한다는 것이다. 미래에는 더욱 명확하게 변화에 적응한자는 살아남고 그렇지 않으면 도태되거나, 이득을 훨씬 적게보는 흐름으로 갈 것이다.</p>
<h2 id="지식을-습득하는-마음가짐">지식을 습득하는 마음가짐</h2>
<p>깊은 지식은 기술이 대체할 수 있으니 얕고 넓게 알아라
지금의 트렌디한 기술들을 ‘내 것이 아니다’ 라고 생각하지 마세요 ⇒ 너무나도 내 이야기이다</p>
<h2 id="지금은-무슨-시대">지금은 무슨 시대?</h2>
<p>이제는 “소비자의 ‘질문’을 이해하는 시대” ⇒ AI가 정교하게 이 사람이 원하는 것이 무엇인지, 어떻게 해결할 것이며 연관있는 정보마저도 정교하게 제공되어야 한다.</p>
<h2 id="흐르지-않는-물은">흐르지 않는 물은?</h2>
<p>플랫폼은 자전거와 같아서, 멈추면 쓰러진다 ⇒ 그렇다고해서 갑자기 기획을 틀어버린다? 클래스101처럼됨</p>
<hr>
<p>본 후기는 유데미-스나이퍼팩토리 10주 완성 프로젝트캠프 학습 일지 후기로 작성 되었습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] 에라토스테네스의 체]]></title>
            <link>https://velog.io/@jay__ss/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%97%90%EB%9D%BC%ED%86%A0%EC%8A%A4%ED%85%8C%EB%84%A4%EC%8A%A4%EC%9D%98-%EC%B2%B4</link>
            <guid>https://velog.io/@jay__ss/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%97%90%EB%9D%BC%ED%86%A0%EC%8A%A4%ED%85%8C%EB%84%A4%EC%8A%A4%EC%9D%98-%EC%B2%B4</guid>
            <pubDate>Thu, 13 Apr 2023 07:14:32 GMT</pubDate>
            <description><![CDATA[<h1 id="알고리즘">알고리즘</h1>
<p>2부터 타겟 숫자까지 모든 숫자를 나열한다.(10이라면 2, ... , 10)
순회를 시작하면서, 숫자가 소수라면 그의 배수들을 모두 지운다.
단, 지울 때 소수인 숫자는 내버려둔다.
그렇게되면 최초 2를 시작으로, 3, 5, 7 등의 배수들이 지워진다.</p>
<h1 id="코드">코드</h1>
<pre><code class="language-js">// 타겟 숫자 n을 받으면
// n 까지 소수 갯수를 반환하는 함수
function isPrime(n) {
  // index는 0부터 시작하니 n + 1 까지 생성
  // arr는 해당숫자(인덱스)가 소수면 true, 아니면 false
  const arr = new Array(n + 1).fill(true);
  // 0, 1은 소수가 아니니 false 처리
  arr.splice(0, 2, false, false);

  // 2부터 순회를 시작하나, 제곱수부터는 순회를 하지 않음
  // 어떤수의 제곱이다 === 이미 소수가 아니다
  for(let i = 2; i ** 2 &lt;= n; i += 1) {
      // 만약 해당숫자가 소수라면, 그 숫자의 배수들을 제거할것임
      if (arr[i]) {
          // 제곱수부터 해당숫자만큼 더해줄것임
          for(let j = i ** 2; j &lt;= n; j += i) {
              arr[j] = false;
          }
      }
  }

  // console.table(arr);
  return arr.filter(el =&gt; el).length;
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[vue.js] 재사용성]]></title>
            <link>https://velog.io/@jay__ss/vue.js-%EC%9E%AC%EC%82%AC%EC%9A%A9%EC%84%B1</link>
            <guid>https://velog.io/@jay__ss/vue.js-%EC%9E%AC%EC%82%AC%EC%9A%A9%EC%84%B1</guid>
            <pubDate>Wed, 22 Feb 2023 09:15:44 GMT</pubDate>
            <description><![CDATA[<p>재사용성이란 말 그대로 임의의 장소에 저장한 무언가를 여기저기에서 끌어다 쓸 수 있는 패턴 또는 행동을 의미한다. 여기서 무언가란 상태(정적인 데이터 값)일 수 있고, 함수(메소드_상태를 변경하는)일 수도 있다.</p>
<h1 id="plugin">plugin</h1>
<blockquote>
<p>플러그인은 일반적으로 Vue에 앱 레벨 기능을 추가하는 자체 코드입니다. 플러그인을 설치하는 방법은 다음과 같습니다.</p>
</blockquote>
<p>앱 레벨이란 최상위를 의미한다. 하나의 웹 서비스가 여러가지 컴포넌트들로 이루어져있을 때, 최상단의 컴포넌트를 의미한다.
이 최상단에 기능을 추가한다면 하위의 컴포넌트에서도 사용가능하다를 의미하므로, 재사용성과 밀접한 관련이 있다. 여러강의를 기웃해본 개발자라면 아무렇지 않게도 우리는 이러한 기능을 쓰고있다.(대표적으로 vue-router)</p>
<pre><code class="language-js">import { createApp } from &#39;vue&#39;;
import App from &#39;./App.vue&#39;;
import router from &#39;@/router&#39;;

const app = createApp(App);

app.use(router);
app.mount(&#39;#app&#39;);</code></pre>
<p><code>app.use()</code> : use라는 메소드를 통해 플러그인을 사용하고 있는 코드이다.
이는 곧 &#39;최상단의 앱에서, 라우터라는 무언가를 계속 끌어다 쓰겠다&#39;와 동일한 의미로 보여진다.</p>
<p>이처럼 라이브러리를 끌어다 쓰는것 말고도, 직접 만든 플러그인을 사용할 수 있다. 그렇다면, 어떻게 커스텀 플러그인을 사용하는지 알아보자.</p>
<blockquote>
<p>플러그인은 install() 메서드를 노출하는 객체 또는 단순히 install 함수 자체로 작동하는 함수로 정의됩니다. install 함수는 app.use()에 전달된 추가 옵션과 함께 앱 인스턴스를 받습니다(있는 경우)</p>
</blockquote>
<p>공식문서에서는 이러한 커스텀 플러그인을 <code>install()</code> 메소드를 가진 객체로 만들거나, 단순히 함수로 만들 수 있다고 말한다. (가능하면 객체나 함수 중 하나로 통일해서 쓰라고 vue에서는 경고로 알려준다)</p>
<p>언제나 그렇듯 플러그인을 몰아줄 디렉토리를 만들고 스크립트 파일들을 만들어 주면 된다. 아래는 스크립트 파일의 예제이다.</p>
<pre><code class="language-js">// object example
const objPlugins = {
  // install이라는 메소드를 가진 객체 생성
  // 최상단의 app과 options를 인자로 받아올 수 있다
  install(app, options) {
    const objTest = {
      foo: &#39;bar&#39;,
    };
    app.config.globalProperties.$objTest = objTest;
    console.log(&#39;objPlugins app: &#39;, app);
    console.log(&#39;objPlugins options: &#39;, options);
    // app.component() 전역 컴포넌트
    // app.config.globalProperties 전역 애플리케이션 인스턴스에 속성 추가
    // app.directive 커스텀 디렉티브
    // app.provide() 리소스
    app.provide(&#39;objTest&#39;, objTest);
  },
};
// function example
function funcPlugins(app, options) {
  console.log(&#39;funcPlugins&#39;, app, options);
}
export default funcPlugins;
</code></pre>
<pre><code class="language-js">// 플러그인 몰아져있는 디렉토리에서 끌어와서,
import funcPlugins from &#39;./plugins/func&#39;;
import objPlugins from &#39;./plugins/obj&#39;;

const app = createApp(App);
// use메소드를 사용해서 쓴다
app.use(funcPlugins(app, { name: &#39;func jayss&#39; }));
app.use(objPlugins, { name: &#39;obj jayss&#39; });</code></pre>
<p><code>option api</code>에서는 <code>created</code> 사이클 이후부터 라이플 사이클 훅 내부에서 사용(접근) 가능하다.</p>
<p><code>scrip setup</code> 내에서 쓰려면 플러그인에서 <code>provide</code>를 사용하여 주입하고, 컴포넌트에서 <code>inject</code>를 사용해서 써야한다</p>
<pre><code class="language-js">&lt;script setup&gt;
    const objTest = inject(&#39;objTest&#39;);
    console.log(&#39;objTest.foo: &#39;, objTest.foo);
&lt;/script&gt;</code></pre>
<pre><code class="language-js">// global-component
import AppAlert from &#39;@/components/app/AppAlert.vue&#39;;
import AppCard from &#39;@/components/app/AppCard.vue&#39;;
import AppGrid from &#39;@/components/app/AppGrid.vue&#39;;
import AppModal from &#39;@/components/app/AppModal.vue&#39;;
import AppPagination from &#39;@/components/app/AppPagination.vue&#39;;

export default {
  install(app) {
    app.component(&#39;AppAlert&#39;, AppAlert);
    app.component(&#39;AppCard&#39;, AppCard);
    app.component(&#39;AppGrid&#39;, AppGrid);
    app.component(&#39;AppModal&#39;, AppModal);
    app.component(&#39;AppPagination&#39;, AppPagination);
  },
};</code></pre>
<pre><code class="language-js">// main.js
import globalComponents from &#39;@/plugins/global-components&#39;;
app.use(globalComponents);</code></pre>
<p>전역 컴포넌트로 등록을 해두면, 기존에 import로 컴포넌트를 등록해서 사용하지 않고 바로 컴포넌트로 사용해도 된다.</p>
<h1 id="custom-directive">custom directive</h1>
<blockquote>
<p>코어에 포함된 기본 디렉티브 세트(예: v-model 또는 v-show) 외에도 Vue를 사용하면 커스텀 디렉티브를 정의할 수 있습니다.</p>
</blockquote>
<p>공식문서에 의하면, 우리가 <code>template</code>부분에서 사용하던 뷰의 디렉티브 기능을 커스텀하게 제작해서도 쓸 수 있다고 한다.</p>
<p>그렇다면 언제 사용해야할까?</p>
<blockquote>
<p>우리는 Vue에서 컴포넌트 기초와 컴포저블이라는 두 가지 형태의 코드 재사용을 도입했습니다. 컴포넌트는 주요 빌딩 블럭(building-block)이고, 컴포저블은 상태 저장 로직을 재사용하는 데 중점을 둡니다. 반면에 커스텀 디렉티브는 주로 일반 엘리먼트에 대한 저수준(low-level) DOM 접근과 관련된 로직을 재사용하기 위한 것입니다.</p>
</blockquote>
<p>DOM접근과 관련된 기능을 재사용하고 싶다면 커스텀 디렉티브를 이용하면 되는 것으로 보인다.</p>
<pre><code class="language-js">// src/directives/focus.js
const focus = {
  mounted(el) {
    el.focus();
  },
};

export default focus;

// src/directives/color.js
function color(el, binding) {
  el.style.color = binding.value;
}

export default color;

// src/plugins/global-directives.js
import focus from &#39;@/directives/focus&#39;;
import color from &#39;@/directives/color&#39;;
export default {
  install(app) {
    app.directive(&#39;focus&#39;, focus);
    app.directive(&#39;color&#39;, color);
  },
};

// main.js
import globalDirectives from &#39;./plugins/global-directives&#39;;
app.use(globalDirectives);</code></pre>
<p>커스텀 디렉티브에서 el(첫번째 인자 값)은 DOM요소를 의미한다.</p>
<h1 id="마무리">마무리</h1>
<p>결국은 전역적으로 상태, UI(component), 로직 등을 저장해두고 어떤 컴포넌트에서든 접근하고 사용할 수 있도록 등록을 해주는 행위이다. 반드시 재사용이 필요한 부분에서만 이를 활용하는 것이 바람직해 보인다.</p>
<h1 id="reference">reference</h1>
<p><a href="https://v3-docs.vuejs-korea.org/guide/reusability/composables.html">vue-재사용성</a></p>
]]></description>
        </item>
    </channel>
</rss>