랜더링(Rendering) 이란?
개발자가 작성한 HTML, CSS, Javascript 코드(문서)를 브라우저에 출력하는 것
사용자가 주소창에 주소를 입력하던가 어떤 링크를 클릭하면 해당 서버에 요청이 들어간다.
서버는 요청을 받아서 요청에 응답하는 문서를 브라우저에 보낸다.
브라우저는 해당 문서를 받아서 코드를 파싱해 출력한다.
브라우저 랜더링 과정
- 랜더 엔진이 HTML, CSS 코드를 다운로드 받는다.
- 다운 받은 HTML 코드를 파싱하여 DOM 트리를 만든다.
- 다운 받은 CSS 코드를 파싱하여 CSSOM 트리를 만든다 (CSS Object Model)
- DOM 트리와 CSSOM 트리를 결합해서 Render Tree를 만든다.
4-1. 랜더링 트리를 만들때 화면에 직접 보이지 않는 속성들 (head 태그, display none 속성 등 ) 은 제외된다.
- 레이아웃 단계 : 랜더링 트리를 이용해 각 노드들의 실제 크기와 위치를 계산한다. (뷰포트 내에서 각 노드들의 위치를 계산하는 과정)
- 계산된 위치에 노드들을 그리고 브라우저에 노출한다.
Ref>>
프로미스란?
- 왜 promise가 나왔을까?
콜백지옥, 비동기 간의 경쟁, 비동기 결과 값을 여러 분기가 사용하는 경우
- ES6에 나온 비동기 처리 객체
- 미래에 대한 약속
- new Promise() 생성자 함수로 생성하며 인자로는 특별한 함수(executor)을 받는다.
const promise = new Promise((resolve, reject) => { });
- Executor 함수는 resolve와 reject 함수를 인자로 받는데, resolve는 비동기 통신이 성공(이행 : fulfilled) 했을 때, reject는 비동기 통신이 실패(rejected) 했을 때 실행
- promise의 상태에는 3가지가 있는데, pending(대기) fulfilled(성공) rejected(실패)
- promise를 반환받아서 이를 상태처리 할 수 있다. then(), catch() 함수를 이용할 수 있는데, then() 함수는 fulfilled상태에서 실행되며, catch() 함수는 rejected 상태에서 실행된다. 두 함수 모두 promise를 반환하며 체이닝해서 코드를 구현할 수 있다.
function promisePrac() {
return new Promise((resolve, reject) => {
resolve(‘success’);
});
}
const promise = promisePrac().then( (response) => {
console.log(‘fulfilled’);
}).catch( (error) => {
console.log(‘rejected’);
});
- async, await 키워드는 ES6 이후에 출시된 키워드다. async는 함수에 붙이는 키워드인데 해당 키워드를 붙이면 해당 함수는 반드시 promise를 리턴한다. await 키워드는 기다리라는 키워드인데 해당 키워드가 붙을 시 비동기 통신이 완료될 때 까지 모든 작업이 멈추게 된다.
reference 블로그
비동기, 비동기 통신이란?
기본적으로 자바스크립트는 싱글 스레드 언어이다. (자바스크립트 자체는 싱글 쓰레드이지만, 자바스크립트 엔진은 멀티 쓰레드이다!) 한번에 하나의 작업만 수행할 수 있다는 것이다.
웹 사이트를 빌드하다보면 페이지의 전환 없이 새로운 데이터가 필요할 때가 있다. 그때 사용할 수 있는게 비동기 통신이다. 페이지의 백그라운드에서 서버단에 요청을 보내고 요청에 대한 응답을 기다리며 응답이 왔을 때 데이터를 화면에 출력해준다. 이런 방법이 비동기 통신이다. 보통 ajax, axois, XMLHttpRequest 방식으로 처리했다.
비동기란 동시에 여러 작업을 하자! 라고 표현할 수 있다.
가령 방청소를 예시를 들어보자. 필자가 혼자 집안 청소를 하고 있다. (싱글 쓰레드) 바닥에 물걸레질을 하고 싶은데, 물걸레질을 하기 위해서는 뜨거운 물이 필요하다. 뜨거운 물이 필요하기에 물을 뜨겁게 데피기 시작했다. 이 때 동기식으로 처리된 로직은 물이 펄펄 끓을때까지 기다린다. 다른 작업은 하지 않고 그저 기다린다. 만약 비동기식으로 처리되었다면 물이 끓을때까지 필자는 설거지를 할 수 있다. 다른 작업을 처리할 수 있는 것이다.
reference 블로그
GET/POST?
클라이언트에서 서버측에 요청을 보낼 때 사용하는 HTTP 메서드
GET
- 요청을 보내는 URL 끝에 쿼리 스트링을 붙여서 파라미터를 전달하는 방식.
- 보내는 정보가 노출되기에 보안이 중요하지 않는 작업에서 처리
- URL에 파라미터를 붙이기 때문에 HTTP body가 존재하지 않는다.
- 캐시가 가능하다, 브라우저에 히스토리가 남으며 길이 제한이 있다.
POST
- 요청을 보낼 때 같이 보내는 파라미터를 HTTP 메세지 body에 담아서 보낸다.
- GET과는 다르게 바디에 담아서 보내기에 어느정도 보안성을 유지할 수 있지만, 정보를 보낼 때 암호화를 하지 않는다면 결국 보안성에서 문제가 발생한다.
- 길이 제한이 없다.
- 캐시되지 않으며 브라우저 히스토리에 남지 않는다.
캐시? 쿠키? 세션?
캐시
캐시는 웹 페이지의 요소를 저장하기 위한 임시 저장소.
특히 후에 필요할 것 같은 요소들을 저장하는데, 요소들은 그림 파일이나 문서 파일일 수 있다.
캐시 목적 : 웹 페이지가 빠르게 렌더링 할 수 있도록 도와준다.
삭제 : 사용자 직접 수동 삭제
예시 : 오디오, 비디오 파일
쿠키
쿠키는 정보를 저장하기 위해 사용되며, 웹 서버에서 PC로 보내는 작은 파일을 저장
쿠키는 누군가 특정한 웹 사이트를 접속할 때 발생
쿠키는 사용자의 인증을 도와준다.
만료 기한이 있기 때문에 시간이 지나면 자동 삭제
예시 : 유저의 선호도 (로그인 정보, 방문 기록, 방문 횟수)
- 로그인 정보와 같이 유저가 굳이 다시 서버에 요청하기에는 비효율적인 정보를 ‘ 로컬 ‘에 저장해 둠으로써 생산성을 높이는게 목적이다.
- 보안에 각별히 신경써야함
- 서버에 다시 Request 하지 않기에 속도가 빠르다.
세션
세션(Session)이란 유저의 상태나 어떤 특정한 것을 ‘ 서버 에 임시적으로 저장해둔 것
- 서버에 저장되어 있기에 쿠키보다 보안성 측면에서 유리
- 서버에 저장되어 있기에 쿠키보다 느리며 메모리 과부하 발생할 여지가 있다.
- 로그 관리가 편하다.
세션과 쿠키의 차이는 데이터가 어디에 저장되어 있는지
쿠키와 캐시의 차이는 데이터의 목적성 차이!
https://ivorycode.tistory.com/entry/Storage
jQuery의 점유율이 떨어지는 이유
- 인터넷의 역사 : ES5전 통합되지 않는 (호환성이 좋지 않은) 브라우저들 사이에서 짧은 코드로 다양한 기능을 구현할 수 있는 jQuery는 인기가 있었다.
- 웹 브라우저의 환경 변화와 가상 DOM을 사용하는 라이브러리의 인기로 더 이상 jQuery를 사용하지 않게 된다. DOM 조작은 이젠 Javascript 와 Typescript 의 코드로 대체가 가능하다.
- 지나치게 무겁다. jQuery는 기존 코드를 Wrapping 해 새롭게 만든 패키지며, 최적화를 위한 설계가 되어있지 않다.
- 다양한 라이브러리의 등장. 가상 DOM을 사용하는 라이브러리의 등장과, 자바스크립트 코드를 통해 직접 DOM을 조작할 수 있다.
함수형 프로그래밍이란??
- 절차형, 객체지향 프로그래밍이 어떻게? 에 중점을 뒀던 반면 무엇을? 에 중점을 두며 프로그래밍을 하는 방식.
- 대입문을 사용하지 않는 프로그래밍이며 모든 것을 순수 함수로 나누어 문제를 해결하는 방식, 가독성을 높이며 유지보수에 용이하다
- 특징 : 부수 효과가 없는 순수 함수를 1급 객체로 간주하여 파라미터로 넘기거나 반환값으로 사용할 수 있으며 참조 투명성을 지킬 수 있다.
- 데이터의 불변성 (immutability)
함수형 프로그래밍의 특징
- 순수함수
- 동일한 입력값에 대해선 동일한 반환값
- 함수의 실행이 프로그램의 실행에 영향을 미치지 않는 함수
- Side Effect가 없는 함수
- 비상태, 불변성( stateless, immutability)
- 데이터의 변경이 필요한 경우에는 데이터의 복사본을 만들어서 일부를 변경하고 변경한 복사본을 활용한다.
- 선언형 함수 (무엇을에 중점)
- 명령형 프로그래밍에서 어떻게에 중점을 두는 반면에 선언형 프로그래밍은 무엇을 에 중점을 둔다.
- 예를들어서 명령형은 for문을 통해서 어떻게 이뤄지는지 코딩하는 반면에 선연형 프로그래밍은 map 함수를 통해서 무엇을 처리할 지 코딩한다.
- 일급객체, 고차함수
- 함수를 1급객체로 여긴다. 함수 자체를 반환하거나 매개변수로 넣어줄 수 있음, 동적으로 프로퍼티 할당이 가능
함수형 프로그래밍의 장점
- 높은 수준의 추상화
- 쉽게 예측 가능한 프로그래밍(불변성으로)
- 함수 단위의 코드 재사용성
Ref >
SSR / CSR /SPA
Server Side Rendering
- 페이지 전환할 때 마다 서버에 새로운 페이지에 대한 요청을 보냄
- 서버에서 렌더링을 하고 Data가 결합된 HTML 파일을 내려주는 방식
- 페이지를 이동할 때 마다 서버에 요청을 하기 떄문에 페이지가 깜빡 깜빡 거리는 현상을 마주친다.
Client Side Rendering
- 최초 요청 시 HTML, CSS, JS등 리소스를 받아온다. 이후 서버에는 데이터만 요청하고 자바스크립트로 뷰를 컨트롤
- 최초 업로드때는 SSR보다 느리지만 페이지 전환시 SSR보다 UX측면에서 유리
- 서버는 단순히 JSON 데이터를 보내주는 역할, html을 그리는 역할은 클라이언트 측의 자바스크립트에서 수행
- SEO (Search Engine Optimizer : 검색 엔진 최적화) 에서의 문제점 why?
- Html 랜더링을 client에서 하기 때문에
- view를 생성하기 위해선 자바스크립트 실행해야 함
- But 대부분의 웹 크롤러 봇들은 자바스크립트 파일을 실행시키지 못한다.
- 그렇기에 HTML에서만 콘텐츠를 수집하게 되고 CSR 페이지를 빈 페이지로 인식하게 됨
SPA (Single Page Application)
- 단 하나의 페이지를 가지고 있는 웹 어플리케이션
- 기본적으로 SPA는 CSR 이지만 SPA === CSR은 아니다. (SPA가 CSR 방식을 사용한 것이지 SPA !== CSR)
- SPA에서 CSR과 SSR을 고민하는 이유는 SEO(Search Engine Optimization) 때문이다.
- CSR에서 SEO가 잘 안되는게 아니라, 크롤러들이 JS를 지원하지 않기 때문
- CSR에서 SEO가 잘 안되는게 아니라, 크롤러들이 JS를 지원하지 않기 때문
- 사용자 친화적, 초기 렌더링 후 데이터만 받아오기 때문에 상대적으로 서버 요청이 적다
- 프론트/백 분리로 개발업무 분화 및 협업 용이
- Virtual DOM
- 트래픽을 감소시키고 사용자에게 최적화된 환경을 제공할 수 있음
SEO
- 검색엔진이 이해하기 쉽도록 홈페이지 구조와 페이지를 개발해 검색 결과 상위에 노출될 수 있도록 하는 작업
SEO 최적화 방법
- 정확한 HTML 문법 작성
- 구체적인 페이지 제목 만들기
- 메타 태그 활용하기
- 이미지에 alt 속성 기재
- 이미지 맵에 중요한 링크 사용 피하기
- 플래시 전용 페이지 피하기
- 앵커 태그를 활용한 적절한 키워드 배치
- Https 사용 권장
For-in / For-of
- for-in 루프는 객체의 프로퍼티를 순회하는데 사용
const obj = {name:'dh', age:29, birth:'1994'};
for (let i in obj) {
console.log(i);
}
//name, age, birth 출력
- for-of 루프는 반복할 수 있는(iterable) 객체의 루프를 돌 때 사용 (array, list..)
마치 Java의 for-each 반복문(향상된 for문)
const arr = [1,2,3,4,5];
for (let i of arr) {
console.log(i)
}
//1, 2, 3, 4, 5 순차 출력
클로저란?
클로저는 함수와 함수가 선언된 어휘적 환경의 조합이다. 클로저는 주변의 상태 (lexical environment)의 참조와 함께 번들로 묶인 함수의 조합입니다. 즉, 클로저는 우리에게 inner함수에서 outer함수의 스코프에 접근을 가능하게 해줍니다.
클로저는 독립적인 변수를 가리키는 함수이다.
MDN에서는 클로저를 어휘적 환경의 조합이라고 정의하고 있다.
또한 클로저는 환경을 기억하고 있다고 한다. (함수와 함수를 둘러싼 환경을 저장)
정의로 살펴보면 너무 어렵다. 그럼 클로저가 무엇이고 어떻게 동작하는지 간단히 살펴보자
function outerFunc() {
const outerVar = 'outerVar';
function innerFunc() {
console.log(outerVar);
}
return innerFunc;
}
var out = outerFunc();
out();
outerFunc 이라는 함수는 내부변수와 내부함수를 가지고있고, 내부함수를 리턴한다.
내부함수는 outerFunc의 변수를 콘솔에 출력하라고 하고있다.
위 정의에서 클로저는 inner함수에서 outer함수의 스코프에 접근이 가능하게 해준다고 하였다.
out 변수에는 innerFunc이 저장되어 있고, 이를 실행할 시 outerVar이 출력된다. 이는 클로저가 환경을 기억(저장)하고 있기 때문에 가능하다.
클로저는 함수 내에서 정의된 함수이며 함수의 환경을 기억하고 있는 함수이다.
https://hyunseob.github.io/2016/08/30/javascript-closure/
https://meetup.toast.com/posts/86
https://developer.mozilla.org/ko/docs/Web/JavaScript/Closures#%ED%81%B4%EB%A1%9C%EC%A0%80closure
https://velog.io/@proshy/JS%ED%81%B4%EB%A1%9C%EC%A0%B8closure%EC%99%80-%ED%81%B4%EB%A1%9C%EC%A0%B8%EC%9D%98-%EC%82%AC%EC%9A%A9-%EC%98%88%EC%A0%9C
Virtual DOM
- 기존의 DOM을 조작하면 랜더링이 비효율적으로 이루어짐
- 모바일의 발전으로 SPA의 등장, DOM 복잡도 증가에 따른 최적화 및 유지 보수가 어려워지는 문제
- 메모리에 Virtual DOM을 놓고 변경된 내용을 virtual DOM에 모아놓고 실제 변경된 부분을 DOM과 비교하면서 변경, 그에 따른 렌더링은 한번만 하는 것으로 해결
- DOM을 조작하면 랜더 트리를 재생성하고 레이아웃을 만들고 페인팅을 하는 과정이 다시 반복
- 리액트를 사용하더라도 최적화 작업을 제대로 하지 않을 경우 퍼포먼스가 DOM보다 안좋을수도. (예시 무한 스크롤 pagination 작업 시 shouldComponentUpdate를 통한 최적화 작업을 하지 않으면 새 데이터가 로딩될 때 기존에 랜더링 된 컴포넌트들도 다시 랜더링 됨)
REF>
TDD(Test Driven Development)
- 테스트 주도 개발 방법론 (테스트를 염두에 둔 프로그램 개발 방법론)
- 설계 이후 테스트 코드를 작성해 설계에 오류가 있다면 바로 설계를 수정하는 방식
- 테스트 이후 오류가 없다면 그대로 개발(코드 작성)
- 기존의 개발 프로세스는 설계 이후 그대로 개발(코드를 작성) 후 테스트를 해 오류가 있으면 수정을 하는 식
- TDD의 장점
- 객체 지향적인 코드 개발 (모든 코드가 재사용 성 기반으로 작성되어야 하기 때문에)
- 설계 수정 시간이 단축된다. ->오류는 일찍 찾을수록 고치는 비용이 적게 든다
- 디버깅 시간의 단축
- 유지 보수의 용이성
- 테스트 문서의 대체 가능성
자식 컴포넌트에서 부모 컴포넌트로 props 보내는 방법
- 부모 컴포넌트에서 함수를 정의하고, 해당 함수를 자식 컴포넌트에 props로 보낸다.
- 자식 컴포넌트에서 위 props를 받아서 보내고자 하는 데이터를 저장한다.
Javascript this 키워드
- this는 기본적으로 window (strict 모드에선 undefined)
- 함수 내부에서도 this는 기본적으로 window
- 객체에 정의된 메서드 내부의 this는 객체를 가리킴 (객체를 통해 함수가 호출된다면 그 객체가 this의 context 객체)
- 호출할 때 호출하는 함수가 객체의 메서드인지, 그냥 함수인지가 중요하다. (그냥 함수이면 window) (전역 스코프에서 생성한 변수는 전역 객체에 등록된다. 전역 this = window)
- bind, call, apply 를 사용하면 (함수의 명시적 바인딩) this는 객체를 가르킴
- 함수 (함수 객체)는 bind, call, apply 라는 메서드를 가지고 있는데 첫 번째 인자로 넘겨주는 것이 this context => 이를 명시적 바인딩이라고 한다
- new 연산자 바인딩
REF >
컴포넌트란?
- 소프트웨어 시스템에서 독립적인 업무 또는 독립적인 기능을 수행하는 모듈
- 시스템을 유지보수 하는데 있어 교체 가능한 부품.
- 리액트에서 컴포넌트
- 리액트로 만들어진 앱을 이루는 최소한의 단위
- 컴포넌트는 MVC의 뷰를 독립적으로 구성하여 재사용을 할 수 있으며 이를 통해 새로운 컴포넌트를 만들수 있다.
- 데이터(props)를 받아 View의 상태(state)에 따라 DOM Node를 출력하는 함수
마운트란?
- 컴포넌트가 DOM에 생성되어 웹 브라우저에 나타나는 것
언마운트란?
컴포넌트 랜더링 시점
- State 변경
- Props 변경
- forceUpdate() 를 실행했을 때
- 부모 컴포넌트가 랜더링 되었을 때
Hook이란?
- 함수형 컴포넌트에서 생명주기를 다룰수 있으며 상태값 관리할 수 있게 해주는 것
- 장점
- 재사용 가능한 로직을 쉽게 만들어서 유지 보수가 용이하게 해줌
- 코드의 중복성을 줄여서 가독성을 높임
- 규칙
- 반드시 함수형 컴포넌트 안에서 호출해야함 (커스텀 훅에서는 가능)
- 최상위에서만 Hook 을 호출해야함 (반복문, 조건문, 중첩된 함수 내에서 Hook 호출 x)
Why Functional Components?
- 리액트 Hook (16.8v) 출시 이전엔 함수형 컴포넌트에서는 컴포넌트의 생명주기와 상태 값을 다룰수 없었다.
- State를 다룰 필요가 없거나 라이프 사이클을 관리할 필요가 없는 컴포넌트에 한에서만 사용했었음
- 훅 출시 이후로 위 기능들을 함수형 컴포넌트에서 다룰 수 있음 (전체 생명주기 메서드는 아니지만)
- 불필요한 코드들과 다소 이해하기 어려운 코드들 (constructor, this 등..)을 함수형 컴포넌트를 통해 가독성을 높일수 있음.
- 직관적임
- 함수형 컴포넌트는 순수함수이다. 클래스형 컴포넌트보다 속도적인 측면에서 빠름
랜더링 최적화?
- Props -> React.memo
- State -> useMemo
- State 선언 위치가 어디냐.. (State가 갱신되면 하위 컴포넌트도 모두 리랜더링 된다.)
- 해당 state를 사용하는 컴포넌트를 파악한 후 State를 사용하는 최상위 컴포넌트에 선언
- 객체 타입의 state는 최대한 분할해서 선언
- shouldComponentUpdate : (false 일 경우 리랜더링 방지 -> 최적화) React.memo
- React.memo 컴포넌트를 래핑하여 props를 비교하고 리랜더링을 막을 수 있는 메모이제이션
- 컴포넌트를 매핑할 때 key 값으로 index를 사용하지 않는다
- useMemo : 종속 변수들이 변하지 않으면 함수를 굳이 다시 호출하지 않고 이전에 반환한 참조값을 재사용한다. 이를 통해 함수 호출 시간도 세이브 하며 같은 값을 props로 받는 하위 컴포넌트의 리랜더링도 방지한다.
useMemo( () => {}, [] )
: 의존 배열들어간 데이터가 변할때만 실행
- useCallback : 상위 컴포넌트에서 하위 컴포넌트로 함수를 props 넘겨줄 때 상위 컴포넌트가 리랜더링 될 때마다 상위 컴포넌트 안에 선언된 함수를 새로 생성하기 때문에 그때마다 새 참조 함수를 하위 컴포넌트에 넘겨주게 된다. 하위 컴포넌트도 props가 달라졌으므로 리랜더링. useCallback으로 함수를 선언해준 경우 종속 변수들이 변하지 않으면 굳이 함수를 재생성 하지 않는다.
- 하위 컴포넌트의 props로 객체를 넘겨주는 경우 새 객체 생성을 주의해야 함
REF>
메모이제이션 이란?
- 계산된 값을 자료구조에 저장하고 필요할 때 계산된 값을 저장된 자료구조에서 꺼내서 재사용하는 것
- useMemo : 메모이제이션 된 값을 반환
- useCallback이 함수 자체를 캐싱한다면 useMemo는 함수를 실행하고 리턴된 값을 캐싱한다.
useMemo(() => {return things}, [ ])
: 인자로 주어진 함수의 반환값을 메모이제이션 하며, 의존성 배열의 값이 변할 때만 실행된다.
- useCallback : 메모이제이션 된 콜백을 반환
- 랜더링 될 때 함수도 새로 생성이 된다. 함수는 객체이기 때문에 랜더링 이전과 이후의 메모리 주소값이 다르기에 React.memo로 최적화를 할 수 없다. 최적화를 위해 useCallback에 콜백 함수를 메모이제이션 시켜놓고 의존성 배열에 상태값을 넣어서 상태 값이 변경될 때 만 반응하도록 설정할 수 있다.
- React.memo -> 해당 컴포넌트에서 의존하고 있는 props, state가 변경될 때만 렌더링
useEffect 실행 시점
- 랜더링 이후
- 의존 배열에 넣어놓은 상태가 변경될 때
- componentDidMount, componentDidUpdate, componentWillUnmount (clean up function)
잘못된 내용에 대해서는 댓글 남겨주시면 감사하겠습니다!!!