React

Redux 예제 (Redux를 알아보자)

데굴데구르르 림 2023. 2. 14. 10:27
728x90

리액트를 독학하기 시작하면서 Redux도 알게돼서 스스로 이해하고 알아본 내용을 작성해봅니다. 공홈을 통해서 공부했고, 영어 번역을 제대로 하지 않아서 제가 알고있는 내용이 정확하지 않을 수 있으므로 공홈을 꼭 참고하시길 바랍니다.

 

Counter 예제를 만들어보자

먼저 완성된 화면부터 보자면, 

위와 같고, +를 누르면 1 증가, -를 누르면 1 감소

Add Amount : 왼쪽박스에 적힌 수 만큼 증가

Add Async : 왼쪽박스에 적힌 수 만큼 Async 증가 (사용자가 지정한 Async, 여기서는 setTimeout 1000.)

 

Redux는 새로고침을 해도 변한 값이 유지가 된다는 장점이 있다.

 

Application Content

 

  • /src
    • index.js : app 시작점
    • App.js : React Component의 최상단 부분이 있는 곳
    • /app
      • store.js : Redux store 생성자를 만듦
    • /features
      • /counter
        • Count.js : 카운터 UI를 보여주는 React Component 
        • counterSlice.js : 카운터 Redux 로직

 

 

Redux Store 생성

app/store.js 

import { configureStore } from '@reduxjs/toolkit'
import counterReducer from '../features/counter/counterSlice'

export default configureStore({
  reducer: {
    counter: counterReducer
  }
})

React Toolkit에서 제공하는 configureStore로 Redux store을 생성함.

{counter: counterReducer}  key-value 또는 alias 개념으로 생각하면된다.
즉, 여기의 counterReducer를 쓰려면 state.counter 이런식으로 불러와서 사용하면 됨.
만약 {counter: counterReducer}이 아니라 그냥 {counterReducer} 라고 쓴다면 state.counterReducer 라고 불러와서 쓰면 됨.

store가 생성됐다.
이제 어디서든 counter의 값이 변하면 여기에 저장이 될 것이다.

 

더 많은 Slice 사용

 

import { configureStore } from '@reduxjs/toolkit'
import usersReducer from '../features/users/usersSlice'
import postsReducer from '../features/posts/postsSlice'
import commentsReducer from '../features/comments/commentsSlice'

export default configureStore({
  reducer: {
    users: usersReducer,
    posts: postsReducer,
    comments: commentsReducer
  }
})

 

 

 

Slice Reducers 및 Actions 생성

features/counter/counterSlice.js

import { createSlice } from '@reduxjs/toolkit'

export const counterSlice = createSlice({
  name: 'counter',
  initialState: {
    value: 0
  },
  reducers: {
    increment: state => {
      // Redux Toolkit allows us to write "mutating" logic in reducers. It
      // doesn't actually mutate the state because it uses the immer library,
      // which detects changes to a "draft state" and produces a brand new
      // immutable state based off those changes
      state.value += 1
    },
    decrement: state => {
      state.value -= 1
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload
    }
  }
})

export const { increment, decrement, incrementByAmount } = counterSlice.actions

export const selectCount = (state) => state.counter.value;

export default counterSlice.reducer

 

첫번째 인트로에서 다음과 같은 세 가지의 서로 다른 버튼을 만들었던 것을 볼 수 있었다.

  • {type: "counter/increment"}
  • {type: "counter/decrement"}
  • {type: "counter/incrementByAmount"}

 

Redux Toolkit은 createSlice를 제공한다. createSlice는 name과 초기값 설정을 할 수 있으며, action을 지정할 수 있다.

이 예제에서는 name: 'counter', 초기값은 0 으로 설정

reducers 내에서 수행할 action을 작성.

increment : 1증가
decrement  : 1감소
incrementByAmount : value 값 만큼 증가

마지막  세 줄의 추가도 잊으면 안된다.

 

The React Counter Component

features/counter/Counter.js

import React, { useState } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import {
  decrement,
  increment,
  incrementByAmount,
  selectCount
} from './counterSlice'
import styles from './Counter.module.css'

export function Counter() {
  const count = useSelector(selectCount)
  const dispatch = useDispatch()
  const [incrementAmount, setIncrementAmount] = useState('2')

  return (
    <div>
      <div className={styles.row}>
        <button
          className={styles.button}
          aria-label="Increment value"
          onClick={() => dispatch(increment())}
        >
          +
        </button>
        <span className={styles.value}>{count}</span>
        <button
          className={styles.button}
          aria-label="Decrement value"
          onClick={() => dispatch(decrement())}
        >
          -
        </button>
      </div>
      {/* omit additional rendering output here */}
    </div>
  )
}

1. useSelectore을 통해 Redux store내의 필요한 값을 가져온다. (여기서는 counter이 되겠다.)
2. useDispatch 를 사용해야지만 store 값을 변경할 수 있다. useDispatch를 사용하지 않으면 우리가 직접적으로 store 값을 변화시킬수가 없음.. 
3. button onClick이벤트는 counterSlice내의 기능 버튼들을 사용

 

마지막, Store 제공

useSelector와 useDispatch hook은 Redux store 를 호출해야 사용해야 쓸 수 있다. 

import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'
import store from './app/store'
import { Provider } from 'react-redux'
import * as serviceWorker from './serviceWorker'

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

ReactDom.render(<App />

1. import app/store.js 
2. <App>을 <Provider> component 안에 감싸고, Provider에 store={store} 사용을 명시.

<Provider>를 가져와서 Redux store을 호출했고, 이제 React Components에서 useSelectore 또는 useDispatch를 쓸 수 있게됐다.

 

 

끝.

 

 

 

최종응용

나는 A페이지에서 어떤 값을 20으로 설정했을때, 다른 페이지 B에서도 20값을 불러와 지는지 궁금했다.
(당연히 Redux는 모든 프로젝트 내 state가 공유되므로 가능하겠지?)

1. 위 예제를 따라서 그냥 단순히 숫자 증가, 감소만 생성

2. 숫자 변경

3. 다른페이지에서 확인

 

재미있다 Redux!

↓Redux 예제 응용한 코드 [더보기]↓

더보기

Redux 예제 응용 코드 [더보기]


-------------------------------------------Market.js

import React, {useState} from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './MarketSlice';
import styles from './Counter.module.css';
export function Market() {
    const product = useSelector((state) => state.market.number);
    const dispatch = useDispatch();
    return(
        <div>
            <div className={styles.row}>
                <button
                    className={styles.button}
                    aria-label="물건빼기"
                    onClick={() => dispatch(decrement())}
                >
                    -
                </button>
                <span>{product}</span>
                <button
                    className={styles.button}
                    aria-label="물건더하기"
                    onClick={() => dispatch(increment())}
                >+</button>
            </div>
        </div>
    );
}
 
 
------------------------------------MarketSlice.js
import { createSlice } from '@reduxjs/toolkit';

// Actions
const INCREMENT = "INCREMENT";
const DECREMENT = "DECREMENT";

// Action Creator
export const increment = () => {
  return {
    type: INCREMENT,
  };
}
export const decrement = () => {
  return {
    type: DECREMENT,
  };
}

// 초기값 설정
const initialState = {
  number: 11,
};

// product Reducer
export default function counter(state = initialState, action) {
  switch (action.type) {
    case INCREMENT:
      return {
        number: state.number + 1,
      };
    case DECREMENT:
      return {
        number: state.number - 1,
      };
    default:
      return state;
  }
}
 
------------------------------------store.js
 
import { configureStore } from '@reduxjs/toolkit';
import storage from 'redux-persist/lib/storage';
import { persistReducer } from 'redux-persist';
import thunk from 'redux-thunk';
import { createStore, combineReducers, applyMiddleware } from 'redux';
import counterReducer from '../features/counter/counterSlice';
import marketReducer from "../features/counter/MarketSlice";
 
const rootReducer = combineReducers({
    counter: counterReducer,
    market: marketReducer,
});


const persistConfig = {
  key: "root",
  storage,
  whitelist: ["market"] // 적용할 리듀스,
  // blacklist : [ " dd"] //적용하지 않을 리듀스
};

const persistedReducer = persistReducer(persistConfig, rootReducer);

const middlewares = [thunk];

const enhancer = applyMiddleware(...middlewares);

const store = createStore(persistedReducer, enhancer);

export default store;
 
 
------------------------------------App.js (다른 페이지 이동을 위한 react-router-dom 사용
 
 
import React from 'react';
import logo from './logo.svg';
import { Routes, Route, BrowserRouter } from 'react-router-dom';

import { Counter } from './features/counter/Counter';
import { Market } from './features/counter/Market';
import { Bada } from './features/bada/badaMarket'

import './App.css';

function App() {
  return (
    <BrowserRouter>
      <div className="App">
     
        <header className="App-header">
          <div>
            <Routes>
              <Route path='/' element={<Counter/>}  />
              <Route path='/market' element={<Market/>}  />
              <Route path='/bada' element={<Bada/>}  />
            </Routes>
          </div>  
        </header>
     
      </div>
    </BrowserRouter>
  );
}

export default App;

 

 


https://redux.js.org/

https://redux.js.org/tutorials/essentials/part-2-app-structure