
상품 리스트나 상품 등록 폼, 주문 조회 리스트 등을 구현하는 데에 있어서 Grid를 사용해야겠다고 생각했다.
사실 그동안 Grid를 제대로 사용해 본 적이 없지만, 어렴풋이라도 알고 있는 상태였기 때문에 써야겠다는 생각을 할 수 있었던 것 같다.
Grid는 CSS 레이아웃을 만들기 위한 도구이다.
그러면 나는 여태 레이아웃을 형성하기 위해 Grid 대신 뭘 써왔냐, 하면
바로 Flex이다.
그렇다면 Grid와 Flex의 차이는 무엇일까?
Flex는 주로 단일 차원(수평 또는 수직)의 레이아웃을 형성하기 위한 도구이다. 주로 아이템을 한 줄에 배치하거나, 한 축을 따라 정렬하는 데 사용된다.
Grid는 Flex보다 복잡한 2차원 레이아웃을 다룬다. 테이블 같은 레이아웃을 생각하면 된다. 수평 및 수직 방향에서 아이템의 위치를 지정한다.
참고로 Table과 Grid의 차이는 Table은 주로 데이터를 표시하고 구조화하기 위해 사용(말 그대로 테이블을 표시하는 데 사용)되는 반면, Grid는 레이아웃을 구성하고 구조화하기 위해 사용된다. Grid는 테이블보다 더 유연하며, 다양한 형태의 콘텐츠를 배치할 수 있다.
다시 돌아와서 아무튼 Flex는 1차원 레이아웃, Grid는 2차원 레이아웃을 다루는 도구이다.
따라서 이번엔 좀 더 복잡한 레이아웃을 형성해야 했고, 화면 크기에 따른 반응형 디자인도 고려해야 했기 때문에 자연스레 Grid를 사용해야겠다고 생각했던 것이다.
그러면 이제 이번 프로젝트에서 Grid를 사용한 부분을 살펴보면서 Grid 관련 CSS 속성들을 간단히 살펴보자.

참고로 이번 프로젝트는 tailwind CSS를 사용했기 때문에 해당 속성에 해당하는 tailwind 문법을 (tailwind CSS Grid) 여기서 찾아볼 수 있었다.
주문 조회에 들어갈 수 있는 주문 번호, 이미지, 주문 상품, 상품 가격, 수량, 합계, 배송 정보에 관한 내용들을 display: grid 속성의 div 태그로 감싼 모습이다.
<div className="grid grid-flow-col lg:grid-cols-7 grid-cols-5 gap-2">
// 주문 번호
<div style={{ gridRow: `span ${order.products.length}` }}>
<div>{order.merchant_uid}</div>
<div>{new Date(order.orderedAt).toLocaleString()}</div>
<div>{order.buyer_name}</div>
</div>
// 이미지
{order.products.map((product) => (
<div key={product.id}>
<img src={product.image} width={96} height={96} />
</div>
))}
// 주문 상품
{order.products.map((product) => (
<div key={product.id}>
{product.name}
</div>
))}
// 상품 가격
{order.products.map((product) => (
<div key={product.id}>
{product.price.toLocaleString()}원
</div>
))}
// 수량
{order.products.map((product) => (
<div key={product.id}>
{product.quantity}개
</div>
))}
// 합계
<div style={{ gridRow: `span ${order.products.length}` }}>
{order.amount.toLocaleString()}원
</div>
// 배송 정보
<div style={{ gridRow: `span ${order.products.length}` }}>
{order.buyer_addr}
</div>
</div>
감싼 자식 태그들을 생략하고 부모 태그 부분만 보면 다음과 같다.
tailwind 문법을 이번 프로젝트에서 처음 사용해봤기 때문에 생소했지만 오히려 한 눈에 알아보기 쉬웠다.
<div className=`grid grid-flow-col row-span-${order.products.length} lg:grid-cols-7 grid-cols-5 gap-2`>
// ...
</div>
그래도 제대로 grid를 파악하기 위해 이를 CSS 속성(property): 값(value)으로 나타내보면 다음과 같다.
display: grid;
grid-auto-flow: column;
gridRow: `span ${order.products.length}`;
grid-template-columns: repeat(7, 1fr);
gap: 2px;
먼저 display값을 grid로 설정해주고,
그리드 아이템의 배치 방향을 지정하는 grid-auto-flow값을 column으로 설정해준다.
grid-auto-flow는 아이템이 자동 배치되는 흐름을 결정하는 속성으로, 이를 column으로 설정하면 그리드 아이템이 열(column) 방향으로 배치된다.
여기서는 열을 기준으로 아이템을 자동 배치 할 것이기 때문에 column으로 설정해준다.
참고로,grid-auto-flow: row로 설정했을 경우 아이템이 행 방향으로 배치되기 때문에 아래 사진처럼 정렬된다.
gridRow는 그리드 아이템의 행(row) 위치를 지정하는 속성이다. 예를 들어,
grid-row: span 2;
는 아이템이 그리드에서 현재 위치한 행부터 시작하여 2개의 행을 차지하도록 지정한다.
(아래 사진에서 One div에 적용)

구현하고자 하는 주문 조회 목록에서는 한 주문에 해당하는 상품들을 행별로 보여줘야 하기 때문에 grid-row값을 한 주문에 해당하는 상품들 개수로 설정해줘야 한다.
여기서 한 주문에 해당하는 상품들(의 개수)은 주문 별로 달라지기 때문에, 이를 동적으로 넣어줘야 한다.
따라서
grid-row: span ${order.products.length};
이렇게 동적으로 넣어준다면, 주문 별로 상품의 개수만큼 행을 차지하도록 설정해줄 수 있다.
grid-template-columns은 Grid 컨테이너 내의 열을 정의하는 데 사용되는데, 이때 각 열의 너비를 지정하여 그리드의 열 레이아웃을 조정할 수 있다.
fr은 fraction으로, 숫자 비율대로 하나의 열의 크기를 나눈다.
1fr 1fr 1fr은 균일하게 1:1:1 비율인 3개의 column을 의미한다.
grid-template-columns: 100px 2fr 1fr;
이렇게 고정 크기와 가변 크기를 섞어서 열의 너비를 정해줄 수도 있다.
상품 조회 목록에서는 하나의 열을 같은 비율의 일곱 섹션으로 나눠서 구현하고 싶었기 때문에 repeat 함수를 이용해서 1fr를 반복적으로 처리해준다.
grid-template-columns: repeat(7, 1fr));
마지막으로 gap은 그리드 셀 사이의 간격을 의미한다.