Web/React

React Redux

또롱또 2022. 5. 27. 18:17
728x90

Redux관련 포스팅은 많지만, 너무 어렵다..

이 포스팅 하나로 최대한 간단하지만 최대한 많이 알리고싶은게 목적이다.

 

 

1. Redux는 왜 쓰는가?

- useState가 불편해서....


2. Redux vs Redux Toolkit

- 항해 7기는 React Redux만 배운다

- Reduxjs Toolkit 은 React Redux를 보다 쉽고 짧고 편하게 쓰기 위해 나왔다. 실제로 코드량이 반토막난다.

- 벨로퍼트님도 Redux Toolkit을 꼭 사용하자고 하신다.

- 하지만 항해에서 안배우는거라 툴킷 잘못쓰면 다른 팀원들이 못알아보고 Redux를 사용 못하게 되는 불상사가 일어날지도 

아래 마치면서 출처: https://ridicorp.com/story/how-to-use-redux-in-ridi/


3. Redux vs Context API

전역으로 데이터를 관리하는애는 Redux말고 Context API도 있는데 왜 Redux를 배울까?

- Redux에는 미들웨어(middleware)가 있다.

- 우리가 변경하고싶은 값을 바로 변경시키지 않고, 그 값을 사용해 다른 걸 할수 있다.

- 예를들어 Greeting 이라는 함수에서 hi 를 return해 주는데,

이걸 바로 return하지않고 hi 한다음에 nice meet you의 행동 까지 추가할수 있단 말.

- 비동기 작업처리가 가능하다 라고 기억하면 된다.

- 비슷한 Hooks 중에 useReducer가 있는데 몰라도된다 일단은.


4. 그러면 Redux를 언제 쓰는게 좋을까?

- 프로젝트 규모가 크거나, 비동기 작업이 많을때 쓰면 좋다. 

- 단순히 전역변수들 상태관리할때는 뭐든 상관없을거 같다.


5. 기본 구조 (이론) - React Redux

- Redux나 툴킷이나 일단 기본 구조 이론편은 똑같다.

- Store상태가 관리되는 공간이다. 쉽게말해서 변수저장은 여기다 하면된다

- 프로젝트내에 단 한개만 존재해야한다.

 

- Reducer는 store에 저장되어있는 상태를 직접적으로 수정해주는 애다.

- 쉽게말해서, CRUD가 일어나면, 얘를 통해서 store에 저장된 값을 업데이트 해야한다.

- CRUD가 일어나면 값을 수정해야하기때문에 취해진 행동(action)과, 수정할 값(state)을 파라미터로 받아와야한다(must)

function counterReducer(state, action) {
  switch (action.type) {
    case 'INCREASE':
      return state + 1;
    case 'DECREASE':
      return state - 1;
    default:
      return state;
  }
}

 

- Action은 상태에 어떤 변화가 필요하게 될때 일어난다.

- 이런 액션들은 객체로 표현되어있고, type이라는 필드를 무조건 가지고 있어야한다 

{
  type: "ADD_TODO",
  data: {
    id: 0,
    text: "리덕스 언제끝내냐.."
  }
}

- 이런 액션들은 보통 함수로 관리한다. 1액션 1함수 기억하자.

export function addTodo(data) {
  return {
    type: "ADD_TODO",
    data: {
    id: 0,
    text: "리덕스 언제끝내냐.."
  }
  };
}

 

- View는 그냥 유저들이 보는 모니터 즉, 브라우저다.

 

쉽게말해서 값이 변경되면 - action을 dispatch했다 라고 외우면된다.

변경된 값은 reducer가 처리해서 store한테 전달

store는 변경된 값을 필요한 애들한테 업데이트해준다.

업데이트는 자동으로 이루어진다.

우리가 유튜브 구독하면 유튜버들이 뭐 올리면 자동으로 알림오듯이, 리듀서도 구독한애들은 자동으로 업데이트해준다.

- 여기서 이용하는 Hook이 useSelector이다. (아래서 설명예정)


5. 기본 구조 (실전) - React Redux

벨로퍼트님이 툴킷 하라해도 이것도 해야한다.

React Redux를 사용해서 to do 를 추가하는걸 만들어 보려고 한다.(리덕스 CRUD 기초 틀 복붙할수있는 사이트 - https://github.com/erikras/ducks-modular-redux)

폴더구조에 정답은 없겠지만, src에 난사하는건 보고있을수 없으니 꼭 redux 폴더를 만들자.

components - 일반 컴포넌트들 보관하는곳

containers - 리덕스 store의 상태를 조회하거나 action을 dispatch 할수있는 컴포넌트

pages - routing 해줄 페이지들

modules - reducer, action함수들이 있다. index.js는 여러개의 reducer를 하나로 합쳐주는 combineReducer가 있다.

configStore.js - 나는 store를 여기서 생성해준다. 하지만 modules의 index.js나 밖의 index.js에서 해줘도 될거같다.

 

설치

npm install react-redux redux

 

최상단 index.js 는 이 구조 외엔 없다.

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
// Provider를 사용하기 위한 import
import { Provider } from 'react-redux';
// store 를 곧 생성할 예정
import store from './redux/configStore'

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
	// 내가 사용하고싶은 전역상태관리의 범위만큼 Provider로 감싸고
    // 전역상태의 데이터가 들어있는 store를 이렇게 넘겨준다
    <Provider store={store}>
        <App />
    </Provider>
);

 

이제 store를 생성해준다

./redux/configStore.js

import { createStore } from "redux";
// 모듈안에서 rootReducer 가져와서 store를 생성 예정
import rootReducer from './modules';

// 생성!
const store = createStore(rootReducer);
export default store;

 

이 store 생성은 최상단 index.js에서 해줘도 된다. 실제로 벨로퍼트님은 최상단 index.js에서 하셨다.

아래 사진은 createStore를 쓰면 툴킷에 있는 configureStore을 쓰라고 권한다.

 

다음은 rootReducer를 생성한다.

combineReducers - reducer들을 합칠수 있다.reducer가 한개면 그 reducer를 이용해서 바로 store를 만들어도 된다../redux/modules/index.js

import { combineReducers } from 'redux';
import counter from './counter';


// counter, todo reducer들을 이렇게 합친다.
const rootReducer = combineReducers({
  counter,
  // 여기에는 새롭게 추가하고싶은 reducer를 가져온다
});

export default rootReducer;

 

 

counter reducer를 만들어 줄 파일

./redux/modules/counter.js

/* 액션 타입 만들기 */
// 다른모듈과 이름 중복방지를 위해 이렇게 만든다
const SET_DIFF = 'counter/SET_DIFF';
const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASE';

/* 액션 생성함수 만들기 */
// 내가 밖에서 사용할 액션함수를 만든다. export빼먹지말기
export const setDiff = diff => ({ type: SET_DIFF, diff });
export const increase = () => ({ type: INCREASE });
export const decrease = () => ({ type: DECREASE });

/* 초기 상태 선언 */
// 제일 처음 store가 가질 초기값이다
const initialState = {
  number: 0, // 화면에 보여줄 초기값
  diff: 1 // input에 띄워줄 초기값
};

/* 리듀서 선언 */
// 리듀서는 합치는애가 받아야하니 export를 잊지말자
// 파라미터에는 초기값이랑 어떤액션이 취해져야하는지가 들어간다.
export default function counter(state = initialState, action) {

  // action은 기본적으로 type이있다. 그걸로 switch case
  switch (action.type) {
 
  	// input에 띄워줄 값
    case SET_DIFF:
    	// state안의 객체에있는 diff key를 가진놈한테 데이터를 덮어씌운다
      return {...state, diff: action.diff}; 
    case INCREASE:
      return { ...state, number: state.number + state.diff};
    case DECREASE:
      return {...state, number: state.number - state.diff};
    default:
      return state;
  }
}

 

 

CounterContainer 는 리덕스 스토어의 상태를 조회하거나, 액션을 디스패치 할 수 있는 컴포넌트를 의미.

./containers/CounterContainer.js

import React from 'react';
// 값을 수정하기 위해 import
import { useSelector, useDispatch } from 'react-redux';
import Counter from '../components/counter';

// counter reducer에서 가져온 액션함수
import { increase, decrease, setDiff } from '../redux/modules/counter';

function CounterContainer() {

  // useSelector는 리덕스 스토어의 상태를 조회하는 Hook
  // 비구조화로 객체의 경우 좌항과 우항이 매칭된다.
  const { number, diff } = useSelector(state => ({
    number: state.counter.number,
    diff: state.counter.diff
  }));

  //-> 그냥 CRUD할땐 무조건 dispatch를 써야한다, 그래서 선언
  const dispatch = useDispatch();
  
  // 함수를 만드는데, 그 함수를 dispatch를 이용해서 reducer에서 만든애들을 불러와야한다.
  const onIncrease = () => dispatch(increase());
  const onDecrease = () => dispatch(decrease());
  const onSetDiff = diff => dispatch(setDiff(diff));

  return (
    <Counter
      // 상태와
      number={number}
      diff={diff}
      // 액션을 디스패치 하는 함수들을 props로 넣어줍니다.
      onIncrease={onIncrease}
      onDecrease={onDecrease}
      onSetDiff={onSetDiff}
    />
  );
}

export default CounterContainer;

 

 

 

./components/counter.js

import React from 'react';

// props로 넘어온애들을 일단 다 써줌
function Counter({ number, diff, onIncrease, onDecrease, onSetDiff }) {

	// 버튼이벤트
  const onChange = e => {
    // e.target.value 의 타입은 문자열이기 때문에 숫자로 변환해주어야 합니다.
    // parseInt(값,진법)
    onSetDiff(parseInt(e.target.value, 10));
  };
  
  return (
    <div>
     // 보여줄 숫자
      <h1>{number}</h1>
      <div>
        // input창에 띄워줄 숫자, diff의 값을 가져와서 보여준다. min은 없어도 되지만,
        // 혹시모를 상황에 대비해서 넣으신거 같다(최소값)
        <input type="number" value={diff} min="1" onChange={onChange} />
        <button onClick={onIncrease}>+</button>
        <button onClick={onDecrease}>-</button>
      </div>
    </div>
  );
}

export default Counter;

 

App.js

import React from "react";
import CounterContainer from './containers/CounterContainer';

function App() {
  return (
    <>
    <div>
      <CounterContainer />
    </div>
    </>
  );
}

export default App;

 

CounterContainer를 만들지 않고 그냥 Counter.js 컴포넌트 안에서 CRUD과정이 이루어 져도 되지만,컴포넌트를 두개로 나눠서 관리하는 이유는 재사용성 때문이라고 한다.

 

728x90