Home 중요하지만 헷갈리는 리엑트 개념 이해하기
Post
Cancel

중요하지만 헷갈리는 리엑트 개념 이해하기

상태값과 속성값으로 관리하는 UI 데이터

UI 라이브러리인 리엑트는 UI 데이터를 관리하는 방법을 제공한다. UI 데이터는 컴포넌트 내부에서 관리되는 상탯값, 부모 컴포넌트에서 내려 주는 속성값으로 구성된다.

UI 데이터가 변동되면 돔 요소를 직접 수정해야한다. 그렇게 되면 비지니스 로직과 UI 수정 코드가 뒤섞여 복잡해 지기 때문에 리엑트에서는 화면을 그리는 모든 코드를 컴포넌트 함수에 선언형을 작성하도록 했다.


컴포넌트의 속성값과 상탯값

컴포넌트의 상탯값은 해당 컴포넌트가 관리하는 데이터이고, 컴포넌트의 속성 값은 부모 컴포넌트로부터 전달받은 데이터다.

리엑트에서 UI 데이터는 반드시 상탯값과 속성값으로 관리해야 한다. UI 데이터를 상탯값, 속성값으로 관리하지 않으면 UI 데이터가 변경돼도 화면이 갱신되지 않을 수 있다.

컴포넌트 상탯값 예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 컴포넌트의 상탯값을 사용하지 않은 코드 */
let color = "red";

function MyComponent() {
  function onClick() {
    color = "blue";
  }

  return (
    <button style= onClick={onClick}>
      버튼
    </button>
  )
}

버튼을 누르게 되면 color 데이터는 ‘blue’로 변경되지만 리엑트는 UI 데이터가 변경된 것을 모르기 때문에 여전히 ‘red’로 나온다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* 컴포넌트의 상탯값을 사용하는 코드 */
import React, {useState} from "react";

function MyComponent() {
  const [color, setColor] = useState("red");

  function onClick() {
    setColor("blue");
  }

  return (
    <button style= onClick={onClick}>
      버튼
    </button>
  )
}

컴포넌트에 상탯값을 추가하기 위해서는 useState([초기값]) 훅을 사용한다. useState 변환하는 배열은 [상탯값, 상탯값 변경 함수]로 이루어져있다.

컴포넌트 속성값 예제

속성값은 부모 컴포넌트가 전달해 주는 데이터이고, 대부분의 경우 UI 데이터를 포함한다.

1
2
3
4
5
/* 속성값을 이용한 코드 */
function Title(props) {
  // 부모로 부터 받은 title
  return <p>{props.title}</p>
}

Title 컴포넌트는 부모 컴포넌트가 랜더링될 때마다 같이 랜더링되므로 title 속성값의 변경 사항이 바로 화면에 반영된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* 부모 컴포넌트에서 속성값을 내려주는 코드 */
function Todo() {
  const [count, setCount] = useState(0);

  function onClick() {
    setCount(count + 1);
  }

  return (
    <div>
      <Title title={'현재 카운트: $(count)}'}/>
      <button onClick={onClick}>증가</button>
    </div>
  )
}

버튼 클릭할 때마다 count 상탯값을 변경하고, Todo 컴포넌트는 다시 랜더링되는데, 이 때 Title 컴포넌트는 새로운 title 속성값을 받는다.

즉, Title 컴포넌트는 부모 컴포넌트가 랜더링 될 때마다 같이 랜더링된다. 만약 title 속성값이 변경될 때만 랜더링을 원하면 React.memo 를 이용할 수 있다.

1
2
3
4
5
6
/* React.memo를 사용한 코드 */
function Ttile(props) {
  return <p>{props.title}</p>
}

export default React.memo(Title);

memo 함수의 인수로 컴포넌트를 입력하면, 컴포넌트의 속성값이 변경되는 경우에만 랜더링 된다.

1
2
3
4
5
6
7
8
9
/* 사용한 컴포넌트 별로 관리되는 상탯값 */
function App() {
  return (
    <div>
      <MyComponent/>
      <MyComponent/>
    </div>
  )
}

같은 컴포넌트는 여러 번 사용할 수 있다. 각 컴포넌트는 상탯값을 위한 자신만의 메모리 공간이 있어서 같은 컴포넌트라도 자신만의 상탯값이 존재한다.

불변 객체로 관리하는 속성값과 상탯값

1
2
3
4
5
/* 속성값 변경을 시도하는 코드 */
function Title(props) {
  props.title = 'abc';
  // ...
}

자식 컴포넌트에 전달되는 속성값은 상위 컴포넌트에서 관리하기 때문에 수정하지 못하도록 막혀 있다. 수정하고 싶다면 title 상탯값을 가진 컴포넌트에서 관리하는 상탯값 변경 함수를 이용해야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
/* 상탯값을 직접 수정하는 코드 */
function MyComponent() {
  const [count, setCount] = useState({value: 0});

  function onClick() {
    count.value = 2;
    //...
    setCount(count);
  }

  //...
}

상탯값을 직접 수정할 수는 있지만 화면이 갱신되지 않는다. 리엑트는 아직 상탯값이 변경된 사실을 모른다. 상탯값 변경 함수를 호출해도 화면은 갱신되지 않는다.

리엑트는 상태값 변경 유무를 이전 값과의 단순 비교로 판단하기 때문에 count 객체 참조값은 그대로이므로 변경 사항이 없다고 판단하고 해당 요청을 무시한다.

따라서 상태값도 속성값과 같이 불변 변수로 관리하는 게 좋다. 불변 변수로 관리하면 코드의 복잡도가 낮아지는 장점도 있다.

컴포넌트 함수의 반환값

1
2
3
4
/* 컴포넌트 함수에서 조건부 랜더링을 하는 코드 */
function MyComponent({title}) {
  return title.length > 0 && <p>{title}</p>;
}

title 속성값의 길이가 0이면 false 반환하여 랜더링이 되지 않고 true의 경우 p요소가 반환되도록 할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
/* 리액트 포털을 시용한 코드 */
function Modal({title, desc}) {
  const domNode = document.getElementById('modal');
  return ReactDOM.createPortal(
    <div>
      <p>{title}</p>
      <p>{desc}</p>
    </div>,
    domNode
  )
}

리엑트 포탈을 이용해서 특정 돔 요소에 리엑트 요소를 랜더링할 수 있다. Modal 컴포넌트가 사용된 위치와 상관없이 랜더링할 위치를 선택할 수 있다.


리엑트 요소와 가상 돔

리액트 요소(element)는 리엑트가 UI를 표현하는 수단이다.

  1. 리엑트는 랜더링 성능을 위해 가상 돔을 활용한다.
  2. 브라우저에서 돔을 변경하는 것은 비교적 오래 걸리는 작업이다.
  3. 따라서 빠른 랜더링을 위해서는 돔 변경을 최소화해야 한다.
  4. 리엑트는 메모리에 가상 돔을 올려 놓고 이전과 이후의 가상 돔을 비교해서 변경된 부분만 실제 돔에 반영하는 전략을 채택

리엑트 요소 이해하기

JSX 문법으로 작성된 코드는 리엑트의 createElement 함수로 변경된다. 이름에서 알 수 있듯이 createElement 함수는 리엑트 요소를 반환한다. 따라서 대부분의 경우 컴포넌트 함수는 리액트 요소를 반환한다.

1
2
3
4
5
6
7
/* JSX 코드가 createElement 함수를 사용하는 코드로 변경된 예 */
const element = <a href="http://google.com">click here</a>; // 사용 전
const element = React.createElement(                        // 사용 후
  'a',
  {href: 'https://google.com'},
  'click here',
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* 리엑트 요소의 구성 */
const element = (
  <a key="key1" style= href="https://google.com">
    click here
  </a>
);// createElement 함수가 반환한 리엑트 요소

const consoleLogResult = {
  type: 'a',// HTML 태그
  key: 'key1',// JSX코드에서 입력한 key 속성값
  ref: null,// JSX코드에서 입력한 ref 속성값
  props: {// key, ref 를 제외한 나머지 속성값
    href: 'https://google.com',
    style: {
      width: 100,
    },
    children: 'clock here',
  },
  //...
};// 리엑트 요소를 로그로 출력한 결과
1
2
3
4
5
6
7
8
9
10
11
/* JSX 코드에서 태그 사이에 표현식을 넣은 코드 */
const element = <h1>제 나이는 {20 + 5} 세 입니다</h1>;

const consoleLogResult = {
  type: 'h1',
  props: {
    // 표현식을 기준으로 분할됨
    children: ['제 나이는', 25, ' 세입니다']
  },
  //...
};// 리엑트 요소를 로그로 출력한 결과
1
2
3
4
5
6
7
8
9
10
11
function Title({title, color}) {
  return <p style=>{title}</p>
}

const element = <Title title="안녕하세요" color="blue"/>;

const consoleLogResult = {
  type: Title, // JSX에서 사용된 Title 컴포넌트 type 속성값에 입력
  props: {title: '안녕하세요', color: 'blue'},
  //...
}
This post is licensed under CC BY 4.0 by the author.