2024.06.20 프로그래머스 - React로 만드는 간단 ToDo 1
React로 ToDoList 만들어보기 1
클래스형 컴포넌트와 함수형 컴포넌트
클래스형 컴포넌트
- 리액트 초기에 사용했던 컴포넌트방식
생명주기
메서드가 있어 다양한 단계에서의 렌더링 조작이 가능하다.간단한 클래스 컴포넌트 작성
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
// ClassComponent.tsx import { Component, ReactNode } from "react"; class ClassComponent extends Component { // 생명주기, state 관련 로직 작성 render(): ReactNode { // Class Component의 출력부 return ( <div className="container"> <h1>HEllo World</h1> <p>이것은 클래스 컴포넌트</p> </div> ); } } // 외부 import를 위한 export export default ClassComponent;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
import React from "react"; import logo from "./logo.svg"; import "./App.css"; // Class Component 호출 import ClassComponent from "./components/ClassComponent"; function App() { const date = new Intl.DateTimeFormat("ko"); return ( <> <div className="App-header"> <h1>Hello React</h1> <p>반가우이</p> <p>오늘 날짜 : {date.format()}</p> </div> {/* ClassComponent 사용 */} <ClassComponent /> </> ); } export default App;
- Class Component는 호출하고 사용하는 과정이 함수 컴포넌트와 다르지 않다.
함수형 컴포넌트
- 현재 권장되는 방식의 컴포넌트
- 사용이 간편하다
import 및 export 부분은 클래스형 컴포넌트와 동일하게 진행
간단한 함수형 컴포넌트 작성
1 2 3 4 5 6 7 8 9
// FuncComponent.tsx import React from "react"; function FuncComponent() { return <div>함수형 컴포넌트</div>; } export default FuncComponent;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
// App.tsx import React from "react"; import logo from "./logo.svg"; import "./App.css"; import FuncComponent from "./components/FuncComponent"; function App() { const date = new Intl.DateTimeFormat("ko"); return ( <> <div className="App-header"> <h1>Hello React</h1> <p>반가우이</p> <p>오늘 날짜 : {date.format()}</p> </div> {/*함수형 컴포넌트 사용*/} <FuncComponent /> </> ); } export default App;
state 사용해보기
React 의 useState훅을 사용하여, 컴포넌트 내부의
상태
를 수정 및 사용할 수 있다.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
import React, { useState } from "react"; function TodoList(): JSX.Element { const [todos, setTodos] = useState<string[]>([ "공부하기", "잠자기", "밥먹기", ]); const title = "오늘 할 일"; return ( <div className="container"> <h1>{title}</h1> <div className="container"> <div className="board"> <ul> {todos.map((todo) => ( <li>{todo}</li> ))} </ul> </div> </div> </div> ); } export default TodoList;
- useState훅은 배열로 state 와 state Setter 를 반환한다.
- state Setter 로 state에 대한 값을 변경(수정) 할 수 있다.
데이터 반복 처리
보통 열거형 데이터의 반복 처리는 map 메소드를사용하여 처리한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
import React, { useState } from "react"; import TodoListItem from "./TodoListItem"; type TodoType = { id: number; text: string; isChecked: boolean; }; const initialTodos: TodoType[] = [ { id: 1, text: "공부하기", isChecked: false, }, { id: 2, text: "잠자기", isChecked: false, }, { id: 3, text: "밥먹기", isChecked: false, }, ]; function TodoListContainer(): JSX.Element { const [todos, setTodos] = useState<TodoType[]>(initialTodos); const title = "오늘 할 일"; return ( <div className="container"> <h1>{title}</h1> <div className="container"> <div className="board"> <ul className="todo-list"> {todos.map((todo) => ( <TodoListItem key={todo.id} text={todo.text} /> ))} </ul> </div> </div> </div> ); } export default TodoListContainer;
- map 또한 축약형으로 생략되었지만, return(반환문) 이 적용되어있다.
열거형 데이터 (반복가능한)를 React Component로 다룰때는
key
속성을 유의하여 작성해야 한다.
key
- React의 key는 해당 항목에 대한 식별자 역할을하며, 엘리먼트에 대한 고유성을 부여한다.
- React는 key를 활용하여, 변경이 필요한 부분만을 변경 및 엽데이트를 수행할 수 있기 때문에, 배열형 엘리먼트를 렌더링 할 때 특히 신경써주어야 하는 부분이다.
체크 박스 기능 추가
위의 todo 어플리케이션에 투두 목록을 체크 하면, 삭선이 그어지도록 코드를 추가하였다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
// TodoListItem.tsx import React from "react"; type TodoListItemType = { text: string; todoId: number; onChecked: (todoId: number) => void; isChecked: boolean; }; function TodoListItem({ todoId, text, onChecked, isChecked, }: TodoListItemType) { return ( <li className="todo-list-item"> <input type="checkbox" className="" onChange={() => onChecked(todoId)} /> {!isChecked && <span>{text}</span>} {isChecked && <del>{text}</del>} </li> ); } export default TodoListItem;
- check 엘리먼트를 누르면 props로 받은 onChange 콜백이 실행되어 isChecked Props의 값이 변경 됨에 따라 렌덩히라는 결과물이 달라진다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// TodoListContainer.tsx
import React, { useState } from "react";
import TodoListItem from "./TodoListItem";
import { setDefaultResultOrder } from "dns";
type TodoType = {
id: number;
text: string;
isChecked: boolean;
};
const initialTodos: TodoType[] = [
{
id: 1,
text: "공부하기",
isChecked: false,
},
{
id: 2,
text: "잠자기",
isChecked: false,
},
{
id: 3,
text: "밥먹기",
isChecked: false,
},
];
function TodoListContainer(): JSX.Element {
const [todos, setTodos] = useState<TodoType[]>(initialTodos);
// isChecked 핸들러
function checkHandler(todoId: number) {
setTodos((prevTodos) =>
prevTodos.map((item) =>
item.id === todoId ? { ...item, isChecked: !item.isChecked } : item
)
);
}
const title = "오늘 할 일";
return (
<div className="container">
<div className="container">
<div className="board">
<ul className="todo-list">
{todos.map((todo) => (
<TodoListItem
key={todo.id}
todoId={todo.id}
text={todo.text}
onChecked={checkHandler}
isChecked={todo.isChecked}
/>
))}
</ul>
</div>
</div>
</div>
);
}
export default TodoListContainer;
할 일 생성하기
- React Bootstrap 의 Button 컴포넌트사용
Bootstrap Button 컴포넌트 사용 에러
1
2
3
'Button' cannot be used as a JSX component.
Its return type 'ReactNode' is not a valid JSX element.
Type 'undefined' is not assignable to type 'Element | null'.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React from "react";
import { Button } from "react-bootstrap";
function TodoGenerator(): JSX.Element {
return (
<div className="todo-generator">
<input type="text" />
<Button variant="primary">생성</Button>
</div>
);
}
export default TodoGenerator;
- 위의 코드로 TodoGenerator를 구성했을 때 에러가 발생하였다.
여러가지 검색을 통해 다양한 케이스를 보고 설정 파일(package.json, tsconfig 등) 을 수정해보기도 하고
타입 명시나 CustomComponent형식으로 변경하여 해보았으나
해결되지 않았다….
일단 CRA로 React를 만들었기 떄문에 Vite로 재구축 하여 해당 과정을 수행해보니
ReactBootStrap이 아주 잘 되었다…
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// App.tsx
import { useState } from "react";
import "./App.css";
import TodoListContainer from "./components/TodoListContainer";
import TodoGenerator from "./components/TodoGenerator";
import Clock from "./components/Clock";
type TodoType = {
id: number;
text: string;
isChecked: boolean;
};
const initialTodos: TodoType[] = [
{
id: 1,
text: "공부하기",
isChecked: false,
},
{
id: 2,
text: "잠자기",
isChecked: false,
},
{
id: 3,
text: "밥먹기",
isChecked: false,
},
];
function App() {
const [todos, setTodos] = useState<TodoType[]>(initialTodos);
function checkHandler(todoId: number) {
setTodos((prevTodos) =>
prevTodos.map((item) =>
item.id === todoId ? { ...item, isChecked: !item.isChecked } : item
)
);
}
function addTodoItem(text: string) {
setTodos((prevTodos) => [
...prevTodos,
{ id: prevTodos.length + 1, text: text, isChecked: false },
]);
}
const title = "오늘 할 일";
return (
<>
<div className="container">
<h1>{title}</h1>
<TodoGenerator addTodoHandler={addTodoItem} />
<Clock />
<TodoListContainer todos={todos} checkHandlerFunc={checkHandler} />
</div>
</>
);
}
export default App;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// TodoGenerator.tsx
import { useState, ChangeEvent } from "react";
import { Button } from "react-bootstrap";
type TodoGeneratorProps = {
addTodoHandler: (text: string) => void;
};
function TodoGenerator({ addTodoHandler }: TodoGeneratorProps): JSX.Element {
const [newTodo, setNewTodo] = useState<string>("");
function changeTodoHandler(e: ChangeEvent<HTMLInputElement>) {
setNewTodo(e.target.value);
}
return (
<div className="todo-generator">
<input type="text" onChange={(e) => changeTodoHandler(e)} />
<Button
as="button"
variant="primary"
onClick={() => addTodoHandler(newTodo)}
>
생성
</Button>
</div>
);
}
export default TodoGenerator;
현재 시간 컴포넌트
- 현재 시간을 알려주는 컴포넌트를 TodoGenerator 밑에 추가 한다.
new Date()를 1초마다 할당한 state를 렌더링!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
import { useState } from "react"; export default function Clock(): JSX.Element { const [time, setTime] = useState(new Date()); setInterval(() => { setTime(new Date()); }, 1000); return ( <div> <h2>현재 시간 - {time.toLocaleTimeString()}</h2> </div> ); }
할 일 삭제하기
- 생성한 할일 목록을 지우려면, state로 관리중인 todos의 값 자체를 변경해야 한다.
- App.tsx 컴포넌트에서 todos에 대한 변경 함수를 작성후, Prop 내려주기를 통해 TodoListItem 에 함수를 전달해주면 해당 item에 대한 삭제 작업이 가능하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
// App.tsx
import { useState } from "react";
import "./App.css";
import TodoListContainer from "./components/TodoListContainer";
import TodoGenerator from "./components/TodoGenerator";
import Clock from "./components/Clock";
type TodoType = {
id: number;
text: string;
isChecked: boolean;
};
const initialTodos: TodoType[] = [
{
id: 1,
text: "공부하기",
isChecked: false,
},
{
id: 2,
text: "잠자기",
isChecked: false,
},
{
id: 3,
text: "밥먹기",
isChecked: false,
},
];
function App() {
const [todos, setTodos] = useState<TodoType[]>(initialTodos);
function checkHandler(todoId: number) {
setTodos((prevTodos) =>
prevTodos.map((item) =>
item.id === todoId ? { ...item, isChecked: !item.isChecked } : item
)
);
}
function addTodoItem(text: string) {
setTodos((prevTodos) => [
...prevTodos,
{ id: Math.random(), text: text, isChecked: false },
]);
}
// 할 일 삭제
function removeTodoItem(todoId: number) {
setTodos((prevTodos) => {
const filteredTodos = prevTodos.filter((todo) => todo.id !== todoId);
return [...filteredTodos];
});
}
const title = "오늘 할 일";
return (
<>
<div className="container">
<h1>{title}</h1>
<TodoGenerator addTodoHandler={addTodoItem} />
<Clock />
<TodoListContainer
todos={todos}
checkHandlerFunc={checkHandler}
removeTodo={removeTodoItem}
/>
</div>
</>
);
}
export default App;
- removeTodoItem 함수를 작성하여 TodoListContainer에 props로 전달하였다.
- filter 메소드를 이용하여 기존에 가지고 있던 todos에서 인수로 전달받은 id 요소만을 제외한 나머지 todos를 다시 할당해주는 방식으로 기존 todo를 유지하였다.
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.