[React] Redux 전역상태관리

MINEW·2022년 9월 19일
1

1. 단일 스토어

  • Redux는 단일 스토어이다. reducer가 여러개인 것!

2. state 란?

  • react 컴포넌트에서 유동적으로 바뀌는 값. store에서 저장되어 있는 값.
  • initialState가 state. (recoil에서 atom 느낌?)

3. Action 액션 (생성자 함수 return 객체)

1) 2가지 형태로 나뉜다

  • payload 없는 액션 { type: 'TEST' }
  • payload 있는 액션 { type: 'TEST', params: 'hello' }
  • type 만이 필수 프로퍼티이며, type은 문자열이다.

2) 액션이 스토어에 전달되면, 'store에 있는 상태를 변경하는 용도'로 사용된다

  • store의 상태를 변경할 때, 특별한 인자값을 주지 않는 경우에, type만 있는 액션이 전달된다.
  • 액션은 스토어에 보내는 일종의 input.

3) 액션 생성자 (Action Creator)

  • 액션 객체를 만들때는, 액션을 만들어내는 함수를 만들어서 사용한다.
  • 함수를 통해 액션을 생성하면, 액션 객체를 리턴해준다 (즉, return 값이 액션 객체)

4) 액션이 동작하는 방식

  • 액션 생성자를 통해 액션 객체를 만들어 낸다.
  • 액션생성자를 호출하면, 액션 객체를 redux store에 보낸다 -> redux store가 액션 객체를 받으면, store에 있는 상태 값이 변경된다.
  • store에 있는 상태 값이 변경되면 -> 해당 상태값을 구독하고 있는 컴포넌트도 변경된다.

4. Reducers 리듀서 (함수)

1) Action -> useSelector(state) -> Reducer 함수 -> Store -> 컴포넌트
2) Action -> dispatch(액션생성자함수) -> Reducer 함수 -> Store -> 컴포넌트
3) 원본을 훼손하면 X, 전후비교로 업데이트가 된다.

  • { ...obj }, [ ...arr ] 이런식으로 원본복사를 통해 해결해야한다.
  • { ...객체복사, 업데이트 값 }, [ ...배열복사, 업데이트 값 ]

5. store폴더 사용함수

1) createStore(rootReducer)

  • store 폴더의 store.js 파일
  • 스토어를 생성한 후, 리듀서를 등록한다.
  • 현재는 이것대신, Redux Toolkit에서 지원하는 것을 사용하는게 표준.

2) combineReducers({})

  • combineReducer로 여러개의 reducer를 통합한다
  • store/modules 혹은 store/reducers 폴더의 index.js 파일

6. 컴포넌트에서 store 사용하기

업데이트 전 ver

1) store.getState()

  • 현재 store에 있는 상태를 출력한다

2) store.dispatch(액션)

  • store에 등록한 reducer에 액션 객체를 전달한다

3) store.subscribe(이벤트)

  • 작업이 store에 전달될 때마다 호출된다

4) replaceReducer(다음 리듀서)

  • store에서 사용하는 reducer를 바꾼다 (고급 API)

업데이트 후

1) useSelector(state)

  • store.getState() + store.subscribe(이벤트)
  • state를 구독해서, '가져오기 + 리렌더링'을 한 번에 가능하게 한다. (atom 느낌)

2) useDispatch()

  • useDispatch의 return값은 store.dispatch()이다.
  • const dispatch = useDispatch(); 로 할당한 후에
  • dispatch(액션생성자(payload)) -> dispatch(액션객체) === reducer함수 호출 -> store의 state 값을 변경한다

7. 기본 예시 (초기값 객체ver)

1) action 파일

// src/store/actions/actions.js (action 파일)

// 액션 객체 (액션 타입 + 액션 생성자 함수)
// 4번) 
export const INCREMENT = 'INCREMENT'; // 1번) 
export function addAction(call) { // 2번) 
  return  { // 3번) 
    type: INCREMENT, // type
    call // payload
  }
}

export const DECREMENT = 'DECREMENT';
export function subAction(call) {
  return  {
    type: DECREMENT,
    call
  }
}

export const RESET = 'RESET';
export function resetAction(call) {
  return  {
    type: RESET,
    call
  }
}

export const PUSH = 'PUSH';
export function pushAction(call) {
  return  {
    type: PUSH,
    call
  }
}
[ 주석 설명 ]
1. 액션의 type을 정의한다. (일반적으로, 액션의 type은 대문자 + _ 조합을 사용)
2. 액션을 생성하는 함수를 만든다. (액션 생성자)
3. 액션 객체를 return (항상 객체 형태로 return을 작성해야한다)
4. export 해주면, reducer에서 사용가능

2) 개별 reducer 파일

// src/store/reducers/addsub.js (reducer 파일 1)

import { INCREMENT, DECREMENT, RESET } from './actions' // 4번)

// 초기값 설정
const initialState = { // 3번) 
	value: 0
}

// 리듀서 함수
export default function addsubReducer(state = initialState, action) { // 1번) 
  if (action.type === INCREMENT) {
    console.info(action.call); // 6번) 
    return { ...state, value: state.value + 1 } // 2번) 
  } else if (action.type === DECREMENT) {
    return { ...state, value: state.value - 1 }
  } else if (action.type === RESET) {
    return { ...state, value: 0 }
  }

  return state // 5번) 
}
[ 주석 설명 ]
1. 1번째 인자는 현재state, 2번째 인자는 액션객체
2. 업데이트 할 새로운state (원본을 훼손하면 안되기때문에, 객체복사를 사용)
3. 초기값 설정 (undefined 방지) // 이 값이 화면에 출력되는 초기값!!!
4. -
5. 꼭 넣어야한다 (undefined 방지) // 이 값이 화면에 출력되는 초기값!!!
6. payload 사용
// src/store/reducers/count.js (reducer 파일 2)

import { PUSH } from './actions' // 4번)

// 초기값 설정
const initialState = { // 3번) 
	count: 0
}

// 리듀서 함수 (if ver)
export default function countReducer(state = initialState, action) { // 1번) 
  if (action.type === PUSH) {
    return { ...state, count: state.count + 1 } // 2번) 
  }

  return state // 5번) 
}

// 리듀서 함수 (switch ver): if문 or switch 중에 원하는 것 사용하면 된다 (취향차이)
// export default function countReducer(state = initialState, action) {
//   switch (action.type) {
// 		case PUSH: {
// 			return {...state, count: state.count + 1}
// 		}
// 		default:
// 			return state
// 	}
// }
[ 주석 설명 ]
1. 1번째 인자는 현재state, 2번째 인자는 액션객체
2. 업데이트 할 새로운state (원본을 훼손하면 안되기때문에, 객체복사를 사용)
3. 초기값 설정 (undefined 방지) // 이 값이 화면에 출력되는 초기값!!!
4. -
5. 꼭 넣어야한다 (undefined 방지) // 이 값이 화면에 출력되는 초기값!!!

3) 통합 reducer 파일

// src/store/reducers/index.js (reducer 파일 통합)

import { combineReducers } from 'redux'; // 1번) 
import addsubReducer from './addsub'
import countReducer from './count'

// 방법 1)
const rootReducer = combineReducers({ // 2번) 
	value: addsubReducer, // const { value } = useSelector(state => state.value)
	count: countReducer // const { count } = useSelector(state => state.count)
})

// 방법 2)
// const rootReducer = combineReducers({
// 	addsubReducer, // const { value } = useSelector(state => state.addsubReducer)
// 	countReducer // const { count } = useSelector(state => state.countReducer)
// })

export default rootReducer; // 3번) 
[ 주석 설명 ]
1. combineReducers
2. 리듀서 함수들 통합
3. 내보내기

4) createStore 파일

// src/store/store.js

import { createStore } from 'redux'; // 1번) 
import rootReducer from './reducers'; // 2번) 

const store = createStore(rootReducer) // 3번) 

export default store; // 4번) 
[ 주석 설명 ]
1. 반드시 필요
2. reducer 통합함수를 가져온다
3. reducer 함수를 등록
4. 콘솔에 store를 찍으면, 
// getState: ƒ getState()
// dispatch: ƒ dispatch(action)
// subscribe: ƒ subscribe(listener)
// replaceReducer: ƒ replaceReducer(nextReducer)

5) redux 사용 컴포넌트

// src/App.js (redux 사용, 컴포넌트 1)

import React from 'react';
import { useSelector, useDispatch } from "react-redux"; // 1번)
import { addAction, subAction, resetAction, pushAction } from './store/actions' // 4번) 

function App() {
	const dispatch = useDispatch() // 2번) 

  // 3번) 
	const { value } = useSelector(state => state.value) // addsubReducer // 처음 렌더링 될때는 초기값이 출력된다
	const { count } = useSelector(state => state.count) // countReducer

	const addButton = () => {
		dispatch(addAction(`action.type === 'INCREMENT' 인 경우`)) // 5번) 
	}
	const subButton = () => {
		dispatch(subAction())
	}
	const resetButton = () => {
		dispatch(resetAction())
	}
	const pushButton = () => {
		dispatch(pushAction())
	}

  
	return (
		<div className="App">
			<div>
				value: { value }
			</div>
			<button onClick={ addButton }> + </button>
			<button onClick={ subButton }> - </button>
			<button onClick={ resetButton }> reset </button>
			<div>
				count: { count }
			</div>
			<button onClick={ pushButton }> click </button>
		</div>
	);
}

export default App;
[ 주석 설명 ]
1. -
2. 꼭 할당해서 사용해야한다
3. state가 rootReducer // 상태가 객체이고 구조분해할당
4. 액션 생성자 함수를 불러온다
5. reducer함수(액션생성자(payload))

6) index 파일

// scr/main.jsx (Vite 버전)
// scr/index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';

import { Provider } from 'react-redux'; // store를 사용하기 위해서 필요한 단계 1
import store from './store/store' // store.js (./폴더명/파일명)

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <Provider store={ store }> // store를 사용하기 위해서 필요한 단계 2
      <App />
    </Provider>
  </React.StrictMode>
);

8. 기본 예시 (초기값 원시ver)

1) action 파일

// src/store/actions/actions.js (action 파일)

// 액션 객체 (액션 타입 + 액션 생성자 함수)
// 4번) 
export const INCREMENT = 'INCREMENT'; // 1번) 
export function addAction(call) { // 2번) 
  return  { // 3번) 
    type: INCREMENT, // type
    call // payload
  }
}

export const DECREMENT = 'DECREMENT';
export function subAction(call) {
  return  {
    type: DECREMENT,
    call
  }
}

export const RESET = 'RESET';
export function resetAction(call) {
  return  {
    type: RESET,
    call
  }
}

export const PUSH = 'PUSH';
export function pushAction(call) {
  return  {
    type: PUSH,
    call
  }
}
[ 주석 설명 ]
1. 액션의 type을 정의한다. (일반적으로, 액션의 type은 대문자 + _ 조합을 사용)
2. 액션을 생성하는 함수를 만든다. (액션 생성자)
3. 액션 객체를 return (항상 객체 형태로 return을 작성해야한다)
4. export 해주면, reducer에서 사용가능

2) 개별 reducer 파일

// src/store/reducers/addsub.js (reducer 파일 1)

import { INCREMENT, DECREMENT, RESET } from './actions' // 4번)

// 초기값 설정
const initialState = 0; // 3번) 

// 리듀서 함수
export default function addsubReducer(state = initialState, action) { // 1번) 
  if (action.type === INCREMENT) {
    console.log(action.call); // 6번) 
    return state + 1 // 2번) 
  } else if (action.type === DECREMENT) {
    return state - 1
  } else if (action.type === RESET) {
    return state * 0
  }

  return state // 5번) 
}
[ 주석 설명 ]
1. 1번째 인자는 현재state, 2번째 인자는 액션객체
2. 업데이트 할 새로운state
3. 초기값 설정 (undefined 방지) // 이 값이 화면에 출력되는 초기값!!!
4. -
5. 꼭 넣어야한다 (undefined 방지) // 이 값이 화면에 출력되는 초기값!!!
6. payload 사용
// src/store/reducers/count.js (reducer 파일 2)

import { PUSH } from './actions' // 4번)

// 초기값 설정
const initialState = 0; // 3번) 

// 리듀서 함수
export default function countReducer(state = initialState, action) { // 1번) 
  if (action.type === PUSH) {
    return state + 1 // 2번) 
  }

  return state // 5번) 
}
[ 주석 설명 ]
1. 1번째 인자는 현재state, 2번째 인자는 액션객체
2. 업데이트 할 새로운state
3. 초기값 설정 (undefined 방지) // 이 값이 화면에 출력되는 초기값!!!
4. -
5. 꼭 넣어야한다 (undefined 방지) // 이 값이 화면에 출력되는 초기값!!!

3) 통합 reducer 파일

// src/store/reducers/index.js (reducer 파일 통합)

import { combineReducers } from 'redux'; // 1번) 
import addsubReducer from './addsub'
import countReducer from './count'

// 방법 1)
const rootReducer = combineReducers({ // 2번) 
	value: addsubReducer, // const value = useSelector(state => state.value)
	count: countReducer // const count = useSelector(state => state.count)
})

// 방법 2)
// const rootReducer = combineReducers({
// 	addsubReducer, // const value = useSelector(state => state.addsubReducer)
// 	countReducer // const count = useSelector(state => state.countReducer)
// })

export default rootReducer; // 3번) 
[ 주석 설명 ]
1. combineReducers
2. 리듀서 함수들 통합
3. 내보내기

4) createStore 파일

// src/store/store.js

import { createStore } from 'redux'; // 1번) 
import rootReducer from './reducers'; // 2번) 

const store = createStore(rootReducer) // 3번) 

export default store; // 4번) 
[ 주석 설명 ]
1. 반드시 필요
2. reducer 통합함수를 가져온다
3. reducer 함수를 등록
4. 콘솔에 store를 찍으면,
// getState: ƒ getState()
// dispatch: ƒ dispatch(action)
// subscribe: ƒ subscribe(listener)
// replaceReducer: ƒ replaceReducer(nextReducer)

5) redux 사용 컴포넌트

// src/App.js (redux 사용, 컴포넌트 1)

import React from 'react';
import { useSelector, useDispatch } from "react-redux"; // 1번)
import { addAction, subAction, resetAction, pushAction } from './store/actions' // 4번) 

function App() {
	const dispatch = useDispatch() // 2번) 

  // 3번) 
  const value = useSelector(state => state.value) // addsubReducer // 처음 렌더링 될때는 초기값이 출력된다
	const count = useSelector(state => state.count) // countReducer

	const addButton = () => {
		dispatch(addAction(`action.type === 'INCREMENT' 인 경우`)) // 5번) 
	}
	const subButton = () => {
		dispatch(subAction())
	}
	const resetButton = () => {
		dispatch(resetAction())
	}
	const pushButton = () => {
		dispatch(pushAction())
	}

  
	return (
		<div className="App">
			<div>
				value: { value }
			</div>
			<button onClick={ addButton }> + </button>
			<button onClick={ subButton }> - </button>
			<button onClick={ resetButton }> reset </button>
			<div>
				count: { count }
			</div>
			<button onClick={ pushButton }> click </button>
		</div>
	);
}

export default App;
[ 주석 설명 ]
1. -
2. 꼭 할당해서 사용해야한다
3. state가 rootReducer
4. 액션 생성자 함수를 불러온다
5. reducer함수(액션생성자(payload))

6) index 파일

// scr/main.jsx (Vite 버전)
// scr/index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';

import { Provider } from 'react-redux'; // store를 사용하기 위해서 필요한 단계 1
import store from './store/store' // store.js (./폴더명/파일명)

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <Provider store={ store }> // store를 사용하기 위해서 필요한 단계 2
      <App />
    </Provider>
  </React.StrictMode>
);
profile
JS, TS, React, Vue, Node.js, Express, SQL 공부한 내용을 기록하는 장소입니다

0개의 댓글