본문 바로가기
typescript

[typescript]react-typescript(redux)

by 남민섭 2023. 2. 24.
728x90
반응형

지금까지 사용한 상태관리

useState<number>()
useReducer(reducer, state)

 

2023.02.20 - [typescript] - [typescript]react - typescript(useState)

 

[typescript]react - typescript(설치, useState)

설치(cmd) npx create-react-app ts-react --template typescript VScode 에디터에서 터미널 열고 설치 npm install -g typescript 1. props 전달시 props 타입을 interface로 지정해야한다. ex) interface HelloProps = { name: string } const H

uou413.tistory.com

2023.02.21 - [typescript] - [typescript] react-typscript(usereducer)

 

[typescript] react-typscript(usereducer)

useReducer 사용하기 reducer 함수 생성 올수 있는 액션객체를 유니언 타입으로 쭉 나열 type Action = {type: 'INCREASE'} | {type: 'DECREASE'} function reducer(state: number, action: Action): number { //카운터를 바꿀거기때문

uou413.tistory.com

react-redux알아보기

2023.01.27 - [react] - [React]redux

 

[React]redux

지금까지 usereducer와 usestate로 상태 관리해보았다 이번엔 redux!!!! 참고사이트!!! https://ko.redux.js.org/ Redux - 자바스크립트 앱을 위한 예측 가능한 상태 컨테이너. | Redux 자바스크립트 앱을 위한 예측

uou413.tistory.com



이번에 배울건

typescript + redux사용하기!!!!!!!

그 전에~  

타입스크립트 지원이 되는지 안되는지 확인
타입스크립트 지원이 안되면 @types/앞에 붙여서 설치


확인방법은
index.d.ts파일이 있는지 확인

 

라이브러리 2개 설치

redux

react-redux

npm install redux react-redux @types/react-redux

 

 

 

counter모듈 만들기

modules 폴더 생성

counter.ts

//액션타입, 액션생성함수, 리듀서
//1. 액션타입
//action.type 이 string으로 추론되지 않고 'counter/INCREASE' 와 같이
//실제 문자열을 추론되도록 as const 붙임


const INCREASE = "counter/INCREASE" as const;
const DECREASE = "counter/DECREASE" as const;


//2. 액션 생성 함수 // 말그대로 액션생성함수를 리턴해줌 return {type: INCREASE, payload: diff}  //액션객체 반환
/* const increase = () => {
    return {type: INCREASE}
} */ //생략해서 밑에꺼
export const increase = () => ({type: INCREASE})


/* const decrease = () => {
    return {type: DECREASE}
} */ //리턴해주는 얘가 객체일때 소괄호 작성
export const decrease = () => ({type: DECREASE})


//액션객체에 대한 타입 (ReturnType<typeof 메모장 확인)
type CounterAction = ReturnType<typeof increase> | ReturnType<typeof decrease>


//상태에 대한 타입 설정하고 초기상태 설정
type CounterState = {count: number}

//초기상태
const initialState: CounterState = {count: 0}

//3.리듀서 (state,action 타입 지정 해줘야함)
function counter(state:CounterState = initialState, action:CounterAction){
    switch(action.type){
        case INCREASE:
            return {count: state.count + 1};
        case DECREASE:
            return {count: state.count - 1};    
        default:
            return state;    
    }
}

export default counter

 

모듈 합치기

modules폴더 안

index.ts

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


const rootReducer = combineReducers({counter});

export default rootReducer;


//리듀서 호출 리턴?? 상태!!!
//rootReducer가 실행되면 state리턴함
//ReturnType<typeof rootReducer> 특정함수의 리턴 타입을 추론
//useSelector(state => state.todos)
//스토어의 상태값의 타입을 추론

export type rootState =ReturnType<typeof rootReducer> 
//특정함수의 리턴하는 값의 타입을 반환. state의 타입 돌려줌

 

스토어 생성

src 폴더 안에

index.tsx

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

//스토어 생성하기
const store = createStore(rootReducer)
console.log(store.getState())


const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
root.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </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();

 

counter 프레젠테이셔널 컴포넌트 생성 (화면구현)

components폴더 생성

Counter.tsx

import React from 'react';


//상태값 count,
//상태 업데이트 해주는 함수 onIncrease, onDecrease
type CounterProps = {
    count: number;
    onIncrease: ()=> void;
    onDecrease: ()=> void;
}
const Counter = ({count, onIncrease, onDecrease}:CounterProps) => {
    return (
        <div>
            <h2>{count}</h2>
            <div>
                <button onClick={onIncrease}>+</button>
                <button onClick={onDecrease}>-</button>
            </div>
        </div>
    );
};

export default Counter;

 

counter 컨테이너컴포넌트 생성(store에 접근/업데이트)

containers 폴더 생성

CounterContainer.tsx

import React from 'react';
import { useDispatch } from 'react-redux';
import { useSelector } from 'react-redux';
import Counter from '../components/Counter';
import { rootState } from '../modules';
import { decrease, increase } from '../modules/counter';

const CounterContainer = () => {
    //상태조회
    const count = useSelector((state: rootState)=>state.counter.count)
    const dispatch = useDispatch()

    //디스패치 함수
    const onIncrease =()=>{
        dispatch(increase())
    }
    const onDecrease =()=>{
        dispatch(decrease())
    }
    return (
        <div>
            <Counter count={count} onDecrease={onDecrease} onIncrease={onIncrease}/>
        </div>
    );
};

export default CounterContainer;

 

counterContainer 조립

src 폴더안에

App.tsx

import React from 'react';
import './App.css';
import CounterContainer from './containers/CounterContainer';

function App() {
  return (
    <div className="App">
      <CounterContainer/>
    </div>
  );
}

export default App;

 


todolist 만들기

 

todo 모듈만들기

modules 폴더안

todos.ts

//1.액션타입
//type-action 적용전
const ADD_TODO = "todos/ADD_TODO" as const
const REMOVE_TODO = "todos/REMOVE_TODO" as const
const TOGGLE_TODO = "todos/TOGGLE_TODO" as const


//t새로운 항목 추가할때 사용할 id값
let nextId = 1;


//2.액션 생성 함수
export const addTodo = (text:string) => ({
    type: ADD_TODO, 
    payload: {
        id: nextId++, 
        text: text
    }
})

export const toggleTodo = (id:number) => ({
    type: TOGGLE_TODO,
    payload: id
})

export const removeTodo = (id:number) => ({
    type: REMOVE_TODO,
    payload: id
})


//ReturnType<typeof addTodo> 특정함수의 리턴타입을 추론
type TodoAction = ReturnType<typeof addTodo> 
| ReturnType<typeof removeTodo> 
|ReturnType<typeof toggleTodo> 


//상태에서 사용할 할일 항목의 타입정의
export type Todo ={
    id: number;
    text: string;
    isDone: boolean;
}
//상태에 대한 타입
export type TodoState = Todo[]

// 초기상태 선언
const initialState: TodoState = [];

//3. 리듀서 함수 - state타입 action타입지정
//type-action 적용전
function todos(state: TodoState= initialState, action:TodoAction){
    switch(action.type){
        case ADD_TODO:
            return [
                ...state,
                {
                    id: action.payload.id,
                    text: action.payload.text,
                    isDone: false
                }
            ]
        case TOGGLE_TODO:
            //이전상태 배열을 순환하며 배열 요소의 id값과 action의 payload일치하는지
            //값이 일치하면 요소의 isDone을 반전해서 리턴
            //일치하지 않으면 배열요소 리턴
            return state.map(todo=>todo.id === action.payload ? 
                {...todo, isDone: !todo.isDone}: todo);
        case REMOVE_TODO:       
            return state.filter(todo=> todo.id !== action.payload)    
        default:
            return state    
    }
}

export default todos;

 

모듈 합치기

 

modules폴더 안

index.ts

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

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

export default rootReducer;


//리듀서 호출 리턴?? 상태!!!
//rootReducer가 실행되면 state리턴함
//ReturnType<typeof rootReducer> 특정함수의 리턴 타입을 추론
//useSelector(state => state.todos)
//스토어의 상태값의 타입을 추론

export type rootState =ReturnType<typeof rootReducer>  
//특정함수의 리턴하는 값의 타입을 반환. state의 타입 돌려줌

 

스토어는 counter만들때 생성함

 

 

todoheader부분 프레젠테이셔널 컴포넌트 생성 (화면구현)

components 폴더안

TodoInsert.tsx

import React, {useState} from 'react';

//props 타입 지정 인터페이스
type TodoInsertProps = {
    onInsert: (text: string) => void;
}


const TodoInsert = ({onInsert}: TodoInsertProps) => {
    //input 입력값 상태관리
    const [inputText, setInputText] = useState("")
    const onChange = (e:React.ChangeEvent<HTMLInputElement>) => {
        setInputText(e.target.value)
    }
    const onClick = () =>{
        onInsert(inputText)
        setInputText("")
    }
    return (
        <div>
            <input placeholder='할일을 등록하세요' value={inputText} onChange={onChange}/>
            <button onClick={onClick}>등록</button>
        </div>
    );
};

export default TodoInsert;

 

todolist부분 프레젠테이셔널 컴포넌트 생성 (화면구현)

components 폴더안

TodoLists.tsx

import React from 'react';
import { Todo } from '../modules/todos';

type TodoItemProps = {
    todo: Todo;
    onToggle: (id:number) => void;
    onRemove: (id:number) => void;
}


//스타일 따로 뺄려고 함수 만듬
function TodoItem({todo, onToggle, onRemove}:TodoItemProps) {
    const removeStyle: React.CSSProperties = {
        textDecoration: todo.isDone? "line-through": "none"
    }
    const onTodoToggle = () => {
        onToggle(todo.id)
    }
    const onTodoRemove = () => {
        onRemove(todo.id)
    }
    return (
        <li style={removeStyle} >
        <span onClick={onTodoToggle}>{todo.text}</span>
        <button onClick={onTodoRemove}>삭제</button></li>
    )
}
type TodoListProps = {
    todos: Todo[]
    onToggle: (id:number) => void;
    onRemove: (id:number) => void;
}
const TodoList = ({todos, onToggle, onRemove}: TodoListProps) => {
    return (
        <div>
            <ul>
                {todos.map(todo=> <TodoItem key={todo.id} todo={todo} 
                onToggle={onToggle} onRemove={onRemove}/>)}
            </ul>
        </div>
    );
};

export default TodoList;

 

Todolist 컨테이너컴포넌트 생성(store에 접근/업데이트)

containers 폴더 안

TodosContainer.tsx

import React from 'react';
import { useDispatch } from 'react-redux';
import { useSelector } from 'react-redux';
import TodoInsert from '../components/TodoInsert';
import TodoList from '../components/TodoList';
import { rootState } from '../modules';
import { addTodo, removeTodo, toggleTodo } from '../modules/todos';

const TodosContainer = () => {
    const todos = useSelector((state: rootState)=>state.todos)
    const dispatch = useDispatch();
    const onToggle = (id:number) => {
        dispatch(toggleTodo(id))
    }
    const onInsert = (text:string) => {
        dispatch(addTodo(text))
    }
    const onRemove = (id:number) => {
        dispatch(removeTodo(id))
    }
    return (
        <div>
            <TodoInsert onInsert={onInsert} />
            <TodoList todos={todos} onToggle={onToggle} onRemove={onRemove}/>
        </div>
    );
};

export default TodosContainer;

 

TodoContainer 조립

src 폴더 안

App.tsx

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


function App() {
  return (
    <div className="App">
      <CounterContainer/>
      <TodosContainer/>
    </div>
  );
}

export default App;
728x90
반응형

댓글