Redux로 초간단 CRUD 만들기

이진경·2023년 12월 24일
0

React

목록 보기
2/4

내 기준 그나마 깔끔하게(?) 쓰는방법을 공유해보고자 한다.

바로 LEGO

redux 관련 라이브러리 설치하기

redux, react-redux, @reduxjs/toolkit 설치하기

왜 이렇게 많이 설치해야 하냐면... 다 필요하기 때문..!!

Redux

  • Redux는 JavaScript 애플리케이션을 위한 상태 관리 라이브러리입니다.
  • Redux는 React뿐만 아니라 Vue, Angular 등 다른 JavaScript 프레임워크/라이브러리와 함께 사용될 수 있습니다.
  • Redux는 애플리케이션의 상태를 저장하는 중앙 저장소(Store)를 제공하며, 상태는 순수 함수인 리듀서(Reducers)를 통해만 변경될 수 있습니다.
  • Redux는 애플리케이션의 상태를 예측 가능하게 만들고, 시간 여행 디버깅 및 핫 리로딩과 같은 강력한 기능을 지원합니다.

React-Redux

  • React-Redux는 React와 Redux를 연결해주는 공식 바인딩 라이브러리입니다.
  • React-Redux는 Provider 컴포넌트와 connect 함수를 제공하여, React 컴포넌트에서 Redux 스토어에 접근하고 상태를 관리할 수 있게 해줍니다.
  • 최근 버전에서는 React의 새로운 훅 API를 활용하는 useSelector와 useDispatch와 같은 훅을 제공합니다.
  • React-Redux는 React 애플리케이션에서 Redux를 더 쉽고 효율적으로 사용할 수 있도록 설계되었습니다.

@reduxjs/toolkit

  • @reduxjs/toolkit는 Redux 개발을 더 간편하게 만들기 위한 공식 도구 모음입니다.
  • 이 툴킷은 설정과 보일러플레이트 코드를 최소화하기 위해 createSlice 및 createAsyncThunk와 같은 API를 제공합니다.
  • Redux 로직을 작성할 때 필요한 많은 고려 사항들을 간소화하고, 더 효율적인 상태 관리를 위한 기능들을 내장하고 있습니다.
  • Redux Thunk 미들웨어를 기본적으로 포함하여 비동기 로직 처리를 간소화합니다.

거두절미하고 설치 ㄱㄱ

$ npm i redux react-redux @reduxjs/toolkit

chrome의 확장 프로그램인 Redux DevTools도 받기

DevTools를 설치하면 chrome의 개발자도구에 Redux탭이 생기면서 state값들이 바뀔때마다 그 정보를 쉽게 볼 수 있어서 아주 편리하다. 🥹

쓸 준비하기

_store/store.js만들기

store.js

import {configureStore} from '@reduxjs/toolkit';
const store = configureStore({
    reducer: {
        
    }
});

export default store;

store는 모든 리듀서들을 합쳐주는 역할을 함.

Provider로 store 연결하기

index.js

import {Provider} from 'react-redux';
import store from './_store/store';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <Provider store={store}>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </Provider>
);

최상위 컴포넌트에다가 연결

slice만들기

CRUD로 대표적인게 댓글기능이니.. reply.slice.js파일로 하겠다.

_store/reply.slice.js

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

const replySlice = createSlice({
    name: 'reply',
    initialState: {
        replyList: []
    },
    reducers: {
        addReply: (state, action) => {
            return {
                ...state,
                replyList: [...state.replyList, action.payload]
            };
        }
    }
});

export const {addReply} = replySlice.actions;
export default replySlice.reducer;

_store/store.js

import {configureStore} from '@reduxjs/toolkit';
import replyReducer from './reply.slice';

const store = configureStore({
    reducer: {
        reply: replyReducer
    }
});

export default store;

스토어에 등록!!

이제 진짜 써보자

사용할 곳에 import하기

src/page/Testpage.js

import React, {useState} from 'react';
import {useDispatch, useSelector} from 'react-redux';
import { addReply } from '../_store/reply.slice';

function TestPage() {
  const [commentValue, setCommentValue] = useState('');
  const [commentList, setCommentList] = useState('');
  
  const handleWriteComment = (e) => {
    console.log(e.target.value);
  };

  return (
    <div>
      <input
        placeholder="댓글 작성..."
        value={commentValue}
        onChange={handleWriteComment}/>
        <button>댓글달기</button>
        <ul>
        </ul>
    </div>
  )
}

export default TestPage;

state값을 바꿀땐 useDispatch를 사용하고, 값을 읽어올때는 useSelector를 사용한다.

날것의 UI는 귀엽다

댓글 달기

src/page/Testpage.js

import React, {useState} from 'react';
import { setReplyList, addReply } from '../_store/reply.slice';
import {useDispatch, useSelector} from 'react-redux';
import './TestPage.scss';

function TestPage() {
  const dispatch = useDispatch();
  const rdxReply = useSelector((state) => state.reply);

  const [commentValue, setCommentValue] = useState('');
  
  const handleWriteComment = (e) => {
    setCommentValue(e.target.value);
  };

  const onclicksubmit = () => {
    if(commentValue === '') {
      console.log('no comment value');
      return;
    }
    
    dispatch(addReply(commentValue));
    setCommentValue('');
  };

  return (
    <div className='testPage-wrapper'>
      <div className=''>
        <input placeholder="댓글 작성..." value={commentValue} onChange={handleWriteComment}/>
        <button onClick={onclicksubmit}>댓글달기</button>
      </div>
      <ul>
        {rdxReply.replyList.map((listItem, idx) => {
          return (
              <li key={idx}>{listItem}</li>
          );
        })}
      </ul>
    </div>
  )
}

export default TestPage;\

dispatch(addReply(commentValue));
dispatch를 사용해서 addReply 액션을 수행한다.

const rdxReply = useSelector((state) => state.reply);
이렇게 사용하면 추후에 reply 내부 state값을 새로 추가할때도 rdxReply.xxx 이런식으로 값을 가져올 수 있음.


아주 저장이 잘됨.

댓글 수정하기, 삭제하기

이제 create, read를 수행하였으니 수정하고 삭제도 해보자.

_store/reply.slice.js

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

const replySlice = createSlice({
    name: 'reply',
    initialState: {
        replyList: []
    },
    reducers: {
        setReplyList: (state, action) => {
            return {
                ...state,
                replyList: action.payload
            };
        },
        addReply: (state, action) => {
            return {
                ...state,
                replyList: [...state.replyList, action.payload]
            };
        },
        setIsEdit: (state, action) => {
            const updatedIdx = action.payload.idx;
            state.replyList[updatedIdx].isEdit = action.payload.isEdit;
        },
        updateReply: (state, action) => {
            const updatedIdx = action.payload.idx;
            state.replyList[updatedIdx].comment = action.payload.comment;
            state.replyList[updatedIdx].isEdit = action.payload.isEdit;
        }
    }
});

export const {setReplyList, addReply, setIsEdit, updateReply} = replySlice.actions;
export default replySlice.reducer;

setReplyList, setIsEdit, updateReply 3개의 액션들을 추가한다.

import React, {useState} from 'react';
import { setReplyList, addReply, setIsEdit, updateReply } from '../_store/reply.slice';
import {useDispatch, useSelector} from 'react-redux';
import './TestPage.scss';

function TestPage() {
  const dispatch = useDispatch();
  const rdxReply = useSelector((state) => state.reply);

  const [commentValue, setCommentValue] = useState('');
  const [editValue, setEditValue] = useState('');

  // 댓글 onchange
  const handleWriteComment = (e) => {
    setCommentValue(e.target.value);
  };

  // 댓글 수정 onchange
  const handleEditInput = (e) => {
    setEditValue(e.target.value);
  };

  // 댓글 달기
  const onclicksubmit = () => {
    if(commentValue === '') {
      console.log('no comment value');
      return;
    }
    
    dispatch(addReply({comment: commentValue, isEdit: false}));
    setCommentValue('');
  };

  // 댓글 수정
  const modifyClick = (idx) => {
    dispatch(setIsEdit({idx: idx, isEdit: true}));
  };

  // 댓글 삭제
  const deleteClick = (deleteIdx) => {
    const newCommentList = rdxReply.replyList.filter((listItem, idx) => idx !== deleteIdx);
    dispatch(setReplyList(newCommentList));
  };

  // 수정 ok
  const editOK = (idx) => {
    if(editValue === '') {
      console.log('editValue empty..');
      return;
    }
    dispatch(updateReply({comment: editValue, idx: idx, idEdit: false}));
    setEditValue('');
  };

  // 수정 취소
  const editCancel = (idx) => {
    setEditValue('');
    dispatch(setIsEdit({idx: idx, isEdit: false}));
  };

  return (
    <div className='testPage-wrapper'>
      <div className='write-wrapper'>
        <input placeholder="댓글 작성..." value={commentValue} onChange={handleWriteComment}/>
        <button onClick={onclicksubmit}>댓글달기</button>
      </div>
      <ul className='ul-wrapper'>
        {rdxReply.replyList.map((listItem, idx) => {
          return (
              <li key={idx}>
                <div>
                  {listItem.isEdit ? (<input placeholder="댓글 작성..." onChange={handleEditInput} value={editValue}></input>) : (<span>{listItem.comment}</span>)}
                </div>

                <div className='list-item'> 
                { listItem.isEdit ? (
                <>
                  <button onClick={() => editOK(idx)}>확인</button>
                  <button onClick={() => editCancel(idx)}>취소</button>
                </>
                ) : (
                <>
                  <button onClick={() => modifyClick(idx)}>수정</button>
                  <button onClick={() => deleteClick(idx)}>삭제</button>
                </>) }
                </div>
              </li>
          );
        })}
      </ul>
    </div>
  )
}

export default TestPage;

댓글 수정 누르면 수정 관련 UI가 보여야하고 수정 ok나 수정 취소를 누르면 다시 원복이 되어야한다.

끄읏👏

profile
기록남기기

0개의 댓글