본문 바로가기

Redux

[Redux강의노트] Redux

반응형

Redux

- react에서 가장 사용률이 높은 상태 관리 라이브러리이다.

 

context API+useReducer 사용과 Redux의 차이점

1. Redux에는 미들웨어라는 개념이 있다. 미들웨어를 사용하면 비동기 작업을 더욱 체계적으로 관리 가능하다.

2. 리덕스 사용하면 유용한 함수와 hook 을 사용할 수 있다. (connect, useSelector, useDispatch, useStore)

3. 기본적인 최적화가 이미되어있다.

4. 하나의 커다란 상태

5. DevTools

6. 이미 사용중인 프로젝트가 많다 

 

언제 써야할까?

프로젝트 규모가 큰경우 - Y : Redux / N : Context API

비동기 작업을 자주 하는 겨우 - Y : Redux / N : Context API

리덕스가 편한 경우 - Y : Redux / N : Context API or MobX

 

리덕스에서 사용되는 키워드 숙지하기

액션 action

- 상태 업데이트 시 어떻게 업데이트해야하는지 정보를 지니고 있는 객체

- type 값은 필수

액션 생성함수 action creator 

- 액션객체를 만들어주는 함수, parameter를 받아와서 액션을 만들어주는 함수

리듀서 reducer

- 상태를 바꿔주는 함수, 변화를 일으키는 함수 (불변성유지(concat, filter))

- 현재상태 state와 action객체를 파라미터로 받아와서 새로운 상태를 만들어줌

- 리듀서에서 default에서는 error 반환보다 기존의 state를 반환하게 작성

스토어 store

- 하나의 애플리케이션에서는 하나의 스토어만 만들어진다

- 스토어안에 현재앱의 상태와 리듀서,

- 추가적으로 내장함수(dispatch(액션 전달, 발생))

+ 내장함수 - 구독 sunscribe(파라미터로 특정함수를 넣어주면 액션이 dispatch 될때마다 설정한 함수 호출)

 

리덕스에서 지켜야  할 3가지 규칙

1. 하나의 애플리케이션에서는 하나의 스토어만 있다.

2. 상태는 읽기전용이다. = 불변성을 지켜야한다. 

3. 변화를 일으키는 함수 리듀서는 순수한 함수여야 한다.

(순수한 함수 ? - 리듀서 함수는 이전 상태와, 액션 객체를 파라미터로 받는다. 이전이 상태는 절대 변경하지 않고, 변화를 일으킨 새로운 상태 객체를 만들어 반환한다. 똑같은 파라미터로 호출된 리듀서 함수는 언제나 똑같은 결과값을 반환해야한다.)

(=> new Date(), Math.random(), axios.get() 불가 - 값이 변경되기 때문에)

(↑사용해야한다면 해당 컴포넌트에서 사용하거나 미들웨어를 사용함)

 

 

1. react component 없이 redux 사용해보기

 

import { createStore } from "redux";

//react component 없이 reudux만 사용해보기 - 실습
//redux에서 관리할 상태 정의
//상태 초깃값
const initialState = {
  counter: 0,
  text: "",
  list: [],
};

//action type 정의
const INCREASE = "INCREASE";
const DECREASE = "DECREASE";
const CHANGE_TEXT = "CHANGE_TEXT";
const ADD_TO_LIST = "ADD_TO_LIST";

//action 생성함수
const increase = () => ({
  type: INCREASE,
});

const decrease = () => ({
  type: DECREASE,
});

const changeText = (text) => ({
  type: CHANGE_TEXT,
  text,
});

const addToList = (item) => ({
  type: ADD_TO_LIST,
  item,
});

//reducer
function reducer(state = initialState, action) {
  switch (action.type) {
    case INCREASE:
      return {
        ...state,
        counter: state.counter + 1,
      };
    case DECREASE:
      return {
        ...state,
        counter: state.counter - 1,
      };
    case CHANGE_TEXT:
      return {
        ...state,
        text: action.text,
      };
    case ADD_TO_LIST:
      return {
        ...state,
        item: state.list.concat(action.item),
      };
    default:
      return state;
  }
}

//store 만들기
const store = createStore(reducer);
console.log(store.getState()); //현재 store 안에 있는 상태 조회하는 법

//store 구독해보기
const listener = () => {
  const state = store.getState();
  console.log(state);
};

const unsubscribe = store.subscribe(listener);
// + 구독해지하고싶을때, unsubscribe(); ---> 호출하면 됨

//action dispatch하기
store.dispatch(increase());
store.dispatch(decrease());
store.dispatch(changeText("안녕하세요"));
store.dispatch(addToList({ id: 1, text: "와우" }));

//구독 후, action이 dispathch 될때마다 subscribe함수(현재 상태)가 호출됨
 ↓
// {counter: 1, text: "", list: Array(0)}
// counter: 1
// list: []
// text: ""
// __proto__: Object

// {counter: 0, text: "", list: Array(0)}
// counter: 0
// list: []
// text: ""
// __proto__: Object

// {counter: 0, text: "안녕하세요", list: Array(0)}
// counter: 0
// list: []
// text: "안녕하세요"
// __proto__: Object

// {counter: 0, text: "안녕하세요", list: Array(0), item: Array(1)}
// counter: 0
// item: [{…}]
// list: []
// text: "안녕하세요"
// __proto__: Object

 

 

2. react 프로젝트에서 redux 사용하기

- action type, action 생성함수, reducer => 하나의 파일에 몰아서 작성하는 Ducks 패턴 사용해보기

- Ducks패턴사용시, 앞에 접두사를 넣어야함 - 다른 모듈과 이름이 겹치지 않게하기 위해서

 

1. counter, todos 각각 redux 모듈 만들어주기

 

//counter.js

//action type 정의
//Ducks패턴사용시 앞에 접두사를 넣기
const SET_DIFF = "counter/SET_DIFF";
const INCREASE = "counter/INCREASE";
const DECREASE = "counter/DECREASE";

//action 생성함수 - export
export const setDiff = (diff) => ({
  type: SET_DIFF,
  diff,
});

export const increase = () => ({
  type: INCREASE,
});

export const decrease = () => ({
  type: DECREASE,
});

//reducer에서 관리할 초기상태
const initialState = {
  number: 0,
  diff: 1,
};

//reducer - export default
export default function counter(state = initialState, action) {
  switch (action.type) {
    case SET_DIFF:
      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;
  }
}

 

//todos.js
const ADD_TODO = "todos/ADD_TODO";
const TOGGLE_TODO = "todos/TOGGLE_TODO";

let nextId = 1;
export const addTodo = (text) => ({
  type: ADD_TODO,
  todo: {
    id: nextId++,
    text,
  },
});

export const toggleTodo = (id) => ({
  type: TOGGLE_TODO,
  id,
});

const initialState = [
  //   {
  //     id: 1,
  //     text: "예시",
  //     done: false,
  //   },
];

export default function todos(state = initialState, action) {
  switch (action.type) {
    case ADD_TODO:
      return state.concat(action.todo);
    case TOGGLE_TODO:
      return state.map((todo) =>
        todo.id === action.id ? { ...todo, done: !todo.done } : todo
      );
    default:
      return state;
  }
}

 

 

2. reducer 두개를 합쳐주는 rootReducer 만들기

 

//modules/index.js
//reducer 두개 합쳐주기 - rootReducer 만들기

import { combineReducers } from "redux";
import counter from "./counter"; //counter reducer 가져오기
import todos from "./todos"; //todos reducer 가져오기

//rootReducer 만들기
const rootReducer = combineReducers({
  counter,
  todos,
});

export default rootReducer;

 

 

3. react에 redux 적용하기 

npm install react-redux 설치

 

//index.js
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { Provider } from "react-redux";
import { createStore } from "redux";
import rootReducer from "./modules"; //index 생략가능

const store = createStore(rootReducer);
//console.log(store.getState()); 현재상태, 객체 확인

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById("root")
);

 

 

4. 컨테이너 컴포넌트 만들기

 

- 컨테이너 컴포넌트

- 컨테이너 컴포넌트란, 리덕스 스토어의 상태를 조회하거나, 액션을 디스패치 할 수 있는 컴포넌트를 의미

- HTML 태그들을 사용하지 않고 다른 프리젠테이셔널 컴포넌트들을 불러와서 사용

- 상태조회시에는 react-redux에 있는 useSelector 사용

=> 상태관리

 

//container/CounterContainer.js
import React from "react";
import Counter from "../components/Counter";
import { useSelector, useDispatch } from "react-redux";
import { decrease, increase, setDiff } from "../modules/counter";

//useSelector의 state는 StorageEvent.getState()의 객체(현재상태)와 같다.
function CounterContainer() {
  const { number, diff } = useSelector((state) => ({
    number: state.counter.number,
    diff: state.counter.diff,
  }));

  const dispatch = useDispatch();

  const onIncrease = () => dispatch(increase());
  const onDecrease = () => dispatch(decrease());
  const onSetDiff = (diff) => dispatch(setDiff(diff));

  return (
    <Counter
      number={number}
      diff={diff}
      onIncrease={onIncrease}
      onSetDiff={onSetDiff}
      onDecrease={onDecrease}
    />
  );
}

export default CounterContainer;

 

 

5. 프리젠테이셔널 컴포넌트 만들기

 

- 프리젠테이셔널 컴포넌트

- 프리젠테이셔널 컴포넌트란, 리덕스 스토어에 직접적으로 접근하지 않고 필요한 값 또는 함수를 props 로만 받아와서 사용하는 컴포넌트

- 프리젠테이셔널 컴포넌트에선 주로 이렇게 UI를 선언하는 것에 집중하며, 필요한 값들이나 함수는 props 로 받아와서 사용하는 형태로 구현

=> 단순히 UI에 집중

 

//components/Counter.js
import React from "react";

function Counter({ number, diff, onSetDiff, onIncrease, onDecrease }) {
  const onChange = (e) => {
    onSetDiff(parseInt(e.target.value, 10));
  };

  return (
    <div>
      <h1>{number}</h1>
      <div>
        <input type="number" onChange={onChange} value={diff} />
        <button onClick={onIncrease}>+</button>
        <button onClick={onDecrease}>-</button>
      </div>
    </div>
  );
}

export default Counter;

 

 

 

 

** 구조 파악하기 **

 

Velopert