: 생각해보면, 진지하게 고민해본적은 없다. redux 아키텍처를 만들면서 보일러 플레이트 코드라고 생각하고 쳤던 것들 중에 리듀서가 있었고, 그 역할에 대해선 이해를 하고 있었지만(특정 액션을 바탕으로 state를 어떻게 변화시킬지를 정의해놓고, 이를 통해 새로운 하나의 상태값을 만들어 내는 것), 왜 이걸 reducer라고 부르는지는 몰랐는데, 갑자기 궁금해졌다.
const numbers = [1, 2, 3, 4, 5 ,6]
const addNumbers = (prev, current) => {
return prev + current
}
const initialValue = 0
const total = numbers.reduce(addNumbers, initialValue)
console.log(total) // 21
리듀서라는 말을 들어보면, reduce 메서드가 생각나는데, 이와 유사한 것일까?. 결론은 어느정도 맞는 말이다.
reduce의 콜백함수인 addNumbers
가 reducer와 유사한 역할을 한다.
const actions = [
{ type: 'counter/increment' },
{ type: 'counter/increment' },
{ type: 'counter/increment' }
]
const initialState = { value: 0 }
const finalResult = actions.reduce(counterReducer, initialState)
console.log(finalResult) // {value: 3}
위와 같이 'counter/increment' 이런 액션을 받으면 + 1을 해주는 reducer가 있을 때, 그 역할이 앞서 말한 reduce의 콜백 함수와 유사하다
.
redux의 상태값을 변화시키는 유일한 방법은 store.dispatch를 사용하는 것입니다. 그리고 이렇게 상태를 변화시키는 방법을 제한했기 때문에 리덕스는 상태 변경을 미리 예측할 수 있는 장점을 가질 수 있게 되었습니다.
dispatch는 이벤트 버스 아키텍쳐에서 event를 trigger하는 역할을 맡는다.
diaptch 함수의 인자로 action object를 넣어 호출하게 되면 event가 trigger되고 리듀서는 해당 action을 받아 새로운 상태 값을 반환합니다.
reducer는 이벤트 버스 아키텍쳐의 event listener 역할을 맡는다.
** 이벤트 버스 아키텍처 이미지
출처 : https://itchallenger.tistory.com/762
이러한 아키텍처를 이용하면 관련 없는 컴포넌트끼리 위치와 무관하게 메시지를 주고 받을 수 있고, 수신자와 발신자를 서로 몰라도 통신이 가능하다.
사실 정확히 redux가 버스 아키텍처를 따르는건 아니지만, 상당히 유사한 구조를 갖고 있기 때문에 비유적으로 설명해본다. 이 때, 이벤트 버스 아키텍처의 장점은 이벤트를 구독한 모든 객체들에게 EventBus 객체의 notify를 호출하는 것으로 모든 구독자들에게 이벤트를 전달할 수 있다는 것이며, 단점은 발행된 데이터의 종류와 상관없이 모든 구독자들이 강제적으로 해당 데이터를 처리해야 한다는 것이다. 하지만, redux는 앞서 말한 단점을 리듀서라고 하는 필터링을 담당하는 객체를 이용하여 각 컴포넌트가 본인에게 관심있는 데이터만 받아 처리하게 함으로 보완한다(useSelector를 이용해 특정 state만 구독할 수 있고, 이에 따라 모든 변화를 구독하지 않도록 할 수 있다).
const reducer = (state = initState, action) => {
switch (action.type) {
case CHANGE_USER:
return {
...state,
user: action.user
}
}
}
예를 들어, 본래는 위와 같이 써야했다면,
import { handleActions } from 'redux-actions';
const reducer = handleActions({
[CHANGE_USER]: (state, action) => ({...state, user: action.user})
});
이렇게 간소화 할 수 있다.
createAction
prev :
const CHANGE_USER = 'user/CHANGE_USER';
// 액션 생성 함수
export const change_user = user => ({type: CHANGE_USER, user});
next :
import { createAction } from 'redux-actions';
const CHANGE_USER = 'user/CHANGE_USER';
// 액션 생성 함수
export const change_user = createAction(CHANGE_USER, user => user);
** 나머지는 그대로거나 익숙한 부분이라 패스
상태 관리의 필요성 : Redux는 복잡한 애플리케이션 상태를 효율적으로 관리하기 위해 고안되었습니다. 현대 웹 애플리케이션은 다양한 컴포넌트 간에 데이터를 공유하고 상태를 동기화해야 합니다. 이것은 애플리케이션을 유지하고 디버깅하기 어렵게 만들 수 있으며 예측 불가능한 버그를 발생시킬 수 있습니다.
단일 소스 오브 진실 (Single Source of Truth): Redux의 핵심 철학 중 하나는 "단일 소스 오브 진실"입니다. 이것은 애플리케이션 상태가 단일 JavaScript 객체로 표현되고 애플리케이션의 모든 컴포넌트가 이 상태를 공유한다는 것을 의미합니다. 이러한 단일 상태 트리를 통해 애플리케이션의 상태를 예측 가능하게 만들고 상태 변경을 추적하기 쉽게 합니다.
불변성 (Immutability): Redux는 상태가 불변성을 유지해야 한다는 원칙을 강조합니다. 상태가 변경되면 이전 상태를 변경하지 않고 새로운 상태 객체를 생성합니다. 이것은 상태 변경을 추적하고 디버깅하기 쉽게 만들며, 예기치 않은 부작용을 방지하는데 도움을 줍니다.
예측 가능한 동작 (Predictable Behavior): Redux는 순수 함수로 작성된 "리듀서"를 사용하여 상태 변경을 다룹니다. 이것은 주어진 상태와 액션에 대해 동일한 결과를 항상 반환하므로 예측 가능한 동작을 제공합니다. 이 예측 가능성은 애플리케이션을 이해하고 테스트하기 쉽게 만듭니다.
중앙 집중화된 상태 관리: Redux는 애플리케이션의 상태를 중앙 집중화된 스토어에 저장하고 관리합니다. 이렇게 하면 여러 컴포넌트가 상태에 접근하고 상태 변경을 요청할 수 있으며, 상태 변경은 일관되게 처리됩니다.
개발자 도구: Redux는 개발자 도구와 통합하기 쉽도록 설계되었습니다. 이 도구를 사용하면 애플리케이션의 상태를 시각적으로 검사하고 디버깅하는 것이 훨씬 쉬워집니다.
위의 내용은 사실 이론적으로 접근하면 와닿지 않지만, 이걸 읽어보고 실제로 redux를 쓰면서 다시 생각해보는 용도로 정리해놓는다.
** 추가로 나는 일단은 레거시 코드가 redux & redux-saga로 돼있는 상태라서 이 둘을 복습하고(기존에 했었기에) 사용해볼 예정이다. 내가 쓸 줄 아는 recoil, context api 등을 이들 대신에 쓰는 방향으로 리팩토링을 할지는 이 플젝의 레거시를 분석하면서 실제로 좀 써보다가 단점이 명확하고, 맞지 않는 것 같을 때 그 때 진행하도록 한다.
: redux-saga는 redux에 적용하는 미들웨어이다. 그러면 왜 비동기처리 등을 위해 이렇게 미들웨어를 이용할까? 직접 리듀서에서 하는 방식으로 하면 안될까?. 이는 리듀서가 순수함수를 지향하고, 이 방향이 리덕스가 지향하는 방향이라 그렇다. 비동기 처리를 리듀서에서 하게 되면 순수함수가 아니기 때문에 미들웨어를 통해 이런 것들을 처리하도록 하게 됐다고 이해하면 될듯하다. redux가 리듀서를 순수함수로 하고자하는건, redux가 지향하는 것 자체가 state를 안정적으로 관리하고자 하는 것이 있는데, 순수함수가 아니게 되면 이러한 state 예측 가능성이 떨어지게 된다는 것이므로 그러하다.
: 하지만, redux는 동기적 코드로 작성이 돼있다. 예전에 쓰던 subscribe도 dispatch가 되면 동기적으로 동작을 한다.
예를 들어, 위와 같이 reducer 내에 fetch(비동기 처리)를 써서 return 문을 컨트롤하려 했을 때 잘될까? nope. reducer 자체는 일반 함수이기 때문에 특정 케이스로 가서 fetch를 실행하고 then 을 썼어도, 이미 reducer 함수 자체는 undefined를 리턴하고 콜스택에서 pop out 되고, fetch문은 비동기적으로 동작을 하지만, fetch문의 then에 따라 return이 될 때는 이미 reducer 함수가 끝난 시점이기에 의미가 없다. 그래서 비동기 처리는 reducer로 할 수 없다.
https://velog.io/@0715yk/FE-Redux-Thunk-RTK-vs-Redux-Saga
https://velog.io/@0715yk/FE-Redux-saga-vs-RTK-Middleware
https://velog.io/@0715yk/FE-Without-Redux-MiddleWares
위에는 내가 쓴 블로그 글이지만, redux-saga를 복습하면서 읽기 좋다고 생각돼 가져와봤다.
"start": "vite --host 0.0.0.0 --port 8080",
이런식으로 써주면 된다.