Web Programming/React

React 1. Component 렌더링

안녕하세요, 씨앤텍시스템즈 이나연 연구원입니다.

 

이 글에서는 React를 사용하면서 한 번은 들어본 함수형 컴포넌트와 클래스형 컴포넌트를 만들고 화면에 컴포넌트가 렌더링 되는 과정에 대해 하나씩 알아보겠습니다.

 


이어지는 글

React 1. Component 렌더링

React 2. Emotion을 이용한 React component 스타일링 (with. CSS-in-JS)

React 3. Redux, Redux Toolkit - Global UI Component 만들기

 


목차

  1. 컴포넌트 렌더링
  2. React 컴포넌트의 라이프 사이클
  3. 마무리

 


1. 컴포넌트 렌더링

우선, 리액트는 v18부터 ReactDOM.render를 더 이상 사용하지 않고 createRoot를 사용하여 root 엘리먼트에서 render 메서드를 사용하여 화면을 렌더링 합니다.

// Before (~v17.x.x)
import { render } from 'react-dom';
const container = document.getElementById('app');
render(<App />, container);

// After (v18.0.0 ~)
import { createRoot } from 'react-dom/client';
const container = document.getElementById('app');
const root = createRoot(container); // createRoot(container!) if you use TypeScript
root.render(<App />);

(before) render from 'react-dom'
(after v18) createRoot from 'react-dom/client'

참고)
https://ko.reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html#updates-to-client-rendering-apis,
https://github.com/facebook/react/blob/main/CHANGELOG.md#react-dom-client

 

예를 들어, 아래와 같은 컴포넌트를 만들어 화면에 렌더링 하려고 한다고 가정해 볼 수 있습니다.

function ListItems({ items }) {
  return items.map((item) => 
    <li key={item.id}>{item.text}</li>
  );
};

function List(props) {
  return (
    <ul>
      <ListItems items={props.items} />
    </ul>
  );
};

위에서 List 컴포넌트에 listItem이라는 데이터를 items라는 이름으로 전달시켜 렌더링 한다면 아래와 같이 사용할 수 있습니다.

//...
const listItems = [
  { id: 0, text: '0' },
  { id: 1, text: '1' },
  { id: 2, text: '2' },
  { id: 3, text: '3' },
];
const element = <List items={listItems} />;
root.render(element);

결과적으로 위의 화면과 같이 List와 ListItem 컴포넌트가 렌더링 되는데, 공식 문서에서 설명하는 바에 따르면 이 과정에서는 다음과 같은 일이 일어난다고 설명할 수 있습니다.

  1. <List items={listItems} /> 엘리먼트를 인자로 하여 root.render()를 호출합니다.
  2. React는 items={listItems}속성을 사용하여 List 컴포넌트를 호출합니다.
  3. 위에서 작성한 List 컴포넌트는 결과적으로 <ul><li>0</li><li>1</li><li>2</li><li>3</li></ul> 형태의 엘리먼트를 반환합니다.
  4. React DOM은 List 컴포넌트가 반환하는 엘리먼트와 일치하도록 DOM을 업데이트합니다.

 

2. React 컴포넌트의 라이프 사이클

컴포넌트는 이렇게 생성되어 DOM에 추가되었다가 유저의 동작에 따라 업데이트가 되거나 혹은 DOM에서 사라지기도 합니다. 이러한 점에서 React 컴포넌트가 렌더링 되기 위해 준비하는 과정부터 페이지(DOM)에서 사라질 때까지를 React 수명 주기(Life Cycle)라고 부릅니다.

React 컴포넌트의 라이프 사이클을 나타낸 다이어그램,&nbsp;https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/

 

2-1. 클래스형 컴포넌트 라이프 사이클 API

React에서 함수형 컴포넌트가 아닌 클래스형 컴포넌트를 살펴보면 라이프 사이클 API의 메서드들을 소개하고 있습니다. 각 메서드들을 공식 문서에서 잘 설명하고 있는데, 주로 사용되는 생명주기 메서드에서 constructor와 render 메서드를 제외한다면, 하나의 컴포넌트가 생성되고 사라지는 과정에서 주로 사용되는 메서드를 아래와 같이 모아볼 수 있습니다.

  • 마운트
    • componentDidMount() : 컴포넌트가 DOM에 추가된 직후 호출
  • 업데이트
    • componentdidUpdate() : 최초 렌더링을 제외하고 갱신이 일어난 직후 호출
  • 언마운트
    • componentWillUnmount() : 컴포넌트가 DOM에서 제거되기 직전 호출

오류 처리를 제외한 위의 마운트, 업데이트, 언마운트 메서드를 클래스형 컴포넌트 안에서 사용한다면 아래와 같이 작성할 수 있습니다.

만약 componentDidCatch()를 이용한 에러 처리에 대해 알고 싶다면 별도의 공식 문서를 추가로 참고하시기를 권해드립니다.

class List extends React.Component {
  componentDidMount() {
    console.log('%c componentDidMount ', 'background: green;');
  }

  componentDidUpdate() {
    console.log('%c componentDidUpdate ', 'background: blue;');
  }

  componentWillUnmount() {
    console.log('%c componentWillUnmount ', 'background: red;');
  }


  render() {
    return (
      <ul>
        {this.props.items.map((item) => (
          <li key={item.id}>{item.text}</li>
        ))}
      </ul>
    );
  }
}

List 컴포넌트를 위와 같이 클래스를 사용하며 componentDidMount(), componentDidUpdate(), componentWillUnmount()를 추가해 주었습니다. 이 컴포넌트에서 렌더링 할 리스트 아이템을 조절하거나 리스트가 렌더링 될지를 선택하기 위해 ListComtainer 컴포넌트로 감싸줍니다. 이 클래스 컴포넌트가 렌더링 되었을 때 콘솔을 확인해 보면 아래와 같이 componentDidMount()가 호출되었다는 것을 알 수 있습니다.

컴포넌트가 최초 렌더링 되었을 때의 콘솔 로그

콘솔을 초기화한 후, 화면의 Add list item 버튼을 클릭한 경우에는 아래와 같이 componentDidUpdate()가 호출되었음을 알 수 있습니다.

Add list item 버튼을 클릭 후 콘솔 로그

마찬가지로 콘솔을 초기화한 후 Unmount list component 버튼을 클릭하여 List 컴포넌트를 사라지게 했을 때, 두 번째 그림과 같이 componentWillUnmount() 가 호출되었음을 알 수 있습니다.

class ListContainer extends React.Component {
// ...

// unmount handler
	handleUnmount = () => {
    this.setState({
      show: false,
    });
  };

  render() {
    const { show, items } = this.state;
    return (
      <>
        {show && <List items={items} />}
        <button onClick={this.handleUpdate}>Add list item</button>{' '}
        <button onClick={this.handleUnmount}>Unmount list component</button>
      </>
    );
  }
}

Unmount list component 버튼을 클릭했을 때의 콘솔 로그

 

2-2. 함수형 컴포넌트 라이프 사이클 구현하기

위에서 작성한 컴포넌트를 함수형 컴포넌트로 작성해 보겠습니다. useEffect 훅에 대한 기본적인 설명은 공식 문서를 참고하시기를 바랍니다.

 

먼저 1번 내용에서 생성한 List 컴포넌트를 사용하여 listItems를 리스트 형태로 렌더링하고 마찬가지로 listItems를 업데이트하고 리스트 컴포넌트 렌더링 여부를 바꿀 수 있도록 ListContainer 컴포넌트를 만들어 줍니다.

function ListContainer() {
  const [show, setShow] = React.useState(true);
  const [items, setItems] = React.useState(listItems);

  const handleUpdate = () => {
    setItems([...items, { id: items.length, text: String(items.length) }]);
  };
  const handleUnmount = () => {
    setShow(false);
  };

  return (
    <>
      {show && <List items={items} />}
      <button onClick={handleUpdate}>Add list item</button>{' '}
      <button onClick={handleUnmount}>Unmount list component</button>
    </>
  );
}

 

그다음 List 컴포넌트에 컴포넌트의 렌더링 여부와 업데이트가 되었을 때를 감지할 수 있도록 useEffect 훅을 컴포넌트 안에 추가해 줍니다.

function List({ items }) {
	//1️⃣ component mount & unmount
  React.useEffect(() => {
    console.log('%c componentDidMount ', 'background: green;');

    return () => {
      console.log('%c componentWillUnmount ', 'background: red;');
    };
  }, []);

	//2️⃣ component update
  React.useEffect(() => {
    console.log('%c componentDidUpdate ', 'background: blue;');
		console.log(items);
  }, [items]);

  return (
    <ul>
      <ListItem items={items} />
    </ul>
  );
}

useEffect의 두 번째 인자로 의존성 배열을 전달할 때, 1️⃣처럼 빈 배열을 전달합니다. 두 번째 인자로 아무것도 전달하지 않고 첫 번째 인자의 함수만 전달하게 되면 컴포넌트가 마운트 되고 업데이트될 때마다 항상 호출되게 됩니다.

반대로, 2️⃣처럼 첫 번째 인자로 작성한 effect가 prop나 state의 값 중에 어떤 값에 의존하고 있는지 의존성 배열에 추가하면, 컴포넌트가 처음 마운트 될 때와 의존성 배열에 입력한 특정한 값이 바뀔 때마다 effect 함수를 호출하게 됩니다.

 

*(참고)전체 코드 https://codesandbox.io/s/component-rendering-rn6wjt

 

3. 마무리

지금까지 React의 컴포넌트를 생성하고 렌더링되도록 만들어봤습니다. 여기까지 읽으셨다면 느끼셨겠지만 클래스형 컴포넌트의 라이프 사이클 API와 함수형 컴포넌트의 effect 훅은 똑같다고 할 수 없습니다. 클래스에서 사용하던 라이프 사이클 API와 비슷하게 함수형 컴포넌트에서도 라이프 사이클에 따라 컴포넌트를 렌더링 하는 것 이외의 부수적인 작업을 실행할 수 있도록 만들어졌다고 이해하면 될 것 같습니다.

useEffect 훅을 사용하는 더 자세한 방법은 공식 문서의 내용을 우선 참고하시기를 권해드립니다.

 

 

Hook의 개요 – React

A JavaScript library for building user interfaces

ko.reactjs.org

 

 

Hook 자주 묻는 질문 – React

A JavaScript library for building user interfaces

ko.reactjs.org


(참고 자료)

728x90