๋ฆฌ๋์ค(Redux)๋ JavaScript ์ ํ๋ฆฌ์ผ์ด์ ์ ์ํ ๊ด๋ฆฌ๋ฅผ ์ํ ์์ธก ๊ฐ๋ฅํ(state predictable) ์ปจํ ์ด๋ ์ํ ๊ด๋ฆฌ ํจํด๊ณผ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค. ๋ฆฌ๋์ค๋ ์ฃผ๋ก React์ ํจ๊ป ์ฌ์ฉ๋๋ฉฐ, React ์ธ์๋ Angular, Vue ๋ฑ ๋ค๋ฅธ ํ๋ ์์ํฌ์๋ ํจ๊ป ์ฌ์ฉํ ์ ์๋ค.
๋ฆฌ๋์ค(Redux)์ ์ฃผ์ ๋ชฉํ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ํ ๋ณํ๋ฅผ ์์ธก ๊ฐ๋ฅํ๊ณ ๋๋ฒ๊น ํ๊ธฐ ์ฝ๊ฒ ๋ง๋ค์ด ๊ฐ๋ฐ์๋ค์ด ๋ณต์กํ ์ํ ๊ด๋ฆฌ๋ฅผ ๊ฐํธํ๊ฒ ํ ์ ์๋๋ก ํ๋ ๊ฒ์ด๋ค. ์ํ๋ฅผ ์ค์ ์ง์คํํ์ฌ ๊ด๋ฆฌํจ์ผ๋ก์จ ๋ค์ํ ์ปดํฌ๋ํธ ๊ฐ์ ์ํ ๊ณต์ ๊ฐ ๊ฐ๋ฅํ๊ณ , ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ณต์ก์ฑ์ ์ค์ผ ์ ์๋ค.
์ ํ๋ฆฌ์ผ์ด์ ์ ์ํ๋ฅผ ์ ์ฅํ๋ ๊ฐ์ฒด, ๋ฆฌ๋์ค ์คํ ์ด๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ํ๋ฅผ ๋จ์ผ ์์ค๋ก ์ ์งํ๋ค.
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);
์ํ๋ฅผ ๋ณ๊ฒฝํ๊ธฐ ์ํ ์ ๋ณด๋ฅผ ๋ํ๋ด๋ ๊ฐ์ฒด, ์ก์ ์ ๋ฐ๋์ type ํ๋๋ฅผ ๊ฐ์ง๊ณ ์์ด์ผ ํ๋ฉฐ, ํ์์ ๋ฐ๋ผ ๋ค๋ฅธ ๋ฐ์ดํฐ๋ฅผ ํฌํจํ ์๋ ์๋ค.
// ์ก์ ํ์ ์ ์ const INCREMENT = 'INCREMENT'; const DECREMENT = 'DECREMENT'; // ์ก์ ์์ฑ ํจ์ ์ ์ function increment() { return { type: INCREMENT }; } function decrement() { return { type: DECREMENT }; } // ๋์คํจ์น ์์ store.dispatch(increment()); store.dispatch(decrement());
์ก์ ๊ณผ ์ด์ ์ํ๋ฅผ ๋ฐ์ ์๋ก์ด ์ํ๋ฅผ ๋ฐํํ๋ ์์ํ ํจ์, ๋ฆฌ๋์๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ํ๋ฅผ ๋ณ๊ฒฝํ๋ ๋ก์ง์ด ํฌํจ๋์ด ์๋ค.
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; } }
์ํ์์ ํ์ํ ๋ฐ์ดํฐ๋ฅผ ์ถ์ถํ๋ ํจ์, ์ ํ์๋ฅผ ์ฌ์ฉํ์ฌ ํ์ํ ๋ฐ์ดํฐ๋ฅผ ์ฝ๊ฒ ๊ฐ์ ธ์ฌ ์ ์๋ค.
// ์ํ์์ ํ์ํ ๋ฐ์ดํฐ๋ฅผ ์ถ์ถํ๋ ์ ํ์ ํจ์ function getCount(state) { return state.count; } // ์ ํ์ ์ฌ์ฉ ์์ const currentState = store.getState(); const currentCount = getCount(currentState); console.log(currentCount); // ์ํ์์ ์ถ์ถํ count ๊ฐ ์ถ๋ ฅ
export const plusStr = () => ({ type: "PLUS", str: "helloWorld" });
const initState = { str: "์๋ ํ์ธ์", };
export const reducer = (state = initState, action) => { switch (action.type) { case "PLUS": return { str: state.str + state.str }; default: return state; } };
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> ); }
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> </> ); }
๋ฆฌ๋์ค : Flux ์ํคํ
์ฒ์ ๊ตฌํ์ฒด ์ค ํ๋๋ก, ๋จ๋ฐฉํฅ ๋ฐ์ดํฐ ํ๋ฆ์ ๋ฐ๋ฅธ๋ค. ์ก์
(Action)
์ ํตํด ์ํ๋ฅผ ๋ณ๊ฒฝํ๊ณ , ๋ณ๊ฒฝ๋ ์ํ๋ฅผ ์ปดํฌ๋ํธ์๊ฒ ์ ๋ฌํ์ฌ ์
๋ฐ์ดํธํ๋ ํจํด์ด๋ค.
๋ฆฌ์ฝ์ผ : React ํ์์ ๊ฐ๋ฐํ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก, Recoil์ ๋จ๋ฐฉํฅ ๋ฐ์ดํฐ ํ๋ฆ ๋์ , ์๋ฐฉํฅ ๋ฐ์ดํฐ ํ๋ฆ์ ์งํฅํ๋ค. ์ํ๋ฅผ ์ฝ๊ณ ์ฐ๋ ๊ฒ์ด ๋ ์์ฐ์ค๋ฝ๊ณ ๊ฐ๋จํ๊ฒ ์ด๋ฃจ์ด์ง๋ค.
๋ฆฌ๋์ค : 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;