๐Ÿ“’ ์˜ค๋Š˜ ๊ณต๋ถ€ํ•œ ๋‚ด์šฉ

๋ฆฌ๋•์Šค(Redux)๋Š” JavaScript ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ ์˜ˆ์ธก ๊ฐ€๋Šฅํ•œ(state predictable) ์ปจํ…Œ์ด๋„ˆ ์ƒํƒœ ๊ด€๋ฆฌ ํŒจํ„ด๊ณผ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋‹ค. ๋ฆฌ๋•์Šค๋Š” ์ฃผ๋กœ React์™€ ํ•จ๊ป˜ ์‚ฌ์šฉ๋˜๋ฉฐ, React ์™ธ์—๋„ Angular, Vue ๋“ฑ ๋‹ค๋ฅธ ํ”„๋ ˆ์ž„์›Œํฌ์™€๋„ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

๋ฆฌ๋•์Šค(Redux)์˜ ์ฃผ์š” ๋ชฉํ‘œ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ƒํƒœ ๋ณ€ํ™”๋ฅผ ์˜ˆ์ธก ๊ฐ€๋Šฅํ•˜๊ณ  ๋””๋ฒ„๊น…ํ•˜๊ธฐ ์‰ฝ๊ฒŒ ๋งŒ๋“ค์–ด ๊ฐœ๋ฐœ์ž๋“ค์ด ๋ณต์žกํ•œ ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ๊ฐ„ํŽธํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ์ƒํƒœ๋ฅผ ์ค‘์•™ ์ง‘์ค‘ํ™”ํ•˜์—ฌ ๊ด€๋ฆฌํ•จ์œผ๋กœ์จ ๋‹ค์–‘ํ•œ ์ปดํฌ๋„ŒํŠธ ๊ฐ„์— ์ƒํƒœ ๊ณต์œ ๊ฐ€ ๊ฐ€๋Šฅํ•˜๊ณ , ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ณต์žก์„ฑ์„ ์ค„์ผ ์ˆ˜ ์žˆ๋‹ค.

โœ… ์Šคํ† ์–ด(Store)

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜๋Š” ๊ฐ์ฒด, ๋ฆฌ๋•์Šค ์Šคํ† ์–ด๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ƒํƒœ๋ฅผ ๋‹จ์ผ ์†Œ์Šค๋กœ ์œ ์ง€ํ•œ๋‹ค.

import { createStore } from 'redux';

// ์ดˆ๊ธฐ ์ƒํƒœ ์ •์˜
const initialState = {
  count: 0,
};

// ๋ฆฌ๋“€์„œ ํ•จ์ˆ˜ ์ •์˜
function reducer(state = initialState, action) {
  switch (action.type) {
    case 'INCREMENT':
      return {
        ...state,
        count: state.count + 1,
      };
    case 'DECREMENT':
      return {
        ...state,
        count: state.count - 1,
      };
    default:
      return state;
  }
}

// ์Šคํ† ์–ด ์ƒ์„ฑ
const store = createStore(reducer);

โœ… ์•ก์…˜(Action)

์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜๊ธฐ ์œ„ํ•œ ์ •๋ณด๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ๊ฐ์ฒด, ์•ก์…˜์€ ๋ฐ˜๋“œ์‹œ type ํ•„๋“œ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์–ด์•ผ ํ•˜๋ฉฐ, ํ•„์š”์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ๋ฐ์ดํ„ฐ๋ฅผ ํฌํ•จํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

// ์•ก์…˜ ํƒ€์ž… ์ •์˜
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';

// ์•ก์…˜ ์ƒ์„ฑ ํ•จ์ˆ˜ ์ •์˜
function increment() {
  return { type: INCREMENT };
}

function decrement() {
  return { type: DECREMENT };
}

// ๋””์ŠคํŒจ์น˜ ์˜ˆ์ œ
store.dispatch(increment());
store.dispatch(decrement());

โœ… ๋ฆฌ๋“€์„œ(Reducer)

์•ก์…˜๊ณผ ์ด์ „ ์ƒํƒœ๋ฅผ ๋ฐ›์•„ ์ƒˆ๋กœ์šด ์ƒํƒœ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ์ˆœ์ˆ˜ํ•œ ํ•จ์ˆ˜, ๋ฆฌ๋“€์„œ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ๋กœ์ง์ด ํฌํ•จ๋˜์–ด ์žˆ๋‹ค.

function reducer(state = initialState, action) {
  switch (action.type) {
    case 'INCREMENT':
      return {
        ...state,
        count: state.count + 1,
      };
    case 'DECREMENT':
      return {
        ...state,
        count: state.count - 1,
      };
    default:
      return state;
  }
}

โœ… ์„ ํƒ์ž(Selector)

์ƒํƒœ์—์„œ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”์ถœํ•˜๋Š” ํ•จ์ˆ˜, ์„ ํƒ์ž๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์‰ฝ๊ฒŒ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋‹ค.

// ์ƒํƒœ์—์„œ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”์ถœํ•˜๋Š” ์„ ํƒ์ž ํ•จ์ˆ˜
function getCount(state) {
  return state.count;
}

// ์„ ํƒ์ž ์‚ฌ์šฉ ์˜ˆ์ œ
const currentState = store.getState();
const currentCount = getCount(currentState);
console.log(currentCount); // ์ƒํƒœ์—์„œ ์ถ”์ถœํ•œ count ๊ฐ’ ์ถœ๋ ฅ

โœ… React redux ์‚ฌ์šฉ๋ฒ•

โ—พ store.js ์ƒ์„ฑ

โ—พstore.js

  • Action ๋งŒ๋“ค๊ธฐ
export const plusStr = () => ({ type: "PLUS", str: "helloWorld" });
  • ์ƒํƒœ ๊ฐ์ฒด ๋งŒ๋“ค๊ธฐ
const initState = {
  str: "์•ˆ๋…•ํ•˜์„ธ์š”",
};
  • reducer ๋งŒ๋“ค๊ธฐ
export const reducer = (state = initState, action) => {
  switch (action.type) {
    case "PLUS":
      return { str: state.str + state.str };
    default:
      return state;
  }
};

โ—พ_app.js์—์„œ Redux์„ค์ •

import { Provider } from "react-redux";
import { createStore } from "redux";
import reducer from "../src/commons/store"; // ๋ฆฌ๋“€์„œ ํŒŒ์ผ ๊ฒฝ๋กœ

import Layout from "../src/commons/Layout/index";
import "../styles/globals.css";

// ๋ฃจํŠธ ๋ฆฌ๋“€์„œ๋กœ ์Šคํ† ์–ด ์ƒ์„ฑ
const store = createStore(reducer);

export default function App({ Component, pageProps }) {
  return (
    <Provider store={store}>
      <Layout>
        <Component {...pageProps} />
      </Layout>
    </Provider>
  );
}

โ—พ useSelector ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉ

import Head from "next/head";
import { useSelector } from "react-redux";

export default function GraphqlMutationPage() {
  const str = useSelector((store) => store.str);
  return (
    <>
      <Head>
        <title>Redux</title>
      </Head>
      <h1>{str}</h1>
    </>
  );
}

โ—พ ์ดˆ๊ธฐํ™”๋ฉด

โœ… ๋ฆฌ๋•์Šค(Redux)์™€ ๋ฆฌ์ฝ”์ผ(Recoil)

โ—พ ์„ค๊ณ„ ์ฒ ํ•™

๋ฆฌ๋•์Šค : Flux ์•„ํ‚คํ…์ฒ˜์˜ ๊ตฌํ˜„์ฒด ์ค‘ ํ•˜๋‚˜๋กœ, ๋‹จ๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ๋”ฐ๋ฅธ๋‹ค. ์•ก์…˜(Action)์„ ํ†ตํ•ด ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜๊ณ , ๋ณ€๊ฒฝ๋œ ์ƒํƒœ๋ฅผ ์ปดํฌ๋„ŒํŠธ์—๊ฒŒ ์ „๋‹ฌํ•˜์—ฌ ์—…๋ฐ์ดํŠธํ•˜๋Š” ํŒจํ„ด์ด๋‹ค.

๋ฆฌ์ฝ”์ผ : React ํŒ€์—์„œ ๊ฐœ๋ฐœํ•œ ์ƒํƒœ ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ, Recoil์€ ๋‹จ๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ํ๋ฆ„ ๋Œ€์‹ , ์–‘๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ์ง€ํ–ฅํ•œ๋‹ค. ์ƒํƒœ๋ฅผ ์ฝ๊ณ  ์“ฐ๋Š” ๊ฒƒ์ด ๋” ์ž์—ฐ์Šค๋Ÿฝ๊ณ  ๊ฐ„๋‹จํ•˜๊ฒŒ ์ด๋ฃจ์–ด์ง„๋‹ค.

โ—พ API ๋ฐ ๋ฌธ๋ฒ•

๋ฆฌ๋•์Šค : Redux๋Š” ๋งŽ์€ ์ค‘๊ฐ„์›จ์–ด์™€ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ์˜ ๋น„๋™๊ธฐ์ ์ธ ํ๋ฆ„์„ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค. ๋˜ํ•œ, ์ƒํƒœ์˜ ๋ณ€๊ฒฝ์„ ์œ„ํ•ด ์•ก์…˜๊ณผ ๋ฆฌ๋“€์„œ(reducer)๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ณ„๋„์˜ ๊ฐœ๋…์ด ์žˆ๋‹ค.

๋ฆฌ์ฝ”์ผ : Recoil์€ React Hook์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ„ํŽธํ•œ ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ์ œ๊ณตํ•œ๋‹ค. ์•ก์…˜๊ณผ ๋ฆฌ๋“€์„œ ๊ฐœ๋…์ด ์—†์œผ๋ฉฐ, ์ปดํฌ๋„ŒํŠธ์—์„œ ์ง์ ‘ ์ƒํƒœ๋ฅผ ์ฝ๊ณ  ์“ฐ๋Š” ๊ฒƒ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

โ—พ ์ ์šฉ์˜ ์šฉ์ด์„ฑ

๋ฆฌ๋•์Šค : ๋ฆฌ๋•์Šค๋Š” ์•ฝ๊ฐ„์˜ ํ•™์Šต ๊ณก์„ ์ด ์žˆ์œผ๋ฉฐ, ์ดˆ๊ธฐ ๊ตฌ์„ฑ ๋ฐ ์„ค์ •์ด ๋” ๋ณต์žกํ•  ์ˆ˜ ์žˆ๋‹ค. ๋” ํฐ ๊ทœ๋ชจ์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์ ํ•ฉํ•˜๋‹ค๊ณ  ์•Œ๋ ค์ ธ ์žˆ๋‹ค.

๋ฆฌ์ฝ”์ผ : Recoil์€ ํ›จ์”ฌ ๊ฐ„๋‹จํ•œ API๋ฅผ ์ œ๊ณตํ•˜๋ฉฐ, React ์ƒํƒœ๊ณ„์™€ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ํ†ตํ•ฉ๋œ๋‹ค. ์ดˆ๊ธฐ ์„ค์ •์ด ๋” ์‰ฝ๊ณ  ๊ฐ„๋‹จํ•˜๋ฉฐ, ์ƒ๋Œ€์ ์œผ๋กœ ์ž‘์€ ๊ทœ๋ชจ์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์ ํ•ฉํ•˜๋‹ค.

โœ ์•Œ๊ฒŒ๋œ ์  / ๊ณผ์ œ

๐Ÿ“ ์‹ค์Šต

store.js

import { createStore, combineReducers } from "redux";

// Action ์ƒ์„ฑ ํ•จ์ˆ˜
export const setLoggedIn = () => {
  return {
    type: "SET_LOGGED_IN",
  };
};

export const setLoggedOut = () => {
  return {
    type: "SET_LOGGED_OUT",
  };
};

export const setCountUp = () => {
  return {
    type: "SET_COUNT_UP",
  };
};

export const setCountDown = () => {
  return {
    type: "SET_COUNT_DOWN",
  };
};

// ๋กœ๊ทธ์ธ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” Reducer
const authReducer = (state = { isLoggedIn: false }, action) => {
  switch (action.type) {
    case "SET_LOGGED_IN":
      return {
        ...state,
        isLoggedIn: true,
      };
    case "SET_LOGGED_OUT":
      return {
        ...state,
        isLoggedIn: false,
      };
    default:
      return state;
  }
};

const counter = (count = { isCounter: 0 }, action) => {
  switch (action.type) {
    case "SET_COUNT_UP":
      return {
        ...count,
        isCounter: count.isCounter + 1,
      };
    case "SET_COUNT_DOWN":
      return {
        ...count,
        isCounter: count.isCounter - 1,
      };
    default:
      return count;
  }
};

// ๋ฆฌ๋“€์„œ๋“ค์„ ํ•ฉ์นจ
const rootReducer = combineReducers({
  auth: authReducer,
  counter: counter,
});

// ์Šคํ† ์–ด ์ƒ์„ฑ
const store = createStore(rootReducer);

export default store;

index.jsx

import React from "react";
import { connect } from "react-redux";
import {
  setLoggedIn,
  setLoggedOut,
  setCountUp,
  setCountDown,
} from "../../commons/store";
import { useSelector, useDispatch } from "react-redux";
import styled from "@emotion/styled";

const Container = styled.div`
  max-width: 500px;
  margin: 100px auto;
  text-align: center;
  padding: 20px;
  border: 1px solid #ccc;
  border-radius: 4px;
`;

const Title = styled.h1`
  font-size: 24px;
  text-align: center;
`;

const Button = styled.button`
  margin-right: 10px;
  padding: 10px 20px;
  background-color: #4caf50;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  transition: background-color 0.3s ease;
  margin-top: 20px;

  &:hover {
    background-color: #45a049;
  }
`;

const SomeComponent = () => {
  const isLoggedIn = useSelector((state) => state.isLoggedIn);
  const isCounter = useSelector((count) => count.isCounter);

  const aa = useDispatch();
  const bb = useDispatch();
  const handleButtonClick = () => {
    aa(setLoggedIn()); // ๋กœ๊ทธ์ธ ์•ก์…˜ ๋””์ŠคํŒจ์น˜
  };

  const handleButtonClick2 = () => {
    aa(setLoggedOut()); // ๋กœ๊ทธ์•„์›ƒ ์•ก์…˜ ๋””์ŠคํŒจ์น˜
  };

  const handleButtonClick3 = () => {
    bb(setCountUp());
  };

  const handleButtonClick4 = () => {
    bb(setCountDown());
  };

  const { auth, counter } = useSelector((aaa) => aaa); // aaa์— ์–ด๋–ค ๊ฐ’์ด ๋“ค์–ด์™€๋„ ์ƒ๊ด€X
  return (
    <>
      <Container>
        <Title>๋กœ๊ทธ์ธ ์ƒํƒœ : {auth.isLoggedIn ? "Yes" : "No"}</Title>
        <Button onClick={handleButtonClick}>๋กœ๊ทธ์ธ</Button>
        <Button onClick={handleButtonClick2}>๋กœ๊ทธ์•„์›ƒ</Button>
      </Container>
      <Container>
        <Title>์นด์šดํ„ฐ : {counter.isCounter}</Title>
        <Button onClick={handleButtonClick3}>์ฆ๊ฐ€</Button>
        <Button onClick={handleButtonClick4}>๊ฐ์†Œ</Button>
      </Container>
    </>
  );
};
export default SomeComponent;
profile
1๋…„์ฐจ ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž

0๊ฐœ์˜ ๋Œ“๊ธ€