#10 리덕스(Redux) -1

김민성·2023년 6월 20일
0
post-thumbnail

리덕스(Redux)

Redux는 JavaScript 앱을 위한 예측 가능한 상태 컨테이너이다.
자바스크립트 애플리케이션을 위한 상태 관리 라이브러리

그래서 Redux는 State을 관리하는 것

Redux 데이터 Flow(strict unidirectional data flow)

ACTION

Action은 간단한 JavaScript 객체이다. 여기에는 우리가 수행하는 작업의 유형을 지정하는 'type' 속성이 있으며 선택적으로 redux 저장소에 일부 데이터를 보내는 데에 사용되는 'payload' 속성을 가질 수도 있다.

REDUCER

REDUCER는 애플리케이션 상태의 변경 사항을 결정하고 업데이트된 상태를 반환하는 함수이다. 그들은 인수로 조치를 취하고 store 내부의 상태를 업데이트한다.

이전 State과 action object를 받은 후에 next state을 return한다.

Redux Store

이들을 하나로 모으는 객체 저장소는 애플리케이션의 전체 상태 트리를 보유한다.
내부 상태를 변경하는 유일한 방법은 해당 상태에 대한 Action을 전달하는 것이다.
Redux Store는 클래스가 아니다. 몇 가지 Methods가 있는 객체일 뿐이다.

미들웨어 없이 리덕스 카운터 앱 만들기

리액트 앱 설치

npx create-react-app my-app --template typescript

리덕스 라이브러리 설치

npm install redux --save

Counter UI 및 함수 생성

<App.tsx>

import React from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      Clicked: times
      <button>
        +
      </button>
      <button>
        -
      </button>
    </div>
  );
}

export default App;

Reducer 생성

<reducer/index.tsx>

const counter = (state = 0, action: { type: string}) => { //counter이라는 reducer
  switch (action.type) {
    case "INCREMENT":
      return state + 1;
    case "DECREMENT":
      return state - 1;
    default:
      return state;
  }
}

export default counter;

Store 생성 및 Action 전달

CreateStore()

앱의 전체 상태 트리를 보유하는 Redux 저장소를 만든다. 앱 에는 하나의 스토어만 있어야 한다.
<index.tsx>

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { createStore } from 'redux';
import counter from './reducers';

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);

const store = createStore(counter);

root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

getState()

애플리케이션의 현재 상태 트리를 반환한다. store의 reducer가 반환한 마지막 값과 같다.
<index.tsx>

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { createStore } from 'redux';
import counter from './reducers';

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);

const store = createStore(counter);

root.render(
  <React.StrictMode>
    <App 
      value={store.getState()} 
      onIncrement={()=> store.dispatch({type: "INCREMENT"})}
      onDecrement={()=> store.dispatch({type: "DECREMENT"})}
    />
  </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

<App.tsx>

import React from 'react';
import logo from './logo.svg';
import './App.css';

type Props = {
  value: number;
  onIncrement: () => void;
  onDecrement: () => void;
}

function App({ value, onIncrement, onDecrement }: Props) {
  return (
    <div className="App">
      Clicked: {value} times
      <button onClick={onIncrement}>
        +
      </button>
      <button onClick={onDecrement}>
        -
      </button>
    </div>
  );
}

export default App;

이렇게까지 하면 다음과 같은 에러가 발생한다.

test쪽에 에러가 발생하는데, 이는 현재 중요한 부분이 아니므로 App.test.tsx에서 다음 코드 부분을 지워주자.

render(<App />);
  const linkElement = screen.getByText(/learn react/i);
  expect(linkElement).toBeInTheDocument();

그러면 에러가 사라지게 된다.

현재 +, - 버튼을 눌러도 숫자의 변화가 없다. subscribe()를 사용해줘야지 변경된 값을 바로 반영시킬 수 있다.

subscribe()

change listener를 추가한다. 작업이 전달될 때마다 호출되며 상태 트리의 일부가 잠재적으로 변경되었을 수 있다. 그런 다음 getState()를 호출하여 콜백 내부의 현재 상태 트리를 읽을 수 있다.

다음과 같이 index.tsx를 수정해주자.

// React, ReactDOM 등 필요한 모듈들을 가져옵니다.
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css'; // 전역 스타일 시트를 가져옵니다.
import App from './App'; // App 컴포넌트를 가져옵니다.
import reportWebVitals from './reportWebVitals'; // 웹 성능을 측정하는 도구를 가져옵니다.

// Redux를 사용하기 위한 createStore 함수를 가져옵니다.
import { createStore } from 'redux';
import counter from './reducers'; // 리듀서를 가져옵니다.

// 웹 페이지에 root라는 id를 가진 HTML 엘리먼트를 찾아 root 상수에 할당합니다.
const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);

// Redux store를 생성합니다. 이 store는 애플리케이션의 상태를 관리합니다.
const store = createStore(counter);

// root 노드에 React 애플리케이션을 렌더링하는 함수를 정의합니다.
const render = () => root.render(
  <React.StrictMode>
    <App 
      value={store.getState()} // store의 현재 상태를 App 컴포넌트에 전달합니다.
      onIncrement={()=> store.dispatch({type: "INCREMENT"})} // 증가 액션을 dispatch하는 함수를 전달합니다.
      onDecrement={()=> store.dispatch({type: "DECREMENT"})} // 감소 액션을 dispatch하는 함수를 전달합니다.
    />
  </React.StrictMode>
);

render(); // 첫 렌더링을 실행합니다.

// store의 상태가 변경될 때마다 애플리케이션을 다시 렌더링합니다.
store.subscribe(render)

// 웹 성능 측정 도구를 실행합니다.
// 필요에 따라 측정 결과를 콘솔에 출력하거나 웹 분석 서비스로 보낼 수 있습니다.
reportWebVitals();

이제 누를 때마다 값이 변경되는 것을 확인할 수 있다.

combineReducers

ToDo 기능 추가

현재 까지 만든 Counter 앱에 ToDo 앱을 추가해보자.

root reducer와 sub reducer

현재까지 counter reducer만 사용했는데, 하나를 더 추가해주기 위해서는 Root reducer를 만들어서 그 아래 counter와 todos라는 sub reducer를 넣어주면 된다. Root reducer를 만들 때 사용하는 것이 combineReducers 이다.

그리고 reducers/index.tsx에 작성했던 코드들을 reducers/counter.tsx로 옮겨주자.
<reducers/counter.tsx>

const counter = (state = 0, action: { type: string}) => {
  switch (action.type) {
    case "INCREMENT":
      return state + 1;
    case "DECREMENT":
      return state - 1;
    default:
      return state;
  }
}

export default counter;

그리고 일단은 reducers/todos.tsx에 reducers/counter.tsx에 있는 코드를 복사 붙여넣기를 하고 함수명을 todos로 바꾼다. 내부의 switch case 문 부분은 추후에 수정하자.
<reducers/todos.tsx>

const todos = (state = 0, action: { type: string}) => {
    switch (action.type) {
      case "INCREMENT":
        return state + 1;
      case "DECREMENT":
        return state - 1;
      default:
        return state;
    }
  }
  
  export default todos;

<reducers/index.tsx>

// 'combineReducers' 함수를 redux 패키지로부터 import합니다. 
// 이 함수는 여러 개의 리듀서를 하나로 합치는데 사용됩니다.
import { combineReducers } from "redux"

// 각각의 리듀서를 해당하는 파일로부터 import합니다. 
// 'counter' 리듀서와 'todos' 리듀서를 가져옵니다.
import counter from './counter';
import todos from './todos';

// 'combineReducers' 함수를 사용하여 여러 개의 리듀서를 하나의 'rootReducer'로 합칩니다.
// 이 함수의 인자는 객체이며, 객체의 각 키는 state의 필드를, 값은 해당 필드를 처리하는 리듀서를 나타냅니다.
// 이 경우, 'counter' 필드는 'counter' 리듀서에 의해 처리되고, 'todos' 필드는 'todos' 리듀서에 의해 처리됩니다.
const rootReducer = combineReducers({
    counter,  // 'counter' 필드를 'counter' 리듀서가 처리합니다.
    todos     // 'todos' 필드를 'todos' 리듀서가 처리합니다.
})

// 'rootReducer'를 export하여 다른 파일에서 import하여 사용할 수 있게 합니다.
export default rootReducer;

이제 reducers/todos.tsx의 코드를 다음과 같이 작성한다.
<reducers/todos.tsx>

// 'ActionType'이라는 이름의 enum을 생성합니다. 이 enum은 액션의 type 필드에 사용될 문자열 상수들을 정의합니다.
// 여기서는 "ADD_TODO"와 "DELETE_TODO" 두 가지 타입이 정의되어 있습니다.
enum ActionType {
    ADD_TODO = "ADD_TODO",
    DELETE_TODO = "DELETE_TODO"
}

// 'Action'이라는 interface를 정의합니다. 이 interface는 액션 객체의 구조를 정의하는데 사용됩니다.
// 여기서는 type 필드는 'ActionType' enum으로 정의되고, text 필드는 string으로 정의되어 있습니다.
interface Action {
    type: ActionType;
    text: string;
}

// 'todos'라는 리듀서 함수를 정의합니다. 이 함수는 현재 상태(state)와 액션 객체(action)를 받아서
// 새로운 상태를 반환하는 함수입니다.
// 여기서는 초기 상태를 빈 배열로 설정하고 있습니다.
const todos = (state = [], action: Action) => {
    switch (action.type) {
      // 액션 타입이 "ADD_TODO"인 경우, 새로운 할 일 텍스트를 상태 배열의 끝에 추가하여 반환합니다.
      case "ADD_TODO":
        return [...state, action.text]
      // 그 외의 액션 타입에 대해서는 현재 상태를 그대로 반환합니다.
      default:
        return state;
    }
}

// 'todos' 리듀서를 export하여 다른 파일에서 import하여 사용할 수 있게 합니다.
export default todos;

reducers/counter.tsx도 interface를 추가해줘서 코드를 다음과 같이 수정해준다.
<reducers/counter.tsx>

// 'Action' interface를 정의합니다. 이는 Redux 액션 객체가 가져야 하는 모양을 정의합니다.
// 모든 Redux 액션은 'type' 필드를 가져야 하며, 이 필드는 액션의 종류를 나타냅니다.
interface Action {
    type: string
}

// 'counter'라는 이름의 Redux 리듀서 함수를 정의합니다. 
// 이 함수는 현재 상태(state)와 액션(action)을 입력으로 받아 새로운 상태를 반환합니다.
// 이 예에서, 상태는 숫자이며 초기값은 0입니다.
const counter = (state = 0, action: Action) => {
    // Redux 액션의 'type' 필드를 기준으로 switch문을 사용하여 다른 작업을 수행합니다.
    switch (action.type) {
      // 'type'이 "INCREMENT"인 경우, 현재 상태에 1을 더하여 반환합니다.
      case "INCREMENT":
        return state + 1;
      // 'type'이 "DECREMENT"인 경우, 현재 상태에서 1을 뺀 값을 반환합니다.
      case "DECREMENT":
        return state - 1;
      // 액션 타입이 "INCREMENT"나 "DECREMENT"가 아닌 경우, 현재 상태를 그대로 반환합니다.
      // 이는 Redux에서 알 수 없는 액션 타입에 대한 표준 처리 방법입니다.
      default:
        return state;
    }
  }
  
// 'counter' 리듀서 함수를 export하여 다른 파일에서 import하여 사용할 수 있게 합니다.
export default counter;

createStore에 rootReducer로 대체

index.tsx에서의 createStore에서 rootReducer로 대체해준다.
<index.tsx>

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { createStore } from 'redux';
import rootReducer from './reducers';

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);

const store = createStore(rootReducer);

const render = () => root.render(
  <React.StrictMode>
    <App 
      value={store.getState().counter}
      onIncrement={()=> store.dispatch({type: "INCREMENT"})}
      onDecrement={()=> store.dispatch({type: "DECREMENT"})}
    />
  </React.StrictMode>
);
render();

store.subscribe(render)
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

그리고 store.dispatch를 추가해주고 잘 작동하는지 console을 찍어보자.
<index.tsx>

// React 라이브러리를 불러옵니다. 이 라이브러리는 React 컴포넌트를 생성하고 사용하는 데 필요합니다.
import React from 'react';

// ReactDOM 라이브러리의 client-side 렌더링 함수를 불러옵니다. 
// 이 라이브러리는 React 컴포넌트를 웹 페이지에 렌더링하는 데 필요합니다.
import ReactDOM from 'react-dom/client';

// index.css 파일을 불러옵니다. 이 파일에는 전체 웹 애플리케이션의 CSS 스타일이 담겨 있습니다.
import './index.css';

// App 컴포넌트를 불러옵니다. 이 컴포넌트는 이 웹 애플리케이션의 최상위 (root) 컴포넌트입니다.
import App from './App';

// 웹 애플리케이션의 성능을 측정하는 함수를 불러옵니다.
import reportWebVitals from './reportWebVitals';

// Redux 라이브러리의 createStore 함수를 불러옵니다.
// 이 함수는 Redux 스토어를 생성하는 데 필요합니다.
import { createStore } from 'redux';

// 애플리케이션의 루트 리듀서를 불러옵니다.
import rootReducer from './reducers';

// 웹 페이지의 'root' id를 가진 DOM 요소를 찾아서 React의 root로 설정합니다.
const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);

// Redux 스토어를 생성합니다. 스토어는 애플리케이션의 상태를 저장하고, 
// 액션을 디스패치하여 상태를 업데이트하는 방법을 제공합니다.
const store = createStore(rootReducer);

// 스토어에 'ADD_TODO' 액션을 디스패치합니다. 
// 이 액션은 새로운 할 일을 추가하는 데 사용됩니다.
store.dispatch({
  type: 'ADD_TODO',
  text: 'Use Redux'
})

// 현재 스토어의 상태를 콘솔에 출력합니다.
console.log('store.getState', store.getState())

// render 함수를 정의합니다. 이 함수는 React 컴포넌트 트리를 root DOM 요소에 렌더링합니다.
const render = () => root.render(
  <React.StrictMode>
    <App 
      value={store.getState().counter}
      onIncrement={()=> store.dispatch({type: "INCREMENT"})}
      onDecrement={()=> store.dispatch({type: "DECREMENT"})}
    />
  </React.StrictMode>
);

// render 함수를 호출하여 초기 렌더링을 수행합니다.
render();

// 스토어의 상태가 변경될 때마다 render 함수를 호출하여 
// 상태 변경을 화면에 반영하도록 설정합니다.
store.subscribe(render)
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

다음과 같이 콘솔창에서 잘 작동되는 것을 확인할 수 있다.

Redux Provider

Provider란?

Provider 구성 요소는 Redux Store 저장소에 액세스해야 하는 모든 중첩 구성 요소에서 Redux Store 저장소를 사용할 수 있도록 한다.
React Redux 앱의 모든 React 구성 요소는 저장소에 연결할 수 있으므로 대부분의 응용 프로그램은 전체 앱의 구성 요소 트리가 내부에 있는 최상위 수준에서 Provider를 렌더링한다.
그런 다음 Hooks 및 연결 API는 React의 컨텍스트 메커니즘을 통해 제공된 저장소 인스턴스에 액세스할 수 있다.

Provider를 렌더링

React Redux 앱의 모든 React 구성 요소는 저장소에 연결할 수 있으므로 대부분의 응용 프로그램은 전체 앱의 구성 요소 트리가 내부에 있는 최상위 수준에서 Provider를 렌더링한다.

Provider는 react-redux 라이브러리에서 가져올 수 있으므로 이를 먼저 설치해준다.

npm install react-redux --save

<index.tsx>

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { createStore } from 'redux';
import rootReducer from './reducers';
import { Provider } from 'react-redux';

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);

const store = createStore(rootReducer);

store.dispatch({
  type: 'ADD_TODO',
  text: 'Use Redux'
});

console.log('store.getState', store.getState());

const render = () => root.render(
  <React.StrictMode>
    <Provider store={store}>
      <App 
        value={store.getState().counter}
        onIncrement={()=> store.dispatch({type: "INCREMENT"})}
        onDecrement={()=> store.dispatch({type: "DECREMENT"})}
      />
    </Provider>
  </React.StrictMode>
);

render();

store.subscribe(render);

reportWebVitals();

Todo UI 생성

<index.tsx>

import React, { useState } from 'react';
import logo from './logo.svg';
import './App.css';

type Props = {
  value: number;
  onIncrement: () => void;
  onDecrement: () => void;
}

function App({ value, onIncrement, onDecrement }: Props) {
  // `useState`를 사용하여 `todoValue`라는 상태 변수를 초기화하고, 그 상태를 변경하기 위한 `setTodoValue` 함수를 선언합니다. 초기 상태는 빈 문자열("")입니다.
const [todoValue, setTodoValue] = useState("");

// `handleChange` 함수는 입력 필드의 값이 변경될 때마다 호출됩니다.
// 이 함수는 이벤트 객체 `e`를 매개변수로 받아, 그 `target.value`(즉, 입력 필드의 현재 값)를 `setTodoValue`를 통해 `todoValue` 상태에 설정합니다.
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  setTodoValue(e.target.value);
}

// `addTodo` 함수는 form의 submit 이벤트가 발생할 때 호출됩니다. 
// 이 함수는 기본 form submit 이벤트의 동작(페이지 새로고침)을 방지하기 위해 `e.preventDefault()`를 호출하며,
// `setTodoValue`를 사용해 `todoValue` 상태를 다시 빈 문자열로 초기화합니다.
const addTodo = (e: React.FormEvent<HTMLFormElement>): void => {
  e.preventDefault();
  setTodoValue("");
}

return (
  <div className="App">
    Clicked: {value} times
    <button onClick={onIncrement}>
      +
    </button>
    <button onClick={onDecrement}>
      -
    </button>

    {/* form 태그에 `onSubmit` 속성을 사용하여 `addTodo` 함수를 form의 submit 이벤트 핸들러로 설정합니다. */}
    <form onSubmit={addTodo}>
      {/* 입력 필드(input 태그)의 value 속성을 `todoValue` 상태로 설정하고, onChange 이벤트 핸들러로 `handleChange` 함수를 설정합니다. */}
      <input type="text" value={todoValue} onChange={handleChange} />
      {/* 제출 버튼(input 태그)을 추가합니다. 이 버튼을 클릭하면 form의 submit 이벤트가 발생하고, 위에서 설정한 `addTodo` 함수가 호출됩니다. */}
      <input type="submit" />
    </form>

  </div>
);

}

export default App;

useSelector & useDispatch

provider로 둘러싸인 컴포넌트에서 store 접근

React에 Hooks가 있듯이 Redux에도 Hooks가 있는데 그게 바로 useSelector와 useDispatch이다. 이 2가지를 이용해서 provider로 둘러싸인 컴포넌트에서 store에 접근 가능하다.

useSelector

useSelector Hooks를 이용해서 스토어의 값을 가져올 수 있다.

App.tsx에 다음 코드를 작성해주면,

const counter = useSelector((state) => state.counter);


이렇게 에러가 발생하게 된다.

해결방법

1. Root Reducer에 RootState 타입을 생성하기
<reducers/index.tsx>

// 'combineReducers' 함수를 redux 패키지로부터 import합니다. 
// 이 함수는 여러 개의 리듀서를 하나로 합치는데 사용됩니다.
import { combineReducers } from "redux"

// 각각의 리듀서를 해당하는 파일로부터 import합니다. 
// 'counter' 리듀서와 'todos' 리듀서를 가져옵니다.
import counter from './counter';
import todos from './todos';

const rootReducer = combineReducers({
    counter,  
    todos    
})

export default rootReducer;

export type RootState = ReturnType<typeof rootReducer> // 이 부분을 추가해준다.

2. 생성한 RootState을 State 객체에 제공하기
우선 App.tsx에 RootState를 import 해오자.

import { RootState } from './reducers';

그리고 todo를 추가해주고, 그리고 counter의 state에 RootState를 추가해주자.

const todos: string[] = useSelector((state: RootState) => state.todos);
const counter = useSelector((state: RootState) => state.counter);

<App.tsx>

import React, { useState } from 'react';
import './App.css';
import { useSelector } from 'react-redux';
import { RootState } from './reducers';

type Props = {
  value: number;
  onIncrement: () => void;
  onDecrement: () => void;
}

function App({ value, onIncrement, onDecrement }: Props) {
  
  // `useSelector`를 사용하여 Redux store로부터 `todos` state를 가져옵니다. 이때 state의 타입은 `RootState`로 지정되어 있습니다.
  const todos: string[] = useSelector((state: RootState) => state.todos)
  
  // `useSelector`를 사용하여 Redux store로부터 `counter` state를 가져옵니다.
  const counter = useSelector((state: RootState) => state.counter);
  
  const [todoValue, setTodoValue] = useState("");
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setTodoValue(e.target.value);
  }

  const addTodo = (e: React.FormEvent<HTMLFormElement>): void => {
    e.preventDefault();
    setTodoValue("");
  }

  return (
    <div className="App">
      {/* `counter` state 값을 출력합니다. */}
      Clicked: {counter} times
      <button onClick={onIncrement}>
        +
      </button>
      <button onClick={onDecrement}>
        -
      </button>

      {/* `todos` 배열의 각 요소를 순회하면서 각각에 대한 `<li>` 태그를 생성합니다. `key` 속성에는 배열의 인덱스를 할당합니다. */}
      <ul>
        {todos.map((todo, index) => <li key={index}>{todo}</li>)}
      </ul>

      <form onSubmit={addTodo}>
        <input type="text" value={todoValue} onChange={handleChange} />
        <input type="submit" />
      </form>

    </div>
  );
}

export default App;

useDispatch

store에 있는 dispatch 함수에 접근하는 hooks이다.

index.tsx에서 다음 코드 부분을 지워준다.

store.dispatch({
  type: 'ADD_TODO',
  text: 'Use Redux'
});

<App.tsx>

import React, { useState } from 'react';
import logo from './logo.svg';
import './App.css';
import { useDispatch, useSelector } from 'react-redux';
import { RootState } from './reducers';


type Props = {
  value: number;
  onIncrement: () => void;
  onDecrement: () => void;
}

function App({ value, onIncrement, onDecrement }: Props) {
  // useDispatch() 함수를 사용해 dispatch 함수를 가져옵니다. 
  // 이 dispatch 함수는 Redux store에 액션을 보내는 역할을 합니다.
  const dispatch = useDispatch() //useDispatch()를 react-redux에서 가져온다.
  const todos: string[] = useSelector((state: RootState) => state.todos)
  const counter = useSelector((state: RootState) => state.counter);
  const [todoValue, setTodoValue] = useState("");
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setTodoValue(e.target.value);
  }

  const addTodo = (e: React.FormEvent<HTMLFormElement>): void => {
    e.preventDefault();
    // dispatch 함수를 사용해 ADD_TODO 액션을 보냅니다. 
    // 이 액션은 todoValue를 텍스트로 가지는 새로운 할 일을 추가하라는 신호입니다.
    dispatch({ type: 'ADD_TODO', text: todoValue}) // 위에서 가져온 useDispatch를 정의한 dispatch를 
    setTodoValue("");
  }

  return (
    <div className="App">
      Clicked: {counter} times
      <button onClick={onIncrement}>
        +
      </button>
      <button onClick={onDecrement}>
        -
      </button>

    <ul>
      {todos.map((todo, index) => <li key={index}>{todo}</li>)}
    </ul>

      <form onSubmit={addTodo}>
        <input type="text" value={todoValue} onChange={handleChange} />
        <input type="submit" />
      </form>

    </div>
  );
}

export default App;

업로드중..
이렇게 잘 추가되는 것을 확인할 수 있다.

profile
다양한 활동을 통해 인사이트를 얻는 것을 즐깁니다. 저 또한 인사이트를 주는 사람이 되고자 합니다.

0개의 댓글

관련 채용 정보