React

10. 리덕스

기 도 2023. 6. 20. 17:44

리덕스란?

리덕스 (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

 

Store | Redux

API > Store: the core Redux store methods

redux.js.org

 

: 애플리케이션의 현재 상태 트리를 반환한다.

 

-> subscribe()

: change listner 를 추가한다.

 

-> 작업이 전달될 때마다 호출되며 상태 트리의 일부가 잠재적으로 변경되었을 수 있다.

=> 다음 getState() 를 호출하여 콜백 내부의 현재 상태 트리를 읽을 수 있다.

 


combineReducers / Todo 기능 추가

root reducer와 sub reducer

: 현재까지 counter 리듀서만 있는데 하나를 더 추가해주려면 Root 리듀서를 만들어서 그 아래 counter와 todos라는 서브(sub) 리듀서를 넣어준다.
#Root 리듀서를 만들 때 사용하는 것이 combineReducers
 
=> reducers 폴더 안에 counter.tsx , index.tsx , todo.tsx 생성
 

 

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

 
-> useSelector Hooks 를 이용해서 스토어의 값을 가져올 수 있다.
 

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

컴포넌트 생성후 thunk 호출하기

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 객체를 받는다. 그 후에,

  1. controller 를 생성
  2. thunkAPI.signal에 abort에 대한 eventListener를 등록
  3. 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
    })
  }
)