여러 Slice를 사용해보기

해당 포스트는 여러 Slice를 사용하는 것에 중점을 두고 있어 React 앱에 대한 기능 설명이 적습니다.

이전 포스트에서는 counterSlice만을 사용하여 Counter 기능을 구현해봤다.

그리고 Redux의 단 하나뿐인 저장소에 configureStore 함수를 사용하여 Slice를 등록하여 사용했었다. configureStore 함수는 여러 개의 Reducer들을 하나로 합치는 역할을 하는데 이번 포스트에서 authSlice를 추가하여 등록하여 사용해보도록 하자.

authSlice 추가하기

추가한 authSlice는 로그인/로그아웃 상태를 업데이트하는 State를 담고 있다.

// store.js

// Counter Slice
const counteInitState = { counter: 0, showCounter: true };
const counterSlice = createSlice({
  name: "counter",
  initialState: counteInitState,
  reducers: {
    increment(state) {
      state.counter++;
    },
    increase(state, action) {
      state.counter = state.counter + action.payload;
    },
    decrement(state) {
      state.counter--;
    },
    toggleCounter(state) {
      state.showCounter = !state.showCounter;
    },
  },
});

// Auth Slice
const authInitState = { isAuth: false };

const authSlice = createSlice({
  name: "authenticated",
  initialState: authInitState,
  reducers: {
    LOGIN(state) {
      state.isAuth = true;
    },
    LOGOUT(state) {
      state.isAuth = false;
    },
  },
});

위의 코드와 같이 authSlice를 추가하여 counterSlice를 포함하여 총 두 개의 Slice가 있다.

이것을 configureStore의 객체 형태의 인자에 reducer 프로퍼티에 객체로 추가한다.
추가를 했다면 이제 다른 컴포넌트에서 dispatch를 통해 action 객체를 전달받기 위해 위해 Slice의 actions 프로퍼티에 접근하여 export 해준다.

// store.js

const store = configureStore({
  // reducer 프로퍼티의 값을 객체로 하면 다수의 Slice의 Reducer를 추가할 수 있다.
  reducer: {
    counter: counterSlice.reducer,
    auth: authSlice.reducer,
  },
});

export const counterActions = counterSlice.actions;
export const authActions = authSlice.actions;

export default store;


action 객체 전달하기

사용하는 것은 다른 컴포넌트에서 authActionsuseDispatch를 사용하여 action 객체를 전달할 수 있다.

Auth.js 컴포넌트 파일에서는 authActionsuseDispatch를 사용하여 form을 제출하면 authSlice의 Reducer인 LOGIN을 호출해서 로그인 상태를 업데이트하고 있다.

// Auth.js

import { useDispatch } from "react-redux";
import { authActions } from "../store/auth";

import classes from "./Auth.module.css";

const Auth = () => {
  const dispatch = useDispatch();

  const loginHandler = (event) => {
    event.preventDefault();
    dispatch(authActions.LOGIN());
  };

  return (
    <main className={classes.auth}>
      <section>
        <form onSubmit={loginHandler}>
          <div className={classes.control}>
            <label htmlFor="email">Email</label>
            <input type="email" id="email" />
          </div>
          <div className={classes.control}>
            <label htmlFor="password">Password</label>
            <input type="password" id="password" />
          </div>
          <button>Login</button>
        </form>
      </section>
    </main>
  );
};

export default Auth;

Header.js 컴포넌트 파일 또한 authActionsuseDispatch를 사용하여 Logout 버튼을 클릭하면 authSlice의 Reducer인 LOGOUT을 호출해서 로그인 상태를 업데이트하고 있다.

그리고 useSelector Hook을 사용하여 isAuth 데이터를 추출하여 State에 따라 조건부 렌더링을 하고 있다.

// Header.js

import { useSelector, useDispatch } from "react-redux";
import { authActions } from "../store/auth";

import classes from "./Header.module.css";

const Header = () => {
  const isAuth = useSelector((state) => state.auth.isAuth);
  const dispatch = useDispatch();

  const logoutHandler = () => {
    dispatch(authActions.LOGOUT());
  };

  return (
    <header className={classes.header}>
      <h1>Redux Auth</h1>
      {isAuth && (
        <nav>
          <ul>
            <li>
              <a href="/">My Products</a>
            </li>
            <li>
              <a href="/">My Sales</a>
            </li>
            <li>
              <button onClick={logoutHandler}>Logout</button>
            </li>
          </ul>
        </nav>
      )}
    </header>
  );
};

export default Header;

App.js 컴포넌트에서는 useSelector Hook을 사용하여 저장소에서 isAuth의 데이터를 추출하여 AuthUserProfile 컴포넌트를 조건부 렌더링하고 있다.

import { useSelector } from "react-redux";

import Header from "./components/Header";
import UserProfile from "./components/UserProfile";
import Auth from "./components/Auth";
import Counter from "./components/Counter";

function App() {
  const isAuth = useSelector((state) => state.auth.isAuth);

  return (
    <>
      <Header />
      {!isAuth && <Auth />}
      {isAuth && <UserProfile />}
      <Counter />
    </>
  );
}

export default App;

여기서 useSelector를 사용한 코드를 보면 길어진 것을 볼 수 있다.

// Header와 Auth 컴포넌트에서 사용된 useSelector이다.
// state의 auth의 isAuth의 데이터를 추출한다.
const isAuth = useSelector((state) => state.auth.isAuth);

이것은 여러 개의 Slice.reducer를 사용하면 그렇다.

하나의 Slice.reducer를 사용하면 아래처럼 store에 등록이 되기 때문에 stateisAuth에 바로 접근할 수 있다.

const store = configureStore({
  reducer: authSlice.reducer,
});

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

하지만 여러 개의 Slice.reducer를 사용하면 store에 등록할 때 Slice.reducer식별자로 key를 작성해야하기 때문에 useSelector를 사용할 때 데이터에 접근해야하는 과정이 길어진다.

const store = configureStore({
  reducer: {
    counter: counterSlice.reducer,
    auth: authSlice.reducer,
  },
});

// state.auth.isAuth는 [저장소의 전체 데이터(state)] > [authSlice의 reducer(auth)] > [추출 데이터(isAuth)]로 접근이 가능하다.
const isAuth = useSelector((state) => state.auth.isAuth);


Slice별 파일 분기

Redux는 하나의 저장소를 가지는데 하나의 파일에 다수의 Slice가 있다면 코드를 읽기도 힘들다.
그래서 이것을 Slice를 개별적인 파일로 분기하여 관리의 편의를 증가시키도록 한다.

// counter.js

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

// Counter Slice
const counteInitState = { counter: 0, showCounter: true };
const counterSlice = createSlice({
  name: "counter",
  initialState: counteInitState,
  reducers: {
    increment(state) {
      state.counter++;
    },
    increase(state, action) {
      state.counter = state.counter + action.payload;
    },
    decrement(state) {
      state.counter--;
    },
    toggleCounter(state) {
      state.showCounter = !state.showCounter;
    },
  },
});

export const counterActions = counterSlice.actions;

// 보통 Slice 자체를 export하지 않고 reducer 프로퍼티에 접근하여 export한다.
export default counterSlice.reducer;
// auth.js

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

// Auth Slice
const authInitState = { isAuth: false };
const authSlice = createSlice({
  name: "authenticated",
  initialState: authInitState,
  reducers: {
    LOGIN(state) {
      state.isAuth = true;
    },
    LOGOUT(state) {
      state.isAuth = false;
    },
  },
});

export const authActions = authSlice.actions;

// 보통 Slice 자체를 export하지 않고 reducer 프로퍼티에 접근하여 export한다.
export default authSlice.reducer;

분기한 Slice 파일의 Slice.reducer를 가져와서 store 파일에서 사용하면 하나의 저장소를 유지하면서 개별적인 Slice 파일을 관리할 수 있다.

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

// 각 Slice 파일에서 export된 Slice.reducer를
import counterReducer from "./counter";
import authReducer from "./auth";

const store = configureStore({
  reducer: {
    // 여기에 사용!
    counter: counterReducer,
    auth: authReducer,
  },
});

export default store;

그리고 파일을 분기하면서 파일의 경로가 변경되었기 때문에 경로를 수정하는 것으로 마무리하면 된다.

댓글남기기