JavaScript 스타일 가이드

우리는 에어비앤비 JavaScript 스타일 가이드와 그에 따르는 린터를 사용하여 대부분의 JavaScript 스타일 지침을 관리합니다.

에어비앤비가 설정한 스타일 가이드 외에도 몇 가지 특정 규칙이 있습니다.

참고: yarn run lint:eslint:all 또는 yarn run lint:eslint $PATH_TO_FILE를 실행하여 ESLint를 로컬에서 실행할 수 있습니다.

forEach 피하기

데이터를 변형할 때 forEach를 피하십시오. 데이터를 변형할 때는 forEach 대신 map, reduce 또는 filter를 사용하십시오. 이렇게 하면 함수 내의 변형을 최소화할 수 있으며, 이는 에어비앤비 스타일 가이드와 일치합니다.

// 안 좋은 예
users.forEach((user, index) => {
  user.id = index;
});

// 좋은 예
const usersWithId = users.map((user, index) => {
  return Object.assign({}, user, { id: index });
});

매개변수 수 제한하기

함수나 메소드의 매개변수가 3개 이상인 경우 대신 객체를 매개변수로 사용하십시오.

// 안 좋은 예
function a(p1, p2, p3, p4) {
  // ...
};

// 좋은 예
function a({ p1, p2, p3, p4 }) {
  // ...
};

DOM 이벤트를 처리하기 위해 클래스 사용하지 않기

클래스의 유일한 목적이 DOM 이벤트를 바인딩하고 콜백을 처리하는 것이라면 함수를 사용하는 것이 좋습니다.

// 안 좋은 예
class myClass {
  constructor(config) {
    this.config = config;
  }

  init() {
    document.addEventListener('click', () => {});
  }
}

// 좋은 예

const myFunction = () => {
  document.addEventListener('click', () => {
    // 여기서 콜백 처리
  });
}

생성자에 요소 컨테이너 전달하기

클래스가 DOM을 조작하는 경우 요소 컨테이너를 매개변수로 받습니다. 이렇게 하는 것이 유지보수하기에 더 나은 성능을 발휘합니다.

// 안 좋은 예
class a {
  constructor() {
    document.querySelector('.b');
  }
}

// 좋은 예
class a {
  constructor(options) {
    options.container.querySelector('.b');
  }
}

문자열을 정수로 변환하기

문자열을 정수로 변환할 때 Number가 의미론적이고 더 가독성이 좋습니다. 둘 다 허용되지만 Number가 약간 더 유지보수하기에 유리합니다.

경고: parseInt를 사용할 때는 진수 인자를 포함해야 합니다.

// 안 좋은 예 (진수 인자 누락)
parseInt('10');

// 좋은 예
parseInt("106", 10);

// 좋은 예
Number("106");
// 안 좋은 예 (진수 인자 누락)
things.map(parseInt);

// 좋은 예
things.map(Number);

참고: 문자열이 정수가 아닌 경우(예: 소수점이 포함된 경우) parseInt를 사용하지 마십시오. 대신 NumberparseFloat를 고려하십시오.

CSS 선택자 - js- 접두사 사용

CSS 클래스가 요소를 참조하는데 JavaScript에서만 사용된다면 클래스 이름에 js- 접두사를 붙입니다.

// 안 좋은 예
<button class="add-user"></button>

// 좋은 예
<button class="js-add-user"></button>

ES 모듈 구문

대부분의 JavaScript 파일에는 모듈에서 가져오거나 내보내기 위해 ES 모듈 구문을 사용하십시오. 이름을 지정한 내보내기를 선호합니다. 왜냐하면 이름의 일관성을 높이기 때문입니다.

// 안 좋은 예 (예외적인 경우는 아래 참조)
export default SomeClass;
import SomeClass from 'file';

// 좋은 예
export { SomeClass };
import { SomeClass } from 'file';

기본 내보내기는 특정한 상황에서 허용됩니다:

  • Vue 단일 파일 컴포넌트(SFC)
  • Vuex 뮤테이션 파일

더 많은 정보는 RFC 20을 참조하십시오.

CommonJS 모듈 구문

우리의 Node 구성은 CommonJS 모듈 구문을 필요로 합니다. 이름을 지정한 내보내기를 선호합니다.

// 안 좋은 예
module.exports = SomeClass;
const SomeClass = require('./some_class');

// 좋은 예
module.exports = { SomeClass };
const { SomeClass } = require('./some_class');

모듈의 절대 경로 vs 상대 경로

가져오는 모듈이 2단계 이하로 떨어져 있는 경우 상대 경로를 사용하십시오.

// 안 좋은 예
import GitLabStyleGuide from '~/guides/GitLabStyleGuide';

// 좋은 예
import GitLabStyleGuide from '../GitLabStyleGuide';

가져오는 모듈이 2단계 이상으로 떨어져 있는 경우 대신 절대 경로를 사용하십시오:

// 안 좋은 예
import GitLabStyleGuide from '../../../guides/GitLabStyleGuide';

// 좋은 예
import GitLabStyleGuide from '~/GitLabStyleGuide';

게다가 전역 네임스페이스에 추가하지 마십시오.

XSS 방지

innerHTML, append() 또는 html()을 사용하여 내용을 설정하지 마십시오. 이는 많은 취약점을 노출시킵니다.

ESLint

ESLint 동작 방법은 도구 가이드에서 확인할 수 있습니다.

IIFEs

즉시 호출 함수 표현(IIFEs)의 사용을 피하십시오. Sprockets에서 webpack으로 전환된 후에는 더 이상 필요하지 않습니다. 더 이상 사용하지 마시고 레거시 코드를 리팩토링할 때 제거해도 됩니다.

전역 네임스페이스

전역 네임스페이스에 추가하는 것을 피하십시오.

// 나쁨
window.MyClass = class { /* ... */ };

// 좋음
export default class MyClass { /* ... */ }

부작용

최상위 부작용

export를 포함하는 스크립트에서 최상위 부작용은 금지됩니다.

// 나쁨
export default class MyClass { /* ... */ }

document.addEventListener("DOMContentLoaded", function(event) {
  new MyClass();
}

생성자에서의 부작용 피하기

constructor에서 비동기 호출, API 요청 또는 DOM 조작을 하지 말아야 합니다. 대신에 별도의 함수로 이동시키세요. 이렇게 하면 테스트 작성이 쉬워지고 단일 책임 원칙을 위반하는 것을 피할 수 있습니다.

// 나쁨
class myClass {
  constructor(config) {
    this.config = config;
    axios.get(this.config.endpoint)
  }
}

// 좋음
class myClass {
  constructor(config) {
    this.config = config;
  }

  makeRequest() {
    axios.get(this.config.endpoint)
  }
}
const instance = new myClass();
instance.makeRequest();

순수 함수와 데이터 변형

많은 작은 순수 함수를 작성하고 변형이 발생하는 위치를 최소화하는 것을 목표로 합니다.

  // 나쁨
  const values = {foo: 1};

  function impureFunction(items) {
    const bar = 1;

    items.foo = items.a * bar + 2;

    return items.a;
  }

  const c = impureFunction(values);

  // 좋음
  var values = {foo: 1};

  function pureFunction (foo) {
    var bar = 1;

    foo = foo * bar + 2;

    return foo;
  }

  var c = pureFunction(values.foo);

상수를 기본형으로 내보내기

개별 네임스페이스와 함께 상수의 기본형을 내보내기를 선호합니다. 이렇게 하면 컴파일 시 참조 검사가 용이해지고 런타임에서의 실수로 인한 undefined 발생을 피할 수 있습니다. 또한 번들 크기를 줄이는 데 도움이 됩니다.

상수를 컬렉션(배열 또는 객체)로 내보내야 할 필요가 있는 경우에만 내보내세요. 예를 들어 프롭 유효성 검사를 위해 반복할 필요가 있는 경우입니다.

  // 나쁨
  export const VARIANT = {
    WARNING: 'warning',
    ERROR: 'error',
  };

  // 좋음
  export const VARIANT_WARNING = 'warning';
  export const VARIANT_ERROR = 'error';

  // 좋음, 상수를 반복해야 할 필요가 있는 경우
  export const VARIANTS = [VARIANT_WARNING, VARIANT_ERROR];

오류 처리

서버가 500을 반환할 때 내부 서버 오류에 대해 일반적인 오류 메시지를 반환해야 합니다.

백엔드가 오류를 반환하는 경우, 해당 오류는 사용자에게 표시할 수 있는 적합한 메시지여야 합니다.

어떠한 이유로든 어렵다면, 마지막 수단으로 접두어를 사용하여 특정 오류 메시지를 선택할 수 있습니다.

  1. 사용자에게 표시할 오류 메시지에 접두어를 추가하도록 백엔드를 보장하세요:

    Gitlab::Utils::ErrorMessage.to_user_facing('사용자에게 표시할 예시 오류 메시지')
    
  2. app/assets/javascripts/lib/utils/error_message.js에 포함된 오류 메시지 유틸리티 함수를 사용하세요.

이 유틸리티는 서버 응답으로부터 받은 오류 객체와 기본 오류 메시지 두 매개변수를 받습니다. 유틸리티는 오류 객체의 메시지를 분석하여 메시지가 사용자에게 표시되어야 하는지 여부를 나타내는 접두어를 확인합니다. 메시지가 사용자에게 표시되어야 하는 경우 유틸리티는 메시지를 그대로 반환합니다. 그렇지 않은 경우 매개변수로 전달된 기본 오류 메시지를 반환합니다.

import { parseErrorMessage } from '~/lib/utils/error_message';

onError(error) {
  const errorMessage = parseErrorMessage(error, genericErrorText);
}

이 접두어는 API 응답에 사용해서는 안 됩니다. 대신 REST APIGraphQL 가이드를 따라 오류 객체를 소비하세요.