웹 전반에 대한 내용

패스트캠퍼트 the red를 듣고 정리하였다.

트래픽 과다

서버의 사용량이 많아지면 느려지다가 어느 시점에 서버가 죽는다.

클라이언트 개발자도 서버가 죽었을 경우 대처해야한다.

때문에 모니터링이 필요하다.(status check,health monitoring)

Scaling

서버의 크기를 늘렸다 줄이는 기법( 가능하도록 서버를 설계해야 한다. )
없을경우 대기열을 만들어 한 번에 접속 가능한 유저의 접속을 막아야한다.
클라우드 서버를 사용할경우 용이하다.

리소스 최적화

서버에서 리소스를 내려주면서 발생하는 트래픽의 줄이는데에 중요하다.

리소스를 주고받으며 db,api 그리고 웹사이에 오가는 트래픽을 줄이는 것이다.

파일 크기, 캐싱 정책에 따라 최적화 할 수 있다.

브라우저의 웹 페이지 로드 과정

Prompt for unload : 다른 페이지로 이동할 것이라는 신호(window의 beforeunload 이벤트가 발생한다.)
– 네트워크 레이어
Redirect(선택) : 리다이렉트가 있다면 실행한다.
AppCache : 브라우저 캐시에 저장된 이벤트가 있는지 확인한다.
DNS : dns에서 도메인 이름을 네트워크 주소로 바꾼다.
TCP : tcp를 연결한다.
Request : html을 요청한다.
Response : html을 받는다.

Processing : html을 파싱하고 렌더링한다. 끝날 때 document.DOMContentLoaded이벤트가 발생한다.

  • DONContentLoaded : html을 다 파싱하고 DOM까지 만들고 화면에 그리기 전에 발생시킨다.(서브 리소스등은 아직 다운로드받지 않은 상태이다.)

Load : 이미지나 css등을 다 다운로드 받은 상태이다. 끝날 때 window.load 이벤트가 발생한다.

  • load 리소스들의 로딩이 완료되면 실행된다.

점진적 향상 vs 우아한 성능 저하

점진적 향상은 기능이 없는 환경에서 출발해서 지원하는 환경이 있으면 기능을 추가하는 방법이다.

1
2
3
<picture>
</img>
</picture>

우아한 성능 저하는 지원하는 환경에서 출발하여 지원하지 않는 환경을 대비하는 방식이다.

1
2
3
<video>
<a>
</video>

브라우저의 랜더링 방식

더 나은 사용자 경험을 위해 아래의 과정은 점진적으로 실행된다.

싱글스레드기 때문에 앞의 단계로 돌아가게 되면 뒷 단계가 멈춘다.

  1. 사이트에서 HTML을 받아온다
  2. HTML을 위에서 아래로 읽으면서 link요소등 외부 리소스를 만나면 다운로드 받고 js파일을 만나면 스크립트 해석이 끝날 때 까지 렌더링을 멈춘다.
  3. css도 해석하고 html도 해석이 끝나면 각각 CSSOM과 DOM을 만든다.
  4. 둘을 조합하여 렌더트리를 만든다.
  5. 렌더트리가 나오게 되면 렌더트리를 바탕으로 요소를 배치할 영역을 정하는 레이아웃을 수행한다.(reflow)
  6. 요소를 실제로 픽셀로 변환하여 paint를 수행한다.
  7. 픽셀로 그려진 여러 레이어를 합성한다.

DOM을 변경했을 경우

배치가 바뀌지 않았을 경우 레이아웃은 생략할 수 있다.
하지만 페인트는 무조건 진행되어야 한다.
레이아웃을 발생시키는 속성을 사용할 경우 60프레임을 유지하기가 어렵다.
때문에 레이아웃을 줄여 성능을 최적화 할 수 있다.

position, left, top, right, bottom 은 레이아웃에 영향을 준다.
하지만 transform:translate()는 레이아웃에 영향을 주지 않아 paint만 발생한다.

reflow는 cpu 자원을 많이 사용하고 repaint는 gpu 자원을 많이 사용한다.

때문에 SPA SSR CSR과 직접적으로 연관성이 있다.

CSR

클라이언트(브라우저)에서 렌더링을 수행한다.
즉 html에 기초적인 것만 적고 js를 이용해 DOM을 그린다.

페이지 이동등이 일어날 때 history api를 이용하여 라우팅 처리를 하고 필요한 DOM만 그 때 그 때 만들어 렌더를 처리한다.

html의 용량이 낮은 대신 js의 용량이 크다.

html의 용량이 작다는 것은 로딩까지의 접근이 빠르다.

또한 필요한 DOM만 만들어서 쓸 수 있다.

리앵뷰 모드 CSR이 베이스이다.

페이지를 이동거나 데이터를 새로 받아오면서 깜빡거림이 발생하지 않는다.

장점

한 번 렌더링에 성공한 경우 그 이후 필요한 부분만 렌더링 할 수 있어 경우에 따라 성능상의 이점이 있다.

단점

js 번들 사이즈가 커진다. 또한 DOM을 브라우저에서 직접 만들기 때문에 브라우저의 리소스를 많이 쓰게되어 렌더링 퍼포먼스가 저하된다.

특징

뉴스 기사나 아티클 위주의 사이트들은 CSR을 할 이유가 없다.

하지만 페이스북이나 인스타그램, 트위터 등 실시간성이 유지될 수록 좋은 어플리케이션의 경우 실시간으로 데이터를 받아오고 처리할 수 있어 CSR을 사용하면 좋다.

SSR

서버에서 렌더링을 수행한다.
즉 서버에서 api를 통해 db를 참조하여 데이터를 받아와서 html을 그리는 모든 과정을 서버에서 수행한다.

장점

서버가 브라우저보다 성능이 좋아서 CSR보다 빠르다.

HTML이 이미 있는 상태로 내려오기 때문에 콘텐츠가 렌더링 되는 속도가 빠르고 검색엔진이 체감하는 속도도 빠르다.

검색엔진은 빠르게 뜨는 사이트를 우선적으로 노출시키기 때문에 콘텐츠가 뜨는 속도가 빨라서 SEO에 이롭다.(상위노출되기 쉽다.)

단점

서버에서 해야할 작업이 많다.

CSR에 비해 최초 로딩속도는 느리다.

특징

html을 만들어 서버와 클라이언트와 주고받는 시간이 늘어날 수록 ux가 안 좋아진다.

콘텐츠가 많을 경우 SSR을 사용한다.

next js나 nodejs에서 template engine을 통해 구현한다.

SSG

사이트를 생성(빌드)하는 시점에 html을 생성한다.

html을 다 만들어놓고 s3나 cdn에 올려서 유저에게 그대로 전달한다.

장점

사이트 생성할 때만 api에 접근하기 때문에 api서버에 대한 부하가 훨씬 줄어든다.

대용량 트래픽에 굉장히 최적화되어있다.(어지간하면 안 죽는다. cdn이 죽어야 죽는다.)

단점

실시간 사이트에는 어울리지 않는다.

특징

데이터가 자주 갱신되지 않는 사이트에 대해서는 최고의 방법이다.

nextjs는 SSG과 SSR을 전부 지원한다.

상세페이지를 만들 경우 SSG 실시간페이지를 만들경우 SSR으로 만들 수 있다.

getsby는 SSG에 최적화되어있다.

정리

SSR,CSR,SSG를 적절히 섞어서 사용하는 것이 좋다.

예를들어 장바구니는 실시간성이 제일 중요하다. 때문에 csr이 어울린다.

상품상세의 경우 데이터가 자주 바뀌지 않는다. 때문에 금액,옵션 그리고 품절은 빠르게 바뀌어야 한다.

때문에 상품상세는 ssr이나 ssg로 만들어도 금액이나 옵션등은 CSR로 구현하는 것이 좋다.

늦게 업데이트 되어도 크게 문제가 되지 않는 블로그나 기사같은 경우 ssg로 구현하는 것이 좋다.

Why React?

  1. 강력한 커뮤니티

리액트는 페이스북이 지원하기 떄문에 망하지는 않을 것이라는 확신과 개발 커뮤니티들도 제일 크다.

  1. 확장성

리액트를 사용한 써드파티 라이브러리들이 굉장히 많다.

  1. 리액트 네이티브

개념은 조금 다르지만 확장할 수 있다.

  1. 경쟁상대의 부재

리액트는 ui자체에 포커싱이 되어있는 라이브러리이다.

Why Function Component

객체지향의 관점에서는 필요하지만 ELEMENT를 만드는 관점에서는 불필요한 작업들이 많이 요구된다.

함수형 컴포넌트가 더 직관적이고 불필요한 작업이 없다.

클래스형 컴포넌트의 경우 라이프사이클로 인해 사용하였다.

HOOKS가 나와서 이 라이프사이클을 대체하게 되었다.

무엇보다 순수함수인 함수형 컴포넌트가 클래스형보다 좀 더 빠르다.

또한 커스텀 HOOKS를 통해 재활용할 수 있다.

FE 성능 최적화

패스트캠퍼스 the red를 듣고 정리해보았다.

파일 다운로드

최신 브라우저는 대체로 도메인 당 6개의 접속만 동시에 처리한다(HTTP 프로토콜 /1.1기준)
http2에서는 하나의 연결로 계속 파일을 주고받아서 괜찮다.
하지만 아직은 1.1기준으로 작동하는 사이트가 많다.

한 도메인에서 6개의 파일만 동시에 다운로드 할 수 있다.
6개 이상부터는 큐에 넣어두고 하나씩 다운로드 하게 된다.

이미지와 같은 파일들은 사용자가 기다리는것에 익숙하지만
js나 cs파일들의 다운로드가 느려지면 페이지에 변화가 없고 인터액션이 늦어지게 된다.

개선방안

  1. 파일 갯수와 용량을 작게한다.

  2. 별도 도메인이나 CDN등으로 분산하여 해결할 수 있다.

  3. 적절한 포맷을 사용한다. 이미지의 경우 webp파일을 사용하여 크기를 줄일 수 있다.
    ex) 레진코믹스

1
2
3
4
5
6
<!-- 지원 타입에 따라 다른 이미지 로딩 -->
<!-- img 태그를 picture태그로 감싸서 picture태그를 지원하는 브라우저에서는 picture를 해석하도록 만들었다. -->
<picture>
<source srcset="img/photo.webp" type="image/webp" />
<img src="img/photo.jpg" alt="my photo" />
</picture>
  1. 화면 크기와 해상도에 따라 적절한 이미지 로딩
1
2
3
4
5
6
7
8
<!-- 화면 크기에 따라 적절한 이미지 로딩 -->
<picture>
<source srcset="img/photo_small.jpg" media="(max-width:800px)" />
<img src="img/photo.jpg" alt="my photo" />
</picture>

<!-- 해상도에 따라 적절한 이미지 로딩 -->
<img src="img/icon72.png" alt="icon" srcset="img/icon14.png 2x" />
  1. 웹 폰트 최적화
  • 구글폰트: 나눔 고딕 등 한국어 폰트 26종 제공
  • WOFF2는 WOFF,TTF등에 비해 30% 용량이 작다.
  • 필요한 글자만 골라서 글꼴을 만들 수도 있다.(메뉴용 글꼴 등)
  1. 화면 크기 등에 따라 필요한 스타일 시트만 로딩
  • 하지만 실제 이렇게 사용하는 경우는 없다. css는 크기가 작고 파일 개수를 늘리는 것은 좋지 않다.
  • 부트스트랩이 차지하는 용량이 20kb쯤
1
2
3
4
5
6
<link href="mobile.css" rel="stylesheet" media="all" />
<link
href="desktop.css"
rel="stylesheet"
media="screen and (min-width: 600px)"
/>
  1. link 태그 만으로 접속 시간 절약
1
2
3
<link rel="dns-prefetch" media="https://taegon.kim" />
<link rel="preconnect" media="https://cdn.example.com" />
<link rel="prefetch" media="https://cdn.example.com/library.min.js" />

Preconnect를 사용할 경우 외부 도메인을 미리 검색하여 소켓을 연결해둔다. 서드파티 자원 연결에 적합하다.

Prefetch를 사용할 경우 외부 도메인을 미리 검색하여 소켓을 연결 후 다운로드까지 해둔다.
하지만 남용하면 안되고 꼭 필요하거나 다음 경로가 명확하게 예상이 될 경우에만 사용해야 한다.

로딩 속도 개선

  1. 필수 컨텐츠가 아니라면 비동기 로딩을 고려해보자(광고,댓글,헤더/푸터 등)

  2. 이미지/아이프레임/스크립트 등은 필요할 때까지는 읽지 않는 게으른 로딩(lazy loading)기법을 고려해보자. 코드를 분할한 후 필요할 때 읽어오는 방식이다. 웹팩에서 자동으로 해주기도 한다.

1
2
<img src="image.jpg" loading="lazy" />
<iframe src="https://example.com" loading="lazy"></iframe>
  1. 시간이 많이 걸린다면 플레이스 홀더 등으로 대체한다.

-스켈레톤 ui

계산 시간

  1. 웹 워커

자바스크립트는 싱글스레드이다. 하지만 워커스레드를 이용하여 멀티스레드가 가능하다.

워커스레드

  • UI를 조작할 수 없다.
  • 워커 스레드 전용으로 분리된 파일이 필요하다.
  • postMessage()로 데이터를 전송하고, onMessage 이벤트를 통해 받는 방식으로 통신한다.
  1. 느긋한 계산

값이 필요해질 때 까지 계산을 미뤄두는 기법이다.

게으른 평가, 지연 평가라고도 한다.

  1. 메모이제이션

계산 결과를 기억해두고 반복 사용하는 기법.
루프, 재귀 호출 등 최적화.

반응시간

지나치게 느린 애니메이션 및 응답 속도는 사용자 경험을 저해한다.

  1. 시스템의응답이 40ms보다 느리면 주의력 분산이 발생함
  2. ui가 100ms 이하로 반응해야 ui를 다루고 있다고 느낀다.
  3. 애니메이션은 60fps를 기준으로 한다.

애니메이션의 렌더링 순서

js-> 스타일-> 레이아웃 ->페인트 -> 합성

페인트까지는 메인스레드에서 진행한다. 합성만 컴포지터 스레드에서 진행한다.

메인스레드는 자바스크립트가 동작하기 때문에 메인스레드의 작업이 많아지면 자바스크립트의 속도도 느려진다.

개선 방법

메인스레드에서 이루어지는 과정은 가능한 한 피하고 컴포지터 스레드에서 일어나도록 하면 된다.

즉 레이아웃이나 페인트는 피하고 합성단계를 유발하는 css 속성들을 주로 사용한다.

각 단계의 동작을 유발하는 css 속성은 https://csstriggers.com/에서 확인할 수 있다.

정리

  1. 가능하면 js보다는 css 애니메이션을 최대한 활용한다.
  2. 다음 css 속성 위주로 애니메이션 -gpu 가속을 적용한다.(자동으로 적용된다.)
  • transform:translate or scale or rotate
  • opacity
  1. 레이아웃 변경이나 리페인팅을 유발하는 css 속성은 비용이 많이 든다.
  • 레이아웃 : width,height,padding, margin,display
  • 페인트 : color,background,outline,box-shadow
  1. setTimeOut 보다는 requestAnimationFrame, Web Animations API 등 활용
  • requestAnimationFrame: 브라우저가 최적화, 비활성 탭에서는 동작 안 함
  • web Animation API: css애니메이션과 같은 애니메이션 엔진 사용
  1. three.js velocity.js와 같은 고성능 애니메이션 라이브러리 사용

  2. will-change 속성을 통해 브라우저가 최적화 할 속성 명시

  • 남용하면 리소스 낭비,
  1. DOM 접근과 업데이트는 가능한 적게, 한 번에 몰아서 처리,
  • 엘리먼트 추가는 DocumentFragment 활용
1
2
3
4
5
6
7
8
9
10
const app = document.getElementById("app");
const frag = document.createDocumentFragment();

for (let i = 0; i < 1001; i++) {
const el = document.createElement("div");
el.innerText = `Element ${i}`;
frag.appendChild(el);
}

app.appendChild(frag);

리소스

cpu 점유율, 전력 소비량, 스토리지, 메모리 사용량, 네트워크 트래픽 중 메모리 누수와 네트워크 트래픽을 신경써야 한다.

메모리 누수

프로그램이 필요하지 않는 메모리를 계속 점유하는 현상

메모리 생명 주기

  1. 할당 : 사용할 메모리를 확보한다.
  2. 사용 : 메모리를 사용한다.
  3. 헤재 : 불필요한 메모리를 반환한다.

개선 방법

js는 가비지 컬렉션을 통해 메모리를 해제한다.

이 메모리를 몇개의 변수가 참조하는지 카운트하여 0개일 경우 지운다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var x = {
a: {
b: 2,
},
};
// 두 객체 모두 생성된다.
// x 객체를 o1 a객체를 o2라고 하자

var y = x; //y도 o1를 참조한다.

x = 1; // x는 이제 o1를 참조하지 않는다. 이제 o1를 참조하는 변수는 y가 유일하다.

var z = y.a; // o1 내부의 o2을 z가 참조한다.

y = "mozilla"; // y는 o1를 참조하지 않지만 z변수가 o1의 o2를 참조하므로 o1은 제거되지 않는다.

z = null; // 이제 o1과 o2는 아무 곳에서도 참조가 되지않아 가비지 컬렉션이 된다.

순환참조 문제

객체끼리 참조가 맞물려서 가비지 컬렉션이 동작하지 않는 문제

마크스위프 알고리즘을 통해 해결할 수 있게 되었다.

메모리 누수의 대표적 예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// case 1 : 큰 전역 변수는 계속 메모리에 존재한다.
const bigData={.../*big data*/};

// case 2 : 클로저는 생성된 실행 컨텍스트의 변수를 해제하지 못하게 한다.
function factory(){
const largeData={.../*big data*/}

return ()=>{
//...
}
}

const fn=factory();
fn();
// fn을 실행한 후에도 여전히 largeData는 메모리에 존재한다.

// case 3: 큰 데이터가 다른 변수에서 참조되면 큰 데이터는 해제되지 않는다.
const data1={linkTo:bigData};
const data2={anotherName:bigData};

개선방안

아래의 코드는 예시일 뿐이다.

실제로는 모듈이나 다른 함수로 분리해서 사용하는 것이 좋다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// case 1 : 큰 전역 변수는 계속 메모리에 존재한다.
const bigData={.../*big data*/};

// case 2 : 클로저는 생성된 실행 컨텍스트의 변수를 해제하지 못하게 한다.
function factory(){
const largeData={.../*big data*/}

return ()=>{
//...
}
};

{
const fn=factory();
fn();
}
// fn은 실행 후 현재의 Lexical Enviroment와 함께 해제되고 largeData도 함께 해제된다.

// case 3: 큰 데이터가 다른 변수에서 참조되면 큰 데이터는 해제되지 않는다.
const data1={linkTo:bigData};
const data2={anotherName:bigData};

네트워크 트래픽

파일 용량을 줄이거나 필요할 때만 불러와서 트래픽 낭비를 줄인다.

  1. 최소화된 js,css파일을 사용한다.
  2. 프레임워크는 한 개 이하만 사용한다.
  3. 파일 주소의 파라미터는 주의해서 사용, 의도하지 않은 캐시버스터가 될 수 있다.
  • 브라우저는 전체 url 기반으로 캐시하기 때문에 업데이트가 되어도 받아오지 못할 수 있다.
  • 일부러 랜덤한 숫자를 붙여 새로 캐시되도록 하는 것이 캐시버스터이다.
  1. 이미지, 미디어는 필요할 때 불러오는 게으른 로딩(lazy loading)기법을 사용한다.

js canvas를 이용하여 text의 길이 계산하기

HTML canvas를 사용하여 문자열의 너비를 알아낼 수 있다.

ctx.measureText(text)는 text를 파라미터로 하는 TextMetrics를 반환한다. 이 객체의 width 속성으로 문자열의 너비를 알아낼 수 있다.

1
2
3
4
5
6
function getTextWidth(text, fontSize, fontFace) {
var canvas = document.createElement("canvas");
var canvasContext = canvas.getContext("2d");
canvasContext.font = fontSize + "px " + fontFace;
return canvasContext.measureText(text).width;
}

Ref

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/measureText

js 색의 밝기 정보 추출하기

그래프의 배경색에 따라 글씨 색을 다르게 하려는 작업을 위해 구글링 하던 중

색의 밝기 정보를 알아내는 코드를 찾게 되었다.

1
2
3
4
5
6
7
8
let c = c.substring(1); //  # 제거
let rgb = parseInt(c, 16); // 10진수로 변경
let r = (rgb >> 16) & 0xff; // r추출
let g = (rgb >> 8) & 0xff; // g추출
let b = (rgb >> 0) & 0xff; // b추출

let luma = 0.2126 * r + 0.7152 * g + 0.0722 * b; // per ITU-R BT.709
// luma가 255에 가까울 수록 밝다.

Ref

stackoverflow

cra 없이 webpack+babel로 리액트 초기 설정하기!

모야 프로젝트를 진행하면서 초기 세팅을 맡게되었다.

cra는 webpack과 babel 세부설정을 위해선 eject해야한다.

유지보수를 위해서 처음부터 cra없이 세팅하는게 맞다는 생각이 들어 직접 세팅하였다.

webpack은 공식문서가 잘 되어있어서 수월하게 세팅할 수 있었다.

아래의 세팅은 정답이 아니다..!

초기 설정

src 폴더와 public 폴더를 만들었다.
src 폴더에는 js 파일들이 작성된다.
public 폴더에는 index.html을 만들었는데, src의 js파일들을 webpack으로 번들링하여 하나의 js파일을 만든다. 그리고 이를 사용한다..!

먼저 초기설정을 해준다.
루트디렉토리로 가서 아래의 커맨드를 입력하면 된다.

1
2
3
4
5
npm init -y
mkdir src public
// src 폴더와 public 폴더를 만든다.
touch src/index.js public/index.html
// 진입점을 정의한다.

public/index.html은 다음과 같이 작성한다.
./bundle.js는 webpack으로 번들링된 결과물이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="root"></div>
<script src="./bundle.js"></script>
</body>
</html>

index.js는 다음과 같이 작성한다.
App 컴포넌트의 리턴값은 진입점이라 생각하고 작성해주면 된다.

1
2
3
4
5
6
7
8
import React from "react";
import reactDOM from "react-dom";

const App = () => {
return <div>hi</div>;
};

reactDOM.render(<App />, document.getElementById("root"));

필요한 모듈 설치

필요한 모듈은 어떤 작업을 할지에 따라 달라진다.
나는 아래의 모듈들을 설치하였다.

1
2
3
4
5
6
7
8
9
10
11
12
npm i @babel/core @babel/preset-env @babel/preset-react babel-loader -D
// babel과 babel의 preset 그리고 webpack과 babel을 연결하기 위한 loader
npm i css-loader node-sass sass-loader style-loader -D
// css를 로드하기위해 설치
npm i "@pmmmwh/react-refresh-webpack-plugin" react-refresh -D
// react hot-reload를 위한
npm i webpack webpack-cli -D
// webpack
npm i webpack-dev-server
// 개발용 서버를 위한 webpack-dev-server 설치
npm i file-loader
// 정적 이미지를 가져오기위해 설치

바벨 세팅

루트 디렉토리에서 다음과 같은 커맨드를 입력한다.

1
touch .babelrc

preset는 자주 쓰이는 plugin들의 집합이다.

babel팀에서 공식적으로 제공하는 preset과 비공식 preset이 있다.

필수적인 preset을 입력하였다.

또한 plugin에는 핫 리로딩을 위해 react-refresh를 넣어주었다.

최종적으로 .babelrc의 내용은 다음과 같다.

1
2
3
4
{
"presets": ["@babel/preset-env", "@babel/preset-react"],
"plugins": ["react-refresh/babel"]
}

webpack.config.js 설정

webpack.config.js 파일을 작성한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
const path = require("path");
// 절대경로를 위한 모듈을 import
const RefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
// 핫리로딩을 위한 플러그인
const webpack = require("webpack");
// 환경변수 설정을 위해 webpack을 불러옴

module.exports = {
mode: "development",
// 모드
entry: "./src/index.js",
//진입점이 될 파일
output: {
path: path.join(__dirname, "public"),
//경로
filename: "bundle.js",
//결과물의 이름
},
resolve: {
extensions: [".js", ".jsx", "json"],
alias: {
"@images": path.resolve(__dirname, "public/images"),
"@components": path.resolve(__dirname, "src/components"),
"@reducers": path.resolve(__dirname, "src/reducers"),
"@sagas": path.resolve(__dirname, "src/sagas"),
"@utils": path.resolve(__dirname, "src/utils"),
},
},
// 절대경로 설정
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
loader: "babel-loader",
},
{
test: /\.css$/,
use: ["style-loader", "css-loader"],
},
{
test: /\.sass$/,
use: ["style-loader", "css-loader", "sass-loader"],
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: "file-loader",
},
],
},
plugins: [
new RefreshWebpackPlugin(),
new webpack.DefinePlugin({
"process.env.NODE_ENV": JSON.stringify("development"),
// 환경변수 설정
// https://yamoo9.gitbook.io/webpack/webpack/webpack-plugins/manage-env-variables
}),
],
devtool: "eval-cheap-module-source-map",
// 소스를 매핑하는 방법
// 여러가지 옵션이 있다..! https://webpack.kr/configuration/devtool/
devServer: {
static: {
directory: path.join(__dirname, "public"),
// 서버에 정적파일의 위치를 알려줌
},
port: 3000,
hot: true,
},
// https://webpack.js.org/configuration/dev-server/
};

eslint + prettier 설정하기

코드 컨벤션을 위해 eslint와 prettier를 적용하였다.
@babel/polyfill은 devDependencies가 아닌 dependencies로 설치해주어야 한다.

1
npm i eslint eslint-config-prettier eslint-plugin-prettier prettier -D

prettier파일을 다음과 같이 작성하였다.
옵션은 공식문서를 참조하였다.

1
2
3
4
5
6
7
8
9
10
11
12
{
"printWidth": 80,
"tabWidth": 2,
"singleQuote": false,
"trailingComma": "all",
"semi": true,
"useTabs": false,
"arrowParens": "always",
"endOfLine": "auto",
"bracketSpacing": true,
"jsxBracketSameLine": true
}

.eslintrc 파일을 다음과 같이 작성해주었다.

1
2
3
4
5
6
7
8
{
"plugins": ["prettier"],
"extends": ["eslint:recommended", "plugin:prettier/recommended"],
"parser": "babel-eslint",
"rules": {
"prettier/prettier": "error"
}
}

babel polyfill 설정하기

더 이상 사용되지 않는 모듈이라고 한다.

@babel/polyfill을 보면 core-js/stable과 regenerator-runtime/runtime로 대체하여 사용한다고 한다!

babel preset-env target 설정하기

최신 크롬버전에 맞는 플러그인만 삽입하도록 변경하였다.

1
2
3
4
5
6
7
{
"presets": [
["@babel/preset-env", { "targets": "last 2 Chrome versions" }],
"@babel/preset-react"
],
"plugins": ["react-refresh/babel"]
}

추가로 해야할 것

  1. webpack-dev-server proxy 설정하기!
    나중에 cors로 인해 작업에 차질이 생길 수 있다. 미리 알아두어야 좋을 것 같다.
    webpack-dev-server proxy

느낀점

밑바닥에서 하나씩 차근차근 설정하니까 재밌고 오히려 욕심도 생긴다..!
webpack 객체의 property 자체에 대해 많이 알아봤는데, 시간이 없어서 다 적지 못하는게 아쉽다.
또 알게 된 것이 많았다.

Ref

Setup Webpack and Babel for React

Webpack 러닝 가이드

Webpack 공식문서

FE개발자의 성장 스토리 02 : Babel7과 corejs3 설정으로 전역 오염 없는 폴리필 사용하기

Babel Polyfill 적용하는 방법들

React useReducer Hook 사용하기

Numble의 챌린지를 하면서 useReducer Hook을 사용하게 되었다.

공식문서의 내용을 간단하게 정리하고 내가 실제로 구현한 예시 코드를 정리하려고 한다.

useReducer?

useReduceruseState의 대체 함수이다.
(state, action) => newState의 형태로 reducer를 받고 dispatch 메서드와 짝의 형태로 현재 state를 반환한다.
redux의 reducer를 하나의 컴포넌트에서 사용한다고 생각하면 된다.

장점

useReducer다수의 하윗값을 포함하는 복잡한 정적 로직을 만드는 경우다음 state가 이전 state에 의존적인 경우useState대신 사용하면 좋다.

또한 useReducer는 콜백 대신 dispatch를 전달 하여 자세한 업데이트를 트리거 하는 컴포넌트의 성능을 최적화할 수 있게 한다.

또한 useMemolazy initial state가 가능하다.

예시

reducer.ts에 reducer를 작성해주었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const reducer = (state: GameType, action: Action): GameType => {
const { actionType } = action;

switch (actionType) {
case DECREASE_TIME_ACTION:
return decreaseTime(state);

case CORRECT_ANSWER_ACTION:
return nextStage(state);

case WRONG_ANSWER_ACTION:
return wrongAnswerPenalty(state);

case RESET_GAME_ACTION:
return initialState;
default:
return state;
}
};

그 후 export하고 컴포넌트에서 다음과 같이 호출하여 사용할 수 있다.

1
const [gameState, dispatch] = useReducer(reducer, initialState);

ref

리액트 공식문서

Numble 다른 색깔 찾기 게임 제작 챌린지

Numble에서 진행하는 [React] 다른 색깔 찾기 게임 제작 챌린지에 참여하였다.

핵심 로직

  • 여러 상태를 관리하기 위해 useReducer를 사용하였다.

  • useInterval Hook을 정의하여 setInterval의 timer가 React의 state에 맞춰서 작동하도록 하였다.

  • 난이도가 올라갈 수록 어려운 색이 나오도록 하였다.

    • 15스테이지까지는 정답색의 r,g,b값 중 b값과 투명도를 조절하였다.
    • 15~30 스테이지까지는 정답색의 투명도를 0.8로 고정하였다.
    • 30이상의 스테이지에서는 정답색의 투명도를 0.9로 하였다.

활용한 라이브러리와 그 이유

  • React.js를 사용하였다.

    • Next.js로 작성하였으나 Next.js는 Univasal Rendering으로 첫 페이지를 SSR로 렌더링하여 첫 렌더링시 정답 블럭과 정답 색의 불일치가 발생하였다.
      때문에 완성본을 React.js로 다시 작성하였다.
  • @emotion/styled 라이브러리를 사용하였다.

    • grid를 잘 사용해보지 않아서 calc를 통해 블록당 width를 계산하도록 하였다.

프로젝트를 진행할 때 어려웠던 점

  • 데모 버전을 살펴보았는데, 모든것이 시간 기준이라 시간만 state로 관리하고 나머지는 useRef나 useMemo를 이용하여 관리할 수 있다고 생각이 되었다.

    • 이를 예전에 다 구현을 해두었으나 위에서 언급한 Next.js의 렌더링 방식으로 인해 첫 렌더링시 정답 블럭과 정답 색의 불일치가 발생하였다.
    • 이 이슈 해결이 제일 어려웠는데 SSR에 대한 로직이나 플러그인을 설치하는 것 보다 React.js로 옮기는 것이 확실하다고 생각되어 옮겼다.
  • 위에서 언급한 이슈해결에 대해서 고민하다가 넘블 챌린지 공고를 다시 한 번 보았다.

    • 공고를 살펴보던 도중 useReducer라는 Hook이 있다는 것을 알게 되었고, 도입하는 것이 코드를 훨씬 깔끔하게 작성할 수 있다고 생각이 들어서 도입하였다. useReducer를 사용하여 컴포넌트 본문의 길이를 줄이고 가독성 좋은 코드로 수정할 수 있었다.

마무리

리액트의 state에 대해 더 잘 알 수 있어서 유익했다.
또한 상태관리 라이브러리를 사용하지 않고 useReducer라는 Hook을 알게되고 활용 해보아서 좋았다..!

React Image preload!

프론트에서 최적화를 하기 위해 이미지 자체를 webp등으로 최적화하는 방법이 있지만 다른 방법으로는 미리 로딩하고 캐시로 가져오는 preload 방식이 있다.

Preload

이미지를 미리 로딩하고 캐시로 가져오는 방식이다.
캐시는 브라우저차원에서 자동으로 하기 떄문에 이미지를 미리 받아오기만 하면 된다.

Preload 에는 두 가지 방식이 있다.

병렬(Parallel) 방식

자바스크립트의 경우 요청을 병렬로 처리하려고 하므로 for문을 돌면서 여러번 호출해주면 된다.

리액트의 경우 useEffect에서 첫 렌더링시에 호출하여 이미지를 미리 로드해두고 캐시해서 사용할 수 있다.

1
2
3
4
5
6
7
8
9
10
useEffect(() => {
function preloading(imageArray) {
imageArray.forEach((url) => {
const image = new Image();
image.src = url;
});
}

preloading(["1.png", "2.png", "3.png"]);
}, []);

특징

모든 요청을 한 번에 보내기 때문에 전체 사진을 불러오는데에 시간이 줄어든다. 하지만 전체 사진이 로드되기 전까지 기다려야한다.

modules

순차적(Sequential) 방식

순차적 방식은 재귀를 통해 구현한다. 이미지의 onload 이벤트에 다음 이미지를 parameter로 재귀호출하도록 하여 이미지가 우선순위를 갖고 캐싱될 수 있도록 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
useEffect(() => {
function preload(imageArray, index) {
index = index || 0;
if (imageArray && imageArray.length > index) {
const image = new Image();
image.onload = function () {
preload(imageArray, index + 1);
};
image.src = images[index];
}
}

preload(images);
}, []);

특징

병렬 방식에 비해 전체 작업시간은 늘어나지만 이미지들이 하나씩 로드되어 하나씩 확인할 수 있다.

modules

마무리

  • 병렬 : 빠르게 불러와야 할 때

  • 순차적 : 조금씩이라도 불러와야할 때, 추가로 슬라이더 등을 구현할 때 다음 이미지를 이 방식으로 미리 불러오면 좋을 것 같다.

ref

image preloading
이미지 프리로딩

HTML label 태그와 input태그

<label> 태그는 사용자 인터페이스 항목의 설명을 나타내는 태그라고만 간략하게 알고있었다.

하지만 <label> 태그에는 내가 몰랐던 기능이 있었다.

<label>태그를 클릭하면 <input>태그가 클릭된다.

아래의 코드는 <label>태그 안에 <span><input>을 작성한 코드이다.

위에서 설명하였듯이 <label>을 클릭할 경우 <input>태그에도 이벤트가 전달된다.

때문에 여기에서 <span>을 클릭할 경우 이벤트 버블링에 의해서 <label>에도 이벤트가 전파되고 이로 인해 <input> 태그에도 이벤트가 전달된다.

즉 이벤트버블링을 명시적으로 막지 않는 한, <label>안의 요소를 클릭하면 <input>에 이벤트가 전달된다.

1
2
3
4
<label>
<input type="checkbox" />
<span></span>
</label>

useLayoutEffect

리액트의 Hooks 중에는 useLayoutEffect라는 Hook이 있다.
이 에 대해 정리해보았다.

Flow Diagram

Hook FLow Diagram에 따르면 Cleanup LayoutEffects는 브라우저가 페인트 작업을 하기 전에 실행된다.

hookflow

useLayoutEffect

위의 이미지를 보면 useEffect는 페인트 작업이 끝난 후 호출된다.

useLayoutEffect는 다음과 같이 작성하여 사용할 수 있다.

1
2
3
4
5
6
useLayoutEffect(() => {
// func
return () => {
// cleanup
};
}, [dependencies]);

사용 예시

useEffect 내부에서 상태값을 설정한다면
페인트를 마친 후에 상태값을 설정하며 리렌더링 되고 렌더링되지 않았던 값들이 렌더링되며 깜빡거리게 된다.

이럴 경우 useLayoutEffect를 호출하게 되면
페인트를 시작하기 전에 상태값을 설정하게 되고, 처음 렌더링시에 상태값이 이미 설정되어있어 깜빡거리지 않는다.

요약

useEffect 내부에서 상태값을 설정해야 한다면 useLayoutEffect를 사용하여 설정하자.( 물론 처음 화면을 렌더링하는데에 필요한 시간은 길어진다. 트레이드오프!)

ref

hook-flow