Never give up

React - redux toolkit example(createStore is deprecated) 본문

WEB

React - redux toolkit example(createStore is deprecated)

대기만성 개발자 2022. 5. 18. 15:40
반응형

< 잘쓰고 있었는데... >

간만에(?) 개발하던 웹 소스를 봤는데 createStore가 deprecated되었더군요..

 

밑에 내용을 확인해보니 redux toolkit에 있는 configureStore로 하라고 해서 사용법을 찾아봤습니다

(링크: https://redux-toolkit.js.org/)

 

필자가 사용하던 createStorereducerconfigureStore, createSlice로 대체됐습니다

createStore -> configureStore

reducer -> createSlice

 

src/store.js

import { configureStore } from "@reduxjs/toolkit"
import counter from "slices/counter"
import dialog from "slices/dialog"
import toast from "slices/toast"

export default configureStore({
    reducer: {
        counter,
        dialog,
        toast
    }
})

configureStore속성중 reducer에 만들어놓은 reducer를 넣어줍니다

 

이전에는 combineReducers에 넣어줬던것과 비슷한데 조금 더 편한거 같습니다

 

src/slices/toast.js

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

const toastSlice = createSlice({
    name: 'toast',
    initialState: { show: false, message: '' },
    reducers: {
        show: (state, action) => {
            if (state.show) {
                state.show = false
                state.message = ''
            }

            state.show = true
            state.message = action.payload
        },
        hide: (state) => {
            state.show = false
            state.message = ''
        }
    }
})

export const { show, hide } = toastSlice.actions

export default toastSlice.reducer

 

src/slices/dialog.js

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

const dialogSlice = createSlice({
    name: 'dialog',
    initialState: { value: false },
    reducers: {
        open: (state) => {
            state.value = true
        },
        close: (state) => {
            state.value = false
        }
    }
})

export const { open, close } = dialogSlice.actions

export default dialogSlice.reducer

src/slices/counter.js

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

const counterSlice = createSlice({
    name: 'counter',
    initialState: { value: 0 },
    reducers: {
        increment: (state) => {
            state.value += 1
        },
        decrement: (state) => {
            state.value -= 1
        },
        setValue: (state, action) => {
            state.value = action.payload
        }
    }
})

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

export default counterSlice.reducer

createSlice에 name, initialState, reducers를 넣어줍니다

 

기존에는 reducer에 switch로 action에 따라서 반환하는 state값을 다르게 해줬었는데

 

훨씬더 간단해진거 같습니다

 

필자가 사용한 slice는 총 세가지로

  1. 화면에 표시되는 숫자 상태변경
  2. dialog 상태 토글
  3. toast 메시지 상태

src/index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from 'App';
import reportWebVitals from 'reportWebVitals';
import { Provider } from 'react-redux';
import store from 'store'

const root = ReactDOM.createRoot(document.getElementById('root'));

root.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);

reportWebVitals();

store를 provider에 넣어주는 부분은 동일합니다

 

src/app.js

import { BrowserRouter, Route, Routes } from 'react-router-dom';
import CounterIndex from 'routes/counter';
import MainIndex from 'routes/main';
import { ToastComponent } from 'components/toast';

function App() {
  const routes = [
    { path: '/', element: <MainIndex /> },
    { path: '/counter', element: <CounterIndex /> }
  ]

  return (
    <BrowserRouter>
      <Routes>
        {routes.map((e) => (<Route key={`route - ${e.path}`} path={e.path} element={e.element} />))}
      </Routes>
      <ToastComponent />
    </BrowserRouter>
  );
}

export default App;

 

BroswerRouter내부에 Toast를 넣은 이유는

 

나중에(?) Toast에 링크를 만들고 navigate기능을 넣을 때 사용하면 좋을것 같아서 넣어봤습니다

 

src/routes/main/index.jsx

import React from 'react'
import { useNavigate } from 'react-router-dom'
import styles from './index.module.css'

function MainIndex() {
    const navigate = useNavigate()

    const pages = [
        { title: 'counter', link: '/counter' }
    ]

    return (
        <main className={styles.main}>
            {pages.map((e, _) => {
                return (
                    <div key={`div-${e.title}`} onClick={() => navigate(e.link)}>
                        {e.title}
                    </div>
                )
            })}
        </main>
    )
}

export default MainIndex

src/routes/main/index.module.css

.main {
    height: 100vh;
    width: 100vw;
    display: flex;
    justify-content: center;
    align-items: center;
}

.main>div{
    cursor: pointer;
}

.main>div:hover{
    color: rgba(0, 0, 0, 0.7);
}

나중에 예제 여러개 만들어서 사용하기 좋게 만들어봤습니다

(사실 중앙에 고작 하나 보여주는데 이렇게까지..)

 

src/routes/counter/index.jsx

import Dialog from 'components/dialog'
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { decrement, increment, setValue } from 'slices/counter'
import { open } from 'slices/dialog'

function CounterIndex() {
    const count = useSelector((state) => state.counter.value)
    const showDialog = useSelector((state) => state.dialog.value)
    const dispatch = useDispatch()

    return (
        <div style={style}>
            <div>
                <p>{count}</p>
                <button
                    aria-label="Increment value"
                    onClick={() => dispatch(increment())}
                >
                    +
                </button>
                <button
                    aria-label="Decrement value"
                    onClick={() => dispatch(decrement())}
                >
                    -
                </button>
                <button
                    aria-label="Set value"
                    onClick={() => dispatch(open())}
                >
                    값 설정
                </button>
                {showDialog ? <Dialog setValue={(value) => dispatch(setValue(value))} /> : <></>}
            </div>
        </div>
    )
}

const style = { height: '100vh', width: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center', textAlign: 'center' }

export default CounterIndex

useSelector를 이용해서 원하는 state를 가져오고

 

dispatch를 통해서 원하는 action을 호출해줍니다

 

(inline style로 만든건 귀찮아서 손이 멋대로...)

 

src/components/dialog.jsx

import React, { useState } from 'react'
import styles from 'components/dialog.module.css'
import { useDispatch } from 'react-redux'
import { close } from 'slices/dialog'
import { showToast } from './toast'

function Dialog(props) {
    const dispatch = useDispatch()
    const [number, setNumber] = useState()

    const confirm = () => {
        if (number === undefined || number === '') {
            showToast({ message: '값을 입력해주세요', dispatch })
            return
        }

        props.setValue(number)
        dispatch(close())
        showToast({ message: `${number}로 변경되었습니다.`, dispatch })
    }

    return (
        <div className={styles.div_background}>
            <div className={styles.div_dialog}>
                <p>값을 입력해주세요</p>
                <input type='number' onChange={(e) => setNumber(Number(e.target.value))} />
                <div className={styles.div_actions}>
                    <button onClick={() => dispatch(close())}>취소</button>
                    <button onClick={confirm}>설정</button>
                </div>
            </div>
        </div>
    )
}

export default Dialog

src/components/dialog.module.css

.div_background {
    position: fixed;
    top: 0;
    left: 0;
    height: 100vh;
    width: 100vw;
    display: flex;
    justify-content: center;
    align-items: center;
    z-index: 2;
    background-color: rgba(0, 0, 0, 0.7);
}

.div_dialog {
    padding: 20px;
    text-align: center;
    border-radius: 20px;
    background-color: white;
}

.div_dialog>p {
    font-size: 18px;
    margin: 0;
    text-align: center;
}

.div_dialog>input {
    border: 1px solid grey;
    margin: 20px 0 20px 0;
}

.div_actions {
    display: flex;
    flex-direction: row;
    justify-content: space-around;
}

.div_actions>button {
    border: 0;
    background-color: transparent;
}

.div_actions>button:hover {
    color: gray;
}

다이얼로그에서는 값 입력부분은 useState로 처리를 했고

 

이후에 showToast로 값 변경 혹은 문제가 발생하면 알림을 주는 형태로 만들어봤습니다

 

src/components/toast.jsx

import React from 'react'
import { useSelector } from 'react-redux'
import { hide, show } from 'slices/toast'
import styles from './toast.module.css'

function ToastComponent() {
    const { show, message } = useSelector((state) => state.toast)

    return (
        <>
            {show ? <div className={styles.toast}>
                {message}
            </div> : <></>}
        </>
    )
}

const showToast = ({ message, dispatch }) => {
    dispatch(show(message))
    setTimeout(() => {
        dispatch(hide())
    }, 2000);
}

export { ToastComponent, showToast }

src/components/toast.module.css

.toast {
    position: fixed;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    background-color: rgba(0, 0, 0, 0.6);
    color: white;
    border-radius: 12px;
    padding: 10px 20px 10px 20px;
    animation: fade 2.0s ease;
    z-index: 999;
}

@keyframes fade {

    0%,
    100% {
        opacity: 0;
    }

    30%,
    80% {
        opacity: 1;
    }
}

마지막으로 toast는 간단하게 만들어봤는데

 

useEffect로 state변경에 따라서 hide하는 부분을 만들어도 괜찮았던것 같았습니다

 

(전체 소스코드 : https://github.com/devmemory/react_redux_example)

 

 

--- 마지막으로.. 필자의 헛소리

 

 

이런저런 자료들 찾아보면서 생각나는대로 코딩하고 있는데.. 잘 하고 있는건지 잘 모르겠습니다

 

일단 결과물은 나오고 있는데 너무 막 코딩하다보면

 

나중에 웹프론트로 이직할 때 문제가 생기지 않을지 걱정됩니다..

 

그리고 css 화면 배치는 어느정도 익숙해진것 같은데 애니메이션은 아직 한참 멀은거 같습니다..

 

한동안 개인공부 시간이 줄어들어서 진도가 잘 안나가고 있는데 시간이 좀 더 생기면 더 열심히 해야될거 같습니다

반응형

'WEB' 카테고리의 다른 글

React, Node - Graphql example(feat. apollo)  (0) 2022.05.24
React - Without CRA  (0) 2022.05.23
Node - XLSX example (feat. scraping)  (0) 2022.05.11
Node - Nodemailer example  (2) 2022.04.26
Node - Google analytics (feat. Chart.js, Excel)  (0) 2022.04.22
Comments