Recoil selector set에 대해서
set 함수는 원하는 state가 어떤 것이든지 그걸로 수정하도록 해주는 기능
selector도 atom과 동일하게 array형태로 [value, setValue] 형태로 준다.
// setter 사용법
const onHoursChange = (event: React.FormEvent<HTMLInputElement>) => {
setHours(+event.currentTarget.value);
};
// 구현부
set: ({set}, newValue) => {
const minutes = Number(newValue) * 60;
set(minuteState, minutes);
}
drag and drop (react-beautiful-dnd)
react로 드래그 앤 드랍을 지원하는 라이브러리
npm i react-beautiful-dnd
npm i --save-dev @types/react-beautiful-dnd
- DragDropContext
- 드래그 앤 드랍을 포함할 부분
- children을 항상 필요로 한다.
- onDragEnd
- 유저가 드래그를 끝낸 시점에 불려지는 함수
- Droppable
- 드롭할 수 있는 영역
- id를 필요로한다.
- children을 필요로하는데 children은 함수여야한다.
- Draggable
- 드래그할 영역
- id를 필요로한다.
- children을 필요로하는데 children은 함수여야한다.
<DragDropContext onDragEnd={onDragEnd}>
<div>
<Droppable droppableId="one">
{() => (
<ul>
<Draggable draggableId="first" index={0}>
{() => <li>One</li>}
</Draggable>
<Draggable draggableId="second" index={1}>
{() => <li>Two</li>}
</Draggable>
</ul>
)}
</Droppable>
</div>
</DragDropContext>
droppable에서 주는 인자
- provided
- innerRef
- draggableProps : 요소가 기본적으로 드래그 되기를 원할 때
- dragHandleProps : 모든 곳 말고 특정한 부분에서만 드래그하게 하고 싶을 때
아래 코드의 경우 불을 통해서만 드래그 이벤트가 일어남
<Droppable droppableId="one">
{(magic) => (
<ul ref={magic.innerRef} {...magic.droppableProps}>
<Draggable draggableId="first" index={0}>
{(magics) => (
<li ref={magics.innerRef} {...magics.draggableProps}>
<span {...magics.dragHandleProps}>🔥</span>
One
</li>
)}
</Draggable>
</ul>
)}
</Droppable>
- provided.placeholder는 드래그할 동안 영역을 고정시켜서 사이즈가 변하는 것을 막음
- draggable을 움직일 때 리스트가 작아지는 현상 방지
<Board ref={magic.innerRef} {...magic.droppableProps}>
{toDos.map((toDo, index) => (
<Draggable key={`todo-${index}`} draggableId={toDo} index={index}>
{(magics) => (
<Card ref={magics.innerRef} {...magics.dragHandleProps} {...magics.draggableProps}>
{toDo}
</Card>
)}
</Draggable>
))}
{magic.placeholder}
</Board>
하지만 아직 리스트를 움직인다고 해서 위치가 바뀌는 효과는 없음.
onDragEnd와 recoil을 사용하면 정렬 기능을 넣을 수 있다.
- onDragEnd : 드래그가 끝났을 때 실행되는 함수
- destination : 드래그의 목적지
- source : 드래그의 시작지
이 두 가지의 정보를 사용하여 위치를 조정할 수 있다.
소스의 인덱스의 위치를 지우고 목적지의 인덱스 위치를 추가하여 shift해주는 전략
source의 index 지우기
// source의 index 지우기 toDosCopy.splice(source.index, 1)
destination의 index 추가
// destination의 index 추가 toDosCopy.splice(destination?.index, 0, draggableId)
하지만 아직 문제가 있는데 연산 시간의 문제로 인해, 순간 텍스트가 이상하게 보인다.
랜더링하는 과정에서 문제가 있는데 f를 처음으로 옮겨도 a, b, c, d, e, f 모두 다시 랜더링되기 때문
아래의 방법으로 해결할 수 있다.
React Memo
변동에 영향이 없는 컴포넌트까지 다시 랜더링 되는 것을 막아준다.
export default React.memo(DraggableCard)
보드를 여러 개 만들기
done, doing, to-do의 보드를 만들어본다.
- object -> array
- Object.prototype.keys을 사용하면 object의 keys를 배열로 만들어준다.
const toDoState = atom<IToDoState>({
key: 'toDo',
default: {
to_do: ['a', 'b'],
doing: ['c', 'd', 'e'],
done: ['f'],
},
})
const [todos, setTodos] = useRecoilState(toDoState)
const toDos = Object.keys(todos)
보드 내 위치 바꾸기
onDragEnd 내에서 처리를 해주는데 보드 내이므로
목적지의 id와 소스의 id가 같을 때만 실행하도록 해야함
나머지는 비슷한데, ...을 사용하여 레퍼런스 복사가 아니라 전 상태와 동일한 새 배열을 만들어서 적용해줘야한다.
등록돼있는 모든 보드를 가져와서, 보드 내 상태를 바꾼 후 변한 상태를 기존에 적용시키는 방식
setToDos((allBoards) => {
const boardCopy = [...allBoards[source.droppableId]];
boardCopy.splice(source.index, 1);
boardCopy.splice(destination?.index, 0, draggableId);
return {
...allBoards,
[source.droppableId]: boardCopy,
};
});
보드 간 위치 바꾸기
- source의 보드에서 해당 원소를 제거
- destination의 보드에서 해당 원소를 추가
- 위 두 가지의 보드를 전체 보드에 반영
setToDos((allBoards) => {
const sourceBoard = [...allBoards[source.droppableId]]
const destinationBoard = [...allBoards[destination.droppableId]]
sourceBoard.splice(source.index, 1)
destinationBoard.splice(destination?.index, 0, draggableId)
return {
...allBoards,
[source.droppableId]: sourceBoard,
[destination.droppableId]: destinationBoard,
}
})
추가적인 기능
내가 보드를 떠나는지 도착하는지에 따라서 area의 색상을 변경해주고 싶음
이 때 필요한 것이 droppable의 두 번째 인자인 snapshot
snapshot
+ isDraggingOver : 유저가 보드 위로 드래그해서 들어오고 있는지 알려줌
+ draggingFromThisWith : 유저가 해당 보드로부터 드래그를 시작했는지 알려줌
일단 시간 상 여기까지만 하고 나머지 시간에 채워넣을게요...ㅜ