가수면

[Redux] Redux 본문

React/라이브러리

[Redux] Redux

니비앙 2022. 12. 2. 09:57

리덕스 설치

yarn add redux react-redux

 

데브툴 설치

npm install redux-devtools-extension

// 저장소에 추가해야할 것

import { composeWithDevTools } from "redux-devtools-extension";

const store = createStore(rootReducer, composeWithDevTools());

 

리덕스 기본

폴더 구성

초기값, reducer 설정 (modules 폴더)

// src/modules/counter.js

// 초기 상태값 (useState와 비슷한 역할)
// 객체 형태가 아니어도 됨.
const initialState = {
  number: 0,
};

// 리듀서 (setState와 비슷한 역할)
const counter = (state = initialState, action) => {
  switch (action.type) {
    default:
      return state;
  }
};

// 모듈파일에서는 리듀서를 export default 한다.
export default counter;

counter모듈 store에 연결

configStore.js

import { createStore } from "redux";
import counter from "../modules/counter";  //연결
import { combineReducers } from "redux";

/*
1. createStore()
리덕스의 가장 핵심이 되는 스토어를 만드는 메소드(함수) 입니다. 
리덕스는 단일 스토어로 모든 상태 트리를 관리한다고 설명해 드렸죠? 
리덕스를 사용할 시 creatorStore를 호출할 일은 한 번밖에 없을 거예요.
*/

/*
2. combineReducers()
리덕스는 action —> dispatch —> reducer 순으로 동작한다고 말씀드렸죠? 
이때 애플리케이션이 복잡해지게 되면 reducer 부분을 여러 개로 나눠야 하는 경우가 발생합니다. 
combineReducers은 여러 개의 독립적인 reducer의 반환 값을 하나의 상태 객체로 만들어줍니다.
*/


// redux가 여러 개일 경우
const rootReducer = combineReducers({
  counter: counter
  todos: totos
});
const store = createStore(rootReducer);

export default store;


// redux가 한 개일 경우 (Import한 변수값과 일치시키기)
const store = createStore(counter);

export default store;

index.js

// 원래부터 있던 코드
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import reportWebVitals from "./reportWebVitals";

// 우리가 추가할 코드
import store from "./redux/conifig/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();

 

확인 및 적용

App.js

// src/App.js

import React from "react";
import { useSelector } from "react-redux"; // import 해주세요.

const App = () => {
  //useSelector (훅) => 프로젝트에서 존재하는 모든 redux module의 state를 가져와라 (컴포넌트에서 store를 조회할 수 있게 해줌)
  const counterStore = useSelector((state) => state)
  const number = useSelector((state) => state.counter.number)
  console.log(counterStore);
  console.log(number);
  return <div></div>;
}

export default App;

 

Counter 기능 구현

작동 원리

view에서 액션 -> dispatch가 액션을 store로 운반 -> 리듀서가 액션에 상응하는 state를 변경 -> view 변경

 

(1) dispatch에서 액션 객체를 받아서 리듀서로 전달 (dispatch는 반드시 객체 형식만 받음. type의 value는 대문자로 작성)

useDispatch추가

// src/App.js

import React from "react";
import { useSelector, useDispatch } from "react-redux"; // import 해주세요.

const App = () => {
  const dispatch = useDispatch()
  return <div>
    {/* dispatch의 변수는 객체 형식을 갖춰야 함 */}
    <button onClick={() => dispatch({ type: 'PLUSE_ONE' })}>+1</button>
  </div>;
}

export default App;
// src/modules/counter.js

const initialState = {
  number: 0,
};

const counter = (state = initialState, action) => {
  console.log(action);  //{type: 'PLUSE_ONE'}
  switch (action.type) {
    case "PLUS_ONE":
      return { number: state.number + 1 };
    default:
      return state;
  }
};

export default counter;

App에 추가

const App = () => {
  const dispatch = useDispatch()
  const number = useSelector((state) => state.counter.number)
  console.log(number)
  return <div>
    {number}
    <button onClick={() => dispatch({ type: 'PLUS_ONE' })}>+1</button>
  </div>;
}

export default App;

 

action creator

액션객체를 효율적으로 관리하기 위한 방법

아래와 같이 추가해줄 수 있음

// src/modules/counter.js

// 추가된 코드 👇 - 액션 value를 상수들로 만들어 줍니다. 보통 이렇게 한곳에 모여있습니다.
const PLUS_ONE = "PLUS_ONE";
const MINUS_ONE = "MINUS_ONE";


// 추가된 코드 👇 - Action Creator를 만들어 줍니다. 
//const로 선언된 값은 action에 dispatch가 붙은 값임
export const plusOne = () => {
  return {
    type: PLUS_ONE,
  };
};

export const minusOne = () => {
  return {
    type: MINUS_ONE,
  };
};


// 초기 상태값
const initialState = {
  number: 0,
};

// 리듀서
const counter = (state = initialState, action) => {
  switch (action.type) {
    case PLUS_ONE: // case에서도 문자열이 아닌, 위에서 선언한 상수를 넣어줍니다. 
      return {
        number: state.number + 1,
      };
    case MINUS_ONE: // case에서도 문자열이 아닌, 위에서 선언한 상수를 넣어줍니다. 
      return {
        number: state.number - 1,
      };
    default:
      return state;
  }
};


export default counter;
// src/App.js

import React from "react";
import { useSelector, useDispatch } from "react-redux"; // import 해주세요.
import { minusOne, plusOne } from "./redux/modules/counter";

const App = () => {
  const dispatch = useDispatch()
  const number = useSelector((state) => state.counter.number)
  return <div>
    {number}
    {/* dispatch의 변수는 객체 형식을 갖춰야 함 */}
    <button onClick={() => dispatch(plusOne())}>+1</button>
    <button onClick={() => dispatch(minusOne())}>-1</button>
  </div>;
}

export default App;

 

payload

사용자가 입력한 값을 action 객체에 같이 담아서 보내주는 것

 

문제) 사용자가 입력한 숫자 더하고 빼주기

(1) App.js 수정

// src/App.js

import { useState } from "react";

const App = () => {
  const [number, setNumber] = useState(0)
  const onChangeHandler = event =>
    setNumber(+event.target.value)

  return <div>
    <input type='number' onChange={onChangeHandler} />
    <button>더하기</button>
    <button>빼기</button>

  </div>;
}

export default App;

 

(2) counter.js 수정 (주석처럼 적어야할 것들을 미리 정리해서 리스트업 해놓으면 작성하기 좋음)

// src/redux/modules/counter.js

// Action Value
const ADD_NUMER = 'ADD_NUMER'
const SUBTRACT_NUMBER = 'SUBTRACT_NUMBER'

// Action Creator
export const addNumber = payload => {
  return {
    type: ADD_NUMER,
    payload     //ES6 문법으로 인해 key와 값이 같으면 값 생략 가능. 원래 형태 => payload: payload
  }
}
export const subtractNumber = payload => {
  return {
    type: SUBTRACT_NUMBER,
    payload     //ES6 문법으로 인해 key와 값이 같으면 값 생략 가능. 원래 형태 => payload: payload
  }
}
// Initial State
const initialState = {
  number: 0
}

// Reducer
const counter = (state = initialState, action) => {
  switch (action.type) {
    case ADD_NUMER:
      return {
        number: state.number + action.payload
      }
    case SUBTRACT_NUMBER:
      return {
        number: state.number - action.payload
      }
    default:
      return state
  }
}

// export default reducer
export default counter;

 

(3)  App.js 다시 수정 (useSelector)

// src/App.js

import { useState } from "react";
import { useSelector } from "react-redux";


const App = () => {
  const [number, setNumber] = useState(0)
  const globalNumber = useSelector(state => state.counter.number)
  const onChangeHandler = event =>
    setNumber(+event.target.value)
  
  return <div>
    <input type='number' onChange={onChangeHandler} />
    <button>더하기</button>
    <button>빼기</button>

  </div>;
}

export default App;

 

(4) dispatch 사용하고 action추가

// src/App.js

import { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { addNumber, subtractNumber } from "./redux/modules/counter";


const App = () => {
  const [number, setNumber] = useState(0)
  const globalNumber = useSelector(state => state.counter.number)
  const dispatch = useDispatch()
  const onChangeHandler = event =>
    setNumber(+event.target.value)

  const onSubtractNumberHandler = () => {
    dispatch(subtractNumber(number))
  }

  return <div>
    {globalNumber}
    <input type='number' onChange={onChangeHandler} />
    <button onClick={() => dispatch(addNumber(number))}>더하기</button>
    <button onClick={onSubtractNumberHandler}>빼기</button>

  </div>;
}

export default App;

 


정리 (Ducks pattern 기반)

1. 프로젝트에서 redux 폴더 생성 후 그 안에 리덕스 설정(config)폴더와 state 그룹(modules)폴더를 생성.

2. 저장소(configStore.js)를 만들고 양식 복붙

import { createStore } from "redux";
import counter from "../modules/counter"; //모듈
import { combineReducers } from "redux";

const rootReducer = combineReducers({
  counter: counter //모듈
});
const store = createStore(rootReducer);

export default store;

3. index.js에 리덕스 요소들을 import하고 <App>를 Provider로 감싸준다..

// 추가할 코드
import store from "./redux/conifig/configStore";
import { Provider } from "react-redux";
.
.
.
  <Provider store={store}>
    <App />
  </Provider>
);

4.  modules 폴더에 js파일 생성 / 목표로 한 action에 dispatch와 action creator를 붙여준 뒤  useDispatch로 리듀서에 연결

// Action Value

// Action Creator

// Initial State

// Reducer

// export default reducer

 

5. 초기값 설정 -> reducer 함수 작성

 

6. 사용할 컴포넌트에 useSelector를 import한다.

 

'React > 라이브러리' 카테고리의 다른 글

[Styled Components] Styled Components Global Style  (0) 2022.12.17
[Redux] Redux ToolKit thunk  (0) 2022.12.10
[Redux] Redux ToolKit  (0) 2022.12.09
Router  (0) 2022.12.03
[Styled Components] Styled Components  (0) 2022.12.02
Comments