|
| 1 | +# React Optimizing Performance |
| 2 | + |
| 3 | +해당 내용은 리액트 공식 문서 - 고급안내서 읽기 스터디를 통해 정리한 내용입니다. 주관적인 견해나 외부 자료에 의해 추가된 내용은 볼드체로 추가해서 남기겠습니다. |
| 4 | + |
| 5 | +우선 문서를 읽을 때 한국어로 해석된 문서를 읽게되면 다소 의미가 다르게 전달될 때가 있어서 이번 최적화관련 내용은 영문 문서를 토대로 읽고 정리해보겠습니다. |
| 6 | + |
| 7 | +# 개인적인 견해 |
| 8 | + |
| 9 | +우선, 항상 최적화는 중요합니다. 면접을 할 때 대부분의 면접관인 시니어 엔지니어들은 기본적인 질문이 어느정도 되면 최적화에 대한 경험에 대해 물어봅니다. **만약 어플리케이션에 대한 구체적인 최적화 경험이 없다면 추상적인 답변만 늘어 놓을 것입니다.** |
| 10 | + |
| 11 | +사실 실제 현업 경험이 없다면 최적화의 필요성을 느낄만큼 큰 사이즈의 어플리케이션을 만들 일이 없기 때문에 없는 것이 당연할 것입니다. 하지만 현업에 바로 투입될 때 어떤 문제들이 있을지 예상해보면 한번 기억해두면 좋을 내용들을 찾아서 정리했습니다. |
| 12 | + |
| 13 | +# React Advanced guides - Optimizing Performance |
| 14 | + |
| 15 | +이미 리액트 **내부적으로** UI를 업데이트하기 위해 필요한 DOM 관련 작업들의 수를 최소화하려고 하는 몇가지 기술들이 적용되어있습니다. (특히 Virtual DOM) |
| 16 | + |
| 17 | +리액트를 사용하는 것만으로 성능을 최적화하기 위해 많은 작업없이 빠른 UI를 제공할 수 있습니다. 그럼에도 불구하고 **우리 리액트 앱의 속도를 높여줄 수 있는 몇가지 방법**들이 더 있습니다. |
| 18 | + |
| 19 | +## Production Build 사용하기 |
| 20 | + |
| 21 | +만약 리액트 앱에서 성능 문제를 겪고 있다면, production build로 테스트하고 있는지 확인해야합니다. |
| 22 | + |
| 23 | +기본적으로 리액트는 유용한 경고 메세지들을 많이 포함합니다. (예를 들어 hook은 조건적으로 쓸수없다는 경고와 같이) |
| 24 | + |
| 25 | +이러한 메세지는 개발과정에서는 매우 유용합니다. 하지만 이러한 기능은 리액트를 더 크고 느리게 만듭니다. 그래서 우리는 배포단계에서는 production 버젼의 리액트 라이브러리를 사용하는 것을 확실히해야합니다. |
| 26 | + |
| 27 | +우리가 리액트 pruduction react인지 쉽게 확인하는 방법은 Chrome의 React Developer Tools Extension을 설치해서 확인할 수 있습니다. |
| 28 | + |
| 29 | +- CRA(Create React App) 을 사용해서 프로젝트를 구성했다면 보다 쉽게 이미 설정되어있는 `npm run build`를 이용하면 배포용 리액트 형태로 제공되게 됩니다. |
| 30 | + |
| 31 | +- CDN에서 제공하는 single file build를 이용할 수도 있습니다. |
| 32 | + |
| 33 | + ```html |
| 34 | + <script src="https://unpkg.com/react@17/umd/react.production.min.js"></script> |
| 35 | + <script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script> |
| 36 | + ``` |
| 37 | + |
| 38 | +### Brunch |
| 39 | + |
| 40 | +문서에서는 Webpack이 아닌 Brunch라는 도구에 대해서도 설명하지만 거의 사용할 일이 없기 때문에 간단히 넘어가겠습니다. 간단한 빌드 스크립트 툴이라고 생각하면 편하실 것 같습니다. |
| 41 | + |
| 42 | +### Browserify |
| 43 | + |
| 44 | +Browserify 도 마찬가지로 번들러 도구입니다. 요샌 거의 웹팩위주의 개발환경이 표준같아서 따로 학습하지않고 필요할 때 다시보는 방향으로 공부를 진행해보겠습니다. |
| 45 | + |
| 46 | +### Rollup |
| 47 | + |
| 48 | +이하 동문. |
| 49 | + |
| 50 | +### Webpack |
| 51 | + |
| 52 | +Webpack 4 버젼 이상에서는 `proudction` 모드에 기본적으로 우리의 코드를 최소화 해줍니다. [관련 링크](https://webpack.js.org/plugins/terser-webpack-plugin/#getting-started) |
| 53 | + |
| 54 | +그 이하버젼에서는 production 모드일 때 아래와 같은 plugin을 추가해줘야합니다. |
| 55 | + |
| 56 | +```javascript |
| 57 | +const TerserPlugin = require("terser-webpack-plugin"); |
| 58 | + |
| 59 | +module.exports = { |
| 60 | + mode: "production", |
| 61 | + optimization: { |
| 62 | + minimizer: [ |
| 63 | + new TerserPlugin({ |
| 64 | + /* additional options here */ |
| 65 | + }), |
| 66 | + ], |
| 67 | + }, |
| 68 | +}; |
| 69 | +``` |
| 70 | + |
| 71 | +문서에서 계속 언급되는 `terser` 라이브러리는 자바스크립트 파서이며 mangler/compressor 도구 입니다.(ES6 이상) |
| 72 | + |
| 73 | +해당 내용은 웹팩 공식 문서에서 Mode 설정과 관련된 내용에 명시되어있습니다. [웹팩 Mode 설정](https://webpack.js.org/guides/production/#specify-the-mode) |
| 74 | + |
| 75 | +> with `process.env.NODE_ENV` set to `'production'` they might drop or add significant portions of code to optimize how things run for your actual users. |
| 76 | +
|
| 77 | +> 실제 유저들을 위해 최적화를 위해 특정 코드를 추가하거나 제거합니다. |
| 78 | +
|
| 79 | +# DevTools Profiler를 이용해서 컴포넌트 Profiling 하기 |
| 80 | + |
| 81 | +`react-dom` 16.5+ 와 `react-native`0.57+ 는 개발 모드에서 profiling 기능을 강화해서 제공해줍니다. Profileer에 대한 개요는 [해당 글](https://reactjs.org/blog/2018/09/10/introducing-the-react-profiler.html)에서 찾아볼 수 있습니다. **최근 리액트 네이티브를 공부하기 시작했는데 적용해볼만하겠네요.** |
| 82 | + |
| 83 | +# Virtualize Long Lists |
| 84 | + |
| 85 | +긴 리스트를 가상화해라? 무슨 뜻인지 모르니 내용을 살펴보겠습니다. 만약 어플리케이션이 많은 데이터를 나열한다면(백개 혹은 천개의 행), "windowing" 이라고 알려져있는 기술을 사용하는 것을 추천한다고 합니다. |
| 86 | + |
| 87 | +해당 기술은 특정 시점에 전체 row 중 일부분만 랜더링합니다. 즉 보이는 부분만 렌더링하는거죠. 일종의 lazy-rendering 라고 볼 수 있겠네요. 이러한 기술은 컴포넌트를 리렌더링하는 시간을 극적으로 줄일 수 있습니다. |
| 88 | + |
| 89 | +특히`react-window`와 `react-virtualized` 는 인기있는 windowing 라이브러리입니다. Lists, grid, 테이블형태 데이터를 보여주는 재사용 컴포넌트를 제공해줍니다. 물론 우리의 windowing component를 생성할 수 있습니다. (마치 트위터 같이) |
| 90 | + |
| 91 | +### react-window 맛만 보자. |
| 92 | + |
| 93 | +https://react-window.vercel.app/#/examples/list/fixed-size |
| 94 | + |
| 95 | +```jsx |
| 96 | +import { FixedSizeList as List } from "react-window"; |
| 97 | + |
| 98 | +const Row = ({ index, style }) => <div style={style}>Row {index}</div>; |
| 99 | + |
| 100 | +const Example = () => ( |
| 101 | + <List height={150} itemCount={1000} itemSize={35} width={300}> |
| 102 | + {Row} |
| 103 | + </List> |
| 104 | +); |
| 105 | +``` |
| 106 | + |
| 107 | +위와 같이 `List` 라는 컴포넌트가 windowing을 제공해주고 있는 듯합니다. |
| 108 | + |
| 109 | +https://addyosmani.com/blog/react-window/ |
| 110 | + |
| 111 | +리액트 윈도우를 이용해서 여러 성능 측정을 하는 블로그입니다. |
| 112 | + |
| 113 | +내부적인 동작 원리는 좀더알아봐야할 것 같습니다. 아마 사용자에게 보여지는 viewport를 통해 특정컴포넌트가 보여지는지 확인해서 처리하는 방식을 사용하지않을까 싶습니다. |
| 114 | + |
| 115 | +Intersaction Observer와 같은 기술도 있으니 충분히 그럴 듯해보입니다. |
| 116 | + |
| 117 | +# Reconcilation 피하기 |
| 118 | + |
| 119 | +이번 주제는 거의 리액트의 꽃이라고도 볼 수 있는 내용입니다. |
| 120 | + |
| 121 | +우선 reconcilation은 조정이라는 의미인데 따로 챕터가 있습니다. 간단히 리액트에서 상태 변화에 따른 UI 변화를 reconcilation이라고 이해해도 괜찮을 것 같습니다. |
| 122 | + |
| 123 | +### Reconcilation |
| 124 | + |
| 125 | +해당 행위를 피해라?는 의미는 상태가 변할때마다 component트리에 속한 컴포넌트들이 모두 리렌더링이 발생한다면 성능에 문제가 발생하기 때문에 최적화가 필요하다는 의미인 것 같습니다. |
| 126 | + |
| 127 | +### Virtual DOM을 이용해서 Reconcilation을 피한다. |
| 128 | + |
| 129 | +> React builds and maintains an internal representation of the rendered UI. It includes the React elements you return from your components |
| 130 | +
|
| 131 | +리액트는 렌더링된 UI의 내부 표현을 빌드하고 유지한다. 우리의 컴포넌트들로부터 반환된 React Element들을 포함한다. |
| 132 | + |
| 133 | +실제적으로 보여지는 DOM 객체 이외에 **또 다른 DOM**을 내부적으로 관리한다는 뜻으로 받아들이면 이해하기가 수월합니다. (가상 DOM을 의미합니다.) |
| 134 | + |
| 135 | +이러한 가상 DOM은 React가 필요이상으로 DOM 노드를 만들거나 존재하는 DOM을 변경하는 것을 피하게 해줍니다. |
| 136 | + |
| 137 | +> 이 내용은 리액트 네이티브도 동일하게 적용됩니다. |
| 138 | +
|
| 139 | +알고리즘의 메모이제이션의 원리를 이용하는 것과 유사합니다. 변경이 필요한 부분만 변경하는 방식이죠. |
| 140 | + |
| 141 | +컴포넌트 props나 상태가 변할 때마다, 리액트는 이전에 렌더링됐던 DOM과 새로 생성된 element(VDOM)과 비교해서 업데이트를 할지 결정을 합니다. 같지 않다면 DOM 업데이트를 진행합니다. |
| 142 | + |
| 143 | +### 변화되었지만 변화시키지 않겠다! |
| 144 | + |
| 145 | +비록 리액트가 오직 변화된 DOM 노드만 업데이트 하더라도 리렌더링이 될 때까지 시간이 좀더 걸립니다. (비교작업 + 리렌더링) |
| 146 | + |
| 147 | +대부분의 경우 문제가 안되지만 느려짐이 느껴지다면 `shouldComponentUpdate` 생명주기 함수를 오버라딩해서 속도를 올릴 수 있습니다. `shouldComponentUpdate`는 리렌더링 처리를 시작하기전에 발생합니다. |
| 148 | + |
| 149 | +해당 method의 기본 동작은 true를 반환합니다. |
| 150 | + |
| 151 | +만약 컴포넌트 업데이트가 필요없는 상황을 안다면, `shouldComponentUpdate`에서 false를 반환해서 전체 렌더링 절차를 생략할 수 있습니다. (해당 컴포넌트의 `render()`도 포함) |
| 152 | + |
| 153 | +대부분의 경우, `shouldComponentUpdate()`를 직접 사용하기 보다는 `React.PureComponent`를 상속해서 사용할 수 있습니다. |
| 154 | + |
| 155 | +함수형 컴포넌트를 작성하신다면`useMemo`와 `React.memo`를 사용해서 PureComponent와 동일한 역할로 처리할 수 있습니다. |
| 156 | + |
| 157 | +리렌더링을 최소화하는 방식에는 위에서 말했듯이 |
| 158 | + |
| 159 | +- `shouldComponentUpdate` |
| 160 | +- `React.PureComponent` |
| 161 | +- `useMemo` + `React.memo` |
| 162 | + |
| 163 | +위 3가지 정도로 정리할 수 있을 듯합니다. |
| 164 | + |
| 165 | +# 데이터를 Mutating 하지않는 것의 힘. |
| 166 | + |
| 167 | +(정리중...) |
0 commit comments