Todos 앱 만들기
index.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
App.tsx
import { useRef, useState, useEffect } from 'react';
import { Todo } from './Type';
import './App.css';
import Editor from './components/Editor';
import TodoItem from './components/TodoItem';
function App() {
const [todos, setTodos] = useState<Todo[]>([])
const idRef = useRef(1)
const onClickAdd = (text:string) => {
setTodos([
...todos,
{
id : idRef.current++,
content : text
},
])
}
const onClickDelete = (id : number) => {
setTodos(todos.filter((todo)=> todo.id !== id))
}
useEffect(()=> {
console.log(todos)
},[todos])
return (
<div className='App'>
<h1>Todo</h1>
<Editor onClickAdd = {onClickAdd}/>
<div>
{todos.map((todo)=> (
<TodoItem key={todo.id} {...todo} onClickDelete={onClickDelete}/>
))}
</div>
</div>
);
}
export default App;
Editor.tsx
import { useState } from "react"
interface Props {
onClickAdd : (text: string) => void
}
export default function Editor(props:Props) {
const [text, setText] = useState("")
const onChangeInput = (e:React.ChangeEvent<HTMLInputElement>) => {
setText(e.target.value)
}
const onClickButton = () => {
props.onClickAdd(text)
setText("")
}
return (
<div>
<input type="text" value={text} onChange={onChangeInput}/>
<button onClick={onClickButton}>추가</button>
</div>
)
}
자세히보기
- todo를 담을 todo 배열
- Ctrl을 눌러 들어가보면 제네릭인걸 확인할 수 있다
- id 값에 사용할 useRef (1번부터 시작)
추가 및 삭제
// todo 추가
const onClickAdd = (text:string) => {
setTodos([
...todos, // 이전의 todos는 유지하고, 아래 새로운 todo를 추가
{
id : idRef.current++, // 현재값을 1씩 증가
content : text
},
])
}
// todo 삭제
const onClickDelete = (id : number) => {
setTodos(todos.filter((todo)=> todo.id !== id))
} // 삭제하려는 id와 다른 todo를 담은 리스트 반환
변경사항 감지
useEffect(()=> {
console.log(todos)
},[todos])
렌더링
return (
<div className='App'>
<h1>Todo</h1>
<Editor onClickAdd = {onClickAdd}/>
<div>
{todos.map((todo)=> (
<TodoItem key={todo.id} {...todo} onClickDelete={onClickDelete}/>
))}
</div>
</div>
);
- map을 사용하여 todo를 하나씩 TodoItem에 props함
- todo의 프로퍼티와 삭제 버튼에 대한 함수를 props
Type.ts
export interface Todo {
id : number;
content : string;
}
- Todo는 여러 컴포넌트에서 사용할 interface이므로 Type.ts에 따로 생성
import { Todo } from './Type';
- 다른 곳에서 사용할 땐 위와 같이 import 하면 됨
TodoItem.tsx
import { Todo } from "../Type";
interface Props extends Todo {
onClickDelete : (id: number) => void
}
export default function TodoItem (props : Props) {
const onClickButton = () => {
props.onClickDelete(props.id)
}
return (
<div>
{props.id}번 : {props.content}
<button onClick={onClickButton}>삭제</button>
</div>
)
}
자세히보기
interface Props extends Todo { }
export default function TodoItem (props : Props) {}
return (
<div>
{props.id}번 : {props.content}
<button onClick={onClickButton}>삭제</button>
</div>
)
}
- 값을 추가하기 편하도록 Todo 를 extends한 Props 생성하여 props에 적용
- id와 content 렌더링
- 삭제 버튼 생성
- 삭제 버튼을 누르면 onClickButton 이벤트 시행
interface Props extends Todo {
onClickDelete : (id: number) => void
}
export default function TodoItem (props : Props) {
const onClickButton = () => {
props.onClickDelete(props.id)
}
- App.tsx에서 onClickDelete 위에 마우스 올리면 `(id: number) => void` 힌트 얻을 수 있음
- Props에 onClickDelete 이벤트핸들러 추가하고 힌트로 얻은 타입 넣어주기
- onClickButton 은 Props로 받아온 onClickDelete를 시행하는 함수
- onClickDelete 본체는 App.tsx에 있음
Reducer 사용하기
App.tsx
import { useRef, useState, useEffect, useReducer } from 'react';
import { Todo } from './Type';
import './App.css';
import Editor from './components/Editor';
import TodoItem from './components/TodoItem';
import { stat } from 'fs';
type Action = {
type : "CREATE",
data : {
id : number,
content : string,
}
} | {
type : "DELETE",
id : number
}
function reducer (state:Todo[], action : Action ) {
switch(action.type) {
case 'CREATE' : {
return [...state, action.data]
}
case 'DELETE' : {
return state.filter((it)=> it.id !== action.id)
}
}
}
function App() {
const [todos, dispatch] = useReducer(reducer,[])
const idRef = useRef(1)
const onClickAdd = (text:string) => {
dispatch({
type:"CREATE",
data : {
id : idRef.current++,
content : text
}
})
}
const onClickDelete = (id : number) => {
dispatch({
type:"DELETE",
id : id,
})
}
useEffect(()=> {
console.log(todos)
},[todos])
return (
<div className='App'>
<h1>Todo</h1>
<Editor onClickAdd = {onClickAdd}/>
<div>
{todos.map((todo)=> (
<TodoItem key={todo.id} {...todo} onClickDelete={onClickDelete}/>
))}
</div>
</div>
);
}
export default App;
- reducer를 사용하면 타입스크립트의 장점을 잘 활용할 수 있음(오류나면 바로 알려주니까!)
자세히보기
- setTodos를 dispatch로 바꾸고 useState도 useReducer로 변경, reducer와 [] 로 변경
reducer 정의
function reducer (state:Todo[], action : Action ) {}
- state는 Todo배열로 action은 Action 타입으로 설정
type Action = {
type : "CREATE",
data : {
id : number,
content : string,
}
} | {
type : "DELETE",
id : number
}
- Action 타입은 CREATE와 DELETE 타입을 같은 유니온 타입
function reducer (state:Todo[], action : Action ) {
switch(action.type) {
case 'CREATE' : {
return [...state, action.data]
}
case 'DELETE' : {
return state.filter((it)=> it.id !== action.id)
}
}
}
- CREATE일때는 기존 todos 에 새로운 값을 추가하여 반환
- DELETE일 때는 id 값이 일치하지 않는 todo만 배열에 담아서 반환
const onClickAdd = (text:string) => {
dispatch({
type:"CREATE",
data : {
id : idRef.current++,
content : text
}
})
}
const onClickDelete = (id : number) => {
dispatch({
type:"DELETE",
id : id,
})
}
- 이벤트 핸들러도 타입에 따라 변경해줌
결과
'개발새발개발 > React' 카테고리의 다른 글
[React + TypeScript + Tailwind] 가독성 높이는 UI 컴포넌트 만들기 (1) | 2025.02.14 |
---|---|
[React] VSCode Prettier 적용 안됨 5가지 해결 방법 (2) | 2025.02.13 |
[React+Typescript] Todos 앱만들기 화살표함수 주의 (0) | 2025.02.10 |
[React] state, 이벤트핸들러 통합하기 (0) | 2025.02.06 |
[React] State와 props, 회원가입 정보 받기(input) (0) | 2025.02.05 |
댓글