<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>slight-snow.log</title>
        <link>https://velog.io/</link>
        <description>주니어 개발자의 기억을 위한 기록 :)</description>
        <lastBuildDate>Fri, 16 Feb 2024 00:52:44 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>slight-snow.log</title>
            <url>https://velog.velcdn.com/images/slight-snow/profile/bbb4192c-2df0-4404-847b-7bd7911609d2/social_profile.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. slight-snow.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/slight-snow" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[JavaScript] 숫자에 쉼표 넣기 & 빼기]]></title>
            <link>https://velog.io/@slight-snow/JavaScript-%EC%88%AB%EC%9E%90%EC%97%90-%EC%89%BC%ED%91%9C-%EB%84%A3%EA%B8%B0-%EB%B9%BC%EA%B8%B0</link>
            <guid>https://velog.io/@slight-snow/JavaScript-%EC%88%AB%EC%9E%90%EC%97%90-%EC%89%BC%ED%91%9C-%EB%84%A3%EA%B8%B0-%EB%B9%BC%EA%B8%B0</guid>
            <pubDate>Fri, 16 Feb 2024 00:52:44 GMT</pubDate>
            <description><![CDATA[<h4 id="■-number-→-string">■ Number → String</h4>
<p>예를 들어, 1234567을 &#39;1,234,567&#39;로 만들고 싶다면 <code>toLocaleString()</code> 메서드를 사용한다.</p>
<pre><code class="language-javascript">const before = 1234567;
const after = target.toLocaleString();

console.log(after); // &#39;1,234,567&#39;
console.log(typeof after); // string</code></pre>
<h4 id="■-string-→-number">■ String → Number</h4>
<p>예를 들어, &#39;1,234,567&#39;을 1234567로 만들고 싶다면, <code>replace()</code> 메서드를 사용하여
쉼표를 없애고 이어 붙여준 다음 <code>parseInt()</code> 메서드를 사용하여 정수로 만들어준다.</p>
<pre><code class="language-javascript">const before = &#39;1,234,567&#39;;
const after = parseInt(before.replace(/,/g, &#39;&#39;));

console.log(after); // 1234567
console.log(typeof after); // number</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React.js] GA4(Google Analytics) - ②API 호출하기]]></title>
            <link>https://velog.io/@slight-snow/React.js-GA4Google-Analytics-API-%ED%98%B8%EC%B6%9C%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@slight-snow/React.js-GA4Google-Analytics-API-%ED%98%B8%EC%B6%9C%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 24 Jan 2024 01:37:07 GMT</pubDate>
            <description><![CDATA[<p>지난 <a href="https://velog.io/@slight-snow/React.js-GA4Google-Analytics-4-%EC%97%B0%EB%8F%99%ED%95%98%EA%B8%B0" target="_blank">[React.js] GA4(Google Analytics 4) - ①연동하기</a>에서 웹사이트와 GA4를 연동해보았으니,
이번에는 웹사이트에서 GA4 API를 활용하여 데이터를 호출하는 방법을 구체적으로 알아볼 것이다!
<br/></p>
<hr>
<br/>

<h2 id="📌구글-애널리틱스google-analytics-api-호출-방법">📌구글 애널리틱스(Google Analytics) API 호출 방법</h2>
<p>여기서는 React.js의 프론트엔드 클라이언트에서 GA에 API를 호출하는 방법을 다룬다.</p>
<br/>


<h3 id="🧩-api-공식-문서">🧩 API 공식 문서</h3>
<blockquote>
<p>GA4 이전의 GA3까지는 <a href="https://developers.google.com/analytics/devguides/reporting/core/v4?hl=ko" target="_blank">Reporting API v4</a>에서 API 사용법을 다루었으나,
GA4 업데이트되면서 해당 API에 대한 지원이 중단되고, <a href="https://developers.google.com/analytics/devguides/reporting/data/v1?hl=ko" target="blank">Analytics Data API v1</a>로 대체되었다.</p>
</blockquote>
<p><strong>Data API v1</strong>에서 사용 가능한 다양한 메서드들과 사용 방법들을 확인할 수 있다.</p>
<p><span style="color:#AAA;">* 참고로 100% 한글화되지 않았기에, 종종 제목과 내용이 숫자로 되어있는 문서들이 있는데,<br/>&nbsp;&nbsp;해당 문서는 언어를 &#39;English&#39;로 바꿔서 영어로 확인해야한다는 단점이 있다.</span></p>
<p>영어가 부담스럽고, 읽어봐도 어떻게 써야하는지 잘 모르겠다면 당신은 잘 오다!
지금부터 한 조각씩 천천히 사용법을 알아볼 것이다.</p>
<br/>

<h3 id="🧩-기본-보고서-runreport">🧩 기본 보고서: runReport</h3>
<p>위의 <strong>Data API v1</strong> 공식문서에서 기본 보고서 메뉴를 클릭하면 제목도, 내용도 숫자로 나타난다.
아마 한국어로 번역하는 과정에서 오류가 발생했거나, 아직 미완성이거나... 그러한 이유인 듯하다.</p>
<p>번역은 되지 않았더라도, 공식문서를 확인해보면 눈여겨볼만한 내용은 다음과 같다.</p>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/0988fd39-2a8d-48d9-8c53-5db722ebc7e2/image.png" alt="runReport 01"></p>
<ul>
<li><code>runReport</code> 메서드는 <code>https://analyticsdata.googleapis.com/v1beta/properties/GA4_PROPERTY_ID:runReport</code>에
<code>HTTP POST</code> 요청을 보내서 사용할 수 있고, <code>GA4_PROPERTY_ID</code> 값이 필요하다.</li>
<li>보고서를 생성하기 위해서는 <code>dataRanges</code>, <code>dimensions</code>, <code>metrics</code> 항목이 필요하다.</li>
</ul>
<p>여기까지 봤을 때, <code>GA4_PROPERTY_ID</code>도 <code>dataRanges</code>, <code>dimensions</code>, <code>metrics</code>도 뭔진 모르겠지만 
React.js에서 <code>axios</code>를 통해 위의 URL로 <code>dataRanges</code>, <code>dimensions</code>, <code>metrics</code> 데이터가 포함된
<code>POST</code> 요청을 보내면 되겠다는 생각을 할 수 있어야 한다!</p>
<p>문서에서 다른 부분은 차치하더라도 딱 이 부분만큼은 반드시 숙지하도록 한다.</p>
<ul>
<li><code>GA4_PROPERTY_ID</code></li>
<li><code>dataRanges</code></li>
<li><code>dimensions</code></li>
<li><code>metrics</code></li>
</ul>
<br/>

<h3 id="🧩-ga4-query-explorer-dataranges-dimensions-metrics">🧩 GA4 Query Explorer (dataRanges, dimensions, metrics)</h3>
<p>저렇게 <code>POST</code> 요청을 보내면 어떤 응답이 돌아올까?
물론 공식문서에서도 확인할 수 있지만, 공식문서에서는 응답의 예시를 보여줄 뿐
내가 GA를 연동한 웹사이트에 관한 응답을 보여주진 않는다.
즉, 예시를 보더라도 딱히 와닿지 않을 수 있다.</p>
<p>하지만 <a href="https://ga-dev-tools.google/ga4/query-explorer/" target="_blank">GA4 Query Explorer</a>를 사용하면 내 웹사이트에 관한 요청과 응답을
확인할 수 있을 뿐만 아니라, 위의 <code>dataRanges</code>, <code>dimensions</code>, <code>metrics</code>를 어떻게 작성해야 하는지도
직관적으로 보여주고 복사 기능까지 있기 때문에 <code>POST</code> 요청에서의 <code>data</code> 부분은 거의 떠먹여주는 수준이다!</p>
<p><strong>GA4 Query Explorer</strong>에 접속하면 아래와 같이 화면이 나올텐데,
이전에 GA를 연동할 때 로그인해둔 계정으로 로그인하면 자동으로 속성을 호출한다.</p>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/f1776eb1-b18c-4bfc-adb6-e49aef35f5e4/image.png" alt="GA4 Query Explorer 01"></p>
<p>[Select property]의 &#39;account&#39;를 설정하면 &#39;property&#39;는 자동으로 설정된다.
&#39;start date&#39;와 &#39;end date&#39;는 데이터를 수집할 시작일과 마지막일이다.
<span style="color:#AAA;">* 하단에 적혀있듯, 꼭 YYYY-MM-DD와 같은 날짜 형식이 아니더라도
&nbsp;&nbsp;&nbsp;yesterday(어제) 혹은 today(오늘)와 같은 텍스트로도 설정이 가능하다.</span></p>
<p>&#39;metrics&#39;는 수집하고 싶은 구체적인 데이터들을 의미한다.
필자는 활성화 사용자 수, 조회수, 세션 등을 수집하고자 위와 같이 설정해두었다.</p>
<p>&#39;dimensions&#39;는 데이터들을 나눌 분기점을 의미한다.
예를 들어, <code>date</code>로 설정해두면 날짜별로 데이터들을 표기한다는 것이다.</p>
<p>그 외에는 추가적으로 설정할 수 있는 옵션들인데,
알아보고자 구글의 상세한 설명을 보기 위해 클릭했으나ㅡ...
<strong>Service Unavailable</strong>이 나타나며 아무런 설명도 나타나지 않는다.</p>
<img src="https://media.giphy.com/media/l3q2K5jinAlChoCLS/giphy.gif" style="border-radius:20px;" />

<p>하지만 필수 옵션도 아니니, 우선 없이 진행하도록 한다.</p>
<p>&#39;keep empty rows&#39;를 체크한 이유는 다른 블로그에서 GA를 활용할 때
이 부분이 활성화되어 있어야 그래프로 나타냈을 때 오류가 나타나지 않는다고 정보를 얻었기 때문이다.</p>
<p>단순히 데이터만 확인하고 싶고 그래프화하지 않을 것이라면 비활성화 해두어도 무방하다.</p>
<p>속성들에 대한 설정을 마치고 [MAKE REQUEST] 버튼을 누르면!</p>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/602c8685-fe3b-482e-bfa7-c028b59dbbcc/image.png" alt="GA4 Query Explorer 02"></p>
<p>짜잔~ 이렇게 요청할 때 사용되는 <code>data</code>의 JSON 형식도 확인 및 복사하여 활용할 수 있고,
요청에 따른 응답을 TABLE / JSON 형식으로 확인할 수도 있다!</p>
<ul>
<li><code>GA4_PROPERTY_ID</code></li>
<li>✅ <code>dataRanges</code> </li>
<li>✅ <code>dimensions</code></li>
<li>✅ <code>metrics</code></li>
</ul>
<p>한 번에 3개의 요구사항이 해결됐다 :0</p>
<p>여기까지 왔을 때, 코드의 형태는 다음과 같다.</p>
<pre><code class="language-javascript">// Example.js

import { useEffect } from &#39;react&#39;;
import axios from &#39;axios&#39;;

const Example = () =&gt; {
  useEffect(() =&gt; {
    axios
      .post(`https://analyticsdata.googleapis.com/v1beta/properties/${GA4_PROPERTY_ID}:runReport`,
            {
             &quot;dimensions&quot;: [{ &quot;name&quot;:&quot;date&quot; }],
             &quot;metrics&quot;: [{ &quot;name&quot;:&quot;activeUsers&quot; }, { &quot;name&quot;:&quot;screenPageViews&quot; }, { &quot;name&quot;:&quot;sessions&quot; }],
             &quot;dateRanges&quot;: [{ &quot;startDate&quot;:&quot;2024-01-01&quot;, &quot;endDate&quot;:&quot;today&quot; }],
             &quot;keepEmptyRows&quot;: true
            }
      )
      .then((response) =&gt; {
        console.log(response);  
      })
      .catch((error) =&gt; {
        console.log(error);
      })
  })
}

export default Example;</code></pre>
<br/>

<h3 id="🧩-ga4-property-id-ga4_property_id">🧩 GA4 Property ID (GA4_PROPERTY_ID)</h3>
<p>GA의 홈 화면에서 좌측 하단의 관리(⚙️) 버튼 ▶ [속성 설정] ▶ [속성 세부정보] ▶ 우측 상단의 &#39;속성 ID&#39;에서 확인할 수 있다!
친절하게 복사버튼도 있으니, 해당 속성 ID를 복사하여 코드에 붙여넣도록 하자 :)</p>
<p><span style="color:#AAA;">* 하지만 이러한 ID, KEY 값과 같은 데이터들은 노출될 경우 보안상 취약할 수 있기 때문에,
&nbsp;&nbsp;&nbsp;<code>.env</code> 파일에 환경변수로 담아두는 것을 추천한다.</span></p>
<p><span style="color:#AAA;">* 필자 역시 REACT_APP_GA4_PROPERTY_ID 라는 환경변수로 <code>.env</code> 파일에 담아두었다.</span></p>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/419c3785-0296-4917-93cf-75f8464084a9/image.png" alt="GA Property ID 01"></p>
<p>여기까지 모든 절차를 잘 따라왔다면, 이제 필요한 값들은 대부분 준비가 되었다.</p>
<ul>
<li>✅ <code>GA4_PROPERTY_ID</code></li>
<li>✅ <code>dataRanges</code> </li>
<li>✅ <code>dimensions</code></li>
<li>✅ <code>metrics</code></li>
</ul>
<br/>

<p>코드도 아래와 같이 작성되었을 것이다.</p>
<pre><code class="language-javascript">// Example.js

import { useEffect } from &#39;react&#39;;
import axios from &#39;axios&#39;;

const Example = () =&gt; {
  useEffect(() =&gt; {
    axios
      .post(`https://analyticsdata.googleapis.com/v1beta/properties/${process.env.REACT_APP_GA4_PROPERTY_ID}:runReport`,
            {
             &quot;dimensions&quot;: [{ &quot;name&quot;:&quot;date&quot; }],
             &quot;metrics&quot;: [{ &quot;name&quot;:&quot;activeUsers&quot; }, { &quot;name&quot;:&quot;screenPageViews&quot; }, { &quot;name&quot;:&quot;sessions&quot; }],
             &quot;dateRanges&quot;: [{ &quot;startDate&quot;:&quot;2024-01-01&quot;, &quot;endDate&quot;:&quot;today&quot; }],
             &quot;keepEmptyRows&quot;: true
            }
      )
      .then((response) =&gt; {
        console.log(response);  
      })
      .catch((error) =&gt; {
        console.log(error);
      })
  })
}

export default Example;</code></pre>
<p>좋다!
이제 <code>runReport</code>를 통해 받은 응답을 확인하기 위해 콘솔창을 딱 열어보면!</p>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/569cec4b-f32a-4a33-be8f-b9ba80d0fcef/image.png" alt="GA Property ID 02"></p>
<p align="center"><img src="https://media.giphy.com/media/iAYupOdWXQy5a4nVGk/giphy.gif" style="margin:0 auto; border-radius:50px;" /></p>

<br/>

<br/>

<h3 id="🧩-google-api-console-client-id-client-secret">🧩 Google API Console (Client ID, Client Secret)</h3>
<p>분명 완벽해야했을 내 응답은 어째서 401(Unauthorized) 상태 코드를 보여주는걸까?
다행히 검색을 통해 원인을 알 수 있었는데, 바로 <code>POST</code> 요청에서 <code>access_token</code>을 빼먹었기 때문이다.
즉, API에 요청을 보낼 때 내가 승인이나 인증을 받은 사람인지 증명하지 못했다는 것!</p>
<p><span style="color:#AAA;">* 무엇을 믿고 데이터를 함부로 주겠나?!</span></p>
<p>당황하지 않고 <a href="https://console.cloud.google.com/apis" target="_blank">Google Cloud API 서비스</a>로 접속한다.</p>
<ol>
<li>아마 처음 접속했다면 아래와 같이 깨끗한 화면을 볼 수 있을텐데,
화면에 보이는 <strong>[프로젝트 만들기]</strong>를 클릭하여 우선 프로젝트를 생성해주도록 한다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/b232bc32-a520-4bb8-8df9-8713f8127287/image.png" alt="Google API Console 01"></p>
<ol start="2">
<li>프로젝트 이름을 입력하고, <strong>[만들기]</strong> 버튼을 누른다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/52c64b8c-d6b8-4270-961c-d6737a161fa6/image.png" alt="Google API Console 02"></p>
<ol start="3">
<li>프로젝트를 만드는 데 성공했다면, 좌측 메뉴의 <strong>[사용자 인증 정보]</strong>에 들어가서 <strong>[+ 사용자 인증 정보 만들기]</strong>를 클릭,</li>
</ol>
<p><strong>[OAuth 클라이언트 ID]</strong>를 눌러 OAuth 2.0 클라이언트 ID를 하나 만들어주도록 한다.</p>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/51d4d0bb-caa0-4d27-bece-c9840c93bd83/image.png" alt="Google API Console 03"></p>
<ol start="4">
<li>그러면 동의 화면을 구성해야 한다는 문구가 나타날텐데, <strong>[동의 화면 구성]</strong> 버튼을 눌러 해당 절차로 이동한다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/4f0c2485-0612-4ffd-a3ad-ac100e23c83b/image.png" alt="Google API Console 04"></p>
<ol start="5">
<li>User Type으로 &#39;외부&#39;를 선택한 후 <strong>[만들기]</strong> 버튼을 눌러준다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/5987f8bc-61cd-4cf7-bcc8-fdc7bf704dd5/image.png" alt="Google API Console 05"></p>
<ol start="6">
<li>앱의 이름과 사용자 지원 이메일을 입력한다. 사용자 지원 이메일은 선택할 수 있는게 자신밖에 없어서 그대로 선택해주면 된다.
배포된 React.js의 홈페이지 URL을 &#39;애플리케이션 홈페이지&#39;에 입력하고, &#39;승인된 도메인 1&#39;에는 <code>http://</code>, <code>https://</code>를 제외하고 입력한다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/20bc096d-1926-4b89-82fe-5799ee3b2827/image.png" alt="Google API Console 06"></p>
<ol start="7">
<li><strong>[범위 추가 또는 삭제]</strong> 버튼을 클릭하여 <code>.../auth/userinfo.email - 기본 Google 계정의 이메일 주소 확인</code>만 선택한 다음,</li>
</ol>
<p><strong>[업데이트]</strong> 버튼을 눌러 갱신해주고, <strong>[저장 후 계속]</strong> 버튼으로 다음 절차로 넘어간다.</p>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/3ef6a0d4-b75f-4ec1-81f4-2a145d1d7fab/image.png" alt="Google API Console 07"></p>
<ol start="8">
<li>테스트 사용자는 나 자신의 메일을 입력하여 추가해주고, <strong>[저장 후 계속]</strong>을 누르면
OAuth 동의 화면 설정이 완료된다!</li>
</ol>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/9168160d-5f65-47ba-afb6-499207a60c18/image.png" alt="Google API Console 08"></p>
<ol start="9">
<li>이제 동의 화면 설정도 완료되었으니, 다시 3.에서 진행하려고 했던 <strong>[OAuth 클라이언트 ID]</strong> 만들기를 재개하도록 한다.
&#39;애플리케이션 유형&#39;은 &#39;웹 애플리케이션&#39;으로 설정하고, 프로젝트의 이름을 입력한다.
&#39;승인된 자바스크립트 원본&#39;에는 프로젝트의 배포 URL을 입력하고,
&#39;승인된 리디렉션 URI&#39;에는 ①배포 URL, ②<code>http://localhost:port</code>, ③<code>https://developers.google.com/oauthplayground</code> 를 입력하고</li>
</ol>
<p><strong>[만들기]</strong> 버튼을 누르면 OAuth 2.0 클라이언트 ID가 생성된다.</p>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/ba9170d2-95bc-4091-a694-b8e5ae5bc589/image.png" alt="Google API Console 09"></p>
<p>여기서 생성된 클라이언트 ID(<code>OAuth Client ID</code>)와 클라이언트 보안 비밀번호(<code>OAuth Client Secret</code>)은 이후에 사용되므로,
JSON 파일을 다운로드 받거나 복사해두도록 한다!
<span style="color:#AAA;">* 이후에도 해당 ID를 클릭하여 정보들을 다시 확인할 수 있긴하다.</span></p>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/910113b8-95da-44c9-844d-fafe04ddc026/image.png" alt="Google API Console 10"></p>
<ol start="10">
<li>좌측 메뉴의 <strong>[사용 설정된 API 및 서비스]</strong> 를 클릭하고, 화면 중앙의 <strong>[+ API 및 서비스 사용 설정]</strong>을 눌러 API 라이브러리로 이동한다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/408d615d-92cd-4c7b-a3eb-8a07ea09ff68/image.png" alt="Google API Console 11"></p>
<ol start="11">
<li>&#39;Google Analytics Reporting API&#39;, &#39;Google Analytics Data API&#39;를 검색하여 아래와 같이 생긴 API를 클릭,</li>
</ol>
<p><strong>[사용]</strong> 버튼을 눌러 활성화 시켜준다.</p>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/c5687123-543f-47c8-a531-53565dd2fdce/image.png" alt="Google API Console 12"></p>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/74011d6b-1b9f-48bc-9bac-705e6329ee96/image.png" alt="Google API Console 13"></p>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/e7568bdc-6cd3-44be-82eb-43427a39ad6f/image.png" alt="Google API Console 14"></p>
<p align="center">(여기서 [사용]을 누르면 아래와 같이 'API 사용 설정됨'이 우측에 나타나고,<br/> [사용] 버튼이 [관리] 버튼으로 바뀐다)</p>

<p><img src="https://velog.velcdn.com/images/slight-snow/post/cb6883b2-2849-4f34-93c9-1f144c155701/image.png" alt="Google API Console 15"></p>
<br/>

<h3 id="🧩-oauth-20-playground-access-token-refresh-token">🧩 OAuth 2.0 PlayGround (Access Token, Refresh Token)</h3>
<p>이제 토큰을 받기 위한 세팅은 끝났으니,
<a href="https://developers.google.com/oauthplayground/" target="_blank">OAuth 2.0 PlayGround</a>로 이동하여 토큰을 받아보자!</p>
<ol>
<li>우측 상단의 설정(⚙️)을 눌러, 다른 것은 크게 손댈 것이 없고 &#39;Use your own OAuth credentials&#39; 체크박스를 활성화한 다음,
이전에 생성했던 클라이언트 ID(<code>OAuth Client ID</code>)와 클라이언트 보안 비밀번호(<code>OAuth Client Secret</code>)을 입력해준다.
따로 저장버튼은 보이지 않는데, 하단의 [Close] 버튼을 누르면 자동으로 설정이 저장된다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/5b0b1f8e-86fd-4b89-8524-26837245f15d/image.png" alt="OAuth 2.0 PlayGround 01"></p>
<ol start="2">
<li>왼쪽에 있는 Scope들 중, 하단의 Scope만 선택한 다음 <strong>[Authorize APIs]</strong> 버튼을 누른다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/b073dae3-90f1-47d4-93e6-fc6e3e4e49db/image.png" alt="OAuth 2.0 PlayGround 02"></p>
<ol start="3">
<li>그러면 이렇게 <code>Refresh token</code>과 <code>Access token</code>이 생성된다!</li>
</ol>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/2cde5ba6-edb0-472f-ab9f-ac1b6eb02b67/image.png" alt="OAuth 2.0 PlayGround 03"></p>
<p>여기까지 왔으니, 다시 한 번 <code>header</code>에 <code>Access token</code>을 넣어서 코드를 작성해보면ㅡ
<span style="color:#AAA;">* <code>GA4_PROPERTY_ID</code>와 마찬가지로 보안상의 이유로 <code>.env</code>에 환경변수로 담아 작성했다.</span></p>
<pre><code class="language-javascript">// Example.js

import { useEffect } from &#39;react&#39;;
import axios from &#39;axios&#39;;

const Example = () =&gt; {
  useEffect(() =&gt; {
    axios
      .post(`https://analyticsdata.googleapis.com/v1beta/properties/${process.env.REACT_APP_GA4_PROPERTY_ID}:runReport`,
            {
             &quot;dimensions&quot;: [{ &quot;name&quot;:&quot;date&quot; }],
             &quot;metrics&quot;: [{ &quot;name&quot;:&quot;activeUsers&quot; }, { &quot;name&quot;:&quot;screenPageViews&quot; }, { &quot;name&quot;:&quot;sessions&quot; }],
             &quot;dateRanges&quot;: [{ &quot;startDate&quot;:&quot;2024-01-01&quot;, &quot;endDate&quot;:&quot;today&quot; }],
             &quot;keepEmptyRows&quot;: true
            },
            {
                  headers: {
                    &#39;Authorization&#39;: `Bearer ${process.env.REACT_APP_OAUTH_ACCESS_TOKEN}`
                }
            }
      )
      .then((response) =&gt; {
        console.log(response);  
      })
      .catch((error) =&gt; {
        console.log(error);
      })
  })
}

export default Example;</code></pre>
<p>두근거리는 마음으로 콘솔창을 열어보니?!</p>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/a9385711-48f8-4369-82b6-abe5b099f420/image.png" alt="OAuth 2.0 PlayGround 04"></p>
<p>정상적으로 응답이 호출되는 것을 확인할 수 있었다!</p>
<p align="center"><img src="https://media.giphy.com/media/YnBntKOgnUSBkV7bQH/giphy.gif" /></p>

<p>다만, <code>Access token</code>만으로는 한계가 있는데
기본적으로 <code>Access token</code>은 만료기간이 존재하기 때문이다.
(아래 예시의 경우, 1시간(60분, 3600초) 이후에 <code>Access token</code>이 만료된다는 것)</p>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/8b5fee1b-41ea-4bbc-b87d-93e19f709376/image.png" alt="OAuth 2.0 PlayGround 05"></p>
<p>1시간마다 코드에서 <code>Access token</code>을 교체해줄 수도 없는 노릇이니, 자동적으로 갱신이 되어야하는데
그 때 사용되는 것이 바로 <code>Refresh token</code>이다.</p>
<p>OAuth 2.0 Playground의 설정(⚙️)에서 입력했던 클라이언트 ID(<code>OAuth Client ID</code>)와 클라이언트 보안 비밀번호(<code>OAuth Client Secret</code>),
그리고 3. 에서 생성된 <code>Refresh token</code>이 있다면 <code>Access token</code>을 손쉽게 갱신할 수 있다.
<span style="color:#AAA;">* 이번에도 역시 마찬가지로 보안상의 이유로 <code>.env</code>에 환경변수로 담아 작성했다.</span></p>
<pre><code class="language-javascript">axios
  .post(&#39;https://accounts.google.com/o/oauth2/token&#39;,
        {
           &quot;client_id&quot;: `${process.env.REACT_APP_OAUTH_CLIENT_ID}`
             &quot;client_secret&quot;: `${process.env.REACT_APP_OAUTH_CLIENT_SECRET}`,
           &quot;refresh_token&quot;: `${process.env.REACT_APP_OAUTH_REFRESH_TOKEN}`,
           // &quot;grant_type&quot;은 &quot;refresh_token&quot;으로, 이 코드와 동일하게 작성해주면 된다.
           &quot;grant_type&quot;: &quot;refresh_token&quot;
        }
  )
  .then((response) =&gt; { console.log(response); }
  .catch((error) =&gt; { console.log(error); })</code></pre>
<p>이렇게 작성해두면 <code>response.data.access_token</code>에 갱신된 <code>Access token</code>이 전달되어 응답이 온다!</p>
<hr>
<p>정리해보자면,
① <code>OAuth Client ID</code>, <code>OAuth Client Secret</code>, <code>Refresh token</code>을 통해 <code>https://accounts.google.com/o/oauth2/token</code>으로
<code>POST</code> 요청을 보내서 갱신된 <code>Access token</code>을 발급 받는다.</p>
<p>② 발급 받은 <code>Access token</code>을 <code>headers</code>에 담아
<code>https://analyticsdata.googleapis.com/v1beta/properties/${process.env.REACT_APP_GA4_PROPERTY_ID}:runReport</code>으로
<code>dataRanges</code>, <code>dimensions</code>, <code>metrics</code> 데이터를 포함하여 <code>POST</code> 요청을 보내면 원하는 결과값이 호출된다는 것!</p>
<hr>
<p>여기까지 이해하는데 성공했다면 자신을 칭찬해주면 된다!
GA4의 이전 버전인 GA3(Universal Analytics, UA)에 관한 글이 검색결과의 주를 이루고
GA4를 연동하는 것까지는 정보가 많아도 API를 실제로 요청하는 것에 대한 내용은 적어서
필자는 거의 1.5일 가까이 GA4를 붙들어매고 이해하려고 애썼다...</p>
<p>어쨌거나! 글을 읽고 고개를 끄덕이며 바로바로 이해가 됐다면 충분히 자부심을 느껴도 된다는 것이다 :)</p>
<br/>

<h3 id="🧩-최종-코드-정리본">🧩 최종 코드 정리본</h3>
<p>이제 마지막이다.
정리한 내용을 바탕으로 코드를 작성해보자면ㅡ</p>
<pre><code class="language-javascript">// Example.js
import { useEffect } from &#39;react&#39;;
import axios from &#39;axios&#39;;

const Example = () =&gt; {
    useEffect(() =&gt; {
        // &#39;client_id&#39;, &#39;client_secret&#39;, &#39;refresh_token&#39;을 사용하여 갱신된 &#39;access_token&#39;을 요청한다.
        axios.post(&#39;https://accounts.google.com/o/oauth2/token&#39;,
            {
                &quot;client_id&quot;: `${process.env.REACT_APP_OAUTH_CLIENT_ID}`,
                &quot;client_secret&quot;: `${process.env.REACT_APP_OAUTH_CLIENT_SECRET}`,
                &quot;refresh_token&quot;: `${process.env.REACT_APP_OAUTH_REFRESH_TOKEN}`,
                &quot;grant_type&quot;: &quot;refresh_token&quot;
            }
        )
        .then((response) =&gt; {
            // 만약 정상적으로 &#39;access_token&#39;을 받았다면, 기본 보고서(runReport)를 호출하는 요청을 보낸다.
            axios.post(`https://analyticsdata.googleapis.com/v1beta/properties/${process.env.REACT_APP_GA4_PROPERTY_ID}:runReport`,
                // runReport 요청에 필요한 &#39;dimensions&#39;, &#39;metrics&#39;, &#39;dataRanges&#39;를 data에 포함하여 전송한다.
                {
                    &quot;dimensions&quot;: [{ &quot;name&quot;:&quot;date&quot; }],
                    &quot;metrics&quot;: [{ &quot;name&quot;:&quot;activeUsers&quot; }, { &quot;name&quot;:&quot;screenPageViews&quot; }, { &quot;name&quot;:&quot;sessions&quot; }],
                    &quot;dateRanges&quot;: [{ &quot;startDate&quot;:&quot;2024-01-01&quot;, &quot;endDate&quot;:&quot;today&quot; }],
                    &quot;keepEmptyRows&quot;: true,
                },
                // 이전에 전달받은 &#39;access_token&#39;을 headers에 담는다(인증).
                {
                    headers: {
                        &#39;Authorization&#39;: `Bearer ${response.data.access_token}`
                    }
                }
            )
            // 정상적으로 응답을 받았다면, 콘솔창에 runReport의 결과가 나타날 것이다.
            .then((response) =&gt; { 
                console.log(response);
            })
            // runReport가 정상적으로 호출되지 않았다면, [REPORT ERROR]라는 문구와 함께 콘솔창에 에러가 보일 것이다.
            .catch((error) =&gt; {
                console.log(&#39;[REPORT ERROR] &#39;, error);
            })
        })
        // &#39;access_token&#39;을 호출하는 것에 실패했다면, [TOKEN ERROR]라는 문구와 함께 콘솔창에 에러가 보일 것이다.
        .catch((error) =&gt; {
            console.log(&#39;[TOKEN ERROR] &#39;, error);
        })
    })
}</code></pre>
<p>이렇게 코드를 작성하면 정상적으로 GA4의 기본 보고서가 응답으로 날아온다 :D</p>
<p>정말 길고 길었다!
이제 보고서 응답도 받았으니, 이것을 야무지게 조리해서 방문자수로 보이게 하거나
도표로 만들어 방문자 통계를 내거나 혹은 방문자들의 다른 데이터를 정리해서 분석하거나 하는 것은
여러분의 몫이니 다양하게 사용해보도록 하자!</p>
<p align="center"><img src="https://media.giphy.com/media/ZXrgXBc606c5Q9fUTz/giphy.gif" /></p>

<br/>]]></description>
        </item>
        <item>
            <title><![CDATA[[React.js] GA4(Google Analytics 4) - ①연동하기]]></title>
            <link>https://velog.io/@slight-snow/React.js-GA4Google-Analytics-4-%EC%97%B0%EB%8F%99%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@slight-snow/React.js-GA4Google-Analytics-4-%EC%97%B0%EB%8F%99%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 23 Jan 2024 09:19:50 GMT</pubDate>
            <description><![CDATA[<p>React.js 프로젝트를 마무리하고 배포된 웹사이트에서 이용자들의 조회수,
방문했을 때 평균적으로 머무르는 시간 등을 확인할 수 있는 방법을 찾아보다가
<strong>구글 애널리틱스 4(Google Analytics 4, GA4)</strong>를 발견했다.</p>
<br/>

<hr>
<br/>

<h2 id="📌구글-애널리틱스google-analytics란">📌구글 애널리틱스(Google Analytics)란?</h2>
<p><strong>구글 애널리틱스</strong>는 2005년부터 구글에서 무료로 제공하기 시작한 서비스로,
웹사이트와 연동시켜두면 웹사이트 이용자들의 유입 경로나 액션, 이벤트 등에 관한
정보들을 수집 및 분석할 수 있도록 해주는 웹 로그분석 툴이다.</p>
<p>2005년에 GA1(Urchin), 2008년에 GA2(Classic), 2013년에 GA3(Universal)
이렇게 3번째 업데이트 이후로 2020년 GA4까지 등장하게 되었다.</p>
<br/>

<hr>
<br/>

<h2 id="📌구글-애널리틱스google-analytics-연동-방법">📌구글 애널리틱스(Google Analytics) 연동 방법</h2>
<h3 id="🧩-구체적인-연동-과정">🧩 구체적인 연동 과정</h3>
<p><a href="https://analytics.google.com/analytics/web/provision/#/provision" target="_blank">구글 애널리틱스(GA)</a>로 접속하여 절차에 따라 요구되는 입력사항을 채우고,
프로젝트에 코드를 심어주면 금방 웹사이트와 GA를 연동시킬 수 있다!</p>
<ol>
<li>위의 링크로 접속하여 GA 관리자로 둘 계정으로 로그인한 다음,
[측정 시작] 버튼을 눌러 설정을 시작한다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/9c07adec-fef9-4472-adcc-a103a197d5b4/image.png" alt="Google Analytics 01"></p>
<ol start="2">
<li>계정 이름(필수)란에 관리자명이나 원하는 이름을 자유롭게 입력하고
하단의 옵션을 선택적으로 체크한다. <span style="color:#AAA;">*필자는 기본 체크박스 그대로 진행</span></li>
</ol>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/687f5bd8-3d31-4d1a-99fc-1fcb52324b1e/image.png" alt="Google Analytics 02"></p>
<ol start="3">
<li>이번에도 마찬가지로 프로젝트명이나 원하는 속성 이름을 자유롭게 입력하고
보고 시간대는 &#39;대한민국 / (그리니치 표준시 +09:00) 대한민국 시간&#39;으로,
통화는 &#39;대한민국 원(￦)&#39;으로 설정해주도록 한다.<br/>
<span style="color:#AAA;">* [고급 옵션 보기]를 누르면, 아래와 같이 [유니버셜 애널리틱스 속성 만들기] 칸이
하단에 나타나는데 지금부터 GA에 연동한다면 이전 버전보다는 현재, 차세대 버전을
이용하는 것이 안정적이기에 체크하지 않는 것을 추천한다.</span></li>
</ol>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/4873e4ef-285b-41ad-8a97-446f5338c2db/image.png" alt="Google Analytics 03"></p>
<ol start="4">
<li>프로젝트와 관련된 업종의 카테고리와 규모를 설정해주도록 한다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/969a7c1b-326c-489a-9bdb-1c1412e78413/image.png" alt="Google Analytics 04"></p>
<ol start="5">
<li>비즈니스 목표를 설정해주도록 한다.
필자는 이용자의 조회수나 유입경로를 분석하기 위해 해당 목표를 선택했으나,
다른 것을 선택한다고 하여 이용할 수 있는 서비스에 차질이 생기는 것도 아니니
마음 편하게 고르면 될 듯하다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/3ed8b18f-4f03-454e-88a0-2d508c7f8c37/image.png" alt="Google Analytics 05"></p>
<ol start="6">
<li>비즈니스 목표를 설정하고 [만들기] 버튼을 누르면 아래와 같은 약관 계약서가 모달로 등장한다.
드롭다운을 열어 국가를 &#39;대한민국&#39;으로 설정하고 체크박스를 누른 뒤 [동의함]을 눌러준다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/8d71026a-b87c-44f8-8d59-aa4dd8d45551/image.png" alt="Google Analytics 06"></p>
<ol start="7">
<li>웹사이트의 데이터를 수집할 것이기 때문에, 플랫폼 선택에서 &#39;웹&#39;을 선택해준다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/bfe2c248-da4e-41be-ba4e-6ee6de4d64e5/image.png" alt="Google Analytics 07"></p>
<ol start="8">
<li>배포한 웹사이트의 URL을 입력하고, 해당 웹사이트의 명칭을 입력한 다음
[스트림 만들기]를 눌러 다음 단계로 넘어간다.<br/>
<span style="color:#AAA;">* 향상된 측정 옵션은 이용자들의 세부 인터랙션 등을 수집하는 데 이용되기 때문에,
불가피한 경우가 아니라면 그대로 활성화된 상태로 진행하도록 한다.</span></li>
</ol>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/8d8beb5f-2d38-49f5-b3bd-3c68fe28fef3/image.png" alt="Google Analytics 08"></p>
<ol start="9">
<li>스트림을 만들면 스트림의 세부정보가 나타나면서 상단에 [태그 안내 보기] 버튼이 보일 것이다.
해당 버튼을 눌러서 웹사이트에 설치하는 단계로 넘어간다.
이제 정말 거의 다 왔다!</li>
</ol>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/8ccd63a5-347a-4561-850c-7140a0f82aa6/image.jpg" alt="Google Analytics 09"></p>
<ol start="10">
<li>[직접 설치] 탭을 누르면 아래와 같은 코드가 나타나고,
해당 코드를 어디 붙여넣으면 되는지 친절하게 설명까지 해준다.<br/>
React.js의 경우, 프로젝트 내의 <code>public</code> 폴더에 <code>index.html</code>이 기본적으로 내장되어 있다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/59541131-0545-4428-b41b-dd72393b7993/image.png" alt="Google Analytics 10"></p>
<ol start="11">
<li>React.js의 <code>public\index.html</code> 에서 구글의 설명대로 <code>&lt;head&gt;</code> 태그 바로 다음에 붙여넣도록 한다.</li>
</ol>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;ko&quot;&gt;
  &lt;head&gt;
    &lt;meta charset=&quot;utf-8&quot; /&gt;
    &lt;link rel=&quot;icon&quot; href=&quot;%PUBLIC_URL%/favicon.ico&quot; /&gt;
    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot; /&gt;
    &lt;meta name=&quot;theme-color&quot; content=&quot;#000000&quot; /&gt;
    &lt;meta name=&quot;description&quot; content=&quot;Web site created using create-react-app&quot;/&gt;
    &lt;link rel=&quot;apple-touch-icon&quot; href=&quot;%PUBLIC_URL%/logo192.png&quot; /&gt;
    &lt;link rel=&quot;manifest&quot; href=&quot;%PUBLIC_URL%/manifest.json&quot; /&gt;
    &lt;title&gt;React App&lt;/title&gt;
  &lt;/head&gt;
  &lt;!-- 이곳에 위의 Google 태그를 삽입해준다 --&gt;
  &lt;!-- Google tag (gtag.js) --&gt;
  &lt;script async src=&quot;https://www.googletagmanager.com/gtag/js?id=G-TXHDDPB10J&quot;&gt;&lt;/script&gt;
  &lt;script&gt;
    window.dataLayer = window.dataLayer || [];
    function gtag(){dataLayer.push(arguments);}
    gtag(&#39;js&#39;, new Date());

    gtag(&#39;config&#39;, &#39;G-XXXXXXX00X&#39;);
  &lt;/script&gt;
  &lt;!--------------------------------------&gt;
  &lt;body&gt;
    &lt;noscript&gt;You need to enable JavaScript to run this app.&lt;/noscript&gt;
    &lt;div id=&quot;root&quot;&gt;&lt;/div&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<ol start="12">
<li>웹사이트에 GA 설치가 끝났다!
성공적으로 해당 Google 코드를 삽입해주었다면, 빌드 후 배포환경에 적용하여
[테스트] 버튼을 눌러 코드가 정상적으로 삽입되었는지, GA에서 인식할 수 있는지 확인할 수 있다.<br/>
<span style="color:#AAA;">* &#39;웹사이트에서 Google 태그가 감지되지 않았습니다.&#39; 라는 문구가 나타난다면,
측정 ID(G-XXXXXXX00X)에 오타는 없는지, 코드가 안내된 위치에 입력되어있는지 다시 확인한다.</span></li>
</ol>
<br/>

<p align="center"><img src="https://media.giphy.com/media/11sBLVxNs7v6WA/giphy.gif" style="margin:0 auto; border-radius:50px;" /></p>

<br/>

<br/>

<h3 id="🧩-연동-후-데이터-확인-방법">🧩 연동 후, 데이터 확인 방법</h3>
<p>아직은 연동한지 시간이 많이 지나지 않아 데이터가 충분히 쌓이지 않았을 것이다.
이전의 12번 과정에서 웹사이트와 정상적으로 연동되었다고 나왔다면, 차분히 기다릴 일만 남았다.</p>
<p>너무 조바심 내지말고 내일이나 모레 GA의 홈화면에 접속해보면,
페이지 중앙의 그래프에 사용자 데이터가 나타날 것이다 :)</p>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/7d5b7275-6c0b-4023-b501-f67c85373a2c/image.png" alt="Google Analytics 11"></p>
<p align="center"><img src="https://media.giphy.com/media/PPzYexDzlVuEsZiONf/giphy.gif" style="margin:0 auto; border-radius:50px;" /></p>



<br/>

<br/>

<p>GA4 API를 호출하는 것까지 한 번에 글을 올리고 싶었으나,
절차 하나하나마다 사진을 넣다보니 글이 너무 길어지는 느낌이라
2편에서 다루도록 하겠다!</p>
<br/>

<p>To be continued...
▶ <a href="https://velog.io/@slight-snow/React.js-GA4Google-Analytics-API-%ED%98%B8%EC%B6%9C%ED%95%98%EA%B8%B0" target="_blank">[React.js] GA4(Google Analytics 4) - ②API 호출하기</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ASP(EUC-KR) 환경에서 Parameter가 '한글'일 경우 Android에서 깨져서 전달되는 현상]]></title>
            <link>https://velog.io/@slight-snow/ASPEUC-KR-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-Parameter%EA%B0%80-%ED%95%9C%EA%B8%80%EC%9D%BC-%EA%B2%BD%EC%9A%B0-Android%EC%97%90%EC%84%9C-%EA%B9%A8%EC%A0%B8%EC%84%9C-%EC%A0%84%EB%8B%AC%EB%90%98%EB%8A%94-%ED%98%84%EC%83%81</link>
            <guid>https://velog.io/@slight-snow/ASPEUC-KR-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-Parameter%EA%B0%80-%ED%95%9C%EA%B8%80%EC%9D%BC-%EA%B2%BD%EC%9A%B0-Android%EC%97%90%EC%84%9C-%EA%B9%A8%EC%A0%B8%EC%84%9C-%EC%A0%84%EB%8B%AC%EB%90%98%EB%8A%94-%ED%98%84%EC%83%81</guid>
            <pubDate>Mon, 30 Oct 2023 07:01:46 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/slight-snow/post/2c370ce9-7721-4057-afa4-64758c34501c/image.png" alt=""></p>
<pre><code class="language-javascript">// EXAMPLE OF `place`
{
  &quot;navSeq&quot;: &quot;0&quot;,
  &quot;rpFlag&quot;: 16,
  &quot;poiName&quot;: &quot;푸른길분수공원&quot;,
  &quot;poiId&quot;: &quot;---&quot;,
  &quot;streetNum&quot;: &quot;광주 동구 산수동 358-21&quot;,
  &quot;roadName&quot;: &quot;광주 동구&quot;,
  &quot;centerX&quot;: &quot;---&quot;,
  &quot;centerY&quot;: &quot;---&quot;,
  &quot;navX&quot;: &quot;---&quot;,
  &quot;navY&quot;: &quot;---&quot;
}

// XML Code
&lt;a href=&quot;https://www.tmap.co.kr/tmap2/mobile/route.jsp?name=${place[&quot;poiName&quot;].replace(/\s/g, &quot;&quot;)}&amp;lon=${place[&quot;navX&quot;]}&amp;lat=${place[&quot;navY&quot;]}&quot; class=&quot;list-place-navigation&quot;&gt;길찾기&lt;/a&gt;</code></pre>
<p>ASP(EUC-KR) 코드상에서 위와 같이 <code>&lt;a&gt;</code> 태그의 <code>href</code> 속성이 작성되어 있을때,
웹이나 iOS에서는 <code>poiName</code>인 <code>&#39;푸른길분수공원&#39;</code>이 정상적으로 파라미터로 전달되지만
Android에서는 <code>&#39;%ED%91%B8%EB%A5%B8%EA%B8%B8%EB%B6%84%EC%88%98%EA%B3%B5%EC%9B%90&#39;</code>와 같은 형태로
인코딩되어 정상적으로 장소명이 전달되지 않는 것을 확인할 수 있었다.</p>
<blockquote>
<p>비정상적으로 작동될 것이라면, iOS와 Android 둘 다 이러한 문제가 발생했다면
초기에 버그를 발견했을텐데 내 기종이 iOS라 당연히 정상적으로 작동하는 줄 알았다.</p>
<p>ASP, iOS, Android에 대한 이해가 아직 미숙하다보니
구체적으로 어떠한 작동원리 때문에 동일한 파라미터를
iOS와 Android가 각기 다르게 전달받았는지 아직도 잘 모르겠다.</p>
</blockquote>
<hr>
<p>■ 현재상황
기대: <code>[ 출발지 : 현재위치 ]</code>, <code>[ 도착지 : 푸른길분수공원 ]</code>
현실: <code>[ 출발지 : 현재위치 ]</code>, <code>[ 도착지 : ????????? ]</code></p>
<hr>
<p>즉, 원래는 위와 같은 형태로 TMAP의 어플리케이션의 길찾기 기능이 실행되어야 하는데,
도착지를 제대로 전달받지 못했다는 것이다.</p>
<p>약 1시간의 구글링 결과 몇 가지의 해결방법을 시도할 수 있었다.
일단 전제는 한글은 <code>UTF-8</code>로 인코딩해야 제대로 파라미터로 전달될 수 있다는 것이다.
그렇다면 위의 파라미터를 어떻게 <code>EUC-KR</code> 환경에서 <code>UTF-8</code>로 인코딩을 하냐는 것인데...</p>
<h3 id="1-escape">1. escape()</h3>
<p><code>escape()</code> 메서드는 ASP에서 알파벳과 숫자 및 * , @, - , _ , + , . , / 를 제외한 문자를 모두 16진수 문자로 바꿔주는 메서드로, 쉼표와 세미콜론 같은 문자가 쿠키문자열과의 충돌을 피하기 위해 사용된다고 한다.</p>
<p>ASP 서버 통신에서도 한글을 전달할 때 <code>escape()</code> 메서드를 사용하였기에,
한글깨짐 현상에 대한 일종의 만능 대처법으로 생각하여 이번에도 <code>escape()</code>를 사용하고,
다시 디코딩을 하기 위해 <code>unescape()</code> 메서드도 함께 사용하였으나
Android 환경에서는 여전히 제대로 전달되지 않는 것을 확인했다.</p>
<h3 id="2-urlencoderencode">2. URLEncoder.encode()</h3>
<p>16진수가 아닌 <code>UTF-8</code>로 인코딩을 하기 위해 찾은 <code>URLEndocer.encode()</code> 메서드를 사용하여</p>
<pre><code class="language-javascript">// XML Code
&lt;a href=&quot;https://www.tmap.co.kr/tmap2/mobile/route.jsp?name=${URLEncoder.encode(place[&quot;poiName&quot;].replace(/\s/g, &quot;&quot;), &quot;UTF-8&quot;)}&amp;lon=${place[&quot;navX&quot;]}&amp;lat=${place[&quot;navY&quot;]}&quot; class=&quot;list-place-navigation&quot;&gt;길찾기&lt;/a&gt;</code></pre>
<p>위와 같이 코드를 작성해주었으나,
콘솔창에서 <code>URLEncoder is not defined</code>라는 에러 문구를 발견할 수 있었고
이에 구글링을 하여 <code>encodeURI()</code>라는 대안을 발견할 수 있었다.</p>
<h3 id="3-encodeuri">3. encodeURI()</h3>
<p><code>encodeURI()</code> 메서드는 URI의 특정한 문자를 UTF-8로 인코딩해 하나, 둘, 셋, 혹은 네 개의 연속된 이스케이프 문자로 나타낸다.
<code>encodeURI()</code> 메서드를 사용하여 한글이 포함된 파라미터를 인코딩하여 전달해줬더니
TMAP 어플리케이션에서도 장소명을 전달받아 정상적으로 한글 장소명을 표기함을 확인할 수 있었다!</p>
<p><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/encodeURI">▶ encodeURI MDN 공식문서</a></p>
<hr>
<blockquote>
<p>HTML, VanillaJS와 React로 최근에 이런저런 작업들을 하고 있다가
갑작스레 ASP 작업이 들어와서 적잖이 당황스러운 경험이었던 것 같다.</p>
<p>인코딩도 기본적으로 UTF-8로 작업을 해왔었기에,
EUC-KR이라는 새로운 인코딩으로 작업하는 것도 처음이었다.</p>
<p>지금 작성하는 ASP 이슈 말고도 여러가지 버그와 디버깅 과정이 있었고
생각보다 낯설고 험난했다고 생각한다.</p>
<p>하지만 그럼에도 이번 작업에서도 경험한 것은
코딩에 불가능은 없다는 것이다.</p>
<p>여러 경험이 쌓여가면 쌓여갈수록, 불가능해 보이는 것은
내가 아직 해결해보지 못해서이지, 불가능하다고 섣불리 단정지어서는
안된다는 것을 느끼게 되는 것 같다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Dart] Data Type의 종류]]></title>
            <link>https://velog.io/@slight-snow/Dart-Data-Type%EC%9D%98-%EC%A2%85%EB%A5%98</link>
            <guid>https://velog.io/@slight-snow/Dart-Data-Type%EC%9D%98-%EC%A2%85%EB%A5%98</guid>
            <pubDate>Thu, 14 Sep 2023 06:20:43 GMT</pubDate>
            <description><![CDATA[<h2 id="1-기본-type-정리">1. 기본 Type 정리</h2>
<p>기본적으로 흔히 사용하는 <code>type</code>에는 <code>String</code>, <code>int</code>, <code>bool</code>, <code>double</code> 이렇게 4가지가 있다.</p>
<pre><code class="language-dart">void main() {
    String name = &#39;이름&#39;; //string
    int number = 123; //number
    bool boolean = true; //boolean
    double decimal = 10.99; //decimal
}</code></pre>
<p><code>num</code>이라는 <code>type</code>도 있는데, <code>int</code>와 <code>double</code>을 모두 사용 가능한 <code>type</code>이라고 보면 된다.</p>
<pre><code class="language-dart">void main() {
    num number = 123; //OK
    num decimal = 123.99; //OK
}</code></pre>
<br/>

<h2 id="2-list">2. List</h2>
<p><code>JavaScript</code>의 배열인 <code>Array</code>와 형태가 유사하다.
<code>type</code>을 지정하여 선언할 수도 있으며, 일반적으로 <code>var</code>를 통해 선언한다.</p>
<pre><code class="language-dart">void main() {
    var numbers = [1, 2, 3, 4];
    // List&lt;int&gt; numbers = [1, 2, 3, 4]; 와 동일하다.
}</code></pre>
<p>
  <img src="https://velog.velcdn.com/images/slight-snow/post/b3fe213c-6d81-45ba-8e23-3c6e0ebb9f02/image.png" width="400" style="border-radius:10px;" />
</p>

<p><code>var</code>로 선언하더라도 자동으로 <code>List&lt;type&gt;</code>으로 인식하기 때문에 문제없다.</p>
<p><code>.add()</code> 매서드를 통해 <code>List</code>에 요소를 추가할 수도 있다.</p>
<pre><code class="language-dart">void main() {
    var numbers = [1, 2, 3, 4];
    numbers.add(1);

    print(numbers); //[1, 2, 3, 4, 1]
}</code></pre>
<p>이뿐만 아니라, 놀랍게도 <code>Dart</code>에는 <code>List</code>내에서 조건문으로 데이터를 추가할 수 있다.</p>
<pre><code class="language-dart">void main() {
    var giveMeFive = true;
    var numbers = [
        1,
        2,
        3,
        4,
        if (giveMeFive) 5,
    ];

    print(numbers); //[1, 2, 3, 4, 5]
}</code></pre>
<p>위의 예시를 들어, <code>true</code> 값을 할당한 <code>giveMeFive</code>는 항상 참이므로, <code>if</code>조건문에 부합된다.
그렇기에 <code>List</code>의 마지막 요소로 <code>5</code>가 추가되고 콘솔에 나타나게 되는 것이다.</p>
<p>이를 <strong>Collection If</strong>라고 부른다.</p>
<p><code>if</code>문과 함께 <code>List</code>에 사용되는 문법이 있으니, 바로 <code>for</code>문이다.</p>
<pre><code class="language-dart">void main() {
    var subscribers = [&#39;John&#39;, &#39;Eric&#39;, &#39;Anna&#39;];
    var viewers = [
        &#39;Tom&#39;,
        &#39;Cindy&#39;,
        for (var subscriber in subscribers) &#39;★$subscriber&#39;,
    ]

    print(viewers); //[Tom, Cindy, ★John, ★Eric, ★Anna]
}</code></pre>
<p>이렇게 <code>List</code>에 <code>for</code>문을 적용하여 요소로 추가하는 것도 가능하다.</p>
<p>이를 <strong>Collection For</strong>이라 부른다.</p>
<br/>

<h2 id="3-string-interpolation">3. String Interpolation</h2>
<p><strong>Collection For</strong>의 예시 코드를 보면서 뜬금없이 <code>$</code>기호가 나타났다.
<code>$</code>기호는 변수값을 사용하게 해주는 것으로, 다음과 같이 활용할 수 있다.</p>
<pre><code class="language-dart">void main() {
    var name = &#39;소설&#39;;
    var age = 26;
    var message = &#39;안녕하세요, 제 이름은 \&#39;$name\&#39;입니다. 저는 2년 뒤면 ${age + 2}살이 됩니다.&#39;;

    print(message); //안녕하세요, 제 이름은 &#39;소설&#39;입니다. 저는 2년 뒤면 28살이 됩니다.
}</code></pre>
<p>이런식으로 말이다!</p>
<p><code>String</code>을 사용하기 위해 <code>&#39;&#39;</code>, <code>&quot;&quot;</code>를 사용하다보면 문장 중간에 <code>&#39;&#39;</code>, <code>&quot;&quot;</code>를 넣었을 때</p>
<img src="https://velog.velcdn.com/images/slight-snow/post/262622b5-d6b2-4091-8dc7-34b349c6ce3b/image.png" width="800" style="border-radius:5px;">

<p>이런식으로 원치 않는 끊김을 마주할 수 있다.</p>
<img src="https://velog.velcdn.com/images/slight-snow/post/1b124a32-58d4-4e80-8afe-f9a894288e72/image.png" width="820" style="border-radius:5px;">

<p>그럴 때 위의 예시와 같이 <code>\</code>를 사용하여 탈출시키면 끊기지 않고 정상적으로 문장을 마칠 수 있다.</p>
<br/>

<h2 id="4-map">4. Map</h2>
<p><code>Map</code>은 <code>JavaScript</code>의 <code>Object</code>와 굉장히 유사하다.
<code>List</code>와 마찬가지로 <code>var</code>를 통해 선언하더라도 자동으로 <code>Map</code>으로 인식한다.</p>
<pre><code class="language-dart">void main() {
    var player = {            // Map&lt;String, Object&gt; player = { 과 같다.
        &#39;name&#39;: &#39;소설&#39;,
        &#39;exp&#39;: 25.97,
        &#39;superpower&#39;: false,
    }

    Map&lt;int, bool&gt; example = {
        1: true,
        2: false,
        3: true,
    }
}</code></pre>
<p>예시를 보면 알겠지만, <code>Map&lt;type, type&gt;</code>으로 선언을 할 수 있다.
＊ <code>Dart</code>는 모두 객체로 이루어져 있기 때문에, <code>Dart</code>에서의 <code>Object</code>라는 <code>type</code>은 <code>any</code>와 같다.</p>
<br/>

<h2 id="5-set">5. Set</h2>
<p><code>Set</code>은 <code>Dart</code>의 독특한 <code>type</code>으로, 유니크한 요소들로 구성되는 하나의 세트를 나타낸다.
<code>{}</code>안에 변수들을 입력하여 <code>var</code>로 선언할 수 있으며, <code>Set&lt;type&gt;</code>으로 직접 선언해줄 수도 있다.</p>
<pre><code class="language-dart">void main() {
    var numbers = {1, 2, 3, 4}; // Set&lt;int&gt; numbers = {1, 2, 3, 4}; 와 같다.
    numbers.add(1);
    numbers.add(1);
    numbers.add(1);
    numbers.add(1);
    numbers.add(1);

    print(numbers); // {1, 2, 3, 4}
}</code></pre>
<p><code>.add()</code> 매서드로 요소들을 추가하였음에도 불구하고 최초의 값이 변경되지 않았음을 알 수 있다.</p>
<br/>]]></description>
        </item>
        <item>
            <title><![CDATA[[Dart] 변수의 선언과 할당]]></title>
            <link>https://velog.io/@slight-snow/Dart-%EB%B3%80%EC%88%98%EC%9D%98-%EC%84%A0%EC%96%B8%EA%B3%BC-%ED%95%A0%EB%8B%B9</link>
            <guid>https://velog.io/@slight-snow/Dart-%EB%B3%80%EC%88%98%EC%9D%98-%EC%84%A0%EC%96%B8%EA%B3%BC-%ED%95%A0%EB%8B%B9</guid>
            <pubDate>Thu, 14 Sep 2023 05:42:57 GMT</pubDate>
            <description><![CDATA[<h2 id="1-hello-world">1. Hello World!</h2>
<p><code>Dart</code>에서는 <code>JavaScript</code>와 다른 점들이 제법 존재하는데,
<code>void main()</code>을 작성해줘야 코드가 실행될 수 있다는 점, <code>print</code>를 사용하여
기존의 <code>JavaScript</code>에서의 <code>console</code>과 유사하게 콘솔 출력을 할 수 있다는 것이다.
그리고 코드가 끝날 때마다 반드시 <code>;(세미콜론)</code>이 찍혀야 에러가 나지 않는다.</p>
<pre><code class="language-dart">void main() {
    print(&#39;Hello World!&#39;);
}</code></pre>
<p>여전히 주니어 개발자지만 이게 얼마만의 Hello World란 말인가, 후후...
괜히 학생때로 돌아간 것 같아 기분이 굉장히 묘하다.</p>
<br/>

<h2 id="2-변수-만들기">2. 변수 만들기</h2>
<p>일반적인 변수 선언 방법에는 크게 <code>var</code>, <code>type</code>, <code>dynamic</code>, <code>final</code> 이렇게 4가지가 있다.</p>
<h4 id="1-var">1) var</h4>
<ul>
<li>관습적으로 함수나 매서드 내부에 지역 변수를 선언할 때 사용된다.</li>
<li><strong>선언과 할당이 동시</strong>에 이뤄진 경우,
할당에 사용된 <code>type</code>과 동일한 <code>type</code>에 한하여 재할당이 가능하다.</li>
<li>예를 들어, <code>&#39;한글&#39;</code>(String)을 할당하고 <code>123</code>(int)를 할당하는 것은 불가능하다. </li>
</ul>
<pre><code class="language-dart">void main() {
    var name = &#39;소설&#39;;
    name = &#39;snow&#39;;
}</code></pre>
<h4 id="2-type">2) type</h4>
<ul>
<li>class에서 변수나 property를 선언할 때 사용된다.</li>
<li><code>type</code>이 지정되어 있으므로 다른 <code>type</code>의 재할당은 불가능하다.</li>
</ul>
<pre><code class="language-dart">void main() {
    String name = &#39;소설&#39;;
    name = &#39;snow&#39;;
}</code></pre>
<h4 id="3-dynamic">3) dynamic</h4>
<ul>
<li>여러가지 타입을 가질 수 있는 변수에 쓰는 키워드다.</li>
<li><strong>선언과 할당을 따로</strong>하는 <code>var</code> 선언을 통해서도 가능하다.</li>
</ul>
<pre><code class="language-dart">// dynamic
void main() {
    dynamic name = &#39;소설&#39;;
    name = 123;
}

// var
void main() {
    var name;
    name = &#39;소설&#39;;
    name = 123;
}</code></pre>
<h4 id="4-final">4) final</h4>
<ul>
<li><code>JavaScript</code>의 <code>const</code> 선언과 동일하다.
재선언, 재할당이 불가한 선언이다.</li>
<li><code>type</code>과 함께 선언할 수 있으나, 할당된 값으로 <code>type</code>을 알아서 유추해주기 때문에
굳이 <code>type</code>을 선언해주지 않아도 된다.</li>
</ul>
<pre><code class="language-dart">void main() {
    final kor = &#39;소설&#39;;
    final String eng = &#39;snow&#39;;
}</code></pre>
<h2 id="3-추가-선언문">3. 추가 선언문</h2>
<h4 id="1-late">1) late</h4>
<ul>
<li><code>late</code> 선언은 <code>var</code>이나 <code>final</code>앞에 선행하여 작성할 수 있는 선언문으로,
초기 값 없이 변수를 선언할 수 있도록 해주며 이후에 원하는 시점에 할당할 수 있게 한다.</li>
<li>할당이 이뤄지지 않는 초기의 선언만으로는 <code>print</code>로 접근할 수 없다.</li>
</ul>
<pre><code class="language-dart">void main() {
    late final name;
    print(name); //ERROR, 불가능

    late final name;
    name = &#39;소설&#39;;
    print(name); //가능
}</code></pre>
<h4 id="2-const">2) const</h4>
<ul>
<li><strong>Compitle-Time Constant</strong>를 만들어주는 선언문이다.
컴파일할 때 알고 있는 값에 사용하는 선언,
즉 앱스토어에 앱 코드를 넘기기 전에 개발자가 아는 값에 한하여 사용하는 선언이다.</li>
<li><code>final</code>과 마찬가지로 재할당, 재선언이 불가능하다.</li>
<li>API로부터 받아온다거나 사용자가 입력해야하는 예측 불가능한 값에 대해서는
<code>final</code>이나 <code>var</code>를 사용하는 것이 적절하다.</li>
</ul>
<pre><code class="language-dart">void main() {
    const name = &#39;소설&#39;; //OK
    name = &#39;snow&#39;; //ERROR, 불가능

    const name = fetchData(); //ERROR, 불가능

    //예를 들어, 아래와 같이 활용이 가능할 것이다.
    const max_user_coin = 10000;
}</code></pre>
<br/>

<h2 id="4-null-safety">4. Null Safety</h2>
<p><strong>Null Safety</strong>란, 데이터가 <code>null</code>이 될 수 있음을 명시하여
<code>null</code>이 무차별적으로 원치 않는 곳에서 참조되지 않도록 방지해주는 것을 의미한다.</p>
<p>기존의 선언과 동일하지만, <code>type</code>뒤에 <code>?</code>를 붙여서 <code>null</code>값을 가질 수 있다는 것을 명시할 수 있다.</p>
<pre><code class="language-dart">void main() {
    String name = &#39;소설&#39;;
    name = null; //ERROR, 불가능

    String? name = &#39;소설&#39;;
    name = null; //가능
}</code></pre>
<p>단, 이렇게 <code>null</code>값을 가질 수 있다고 명시한 경우
<code>null</code>이 아닌 값을 가졌을 때에 한하여 코드가 돌아가도록 하는 것이 바람직하다.</p>
<pre><code class="language-dart">void main() {

    String? name = &#39;소설&#39;;

    if ( name != null ) {
        name.isEmpty;
    }

    // 이 주석을 기준으로 위와 아래의 코드는 동일하게 작동한다.

    name?.isEmpty;

}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Dart] Beginning]]></title>
            <link>https://velog.io/@slight-snow/Dart-Beginning</link>
            <guid>https://velog.io/@slight-snow/Dart-Beginning</guid>
            <pubDate>Thu, 14 Sep 2023 02:34:34 GMT</pubDate>
            <description><![CDATA[<h2 id="■-dart란">■ &#39;Dart&#39;란?</h2>
<ul>
<li><p><code>Dart</code>는 구글 개발자들이 만든 객체 지향 언어로,
UI(User Interface)를 만드는 데에 최적화되어 있다.</p>
</li>
<li><p><code>Flutter</code>로 앱을 개발하는데 기초가 되는 언어이며,
다양하고 많은 플랫폼에 컴파일이 가능하다는 장점이 있다.</p>
</li>
</ul>
<p align="center">
  <img src="https://velog.velcdn.com/images/slight-snow/post/b46e7dc8-8297-497f-bb79-42b2bfa926f4/image.png" width="700" height="auto" loading="lazy" />
〈 Dart와 다른 언어들 간의 유사성 〉
</p>

<br/>

<h3 id="▶-dart-의-구체적인-장점">▶ Dart 의 구체적인 장점</h3>
<ul>
<li><p>기본적으로 개발을 할 때는 <strong>AOT(Ahead-Of-Time)</strong> 컴파일을 접한다.
코드를 작성하고 빌드를 하고 컴파일을 하는 순서를 따른다는 것이다.
이러한 경우, 개발 중일 때 즉각적으로 변화한 부분을 확인하고 싶다면
계속해서 컴파일 과정을 처음부터 끝까지 반복해야하기 때문에 비효율적이다.</p>
</li>
<li><p><code>Dart</code>는 <code>Dart VM(Dart Virtual Machine)</code>을 통해 <strong>JIT(Just-In-Time)</strong> 을 지원한다.
개발을 하는 과정에서 느리긴하지만, 실시간으로 변화된 부분을 확인할 수 있다.
개발이 끝나면 <strong>AOT</strong>를 기반으로 실제 기계어로 변환하여 Linux, MAC, iOS, PC 등
여러 플랫폼에 사용될 수 있도록 컴파일이 진행된다.</p>
</li>
</ul>
<p>&nbsp;&nbsp;&nbsp;▶ 즉, 개발 환경에서는 즉각적인 피드백을 받을 수 있고,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;배포 환경에서는 기계어로 컴파일되어 빠르게 동작하는 이점을 모두 챙길 수 있다는 것</p>
<ul>
<li><strong>Null Safety</strong> 를 제공한다.
개발 과정에서 개발자가 <code>null</code> 값을 참조해버리면 많은 문제가 발생하는데,
이에 대한 안전장치를 도입한 언어다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[CSS] :root 를 활용한 전역 스타일링 설정]]></title>
            <link>https://velog.io/@slight-snow/CSS-root-%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%A0%84%EC%97%AD-%EC%8A%A4%ED%83%80%EC%9D%BC%EB%A7%81-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@slight-snow/CSS-root-%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%A0%84%EC%97%AD-%EC%8A%A4%ED%83%80%EC%9D%BC%EB%A7%81-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Thu, 20 Jul 2023 07:02:02 GMT</pubDate>
            <description><![CDATA[<p>예를 들어, 같은 스타일링을 적용한 태그가 여러개 있다고 가정해보자!</p>
<pre><code class="language-javascript">// JavaScript
function Example () {
    return (
        &lt;&gt;
              &lt;div className=&quot;box-01&quot;&gt;&lt;/div&gt;
              &lt;div className=&quot;box-02&quot;&gt;&lt;/div&gt;
              &lt;div className=&quot;box-03&quot;&gt;&lt;/div&gt;
        &lt;/&gt;
    );
}

export default Example;</code></pre>
<pre><code class="language-css">/* CSS */
.box-01 {
    width: 40px;
    height: 40px;
    background-color: red;
}

.box-02 {
    width: 40px;
    height: 40px;
    background-color: yellow;
}

.box-03 {
    width: 40px;
    height: 40px;
    background-color: green;
}</code></pre>
<p><code>box-01</code>, <code>box-02</code>, <code>box-03</code> 모두 <code>background-color</code> 속성이 모두 다른 색으로 설정되어 있다.
만약 <code>box-01</code>, <code>box-02</code>, <code>box-03</code> 세 가지 태그 모두 같은 색으로 구성되어야 한다면 어떨까?</p>
<p>단순히 <code>background-color</code> 속성을 같은 색으로 설정하면 된다고 생각할 수 있지만,
<code>background-color: red;</code>, <code>background-color: red;</code>, <code>background-color: red;</code> ...
이와 같이 작성을 해두면 나중에 속성을 변경해야할 일이 생겼을 때 모두 하드코딩으로 수정해야한다.</p>
<p>그렇기에 변수를 만들어 속성에 할당해두는 편이 나중에 유지/보수하는 데에 훨씬 수월할 것이다 :)
그때 사용할 수 있는 방법이 바로 <code>:root</code> 다!</p>
<p><code>:root</code>는 최상단의 전역 스타일링인 <code>index.css</code>에 작성하면 모든 페이지, 컴포넌트에 쓸 수 있다.</p>
<pre><code class="language-css">// CSS index.css
:root {
    --backgroundColor: #000000;
}</code></pre>
<p>이렇게 작성해두면 이후 다른 CSS 파일에서 작성할 때, <code>attribute: var(value name);</code> 형식으로 적어주면 된다.
앞선 예시와 <code>:root</code> 사용방법을 활용하여 표현하면 아래와 같은 CSS 코드가 될 것이다.</p>
<pre><code class="language-css">/* CSS */
.box-01 {
    width: 40px;
    height: 40px;
    background-color: var(--backgroundColor);
}

.box-02 {
    width: 40px;
    height: 40px;
    background-color: var(--backgroundColor);
}

.box-03 {
    width: 40px;
    height: 40px;
    background-color: var(--backgroundColor);
}</code></pre>
<p>이렇게 해두면 나중에 최상단인 <code>index.css</code>에서 <code>--backgroundColor</code>의 값만 수정해주면
해당 값을 적용한 다른 모든 컴포넌트나 엘리먼트를 동시에 수정할 수 있다 :)</p>
<p>매번 하드코딩으로 바꿔가던 나를 되돌아보며.. 앞으로 <code>:root</code>를 사용하여 효율적인 스타일링을 해보자!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JavaScript] iPad Safari 전체화면 활성화]]></title>
            <link>https://velog.io/@slight-snow/JavaScript-iPad-Safari-%EC%A0%84%EC%B2%B4%ED%99%94%EB%A9%B4-%ED%99%9C%EC%84%B1%ED%99%94</link>
            <guid>https://velog.io/@slight-snow/JavaScript-iPad-Safari-%EC%A0%84%EC%B2%B4%ED%99%94%EB%A9%B4-%ED%99%9C%EC%84%B1%ED%99%94</guid>
            <pubDate>Thu, 20 Jul 2023 02:59:58 GMT</pubDate>
            <description><![CDATA[<p>먼저, 스크립트 내부에 전체화면을 활성화하고 비활성화하는 함수를 선언한다.</p>
<pre><code class="language-javascript">// 전체화면 활성화
function openFullScreen() {
  if (document.documentElement.requestFullscreen) {
    document.documentElement.requestFullscreen();
  } else if (document.documentElement.mozRequestFullScreen) {
    /* Firefox */
    document.documentElement.mozRequestFullScreen();
  } else if (document.documentElement.webkitRequestFullscreen) {
    /* Chrome, Safari and Opera */
    document.documentElement.webkitRequestFullscreen();
  } else if (document.documentElement.msRequestFullscreen) {
    /* IE/Edge */
    document.documentElement.msRequestFullscreen();
  }
}

// 전체화면 비활성화
function closeFullScreen() {
  if (document.exitFullscreen) {
    document.exitFullscreen();
  } else if (document.mozCancelFullScreen) {
    /* Firefox */
    document.mozCancelFullScreen();
  } else if (document.webkitExitFullscreen) {
    /* Chrome, Safari and Opera */
    document.webkitExitFullscreen();
  } else if (document.msExitFullscreen) {
    /* IE/Edge */
    document.msExitFullscreen();
  }
}</code></pre>
<p>이렇게 스크립트 내에 <code>openFullScreen</code> 함수와 <code>closeFullScreen</code> 함수를 작성해준 다음,
<code>onClick</code> 메서드를 사용하여 함수를 호출할 수 있도록 코드를 작성하면 된다.</p>
<pre><code class="language-javascript">function Example() {
  return (
    &lt;&gt;
      &lt;button className=&quot;full-screen-button&quot; onClick={() =&gt; openFullScreen()}&gt;
        OPEN
      &lt;/button&gt;
      &lt;button className=&quot;close-screen-button&quot; onClick={() =&gt; closeFullScreen()}&gt;
        CLOSE
      &lt;/button&gt;
    &lt;/&gt;
  );
}

export default Example;</code></pre>
<p><code>OPEN</code> 버튼을 클릭하면 전체화면이 활성화 되고,
<code>CLOSE</code> 버튼을 클릭하면 전체화면이 비활성화 된다.</p>
<p>모바일에서는 작동하지 않으며, 태블릿의 경우에도 Safari에 한하여 작동하였다.
Chrome은 전체화면이 아예 작동하지 않는다 :(</p>
<blockquote>
<p>Mobile: Chrome (X), Safari (X)
Tablet: Chrome (X), Safari (O)</p>
</blockquote>
<p><a href="https://codepen.io/DukeBoy200/pen/moaVKY?css-preprocessor=sass">Codepen</a> 의 코드를 참고하여 실제로 사용해보고 정리해보았다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React.js] tabIndex 로 Tab 이동 설정하기]]></title>
            <link>https://velog.io/@slight-snow/React.js-Tab%EC%9C%BC%EB%A1%9C-%EC%9D%B4%EB%8F%99%EB%90%98%EC%A7%80-%EC%95%8A%EB%8A%94-%ED%83%9C%EA%B7%B8%EC%97%90-Tab%EC%9D%B4%EB%8F%99%EC%9D%84-%EB%B6%80%EC%97%AC%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@slight-snow/React.js-Tab%EC%9C%BC%EB%A1%9C-%EC%9D%B4%EB%8F%99%EB%90%98%EC%A7%80-%EC%95%8A%EB%8A%94-%ED%83%9C%EA%B7%B8%EC%97%90-Tab%EC%9D%B4%EB%8F%99%EC%9D%84-%EB%B6%80%EC%97%AC%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Wed, 12 Jul 2023 01:37:21 GMT</pubDate>
            <description><![CDATA[<p>해당 태그 내부에 <code>tabIndex=&quot;0&quot;</code> 을 추가한다.</p>
<pre><code class="language-javascript">import { ReactComponent as Arrow } from &quot;../assets/arrow.svg&quot;;

&lt;Arrow
  className=&quot;back-button&quot;
  stroke=&quot;#e10098&quot;
  onClick={() =&gt; goToPrevPage()}
  onKeyUp={(event) =&gt;
    event.key === &quot;Enter&quot; || event.key === &quot; &quot;
      ? goToPrevPage()
      : &quot;&quot;
 }
 tabIndex=&quot;0&quot;
/&gt;</code></pre>
<p>위의 예시는 <code>arrow.svg</code> 이미지 파일을 <code>Arrow</code> 라는 컴포넌트 형식으로
<code>return</code>되는 JSX 코드 내부의 원하는 위치에 끼워 넣어주고,
<code>tabIndex</code> 속성을 부여한 코드다.</p>
<p>기존에 <code>Arrow</code>는 <code>button</code> 태그가 아니기 때문에 <code>Tab</code> 키를 눌러도 포커싱되지 않는다.
하지만 <code>tabIndex</code>를 부여함으로써 결과적으로 <code>Tab</code> 으로 포커스가 가능해졌다.</p>
<p>마우스를 움직이지 않고도 <code>Tab</code> 으로 포커스를 <code>Arrow</code>로 옮긴 다음 <code>Enter</code> 키를 누르는 것만으로
이전 페이지로 가는 <code>goToPrevPage()</code> 함수를 실행시킬 수 있다 :)</p>
<p><code>tabIndex</code> 속성에는 꼭 <code>0</code>만 부여되는 것이 아니다!
정리하자면 다음과 같다.</p>
<blockquote>
<p>tabIndex=&quot;0&quot; //상호작용하지 않는 태그에도 포커스 이동 기능 설정
tabIndex=&quot;-1&quot; //상호작용이 되는 태그에도 포커스 이동 기능 제거
tabIndex=&quot;1&quot; //포커스 순서를 정하여 1부터 순서대로 이동하도록 설정</p>
</blockquote>
<p>이전에는 <code>Tab</code>으로 포커스를 필요한 곳에 이동시키는 것이 왜 중요한지 몰랐는데,
웹 접근성의 관점에서 굉장히 중요하다는 것을 알게 되었다 :0</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[CSS] Tab 키로 이동할 경우에만 Focus CSS가 적용되도록 하는 방법]]></title>
            <link>https://velog.io/@slight-snow/CSS-TabKeyboard-Press%EC%8B%9C%EC%97%90%EB%A7%8C-Focus-CSS%EA%B0%80-%EC%A0%81%EC%9A%A9%EB%90%98%EB%8F%84%EB%A1%9D-%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@slight-snow/CSS-TabKeyboard-Press%EC%8B%9C%EC%97%90%EB%A7%8C-Focus-CSS%EA%B0%80-%EC%A0%81%EC%9A%A9%EB%90%98%EB%8F%84%EB%A1%9D-%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Wed, 12 Jul 2023 01:34:53 GMT</pubDate>
            <description><![CDATA[<p><code>:focus-visible</code> 로 CSS Style을 아래와 같이 정의해주면 <code>outline</code>이 평소에는 보이지 않고
<code>Tab</code>키나 키보드 제어를 통해 이동하는 경우에만 커스텀해둔 <code>outline</code>이 나타난다.</p>
<pre><code class="language-CSS">/* FOCUS CSS ONLY  */
:focus-visible {
  /* Remove Default Focus Style */
  outline: none;
  /* Custon Focus Styles */
  outline-color: rgb(80, 150, 255);
  outline-style: solid;
  outline-width: 8px;
}</code></pre>
<p>■ <a href="https://stackoverflow.com/questions/31402576/enable-focus-only-on-keyboard-use-or-tab-press">Stack Overflow</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Socket.IO] Double Emit/On 현상 확인 및 해결 방법]]></title>
            <link>https://velog.io/@slight-snow/Socket.IO-Double-Emit-%ED%98%84%EC%83%81-%ED%99%95%EC%9D%B8-%EB%B0%8F-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@slight-snow/Socket.IO-Double-Emit-%ED%98%84%EC%83%81-%ED%99%95%EC%9D%B8-%EB%B0%8F-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Mon, 19 Jun 2023 15:50:15 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>   ✴︎
     글을 쓰기에 앞서,
     문제를 해결하고 보니 Double Emit Issue 라기보다는
     Double On Issue 라고 보는 것이 가깝지 않나 생각이 듭니다.</p>
<p>   구글링 결과 해당 현상을 Emit Events Twice 로 부르는 경우가 많아
     편의상 제목에 Emit 도 추가하였습니다 :)</p>
</blockquote>
<br />

<h2 id="✓-double-emiton-현상-확인">✓ Double Emit/On 현상 확인</h2>
<p><code>socket.io</code>를 활용하여 웹 채팅 어플리케이션을 만들던 중,
의도치 않게 채팅이 두 번 수신되는 현상을 확인했다.</p>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/fa111b41-da19-4b66-99c3-1b7aaf609f57/image.png" alt=""></p>
<p>위의 사진처럼 각각의 사용자가 전송버튼을 한 번만 눌렀음에도 불구하고
메세지의 수신은 두 번 이뤄지는 것을 발견할 수 있었다.</p>
<p><code>socket.io</code>를 활용한 코드는 아래와 같다.</p>
<pre><code class="language-javascript">#Client Side

//전송버튼을 눌렀을 때 실행되는 sendMessage 함수
const sendMessage = async () =&gt; {
  let messageData;
  if (message !== &#39;&#39;) {
    messageData = {
      room: room,
      author: name,
      message: message,
      time: String(new Date(Date.now()).getHours()).padStart(2, &#39;0&#39;)
          + &#39;:&#39; + String(new Date(Date.now()).getMinutes()).padStart(2, &#39;0&#39;),
    }
  }

  //socket.emit 메서드를 통해 Client &gt; Server로 데이터를 전송
  await socket.emit(&#39;send_message&#39;, messageData);
  setMessageList((list) =&gt; [...list, messageData]);
}

useEffect(() =&gt; {
  //socket.on 메서드를 통해 Server &gt; Client로 데이터를 수신
  socket.on(&#39;receive_message&#39;, (data) =&gt; {
    setMessageList((list) =&gt; [...list, data]);
  })
}, [socket])</code></pre>
<pre><code class="language-javascript">#Server Side

io.on(&#39;connection&#39;, (socket) =&gt; {
  //socket.on 메서드를 통해 Client &gt; Server로 데이터를 수신
  socket.on(&#39;send_message&#39;, (data) =&gt; {
    console.log(data);
    //socket.emit 메서드를 통해 Server &gt; Client로 데이터를 전송
    socket.to(data.room).emit(&#39;receive_message&#39;, data);
  })
})</code></pre>
<p>코드에서 <code>socket.emit</code>이나 <code>socket.on</code> 메서드가 중복되어 작성되어 있는 곳은 없었다.</p>
<br />

<h2 id="✓-double-emiton-원인-분석">✓ Double Emit/On 원인 분석</h2>
<p>Server Side의 코드를 여러모로 수정해보았지만 좀처럼 해결되지 않아서,
이번엔 Client Side에서 console을 이용하여 문제 발생 지점을 찾고자 하였다.</p>
<p>가능성이 높은 <code>useEffect</code> 내부에 console을 찍어보았다.</p>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/7cb896b5-e832-45c9-813c-1c754d659fab/image.png" alt=""></p>
<p>그 결과, 
처음에 채팅방을 들어갔을 때는 <code>socket.on</code> 메서드가 실행되지 않고
이외의 부분만 <code>useEffect</code>에 의해 동작하는 것을 알 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/de18920e-8765-4d67-8da4-824f4e050ccf/image.png" alt=""></p>
<p>하지만!</p>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/ca7bce63-2ab2-4ad4-9aae-0432db835233/image.png" alt=""></p>
<p>메세지를 보내면 이처럼 <code>socket.on</code> 메서드가 두 번 실행되면서
현재 메세지 리스트에 수신한 메세지를 두 번 입력하는 것을 볼 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/4f628b3e-ba2e-4e44-aa76-51c90f521ecf/image.gif" alt=""></p>
<p>Server Side 의 <code>socket.emit</code> 코드에 console을 찍어보았지만,
해당 코드에서는 console이 중복으로 출력되지 않았으니...
아무래도 <code>useEffect</code> 내부에 뭔가 조치를 취해줘야 할 것 같다.</p>
<br />

<h2 id="✓-double-emiton-문제-해결">✓ Double Emit/On 문제 해결</h2>
<p>다행히 구글링을 통해 <a href="https://stackoverflow.com/questions/56278700/socket-io-emit-events-twice">Stack Overflow</a> 에서 그 답을 찾을 수 있었다!
( 검색을 생활화합시다... :D )</p>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/27b35b36-cd98-4f09-acea-9517b54a62f8/image.png" alt=""></p>
<p>요컨대, <code>useEffect</code> 내부에 <code>socket.on</code> 메서드에 이어 <code>socket.off</code> 메서드를 통해
<code>socket.io</code>의 리스너가 꺼지도록 <code>return</code> 문을 작성해주어야 한다는 것이다.</p>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/e3938245-0444-41d1-8bf0-254e7893e39f/image.png" alt=""></p>
<p>그리하여 <code>useEffect</code> 내부에 위와 같이 <code>return</code> 문을 통해
<code>socket.off</code> 메서드가 실행되도록 코드를 추가해주었다.</p>
<p>새로고침을 하고 다시 메세지를 보내보았다.</p>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/1150f82b-38c0-49d3-9cee-832f95e3ddbf/image.png" alt=""></p>
<p>드디어!
정상적으로 메세지가 한 번만 수신되고, 출력되는 것을 확인할 수 있었다.</p>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/d221d4c4-4ba2-4aa3-9afb-4dabc05baafd/image.gif" alt=""></p>
<br />

<hr>
<h2 id="✦-후일담">✦ &nbsp;후일담</h2>
<p><code>socket.io</code> 튜토리얼 영상 댓글에도 해당 문제에 관한 얘기가 있어서 확인해보니
<code>useEffect</code> 로 더블 렌더링 현상을 마주한 경우 <code>useMemo</code> 를 사용해보라는 글이었다.</p>
<p>하지만 <code>useMemo</code> 를 사용했음에도 해당 현상이 좀처럼 고쳐지질 않아서
당황하고 있었을 찰나, 구글링을 통해 해결 방법을 찾을 수 있어서 다행이었다 :)</p>
<p>이전 프로젝트에서 다룰까 했다가 멈췄던 <code>socket.io</code> 를 직접 다뤄보고
메세지가 정상적으로 송수신되는 것을 확인해보니 기분이 묘하다.</p>
<p>성장했다는 느낌?!</p>
<p>아직 코린이이긴 하지만!
어제보다 성장한 오늘, 오늘보다 성장한 내일을 마주하다보면 언젠가 스스로를
많이 성장했다고 느낄 수 있을 것이라 믿는다.</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JavaScript] 원하는 자리수까지 표현하는 방법, padStart()]]></title>
            <link>https://velog.io/@slight-snow/JavaScript-%EC%9B%90%ED%95%98%EB%8A%94-%EC%9E%90%EB%A6%AC%EC%88%98%EA%B9%8C%EC%A7%80-%ED%91%9C%ED%98%84%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95-padStart</link>
            <guid>https://velog.io/@slight-snow/JavaScript-%EC%9B%90%ED%95%98%EB%8A%94-%EC%9E%90%EB%A6%AC%EC%88%98%EA%B9%8C%EC%A7%80-%ED%91%9C%ED%98%84%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95-padStart</guid>
            <pubDate>Tue, 13 Jun 2023 09:46:55 GMT</pubDate>
            <description><![CDATA[<p><code>socket.io</code>를 활용하여 채팅창을 만들던 중, 메세지를 보냈을 때의 시간이 표기되길 원하여
메세지의 내용에 <code>time: new Date(Date.now()).getHours() + &#39;:&#39; + new Date(Date.now()).getMinutes(),</code> 형식으로
현재 시간을 포함하여 <code>socket</code> 통신에 전송해주었다.</p>
<p>예를 들어 오전 9시 5분이라고 가정하면 09:05 와 같은, 즉 00:00 형식으로 표기되는 것이 일반적인데
실제로 작동상에서는 9:5 로 표기되었다.</p>
<br />

<p>시간의 길이를 측정하여 2자리 미만일 경우 앞에 0을 물리적으로 추가해주는 함수를 짜고있던 찰나!
<code>String.prototype.padStart()</code> 라는 편리한 메서드를 발견했다.</p>
<p>▶ MDN 공식문서 : <a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/String/padStart">padStart() MDN Web Docs</a></p>
<br />

<hr>
<br />

<p>사용방법은 다음과 같다.
기본적으로 <code>padStart()</code> 메서드는 <code>String</code> 타입에만 동작하며,
<code>padStart()</code>의 인자로 자릿수, 입력할 문자열을 넣어주면 된다.</p>
<p>예를 들어, <code>String</code> 타입의 변수 <code>letter</code> 가 있다고 가정하고 (<code>const letter = &#39;1&#39;;</code>)
출력하고 싶은 결과가 <code>001</code> 이라면, <code>letter.padStart(3, &#39;0&#39;)</code> 과 같이 사용해주면 된다!
해석하자면 <code>length</code>가 3이 될 때까지 <code>&#39;0&#39;</code>을 넣겠다는 것이다.</p>
<p>꼭 <code>String</code> 타입의 숫자가 아니어도 되며,
두 번째 인자인 문자열을 입력하지 않고 실행하면 공백이 입력된다.</p>
<pre><code>&#39;abc&#39;.padStart(10);         // &quot;       abc&quot;
&#39;abc&#39;.padStart(10, &quot;foo&quot;);  // &quot;foofoofabc&quot;
&#39;abc&#39;.padStart(6,&quot;123465&quot;); // &quot;123abc&quot;
&#39;abc&#39;.padStart(8, &quot;0&quot;);     // &quot;00000abc&quot;
&#39;abc&#39;.padStart(1);          // &quot;abc&quot;</code></pre><br />

<hr>
<br />

<p>위에서 시간에 포함될 데이터에 <code>new Date(Date.now()).getHours()</code> 을 담아주었는데,
<code>getHours()</code>를 통해 출력되는 값은 <code>Number</code> 타입이다.
그렇기에 아래와 같은 TypeError 가 발생한다.</p>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/4ba503c3-b58f-41e7-9683-484c38bf5196/image.png" alt=""></p>
<p>그래서! 앞에 <code>String()</code>을 붙여 출력되는 값을 <code>String</code> 타입으로 바꿔준 후,
<code>padStart()</code> 메서드를 사용하면 정상적으로 작동하는 것을 확인할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/4c3ce8fe-215f-4acd-acaa-a8d0aee4877f/image.png" alt=""></p>
<p>직접 함수를 만들어서 <code>value</code>, <code>targetLength</code>, <code>filler</code> 를 인자로 받고 공간을 채울 수도 있었겠지만...
역시 메서드는 알면 알수록 편리하다는 생각이 든다 :0</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JavaScript] form의 submit으로 인한 새로고침 방지]]></title>
            <link>https://velog.io/@slight-snow/JavaScript-form%EC%9D%98-submit%EC%9C%BC%EB%A1%9C-%EC%9D%B8%ED%95%9C-%EC%83%88%EB%A1%9C%EA%B3%A0%EC%B9%A8-%EB%B0%A9%EC%A7%80</link>
            <guid>https://velog.io/@slight-snow/JavaScript-form%EC%9D%98-submit%EC%9C%BC%EB%A1%9C-%EC%9D%B8%ED%95%9C-%EC%83%88%EB%A1%9C%EA%B3%A0%EC%B9%A8-%EB%B0%A9%EC%A7%80</guid>
            <pubDate>Wed, 10 May 2023 14:25:32 GMT</pubDate>
            <description><![CDATA[<p><code>&lt;form&gt;</code> 내부의 <code>type=&#39;submit&#39;</code>의 <code>&lt;button&gt;</code>을 사용하여 폼을 제출했더니,
의도와는 달리 페이지가 새로고침되는 것을 확인했다.</p>
<p>검색해서 조금 알아보니, <code>submit</code>은 <code>href</code>, <code>replace</code>와 마찬가지로
페이지를 이동시키는 기능이 자체적으로 내장되어 있는 듯하다.</p>
<br/>

<p>이렇듯 원치 않는 이벤트가 발생할 때,
해당 이벤트를 중지할 수 있는 방법이 바로 <code>event.preventDefault()</code> 메서드다.
이 메서드에 대한 공식 문서는 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault">여기</a> 통해 확인할 수 있다.</p>
<br/>

<p>나의 경우를 천천히 풀어보자면, 다음과 같다.</p>
<pre><code class="language-javascript">function Example() {
  function action(event) {
    console.log(&#39;Hello World!&#39;); 
  };

  return
  (
      &lt;&gt;
      &lt;form onSubmit={(event) =&gt; action(event)}&gt;
        &lt;input type=&#39;text&#39; name=&#39;name&#39; placeholder=&#39;Enter your name.&#39; /&gt;
        &lt;button type=&#39;submit&#39; /&gt;
      &lt;/form&gt;
    &lt;/&gt;
  )
};

export default Example;</code></pre>
<p><code>Example</code>이라는 컴포넌트 내부에 <code>&lt;form&gt;</code> 태그가 있고, <code>&lt;button&gt;</code>을 누르면
<code>action</code> 함수가 실행되어 콘솔 창에 &#39;Hello World!&#39;가 입력되는 구조다.</p>
<br/>

<p>허나, <code>action</code> 함수가 실행되고 해당 화면에 고정되어 있었으면 좋겠는데,
이 상태로는 계속 페이지가 새로고침될 것이다.</p>
<br/>

<p>이때 <code>event.preventDefault()</code> 메서드를 사용하면 새로고침을 쉽게 방지할 수 있다.
나머지 코드들은 그대로 두고, 실행되는 함수 내부에 <code>event.preventDefault()</code>를 작성해주면 된다.</p>
<pre><code class="language-javascript">function Example() {
  function action(event) {
    //----------***----------//
    event.preventDefault();
    //----------***----------//
    console.log(&#39;Hello World!&#39;); 
  };

  return
  (
      &lt;&gt;
      &lt;form onSubmit={(event) =&gt; action(event)}&gt;
        &lt;input type=&#39;text&#39; name=&#39;name&#39; placeholder=&#39;Enter your name.&#39; /&gt;
        &lt;button type=&#39;submit&#39; /&gt;
      &lt;/form&gt;
    &lt;/&gt;
  )
};

export default Example;</code></pre>
<p>위와 같이 실행되는 함수 내부에 작성해주면 페이지가 더 이상 새로고침되지 않는다!
<br/></p>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/acb8afc1-8ab2-499e-a78d-0076ed7b3cdf/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[CSS] 자체적으로 transition을 갖는 방법, 'animation']]></title>
            <link>https://velog.io/@slight-snow/CSS-%EC%9E%90%EC%B2%B4%EC%A0%81%EC%9C%BC%EB%A1%9C-transition%EC%9D%84-%EA%B0%96%EB%8A%94-%EB%B0%A9%EB%B2%95-animation</link>
            <guid>https://velog.io/@slight-snow/CSS-%EC%9E%90%EC%B2%B4%EC%A0%81%EC%9C%BC%EB%A1%9C-transition%EC%9D%84-%EA%B0%96%EB%8A%94-%EB%B0%A9%EB%B2%95-animation</guid>
            <pubDate>Thu, 20 Apr 2023 02:09:10 GMT</pubDate>
            <description><![CDATA[<p>특정 엘리먼트가 꼭 <code>hover</code>, <code>focus</code>, <code>active</code> 와 같은
CSS 선택자가 함께 사용되지 않더라도 렌더링될 때 효과를 가질 순 없을까?</p>
<p>그걸 가능하게 해주는 것이 바로 CSS의 <code>animation</code> 기능이다.</p>
<h3 id="애니메이션animation-이란">애니메이션(animation) 이란?</h3>
<p>CSS3 애니메이션은 <strong>엘리먼트에 적용되는 CSS 스타일을 다른 CSS 스타일로 부드럽게 전환시켜 주는 기능</strong>으로, 다음의 하위 속성을 갖는다.
<a href="https://developer.mozilla.org/ko/docs/Web/CSS/CSS_Animations/Using_CSS_animations">#MDN 애니메이션 공식 문서</a></p>
<ul>
<li><p><code>animation-name</code> (en-US)
: 이 애니메이션의 중간 상태를 지정한다.
중간 상태는 <code>@keyframes</code> 규칙을 이용하여 기술한다.</p>
</li>
<li><p><code>animation-delay</code>
: 엘리먼트가 로드되고 나서 언제 애니메이션이 시작될지를 지정한다.</p>
</li>
<li><p><code>animation-direction</code>
: 애니메이션이 종료되고 다시 처음부터 시작할지 역방향으로 진행할지 지정한다.</p>
</li>
<li><p><code>animation-duration</code>
: 한 싸이클의 애니메이션이 얼마에 걸쳐 일어날지 지정한다.</p>
</li>
<li><p><code>animation-iteration-count</code> (en-US)
: 애니메이션이 몇 번 반복될지 지정한다.
<code>infinite</code>로 지정하여 무한히 반복하는 것도 가능하다.</p>
</li>
<li><p><code>animation-play-state</code> (en-US)
: 애니메이션을 멈추거나 다시 시작할 수 있다.</p>
</li>
<li><p><code>animation-timing-function</code> (en-US)
: 중간 상태들의 전환을 어떤 시간간격으로 진행할지 지정한다.</p>
</li>
<li><p><code>animation-fill-mode</code>
: 애니메이션이 시작되기 전이나 끝나고 난 후 어떤 값이 적용될지 지정한다.</p>
</li>
</ul>
<br/>

<h3 id="애니메이션animation-사용하기">애니메이션(animation) 사용하기</h3>
<p><code>example.js</code></p>
<pre><code class="language-javascript">function Example() {
  return (
    &lt;&gt;
      &lt;div className=&#39;example_container&#39;&gt;Container&lt;/div&gt;
    &lt;/&gt;
  )
}</code></pre>
<p><code>example.css</code></p>
<pre><code class="language-CSS">.example_container {
  display: flex;
  width: 10px;
  height: 10px;
  animation-name: fade-in;
  animation-duration: 0.75s;
  animation-fill-mode: forwards;
}

@keyframes fade-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}</code></pre>
<p><code>example.js</code>에는 클래스명이 &#39;example_container&#39;인 <code>&lt;div&gt;</code> 엘리먼트가 있다.
<code>example.css</code>에는 &#39;example_container&#39; 클래스에 대한 속성이 명시되어 있다.</p>
<hr>
<p>그리고 그 아래의 <code>@keyframes</code>를 통해 애니메이션의 이름과 효과를 설정할 수 있다.</p>
<pre><code class="language-CSS">@keyframes 애니메이션 이름 {
  from {
    애니메이션이 시작될 때의 속성
  }
  to {
      애니메이션이 끝날 때의 속성
  }
}</code></pre>
<p>이렇게 말이다!</p>
<hr>
<p><code>example.css</code>를 풀어보자면,
&#39;example_container&#39; 클래스에 대해 <code>@keyframes</code>로 명시된 &#39;fade-in&#39; 애니메이션을 적용하고, 애니메이션은 <code>animation-duration</code>으로 설정된 0.75초 동안 진행되도록 한다. 그리고 <code>animation-fill-mode</code>속성을 forwards로 설정하여 애니메이션이 끝나면 끝난 그대로 상태를 유지하도록 하겠다는 것을 의미한다.</p>
<p>즉, <code>example.css</code>에 의해 &#39;example_container&#39;는 처음에는 투명도가 0이었다가 0.75초 동안 투명도가 1로 바뀌는 애니메이션이 등장하고, 투명도가 1인 상태로 끝나 그 상태를 유지하게 된다.</p>
<p>CSS 선택자를 사용하지 않고서도 자체적으로 CSS를 적용가능하게 해주는
애니메이션(animation)에 대해 알아보았다 :)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JavaScript] setTimeout() 을 활용한 렌더링 지연]]></title>
            <link>https://velog.io/@slight-snow/JavaScript-setTimeout-%EC%9D%84-%ED%99%9C%EC%9A%A9%ED%95%9C-%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%A7%80%EC%97%B0</link>
            <guid>https://velog.io/@slight-snow/JavaScript-setTimeout-%EC%9D%84-%ED%99%9C%EC%9A%A9%ED%95%9C-%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%A7%80%EC%97%B0</guid>
            <pubDate>Thu, 20 Apr 2023 01:35:11 GMT</pubDate>
            <description><![CDATA[<h2 id="settimeout의-구성-요소">setTimeout()의 구성 요소</h2>
<p><code>setTimeout()</code>은 <code>function</code>, <code>code</code>, <code>delay</code>를 매개변수로 받는다.</p>
<pre><code class="language-javascript">setTimeout(() =&gt; { //function
    console.log(&#39;Hello World!&#39;); //code
}, 1000) //delay</code></pre>
<p>위와 같은 형태로 작성할 수 있는데,
<code>delay</code>에 해당하는 시간이 만료되면 <code>function</code>이나 <code>code</code>를 실행하는 메서드다.</p>
<p>여기서 <code>delay</code>는 밀리초 단위로, 위의 예시의 경우 1000ms = 1s
즉, 1초의 지연 이후에 <code>console.log(&#39;Hello World!&#39;)</code>를 실행하게 된다.</p>
<p>개인 포트폴리오 페이지를 제작하면서 특정 엘리먼트나 컴포넌트가 몇 초의 시간지연 이후에 렌더링 되면 좋겠다고 생각했고, 다음과 같이 코드를 작성했었다.</p>
<pre><code class="language-javascript">function Example() {
  return (
    &lt;&gt;
    {setTimeout(() =&gt; {
          &lt;div&gt;Hello World!&lt;/div&gt;
    }, 1000)}
    &lt;/&gt;
  )
};

export default Example;</code></pre>
<p>Compiled Successfully! 라는 문구가 등장하며 컴파일 에러를 마주하진 않았으나,
1초 뒤에 등장하길 바랬던 Hello World! 는 어디가고 <code>delay</code>에 작성해둔 숫자만 표시됐다.</p>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/7a1996ea-330b-4d4c-a0b1-593701a19b21/image.gif" alt=""></p>
<br/>

<h2 id="settimeout이-작동하지-않는-이유">setTimeout()이 작동하지 않는 이유</h2>
<p><code>setTimeout()</code>은 JavaScript 함수로, 콜백 되는 함수 역시 일반적인 JavaScript 함수이어야한다. 내가 작성한 코드는 <strong>일반적인 함수가 아니라 JSX 태그를 반환하는 코드이기 때문에 정상적으로 작동하지 않는 것</strong>이었다.</p>
<br/>

<h2 id="settimeout을-활용한-렌더링-지연">setTimeout()을 활용한 렌더링 지연</h2>
<p>그렇다면 렌더링되는 코드 안에 직접적으로 <code>setTimeout()</code>을 사용하여 렌더링을 지연할 수는 없다는 얘긴데, 방법이 없을까? 생각하던 중 <code>setTimeout()</code>을 렌더링 바깥에 지정해주고, <code>useState</code>와 <code>useEffect</code>를 사용하여 상태의 변환을 간접적으로 활용해보는건 어떨까하는 생각이 들었다.</p>
<pre><code class="language-javascript">//01
import React, { useState, useEffect } from &#39;react&#39;;

function Example() {
  //02
  const [show, setShow] = useState(false);

  //03
  useEffect(() =&gt; {
    setTimeout(() =&gt; {
      setShow(true);
    }, 1000)
  }, [])

  return (
    &lt;&gt;
    //04
    {show &amp;&amp; (
     &lt;div&gt;Hello World!&lt;/div&gt;
     )}
    &lt;/&gt;
  )
}

export default Example;</code></pre>
<p>차근차근 코드를 살펴보도록 하자!</p>
<h3 id="01">01</h3>
<p><code>useState</code>와 <code>useEffect</code>를 사용하기 위해 <code>react</code>로부터 가져와 import 해준다.</p>
<h3 id="02">02</h3>
<p>초기의 상태가 <code>false</code>인 <code>show</code>라는 변수와 해당 변수의 상태를 변경하는 상태변경함수 <code>setShow</code>를 <code>useState</code>를 사용하여 선언해준다.</p>
<h3 id="03">03</h3>
<p><code>useEffect</code>를 사용하여 <code>setTimeout()</code>을 설정해준다. 위의 예시에서는 1000ms, 즉 1초 뒤에 <code>show</code>의 상태가 <code>setShow</code> 상태변경함수를 통해 <code>true</code>가 된다. <code>show</code>가 조건에 따라 <code>true</code>가 됐다가 <code>false</code>가 되는 것을 원한다면, 다음과 같이 작성하면 된다.</p>
<pre><code class="language-javascript">useEffect(() =&gt; {
  if (조건) {
      setTimeout(() =&gt; {
      상태변경함수(true);
    }, delay)
  } else {
    상태변경함수(false);
}, [])</code></pre>
<p>이렇게 작성해주면 조건에 따라 <code>true</code>와 <code>false</code>를 넘나드는 <code>show</code>를 만들 수 있고,
이를 통해 사용자의 상호작용에 의해 렌더링될 때마다 시간지연을 줄 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/slight-snow/post/2ca551d1-b58b-449d-8e9e-f35f399dae04/image.gif" alt=""></p>
<h3 id="04">04</h3>
<p><code>&amp;&amp;</code> 연산자를 통해 둘 다 참인 경우에 렌더링 되도록 한다.
<strong>JSX 태그의 경우, 빈 태그를 포함하여 모든 태그는 기본적으로 <code>true</code>값을 지닌다.</strong>
즉, <code>{show &amp;&amp; &lt;div&gt;Hello World!&lt;/div&gt;}</code>에서 JSX 태그에 해당하는 <code>&lt;div&gt;Hello World!&lt;/div&gt;</code>는 항상 <code>true</code>이다. </p>
<p>1) <code>show</code>가 <code>true</code>이면 ‣ <code>true &amp;&amp; true</code>가 되어
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<code>&amp;&amp;</code> 연산의 결과가 <code>true</code>이므로 Hello World! 가 정상적으로 출력되고,
2) <code>show</code>가 <code>false</code>이면 ‣ <code>false &amp;&amp; true</code>가 되어
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<code>&amp;&amp;</code> 연산의 결과가 <code>false</code>이므로 Hello World! 가 출력되지 않는 것이다.</p>
<p>이렇게 <code>setTimeout()</code>을 활용한 렌더링 지연에 대해 알아보았다!
<code>useState</code>, <code>useEffect</code>에 대한 개념도 어느 정도 갖춰져 있어야 하니 잘 모르겠다면 꼭 검색을 해서 사용방법을 되새겨보도록 하자 :)</p>
<p>✴︎ 잘못된 정보나 표기 오류가 있는 경우 의견이나 피드백 모두 환영합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Algorithm] 성격 유형 검사하기 #2022 KAKAO TECH INTERNSHIP
]]></title>
            <link>https://velog.io/@slight-snow/Algorithm-%EC%84%B1%EA%B2%A9-%EC%9C%A0%ED%98%95-%EA%B2%80%EC%82%AC%ED%95%98%EA%B8%B0-2022-KAKAO-TECH-INTERNSHIP</link>
            <guid>https://velog.io/@slight-snow/Algorithm-%EC%84%B1%EA%B2%A9-%EC%9C%A0%ED%98%95-%EA%B2%80%EC%82%AC%ED%95%98%EA%B8%B0-2022-KAKAO-TECH-INTERNSHIP</guid>
            <pubDate>Fri, 14 Apr 2023 16:24:06 GMT</pubDate>
            <description><![CDATA[<h4 id="-난이도">• 난이도</h4>
<p>: Level.1</p>
<h4 id="-정답률">• 정답률</h4>
<p>: 45%</p>
<h4 id="-문제">• 문제:</h4>
<p>: 성격 유형 검사하기 #2022 KAKAO TECH INTERNSHIP</p>
<h4 id="-풀이-시간">• 풀이 시간:</h4>
<p>: 16m</p>
<h4 id="-설명">• 설명:</h4>
<p>: 나만의 카카오 성격 유형 검사지를 만들려고 합니다.
성격 유형 검사는 다음과 같은 4개 지표로 성격 유형을 구분합니다. 성격은 각 지표에서 두 유형 중 하나로 결정됩니다.</p>
<table>
<thead>
<tr>
<th align="center">지표 번호</th>
<th align="center">성격 유형</th>
</tr>
</thead>
<tbody><tr>
<td align="center">1번 지표</td>
<td align="center">라이언형(R), 튜브형(T)</td>
</tr>
<tr>
<td align="center">2번 지표</td>
<td align="center">콘형(C), 프로도형(F)</td>
</tr>
<tr>
<td align="center">3번 지표</td>
<td align="center">제이지형(J), 무지형(M)</td>
</tr>
<tr>
<td align="center">4번 지표</td>
<td align="center">어피치형(A), 네오형(N)</td>
</tr>
<tr>
<td align="center">4개의 지표가 있으므로 성격 유형은 총 16(=2 x 2 x 2 x 2)가지가 나올 수 있습니다. 예를 들어, &quot;RFMN&quot;이나 &quot;TCMA&quot;와 같은 성격 유형이 있습니다.</td>
<td align="center"></td>
</tr>
</tbody></table>
<p>검사지에는 총 <code>n</code>개의 질문이 있고, 각 질문에는 아래와 같은 7개의 선택지가 있습니다.</p>
<ul>
<li><code>매우 비동의</code></li>
<li><code>비동의</code></li>
<li><code>약간 비동의</code></li>
<li><code>모르겠음</code></li>
<li><code>약간 동의</code></li>
<li><code>동의</code></li>
<li><code>매우 동의</code></li>
</ul>
<p>각 질문은 1가지 지표로 성격 유형 점수를 판단합니다.</p>
<p>예를 들어, 어떤 한 질문에서 4번 지표로 아래 표처럼 점수를 매길 수 있습니다.</p>
<table>
<thead>
<tr>
<th align="left">선택지</th>
<th align="left">성격 유형 점수</th>
</tr>
</thead>
<tbody><tr>
<td align="left">매우 비동의</td>
<td align="left">네오형 3점</td>
</tr>
<tr>
<td align="left">비동의</td>
<td align="left">네오형 2점</td>
</tr>
<tr>
<td align="left">약간 비동의</td>
<td align="left">네오형 1점</td>
</tr>
<tr>
<td align="left">모르겠음</td>
<td align="left">어떤 성격 유형도 점수를 얻지 않습니다</td>
</tr>
<tr>
<td align="left">약간 동의</td>
<td align="left">어피치형 1점</td>
</tr>
<tr>
<td align="left">동의</td>
<td align="left">어피치형 2점</td>
</tr>
<tr>
<td align="left">매우 동의</td>
<td align="left">어피치형 3점</td>
</tr>
</tbody></table>
<p>이때 검사자가 질문에서 <code>약간 동의</code> 선택지를 선택할 경우 어피치형(A) 성격 유형 1점을 받게 됩니다. 만약 검사자가 <code>매우 비동의</code> 선택지를 선택할 경우 네오형(N) 성격 유형 3점을 받게 됩니다.</p>
<p><strong>위 예시처럼 네오형이 비동의, 어피치형이 동의인 경우만 주어지지 않고, 질문에 따라 네오형이 동의, 어피치형이 비동의인 경우도 주어질 수 있습니다.</strong>
하지만 각 선택지는 고정적인 크기의 점수를 가지고 있습니다.</p>
<ul>
<li><code>매우 동의</code>나 <code>매우 비동의</code> 선택지를 선택하면 3점을 얻습니다.</li>
<li><code>동의</code>나 <code>비동의</code> 선택지를 선택하면 2점을 얻습니다.</li>
<li><code>약간 동의</code>나 <code>약간 비동의</code> 선택지를 선택하면 1점을 얻습니다.</li>
<li><code>모르겠음</code> 선택지를 선택하면 점수를 얻지 않습니다.</li>
</ul>
<p>검사 결과는 모든 질문의 성격 유형 점수를 더하여 각 지표에서 더 높은 점수를 받은 성격 유형이 검사자의 성격 유형이라고 판단합니다. 단, 하나의 지표에서 각 성격 유형 점수가 같으면, 두 성격 유형 중 사전 순으로 빠른 성격 유형을 검사자의 성격 유형이라고 판단합니다.</p>
<p>질문마다 판단하는 지표를 담은 1차원 문자열 배열 <code>survey</code>와 검사자가 각 질문마다 선택한 선택지를 담은 1차원 정수 배열 <code>choices</code>가 매개변수로 주어집니다. 이때, 검사자의 성격 유형 검사 결과를 지표 번호 순서대로 return 하도록 solution 함수를 완성해주세요.</p>
<h4 id="-제한사항">• 제한사항:</h4>
<ul>
<li>1 ≤ <code>survey</code>의 길이 ( = <code>n</code>) ≤ 1,000<ul>
<li><code>survey</code>의 원소는 <code>&quot;RT&quot;, &quot;TR&quot;, &quot;FC&quot;, &quot;CF&quot;, &quot;MJ&quot;, &quot;JM&quot;, &quot;AN&quot;, &quot;NA&quot;</code> 중 하나입니다.</li>
<li><code>survey[i]</code>의 첫 번째 캐릭터는 i+1번 질문의 비동의 관련 선택지를 선택하면 받는 성격 유형을 의미합니다.</li>
<li><code>survey[i]</code>의 두 번째 캐릭터는 i+1번 질문의 동의 관련 선택지를 선택하면 받는 성격 유형을 의미합니다.</li>
</ul>
</li>
<li><code>choices</code>의 길이 = <code>survey</code>의 길이<ul>
<li><code>choices[i]</code>는 검사자가 선택한 i+1번째 질문의 선택지를 의미합니다.</li>
<li>1 ≤ <code>choices</code>의 원소 ≤ 7</li>
</ul>
</li>
</ul>
<table>
<thead>
<tr>
<th align="left">choices</th>
<th align="left">뜻</th>
</tr>
</thead>
<tbody><tr>
<td align="left">1</td>
<td align="left">매우 비동의</td>
</tr>
<tr>
<td align="left">2</td>
<td align="left">비동의</td>
</tr>
<tr>
<td align="left">3</td>
<td align="left">약간 비동의</td>
</tr>
<tr>
<td align="left">4</td>
<td align="left">모르겠음</td>
</tr>
<tr>
<td align="left">5</td>
<td align="left">약간 동의</td>
</tr>
<tr>
<td align="left">6</td>
<td align="left">동의</td>
</tr>
<tr>
<td align="left">7</td>
<td align="left">매우 동의</td>
</tr>
</tbody></table>
<h4 id="-입출력-예">• 입출력 예:</h4>
<table>
<thead>
<tr>
<th align="center">survey</th>
<th align="center">choices</th>
<th align="center">result</th>
</tr>
</thead>
<tbody><tr>
<td align="center">[&quot;AN&quot;, &quot;CF&quot;, &quot;MJ&quot;, &quot;RT&quot;, &quot;NA&quot;]</td>
<td align="center">[5, 3, 2, 7, 5]</td>
<td align="center">&quot;TCMA&quot;</td>
</tr>
<tr>
<td align="center">[&quot;TR&quot;, &quot;RT&quot;, &quot;TR&quot;]</td>
<td align="center">[7, 1, 3]</td>
<td align="center">&quot;RCJA&quot;</td>
</tr>
</tbody></table>
<h4 id="-입출력-예-설명">• 입출력 예 설명:</h4>
<ul>
<li><p>입출력 예 #1</p>
<p>1번 질문의 점수 배치는 아래 표와 같습니다.</p>
<table>
<thead>
<tr>
<th align="left">선택지</th>
<th align="left">성격 유형 점수</th>
</tr>
</thead>
<tbody><tr>
<td align="left">매우 비동의</td>
<td align="left">어피치형 3점</td>
</tr>
<tr>
<td align="left">비동의</td>
<td align="left">어피치형 2점</td>
</tr>
<tr>
<td align="left">약간 비동의</td>
<td align="left">어피치형 1점</td>
</tr>
<tr>
<td align="left">모르겠음</td>
<td align="left">어떤 성격 유형도 점수를 얻지 않습니다</td>
</tr>
<tr>
<td align="left">약간 동의</td>
<td align="left">네오형 1점</td>
</tr>
<tr>
<td align="left">동의</td>
<td align="left">네오형 2점</td>
</tr>
<tr>
<td align="left">매우 동의</td>
<td align="left">네오형 3점</td>
</tr>
<tr>
<td align="left">1번 질문에서는 지문의 예시와 다르게 비동의 관련 선택지를 선택하면 어피치형(A) 성격 유형의 점수를 얻고, 동의 관련 선택지를 선택하면 네오형(N) 성격 유형의 점수를 얻습니다.</td>
<td align="left"></td>
</tr>
<tr>
<td align="left">1번 질문에서 검사자는 <code>약간 동의</code> 선택지를 선택했으므로 네오형(N) 성격 유형 점수 1점을 얻게 됩니다.</td>
<td align="left"></td>
</tr>
</tbody></table>
<p>2번 질문의 점수 배치는 아래 표와 같습니다.</p>
<table>
<thead>
<tr>
<th align="left">선택지</th>
<th align="left">성격 유형 점수</th>
</tr>
</thead>
<tbody><tr>
<td align="left">매우 비동의</td>
<td align="left">콘형 3점</td>
</tr>
<tr>
<td align="left">비동의</td>
<td align="left">콘형 2점</td>
</tr>
<tr>
<td align="left">약간 비동의</td>
<td align="left">콘형 1점</td>
</tr>
<tr>
<td align="left">모르겠음</td>
<td align="left">어떤 성격 유형도 점수를 얻지 않습니다</td>
</tr>
<tr>
<td align="left">약간 동의</td>
<td align="left">프로도형 1점</td>
</tr>
<tr>
<td align="left">동의</td>
<td align="left">프로도형 2점</td>
</tr>
<tr>
<td align="left">매우 동의</td>
<td align="left">프로도형 3점</td>
</tr>
<tr>
<td align="left">2번 질문에서 검사자는 <code>약간 비동의</code> 선택지를 선택했으므로 콘형(C) 성격 유형 점수 1점을 얻게 됩니다.</td>
<td align="left"></td>
</tr>
</tbody></table>
<p>3번 질문의 점수 배치는 아래 표와 같습니다.</p>
<table>
<thead>
<tr>
<th align="left">선택지</th>
<th align="left">성격 유형 점수</th>
</tr>
</thead>
<tbody><tr>
<td align="left">매우 비동의</td>
<td align="left">무지형 3점</td>
</tr>
<tr>
<td align="left">비동의</td>
<td align="left">무지형 2점</td>
</tr>
<tr>
<td align="left">약간 비동의</td>
<td align="left">무지형 1점</td>
</tr>
<tr>
<td align="left">모르겠음</td>
<td align="left">어떤 성격 유형도 점수를 얻지 않습니다</td>
</tr>
<tr>
<td align="left">약간 동의</td>
<td align="left">제이지형 1점</td>
</tr>
<tr>
<td align="left">동의</td>
<td align="left">제이지형 2점</td>
</tr>
<tr>
<td align="left">매우 동의</td>
<td align="left">제이지형 3점</td>
</tr>
<tr>
<td align="left">3번 질문에서 검사자는 <code>비동의</code> 선택지를 선택했으므로 무지형(M) 성격 유형 점수 2점을 얻게 됩니다.</td>
<td align="left"></td>
</tr>
</tbody></table>
<p>4번 질문의 점수 배치는 아래 표와 같습니다.</p>
<table>
<thead>
<tr>
<th align="left">선택지</th>
<th align="left">성격 유형 점수</th>
</tr>
</thead>
<tbody><tr>
<td align="left">매우 비동의</td>
<td align="left">라이언형 3점</td>
</tr>
<tr>
<td align="left">비동의</td>
<td align="left">라이언형 2점</td>
</tr>
<tr>
<td align="left">약간 비동의</td>
<td align="left">라이언형 1점</td>
</tr>
<tr>
<td align="left">모르겠음</td>
<td align="left">어떤 성격 유형도 점수를 얻지 않습니다</td>
</tr>
<tr>
<td align="left">약간 동의</td>
<td align="left">튜브형 1점</td>
</tr>
<tr>
<td align="left">동의</td>
<td align="left">튜브형 2점</td>
</tr>
<tr>
<td align="left">매우 동의</td>
<td align="left">튜브형 3점</td>
</tr>
<tr>
<td align="left">4번 질문에서 검사자는 <code>매우 동의</code> 선택지를 선택했으므로 튜브형(T) 성격 유형 점수 3점을 얻게 됩니다.</td>
<td align="left"></td>
</tr>
</tbody></table>
<p>5번 질문의 점수 배치는 아래 표와 같습니다.</p>
<table>
<thead>
<tr>
<th align="left">선택지</th>
<th align="left">성격 유형 점수</th>
</tr>
</thead>
<tbody><tr>
<td align="left">매우 비동의</td>
<td align="left">네오형 3점</td>
</tr>
<tr>
<td align="left">비동의</td>
<td align="left">네오형 2점</td>
</tr>
<tr>
<td align="left">약간 비동의</td>
<td align="left">네오형 1점</td>
</tr>
<tr>
<td align="left">모르겠음</td>
<td align="left">어떤 성격 유형도 점수를 얻지 않습니다</td>
</tr>
<tr>
<td align="left">약간 동의</td>
<td align="left">어피치형 1점</td>
</tr>
<tr>
<td align="left">동의</td>
<td align="left">어피치형 2점</td>
</tr>
<tr>
<td align="left">매우 동의</td>
<td align="left">어피치형 3점</td>
</tr>
<tr>
<td align="left">5번 질문에서 검사자는 <code>약간 동의</code> 선택지를 선택했으므로 어피치형(A) 성격 유형 점수 1점을 얻게 됩니다.</td>
<td align="left"></td>
</tr>
</tbody></table>
<p>1번부터 5번까지 질문의 성격 유형 점수를 합치면 아래 표와 같습니다.</p>
<table>
<thead>
<tr>
<th align="left">지표 번호</th>
<th align="left">성격 유형</th>
<th align="left">점수</th>
<th align="left">성격 유형</th>
<th align="left">점수</th>
</tr>
</thead>
<tbody><tr>
<td align="left">1번 지표</td>
<td align="left">라이언형(R)</td>
<td align="left">0</td>
<td align="left">튜브형(T)</td>
<td align="left">3</td>
</tr>
<tr>
<td align="left">2번 지표</td>
<td align="left">콘형(C)</td>
<td align="left">1</td>
<td align="left">프로도형(F)</td>
<td align="left">0</td>
</tr>
<tr>
<td align="left">3번 지표</td>
<td align="left">제이지형(J)</td>
<td align="left">0</td>
<td align="left">무지형(M)</td>
<td align="left">2</td>
</tr>
<tr>
<td align="left">4번 지표</td>
<td align="left">어피치형(A)</td>
<td align="left">1</td>
<td align="left">네오형(N)</td>
<td align="left">1</td>
</tr>
<tr>
<td align="left">각 지표에서 더 점수가 높은 <code>T</code>,<code>C</code>,<code>M</code>이 성격 유형입니다.</td>
<td align="left"></td>
<td align="left"></td>
<td align="left"></td>
<td align="left"></td>
</tr>
<tr>
<td align="left">하지만, 4번 지표는 1점으로 동일한 점수입니다. 따라서, 4번 지표의 성격 유형은 사전순으로 빠른 <code>A</code>입니다.</td>
<td align="left"></td>
<td align="left"></td>
<td align="left"></td>
<td align="left"></td>
</tr>
</tbody></table>
<p>따라서 <code>&quot;TCMA&quot;</code>를 return 해야 합니다.</p>
</li>
<li><p>입출력 예 #2</p>
<p>1번부터 3번까지 질문의 성격 유형 점수를 합치면 아래 표와 같습니다.</p>
<table>
<thead>
<tr>
<th align="left">지표 번호</th>
<th align="left">성격 유형</th>
<th align="left">점수</th>
<th align="left">성격 유형</th>
<th align="left">점수</th>
</tr>
</thead>
<tbody><tr>
<td align="left">1번 지표</td>
<td align="left">라이언형(R)</td>
<td align="left">6</td>
<td align="left">튜브형(T)</td>
<td align="left">1</td>
</tr>
<tr>
<td align="left">2번 지표</td>
<td align="left">콘형(C)</td>
<td align="left">0</td>
<td align="left">프로도형(F)</td>
<td align="left">0</td>
</tr>
<tr>
<td align="left">3번 지표</td>
<td align="left">제이지형(J)</td>
<td align="left">0</td>
<td align="left">무지형(M)</td>
<td align="left">0</td>
</tr>
<tr>
<td align="left">4번 지표</td>
<td align="left">어피치형(A)</td>
<td align="left">0</td>
<td align="left">네오형(N)</td>
<td align="left">0</td>
</tr>
<tr>
<td align="left">1번 지표는 튜브형(T)보다 라이언형(R)의 점수가 더 높습니다. 따라서 첫 번째 지표의 성격 유형은 <code>R</code>입니다.</td>
<td align="left"></td>
<td align="left"></td>
<td align="left"></td>
<td align="left"></td>
</tr>
<tr>
<td align="left">하지만, 2, 3, 4번 지표는 모두 0점으로 동일한 점수입니다. 따라서 2, 3, 4번 지표의 성격 유형은 사전순으로 빠른 <code>C</code>, <code>J</code>, <code>A</code>입니다.</td>
<td align="left"></td>
<td align="left"></td>
<td align="left"></td>
<td align="left"></td>
</tr>
</tbody></table>
<p>따라서 <code>&quot;RCJA&quot;</code>를 return 해야 합니다.</p>
</li>
</ul>
<h4 id="-작성-답안">• 작성 답안:</h4>
<p>solution.js</p>
<pre><code class="language-javascript">function solution(survey, choices) {
    let score = { &quot;R&quot;: 0, &quot;T&quot;: 0,
                  &quot;C&quot;: 0, &quot;F&quot;: 0,
                  &quot;J&quot;: 0, &quot;M&quot;: 0,
                  &quot;A&quot;: 0, &quot;N&quot;: 0};
    let character = [];

    let board = survey.map((value) =&gt; value.split(&quot;&quot;));

    for(i=0; i &lt; board.length; i++) {
        if (choices[i] &lt;= 3) score[board[i][0]] += (4 - choices[i]);
        if (choices[i] &gt;= 5) score[board[i][1]] += (choices[i] - 4);
    }

    score[&quot;R&quot;] &gt;= score[&quot;T&quot;] ? character.push(&quot;R&quot;) : character.push(&quot;T&quot;);
    score[&quot;C&quot;] &gt;= score[&quot;F&quot;] ? character.push(&quot;C&quot;) : character.push(&quot;F&quot;);
    score[&quot;J&quot;] &gt;= score[&quot;M&quot;] ? character.push(&quot;J&quot;) : character.push(&quot;M&quot;);
    score[&quot;A&quot;] &gt;= score[&quot;N&quot;] ? character.push(&quot;A&quot;) : character.push(&quot;N&quot;);

    return character.join(&quot;&quot;);
}</code></pre>
<h4 id="-다른-답안">• 다른 답안:</h4>
<p>solution.js</p>
<pre><code class="language-javascript">{}</code></pre>
<blockquote>
<p>객체를 활용하니 문제 접근이 생각보다 수월하고, 시간도 얼마 걸리지 않았다.</p>
<p>내일 코딩 테스트를 앞두고 풀어본 연습문제가 금방 풀려버려서
자신감이 생기면서도 이 문제가 쉬웠던 건 아닐까 내일 어려우면 어쩌지하고 다시금 두려워지기도 한다.</p>
<p>하지만 부딪치지 않으면 넘을 수 없다.
내일의 코테... 물론 잘 치면 좋겠지만 지더라도 졌잘싸가 되길 바래본다 :0</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Algorithm] 문자열 나누기]]></title>
            <link>https://velog.io/@slight-snow/Algorithm-%EB%AC%B8%EC%9E%90%EC%97%B4-%EB%82%98%EB%88%84%EA%B8%B0</link>
            <guid>https://velog.io/@slight-snow/Algorithm-%EB%AC%B8%EC%9E%90%EC%97%B4-%EB%82%98%EB%88%84%EA%B8%B0</guid>
            <pubDate>Wed, 12 Apr 2023 16:27:52 GMT</pubDate>
            <description><![CDATA[<h4 id="-난이도">• 난이도</h4>
<p>: Level.1</p>
<h4 id="-정답률">• 정답률</h4>
<p>: 49%</p>
<h4 id="-문제">• 문제:</h4>
<p>: 문자열 나누기</p>
<h4 id="-설명">• 설명:</h4>
<p>: 문자열 <code>s</code>가 입력되었을 때 다음 규칙을 따라서 이 문자열을 여러 문자열로 분해하려고 합니다.</p>
<ul>
<li>먼저 첫 글자를 읽습니다. 이 글자를 x라고 합시다.</li>
<li>이제 이 문자열을 왼쪽에서 오른쪽으로 읽어나가면서, x와 x가 아닌 다른 글자들이 나온 횟수를 각각 셉니다.
처음으로 두 횟수가 같아지는 순간 멈추고, 지금까지 읽은 문자열을 분리합니다.</li>
<li><code>s</code>에서 분리한 문자열을 빼고 남은 부분에 대해서 이 과정을 반복합니다. 남은 부분이 없다면 종료합니다.</li>
<li>만약 두 횟수가 다른 상태에서 더 이상 읽을 글자가 없다면, 역시 지금까지 읽은 문자열을 분리하고, 종료합니다.</li>
</ul>
<p>문자열 <code>s</code>가 매개변수로 주어질 때, 위 과정과 같이 문자열들로 분해하고, 분해한 문자열의 개수를 return 하는 함수 solution을 완성하세요.</p>
<h4 id="-제한사항">• 제한사항:</h4>
<ul>
<li>1 ≤ <code>s</code>의 길이 ≤ 10,000</li>
<li><code>s</code>는 영어 소문자로만 이루어져 있습니다.<h4 id="-입출력-예">• 입출력 예:</h4>
<table>
<thead>
<tr>
<th align="center">s</th>
<th align="center">result</th>
</tr>
</thead>
<tbody><tr>
<td align="center">&quot;banana&quot;</td>
<td align="center">3</td>
</tr>
<tr>
<td align="center">&quot;abracadabra&quot;</td>
<td align="center">6</td>
</tr>
<tr>
<td align="center">&quot;aaabbaccccabba&quot;</td>
<td align="center">3</td>
</tr>
</tbody></table>
</li>
</ul>
<h4 id="-입출력-예-설명">• 입출력 예 설명:</h4>
<ul>
<li><p>입출력 예 #1
<code>s</code>=&quot;banana&quot;인 경우 ba - na - na와 같이 분해됩니다.</p>
</li>
<li><p>입출력 예 #2
<code>s</code>=&quot;abracadabra&quot;인 경우 ab - ra - ca - da - br - a와 같이 분해됩니다.</p>
</li>
<li><p>입출력 예 #3
<code>s</code>=&quot;aaabbaccccabba&quot;인 경우 aaabbacc - ccab - ba와 같이 분해됩니다.</p>
</li>
</ul>
<h4 id="-작성-답안">• 작성 답안:</h4>
<p>solution.js</p>
<pre><code class="language-javascript">function solution(s) {
    let result = [];
    let compare = { &quot;x&quot;: 0, &quot;y&quot;: 0 };

    let letters = s.split(&quot;&quot;);
    let start;
    for (i=0; i &lt; letters.length; i++){
        if (compare.x === 0) start = letters[i];

        if (letters[i] === start) compare.x += 1;
        else compare.y += 1;

        result.push(letters[i]);

        if (compare.x === compare.y) {
            result.push(&quot;.&quot;);
            compare.x = 0;
            compare.y = 0;
        }
    }

    result = result.join(&quot;&quot;).split(&quot;.&quot;).filter((x) =&gt; x);

    return result.length;
}</code></pre>
<h4 id="-다른-답안">• 다른 답안:</h4>
<p>solution.js</p>
<pre><code class="language-javascript">{}</code></pre>
<blockquote>
<p>시간이 얼마 걸리지 않았고, 비교적 적은 조건문을 사용한 것 같다.
그리고 무엇보다 테스트 케이스들을 한 번에 통과한게 너무 상쾌하다!
기존에 제공되는 테스트 케이스들은 통과하지만,
매번 제출 후 테스트 케이스들에서 막히는 게 너무 스트레스였는데
이번엔 무사 통과해서 다행이다 :)</p>
<p>다른 답안들을 확인해봤지만 시간 복잡도나 코드 양이 비슷했다.
나름 괜찮게 코드를 짰다는 반증이 아닐까?!</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Algorithm] 카드 뭉치]]></title>
            <link>https://velog.io/@slight-snow/Algorithm-%EC%B9%B4%EB%93%9C-%EB%AD%89%EC%B9%98</link>
            <guid>https://velog.io/@slight-snow/Algorithm-%EC%B9%B4%EB%93%9C-%EB%AD%89%EC%B9%98</guid>
            <pubDate>Sun, 09 Apr 2023 13:58:08 GMT</pubDate>
            <description><![CDATA[<h4 id="-난이도">• 난이도</h4>
<p>: Level.1</p>
<h4 id="-정답률">• 정답률</h4>
<p>: 55%</p>
<h4 id="-문제">• 문제:</h4>
<p>: 카드 뭉치</p>
<h4 id="-설명">• 설명:</h4>
<p>: 코니는 영어 단어가 적힌 카드 뭉치 두 개를 선물로 받았습니다. 코니는 다음과 같은 규칙으로 카드에 적힌 단어들을 사용해 원하는 순서의 단어 배열을 만들 수 있는지 알고 싶습니다.</p>
<p>원하는 카드 뭉치에서 카드를 순서대로 한 장씩 사용합니다.
한 번 사용한 카드는 다시 사용할 수 없습니다.
카드를 사용하지 않고 다음 카드로 넘어갈 수 없습니다.
기존에 주어진 카드 뭉치의 단어 순서는 바꿀 수 없습니다.
예를 들어 첫 번째 카드 뭉치에 순서대로 [&quot;i&quot;, &quot;drink&quot;, &quot;water&quot;], 두 번째 카드 뭉치에 순서대로 [&quot;want&quot;, &quot;to&quot;]가 적혀있을 때 [&quot;i&quot;, &quot;want&quot;, &quot;to&quot;, &quot;drink&quot;, &quot;water&quot;] 순서의 단어 배열을 만들려고 한다면 첫 번째 카드 뭉치에서 &quot;i&quot;를 사용한 후 두 번째 카드 뭉치에서 &quot;want&quot;와 &quot;to&quot;를 사용하고 첫 번째 카드뭉치에 &quot;drink&quot;와 &quot;water&quot;를 차례대로 사용하면 원하는 순서의 단어 배열을 만들 수 있습니다.</p>
<p>문자열로 이루어진 배열 <code>cards1</code>, <code>cards2</code>와 원하는 단어 배열 <code>goal</code>이 매개변수로 주어질 때, <code>cards1</code>과 <code>cards2</code>에 적힌 단어들로 <code>goal</code>를 만들 있다면 &quot;Yes&quot;를, 만들 수 없다면 &quot;No&quot;를 return하는 solution 함수를 완성해주세요.</p>
<h4 id="-제한사항">• 제한사항:</h4>
<ul>
<li>1 ≤ <code>cards1</code>의 길이, <code>cards2</code>의 길이 ≤ 10<ul>
<li>1 ≤ <code>cards1[i]</code>의 길이, <code>cards2[i]</code>의 길이 ≤ 10</li>
<li><code>cards1</code>과 <code>cards2</code>에는 서로 다른 단어만 존재합니다.</li>
</ul>
</li>
<li>2 ≤ <code>goal</code>의 길이 ≤ <code>cards1</code>의 길이 + <code>cards2</code>의 길이<ul>
<li>1 ≤ <code>goal[i]</code>의 길이 ≤ 10</li>
<li><code>goal</code>의 원소는 <code>cards1</code>과 <code>cards2</code>의 원소들로만 이루어져 있습니다.</li>
</ul>
</li>
<li><code>cards1</code>, <code>cards2</code>, <code>goal</code>의 문자열들은 모두 알파벳 소문자로만 이루어져 있습니다.<h4 id="-입출력-예">• 입출력 예:</h4>
<table>
<thead>
<tr>
<th align="center">cards1</th>
<th align="center">cards2</th>
<th align="center">goal</th>
<th align="center">result</th>
</tr>
</thead>
<tbody><tr>
<td align="center">[&quot;i&quot;, &quot;drink&quot;, &quot;water&quot;]</td>
<td align="center">[&quot;want&quot;, &quot;to&quot;]</td>
<td align="center">[&quot;i&quot;, &quot;want&quot;, &quot;to&quot;, &quot;drink&quot;, &quot;water&quot;]</td>
<td align="center">&quot;Yes&quot;</td>
</tr>
<tr>
<td align="center">[&quot;i&quot;, &quot;water&quot;, &quot;drink&quot;]</td>
<td align="center">[&quot;want&quot;, &quot;to&quot;]</td>
<td align="center">[&quot;i&quot;, &quot;want&quot;, &quot;to&quot;, &quot;drink&quot;, &quot;water&quot;]</td>
<td align="center">&quot;No&quot;</td>
</tr>
</tbody></table>
</li>
</ul>
<h4 id="-입출력-예-설명">• 입출력 예 설명:</h4>
<ul>
<li><p>입출력 예 #1
문제 예시와 같습니다.</p>
</li>
<li><p>입출력 예 #2
<code>cards1</code>에서 &quot;i&quot;를 사용하고 <code>cards2</code>에서 &quot;want&quot;와 &quot;to&quot;를 사용하여 &quot;i want to&quot;까지는 만들 수 있지만 &quot;water&quot;가 &quot;drink&quot;보다 먼저 사용되어야 하기 때문에 해당 문장을 완성시킬 수 없습니다. 따라서 &quot;No&quot;를 반환합니다.</p>
</li>
</ul>
<h4 id="-작성-답안">• 작성 답안:</h4>
<p>solution.js</p>
<pre><code class="language-javascript">function solution(cards1, cards2, goal) {
      //배열 arr1 과 배열 arr2 가 동일한지 비교하는 함수를 작성한다.
    const equals = (arr1, arr2) =&gt; {
        return arr1.length === arr2.length &amp;&amp; arr1.every((v, i) =&gt; v === arr2[i]);
    }

    //goal에서 cards1의 원소와 일치하는 원소들을 색출해내고,
    //일치하는 원소들이 cards1의 몇 번째 인덱스에 해당하는지를 담아낸 checkCards1을 만든다.
    let checkCards1 = goal.filter((card) =&gt;
                                  cards1.includes(card)).map((card) =&gt; 
                                                             cards1.indexOf(card));
      //cards1의 원소들을 인덱스로 매핑하고,
    //cards1에는 있는 원소가 goal에서는 사용되지 않았을 수 있다.
    //따라서, checkCards1.length &lt;= cards1.length 이므로 checkCards1의 길이만큼 자른다.
    let indexCards1 = cards1.map((card, index) =&gt; index).slice(0, checkCards1.length);
    //checkCards1과 indexCards1이 동일한지 판단하는 변수 check1을 선언하고 할당한다.
    let check1 = equals(checkCards1, indexCards1);

      //cards2 역시 cards1과 마찬가지로 정리한다.
    let checkCards2 = goal.filter((card) =&gt;
                                  cards2.includes(card)).map((card) =&gt;
                                                             cards2.indexOf(card));
    let indexCards2 = cards2.map((card, index) =&gt; index).slice(0, checkCards2.length);
    let check2 = equals(checkCards2, indexCards2);

      //check1과 check2가 모두 true일 경우 &quot;Yes&quot;를 반환한다.
    if (check1 &amp;&amp; check2) return &quot;Yes&quot;;

      //이외에는 &quot;No&quot;를 반환한다.
    return &quot;No&quot;;
}</code></pre>
<h4 id="-다른-답안">• 다른 답안:</h4>
<p>solution.js</p>
<pre><code class="language-javascript">function solution(cards1, cards2, goal) {

    for(const card of goal) {
        if (cards1[0] === card) cards1.shift();
        else if (cards2[0] === card) cards2.shift();
        else return &quot;No&quot;
    }

    return &quot;Yes&quot;;
}</code></pre>
<blockquote>
<p>다른 답안을 보고 입이 쩍 벌어졌다 :0
어떻게 이렇게 간단하게 작성할 수 있단말인가...
코드 양도 적고, 테스트 케이스 실행결과 출력 속도도 0.20ms 가량 차이난다.</p>
<p>자기객관화를 조금 해보자면, 배열과 배열을 비교하거나
배열을 탐색함에 있어서 너무 물리적으로 접근하는 것 같다.
(하나하나 다 파헤쳐보는 느낌... 그러니 불필요한 시간이 더 걸리는 기분)</p>
<p>모범 답안들을 참고하면서 이런 접근도 가능하다는 것을
계속해서 학습해나가야 할 것 같다.</p>
<p>세상엔 알고리즘 강자가 정말 많구나... 새삼 느낀다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Algorithm] 개인정보 수집 유효기간 #2023 KAKAO BLIND RECRUITMENT
]]></title>
            <link>https://velog.io/@slight-snow/Algorithm-%EA%B0%9C%EC%9D%B8%EC%A0%95%EB%B3%B4-%EC%88%98%EC%A7%91-%EC%9C%A0%ED%9A%A8%EA%B8%B0%EA%B0%84-2023-KAKAO-BLIND-RECRUITMENT</link>
            <guid>https://velog.io/@slight-snow/Algorithm-%EA%B0%9C%EC%9D%B8%EC%A0%95%EB%B3%B4-%EC%88%98%EC%A7%91-%EC%9C%A0%ED%9A%A8%EA%B8%B0%EA%B0%84-2023-KAKAO-BLIND-RECRUITMENT</guid>
            <pubDate>Sat, 08 Apr 2023 11:56:21 GMT</pubDate>
            <description><![CDATA[<h4 id="-난이도">• 난이도</h4>
<p>: Level.1</p>
<h4 id="-정답률">• 정답률</h4>
<p>: 37%</p>
<h4 id="-문제">• 문제:</h4>
<p>: 개인정보 수집 유효기간 #2023 KAKAO BLIND RECRUITMENT</p>
<h4 id="-설명">• 설명:</h4>
<p>: 고객의 약관 동의를 얻어서 수집된 1~<code>n</code>번으로 분류되는 개인정보 <code>n</code>개가 있습니다. 약관 종류는 여러 가지 있으며 각 약관마다 개인정보 보관 유효기간이 정해져 있습니다. 당신은 각 개인정보가 어떤 약관으로 수집됐는지 알고 있습니다. 수집된 개인정보는 유효기간 전까지만 보관 가능하며, 유효기간이 지났다면 반드시 파기해야 합니다.</p>
<p>예를 들어, A라는 약관의 유효기간이 12 달이고, 2021년 1월 5일에 수집된 개인정보가 A약관으로 수집되었다면 해당 개인정보는 2022년 1월 4일까지 보관 가능하며 2022년 1월 5일부터 파기해야 할 개인정보입니다.
당신은 오늘 날짜로 파기해야 할 개인정보 번호들을 구하려 합니다.</p>
<p>모든 달은 28일까지 있다고 가정합니다.</p>
<p>다음은 오늘 날짜가 <code>2022.05.19</code>일 때의 예시입니다.</p>
<table>
<thead>
<tr>
<th align="center">약관 종류</th>
<th align="center">유효기간</th>
</tr>
</thead>
<tbody><tr>
<td align="center">A</td>
<td align="center">6 달</td>
</tr>
<tr>
<td align="center">B</td>
<td align="center">12 달</td>
</tr>
<tr>
<td align="center">C</td>
<td align="center">3 달</td>
</tr>
<tr>
<td align="center"><br/></td>
<td align="center"></td>
</tr>
</tbody></table>
<table>
<thead>
<tr>
<th align="center">번호</th>
<th align="center">개인정보 수집 일자</th>
<th align="center">약관 종류</th>
</tr>
</thead>
<tbody><tr>
<td align="center">1</td>
<td align="center">2021.05.02</td>
<td align="center">A</td>
</tr>
<tr>
<td align="center">2</td>
<td align="center">2021.07.01</td>
<td align="center">B</td>
</tr>
<tr>
<td align="center">3</td>
<td align="center">2022.02.19</td>
<td align="center">C</td>
</tr>
<tr>
<td align="center">4</td>
<td align="center">2022.02.20</td>
<td align="center">C</td>
</tr>
</tbody></table>
<ul>
<li>첫 번째 개인정보는 A약관에 의해 2021년 11월 1일까지 보관 가능하며, 유효기간이 지났으므로 파기해야 할 개인정보입니다.</li>
<li>두 번째 개인정보는 B약관에 의해 2022년 6월 28일까지 보관 가능하며, 유효기간이 지나지 않았으므로 아직 보관 가능합니다.</li>
<li>세 번째 개인정보는 C약관에 의해 2022년 5월 18일까지 보관 가능하며, 유효기간이 지났으므로 파기해야 할 개인정보입니다.</li>
<li>네 번째 개인정보는 C약관에 의해 2022년 5월 19일까지 보관 가능하며, 유효기간이 지나지 않았으므로 아직 보관 가능합니다.</li>
</ul>
<p>따라서 파기해야 할 개인정보 번호는 [1, 3]입니다.</p>
<p>오늘 날짜를 의미하는 문자열 <code>today</code>, 약관의 유효기간을 담은 1차원 문자열 배열 <code>terms</code>와 수집된 개인정보의 정보를 담은 1차원 문자열 배열 <code>privacies</code>가 매개변수로 주어집니다. 이때 파기해야 할 개인정보의 번호를 오름차순으로 1차원 정수 배열에 담아 return 하도록 solution 함수를 완성해 주세요.</p>
<h4 id="-제한사항">• 제한사항:</h4>
<ul>
<li>today는 &quot;<code>YYYY</code>.<code>MM</code>.<code>DD</code>&quot; 형태로 오늘 날짜를 나타냅니다.</li>
<li>1 ≤ <code>terms</code>의 길이 ≤ 20<ul>
<li><code>terms</code>의 원소는 &quot;<code>약관 종류</code> <code>유효기간</code>&quot; 형태의 <code>약관 종류</code>와 <code>유효기간</code>을 공백 하나로 구분한 문자열입니다.</li>
<li><code>약관 종류</code>는 <code>A</code>~<code>Z</code>중 알파벳 대문자 하나이며, <code>terms</code> 배열에서 <code>약관 종류</code>는 중복되지 않습니다.</li>
<li><code>유효기간</code>은 개인정보를 보관할 수 있는 달 수를 나타내는 정수이며, 1 이상 100 이하입니다.</li>
</ul>
</li>
<li>1 ≤ <code>privacies</code>의 길이 ≤ 100<ul>
<li><code>privacies[i]</code>는 <code>i+1</code>번 개인정보의 수집 일자와 약관 종류를 나타냅니다.</li>
<li><code>privacies</code>의 원소는 &quot;<code>날짜</code> <code>약관 종류</code>&quot; 형태의 <code>날짜</code>와 <code>약관 종류</code>를 공백 하나로 구분한 문자열입니다.</li>
<li><code>날짜</code>는 &quot;<code>YYYY</code>.<code>MM</code>.<code>DD</code>&quot; 형태의 개인정보가 수집된 날짜를 나타내며, <code>today</code> 이전의 날짜만 주어집니다.</li>
<li><code>privacies</code>의 <code>약관 종류</code>는 항상 <code>terms</code>에 나타난 <code>약관 종류</code>만 주어집니다.</li>
</ul>
</li>
<li><code>today</code>와 <code>privacies</code>에 등장하는 <code>날짜</code>의 <code>YYYY</code>는 연도, <code>MM</code>은 월, <code>DD</code>는 일을 나타내며 점(<code>.</code>) 하나로 구분되어 있습니다.<ul>
<li>2000 ≤ <code>YYYY</code> ≤ 2022</li>
<li>1 ≤ <code>MM</code> ≤ 12</li>
<li><code>MM</code>이 한 자릿수인 경우 앞에 0이 붙습니다.</li>
<li>1 ≤ <code>DD</code> ≤ 28</li>
<li><code>DD</code>가 한 자릿수인 경우 앞에 0이 붙습니다.</li>
</ul>
</li>
<li>파기해야 할 개인정보가 하나 이상 존재하는 입력만 주어집니다.<h4 id="-입출력-예">• 입출력 예:</h4>
<table>
<thead>
<tr>
<th align="center">today</th>
<th align="center">terms</th>
<th align="center">privacies</th>
<th align="center">result</th>
</tr>
</thead>
<tbody><tr>
<td align="center">&quot;2022.05.19&quot;</td>
<td align="center">[&quot;A 6&quot;, &quot;B 12&quot;, &quot;C 3&quot;]</td>
<td align="center">[&quot;2021.05.02 A&quot;, &quot;2021.07.01 B&quot;, &quot;2022.02.19 C&quot;, &quot;2022.02.20 C&quot;]</td>
<td align="center">[1, 3]</td>
</tr>
<tr>
<td align="center">&quot;2020.01.01&quot;</td>
<td align="center">[&quot;Z 3&quot;, &quot;D 5&quot;]</td>
<td align="center">[&quot;2019.01.01 D&quot;, &quot;2019.11.15 Z&quot;, &quot;2019.08.02 D&quot;, &quot;2019.07.01 D&quot;, &quot;2018.12.28 Z&quot;]</td>
<td align="center">[1, 4, 5]</td>
</tr>
</tbody></table>
</li>
</ul>
<h4 id="-입출력-예-설명">• 입출력 예 설명:</h4>
<ul>
<li><p>입출력 예 #1
문제 예시와 같습니다.</p>
</li>
<li><p>입출력 예 #2</p>
</li>
</ul>
<table>
<thead>
<tr>
<th align="center">약관 종류</th>
<th align="center">유효기간</th>
</tr>
</thead>
<tbody><tr>
<td align="center">Z</td>
<td align="center">3 달</td>
</tr>
<tr>
<td align="center">D</td>
<td align="center">5 달</td>
</tr>
<tr>
<td align="center"><br/></td>
<td align="center"></td>
</tr>
</tbody></table>
<table>
<thead>
<tr>
<th align="center">번호</th>
<th align="center">개인정보 수집 일자</th>
<th align="center">약관 종류</th>
</tr>
</thead>
<tbody><tr>
<td align="center">1</td>
<td align="center">2019.01.01</td>
<td align="center">D</td>
</tr>
<tr>
<td align="center">2</td>
<td align="center">2019.11.15</td>
<td align="center">Z</td>
</tr>
<tr>
<td align="center">3</td>
<td align="center">2019.08.02</td>
<td align="center">D</td>
</tr>
<tr>
<td align="center">4</td>
<td align="center">2019.07.01</td>
<td align="center">D</td>
</tr>
<tr>
<td align="center">5</td>
<td align="center">2018.12.28</td>
<td align="center">Z</td>
</tr>
</tbody></table>
<ul>
<li><p>오늘 날짜는 2020년 1월 1일입니다.</p>
<ul>
<li>첫 번째 개인정보는 D약관에 의해 2019년 5월 28일까지 보관 가능하며, 유효기간이 지났으므로 파기해야 할 개인정보입니다.</li>
<li>두 번째 개인정보는 Z약관에 의해 2020년 2월 14일까지 보관 가능하며, 유효기간이 지나지 않았으므로 아직 보관 가능합니다.</li>
<li>세 번째 개인정보는 D약관에 의해 2020년 1월 1일까지 보관 가능하며, 유효기간이 지나지 않았으므로 아직 보관 가능합니다.</li>
<li>네 번째 개인정보는 D약관에 의해 2019년 11월 28일까지 보관 가능하며, 유효기간이 지났으므로 파기해야 할 개인정보입니다.</li>
<li>다섯 번째 개인정보는 Z약관에 의해 2019년 3월 27일까지 보관 가능하며, 유효기간이 지났으므로 파기해야 할 개인정보입니다.</li>
</ul>
</li>
</ul>
<h4 id="-작성-답안">• 작성 답안:</h4>
<p>solution.js</p>
<pre><code class="language-javascript">function solution(today, terms, privacies) {
    let result = [];

    let rules = {}; //{ &quot;A&quot;: 6, &quot;B&quot;: 12, &quot;C&quot;: 3 }
    terms.map((term) =&gt; rules[term.split(&quot; &quot;)[0]] = Number(term.split(&quot; &quot;)[1]));

    let list = []; //[ [&quot;2021.05.02&quot;,&quot;A&quot;],[&quot;2021.07.01&quot;,&quot;B&quot;],[&quot;2022.02.19&quot;,&quot;C&quot;],[&quot;2022.02.20&quot;,&quot;C&quot;] ]
    privacies.map((privacy) =&gt; list.push(privacy.split(&quot; &quot;)));

    result = list.map((item, index) =&gt; {
             //[&quot;2021.05.02&quot;,&quot;A&quot;] =&gt; [ [&quot;2021&quot;, &quot;05&quot;, &quot;02&quot;], &quot;A&quot; ]
                let splitDate = item[0].split(&quot;.&quot;); //[&quot;2022&quot;, &quot;03&quot;, &quot;01&quot;]

                splitDate[2] = String(Number(splitDate[2]) - 1); //[&quot;2022&quot;, &quot;03&quot;, &quot;0&quot;]
                if(Number(splitDate[2]) &lt; 1) {
                    splitDate[1] = String(Number(splitDate[1]) - 1); //[&quot;2022&quot;, &quot;02&quot;, &quot;0&quot;]
                    splitDate[2] = &quot;28&quot;;                             //[&quot;2022&quot;, &quot;02&quot;, &quot;28&quot;]
                }

                splitDate[1] = String(Number(splitDate[1]) + rules[item[1]]); //[&quot;2022&quot;, &quot;24&quot;, &quot;28&quot;]
                if(Number(splitDate[1] &gt; 12)) {
                    splitDate[0] = String(Number(splitDate[0]) + Math.trunc(splitDate[1] / 12)); //[&quot;2024&quot;, &quot;24&quot;, &quot;28&quot;]
                    splitDate[1] = String(Number(splitDate[1]) - Math.trunc(splitDate[1] / 12) * 12); //[&quot;2024&quot;, &quot;00&quot;, &quot;28&quot;]
                }
                if(Number(splitDate[1]) === 0) {
                    splitDate[0] = String(Number(splitDate[0]) - 1);
                    splitDate[1] = &quot;12&quot;;
                }

                splitDate = splitDate.map((each) =&gt; {
                    if(each.length &lt; 2) each = &quot;0&quot; + each;
                    return each;
                })

                let joinDate = splitDate.join(&quot;.&quot;); //&quot;2021.11.01&quot;
                // return joinDate;

                if(new Date(joinDate) &lt; new Date(today)) return index + 1;
            })

    result = result.filter((el) =&gt; el);

    return result;
}</code></pre>
<h4 id="-새로-작성한-답안">• 새로 작성한 답안:</h4>
<p>solution.js</p>
<pre><code class="language-javascript">{}</code></pre>
<blockquote>
<p>다소 물리적으로 접근한 느낌이 없지 않아 있지만,
그래도 일단은 풀었다는 데에 의의를 두려고 한다!</p>
<p>다른 답안을 참고하는 것도 학습에 도움이 될 수 있지만
어제처럼 스스로 최적화하고 시간을 줄이는 것이 몸에 더 와닿는 느낌이다.</p>
<p>이 코드도 굉장히... 굉장히 길고 복잡하지만
조금 더 간단하게 만들고 불필요한 조건문들을 간결화하면서 코드를 정리해봐야겠다.</p>
<p>기본으로 제공되는 테스트들은 모두 쉽게 통과했지만,
개인정보 수집 월 + 유효기간이 12의 배수일 때
계산 이후의 월이 0이 되면서 테스트 케이스에서 많이 막혔다.</p>
<p>경우의 수에 대한 시야를 조금 더 늘릴 필요가 있을 것 같다 :0</p>
</blockquote>
]]></description>
        </item>
    </channel>
</rss>