├── README.md ├── architecture ├── README.md └── assets │ └── img │ └── clean-architecture.png ├── browser ├── README.md └── assets │ └── img │ ├── cache-control.png │ ├── data-from-cache.png │ ├── data-from-server.png │ ├── disk-cache.png │ ├── memory-cache-vs-disk-cache.png │ ├── memory-cache.png │ ├── next.js-api-routes.png │ ├── script.png │ ├── web-cache.png │ └── web-performance-metrics.png ├── css └── README.md ├── html └── README.md ├── javascript ├── README.md └── assets │ └── img │ └── execution-context.png ├── next.js ├── README.md └── assets │ └── img │ ├── data-cache.avif │ ├── request-memoization.avif │ └── router-cache.avif ├── project-setup └── TypeScript+Nextjs+ESLint+Prettier+Emotion+TaillwindCSS+Storybook.md ├── react-hook-form └── README.md ├── react-query └── README.md ├── react-rendering-optimization └── Challenge.md ├── react ├── README.md └── assets │ └── img │ ├── react-hook-flow-diagram.png │ └── virtual-dom.png └── typescript └── README.md /README.md: -------------------------------------------------------------------------------- 1 | # frontend-basic-concept 2 | Browser, HTML, CSS, JavaScript, TypeScript, React, Next.js 등 Frontend Software Engineer로서 갖추어야 할 기본적인 Frontend 지식을 정리해둔 공간입니다. 3 | 4 | ## [Browser](https://github.com/sekhyuni/frontend-basic-concept/blob/main/browser/README.md) 5 | - [x] Communication Process 6 | - [x] Rendering Process 7 | - [x] CSR vs SSR 8 | - [x] Local Storage vs Session Storage vs Cookies 9 | - [x] CORS 10 | - [x] Cache 11 | - [x] Web Performance Metrics 12 | - [x] Event Bubbling vs Event Capturing 13 | - [x] Script 14 | - [x] Authentication 15 | 16 | ## [HTML](https://github.com/sekhyuni/frontend-basic-concept/blob/main/html/README.md) 17 | - [x] Semantic Tags 18 | - [x] Not Semantic Tags 19 | 20 | ## [CSS](https://github.com/sekhyuni/frontend-basic-concept/blob/main/css/README.md) 21 | - [x] display 22 | - [x] position 23 | - [x] overflow 24 | - [ ] Layout Shift 25 | - [x] styled-components vs Emotion vs Tailwind CSS 26 | - [ ] ETC 27 | 28 | ## [JavaScript](https://github.com/sekhyuni/frontend-basic-concept/blob/main/javascript/README.md) 29 | - [x] Execution Context 30 | - [x] Scope 31 | - [x] Hoisting 32 | - [x] Closure 33 | - [x] Asynchronous Processing 34 | - [x] Prototype 35 | - [x] First-class object 36 | - [x] Object vs Map 37 | 38 | ## [TypeScript](https://github.com/sekhyuni/frontend-basic-concept/blob/main/typescript/README.md) 39 | - [x] Type Options 40 | - [x] interface vs type 41 | - [x] Utility Type 42 | - [x] Index Signature 43 | - [x] Use Property Type Of Any Interface 44 | - [x] Generic 45 | - [x] Readonly 46 | 47 | ## [React](https://github.com/sekhyuni/frontend-basic-concept/blob/main/react/README.md) 48 | - [x] Benefits of using React 49 | - [x] Rendering Process 50 | - [x] Reconciliation 51 | - [x] Life-Cycle 52 | - [x] Hooks 53 | - [ ] Performance Optimization 54 | 55 | ## [Next.js](https://github.com/sekhyuni/frontend-basic-concept/blob/main/next.js/README.md) 56 | - [ ] Rendering 57 | - [ ] Caching 58 | 59 | ## [React Query](https://github.com/sekhyuni/frontend-basic-concept/blob/main/react-query/README.md) 60 | - [x] Life-Cycle 61 | - [x] useQuery 62 | - [ ] useMutation 63 | - [x] QueryClient 64 | 65 | ## [React Hook Form](https://github.com/sekhyuni/frontend-basic-concept/blob/main/react-hook-form/README.md) 66 | - [x] Controlled vs Uncontrolled 67 | 68 | ## [Architecture](https://github.com/sekhyuni/frontend-basic-concept/blob/main/architecture/README.md) 69 | - [x] Clean Architecture -------------------------------------------------------------------------------- /architecture/README.md: -------------------------------------------------------------------------------- 1 | # Architecture 2 | 3 | * [Clean Architecture](#clean-architecture) 4 | 5 | ## Clean Architecture 6 | ![Clean Architecture](./assets/img/clean-architecture.png) 7 | - Layers 8 | - Frameworks and Drivers 9 | - 정의: MongoDB Node.js Driver, firestore와 같은 Driver 또는 mongoose와 같은 ORM을 사용해서 DB Connection을 맺고 CRUD를 하는 등의 작업을 수행 10 | ```typescript 11 | // src/frameworks/getArticleListAPI.ts 12 | import { firestore } from './admin'; 13 | 14 | interface GetArticleResponse { 15 | id: string; 16 | media: string; 17 | title: string; 18 | createdAt: { _seconds: number }; 19 | link: string; 20 | } 21 | 22 | export async function getArticleListAPI(): Promise { 23 | const articlesCollectionRef = firestore.collection('articles'); 24 | const articlesQuerySnapshot = await articlesCollectionRef.orderBy('createdAt', 'desc').get(); 25 | 26 | const response = articlesQuerySnapshot.docs.map((articleQuerySnapshot) => ({ 27 | ...articleQuerySnapshot.data(), 28 | id: articleQuerySnapshot.id, 29 | } as GetArticleResponse)); 30 | 31 | return response; 32 | } 33 | ``` 34 | - Interface Adapters (Infrastructure) 35 | - 정의: 외부 요소(DB 또는 Web)로부터 얻은 데이터를 Entities 또는 Use Cases에 적합한 형식으로 변환하거나, 그 반대의 작업을 수행 36 | ```typescript 37 | // src/infrastructures/repository/articleAPIRepository.ts 38 | import type { Article, ArticleRepository } from '@/entities/article'; 39 | import { getArticleListAPI } from '@/frameworks/getArticleListAPI'; 40 | 41 | export default class ArticleAPIRepository implements ArticleRepository { 42 | async getArticleList(): Promise { 43 | const articleList = await getArticleListAPI(); 44 | 45 | return articleList.map((article) => { 46 | const createdAtTimestamp = article.createdAt._seconds * 1000; 47 | 48 | return { 49 | ...article, 50 | createdAt: createdAtTimestamp 51 | } 52 | }); 53 | } 54 | } 55 | ``` 56 | - Use Cases 57 | - 정의: 애플리케이션의 특정한 사용 사례나 시나리오를 구현하며, Entities에는 의존하지만 외부 요소들에는 의존하지 않음 58 | ```typescript 59 | // src/usecases/articleService.ts 60 | import type { Article, ArticleRepository } from '@/entities/article'; 61 | 62 | export class ArticleService { 63 | private readonly repository: ArticleRepository | null; 64 | 65 | constructor(repository: ArticleRepository) { 66 | this.repository = repository ?? null; 67 | } 68 | 69 | getArticleList(): Promise { 70 | if (!this.repository) { 71 | throw new Error('repository is required'); 72 | } 73 | 74 | const articleList = this.repository.getArticleList(); 75 | 76 | return articleList; 77 | } 78 | } 79 | ``` 80 | - Entities 81 | - 정의: 애플리케이션의 비즈니스 로직을 포함하며, 어떠한 외부 요소에도 의존하지 않는 순수한 비즈니스 규칙을 담음 82 | ```typescript 83 | // src/entities/article.ts 84 | export interface Article { 85 | id: string; 86 | media: string; 87 | title: string; 88 | createdAt: number; 89 | link: string; 90 | } 91 | 92 | export interface ArticleRepository { 93 | getArticleList(): Promise; 94 | } 95 | ``` 96 | - 사용 예시 (Next.js 13 App Router) 97 | ```tsx 98 | import { ArticleService } from '@/usecases/articleService'; 99 | import ArticleAPIRepository from '@/infrastructures/repository/articleAPIRepository'; 100 | 101 | import ArticleSection from '@/components/ArticleSection'; 102 | 103 | export default async function Home() { 104 | const articleService = new ArticleService(new ArticleAPIRepository()); 105 | 106 | const articleList = await articleService.getArticleList(); 107 | 108 | return ( 109 | <> 110 | 111 | 112 | ); 113 | } 114 | ``` 115 | 116 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
117 | [맨 위로 가기](#architecture) -------------------------------------------------------------------------------- /architecture/assets/img/clean-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sekhyuni/frontend-basic-concept/373798ba22b3b05f9302222f697491583a0ca6cc/architecture/assets/img/clean-architecture.png -------------------------------------------------------------------------------- /browser/README.md: -------------------------------------------------------------------------------- 1 | # Browser 2 | 3 | * [Communication Process](#communication-process) 4 | * [Rendering Process](#rendering-process) 5 | * [CSR vs SSR](#csr-vs-ssr) 6 | * [Local Storage vs Session Storage vs Cookies](#local-storage-vs-session-storage-vs-cookies) 7 | * [CORS](#cors) 8 | * [Cache](#cache) 9 | * [Web Performance Metrics](#web-performance-metrics) 10 | * [Event Bubbling vs Event Capturing](#event-bubbling-vs-event-capturing) 11 | * [Script](#script) 12 | * [Authentication](#authentication) 13 | 14 | ## Communication Process 15 | 1. 브라우저가 UDP 프로토콜을 통해 DNS에게 입력한 도메인 이름에 해당하는 IP 주소를 요청 16 | 1. 응답받은 IP 주소를 토대로 서버와의 TCP 연결을 진행 17 | 1. 브라우저가 생성한 HTTP 요청을 TCP 연결을 통해 서버로 전송 18 | 1. 서버는 전달받은 HTTP 요청을 처리한 후, TCP 연결을 통해 HTTP 응답을 브라우저로 전송 19 | 1. 브라우저는 전달받은 HTTP 응답을 처리하여 화면에 렌더링 20 | 21 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
22 | [맨 위로 가기](#browser) 23 | ## Rendering Process 24 | - 순서 25 | 1. HTML과 CSS를 Parsing하여 DOM Tree와 CSSOM Tree 생성 (Parsing) 26 | 1. DOM Tree와 CSSOM Tree로 Render Tree 구축 (Style) 27 | 1. Render Tree 배치 (Layout) 28 | 1. Painting (Paint) 29 | - reflow와 repaint 30 | - reflow 31 | - 정의: 특정 요소의 속성값이 변경됨에 따라 Render Tree를 재배치하고 Painting 작업을 다시 진행하는 것 32 | - 대표적인 속성: position, top, right, bottom, left, display, width, height, padding, border, margin, font-size, font-weight, etc. 33 | - repaint 34 | - 정의: 특정 요소의 속성값이 변경됨에 따라 Painting 작업을 다시 진행하는 것 35 | - 대표적인 속성: visibility, border-radius, border-style, box-shadow, outline, text-decoration, color, background, etc. 36 | - 참고 37 | - opacity 값이 1이 아니면 요소를 새로운 stacking context에 배치하게 되며, 이에 따라 reflow는 발생하지 않고 repaint만 발생 38 | - 렌더링 최적화 39 | 1. 요소 숨기기 40 | 1. 사용하지 않는 요소에는 visibility: hidden보다 display: none 사용 (display: none으로 처리된 요소는 reflow가 일어나지 않기 때문) 41 | 1. 사용/미사용이 가끔 변경되는 요소이나, 요소의 위치가 변하면 안 되는 경우 visibility: hidden 사용 (display: none으로 처리된 요소는 document에서 완전히 사라지기 때문) 42 | 1. 사용/미사용이 자주 변경되는 요소에는 display: none보다 visibility: hidden 사용 (display 속성 변경으로 인해 reflow가 일어나기 때문) 43 | 44 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
45 | [맨 위로 가기](#browser) 46 | ## CSR vs SSR 47 | ||처리하는 곳|서버 부하|초기 로딩 속도|페이지 리로드|검색엔진최적화 대응| 48 | |:---:|:---:|:---:|:---:|:---:|:---:| 49 | |CSR|Browser|X|상대적으로 느림|X|어려움| 50 | |SSR|Web Server (Node.js, Tomcat, etc.)|O|상대적으로 빠름|O|수월함| 51 | - 참고 52 | - CSR 방식이 SSR 방식보다 SEO 대응에 불리한 이유: 검색엔진 크롤러가 특정 페이지를 서버에 요청했을 때, SSR 방식과 달리 CSR 방식은 응답 결과에 동적인 컨텐츠가 존재하지 않기 때문 53 | - CSR 방식에서도 meta 태그를 사용해서 어느 정도 SEO 대응이 가능 54 | - CSR 방식에서도 Code Splitting을 사용해서 초기 로딩 속도 개선 가능 55 | - Next.js에서의 SSR은 서버에서 정적 페이지를 생성한 뒤, 클라이언트에서 해당 페이지를 hydrate 하는 방식으로 동작 56 | - Next.js에서의 Rendering은 CSR과 SSR이 혼합된 방식 57 | - CSR 58 | - next/link의 Link 컴포넌트가 클릭됐을 때 59 | - next/router의 router.push 함수가 호출됐을 때 60 | - SSR 61 | - 초기 페이지가 로드됐을 때 62 | - 페이지가 리로드됐을 때 63 | - anchor 요소가 클릭됐을 때 64 | 65 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
66 | [맨 위로 가기](#browser) 67 | ## Local Storage vs Session Storage vs Cookies 68 | ||크기|수명|저장 장소|접근 가능한 곳|서버로 HTTP 요청 시, 자동 전송 여부| 69 | |:---:|:---:|:---:|:---:|:---:|:---:| 70 | |Local Storage|전체 5MB|수동으로 제거될 때까지|Disk/Browser Memory|Browser|X| 71 | |Session Storage|전체 5MB|수동으로 제거되거나 브라우저 탭이 닫힐 때까지|Browser Memory|Browser|X| 72 | |Cookies|개당 4KB, 도메인당 최대 20개 저장 가능, 전체 80KB|만료 시간이 지날 때까지|Browser Memory|Browser/Server|O| 73 | - Cookies에서 가능한 설정 74 | - HttpOnly: 브라우저 접근 가능 여부 (기본값: false) 75 | - SameSite: 전송 가능 범위 (기본값: Lax) 76 | - None: Third-Party Cookies 전송 가능 (단, Secure: true가 함께 적용되어야 함) 77 | - Lax: 몇가지 예외적인 요청(GET과 같이 안전하다고 판단되는)을 제외하고는 Third-Party Cookies 전송 불가 78 | - Strict: 항상 First-Party Cookies만 전송 가능 79 | - Secure: https가 적용된 요청만 전송 가능 여부 (기본값: false, chrome에서 도메인이 localhost인 경우는 예외처리됨) 80 | - Domain: 도메인 (기본값: 쿠키를 설정한 서버 도메인) 81 | - Cross Origin으로 Cookies를 전송하기 위한 설정 82 | - 조건: 브라우저와 서버 간의 통신에만 해당되며, 서버와 서버 간의 통신에는 해당되지 않음 83 | - 방법 84 | - 서버: HTTP Response Header의 Access-Control-Allow-Credentials 속성값을 true로 설정 85 | - 클라이언트 86 | - XMLHttpRequest: withCredentials 속성값을 true로 설정 87 | - fetch: credentials 속성값을 'include'로 설정 88 | - axios: withCredentials 속성값을 true로 설정 89 | 90 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
91 | [맨 위로 가기](#browser) 92 | ## CORS 93 | - 정의: CORS는 Cross Origin Resource Sharing의 약자이며, 클라이언트와 서버가 교차 출처임에도 서로 상호작용이 가능하도록 도와주는 브라우저의 정책 94 | - 역사: 기존에는 브라우저에서 서버의 응답을 받으려면 Same Origin(동일 출처)일 경우에만 가능했으나, 웹 생태계가 발전하고 서로 다른 Origin임에도 상호작용해야 할 상황들이 많아지면서 CORS 정책의 필요성이 대두되었고, 이에 따라 최초로는 2004년에 Tellme Networks라는 회사에서 도입을 제안한 뒤, 최종적으로는 2014년에 W3C에서 공식적으로 표준화 95 | - 설정: 서버 측에서 HTTP Response Header의 Access-Control-Allow-Origin 속성에 요청을 허용할 클라이언트 측 Origin을 등록 96 | - Preflight Request 97 | - 정의: 교차 출처 HTTP 요청 전에 서버 측에서 그 요청의 메서드와 헤더에 대해 인식하고 있는지를 체크하는 것 98 | - 확인하는 것: Access-Control-Request-Headers, Access-Control-Request-Method, Origin 99 | - 보내는 이유: 서버는 기본적으로 모든 요청에 대해 처리를 하기 때문에 수정이나 삭제와 같은 위험한 요청에 대해서는 먼저 유효성 검증을 한 뒤에 본 요청을 보낼지 말지 결정하기 위함 100 | - 보내는 조건: 메서드가 GET, POST, HEAD가 아닌 모든 경우 (단, POST는 Content-Type이 application/x-www-form-urlencoded, multipart/form-data, text/plain 중 하나여야 함) 101 | - Simple Request 102 | - 정의: Preflight Request를 생략하고 곧장 서버로 본 요청을 보내는 것 103 | - 보내는 조건: 메서드가 GET, POST, HEAD인 경우 (단, POST는 Content-Type이 application/x-www-form-urlencoded, multipart/form-data, text/plain 중 하나여야 함) 104 | - 프로세스: 요청 자체는 무조건 서버로 보내며, 서버 측에서 HTTP Response Header의 Access-Control-Allow-Origin 속성에 클라이언트 측 Origin이 등록되어 있는지의 여부에 따라 브라우저에서 응답을 차단할지 말지 정함 105 | - Frontend에서 CORS Errors를 우회하는 방법 106 | - axios 사용 시, baseURL에 API 서버의 Origin을 설정하지 않아야 함 (클라이언트에서 API 서버가 아니라, Proxy 역할을 하는 Frontend 서버로 요청해야 한다는 뜻) 107 | - Proxy 설정 시, API 서버의 Origin을 설정해야 함 (Proxy 역할을 하는 Frontend 서버에서 API 서버로 요청해야 한다는 뜻) 108 | 1. React 109 | - setupProxy.js에서 http-proxy-middleware 라이브러리를 사용하여 Proxy 환경 구성 (dev에서만 가능) 110 | ```javascript 111 | // setupProxy.js 112 | const { createProxyMiddleware } = require('http-proxy-middleware'); 113 | 114 | const proxy = { 115 | target: 'http://{apiServerIP}:{apiServerPort}', 116 | }; 117 | 118 | module.exports = app => { 119 | app.use([ 120 | '/a', 121 | '/b', 122 | '/c', 123 | ], 124 | createProxyMiddleware(proxy) 125 | ); 126 | }; 127 | ``` 128 | ```typescript 129 | // index.ts 130 | import Axios from 'axios'; 131 | 132 | const isProd = process.env.NODE_ENV === 'production'; 133 | 134 | export const apiServerOrigin = 'http://{apiServerIP}:{apiServerPort}'; 135 | 136 | const axios = Axios.create({ 137 | baseURL: isProd ? apiServerOrigin : '', 138 | }); 139 | 140 | export default axios; 141 | ``` 142 | 1. Next.js 143 | - next.config.js 파일에 rewrite 설정을 함으로써 Proxy 환경 구성 또는 API Routes를 통해 Proxy 환경 구성 (dev, prod 둘 다 가능) 144 | ```javascript 145 | // next.config.js 146 | /** @type {import('next').NextConfig} */ 147 | const nextConfig = { 148 | reactStrictMode: true, 149 | async rewrites() { 150 | return [ 151 | { 152 | source: '/:path*', 153 | destination: 'http://{apiServerIP}:{apiServerPort}/:path*', 154 | }, 155 | ]; 156 | }, 157 | }; 158 | 159 | module.exports = nextConfig; 160 | ``` 161 | ```typescript 162 | // index.ts 163 | import Axios from 'axios'; 164 | 165 | const isProd = process.env.NODE_ENV === 'production'; 166 | 167 | export const apiServerOrigin = 'http://{apiServerIP}:{apiServerPort}'; 168 | 169 | const axios = Axios.create({ 170 | baseURL: isProd ? apiServerOrigin : '', 171 | }); 172 | 173 | export default axios; 174 | ``` 175 | - Frontend 서버에 Proxy 환경을 구성함으로써 CORS Errors를 우회하는 원리 176 | 1. 브라우저에서 API 요청 시, Proxy 서버로 요청을 보냄 (클라이언트와 Proxy 서버는 Same Origin이므로 별도 설정 없이 상호작용이 가능) 177 | - Proxy 서버는 로컬에서 실행되는 서버이며, Frontend App이 실행되는 서버와 Proxy 서버가 각각 실행되는 것이 아니라, Frontend App이 실행되는 서버가 Proxy 역할을 수행함. 따라서 Frontend App이 localhost:3000이라는 Host로 실행되면, Proxy 서버의 Host도 localhost:3000가 되는 것임 178 | - 만약 Proxy 서버가 Frontend App이 실행되는 서버와 다른 Host로 실행된 경우, Proxy 서버에서 응답 헤더의 Access-Control-Allow-Origin 속성에 클라이언트 측 Origin 정보를 등록하면 됨 179 | 1. Proxy 서버로 요청이 들어오면 해당 요청을 API 서버로 보냄 180 | - Proxy 서버에서 해당 요청을 API 서버로 보낼 수 있는 이유는 브라우저를 통하지 않는 상호작용이기 때문임 181 | 1. API 서버에서 요청 처리 후, 응답을 Proxy 서버로 보냄 182 | 1. Proxy 서버에서 응답을 브라우저로 보냄 183 | - 만약 Proxy 서버가 Frontend App이 실행되는 서버와 다른 Host로 실행된 경우, 브라우저는 응답 헤더를 확인하여 CORS 설정이 잘 되어있다는 판단 하에 정상 처리 184 | 185 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
186 | [맨 위로 가기](#browser) 187 | ## Cache 188 | ### 캐시란 무엇인가? 189 | - 정의: 자주 사용되는 데이터를 저장해둔 저장소 190 | - 설정: 서버 측에서 HTTP Response Header의 Cache-Control 속성을 통해 설정 191 | - no-store: 데이터를 캐싱하지 않음 192 | - no-cache: 데이터를 캐싱하긴 하지만, 매 요청마다 서버 측에서 유효성 검사를 해야 함 193 | - max-age={seconds}: {seconds} 동안은 캐싱된 데이터를 사용하며, {seconds}가 지나면 서버 측에서 유효성 검사를 해야 함 194 | - public: 브라우저, 프록시 서버 등 어디에서든 데이터를 캐싱 가능 195 | - private: 브라우저에서만 데이터를 캐싱 가능 196 | - s-maxage={seconds}: 프록시 서버 등 중간 서버에서만 적용되는 속성으로 {seconds} 동안은 캐싱된 데이터를 사용하며, {seconds}가 지나면 서버 측에서 유효성 검사를 해야 함 197 | - 유효성 검사: 클라이언트 측에서 가지고 있던 캐싱된 데이터의 If-None-Match 값과 서버 측에서 생성한 ETag 값을 비교 198 | - Frontend에서 서버의 데이터를 캐싱하는 방법 199 | - React Query 200 | - staleTime: staleTime 동안 캐싱된 데이터를 사용함 201 | - cacheTime: 쿼리가 inactive된 시점부터 cacheTime까지 데이터를 캐싱함 202 | - Next.js API Routes 203 | - HTTP Response Header의 Cache-Control 속성을 통해 설정 204 | ### 브라우저에서 서버의 데이터를 캐싱하는 방식 205 | ![Web Cache](./assets/img/web-cache.png) 206 | - 브라우저가 데이터를 캐싱하는 장소: 메모리 또는 디스크 207 | - 브라우저가 데이터를 캐싱할 장소를 정하는 방법: 내부 알고리즘에 의해 결정되며, 이 알고리즘은 브라우저에 따라 다르지만 일반적으로 데이터의 크기, 사용 빈도, 최근에 액세스한 시간 등을 고려하여 결정됨 208 | - 메모리 캐시 vs 디스크 캐시 성능 비교 209 | ![Memory Cache vs Disk Cache](./assets/img/memory-cache-vs-disk-cache.png) 210 | - 메모리 캐시 (브라우저 내부에 존재, hashmap 구조) => 탐색하는데에 약 0ms 소요 211 | ![Memory Cache](./assets/img/memory-cache.png) 212 | - 디스크 캐시 (브라우저 외부에 존재) => 탐색하는데에 약 30ms ~ 300ms, 최대 1.4s 소요 213 | ![Disk Cache](./assets/img/disk-cache.png) 214 | - 실제로 데이터를 메모리와 디스크에 캐싱하는 예시 215 | 1. 캐싱된 데이터가 없는 상태로 구글 메인 페이지에 접속하면, 모든 데이터를 서버에서 가져옴 216 | ![Data From Server](./assets/img/data-from-server.png) 217 | 1. 새로고침을 통해 재접속을 하면, 특정 데이터들은 메모리 또는 디스크에서 가져옴 218 | ![Data From Cache](./assets/img/data-from-cache.png) 219 | - 캐싱된 데이터의 유효성 검사 주기를 정하는 방법: 서버에서 응답 헤더의 Cache-Control 속성값으로 max-age={seconds}을 설정 220 | ![Cache-Control](./assets/img/cache-control.png) 221 | ```typescript 222 | // Express.js Server Example 223 | const app = express(); 224 | 225 | app.get('/api/endpoint', (_, res) => { 226 | res.set('Cache-Control', 'max-age=3600'); 227 | 228 | res.status(200).json({ message: 'success' }); 229 | }); 230 | ``` 231 | 1. {seconds}가 지나기 전에는 서버에 데이터 요청을 하지 않고 메모리 또는 디스크에서 캐싱된 데이터를 가져옴 232 | 1. {seconds}가 지나면 캐싱된 데이터를 지우는 것이 아니라, 서버에 유효성 검사 요청을 보냄 233 | - 유효성 검사는 클라이언트 측에서 가지고 있던 캐싱된 데이터의 If-None-Match 값과 서버 측에서 생성한 ETag 값이 일치하는지 확인하는 과정 234 | - Nginx, Apache, Tomcat과 같은 대부분의 Web Server(or WAS)는 ETag 값을 통해 캐싱된 데이터의 유효성을 검사하는 프로세스가 기본적으로 내장되어 있음 235 | 1. 유효성 검사 결과, 브라우저에서 가지고 있는 캐싱된 데이터가 유효하면 서버는 304 Not Modified 응답을 보내며, 캐싱된 데이터가 유효하지 않으면 서버는 200 Success 응답을 보내는 동시에 새로운 데이터에 대한 Cache-Control 속성값으로 max-age={seconds}를 갱신함 236 | 237 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
238 | [맨 위로 가기](#browser) 239 | ## Web Performance Metrics 240 | ![Web Performance Metrics](./assets/img/web-performance-metrics.png) 241 | 1. 페인트 관련 지표 242 | - FP(First Paint): 픽셀(배경색 등)을 처음 렌더링한 시점 243 | - FCP(First Contentful Paint): 의미있는 컨텐츠(텍스트, 이미지 등)를 처음 렌더링한 시점 244 | - LCP(Largest Contentful Paint): 가장 크고 의미있는 컨텐츠(텍스트, 이미지)를 렌더링한 시점 245 | 1. 로딩 관련 지표 246 | - DCL(DOMContentLoaded): HTML 파싱이 완료된 후, 스크립트(+defer 속성이 적용된)의 실행이 완료된 시점 247 | - L(LOAD): 웹 페이지에 있는 모든 리소스(스크립트, 스타일시트, 이미지, 기타 미디어 파일 등)의 로드가 완료된 시점 248 | 249 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
250 | [맨 위로 가기](#browser) 251 | ## Event Bubbling vs Event Capturing 252 | 1. 이벤트 버블링 253 | - 특정 요소에서 이벤트가 발생하면 최상위에 있는 html 요소까지 이벤트가 전파되어가는 특성 254 | - 대표적으로 버블링되는 이벤트: click, mousedown, mouseup, wheel, scroll, keydown, keyup 255 | ```html 256 | 257 | 258 | 284 | 285 | 286 |
287 |
288 |
289 | 320 | 321 | 322 | ``` 323 | - 이벤트 버블링을 활용한 예 324 | - Event Delegation 325 | - 이벤트가 버블링되는 특성을 활용하여 이벤트 핸들링이 필요한 하위 요소들의 상위 요소가 하위 요소들의 이벤트를 처리하는 패턴 326 | ```html 327 |
328 |
329 | 330 | 331 | 332 |
333 |
334 | 349 | ``` 350 | - 구현 순서 351 | 1. 컨테이너에 하나의 핸들러를 할당 352 | 1. 핸들러의 event.target을 사용해서 이벤트가 발생한 요소가 어디인지 알아냄 353 | 1. 원하는 요소에서 이벤트가 발생했다고 확인되면 이벤트를 핸들링 354 | - 장점 355 | 1. 많은 핸들러를 할당하지 않아도 되기 때문에 초기화가 단순해지고 메모리가 절약됨 356 | 1. 요소를 추가하거나 제거할 때 해당 요소에 할당된 핸들러를 추가하거나 제거할 필요가 없음 357 | - 단점 358 | 1. 이벤트 위임을 사용하려면 이벤트가 반드시 버블링되어야 하는데, 몇몇 이벤트는 버블링 되지 않음 359 | 1. 컨테이너에 할당된 핸들러가 모든 하위 요소에서 발생하는 이벤트에 응답해야 하므로 CPU 작업 부하가 늘어날 수 있으나, 이런 부하는 무시할만한 수준이므로 실제로는 잘 고려하지 않음 360 | 1. 이벤트 캡처링 361 | - 특정 요소에서 이벤트가 발생했을 때 해당 이벤트가 최상위 요소인 html 요소부터 가장 하위에 있는 요소까지 점점 더 하위 요소들로 전달되어가는 특성 362 | ```html 363 | 364 | 365 | 391 | 392 | 393 |
394 |
395 |
396 | 431 | 432 | 433 | ``` 434 | 1. 이벤트 전파 막기 435 | ```javascript 436 | event.stopPropagation(); // 버블링 또는 캡처링 전파를 막고 싶을 때 437 | event.stopImmediatePropagation(); // 버블링 또는 캡처링 전파뿐만 아니라, 현재 실행중인 이벤트 핸들러 이후 어떤 이벤트 핸들러도 실행시키지 않고 싶을 때 438 | ``` 439 | 440 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
441 | [맨 위로 가기](#browser) 442 | ## Script 443 | ![Script](./assets/img/script.png) 444 | 1. default 445 | - 동작: 브라우저의 렌더링 엔진이 HTML을 Parsing하다가 script 태그를 만나면 (외부 script의 경우 다운로드 후) 실행한 뒤, 남은 HTML을 Parsing함 446 | - 문제 447 | - script 태그 아래에 있는 HTML element에 접근할 수 없기 때문에 HTML element에 이벤트 핸들러를 추가하는 것과 같은 여러 행위를 할 수 없음 448 | - script 태그가 HTML 상단부에 위치할 경우, (외부 script의 경우 다운로드 후) script를 실행하는 동안 script 태그 하단부에 있는 남은 HTML을 Parsing할 수 없음 449 | - 해결 방법 450 | - 단적으로 script 태그를 HTML 하단부에 위치시키면 되지만, HTML 용량이 매우 큰 경우, script 다운로드를 시작하는데에 너무 오랜 시간이 걸린다. 451 | - defer 또는 async 속성을 사용하면 백그라운드에서 script 다운로드를 시작할 수 있다. 452 | 1. defer 453 | - 특징 454 | - script 다운로드를 하는 동안 HTML parsing을 멈추지 않음 455 | - 다른 script와 동시에 다운로드할 수 있음 456 | - HTML parsing 완료 -> script 실행 -> DOMContentLoaded 이벤트 발생 457 | - 여러 개의 스크립트가 있는 경우 선언된 순서대로 실행 (의존성 있는 스크립트군에 적합) 458 | 1. async 459 | - 특징 460 | - script 다운로드를 하는 동안 HTML parsing을 멈추지 않음 461 | - 다른 script와 동시에 다운로드할 수 있음 462 | - HTML parsing 완료, script 실행 순서 간에 의존성 없음 463 | - DOMContentLoaded 이벤트 발생, script 실행 순서 간에 의존성 없음 464 | - 여러 개의 스크립트가 있는 경우 응답받은 순서대로 실행 (의존성 없는 스크립트군에 적합) 465 | 466 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
467 | [맨 위로 가기](#browser) 468 | ## Authentication 469 | ### HttpOnly & SameSite Cookie를 통한 JWT 인증 방식 (with Next.js API Routes) 470 | ![Next.js API Routes](./assets/img/next.js-api-routes.png) 471 | 1. 클라이언트에서 인증 서버에 JWT를 달라고 요청함 472 | 1. Next.js API Routes를 통해 요청을 대신 받아서 인증 서버에 요청함 473 | 1. 인증 서버로부터 Access Token, Refresh Token, Access Token Expires In, Refresh Token Expires In 값을 응답으로 받음 474 | 1. Next.js API Routes에서 Access Token과 Refresh Token을 Cookie에 저장 후, 클라이언트에 응답함 475 | 1. HttpOnly, SameSite=Strict, Max-Age=Date.now() + {Access Token Expires In} * 1000을 적용하여 Access Token을 Cookie에 저장 476 | 1. HttpOnly, SameSite=Strict, Max-Age=Date.now() + {Refresh Token Expires In} * 1000을 적용하여 Refresh Token을 Cookie에 저장 477 | 1. Middleware에서 모든 페이지 이동 또는 API 호출에 대해 Access Token과 Refresh Token의 만료 여부를 체크하여 각 조건에 맞게 분기 처리 478 | 1. Access Token만 만료된 경우 Refresh Token을 통해 Access Token과 Refresh Token을 재발급 받음 479 | 1. Access Token과 Refresh Token이 모두 만료된 경우 로그인 페이지로 Redirect 480 | 1. 클라이언트에서 인증 서버에 로그아웃 요청 시, 마찬가지로 Next.js API Routes를 통해 해당 요청을 인증 서버에 보내고, 인증 서버로부터 응답을 받으면 기존에 있던 Access Token Cookie와 Refresh Token Cookie 속성에 Max-Age=0을 적용하여 클라이언트에 응답함 481 | 482 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
483 | [맨 위로 가기](#browser) -------------------------------------------------------------------------------- /browser/assets/img/cache-control.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sekhyuni/frontend-basic-concept/373798ba22b3b05f9302222f697491583a0ca6cc/browser/assets/img/cache-control.png -------------------------------------------------------------------------------- /browser/assets/img/data-from-cache.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sekhyuni/frontend-basic-concept/373798ba22b3b05f9302222f697491583a0ca6cc/browser/assets/img/data-from-cache.png -------------------------------------------------------------------------------- /browser/assets/img/data-from-server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sekhyuni/frontend-basic-concept/373798ba22b3b05f9302222f697491583a0ca6cc/browser/assets/img/data-from-server.png -------------------------------------------------------------------------------- /browser/assets/img/disk-cache.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sekhyuni/frontend-basic-concept/373798ba22b3b05f9302222f697491583a0ca6cc/browser/assets/img/disk-cache.png -------------------------------------------------------------------------------- /browser/assets/img/memory-cache-vs-disk-cache.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sekhyuni/frontend-basic-concept/373798ba22b3b05f9302222f697491583a0ca6cc/browser/assets/img/memory-cache-vs-disk-cache.png -------------------------------------------------------------------------------- /browser/assets/img/memory-cache.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sekhyuni/frontend-basic-concept/373798ba22b3b05f9302222f697491583a0ca6cc/browser/assets/img/memory-cache.png -------------------------------------------------------------------------------- /browser/assets/img/next.js-api-routes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sekhyuni/frontend-basic-concept/373798ba22b3b05f9302222f697491583a0ca6cc/browser/assets/img/next.js-api-routes.png -------------------------------------------------------------------------------- /browser/assets/img/script.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sekhyuni/frontend-basic-concept/373798ba22b3b05f9302222f697491583a0ca6cc/browser/assets/img/script.png -------------------------------------------------------------------------------- /browser/assets/img/web-cache.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sekhyuni/frontend-basic-concept/373798ba22b3b05f9302222f697491583a0ca6cc/browser/assets/img/web-cache.png -------------------------------------------------------------------------------- /browser/assets/img/web-performance-metrics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sekhyuni/frontend-basic-concept/373798ba22b3b05f9302222f697491583a0ca6cc/browser/assets/img/web-performance-metrics.png -------------------------------------------------------------------------------- /css/README.md: -------------------------------------------------------------------------------- 1 | # CSS 2 | 3 | * [display](#display) 4 | * [position](#position) 5 | * [overflow](#overflow) 6 | * [Layout Shift](#layout-shift) 7 | * [styled-components vs Emotion vs Tailwind CSS](#styled-components-vs-emotion-vs-tailwind-css) 8 | * [ETC](#etc) 9 | 10 | ## display 11 | - block 12 | - 한 줄을 점유하고 있으며, 다음 요소는 줄바꿈 발생 13 | - width/height/padding/margin 스타일 적용 가능 14 | - 대표적인 요소: div, header, main, footer, section, form, ul, li, table, p, etc. 15 | - block에서의 width: auto와 height: auto 16 | 1. width: auto (기본값) 17 | - 부모 요소를 기준으로 함 (부모 width와 동일) 18 | ```css 19 | .parent { 20 | width: 200px; 21 | } 22 | 23 | 24 | ``` 25 | 1. height: auto (기본값) 26 | - 자식 요소를 기준으로 함 (자식 width + padding + border와 동일) 27 | ```css 28 | .child { 29 | height: 200px; 30 | border: 1px; 31 | padding: 10px; 32 | } 33 | 34 | 35 | ``` 36 | - inline 37 | - text 크기만큼 공간을 점유하고 있으며, 줄바꿈 발생하지 않음 38 | - width/height/padding/margin 스타일 적용 불가 39 | - 대표적인 요소: button, a, input, span, img, label, etc. 40 | - inline-block 41 | - text 크기만큼 공간을 점유하고 있으며, 줄바꿈 발생하지 않음 42 | - width/height/padding/margin 스타일 적용 가능 43 | - flex 44 | - 속성 45 | ```css 46 | display: flex; 47 | flex-direction: row | column; 48 | justify-content: start | center | end; 49 | align-items: start | center | end; 50 | flex-wrap: no-wrap | wrap; 51 | ``` 52 | - flex-direction이 row인 경우 기본적으로 자식 요소의 width는 content만큼 적용되며, height는 상속을 받음 53 | - flex-direction이 column인 경우 기본적으로 자식 요소의 width는 상속을 받으며, height는 content만큼 적용됨 54 | - 자식 요소 css에 flex-grow: 1;을 적용하면, calc(100% - {anotherElementSize})와 같은 효과를 낼 수 있음 55 | 56 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
57 | [맨 위로 가기](#css) 58 | ## position 59 | - 속성 60 | ```css 61 | position: static | relative | absolute | fixed | sticky; 62 | ``` 63 | - 값 64 | - static: 기본값이며, 위치를 임의로 지정 불가 65 | - relative: 원래 있던 위치를 기준으로 좌표 지정 66 | - absolute: 절대 좌표와 함께 위치 지정 67 | - top, left, bottom, right 속성과 같이 쓰임 68 | - 상위 레벨 Element 중 속성값이 relative 또는 absolute인 것이 있으면 해당 Element 기준 위치 지정 69 | - 상위 레벨 Element 중 속성값이 relative 또는 absolute인 것이 없으면 최상위 Element 기준 위치 지정 70 | - fixed: 스크롤과 상관없이 항상 문서 최좌측상단을 기준으로 좌표 고정 71 | - sticky: 평소에는 문서 내에서 static 속성값과 같이 일반적인 흐름을 따르지만 스크롤 위치가 임계치에 이르면 fixed 속성값과 같이 좌표 고정 72 | 73 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
74 | [맨 위로 가기](#css) 75 | ## overflow 76 | - 역할: 콘텐츠가 요소의 상자를 벗어났을 때 어떻게 표시되어야 하는지를 결정 77 | - 속성 78 | ```css 79 | overflow: visible | hidden | clip | scroll | auto; 80 | ``` 81 | - 값 82 | - visible: 콘텐츠를 자르지 않으며 요소의 상자 밖에도 그릴 수 있음 83 | - hidden: 콘텐츠를 요소의 상자에 맞춰 잘라내며, 스크롤바를 제공하지 않고, 스크롤 할 방법(드래그, 마우스 휠 등)도 지원하지 않음. 다만, 코드를 사용해 스크롤 할 수는 있음 (Element.scrollTop, Element.scrollLeft) 84 | - clip: hidden과 마찬가지로, 콘텐츠를 요소의 상자에 맞춰 잘라내며, 스크롤바를 제공하지 않고, 스크롤 할 방법(드래그, 마우스 휠 등)도 지원하지 않음. hidden과 다르게, 코드를 사용해도 스크롤 할 수 없음 85 | - scroll: 콘텐츠를 요소의 상자에 맞춰 잘라내며, 콘텐츠를 실제로 잘라냈는가에 상관없이 항상 스크롤바를 노출 86 | - auto: 콘텐츠가 요소의 상자에 들어간다면 visible과 동일하게 보이지만, 콘텐츠가 넘칠 때는 스크롤바를 노출 87 | - overflow-x와 overflow-y의 관계 88 | - [W3C CSS Overflow Module Level 3](https://www.w3.org/TR/css-overflow-3/)를 따르면, "The visible/clip values of overflow compute to auto/hidden (respectively) if one of overflow-x or overflow-y is neither visible nor clip."이라고 함. 다시 말해, overflow-x에 visible을 지정하고 overflow-y에는 visible/clip 이외의 값을 지정하는 경우, overflow-x의 값은 auto로 계산되고, overflow-x에 clip을 지정하고 overflow-y에는 visible/clip 이외의 값을 지정하는 경우, overflow-y의 값은 hidden으로 계산됨 89 | - overflow-x: visible, overflow-y: auto -> overflow-x: auto, overflow-y: auto 90 | - overflow-x: visible, overflow-y: scroll -> overflow-x: auto, overflow-y: scroll 91 | - overflow-x: visible, overflow-y: hidden -> overflow-x: auto, overflow-y: hidden 92 | - overflow-x: clip, overflow-y: auto -> overflow-x: hidden, overflow-y: auto 93 | - overflow-x: clip, overflow-y: scroll -> overflow-x: hidden, overflow-y: scroll 94 | - overflow-x: clip, overflow-y: hidden -> overflow-x: hidden, overflow-y: hidden 95 | 96 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
97 | [맨 위로 가기](#css) 98 | ## Layout Shift 99 | - 레이아웃 이동이란 화면상의 요소 변화로 레이아웃이 갑자기 밀리는 현상 100 | - CLS가 발생하는 이유 101 | 1. 크기가 정해지지 않은 이미지 102 | 1. 크기가 정해지지 않은 광고, 임베드 및 iframe 103 | 1. 동적으로 주입된 콘텐츠 104 | 1. FOIT/FOUT을 유발하는 웹 글꼴 105 | 1. DOM을 업데이트하기 전에 네트워크 응답을 대기하는 작업 106 | - CLS를 측정하는 방법 107 | 1. Lighthouse를 통해 Analyze page load 클릭 후, View Original Trace를 클릭하면 Chrome DevTools Performance 탭으로 이동되어 Layout Shift가 발생한 요소를 확인할 수 있음 108 | - CLS를 개선하는 방법 109 | 1. 이미지 및 비디오 요소에 항상 크기 속성을 포함하거나 CSS 가로 세로 비율 상자와 같은 방식으로 필요한 공간을 미리 확보 110 | 1. 사용자 상호 작용에 대한 응답을 제외하고는 기존 콘텐츠 위에 콘텐츠를 삽입하지 않기 111 | 1. 레이아웃 변경을 트리거하는 속성의 애니메이션보다 전환 애니메이션을 사용하기 112 | 113 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
114 | [맨 위로 가기](#css) 115 | ## styled-components vs Emotion vs Tailwind CSS 116 | ||재사용 가능 여부|가독성|생산성|CSS Props| 117 | |:---:|:---:|:---:|:---:|:---:| 118 | |styled-components|O|Higher than Tailwind CSS|Slower than Tailwind CSS|X| 119 | |Emotion|O|Higher than Tailwind CSS|Slower than Tailwind CSS|O| 120 | |Tailwind CSS|X|Lower than CSS-in-JS|Fast than CSS-in-JS|-| 121 | - styled-components와 Emotion은 SCSS 문법의 일부를 따름 (Variable, Nesting, Mixin) 122 | - styled-components와 Emotion의 기능은 전체적으로 비슷하나, Emotion에서는 CSS Props 기능을 추가로 지원함. 다만, 추가로 지원되는 기능이라고 해서 무조건적으로 장점만 있는 것은 아니라고 생각하며, 오히려 styled-components를 사용했을 때 코드 작성의 일관성을 유지할 수 있기 때문에 개인적으로는 코드의 예측성 측면에서 Emotion보다 styled-components를 더 선호하는 편임 123 | 124 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
125 | [맨 위로 가기](#css) 126 | ## ETC 127 | 1. CSS-in-JS 128 | - 각 컴포넌트 별 고유 네임 스페이스에 CSS 작성 가능 129 | - 복잡한 애플리케이션 개발 시, 선택자 충돌 방지 가능 ({file}.module.css 형식으로 사용하면 CSS-in-CSS로도 선택자 충돌 방지 가능) 130 | - JavaScript와 CSS간에 상수와 함수 공유 가능 (props를 사용하여 조건부 스타일링 가능) 131 | 132 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
133 | [맨 위로 가기](#css) -------------------------------------------------------------------------------- /html/README.md: -------------------------------------------------------------------------------- 1 | # HTML 2 | 3 | * [Semantic Tags](#semantic-tags) 4 | * [Not Semantic Tags](#not-semantic-tags) 5 | 6 | ## Semantic Tags 7 | - The semantic tags help the search engines and other user devices to determine the importance and context of web pages. 8 | - header, nav, main, section, article, aside, footer, etc. 9 | 10 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
11 | [맨 위로 가기](#html) 12 | ## Not Semantic Tags 13 | - semantic tags는 아니지만, 그 외 의미있는 tags 14 | - ol, ul, li, h1~h6, p, strong, etc. 15 | 16 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
17 | [맨 위로 가기](#html) -------------------------------------------------------------------------------- /javascript/README.md: -------------------------------------------------------------------------------- 1 | # JavaScript 2 | 3 | * [Execution Context](#execution-context) 4 | * [Scope](#scope) 5 | * [Hoisting](#hoisting) 6 | * [Closure](#closure) 7 | * [Asynchronous Processing](#asynchronous-processing) 8 | * [Prototype](#prototype) 9 | * [First-class object](#first-class-object) 10 | * [Object vs Map](#object-vs-map) 11 | 12 | ## Execution Context 13 | ![Execution Context](./assets/img/execution-context.png) 14 | - 정의: JavaScript Engine에 JavaScript 코드가 로드된 뒤에 코드의 구문을 분석하고 실행을 처리하는 환경이며, 전역 실행 컨텍스트, 함수 실행 컨텍스트, eval 함수 실행 컨텍스트로 구성됨 15 | - 실행 컨텍스트 관점에서 JavaScript 코드가 실행되는 순서 16 | 1. JavaScript Engine에 JavaScript 코드가 로드되면 전역 실행 컨텍스트를 생성하고 Call Stack에 push함 17 | 1. JavaScript Engine은 전역 실행 컨텍스트에서 코드 구문 분석을 진행하며, 이후 코드 맨 윗줄부터 차례대로 코드를 실행함 18 | 1. JavaScript Engine이 함수 호출을 발견하면 함수 실행 컨텍스트를 생성하고 Call Stack에 push함 19 | 1. JavaScript Engine은 함수 실행 컨텍스트에서 코드 구문 분석을 진행하며, 이후 코드 맨 윗줄부터 차례대로 코드를 실행함 20 | 1. JavaScript Engine은 함수 실행이 끝나면 Call Stack에서 함수 실행 컨텍스트를 pop함 21 | 1. JavaScript Engine은 전역 실행 컨텍스트에서 계속해서 코드를 실행하며, 더 이상 실행할 코드가 없다면 Call Stack에서 전역 실행 컨텍스트를 pop함 22 | - 생명 주기 23 | 1. 생성 단계 24 | - JavaScript 코드의 구문을 분석하고 Lexical Environment와 Variable Environment를 생성 25 | - Lexical Environment 26 | - Environment Record: 변수 및 함수 선언이 저장되는 장소이며, Type은 Object와 Declarative로 나뉨 27 | - let, const로 선언된 변수가 바인딩되며, 초기화 및 할당은 되지 않음 28 | - function으로 선언된 함수가 바인딩된 후 함수 전체가 할당됨 29 | - Type이 Declarative인 경우: arguments 객체가 생성됨 30 | - Reference to the outer environment: 외부 함수의 Lexical Environment에 대한 참조 값 31 | - 전역 실행 컨텍스트인 경우: null 32 | - 함수 실행 컨텍스트인 경우: GlobalLexicalEnvironment 혹은 OuterFunctionLexicalEnvironment 33 | - This Binding: 함수 호출 방식에 의해 결정되는 값 34 | - 전역 실행 컨텍스트인 경우: Global Object 35 | - 함수 실행 컨텍스트인 경우 36 | - 일반 함수 호출: Global Object 혹은 (strict mode에서) undefined 37 | - 메서드 호출: 해당 메서드를 호출한 객체 38 | - 생성자 함수 호출: new 키워드를 통해 새로 생성된 객체 39 | - apply/call/bind 호출: 첫 번째 인자로 전달된 객체 40 | - 화살표 함수 호출: 외부 함수의 This Binding 41 | - Variable Environment: Lexical Environment의 일종이며, Environment Record를 제외한 나머지는 Lexical Environment와 동일 42 | - Environment Record: 변수 선언이 저장되는 장소이며, Type은 Object와 Declarative로 나뉨 43 | - var로 선언된 변수가 바인딩된 후 undefined로 초기화됨 44 | - Lexical Environment와 Variable Environment를 나눈 이유: 변수 선언 키워드별 호이스팅 동작을 일관성 있게 처리하기 위해 45 | 1. 실행 단계 46 | - 실행 컨텍스트 내에서 코드를 실행 47 | 48 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
49 | [맨 위로 가기](#javascript) 50 | ## Scope 51 | - 정의: 식별자를 찾아내기 위한 규칙으로 JavaScript에서 스코프는 전역 스코프, 함수 레벨 스코프, 블록 레벨 스코프로 구성됨 52 | 53 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
54 | [맨 위로 가기](#javascript) 55 | ## Hoisting 56 | - JavaScript 실행 컨텍스트의 생성 단계에서 코드 구문 분석이 이루어지면서 변수 및 함수를 해당 Scope의 최상단으로 끌어올리는 프로세스 (논리적인 개념) 57 | - 모든 선언 키워드는 호이스팅되지만, 호이스팅으로 인해 선언문 전에서도 식별자에 접근 가능한 키워드는 var와 function밖에 없음 58 | - 선언 키워드별 동작 방식 59 | - var: 선언+초기화, 할당이 각각 따로 실행 60 | ```javascript 61 | console.log(someVarVariable); 62 | var someVarVariable = 1; 63 | 64 | // undefined 출력 65 | ``` 66 | - let: 선언, 초기화, 할당이 각각 따로 실행 (Temporal Dead Zone 존재) 67 | ```javascript 68 | console.log(someLetVariable); 69 | let someLetVariable = 1; 70 | 71 | // ReferenceError: Cannot access 'someLetVariable' before initialization 출력 72 | ``` 73 | - const: 선언, 초기화+할당이 각각 따로 실행 (Temporal Dead Zone 존재) 74 | ```javascript 75 | console.log(someConstVariable); 76 | const someConstVariable = 1; 77 | 78 | // ReferenceError: Cannot access 'someConstVariable' before initialization 출력 79 | ``` 80 | - function: 선언+초기화+할당이 한 번에 실행 (호이스팅으로 인해 실행 단계에서 의도한 대로 동작하는 코드는 함수 선언식의 호이스팅밖에 없음) 81 | ```javascript 82 | someFunction(); 83 | 84 | function someFunction() { 85 | console.log('this works'); 86 | } 87 | 88 | // this works 89 | ``` 90 | ```python 91 | some_function() 92 | 93 | def some_function(): 94 | print('this does not work') 95 | 96 | # NameError: name 'some_function' is not defined 97 | ``` 98 | 99 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
100 | [맨 위로 가기](#javascript) 101 | ## Closure 102 | - 정의: 외부 함수의 실행 컨텍스트가 소멸되어도 해당 컨텍스트에서 생성되었던 Lexical Environment에 접근할 수 있는 내부 함수 (실행 컨텍스트는 Call Stack에서 사라지지만, Lexical Environment는 Memory 상에 남음) 103 | - 클로저가 외부 함수의 Lexical Environment에 접근할 수 있는 이유: 클로저의 Lexical Environment에서 외부 함수의 Lexical Environment에 대한 참조 값을 가지고 있기 때문 104 | - 장점: 실행 컨텍스트가 소멸되어 Lexical Environment에 직접 접근할 수 없으므로 정보를 은닉할 수 있음 105 | - 단점: Lexical Environment가 남아있으므로 사용하지 않는 식별자의 양에 따라 그만큼 메모리 누수가 발생할 수 있음 106 | - 클로저를 활용한 예 107 | - Debouncing vs Throttling 108 | 1. 디바운씽 109 | - 정의: 동일 이벤트가 반복적으로 일어나는 경우 마지막 이벤트가 일어나고 나서 일정 시간(ms)동안 해당 이벤트가 다시 일어나지 않으면 해당 이벤트의 콜백함수를 실행시키는 기술 110 | - 사용처: 자동 완성 기능이 들어간 검색 이벤트 111 | ```html 112 | 113 | 147 | ``` 148 | 1. 쓰로틀링 149 | - 정의: 동일 이벤트가 반복적으로 일어나는 경우 이벤트의 실제 반복 주기와 상관없이 임의로 설정한 일정 시간(ms) 간격으로 콜백함수를 실행시키는 기술 150 | - 사용처: 스크롤 이벤트, 리사이즈 이벤트 151 | ```html 152 | 179 | ``` 180 | 181 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
182 | [맨 위로 가기](#javascript) 183 | ## Asynchronous Processing 184 | - 방법: JavaScript Engine은 Call Stack이 1개이므로 싱글 스레드로 동작하지만, 브라우저 또는 Node.js 런타임 환경에 존재하는 Web API, Event Queue, Event Loop 덕분에 비동기 처리가 가능 185 | - 비동기 처리 과정 186 | 1. JavaScript Engine이 Call Stack에 있는 Task들을 순차적으로 실행하다가 setTimeout 함수를 만나게 되면 setTimeout 함수를 실행하고 Callback 함수를 Web API에 위임 187 | 1. Web API는 Callback 함수를 setTimeout에서 설정된 시간 동안 대기시킨 후, Event Queue로 전달 188 | 1. Event Loop는 주기적으로 Call Stack과 Event Queue를 확인하면서 Call Stack이 비었을 경우 Event Queue에 있는 Task들을 Call Stack으로 전달 189 | 1. Event Loop에 의해 Callback 함수가 Call Stack으로 이동되면 JavaScript Engine이 해당 Callback 함수를 실행 190 | - 브라우저 또는 Node.js 런타임 환경 내 존재하는 컴포넌트 191 | - Web API: setTimeout, setInterval, XMLHttpRequest, Promise, requestAnimationFrame 등 실질적인 비동기 이벤트 처리 및 비동기 네트워크 통신을 담당 192 | - Event Queue: 비동기 작업의 Callback 함수가 Call Stack으로 옮겨지기 전에 대기하는 Queue 193 | - Task가 Call Stack으로 전달되는 순서: Microtask Queue -> Animation Frames -> Task Queue 194 | - Event Loop: 주기적으로 Call Stack과 Event Queue를 확인하면서 Call Stack이 비었을 경우 Event Queue에 있는 Task들을 Call Stack으로 전달 195 | 196 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
197 | [맨 위로 가기](#javascript) 198 | ## Prototype 199 | - 정의: JavaScript의 모든 객체는 자신의 부모 역할을 담당하는 객체와 연결되어 있는데, 이 부모 역할을 담당하는 객체가 바로 프로토타입 200 | - 특징: JavaScript의 모든 객체는 프로토타입 체인을 통해 다른 객체로부터 상속받은 속성과 메서드를 사용할 수 있음 201 | - 프로토타입에 직접 접근하는 방법 202 | - Object.getPrototypeOf 메서드 사용 203 | - `__proto__` 속성 사용 204 | - 클래스와 프로토타입의 차이 205 | - 프로토타입은 동적으로 속성과 메서드를 추가, 수정, 삭제할 수 있음 206 | - 프로토타입은 여러 개의 인스턴스가 속성과 메서드를 하나의 프로토타입으로부터 공유 받으므로 메모리 사용을 최소화할 수 있음 207 | 208 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
209 | [맨 위로 가기](#javascript) 210 | ## First-class object 211 | - 정의: 다른 객체들에 일반적으로 적용 가능한 연산을 모두 지원하는 객체 212 | - 조건 213 | - 변수에 할당 가능 214 | - 매개변수로 전달 가능 215 | - 반환값으로 사용 가능 216 | 217 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
218 | [맨 위로 가기](#javascript) 219 | ## Object vs Map 220 | 1. Object 221 | - Object는 iterable하지 않음 222 | - Object의 key는 String 또는 Symbol 타입으로만 설정 가능 223 | - 빈번한 추가 및 제거 작업에서 Map보다 성능이 낮음 224 | - Serialization 또는 Parsing을 기본적으로 지원함 225 | - V8 Engine에서 Object는 Fast Mode 형태(Hash Table로 구현되지 않음)와 Dictionary Mode 형태(Hash Table로 구현되어 있음)를 가질 수 있으며, 최초 선언 시 Fast Mode 형태를 따르고, 이후 삭제 연산이 발생하면 Dictionary Mode 형태로 변경되는 것으로 알려져 있음 226 | ```javascript 227 | var obj = {}; 228 | var obj = Object.create(null); 229 | obj.key = 1; 230 | obj.key += 10; 231 | for (const k in obj) obj[k]++; 232 | let sum = 0; 233 | for (const v of Object.values(obj)) sum += v; 234 | if ('key' in obj); 235 | if (obj.hasOwnProperty('key')); 236 | delete (obj.key); 237 | Object.keys(obj).length; 238 | ``` 239 | 1. Map 240 | - Map은 iterable함 241 | - Map의 key는 모든 타입으로 설정 가능 242 | - 빈번한 추가 및 제거 작업에서 Object보다 성능이 높음 243 | - Serialization 또는 Parsing을 기본적으로 지원하지 않음 244 | - V8 Engine에서 Map은 Hash Table로 구현되어 있음 245 | ```javascript 246 | const hashTable = new Map(); 247 | hashTable.set('key', 1); 248 | hashTable.set('key', hashTable.get('key') + 10); 249 | hashTable.foreach((k, v) => hashTable.set(k, hashTable.get(k) + 1)); 250 | for (const k of hashTable.keys()) hashTable.set(k, hashTable.get(k) + 1); 251 | let sum = 0; 252 | for (const v of hashTable.values()) sum += v; 253 | if (hashTable.has('key')); 254 | hashTable.delete('key'); 255 | hashTable.size(); 256 | ``` 257 | 258 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
259 | [맨 위로 가기](#javascript) -------------------------------------------------------------------------------- /javascript/assets/img/execution-context.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sekhyuni/frontend-basic-concept/373798ba22b3b05f9302222f697491583a0ca6cc/javascript/assets/img/execution-context.png -------------------------------------------------------------------------------- /next.js/README.md: -------------------------------------------------------------------------------- 1 | # Next.js 2 | 3 | * [Rendering](#rendering) 4 | * [Caching](#caching) 5 | 6 | ## Rendering 7 | 1. Server Components 8 | - 정의: 서버에서 렌더링되며 캐싱이 가능한 컴포넌트 9 | - 이점 10 | - Data Fetching 11 | - Security 12 | - Caching 13 | - Bundle Sizes 14 | - Initial Page Load and First Contentful Paint (FCP) 15 | - Search Engine Optimization and Social Network Shareability 16 | - Streaming 17 | 1. Client Components 18 | - Update later.. 19 | 1. Composition Patterns 20 | - Update later.. 21 | 22 | ## Caching 23 | 1. Request Memoization: 특정 페이지 내에서 같은 URL과 같은 options의 fetch API를 사용하는 경우, 페이지가 렌더링되는 동안 해당 요청은 캐싱되며 페이지 렌더링이 완료되면 캐싱되어 있던 모든 요청은 삭제됨 24 | ![Request Memoization](./assets/img/request-memoization.avif) 25 | - React 기능 26 | - GET 요청만 가능 27 | 1. Data Cache: 특정 페이지 내에서 fetch API를 사용하는 경우, cache와 next.revalidate 옵션을 통해 캐싱 동작을 설정 가능 28 | ![Data Cache](./assets/img/data-cache.avif) 29 | - 속성 30 | ```typescript 31 | const response = await fetch('/api/endpoint', { 32 | cache: 'force-cache' | 'no-store' // 기본값: 'force-cache' 33 | next: { 34 | revalidate: false | 0 | number, 35 | }, 36 | }); 37 | ``` 38 | - revalidate 동작 39 | - cache가 'force-cache'인 경우, revalidate는 0이 아닌 값으로 설정되어야 함 40 | - revalidate 동안은 캐싱된 데이터를 사용하며, revalidate가 지나면 서버 측에서 유효성 검사를 해야 함 41 | - revalidate가 지나 서버 측에서 유효성 검사를 진행하는 동안에도 여전히 캐싱된 데이터를 사용하며, 데이터가 변경된 경우 Next.js는 새로운 데이터를 캐싱함 (새로운 데이터는 그다음 요청에서 사용됨) 42 | 1. Full Route Cache 43 | - Update later.. 44 | 1. Router Cache 45 | - Update later.. 46 | ![Router Cache](./assets/img/router-cache.avif) 47 | 48 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
49 | [맨 위로 가기](#nextjs-app-router) -------------------------------------------------------------------------------- /next.js/assets/img/data-cache.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sekhyuni/frontend-basic-concept/373798ba22b3b05f9302222f697491583a0ca6cc/next.js/assets/img/data-cache.avif -------------------------------------------------------------------------------- /next.js/assets/img/request-memoization.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sekhyuni/frontend-basic-concept/373798ba22b3b05f9302222f697491583a0ca6cc/next.js/assets/img/request-memoization.avif -------------------------------------------------------------------------------- /next.js/assets/img/router-cache.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sekhyuni/frontend-basic-concept/373798ba22b3b05f9302222f697491583a0ca6cc/next.js/assets/img/router-cache.avif -------------------------------------------------------------------------------- /project-setup/TypeScript+Nextjs+ESLint+Prettier+Emotion+TaillwindCSS+Storybook.md: -------------------------------------------------------------------------------- 1 | 1. TypeScript + Next.js 설치 2 | ```shell 3 | $ npx create-next-app@latest next-app --typescript 4 | ``` 5 | 1. Prettier 설치 6 | ```shell 7 | $ yarn add -D prettier eslint-plugin-prettier eslint-config-prettier 8 | ``` 9 | 1. Prettier관련 .eslintrc.json 수정 10 | ```json 11 | { 12 | "globals": { 13 | "JSX": true 14 | }, 15 | "env": { 16 | "browser": true, 17 | "es2021": true, 18 | "node": true 19 | }, 20 | "extends": [ 21 | "eslint:recommended", 22 | "next", 23 | "next/core-web-vitals", 24 | "plugin:prettier/recommended" 25 | ], 26 | "rules": { 27 | "no-unused-vars": "off" 28 | } 29 | } 30 | ``` 31 | 1. Prettier관련 .prettier.json 생성 32 | ```json 33 | { 34 | "semi": true, 35 | "trailingComma": "es5", 36 | "singleQuote": true, 37 | "jsxSingleQuote": true, 38 | "tabWidth": 2, 39 | "useTabs": false 40 | } 41 | ``` 42 | 1. VSCode에서 Prettier Plugin 설치 후, setting.json 수정 (.prettier.json보다 Code Formatting 적용 우선순위 낮음) 43 | ```json 44 | { 45 | // ... 46 | // "editor.fontWeight": "normal", 47 | "editor.codeActionsOnSave": { 48 | "source.fixAll.eslint": true 49 | }, 50 | "editor.formatOnSave": true, 51 | "editor.defaultFormatter": "esbenp.prettier-vscode" 52 | } 53 | ``` 54 | 1. src 디렉토리 생성 및 각종 디렉토리 이동 55 | ```shell 56 | $ mkdir src && mv ./pages ./src/pages && mv ./styles ./src/styles 57 | ``` 58 | 1. 유형별 디렉토리 생성 59 | ```shell 60 | $ cd src && mkdir assets components constants contexts helpers hooks i18n layouts styles services types utils 61 | ``` 62 | 1. 절대경로관련 tsconfig.json 수정 63 | ```json 64 | { 65 | "compilerOptions": { 66 | // ... 67 | // "incremental": true, 68 | "baseUrl": ".", 69 | "paths": { 70 | "~assets/*": ["./src/assets/*"], 71 | "~components/*": ["./src/components/*"], 72 | "~constants/*": ["./src/constants/*"], 73 | "~contexts/*": ["./src/contexts/*"], 74 | "~helpers/*": ["./src/helpers/*"], 75 | "~hooks/*": ["./src/hooks/*"], 76 | "~i18n/*": ["./src/i18n/*"], 77 | "~layouts/*": ["./src/layouts/*"], 78 | "~pages/*": ["./src/pages/*"], 79 | "~styles/*": ["./src/styles/*"], 80 | "~services/*": ["./src/services/*"], 81 | "~types/*": ["./src/types/*"], 82 | "~utils/*": ["./src/utils/*"], 83 | "~public/*": ["./public/*"], 84 | "~src/*": ["./src/*"], 85 | "~/*": ["./*"] 86 | } 87 | } 88 | // ... 89 | } 90 | ``` 91 | 1. Emotion 설치 92 | ```shell 93 | $ yarn add @emotion/react @emotion/styled @emotion/css @emotion/server 94 | ``` 95 | 1. Emotion css props관련 tsconfig.json 수정 96 | ```json 97 | { 98 | "compilerOptions": { 99 | // ... 100 | // "jsx": "preserve", 101 | "jsxImportSource": "@emotion/react" 102 | // ... 103 | } 104 | // ... 105 | } 106 | ``` 107 | 1. Tailwind CSS 설치 108 | ```shell 109 | $ yarn add -D tailwindcss postcss autoprefixer 110 | ``` 111 | 1. tailwind.config.js 생성 112 | ```shell 113 | $ npx tailwindcss init --full 114 | ``` 115 | 1. postcss.config.js 생성 116 | ```javascript 117 | module.exports = { 118 | plugins: { 119 | tailwindcss: {}, 120 | autoprefixer: {}, 121 | }, 122 | }; 123 | ``` 124 | 1. Template 경로 설정을 위한 tailwind.config.js 수정 125 | ```javascript 126 | module.exports = { 127 | content: ['./src/**/*.{js,ts,jsx,tsx}'], 128 | // presets: [], 129 | // ... 130 | }; 131 | ``` 132 | 1. Tailwind CSS 기본 스타일 적용을 위한 globals.css 수정 133 | ```css 134 | @tailwind base; 135 | @tailwind components; 136 | @tailwind utilities; 137 | ``` 138 | 1. Emotion와 Tailwind CSS의 연동을 위한 macro 설치 139 | ```shell 140 | $ yarn add -D twin.macro @emotion/babel-plugin babel-plugin-macros 141 | ``` 142 | 1. .babelrc.js 생성 143 | ```javascript 144 | module.exports = { 145 | presets: [ 146 | [ 147 | 'next/babel', 148 | { 149 | 'preset-react': { 150 | runtime: 'automatic', 151 | importSource: '@emotion/react', 152 | }, 153 | }, 154 | ], 155 | ], 156 | plugins: ['@emotion/babel-plugin', 'babel-plugin-macros'], 157 | }; 158 | ``` 159 | 1. CORS 이슈 우회 설정을 위한 next.config.js 수정 160 | ```javascript 161 | const nextConfig = { 162 | // reactStrictMode: true, 163 | async rewrites() { 164 | return [ 165 | { 166 | source: '/:path*', 167 | destination: 'http://{BACKEND_IP}:{BACKEND_PORT}/:path*', 168 | }, 169 | ]; 170 | }, 171 | }; 172 | ``` 173 | 1. SVGR 설정을 위한 file-loader, @svgr/webpack 설치 174 | ```shell 175 | $ yarn add file-loader 176 | $ yarn add -D @svgr/webpack 177 | ``` 178 | 1. SVGR 설정을 위한 next.config.js 수정 179 | ```javascript 180 | const nextConfig = { 181 | // ... 182 | // async rewrites() { 183 | // // ... 184 | // }, 185 | webpack: (config) => { 186 | config.module.rules.push({ 187 | test: /\.svg$/, 188 | use: [ 189 | { 190 | loader: '@svgr/webpack', 191 | }, 192 | { 193 | loader: 'file-loader', 194 | options: { 195 | name: 'static/[path][name].[ext]', 196 | }, 197 | }, 198 | ], 199 | type: 'javascript/auto', 200 | issuer: { 201 | and: [/\.(ts|tsx|js|jsx|md|mdx)$/], 202 | }, 203 | }); 204 | return config; 205 | }, 206 | }; 207 | ``` 208 | 1. SVGR 설정을 위한 svg.d.ts 생성 209 | ```typescript 210 | declare module '*.svg; { 211 | import React = require('react'); 212 | export const ReactComponent: React.FunctionComponent< 213 | React.SVGProps 214 | >; 215 | const src: string; 216 | export default src; 217 | } 218 | ``` 219 | 1. svg.d.ts 인식을 위한 tsconfig.json 수정 220 | ```json 221 | { 222 | // "compilerOptions": { 223 | // // ... 224 | // }, 225 | "include": [ 226 | // ... 227 | // ".next/types/**/*.ts", 228 | "src/types/*.d.ts" 229 | ], 230 | // ... 231 | } 232 | ``` 233 | 1. Storybook 설치 234 | ```shell 235 | $ npx sb init --builder webpack5 236 | ``` 237 | 1. Storybook에 globals.css 적용 및 Next Images관련 .storybook/preview.js 수정 238 | ```javascript 239 | import '../src/styles/globals.css'; 240 | import * as NextImage from 'next/image'; 241 | 242 | const OriginalNextImage = NextImage.default; 243 | 244 | Object.defineProperty(NextImage, 'default', { 245 | configurable: true, 246 | value: (props) => , 247 | }); 248 | ``` 249 | 1. Storybook에 절대경로관련 설정을 위한 .storybook/main.js 수정 250 | ```javascript 251 | const path = require('path'); 252 | const PROJECT_ROOT = process.cwd(); 253 | const pathAlias = require('../tsconfig.json').compilerOptions.paths; 254 | 255 | module.exports = { 256 | // ... 257 | // addons: [ 258 | // // ... 259 | // ], 260 | webpackFinal: async (config) => { 261 | config.resolve.alias = { 262 | ...config.resolve.alias, 263 | ...Object.fromEntries( 264 | Object.entries(pathAlias).map(([key, valArr]) => [ 265 | key.replace('/*', ''), 266 | valArr.map((val) => 267 | path.resolve( 268 | PROJECT_ROOT, 269 | ...val.replace('/*', '').replace('./', '').split('/') 270 | ) 271 | ), 272 | ]) 273 | ), 274 | }; 275 | return config; 276 | }, 277 | // ... 278 | }; 279 | ``` 280 | 1. Storybook에 보여줄 디렉토리 추가 및 public 디렉토리 제공을 위한 .storybook/main.js 수정 281 | ```javascript 282 | module.exports = { 283 | stories: [ 284 | '../src/components/**/*.stories.@(js|jsx|ts|tsx|mdx)', 285 | '../src/layouts/**/*.stories.@(js|jsx|ts|tsx|mdx)', 286 | '../src/pages/**/*.stories.@(js|jsx|ts|tsx|mdx)', 287 | // '../src/**/*.stories.mdx', 288 | // ... 289 | ], 290 | staticDir: ['../public'], 291 | // ... 292 | }; 293 | ``` 294 | 1. @emotion/babel-preset-css-prop 설치 295 | ```shell 296 | $ yarn add -D @emotion/babel-preset-css-prop 297 | ``` 298 | 1. @emotion/babel-preset-css-prop 설정을 위한 .storybook/main.js 수정 299 | ```javascript 300 | module.exports = { 301 | // ... 302 | // addons: [ 303 | // // ... 304 | // ], 305 | webpackFinal: async (config) => { 306 | // config.resolve.alias = { 307 | // // ... 308 | // }; 309 | config.module.rules.push({ 310 | test: /\.(ts|tsx)$/, 311 | loader: require.resolve('babel-loader'), 312 | options: { 313 | presets: [require.resolve('@emotion/babel-preset-css-prop')], 314 | }, 315 | }); 316 | return config; 317 | }, 318 | // ... 319 | }; 320 | ``` 321 | 1. PostCSS addon 설치 322 | ```shell 323 | $ yarn add -D @storybook/addon-postcss 324 | ``` 325 | 1. PostCSS addon 설정을 위한 .storybook/main.js 수정 326 | ```javascript 327 | module.exports = { 328 | // ... 329 | // staticDir: ['../public'], 330 | addons: [ 331 | // ... 332 | // '@storybook/addon-interactions', 333 | { 334 | name: '@storybook/addon-postcss', 335 | options: { 336 | postcssLoaderOptions: { 337 | implementation: require('postcss'), 338 | }, 339 | }, 340 | }, 341 | ], 342 | // ... 343 | }; 344 | ``` 345 | 1. Storybook에 SVGR 설정을 위한 .storybook/main.js 수정 346 | ```javascript 347 | module.exports = { 348 | // ... 349 | // addons: [ 350 | // // ... 351 | // ], 352 | webpackFinal: async (config) => { 353 | // ... 354 | // config.module.rules.push({ 355 | // // ... 356 | // }); 357 | config.module.rules.push({ 358 | test: /\.svg$/, 359 | use: [ 360 | { 361 | loader: '@svgr/webpack', 362 | }, 363 | { 364 | loader: 'file-loader', 365 | options: { 366 | name: 'static/[path][name].[ext]', 367 | }, 368 | }, 369 | ], 370 | type: 'javascript/auto', 371 | issuer: { 372 | and: [/\.(ts|tsx|js|jsx|md|mdx)$/], 373 | }, 374 | }); 375 | return config; 376 | }, 377 | // ... 378 | }; 379 | ``` 380 | -------------------------------------------------------------------------------- /react-hook-form/README.md: -------------------------------------------------------------------------------- 1 | # React Hook Form 2 | 3 | * [Controlled vs Uncontrolled](#controlled-vs-uncontrolled) 4 | 5 | ## Controlled vs Uncontrolled 6 | 1. defaultValue 설정 7 | - React Hook Form에서 input을 사용하는 경우, 제어 방식과 비제어 방식에 따라 defaultValue를 설정하는 방법이 다름. 사실 공식 문서에서는 reset API를 사용하는 것을 권장하지만, 비제어 방식인 경우 굳이 리렌더링을 trigger하는 reset API를 사용하지 않고도 defaultValue를 설정할 수 있음 8 | 1. 제어 방식 9 | ```jsx 10 | import { useState, useEffect } from 'react'; 11 | 12 | import { useForm, Controller } from 'react-hook-form'; 13 | 14 | const App = () => { 15 | // ============= Server Data ============= 16 | const [data, setData] = useState(undefined); 17 | useEffect(() => { 18 | const timer = setTimeout(function () { 19 | setData('success'); 20 | }, 50); 21 | 22 | return () => { 23 | clearTimeout(timer); 24 | }; 25 | }, []); 26 | 27 | // ============= React Hook Form ============= 28 | const { control, handleSubmit, reset } = useForm({ 29 | defaultValues: { 30 | test: '', // defaultValues 속성에 test가 정의되어 있어야 제어 방식으로 인식하여 오류없이 reset이 가능 31 | }, 32 | }); 33 | useEffect(() => { 34 | if (data) { 35 | reset({ 36 | test: data, 37 | }); 38 | } 39 | }, [data, reset]); 40 | const onSubmit = (data) => { 41 | console.log(data); 42 | }; 43 | 44 | console.log('render'); // 총 3번 render 45 | 46 | return ( 47 |
48 | ( 52 | { 55 | onChange(e.target.value); 56 | }} 57 | /> 58 | )} 59 | /> 60 | 61 | 62 | ); 63 | }; 64 | 65 | export default App; 66 | ``` 67 | 1. 비제어 방식 68 | ```jsx 69 | import { useState, useEffect } from 'react'; 70 | 71 | import { useForm } from 'react-hook-form'; 72 | 73 | const App = () => { 74 | // ============= Server Data ============= 75 | const [data, setData] = useState(undefined); 76 | useEffect(() => { 77 | const timer = setTimeout(function () { 78 | setData('success'); 79 | }, 50); 80 | 81 | return () => { 82 | clearTimeout(timer); 83 | }; 84 | }, []); 85 | 86 | // ============= React Hook Form ============= 87 | const { register, handleSubmit } = useForm(); 88 | const onSubmit = (data) => { 89 | console.log(data); 90 | }; 91 | 92 | console.log('render'); // 총 2번 render 93 | 94 | return ( 95 |
96 | 101 | 102 |
103 | ); 104 | }; 105 | 106 | export default App; 107 | ``` 108 | 109 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
110 | [맨 위로 가기](#react-hook-form) -------------------------------------------------------------------------------- /react-query/README.md: -------------------------------------------------------------------------------- 1 | # React Query 2 | 3 | * [Life-Cycle](#life-cycle) 4 | * [useQuery](#usequery) 5 | * [useMutation](#usemutation) 6 | * [QueryClient](#queryclient) 7 | 8 | ## Life-Cycle 9 | 1. 컴포넌트 Mount 시, useQuery 인스턴스 Mount 10 | 1. 데이터 fetch 후, query key별로 데이터 캐싱 11 | 1. staleTime이 지난 뒤, 해당 데이터는 fresh 상태에서 stale 상태로 변경됨 12 | 1. 컴포넌트 Unmount 시, useQuery 인스턴스 Unmount 13 | 1. useQuery 인스턴스가 Unmount된 이후 cacheTime이 지나기 전에 다시 useQuery 인스턴스가 Mount되면 캐싱된 데이터가 적용되며, 이와는 별개로 네트워크 요청은 보낸 뒤, 응답이 오면 fresh한 데이터로 교체됨 14 | 15 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
16 | [맨 위로 가기](#react-query) 17 | ## useQuery 18 | 1. Options 19 | 1. staleTime 20 | - 기본값: 0ms 21 | - 데이터 fetch 후, staleTime 동안 네트워크 요청 자체를 보내지 않음 22 | - Cache-Control 속성의 max-age값과 동일한 맥락으로 동작 23 | 1. cacheTime 24 | - 기본값: 300,000ms(5min) 25 | - useQuery 인스턴스가 Unmount된 이후 cacheTime이 지나면 캐싱된 데이터가 제거됨 26 | - useQuery 인스턴스가 Unmount된 이후 cacheTime이 지나기 전에 다시 useQuery 인스턴스가 Mount되면 캐싱된 데이터가 적용되며, 이와는 별개로 네트워크 요청은 보낸 뒤, 응답이 오면 fresh한 데이터로 교체됨 27 | 28 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
29 | [맨 위로 가기](#react-query) 30 | ## useMutation 31 | 1. Options 32 | - Update later.. 33 | 34 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
35 | [맨 위로 가기](#react-query) 36 | ## QueryClient 37 | 1. queryClient.refetchQueries 38 | - 쿼리가 마운트되지 않은 상태에도 queryKey와 일치하는 모든 새 쿼리를 즉시 요청 39 | 1. queryClient.invalidateQueries 40 | - 쿼리가 마운트되지 않은 상태인 경우 queryKey와 일치하는 모든 새 쿼리를 즉시 요청하지 않으며, 쿼리가 마운트된 경우 staleTime 설정 유무와 관계없이 캐싱된 데이터를 무효화하고 새 쿼리를 요청 41 | - 쿼리가 마운트되지 않은 상태에서 mutate를 하는 경우, refetchQueries보다 invalidateQueries를 사용함으로써 네트워크 요청 횟수를 줄일 수 있음 (일반적인 상황에서는 invalidateQueries를 사용하는 것이 좋음) 42 | 43 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
44 | [맨 위로 가기](#react-query) -------------------------------------------------------------------------------- /react-rendering-optimization/Challenge.md: -------------------------------------------------------------------------------- 1 | 2 | 1. 포럼 및 댓글(Forum) 3 | 1. 키워드 검색 4 | - Not Memoization 5 | |세트|1|2|3|4|5|6|7|8|9|10|평균| 6 | |:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| 7 | |1세트|13.3|16.5|14.3|9.6|12.1|11.8|9.4|13.3|9.1|11.4|12.08| 8 | |2세트|14.9|13.7|10.4|10.6|12.7|11.8|9.7|9.7|10.3|12|11.58| 9 | - React.memo + useCallback + useMemo 10 | |세트|1|2|3|4|5|6|7|8|9|10|평균| 11 | |:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| 12 | |1세트|9.6|8.2|23.8|6.9|11.2|6.5|6.7|8.2|9.2|6.6|9.69| 13 | |2세트|12.1|11.6|7.1|7.7|7|7.5|7.7|21.7|8.1|7.1|9.76| 14 | 1. Small Navigation Tab 클릭 15 | - Not Memoization 16 | |세트|1|2|3|4|5|6|7|8|9|10|평균| 17 | |:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| 18 | |1세트|23.2|23|24|19.7|17.5|16.8|16.3|17.7|21.3|16.2|19.57| 19 | |2세트|28.6|23.1|15.8|16.5|16.4|16|20.3|19.5|34.4|18.6|20.92| 20 | - React.memo + useCallback + useMemo 21 | |세트|1|2|3|4|5|6|7|8|9|10|평균| 22 | |:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| 23 | |1세트|37.5|17.6|23.4|17.7|13.5|16.7|13.9|22.5|13.5|12.7|18.885| 24 | |2세트|19|16.3|17.6|15.7|14.4|14.6|16.6|15.7|13.7|14.7|15.83| 25 | 1. 리더보드(Leaderboard) 26 | 1. 전체보기 27 | - Not Memoization 28 | |세트|1|2|3|4|5|6|7|8|9|10|평균| 29 | |:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| 30 | |1세트|24.4|16|17.8|17.1|14.5|13.8|16|14.1|13.6|14.5|16.18| 31 | |2세트|24.1|25.4|18.5|14.8|16.1|18.6|14.4|19.9|17.7|14.6|18.41| 32 | - React.memo + useCallback + useMemo 33 | |세트|1|2|3|4|5|6|7|8|9|10|평균| 34 | |:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| 35 | |1세트|15|15.2|12.8|16.9|13.7|11.8|11.3|11.3|11|16.3|13.53| 36 | |2세트|16.3|16.8|12.7|12.2|9.9|11|10.6|11.3|11.4|11.7|12.39| 37 | -------------------------------------------------------------------------------- /react/README.md: -------------------------------------------------------------------------------- 1 | # React 2 | 3 | * [Benefits of using React](#benefits-of-using-react) 4 | * [Rendering Process](#rendering-process) 5 | * [Reconciliation](#reconciliation) 6 | * [Life-Cycle](#life-cycle) 7 | * [Hooks](#hooks) 8 | * [Performance Optimization](#performance-optimization) 9 | 10 | ## Benefits of using React 11 | 1. 페이지 이동 시, 앱을 사용하는 듯한 사용자 경험 12 | - React는 기본적으로 Single Page기반의 CSR 방식을 채택하므로 페이지 이동 시 브라우저단에서 화면을 그리고 데이터만 서버에서 받아옴. 이는 페이지 전부를 가져와야하는 SSR과 다르게 페이지 리로딩이 없기 때문에 앱을 사용하는 듯한 사용자 경험을 제공할 수 있음 13 | 1. 높은 렌더링 효율 (feat. 가상 DOM) 14 | - 변경 사항이 있을 때, 업데이트를 여러 번 하지 않고 한 번만 수행 15 | - Observable Pattern을 사용하여 약 16ms 버퍼 후, 한 번에 업데이트 16 | - 이전 가상 DOM과 새 가상 DOM을 비교하여 실제 DOM에는 변경 사항만 업데이트 17 | - HTML & Vanilla JavaScript만 사용한 경우 실제 DOM의 요소가 변경되면 해당 요소와 모든 자식 요소가 업데이트 18 | - HTML & Vanilla JavaScript 19 | ```html 20 | 21 | 22 |
23 |
24 | old parent content 25 |
child content
26 |
27 |
28 | 38 | 39 | 40 | ``` 41 | - React 42 | ```jsx 43 | import React, { useState, useEffect, useRef } from 'react'; 44 | 45 | const useInterval = (callback, delay) => { 46 | const savedCallback = useRef(); 47 | 48 | useEffect(() => { 49 | savedCallback.current = callback; 50 | }, [callback]); 51 | 52 | useEffect(() => { 53 | const tick = () => { 54 | savedCallback.current(); 55 | }; 56 | 57 | if (delay) { 58 | const intervalId = setInterval(tick, delay); 59 | return () => { 60 | clearInterval(intervalId); 61 | }; 62 | } 63 | }, []); 64 | } 65 | 66 | const App = () => { 67 | const [on, setOn] = useState(true); 68 | 69 | useInterval(function () { 70 | setOn(!on); 71 | }, 1000); 72 | 73 | return ( 74 |
75 | {on ? 'old parent content' : 'new parent content'} 76 |
child content
77 |
78 | ); 79 | }; 80 | 81 | export default App; 82 | ``` 83 | 1. 선언형 프로그래밍으로 인한 가독성, 예측성, 유지보수성 향상 84 | - 선언형 프로그래밍: What에 집중 -> 무엇을 구현할 것인지 기술 -> 가독성, 예측성, 유지보수성 향상 85 | - React 86 | ```jsx 87 | const App = () => { 88 | const arr = [1, 2, 3, 4, 5]; 89 | return ( 90 |
    91 | {arr.map((LIElement) => ( 92 |
  • {LIElement}
  • 93 | ))} 94 |
95 | ); 96 | }; 97 | ``` 98 | - 명령형 프로그래밍: How에 집중 -> 어떻게 구현할 것인지 기술 -> 처리 속도는 빠르나, 가독성, 예측성, 유지보수성 저하 99 | - jQuery 100 | ```html 101 | 102 | 103 |
    104 | 110 | 111 | 112 | ``` 113 | 1. 다양한 라이브러리와 플러그인 지원 114 | 1. 타 프론트엔드 프레임워크 대비 큰 개발 커뮤니티 115 | - 개발/운영간 이슈 발생 시, 다른 개발자들의 도움을 손쉽게 받을 수 있음 116 | 117 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
    118 | [맨 위로 가기](#react) 119 | ## Rendering Process 120 | - 정의: Component가 화면에 그려지기 전, 새 가상 DOM을 생성하고 실제 DOM에 업데이트하는 과정 121 | - 순서: Render -> Commit 122 | ![Virtual DOM](./assets/img/virtual-dom.png) 123 | - Trigger 조건에 따른 프로세스 124 | - React App이 최초 실행된 경우 125 | 1. Render: 새 가상 DOM을 생성 126 | 1. Root Component를 호출하여 React Element 반환 127 | - render 함수 호출 128 | 1. current FiberNode를 재사용하여 workInProgress FiberNode를 생성할 수 있는지 확인 (React Element의 key, type을 비교) 129 | - reconcileChildren -> reconcileChildFibers -> reconcileSingleElement 함수 호출 130 | 1. current FiberNode가 없으므로 React Element를 통해 workInProgress FiberNode 생성 131 | - createFiberFromElement 함수 호출 132 | 1. workInProgress FiberNode를 통해 DOM Element 생성 후, workInProgress FiberNode의 stateNode에 기록 133 | - completeWork 함수 호출 134 | 1. Commit: 새 가상 DOM을 실제 DOM에 업데이트 135 | - state가 업데이트된 경우 136 | 1. Render: 새 가상 DOM을 생성하거나, 새 가상 DOM을 생성 후 이전 가상 DOM과 새 가상 DOM을 비교하여 변경 사항을 기록 137 | - 공통 프로세스 138 | 1. state 업데이트를 trigger한 Component를 호출하여 React Element 반환 139 | - renderWithHooks 함수 호출 140 | 1. current FiberNode를 재사용하여 workInProgress FiberNode를 생성할 수 있는지 확인 (React Element의 key, type을 비교) 141 | - reconcileChildren -> reconcileChildFibers -> reconcileSingleElement/reconcileChildrenArray 함수 호출 142 | - current FiberNode 재사용 가능 여부에 따른 분기 143 | - 불가능 144 | 1. React Element를 통해 workInProgress FiberNode 생성 145 | - createFiberFromElement 함수 호출 146 | 1. workInProgress FiberNode를 통해 DOM Element 생성하여 workInProgress FiberNode의 stateNode에 기록 147 | - completeWork 함수 호출 148 | - 가능 149 | 1. current FiberNode를 재사용하여 workInProgress FiberNode 생성 150 | - useFiber -> createWorkInProgress 함수 호출 151 | 1. current FiberNode와 workInProgress FiberNode를 비교하여 변경 사항을 workInProgress FiberNode의 updateQueue에 기록 152 | - completeWork 함수 호출 153 | 1. Commit: 새 가상 DOM 또는 변경 사항을 실제 DOM에 업데이트 154 | 155 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
    156 | [맨 위로 가기](#react) 157 | ## Reconciliation 158 | - 정의: 이전 가상 DOM과 새 가상 DOM을 비교하여 변경 사항을 실제 DOM에 업데이트하는 과정 159 | - 비교 방법: 시간 복잡도 O(n)의 Diffing 휴리스틱 비교 알고리즘을 사용 (아래 2가지 가정을 기반) 160 | 1. 서로 다른 타입의 두 Elements는 서로 다른 Tree를 만들어냄 161 | - 성능을 해치지 않기 위해 개발자가 항상 같은 타입의 Elements가 렌더링되도록 구현해야 함 162 | 1. 형제 노드 사이에서 각 Element의 key prop을 확인하여 어떤 노드를 변경하지 않아도 되는지 구분할 수 있음 163 | - 성능을 해치지 않기 위해 개발자가 형제 노드 사이에서 재정렬이 발생하는 경우를 대비하여 각 Element에 형제 노드 사이 기준 항상 unique한 key prop을 할당해야 함 (key prop을 할당하지 않으면 기본적으로 형제 노드 기준 index를 사용함) 164 | - Diffing 휴리스틱 비교 알고리즘 165 | - React가 두 개의 트리를 비교하는 시기: **state 또는 props가 업데이트**됐을 때 166 | - 가장 먼저 비교하는 것: 두 개의 **Root Elements** 167 | 1. 다른 타입의 Elements 168 | - 두 Root Elements의 타입이 다르면, React는 이전 Tree를 버리고 완전히 새로운 Tree를 구축 169 | ```tsx 170 | import { useState, useEffect } from 'react'; 171 | 172 | const App = (): JSX.Element => { 173 | const [tagName, setTagName] = useState('div'); 174 | const TagName = tagName; 175 | 176 | return ( 177 | 178 | 179 | 186 | 187 | ); 188 | }; 189 | 190 | const ChildComponent = (): JSX.Element => { 191 | useEffect(() => { 192 | console.log('ChildComponent --> Mounted'); 193 | 194 | return () => { 195 | console.log('ChildComponent --> Unmounted'); 196 | }; 197 | }, []); 198 | 199 | return <>; 200 | }; 201 | 202 | export default App; 203 | ``` 204 | 1. 같은 타입의 DOM Elements 205 | - 같은 타입의 DOM Elements를 비교할 때, React는 두 Elements의 속성을 확인하여, 동일한 내역은 유지하고 변경된 속성들만 갱신 206 | - DOM 노드의 처리가 끝나면, React는 이어서 해당 노드의 자식들을 재귀적으로 처리 207 | ```tsx 208 | import { useState, useEffect } from 'react'; 209 | 210 | const App = (): JSX.Element => { 211 | const [state, setState] = useState('before'); 212 | 213 | return ( 214 |
    219 | 220 | 228 |
    229 | ); 230 | }; 231 | 232 | const ChildComponent = (): JSX.Element => { 233 | useEffect(() => { 234 | console.log('ChildComponent --> Mounted'); 235 | 236 | return () => { 237 | console.log('ChildComponent --> Unmounted'); 238 | }; 239 | }, []); 240 | 241 | return <>; 242 | }; 243 | 244 | export default App; 245 | ``` 246 | 1. 같은 타입의 Component Elements 247 | - Component가 갱신되면 인스턴스는 동일하게 유지되어 렌더링 간 state가 유지됨. React는 새로운 Element의 내용을 반영하기 위해 현재 Component 인스턴스의 props를 갱신함. 이때 해당 인스턴스의 UNSAFE_componentWillReceiveProps(), UNSAFE_componentWillUpdate(), componentDidUpdate를 호출 248 | - 다음으로 render() 메서드가 호출되고 비교 알고리즘이 이전 결과와 새로운 결과를 재귀적으로 처리 249 | 1. 자식에 대한 재귀적 처리 250 | - DOM 노드의 자식들을 재귀적으로 처리할 때, React는 기본적으로 **동시에 두 리스트를 순회하고 차이점이 있으면 변경을 생성** 251 | - 자식들이 key prop을 가지고 있다면, React는 **key prop을 통해 이전 Tree와 새로운 Tree의 자식들이 일치하는지 확인**함. 예를 들어, 아래 예시에서 ChildComponentMemo에 key prop을 추가하여 Tree의 변환 작업이 효율적으로 수행되도록 수정할 수 있음 252 | ```tsx 253 | import React, { useState } from 'react'; 254 | 255 | const App = (): JSX.Element => { 256 | const [counts, updateCount] = useState( 257 | Array.from({ length: 10000 }, (_, i) => i) 258 | ); 259 | 260 | return ( 261 | <> 262 | 265 |
      266 | {counts.map((item: number) => ( 267 | // key prop을 넣지 않은 경우 268 | // 269 | 270 | // key prop을 넣은 경우 271 | 272 | ))} 273 |
    274 | 275 | ); 276 | }; 277 | 278 | const ChildComponent = (props: { text: number }): JSX.Element => { 279 | const [state] = useState(props.text); 280 | 281 | console.log('ChildComponent --> Rendered'); 282 | 283 | return ( 284 |
  • 285 | state: {state} -- text props: {props.text} 286 |
  • 287 | ); 288 | }; 289 | 290 | const ChildComponentMemo = React.memo(ChildComponent); 291 | 292 | export default App; 293 | ``` 294 | - key prop의 역할 295 | 1. Reconciliation이 진행될 때, 형제 노드 사이에서 재정렬이 발생하는 경우 어떤 노드가 변경되지 않아도 되는지 알려주는 역할 296 | - 각 Element의 key prop이 형제 노드 사이 기준 항상 unique한 경우 DOM Element의 속성 또는 Component Element의 props를 갱신하지 않아도 됨 297 | - 각 Element의 key prop이 형제 노드 사이 기준 항상 unique하지 않을 경우 DOM Element의 속성 또는 Component Element의 props를 갱신하기 때문에 성능적으로 좋지 않음 (최악의 경우 모든 형제 노드의 속성 또는 props가 변경됨) 298 | 1. Reconciliation이 진행될 때, 특정 Element의 key prop이 이전 형제 노드의 어떤 key prop과도 동일하지 않은 key prop으로 변경된 경우 DOM Element 또는 Component Element를 다시 생성하게 할 수 있음 299 | - DOM Element 300 | ```tsx 301 | import { useState, useEffect } from 'react'; 302 | 303 | const App = (): JSX.Element => { 304 | const [state, setState] = useState('before'); 305 | 306 | return ( 307 |
    308 | 309 | 316 |
    317 | ); 318 | }; 319 | 320 | const ChildComponent = (): JSX.Element => { 321 | useEffect(() => { 322 | console.log('ChildComponent --> Mounted'); 323 | 324 | return () => { 325 | console.log('ChildComponent --> Unmounted'); 326 | }; 327 | }, []); 328 | 329 | return <>; 330 | }; 331 | 332 | export default App; 333 | ``` 334 | - Component Element 335 | ```tsx 336 | import { useState, useEffect } from 'react'; 337 | 338 | const App = (): JSX.Element => { 339 | const [state, setState] = useState('before'); 340 | 341 | return ( 342 | <> 343 | 344 | 351 | 352 | ); 353 | }; 354 | 355 | const ChildComponent = (): JSX.Element => { 356 | useEffect(() => { 357 | console.log('ChildComponent --> Mounted'); 358 | 359 | return () => { 360 | console.log('ChildComponent --> Unmounted'); 361 | }; 362 | }, []); 363 | 364 | return <>; 365 | }; 366 | 367 | export default App; 368 | ``` 369 | 370 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
    371 | [맨 위로 가기](#react) 372 | ## Life-Cycle 373 | - 순서: Mount -> Update -> Unmount 374 | ![React Hook Flow Diagram](./assets/img/react-hook-flow-diagram.png) 375 | - 상태 376 | - Mount 377 | - 정의: Component가 최초 렌더링되어 DOM을 조작할 수 있는 상태 378 | - 순서: Run lazy initializers -> Render -> React updates DOM(Commit) -> Run LayoutEffects -> Browser paints screen -> Run Effects 379 | - 특징 380 | - Class Component에서의 componentDidMount는 Browser Painting 완료 후 동기적으로 호출되지만, Functional Component에서의 useEffect는 Browser Painting 완료 후 비동기적으로 호출됨 381 | - Update 382 | - 정의: Component 내 props나 state 값이 변경되면서 Component가 리렌더링되어 변경된 DOM을 조작할 수 있는 상태 383 | - 순서: Render -> React updates DOM(Commit) -> Cleanup LayoutEffects -> Run LayoutEffects -> Browser paints screen -> Cleanup Effects -> Run Effects 384 | - 특징 385 | - Parent-Child Component상에서의 호출 순서: Parent Render -> Child Render -> Child Cleanup LayoutEffects -> Parent Cleanup LayoutEffects -> Child Run LayoutEffects -> Parent Run LayoutEffects -> Child Cleanup Effects -> Parent Cleanup Effects -> Child Run Effects -> Parent Run Effects 386 | - Unmount 387 | - 정의: Component가 페이지 상에서 사라진 상태 388 | - 순서: Cleanup LayoutEffects -> Cleanup Effects 389 | - 부가 개념 390 | - Cleanup 391 | - Cleanup Trigger 조건 392 | 1. Update 393 | 2. UnMount 394 | - Cleanup 과정 (Render, Effects 위주로 단순화) 395 | 1. Update: Component 내 props나 state 값이 변경 -> Render -> Cleanup LayoutEffects -> Run LayoutEffects -> Cleanup Effects -> Run Effects 396 | - 최신 state를 참조: Render, Run LayoutEffects, Run Effects 397 | - 이전 렌더링의 Effects에서 들고 있던 state를 참조: Cleanup LayoutEffects, Cleanup Effects 398 | 2. UnMount: Component가 페이지 상에서 사라짐 -> Cleanup LayoutEffects -> Cleanup Effects 399 | - 이전 렌더링의 Effects에서 들고 있던 state를 참조: Cleanup LayoutEffects, Cleanup Effects 400 | - Cleanup이 필요없는 effect: 네트워크 요청, DOM 조작, 로깅 등 401 | - Cleanup이 필요한 effect: 이벤트 리스너 추가, 타이머 추가 등 402 | - Cleanup을 하지 않았을 때 발생 가능한 버그: 예를 들어 특정 페이지에서만 사용할 목적으로 window에 Event Listener 설정 후 Cleanup을 하지 않고 다른 페이지로 넘어가면, 해당 Event 발생 시 Event Handler 함수가 호출됨 403 | 404 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
    405 | [맨 위로 가기](#react) 406 | ## Hooks 407 | 1. useState 408 | - 정의 409 | - 상태값을 다루기 위한 Hook 410 | - 필요한 곳 411 | - Component Life-Cycle동안 값을 매 렌더링마다 초기화하고 싶지 않을 때 412 | - 값이 업데이트되면서 화면이 새로 바뀌어야 할 때 413 | 1. useEffect 414 | - 정의 415 | - Mount 또는 Update 시, Browser Painting 완료 후 DOM에 접근할 수 있는 상태에서 비동기적으로 호출되는 Hook 416 | - Component Tree 외부에 있는 것들을 props나 state에 따라 동기화하는 것 417 | - 의존성 배열 내에는 props나 state뿐만 아니라 ref.current도 포함될 수 있음 418 | - 필요한 곳 419 | - 네트워크 요청, DOM 조작, 이벤트 리스너 추가, 타이머 추가, 로깅 등의 side effects를 유발하는 작업을 실행해야 할 때 420 | - 일반적으로 useEffect 내에서 해야하는 작업을 useEffect 외부에서 하면 안되는 이유 421 | - 네트워크 요청 후 응답 데이터를 통해 상태 업데이트를 하려는 경우, 렌더링 프로세스가 정상적으로 진행되지 않을 것이기 때문 422 | - DOM을 조작하려는 경우, Life-Cycle 상 React updates DOM(Commit) 전이므로 DOM에 접근할 수 없을 것이기 때문 423 | 1. useRef 424 | - 정의 425 | - DOM 요소 접근 또는 특정 값을 담기 위한 객체를 반환하는 Hook 426 | - 필요한 곳 427 | 1. DOM 요소 접근 428 | 1. 특정 값을 담기 429 | - Component Life-Cycle동안 값을 매 렌더링마다 초기화하고 싶지 않을 때 430 | - 값이 업데이트되면서 화면이 새로 바뀌지 않아도 될 때 431 | - 값이 업데이트되는 즉시 해당 값을 Component 내부에서 사용하고 싶을 때 432 | ```tsx 433 | import { useState, useRef } from 'react'; 434 | 435 | const App = (): JSX.Element => { 436 | const refObject = useRef(1); 437 | const rawObject = { current: 1 }; 438 | 439 | const [isChanged, setIsChanged] = useState(false); 440 | 441 | return ( 442 | <> 443 | 447 | 450 | 454 | 455 | ); 456 | }; 457 | 458 | export default App; 459 | // 값 변경 버튼 클릭 -> 리렌더링 버튼 클릭 -> 값 확인 버튼 클릭 시, 460 | // refObject.current는 2 출력 (useRef가 반환한 객체는 Life-Cycle동안 값을 유지) 461 | // rawObject.current는 1 출력 (일반 JavaScript 객체는 리렌더링 시 값이 초기화됨) 462 | ``` 463 | 1. useCallback 464 | - 정의 465 | - 함수를 메모이제이션하기 위해서 사용되는 Hook 466 | - 필요한 곳 467 | - 자식 Component가 React.memo로 Wrapping되어 있고, 해당 자식 Component에 함수를 props로 전달해야 할 때 468 | - 주의사항 469 | - 메모이제이션함으로써 추가적인 메모리를 더 사용해야 하고, 이전 렌더링에서의 함수 참조값과 새 렌더링에서의 함수 참조값을 비교하는 추가적인 복잡도가 존재한다는 점을 고려해야 함 470 | 1. useMemo 471 | - 정의 472 | - 값을 메모이제이션하기 위해서 사용되는 Hook 473 | - 필요한 곳 474 | - 자식 Component가 React.memo로 Wapping되어 있고, 해당 자식 Component에 배열 또는 객체를 props로 전달해야 할 때 475 | - 굉장히 오래 걸리는 연산의 반환값을 다루어야 할 때 476 | ```tsx 477 | import { useState, useMemo } from 'react'; 478 | 479 | const CounterButton = ({ counter, setCounter }: { counter: number; setCounter: (counter: number) => void }): JSX.Element => { 480 | return ( 481 |
    482 | 492 |
    493 | ); 494 | }; 495 | 496 | const OtherCounterButton = ({ otherCounter, setOtherCounter }: { otherCounter: number; setOtherCounter: (otherCounter: number) => void }): JSX.Element => { 497 | return ( 498 |
    499 | 509 |
    510 | ); 511 | }; 512 | 513 | const App = (): JSX.Element => { 514 | const [counter, setCounter] = useState(0); 515 | const [otherCounter, setOtherCounter] = useState(0); 516 | const doubleNumber = useMemo(() => { 517 | return slowFunction(counter); 518 | }, [counter]); 519 | // const doubleNumber = slowFunction(counter); 520 | 521 | return ( 522 |
    529 |
    534 |

    카운터: {counter}

    535 | 536 |
    537 |
    542 |

    다른 카운터: {otherCounter}

    543 | 544 |
    545 |

    카운터 X 2: {doubleNumber}

    546 |
    547 | ); 548 | }; 549 | 550 | const slowFunction = (num: number): number => { 551 | for (let i = 0; i < 500000000; i++) { } 552 | return num * 2; 553 | } 554 | 555 | export default App; 556 | ``` 557 | - 주의사항 558 | - 메모이제이션함으로써 추가적인 메모리를 더 사용해야 하고, 이전 렌더링에서의 함수 참조값과 새 렌더링에서의 함수 참조값을 비교하는 추가적인 복잡도가 존재한다는 점을 고려해야 함 559 | 1. useLayoutEffect 560 | - 정의 561 | - Mount 또는 Update 시, Browser Painting 완료 전 DOM에 접근할 수 있는 상태에서 동기적으로 호출되는 Hook 562 | - 필요한 곳 563 | - Browser Local Storage에 들어있는 로그인 상태값를 가져와서 그에 맞는 화면에 보여주는 등 Browser Painting하기 전에 값을 가져올 수 있을 때 564 | ```tsx 565 | import { useState, useEffect, useLayoutEffect } from 'react'; 566 | 567 | localStorage.setItem('isLogin', 'true'); 568 | 569 | const App = () => { 570 | const [isLogin, setIsLogin] = useState('false'); 571 | 572 | useLayoutEffect(() => { 573 | for (let i = 0; i < 100000000; i++) {} 574 | setIsLogin(localStorage.getItem('isLogin') as string); 575 | }, []); 576 | 577 | // useEffect(() => { 578 | // for (let i = 0; i < 100000000; i++) {} 579 | // setIsLogin(localStorage.getItem('isLogin') as string); 580 | // }, []); 581 | 582 | return ( 583 |
    584 | {JSON.parse(isLogin) ? 'You are logged in' : 'You are not logged in'} 585 |
    586 | ); 587 | }; 588 | 589 | export default App; 590 | ``` 591 | - 주의사항 592 | - 동기적으로 호출되므로 많은 로직이 존재할 경우, 사용자가 화면을 보기까지 시간이 오래 걸릴 수 있음 593 | ```tsx 594 | import { useEffect, useRef, useLayoutEffect } from 'react'; 595 | 596 | const App = (): JSX.Element => { 597 | const inputRef = useRef(null); 598 | 599 | useEffect(() => { 600 | inputRef.current.value = 'another user'; 601 | }); 602 | 603 | useLayoutEffect(() => { 604 | console.log(inputRef.current.value); 605 | }); 606 | 607 | return ( 608 |
    609 | 610 |
    611 | ); 612 | } 613 | 614 | export default App; 615 | // 화면에는 "another user"가 나오지만, 콘솔에는 "EmmanuelTheCoder"가 찍힘 616 | ``` 617 | 618 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
    619 | [맨 위로 가기](#react) 620 | ## Performance Optimization 621 | 1. Input Element 622 | - 기존 623 | ```tsx 624 | import { useState, FormEvent, ChangeEvent } from 'react'; 625 | 626 | const App = (): JSX.Element => { 627 | const [inputState, setInputState] = useState(''); 628 | 629 | return ( 630 |
    ) => { 631 | event.preventDefault(); 632 | 633 | // do something with inputValue 634 | }}> 635 | ) => { 636 | setInputState(event.target.value); 637 | }} /> 638 |
    639 | ); 640 | }; 641 | 642 | export default App; 643 | ``` 644 | - 개선 645 | ```tsx 646 | import { useRef, FormEvent } from 'react'; 647 | 648 | const App = (): JSX.Element => { 649 | const inputRef = useRef(null); 650 | 651 | return ( 652 |
    ) => { 653 | event.preventDefault(); 654 | 655 | if (!inputRef.current) { 656 | return; 657 | } 658 | 659 | // do something with inputRef.current.value 660 | }}> 661 | 662 |
    663 | ); 664 | }; 665 | 666 | export default App; 667 | ``` 668 | 669 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
    670 | [맨 위로 가기](#react) -------------------------------------------------------------------------------- /react/assets/img/react-hook-flow-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sekhyuni/frontend-basic-concept/373798ba22b3b05f9302222f697491583a0ca6cc/react/assets/img/react-hook-flow-diagram.png -------------------------------------------------------------------------------- /react/assets/img/virtual-dom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sekhyuni/frontend-basic-concept/373798ba22b3b05f9302222f697491583a0ca6cc/react/assets/img/virtual-dom.png -------------------------------------------------------------------------------- /typescript/README.md: -------------------------------------------------------------------------------- 1 | # TypeScript 2 | 3 | * [Type Options](#type-options) 4 | * [interface vs type](#interface-vs-type) 5 | * [Utility Type](#utility-type) 6 | * [Index Signature](#index-signature) 7 | * [Use Property Type Of Any Interface](#use-property-type-of-any-interface) 8 | * [Generic](#generic) 9 | * [Readonly](#readonly) 10 | 11 | ## Type Options 12 | |Type|JS|TS|Description| 13 | |:---:|:---:|:---:|:---:| 14 | |boolean|O|O|true와 false| 15 | |null|O|O|값이 없다는 것을 명시| 16 | |undefined|O|O|값을 할당하지 않은 변수의 초기값| 17 | |number|O|O|숫자(정수와 실수, Infinity, NaN| 18 | |string|O|O|문자열| 19 | |symbol|O|O|고유하고 수정 불가능한 데이터 타입이며 주로 객체 프로퍼티들의 식별자로 사용(ES6에서 추가)| 20 | |object|O|O|객체형(참조형)| 21 | |array|X|O|배열| 22 | |tuple|X|O|고정된 요소수 만큼의 타입을 미리 선언후 배열을 표현| 23 | |enum|X|O|열거형. 숫자값 집합에 이름을 지정한 것| 24 | |any|X|O|타입 추론(type inference)할 수 없거나 타입 체크가 필요없는 변수에 사용. var 키워드로 선언한 변수와 같이 어떤 타입의 값이라도 할당 가능| 25 | |void|X|O|일반적으로 함수에서 반환값이 없을 경우 사용| 26 | |never|X|O|결코 발생하지 않는 값| 27 | 28 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
    29 | [맨 위로 가기](#typescript) 30 | ## interface vs type 31 | 1. interface 32 | - 확장 방법 33 | ```typescript 34 | interface PeopleInterface { 35 | name: string; 36 | age: number; 37 | } 38 | 39 | interface StudentInterface extends PeopleInterface { 40 | school: string; 41 | } 42 | ``` 43 | - 선언적 확장 가능 44 | ```typescript 45 | interface Window { 46 | title: string; 47 | } 48 | 49 | interface Window { 50 | ts: TypeScriptAPI; 51 | } 52 | 53 | // 같은 interface 명으로 Window를 다시 만든다면, 자동으로 확장 54 | ``` 55 | - 객체에만 사용 가능 56 | ```typescript 57 | interface FooInterface { 58 | value: string; 59 | } 60 | 61 | type FooType = { 62 | value: string; 63 | } 64 | 65 | type FooOnlyString = string; 66 | type FooTypeNumber = number; 67 | 68 | // 불가능 69 | interface X extends string {} 70 | ``` 71 | 1. type 72 | - 확장 방법 73 | ```typescript 74 | type PeopleType = { 75 | name: string; 76 | age: number; 77 | } 78 | 79 | type StudentType = PeopleType & { 80 | school: string; 81 | } 82 | ``` 83 | 84 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
    85 | [맨 위로 가기](#typescript) 86 | ## Utility Type 87 | 1. Partial 88 | - 특정 타입의 부분 집합을 만족하는 타입을 정의 89 | ```typescript 90 | interface Address { 91 | email: string; 92 | address: string; 93 | } 94 | 95 | type MyEmail = Partial
    ; 96 | const me: MyEmail = {}; // 가능 97 | const you: MyEmail = { email: "noh5524@gmail.com" }; // 가능 98 | const all: MyEmail = { email: "noh5524@gmail.com", address: "secho" }; // 가능 99 | ``` 100 | 1. Pick 101 | - 특정 타입에서 몇 개의 속성을 선택해서 타입을 정의 102 | ```typescript 103 | interface Product { 104 | id: number; 105 | name: string; 106 | price: number; 107 | brand: string; 108 | stock: number; 109 | } 110 | 111 | type shoppingItem = Pick; 112 | 113 | const apple: Pick = { 114 | id: 1, 115 | name: 'red apple', 116 | price: 1000, 117 | }; 118 | ``` 119 | 1. Omit 120 | - 특정 속성만 제거한 타입을 정의 121 | ```typescript 122 | interface Product { 123 | id: number; 124 | name: string; 125 | price: number; 126 | brand: string; 127 | stock: number; 128 | } 129 | 130 | const apple: Omit = { 131 | id: 1, 132 | name: 'red apple', 133 | price: 1000, 134 | brand: 'del', 135 | }; 136 | ``` 137 | 138 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
    139 | [맨 위로 가기](#typescript) 140 | ## Use Property Type Of Any Interface 141 | ```typescript 142 | interface Meta { 143 | code: string; 144 | message: string; 145 | } 146 | 147 | interface Product { 148 | id: number; 149 | name: string; 150 | price: number; 151 | brand: string; 152 | stock: number; 153 | } 154 | 155 | interface ListOfProduct = { 156 | meta: Meta; 157 | data: Product[]; 158 | }; 159 | 160 | const product: ListOfProduct['data'][0] = { 161 | id, 162 | name, 163 | price, 164 | brand, 165 | stock, 166 | }; 167 | ``` 168 | 169 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
    170 | [맨 위로 가기](#typescript) 171 | ## Index Signature 172 | - interface 또는 객체형 type의 속성 이름은 알지 못하고 값의 타입은 알고 있을 때 사용 173 | - key의 타입은 string, number, symbol, template literal만 가능 174 | ```typescript 175 | type UserType = { 176 | [key: string]: string | number | boolean; 177 | }; 178 | 179 | const user: UserType = { 180 | name: '홍길동', 181 | age: 20, 182 | man: true, 183 | }; 184 | ``` 185 | 186 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
    187 | [맨 위로 가기](#typescript) 188 | ## Generic 189 | - 제네릭은 선언 시점이 아니라 생성 시점에 타입을 명시하여 하나의 타입만이 아닌 다양한 타입을 사용할 수 있도록 하는 기법 190 | - 한번의 선언으로 다양한 타입에 재사용 가능 191 | - T는 제네릭을 선언할 때 관용적으로 사용되는 식별자로 타입 파라미터(Type parameter)를 의미 192 | ```typescript 193 | const doSomething = (param: T): T => { 194 | return param; 195 | }; 196 | 197 | doSomething('a'); 198 | ``` 199 | 200 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
    201 | [맨 위로 가기](#typescript) 202 | ## Readonly 203 | 1. readonly 204 | ```typescript 205 | type SomeType = { 206 | readonly prop: number; 207 | }; 208 | interface SomeInterface { 209 | readonly prop: number; 210 | } 211 | 212 | const objOfSomeType: SomeType = { prop: 1 }; 213 | const objOfSomeInterface: SomeInterface = { prop: 1 }; 214 | 215 | objOfSomeType.prop = 2; // Error. 읽기 전용 속성이므로 'prop'에 할당할 수 없습니다. 216 | objOfSomeInterface.prop = 2; // Error. 읽기 전용 속성이므로 'prop'에 할당할 수 없습니다. 217 | ``` 218 | 1. Readonly 219 | ```typescript 220 | type SomeType = { 221 | prop: number; 222 | }; 223 | type SomeTypeReadonly = Readonly; 224 | 225 | const objOfSomeType: SomeType = { prop: 1 }; 226 | const objOfSomeTypeReadonly: SomeTypeReadonly = { prop: 1 }; 227 | 228 | objOfSomeType.prop = 2; // OK 229 | objOfSomeTypeReadonly.prop = 2; // Error. 읽기 전용 속성이므로 'prop'에 할당할 수 없습니다. 230 | ``` 231 | 1. ReadonlyArray 232 | ```typescript 233 | const listOfStringUseReadonlyArray: ReadonlyArray = ['a']; 234 | const listOfStringUseReadonly: readonly string[] = ['a']; 235 | 236 | listOfStringUseReadonlyArray.push('b'); // Error. 'readonly string[]' 형식에 'push' 속성이 없습니다. 237 | listOfStringUseReadonly.push('b'); // Error. 'readonly string[]' 형식에 'push' 속성이 없습니다. 238 | ``` 239 | 1. const Type Parameters 240 | - Version < 5.0 241 | ```typescript 242 | const outerFunction = (outerParam: T) => { 243 | const innerFunction = (innerParam: T) => {}; 244 | return { innerFunction }; 245 | }; 246 | 247 | outerFunction(['a'] as const).innerFunction(['a', 'b']); // Error. '["a", "b"]' 형식의 인수는 'readonly ["a"]' 형식의 매개 변수에 할당될 수 없습니다. 소스에 2개 요소가 있지만, 대상에서 1개만 허용합니다. 248 | ``` 249 | - Version >= 5.0 250 | ```typescript 251 | const outerFunction = (outerParam: T) => { 252 | const innerFunction = (innerParam: T) => {}; 253 | return { innerFunction }; 254 | }; 255 | 256 | outerFunction(['a']).innerFunction(['a', 'b']); // Error. '["a", "b"]' 형식의 인수는 'readonly ["a"]' 형식의 매개 변수에 할당될 수 없습니다. 소스에 2개 요소가 있지만, 대상에서 1개만 허용합니다. 257 | ``` 258 | 259 | [메인으로 가기](https://github.com/sekhyuni/frontend-basic-concept)
    260 | [맨 위로 가기](#typescript) --------------------------------------------------------------------------------