1. 렌더링 (Rendering)
1-1. 렌더링이란?
- 렌더링
- 렌더링이란 화면에 특정한 요소를 그려내는 것을 의미한다. 사실 이 렌더링 과정을 잘 처리해주는 것이 우리가 Vanila JavaScript를 사용하지 않고 React 같은 UI 라이브러리 또는 프레임워크를 사용하는 이유다. - 브라우저에서 렌더링
- DOM요소를 계산하고 그려내는 것을 의미한다. HTML과 CSS를 통해서 만들어지고 계산된 DOM과 CSSOM은 결합되고, 위치를 계산하고, 최종적으로 브라우저에 그려진다. 그리고 우리는 브라우저에서 제공하는 DOM API를 JavaScript를 통해 호출하면서 브라우저에 그려진 화면을 변화시킨다. - 명령형과 선언형
- Vanila JavaScript를 이용해서 DOM에 직접 접근하고 수정하는 것 (명령형), 그리고 이를 최적화 하는 것은 애플리케이션의 규모가 커지면 커질수록 관리하기 힘들어진다. 그래서 개발자들은 애플리케이션에서 보여주고 싶은 핵심 UI를 선언 하기만 하면 실제로 DOM을 조작해서 UI를 그려내고, 변화시키는 일은 라이브러리나 프레임워크가 대신 해주는 방식을 찾게 된다. (선언적 개발)
이런 니즈에 맞춰서 React, Vue, Angular등의 라이브러리, 프레임워크가 등장하게 되고 그 중에서 React가 현재는 가장 많이 사용되고 있는 것이다. 실제로 React 공식문서를 보면 가장 첫번째 장점으로 선언형 을 내세우고 있다.
이처럼, React는 선언형으로 실제 렌더링 과정은 React에서 대신 처리해주고, 개발자는 UI를 설계하는대만 집중하게 해준다. 하지만 때로는 React 내부에서 처리해주는 렌더링을 최적화 해야 되는 상황이 발생한다. 이러한 상황에서는 React 내부에서 렌더링이 언제 발생하는지, 어떤 과정을 거쳐서 이루어지는지를 이해하고 있어야 각 과정에서 렌더링을 최적화 할 수 있다.
1-2. 리액트에서 렌더링이 되는 시점
- 리액트에서 리렌더링은 언제 발생할까? ▶ 근본적으로 리액트에서 state를 왜 사용할까?
- 리액트에서 state를 사용하는 이유는 UI와 상태(state)를 연동시키기 위해서다. UI는 어떠한 데이터가 있고 그것을 보기 편한 형태로 표현한 것이다. 리액트는 이를 이해하고 UI와 연동되어야 하고, 변할 여지가 있는 데이터들을 state라는 형태로 사용할 수 있게 해준다. 그리고 데이터가 변경되었을 때 UI가 그에 맞춰서 변화하기 위해서 state를 변경시키는 방법을 제한시키고(setState), 이 함수가 호출 될 때 마다 리렌더링이 되도록 설계되었다.
- 이런 이유로 인해서 리액트에서 리렌더링이 발생하는 시점은 state가 변했을 때다. 특정 컴포넌트의 state가 변한다면, 해당 컴포넌트와 해당 컴포넌트의 하위에 있는 모든 컴포넌트들은 리렌더링이 발생하게 된다.
즉, “state가 변하면 해당 컴포넌트를 포함한 하위 컴포넌트들은 모두 리렌더링 된다.” 라는 핵심 개념을 이해하고 있는 것이 리액트를 이용해서 애플리케이션을 설계하고, 최적화하는데 가장 기본이 되는 사항이라고 할 수 있다.
1-3. 리액트의 렌더링 과정
- 리액트는 state가 변화했을 때 리렌더링을 발생시킨다고 했다. 이 과정을 좀 더 뜯어보면 state가 변화되고 최종적으로 브라우저상의 UI에 반영되기까지 각 컴포넌트에서는 크게 아래의 4단계를 거치게 된다.
- 기존 컴포넌트의 UI를 재사용할 지 확인한다.
- 함수 컴포넌트: 컴포넌트 함수를 호출한다 / Class 컴포넌트: render 메소드를 호출한다.
- 2번 과정의 결과를 통해서 새로운 VirtualDOM을 생성한다.
- 이전의 VirtualDOM과 새로운 VirtualDOM을 비교해서 실제 변경된 부분만 DOM에 적용한다.
- 그렇다면 4번의 과정을 왜 하는지, 근본적으로 VirtualDOM을 왜 사용하는지에 대해서
브라우저는 근본적으로 화면을 보여주기 위해서 HTML, CSS, JavaScript를 다운로드 받고 그를 처리해서 화면에 픽셀 형태로 그려낸다. 그리고 이 과정을 CRP(Critical Rendering Path)라고 부른다.
Critical Rendering Path는 기본적으로 아래의 과정을 수행
- HTML을 파싱해서 DOM을 만든다.
- CSS를 파싱해서 CSSOM을 만든다.
- DOM과 CSSOM을 결합해서 Render Tree를 만든다.
- Render Tree와 Viewport의 witdh를 통해서 각 요소들의 위치와 크기를 계산한다.(Layout)
- 지금까지 계산된 정보를 이용해 Render Tree상의 요소들을 실제 Pixel로 그려낸다. (Paint)
이후 DOM 또는 CSSOM이 수정될 때 마다 위의 과정을 반복한다. 따라서 이 과정을 최적화 하는 것이 퍼포먼스상에 중요한 포인트이다. 그런데 위 과정중에서 Layout, Paint 과정은 특히나 많은 계산을 필요로하는 부분이다. 따라서 리액트는 이 CRP이 수행되는 횟수를 최적화 하기 위해서 VirtualDOM을 사용하는 것 이다.
UI를 변화하기 위해서는 많은 DOM 조작이 필요하다. 하나하나의 DOM조작마다 CRP가 수행될 것이고 이는 곧 브라우저에게 많은 연산을 요구하게 되고, 퍼포먼스를 저하시키는 요인이 될 수 있다. 리액트는 이를 해결하고자 VirtualDOM이란 개념을 도입한 것이다.
리액트에서는 UI의 변화가 발생하면 변화에 필요한 DOM조작들을 매번 바로 실제 DOM에 적용하는 것이 아니라, VirtualDOM이란 리액트가 관리하고 있는 DOM과 유사한 객체형태로 만들어낸다. 그리고 이전의 VirtualDOM과 새로운 VirtualDOM을 비교해서 실제로 변화가 필요한 DOM요소들을 찾아낸다. 그 다음에 한번에 해당 DOM요소들을 조작한다.
이 처리를 통해서 브라우저에서 수행되는 CRP의 빈도를 줄일 수 있고 이게 VIrtualDOM을 이용해서 리액트가 수행하는 최적화이다. 즉, 4. 이전의 VirtualDOM과 새로운 VirtualDOM을 비교해서 실제 변경된 부분만 DOM에 적용한다. 에 해당하는 최적화는 리액트 내부적으로 수행하고 있다는 의미이고 이부분은 리액트를 사용하는 개발자입장에서는 따로 최적화를 수행할 여지가 없다.
리액트를 사용하는 개발자가 할 수 있는 최적화는
1. 기존 컴포넌트의 UI를 재사용할 지 확인한다.
3. 2의 결과를 통해서 새로운 VirtualDOM을 생성한다.
위 두부분에 해당하는 최적화이다.
좀 더 자세히 말하자면 1. 기존 컴포넌트의 UI를 재사용할 지 확인한다. 의 경우에는 만약 리렌더링 될 컴포넌트의 UI가 이전의 UI와 동일하다고 판단되는 경우 새롭게 컴포넌트 함수를 호출하지 않고 이전의 결과값을 그대로 사용하도록 함으로서 최적화를 수행할 수 있다.
또한 3. 2의 결과를 통해서 새로운 VirtualDOM을 생성한다. 의 경우는 컴포넌트 함수가 호출되면서 만들어질 VirtualDOM의 형태를 비교적 차이가 적은 형태로 만들어지도록 하는 것이다.
이번 포스팅에서는 1. 기존 컴포넌트의 UI를 재사용할 지 확인한다. 에 해당하는 부분을 집중적으로 알아보고 이해해보자.
2. React.memo
앞서 말했듯이 리액트는 state가 변할 경우 해당 컴포넌트와 하위의 컴포넌트들을 모두 리렌더링 한다. 그런데 state가 변한 컴포넌트의 경우 당연히 UI의 변화가 있을 것이기에 리렌더링을 해야 하지만, 하위 컴포넌트의 경우에는 경우에는 props가 변화하지 않았다면 해당 컴포넌트가 UI가 변화하지 않았을 수도 있을 것이다. 이런 경우에는 굳이 새롭게 컴포넌트 함수를 호출할 필요 없이 이전에 저장되어 있던 결과를 그대로 사용하는 것이 효율적이다.
하지만 UI가 실질적으로 변화되었는지 안되었는지를 매번 리액트가 렌더링 과정에서 일일이 모든 컴포넌트 트리를 순회하면서 검사하는 것은 비효율적이다. 따라서 리액트에서는 개발자에게 이 컴포넌트가 리렌더링이 되어야 할지 아닐지에 대한 여부를 표현할 수 있는 React.memo 함수를 제공하고 이를 통해 기존의 컴포넌트의 UI를 재사용할 지 판단하는 방법을 채택했다.
2-1. React.memo
const MyComponent = React.memo(function MyComponent(props) {
/* render using props */
});
React.memo는 HOC(Higher Order Component) 다.
HOC란 컴포넌트를 인자로 받아서, 컴포넌트를 리턴하는 컴포넌트다.
function HOC(Component) {
/* do something */
return <Component />
}
React.memo로 감싸진 컴포넌트의 경우에는 상위 컴포넌트가 리렌더링 될 경우 무조건 리렌더링 되는 것이 아니라 컴포넌트의 이전의 Props와 다음 렌더링 때 사용될 Props를 비교해서 차이가 있을 경우에만 리렌더링을 수행한다. 만약 차이가 없다면 리렌더링을 수행하지 않고 기존의 렌더링 결과를 재사용한다. 이를 통해 컴포넌트에서 불필요하게 리렌더링이 되는 경우를 막을 수 있다.
- 중요하게 생각해야 할 React.memo의 props를 비교하는 방식
- React.memo는 기본적으로 props의 변화를 이전 props와 새로운 props를 shallow compare 해서 판단한다. 만약 이 기본적인 비교 로직을 사용하지 않고 비교를 판단하는 로직을 직접 작성하고 싶을 경우를 대비해서 React.memo는 변화를 판단하는 함수를 인자로 받을 수 있도록 설정해두었다.
function MyComponent(props) {
/* render using props */
}
function areEqual(prevProps, nextProps) {
/*
true를 return할 경우 이전 결과를 재사용
false를 return할 경우 리렌더링을 수행
*/
}
export default React.memo(MyComponent, areEqual);
React.memo의 두번째 인자로 함수를 전달할 경우 해당 함수의 인자로는 이전의 props와 새로운 props가 순서대로 인자로 전달되며, 이 함수의 return 값이 true일 경우 이전 결과를 재사용하고, false를 return할 경우 리렌더리을 수행한다.
- shallow compare 참고자료
2-2. 자바스크립트 데이터 타입
React.memo는 기본적으로 props 객체간을 비교하는 방식을 통해서 동작한다. 이를 잘 인식하고 활용하려면 자바스크립트의 데이터 타입, 그중에서도 기본형 타입과 참조형 타입의 차이에 대해서 명확히 알고 있어야 한다.
자바스크립트의 데이터 타입은 string, number, boolean, null, undefined, object 등 다양하게 있습니다. 그리고 이를 크게 두가지로 나누면 기본형 타입과 참조형 타입으로 구분할 수 있다.
- 기본형 타입 (원시형)
- 기본형 타입은 원시형 타입이라는 용어로도 표현
- 자바스크립트에서 지원하는 원시적이고 기본적인 형태의 데이터 타입이며 다른 데이터 없이 해당 데이터 스스로 온전히 존재할 수 있는 형태
- 원시형 타입의 예시에는 string, number, boolean, null, undefined, bigint, symbol - 참조형 타입 (객체형)
- 참조형 타입은 달리말해 객체형 타입이라고도 불린다.
- 즉 원시형 타입을 제외한 Object 가 참조형 타입이라고 할 수 있다(자바스크립트에서는 Array도 Function도 Object의 한 종류)
- 참조형 타입의 가장 큰 특징은 다른 데이터들을 모아서 만들어진 타입이라는 것
const yeonuk = {name: “yeonuk”, gender:”male”}
위의 객체를 보면 이 객체는 “yeonuk”, “male” 이란 두가지 string 타입을 모아서 만들어진 것을 확인할 수 있다.
참조형 타입과 기본형 타입을 생각할 때 고려해야 할 가장 큰 특징은 “불변성" 이다.
2-3. 불변성
불변성이란 값이 변하지 않는 것을 의미한다. 기본적으로 원시형 타입은 모두 불변!
예를들어,
let dog = "tori";
dog = "mozzi"
위 코드에서는 dog에 할당된 “tori”란 string을 “mozzi”란 string으로 변경하는 방식으로 이루어지는 것이 아니라 “mozzi”라는 새로운 string을 만들고, dog에 할당된 값 자체를 교체 하는 식으로 동작한다.
이처럼 자바스크립트에서는 이미 만들어진 원시형 타입을 변경할 수 있는 방법은 없다.
하지만 참조형 타입은 가변한다.
const yeonuk = {name:"yeonuk", gender:"male"};
yeonuk.name = "yeonwook";
객체(참조형 타입)는 여러 타입들을 모아서 만들어진 형태다. 따라서 객체 안의 내용물들은 언제든지, 어떤 형태로든 변경할 수 있다. 이를 객체가 가변한다고 말한다.
- 가변성의 특징
- 메모리를 절약하면서 객체를 유연하게 사용할 수 있게 해줌
- 때때로 결과를 예상하기 힘들다는 단점
- 객체간의 비교가 어렵다는 단점
자바스크립트는 기본적으로 비교연산자를 수행할 때 해당 데이터의 메모리 주소를 통해서 일치 여부를 판단한다. 하지만 원시형 타입의 경우에는 변경할 시 새로운 데이터가 만들어지고 교체되는 방식이기에 메모리 주소가 달라져서 비교연산자를 활용하기 용이하지만, 객체의 경우에는 안의 내용물이 어떻게 바뀌었는지에 상관없이 해당 객체를 가리키는 메모리 주소는 동일하기에 실질적으로 내용이 변했는지를 판단하기는 어렵다. 또한 내용물이 완벽히 일치하는 두 객체를 비교하더라도 각 객체를 가리키는 메모리 주소가 다르기에 두 객체는 동일하지 않다는 결과가 나오게 된다.
"dog" === "cat" // false
const prev = {name:"prev"};
const next = prev;
next.name = "next";
prev === next // true
const one = {hello:"world"};
const two = {hello:"world"};
one === two // false
이런 동작으로 인해서 실제 객체의 내용물이 같은지 판단하기 위해서는 두 객체 안의 모든 property들을 순회하면서 일일이 비교를 해주어야 한다. 만약 property 중에 객체가 존재한다면 또다시 해당 객체를 순회해야 하기에 이 연산을 수행하기 위한 복잡도는 기하급수적으로 늘어난다.
이처럼 객체간의 비교를 하기 힘들다는 점과 과거에 비해 객체를 선언하고 저장하는데 사용할 수 있는 메모리 용량이 늘어났기에 객체를 불변하게 활용하는 방식이 최근에는 많이 사용되고 있다.
- 객체를 불변하게 이용하는 방법
- 객체의 내용이 변해야 할 경우에는 기존의 객체를 수정하지 않고 무조건 새로운 객체를 만드는 방식이다.
const prev = {name:"prev", hello:"world"};
const next = {...prev, name:"next"};
prev === next // false
2-4. memo의 잘못된 활용
앞서 React.memo는 기본적으로 props의 변화를 이전 props와 새로운 props를 shallow compare 해서 판단한다고 했다.
- props를 shallow compare 한다는 의미
- props는 객체 형태로 표현된다. 그리고 props 객체는 매 렌더링마다 새롭게 생성된다. 따라서 props 객체 자체를 비교하는 것은 의미가 없고 비교해야 하는 것은 props객체 안의 각 property들이다. 따라서 리액트는 props 객체 안의 각 property들을 === 연산자를 통해서 비교한다. 이 중 하나라도 false가 나올 경우 props가 변경되었다고 판단하고 리렌더링을 수행한다.
<Component name="foo" hello="world" />
<Component name="bar" hello="world" />
const areEqual = (prevProps, nextProps) => {
if(prevProps.name !== nextProps.name) return false;
if(prevProps.hello !== nextProps.hello) return false;
return true;
}
이러한 동작과 데이터 타입에 대해서 제대로 이해하지 않으면 memo를 잘 못 활용하는 상황이 발생한다.
3. Memoization
Memoization은 특정한 값을 저장해뒀다가, 이후에 해당 값이 필요할 때 새롭게 계산해서 사용하는게 아니라 저장해둔 값을 활용하는 테크닉을 의미한다.
함수 컴포넌트는 근본적으로 함수다. 그리고 리액트는 매 렌더링마다 함수 컴포넌트를 다시 호출한다. 함수는 기본적으로 이전 호출과 새로운 호출간에 값을 공유할 수 없다. 만약 특정한 함수 호출 내에서 만들어진 변수를 다음 함수 호출에도 사용하고 싶다면 그 값을 함수 외부의 특정한 공간에 저장해뒀다가 다음 호출 때 명시적으로 다시 꺼내와야 한다.
이것을 직접 구현하는 것은 꽤나 번거로운 일이고, 특히 함수 컴포넌트에서 이를 구현하고 관리하는 것은 많은 노력이 드는 행위라고 할 수 있다.
다행히 리액트에서는 함수 컴포넌트에서 값을 memorizing 할 수 있도록 API를 제공해준다.
3-1. useMemo
useMemo는 리액트에서 값을 memorizing 할 수 있도록 해주는 함수다.
// useMemo(callbackFunction, deps]
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
useMemo는 두가지 인자를 받는다.
- 첫번째 인자
- 콜백함수이며 이 함수에서 리턴하는 값이 메모라이징 된다. - 두번째 인자
- 의존성 배열이다. 메모라이징을 할 때 주의해야 할 점은 만약 새로운 값을 만들어서 사용해야 하는데 이전의 결과를 그대로 활용해버리면 버그가 발생할 수 있다는 점이다.
위의 예시에서 a, b 라는 두가지 변수를 이용해서 메모라이징 하기 위한 값을 계산하고 있다. 그런데 만약 a, b 라는 값이 변경되었는데 이전의 값을 그대로 활용해버리면 의도한 결과와 다른 결과가 나오게 될 것이다.
이런 상황을 방지하기 위해서 useMemo에서는 의존성 배열을 인자로 받아, 의존성 배열에 있는 값 중 하나라도 이전 렌더링과 비교했을 때 변경되었다면 메모라이징 된 값을 활용하는 것이 아니라 새로운 값을 다시 계산한다.
3-2. useCallback
useCallback은 useMemo를 조금 더 편리하게 사용할 수 있도록 만든 버전이다.
일반적인 값들은 useMemo를 통해서 메모라이징 하기 편리하다. 하지만 함수의 경우에는 useMemo를 사용해서 메모라이징 하게 되면 콜백함수에서 또다른 함수를 리턴하는 형태가 되게 된다. 이는 동작상에는 아무런 이상이 없지만 코드 스타일에 따라 문법적으로 다소 보기가 불편해지는 단점이 있다. 따라서 이러한 동작을 간소화한 useCallback이란 함수를 만들어서 제공해주고 있다.
const memorizedFunction = useMemo(() => () => console.log("Hello World"), []);
const memorizedFunction = useCallback(() => console.log("Hello World"), []);
3-3. 언제 memoization을 해야 할까?
메모이제이션, 개념만 보았을 때는 굉장히 효율적이고 사용하기만 하면 최적화가 이루어질 것 같다. 하지만 명확한 목적없이 메모이제이션을 사용하는 것은 오히려 비효율적이다.
리액트에서 제공하는 메모이제이션 함수를 호출해서 특정한 값을 저장해둘 수 있기에 쓰기만 하면 효율이 올라갈 것 같다. 하지만 메모이제이션을 하기 전 생각해봐야 할 사항이 있다.
- 새로운 값을 만드는 것 vs
이전의 값을 저장해두고 메모라이징 함수를 호출하고 의존성을 비교해서 가져올지 말지 여부를 판단하는 것
과연 어떤 것이 비용이 더 적게 들까?
정답은 상황에 따라 다르다.
- 새로운 값을 만드는 과정이 복잡 ▶ 메모라이징을 사용하는 것이 더 효율적일 수 있다.
- 새로운 값을 만드는 과정이 복잡 X ▶ 메모라이징을 사용하는 것은 오히려 비용이 더 많이 들 수도 있다.
컴퓨터 자원을 소모하는 비용 뿐만 아니라 메모라이징을 쓰면서 코드의 복잡도가 올라간다는 개발적인 측면의 비용도 무시할 수 없다.
따라서 메모라이징은 필요성을 분석하고 필요하다고 판단되는 순간에만 사용해야 한다.
- 리액트에서 메모라이징이 필요하다고 판단할 수 있는 요인
1. 새로운 값을 만드는 연산이 복잡하다.
2. 함수 컴포넌트의 이전 호출과, 다음 호출 간 사용하는 값의 동일성을 보장하고 싶다.
- 1번의 경우
- 만약 10000개의 요소를 가진 배열이 있다고 생각하면 이 배열을 매번 생성하는 것 보다는 메모라이징 해서 활용하는 것이 효율적일 것이다. - 2번의 경우
- 함수 컴포넌트의 호출 간 동일성을 보장하기 위해서다. 동일성을 보장하는 이유는 React.memo 와 연동해서 사용하기 위해서이다.
앞서 memo의 잘못된 활용 예시에서 props로 전달되는 객체(함수를 포함한)의 동일성이 보장되지 않아 실제 객체의 내용은 똑같아도 shallow compare를 통해서 다른 객체라고 판단되어서 매번 리렌더링이 실행되는 상황을 보았다.
이런 상황에서 전달되는 객체의 동일성을 보장하기 위해서 메모이제이션을 활용할 수 있다. 메모이제이션 된 객체는 새롭게 만들어진 것이 아니라 이전의 객체를 그대로 활용하는 것이기에 shallow compare에서 동일함을 보장받을 수 있다.
4. 그래서 언제 최적화를 해야할까?
Aha-moment ! from 프리온보딩 멘토님👨🏫
최적화는 개발자에게 굉장히 매혹적인 주제입니다. 뭔가 일반적인 기능 개발을 하는 것 보다 최적화를 하는 것이 더 어려워보이고, 더 많은 지식이 필요하다고 생각되고, 아무나 할 수 없을 것 같다는 인상을 줍니다.
그리고 이러한 요소들은 도전정신이 있는 개발자에게 매력적인 포인트가 되어서 프로젝트를 최적화 하고 싶다는 욕구를 생기게 하고 본인을 인정받고 싶은 개발자들은 야심차게 기존의 프로젝트에 최적화를 시도하기도 합니다.
하지만, 대부분의 상황에서 기대한대로 "👍"와 같은 반응보다는 "😳" 또는 "🤬" 와 같은 반응을 주위 개발자들이 보일 확률이 높습니다. 항상 명심해야 할 사항이 있습니다.
“최적화는 공짜가 아닙니다” 최적화를 하기 위해서는 최적화를 위한 코드가 프로젝트에 추가되어야 할 수 있고, 이는 프로젝트의 복잡도를 비교적 증가시키게 됩니다. 그리고 최적화를 하기 위한 개발자의 시간과 노력 또한 투입되게 될 것입니다. 이처럼 최적화는 꽤나 비싼 비용을 투자해야 하는 작업입니다.
현업에서의, 실제 필드에서의 개발자는 자기만족을 위해서 개발을 하는 사람이 아닙니다. 현업에 있는 개발자는 개발을 통해서 가치를 창출해내야 합니다. 만약 개발자가 하는 개발이 아무런 가치도 창출해내지 못한다면 절대 좋은 평가를 받을 수 없습니다. 자기의 도전정신, 탐구욕을 채우기 위한 개발은 사이드 프로젝트등을 통해서 수행해야 하지 실제 내가 현업에서 가치를 창출해낼 것을 기대받는 상황에서 하는 것이 아닙니다.
따라서 최적화를 해야 하는 시기는 이 최적화가 명확히 가치를 창출해낼 수 있을 것이라고 기대되는 상황 즉, 현재의 프로젝트에 성능적인 이슈가 발생했거나, 발생할 가능성이 있고 이를 해결해야 될 필요성이 있는 상황에서 수행하는 것입니다. 만약 내가 최적화를 하고 싶다면 현재 상황을 분석해서 최적화를 해야 하는 이유를 정리하고 이를 관련된 사람들에게 알리고 최적화의 필요성에 대한 공감대가 형성시키고 난 후 최적화를 수행해야 합니다.
'Library & Framework > React.js' 카테고리의 다른 글
관심사의 분리 & 리액트에서 관심사를 분리하는 법 (0) | 2022.10.24 |
---|---|
Context API 개념과 사용법 (0) | 2022.10.21 |
useEffect의 Dependency array(의존성 배열) (0) | 2022.10.19 |
클래스형 컴포넌트 vs 함수형 컴포넌트 (0) | 2022.03.28 |