<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>제리님 블로그</title>
        <link>https://velog.io/</link>
        <description>Basic in the end👻</description>
        <lastBuildDate>Tue, 07 Dec 2021 01:36:25 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>제리님 블로그</title>
            <url>https://images.velog.io/images/jerrynim_/profile/1f95a71e-502d-4591-8e44-19b86d1a0050/Logo-square.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. 제리님 블로그. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jerrynim_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[vanilla javascript SPA 만들기]]></title>
            <link>https://velog.io/@jerrynim_/vanilla-javascript-SPA-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@jerrynim_/vanilla-javascript-SPA-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Tue, 07 Dec 2021 01:36:25 GMT</pubDate>
            <description><![CDATA[<p>블로그에서 보기 =&gt; <a href="https://jerrynim.dev/post/vanilla-route">https://jerrynim.dev/post/vanilla-route</a></p>
<p>바닐라 자바스크립트에서 라우팅을 만드는 작업을 함께 보도록 하겠습니다. 이 블로그에서도 사용하고 있는 방식으로 브라우저의 history API, popstate 이벤트, 커스텀 이벤트를 사용하여 제작하게 됩니다.
프로젝트 설정
우리에게 필요한 것은 html과 js 파일이 전부입니다.
기본적인 html 파일을 만들도록 하겠습니다.
index.html</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
    &lt;body&gt;
        &lt;main&gt;&lt;/main&gt;
    &lt;/body&gt;
&lt;/html&gt;</code></pre>
<p>실행될 js 파일을 만들어 추가하도록 하겠습니다.
index.html 파일을 열 때 내부 콘텐츠를 설정하도록 하겠습니다.
index.js</p>
<pre><code class="language-typescript">window.onload = () =&gt; {
    const main = document.querySelector(&quot;main&quot;);
    main.innerHTML = &quot;&lt;div&gt;js loaded&lt;/div&gt;&quot;;
};</code></pre>
<p>만들어준 js 파일을 사용하도록 index.html에서 <script>태그를 사용하여 실행해 주도록 하겠습니다.
index.html</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
    &lt;body&gt;
        &lt;script src=&quot;./index.js&quot;&gt;&lt;/a&gt;
        &lt;main&gt;&lt;/main&gt;
    &lt;/body&gt;
&lt;/html&gt;
index.js</code></pre>
<pre><code class="language-javascript">window.onload = () =&gt; {
    const main = document.querySelector(&quot;main&quot;);
    main.innerHTML = &quot;&lt;div&gt;js loaded&lt;/div&gt;&quot;;
};</code></pre>
<p><code>&lt;a&gt;</code>태그와 window.location.href 값을 변경하는 방법을 통해 이동을 할 수 있지만, 페이지 자체를 새로 불러오기 때문에 같은 어플리케이션이라고 해도 깜박임이 발생하게 됩니다.
주소를 이동해도 페이지 자체가 새로고침이 되지 않기 위해서 
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/history">https://developer.mozilla.org/en-US/docs/Web/API/Window/history</a></p>
<p>window.history의 pushState 메서드를 사용하여 주소 변경을 할 수 있습니다.
pushState 메서드는 (데이터, 타이틀, URL)의 매개변수를 받도록 되어있습니다.
window.history.pushState(undefined,"타이틀","/some")
메서드를 실행시켜보면 주소창의 주소가 변경된 것을 확인할 수 있습니다.
주소가 바뀌었다면 이를 감지하여 <code>&lt;main&gt;</code>태그내의 콘텐츠를 바꾸는것이 라우팅의 원리가 되겠습니다.
주소가 변경된 것을 알리고 감지하기 위하여 locationchange라는 
<a href="https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent">https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent</a></p>
<p>커스텀 이벤트를 만들도록 하겠습니다.
index.js</p>
<pre><code class="language-javascript">window.onload = () =&gt; {
    const main = document.querySelector(&quot;main&quot;);

    const handleLocationChange = (e) =&gt; {
        console.log(&quot;locationchanged&quot;);
    };

    window.addEventListener(&quot;locationchange&quot;, handleLocationChange);

    main.innerHTML = &quot;&lt;div&gt;js loaded&lt;/div&gt;&quot;;
};</code></pre>
<p>주소 변경 이벤트를 발생시키는 버튼을 만들어 주소 변경 및 이벤트 감지를 확인해 보도록 하겠습니다.
index.js</p>
<pre><code class="language-javascript">window.onload = () =&gt; {
    const main = document.querySelector(&quot;main&quot;);

    const handleLocationChange = (e) =&gt; {
        console.log(&quot;locationchanged&quot;);

        //* 주소변경
        window.history.pushState(undefined, &quot;타이틀&quot;, &quot;/some&quot;);
    };

    //* locationchange 이벤트리스너
    window.addEventListener(&quot;locationchange&quot;, handleLocationChange);

    main.innerHTML = &quot;&lt;div&gt;&lt;button type=&#39;button&#39;&gt;move to /some&lt;/button&gt;&lt;/div&gt;&quot;;

    const button = document.querySelector(&quot;button&quot;);
    button.addEventListener(&quot;click&quot;, () =&gt; {
        const locationChangeEvent = new CustomEvent(&quot;locationchange&quot;, {
            composed: true, //웹 컴포넌트라면 넣어주세요
        });
        //* 주소변경 이벤트 Dispatch
        window.dispatchEvent(locationChangeEvent);
    });
};</code></pre>
<p>버튼을 클릭하면 콘솔 창에 메세지가 출력되고 브라우저의 주소가 변경되는 것을 확인할 수 있습니다.</p>
<p>locationchange 이벤트가 항상 같은 주소로 이동하는 것은 아니기에 변경할 주소를 인자로 받을 수 있도록 하도록 하겠습니다.
커스텀 이벤트를 생성할 때에 detail에 값을 주어 원하는 변수를 전달할 수 있습니다.
index.js</p>
<pre><code class="language-javascript">window.onload = () =&gt; {
    const main = document.querySelector(&quot;main&quot;);

    const handleLocationChange = (e) =&gt; {
        const { href } = e.detail;
        console.log(href);

        //* 주소변경
        window.history.pushState(undefined, &quot;타이틀&quot;, href);
    };

    //* locationchange 이벤트리스너
    window.addEventListener(&quot;locationchange&quot;, handleLocationChange);

    main.innerHTML = &quot;&lt;div&gt;&lt;button type=&#39;button&#39;&gt;move to /some&lt;/button&gt;&lt;/div&gt;&quot;;

    const button = document.querySelector(&quot;button&quot;);
    button.addEventListener(&quot;click&quot;, () =&gt; {
        const locationChangeEvent = new CustomEvent(&quot;locationchange&quot;, {
            composed: true,
            detail: { href: &quot;some&quot; },
        });

        //* 주소변경 이벤트 Dispatch
        window.dispatchEvent(locationChangeEvent);
    });
};</code></pre>
<p>주소이동에 성공했으니 주소에 맞게 콘텐츠를 변경하도록 하면 라우팅을 구현할 수 있습니다. 이때 window.location.pathname을 사용하여 라우팅 경로를 받아오도록 합니다. pathname을 사용하는 이유는 "/some? page=1"처럼 쿼리 값이 있을 때 쿼리 값을 제외한 경로를 받을 수 있기 때문입니다.
index.js</p>
<pre><code class="language-javascript">//* 경로에 맞는 콘텐츠 렌더
const renderContents = () =&gt; {
    const { pathname } = window.location;
    switch (pathname) {
        case &quot;/some&quot;:
            main.innerHTML = &quot;&lt;div&gt;some Contents&lt;/div&gt;&quot;;
            break;
        default:
            main.innerHTML = &quot;&lt;div&gt;404&lt;/div&gt;&quot;;
    }
};

const handleLocationChange = (e) =&gt; {
    const { href } = e.detail;

    //* 주소변경
    window.history.pushState(undefined, &quot;타이틀&quot;, href);
    //* 콘텐츠 렌더링
    renderContents();
};</code></pre>
<p>locationchange 커스텀 이벤트를 통해 라우팅을 하는데 성공했지만, 뒤로 가기 혹은 앞으로 가기를 할 때 주소는 이동하지만 콘텐츠가 바뀌지 않는 것을 볼 수 있습니다.
이를 위해 뒤로 가기 및 앞으로가기 이벤트를 감지하는 
<a href="https://developer.mozilla.org/ko/docs/Web/API/Window/popstate_event">https://developer.mozilla.org/ko/docs/Web/API/Window/popstate_event</a></p>
<p>popstate 이벤트를 이용하여 변경된 주소로 콘텐츠를 변경해 주도록 하겠습니다.
index.js</p>
<pre><code class="language-javascript">window.addEventListener(&quot;popstate&quot;, () =&gt; {
    renderContents();
});</code></pre>
<p>이제 페이지 이동, 뒤로 가기, 앞으로 가기로 콘텐츠를 불러오게 되었습니다. 하지만 이러한 방식에는 문제가 있는데 동일한 경로로 이동을 하게 될 경우 history 스택에 같은 경로가 쌓이게 되고, 뒤로 가기 시 같은 페이지가 나오는 문제가 발생합니다.</p>
<p>띠라서 주소이동시에 같은경로라면 이동하지 않도록 작업을하도록 하겠습니다.
index.js</p>
<pre><code class="language-javascript">
button.addEventListener(&quot;click&quot;, () =&gt; {
    const targetUrl = &quot;some?foo=bar&quot;;
    const { pathname } = window.location;

    //* 같은 URL 은 스택에 추가하지 않는다
    if (targetUrl === pathname) {
        return;
    }

    const locationChangeEvent = new CustomEvent(&quot;locationchange&quot;, {
        composed: true,
        detail: { href: &quot;some&quot; },
    });

    //* 주소변경 이벤트 Dispatch
    window.dispatchEvent(locationChangeEvent);
});</code></pre>
<p>자주 사용하게 될 주소 이동을 함수로 만들고, 내부 콘텐츠를 switch 내에서 require 하여 동적으로 콘텐츠를 불러올 수도 있습니다. 이러한 작업은 작업자가 필요에 따라 추가해 주시면 됩니다.</p>
<p>앞에서 다룬 내용을
<a href="https://github.com/jerrynim/vanillajs-router/tree/master">https://github.com/jerrynim/vanillajs-router/tree/master</a></p>
<p>깃허브에 예제를 통해 올리도록 하겠습니다.
도움이 되었다면 좋겠네요ㅎㅎ</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2capcha with nodejs]]></title>
            <link>https://velog.io/@jerrynim_/2capcha-with-nodejs</link>
            <guid>https://velog.io/@jerrynim_/2capcha-with-nodejs</guid>
            <pubDate>Tue, 13 Apr 2021 13:06:16 GMT</pubDate>
            <description><![CDATA[<p><a href="https://2captcha.com/?utm_medium=content&amp;utm_source=velogio&amp;utm_campaign=korea">2Captcha</a>는 실시간으로 캡차 문제를 풀어주도록 하여 비용을 지불하거나, 캡차 문제를 풀어냄으로써 돈을 버는 사이트 입니다.
인터넷을 사용하다보면 캡차로 인하여 시간을 허비하는 경우가 있고 어려운 퍼즐을 풀 때에는 지치고 성가십니다. 또 어떤 때에는 퍼즐이 안풀리기도 하여 많은 시간을 허비하게 되는데, 이 때 캡차를 빠르게 해결하기를 원하는 사람은 2Capcha를 이용하여 풀어낼 수 있습니다. 
2Captcha를 bypassing Captcha 솔루션이라고도 할 수 있겠습니다.</p>
<p><a href="https://2captcha.com/?utm_medium=content&amp;utm_source=velogio&amp;utm_campaign=korea">2Captcha</a>가 캡차를 풀어내는 방식은 이러합니다.
사용자가 캡차문제를 2Captcha로 보낸다. =&gt; 2Cpatcha에서는 문제를 풀어주는 사람들이 있는데, 이 사람들이 문제를 실시간으로 풀어줍니다!</p>
<p>문제를 빠르게 풀기 원하는 사람은 이에대하여 비용을 지불하고, 반대로 문제를 풀어 준 사람은 돈을 벌게 됩니다.</p>
<p>직접 확인하기 위하여 크롬 익스텐션의 &#39;2Captcha Solver&#39;를 설치하여 캡차를 풀기를 시도하였더니, 어려운 문제임에도 캡차가 풀리게 되었습니다.
<img src="https://images.velog.io/images/jerrynim_/post/6d80d5a0-2e18-4683-b36f-f09f34bd4896/image%201.jpg" alt=""></p>
<p> 2Captcha는 크롬 익스텐션이외에도 캡차를 풀어낼 수 있는 API를 제공하고 있습니다.
 2Cpatcha API를 사용하여 캡차를 풀어보도록 하겠습니다.</p>
<p> 간단한 express서버를 연후 캡차를 풀어내도록 요청하도록 하겠습니다</p>
<pre><code class="language-javascript">import express from &quot;express&quot;;

const app = express();

app.get(&quot;/captcha&quot;, (req, res) =&gt; {

    return;
});

app.listen(4000, () =&gt; {
    console.log(&quot;server started at http://localhost:4000🚀&quot;);
});</code></pre>
<p> express 서버를 만들었으니 <a href="https://github.com/infosimples/node_two_captcha">https://github.com/infosimples/node_two_captcha</a> 
 2captcha에서 제공하는 API를 설치하도록 하겠습니다.</p>
<blockquote>
<p>$ yarn add @infosimples/node_two_captcha</p>
</blockquote>
<p> 설치가 완료 되었다면 가이드를 따라 2Captcha API를 실행해 보도록 하겠습니다. 저희는 reCaptcha v2 를 해독하는 기능을 사용 해 볼 것이고, 구글이 제공해주는 reCaptcha v2의 데모 사이트 캡차를 풀어보도록 하겠습니다.</p>
<ul>
<li><p>2Captch api를 사용하기 위해서는 2captcha api key 가 필요합니다. 가입을 한 후 대쉬보드에서 확인 할 수 있습니다.</p>
<pre><code class="language-javascript">app.get(&quot;/captcha&quot;, (req, res) =&gt; {
 const client = new Client(&quot;your_key&quot;, {
     timeout: 60000,
     polling: 5000,
     throwErrors: false,
 });

 client
     .decodeRecaptchaV2({
         googlekey: &quot;6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-&quot;,
         pageurl: &quot;https://my-app-3x1qowiru-jerrynim.vercel.app/&quot;,
     })
     .then((response) =&gt; {
         console.log(&quot;captcha solved&quot;);
         console.log(response?.text);
     })
     .catch((e) =&gt; {
         console.log(e.message);
     });

 return;
});</code></pre>
<p>캡차를 풀어내기 위해서는 인자 값인 googlekey와 pageurl 이 필요한데요. 그 값은 개발자도구를 연 후다음 그림과 같이 date-key를 찾으시면 됩니다.
<img src="https://images.velog.io/images/jerrynim_/post/59633721-fa70-4638-9aed-c6df03de89f8/image.png" alt=""></p>
</li>
</ul>
<p>이 값은 reCaptcha v2 를 사용할때 필요한 sitekey 입니다. 이 키 값을 api의 googlekey에 넣어주도록 한 후 서버를 켜도록 하겠습니다.
그리고 대망의 &quot;/capcha&quot; api를 실행해 보도록 하겠습니다.</p>
<blockquote>
<p>$ curl --request GET <a href="http://localhost:4000/captcha">http://localhost:4000/captcha</a></p>
</blockquote>
<p> 캡차가 풀리기 까지 기다리면 잠시 후에 콘솔 출력과 함께 키 값을 받게 됩니다!
<img src="https://images.velog.io/images/jerrynim_/post/8113707d-0d66-43fb-81cb-cc48c0736c8a/image.png" alt=""></p>
<p> 이 값을 이제 복사하여서 캡차가 있는 사이트로 이동하여 개발자 콘솔을 킨후 다음과 같이 입력합니다.
 <code>document.getElementById(&quot;g-recaptcha-response&quot;).innerHTML=&quot;TOKEN_FROM_2CAPTCHA&quot;;</code>
 <img src="https://images.velog.io/images/jerrynim_/post/2f58c7f8-1c3f-4f77-9a70-ae66bbf071db/image.png" alt=""></p>
<p> 이제 &#39;제출&#39; 버튼을 클릭하면 다음 그림과 같이 인증이 성공하신 것을 볼 수 있습니다!
 <img src="https://images.velog.io/images/jerrynim_/post/a82668f7-b1bf-4b52-b60c-6aa384566012/image.png" alt=""></p>
<p>앞의 과정에서 &#39;sitekey 값을 찾는 것&#39;과 &#39;인증 키 값을 복사하는 것&#39; 그리고 &#39;인증 키 값을 넣어주는 것&#39;은 전부 수작업을 통하여 하였는데요. 자신의 프로그램을 테스트 할때 이러한 과정은 자동화를 통하여 충분히 해결 할 수 있으니, 테스트할 때 2Captcha를 사용한다면 될 것 같습니다.</p>
<p> 해당 코드는 <a href="https://github.com/jerrynim/with-2captcha">https://github.com/jerrynim/with-2captcha</a> 에서 확인할 수 있습니다.</p>
<ul>
<li>이 글은 <a href="https://2captcha.com/?utm_medium=content&amp;utm_source=velogio&amp;utm_campaign=korea">2Captcha</a>의 지원을 받아 작성합니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[캡챠(CAPTCHA)를 알아보자 (로봇이 아닙니다! 🤖 )]]></title>
            <link>https://velog.io/@jerrynim_/%EC%BA%A1%EC%B1%A0CAPTCHA%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90%EB%A1%9C%EB%B4%87%EC%9D%B4-%EC%95%84%EB%8B%99%EB%8B%88%EB%8B%A4</link>
            <guid>https://velog.io/@jerrynim_/%EC%BA%A1%EC%B1%A0CAPTCHA%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90%EB%A1%9C%EB%B4%87%EC%9D%B4-%EC%95%84%EB%8B%99%EB%8B%88%EB%8B%A4</guid>
            <pubDate>Sun, 11 Apr 2021 13:01:03 GMT</pubDate>
            <description><![CDATA[<p><code>그림 google.com 캡차 설명 화면 캡처 제공</code></p>
<p>인터넷을 사용하다 보면 자주 볼 수 있는 보안 프로그램이 있습니다.
다음 그림처럼 &#39;로봇이 아닙니다&#39;, &#39;이미지를 선택해 주세요&#39; 등의 프로그램을 자주 볼 수 있는데요. </p>
<p><img src="https://images.velog.io/images/jerrynim_/post/a0a5ec9b-4f87-4b68-a3b9-fe2c80bd515a/image.png" alt=""></p>
<p><code>[그림1 인터넷에서 자주 보이는 보안 프로그램]</code> - <a href="https://statkclee.github.io/deep-learning/r-captcha.html">https://statkclee.github.io/deep-learning/r-captcha.html</a></p>
<p>이러한 프로그램은 캡차(CAPTCHA)라고 부릅니다.
캡차로 인해 번거롭고, 퀴즈에 실패하면 짜증이 납니다.(난 사람인데 왜 로봇이라고 하지??)
일반 유저에게는 귀찮기만 한 대상이겠지만, 개발자로서 캡차를 사용하는 의미를 알아보았습니다.</p>
<h3 id="captcha">CAPTCHA??</h3>
<p>CAPTCHA는 <code>Completely Automated Public Turing test to tell Computers and Humans Apart</code> 의 약어로 &#39;컴퓨터와 사람을 구분하기 위한 완전히 자동화된 <a href="https://www.google.com/search?q=%ED%8A%9C%EB%A7%81+%ED%85%8C%EC%8A%A4%ED%8A%B8&amp;oq=%ED%8A%9C%EB%A7%81+%ED%85%8C%EC%8A%A4%ED%8A%B8&amp;aqs=chrome..69i57j0l9.1655j0j7&amp;sourceid=chrome&amp;ie=UTF-8">튜링 테스트</a>&#39;를 의미합니다.</p>
<p>사용자에게 컴퓨터는 구분할 수 없는 텍스트나 이미지 혹은 소리를 재생하여 컴퓨터와 사람을 구분하게 됩니다. 
그렇다면 왜! 사람과 컴퓨터를 구분하려고 하는 것일까요?</p>
<p>봇은 사이트에 접속하여 다음과 같을 악의적인 행위를 할 수 있습니다.</p>
<ol>
<li>사이트의 게시물 혹은 댓글에 스팸성 글을 작성합니다.</li>
<li>검색 엔진 순위를 높이기 위해 링크가 있는 댓글 섹션을 폭격합니다.</li>
<li>사용자의 이메일 주소를 복사합니다.</li>
<li>공연 티켓 등의 물품을 대량 구매합니다.</li>
</ol>
<p>이러한 봇의 행동은 사용자들에게 불편함을 줄 수 있으며, 사이트의 신뢰도를 떨어트릴 수 있습니다.
이를 위하여 여러 사이트에서 CAPTCHA를 사용하여 반복된 사이트 접속 처단, 회원가입, 사용자 인증, 게시물 게시 등에 사용이 되고 있습니다.
만약 봇들이 계정 생성을 자유롭게 하고, 게시물을 만들 수 있게 된다면 해당 사이트에는 스팸과 광고성 게시물들로 도배를 하고, 사용자들에게 스팸 이메일을 보낼 수 있게 됩니다.</p>
<h3 id="캡차사용하기">캡차사용하기</h3>
<p>캡차 서비스를 제공하는 다양한 서비스가 있습니다. 구글 reCAPTCHA, hcpatcha, geeTest 등이 있는데요. 저는 이중에 구글 reCAPTCHA v2 를 사용해 보도록하겠습니다. reCAPTCHA v2는 여러분에게 가장 익숙하고 편리한 체크박스 버튼을 가진 &#39;로봇이 아닙니다&#39;를 제공합니다.
<a href="https://developers.google.com/recaptcha/docs/display">reCAPTCHA 공식 가이드</a>를 따라서 다음의 코드를 실행함으로써 간단하게 사용할 수 있습니다.</p>
<pre><code class="language-javascript">&lt;html&gt;
  &lt;head&gt;
    &lt;title&gt;reCAPTCHA demo: Explicit render after an onload callback&lt;/title&gt;
    &lt;script type=&quot;text/javascript&quot;&gt;
      var onloadCallback = function() {
        grecaptcha.render(&#39;html_element&#39;, {
          &#39;sitekey&#39; : &#39;your_site_key&#39;
        });
      };
    &lt;/script&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;form action=&quot;?&quot; method=&quot;POST&quot;&gt;
      &lt;div id=&quot;html_element&quot;&gt;&lt;/div&gt;
      &lt;br&gt;
      &lt;input type=&quot;submit&quot; value=&quot;Submit&quot;&gt;
    &lt;/form&gt;
    &lt;script src=&quot;https://www.google.com/recaptcha/api.js?onload=onloadCallback&amp;render=explicit&quot;
        async defer&gt;
    &lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre>
<p>가이드를 따라 사이트에 실행된 코드를 올려보았는데요, <a href="https://my-app-3x1qowiru-jerrynim.vercel.app/">https://my-app-3x1qowiru-jerrynim.vercel.app/</a>
캡차를 풀게되면 콜백을 실행시키는 방식으로 캡차를 사용할 수 있습니다!!</p>
<h3 id="captcha의-한계">CAPTCHA의 한계</h3>
<ol>
<li>사용자 경험 저하와 이탈
캡차를 사용하여 봇의 행위를 지연시키거나 일부 막을 수 있지만, 사용자에게 불편함을 주게 됩니다.
대표적인 예로 디스코드 사용 시에 캡차만 십여분 동안 풀고 있으면 사용하기 싫어질 만큼 화가 납니다.
<img src="https://images.velog.io/images/jerrynim_/post/4a4c49ed-9f3b-45b4-94c9-bf2d8a7d65de/image%202.jpg" alt="">
<code>[그림2 구글 디스코드 로봇 연관검색어]</code></li>
</ol>
<p>어려운 난이도의 문제일수록 캡차를 푸는 것은 시간이 오래 걸리고, 스트레스를 유발하고, 그로 인해 사용자들이 캡차 도중에 이탈이 발생할 수 있습니다. 
 2. 100% 봇을 막을 수 없다.</p>
<p> 캡차는 주로 텍스트나 이미지 인식을 사용하여 봇을 구분하게 되는데, 봇은 <a href="https://www.boannews.com/media/view.asp?idx=93947">AI를 이용하여 텍스트와 이미지를 인식하는데 시간이 걸리지 않는다는 뉴스</a>를 쉽게 찾아볼 수 있을 만큼 간단하게 캡차를 풀어냅니다. 
또한, 여러 개발자들이 데이터를 크롤링하기 위하여 언캡차를 위한 다양한 방법들을 고안해 내며 캡차를 우회하려고 하고 있습니다!!(앞의 2Cpatcha API를 이용하여도 언캡차를 할 수도 있습니다.)
이를 막기 위해 더 어려운 문제를 준다면 사용자 경험이 떨어지게 되니 캡차를 사용하는 것에 어려움이 있습니다.</p>
<h3 id="그래서-캡차를-사용해야-할까">그래서 캡차를 사용해야 할까?</h3>
<p>한국 사이트에서는 캡차를 사용하는 경우가 외국보다 적습니다. 가입 시에 주민등록번호, 휴대폰 인증, 공인인증서 등의 인증과정을 통하여 확인을 하기 때문에 필요성이 낮다고 생각합니다. 그래서 로그인 시도를 여러 번 하였을 때, 휴대폰 인증을 받으려 할 때처럼 인증 없이 여러 악의적인 행동이 가능할 때 캡차를 사용하여 봇을 방지하는 사례를 많이 볼 수 있습니다.</p>
<p>이메일과 비밀번호로만으로 간단하게 가입이 되는 서비스의 경우 봇이 활동하기에 편리하기에, 캡차를 활용하여 봇의 활동을 지연시키거나, 일부 방지할 필요가 있다고 생각합니다. 임의의 이메일을 만드는 것은 너무나 쉽기 때문에 수많은 봇 계정이 생성될 수 있으니까요.</p>
<h3 id="마치며">마치며</h3>
<p>내가 봇에 의한 공격을 신경 쓰며 개발을 한 적이 있는가? 에 대답은 NO입니다. 서비스를 만들면서 보안에 대한 이슈도 생각해보아야 하는데 캡챠를 알아보며 조금은 생각해볼 수 있었네요. 벨로그에도 종종 광고성 댓글들이 달리던데 해결되면 좋겠네요 ㅎㅎ.</p>
<p>출처</p>
<p><a href="https://www.onsightapp.com/blog/why-do-we-need-captchas#:~:text=CAPTCHA%20prevents%20spam%20in%20website%20comment%20sections%20and%20on%20blogs.&amp;text=Free%20services%20should%20be%20protected,are%20posted%20in%20clear%20text.">Why do we need CAPTCHAs?</a></p>
<p><a href="https://www.imperva.com/learn/application-security/what-is-captcha/#:~:text=CAPTCHA%20stands%20for%20the%20Completely,but%20relatively%20easy%20for%20humans.">CAPTCHA inperva</a></p>
<p><a href="https://ko.wikipedia.org/wiki/CAPTCHA#%EC%A2%85%EB%A5%98">CAPTCHA wikipedia</a></p>
<p><a href="https://hive.blog/kr/@ai1love/captcha">captcha와 사람...</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[개발자의 디자인 시스템 회고]]></title>
            <link>https://velog.io/@jerrynim_/%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%94%94%EC%9E%90%EC%9D%B8%EC%8B%9C%EC%8A%A4%ED%85%9C%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@jerrynim_/%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%94%94%EC%9E%90%EC%9D%B8%EC%8B%9C%EC%8A%A4%ED%85%9C%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Sun, 21 Mar 2021 11:23:42 GMT</pubDate>
            <description><![CDATA[<p>근래에는 회사에서 디자인 시스템을 도입하였다는 소식을 쉽게 찾아볼 수 있습니다. <a href="https://www.surfit.io/tag/%EB%94%94%EC%9E%90%EC%9D%B8%20%EC%8B%9C%EC%8A%A4%ED%85%9C">링크</a></p>
<p>저 또한 디자인 시스템을 도입하고 정착시키는 과정을 겪었습니다.</p>
<p>개발자 입장에서 디자인 시스템을 도입한 경험을 돌아보고, 또 앞으로 어떻게 발전시킬 수 있을지 돌아보도록 하겠습니다.</p>
<h3 id="디자인-시스템이란">디자인 시스템이란??</h3>
<p>디자인 시스템에 관한 설명은 <a href="https://brunch.co.kr/@thinkaboutlove/320">10분 만에 읽는 디자인 시스템 A to Z</a>을 참고해 주세요. </p>
<p><strong>제가 사용해보면서 겪은 디자인 시스템은 이러했습니다.</strong></p>
<p>디자이너들은 제품의 아이덴티티, 디자인, 컴포넌트, 디자인 가이드 등을 고려하여 디자인 시스템을 구축하고 개발자에게 UI, UX 가이드라인을 제공합니다.</p>
<p>개발자는 전달받은 내용을 토대로 어떻게 만들지를 고민하게 됩니다. 주로 컴포넌트를 만들어서 사용하게 되는데, 가이드라인에 따라서 컴포넌트를 제작하고 재사용할 수 있도록 만듭니다. 이 과정에서 개발자는 디자인 시스템은 디자이너들이 제공하는 UI, UX 가이드라인에 따라 만든 단순 &#39;UI kit&#39;라고 생각하게 되기 쉽습니다.</p>
<p>폰트, 아이콘, 레이아웃, 규격 등은 디자이너분들께서 정의하였으니, 개발자가 신경 써야 할 부분은 이것을 어떻게 사용할지에 대해 고민을 할 뿐입니다.</p>
<h2 id="실제-사용한-방법">실제 사용한 방법</h2>
<h3 id="폰트-및-색상">폰트 및 색상</h3>
<p>폰트 크기 및 색상, 반응형의 크기는 변수를 사용하여 사용하였습니다.</p>
<pre><code class="language-typescript">


export const palette = {

red_500: &quot;red&quot;,

  gray_500: &quot;#ddd&quot;,

    ...

}  

export const  ft21 = css`

font-size: 21px;

        line-height: 1.5;

`

...
</code></pre>
<p>폰트 크기와 색상을 사용할 때에는 변수 이외의 값을 사용하지 않는다는 규칙을 정하도록 합니다.</p>
<pre><code class="language-typescript">
//? 빨간색 글씨의 21px 글자



//good

const style = styled.div`

color: ${palette.red_500};

        ${ft21};

`



//bad

const style= {color:&quot;red&quot;, font-size:21px;} // 색상 불일치, line-height 불일치
</code></pre>
<p>앞의 예제처럼 개발자들끼리 디자인 시스템을 사용하기 위해서는 규칙을 만들게 되고, 이에 따라 가이드라인이 필요하게 되었습니다.</p>
<blockquote>
<p>이 때는 적은 인원이기에 이러한 규칙들을 서로 알기만 하고 문서화시키지 않았었는데, 새로운 개발자가 왔을 때 이러한 규칙들을 직접 전달해야만 했습니다. 후속 개발자를 위해 가이드라인의 필요성이 느껴졌습니다.</p>
</blockquote>
<h3 id="컴포넌트">컴포넌트</h3>
<p>대부분의 디자인 가이드를 위해 인풋, 버튼, 실렉터, 등의 간단한 컴포넌트를 만들게 됩니다.</p>
<p>컴포넌트를 만들 때에는 리액트 애플리케이션 구조에서 <a href="https://ui.toast.com/weekly-pick/ko_20200213">아토믹(atomic) 디자인</a>을 따라 atom이라 생각되는 컴포넌트를 만듭니다.</p>
<p>디자인만 반영된 컴포넌트는 기존의 html 태그를 스타일만 더하여 제작합니다. 이렇게 하여 사용하는 개발자가 추가적으로 알아야 할 것이 없어 사용하는데 어려움이 없습니다.</p>
<p>드롭다운 및 모달 등의 기능이 필요한 컴포넌트는 hooks를 사용하여 기능들을 쉽게 불러와 사용할 수 있도록 하였습니다.</p>
<pre><code class="language-typescript">


const {openModal, closeModal, ModalWrapper} = useModal();



  ...  

  &lt;Button onClick={openModal}&gt;모달열기&lt;/Button&gt;

  &lt;ModalWrapper&gt;

     &lt;ModalContents /&gt;

  &lt;/ModalWrapper&gt;


</code></pre>
<p>여기까지 사용한다면 디자인 시스템 아니 &#39;UI kit&#39;를 만들어 사용하는 데에는 큰 어려움이 없습니다. 하지만 서비스는 다양한 요구를 하기 마련이죠.</p>
<p>예를 들어 간단한 버튼이 있는데, 이 버튼에 아이콘도 추가되어야 하고, 색도 바뀌어야 하고, hover 방식도 3가지가 되고, 아이콘 위치도 바뀔 수 있고, 버튼이 2등분으로 나눠질 때도 있고...</p>
<p>이렇게 조건이 하나 생길 때마다 개발자들은 이에 대응하기 위해 property를 하나씩 추가하게 되고, 코드는 보기 힘들어지고, 저런 조건들은 결국 개발자들이 평소에 숙지하고 있어야 할 숙제가 되고 맙니다.</p>
<p>그러한 조건들을 만족하기 위해 어떻게 하면 효율적으로 작성하고 재사용할 수 있을까를 고민해보았었습니다.</p>
<p>디자인 가이드만 추가된 버튼 컴포넌트를 만들었습니다.</p>
<pre><code class="language-typescript">
interface ButtonProps extends React.ButtonHTMLAttributes&lt;HTMLButtonElement&gt; {}



const Button: React.FC&lt;ButtonProps&gt; = ({ children, ...props }) =&gt; {

return (

&lt;button className=&quot;button-style&quot; {...props}&gt;

{children}

&lt;/button&gt;

)

}
</code></pre>
<p>앞서 예제처럼 다양한 옵션들을 추가하면 다음처럼 변형이 됩니다.</p>
<pre><code class="language-typescript">
interface ButtonProps extends React.ButtonHTMLAttributes&lt;HTMLButtonElement&gt; {

color: ColorType

size: SizeType

icon?: React.ReactElement

        iconPlace?:IconPlaceType

hover?: ButtonHoverType

half?: boolean

}
</code></pre>
<pre><code class="language-typescript">
const Button: React.FC&lt;ButtonProps&gt; = ({ children, color,...props }) =&gt; {

    if(icon)...

    if(hover)...

    if(half)...

return (

&lt;button className=&quot;button-style&quot; color={color}  {...props}&gt;

{children}

&lt;/button&gt;

)

}
</code></pre>
<p>조건이 생길수록 숙지해야 할 옵션들이 많아지고, 코드도 복잡해져서 유지보수 측면에서 점점 난이도가 올라가게 됩니다.</p>
<p>그렇다면 컴포넌트를 나누는 방법은 어떤가요.</p>
<pre><code>
&lt;ButtonWithIcon&gt;

&lt;HalfButton&gt;

&lt;BlueButton&gt;

&lt;YellowButton&gt;
</code></pre><p>이러한 방식으로 코드를 분리를 할 수 있게 되겠지만 모양이 바뀌면 그만큼의 파일을 수정해야 하고, 결국 숙지해야 하는 건 같습니다. props로 구분하냐 컴포넌트로 구분하냐의 차이일 뿐입니다.</p>
<p>결론적으로, 저는  다양한 옵션들을 지원하기 위해서 공통 컴포넌트에 옵션을 추가하지 않고, 각각 따로 만들어 주었습니다.</p>
<pre><code class="language-typescript">
//? 아이콘이 들어가는 인풋

&lt;div className=&quot;search-input-wrapper&quot;&gt;

  &lt;Input/&gt;

  &lt;InputIcon className=&quot;search-input-icon&quot;/&gt;

&lt;/div&gt;
</code></pre>
<blockquote>
<p> inline style 대신 wrapper를 추가하는 것을 선호합니다.</p>
</blockquote>
<p>매번 아이콘 넣어주는 거 번거롭지 않나요? 네. 번거롭습니다. 그래서 이것을 분자(Molecules)로 만들어 사용하게 됩니다.</p>
<pre><code class="language-typescript">
&lt;form&gt;

  &lt;SearchInput/&gt; // 분자

  &lt;ul className=&quot;result&quot;&gt;

       &lt;li&gt;result&lt;/li&gt;

  &lt;/ul&gt;

&lt;/form&gt;
</code></pre>
<p>사실 분자로 만들지도 않았습니다. 재사용의 필요성이 없다면 분자로 만들지 않고, 필요성이 생겼을 때에만 분자로 만들어주었습니다. 실제로 여러 명이 비슷한 기능을 만들었고, 후에 하나의 컴포넌트로 통일하는 작업을 하기도 했습니다. 매번 만드는 것은 번거롭지만, 반대로 예외 상황에서는 자유로울 수 있었습니다. 가령 아이콘 옆에 구분 바를 만든다고 하면 옵션을 하나 더 추가해야겠지만 따로 만들면 해당 부분만 수정하면 됐습니다.</p>
<p>우선 원자(atom)만을 사용하여 작업을 하고, 해당 옵션이 계속해서 필요로 할 경우에는 hooks나 공통 컴포넌트로 만들어 재사용하도록 하였습니다. 그 작업을 미리 하지는 않았습니다. 뭐든 필요에 의해서만 작업을 하기로 하고 있습니다.</p>
<pre><code class="language-typescript">
// 타이머 기능

const {hour,minutes,seconds} = useTimer();



// 페이지 이탈 방지 기능

&lt;&gt;

&lt;PreventRouting/&gt;

&lt;Content/&gt;

&lt;/&gt;
</code></pre>
<blockquote>
<p>사실 작업하기 전에 해당 옵션의 재사용 정도는 디자이너님들이 잘 알고 있어 미리 물어보는 게 좋습니다.</p>
</blockquote>
<p>원자에 옵션을 추가되거나, 분자가 만들까 만들어지게 되면 사용자에게 공유를 해야 합니다.</p>
<p>처음에는 구두로 전달했었습니다. 하지만, 이러한 컴포넌트들이 많아질수록 서로 놓치는 부분이 생기고, 커뮤니케이션 미스로 인하여 결과적으로 반영이 안 된 부분도 빈번했습니다.</p>
<p>전체 개발자에게 디자인 시스템에 대한 내용을 전달을 위한 문서가 필요해졌습니다.</p>
<p>이에 따라 <a href="https://storybook.js.org/">스토리북</a>을 도입하기로 합니다.</p>
<h2 id="스토리북">스토리북</h2>
<p>스토리북은 다음 그림처럼 컴포넌트 단위 개발을 하는데 도움을 주는 UI 개발 도구입니다.</p>
<p><img src="https://images.velog.io/images/jerrynim_/post/1f2ca353-ab4a-4c15-a14e-3a1331a93f06/image.png" alt=""></p>
<h3 id="장점">장점</h3>
<p>스토리북을 사용하여 다음과 같은 장점을 얻을 수 있습니다.(주관적)</p>
<ol>
<li><p>디자이너와 제작한 컴포넌트 공유 // 디자이너분들은 처음에만 봤던 것 같다.</p>
</li>
<li><p>&#39;docgen&#39; addon을 이용하여 컴포넌트의 property 정리 // 타입 스크립트를 사용하면 거의 안 봐요.</p>
</li>
<li><p>컴포넌트의 옵션들과 기능들을 직접 사용해보며 확인할 수 있다. // 거의 안 쓰더라</p>
</li>
<li><p>신규 개발자에게 가이드를 제공한다.(⭐️⭐️⭐️)</p>
</li>
</ol>
<p>이렇게 장점들을 가지고 있습니다</p>
<p>앞에서 말한 것처럼 저희 팀은 옵션을 잘 추가하지 않고, 분자를 많이 만들지도 않았습니다. 그에 따라 시간이 지나면서 개발자들이 &#39;UI kit&#39;를 사용하는데 익숙해졌고 스토리북을 보지 않고도 개발이 가능했습니다.(서버가 한 달 정도 꺼져있어도 찾는 사람이 없었다...)</p>
<p>신규 개발자가 팀에 합류하였고, 스토리북과 함께 예제를 몇 개 만들어 보면 금세 익숙해져서 스토리북의 역할이 끝났다. </p>
<h3 id="비용">비용</h3>
<p>스토리북을 제작하는 데는 어느 정도의 노력이 필요했나요?</p>
<blockquote>
<p>초반에 이해하고 설정을 하는 것에 시간이 걸린다.</p>
</blockquote>
<p>그 후에 스토리를 만드는 데에는 어렵지 않다.</p>
<p>옵션이나 분자가 추가된다면 내용을 업데이트해야 한다.</p>
<h3 id="결론">결론</h3>
<p>신규 입사자와 개발자 간의 공유를 위하여 문서화는 필요하였다. </p>
<p>스토리북이 도입되고 그러한 문제를 해결하였지만, 사실 시간이 해결해 준 문제였다.</p>
<p>하지만 이건 컴포넌트의 변경이 적고, 인원이 적은 팀에서 해결된 문제이다.</p>
<p>인원이 많고, 컴포넌트의 작업이 많을수록 스토리북은 힘을 발휘할 것이라고 예상합니다.</p>
<p><strong>좋았던 것들</strong></p>
<ul>
<li><p>디자이너와의 함께 사용하는 만큼 사용하는 언어에서도 통일을 컴포넌트의 구성도 통일하였을 때 소통과 개발이 편리하였던 경험이 있습니다.</p>
</li>
<li><p>스토리북에 다양한 예제를 제공할수록 이해하는데 도움이 되었습니다.</p>
</li>
</ul>
<p>결과적으로, &#39;디자인 시스템&#39;을 도입하여 전체적인 서비스의 통일성이 생겼습니다.</p>
<p>반복적인 작업은 컴포넌트를 재사용하면서 개발 속도를 낮출 수 있었고, 서비스의 확장성도 좋아졌습니다.</p>
<p>이를 위해서 디자이너와 소통을 많이 하였고,</p>
<p>발전을 거듭하며 사용하는 모두가 만족하는 디자인 시스템이 될 수 있었습니다.   </p>
<h2 id="앞으로">앞으로</h2>
<p>디자인 시스템에 관한 회고 글을 쓰면서 다른 글들을 찾아보면서 새롭게 깨달은 것이 있습니다. 내가 경험한 디자인 시스템은 &#39;UK kit&#39;에 불과했다는 것입니다. 참고. <a href="https://www.wedesignx.com/knowledge/design-system">디자인 시스템, 제대로 알고 제대로 만들어야 한다</a>, <a href="https://brunch.co.kr/@thinkaboutlove/320">10분 만에 읽는 디자인 시스템 A to Z</a></p>
<p>저는 디자인 시스템의 &#39;유형적인 요소&#39;만을 고려하였고, &#39;무형적인 요소&#39;를 고려해보지 못했습니다.</p>
<p>새로운 팀에 들어오면서 팀의 방향성, 일하는 방법, 사고방식들을 배우며 디자인 시스템에 대한 공부를 하고 있습니다. </p>
<p>디자인 시스템을 발전시키는 역할로써 개발자들이 디자인 시스템을 더 잘 이해하고 쉽게 사용할 수 있도록 노력하고자 합니다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[책을 쓰게 된 이야기 - 2부]]></title>
            <link>https://velog.io/@jerrynim_/%EC%B1%85%EC%9D%84-%EC%93%B0%EA%B2%8C-%EB%90%9C-%EC%9D%B4%EC%95%BC%EA%B8%B0-2%EB%B6%80</link>
            <guid>https://velog.io/@jerrynim_/%EC%B1%85%EC%9D%84-%EC%93%B0%EA%B2%8C-%EB%90%9C-%EC%9D%B4%EC%95%BC%EA%B8%B0-2%EB%B6%80</guid>
            <pubDate>Sun, 14 Mar 2021 14:31:06 GMT</pubDate>
            <description><![CDATA[<p>블로그에서 보기 &gt; <a href="https://jerrynim.dev/post/writing-book-2">https://jerrynim.dev/post/writing-book-2</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[책을 쓰게 된 이야기 - 1부]]></title>
            <link>https://velog.io/@jerrynim_/%EC%B1%85%EC%9D%84-%EC%93%B0%EA%B2%8C-%EB%90%9C-%EC%9D%B4%EC%95%BC%EA%B8%B0-1%EB%B6%80</link>
            <guid>https://velog.io/@jerrynim_/%EC%B1%85%EC%9D%84-%EC%93%B0%EA%B2%8C-%EB%90%9C-%EC%9D%B4%EC%95%BC%EA%B8%B0-1%EB%B6%80</guid>
            <pubDate>Sun, 07 Mar 2021 14:47:20 GMT</pubDate>
            <description><![CDATA[<p>블로그에서 보기 &gt; <a href="https://jerrynim.dev/post/writing-book-1">https://jerrynim.dev/post/writing-book-1</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[html, css 문제와 몰랐던 것들]]></title>
            <link>https://velog.io/@jerrynim_/html-css-%EB%AC%B8%EC%A0%9C%EC%99%80-%EB%AA%B0%EB%9E%90%EB%8D%98-%EA%B2%83%EB%93%A4</link>
            <guid>https://velog.io/@jerrynim_/html-css-%EB%AC%B8%EC%A0%9C%EC%99%80-%EB%AA%B0%EB%9E%90%EB%8D%98-%EA%B2%83%EB%93%A4</guid>
            <pubDate>Sun, 21 Feb 2021 12:36:00 GMT</pubDate>
            <description><![CDATA[<p>저는 프론트엔드 개발 2년차이지만 html, css, javascript를 책이나 글로 공부해 본 적이 없었습니다. 최근에 시간을 가지고 <a href="https://poiemaweb.com/">모던 자바스크립트 deep dive</a> 책을 하나씩 살펴보고 있습니다. 책에는 프론트엔드 면접 기출문제에서 보던 내용과 실제로 면접에서 받아보았던 내용들이 있습니다. 이 책을 보고 실제 면접에서 나올 수 있을만한 문제, 받아보았던 문제들과 대답 그리고 2년간 개발하면서 몰랐던 내용들을 공유하고자 합니다. 1편에서는 html, css 에 관한 내용을, 2편에서는 javascript에 관한 내용을 다루도록 하겠습니다.</p>
<h1 id="html">HTML</h1>
<p>-- <a href="https://poiemaweb.com/html5-semantic-web">시맨틱 태그(semantic tag)</a>는 무엇이고 어떤것이 있나요?</p>
<pre><code>시맨틱 태그란 브라우저, 검색엔진, 개발자 모두에게 콘텐츠의 의미를 명확히 설명하는 역할을 
하는 태그를 말하며, form, table, img, header, nav, aside, section, 
article, footer 등이 있습니다.</code></pre><h2 id="몰랐던-것들">몰랐던 것들</h2>
<p>-- b태그 대신 strong 태그를 사용하는것이 웹표준을 준수하는 방법이다.</p>
<h1 id="css">CSS</h1>
<p>-- <a href="https://poiemaweb.com/css3-selector">css 셀렉터</a>(or 선택자)는 무엇이고 어떤것이 있나요?</p>
<pre><code>셀렉터는 스타일을 적용하고자 하는 HTML 요소를 선택하기 위해 CSS에서 제공하는 수단이다.
셀렉터에는 종류는 다음과 같다.
 - 전체 셀렉터(*)
 - 태그 셀렉터(p, div)
 - ID 셀렉터(#id)
 - 클래스 셀렉터(.class)
 - 어트리뷰트 셀렉터(a[href])
 - 복합 셀렉터
    - 후손 셀렉터(div p)
    - 자식 셀렉터(div &gt; p)
    - 형제 셀렉터
      - 인접 형제 셀렉터 (div + p)
      - 일반 형제 셀렉터 (div ~ p)
 - 가상 클래스 셀렉터
    - 링크 셀렉터(:link)
    - 동적 셀렉터(:hover)
    - UI 요소 상태 셀렉터(:disabled)
    - 구조 가상 클래스 셀렉터(:first-child)
    - 부정 셀렉터(:not([type=password]))
    - 정합성 체크 셀렉터(:valid)
 - 가상 요소 셀렉터(::after)</code></pre><p> -- <a href="https://poiemaweb.com/css3-units">css 프로퍼티 값의 단위</a>는 어떤게 있나요?</p>
<pre><code> px : px은 픽셀(화소) 단위이다.
 % : %는 백분률 단위의 상대 단위이다.
 em : em은 배수(倍數) 단위로 상대 단위이다. 요소에 지정된 사이즈(상속된 사이즈나
 디폴트 사이즈)에 상대적인 사이즈를 설정한다.
 rem : rem은 최상위 요소(html)의 사이즈를 기준으로 삼는다. rem의 r은 root를 의미한다.</code></pre><p> -- HTML요소의 block 특성과 inline 특성에 대해 아시나요?</p>
<pre><code> block

항상 새로운 라인에서 시작한다.
화면 크기 전체의 가로폭을 차지한다. (width: 100%)
width, height, margin, padding 프로퍼티 지정이 가능하다.
block 레벨 요소 내에 inline 레벨 요소를 포함할 수 있다
block 레벨 요소 예
div, h1 ~ h6, p, ol, ul, li, hr, table, form 

inline 

새로운 라인에서 시작하지 않으며 문장의 중간에 들어갈 수 있다. 즉, 줄을 바꾸지 않고 다른
요소와 함께 한 행에 위치한다.
content의 너비만큼 가로폭을 차지한다.
width, height, margin-top, margin-bottom 프로퍼티를 지정할 수 없다.
상, 하 여백은 line-height로 지정한다.
inline 레벨 요소 뒤에 공백(엔터, 스페이스 등)이 있는 경우, 정의하지 않은 space(4px)가 자동 지정된다.
inline 레벨 요소 내에 block 레벨 요소를 포함할 수 없다. inline 레벨 요소는 일반적으로 block 레벨 요소에 포함되어 사용된다.
inline 레벨 요소 예 : span, a, strong, img, br, input, select, textarea, button

inline-block
block과 inline 레벨 요소의 특징을 모두 갖는다. inline 레벨 요소와 같이 한 줄에 표현되면서
width, height, margin 프로퍼티를 모두 지정할 수 있다.</code></pre><p> <a href="https://poiemaweb.com/css3-display">https://poiemaweb.com/css3-display</a></p>
<p> -- <a href="https://poiemaweb.com/css3-inheritance-cascading#21-%EC%A4%91%EC%9A%94%EB%8F%84">css 적용 우선순위</a>는 어떻게 되나요?</p>
<pre><code> CSS가 어디에 선언 되었는지에 따라서 우선순위가 달라진다.

1. head 요소 내의 style 요소
2. head 요소 내의 style 요소 내의 @import 문
3. &lt;link&gt; 로 연결된 CSS 파일
4. &lt;link&gt; 로 연결된 CSS 파일 내의 @import 문
5. 브라우저 디폴트 스타일시트</code></pre><p> -- 콘텐츠를 중앙에 위치시키기 위해서 어떻게 할 수 있을가요?
<a href="https://poiemaweb.com/css3-centering">https://poiemaweb.com/css3-centering</a></p>
<h2 id="몰랐던-것들-1">몰랐던 것들</h2>
<p> -- <a href="https://poiemaweb.com/css3-removing-white-space-image-element">image 요소 아래에 패딩된 여분의 공간이 생기는 이유와 제거하는 법</a></p>
<pre><code>이를 제거하기위해 line-height: 0; 또는 margin-top:-1px;
등의 방법을 사용하였었는데 정확한 이유와 방안을 알 수 있다.</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[첫 이직 여정기]]></title>
            <link>https://velog.io/@jerrynim_/%EB%82%98%EC%9D%98-%EC%B2%AB-%EC%9D%B4%EC%A7%81-%EC%97%AC%EC%A0%95%EA%B8%B0</link>
            <guid>https://velog.io/@jerrynim_/%EB%82%98%EC%9D%98-%EC%B2%AB-%EC%9D%B4%EC%A7%81-%EC%97%AC%EC%A0%95%EA%B8%B0</guid>
            <pubDate>Wed, 03 Feb 2021 06:40:20 GMT</pubDate>
            <description><![CDATA[<p>12월 초에 무턱대고 20개의 공고를 지원했습니다. 그렇게 갑자기 저의 이직 여정이 시작되었습니다. 이직을 위해 따로 준비하거나 공부를 하지 않아서 자신은 없었지만, 일단 겪어보기로 하였습니다. 채용과정을 진행하게 된 회사의 이름은 크게 중요하지 않다고 생각하여 이니셜로만 표시하도록 하겠습니다. </p>
<h2 id="r회사">&#39;R&#39;회사</h2>
<p>다음 날 바로 서류합격 전화를 받게 되었다. </p>
<h3 id="코딩테스트">코딩테스트</h3>
<p>코딩테스트를 보게되어 이메일로 프로그래머스 코딩테스트 안내 메일을 받았다. 코딩테스트 문제는 알고리즘 문제로 준비를 하지 않고, 평소에는 다뤄보지 못한 내용이이게 바로 탈락을 하게 되었다.(주관적으로 프로그래머스 2-4단계??일 것 같다.)</p>
<h2 id="k회사">&#39;K&#39;회사</h2>
<p>24일정도 뒤에 서류합격 안내를 받았고, 사전 과제를 받게 되었다. 과제기간은 5일 주어줬고, 평일중에 하기로 하였다.</p>
<h3 id="과제">과제</h3>
<p> 자바스크립트만을 이용하여 알람 프로그램을 만드는 것이였다. 기능명세가 어느정도 자세하게 기재되어있었고, ie10을 지원해야한다는 특이사항이 있었다. 웹팩과 함께 간단하게 구현하였고, ie에서 실행하니 작동하지 않아 2식간정도 babel 플러그인 추가 및 수정을 하여야 했다. 
 2일의 기간이 소요되었고, 사전 과제를 합격하여 면접을 보게 되었다.</p>
<h3 id="면접">면접</h3>
<p>해당 회사 면접에 대해 찾아보았으나 정보가 없었다. 크게 면접 준비를 하지 않았고 화상 면접을 보게되었다. 면접은 1:3으로 진행이 되었고, 시작부터 무엇인가 싸늘했다. 아이스 브레이킹 없이 안내와 함께 일반적인 질문, 자바스크립트 코어 질문, 리액트 질문을 받게 되었다. 일반적인 문제부터 생각해두지 않아서 당황했고, 평소에 가볍게 읽은 자바스크립트 코어정보를 전문가 세명에게 설명을 하려니 머리가 하얘지고 꼬투리만 잡히게 되었다. 대답을 잘 하지 못해 부끄러워지고 빨리 끝내고 싶은 마음뿐이였다. 결과적으로 서로 불편한 면접이 되었고, 자바스크립트 코어에 대한 나의 약점을 인정하고 받았던 질문들을 정리하였다. 그리고 지금도 자바스크립트 코어는 보고 있지만 포스팅을 하면서 더 깊이 알아보려고 한다.</p>
<p>결과적으로 불합격하게 되었고, 회사에 대한 불편한 감정만 남았다.</p>
<h2 id="z회사">&#39;Z&#39;회사</h2>
<p>18일 뒤에 서류합격 안내를 받았고, 코딩테스트를 메일을 받게 되었다. 사용한 플랫폼은 testdome이라는 처음보는 사이트였다.</p>
<h3 id="코딩테스트-1">코딩테스트</h3>
<p>코딩 테스트는 알고리즘 문제일 것이라는 예상과 달리 javascript, css, typescript, react, 객관식 등의 문제라서 괜찮다(good!)고 생각하였다. 12일정도 후에, 운이 좋게도 코딩 테스트에 통과 안내를 받게 되었고 면접 일정을 잡게 되었다.</p>
<h3 id="면접-1">면접</h3>
<p>면접 일정을 잡아 일주일 내로 면접을 보게 되었다. 화상면접이 아닌 대면 면접을 하게 되었고 1:3으로 진행되게 되었다. 면접 전의 간단한 아이스 브레이킹이 있었고, 덕분에 편안한 분위기를 가지게 되었다. 질문은 이력서 기반의 질문, 자바스크립트 코어 질문, + 연관된 질문으로 이어졌습니다. 생각외로 오래걸려 2시간넘게 진행되었고, 밝은 분위기에서 면접을 마칠 수 있었다. 팀의 분위기가 좋아 보였고, 질문을 통해 자신들의 기술을 소개해 주었는데 매력적으로 느껴졌다.
+면접을 보고나서 소정의 선물을 받았다.</p>
<h2 id="d회사">&#39;D&#39;회사</h2>
<p>6일 뒤에 서류합격 안내를 받았고, 연말이었기 때문에 연초에 면접을 보게 되었다.</p>
<h3 id="면접-2">면접</h3>
<p>d 회사 면접에대한 정보를 구글링 해보았고, 스피드퀴즈 방식의 15분 면접을 보게 되었다는 것을 알고 자바스크립트에 대해 공부하는 시간을 가졌다. 면접은 화상으로 진행되었고 1:1로 진행되었다. 스피드 퀴즈에는 일반적인 질문과, html, css, 자바스크립트에 대한 질문을 받았다. 결과적으로 불합격하게 되었는데, 준비를 적게한 나의 잘못이기에 불만은 없었다.</p>
<h2 id="b회사">&#39;B&#39;회사</h2>
<p>10일 뒤에 서류합격 안내를 받았고, 3일정도 소요되는 과제를 받게 되었다. 1월1일부터 1월3일까지 작업을 하기로 하였다.</p>
<h3 id="과제-1">과제</h3>
<p>과제는 CRA를 사용하여 리액트를 사용한 리스트와 관련된 과제였다. 20줄 정도의 과제 설명과 만들어야 할 뷰의 제플린 주소를 받게 되었다. 일단 원하는게 무엇인지 모호했다. 그래서 우선 리액트만을 사용하여 만들었고, 추가적으로 리덕스를 사용하여 만들었다.
결과적으로 불합격하게 되었는데, 리뷰를 자세하게 작성해 주었다.
성능 최적화를 위해 DOM에 직접 접근하는 부분이 좋지 않게 보였던 것 같다. 리렌더링을 피하고 리페인팅으로 성능을 최적화 하려고 하는 나만의 방식이 맞지 않았던 것 같다.  이 부분에 대해서는 호기심을 가지고 연구하여 포스팅 해보려고 한다. 
+회사 서비스의 3만?5만? 포인트를 과제비로 받게 되었다</p>
<h3 id="b-회사2">&#39;B&#39; 회사2</h3>
<p>14일뒤에 서류합격 안내를 받았고, 크리스마스(12/25)에서 연말(12/31)까지 진행되는 과제를 받았다. 날짜를 조정할 수 있었지만 하지 않았다.</p>
<h3 id="과제-2">과제</h3>
<p>과제는 라이브러리를 사용하지 않고, 자바스크립트만와 회사api를 이용하여 폼을 만들고, 결과에 대한 리스트를 분류하는 작업이였다. 나는 바닐라 자바스크립트 개발을 해본적이 없기도 하였고 기왕하는거 재미있게 평소에 해보고 싶은것을 해보자며 모든 것을 웹 컴포넌트로 만들어서 작업하였다. 시간은 2일이 소요되었고, 결과물은 약간 불만족 일 정도였지만, 작동은 훌륭하게 되었다. 웹 컴포넌트로 만들었기에 회사측에서 평가하기 힘든 요소가 있게다고 생각하였다. 결과적으로 불합격하게 되었고, 리뷰를 요청했으나 &#39;보안을 위해&#39;라는 편리한 대답으로 리뷰를 받지 못했다. 
+과제비로 2만원어치 상품권을 받았다.</p>
<p>좋았던 점은 과제가 기능문서와 함께 전달되어 어떻게 만들어야 하는지 정확하게 설명되어 있어서 만드는데 혼란스러운 점이 없었다. </p>
<h2 id="w-회사">&#39;W&#39; 회사</h2>
<p>3일 후에 연락을 서류합격 안내를 받았고, 면접일정을 잡게 되었다. 면접 전에 코드를 주어 면접 중에 해당 코드에 대한 리뷰를 하게될 것이라고 하였다. 코드리뷰를 통하여 지원자를 판단하는 방식이 색다르기도하고 마음에 들었습니다.</p>
<h3 id="면접-3">면접</h3>
<p>면접에서는 이력서 기반의 질문과 회사에서 일반적인 질문, 사용하는 기술과 관련된 질문, 최적화 및 반응형에 대한 질문을 받았습니다. 이 중 제일 좋았던 질문은 &#39;자신의 회사와 서비스를 소개해달라&#39; 였습니다. 이 질문을 통하여 회사원의 구성원으로써 역활을 잘 하고 있는지 파악하기 좋은 질문이라고 생각합니다.
기술면접에는 합격을 하였고 이후 2차인 매니저와의 면접에서 떨어지게 되었다. 불합격을 하게 된 후 불합격한 사유에 대해 물어보게 되었는데, 친절하게 대답해 주어 회사에 대한 좋은 인상을 가지게 되었다.</p>
<h2 id="마치며">마치며</h2>
<p>이렇게 갑작스럽게 시작된 이직 여정을 진행하게 되었고, 결과적으로 가장 분위기가 좋았던 곳에 합격하게 되었습니다. 이제와서 생각하면 이직은 &#39;시작이 반이다&#39;라고 생각 할 만큼 시작하는 것이 중요한 것 같습니다. 물론 서로 얼굴 붉히지 않도록 여러분은 면접 준비를 하고 가시는 것을 추천드립니다 ㅠㅠ. </p>
<p>이번 이직 여정을 통해 배운 것은 면접 전에 일반적인 질문, html, css, 자바스크립트 코어, react에 대한 질문과 대답을 미리 준비하고 지원할 때에는 20개는 안되고 3개정도 해야 좋을 것 같습니다. (계속 연차를 쓰게되서 눈치 보임;;)</p>
<p>기본적으로 이직이기 때문에 &#39;이직사유&#39;는 필수적인 질문입니다. +자기소개
채용 담당자들은 대부분 친절하시다.(그런데 왜 메일이 항상 밤에오지???)</p>
<p>이직은 끝났지만 프론트엔드 면접 기출문제라 불리우는 문제들을 오히려 더 찾아보고, 튜토리얼, 공식문서, 책을 찾아보며 저의 약점이 되고 있는 부분을 더 이상  약점이 아니게 만들고, 포스팅 할 수 있도록 하려고 합니다.</p>
<p>제 이직 경험이 도움이 된다면 좋겠습니다.
갑사합니다:)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[오픈놀] - 나가기 방지 팝업 띄우기]]></title>
            <link>https://velog.io/@jerrynim_/%EB%82%98%EA%B0%80%EA%B8%B0-%EB%B0%A9%EC%A7%80</link>
            <guid>https://velog.io/@jerrynim_/%EB%82%98%EA%B0%80%EA%B8%B0-%EB%B0%A9%EC%A7%80</guid>
            <pubDate>Wed, 03 Feb 2021 05:08:23 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요. 오픈놀 프론트엔드 개발자 제리님 입니다. 오픈놀의 미니인턴 사이트에서는 참여자가 신청을 할 수 있도록 신청양식을 작성하거나, 이력서를 작성하는 등의 사용자가 양식을 작성하는 일이 많습니다. 사용자는 실수로 양식을 작성하는 중 페이지를 이탈하게 되어 작성해둔 양식을 잃어버리는 경우가 많습니다. 특히, 이력서 처럼 장문의 글을 작성중에 실수로 페이지를 이탈하여 작성한 글을 잃어버리게 되었을때는 분노가 차오르게 됩니다.
이를 방지하기 위해 미니인턴 서비스에서 사용자가 양식을 작성하는 도중에 페이지를 이탈하는 것을 방지하기로 하였습니다. 여러 사이트를 사용하다보면 다음과 같은 팝업을 자주 보셨으리라고 생각합니다.
<img src="https://images.velog.io/images/jerrynim_/post/3f507ce6-2cee-41a7-97a2-ba6e38fa727e/image.png" alt="">
이는 양식을 작성한 후에 새로고침 혹은 다른 사이트로 이동하게 될 경우에 볼 수 있는 팝업입니다. 우선적으로 이를 구현하기 위해 window의 [beforeunload] (<a href="https://developer.mozilla.org/ko/docs/Web/API/Window/beforeunload_event">https://developer.mozilla.org/ko/docs/Web/API/Window/beforeunload_event</a>) 이벤트를 이용하여 쉽게 구현할 수 있습니다.</p>
<pre><code class="language-typescript">  //* 새로고침 및 타 사이트 이동 방지
  const handleBeforeunload = (e: BeforeUnloadEvent) =&gt; {
    if (hasChanged) {
      e.preventDefault();
      e.returnValue = &#39;&#39;;

      return &#39;&#39;;
    }
    return undefined;
  };
  useEffect(() =&gt; {
    window.addEventListener(&#39;beforeunload&#39;, handleBeforeunload);

    return () =&gt; {
      window.removeEventListener(&#39;beforeunload&#39;, handleBeforeunload);
    };
  }, [hasChanged]);</code></pre>
<p>하지만 새로고침과 타 사이트 이동이 아닌 클라이언트 측 네비게이션을 이용하여 라우팅을하게될 경우에는 팝업이 나타나지 않았습니다. 클라이언트 측 네비게이션 시에도 팝업이 나타나서 사용자의 이탈을 방지하길 원했고 구글링을 해보며 가능성과 방법을 찾았습니다. 에어비앤비에서 호스트 등록시 페이지에서 이탈을 시도할 경우 다음과 같은 팝업이 나타나는 것을 볼 수 있었습니다.</p>
<p><img src="https://images.velog.io/images/jerrynim_/post/14bc5bb8-3804-417f-bd22-d1de2ce334ec/image.png" alt="">
가능성을 확인하여 디자인팀에게 모달 디자인을 요청하여 다음과 같이 만들도록 하기로 하였습니다. 다음은 미니인턴의 이탈 방지 모달의 예시입니다.
<img src="https://images.velog.io/images/jerrynim_/post/08b4e4a6-b820-4a26-a3e6-4bca47d99413/image.png" alt=""></p>
<p>팝업을 구현하기위한 고려해야할 요소는 다음과 같습니다.</p>
<ol>
<li>사용자가 양식을 입력하여 양식이 변경됨(hasChanged)</li>
<li>사용자가 이동하기를 클릭함(confirmed)</li>
<li>이동할 url(toUrl)</li>
<li>모달 띄우기(useModal)</li>
</ol>
<p>우선 사용자가 페이지를 이동하는 것을 감지하여야 합니다. 이를 위해서 &#39;next/router&#39;의 &#39;routeChangeStart&#39; 이벤트를 이용하여 라우트 변경시 이벤트헨들러를 실행합니다.</p>
<pre><code class="language-typescript">  useEffect(() =&gt; {
    window.addEventListener(&#39;beforeunload&#39;, handleBeforeunload);
    Router.events.on(&#39;routeChangeStart&#39;, routeChangeStart);
    Router.events.on(&#39;routeChangeError&#39;, routerChangeError);

    return () =&gt; {
      window.removeEventListener(&#39;beforeunload&#39;, handleBeforeunload);
      Router.events.off(&#39;routeChangeStart&#39;, routeChangeStart);
      Router.events.off(&#39;routeChangeError&#39;, routerChangeError);
    };
  }, [confirmed, hasChanged]);</code></pre>
<p>이벤트헨들러는 인자로 이동하게될 url을 받게되고, 현재주소와 url을 비교하여 이동하는지 여부를 파악합니다. 이동을하게 될때 양식이 변경되었다면(hasChanged) 모달을 띄우게합니다. 이때 에러를 발생시켜 라우트 이동을 막을 수 있습니다.</p>
<pre><code class="language-typescript">  const routeChangeStart = (url: string) =&gt; {
    //? 다른 url로 가고 / 변경이 있고/ 승인이 안되었다면
    if (
      decodeURI(Router.asPath).split(&#39;?&#39;)[0] !== decodeURI(url).split(&#39;?&#39;)[0] &amp;&amp;
      hasChanged &amp;&amp;
      !confirmed
    ) {
      setToUrl(url);
      openModal();

      Router.events.emit(&#39;routeChangeError&#39;);
      throw &#39;Abort route change. Please ignore this error.&#39;;
    }
  };</code></pre>
<blockquote>
<p>spilt(&#39;?&#39;)를 사용하여 url 파라미터로 인한 불일치를 막습니다.</p>
</blockquote>
<p>라우트 이동을 막았다면 사용자가 &#39;이동하기&#39;버튼을 클릭하여 페이지 이탈을 원할때 이동시키도록합니다. 이를위해 이동 할 url 을 state 에 저장해두도록 합니다. 그리고 사용자가 &#39;이동하기&#39;를 클릭하였음(confirmed)을 분기하기 위하여 confirmed 값을 state 에 저장합니다.</p>
<pre><code class="language-typescript">  const [toUrl, setToUrl] = useState(&#39;&#39;);
  const [confirmed, setConfirmed] = useState(false);</code></pre>
<p>유저가 &#39;이동하기&#39; 버튼을 클릭하게 된다면 confirmed 를 true 로 바꾸게됩니다.</p>
<pre><code class="language-typescript">onClick={() =&gt; setConfirmed(true)}}</code></pre>
<p>그리고 useEffect 를 사용하여 conffirmd 가 변경되었을 때 모달을 종료하고 이동하도록 합니다.</p>
<pre><code class="language-typescript"> useEffect(() =&gt; {
    if (confirmed) {
      closePortal();
      Router.push(toUrl);
    }
  }, [toUrl, confirmed]);</code></pre>
<p>이렇게 이탈 방지 컴포넌트가 만들어지게 되었습니다.
저희는 이 이탈 방지 모달을 공통 컴포넌트로 만들어 여러곳에서 사용하도록 하였습니다.
이를 위해서 컴포넌트는 양식이 입력되었는지를 확인하는 hasChanged 값만을 props 로 받도록 하여 여러곳에서 사용할 수 있도록 하였습니다.</p>
<p>결과적으로, 여러 페이지에서 사용자의 실수로 인한 이탈을 방지하여 사용자의 불편함을 줄일 수 있게되었습니다. 미니인턴에서는 이러한 사용자의 불편함을 줄이기 위해 계속해서 방법을 찾아내고 있습니다.^^</p>
<p>오픈놀 미니인턴팀에 합류하세요!. 미니인턴은 채용중~
<a href="https://www.wanted.co.kr/wd/52637">https://www.wanted.co.kr/wd/52637</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SEO를 위한 노오오력!🔥]]></title>
            <link>https://velog.io/@jerrynim_/SEO%EB%A5%BC%EC%9C%84%ED%95%9C-%EB%85%B8%EC%98%A4%EC%98%A4%EB%A0%A5</link>
            <guid>https://velog.io/@jerrynim_/SEO%EB%A5%BC%EC%9C%84%ED%95%9C-%EB%85%B8%EC%98%A4%EC%98%A4%EB%A0%A5</guid>
            <pubDate>Thu, 28 Jan 2021 06:18:05 GMT</pubDate>
            <description><![CDATA[<p>SEO(Search Engine Optimization)는 검색어 최적화로써, 사이트가 검색자에게 적절하게 노출되도록 하는 것을 말합니다. 사이트를 검색어 최상단에 위치시키는 것, 사용자의 니즈에 맞춰 사이트를 노출시키는 것, 검색어에 관련된 사이트를 표시하는 것 또한 SEO라고할 수 있습니다.</p>
<p>이번 포스팅에서는 <a href="https://moz.com/learn/seo/on-site-seo">on-site-seo</a>를 읽고, 평소에 제가 SEO를 위해 사용하던 방법들을 공유하고, 여러 도구들을 소개하도록 하겠습니다.</p>
<p>프론트엔드 개발자로써 제가 SEO를 위해 고려할 수 있는 항목은 다음과 같습니다.</p>
<ul>
<li>제목(&lt; title &gt;)</li>
<li>키워드(&lt; keyword &gt;)</li>
<li>url</li>
<li>콘텐츠(&lt; h1 &gt;)</li>
<li>대체 텍스트(alt)</li>
</ul>
<p>하나씩 살펴보도록 하겠습니다.</p>
<h2 id="제목title">제목(title)</h2>
<p>&lt; head &gt; 태그 안에 &lt; title &gt; 태그를 이용하여 제목을 설정할 수 있으며, 다음과 같이 SERP에 표시됩니다.</p>
<blockquote>
<p>SERP(Search Engine Results Pages)는 검색결과 페이지입니다.</p>
</blockquote>
<p><img src="https://images.velog.io/images/jerrynim_/post/74f974f1-6d25-4da3-a844-3a3e6f45ac7a/image.png" alt=""></p>
<h3 id="제목의-길이">제목의 길이</h3>
<p>글에서는 제목의 길이를 60자 미만으로 하라고 하지만 알파벳 기준입니다. 한글로 작성할 경우 &#39;가&#39;를 최대 32번 사용할 수 있었습니다.
<img src="https://images.velog.io/images/jerrynim_/post/d1830891-3e53-4495-8b10-e6f7c93dd3f5/image.png" alt="">
길어진 제목은 잘려져 ... 으로 표시되기 때문에 적절한 제목을 표시하여 사용자의 클릭을 유도하여야합니다.</p>
<h3 id="중복-타이틀-방지">중복 타이틀 방지</h3>
<p>모든 페이지에 고유한 제목을 지정하여 Google이 중복 된 콘텐츠로 판단하는 것을 방지하고, 검색자에게 원하는 정보를 클릭할 수 있도록하여야합니다.</p>
<h2 id="키워드keyword">키워드(keyword)</h2>
<p>키워드는 콘텐츠의 내용을 정의하는 아이디어와 주제입니다. SEO 측면에서는 검색자가 검색 엔진에 입력하는 단어와 구문으로 &quot;검색 쿼리&quot;라고도합니다.
콘텐츠에 적합한 키워드를 사용하여 사용자에게 노출시키고, 키워드와 관련된 콘텐츠를 제공하여 사용자의 클릭율을 높여 검색 순위를 올릴 수 있습니다.</p>
<h3 id="개념-타겟팅">개념 타겟팅</h3>
<p>키워드를 사용할때에 다음과 같은 방법은 좋지 않습니다.</p>
<p>Buy Widgets, Best Widgets, Cheap Widgets, Widgets for Sale</p>
<p>키워드 목록에 불과한 제목이나 동일한 키워드의 변형을 반복해서 사용하지 마십시오. 이러한 제목은 검색 사용자에게 좋지 않으며 검색 엔진에 문제를 일으킬 수 있습니다. 
구글에서는 개념을 공유하여 하나의 키워드로 충분히 대치할 수 있습니다.
<a href="https://moz.com/blog/tactical-keyword-research-in-a-rankbrain-world">Tactical Keyword Research in a RankBrain World</a></p>
<h3 id="키워드-순서">키워드 순서</h3>
<p>Moz의 테스트 및 경험에 따르면 키워드 앞쪽의 키워드가 검색 순위에 더 많은 영양을 미친다고합니다. 또한 사용자들은 처음 두 키워드만 스캔할 수 있어 가장 독특한 키워드가 먼저 나타나는 것을 권장한다고합니다.</p>
<h2 id="메타-설명meta-description">메타 설명(meta description)</h2>
<p>메타 설명은 웹 페이지에 대한 간략한 요약을 제공하는 HTML 속성입니다.
&lt; head &gt; 태그 안에 &lt; meta &gt; 태그를 이용하여 다음과같이 사용합니다.</p>
<pre><code class="language-typescript">&lt;head&gt;  
  &lt;meta name=&quot;description&quot; content=&quot;This is description&quot;&gt;
&lt;/head&gt;</code></pre>
<p><img src="https://images.velog.io/images/jerrynim_/post/47d0c063-f2cb-4667-bc0b-99cf7f52726f/image.png" alt=""></p>
<p>결과적으로 메타 설명은 검색어 순위에 영향을 주지 않습니다. 하지만 검색자에게 유용한 설명을 제공함으로써 클릭률을 높일 수 있습니다. </p>
<blockquote>
<p>구글은 2009년 9월에 웹 검색을위한 구글의 순위알고리즘에 메타 설명이나 메타 키워드가 고려되지 않는다고 발표했습니다 . </p>
</blockquote>
<p>메타 설명은 충분히 설명 할 수있을만큼 길게 유지하는 것이 가장 좋으므로 50 ~ 160자를 권장합니다. 이또한 알파벳기준이기에 다음 그림과 같이 잘리게 됩니다.
<img src="https://images.velog.io/images/jerrynim_/post/5928144f-7ee0-4484-baf3-ea60bf9b0bed/image.png" alt=""></p>
<h3 id="주의사항">주의사항</h3>
<p>메타 설명에 큰 따옴표(&quot;)를 사용하시면 안됩니다.
따옴표를 사용하게된다면 SERP에서 따옴표가 있는 부분을 자르게 됩니다. 대신에 <a href="https://www.w3schools.com/html/html_entities.asp">HTML엔티티</a>를 사용해 &quot;를 사용할 수 있습니다.</p>
<h2 id="대체-텍스트alt-text">대체 텍스트(alt text)</h2>
<p>대체 텍스트는 이미지의 모양과 기능을 설명하기 위해 HTML코드 내에서 사용됩니다.</p>
<pre><code>  &lt;img src = &quot;pupdanceparty.gif&quot;alt = &quot;춤추는 강아지&quot;&gt;</code></pre><h3 id="대체-텍스트의-장점">대체 텍스트의 장점</h3>
<ol>
<li><p>사진에 대체 텍스트를 추가하는 것은 무엇보다도 웹 접근성의 원칙입니다. 스크린 리더를 사용하는 시각장애가있는 사용자는 페이지의 이미지를 더 잘 이해하기위해 대체 텍스트를 읽습니다.</p>
</li>
<li><p>이미지파일을 로드 할 수없는 경우 이미지 대신 대체 텍스트가 표시됩니다.</p>
</li>
<li><p>대체 텍스트는 검색 엔진 크롤러에 더 나은 이미지 컨텍스트 / 설명을 제공하여 이미지를 올바르게 색인화하는 데 도움이됩니다.</p>
</li>
</ol>
<h3 id="이미지-seo">이미지 SEO</h3>
<p>검색 크롤러는 이미지를 볼 수 없습니다. 크롤러가 이해할 수 있도록 이미지에 대한 정보를 파일 이름과 대체 텍스트를 이용하는 것이 좋습니다.
대체 텍스트는 타겟 키워드를 포함할 수 있는 또 다른 기회를 제공합니다. 이미지를 설명하고 가능한 경우 타겟팅하는 키워드 또는 키워드 구문을 포함하는 대체 텍스트를 만드는 것이 가장 좋습니다.</p>
<p>우수한 대체 텍스트 작성 예시 
<a href="https://moz.com/learn/seo/alt-text">https://moz.com/learn/seo/alt-text</a></p>
<h2 id="중복방지">중복방지</h2>
<p>다음과 같은 이유로 페이지의 중복이 발생할 수 있습니다.</p>
<ul>
<li>다양한 url
URL 매개 변수는 중복 콘텐츠 문제를 일으킬 수 있습니다.
<img src="https://images.velog.io/images/jerrynim_/post/e9dae8be-c32e-4662-afc5-887586b58df9/image.png" alt=""></li>
</ul>
<ul>
<li>www 와 https
사이트의 주소가  &quot;<a href="http://www.site.com&quot;%EB%B0%8F">www.site.com&quot;및</a> &quot;site.com&quot;, 혹은 &quot;http :&quot; // 및 &quot;https : //&quot; 처럼 다른 버전이 있다면 복제본이 생성되게됩니다.</li>
<li>복제된 콘텐츠
여러 웹 사이트에서 동일한 항목을 판매하고 모두 해당 항목에 대한 제조업체의 설명을 사용하는 경우 동일한 콘텐츠가 웹의 여러 위치에 표시됩니다.</li>
</ul>
<h3 id="해결방법">해결방법</h3>
<ul>
<li>301 리디렉션
대부분의 경우 중복 콘텐츠를 방지하는 가장좋은 방법은 &quot;중복&quot;페이지에서 원본 콘텐츠 페이지로 301 리디렉션을 설정하는 것 입니다.<ul>
<li>Rel = &quot;canonical&quot;
중복 콘텐츠를 처리하는 또 다른 옵션은 rel = canonical 속성 을 사용하는 것 입니다. 검색엔진이 페이지가 특정 URL의 사본으로 처리됨을 알려줍니다. 또한 이 페이지에 부여된 링크, 콘텐츠, &quot;ranking power&quot; 은 특정 URL에 부여됩니다.</li>
</ul>
</li>
<li>Google Search Console 이용
<a href="https://moz.com/learn/seo/duplicate-content">https://moz.com/learn/seo/duplicate-content</a></li>
</ul>
<h2 id="robotstxt">robots.txt</h2>
<p>robots.txt파일은 웹 크롤러(user-agents)가 웹 사이트를 크롤링할 수 있는지 여부를 나타내게 됩니다.
ex)
<code>/robots.txt</code></p>
<pre><code>User-agent: *
Allow: /
Sitemap: https://miniintern.com/sitemap.xml

User-agent: AdsBot-Google
Disallow:</code></pre><p>예시와 같이 User-agent, Disallow, Allow, Crawl-delay, Sitemap의 구문을 사용하며 user-agent(크롤러)를 지정하여 특정 페이지를 &#39;allow&#39;혹은 &#39;disallow&#39;할 수 있습니다.
사이트맵(sitemap)은 : URL과 관련된 XML 사이트 맵의 위치를 호출하는데 사용됩니다. 이 명령 은 Google, Ask, Bing 및 Yahoo 에서만 지원 됩니다.</p>
<h3 id="robotstxt의-장점">robots.txt의 장점</h3>
<p>robots.txt를 사용하게되면 다음과 같은 이점을 얻을 수 있습니다.</p>
<ul>
<li>SERP에 중복 콘텐츠가 표시되지 않도록 방지 (이 경우 <a href="https://moz.com/learn/seo/robots-meta-directives">메타 로봇</a>이 더 나은 선택 인 경우가 많습니다)</li>
<li>웹 사이트의 전체 섹션을 비공개로 유지 (예 : 개발 dev 사이트)</li>
<li>내부 검색 결과 페이지가 공개 SERP에 표시되도록 유지</li>
<li>사이트 맵 위치 지정</li>
<li>검색 엔진이 웹 사이트의 특정 파일 (이미지, PDF 등)을 인덱싱하지 못하도록 방지</li>
<li>크롤러가 한 번에 여러 콘텐츠를로드 할 때 서버에 과부하가 걸리지 않도록 크롤링 지연(Crawl-delay) 지정</li>
</ul>
<p>사이트에 사용자 에이전트 액세스를 제어하려는 영역이없는 경우 robots.txt 파일이 전혀 필요하지 않을 수 있습니다.</p>
<h2 id="schemaorg">schema.org</h2>
<p>Schma.org는 검색엔진이 SERP에서 페이지를 읽고 표현하는 방식을 개선하기 위해 HTML에 추가할  수 있는 태그의 의미론적 어휘입니다.</p>
<p>다음 그림과 같이 제목, 경로(breadScrumbs), 메타 설명이외에 다양한 정보를 표시할 수 있습니다.
<img src="https://images.velog.io/images/jerrynim_/post/79602a7f-08c9-4dbb-9979-1eefb4f13412/image.png" alt=""></p>
<p>이를 위해선 구조화 된 데이터를 HTML에 마크업으로 추가하여야 합니다.
구조화 된 데이터를 사용하여 표시할 수 있는 콘텐츠는 다음 링크를 통해 확인할 수 있습니다.
<a href="https://developers.google.com/search/docs/guides/search-gallery?hl=ko">https://developers.google.com/search/docs/guides/search-gallery?hl=ko</a></p>
<p>사례를 통해 알아보도록 하겠습니다. 구글에 &#39;미니인턴 채용&#39;이라고 검색을하게된다면 다음과같이 검색결과가  나타납니다.
<img src="https://images.velog.io/images/jerrynim_/post/ea21b609-3fb6-48eb-a328-e2b5a69826d5/image.png" alt=""></p>
<p>사이트에 접속하여 &lt; head &gt;를 확인해 보면 구조화 된 데이터가있는 것을 확인할 수 있습니다.
<img src="https://images.velog.io/images/jerrynim_/post/991c9ae9-22fb-4f0c-827e-aa1eae7f2248/image.png" alt=""></p>
<p>이처럼 HTML에 JSON_LD(권장), 마이크로데이터, RDFa 형식의 구조화 된 데이터를 제공한다면 SERP에 향상된 방식으로 콘텐츠를 표시할 수 있습니다.</p>
<h3 id="검색-순위">검색 순위</h3>
<p>구조화 된 데이터가 순위에 영향을 미치는지 여부는 많은 논의와 실험의 주제였습니다. 아직까지이 마크 업이 순위를 향상 시킨다는 결정적인 증거는 없다고합니다.</p>
<h2 id="seo-테스트">SEO 테스트</h2>
<p>사이트가 SEO 최적화가 잘 적용되어있는지 확인하는 방법을 소개하도록 하겠습니다.</p>
<h3 id="구글-라이트하우스google-lighthouse">구글 라이트하우스(google lighthouse)</h3>
<p>크롬 브라우저의 개발자도구(F12)를 열게되면 &#39;lighthouse&#39;라는 탭이있습니다.
<img src="https://images.velog.io/images/jerrynim_/post/7e86fb81-2e68-475b-938b-6cc71698731e/image.png" alt=""></p>
<p>lighthouse는 등대라는 뜻으로 앞의 그림에 나온 항목들을 측정하고 테스트하여 결과를 알려줍니다. 또한, 점수로 사이트의 품질을 나타내고 개선사항을 알려줍니다. 이 중 SEO를 위하여 SEO항목을 100점으로 만들어주면 좋습니다.
<img src="https://images.velog.io/images/jerrynim_/post/8d53826b-38c3-4176-9335-d6024e66c95c/image.png" alt=""></p>
<h3 id="구조화-데이터-테스트-도구">구조화 데이터 테스트 도구</h3>
<p>구글의 구조화 데이터 테스트 도구를 이용하여 구조화 데이터가 잘 적용되고 있는지 확일할 수 있습니다.
<a href="https://search.google.com/structured-data/testing-tool/#url=https%3A%2F%2Fminiintern.com%2F">https://search.google.com/structured-data/testing-tool/#url=https%3A%2F%2Fminiintern.com%2F</a></p>
<h3 id="checkbot-확장프로그램">checkbot 확장프로그램</h3>
<p><a href="chrome-extension://dagohlmlhagincbfilmkadjgmdnkjinl/index.html#/report/set-page-descriptions">checkbot 확장프로그램</a>을 이용하여 사이트의 SEO가 적용되어있는 상황과 개선하여야할 페이지를 쉽게 파악할 수 있습니다. 또한, 개선해야할 가이드를 알려주며 점수로 표시해주어 제일 만족해하는 사이트입니다.</p>
<h3 id="greenflare">greenflare</h3>
<p><a href="https://greenflare.io/">greenfalre</a>는 로컬에서 사이트를 크롤링하여 데이터를볼 수 있는 프로그램입니다. 
<img src="https://images.velog.io/images/jerrynim_/post/52b584bb-f560-4748-8817-8ba818fd5333/image.png" alt=""></p>
<h3 id="구글-서치-콘솔">구글 서치 콘솔</h3>
<p><a href="https://search.google.com/search-console/welcome?hl=ko">구글 서치 콘솔</a>을 이용하면 나의 사이트를 직접적으로 확인할 수 있습니다.</p>
<h3 id="직접-검색해보기">직접 검색해보기</h3>
<p>직접 검색을 통하여 SERP를 확인하는 것 만큼 확실한 테스트는 없습니다. 직접 검색하여 의도한대로 콘텐츠가 나타나는지 확인하는것이 좋습니다.</p>
<h2 id="기타">기타</h2>
<h3 id="네이버">네이버</h3>
<p>네이버에서의 SEO를 고려한다면 <a href="https://searchadvisor.naver.com/">네이버 search advisor</a>를 활용하여 구축해보시길 바랍니다. 
naver-site-verification, 연관채널 등 네이버의 SEO를 위한 여러 제휴를 요청할 수 있습니다.</p>
<h3 id="ogpopen-graph">ogp(open-graph)</h3>
<p><a href="https://ogp.me/">Open Graph</a>는 Facebook에서 표시 할 이미지 및 설명과 같은 정보를 구문 분석하는 데 사용하는 마크 업 유형입니다. 주로 소셜공유를 하게 될때에 표시되는 콘텐츠 정보를 나타내기위해 사용됩니다.</p>
<h2 id="마치며">마치며..</h2>
<p>우리가 SEO를 최적화 하려는 이유는 결국 사이트를 상단에 노출시키기 위함입니다. 저는 SEO 최적화를 위해 여러 사이트들의 head를 들여다보고, 다양한 포스팅을 참고하고 여러 도구를 사용해가며 SEO 최적화를 위해 노력하였습니다. 이번에 MOZ를 보니 어렵게 느껴지던 SEO가 어느정도 쉽게 다가온 것같은 느낌이 들었고, 제 경험을 공유하고자 이렇게 포스팅하게 되었습니다. ㅎㅎ
SEO 하면 뭔가 복잡하고 어려울 것 같지만, 결국 크롤러와 검색자에게 유용한 콘텐츠를 제공하는데 목적이 있습니다. 그것을 기억하면서 페이지를 만들면 제가 만든 사이트가 사람들에게 클릭될꺼라 기대합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[코드속 불편함 줄이기]]></title>
            <link>https://velog.io/@jerrynim_/%EC%BD%94%EB%93%9C%EC%86%8D-%EB%B6%88%ED%8E%B8%ED%95%A8-%EC%A4%84%EC%9D%B4%EA%B8%B0</link>
            <guid>https://velog.io/@jerrynim_/%EC%BD%94%EB%93%9C%EC%86%8D-%EB%B6%88%ED%8E%B8%ED%95%A8-%EC%A4%84%EC%9D%B4%EA%B8%B0</guid>
            <pubDate>Wed, 27 Jan 2021 10:39:21 GMT</pubDate>
            <description><![CDATA[<p>개발자는 반복적인 작업을 싫어합니다.
개발자는 간단한 것을 좋아합니다.
개발자는 간편한 것을 좋아합니다.</p>
<p>개발자는 반복적으로 작성하는 필수적인 코드, 보기힘든 코드, 복잡한 타입으로 인하여 개발하면서 불편함을 겪게되는데요. 저도 개발자로서 그러한 불편함을 계속 겪고있습니다. 코딩을하면서 그러한 불편함을 줄이기 위해 여러가지 시도를 계속 하였습니다. 이번글에서는 제가 코드를 작성하면서 불편함을 줄이기위해 사용하는 방식들을 공유해보도록 하겠습니다.</p>
<blockquote>
<p>앞으로 소개하는 방식들은 주관적이며, Typescript를 사용하는 리액트 개발에 맞추어져 있습니다.</p>
</blockquote>
<h3 id="반복되는-optional-chaining">반복되는 ?(Optional Chaining)</h3>
<p>값이 null이거나 undefined일 수 있는 값을 편하게 사용하기 위해서 ?를 자주 사용합니다. 
ex) user?.profile?.contact?.email 
리액트에서 값을 화면에 출력할때에 값이 없을때의 에러를 방지하기 위해 다음과 같이 작성하게 됩니다.</p>
<blockquote>
<p>{user &amp;&amp; user.profile &amp;&amp; user.profile.contact &amp;&amp; user.profile.contact.email}</p>
</blockquote>
<p>이것을 옵셔널 체이닝을 이용하여 간단하게 할 수 있습니다.</p>
<blockquote>
<p>{user?.profile?.contact?.email }</p>
</blockquote>
<p>하지만 문제는 이 ?가 너무 많이 사용된다는 것입니다.</p>
<pre><code class="language-typescript">
&lt;p&gt;{user?.profile?.contact?.email }&lt;/p&gt;
&lt;p&gt;{user?.profile?.contact?.phone }&lt;/p&gt;
&lt;p&gt;{user?.profile?.company?.name }&lt;/p&gt;</code></pre>
<p>?를 사용하지 않아도 되는 상황에서 사용하는 것은 가독성을 좋지않게만듭니다.
만약에 user의 값이 정말로 null이라면 어떻게 될까요?
그렇게 된다면, 비어있는 <code>&lt;p&gt;</code>태그만 출력되게 됩니다. </p>
<p>그런데 유저에게 비어있는 <code>&lt;p&gt;</code>태그를 보여주어야 할까요?
유저에게 비어있는 박스를 보여주는 것을 의도하였다면 앞에서 한 것처럼 ?를 일일히 붙여주는 것이 맞습니다.
하지만 유저에게 비어있는 박스를 보여 줄 필요가 없다면 다음과같이 ?를 사용하지 않아도 됩니다.</p>
<pre><code class="language-typescript">if(!user){
    return null;
}

&lt;p&gt;{user.profile?.contact?.email }&lt;/p&gt;
&lt;p&gt;{user.profile?.contact?.phone }&lt;/p&gt;
&lt;p&gt;{user.profile?.company?.name }&lt;/p&gt;</code></pre>
<p>저의 경우에는 값이 없을때 보여줄 필요가 없다면 화면에 출력을 하지 않게 하여 ? 사용을 줄이고, 다른 화면을 표시하거나 404페이지로 이동하게합니다.
주의할 것은 다음과같이 에러가 발생할 수 있습니다.</p>
<p><img src="https://images.velog.io/images/jerrynim_/post/8a3e8ebe-062c-4134-8a5c-fe1d4edd2d03/image.png" alt=""></p>
<p>return 으로 인하여 모든 훅스가 렌더되지 않을 수 있기에 가급적 jsx를 return하기 전에 사용하고 있습니다.</p>
<h3 id="타입사용">타입사용</h3>
<p>타입스크립트를 사용할때에 타입을 옳바르게 설정하는 것은 매우 중요합니다. 옳바르지 않은 타입을 사용한다면 오히려 개발할 때 불편함을 유발합니다. 때로는 타입이 지정되지 않아서 불편함을 겪습니다. 예제와 함께 제가 타입을 사용하는 방법들을 소개하도록 하겠습니다.</p>
<pre><code class="language-typescript">// 돔 접근하기
document.querySelector&lt;HTMLDivElement&gt;(&quot;div&quot;)

// axios
axios.get&lt;CustomResponse&gt;(url)

// promise
new Promise&lt;Result&gt;((resolve)=&gt;())

// Object key index
const object:{[key:string]:number} = {what:0}

// useState
const [value,setValue] = useState&lt;number&gt;(0);

// useRef
const ref = useRef&lt;HTMLDivElement&gt;(null);
</code></pre>
<h4 id="리덕스">리덕스</h4>
<p>루트리듀서로부터 스토어의 타입을 얻을 수 있습니다.</p>
<pre><code class="language-typescript">export type RootState = ReturnType&lt;typeof rootReducer&gt;;</code></pre>
<p>useSelector의 기본 타입을 RootState로 변경할 수 있습니다.</p>
<pre><code class="language-typescript">
//* 타입 지원되는 커스텀 useSelector 만들기
declare module &#39;react-redux&#39; {
  interface DefaultRootState extends RootState {}
}</code></pre>
<h3 id="커스텀-hoooks">커스텀 hoooks</h3>
<p>자주 사용하는 기능들은 커스텀 훅스를 이용하여 간편하게 재사용하며, 코드가독성을 높일 수 있습니다.
저는 모달, 토스터, 에러 헨들러 등의 기능을 자주 사용하게 되었습니다.
ex)</p>
<pre><code class="language-typescript">const { openToast } = useToaster();
const { openModal } = useModal();
const { handleError } = useErrorHandler();</code></pre>
<p>훅스를 이용하여 코드가 깔끔해 보이게 되었습니다.</p>
<h3 id="isempty">isEmpty</h3>
<p>배열을 사용하게 될때에 배열이 있으면서 배열의 값이 1개이상이어야 하는 경우가 많았습니다.</p>
<pre><code class="language-typescript">{array &amp;&amp; array.length&gt;0 &amp;&amp; (</code></pre>
<p>저는 isEmpty를 사용하여 이러한 구문을 대체했을때 가독성이 좋다는 느낌을 받아 isEmpty를 즐겨 사용합니다.</p>
<pre><code class="language-typescript">{!isEmpty(array)&amp;&amp;}</code></pre>
<h3 id="리덕스-툴킷">리덕스 툴킷</h3>
<p>리덕스를 사용하신다면 <a href="https://redux-toolkit.js.org/">리덕스 툴킷</a>을 정말 필수라고 생각합니다. 
기존에는 &#39;typesafe-actions&#39;를 사용하여 다음과 같이 사용하였습니다.</p>
<pre><code class="language-typescript">//타입정의
const SET_PROJECT = &#39;project/SET_PROJECT&#39;;

//액션생성
export const projectActions = {
  setProject: createStandardAction(SET_FOUCS_EVENT)&lt;Project&gt;()
}


//액션타입

type SetProjectAction = ReturnType&lt;typeof projectActions.setProject&gt;;

//리듀서   
    [SET_PROJECT]: (state, action: SetProjectAction) =&gt;
      produce(state, draft =&gt; {
        draft.project = action.payload;
      }),</code></pre>
<p>리덕스 툴킷을 사용한다면 같은 기능을 간단하게 만들 수 있습니다.</p>
<pre><code class="language-typescript">const project = createSlice({
  name: &#39;project&#39;,
  initialState,
  reducers: {
    setProject(state, action: PayloadAction&lt;Project&gt;) {
      state.project = action.payload;
    },
  }
})</code></pre>
<h3 id="스니펫-사용">스니펫 사용</h3>
<p>&#39;스니펫&#39;이란 재사용 가능한 소스 코드를 의미한다. 즉, 자주 쓰는 코드를 저장해두고, 필요할 때마다 별칭을 통해 불러올 수 있다. 저는 반응형에서 동작하는 컴포넌트를 자주 만들고있습니다.
vscode에서는  이러한 스니펫을 지원하는데 언어별로 스니펫을 지정할 수 있습니다.
<img src="https://images.velog.io/images/jerrynim_/post/c094a410-7d5d-4fae-8ffa-f0c743a82779/image.png" alt="">
그중 css.json에서 반복되는 @media query문을 작성하는 번거로움을 줄이기 위해 스니펫을 사용하고 있습니다.
<code>css.json</code></p>
<pre><code class="language-json">{
    &quot;responsive medium snippet&quot;: {
      &quot;prefix&quot;: &quot;medium&quot;,
      &quot;body&quot;: [
        &quot;@media (max-width: 1024px) {&quot;,
        &quot;\t${1: CssProperty}&quot;,
        &quot;}&quot;
      ]
    }
  }</code></pre>
<p>css 코드에서 medium이라고 작성하게 되면 지정해둔 코드가 나타나게 됩니다.</p>
<p>리액트 컴포넌트를 빠르게 만들기 위한 스니펫을 소개하도록 하겠습니다.
<code>typescriptreact.json</code></p>
<pre><code class="language-json">{
    &quot;react functional component&quot;: {
      &quot;prefix&quot;: &quot;jerryFC&quot;,
      &quot;body&quot;: [
        &quot;import React from &#39;react&#39;;&quot;,
        &quot;&quot;,
        &quot;const $TM_FILENAME_BASE: React.FC = () =&gt; {&quot;,
        &quot;\treturn (&quot;,
        &quot;\t\t&lt;div&gt;&quot;,
        &quot;\t\t\thello world&quot;,
        &quot;\t\t&lt;/div&gt;&quot;,
        &quot;\t);&quot;,
        &quot;};&quot;,
        &quot;&quot;,
        &quot;export default $TM_FILENAME_BASE;&quot;
      ]
    },</code></pre>
<p>스니펫을 사용하면 다음과같이 코드가 작성되어 빠르게 컴포넌트를 만들 수 있습니다.</p>
<pre><code class="language-typescript">import React from &#39;react&#39;;
import styled from &#39;styled-components&#39;;

const AuthInfo: React.FC = () =&gt; {
  return (
    &lt;div&gt;
      hello world
    &lt;/div&gt;
  );
};

export default AuthInfo;</code></pre>
<p>파일명과 동일한 리액트 컴포넌트 스니펫을 만들어주게 됩니다.</p>
<h3 id="글로벌-타입">글로벌 타입</h3>
<p>다음과같이 window의 타입을 확장할 수 있습니다.</p>
<pre><code class="language-typescript">declare global {
  interface Window {
    google: any;
    initMap: any;
  }
}</code></pre>
<p>타입을 지원하는 라이브러리는 타입을 추가적으로 설치하여 사용할 수 있습니다.
ex) @types/googlemaps</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[jerrynim-2020]]></title>
            <link>https://velog.io/@jerrynim_/2020-%EC%A0%9C%EB%A6%AC%EB%8B%98</link>
            <guid>https://velog.io/@jerrynim_/2020-%EC%A0%9C%EB%A6%AC%EB%8B%98</guid>
            <pubDate>Tue, 19 Jan 2021 10:57:11 GMT</pubDate>
            <description><![CDATA[<p>2020년을 생각하면 생각나는 것은 &#39;참 바빴다..&#39; 네요. 저는 일을 벌이는걸 참잘하는 것 같아요. 대표적으로 두개의 일을 벌였네요.</p>
<h2 id="집필">집필</h2>
<p>2020년 1월에 메일로 집필제의가 들어왔고, 고민고민하다가 &#39;그래, 까짓거 한번 해보자!&#39; 라는 생각에 시작하게 되었는데 이때부터 저의 &#39;바쁘다..&#39;가 시작된 것 같아요. 벌써 1년째 인데 정말 마감으로 인한 스트레스로 머리가 빠질 것 같았습니다 ㅠㅠ. 회사 외의 시간 + 주말을 갈아 넣어서 만들게 되는데 &#39;돈도 안되는 거 내가 왜하고 있지!&#39; 이런생각이 엄청 들었지만, 시작에 대한 책임을 져야하기 때문에 열심히 작성했었어요. 책 하나에 들어가는 노력이 엄청나게 필요하더라고여...  (아직도 안끝났다니!🤪) </p>
<blockquote>
<p>책 한권 만드는데 들어가는 노력이 어마어마합니다. 1년씩 걸리는덴 이유가 있더라고여.</p>
</blockquote>
<h2 id="노마드코더-해커톤">노마드코더 해커톤</h2>
<p>친구와 함께 노마드코더 해커톤에 신청했는데, 신청을 한 이유가 &#39;재밌겠다&#39; 였어요.
재미로 시작했지만 &#39;서비스라면 이정돈 해야지!&#39; 하면서 피그마에 마구마구 만들다 보니 뭔가 많아졌어요.. 원래는 해커톤 하면서 취준중인 친구의 코드리뷰 해주면서, 친구 스펙업 시키려고 했지만,
기획, 디자인, 모델링, 백엔드 제작을 끝내야 친구가 시작 할 수 있어서 2-3주동안 달렸고,
취준 중인 친구가 만드는 게 빠를 수 없기에 프론트의 대부분도 제가하게 됬어요..
<img src="https://images.velog.io/images/jerrynim_/post/cd800826-bd59-4273-a23a-77e6078bfa72/image.png" alt="">
결과적으로 한달+2주 동안 다 만들었고, 수상도 했었어요.(그동안 책을 못써서 또 마감지옥!😈)
1등상품인 아이패드도 받았구요 ㅎㅎ.</p>
<blockquote>
<p>나는 분명 최소기능 프로그램을 만든다고 했는데...</p>
</blockquote>
<h2 id="하고싶은게-많다">하고싶은게 많다.</h2>
<h3 id="go언어">Go언어</h3>
<p>책에 많은 시간을 쓰면서 항상 &#39;시간이 생기면 00해야지&#39;가 많아졌어요. 가장 하고 싶었던건 Go언어를 다루는거와 블로그 포스팅이였어요. 회사일이 여유로워져서 Go를 좀 다루게 되었는네 이게 욕심이 생기게 되더라고여. 기왕 Go를 하는거 api를 만들 수 있어야지! 하면서 graphql까지 넣게되고, 재미삼아 사내 휴가프로그램을 만들려고 하였지만!. 필요가 없다고 해서 프로젝트가 버려지게 되었네요.ㅎㅎ 하지만 이 기술을 계속 쓰고 싶어졌어요.</p>
<blockquote>
<p>Go를 잘다뤄서 Node.js를 안쓰는게 목표!</p>
</blockquote>
<h3 id="lit-html">lit-html</h3>
<p>관심을 가지고 지켜만 보던 라이브러리 였는데 실제로 프로덕션으로 사용할 생각으로 만들다 보니 빠지게되었어요. 정말 자바스크립트스럽고 리액트를 사용하지 않고고도 컴포넌트 개발을 할 수 있다니!. <a href="https://github.com/jerrynim/jerrynim-lit-project">https://github.com/jerrynim/jerrynim-lit-project</a>
SSR만 지원된다면 정말 완벽할텐데...</p>
<blockquote>
<p>개인적으로는 이제 리액트(프레임워크)를 사용하지 않는 방향으로 가려고해요.</p>
</blockquote>
<h3 id="할일하고싶은-일">할일(하고싶은 일)</h3>
<h4 id="개인-블로그">개인 블로그</h4>
<p>올해에는 개인 블로그를 만들어야 겠어요. 기술 스택도 미리 정해놨답니다.
프론트 : lit-element, typescript, webpack 끝
백엔드 : go, gin, gorm
사실 백엔드도 필요없을 것 같지만, 직접 만드는 이유는 GA를 사용할 수 있다는 점이 가장 큰 것 같아요.
개인적으로 도적적인 작업을할때 블로그에 실험해보는 것도 좋구요.</p>
<h4 id="자바스크립트-기본">자바스크립트 기본</h4>
<p>이직을 준비하면 자바스크립트 기본문제들을 외우던 기억이 있습니다. 최근에 자바스크립트 기본문제들을 보면 외우는 것이 아닌 이해가되기 시작했습니다. 경험에 근거하여 지식이 다가오니 더 쉽게 이해가 됬습니다. 자바스크립트 기본을 더 알아본후 프레임워크 없는 개발을 할려고합니다.</p>
<h4 id="강의">강의</h4>
<p>제가 다른사람들이 많이 사용했으면 하는 것은, go, next.js, lit-element 입니다. 참 좋은 기술이라 생각되어서 다른사람들도 많이 이용해서 생태계가 커지면 좋겠습니다. 다른사람들이 쉽게 접할 수 있도록 튜토리얼과 심화 강의를 만들어 보고 싶어요.</p>
<p>(올해는 이것들을 다 이루길...🙏)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[유지보수 가능한 코드를 위한 노오오력!🔥]]></title>
            <link>https://velog.io/@jerrynim_/%EC%9C%A0%EC%A7%80%EB%B3%B4%EC%88%98-%EA%B0%80%EB%8A%A5%ED%95%9C-%EC%BD%94%EB%93%9C%EB%A5%BC-%EC%9C%84%ED%95%9C-%EB%85%B8%EC%98%A4%EC%98%A4%EB%A0%A5</link>
            <guid>https://velog.io/@jerrynim_/%EC%9C%A0%EC%A7%80%EB%B3%B4%EC%88%98-%EA%B0%80%EB%8A%A5%ED%95%9C-%EC%BD%94%EB%93%9C%EB%A5%BC-%EC%9C%84%ED%95%9C-%EB%85%B8%EC%98%A4%EC%98%A4%EB%A0%A5</guid>
            <pubDate>Tue, 19 Jan 2021 08:45:23 GMT</pubDate>
            <description><![CDATA[<p>작동 되는 코드를 만드는 것은 쉽다, 하지만 나중에 작성한 코드를 고치는 것은 어렵다. 
내가 작성한 코드를 보면서 &#39;왜 이렇게 만들었지?&#39;, &#39;이건 왜 이렇게 했을까?&#39; 란 물음표들이 뜨게 되고 &#39;다음부터는 이러지 말아야지&#39;라고 생각하게 됩니다. 내가 나의 코드를 보는데 다른 사람이 본다면 얼마나 많은 물음표가 뜨게 될까요. 그래서 이번 포스팅에서는 그동안 다음엔 이러지말아야지 개선해온 저의 코딩스타일을 공유해보려고 합니다.</p>
<h2 id="1-주석을-쓰기">1. 주석을 쓰기.</h2>
<p>저는 주석을 적는걸 취향이라고 생각하지 않습니다. 주석은 미래의 나와 다른사람을 위한 매너고 생산성을 높일 수 있는 필수 코드입니다.</p>
<p>저는 예전에 lodash함수를 사용하는 것을 좋아했습니다. lodash를 사용하면 마법처럼 값이 내가 원하는대로 변형되는게 좋았습니다. 아래는 제가 1년전 쯤에 작성한 코드입니다. </p>
<pre><code class="language-typescript">import {intersection, pick, keys, pickBy} from &quot;lodash&quot;;

const values = pick(object1, intersection(keys(draft), keys(object1)));
const notNullValues = pickBy(values, value =&gt; value !== null);</code></pre>
<p>object1에서 draft와의 중복 속성만 가져와 null이 아닌 값만을 만들어주는 함수이다. 
이것을 단 두줄로 해결하였다. 와우! 놀랍지 않나요?😱</p>
<p>지금 봐도 intersection은 뭔지 pick은 뭔지 pickBy는 뭔지 알수가 없다.
어떤 기능을 하는지 알기 위해서는 lodash문서에서 일일히 찾아서 파악을 하는수밖에....</p>
<p>이 코드를 보는 누군가(내)가 고통받지 않도록 주석을 적어 그러한 수고를 방지하여야 합니다.
<img src="https://images.velog.io/images/jerrynim_/post/3061e77f-fe75-49f4-90bf-9f54cc5fad01/image.png" alt=""></p>
<blockquote>
<p>지금은 lodash 사용을 잘 안하게 되었고, 모든 함수에 설명을 달고 있습니다.</p>
</blockquote>
<p>*Better Comments라는 vscode 익스텐션을 사용하는데 이것을 사용하면 주석에 색상을 줄 수 있습니다.</p>
<p>최소한 이정도의 주석은 있어야 저런 코드를 분해해 보지 않고 기능을 파악 할 수 있다.
물론 이해하기 어려운 코드를 안쓰면 되잖아요?, 변수명을 잘 지으면 되잖아요?, 주석과 코드가 다를수 있자나요? 라고 할수 있지만
주석을 사용하지 않을때 보다 주석을 쓰기 시작한 후의 코드를  찾는 속도, 이해하는 속도, 팀원이 제 코드를 이해하는데 용이하였습니다.</p>
<h3 id="11-주석은-한글로-씁니다">1.1 주석은 한글로 씁니다.</h3>
<p>한국인으로써 영어보다는 한글에 눈이갑니다. 한글로 작성된 주석만 보더라도 함수의 기능을 빠르게 파악하고 원하는 것을 찾기 쉽습니다. 마우스를 휙휙 내릴때 한글만 보고 원하는 기능을 쉽게 찾을 수 있습니다.</p>
<h2 id="2-클래스명을-유니크하게-만들기">2. 클래스명을 유니크하게 만들기.</h2>
<p>저는 스타일드 컴포넌트(styled-components)를 자주 사용하고 있는데요. 스타일드 컴포넌트 내부에는 html태그와 class명을 이용하여 스타일링을 하고 있습니다.
<img src="https://images.velog.io/images/jerrynim_/post/f17e4830-7c0b-4650-a048-f68e467d469f/image.png" alt=""></p>
<p>저는 그림과 같이 &#39;컴포넌트-콘텐츠&#39;로 클래스명을 지어 사용하고있습니다. 이렇게 길게 클래스명을 작성하게되었을때 다음과 같은 장점을 얻을 수 있었습니다.</p>
<ol>
<li>코드만보고도 어떤부분을 담당하는지 상상할 수 있습니다.</li>
<li>디버깅시 클래스명을 복사하여 검색하면 원하는 코드위 위치를 빠르게 찾을 수 있습니다.</li>
<li>스타일드 컴포넌트 대신 html 태그를 사용함으로써 시맨틱 태그(Semantic Tag)의 이점을 가져올 수 있습니다.</li>
</ol>
<h2 id="3-삼항연산자를-중첩하지-않기">3. 삼항연산자를 중첩하지 않기.</h2>
<p>3항연산자를 사용하면 가독성이 좋은 코드를 만들 수 있습니다.
하지만 3항연산자를 중첩하게 된다면 이야기가 달라집니다.</p>
<pre><code class="language-typescript">a==b ? &quot;a=b&quot; : &quot;a!=b&quot;

a==b ? a==c ? &quot;a=b,a=c&quot; :&quot;a=b,a!=c&quot; : &quot;a!=b&quot;  
</code></pre>
<p>2단중첩만 해도 헷갈리기 시작합니다. 그런데 리액트의 JSX문법에서는 다음과 같이 사용하게 되는데</p>
<pre><code class="language-typescript">&lt;&gt;
  {a==b ? &lt;Component1&gt;
   ...
   ...
   ...
   &lt;div&gt;
   {a==c ? &lt;Component2&gt;
       ...

           :&lt;Component3&gt; }
   &lt;/div&gt;
   :
   &lt;Component4&gt;}
  &lt;/&gt;</code></pre>
<p>이것이 스크롤이 길어질수록 정말 보기 힘들어집니다. 
그래서 저는 삼항연산자의 중첩을 사용하지 않습니다. 대신에 다음과 같이 사용하도록 하였습니다.</p>
<pre><code class="language-typescript">{a==b &amp;&amp; &lt;Component1&gt;}
{a==b &amp;&amp; a==c &amp;&amp; &lt;Component2&gt;}
{a==b &amp;&amp; a!==c &amp;&amp; &lt;Component3&gt;}
{a!==b &amp;&amp; a!==c &amp;&amp; &lt;Component4&gt;}
</code></pre>
<p>삼항연산자 중첩대신 중복되어 보일지라도 직접적으로 보이는것이 만족도가 매우 높았습니다.</p>
<h2 id="4-변수의-타입은-바로-생각나는것으로">4. 변수의 타입은 바로 생각나는것으로.</h2>
<p>price라는 변수의 타입은 무엇일 것 같나요? 저의 생각은 number입니다.
그런데 프로그래밍을 하다보니 url에서 값을 받게되는 경우가 많았는데요. 이러한 경우 값의 타입은 string | string[] 였습니다.
그래서 과거에는 api를 보내거나 받을때 url로부터 값을받을때 전부 string으로 만들어서 사용하였습니다. 그런데 나중에 보게될 때 &#39;이 값은 number일 것 같은데 왜 string일까?&#39;라는 생각이 계속들게 됬습니다. 그 코드를 처음 본 사람도 왜 string일지 찾아보게될 것이라 생각했고, 결국 편할거라 생각하여 변형된 타입이 오히려 더 혼란을 주는 것 같아서. 단순하게 가는것이 제일 좋다고 생각하게 됬습니다. 그래서 변수의 타입은 바로 떠오르는 타입으로 사용하는것이 제일 만족도가 높았습니다.</p>
<h2 id="5-그외에">5. 그외에..</h2>
<p>css를 작성할때에 속성들의 순서를 균일하게 하고있습니다.</p>
<p>함수형 컴포넌트를 만들때 state-커스텀hooks-function-useEffect 처럼 구조를 정하여 사용하고 있습니다.</p>
<p>page폴더와 component의 구조를 동일하게 하여 관련 컴포넌트를 찾기 쉽도록 합니다.
아이콘의 저장 위치를 &#39;페이지-컴포넌트-콘텐츠&#39; 구조로 클래스명과 동일하게 저장합니다.
ex) 투두리스트 삭제아이콘 : public/static/svg/todo-list/todo/delete.svg
api, type 등을 카테고리로 분리합니다.</p>
<p>ex) api/user, api/todo 
    types/user, types/todo
스타일 정보는 스타일이 사용되는 곳에 있어야 합니다. 다른 파일에 있다면 찾기가 쉽지 않습니다.</p>
<pre><code class="language-typescript">
.toto-list{}

...

&lt;ul className=&quot;todo-list&quot;&gt;</code></pre>
<h2 id="끝으로">끝으로</h2>
<p>글의 처음에 작성한 것 처럼 작동 되는 코드를 만드는 것은 쉽다, 하지만 나중에 작성한 코드를 고치는 것은 어렵습니다. 계속해서 코드를 읽기 쉽고, 찾기 쉽고, 다른 사람(나)가 이해하기 쉽도록 작성하는법을 고안해보았고, 팀에서도 좋다고 생각되는 부분은 함께 사용되게 되었습니다. 저는 코딩스타일이 2-3개월마다 바뀐다고 생각할만큼 더 좋은 방법이 있다면 개선해나가고 있습니다. 만약 제 생각과 반대가되거나, 여러분만의 노하우가 있다면 댓글로 알려주신다면 알려주세요. 감사합니다 :)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[12월 꿀포스팅]]></title>
            <link>https://velog.io/@jerrynim_/12%EC%9B%94</link>
            <guid>https://velog.io/@jerrynim_/12%EC%9B%94</guid>
            <pubDate>Tue, 19 Jan 2021 07:14:09 GMT</pubDate>
            <description><![CDATA[<p><a href="https://github.com/lezhin/accessibility">레진 웹 접근성 가이드라인</a>
웹 접근성을 준수하기위한 가이드라인이다.
구글 lighthouse와 airbnb 린트를 사용하다보니 자연스럽게 사용하게 되었더군요!.
<a href="https://www.w3.org/WAI/tutorials/">웹 표준성 튜토리얼</a>
제목 그대로 웹 표준성 튜토리얼 사이트입니다. 
요즘은 뷰를 그리기 전에 이사이트에 제시된 방식대로 만드는 편입니다.</p>
<p><a href="https://ko.javascript.info">모던 Javascript 튜토리얼</a>
자바스크립트의 기본부터 심화까지 잘 정리되어있는 사이트이다.
그 중 이 <a href="https://ko.javascript.info/ninja-code">닌자 코드</a> 글이 가장 재밌습니다ㅋㅋ.</p>
<p><a href="https://medium.com/@daelity/%EC%9B%B9%ED%8E%98%EC%9D%B4%EC%A7%80%EC%97%90%EC%84%9C-dark-mode-%EA%B0%90%EC%A7%80%ED%95%98%EA%B8%B0-81526e0a0c7a">다크모드 감지하기</a>
이전에 다크모드를 만들었을때 로컬스토리지를 사용하여 다크모드를 사용하였는데
컴퓨터에 설정된 다크모드를 불러와서 초반에 설정해 준다면 더 좋을 것 같다.</p>
<p><a href="https://web.dev/squoosh-v2/">squooosh v2</a>
구글에서 만든 이미지 최적화 cli
OxiPNG, MozJPEG, WebP등을 지원한다.</p>
<p><a href="https://github.com/vercel/virtual-event-starter-kit">next conf 소스코드</a>
vercel에서 넥스트 conf 데모 소스코드를 스타터 킷으로 제공했다.
사이트가 빠르다는 느낌을 받았었는데 대부분 정적 getStaticProps를 사용하여 정적 페이지로 되어있음을 확인 할 수 있었다.
@를 이용한 경로설정이 맞추어져 있으니 보기 좋아보여 다음에 사용해 보려고 한다.
<a href="https://reactjs.org/blog/2020/12/21/data-fetching-with-react-server-components.html">react-server-component</a>
<img src="https://images.velog.io/images/jerrynim_/post/1754d107-d1ab-4523-bbc0-2ab271fca937/image.png" alt=""></p>
<p>리액트에서 실험중인 기능이지만, 다가올 미래처럼 보이네요.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[11월 돌아보기 👀]]></title>
            <link>https://velog.io/@jerrynim_/11%EC%9B%94-%EC%A2%8B%EC%95%98%EB%8D%98-%EA%B8%80%EB%93%A4</link>
            <guid>https://velog.io/@jerrynim_/11%EC%9B%94-%EC%A2%8B%EC%95%98%EB%8D%98-%EA%B8%80%EB%93%A4</guid>
            <pubDate>Tue, 01 Dec 2020 12:19:42 GMT</pubDate>
            <description><![CDATA[<h3 id="nextjs-100-httpsnextjsorgblognext-10">[Next.js 10.0] (<a href="https://nextjs.org/blog/next-10">https://nextjs.org/blog/next-10</a>)</h3>
<p>넥스트가 버전10.0이 나왔다. 이번 업데이트에서는 크게 반응할 것이 없어보였다.
다국어 지원에 대한것과 &#39;next/image&#39;를 통한 이미지 최적화가 가장 유용해 보였는데, 막상 &#39;next/image&#39;를 사용해보니 반응형 환경에서 webp로 변환해서 사용하던 나에게는 필요가 없을 것 같아 보였다.
하지만 반응형이 없는 페이지라면 사용하는 것을 고려해보려고 한다.</p>
<h3 id="신입-개발자-연봉-이야기"><a href="https://velog.io/@dongyi/%EC%8B%A0%EC%9E%85-%EA%B0%9C%EB%B0%9C%EC%9E%90-%EC%97%B0%EB%B4%89-%EC%9D%B4%EC%95%BC%EA%B8%B0">신입 개발자 연봉 이야기</a></h3>
<p>연봉 이야기는 흥미를 끌기에 충분한 이야기이다.
돈은 많이 받을수록 좋으니까, 대기업과 비교하면 항상 이직욕구가 샘솟는다.
취업을 할때 고려해야 할것은 많지만 연봉은 항상 고려대상이 되니까요.</p>
<h3 id="이제-reactjs-를-버릴-때가-왔다"><a href="https://seokjun.kim/time-to-stop-react/">이제 React.js 를 버릴 때가 왔다</a></h3>
<p>제목부터 어그로가 강하다. 읽어보니 공감이 되는부분과 안되는 부분이 있지만,
웹 개발에는 정답이 없으니 리액트에 대한 다양한 생각을 볼 수 있었습니다.
저 또한 리액트에 실증이 나고 있고 새로운 멋진 기술들을 사용해 보고 있지만, 현재 무언가가 리액트를 대체하기에는 오랜시간이 필요할거라 생각합니다.</p>
<h3 id="20까지-해본-개발자"><a href="https://jojoldu.tistory.com/485">2.0까지 해본 개발자</a></h3>
<p>개발만 잘하면 된다?? 규모가 큰 회사를 다녀보지 않아서 모르겠지만, 현재 중소기업에 다니는 저로서는 공감되는 글이었습니다. 처음에는 개발측만 생각하였지만, 점점 사용자 경험을 생각하였고, 운영자의 측면도 생각하는 법을 기르게 되었습니다. 만드는 것은 쉽지만 유지보수가 편하게 만드는 것이 중요하다는 생각을 다시 되집어본 글이였습니다.</p>
<h3 id="일-잘-하는-개발자는-왜-비즈니스까지-신경쓸까"><a href="https://evan-moon.github.io/2020/10/24/buisiness-with-programming/">일 잘 하는 개발자는 왜 비즈니스까지 신경쓸까?</a></h3>
<p>이 글 또한 개발자가 개발만 잘하면 된다??가 아니라는 질문에 답해줍니다.
서비스를 만들면서 깨닫게 되는 효율성과 회사의 일원으로서 비지니스 적으로 생각하는 것을 생각하게 해주는 글이었다.</p>
<h3 id="뱅크샐러드는-어떻게-레거시-서비스를-박살-내는가"><a href="https://blog.banksalad.com/tech/how-banksalald-decomposes-legacy-services/">뱅크샐러드는 어떻게 레거시 서비스를 박살 내는가</a></h3>
<p>코드를 작성하는 순간 레거시가 된다. 레거시 자체가 나쁜 것은 아니다. 
레거시를 수정할때 코드를 변경하면서 같은 결과를 이끌어 내는것은 쉽지않고 중요하다.
섀도잉이라는 생각지 못한 방법이 인상깊었습니다.</p>
<h3 id="누구나-원하는-개발자되기"><a href="https://blog.shiren.dev/2020-11-23/">누구나 원하는 개발자되기</a></h3>
<p>취업 혹은 이직은 준비하고 있다면 정말 도움이되고 공감되는 글입니다.</p>
<h3 id="11월에는-👀">11월에는 👀</h3>
<p>lit-element와 웹 컴포넌트에 대해 공부하는 시간과 글을 쓰는 시간을 보내고, Go언어와 gin, gorm, gqlgen 등의 도구로 Go언어로 백엔드를 구성하는 공부하는 시간을 보냈다. 내가 관심있는 개발을 하다보니 &#39;취업과 이직을 위한 개발은 하는 것보다 본인이 좋아하는 것을 개발하는게 즐겁구나&#39;라고 생각이 들었습니다.
테스트코드에 대한 사내공유와 마케팅 API를 사용해보면서 11월을 돌아보니 알차게 보낸 것 같습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[lit-프로젝트 시작하기 - (6/6) - 라우팅]]></title>
            <link>https://velog.io/@jerrynim_/lit-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-6n-%EB%9D%BC%EC%9A%B0%ED%8C%85</link>
            <guid>https://velog.io/@jerrynim_/lit-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-6n-%EB%9D%BC%EC%9A%B0%ED%8C%85</guid>
            <pubDate>Mon, 16 Nov 2020 07:17:12 GMT</pubDate>
            <description><![CDATA[<p>웹 컴포넌트에서 라우팅을 하는 법을 알아보도록 하겠습니다. 기존의 라우팅 처럼 해당 페이지를 불러와서 렌더링 한다기 보다, 웹 컴포넌트를 교체하는 방식이라고 생각하시면 편할거라 생각합니다.</p>
<p>라우팅을 하기위해 <a href="https://vaadin.com/router">&#39;@vaadin/router&#39;</a>라는 라이브러리를 설치하도록 하겠습니다.</p>
<blockquote>
<p>yarn add @vaadin/router</p>
</blockquote>
<p>&#39;@vaadin/router&#39;는 A CLIENT-SIDE ROUTER FOR WEB COMPONENTS, 웹 컴포넌트를 위한 클라이언트 사이드 라우터 입니다. 이전에는 &#39;index.html&#39;안에 웹 컴포넌트를 직접 불러와 사용하였지만 &#39;@vaadin/router&#39;를 사용하여 웹 컴포넌트를 교체하도록 하겠습니다.</p>
<p>&#39;index.html&#39;에 <code>&lt;main&gt;</code>컴포넌트를 만들도록 하겠습니다.
<strong>index.html</strong></p>
<pre><code class="language-html">  &lt;body&gt;
    &lt;main&gt;&lt;/main&gt;
    &lt;script src=&quot;/vendor/webcomponents-loader.js&quot;&gt;&lt;/script&gt;
  &lt;/body&gt;</code></pre>
<p>앞에서 만든 <code>&lt;main&gt;</code>태그를 라우터로 만들어 내부에 웹 컴포넌트를 불러오도록 하겠습니다.</p>
<p><strong>index.ts</strong></p>
<pre><code class="language-typescript">import &quot;./styles/styles.css&quot;;
import &quot;./pages/lit-tomato&quot;;
import { Router } from &quot;@vaadin/router&quot;;

window.addEventListener(&quot;load&quot;, () =&gt; {
  initRouter();
});

function initRouter() {
  const router = new Router(document.querySelector(&quot;main&quot;));
  router.setRoutes([
    {
      path: &quot;/&quot;,
      component: &quot;lit-tomato&quot;,
    },
  ]);
}</code></pre>
<p><code>&lt;main&gt;</code>태그를 불러와 경로가 &quot;/&quot;라면 &#39;lit-tomato&#39; 컴포넌트 불러오도록 설정하였습니다. &#39;lit-tomao&#39;를 사용하기 위해 import를 해주었는네, 이를 다음과 같은 방식으로 경로가 변경될 때 불러 올 수 있습니다.</p>
<pre><code class="language-typescript">function initRouter() {
  const router = new Router(document.querySelector(&quot;main&quot;));
  router.setRoutes([
    {
      path: &quot;/&quot;,
      component: &quot;lit-tomato&quot;,
    },
    {
      path: &quot;/lit-potato&quot;,
      component: &quot;lit-potato&quot;,
      action: () =&gt; {
        import(&quot;./pages/lit-potato&quot;);
      },
    },
  ]);
}</code></pre>
<p>&#39;/lit-potato&#39;경로를 주소창에 입력해 본다면 &#39;lit-potato&#39;컴포넌트가 <code>&lt;main&gt;</code>태그 안에 렌더링 된 것을 확인 할수 있었습니다.
<img src="https://images.velog.io/images/jerrynim_/post/c18a0a3e-06ca-4ac8-9e8d-946cf73de83c/image.png" alt=""></p>
<p>이번에는 depth가 있는 경로를 가진 컴포넌트를 다뤄보겠습니다. 경로가 &#39;/vegetable/lit-tomato&#39;라면 어떻게 만들어야 할까요?
간단하게 path를 경로와 맞게 설정해주면 됩니다.</p>
<pre><code class="language-typescript">    {
      path: &quot;/vegetable/lit-tomato&quot;,
      component: &quot;lit-tomato&quot;,
    },</code></pre>
<p>이번에는 children이라는 속성을 이용하여 &#39;/vegetable/lit-tomato&#39;경로를 설정해보도록 하겠습니다.</p>
<pre><code class="language-typescript">    {
      path: &quot;/vegetable&quot;,
      component: &quot;lit-tomato&quot;,
      children: [{ path: &quot;/lit-tomato&quot;, component: &quot;lit-tomato&quot; }],
    },</code></pre>
<p>브라우저에 &#39;/vegetable/lit-tomato&#39; 경로로 이동하여 보면 &#39;lit-tomato&#39; 컴포넌트가 렌더링 된 것을 확인 할 수 있습니다.</p>
<h3 id="동적라우팅">동적라우팅</h3>
<p>동적 경로를 설정하는 것 또한 간단합니다. 전달받을 query를 :를 사용하여 동적페이지를 만들 수 있습닏다.</p>
<pre><code class="language-typescript">    {
      path: &quot;/vegetable/:name&quot;,
      component: &quot;lit-tomato&quot;,
    },</code></pre>
<p>기존에 없던 경로인 &#39;/vegetable/lit-apple&#39;을 브라우저에 입력하면 lit-tomato 컴포넌트가 나타나는 것을 확인 할 수 있습니다.</p>
<p>작성한 코드는 <a href="https://github.com/jerrynim/jerrynim-lit-project">https://github.com/jerrynim/jerrynim-lit-project</a> 에서 확인할 수 있습니다.
..
.
.
.
.
.
.
.
.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[lit-프로젝트 시작하기 - (5/6) - with.redux ]]></title>
            <link>https://velog.io/@jerrynim_/lit-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-5n-with.redux</link>
            <guid>https://velog.io/@jerrynim_/lit-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-5n-with.redux</guid>
            <pubDate>Mon, 16 Nov 2020 05:52:07 GMT</pubDate>
            <description><![CDATA[<p>블로그에서 보기 : <a href="https://lit-blog.vercel.app/post/lit-tutorial-5">https://lit-blog.vercel.app/post/lit-tutorial-5</a></p>
<p>lit-프로젝트에 전역 형상 관리를 위하여 redux(리덕스)를 사용하도록 하겠습니다. </p>
<p>리덕스를 사용하는데 필요한 라이브러리들을 설치해 주도록 하겠습니다.</p>
<blockquote>
<p>yarn add redux @reduxjs/toolkit pwa-helpers</p>
</blockquote>
<p>리덕스 툴킷을 이용하여 간단하게 스토어와 간단한 모듈을 만들도록 하겠습니다. &#39;pwa-helpers&#39;는 컴포넌트에 스토어를 connect 하는 함수를 제공합니다.</p>
<h3 id="리덕스-스토어-만들기">리덕스 스토어 만들기</h3>
<p><strong>store/vegetable.ts</strong></p>
<pre><code class="language-typescript">import { createSlice, PayloadAction } from &quot;@reduxjs/toolkit&quot;;

interface VegetableState {
  name: string;
}

const initialState: VegetableState = {
  name: &quot;redux-tomato&quot;,
};

const vegetable = createSlice({
  name: &quot;vegetable&quot;,
  initialState,
  reducers: {
    setName(state, action: PayloadAction&lt;string&gt;) {
      state.name = action.payload;
    },
  },
});

export const vegetableActions = { ...vegetable.actions };

export default vegetable;</code></pre>
<p><strong>store/index.ts</strong></p>
<pre><code class="language-typescript">import { combineReducers, configureStore } from &quot;@reduxjs/toolkit&quot;;
import vegetable from &quot;./vegetable&quot;;

const rootReducer = combineReducers({
  vegetable: vegetable.reducer,
});

export type RootState = ReturnType&lt;typeof rootReducer&gt;;

export const store = configureStore({
  reducer: rootReducer,
  devTools: true,
});

</code></pre>
<h4 id="option리덕스-사가-사용하기">(option)리덕스 사가 사용하기</h4>
<p>redux-saga를 사용하길 원한다면 다음과 같이 saga를 실행할 코드를 추가해 주세요.</p>
<pre><code class="language-typescript">import {
  combineReducers,
  configureStore,
  getDefaultMiddleware,
} from &quot;@reduxjs/toolkit&quot;;
import createSagaMiddleware, { Task } from &quot;redux-saga&quot;;
import { Store } from &quot;redux&quot;;
import { all } from &quot;redux-saga/effects&quot;;

const rootReducer = combineReducers({});

export type RootState = ReturnType&lt;typeof rootReducer&gt;;

let sagaMiddleware = createSagaMiddleware();

const middleware = [
  ...getDefaultMiddleware({
    thunk: false,
    serializableCheck: false,
  }),
  sagaMiddleware,
];

export const store = configureStore({
  reducer: rootReducer,
  middleware,
  devTools: true,
});

export function* rootSaga() {
  yield all([]);
}

export interface SagaStore extends Store {
  sagaTask?: Task;
}

(store as SagaStore).sagaTask = sagaMiddleware.run(rootSaga);
</code></pre>
<h3 id="리덕스-스토어-연결하기">리덕스 스토어 연결하기</h3>
<p>리덕스를 사용할 컴포넌트에서 다음과 같이 connect를 해주도록 합니다.
<strong>pages/lit-tomato.ts</strong></p>
<pre><code class="language-typescript">import { connect } from &quot;pwa-helpers&quot;;
import { store } from &quot;../store&quot;;

class Tomato extends connect(store)(LitElement) {
</code></pre>
<p>리덕스의 값을 사용하기 위해 다음과 같이 작성합니다.</p>
<pre><code class="language-typescript">import { LitElement, html, customElement, property } from &quot;lit-element&quot;;
import &quot;../components/child-tomato&quot;;
import { connect } from &quot;pwa-helpers&quot;;
import { RootState, store } from &quot;../store&quot;;

@customElement(&quot;lit-tomato&quot;)
class Tomato extends connect(store)(LitElement) {
  //? state 정의 부분
  @property() name = &quot;&quot;;

  //* 리덕스 업데이트 될때 실행 된다
  stateChanged(state: RootState) {
    console.log(state);
    this.name = state.vegetable.name;
    super.stateChanged(state);
  }

  render() {
    return html`&lt;p&gt;${this.name}&lt;/p&gt;`;
  }
}

declare global {
  interface HTMLElementTagNameMap {
    &quot;lit-tomato&quot;: Tomato;
  }
}</code></pre>
<p>앞의 코드의 실행 결과 리덕스 값인 &#39;redux-tomato&#39;를 불러 올 수 있게 되었습니다.
<img src="https://images.velog.io/images/jerrynim_/post/fc8406fc-1990-422a-a7be-55e52a912eb6/image.png" alt=""></p>
<h3 id="스토어-업데이트하기">스토어 업데이트하기</h3>
<p>디스패치를 하여 리덕스의 name값을 변경해보도록 하겠습니다.</p>
<pre><code class="language-typescript">class Tomato extends connect(store)(LitElement) {
  //? state 정의 부분
  @property() name = store.getState().vegetable.name;

  //* 리덕스 업데이트 될때 실행 된다
  stateChanged(state: RootState) {
    console.log(&quot;stateChanged&quot;);
    this.name = state.vegetable.name;
    super.stateChanged(state);
  }

  //* name 변경하기
  changeName() {
    store.dispatch(vegetableActions.setName(&quot;changed-tomato&quot;));
  }

  render() {
    return html`&lt;p&gt;${this.name}&lt;/p&gt;
      &lt;button @click=&quot;${this.changeName}&quot;&gt;change&lt;/button&gt; `;
  }
}</code></pre>
<p>버튼을 클릭하면 액션을 디스패치하여 값이 변경되고, 바인딩된 name도 변경되는 것을 확인 할 수 있었습니다.
<img src="https://images.velog.io/images/jerrynim_/post/48dae1dc-dba5-45e7-8e89-c360ec3f8a3a/image.png" alt=""></p>
<p>.
.
.
.
.
.
.
..
.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[lit 프로젝트 시작하기 (4/6) - 바인딩]]></title>
            <link>https://velog.io/@jerrynim_/lit-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-4n-%EB%B0%94%EC%9D%B8%EB%94%A9</link>
            <guid>https://velog.io/@jerrynim_/lit-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-4n-%EB%B0%94%EC%9D%B8%EB%94%A9</guid>
            <pubDate>Wed, 04 Nov 2020 05:11:53 GMT</pubDate>
            <description><![CDATA[<p>블로그에서 보기 : <a href="https://lit-blog.vercel.app/post/lit-tutorial-4">https://lit-blog.vercel.app/post/lit-tutorial-4</a></p>
<p>이번 포스팅에는 LitElement에서 바인딩(bind) 하는 것을 알아보도록 하겠습니다.
lit-html에서는 엘리먼트에 자바스크립트 표현식을 바인딩 하는 방법이 특별합니다.
<a href="https://lit-html.polymer-project.org/guide/template-reference#binding-types">&gt; lit-html-biding</a>
예제를 통해 살펴보도록 하겠습니다.</p>
<h2 id="element">element</h2>
<h3 id="property">property</h3>
<p><code>&lt;input&gt;</code> 태그에 값을 변경하고 저장할 수 있도록 value와 change 이벤트를 넣어주도록 하겠습니다.</p>
<pre><code class="language-typescript">@customElement(&quot;lit-tomato&quot;)
class Tomato extends LitElement {
  @property() value = &quot;&quot;;

  onChange(e) {
    this.value = e.target.value;
    console.log(e.target.value);
  }

  render() {
    return html` &lt;input .value=&quot;${this.value}&quot; @keyup=&quot;${this.onChange}&quot; /&gt; `;
  }
}</code></pre>
<p><code>&lt;input&gt;</code>태그에 .value라는 값으로 value property를 넣어주었습니다. 여기서 속성에 &#39; . &#39;이 붙어있다면 값은 <code>&lt;input&gt;</code>태그의 value property에 바인딩 되게 됨을 의미합니다.</p>
<p>&#39; . &#39;은 lit-html에서 property를 바인딩 할 때 사용됩니다. </p>
<h3 id="event-listerner">event listerner</h3>
<p>@keyup을 넣게 되면 <code>&lt;input&gt;</code>의 &#39;keyup&#39;에 이벤트에  onChange라는 이벤트 헨들러를 추가해 주게 됩니다. 결과적으로, <code>&lt;input&gt;</code>의 keyup 이벤트가 발생할 때 마다 value property가 변경되며 콘솔이 찍히게 됩니다.</p>
<h3 id="attribute">attribute</h3>
<p>이번에는 <code>&lt;input&gt;</code>의 &#39;.value&#39; 속성을 &#39;value&#39;로 변경해보도록 하겠습니다.</p>
<pre><code>return html` &lt;input value=&quot;${this.value}&quot; @keyup=&quot;${this.onChange}&quot; /&gt; `;</code></pre><p>속성에 &#39; . &#39;을 제거하였을때 어떤 차이가 있는지 개발자도구의 &#39;Element&#39;탭을 보면 직접적으로 볼 수 있습니다.
<img src="https://images.velog.io/images/jerrynim_/post/5b359ee2-0929-4801-8923-7ceea9dc36c4/image.png" alt="">
&#39;value=&#39; 와 같이 속성이름 앞에 기호가 없다면 attribute로 바인딩 되게 됩니다.
attribute를 전달해 주면 그림과 같이 html 속성으로 추가되는걸 볼 수 있습니다. 몇 가지 attribute를 더 사용해 보도록 하겠습니다.</p>
<pre><code class="language-typescript">
  render() {
    return html`
      &lt;input
        id=&quot;my-input&quot;
        class=&quot;my-class&quot;
        type=&quot;text&quot;
        style=&quot;color:blue;&quot;
        .value=&quot;${this.value}&quot;
        @keyup=&quot;${this.onChange}&quot;
      /&gt;
    `;
  }</code></pre>
<p>이러한 값들을 attribute로 바인딩 할 수 있습니다.</p>
<h3 id="boolean-attribute">boolean attribute</h3>
<p>이번에는<code>&lt;input&gt;</code> 태그에 &#39;disabled&#39;를 추가해 보도록 하겠습니다. </p>
<pre><code>      &lt;input
        .value=&quot;${this.value}&quot;
        @keyup=&quot;${this.onChange}&quot;
        disabled=&quot;${false}&quot;
      /&gt;</code></pre><p>&#39;disabled&#39;를 추가하려하니 다음과 같은 경고가 나오게 됩니다.</p>
<p><img src="https://images.velog.io/images/jerrynim_/post/1bbe00bf-8496-471e-888d-f0982e434ad0/image.png" alt=""></p>
<p>해당 attribute는 boolean 타입이므로 boolean 바인딩을 사용하라는 경고 입니다. boolean 바인딩을 사용하기 위해서는 속성앞에 &#39;?&#39; 를 추가해 주어야 합니다.</p>
<pre><code class="language-typescript">  @property() value = &quot;4&quot;;
  @property({ type: Boolean }) disabled = false;

  onChange(e) {
    this.value = e.target.value;
  }

  render() {
    return html`
      &lt;input
        id=&quot;my-input&quot;
        .value=&quot;${this.value}&quot;
        @change=&quot;${this.onChange}&quot;
        ?disabled=&quot;${this.disabled}&quot;
      /&gt;
    `;
  }</code></pre>
<p>이렇게 lit-html의 엘리먼트에 값을 바인딩 하는 것을 살펴 보았습니다.
그렇다면 커스텀 엘리먼트에서는 어떻게 사용해야 할까요?
커스텀 엘리먼트에서는 값들을 어떻게 바인딩하여 사용 할 수 있는지 알아보도록 하겠습니다.</p>
<h2 id="custom-element">custom element</h2>
<p>앞에서 <code>&lt;input&gt;</code>태그를 사용하여 property를 바인딩 하였드이 커스텀 엘리먼트를 만들어 property를 바인딩 해보도록 하겠습니다. 우선 property를 가진 <code>&lt;lit-tomato&gt;</code>라는 엘리먼트를 만들어 <code>&lt;child-tomato&gt;</code>라는 컴포넌트를 바인딩 시키도록 하겠습니다.</p>
<p>앞의 코드에서 id는 attribute, .value는 property, @change는 event Listener, ?disabled는 boolean Attribute로 <code>&lt;input&gt;</code>태그에 바인딩 되었습니다. 만약 앞에 기호를 붙이지 않는다면 전부 attribute로 바인딩 되게 됩니다.</p>
<p>이번에는 커스텀 엘리먼트를 만들어 속성들을 바인딩해보도록 하겠습니다.</p>
<p><strong>pages/lit-tomato.ts</strong></p>
<pre><code class="language-typescript">import { LitElement, html, customElement, property } from &quot;lit-element&quot;;
import &quot;../components/child-tomato&quot;;

@customElement(&quot;lit-tomato&quot;)
class Tomato extends LitElement {
  render() {
    return html` &lt;child-tomato&gt;&lt;/child-tomato&gt; `;
  }
}

declare global {
  interface HTMLElementTagNameMap {
    &quot;lit-tomato&quot;: Tomato;
  }
}</code></pre>
<p><strong>components/child-tomao.ts</strong></p>
<pre><code class="language-typescript">import { LitElement, html, customElement, property } from &quot;lit-element&quot;;

@customElement(&quot;child-tomato&quot;)
class ChildTomato extends LitElement {
  render() {
    return html`
      &lt;style&gt;&lt;/style&gt;
      &lt;h1&gt;i&#39;m child-tomato&lt;/h1&gt;
      &lt;input /&gt;
    `;
  }
}

declare global {
  interface HTMLElementTagNameMap {
    &quot;child-tomato&quot;: ChildTomato;
  }
}
</code></pre>
<p><code>&lt;lit-tomato&gt;</code>에 앞에서와 같이 전달할 property를 만들도록 하겠습니다.</p>
<pre><code class="language-typescript">  @property({ type: String }) value = &quot;toamto&quot;;
  @property({ type: Boolean }) disabled = true;

  onChange(e) {
    this.value = e.target.value;
    console.log(e.target.value);
  }</code></pre>
<p><code>&lt;input&gt;</code>에서와 동일하게 속성을 전달해 보도록 하겠습니다.</p>
<pre><code class="language-typescript">      &lt;child-tomato
        id=&quot;my-input&quot;
        .value=&quot;${this.value}&quot;
        @change=&quot;${this.onChange}&quot;
        ?disabled=&quot;${this.disabled}&quot;
      &gt;&lt;/child-tomato&gt;</code></pre>
<p>그리고 child-tomato가 값을 받았는지 확인해 보도록 하겠습니다.</p>
<p><strong>components/child-tomao.ts</strong></p>
<pre><code class="language-typescript">class ChildTomato extends LitElement {
  //*connectedCallback은 커스텀 엘리먼트가 문서의 DOM에 처음 연결될 때 호출됩니다.
  connectedCallback() {
    super.connectedCallback();
    console.log(this.id);
    console.log(this.value);
    console.log(this.change);
    console.log(this.disabled);
  }</code></pre>
<p><img src="https://images.velog.io/images/jerrynim_/post/7c8319d2-8de0-45e5-b308-46adcf1bbb0f/image.png" alt=""></p>
<p>attribute 인 &#39;id&#39;와, property인 &#39;.value&#39;값이 출력 된 것을 확인 할 수 있었습니다. 
이벤트 헨들러와 disabled 값은 어떻게 받아와야 할까요?</p>
<p>이벤트 헨들러를 @change로 전달하게 되면 onChange 함수를 값으로 전달받는 것이 아닌, <code>&lt;child-tomato&gt;</code>에 change 의 change 이벤트에 이벤트 리스너가 추가 됨을 의미합니다. 이를 확인하기위해 event를 발생시켜 보도록 하겠습니다.</p>
<p><strong>components/child-tomao.ts</strong></p>
<pre><code class="language-typescript">  connectedCallback() {
    super.connectedCallback();

    this.dispatchEvent(new Event(&quot;change&quot;));

    // console.log(this.id);
    // console.log(this.value);
    // console.log(this.change);
    // console.log(this.disabled);
  }</code></pre>
<p>콘솔에 &#39;tomato&#39;가 출력 되는 것을 확인 할 수 있었습니다. 
좀 더 직접적으로 확인해 보기위해 이벤트 이름을 바꿔보도록 하겠습니다!
<strong>pages/lit-tomato.ts</strong></p>
<pre><code class="language-typescript">      &lt;child-tomato
        @jerrynim-custom-event=&quot;${this.onChange}&quot;
      &gt;&lt;/child-tomato&gt;</code></pre>
<pre><code class="language-typescript">class ChildTomato extends LitElement {
  connectedCallback() {
    super.connectedCallback();

    this.dispatchEvent(new Event(&quot;jerrynim-custom-event&quot;));</code></pre>
<p>동일하게 &#39;tomato&#39;가 출력되는 것을 확인 할 수 있었습니다.
그렇다면 <code>&lt;child-tomato&gt;</code>의 <code>&lt;input&gt;</code>태그에 onChange를 전달하여 사용하려면 어떻게 해야할가요?
<code>&lt;lit-tomato&gt;</code>에서 &#39;@change&#39;를 &#39;.onchange&#39;로 변경해 보도록 하겠습니다.</p>
<p><strong>pages/lit-tomato.ts</strong></p>
<pre><code class="language-typescript">      &lt;child-tomato
        .onchange=&quot;${this.onChange}&quot;
      &gt;&lt;/child-tomato&gt;</code></pre>
<p><strong>components/child-tomao.ts</strong></p>
<pre><code class="language-typescript">  connectedCallback() {
    super.connectedCallback();
    console.log(this.onchange);
  }</code></pre>
<p><img src="https://images.velog.io/images/jerrynim_/post/0033bb9e-b0c9-470b-b8a1-98d755ed5a2b/image.png" alt=""></p>
<p>이벤트 헨들러를 property로 전달해주었습니다. 전달받은 이벤트 헨들러를 <code>&lt;input&gt;</code>태그의 @keyup 속성에 넣어주도록 하겠습니다. 그리고 전달받은 value를 띄우도록 하겠습니다.</p>
<pre><code class="language-typescript">class ChildTomato extends LitElement {
  customFunction: any;

  render() {
    return html`
      &lt;style&gt;&lt;/style&gt;
      &lt;h1&gt;i&#39;m child-tomato&lt;/h1&gt;
      &lt;input .value=&quot;${this.value}&quot; @keyup=&quot;${this.customFunction}&quot; /&gt;
      &lt;p&gt;input value is ${this.value}&lt;/p&gt;
    `;
  }
}
</code></pre>
<p><img src="https://images.velog.io/images/jerrynim_/post/9dee8399-c42b-473a-9aa1-a9783aee06ee/image.png" alt=""></p>
<p>인풋안에 값을 입력하면 콘솔에 입력값이 출력 되는 걸 확인 할 수 있습니다.하지만 &#39;input value is tomaoto&#39;는 변경되지 않는 걸로 보아 <code>&lt;lit-tomato&gt;</code>의 value가 <code>&lt;child-tomato&gt;</code>에 바인딩 되지 않았다는 걸 알수 있습니다. 바인딩 시키기위해 <code>&lt;child-tomato&gt;</code>에 value property를 만들어 주도록 하겠습니다.</p>
<p><strong>components/child-tomato.ts</strong></p>
<pre><code class="language-typescript">class ChildTomato extends LitElement {
  @property({ type: String }) value = &quot;&quot;;
</code></pre>
<p>이제 인풋에 값을 입력하면 <code>&lt;p&gt;</code>태그안의 this.value 값도 변경되 는것을 확인 할 수 있습니다.
<img src="https://images.velog.io/images/jerrynim_/post/394cb829-5acf-4509-8737-c05baf7b8d27/image.png" alt=""></p>
<p>이처럼 커스텀 엘리먼트에 property를 바인딩 하기 위해선 커스텀 엘리먼트에서 property를 선언해 주어야 합니다.</p>
<p>이전에 &#39;?disabled&#39;로 전달해준 속성의 값이 undefined 였습니다. &#39; ?&#39; 기호는 attribute이기에 property처럼 값을 받지 못하였습니다. disabled 값을 바인딩하기 위해 커스텀 엘리먼트에 &#39;disabled&#39;라는 이름을 가진 attribute가 필요합니다. <code>&lt;child-tomato&gt;</code>에 &#39;disabled&#39;라는 attribute를 가진 property를 만들어 보도록 하겠습니다.</p>
<pre><code class="language-typescript">  @property({ type: Boolean, attribute: &quot;disabled&quot; }) disabledValue = false;

     &lt;input
        .value=&quot;${this.value}&quot;
        @keyup=&quot;${this.customFunction}&quot;
        ?disabled=&quot;${this.disabledValue}&quot;
      /&gt;</code></pre>
<p><img src="https://images.velog.io/images/jerrynim_/post/755285e0-d24a-4e1a-831b-504873707d22/image.png" alt="">
<code>&lt;lit-tomato&gt;</code>의 disabled 값은 &#39;true&#39;로 disabled 값이 잘 적용 된 것을 확인할 수 있습니다.</p>
<h3 id="children">children</h3>
<p>이번에는 <code>&lt;child-tomato&gt;</code>에게 html 템플릿을 전달해보도록 하겠습니다. 
<strong>pages/lit-tomato.ts</strong></p>
<pre><code class="language-typescript">import { LitElement, html, customElement, property } from &quot;lit-element&quot;;
import &quot;../components/child-tomato&quot;;

@customElement(&quot;lit-tomato&quot;)
class Tomato extends LitElement {
  render() {
    return html`&lt;child-tomato&gt;&lt;p&gt;자식&lt;/p&gt;&lt;/child-tomato&gt;`;
  }
}

declare global {
  interface HTMLElementTagNameMap {
    &quot;lit-tomato&quot;: Tomato;
  }
}

</code></pre>
<p><strong>components/child-tomato.ts</strong></p>
<pre><code class="language-typescript">import { LitElement, html, customElement, property } from &quot;lit-element&quot;;

@customElement(&quot;child-tomato&quot;)
class ChildTomato extends LitElement {
  render() {
    return html`
      &lt;style&gt;&lt;/style&gt;
      &lt;div&gt;자식 :&lt;/div&gt;
    `;
  }
}

declare global {
  interface HTMLElementTagNameMap {
    &quot;child-tomato&quot;: ChildTomato;
  }
}


</code></pre>
<p><img src="https://images.velog.io/images/jerrynim_/post/fc013f82-0baa-4f09-af42-771d8daadc14/image.png" alt="">
자식을 나타내려면 어떻게 해야할까요?
첫번째 방법은 간단하게 this.children을 사용하면 됩니다.
<strong>components/child-tomato.ts</strong></p>
<pre><code class="language-typescript">class TomatoChild extends LitElement {
  render() {
    return html`
      &lt;style&gt;&lt;/style&gt;
      &lt;div&gt;${this.children}&lt;/div&gt;
    `;
  }
}</code></pre>
<p>다른 방법은 쉐도우 돔의 <code>&lt;slot&gt;</code>태그를 사용하는 것입니다. 
<a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot">&gt; slot MDN</a></p>
<pre><code class="language-typescript">    return html`
      &lt;style&gt;&lt;/style&gt;
      &lt;div&gt;자식 :&lt;/div&gt;
      &lt;slot&gt;&lt;/slot&gt;
    `;</code></pre>
<h3 id="마치며">마치며...</h3>
<p>Lit-html에서 property를 바인딩 하는 것을 알아 보았습니다. 바인딩하는 법만 알게된다면 투두리스트 정도의 웹을 만드는데 큰 어려움을 없을 거라 생각합니다. 
다음 포스팅에서는 redux를 사용하여 전역 상태관리를 해보려고 합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[lit 프로젝트 시작하기 (3/6) - LifeCycle]]></title>
            <link>https://velog.io/@jerrynim_/lit-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-3n-LifeCycle</link>
            <guid>https://velog.io/@jerrynim_/lit-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-3n-LifeCycle</guid>
            <pubDate>Thu, 29 Oct 2020 08:15:37 GMT</pubDate>
            <description><![CDATA[<p>블로그에서 보기 : <a href="https://lit-blog.vercel.app/post/lit-tutorial-3">https://lit-blog.vercel.app/post/lit-tutorial-3</a></p>
<p><a href="https://lit-element.polymer-project.org/guide/lifecycle">LitElement의 라이프사이클</a>에 대해 다뤄보도록 하겠습니다. 모든 라이프사이클 메서드에서는 super를 사용하여야 하는것을 유의하여야 합니다.</p>
<p>lit-element는 웹 컴포넌트의 기본 라이프사이클을 상속하게 됩니다.
<a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements#Using_the_lifecycle_callbacks">&gt; 웹 컴포넌트 라이프사이클</a></p>
<h2 id="웹-컴포넌트-라이프-사이클">웹 컴포넌트 라이프 사이클</h2>
<p>웹 컴포넌트의 기본 라이프사이클은 다음과 같습니다.</p>
<blockquote>
<p>connectedCallback: 커스텀 엘리먼트가 문서의 DOM에 처음 연결될 때 호출됩니다.
disconnectedCallback: 커스텀 엘리먼트가 문서의 DOM에서 연결 해제 될 때 호출됩니다.</p>
</blockquote>
<p>conntectedCallback을 사용하여 리액트에서 componentDidMount처럼 이벤트를 추가하거나 데이터를 불러올 수 있습니다.</p>
<pre><code class="language-typescript">connectedCallback() {
  super.connectedCallback();
  window.addEventListener(&#39;resize&#39;, this._handleResize);
}
disconnectedCallback() {
  window.removeEventListener(&#39;resize&#39;, this._handleResize);
  super.disconnectedCallback();
}</code></pre>
<blockquote>
<p>adoptedCallback: 커스텀 엘리먼트가 새 문서로 이동 될 때 호출됩니다.</p>
</blockquote>
<blockquote>
<p>attributeChangedCallback: 커스텀 엘리먼트의 속성 중 하나가 추가, 제거 또는 변경 될 때 호출됩니다.</p>
</blockquote>
<p>다음과 같이 변경된 속성의 이름, 변경되기 전의 값, 변경된 값을 세개의 인자로 받게 됩니다.</p>
<pre><code class="language-typescript">  attributeChangedCallback(name, oldValue, newValue) {
    super.attributeChangedCallback(name, oldValue, newValue);
    console.log(name, oldValue, newValue);
  }
}</code></pre>
<h2 id="property-변경-라이프사이클">property 변경 라이프사이클</h2>
<p>엘리먼트의 property를 변경하게 될때의 라이프 사이클 순서입니다.</p>
<ol>
<li>someProperty.hasChanged</li>
<li>requestUpdate</li>
<li>performUpdate</li>
<li>shouldUpdate</li>
<li>update</li>
<li>render</li>
<li>firstUpdated</li>
<li>updated</li>
<li>updateComplete</li>
</ol>
<p>하나씩 살펴보도록 하겠습니다.</p>
<h3 id="1-somepropertyhaschanged">1. someProperty.hasChanged</h3>
<p>이전 포스팅에서 property의 hasChanged 옵션을 이용하여 업데이트를 제어 할 수 있었습니다.</p>
<pre><code class="language-typescript">  @property({
    hasChanged: (newVal, oldVal) =&gt; {
      console.log(&quot;has changed&quot;, oldVal, &quot; to &quot;, newVal);
      return true;
    },
  })
  name = &quot;original-name&quot;;</code></pre>
<h3 id="2-requestupdate">2. requestUpdate</h3>
<p>property의 hasChanged가 true라면 property의 setter가 실행 됩니다. 그리고 setter 내부의 
    this.requestUpdate(&quot;prop&quot;, oldVal) 를 실행하게 됩니다.</p>
<pre><code class="language-typescript">@customElement(&quot;lit-tomato&quot;)
class Tomato extends LitElement {
  private _name: string = &quot;original-name&quot;;

  @property({
    hasChanged: (newVal, oldVal) =&gt; {
      return true;
    },
  })
  get name() {
    return this._name;
  }

  set name(val) {
    let oldVal = this._name;
    this._name = val;
    this.requestUpdate(&quot;prop&quot;, oldVal);
  }
  render() {
    return html`
      &lt;style&gt;&lt;/style&gt;
      &lt;h1&gt;Hello ${this.name}&lt;/h1&gt;
      &lt;button @click=&quot;${this.changeProperties}&quot;&gt;changeProperties&lt;/button&gt;
    `;
  }

  changeProperties() {
    this.name = &quot;changed-name&quot;;
  }
}</code></pre>
<h3 id="3-performupdate">3. performUpdate</h3>
<p>기본적으로 performUpdate는 브라우저 이벤트 루프의 다음 실행이 끝난 후 마이크로 태스크로 예약됩니다. 일정을 잡으려면 performUpdate호출하기 전에 상태를 기다리는 비동기 메서드로 구현합니다 super.performUpdate(). 예를 들면 :</p>
<pre><code class="language-typescript"> async performUpdate() {
    console.log(&quot;performUpdate&quot;);
    await new Promise((resolve) =&gt; requestAnimationFrame(() =&gt; resolve()));
    super.performUpdate();
  }</code></pre>
<h3 id="4-shouldupdate">4. shouldUpdate</h3>
<p>업데이트 진행 여부를 제어합니다. 기본적으로 이 메서드는 항상 true를 반환합니다.</p>
<pre><code class="language-typescript">  shouldUpdate(changeProperties) {
    console.log(&quot;shouldUpdate?&quot;, changeProperties);
    super.shouldUpdate(changeProperties);
    return true;
  }</code></pre>
<p>그림과 같이 콘솔에 변경된 값들의 이전 값이 출력 됩니다.
<img src="https://images.velog.io/images/jerrynim_/post/0ca61c5a-2189-4047-8034-13c636cc028b/image.png" alt=""></p>
<h3 id="5-update">5. update</h3>
<p>reflects property values to attributes(property값들을 attributes에 반영합니다.
lit-html의 render를 호출 합니다.</p>
<h3 id="6-render">6. render</h3>
<p>lit-html의 render를 이용하여 DOM을 렌더링 합니다.</p>
<h3 id="7-firstupdated">7. firstUpdated</h3>
<p>돔이 처음으로 업데이트 되었을때 호출 됩니다. 엘리먼트의 템플릿이 처음 만들어 졌을때 한번만 실행하기 위해 사용 됩니다.</p>
<pre><code class="language-typescript">  firstUpdated(changedProperties) {
    console.log(&quot;first updated!&quot;);
    super.firstUpdated(changedProperties);
  }
</code></pre>
<h3 id="8-updated">8. updated</h3>
<p>돔이 업데이트되어 렌더링된 후에 호출 됩니다.</p>
<pre><code class="language-typescript">  updated(changedProperties) {
    console.log(&quot;updated&quot;, changedProperties);
    super.updated(changedProperties);
  }</code></pre>
<h3 id="9-updatecomplete">9. updateComplete</h3>
<p>updateComplete 는  Promise 로 업데이트가 끝났을때 resolve 합니다.</p>
<pre><code class="language-typescript">  async changeProperties() {
    this.name = &quot;changed-name&quot;;
    await this.updateComplete;
    console.log(&quot;update completed!&quot;);
  }</code></pre>
<h2 id="한번에-보기">한번에 보기</h2>
<pre><code class="language-typescript">@customElement(&quot;lit-tomato&quot;)
class Tomato extends LitElement {
  @property({
    hasChanged: (newVal, oldVal) =&gt; {
      console.log(&quot;has changed&quot;, oldVal, &quot; to &quot;, newVal);
      return true;
    },
  })
  name = &quot;original-name&quot;;

  connectedCallback() {
    super.connectedCallback();
    console.log(&quot;connected&quot;);
  }
  disconnectedCallback() {
    super.connectedCallback();
    console.log(&quot;disconnected&quot;);
  }

  async performUpdate() {
    console.log(&quot;performUpdate&quot;);
    await new Promise((resolve) =&gt; requestAnimationFrame(() =&gt; resolve()));
    super.performUpdate();
  }

  shouldUpdate(changeProperties) {
    console.log(&quot;shouldUpdate?&quot;, changeProperties);
    super.shouldUpdate(changeProperties);
    return true;
  }

  firstUpdated(changedProperties) {
    console.log(&quot;first updated!&quot;);
    super.firstUpdated(changedProperties);
  }

  updated(changedProperties) {
    console.log(&quot;updated&quot;, changedProperties);
    super.updated(changedProperties);
  }

  render() {
    return html`
      &lt;style&gt;&lt;/style&gt;
      &lt;h1&gt;Hello ${this.name}&lt;/h1&gt;
      ${console.log(&quot;render!!&quot;)}
      &lt;button @click=&quot;${this.changeProperties}&quot;&gt;changeProperties&lt;/button&gt;
    `;
  }

  async changeProperties() {
    this.name = &quot;changed-name&quot;;
    await this.updateComplete;
    console.log(&quot;update completed!&quot;);
  }
}</code></pre>
<p>실행한 후 처음 페이지에 접속하여 커스텀 엘리먼트를 불러왔을 때
<img src="https://images.velog.io/images/jerrynim_/post/b4b9c0c4-e460-45c8-bcf7-993ce6ba406f/image.png" alt=""></p>
<p>버튼을 클릭하였을 때
<img src="https://images.velog.io/images/jerrynim_/post/9de9a902-6cc9-419a-813e-c84425c77555/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[lit 프로젝트 시작하기 (2/6) - property]]></title>
            <link>https://velog.io/@jerrynim_/lit-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-2n-property</link>
            <guid>https://velog.io/@jerrynim_/lit-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-2n-property</guid>
            <pubDate>Thu, 29 Oct 2020 05:09:50 GMT</pubDate>
            <description><![CDATA[<p>블로그에서 보기 : <a href="https://lit-blog.vercel.app/post/lit-tutorial-2">https://lit-blog.vercel.app/post/lit-tutorial-2</a></p>
<p>이전 포스팅에서 lit-project를 설치하여 lit-element를 사용하여 커스텀 엘리먼트가 만들어 지는 것을 보았습니다. 이번포스팅 에서는 엘리먼트의 property를 사용하여 템플릿을 업데이트하며 property의 옵션들을 살펴보도록 하겠습니다.</p>
<h1 id="properties">properties</h1>
<p>lit-element에서 템플릿을 정의할때 property를 템플릿에 바인딩 할 수 있으며, 속성이 변경 될 때 마다 템플릿 이 변경 됩니다. 
<strong>pages/lit-tomato.ts</strong></p>
<pre><code class="language-typescript">class Tomato extends LitElement {
    static get properties() {
      return {
        name,
      };
    }


  render() {
    return html`
      &lt;style&gt;&lt;/style&gt;
      &lt;h1&gt;Hello ${this.name}&lt;/h1&gt;
    `;
  }
}</code></pre>
<p>static get properties()를 사용하여 엘리먼트의 property를 선언 할 수 있습니다. name이라는 property를 선언하였고 name property를 html 템플릿에 바인딩 하였습니다. 후에 name이 변경된다면 html 템플릿도 변경 되게 됩니다.</p>
<h2 id="property-초기화">property 초기화</h2>
<p>선언된 property를 초기화 하는 방법에 대해 알아보도록 하겠습니다. 
앞에서 선언한 name에 초기값을 주도록 하겠습니다.</p>
<h3 id="constructor">constructor</h3>
<p>property의 초기값을 주기 위해 constructor()를 사용 할 수 있습니다.</p>
<pre><code class="language-typescript">class Tomato extends LitElement {
  static get properties() {
    return {
      name,
    };
  }

  constructor() {
    super();
    this.name = &quot;tomato1&quot;;
  }
  render() {
    return html`
      &lt;style&gt;&lt;/style&gt;
      &lt;h1&gt;Hello ${this.name}&lt;/h1&gt;
    `;
  }
}</code></pre>
<p>constructor()를 사용하여 name property에 &#39;tomato1&#39;값으로 초기화 해주었습니다.
&#39;Hello tomato1&#39;이 브라우저에 출력 되는 것을 확인 할 수 있습니닫. 하지만 vsCode에서는 타입에러가 있다는 에러가 발생합니다.</p>
<blockquote>
<p>Property &#39;name&#39; does not exist on type &#39;Tomato&#39;.ts(2339)</p>
</blockquote>
<h3 id="property">@property</h3>
<p>타입스크립트를 사용한다면 @property 데코레이터를 사용하여 property 선언과 초기화를 할 수 있습니다.</p>
<pre><code class="language-typescript">import { LitElement, html, customElement, property } from &quot;lit-element&quot;;

@customElement(&quot;lit-tomato&quot;)
class Tomato extends LitElement {
  @property() name = &quot;tomato1&quot;;

  render() {
    return html`
      &lt;style&gt;&lt;/style&gt;
      &lt;h1&gt;Hello ${this.name}&lt;/h1&gt;
    `;
  }
}
</code></pre>
<p>constructor를 사용 할 때와 동인한 결과가 나타납니다. 앞의 constructor를 사용할때와 같은 기능을 한다는 것을 확인 할 수 있습니다.
@property의 인자로 <a href="https://lit-element.polymer-project.org/guide/properties#property-options">다양한 옵션들</a>을 설정해 사용할 수 있습니다. 후에 하나씩 살펴보도록 하겠습니다.</p>
<h2 id="change-property">change property</h2>
<p>property에 변화를 주어 리렌더가 되는것을 해보려고 합니다. setTimeOut을 이용하여 2초후에 이름이 바뀌게 해보도록 하겠습니다.</p>
<pre><code class="language-typescript">@customElement(&quot;lit-tomato&quot;)
class Tomato extends LitElement {
  @property() name = &quot;tomato1&quot;;

  connectedCallback() {
    super.connectedCallback();

    setTimeout(() =&gt; {
      this.name = &quot;tomato2&quot;;
    }, 2000);
  }</code></pre>
<p>실행을 해보면 2초후에 &#39;tomato1&#39;이 &#39;tomato2&#39;로 변경되는 것을 볼 수 있습니다.</p>
<p>connectedCallback() : 웹 컴포넌트의 생명주기 중 하나로 문서의 DOM에 컴포넌트가 추가 될 때 호출됩니다. 리액트에서 componentDidMount라고 생각하면 쉽게 다가올 것입니다. 모든 생명주기 메서드는 &#39;super.connectedCallback()&#39; 와 같이 super 메서드를 호출해야합니다.</p>
<pre><code class="language-typescript">this.name=&quot;tomato2&quot;;</code></pre>
<p>앞의 코드처럼 property를 변경 할 수 있습니다. property 변경은 비동기 업데이트주기를 트리거하여 구성 요소가 템플릿을 다시 렌더링하도록 할 수 있습니다. lit-element의 생명주기는 다음 포스팅에서 알아보기로 하겠습니다. </p>
<h3 id="attribute">attribute</h3>
<p>이번에는 다른 방법을 이용하여 property를 변경시켜보도록 하겠습니다.
this.setAttribute()를 사용하여  property를 변경 해보도록 하려고 합니다.</p>
<pre><code class="language-typescript">  @property() name = &quot;tomato1&quot;;

  connectedCallback() {
    super.connectedCallback();

    setTimeout(() =&gt; {
      this.setAttribute(&quot;name&quot;, &quot;tomato3&quot;);
      this.requestUpdate();
    }, 2000);
  }</code></pre>
<p>2초후에 &#39;Hello tomato3&#39;으로 변경이 되었습니다.
 setAttribute는 attribute를 변경하는 함수인데 어째서 property가 변경 되었을까요?</p>
<blockquote>
<p>lit-element에서 선언된 모든 property는 observed attribute가 만들어 지게 됩니다.</p>
</blockquote>
<p>property에 대한 attribute는 lowercased 문자열로 생성이 됩니다. 예로 myProp이라는 property는 &quot;myprop&quot;이라는 attribute를 가지게 됩니다. </p>
<p>앞에 property를 선언할때 옵션을 줄 수 있다고 하였는데 옵션중 attribute는 이와 관련된 설정을 하게 됩니다.</p>
<pre><code class="language-typescript">  @property({ attribute: true }) myProp1 = &quot;myProp1&quot;;
  @property({ attribute: false }) myProp2 = &quot;myProp2&quot;;
  @property({ attribute: &quot;my-custom-prop3&quot; }) myProp3 = &quot;myProp3&quot;;</code></pre>
<p>attribute를 설정해주지 않는다면 attribute의 기본값인 true가 설정 됩니다.
값이 true일 때 myProps1(property)는 &quot;myprop1&quot; attribute를 가지게 됩니다.
값이 true일 때 myProps2(property)는 attribute를 가지지 않습니다.
값이 &quot;my-custom-prop3&quot; 일 때 myProp3(property)는 &quot;my-custom-prop3&quot;이라는 attribute를 가지게 됩니다.</p>
<p>setAttribute()를 사용하면 첫번째 인자로 전달해준 attribute명의 property 값을 두번째 인자로 전달해준 값으로 변경하게 됩니다. 앞에서 &#39;name&#39;이 변경 된 것은 name property가 &#39;name&#39; attribute값을 가지게 되었고, 
this.setAttribute(&quot;name&quot;, &quot;tomato3&quot;)를 통해 &#39;name&#39; attribute를 가진 property의 값을 &#39;tomato3&#39;으로 변경하게 되었기 때문입니다.</p>
<p>각자 다른 attribute 값을 가진 프로퍼티에 setAttribute()를 이용하여 property 값이 변경되는지 확인해보도록 하겠습니다.</p>
<pre><code class="language-typescript">@customElement(&quot;lit-tomato&quot;)
class Tomato extends LitElement {
  @property() name = &quot;tomato1&quot;;
  @property({ attribute: true }) myProp1 = &quot;myProp1&quot;;
  @property({ attribute: false }) myProp2 = &quot;myProp2&quot;;
  @property({ attribute: &quot;my-custom-prop3&quot; }) myProp3 = &quot;myProp3&quot;;

  connectedCallback() {
    super.connectedCallback();
    setTimeout(() =&gt; {
      this.setAttribute(&quot;name&quot;, &quot;changed-name&quot;);
      this.setAttribute(&quot;myprop1&quot;, &quot;changed-myProp1&quot;);
      this.setAttribute(&quot;myprop2&quot;, &quot;changed-myProp2&quot;);
      this.setAttribute(&quot;my-custom-prop3&quot;, &quot;changed-myProp3&quot;);

      this.requestUpdate();
    }, 2000);
  }

  render() {
    return html`
      &lt;style&gt;&lt;/style&gt;
      &lt;h1&gt;Hello ${this.name}&lt;/h1&gt;
      &lt;h1&gt;Hello ${this.myProp1}&lt;/h1&gt;
      &lt;h1&gt;Hello ${this.myProp2}&lt;/h1&gt;
      &lt;h1&gt;Hello ${this.myProp3}&lt;/h1&gt;
    `;
  }
}</code></pre>
<p><img src="https://images.velog.io/images/jerrynim_/post/12a315d6-2984-4027-9470-5f6672d984f7/image.png" alt="">
myProp2를 제외한 property의 값이 변경 되었습니다. myProp2는 왜 변경되지 않았을까요?</p>
<blockquote>
<p>@property({ attribute: false }) myProp2 = &quot;myProp2&quot;;</p>
</blockquote>
<p>attribute 값을 false로 하여attribute가 생성되지 않아 setAttribute(&quot;myprop2&quot;,&quot;&quot;)에서 &quot;myprop2&quot;를 찾을 수 없었기 때문입니다. attribute가 변경 되는 것을 좀더 직접적으로 확인해 보도록 하겠습니다.</p>
<pre><code class="language-typescript">   attributeChangedCallback(attributeName: string, oldVal: any, newVal: any) {
    super.attributeChangedCallback(attributeName, oldVal, newVal);
    console.log(
      &quot;attribute &quot;,
      attributeName,
      &quot;은 &quot;,
      oldVal,
      &quot;에서 &quot;,
      newVal,
      &quot;로 변경되었습니다.&quot;
    );
  }</code></pre>
<p>콘솔에 다음과 같이 출력 되었습니다.
<img src="https://images.velog.io/images/jerrynim_/post/d638616e-070a-4963-a7f3-5f6190103a2a/image.png" alt="">
attributeChangedCallback(): 웹 컴포넌트의 생명주기 중 하나로 컴포넌트 attribute값이 변경 될 때 호출됩니다. 첫번째 인자로 변경된 attribute의 문자열을, 두번째 인자로 변견 전의 값을, 세번째 인자로 변경 된 값을 받게 됩니다. 마찬자기로 생명주기 메서드는 super를 사용해야 합니다. </p>
<p>그렇다면 이전에 하였던 property를 직접 변경하는 방법을 사용하면 어떻게 될까요?
버튼을 만들어 setAttribute() 대신 직접 property를 변경하는 함수를 버튼의 클릭 이벤트로 전달해 주도록 하겠습니다.</p>
<pre><code class="language-typescript">@customElement(&quot;lit-tomato&quot;)
class Tomato extends LitElement {
  @property() name = &quot;tomato1&quot;;
  @property({ attribute: true }) myProp1 = &quot;myProp1&quot;;
  @property({ attribute: false }) myProp2 = &quot;myProp2&quot;;
  @property({ attribute: &quot;my-custom-prop3&quot; }) myProp3 = &quot;myProp3&quot;;

  changeProperties() {
    this.name = &quot;changed-name&quot;;
    this.myProp1 = &quot;changed-myProp1&quot;;
    this.myProp2 = &quot;changed-myProp2&quot;;
    this.myProp3 = &quot;changed-myProp3&quot;;
  }

  render() {
    return html`
      &lt;style&gt;&lt;/style&gt;
      &lt;h1&gt;Hello ${this.name}&lt;/h1&gt;
      &lt;h1&gt;Hello ${this.myProp1}&lt;/h1&gt;
      &lt;h1&gt;Hello ${this.myProp2}&lt;/h1&gt;
      &lt;h1&gt;Hello ${this.myProp3}&lt;/h1&gt;
      &lt;button @click=&quot;${this.changeProperties}&quot;&gt;changeProperties&lt;/button&gt;
    `;
  }
}</code></pre>
<p><img src="https://images.velog.io/images/jerrynim_/post/64bc5b0e-7ff2-466d-8d9a-7b1791050a92/image.png" alt=""></p>
<p>버튼을 클릭하자 값이 전부 변경 되었습니다. 이 방식은 property의 attribute을 변경 한 것이아닌 property의 자동으로 생성 된 setter를 실행하여 변경하였기 때문입니다.</p>
<h3 id="accessors">accessors</h3>
<p>property를 선언하였을때 다음과 같이 getter와 setter가 생성 됩니다.</p>
<pre><code class="language-typescript">static get properties() { return { myProp: { type: String } }; }

set myProp(value) {
  // Implement setter logic here... 
  // retrieve the old property value and store the new one
  this.requestUpdate(&#39;myProp&#39;, oldValue);
} 
get myProp() { ... }

...

// Later, set the property
this.myProp = &#39;hi&#39;; // Invokes your setter</code></pre>
<p>getter와 setter 쌍을 property accessors라고 부릅니다. property를 선언할때에 accessors를 자동으로 만들지 않고 getter와 setter를 직접 만들어 줄 수 있습니다. &#39;name&#39; property를 다음과 같이 변경하도록 하겠습니다.</p>
<pre><code class="language-typescript">  private _name: string = &quot;tomato1&quot;;

  @property({ noAccessor: true })
  get name() {
    return this._name;
  }

  set name(val) {
    let oldVal = this._name;
    this._name = val + &quot;-custom&quot;;
    this.requestUpdate(&quot;prop&quot;, oldVal);
  }</code></pre>
<p>setter에 변경될 값에 &quot;-custom&quot;이라는 글자가 추가되도록 하였습니다.
<img src="https://images.velog.io/images/jerrynim_/post/f30a1d3b-c24d-4665-90f2-b81b55308676/image.png" alt="">
커스텀 setter가 실행되어 그림과 같이 출력이 되는 것을 볼 수 있습니다.
앞에서 attribute를 변경하는 것과 accessors를 이용하여 property를 변경하는 것을 해보았습니다.</p>
<p>그렇다면 이번에는 반대로 property의 업데이트를 방지하는 것은 어떻게 해야할까요?</p>
<h2 id="haschanged">hasChanged</h2>
<p>property의 hascChanged옵션을 사용한다면 이전 값과, 새로운 값을 이용하여 property의 업데이트를 결정 할 수 있습니다.</p>
<pre><code class="language-typescript">class Tomato extends LitElement {
  @property({
    hasChanged: (newVal, oldVal) =&gt; {
      return false;
    },
  })
  name = &quot;original-name&quot;;

  changeProperties() {
    this.name = &quot;changed-name&quot;;
  }
  render() {
    return html`
      &lt;style&gt;&lt;/style&gt;
      &lt;h1&gt;Hello ${this.name}&lt;/h1&gt;
      &lt;button @click=&quot;${this.changeProperties}&quot;&gt;changeProperties&lt;/button&gt;
    `;
  }
}</code></pre>
<p>버튼을 클릭해도 업데이트가 발생하지 않는 것을 볼 수 있습니다.
(버그인지 다른 값이 업데이트 되면 hasChanged가 false여도 업데이트가 되버리네요... 그래서 Issue를 만들엇습니다.<a href="https://github.com/Polymer/lit-element/issues/1098">https://github.com/Polymer/lit-element/issues/1098</a>)</p>
<p>여기까지 property의 옵션 중attribute, noAccessor, hasChanged 값에대해 다뤄 보았습니다.
property는 이외에도 type, reflect, converter 옵션을 가지고 있습니다. 이는 property의 값을 변환해주는 용도로 사용할 수 있습니다. 더 자세한 정보는 <a href="https://lit-element.polymer-project.org/guide/properties">properties 공식문서</a> 를 참고 하시기 바랍니다.</p>
<p>다음 장에서는 lit-element의 생명주기를 살펴보면서 업데이트가 발생하는 순서를 살펴보도록 하겠습니다.</p>
]]></description>
        </item>
    </channel>
</rss>