Skip to content

[FE] 개발 문서

Soyeon Choe edited this page Jul 18, 2024 · 7 revisions

1️⃣ 코드 컨벤션

1. 네이밍

  • 객체, 함수, 인스턴스의 이름은 camelCase로 한다.

    // bad
    const haeng_dong = '행동대장';
    
    // good
    const haengDong = '행동대장';
  • 컴포넌트, 생성자, 클래스, 타입, 인터페이스명은 PascalCase 로 작성한다.

    // bad
    const userCard = () => {
    	return <div />
    }
    
    // good
    const UserCard = () => {
    	return <div />
    }
    
    // good
    interface Props {
    	...
    }
  • 상수명은 UPPER_CASE로 한다.

    // bad
    const lunchTime = '11시 30분';
    
    // good
    const LUNCH_TIME = '11시 30분';

    상수로 선언된 객체의 프로퍼티는 camelCase로 작성한다.

    // bad
    const CAFE = {
    	AMERICANO: 3800,
    	LATTE: 5000,
    };
    
    // good
    const CAFE = {
    	americano: 3800,
    	latte: 5000,
    };
  • 이름에 축약어를 사용하지 않는다.

    // bad
    const getBgColor = '#333333';
    
    // good
    const getBackgroundColor = '#333333';
  • api 함수를 만들 때 http method를 접두사에 작성한다.

    api 함수: 순수 api를 호출하는 함수를 말한다.

    // bad
    const sendProfile = () => {
    	...
    }
    
    // bad
    const profilePost = () => {
    	...
    }
    
    // good
    const postProfile = () => {
    	...
    }
  • createHTML같은 기본으로 제공되는 webAPI의 경우는 카멜 케이스를 지키지 못하지만, 우리들이 자체적으로 HTML과 같은 이름을 네이밍에 넣을 경우 카멜 케이스를 사용한다.

    // bad
    const ourHTML = // ...;
    
    // good
    const outHtml = // ...;

2. 객체(Object)

  • 객체를 생성할 때 리터럴 구문을 사용한다.

    // bad
    const obj = new Object();
    
    // good
    const obj = {};
  • 객체 프로퍼티가 2개 이상일 때는 개행한다.

    // bad
    const obj = { a: 1, b: 2 };
    
    // good
    const obj = {
    	a: 1, 
    	b: 2,
    };
  • 프로퍼티 값 단축형을 사용한다.

    // bad
    const soha = 'soha';
    
    // bad
    const crew = {
    	soha: soha,
    };
    
    // good
    const crew = {
    	soha,
    };
  • 객체 선언부에 단축형을 그룹화한다.

    const soha = 'soha';
    const todari = 'todari';
    
    // bad
    const crews = {
    	soha,
    	cookie: '쿠키',
    	todari,
    	weadie: '웨디',
    };
    
    // good
    const crews = {
    	soha,
    	todari,
    	cookie: '쿠키',
    	weadie: '웨디',
    };
  • 객체의 프로퍼티를 새로운 변수에 할당할 경우 아래처럼 사용한다.

    const {name: AName} = user;

3. 콤마(,)

  • 항상 후행 쉼표를 붙인다.

    // bad
    const crews = {
    	soha,
    	todari
    };
    
    // good
    const crews = {
    	soha,
    	todari,
    };
  • 콤마 다음 값이 올 경우 공백을 넣는다.

    // bad
    const crews = ['todari','soha'];
    
    // good
    const crews = ['todari', 'soha'];

4. 문자열(String)

  • 문자열은 기본적으로 single quotes 를 사용한다.

    // bad
    const name = "soha";
    
    // bad
    const name = `soha`;
    
    // good
    const name = 'soha';
  • 문자열과 변수를 같이 사용할 경우 literal를 사용한다.

    // bad
    const sayHi = 'Hello, ' + name + '!';
    
    // bad
    const sayHi = ['Hello, ', name, '!'].join();
    
    // bad
    const sayHi = `Hello, ${ name }!`;
    
    // good
    const sayHi = `Hello, ${name}!`;

5. 따옴표(’’)

  • 기본적으로 문자열은 single quote('')를 사용한다.

    // bad
    const name = "soha";
    
    // bad
    const name = `soha`;
    
    // good
    const name = 'soha';
  • 긴 문자열은 문자열 연결을 사용하여 여러 줄에 걸쳐 작성하지 않는다.

    // bad
    const message = '이 편지는 영국에서 최초로 시작되어 일년에' +
    '한바퀴를 돌면서 받는 사람에게 행운을 주었고 지금은 당신에게로 옮겨진' +
    '이 편지는 4일 안에 당신 곁을 떠나야 합니다. 이 편지를 포함해서 7통을' +
    '행운이 필요한 사람에게 보내 주셔야 합니다. 복사를 해도 좋습니다.' +
    '혹 미신이라 하실지 모르지만 사실입니다.';
    
    // good
    const message = '이 편지는 영국에서 최초로 시작되어 일년에 한바퀴를 돌면서 받는 사람에게 행운을 주었고 지금은 당신에게로 옮겨진 이 편지는 4일 안에 당신 곁을 떠나야 합니다. 이 편지를 포함해서 7통을 행운이 필요한 사람에게 보내 주셔야 합니다. 복사를 해도 좋습니다. 혹 미신이라 하실지 모르지만 사실입니다.'
  • jsx 문법 내의 attribute에서는 double quote(””)를 사용한다.

    const Weadie = () => {
      const name = '웨디';
      
      return <input placeholder="이름을 입력해주세요" name={name} />
    };

6. 변수

  • 변수 선언시 const, let만 사용한다.

    // bad
    var idx = 1;
    
    // good
    let idx = 1;
    
    // good
    const name = '행동대장';
  • 전역 변수를 사용하지 않는다.

7. 타입과 인터페이스

  • 타입의 종류를 선언할 때는 type을 사용한다.

    // good
    type ButtonStyle = 'primary' | 'secondary' | 'tertiary' | 'text';
  • 프로퍼티를 포함하는 객체 타입을 선언할 때는 interface를 사용한다.

    // good
    interface ButtonProps {
      style: ButtonStyle;
      text: string;
      disabled?: boolean;
      onClick?: () => void;
    }
  • 타입 import할 때는 type only import를 한다.

    import type { myType } from @types/myTypes
  • 컴포넌트의 인수는 별도의 ComponentProps 의 이름으로 interface를 선언하여 사용한다.

    interface ButtonProps {
    	size : ButtonSize;
    	color : ButtonColor;
    }
    
    const Button = (props : ButtonProps) => {
    	const {size, color} = props;
    	// ...
    }

8. 공백

  • 콤마 다음에 값이 올 경우 공백을 넣는다.

    // bad
    const array = [1,2,3];
    
    // good
    const array = [1, 2, 3];

9. 함수

  • 모든 함수는 화살표 함수를 사용한다.

    // bad 
    function foo() {
    	...
    }
    
    // bad
    [1,2,3].map(function (x) {
    	const y = x + 1;
    	return x * y;
    }
    
    // good 
    const foo = () => {
    	...
    }
    
    // good
    [1,2,3].map(x => {
    	const y = x + 1;
    	return x * y;
    }
  • 함수 내에서 반환은 한 번만 하되, 빠른 함수 탈출의 경우는 허용한다.

    // good
    const foo = () => {
    	const boo = 1;
    	
    	// ...
    	if(boo) return;
    	
    	return boo;
    }
  • return문 바로 위는 개행한다.

    // good
    const getResult = () => {
      ...
    
      return someDataInFalse;
    }
  • arguments를 사용하지 않고, rest 구문을 사용한다.

    // bad
    const getCrewNames = () => {
    	const args = Array.proptotype.slice.call(arguments);
    	return args.join('');
    }
    
    // good
    const getCrewNames = (...args) => {
    	return args.join('')
    }
  • 매개변수를 절대로 변경하지 않고, 기본 매개변수 구문을 사용한다.

    // bad
    const getName(name) {
    	name = name || ''
    	// ...
    }
    
    // bad
    const getName(name) {
    	if (!name) {
    		name = ''
    	}
    	// ...
    }
    
    // good
    const getName(name = '') {
    	// ...
    }
  • 기본 매개변수는 항상 마지막에 넣는다.

    // bad
    const getDetails(name = '', age) {
    	// ...
    }
    
    // good
    const getDetails(age, name = '') {
    	// ...
    }
  • 함수는 사용 전에 선언한다.

    // bad
    const a = foo();
    const foo = () => { // .. };
    
    // good
    const foo = () => { // .. };
    const a = foo();

10. 화살표 함수

  • 화살표 함수의 파라미터가 하나면 괄호는 생략한다.

    // bad
    foo.forEach((a) => a.name);
    
    // good
    foo.forEach(a => a.name);
  • 암시적 반환을 최대한 사용한다. 함수 본체가 하나의 표현식이면 중괄호를 생략해 return문을 사용하지 않고 짧은 경우 개행없이 볼 수 있어 깔끔하다.

    // bad
    foo.forEach((a) => {
    	return a.name;
    });
    
    // good
    foo.forEach(a => a.name);

11. 구조 분해

  • 객체의 여러 속성에 접근하여 사용할 때, 객체 분해를 사용한다.

    // bad
    const getCrewDetails = (crew) => {
    	const crewName = crew.name;
    	const crewAge = crew.age;
    	
    	return `${crewName}:${crewAge}세`
    }
    
    // good
    const getCrewDetails = (crew) => {
    	const {name, age} = crew;
    	
    	return `${name}:${age}세`
    }
    
    // best
    const getCrewDetails = ({name, age}) => {
    	
    	return `${name}:${age}세`
    }
  • 배열 분해를 사용한다.

    const crews = ['cookie', 'soha', 'weadie', 'todari'];
    
    // bad
    const first = crews[0];
    const second = crews[1];
    
    // good
    const [first, second] = crews;
  • 여러 값을 반환할 경우 배열 분해가 아닌 객체 분해를 사용한다.

    // bad
    const getCrews = () => {
    	return [cookie, soha, weadie, todari];
    }
    
    // good
    const getCrews = () => {
    	return {cookie, soha, weadie, todari};
    }

12. 배열(Array)

  • 배열 선언은 리터럴을 사용한다.

    // bad
    const array = new Array('r', 'g', 'b');
    
    // good
    const array = ['r', 'g', 'b'];
  • 배열 복사는 전개(spread) 연산자를 사용한다.

    const arr = [1, 2, 3, 4];
    
    // bad
    const newArray = arr.slice();
    
    // good
    const newArray = [...arr];
  • 특정 길이 만큼의 배열을 선언해야하는 경우 생성자를 사용해 선언한다.

    // bad 
    const array = [undefined, undefined, undefined, undefined, undefined];
    
    // bad
    const array = Array.from({ length: 5 });
    
    // good
    const array = new Array(5).fill();

13. 비교 연산자

  • 조건 확인은 ===, !==만 사용한다. ==, != 는 사용하지 않는다.

  • boolean에는 단축형을 사용한다.

    // bad
    if (isTired === 'true') {
    	// ...
    }
    
    // good
    if (isTired) {
    	// ...
    }

14. Switch

  • switch문 사용시 case마다 개행을 둔다.

    // good
    switch (value) {
      case 1:
        doSomething1();
        break;
        
      case 2:
        doSomething2();
        break;
    
      case 3:
        return true;
    
      default:
        throw new Error('This shouldn\'t happen.');
    }

15. 주석

  • 주석 기호 뒤에 공백을 넣는다.

    //bad
    
    // good
  • 설명 주석의 경우 2줄 이상 넘어갈 경우 /**/ 를 사용한다.

    // bad
    
    // 이건
    // 주석입니다.
    
    // good
    
    /*
     * 이건
     * 주석입니다.
     */
     
    // good
    
    /**
     * 이건
     * 주석입니다.
     */
  • 코드를 주석 처리한 경우에는 2줄 이상이라도 //를 사용한다.

    // const hello = '안녕하세요!';
    // const name = 'soha';
  • TODO 주석의 경우에는 작성자의 이름을 포함한다.

    // good
    // TODO: (@soha) 내용
    
    // 작서자 이름: soha, weadie, cookie, todari 

16. 모듈

  • 항상 모듈(import)을 사용한다.

    // bad
    const HaengdongStyle = require('./HaengdongStyle');
    module.exports = HaengdongStyle.button;
    
    // good
    import HaengdongStyle from './HaengdongStyle';
    export default HaengdongStyle.button;
    
    // best
    import { button } from './HaengdongStyle';
    export default button;
  • 한 곳의 여러 모듈은 한 번에 가져온다.

    // bad
    import haengdong from 'haengdong';
    import { cookie, weadie } from 'haengdong';
    
    // good
    import haengdong, { cookie, weadie } from 'haengdong';
    
    // good
    import haengdong, {
    	cookie,
    	weadie,
    } from 'haengdong';
  • ts, tsx 파일 이름 확장자를 포함하지 않는다.

    // bad
    import soha from './soha.ts';
    import cookie from './cookie.tsx';
    import haengdong from './haedong/index.ts';
    
    // good
    import soha from './soha';
    import cookie from './cookie';
    import haengdong from './haedong';

17. 파일

  • component와 관련된(Button.tsx, Button.type.ts, Button.stories.tsx 등) 파일은 PascalCase를 사용한다.

  • 그 외에는 camelCase를 사용한다.

  • 참조 명명 시 React 구성 요소에는 PascalCase를 사용하고 인스턴스에는 camelCase를 사용한다.

    // bad
    import button from './Button';
    
    // good
    import Button from './Button';
    
    // bad
    const Button = <Button />;
    
    // good
    const button = <Button />;
  • React 구성 요소 명명 시 파일 이름을 구성 요소 이름으로 사용합니다.

    // bad
    import Button from './Button/Button';
    
    // bad
    import Button from './Button/index';
    
    // good
    import Button from './Button';

18. React

  • prop 이름에는 항상 camelCase를 사용하고, prop 값이 React Component인 경우 PascalCase를 사용한다.

    // bad
    <User
    	Name="weadie"
    	phone_number={01011112222}
    	card={Card}
    />
    
    // good
    <User
    	name="weadie"
    	phoneNumber={01011112222}
    	Card={Card}
    />
  • prop 값이 명시적으로 true인 경우 생략한다.

    // bad
    <Button hidden={true} />
    
    // true
    <Button hidden />
  • prop의 key에 배열 index를 사용하지 않는다. 고유하고 안정적인 ID를 할당한다.

    // bad
    {cards.map((card, index) =>
    	<Card
    	{...card}
    	key={index} />
    )};
    
    // good
    {cards.map(todo =>
    	<Card
    	{...card}
    	key={todo.id} />
    )};
  • 필수가 아닌 prop에 대해선 명시적인 defaultProp을 정의한다.

    // good
    const Item = ({id, name = 'todari'}: ItemProps) => {
    	return // ...
    }
  • children이 없는 태그는 스스로 닫는다.

    // bad
    <Card name="soha"></Card>
    
    // good
    <Card name="soha" />
    
    // bad
    <Card
    	name="soha"
    	job="developer" />
    	
    // good
    <Card
    	name="soha"
    	job="developer"
    />
  • div 태그를 사용하는 것을 지양하고, 시멘틱 태그를 사용하는 것을 지향한다.

    // bad
    <div>hello</div>
    
    // good
    <p>hello</p>

19. CSS

  • css prop의 네이밍은 컴포넌트의 이름 + Style을 사용한다.

    // bad
    const ButtonStyle = css`
    	display: flex;
    `;
    
    const Wedy = () => {
    	return (
    		<div css={ButtonStyle} />
    	);
    }
    
    // good
    const buttonStyle = css`
    	display: flex;
    `;
    
    const Wedy = () => {
    	return (
    		<div css={buttonStyle} />
    	);
    }
  • attribute 순서는 유사한 성격의 attribute을 묶어 작성한다. 다른 성격의 attribute와는 개행으로 구분한다.

    순서 성격 대표 attribute
    1 레이아웃 position, top, bottom, left, right, z-index, float, display, flex, flex-direction, grid, visibility
    2 BOX width, height, margin, padding, border, outline, box-sizing
    3 배경 background, box-shadow
    4 폰트 font, color, text, line-height, letter-spacing, white-space, break-word
    5 변형 transform, transition, animate
    6 기타 overflow, opacity, cursor

2️⃣ 폴더 구조

절대 경로를 사용한다.

경로 위치
./src/apis @apis
./src/assets @assets
./src/components @components
./src/constants @constatns
./src/hooks @hooks
./src/mocks @mocks
./src/pages @pages
./src/utils @utils

1. import 순서

  • 각 import group 간에는 개행을 한다.

    그룹 패턴
    1 React*
    2 @hooks/*
    3 @apis/*
    4 @pages/*
    5 @components/*
    6 @assets/*
    7 @utils/*
    8 @constants/*
    9 @mocks/*

3️⃣ 스타일링 라이브러리

  • 사용할 스타일링 라이브러리 후보는 다음과 같아요. emotion, styled-components, css modules, panda css
  • 이 중에서 스타일링 라이브러리는 emotion을 사용하기로 결정했어요!

결정 근거

  • 행동대장들은 협업, 학습, 성장에 대한 경험을 우선순위로 생각하기 때문에, 모든 크루가 이미 많이 사용해 본 styled-components는 배제했어요.
  • theme이나 component state에 따른 anatomy등을 동적으로 다양하게 적용하기 위해서 동적 스타일링이 불편한 module css는 사용하지 않기로 했어요.
  • pandaCSS는 런타입 오버헤드가 없어 성능 최적화 면에서 매우 뛰어나지만, design foundation이나 static 파일들에 대한 러닝커브가 매우 높아 사용하지 않기로 했어요.
  • styled-component와 css prop을 사용하는 emotion 모두 별도의 변수명을 지정해서 이를 할당해 줘야 하는 번거로움과 코드의 길이가 길어지는 문제가 있어요. 따라서 이 부분을 선택 기준으로 잡는 건 부적절 할 것이라 판단했어요.
  • 하지만, html 태그가 바로 보여 웹 접근성을 고려하여 개발하기 좋다는 장점과, 점유율이 높고 정보가 높은 이유로 인해 emotion css를 사용하기로 결정했어요.

4️⃣ 사용 라이브러리

  • react
  • react-router-dom
  • typescript
  • emotion
  • webpack
  • esLint
  • prettier

5️⃣ 테스트

  • storybook
  • jest
  • react-testing-library
  • msw

6️⃣ 개발 환경

  • node 버전: v20.15.1 (LTS)
  • npm 버전: v10.7.0

7️⃣ 서비스 타겟 환경 및 브라우저 지원 범위

  • 서비스 타겟 환경: 모바일
  • 브라우저 지원 범위: 크롬, 사파리, 엣지