[낙서 #3] 2022년 1월 24일

낙서·2022년 1월 24일
0

낙서

목록 보기
3/22

공부 주제

redux middleware, redux toolkit, redux-saga, redux-thunk 등의 이해

글 목록

React-Query 살펴보기
https://maxkim-j.github.io/posts/react-query-preview

Redux Toolkit (리덕스 툴킷)은 정말 천덕꾸러기일까?
http://blog.hwahae.co.kr/all/tech/tech-tech/6946/

Redux Toolkit을 활용한 React 상태 관리
https://blog.rhostem.com/posts/2020-03-04-redux-toolkits

redux toolkit 공식 문서
https://redux-toolkit.js.org/api/configureStore

Redux Toolkit

Redux toolkit은 Redux 로직을 작성하는 표준적인 방식을 제공하고,
Redux를 사용하며 접하면서 마주칠 수 있는 문제들의 해결을 위해 만들어졌다.

Redux를 사용하면서 마주칠 수 있는 3가지 문제
1. 스토어를 구성하는 것이 복잡하다.
2. 추가로 설치해야 할 라이브러리들이 많다. (Redux Thunk, Redux DevTools, Redux Actions 등)
3. 많은 boiler plate code가 필요하다.
(boiler plate code: 필요한 기능인데 반복적인 코드를 필요로하여 만들어진 중복된 코드들)

Redux Toolkit은 RTK Query를 제공하기도 한다. Redux toolkit가 가진 여러 기능들에 대해 공부 해 보도록 하자.

RTK Query

Redux Tool Kit 이 가지고 있는 Data fetching 기능

기존 프로젝트에 Redux Toolkit 적용

최근 작업하는 중인 Image cropper 프로젝트는 create-react-app typescript 템플릿으로 작성되었고 redux가 추가되어 있었다. 여기에 Redux Toolkit을 추가하고 configureStore와 createAction을 사용했다. configureStore는 thunk와 devTools를 내장하고 있기 때문에 더 이상 필요 없어진 Redux Thunk와 Redux DevTools 모듈을 제거해주었다. 코드가 간소화되어졌고 이 맛에 Redux Toolkit을 사용하는구나라는 생각이 들었다.

configureStore

configureStore를 사용하면 devTools와 thunk를 기본적으로 제공해서 따로 추가해주지 않아도 된다.

createAction

action creator 함수 선언, action type 선언을 한번에 해준다.

gitKraken

기존 회사에서는 webStorm을 사용했기 때문에 git을 GUI로 쉽게 이용 할 수 있었다.
이제는 VS code를 사용하게 되면서 code diff를 보기가 어려워졌다.
git 활용을 좀 더 잘 하기 위해 gitKraken을 사용하기 시작했다.
UI도 잘되어있고 유용한 것 같다. 재미있는 프로그램인 것 같다.

Redux Saga

Redux Thunk 와 마찬가지로 비동기적인 flow 처리를 도와준다. Redux Thunk보다 테스트 시에 용이하다고 한다.

Redux Saga Effect

takeEvery: 첫번 째 인자로 들어온 type이 dispatch 될 때마다 fetchUser generator 함수가 실행된다.

function* mySaga() {
	yield takeEvery("USER_FETCH_REQUESTED", fetchUser);
}

put: dispatch effect를 만들어낸다.

yield put({type: 'USER_FETCH_SUCCEEDED', payload: userData.json()});

call: api호출이나 함수 호출을 직접하지 않고 call의 파라미터로 함수를 전달한다.

const products = yield call(Api.fetch, '/products')

Api.fetch 함수에 인자로 '/products'를 넣어 실행시키고 완료될 때까지 기다린다.

Redux Persist

redux store를 localStorage 또는 session에 저장 할 때 사용하는 라이브러리

React Query

Data Fetching 라이브러리, 캐싱, 에러핸들링 등을 편하게 하기 위해 사용

React Query의 QueryClient

React Query를 사용하기 위해서는 사용하고자 하는 컴포넌트를 QueryClientProvider 컴포넌트로 감싸주고 QueryClient 값을 Props로 넣어줘야 한다.
앱 전체에서 사용하려면 최상위 컴포넌트에 감싸주면 된다.

[포스트 읽기] React Query와 함께하는 API 에러 처리 설계하기

http://blog.hwahae.co.kr/all/tech/tech-tech/7867/

주제: API 에러 처리
목표: 일관된 API 에러 처리 방법 구축
환경: React, React Query(API 요청, onError 이벤트 리스너로 에러 감지)

에러 처리 흐름

  1. 에러 발생: React Query의 onError 이벤트 리스너로 API 에러 상황 감지, 에러 객체 획득
  2. 에러 종류 파악: 에러를 파싱하여 서비스 표준 에러 응답인지, 네트워크 에러인지 판단
  3. 상황별 에러 처리: HTTP Status와 서비스 표준 에러 Code로 분기하여 상황별 에러 처리 로직 수행, 만약 Alert를 띄워야 하는 에러 상황이 있다면 상황별 에러 처리 로직에서 적절한 메시지를 지정하여 Alert를 띄운다.
  4. 공통 처리: 모든 에러 상황에서 동일하게 수행해야 하는 로직 수행, 센트리 등으로 에러 로그 발송

구현 설계 - 에러 처리 Hook

코드들을 가급적 분산시키지 않고 모아서 작성,
React를 이용하고 있으므로 Hook으로 에러 처리 흐름의 주요 로직 작성
커스텀 Hook의 이름은 useApiError로 작성하였음

  1. React Query의 QueryClient를 초기화 할 때, Default Error Handler를 전달하기 위해 Hook 사용
import { QueryClient } from 'react-query';

// ...

const { handleError } = useApiError();

const queryClient = new QueryClient({
  defaultOptions: {
    onError: handleError,
  }.
});

// ...
  1. 개별 컴포넌트에서 특정 HTTP Status와 서비스 표준 에러 Code에 실행 할 로직을 지정하고 싶다면 Hook을 사용 할 때 인자로 Handler 함수를 전달한다. 아래 코드는 (HTTP Status: 409, 서비스 표준 에러 Code: 10001)과 (HTTP Status: 500) 상황에서 개별 핸들러를 사용하는 예시
import { useQuery } from 'react-query';

// ...

// HTTP Status가 409이면서 서비스 표준 에러 Code가 10001일 때, 실행 할 핸들러 함수
const errorHandler40910001 = () => {
  // 컴포넌트의 상황에 맞게 처리 로직을 작성
}
// HTTP Status가 500일 때, 실행 할 핸들러 함수
const errorHandler500 = () => {
  // 컴포넌트의 상황에 맞게 처리 로직을 작성
}

const { handleError } = useApiError({
  409: {
    10001: errorHandler40910001,
  },
  500: {
    default: errorHandler500,
  },
});

const { isLoading, error, data, isFetching } = useQuery('key', fetchData, { 
  onError: handleError, 
});

// ...

React Query의 onError의 값으로는 기본적으로 QueryClient를 생성 할 때 설정한 핸들러가 사용되지만 useQuery나 useMutation Hook을 사용 할 때, option으로 onError에 새로운 핸들러를 설정하면 기본값을 덮어쓴다. 그래서 위와 같이 에러 처리 Hook을 사용하면 모든 에러에 기본적인 에러 처리 흐름을 적용하면서 재정의가 필요한 부분만 상황에 따라 추가 할 수 있다.

Hook 내부에서는 상황 별로 어떤 에러 핸들러를 수행 할 지 결정하는 부분이 가장 주요하다. 그 결정은 아래 조건에 따른다.

에러 발생 시 실행 할 가능성이 있는 핸들러는 5가지가 있다. 나열 순서는 실행 우선 순위이다.
1. 컴포넌트에서 (HTTP Status, 서비스 표준 에러 Code) Key 조합으로 재정의한 핸들러
2. 컴포넌트에서 (HTTP Status) Key로 재정의한 핸들러
3. Hook에서 (HTTP Status, 서비스 표준 에러 Code) Key 조합으로 정의한 핸들러
4. Hook에서 (HTTP Status) Key로 정의한 핸들러
5. 어디에서도 정의되지 못한 에러를 처리하는 핸들러

위의 조건을 예시 코드로 작성해보면 아래와 같습니다.

/** useApiError.ts */
// 예시 코드로, 주요한 부분만 추출하였습니다.

// 기본 핸들러 예시, 특정 HTTP Status와 서비스 표준 에러 Code 일 때, 전역적으로 적용하기로 사전 정의한 핸들러들입니다.
const defaultHandlers = {
  common: commonHandler,
  default: defaultHandler,
  401: {
    default: handler401,
  },
  403: {
    default: handler403,
  },
  409: {
    10001: handler40910001,
    10002: handler40910002,
  },
  500: {
    default: handle500,
  },
};

// 매개변수 handlers: 컴포넌트에서 재정의한 핸들러 모음
const useApiError = (handlers) => {
  // ... 
  
  // 우선순위에 따른 핸들러의 선택과 실행
  const handleError = useCallback((error) => {
    const httpStatus = error.status; // HTTP Status
    const serviceCode = error.response.meta.code; // 서비스 표준 에러 Code
    
    switch(true) {
      case handlers && handlers[httpsStatus][serviceCode]:
        // 우선순위 1. 컴포넌트에서 (HTTP Status, 서비스 표준 에러 Code) Key 조합으로 재정의한 핸들러
        handlers[httpsStatus][serviceCode]();
        break;
      case handlers && handlers[httpsStatus]:
        // 우선순위 2. 컴포넌트에서 (HTTP Status) Key로 재정의한 핸들러
        handlers[httpsStatus].default();
        break;
      case defaultHandlers[httpsStatus][serviceCode]:
        // 우선순위 3. Hook에서 (HTTP Status, 서비스 표준 에러 Code) Key 조합으로 정의한 핸들러
        defaultHandlers[httpsStatus][serviceCode]();
        break;
      case defaultHandlers[httpStatus]:
        // 우선순위 4. Hook에서 (HTTP Status) Key로 정의한 핸들러
        defaultHandlers[httpStatus].default();
        break;
      default:
        // 우선순위 5. 어디에서도 정의되지 못한 에러를 처리하는 핸들러
        defaultHandlers.default();
    }
    
    // 공통 처리 로직 수행
    defaultHandlers.common();
  }, [handlers]);
  
  // ...
  return { handleError };
};

[포스트 읽기][DevOps] MonoRepo란?

https://velog.io/@sms8377/DevOps-MonoRepo%EB%9E%80

MonoRepo는 Monolithic Repositories의 약자로,
하나의 리포지토리에서 여러개의 프로젝트가 구성된 것을 의미

일반적으로는 보통 하나의 리포지토리 안에는 하나의 프로젝트가 들어있다.
이러한 일반적인 형태의 프로젝트 구성을 Multi-repo, PolyRepo라고 한다.

MonoRepo를 사용하는 이유

MonoRepo를 사용하는 이유는 여러가지가 있다. 장점만 있지는 않기에 프로젝트 목적 및 환경 등 여러가지 조건을 고려해서 결정하면 좋다.

장점 1. 하나의 리포지토리로 여러개의 프로젝트 관리

하나의 리포지토리가 여러개의 프로젝트를 포함하고 있는 것은 큰 편의성을 가진다.
코드를 짜는 입장에서도 IDE에서 프로젝트를 스위칭해가며 개발 할 필요 없이 하나의 IDE 창에서 하위 폴더로 구분된 여러 패키지들의 코드를 작성 할 수 있다.

장점 2. 중첩되는 코드의 공통화

A, B, C, D 패키지로 구성된 MonoRepo가 있고, 여러 프로젝트들이 공통으로 사용해야 하는 로직이 있을 때, 이를 쉽게 추가적인 E 패키지로 분리하고, A~D 패키지에서 import 하여 사용 할 수 있다.

장점 3. 중첩되는 모듈은 하나만 설치해서 사용

A, B, C, D 패키지 모두 node 16.1.0 버전을 사용한다고 가정하면,
각각의 패키지에 node를 설치 할 필요 없이, root path에만 node를 설치하고 A~D에서 끌어다 쓸 수 있다.

단점 1. Dependency 충돌 문제

특정 패키지가 특정 버전의 모듈을 필요로 하는 경우, 다른 버전의 모듈을 사용하는 패키지와 Dependency 충돌이 발생 할 수 있다.

단점 2. 단일 리포지토리 문제

여러 프로젝트를 하나의 리포지토리에서 관리하기 때문에 오히려 관리가 어려워 질 수 있다.
MonoRepo로 관리하는 패키지가 많지 않을 경우에는 해당되지 않지만, 관리하는 패키지가 증가함에 따라 오히려 가독성이나 여러가지 측면에서 비효율적이게 될 수 있습니다.

단점 3. 긴 초기 프로젝트 설정시간

MonoRepo로 포함되는 모든 프로젝트를 사용한다면 상관없지만, 그 중 일부만 필요한 경우에도 node_module 설치가 이루어져야 합니다.

MonoRepo를 구성하는 여러가지 방법

MonoRepo를 구성하는 방법은 아래와 같이 여러가지 방법이 있습니다.

  1. yarn workspace
    node package manager 중에 하나인 yarn 에서는 workspace 기능을 통해서 MonoRepo를 가능하게 해줍니다.

  2. Lerna
    Lerna는 node module로, yarn의 workspace와 같은 역할을 합니다.
    MonoRepo를 가능하게 해주는 기능을 제공함과 동시에,
    설치된 의존성을 제거해주는 clean 기능이나,
    MonoRepo로 구성한 package를 npm 배포 할 수있는 기능들을 제공합니다.

yarn workspace로 MonoRepo 구성하기

yarn의 workspace 기능을 통해서 MonoRepo를 구성하는 방식은 아주 간단합니다.

yarn init 

위 커맨드를 통해서 Project를 초기화하고,
package.json에 아래와 같이 privateworkspaces Property를 설정하면 됩니다.

// package.json
{
  ...
  "private": true,
  "workspaces": [
    "packages/*"
  ],
  ...
}

Dependency 관리 방식

위 방식대로 설정을 마치고, 패키지를 구성한 뒤 의존성을 설치하게 되면, 패키지 각각의 패키지에서 요구하는 모듈의 버전을 확인하여 공통되는 부분은 root의 node_modules 폴더 안에 설치를 하고 각각의 패키지로 연결하여 사용하면 됩니다.

Sentry?

프론트엔드 에러 로그 시스템
오류가 발생한 클라이언트 장비에 원격 접속하여 직접 웹 브라우저를 확인 할 필요 없이 오류가 발생하면 메일이나 슬랙 메시지를 통해 오류 파악이 가능하다.

React ErrorBoundary

리액트 공식문서 - Error Boundary - https://ko.reactjs.org/docs/error-boundaries.html
일부분의 에러가 전체 애플리케이션을 중단시키면 안되기 때문에 이 개념이 도입되었다.

Error Boundary는 하위 컴포넌트 트리의 어디에서든 자바스크립트 에러를 기록하며 깨진 컴포넌트 트리 대신 풀백 UI를 보여주는 React 컴포넌트이다. 렌더링 도중 라이프사이클 혹은 그 하위에서 에러를 잡아낸다.

단 다음 에러는 포착하지 않는다.

  • 이벤트 핸들러
  • 비동기적 코드
  • 서버 사이드 렌더링
  • 자식에서가 아닌 에러 경계 자체에서 발생하는 에러

React Suspense

리액트 공식 문서 - https://reactjs.org/docs/concurrent-mode-suspense.html
Suspense는 무엇인가 렌더 되기 전에 컴포넌트가 기다리게 한다. 비동기 api 호출을 기다릴 수 있다.

History API

DOM window 객체는 history 객체를 통해 브라우저의 세션 기록에 접근 할 수 있는 방법을 제공,
사용자를 자신의 방문 기록 앞과 뒤로 보내고 기록 스택의 콘텐츠도 조작 할 수 있는 유용한 메서드와 속성을 가진다.

다음 공부는?

Redux Router 세부 기능들에 대해서 공부해보고 아래 잘 모르는 키워드들에 대한 조사, 그리고 블로그 포스트들을 읽기, Next.js, MobX

What are these?

precompiled UMD package?
UMD (Universal Module Definition)

포스트 읽기

JavaScript 표준을 위한 움직임: CommonJS와 AMD
https://d2.naver.com/helloworld/12864

화해 기술블로그 정독 - 프론트엔드 관련 좋은 정보
http://blog.hwahae.co.kr/

기억보단 기록을 기술블로그 Redux 정독 - redux 관련 좋은 정보
https://kyounghwan01.github.io/blog/React/redux/redux-persist/#%E1%84%89%E1%85%A9%E1%84%80%E1%85%A2-%E1%84%89%E1%85%A1%E1%84%8B%E1%85%AD%E1%86%BC%E1%84%92%E1%85%A1%E1%84%82%E1%85%B3%E1%86%AB-%E1%84%8B%E1%85%B5%E1%84%8B%E1%85%B2

profile
Deprecated

0개의 댓글