어드민 페이지를 개발할 때 가장 많이 사용하는 요소가 아마 Table일 것 같다.
그동안은 table을 table 태그를 사용해서 개발하지 않았다. 테두리 관련된 작업이나, 요소를 구현할 때 colspan 등을
사용하는 방법보단 div 태그를 활용해서 만드는게 더 간단하다고 생각했기 때문이다.
어드민 한정된 요소라면 상관없을텐데, 클라이언트 페이지에서 표를 사용하는 경우도 있을텐데 시맨틱한 구조로
개발하는 것이 SEO 측면에서도 좋을 것이다.
또한 rowspan, colspan을 사용할 때도 div로 만든다면 flex 옵션 등을 사용해서 추가 설정을 줘야한다.
이렇다보니 테이블을 어떤 방식으로 만드는 것이 좋을지 여러가지 생각을 해봤다.
어쩌면 이런 고민이 의미가 없을 수 있는게 antd나 material ui 같은 라이브러리를 사용하면 왠만한 기능은 전부
가지고 있는 테이블을 만들 수 있기 때문이다.
하지만 UI 라이브러리를 사용하면 스타일 커스텀이 생각보다 불편했고 이런 라이브러리가 있다는 것은
나도 하려면 할 수 있지 않을까? 라는 생각으로 직접 만들어보려고 한다.
list 받아서 만들기
가장 편하다고 생각했는 것이 바로 data와 head를 받아서 처리하는 것이다.
<Table list={data} head={head}/>
데이터들만 넣어주면 짜잔! 하고 표를 만들어준다면 엄청 편할 것이라고 생각했다.
antd 같은 라이브러리 역시 data를 받아와서 표를 만들어주기 때문에 이 방식에 신뢰성이 높았다.
하지만 불편사항이 존재했다. UI 라이브러리와 동일하게 작은 부분이라도 스타일을 커스텀하는 경우가 발생하면 속
성이 붙거나 비슷한 유형의 코드가 많아졌다. 비슷한 유형의 코드가 많아지는건 크게 문제가 되지 않는다고 생각한다.
어쩔 수 없는게 모든 종류의 테이블을 커버할 수 있는 컴포넌트라는게 가능할까? 또 그게 과연 테이블 컴포넌트라고
할 수 있을까?
완벽한 하나의 테이블보단 테이블을 쉽게 만들 수 있는 파츠를 모아서 하나의 테이브를 만들 수 있게 하는 방식으로
변경하였다.
테이블 파츠
테이블에서 제일 어려웠던 부분이 테두리 요소이다.
border collapse
왜 separate와 collapse가 함께하는 세계관은 없을까? block도 inline-block이라는 이로운 속성이 있는데,,,,
separate는 요소들이 border radius를 할 수 있다. 하지만 내부 border끼리 겹칠 수 있다.
그렇다면 collapse는 어떤까?
내부 요소들의 border가 겹칠일이 없다! 그러니 td & th 요소들에게 border 1px을 동일하게 주더라도 아주 이쁜
내부 선이 생긴다. 하지만 border radius를 할 수 없다....
깊은 고민을 한 끝에 separate를 사용해서 만들기로 했다.
collapse는 radius 속성이 아에 안되지만 separate의 내부 border 문제는 어떻게 처리는 할 수 있다.
그렇다면 처리는 할 수 있는 방향으로 작업을 하는게 맞다고 생각한다.
border 처리
컴포넌트의 핵심 방향을 골랐으니 다음으론 border를 처리해야한다.
여기서 separate의 문제가 나타나는데 separate는 border가 뾰족뾰족하다는 특징이 있다.
앞서 이야기했던 border가 겹쳐서 내부 border들은 두꺼워지는 것 뿐만 아니라 모서리가 각지는 문제가 있었다.
그래서 테두리를 줄 때 일직선으로 지워지는 게 아니라 틈이 남아 있었다.
그런데 내가본 많은 테이블들은 이렇게 구성된 것을 보지 못해서 개인적으로 정말 괜찮다고 생각하는
antd의 테이블 예시를 살펴봤다.
생긴 것은 당연히 만족!
내부 코드를 봤을 때 antd의 테이블은 좀 더 능동적인 것 같다. 어떤 경우에는 좌우 border가 속성으로 들어가고
어떤 경우에는 after, before 요소로 구성되어 있었다.
나는 여기서 before와 after를 사용하는 방향으로 진행하려고 한다.
antd 같은 경우 내가 생각한 것 이상의 기능들은 제공하기 때문에 이렇게 작업을 할 수 있겠지만 테이블을 활용해서
이렇게까지 많은 기능들은 필요없다고 생각하고 그렇기 때문에 어떤 경우에는 after, 어떤 경우에는 border로 처리하는
작업은 과하다고 생각한다.
하지만 추가로 생각할 것이 테이블의 꼭짓점들은 radius가 들어갈 수 있다.
radius를 사용하려면 border 속성은 필요하다. 즉, 꼭짓점에 있는 요소는 border로 처리해야 하는 조건이 필요하다.
지금까지의 내용을 정리하면,
- border의 속성은 separate를 사용하고 border-spacing을 0으로 줘서 테이블을 구성한다.
- 기본적으로 좌우 테두리는 after, before로 처리한다.
- 꼭짓점에 존재하는 요소들은 border로 처리해야 하는 조건이 있다.
해당 조건을 고려해서 개발을 진행하였다.
만들면서 Table > Tr > Td & Th 순서로 전체적인 스타일을 줄 수 있게 작업해서 코드의 중복을 최대한 줄이는 방향으로
작업했다.
테두리를 작업하면서 발생한 문제가 하나 더 있는데, 전체 테두리의 조건이 first-child에게 왼쪽 테두리를 추가하도록
설정을 주었다.
<Table>
<thead>
<tr>
<td rowspan=2>...</td>
<td rowspan=2>...</td>
<td rowspan=1>...</td>
<td rowspan=2>...</td>
</tr>
<tr>
<td>...</td>
</tr>
</thead>
</Table>
위와 같이 구현을 할 때 문제가 생겼다.
의미상 2번째 tr이 첫번째 요소는 맞다. 하지만 왼쪽 테두리가 생기는건 안되는게, 위치상 가운데에 있기 때문이다.
다음과 같이 테두리가 구성되어 버렸다. 이것을 처리해주는 방법을 다시 고려해야 했다.
이것저것 생각하다가 thead와 tbody를 라인을 보면서 rowspan이 2인데 다음 라인의 첫 번째 요소가 필요없는 경우를
처리하는 작업을 같이 해주는 컴포넌트로 만들려고 했다.
<TblockStyle>
{React.Children.map(children, (child, idx) => {
return React.cloneElement(child, {
children: React.Children.map(
child.props.children,
(deepChild, deepIdx) => {
if (rowspanIdx !== -1 && rowspanIdx !== idx) {
rowspanIdx = -1;
return React.cloneElement(deepChild, {
borderLeftColor: "transparent",
});
}
if (deepIdx === 0 && deepChild.props.rowspan > 1) {
rowspanIdx = idx;
}
return deepChild;
}
),
});
})}
</TblockStyle>
thead와 tbody의 역할도 하기 때문에 Tblock이라는 명칭으로 만들었고 핵심은 내부 코드이다. 하나씩 살펴보면,
{React.Children.map(children, (child, idx) => {
// ...
})}
tr 요소들이 들어오기 때문에 Children.map을 활용해서 내부를 반복하게 했다.
return React.cloneElement(child, {
children: ...
})
이때 tr의 자식인 td 또는 th를 변경해야 하기 때문에 cloneElement를 활용해서 변경할 수 있는 여지를 주었다.
React.Children.map(
child.props.children,
(deepChild, deepIdx) => {
if (rowspanIdx !== -1 && rowspanIdx !== idx) {
rowspanIdx = -1;
return React.cloneElement(deepChild, {
borderLeftColor: "transparent",
});
}
if (deepIdx === 0 && deepChild.props.rowspan > 1) {
rowspanIdx = idx;
}
return deepChild;
}
),
deepChild는 th & td 요소인데, 첫 번째 요소이면서 rowspan이 1보다 크면 rowspanIdx에 현재 idx를 넣어줬다.
그리고 다음 라인이 되었을 때 rowspanIdx가 존재하고 현재와 다른 라인이면 왼쪽 테두리를 없애주는 작업을 했다.
깊은 작업은 더 있지만 예전에 사용하던 테이블 컴포넌트보단 가볍고 직관적인 방법으로 구현을 할 수 있게 되었다.
'React > 실험실' 카테고리의 다른 글
CSS를 컴포넌트에 중복 호출하면 안되는 EU (1) | 2024.08.17 |
---|---|
React Query 고려하기 - Request Waterfalls (1) | 2024.08.07 |
useFunnel 만들기 (2) | 2024.06.16 |
[React] Controlled and UnControlled Input (0) | 2024.03.14 |
[React] TDD, 클린 코드 with React 3기 - 1주차 (0) | 2024.03.09 |