본문 바로가기

나만의 모음집

JS/TS/HTML/CSS /... 등 관련 개념 및 활용 예제를 모아두는 아카이브

반응형

연관 포스트

[연관 포스트] IT 용어 모음집

 

[모음집] IT 용어 모음집

오늘의 명언예제 코드가 있다면 자바스크립트/타입스크립트 기준으로 작성되어 있습니다. libuv 라이브러리libuv는 Node.js에서 주로 사용되는 C 라이브러리로, 주로 비동기 이벤트 기반 프

duklook.tistory.com

[연관 포스트] 자바스크립트 활용 모음집

 

자바스크립트 활용 모음집

해당 포스트는..자바스크립트 활용을 위한 간단한 예제를 정리해두는 모음집입니다. 필요에 따라 추가될 수 있고, 추가되는 경우 해당 날짜를 기준으로 갱신됩니다. 이미지 미리보기 기능을 구

duklook.tistory.com

[연관 포스트] 리액트 활용 모음집

 

리액트 활용 모음집

해당 포스트는..해당 포스트는 리액트를 사용하면서 알게된 팁이나 주요하다고 판단되는 활용 예제를 정리하여 모아두는 공간입니다. 직접 활용하다가 알게된 것은 출처가 없고, 참고한 자료가

duklook.tistory.com

 

이 포스트는..

자바스크립트와 연관된 모든 기술 스텍에 대한 활용 개념 등을 모아두는 저장소 입니다. 그 때 그 때 공부한 내용이나 알아야 했던 내용들을 간략하게 정리하는 방식으로 처리되며, 자료의 출처는 GPT 등의 도구를 통해서 얻거나, MDN 과 같은 어느 정도 공신력이 있는 사이트 혹은 실제 도구의 공식문서를 참고해서 정리합니다. 자료가 추가되는 경우에는 해당 날짜를 기준으로 갱신될 수 있습니다. 현재 이 글이 최신 날짜로 올라와 있다고 해도, 실제 포스트가 생성된 날짜는 오래되었을 수 있습니다.


보안과 관련한 개념

XSS | 웹 사이트 관리자가 아닌 악의적 사용자에 의해 심어진 악의적 스크립트가 서버에서 실행되는 공격 기법

웹 보안과 관련한 정보들 중에서 XSS 가 제일 많이 보였던 것 같습니다. 해당 공격 기법은 악의적 제 3자가 사용자로 하여금 특정한 행동을 하게 만드는 CSRF 와 달리 공격자가 직접 악의적인 스크립트를 특정 태그나 요청에 심어서 서버로 보내고, 서버에서는 정상적인 요청이라 판단하고 처리하는 아주 무서운 공격 기법입니다.

 

웹 앱이 충분한 유효성 검사나 인코딩을 사용하지 않으면 이러한 공격은 성공하게 됩니다. 사용자의 브라우저는 신뢰할 수 없는 악성 스크립트를 탐지할 수 없고, 쿠키, 세션 토큰 또는 기타 민감한 사이트별 정보에 대한 접근 권한을 부여해버리거나 악성 스크립트가 HTML 콘텐츠를 다시 작성할 수 있도록 합니다.

 

XSS 유형

여기서 몇 가지 대표되는 XSS 유형에 대해 정리해봅니다. 

 

- 반사된 XSS 공격
반사 공격은 오류 메시지, 검색 결과 또는 요청의 일부로 서버에 전송된 입력의 일부 또는 전부를 포함하는 기타 응답과 같이 삽입된 스크립트가 웹 서버 외부에 반사되는 공격입니다. 반영된 공격은 이메일 메시지나 다른 웹사이트 등 다른 경로를 통해 피해자에게 전달됩니다. 사용자가 속아서 악성 링크를 클릭하거나 특별히 제작된 양식을 제출하거나 심지어 악성 사이트를 탐색하게 되면 주입된 코드가 취약한 웹 사이트로 이동하고, 이는 공격을 사용자의 브라우저에 다시 반영합니다. 그러면 브라우저는 해당 코드가 "신뢰할 수 있는" 서버에서 왔기 때문에 실행합니다. 반사 XSS는 비지속성 또는 Type-I XSS라고도 합니다(공격은 단일 요청/응답 주기를 통해 수행됨).

- 저장된 XSS 공격
저장 공격은 주입된 스크립트가 데이터베이스, 메시지 포럼, 방문자 로그, 댓글 필드 등과 같은 대상 서버에 영구적으로 저장되는 공격입니다. 그런 다음 피해자는 저장된 스크립트를 요청할 때 서버에서 악성 스크립트를 검색합니다. 정보. 저장된 XSS는 영구 또는 Type-II XSS라고도 합니다.

- 블라인드 크로스 사이트 스크립팅
블라인드 크로스 사이트 스크립팅은 지속적인 XSS의 한 형태입니다. 이는 일반적으로 공격자의 페이로드가 서버에 저장되고 백엔드 애플리케이션에서 피해자에게 다시 반영될 때 발생합니다. 예를 들어 피드백 양식에서 공격자는 해당 양식을 사용하여 악성 페이로드를 제출할 수 있으며, 애플리케이션의 백엔드 사용자/관리자가 백엔드 애플리케이션을 통해 공격자가 제출한 양식을 열면 공격자의 페이로드가 실행됩니다. 블라인드 크로스 사이트 스크립팅은 실제 시나리오에서 확인하기 어렵지만 이를 위한 가장 좋은 도구 중 하나는 XSS Hunter입니다.

 

 

XSS 에 대해서 간략하게 정리해 보았습니다. 아이러니 하게도 요청 시 받은 데이터에 대한 유효성 검사만 엄격하게 해줘도 대부분의 XSS 는 막을 수 있다고 합니다. 그럼에도 XSS 에 뚫리는 웹 사이트나 앱이 많다고 어디선가 본 적이 있는 것 같은데, 데이터베이스에 중요한 정보를 담고 있다면, 클라이언트나 백엔드 모두에서 엄격한 유효성 검사를 2중 3중으로 하는 것이 중요하지 않을까 생각하면서 글을 줄여 봅니다.

 

 

CSRF | 사용자가 자신의 의지와 무관하게 공격자가 의도한 행동을 하게 만드는 공격 기법

 2024.06.23 추가

CSRF(Cross-Site Request Forgery)는 웹 보안 공격 중 하나로, 사용자가 자신의 의지와는 무관하게 공격자가 의도한 행동을 하게 만드는 공격 기법입니다. 이는 주로 사용자가 신뢰하는 웹 사이트에 대한 권한을 가진 상태에서 발생합니다. 즉, 백엔드 서버 환경에서는 사용자의 자격증명이 된 상태에서 클라이언트로 부터 전달되는 요청을 신뢰하게 되고, 그 요청을 처리하게 되는 것이죠. 즉, 사용자는 자신도 모르게 요청을 보냈지만, 그 요청이 알고보니 제 3 자에 의해서 보내진 것이니 등꼴이 오싹한 순간이 아닐까 싶습니다.

 

CSRF의 동작 원리

그럼 CSRF 는 어떤 과정을 거쳐서 발생할까요? 간단한 예시를 통해서 살펴보겠습니다. 

 

- 사용자 인증: 사용자가 웹 애플리케이션에 로그인하여 인증된 세션을 얻습니다.
- 악의적인 요청 생성: 공격자는 사용자를 속여 특정 웹 사이트에 접속토록 유도하고, 인증된 세션이 유지된 상태에서 악의적인 요청을 보냅니다. 이 요청은 사용자가 로그인한 웹 애플리케이션 서버를 대상으로 합니다.

 

여기서 핵심을 정리하면 자동 전송과 서버 처리 부분입니다. 


- 자동 전송: 사용자는 악의적인 링크를 클릭하거나 악성 웹 사이트를 방문하여, 자신도 모르게 요청을 전송합니다.
- 서버 처리: 서버는 요청을 신뢰할 수 있는 사용자로부터 온 것으로 간주하고 정상적으로 처리합니다.

 

이렇듯, CSRF 는 사용자가 자격증명을 획득한 상태에서 유지되는 세션이 살아 있는 상태로 특정 사이트 접속을 유도하고, 해당 사이트에서 사용자는 자신도 모르게 악의적인 요청을 서버로 전송합니다. 서버는 당연히 증명된 자격을 토대로 보내온 요청으로 판단하고, 그 요청을 처리해서 응답하게 되죠.

 


 

동작원리만 보면 이해가 안 될 수 있으니,  조금 더 이해하기 쉬운 예시를 가져와서 살펴봅시다. 우선, 사용자가 로그인된 상태에서 공격자가 조작한 페이지에 접속했을 때, 해당 페이지가 자동으로 아래와 같은 요청을 보낸다고 가정해봅시다:

<img src="http://trusted-website.com/transfer?amount=1000&to=attacker_account" style="display:none;">


사용자는 아무런 의심 없이 해당 페이지를 방문했고, img 태그에 적힌 src 주소의 서버로 특정 요청을 보냅니다. 이 때, 서버는 해당 요청(transfer?amount=1000&to=attacker_account)을 정상적인 사용자 요청으로 받아들이고, 결과적으로 공격자의 계정으로 돈이 이체됩니다.

 

아주 단순하고, 있을 수 없을 것 같은 사례처럼 보이지만, CSRF 는 교묘하게 동작하기 때문에, 조금이라도 소홀할 수 있는 보안의 허점을 노리는 만큼 그 여파는 무시하지 못할 정도로 큽니다.

 

CSRF 방지 방법

그럼 이를 방지하기 위해서는 어떤 조치를 취해야 할까요? 몇 가지는 정리해 봤습니다.

 

- CSRF 토큰 사용: 각 요청에 대해 고유한 토큰을 발행하고, 서버는 요청 시 이 토큰을 확인하여 유효성을 검증합니다. 즉, 서버에서 클라이언트가 자격 증명을 얻을 때, CSRF 토큰을 같이 보내고, 클라이언트에서는 요청을 보낼 때 해당 CSRF 토큰을 같이 보냄으로서 서버는 해당 토큰이 처음 발급된 토큰이 맞는지 검증하고, 통과를 하는 경우 요청을 처리하고 응답하게 됩니다.


- Referer 헤더 검사: 요청의 출처를 확인하여 신뢰할 수 있는 출처에서 온 요청인지 확인합니다. 이는 악의적인 웹 사이트로 사용자의 방문을 유도하고, 해당 사이트에서 유저가 로그인한 사이트의 백엔드로 요청을 보내게 됩니다. 이 때, Referer 헤더에는 요청을 보낸 사이트의 도메인 주소를 포함하고 있기 때문에, 서버에서는 해당 헤더를 검사해서 CSRF 를 방지할 수 있습니다.


- SameSite 속성 설정: 쿠키의 SameSite 속성을 설정하여 크로스 사이트에서의 쿠키 전송을 제한합니다.


- 정확한 CORS 설정: 적절한 Cross-Origin Resource Sharing (CORS) 설정을 통해 신뢰할 수 있는 도메인에서만 요청을 허용합니다. 이는 요청을 보내는 주소의 Origin 을 명확히 해서 프로토콜, 도메인, 포트가 서버에서 요구하는 주소 체계와 동일하지 않으면 해당 HTTP 요청을 차단하는 것입니다.

 

예를 들어, 백엔드 서버에서 https://www.example.com 이라는 주소의 출처만 허용 했다고 가정해봅시다. 이 떄, http://www.example.com 이라는 주소로 서버에 요청을 보내면, 어떻게 될까요?

 

정답)

더보기

정답은 차단한다 입니다.  왜 그럴까요? 바로 https 와 http 라는 프로토콜이 서로 다르기 때문입니다.

 

오늘은 CSRF 에 대해서 정리해보는 시간을 가졌습니다. 위에서 언급한 예시와 대처법은 극히 일부에 불과하고, 정말 악의적으로 접근하는 CSRF 는 저리 단순한 형태가 아닐겁니다. 그 만큼 갈수록 수법은 고도화되고 있고, 서버에서 보안과 관련한 처리를 엄격하게 해야하는 이유이기도 하겠죠. 이 글을 정리하고 있는 시점에도 본인의 사이트에는 그러한 처리가 되어 있는가 물어본다면 허점이 많지 않을까 생각이 듭니다. 한 번 살펴보면서 심각한 문제가 없을지 찾아보는 시간을 가져봐야 겠네요.

 

테스트 관련 개념

테스트를 왜 하세요?

우리는 인간이고 인간은 실수를 합니다. 테스트는 이러한 실수를 발견하고 코드가 작동하는지 확인하는 데 도움이 되기 때문에 중요합니다. 아마도 더 중요한 것은 테스트를 통해 향후 새로운 기능을 추가하거나 기존 기능을 리팩터링하거나 프로젝트의 주요 종속성을 업그레이드할 때 코드가 계속 작동하는지 확인하는 것입니다.

테스트에는 생각보다 더 많은 가치가 있습니다. 코드의 버그를 수정하는 가장 좋은 방법 중 하나는 이를 노출하는 실패한 테스트를 작성하는 것입니다. 그런 다음 버그를 수정하고 테스트를 다시 실행할 때 통과하면 버그가 수정되었으며 코드 베이스에 다시 도입되지 않음을 의미합니다.

테스트는 팀에 합류하는 새로운 사람들을 위한 문서 역할을 할 수도 있습니다. 이전에 코드베이스를 본 적이 없는 사람들의 경우 테스트를 읽으면 기존 코드가 어떻게 작동하는지 이해하는 데 도움이 될 수 있습니다.

마지막으로, 자동화된 테스트는 수동 QA 에 소요되는 시간을 줄여 귀중한 시간을 확보할 수 있다는 것을 의미합니다.

 

정적 분석

코드 품질을 향상시키는 첫 번째 단계는 정적 분석 도구를 사용하는 것입니다. 정적 분석은 코드를 작성할 때 코드에 오류가 있는지 확인하지만 해당 코드를 실행하지는 않습니다.

Linter는 코드를 분석하여 사용되지 않는 코드와 같은 일반적인 오류를 포착하고 함정을 피하는 데 도움을 주며, 공백 대신 탭을 사용하는 것과 같은 스타일 가이드 no-nos에 플래그를 지정합니다(또는 구성에 따라 그 반대).


타입 검사(ex. 타입스크립트 )는 함수에 전달하는 구문이 함수가 허용하도록 설계된 구문과 일치하는지 확인하여 예를 들어 숫자를 기대하는 계산 함수에 문자열이 전달되는 것을 방지합니다.

 

테스트 가능한 코드를 만드는 것

테스트를 시작하려면 먼저 테스트 가능한 코드를 작성해야 합니다. 항공기 제조 공정을 생각해 보세요. 모든 복잡한 시스템이 서로 잘 작동하는지 보여주기 위해 모델이 처음 이륙하기 전에 개별 부품을 테스트하여 안전하고 올바르게 작동하는지 확인합니다. 예를 들어, 날개는 극심한 하중 하에서 구부려 테스트됩니다. 엔진 부품의 내구성 테스트를 거쳤습니다. 앞유리는 시뮬레이션된 새 충격에 대해 테스트되었습니다.

 

소프트웨어도 비슷합니다. 여러 줄의 코드가 포함된 하나의 거대한 파일에 전체 프로그램을 작성하는 대신, 전체를 테스트하는 것보다 더 철저하게 테스트할 수 있는 여러 개의 작은 모듈에 코드를 작성합니다. 이러한 방식으로 테스트 가능한 코드를 작성하는 것은 깔끔한 모듈식 코드 작성과 얽혀 있습니다.

 

앱을 더욱 테스트 가능하게 만들려면 앱의 보기 부분(React 구성요소)을 비즈니스 로직 및 앱 상태(Redux, MobX 또는 기타 솔루션 사용 여부에 관계없이)에서 분리하는 것부터 시작하세요. 이렇게 하면 React 구성 요소에 의존해서는 안 되는 비즈니스 로직 테스트를 주로 앱의 UI를 렌더링하는 구성 요소 자체와 독립적으로 유지할 수 있습니다!

 

이론적으로는 구성 요소에서 가져오는 모든 논리와 데이터를 이동할 수도 있습니다. 이렇게 하면 구성 요소가 렌더링에만 전념하게 됩니다. 귀하의 상태는 귀하의 구성 요소와 완전히 독립적입니다. 앱의 로직은 React 구성 요소 없이도 작동합니다!

이 부분은 이렇게 생각해볼 수 있습니다. 예를 들어 FoodList 컴포넌트가 있고, 그 상위에는 FoodPage 라는 컴포넌트가 있다고 해봅시다 즉, FoodPage 내에 FoodList 가 자식 구성 요소로 있는 것이죠. 이 떄 FoodList 는 부모 컴포넌트인 FoodPage 로 부터 데이터를 props 으로 전달 받는다면 어떨까요? 이렇게 되면 props 으로 전달된 데이터를 FoodList 는 받아서 렌더링하기만 하면 되므로 View 로서 역할만 수행하면 됩니다.

 

CSS / HTML 관련 개념

px, em, rem 의 차이

px 는 고정 단위로서 화면의 해상도와 상관없이 항상 동일한 크기를 가집니다. 주로 정확한 크기를 지정할 떄 하죠.

반면 em 와 rem은 상대적인 단위입니다. 그 중 em 은 부모 요소의 font-size 에 따라서 계산되는 값이 달라지는데, 예를 들어, 부모 요소의 font-size 가 16px 라면, 2em 은 32px 가 됩니다.

 

rem 의 경우에는 em과 유사하지만, 상위 부모 요소가 아니라 최상위 HTML 요소를 기준으로 결과가 달라집니다. 즉, html 태그를 기준으로 만일 font-size 가 16px 라면 2rem 은 32px 로 계산이 됩니다. 

 

반응형 웹을 구현하는 경우 px, em, rem 중 무엇을 주로 사용하면 좋을까요?

이는 정답은 없다고 봅니다. 다만, px 의 경우에는 절대적인 단위이므로 화면의 해상도가 넓든 좁든 동일한 크기로 계산되므로 반응형 웹을 구현할 때 제약사항이 많습니다. 따라서 em 과 rem 중에서 사용하면 좋은데, 이 때 중요한 것은 em 단위로 지정한 요소의 부모 font-size 가 자주 변동되어 레이아웃의 일관성을 유지하는데 방해가 된다면, em 보다는 rem을 사용하여 전체적인 레이아웃의 일관성을 가져가는 편이 좋다고 봅니다.

 

vw 과 vh 의 차이

보통 height 과 width 의 최대 넓이를 고정할 때 사용하는 상대적인 단위인데 여기서 v 는 viewport 의 약자입니다. 즉, 뷰포트의 상대적인 width와 height에 따라 값이 결정되는데, 1vw 는 뷰포트 width가 100% 라면 그 중 1%에 해당하는 값이 되며, 1vh 도 뷰포트의 height가 100% 일 때 그 중 1%의 비율을 나타냅니다. 이러한 특징으로 인해 100vw 를 하게 되면 브라우저의 전체 가로 길이를 가득 채우는 형태로 요소가 배치되는 것 이죠.

 

이 외에도 앞서 단위를 포함해서 다양한 단위가 있습니다. 글로만 보기에는 제한이 따르기 때문에, 스니펫 형태로 준비해 보았습니다.

/* 절대적 단위 */
.absolute-example {
  width: 1in; /* 1 인치 */
  height: 2.54cm; /* 2.54 센티미터 */
  margin: 10mm; /* 10 밀리미터 */
  font-size: 12pt; /* 12 포인트 */
}

/* 상대적 단위 */
.relative-example {
  width: 50%; /* 부모 요소 너비의 50% */
  height: 2em; /* 부모 요소 폰트 크기의 2배 */
  padding: 1rem; /* 루트 요소 폰트 크기의 1배 */
  margin: 10vw; /* 뷰포트 너비의 10% */
  font-size: 3vh; /* 뷰포트 높이의 3% */
  line-height: 1.5lh; /* 요소의 줄 높이의 1.5배 */
}

 

반응형 브레이크 포인트

보통 반응형 레이아웃을 구성할 때, 다양한 장치 크기에 맞춘 레이아웃을 지정하기 위해 브레이크 포인트를 지정하게 됩니다.  일반적으로 다음과 같은 기준에 따라서 선택하죠.

 

320px: 작은 모바일 장치

480px: 작은 모바일 장치(세로)

768px: 태블릿

1024px: 작은 데스크탑 및 큰 태블릿(가로)

1200px: 데스크탑

1440px: 큰 데스크탑

 

CSS 선택자 우선순위

CSS 는 상속 구조로 부모의 프로퍼티를 자식에게 상속하는 방식으로 구현이 되는데요.  보통 다음과 같이 우선순위가 계산 됩니다.

 

인라인 스타일 : <div style ='color:red'/> (우선순위: 1000)

-> ID 선택자 : #id (우선순위: 100)

-> 클래스 선택자 = 속성 선택자 = 가상 클래스 : .container, [type="text"], :hover (우선순위: 10)

-> 요소 선택자  및 가상 요소: div, p, ::before, ::after 등 (우선순위: 1)

-> 전체 선택자: * { color:black;  }

 

페이지가 변해도 항상 같은 비율을 유지하는 요소를 만드려면

페이지가 변해도 항상 같은 비율을 유지하게 만드려면 padding-top 혹은 padding-bottom 을 이용하거나 최근에 자주 사용되는 aspect-ratio 에 직접 종횡비를 지정해주면 됩니다.

 

해당 ratio 속성이 없을 때는 padding-top 을 사용하였는데, 아래와 같이 사용할 수 있습니다. aspect-ratio 를 적용하는 컨테이너 요소에 padding-top 을 지정하여 종횡비를 계산하고, 그 자식 요소가 부모 영역의 전체를 가득차도록 해서 비율을 유지하게 만들 수 있습니다. 

.aspect-ratio-16-9 {
  position: relative;
  width: 100%;
  padding-top: 56.25%; /* 9 / 16 * 100 */
}

.aspect-ratio-16-9 > * {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

 

앞서 복잡한 계산 보다는 아래와 같이 aspect-ratio 속성을 추가해주기만 해도 깔끔하게 유지할 수 있겠습니다.

.container {
  width: 100%;
  aspect-ratio: 16 / 9; /* 16:9 비율 */
  background-color: lightblue;
}

 

해당 속성의 경우에는 이전에 봤을 때만 해도 일부 브라우저에서 지원을 안 해준 것으로 아는데, 이제는 모든 브라우저에서 지원을 해주네요. 

https://developer.mozilla.org/en-US/docs/Web/CSS/aspect-ratio

 

 

Flex 박스 | 1차원 레이아웃 모델로서, 요소들을 행이나 열 형태로 배치 시 사용

1차원 레이아웃 모델로, 아이템의 정렬과 배치를 쉽게 만들어 줍니다. 보통 요소들을 행이나 열 형태로 배치할 때 사용하고, 주로 다음과 같은 속성들을 지니고 있습니다.

컨테이너 속성

- display: flex 또는 inline-flex

- flex-direction: row 혹은 column

- flex-wrap: 자식 요소의 줄 바꿈 설정

- justify-content: 주 축 정렬(flex-start 혹은 end, center, space-between 등이 있음

- align-items: 교차 축 정렬(stretch, center, flex-start 등)

- align-content: 여러 줄의 교차 축 정렬

아이템 속성

- order: 요소의 순서 변경

- flex-grow: 남은 공간을 어떻게 나눌지 설정

- flex-shrink: 공간이 부족할 때 요소가 어떻게 줄어들지 설정

- flex-basis: 요소의 기본 크기 설정

- align-self: 특정 요소의 교차 축 정렬

 

float 의 동작원리

요즘에는 flex 와 grid 를 사용해서 잘 사용되지 않지만, float는 그 전 까지만 해도 자주 사용되는 속성 중 하나였습니다. float는 요소를 왼쪽 또는 오른쪽으로 띄우고, 요소가 다른 텍스트나 인라인 요소들이 주위에 감싸도록 만드는 기능을 합니다.

 

float는 기본적으로 띄워진 요소 주위에 감싸는 요소들이 생기기 때문에, 약간의 레이아웃 왜곡이 발생할 수 있습니다. 따라서 해당 문제를 해결하기 위해 clear 속성을 별도로 적용해주는데, both 로 지정하면, left, right 값이 지정한 float 의 영향을 받은 다음 요소가 영향을 미치지 못하도록 방지해주는 역할을 합니다.

 

CSS 에서 Cacading

캐스캐이딩은 여러 CSS 규칙이 동일한 요소에 적용될 때, 어떤 규칙이 최종적으로 적용될지를 결정하는 메커니즘 입니다. 보통 다음 3 가지 주요 원칙을 따라 진행됩니다.

 

- 우선순위: 선택자의 우선순위에 따라 결정

- 출처: 브라우저 기본 스타일, 사용자 스타일 시트, 작성자 스타일 시트 순으로 우선순위가 적용됩니다.

- 명시도: 동일한 우선순위와 출처의 규칙 중 나중에 선언된 규칙이 우선합니다.

 

SCSS

Sassy CSS 라고 불리는 SCSS 는 SASS 의 확장된 문법 입니다.  CSS 와 유사한 구문을 사용하는 CSS 전처리 프레임워크로서 일반적으로 다음과 같은 기능을 제공합니다.

 

- 변수: 스타일 속성에 사용할 값을 변수로 저장

- 중첩: CSS 선택자를 중첩하여 구조적으로 작성(CSS 에서도 이제 지원해줍니다.)

- 파셜: 여러 파일로 코드를 분할하고, @import 를 통해서 불러올 수 있습니다.

- 믹스인: 재사용 가능한 코드를 분리하야 @incloud 를 통해 사용할 수 있습니다.

- 확장: 기존 클래스의 스타일을 상속합니다.

- 함수: 복잡한 계산을 수행하고 값을 반환할 수 있습니다.

 

아래는 위 개념을 일부 적용한 간단한 예시 입니다. 한 번 참고해보세요.

$primary-color: #333; // 변수 선언

body {
  font-family: Arial, sans-serif;
  color: $primary-color; // 변수 사용

  .container { // 중첩 사용
    margin: 0 auto;
    padding: 1rem;
    width: 100%;
  }
}

@mixin box-shadow($shadow) { // 재사용 가능한 코드 블록 생성
  -webkit-box-shadow: $shadow;
  -moz-box-shadow: $shadow;
  box-shadow: $shadow;
}

.box {
  @include box-shadow(0 4px 8px rgba(0, 0, 0, 0.1)); // 코드 블록 사용
}

 

position 속성에 대한 정리

position 속성은 요소의 배치 방법을 지정하는데 사용 합니다. 기본적으로 아래와 같은 값을 가질 수 있습니다.

 

- static: 기본값으로 요소가 문서 흐름에 따라 배치됩니다.

- relative: 요소를 원래 위치를 기준으로 이동 합니다. 즉, 위 아래로 이동 시킬 수는 없으나 좌우로는 left와 right 를 통해 이동할 수 있습니다.

- absolute: 요소를 가장 가까운 조상 요소를 기준으로 이동 합니다. 만일 조상 요소가 relative, absolute, fixed 가 지정된 경우에 해당 조상 요소를 기준으로 배치됩니다.

- fixed: 뷰포트를 기준으로 고정이 되며, 스크롤 해도 그 자리에 고정됩니다.

- sticky: 스크롤 위치에 따라 'relative', 'fixed' 를 전환 합니다. 

 

margin 과 padding

margin 은 요소의 경계 바깥에 있는 공간을 지정할 때 사용합니다. padding 요소의 경계 안쪽에 있는 공간을 지정합니다. 

 

 

HTML 렌더링 시 자바스크립트가 실행되면 렌더링이 멈추는 이유

자바스크립트 코드는DOM 을 조작하고 스타일을 변경할 수 있기 때문에, 브라우저는 불필요한 레이아웃 재계산을 피하기 위해 자바스크립트를 먼저 실행하고, 렌더링을 이어 갑니다. 이 과정에서 렌더링이 잠시 중단될 수 있습니다.

 

CSS 박스 모델

앞서 margin 과 padding 부분에서 첨부한 박스가 CSS 박스 모델입니다. margin, border, padding, content 로  구성되고, 보통 요소의 가로 넓이를 계산할 때 앞서 속성에 지정된 px 값을 모두 포함시킵니다. 즉, 실제로 width:100px 으로 지정했음에도 margin, padding, border 에 지정된 px 크기 만큼 100px+ 알파 가 됩니다.

 

만일 width:100px 로 지정 시 요소의 크기가 100px 에 딱 맞추고자 한다면, 해당 요소에 box-sizing:border-box 로 지정해주면 됩니다. 이렇게 되면 콘텐츠의 width 를 계산 시 100px 에 맞춰 그 공간을 나눠 가지게 됩니다.

 

Attribute 와 Property 의 차이

Attribute 는 HTML 태그 내에서 지정하는 값입니다. 문자열 형태로 저장이 되는데, <input type="text"/> 에서 type 이 Attribute 입니다. 

 

Property 는 DOM 객체의 속성입니다. 자바스크립트로 접근할 수 있습니다. 예를 들어 <input value="감자"/> 라는 HTML 태그가 있다고 가정해봅시다. 여기서 HTML 태그 내에 지정된 value 은 Attribute 이지만 자바스크립트를 통해서 input.value 으로 접근 시의 value은 Property 가 됩니다.  점 표기법을 통해 접근한 속성들은 모두 프로퍼티라고 이해하면 쉽습니다.

<input id="myInput" type="text" value="Hello">
<script>
  const input = document.getElementById('myInput');
  console.log(input.getAttribute('value')); // "Hello" (attribute)
  console.log(input.value); // "Hello" (property)
</script>

 

display 속성에 대한 설명

display 는 요소의 박스를 생성하는 방식을 지정합니다. 보통 다음과 같은 속성들을 지니고 있습니다. 그 중에서 grid, flex, block, inline-block 이 자주 사용되지요.

 

- block: 요소를 블록 레벨 요소로 지정하고, 전체 넓이를 차지하도록 합니다.

- inline: 요소를 인라인 레벨 요소로 지정하고, 콘텐츠 넓이 만큼만 너비를 차지합니다.

-inline-block: 인라인 레벨 요소 처럼 콘텐츠 넓이를 차지하지만, block 속성으로 지정된 요소가 가질 수 있는 모든 속성을 가질 수 있습니다.

- flex: 요소를 플렉스 컨테이너로 지정합니다. 

- grid: 요소를 그리드 컨테이너로 지정합니다.

- inline-flex 과 inline-grid:  기존 flex 와 grid 는 블랙 레벨 요소 처럼 동작하지만, 이들은 인라인 레벨 요소처럼 컨테이너를 지정합니다.

- table: 테이블 레이아웃을 지정합니다. 거의 사용되지 않습니다.

 

CSS 애니메이션과 JS 애니메이션

CSS 애니메이션은 @keyframems 와 애니메이션 속성을 사용하여 정의합니다. 간단한 애니메이션에 적합했으나, 최근에는 복잡한 애니메이션도 생성이 가능하도록 영역이 확장되는 중입니다. 브라우저의 하드웨어 가속을 사용하기 때문에 성능이 우수한 편입니다.

 

JS 애니메이션은 자바스크립트를 사용하여 애니메이션을 조작하므로 CSS 만을 사용한 것 보다 정교하고 복잡한 애니메이션을 만들 수 있습니다. 보통 .animate 메소드를 사용하여 첫 번째 인자에는 키프레임을 두 번째 인자에는 애니메이션 실행에 필요한 옵션을 지정하여 간단펴하게 사용할 수 있습니다. 

document.getElementById("alice").animate(
  [
    { transform: "rotate(0) translate3D(-50%, -50%, 0)", color: "#000" },
    { color: "#431236", offset: 0.3 },
    { transform: "rotate(360deg) translate3D(-50%, -50%, 0)", color: "#000" },
  ],
  {
    duration: 3000,
    iterations: Infinity,
  },
);

 

 

CSS in JS 의 장점과 단점

JS 와 함꼐 CSS 를 작성하여 관리할 수 있으므로 컴포넌트 기반 스타일링을 통한 코드베이스의 일관성과 글로벌 네임 스페이스 오염을 방지하는  장점이 있습니다. JS 를 사용해서 동적으로 스타일을 변경할 수 있으므로 보다 유연한 코드를 작성할 수도 있습니다.

 

하지만, 런타임 시 스타일 생성이 이루어지므로 이로 인한 성능 저하가 발생할 수 있습니다 .CSS 와 JS 가 결합한 형태이므로 코드가 길어질 경우 코드 복잡성이 커지고, 오히려 코드와 CSS 를 구분하기 어려워 코드 가독성을 저해할 수 있습니다.

 

 

시멘틱 마크업 | HTML 요소를 그 의미에 맞게 사용하는 것

HTML 요소가 그 의미에 맞게 사용되는 것을 의미합니다. 시멘틱 마크업에 따라 요소를 배치하면 크롤링 봇이 사이트의 구조를 파악하는 데 도움을 주므로 SEO 에도 도움이 되됩니다. 명확한 의미에 따라 구조가 설계되면, 코드의 가독성이 높아질 수 있습니다. 또한 스크린 리더기를 사용하는 사용자에게 보다 명확한 구조 설명이 가능하여 정보 접근성을 향상시킬 수 있습니다. 

 

- header: 문서나 섹션의 머리말

- nav: 네비게이션 링크

- main: 문서의 주요 콘텐츠

- section: 콘텐츠의 주제별 섹션

- article: 독립적으로 구분된 콘텐츠

- aside: 부가적인 콘텐츠

- footer: 문서나 섹션의 바닥글

 

 

offsetWidth 와 offsetHeight | margin 을 제외하고 border 을 포함한 요소의 높이와 가로 길이를 알고 싶을 때

https://developer.mozilla.org/ko/docs/Web/API/CSS_Object_Model/Determining_the_dimensions_of_elements

 

 

clientWidth 와 clientHeight | margin 과 border 을 제외한 padding 까지만 포함한 요소의 높이와 가로 넓이를 알고 싶을  때

https://developer.mozilla.org/ko/docs/Web/API/CSS_Object_Model/Determining_the_dimensions_of_elements

 

scrollWidth 와 scrollHeight | 보여지는 크기가 아닌 실제 요소의 높이와 가로 길이를 알고 싶을 때.

해당 속성들은 실제 요소가 브라우저에서 차지는 픽셀 사이즈를 측정하여 반환해줍니다. 예를 들어, 600x400(px) 이 되는 요소가 있다고 할 때, 사용자는 화면상에서 300x300(px) 을 보고 있습니다. 즉, 요소 전체를 보려면 스크롤해야 볼 수 있게 되는데, scrollWidth와 scrollHeight 는 사용자에게 현재 보여지는 요소의 크기가 아니라, 실제 해당 요소의 전체 크기를 측정하여 반환해줍니다.

 

CSSOM(CSS 객체모델) | JS 에서 CSS를 조작할 수 있도록 해주는 API 집합

CSS Object Model 은 JavaScript에서 CSS를 조작할 수 있는 API 집합입니다. HTML 대신 CSS가 대상인 DOM이라고 생각할 수 있으며, 사용자가 CSS 스타일을 동적으로 읽고 수정할 수 있는 방법입니다.

 

블록 레벨 콘텐츠(요소) | 블록 레이아웃(각 행 마다 세로로 공간을 차지하는 레이아웃)을 구성하는 요소

블록 레벨 요소는 일반적으로 새로운 줄에서 시작되며, 기본적으로 가로 전체의 너비를 차지합니다. 블록 레벨 요소는 다른 블록 레벨 요소 또는 인라인 요소를 포함할 수 있습니다.

일반적으로 사용되는 블록 레벨 요소들은 다음과 같습니다

<div>: 가장 일반적인 블록 레벨 요소로, 별도의 의미를 가지지 않고 구획을 나누거나 스타일을 적용하기 위해 사용됩니다.

<p>: 단락을 나타내는 요소로, 텍스트 블록을 구성합니다.

제목 요소(<h1>부터 <h6>): 문서의 제목을 나타내는 요소로, 중요도에 따라 제목의 수준을 지정합니다.
목록 요소(<ul>, <ol>, <dl>): 항목들을 나열하는 목록을 만들기 위해 사용됩니다. <ul>은 순서 없는 목록, <ol>은 순서 있는 목록, <dl>은 설명 목록을 나타냅니다.

<blockquote>: 인용문을 나타내는 요소로, 텍스트를 들여쓰기하여 인용 부분을 구분합니다.


<pre>: 사전 서식화된 텍스트를 나타내는 요소로, 텍스트를 그대로 표시하며 공백과 줄 바꿈을 유지합니다.


이외에도 무수히 많은 블록 레벨 요소들이 있지만, 글이 길어질 것 같아서 여기서 마무리 합니다.

 

script 태그에서 async 와 defer  의 차이

두 속성 다 비동기적으로 스크립트를 다운로드 하는 것은 동일하지만 실행하는 타이밍에는 차이가 존재합니다.

 

async 는 스크립트를 비동기적으로 로드하고 실행합니다. HTML 파싱과 스크립트 실행이 병렬로 이루어지며, 스크립트 로드가 완료되는 즉시 실행 됩니다(HTML 파싱 이전 실행 가능).

 

defer 는 스크립트를 비동기적으로 로드하지만, HTML 파싱이 완료된 후 스크립트를 실행합니다. 따라서 스크립트가 HTML 파싱을 방해하지 않고, 실행 순서가 HTML 문서에 나타나는 순서를 따릅니다(HTML 파싱 후 순차 실행).

 

가상 클래스

CSS 가상 클래스는 특정 상태에 따라 요소에 스타일을 적용하는 데 사용 됩니다. 주요 가상 클래스를 나열해보면 아래와 같습니다.

 

- :hover: 요소에 마우스 포인터가 올라갔을 때,

- :active: 요소가 활성화 되었을 때(클릭 시)

- :focus: 요소가 포커스를 받았을 때,

- :visited: 방문한 링크

- :nth-child(n) : 특정 자식 요소

 margin 병합

마진 병합은 상하 인접한 블록 요소의 마진이 합쳐지는 현상을 의미합니다. 두 요소의 마진이 만나면, 두 마진 중 큰 값이 적용됩니다. 아래의 경우 p 태그의 30px 를 기준으로 div 와 p  태그 사이에 여백이 생성됩니다.

div {
  margin: 20px 0;
}

p {
  margin: 30px 0;
}

 

 

자바스크립트 관련 개념

이터러블 | 반복 가능한 객체로서  [Symbol.Iterator] 를 필수적으로 가져야 함.

이터러블은 반복 가능한 객체를 의미합니다. 즉, 이터러블 객체는 내부의 요소를 하나씩 순회할 수 있습니다. 이터러블 객체의 예로는 배열, 문자열, Map, Set 등이 있습니다. 

이터러블 객체는 Symbol.iterator 메서드를 가지고 있어야 합니다. 이 메서드는 이터레이터를 반환 합니다.

const array = [1, 2, 3];
const iterator = array[Symbol.iterator]();

console.log(typeof iterator);  // "object"

 

이렇듯 이터러블은 배열, 문자열, Map, Set 같은 객체들이 순회 가능하게 해주는 객체로서 이터러블이 정의되어 있지 않은 숫자, 불린과 같은 원시 데이터의 경우에는 순회가 불가능 하다는 사실도 알 수 있습니다.

 

이터레이터(Iterator) | [Symbol.Iterator]  를 호출하면, 반환하는 next 메서드를 가진 객체

[Symbol.Iterator]  를 호출하면, 반환하는 이터레이터(Iterator)는 next 메서드를 가지고 있는 객체입니다. next 메서드는 호출될 때마다 이터러블 객체의 다음 값을 반환합니다. 반환되는 값은 { value: 값, done: 불리언 } 형태의 객체입니다. 여기서 done이 true가 될 때까지 next 메서드를 계속 호출할 수 있습니다.

const array = [1, 2, 3];
const iterator = array[Symbol.iterator]();

console.log(iterator.next());  // { value: 1, done: false }
console.log(iterator.next());  // { value: 2, done: false }
console.log(iterator.next());  // { value: 3, done: false }
console.log(iterator.next());  // { value: undefined, done: true }

 

아래는  while 문을 사용하여 done 이 true 가 될 때 까지 반복한 예시 입니다.

const iterable = [10, 20, 30];
const iterator = iterable[Symbol.iterator]();

let result = iterator.next();
while (!result.done) {
    console.log(result.value);  // 10, 20, 30
    result = iterator.next();
}


또한 for...of 를 통해서도 이터러블을 쉽게 순회할 수 있습니다. 

const iterable = [10, 20, 30];

for (const value of iterable) {
    console.log(value);  // 10, 20, 30
}

 

고차함수 | 다른 함수를 인자로 받거나 함수를 반환하는 함수

고차 함수는 다음 두 가지 중 하나 이상을 수행하는 함수입니다. 

함수를 인자로 받는다

고차 함수는 하나 이상의 함수를 인자로 받을 수 있습니다.

function repeat(n, action) {
    for (let i = 0; i < n; i++) {
        action(i);
    }
}

repeat(3, console.log);
// 출력: 0
// 출력: 1
// 출력: 2


함수를 반환한다

고차 함수는 함수를 반환할 수 있습니다.

function createMultiplier(multiplier) {
    return function(x) {
        return x * multiplier;
    };
}

const double = createMultiplier(2);
console.log(double(5));  // 10

 

일급 객체와 일급 함수

자바스크립트에서 함수는 일급 객체의 모든 특징을 가지고 있습니다(애초에 함수는 객체 입니다). 여기서 정리하는 것은 일급 객체(또는 일급함수)의 특징 별로 어떻게 자바스크립트에서 이것이 적용되는지 정리한 것입니다.

 

변수에 할당할 수 있다

객체(또는 함수)를 변수에 할당할 수 있습니다.

const a = 42;
const func = function() { return "Hello"; };

 

함수의 인자로 전달할 수있다

객체(또는 함수)를 함수의 인자로 전달할 수 있습니다.

function greet(callback) {
    console.log(callback());
}
greet(function() { return "Hello"; });


함수의 반환값으로 사용할 수 있다

객체(또는 함수)를 함수의 반환값으로 사용할 수 있습니다. 

javascript
코드 복사
function getGreeter() {
    return function() { return "Hello"; };
}
const greeter = getGreeter();
console.log(greeter());  // "Hello"

 

자료구조에 저장할 수 있다

객체(또는 함수)를 배열이나 객체와 같은 자료구조에 저장할 수 있습니다.

const functions = [function() { return "Hello"; }, function() { return "World"; }];
console.log(functions[0]());  // "Hello"

 

Selectionchange 이벤트 | 사용자가 텍스트를 선택하는 순간 발생하는 이벤트

이는 사용자가 텍스트를 선택하는 순간 발생하는 이벤트을 나타냅니다. 이 때 document 객체의 메소드인 getSelection() 을 호출하게 되면 사용자가 선택하여 드래그한 텍스트를 반환하게 됩니다.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Selection Example</title>
</head>
<body>
  <p>이 텍스트를 선택해 보세요.</p>

  <script>
    document.addEventListener("selectionchange", () => {
      const selection = document.getSelection();
      console.log("선택된 텍스트:", selection.toString());
    });
  </script>
</body>
</html>

 

이대로는 이해가 쉽지 않기 때문에, 실제 체험할 수 있도록 에디터를 가져와 봤습니다. 한 번 실습해보세요.

 

See the Pen Untitled by youngwan2 (@youngwan2) on CodePen.

 

 

this바인딩 | 함수가 호출되는 방식에 따라서 this 에 묶이는(바인딩 되는) 객체가 달라지진다.

함수선언문(function (){ }) 에서 this |  함수가 호출된 시점에 함수를 감싸고 있는 컨텍스트를 가리킴

일반적으로 다음과 같은 함수선언문에서 this 는 전역 객체인 Window 객체에 바인딩 됩니다. 이는 현재 func 함수를 감싸고 있는 컨텍스트가 전역 실행 컨텍스트 이기 때문입니다. 참고로 'use strict'를 함수의 유효범위 최상단에 입력하시면, this 는 undefined 가 됩니다.

function func(){
  console.log(this) // 전역 객체인 Window 가 this 에 묶임
}

func()

 

이는 외부 함수 내에 내부 함수를 또 다시 선언하여 호출하는 아래와 같은 경우도 동일합니다.

function func(){
  console.log(this) // 전역 객체인 Window 가 this 에 묶임 1
  
  	return function innerFunc(){
    	console.log(this) // 전역 객체인 Window 가 this 에 묶임 2
    }
}

func()()

 

심지어 객체 리터럴 내에서 생성된 메소드 내에 정의된 함수선언문(내부함수)의 경우에도 동일하게 WIndow 객체를 가리킵니다. 여기서 객체 내부 메소드에서 호출된 함수가 obj 가 아닌 window 를 가리키는 동작이 발생하는 이유는 해당 내부 함수가 호출되는 시점이 return 을 통해 반환된 독립된 환경 즉, 전역 환경에서 호출되기 때문입니다.

const obj = {
    func:function(){
        console.log(this) // 변수 obj 객체를 가리킴
         return function inner(){
             console.log(this) // window 객체
         }
    }
    
}

obj.func()()

 

객체 리터럴 내에서 함수선언문의 this 

앞서 작성한 코드를 다시 가져왔습니다. 객체 내에서는 메소드로 호출되는 함수는 this 가 obj 에 묶이게 됩니다. func() 함수를 호출하는 시점에 함수를 감싸고 있는 컨텍스트는 obj 이므로 해당 obj를 this 가 가리키고 있습니다.

const obj = {
    func:function(){
        console.log(this) // 변수 obj 객체를 가리킴
    }
}

obj.func()

 

생성자 함수 내에서의 this |  초기에는 빈 객체를 가리키고, 생성자 함수 호출 시 새로 생성된 인스턴스를 가리킴

생성자 함수 내에서의 this는 새로 생성된 인스턴스를 가리키게 됩니다. 이 또한 앞서 객체 리터럴과 유사한 이유로 생성자 함수가 호출되는 시점에 해당 함수를 감싸고 있는 컨텍스트가 새로 생성된 인스턴스가 되기 때문입니다.

 

예를 들어 다음 코드를 살펴봅시다. 여기서 this 는 { } 빈 객체를 가리키고 있기 때문에, this.name = name 은 사실상 빈 객체에 점 표기법(.) 을 이용하여 값을 추가하는 것과 같게 됩니다.

function Person(name, age) {
    // 1. 새로운 빈 객체가 생성됨
    // var this = { }  --> 사용자에게 안 보임
    
    // 2. this는 새로 생성된 객체를 가리킴
    this.name = name;
    this.age = age;
    // 3. 생성자 함수의 prototype이 새 객체의 [[Prototype]]으로 설정됨
    // 4. 함수 코드가 실행됨
    // 5. 새 객체가 반환됨 (명시적 반환이 없을 경우)
    // return this  --> 사용자에게 안 보임
}

const alice = new Person('Alice', 30);
console.log(alice.name); // Alice
console.log(alice.age);  // 30

 

화살표 함수( ( )=>{ }) 에서의 this |  상위 렉시컬 스코프의 this 를 상속 받음

화살표 함수는 function(){ } 과는 다르게 this 를 상위 렉시컬 스코프로 부터 상속 받아서 사용합니다. 

 

예를 들어, 다음 함수가 있다고 가정해봅시다. 여기서 serInterval 내의 콜백함수는 화살표 함수 입니다. 

function Person() {
  this.age = 0;

  setInterval(() => {
    this.age++; // 이 경우의 `this`는 `Person` 객체를 가리킵니다.
    console.log(this.age);
  }, 1000);
}

const p = new Person();

 

화살표 함수의 경우 앞서 언급했듯이 상위 렉시컬 스코프의 this 를 상속 받기 때문에, 해당 콜백함수의 상위 스코프인 Person 생성자 함수 내에서 { } 빈객체를 가리키는 this 를 상속 받습니다. 따라서 this.age 는 사실상 this = {age :0} 에 접근하는 것과 같습니다.

 

 

렉시컬스코프 | 함수가 선언(정의)된 위치에 따라 해당 함수의 스코프와 상위 스코프를 결정하는 방식

렉시컬 스코프(lexical scope)는 변수 및 함수의 유효 범위를 결정하는 방식 중 하나입니다. 이는 코드를 작성할 때 변수 및 함수가 어디에서 참조될 수 있는지를 결정합니다.

 

렉시컬 스코프의 원리는 다음과 같습니다

 

정적 스코프 (Static Scope) 

렉시컬 스코프는 함수가 선언된 위치에 따라 해당 함수의 스코프가 결정됩니다. 이를 정적 스코프라고도 합니다. 함수가 선언된 위치에서부터 함수가 중첩된 범위까지 스코프가 유지됩니다.

 

함수의 중첩 (Function Nesting)

함수가 다른 함수 내에 중첩되어 있는 경우, 내부 함수는 외부 함수의 변수에 접근할 수 있습니다. 이때 내부 함수의 스코프는 외부 함수의 스코프와 중첩된 관계를 가지며, 이를 렉시컬 스코프 체인(lexical scope chain)이라고 합니다.


렉시컬 스코프는 코드를 읽을 때 함수가 어디에 정의되었는지를 보고 해당 함수의 스코프를 결정합니다. 이것은 코드의 구조를 보고 변수나 함수의 스코프를 추론할 수 있다는 장점을 제공합니다. 따라서 코드의 동적인 실행 순서에 의해 스코프가 변경되지 않습니다.

간단한 JavaScript 예시로 렉시컬 스코프를 설명하겠습니다:

function outerFunction() {
    let outerVariable = 'Outer';

    function innerFunction() {
        console.log(outerVariable); // 내부 함수에서 외부 변수에 접근
    }

    innerFunction();
}

outerFunction(); // "Outer"를 출력


이 예시에서 innerFunction은 외부 함수 outerFunction 내에서 정의되었으며, 내부에서 outerVariable에 접근합니다. 렉시컬 스코프의 원리에 따라 innerFunction은 outerVariable에 접근할 수 있습니다.

클로저 |  함수가 속한 렉시컬스코프를 기억하여 함수가 렉시컬 스코프 밖에서 실행될 때도 그 스코프에 접근할 수 있게 하는 기능 

클로저(Closure)는 프로그래밍에서 중요한 개념 중 하나로, 함수와 그 함수가 만들어진 환경(lexical environment) 사이의 관계를 나타냅니다. 클로저는 함수가 다른 함수 내부에서 정의되고 반환되는 경우에 자주 발생합니다.

클로저는 세 가지 주요 구성 요소로 이루어집니다

함수 (Function)

클로저를 만드는 주체입니다. 함수 내부에서 다른 함수를 정의하고 반환할 수 있습니다.

 

외부 함수의 변수 (Variables in the Outer Function)

클로저에 의해 포착되는 외부 함수 내의 변수입니다. 내부 함수가 외부 함수의 변수를 사용하거나 변경할 수 있습니다.

 

함수와 외부 변수 간의 관계 (Relationship between the Function and Outer Variables)

내부 함수가 반환되고 외부 변수를 포착하여 그 값을 유지하는 메커니즘입니다. 이 관계는 클로저의 핵심이며, 내부 함수는 외부 변수에 대한 참조를 유지하고, 해당 변수는 함수가 반환되더라도 계속해서 사용될 수 있습니다.


클로저는 다양한 상황에서 유용하게 활용될 수 있습니다. 예를 들어, 클로저를 사용하여 함수 팩토리를 구현할 수 있으며, 이를 통해 매개변수화된 함수를 생성할 수 있습니다. 또한 비동기 작업에서 클로저를 사용하여 상태를 유지하고 콜백 함수에 전달할 수 있습니다.

간단한 JavaScript 예시로 클로저를 살펴보겠습니다

function outerFunction() {
    let outerVariable = 'I am outer';

    function innerFunction() {
        console.log(outerVariable); // 외부 변수에 접근
    }

    return innerFunction; // 내부 함수를 반환
}

const closure = outerFunction(); // 내부 함수가 반환되고, 외부 변수가 포착됨
closure(); // "I am outer"를 출력


이 예시에서 innerFunction은 외부 함수 outerFunction 내에서 정의되었고, outerVariable을 참조합니다. 그러나 outerFunction이 실행을 마쳤더라도 innerFunction은 여전히 outerVariable에 접근할 수 있습니다. 이것이 바로 클로저의 동작 원리입니다.

 

브라우저 렌더링 원리

문서 파싱(Parsing)

브라우저는 웹페이지의 HTML 및 CSS를 읽어들입니다. 이 과정에서 HTML 문서를 파싱하여 DOM(Document Object Model) 트리를 만들고, CSS를 파싱하여 CSS 객체 모델(CSS Object Model)을 생성합니다.

 

렌더 트리 구축(Render Tree Construction) | DOM + CSSOM 트리를 결합한 화면에 표시될 요소의 구조 생성

DOM 트리와 CSS 객체 모델을 결합하여 렌더 트리를 구축합니다. 렌더 트리는 화면에 표시될 요소들의 구조를 나타내며, 각 요소의 스타일 정보도 포함합니다.

 

레이아웃(Layout) | 렌더트리 기반의 각 요소의 위치와 크기를 계산 후 브라우저 내 정확한 배치에 사용

렌더 트리를 기반으로 각 요소의 위치와 크기를 계산하여 브라우저 창 내에서의 정확한 배치를 결정합니다. 이 단계에서는 요소의 크기, 위치, 여백 등의 정보를 결정합니다.

 

페인팅(Painting) | 레이아웃 과정을 통해 결정된 위치에 각 노드에 대한 실제 픽셀을 그리는 작업 수행

레이아웃 단계에서 계산된 위치와 크기에 따라 렌더 트리의 각 노드에 대해 실제로 화면에 픽셀을 그립니다. 이 단계에서는 픽셀 단위로 요소의 내용, 색상, 텍스트 등을 브라우저 화면에 그립니다.

 

리액트 관련 개념

리액트에서 배열에 Key 속성을 변하지 않는 고유한 식별자로 지정하는 것을 권장하는 이유

리액트에서 key prop의 순서가 중요한 이유는 주로 성능 최적화와 안정적인 상태 관리를 위해서입니다. 리액트는 각 컴포넌트의 업데이트를 효율적으로 처리하기 위해 key prop을 사용하여 리스트 아이템을 식별합니다. 이는 특히 동적인 리스트를 렌더링할 때 중요합니다. 다음은 key prop의 순서가 중요한 이유를 자세히 설명합니다:


 효율적인 재조정 (Reconciliation)

리액트는 가상 DOM(Virtual DOM)을 사용하여 실제 DOM과의 차이를 비교하고 최소한의 변경만 실제 DOM에 적용합니다. 이를 효율적으로 수행하기 위해 key를 사용하여 각 리스트 아이템을 고유하게 식별합니다. key는 리스트의 각 요소를 구분하고, 요소의 순서와 일치하도록 유지됩니다.

변경 감지: key를 사용하여 리액트는 요소가 추가되었는지, 제거되었는지, 아니면 순서가 바뀌었는지를 정확하게 감지할 수 있습니다. 만약 key가 없다면, 리액트는 매 렌더링마다 모든 요소를 다시 그리게 되어 비효율적입니다.
최소한의 업데이트: key를 통해 리액트는 변경된 요소만 업데이트합니다. 이는 성능을 최적화하고, 불필요한 DOM 조작을 최소화합니다.

 

안정적인 상태 유지

컴포넌트는 상태(state)를 가질 수 있으며, 리스트 아이템의 상태는 key에 의해 추적됩니다. key가 변경되면 리액트는 해당 컴포넌트를 새롭게 생성된 것으로 간주합니다. 이는 상태가 초기화되는 결과를 초래합니다.

일관된 상태: key가 일정하면, 리액트는 각 리스트 아이템의 상태를 올바르게 유지합니다. key가 일관되지 않으면 상태가 예상치 못하게 초기화될 수 있습니다.


정확한 업데이트: 동일한 key를 가진 요소는 상태와 props가 그대로 유지되며, 위치만 변경됩니다. 이는 리스트 아이템의 상태를 안정적으로 관리할 수 있게 합니다.


안정된 상태를 유지하기 위한 key는 다음과 같이 리스트 내에서 각 아이템을 고유하게 식별할 수 있는 값이어야 합니다. 일반적으로 데이터베이스의 고유 ID를 사용합니다.

// 올바른 사용법:  key prop이 있는 경우
function ListWithKey({ items }) {
  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>{item.text}</li>
      ))}
    </ul>
  );
}

 

리액트의 JSX 요소가 DOM 트리의 요소로 변환하는 과정

리액트의 JSX 요소가 실제 DOM 트리의 요소로 변환되는 과정은 몇 가지 주요 단계로 이루어집니다. 이 과정을 이해하면 리액트가 어떻게 효율적으로 UI를 업데이트하는지 알 수 있습니다.

JSX 변환

JSX는 자바스크립트의 확장 문법으로, HTML과 유사하게 작성할 수 있습니다. 그러나 브라우저가 JSX를 직접 이해할 수 없기 때문에, 이를 자바스크립트로 변환하는 과정이 필요합니다. 바벨(Babel)과 같은 트랜스파일러가 이 역할을 수행합니다.

예를 들어, 다음과 같은 JSX 코드가 있다고 가정해봅시다:

const element = <h1 className="greeting">Hello, world!</h1>;


이 코드는 바벨에 의해 다음과 같은 자바스크립트 코드로 변환됩니다:

const element = React.createElement(
  'h1',
  { className: 'greeting' },
  'Hello, world!'
);

Virtual DOM 생성

리액트는 변환된 자바스크립트 코드를 사용하여 가상 DOM(Virtual DOM)을 생성합니다. 가상 DOM은 실제 DOM을 추상화한 자바스크립트 객체 트리로, UI의 상태를 메모리에 저장합니다.

위의 React.createElement 호출은 다음과 같은 가상 DOM 요소를 생성합니다.

const element = {
  type: 'h1',
  props: {
    className: 'greeting',
    children: 'Hello, world!'
  }
};

 

실제 DOM에 렌더링

가상 DOM이 생성되면, 리액트는 이를 실제 DOM에 렌더링합니다. 이 과정은 다음 단계를 포함합니다:

Initial Rendering: 초기 렌더링 시, 리액트는 가상 DOM 트리를 순회하며 실제 DOM 요소를 생성하고, 이를 브라우저의 DOM 트리에 추가합니다.

ReactDOM.render(element, document.getElementById('root'));


ReactDOM.render는 가상 DOM 트리를 실제 DOM 트리로 변환하고, 지정된 DOM 노드에 추가합니다. 이 과정에서 각 가상 DOM 요소는 실제 DOM 요소로 변환되어 브라우저에 나타납니다.

 

업데이트 과정

리액트의 주요 장점 중 하나는 효율적인 업데이트입니다. 컴포넌트의 상태나 props가 변경되면, 리액트는 새로운 가상 DOM 트리를 생성하고, 이전 가상 DOM 트리와 비교하여 변경된 부분만 실제 DOM에 반영합니다. 이 과정을 리콘실리에이션(reconciliation) 이라고 합니다.

- 새 가상 DOM 생성: 상태나 props의 변경으로 인해 새로운 가상 DOM 트리가 생성됩니다.
- 변경 사항 비교: 리액트는 새로운 가상 DOM 트리와 이전 가상 DOM 트리를 비교(diffing)하여 변경된 부분을 찾습니다.
- 실제 DOM 업데이트: 변경된 부분만 실제 DOM에 반영하여, 최소한의 연산으로 UI를 업데이트합니다.

 

예를 들어, 컴포넌트의 상태가 변경되어 className이 변경된다고 가정합시다.

this.setState({ className: 'farewell' });


리액트는 새로운 가상 DOM 트리를 생성하고, 이전 트리와 비교하여 className 속성이 변경되었음을 감지합니다. 그런 다음, 해당 DOM 노드의 className 속성만 업데이트합니다.

 

컴포넌트 언마운트 및 정리

컴포넌트가 더 이상 필요하지 않게 되면, 리액트는 이를 DOM 트리에서 제거하고, 관련된 이벤트 핸들러나 메모리 자원을 정리합니다. 이는 componentWillUnmount 생명주기 메서드 또는 useEffect 훅의 클린업 함수를 통해 수행됩니다.

 

요약

  1. JSX 변환: 바벨이 JSX를 React.createElement 호출로 변환.
  2. Virtual DOM 생성: 변환된 자바스크립트 코드를 기반으로 가상 DOM 트리 생성.
  3. 초기 렌더링: 가상 DOM 트리를 실제 DOM으로 변환하여 브라우저에 렌더링.
  4. 업데이트 과정: 상태나 props 변경 시 새로운 가상 DOM 생성, 이전 트리와 비교하여 실제 DOM 업데이트.
  5. 언마운트 및 정리: 필요 없는 컴포넌트를 DOM에서 제거하고 자원 정리.

리액트에서 변이가 허용되는 경우와 피해야 하는 경우

이 경우에는 로컬에서 변이가 이루어지기 때문에, 괜찮습니다. 즉, FriendList 컴포넌트가 살아있는 동안(렌더링 동안)에만 유효하기 때문에, 렌더링 마다 새롭게 생성되므로, 컴포넌트는 부수효과에 영향을 받지 않고, 동일한 출력을 반환하게 됩니다.

function FriendList({ friends }) {
  const items = []; // ✅ Good: locally created
  for (let i = 0; i < friends.length; i++) {
    const friend = friends[i];
    items.push(
      <Friend key={friend.id} friend={friend} />
    ); // ✅ Good: local mutation is okay
  }
  return <section>{items}</section>;
}

 

반면, 컴포넌트의 외부에 선언된 변수는 컴포넌트가 렌더링 되더라도 메모리에 남아 있기 때문에, 컴포넌트는 렌더링 된 이후에도 해당 변수를 참조하게 되고, 이는 결국 중복된 데이터가 쌓이게 되며 메모리 누수 문제로도 이어질 수 있습니다.

const items = []; // 🔴 Bad: created outside of the component
function FriendList({ friends }) {
  for (let i = 0; i < friends.length; i++) {
    const friend = friends[i];
    items.push(
      <Friend key={friend.id} friend={friend} />
    ); // 🔴 Bad: mutates a value created outside of render
  }
  return <section>{items}</section>;
}

 

만일 이렇게 작성하는 경우 문제점을 요약해면 다음과 같습니다(이 부분은 GPT4o 의 도움을 받았습니다).

상태 관리 문제
items 배열은 컴포넌트 외부에 정의되어 있어, 컴포넌트의 상태나 라이프사이클과 연관되지 않습니다. 이는 리액트의 상태 관리 원칙을 위반하며, 컴포넌트의 상태가 예측 불가능하게 됩니다.
여러 번 렌더링될 때마다 items 배열이 계속 누적되기 때문에, 메모리 누수 및 불필요한 리렌더링 문제를 일으킬 수 있습니다.
불변성 원칙 위반
리액트에서는 상태나 props를 직접 변이하지 않는 것이 원칙입니다. items 배열을 컴포넌트 외부에 정의하고 이를 변이시키는 것은 이 원칙을 위반합니다.
리렌더링 문제
리액트 컴포넌트는 상태나 props가 변경될 때 자동으로 리렌더링됩니다. 그러나 items 배열이 외부에서 관리되면, 리액트는 이 배열의 변경을 감지하지 못하고 필요할 때 제대로 리렌더링되지 않을 수 있습니다. 또한, items 배열이 한 번만 초기화되고 이후 계속해서 누적되기 때문에, 컴포넌트가 리렌더링될 때마다 동일한 항목이 중복되어 렌더링됩니다.

 

Fragment 

JSX 가 여러 개인 경우 이를 그룹화 활 때 사용되는 리액트의 특수한 타입의 컴포넌트 입니다. 보통 표기는 <Fragment/> 혹은 </> 형태로 작성하며, 이렇게 감싸진 컴포넌트는 실제 DOM 에 그려질 때는 DOM 트리에 반영되지 않습니다. 즉, 보이지는 않지만 JSX 가 두 개인 것을 하나의 자바스크립트 객체로 묶어주는 역할을 해주기 때문에, 불가피하게 두 개의 JSX 태그는 처리해야 하는 경우에 해당 컴포넌트를 감싸주는 방식으로 사용할 수 있습니다.

JSX 태그를 하나로 감싸줘야 하는 이유

JSX 는 언뜻보면 일반적인 HTML과 별차이가 없기 때문에, 간과할 수 있지만, 리액트의 컴포넌트는 함수입니다. 함수로서 반환(return) 되는 JSX 태그는 리액트 createElement 를 통해 하나의 자바스크립트 객체로 변환되는데, JSX 태그가 여러 개 병렬적으로 나열된 경우에는 이를 하나의 자바스크립트 객체로 반환할 수 없기 때문입니다. 즉, 내부 연산의 순서에 문제를 일으키게 됩니다.

코드분할 |  앱의 번들을 더 작은 청크(chunk)로 분할하여 필요할 때 (lazy) 로드 할 수 있는 기술

리액트는 코드분할을 다음과 같은 Lazy 함수와 Suspense 컴포넌트를 사용하여 구현할 수 있습니다.

const MyComponent = React.lazy(() => import('./MyComponent'));

function App() {
    return (
        <Suspense fallback={<div>Loading...</div>}>
            <MyComponent />
        </Suspense>
    );
}

 

State | 컴포넌트 내부에서 관리되는 데이터로서 컴포넌트 렌더링 유무에 관여하는 값

리액트에서의 "상태"는 컴포넌트 내부에서 관리되는 데이터를 의미합니다. 여기서 데이터는 컴포넌트의 렌더링 결과물에 영향을 주는 값으로, 컴포넌트가 변경될 때마다 변하게 됩니다.

리액트의 함수 컴포넌트에서는 useState 훅을 사용하여 상태를 관리합니다. 상태는 사용자 인터랙션, 외부 데이터 로딩, 시간에 따른 변화 등 여러 요인에 따라 변경될 수 있습니다.

상태는 컴포넌트의 렌더링 결과를 결정하는 주요 요소 중 하나이며, 상태가 변경될 때마다 리액트는 해당 컴포넌트를 다시 렌더링하여 업데이트된 상태를 반영합니다. 이를 통해 동적인 UI를 만들고 사용자와의 상호작용을 제어할 수 있습니다.

 

리액트에서 리렌더링을 유발하는 트리거

상태(State) 변경

useState 훅을 사용하여 관리되는 상태(state)가 변경될 때마다 함수 컴포넌트가 다시 호출되어 리렌더링됩니다.

const [count, setCount] = useState(0);

// 상태 변경 후
setCount(count + 1); // 리렌더링 유발

 

속성(Props) 변경

부모 컴포넌트로부터 전달받은 속성(props)이 변경될 때마다 함수 컴포넌트가 다시 호출되어 리렌더링됩니다.

function ParentComponent() {
  const [value, setValue] = useState(0);
  return <ChildComponent prop={value} />;
}

// 부모 컴포넌트에서 속성 변경 후
setValue(newValue); // ChildComponent 재렌더링 유발

 

useContext 훅을 통한 전역 상태 변경

useContext 훅을 사용하여 전역 상태가 변경될 때마다 함수 컴포넌트가 다시 호출되어 리렌더링됩니다.

const MyContext = React.createContext();

function MyComponent() {
  const value = useContext(MyContext);
  // 전역 상태 변경 후
  // MyComponent 재렌더링 유발
}

 

useEffect 훅의 의존성 배열

useEffect 훅에서 관리하는 상태나 props가 변경될 때마다 함수 컴포넌트가 다시 호출되어 리렌더링됩니다.

useEffect(() => {
  // 이펙트 코드
}, [dependency]); // dependency가 변경될 때마다 재렌더링 유발



리액트에서 리렌더링의 동작 원리

함수 컴포넌트에서의 리렌더링은 상태(state)나 속성(props)이 변경될 때 발생합니다. 이러한 변경이 발생하면 리액트는 해당 함수 컴포넌트를 다시 호출하여 반환된 JSX를 기반으로 화면을 업데이트합니다.

리렌더링의 동작 방식은 다음과 같습니다

상태(State) 또는 속성(Props)의 변경

함수 컴포넌트가 소유한 상태(state)나 부모 컴포넌트로부터 전달받은 속성(props)이 변경될 때 리렌더링이 발생합니다.

 

함수 컴포넌트 재호출

리액트는 해당 함수 컴포넌트를 재호출하여 반환된 JSX를 생성합니다.

 

가상 DOM 비교

이전 가상 DOM과 새로운 가상 DOM을 비교하여 변경된 부분을 찾습니다.

 

변경된 부분의 실제 DOM 업데이트

변경된 부분만을 실제 DOM에 반영하여 화면을 업데이트합니다. 이때, 실제 DOM을 직접 조작하는 것이 아니라 가상 DOM을 이용하여 최소한의 변경만을 적용합니다. 이 때 변경된 부분만을 효율적으로 찾는 알고리즘은 디핑(diffing) 이라 부르며, 변경된 사항들을 한 번에 업데이트 하는 기법을 배치 업데이트라고 부릅니다.

 

리액트에서 제어 컴포넌트(Controlled Component) | React의 상태(state)를 사용하여 폼 요소의 값을 제어

제어 컴포넌트는 React의 상태(state)를 사용하여 폼 요소의 값을 제어합니다. 폼 요소의 값이 React의 상태로 저장되며, 이 상태가 변경될 때마다 리렌더링됩니다. 따라서, 사용자 입력에 대한 변화를 실시간으로 감지하고 반응할 수 있습니다.

주로 input의 value prop을 사용하여 React의 상태와 폼 요소를 연결합니다.

function ControlledComponent() {
  const [value, setValue] = useState('');

  const handleChange = (e) => {
    setValue(e.target.value);
  };

  return (
    <input type="text" value={value} onChange={handleChange} />
  );
}


리액트에서 비제어 컴포넌트(Uncontrolled Component) |  React의 상태(state)를 사용하지 않고, 폼 요소의 DOM을 직접 조작하여 값을 관리

비제어 컴포넌트는 React의 상태(state)를 사용하지 않고, 폼 요소의 DOM을 직접 조작하여 값을 관리합니다. 컴포넌트가 렌더링된 후에는 React는 해당 요소를 더 이상 제어하지 않습니다. 주로 ref를 사용하여 폼 요소의 DOM에 직접 접근합니다.

function UncontrolledComponent() {
  const inputRef = useRef(null);

  const handleClick = () => {
    console.log(inputRef.current.value);
  };

  return (
    <div>
      <input type="text" ref={inputRef} />
      <button onClick={handleClick}>Submit</button>
    </div>
  );
}


제어 컴포넌트는 React의 강력한 상태 관리 기능을 활용하여 폼 요소를 다루는 데 유용하며, 비제어 컴포넌트는 외부 라이브러리나 기존의 DOM 조작과의 통합 등 특정 상황에서 유용할 수 있습니다.

 

리액트에서 상태 끌어 올리기 | 상태를 끌어올려서 상위 컴포넌트에서 하위 컴포넌트로 데이터를 전달하고 setState 를 통해 부모 상태를 관리하는 것

리액트에서 상태(state) 끌어올리기란 여러 하위 컴포넌트에서 공유되는 상태를 부모 컴포넌트로 끌어올리는 것을 말합니다. 이를 통해 상위 컴포넌트에서 하위 컴포넌트로 상태를 전달하고 변경할 수 있습니다. 주로 상위 컴포넌트에서 상태를 관리하고 하위 컴포넌트에서는 이 상태를 props로 전달받아 사용합니다. 이러한 방식은 컴포넌트 간의 데이터 흐름을 명확하게 만들어 주고, 상태 관리를 더욱 효율적으로 할 수 있습니다.

상태를 끌어올리는 예시를 살펴보겠습니다.

// ParentComponent.js
import React, { useState } from 'react';
import ChildComponent from './ChildComponent';

function ParentComponent() {
  const [count, setCount] = useState(0);

  const incrementCount = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <h2>Parent Component</h2>
      <p>Count: {count}</p>
      <ChildComponent count={count} onIncrement={incrementCount} />
    </div>
  );
}

export default ParentComponent;

 

// ChildComponent.js
import React from 'react';

function ChildComponent({ count, onIncrement }) {
  return (
    <div>
      <h3>Child Component</h3>
      <p>Count from Parent: {count}</p>
      <button onClick={onIncrement}>Increment Count</button>
    </div>
  );
}

export default ChildComponent;


위 예시에서 ParentComponent는 count라는 상태를 가지고 있고, incrementCount 함수를 통해 이 상태를 변경할 수 있습니다. ChildComponent는 count 상태를 props로 받아와서 사용하고, onIncrement 함수를 호출하여 상위 컴포넌트의 상태를 변경할 수 있습니다. 이처럼 상태를 끌어올려서 상위 컴포넌트에서 하위 컴포넌트로 데이터를 전달하고 상태를 관리하는 것이 상태 끌어올리기입니다.

 

리액트 컴포넌트는 언제나 멱등성이 중요함

리액트에서 컴포넌트는 입력(state, prop, content) 와 관련해서 항상 동일한 출력을 반환해야 하는데, 이를 멱등성이라고 합니다. 다만, 이는 멱등성을 위배하는 함수를 사용하지 말아야 한다는 것이 아니라, 리액트가 렌더링 되는 동안에는 그러한 동작이 실행되어서는 안 된다는 점입니다.

 

즉, new Date 와 같이 매번 결과가 바뀌는 비멱등성을 가지는 함수를 매번 호출 해야 하는 경우, useEffect 훅을 사용하여 컴포넌트의 렌더링 시간과 해당 함수가 실행되는 시점을 동기화하여 처리하는 것이 좋습니다.

 

 

Components and Hooks must be pure – React

The library for web and native user interfaces

react-ko.dev

 

 

window 객체의 devicePixelRatio | 사용자의 디스플레이 픽셀 밀도를 나타내는 속성

window.devicePixelRatio는 웹 브라우저에서 사용자의 디스플레이의 픽셀 밀도를 나타내는 JavaScript 속성입니다. 이 속성은 현재 사용 중인 디스플레이의 실제 픽셀 수와 CSS 픽셀 수 사이의 비율을 나타냅니다.

예를 들어, Retina 디스플레이를 가진 장치는 일반 디스플레이보다 더 높은 픽셀 밀도를 가질 것입니다. 이 경우 window.devicePixelRatio 값은 2를 반환할 것입니다. 이는 Retina 디스플레이에서 CSS 1에 해당하는 픽셀이   실제로는 2픽셀에 매핑될 수 있음을 의미합니다.

음.. 이대로는 설명이 빈약한 것으니까 조금 풀어서 설명해보겠습니다

Retina 디스플레이는 일반적으로 고해상도 디스플레이를 가리킵니다. 또한, 픽셀 밀도가 높아서 더 많은 픽셀을 한정된 영역에 채워 넣어 선명한 화면을 제공합니다.

실제로, Retina 디스플레이에서는 CSS 픽셀과 실제 픽셀 사이의 매핑이 복잡하게 이루어지는데, 하나의 CSS 픽셀은 여러 개의 실제 픽셀로 매핑될 수 있습니다. 이러한 특징은 더 높은 해상도(고화질)를 제공하면서도 동일한 크기의 요소를 더 선명하게 보이게 할 수 있습니다.

즉, Retina 디스플레이에서는 1개의 CSS 픽셀이 2개 또는 그 이상의 실제 픽셀로 매핑될 수 있습니다(이것이 동일한 사이즈의 이미지임에도 고화질 디스플레이 환경에서는 더 선명하게 보일 수 있는 이유입니다).

이를 통해 웹 개발자는 디바이스의 픽셀 밀도에 따라 레티나 또는 고해상도 디스플레이에 적합한 이미지를 선택하거나 다른 디바이스에 맞게 레이아웃을 조정할 수 있습니다. 

 

타입스크립트 관련 개념

인덱스 시그니쳐 | 객체의 프로퍼티가 사전 정의되지 않은 경우 동적으로 프로퍼티를 추가 시 적용하는 방법

객체 리터럴

아래 예시에 따르면 객체에 접근 시 key 의 경우는 string 타입이고, 접근하는 값(value)의 타입은 number 임을 명시하고 있습니다.

interface MyObject {
    [key: string]: number;
}

let obj: MyObject = { a: 1, b: 2, c: 3 };
let valueA: number = obj['a']; // 속성 'a'에 접근

 

이 경우 obj['a'] 에서 키에 해당하는 a 는 string 타입이고, 그에 접근하는 1 이라는 값은 number 타입임을 알 수 있습니다.

 

유니온 타입을 사용 시

추가로 유니온 타입을 사용하여, 여러 타입의 값에 인덱싱하도록 처리할 수도 있습니다.

interface MyObject {
    [key: string]: number | string;
}

let obj: MyObject = { a: 1, b: 'two', c: 3 };
let valueA: number | string = obj['a']; // 속성 'a'에 접근

 

 

 

타입(Type) | 변수, 매개변수, 함수의 반환값 등의 데이터의 형태를 나타내는 것

변수, 매개변수, 함수의 반환값 등의 데이터의 형태를 나타내는 것으로, 변수에 할당되는 값의 종류를 의미합니다. TypeScript에서는 변수와 함수에 대한 타입을 명시할 수 있습니다.

 let age: number = 30;

 

인터페이스(Interface) | 객체의 구조(속성 및 메서드)를 정의하는 방법으로, 객체가 반드시 가져야 하는 속성과 메서드를 명시하여 구조를 정의

객체의 구조(속성 및 메서드)를 정의하는 방법으로, 객체가 반드시 가져야 하는 속성과 메서드를 명시하여 구조를 정의합니다. 타입스크립트에서 재사용 가능한 코드를 작성하고 코드의 가독성을 높이기 위해 인터페이스를 사용합니다.

interface Person {
  name: string;
  age: number;
  greet: () => void;
}

 

제네릭(Generic) | 타입 또는 함수를 정의할 때 타입을 매개변수로 사용하는 방법

타입 또는 함수를 정의할 때 타입을 매개변수로 사용하는 방법으로, 재사용 가능한 코드를 작성할 때 타입에 관한 유연성을 제공합니다.

function identity<T>(arg: T): T {
  return arg;
}

 

타입 별칭(Type Alias) | 기존 타입에 대한 별칭을 지정하는 것

기존 타입에 대한 별칭을 지정하는 것으로, 복잡한 타입을 간단하게 표현하거나 유지보수성을 높이기 위해 사용됩니다.

type Coordinates = [number, number];

 

타입 가드(Type Guard) | 런타임에 어떤 타입이 사용되는지를 체크하여 해당 타입에 맞는 코드 블록을 실행하는 방법

 런타임에 어떤 타입이 사용되는지를 체크하여 해당 타입에 맞는 코드 블록을 실행하는 방법으로, 타입 안전성을 확보하기 위해 사용됩니다.

function isNumber(value: any): value is number {
  return typeof value === 'number';
}

 

여기서 value is number 는 타입 가드 시에 조건문이 true 인 경우 value 의 타입이 number 임을 타입스크립트 컴파일러에 알려주는 역할을 합니다.

 

타입 추론(Type Inference) |  TypeScript 컴파일러가 코드를 분석하여 변수 또는 함수의 타입을 추론하는 기능

 TypeScript 컴파일러가 코드를 분석하여 변수 또는 함수의 타입을 추론하는 기능으로, 타입을 명시하지 않아도 TypeScript가 코드를 이해하고 타입을 추론할 수 있습니다.

let name = "John"; (name 변수의 타입은 자동으로 문자열로 추론됨)

 

타입 단언(Type Assertion) | 개발자가 컴파일러에게 변수의 타입을 강제로 지정하는 것

개발자가 컴파일러에게 변수의 타입을 강제로 지정하는 것으로, 컴파일러가 변수의 타입을 추론하지 못할 때 사용됩니다.

let value: any = "hello"; let length = (value as string).length;

 

타입 단언은 any 타입과 마찬가지로 너무 남용하게 되면 타입스크립트 정적 타이핑의 이점을 살리지 못하고, 컴파일이 예기치 못한 문제를 발생시킬 수 있으므로 이를 염두에 두고 해당 타입을 확신할 수 있는 경우에만 사용해야 합니다.

 

 

NextJS

revalidatePath | 특정 서버 컴포넌트 재유효화

NextJS 13 버전 이후에 등장한 revalidatePath 는 서버 컴포넌트에서 주로 사용되는 메서드입니다. 해당 메서드를 사용하면, 특정 경로의 캐시를 무효화하고 다시 검증할 수 있습니다. 서버 컴포넌트에서만 동작하는 이유는, 이 메서드가 서버에서 실행되는 빌드 타임 또는 런타임에 페이지나 데이터의 정적 생성 및 재검증을 관리하기 때문입니다.

 

보통 NextJS 에서 npm run build 를 통해 기본적으로 서버 구성요소에 해당하는 파일들은 정적으로 빌드 됩니다. 따라서 사전 빌드된 페이지들은 캐싱되어 사용자가 해당 주소로 접속하게 되면 보여지게 되는데, 이 때 정적이기 때문에, 새로운 데이터가 추가되더라도 해당 페이지는 아무런 변화가 보이지 않습니다. 

 

여기서 revalidatePath( ) 의 인자로 '/path' 와 같이 특정 경로의 URI 를 전달하면, 해당 메서드가 실행될 때 /path  경로의 정적 페이지는 재유효화(쉽개 말해, 갱신)되어 앞서 추가된 데이터를 포함한 최신 페이지가 보여지게 됩니다.

 

사용방법

서버 측에서 클라이언트에서 온 요청 중 body 에 담겨져온 path 프로퍼티를 revalidatePath(path) 와 같이 전달해줍니다.

// app/api/revalidate.js

import { NextResponse } from 'next/server';
import { revalidatePath } from 'next/cache';

export async function POST(request) {
  const { path } = await request.json();
  
  // 특정 경로를 다시 검증
  revalidatePath(path);

  return NextResponse.json({ revalidated: true, now: Date.now() });
}

 

그리고 클라이언트 측에서는 fetch 요청을 통해 재유효화 할 경로 정보를 body 객체에 담아서 요청을 보내줍니다.

// components/TriggerRevalidateButton.js

import { useState } from 'react';

export default function TriggerRevalidateButton() {
  const [isRevalidating, setIsRevalidating] = useState(false);

  const handleRevalidate = async () => {
    setIsRevalidating(true);

    await fetch('/api/revalidate', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ path: '/path-to-revalidate' })
    });

    setIsRevalidating(false);
  };

  return (
    <button onClick={handleRevalidate} disabled={isRevalidating}>
      {isRevalidating ? 'Revalidating...' : 'Revalidate'}
    </button>
  );
}

 

이렇게 하면, POST 요청을 받은 서버 측에서 이를 처리하여 해당 경로가 새롭게 갱신되어, 사용자는 변화된 상태가 반영된 화면을 볼 수 있게 됩니다.

 

여기서 정리 

클라이언트 컴포넌트에서는 Next.js의 빌드 타임이나 서버에서 발생하는 정적 생성, 데이터 페칭 등의 작업을 직접적으로 제어할 수 없습니다. 클라이언트 컴포넌트는 브라우저에서 실행되기 때문에 이러한 서버 사이드 기능을 직접 사용할 수 없으며, 대신 서버와의 통신을 통해 간접적으로 제어할 수 있습니다.

 

참고자료

- 리액트 공식문서(컴포넌트와 훅은 순수해야 된다): https://react-ko.dev/reference/rules/components-and-hooks-must-be-pure

- OWASP XSS: https://owasp.org/www-community/attacks/xss/

- MDN XSS : https://developer.mozilla.org/ko/docs/Glossary/Cross-site_scripting

반응형