4 분 소요

Course

Wanted Pre Onboarding FE Course

study

팀 프로젝트

회의

기술 스택

  • tailwind
  • React

기능 명세 확인

컴포넌트 나누기

  • <Input /> - placeholder
  • <StackedList /> - width, height 전달 받아 인라인 스타일로 적용
  • <ListItem /> - children 전달받기
  • <Button /> - 스타일만 필요
  • <Toggle /> - on, setOn, onCallback, offCallback
  • <Radio /> - 라디오 이쁜거 있으면 제공, 없으면 쌩으로 쓰기
  • <Popup /> - 소메뉴

컨벤션 정하기

복잡하면 오히려 생산성이 떨어지고 지키기 힘들어질 것 같다고 판단했다.

따라서, 간결하게 템플릿을 만들었다.

커밋 메시지 규칙

Feat: 작업 내용
유형 설명
Feat 새로운 기능 추가
Add 파일추가(이미지, 아이콘, 폰트…)
Fix 버그 수정
Docs 문서 수정
Style 스타일(CSS, 퍼블, UI적인 요소) 수정
Comment 주석 추가, 수정, 삭제
Chore 빌드업 업무, 패키지 매니저 수정
Refactor 수정
Rename 변수명, 함수명, 파일명, 폴더명 변경
Remove 변수, 함수, 기능, 파일, 폴더 삭제
!BREAKINGCHANGE 큰 api 변경, 큰 구조적인 변경
!HOTFIX 급한 치명적인 버그 수정
Deploy 배포

Lint 규칙

  • prettier 사용

PR 형식

  • PR 제목
Feat: 작업 내용
Refactor: 작업 내용
  • PR 본문
### 반영 브랜치
feature/toggle -> dev

### 작업 사항 상세
로그인 , 구글 소셜 로그인 기능을 추가했습니다.

아이템 항목 이동 구현 (컨테이너 간 이동)

멘붕의 현장이었지만, 팀원 분의 도움으로 끝까지 구현할 수 있었다.

아이템 이동

  1. state 선언 및 관리

state는 app(가장 최상위)에서 관리한다.

먼저, items(모든 아이템)을 디폴트로 leftItems에 넣어놓는다. rightItems는 빈 배열로 둔다.

클릭되었을 때 선택된 아이템들의 배열도 만들어 놓는다.

중요한 점은 left 와 right를 나누는 것이 중요하다.

=> 왼쪽 컨테이너에 있는 아이템을 클릭해놓고, 오른쪽 컨테이너에 있는 아이템을 클릭했을 때 두 군데 모두 선택이 되어서는 안되기 때문에 나누어 둔다.

const items = emojiMenus;

const initialSeleted = {
  left: [],
  right: [],
};

const [leftItems, setLeftItems] = useState(items);
const [rightItems, setRightItems] = useState([]);
const [selectedItems, setSelectedItems] = useState(initialSeleted);
  1. 한 개씩만 이동

기본적으로 하나를 선택해서 이동 버튼을 클릭하면 다른 쪽으로 옮겨가도록 한다.

이 때, 왼쪽에서 선택하는 함수와 오른쪽에서 선택하는 함수를 구분해준다.

// moveItems.js
const handleLeftSelect = (e, id) => {
  setSelectedItems((prev) => ({
    left: prev.left.map(({ id }) => id).includes(id) // 이미 있던 아이템이라면
      ? prev.left.filter(({ id: itemId }) => itemId !== id) // selected에서 삭제
      : leftItems.filter(({ id: itemId }) => itemId === id), // 아니면 그냥 selected에 추가
    right: [], // 오른쪽을 비워줌으로써 왼쪽/오른쪽 컨테이너에서 중복으로 클릭 안되도록
  }));
};

// App.js
<Selector
  list={leftItems}
  selectedItems={selectedItems.left}
  handleSelect={handleLeftSelect}
/>;

이제 리스트 컨테이너 컴포넌트에서 아이템이 클릭될 때마다 해당 아이템의 id를 보내준다.

e는 추후 ctrl, shift 키 때문에 둠

<StackedList>
  {list?.map((item) => {
    return (
      <ListItem
        key={item.id}
        id={item.id}
        onClick={(e) => handleSelect(e, item.id)}
        className={`block hover:bg-gray-100 select-none
        // 모든 아이템을 돌다가 선택된 아이템과 같은 id가 있으면 css 적용
        ${
          selectedItems.map(({ id }) => id).includes(item.id)
            ? "bg-gray-100"
            : "bg-white"
        }
        `}
      >
        <div className="p-3 cursor-pointer">
          <span>{item.emoji}</span>
          <span>{item.name}</span>
        </div>
      </ListItem>
    );
  })}
</StackedList>

오른쪽 버튼 클릭 시, 왼쪽에 있던 아이템들에서 선택된 아이템들 빼준다. (id 이용)

오른쪽에서는 선택된 아이템들을 추가해준다.

끝나면 선택된 아이템 모두 날리기

const moveToRight = ({ all = false }) => {
  // 선택된 아이템들의 id 배열
  const selectedItemIds = all
    ? leftItems.map(({ id }) => id)
    : selectedItems.left.map(({ id }) => id);

  // leftItems에서 선택된 아이템 제거
  setLeftItems((prev) =>
    prev.filter((item) => !selectedItemIds.includes(item.id))
  );
  // rightItems에 선택된 아이템 추가
  setRightItems((prev) =>
    all ? [...prev, ...leftItems] : [...prev, ...selectedItems.left]
  );
  // 초기화
  setSelectedItems(initialSeleted);
};

이렇게 하면 하나씩 이동은 쉽게 구현할 수 있다. (왼쪽, 오른쪽 변수만 다르고 로직 모두 동일)

키포인트는 선택된 아이템 배열을 left와 right로 나누는 것이 중요하다. 그래야 왼쪽 컨테이너 오른쪽 컨테이너에서 선택 상태가 중첩되지 않을 수 있다.

ctrl, shift 다중 선택 및 이동

아이템 클릭 시 받아온 이벤트에서

e.ctrlKey, e.shiftKey로 키보드 이벤트를 확인할 수 있다 (boolean으로 나옴)

// shift 클릭
// 선택된 아이템들, 왼쪽/오른쪽 아이템들, 지금 클릭된 id받아서 범위 내의 아이템들 반환
const shiftClick = (selectedItems, originItems, id) => {
  // start 초기값이 0이기 때문에 처음부터 shift 누르고 아이템 선택하면 맨 처음 아이템부터 모두 선택
  let [start, end] = [0, 0];

  // start는 selectedItems에 가장 처음에 있던 아이템의 id로 index를 구함
  if (selectedItems[0]) {
    const startId = originItems.filter(
      ({ id }) => id === selectedItems[0].id
    )[0].id;
    // start 아이템의 인덱스 위치
    start = originItems.map(({ id }) => id === startId).indexOf(true);
  }
  // end는 지금 클릭된 id로 index를 구함
  end = originItems.map(({ id: itemId }) => itemId === id).indexOf(true);

  // start 부터 end 까지 선택된 것으로 설정
  const selects = originItems.filter(
    (item, index) => index >= start && index <= end
  );

  return selects;
};
const handleLeftSelect = (e, id) => {
  // shift가 눌린채로 이벤트가 발생했다면
  if (e.shiftKey) {
    // shift 클릭 관리하는 함수로 넘어감
    // 클릭 범위의 아이템들을 반환해줌
    const selects = shiftClick(selectedItems.left, leftItems, id);

    setSelectedItems((prev) => ({
      left: prev.left.map(({ id }) => id).includes(id)
        ? prev.left.filter(({ id: itemId }) => itemId !== id)
        : selects, // start~end의 아이템
      right: [],
    }));
  } else {
    // ctrl 다중 & 하나씩 옮기기
    setSelectedItems((prev) => ({
      left: prev.left.map(({ id }) => id).includes(id) // 이미 있던 아이템이라면
        ? prev.left.filter(({ id: itemId }) => itemId !== id) // selected에서 삭제
        : e.ctrlKey || e.metaKey // ctrl, cmd 키 확인
        ? [
            ...prev.left,
            ...leftItems.filter(({ id: itemId }) => itemId === id), // ctrl/cmd 키 누른거면 축적
          ]
        : leftItems.filter(({ id: itemId }) => itemId === id), // 아니면 그냥 selected에 추가
      right: [],
    }));
  }
};

처음에 너무 막막하고, 멘붕이었다. 정말로 자괴감이 들었다.

하지만, 머리를 식히고 다시 정신차리고 보니 또 어느샌가 되긴 했다.

차근차근, divide & conquer가 매우매우 중요하다고 생각한다.

회고 (TIL)

2022.02.24 Daily 회고

✏오늘 한 일

  • 팀 과제 전 회의
  • 팀 과제 수행
    • 아이템 옮기기 기능
    • 아이템 다중 선택 기능

⁉느낀 점

다른 과제를 시작한다. 저번 과제에서 부족한 점이었던 컨벤션을 미리 회의를 통해서 정해두어서 앞으로도 부족한 점을 회고를 통해 개선해나가면 좋겠다.

실력에 비해 어려운 부분을 맡은 것 같다. 하지만 오늘 안에 끝낼 생각이다. 팀원분들이 있기 때문에 걱정이 크게 되지는 않는다.

제대로 하고 있는 것이 맞는 건지에 대해 팀원분들께 여쭤보고 진행하니 진행 방향도 잘 잡히는 것 같아서 좋다.

🎃현재 나의 상태

오늘 안에 끝낼 생각이기 때문에 정신차려야 할 것 같다.

구현 자체도 어려운 기능이 아직도 많다는 것이 슬프지만 어차피 평생 가도 모든 것을 통달하는 것은 아니기에 위안을 조금 삼아본다.


댓글남기기