[Develog] 타입스크립트로 TodoList만들기2
어제에 이어서 추가 진행된 부분
1) 삭제 기능
2) 완료 기능
3) 취소 기능
4) 상세페이지 토글창 구현
삭제까지는 유데미 영상을 보면서 클론코딩으로 진행했고
이후 기능은
깃허브링크
GitHub - jeLee94/type_prac
Contribute to jeLee94/type_prac development by creating an account on GitHub.
github.com
0. 이미지
1. 삭제 기능
TodoItems.tsx는 Todos에서 RemoveItem을 받아온다.
Todos.tsx는 App.tsx에서 onDeleteHandler를 받아온다.
App.tsx에서는 state를 set하도록 함수를 정의한다.
2. 완료/취소 기능
흐름은 위와 같고 App.tsx에서 정의하는 함수만 다르다
onDoneHandler에 이벤트가 발생하면 매개변수로 전달받은 todoItem의 isDone이 토글된다.
true일 때는 완료가 된 상태이므로
todos에는 현재 완료 버튼을 누른 todo를 제외한 나머지만 남게하고
doneTodos에는 이전에 doneTodos의 데이터와 현재 todo를 합친다.
false일 때는 done -> todo로 취소를 하는 상황이므로
true일 때와 반대로 작성한다.
3. 상세 페이지 구현
todo 컴포넌트 하나를 누르면 우측에 화면이 토글되도록 한다.
todo 하나를 눌러 활성화 된 상태에서 다른 todo를 누르면 토글되지 않고 다른 todo의 내용으로 변환되도록 한다.
4. 코드
App.tsx
function App() {
const [allList, setAllList] = useState<Todo[]>([]); //상세 토글 창을 위해 전체 리스트 생성
const [todos, setTodos] = useState<Todo[]>([]);
const [doneTodos, setDoneTodos] = useState<Todo[]>([]);
const [isDetailShown, setIsDetailShown] = useState(false);
const [onClickId, setOnClickId] = useState('');
const addTodoHandler = (todoText: string) => {
const newTodo = new Todo(todoText);
setTodos((prevTodos) => {
return prevTodos.concat(newTodo);
});
setAllList((prevTodos) => {
return prevTodos.concat(newTodo);
});
};
const onDeleteHandler = (todoId: string) => {
if (window.confirm('정말 삭제하시겠습니까?')) {
setTodos((prevTodos) => {
return prevTodos.filter((todo) => todo.id !== todoId);
});
setDoneTodos((prevTodos) => {
return prevTodos.filter((todo) => todo.id !== todoId);
});
setAllList((prevTodos) => {
return prevTodos.filter((todo) => todo.id !== todoId);
});
}
};
const onDoneHandler = (todoItem: Todo) => {
todoItem.isDone = !todoItem.isDone;
if (todoItem.isDone) {
setTodos((prevTodos) => {
return prevTodos.filter((todo) => todo.id !== todoItem.id);
});
setDoneTodos((prevTodos) => {
return prevTodos.concat(
todos.filter((todo) => todo.id === todoItem.id)
);
});
} else {
setTodos((prevTodos) => {
return prevTodos.concat(todoItem);
});
setDoneTodos((prevTodos) => {
return prevTodos.filter((todo) => todo.id !== todoItem.id);
});
}
};
const onDetailHandler = (todoItem: Todo) => {
console.log('클릭!');
!isDetailShown ? setIsDetailShown(true) : setOnClickId(todoItem.id); //디테일 창이 false이면 보여주고 보여주고 있으면 내용 변경
todoItem.id === onClickId && isDetailShown === true
? setIsDetailShown(false)
: setOnClickId(todoItem.id);
setOnClickId(todoItem.id);
};
return (
<Div>
<MainDiv>
<AddTodo onAddTodo={addTodoHandler} />
<Todos
todoItems={todos}
doneItems={doneTodos}
onDeleteHandler={onDeleteHandler}
onDoneHandler={onDoneHandler}
onDetailHandler={onDetailHandler}
/>
</MainDiv>
{/* 상세페이지 토글기능 */}
{isDetailShown
? allList.map((todo) =>
todo.id === onClickId ? <DetailPage todo={todo} /> : null
)
: null}
</Div>
);
}
Todos.tsx
import React from 'react';
import styled from 'styled-components';
import Todo from '../models/todo';
import TodoItem from './TodoItems';
// import DoneItem from './DoneItem';
const Todos: React.FC<{
todoItems: Todo[];
doneItems: Todo[];
onDeleteHandler: (todoId: string) => void;
onDoneHandler: (todo: Todo) => void;
onDetailHandler: (todo: Todo) => void;
}> = (props) => {
return (
<Div>
<TodoDiv>
<p>To Do</p>
{props.todoItems.map((item) => (
<TodoItem
key={item.id}
item={item}
onRemoveItem={props.onDeleteHandler.bind(null, item.id)}
onDoneItem={props.onDoneHandler.bind(null, item)}
onDetailItem={props.onDetailHandler.bind(null, item)}
/>
))}
</TodoDiv>
<hr />
<DoneDiv>
<p>Done</p>
{props.doneItems.map((item) => (
<TodoItem
key={item.id}
item={item}
onRemoveItem={props.onDeleteHandler.bind(null, item.id)}
onDoneItem={props.onDoneHandler.bind(null, item)}
onDetailItem={props.onDetailHandler.bind(null, item)}
/>
))}
</DoneDiv>
</Div>
);
};
export default Todos;
const Div = styled.div`
display: flex;
`;
const TodoDiv = styled.div`
width: 50%;
display: flex;
flex-direction: column;
align-items: center;
height: 74vh;
overflow: auto;
`;
const DoneDiv = styled.div`
width: 50%;
display: flex;
flex-direction: column;
align-items: center;
height: 74vh;
overflow: auto;
`;
TodoItems.tsx
import React from 'react';
import styled from 'styled-components';
import Todo from '../models/todo';
const TodoItem: React.FC<{
item: Todo;
onRemoveItem: (event: React.MouseEvent) => void;
onDoneItem: (event: React.MouseEvent) => void;
onDetailItem: (event: React.MouseEvent) => void;
}> = (props) => {
return (
<Div>
<TextDiv onClick={props.onDetailItem}>{props.item.text}</TextDiv>
<p>
<button onClick={props.onDoneItem}>
{props.item.isDone ? '취소' : '완료'}
</button>
<button onClick={props.onRemoveItem}>삭제</button>
</p>
</Div>
);
};
export default TodoItem;
const Div = styled.div`
border: 1px solid black;
border-radius: 10px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 50%;
height: 7.5rem;
margin: 20px;
`;
const TextDiv = styled.div`
width: 100%;
height: 4rem;
display: flex;
align-items: center;
justify-content: center;
`;
DetailPage.tsx
import styled from 'styled-components';
import Todo from '../models/todo';
const DetailPage: React.FC<{ todo: Todo }> = (props) => {
return (
<DetailDiv>
<div>{props.todo.id}</div>
<div>{props.todo.text}</div>
<div>
<button>수정</button>
<button>삭제</button>
</div>
</DetailDiv>
);
};
export default DetailPage;
const DetailDiv = styled.div`
width: 60vw;
height: 80vh;
margin: 5rem auto;
background-color: gray;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
`;