React, Redux, To-Do List

2024. 12. 30. 15:18웹 개발

npm install redux
npm install @reduxjs/toolkit

 

Main.jsx

import './index.css';

import App from './App.jsx';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import { configureStore } from '@reduxjs/toolkit';
import rootReducer from './modules';

// `createStore` 대신 `configureStore`, `configureStore`는 자동으로 Redux DevTools를 지원함
const store = configureStore({
  reducer: rootReducer,
});

// `createRoot` 사용
const root = ReactDOM.createRoot(document.getElementById('root'));

root.render(
  <Provider store={store}>
    <App />
  </Provider>
);

 

App.jsx

import './App.css';

import CounterContainer from './containers/CounterContainer';
import TodosContainer from './containers/TodosContainer';

const App = () => {
  return (
    <div>
      <CounterContainer />
      <hr />
      <TodosContainer />
    </div>
  );
};

export default App;

 

/containers/CounterContainer.jsx

import { connect } from 'react-redux';
import Counter from '../components/Counter';
import { increase, decrease } from '../modules/counter';

const CounterContainer = ({ number, increase, decrease }) => {
  return (
    <Counter number={number} onIncrease={increase} onDecrease={decrease} />
  );
};

export default connect(
  (state) => ({
    number: state.counter.number,
  }),
  {
    increase,
    decrease,
  }
)(CounterContainer);

 

/containers/TodosContainer.jsx

import { connect } from 'react-redux';
import { changeInput, insert, toggle, remove } from '../modules/todos';
import Todos from '../components/Todos';

const TodosContainer = ({
  input,
  todos,
  changeInput,
  insert,
  toggle,
  remove,
}) => {
  return (
    <Todos
      input={input}
      todos={todos}
      onChangeInput={changeInput}
      onInsert={insert}
      onToggle={toggle}
      onRemove={remove}
    />
  );
};

export default connect(
  ({ todos }) => ({
    input: todos.input,
    todos: todos.todos,
  }),
  {
    changeInput,
    insert,
    toggle,
    remove,
  }
)(TodosContainer);

 

/modules/index.jsx

import { combineReducers } from 'redux';
import counter from './counter';
import todos from './todos';

const rootReducer = combineReducers({
  counter,
  todos,
});

export default rootReducer;

 

/modules/counter.jsx

const INCREASE = 'counter/INCREASE'; // 액션 타입 정의
const DECREASE = 'counter/DECREASE';

export const increase = () => ({ type: INCREASE }); // 액셩 생성 함수 정의
export const decrease = () => ({ type: DECREASE });

const initialState = {
  // 초기 상태 설정
  number: 0,
};

const counter = (state = initialState, action) => {
  // 리듀서 함수 생성
  switch (action.type) {
    case INCREASE:
      return {
        number: state.number + 1,
      };
    case DECREASE:
      return {
        number: state.number - 1,
      };
    default:
      return state;
  }
};

export default counter;

 

/modules/todos.jsx

const CHANGE_INPUT = 'todos/CHANGE_INPUT'; // 액션 타입 정의
const INSERT = 'todo/INSERT';
const TOGGLE = 'todos/TOGGLE';
const REMOVE = 'todos/REMOVE';

export const changeInput = (input) => ({
  type: CHANGE_INPUT,
  input,
});

let idx = 3;

export const insert = (text) => ({
  type: INSERT,
  todo: {
    id: idx++,
    text,
    done: false,
  },
});

export const toggle = (id) => ({
  type: TOGGLE,
  id,
});

export const remove = (id) => ({
  type: REMOVE,
  id,
});

const initialState = {
  input: '',
  todos: [
    {
      id: 1,
      text: '리덕스 기초 배우기',
      done: true,
    },
    {
      id: 2,
      text: '리액트와 리덕스 사용하기',
      done: false,
    },
  ],
};

const todos = (state = initialState, action) => {
  switch (action.type) {
    case CHANGE_INPUT:
      return {
        ...state,
        input: action.input,
      };
    case INSERT:
      return {
        ...state,
        todos: state.todos.concat(action.todo),
      };
    case TOGGLE:
      return {
        ...state,
        todos: state.todos.map((todo) =>
          todo.id === action.id ? { ...todo, done: !todo.done } : todo
        ),
      };
    case REMOVE:
      return {
        ...state,
        todos: state.todos.filter((todo) => todo.id !== action.id),
      };
    default:
      return state;
  }
};

export default todos;

 

/components/Counter.jsx

const Counter = ({ number, onIncrease, onDecrease }) => {
  return (
    <div>
      <h1>{number}</h1>
      <div>
        <button onClick={onIncrease}>+1</button>
        <button onClick={onDecrease}>-1</button>
      </div>
    </div>
  );
};

export default Counter;

 

/components/Todos.jsx

const TodoItem = ({ todo, onToggle, onRemove }) => {
  return (
    <div>
      <label style={{ cursor: 'pointer' }}>
        <input
          type="checkbox"
          onClick={() => onToggle(todo.id)}
          checked={todo.done}
          readOnly={true}
        />
        <span style={{ textDecoration: todo.done ? 'line-through' : 'none' }}>
          {todo.text}
        </span>
      </label>
      <button onClick={() => onRemove(todo.id)}>삭제</button>
    </div>
  );
};

const Todos = ({
  input,
  todos,
  onChangeInput,
  onInsert,
  onToggle,
  onRemove,
}) => {
  const onSubmit = (e) => {
    e.preventDefault();
    onInsert(input);
    onChangeInput('');
  };
  const onChange = (e) => onChangeInput(e.target.value);

  return (
    <div>
      <form onSubmit={onSubmit}>
        <input type="text" value={input} onChange={onChange} />
        <button type="submit">등록</button>
      </form>
      <div>
        {todos.map((todo) => (
          <TodoItem
            todo={todo}
            key={todo.id}
            onToggle={onToggle}
            onRemove={onRemove}
          />
        ))}
      </div>
    </div>
  );
};

export default Todos;

 

https://stackblitz.com/edit/vitejs-vite-bzwnnpg5?embed=1&file=src%2Fmain.jsx

'웹 개발' 카테고리의 다른 글

React, Redux, Connetc 함수 대신 useSelect 사용하기  (0) 2024.12.30
React, Redux-actions 라이브러리  (1) 2024.12.30
React + Redux, useStore, useDispatch  (1) 2024.12.27
React, Redux  (1) 2024.12.26
React, Context, useContext Hook  (1) 2024.12.26