삭제와 추가가 가능한 탭을 개발하자

박종대·2022년 10월 13일
0

Convi

목록 보기
3/9

Tab

Tab은 정말 많이 사용되는 컴포넌트입니다. 처음에는 Mui와 같은 라이브러리를 사용했지만 여러가지 제약사항들이 존재했습니다.

  • Tab 삭제 기능 X
  • Tab Title 수정 기능 X
  • Default Tab 생성 기능 X

분명 기본적으로 있어야할 것 같은 기능인데 존재하지 않습니다.

왜 없을까 고민해보면 tab elements 즉, tab 아래에 표시될 contents들을 관리하는 로직을 tab 컴포넌트 안에서 하기가 애매하다는 점 때문입니다. tab을 개발할 때 가장 중요한 점도 이 tab elements들을 어디서 어떻게 관리하냐 입니다.

tab에 들어갈 tab elements들은 분명 부모 컴포넌트에서 attribute 형태로 제공해줄 것인데 tab 컴포넌트 내부에서 이러한 tab elements들을 삭제하고 생성하는 로직을 추가하는 것이 어렵고 가능하게 한다고 해도 억지스럽기 때문입니다.

말이 어려우니 그림과 코드로 알아보겠습니다.

이런식으로 tab elements들을 넘겨주면 tab은 해당 tab elements들을 대입 연산으로는 변경할 수 없습니다. props는 재할당 불가능하기 때문입니다. (불변은 아닙니다.)
기존에 존재하던 Tab들에 Tab 삭제와 추가 기능이 없었던 이유는 위와 같은 tab elements 관리 문제 때문에 tab elements 관리에 관한 로직은 사용하는 컴포넌트에서 하도록 하고 Tab 내부에서는 전달 받은 tab elements를 tab 구조에 맞게 출력하는 로직만 담당하도록 개발했기 때문이었음을 유추할 수 있습니다.

  • tab elements 관리는 사용하는 컴포넌트에서 담당
  • tab 컴포넌트는 화면에 출력하는 것만 담당

간단하고 원칙적으로 생각했을 때는 이 구조가 가장 합당해 보입니다. 하지만 기본적인 탭 추가나 삭제 기능을 개발하기 위해 Tab 컴포넌트 바깥에 로직을 추가해야 하기 때문에 기능 버튼의 위치 같은 부분들이 애매합니다.

요약해보면, 탭 element는 바깥에서 주입되는데 주입된 element들을 삭제하고 추가하는 로직을 어디다 담아야 하는지가 관건입니다.

해당 컴포넌트를 보시면 tab header들 마다 옆에 x 버튼이 있고 해당 x 버튼을 누르면 탭이 삭제됩니다. 제어권이 전적으로 Tab 외부 컴포넌트에만 있다면 tab header들 마다 x 버튼을 위치시키기가 애매함을 느끼실 수 있을 겁니다.

삭제

제어권이 없다면 제어권을 넘겨주면 됩니다. 사용하는 컴포넌트에서 tab을 삭제할 수 있는 onClose 메소드를 props로 넘겨준다면 충분히 tab 내부적으로 tab 삭제 기능을 구현할 수 있습니다.

onClose={
	(id: number) => 
		setTabElements([...tabElements.filter((_: any, index: number) => index !== id)])
}

사용자 컴포넌트에서 Tab 컴포넌트로 넘겨줄 onClose 메소드 입니다. id를 인자로 받아서 해당 id의 탭을 삭제 시킬 수 있습니다.
Tab 내부적으로는 Header Element마다 X 버튼을 달아두고 해당 X 버튼 onClick 이벤트 핸들러에 해당 onClose(tab 인덱스) 메소드를 호출하는 로직을 넣으면 됩니다.

 <ConviTabCloseButton onClick={() => props.onClose()} />

추가

탭 추가 기능에서 중요한 점은 어떤 탭을 추가할 지 모른다는 것입니다. 탭을 사용하는 컴포넌트의 기능에 따라 추가할 컴포넌트는 달라집니다. 따라서 어떤 컴포넌트를 추가할 지는 사용자 컴포넌트에서 결정되어야 합니다.
그래서 어떤 컴포넌트를 추가할 지에 대한 로직이 담긴 onAdd 메소드를 props로 넘겨받아 Tab 내의 Plus 버튼 클릭 이벤트에 해당 onAdd 메소드를 호출할 수 있도록 구현했습니다.

<ConviTabPlusButton onClick={() => props.onAdd!()} />

Optional

경우에 따라서 삭제하면 안되는 탭, 추가 기능이 필요없는 탭이 있을 수 있습니다.
onAdd 메소드가 props로 전달 되었을 때만 + 버튼 기능을 활성화 시키도록 추가 기능을 Optional하게 제공했습니다.
각 tab elements에 fixed 라는 속성을 추가 하고 fixed가 true인 element는 삭제 아이콘이 생성되지 않도록 하였습니다.

개발

이제 테스트를 해보겠습니다.

function App() {
	const [selected, setSelected] = useState<number>(0);
	const [tabElements, setTabElements] = useState<React.ReactElement<ConviTabElementProps>[]>([
		<ConviTabElement fixed title="fixed test">
			<div>fixed test</div>
		</ConviTabElement>,
		<ConviTabElement title="test2">
			<div>test</div>
		</ConviTabElement>,
	]);
  
	return (
		<div className="App">
			<ConviTab
				onClose={(id: number) => setTabElements(prevElements => [...prevElements.filter((_: any, index: number) => index !== id)])}
				selected={selected}
				onSelected={(id: number) => setSelected(id)}
				onAdd={() =>
					setTabElements(prevElements => [
						...prevElements,
						<ConviTabElement title="Add Test">
							<div>add test</div>
						</ConviTabElement>,
					])
				}
			>
				{tabElements}
			</ConviTab>
		</div>
	);
}

export default App;

기본적인 탭이 완성되었습니다. 하지만 아직 갈 길이 멉니다. 다음 포스트에서 이어가겠습니다.

profile
Frontend Developer

0개의 댓글