본문 바로가기

나야, 리액트 스터디

[week2] - 조건부 렌더링, 리스트 렌더링, 컴포넌트를 순수하게 유지하기, 트리로서의 UI

안녕하세요! 웹 35기 한수정입니다.

이번 나리스 2주차 아티클의 주제는 조건부 렌더링, 리스트 렌더링, 컴포넌트를 순수하게 유지하기, 트리로서의 UI입니다. 모든 개념을 아직 완벽히 알고 있진 않아, 하나씩 차근차근 알아보고 정리해보려고 합니다. 조건부 렌더링과 리스트 렌더링은 예시 코드를 중심으로 알아보고 컴포넌트를 순수하게 유지하기와 트리로서의 UI는 공식문서를 중심으로 정리해보겠습니다...😊 나리스 모두 파이팅! 🙌

 

조건부 렌더링

조건부 렌더링(Conditional Rendering)은 특정 조건에 따라 UI 컴포넌트를 화면에 표시하거나 숨기는 방식입니다. React는 if 문, && 및 ?: 연산자와 같은 자바스크립트 문법을 사용하여 조건부로 JSX를 렌더링할 수 있습니다.

조건부 렌더링 구현 방법

1) if 문을 사용한 조건부 렌더링

가장 기본적인 방법으로, 특정 조건에 맞춰 JSX를 반환하거나 null을 반환하여 컴포넌트를 숨길 수 있습니다. 

function Greeting({ isLoggedIn }) {
  if (isLoggedIn) {
    return <h1>환영합니다!</h1>;
  }
  return <h1>회원가입해주세요.</h1>;
}

// App 컴포넌트에서 호출하는 방식
function App() {
  const userLoggedIn = true;
  return <Greeting isLoggedIn={userLoggedIn} />;
}

 

2) 삼항 연산자 (Ternary Operator)
삼항 연산자는 조건에 따라 두 가지 중 하나의 값을 선택할 때 유용합니다. 조건식이 간단할 경우, 코드를 간결하게 작성할 수 있습니다.

function Greeting({ isLoggedIn }) {
  return (
    <h1>
      {isLoggedIn ? '환영합니다!' : '회원가입해주세요.'}
    </h1>
  );
}

 

3) 논리 연산자 &&를 사용한 조건부 렌더링
&& 연산자는 특정 조건이 참일 때만 컴포넌트를 렌더링하고, 거짓일 경우 아무것도 렌더링하지 않습니다. 이 방법은 조건부 컴포넌트가 단일 컴포넌트일 때 자주 사용됩니다.

function Mailbox({ unreadMessages }) {
  return (
    <div>
      <h1>안녕하세요!</h1>
      {unreadMessages.length > 0 && (
        <h2>읽지 않은 메시지가 {unreadMessages.length}개 있습니다.</h2>
      )}
    </div>
  );
}

// App 컴포넌트에서 호출하는 방식
function App() {
  const messages = ['리액트', '타입스크립트', '자바스크립트'];
  return <Mailbox unreadMessages={messages} />;
}

 

4) 컴포넌트를 조건부로 숨기기
특정 조건이 충족되지 않으면 컴포넌트를 아예 렌더링하지 않아야 할 때도 있습니다. 이 경우 null을 반환하여 해당 컴포넌트를 숨길 수 있습니다.

function WarningBanner({ warn }) {
  if (!warn) {
    return null; // warn이 false면 컴포넌트를 렌더링하지 않음
  }
  return <div className="warning">경고!</div>;
}

// App 컴포넌트에서 호출하는 방식
function App() {
  const [showWarning, setShowWarning] = useState(false);

  return (
    <div>
      <WarningBanner warn={showWarning} />
      <button onClick={() => setShowWarning(!showWarning)}>
        {showWarning ? 'Hide' : 'Show'} 경고
      </button>
    </div>
  );
}

01

리스트 렌더링

리스트 렌더링은 배열이나 리스트 형태의 데이터를 반복하여 컴포넌트로 변환하는 과정입니다. React는 배열의 map() 메서드를 사용하여 각 항목을 반복하고, 해당 항목을 렌더링할 수 있습니다. 이렇게 하면 같은 구조의 여러 컴포넌트를 쉽게 생성할 수 있습니다.

 

키(Key) 속성

리스트를 렌더링할 때는 각 항목에 고유한 key 속성을 부여해야 합니다. 이 key는 React가 리스트 항목을 식별하고 변경 사항을 추적하는 데 도움을 줍니다. 일반적으로 항목의 ID나 인덱스를 사용합니다.

 

예시 코드

1) 리스트 렌더링(map() 사용)

import React from "react";

function StudyList() {
  const studies = [
    { id: 1, text: "나야, 리액트 스터디 feat: 공식문서" },
    { id: 2, text: "미라클모닝알고리즘스터디" },
    { id: 3, text: "C박과 함께하는 CSS & 퍼블리싱 스터디" },
  ];

  return (
    <div>
      <h1>스터디 목록</h1>
      <ul>
        {studies.map(study => (
          <li key={study.id}>{study.text}</li>
        ))}
      </ul>
    </div>
  );
}

export default StudyList;

 

2) 조건부 렌더링과 결합된 리스트

import React from "react";

function TodoList() {
  const todos = [];

  return (
    <div>
      <h1>할 일 목록</h1>
      {todos.length === 0 ? (
        <p>목록이 비어 있습니다.</p>
      ) : (
        <ul>
          {todos.map(todo => (
            <li key={todo.id}>{todo.text}</li>
          ))}
        </ul>
      )}
    </div>
  );
}

export default TodoList;

 

 

 

3) 컴포넌트로 분리된 리스트 항목 및 동적 추가/삭제(filter() 사용) 기능 구현

import React, { useState } from "react";

// 개별 아이템 컴포넌트
function Item({ name, onDelete }) {
  return (
    <li>
      {name} <button onClick={onDelete}>삭제</button>
    </li>
  );
}

// 아이템 추가 컴포넌트
function AddItemForm({ onAdd }) {
  const [newItem, setNewItem] = useState("");

  const handleAdd = () => {
    if (newItem.trim() !== "") {
      onAdd(newItem);
      setNewItem(""); // 입력 필드 초기화
    }
  };

  return (
    <div>
      <input
        type="text"
        value={newItem}
        onChange={(e) => setNewItem(e.target.value)}
        placeholder="사야하는 물건을 입력하세요"
      />
      <button onClick={handleAdd}>추가</button>
    </div>
  );
}

// 아이템 목록 컴포넌트
function ItemList() {
  const [items, setItems] = useState(["테이프", "상자", "풍선"]);

  // 아이템 추가 함수
  const addItem = (item) => {
    setItems([...items, item]);
  };

  // 아이템 삭제 함수
  const deleteItem = (index) => {
    const updatedItems = items.filter((_, i) => i !== index);
    setItems(updatedItems);
  };

  return (
    <div>
      <h1>물건 목록</h1>
      <AddItemForm onAdd={addItem} />
      <ul>
        {items.map((item, index) => (
          <Item key={item} name={item} onDelete={() => deleteItem(index)} />
        ))}
      </ul>
    </div>
  );
}

export default ItemList;

 

컴포넌트를 순수하게 유지하기

순수 컴포넌트?

순수 컴포넌트는 주어진 동일한 입력(props)과 상태(state)에 대해 항상 동일한 출력(UI)을 생성하는 컴포넌트를 말합니다. 이러한 컴포넌트는 외부 상태나 부작용에 의존하지 않으며, 독립적으로 작동합니다. React에서는 PureComponent 또는 React.memo를 사용하여 이러한 컴포넌트를 만들 수 있습니다.

장점

  • 예측 가능성: 순수 컴포넌트는 예측 가능한 동작을 하므로, 디버깅이 용이하고 테스트가 간편합니다.
  • 재사용성: 동일한 입력으로 동일한 출력을 생성하기 때문에, 다양한 곳에서 재사용할 수 있습니다.
  • 성능 최적화: React는 컴포넌트가 변경되지 않는 한 다시 렌더링하지 않기 때문에, 성능이 향상됩니다.

부작용 관리

순수 컴포넌트는 상태를 관리할 때 부작용을 최소화해야 합니다. 이를 위해 다음과 같은 방법을 고려할 수 있습니다.

  • 상태를 상위 컴포넌트로 끌어올리기: 상태를 여러 컴포넌트에서 공유해야 할 때, 상태를 상위 컴포넌트에서 관리하고 하위 컴포넌트에 props로 전달합니다.
  • 이펙트를 사용하여 부작용 처리: useEffect를 사용하여 비동기 작업이나 외부 API 호출 등의 부작용을 처리할 수 있습니다.

React에서 순수함의 중요성

  • 일관된 결과: 순수 함수는 동일한 입력에 대해 항상 같은 출력을 반환합니다. 이는 컴포넌트가 다른 환경(예: 서버)에서도 쉽게 실행될 수 있게 합니다.
  • 성능 향상: 입력이 변경되지 않은 컴포넌트는 렌더링을 건너뛸 수 있습니다. 이를 통해 성능을 최적화하고 불필요한 렌더링을 줄일 수 있습니다.
  • 안전한 캐시: 순수 함수는 항상 동일한 결과를 반환하므로, 결과를 캐시해도 안전합니다. 이는 데이터 처리에서 효율성을 높입니다.
  • 효율적인 업데이트: 컴포넌트 트리에서 일부 데이터가 변경되면, React는 오래된 렌더링을 완료하는 데 시간을 낭비하지 않고 새로 렌더링을 시작할 수 있습니다.
  • 새로운 기능 활용: React의 새로운 기능(예: 데이터 가져오기, 애니메이션 등)은 순수성을 활용합니다. 따라서 컴포넌트를 순수하게 유지하면 React의 전체적인 성능과 유연성을 극대화할 수 있습니다.

 

트리로서의 UI

트리는 요소와 UI 사이의 관계 모델입니다. 브라우저는 HTML(DOM)과 CSS(CSSOM)를 모델링하기 위해 트리 구조를 사용합니다. React도 다른 플랫폼과 마찬가지로 컴포넌트 간의 관계를 관리하고 모델링하기 위해 트리 구조를 사용합니다.

React는 컴포넌트로부터 UI 트리를 생성합니다. 위 그림에서 UI 트리는 DOM을 렌더링하는 데 사용됩니다.

브라우저의 렌더링 과정

렌더 트리: 브라우저가 HTML(DOM)과 CSS(CSSOM)를 모델링하기 위해 사용하는 구조입니다. React는 컴포넌트로부터 UI 트리를 생성하여 DOM을 렌더링합니다.

렌더 트리

 

사실, 렌더 트리에 대해 이번에 처음 알게 되어 어렵게 생각하고 있었는데 좋은 사진이 있어 가져와 보았습니다. 

 

1. DOM 트리 생성

HTML을 파싱하여 DOM 객체로 이뤄진 DOM 트리를 생성합니다. 

 

2. CSSOM(CSS Object Model) 트리 생성

CSS parser는 inline style과 CSS 코드를 파싱 하여 CSSOM 트리 생성합니다. 

 

3. 렌더 트리 생성

DOM CSSOM의 정보를 바탕으로, 실제로 브라우저의 화면에 노출되어야 하는 노드들에 대한 정보인 렌더 트리 생성합니다. 

 

4. 레이아웃 단계

렌더 트리가 완성되면, 각 요소의 크기와 위치를 계산하는 레이아웃 과정이 진행됩니다. 이 단계에서 각 요소가 화면에 어떻게 배치될지 결정됩니다.

 

5. 페인팅 단계

레이아웃이 완료된 후, 각 요소를 화면에 그리는 페인팅 과정이 이루어집니다. 이 과정에서 픽셀 단위로 각 요소의 시각적 표현이 렌더링됩니다.

 

6.합성 단계

브라우저는 페인팅된 요소들을 합성하여 최종적으로 화면에 표시합니다. 이 단계에서 여러 레이어가 합쳐져 최종 결과물이 만들어집니다.

 

리플로우와 리페인트

  • 리플로우: 레이아웃 계산을 다시 수행하는 과정으로, 요소의 크기나 위치가 변경될 때 발생합니다.
  • 리페인트: 요소의 시각적인 변화를 처리하는 과정으로, 크기나 위치는 변하지 않고 색상이나 그림자 등의 스타일이 변경될 때 발생합니다.

 

이 개념들에 비해 집 짓기에 비유해 설명하자면,

 

1. DOM 트리 생성 = 집의 구조 짓기

  • HTML을 받아오는 것은 집의 구조(벽, 문, 창문 등)를 짓는 것과 같습니다. 각 태그는 집의 한 부분을 나타냅니다.

2. CSSOM 트리 생성 = 집 꾸미기 계획 세우기

  • CSS는 집을 어떻게 꾸밀지에 대한 계획입니다. 벽의 색, 바닥재의 종류 등을 결정합니다.

3. 렌더 트리 생성 = 최종 인테리어 디자인

  • DOM과 CSSOM을 결합하여 렌더 트리를 만드는 것은, 집의 구조와 꾸미기 계획을 합쳐 최종적인 인테리어 디자인을 완성하는 과정입니다.

4. 레이아웃 단계 = 가구 배치

  • 각 요소의 정확한 위치와 크기를 계산하는 것은, 각 가구가 어디에 놓일지 결정하는 것과 같습니다.

5. 페인팅 단계 = 페인트 칠하기 및 장식하기

  • 실제로 색을 칠하고 벽에 그림을 거는 등의 작업으로 집의 모습을 완성합니다.

6.합성 단계 = 최종 점검

  • 모든 장식이 완료된 후, 전체적으로 다시 한번 둘러보며 조정하는 과정입니다.

순수 컴포넌트를 유지하고, 렌더링 과정과 리플로우, 리페인트를 이해하는 것은 React 애플리케이션의 성능 최적화와 효율적인 UI 관리에 필수적입니다. 이러한 원칙을 따르면 더 나은 사용자 경험을 제공할 수 있습니다 !!

 

 

컴포넌트를 순수하게 유지하기와 트리로서의 UI는 모르는 부분이 많이 적다보니 두서가 없어진 것 같은데.. 읽어주셔서 감사합니다. 앞으로 더 꼼꼼히 정리하고, 공부하고, 요약하고, 공유하겠습니다 !!