22-11-22-리액트(8), Redux

YJ·2022년 11월 22일
0
post-thumbnail

Redux

자바스크립트 앱을 위한 예측 가능한 상태 컨테이너
시간여행형 디버거와 결합된 실시간 코드 수정 개발자 경험 제공
Redux를 다른 뷰 라이브러리나 리액트와 함께 사용 가능
상태 관리 라이브러리

바닐라 JS에서의 Redux

리액트에서 클릭 이벤트 발생 -> Hook을 사용 (useState) 리액트에게 값의 변경을 알림

Redux Toolkit

Redux 앱을 만들기에 필수적인 패키지와 함수들을 포함하여 작업을 단순화한다.
리덕스도 useState와 마찬가지변하는 값을 관리해주는 관리 시스템
전역으로 데이터 상태를 관리

  • 리액트는 사용자 인터페이스를 위한 JS 라이브러리
  • 리덕스는 데이터를 엄격하게, 통합하게 관리함으로 예측 가능하게 데이터를 관리

Redux 흐름

  • 1개의 중앙 데이터를 갖는다. (Store) - 데이터는 상태(state)
  • 상태값이 바뀔 때마다 데이터의 state를 수정하는 reducer 함수를 만듦
  • dispatch를 이용하여 reducer에게 state 값 수정하라고 요청함 (dispatch action)
  • reducer이 state 값을 수정하면 subscribe를 사용하여 수정된 부분을 UI에 업로드

  • store에 저장하고 싶은 상태를 저장
    - 변화가 일어날 데이터는 모두 하나의 store에 저장
    • 에러가 발생한다면 store만 관리하면 됨
  • reducer
    - reducer를 통해서만 데이터를 조작해야한다.
    • 전달된 action과 이전 state값으로 어떻게 값을 처리할지 결정
    • 값이 변경되어 reducer가 호출되면 action에 따라 값이 바뀌며 새로운 state 값을 반환
  • render
    - 실제 화면에 뿌려줌
    • const state = store.getState()를 통해 데이터를 받아야 한다.
  • subscribe
    - 새로운 데이터가 생성될 때마다 화면을 갱신
  • action, dispatch
    - dispatch 내용으로 요청하면 reducer에 내용을 전달
    • action 객체type 필드를 반드시 가지고 있어야 한다. (이전 state 값을 참고하여 새로운 state 값 생성)

Redux를 사용하는 이유

  • useContext : 리액트의 prop-drilling를 피하기 위해 사용하지만 프로젝트가 커지면서 Provider를 많이 사용하게 된다면 복잡해진다.

  • Redux는 뷰 / 바닐라 JS에서든 사용이 가능하며 state관리의 엄격함과 1개의 저장소, 업데이트 기능 등을 제공하여 큰 프로젝트 상태 관리에 적합하다.

  • 변경되는 값을 사용하는 컴포넌트만 리렌더링할 수 있도록 최적화해줌

Redux

  1. reducer는 여러개가 가능하다. (리듀서를 합침)
const rootReducer = combineReducers({
	aReducer,
  	bReducer,
  	cReducer,
})
  1. 상태 저장 store는 1개만 사용 (객체 구조로 저장하여 사용)

  2. reducer 함수는 순수 함수로만 만들어야 함 (이전 상태와 액션 파라미터에만 의존)

  • 바닐라 js에서의 리덕스
import {createStore} from 'react';

const plus = document.getElementById("plus");
const minus = document.getElementById("minus");
const number = document.getElementById("number"); // 수량
const quantity = document.getElementById("quantity"); // 페이지 하단에 총 수량
const totalPrice = document.getElementById("total"); // 페이지 하단에 총 가격

const PRICE = 17500;

// 2. state값 수정하는 reducer 함수 (state, action을 전달)
const countReducer = (state = 0, action) => {
	switch (action.type) {
      case 'ADD':
        minus.disabled = false;
        return state + 1;
      case 'SUBSTRACT':
        plus.disabled = false;
        retrun state - 1;
      default:
        return state;
    }
}
// 1. store 생성
const store = createStore(countReducer);

// 3. dispatch를 사용하여 reducer에게 action을 넘기기
const addNumber = () => {
	store.dispatch({type:'ADD'});
}
const substractNumber = () => {
	store.dispatch({type:'SUBSTRACT'});
}
plus.addEventListener('click', addNumber);
minus.addEventListener('click', substractNumber);

// 4. subscribe를 활용하여 state 상태 변화를 감지하여 ui 업데이트하는 함수 생성
const handleWrite = () => {
    number.innerText = store.getState();
    quantity.innerText = store.getState();
    totalPrice.innerText = store.getState() * PRICE;
    console.log(store.getState());
} 
// update UI
store.subscribe(handleWrite);

리액트에서 리덕스 사용

npx create-react-app my-app --template basic-react
npm i redux react-redux
  • components 폴더 (컴포넌트 존재)
  • modules 폴더 (reducer를 만들고 통합된 reducer를 내보냄)
  1. modules 만들기
  • 리듀서1 : 상품 구매 개수 관리

  • 리듀서2 : 재고 없는 경우 나오는 메시지 관리

  • goodsCounter.jsx

// 액션 생성 함수
export const addNumber = () => {
	return {type : 'ADD'}
}
export const substractNumber = () => {
	return {type : 'SUBSTRACT'}
}

// 초기값 설정
const initialState = {
	stock : 100,
  	goods: 1
}

// 상품 구매 개수 관리하는 리듀서
const goodsReducer = (state = initialState, action) => {
	switch(action.type) {
        case "ADD":
            return {
                ...state,
                stock : state.stock - 1,
                goods : state.goods + 1,
            }
        case "SUBSTRACT":
            return {
                ...state,
                stock : state.stock + 1,
                goods : state.goods - 1,
            }
        default:
            return state
        }
    }
}
export default goodsReducer;
  • 그룹 리듀서 modulex/index.js
import {combineReducers} from 'redux';
import goodsReducer from './goodsCounter';
import stockReducer from './stockCounter';

const rootReducer = combineReducers({
	goodsReducer,
  	stockReducer
})
export default rootReducer;
  • src/index.js
import React from 'react'
import {creatRoot} from 'react-dom/client';
import App from './App';
import { createStore } from 'redux';
import rootReducer from './modules';
import { Provider } from 'react-redux';

const store = createStore(rootReducer);
console.log(store.getState());

const container = document.getElementById('root');
const root = createRoot(container);
root.render(
    <Provider store={store}>
        <App tab="home" />
    </Provider>,
);
  • GoodCounter.jsx 컴포넌트
import { useSelector, useDispatch } from 'react-redux'
import { addNumber, substractNumber } from '../modules/goodsCounter'

function GoodCounter() {
	// store 상태 조회 : useSelector
  	const {stock, goods} = useSelector(state => ({
    	stock: state.goodsReducer.stock,
      	goods: state.goodsReducer.goods,
    }))
    console.log(stock, goods);
  
  // store의 dispatch를 함수 내부에서 사용가능하도록 useDispatch
  const dispatch = useDispatch()
  const onAddNumber = () => dispatch(addNumber())
  const onSubstractNumber = () => dispatch(substractNumber());
  
  return (// 작성)
}
export default GoodCounter

리덕스 툴킷

리덕스 공식 팀에서 만든 툴로 리덕스와 관련된 틀이 묶여있다.

npm install @reduxjs/toolkit react-redux
  • 액션 생성 함수 필요 X

  • ...state 없어짐

  • swith / if 문 대신 함수 형태로 변경

  • modules/goodsCounter.jsx

import {createSlice} from '@reduxjs/toolkit';
const initialState = {
	stock: 10,
  	goods: 1
}
export const counterSlice = createSlice({
	name : 'counter',
  	initialState,
  	reducers: {
    	// 함수로 변경됨
      	increment: (state) => {
          // ...state 삭제
        	state.stock -= 1
          	state.goods += 1
        },
      	decrement: (state) => {
        	state.stock += 1
          	state.goods -= 1
        },
    },
})
console.log(counterSlice)

export const {increment, decrement} = counterSlice.actoins;
export default counterSlice.reducer
profile
함께 배워나가고 싶습니다!

0개의 댓글