10. 리덕스
리덕스란?
리덕스 (Redux) 란?
: 자바스크립트 애플리케이션을 위한 상태 관리 라이브러리

-> 리덕스는 State 을 관리한다.

-> 리덕스의 데이터 관리 과정은 위와 같다.
=> 좀 더 효율적으로 애플리케이션을 관리할 수 있다.
Action 이란?
: 간단한 JavaScript 객체이다.

-> 우리가 수행하는 작업의 유형을 지정하는 'type' 속성이 있으며, 선택적으로 리덕스 저장소에 (Redux Store) 일부 데이터를 보내는데 사용되는 'payload' 속성을 가질 수 있다.
Reducer 란?
: 애플리케이션 상태의 변경 사항을 결정하고 업데이트된 상태를 반환하는 함수.

-> 인수로 조치를 취하고 Store 내부의 상태를 업데이트한다.
=> State 과 action object 를 받은 후에 next state 을 return 한다.
Redux Store 란?
: 하나로 모으는 객체 저장소 (애플리케이션의 전체 상태 트리를 보유한다.)

-> 내부 상태를 변경하는 유일한 방법은 해당 상태에 대한 Action을 전달하는 것.
#Redux Store는 클래스가 아님. 몇 가지 Methods가 있는 객체일 뿐이다.
미들웨어 없이 리덕스 카운터 앱 만들기
리액트 앱 설치
npx create-react-app my-app --template typescript
리액트 라이브러리 설치
npm install redux --save
Counter UI 및 함수 생성

Reducer 생성
-> reducers 폴더 안에 index.tsx

Store 생성 및 Action 전달
-> CreateStore()

: 앱의 전체 상태 트리를 보유하는 Redux 저장소를 만든다.
#앱에는 하나의 스토어만 있어야 한다.
-> getState()
Store | Redux
API > Store: the core Redux store methods
redux.js.org

: 애플리케이션의 현재 상태 트리를 반환한다.
-> subscribe()
: change listner 를 추가한다.
-> 작업이 전달될 때마다 호출되며 상태 트리의 일부가 잠재적으로 변경되었을 수 있다.
=> 다음 getState() 를 호출하여 콜백 내부의 현재 상태 트리를 읽을 수 있다.
combineReducers / Todo 기능 추가
root reducer와 sub reducer



createStore 에 루트 리듀서로 대체


Redux Provider
: Redux Store 저장소에 액세스해야 하는 모든 중첩 구성 요소는 Redux Store 저장소를 사용할 수 있도록 한다.
-> React Redux 앱의 모든 React 구성 요소는 저장소에 연결할 수 있으 므로 대부분의 응용 프로그램은 전체 앱의 구성 요소 트리가 내부에 있는 최상위 수준에서 <Provider> 를 렌더링한다. 그런 다음 Hooks 및 연결 API는 React 의 컨텍스트 메커니즘을 통해 제공된 저장소 인스턴스에 액세스할 수 있다.
Provider 를 렌더링

-> React Redux 앱의 모든 React 구성 요소는 저장소에 연결할 수 있으므로 대부분의 응용 프로그램은 전체 앱의 구성 요소 트리가 내부에 있는 최상위 수준에서 <Provider> 를 렌더링한다.
Todo UI

useSelector & useDispatch
: provider 로 둘러 쌓인 컴포넌트에서 store 접근 리액트에 Hooks 가 있듯이 리덕스에도 Hooks 가 있는데, 그게 바로 useSelector와 useDispatch이다.
useSelector

1. Root Reducer 에 RootState 타입을 생성

2. 생성한 RootState 을 State 객체에 제공


useDispatch
: Store 에 있는 dispatch 함수에 접근하는 hooks

리덕스 미들웨어
: Action 을 dispatch 전달하고 리듀서에 도달하는 순간 사이에 사전에 지정된 작업을 실행할 수 있게 해주는 중간자.
-> 로깅, 충돌 보고, 비동기 API와 통신, 라우팅 등을 위해 Redux 미들웨어를 사용한다.
리덕스 로깅 미들웨어 생성하기
https://www.freecodecamp.org/news/what-is-redux-middleware-and-how-to-create-one-from-scratch/
1. 로깅 미들웨어 함수 생성

2. 미들웨어 함수를 applyMiddleware 함수에 넣어주기

3. createStore 에서 미들웨어 넣어주기

Redux Thunk
리덕스 Thunk란?
: 리덕스를 사용하는 앱에서 비동기 작업을 할 때 많이 사용하는 방법이 redux-thunk
-> 이것도 앞서 만들어본 logger 미들웨어 처럼 리덕스 미들웨어이다.
비동기 작업을 해야 할 때는 ?
-> 여러 경우가 있지만 서버에 요청을 보내서 데이터를 가져올 때 주로 비동기 요청을 보낸다.
Thunk ?
: "thunk"라는 단어는 "일부 지연된 작업을 수행하는 코드 조각"을 의미하는 프로그래 밍 용어이다.
-> 지금 일부 논리(logic) 를 실행하는 대신 나중에 작업을 수행하는 데 사 용할 수 있는 함수 본문이나 코드를 작성할 수 있다.
Axios 모듈 설치
: Api request를 위한 모듈
npm install axios --save posts
리듀서 생성

posts 데이터를 위한 요청 보내기

-> 에러 발생
에러 발생 이유는 ?

-> 원래 Actions은 객체여야 하는데 현재는 함수를 Dispatch 하고 있다.
=> 그래서 함수를 dispatch 할 수 있게 해주는 Redux-Thunk 미들웨어를 설치해서 사용해보자!
redux-thunk 설치
npm install redux-thunk --save
redux-thunk 적용

posts 데이터 화면에 표출


actions 들은 actions 폴더로 따로 분리

modern ES2015 형태로 변경

=> 이렇게 리덕스 던크를 사용함으로써 액션 생성자가 그저 하나의 액션 객체를 생성할 뿐 아니라, 그 내부 안에서 여러 가지 작업도 할 수 있게 만들어준다.
Redux Toolkit
리덕스 익스텐션이란 ?
: Redux-Devtools는 Redux 앱을 위한 디버깅 플랫폼을 제공한다.
-> 시간 여 행 디버깅 및 라이브 편집을 수행할 수 있다.
-> 공식 문서의 일부 기능은 다음과 같다.
- 모든 상태 및 작업 페이로드를 검사할 수 있다.
- 작업을 "취소"하여 시간을 되돌릴 수 있다.
설치 방법
https://chrome.google.com/webstore/detail/redux
패키지 설치하기
npm install redux-devtools-extension --save
#리덕스 툴킷에서는 devTools를 위한 파라미터가 있는데 기본적으로 사용을 할수있게 설정되어있다
https://redux-toolkit.js.org/api/configureStore#parameters
리덕스 툴킷
: Redux 툴킷은 Redux 로직을 작성하기 위한 공식 권장 접근 방식.
-> Redux 코어 를 둘러싸고 있으며 Redux 앱을 빌드하는 데 필수적이라고 생각하는 패키지와 기능이 포함되어 있다.
-> Redux 툴킷은 제안된 모범 사례를 기반으로 하여 대부분의 Redux 작업을 단순화하고 일반적인 실수를 방지하고 Redux 애플리케이션을 더 쉽 게 작성할 수 있도록 한다.
리덕스 툴킷으로 만들어진 카운터 앱 살펴보기
https://redux-toolkit.js.org/tutorials/quick-star
Quick Start | Redux Toolkit
redux-toolkit.js.org
React에 Redux 스토어 제공
: app/store.ts 저장소가 생성되면 src/index.js에서 애플리케이션 주위에 React-Redux <Provider> 를 배치하여 React 구성 요소에서 사용할 수 있도록 할 수 있다.

-> 방금 만든 Redux 저 장소를 가져오고 주위에 <Provider> 를 배치하고 저장소를 prop으로 전달한다.
Redux State Slice 생성
: src/features/counter/counterSlice.js라는 새 파일을 추가한다.

-> 해당 파일에서 Redux Toolkit의 createSlice API를 가져온다.
스토어에 Slice Reducer 추가
: 리듀서 매개변수 내부에 필드를 정의함으로써 스토어에 이 슬라이스 리듀서 함수를 사용 하여 해당 상태에 대한 모든 업데이트를 처리하도록 지시한다.

React 컴포넌트에서 Redux State 및 Actions 사용


Redux Toolkit APIs
App.tsx
function App() {
const [isTestOpen, setISTestOpen] = useState(true);
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<button onClick={() => setISTestOpen(prev => !prev)}>Toggle</button>
{isTestOpen && <Test />}
<Counter />
</header>
</div>
);
}
features/counter/Test.tsx
import React, { useEffect } from 'react'
import { useAppDispatch } from '../../app/hooks'
import { incrementAsync } from './counterSlice';
const Test = () => {
const dispatch = useAppDispatch();
useEffect(() => {
dispatch(incrementAsync(10));
return () => {
}
}, [])
return (
<div>test</div>
)
}
export default Test
=> 맨 처음에 앱을 실행하면 state변수가 true이다.
=> Test 컴포넌트는 마운트된다. (비동기 요청을 보낸다)
=> 때문에 pending, fulfilled가 보인다.
=> counter 앱에도 10이 증가한다.
비동기 요청 도중에 Test 컴포넌트가 unmount 된다면?
(toggle 버튼을 두 번 빠르게 눌러 생성하자마자 없애보면)
-> 그럼에도 불구하고 잘 작동된다!
=> useEffect() clean up 함수를 통해서 이를 해결할 수 있다!
clean up
1. dispatch()함수는 promise 객체를 반환한다.
2. 그 객체를 받아, clean up 함수에 promise.abort() 를 추가한다.
Test.tsx
import React, { useEffect } from 'react'
import { useAppDispatch } from '../../app/hooks'
import { incrementAsync } from './counterSlice';
const Test = () => {
const dispatch = useAppDispatch();
useEffect(() => {
const promise = dispatch(incrementAsync(10));
return () => {
promise.abort()
}
}, [])
return (
<div>test</div>
)
}
export default Test
=> 컴포넌트 마운트 후 빠르게 바로 언마운트 시키면 비동기 요청이 reject 된다.
abort() 할 때 request 취소하기
counterSlice.tsx
export const fetchUsesAsync = createAsyncThunk(
'counter/fetchUsers',
async() => {
await axios.get("https://jsonplaceholder.typicode.com/users")
}
)
Test.tsx
const Test = () => {
const dispatch = useAppDispatch();
useEffect(() => {
const promise = dispatch(fetchUsesAsync());
return () => {
promise.abort()
}
}, [])
return (
<div>test</div>
)
}
-> rejected가 되었는데, 요청이 보내지고 그에 대한 답이 돌아온다.
=> request 자체를 취소하겠다.
async thunk 함수의 두번째 인자인 thunkAPI 객체를 받는다. 그 후에,
- controller 를 생성
- thunkAPI.signal에 abort에 대한 eventListener를 등록
- axios 요청에 추가
export const fetchUsesAsync = createAsyncThunk(
'counter/fetchUsers',
async(_, thunkAPI) => {
const controller = new AbortController();
thunkAPI.signal.addEventListener('abort', () => {
controller.abort();
});
await axios.get("https://jsonplaceholder.typicode.com/users", {
signal: controller.signal
})
}
)