티스토리 뷰
[진행 기간]: 23.07.03(월) ~ 07(금)
[교육 내용]
- 자소서를 작성하는 방법에 대해서 알려주는 세션(개발자로서의 장점)
- 과제 피드백을 통한 개선 방향 제시(클린코드,비동기 코드 에러 핸들링, 함수 단일 책임 원칙, 좋은 주석, 불필요한 state 제거)
- React 렌더링 최적화 & 메모이제이션을 해야하는 상황
[느낀 점]
- 관심사 분리? 제어의 역전? 잘 와닿지 않는 개념을 쉽게 풀어 설명해준다.
- 물론 코드에 알려준 개념들을 자연스럽게 녹여서 적용하기는 아직은 어렵지만 생각하지 못한 방법에 대해서 알게되어 좋은 경험이라고 생각한다. 함수형 컴포넌트 위주로 작성하다보니 획일적인 방법만을 사용하려는 경향이 생겼었는데 ES6 class 문법을 통해서(물론 bind()를 써줘야 하지만) 횡단 관심사를 처리하는 것이라든가.. 내가 처리하고 싶었는데 함수형 컴포넌트의 한계에 부딪혀서 적용해보지 못한 것들을 코드로 풀어내는 것을 보고 신기했다.
- 이외에도 비동기 코드에서 에러 핸들링하는 부분이 미흡했던 것(axios interceptor를 사용해도 try catch로 잡아줘야함)과 불필요한 state의 남발등 미흡했던 것들이 너무 많이 보여서 수치스럽기도하고 즐겁기도하고 양가적인 감정이 들었다.
[복습]
브라우저 렌더링 과정
리액트에서 렌더링이란? 화면에 특정한 요소를 그려내는 것을 의미한다.
브라우저에서 렌더링이란 DOM요소를 계산하고 그려내는 것을 의미한다. HTML과 CSS를 통해서 만들어지고 계산된 DOM과 CSSOM은 결합되어 렌더트리를 형성하고(layout), 이 렌더트리의 위치를 계산하고 최종적으로 브라우저에 그려진다(paint). 그리고 브라우저에서 제공하는 DOM API를 JS를 통해 호출하면서 브라우저에 그려진 화면을 변화시킨다. (JS)
[CRP(Critical Rendering Path) 과정]
: 화면을 보여주기 위해 HTML, CSS, JS를 다운로드 받고 그를 처리해서 화면에 픽셀 형태로 그려내는 과정
- HTML을 파싱해서 DOM을 만든다.
- CSS를 파싱해서 CSSOM을 만든다.
- DOM과 CSSOM을 결합해서 Render Tree를 만든다.
- Render Tree와 Viewport의 width를 통해서 각 요소들의 위치와 크기를 계산한다.(Layout)
- 지금까지 계산된 정보를 이용해 Render Tree상의 요소들을 실제 Pixel로 그려낸다. (Paint)
바닐라 JS를 이용해서 DOM에 직접 접근하고 수정하는 것(명령형)
- 최적화하는 일이 어플리케이션 규모가 커질수록 힘들어진다는 단점을 가진다.
- 위와 같은 이유로 명령형 개발 => 선언적 개발을 하게 된다.
어플리케이션에서 보여주고 싶은 핵심 UI를 선언하기만 하면 실제로 DOM을 조작해서 UI를 그려내고, 변화시키는 일은 라브러리나 프레임워크가 대신해주는 방식을 채택(선언적 개발)
선언적 개발의 니즈에 맞춰 React, Vue, Angular등의 라이브러리, 프레임워크가 등장하게 된다.
리액트 공식문서에 따르면 장점으로 '선언형, 컴포넌트 기반, 유연성' 등을 내세우고 있다.
리액트에서 리렌더링이 되는 시점
리액트에서 state 를 사용하는 이유는 UI와 상태(state)를 연동시키기 위해서이다.
=> 즉 state로 관리되어야 할 데이터는 UI에 반영되어 변할 여지가 있는 데이터들이어야 한다.
결론적으로 리액트에서 리렌더링이 발생하는 시점은 state가 변했을 때이다. 특정 컴포넌트의 state가 변한다면, 해당 컴포넌트와 하위에 이는 모든 컴포넌트들은 리렌덜이이 발생하게 된다.
리액트의 렌더링 과정(컴포넌트의 변화)
- 기존 컴포넌트의 UI를 재사용할 지 확인
- 함수 컴포넌트: 컴포넌트 함수를 호출 / Class 컴포넌트: `render` 메소드 호출
- 2의 결과를 통해서 새로운 VirtualDOM을 생성
- 이전의 VirtualDOM과 새로운 VirtualDOM을 비교해서 실제 변경된 부분만 DOM(real)에 적용한다.
[VirtualDOM의 도입 배경]
UI를 변화하기 위해서는 많은 DOM 조작이 필요하다. 하나하나의 DOM조작마다 CRP가 수행될 것이고 이는 브라우저에게 많은 연산을 요구하게 된다. 퍼포먼스를 저하시키는 요인이될 수 있으며 리액트는 이를 해결하기 위해 VirtualDOM 개념을 도입하게 된다.
즉, 기본적으로 리액트가 4번의 렌더링 과정을 통해서 최적화를 하고 있기 때문에 개발자가 최적화할 수 있는 과정은 1,2번이다.
[리액트에서 최적화하기]
1번 과정에서는 리렌더링될 컴포넌트의 UI가 이전과 동일하다고 판단되는 경우 새롭게 컴포넌트를 호출하지 않고 이전의 결과값을 그대로 사용하도록 하면서 최적화를 할 수 있다.(useCallback, useMemo, React.memo 활용)
2번과정에서는 컴포넌트 함수가 호출되면서 만들어질 VirtualDOM의 형태를 비교적 차이가 적은 형태로 만들어지도록 하는 것이다.
- e.g) UI를 바꾸기 위해 div 태그를 span태그로 변환시키는 것보다 div className="block" 을 className="inline" 으로 변환시키는 것이 VirtualDOM끼리 비교했을 때 차이가 적은 형태로 만들어진다.
- 2번 과정에서 최적화하는 것보단 1번 과정에서 최적화를 하는 것이 좀 더 유의미한 차이를 낼 거 같다는 개인의견..
React.memo 와 두번째 인자로 비교 함수 넘겨주기
HOC란 컴포넌트를 인자로 받아서, 컴포넌트를 리턴하는 컴포넌트를 말한다. 고차컴포넌트
- memo는 HOC이다.
- React.memo로 감싸진 컴포넌트는 상위 컴포넌트가 리렌더링될 경우 무조건 리렌더링되는 것이 아니라 Props를 비교하여 차이가 있을 경우에만 리렌더링을 수행한다.
- props를 비교하는 방식은 `shallow compare`를 하여 판단한다. 이 방식은 기본 비교 로직으로 비교로직을 커스텀하여 사용하고 싶다면 React.memo의 2번째 인자로 변화를 판단하는 함수를 넘겨주면 된다.
function MyComponent(props) {
/* render using props */
}
function areEqual(prevProps, nextProps) {
/*
true를 return할 경우 이전 결과를 재사용
false를 return할 경우 리렌더링을 수행
*/
}
export default React.memo(MyComponent, areEqual);
- 위처럼 직접 만든 비교함수를 전달할 경우, 해당 함수의 인자로는 이전의 props와 새로운 props가 순서대로 인자로 전달된다. 이 함수의 return 값이 true일 경우 이전 결과를 재사용하고, false를 return 할 경우 리렌더링을 수행한다.
자바스크립트 데이터 타입 그리고 불변성
원시형 타입 => 새로운 값으로 '교체' 하는 식으로 동작, 원시형 타입을 변경할 수 있는 방법은 없다. (불변성)
참조형 타입 => 여러 타입을 모아서 만들어진 형태로 객체 안의 내용물은 언제든, 어떤 형태로든 변경할 수 있다.(가변성)
그렇다면 가변성을 가진 참조형 타입이 원시형 타입보다 좋은 거 아닌가?
- [장점] 가변성은 메모리를 절약하면서 객체를 유연하게 사용할 수 있게 해준다.
- [단점] 결과를 예상하기 힘들다. 객체간의 비교가 어렵다.
[참조형 데이터 타입의 단점 추가 설명]
JS는 기본적으로 비교연산자를 수행할 때 해당 데이터의 메모리 주소를 통해서 일치 여부를 판단한다.
즉, 원시형 타입의 경우는 변경할 시 새로운 데이터가 만들어지면서 교체되는 방식이기 때문에 메모리 주소가 달라져서 비교연산자를 활용하기 매우 용이하다. 하지만, 객체의 경우 안의 내용무링 어떻게 바뀌었는지에 상관없이 해당 객체를 가리키는 메모리 주소는 동일하기 때문에 실질적으로 내용이 변했는지 판단하기 어렵다. 또한 내용물이 완전히 같은 두 객체를 비교하더라도 각 객체를 가리키는 메모리 주소가 다르기 때문에 두 객체는 동일하지 않다는 결과가 나온다.
const prev = {name:"prev"};
const next = prev;
next.name = "next";
prev === next // true
위와 같이 next 객체의 name프로퍼티가 next로 변했음에도 prev와 next객체의 메모리 주소가 같아서 true로 결과가 나온다.
최근에는 메모리 효율을 추구하기보다는 객체 비교의 편리함을 취하기 위해 객체를 불변하게 활용하는 방식을 많이 사용한다.
🤔객체를 불변하게 한다는 것?
- 한번 만들어진 객체를 수정하지 않는다는 의미
- 객체의 내용을 변경(수정)해야 할 경우, 원시형 타입과 마찬가지로 기존의 객체를 수정하지 않고 무조건 새로 객체를 만든 후 교체하는 방식을 적용한다.
const prev = {name:"prev", hello:"world"};
const next = {...prev, name:"next"}; // 전개 연산자 사용하여 name 프로퍼티만 next로 변경
prev === next // false
memo의 shallow compare 동작 방식
메모는 props 를 비교한다고 했다. 기본적으로 우리가 리액트에서 내려주는 props는 객체형태이다.
즉 매 렌더링맘다 새롭게 생성된다는 의미이다. 따라서 props객체 자체를 비교하는 것은 의미가 없음(매번 새로운 객체를 생성하는 것이기 때문에)
그렇다면 비교하는 것은 props객체 안의 각 property들이다. 따라서 리액트는 props 객체 안의 각 property들을 `Object.is(===)` 연산자를 통해서 비교한다. 이 중 하나라도 false가 나올 경우 props가 변경되었다고 판단하여 리렌더링을 수행한다.
Object.is()은 객체의 프로퍼티들이 완전히 일치하더라도 메모리 주소가 다르면 다른 객체로 판단한다. 자세한 내용은 MDN을 참고하면 좋을듯
ref: React.memo의 Object.is 연산 관련 공식 문서 react.dev
'Frontend > WIL😎' 카테고리의 다른 글
[WIL | 원티드 FE 인턴십] 3주차 클린 코드를 위한 피드백 (0) | 2023.08.01 |
---|---|
[TIL] npm install vs npm ci 명령어 차이 (0) | 2023.07.04 |
[WIL | 원티드 FE 인턴십] 1주차 (06.26 ~ 30) 후기 (0) | 2023.07.03 |
[CRA | Vercel] lazy 로드된 컴포넌트 배포 시 react router 동작 오류 (0) | 2023.06.13 |
[server response] 백의 응답값으로 html이 오는 경우 (1) | 2023.06.06 |
- Total
- Today
- Yesterday
- text input pattern
- 틸드와 캐럿
- Prittier
- nvm경로 오류
- ~ ^
- 원티드 3월 프론트엔드 챌린지
- 원티드 프리온보딩 프론트엔드 챌린지 3일차
- 프리온보딩 프론트엔드 챌린지 3월
- 원티드 프리온보딩 FE 챌린지
- reactAPI
- getServerSideProps
- 항해99프론트
- D 플래그
- 항해99추천비추천
- 프리렌더링확인법
- 형제 요소 선택자
- && 셸 명령어
- fs모듈 넥스트
- nvm 설치순서
- grid flex
- float 레이아웃
- tilde caret
- 타입스크립트 DT
- getStaticPaths
- 항해99프론트후기
- 타입스크립트 장점
- 원티드 FE 프리온보딩 챌린지
- 부트캠프항해
- is()
- aspect-ratio
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |