<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>zsj_tapout.log</title>
        <link>https://velog.io/</link>
        <description>사람도 사랑도 계획적으로</description>
        <lastBuildDate>Fri, 05 Dec 2025 07:46:57 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>zsj_tapout.log</title>
            <url>https://velog.velcdn.com/images/zsj_tapout/profile/f3702989-845c-4bdd-ad93-03872f084d03/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. zsj_tapout.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/zsj_tapout" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[JS/Programmers] 조이스틱 - Greedy 알고리즘 활용하기]]></title>
            <link>https://velog.io/@zsj_tapout/JSProgrammers-Greedy-joystick</link>
            <guid>https://velog.io/@zsj_tapout/JSProgrammers-Greedy-joystick</guid>
            <pubDate>Fri, 05 Dec 2025 07:46:57 GMT</pubDate>
            <description><![CDATA[<h3 id="문제-조이스틱">문제: 조이스틱</h3>
<p><img src="https://velog.velcdn.com/images/zsj_tapout/post/f03976bd-3c67-4bbf-86f1-32d874145c01/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/zsj_tapout/post/645322f3-4d20-461b-a137-f04b47b1e63f/image.png" alt=""></p>
<h3 id="해결-과정">해결 과정</h3>
<p>그리디 알고리즘의 특성을 고려하여, 우선 필요한 작업부터 수행해 나가는 것이 좋다.(=당장의 최적합을 찾기)</p>
<ol>
<li><p>최초값인 A를 기준으로, 알파벳의 중간 지점인 N을 기준으로</p>
<p> 1) 다음 알파벳으로 올라가는 것    2) Z부터 역순으로 내려가는 것
중 최솟값을 선택한다.
이때, 알파벳의 ASCII 코드를 찾아내는 함수인 <code>charCodeAt()</code>을 활용하기!</p>
</li>
</ol>
<pre><code class="language-javascript">count += Math.min((name[i].charCodeAt(0) - 65), (91 - name[i].charCodeAt(0)));</code></pre>
<ol start="2">
<li><p>다음 문자에 연속된 A가 있는지 판단하기 위해, <code>while</code> 반복문을 사용하여 연속된 가장 마지막 A를 찾는다.</p>
<pre><code class="language-javascript">let nextA = i + 1;
while(nextA &lt; name.length &amp;&amp; name.charCodeAt(nextA) === 65) nextA++;</code></pre>
</li>
<li><p>다음 문자로 이동할 때, 앞으로 가는 것이 빠른지, 혹은 뒤로 가는 것이 빠른지를 판단한다.
이때, A가 연속으로 나올 경우, 굳이 그쪽 끝까지 갔다가 다시 돌아오는 것보다, 미리 돌아서 다른 방향으로 가는 것이 더 효율적일 수 있는데, 그 비용을 <code>i * 2</code>로 설정해 준다.</p>
<pre><code class="language-javascript">min = Math.min(min, (i * 2) + name.length - nextA, i + (name.length - nextA) * 2);</code></pre>
</li>
</ol>
<h3 id="최종-답안">최종 답안</h3>
<pre><code class="language-javascript">function solution(name) {
    var count = 0;
    let min = name.length - 1; //좌우 이동 최초 최소값
    for(let i = 0; i &lt; name.length; i++){
      //위아래 버튼 여부 결정
        count += Math.min((name[i].charCodeAt(0) - 65), (91 - name[i].charCodeAt(0)));
        let nextA = i + 1; //A의 연속 개수를 고려한 좌우 이동 여부 결정
          //연속된 A 개수 찾기
        while(nextA &lt; name.length &amp;&amp; name.charCodeAt(nextA) === 65) nextA++;
      // 그냥 좌로만 쭉 가는 거랑, 우로 한번 거쳐서 맨 뒤로 가는 거, 뒤로 간 후 앞으로 돌아오는 것 중에서 최소값 비교
        min = Math.min(min, (i * 2) + name.length - nextA, i + (name.length - nextA) * 2);
    }
    return count + min;
}</code></pre>
<h3 id="greedy-알고리즘">Greedy 알고리즘</h3>
<p>그리디 알고리즘이란, 선택해야 하는 상황에서 순간순간 보이는 가장 최적화된 결과를 선택하여 최종 결과에 도달하는 것이다. 이러한 특징은 곧 빠른 실행 속도를 보장하는데, 맹점은 이러한 알고리즘이 무조건 최적의 결과를 보장하지는 않는다는 것이다.</p>
<ul>
<li>그리디 알고리즘의 주요 특징<blockquote>
<ol>
<li>이전의 선택이 이후의 선택에 영향을 주지는 않는다.</li>
<li>하나의 큰 문제는 여러 개의 작은 문제들로 분리되며, 이 결과는 작은 문제들의 최적화된 해결 방법으로 결정된다.</li>
</ol>
</blockquote>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Debounce란? 목적과 활용 예시]]></title>
            <link>https://velog.io/@zsj_tapout/Debounce%EB%9E%80-%EB%AA%A9%EC%A0%81%EA%B3%BC-%ED%99%9C%EC%9A%A9-%EC%98%88%EC%8B%9C</link>
            <guid>https://velog.io/@zsj_tapout/Debounce%EB%9E%80-%EB%AA%A9%EC%A0%81%EA%B3%BC-%ED%99%9C%EC%9A%A9-%EC%98%88%EC%8B%9C</guid>
            <pubDate>Mon, 01 Dec 2025 07:11:51 GMT</pubDate>
            <description><![CDATA[<h3 id="debounce란">Debounce란?</h3>
<p>Debounce란, 굉장히 짧은 시간에 발생되는 작업들이 아닌, 일정한 시간 간격을 두고 그동안 일어난 모든 결과들을 한 번의 호출로 처리하는 것을 의미한다.
</br></br></p>
<h3 id="debounce의-사용-목적">Debounce의 사용 목적</h3>
<p>일반적으로 많은 양의 데이터가 있고 이를 브라우저 상의 사용자가 탐색하는 과정이 있다면, 순간 순간의 탐색마다 많은 브라우저 리소스가 소모될 것이다. Debounce는 이러한 수많은 이벤트 호출을 방지하여 리소스 부하와 성능 저하를 개선할 수 있다. 따라서 Debounce는 사용자 입력에 관한 기능이나 이미지 리사이즈, 버튼 더블클릭 방지 등에서 활용된다.
</br></br></p>
<h3 id="간단한-예시">간단한 예시</h3>
<p>예를 들어, 내가 검색창에 단어를 입력할 때, 입력하는 문자마다 그에 대한 결과값을 받는 것이 아닌, 전체 단어를 입력했을 때의 자동완성 결과만 받고 싶다고 가정해 보자.</p>
<p>일반적으로는 setTimeout을 통해 사용자가 검색창에서 원하는 단어를 입력할 시간을 준 뒤 그 이후에 결과를 찾는 이벤트를 수행하도록 하는 구조이다.</p>
<pre><code class="language-typescript">import React, {useState, useEffect} from &#39;react&#39;;

export function useDebounce&lt;T&gt;(value: T, delay: number): T {
  const [debouncedValue, setDebouncedValue] = useState(value);
  useEffect(()=&gt;{ //timeout을 줌으로써 그 사이의 이벤트 호출은 무시
    const timer = setTimeout(()=&gt;{
      setDebouncedValue(value);
    }, delay);
    return ()=&gt;{clearTimeout(timer)};
  }, [value]);
  return debouncedValue;
}

export function App() {
  const [value, setValue] = useState&lt;string&gt;(&quot;&quot;);
  const debouncedValueTest = useDebounce(value, 1000); //1초의 디바운스 기간 부여
  return (
    &lt;div&gt;
      &lt;p&gt;test input&lt;/p&gt;
      &lt;input
        value={value}
        onChange={(e)=&gt;setValue(e.target.value)}
      /&gt; // 디바운스 기간동안 입력이 없다면, 결과로 인식하게 됨
      &lt;p&gt;now: {value}&lt;/p&gt; //input에 넣는 즉시 나타나는 값
      &lt;p&gt;debounced: {debouncedValueTest}&lt;/p&gt; //디바운스 후 나타나게 됨!
    &lt;/div&gt;
  )
}</code></pre>
</br>

<h3 id="유사한-개념---throttling쓰로틀링">유사한 개념 - Throttling(쓰로틀링)</h3>
<p>목적 자체는 두 기법 모두 성능 저하를 방지한다는 면에서 유사하다. 하지만 Debounce와 달리, Throttling은 일정한 주기마다 이벤트 핸들러를 처리함으로써 리소스 부하를 막는다는 차이점이 있다. 이벤트 처리 기준으로 비유하자면 Debounce가 바루스의 &#39;꿰뚫는 화살&#39;처럼 한 번에 충전했다가 발사하는 느낌이라면, Throttling은 진의 궁극기인 &#39;커튼콜&#39;처럼 쿨타임마다 꾸준히 발사되는 것과 같은 것이다(...) 이러한 특징 덕분에, Throttling은 페이지 스크롤이나 마우스 움직임과 같은 곳에서 활용된다.</p>
</br>

<h3 id="참고자료">참고자료</h3>
<p><a href="https://developer.mozilla.org/ko/docs/Glossary/Debounce">https://developer.mozilla.org/ko/docs/Glossary/Debounce</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JS/Programmers] 문자열 겹쳐쓰기 - splice()와 그 활용]]></title>
            <link>https://velog.io/@zsj_tapout/JSProgrammers-%EB%AC%B8%EC%9E%90%EC%97%B4-%EA%B2%B9%EC%B3%90%EC%93%B0%EA%B8%B0-splice%EC%99%80-%EA%B7%B8-%ED%99%9C%EC%9A%A9</link>
            <guid>https://velog.io/@zsj_tapout/JSProgrammers-%EB%AC%B8%EC%9E%90%EC%97%B4-%EA%B2%B9%EC%B3%90%EC%93%B0%EA%B8%B0-splice%EC%99%80-%EA%B7%B8-%ED%99%9C%EC%9A%A9</guid>
            <pubDate>Mon, 08 Sep 2025 02:25:48 GMT</pubDate>
            <description><![CDATA[<h2 id="문제-문자열-겹쳐쓰기">문제: 문자열 겹쳐쓰기</h2>
<p><img src="https://velog.velcdn.com/images/zsj_tapout/post/affaf78b-648e-4a93-a6c9-43da8041fc83/image.png" alt=""></p>
<h2 id="해결-과정">해결 과정</h2>
<ol>
<li><p>주어지는 my_string을 배열로 바꾸기</p>
<pre><code class="language-javascript">const arr = [...my_string];</code></pre>
</li>
<li><p>splice()를 이용하여 배열화된 my_string의 s번째 문자부터, overwrite_string의 길이만큼 바꿔주기</p>
<pre><code class="language-javascript">arr.splice(s, overwrite_string.length, overwrite_string);</code></pre>
</li>
<li><p>배열화되어 쪼개진 문자들을 .join을 통해 다시 이어주기</p>
<pre><code class="language-javascript">return arr.join(&quot;&quot;);</code></pre>
</li>
</ol>
<h2 id="최종-답안">최종 답안</h2>
<pre><code class="language-javascript">function solution(my_string, overwrite_string, s){
  const arr = [...my_string];
  arr.splice(s, overwrite_string.length, overwrite_string);
  return arr.join(&quot;&quot;);
}</code></pre>
<br/>

<h2 id="splice의-사용법">*splice()의 사용법</h2>
<p>원래 배열의 특정 부분에 요소를 추가하거나 제거할 때 사용한다.</p>
<pre><code class="language-javascript">splice(기존 배열에서 제거가 시작될 인덱스, 제거될 인덱스 개수, 그 자리에 들어갈 아이템들);</code></pre>
<p>의 순으로 작성된다.</p>
<ul>
<li><p>제거될 인덱스 개수가 0인 경우, 아무것도 제거되지 않고 새롭게 첫 인덱스 뒤부터 아이템들이 추가된다.</p>
<pre><code class="language-javascript">const arr = [1, 2, 3, 4];
const result = arr.splice(1,0,100);
console.log(arr); // 1, 100, 2, 3, 4 가 출력됨.</code></pre>
<br/>
</li>
<li><p>만약 대체될 아이템이 없는 경우, 단순히 인덱스 내의 값들이 제거되기만 한다.</p>
<pre><code class="language-javascript">const arr = [1, 2, 3, 4];
const result = arr.splice(1,2);
console.log(arr); // 1, 4 가 출력됨.</code></pre>
<br/>
</li>
<li><p>제거할 인덱스 개수를 미지정하는 경우, 시작 인덱스부터 모든 값이 제거된다.</p>
<pre><code class="language-javascript">const arr = [1, 2, 3, 4];
const result = arr.splice(1);
console.log(arr); // 1 이 출력됨.</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Content-visibility를 활용한 가상 스크롤 적용해보기]]></title>
            <link>https://velog.io/@zsj_tapout/Content-visibility%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EA%B0%80%EC%83%81-%EC%8A%A4%ED%81%AC%EB%A1%A4-%EC%A0%81%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@zsj_tapout/Content-visibility%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EA%B0%80%EC%83%81-%EC%8A%A4%ED%81%AC%EB%A1%A4-%EC%A0%81%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Fri, 13 Jun 2025 06:44:42 GMT</pubDate>
            <description><![CDATA[<p>Playus 서비스를 개발하는 중, 커뮤니티 게시글을 보여주는 과정에서 문제가 발생했다.</p>
<h2 id="데이터-용량이-너무-많다">데이터 용량이 너무 많다</h2>
<p>이전부터 다른 프로젝트에서도 나왔던 QA 사항이었는데, 아이템의 목록을 보여줄 때 아이템의 양이 너무 많은 경우, 혹은 용량이 너무 많은 경우에는 데이터 렌더링에 있어서 너무 오랜 시간이 소모되었다. 당시에는 백엔드에게 Pagination을 통해 자체적으로 데이터를 분할하여 로드할 수 있도록 요청했지만, 당시에는 이미 프로젝트의 진척이 많이 나가버린 상황하다 보니, 백엔드의 수정이 생각보다 복잡해지는 문제가 있었다. 문제 해결을 위해 React에서 자체적으로 Pagination을 적용해 보았지만, 여전히 데이터 로딩 속도에서는 딱히 감소폭을 보여줄 수 없었다.</p>
<p>문제는 이번에도 똑같은 상황이 벌어졌다는 것이었다. 당시의 이슈를 해결하지 못한 채 같은 실수를 반복해버리고 만 것이다... 채팅과 커뮤니티 구현에 있어 무한스크롤을 적용하려다 보니, 계속해서 아이템이 쌓여가며 로딩 시간이 점점 길어져 버리는 이슈가 생겼다. 특히나 우리 서비스의 경우 모바일 웹을 기반으로 서비스할 예정이었기 때문에 이 이슈를 반드시 해결해야만 사용자 경험이 크게 개선될 수 있었다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/zsj_tapout/post/05c5b399-2478-4e3d-8dfb-af55b0a22bb4/image.png" width="300"/></p>

<blockquote>
<p>아이템의 양이 너무 많다 보니, 페이지 로딩부터가 장시간을 소모해버리는 이슈 발생.</p>
</blockquote>
<br/>

<h2 id="해결책">해결책</h2>
<p>인터넷을 뒤지다, 가상 스크롤에 대한 글을 읽게 되었다. 
가상 스크롤이란, 사용자가 눈으로 보게 되는 데이터만 웹에서 렌더링하고, 나머지 데이터의 경우 화면에 보이기 전까지는 렌더링을 하지 않음으로써 데이터 로딩 효율을 크게 증가시키는 기법이며, 예시를 들자면 인스타그램이나 X같은 대형 커뮤니티 서비스의 게시글 피드 렌더링이 있다.</p>
<p>이 기법을 사용하기 위해서는 React Virtualized, @tanstack/react-virtual 등의 라이브러리를 사용하는 방법도 있지만, 현재 우리 팀의 개발 진척 상황을 고려했을 때, 이미 다른 주변 기능들이 많이 구현된 시점에서 새로운 라이브러리를 도입하는 것은 다소 무리가 있다고 판단했다.</p>
<p>따라서 나는 css에 존재하는 <strong>content-visibility</strong> 클래스를 사용하기로 결정했다.</p>
<br/>

<h2 id="content-visibility-클래스의-사용법">Content-visibility 클래스의 사용법</h2>
<p><a href="https://developer.mozilla.org/ko/docs/Web/CSS/content-visibility">https://developer.mozilla.org/ko/docs/Web/CSS/content-visibility</a>
위 링크의 문서에서 알 수 있듯, content-vibility 클래스는 언제 컨텐트를 렌더링할지를 사용자에게 명시함으로써 불필요한 컨텐트 렌더링을 줄일 수 있다는 장점을 가진다.
구현을 위해서는 단 두 줄의 css 코드를 작성해주면 된다.</p>
<pre><code class="language-css">content-visibility: auto;
contain-intrinsic-size: auto 200px;</code></pre>
<br/>
여기서 content-visibility는 요소를 언제 사용자에게 렌더링할지를 명시해주며, auto로 설정해 준다면 자동으로 레이아웃 및 스타일 isolation을 실행해 준다. 하지만 content-visibility만 설정해 주는 경우, 스크롤을 내릴 때 브라우저가 요소의 크기를 몰라서 언제 렌더링해야 할 지를 제대로 이해하지 못할 수 있기 때문에, contain-instrinsic-size라는 '렌더링할 요소의 크기를 명시해주는' 클래스를 사용하여 브라우저로 하여금 렌더링 시점을 알 수 있게 해준다.


<p>PlayUs의 적용에서는, 기존 단순 Pagination에서 무한 스크롤로의 구현 변경을 시도했고, 이 과정에서 가상 스크롤 기능을 추가하여 렌더링 시간을 줄이고자 했다.</p>
<br/>

<h2 id="수정-결과">수정 결과</h2>
<blockquote>
<p>Javascript 변경</p>
</blockquote>
<pre><code class="language-javascript">&lt;ul className={styles.postList}&gt;
                {sortedPosts.length === 0 &amp;&amp; !isLoading ? (
                    &lt;li className={styles.listIsLoading}&gt;게시글이 없습니다.&lt;/li&gt;
                ) : (
                    sortedPosts.map((post) =&gt; (
                        &lt;div
                            key={post.id || post.postId}
                            className={styles.autoScrollWrapper}
                        &gt;
                            &lt;PostListItem
                                title={post.title}
                                date={post.date}
                                writerNickname={post.writerNickname}
                                image={post.image ? `${process.env.REACT_APP_PRESIGNED_URI}/${post.image}` : null}
                                onClick={() =&gt; handlePostClick(post.team, post.id || post.postId)}
                            /&gt;
                        &lt;/div&gt;
                    ))
                )}
                {isLoading &amp;&amp; (
                    &lt;&gt;
                        {Array.from({ length: 1 }).map((_, index) =&gt; (
                            &lt;li key={`skeleton-${index}`} className={styles.skeletonItem}&gt;
                                &lt;div className={styles.skeletonThumbnail}&gt;&lt;/div&gt;
                                &lt;div className={styles.skeletonContent}&gt;
                                    &lt;div className={`${styles.skeletonLine} ${styles.long}`}&gt;&lt;/div&gt;
                                    &lt;div className={`${styles.skeletonLine} ${styles.short}`}&gt;&lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/li&gt;
                        ))}
                    &lt;/&gt;
                )}
                &lt;div ref={bottomRef} style={{ height: &#39;1px&#39; }} /&gt;
            &lt;/ul&gt;</code></pre>
<br/>
<br/>

<blockquote>
<p>css 변경</p>
</blockquote>
<pre><code class="language-css">.autoScrollWrapper{
    content-visibility: auto;
    contain-intrinsic-size: 200px;
}</code></pre>
<p align="center"><img src="https://velog.velcdn.com/images/zsj_tapout/post/f5a41032-8b83-4670-a063-57cfd2ca1788/image.gif" width="300"/></p>

<br/>

<p>기존 수백 개씩 렌더링하던 아이템을 한 번의 스크롤당 8개씩 렌더링하도록 수정함으로써, 렌더링 효율이 크게 증가하고 사용자의 경험을 개선시킬 수 있었다..! 단, 특정 게시글 클릭 후 뒤로가기 버튼을 눌렀을 경우, 새롭게 데이터를 불러오기 때문에 기존에 브라우저에서 기억하던 게시글 페이지 위치는 불러오지 못하게 되었다. 이를 위해선 데이터 클릭 시 스크롤 위치와 페이지에 대한 자체 캐싱이 필요하다고 생각된다...</p>
<blockquote>
<p><strong>참조 게시글</strong>
<br/>
가상스크롤의 원리 알고 계신가요?
<a href="https://velog.io/@k-svelte-master/virtual-scroll-principle">https://velog.io/@k-svelte-master/virtual-scroll-principle</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[<HTML/CSS> ARIA란 무엇일까?]]></title>
            <link>https://velog.io/@zsj_tapout/HTMLCSS-ARIA%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C</link>
            <guid>https://velog.io/@zsj_tapout/HTMLCSS-ARIA%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C</guid>
            <pubDate>Tue, 06 May 2025 05:57:31 GMT</pubDate>
            <description><![CDATA[<p>Playus 프로젝트를 진행하면서 페이지 디자인을 작성하던 도중, 프론트엔드 레포지토리에 Pull Request를 하기 위해 Approve를 하다가 CodeRabbit으로부터 새로운 Refactor Suggestion을 받았다.
<img src="https://velog.velcdn.com/images/zsj_tapout/post/7c03f99b-0e8e-464c-aa29-9a6e18c5b4cd/image.png" alt="">
이 ARIA 속성이라는 단어를 이전의 개발에서도 언뜻 스쳐지나갔던 기억이 있었지만, 막상 다시 나타나게 되니 제대로 이해가 되지 않아 ARIA에 대해 알아보게 되었다.</p>
<p><a href="https://developer.mozilla.org/ko/docs/Web/Accessibility/ARIA">https://developer.mozilla.org/ko/docs/Web/Accessibility/ARIA</a></p>
<p>공식 문서에서 설명하는 ARIA란 Accessible Rich Internet Applications, 즉 접근 가능한 리치 인터넷 어플리케이션으로써 시각적으로 문제를 가진 사용자의 접근성을 위해 HTML을 통해 일반적으로 보조 기술이 알 수 없는 상호작용 및 흔히 쓰이는 어플리케이션 위젯에 필요한 정보를 제공하는 방식을 의미한다.</p>
<h3 id="aria의-5가지-속성">ARIA의 5가지 속성</h3>
<h4 id="1-위젯-속성">1. 위젯 속성</h4>
<pre><code>•    UI 구성 요소(버튼, 체크박스, 슬라이더 등)의 상태나 속성을 정의한다.</code></pre><p>예) </p>
<table>
<thead>
<tr>
<th>속성</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>aria-autocomplete</td>
<td>입력값 자동완성 가능 여부 (예: 검색창)</td>
</tr>
<tr>
<td>aria-disabled</td>
<td>비활성화 상태 (true이면 클릭 불가 등)</td>
</tr>
</tbody></table>
<h4 id="2-실시간-영역-속성">2. 실시간 영역 속성:</h4>
<pre><code>•    콘텐츠가 실시간으로 바뀌는 경우, 스크린리더에게 알리는 방식을 정의한다.</code></pre><p>예)</p>
<table>
<thead>
<tr>
<th>속성</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>aria-live</td>
<td>콘텐츠가 변경될 때 스크린리더가 읽는 방식 (off, polite, assertive)</td>
</tr>
<tr>
<td>aria-atomic</td>
<td>전체 영역을 읽을지(true) 또는 일부만 읽을지(false)</td>
</tr>
</tbody></table>
<h4 id="3-드래그--드롭-속성">3. 드래그 &amp; 드롭 속성:</h4>
<pre><code>•    요소 간 드래그/드롭 인터페이스 구현 시 사용한다.</code></pre><p>예)</p>
<table>
<thead>
<tr>
<th>속성</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>aria-dropeffect</td>
<td>요소가 드롭 가능한 대상인지 (copy, move, link, execute, popup, none)</td>
</tr>
<tr>
<td>aria-grabbed</td>
<td>드래그 중인 항목인지 여부 (true, false)</td>
</tr>
</tbody></table>
<h4 id="4-관계-속성">4. 관계 속성:</h4>
<pre><code>•    요소 간 논리적 관계를 연결하여 스크린리더가 의미를 이해하게 돕는다.
예)</code></pre><table>
<thead>
<tr>
<th>속성</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>aria-activedescendant</td>
<td>현재 활성화된 자식 요소의 id를 지정</td>
</tr>
<tr>
<td>aria-controls</td>
<td>제어 대상의 ID를 지정</td>
</tr>
</tbody></table>
<h4 id="-일부-전역-속성이-따로-존재하는데-이는-aria-역할-적용-여부와-관계없이-모든-html-요소에-적용된다">* 일부 전역 속성이 따로 존재하는데, 이는 ARIA 역할 적용 여부와 관계없이 모든 HTML 요소에 적용된다.</h4>
<h3 id="aria-사용-시의-주의사항">ARIA 사용 시의 주의사항</h3>
<h4 id="1-기존-html-시맨틱-요소가-우선">1. 기존 HTML 시맨틱 요소가 우선</h4>
<pre><code>•    원칙: 가능한 경우 HTML5의 시맨틱 태그(&lt;button&gt;, &lt;nav&gt;, &lt;header&gt; 등)를 먼저 사용할 것.
•    예: &lt;div role=&quot;button&quot;&gt; 대신 &lt;button&gt; 사용
•    이유: 시맨틱 태그는 이미 브라우저와 보조기기에 기본 역할을 제공하며, 추가 설정이 필요 없습니다.</code></pre><h4 id="2-불필요한-aria-사용-금지">2. 불필요한 ARIA 사용 금지</h4>
<pre><code>•    ARIA 속성을 무조건 넣는 건 오히려 접근성을 해칠 수 있다.
•    role=&quot;presentation&quot;을 잘못 사용하면 스크린리더가 요소를 완전히 무시할 수 있음</code></pre><h4 id="3-적절한-role과-속성-조합">3. 적절한 role과 속성 조합</h4>
<pre><code>•    role에 따라 요구되는 ARIA 속성이 정해져 있다. (예: role=&quot;progressbar&quot; → aria-valuenow, aria-valuemin, aria-valuemax)
•    누락 시 의미가 전달되지 않게 된다.</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[.module.css를 .css 대신 사용해보자]]></title>
            <link>https://velog.io/@zsj_tapout/.module.css%EB%A5%BC-.css-%EB%8C%80%EC%8B%A0-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@zsj_tapout/.module.css%EB%A5%BC-.css-%EB%8C%80%EC%8B%A0-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Tue, 22 Apr 2025 06:20:01 GMT</pubDate>
            <description><![CDATA[<h2 id="개요">개요</h2>
<p>기존에 내가 진행했던 프로젝트나 레퍼런스에서는, 항상 .css 파일을 통해 페이지를 디자인하려고 시도했었다. 하지만 이 구조로 페이지를 구현하는 경우, 만약 다른 파일의 클래스명이 같은 경우 다른 컴포넌트에서 사용하는 전혀 다른 구조의 클래스더라도 컴파일러에서 잘못 인식하여 종종 다른 컴포넌트 스타일 클래스와 충돌해버리는 문제가 발생하곤 했다.</p>
<pre><code class="language-javascript">import React from &#39;react&#39;;
import &#39;./button.css&#39;;

export default function Button({ children, onClick }) {
  return (
    &lt;button className=&quot;btn&quot; onClick={onClick}&gt;
      {children}
    &lt;/button&gt;
  );
}</code></pre>
<p>위 버튼 컴포넌트의 예시는 내가 기존에 사용하던 .css 파일을 활용하는 <code>button.jsx</code> 컴포넌트이다. 이 버튼에 대한 스타일 코드는,</p>
<pre><code class="language-css">.btn {
  background-color: red;
  color: white;
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}</code></pre>
<p>와 같은 식으로 구현된다. 하지만 내가 만약 다른 컴포넌트에서도 <code>.btn</code>이라는 스타일 클래스를 사용하고 있는 경우,</p>
<pre><code class="language-css">/* ex) App.css에서의 .btn 클래스라면? */
.btn {
  background-color: blue;
  color: black;
}</code></pre>
<p>내가 사용하고자 했던 빨간색 버튼이 App.css의 영향으로 파란색으로 바뀌게 되어버린다.
<br>
<br></p>
<h3 id="어떻게-해야-할까">어떻게 해야 할까?</h3>
<p>css 파일을 만들 때 기존의 <code>{클래스명} .css</code> 대신, <code>{클래스명}.module.css</code>의 형태로 파일을 만들어 준다.</p>
<pre><code class="language-css">/* button.module.css라면 */
.btn {
  background-color: red;
  color: white;
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}</code></pre>
<p>위와 같이, css 파일 작성 구조 자체는 기존과 다르지 않다. 하지만, 다른 점은 css를 실제 javascript에서 활용하는 과정에서 나타난다.</p>
<pre><code class="language-javascript">import React from &#39;react&#39;;
import styles from &#39;./button.module.css&#39;;

export default function Button({ children, onClick }) {
  return (
    &lt;button className={styles.btn} onClick={onClick}&gt;
      {children}
    &lt;/button&gt;
  );
}</code></pre>
<p>위와 같이, <code>.btn</code> 클래스를 실제로 사용할 button.jsx에서는 className에 <code>className=&quot;클래스명&quot;</code> 대신 <code>className={styles.[클래스명]}</code>이 사용된다. 이렇게 명시해 주면, 같은 <code>.btn</code>이라는 컴포넌트 스타일이 있더라도 각각의 css마다 고유한 className을 부여받게 되어 고유한 스타일을 보장받을 수 있게 되는 것이다.</p>
<h3 id="결론">결론</h3>
<p>.css 대신 .modules.css를 사용한다면 구체화된 클래스 참조를 통해 같은 스타일 클래스 간 충돌이 방지되고, 나아가 코드의 가독성 상승으로 인해 유지보수가 훨씬 더 간편해질 것이라고 생각된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[DB에 Base64로 인코딩된 이미지를 React에서 출력하기]]></title>
            <link>https://velog.io/@zsj_tapout/DB%EC%97%90-Base64%EB%A1%9C-%EC%9D%B8%EC%BD%94%EB%94%A9%EB%90%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80%EB%A5%BC-React%EC%97%90%EC%84%9C-%EC%B6%9C%EB%A0%A5%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@zsj_tapout/DB%EC%97%90-Base64%EB%A1%9C-%EC%9D%B8%EC%BD%94%EB%94%A9%EB%90%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80%EB%A5%BC-React%EC%97%90%EC%84%9C-%EC%B6%9C%EB%A0%A5%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 06 Mar 2025 09:22:08 GMT</pubDate>
            <description><![CDATA[<p>프로젝트를 진행하면서, DB에 저장된 텍스트와 이미지를 불러와야 하는데, 텍스트는 정상적으로 출력되지만 이미지가 출력되지 않는 이슈가 발생했다.
문제는 텍스트의 경우 Unicode 형식 그대로 MongoDB에 저장되어 있었지만, 이미지의 경우 Flask 서버에서 Base64 Encoding 과정을 통해 문자열 형태로 저장되고 있었다.
<br>
이를 다시 Decoding을 통해 정상적으로 브라우저에서 출력하기 위해, JavaScript 문서를 뒤져보다 해결 방안을 찾을 수 있었고, 이를 직접 적용해 보기로 했다.
<br>
먼저 기존의 axios로 서버에서 받아오는 데이터의 JSON 형태는 아래와 같다.</p>
<pre><code class="language-javascript">const fetchDetailsByChannelId = async (channelId) =&gt; {
        setLoading(true);
        try {
            const response = await axios.get(`http://localhost:8080/chat/channel/${channelId}`);
            const formattedDetails = response.data.map((item) =&gt; ({
                msgUrl: item.msgUrl || &quot;N/A&quot;,
                text: item.text || &quot;No text available&quot;,
                image: item.image, // 이 부분이 출력되지 않았음
                timestamp: parseDateTime(item.timestamp),
            }));
            setSelectedDetails(formattedDetails);
        } catch (err) {
            setError(`Error fetching channel details: ${err.message}`);
            setSelectedDetails([]);
        } finally {
            setLoading(false);
        }
    };</code></pre>
<p><br><br></p>
<h3 id="base64로-인코딩된-이미지의-형태">base64로 인코딩된 이미지의 형태</h3>
<pre><code class="language-json">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgA...</code></pre>
<p>현재 mongoDB에 저장되어 있는 encoded image의 형태는 위와 같이
<strong><em>&#39;data url:데이터타입/이미지타입;인코딩여부,순수문자열&#39;</em></strong>
의 형태로 저장되어 있다.</p>
<p>하지만 나에게 필요한 것은 오로지 순수한 문자열이기 때문에, 우선 문자열 앞 헤더 부분을 모두 제거해 주어야 한다.
이를 위해 나는 JavaScript의 replace() 함수를 사용했다.
<br></p>
<pre><code class="language-javascript">image: item.image.replace(/^data:image\/\w+;base64,/, &quot;&quot;)</code></pre>
<p>위와 같은 형태로 image column의 데이터에서 앞부분의 필요 없는 헤더 부분을 모두 공백으로 만들어 준다.
즉, &quot;data:image/확장자;base64,&quot; 라는 문자열을 모두 찾아서 삭제해 버리는 것이다.
<br>
그리고 이제 수정해준 데이터 string을 <code>&lt;img&gt;</code> 태그에서 다시 디코딩해주기만 하면 된다.</p>
<pre><code class="language-javascript">{detail.image &amp;&amp; (
    &lt;img src={`data:image/png;base64,${detail.image}`}
         alt=&quot;채널 이미지&quot;
         className=&quot;channel-image&quot;/&gt;
)}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[FlexBox란?]]></title>
            <link>https://velog.io/@zsj_tapout/FlexBox%EB%9E%80</link>
            <guid>https://velog.io/@zsj_tapout/FlexBox%EB%9E%80</guid>
            <pubDate>Tue, 01 Oct 2024 07:34:19 GMT</pubDate>
            <description><![CDATA[<ul>
<li>Flexible Box Module, 즉 한 번에 하나의 행 또는 열을 다루어 박스 내 아이템 공간 배분 및 정렬을 유도하는 레이아웃 모델이다.</li>
</ul>
<h2 id="rn에서의-flexbox의-특징">RN에서의 FlexBox의 특징</h2>
<ol>
<li><p>View에서 Flex display를 설정할 때, 이미 모든 View는 기본값이 Flex Container이기 때문에, 일일이 {display: flex}와 같이 지정해 줄 필요가 없다.</p>
</li>
<li><p>Flex Direction의 기준은 Column이다.(웹에서는 Row였음)</p>
</li>
<li><p>모바일 디바이스들의 크기는 저마다 다르므로, 레이아웃을 설정해줄 때에는 width와 height의 사용을 최대한 지양한다.(아이콘 제외)</p>
</li>
<li><p>flex: x ⇒ 부모 View의 x만큼의 배율로 해당 자식 View를 설정해 준다는 의미.
 4-1. 만약 부모 View가 한 코드에 하나만 존재한다면, 부모 View의 flex를 어떤 숫자로 변    경하더라도 비율에 변화는 생기지 않는다. 당연히 비율을 비교할 다른 View가 존재하지 않으니    까.</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[RN의 여러가지 규칙들]]></title>
            <link>https://velog.io/@zsj_tapout/RN%EC%9D%98-%EC%97%AC%EB%9F%AC%EA%B0%80%EC%A7%80-%EA%B7%9C%EC%B9%99%EB%93%A4</link>
            <guid>https://velog.io/@zsj_tapout/RN%EC%9D%98-%EC%97%AC%EB%9F%AC%EA%B0%80%EC%A7%80-%EA%B7%9C%EC%B9%99%EB%93%A4</guid>
            <pubDate>Tue, 01 Oct 2024 07:33:36 GMT</pubDate>
            <description><![CDATA[<ol>
<li><p>RN은 웹사이트가 아니므로 HTML을 사용하지 않는다. 따라서 <div> 대신 <View>를 import하여 사용한다.</p>
</li>
<li><p>모든 텍스트는 <Text> 컴포넌트 안에 들어가야 한다.</p>
</li>
<li><p>StyleSheet.create({object}) = RN에서 css의 역할을 하는 것.  굳이 사용하지 않아도 css의 적용이 가능하지만, StyleSheet을 사용하지 않는 경우 ‘CSS 자동완성’ 기능을 사용할 수 없다.
 3-1. style을 사용할 때, react에서 사용하던 기능 중 일부는 RN에서 사용할 수 없다.(ex: border)</p>
</li>
<li><p>일부 Third-Party Package 컴포넌트는 return 내부에 존재하지만, 렌더링되는 것이 아닌 운영체제와의 소통을 위한 컴포넌트이다.
 4-1. (ex. StatusBar = 화면 최상단 시계, 와이파이, 배터리 등의 표시)</p>
</li>
<li><p>StatusBar를 expo-status-bar에서 import하는 이유: RN이 제공할 컴포넌트/API의 규모를 축소했기 때문. 대신 commutity package를 이용하여 deprecated된 컴포넌트들을 사용할 수 있다.
 5-1. expo의 경우 expo sdk에 자체 컴포넌트/API를 만들어, 사용자로 하여금 다양한 기능을 제공한다. 그냥 터미널창에서 expo install + (원하는 API) 입력하면 됨.
 5-2. github 등에서도 여러 패키지를 찾을 수 있다.</p>
</li>
<li><p>API를 사용하기 위한 API KEY는 반드시 RN 코드가 아닌 서버에서 사용되어야 한다.(보안성 문제)</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[React-Native의 작동 원리에 대해.]]></title>
            <link>https://velog.io/@zsj_tapout/React-Native%EC%9D%98-%EC%9E%91%EB%8F%99-%EC%9B%90%EB%A6%AC%EC%97%90-%EB%8C%80%ED%95%B4</link>
            <guid>https://velog.io/@zsj_tapout/React-Native%EC%9D%98-%EC%9E%91%EB%8F%99-%EC%9B%90%EB%A6%AC%EC%97%90-%EB%8C%80%ED%95%B4</guid>
            <pubDate>Tue, 01 Oct 2024 07:31:24 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/zsj_tapout/post/84418bf2-8cc7-4f8c-9970-690bce1ef299/image.png" alt=""></p>
<h3 id="일반적인-리액트의-구조">일반적인 리액트의 구조</h3>
<ul>
<li>컴포넌트를 이용해 HTML 생성 → ReactJS를 이용해 JavaScript화 → 브라우저를 이용해 실행</li>
</ul>
<h2 id="react-native의-구조">React-Native의 구조</h2>
<ul>
<li>운영체제와 개발자 사이의 ‘인터페이스’. 우리가 코드를 만들면 IOS나 안드로이드 자체의 코드로 ‘번역’되도록 그들에게 요청하는 것. 단지 메세지를 주고받는 레이어일 뿐이다.</li>
</ul>
<h2 id="차이점">차이점:</h2>
<ul>
<li>리액트에는 Browser가 존재하지만, RN에는 대신 ‘Bridge’가 존재한다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/zsj_tapout/post/659fe1fc-3a4e-4e83-ab61-29c424955070/image.png" alt=""></p>
<ul>
<li>우리는 ‘JavaScript’ 부분만 코드를 작성해주면 된다.</li>
<li>예를 들어 우리가 버튼을 누를 때, 운영체제가 모든 화면 터치의 시간과 위치를 감지하고(native 부분), RN이 그 정보로 JSON 메세지를 생성하고(Bridge 부분), JS(우리가 작성한 코드)가 해당 이벤트를 받는다. 이후 어떠한 반응을 보일 지를 native에게 메세지를 보내준다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/zsj_tapout/post/88f0f988-4c61-4c61-8c7a-c05a85e50ab7/image.png" alt=""></p>
<ul>
<li>전체적인 앱의 구조.</li>
<li>우리는 JavaScript와 Styling 부분만 만들어서 전송해주는 것. 나머지 InfraStructure은 Java(IOS에서는 Swift) 내부에 존재한다.</li>
<li>화살표 부분 = Bridge.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[React 응용하기: 메모장 만들기]]></title>
            <link>https://velog.io/@zsj_tapout/React-%EC%9D%91%EC%9A%A9%ED%95%98%EA%B8%B0-%EB%A9%94%EB%AA%A8%EC%9E%A5-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@zsj_tapout/React-%EC%9D%91%EC%9A%A9%ED%95%98%EA%B8%B0-%EB%A9%94%EB%AA%A8%EC%9E%A5-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Tue, 01 Oct 2024 07:29:41 GMT</pubDate>
            <description><![CDATA[<h1 id="1-메모장-프로젝트의-소개-및-설명">1. 메모장 프로젝트의 소개 및 설명</h1>
<h3 id="11-설계-및-기본-컴포넌트-구현">1.1. 설계 및 기본 컴포넌트 구현</h3>
<ul>
<li>왼쪽 사이드바에서는 현재 등록된 메모의 목록이, 오른쪽 메모 편집에서는 제목과 내용을 수정할 수 있는 입력 폼.</li>
<li>사이드바 구조: SideBarHeader, MemoList(MemoItem), SideBarFooter</li>
<li>메모 편집 구조: MemoContainer(MemoTitle + MemoContent)</li>
<li>메모 리스트 = [메모1, 메모2, 메모3, 메모4]</li>
</ul>
<h3 id="12-메모-수정-및-선택-기능-구현">1.2. 메모 수정 및 선택 기능 구현</h3>
<ul>
<li>사이드바에서 선택한 메모를 오른쪽 편집 영역에서 확인하고 수정.</li>
<li>수정된 내용은 다른 메모로 이동하더라도 보존.</li>
</ul>
<h3 id="13-메모-추가-기능-구현">1.3. 메모 추가 기능 구현</h3>
<ul>
<li>사이드바 최하단에는 새로운 메모를 추가할 수 있는 버튼 존재</li>
<li>새로운 메모 추가 시 초기 제목은 ‘untitled’로 설정되며, 내용은 비어있음</li>
<li>새로운 메모 추가 시 해당 메모가 자동으로 선택됨</li>
</ul>
<h3 id="14-메모-삭제-기능-구현">1.4. 메모 삭제 기능 구현</h3>
<ul>
<li>사이드바의 각 메모 오른쪽에는 메모를 삭제하는 x 버튼 존재</li>
<li>x버튼 클릭 시 해당 메모는 삭제, 해당 메모를 선택한 상태라면 가장 첫 번째 메모로 선택이 변경됨</li>
<li>모든 메모 삭제 시, 오른쪽 편집 영역에는 “메모가 없습니다. 새로운 메모를 생성해주세요” 라는 문구가 출력됨</li>
</ul>
<h3 id="15-메모-저장-기능-구현">1.5. 메모 저장 기능 구현</h3>
<ul>
<li>브라우저를 닫아도 편집한 메모가 유지됨 (localStroage 사용)</li>
</ul>
<h1 id="2-기본-컴포넌트-구현하기">2. 기본 컴포넌트 구현하기</h1>
<h3 id="21-appjs">2.1. App.js</h3>
<pre><code class="language-jsx">function App (){
    const [memos, setMemos] = useState([
        {
            title: &#39;Memo 1&#39;,
            content: &#39;This is memo1&#39;,
            createdAt: 1719983657532, // 생성한 시간 값.
            updatedAt: 1719983657532, // 업데이트된 시간 값.
        },
        {
            title: &#39;Memo 2&#39;,
            content: &#39;This is memo2&#39;,
            createdAt: 1719983697309, // 생성한 시간 값.
            updatedAt: 1719983697309, // 업데이트된 시간 값.
        }
    ]);

    return (
            &lt;div className=&quot;App&quot;&gt;
            &lt;SideBar memos={memos}/&gt;
            &lt;MemoContainer/&gt;
        &lt;/div&gt;

    );
}

export default App;</code></pre>
<h3 id="22-memocontainerjs">2.2. MemoContainer.js</h3>
<pre><code class="language-jsx">function MemoContainer(){
    return(
        &lt;div className=&quot;MemoContainer&quot;&gt;
            &lt;div&gt;
                &lt;input type=&quot;text&quot; className=&quot;MemoContainer_title&quot;/&gt;
            &lt;/div&gt;
            &lt;div&gt;
                &lt;textarea className=&quot;MemoContainer_content&quot;/&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    )
}

export default MemoContainer;</code></pre>
<h3 id="23-sidebarjs">2.3. Sidebar.js</h3>
<pre><code class="language-jsx">import MemoList from &quot;./MemoList&quot;;
import SideBarHeader from &quot;./SideBarHeader&quot;;
import SideBarFooter from &quot;./SideBarFooter&quot;;

function SideBar({memos}){
    return(
        &lt;div className=&quot;SideBar&quot;&gt;
            &lt;SideBarHeader/&gt;
            &lt;MemoList memos={memos}/&gt;
            &lt;SideBarFooter/&gt;
        &lt;/div&gt;
    )
}
export default SideBar;</code></pre>
<h3 id="24-memolistjs">2.4. MemoList.js</h3>
<pre><code class="language-jsx">function MemoList({memos}){
    return(
        &lt;div&gt;
            {
                memos.map((memo, index)=&gt;(
                    &lt;div key={index}&gt;{memo.title}&lt;/div&gt;
                ))
            }
        &lt;/div&gt;
    );
}
export default MemoList;</code></pre>
<h3 id="25-sidebarheaderjs">2.5. SideBarHeader.js</h3>
<pre><code class="language-jsx">function SideBarHeader({memos}){
    return(
        &lt;div className=&quot;SideBar&quot;&gt;
            SideBarHeader
        &lt;/div&gt;
    )
}
export default SideBarHeader;</code></pre>
<h3 id="26-sidebarfooterjs">2.6. SideBarFooter.js</h3>
<pre><code class="language-jsx">function SideBarFooter({memos}){
    return(
        &lt;div className=&quot;SideBar&quot;&gt;
            SideBarFooter
        &lt;/div&gt;
    )
}
export default SideBarFooter;</code></pre>
<h1 id="3-메모-수정-기능-및-메모-선택-기능-구현">3. 메모 수정 기능 및 메모 선택 기능 구현</h1>
<h3 id="31-memocontainerjs에-메모-제목과-내용을-수정해주기">3.1. MemoContainer.js에 메모 제목과 내용을 수정해주기</h3>
<pre><code class="language-jsx">function MemoContainer({memo, setMemo}){
    return(
        &lt;div className=&quot;MemoContainer&quot;&gt;
            &lt;div&gt;
                &lt;input
                    type=&quot;text&quot;
                    className=&quot;MemoContainer_title&quot;
                    value={memo.title}
                    onChange={(e)=&gt;{
                        setMemo({
                            ...memo,
                            title: e.target.value,   // 메모 타이틀의 값이 변화하면 value를 업데이트.
                            updatedAt: new Date().getTime(), // setMemo 업데이트 시 시간도 업데이트.
                        });
                    }}
                /&gt;
            &lt;/div&gt;
            &lt;div&gt;
                &lt;textarea
                    className=&quot;MemoContainer_content&quot;
                    value={memo.content}
                    onChange={(e)=&gt;{
                        setMemo({
                            ...memo,
                            content: e.target.value,   // 메모 내용의 값이 변화하면 value를 업데이트.
                            updatedAt: new Date().getTime(), // setMemo 업데이트 시 시간도 업데이트.
                        });
                    }}
                /&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    )
}

export default MemoContainer;</code></pre>
<h3 id="32-appjs에서-setmemo-변수를-이용해-내용을-rerendering해주기">3.2. App.js에서 setMemo 변수를 이용해 내용을 ReRendering해주기</h3>
<pre><code class="language-jsx">    const setMemo = (newMemo) =&gt; {
        // memos[selectedMemoIndex] = newMemo;
        //불변성의 유지를 위해, 아예 newMemos라는 새로운 배열을 만들고, 여기에 기존 배열을 풀어서 렌더링을 위해 다시 집어넣어준다.
        const newMemos = [...memos];
        newMemos[selectedMemoIndex] = newMemo;
        setMemos(newMemos);

        //setMemos([...memos]);   //[...memos]를 이용해 memos의 배열을 풀었다가 다시 감싸는 이유: 기존 reference와 새롭게 변화하는
        //값에 대해 setMemos가 변화했다고 인식해서 새롭게 렌더링을 하기 위해.
        //만약 [...memos] 대신 memos만 적으면, 내부에서 값은 바뀌지만, 바뀐 값이 리렌더링되지는 않는다.
    }</code></pre>
<h3 id="33-메모를-선택하기-위해-새롭게-메모-내용을-불러와주는-컴포넌트인-memoitemjs를-만들어주고-memolistjs에-onclick을-넣어주기">3.3. 메모를 선택하기 위해 새롭게 메모 내용을 불러와주는 컴포넌트인 MemoItem.js를 만들어주고, MemoList.js에 onClick을 넣어주기</h3>
<pre><code class="language-jsx">function MemoItem({children, onClick, isSelected}){ //부모 함수에서 onClick 때 실행되는 것을 받아옴.
    return(

            &lt;div className={&quot;MemoItem&quot; + (isSelected ? &#39; selected&#39; : &#39;&#39;)}
                onClick={onClick}
            &gt;
                {children}
            &lt;/div&gt;
    );
}
export default MemoItem;</code></pre>
<pre><code class="language-jsx">import MemoItem from &quot;./MemoItem&quot;;

function MemoList({memos, setSelectedMemoIndex, selectedMemoIndex}){
    return(
        &lt;div&gt;
            {
                memos.map((memo, index)=&gt;(
                    &lt;MemoItem
                        key = {index}
                        onClick={()=&gt;{  //MemoItem을 클릭할 때의 onClick 함수가 여기서 구현된다.
                            //사이드바의 메모 리스트를 클릭하면 메모가 수정된다.
                            setSelectedMemoIndex(index);
                        }}
                        isSelected={index === selectedMemoIndex}
                        index = {index}
                        setSelectedMemoIndex={setSelectedMemoIndex}

                    &gt;{memo.title}&lt;/MemoItem&gt;
                ))
            }
        &lt;/div&gt;
    );
}
export default MemoList;</code></pre>
<h3 id="34-appjs에-selectedmemoindex와-setselectedmemoindex를-선언해주기">3.4. App.js에 selectedMemoIndex와 setSelectedMemoIndex를 선언해주기</h3>
<pre><code class="language-jsx">function App (){
    return (
            &lt;div className=&quot;App&quot;&gt;
            &lt;SideBar
                memos={memos}
                selectedMemoIndex={selectedMemoIndex}
                setSelectedMemoIndex={setSelectedMemoIndex}/&gt;
            &lt;MemoContainer memo={memos[selectedMemoIndex]}
            setMemo={setMemo}/&gt;
        &lt;/div&gt;

    );
}

export default App;</code></pre>
<h3 id="35-시각적인-변화를-보여주기-위해-appcss-구현하기">3.5. 시각적인 변화를 보여주기 위해 App.css 구현하기.</h3>
<pre><code class="language-jsx">.App {
  text-align: center;
  display: flex;
  height: 100vh;  /* vh = 윈도우 창의 높이 단위.*/
}
.SideBar{
  background-color: aquamarine;
  width: 200px;
}
.MemoContainer{
  background-color: beige;
  flex: 1; /* flex: 해당 페이지에서 남은 만큼 넓이를 부여함.*/
}
.MemoContainer_title{
  width: 100%;
  box-sizing: border-box; /* border-box 사용 이유: input 컨테이너가 여백을 꽉 채워서 출력되기 때문에, 기존 페이지에 맞출 필요가 있다.*/
  font-size: 2rem;
}
.MemoContainer_content{
  width: 100%;
  font-size: 1.1rem;
}
.MemoItem{
  width: 100%;
  white-space: nowrap;  /*사이드바의 제목이 다음 줄로 넘어가지 않게 만들어줌.*/
  text-overflow: ellipsis;  /*사이드바를 넘어간 제목은 ...으로 처리해줌*/
  overflow: hidden;
}
.MemoItem.selected{
  background-color: rgba(100, 100, 100, 0.5); /*선택된 메모의 색을 변경해줌*/
  font-weight: 500;
}</code></pre>
<h3 id="36-npm-start시-화면">3.6. npm start시 화면</h3>
<p><img src="https://prod-files-secure.s3.us-west-2.amazonaws.com/bfbbe043-3edf-4abc-9cb4-78e0e4f1a7d6/9bb5b295-3c65-48ba-b299-f649c3f5f25b/%EB%B3%80%ED%99%94%EC%B0%BD.png" alt="변화창.png"></p>
<h1 id="4-메모-추가-기능-구현">4. 메모 추가 기능 구현</h1>
<h3 id="41-sidebarfooterjs의-css-변경-후-버튼화하기">4.1. SideBarFooter.js의 css 변경 후 버튼화하기</h3>
<pre><code class="language-jsx">function SideBarFooter({onClick}){
    //새로운 메모를 추가하는 버튼이 구현됨.
    return(
        &lt;div className=&quot;SideBar&quot;&gt;
            &lt;button
                className=&quot;SideBarFooter_add-Button&quot;
                onClick={onClick}
                //onClick 함수를 통해 버튼 클릭 시 변화를 줌
            &gt;
                +
            &lt;/button&gt;
        &lt;/div&gt;
    )
}
export default SideBarFooter;</code></pre>
<h3 id="42-appjs에-addmemo-함수를-생성하여-버튼-클릭-시-변화를-정의하기">4.2. App.js에 addMemo 함수를 생성하여 버튼 클릭 시 변화를 정의하기</h3>
<pre><code class="language-jsx">    const addMemo = () =&gt; {
        const now = new Date().getTime();
        setMemos([...memos, {
            title: &#39;Untitled&#39;,
            content: &#39;&#39;,
            createdAt: new Date().getTime(),
            updatedAt: new Date().getTime()},   //새 메모 생성 시 제목은 &#39;Untitled&#39;,
        ]);
        setSelectedMemoIndex(memos.length);
        //새 메모 생성 시, 자동으로 가장 마지막으로 생성된 메모가 띄워짐.
    };

    return (
            &lt;div className=&quot;App&quot;&gt;
            &lt;SideBar
                memos={memos}
                addMemo={addMemo} //메모 추가 기능 탑재.
                selectedMemoIndex={selectedMemoIndex}
                setSelectedMemoIndex={setSelectedMemoIndex}/&gt;
            &lt;MemoContainer memo={memos[selectedMemoIndex]}
            setMemo={setMemo}/&gt;
        &lt;/div&gt;

    );
}

export default App;</code></pre>
<h3 id="43-sidebarjs에-addmemo-함수를-추가하여-적용하기">4.3. SideBar.js에 addMemo 함수를 추가하여 적용하기.</h3>
<pre><code class="language-jsx">import MemoList from &quot;./MemoList&quot;;
import SideBarHeader from &quot;./SideBarHeader&quot;;
import SideBarFooter from &quot;./SideBarFooter&quot;;

function SideBar({memos, addMemo, setSelectedMemoIndex, selectedMemoIndex}){
    return(
        &lt;div className=&quot;SideBar&quot;&gt;
            &lt;SideBarHeader/&gt;
            &lt;MemoList
                memos={memos}
                selectedMemoIndex={selectedMemoIndex}
                setSelectedMemoIndex={setSelectedMemoIndex}/&gt;
            &lt;SideBarFooter onClick={addMemo}/&gt;
            {/*//addMemo: 메모를 추가해주는 함수.*/}
        &lt;/div&gt;
    )
}
export default SideBar;</code></pre>
<h1 id="5-메모-삭제-기능-구현">5. 메모 삭제 기능 구현</h1>
<h3 id="51-sidebar에-삭제-버튼을-구현하기-위해-memoitemjs에-x버튼-생성해주기">5.1. SideBar에 삭제 버튼을 구현하기 위해, MemoItem.js에 x버튼 생성해주기</h3>
<pre><code class="language-jsx">
function MemoItem({children, onClickItem, onClickDelete, isSelected}){ //부모 함수에서 onClick 때 실행되는 것을 받아옴.
    return(

            &lt;div className={&quot;MemoItem&quot; + (isSelected ? &#39; selected&#39; : &#39;&#39;)}
                onClick={onClickItem}
            &gt;
                {children}
                &lt;button className=&quot;MemoItem_delete_button&quot; //삭제 버튼 구현.
                // onClickDelete라는 삭제 기능을 구현할 함수를 구현.
                onClick={onClickDelete}&gt;X&lt;/button&gt;
            &lt;/div&gt;
    );
}
export default MemoItem;</code></pre>
<h3 id="52-memolistjs에-onclickdelete라는-함수를-정의해주기">5.2. MemoList.js에 onClickDelete라는 함수를 정의해주기</h3>
<pre><code class="language-jsx">import MemoItem from &quot;./MemoItem&quot;;

function MemoList({memos, setSelectedMemoIndex, selectedMemoIndex, deleteMemo}){
    return(
        &lt;div&gt;
            {
                memos.map((memo, index)=&gt;(
                    &lt;MemoItem
                        key = {index}
                        onClickItem={()=&gt;{  //MemoItem을 클릭할 때의 onClick 함수가 여기서 구현된다.
                            //사이드바의 메모 리스트를 클릭하면 메모가 수정된다.
                            setSelectedMemoIndex(index);
                        }}
                        onClickDelete={(e)=&gt;{ //MemoItem 옆 x버튼을 누를 시, deleteMemo라는 이벤트가 발생.
                        //버튼 누를 시 해당 index의 메모가 삭제됨.
                            deleteMemo(index);
                            e.preventDefault();
                            e.stopPropagation();
                        }}
                        isSelected={index === selectedMemoIndex}

                    &gt;{memo.title}&lt;/MemoItem&gt;
                ))
            }
        &lt;/div&gt;
    );
}
export default MemoList;</code></pre>
<h3 id="53-appjs에-deletememo-함수의-정의-적용하기">5.3. App.js에 deleteMemo 함수의 정의 적용하기</h3>
<pre><code class="language-jsx">function App (){

    const deleteMemo = (index) =&gt; { //deleteMemo의 의미: newMemos라는 새로운 memos를 생성한 뒤,
    //해당 index의 memo가 선택되면, 그 memo를 0으로 지워버리는 역할.
        const newMemos = [...memos];
        newMemos.splice(index, 1);
        setMemos(newMemos);
        if(index === selectedMemoIndex){
            setSelectedMemoIndex(0);
        }
    }

    return (
            &lt;div className=&quot;App&quot;&gt;
            &lt;SideBar
                memos={memos}
                addMemo={addMemo}
                selectedMemoIndex={selectedMemoIndex}
                setSelectedMemoIndex={setSelectedMemoIndex}
                deleteMemo={deleteMemo}/&gt; //deleteMemo 함수 적용
            &lt;MemoContainer memo={memos[selectedMemoIndex]}
                   setMemo={setMemo}/&gt;
        &lt;/div&gt;

    );
}

export default App;</code></pre>
<h3 id="54-sidebarjs에-deletememo-적용해주기">5.4. SideBar.js에 deleteMemo 적용해주기</h3>
<pre><code class="language-jsx">import MemoList from &quot;./MemoList&quot;;
import SideBarHeader from &quot;./SideBarHeader&quot;;
import SideBarFooter from &quot;./SideBarFooter&quot;;

function SideBar({memos, addMemo, setSelectedMemoIndex, selectedMemoIndex, deleteMemo}){
    return(
        &lt;div className=&quot;SideBar&quot;&gt;
            &lt;SideBarHeader/&gt;
            &lt;MemoList
                memos={memos}
                selectedMemoIndex={selectedMemoIndex}
                setSelectedMemoIndex={setSelectedMemoIndex}
                deleteMemo={deleteMemo}
            /&gt;
            &lt;SideBarFooter onClick={addMemo}/&gt;
            {/*//addMemo: 메모를 추가해주는 함수.*/}
        &lt;/div&gt;
    )
}
export default SideBar;</code></pre>
<h1 id="6-localstorage를-이용한-데이터-저장">6. LocalStorage를 이용한 데이터 저장</h1>
<h3 id="61-cookie-localstorage와-session-storage의-비교">6.1. Cookie, LocalStorage와 Session Storage의 비교</h3>
<ul>
<li>Cookie: 네트워크 요청 시, 서버로 함께 저장되는 문자열 데이터.</li>
<li>Local Storage: key-value 쌍으로 데이터를 저장/조회할 수 있는 데이터 저장소. 서버로 전달되지 않고, 지우지 않는 이상 브라우저나 탭을 닫았다 열어도 유지됨</li>
<li>Session Storage: Local Storage와 같으나, 같은 탭 안에서만 데이터가 유지되고, 탭이나 브라우저를 닫으면 데이터가 지워짐.</li>
</ul>
<p>###</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React 환경설정]]></title>
            <link>https://velog.io/@zsj_tapout/React-%ED%99%98%EA%B2%BD%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@zsj_tapout/React-%ED%99%98%EA%B2%BD%EC%84%A4%EC%A0%95</guid>
            <pubDate>Tue, 01 Oct 2024 07:28:56 GMT</pubDate>
            <description><![CDATA[<h1 id="1-nodejs">1. Node.js</h1>
<h2 id="11-정의">1.1. 정의</h2>
<ul>
<li>브라우저 밖에서도 서버 구축 등의 javascript를 실행할 수 있게 해주는 런타임 환경.</li>
</ul>
<h2 id="12-특징">1.2. 특징</h2>
<ul>
<li>오픈소스 Javascript 엔진인 Chrome V8을 기반으로 동작.</li>
<li>Single-Thread의 non-blocking I/O 이벤트 기반의 ‘Async’ 방식. 즉 시간이 오래 걸리는 행동을 계속 기다리지 않고, 시간이 지나 해당 행동의 결과만 따로 받아서 출력한다는 개념.</li>
</ul>
<h2 id="13-npmnode-package-manager">1.3. NPM(Node Package Manager)</h2>
<ul>
<li>Javascript를 위한 패키지 관리자.</li>
<li>공개된 라이브러리들을 쉽게 설치해 사용할 수 있다. (ex. npm start)</li>
<li>Node.js의 경우 NPM을 포함하기 때문에, 설치 시 함께 설치된다.</li>
</ul>
<h2 id="14-nodejs의-설치방법">1.4. Node.js의 설치방법</h2>
<ol>
<li>홈페이지에서 자신의 환경에 맞는 Node.js 다운로드<ol>
<li>*필자의 경우 Intellij 내부에서 Node.js를 다운로드하여 사용함.</li>
</ol>
</li>
<li>설치 확인 방법(터미널에서)</li>
</ol>
<pre><code>$ node --version
$ npm --version</code></pre><ol>
<li>실행 확인 방법</li>
</ol>
<pre><code>$ node
&gt; console.log(&#39;test&#39;);
&gt; .exit</code></pre><h1 id="2-create-react-app">2. Create-react-app</h1>
<h2 id="21-정의">2.1. 정의</h2>
<ul>
<li>React 기반의 프로젝트 개발 환경을 구성해주는 툴.</li>
</ul>
<h2 id="22-설치-방법">2.2. 설치 방법</h2>
<ul>
<li>CRA를 이용한 프로젝트 생성</li>
</ul>
<pre><code>    $ npx create-react-app memo-project
    // npx = npm 레지스트리에 존재하는 최신 버전의 모듈을 설치해줌.
    $ cd memo-project</code></pre><ul>
<li>생성한 프로젝트 실행</li>
</ul>
<pre><code>$ npm run start
// 실행 완료 시 localhost:3000으로 접속하게 된다.</code></pre><ul>
<li>실행 종료 = Ctrl + C</li>
</ul>
<h2 id="22-cra의-구조">2.2. CRA의 구조</h2>
<ul>
<li>전체 구성 요소</li>
</ul>
<p><img src="https://prod-files-secure.s3.us-west-2.amazonaws.com/bfbbe043-3edf-4abc-9cb4-78e0e4f1a7d6/0c9ca5c1-e190-4eb6-ab66-7ccbf39afe5d/CRA_%EA%B5%AC%EC%A1%B0.png" alt="CRA 구조.png"></p>
<ul>
<li>package.json: 프로젝트에 설치된 라이브러리와 정보들을 담은 파일.<ul>
<li>“scripts”: 터미널에 해당 문구를 입력하는 경우 start, build, test, eject 등의 기능을 실행하는 react library.</li>
<li>dependencies: 현재 사용 중인 라이브러리 버전 정보.</li>
</ul>
</li>
<li>index.js: 프로그램의 시작점. 서비스 실행 시 index.js 내부에 존재하는 ‘ReactDOM’에 의해서 안의 코드들이 실행되게 된다.</li>
<li>App.js: 프로그램 생성 시 기본적으로 존재하는 메인 코드.</li>
<li>index.css: 모든 js에서 전역으로 사용되는 css.</li>
<li>App.css: App.js에서만 사용되는 css.</li>
</ul>
<h1 id="3-eslint와-prettier">3. eslint와 prettier</h1>
<h2 id="31-eslint">3.1. eslint</h2>
<ul>
<li>문법 및 코드 스타일을 검사하는 도구.</li>
<li>문법적인 오류를 사전에 발견할 수 있고, 협업에서 통일된 코드 스타일을 유지할 수 있도록 도와줌</li>
<li>CRA에 자체 내장되어 있다. (package.json의 eslintConfig)</li>
</ul>
<h2 id="32-prettier">3.2. prettier</h2>
<ul>
<li>자동으로 코드를 정해진 규칙에 맞게 고쳐주는 툴</li>
<li>prettierrc.json에서 규칙 정의</li>
<li>CRA에 내장되어 있지 않으므로 직접 extension에서 설치해 주어야 한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[이벤트 핸들링하기]]></title>
            <link>https://velog.io/@zsj_tapout/%EC%9D%B4%EB%B2%A4%ED%8A%B8-%ED%95%B8%EB%93%A4%EB%A7%81%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@zsj_tapout/%EC%9D%B4%EB%B2%A4%ED%8A%B8-%ED%95%B8%EB%93%A4%EB%A7%81%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 01 Oct 2024 07:28:24 GMT</pubDate>
            <description><![CDATA[<h1 id="1-이벤트-연결하기">1. 이벤트 연결하기</h1>
<ul>
<li>React는 JavaScript처럼 Camel Case 형태로 함수를 전달해줌.</li>
<li>HTML의 경우에는 Camel Case가 아닌, 소문자 onclick에 실행함수를 넣는다.</li>
</ul>
<pre><code class="language-jsx">import {useCallback, useState} from &quot;react&quot;;
function handleClick2(e) {
    console.log(&#39;click2&#39;, e);
}
function App (){
// 이벤트 연결하기
    function handleClick1(e) {  // e = 합성 이벤트 = JavaScript에서 전달받은 원본 이벤트 객체를 &#39;확장&#39;한 객체.
        // 사실상 동일한 이벤트 객체라고 봐도 된다.
        console.log(&#39;click1&#39;, e);
    }
        //이벤트 핸들러를 만들 때는 react LifeCycle을 고려하기.
    // - 컴포넌트가 리렌더링 되는 경우 컴포넌트 내에서 단순 정의한 함수가 새롭게 만들어져 성능이 떨어짐
    // - 따라서 함수 정의를 컴포넌트 밖으로 빼거나, useCallback으로 감싸줘서 매 렌더링마다 새롭게 만들지 않게 할 필요가 있다.
    const handleChange = useCallback((e) =&gt; {   //e = 버튼 onClick에서의 변화를 인자로 받은 것.
        console.log(&#39;change&#39;, e.target.value);
    }, []);

    return (
    // 이벤트 연결하기
        &lt;div&gt;
            &lt;button onClick={handleClick1}&gt;
                Button1
            &lt;/button&gt;
            &lt;button onClick={handleClick2}&gt;
                Button2
            &lt;/button&gt;
            &lt;div&gt;
                &lt;input
                type=&quot;text&quot;
                onChange={handleChange}
                /&gt;
            &lt;/div&gt;

        &lt;/div&gt;
    );
}

export default App;</code></pre>
<h1 id="2-이벤트의-종류">2. 이벤트의 종류</h1>
<h2 id="21-마우스와-관련된-이벤트">2.1. 마우스와 관련된 이벤트</h2>
<ul>
<li>onClick: 마우스 버튼을 ‘클릭’했을 때 발생 (Down + Up)</li>
<li>onMouseDown: 마우스 버튼을 ‘눌렀을 때’ 발생</li>
<li>onMouseUp: 마우스 버튼을 ‘뗐을 때’ 발생</li>
<li>onMouseEnter: 요소 밖에서 안으로 마우스가 들어갔을 때 발생</li>
<li>onMouseLeave: 요소 안에서 밖으로 마우스가 나갔을 때 발생</li>
<li>onMouseMove: 요소 안에서 마우스 커서를 움직였을 때 발생.</li>
</ul>
<h2 id="22-키보드와-관련된-이벤트">2.2. 키보드와 관련된 이벤트</h2>
<ul>
<li>onKeyDown: 키보드의 자판을 눌렀을 때 발생(물리적인 키에 반응)</li>
<li>onKeyUp: 키보드의 자판을 뗐을 때 발생</li>
<li>onKeyPress: 키보드의 자판을 눌러 ‘문자가 입력되었을 때’ 발생</li>
</ul>
<h2 id="23-focus-form-이벤트">2.3. Focus, Form 이벤트</h2>
<ul>
<li>onFocus: 요소가 Focus되었을 때 발생</li>
<li>onBlur: 요소의 Focus가 사라지면 발생</li>
<li>onChange: 요소의 값이 변화하면 발생</li>
</ul>
<h1 id="3-form">3. Form</h1>
<ul>
<li>Controlled Component: React에 의해 입력 요소값이 제어되는 컴포넌트.</li>
<li>장점: 컴포넌트의 state와 input value가 완전히 동일한 값을 가짐(신뢰 가능한 단일 출처), 다른 컴포넌트에 input value를 전달하거나 다른 이벤트 핸들러에서 값을 재설정해줄 수 있음</li>
<li>단점: 값이 변경될 때마다 렌더링이 됨(컴포넌트의 영향 범위가 클수록 성능 저하)</li>
<li>UnControlled Component: Controlled Component와는 반대로, input value를 직접 DOM에서 가져와 사용. 이때 렌더링은 단 한 번만 하게 됨.</li>
</ul>
<pre><code class="language-jsx">// Controlled Component의 형식
function TextInput(){
    const [text, setText] = useState(&#39;&#39;);

    return(
        &lt;input
            type=&#39;text&#39;
            value={text}
            onChange={(e)=&gt;{
                setText(e.target.value);
            }}
        /&gt;
    );
}</code></pre>
<h2 id="31-controlled-vs-uncontrolled-component">3.1. Controlled vs Uncontrolled Component</h2>
<pre><code class="language-jsx">//1. Controlled Component - TextInput
import {useState} from &quot;react&quot;;

function TextInput(){
    const [value, setValue] = useState(&#39;&#39;);
    console.log(&#39;[TextInput] render&#39;, value);
    //form 요소와 value가 직접적으로 연결된 controlled component.
    //렌더링이 value가 변경될 때마다 일어나게 됨.
    return(
        &lt;input
            type=&quot;text&quot;
            value={value}
            onChange={(e)=&gt;{
                setValue(e.target.value);
            }}
        /&gt;
    );
}
export default TextInput;</code></pre>
<pre><code class="language-jsx">//2. UnControlled Component - UncontrolledTextInput
import {useRef} from &quot;react&quot;;

function UncontrolledTextInput(){
    const inputRef = useRef();  //useRef: DOM의 특정 Reference를 직접 가져올 때 사용.
    //Uncontrolled Component: input value를 state에 연결하지 않고, 직접 useRef를 이용해 DOM 객체에 접근하여 사용.
    console.log(&#39;[UncontrolledTextInput] render&#39;);

    return(
        &lt;&gt;
            &lt;input
                ref={inputRef} type=&quot;text&quot;  //가져온 객체를 직접 연결하여 사용
            /&gt;
            &lt;button
                onClick={()=&gt;{
                    console.log(inputRef.current.value);
                    //state의 상태는 변화하지 않기 때문에, 값이 변경되더라도 Rerendering이 되지 않는다.
                }}
                &gt;
                Get Value
            &lt;/button&gt;
        &lt;/&gt;
    );
}
export default UncontrolledTextInput;</code></pre>
<h1 id="4-설문조사-만들기">4. 설문조사 만들기</h1>
<ul>
<li>이름과 사는 곳을 입력받는 설문조사 폼 만들기</li>
<li>만약 사는 곳이 ‘한국’인 경우, ‘2-1. 한국 어디에 사나요?’라는 항목을 노출시키기.</li>
<li>1번과 2번을 모두 입력하지 않았다면 저장 버튼 비활성화.</li>
<li>저장 버튼 클릭 시 “저장되었습니다”라는 문구를 띄우고, 입력된 폼 내용을 모두 제거.</li>
</ul>
<h2 id="41-입력-코드">4.1. 입력 코드</h2>
<pre><code class="language-jsx">//1. TextInputEx.js: 이름과 한국 어디에 사는 지 입력할 때 사용됨.
function TextInputEx({value, setValue}){
    //form 요소와 value가 직접적으로 연결된 controlled component.
    //렌더링이 value가 변경될 때마다 일어나게 됨.
    return(
        &lt;input
            type=&quot;text&quot;
            value={value}
            onChange={(e)=&gt;{
                setValue(e.target.value);
            }}
        /&gt;
    );
}
export default TextInputEx;</code></pre>
<pre><code class="language-jsx">//2. SelectEx.js: 사는 곳을 선택할 때 사용.
function SelectEx({value, setValue, options= []}){
    return(
        &lt;select value={value}
                onChange={(e)=&gt;{
                setValue(e.target.value);
            }}
        &gt;
            &lt;option value=&quot;&quot; disabled&gt;지역을 선택해 주세요.&lt;/option&gt;
            {options.map((item) =&gt; (
                &lt;option key={item} value={item}&gt;{item}&lt;/option&gt;
                ))}
        &lt;/select&gt;
    );
}
export default SelectEx;</code></pre>
<pre><code class="language-jsx">//3. SSulMoon.js: 메인 페이지 렌더링.
import {useState} from &quot;react&quot;;
import TextInputEx from &quot;./eventComponent/TextInputEx&quot;;
import SelectEx from &quot;./eventComponent/SelectEx&quot;;

const countryOptions = [
    &quot;한국&quot;,
    &quot;러시아&quot;,
    &quot;일본&quot;,
    &quot;중국&quot;,
    &quot;미국&quot;
];
function SSulMoon(){
    const [formValue, setFormValue] = useState({
        name: &quot;&quot;,
        country: &quot;&quot;,
        address: &quot;&quot;
    });
    console.log(&#39;[App] formValue&#39;, formValue);

    return(
        &lt;div className=&quot;App&quot;&gt;
            &lt;div className=&quot;form&quot;&gt;
                &lt;div className=&quot;form-item&quot;&gt;
                    &lt;h1&gt;1. 이름이 무엇인가요?&lt;/h1&gt;
                    &lt;TextInputEx value={formValue.name} setValue={(value)=&gt;{
                        setFormValue((state)=&gt;({
                            ...state,   //기존의 formValue 내부 요소들을 그대로 유지해 주는 기능.
                            name: value   //name만 바꿔준다.
                        }));
                    }}/&gt;
                &lt;/div&gt;
                &lt;div className=&quot;form-item&quot;&gt;
                    &lt;h1&gt;2. 사는 곳은 어디인가요?&lt;/h1&gt;
                    &lt;SelectEx value={formValue.country}
                              setValue={(value)=&gt;{
                                setFormValue((state)=&gt;({
                                    ...state,
                                    country: value
                                }));
                              }}
                              options={countryOptions}
                    /&gt;
                &lt;/div&gt;
                {formValue.country === &quot;한국&quot; &amp;&amp; ( //만약 country의 값이 &#39;한국&#39;이라면,
                    &lt;div className=&quot;form-item&quot;&gt;
                        &lt;h1&gt;2-1. 한국 어디에 사나요?&lt;/h1&gt;
                        &lt;TextInputEx value={formValue.address} setValue={(value) =&gt; {
                            setFormValue((state) =&gt; ({
                                ...state,   //기존의 formValue 내부 요소들을 그대로 유지해 주는 기능.
                                address: value   //address만 바꿔준다.
                            }));
                        }}/&gt;
                    &lt;/div&gt;
                )}
                {/*2-1의 div를 활성화해주고, 아니라면 null값을 출력한다.*/}

                &lt;div className=&quot;button-group&quot;&gt;
                    &lt;button onClick={() =&gt; {
                        alert(&#39;저장되었습니다&#39;);
                        setFormValue({
                            name: &quot;&quot;,
                            country: &quot;&quot;,
                            address: &quot;&quot;
                        });
                    }}
                            disabled={!formValue.name || !formValue.country}
                        //name과 country가 공백이면 버튼이 비활성화 되도록 만들기.
                            &gt;
                        저장
                    &lt;/button&gt;
                &lt;/div&gt;
            &lt;/div&gt;

        &lt;/div&gt;
    )
}

export default SSulMoon;</code></pre>
<h2 id="41-결과-페이지">4.1. 결과 페이지</h2>
<p><img src="https://prod-files-secure.s3.us-west-2.amazonaws.com/bfbbe043-3edf-4abc-9cb4-78e0e4f1a7d6/02baa927-430b-4bcf-b9b2-831f46fdd774/%EC%84%A4%EB%AC%B8%ED%8E%98%EC%9D%B4%EC%A7%80.png" alt="설문페이지.png"></p>
<p><img src="https://prod-files-secure.s3.us-west-2.amazonaws.com/bfbbe043-3edf-4abc-9cb4-78e0e4f1a7d6/4e1a2e8e-16f5-4c07-a1e7-c5e98f35a1d5/%EC%84%A4%EB%AC%B8%EC%A0%80%EC%9E%A5.png" alt="설문저장.png"></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Hook의 사용과 렌더링]]></title>
            <link>https://velog.io/@zsj_tapout/Hook%EC%9D%98-%EC%82%AC%EC%9A%A9%EA%B3%BC-%EB%A0%8C%EB%8D%94%EB%A7%81</link>
            <guid>https://velog.io/@zsj_tapout/Hook%EC%9D%98-%EC%82%AC%EC%9A%A9%EA%B3%BC-%EB%A0%8C%EB%8D%94%EB%A7%81</guid>
            <pubDate>Tue, 01 Oct 2024 07:27:00 GMT</pubDate>
            <description><![CDATA[<h1 id="1-hook의-종류와-사용법">1. Hook의 종류와 사용법</h1>
<ul>
<li><p>Hook: 여러 컴포넌트 중 function이 class를 사용할 수 있도록 도와주는 기능.</p>
</li>
<li><p>useEffect: useEffect(() ⇒ {함수}, [state or data값])의 형태로, 렌더링 때마다 특정한 작업을 하게 도와준다.</p>
<pre><code class="language-jsx">  useEffect(() ⇒ {함수})</code></pre>
<ul>
<li><p>=⇒ 페이지를 렌더링(mount)할 때마다 함수가 실행된다.</p>
<pre><code class="language-jsx">useEffect(() ⇒ {함수}, [state or data값])</code></pre>
</li>
<li><p>=⇒ 첫 렌더링 시에만 함수가 실행된다.</p>
<pre><code class="language-jsx">useEffect(() ⇒ {함수}, [state or data값])</code></pre>
</li>
<li><p>=⇒ 해당 state or data값이 변화할 때 함수가 실행된다.</p>
</li>
</ul>
</li>
<li><p>useCallback: 기존의 연산 결과값을 다음 번에 재사용하기 위해 저장해두었다가 함수를 반환하는‘Memoization’의 기능을 구현함.</p>
</li>
</ul>
<h2 id="11-useeffect-사용예시">1.1. useEffect 사용예시</h2>
<pre><code class="language-jsx">import {
    useCallback,
    useEffect,
    useState
} from &quot;react&quot;;

function Counter() {
    console.log(&#39;Render Counter&#39;)

    const [value, setValue] = useState(0);

    //useEffect 사용 1. body 클릭시 콘솔에 어떻게 뜨는지 보기
    useEffect(() =&gt; {
        console.log(
            &#39;[Function] useEffect []: 컴포넌트가 마운트될 때 한 번만.&#39;
        );
        const eventHandler = () =&gt;{
            console.log(&#39;click body&#39;);
        };
        document.body.addEventListener(
            &#39;click&#39;,
            eventHandler
        );
        return () =&gt; {
            console.log(
                &#39;[Function] useEffect return []: 컴포넌트가 언마운트 될 때.&#39;
            );
            document.body.removeEventListener(
                &#39;click&#39;,
                eventHandler
            );
        };
    }, []);

    // useEffect 사용 2. 컴포넌트 마운트 시 value가 변경 or 새로 useEffect를 수행할 때.
    useEffect(() =&gt; {
        console.log(
            &#39;[Function] useEffect [value]: 컴포넌트가 마운트될 때, + value가 변경되면, &#39;
        );
            const eventHandler = () =&gt;{
                console.log(&#39;click body&#39;);
            };
            document.body.addEventListener(
                &#39;click&#39;,
                eventHandler
            );

        return () =&gt; {
            console.log(
                &#39;[Function] useEffect return [value]: 새로 useEffect를 수행하기 전에,&#39;
            );
        };
    },[value]);

    return(
        &lt;div&gt;
            &lt;h1&gt;value:{value}&lt;/h1&gt;
            &lt;button onClick={increaseValue}&gt;Increase Value&lt;/button&gt;
            &lt;button onClick={resetValue}&gt;reset Value&lt;/button&gt;
        &lt;/div&gt;
    )
}
export default Counter;</code></pre>
<h2 id="12-usecallback-사용예시">1.2. useCallback 사용예시</h2>
<pre><code class="language-jsx">import {
    useCallback,
    useEffect,
    useState
} from &quot;react&quot;;

function Counter() {
    console.log(&#39;Render Counter&#39;)

    const [value, setValue] = useState(0);

// useCallback의 사용법. 이전에 있던 memoization된 값(여기서는 value)을 다음 렌더링에서 재활용하는 방식.
    const increaseValue = () =&gt; {
        setValue(value + 1);
        console.log(&#39;Render Counter2&#39;)
    };

    const resetValue = useCallback(() =&gt; {
        setValue(0);
    }, []);

    return(
        &lt;div&gt;
            &lt;h1&gt;value:{value}&lt;/h1&gt;
            &lt;button onClick={increaseValue}&gt;Increase Value&lt;/button&gt;
            &lt;button onClick={resetValue}&gt;reset Value&lt;/button&gt;
        &lt;/div&gt;
    )
}
export default Counter;</code></pre>
<h2 id="13-hook-기능-사용-시-주의점">1.3. Hook 기능 사용 시 주의점</h2>
<ol>
<li>반복문 or 조건문 내부에서 Hook 구문이 실행될 수 없다.</li>
<li>Hook 기능은 component 내부에서만 사용하자.</li>
</ol>
<h1 id="2-렌더링">2. 렌더링</h1>
<h2 id="21-렌더링의-과정">2.1. 렌더링의 과정</h2>
<ol>
<li>Re-rendering이란? state나 props의 상태가 변화했을 때, 다시 렌더링하는 것.</li>
</ol>
<h2 id="22-class-component와-functional-component의-life-cycle-비교">2.2. Class Component와 Functional Component의 Life Cycle 비교</h2>
<p><img src="https://prod-files-secure.s3.us-west-2.amazonaws.com/bfbbe043-3edf-4abc-9cb4-78e0e4f1a7d6/2a2b0925-abab-4408-8a14-fbaf65346604/%EB%A6%AC%EC%95%A1%ED%8A%B8_%EB%A0%8C%EB%8D%94%EB%A7%81_%EA%B3%BC%EC%A0%95_%ED%81%B4%EB%9E%98%EC%8A%A4_%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8.png" alt="리액트 렌더링 과정_클래스 컴포넌트.png"></p>
<ol>
<li>Class Component</li>
</ol>
<ul>
<li>Mounting: 가장 처음 Component가 실행되었을 때</li>
<li>Updating: Component의 상태가 변경되었을 때</li>
<li>UnMounting: Component를 지울 때</li>
</ul>
<p><img src="https://prod-files-secure.s3.us-west-2.amazonaws.com/bfbbe043-3edf-4abc-9cb4-78e0e4f1a7d6/6a05aa00-1654-416e-a4ba-199ccebe0b3f/%ED%95%A8%EC%88%98%ED%98%95_%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8_%EB%9D%BC%EC%9D%B4%ED%94%84%EC%82%AC%EC%9D%B4%ED%81%B4.png" alt="함수형 컴포넌트 라이프사이클.png"></p>
<ol>
<li>Functional Component</li>
</ol>
<ul>
<li>처음 mount되면, 자신을 바로 실행하고, 반환된 jsx값을 DOM에 반환함.</li>
<li>Update하는 경우에도 똑같이 실행하고 반환된 jsx값을 DOM에 반환함.</li>
<li>useEffect를 통해 Class Component의 componentDidMount, componentDidUpdate, componentWillUnmount 기능을 모두 수행할 수 있다.</li>
</ul>
<h2 id="23-예시를-통한-렌더링의-비교">2.3. 예시를 통한 렌더링의 비교</h2>
<ol>
<li>Class Component</li>
</ol>
<pre><code class="language-jsx">import {Component, useEffect, useState} from &quot;react&quot;;

//Class Component의 lifecycle. 렌더링을 기반으로 다양한 method들이 실행된다.
class ClassComponent2 extends Component{
    state = {
        value: 0
    };

    constructor(props) {
        console.log(&#39;[Class] Constructor&#39;);
        super(props);
        this.state = {
            value: 0
        };
    }

    // 컴포넌트 렌더링 전 실행(return이 false면 렌더링 안함)
    shouldComponentUpdate(nextProps, nextState, nextContext) {
        console.log(&#39;[Class] shouldComponentUpdate&#39;);
        return true;
    }

    // 컴포넌트 마운트가 끝나면 실행
    componentDidMount() {
        console.log(&#39;[Class] componentDidMount&#39;);
    }

    // 컴포넌트 업데이트 후 실행
    componentDidUpdate(prevProps, prevState, snapshot) {
        console.log(&#39;[Class] componentDidUpdate&#39;);
    }

    // 컴포넌트 언마운트 직전 실행
    componentWillUnmount() {
        console.log(&#39;[Class] componentWillUnmount&#39;);
    }

    render() {
        console.log(&#39;[Class] render&#39;);
        return(
            &lt;div&gt;
                &lt;h1&gt;ClassComponent&lt;/h1&gt;
                &lt;h1&gt;value: {this.state.value}&lt;/h1&gt;
                &lt;button
                    onClick={()=&gt;{
                        this.setState((state)=&gt; ({
                            value: state.value + 1
                        }));
                    }}
                &gt;
                    Increase Value&lt;/button&gt;
            &lt;/div&gt;
        )
    }

}
export default ClassComponent2;</code></pre>
<ol>
<li>Functional Component</li>
</ol>
<pre><code class="language-jsx">import {useEffect, useState} from &quot;react&quot;;

//Functional Component의 구현. useEffect를 사용하여 Mount/Update/UnMount가 모두 처리된다.
function FunctionalComponent() {
    console.log(&#39;[Function] Beggining&#39;);    //Mount 시 1번 로그, Update 시 1번 로그
    const [value, setValue] = useState(0);

    useEffect(() =&gt; {   //Mount될 때의 useEffect. //Mount 시 3번 로그, Update 시 생략.
        console.log(&#39;[Functional] useEffect []&#39;);
        return() =&gt; {   //Unmount 시 1번 로그.
            console.log(
                &#39;[Function] useEffect return []&#39;
            );
        }
    }, []);

    useEffect(() =&gt; {   //value가 update될 때, 위 useEffect는 생략하고 ReRendering된다. //Mount 시 4번 로그, Update 시 4번 로그.
        console.log(&#39;[Functional] useEffect [value]&#39;);
        return() =&gt; {
            console.log(
                &#39;[Function] useEffect return [value]&#39;
                   //Update 시 3번 로그, Unmount 시 2번 로그.
            );
        }
    }, [value]);

    console.log(&#39;[Function] end&#39;);  //Update상황에서 이 로그가 실행된 뒤 화면에 출력된다.
     //Mount 시 2번 로그, Update 시 2번 로그

    return(
        &lt;div&gt;
            &lt;h1&gt;FunctionComponent&lt;/h1&gt;
            &lt;h1&gt;value: {value}&lt;/h1&gt;
            &lt;button
                onClick={()=&gt;{
                    setValue((state)=&gt;state + 1);   //value를 update시키는 trigger.
                }}
            &gt;Increase Value&lt;/button&gt;
        &lt;/div&gt;
    )
}
export default FunctionalComponent;</code></pre>
<h1 id="3-간단한-아코디언-컴포넌트-만들기">3. 간단한 아코디언 컴포넌트 만들기</h1>
<ul>
<li><p>조건: 오른쪽 상단의 ‘+’ 버튼을 누르면 하단 ‘content’ 페이지가 펼쳐지고, 다시 ‘-’ 버튼을 누르면 하단 ‘content’ 페이지가 접히게 만들어보자.</p>
<p>  ⇒ 버튼에 대해 변화를 주는 함수를 만들자!</p>
<pre><code class="language-jsx">  import {useEffect, useState} from &quot;react&quot;;

  function Accordion({title, content}){
      const [isOpened, setIsOpened] = useState(false);

      return(
          &lt;div&gt;
              &lt;div
                  style={{
                      background: &#39;#666&#39;,
                      color: &#39;white&#39;,
                      fontWeight: &#39;bold&#39;,
                      padding: 10,
                      display: &#39;flex&#39;,
                      justifyContent: &#39;space-between&#39;
                  }}
              &gt;
                  &lt;div&gt;{title}&lt;/div&gt;
                  &lt;div
                      onClick={()=&gt;{
                          // setIsOpened(!isOpened);  //1안. 직접 바꿔주기
                          setIsOpened((state)=&gt;{
                              return !state   //2안. onClick 내부에서 자체적으로 바꾸기.

                          });
                      }}
                  &gt;{isOpened ? &#39;-&#39; : &#39;+&#39;}&lt;/div&gt;
              &lt;/div&gt;
              {isOpened &amp;&amp; &lt;div style={{
                  border: &#39;1px solid #999&#39;,
                  padding: 20
              }}
              &gt;
                  {content}
              &lt;/div&gt;}

          &lt;/div&gt;
      )
  }

  export default Accordion;</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Component]]></title>
            <link>https://velog.io/@zsj_tapout/Component</link>
            <guid>https://velog.io/@zsj_tapout/Component</guid>
            <pubDate>Tue, 01 Oct 2024 07:26:38 GMT</pubDate>
            <description><![CDATA[<h1 id="1-컴포넌트의-정의">1. 컴포넌트의 정의</h1>
<ul>
<li>한 페이지를 구성하게 되는 작은 구성요소.</li>
<li>장점:<ul>
<li>코드의 양이 줄어듦</li>
<li>개발 시간이 줄어듦</li>
<li>유지보수가 간편해짐</li>
</ul>
</li>
<li>예시: hello와 world라는 component로 App.js를 구성하기</li>
</ul>
<pre><code class="language-jsx">// hello.js

export default function Hello() {
    return &lt;div&gt;Hello&lt;/div&gt;;
}</code></pre>
<pre><code class="language-jsx">// world.js

export default function World(){
    return &lt;div&gt;World&lt;/div&gt;;
}</code></pre>
<pre><code class="language-jsx">// App.js

import Hello from &#39;./components/hello.js&#39;;
import World from &#39;./components/World.js&#39;;

function App () {
    return(
        &lt;div&gt;
            &lt;Hello&gt;&lt;/Hello&gt;
            &lt;World/&gt;
        &lt;/div&gt;
    );
}
export defualt App;</code></pre>
<h1 id="2-props와-state">2. props와 state</h1>
<ul>
<li>props: 부모 component에서 자식 component로 전달되는 ‘읽기 전용’의 입력 데이터. 자식 component에서 수정이 불가능.</li>
<li>state: component 자체가 가진 데이터. 읽기와 쓰기 모두 가능하며, setState를 활용해 값에 변화를 주어 component의 상태를 변환할 수도 있다.</li>
<li>예시: useState를 활용하여 increase버튼을 클릭할 때마다 value값이 1씩 증가하고, reset버튼을 누르면 값이 0이 되도록 만들기</li>
</ul>
<pre><code class="language-jsx">import {useState} from &quot;react&quot;;

function App (){
    const [value, setValue] = useState(0);
    //state의 형식: const [state, setState] = useState();

    return (
        &lt;div&gt;
        &lt;h1&gt;value: {value}&lt;/h1&gt;
        &lt;button
            onClick={() =&gt; {
                console.log(&#39;increase value&#39;, value);
                setValue(value + 1);
                //setValue를 사용하여 value값을 변화시킨다.
            }}
            &gt;
            Increase value
        &lt;/button&gt;
        &lt;button
            onClick={() =&gt; {
                setValue(0);
                //setValue();를 사용하여 value값을 설정해준다.
            }}
            &gt;
            Reset Value
        &lt;/button&gt;
        &lt;/div&gt;
    );
}
export default App;</code></pre>
<h1 id="3-class형-component">3. Class형 Component</h1>
<pre><code class="language-jsx">import {Component} from &quot;react&quot;;

// 클래스형 컴포넌트의 사용방식
export default class ClassComponent extends Component{  //Component라는 클래스를 react에서 가져옴.
    state = {   //const [value, setValue] = useState(0); 의 형태 대신, 클래스형 컴포넌트에서는
                            // state={value: 0}; 의 형태로 사용한다.
        value: 0
    };

    constructor(props) {    //Component에서 constructor를 바로 가져와서 생성이 가능
        super(props);
        this.state = {
            value: 1
        };
    }

    resetValue() {
        this.setState({value: 0});  //setState도 Component에 있으므로 바로 가져와서 사용 가능하다.
    }

    render(){
        return(
            &lt;div&gt;
                &lt;h1&gt;value: {this.state.value}&lt;/h1&gt;
                &lt;button
                    onClick={()=&gt;{
                        this.setState((state) =&gt; ({
                            value: state.value + 1
                        }));
                    }}
                    &gt;
                    Increase value
                &lt;/button&gt;
                &lt;button
                    onClick={this.resetValue.bind(this)}
                    // resetValue라는 함수를 외부에서 만들고, this를 통해 가져오고, .bind를 통해 온다.
                    &gt;
                    Reset Value
                &lt;/button&gt;
            &lt;/div&gt;
        //     클래스형 컴포넌트의 단점으로 인해 Hooks라는 개념을 사용 =&gt; 함수형 컴포넌트.
        )
    }
}</code></pre>
<h1 id="4-component-실습">4. Component 실습:</h1>
<ul>
<li>CourseCard.js의 Component인 CourseCard를, StudyFi.js에서 호출하여 데이터값을 집어넣고 스터디파이 강의 페이지를 만들어 보자.</li>
</ul>
<pre><code class="language-jsx">//CourseCard.js

export default function CourseCard({
        img,
        tags,
        title,
        salePercent,
        monthlyPrice,
        installmentMonth
    }){
    return (
        &lt;div className=&quot;CourseCard&quot;&gt;
                &lt;div className=&quot;cover&quot;&gt;
                    &lt;img alt=&quot;&quot;
                         src={img}
                         /&gt;
                &lt;/div&gt;
                &lt;div className=&quot;info&quot;&gt;
                    &lt;ul className=&quot;tags&quot;&gt;
                        {tags.map((item, idx) =&gt;
                            &lt;li key = {idx} className=&quot;tag&quot;&gt;{item}&lt;/li&gt;
                        )}
                    &lt;/ul&gt;
                    &lt;h4 className=&quot;name&quot;&gt;{title}&lt;/h4&gt;
                    &lt;div className=&quot;prices&quot;&gt;
                        &lt;span className=&quot;sale-percent&quot;&gt;{salePercent}%↓&lt;/span&gt;
                        &lt;span className=&quot;monthly-price&quot;&gt;월 {monthlyPrice.toLocaleString()}원&lt;/span&gt;
                        &lt;span className=&quot;installment-month&quot;&gt;/ {installmentMonth}개월&lt;/span&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
        &lt;/div&gt;
    );
}</code></pre>
<pre><code class="language-jsx">//StudyFi.js

import CourseCard from &quot;./components/CourseCard&quot;;
function StudyFi () {
    return(
        &lt;div style={{padding: 30}}&gt;
            &lt;CourseCard
                img = {&quot;https://dst6jalxvbuf5.cloudfront.net/media/images/Course/cover_image/221020_172526/%E1%84%8F%E1%85%A9%E1%84%89%E1%85%B3%E1%84%8F%E1%85%A1%E1%84%83%E1%85%B3_%E1%84%92%E1%85%A1%E1%86%A8%E1%84%89%E1%85%B3%E1%86%B8%E1%84%8B%E1%85%A8%E1%84%8C%E1%85%A5%E1%86%BC_PC.png&quot;}
                tags = {[&#39;발표&#39;, &#39;패키지&#39;, &#39;최대할인&#39;]}
                title = &quot;비즈니스 올인원, 방구석 어학연수 패키지&quot;
                salePercent = {51}
                monthlyPrice = {16583}
                installmentMonth = {12}
                /&gt;
        &lt;/div&gt;
    );
}

export default StudyFi;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[JSX의 기본 형식과 구문]]></title>
            <link>https://velog.io/@zsj_tapout/JSX%EC%9D%98-%EA%B8%B0%EB%B3%B8-%ED%98%95%EC%8B%9D%EA%B3%BC-%EA%B5%AC%EB%AC%B8</link>
            <guid>https://velog.io/@zsj_tapout/JSX%EC%9D%98-%EA%B8%B0%EB%B3%B8-%ED%98%95%EC%8B%9D%EA%B3%BC-%EA%B5%AC%EB%AC%B8</guid>
            <pubDate>Tue, 01 Oct 2024 07:26:02 GMT</pubDate>
            <description><![CDATA[<h1 id="1-리액트의-기본-사용법">1. 리액트의 기본 사용법</h1>
<ul>
<li>jsx의 기본형식:</li>
</ul>
<pre><code class="language-jsx">///예시: 여러 가지 형태의 변수 사용 가능성 및 사용예시
let text = &quot;hello, world!&quot;;

const num = 15;// const obj = {key: 0, a: 1, b: 2};
    //object는 jsx에서 사용 불가
    const arr = [&#39;a&#39;, &quot;b&quot;, &quot;c&quot;];

const imageUrl =
    &quot;https://dst6jalxvbuf5.cloudfront.net/static/img/logo/logo.svg&quot;;

function App (){

    return (

&lt;div className={&quot;new&quot;}&gt;

    &lt;h1&gt;변수 넣기&lt;/h1&gt;

    &lt;ul&gt;

        &lt;li&gt;{text}&lt;/li&gt;

        &lt;li&gt;{text + &#39;test&#39;}&lt;/li&gt;

    &lt;/ul&gt;

    &lt;h1&gt;숫자 및 계산식 넣기&lt;/h1&gt;

    &lt;ul&gt;

        &lt;li&gt;{num}&lt;/li&gt;

        &lt;li&gt;{num + 10}&lt;/li&gt;

    &lt;/ul&gt;

    &lt;h1&gt;Boolean, Nullish 값 넣기&lt;/h1&gt;

    &lt;ul&gt;

        &lt;li&gt;{true}&lt;/li&gt;

        &lt;li&gt;{false}&lt;/li&gt;

        &lt;li&gt;{undefined}&lt;/li&gt;

        &lt;li&gt;{null}&lt;/li&gt;

    &lt;/ul&gt;

    &lt;h1&gt;Object, array 넣기&lt;/h1&gt;

    &lt;ul&gt;

        {/*&lt;li&gt;{obj}&lt;/li&gt;*/}

        &lt;li&gt;{arr}&lt;/li&gt;

        &lt;li&gt;{[&lt;div&gt;hello&lt;/div&gt;, &lt;div&gt;you&lt;/div&gt;]}&lt;/li&gt;

    &lt;/ul&gt;

    &lt;h1&gt;주석 넣기&lt;/h1&gt;

    &lt;ul&gt;

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

    &lt;/ul&gt;

    &lt;h1&gt;주석 속석에 넣기&lt;/h1&gt;

    &lt;img src={imageUrl} alt=&#39;logo&#39;/&gt;

&lt;/div&gt;

    );

}
export default App;</code></pre>
<p>형태와 같이,  java + HTML의 형태를 구사하고 있다. </p>
<p>마지막 줄의 ‘export default App;’이라는 구문을 통해 App함수를 추출하고, 메인 렌더링을 담당하는 index.js를 비롯한 다른 여러 component 소스들에서 이 App함수를 import하여 사용할 수 있게 해준다.</p>
<h1 id="2-if문과-반복문">2. if문과 반복문</h1>
<ol>
<li><p>조건문</p>
<ul>
<li><p>삼항연산자: {조건 ? 조건이 참일 때 return값 : 조건이 거짓일 때 return값}의 형태.</p>
</li>
<li><p>or문: {조건 || 조건이 거짓일 때 return값}의 형태.</p>
</li>
<li><p>And문: {조건 &amp;&amp; 조건이 참일 때 return값}의 형태.</p>
</li>
<li><p>if문: {(()=&gt;{ if (조건) return &#39;조건이 참일 때 return값&#39;;</p>
<p>   else return &#39;조건이 거짓일 때 return값&#39;; })()}의 형태로 컴포넌트 내에서 사용 가능.</p>
</li>
</ul>
</li>
</ol>
<pre><code class="language-jsx">function App (){
    return (
const arr = [1, 2, 3];

const text = &#39;&#39;;

function App (){    return (

&lt;div className={&quot;ifMoon&quot;}&gt;

    &lt;h1&gt;삼항연산자&lt;/h1&gt;

    &lt;ul&gt;

        &lt;li&gt;{1 + 1 === 2

            ? &#39;참입니다&#39;    //true일때

            : &#39;거짓입니다&#39;   //false일때

        }

        &lt;/li&gt;

    &lt;/ul&gt;

    &lt;h1&gt;And 연산자&lt;/h1&gt;

    &lt;ul&gt;

        &lt;li&gt;

            {1 + 1 === 2 &amp;&amp; &#39;And 연산자 1&#39;}

        {/*만일 &amp;&amp; 앞의 값이 true일 경우, 뒤의 값을 리턴. 그렇지 않은 경우 리턴하지 않음*/}

        &lt;/li&gt;

        &lt;li&gt;

            {arr.length &amp;&amp; &#39;And 연산자 2&#39;}

        &lt;/li&gt;

    &lt;/ul&gt;

    &lt;h1&gt;Or 연산자&lt;/h1&gt;

    &lt;ul&gt;

        &lt;li&gt;

            {1 + 1 !== 2 || &#39;Or 연산자 1&#39;}

        &lt;/li&gt;

        {/*만일 || 앞의 값이 false일 경우, 뒤의 값을 리턴. 앞의 값이 True인 경우 리턴하지 않음*/}

        &lt;li&gt;

            {text || &#39;Or 연산자 2&#39;}

        &lt;/li&gt;

    &lt;/ul&gt;

    &lt;h1&gt;IF문&lt;/h1&gt;

    &lt;ul&gt;

        &lt;li&gt;

            {(()=&gt;{

                if (1+1!==2) return &#39;IF&#39;;

                else return &#39;ELse&#39;;

            })()}

        &lt;/li&gt;

        &lt;li&gt;

            {(()=&gt;{

                const data = &#39;조건실행함수&#39;;

            //     어떤 연산이든 추가가 가능하다.

                return data;

            })()}

        &lt;/li&gt;

    &lt;/ul&gt;

&lt;/div&gt;

    );

}
export default App;</code></pre>
<ul>
<li>반복문<ul>
<li>java의 그것과 같은 형태로 사용.</li>
<li>mapping을 통해 반복적인 출력이 가능함.</li>
</ul>
</li>
</ul>
<pre><code class="language-jsx">const arr = [&#39;1번&#39;, &#39;2번&#39;, &#39;3번&#39;, &#39;3번&#39;];
const arr2 = [];
for (let i = 0; i &lt; arr.length; i++){
    arr2.push(&lt;h4 key={i}&gt;{arr[i]}&lt;/h4&gt;);   //arr의 길이만큼 반복해서 &lt;h4&gt;로 넣어주기.
}

function App (){

    return (
            &lt;div&gt;
            &lt;h1&gt;배열로 넣기&lt;/h1&gt;
            &lt;ul&gt;
                &lt;li&gt;{arr}&lt;/li&gt;
                &lt;li&gt;{arr2}&lt;/li&gt;
            &lt;/ul&gt;

            &lt;hr/&gt;

            &lt;h1&gt;Array.map&lt;/h1&gt;
            &lt;ul&gt;
                &lt;li&gt;
                      {arr.map((item, index) =&gt; {  //map((item, index) =&gt; {})의 형식을 이용
                                                                                  //하여 데이터를 index(순서)에 맞게 출력.
                    return &lt;h4 key ={item}&gt;{item}&lt;/h4&gt;;
                })}&lt;/li&gt;
            &lt;/ul&gt;
        &lt;/div&gt;
     );
}
export default App;</code></pre>
<ul>
<li>CSS</li>
</ul>
<pre><code class="language-jsx">import &#39;./App.css&#39;;
function App (){
    return (
        &lt;div
            style={
                {
                    position: &#39;relative&#39;, width: 400, height: 1000, background: &#39;#f1f1f1&#39;
                }
            }
        &gt;
            &lt;div style={roundBoxStyle}&gt;Hello1&lt;/div&gt;
            &lt;div style={{...roundBoxStyle, top: 350}}&gt;
                &lt;div className={&quot;highLight&quot;}&gt;Hello2&lt;/div&gt;
            {/*className을 이용해서 .css의 스타일을 가져오기 --&gt; 굳이 쓸 필요가 있나...*/}
            &lt;/div&gt;

            &lt;div style={{...roundBoxStyle, top: 650}}&gt;
                &lt;div className={
                    1 + 1 === 2 ? &#39;highLight&#39; : &#39;&#39;
                }&gt;
                Hello3
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;</code></pre>
<h1 id="3-활용예제-구구단">3. 활용예제: 구구단</h1>
<ul>
<li>조건문, 반복문, css를 활용하여 구구단 표 만들어 보기</li>
</ul>
<pre><code class="language-jsx">//조건: 2단부터 9단까지의 구구단을 작성하되, 1단과 5단은 제외하고, 짝수는 blue/홀수는
// black으로 작성하기
import &#39;./App.css&#39;;

const num = [1, 2, 3, 4, 5, 6, 7, 8, 9];

function App (){

    return (
        &lt;div style={{display: &#39;flex&#39;}}&gt;
            {num.map(
                (n) =&gt;
                    n &gt;= 2 &amp;&amp; n !== 5 &amp;&amp; (&lt;div style={{padding:10, color: n%2
                     ? &#39;blue&#39; : &#39;black&#39;}}&gt;
                            {num.map((m) =&gt; (
                                &lt;div&gt;
                                    {n} x {m} = {n * m}
                                &lt;/div&gt;
                            ))}
                        &lt;/div&gt;
                    )
            )}
        &lt;/div&gt;
    );
}
export default App;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Router의 종류 비교]]></title>
            <link>https://velog.io/@zsj_tapout/Router%EC%9D%98-%EC%A2%85%EB%A5%98-%EB%B9%84%EA%B5%90</link>
            <guid>https://velog.io/@zsj_tapout/Router%EC%9D%98-%EC%A2%85%EB%A5%98-%EB%B9%84%EA%B5%90</guid>
            <pubDate>Tue, 01 Oct 2024 07:25:00 GMT</pubDate>
            <description><![CDATA[<h1 id="0-react에서의-router-사용법">0. React에서의 Router 사용법</h1>
<h3 id="01-react-router-dom-라이브러리-설치">0.1. react-router-dom 라이브러리 설치.</h3>
<pre><code class="language-jsx">npm install react-router-dom</code></pre>
<h3 id="02-기초-사용법">0.2. 기초 사용법</h3>
<pre><code class="language-jsx">import { Routes, Route } from &#39;react-router-dom&#39;;

const router = () =&gt; {
    return (
        &lt;Routes&gt;
                &lt;Route    //r
                    path=&#39;/&#39;
                    element={&lt;rendering_Page/&gt;}
            /&gt;
        &lt;/Routes&gt;
    )
}
export default router;</code></pre>
<h1 id="1-browserrouter">1. BrowserRouter</h1>
<ul>
<li>페이지의 새로고침 없이도 주소의 변경이 가능하고, 현재 주소의 정보들을 props를 통해 활용이 가능하다.</li>
<li>pushState, replaceState, popstate event 등, HTML5의 History API를 사용하여 URL과 UI를 동기해줌</li>
<li>위 코드에서는 <Routes> 컴포넌트를 감싸게 된다.</li>
</ul>
<h2 id="11-browserrouter-props">1.1. BrowserRouter props</h2>
<ul>
<li>basename: 특정한 URL에서 실행되도록 만들어줌. string의 형태.</li>
<li>future: 무엇을 ‘future flag’로 선택할 지. 즉 어떤 새로운 기능이나 변경된 동작을 활성화하거나 비활성화할 지 선택함. FutureConfig 타입 객체.</li>
<li>window: 다른 window에서의 변경 사항을 추적할 지에 대한 여부.</li>
<li>props type 선언:</li>
</ul>
<pre><code class="language-jsx">declare function BrowserRouter(
  props: BrowserRouterProps
): React.ReactElement;

interface BrowserRouterProps {
  basename?: string;
  children?: React.ReactNode;
  future?: FutureConfig;
  window?: Window;
}</code></pre>
<h1 id="2-hashrouter">2. HashRouter</h1>
<ul>
<li><p>URL의 해쉬값(#)을 사용하여 주소를 나타냄</p>
</li>
<li><p>별도의 서버 설정 없이도 페이지 새로고침 시 오류가 발생하지 않음</p>
<p>  ⇒ HashRouter는 # 뒤의 값을 브라우저에서만 관리하고, 서버에는 기본 URL로 데이터를 요청하기 때문.</p>
</li>
<li><p>History API를 사용하지 않아 동적 페이지와의 호환이 어렵다.</p>
</li>
<li><p>*** 서버를 온전히 제어하기 어려운 “공유 호스팅 시나리오” 상에서 활용하기 좋다!!!</p>
</li>
</ul>
<h1 id="3-browserrouter-vs-hashrouter">3. BrowserRouter vs HashRouter</h1>
<table>
<thead>
<tr>
<th></th>
<th>BrowserRouter</th>
<th>HashRouter</th>
</tr>
</thead>
<tbody><tr>
<td>props</td>
<td>basename, children, window, future</td>
<td>basename, children, window, future</td>
</tr>
<tr>
<td>새로고침 시</td>
<td>경로를 찾지 못할 경우 404 에러 발생</td>
<td>별도 서버 설정 없이도 에러 발생 X</td>
</tr>
<tr>
<td>사용 상황</td>
<td>동적 페이지 및 대부분의 페이지에서 사용</td>
<td>정적 페이지 및 공유 호스팅 서버에서 사용</td>
</tr>
<tr>
<td>사용법</td>
<td>HTML5의 History API를 활용한 UI 업데이트</td>
<td>URL Hash(#)를 활용한 업데이트</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android] INSTALL_FAILED_BAD_PERMISSION_GROUP 해결하기]]></title>
            <link>https://velog.io/@zsj_tapout/Android-INSTALLFAILEDBADPERMISSIONGROUP-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@zsj_tapout/Android-INSTALLFAILEDBADPERMISSIONGROUP-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 22 May 2024 11:03:42 GMT</pubDate>
            <description><![CDATA[<p>팀플용 앱을 만들기 위해 열심히 코딩 연습을 하던 도중, 앱을 실행하려는데 갑자기 </p>
<p><img src="https://velog.velcdn.com/images/zsj_tapout/post/2bac4663-2157-40c4-aca4-f7c081d15a0c/image.png" alt=""></p>
<p>이런 에러가 발생하면서 앱이 실행되지를 않는다. 분명 어제까지만 해도 잘 실행됐는데..? 하면서 코드를 다시 한 번 천천히 뜯어봤다. 그런데 java 코드에서도 딱히 오류도 없는 듯 하고, <strong><em>PERMISSION_GROUP</em></strong> 이라는 단어에 갑자기 꽂혀서 &#39;혹시 github에서 pull받아올 때 생긴 오류는 아닐까?&#39; 하고 github의 base permission 설정까지 바꿔봤지만, 역시 이것도 아니었다.</p>
<p> 그렇게 늦은 새벽에 뜬금없이 안드로이드와의 싸움을 지속하던 앱린이... 그러다가 드디어! 찾아내고야 말았다...</p>
<p> <img src="https://velog.velcdn.com/images/zsj_tapout/post/1bfa9de5-d2e0-434c-bf88-ce6971bcbbd8/image.png" alt=""></p>
<p>build.gradle.kts(:app) 부분의 의존성 설정을 살펴보던 중, 팀원의 코드를 pull받아올 때 추가된 <strong><em>implementation(&quot;yanzhenjie:permission:2.0.3&quot;)</em></strong> 부분이 눈에 띄었다. 이 기능이 무엇인가 하고 인터넷을 찾아보니, &#39;위험 권한 설정&#39;을 위한 permission이었다. 그런데 이미 나에게는 <strong>Tedpermission</strong>이라는 동일한 기능을 가진 라이브러리가 존재하고 있다. 혹시 중복되는 기능의 존재로 인해 해당 오류가 발생하는 건 아닐까? 하고 yanzhenjie:permission:2.0.3을 주석 처리한 뒤 다시 실행했더니..!</p>
<p><img src="https://velog.velcdn.com/images/zsj_tapout/post/2f71a810-dc13-4324-9087-7b5168c5ed70/image.png" alt="">
(많이 구린 인터페이스..)</p>
<p>정상적으로 작동한다!</p>
<h2 id="결론"><strong>결론)</strong></h2>
<p>&#39;INSTALL_FAILED_BAD_PERMISSION_GROUP&#39; 의 해결책 = 중복되는 permission을 찾아 단일화하자!</p>
]]></description>
        </item>
    </channel>
</rss>