Skip to content

๐Ÿ“š ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ ๋ฌธ์„œ

sแดสŸส™ษช โ˜”๏ธ edited this page Aug 31, 2023 · 6 revisions

๊ธฐ์ˆ  ์Šคํƒ

  • ๋ฆฌ์•กํŠธ
  • ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ
  • ์›นํŒฉ
  • ts-loader
  • styled-components
    • props๋ฅผ ๋ฐ›์•„ ๋™์  ์Šคํƒ€์ผ๋ง ๊ฐ€๋Šฅ
    • ์Šคํƒ€์ผ๊ณผ ๋งˆํฌ์—…์ด ๊ฐ™์ด ์กด์žฌํ•ด์„œ UI๋ณต์žก๋„๊ฐ€ ๋†’์•„์ ธ๋„ ์‰ฝ๊ฒŒ CSS๋ฅผ ์ฐพ์„ ์ˆ˜ ์žˆ์Œ โ†’ ๊ฐ€๋…์„ฑ ๋†’์Œ+์œ ์ง€๋ณด์ˆ˜ ์‰ฌ์›€
    • ๋Ÿฐํƒ€์ž„์— CSS ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑํ•˜๋ฏ€๋กœ CSS in CSS์™€ ๋น„๊ตํ•˜์—ฌ ์†๋„๊ฐ€ ๋Š๋ฆฌ์ง€๋งŒ ์•„์ง ์„œ๋น„์Šค ํฌ๊ธฐ์— ์œ ์˜๋ฏธํ•œ ์ฐจ์ด๋Š” ๋ณด์ด์ง€ ์•Š์„ ๊ฒƒ์ด๋ผ ํŒ๋‹จ

์„œ๋น„์Šค ํƒ€๊ฒŸ ํ™˜๊ฒฝ ๋ฐ ๋ธŒ๋ผ์šฐ์ € ์ง€์› ๋ฒ”์œ„ ์„ ์ •

ํŽธ์˜์ ์œผ๋กœ ์ด๋™ํ•˜๋ฉด์„œ ๊ฐ„ํŽธํ•˜๊ฒŒ ๋ณผ ์ˆ˜ ์žˆ๊ฒŒํ•˜๊ธฐ ์œ„ํ•ด ๋ชจ๋ฐ”์ผ์„ ์šฐ์„ ์ ์œผ๋กœ ์ง€์›ํ•˜๊ธฐ๋กœ ๊ฒฐ์ •
๋ธŒ๋ผ์šฐ์ € ์ง€์› ๋ฒ”์œ„

์ฝ”๋“œ ์ปจ๋ฒค์…˜

export ๋ฐฉ์‹

  • ์ปดํฌ๋„ŒํŠธ๋Š” export default
  • hook์€ exportํ•ด์„œ index.ts์— ์ •์˜ํ•˜๊ธฐ
//correct
const Component=()=> {...};
export default Component;

export const func =()=>{...};

export const useHook=()=>{...};

//incorrect
export const Component=()=> {...};

์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ๋„ค์ด๋ฐ

  • ์–ด๋–ค ๋™์ž‘์„ ํ•˜๋Š”์ง€ ๋ช…์‹œ์ ์œผ๋กœ ์ž‘์„ฑํ•˜๊ธฐ
  • ๋„ค์ด๋ฐ: handle+๋™์ž‘(๋™์‚ฌ)
    • ex) handleLogin, handleRegisterForm

props ๋„ค์ด๋ฐ

  • ์ปดํฌ๋„ŒํŠธ ์ด๋ฆ„+Props
//correct
interface HeaderProps {};

//incorrect
interface Props {};

type vs interface

  • ์œ ๋‹ˆ์˜จ ํƒ€์ž…์€ type
  • ๋‚˜๋จธ์ง€ interface
//correct
type T = 'type' | 'example';
interface I {};

//incorrect
type T = {};

์Šคํƒ€์ผ ์ˆœ์„œ

display
	align-items
	justify-contents
position
	top right bottom left
float
width
height
margin
padding
border
background
font
color
text-decoration
text-align / vertical-align
white-space
other text
content
๊ฐ€์ƒ ์„ ํƒ์ž

import ์ˆœ์„œ

  • eslint๋ฅผ ํ†ตํ•ด ์ •ํ•  ์˜ˆ์ •

styled-components

  • ํ•œ ํŒŒ์ผ์—์„œ ๋งˆํฌ์—…+์Šคํƒ€์ผ ์ •์˜
  • ๊ฐ€์žฅ ํฐ ๋ฌถ์Œ์€ XXContainer , ์ž์ž˜ํ•œ ๊ฒƒ๋“ค์€ XXWrapper

if๋ฌธ

  • ํ•œ ์ค„ ์ด๋”๋ผ๋„ ์ค‘๊ด„ํ˜ธ ํ•„์ˆ˜
//correct
if (ok) {
	return true;
}

//incorrect
if (ok) return true;

๋‚˜๋จธ์ง€ props ๋„ค์ด๋ฐ

const Component = ({ color, children, ...props }) => { ... }

์Šคํƒ€์ผ ๋ถ„๊ธฐํ•  ๋•Œ

const dividerSizeStyles = {
  'default' : css ``,
}

const dividerColorStyles = { ... };

storybook description

์„œ์ˆ ํ˜•์œผ๋กœ ์“ฐ๊ธฐ

color: {
	description: '์ปดํฌ๋„ŒํŠธ์˜ ์ƒ‰์ƒ์ž…๋‹ˆ๋‹ค.'
}

style์€ export ์•„๋ž˜์—

export default Component;

const Container=styled.div``;

interface์— jsdocs ์ถ”๊ฐ€

interface Props {
/**
 * ์ปดํฌ๋„ŒํŠธ์˜ ์ƒ‰์ƒ์ž…๋‹ˆ๋‹ค.
 */
color?: Colors
/**
 * ์ปดํฌ๋„ŒํŠธ์˜ ๊ธธ์ด๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.
 */
length?: string
}

์ปดํฌ๋„ŒํŠธ์˜ ํƒ€์ž… โ†’ variant๋กœ ๋„ค์ด๋ฐ ๊ณ ์ •

interface Component {
	variant: 'outlined' | 'filled'
}

storybook meta

const meta:Meta<typeof Comonent> = { ... }

CSS Props

๋ณธ์ธ์ด ์›ํ•˜๋Š” ์Šคํƒ€์ผ ํƒ€์ž…์ด ์—†์„ ๋•Œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ด๋ ‡๊ฒŒ ํ™•์žฅํ•ด ์“ธ ์ˆ˜ ์žˆ๋‹ค.

<Box
  css={`
    font-size: 16px;
  `}
/>;

StyleProps

interface ComponentProps {
	variant:
	border:
	color:
}

type StyleProps = Pick<ComponentProps, 'variant'| 'border' | 'color'>;

ํด๋” ๊ตฌ์กฐ

๐Ÿ“ฆsrc
 โ”ฃ ๐Ÿ“‚apis
 โ”ฃ ๐Ÿ“‚assets
 โ”ฃ ๐Ÿ“‚components
 โ”ฃ ๐Ÿ“‚constants
 โ”ฃ ๐Ÿ“‚hooks
 โ”ฃ ๐Ÿ“‚mocks
 โ”ฃ ๐Ÿ“‚pages
 โ”ฃ ๐Ÿ“‚router
 โ”ฃ ๐Ÿ“‚styles
 โ”ฃ ๐Ÿ“‚types
 โ”ฃ ๐Ÿ“‚utils
 โ”ฃ ๐Ÿ“œApp.tsx
 โ”— ๐Ÿ“œindex.tsx

TypeScript ์ปจ๋ฒค์…˜

์šฐ๋ฆฌ ํŒ€์—์„œ typescript๋ฅผ ์“ธ ๋•Œ ์ค‘์š”ํ•˜๊ฒŒ ์ƒ๊ฐํ•˜๋Š” ๋ถ€๋ถ„

์ตœ๋Œ€ํ•œ ํƒ€์ž… ์ถ”๋ก ์„ ์‚ฌ์šฉํ•˜๊ธฐ ์ค‘๋ณต๋˜๋Š” ํƒ€์ž…์„ ์ค„์ด๊ธฐ ์œ„ํ•ด ์œ ํ‹ธ๋ฆฌํ‹ฐ ํƒ€์ž… ์‚ฌ์šฉํ•˜๊ธฐ

Component

์„ ์–ธ๋ฐฉ์‹ - ํ•จ์ˆ˜ ํ‘œํ˜„์‹

//correct

const Component = () => {...};

Props - ์ปดํฌ๋„ŒํŠธ ์ด๋ฆ„+Props

//correct

interface HeaderProps {};

//incorrect

interface Props {};

Props๋กœ ๋ช…์‹œํ–ˆ์„ ๋•Œ ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ์˜ Props๊ฐ€ ๊ฒ€์ƒ‰๋˜์–ด ์ฐพ๊ธฐ ์–ด๋ ต๋‹ค. ๋”ฐ๋ผ์„œ ์ปดํฌ๋„ŒํŠธ ์ด๋ฆ„+Props๋กœ ์ •์˜ํ•ด ๋ณด๋‹ค ์ง๊ด€์ ์œผ๋กœ ๋ช…์‹œํ•œ๋‹ค.

type vs interface - ์œ ๋‹ˆ์˜จ ํƒ€์ž…์€ type, ๋‚˜๋จธ์ง€๋Š” interface ์‚ฌ์šฉ

//correct

type T = 'type' | 'example';
interface SomeThing {};

//incorrect

type T = {};

interface์˜ ๊ฒฝ์šฐ ํ™•์žฅ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

Component with Children / Component without Children
์ปดํฌ๋„ŒํŠธ ๋งŒ๋“ค ๋•Œ ComponentPropsWithoutRef
children ๋ฐ›์„ ๋•Œ PropsWithChildren
๋ฆฌ์•กํŠธ ํ‘œ์ค€์œผ๋กœ ์ œ๊ณตํ•ด์ฃผ๊ณ  ์žˆ๊ณ  ๊ฐ„ํŽธํ•˜๊ฒŒ ์“ธ ์ˆ˜ ์žˆ๋‹ค.

Event

// correct

const handleClick: MouseEventHandler<HTMLButtonElement> = (event) => { /*...*/ };

// incorrect

const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => { /*...*/ };

์ด๋ฒคํŠธ ์ž์ฒด์˜ ํƒ€์ž…๋ณด๋‹ค๋Š” ํ•จ์ˆ˜์˜ ํƒ€์ž…์„ ์ง€์ •ํ•ด ๋ช…์‹œ์ ์œผ๋กœ ์“ฐ๊ณ  ์‹ถ์–ด์„œ ์‚ฌ์šฉํ–ˆ๋‹ค.

Hooks

๊ธฐ๋ณธ ํ›…

const [data, setUser] = useState<User>();

ํ˜„์žฌ๊นŒ์ง€๋Š” ์œ„์˜ ๋ฐฉ์‹์œผ๋กœ ์“ฐ๊ณ  ์žˆ์ง€๋งŒ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธด๋‹ค๋ฉด ๋ณ€๊ฒฝํ•  ์˜ˆ์ • Ref

const ref = useRef<HTMLElement>(null);

ํ˜„์žฌ๊นŒ์ง€ ref๋Š” ๋”๊ฐ์ฒด์— ์ ‘๊ทผํ•˜๊ธฐ ๋•Œ๋ฌธ์— null๋กœ ์ดˆ๊ธฐํ™”

๋ชจ๋“ˆ

type ๊ด€๋ฆฌ ๋ฐฉ์‹ types ํด๋” ์•„๋ž˜์— common, products, review ๋“ฑ์˜ ํด๋”๋ฅผ ๋งŒ๋“ค์–ด ์‚ฌ์šฉ ๋‹ค๋ฅธ ํƒ€์ž…๋“ค์— ๋Œ€ํ•ด์„œ๋Š” ๋”ฑํžˆ prefix๋‚˜ suffix๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ

type import/export
//correct
import type { Type } from '@/types/common';
//incorrect
import { Type } from '@/types/commmon';

lint๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ž๋™์œผ๋กœ type import๊ฐ€ ๋˜๊ฒŒ๋” ์„ค์ •

API

Request / Response Type

api response์˜ ๊ฒฝ์šฐ CategoryProductResponse์™€ ๊ฐ™์ด ๋„ค์ด๋ฐ์„ ์ง€์Œ

const useCategoryProducts = (categoryId: number, sort: string = PRODUCT_SORT_OPTIONS[0].value) => {
  return useGet<CategoryProductResponse>(
    () => categoryApi.get({ params: `/${categoryId}/products`, queries: `?page=1&sort=${sort}` }),
    [categoryId, sort]
  );
};

๋นŒ๋“œ ์„ค์ •

loader

ts-loader ์‚ฌ์šฉ

babel-loader๊ฐ€ ์†๋„๋Š” ๋” ๋น ๋ฅด์ง€๋งŒ ํƒ€์ž… ์ฒดํ‚น์„ ํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ์ ๊ณผ ํด๋ฆฌํ•„์ด ํ•„์š”ํ•œ IE๊ฐ€ ์ง€์› ์ข…๋ฃŒ๋˜์—ˆ๋‹ค๋Š” ์ ์„ ๊ณ ๋ คํ•˜์—ฌ ts-loader๋กœ ๊ฒฐ์ •ํ•จ

{
  "compilerOptions": {
    "target": "esnext",
    "lib": ["dom", "dom.iterable", "esnext"], //์ปดํŒŒ์ผ ๊ณผ์ •์—์„œ ์‚ฌ์šฉ๋˜๋Š” JS API์™€ ๊ฐ™์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ง€์ •
    "esModuleInterop": true,
    "module": "esnext", //์ปดํŒŒ์ผ์„ ๋งˆ์นœ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๋ชจ๋“ˆ์ด ์–ด๋–ค ๋ชจ๋“ˆ ์‹œ์Šคํ…œ์„ ์‚ฌ์šฉํ•  ์ง€
    "moduleResolution": "node",  //node์ „๋žต์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ชจ๋“ˆ ํƒ์ƒ‰
    "resolveJsonModule": true, //json ํŒŒ์ผ import ํ—ˆ์šฉ
    "forceConsistentCasingInFileNames": true, //ํŒŒ์ผ ์ด๋ฆ„์˜ ๋Œ€์†Œ๋ฌธ์ž ๊ตฌ๋ถ„
    "strict": true,
    "skipLibCheck": true, //๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ๋Œ€ํ•ด์„œ๋Š” ํƒ€์ž… ์ฒดํ‚นx (์ปดํŒŒ์ผ ์‹œ๊ฐ„ ์ค„์ผ ์ˆ˜ ์žˆ์Œ)
    "jsx": "react-jsx", //tsx๋ฅผ ์–ด๋–ป๊ฒŒ ์ปดํŒŒ์ผ ํ•  ์ง€ ๋ฐฉ์‹์„ ์ •ํ•จ
    "outDir": "./dist",
    "typeRoots": ["node_modules/@types", "src/types"], //ํƒ€์ž…์˜ ์œ„์น˜ ์ง€์ •
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  },
  "include": ["src"], //์ปดํŒŒ์ผ์— ํฌํ•จ
  "exclude": ["node_modules", "dist"] //์ปดํŒŒ์ผ์— ์ œ์™ธ
}

๐Ÿ” ๊ณตํ†ต

๐Ÿ”‘ ํ”„๋ก ํŠธ์—”๋“œ

๐Ÿ”’ ๋ฐฑ์—”๋“œ

๐Ÿ“ ํšŒ์˜๋ก

๐Ÿคฉ ๋ฐ๋ชจ๋ฐ์ด

Clone this wiki locally