<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>heejin</title>
        <link>https://velog.io/</link>
        <description>FE DEVELOPER 👩🏻‍💻🤍</description>
        <lastBuildDate>Tue, 11 Feb 2025 01:39:23 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>heejin</title>
            <url>https://images.velog.io/images/heejin-k/profile/18ac42a6-3651-4730-9ad4-71e6ee8e43bf/미모티콘.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. heejin. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/heejin-k" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[웹사이트 해외 접속 테스트 : VPN]]></title>
            <link>https://velog.io/@heejin-k/%EC%9B%B9%EC%82%AC%EC%9D%B4%ED%8A%B8-%ED%95%B4%EC%99%B8-%EC%A0%91%EC%86%8D-%ED%85%8C%EC%8A%A4%ED%8A%B8-VPN</link>
            <guid>https://velog.io/@heejin-k/%EC%9B%B9%EC%82%AC%EC%9D%B4%ED%8A%B8-%ED%95%B4%EC%99%B8-%EC%A0%91%EC%86%8D-%ED%85%8C%EC%8A%A4%ED%8A%B8-VPN</guid>
            <pubDate>Tue, 11 Feb 2025 01:39:23 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>해외에서 자사 서비스가 잘 동작하는지 테스트를 위해 찾아본 VPN 리스트와 사용방법입니다. 
2023.08.18에 작성해둔 글로 현재와 다를수도 있습니다.</p>
</blockquote>
<h2 id="vpn-리스트">VPN 리스트</h2>
<h3 id="무료-vpn-리스트">무료 VPN 리스트</h3>
<ol>
<li>privado VPN
링크 : <a href="https://privadovpn.com/free-vpn/">https://privadovpn.com/free-vpn/</a>
데이터 용량 : 매월 10GB</li>
<li>proton VPN
지원 국가 : 미국, 일본, 네덜란드
링크 : <a href="https://protonvpn.com/">https://protonvpn.com/</a>
데이터 용량 :</li>
<li>Hide.me ( 네덜란드, 캐나다, 미국 )
링크 : <a href="https://hide.me/en/free-vpn">https://hide.me/en/free-vpn</a></li>
<li>TunnelBear
지원 국가 : ( 20개국 이상 )
링크 : <a href="https://www.tunnelbear.com/">https://www.tunnelbear.com/</a>
데이터 용량 : 매월 2GB</li>
</ol>
<h3 id="유료-vpn-리스트">유료 VPN 리스트</h3>
<ol>
<li>ExpressVpn
링크 : <a href="https://www.expressvpn.com/">https://www.expressvpn.com/</a></li>
<li>CypberGhost
링크 : <a href="https://www.cyberghostvpn.com/">https://www.cyberghostvpn.com/</a></li>
<li>PrivateVPN
링크 : <a href="https://privatevpn.com/">https://privatevpn.com/</a></li>
<li>IPVanish
링크 : <a href="https://www.ipvanish.com/">https://www.ipvanish.com/</a></li>
</ol>
<p> VPN 마다 무료 버전의 경우 지원하는 국가 수의 차이, 데이터 용량의 차이가 있으므로 상황에 맞는 적절한 VPN을 사용하시면 됩니다. 현재 단순 해외에서 접속 가능 여부 테스트 용의 경우 많은 데이터가 필요하지 않기 때문에 <strong>가장 많은 국가를 지원하는 &#39;TunnelBear&#39;를 채택</strong>하여 테스트 하고 있습니다. </p>
<p>60개국, 90개국 이상 등 많은 국가 지원 및 많은 용량의 데이터가 필요한 작업일 경우 유료 VPN을 구독하여 사용해야 합니다.</p>
<h2 id="vpn-테스트-방법">VPN 테스트 방법</h2>
<p>** 테스트 가능한 국가 수가 많은 TunnelBear를 주로 사용합니다.</p>
<h3 id="1️⃣-tunnelbear">1️⃣ TunnelBear</h3>
<p>✅ 파일을 다운로드 받은 후 간단한 회원가입, 이메일 인증 필요
무료버전으로도 여러개국 확인 가능
한달에 2GB 사용가능</p>
<ol>
<li><p>TunnelBear 다운로드 하기</p>
<p> 다운로드 링크 : <a href="https://www.tunnelbear.com/download">https://www.tunnelbear.com/download</a></p>
<p> TunnelBear를 다운로드 하여 안내에 따라 데스크탑에 파일을 설치합니다. </p>
</li>
<li><p>회원가입</p>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/d6830eaa-dbca-47de-a29a-be990cc69fbf/image.png" alt=""></p>
<p>간단한 이메일과 패스워드를 입력하여 회원가입을 합니다.</p>
<ol>
<li>이메일 인증
<img src="https://velog.velcdn.com/images/heejin-k/post/ad4ccc46-4694-4418-961d-bd951ac5c627/image.png" alt=""></li>
</ol>
<p>등록한 이메일로 들어가서 계정을 확인 후 다시 프로그램에 접속합니다.</p>
<ol>
<li>국가 선택 및 연결</li>
</ol>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/ba5dac61-2190-40e4-85b0-b85175d48475/image.png" alt=""></p>
<p>on &amp; off 버튼 옆의 셀렉트 박스에서 연결할 국가를 선택하거나 지도에서 클릭합니다. </p>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/1143f2f7-c6c6-4bac-9bd8-96d3cf0b4d22/image.png" alt=""></p>
<p>국가를 선택하면 자동으로 버튼이 on으로 변경되며 연결이 완료되면 회색이었던 지도가 컬러로 변하며 연결이 완료되었다는 알림 팝업이 뜹니다.</p>
<ol start="5">
<li><p>이제 연결이 완료되었습니다. 테스트를 원하는 사이트 주소로 들어가서 정상적으로 작동하는지 확인해주세요!  ip가 제대로 변경되었는지 확인을 원하신다면 <a href="https://ipleak.net/">🔁 ip 변경 확인하는 사이트</a> 이 사이트에서 확인해주세요 </p>
</li>
<li><p>사용을 마쳤으면 <strong>버튼을 off 로 변경</strong>해주어 연결을 끊어줍니다. 
TunnelBear 의 경우 2GB만 사용가능하며 아래 하단에 남은 잔여 데이터가 표시됩니다. </p>
</li>
</ol>
<p><strong>사용 가능한 용량이 한정적이기 때문에 꼭 연결을 끊어주세요 !</strong></p>
<h3 id="2️⃣-proton-vpn">2️⃣ proton VPN</h3>
<aside>
✅ proton vpn 홈페이지에서 파일을 다운로드 받은 후 회원가입 진행필요
무료 계정의 경우 미국, 일본, 네덜란드 총 3개국만 이용 가능

</aside>

<ol>
<li>proton VPN 사이트 접속</li>
</ol>
<p><a href="https://account.protonvpn.com/signup">Proton VPN: Sign-up</a>
<img src="https://velog.velcdn.com/images/heejin-k/post/beae01ee-4ec3-42f0-af85-8bac45da9e8f/image.png" alt=""></p>
<p>오른쪽 하단의 Sign Up for Free 를 클릭합니다.</p>
<ol>
<li>회원가입</li>
</ol>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/9b4b871a-2860-4fe3-a171-ab11a45f4aaa/image.png" alt=""></p>
<p>무료버전으로 계속 하기를 클릭합니다. </p>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/304d5790-be33-4f61-85b2-1d5091b99406/image.png" alt=""></p>
<p>사용할 이메일을 입력합니다.</p>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/3a35a0f3-9ee9-4fc7-8936-e28ca1658779/image.png" alt=""></p>
<p>자동으로 비밀번호가 세팅됩니다. 만약 임의로 다른 비밀번호를 등록하고 싶다면 아래에 있는 <strong>Choose my own password</strong>를 눌러서 비밀번호를 설정해줍니다. </p>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/d684c45d-e238-4375-8397-feb061ee1e50/image.png" alt=""></p>
<p>회원가입이 완료되면 다운로드 팝업을 통해 다운로드 페이지로 이동합니다 아래 링크로도 이동 가능합니다. ( 다운로드 링크 : <a href="https://protonvpn.com/download">https://protonvpn.com/download</a> )</p>
<p>파일이 다운로드 되었으면 안내에 따라 프로그램을 설치하고 실행해줍니다.  </p>
<ol start="2">
<li>proton VPN 앱 실행</li>
</ol>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/8af57de4-d048-46f0-b829-d138130b01eb/image.png" alt=""></p>
<p>회원가입을 진행했던 이메일과 패스워드를 입력합니다. </p>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/030ddbc0-9095-40bc-90e0-43e959029a39/image.png" alt=""></p>
<p>proton VPN 의 경우 무료 계정은 Japan, Netherlands, US 세가지 국가만 지원합니다. </p>
<ol start="3">
<li>국가 선택 및 연결</li>
</ol>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/db92c824-a9d9-48fd-8a00-bc2e8b9f45f8/image.png" alt=""></p>
<p>국가 선택
세가지 국가중 원하는 국가를 선택하여 <strong>connect</strong> 버튼을 클릭합니다.</p>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/78ae46a5-dd76-48f2-bcc0-56975508ea99/image.png" alt=""></p>
<p>국가 연결 중 
이제 로딩이 끝나면 연결이 완료됩니다.</p>
<ol start="4">
<li>연결 완료 </li>
</ol>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/a53b2f39-99c9-43b4-a23d-1e6e80064470/image.png" alt=""></p>
<p>이제 연결이 완료되었습니다. 왼쪽 상단에서 해당하는 국가의 ip주소도 확인이 가능합니다. </p>
<p>테스트를 원하는 사이트 주소로 들어가서 정상적으로 작동하는지 확인해주세요!  ip가 제대로 변경되었는지 확인을 원하신다면 <a href="https://ipleak.net/">🔁 ip 변경 확인하는 사이트</a> 에서 확인해주세요 </p>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/6c556ae8-2f27-4947-bbac-573cc2d3a253/image.png" alt=""></p>
<p>ip 확인 사이트 내의 ip address</p>
<p>proton VPN에서 나온 ip 주소와 동일한 걸 확인할 수 있습니다.</p>
<ol start="5">
<li>테스트가 끝나고나면 <strong>Disconnect 버튼</strong>을 클릭하여 <strong>연결을 해제</strong>해주세요 !</li>
</ol>
<h3 id="🔁-ip-변경-확인하는-사이트">🔁 ip 변경 확인하는 사이트</h3>
<p><a href="https://ipleak.net/">https://ipleak.net/</a></p>
<p>위 사이트에 접속하여 ip주소가 해당 국가로 변경되었는지 확인 가능합니다. </p>
<p>update 2023.08.18</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[PDF 용량 최적화: 3.2MB → 220KB, 93% 줄이기!]]></title>
            <link>https://velog.io/@heejin-k/PDF-%EC%9A%A9%EB%9F%89-%EC%B5%9C%EC%A0%81%ED%99%94</link>
            <guid>https://velog.io/@heejin-k/PDF-%EC%9A%A9%EB%9F%89-%EC%B5%9C%EC%A0%81%ED%99%94</guid>
            <pubDate>Mon, 03 Feb 2025 07:41:13 GMT</pubDate>
            <description><![CDATA[<p>이번에 회사에서 진행한 프로젝트에서 PDF변환 작업이 있었는데 PDF를 다운 받을 경우 3~4MB에 해당하는 용량이 나오는 이슈가 있었다. 
<img src="https://velog.velcdn.com/images/heejin-k/post/c6b1489f-bc79-4fde-8f66-4e3c3c29f5fd/image.png" alt=""></p>
<p>해당 프로젝트를 진행할 당시 촉박한 데드라인으로 요구사항인 PDF 다운로드 기능만 구현한 채 배포하게 되었는데, PDF 파일의 용량이 커질수록 업로드/다운로드 속도가 느려지고, 저장 공간도 많이 차지하게 되어 최적화를 진행하게 되었다.</p>
<p>다음은 PDF 용량을 3.2MB에서 220KB까지 줄인 과정과 적용한 최적화 기법들이다.</p>
<blockquote>
<p>ps. 현재 프로젝트에서 사용중인 PDF라이브러리 : pdf.js </p>
</blockquote>
<hr>
<h3 id="1-기본적인-pdf-최적화-기법-적용">1. 기본적인 PDF 최적화 기법 적용</h3>
<p>📌 <strong>PDF 저장 시 객체 스트림 사용 최소화</strong>
PDF는 기본적으로 객체 스트림(Object Streams)을 사용해 저장되는데, 이를 비활성화하면 용량을 줄일 수 있다.</p>
<pre><code class="language-javascript">const pdfBytes = await pdfDoc.save({ useObjectStreams: false });</code></pre>
<p>📌 <strong>이미지 용량 최적화</strong>
PDF 내부에 포함된 이미지의 용량을 줄이는 것도 중요한 최적화 방법이다.</p>
<p>✅ <strong>JPG 품질 조정</strong>
기본적으로 quality: 0.8로 설정되어 있는데, 값을 낮추면 용량을 줄일 수 있다.</p>
<pre><code class="language-javascript">const imageJpg = await pdfDoc.embedJpg(imageBytes, { quality: 0.6 });</code></pre>
<p>✅ <strong>이미지 포맷 변경</strong>
이번 프로젝트에서는 이미지 포맷을 변경 할 경우 앱에서 그린 이미지가 서버에 저장되고, 이를 뿌려주고 있기 때문에 최후의 선택으로 두었다.
실제로 이미지가 차지하는 용량이 많이 크지는 않아서 줄인다고해도 미묘할 것으로 예상되었기 때문이다.
이미지 포맷의 경우 jpg는 카메라로 찍은 사진, png는 만들어진 이미지에 최적화되어있다. 현재 jpg를 사용하고 있는데 우리가 사용하고있는 사인 이미지처럼 만들어진 이미지일 때는 PNG로 저장하는게 용량이 줄어들기 때문에 PNG 혹은 다른 포맷을 사용하면 용량을 더 줄일 수 있다.</p>
<p>📌 <strong>기본 PDF 용량 압축</strong></p>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/b263e26b-aa1c-4af6-bfef-652fb0cb1fe1/image.png" alt=""></p>
<p>기본적으로 위에 글자를 입히기 전인 기본 pdf 파일의 용량을 압축해주었다. 
260kb의 pdf를 압축 프로그램을 통해 172kb까지 압축하였으며 화질이 저하되는 문제는 없었다.</p>
<hr>
<h3 id="2-폰트-최적화-경량화된-서브셋-폰트-적용">2. 폰트 최적화: 경량화된 서브셋 폰트 적용</h3>
<p>기존에는 PretendardVariable.ttf 전체를 PDF에 임베드했기 때문에 불필요한 글자까지 포함되면서 용량이 커지게 되었다.
이를 해결하기 위해 서브셋을 적용하여 필요한 글자만 포함시키는 방법을 사용했다.</p>
<p>처음에는 pdf.js에서 자체적으로 지원하는 subset 속성을 사용해서 적용하였다. </p>
<pre><code class="language-javascript">const customFont = await pdfDoc.embedFont(fontBytes, { subset: true });</code></pre>
<p>하지만 pdf에서 일부 글자가 표시가 되지 않는 등 폰트가 깨지는 문제가 발생했다. 
<img src="https://velog.velcdn.com/images/heejin-k/post/de18b61a-d8c0-4c77-af4c-3e8586607058/image.png" alt=""><img src="https://velog.velcdn.com/images/heejin-k/post/f989cb75-0fc4-4d78-a61f-bbb16971ebfd/image.png" alt=""></p>
<p>그러나 해당 파일의 용량을 봤을때 용량이 현저히 줄어있었다.
<img src="https://velog.velcdn.com/images/heejin-k/post/9debab4c-7889-49d6-9e13-f79858cfa348/image.png" alt=""></p>
<p>220kb까지 용량이 줄어든 것을 보아 현재 pdf에서 가장 큰 문제점이 바로 폰트임을 알 수 있었다. 실제로 프리텐다드 폰트의 용량을 보니 약3MB였고, pdf에서 폰트를 불러오면서 용량이 커진것을 확인할 수 있었다.</p>
<p>그렇다면 폰트 자체의 용량을 줄이면 해결되지 않을까 싶어서 프리텐다드의 서브셋 폰트를 찾아봤는데 현재 pdf에서 사용가능한 폰트가 없었다.</p>
<p>📌** subset 프로그램으로 폰트 경량화 후 적용 **
<img src="https://velog.velcdn.com/images/heejin-k/post/eccebb2c-4270-48aa-9bd3-f9ad0829cfb2/image.png" alt=""></p>
<p>현재 사이트에서는 프리텐다드를 사용중인데 해당글자의 경우 경량화된 폰트를 찾기가 어려워서 직접 경량화 작업을했다. 
경량화 작업을 하는방법은 일본 프로그램을 사용했는데 주로 많이 사용되고 있는 것 같았다.</p>
<blockquote>
<p>경량화 방법은 해당 블로그에서 자세히 다루고있다.
링크 : <a href="https://drybone-developer.tistory.com/72">https://drybone-developer.tistory.com/72</a></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/52cb9015-9063-4b1e-b11d-a3e14d825ee8/image.png" alt=""></p>
<p>경량화 작업을 통해 폰트 용량을 400kb대로 줄였으며 최종 pdf의 용량또한 455kb로 줄었다. </p>
<p>📌** 서브셋 속성 적용**</p>
<pre><code class="language-javascript">const customFont = await pdfDoc.embedFont(fontBytes, { subset: true });</code></pre>
<p>아까 적용해보았던 서브셋 속성이 특정 폰트에서만 적용이 안되기때문에 경량화 작업한 폰트에서는 적용이 되는지 확인해보았는데 다행히 폰트가 깨지는 것도 없었으며 용량도 효과적으로 줄어드는 것을 볼 수 있었다. </p>
<p>처음 원본 프리텐다드 폰트에서 속성이 제대로 적용 되지 않았던 것은 원본 폰트가 최적화되지 않았기 때문이었던 것으로 추측한다. </p>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/94cab651-ad87-48f3-8f95-5b14339b8de6/image.png" alt=""></p>
<p>경량화된 폰트를 적용한 후에는 pdf에 폰트 역시 정상적으로 출력되었으며, 용량 역시 대폭 감소하였다.</p>
<p>지금까지 비교한 피디에프의 경우 220kb까지 줄어든 것 을 볼 수 있었다.
글자수나 내용에 따라서 용량이 달라지긴하나 약 190kb ~ 250kb 사이정도의 용량으로 출력되는것을 확인했으며 텍스트를 최대 값으로 넣었을때도 255kb 정도로 확인되었다. </p>
<hr>
<h3 id="최적화-결과">최적화 결과</h3>
<p>📊 <strong>기존 대비 용량 변화</strong></p>
<p><strong>최적화 전</strong> : 3.2MB
<strong>최적화 후</strong> : 220KB
<strong>감소율</strong> : <strong><em>93.13%</em></strong> 감소</p>
<p>📌 <strong>텍스트 양에 따른 용량 변화:</strong></p>
<p>일반적인 PDF: 190~250KB
텍스트 최대 입력 시: 255KB</p>
<p>최종적으로 약 <em><strong>3.2mb -&gt; 220kb</strong>_로 용량을 최적화해서 _<strong>93.13%</strong></em> 의 용량을 줄이게 되었다. 
</br></p>
<p>지금까지 프론트엔드 개발을 하면서 데드라인이 촉박하다보니 최적화를 깊게 신경쓰지 못한 부분들이 많았는데 이번 기회를 통해 다시 한 번 최적화의 중요성을 깨닫게 되었다.
이렇게 큰 감소율이 나타나게 된 것이 뿌듯하기도 하면서 한편으로 내가 처음 개발할때 너무 안일하게 생각하고 구현에 급급했던 것 같아 반성하게 되기도 하였다.
앞으로 차근차근 개발 기간때문에 놓친 최적화 부분들을 찾아보며 개선해나가야지! 그리고 새로운 개발을할때도 최적화를 신경쓰며 유저가 사용하는데 불편함 없는 페이지를 개발하도록 노력해야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[개발 문화 형성기 - 커밋컨벤션, 코드컨벤션]]></title>
            <link>https://velog.io/@heejin-k/%EA%B0%9C%EB%B0%9C-%EB%AC%B8%ED%99%94-%ED%98%95%EC%84%B1%EA%B8%B0</link>
            <guid>https://velog.io/@heejin-k/%EA%B0%9C%EB%B0%9C-%EB%AC%B8%ED%99%94-%ED%98%95%EC%84%B1%EA%B8%B0</guid>
            <pubDate>Thu, 18 Jul 2024 06:42:30 GMT</pubDate>
            <description><![CDATA[<h3 id="💙-컨벤션-형성-계기">💙 컨벤션 형성 계기</h3>
<p>회사에 입사하고나서 내가 제일 먼저 놀랐던 것은 우리팀의 개발 문화의 부재였다.
커밋컨벤션 하나 없이 중구난방으로 작성된 커밋메세지가 제일 먼저 눈에 띄였고, 코드 또한 컨벤션이라 할 것이 없어보였다. 
그래서 회의 때나 커피타임 때 종종 커밋컨벤션이나 코드컨벤션이 중요할 것 같다고 언급은 했었으나, 실현되지는 않았고 답답한 나머지 팀 전체로 바꾸기가 어렵다면 프론트엔드 파트에서 먼저 시행해보자!!! 하고 커밋컨벤션과 코드컨벤션을 정하게 되었다. </p>
<p>원래 우리팀에서 프론트엔드 개발자는 나 혼자뿐이어서 혼자 나름 컨벤션을 정해서 쓰고있었는데, 다른팀의 프론트엔드 개발자분께서 우리팀에 합류하게 되면서 내가 기존에 쓰고 있던 컨벤션을 체계화해서 사용하게되었다.</p>
<h3 id="💙-팀-내에서도-공통된-컨벤션을-적용하자">💙 팀 내에서도 공통된 컨벤션을 적용하자!</h3>
<p>그렇게 약 1년간 프론트엔드에서만 컨벤션을 지키며 사용하고있었는데 여러 프로젝트들을 하면서 다른 직군들과도 통일할 필요성이 있다는것을 느끼게 되었다.</p>
<p>마침 팀장님과 이야기 나눌 일이 생겨서 진지하게 현재 프론트엔드 파트에서 이러한 컨벤션을 정해서 사용하고있고, 업무 효율이 상승했기에 팀내에도 적용하는게 좋을 것 같다고 건의했다. 직군이 다르니 코드컨벤션은 파트 내에서 알아서 하더라도 개발팀 내의 커밋 컨벤션은 통일했으면 좋겠다고 말씀드리게 되었다. </p>
<p>나는 사실 회의때 몇번 의견을 말했을때 다들 반응이 별로 없어서 원하지 않는다고 생각했었는데 팀장님께서 다른 팀원분들이 무엇을 해야할 지 확실하게 정의되지 않아서 그럴 것이라며 *<em>다음 개발팀 회의때 내가 직접 팀에서 사용했으면 하는 커밋컨벤션을 준비해와서 발표해봐라고 하셨다. *</em></p>
<p>그렇게 기존 프론트엔드파트에서 사용하고 있던 커밋컨벤션을 중심으로 통일된 컨벤션을 위해 깃에서 <strong>커밋템플릿을 작성하는 방법</strong>까지 발표하게 되었고 현재 팀내에서 모두 통일된 컨벤션을 간편하게 사용하고 있게 되었다. </p>
<p>다음은 프론트엔드 파트에서 실제로 사용하고 있는 컨벤션이고, 코드 컨벤션을 제외한 커밋컨벤션 부분은 개발팀 전체에서 통일하여 사용하고있다!!</p>
<p>입사 첫날부터 신경쓰였던 묵은 때를 벗겨낸 느낌이라 너무 홀가분하고 내 의견이 적극 반영되어 팀내에서 통일되게 사용하고 있는것이 너무 뿌듯했다. 팀의 협업환경에 작게나마 기여할 수 있어서 영광이었다.</p>
<h1 id="frontend-convention">Frontend <strong>Convention</strong></h1>
<ul>
<li>HTML / CSS / JS 모두 들여쓰기는 <strong>2칸</strong>입니다.</li>
<li><a href="https://validator.w3.org/">W3C validator</a>를 통과할 수 있도록 웹 표준에 맞게 작성</li>
<li>시맨틱 태그 사용 및 접근성을 고려하도록 노력하기</li>
</ul>
<hr>
<h3 id="css-속성-순서--참고-">CSS 속성 순서 ( 참고 )</h3>
<pre><code class="language-jsx">1. display
  1.1 justify-content
  1.2 align-items
  1.3 기타 flex, grid 관련 속성들...
2. overflow
3. float
4. position
  4.1 top / bottom / left / right
  4.2 z-index
  4.3 transform
5. width / height
6. padding / margin
7. border
8. background
9. color / font
10. animation
11. 기타</code></pre>
<h3 id="따옴표">따옴표</h3>
<ul>
<li><strong>js 작은 따옴표</strong> 우선 사용</li>
<li><strong>html 큰 따옴표</strong> 사용</li>
</ul>
<h2 id="js">JS</h2>
<hr>
<ul>
<li>ES6+ 문법 사용</li>
<li>var 금지</li>
<li>세미 콜론 필수</li>
<li><strong>작은 따옴표</strong> 사용</li>
</ul>
<h3 id="변수명">변수명</h3>
<ul>
<li>일반적인 변수, 함수: <strong>camelCase</strong></li>
</ul>
<pre><code class="language-jsx">let myVar = 1;

const myArr = [1, 2, 3];

function myFunction() {

}</code></pre>
<ul>
<li>상수: <strong>UPPER_SNAKE_CASE</strong></li>
</ul>
<pre><code class="language-jsx">const MAX_NUM = 10;</code></pre>
<ul>
<li>생성자 함수, 클래스: <strong>PascalCase</strong></li>
</ul>
<pre><code class="language-jsx">function MyFunction(title) {

}

class MyClass {

}</code></pre>
<h2 id="폴더-명">폴더 명</h2>
<hr>
<ul>
<li>직접적으로 바로 컴포넌트들이 들어있는 컴포넌트 폴더명은 <strong>대문자</strong>로 시작한다. <strong>PascalCase</strong></li>
<li>직접적으로 바로 컴포넌트들이 들어있지 않은 폴더명은 <strong>소문자</strong>로 작성한다.</li>
</ul>
<h2 id="파일-명">파일 명</h2>
<hr>
<ul>
<li><p>HTML: <strong>kebab-case</strong></p>
</li>
<li><p>CSS: <strong>kebab-case</strong></p>
</li>
<li><p>JS: <strong>camelCase</strong></p>
</li>
<li><p>JSX: <strong>PascalCase</strong></p>
</li>
<li><p>ASSET: <strong>snake_case</strong></p>
</li>
</ul>
<h2 id="커밋-컨벤션">커밋 컨벤션</h2>
<hr>
<aside>
💡  커밋은 논리적으로 구분이 되고, 일관성이 유지되는 단위로 최대한 작게 쪼갠다.

</aside>

<h3 id="1-commit-메세지-구조">1. Commit 메세지 구조</h3>
<ul>
<li>기본 적인 커밋 메시지 구조는 제목, 본문, 꼬리말 세가지 파트로 나누고, 각 파트는 빈줄을 두어 구분한다.</li>
</ul>
<pre><code class="language-powershell">type: subject

body

footer</code></pre>
<h3 id="2-commit-type-rule">2. Commit Type Rule</h3>
<ul>
<li>태그는 영어로 소문자로 작성합니다.</li>
<li>태그 뒤에는 “: “을 붙입니다. ( 콜론 뒤에만 한 칸 띄움 )</li>
</ul>
<table>
<thead>
<tr>
<th>Commit Type</th>
<th>Description</th>
</tr>
</thead>
<tbody><tr>
<td><strong>feat</strong></td>
<td>새로운 기능 추가</td>
</tr>
<tr>
<td><strong>fix</strong></td>
<td>버그 수정</td>
</tr>
<tr>
<td><strong>design</strong></td>
<td>CSS 등 사용자 UI 디자인 변경</td>
</tr>
<tr>
<td><strong>style</strong></td>
<td>코드 포맷팅, 세미 콜론 누락 등 코드 변경이 없는 경우</td>
</tr>
<tr>
<td><strong>refact</strong></td>
<td>코드 리팩토링</td>
</tr>
<tr>
<td><strong>del</strong></td>
<td>코드 삭제 수행</td>
</tr>
<tr>
<td><strong>add</strong></td>
<td>에셋 또는 기타 파일 추가</td>
</tr>
<tr>
<td><strong>rename</strong></td>
<td>파일 혹은 폴더명 수정하거나 옮기는 경우</td>
</tr>
<tr>
<td><strong>remove</strong></td>
<td>파일 삭제만 수행</td>
</tr>
<tr>
<td><strong>test</strong></td>
<td>테스트 추가 (프로덕션 코드 변경 없는 경우)</td>
</tr>
<tr>
<td><strong>chore</strong></td>
<td>빌드 또는 패키지 관리자 설정 업데이트</td>
</tr>
<tr>
<td><strong>init</strong></td>
<td>초기 설정</td>
</tr>
<tr>
<td><strong>set</strong></td>
<td>초기 설정 이후의 추가 설정</td>
</tr>
</tbody></table>
<h3 id="3-subject-rule"><strong>3. Subject Rule</strong></h3>
<ul>
<li><p>제목은 최대 50글자가 넘지 않도록 하고 마침표 및 특수기호는 사용하지 않는다.</p>
</li>
<li><p>영문사용 시, 동사원형 사용하고, 과거 시제를 사용하지 않는다.</p>
</li>
<li><p>제목은 개조식 구문으로 작성한다.</p>
<p>  -&gt; 완전한 서술형 문장이 아니라, 간결하고 요점적인 서술을 의미.</p>
</li>
</ul>
<h3 id="4-body-rule"><strong>4. Body Rule</strong></h3>
<ul>
<li>본문은 한 줄 당 72자 내로 작성한다.</li>
<li>본문 내용은 양에 구애받지 않고 최대한 상세히 작성한다.</li>
<li>본문 내용은 어떻게 변경했는지 보다 &#39;무엇을&#39;, &#39;왜&#39; 변경했는지를 설명한다.</li>
</ul>
<h3 id="5-footer-rule-선택---미정"><strong>5. Footer Rule (선택) - 미정</strong></h3>
<ul>
<li><p>꼬릿말은 다음의 규칙을 지킨다.</p>
<p>  1. 유형: #이슈 번호의 형식으로 작성</p>
<p>  2. 이슈 트래커 ID를 작성</p>
<p>  3. 여러개의 이슈 번호는 ,로 구분</p>
<p>  4. 이슈 트래커 유형은 아래와 같다</p>
<ul>
<li>꼬리말은 optional이고 이슈 트래커 ID를 작성한다.</li>
<li>꼬리말은 &quot;유형: #이슈 번호&quot; 형식으로 사용한다.여러 개의 이슈 번호를 적을 때는 쉼표(,)로 구분한다.</li>
<li>~~이슈 트래커 유형은 다음 중 하나를 사용한다.</li>
<li><em>-  Fixes:*</em> 이슈 수정중 (아직 해결되지 않은 경우)</li>
<li><em>-  Resolves:*</em> 이슈를 해결했을 때 사용</li>
<li><em>-  Ref:*</em> 참고할 이슈가 있을 때 사용</li>
<li><em>-  Related to:*</em> 해당 커밋에 관련된 이슈번호 (아직 해결되지 않은 경우)~~</li>
</ul>
</li>
</ul>
<aside>
💡 **커밋 예시**

<p>feat: add signin, signup
회원가입 기능, 로그인 기능 추가(예시를 위해 간단히 작성)
Resolves: #1</p>
</aside>

<ul>
<li><p><strong>commit template</strong></p>
<pre><code class="language-bash">  ###########################
  ### 제목(Subject) - (영문기준) 50자 이내
  # &lt;커밋 타입&gt;: &lt;제목&gt;    * 콜론 뒤만 한칸 띄움
  # 변경사항이 &quot;무엇&quot;인지 명확히 작성 / 끝에 마침표 금지
  # ex) feat: 로그인 기능 추가
  # 바로 밑줄에 작성

  # 바로 아래 공백은 지우지 마세요 (제목과 본문의 분리를 위함)

  ###########################
  ### 본문(Body) - 한 줄당 최대 72자, 초과시 줄바꿈 
  # (1) 무엇을 (2) 왜 (3) 어떻게 수정했는지
  # 바로 밑줄에 작성

  # 바로 아래 한 줄 공백 유지 (본문과 바닥글의 분리를 위함)

  ###########################
  ### 꼬릿말(Footer) - (선택)
  # ex) Close #7
  # 바로 밑줄에 작성

  ###########################
  ### [커밋 타입 목록]
  # feat    : 새로운 기능 추가
  # fix     : 버그 수정
  # design  : CSS 등 사용자 UI 디자인 변경
  # style   : 코드 의미에 영향을 주지 않는 변경사항 ( 코드 포맷변경, 세미콜론 누락 등 )
  # del     : 코드 삭제
  # add     : 에셋 또는 기타 파일 추가
  # docs    : 문서 수정
  # refact  : 코드 리팩토링
  # test    : 테스트 코드 추가 (프로덕션 코드 변경 X)
  # chore   : 빌드 부분 혹은 패키지 매니저 수정사항 (프로덕션 코드 변경 X)
  # rename  : 파일 혹은 폴더명을 수정하거나 옮기는 작업
  # remove  : 파일 삭제만 수행
  # init    : 초기 설정
  # set     : 초기 설정 이후의 추가 설정
  ###########################</code></pre>
</li>
</ul>
<p>✅ git commit ⇒ 템플릿 접속 ⇒ 빈칸에 제목, 본문(선택), 꼬릿말(선택) 작성</p>
<ul>
<li>제목에서 이미 내용을 다 설명하고있어 본문에 쓸 내용이 없는 경우 제외하고는 본문 작성 권장 ! </li>
</ul>
<h3 id="템플릿-등록">템플릿 등록</h3>
<p>내 로컬 즉 commit 할 때마다 사용해주고 싶다면 &quot;~/&quot;를 앞에 붙여서 파일을 생성</p>
<pre><code class="language-bash">touch ~/.gitmessage.txt</code></pre>
<p>이번 프로젝트에서만 사용하고 싶다면 ~/을 빼주고 파일을 생성</p>
<pre><code class="language-bash">touch .gitmessage.txt</code></pre>
<pre><code class="language-bash">vim ~/.gitmessage.txt  
or
vim .gitmessage.txt</code></pre>
<p>template 저장</p>
<h3 id="git-commit-template--설정">git commit template  설정</h3>
<pre><code class="language-bash">git config --global commit.template ~/.gitmessage.txt
or
git config --global commit.template .gitmessage.txt 
git commit </code></pre>
<h2 id="폴더-구조">폴더 구조</h2>
<hr>
<p>*아토믹 패턴</p>
<ul>
<li>atom</li>
<li>module</li>
<li>template</li>
<li>page</li>
</ul>
<blockquote>
<p>참고 :
 <a href="https://chlolisher.tistory.com/173">https://chlolisher.tistory.com/173</a>
<a href="https://overcome-the-limits.tistory.com/entry/%ED%98%91%EC%97%85-%ED%98%91%EC%97%85%EC%9D%84-%EC%9C%84%ED%95%9C-%EA%B8%B0%EB%B3%B8%EC%A0%81%EC%9D%B8-git-%EC%BB%A4%EB%B0%8B%EC%BB%A8%EB%B2%A4%EC%85%98-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0">https://overcome-the-limits.tistory.com/entry/협업-협업을-위한-기본적인-git-커밋컨벤션-설정하기</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[디자이너와 Asset Name 컨벤션 정하기]]></title>
            <link>https://velog.io/@heejin-k/Asset-Name-%EC%BB%A8%EB%B2%A4%EC%85%98</link>
            <guid>https://velog.io/@heejin-k/Asset-Name-%EC%BB%A8%EB%B2%A4%EC%85%98</guid>
            <pubDate>Thu, 17 Aug 2023 06:10:57 GMT</pubDate>
            <description><![CDATA[<p>회사 내에서 에섯 네임 컨벤션이 마땅히 정해져 있지않아서 다른 프론트엔드 개발자와 협업을할때도, 피그마로 디자인을 전달받아서 작업을 할때도 불편함을 느꼈다. </p>
<p>디자인에서 이미지를 img1 img2 이런식으로 주거나 이상한 숫자들로만 줘서 내가 임의로 이미지 네임을 변경해서 사용하곤 했는데 다른 프론트엔드 개발자와 같이 하나의 프로젝트를 작업하게 되면서 불편함이 커지게 되었고, 같은 아이콘을 다른이름으로 저장하는 경우가 빈번해져 디자이너분께 Asset Name Convention을 정하자고 제안하게 되었다.</p>
<p>흔쾌히 우리팀의 디자이너분께서 필요하다고 생각한다며 좋은 아이디어라고 수락해주셨고 <strong>디자이너 1명, 프론트엔드 개발자 2명, ios개발자 1명</strong> 이 회의에 참여하여 함께 Asset Name Convention을 정하게 되었다. ios개발자는, 앱개발에서 또한 비슷하게 사용되는 이미지나 아이콘이 많기때문에 같이 하면 좋을 것 같아 역시 제안드렸다.</p>
<p>먼저 해당 컨벤션을 제안한 내가 기본적으로 사용되는 것들을 찾아보면서 자료를 준비해갔고, 더 필요하거나 불필요한부분을 덜어내는 형식으로 회의가 진행되었다. </p>
<h2 id="asset-name-convention">Asset Name Convention</h2>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/25d4bc35-5e4d-4995-a2ea-b03cf29c430e/image.png" alt=""></p>
<ul>
<li>네이밍은 <strong>영문(소문자)</strong> 와 <strong>숫자</strong>, <strong>언더스코어(_)</strong>만을 사용합니다.</li>
<li>네이밍은 prefix, name, suffix로 구성됩니다.</li>
<li>prefix, name, suffix는 <strong>언더스코어(_)</strong>로 구분합니다.</li>
<li>첫글자에는 숫자를 사용하지 않습니다. ex) 1-icon (x)</li>
<li>띄어쓰기 없이 작성합니다.</li>
<li>웹에서의 포맷은 svg를 기본 권장합니다.</li>
</ul>
<h3 id="prefix"><strong>Prefix</strong></h3>
<p>prefix에서는 asset type을 정의합니다.</p>
<ul>
<li>asset type에는 다음과 같은 항목을 쓰며 약어로 사용할 경우 모음을 탈락 시켜 사용합니다.<ul>
<li>icon → icn</li>
<li>image → img</li>
<li>button → btn</li>
</ul>
</li>
</ul>
<h3 id="name"><strong>Name</strong></h3>
<ul>
<li>name은 에셋의 쓰임과 의미를 구분할 수 있는 고유한 단어를 사용합니다</li>
<li>사용되는 기능이 아닌 형태 위주로 네이밍합니다. (사용되는 용도는 다를 수 있어도 동일한 파일을 사용할 가능성이 크기 때문)</li>
<li>name은 한 단어로 떨어지는 단일어를 사용합니다.</li>
<li>두 단어 이상이 결합된 복합어를 사용할 경우 띄어쓰기나 언더스코어(_) 없이 붙여서 사용합니다.</li>
<li>Asset name 정의 시 fontawesome을 참고합니다.</li>
</ul>
<h3 id="suffix"><strong>Suffix</strong></h3>
<ul>
<li>suffix에서는 direction, shape, status, color, size 등의 요소를 정의합니다.</li>
<li>에셋 구분에 필요한 요소를 선택하여 사용합니다.</li>
<li>2개 이상의 suffix가 사용되는 경우 direction, shape,action, status, size의 순서로 작성합니다.</li>
<li>suffix에서는 단어를 축약하지 않습니다.</li>
<li>size는 가로를 기준으로 작성하고 세로는 표기하지 않습니다.</li>
<li>해상도별로 구분해야 하는 경우 1x, 2x를 사용합니다.</li>
</ul>
<p>suffix의 경우 너무 많이 쓰게되면 복잡하게 될 수도 있다는 의견이있어 구분할때 꼭 필요한 경우만 사용하기로 하였다.</p>
<p>추가적으로 피그마상의 페이지 네임 또한 프레임 번호로 지정되어있어 알아보기 불편함이 있었기에 해당 회의에서 페이지 네임 규칙 또한 정하게 되었다.</p>
<h2 id="page-name-convention">Page Name Convention</h2>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/fa463dee-b134-47a2-bdf2-71bce1c22dde/image.png" alt=""></p>
<p>기존의 프레임 번호만 있는 방식에서 페이지 번호와 페이지이름, 그리고 breakpoint를 함께 기재하면서 반응형의 경우 범위를 쉽게 알아 볼 수 있도록 변경하였다. 
breakpoint의 경우 이상, 이하의 경계가 모호하게 360, 1920 이런식으로 알려주시는 경우가 많아 매번 물어보는 경우가 생겨서 범위를 제안하게 되었다. 모든 프로젝트에서 공통된 방식의 break point를 지정하는것 또한 하나의 방법일 것 같다. </p>
<p>아무튼 이로서 피그마를 조금 더 알아보기 쉽게되면서 개발효율성 및 소통 부분이 많이 개선 될 것 같다!</p>
<p>끝으로 신입 개발자인 내 의견을 귀기울여주고 제안을 수락해주신 우리 팀원분들께 감사의 인사를 전하며 앞으로 우리가 정한 Asset Name Convention 및 Page Name Convention을 통해 더욱 원활한 협업이 되었으면 좋겠다. 부족한 부분은 개선하고 불필요한 부분은 덜어내면서 점차 우리만의 컨벤션이 완성되었으면 좋겠다!</p>
<blockquote>
<p>참고 : <a href="https://spoqa.github.io/2020/03/27/asset-naming.html">https://spoqa.github.io/2020/03/27/asset-naming.html</a>
참고 : <a href="https://inseq.co.kr/ko/bbs/i-24/show.do?seq=9">https://inseq.co.kr/ko/bbs/i-24/show.do?seq=9</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Svelte ] 스벨트에서 제공하는 기본 애니메이션]]></title>
            <link>https://velog.io/@heejin-k/Svelte-Style-effects</link>
            <guid>https://velog.io/@heejin-k/Svelte-Style-effects</guid>
            <pubDate>Mon, 20 Feb 2023 07:16:43 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/heejin-k/post/c6cd0f12-2299-4a93-be7f-e2bd147c5c82/image.png" alt=""></p>
<h1 id="style-effects-in-svelte">Style effects in Svelte</h1>
<h2 id="1-motion">1. Motion</h2>
<p>스벨트에서는 변경 사항을 매끄럽게 보여주는 UI를 구축하기 편리한 애니메이션 도구들을 제공합니다. CSS로도 이러한 효과들을 만들 수 있지만, Svelte의 Motion은 Writable Store를 사용하여 효과를 쉽게 적용할 수 있게 도와줍니다.</p>
<h3 id="11-tweened">1.1. Tweened</h3>
<p>트위닝 효과는 DOM에서 상태가 변경될 때, 변경되는 요소를 부드럽게 변경하는 것처럼 보여주는 효과입니다. progress 바의 진행 막대의 상태를 변경할 때 주로 사용합니다. </p>
<pre><code class="language-jsx">import { tweened } from &#39;svelte/motion&#39;;

const progress = tweened(value: any, { options })</code></pre>
<aside>
💡 **options 종류**

<p><strong>delay</strong> : Tweened 효과가 시작하기 전 시간</p>
<p><strong>duration</strong> : Tweened 효과가 지속되는 시간</p>
<p><strong>ease</strong> : 시간 경과에 따른 효과를 지정</p>
<p><strong>interpolate</strong> : Tweened 효과의 보간법 설정</p>
<p>※ 참고 : 두 번째 파라미터인 options를 설정하면 Tweened 기본 값이 모두 options의 내용으로 덮어 씌워집니다. Tweened 효과가 끝난 후에는 <strong>tweened.set</strong>과 <strong>tweened.update</strong>가 Promise값을 리턴합니다.</p>
</aside>

<p>progress 상태 바 예제 )</p>
<pre><code class="language-jsx">    import { tweened } from &#39;svelte/motion&#39;;
    import { cubicOut } from &#39;svelte/easing&#39;;

    const progress = tweened(0, {
        duration: 400,
        easing: cubicOut
    });
&lt;/script&gt;

&lt;progress value={$progress}&gt;&lt;/progress&gt;

&lt;button on:click=&quot;{() =&gt; progress.set(0)}&quot;&gt;
    0%
&lt;/button&gt;

&lt;button on:click=&quot;{() =&gt; progress.set(0.25)}&quot;&gt;
    25%
&lt;/button&gt;

&lt;button on:click=&quot;{() =&gt; progress.set(0.5)}&quot;&gt;
    50%
&lt;/button&gt;

&lt;button on:click=&quot;{() =&gt; progress.set(0.75)}&quot;&gt;
    75%
&lt;/button&gt;

&lt;button on:click=&quot;{() =&gt; progress.set(1)}&quot;&gt;
    100%
&lt;/button&gt;

&lt;style&gt;
    progress {
        display: block;
        width: 100%;
    }
&lt;/style&gt;</code></pre>
<h3 id="12-spring">1.2. spring</h3>
<p>spring은 자주 변경되는 값에 사용하는 것이 좋습니다. </p>
<pre><code class="language-jsx">store = spring(value: any, options)</code></pre>
<p><code>spring</code>함수는 2개의 파라미터를 가집니다. 첫 번째 파라미터는 변경되는 값이고 두 번째 파라미터는 옵션입니다.</p>
<p>💡 options 종류</p>
<p><strong>stiffness</strong></p>
<p>: 스프링의 강도 설정, 값이 높을수록 반응이 빨라진다. (0 ~ 1)</p>
<p><strong>damping</strong></p>
<p>: 스프링의 탄성 정도 설정, 값이 낮을수록 튕기는 범위가 넓어진다. (0 ~ 1)</p>
<p><strong>precision</strong></p>
<p>: 스프링의 안정화, 값이 높으면 튕기는 횟수가 줄어들고, 값이 낮을수록 튕기는 횟수가 증가 (0 ~ 1 )</p>
<p>※ 참고 : Tweened와 동일하게 Promise가 리턴됩니다.</p>
<pre><code class="language-jsx">    import { spring } from &#39;svelte/motion&#39;;

    let coords = spring({ x: 50, y: 50 });
    let size = spring(10);
&lt;/script&gt;</code></pre>
<p>다음과 같이 spring 옵션 값을 추가해 세부적인 값들을 지정해주어 작성할 수 있습니다.</p>
<pre><code class="language-jsx">let coords = spring({ x: 50, y: 50 }, {
    stiffness: 0.1,
    damping: 0.25
});</code></pre>
<ul>
<li><p>spring 전체 예제</p>
<pre><code class="language-jsx">
      import { spring } from &#39;svelte/motion&#39;;

      let coords = spring({ x: 50, y: 50 }, {
          stiffness: 0.1,
          damping: 0.25
      });

      let size = spring(10);
  &lt;/script&gt;

  &lt;div style=&quot;position: absolute; right: 1em;&quot;&gt;
      &lt;label&gt;
          &lt;h3&gt;stiffness ({coords.stiffness})&lt;/h3&gt;
          &lt;input bind:value={coords.stiffness} type=&quot;range&quot; min=&quot;0&quot; max=&quot;1&quot; step=&quot;0.01&quot;&gt;
      &lt;/label&gt;

      &lt;label&gt;
          &lt;h3&gt;damping ({coords.damping})&lt;/h3&gt;
          &lt;input bind:value={coords.damping} type=&quot;range&quot; min=&quot;0&quot; max=&quot;1&quot; step=&quot;0.01&quot;&gt;
      &lt;/label&gt;
  &lt;/div&gt;

  &lt;svg
      on:mousemove=&quot;{e =&gt; coords.set({ x: e.clientX, y: e.clientY })}&quot;
      on:mousedown=&quot;{() =&gt; size.set(30)}&quot;
      on:mouseup=&quot;{() =&gt; size.set(10)}&quot;
  &gt;
      &lt;circle cx={$coords.x} cy={$coords.y} r={$size}/&gt;
  &lt;/svg&gt;

  &lt;style&gt;
      svg {
          width: 100%;
          height: 100%;
          margin: -8px;
      }
      circle {
          fill: #ff3e00;
      }
  &lt;/style&gt;</code></pre>
</li>
</ul>
<h2 id="2-transitions">2. Transitions</h2>
<p>transition을 사용해 요소를 DOM의 안과 밖으로 부드럽게 전환할 수 있습니다. </p>
<h3 id="21-fly--fade-transitions">2.1. fly &amp; fade transitions</h3>
<ul>
<li>fade</li>
</ul>
<pre><code class="language-jsx">import { fade } from &#39;svelte/transition&#39;;
let visible = true;

&lt;p transition:fade&gt;Fades in and out&lt;/p&gt;&quot;</code></pre>
<p>요소가 화면에서 서서히 사라지는 효과를 볼 수 있습니다.</p>
<ul>
<li>fly</li>
</ul>
<pre><code class="language-jsx">import { fly } from &#39;svelte/transition&#39;;
let visible = true;

&lt;p transition:fly=&quot;{{ y: 200, duration: 2000 }}&quot;&gt;
    Flies in and out
&lt;/p&gt;</code></pre>
<p>요소가 화면 밖으로 날아가는 듯한 효과를 줄 수 있습니다.</p>
<h3 id="22-in--out">2.2. in &amp; out</h3>
<p>transition 요소는 in 또는 out과 함께 사용할 수 있습니다.</p>
<pre><code class="language-jsx">import { fade, fly } from &#39;svelte/transition&#39;;

&lt;p in:fly=&quot;{{ y: 200, duration: 2000 }}&quot; out:fade&gt;
    Flies in, fades out
&lt;/p&gt;</code></pre>
<h3 id="23-local-transition">2.3. local transition</h3>
<p>트랜지션을 전역적으로 설정할 수 있습니다.</p>
<pre><code class="language-jsx">&lt;div transition:slide|local&gt;
    {item}
&lt;/div&gt;</code></pre>
<h3 id="24-deferred-transitions">2.4. Deferred transitions</h3>
<p>지연된 전환</p>
<p>여러 요소들 사이에서 조정하여 전환을 사용할 수 있습니다.</p>
<pre><code class="language-jsx">&lt;label
    in:receive=&quot;{{key: todo.id}}&quot;
    out:send=&quot;{{key: todo.id}}&quot;
&gt;</code></pre>
<pre><code class="language-jsx">
&lt;label
    class=&quot;done&quot;
    in:receive=&quot;{{key: todo.id}}&quot;
    out:send=&quot;{{key: todo.id}}&quot;
&gt;</code></pre>
<h3 id="25-key-blocks">2.5. Key blocks</h3>
<p>표현식의 값이 변경될 때 내용을 다시 만듭니다.</p>
<p>요소가 DOM에 들어가거나 나올 때 뿐만 아니라 값이 변경될 때 마다 전환을 재생하는 경우 유용하게 사용할 수 있습니다.</p>
<pre><code class="language-jsx">{#key value}
    &lt;div transition:fade&gt;{value}&lt;/div&gt;
{/key}</code></pre>
<p>전체 예제)</p>
<pre><code class="language-jsx">&lt;script&gt;
    import { fly } from &#39;svelte/transition&#39;;

    let number = 0;
&lt;/script&gt;

&lt;div&gt;
    The number is:
    {#key number}
        &lt;span style=&quot;display: inline-block&quot; in:fly={{ y: -20 }}&gt;
            {number}
        &lt;/span&gt;
    {/key}
&lt;/div&gt;
&lt;br /&gt;
&lt;button
    on:click={() =&gt; {
        number += 1;
    }}&gt;
    Increment
&lt;/button&gt;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Svelte ] 스벨트에서의 전역상태관리 Store]]></title>
            <link>https://velog.io/@heejin-k/Svelte-%EC%8A%A4%EB%B2%A8%ED%8A%B8%EC%97%90%EC%84%9C%EC%9D%98-%EC%A0%84%EC%97%AD%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC-Store</link>
            <guid>https://velog.io/@heejin-k/Svelte-%EC%8A%A4%EB%B2%A8%ED%8A%B8%EC%97%90%EC%84%9C%EC%9D%98-%EC%A0%84%EC%97%AD%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC-Store</guid>
            <pubDate>Tue, 14 Feb 2023 09:51:42 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/heejin-k/post/51aac3d1-ea88-4a51-bece-c1306b802ad8/image.png" alt=""></p>
<h1 id="stores">Stores</h1>
<p>svelte에는 전역 상태를 저장하는 store가 계층구조에 포함되어있어 유용하게 사용할 수 있습니다. 그러나 모든 어플리케이션이 상태가 어플리케이션 계층 구조에 속하는 것은 아닙니다. 상태 관리를 위해서 자바스크립트 모듈 등을 사용해야 하는 경우도 많습니다. ( redux, recoil … )</p>
<p>스벨트에서는 상태관리를 <code>stores</code>를 통해 관리합니다. store는 값이 변경될 때 마다 <code>subscribe</code> 메소드를 사용하는 간단한 객체입니다.</p>
<h2 id="1-writable-stores">1. Writable stores</h2>
<pre><code class="language-jsx">import { writable } from &#39;svelte/store&#39;;

export const count = writable(0);</code></pre>
<pre><code class="language-dart">    import { count } from &#39;./stores.js&#39;;

    function reset() {
        count.set(0);
    }
&lt;/script&gt;

&lt;button on:click={reset}&gt;
    reset
&lt;/button&gt;</code></pre>
<pre><code class="language-jsx">    import { count } from &#39;./stores.js&#39;;

    function increment() {
        count.update(n =&gt; n + 1);
    }
&lt;/script&gt;

&lt;button on:click={increment}&gt;
    +
&lt;/button&gt;</code></pre>
<pre><code class="language-jsx">    import { count } from &#39;./stores.js&#39;;

    function decrement() {
        count.update(n =&gt; n - 1);
    }
&lt;/script&gt;

&lt;button on:click={decrement}&gt;
    -
&lt;/button&gt;</code></pre>
<pre><code class="language-jsx">    import { count } from &#39;./stores.js&#39;;
    import Incrementer from &#39;./Incrementer.svelte&#39;;
    import Decrementer from &#39;./Decrementer.svelte&#39;;
    import Resetter from &#39;./Resetter.svelte&#39;;

    let countValue;

    count.subscribe(value =&gt; {
        countValue = value;
    });
&lt;/script&gt;

&lt;h1&gt;The count is {countValue}&lt;/h1&gt;

&lt;Incrementer/&gt;
&lt;Decrementer/&gt;
&lt;Resetter/&gt;</code></pre>
<h2 id="2-auto-subscriptions">2. Auto-subscriptions</h2>
<p>이전의 예제는 스토어가 구독되지만 구독 취소는 되지 않았습니다. </p>
<pre><code class="language-jsx">const unsubscribe = count.subscribe(value =&gt; {
    countValue = value;
});</code></pre>
<p><code>subscribe</code> 메소드를 호출하면 <code>unsubscribe</code> 함수가 반환됩니다.</p>
<p>이제 <code>unsubscribe</code>를 선언할 수 있으며 <code>onDestroy</code> lifecycle hook을 통해 호출할 수 있습니다. </p>
<pre><code class="language-jsx">  import { onDestroy } from &#39;svelte&#39;;
    import { count } from &#39;./stores.js&#39;;
    import Incrementer from &#39;./Incrementer.svelte&#39;;
    import Decrementer from &#39;./Decrementer.svelte&#39;;
    import Resetter from &#39;./Resetter.svelte&#39;;

    let countValue;

    const unsubscribe = count.subscribe(value =&gt; {
        countValue = value;
    });

    onDestroy(unsubscribe);
&lt;/script&gt;&lt;h1&gt;The count is {countValue}&lt;/h1&gt;</code></pre>
<p>svelte에서는 컴포넌트가 여러 스토어를 구독하는 경우 등을 위해서 <strong>자동 구독</strong>을 지원합니다. 구독하는 변수 앞에 <code>$</code> 를 붙여 사용할 수 있습니다. </p>
<pre><code class="language-jsx">    import { count } from &#39;./stores.js&#39;;
    import Incrementer from &#39;./Incrementer.svelte&#39;;
    import Decrementer from &#39;./Decrementer.svelte&#39;;
    import Resetter from &#39;./Resetter.svelte&#39;;
&lt;/script&gt;
    &lt;h1&gt;The count is {$count}&lt;/h1&gt;</code></pre>
<p><strong>자동 구독</strong>은 컴포넌트의 최상위 스코프에서 선언된 store 변수에서만 작동합니다. </p>
<p><code>$count</code> 는 위 예제와 같이 마크업 내에서만 사용 가능한 것이 아닌, 이벤트 핸들러나 반응성 선언과 같이 어디에서나 사용 가능합니다. </p>
<p><code>$</code> 가 앞에 붙어있는 변수는 모두 store 값을 참조하는 값입니다. 따라서 Svelte에서는 <code>$</code>로 시작하는 변수명을 짓는 것은 피해야 합니다. </p>
<h2 id="3-readable-stores">3. Readable Stores</h2>
<p>만약 마우스 위치나 사용자의 지리적 위치 등을 나타내는 store 가 있다면 외부에서 해당 스토어의 값을 설정하지 못합니다. 이러한 경우을 위하여 <strong>읽기만 가능</strong>한 store를 지정할 수 있습니다. </p>
<pre><code class="language-jsx">import { readable } from &#39;svelte/store&#39;;

export const time = readable(new Date(), function start(set) {
    const interval = setInterval(() =&gt; {
        set(new Date());
    }, 1000);

    return function stop() {
        clearInterval(interval);
    };
});</code></pre>
<p><code>readable</code>에 대한 첫번째 인자는 초기 값을 지정해줍니다. 아직 값이 없는 경우라면 null값이나 undefined 값이 될 수도 있습니다. 두번째로는 set을 콜백으로 받고 stop 함수를 리턴하는 start 함수를 입니다. </p>
<p>store에서 첫번째 구독자를 얻을 때 start 함수가 호출되며 마지막 구독자가 구독을 해지하게 된다면 stop 함수가 호출하게 되는 구조입니다. </p>
<h2 id="4-derived-stores">4. Derived stores</h2>
<p><code>Derived</code> 를 통해서 다른 store에 기초되는 값을 가지는 store를 만들 수 있습니다. 이전 예제를 기반으로 page가 열린 시간을 파생하는 store를 만들 수 있습니다.</p>
<pre><code class="language-jsx">export const elapsed = derived(
    time, $time =&gt; Math.round(($time - start) / 1000)
);</code></pre>
<p>여러 입력에서 저장소를 파생하고, 값을 반환하는 대신 명시적으로 값을 ‘set’ 할 수 있습니다 ( 비동기적으로 값을 파생하는데 유용)</p>
<p><code>Derived</code>는 기존에 생성되어있는 쓰기 가능한 store, 읽기 가능한 store의 데이터를 기반으로 동작하게 됩니다. </p>
<pre><code class="language-jsx">import { writable, derived } from &#39;svelte/store&#39;

export let count = writable(1)
export let double = derived(count,$count =&gt; $count * 2 )
//구독의 의미가 아니라 count 함수의 데이터라는 뜻으로 $를 추가한다.
//svelte 파일이 아닌 js 파일 내 이므로 자동구독으로 실행되지 않음
export let total = derived([count,double], ([$count, $double], set) =&gt; {
    set($count+$double)
})
// export let total = derived([count,double], ([$count, $double])=&gt; { return $count+$double } 과 동일하다
export let initialValue = derived(count, ($countm set) =&gt; {
    setTimeout(() =&gt; {
    set($count + 1)
},1000)
},&#39;최초 계산중 ...&#39;)
</code></pre>
<pre><code class="language-jsx">import { count, double, total } from &#39;./store.js&#39;

console.log(double)
&lt;/script&gt;

&lt;button on:click={()=&gt; $count += 1 }&gt; click! &lt;/button&gt;
&lt;h1&gt;total: {$total}&lt;/h1&gt;
&lt;h2&gt;count: {$count}&lt;/h2&gt;
&lt;h2&gt;double: {$double}&lt;/h2&gt;
&lt;h2&gt;count + 1 : {$initialValue}&lt;/h2&gt;</code></pre>
<p>derived는 계산된 store입니다. 따라서 함수의 첫번째 인자로 계산할 대상의 store를 매개변수로 입력하고 두번째는 실행 될 코드를 입력합니다. 두가지 값을 입력할 때는 배열을 사용해서 입력할 수 있습니다. 연결되어있는 스토어의 값이 바뀌면 매번 실행되며 초기화 되었다가 재 구독 하는 형식으로 다른 store들과 차이점이 있습니다. </p>
<h2 id="5-store-값-얻기-get">5. Store 값 얻기 (get)</h2>
<p>store에 구독 하지 않고 단순히 해당하는 값만 읽어오고 싶은 경우 사용합니다.</p>
<pre><code class="language-jsx">import { get } from &#39;svelte/store&#39;
import { count, double, user } from &#39;./store.js&#39;

console.log(get(count))
console.log(get(double))
console.log(get(user))</code></pre>
<pre><code class="language-jsx">import { writable, readable, derived, get } from &#39;svelte/store&#39;

export let count = writable(1)
export let double = derived(count, $count =&gt; $count * 2)
export let user = readable({
    name: &#39;Heropy&#39;
    age: 85,
    email:&#39;thesecon@gmail.com&#39;
})

console.log(get(count))
console.log(get(double))
console.log(get(user))</code></pre>
<h2 id="6-custom-stores">6. Custom stores</h2>
<p>custom store는 기존의 store에서 추가로 원하는 메소드를 추가하여 커스텀해서 사용 가능한 store입니다. writable, readable 등 </p>
<pre><code class="language-jsx">&lt;script&gt;
    import { count } from &#39;./stores.js&#39;;
&lt;/script&gt;

&lt;h1&gt;The count is {$count}&lt;/h1&gt;

&lt;button on:click={count.increment}&gt;+&lt;/button&gt;
&lt;button on:click={count.decrement}&gt;-&lt;/button&gt;
&lt;button on:click={count.reset}&gt;reset&lt;/button&gt;</code></pre>
<pre><code class="language-jsx">import { writable } from &#39;svelte/store&#39;;

function createCount() {
    const { subscribe, set, update } = writable(0);

    return {
        subscribe, //subscribe를 가지고있으면 App.svelte에서 &#39;$&#39;자동구독을 이용해서 사용 가능
        increment: () =&gt; update(n =&gt; n + 1),
        decrement: () =&gt; update(n =&gt; n - 1),
        reset: () =&gt; set(0)
    };
}

export const count = createCount();</code></pre>
<p>예제와 같이 기본 <strong>writable store</strong>에서 increment, decrement, reset 메소드가 추가로 포함되게 구성할 수 있습니다.  return 값에서 set 과 update는 입력하지 않아도 됩니다. </p>
<pre><code class="language-jsx">import { writable } from &#39;svelte/store&#39;

const { set, update, subscribe } = writable(0)

export let count = {
set,
update,
subscribe,
increment: () =&gt; update(n =&gt; n + 1),
decrement: () =&gt; update(n =&gt; n - 1),
reset: () =&gt; set(0)
}</code></pre>
<p>위와 같이 작성할 수도 있습니다.</p>
<p><strong>예제 2)</strong></p>
<pre><code class="language-jsx">import { fruits } from &#39;./fruits.js&#39;

let value

&lt;input bind:value/&gt;
&lt;button on:click={() =&gt; fruits.setItem(value)}&gt;
    Add fruit!
&lt;/button&gt;
&lt;button on:click={() =&gt; console.log(fruits.getList())}&gt;
    Log fruit list!
&lt;/button&gt;

&lt;ul&gt;
    {#each $fruits as {id, name} (id)}
        &lt;li&gt;{name}&lt;/li&gt;
    {/each}
&lt;/ul&gt;</code></pre>
<pre><code class="language-jsx">import { writable , get } from &#39;svelte/store&#39;

const _fruits = writable([
{ id: 1, name: &#39;Apple&#39; },
{ id: 2, name: &#39;Banana&#39; },
{ id: 3, name: &#39;Cherry&#39; }
])

export let fruits = {
..._fruits, // writable이기 때문에 set, update, subscribe가 포함되어있다
getList: () =&gt; get(_fruits).map((f) =&gt; f.name),
setItem: () =&gt; { 
    _fruits.update((f) =&gt; {
        f.push({
            id: f.length + 1,
            name
        })
        return f
    })
    }
}</code></pre>
<h2 id="7-store-bindings">7. Store bindings</h2>
<p><code>set</code> 메소드를 가지고 있는 writable store라면 컴포넌트에 bind하는 것과 동일하게 값을 bind 할 수 있습니다. </p>
<p>예를 들어 쓰기 가능한 저장소에 name을 가지고 있고 파생된 저장소에는  greeting을 가지고 있을 때 input 요소에 해당 값을 업데이트 할 수 있습니다. </p>
<pre><code class="language-jsx">&lt;input bind:value={$name}&gt;</code></pre>
<p>컴포넌트 내부에 값을 저장하도록 직접 할당할 수도 있습니다. </p>
<pre><code class="language-jsx">&lt;button on:click=&quot;{() =&gt; $name += &#39;!&#39;}&quot;&gt;
    Add exclamation mark!
&lt;/button&gt;</code></pre>
<p><code>$name += &#39;!&#39;</code> 와 <code>name.set($name + &#39;!&#39;)</code> 는 동일합니다.</p>
<ul>
<li><p><strong>전체 예제</strong></p>
<pre><code class="language-jsx">  &lt;script&gt;
      import { name, greeting } from &#39;./stores.js&#39;;
  &lt;/script&gt;

  &lt;h1&gt;{$greeting}&lt;/h1&gt;
  &lt;input bind:value={$name}&gt;

  &lt;button on:click=&quot;{() =&gt; $name += &#39;!&#39;}&quot;&gt;
      Add exclamation mark!
  &lt;/button&gt;</code></pre>
<pre><code class="language-jsx">  import { writable, derived } from &#39;svelte/store&#39;;

  export const name = writable(&#39;world&#39;);

  export const greeting = derived(
      name,
      $name =&gt; `Hello ${$name}!`
  );</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Svelte ] 스벨트의 Lifecycle에 대해 알아보자]]></title>
            <link>https://velog.io/@heejin-k/Svelte-%EC%8A%A4%EB%B2%A8%ED%8A%B8%EC%9D%98-Lifecycle%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@heejin-k/Svelte-%EC%8A%A4%EB%B2%A8%ED%8A%B8%EC%9D%98-Lifecycle%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Thu, 09 Feb 2023 05:16:06 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/heejin-k/post/e13902e0-b818-47ae-bb4e-32a617bf3314/image.png" alt=""></p>
<h1 id="lifecycle">Lifecycle</h1>
<h2 id="1-onmount">1. onMount</h2>
<p>컴포넌트가 처음 DOM에 렌더링 된 후 실행되는 함수입니다. </p>
<pre><code class="language-jsx">import { onMount } from &#39;svelte&#39;;

    let photos = [];

    onMount(async () =&gt; {
        const res = await fetch(`/tutorial/api/album`);
        photos = await res.json();
    });</code></pre>
<p><code>fetch</code>를 <script>의 최상위 레벨에 넣는 것 보다 <code>onMount</code> 함수 안에 넣는 것을 추천합니다.</p>
<p><code>onDestroy</code>를 제외하고 생명 주기 함수는 SSR에서 작동하지 않기 때문에fetch를 <code>onMout</code>함수 안에 넣게 되면 컴포넌트에 fetching 된 데이터가 <strong>느리게 로딩 되는 것을 피할 수 있습니다.</strong> </p>
<aside>
💡 onMount 에 return값을 반환하게 되면, 컴포넌트가 소멸되기 직전에 실행됩니다. = onDestroy

</aside>

<h2 id="2-ondestroy">2. onDestroy</h2>
<p>컴포넌트가 소멸될 때 <code>onDestroy</code>를 사용합니다.</p>
<p>예를 들어 setInterval  함수를 컴포넌트가 초기화 될 때 추가하고, 더 이상 관련이 없을 때 정리할 수 있습니다. 이렇게 하면 메모리 누수를 방지할 수 있습니다. </p>
<pre><code class="language-jsx">import { onDestroy } from &#39;svelte&#39;;

let counter = 0;
const interval = setInterval(() =&gt; counter += 1, 1000);

onDestroy(() =&gt; clearInterval(interval));
&lt;/script&gt;</code></pre>
<p>컴포넌트를 초기화하는 동안 lifecycle 함수를 호출하는 것은 중요하지만 호출하는 위치는 중요하지 않습니다. 따라서 원하는 경우 interval 로직을 utils.js에 넣고 컴포넌트에 import하여 사용할 수 있습니다. </p>
<pre><code class="language-jsx">import { onDestroy } from &#39;svelte&#39;;

export function onInterval(callback, milliseconds) {
    const interval = setInterval(callback, milliseconds);

    onDestroy(() =&gt; {
        clearInterval(interval);
    });
}</code></pre>
<pre><code class="language-jsx">import { onInterval } from &#39;./utils.js&#39;;

let counter = 0;
onInterval(() =&gt; counter += 1, 1000);
&lt;/script&gt;</code></pre>
<h3 id="21-onmount--ondestroy">2.1. <strong>onMount & onDestroy</strong></h3>
<pre><code class="language-jsx">import Something from &#39;./Something.svelte&#39;

let toggle = false
&lt;/script&gt;

&lt;button on:click={()=&gt;{toggle = !toggle}}&gt;
Toggle
&lt;/button&gt;

{#if toggle}
&lt;Something /&gt;
{/if}</code></pre>
<pre><code class="language-jsx">import { onMount, onDestroy } from &#39;svelte&#39;

onMount(()=&gt;{
    console.log(&#39;Mounted!&#39;)
    return()=&gt;{
        console.log(&#39;Destroy in mount&#39;)
    } 
//onMount의 return문은 onDestroy와 동일한 역할을 한다 
//onMount에서 return을 사용해 onDestroy를 구현할때는 비동기가 없는 상황에서만 가능하다 
})
onDestroy(()=&gt;{
    console.log(&#39;destroy~&#39;)
})
&lt;/script&gt;

&lt;h1&gt;Something...&lt;/h1&gt;</code></pre>
<p><code>onMount</code>는 something 컴포넌트가 나타난 직후 바로 실행되는 것을 볼 수 있으며 <code>onDestroy</code>는 컴포넌트가 사라지기 직전에 실행되는 것을 볼 수 있습니다.</p>
<h2 id="3-beforeupdate--afterupdate">3. BeforeUpdate & AfterUpdate</h2>
<ul>
<li>반응성을 가지는 데이터가 할당을 통해서 갱신이 되기 전,후에 실행됩니다.</li>
<li>컴포넌트가 연결될 때 또한 실행 됩니다.</li>
<li><code>beforeUpdate</code> 함수는  DOM이 업데이트 되기 직전, 반응성을 가지는 데이터가 갱신이 되기 직전에 작업이 수행됩니다.</li>
<li><code>afterUpdate</code> 함수는 DOM이 데이터와 동기화 된 후(업데이트 직후), 반응성을 가지는 데이터가 갱신이 된 직후에 작업이 수행됩니다.</li>
<li>반응성을 가지는 데이터는 <code>beforeUpdate</code>, <code>afterUpdate</code> 안에 있게 되면 무한 루프에 빠질 수 있기 때문에 내부에 넣지 않는 것이 좋습니다.</li>
</ul>
<p>이 두 함수를 함께 사용하면 요소의 스크롤 위치를 업데이트하는 동작과 같이 단순히 상태 기반으로 작업하기 어려운 작업을 수행하는데 유용합니다. </p>
<p>공식 문서 예제)</p>
<pre><code class="language-jsx">import Eliza from &#39;elizabot&#39;;
import { beforeUpdate, afterUpdate } from &#39;svelte&#39;;

let div;
let autoscroll;

beforeUpdate(() =&gt; {
        autoscroll = div &amp;&amp; (div.offsetHeight + div.scrollTop) &gt; (div.scrollHeight - 20);
});

afterUpdate(() =&gt; {
        if (autoscroll) div.scrollTo(0, div.scrollHeight);
});

const eliza = new Eliza();

let comments = [
    { author: &#39;eliza&#39;, text: eliza.getInitial() }
];

function handleKeydown(event) {
    if (event.key === &#39;Enter&#39;) {
        const text = event.target.value;
        if (!text) return;

        comments = comments.concat({
            author: &#39;user&#39;,
            text
        });

        event.target.value = &#39;&#39;;

        const reply = eliza.transform(text);

        setTimeout(() =&gt; {
            comments = comments.concat({
                author: &#39;eliza&#39;,
                text: &#39;...&#39;,
                placeholder: true
            });

            setTimeout(() =&gt; {
                comments = comments.filter(comment =&gt; !comment.placeholder).concat({
                    author: &#39;eliza&#39;,
                    text: reply
                });
            }, 500 + Math.random() * 500);
        }, 200 + Math.random() * 200);
        }
    }
&lt;/script&gt;</code></pre>
<p> <code>beforeUpdate</code>가 컴포넌트가 마운트 되기 전에 먼저 실행되므로 div가 해당 속성을 읽어오기 전에 존재 여부를 체크해야 합니다.</p>
<p>간단한 예제)</p>
<pre><code class="language-jsx">import Something from &#39;./Something.svelte&#39;

let toggle = false
&lt;/script&gt;

&lt;button on:click={()=&gt;{toggle = !toggle}}&gt;
Toggle
&lt;/button&gt;

{#if toggle}
&lt;Something /&gt;
{/if}</code></pre>
<pre><code class="language-jsx">import { onMount, onDestroy, beforeUpdate, afterUpdate } from &#39;svelte&#39;

let name = &#39;Something..&#39;
let h1

function moreDot() {
name += &#39;.&#39;
}

beforeUpdate(()=&gt;{
    console.log(&#39;Before update!&#39;)
    console.log(h1 &amp;&amp; h1.innerText)
})

afterUpdate(()=&gt;{
    console.log(&#39;After update!&#39;)
    console.log(h1 &amp;&amp; h1.innerText)
})

onMount(()=&gt;{
    console.log(&#39;Mounted!&#39;)
    h1 = document.querySelector(&#39;h1&#39;)
})

onDestroy(()=&gt;{
    console.log(&#39;Before destroy~&#39;)
})

&lt;/script&gt;

&lt;h1 on:click={moreDot}&gt;Something...&lt;/h1&gt;</code></pre>
<p>위 예제를 실행해보면 <code>.</code>이 추가되기 직전과 something 컴포넌트가 나타나기 직전 beforeUpdate 실행되고, <code>.</code>이 추가된 직후와 something 컴포넌트가 나타난 후 beforeUpdate가 실행된다. </p>
<p>toggle 버튼을 눌렀을 때 실행 순서</p>
<p>Before Update → Mounted → After update</p>
<h2 id="4-tick">4. Tick</h2>
<p><code>tick</code> 함수는 promise를 반환하는 비동기 함수이며 데이터가 갱신 되고 나서 화면이 바뀌는 반응성을 가질 때까지 기다려줍니다.</p>
<p>컴포넌트가 처음 초기화 될 때만 아니라 언제든지 호출할 수 있다는 점에서 다른 lifecycle함수와 다릅니다. </p>
<p>pending 중인 상태가 변경되어 DOM에 반영되는 즉시 ( 또는 pending 되지 않은 상태가 변경되는 즉시 ) promise를 반환합니다.</p>
<p>svelte에서 컴포넌트의 상태를 업데이트하면 즉시 DOM에 업데이트 되지는 않습니다. 대신 브라우저가 불필요한 작업을 피하고, 더 효과적으로 일괄 처리하기 위하여  다른 컴포넌트를 포함해서 다른 변경 사항의 적용이 필요한지 확인하기 위하여 microtask 작업까지 기다립니다. </p>
<pre><code class="language-jsx">import { tick } from &#39;svelte&#39;;

await tick();
this.selectionStart = selectionStart;
this.selectionEnd = selectionEnd;</code></pre>
<ul>
<li><p>전체 예제 보기</p>
<pre><code class="language-jsx">
      import { tick } from &#39;svelte&#39;;

      let text = `Select some text and hit the tab key to toggle uppercase`;

      async function handleKeydown(event) {
          if (event.key !== &#39;Tab&#39;) return;

          event.preventDefault();

          const { selectionStart, selectionEnd, value } = this;
          const selection = value.slice(selectionStart, selectionEnd);

          const replacement = /[a-z]/.test(selection)
              ? selection.toUpperCase()
              : selection.toLowerCase();

          text = (
              value.slice(0, selectionStart) +
              replacement +
              value.slice(selectionEnd)
          );

          await tick();
          this.selectionStart = selectionStart;
          this.selectionEnd = selectionEnd;
      }
  &lt;/script&gt;

  &lt;style&gt;
      textarea {
          width: 100%;
          height: 200px;
      }
  &lt;/style&gt;

  &lt;textarea value={text} on:keydown={handleKeydown}&gt;&lt;/textarea&gt;</code></pre>
</li>
</ul>
<p>간단한 예제)</p>
<pre><code class="language-jsx">import {tick} from &#39;svelte&#39;

let name = &#39;world&#39;

async function handler(){
    name = &#39;heejin&#39;
    await tick()
    const h1 = document.querySelector(&#39;h1&#39;)
    console.log(h1.innerText)
}
&lt;/script&gt;

&lt;h1 on:click={handler}&gt;Hello {name}! &lt;/h1&gt;</code></pre>
<p><code>tick()</code>을 사용하지 않았을 경우 handler 함수 안에서 name의 값을 할당해주었어도 함수 내에서 바로 변경되지 않기 때문에 즉각적으로 변경되지 않습니다.. 그러나 <code>tick</code>을 사용하게 되면 name을 할당된 값으로 변경될 때까지 기다려 준 후 다음 코드를 수행하게 됩니다. </p>
<p><code>tick</code>은 promise를 반환하는 비동기 함수이기 때문에 async await과 함께 사용합니다.</p>
<h2 id="5-lifecycle의-모듈화">5. Lifecycle의 모듈화</h2>
<pre><code class="language-jsx">import { lifecycle, delayRender } from &#39;./lifecycle.js&#39;

let done = delayRender()
lifecycle()
&lt;/script&gt;

{#if $done}
&lt;h1&gt;Hello Lifecycle!&lt;/h1&gt;
{/if}</code></pre>
<pre><code class="language-jsx">import { onMount, onDestroy, beforeUpdate, afterUpdate } from &#39;svelte&#39;
import { writable } from &#39;svelte/store&#39;

export function lifecycle(){
    beforeUpdate(()=&gt;{
        console.log(&#39;Before update!&#39;)
    })

    afterUpdate(()=&gt;{
        console.log(&#39;After update!&#39;)
    })

    onMount(()=&gt;{
        console.log(&#39;Mounted!&#39;)
    })

    onDestroy(()=&gt;{
        console.log(&#39;Before destroy!&#39;)
    })
}

export function delayRender(delay = 3000){ //인수가 없으면 3000으로 세팅
let render = writable(false)
    onMount(()=&gt;{
        setTimeout(() =&gt; {
            console.log(render)
            render.set(true)
        },delay)
    })
}</code></pre>
<p>lifecylce을 다른 js파일로 분리하여 import 해서 사용할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Svelte ] svelte 사용하기 - Events와 Binding]]></title>
            <link>https://velog.io/@heejin-k/Svelte-svelte-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-Events%EC%99%80-Binding</link>
            <guid>https://velog.io/@heejin-k/Svelte-svelte-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-Events%EC%99%80-Binding</guid>
            <pubDate>Wed, 18 Jan 2023 06:05:28 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/heejin-k/post/303bbeb9-257f-455c-b70c-44118fe40e04/image.png" alt=""></p>
<h2 id="events">Events</h2>
<h3 id="1-on">1. <code>on:</code></h3>
<pre><code class="language-jsx">&lt;div on:mousemove={handleMousemove}&gt;
    The mouse position is {m.x} x {m.y}
&lt;/div&gt;</code></pre>
<h3 id="2-lnline-handlers">2. lnline handlers</h3>
<pre><code class="language-jsx">&lt;div on:mousemove=&quot;{e =&gt; m = { x: e.clientX, y: e.clientY }}&quot;&gt;
    The mouse position is {m.x} x {m.y}
&lt;/div&gt;</code></pre>
<p>따옴표는 선택 사항으로 일부 환경에서는 구문 강조 표시로 사용됩니다.</p>
<aside>
💡 일부 프레임워크에서는 내부 성능상의 이유로 인라인 이벤트 핸들러 사용을 지양하는것이 좋으나 Svelte는 해당하지 않으며 어떤 형식이든 상관없다.

</aside>

<ul>
<li><p><strong>수정자</strong></p>
<pre><code class="language-jsx">  &lt;button on:click|once={handleClick}&gt;
      Click me
  &lt;/button&gt;</code></pre>
<ul>
<li><p><code>preventDefault</code></p>
<p>  <code>event.preventDefault()</code></p>
<p>  : 핸들러를 실행하기 전에 호출하여 핸들러의 이벤트 동작을 중단시킵니다.</p>
</li>
<li><p><code>stopPropagation</code></p>
<p>  <code>event.stopPropagation()</code></p>
<p>  다음 요소에 이벤트가 도달하지 못하도록 이벤트 전파를 막습니다.</p>
</li>
<li><p><code>passive</code></p>
<p>  : 터치/휠 이벤트에서 스크롤 성능을 향상시킵니다.</p>
</li>
<li><p><code>nonpassivepassive: false</code></p>
<p>  : 명시적으로 설정</p>
</li>
<li><p><code>capture</code></p>
<p>  : <em>버블링</em> 단계 대신 캡쳐링단계 에서 이벤트를 실행합니다. ( <a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#Event_bubbling_and_capture">MDN 문서</a> ).</p>
</li>
<li><p><code>once</code></p>
<p>  핸들러가 처음 실행된 후 핸들러를 제거하여 한번만 이벤트가 실행됩니다.</p>
</li>
<li><p><code>self</code></p>
<p>  : event.target이 요소 자체(self)인 경우에만 핸들러를 실행합니다.</p>
</li>
<li><p><code>trusted</code></p>
<p>  : event.isTrustedtrue 인 경우에만 핸들러를 트리거합니다. 즉, 이벤트가 사용자 작업에 의해 트리거되는 경우입니다.</p>
</li>
</ul>
</li>
</ul>
<pre><code>`on:click|once|capture={...}` ⇒ 여러개의 수정자 연결</code></pre><h3 id="3-컴포넌트에-이벤트-전달">3. <strong>컴포넌트에 이벤트 전달</strong></h3>
<p>Svelte에서는 이벤트 디스패처(dispatcher)를 사용하여 이벤트를 전달할 수 있습니다. Svelte는 자식 컴포넌트에서 이벤트가 발생하면  해당 이벤트를 부모 컴포넌트에게 전달하고 부모 컴포넌트에서 구현된 이벤트 처리 함수를 실행합니다.</p>
<p>다음 예제는 자식 컴포넌트에서 click 이벤트가 발생하면 부모 컴포넌트에서 구현된 이벤트 처리 함수를 실행합니다.</p>
<pre><code class="language-jsx">
import Inner from &#39;./Inner.svelte&#39;;

function handleMessage(event) {
        alert(event.detail.text);
}
&lt;/script&gt;

&lt;Inner on:message={handleMessage}/&gt;</code></pre>
<pre><code class="language-jsx">
import { createEventDispatcher } from &#39;svelte&#39;;

const dispatch = createEventDispatcher();

function sayHello() {
        dispatch(&#39;message&#39;, {
            text: &#39;Hello!&#39;
        });
}
&lt;/script&gt;

&lt;button on:click={sayHello}&gt;
    Click to say hello
&lt;/button&gt;</code></pre>
<p>자식 컴포넌트는 svelte의 createEventDispatcher 함수를 호출하여 Displatcher를 생성합니다. Dispatcher는 첫번째 매개변수로 이벤트 이름을 작성하고, 두번째로는 이벤트 핸들러에 전달할 인자를 설정합니다.</p>
<p>부모 컴포넌트는 자식 컴포넌트에서 디스패치로 보낸 이벤트 이름인 <strong>message와 이벤트 핸들러 함수 (handleMessage)을 연결하여 사용</strong>합니다. 이벤트가 발생하면 event의 detail 프로퍼티에서 dispatch함수의 두번째 매개변수로 지정한 데이터 값을 확인할 수 있습니다. </p>
<ul>
<li><p><strong>중첩된 컴포넌트에서의 이벤트 전달</strong></p>
<p>  DOM 이벤트와 다르게 컴포넌트 이벤트는 버블링되지 않습니다. 따라서 중첩된 컴포넌트에서 이벤트를 수신하기 위해서는 중간 컴포넌트가 이벤트를 전달해야합니다. </p>
<pre><code class="language-jsx">  import Inner from &#39;./Inner.svelte&#39;;
  import { createEventDispatcher } from &#39;svelte&#39;;

  const dispatch = createEventDispatcher();

  function forward(event) {
      dispatch(&#39;message&#39;, event.detail);
  }
  &lt;/script&gt;

  &lt;Inner on:message={forward}/&gt;</code></pre>
<pre><code class="language-jsx">  import Inner from &#39;./Inner.svelte&#39;;
  &lt;/script&gt;

  &lt;Inner on:message/&gt;
  //svelte에서 지원하는 줄임말로 &#39;모든 message 이벤트 전달&#39; 의미</code></pre>
<pre><code class="language-jsx">  import { createEventDispatcher } from &#39;svelte&#39;;

  const dispatch = createEventDispatcher();

  function sayHello() {
      dispatch(&#39;message&#39;, {
          text: &#39;Hello!&#39;
      });
  }
  &lt;/script&gt;

  &lt;button on:click={sayHello}&gt;
      Click to say hello
  &lt;/button&gt;</code></pre>
</li>
<li><p><strong>Dom 이벤트 전달</strong></p>
<pre><code class="language-jsx">  import CustomButton from &#39;./CustomButton.svelte&#39;;

      function handleClick() {
          alert(&#39;Button Clicked&#39;);
      }
  &lt;/script&gt;

  &lt;CustomButton on:click={handleClick}/&gt;</code></pre>
<pre><code class="language-jsx">  &lt;button on:click&gt;
      Click me
  &lt;/button&gt;

  &lt;style&gt;
      button {
          background: #E2E8F0;
          color: #64748B;
          border: unset;
          border-radius: 6px;
          padding: .75rem 1.5rem;
          cursor: pointer;
      }
      button:hover {
          background: #CBD5E1;
          color: #475569;
      }
      button:focus {
          background: #94A3B8;
          color: #F1F5F9;
      }
  &lt;/style&gt;</code></pre>
<p>  DOM 이벤트에서 이벤트 전달을 할 때는 예제와 같이  <code>&lt;CustomButton&gt;</code> 클릭에 대한 알림을 받고 싶다면, CustomButton.svelte의 <code>&lt;button&gt;</code>요소에 <code>click</code>이벤트를 전달하면 됩니다.</p>
</li>
</ul>
<h2 id="binding">Binding</h2>
<p>일반적인 Svelte의 데이터 흐름 방식은 <code>하향식</code>으로 부모에서 자식의 방향으로 흘러갑니다. 그러나 <code>bind</code> 요소를 사용하게 되면 아래 예제의 input 값이 변경되게 되면, h1 태그 속 name 또한 즉각적으로 변경되어 반영됩니다.</p>
<pre><code class="language-jsx">let name = &#39;world&#39;;
&lt;/script&gt;

&lt;input bind:value={name}&gt;

&lt;h1&gt;Hello {name}!&lt;/h1&gt;</code></pre>
<h3 id="1-inputs-binding">1. <strong>Inputs Binding</strong></h3>
<ul>
<li><p><strong>input의 number type과 range 타입 값 연결</strong></p>
<pre><code class="language-jsx">  &lt;label&gt;
      &lt;input type=number bind:value={b} min=0 max=10&gt;
      &lt;input type=range bind:value={b} min=0 max=10&gt;
  &lt;/label&gt;</code></pre>
<p>  <img src="https://s3-us-west-2.amazonaws.com/secure.notion-static.com/88177bf0-c4cb-4fc9-88c5-28709958a742/Animation.gif" alt="Animation.gif"></p>
</li>
<li><p><strong>checkbox inputs</strong></p>
<pre><code class="language-jsx">  &lt;input type=checkbox bind:checked={yes}&gt;

  {#if yes}
  ...
  {:else}
  ...
  {/if}</code></pre>
<p>  <img src="https://s3-us-west-2.amazonaws.com/secure.notion-static.com/8ac8acd8-abff-4f5b-9023-fdd2c693af16/Animation.gif" alt="Animation.gif"></p>
<p>  <code>input.value</code>에 바인딩 하는 대신 <code>input.checked</code>에 바인딩 합니다.</p>
  <br/>
</li>
<li><p><strong>bind:group</strong>
  : 동일한 값과 관련된 여러 입력이 있는 경우 <code>bind:group</code> 특성을 사용하여 묶을 수 있습니다.</p>
<pre><code class="language-jsx">  &lt;label&gt;
      &lt;input type=radio bind:group={scoops} name=&quot;scoops&quot; value={1}&gt;
      One scoop
  &lt;/label&gt;

  &lt;label&gt;
      &lt;input type=radio bind:group={scoops} name=&quot;scoops&quot; value={2}&gt;
      Two scoops
  &lt;/label&gt;

  &lt;label&gt;
      &lt;input type=radio bind:group={scoops} name=&quot;scoops&quot; value={3}&gt;
      Three scoops
  &lt;/label&gt;</code></pre>
</li>
<li><p><strong>textarea</strong></p>
<pre><code class="language-jsx">  import { marked } from &#39;marked&#39;;
  let text= `Some words are *italic*, some are **bold**`;
  &lt;/script&gt;

  {@html marked(text)}

  &lt;textarea bind:value={text}&gt;&lt;/textarea&gt;</code></pre>
<p>  textarea요소는 input과 유사하게 <code>bind:value</code>를 사용합니다.</p>
<p>  만약 이름이 일치하는 경우 단축 형식을 사용할 수 있습니다.</p>
<pre><code class="language-jsx">  &lt;textarea bind:value&gt;&lt;/textarea&gt;</code></pre>
<p>  ⇒ svelte에서 지원하는 단축 형식으로 모든 바인딩에 적용됩니다.</p>
</li>
<li><p><strong>select</strong></p>
<p>  select에서도 <code>bind:value</code>를 사용할 수 있습니다.</p>
<pre><code class="language-jsx">  &lt;select bind:value={selected} on:change=&quot;{() =&gt; answer = &#39;&#39;}&quot;&gt;
          {#each questions as question}
              &lt;option value={question}&gt;
                  {question.text}
              &lt;/option&gt;
          {/each}
      &lt;/select&gt;</code></pre>
<ul>
<li>select는 <code>multiple</code> 속성을 가질 수 있으며 이 경우 단일 값을 선택하는 대신 배열을 채우게 됩니다.</li>
</ul>
</li>
</ul>
<h3 id="2-each-블록-속성에-binding">2. <code>Each</code> 블록 속성에 binding</h3>
<p><code>each</code> 블록 내부의 속성에도 바인딩 할 수 있습니다. </p>
<pre><code class="language-jsx">{#each todos as todo}
    &lt;div class:done={todo.done}&gt;
        &lt;input
            type=checkbox
            bind:checked={todo.done}
        &gt;

        &lt;input
            placeholder=&quot;What needs to be done?&quot;
            bind:value={todo.text}
        &gt;
    &lt;/div&gt;
{/each}</code></pre>
<p>그러나 이러한 input 요소와 사용하게 되면, 배열이 변경되기 때문에 변경할 수 없는 데이터로 작업하는 경우 이벤트 핸들러를 사용하는 것이 좋습니다.</p>
<h3 id="3--미디어-요소에-binding--audio-video">3.  미디어 요소에 binding : <audio>, <video></h3>
<p><code>&lt;audio&gt;</code>및 <code>&lt;video&gt;</code>요소에는 바인딩할 수 있는 여러 속성이 있습니다 .</p>
<pre><code class="language-jsx">&lt;video
    poster=&quot;https://sveltejs.github.io/assets/caminandes-llamigos.jpg&quot;
    src=&quot;https://sveltejs.github.io/assets/caminandes-llamigos.mp4&quot;
    on:mousemove={handleMove}
    on:touchmove|preventDefault={handleMove}
    on:mousedown={handleMousedown}
    on:mouseup={handleMouseup}
    bind:currentTime={time}
    bind:duration
    bind:paused&gt;
    &lt;track kind=&quot;captions&quot;&gt;
&lt;/video&gt;</code></pre>
<ul>
<li><p>전체 예제 보기</p>
<pre><code class="language-jsx">
      // These values are bound to properties of the video
      let time = 0;
      let duration;
      let paused = true;

      let showControls = true;
      let showControlsTimeout;

      // Used to track time of last mouse down event
      let lastMouseDown;

      function handleMove(e) {
          // Make the controls visible, but fade out after
          // 2.5 seconds of inactivity
          clearTimeout(showControlsTimeout);
          showControlsTimeout = setTimeout(() =&gt; showControls = false, 2500);
          showControls = true;

          if (!duration) return; // video not loaded yet
          if (e.type !== &#39;touchmove&#39; &amp;&amp; !(e.buttons &amp; 1)) return; // mouse not down

          const clientX = e.type === &#39;touchmove&#39; ? e.touches[0].clientX : e.clientX;
          const { left, right } = this.getBoundingClientRect();
          time = duration * (clientX - left) / (right - left);
      }

      // we can&#39;t rely on the built-in click event, because it fires
      // after a drag — we have to listen for clicks ourselves
      function handleMousedown(e) {
          lastMouseDown = new Date();
      }

      function handleMouseup(e) {
          if (new Date() - lastMouseDown &lt; 300) {
              if (paused) e.target.play();
              else e.target.pause();
          }
      }

      function format(seconds) {
          if (isNaN(seconds)) return &#39;...&#39;;

          const minutes = Math.floor(seconds / 60);
          seconds = Math.floor(seconds % 60);
          if (seconds &lt; 10) seconds = &#39;0&#39; + seconds;

          return `${minutes}:${seconds}`;
      }
  &lt;/script&gt;

  &lt;h1&gt;Caminandes: Llamigos&lt;/h1&gt;
  &lt;p&gt;From &lt;a href=&quot;https://studio.blender.org/films&quot;&gt;Blender Studio&lt;/a&gt;. CC-BY&lt;/p&gt;

  &lt;div&gt;
      &lt;video
          poster=&quot;https://sveltejs.github.io/assets/caminandes-llamigos.jpg&quot;
          src=&quot;https://sveltejs.github.io/assets/caminandes-llamigos.mp4&quot;
          on:mousemove={handleMove}
          on:touchmove|preventDefault={handleMove}
          on:mousedown={handleMousedown}
          on:mouseup={handleMouseup}
          bind:currentTime={time}
          bind:duration
          bind:paused&gt;
          &lt;track kind=&quot;captions&quot;&gt;
      &lt;/video&gt;

      &lt;div class=&quot;controls&quot; style=&quot;opacity: {duration &amp;&amp; showControls ? 1 : 0}&quot;&gt;
          &lt;progress value=&quot;{(time / duration) || 0}&quot;/&gt;

          &lt;div class=&quot;info&quot;&gt;
              &lt;span class=&quot;time&quot;&gt;{format(time)}&lt;/span&gt;
              &lt;span&gt;click anywhere to {paused ? &#39;play&#39; : &#39;pause&#39;} / drag to seek&lt;/span&gt;
              &lt;span class=&quot;time&quot;&gt;{format(duration)}&lt;/span&gt;
          &lt;/div&gt;
      &lt;/div&gt;
  &lt;/div&gt;

  &lt;style&gt;
      div {
          position: relative;
      }

      .controls {
          position: absolute;
          top: 0;
          width: 100%;
          transition: opacity 1s;
      }

      .info {
          display: flex;
          width: 100%;
          justify-content: space-between;
      }

      span {
          padding: 0.2em 0.5em;
          color: white;
          text-shadow: 0 0 8px black;
          font-size: 1.4em;
          opacity: 0.7;
      }

      .time {
          width: 3em;
      }

      .time:last-child { text-align: right }

      progress {
          display: block;
          width: 100%;
          height: 10px;
          -webkit-appearance: none;
          appearance: none;
      }

      progress::-webkit-progress-bar {
          background-color: rgba(0,0,0,0.2);
      }

      progress::-webkit-progress-value {
          background-color: rgba(255,255,255,0.6);
      }

      video {
          width: 100%;
      }
  &lt;/style&gt;</code></pre>
</li>
</ul>
<p>비디오를 클릭하게 되면 time, duration, paused가 업데이트 됩니다. 이처럼 사용자가 커스텀하여 컨트롤 할 수 있게 됩니다.</p>
<ul>
<li><p><strong>읽기 전용 binding</strong></p>
<ul>
<li><p><code>duration</code></p>
<p>  : 비디오의 총 재생 시간(초)</p>
</li>
<li><p><code>buffered</code></p>
<p>  : {start, end}의 객체 배열</p>
</li>
<li><p><code>seekable</code></p>
<p>  : {start, end}의 객체 배열</p>
</li>
<li><p><code>played</code></p>
<p>  : {start, end}의 객체 배열</p>
</li>
<li><p><code>seeking</code></p>
<p>  : Boolean</p>
</li>
<li><p><code>ended</code></p>
<p>  : Boolean</p>
</li>
</ul>
</li>
</ul>
<pre><code>**`&lt;video&gt;`**는  `videoWidth` 와 `videoHeight` 가 추가적으로 있습니다.</code></pre><ul>
<li><p><strong>양방향 binding</strong></p>
<ul>
<li><p><code>currentTime</code></p>
<p>  : 비디오의 현재 지점(초)</p>
</li>
<li><p><code>playbackRate1</code></p>
<p>  : 비디오 재생 속도 // 1 = 보통 속도</p>
</li>
<li><p><code>paused</code></p>
<p>  : 이것은 자명해야 합니다.</p>
</li>
<li><p><code>volume</code></p>
<p>   : 0과 1 사이의 값</p>
</li>
<li><p><code>muted</code></p>
<p>  : boolean 값 // true : 음소거</p>
</li>
</ul>
</li>
</ul>
<h3 id="4-block-레벨-요소의-binding">4. Block-레벨 요소의 binding</h3>
<p>모든 block-level 요소에는 <code>clientWidth</code>, <code>clientHeight</code>, <code>offsetWidth</code>, <code>offsetHeight</code> 바인딩을 가지고 있습니다. </p>
<p>이 바인딩들은 읽기 전용으로 <code>w</code> , <code>h</code>를 바꾸는 것 이외에는 어떤 효과도 없습니다. </p>
<pre><code class="language-jsx">&lt;div bind:clientWidth={w} bind:clientHeight={h}&gt;
    &lt;span style=&quot;font-size: {size}px&quot;&gt;{text}&lt;/span&gt;
&lt;/div&gt;</code></pre>
<p>div의 크기를 받아온 값으로 변경해줍니다. </p>
<p><img src="https://s3-us-west-2.amazonaws.com/secure.notion-static.com/4623bd79-9c59-40b4-9006-034d73a9b750/Animation.gif" alt="Animation.gif"></p>
<blockquote>
<p>💡 inline 요소와 <code>&lt;canvas&gt;</code>에서는 사용이 불가능합니다.</p>
</blockquote>
<h3 id="5-this-binding">5. This binding</h3>
<ul>
<li><p>읽기 전용 this binding은 모든 요소에서 적용됩니다.</p>
<pre><code class="language-jsx">  &lt;canvas
      bind:this={canvas}
      width={32}
      height={32}
  &gt;&lt;/canvas&gt;</code></pre>
<p>  <a href="https://svelte.dev/tutorial/bind-this">https://svelte.dev/tutorial/bind-this</a></p>
</li>
</ul>
<h3 id="6-컴포넌트-binding">6. 컴포넌트 binding</h3>
<p>DOM 요소의 속성(<code>&lt;div&gt;</code>,<code>&lt;button&gt;</code>등) 에 바인딩 할 수 있는 것처럼 컴포넌트에도 바인딩 할 수 있습니다. 예를 들어 keypad 컴포넌트의 props인 value를 form 요소를 통해 바인딩할 수 있습니다.</p>
<pre><code class="language-jsx">&lt;Keypad bind:value={pin} on:submit={handleSubmit}/&gt;</code></pre>
<ul>
<li><p>keypad 전체 예제 보기</p>
<pre><code class="language-jsx">  import Keypad from &#39;./Keypad.svelte&#39;;

  let pin;
  $: view = pin ? pin.replace(/\d(?!$)/g, &#39;•&#39;) : &#39;enter your pin&#39;;

  function handleSubmit() {
      alert(`submitted ${pin}`);
  }
  &lt;/script&gt;

  &lt;h1 style=&quot;color: {pin ? &#39;#333&#39; : &#39;#ccc&#39;}&quot;&gt;{view}&lt;/h1&gt;

  &lt;Keypad bind:value={pin} on:submit={handleSubmit}/&gt;</code></pre>
<pre><code class="language-jsx">  import { createEventDispatcher } from &#39;svelte&#39;;

  export let value = &#39;&#39;;

  const dispatch = createEventDispatcher();

  const select = num =&gt; () =&gt; value += num;
  const clear  = () =&gt; value = &#39;&#39;;
  const submit = () =&gt; dispatch(&#39;submit&#39;);
  &lt;/script&gt;

  &lt;div class=&quot;keypad&quot;&gt;
      &lt;button on:click={select(1)}&gt;1&lt;/button&gt;
      &lt;button on:click={select(2)}&gt;2&lt;/button&gt;
      &lt;button on:click={select(3)}&gt;3&lt;/button&gt;
      &lt;button on:click={select(4)}&gt;4&lt;/button&gt;
      &lt;button on:click={select(5)}&gt;5&lt;/button&gt;
      &lt;button on:click={select(6)}&gt;6&lt;/button&gt;
      &lt;button on:click={select(7)}&gt;7&lt;/button&gt;
      &lt;button on:click={select(8)}&gt;8&lt;/button&gt;
      &lt;button on:click={select(9)}&gt;9&lt;/button&gt;

      &lt;button disabled={!value} on:click={clear}&gt;clear&lt;/button&gt;
      &lt;button on:click={select(0)}&gt;0&lt;/button&gt;
      &lt;button disabled={!value} on:click={submit}&gt;submit&lt;/button&gt;
  &lt;/div&gt;

  &lt;style&gt;
      .keypad {
          display: grid;
          grid-template-columns: repeat(3, 5em);
          grid-template-rows: repeat(4, 3em);
          grid-gap: 0.5em
      }

      button {
          margin: 0
      }
  &lt;/style&gt;</code></pre>
</li>
</ul>
<ul>
<li><p><strong>컴포넌트의 인스턴스에 바인딩</strong></p>
<p>  DOM 요소에 바인딩 하는 것처럼 컴포넌트의 인스턴스에도 바인딩이 가능합니다. </p>
<pre><code class="language-jsx">  import InputField from &#39;./InputField.svelte&#39;;

  let field;
  &lt;/script&gt;

  &lt;InputField bind:this={field}/&gt;

  &lt;button on:click={() =&gt; field.focus()}&gt;
  Focus field
  &lt;/button&gt;</code></pre>
<pre><code class="language-jsx">  let input;

  export function focus() {
      input.focus();
  }
  &lt;/script&gt;

  &lt;input bind:this={input} /&gt;</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Svelte] svelte 사용하기 1]]></title>
            <link>https://velog.io/@heejin-k/Svelte-svelte-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-1</link>
            <guid>https://velog.io/@heejin-k/Svelte-svelte-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-1</guid>
            <pubDate>Wed, 18 Jan 2023 05:58:34 GMT</pubDate>
            <description><![CDATA[<p>스벨트에서 사용하는 간단한 속성들과 로직 블럭들에 대해 알아보도록 하겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/0d176f79-591d-48b9-8787-66079b0d5cf1/image.png" alt=""></p>
<h2 id="1-속기-속성">1. 속기 속성</h2>
<pre><code class="language-jsx">&lt;img src={src} alt=&quot;A man dances.&quot;&gt;
=&gt; &lt;img {src} alt=&quot;A man dances.&quot;&gt;</code></pre>
<h2 id="2-html-삽입--html">2. HTML 삽입 : <code>{@html…}</code></h2>
<p>일반적으로 문자열은 일반 텍스트로 삽입됩니다 그러나 HTML을 구성 요소에 직접 렌더링 할 경우 <code>{@html…}</code> 태그를 사용합니다</p>
<pre><code class="language-jsx">&lt;script&gt;
    let string = `this string contains some &lt;strong&gt;HTML!!!&lt;/strong&gt;`;
&lt;/script&gt;

&lt;p&gt;{@html string}&lt;/p&gt;</code></pre>
<h2 id="3-event-listener">3. Event Listener</h2>
<pre><code class="language-jsx">&lt;script&gt;
    let count = 0;

    function incrementCount() {
        count += 1;
    }
&lt;/script&gt;

&lt;button on:click={incrementCount}&gt;
    Clicked {count} {count === 1 ? &#39;time&#39; : &#39;times&#39;}
&lt;/button&gt;</code></pre>
<h2 id="4-반응성">4. 반응성</h2>
<pre><code class="language-jsx">&lt;script&gt;
    let count = 0;
    $: doubled = count * 2;
//참조된 값이 변경 될 때 마다 다시 실행

    function handleClick() {
        count += 1;
    }
&lt;/script&gt;

&lt;button on:click={handleClick}&gt;
    Clicked {count} {count === 1 ? &#39;time&#39; : &#39;times&#39;}
&lt;/button&gt;

&lt;p&gt;{count} doubled is {doubled}&lt;/p&gt;</code></pre>
<p>선언한 값 앞에 <code>$:</code> 을 사용하게 되면, 참조된 값이 변경될 때 마다 다시 실행하게 됩니다.  그렇기 때문에 값을 탐지하고 있다가 데이터가 변화되는 상황이 생기면 특정 기능이 수행됩니다.</p>
<p>반응성 구문인 <code>$:</code> 은 반응성을 계측하는 것일 뿐, 즉각 반영은 아닙니다.
조금의 시간 차이가 있어서 연속적인 작업이 있을 때 오류가 생길 수 있습니다. 따라서, svelte의 lifecycle중 하나인 <code>tick</code> 을 이용해서 반응성 반영을 보장받을 수 있도록 함께 사용합니다.</p>
<p>ex)</p>
<pre><code class="language-jsx">let name = &#39;&#39;;     
let age = &#39;&#39;;  
    $: pow = age * age; 
    //age값이 변할때마다 pow값을 age*age로 저장

const console3 = () =&gt; {         
console.log(`3 → name: ${name}, age: ${age}, pow: ${pow}`);     
}      

$: age, (() =&gt; {     console.log(`0 → age`); }
// age가 변경될 때 마다 출력     
)()      

$: [age, name], (() =&gt; {         console.log(`1 → age, name`); }
//age, name이 변경될 때 마다 출력     
)()      

$: name, (() =&gt; {         console.log(`2 → name: ${name}, age: ${age}, pow: ${pow}`); }
// name, age가 변경될 때 마다 출력     
)()      

$: {     console3();    } 
// 렌더링 될 때만 출력 

$: (() =&gt; {     console.log(`4 → name: ${name}, age: ${age}, pow: ${pow}`);      })()
// name, age가 변경될 때 마다 출력

$: if(age &gt;= 1) {     console.log(`5 → name: ${name}, age: ${age}, pow: ${pow}`); } 
// age가 1이상이고 name, age가 변경될 때 마다 출력     

&lt;main&gt;     
&lt;input type=&quot;text&quot; bind:value={name} placeholder=&quot;name&quot; /&gt;     
&lt;input type=&quot;number&quot; bind:value={age} placeholder=&quot;age&quot; /&gt; 
&lt;/main&gt;

&lt;style&gt;     input {     border: 1px solid orange;     } &lt;/style&gt;</code></pre>
<h2 id="5-다른-파일에서-값-가져오기--export-import">5. 다른 파일에서 값 가져오기 : export, import</h2>
<pre><code class="language-jsx">import Nested from &#39;./Nested.svelte&#39;;

&lt;Nested answer={42}/&gt;
&lt;Nested/&gt;</code></pre>
<pre><code class="language-jsx">export let answer = &#39;a mystery&#39;;

&lt;p&gt;The answer is {answer}&lt;/p&gt;</code></pre>
<p>export와 import를 통해서 다른 파일에서 값을 props로 넘겨 사용할 수 있습니다.</p>
<h2 id="6-logic">6. Logic</h2>
<p>svelte에서는 <code>block</code>들을 사용하여 if, else, await 등을 사용합니다.</p>
<p>구문을 시작할 때는 <code>{#..}</code>을 사용하며 반드시 <code>{/..}</code>를 사용하여 닫아주어야 합니다.</p>
<h3 id="61-if-block">6.1 <code>If</code> block</h3>
<pre><code class="language-jsx">{#if user.loggedIn}
    &lt;button on:click={toggle}&gt;
        Log out
    &lt;/button&gt;
{/if}

{#if !user.loggedIn}
    &lt;button on:click={toggle}&gt;
        Log in
    &lt;/button&gt;
{/if}</code></pre>
<h3 id="62-else-block">6.2 <code>Else</code> block</h3>
<pre><code class="language-jsx">{#if user.loggedIn}
    &lt;button on:click={toggle}&gt;
        Log out
    &lt;/button&gt;
{:else}
    &lt;button on:click={toggle}&gt;
        Log in
    &lt;/button&gt;
{/if}</code></pre>
<h3 id="63-else-if-block">6.3 <code>Else-if</code> block</h3>
<pre><code class="language-jsx">{#if x &gt; 10}
    &lt;p&gt;{x} is greater than 10&lt;/p&gt;
{:else if 5 &gt; x}
    &lt;p&gt;{x} is less than 5&lt;/p&gt;
{:else}
    &lt;p&gt;{x} is between 5 and 10&lt;/p&gt;
{/if}</code></pre>
<h3 id="64-each-block">6.4 <code>Each</code> block</h3>
<pre><code class="language-jsx">&lt;ul&gt;
    {#each cats as cat}
        &lt;li&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.youtube.com/watch?v={cat.id}&quot; rel=&quot;noreferrer&quot;&gt;
            {cat.name}
        &lt;/a&gt;&lt;/li&gt;
    {/each}
&lt;/ul&gt;</code></pre>
<h3 id="65-keyed-each-block">6.5 <code>Keyed each</code> block</h3>
<pre><code class="language-jsx">{#each things as thing (thing.id)}
    &lt;Thing name={thing.name}/&gt;
{/each}</code></pre>
<h3 id="66-await-block">6.6 <code>Await</code> block</h3>
<pre><code class="language-jsx">{#await promise}
    &lt;p&gt;...waiting&lt;/p&gt;
{:then number}
    &lt;p&gt;The number is {number}&lt;/p&gt;
{:catch error}
    &lt;p style=&quot;color: red&quot;&gt;{error.message}&lt;/p&gt;
{/await}</code></pre>
<p>async await 사용</p>
<pre><code class="language-jsx">let planetPromise = getPlanet();
async function getPlanet() {
        const id = Math.floor(Math.random() * 60) + 1;
        const response = await fetch(`https://swapi.co/api/planets/${id}`);
        const data = await response.json();
        return data.name;
    }

&lt;p&gt;
    {#await planetPromise}
        Loading planet...
    {:then planet}
        Your next planet is {planet}.
    {:catch someError}
        System error: {someError.message}.
    {/await}
&lt;/p&gt;

&lt;button on:click={() =&gt; planetPromise = getPlanet()}&gt;
    Explore the next planet
&lt;/button&gt;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Svelte ] Svelte의 장단점]]></title>
            <link>https://velog.io/@heejin-k/Svelte-Svelte%EC%9D%98-%EC%9E%A5%EB%8B%A8%EC%A0%90</link>
            <guid>https://velog.io/@heejin-k/Svelte-Svelte%EC%9D%98-%EC%9E%A5%EB%8B%A8%EC%A0%90</guid>
            <pubDate>Wed, 18 Jan 2023 04:58:07 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/heejin-k/post/41ae8a19-bdea-4fc0-8c5c-6dbed8449cb9/image.png" alt=""></p>
<h1 id="svelte의-특징-및-개념">Svelte의 특징 및 개념</h1>
<h2 id="11-스벨트란">1.1 스벨트란?</h2>
<p>Svelte는 2016년에 출시한 오픈 소스, 웹 프론트엔드 프레임워크입니다.</p>
<p>svelte의 영어 뜻은 <code>날씬한, 호리호리한</code> 이라는 뜻으로 기존에 여러 플러그인을 설치하여 무겁게 사용했던 웹 프론트엔드 프레임워크들과 다르게 순수 자바스크립트를 이용하여 보다 가볍게 사용하는 프레임워크라는 뜻을 담고 있습니다.</p>
<blockquote>
<p>&quot;Frameworks without the framework”</p>
</blockquote>
<p>따라서 “스벨트는 프레임워크 없는 프레임워크” 입니다. </p>
<h2 id="12-svelte-의-장단점">1.2 Svelte 의 장단점</h2>
<h3 id="121-svelte-장점">1.2.1 Svelte 장점</h3>
<ul>
<li><p><strong>Wirte less code!</strong></p>
<p>  스벨트는 적은 코드로 높은 가독성을 유지하고, 개발 시간을 단축시켜줍니다.</p>
<p>  가독성이 좋기 때문에 리팩토링이 보다 쉬워지며 쉬운 디버깅, SPA에 최적화된 더 작은 번들 사용, 낮은 러닝 커브가 장점입니다.</p>
</li>
<li><p><strong>No virtual DOM!</strong></p>
<p>  virtual DOM은 DOM에 직접 접근하여 조작하는 것이 아닌, 가상돔을 만들어 비교하여 조작하는 방법입니다. 이렇게 가상돔을 통해 diffing작업을 하는 다른 프레임워크들과 다르게 svelte는 virtual DOM을 사용하지 않고도 DOM을 가볍게 제어할 수 있습니다.  </p>
<p>  <img src="https://blog.kakaocdn.net/dn/sb31p/btrMyfpavrp/1mmFEuXlcAmoERDb6k7VFK/img.png" alt="웹팩[webpack]을 이용해서 번들링 하는 과정 | 출처 : https://webpack.js.org/  "></p>
<p>  웹팩[webpack]을 이용해서 번들링 하는 과정 | 출처 : <a href="https://webpack.js.org/">https://webpack.js.org/</a>  </p>
</li>
</ul>
<blockquote>
<p>💡 <strong>번들링</strong>이란 복잡한 DOM이나 CSS, 요소들을 나눠 Node.js로 작업하고 배포 시 하나의 파일로 모이게 만드는 것을 말합니다.</p>
</blockquote>
<p>   번들링 과정에서 여러 js파일들은 하나의 js파일로 모이고, sass나 css파일은 하나의 css로 모이게 됩니다.</p>
<p>   리액트<code>나</code>뷰`는 이 과정에서 자신의 규칙과 패턴으로 이루어진 자신의 언어로 js파일로 들어가며 브라우저가 인식할 수 있도록 라이브러리도 함께 들어갑니다.</p>
<p>  그러나 <code>Svelte</code>는 이 과정에서 언어를 컴파일만 하고, 순수 자바스크립트로 들어가게 되기 때문에 Svelte는 프레임워크라고 소개하지 않고, <strong>최적화된 자바스크립트로 변환하는 컴파일러</strong>라고 소개합니다.</p>
<p>  또한 라이브러리 없이 순수 자바스크립트로 들어가기 때문에 빌드 될 때 용량이 작아진다는 장점도 생기게 됩니다.</p>
<ul>
<li><p><strong>Truly reactive!</strong></p>
<p>  스벨트는 반응성이 좋습니다. 하나의 이벤트가 발생할 때 나머지 이벤트들이 자동으로 발생할 수 있습니다</p>
</li>
</ul>
<h3 id="122-svelte-단점">1.2.2 Svelte 단점</h3>
<ul>
<li>Svelte는 아직까지 대중적으로 사용하고 있지 않기 때문에 낮은 성숙도, 다양한 소스코드나 플러그인들이 많지 않은 작은 생태계라는 점이 단점이라고 볼 수 있습니다.</li>
<li>Svelte는 IE를 지원하지 않습니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[error ] yarn, npm 실행 에러 - node 버전 관리로 해결]]></title>
            <link>https://velog.io/@heejin-k/error-yarn-npm-%EC%8B%A4%ED%96%89-%EC%97%90%EB%9F%AC-node-%EB%B2%84%EC%A0%84-%EA%B4%80%EB%A0%A8-%EB%AC%B8%EC%A0%9C-ubuntu</link>
            <guid>https://velog.io/@heejin-k/error-yarn-npm-%EC%8B%A4%ED%96%89-%EC%97%90%EB%9F%AC-node-%EB%B2%84%EC%A0%84-%EA%B4%80%EB%A0%A8-%EB%AC%B8%EC%A0%9C-ubuntu</guid>
            <pubDate>Thu, 12 Jan 2023 01:59:31 GMT</pubDate>
            <description><![CDATA[<blockquote>
</blockquote>
<p>프로젝트를 실행하려고 패키징 매니저를 설치하고 start를 했는데 
<code>Error: error:0308010C:digital envelope routines::unsupported</code>
와 같은 에러를 마주치게 되었다..!</p>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/263ffb61-a8e6-46f3-a815-d7d750910010/image.png" alt=""></p>
<p>yarn 과 npm 모두 동일한 문제 발생하여 서칭해봤더니 node 버전으로 인한 문제였다. 
최신버전의 노드를 사용하면서 발생하는 문제점으로 안정적인 버전으로 node를 다운그레이드 해주면 해결 가능하다.</p>
<ul>
<li>Node.js 버전 확인<pre><code class="language-js">node -v</code></pre>
</li>
</ul>
<h2 id="nvm으로-관리">NVM으로 관리</h2>
<h4 id="nvm이란-">nvm이란 ?</h4>
<p>node js** 버전관리 매니저<strong>로 여러개의 nodejs를 설치하고 **사용할 버전을 쉽게 전환</strong>할 수 있도록 도와주는 shell script</p>
<h3 id="1-nvm-설치방법">1. NVM 설치방법</h3>
<blockquote>
<p><a href="https://github.com/nvm-sh/nvm">NVM repository(공식 문서)</a> 참고</p>
</blockquote>
<p><a href="https://github.com/coreybutler/nvm-windows">https://github.com/coreybutler/nvm-windows</a>
위 링크로 들어가서 
<img src="https://velog.velcdn.com/images/heejin-k/post/4796174b-8dcb-4946-bcc3-f19b4777d35b/image.png" alt=""><code>Download Now!</code> 클릭 후 <code>nvm-setup.exe</code>를 설치한다.</p>
<ul>
<li>NVM 설치 확인
<code>nvm</code>만 입력하여 버전과 사용법 모두 확인하거나 
<code>nvm version</code> 입력하여 설치된 버전 확인</li>
</ul>
<h3 id="2-nvm-사용법">2. NVM 사용법</h3>
<ul>
<li><p><code>nvm list</code>
: 현재 설치된 Node.js의 버전들을 확인할 수 있다.</p>
</li>
<li><p><code>nvm install {version}</code>
새로운 버전의 노드를 설치할때 사용한다</p>
</li>
</ul>
<p>-<code>nvm use {version}</code>
현재 프로젝트에서 설치한 node 버전으로 바꿔서 사용
nvm use 16을 입력해주면 다음과같이 지금부터 16버전을 사용하겠다는 메세지가 출력된다
<img src="https://velog.velcdn.com/images/heejin-k/post/752f8ec2-02e1-4efe-940e-21c4afb7c71a/image.png" alt=""></p>
<h2 id="n-플러그인으로-관리">N 플러그인으로 관리</h2>
<p>n을 사용하여 Node.js의 버전을 관리 가능하는 방법도 있다.</p>
<ul>
<li><h4 id="n-플러그인-설치">n 플러그인 설치</h4>
<p><code>sudo npm install -g n</code></p>
</li>
<li><h4 id="최신-버전-설치">최신 버전 설치</h4>
<p><code>n latest</code></p>
</li>
<li><h4 id="안정적-버전-설치">안정적 버전 설치</h4>
<p><code>n lts</code></p>
</li>
<li><h4 id="특정-버전-설치">특정 버전 설치</h4>
<p><code>n 10.16.0</code></p>
<p>  ❗ Permission denied가 뜬다면 맨 앞에 sudo를 붙여준다.</p>
</li>
<li><h4 id="다운로드-목록-보기">다운로드 목록 보기</h4>
<p><code>n</code>
엔터키로 선택하여 해당 버전을 설치해서 사용할 수 있다.</p>
</li>
</ul>
<blockquote>
<p>이후 노드버전을 16으로 변경해주어 프로젝트를 실행해주니 정상적으로 프로젝트가 실행되는 것을 볼 수 있다!</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[협업도전기 ] API 명세에 없는 기능 냅다 구현하기]]></title>
            <link>https://velog.io/@heejin-k/%ED%98%91%EC%97%85%EB%8F%84%EC%A0%84%EA%B8%B0-API-%EB%AA%85%EC%84%B8%EB%A5%BC-%EC%A1%B0%ED%95%A9%ED%95%B4%EC%84%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@heejin-k/%ED%98%91%EC%97%85%EB%8F%84%EC%A0%84%EA%B8%B0-API-%EB%AA%85%EC%84%B8%EB%A5%BC-%EC%A1%B0%ED%95%A9%ED%95%B4%EC%84%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 21 Jul 2022 08:28:06 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>🗯️ 어쩌다 보니 명세에 없는 기능을 구현하게 되었다.. 
잘 보고 기획했어야했는데 🤔 근데.. 이런 기회? 오히려 좋아 ~! </p>
</blockquote>
<h2 id="사건의-개요">사건의 개요..</h2>
<p>우리가 구현하고자 하는 API명세는 &#39;감귤마켓&#39;의 명세이다. 
그냥 딸기마켓, 포도마켓 이렇게 이름만 바꾸고 이런 종류의 프로젝트를 구현해봐라! 라는 목적인 것 같았는데 우리는 남들과는 다른 차별화된 우리만의 프로젝트를 만들고싶었고..
그러다보니 마켓이아닌 &#39;펫메이트&#39; 라는 반려동물 매칭 서비스를 기획하게 되었다.
감귤마켓의 기본 명세는 지키되, 우리는 사용용도를 살짝씩 변화를 주었고 
기존의 감귤마켓에는 없는 매칭할 반려동물을 구하는 피드를 추가로 기획했다!</p>
<p>그래서 최종적으로 우리피드는 매칭피드 , 커뮤니티피드 두가지였는데 둘 다 모두 자신이 팔로우한 사람의 게시글이 피드에 올라가는 방식이다. 그러나 API 명세에서 제공되는 것은 커뮤니티피드 단 한 개였다...! </p>
<p>이거 명세에 없는거면,, 우리가 구현할 수 없는건가? 하는 생각에 멘토님께 질문을 드렸고
<img src="https://velog.velcdn.com/images/heejin-k/post/b38c1355-bc51-4b07-8d11-e07e94e24cc6/image.png" alt=""></p>
<p>가능하다는 답변을 받아 냅다 도전!!!!!!</p>
<h2 id="도전">도전</h2>
<p>일단 문제를 해결하려면, 전체게시글 중 내가 팔로워 한 사람의 글만 골라서 보이게 구현하면 될 것 같았다!</p>
<p>현재 내가 팔로우 하는 사람의 정보를 불러오는건 API명세에 있기때문에 그 명세를 사용해서 이 기능을 구현하였다. 처음 써보는 리덕스툴킷을 이용해서 팔로워의 정보의 상태관리를 시도했다.</p>
<p>내가 팔로우한 사람들의 정보를 불러오는데는 성공..!
follower 배열을 얻게되었다.
<img src="https://velog.velcdn.com/images/heejin-k/post/b201cb5c-19bc-499b-b40a-c6b898c31f2c/image.png" alt="">
이제 여기서 follwer의 accountname과 posts의 accountname이 일치하는 posts를 찾아서 배열로 만들어주면되는데,, 
filter 함수를 사용하면 될 것 같았다. </p>
<pre><code class="language-jsx">function isFollower(e) {
    if (e.accountname === follower.accountname) {
      return true
    }
  }

  const followpost = posts.filter(isFollower);</code></pre>
<p>무작정 위 코드를 쳐봤지만 당연히 안될 것 같았다 ^^.. </p>
<p>일단 followerId를 따로 빼내서 배열을 만들었다.
<img src="https://velog.velcdn.com/images/heejin-k/post/2c412e7a-19cc-4830-a512-9badcd56a06a/image.png" alt=""></p>
<p>그럼 이제 얘네랑 일치하는 posts를 찾으면되는데,,
이거 뭔가 가볍게 해결 가능 할 것 같은데 ..! 
어떤 로직을 짜야할까</p>
<ul>
<li><h3 id="첫-시도">첫 시도</h3>
<pre><code class="language-js">function isFollower() {
  for (let i = 0; i &lt; followerId.length; i++) {
    for (let j = 0; j &lt; posts.length; j++) {
      if (posts[j].accountname === followerId[i]) {
        return true
      }
    }
  }
}
</code></pre>
</li>
</ul>
<p>const followpost = posts.filter(isFollower);</p>
<pre><code>![](https://velog.velcdn.com/images/heejin-k/post/bc379266-b5db-42c5-ab2b-31d7f0702da4/image.png)흠.. 결과값이 몬가 이상하다..

- ### 두번째 시도

이번에는 filter를 안쓰고 그냥 짜보았다.  
```js
const followPost = [];

  for (let i = 0; i &lt; followerId?.length; i++) {
    for (let j = 0; j &lt; posts?.length; j++) {
      if (posts[j].accountname === followerId[i]) {
        followPost.push(posts[j])
      }
    }
  }</code></pre><p>나올 것 같은데 안나온다. 
콘솔로 찍었을 경우 i와 j는 잘 찍히는걸 보면 posts[j]쪽이 문제인 것 같다.
posts는 객체들이 배열로 들어가 있기 때문에 몇번째인지 정해주면 출력이 될 것 같았는데 콘솔로 찍어본 결과 <img src="https://velog.velcdn.com/images/heejin-k/post/69e3e008-1884-4574-a959-3e4ca91fedd0/image.png" alt="">
이렇게 object로 출력된다 ㅠㅠ </p>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/e3323907-17f9-47d1-b90c-9a99cfa4b3f8/image.png" alt="">삽질하다가 배열을 클릭해봤는데,, posts[j]다음에 accountname이 아니었구나..? 그래서 그런건가.. 싶어서  <code>posts[j].author.accountname</code> 으로 바꿔주었다!</p>
<pre><code class="language-js">const followpost = [];

  for (let i = 0; i &lt; followerId?.length; i++) {
    for (let j = 0; j &lt; posts?.length; j++) {
      if (posts[j].author.accountname === followerId[i]) {
        followpost.push(posts[j])
      }
    }
  }</code></pre>
<p>근데도 undefind가 떠서 한참을 삽질했다 근데 알고보니,,</p>
<pre><code class="language-js">const loginReqPath = `/product/?limit=100`;</code></pre>
<p>100개의 리미트 중에 내가 팔로우 한 사람의 글이 없었음 ㅋ..ㅋ...ㅋㅋ..
팔로워 배열에 다른사람 막추가해보고 콘솔찍다 알아차려따.. 
그래서 리미트 한도를 올려줬더니??</p>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/e3593bbb-0be4-452a-81c8-5c0ca1219d55/image.png" alt=""></p>
<p>잘뜬다 ^_^
결론 코드는 맞는 코드였다 ㅠ </p>
<p>for문으로 성공은 했지만 for문에서 undefind 뜬 것 때문에 잘못 사용한 줄 알고
아까 하다가 말았던 filter를 사용해서 다시 구현했었다.</p>
<p>여기서도 제대로 했는데 계속 동작이 안돼서 봤더니 100개중에 팔로워 게시글이 없는거 알아차려서 for문도 이렇게 수정할 수 있었다..ㅎㅎ</p>
<blockquote>
<ul>
<li><h3 id="수정한-filter를-사용한-코드-최종코드✨">수정한 filter를 사용한 코드 (최종코드)✨</h3>
</li>
</ul>
<pre><code class="language-js">let followpost = posts?.filter(e =&gt; followerId.includes(e.author.accountname));</code></pre>
</blockquote>
<pre><code>&gt;
&gt;for문보다 더 깔끔하고 간결하다!!!
그리고 for문을 사용했을 때는 아이디별로 포스트를 찾아서 넣어주는 방식이다보니 순서가 시간순서가 아닌, 아이디별로 포스트가 모아져 있어서 시간순으로 다시 정렬해주는 코드를 추가로 짜야했다. 그러나 filter를 사용해서 짠 코드는 순서가 최신글이 위에 올라와있어서 훨씬 보기좋고 효율적인 것 같아 이 코드를 사용할 예정이다!





- ### 그냥 끄적거리다가 버린 코드

dipatch 쪽도 한 번 건드려봤다. 
```js
AxiosPetInfo(URL + `/product/${followerId}/?limit=30`)</code></pre><p>저 괄호속의 아이디를 가진 사람의 글을 불러오기때문에 .. 팔로워된 모든 사람의 아이디를 요청하면 되지않을까..? 싶어서 코드를 작성해봤는데 </p>
<pre><code class="language-jsx">  useEffect(() =&gt; {
    for (let i = 0; i &lt; followerId?.length; i++) {
      dispatch(AxiosPetInfo(URL + `/product/${followerId[i]}/?limit=30`))
      console.log(&#39;gg&#39;, followerId[i])
    }
  }, [followerId])</code></pre>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/96a5705d-5ff4-474b-a04d-31dff5b97b8d/image.gif" alt=""></p>
<p>아주 신나는 화면이 출력되고있다. 무한로딩이다 ... ㅋㅋㅋ 
일단 for문을 디스패치에 사용하는게 옳지 않는 것 같다. 그리고 생각해보니까</p>
<pre><code class="language-js">dispatch(AxiosPetInfo(URL + `/product/${followerId[i]}/?limit=30`)</code></pre>
<p>여기서 followerId의 값이 계속 바뀌어 통신을 해주어야해서 안좋은 코드가 될 것 같아서 이방법은 끄적거려 보다가 바로 포기했다! 
만약 팔로워가 1000명이라면 1000번의 통신을 해야하는데,, 끔찍하다 ,,,, </p>
<p>.
.
.</p>
<p>그래서 결론은 깔끔하게 <strong>filter</strong>를 사용해서 구현 성공!!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React ] NavLink로 탭 메뉴 클릭 구현하기 ]]></title>
            <link>https://velog.io/@heejin-k/React-NavLink%EB%A1%9C-%ED%83%AD-%EB%A9%94%EB%89%B4-%ED%81%B4%EB%A6%AD-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@heejin-k/React-NavLink%EB%A1%9C-%ED%83%AD-%EB%A9%94%EB%89%B4-%ED%81%B4%EB%A6%AD-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 15 Jul 2022 15:03:20 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>현재 프로젝트에서 기존에 내가 탭메뉴 컴포넌트를 구현해놨던 방식은 그냥 hover 했을 경우 배경 아이콘의 색이 변경되는 코드이다. 
이제 프로젝트가 점점 어느정도 틀이 잡히면서 탭메뉴에 페이지도 연결하고 해당 탭을 클릭했을 경우 배경아이콘의 컬러가 유지되는 기능을 추가하기 위해 NavLink를 사용하게 되었다. </p>
</blockquote>
<h2 id="navlink란-무엇인가">NavLink란 무엇인가?</h2>
<h3 id="link">Link</h3>
<p><code>Link</code>는 <code>react-router-dom</code>에서 지원하는 속성으로 라우터를 연결할때 자주 사용해왔을 것이다.</p>
<pre><code class="language-jsx">&lt;Link to=&quot;/home&quot;&gt;home&lt;/Link&gt;</code></pre>
<p>리액트 라우터에서 페이지를 이동할 때 Link를 사용하면 브라우저의 주소만 바꾸어 페이지를 새로 불러오지 않은채로 내가 이동하고자 하는 경로에 이동할 수 있다. 즉 a태그로 페이지를 이동할 때 발생하던 새로고침이 일어나지 않는다.</p>
<h3 id="navlink">NavLink</h3>
<p><code>NavLink</code>는 이 Link 태그의 special version 이다.
선택된 요소에 active 클래스가 자동으로 추가되어서 nav스타일링을 원활하게 할 수 있어 아주 편리하다. 지금까지 이런 비슷한 기능을 구현하며 자바스크립트로 selected 클래스를 넣었다 뺐다하는 작업을 했던 걸 생각하면 정말 간단하게 구현 가능할 것 같았다!
이 active 클래스는 </p>
<pre><code class="language-jsx">className={({isActive}) =&gt; (isActive? &quot;red&quot; : &quot;blue&quot;)}</code></pre>
<p>이런 방식으로 isActive인 경우 클래스명을 다르게 지정할 수도 있다.</p>
<p>두번째로, activestyle을 지정하여 사용가능하다.</p>
<pre><code class="language-jsx">activestyle={color:red} </code></pre>
<p>이렇게 active 된 스타일을 인라인으로 바꿔 줄 수 있으며 객체를 넣어 전달할 수도 있다.</p>
<hr>
<h2 id="문제점">문제점</h2>
<p>이렇게 active 클래스가 내가 선택한 요소에 추가되는 것 까지는 알겠는데.. 너무 유용해보이긴 하는데.. 이걸 내 프젝에서 어떻게 해야할까? 고민에 빠졌다.</p>
<p>마냥 css로 .active { } 해서 요소를 추가하기에는 현재 styled-components로 구현하고 있으며 프롭스를 사용하고 있다. 특히 가상요소인 아이콘의 background를 프롭스로 넣어주고 있기 때문에 더욱 막막했다. </p>
<h2 id="해결">해결</h2>
<h3 id="기존코드">기존코드</h3>
<pre><code class="language-jsx">&lt;Link to=&quot;/homepage&quot;&gt;
  &lt;IconNameStyle
   icon={homeIcon}
   hoverIcon={homeIconFill}&gt;
    홈 &lt;/IconNameStyle&gt;
&lt;/Link&gt;</code></pre>
<p>기존에는 아직 active를 고려하지 않아 Link로 구현하였으며, 그 안에 IconNameStyle 이라는 p 태그 스타일컴포넌트를 만들어주었다.
이미 텍스트로 해당 영역을 알려주기 때문에 아이콘은 사실상 디자인일 뿐이지 필수요소가 아니기에 이미지값이아닌, 스크린리더에 읽히지 않는 가상요소로 주었으며 p태그인 IconNameStyle의 가상요소로 배경 아이콘이 props를 통해 바뀌고 있는 구조이다. </p>
<p>우선 active는 IconNameStyle의 부모요소인 Link의 클래스로 들어가기때문에 IconNameStyle에 있는 가상요소를 건드리기에는 다소 무리가 있다고 판단하였으며 <strong>굳이 저걸 Link &gt; IconNameStyle 이렇게 두번 감싸 줄 필요가 있을까?</strong> 하는 생각이 들었다.</p>
<p>그래서 Link나 NavLink도 태그이기 때문에 스타일 컴포넌트가 적용되지 않을까 하고 시도해봤더니 오류가 떴다.. 뭔가 될 것 같았는데. 
서칭해보던 중 스타일 컴포넌트에서 NavLink를 import 해준다면 스타일링이 가능 하다는 글을 보게 되었고, 작업하는 탭메뉴 컴포넌트 페이지가 아닌, 스타일컴포넌트를 만드는 스타일 페이지에 import 해주었다.</p>
<pre><code class="language-js">import { NavLink } from &#39;react-router-dom&#39;;</code></pre>
<p>기존의 IconNameStyle 은 NavLinkStyle로 네이밍을 변경해주고, </p>
<pre><code class="language-js">export const NavLinkStyle = styled(NavLink)`

`</code></pre>
<p>다음과 같이 바꾸어 스타일 컴포넌트를 변경해주었더니 NavLink 태그도 스타일이 잘 이루어졌다!
이제 active class가 추가되었을 경우 스타일을 추가해주기위해</p>
<pre><code class="language-js">&amp;.active{
  color: #1D57C1;
::before{
  ${(props) =&gt; {
    return css`
    background-image: url(${props.hoverIcon});
    `
  }}}
}
</code></pre>
<p>다음과 같은 코드를 추가해주었다. 
.active::before을 따로 만들지 않고, sass 문법을 사용하여 중첩해서 사용하였다.
스타일 컴포넌트에서 기본적으로 sass문법이 사용하능해서 편리한 것 같다!</p>
<p>이렇게 NavLink를 스타일 컴포넌트해서 불필요한 코드도 줄이고, 간단하게 탭메뉴 선택 기능을 구현 끝 !! </p>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/995f80f2-6c32-404b-b0b8-1e476080b777/image.gif" alt=""></p>
<p>피드는 아직 라우터 연결이 제대로 되어있지 않아서 제외한 완성 결과물 !!</p>
<p>리액트는 너무너무 편한게 많은 것 같다..!! 짱짱</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React ] 다사다난했던 회원가입 기능 구현..🔥]]></title>
            <link>https://velog.io/@heejin-k/%ED%98%91%EC%97%85%EB%8F%84%EC%A0%84%EA%B8%B0-react-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85</link>
            <guid>https://velog.io/@heejin-k/%ED%98%91%EC%97%85%EB%8F%84%EC%A0%84%EA%B8%B0-react-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85</guid>
            <pubDate>Thu, 07 Jul 2022 04:13:12 GMT</pubDate>
            <description><![CDATA[<h1 id="회원가입기능--프로필설정-트러블-슈팅">회원가입기능 + 프로필설정 트러블 슈팅</h1>
<blockquote>
<p>회원가입 기능을 구현하면서 겪었던 여러가지 이슈들을 기록해보고자 한다.
정말 다사다난 했던 회원가입과 프로필설정 기능.. 💦 
많은 에러들 중 가장 나를 힘들게 했던 에러들을 꼽자면, </p>
</blockquote>
<h2 id="1-미리보기와-서버에-통신하는-파일-형식이-달라서-발생하는-문제점">1. 미리보기와 서버에 통신하는 파일 형식이 달라서 발생하는 문제점.</h2>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/619e52b4-248c-47a3-85df-12f647e8e56e/image.png" alt=""></p>
<p>회원가입 기능 중 프로필설정 부분에서 이미지 아이콘을 누르면 이미지가 업로드되고, 미리보기가 가능한 기능을 구현하기위해 FileReader를 사용하였다. 
<img src="https://velog.velcdn.com/images/heejin-k/post/c1545307-25f2-4d23-b5db-40df9a396011/image.png" alt=""></p>
<h3 id="문제점-">문제점 :</h3>
<p>화면상으로는 파일이 잘 바뀌었지만, 서버와 통신하는 과정에서 화면에 출력하기위해서 주소로 변환해주는 reader.readAsDataURL()때문에 이미지의 주소값이 API에서 요청하는 파일형식이아닌, base64 형식으로 들어가서 계속 이미지응답이 빈값으로 들어왔다. </p>
<h3 id="해결-">해결 :</h3>
<p>화면에 프로필 사진이 표시되는 저 위에 주석 코드를 전부 없애주고 
서버 통신용 이미지 파일의 상태관리를 서버전송용과 view용으로 2개 나누어 관리해주었다.
파일을 업로드 할 때 setImg(서버통신용 파일형식)와 showImg(미리보기용 파일형식)으로 나누어 저장해주고, 화면에 랜더링 되는 부분은 전부 showImg 형식으로 바꾸어 문제를 해결했다.</p>
<h2 id="2-이미지-파일-전환-함수-실행-이슈">2. 이미지 파일 전환 함수 실행 이슈</h2>
<h3 id="개요-">개요 :</h3>
<p>우선, 회원가입 기능은 이메일로 회원가입과 프로필설정이 모두 이루어져 6가지 항목을 전부 입력해야 회원가입이 가능하다. 
<img src="https://velog.velcdn.com/images/heejin-k/post/51e4436b-b8cb-45b3-9a05-82db72b9dadd/image.png" alt="">
그렇기 때문에 시작하기 버튼을 눌러 6가지 항목이 저장된 userData를 전송하기전, image파일을 명세에서 요구하는 형식으로 변환해주는 함수(postImgData)를 먼저 실행해서 res값을 userData의 image에 넣어 준 후 회원가입 통신(postData)이 실행되어야 한다.</p>
<h3 id="문제점--1">문제점 :</h3>
<p>이과정에서 나는 먼저 프로필 이미지가 변경되었을때 바로 이미지변환함수가 실행되어 파일형식이 변환되었으면 했다. 그래서 프로필 이미지에 onChange 함수를 사용하고 싶었는데 이미 onChange 함수가 사용되어서 파일이 업로드되었을때 setImg에 저장되는 함수가 실행되고있었다.<br><img src="https://velog.velcdn.com/images/heejin-k/post/11d6f968-5c3b-4577-9294-9422351a95ef/image.png" alt=""></p>
<p>그래서 냅다 코드 맨밑에 이미지변환함수(postImgData)를 넣었더니 빈값이 출력되었다.
useState의 경우 부모함수를 벗어난 다음에 값이 저장되는데 onChange함수안에 postImgData가 들어있어 여전히 setImg에 값이 저장되지 않아 빈값이 출력된 것이다.</p>
<p>그리고 onChange로 이미지가 변경될 때마다 변환함수를 실행하게 된다면, 유저가 프로필 이미지를 고민하느라 여러번 이미지를 바꾸게 된다면 불필요한 통신이 발생하기 때문에 이 방법 보다 회원가입통신을 하기 직전 바로 이미지를 변환함수가 실행된 후 회원가입이 되게 구현하는 것이 더 낫다고 판단하였다.  </p>
<p>postImgData와 postData를 모두 시작하기 버튼이 눌러졌을때 순차적으로 실행되게 해야했는데, 
통신코드를 주석으로 닫고 콘솔로그만 찍었을 경우에는 userData의 image에 postImgData의 res값이 잘 반영되다가도 통신코드를 실행하면 순서가 꼬였고 postData가 postImgData의 res값을 제대로 받아오지 못해 이미지가 빈값으로 전송되었다.</p>
<h3 id="해결--1">해결 :</h3>
<h3 id="1-찝찝한-해결">1. 찝찝한 해결...</h3>
<p>우리는 uerData의 img값만 받아오면 통신문제는 해결되었기 때문에 postImgData의 res값을 바로 postData의 매개변수로 받아오는 방법으로 임시적으로 해결했다.
<img src="https://velog.velcdn.com/images/heejin-k/post/9a424245-8a76-4ece-9e33-c37bd347af69/image.png" alt=""></p>
<p>그러나 이렇게 할 경우 프로필 이미지 업로드 함수를 재사용하는데 어려움이 있고 postData가 갑자기 뜬금없이 file만 받아오기 때문에 코드 가독성에서 문제가 있다.</p>
<h3 id="2-위-1번-해결의-문제점-해결방안">2. 위 1번 해결의 문제점 해결방안</h3>
<p>userData를 부모함수에 선언하고, image에 postImgData의 리턴값을 저장한 후 postData에서 매개변수로 userData를 불러온다.
이렇게 되면 postData가 userData를 받아오기 때문에 코드 가독성에도 문제가 없다.</p>
<h3 id="3-최종-코드">3. 최종 코드</h3>
<p>postData와 postImgData를 합쳐 SignUp함수를 만들었다.
실질적으로 이 모든 함수의 기능은 결국 회원가입에 도달하기 때문에 함수명을 좀 더 직관적으로 변경하였으며 postImgData는 ImgUpload 함수로 바꾸고, 재사용을 위해 코드를 따로 분리해 export 하였다. 위 2번을 참고하여 userData를 통신함수의 부모함수에 선언하였고 
Signup함수에서 ImgUploade함수를 실행하여 이미지를 받아오고, 회원가입 통신까지 모두 이루어지게 구현하였다.
ImgUploade 함수의 리턴값을 받아올때 promiss로 받아오는 문제점과, imgUpload함수를 먼저 실행되게 해야하는 문제점을 해결하기 위하여 async await을 사용하여 </p>
<pre><code class="language-jsx">async function signUp() {
    const imgUploadData = await ImgUpload(userImg)
    //유저데이터 변수의 이미지에 저장 
    userData.user.image = imgUploadData
    //회원가입 최종 전송
    axios.post(url + &#39;/user&#39;, userData,
      { headers: { &quot;Content-type&quot;: &quot;application/json&quot; } })
      .then((res) =&gt; console.log(&#39;회원가입&#39;, res))
  }</code></pre>
<p>imgUpload함수를 먼저 실행한후, return 값을 imgUploadData에 저장되게 하였으며 
userData 내에 user.img에 해당 값을 넣어주었다.
따라서 최종적으로 시작하기 버튼을 클릭했을 때 signUp함수가 실행되면 img파일명 변환함수가 실행된 후 해당 리턴값이 signUp에서 받아와 정상적으로 img파일명 변환 또한 잘 반영된 채로 회원가입 통신이 이루어 진 것을 볼 수 있다. </p>
<h2 id="느낀점">느낀점</h2>
<p>꼬박 2~3일을 아침 9시부터 새벽까지 불태우며 아주 많은 삽질을 했지만 그래도 이렇게 여러 에러사항들을 마주치고 해결하다보니 useState와 axios에 대해 더욱 잘 이해할 수 있게 된 것 같다!! 
특히 useState의 경우 값이 저장되기 위해서는 함수가 다 실행되고 난 후 값이 저장되기때문에 이 특징을 잘 고려해가며 코드를 작성해야 할 것 같다.
이렇게하면 돌아갈 것 같은데? 라는 생각으로 내가 구현하는 코드의 특징을 잘 파악하지 않고 무작정 구현했던 것이 시간이 오래걸린데 한 몫 한 것 같다.
그래도 여러번의 삽질이 힘은 들었지만 기억에도 오래남고, 얻는게 많아서 마냥 아까운 시간만은 아니었다. 앞으로 남은 기능들도 구현하기위해 열심히 공부해야겠다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[협업을 위한 컨벤션 ] Gitmoji 사용하기✨💄🧪🐛🔥 ]]></title>
            <link>https://velog.io/@heejin-k/%ED%98%91%EC%97%85%EC%9D%84-%EC%9C%84%ED%95%9C-%EC%BB%A8%EB%B2%A4%EC%85%98-Gitmoji-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@heejin-k/%ED%98%91%EC%97%85%EC%9D%84-%EC%9C%84%ED%95%9C-%EC%BB%A8%EB%B2%A4%EC%85%98-Gitmoji-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 23 Jun 2022 07:38:44 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/heejin-k/post/95225393-2460-4859-b3ff-8e90b1c3dd2d/image.png" alt=""></p>
<h1 id="gitmoji-😆">Gitmoji 😆</h1>
<p>협업을 시작하다보니 커밋메세지와 컨벤션이 가장 고민됐다.
그래서 오늘은 깃모지 컨벤션과 깃모지를 쉽게 사용하는 방법을 소개해보려한다.
이전에 flex &amp; grid 오픈소스 프로젝트때 gitmoji를 활용한 컨벤션을 채택하여 사용해본 적이 있었는데 장단점이 확 나눴었다.</p>
<h3 id="장점">장점</h3>
<ul>
<li>어떤 커밋을했는지 이모지로 한눈에 확인 가능하다. 글자를 읽을 필요없음!</li>
<li>그래서 깔끔하고 보기좋다</li>
<li>깃모지의 경우 정해진 양식(?)이 공통이다보니 깃모지를 사용하는 사람이라면 누구나 어떤 커밋인지 파악 가능!</li>
<li>내 커밋 기록이 귀여워진다 🐛✨ 그래서 커밋하고싶어짐.</li>
</ul>
<h3 id="단점">단점</h3>
<ul>
<li>이모지를 찾기가 힘들다...</li>
</ul>
<p>이렇게 있었는데 저 단점때문에 평소에는 거의 사용하지않았다. 
협업을 위해 컨벤션을 찾아보다 이 단점을 커버해줄 익스텐션과, 프로그램(?)설치방법을 알아냈다.
앗 그럼 깃모지,, 단점이 없는 친구자나?</p>
<h2 id="vsc-gitmoji-extention">VSC Gitmoji extention</h2>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/918b3f7d-34a7-4576-ae88-0fa768f24617/image.png" alt=""></p>
<p>이렇게 귀여운 익스텐션이 존재했단 사실..! 그것도 모르고 플렉스그리드 커밋할때 맨날 이모티콘 복붙했다 😂
미리미리 좀 알아볼걸 ~! 
<img src="https://velog.velcdn.com/images/heejin-k/post/0db31b5c-ef2c-4ce0-9b64-1562e48ab734/image.png" alt=""></p>
<p>위 익스텐션을 설치해주면 요렇게 VSC에서 커밋을 해줄때 이모티콘 모양이 생겨난다.
<img src="https://velog.velcdn.com/images/heejin-k/post/9c39d444-6149-44d1-acb1-eda415600bc6/image.png" alt="">
이모티콘을 누르면 이렇게 깃모지들이 쭉 나오는데 이중에서 원하는 이모지를 검색하여 사용이 가능하다!!
뒤에 관련 내용을 넣고 커밋을하면
<img src="https://velog.velcdn.com/images/heejin-k/post/546598a9-b313-45bc-942f-f75ec8457a23/image.png" alt="">
이렇게 커밋이 완료되는걸 볼 수 있다.
매번 이모지 복붙하기가 넘 귀찮았는데 이런 익스텐션이 있었다니....</p>
<p>하지만! 익스텐션은 GUI 전용 인 것 같았다. 나는 CLI로 커밋메세지를 작성하는데 다른방법은 없을까???</p>
<h2 id="cli-with-gitmoji">CLI with Gitmoji</h2>
<p>먼저 gitmoji를 설치해주어야 하는데, 그전에 node.js가 설치되어있는지 꼭 확인하기!! node.js가 설치되어있지않다면, node.js먼저 설치하자!</p>
<ul>
<li><code>brew install gitmoji</code> : mac</li>
<li><code>npm i -g gitmoji-cli</code> : window</li>
</ul>
<p>위의 명령어를 터미널에 입력하여 깃모지를 설치해준다. </p>
<p>설치 후 <code>gitmoji -help</code> 명령어를 입력하면, 아래와 같이 깃모지 사용방법이 나온다.
<img src="https://velog.velcdn.com/images/heejin-k/post/cb1ed056-93ed-4fab-ada9-5e7f2deb4b94/image.png" alt=""></p>
<p>깃모지 사용방법은 평소 커밋할때 사용하던 <code>git commit -m [메세지]</code> 대신 <code>gitmoji -c</code> 를 입력하면 끝! 간단하죠?</p>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/883dcc81-772d-426b-9344-20e7ca4f2c8a/image.png" alt="">
이렇게 <code>gitmoji -c</code> 명령어를 입력하면, 깃모지들을 화살표 방향키로 선택할 수도 있고 </p>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/c3e94473-0008-463a-94fc-f3a23ac3df22/image.png" alt=""></p>
<p>이렇게 검색을통해 알맞은 깃모지를 선택할 수도 있다.</p>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/1e88148b-0dd5-4b81-a5b4-1528b44b27a5/image.png" alt="">
깃모지를 선택후 엔터를 누르면 이렇게 타이틀을 입력할 수 있고, 
<img src="https://velog.velcdn.com/images/heejin-k/post/8cb8aa37-fede-44dc-87f5-aa194a39c947/image.png" alt=""> 자세한 내용을 입력하면 깃모지로 커밋 끝! </p>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/79ce2671-e292-494a-a55e-6a7a32758ab9/image.png" alt=""></p>
<p>그대로 푸쉬하면 이렇게 보기좋은 커밋메세지 완성!</p>
<p><a href="https://gitmoji.dev/">https://gitmoji.dev/</a>
위 사이트에 들어가면 다양한 깃모지들을 구경할 수 있다.
깃모지가 생각보다 양이 많다보니 팀에서 쓸 것만 간추려도 좋을 것 같다. </p>
<h2 id="gitmoji-컨벤션-예시">Gitmoji 컨벤션 예시</h2>
<p>💄  :lipstick: <strong>UI, 스타일 관련 파일 추가 및 수정</strong>
✨  :sparkles: <strong>새로운 기능 추가, 구현</strong>
💬  :speech_balloon: <strong>텍스트 또는 리터럴 추가 및 수정</strong>
🍱  :bento: <strong>asset 파일(이미지, 아이콘 등) 추가</strong>
📝  :memo: <strong>문서 파일 추가 및 수정</strong>
♿️  :wheelchair: <strong>웹 접근성 향상을 위한 코드 추가 및 수정</strong></p>
<hr>
<p>✏️  :pencil2: <strong>단순 오타 수정</strong>
🐛  :bug: <strong>버그 수정</strong>
🩹  :adhesive_bandage: <strong>단순한, critical하지 않은 이슈 수정</strong>
🚚  :truck: <strong>파일, 경로, route를 옮기거나 이름 변경</strong>
♻️  :recycle: <strong>코드 리팩토링</strong>
🔥  :fire: <strong>삭제(파일, 코드)</strong>
🙈  :see_no_evil: <strong>gitignore 추가 및 수정</strong></p>
<blockquote>
<ul>
<li><strong>깃모지 + 한글 커밋 메시지</strong> 형식으로 작성</li>
</ul>
</blockquote>
<ul>
<li>깃모지는 이모지를 직접 사용하거나, <code>:이모지이름:</code>의 형태로 사용할 수 있음</li>
<li><code>:이모지이름:</code>을 직접 사용하면 실수할 수 있으므로 이모지를 직접 넣거나 vscode의 <strong>gitmoji extension</strong>을 사용하는 것을 권장</li>
<li>예시 
git commit -m &quot;:sparkles: 검색 기능 추가&quot;</li>
</ul>
<p>이렇게 컨벤션을 정해두고 익스텐션 및 깃모지 프로그램을 이용해 커밋하니 넘 편하고 직관적이고 깔끔해서 자주 사용할 예정이다 👍🏻👍🏻</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[협업 시작 전 테스트로 발견한 주의 점 기록]]></title>
            <link>https://velog.io/@heejin-k/%ED%98%91%EC%97%85-%EC%8B%9C%EC%9E%91-%EC%A0%84-%ED%85%8C%EC%8A%A4%ED%8A%B8%EB%A1%9C-%EB%B0%9C%EA%B2%AC%ED%95%9C-%EC%A3%BC%EC%9D%98-%EC%A0%90-%EA%B8%B0%EB%A1%9D</link>
            <guid>https://velog.io/@heejin-k/%ED%98%91%EC%97%85-%EC%8B%9C%EC%9E%91-%EC%A0%84-%ED%85%8C%EC%8A%A4%ED%8A%B8%EB%A1%9C-%EB%B0%9C%EA%B2%AC%ED%95%9C-%EC%A3%BC%EC%9D%98-%EC%A0%90-%EA%B8%B0%EB%A1%9D</guid>
            <pubDate>Wed, 22 Jun 2022 17:00:08 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/heejin-k/post/9c63137b-d052-4884-9d8c-e63844ef97a4/image.png" alt="">
다들 제대로된 협업, 프젝이 처음이다보니 시작전에 git flow를 연습용 레포에다 이것 저것 연습해보고 시작하기로 하였다. </p>
<blockquote>
<p>🤍 테스트의 목표 :</p>
</blockquote>
<ul>
<li>프젝 진행도중 발생할 수 있는 문제점을 미리 캐치함으로써 우리만의 규칙도 세우고, 협업시 발생 할 문제점을 줄일 수 있다.</li>
<li>모든 팀원들이 git flow와 git을 통한 협업에 익숙해지기!</li>
</ul>
<h2 id="test-history">&lt; TEST HISTORY &gt;</h2>
<h3 id="1-test1새로운-브랜치에서-add--commit까지-완료해야-develop에서-수정내용이-보이지-않는다">1. test1(새로운 브랜치)에서 add + commit까지 완료해야 develop에서 수정내용이 보이지 않는다.</h3>
<p>그냥 수정만 한 경우 develop으로 이동하여도 test1의 내용이 남아있음 </p>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/bbf93fd4-b41c-4374-a473-c22c7142c8d3/image.png" alt=""></p>
<h3 id="2-develop에서-test1브랜치와-test2브랜치에서-각각-수정-후-test1만-커밋하였을-때-변화">2. develop에서 test1브랜치와 test2브랜치에서 각각 수정 후 test1만 커밋하였을 때 변화</h3>
<p>test2브랜치에서 희진 테스트 2파일을 추가하고 내용 추가 
<img src="https://velog.velcdn.com/images/heejin-k/post/17a138f7-3dcf-48d5-a774-941f508d8df6/image.png" alt=""></p>
<p>test1 브랜치 돌아와서 기존내용 밑에 내용을 추가한다. ( 이때 test2브랜치 에서 커밋을 안하고 넘어왔기 때문에 test2파일은 남아있다..!)
<img src="https://velog.velcdn.com/images/heejin-k/post/cf7e4319-bf6d-4d4e-b402-c9a68e054b3b/image.png" alt=""></p>
<p>여기서 test1 브랜치만 커밋해보았다.
<img src="https://velog.velcdn.com/images/heejin-k/post/2a41c882-ef1a-4601-b196-350b683c8e9d/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/80801400-ce81-4880-98fa-56475286ccf7/image.png" alt="">
test1만 커밋 푸쉬했는데 test2 브랜치에서 수정한 내용까지 같이 올라가있다. </p>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/fedbaa38-2fa9-4a19-abef-4b5616901e4f/image.png" alt=""></p>
<p>브랜치는 test2는 올라가지 않고, test1만 올라간 걸 볼 수 있다.</p>
<blockquote>
<p>❗결론 : 기존브랜치에서 수정하다가 커밋안하고 다른 브랜치로 넘어가서 커밋하게되면 꼬일 수 있다!! 
다른 브랜치에서 수정 및 커밋할 일이 생기면 무조건 커밋 or 임시저장 (stash) 후 이동하기 </p>
</blockquote>
<h3 id="3-merge">3. merge</h3>
<p>수업시간에 merge가 나왔는데, 우리는 지금까지 merge를 쓰고있지 않아서 한번 써보고싶어서 냅다 테스트해보기</p>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/6bc5b50d-e5f0-4e71-94f2-e403742d6447/image.png" alt="">
merge/test 브랜치를 만들어서 내용을 추가해주고
<img src="https://velog.velcdn.com/images/heejin-k/post/a7817e67-9b6b-4afa-b769-ceabdd151103/image.png" alt="">
커밋!!</p>
<p>develop 브랜치에 다시 돌아와서 git merge merge/test 를 해주었따.</p>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/5ad1613c-4497-463f-a65c-1d9f98b48ff9/image.png" alt=""></p>
<p>merge/test에서 커밋한게 업로드 완료 </p>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/3350d884-b9c0-4c0f-b1bb-9d83fbf425b2/image.png" alt=""></p>
<p>브랜치도 안남고 풀리퀘해란 안내도 안뜨고 그냥 깔끔하게 develop에 merge된다! </p>
<p>우린 풀리퀘를 할거니까 그냥 기존의 branch를 push하는 방법이 나을거 같다!! 나중에 개발이 다 끝나고 develop을 main에 merge할 경우 사용하면 좋을듯하다 </p>
<h3 id="4-브랜치-삭제">4. 브랜치 삭제</h3>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/7b36c8db-c9e5-4bfb-bc3e-1b9fa743363c/image.png" alt=""> push를 하고, merge까지 성공적으로 이루어져서 브랜치를 삭제하려고했는데 이런 오류가 뜬다. 아직 브랜치가 완전히 머지되지않았다는 오류인데 삭제를 하기위해서는 강제삭제인 -D를 사용하면된다. </p>
<p>하지만 강제는 뭐든 최후의 수단으로 사용하는것,,!</p>
<p>이렇게 오류가 나타나는 이유는 현재 develop브랜치에는 내가 merge한 브랜치가 합쳐지지않아서 나타나므로 pull을 한번 받아준 후 branch를 삭제하면 바로 삭제가 된다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[제주 웹 컨퍼런스 참여 및 연사 후기]]></title>
            <link>https://velog.io/@heejin-k/%EC%A0%9C%EC%A3%BC-%EC%9B%B9%EC%BB%A8%ED%8D%BC%EB%9F%B0%EC%8A%A4-%EC%B0%B8%EC%97%AC-%EB%B0%8F-%EC%97%B0%EC%82%AC-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@heejin-k/%EC%A0%9C%EC%A3%BC-%EC%9B%B9%EC%BB%A8%ED%8D%BC%EB%9F%B0%EC%8A%A4-%EC%B0%B8%EC%97%AC-%EB%B0%8F-%EC%97%B0%EC%82%AC-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Mon, 20 Jun 2022 19:58:18 GMT</pubDate>
            <description><![CDATA[<p><a href="https://jejuweb.kr/">https://jejuweb.kr/</a></p>
<p>6월초에 열린 제주 웹 컨퍼런스에 아주 좋은 기회로 참여하게 되어 참여후기 및 연사후기를 가져와봤어요 :)
이것 저것 하다보니 후기가 넘 늦어졌네요 ㅎㅎ </p>
<p>6월 2일부터 4일까지 총 3일동안 진행되었으며 저는 1일차는 참가자로, 2일차는 연사자로 참여하게 되었습니다!
생애 첫 연사다보니 긴장도 많이 되고, 연사준비한다고 너무 정신이 없어서 행사 참여에 집중하지 못한 것 같아 너무 아쉬웠어요. 좋은 세션들이 참 많았는데 다 듣지못해 아쉽습니다 흑흑 
그래도 너무 좋은 경험이었습니다! </p>
<h2 id="웹-컨퍼런스-1일차">웹 컨퍼런스 1일차</h2>
<p>1일차에는 총 3개의 세션에 참여했습니다 </p>
<ul>
<li><p>Web3를 위한 인프라
<img src="https://velog.velcdn.com/images/heejin-k/post/4ea858af-2b91-4d5b-8f7e-3a0084f233d4/image.png" alt="">Web3가 무엇인지 전혀 알지 못하는 상태로 참여하게 되었는데,, 이게 가장 큰 불찰이었던 것 같습니다 ㅠㅠ 주변에서 Web3 이야기를 많이 들어왔는데 도대체 뭐길래 Web3, Web3 하는거지!? 라는 생각으로 듣게 되었는데 Web3에 대한 기초지식이 없다보니 이해하기가 다소 어려웠습니다. 블록체인, 암호화폐 등과 관련된 이야기가 주 내용이었으며 간간히 관련 코드도 보여주시고, 투표에서 사용하였을 경우의 장단점 등 .. 좋은 내용은 많았는데 제가 다 흡수하지 못해서 아쉬웠습니다ㅠㅠ 아직 Web3에 대해 잘 알지 못해 가볍게 듣게 되었지만 추후 다시 깊게 공부해봐야겠단 생각이 들었습니다!</p>
</li>
<li><p>개발자 = 콘텐츠 크리에이터
<img src="https://velog.velcdn.com/images/heejin-k/post/b6e3672d-5c3f-4dfc-8bb6-9182e1d5eee7/image.png" alt="">개발자는 모두 콘텐츠 크리에이터가 되어야한다 라는 주제로 연사를 해주셨는데 정말 흥미있게 들은 세션이었습니다! 중간에 퀴즈도 맞춰 직접 주니어 개발자들과 집필하신 IT용어 책도 선물받아 영광이었습니다.
20년차 개발자에서 끝이아닌, 작가, 번역가, 유튜버, 강사, 사업가 , 투자자 등,, 여러가지 분야로 확장하며 도전하시는 모습이 정말 대단하신 것 같아요. 특히 유튜브나, 글쓰기와 같이 기록으로 남기는 것을 중요시 하셨는데 글쓰기와 코딩이 유사점을 짚어주시며 글쓰기를 강조해주신 것이 가장 기억에 남습니다. 
목차를 정리하는것은 함수와 비슷하고, 글을쓰고 퇴고하는것은 리팩토링과 유사하며 지식을 글로 정리하는 것은 사고체계가 알고리즘화 되는데 많은 도움을 준다고 하셨는데 다시한번 글쓰기의 중요성을 깨닫게 되었습니다. 좋은 개발자가 되기위해 글쓰기를 생활화하며 내가 알고있는 지식을 남이 이해할 수 있는언어로 잘 정리해나가는 습관을 기르도록 노력해야겠습니다! </p>
</li>
<li><p>비 온 뒤 단단해지는 땅처럼 : 신입 개발자들의 학습과 성장 이야기
<img src="https://velog.velcdn.com/images/heejin-k/post/2f91bc17-12f6-484b-b8f8-b424da8917a7/image.png" alt="">저와 같은 과정을 공부하시는 동기분들의 연사라 더욱 집중해서 들었던 것 같습니다! 저희 동기들 중 유일하게 첫날 연사인데다, 다음날 바로 제 연사가 있어 덩달아 저까지 아침부터 긴장됐네요,, 동기분들 긴장하실까봐 맨 앞자리에 앉아서 들었는데 많은 도움이 됐다고 해주셔서 넘 뿌듯했어요! 주니어 개발자로 성장하기까지 어려웠던 점과 조언들을 각자의 경험에 맞게 토크콘서트 형식으로 잘 풀어나가주셔서 부담없이 듣기 좋았습니다. 함께 성장한 동료들이라 그런지 연사를 들으며 지난 날들이 스쳐지나가더라구요 새삼 우리 참 많이 성장했다! 느꼈습니다. 다들 첫 연사인데도 너무너무 잘 마무리하셔서 멋졌어요 👍🏻</p>
</li>
</ul>
<p>이후에도 듣고싶은 세션과 채용설명회가 있었으나 연사팀과 연사회의가 있어 참여하지 못해 아쉬웠습니다 다들 좋은 연사 감사드립니다!👏🏻</p>
<h2 id="웹-컨퍼런스-2일차-연사-당일">웹 컨퍼런스 2일차 연사 당일</h2>
<p>아침부터 정말 분주했던 하루였던 것 같아요. 컨퍼런스가 시작하기 전부터 만나 계속해서 리허셜을 진행하고, 유튜브 업로드용 영상촬영 하느라 시간이 정말 빨리 흘렀습니다.
<img src="https://velog.velcdn.com/images/heejin-k/post/f0d92195-9658-4782-98f9-ad76ceeb900a/image.jpg" alt=""></p>
<h3 id="언택트-시대에-주니어-개발자들이-함께-성장하는-방법">언택트 시대에 주니어 개발자들이 함께 성장하는 방법</h3>
<p>저희는 &#39;언택트 시대에 주니어 개발자들이 함께 성장하는 방법&#39; 이라는 주제로 김창준님의 함께자라기 책을 기반으로 주니어개발자들이 어떻게 &#39;잘&#39;성장하고 있는지, 우리가 &#39;잘&#39;성장하고 있는 비법은 무엇인지 저희의 경험담을 이야기하는 토크 콘서트 형식의 연사를 진행하게 되었습니다. 그 중 저는 이수빈님과 함께 질문으로 성장하기 파트를 담당하게 되었어요</p>
<p>저희의 성장을 이야기하는 연사였지만, 저는 이 연사로인해 한걸음 더 성장하게 된 것 같아요. 
한달동안 매주 2번의 정기모임과 더불어 비정기적으로 수빈님과 새벽까지 호흡을 맞추고, 내용을 가다듬는 등 정말 많은 시간을 쏟았는데 날이 갈 수록 모든 팀원들이 연사내용도 탄탄해지고 성장하고 있단 걸 느꼈습니다.</p>
<p>저는 특히 피드백의 중요성을 다시 한 번 뼈저리게 느끼게 되었어요.
정해진 시간안에 우리가 이렇게 잘 성장하고있다는 많은 내용을 전달하고싶어 처음 원고(?)는 정말 들쑥날쑥 했는데 피드백을 바탕으로 &#39;나&#39;의 이야기를 한다는 생각으로 작성하다보니 필요한 것과 불필요한 내용이 정돈되며 한층 깔끔해지더라구요.
매번하는 팀원들과의 피드백과 영웅님과 희승님의 피드백이 정말 많은 도움이 되었습니다. 피드백들로 인해 연사내용과 흐름이 정말 좋은쪽으로 많이 변화하게 되었고, 좋은 연사를 할 수 있게되었던 것 같습니다.
좋은 피드백을 주신 우리 팀원들과 영웅님, 희승님 정말 감사드립니다 💗
<img src="https://velog.velcdn.com/images/heejin-k/post/02bd1864-7289-4893-8c30-15ef69d1fc48/image.jpg" alt="">첫 연사다보니 &#39;실패하더라도 경험을 쌓고오자&#39; 라는 생각이었는데 생각보다 너무 잘 마무리되었고, 이런 나의 작은 경험을 주의깊게 들어주시는 분들이 있다는게 너무 감사했습니다. 
멋사 동료분들도 저희 세션에 많이와주셔서 동료분들을 쳐다보며 이야기해야겠다! 생각했는데, 다들 집중해서 들어주시는 모습에 오히려 생전 처음보는 분들과 계속 아이컨택하며 이야기한게 가장 기억에 남아요. 저의 이야기를 듣기위해 시간을 할애해주시고, 이렇게 집중해서 들어주시다니 너무 감사하고 소중한 경험이었습니다.</p>
<p>연사를 준비하던 기간과 연사 당일의 그 경험 모두 평생 잊지 못 할 것 같습니다! </p>
<p>저희 연사가 궁금하시다면 아래링크에 저희가 유튜브용으로 따로 녹화한 영상이 올라와 있으니 시청해보시면 좋을 것 같습니다 🙂</p>
<p><a href="https://www.youtube.com/watch?v=w8FsCeoK19U&amp;t=10s">https://www.youtube.com/watch?v=w8FsCeoK19U&amp;t=10s</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[인프런 심야FE 네트워킹 파티 🎉]]></title>
            <link>https://velog.io/@heejin-k/%EC%9D%B8%ED%94%84%EB%9F%B0-%EC%8B%AC%EC%95%BCFE-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%82%B9-%ED%8C%8C%ED%8B%B0-%EC%B0%B8%EC%84%9D-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@heejin-k/%EC%9D%B8%ED%94%84%EB%9F%B0-%EC%8B%AC%EC%95%BCFE-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%82%B9-%ED%8C%8C%ED%8B%B0-%EC%B0%B8%EC%84%9D-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Mon, 20 Jun 2022 19:57:52 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/heejin-k/post/d42022a7-2165-4cd1-8417-babf7530eef7/image.png" alt=""></p>
<p>좋은기회에 6월 17일날 열린 인프런 심야 FE 네트워킹 파티에 참여하고 오게 되었습니다! 프론트엔드 개발자들만의 네트워킹파티라니,, 이름부터 너무 설렜어요 특히 취준생의 입장에서 현직 개발자들과 소통할 기회가 전혀 없었는데 너무 좋은 기회였던 것 같습니다!
이번 심야 네트워킹 파티는 약 700명의 지원자중에 10명을 늘려, 60명이 당첨되었다고 해요🤍 어마어마한 경쟁률이라 더욱 소중한 기회였던 것 같아요!</p>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/046ecbaf-89fa-40ae-9df5-d104321021e5/image.jpg" alt=""></p>
<p>저녁시간대라 이렇게 피자와 맥주 or 콜라도 준비해주셨어요 넘 센스쟁이,, 간단한 네트워킹 시간이후, 인프랩 프론트엔드 개발자 조성륜님과, 카카오 엔터테이먼트 프론트엔드 리드 조성호님의 연사를 듣게 되었습니다! </p>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/380568a6-02d3-455e-8884-448a2d3a4c39/image.png" alt="">
인프런의 랠릿이 어떻게 만들어졌고, 어떤 실패과정을 겪으며 어떻게 해결해 나가셨는지에 대한 정말 날 것 그대로(?)의 이야기를 공유해주셨어요! 아직 공부중인 단계라 제가 잘 모르는 내용들도 있었지만 이야기를 재치있게 잘해주셔서 재밌게 듣기 좋았습니다 ㅎㅎ 자세한 기술적 내용은 인프콘에서 다룬다고하니 관심있으신분들은 인프콘 꼭 참여하시면 좋을 것 같아요! 👍🏻</p>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/8f111b58-3160-4066-856c-7dbd1c71aa76/image.png" alt=""></p>
<p>아무래도 저는 취준생이다보니 김성호님의 &#39;뽑히는 주니어의 조건&#39;세션에 주의깊게 듣게되었어요! 정말 취준생에게 도움되는, 이직하는 분들에게도 도움이 될만한 좋은 이야기들을 많이 해주셨는데 </p>
<p>가장 기억에 남는것은</p>
<ul>
<li>이력서에는 성과를 담는다. 성과가 없다면 사이드 프로젝트에서 만든다</li>
<li>이력서는 미리미리 평소에 업데이트 할 것 
  주기적으로 업데이트하면 본인을 반성하는 시간을 가지게되어 동기부여 및 목표설정에 도움이 된다.</li>
<li>프론트엔드 개발자는 백엔드 역량을 키우는 것도 좋지만, UX,UI 등 디자인적 역량을 키우는것이 매우 좋다</li>
</ul>
<p>이외에도 사전과제나 면접에서 어필하기 좋은 점 등 주니어 개발자 입장에서 정말 좋은 정보들을 많이 알려주셔서 보람찬 내용이었습니다 ❣
특히 디자인에 관심이 많아 디자인적 역량을 키우고싶었는데 강점으로 어필하기위해 UX,UI를 더욱 자세히 공부하여 프로젝트에 적용시켜봐야겠습니다 ㅎㅎ</p>
<p><img src="https://velog.velcdn.com/images/heejin-k/post/cee3d373-7aa2-4fe4-bfe6-9176f8438be8/image.jpg" alt=""></p>
<p>연사도 모두 끝나고, 30분의 네트워킹 시간이 끝난 후 7시~9시로 계획되어 있던 본 행사는 막을 내렸어요. 아쉬운분들을 위해 9시부터 11시까지 추가 네트워킹 시간이 있어 좋았어요!
이 시간에는 미리 정해주신 조 말고도 자유롭게 네트워킹이 가능해서 자리를 옮겨 다양한 분들과 대화를 나누게 되었습니다 </p>
<p>여러 개발자분들과 이야기해보며 블로그의 중요성을 다시한번 느끼게 되었습니다..
블로그 작성이 아무래도 시간을 많이 쏟다보니까 개발보다 좀 소홀하게 되었는데 개발자에게 문서화능력은 정말 너무나 중요한 것 같아요!! 저는 블로그 작성하는데 시간이 많이걸려 고민이었는데 현직 개발자분께서 본인도 블로그쓰는데 시간투자를 많이하시며, 그래도 써놓으면 다 자산이고 블로그를 통해 이직제안이 많이와서 쓰는걸 추천하시더라구요 ㅎㅎ
앞으로도 꾸준히 블로그 쓰기 노력해야겠습니다 계속 쓰다보면 글쓰는 속도도 빨라지겠죠..!?</p>
<p>리액트나 자바스크립트의 경우 이론은 아는데 구현이 계속 막히고 생각대로 되지않아 시간이 오래걸린다고 하니 페어프로그래밍을 추천해주셔서 동기분들과 한번 페어프로그래밍에도 도전해보려구요! </p>
<p>정말 다양한 개발자분들을 많이 만나고 온 것 같아 뜻깊은 네트워킹 데이였던 것 같습니다 ㅎㅎ
연사때 만든 명함이 있어, 명함을 많이 뿌리고 오고싶었는데 3장밖에 못드려서 아쉬웠습니다 흑 
다음번에도 네트워킹데이가 열린다면 꼭 다시 참여하고 싶어요~! 💜</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JS 100제 알고리즘 - 13 ~ 16번 문제 풀이 ]]></title>
            <link>https://velog.io/@heejin-k/JS-100%EC%A0%9C-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-13-14-15-16%EB%B2%88-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4</link>
            <guid>https://velog.io/@heejin-k/JS-100%EC%A0%9C-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-13-14-15-16%EB%B2%88-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4</guid>
            <pubDate>Tue, 14 Jun 2022 16:19:19 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>제코베 JS 100제 알고리즘 문제를 풀며 정리중입니다</p>
</blockquote>
<h2 id="문제13--몇-번째-행성인가요">문제13 : 몇 번째 행성인가요?</h2>
<blockquote>
<p>우리 태양계를 이루고 있는 행성은 <strong>수성, 금성, 지구, 화성, 목성, 토성, 천왕성, 해왕성</strong>으로 총 8개 입니다. 저희는 우리 태양계의 n번째 행성이 무엇인지 알고 싶습니다.</p>
</blockquote>
<p>입력으로 행성의 순서를 나타내는 숫자 n이 입력됩니다. 
출력으로 그 순서에 해당하는 행성의 이름을 출력해 주세요.</p>
<blockquote>
</blockquote>
<p>예를들어 1이 입력되면, 첫번째 행성인 수성이 출력됩니다.</p>
<pre><code class="language-js">const planet = [&quot;수성&quot;, &quot;금성&quot;, &quot;지구&quot;, &quot;화성&quot;, &quot;목성&quot;, &quot;토성&quot;, &quot;천왕성&quot;, &quot;혜왕성&quot;]
function planetSelect(num) {
    console.log(planet[num - 1])
    if (num &gt; planet.length) {
        console.log(`태양계의 ${num}번째 행성은 없습니다.`)
    }
}

planetSelect(1)</code></pre>
<p>처음에는 if문을 추가하지 않았는데, 8 이상의 값이 나오면 undefind가 출력되기 때문에 예외처리를 해줘야 겠다고 생각하여 추가하였다. </p>
<h2 id="문제14--3의-배수-인가요">문제14 : 3의 배수 인가요?</h2>
<blockquote>
<p>영희는 친구와 게임을 하고 있습니다. 서로 돌아가며 랜덤으로 숫자를 하나 말하고 그게 3의 배수이면 박수를 치고 아니면 그 숫자를 그대로 말하는 게임입니다.</p>
</blockquote>
<p>입력으로 랜덤한 숫자 n이 주어집니다.</p>
<blockquote>
</blockquote>
<p>만약 그 수가 <strong>3의 배수라면 &#39;짝&#39;이라는 글자를, 3의 배수가 아니라면 n을 그대로 출력</strong>해 주세요.</p>
<pre><code class="language-js">function play(num){
    if (num % 3 === 0 &amp;&amp; num &gt; 0){
        console.log(&quot;짝👏🏻&quot;)
    }
    if (num &lt;= 0) {
        console.log(&quot;양수로 입력하세요&quot;)

    } else {
        console.log(num)
    }
}

play(3)</code></pre>
<p>이렇게 함수를 통해서 직접 함수에 입력값을 적어넣도록 구현하였는데, 아래 15번 문제를 풀고나니 입력값을 받는 문제들을 promts를 활용하여 해결해볼 수 있을 것 같다.</p>
<h2 id="문제15--자기소개">문제15 : 자기소개</h2>
<blockquote>
<p>신학기가 시작되고, 아이들이 돌아가면서 자기소개를 하기로 했습니다.</p>
</blockquote>
<p>만약 입력으로 <code>김다정</code>이라는 이름이 주어지면 &quot;안녕하세요. 저는 김다정입니다.&quot;라고 출력하게 
해주세요.</p>
<pre><code class="language-js">function sayHello(name) {
    console.log(`안녕하세요. 저는 ${name}입니다.`)
}

sayHello(&quot;김희진&quot;)</code></pre>
<p>이렇게 작성하고나니, 뭔가 이상했다. 일단 sayHello에 &quot;&quot;없이 적으면 입력이 안되는점, 함수를 사용한게 아닌 직접 입력받으려면 어떻게 해야할까 생각하다 prompt() 함수를 통해 구현가능했다! alert창을 통해서 입력값을 받는다는 생각은 못했는데 이런 방법도 있었구낭 </p>
<pre><code class="language-js">let 이름 = prompt(&#39;이름을 입력하세요&#39;);
console.log(`안녕하세요. 저는 ${이름}입니다.`);</code></pre>
<h2 id="문제16--로꾸거">문제16 : 로꾸거</h2>
<blockquote>
<p>문장이 입력되면 거꾸로 출력하는 프로그램을 만들어 봅시다.</p>
</blockquote>
<p>입력 : 거꾸로
출력 : 로꾸거</p>
<ul>
<li>1번째 풀이<pre><code class="language-js">let input =  prompt(&quot;문장을 입력하세요&quot;)
let inputArray = [...input]
let replaceInputArray = []
</code></pre>
</li>
</ul>
<p>for (let i = 0; i &lt; input.length ; i++){
    replaceInputArray.push(inputArray.pop())
}</p>
<p>let result = replaceInputArray.join(&quot;&quot;)
console.log(result)</p>
<pre><code>
변수선언을 너무 많이 한 것 같다 좀더 간결하게 할 수는 없을까..? 하다가 이전에 배운 reverse 함수가 떠올랐다. JS 배운지 얼마안됐는데 배웠던 함수들을 계속 까먹어서 비효율적인 코드가 완성됐다 ㅠㅠ 다시 정주행 해야지. 그래도 알고리즘을 풀며 다시 기억나고있어 다행이당

- 2번째 풀이

```js
let inputArray2 =  [...prompt(&quot;문장을 입력하세요&quot;)]
let changeResult = inputArray2.reverse().join(&quot;&quot;)
console.log(changeResult)</code></pre><p>reverse함수를 사용하고, 처음 input과 inputArray도 한 코드로 묶어 더 간결한 코드를 작성하였다. </p>
<ul>
<li>3번째 풀이</li>
</ul>
<pre><code class="language-js">let Str = prompt(); console.log(`${Str.split(&#39;&#39;).reverse().join(&#39;&#39;)}`)</code></pre>
<p>이렇게 전개구문 없이 split로 하니까 더 깔끔한 것 같다!</p>
<h2 id="사용된-함수-총-정리">사용된 함수 총 정리</h2>
<h4 id="join">join()</h4>
<ul>
<li>배열을 문자열로 합치는 함수</li>
</ul>
<h4 id="split">split()</h4>
<ul>
<li>문자열을 분할하는 함수</li>
<li>string.split( separator, limit )
separator = 분할의 기준
limit = 최대 분할 개수 ( 값을 정하지 않으면 전체를 다 분할)</li>
</ul>
<h4 id="reverse">reverse()</h4>
<ul>
<li>배열을 거꾸로 뒤집는 함수</li>
</ul>
<h4 id="prompt">prompt()</h4>
<ul>
<li>문자열을 입력할 때 사용하는 함수</li>
<li>숫자를 입력 받아야 하는 경우는 문자열로 입력 받은 뒤 parsInt등을 이용하여 변환</li>
<li>첫번째 매개변수는 입력 창에서 띄워줄 메시지</li>
<li>두번째 매개변수는 입력 부분의 기본 값</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[React 설치부터 배포까지]]></title>
            <link>https://velog.io/@heejin-k/React-%EC%84%A4%EC%B9%98%EB%B6%80%ED%84%B0-%EB%B0%B0%ED%8F%AC%EA%B9%8C%EC%A7%80</link>
            <guid>https://velog.io/@heejin-k/React-%EC%84%A4%EC%B9%98%EB%B6%80%ED%84%B0-%EB%B0%B0%ED%8F%AC%EA%B9%8C%EC%A7%80</guid>
            <pubDate>Tue, 14 Jun 2022 14:17:44 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>나중에 내가 보려고 올리는 리액트 설치, 페이지세팅, 배포 방법!</p>
</blockquote>
<h2 id="react-설치부터-배포까지">React 설치부터 배포까지</h2>
<h3 id="설치">설치</h3>
<ul>
<li>react 설치 명령어 : npx create-react-app my-app</li>
</ul>
<h3 id="실행--초기-페이지-세팅">실행 &amp; 초기 페이지 세팅</h3>
<ul>
<li><p>react 실행 : npm start</p>
</li>
<li><p>index.html</p>
<pre><code class="language-jsx">  &lt;!DOCTYPE html&gt;
  &lt;html lang=&quot;ko&quot;&gt;

  &lt;head&gt;
    &lt;meta charset=&quot;utf-8&quot; /&gt;
    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot; /&gt;
    &lt;title&gt;React App&lt;/title&gt;
  &lt;/head&gt;

  &lt;body&gt;
    &lt;p&gt;test1&lt;/p&gt;
    &lt;div id=&quot;root&quot;&gt;&lt;/div&gt;
  &lt;/body&gt;

  &lt;/html&gt;</code></pre>
</li>
</ul>
<ul>
<li><p>App.js</p>
<pre><code class="language-jsx">  function App() {
    return (
      &lt;div className=&quot;App&quot;&gt;
        &lt;h1&gt;test2&lt;/h1&gt;
      &lt;/div&gt;
    );
  }

  export default App;</code></pre>
</li>
</ul>
<ul>
<li><p>index.js</p>
<pre><code class="language-jsx">  import React from &#39;react&#39;;
  // import ReactDOM from &#39;react-dom&#39;; //구버전
  import { createRoot } from &#39;react-dom/client&#39;;
  import App from &#39;./App&#39;;

  const container = document.getElementById(&#39;root&#39;);
  const root = createRoot(container);
  root.render(&lt;App tab=&quot;home&quot; /&gt;);</code></pre>
<blockquote>
<p> App.js가 메인페이지고, index.js가 index.html에 메인페이지를 보여지게 한다.</p>
</blockquote>
</li>
</ul>
<h3 id="배포">배포</h3>
<ol>
<li><p>수동 업로드</p>
<ul>
<li><p>github에 새 레파지토리를 만든후 page를 만들어 링크를 복사한다.</p>
</li>
<li><p>아래 코드를 <code>package.json</code>에 추가.</p>
<pre><code class="language-jsx">  {
      &quot;homepage&quot;:&quot;github page url&quot;,
      ...
  }</code></pre>
</li>
<li><p><code>npm run build</code>하면 build 폴더로 resource 파일들이 올라옴</p>
</li>
<li><p>build 안에 있는 파일을 그대로 github pages에 업로드한다.</p>
</li>
</ul>
</li>
</ol>
<ol start="2">
<li><p>자동 업로드</p>
<ul>
<li><p><code>npm run build</code></p>
</li>
<li><p><code>npm install gh-pages</code> 한 다음에 아래 코드를 <code>package.json</code>에 추가</p>
<pre><code class="language-jsx">  {
      &quot;homepage&quot;:&quot;github url&quot;,
      ...
      &quot;scripts&quot; : {
      ...
      &quot;deploy&quot; : &quot;gh-pages -d build&quot;
      }
  }</code></pre>
</li>
<li><p><code>npm run deploy - add</code></p>
</li>
<li><p>pages에서 gh-pages을 선택한다.</p>
</li>
</ul>
</li>
</ol>
]]></description>
        </item>
    </channel>
</rss>