row에서 한가지 옵션을 선택했다면 나머지 row에서는 선택한 옵션을 제외한 리스트가 보이도록 한다.
(selected options가 중복된 값을 가질 수 없도록 하기위함)
코드에 대한 설명은 주석으로 대체 하겠다.
간단한 custom select box를 만들어 보았다.
1. box 클릭 시 list가 펼처짐
2. list에서 option 선택 시 선택한 option이 박스에 보여짐
const DropdownList = ({ rowId, list, onSelect, onDelete }) => {
const [selected, setSelected] = useState("");
const [isOpen, setIsOpen] = useState(false);
const selectRef = useRef(null);
const _onSelect = (value) => {
onSelect && onSelect(rowId, value);
setSelected(value);
setIsOpen(false);
};
// 선택 옵션 초기화
const _onDelete = () => {
setSelected("");
onDelete && onDelete(rowId);
};
const handleClickOutSide = (e) => {
if (!selectRef.current) return;
if (isOpen && !selectRef.current.contains(e.target)) {
setIsOpen(false);
}
};
useEffect(() => {
if (isOpen) document.addEventListener("mousedown", handleClickOutSide);
return () => {
document.removeEventListener("mousedown", handleClickOutSide);
};
});
return (
<div className="flexRow">
<span>{rowId}</span>
<div ref={selectRef} className="dropdownContainer">
<div className="dropDownLabel" onClick={() => setIsOpen(true)}>
{selected}
</div>
{!!isOpen && (
<ul className="dropDownList">
{list.map((item) => (
<li className="item" key={item} onClick={() => _onSelect(item)}>
{item}
</li>
))}
</ul>
)}
</div>
<button onClick={_onDelete}>clear</button>
</div>
);
};
option 선택 시 Map에 해당 [key, value]
를 세팅한다.
선택된 옵션을 제외한 list
를 <DropdownList>
에 전달 LIST.filter((item) => !new Set(selectedOptions.values()).has(item))}
selectedOptions.values() : 삽입 순서로 개체 의 각 요소에 대한 값 을 포함하는 새 Iterator 개체를 반환
ex) ['item 1', 'item 2', 'item 3']
selected option 초기화(추가기능)
export default function App() {
const [selectedOptions, setSelectedOptions] = useState(new Map()); // 선택된 옵션을 담기 위한 상태
const handleSelected = (rowId, option) => {
setSelectedOptions((prev) => {
const newMap = new Map(prev);
newMap.set(rowId, option); // Map에 [key: rowId, value: option]로 설정한다.
return newMap;
});
};
const deleteOption = (rowId) => {
if (!selectedOptions.has(rowId)) return;
const newMap = new Map(selectedOptions);
newMap.delete(rowId); // Map에서 해당 key(rowId)의 요소를 제거한다.
setSelectedOptions(newMap);
};
return (
<div className="App">
{...}
<h3>Mission: 선택 된 옵션은 리스트에서 보이지 않게 하기</h3>
<h3>
selected options:{" "}
{[...selectedOptions.entries()].map((v) => (
<span key={v[0]} className="tags">{`${v[0]}: ${v[1]}`}</span>
))}
entries()
</h3>
<div className="flexColumn">
{[1, 2, 3].map((id) => (
<DropdownList
key={id}
rowId={id} // required: Unique key
list={LIST.filter(
(item) => !new Set(selectedOptions.values()).has(item)
)} // has함수를 사용하기 위해 Set자료구조로 변환
value={selectedOptions.get(1)}
onSelect={handleSelected}
onDelete={deleteOption}
/>
))}
</div>
</div>
);
}
Map객체는 키-값 쌍을 보유하고 키의 원래 삽입 순서를 기억합니다 . 모든 값(객체 및 기본 값 모두 )은 키 또는 값으로 사용할 수 있습니다. Map - JavaScript | MDN
선택된 옵션을 담는 상태는 여러가지가 될 수 있다.
Array<string>
: [ 'item 1', 'item 2', 'item 3' ] 부적합
1. rowId===index row별 옵션 변경 또는 삭제 시 해당 옵션의 출처(rowId) 필요
2.<DropdownList>
개수 ===selectedOptions.length
지난 포스트 Check Box 상태 관리(feat. Set 활용법)에서 활용한Set
자료구조도 key/value쌍이 아니므로 부적합.
{ key: rowId, value: option }
적합
1. 옵션의 출처를 위한 rowId가 필요 하므로 key/vlaue를 가진 Object가 적절const selectedOptions = { row1: 'item1', row2: 'item3', }
- 추가/삭제/변경 등이 간단하다.
- 삽입 순서가 보장 되지 않는다.
new Map()
:[key/value]
쌍의 삽입 순서를 기억하는Iterator
적합TMI...
회사 프로젝트 중 필요한 스펙은 row 별 DropdoownList가 필요 했고,
다른 row에서 선택된 값이 있으면 다음 조회하는 리스트에서는 중복 방지를 위한 선택된 값을 제외한 리스트만 보이도록 원했다.
그래서 해당 기능에선 삽입 순서까지 필요하진 않았지만Map
자료구조를 공부하며 예제를 만들기 좋아 보여
일반 적으로 많이 쓰이는Tag 기능
을 추가로 구현했다.
1.[key,value]
쌍을 요소로 가진다.Map(2) {'row1' => 'item1', 'row2' => 'item2'}
2. 추가/삭제/변경 등이 매우 간단하다. (인스턴스 메서드set, delete, clear, has, keys, values, entries
등을 보유)
3. 삽입 순서 보장
4. 키 동등성Key equality - MDN
- 키 동등성은 sameValueZero 알고리즘을 기반으로 합니다.
- NaN은 NaN과 동일한 것으로 간주되며(NaN !== NaN일지라도) 다른 모든 값은 === 연산자의 의미 체계에 따라 동일한 것으로 간주됩니다.
- 현재 ECMAScript 사양에서 -0과 +0은 동일한 것으로 간주되지만 이전 초안에서는 그렇지 않았습니다. 자세한 내용은 브라우저 호환성 표에서 "-0 및 0에 대한 값 동일성"을 참조하십시오.
- Map -MDN objects_vs._maps
key type의 유연성, 순서 보장, 요소 추가/제거의 경우 Object에 비해 좀더 나은 성능