# 2022년 9월 TIL
# 2022.09.30
# Next.js의 <Image />
Next.js의 <Image/>
는 자체적으로 많은 최적화 기능을 제공한다.
- 레이지 로딩
- 이미지 사이즈 최적화 (source, picture, srcset 등을 이용해 구현해야 하는 그것)
- placeholder
- 이 부분이 인상적이었는데, 웹에서는 이미지가 로드되기 전까지 영역의 높이가 0이었다가 이미지가 로드되면 그만큼 공간이 생기면서 레이아웃이 흔들리거나 깜빡이는 현상을 자주 볼 수 있다. 이러한 것을 CLS(Cumulative Layout Shift)라고 부르는데, Next/Image는 이미지가 로드되기 전에도 그만큼의 영역을 표시해서 레이아웃이 흔들리지 않도록 만들어준다.
이러한 기능을 통해서는 다음과 같은 효과를 얻을 수 있다.
- 성능 향상
- 시각적 안정성 (placeholder를 이용해)
- 빠른 페이지 로딩
사용 시 주의사항
로컬 이미지를 사용할 경우에는 크게 신경 쓸 것이 없다.
단, 리모트 이미지를 사용할 경우 해당 이미지를 불러오는 cdn의 도메인을 next.config.js 파일에 명시해주어야 한다.
module.exports = {
images: {
domains: ["assets.acme.com"],
},
};
# Next.js에서 404 에러 페이지 만들기
pages 경로에 404.js 또는 404.tsx 파일을 만들어주면 된다.
그러면 해당 페이지가 404 에러 발생 시 자연스럽게 노출된다.
이보다 간단할 수 없다..
# Next.js redirects
next.config.js에서 아주아주 간단하게 리다이렉트를 설정해줄 수 있다.
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
swcMinify: true,
async redirects() {
return [
{
source: "/",
destination: "/about",
permanent: true,
},
];
},
};
module.exports = nextConfig;
source
: 들어오는 요청 경로, 즉 시작점destination
: 라우트될 위치 (목표 경로)permanent
: 영구 리다이렉트인지 임시 리다이렉트인지 설정 가능. 영구인 경우 true, 임시인 경우 false를 설정해주면 된다. 설정값에 따라 해당 리다이렉트를 브라우저에 캐싱할 것인지 말 것인지를 결정한다.
⭐️ 번외 - rewrite
redirects 말고 rewrite라는 기능도 있다. rewrite는 말 그대로 '다시 쓰기'의 기능을 가지고 있는데, 수신 요청 경로(source)를 대상 경로(destination)에 매핑할 수 있다. router.replace
와 router.push
의 차이점과 유사한 느낌.
ref
공식 문서 - Next/Image (opens new window)
Next/Image를 활용한 이미지 최적화 - 카카오 엔터테인먼트 (opens new window)
Next 공식문서의 Redirects (opens new window)와 Rewrite (opens new window)
# 2022.09.28
# React - Context api의 단점 : 불필요한 리렌더링
난 브라우저 기본 Alert가 정말 싫다. 이유는 못생겨서! 그리고 웹서비스와의 통일감이 떨어져서!
그래서 Alert 컴포넌트를 따로 만들어서 모든 alert를 해당 컴포넌트를 이용하는 방식으로 작업하는 것을 선호하는데, 이걸 서비스 전체 범위에서 효율적으로 작동하도록 구현하려면 Alert 모달의 on/off 값과 모달의 종류(Success, Error...), 모달에 입력될 내용 등을 전역 상태로 관리해야 한다.
제일 처음에 React를 공부할 때에는 Context api를 이용해 전역 상태관리를 했는데 이상하게 모든 페이지에서 Alert modal에 관련된 상태가 변경될때마다 페이지가 깜빡이는 것이다. 메모이제이션으로 웹서비스의 컨텐츠 부분에 해당하는 페이지 컴포넌트를 묶어 불필요한 리렌더링을 방지할 수도 있지만, 그러려면 거의 대부분의 컴포넌트를 memo()에 담아줘야 하는데 이게 맞나? 싶은 의문이 계속 들었다.😵💫
그런데 Recoil을 공부하다보니, 그러한 리렌더링이 Context api의 고질적인 문제더라고..? 나는 또 내가 코드를 잘못 쓴 줄 알고 한참 고민했는데 말이다.
React 공식 문서의 Context 부분 (opens new window)
공식 문서 中 '주의사항'
다시 렌더링할지 여부를 정할 때 참조(reference)를 확인하기 때문에, Provider의 부모가 렌더링 될 때마다 불필요하게 하위 컴포넌트가 다시 렌더링 되는 문제가 생길 수도 있습니다. 예를 들어 아래 코드는 value가 바뀔 때마다 매번 새로운 객체가 생성되므로 Provider가 렌더링 될 때마다 그 하위에서 구독하고 있는 컴포넌트 모두가 다시 렌더링 될 것입니다.
Context api가 엄연히 존재함에도 불구하고 수많은 상태 관리 라이브러리들이 쓰이는 데에는 이유가 있는 것..
# 2022.09.25
# 브라우저 렌더링 과정 (with Critical Rendering Path)
직관적으로는 이해하고 있으나 제대로 정리해본 적은 없는 그것.. 브라우저 렌더링 과정에 대해 알아보자. 웹 브라우저의 구조에 대해 살펴보고, 그 중에서도 웹서비스의 렌더링 퍼포먼스 향상에 큰 영향을 미치는 렌더링 엔진의 작동 방식과 CRP(Critical Rendering Path)에 대해서도 정리해보는 것으로.
먼저 웹 브라우저의 기본적인 구조를 정리해보면 다음과 같다.
- 사용자 인터페이스
- 주소창, 새로고침/이전/다음 버튼 등 웹페이지 이외에 사용자가 접근할 수 있는 인터페이스 영역.
- 브라우저 엔진
- 사용자 인터페이스와 렌더링 엔진 사이의 동작을 제어한다. 두 가지를 연결해주는 브릿지 역할.
- 렌더링 엔진
- 브라우저의 핵심.
- HTML과 CSS를 파싱(Parsing)해서 웹 페이지에 그려주는 엔진이다.
- 네트워크 (통신)
- HTTP와 같은 각종 네트워크 요청에 사용된다.
- JS 해석기 (인터프리터)
- 자바스크립트를 해석하고 실행하는 엔진. 많이 언급되는 V8엔진이 바로 이 JS 해석기에 해당한다.
- UI 백엔드
- 기본 input, select 등 브라우저의 기본 위젯을 그려준다.
- 사용자 인터페이스와 상호 작용한다.
- 자료 저장소
- localStorage, sessionStorage, cookie처럼 브라우저에 데이터를 임시 저장할 수 있는 저장공간 영역.
이 때, 렌더링 엔진은 크게 다음과 같은 단계를 거쳐 화면을 그린다.
CRP 과정 요약
DOM Tree & CSSOM Tree 파싱
> 렌더 트리 생성
> Layout
> Paint
파싱
- HTML을 파싱하여 DOM Tree를 그린다. (HTML의 DOM Tree가 맞음)파싱
- CSS와 스타일 요소를 파싱하여 CSSOM Tree를 그린다.렌더 트리 생성
- 만들어진 DOM Tree와 CSSOM Tree를 합쳐 렌더 트리를 만든다.Layout(Reflow)
- 요소의 위치와 크기 등을 계산하여 브라우저에 배치한다.Paint(Repaint)
- 배치된 픽셀 정보를 이용하여 실제 화면에 요소를 그린다.
위와 같은 과정을 Critical Rendering Path라고 부르며, 레이아웃과 페인트 단계를 얼마나 효율적으로 생략하느냐에 따라 웹서비스의 렌더링 효율이 달라지게 된다. (특히 애니메이션을 구현할 때 영향을 많이 미친다.)
CSS 속성별로 수정시 Layout 이후의 과정을 모두 거쳐야하는 속성, Paint 이후의 과정만 거치는 속성, Layout과 Paint를 거칠 필요 없이 Composite만 수행해도 되는 속성이 정해져있다.
tranform이 Composite만 발생하는 대표적인 속성인데, 그래서 애니메이션을 많이 넣어야 하는 웹페이지의 경우 transform을 우선적으로 사용하는 듯. (그렇게 배워서 그렇게 쓰고는 있었지만 이유는 CRP 공부하며 처음 알았다.)
# TIL 블로그 github action 자동배포 성공
지난번에 실패했던 github action을 이용한 자동 배포에 성공했다.
어이없게도 github action 설정 yml 파일에 오타가 있어서 안되는 거였음.. 😂
이제는 main 브랜치에 소스코드를 push하면 자동으로 페이지 배포가 된다.
deploy 명령어는 혹시 또 쓸 일이 있을지 모르니 살려두었다.
# 2022.09.14
# nodejs 연습
코드캠프 백엔드 수업 커리큘럼을 참고하여 nodejs를 이용해서 간단한 함수나 템플릿 만드는 방법을 연습 중.
실행하는 부분이 브라우저에서 nodejs로 바뀌었을 뿐인데도 굉장히 신기하다. javascript가 작동하는 원리는 동일한데도!
오늘의 기억할 점
vercel 등으로 개발 환경을 세팅하지 않고 순수 node만으로 module 타입의 ES6 자바스크립트를 실행할 경우, import해오는 js파일에도 반드시 확장자명을 붙여줘야 한다. 그렇지 않을 경우 에러를 뱉음.import { checkValidationEmail, getWelcomeTemplate, sendWelcomeTemplateToEmail, } from "./email";
이런 식으로 쓰면
이렇게 에러를 뱉는다.
귀찮아도 꼭.js
붙여줘야 함..😇import { checkValidationEmail, getWelcomeTemplate, sendWelcomeTemplateToEmail, } from "./email.js";
백엔드 공부를 갑작스레 시작한 이유
간단한 api는 스스로 개발해서 개인 프로젝트 배포까지 진행하고 싶기 때문.
Next.js에서 제공하는 api 기능을 이용해도 좋지만, 기왕 공부하는 거 학습을 위해서라도 백엔드에 대해 좀 더 알아보고 싶었다. 새로운 언어를 배운다면 시간이 오래 걸리겠지만 javascript는 원래 사용하고 있던 언어이기도 하고.
간단한 프로덕트는 나 혼자서 처음부터 끝까지 개발할 수 있는 개발자가 되고 싶다. 💪
# 2022.09.12
# Typescript - 타입 가드
ref
원티드 프리온보딩 챌린지 9월 - Typescript의 강의 내용을 일부 요약 정리 해놓은 것
- 타입 가드란?
타입스크립트가 런타임 환경에서의 모든 오류를 방지해 주는 것은 아니다.
런타임 환경에서 실행했을때에만 확인할 수 있는 요소 정보의 경우 (dom 조작 등) type guard를 통해 미연에 방지하는 것이 좋다.
타입 가드의 예시
const $modalClose = document.querySelector(".modalCloseButton"); // 3. 최소한의 타입 가드 if ($modalClose) { $modalClose?.addEventListener("click", () => "aaa"); }
타입 좁히기란? (Type narrowing)
예시를 살펴보자.const $app: HTMLElement = document.createElement("div"); // 타입추론의 효과가 거의 없는 타입 const func: Function = function () { return "string"; };
위와 같은 스크립트의 경우 실질적으로 any script나 다를바 없다. 연습을 통해 타입 추론을 예측하고 타입을 좁혀나가는 연습을 해야 한다.
const $app: HTMLElement = document.createElement("div"); const $app: HTMLDivElement = document.createElement("div");
컴파일 vs 런타임
prop-types vs typescript → 무슨 차이가 있을까?
바로 런타임시 타입 체크 vs 컴파일시 타입 체크의 차이.
typescript는 컴파일시 js로 변경되기 때문에 런타임시 발생할 수 있는 타입으로 인한 오류는 checking 하지 못하는 문제가 있다.그렇다면 이러한 문제를 방지하기 위해서는 반드시 props-types 등의 추가 도구를 사용해야 하는가?
‼️‼️‼️ 아니다!
타입 가드를 잘 사용하면 런타임시 발생할 수 있는 타입 에러도 상당부분 방지할 수 있다.unknown을 활용한 예시를 통해 확인해보자.
let num: unknown = 99; // 타입가드 예시 1 // if (typeof num === 'string') { num.trim(); // } // 타입가드 예시 2 // (num as string).trim(); // 스크립트 내부에 인라인 타입가드를 넣는 것도 가능 (typdof, instanceof 등 사용) // function func(x: unknown) { // let val1: any = x; // let val2: unknown = x; // let val3: string = x; // let val4: boolean = x; // let val5: number = x; // let val6: string[] = x; // let val7: {} = x; // }
unknown은 any와 비슷하게 사용할 수 있지만 에러를 던진다.
즉, 타입 가드를 유도한다.
# 2022.09.11
# Netflix-reviews 프로젝트
댓글 CRD를 완료하고 리뷰 본문과 댓글 Update 작업을 시작했다.
그런데 본문 Update 코드를 추가하려고 보니 ReviewsWrite 페이지 스크립트가 많이 길어졌길래 가능한 코드들을 custom-hooks로 분리해서 리팩토링 하는 작업을 우선 시작했다.
const onChangeFile = async (file: ChangeEvent<HTMLInputElement>) => {
changeFile({ file, setImages, images });
};
const handleFileBtn = () => {
if (inputRef.current) {
inputRef.current.click();
}
};
const onSubmitWrite = (data: FieldValues) => {
reviewsSubmit(data, images);
};
const onSubmitEdit = (data: FieldValues) => {
const originImages = data.fetchBoard?.images || [""];
reviewsEdit(data, images, originImages);
};
깔끔~
작성 / 수정 / 이미지 업로드처럼 볼륨이 큰 스크립트들을 custom-hooks로 분리했고, 동일한 쿼리와 기능을 요청하는 경우에는 해당 커스텀훅을 가져다 쓸 수 있도록 인자 설정도 해두었다.
앞으로 할 일
- react-hook-form의 yupResolver schema가 수정/작성 페이지 여부에 따라 다르게 적용되도록 하기.
- 게시글 수정 후 상세페이지로 돌아왔을 때 ApolloCacheState가 변경되도록 하기
- fetchPolicy를 변경할까 cache를 직접 수정할까 고민했는데, cache를 직접 수정하는 쪽이 더 효율적일 것이라고 판단했다.