Notion Blog

Redux, Redux-Thunk, Redux-Saga 주요 개념 요약

1. Redux, React-Redux 주요 개념 요약

notion image

1-1. 스토어

  • 애플리케이션의 상태를 트리 구조로 관리한다.
  • 상태를 읽으려면 getState() 함수를 호출한다.
  • 상태를 바꾸려면 dispatch() 함수를 호출해서 액션을 디스패치 한다.
  • 리덕스는 스토어가 생성되는 즉시 더미 액션을 디스패치 하여 스토어의 초기 상태를 설정한다.

1-2. 미들웨어 store => next => dispatch

  • 리덕스의 기능을 확장하기 위한 수단으로, dispatch() 함수를 래핑 하는 역할을 수행한다.
  • 다음 미들웨어의 dispatch() 함수를 next 인자로 받아서 새로운 dispatch() 함수를 만들어낸다.
  • 마지막 미들웨어는 스토어의 기본 dispatch() 함수를 인자로 받아서 미들웨어 체인을 종료시킨다.
  • 이러한 체인의 결과로 만들어지는 최종적인 dispatch() 함수를 리덕스의 스토어에 부착하게 된다.
  • 각 미들웨어는 디스패치 된 액션을 직접 처리하거나 다음 미들웨어의 dispatch() 함수를 호출한다.
  • 이때, 미들웨어는 store 인자로 스토어를 받기 때문에 스토어와도 직접적인 상호작용이 가능하다.
  • 예를 들어, 썽크 미들웨어는 디스패치 된 썽크의 호출 시 스토어의 메소드들을 인자로 전달해준다.

1-3. 스토어 인핸서 storeCreator => storeCreator

  • 스토어 생성자를 인자로 받아서 새로운 (개선된) 스토어 생성자를 만들어낸다.
  • 일반적으로, createStore() 함수의 마지막 인자로 스토어 인핸서를 넘겨준다.

1-4. 리덕스 동작 원리

  • 리덕스의 스토어는 Context로 구성되어 Provider 컴포넌트의 value prop으로 전달된다.
  • 해당 Context는 구체적으로 { store, subscription }과 같은 형태를 띠고 있다.
  • connect() 함수에 의해 만들어진 컴포넌트나 useSelector() 함수를 사용한 컴포넌트는 액션이 디스패치 된 후 (어떠한 구독 메커니즘에 의해) 강제적으로 리렌더링이 유발된다. 이때 해당 컴포넌트는 Provider 컴포넌트가 제공해주는 리덕스 스토어의 상태를 읽는다.
  • 참고로 스토어는 늘 참조값이 같은 가변 객체에 해당하며, Provider 컴포넌트는 중간에 스토어가 다른 걸로 바뀌지 않는 이상 늘 같은 참조값을 가지는 객체를 Context로 구성하도록 구현된다. 따라서 액션을 디스패치 하든 무엇을 하든, Provider 컴포넌트에 의해 자식 컴포넌트들이 전부 리렌더링 될 일은 없다. 즉, 리렌더링은 오로지 구독 메커니즘에 의존하여 발생할 뿐이다.

1-5. useSelector()

  • 액션이 디스패치 될 때마다 useSelector() 함수를 실행하여 새로운 상태 값을 계산한다.
  • 만약 이전 값과 새로 계산된 값이 다르면 해당 컴포넌트의 리렌더링이 강제로 유발된다.

1-6. useDispatch()

  • 참조값이 늘 같은 dispatch() 함수를 반환한다.

2. Redux-Thunk 주요 개념 요약

2-1. 썽크

  • 객체가 아닌, 동기 또는 비동기 작업을 수행할 수 있는 함수를 말한다.
  • 썽크를 사용하려면 액션 생성자는 객체가 아닌 이 함수를 반환해야 한다.
  • 이는 리덕스 스토어와의 상호작용을 위해 getState(), dispatch() 함수를 인자로 받는다.
  • 따라서 리덕스 스토어의 상태에 접근하거나 또 다른 액션을 디스패치 하는 것이 가능하다.

2-2. 썽크 미들웨어

  • 디스패치 된 액션의 타입이 객체가 아닌 함수(= 썽크)인 경우, 이를 직접 처리한다.
  • 이때 인자로 store.dispatch(), store.getState() 함수를 넘기며 해당 썽크를 호출한다.
  • 참고로 여기서 전달받은 dispatch() 함수를 호출하면 다음 미들웨어의 dispatch() 함수를 호출하는 것이 아니라 스토어의 dispatch() 함수를 호출하는 것이기 때문에 미들웨어 체인의 맨 처음으로 다시 돌아가게 된다.
  • 만약 액션의 타입이 객체라면, 다음 미들웨어의 dispatch() 함수로 이를 처리한다(다음 미들웨어에게 넘긴다).
 

3. Redux-Saga 주요 개념 요약

notion image

3-1. 사가 (= 제네레이터 함수)

  • 제네레이터를 반환하는 함수이다.
  • 해당 제네레이터의 next() 함수를 호출함으로써 사가를 실행할 수 있다.
  • 사가는 yield 표현식을 만날 때마다 실행이 중단되며, 해당 표현식의 값은 next() 함수의 반환 값에 포함된다.
  • next() 함수의 인자로 넘기는 값은 해당 사가의 실행이 중단되어 있는 위치의 yield 표현식 자리를 채워준다.

3-2. 사가의 실행 흐름

  • 사가 미들웨어의 run 메소드가 엔트리 포인트에 해당하는 루트 사가를 실행한다.
  • 사가의 실행은 해당 제네레이터 함수를 호출하여 반복 가능한 제네레이터를 획득하는 것으로 시작한다.
  • 이제 해당 제네레이터의 next() 함수를 통해 이펙트를 읽고 지시된 동작을 수행하는 작업을 반복한다.
  • 그러다가 fork 이펙트를 읽으면, 해당 사가를 실행하는 또 다른 새로운 실행 맥락을 하나 만들어낸다.

3-3. 사가 미들웨어의 구현

  • 사가 미들웨어도 다른 미들웨어와 똑같이 store => next => dispatch 시그니쳐를 가지는 함수이다.
  • 그런데 사가 미들웨어에는 특별히 루트 사가의 실행을 위한 하나의 함수가 run 메소드로서 부착된다.
  • 그리고 사가 미들웨어는 호출되는 순간 그 함수에 store의 두 메소드를 바인딩시켜주도록 구현된다.
  • 따라서 run 메소드는 루트 사가를 실행하는 동안, 리덕스의 스토어와 직접적인 상호작용이 가능하다.
  • 그래서 select, put 이펙트를 각각 getState(), dispatch() 함수로 처리할 수 있게 되는 것이다.

3-4. 일반적인 사가 사용 패턴

  • 루트 사가는 여러 개의 Watcher 사가를 fork 한다. 각 Watcher 사가는 특정 액션을 기다린다.
  • 각 Watcher 사가는 특정 액션이 디스패치 될 때 이를 감지하여 해당 Worker 사가를 fork 한다.

3-5. 사가 미들웨어의 역할

  • 사가 미들웨어는 디스패치 된 액션을 캐치해서 이를 Watcher 사가에게 알리는 역할만 수행할 뿐이다.
  • 따라서 해당 액션이 리듀서에 도달했는지 다른 미들웨어에 의해 중간에 변형되었는지는 알지 못한다.
  • 즉, 다른 미들웨어들과 달리 액션을 디스패치 하는 과정 자체에는 직접적으로 관여하지 않는다.

3-6. 이펙트

  • 사가를 실행하는 실행부에게 어떠한 동작을 수행해야 할지 알려주는 일반 객체(Plain Object)이다.
  • 일반적으로 put(), call(), select() 등의 헬퍼 함수를 호출함으로써 해당 이펙트 객체를 생성한다.
  • 이펙트 자체는 단순 객체이므로 아무런 사이드 이펙트를 발생시키지 않는다. 따라서 테스트가 용이하다.

3-7. 주요 이펙트

  • call(fn, ...args): (비동기 혹은 동기) 함수 fn을 호출한다.
  • select(selector): selector를 이용하여 리덕스 스토어의 상태를 읽어온다. (= store.getState() 함수)
  • put(action): action을 디스패치 한다. (= store.dispatch() 함수)
  • take(actionType): actionType의 액션이 디스패치 될 때까지 기다린다.
  • fork(saga, ...args): 새로운 실행 맥락으로 saga를 실행한다.
  • takeEvery(actionType, saga, ...args): 사가를 하나 fork 하는 헬퍼 함수이다. 해당 사가는 actionType의 액션을 기다렸다가 sagafork 하는 작업을 무한히 반복한다.

태그