[TIL] 2023/10/04

yongkini ·2023년 10월 3일
0

Today I Learned

목록 보기
155/173
post-thumbnail

Today I Learned

Redux 복습

Reducer는 왜 Reducer 일까?..

: 생각해보면, 진지하게 고민해본적은 없다. redux 아키텍처를 만들면서 보일러 플레이트 코드라고 생각하고 쳤던 것들 중에 리듀서가 있었고, 그 역할에 대해선 이해를 하고 있었지만(특정 액션을 바탕으로 state를 어떻게 변화시킬지를 정의해놓고, 이를 통해 새로운 하나의 상태값을 만들어 내는 것), 왜 이걸 reducer라고 부르는지는 몰랐는데, 갑자기 궁금해졌다.

Array.prototype.reduce와 관련된 것일까?
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의 콜백 함수와 유사하다.

  • 공통점 :
    • 이전 상태들과 현재 상태를 변경하기 위한 메타 데이터인 action 을 가지고 새로운 상태를 만들어낸다.
    • dispatch 된 액션 값들을 가지고 특정 시점에 하나의 상태 값으로 만든다.
  • 차이점 :
    • Array.reduce()는 모든 작업이 한번에 일어나지만(동일한 시점에 일어난다), 리덕스는 앱의 생명주기에 걸쳐서 동작한다.

dispatch는 이벤트 버스 아키텍쳐의 event trigger 이다.

redux의 상태값을 변화시키는 유일한 방법은 store.dispatch를 사용하는 것입니다. 그리고 이렇게 상태를 변화시키는 방법을 제한했기 때문에 리덕스는 상태 변경을 미리 예측할 수 있는 장점을 가질 수 있게 되었습니다.

dispatch는 이벤트 버스 아키텍쳐에서 event를 trigger하는 역할을 맡는다.

dispatch + action

diaptch 함수의 인자로 action object를 넣어 호출하게 되면 event가 trigger되고 리듀서는 해당 action을 받아 새로운 상태 값을 반환합니다.

reducer는 이벤트 버스 아키텍쳐의 event listener 역할을 맡는다.

** 이벤트 버스 아키텍처 이미지
출처 : https://itchallenger.tistory.com/762

이러한 아키텍처를 이용하면 관련 없는 컴포넌트끼리 위치와 무관하게 메시지를 주고 받을 수 있고, 수신자와 발신자를 서로 몰라도 통신이 가능하다.

사실 정확히 redux가 버스 아키텍처를 따르는건 아니지만, 상당히 유사한 구조를 갖고 있기 때문에 비유적으로 설명해본다. 이 때, 이벤트 버스 아키텍처의 장점은 이벤트를 구독한 모든 객체들에게 EventBus 객체의 notify를 호출하는 것으로 모든 구독자들에게 이벤트를 전달할 수 있다는 것이며, 단점은 발행된 데이터의 종류와 상관없이 모든 구독자들이 강제적으로 해당 데이터를 처리해야 한다는 것이다. 하지만, redux는 앞서 말한 단점을 리듀서라고 하는 필터링을 담당하는 객체를 이용하여 각 컴포넌트가 본인에게 관심있는 데이터만 받아 처리하게 함으로 보완한다(useSelector를 이용해 특정 state만 구독할 수 있고, 이에 따라 모든 변화를 구독하지 않도록 할 수 있다).

Redux 인터페이스 복습

  • combineReducers로 사용할 리듀서들을 묶어준다.
  • createStore가 deprecated 됐고, redux-toolkit의 configureStore를 사용하는 것을 권장한다(물론 아직 createStore을 쓸수는 있다).
  • reducer를 좀 더 간단하게 작성할 수 있는 handleActions(from redux-actions)를 사용한다.
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})
});

이렇게 간소화 할 수 있다.

  • 위와 같이 액션 생섬 함수를 만들어주는 것도 간소화 됐다 by 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를 꼭 써야할까(내가 진행하는 플젝에서)??

Redux란

  • 자바스크립트 앱을 위한 예측 가능한 상태 컨테이너
  • 매우 가벼움 (의존 라이브러리 포함 2KB라고 한다)
  • React 외에도 다른 뷰 라이브러리와 함께 사용 가능

어떤 문제를 해결하는데 도움이 되는가?

  • 상태가 예측 가능한 방식으로만 업데이트될 수 있도록 하는 규칙 보유
  • 전체 애플리케이션에서 사용해야 하는 상태에 대한 중앙 집중식 저장소 역할 (= 애플리케이션의 여러 부분에서 필요한 "전역 상태" 관리)
  • 애플리케이션의 상태가 언제, 어디서, 왜, 어떻게 업데이트되는지, 이러한 변경이 발생할 때 애플리케이션 로직이 어떻게 작동하는지 더 쉽게 이해 가능
  • 예측 가능하고 테스트 가능한 코드를 작성하도록 유도 => 애플리케이션이 예상대로 작동할 것이라는 확신을 준다

Redux Toolkit (= RTK) 란?

  • Redux 로직을 작성하기 위해 공식적으로 추천되는 방법
  • 대부분의 Redux 작업을 단순화하고 실수를 방지
  • Redux 앱을 더 만들기 쉽게 해주는 유틸리티 포함 예를 들어, store 준비, reducer 생성, 불변 수정 로직 작성, 상태 "조각(slice)"을 한 번에 작성 등에 대한 유틸리티를 제공함.

리듀서는 상태가 바뀌었을 때 상태 객체를 변경하지 않고, 새로운 객체를 반환하는 순수 함수이다.

ChatGPT에게 물어본 Redux의 철학 및 필요성

  • 상태 관리의 필요성 : Redux는 복잡한 애플리케이션 상태를 효율적으로 관리하기 위해 고안되었습니다. 현대 웹 애플리케이션은 다양한 컴포넌트 간에 데이터를 공유하고 상태를 동기화해야 합니다. 이것은 애플리케이션을 유지하고 디버깅하기 어렵게 만들 수 있으며 예측 불가능한 버그를 발생시킬 수 있습니다.

  • 단일 소스 오브 진실 (Single Source of Truth): Redux의 핵심 철학 중 하나는 "단일 소스 오브 진실"입니다. 이것은 애플리케이션 상태가 단일 JavaScript 객체로 표현되고 애플리케이션의 모든 컴포넌트가 이 상태를 공유한다는 것을 의미합니다. 이러한 단일 상태 트리를 통해 애플리케이션의 상태를 예측 가능하게 만들고 상태 변경을 추적하기 쉽게 합니다.

  • 불변성 (Immutability): Redux는 상태가 불변성을 유지해야 한다는 원칙을 강조합니다. 상태가 변경되면 이전 상태를 변경하지 않고 새로운 상태 객체를 생성합니다. 이것은 상태 변경을 추적하고 디버깅하기 쉽게 만들며, 예기치 않은 부작용을 방지하는데 도움을 줍니다.

  • 예측 가능한 동작 (Predictable Behavior): Redux는 순수 함수로 작성된 "리듀서"를 사용하여 상태 변경을 다룹니다. 이것은 주어진 상태와 액션에 대해 동일한 결과를 항상 반환하므로 예측 가능한 동작을 제공합니다. 이 예측 가능성은 애플리케이션을 이해하고 테스트하기 쉽게 만듭니다.

  • 중앙 집중화된 상태 관리: Redux는 애플리케이션의 상태를 중앙 집중화된 스토어에 저장하고 관리합니다. 이렇게 하면 여러 컴포넌트가 상태에 접근하고 상태 변경을 요청할 수 있으며, 상태 변경은 일관되게 처리됩니다.

  • 개발자 도구: Redux는 개발자 도구와 통합하기 쉽도록 설계되었습니다. 이 도구를 사용하면 애플리케이션의 상태를 시각적으로 검사하고 디버깅하는 것이 훨씬 쉬워집니다.

위의 내용은 사실 이론적으로 접근하면 와닿지 않지만, 이걸 읽어보고 실제로 redux를 쓰면서 다시 생각해보는 용도로 정리해놓는다.

** 추가로 나는 일단은 레거시 코드가 redux & redux-saga로 돼있는 상태라서 이 둘을 복습하고(기존에 했었기에) 사용해볼 예정이다. 내가 쓸 줄 아는 recoil, context api 등을 이들 대신에 쓰는 방향으로 리팩토링을 할지는 이 플젝의 레거시를 분석하면서 실제로 좀 써보다가 단점이 명확하고, 맞지 않는 것 같을 때 그 때 진행하도록 한다.

Redux와 비동기처리

비동기처리는 왜 미들웨어에서 처리할까?

: redux-saga는 redux에 적용하는 미들웨어이다. 그러면 왜 비동기처리 등을 위해 이렇게 미들웨어를 이용할까? 직접 리듀서에서 하는 방식으로 하면 안될까?. 이는 리듀서가 순수함수를 지향하고, 이 방향이 리덕스가 지향하는 방향이라 그렇다. 비동기 처리를 리듀서에서 하게 되면 순수함수가 아니기 때문에 미들웨어를 통해 이런 것들을 처리하도록 하게 됐다고 이해하면 될듯하다. redux가 리듀서를 순수함수로 하고자하는건, redux가 지향하는 것 자체가 state를 안정적으로 관리하고자 하는 것이 있는데, 순수함수가 아니게 되면 이러한 state 예측 가능성이 떨어지게 된다는 것이므로 그러하다.

대부분의 데이터는 fetch를 통해 가져온다(비동기 처리이다).

: 하지만, 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를 복습하면서 읽기 좋다고 생각돼 가져와봤다.

npm start를 할 때 3000 말고 다른 포트를 쓰려면

  "start": "vite --host 0.0.0.0 --port 8080",

이런식으로 써주면 된다.

profile
완벽함 보다는 최선의 결과를 위해 끊임없이 노력하는 개발자

0개의 댓글