redux-toolkit

oneidsin·2025년 4월 15일

React

목록 보기
7/7

redux

기존 리액트에서는 state에 있는 것들을 자식 컴포넌트를 타고 하나한 내려보내야 했는데,
이것이 불편하기 때문에 리덕스라는 것이 등장했다.
리덕스는 설치해서 사용해야 한다.
npm install react-redux 로 설치한다.

  1. Store

    • 전역 상태를 저장하는 중앙 저장소
    • 하나의 앱에는 하나의 store만 존재 (보통)
    • getState()로 현재 상태 조회 가능
    • subscribe()로 상태 변화 감지해서 특정 함수 실행 가능
  2. Action

    • 상태를 어떻게 바꿀지 설명하는 "객체"
    • 필수로 type 필드를 포함해야 함
      예시: { type: "INCREMENT", payload: 1 }
  3. Reducer

    • 현재 상태(state)와 action을 받아서, 새로운 상태를 반환하는 함수
    • 순수 함수여야 함 (기존 상태를 직접 수정하지 않음)
  4. Dispatch

    • action 객체를 store에 전달해서 reducer를 실행하도록 함
    • store.dispatch(action) 형태로 사용
  5. subscribe()

    • store의 상태가 바뀔 때마다 호출될 함수를 등록하는 메서드
    • UI 업데이트 등에 사용됨

전체 흐름

  1. 컴포넌트에서 dispatch(action) 호출
  2. store가 해당 action을 reducer에 전달
  3. reducer가 현재 state와 action을 바탕으로 새로운 state를 계산
  4. store에 state가 업데이트됨
  5. 상태 변경을 감지한 subscribe() 함수들이 실행됨 (필요한 컴포넌트 리렌더링)

redux-toolkit

기존 리덕스를 좀 더 사용하기 쉽게 개선한 것이 리덕스 툴킷이다.
리덕스 툴킷도 설치가 필요하다.
npm install @reduxjs/toolkit 로 설치한다.

  1. 리덕스 툴킷 사용하기
    기존 리덕스에서 사용되던 것들이 툴킷에서는 통합되어 관리된다.

store

기존 리덕스에서 store는 단순히 전역 저장소 역할만 했으나, 툴킷에서는 리듀서를 등록하고
관리하는 역할도 한다.

store.jsx

// createStore() 는 deprecated 되었다.
// 그래서 configureStore 를 권장한다.
// store    : state 를 관리하는 객체
// reducer  : state 를 변화시키는 함수(이때 action 을 활용)
// dispatch    : 특정 reducer 를 호출하는 함수
// subscribe : state 변화 후 호출되는 callback 함수

// redux toolkit 에서는 위 내용을 조금 더 통합적으로 관리.
// store 는 reducer 를 등록하고 관리함.
const Store = configureStore({
    reducer: {
        todo: TodoReducer
    }
});

export default Store;

configureStore 를 사용해서 생성한다.


TodoReducer.jsx

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

// reducer : slice 라고도 불리며, state 를 수정하는 함수와 해당 state 를 관리.
const TodoReducer = createSlice({
    name: 'todo', // store 에 등록시 사용할 이름
    initialState: { // state 초기값(여러 컴포넌트에서 사용할 수 있다)
        idx: 3,
        list: [
            { idx: 1, todo: '스프링 부트 공부', done: true },
            { idx: 2, todo: '리액트 공부', done: false },
            { idx: 3, todo: '리덕스 공부', done: false },
        ]
    },
    reducers: { // 위 state 를 관리할 리듀서들...
        insert(state, action) { // state, 보내온 값(payload)
            state.idx++;
            let todo = { idx: state.idx, todo: action.payload, done: false };
            state.list.push(todo);
            return state; // reducer 는 항상 state 값 반환 필요.
        },
        toggle(state, action) {
            let index = state.list.findIndex((item) => {
                return item.idx === action.payload;
            });
            state.list[index].done = !state.list[index].done;
            return state;
        },
        del(state, action) {
            let index = state.list.findIndex((item) => {
                return item.idx === action.payload;
            });
            state.list.splice(index, 1);
            return state;
        }
    }
});

// slice 의 reducer 들을 외부에서 사용할 수 있도록 공개
export default TodoReducer.reducer;

그리고 리듀서는 createSlice로 생성하여 store 등록시 사용할 이름과 state 초기값을 지정하고, state 를 관리할 reducers 라는 곳에 사용할 함수들을 만든다.
참고로 리듀서는 슬라이스 라고도 불린다.


main.jsx

// npm install @reduxjs/toolkit 설치
// npm install react-redux
import { Provider } from "react-redux";
import { createRoot } from 'react-dom/client'
import App from './App.jsx'
import Store from "./redux/store.jsx";

// store 를 반드시 Provider 로 등록해야 한다.
createRoot(document.getElementById('root')).render(
  <Provider store={Store}>
    <App />
  </Provider>

)

main.jsx 에서는 store를 Provider 로 등록해줘야 한다.


Input.jsx

import { useState } from "react";
import Store from "../redux/store";

export default function Input() {

    const [input, setInput] = useState('');

    const keyUp = (e) => {
        if (e.keyCode === 13) {
            console.log(e);
            insert();
        }
    }

    const insert = () => { // slice 에 있는 reducer 를 호출
        // type : '호출할 리듀서', payload : Action
        Store.dispatch({ type: 'todo/insert', payload: input });
        setInput('');

    }

    return (
        <div>
            <h2>해야할 일</h2>
            <hr />
            <input type="text" value={input} onKeyUp={keyUp}
                onChange={(e) => setInput(e.target.value)} />
            <button onClick={insert}>추가</button>
        </div>
    );
}

입력을 받는 부분인 Input.jsx 이다. 여기서는 TodoReducer.jsx 에서 등록한 insert 함수를 호출해서 사용할 수 있다. 등록한 함수를 사용할 때는 Store.dispatch({}) 를 사용하고,
type 에는 reducer의 이름과 함수명을 넣고, payload 는 전달할 매개변수인데, 우리가 입력하는 input 을 사용할 것이므로 input 을 넣는다.


List.jsx

import { useSelector } from "react-redux";
import Store from "../redux/store";

export default function List() {

    let list = <div>등록된 아이템이 없습니다</div>;

    // slice 의 state 값 가져오기
    // useSelector 를 이용하면 모든 slice 의 state 값을 볼 수 있다.
    let slices = useSelector((state) => {
        console.log(state);
        return state.todo.list;
    });

    list = slices.map((item) => {
        return (<Item key={item.idx} item={item} />);
    });

    return (
        <>
            {list}
        </>
    );
}

// 어차피 list 에서만 쓸 것이므로 한 파일에 item 도 같이 넣음.
function Item({ item }) {
    let class_name = item.done ? 'text done' : 'text';

    const toggle = () => {
        Store.dispatch({ type: 'todo/toggle', payload: item.idx });
    }

    const del = () => {
        Store.dispatch({ type: 'todo/del', payload: item.idx });
    }

    return (
        <div className="item">
            <input type="checkbox" checked={item.done}
                onChange={toggle} />
            <div className={class_name}>{item.todo}</div>
            <div className="delete" onClick={del}>삭제</div>
        </div>
    );
}

List.jsx 에서는 리스트들을 렌더링 한다.
우선 TodoReducer.jsx 에 있는 state 값을 가져오기 위해 useSelector 를 사용한다.
가져온 것을 하나하나 꺼내기 위해 map을 사용한다.
그것을 리턴문에 집어넣어 렌더링한다.
그리고 Item 함수에서는 toggle 과 del 함수를 TodoReducer.jsx 로부터 호출해서 사용한다.

0개의 댓글