[React] table 열 합치기 - JavaScript

문지은·2024년 6월 8일
post-thumbnail

요구 사항

  • 테이블에서 같은 카테고리의 셀은 병합하여 표시한다.

rowspan

  • 일반적으로 테이블 셀을 수직으로 병합하기 위해 rowspan 속성을 사용할 수 있다.
    • rowspan 속성은 HTML 테이블에서 셀(td 또는 th)이 수직으로 몇 개의 행(row)을 병합할 것인지를 지정하는 속성이다.
    • 기본적으로 rowspan 속성은 한 개의 셀을 여러 행에 걸쳐 병합할 때 사용되며, 병합된 셀은 하나의 큰 셀처럼 보이게 된다.

기본 문법


<td rowspan="n">내용</td>
  • 여기서 n은 셀이 병합할 행의 수를 나타낸다.
    • 예를 들어 rowspan="2"는 셀이 두 개의 행에 걸쳐 병합됨을 의미한다.

방법 1. class 비교

  • 주어진 클래스 이름을 가진 HTML 테이블 셀(td) 요소들을 찾아, 동일한 텍스트 콘텐츠를 가진 셀들을 수직으로 병합
const genRowspan = (className: string) => {
  // 주어진 클래스 이름을 가진 모든 요소를 선택하고 HTMLTableCellElement 배열로 변환합니다.
  const elements = Array.from(document.querySelectorAll(`.${className}`)) as HTMLTableCellElement[];

  // 이전 카테고리 값을 저장하기 위한 변수
  let lastCategory = "";

  // 현재 병합할 셀의 개수를 저장하기 위한 변수
  let rowspan = 1;

  // 병합할 첫 번째 셀을 저장하기 위한 변수. 초기값은 null.
  let firstElement: HTMLTableCellElement | null = null;

  // 각 요소에 대해 반복문을 실행
  elements.forEach((element) => {
    // 현재 요소의 텍스트 콘텐츠를 가져옵니다.
    const text = element.textContent;

    // 현재 요소의 텍스트가 이전 카테고리 값과 같은지 확인
    if (text === lastCategory) {
      // 병합할 셀의 개수를 증가시킵니다.
      rowspan++;
      // 현재 요소를 숨깁니다.
      element.style.display = "none";
      // 첫 번째 요소에 rowspan 속성을 설정
      if (firstElement) {
        firstElement.setAttribute("rowspan", rowspan.toString());
      }
    } else {
      // 현재 요소의 텍스트가 이전 카테고리 값과 다르다면 새로운 카테고리를 시작

      // 새로운 카테고리 값을 설정
      lastCategory = text || "";
      // 병합할 셀의 개수를 초기화
      rowspan = 1;
      // 현재 요소를 첫 번째 요소로 설정
      firstElement = element;
      // 현재 요소의 rowspan 속성을 제거
      element.removeAttribute("rowspan");
      // 현재 요소를 표시
      element.style.display = "";
    }
  });
};
  • 전체 코드
    • useEffect 훅을 사용하여 컴포넌트가 마운트된 후 genRowspan 함수를 호출
"use client";

import { useEffect, useRef } from "react";
import "../styles/table.scss";

export default function Home() {
  const data = [
    { category: "Fruit", item: "Apple", price: "$1" },
    { category: "Fruit", item: "Banana", price: "$1" },
    { category: "Vegetable", item: "Carrot", price: "$2" },
    { category: "Vegetable", item: "Broccoli", price: "$2" },
    { category: "Vegetable", item: "Spinach", price: "$2" },
  ];

  useEffect(() => {
    genRowspan("category");
  }, []);

  const genRowspan = (className: string) => {
    const elements = Array.from(
      document.querySelectorAll(`.${className}`)
    ) as HTMLTableCellElement[];
    let lastCategory = "";
    let rowspan = 1;
    let firstElement: HTMLTableCellElement | null = null;

    elements.forEach((element) => {
      const text = element.textContent;

      if (text === lastCategory) {
        rowspan++;
        element.style.display = "none";
        if (firstElement) {
          firstElement.setAttribute("rowspan", rowspan.toString());
        }
      } else {
        lastCategory = text || "";
        rowspan = 1;
        firstElement = element;
        element.removeAttribute("rowspan");
        element.style.display = "";
      }
    });
  };

  return (
    <div className="container mx-auto py-20 flex flex-col gap-12">
      <div className="horizontal-headings">
        <table>
          <thead>
            <tr>
              <th>Category</th>
              <th>Item</th>
              <th>Price</th>
            </tr>
          </thead>
          <tbody>
            {data.map((row, index) => (
              <tr key={index}>
                <td className="category">{row.category}</td>
                <td>{row.item}</td>
                <td>{row.price}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}

방법 2. attribute 비교

  • HTML에서 동일한 클래스를 여러 용도로 사용할 수 있지만, 이는 클래스 이름의 중복 문제를 야기할 수 있다.
  • 속성을 사용하면 특정 용도에 맞는 고유한 식별자를 제공할 수 있고, JavaScript와 HTML 간의 데이터 전달이 더 명확해진다.
  • 속성 값을 비교하여 셀을 합치도록 함수 수정
const genRowspan = (attribute: string) => {
  // 주어진 속성을 가진 모든 요소를 선택하고 HTMLTableCellElement 배열로 변환합니다.
  const elements = Array.from(document.querySelectorAll(`[${attribute}]`)) as HTMLTableCellElement[];

  // 이전 카테고리 값을 저장하기 위한 변수
  let lastCategory = "";

  // 현재 병합할 셀의 개수를 저장하기 위한 변수
  let rowspan = 1;

  // 병합할 첫 번째 셀을 저장하기 위한 변수. 초기값은 null.
  let firstElement: HTMLTableCellElement | null = null;

  // 각 요소에 대해 반복문을 실행
  elements.forEach((element) => {
    // 현재 요소의 속성 값을 가져옵니다.
    const text = element.getAttribute(attribute);

    // 현재 요소의 속성 값이 이전 카테고리 값과 같은지 확인
    if (text === lastCategory) {
      // 병합할 셀의 개수를 증가시킵니다.
      rowspan++;
      // 현재 요소를 숨깁니다.
      element.style.display = "none";
      // 첫 번째 요소에 rowspan 속성을 설정
      if (firstElement) {
        firstElement.setAttribute("rowspan", rowspan.toString());
      }
    } else {
      // 현재 요소의 속성 값이 이전 카테고리 값과 다르다면 새로운 카테고리를 시작

      // 새로운 카테고리 값을 설정
      lastCategory = text || "";
      // 병합할 셀의 개수를 초기화
      rowspan = 1;
      // 현재 요소를 첫 번째 요소로 설정
      firstElement = element;
      // 현재 요소의 rowspan 속성을 제거
      element.removeAttribute("rowspan");
      // 현재 요소를 표시
      element.style.display = "";
    }
  });
};
  • 전체 코드
"use client";

import { useEffect, useRef } from "react";
import "../styles/table.scss";

export default function Home() {
  const data = [
    { category: "Fruit", item: "Apple", price: "$1" },
    { category: "Fruit", item: "Banana", price: "$1" },
    { category: "Vegetable", item: "Carrot", price: "$2" },
    { category: "Vegetable", item: "Broccoli", price: "$2" },
    { category: "Vegetable", item: "Spinach", price: "$2" },
  ];

  useEffect(() => {
    genRowspan("data-category");
  }, []);

  const genRowspan = (attribute: string) => {
    const elements = Array.from(
      document.querySelectorAll(`[${attribute}]`)
    ) as HTMLTableCellElement[];
    let lastCategory = "";
    let rowspan = 1;
    let firstElement: HTMLTableCellElement | null = null;

    elements.forEach((element) => {
      const text = element.getAttribute(attribute);

      if (text === lastCategory) {
        rowspan++;
        element.style.display = "none";
        if (firstElement) {
          firstElement.setAttribute("rowspan", rowspan.toString());
        }
      } else {
        lastCategory = text || "";
        rowspan = 1;
        firstElement = element;
        element.removeAttribute("rowspan");
        element.style.display = "";
      }
    });
  };

  return (
    <div className="container mx-auto py-20 flex flex-col gap-12">
      <div className="horizontal-headings">
        <table>
          <thead>
            <tr>
              <th>Category</th>
              <th>Item</th>
              <th>Price</th>
            </tr>
          </thead>
          <tbody>
            {data.map((row, index) => (
              <tr key={index}>
                <td data-category={row.category}>{row.category}</td>
                <td>{row.item}</td>
                <td>{row.price}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}
profile
코드로 꿈을 펼치는 개발자의 이야기, 노력과 열정이 가득한 곳 🌈

0개의 댓글