-
Notifications
You must be signed in to change notification settings - Fork 7
프론트엔드
- TypeScript는 우리 팀에 도움이 되고 있나요? 어떤 측면에서 도움이 되고, 혹은 어떤 측면에서는 어려움이나 불편을 겪고 있나요?
- 미리 선언되어 있는 타입을 통해서 처음 보는 코드를 이해하는 데 도움이 된다.
- 타입을 통해 런타임 전에 타입 오류를 잡을 수 있다.
- 우리 팀에서 TypeScript를 사용할 때 중요하게 생각하는 부분은?
- 타입 네이밍과 올바른 타입 선언(
any
❌) - 동일한 도메인에 대해서 동일한 타입 이용(분산되지 않은 타입 선언)
- 타입 네이밍과 올바른 타입 선언(
Component는 화살표 함수를 이용하여 선언한다.
const Component = () => { return ... };
export default Component;
- 함수 선언 코드 작성의 일관성 유지
-
this
바인딩 관련 문제가 발생하지 않음
type vs interface vs inline
type GetWritingResponse = {
id: number;
title: string;
content: string;
};
// styled-components
const LeftSidebarSection = styled.section<{ isLeftSidebarOpen: boolean }>`
display: ${({ isLeftSidebarOpen }) => !isLeftSidebarOpen && 'none'};
`,
- Type에
interface
를 사용하지 않고type
을 사용-
interface
의 선언 병합(declaration merging)을 통해 타입을 보강(augment)하는 기능을 원하지 않았기 때문
-
-
union type(|)
이 필요한 경우type
을 사용하는데 일관성 유지를 위해 모두type
사용 -
styled-components
의 경우inline
타입을 사용
VFC, FC, PropsWithChildren
-
React.FC
로 children이 있는 props를 사용하려면React.FC<React.PropsWithChildren<Props>>
이런 식으로 사용해야 한다. - 제네릭이 겹쳐서 코드가 복잡해지고,
PropsWithChildren
을 사용하면 더 간단하게 코드를 작성할 수 있고 직관적이다. -
children
을 강제해야 하는 경우PropsWithChildren
을 사용하지 않고Props
타입에 추가한다 - Children, render메서드/컴포넌트 반환 타입(JSX.Element vs React.ReactElement vs ReactNode)
-
ReactNode: 다수의 children 허용(elements, strings, numbers, fragments, portals, null 등)
type ReactNode = | ReactElement | string | number | Iterable<ReactNode> | ReactPortal | boolean | null | undefined
type Props = { children: ReactNode; }; const CustomLayout = ({ children }: Props) => { return <>{children}</>; }; export default CustomLayout;
- 위
children
prop 와 같이 불특정 다수의 Element들을 받아야할 때ReactNode
를 이용한다
- 위
-
React.ReactElement: 다수 children 허용 x
export type Props = { ... icon?: ReactElement; } & ComponentPropsWithRef<'button'>; // Component const Button = ( { ... icon, ...rest }: Props, ref: ForwardedRef<HTMLButtonElement>, ) => { return ( ... {Boolean(icon) && <S.IconWrapper size={size}>{icon}</S.IconWrapper>} ... ); };
위와 같이
icon
컴포넌트 하나가 와야하는 경우ReactElement
를 이용한다. -
JSX.Element: 다수 children 허용 x, props 와 type 의 타입이 any인 ReactElement
-
Event Handler vs Event
const removeTag: MouseEventHandler<HTMLButtonElement> = (event) => { ... };
이벤트 핸들러 이름은 내부 동작을 잘 나타내도록 짓기로 했기 때문에(ex. removeTag) 이벤트 핸들러임을 명시적으로 나타내주기 위해 Event Handler를 사용한다.
-
import 방식
MouseEventHandler
이벤트의
import
방식으론React.[type]
를 사용하지 않고,[type]
만 명시한다.
const [data, setUser] = useState<User | null>(null);
const [writingId, setWritingId] = useState<number | null>(null);
const [nickname, setNickname] = useState<string | null>(null);
string이나 number같이 빈 값을 나타내는 것을 명시적으로 할 때 null이 없으면 애매하다.
RefObject
의 경우 초기 값을 null로 지정한다.
const inputRef = useRef<HTMLInputElement>(null);
MutableRefObject
의 경우 초기 값을 지정하지 않는다.
const count = useRef<number>();
참고(MutableRefObject
, RefObject
타입)
interface MutableRefObject<T> {
current: T;
}
interface RefObject<T> {
readonly current: T | null;
}
- 네이밍 컨벤션
컴포넌트 내의 Props 타입 네이밍은
Props
로 한다.
type Props = {
...
};
-
Props 타입은 컴포넌트에서 선언되므로 네이밍을 굳이
[Component]Props
로 하지 않아도 의미가 명확하다. -
해당 Props 타입이 필요한 곳에서는
as
를 이용해 네이밍을 바꿔서 이용한다.import { Props as WritingViewerProps } from 'components/WritingViewer/WritingViewer';
-
제네릭을 사용할 때, 명시적인 이름을 사용합니다.
// BAD 👎
const customFetch = <T, U>(url: T, options: U) => { ... }
// GOOD 👍
const customFetch = <URL, OPTIONS>(url: URL, options: OPTIONS) => { ... }
- 타입 이름에
Type
을 붙이지 않는다.
// BAD 👎
type ApiType = { ... }
// GOOD 👍
type Api = { ... }
- 타입 이름을 명시적으로 작성하여 이름만 보고도 어떤 타입인지 쉽게 알 수 있다.
- 디렉터리 구조
- `src`폴더 바로 밑에서 성격 별로 정리한다
-
hook 분리
- 하나의 컴포넌트에서만 사용되는 훅은 해당 컴포넌트 내에 위치한다.
- 최상단 훅 폴더
- 라이브러리 성격 (common)
- 도메인 종속적이지만 재사용 가능한 (폴더 없이)
-
import/export 방식
- 컴포넌트는 export default, 나머지는 named export
// 컴포넌트 const Component = () => {}; export default Component; // 나머지 export const useCustomHook = () => {};
- default export를 사용하여 컴포넌트임을 알 수 있게 함
- export 는 그 외의 함수 내보내기 방식, 또한 어떤 함수가 export 되었는지 바로 알 수 있음(DX👍)
-
[Type-Only Import and Exports](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#type-only-imports-and-export) 사용
import type { SomeThing } from "./some-module.js"; export type { SomeThing };
- 타입 임을 명시적으로 드러내기위해 사용
- API 호출 로직에서 Request / Response 데이터를 다루는 방식.
-
types/apis
폴더에 Request & Response 타입을 정의한다.
export type GetWritingResponse = {
id: number;
title: string;
content: string;
};
export type GetWritingPropertiesResponse = {
createdAt: Date;
isPublished: boolean;
publishedAt: Date;
publishedTo: Blog;
};
- TS 컴파일을 위해 어떤 loader를 사용하고 있는지와 선택 이유
-
ts-loader
- 빌드 타임에
tsc
를 이용해서 타입 검사를 해주기 위해 -
babel-loader
의preset/typescript
는 별도의 타입 검사를 수행하지 않음
- 빌드 타임에
- 설정 기준과 설정한 항목들에 대한 이해
{
"compilerOptions": {
"baseUrl": "./src", // 모듈 이름을 해석하기 위한 기본 디렉터리
"paths": { // 절대 경로 설정
"*": ["*"]
},
"target": "ES2021", // ES2021 버전에 대한 코드를 생성
"lib": ["DOM", "DOM.Iterable", "ESNext"], // 컴파일러가 포함해야 하는 라이브러리 목록
"jsx": "react-jsx", // JSX 처리 방식
"module": "ESNext", // 사용할 모듈 시스템
"moduleResolution": "Node", // 모듈 해석 방식
"sourceMap": true, // 소스 맵 생성 여부
"esModuleInterop": true, // CommonJS와 ES Modules간의 호환성 설정
"forceConsistentCasingInFileNames": true, // 파일 이름의 대소문자 일관성을 강제하는 설정
"strict": true, // 엄격한 타입 체크 옵션 설정
"noImplicitAny": true, // 암시적인 'any' 타입에 오류를 발생
"skipLibCheck": true // 타입 체킹 시에 .d.ts 파일을 건너뜀
},
"include": ["src", "__tests__"], // 컴파일할 파일 또는 디렉토리 목록
"exclude": ["node_modules"] // 컴파일에서 제외할 파일 또는 디렉토리 목록
}
동글이 선정한 서비스 타겟 환경과 브라우저 지원 범위를 소개합니다.
- 동글의 주요 기능은 블로그에 쓸 글을 **업로드(import)**하고 올라온 글들을 블로그에 포스팅 하는 서비스
- 모바일 대응도 할 수는 있겠지만 서비스 특성 상 모바일이 핵심 타겟 환경은 아니라고 생각해서 PC 버전을 우선적으로 지원하기로 결정.
- 저희 서비스의 브라우저 지원 범위는 [Browser Market Share Worldwide](https://gs.statcounter.com/browser-market-share/desktop/south-korea) 에서의 브라우저 점유율을 참고하여 정하였습니다.
- 일단 타겟을 개발자로 잡았고 주변 개발자가 타겟층이 될 확률이 높기 때문에 한국으로, 서비스 타겟 환경은 PC를 설정하여 본 결과는 다음과 같습니다.
- 크게 Top3인
Chrome
,Edge
,Whale
을 지원하기로 결정하였습니다.
-
Whale
과Ark
브라우저는 크로미움 기반으로 개발되기 때문에 호환성에 크게 문제가 없을 것이라고 판단하였습니다.
© 2023 dong-gle - All Rights Reserved.