저번에 넘거갔던 selector의 기능 중 set에 대해서 알아보자
set은 atom state를 수정(set)하는 것을 도와주는 속성이다.

쉽게말해 selector({key: 'value', get: ()=>{}, set: ()=>{} })에서
key는 selector를 식별하는 고유값,
get은 atom의 state를 받아와(get) 원하는 방식으로 가공 후 반환해주는 state 역할을 하게 되고,
set은 atom의 state를 새로운 값으로 수정해(set) 줄 수 있는 setState역할을 하게된다.
setState를 사용할 때
setState((state) => { newState})를 사용하는 것과 같이
selector에서 set도
set : ({set}, newValue)=>{
set(atomState, newvalue)
}
로 atomState 변경할 값을 set()함수의 두번째 인자로 넣어주고 useSetRocilState, useRecoilState을 사용해서 setState처럼 사용할 수 있다.

이로서 selector로 atom의 state를 get, set 모든 기능이 가능하다. 이렇게되면 최초에 입력받은 data는 atom에 저장되어있고 selector를 이용해서 그 중 필요한 부분만 가공해서 selector의 get으로 반환 해줄 수도 있고, selector의 set함수를 이용해서 다시 atom의 state를 가공할 수도(setState) 있어진다.

즉, 필요한 부분을 atom을 이용해서 get, set을 해주는게 아니라
모든 data가 담긴 atom 중 필요한 부분은 selector한 후 selector에서 get, set 해줄 수 있어진다. 이러면 필요한 부분들을 위해 각각 atom 값을 만들 필요도 없어지고, componenet에서 넘겨받는 data, 렌더링 고려요소가 현저히 줄 수 있으므로 성능이 개선될 것 같다.

selector의 또다른 장점은 하나의 atom에서 가져올 필요없이 여러가의 값을 select 할 수 있다. 그래서 필요한 값들은 가져와 가공 후 새로운 state를 만들어 줄 수 있고 기존의 data는 그대로 남아있는다.



자 이제 trello cloning을 시작할 것인데 이에 앞서 trello의 핵심 기능인 각각의 list들을 drag and drop할 수 있어야 한다. react라이브러리인 react-beautful-dnd를 설치하자 ``` npm i /react-beautiful-dnd npm i --save-dev @types/react-beautiful-dnd ```

(참고 : https://www.npmjs.com/package/react-beautiful-dnd)
먼저 react-beautful-dnd를 배워보자

  • react-beautful-dnd 사용법

먼저 크게 3가지 테마가 있다.


1) DragDropContext

dnd 기능을 사용할 장소를 지정해주는 것이다. 필수prop로 onDragEnd와 children을 필요로 한다.
먼저 onDragEnd는 안에 children을 drag한 이후 drop 했을때 발생할 행동의 받는 이벤트이다.
children으로는 React.Node들이 들어올 수 있다.


2) Droppable

drop될 수 있는 area를 형성하며 필수 prop로 droppableId, children를 필요로 한다.

droppableId는 drop할 area의 고유값을 의미하고 children으로는 DragDropContext와는 다르게 React.Node를 받을 수 없고 함수의 반환 값으로서 React.ReactElement< HTMLElement>를 받는다.

children에는 (provided) => {< ul>list< /ul>} 이런식으로 들어가야 한다.
인자로 받는 provided는
이러한 type을 갖고있으며 innerRef와 droppableProps를 children의 prop로 넣어주어야 부모 자식간의 접근과 css가 작동하여 dnd을 동적으로 수행할 수 있고
이렇게 만들어 준 children이 Droppable가 하나의 Board가 되는 것이다.

board안의 list들을 옮길때 요소가 줄어드므로 heighth가 줄어드는데 이제 placeholder를 사용하면 board의 크기를 list개수 대비로 고정시킬 수 있다.

3) Draggable

drag할 수 있는 list들을 만드는 곳으로 필수 prop로는 draggableId, index, children이 있다.
마찬가지로 draggableId는 식별자역할을 하는 고유값이고,
index는 Droppable 내에서 몇번째 위치하는지 알려주는 index를 넣어야하며 고유하고, 연속적이여야한다.
children은 Droppable과 마찬가지로 (provided) => {< li>item< /li>} 형태로 넣어줘야한다.
인자로 받는 provided는
이러한 type을 갖고있으며 Droppable과와 동일하게 innerRef와 draggableProps는 자식들의 prop로 넣어주어야 부모 자식간의 접근과 css가 작동하여 dnd을 동적으로 수행할 수 있고
dragHandleProps를 넣는다면 drag할 수 있는 영역과 없는 영역으로 나눌 수 있다.

위의 코드같이 되면 이모티콘은 drag할 수 있고 string값은 drag할 수 없게 된다.


여기서 주의점이 map같은 함수를 사용할때 고유값이 되는 key를 지정해줘야하는데 이때 droppableId, draggableId 의 고유값과 동일하게 해야 한 태그에 하나의 고유값이 생성되며 정상 작동하게 된다.

위의 항목들을 준수해서 기본 뼈대 코드를 만들면

const list = ["a", "b", "c", "d", "e", "f", "g"]
...
    <Container>
      <DragDropContext onDragEnd={onDragEnd}>
        <Title>Trello cloning</Title>
        <Boards>
          <Droppable droppableId="one">
            {(provided) => (
              <ul ref={provided.innerRef} {...provided.droppableProps}>
                {list.map((toDo, idx) => (
                  // ! key랑 draggableId랑 같아야 함 dnd에서 그렇게 정함
                  <Draggable key={toDo} draggableId={toDo} index={idx}>
                    {(provided) => (
                      <li
                        ref={provided.innerRef}
                        {...provided.draggableProps}
                        {...provided.dragHandleProps}
                      >
                        <span>🥱</span>
                        {toDo}
                      </li>
                    )}
                  </Draggable>
                ))}
                {/* //! 옮길 수 있는 요소 선택 */}
                <Draggable draggableId="second" index={1}>
                  {(provided) => (
                    <li ref={provided.innerRef} {...provided.draggableProps}>
                      <span {...provided.dragHandleProps}>🥱</span>
                      two
                    </li>
                  )}
                </Draggable>
                {/* //! 태두리 크기 고정 */}
                {provided.placeholder}
              </ul>
            )}
          </Droppable>
        </Boards>
      </DragDropContext>
    </Container>

와 같이 되며
ul, li에 style을 입혀주면 아래 그림과 같이 된다.

recoil 이용해서 atom에 state만들기

지금까지 배운것처럼 동일하게 atom을 이용해서 todos에 들어갈 data를 임시로 만들어준다.

export const toDoState = atom({
  key: "ToDo_Trello",
  default: ["a", "b", "c", "d", "e", "f", "g"],
});

이제 진짜 dnd작업을 했을때 atom state가 변하면서 정상 작동 되도록 onDragEnd로직을짜줘야한다.

onDragEnd 함수 만들기

먼저 넘겨 받는 인자를 찍어보면

  const onDragEnd = (args: any) => {
    console.log(args);
  };

이러한 값을 반환한다.
여기서 destination 안에는 drop에 대한 data가 source 안에는 drag에 대한 data가 draggableId 안에는 선택한 item의 state값이 들어있음을 알 수 있다.

우리가 짜야하는 로직을 생각해보면 먼저 drag한 item의 index에 해당하는 값을 없애주고 drop한 index에 추가해줘야한다 여기에 state인 것을 감안해서 직접 바꿀 수 없으므로 복사 -> 삭제 -> 추가 한 새로운 배열을 반환하는 구조로 로직을 짜야한다.

이때 args의 타입은 DropResult 가 되야한다.
위에서 DragDropContext 설명할때 말했듯이 onDragEnd의 prop 타입은 DropResult이다.

1)깊은 복사
깊은 복사를 하는 방법은 두가지로 ES6문법을 이용하는방법과 slice()를 이용하는 방법이있다.

const newList = oldList.slice();
const newList = [...oldList];

아무렇게나 해서 복사를 해주고

cf) JS에는 깊은 복사와 얕은 복사가 있는데
const newList= list 를 이용해서 바로 array를 할당해주면 얕은복사가된다.
이는 list안의 값을 복사하는것이 아니라 사실 list를 저장하고 있는 주소를 복사하는 것으로 newlist와 list가 연결되어있어 어느 한 곳을 바꾸게 되면 주소를 할당하고 있는 두 값이 모두 바뀐다. 따라서 주소를 할당하는것이 아닌 값을 할당하는 새로운 배열을 만들어주는 깊은 복사를 해주어야 한다.

2)drag index 제거
.splice(dragIndex, 1)을 사용해서 drag한 index를 없애주고

3)drop index 추가
.splice(dropIndex, 0, draggableId)를 사용해서 drap한 index에 선택한 item의 state값을 넣어준다.

최종 완성한 onDragEnd함수는 아래와같아진다.

  const onDragEnd = ({ destination, draggableId, source }: DropResult) => {
    console.log(
      "draggin finished",
      draggableId,
      source.index,
      "=>",
      destination.droppableId,
      destination.index
    );

    const dragIndex = source.index;
    const dropIndex = destination?.index;
    // ! 같은 자리에 두었을 때
    if (!draggableId) return;

    setList((oldList) => {
      // ! 0)깊은 복사 두 가지 방법
      // const newList = oldList.slice();
      const newList = [...oldList];

      newList.splice(dragIndex, 1);
      newList.splice(dropIndex, 0, draggableId);
      console.log("dragIndex", dragIndex);
      console.log("dropIndex", dropIndex);
      console.log("newList", newList);

      return newList;
    });
  };

여기까지 완성되면 이제 글자가 옮겨진다.
하지만 아직 글자를 옮길 때 리렌더링 되면서 글자가 깨지는 현상이 있다.

그 원인은 바로 react의 기본 기능인 re-rendaring 때문인때 기본적으로 react는 부모 state가 바뀌면 자식의 state가 리렌더링된다. 이러한 기능의 단점이 바로 지금처럼 리렌더링 할 필요없는 자식들도 다시 렌더링 된다는 것이다.

예를 들어

<ul>
	<li>1</li>
	<li>2</li>
    <li>3</li>
    <li>4</li>
</ul>

이런 구조에 compoenent가 있다고 할때 ul쪽의 state가 바뀌면 자식인 li모두가 변화가 있는지와 상관없이 리렌더링 되는 것이다 이런 비효율적인 렌더링을 없애기 위해 렌더링 최적화를 해주는데 이를 위한 기법으로 몇가지가 있다.

  1. state선언은 필요한 componenet들 중 최상위에 한다.

  2. obj type의 state는 최대한 분할해서 선언한다. (같이 변경되는 값들끼리 state화)

  3. class형 이라면 shouldComponenetUpdate()를 잘 활용, hook에서는 React.memo 활용해서 변경되는 값만 메모화자식componenet를 만들어준다. (메모제이션 기법을 활용)

  4. component를 mapping할 때 key값을 index를 쓰는 것을 지양한다.
    (index는 중간요소가 바뀌면 다른 요소들도 모두 변경되므로 리렌더링의 원인이된다. 따라서 고유하며 서로 독립적인 값을 key값에 넣는 것이 좋다)

  5. useMemo, useCallback을 사용해서 state를 메모제이션 해준다.

(참고 : https://cocoder16.tistory.com/36)
등의 방법이 있다.

이번에는 React.memo로 card componenet를 만들어 줌으로써 board의 state가 변경될때 새로 변경된 card만 리렌더링 되고 변경이 없는 card component는 리렌더링 되지 않게 해준다.

이로써 불필요한 리렌더링이 많아지는 경우를 해결해주고 글자가 깨지는 현상을 해결했다.


그 다음에 board를 여러개 만들 것인데 이때 또 문제가 생긴다.
먼저 여러개의 board가 와 card가 생길 것이므로 컴포넌트 화 하자.

그리고 atom의 state값을 여러개의 board를 만들 것이므로 obj형태로 바꿔주고

export const toDoState = atom({
  key: "ToDo_Trello",
  default: {
    To_Do: ["a", "b", "c"],
    Doing: ["d", "e"],
    Done: ["f", "g"],
  },
});

map은 array의 method이기 때문에 atom state를 obj로 만들면 arrer가 생긴다.
우리는 state obj의 key값에 따라 Board component를 띄울 것이므로

따라서 state를 Object.keys()를 이용해서 key값 array를 생성하므로 이 값에 map을 적용하여 Obj의 key값에 따른 value 값들을 따로따로 사용할수 있다.
이것을 이용해서 atom 의 state값을 받아온다.

          {Object.keys(list).map((boardId) => (
            <TrelloBoard key={boardId} boardId={boardId} list={list[boardId]} />
          ))}

이제 각각의 TrelloBoard component 안에 key값에 따라 value를 담은 array가 들어가게된다( = list[boardId])
여기서 list가 error가 나는데 그 이유는 list에는 string이 들어가지만 list[boardId] 에는 우리가 지정한 값만 들어갈 수 있기 때문에 타입 error가 난다.
따라서 atom state에 지정한 값 말고도 같은 형태의 값을 받아올 수 있게 해준다. 이렇게하면 사용자가 board를 직접 만들 수 있어진다.

interface IToDoState {
  [key: string]: string[];
}

export const toDoState = atom<IToDoState>({
  key: "ToDo_Trello",
  default: {
    To_Do: ["a", "b", "c"],
    Doing: ["d", "e"],
    Done: ["f", "g"],
  },
});

이제 onDragEnd도 Obj로 바꾼 것에 맞게 변경해준다.

이거는 내일할꺼다!

ps.
오늘js 공부는 여기까지하고 운동,영어, python을 하다가 자야징~~
내일은 계절학기 기말이다 드디어 끝나는구나!!! 내일 왔다갈때는 python강의를 들어야겠다ㅎㅎ 이제 후딱 홈트 하고 영어공부하고 슈수슈융

profile
과정을 적는 곳

0개의 댓글