프로젝트 초기화 및 패키지 설치

- 리액트와 타입스크립트를 사용하기에 한번에 다운 받아준다.

# yarn을 사용하는 경우
yarn create react-app my-app --template typescript

# npx를 사용하는 경우
npx create-react-app my-app --template typescript

 

- 리덕스와 리덕스 관련 라이브러리 설치

yarn add redux react-redux @types/react-redux

 

- devtools도 사용해주려면 같이 설치 (선택)

yarn add redux-devtools-extension @types/redux-devtools-extension

 


Create Slice 작성

- 액션 생성자와 리듀서를 자동으로 생성해주는 함수입니다.

// src/store/todoSlice.ts

import { createSlice, PayloadAction } from "@reduxjs/toolkit";

interface TodoState {
  items: {
    text: string;
    completed: boolean;
  }[];
}

const initialState: TodoState = {
  items: [],
};

const todoSlice = createSlice({
    name: 'todo',
    initialState,
    reducers: {
      addTodo: (state, action: PayloadAction<string>) => {
        state.items.push({
          text: action.payload,
          completed: false, // 새로운 todo는 기본값이 false
        });
      },
      removeTodo: (state, action: PayloadAction<number>) => {
        state.items.splice(action.payload, 1);
      },
      toggleTodo: (state, action: PayloadAction<number>) => {
        const todo = state.items[action.payload];
        todo.completed = !todo.completed; // 완료 상태 변경
      },
    },
  });

export const { addTodo, removeTodo, toggleTodo } = todoSlice.actions;
export default todoSlice.reducer;

 


 

리듀서 작성하기

// src/reducers/todoReducer.ts

import { TodoActionTypes, ADD_TODO, TOGGLE_TODO, DELETE_TODO } from '../actions/types';

export interface TodoState {
  todos: Todo[];
}

const initialState: TodoState = {
  todos: [],
};

const todoReducer = (state = initialState, action: TodoActionTypes): TodoState => {
  switch (action.type) {
    case ADD_TODO:
      return {
        ...state,
        todos: [...state.todos, action.payload],
      };
    case TOGGLE_TODO:
      return {
        ...state,
        todos: state.todos.map((todo) =>
          todo.id === action.payload ? { ...todo, completed: !todo.completed } : todo
        ),
      };
    case DELETE_TODO:
      return {
        ...state,
        todos: state.todos.filter((todo) => todo.id !== action.payload),
      };
    default:
      return state;
  }
};

export default todoReducer;

리덕스 사용하기

// src/index.tsx

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './App';
import store from './store/configStore';

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
);

스토어 설정하기

- 리덕스 스토어를 설정.

import { configureStore } from '@reduxjs/toolkit';
import todoReducer from './todoSlice';

const store = configureStore({
  reducer: {
    todo: todoReducer,
  },
});

export type RootState = ReturnType<typeof store.getState>;
export default store;

 

 

 

 


App 컴포넌트

// src/App.tsx

import React, { useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { addTodo, removeTodo, toggleTodo } from "./store/todoSlice";
import { RootState } from "./store/configStore";
import styled from "styled-components";
import "./App.css";

const App: React.FC = () => {
  const [newTodo, setNewTodo] = useState("");
  const todos = useSelector((state: RootState) => state.todo.items);
  const dispatch = useDispatch();

  const handleAddTodo = () => {
    if (newTodo.trim() !== "") {
      dispatch(addTodo(newTodo));
      setNewTodo("");
    }
  };

  const handleRemoveTodo = (index: number) => {
    dispatch(removeTodo(index));
  };

  const handleToggleTodo = (index: number) => {
    dispatch(toggleTodo(index));
  };

  return (
    <AppContainer>
    <Title>To-Do List</Title>
    <TodoForm>
      <input
        type="text"
        value={newTodo}
        onChange={(e) => setNewTodo(e.target.value)}
      />
      <button onClick={handleAddTodo}>Add</button>
    </TodoForm>
    <ul>
      {todos.map((todo, index) => (
        <TodoItem key={index}>
          <TodoText completed={todo.completed}>{todo.text}</TodoText>
          <RemoveButton onClick={() => handleRemoveTodo(index)}>
            Remove
          </RemoveButton>
          <CheckboxLabel>
            <CheckboxInput
              type="checkbox"
              checked={todo.completed}
              onChange={() => handleToggleTodo(index)}
            />
            Completed
          </CheckboxLabel>
        </TodoItem>
      ))}
    </ul>
  </AppContainer>
  );
};

export default App;

const AppContainer = styled.div`
  font-family: Arial, sans-serif;
  max-width: 400px;
  margin: 0 auto;
`;

const Title = styled.h1`
  text-align: center;
`;

const TodoForm = styled.div`
  display: flex;
  justify-content: center;
  align-items: center;
  margin-bottom: 20px;

  input {
    padding: 8px;
    border: 1px solid #ccc;
    border-radius: 4px;
    margin-right: 10px;
  }

  button {
    padding: 8px 12px;
    background-color: #007bff;
    color: #fff;
    border: none;
    border-radius: 4px;
    cursor: pointer;

    &:hover {
      background-color: #0056b3;
    }
  }
`;

const TodoItem = styled.li`
  display: flex;
  align-items: center;
  margin-bottom: 10px;
`;

const TodoText = styled.span<{ completed: boolean }>`
  flex: 1;
  text-decoration: ${(props) => (props.completed ? "line-through" : "none")};
`;

const RemoveButton = styled.button`
  margin-left: 10px;
  padding: 4px 8px;
  background-color: #dc3545;
  color: #fff;
  border: none;
  border-radius: 4px;
  cursor: pointer;

  &:hover {
    background-color: #c82333;
  }
`;

const CheckboxLabel = styled.label`
  margin-left: 10px;
  cursor: pointer;
`;

const CheckboxInput = styled.input`
  margin-right: 5px;
`;

 

 

 

 

'IT > TypeScript' 카테고리의 다른 글

[TypeScript] 타입 스크립트 기본  (0) 2023.07.31
[TypeScript] 타입 스크립트 활용  (0) 2023.07.26
[TypeScript] 타입 스크립트 시작  (0) 2023.07.26

+ Recent posts