<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>y_jem.log</title>
        <link>https://velog.io/</link>
        <description>프론트엔드 개발자</description>
        <lastBuildDate>Sun, 12 Mar 2023 14:50:19 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>y_jem.log</title>
            <url>https://images.velog.io/images/y_jem/profile/649fa556-bcd8-4405-8a1d-6c501c7b54d4/KakaoTalk_Photo_2021-10-13-18-03-46.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. y_jem.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/y_jem" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[study] js 복습 1]]></title>
            <link>https://velog.io/@y_jem/study-js-%EB%B3%B5%EC%8A%B5-1</link>
            <guid>https://velog.io/@y_jem/study-js-%EB%B3%B5%EC%8A%B5-1</guid>
            <pubDate>Sun, 12 Mar 2023 14:50:19 GMT</pubDate>
            <description><![CDATA[<h2 id="-js-복습-1"># js 복습 1</h2>
<h4 id="-어셈블리어"># 어셈블리어</h4>
<p>기계어와 일대일 대응이 되는 컴퓨터 프로그래밍의 저급 언어이다. 기계어는 사람이 이해하기 어렵기 때문에 기계어를 사람이 이해할 수 있게 하기 위해 어셈블리어가 등장했다. 하지만 컴퓨터 구조에 따라 사용하는 기계어가 다르고 그로 인해 기계어에 대응되어 만들어지는 어셈블리어도 각각 다르다는 문제가 발생했다. 이 때문에 통일된 언어체계로 작성한 코드의 필요성이 대두되고, 여기서 고급 프로그래밍 언어가 나오게 된다. 고급 프로그래밍 언어를 OS가 인식하는 기계어로 번역해주는 방식 중 2가지가 인터프리터 방식과 컴파일러 방식이다.</p>
<h4 id="-컴파일러-인터프리터-차이"># 컴파일러 인터프리터 차이</h4>
<table>
<thead>
<tr>
<th>특징</th>
<th>컴파일러</th>
<th>인터프리터</th>
</tr>
</thead>
<tbody><tr>
<td>번역 단위</td>
<td>전체</td>
<td>문장</td>
</tr>
<tr>
<td>실행 속도</td>
<td>빠름</td>
<td>느림</td>
</tr>
<tr>
<td>실행 파일</td>
<td>생성</td>
<td>미생성, 한줄 씩 해석 후 바로 실행</td>
</tr>
<tr>
<td>메모리 할당</td>
<td>할당</td>
<td>미할당</td>
</tr>
<tr>
<td>에러 발생</td>
<td>전체 코드 변환 후 에러 보고</td>
<td>에러가 나면 그 이후 코드 실행 안함</td>
</tr>
<tr>
<td>대표 언어</td>
<td>C, C++, Java 등</td>
<td>Python, Ruby, Javascript 등</td>
</tr>
</tbody></table>
<h4 id="-자바스크립트는-컴파일러일까-인터프리터일까"># 자바스크립트는 컴파일러일까 인터프리터일까?</h4>
<p>결론은 인터프리터 언어이다. 개발자 도구에서 콘솔을 열어 스크립트를 작성해보면 컴파일이 필요없이 실행이 된다. 자바스크립트의 목적은 웹 문서 구조를 동적으로 나타내기 위함이기 때문에 목적에 맞게 빌드 과정이 필요없이 바로 실행되고, 수정 및 디버깅이 더 편한 인터프리터 언어로 만들어졌다고 한다. 하지만 유저 인터렉션이 점점 늘어나고 애플리케이션의 구조가 커지게 되며 실행 속도를 빠르게 개선하기 위해 V8엔진이 나오게 되었고 최근 V8엔진은 AJITC(Adaptive Just In Time Compiler)로 구동되어 반복 수행 정도에 따라 유동적으로(adaptive) 서로 다른 최적화 수준을 적용하는 방식을 사용한다. AJITC의 동작 방식은 V8엔진이 JS 소스코드를 받아 파싱 후 추상 구문 트리(abstract syntax tree)를 생성한다. 그 후 기본적으로 인터프리터로 동작을 하며 코드를 해석(중간 번역)하고 실행하게 된다. 프로파일러가 실행하는 과정을 지켜보며 반복되어 사용되는 코드 등 과열 지점을 찾은 후 과열 지점을 찾게 되면 해당 코드를 컴파일러에게 전달해주어 컴파일러가 최적화를 진행하게 된다. 그리고 기존에 있던 코드와 최적화된 코드를 바꿔준다. 즉 결론은 인터프리터는 중복되는 코드도 동일하게 컴파일 과정을 거쳐 실행 속도를 늦춘다는 단점이 있었는데 이를 해결하기 위해 유동적으로 최적화 방식을 변경하는 AJITC를 사용하여 인터프리터 + 컴파일러 방식으로 동작해 실행 속도를 높여준 것이다.</p>
<h4 id="-v8엔진의-ajitcadaptive-just-in-time-compiler-동작-원리"># V8엔진의 AJITC(Adaptive Just In Time Compiler) 동작 원리</h4>
<p>  (1) 자바스크립트 엔진에 자바스크립트 파일을 넘긴다.
  (2) 파서가 소스 코드 분석 후 추상 구문 트리(AST) 생성
  (3) 이그니션 인터프리터(Ignition)가 추상 구문 트리(AST)를 컴파일하여 바이트 코드(중간 코드)로 변환 후 실행한다.
  (4) 실행 과정 중 프로파일러가 프로파일링을 통해 반복되어 사용되는 코드와 같이 과열 지점을 찾는다.
  (5) 과열 지점을 찾게 되면 해당 코드를 터보팬 컴파일러(TurboFan)를 통해 최적화하여 최적화 된 기계어(Optimized Machine Code)로 다시 컴파일 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[study] react 복습 1]]></title>
            <link>https://velog.io/@y_jem/study-react-%EB%B3%B5%EC%8A%B5-1</link>
            <guid>https://velog.io/@y_jem/study-react-%EB%B3%B5%EC%8A%B5-1</guid>
            <pubDate>Sun, 26 Feb 2023 19:57:37 GMT</pubDate>
            <description><![CDATA[<h2 id="-react-복습-1"># react 복습 1</h2>
<h3 id="-state"># State</h3>
<p>컴포넌트 내부에서 가지고 있는 컴포넌트의 상태값, 유동적인 데이터를 다루기 위한 객체</p>
<h4 id="-let으로-변수-선언해서-관리하면-될껄-왜-state로-관리할까"># let으로 변수 선언해서 관리하면 될껄 왜 state로 관리할까?</h4>
<p>state는 일반 변수와 다르게 값이 변하게 되면 리렌더링이 일어난다. 값이 변함에 따라 관련 컴포넌트들이 업데이트되어야 하기 때문에 리렌더링이 발생하는 state를 사용하는 것이다.</p>
<h4 id="-직접-값을-조작하지-않고-setstate를-사용하는-이유는"># 직접 값을 조작하지 않고 setState를 사용하는 이유는?</h4>
<p>만약 컴포넌트의 state를 직접 변경하면 react는 컴포넌트를 다시 렌더링해야 하는지 알 수 없다. setState 메소드를 사용하면 react는 state의 변화를 감지해 리렌더링을 발생시켜 컴포넌트를 업데이트 시킬 수 있다.</p>
<h4 id="-setstate는-비동기-동기"># setState는 비동기? 동기?</h4>
<p>setState는 비동기로 동작한다. 비동기로 동작하는 이유는 일정 시간동안 변화하는 상태를 모아 한번에 렌더링하기 위해서이다. 리액트의 배치 업데이트(batch update)는 16ms(밀리세컨드)당 한번만 일어난다. 그러므로 상태 업데이트 후 바로 상태 값을 참조하여 다른 작업을 할 때 문제가 발생할 수 있다. 이런 경우 이전 상태를 바로 참조할 수 있는 prevState를 활용하여 상태를 업데이트 한 후 사용하는 방식으로 해결 할 수 있다. prevState는 setState안에 화살표 함수를 통해 파라미터로 이 전 state를 전달하는 방식으로 사용할 수 있다. (ex : <code>setState((prev) =&gt; prev + 1</code>)</p>
<h4 id="-setstate가-비동기-동작을-취했을-때-얻을-수-있는-이점"># setState가 비동기 동작을 취했을 때 얻을 수 있는 이점</h4>
<p>setState가 여러 번 호출 될 경우 호출 될 때마다 리렌더링이 발생하면 성능 저하가 발생할 수 있다. 그러므로 react는 batch update를 16ms마다 진행하여 만약 16ms 안에 100개의 state 변화가 일어난다면 변화 된 상태 값을 취합하여 한번에 업데이트 시키는 것이다. 결론은 setState의 비동기 동작으로 인해 성능 저하를 막을 수 있다는 것이다.</p>
<h4 id="-react의-불변성-setstate의-얕은-비교"># react의 불변성 (setState의 얕은 비교)</h4>
<p>불변성은 변하지 않는 상태나 값을 말한다. 리액트에서의 불변성은 값을 직접적으로 변경을 하지 않고, 기존의 값을 수정하지 않으면서 새로운 값을 만들어내는 것을 의미한다. 리액트는 얕은 비교를 통해 state의 업데이트를 감지한다. 얕은 비교는 실제 내부 값까지 비교하는 것이 아닌 참조하는 값에 메모리 주소를 비교하는 것이다. 기존 값과 최신 값에 얕은 비교를 수행하여 최신 값이 새로운 메모리 주소를 참조할 때 업데이트가 발생하게 되는 것이다. 이 때 배열이나 객체의 상태를 업데이트 하는 경우 리액트에서는 새로운 메모리 주소를 생성하기 위해 setState에 배열이나 객체를 할당하고 배열이나 객체 내부에 스프레드 연산자를 활용한 기존 값의 복사본과 최신 값을 할당해준다. (<code>setState([...state, newState]), setState({...state, [key]: value})</code>) 이렇게 복사본을 활용한 방식과 새로운 메모리 주소를 생성하는 방식을 통해 불변성을 지킬 수 있고 불변성을 지켜줌으로서 예상치 못한 사이드 이펙트를 방지할 수 있고 얕은 비교를 통해 효과적인 상태 업데이트를 할 수 있다. 만약 불변성을 지키지 않고 기존 값을 직접 변경할 경우 참조하는 값에 메모리 주소가 동일하기 때문에 리액트는 상태 변화를 감지할 수 없어 컴포넌트의 최신화를 유지할 수 없게 된다.</p>
<h3 id="-react-렌더링-성능-향상-방법"># react 렌더링 성능 향상 방법</h3>
<p>(1) 렌더링 성능 측정 : react developer 익스텐션이나 lighthouse 를 활용하여 렌더링 성능을 측정하고 perpomance API나 console.time 메소드를 활용하여 함수 성능을 측정하고 개선한다.</p>
<p>(2) useMemo, useCallback : useMemo, useCallback 등을 사용하여 값이나 함수를 메모이제이션 해 사용한다.</p>
<p>(3) 코드 스플리팅 : Lazy와 Suspense를 사용하여 코드 스플리팅하고 번들의 크기를 줄여 당장 필요한 컴포넌트만 우선적으로 렌더링하도록 한다.</p>
<p>(4) 이미지 사이즈 최적화 : 이미지를 압축율이 좋은 이미지 포맷으로 변경하거나 사용자에게 보여지는 크기를 고려하여 사이즈를 줄인다. 또한 고정 사이즈를 사용하여 리플로우를 최소화할 수 있다.</p>
<p>(5) 이미지 레이지 로딩 : IntersectionObserver API와 같이 뷰포트를 감지하는 API를 활용하여 해당 이미지 보여져야 할 때 서버에 요청을 보내 이미지를 가져온다.</p>
<p>(6) 애니메이션 최적화 : 애니메이션은 리플로우와 리페인트를 발생시키는 원인이다. witdh와 같은 크기를 직접적으로 변경하는 애니메이션의 경우 cpu에서 처리하게 되는데 transform의 scale을 활용하여 크기를 변경시키는 경우 gpu에서 처리하게 되므로 성능 향상에 도움이 된다.</p>
<p>(7) 웹폰트 경량화 : 사용할 글자만 남겨놓은 서브셋 파일을 활용하여 웹폰트 파일의 크기를 줄인다.</p>
<p>(8) state와 props변경 최소화 : state와 props의 변경을 최소화하여 리렌더링을 최소화한다.</p>
<p>(9) 프롭시 드릴링(props drilling) 피하기 : 부모 컴포넌트에서 자식 컴포넌트로 전달하는 프롭스의 깊이가 깊어질수록 성능에 좋지 않다. 부모의 state가 변경되면 자식 컴포넌트 모두 리렌더링되기 때문이다. 개인적으로 2번 이상 넘겨주지 않도록 기준을 정해서 작업하고 있다. 넘어가는 경우 전역 상태 관리 라이브러리를 사용하여 해결할 수 있다.</p>
<p>(10) state의 분할 : 한 컴포넌트에 state를 몰아서 사용하는 경우 state 변화가 빈번하게 발생할 수 있기 때문에 state를 사용하는 컴포넌트 내부에 작성할 수 있도록 한다. 또한 객체 형태의 state의 경우 객체 프로퍼티 중 하나만 사용하는 컴포넌트가 있거나 하는 경우 분리해서 사용하는 것이 좋다.</p>
<blockquote>
<p>react의 리렌더링 조건
(1) 부모 컴포넌트가 렌더링 될 때
(2) 자신의 state가 변경 될 때, 단 setState를 사용하여 변경해야한다. state를 직접 변경할 경우 state의 변경을 감지하지 못하기때문에 render 함수가 호출 되지 않는다.
(3) 자신이 전달받은 props가 변경될 때</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[study] JWT 토큰]]></title>
            <link>https://velog.io/@y_jem/JWT-%ED%86%A0%ED%81%B0</link>
            <guid>https://velog.io/@y_jem/JWT-%ED%86%A0%ED%81%B0</guid>
            <pubDate>Sun, 12 Feb 2023 15:06:48 GMT</pubDate>
            <description><![CDATA[<h2 id="-jwt-토큰json-web-token"># JWT 토큰(JSON Web Token)</h2>
<h3 id="-jwt-토큰이란"># JWT 토큰이란?</h3>
<p>JSON Web Token으로 로그인과 같은 인증(유저 비밀번호 확인)/인가(로그인 유저가 요청하는 request를 처리할 수 있는 지 권한 확인)에서 사용한다. 로그인을 처리하는 방식으로 세션 기반 인증과 토큰 기반 인증이 있는데 토큰 기반 인증에서 사용한다. 세션 기반 인증은 서버의 메모리, 데이터베이스와 같은 서버의 자원들을 사용해서 사용자의 정보를 유지시키는 방식(서버에 저장)이다. 토큰 기반 인증은 사용자가 로그인을 하면 서버에서 발행해주는 토큰을 가지고 브라우저의 저장소에 토큰을 유지시키는 방식(클라이언트에 저장)이다.</p>
<h3 id="-토큰-기반-인증을-사용하는-이유"># 토큰 기반 인증을 사용하는 이유</h3>
<p>세션 기반 인증은 서버의 확장성이 떨어지고, 세션을 저장 및 유지하기 위한 서버의 자원이 필요하다. 또한 세션이 서버에 저장이 되고, 트래픽 분산을 위해서 여러 대의 서버를 사용할 때 만약 사용자가 초기 로그인할 경우 서버에서 세션을 생성하게 되는데 다음 로그인 시에도 초기 로그인 시 만들어진 세션을 참조해야 하기 때문에 처음 로그인한 서버에 요청을 보내야 한다는 단점이 있다. 반면 토큰 기반 인증은 서버에 저장하지 않아서 서버에 확장성이 상대적으로 높고 로그인을 했을 때 해당 서버에만 요청을 보내는 것이 아닌 요청이 들어왔을 때 해당 토큰이 유효한지만 체크하면 되기 때문에 어떤 서버로 요청을 보내도 상관이 없다.</p>
<h3 id="-jwt-토큰을-사용하는-이유는"># JWT 토큰을 사용하는 이유는?</h3>
<p>(1) 데이터 크기 : JWT는 XML 기반의 SAML(Security Assertion Markup Language) 방식보다 크기가 작다.</p>
<p>(2) 보안성 : SMT(Simple Web Token) 방식은 대칭키 방식으로 해싱하지만 JWT와 SAML 토큰은 공개키/개인키 방식을 사용한다. 인증 과정에서 대칭키 방식은 인증 확인자가 같은 키로 데이터를 만들어 다른 인증 확인자에게 잘못 사용될 수 있다는 문제가 있다. 또한 인증 과정은 인증 확인자가 데이터를 생성할 필요 없이 확인만 하면 되기 때문에 공개키/개인키 방식이 더 적합하다.</p>
<p>(3) 호환성 : JSON은 대부분 언어에서 객체로 바로 변환될 수 있기 때문에 대부분의 언어에서 지원하고 있다.</p>
<h3 id="-jwt-토큰-구조"># JWT 토큰 구조</h3>
<p>(1) Header (헤더) : Token의 기본요소, 헤더에는 일반적으로 토큰의 타입과 해싱 알고리즘 명시</p>
<p>(2) Payload (페이로드) : 전달하려는 데이터, key-value 페어로 클레임 정보를 포함한다. 클레임의 종류로는 등록된 클레임, 공개 클레임, 비공개 클레임이 있다.</p>
<p>(3) Signature (시그니쳐) : 서명된 값, header와 payload를 해싱 알고리즘에 의해 계산한 결과를 포함</p>
<h3 id="-jwt-동작-방식"># JWT 동작 방식</h3>
<p>(1) 클라이언트에서 id, pw 정보를 서버로 보냄</p>
<p>(2) 서버에서 id, pw 정보를 이용하여 JWT token 을 생성함</p>
<p>(3) 서버에서 클라이언트로 JWT token 을 보냄</p>
<p>(4) 클라이언트에서 서버로 서비스 요청시, JWT token 을 같이 보냄.</p>
<p>(5) 서버에서 서비스 처리시, JWT token 을 검증, 일치하면 서비스를 동작시켜 클라이언트로 응답</p>
<h3 id="-json-사용-이유"># JSON 사용 이유</h3>
<p>서버와 클라이언트 또는 애플리케이션 처리할 데이터를 주고받을 때 자료 형식 중 대표적인 것이 XML과 JSON이 있다. 이 중 XML은 데이터 포맷 중 하나로 HTML과 유사한 마크업 언어이다. 데이터를 저장하고, 전달할 목적으로 고안되었다. 불필요한 태그들이 들어가 파일의 사이즈가 커질 뿐만 아니라 가독성도 좋지 않아 XML대신 JSON이 사용된다. JSON은 데이터 포맷 중 하나로 key와 value가 한 쌍을 이루는 구조의 객체로 구성되어 있으며 XML의 대안으로서 고안되었고 XML 대비 더 직관적이며, 작성하기 편리하다는 특징이 있다. 또 배열을 파싱할 수 없는 XML과 달리 JSON은 배열을 사용할 수 있다. 또한 프로그래밍 언어나 플랫폼에 상관없이 사용할 수 있다.</p>
<h3 id="-jwt-안전하게-사용하는-방법"># JWT 안전하게 사용하는 방법</h3>
<p>(1) 사용자 로그인 시 서버에서 사용자 확인 후 액세스 토큰, 리프레쉬 토큰 발급, 이 때 실제 리프레쉬 토큰 값은 DB에 저장</p>
<p>토큰 유효 시간 설정 : 이 때 액세스 토큰의 시간 유효기간은 짧게, 리프레쉬 토큰의 시간 유효기간은 길게 설정</p>
<p>(2) 서버에서 리프레쉬 토큰의 실제 값이 아닌 index값이나 해쉬 값을 액세스 토큰과 함께 클라이언트에 전달</p>
<p>(3) 클라이언트에서 리프레쉬 토큰 index 혹은 해쉬 값은 쿠키로 관리, 왜냐하면 가장 필수로 막아야하는 XSS 보안에 유리, 서버에서 httpOnly쿠키로 설정해서 XSS 막기, 또 추가로 secure / SameSite(Strict or Lax 모드) 옵션을 지정</p>
<blockquote>
<ul>
<li>XSS : 클라이언트 단에서 실행되는 악의적 스크립트, 가장 필수적으로 막아야하는 공격</li>
</ul>
</blockquote>
<ul>
<li>httpOnly : document.cookie와 같은 자바스크립트로 쿠키를 조회하는 것을 막는 옵션)</li>
</ul>
<p>(4) 클라이언트에서 액세스 토큰은 비공개 변수로 관리, 권한 필요 시 Authorization 헤더에 access token을 담아 요청</p>
<p>(5) 매 요청 시마다 액세스 토큰 + 리프레쉬 토큰 index 혹은 해쉬 값을 같이 담아서 보내어 매번 액세스 토큰을 새로 발급받는 방식을 사용</p>
<p>(5-1) 요청 시 항상 리프레쉬 해쉬 값 + 액세스 토큰 보냄</p>
<p>(5-2-1) 클라이언트 요청 시 액세스 토큰 만료면 서버 렌더링 과정 혹은 API 통신을 통해 재발급을 요청, 서버에서 리프레쉬 해쉬 값 검증 후 액세스 토큰 새로 발급해서 응답, 이 때 요청 시 쿠키에는 자바스크립트에서 접근이 불가능한(httpOnly 옵션) refresh token이 이미 담겨진 상태로 서버와 통신하게 됨</p>
<p>(5-2-2) 리프레쉬 토큰 만료면 DB와 다시 한번 통신하여 로그인 만료 페이지 혹은 로그아웃 상태로 렌더링을 하여준다. (ex 로그인이 만료되었습니다.)</p>
<p>(6) refresh token이 저장된 쿠키는 외부 경로와 자바스크립트 상에서의 접근이 불가능하여 CSRF, XSS 공격에서 모두 안전성이 확보된다. access token이 저장된 비공개 변수는 XSS, CSRF 공격을 시도할 방법이 사라지며, 토큰이 휘발되어 사라졌던 UX 문제도 해결된다.</p>
<h3 id="-토큰-만료-시-처리"># 토큰 만료 시 처리</h3>
<p>액세스 토큰과 리프레쉬 토큰을 함께 사용하는 방식으로 처리할 수 있다. 액세스 토큰은 그 자체로 인증 정보를 모두 가지고 있어서 탈취되면 매우 위험한 상황이 발생할 수 있다. 그러므로 만료 기간을 지정해주어야 하는데 만료 기간이 다 했을 경우에는 액세스 토큰을 재발급 할 수 있는 리프레쉬 토큰을 사용해야한다. 리프레쉬 토큰은 새로운 액세스 토큰을 생성하는 용도로만 사용된다. 굳이 별도의 리프레쉬 토큰을 두고 새로운 액세스 토큰을 발급받도록 한 이유는 보안 때문이다. 액세스 토큰의 유효기간을 짧게 설정하고 리프레쉬 토큰의 유효기간을 길게 설정한 뒤 둘 다 서버에 전송하여 액세스 토큰으로 인증하고 만료 시 리프레쉬 토큰으로 액세스 토큰을 새로 발급받는다. 만약 공격자에 의해 액세스 토큰을 탈취 당하더라도 유효 기간이 짧기 때문에 유효 기간이 지나면 사용할 수 없고 정상적인 클라이언트는 리프레쉬 토큰으로 액세스 토큰을 재발급 받은 뒤 사용할 수 있다. 단 리프레쉬 토큰은 서버에 저장해두어야 한다고 한다. 서버에 실제 리프레쉬 토큰 값을 저장하고 index값을 쿠키나 로컬스토리지에 저장하는 방식으로 유효기간이 긴 리프레쉬 토큰이 탈취당하는 것을 방지할 수 있다고 한다. 또 index값 또한 단순 index값이 아닌 hash값을 생성해 사용하면 보안에 더욱 유리하다고 한다.</p>
<br>

<h2 id="-sop정책과-cors"># SOP정책과 CORS</h2>
<h3 id="-sopsame-origin정책이란"># SOP(same-origin)정책이란?</h3>
<p>same-origin policys는 동일 출처 정책인데 동일 출처 정책은 출처(origin)에서 로드된 문서나 스크립트가 다른 출처에 자원과 상호작용하지 못하도록 제약하는 정책이다. 통신을 주고 받는 두 URL의 프로토콜, 호스트, 포트번호 모두 동일한 경우만 동일 출처가 된다. 만약 다른 출처에서 리소스를 불러오려면 그 출처에서 올바른 CORS 헤더를 포함한 응답을 반환해야 한다.</p>
<h3 id="-cors란"># CORS란?</h3>
<p>CORS는 교차 출처 리소스 공유(Cross-Origin Resource Sharing)라고 하며 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 정책이다. 기본적인 동작 과정은 HTTP 프로토콜을 사용하여 요청을 보낼 때 브라우저는 요청 헤더에 Origin이라는 필드에 요청을 보내는 출처를 함께 담아보낸다. 이후 서버가 이 요청에 대한 응답을 할 때 응답 헤더의 Access-Control-Allow-Origin에 허용된 출처를 담아 응답하고 이후 응답을 받은 브라우저는 자신이 보냈던 요청의 Origin과 서버가 보내준 응답의 Access-Control-Allow-Origin을 비교해본 후 이 응답이 유효한 응답인지 아닌지를 결정한다.</p>
<h3 id="-cors-에러-대처-방법"># CORS 에러 대처 방법</h3>
<p>(1) 서버에서 Access-Control-Allow-Origin 세팅 : 서버에서 모든 클라이언트에 요청에 대한 cross-origin HTTP 요청을 허가하는 Access-Control-Allow-Origin: * 헤더를 추가하면 된다. *을 사용하게 되면 보안에 취약할 수 있으므로 출처를 명시해줄 수 있다.</p>
<p>(2) proxy 설정 : proxy를 설정하여 요청 출처를 바꿀 수 있다.</p>
<br>

<h2 id="-xxs와-csrf"># XXS와 CSRF</h2>
<h3 id="-xsscross-site-scripting이란"># XSS(Cross Site Scripting)이란?</h3>
<p>XSS은 공격자가 의도하는 악의적인 js 코드를 피해자 웹 브라우저에서 실행시키는 것이다. 희생자 클라이언트 PC에서 실행되며 사용자의 정보를 탈취하는 것이다. XSS 공격을 막는 것은 웹 보안을 위한 최소한의 조치이다.</p>
<h3 id="-csrfcross-site-request-forgery이란"># CSRF(Cross site request forgery)이란?</h3>
<p>정상적인 request를 가로채 피해자인 척 하고 백엔드 서버에 변조된 request를 보내 악의적인 동작을 수행하는 공격을 의미한다. CSRF는 위조된 요청을 서버에 보내어 서버단에서 스크립트가 실행된다.</p>
<h3 id="-xss-대응방법"># XSS 대응방법</h3>
<p>(1) 입/출력값 검증 : 입출력 값에 대해 목적에 맞는지 다양한 검증</p>
<p>(2) 보안 라이브러리 사용 : 오픈소스 보안 라이브러리를 활용하여 XSS를 방지한다.</p>
<p>(3) HttpOnly 속성 사용 : 스크립트에서 쿠키에 접속하는 것을 방지하는 HttpOnly 옵션을 사용한다.</p>
<p>(4) CSP(Content Security Policy) : 사이트에서 직접 컨텐츠별로 정책을 정의하여 사이트에서 허용한 컨텐츠에만 접근하도록 하는 브라우저 표준 보안 정책을 적용한다.</p>
<p>(5) 올바른 Content-Type 사용 : 적절한 Content-Type을 지정하여 악성 스크립트가 실행되지 않도록 해준다.</p>
<h3 id="-csrf-대응방법"># CSRF 대응방법</h3>
<p>(1) Referrer 검증 : request의 header에 존재하는 referrer 속성을 확인하여 요청을 한 페이지의 정보를 검증하고 차단하는 방법이다.</p>
<p>(2) Security Token(CSRF Token) 검증 : 특정 조건(로그인 등)일 때 사용자의 세션에 임의의 난수 값을 저장하고, 사용자의 요청 마다 해당 난수를 포함시켜 전송한다. 그리고 요청이 들어올 때 마다 세션에 저장된 값과 요청으로 전송된 난수값이 일치하는지 서버에서 검증하는 방법이다.</p>
<p>(3) Double Sumbit Cookie 검증 : 세션을 사용 못하는 환경에서 사용하는 방법으로 웹브라우저의 Same Origin 정책으로 인해 자바스크립트에서 타 도메인의 쿠키 값을 확인/수정하지 못한다는 것을 이용한 방법이다. 스크립트 단에서 요청 시 난수 값을 생성하여 쿠키에 저장하고, 동일한 난수 값을 요청 파라미터로 서버에 전송한다. 서버에서는 쿠키의 토큰 값과 요청시 들어온 파라미터의 토근 값이 일치하는 지 검사하는 방법이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Safari 버전별 대응 타겟 설정]]></title>
            <link>https://velog.io/@y_jem/Safari-%EB%B2%84%EC%A0%84%EB%B3%84-%EB%8C%80%EC%9D%91-%ED%83%80%EA%B2%9F-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@y_jem/Safari-%EB%B2%84%EC%A0%84%EB%B3%84-%EB%8C%80%EC%9D%91-%ED%83%80%EA%B2%9F-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Mon, 05 Sep 2022 09:38:01 GMT</pubDate>
            <description><![CDATA[<h1 id="-safari-버전별-대응-타겟-설정"># Safari 버전별 대응 타겟 설정</h1>
<br>

<h2 id="-browser--mobile-os-점유율"># Browser / Mobile OS 점유율</h2>
<p>2022년 08월의 세계 브라우저 점유율과 영국 브라우저 점유율입니다.
두 차트 모두 Chrome과 Safari가 압도적인 점유율을 나타내고 있습니다.</p>
<table>
<thead>
<tr>
<th align="center">Global</th>
<th align="center">United Kingdom</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><img src="https://user-images.githubusercontent.com/85284246/188083581-709b6073-4c2b-44a0-af6a-18f0fbf5874d.png" alt="브라우저점유율_202208_세계"></td>
<td align="center"><img src="https://user-images.githubusercontent.com/85284246/188063510-404bd61f-bac8-4872-835b-c17285b6c567.png" alt="브라우저점유율_202208"></td>
</tr>
</tbody></table>
<br>

<p>또한 Mobile OS도 마찬가지로 예상한 바와 같이 Android와 iOS가 압도적인 점유율을 나타내고 있습니다.</p>
<table>
<thead>
<tr>
<th align="center">Global</th>
<th align="center">United Kingdom</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><img src="https://user-images.githubusercontent.com/85284246/188084739-c563469b-3dac-4b0f-93e5-03c54e9875b8.png" alt="모바일OS점유율_글로벌"></td>
<td align="center"><img src="https://user-images.githubusercontent.com/85284246/188084745-768ac07a-196a-4657-bfce-b926ec8e1070.png" alt="모바일OS점유율_영국"></td>
</tr>
</tbody></table>
<p><br><br></p>
<h2 id="-issue--safari-버전이-다른-경우-cross-browser-compatibility-issue-발생"># Issue : Safari 버전이 다른 경우 Cross-browser compatibility issue 발생</h2>
<p>Safari QA 작업 진행 중 아래 이미지와 같이 동일한 Safari 브라우저임에도 불구하고 Safari 버전에 따라 스타일이 다르게 적용되는 문제점이 발생하였습니다.
좌측 이미지는 safari 특정 버전에서 스타일 관련 이슈이며 우측은 정상적으로 스타일이 적용 된 모습입니다.</p>
<p><img src="https://user-images.githubusercontent.com/85284246/188085494-47fbaaf6-1239-498b-a552-a77ce6a804d3.png" alt="safari크로스브라우징이슈"></p>
<p>Apple 공식 홈페이지에서 확인해보면 Safari의 버전은 MacOS / iOS 버전을 따라가게 됩니다.
즉 사용자의 OS 버전에 따라 Safari 버전은 달라지게 되고 이에 따라 크로스 브라우징 이슈가 발생할 수 있다는 것을 알 수 있습니다.</p>
<p><img src="https://user-images.githubusercontent.com/85284246/188065769-e4d24cc8-c09f-45f4-a6b1-1a248675658a.png" alt="애플공홈Safari업데이트방법"></p>
<p>아래와 같은 환경에서 크로스 브라우징 이슈 테스트가 진행되었습니다.
아래 표를 보면 알 수 있듯이 OS 버전에 따라 이슈가 발생하지 않거나, 일부 발생하거나, 또 여러 부분에 발생하기도 하였습니다.</p>
<table>
<thead>
<tr>
<th>Testing Version</th>
<th>Jay</th>
<th>Kylie (1st test)</th>
<th>Kylie (2st test)</th>
</tr>
</thead>
<tbody><tr>
<td>MacOS</td>
<td>Big Sur 11.6.6 (issue X, Only MAT-675)</td>
<td>Big sur 11.6.8 (issue O, All)</td>
<td>Monterey (issue O, MAT-675, 682, 687, 688)</td>
</tr>
<tr>
<td>iOS</td>
<td>15.6.1 (issue X, Only MAT-675)</td>
<td>15.6.1 (issue O, All)</td>
<td>15.6.1 (issue O, MAT-675, 682, 687, 688)</td>
</tr>
</tbody></table>
<p><br><br></p>
<h2 id="-macos--ios-출시-날짜-및-점유율"># MacOS / iOS 출시 날짜 및 점유율</h2>
<p>크로스 브라우징 타깃을 선정하기 위해 현재 날짜를 기준으로 글로벌 / 영국의 MacOS, iOS 출시 날짜 및 점유율을 조사한 결과입니다.
두 OS 모두 가장 최근에 릴리즈 된 버전에 점유율이 가장 높은 것으로 나타났습니다.</p>
<ul>
<li><p>MacOS 출시 날짜</p>
<table>
<thead>
<tr>
<th align="center">Global</th>
<th align="center">United Kingdom</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><img src="https://user-images.githubusercontent.com/85284246/188086649-876497fa-8f33-473b-af03-3a03db6182b9.png" alt="MacOS점유율_202208_국가(카탈리나에빅서몬테레이포함)"></td>
<td align="center"><img src="https://user-images.githubusercontent.com/85284246/188086666-08196d87-890d-4e98-a84b-3cb4b585b2fe.png" alt="MacOS점유율_202208_영국(카탈리나에빅서몬테레이포함)"></td>
</tr>
</tbody></table>
<table>
<thead>
<tr>
<th>MacOS</th>
<th>Catalina</th>
<th>BigSur</th>
<th>Monterey</th>
<th>High Sierra</th>
<th>Mojave</th>
</tr>
</thead>
<tbody><tr>
<td>Release</td>
<td>2019.10.07</td>
<td>2020.11.12</td>
<td>2021.10.25</td>
<td>2017.09.26</td>
<td>2018.09.24</td>
</tr>
<tr>
<td>Share</td>
<td>85.11%</td>
<td>85.11%</td>
<td>85.11%</td>
<td>5.34%</td>
<td>3.06%</td>
</tr>
</tbody></table>
</li>
</ul>
<br>

<ul>
<li><p>iOS 출시 날짜 : 22/08 기준 상위 3개 버전 합산 점유율 80.76%</p>
<table>
<thead>
<tr>
<th align="center">Global</th>
<th align="center">United Kingdom</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><img src="https://user-images.githubusercontent.com/85284246/188087012-66e25a11-aa60-4eed-aa0d-a86f4f5ea0ed.png" alt="IOS점유율_202208_국가"></td>
<td align="center"><img src="https://user-images.githubusercontent.com/85284246/188087017-ae08a93b-20fc-45b3-bb07-138a929c02e8.png" alt="IOS점유율_202208_영국"></td>
</tr>
</tbody></table>
<table>
<thead>
<tr>
<th>iOS</th>
<th>15.6</th>
<th>15.5</th>
<th>12.5</th>
<th>15.4</th>
<th>14.8</th>
</tr>
</thead>
<tbody><tr>
<td>Release</td>
<td>2022.05.31</td>
<td>2022.05.16</td>
<td>2020.12.15</td>
<td>2022.03.14</td>
<td>2021.09.14</td>
</tr>
<tr>
<td>Share</td>
<td>56.36%</td>
<td>20.3%</td>
<td>4.1%</td>
<td>3.16%</td>
<td>2.95%</td>
</tr>
</tbody></table>
</li>
</ul>
<p><br><br></p>
<h2 id="-크로스-브라우징-타깃-선정-사례"># 크로스 브라우징 타깃 선정 사례</h2>
<ul>
<li><p>라인(LINE) : <a href="https://engineering.linecorp.com/ko/blog/the-baseline-for-web-development-in-2022">https://engineering.linecorp.com/ko/blog/the-baseline-for-web-development-in-2022</a></p>
<ol>
<li><p>브라우저의 세계 시장 점유율과 타깃 국가의 시장 점유율을 파악합니다.</p>
<table>
<thead>
<tr>
<th align="center">세계 시장 점유율</th>
<th align="center">타깃 국가 시장 점유율</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><img src="https://user-images.githubusercontent.com/85284246/188078979-4bfd9f0e-39e8-4b51-a390-f3bf8d08f61e.svg" alt="Line_브라우저와OS의시장점유율"></td>
<td align="center"><img src="https://user-images.githubusercontent.com/85284246/188080840-daa24c11-b569-4eda-a876-338303fcdaa2.svg" alt="Line_Japanese-Browser-Share-Bar-1"></td>
</tr>
</tbody></table>
</li>
</ol>
<br>

<ol start="2">
<li><p>Mobile OS의 세계 시장 점유율과 타깃 국가의 시장 점유율을 파악합니다.</p>
<table>
<thead>
<tr>
<th align="center">세계 시장 점유율</th>
<th align="center">타깃 국가 시장 점유율</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><img src="https://user-images.githubusercontent.com/85284246/188079324-43c282f6-1770-496f-bb65-d31063cb1be0.svg" alt="Line_Global-Mobile-OS-Share-1"></td>
<td align="center"><img src="https://user-images.githubusercontent.com/85284246/188081245-60993f83-d14a-4626-a28f-b51a6f4bb726.svg" alt="Line_Japanese-Mobile-OS-Share-1"></td>
</tr>
</tbody></table>
</li>
</ol>
<br>

<ol start="3">
<li><p>Safari와 iOS와의 긴밀한 관계를 고려하여 iOS 버전 릴리즈에 따른 따른 시장 점유율을 파악합니다.</p>
<p><img src="https://user-images.githubusercontent.com/85284246/188078999-f169e0ca-9d15-4282-8139-ddc8ce12b803.png" alt="Line_iOS-Share-1"></p>
<p>2019년 1분기부터 3분기까지 iOS 12의 점유율은 점차 증가했지만, 3분기에 iPhone 11과 iOS 13이 출시되면서 점유율이 급감했습니다. 그 다음 해에 iOS 12의 점유율은 20% 아래로 떨어졌습니다. 2020년 3분기에 iPhone 12와 iOS 14가 출시되자마자 iOS 12의 점유율은 급격히 하락해 2021년 2분기에는 미미한 수준이 되었습니다. iOS 12의 이와 같은 점유율 패턴은 iOS 13과 14에서도 관찰할 수 있었습니다. 결론적으로, 앞으로 iOS 시장 점유율의 90% 이상은 지난 2년간 출시된 주요 버전으로 구성될 것이라고 안심하고 가정할 수 있습니다. 즉, <strong>최근 2년 이내에 출시된 Safari 버전만 지원하면 되는 것</strong>입니다.</p>
</li>
</ol>
</li>
</ul>
<br>

<ul>
<li><p>야놀자(yanolja) : <a href="https://www.theteams.kr/teams/528/post/64942">https://www.theteams.kr/teams/528/post/64942</a></p>
<p>GA를 통해 실시간 유입 사용자의 OS 버전을 확인하여 크로스 브라우징 타깃을 선정합니다. 야놀자의 경우 특이점은 라인과는 다르게 1%의 낮은 점유율의 버전도 대응하는 것을 알 수 있었습니다.</p>
<p><img src="https://user-images.githubusercontent.com/85284246/188088136-030032b8-3ca8-47bb-8ccf-66e0830eb51a.png" alt="야놀자_타깃선정"></p>
</li>
</ul>
<p><br><br></p>
<h2 id="-결론"># 결론</h2>
<ul>
<li><p>저는 타깃 설정의 방법을 두 가지 방법으로 나누어보았습니다.</p>
<p>첫번째는 <strong>영국 전체의 OS 점유율에 따른 대응 버전을 설정</strong>하는 방법과 두번째는 <strong>사용자 접속 브라우저 버전 정보를 수집하여 그에 따른 대응 버전을 설정하는 방법</strong>으로 나누었습니다.</p>
<p>이 때 각 방법들은 단점이 존재하는데 첫번째 영국 전체의 OS 점유율에 따른 대응 버전을 설정하는 방법의 경우 실제 서비스를 사용하는 사용자의 통계가 아니기 때문에 정확한 통계라고 할 수 없습니다. 또 두번째 사용자 접속 브라우저 버전 정보를 수집하여 그에 따른 대응 버전을 설정하는 방법의 경우 데이터를 수집해야하므로 시간적 비용이 소모될 수 있으므로 즉시 적용이 불가능하다는 단점이 있습니다.</p>
<p>이러한 문제점들을 고려해보았을 때 가장 효율적인 해결책이라고 생각되는 방법은 2년 이내에 상대적으로 최신버전을 우선적으로 대응하되 그 외 하위버전도 함께 검토해보며 크리티컬한 이슈에 대해서는 함께 대응해주는 것이 좋은 해결책이라고 생각됩니다. 또한 다른 하위 버전에 경우에는 사용자가 접속한 브라우저 버전 데이터를 수집해나가며 점진적으로 대응할 버전을 결정하고 대응하는 것이 최선의 방법이라고 생각됩니다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[study] 20220823 - Next.js _app과 _document]]></title>
            <link>https://velog.io/@y_jem/study</link>
            <guid>https://velog.io/@y_jem/study</guid>
            <pubDate>Fri, 19 Aug 2022 08:12:24 GMT</pubDate>
            <description><![CDATA[<h3 id="-_app"># <code>_app</code></h3>
<br>

<ul>
<li><p>참고 : <a href="https://nextjs.org/docs/advanced-features/custom-app">https://nextjs.org/docs/advanced-features/custom-app</a></p>
</li>
<li><p>특징</p>
<ol>
<li><p>가장 먼저 실행되는 컴포넌트입니다. (<code>_app</code> -&gt; <code>page component</code> -&gt; <code>_documnet</code>(Server side))</p>
</li>
<li><p>모든 페이지는 이 컴포넌트를 통합니다. (각 Route 구성 요소를 래핑하는 역할)</p>
</li>
<li><p>페이지에 적용할 공통 레이아웃 역할을 합니다. (ex header, footer, layout component 등)</p>
</li>
<li><p>페이지 전환 시 전체 레이아웃을 유지할 수 있습니다.</p>
</li>
<li><p>페이지 전환 후 상태를 유지시킬 수 있습니다.</p>
</li>
<li><p>글로벌 CSS를 적용시킬 수 있습니다.</p>
</li>
<li><p>추가적인 데이터를 페이지로 주입시켜주는게 가능합니다.</p>
</li>
<li><p>각종 Provider 설정할 수 있습니다. (ex Redux, Apollo 등)</p>
</li>
<li><p>props로 받는 Component는 페이지에 보여줄 컴포넌트이며, 페이지 전환 시 이 props의 값이 변경됩니다.</p>
</li>
<li><p>props로 받는 pageProps는 데이터 패칭 메소드를 통해 가져온 초기 객체입니다.</p>
</li>
<li><p><code>_app</code> 내부에서는 getStaticProps 또는 getServerSideProps 메서드를 지원하지 않습니다.</p>
</li>
<li><p><code>_app</code> 내부에 getInitialProps가 있는 경우 자동 정적 최적화가 비활성화 됩니다. 자동 정적 최적화란 요구사항이 없는 경우 자동으로 페이지를 정적으로 생성하는 것을 말합니다.</p>
</li>
</ol>
</li>
</ul>
<br>

<h3 id="-_document"># <code>_document</code></h3>
<br>

<ul>
<li><p>참고 : <a href="https://nextjs.org/docs/advanced-features/custom-document">https://nextjs.org/docs/advanced-features/custom-document</a></p>
</li>
<li><p>특징</p>
<ol>
<li><p><code>_document</code>는 <code>_app</code> 다음에 실행되며, 공통적으로 활용할 head(ex meta태그)나 body 태그 안에 들어갈 내용들을 커스텀할 때 활용합니다.</p>
</li>
<li><p>폰트 import, CDN 등을 연결하여 사용할 수 있습니다.</p>
</li>
<li><p>Document 클래스를 상속받는 클래스 컴포넌트로 작성해야한다는 규칙이 있습니다.</p>
</li>
<li><p>렌더 함수는 꼭 Html, Head, Main, NextScript를 포함해야 합니다.</p>
</li>
<li><p>페이지 별 공통적인 사항이 아닌 title같은 경우 app에서 처리합니다.</p>
</li>
<li><p>서버 사이드에서 동작하기 때문에 onClick과 같은 이벤트나, CSS 스타일 파일은 작동하지 않습니다. 테스트해보기 위해 useEffect hook을 사용하여 console에 문자열이 찍히는지 확인해보았는데 역시 <code>_document</code>는 찍히지 않고 <code>_app</code>은 찍힙니다.</p>
</li>
<li><p>커스텀이 필수는 아니며 커스텀하지 않을 경우 Next 모듈에 존재하는 document.js을 실행하게 됩니다.</p>
</li>
<li><p>Head 컴포넌트 조작 시 import 위치를 주의해야 합니다. <code>_document</code> 내부에 사용하는 모든 페이지에 공통적으로 적용시킬 Head의 경우 next/document에서 가져온 Head를 사용해야 합니다. 또 title 태그와 같이 페이지별로 다른 Head 설정을 할 경우에는 next/head에서 가져온 Head를 사용해야 합니다.</p>
</li>
<li><p><code>_app</code>과 마찬가지로 <code>_document</code> 내부에서는 getStaticProps 또는 getServerSideProps 메서드를 지원하지 않습니다.</p>
</li>
<li><p><code>_document</code>는 페이지 별 오버라이딩이 가능합니다. 하지만 공통으로 사용하고 있는 <code>_document</code>가 있다면 페이지 별로 지정 된 <code>_document</code>로 대체되어 문제가 발생할 수 있습니다.</p>
</li>
</ol>
</li>
</ul>
<br>

<h3 id="-_document는-_app-한-줄-정리"># <code>_document</code>는 <code>_app</code> 한 줄 정리</h3>
<br>

<ul>
<li><code>_document</code>는 정적인 상태로 적용시킬 공통 사항을 적용시킬 때 사용하며 react의 <code>index.html</code>과 유사한 기능을 수행합니다. <code>_app</code>는 동적인 상태로 적용시킬 공통 사항을 적용시킬 때 사용하며 react의 router 설정 컴포넌트와 유사한 기능을 수행합니다.</li>
</ul>
<br>

<h2 id="-nextjs의-데이터-패칭-방식"># Next.js의 데이터 패칭 방식</h2>
<br>

<p>Next.js의 pre-rendering(사전 렌더링)을 위한 데이터 패칭 방식은 크게 4가지가 있습니다. 각각의 데이터 패칭 방식을 통해 받아온 데이터는 컴포넌트에 props로 전달받아 사용할 수 있습니다.</p>
<ul>
<li><p>getInitialprops (SSR : Server Side rendering)</p>
<ul>
<li>최초에 앱이 렌더링되거나, 클라이언트 라우팅이 일어나는 순간 데이터를 패칭합니다. getServerSideProps와 비슷하지만 route로 접근할 때 새로고침이나 직접 URL을 입력하는 방식으로 접근하면 서버에서, Next.js에서 제공하는 Link 컴포넌트를 통해 접근한다면 클라이언트에서 호출됩니다.</li>
</ul>
</li>
</ul>
<br>

<ul>
<li><p>getServerSideProps (SSR : Server Side rendering)</p>
<ul>
<li>페이지 접근 시 매번 새로 데이터를 패칭합니다.</li>
</ul>
</li>
</ul>
<br>

<ul>
<li><p>getStaticProps (SSG: Static Site Generation)</p>
<ul>
<li><p>빌드 시 데이터를 패칭하고 CDN에 캐싱하여 모든 요청에 재사용합니다. 빌드 된 디렉토리에 접근해보면 빌드 시점에 맞는 데이터가 <code>index.html</code>에 채워져있는 것을 확인해볼 수 있습니다. 즉 빌드 시점 후에 서버에 데이터 변화가 일어나게 되면 빌드되지 않은 경우에는 데이터가 최신화되지 않습니다.</p>
</li>
<li><p>development 환경에서 getStaticProps는 매 요청마다 실행되며 production 모드에서는 getStaticProps는 빌드 시 실행됩니다.</p>
</li>
</ul>
</li>
</ul>
<br>

<ul>
<li><p>getStaticProps + revalidate (ISR: Incremental Static Regeneration)</p>
<ul>
<li><p>getStaticProps 사용 시 Data의 최신화가 이루어지지 않는다는 단점이 있는데 revalidate 속성을 활용하여 해결할 수 있습니다. 초기에 캐싱 된 페이지를 보여준 후 revalidate 주기마다 데이터의 업데이트를 확인하여 페이지를 재생성합니다.</p>
</li>
<li><p>트래픽이 발생하면 재생성이 이루어지기 때문에 사이트를 이용하는 유저가 없는 경우 revalidate 주기를 넘어도 페이지가 재생성되지 않습니다.</p>
</li>
</ul>
</li>
</ul>
<br>

<h2 id="-프로젝트-분석"># 프로젝트 분석</h2>
<br>

<ul>
<li><p><code>_document.tsx</code></p>
<pre><code class="language-tsx">// 페이스북에서는 광고효과를 측정하기 위해서 사용하는 페이스북 픽셀 측정 도구를 사용하기 위한 스크립트입니다.
const META_SDK_SNIPPET = `!function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?n.callMethod.apply(n,arguments):n.queue.push(arguments)};
            if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version=&#39;2.0&#39;;
              n.queue=[];t=b.createElement(e);t.async=!0;
              t.src=v;s=b.getElementsByTagName(e)[0];
              s.parentNode.insertBefore(t,s)}(window, document,&#39;script&#39;,
            &#39;https://connect.facebook.net/en_US/fbevents.js&#39;);
            fbq(&#39;init&#39;, &#39;${META_ID}&#39;);
            fbq(&#39;track&#39;, &#39;PageView&#39;);`;

class _document extends Document&lt;{
  stylesheets: Sheet[];
}&gt; {
  /*
    찾아보니 segment가 제공하는 analytics.js 스니펫을 렌더링할 때 사용한다는데 무슨 말인지 잘 이해가 가지 않습니다.
    Google analytics처럼 페이지 추적해서 사용자 흐름 분석하기 위해 적용시키는 것이 아닐까 싶습니다.
    그렇기 때문에 개발 모드를 구분하는 조건문이 포함되어 있는 것 같습니다.
  */
  renderSnippet() {
    const opts = {
      apiKey: ANALYTICS_WRITE_KEY,
      page: true,
    };
    if (NODE_ENV === &quot;development&quot;) {
      return snippet.max(opts);
    }
    return snippet.min(opts);
  }

  /*
      공식문서에서는 getInitialProps보다 getServerSideProps나 getStaticProps로 데이터 패칭하는 것을 권장하고 있습니다.
      getInitialProps는 서버 사이드에서 실행되며 서버 사이드 렌더링 적용 시 사용합니다.
      아래 코드에서 사용한 이유는 styletron을 우선적으로 적용시키기 위해 사용한 것이 아닌가 싶습니다.
      nextjs의 Pre-rendering 과정은 html을 먼저 로드하여 보여주는 inital load html 과정과 hydration 과정으로 나누어 이루어지는데
      이 hydration 과정에서 js가 로드되어 우선적으로 로드한 html과 동기화하게 된다고 합니다.
      그렇기 때문에 js내부에 작성되는 css-in-js인 styletron은 html이 보여질 때 바로 적용되는 것이 아닌 그 후에 로드되게 되는 것입니다.
      이를 해결하기 위해서 renderPage 함수를 사용해야 한다고 합니다.
      공식 페이지에도 renderPage 함수를 커스터마이징하는 것은 css-in-js 라이브러리를 사용할 때만 하라고 나와있습니다.
      renderPage 함수를 통해 렌더링 되는 조건을 설정하여 렌더링할 수 있습니다.
  */
  static getInitialProps(props: any) {
    // eslint-disable-next-line react/display-name
    const page = props.renderPage((App: any) =&gt; (props: any) =&gt; (
      &lt;StyletronProvider value={styletron}&gt;
        &lt;App {...props} /&gt;
      &lt;/StyletronProvider&gt;
    ));
    const stylesheets = (styletron as Server).getStylesheets() || [];
    return { ...page, stylesheets };
  }

  /*
      return 내부에서 모든 페이지에 공통적으로 적용 될 html 문서의 구조를 커스텀할 수 있습니다.
      페이지가 제대로 렌더링되기 위해서는 `&lt;Html&gt;`, `&lt;Head /&gt;`, `&lt;Main /&gt;` 및 `&lt;NextScript /&gt;`가 필요하며 폰트를 적용시키거나 글로벌 CSS를 정의할 수 있습니다.
      title과 같이 페이지 별로 다르게 적용되는 태그는 `_document`가 아닌 `_app`에서 관리합니다.
      main같은 경우는 `_app`에 의해 동적으로 변경되는 부분입니다.
  */
  render() {
    return (
      &lt;Html&gt;
        &lt;Head&gt;...&lt;/Head&gt;
        &lt;body&gt;
          &lt;Main /&gt;
          &lt;NextScript /&gt;
        &lt;/body&gt;
      &lt;/Html&gt;
    );
  }
}</code></pre>
</li>
</ul>
<br>

<ul>
<li><p><code>_app.tsx</code></p>
<pre><code class="language-tsx">const debouncedCalculateAlternativeViewportUnit = debounce(
  calculateAlternativeViewportUnit,
  700
);

interface DefaultStaticProps {
  isPublic?: boolean;
  appHeaderConfig?: Omit&lt;AppHeaderProps, &quot;theme&quot;&gt;;
  hasAppBar?: boolean;
  isNoProfileUserAccessible?: boolean;
}

// dayjs 사용 시 global로 한국어 locale 사용합니다.
dayjs.locale(&quot;ko&quot;);

/*
  - Component : 렌더링 될 컴포넌트, 요청한 페이지를 말한다. GET `/` 요청을 보냈다면, Component 에는 /pages/index.js 파일이 props로 내려오게 됩니다.
  - pageProps : getInitialProps을 통해 내려받은 props 입니다. (getInitialProps, getStaticProps, getServerSideProps 를 통해 가져온 초기 속성값을 의미합니다. 위의 값들이 없다면 빈 객체를 반환합니다.) appHeaderConfig, isPublic, hasAppBar는 각 페이지 getStaticProps로 빌드 타임 시 해당 속성들의 값을 props로 패칭하여 각 페이지 별 접근 및 헤더 등을 페이지 별로 제어합니다.
  - router : 요청한 페이지의 정보를 가진 객체입니다.
*/
const _App: FC&lt;AppProps&gt; = ({
  Component,
  pageProps: {
    appHeaderConfig,
    isPublic = false,
    hasAppBar = false,
    isNoProfileUserAccessible = false,
    ...pageProps
  },
  router,
}) =&gt; {
  const [isBrowserModalOpen, setBrowserModalOpen] = useState(false);
  const [historyArr, setHistoryArr] = useState&lt;string[]&gt;([]);

  useMount(function didMounted() {
    // 초기 렌더링 시 디스플레이 크기에 대응합니다.
    calculateAlternativeViewportUnit();
    // 브라우저를 감지하여 크로스 브라우징에 대응, 모달 띄워서 크롬으로 사용 유도합니다.
    const client = detectBrowser();
    client === browser.SAFARI &amp;&amp; setBrowserModalOpen(true);

    const { asPath } = router;
    setHistoryArr((prevState) =&gt; [...prevState, asPath]);
  });
  // 윈도우 리사이징 시 디스플레이 크기 대응, 여러번 호출 시 0.7초마다 실행되도록 디바운스 걸어 과부하를 방지합니다.
  useEvent(&quot;resize&quot;, debouncedCalculateAlternativeViewportUnit);
  // 페이지 history 기록, 현재 페이지가 마지막 배열 요소와 같지 않을 경우 누적해서 쌓이는 형식으로 저장됩니다.
  // 이런 식으로 전체 페이지에서 사용 될 로직의 경우 `_app` 내부에 작성하여 상태를 유지시킬 수 있습니다.
  useEffect(() =&gt; {
    const { asPath } = router;
    console.log(router);
    if (historyArr[historyArr.length - 1] !== asPath) {
      setHistoryArr((prevState) =&gt; [...prevState, asPath]);
    }
  }, [pageProps]);

  return (
    &lt;StyleTronProvider value={styletron}&gt;
      {/* SEO를 위한 타이틀 및 메타태그를 설정합니다. */}
      &lt;DefaultSeo {...SEO} /&gt;
      {/* 로그인 상태를 전달하는 Provider 입니다. */}
      &lt;WithRealmAppProvider appId={process.env.NEXT_PUBLIC_REALM_ID ?? &quot;&quot;}&gt;
        {/* Graphql API와 연동 가능하도록 ApolloProvider로 감싸줍니다. */}
        &lt;WithApolloProvider pageProps={pageProps}&gt;
          {/* baseui를 사용하기 위해 BaseProvider로 감싸줍니다.*/}
          &lt;WithBaseProvider
            appHeaderConfig={appHeaderConfig}
            hasAppBar={hasAppBar}
          &gt;
            {/* 조건에 따른 헤더 노출. AppHeader가 잘 이해가 가지 않습니다.. */}
            {appHeaderConfig &amp;&amp; (
              &lt;Layer&gt;
                {appHeaderConfig &amp;&amp; &lt;AppHeader {...appHeaderConfig} /&gt;}
              &lt;/Layer&gt;
            )}
            {/* props로 전달받은 프로필 입력 상태나 유저의 로그인 상태를 구분하여 사용자에게 보여줄 children(컴포넌트 페이지)를 결정합니다.*/}
            &lt;WithAuth
              isPublic={isPublic}
              isNotFound={router.route.includes(&quot;404&quot;)}
              isNoProfileUserAccessible={isNoProfileUserAccessible}
            &gt;
              &lt;Component {...pageProps} historyArr={historyArr} /&gt;
            &lt;/WithAuth&gt;
            {/* 각 페이지 별로 getStaticProps를 사용하여 빌드 타임 시 해당 속성들의 값을 props로 패칭하고
            이 props를 통해 hasAppBar의 boolean값을 제어합니다. */}
            {hasAppBar &amp;&amp; (
              &lt;Layer&gt;
                &lt;AppBar items={DEFAULT_APP_BAR_SETS} /&gt;
              &lt;/Layer&gt;
            )}
            {/* 브라우저를 감지하여 크로스 브라우징에 대응, 모달 띄워서 크롬으로 사용 유도,
            다른 브라우저(ex 사파리)로 테스트 시 확인 가능합니다. */}
            &lt;MAModal
              type=&quot;exit&quot;
              status=&quot;error&quot;
              isOpen={isBrowserModalOpen}
              title=&quot;We work best in Google Chrome&quot;
              content=&quot;Go and experience Matchark in Google Chrome!&quot;
              okText=&quot;I got it&quot;
              cancelText=&quot;Cancel&quot;
              onOk={() =&gt; setBrowserModalOpen(false)}
              onModalClose={() =&gt; setBrowserModalOpen(false)}
            /&gt;
          &lt;/WithBaseProvider&gt;
        &lt;/WithApolloProvider&gt;
      &lt;/WithRealmAppProvider&gt;
    &lt;/StyleTronProvider&gt;
  );
};</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Javascript] for문에서의 비동기 함수 사용 시 스코프와 클로저]]></title>
            <link>https://velog.io/@y_jem/Javascript-for%EB%AC%B8%EC%97%90%EC%84%9C%EC%9D%98-%EB%B9%84%EB%8F%99%EA%B8%B0-%ED%95%A8%EC%88%98-%EC%82%AC%EC%9A%A9-%EC%8B%9C-%EC%8A%A4%EC%BD%94%ED%94%84%EC%99%80-%ED%81%B4%EB%A1%9C%EC%A0%80</link>
            <guid>https://velog.io/@y_jem/Javascript-for%EB%AC%B8%EC%97%90%EC%84%9C%EC%9D%98-%EB%B9%84%EB%8F%99%EA%B8%B0-%ED%95%A8%EC%88%98-%EC%82%AC%EC%9A%A9-%EC%8B%9C-%EC%8A%A4%EC%BD%94%ED%94%84%EC%99%80-%ED%81%B4%EB%A1%9C%EC%A0%80</guid>
            <pubDate>Wed, 10 Aug 2022 07:10:19 GMT</pubDate>
            <description><![CDATA[<h2 id="-for문에서의-비동기-함수-사용-시-스코프와-클로저"># for문에서의 비동기 함수 사용 시 스코프와 클로저</h2>
<p>for문에서 비동기 함수 사용 시 초기 값을 var를 사용하느냐 혹은 let 사용하느냐에 따라 출력 값이 달라질 수 있다. 이러한 결과가 나타나는 이유는 블록 레벨 스코프와 함수 레벨 스코프의 차이 때문이다.</p>
<br>

<h3 id="-사용-예시1--var"># 사용 예시1 : var</h3>
<pre><code class="language-js">// 예시1 : var 사용
const functionScope = () =&gt; {
  for (var i = 0; i &lt; 10; i++) {
    setTimeout(() =&gt; {
      console.log(i);
    }, 100);
  }
};

functionScope(); // 10이 10번 반복해서 찍힌다.</code></pre>
<p>functionScope의 결과 값은 10이 10번 찍히게 된다. 이유는 for문 동작 시 setTimeout을 호출하게 되면 콜스택에 함수 컨텍스트를 생성한 후 바로 제거하여 웹API 백그라운드에서 처리된다. 이 후 setTimeout은 비동기적으로 처리되기 때문에 나머지 for문도 방금과 같은 작업을 반복하게 된다. 그렇기 때문에 setTimeout이 타이머에 의해 웹API에서 처리되기 전 for문은 이미 마지막 루프까지 마친 상태이기 때문에 i값은 10이 되고 웹API에서 처리 후 큐를 통해 실행되는 setTimeout 콜백 함수들은 이 i값을 참조하게 되어 모두 10을 반환한다. 또한 i보다 10보다 작은 경우에만 반복을 수행하도록 하였는데 i가 10인 이유는 반복문에서 10이 되는 순간 false가 되어 더 이상 반복문을 실행하지 않지만 i는 이미 10이 되어있기 때문이다.</p>
<p>이와 같은 경우 클로저로 해결할 수 있다.</p>
<pre><code class="language-js">// 예시2: var 사용 + 클로저
const closerFunction = () =&gt; {
  for (var i = 0; i &lt; 10; i++) {
    ((x) =&gt; {
      setTimeout(() =&gt; {
        console.log(x);
      }, 100);
    })(i);
  }
};

closerFunction(); // 0~9까지 순서대로 찍힌다.</code></pre>
<p>클로저란 간단하게 말하면 내부함수가 외부함수에 접근할 수 있는 것을 말한다. 기존에 동작시키던 방식과 다르게 즉시 실행 함수를 생성한 후 파라미터로 i값을 전달하는 방식을 활용한다. 이런 방식을 활용하게 되면 콜스택에 즉시 실행 함수 컨텍스트가 쌓이고 먼저 사라지더라도 웹API에 콜백으로 넘긴 setTimeout 콜백 함수 스코프에 파라미터로 넘긴 x의 값을 기억하고 있기 때문에 해당 x값을 참조하여 최신 값을 유지할 수 있다.</p>
<br>

<h3 id="-사용-예시2--let"># 사용 예시2 : let</h3>
<pre><code class="language-js">// 예시3: let 사용
const blockScope = () =&gt; {
  for (let i = 0; i &lt; 10; i++) {
    setTimeout(() =&gt; {
      console.log(i);
    }, 100);
  }
};

blockScope(); // 0~9까지 순서대로 찍힌다.</code></pre>
<p>예를 들어 functionScope()와 같이 var를 사용한 경우 var는 함수 레벨 스코프를 가지기 때문에 for 루프마다 새로운 스코프를 만들어내는 것이 아닌 functionScope 내부에 하나의 스코프로만 존재하게 된다. 그렇기 때문에 for 루프마다 동일한 참조를 바인딩해서 사용하게 되는 것이다. 반면 blockScope()와 같이 let을 사용한 경우 let은 블록 레벨 스코프를 가지기 때문에 for 루프마다 새로운 스코프를 만들어낸다. i가 0인 스코프, 1인 스코프, 2인 스코프 ... 등등으로 만들어내게 되고 이 각 루프마다 만들어지는 새로운 스코프를 참조하게 되므로 서로 다른 참조를 하게 되어 동일한 값이 출력되지 않게 된다.</p>
<br>]]></description>
        </item>
        <item>
            <title><![CDATA[[javascript] 함수 호출 방법 Call by value & call by Reference]]></title>
            <link>https://velog.io/@y_jem/javascript-%ED%95%A8%EC%88%98-%ED%98%B8%EC%B6%9C-%EB%B0%A9%EB%B2%95-Call-by-value-call-by-Reference</link>
            <guid>https://velog.io/@y_jem/javascript-%ED%95%A8%EC%88%98-%ED%98%B8%EC%B6%9C-%EB%B0%A9%EB%B2%95-Call-by-value-call-by-Reference</guid>
            <pubDate>Tue, 26 Jul 2022 01:54:47 GMT</pubDate>
            <description><![CDATA[<h2 id="-함수-호출-방법-call-by-value--call-by-reference"># 함수 호출 방법 Call by value &amp; call by Reference</h2>
<br>

<h3 id="-call-by-value값에-의한-호출"># Call by value(값에 의한 호출)</h3>
<p>인자로 원시 타입의 객체를 넘기는 것을 말한다. 함수의 파라미터는 인자와 같은 데이터의 주소 값을 참조하고 있지만 함수 내부에서 파라미터를 수정하는 경우 콜스택에서 새로운 데이터를 생성하여 그 데이터의 주소 값을 참조하기 때문에 원본의 변화는 없다. 값을 복사하여 처리하기 때문에 안전하고 원래의 값이 보존되지만 메모리 사용량이 늘어난다.</p>
<pre><code class="language-js">// Call by value(값에 의한 호출)
const a = 1;

const callByValue = (param) =&gt; {
  param = 2;

  return param;
};

console.log(callByValue(a)); // 2
console.log(a); // 1, 원본 변경 X</code></pre>
<br>

<h3 id="-call-by-reference참조에-의한-호출"># Call by reference(참조에 의한 호출)</h3>
<p>인자로 참조 타입의 객체를 넘기는 것을 말한다. 함수의 파라미터는 인자와 같은 데이터의 주소 값을 참조하고 있어 함수 내부에서 파라미터를 수정하는 경우 원본이 변화된다. 복사하지 않고 직접 참조를 하기에 처리가 빠르지만 원본의 변화가 생길 수 있다.</p>
<pre><code class="language-js">// Call by reference(참조에 의한 호출)
const arr = [1, 2, 3];

const callByReference = (param) =&gt; {
  param.push(4);

  return param;
};

console.log(callByReference(arr)); // [1, 2, 3, 4]
console.log(arr); // [1, 2, 3, 4], 원본 변경 O</code></pre>
<br>]]></description>
        </item>
        <item>
            <title><![CDATA[[javascript] 재귀함수와 꼬리재귀]]></title>
            <link>https://velog.io/@y_jem/javascript-%EC%9E%AC%EA%B7%80%ED%95%A8%EC%88%98%EC%99%80-%EA%BC%AC%EB%A6%AC%EC%9E%AC%EA%B7%80</link>
            <guid>https://velog.io/@y_jem/javascript-%EC%9E%AC%EA%B7%80%ED%95%A8%EC%88%98%EC%99%80-%EA%BC%AC%EB%A6%AC%EC%9E%AC%EA%B7%80</guid>
            <pubDate>Sun, 24 Jul 2022 07:03:52 GMT</pubDate>
            <description><![CDATA[<h2 id="-재귀함수"># 재귀함수</h2>
<p>함수가 자기 자신을 호출하는 것을 재귀함수라고 한다. 재귀함수는 종료조건이 있어야 하며, 종료조건을 설정해주지 않으면 무한 반복을 하게된다. 재귀 함수를 사용하는 이유는 알고리즘 자체가 재귀적으로 표현하기 자연스러울 때 혹은 변수 사용을 줄이기 위해 사용한다.</p>
<br>

<h3 id="-재귀함수-사용"># 재귀함수 사용</h3>
<p>일반적인 재귀함수 사용방법이다.</p>
<pre><code class="language-js">  // 재귀 함수
  function factorial(n) {
    if (n === 1) {
      return 1;
    }
    return n * factorial(n - 1);
  }

  factorial(3); // 6</code></pre>
<blockquote>
<h4 id="-재귀함수-동작-과정"># 재귀함수 동작 과정</h4>
<p>(1) factorial(3) 함수 컨텍스트 생성 -&gt; factorial 호출문 만나면 실행 단계 중지 및 인자로 (2) 전달
(2) factorial(2) 함수 컨텍스트 생성 -&gt; factorial 호출문 만나면 실행 단계 중지 및 인자로 (1) 전달
(3) factorial(1) 함수 컨텍스트 생성 -&gt; n이 1이므로 1 출력 -&gt; factorial(1,6) 함수 컨텍스트 제거
(4) factorial(2) 함수 컨텍스트가 n과 전달받은 호출 결과 1을 연산하여 2 출력 -&gt; factorial(2) 함수 컨텍스트 제거
(5) factorial(3) 함수 컨텍스트가 n과 전달받은 호출 결과 2를 연산하여 6 출력 -&gt; factorial(3) 함수 컨텍스트 제거</p>
</blockquote>
<br>

<h3 id="-꼬리재귀-사용"># 꼬리재귀 사용</h3>
<p>재귀 함수의 단점으로는 콜스택의 부하로 인한 메모리 낭비인데 꼬리 재귀(재귀 호출이 끝나면 아무 일도 하지 않고 결과만 바로 반환되도록 하는 방법)를 통해 해결할 수 있다. 꼬리 재귀를 사용하면 이전 함수의 상태를 유지하지도 않고 추가 연산을 하지도 않아서 콜스택의 부하 문제를 해결할 수 있게 된다.</p>
<pre><code class="language-js">  // 꼬리 재귀 함수
  function factorial(n, total = 1) {
    if (n === 1) {
      return total;
    }
    return factorial(n - 1, n * total);
  }

  factorial(3); // 6</code></pre>
<blockquote>
<h4 id="-꼬리재귀-동작-과정"># 꼬리재귀 동작 과정</h4>
<p>(1) factorial(3,1) 함수 컨텍스트 생성 -&gt; factorial 호출문 만나면 실행 단계 중지 및 인자로 (2,3) 전달
(2) factorial(2,3) 함수 컨텍스트 생성 -&gt; factorial 호출문 만나면 실행 단계 중지 및 인자로 (1,6) 전달
(3) factorial(1,6) 함수 컨텍스트 생성 -&gt; n이 1이므로 파라미터로 전달받은 total 변수의 값인 6 출력 -&gt; factorial(1,6) 함수 컨텍스트 제거
(4) factorial(2,3) 함수 컨텍스트가 전달받은 호출 결과 6 출력 -&gt; factorial(2,3) 함수 컨텍스트 제거
(5) factorial(3,1) 함수 컨텍스트가 전달받은 호출 결과 6 출력 -&gt; factorial(3,1) 함수 컨텍스트 제거</p>
</blockquote>
<p>위와 같이 재귀함수의 경우 전달받은 호출 값을 사용하여 연산을 진행하게 되는 반면 꼬리재귀는 전달받은 호출 값을 그대로 사용하게 된다. 위와 같은 동작 과정에서도 알 수 있듯이 매번 연산을 진행하는 재귀함수의 문제를 마지막 한번에 출력 값을 활용하는 꼬리재귀를 통해 해결할 수 있게 된다.</p>
<br>]]></description>
        </item>
        <item>
            <title><![CDATA[[javascript] IntersectionObserver API]]></title>
            <link>https://velog.io/@y_jem/javascript-IntersectionObserver-API</link>
            <guid>https://velog.io/@y_jem/javascript-IntersectionObserver-API</guid>
            <pubDate>Wed, 20 Jul 2022 13:35:55 GMT</pubDate>
            <description><![CDATA[<h2 id="-intersectionobserver-api"># IntersectionObserver API</h2>
<p>IntersectionObserver는 WebAPI로 비동기적으로 실행되며 관찰 대상과 뷰포트의 교차점을 관찰하고 뷰포트 안으로 들어오는 시점에 정보를 제공하는 기능을 한다.
아래와 같은 상황에 활용한다.</p>
<ul>
<li>페이지가 스크롤되는 도중에 발생하는 이미지나 다른 콘텐츠의 지연 로딩</li>
<li>무한 스크롤(infinite-scroll) 구현</li>
<li>광고 수익을 계산하기 위한 용도로 광고의 가시성 보고</li>
<li>사용자에게 결과가 표시되는 여부에 따라 작업이나 애니메이션 수행 여부</li>
</ul>
<br>

<h3 id="-intersection-observer-api-사용법"># Intersection Observer API 사용법</h3>
<h4 id="-1-intersection-observer-생성"># 1. Intersection observer 생성</h4>
<p>Intersection observer를 생성하기 위해서는 생성자 호출 시 콜백 함수를 제공해야 한다. 이 콜백은 함수 threshold가 한 반향 혹은 다른 방향으로 교차할 때 실행된다.</p>
<pre><code class="language-js">let options = {
  root: document.querySelector(&#39;#scrollArea&#39;),
  rootMargin: &#39;0px&#39;,
  threshold: 1.0
}

let observer = new IntersectionObserver(callback, options);</code></pre>
<br>

<h4 id="-2-intersection-observer-설정"># 2. Intersection observer 설정</h4>
<ul>
<li><strong>root</strong> : 대상 객체의 가시성을 확인할 때 사용되는 뷰포트 요소이며 root는 대상 객체의 조상 요소여야 한다. default는 브라우저의 viewport이다.</li>
<li><strong>rootMargin</strong> : root가 가진 여백으로 이 속성의 값은 css의 margin 속성과 비슷하며 rootMargin은 root 요소의 각 측면의 bounding box를 수축시키거나 증가시키며, 교차성을 계산하기 전에 적용된다. default는 0이다.</li>
<li><strong>threshold</strong> : observer의 콜백이 실행될 대상 요소의 가시성 퍼센티지를 나타내는 단일 숫자 혹은 배열이다. 만약 50%만큼 요소가 보였을 때를 탐지하고 싶다면, 값을 0.5로 설정하면 된다. 또는 25% 단위로 요소의 가시성이 변경될 때마다 콜백이 실행되게 하고 싶다면 [0, 0.25, 0.5, 0.75, 1]과 같이 배열을 설정하면 된다. default는 0이며 이는 요소가 1px이라도 보이자마자 콜백이 실행됨을 의미한다. 1.0은 요소의 모든 픽셀이 화면에 노출되기 전에 콜백을 실행시키지 않음을 의미한다.</li>
</ul>
<br>

<h4 id="-3-intersection-observer-callback-형태"># 3. Intersection Observer Callback 형태</h4>
<pre><code class="language-js">let callback = (entries, observer) =&gt; {
  entries.forEach(entry =&gt; {
    // Each entry describes an intersection change for one observed
    // target element:
    //   entry.boundingClientRect
    //   entry.intersectionRatio
    //   entry.intersectionRect
    //   entry.isIntersecting
    //   entry.rootBounds
    //   entry.target
    //   entry.time
  });
};</code></pre>
<br>

<h4 id="-4-intersection-observer을-통해-타깃으로-하는-요소-관찰하기"># 4. Intersection observer을 통해 타깃으로 하는 요소 관찰하기</h4>
<pre><code class="language-js">let target = document.querySelector(&#39;#listItem&#39;);

observer.observe(target);</code></pre>
<br>

<h3 id="-intersection-observer-api-실제-활용"># Intersection Observer API 실제 활용</h3>
<h4 id="-동적-이미지-로딩"># 동적 이미지 로딩</h4>
<p>IntersectionObserver를 이용하면 동적 이미지 로딩을 손쉽게 구현 할 수 있다. 구현 방법은 대상 element가 뷰포트에 들어왔을 때 이미지 경로를 변경 한 뒤 unobserve시킨다. 다음의 코드는 간단한 예제 코드이다.</p>
<pre><code class="language-html">&lt;div class=&quot;example&quot;&gt;
  &lt;img src=&quot;https://picsum.photos/600/400/?random?0&quot; alt=&quot;random image&quot; class=&quot;image-default&quot;&gt;
  &lt;img data-src=&quot;https://picsum.photos/600/400/?random?1&quot; alt=&quot;random image&quot; class=&quot;image&quot;&gt;
  &lt;img data-src=&quot;https://picsum.photos/600/400/?random?2&quot; alt=&quot;random image&quot; class=&quot;image&quot;&gt;
  &lt;img data-src=&quot;https://picsum.photos/600/400/?random?3&quot; alt=&quot;random image&quot; class=&quot;image&quot;&gt;
  &lt;img data-src=&quot;https://picsum.photos/600/400/?random?4&quot; alt=&quot;random image&quot; class=&quot;image&quot;&gt;
  &lt;img data-src=&quot;https://picsum.photos/600/400/?random?5&quot; alt=&quot;random image&quot; class=&quot;image&quot;&gt;
  &lt;img data-src=&quot;https://picsum.photos/600/400/?random?6&quot; alt=&quot;random image&quot; class=&quot;image&quot;&gt;
  &lt;img data-src=&quot;https://picsum.photos/600/400/?random?7&quot; alt=&quot;random image&quot; class=&quot;image&quot;&gt;
&lt;/div&gt;</code></pre>
<p>동적 이미지 로딩을 수행할 이미지 태그의 경우 dataset 속성에 이미지 url을 담아둔다.
그 후 IntersectionObserver API를 활용하여 관찰하는 뷰포트 안으로 들어왔을 경우 dataset 속성에 이미지 url을 src 속성으로 할당해준다. 주의해야 할 점은 사용자에게 바로 노출되는 최상위 이미지의 경우 동적 이미지 로딩이 필요하지 않으므로 일반적인 방법과 같이 src 속성에 이미지 url을 담아둔다.</p>
<pre><code class="language-js">// IntersectionObserver의 options를 설정
const options = {
  root: null,
  // 타겟 이미지 접근 전 이미지를 불러오기 위해 rootMargin을 설정
  rootMargin: &#39;0px 0px 30px 0px&#39;,
  threshold: 0
}

// IntersectionObserver 를 등록
const io = new IntersectionObserver((entries, observer) =&gt; {
  entries.forEach(entry =&gt; {
    // 관찰 대상이 viewport 안에 들어온 경우 image 로드
    if (entry.isIntersecting) {
      // data-src 정보를 타켓의 src 속성에 설정
      entry.target.src = entry.target.dataset.src;
      // 이미지를 불러왔다면 타켓 엘리먼트에 대한 관찰 멈춤
      observer.unobserve(entry.target);
    }
  })
}, options)

// 관찰할 대상을 선언하고, 해당 속성을 관찰
const images = document.querySelectorAll(&#39;.image&#39;);
images.forEach((el) =&gt; {
  io.observe(el);
})</code></pre>
<br>

<h4 id="-무한-스크롤"># 무한 스크롤</h4>
<p>무한 스크롤 기능도 IntersectionObserver를 이용하면 쉽게 구현 할 수 있다.</p>
<pre><code class="language-html">&lt;div class=&quot;container&quot;&gt;
  &lt;div id=&quot;items&quot;&gt;&lt;/div&gt;
  &lt;div id=&quot;loader&quot;&gt;&lt;/div&gt;
&lt;/div&gt;</code></pre>
<p>컨테이너 역할을 하는 DOM의 하단에 추가 로딩을 위한 element를 추가 한뒤, 해당 element가 뷰포트에 접근했을 때 추가 컴포넌트 또는 이미지를 로딩 하면 된다. 이미지 동적 로딩 때와는 달리, 계속해서 추가 로딩을 해야하기 때문에 unobserve는 하지 않는다.</p>
<pre><code class="language-js">const count = 20
let index = 0

const loadItems = () =&gt; {
  const fragment = document.createDocumentFragment()

  for (let i = index; i &lt; index + count; i += 1) {
    const item = document.createElement(&#39;div&#39;)
    item.innerText = `${i + 1}`
    item.classList.add(&#39;item&#39;)
    fragment.appendChild(item)
  }

  document.getElementById(&#39;items&#39;).appendChild(fragment)
  index += count
}

const observer = new IntersectionObserver(([loader]) =&gt; {
  if(!loader.isIntersecting) return

  loadItems()
})

loadItems()
observer.observe(document.getElementById(&#39;loader&#39;))</code></pre>
<br>

<hr>
<p>참고자료</p>
<ul>
<li><a href="https://armadillo-dev.github.io/javascript/what-is-intersection-observer/" target='_blank'>[Javascript] IntersectionObserver API 설명과 사용 방법</a></li>
<li><a href="https://woojong92.tistory.com/entry/JS-Intersection-Observer-API-%EC%82%AC%EC%9A%A9%EB%B2%95%EA%B3%BC-%ED%99%9C%EC%9A%A9" target='_blank'>[JS] Intersection Observer API 사용법과 활용</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[javascript] slice가 1차원 배열은 깊은 복사, 2차원 배열은 얕은 복사를 수행하는 이유]]></title>
            <link>https://velog.io/@y_jem/javascript-slice%EA%B0%80-1%EC%B0%A8%EC%9B%90-%EB%B0%B0%EC%97%B4%EC%9D%80-%EA%B9%8A%EC%9D%80-%EB%B3%B5%EC%82%AC-2%EC%B0%A8%EC%9B%90-%EB%B0%B0%EC%97%B4%EC%9D%80-%EC%96%95%EC%9D%80-%EB%B3%B5%EC%82%AC%EB%A5%BC-%EC%88%98%ED%96%89%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0</link>
            <guid>https://velog.io/@y_jem/javascript-slice%EA%B0%80-1%EC%B0%A8%EC%9B%90-%EB%B0%B0%EC%97%B4%EC%9D%80-%EA%B9%8A%EC%9D%80-%EB%B3%B5%EC%82%AC-2%EC%B0%A8%EC%9B%90-%EB%B0%B0%EC%97%B4%EC%9D%80-%EC%96%95%EC%9D%80-%EB%B3%B5%EC%82%AC%EB%A5%BC-%EC%88%98%ED%96%89%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0</guid>
            <pubDate>Tue, 19 Jul 2022 11:19:33 GMT</pubDate>
            <description><![CDATA[<h2 id="-얕은-복사와-깊은-복사"># 얕은 복사와 깊은 복사</h2>
<p>자바스크립트에서 객체 복사 방법에는 얕은 복사 방법과 깊은 복사 방법이 있다.
얕은 복사란 객체를 복사할 때 객체만 복사하여 기존 객체와 복사된 객체가 같은 참조를 가리키는 복사를 말한다. 깊은 복사란 객체를 복사할 때 객체와 참조 값을 모두 복사하여 기존 객체와 복사된 객체의 참조가 완전히 끊어진 복사를 말한다.</p>
<blockquote>
</blockquote>
<ul>
<li>얕은 복사 방법 : slice(), Object.assign(), Spread 연산자()</li>
<li>깊은 복사 방법 : JSON.parse(JSON.stringify(obj)), Lodash 라이브러리의 cloneDeep 메소드</li>
</ul>
<p>자바스크립트에서 값은 원시값과 참조값 두 가지 데이터 타입의 값이 존재한다. 자바스크립트에서 원시 타입의 경우 값이 수정(새로운 값 할당)되면 새로운 메모리 공간에 독립적인 값을 저장하기 때문에 깊은 복사가 되고 참조 타입의 경우 값이 수정(새로운 요소 추가)되면 원본을 직접 수정하므로 얕은 복사가 된다.</p>
<br>

<h3 id="-slice가-1차원-배열은-깊은-복사-2차원-배열은-얕은-복사를-수행하는-이유"># slice가 1차원 배열은 깊은 복사, 2차원 배열은 얕은 복사를 수행하는 이유</h3>
<p>slice 메소드는 기본적으로 얕은 복사를 수행하지만 1차원 배열의 경우 값이 원시 타입이기 때문에 원시 타입에 새로운 값 할당 시 독립적인 메모리 공간을 생성하여 이 메모리 공간에 새로운 데이터가 추가되어 서로 다른 참조를 바라보게 된다. 그렇기 때문에 깊은 복사처럼 보인다.</p>
<p>하지만 2차원 배열의 경우 내부 배열도 참조 타입이기 때문에 메모리 힙에 독립적인 공간에 존재하지만 <code>[[2차원 배열의 주소값], 원시 타입]</code>과 같이 결국 내부 배열의 메모리 주소를 바라보고 있으므로 최종적으로 같은 참조를 바라보게 되는 것이다.</p>
<pre><code class="language-js">const test = [[1, 2, 3], 100];
const copy = test.slice();

test[0].push(10);
test[1] = 200;

console.log(test); // [[1, 2, 3, 10], 200]
console.log(copy); // [[1, 2, 3, 10], 100]</code></pre>
<p>위 코드의 주소 값 참조는 아래와 같이 이루어진다. 이 때 변수가 참조하는 주소 값은 임의의 동네 이름으로 나타내보았다.</p>
<p>(1) test는 콜스택의 &quot;주소: 광명1동 / 값: 소하1동&quot; 의 주소를 가리키고, copy는 가리키는 콜스택의 &quot;주소: 광명2동 / 값: 소하1동&quot; 의 주소를 가리킨다.</p>
<p>(2) 위 test, copy의 값 필드에 소하1동은 메모리힙에 존재하며 &quot;주소: 소하1동 / 값: [하안1동, 100]&quot;과 같이 존재한다.</p>
<p>(3) 2차원 배열인 [1, 2, 3] 또한 참조 데이터이기 때문에 별도의 메모리 공간에 &quot;주소: 하안1동 / 값: [1, 2, 3]&quot; 과 같이 존재하게 되는 것이다.</p>
<p>(4) <code>test[0].push(10)</code> 이 실행되면 하안1동의 값 필드가 &quot;주소: 하안1동 / 값: [1, 2, 3, 10]&quot; 과 같이 변경되게 된다.</p>
<p>(5) <code>test[1] = 200</code> 이 실행되면 메모리에 존재하지 않던 원본 데이터가 새로 생성되므로 메모리 힙에 새로운 메모리 공간이 생성되고 기존에 존재하던 &quot;주소: 소하1동 / 값: [하안1동, 100]&quot;과 함께 &quot;주소: 소하2동 / 값: [하안1동, 200]&quot; 이 생성되게 된다.</p>
<p>(6) test는 콜스택의 &quot;주소: 광명1동 / 값: 소하2동&quot; 을 가리키게 되고 copy는 콜스택의 &quot;주소: 광명2동 / 값: 소하1동&quot; 을 가리키게 된다.</p>
<p>(7) 콜스택에서 참조하는 값은 다른 메모리힙에 데이터(소하1동, 소하2동)를 참조하므로 원본 데이터는 공유되지 않고 메모리힙에서는 같은 메모리힙에 데이터(하안1동)를 공유하고 있으므로 참조 데이터는 여전히 같은 데이터를 바라보고 있으므로 변경이 공유된다. 결과는 아래 이미지와 같다.</p>
<p><img src="https://velog.velcdn.com/images/y_jem/post/4e482b47-6f58-4621-a648-d3bb00453c26/image.jpeg" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[react] requestAnimationFrame 활용한 슬롯 카운트 기능 구현 (라이브러리 x)]]></title>
            <link>https://velog.io/@y_jem/react-%EC%8A%AC%EB%A1%AF-%EC%B9%B4%EC%9A%B4%ED%8A%B8-%EA%B8%B0%EB%8A%A5</link>
            <guid>https://velog.io/@y_jem/react-%EC%8A%AC%EB%A1%AF-%EC%B9%B4%EC%9A%B4%ED%8A%B8-%EA%B8%B0%EB%8A%A5</guid>
            <pubDate>Thu, 30 Jun 2022 07:19:35 GMT</pubDate>
            <description><![CDATA[<h2 id="-슬롯-카운트-기능-구현"># 슬롯 카운트 기능 구현</h2>
<p>라이브러리를 사용하지 않은 슬롯 카운트 기능을 구현해보았다.</p>
<br>

<h3 id="-easeoutexpo"># easeOutExpo</h3>
<p><code>https://easings.net/ko#easeOutExpo</code>에서 참고할 수 있는 Easing 함수이다.
함수 내부에 1은 최대 값을 말하고 파라미터 x는 진행율을 말한다. 파라미터로 전달되는 진행율은 점점 증가해야 함으로 보통 재귀 호출을 통해 사용한다.</p>
<pre><code class="language-js">function easeOutExpo(x: number): number {
  return x === 1 ? 1 : 1 - Math.pow(2, -10 * x);
}</code></pre>
<p>진행율 x가 최대 값에 도달할 때까지 반복 실행하는데 Math.pow의 음수 제곱 값을 활용하여 전달되는 파라미터 값이 커질수록 최대 값에 빼는 수가 점점 작아지게 된다. 즉 최대 값에 가까워질수록 증감하는 값이 작아지는 것이다. 이로 인해서 카운트 기능 중 최대 값에 가까워질수록 점점 느리게 증가하는 기능을 구현할 수 있다.</p>
<br>

<h3 id="-requestanimationframe과-cancelanimationframe"># requestAnimationFrame과 cancelAnimationFrame</h3>
<h4 id="-애니메이션의-프레임frame"># 애니메이션의 프레임(frame)</h4>
<p>사람의 눈은 적게는 초당 10프레임에서, 집중한 때에 초당 60프레임 정도의 프레임을 볼 수 있다고 한다. 그 이상의 프레임을 찍어내더라도 사람의 눈으로는 체감하기 힘들다는 것이다.</p>
<p>그래서 자바스크립트로 애니메이션을 구현할때도 1초에 60프레임 정도를 찍어내면 된다.
그 말은, 1프레임을 찍어내는데 16.6ms(1000ms / 60frame)를 넘겨서는 안된다는 말이다.</p>
<p>자바스크립트를 활용한 애니메이션에서 16.6ms마다 프레임을 찍어내기 위해 사용할 수 있는 방법은 setInterval과 requestAnimationFrame이 있다. 또한 빈번하게 호출되는 이벤트 핸들러에는 보통 3~4ms 정도로 실행을 마치게끔 해야한다. 그래야, 자바스크립트 실행 이후 리플로우 과정까지 총 16ms내에 프레임을 찍어낼 수 있게 된다.</p>
<br>

<h4 id="-requestanimationframeraf"># requestAnimationFrame(rAF)</h4>
<p>함수를 반복할 때 사용할 수 있는 메서드이다. 애니메이션을 구현할 때 사용되며 일반적으로 재귀적인 호출 방식을 통해 반복한다. 보통 setInterval 혹은 setTimeout과 비교된다.</p>
<pre><code class="language-js">requestAnimationFrame(반복할 함수)</code></pre>
<p>콜백으로 반복할 함수를 전달받는다. requestAnimationFrame가 직접 출력하는 값은 콜백 리스트의 항목을 정의하는 고유한 요청 id 인 long 정수 값을 출력한다. 이 값은 0 이 아니라는것 외에는 다른 추측을 할 수가 없는 값이므로 cancelAnimationFrame에 전달해 콜백 요청을 취소하는 경우에만 사용한다.</p>
<p>requestAnimationFrame 메서드의 특징은 아래와 같다.</p>
<ul>
<li><p>최대 호출 횟수 : Web API에서 동작하며 최대 1초에 60번 동작한다. 다수 애니메이션에도 각각 타이머를 생성하지 않고 동일한 타이머를 참조하게 된다.</p>
</li>
<li><p>rAF는 시간이 아닌 repaint 시점을 기준으로 호출 : 다음 리페인트가 진행되기 전에 함수를 호출 시키기 때문에 프레임 누락을 방지할 수 있다. 즉 프레임 시작 시간에 애니메이션 움직임을 업데이트하는 함수를 시작하게 되는 것이다. 애니메이션 함수의 실행과 실제 픽셀을 채우는 리페인트하는 과정에 순서를 보장하여 프레임 누락을 방지할 수 있는 것이다.</p>
</li>
<li><p>백그라운드 동작 및 비활성화시 중지 : WebAPI에서 실행되며 브라우저창이 숨겨지거나 최소화되어 보여지지 않는 경우 애니메이션을 중지시키고 보여질 때 다시 실행시킨다. 이를 통해 CPU 리소스를 절약할 수 있다.</p>
</li>
</ul>
<br>

<h4 id="-cancelanimationframecaf"># cancelAnimationFrame(cAF)</h4>
<p>setTimeout의 clearTimeout과 같이 반복되는 requestAnimationFrame 함수를 정지시킬 때 사용한다.</p>
<pre><code class="language-js">const animation = requestAnimationFrame(callback)

cancelAnimationFrame(animation)</code></pre>
<br>

<h3 id="-useref"># useRef</h3>
<p>지금까지 useRef는 DOM에 직접 접근하는 경우에만 사용하였다. 하지만 이와 같은 타이머 같은 기능을 구현할 때도 사용할 수 있다. useRef가 반환하는 ref 객체의 current 프로퍼티에 값을 저장하게 되면 리렌더링에 관계없이 값을 유지시킬 수 있다. 계속 구독하여 사용해야하는 값이라면 useRef ref객체의 current 프로퍼티를 사용할 수 있다.</p>
<pre><code class="language-js">const ref = useRef(0)

ref.current = ref.current += 1</code></pre>
<p>구현한 슬롯 카운트 기능에서는 setState에 담긴 현재 카운트 값이 변화될 때 마다 리렌더링이 발생하는데 리렌더링 시 카운트 값을 현재 값으로 유지하기 위해 ref 객체의 current 프로퍼티를 사용하였다.</p>
<br>

<h3 id="-구현-코드"># 구현 코드</h3>
<p>아래는 커스텀 훅으로 작성한 슬롯 카운트 기능 구현 코드이다.</p>
<pre><code class="language-ts">import { useEffect, useRef, useState } from &#39;react&#39;

const useCounter = (maxCount: number) =&gt; {
  const [count, setCount] = useState(0)
  const ref = useRef(0)

  const countEasingOut = (progressRate: number) =&gt; {
    const remainder = Math.pow(2, -10 * progressRate)
    const maxProgress = 1

    if (progressRate === maxProgress) {
      return maxProgress
    } else {
      return maxProgress - remainder
    }
  }

  useEffect(() =&gt; {
    // 0. 마운트 후 useEffect 실행
    const onCount = () =&gt; {
      const duration = 2000
      const frameTimeout = 1000 / 60
      const totalFrame = Math.round(duration / frameTimeout)
      const percentage = countEasingOut(ref.current / totalFrame)
      const currentCount = Math.round(maxCount * percentage)

      setCount(currentCount)

      ref.current = ref.current += 1
      const counting = requestAnimationFrame(onCount)

      if (maxCount === currentCount) {
        cancelAnimationFrame(counting)
      }
    }

    const counting = requestAnimationFrame(onCount)

    return () =&gt; {
      cancelAnimationFrame(counting)
    }
  }, [maxCount])

  return count
}

export default useCounter</code></pre>
<p>컴포넌트 마운트 시 카운트가 시작되도록 useEffect hook을 사용하였고 state 변경으로 인한 리렌더링 시 카운트 값을 유지하기 위해 useRef hook을 사용하였다.</p>
<p>카운트 함수 호출 시 ref 객체의 current 프로퍼티 값을 증가시켜 카운트가 점점 증가되도록 구현하였으며 해당 값을 최대 프레임과 비교하여 애니메이션 진행율을 구한 뒤 퍼센티지 값으로 변환하여 여러 개의 카운트 값이 동일한 비율로 증가하고 동시에 종료되도록 구현하였다.</p>
<p>requestAnimationFrame 메서드를 활용하여 카운트 함수를 반복 실행하였으며 카운트가 최대 값에 도달하였을 경우 cancelAnimationFrame 메서드를 활용하여 반복 실행을 취소하였다.</p>
<br>

<hr>
<p>참고자료</p>
<ul>
<li><a href="https://easings.net/ko#easeOutExpo" target='_blank'>easeOutExpo</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[react] useReducer hook 사용법]]></title>
            <link>https://velog.io/@y_jem/react-useReducer-hook</link>
            <guid>https://velog.io/@y_jem/react-useReducer-hook</guid>
            <pubDate>Sat, 25 Jun 2022 08:27:25 GMT</pubDate>
            <description><![CDATA[<h2 id="-usereducer-hook-사용법"># useReducer hook 사용법</h2>
<h3 id="-usereducer란"># useReducer란?</h3>
<p>useReducer는 useState와 같이 state를 관리하고 업데이트할 수 있는 hook이다. useState와의 차이는 예를 들어 컴포넌트에서 관리하는 값이 딱 하나고, 그 값이 단순한 숫자, 문자열 또는 boolean 값이라면 확실히 useState 로 관리하는게 편할 수 있고 만약에 컴포넌트에서 관리하는 값이 여러개가 되어서 상태의 구조가 복잡해진다면 useReducer로 관리하는 것이 편할 수 있다.</p>
<h3 id="-usereducer의-장점"># useReducer의 장점</h3>
<p>useReducer의 장점으로는 한 컴포넌트 내에서 State를 업데이트하는 로직 부분을 그 컴포넌트로부터 분리시키는 것을 가능하게 해준다는 것이다. 그렇게 useReducer는 State 업데이트 로직을 분리하여 컴포넌트의 외부에 작성하는 것을 가능하게 함으로써, 코드의 최적화를 이루게 해준다는 장점이 있다.</p>
<h3 id="-usereducer-사용법"># useReducer 사용법</h3>
<p>useReducer의 사용법으로는 useReducer의 첫번째 인자로 reducer 함수, 두번째 인자는 초기값을 넣어준다. 첫번째 인자로 넘어오는 reducer 함수 내부에 switch문으로 액션을 정의하고 dispatch를 통해 reducer 함수 내부에 case를 비교하여 액션을 실행시킨다.</p>
<pre><code class="language-js">import { useReducer } from &#39;react&#39;

function reducer(state, action) {
  switch (action.type) {
    case &#39;INCREMENT&#39;:
      return state + 1
    case &#39;DECREMENT&#39;:
      return state - 1
    default:
      return state
  }
}

function App() {
  const [number, dispatch] = useReducer(reducer, 0)

  const onIncrease = () =&gt; {
    dispatch({ type: &#39;INCREMENT&#39; })
  }

  const onDecrease = () =&gt; {
    dispatch({ type: &#39;DECREMENT&#39; })
  }

  return (
    &lt;div&gt;
      &lt;h1&gt;{number}&lt;/h1&gt;
      &lt;button type=&#39;button&#39; onClick={onIncrease}&gt;
        +1
      &lt;/button&gt;
      &lt;button type=&#39;button&#39; onClick={onDecrease}&gt;
        -1
      &lt;/button&gt;
    &lt;/div&gt;
  )
}

export default App
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[etc] 알고리즘 종류]]></title>
            <link>https://velog.io/@y_jem/etc-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%A2%85%EB%A5%98</link>
            <guid>https://velog.io/@y_jem/etc-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%A2%85%EB%A5%98</guid>
            <pubDate>Mon, 20 Jun 2022 13:25:39 GMT</pubDate>
            <description><![CDATA[<h2 id="-알고리즘의-종류"># 알고리즘의 종류</h2>
<h3 id="-탐색search"># 탐색(Search)</h3>
<p>선형 탐색 (Linear Search)
이분(이진) 탐색 (Binary Search)
순차 탐색(Sequential Search)
해시 탐색(Hash Search)</p>
<h3 id="-정렬sorting"># 정렬(Sorting)</h3>
<p>버블 정렬(Bubble Sort)
선택 정렬(Selection Sort)
삽입 정렬(Insertion Sort)
퀵 정렬(Quick Sort)
병합 정렬(Merge Sort)
힙 정렬(Heap Sort)
기수 정렬(Radix Sort)
계수 정렬(Count Sort)</p>
<h3 id="-완전-탐색"># 완전 탐색</h3>
<p>브루트 포스(Brute Force)
비트 마스크(Bit Mask)
백트래킹
재귀함수
순열
BFS(너비 우선 탐색) &amp; DFS(깊이 우선 탐색)</p>
<h3 id="-분할-정복"># 분할 정복</h3>
<h3 id="-동적-계획법dynamic-programming"># *동적 계획법(Dynamic Programming)</h3>
<h3 id="-그리디greedy"># *그리디(Greedy)</h3>
<h3 id="-트리"># 트리</h3>
<p>이진 검색 트리(Binary Search Tree)</p>
<h3 id="-그래프"># 그래프</h3>
<p>위상 정렬</p>
<h3 id="-최단-경로"># 최단 경로</h3>
<p>Floyd-Warshall
다익스트라(Dijkstra)
Bellman-Ford</p>
<h3 id="-최소-신장트리mst"># 최소 신장트리(MST)</h3>
<p>Kruskal
Prim</p>
<h3 id="-기타"># 기타</h3>
<p>비트 연산
진수 변환
재귀(Recursion)
유클리드 호제법(최대공약수, 최소공배수)
투포인터(슬라이딩 윈도우)
조합, 순열
파라메트릭 서치
최장 증가 수열(LIS)
최소 공통 조상(LCA)
비트 마스크(BitMask)
Matching Parenthesis problem
Variables / Pointers manipulation
reverse linked list(duplicates, removing duplicates)
Custom data structures(Object oriented programming)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] 빌드 시 "Module not found: Error: Can't resolve 'stream..." 에러]]></title>
            <link>https://velog.io/@y_jem/React-%EB%B9%8C%EB%93%9C-%EC%8B%9C-webpack-5-used-to-include-polyfills...-%EC%97%90%EB%9F%AC</link>
            <guid>https://velog.io/@y_jem/React-%EB%B9%8C%EB%93%9C-%EC%8B%9C-webpack-5-used-to-include-polyfills...-%EC%97%90%EB%9F%AC</guid>
            <pubDate>Sat, 04 Jun 2022 16:16:51 GMT</pubDate>
            <description><![CDATA[<h2 id="-빌드-시-에러-발생"># 빌드 시 에러 발생</h2>
<p>kpa pedia 프로젝트를 netlify로 배포하는데 아래와 같은 문제가 발생했다.
<img src="https://velog.velcdn.com/images/y_jem/post/b7dc34b9-c4f1-4e16-b4fb-50add7b52933/image.png" alt=""></p>
<br>

<h3 id="-해결-방법"># 해결 방법</h3>
<h4 id="-1-필요-패키지-설치"># 1. 필요 패키지 설치</h4>
<p>해결에 필요한 패키지를 설치한다. 필요 없는 패키지가 아래 목록 중 포함 되어있을 수 있다.</p>
<blockquote>
<p>yarn add url webpack-cli stream-http stream-browserify react-app-rewired os-browserify os https-browserify fs crypto-browserify buffer assert process</p>
</blockquote>
<br>

<h4 id="-2-packagejson-수정"># 2. package.json 수정</h4>
<p>package.json에 script 부분을 수정한다.</p>
<pre><code class="language-json">// package.json
...
  &quot;scripts&quot;: {
    &quot;start&quot;: &quot;react-app-rewired start&quot;,
    &quot;build&quot;: &quot;react-app-rewired build&quot;,
    &quot;test&quot;: &quot;react-app-rewired test&quot;,
    &quot;eject&quot;: &quot;react-app-rewired eject&quot;
  },
...</code></pre>
<br>

<h4 id="-3-config-overridesjs-생성"># 3. config-overrides.js 생성</h4>
<p>root 경로(package.json과 같은 위치)에 config-overrides.js를 생성한다.</p>
<pre><code class="language-js">// config-overrides.js
module.exports = {
  // The Webpack config to use when compiling your react app for development or production.
  webpack: function (config, env) {
    const overridedConfig = {
      ...config,
      resolve: {
        ...config.resolve,
        fallback: {
          ...config.resolve.fallback,
          fs: false,
          net: false,
          stream: require.resolve(&#39;stream-browserify&#39;),
          crypto: require.resolve(&#39;crypto-browserify&#39;),
          http: require.resolve(&#39;stream-http&#39;),
          https: require.resolve(&#39;https-browserify&#39;),
          os: require.resolve(&#39;os-browserify/browser&#39;),
          url: require.resolve(&#39;url&#39;),
        },
      },
    };
    return overridedConfig;
  },
};</code></pre>
<br>

<h4 id="-4-webpackconfigjs-생성"># 4. webpack.config.js 생성</h4>
<p>root 경로(package.json과 같은 위치)에 webpack.config.js를 생성한다.</p>
<pre><code class="language-js">const webpack = require(&#39;webpack&#39;);

module.exports = {
  plugins: [
    new webpack.ProvidePlugin({
      process: &#39;process/browser&#39;,
    }),
  ],
};</code></pre>
<p>webpack polyfills 이슈과 연간이 있다. react-app-rewired 패키지 이용해서 웹팩 오버라이딩 해주니 정상적으로 빌드되었다.</p>
<p><img src="https://velog.velcdn.com/images/y_jem/post/3c728d3e-13ea-441a-82e7-187bbc40556d/image.png" alt=""></p>
<h4 id="-추가-에러-해결"># 추가 에러 해결</h4>
<blockquote>
<p>위 에러를 해결한 뒤 라우팅 에러를 처리하는데 또 다시 에러가 발생하였다. axios의 return값이 index.html 자체를 반환하는 에러였다. 알고 보니 public/_redirects에 만들어놓은 파일이 문제였다. 이 파일을 지우고 프로젝트 최상위 경로에 netlify.toml 파일에 아래와 같이 수정하여 해결하였다.</p>
</blockquote>
<pre><code class="language-toml">[[redirects]]
  from = &quot;/proxy/*&quot;
  to = &quot;https://www.kopis.or.kr/:splat&quot;
  status = 200
  force = true

[[redirects]]
  from = &quot;/*&quot;
  to = &quot;/index.html&quot;
  status = 200</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[react] modal Portal로 사용하기]]></title>
            <link>https://velog.io/@y_jem/react-modal-Portals%EB%A1%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@y_jem/react-modal-Portals%EB%A1%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 01 Jun 2022 23:42:20 GMT</pubDate>
            <description><![CDATA[<h2 id="-modal-portals로-사용하기"># modal Portals로 사용하기</h2>
<p>기존의 리액트에서 컴포넌트를 렌더링 하게 될 때, children 은 부모컴포넌트의 DOM 내부에 렌더링 되어야 했지만 Portals 를 사용하면 DOM 의 계층구조 시스템에 종속되지 않으면서 컴포넌트를 렌더링 할 수 있다.</p>
<p>modal 사용 시 다른 컴포넌트와 겹치거나 css속성인 z-index를 신경써야 한다는 문제점이 발생할 수 있다. 이러한 문제를 Portal을 통해 해결할 수 있다.</p>
<br>

<h3 id="-코드-구현"># 코드 구현</h3>
<h4 id="-1-modal이-생성-될-위치에-div-생성"># 1. modal이 생성 될 위치에 div 생성</h4>
<p>public/index.html에 root div하단에 modal_root div를 생성한다.</p>
<pre><code class="language-html">  &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;div id=&quot;modal-root&quot;&gt;&lt;/div&gt;
    &lt;!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the &lt;body&gt; tag.

      To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    --&gt;
  &lt;/body&gt;</code></pre>
<br>

<h4 id="-2-modal-potal-만들기"># 2. modal Potal 만들기</h4>
<p>모달 포탈을 만들어준다.</p>
<pre><code class="language-tsx">// src/components/ModalPortal.ts
import { ReactNode } from &#39;react&#39;;
import ReactDom from &#39;react-dom&#39;;

interface Props {
  children: ReactNode;
}

const ModalPortal = ({ children }: Props) =&gt; {
  const el = document.getElementById(&#39;modal-root&#39;) as HTMLElement;

  return ReactDom.createPortal(children, el);
};

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

<h4 id="-3-실제-사용할-modal-만들기"># 3. 실제 사용할 modal 만들기</h4>
<p>실제 사용 될 modal을 만들어준다. props로 전달받은 값은 modal 생성 후 닫기 위해 close 이벤트를 props로 전달받는다.</p>
<pre><code class="language-tsx">// src/components/LoginModal/LoginModal.tsx
import styles from &#39;./loginModal.module.scss&#39;;

const LoginModal = ({ onClose }: any) =&gt; {
  return (
    &lt;div className={styles.loginModal}&gt;
      &lt;div className={styles.content}&gt;
        &lt;h3&gt;아이디와 비밀번호 모두 입력해주세요.&lt;/h3&gt;
        &lt;button type=&#39;button&#39; onClick={onClose}&gt;
          닫기
        &lt;/button&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
};

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

<h4 id="-4-portal을-사용하여-modal-사용하기"># 4. Portal을 사용하여 modal 사용하기</h4>
<p>2.에 생성해놓은 ModalPortal로 실제 사용할 modal인 LoginModal를 감싸준다.</p>
<pre><code class="language-tsx">import { useState } from &#39;react&#39;;
import { useNavigate } from &#39;react-router-dom&#39;;
import { useRecoilState } from &#39;recoil&#39;;
import { bookMarkList } from &#39;states/atom&#39;;
import LoginModal from &#39;components/Modal/LoginModal/LoginModal&#39;;
import ModalPortal from &#39;components/Modal/ModalPortal&#39;;

const Login = () =&gt; {
  const [userInfo, setUserInfo] = useState({ id: &#39;&#39;, pw: &#39;&#39; });
  const [userId, setUserId] = useRecoilState(bookMarkList);
  const [modalOpen, setModalOpen] = useState&lt;boolean&gt;();

  const navigate = useNavigate();

  const HandleUserInfo = (event: any) =&gt; {
    const { name, value } = event.currentTarget;

    const nextInput = { ...userInfo, [name]: value };
    setUserInfo(nextInput);
  };

  const HandleLoginCheck = () =&gt; {
    const { id, pw } = userInfo;
    const foamCheck = !id || !pw;

    if (foamCheck) {
      setModalOpen(true);
      return;
    }

    navigate(&#39;/&#39;);
  };

  const HandleModalShow = () =&gt; {
    setModalOpen(false);
  };

  return (
    &lt;div className={styles.login}&gt;
      &lt;input type=&#39;text&#39; name=&#39;id&#39; onChange={HandleUserInfo} /&gt;
      &lt;input type=&#39;password&#39; name=&#39;pw&#39; onChange={HandleUserInfo} /&gt;
      &lt;button type=&#39;button&#39; onClick={HandleLoginCheck}&gt;
        로그인
      &lt;/button&gt;
      {modalOpen &amp;&amp; (
        &lt;ModalPortal&gt;
          &lt;LoginModal onClose={HandleModalShow} /&gt;
        &lt;/ModalPortal&gt;
      )}
    &lt;/div&gt;
  );
};

export default Login;</code></pre>
<p>위와 같이 감싸서 사용하게 되면 개발자 도구에서 아래와 같이 root div 바로 하단에 modal이 생성된 것을 확인할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/y_jem/post/511353bd-3832-410f-841a-2aed8e3ee208/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Node.js] React와 Node.js연동]]></title>
            <link>https://velog.io/@y_jem/Node.js-React%EC%99%80-Node.js%EC%97%B0%EB%8F%99</link>
            <guid>https://velog.io/@y_jem/Node.js-React%EC%99%80-Node.js%EC%97%B0%EB%8F%99</guid>
            <pubDate>Tue, 31 May 2022 19:51:36 GMT</pubDate>
            <description><![CDATA[<h2 id="-react와-nodejs연동"># React와 Node.js연동</h2>
<br>

<h3 id="-연동-과정"># 연동 과정</h3>
<h4 id="-1-node-express-설치"># 1. node express 설치</h4>
<blockquote>
<p>yarn add express</p>
</blockquote>
<br>

<h4 id="-2-프로젝트-root-경로에-server-디렉토리와-serverjs-생성"># 2. 프로젝트 root 경로에 server 디렉토리와 server.js 생성</h4>
<pre><code class="language-js">// server/server.js
const express = require(&#39;express&#39;);

const app = express();
const api = require(&#39;./routes/index&#39;);
const cors = require(&#39;cors&#39;);

app.use(cors());
app.use(&#39;/api&#39;, api);

const PORT = 3001;
app.listen(PORT, () =&gt; console.log(&#39;Node.js Server is running on port 3001&#39;));</code></pre>
<p>서버 생성 코드이다. 포트번호는 충돌을 피하기 위해 3001번으로 할당하였으며 &#39;/api&#39;의 경로 요청을 라우팅해주었다.</p>
<br>

<h4 id="-3-server-디렉토리에-routes-디렉토리-생성-후-indexjs-파일-생성"># 3. server 디렉토리에 routes 디렉토리 생성 후 index.js 파일 생성</h4>
<pre><code class="language-js">// server/routes/index.js
const express = require(&#39;express&#39;);

const router = express.Router();

router.get(&#39;/&#39;, (req, res) =&gt; {
  res.send({ title: &#39;hello react!&#39; });
});

module.exports = router;</code></pre>
<br>

<h4 id="-4-proxy-설정을-위한-모듈-설치"># 4. proxy 설정을 위한 모듈 설치</h4>
<blockquote>
<p>yarn add http-proxy-middleware</p>
</blockquote>
<br>

<h4 id="-5-src-디렉토리-안에-setupproxyjs-파일-생성"># 5. src 디렉토리 안에 setupProxy.js 파일 생성</h4>
<pre><code class="language-js">// src/setupProxy.js
const proxy = require(&#39;http-proxy-middleware&#39;);

module.exports = function (app) {
  app.use(
    proxy(&#39;/api&#39;, {
      target: &#39;http://localhost:3001/&#39;,
    })
  );
};</code></pre>
<p>설정된 프록시는 클라이언트 사이드에서 Node.js 서버 사이드인 <code>http://localhost:3001/api</code>로의 요청을 처리하여 서버 데이터를 가져올 수 있도록 해준다.</p>
<br>

<h4 id="-6-클라이언트서버-사이드-실행"># 6. 클라이언트/서버 사이드 실행</h4>
<blockquote>
<p><strong>- 서버 사이드</strong> : node ./server/server.js
<strong>- 클라이언트 사이드</strong> : yarn start</p>
</blockquote>
<br>

<h4 id="-7-http-통신-메소드를-활용하여-요청-보내기"># 7. http 통신 메소드를 활용하여 요청 보내기</h4>
<pre><code class="language-js">useEffect(() =&gt; {
  axios.get(&#39;http://localhost:3001/api&#39;).then((res) =&gt; {
    console.log(res);
  });
});</code></pre>
<br>

<h4 id="-8-성공-시-이미지"># 8. 성공 시 이미지</h4>
<ul>
<li>콘솔 이미지
<img src="https://velog.velcdn.com/images/y_jem/post/5cabfc89-7313-4b8a-ae88-cc8d62bb2ddd/image.png" alt=""></li>
<li>node 실행 중인 터미널
<img src="https://velog.velcdn.com/images/y_jem/post/6c667b7d-134c-444b-b740-59c2e7a96e92/image.png" alt=""></li>
</ul>
<blockquote>
<p><strong>! cors에러 발생</strong> : yarn add cors로 모듈 설치 후 server.js에 아래 코드 추가</p>
</blockquote>
<pre><code class="language-js">// server.js
const cors = require(&#39;cors&#39;);
app.use(cors());</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Typescript] input 입력 값 날짜 형식으로 자동 변경]]></title>
            <link>https://velog.io/@y_jem/Typescript-input-%EC%9E%85%EB%A0%A5-%EA%B0%92-%EB%82%A0%EC%A7%9C-%ED%98%95%EC%8B%9D%EC%9C%BC%EB%A1%9C-%EC%9E%90%EB%8F%99-%EB%B3%80%EA%B2%BD</link>
            <guid>https://velog.io/@y_jem/Typescript-input-%EC%9E%85%EB%A0%A5-%EA%B0%92-%EB%82%A0%EC%A7%9C-%ED%98%95%EC%8B%9D%EC%9C%BC%EB%A1%9C-%EC%9E%90%EB%8F%99-%EB%B3%80%EA%B2%BD</guid>
            <pubDate>Sun, 29 May 2022 07:31:07 GMT</pubDate>
            <description><![CDATA[<h2 id="-input-입력-값-날짜-형식으로-자동-변경"># input 입력 값 날짜 형식으로 자동 변경</h2>
<p>input의 입력 값을 YYYY-MM-DD 형식으로 자동 변환하는 코드이다.
사실 아래와 같은 코드 외에도 input의 type을 date로 설정하여 사용자가 날짜를 선택할 수 있도록 구현할 수 있다. 또 datepicker와 같은 관련 라이브러리를 사용해도 된다.
아래 코드는 input의 type이 text인 경우 입력 값을 YYYY-MM-DD 형식으로 자동으로 변환해주는 코드이다.</p>
<br>

<h3 id="-구현-코드"># 구현 코드</h3>
<pre><code class="language-ts">  const handleDateChange = (event: ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
    const { value } = event.currentTarget
    const RegNotNum = /[^0-9]/g
    const onlyNum = value.replace(RegNotNum, &#39;&#39;) // 숫자가 아닌 경우 &#39;&#39;

    let DataFormat: any
    let RegDateFmt: any

    if (onlyNum.length &lt;= 6) { // 000000 -&gt; 0000-00
      DataFormat = &#39;$1-$2&#39;
      RegDateFmt = /([0-9]{4})([0-9]+)/
    } else if (onlyNum.length &lt;= 8) { // 00000000 -&gt; 0000-00-00
      DataFormat = &#39;$1-$2-$3&#39;
      RegDateFmt = /([0-9]{4})([0-9]{2})([0-9]+)/
    }

    const newDate = onlyNum.replace(RegDateFmt, DataFormat)

    setDate(newDate) // YYYY-MM-DD
  }</code></pre>
<p>input에 onChange이벤트를 걸어 해당 함수를 실행시켰다. input 입력되는 값을 value로 디스트럭쳐링으로 받은 뒤 해당 value를 정규식을 통해 숫자만 감지하도록 하여 숫자가 아닌 경우는 제거하고 숫자인 경우 새로운 변수인 onlyNum에 할당하였다. 그 후 if문을 통해 입력 값이 6자리보다 적은 경우, 8자리보다 적은 경우 형식을 변경하여 newDate에 담도록 한 뒤 state에 할당해주었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[POB] POB TIL - 8]]></title>
            <link>https://velog.io/@y_jem/POB-POB-TIL-8</link>
            <guid>https://velog.io/@y_jem/POB-POB-TIL-8</guid>
            <pubDate>Tue, 17 May 2022 09:55:02 GMT</pubDate>
            <description><![CDATA[<h2 id="-today-i-learn---20220516"># Today I Learn - 2022.05.16</h2>
<h3 id="-pre-onboarding-course"># Pre Onboarding Course</h3>
<h4 id="리액트-쿼리"><strong>리액트 쿼리</strong></h4>
<ul>
<li><strong>리액트 쿼리의 장점</strong></li>
</ul>
<ol>
<li>캐싱</li>
<li>get을 한 데이터에 대해 update를 하면 자동으로 get을 다시 수행 (예를 들면 게시판의 글을 가져왔을 때 게시판의 글을 생성하면 게시판 글을 get하는 api를 자동으로 실행)</li>
<li>데이터가 오래 되었다고 판단되면 다시 get (invalidateQueries)</li>
<li>동일 데이터 여러번 요청하면 한번만 요청 (옵션에 따라 중복 호출 허용 시간 조절 가능)</li>
<li>무한 스크롤 (Infinite Queries (opens new window))</li>
<li>비동기 과정을 선언적으로 관리</li>
<li>react hook과 사용하는 구조가 비슷함</li>
</ol>
<br>

<ul>
<li><strong>리액트 쿼리 세팅</strong></li>
</ul>
<ol>
<li>react프로젝트에서 yarn install react-query &amp;&amp; npm i react-query로 패키지 설치</li>
<li>가장 기본이 되는 index.js에 아래와 같이 QueryClient, QueryClientProvider, ReactQueryDevtools를 import한 후 컴포넌트 배치</li>
</ol>
<pre><code class="language-tsx">// root index.js
import { QueryClient, QueryClientProvider } from &quot;react-query&quot;;
import { ReactQueryDevtools } from &quot;react-query/devtools&quot;;

const queryClient = new QueryClient();

const root = ReactDOM.createRoot(
  document.getElementById(&quot;root&quot;) as HTMLElement
);
root.render(
  &lt;React.StrictMode&gt;
    &lt;QueryClientProvider client={queryClient}&gt;
      &lt;ReactQueryDevtools initialIsOpen /&gt;
      &lt;Routes /&gt;
    &lt;/QueryClientProvider&gt;
  &lt;/React.StrictMode&gt;
);</code></pre>
<br>

<ul>
<li><strong>useQuery</strong> : 데이터를 get 하기 위한 api. post, update는 useMutation을 사용</li>
</ul>
<br>

<ul>
<li><strong>useQuery 사용법</strong></li>
</ul>
<ol>
<li>첫번째 파라미터는 unique Key, 두번째 파라미터는 비동기 함수(api호출 함수(promise))</li>
<li>첫번째 파라미터 unique Key를 사용하면 다른 컴포넌트에서도 호출 가능</li>
<li>unique Key는 string과 배열을 받음. 배열로 넘기면 0번 값은 string값으로 다른 컴포넌트에서 부를 값이 들어가고 두번째 값을 넣으면 query 함수 내부에 파라미터로 해당 값이 전달</li>
<li>return 값은 api의 성공&amp;실패 여부, api return 값을 포함한 객체</li>
<li>useQuery는 비동기로 작동. 즉, 한 컴포넌트에 여러개의 useQuery가 있다면 하나가 끝나고 다음 useQuery가 실행되는 것이 아닌 두 개의 useQuery가 동시에 실행. 여러개의 비동기 query가 있다면 useQuery보다는 useQueries권장</li>
<li>enabled를 사용하면 useQuery를 동기적으로 사용 가능</li>
</ol>
<br>

<ul>
<li><strong>useQuery 문법</strong> : 아래는 useQuery 기본 문법이다.</li>
</ul>
<pre><code class="language-tsx">const result = useQuery({
  queryKey,
  queryFn,
  enabled,
});</code></pre>
<br>

<ul>
<li><strong>useQuery 사용 예시</strong> : 아래는 useQuery 사용 예시이다.</li>
</ul>
<pre><code class="language-tsx">import { useQuery } from &quot;react-query&quot;;

const Test = () =&gt; {
  // fetch api 함수
  const HandleFetch = (): Promise&lt;any&gt; =&gt; {
    return fetch(&quot;/mock.json&quot;, {}); // promise를 반환해야 하기 떄문에 return해준다.
  };

  // useQuery 사용
  const { isLoading, isError, data, error } = useQuery(
    [&quot;HandleFetch&quot;, params1, params2]], // string은 unique Key,
    () =&gt; HandleFetch() .then((res) =&gt; res.json())
        .then((res) =&gt; setTest(res)),
    {
      refetchOnWindowFocus: false, // 사용자가 사용하는 윈도우가 다른 곳을 갔다가 다시 화면으로 돌아오면 이 함수를 재실행 하는 지 여부 설정
      retry: 0, // 호출 실패 시 재호출 몇번 할지
      suspense: true, // suspense 컴포넌트를 사용하여 isLoading을 대체할 수 있다.
      useErrorBoundary: true, // useErrorBoundary 컴포넌트를 사용하여 isError를 대체할 수 있다.
      // refetchInterval: 3000, // 해당 시간마다 반복하여 호출
      onSuccess: () =&gt; {
        console.log(test); // 성공 시 실행
      },
      onError() {
        console.log(error); // 실패 시 실행 (401, 404 같은 error가 아니라 정말 api 호출이 실패한 경우만 호출)
      },
    }
  );

  return (
    &lt;ul&gt;
      {isLoading &amp;&amp; &quot;loading...&quot;}
      {isError &amp;&amp; &quot;Error!&quot;}
    &lt;/ul&gt;
  );
};

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

<ul>
<li><strong>useQuery suspense와 useErrorBoundary</strong> : useQuery의 옵션 중 suspense와 useErrorBoundary를 true로 설정하면 Suspense 컴포넌트와 useErrorBoundary 컴포넌트를 사용할 수 있다.</li>
</ul>
<pre><code class="language-tsx">import { useQuery } from &quot;react-query&quot;;
import { Suspense } from &quot;react&quot;;
import { ErrorBoundary } from &quot;react-error-boundary&quot;;

const Test = () =&gt; {
  const HandleFetch = (): Promise&lt;any&gt; =&gt; {
    return fetch(&quot;/mock.json&quot;, {});
  };

  const { data } = useQuery(
    [&quot;HandleFetching&quot;],
    () =&gt;
      HandleFetch()
        .then((res) =&gt; res.json())
        .then((res) =&gt; setTest(res)),
    {
      refetchOnWindowFocus: false,
      retry: 0,
      suspense: true, // suspense 컴포넌트를 사용하여 isLoading을 대체할 수 있다.
      useErrorBoundary: true, // useErrorBoundary 컴포넌트를 사용하여 isError를 대체할 수 있다.
    }
  );

  // 아래와 같이 Suspense, ErrorBoundary 컴포넌트를 사용하며 true가 되면 fallback 컴포넌트가 렌더링 된다.
  return (
    &lt;ul&gt;
      &lt;Suspense fallback={&lt;LoadingPage /&gt;}&gt;
        &lt;ErrorBoundary fallbackRender={&lt;ErrorPage /&gt;}&gt;
          &lt;ul&gt;
            {data.map((el) =&gt; (
              &lt;li key={el.id}&gt;{el.name}&lt;/li&gt;
            ))}
          &lt;/ul&gt;
        &lt;/ErrorBoundary&gt;
      &lt;/Suspense&gt;
    &lt;/ul&gt;
  );
};</code></pre>
<br>

<ul>
<li><strong>useQuery 그 외의 옵션들</strong></li>
</ul>
<pre><code class="language-tsx">const {
   data,
   dataUpdatedAt,
   error,
   errorUpdatedAt,
   failureCount,
   isError,
   isFetched,
   isFetchedAfterMount,
   isFetching,
   isIdle,
   isLoading,
   isLoadingError,
   isPlaceholderData,
   isPreviousData,
   isRefetchError,
   isRefetching,
   isStale,
   isSuccess,
   refetch,
   remove,
   status,
 } = useQuery(queryKey, queryFn?, {
   cacheTime,
   enabled,
   initialData,
   initialDataUpdatedAt
   isDataEqual,
   keepPreviousData,
   meta,
   notifyOnChangeProps,
   notifyOnChangePropsExclusions,
   onError,
   onSettled,
   onSuccess,
   placeholderData,
   queryKeyHashFn,
   refetchInterval,
   refetchIntervalInBackground,
   refetchOnMount,
   refetchOnReconnect,
   refetchOnWindowFocus,
   retry,
   retryOnMount,
   retryDelay,
   select,
   staleTime,
   structuralSharing,
   suspense,
   useErrorBoundary,
 })</code></pre>
<br>

<ul>
<li><p><strong>useQueries</strong> : 여러 개의 비동기를 처리할 경우 사용할 수 있다. promise.all과 마찬가지로 하나의 배열에 각 쿼리에 대한 상태 값이 객체로 들어온다.</p>
</li>
<li><p><strong>proxy로 우회하기</strong> : CORS에러를 피하기 위해 package.json에 &quot;proxy&quot; 값을 설정하여 우회할 수 있다.</p>
</li>
</ul>
<pre><code class="language-json">// package.json
{
    ...
    &quot;prettier&quot;: &quot;^2.6.2&quot;,
    &quot;sass&quot;: &quot;^1.51.0&quot;,
    &quot;sass-loader&quot;: &quot;^12.6.0&quot;,
    &quot;style-loader&quot;: &quot;^3.3.1&quot;,
    &quot;stylelint&quot;: &quot;^14.8.1&quot;,
    &quot;stylelint-config-recess-order&quot;: &quot;^3.0.0&quot;,
    &quot;stylelint-config-standard-scss&quot;: &quot;^3.0.0&quot;,
    &quot;stylelint-declaration-strict-value&quot;: &quot;^1.8.0&quot;,
    &quot;typescript&quot;: &quot;^4.4.2&quot;
  },
  &quot;proxy&quot;: &quot;http://apis.data.go.kr/&quot;
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[react] 쓰로틀링(throttling)과 디바운싱(debouncing)]]></title>
            <link>https://velog.io/@y_jem/%EC%93%B0%EB%A1%9C%ED%8B%80%EB%A7%81throttling%EA%B3%BC-%EB%94%94%EB%B0%94%EC%9A%B4%EC%8B%B1debouncing</link>
            <guid>https://velog.io/@y_jem/%EC%93%B0%EB%A1%9C%ED%8B%80%EB%A7%81throttling%EA%B3%BC-%EB%94%94%EB%B0%94%EC%9A%B4%EC%8B%B1debouncing</guid>
            <pubDate>Tue, 17 May 2022 09:29:11 GMT</pubDate>
            <description><![CDATA[<h2 id="-쓰로틀링throttling과-디바운싱debouncing"># 쓰로틀링(throttling)과 디바운싱(debouncing)</h2>
<p>쓰로틀링(throttling)과 디바운싱(debouncing)는 프로그래밍 기법 중 하나이다.</p>
<ul>
<li>쓰로틀링(throttling): 마지막 함수가 호출된 후 일정 시간이 지나기 전에 다시 호출되지 않도록 하는 것, 스크롤을 올리거나 내릴 때 자주 쓰인다.</li>
<li>디바운싱(debouncing): 연이어 호출되는 함수들 중 마지막 함수(또는 제일 처음)만 호출하도록 하는 것, ajax 검색에 자주 쓰인다.</li>
</ul>
<blockquote>
<p>쓰로틀링(throttling)과 디바운싱(debouncing)는 underscore에도 있는 기능이다. underscore나 lodash를 쓴다면 그 라이브러리의 메소드를 쓰는게 편하다.</p>
</blockquote>
<br>

<h3 id="-디바운싱debouncing-구현"># 디바운싱(debouncing) 구현</h3>
<p>검색어를 input에 입력하여 input의 value가 변할 때마다 api를 요청하여 디바운싱(debouncing)을 구현하였다. 디바운싱 사용 전에는 예를 들어 토끼를 검색 시 &#39;ㅌ&#39;, &#39;토&#39;, &#39;톢&#39;, &#39;토끼&#39;와 같이 input에 값이 변할 경우 모두 요청이 된다. (한글같은 조합형 언어는 더 많이 이벤트가 발생할 수도 있다).</p>
<pre><code class="language-tsx">// 디바운싱 구현
  const [query, setQuery] = useState(&#39;&#39;); // 디바운싱 처리 후 input을 저장하는 state
  const [searchText, setSearchText] = useState&lt;string&gt;(query); // input 입력 값을 그대로 저장하는 state

  // 검색어 저장
  const HandleSearchWord = (event: ChangeEvent&lt;HTMLInputElement&gt;): void =&gt; {
    setSearchText(event.currentTarget.value);
  };

  // 디바운스 구현
  useEffect(() =&gt; {
    const debounce = setTimeout(() =&gt; {
      return setQuery(searchText); // input 값을 저장한 state를 새로운 state에 담는다. 즉 마지막으로 호출될 때의 input value값을 넣는 것이다.
    }, 300); // setTimeout 설정, 이와 같은 경우 최소 300밀리초마다 요청을 보낸다.
    return () =&gt; clearTimeout(debounce); // clearTimeout 바로 타이머 제거
  }, [searchText]); // 결국 마지막 이벤트에만 setTimeout이 실행된다.

  // react query api 호출
  const { data, isLoading } = useQuery(
    [&#39;diseaseApi&#39;, query],
    () =&gt; diseaseApi({ searchText: query }).then((res) =&gt; res.data.response.body.items.item),
    {
      refetchOnWindowFocus: true,
      staleTime: 3000,
      cacheTime: Infinity,
      onSuccess: HandleAPICounter,
      onError: HandleAPICounter,
    }
  );

  return (
    ...
          &lt;form&gt;
            &lt;input type=&#39;text&#39; value={searchText} onChange={HandleSearchWord} /&gt;
          &lt;/form&gt;
    ...</code></pre>
<br>

<h3 id="-쓰로틀링throttling-구현"># 쓰로틀링(throttling) 구현</h3>
<pre><code class="language-tsx">function SearchResult({ query }: searchResultProp) {
  const [page, setPage] = useState(1);
  const [result, setResult] = useState&lt;Array&lt;any&gt;&gt;([]);
  const [throttle, setThrottle] = useState(false);

  const handleScroll = () =&gt; {
    if (throttle) return;
    if (!throttle) {
      setThrottle(true);
      setTimeout(async () =&gt; {
        setPage((page) =&gt; page + 1);
        setThrottle(false);
      }, 300);
    }
  };

  useEffect(() =&gt; {
    if (query.length === 0 || isEmpty || page === 0) return;
    (async () =&gt; {
      try {
        const data = await getSearchResult(query, page);
        if (data.length) setResult((result) =&gt; 
            (result === data ? [...data] : [...result, ...data]));
      } catch (e) {
        setResult([]);
      }
    })();
  }, [page]);


  return (
    &lt;SearchResultBlock onScroll={handleScroll}&gt;
        {result.map((data: searchDataType) =&gt;&lt;ProductCard key={data.id}/&gt;}
    &lt;/SearchResultBlock&gt;
  );
}</code></pre>
<br>

<hr>
<p>참고자료</p>
<ul>
<li><a href="https://www.zerocho.com/category/JavaScript/post/59a8e9cb15ac0000182794fa" target='_blank'>쓰로틀링과 디바운싱</a></li>
<li><a href="https://velog.io/@skawnkk/debounce-throttle" target='_blank'>Debounce 와 Throttle 리액트로 구현하기</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[POB] POB TIL - 7]]></title>
            <link>https://velog.io/@y_jem/POB-POB-TIL-7</link>
            <guid>https://velog.io/@y_jem/POB-POB-TIL-7</guid>
            <pubDate>Sun, 15 May 2022 14:25:10 GMT</pubDate>
            <description><![CDATA[<h2 id="-today-i-learn---20220515"># Today I Learn - 2022.05.15</h2>
<h3 id="-pre-onboarding-course"># Pre Onboarding Course</h3>
<ul>
<li><p><strong>그립 프로젝트 완료</strong> : 그립 영화 앱 프로젝트를 마무리하였다. react + typescrt, 리코일, 무한 스크롤, 드래그 앤 드롭 등등 좋은 경험이었다.</p>
</li>
<li><p><strong>그립 무비 앱 프로젝트 코드 리뷰</strong> : 그립 무비 앱 프로젝트 코드 리뷰가 진행되었다.</p>
</li>
</ul>
<ol>
<li><strong>텍스트 드래그 막기</strong> : 드래그 앤 드롭 기능 구현 시 타겟 css에 use-select:none을 주면 텍스트가 드래그되는 것을 막을 수 있다. 사용자 경험이 좋아진다.</li>
<li><strong>커스텀 훅의 state</strong> : 커스텀 훅으로 재사용 시 복사 붙여넣기와 같은 개념이라 커스텀 훅 내부에서 사용한 state가 초기화 될 수 있다. 이럴 때 전역 상태 관리 툴을 사용하면 된다.</li>
<li><strong>한 가지 조건 ??연산자</strong> : 한 가진 조건만 사용할 경우 ?? 연산자를 사용한다. (ex &quot;a면 b이다.&quot;)</li>
<li><strong>네트워크 탭에 api 노출</strong> : api 통신시 개발자 도구에서 네트워크 탭에 Header탭을 확인해보면 환경 변수로 적용하여도 api key가 노출된다. AJAX 방식으로 request를 보내는 것이라 이 정보를 숨길 수는 없다고 한다. 이 정보를 숨기기 위해서는 서버리스 백엔드를 이용하는 방법이 있다고 한다. 백엔드가 api와 직접 통신하고 프론트는 백엔드와 통신해 그 정보를 받아오는 것이다. (ex &quot;서버리스 서비스(netlify)&quot;)</li>
<li><strong>국문과 영문의 폰트 적용 순서</strong> : 폰트 적용 순서를 고려해야한다. 국문을 먼저, 영문을 나중에 넣는 것이 좋다. 영문은 국문이 지원안되는 경우가 많기 때문이다.</li>
<li><strong>이미지 태그의 onError</strong> : 이미지 태그의 onError를 사용하여 이미지가 없는 경우 에러 처리를 할 수 있다. (ex &quot;api로 이미지 주소 불러올 때 이미지 주소가 N/A로 오는 경우&quot;)</li>
<li><strong>backdrop-filter속성과 브라우저 지원 범위</strong> : backdrop-filter 속성을 활용하여 블러처리를 할 수 있다.</li>
<li><strong>브라우저 지원 범위</strong> : backdrop-filter와 같이 생소한 css의 경우 can i use(<a href="https://caniuse.com/)%EC%97%90">https://caniuse.com/)에</a> 검색해보며 지원하는 브라우저 범위를 확인해봐야한다.</li>
<li><strong>웹팩의 prefix처리</strong> : prefix로 크로스 브라우징 이슈를 해결하고는 했는데 웹팩은 auto prefixer가 들어가 있기 때문에 알아서 해결해준다.</li>
<li><strong>쿼리 스트링 활용</strong> : 검색 기능 구현 시 쿼리 스트링을 활용하면 사용자가 검색 결과를 다른 사용자들과 공유할 수 있다.</li>
<li><strong>promise의 finally 활용</strong> : 기본적인 자바스크립트 문법을 잊어버리지 말자. 비동기 통신 후 성공 실패 여부 관계없이 finally의 콜백 함수를 활용할 수 있다.</li>
<li><strong>로컬스토리지 유지를 위한 아톰의 recoil-persist</strong> : atom 사용 시 로컬스토리지에 데이터를 저장 후 새로고침 및 재접속 시 데이터를 유지하기 위해 recoil-persist를 사용할 수 있다.</li>
<li><strong>text-align의 start는 미사용</strong> : text-align의 start보다는 left를 사용하는 것이 권장된다.</li>
<li><strong>무한 스크롤 구현을 위한 react-intersection-observer</strong> : 무한 스크롤 구현 시 react-intersection-observer 라이브러리를 사용할 수 있다.</li>
<li><strong>react 프로젝트의 git 배포</strong> : react 프로젝트의 git 배포 시 404에러가 나타날 수 있다. git 배포는 spa를 지원하지 않기 때문이다. 이러한 에러 사항들을 404.html을 생성하여 우회하는 방법으로 해결할 수 있다.</li>
<li><strong>react 렌더 부분의 함수 사용 자제</strong> : react return문 렌더 부분에 함수를 즉시 실행하는 것은 자제해야 한다. (ex <code>&lt;Button name={IsOpen() ? &#39;A&#39; : &#39;B&#39;}&gt;&lt;/Button&gt;</code>)</li>
<li><strong>기본 alert는 사용을 자제</strong> : 기본 alert는 사용을 자제해야한다.</li>
<li><strong>데이터 key값이 이상한 경우 디스트럭처링 활용</strong> : api 통신 후 데이터 key값이 이상하게 하는 경우 디스트럭처링을 활용하여 새로운 key를 할당하여 사용할 수 있다. 즉 key를 변수에 담는 것과 같은 것이다. (ex &quot;const {Title: title, Year: year, ImdbID: imdbId, Type: type, Poster: poster } = res.Data&quot;) 또한 이처럼 api 통신 후 reponse를 디스트럭처링하여 짧게 사용할 수 있다.</li>
</ol>
<ul>
<li><strong>강의 내용</strong></li>
</ul>
<ol>
<li><strong>useQuery의 캐시 저장</strong> : useQuery는 캐시를 자동으로 저장해둔다. 데이터가 변할 때만 데이터를 재요청한다. 최상단 배열이 캐시 key값이 된다.</li>
<li><strong>useQuery의 api 요청 실패</strong> : useQuery로 api 요청 시 실패하면 자동으로 3번 정도 자동으로 요청을 re try(다시 시도)한다.</li>
<li><strong>useQuery의 suspense를 활용한 로딩</strong> : useQuery의 suspense 속성을 주고 suspence 컴포넌트에 fallback props로 api 연동 시 로딩을 구현할 수 있다.</li>
<li><strong>반응형 작업 시 유의사항</strong> : 모바일을 먼저 작업 후 데스크탑 작업을 하는 것을 권장하셨다. 이유는 구글링해보면 나온다. 퍼블리싱했을 때 경험을 비롯해서 생각해보면 레이아웃이 큰 상태서 줄어들면 깨지는 경우가 많아서 그런 것 같다.</li>
<li><strong>검색어 입력 시 입력마다 호출하지 않고 호출 횟수 줄이기</strong> : 디바운싱 쓰로틀링 axios 캔슬토큰을 활용한다.</li>
</ol>
<br>

<h2 id="-takeaway"># Takeaway</h2>
<p>오늘은 그립 프로젝트를 완료하였다. 타입스크립트를 사용한 프로젝트가 처음이었고 멘토님께서 제시해주신 린터에 맞추어 작업을 하다보니 더 어려웠었던 것 같다. 또 프로젝트 구조 잡는 것이 얼마나 중요한 것인지 느낄 수 있었다. 프로젝트 구조를 제대로 잡지 않으면 같은 작업을 반복할 수 있기 때문에 애초에 프로젝트 시작 단계에서 구조 설계를 효율적으로 해놓은 뒤 작업하는 것도 좋은 방법이라고 생각이 되었다. 또한 상태관리를 위해 리코일을 사용하였는데 새로고침 시 로컬스토리지 데이터가 날아가 useEffect hook으로 고치기 위해 엄청 고생하였는데 리코일 라이브러리 중 로컬스토리지에 저장하는 라이브러리가 있어 결국 그것을 사용하였다. 이번 프로젝트를 진행하며 타입스크립트, 리코일, 무한 스크롤, 리액트 프로젝트 배포 등등 많은 경험을 할 수 있었던 것 같다. 가장 아쉬운 점은 프로젝트 완료 시점을 잘못 알아 기획 단계에서 시간을 많이 잡아먹었던 것 같다. 피그마를 활용하여 기획서를 작성하였는데 이 부분에 쓸데없는 시간을 많이 투자했다. 다음부터는 꼭 일정을 잘 살펴보아야겠다. 그래도 정말 많이 성장할 수 있는 기회가 된 것 같다. 다음에 시간 될 때 마무리가 덜 된 드래그앤드롭 기능을 수정해야겠다. but.. 내일부터는 또 새로 프로젝트가 시작되기 때문에 시간날 때 해야겠다. 지나간 일은 붙잡지 말고 오늘 해야하는 일에 집중하자!! 화이팅!!</p>
]]></description>
        </item>
    </channel>
</rss>