ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Redux
    카테고리 없음 2023. 4. 26. 00:39

    설치방법 

    yarn add redux react-redux

     

    기본세팅

    1. src 폴더 아래에 redux폴더를 만든다.

    2. redux폴더 아래에 각각 config, modules폴더를 만든다.

    3. config폴더 아래에 configStore.js파일을 만든다.

    import { createStore } from "redux";
    import { combineReducers } from "redux";
    
    /*
    1. createStore()
    리덕스의 가장 핵심이 되는 스토어를 만드는 메소드(함수) 이다. 
    리덕스를 사용할 시 creatorStore를 호출할 일은 한 번밖에 없다.
    */
    
    /*
    2. combineReducers()
    리덕스는 action —> dispatch —> reducer 순으로 동작한다.
    이때 애플리케이션이 복잡해지게 되면 reducer 부분을 여러 개로 나눠야 하는 경우가 발생한다.
    combineReducers은 여러 개의 독립적인 reducer의 반환 값을 하나의 상태 객체로 만들어준다.
    */
    
    const rootReducer = combineReducers({}); 
    const store = createStore(rootReducer); 
    
    export default store;

    4. index.js에서 설정해준다.

    // 원래부터 있던 코드
    import React from "react";
    import ReactDOM from "react-dom/client";
    import App from "./App";
    import reportWebVitals from "./reportWebVitals";
    
    // 우리가 추가할 코드
    import store from "./redux/config/configStore";
    import { Provider } from "react-redux";
    
    const root = ReactDOM.createRoot(document.getElementById("root"));
    root.render(
    
    //App을 Provider로 감싸주고, configStore에서 export default 한 store를 넣어줍니다.
      <Provider store={store}> 
        <App />
      </Provider>
    );
    
    // If you want to start measuring performance in your app, pass a function
    // to log results (for example: reportWebVitals(console.log))
    // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
    reportWebVitals();

    5. 모듈에 들어갈 reducer함수를 만들어준다.  

    // src/modules/counter.js
    
    // 초기 상태값
    const initialState = {
      data:{
      
      }
    };
    
    // 리듀서
    const todo = (state = initialState, action) => {
      switch (action.type) {
        default:
          return state;
      }
    };
    
    // 모듈파일에서는 리듀서를 export default 한다.
    export default todo;

     


    리듀서 함수 작성하기 

    1. switch문에 case작성하기(type에 따라 리턴문을 어떻게 줄건지 결정한다.)

    import { v4 as uuidv4 } from 'uuid';
    export const DATA_SAVE = 'todoList/data_save'
    
    const initialState = {
      // const [data, setData] = useState([
      //   { id: 1, title: '리엑트 1주차강의듣기', content: '1주차 강의 1회독하기', done: true },
      //   { id: 2, title: '리엑트 2주차강의듣기', content: '2주차 강의 1회독하기', done: false },
      // ])
      data:
        [
          { id: 1, title: '리엑트 1주차강의듣기', content: '1주차 강의 1회독하기', done: true }
        ]
    }
    
    export default todo

    initialState에 기본값을 넣어주는데 "" 로 빈문자열을 넣어줄 수 있지만 초기값을 넣어줌으로써  위에 주석처럼 원래는 data,setData로 useState를 이용해서 데이터 값을 저장하고 관리했지만 reducer을 이용해서 관리 할 수 있다.

    const todo = (state = initialState, action) => {
      switch (action.type) {
    
        case DATA_SAVE:
          return {
          //기존에 들어온 내용을 ...으로 복사 해서 배치한다.
            ...state,
            data: [
            //inputData배열에 새로운 객체를 추가한다.
              ...state.data,
              {
                id: uuidv4(),
                title: action.payload.title,
                content: action.payload.content,
                done: false
              }]
          }
    
        default:
          return state
    
      }
    }

    id값과 done값은 reducer자체에서 생성하고 title, content값만 유저가 입력한 값을 가지고 action에 payload를 통해 title,content로 받아올 수 있다.

     

    2. dispatch를 사용할 컴포넌트에서 임포트 해서 변수에 넣어준다.

      const dispatch = useDispatch();

    dispatch가 액션을 스토어에 던진다!!!

    action 객체에는 type, payload가 있다.

    type에 따라 스위치문이 변경된다.

      const onCreate = (title, content) => {
        dispatch({
          type: DATA_SAVE,
          payload: {
            title,
            content
          }
        })
      }
      
    //    const onCreate = (title, content) => {
    //      const newList = {
    //        title,
    //        content,
    //        done,
    //        id: data.length + 1
    //      }
    //    setData([...data, newList])
    //  }

    아래 코드와 동일한 코드이지만 아래코드는 useState를 이용해서 data에 리스트를 저장해서 사용하는 방식이였다면 

    dispatch를 이용해서 reducer에 타입에 맞게 payload에 추가로 보내줄 데이터를 담아서 보내준다.

     


    redux를 이용해서 데이터 가져오기

    1. useSelector를 import한다.

    import { useSelector } from "react-redux";
    
    const todoList = useSelector((state) => {
        return state.todo.data
      })
      console.log('todoList:', todoList)
      //todoList: (4) [{…}, {…}, {…}, {…}]

    todoList라는 변수에 데이터를 담아온다. console에 찍어봤을때 정상적으로 입력된 데이터를 전부 가져오는걸 볼 수 있다.

     

    가져온 데이터를 가지고 map메소드를 돌려서 리스트를 ui에 보여질 수 있도록 할 수 있다.

     

     


    redux를 이용해서 데이터 삭제하기

    삭제를 원하는 아이템에 id값을 reducer에 보내야되기 때문에 useDispatch를 import해준다.

    import { useDispatch, useSelector } from "react-redux";
    
    const dispatch = useDispatch()
          import { useDispatch, useSelector } from "react-redux";
    import { DATA_DELETE, DONE_CHANGE } from "redux/modules/todo";
    
    
    const TodoWorking = () => {
      const dispatch = useDispatch()
    
      const todoList = useSelector((state) => {
        return state.todo.data
      })
      
      const onDeleteHandle = (id) => {
        dispatch({
          type: DATA_DELETE,
          payload: id
        })
      }
          
          <div className="TodoWorking">
            <h1>Working.. </h1>
            <div className="list">
              {
                todoList.filter((item) => {
                  return item.done !== true
                }).map((item) => {
                  return (
                    <div className="listBox" key={item.id}>
                      <h2>{item.title}</h2>
                      <p>{item.content}</p>
                      <div className="btn">
                        <button onClick={() => onDeleteHandle(item.id)}>삭제하기</button>
                        <button onClick={() => onDoneChangeHandle(item.id)}>완료하기</button>
                      </div>
                    </div>
                  )
                })
              }
            </div>

    case문에 삭제할 수 있도록 state.data에서 filter매서드를 이용해서 payload로 들어온 id값과 동일하지 않은걸로 다시 배열을 만들어서 다시 리스트에 보여지게 한다. 

        case DATA_DELETE:
          return {
            data: state.data.filter((item) => item.id !== action.payload)
          }

     

    redux이용해서 수정하기

    삭제하기랑 동일하게 id값을 받아서 일치하는 type으로 id값을 payload로 담아서 보낸다.

      const onDoneChangeHandle = (id) => {
        dispatch({
          type: DONE_CHANGE,
          payload: id
        })
      }

    working이랑 done이 동일한 작업을 하지만 done값이 true, false로 나뉘기 때문에 동일한 함수로 id값을 보내지만 

    리듀서에서는 삼항연산자를 이용해서 들어오는 값의 done값을 파악하고 각각 반대로 변경해주면 토글방식으로 함수 한개에 끝낼 수 있다.

        case DONE_CHANGE:
          return {
            data: state.data.map((item) => {
              if (item.id === action.payload) {
                return item.done === false ? { ...item, done: true } : { ...item, done: false }
              }
              return item
            })
          }

     

    여기서 끝내도 정상적으로 코딩이 되었고 잘 돌아가긴 하나 만약 엄청 많은 컴포넌트에서 사용중이라면 하나하나 변경하기 어렵다.

    하나하나 바꿔주는 하드코딩방식은 지양해야한다. dispatch도 사람이 적는거다 보니 실수가 날 수 있어서 변수로 지정해놓고 쓰는게 코드를 단축화 할 수 있다. 

     

    export const data_save = (payload) => {
      return {
        type: DATA_SAVE,
        payload
      }
    }
    export const data_delete = (payload) => {
      return {
        type: DATA_DELETE,
        payload
      }
    }
    export const done_change = (payload) => {
      return {
        type: DONE_CHANGE,
        payload
      }
    }

    가져올 데이터를 함수에 담아서 가져올 수 있다. 

      //App.js
      const onCreate = (title, content) => {
        dispatch(data_save{
          title,
          content
        })
      }
    //todoList
    const onDeleteHandle = (id) => {
        dispatch(data_delete(id))
      }
    
      const onDoneChangeHandle = (id) => {
       dispatch(done_change(id))
      }

    더 간략하게 코드를 짤 수 있고 실수 없이 보낼 수 있다. 

     

    어느정도 이해했다 생각했는데 다시 혼자서 해보려니까 너무 어렵고 뭐가뭔지 전혀 기억하지 못했다. 

    위에 이미지를 보고 왜 저렇게 가는지만 먼저 이해하는게 먼저일 것 같다는 생각이 들었다. 

    다음번엔 이전에 프로젝트했던 항해캡슐을 리덕스를 이용해서 하는걸로 연습해봐야 할 것 같다.

     

Designed by Tistory.