프로젝트/토이 프로젝트

[React + TypeScript] Todo-List

진기명기 2023. 4. 30. 21:27
📝 요즘 해야 할 일이 많아, 어딘가 적고 체크하지 않으면 까먹기 일쑤라 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