[Optimization] using JSX props

2021. 4. 4. 22:35Frontend

Kent. C Dodds의 'One simple trick to optimize React re-renders'를 Reference 하였습니다.

 

 

 

Overview

React를 사용해서 Web Application을 만들 때, 퍼포먼스의 향상하기 위하여 여러 가지 렌더링 최적화 기법들을 시도합니다. 대표적인 방법이 React.Memo를 사용하여 불필요한 리렌더링을 방지하기, 상태(State)를 업데이트 하는 로직은 최대한 하위 컴포넌트로 내리기등이 있습니다. 이번 포스팅에서는 자주 사용되고, 또 자주 접할 수 있는 위 두 가지 최적화 방법 이외에도 JSX가 가지고 있는 특성을 사용해서 불필요한 리 렌더링을 방지하는 방법을 살펴보려고 합니다.

 

 

 

JSX

 

JSX는 Javascript의 확장 문법으로, React에서 UI가 어떻게 표현될지를 구현합니다. JSX문법으로 다음과 같이 Component를 구현하면, 이는 실제로 아래와 같이 트랜스 파일 됩니다.

// jsx format
function App () {
	return <div>hello world</div>
}

// transpiled
React.createElement('div', {}, 'hello world');

 

React 라이브러리의  createElement함수는  HTML 태그의 형태를 띠는 문법의 코드를 type, props, children의 key를 갖는 Object로 변환하고, 각각의 children들에 대해서 동일한 연산들을 재귀적으로 수행하면서 전체적인 DOM Tree(React만의 Virtual DOM Tree)를 구성합니다. 

 

 

DOM Tree를 구성하는 특정 노드(컴포넌트)에서 상태변화 등의 이유로 리 렌더링이 발생하면 리액트는 이 상태변화가 어떤 자식컴포넌트들을 변화시켜야 하는지 알지 못하므로 모든 자식 컴포넌트들을 리렌더링 합니다. 이는 상태변화가 Root Node와 가까운 쪽에서 일어났을 때에 재귀적으로 리렌더링되어야 하는 노드들의 개수를 기하급수적으로 증가시키며 특정 상황에서는 퍼포먼스에 영향을 줄 수 있는 수준에 이르기도 합니다.

 

 

따라서 이와 같은 경우에는 React.memo 함수를 사용해서 명시적으로 리렌더링 해야 하는 조건을 정해주거나, state update 하는 로직을 최대한 Child Node 쪽으로 옮겨 영향을 받는 Child Node들의 수를 줄이는 방법을 고려할 수 있습니다.

 

 

Using Props

 

다음과 같은 코드가 있다고 했을 때, button을 클릭해서 count를 업데이트하면, <Logger/> 컴포넌트는 counter 상태에 영향을 받지 않음에도 불구하고 Counter Component가 업데이트될 때 React의 렌더링 로직에 의해 무조건 다시 리 렌더링 됩니다. 여기서 리렌더링 된다는 것은 JSX문법에 의해 생성된 Logger Node 객체가 새롭게 다시 생성된다는 것입니다. 이는 잠재적으로 불필요한 리 렌더링(counter 상태변화와는 아무런 관계가 없으므로)을 유발하게 됩니다. 

import * as React from 'react'
import ReactDOM from 'react-dom'

function Logger(props) {
  console.log(`${props.label} rendered`)
  return null
}

function Counter() {
  const [count, setCount] = React.useState(0)
  const increment = () => setCount(c => c + 1)
  return (
    <div>
      <button onClick={increment}>The count is {count}</button>
      <Logger label="counter" />
    </div>
  )
}

ReactDOM.render(<Counter />, document.getElementById('root'))

 

 

트랜스 파일 된 위의 카운터 로직을 단순화하여 살펴보면 다음과 같습니다. Logger가 counter Element의 children으로 생성되기 때문에  Counter가 다시 렌더링 되면 Dependency와 상관없이 Logger가 다시 생성되는 것입니다. 

const counterElement = {
  type: 'div',
  props: {
    children: [
      {
        type: 'button',
        props: {
          onClick: increment,
          children: 'The count is 0',
        },
      },
      {
        type: Logger,
        props: {
          label: 'counter',
        },
      },
    ],
  },
}

 

 

Modified Code

위에서 JSX는 HTML과 같은 문법을 다음과 같은 형식으로 트랜스 파일 한다는 것을 살펴보았습니다.

function createElement(elementType, props, ...children) {}

그렇기 때문에 children은 Component 리 렌더시 React.memo 등으로 별도의 처리를 하지 않는 한 다시 생성되어 렌더링 되게 되는 것입니다. 하지만, props에 특정 Component (혹은 Element)를 넣어준다면, 이는 부모 컴포넌트에서 명시적으로 리 렌더를 하지 않는 한, Component는 이 props를 다시 리 렌더 하지 않습니다. 즉 다음과 같은 구현이 가능하게 되는 것입니다.

 

 

import * as React from 'react'
import ReactDOM from 'react-dom'

function Logger(props) {
  console.log(`${props.label} rendered`)
  return null // what is returned here is irrelevant...
}

function Counter(props) {
  const [count, setCount] = React.useState(0)
  const increment = () => setCount(c => c + 1)
  return (
    <div>
      <button onClick={increment}>The count is {count}</button>
      {props.logger}
    </div>
  )
}

ReactDOM.render(
  <Counter logger={<Logger label="counter" />} />,
  document.getElementById('root'),
)

 

위와 같이 구현하게 된다면, Logger함수는 Counter함수의 Children이 아닌 Props에 포함되기 때문에 counter state 변화에 따른 리 렌더시에도 영향을 받지 않아서 불필요한 리 렌더링을 방지할 수 있게 됩니다. 별도의 함수를 사용하지 않고 JSX의 성질을 사용해서 퍼포먼스를 향상할 수 있는 방법인 것입니다. 

 

Reference

ko.reactjs.org/docs/introducing-jsx.html

 

JSX 소개 – React

A JavaScript library for building user interfaces

ko.reactjs.org

kentcdodds.com/blog/optimize-react-re-renders

 

One simple trick to optimize React re-renders

Without using React.memo, PureComponent, or shouldComponentUpdate

kentcdodds.com

kentcdodds.com/blog/what-is-jsx

 

What is JSX?

You may use it every day, but have you seen what happens after Babel compiles it?

kentcdodds.com

 

반응형

'Frontend' 카테고리의 다른 글

[Webpack] Config file & Asset Modules  (0) 2021.06.11
[Webpack] Introduction & Setup  (0) 2021.06.09
[React] Timer 만들기  (0) 2021.01.06
[Safari] 내 iPhone 브라우저 Inspect하기  (0) 2021.01.04
[React] 클로저와 useState Hooks (2)  (3) 2020.11.03