Redux 를 쓰는 이유
- 앱 상태가 React 만으로 관리하기 힘들 정도로 많아짐.
→ 상태를 외부로 빼내고, 변경점만 전파받고 싶음.
→ 외부 상태도 바인딩하고 싶은데… 어떻게??
상태 관리 패턴의 분류
- 상태 변경을 감지하는 방식에 따른 분류
Flux-based
- 전역, 단일 저장소 사용 ex) react-redux의 최상위 Provider 컴포넌트 Action 실행을 제외한 상태 변경 제한.
- 단일 store 를 감시하므로 디버깅 기준이 action의 실행 ⇒ 전역 state 변경으로 유일해짐.
Redux(Flux + Reducer) / Zustand (Flux + Context)
Vuex (Flux for Vue)
NgRx (Flux + Rx for Angular)
Proxy-based
- State 재할당 감지 및 전파
- 직접 할당(state = newState)할 지, Action을 사용할지 선택 가능
Mobx
Context-based
- 지역 상태 전파
- 가장 가까운 상태 바인딩 (Provider 위치가 자유로움)
- 디버깅 기준점을 별도 제공해야 함. (단일 Store 가 아니므로, Key 를 통해 무슨 상태인지 표현)
React - Context API
- id /displayName (optional) 제공을 통한 디버깅
Jotai
- debugLabel (optional) 제공
Recoil
- State 생성 시점에 Key 속성 입력 강제
Redux 동작 구조
- redux 상태가 변경된다고 해서 View가 무조건 변경되는 것은 아니다. 제약도 없다.
- react의 global state 를 갱신해서 하위 컴포넌트로 상태 변경점을 내려주는 react-redux라고 부른다.
- react는 dispatch(action) 를 실행하면 store를 통해 Reducer를 실행하게 되는데 이전 state를 가지고 새로 갱신될 상태를 reducer를 통해 가공해 받은 다음 새로운 State 를 UI에 보여준다.
Side-effect
- 비동기 처리가 가장 대표적.
- 항상 모든 문제들이 하나의 action 실행해서 끝나지는 않는다.
API 를 실행하고 3초후에 요청에 대한 결과값을 state에 넣고싶으면 action을 실행해서 비동기 처리가 끝난 이후에 또 다른 action을 실행하고 싶을텐데 이러한 작업을 action 의 Side-Effect 라고 부름.
redux action은 동기 작업밖에 처리하지 못하기 때문에 해당 동기 action이 실행되었을때 비동기 처리를 실행하고 다른 동기 action을 실행해서 마무리하는 작업을 MiddleWare
에서 처리한다.
이러한 작업들을 Redux-Saga 를 통해서 해결할 수 있습니다.
Middleware
- dispatch 함수를 직접 갱신하는걸 몽키 패칭 이라고 한다.
- 보통 SW 개발을 할 때 몽키 패칭은 최대한 피해야 한다.
- action으로
state
가 변경되기 전에 할 수 있는 행동들을Middleware
에서 처리할 수 있다.
Logging 예시
store.dispatch(addTodo('Use Redux')) // dispatch 등록
아래 코드는 동작하지만 모든 액션이 실행될 때 로그를 찍을 수 없기에 적합하지 않다.
const action = addTodo('Use Redux')
console.log('dispatching', action)
store.dispatch(action)
console.log('next state', store.getState())
log 함수를 유틸리티 함수로 만들었다.
하지만 redux가 아닌 새로운 유틸리티 함수를 어디에서 import 하기에 불편할 수 있다.
function dispatchAndLog(store, action) {
console.log('dispatching', action)
store.dispatch(action)
console.log('next state', store.getState())
}
몽키 패칭 dispatch
next에 dispatch 저장해놓고 실제 dispatch 함수를 다른 함수로 덮어 쓴다.
원본 dispath 함수의 input 과 output 이 똑같은 함수를 return 했기에 외부에서는 사용할 때는 티가 안난다.
const next = store.dispatch
store.dispatch = function dispatchAndLog(action) {
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
return result
}
Redux에서 실제로 Middleware 적용하는 방법
applyMiddleware 로직은 아래와 비슷.
원본 리덕스 applyMiddleware 는 다르게 동작.
아래 코드는 런타임에도 미들웨어를 추가할 수 있고 새로운 store 인스턴스를 받아서 실행을 해야하기 때문에 store를 생성하는 시점에 단 한번만 middleware 를 반영할 수 있도록 해놨다.
function applyMiddleware(store, middlewares) {
middlewares = middlewares.slice()
middlewares.reverse()
let dispatch = store.dispatch
middlewares.forEach(middleware => (dispatch = middleware(store)(dispatch)))
return Object.assign({}, store, { dispatch })
}
미들웨어 유틸리티 함수
const logger = store => next => action => {
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
return result
}
const crashReporter = store => next => action => {
try {
return next(action)
} catch (err) {
console.error('Caught an exception!', err)
Raven.captureException(err, {
extra: {
action,
state: store.getState()
}
})
throw err
}
}
redux store 에 middleware 등록하기!!
import { createStore, combineReducers, applyMiddleware } from 'redux'
const todoApp = combineReducers(reducers)
const store = createStore(
todoApp,
applyMiddleware(logger, crashReporter)
)
Redux-Thunk & redux-observable
redux-saga 는 제너레이터를 기반으로 동작하기에 보기에 복잡할 수 있습니다.
그렇게 async await 함수로 충분히 대체가 가능하므로 redux-Thunk를 사용하는게 간결하며 좋다고 생각한다.
그러므로 redux 에서 굉장히 복잡한 미들웨어를 사용하지 않을 경우 redux-Thunk를 사용하는것이 좋으며,
엄청 복잡한 미들웨어를 지니고 있는 경우는 redux-observable 을 사용하는게 좋다.
정리
- Redux 가 어떤 상태 관리 패턴인지 알아봤으며 어떠한 라이브러리들이 존재하는지 살펴봤습니다.
- 이것으로 리덕스에서 가장 중요하다고 생각하는 미들웨어 개념을 살펴봤습니다.
- Redux 말고 Recoil 을 사용하는 것도 좋을 것 같지만 Redux 의 개념은 알고 있어야 할 것 같습니다.
'React' 카테고리의 다른 글
[React] Graphql 알아보기 (0) | 2024.02.04 |
---|---|
React 테스트하기!!~~ (0) | 2023.12.23 |
React 기초 정리! (0) | 2023.12.16 |