📝 요즘 해야 할 일이 많아, 어딘가 적고 체크하지 않으면 까먹기 일쑤라 Todo-List 작성이 필수가 되어버렸다.
파워 J인 나(사실 MBTI를 믿지 않지만..!)는 항상 기록해야하기 때문에 Todo-List를 만들어보고자 한다 💜
✨ 결과물
✅ 해야할 일을 기록하면 아래 list로 표시 & 위에 완료 전 list 개수 표시
✅ list 마무리 후, todo 완료 체크하면 todo 내용이 완료되었다고 표시
✅ 각 todo-list 삭제 기능
✅ 전체 완료 기능 & 전체 삭제 기능
✏️ useState()로 todo-list 상태값 관리하기
const [text, setText] = useState('');
const [todos, setTodos] = useState<TodoType[]>([]);
// TodoType
export type TodoType = {
id : number
text : string
isChecked : boolean
}
✅ App.tsx에서 useState() 상태값 관리!
✅ todos는 상태값 관리를 위해 todo-list에 기본적으로 필요한 설정을 type으로 선언(배열로 받음)
💜 App.tsx에서 관리하는 값
* text : 입력창에 변경되는 상태값
*handleTextChange : 입력창에 작성한 값 표시
*handleSubmit : Enter or [+] 버튼 클릭 시, 입력 값 전달
* handleClick : 각 todo-list 완료 체크
*handleRemove : 각 todo-list 삭제
* handleToggleAllClick : todo-list 전체 완료
* handleRemoveAllClick : todo-list 전체 삭제
💜 props로 상태값 전달
* TodoList.tsx의 역할 : 작성한 todo-list를 map()을 통해 화면에 반환 (Ol 해당)
* TodoItem.tsx의 역할 : 각 todo-list 반환 (li 해당)
* TodoInput.tsx의 역할 : 입력창 기능
* TodoListTools.tsx의 역할 : todo-list '전체 완료' & '전체 삭제' 기능
❗️ TodoListArea.tsx의 역할 : todo-list가 없을 경우, HOC 기능
❗️ HOC(High Order Component)란?
👉🏻 상위 컴포넌트에서 하위 컴포넌트를 감싸고, 조건에 맞게 하위 컴포넌트 표시 여부를 결정하는 것
📝 따라서 하위 컴포넌트인 TodoListTools, TodoList 컴포넌트를 감싸고, todo-list가 없는 경우, 빈 화면 반환
// Type 선언
interface TodoListArea {
children: ReactNode
todoCount: number
}
const TodoListArea = (props:TodoListArea) => {
if(props.todoCount < 1) {
return null
}
return (
<>
{props.children}
</>
)
}
✅ todoCount는 todo-list의 길이로, 만약 길이가 1보다 작을 경우, null을 return
✅ props.children 부분이 하위 컴포넌트인 TodoListTools, TodoList 해당
✏️ useReducer()로 todo-list 상태값 관리하기
// App.tsx Reducer() 사용
const [inputState, inputDispatch] = useReducer(todoInputReducer, { text: '' })
// TodoInputReducer.ts 파일 생성
type TodoInputStateType = {
text: string
}
type TodoInputActionType = {
// change, clear, ...
type: 'change'
payload: string
} | {
type: 'clear'
}
export const todoInputReducer = (state:TodoInputStateType, action:TodoInputActionType) => {
switch(action.type) {
case 'change':
return {
text: action.payload
}
case 'clear':
return {
text: ''
}
}
}
✅ App.tsx에서 Reducer를 사용하기 위해 useReducer()로 상태값 관리
✅ TodoInputReducer.ts에서 Reducer는 state와 action을 받으며, 이를 나타내기 위해 각각 type 선언
💜 기존에 관리하던 todos도 Reducer로 관리해 보자!
export type TodoType = {
id : number
text : string
isChecked : boolean
}
type TodoStateType = {
todos: TodoType[]
}
// action 타입은 길어서 요약
type TodoActionType = {
type: 'add',
...
type: 'remove',
...
type: 'checked',
...
type: 'allChecked',
...
type: 'allRemove'
}
// action 중 add만 표시(나머지 action 생략)
export const todoReducer = (state:TodoStateType, action:TodoActionType) => {
switch(action.type) {
case 'add':
return {
todos: state.todos.concat({
id: Date.now(),
text: action.payload.text,
isChecked: false
})
}
.
.
.
}
}
❗️ todos의 action은 add, remove, checked, allChecked, allRemove 총 5가지로, 코드가 너무 길어지기 때문에 아래 github에서 코드 참조 🙌🏻
const handleClick = (id:number) => {
todoDispatch({
type: 'checked',
payload: {
id: id
}
})
}
✅ Reducer의 Dispatch를 이용하여 해당 action에 맞는 코드 구현
✏️ Context()로 todo-list 상태값 관리하기
const TodoStateContext = createContext<TodoStateType | null>(null)
const TodoDispatchContext = createContext<Dispatch<TodoActionType> | null>(null)
const InputTodoContext = createContext<TodoInputStateType | null>(null)
const InputTodoDispatchContext = createContext<Dispatch<TodoInputActionType> | null>(null)
const TodoProvider = (props:TodoProviderProps) => {
// state와 Dispatch를 어디서든 사용할 수 있도록 위에 선언
const [todoState, todoDispatch] = useReducer(todoReducer,{ todos: loadTodos() })
const [inputState, inputDispatch] = useReducer(todoInputReducer, { text: '' })
return (
<TodoStateContext.Provider value={todoState}>
<TodoDispatchContext.Provider value={todoDispatch}>
<InputTodoContext.Provider value={inputState}>
<InputTodoDispatchContext.Provider value={inputDispatch}>
{props.children}
</InputTodoDispatchContext.Provider>
</InputTodoContext.Provider>
</TodoDispatchContext.Provider>
</TodoStateContext.Provider>
)
}
✅ Context()를 사용하기 위해 위와 같이 선언 후, 모든 state 상태값을 관리하는 Reducer를 가져옴
✅ state를 사용하기 위해 createContext 사용 시, 해당 Type 선언
(Reducer.ts에서 따로 작성한 Type을 export 해서 사용)
✅ todoState, todoDispatch에서 { todos: loadTodos() }를 기본값으로 한 이유는 새로고침시 작성한 todo-list가 삭제되기 때문! (localStorage에서 가져옴)
❗️ state와 dispatch를 따로 감싸서 작성한 이유
👉🏻 Context.Provider는 안에 있는 value 값이 변경될 때마다 안쪽부터 리렌더링 현상 발생
렌더링 되지 않아도 되는 값까지 렌더링이 불필요하게 되기 때문에, 이를 방지하기 위해 따로 작성
export const useTodoState = () => {
const value = useContext(TodoStateContext)
if(!value) {
throw new Error('cannot find TodoState')
}
return value
}
export const useTodoDispatch = () => {
const value = useContext(TodoDispatchContext)
if(!value) {
throw new Error('cannot find TodoDispatch')
}
return value
}
.
.
.
✅ Context()를 사용하기 위해 useContext() 작성
🫠 todo-list를 통해 React에서 가장 많이 사용되는 useState()와 state의 상태값을 관리해 주는 useReducer(), useContext()를 모두 사용해 볼 수 있어서 좋았다 🥹
useState()만 익숙해서 많이 사용해 봤지, 상태값을 관리해 주는 훅은 써본 경험이 별로 없었다.
앞으로 계속 써봐서 익숙해져야겠다 🙌🏻
💜 전체 코드 github ⬇️⬇️⬇️
https://github.com/YouJin-Cho/todo-list
👉🏻 useState(), useReducer(), Context()를 사용한 각각의 코드 모두 github에서 확인!
'프로젝트 > 토이 프로젝트' 카테고리의 다른 글
[React] 야구게임(BULLS AND COWS) (0) | 2023.04.28 |
---|---|
[javascript] News-Viewer (0) | 2023.04.24 |
[javascript] Calendar & DatePicker (0) | 2023.04.23 |
[javascript] star-rating (0) | 2023.04.22 |
[javascript] AnalogClock (0) | 2023.04.06 |