favicon

Jayden { do: smite }

231108(수)

🌱 성장일지 8.0

행복한 이기주의자(웨인 다이어)의 내용에 자극받아 시작하는 소박한 성장기록

  • 살아있는 꽃과 죽은 꽃은 어떻게 구별하는가?
  • 성장하고 있는 것이 살아 있는 것이다.
  • 생명의 유일한 증거는 성장이다!

⚛ (8.0)<완전 개편> 그 날의 키워드 중심으로 간단하게 정리하되 매일 꾸준히 작성할 수 있는 공간을 만들어보자.

복기하며 공부하기

오늘 면접 본 내용을 복기하며 기록해둔다!!!

TypeScript의 Enum

Enum은 어떤 용도인가?

  • 열거형으로 TypeScript가 제공하는 기능이다.
  • 열거형으로 이름있는 상수들의 집합을 정의할 수 있다.
  • 개발자의 의도를 알기 쉽게 만들어준다.
// 가장 흔한 사용법 enum Direction { Up = 1, Down, // 2 Left, // 3 Right, // 4 }
enum UserResponse { No = 0, Yes = 1, }
enum Direction { Up = 'UP', Down = 'DOWN', Left = 'LEFT', Right = 'RIGHT', }

자 그렇다면 과연 Enum을 사용하는 게 좋을까?

사실 그 동안 프로젝트를 하면서 학습의 목적을 두고 한 두번 사용해본 적은 있었다. 하지만 프로젝트 규모가 작아서인지 솔직히 Enum으로 상수들의 결합도를 높이는 게 아주 크게 의미가 있다고 느끼지 못했던 것 같다. 개인적으로는 JS의 Object.freeze()를 사용하는 게 더 직관적이고 편리한 느낌..? 그렇지만 과연 이게 답이 될까..? TS를 사용하면서 제공하는 기능을 사용하지 않으려면 좀더 명확한 근거가 있어야 할 것 같다. 그러던 중 tree shaking과 관련한 글을 찾게 되었다.(맨 아래에 참고 링크를 남겨두었다.)

Tree Shaking

Tree Shaking은 간단하게 말하면 사용하지 않는 코드를 제거해주는 기능이다. 마치 나무를 흔들어서 불필요한 잎사귀를 떨어뜨리는 것과 같다. 주로 Webpack이나 Rollup 같은 번들러에서 번들링을 하면서 사용하지 않는 코드를 제거해주는 기능이다.

그렇다면 이게 왜 Enum을 사용하지 않는 게 좋은 이유일까?

바로 Enum은 Tree Shaking이 되지 않는다. 타입스크립트 플레이그라운드에서 아래와 같은 코드를 작성해보자.

enum Direction { UP = 1, DOWN, LEFT, RIGHT, }

그리고 JS로 변환된 파일을 보자

'use strict'; var Direction; (function (Direction) { Direction[(Direction['UP'] = 1)] = 'UP'; Direction[(Direction['DOWN'] = 2)] = 'DOWN'; Direction[(Direction['LEFT'] = 3)] = 'LEFT'; Direction[(Direction['RIGHT'] = 4)] = 'RIGHT'; })(Direction || (Direction = {}));

위의 즉시실행함수를 보면 처음 Direction은 undefined로 초기화되어있기 때문에 빈 객체가 파라미터로 전달된다. 그리고 그 파라미터에 UP, DOWN, LEFT, RIGHT가 추가되는 것을 볼 수 있다. 즉, 이렇게 되면 즉시실행함수가 호출이 되었으므로 번들러 입장에서는 사용되는 코드로 인식하게 된다. 그래서 Tree Shaking이 되지 않는 것이다.

그렇다면 어떤 코드를 사용하는 게 좋을까?

정답은 바로 Union Types를 사용하는 것이다. Union Types는 여러 타입 중 하나의 타입을 지정할 수 있는 타입이다. 아래의 코드를 보자.

const MOBILE_OS = { IOS: 'iOS', Android: 'Android', } as const;

위의 코드는 아래와 같이 변환된다.

'use strict'; const MOBILE_OS = { IOS: 'iOS', Android: 'Android', };

즉, 번들러 입장에서 위의 코드가 사용되지 않는다면 Tree Shaking이 되어서 번들링 과정에서 제거된다. 그리고 이렇게 사용하면 Enum을 사용했을 때보다 더 직관적이고 가독성이 좋아진다.

Context API

React의 Conext API는 어떤 용도인가?

일단은 말그대로 문맥을 전달한다. 즉, 거리가 먼 부모 컴포넌트로부터 자식 컴포넌트로 데이터(props)를 전달할 때 사용한다.

일반적으로 우리는 Context API를 useState 혹은 useReducer와 함께 사용한다. 그러다보니 자연스럽게 Context API는 전역 상태관리를 위해 사용한다고 생각하게 된다. 하지만 Context API는 전역 상태관리를 위해 사용하는 것이 아니라 문맥을 전달하기 위해 사용하는 것이다. 그래서 전역 상태관리를 위해 사용하려면 Context API를 사용하는 것보다는 Redux나 jotai, recoil, zustand 같은 상태관리 라이브러리를 사용하는 게 더 좋다. (왜냐하면 Context API는 리렌더링이 발생할 때마다 모든 자식 컴포넌트들이 리렌더링 되기 때문이다. 그래서 전역 상태관리를 위해 Context API를 사용하면 성능상의 문제가 발생할 수 있다.)

useMemo와 useCallback

useMemo와 useCallback은 어떤 용도인가?

  • useMemo: 특정 값이 바뀌었을 때만 연산을 실행하고 그렇지 않으면 이전에 연산한 결과를 재사용한다.
  • useCallback: 특정 함수를 새로 만들지 않고 재사용하고 싶을 때 사용한다.

그렇다면 useMemo와 useCallback을 사용할 때 주의할 점은 무엇일까?

바로 의존성에 참조형태의 데이터를 넣었을 때, 얕은 비교를 한다는 점이다. 그래서 의존성에 참조형태의 데이터를 넣었을 때는 꼭 주의해야 한다. 정말 단순한 예시로 아래와 같은 코드가 있다.

useMemo(() => 123, [{}])

위의 코드에서 의존성에 빈 객체를 넣었을 때, 얕은 비교를 하기 때문에 항상 새로운 값을 반환한다. 새로운 {}과 기존의 {}는 생김새만 같을 뿐 그 메모리값이 다르기 때문이다. 그래서 이런 경우에는 useMemo를 사용하는 것이 아무런 의미가 없다. 그래서 이런 경우에는 useMemo를 사용하지 않는 것이 더 좋다.

이런 예시는 react의 모든 hook에서 동일하게 적용된다. 그래서 의존성에 참조형태의 데이터를 넣었을 때는 꼭 주의해야 한다.

Redux의 ducks pattern

Redux ducks pattern은 redux의 디자인 패턴 중 하나이다. 정말 간단하게 말하면 action, reducer, action creator를 한 파일에 몰아넣는 것이다. 이렇게 하면 파일을 하나만 보고도 해당 모듈의 action, reducer, action creator를 한 눈에 볼 수 있어서 편하다. 또한, action type을 정의할 때, action type의 prefix를 정의해두면 action type의 중복을 막을 수 있다.

아래는 공부한 내용을 토대로 작성해본 예시 코드

// Action Types export const ACTION_TYPE_ADD_TODO = "react-n-redux/todo/ADD_TODO"; export const ACTION_TYPE_REMOVE_TODO = "react-n-redux/todo/REMOVE_TODO"; export const ACTION_TYPE_REMOVE_ALL = "react-n-redux/todo/REMOVE_ALL"; // Reducer const initialState = []; const todoReducer = (state = initialState, action) => { switch (action.type) { case ACTION_TYPE_ADD_TODO: return state.concat(action.text); case ACTION_TYPE_REMOVE_TODO: return state.slice(0, -1); case ACTION_TYPE_REMOVE_ALL: return []; default: return state; } }; export default todoReducer; // Action Creators export const addTodoActionCreator = (todo) => { return { type: ACTION_TYPE_ADD_TODO, text: todo, }; }; export const removeTodoActionCreator = () => { return { type: ACTION_TYPE_REMOVE_TODO, }; }; export const removeAllActionCreator = () => { return { type: ACTION_TYPE_REMOVE_ALL, }; }; // Side Effects: 현재는 따로 없음

Redux의 ducks 패턴과 React Query

GPT를 사용해서 얻은 답변

Redux의 Ducks 패턴
Ducks 패턴은 Redux의 액션 타입, 액션 생성자, 리듀서를 하나의 파일로 조합하는 방식입니다. 이 패턴은 Redux 애플리케이션에서 관련 있는 로직을 한 곳에 모아 관리하기 위해 고안되었습니다. 일반적인 Redux 구조에서는 액션 타입, 액션 생성자, 리듀서가 각각 다른 파일로 분리되어 있는데, 이를 하나의 파일로 모으는 것이 Ducks 패턴의 핵심입니다.

React Query
React Query는 서버 상태 관리를 위한 라이브러리입니다. 데이터 fetching, caching, synchronization 및 업데이트를 처리하여 개발자가 서버 상태와 관련된 복잡성을 쉽게 관리할 수 있도록 도와줍니다. React Query를 사용하면 서버의 데이터를 가져오고, 캐시하고, 동기화하는 작업을 쉽게 처리할 수 있으며, 이를 위해 Redux와 같은 상태 관리 라이브러리를 사용할 필요가 없습니다.

Ducks 패턴과 React Query의 연관성
이 두 개념을 연관지어 설명하면, Redux의 Ducks 패턴은 주로 클라이언트 측 상태를 효율적으로 관리하기 위한 구조적인 패턴이며, React Query는 서버와의 비동기 통신과 관련된 데이터를 관리하는 데 초점을 맞추고 있습니다.

Ducks 패턴을 사용하면 Redux를 통해 애플리케이션의 상태를 관리할 때 보다 깔끔하고 모듈화된 구조를 가질 수 있습니다. 예를 들어, 사용자 인증, UI 상태, 폼 상태 관리 등의 클라이언트 상태를 Ducks 패턴을 이용하여 관리할 수 있습니다.

React Query는 서버에서 데이터를 가져오는 작업을 단순화하고 최적화합니다. 예를 들어, 데이터를 가져오기(fetch), 캐시 관리, 데이터 동기화, background updates와 같은 작업을 효율적으로 처리할 수 있습니다.

결국, 이 두 라이브러리/패턴은 상호 보완적으로 사용될 수 있습니다. Ducks 패턴을 사용한 Redux로는 클라이언트 상태를 관리하고, React Query로는 서버 상태(데이터 fetching 및 caching 등)를 관리합니다. 복잡한 애플리케이션에서는 Ducks 패턴을 적용한 Redux가 애플리케이션의 전역 상태 관리를 위해 사용되고, React Query는 서버와의 상호 작용을 위해 사용되어 각각의 복잡성을 분리하고 전체적인 상태 관리 구조를 단순화할 수 있습니다.

📝 회고

에고고... 나름의 기준을 갖고 잘 대답을 했다고 생각했는데 이렇게 정리하면서 보니 아쉬운 부분이 많다. 그래도 정말 좋은 질문과 면접 경험으로 더 발전할 수 있는 계기가 된 것 같아서 기분이 좋다. 그리고 내가 모르고 있는 게 이렇게나 많다는 생각을 하니 다시 개발 공부에 대한 열정이 생기는 것 같다!!!

참고

Copyright 2023. all rights reserved by Jayden