Mission: 선택 된 옵션은 리스트에서 보이지 않게 하기(feat. Map)

하태현·2022년 7월 5일
0

React

목록 보기
10/11
post-thumbnail

Mission

row에서 한가지 옵션을 선택했다면 나머지 row에서는 선택한 옵션을 제외한 리스트가 보이도록 한다.
(selected options가 중복된 값을 가질 수 없도록 하기위함)

Code

코드에 대한 설명은 주석으로 대체 하겠다.

간단한 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>
  );
};

App

  1. option 선택 시 Map에 해당 [key, value]를 세팅한다.

  2. 선택된 옵션을 제외한 list<DropdownList>에 전달 LIST.filter((item) => !new Set(selectedOptions.values()).has(item))}

    selectedOptions.values() : 삽입 순서로 개체 의 각 요소에 대한 값 을 포함하는 새 Iterator 개체를 반환
    ex) ['item 1', 'item 2', 'item 3']

  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객체는 키-값 쌍을 보유하고 키의 원래 삽입 순서를 기억합니다 . 모든 값(객체 및 기본 값 모두 )은 키 또는 값으로 사용할 수 있습니다. Map - JavaScript | MDN

해당 문제에서 Map을 사용하게 된 이유

선택된 옵션을 담는 상태는 여러가지가 될 수 있다.

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',
}
  1. 추가/삭제/변경 등이 간단하다.
  2. 삽입 순서가 보장 되지 않는다.

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에 대한 값 동일성"을 참조하십시오.
  1. Map -MDN objects_vs._maps
    key type의 유연성, 순서 보장, 요소 추가/제거의 경우 Object에 비해 좀더 나은 성능

Preivew

profile
왜?를 생각하며 개발하기, 다양한 프로젝트를 경험하는 것 또한 중요하지만 내가 사용하는 기술이 어떤 배경과 이유에서 만들어진 건지, 코드를 작성할 때에도 이게 최선의 방법인지를 끊임없이 질문하고 고민하자. 이 과정은 앞으로 개발자로 커리어를 쌓아 나갈 때 중요한 발판이 될 것이다.

0개의 댓글