JavaScript 스타일 가이드
GitLab v19.4요약
GitLab은 JavaScript 스타일 가이드라인 대부분을 Airbnb JavaScript 스타일 가이드와 그에 딸린 린터로 관리합니다. Airbnb가 정한 스타일 가이드라인에 더해, 아래와 같은 몇 가지 별도 규칙이 있습니다.
GitLab은 JavaScript 스타일 가이드라인 대부분을 Airbnb JavaScript 스타일 가이드와 그에 딸린 린터로 관리합니다.
Airbnb가 정한 스타일 가이드라인에 더해, 아래와 같은 몇 가지 별도 규칙이 있습니다.
yarn run lint:eslint:all 또는 yarn run lint:eslint $PATH_TO_FILE을 실행하면 로컬에서 ESLint를 실행할 수 있습니다.
준비 완료 신호#
기능 테스트가 JavaScript 초기화를 기다려야 한다면 사용자에게 보이는 결과를 관찰하는 방식을 우선합니다. 페이지에 단언할 만한 UI 상태가 없을 때만 준비 완료 신호를 추가합니다.
해당 이니셜라이저가 완료될 때 설정되는, 범위가 정확한 data-* 속성을
사용합니다.
document.body.dataset.rightSidebarInitialized = 'true';
page-initialised처럼 타이머에 기반한 클래스를 완료 신호로 사용하지 않습니다.
타임아웃은 초기화가 끝났다는 증거가 되지 않습니다. 기능 테스트 동기화 패턴은
wait_for_requests 나 wait_for_all_requests를 사용하지 않습니다를 참고합니다.
forEach 사용 지양#
데이터를 변경할 때는 forEach를 피합니다. 데이터를 변경할 때는 forEach 대신
map, reduce, filter를 사용합니다. 이렇게 하면 함수 안의 변경이 줄어들며,
이는 Airbnb 스타일 가이드와도 부합합니다.
// bad
users.forEach((user, index) => {
user.id = index;
});
// good
const usersWithId = users.map((user, index) => {
return Object.assign({}, user, { id: index });
});
매개변수 수 제한#
함수나 메서드의 매개변수가 3개를 넘으면 매개변수 대신 객체 하나를 사용합니다.
// bad
function a(p1, p2, p3, p4) {
// ...
};
// good
function a({ p1, p2, p3, p4 }) {
// ...
};
DOM 이벤트 처리 시 클래스 사용 지양#
클래스의 유일한 목적이 DOM 이벤트를 바인딩하고 콜백을 처리하는 것이라면 함수를 사용합니다.
// bad
class myClass {
constructor(config) {
this.config = config;
}
init() {
document.addEventListener('click', () => {});
}
}
// good
const myFunction = () => {
document.addEventListener('click', () => {
// handle callback here
});
}
생성자에 엘리먼트 컨테이너 전달#
클래스가 DOM을 조작한다면 엘리먼트 컨테이너를 매개변수로 받습니다. 이렇게 하면 유지 보수성과 성능이 더 좋습니다.
// bad
class a {
constructor() {
document.querySelector('.b');
}
}
// good
class a {
constructor(options) {
options.container.querySelector('.b');
}
}
문자열을 정수로 변환#
문자열을 정수로 변환할 때 Number는 의미가 분명하고 더 읽기 쉬울 수 있습니다. 둘 다 사용할 수 있지만 Number가 유지 보수 측면에서 조금 더 유리합니다.
parseInt에는 radix 인수를 반드시 포함해야 합니다.
// bad (missing radix argument)
parseInt('10');
// good
parseInt("106", 10);
// good
Number("106");
// bad (missing radix argument)
things.map(parseInt);
// good
things.map(Number);
문자열이 정수가 아닌 값(소수점이 포함된 수)을 나타낼 수 있다면 parseInt를 사용하지 않습니다. 대신 Number 나 parseFloat를 검토합니다.
CSS 셀렉터 - js- 접두사 사용#
CSS 클래스를 JavaScript에서 엘리먼트 참조용으로만 사용한다면 클래스 이름에
js- 접두사를 붙입니다.
// bad
<button class="add-user"></button>
// good
<button class="js-add-user"></button>
ES 모듈 문법#
대부분의 JavaScript 파일에서는 모듈을 가져오거나 내보낼 때 ES 모듈 문법을 사용합니다. 이름 일관성이 높아지므로 명명된 내보내기를 우선합니다.
// bad (with exceptions, see below)
export default SomeClass;
import SomeClass from 'file';
// good
export { SomeClass };
import { SomeClass } from 'file';
다음과 같은 일부 상황에서는 기본 내보내기를 사용해도 됩니다.
- Vue 단일 파일 컴포넌트(SFC)
- Vuex 뮤테이션 파일
자세한 내용은 RFC 20을 참고합니다.
CommonJS 모듈 문법#
GitLab의 Node 구성은 CommonJS 모듈 문법을 요구합니다. 명명된 내보내기를 우선합니다.
// bad
module.exports = SomeClass;
const SomeClass = require('./some_class');
// good
module.exports = { SomeClass };
const { SomeClass } = require('./some_class');
모듈의 절대 경로 vs 상대 경로#
가져오려는 모듈이 두 단계 미만 위에 있으면 상대 경로를 사용합니다.
// bad
import GitLabStyleGuide from '~/guides/GitLabStyleGuide';
// good
import GitLabStyleGuide from '../GitLabStyleGuide';
가져오려는 모듈이 두 단계 이상 위에 있으면 절대 경로를 사용합니다.
// bad
import GitLabStyleGuide from '../../../guides/GitLabStyleGuide';
// good
import GitLabStyleGuide from '~/GitLabStyleGuide';
또한 전역 네임스페이스에 추가하지 않습니다.
페이지 모듈이 아닌 곳에서 DOMContentLoaded 사용 금지#
가져온 모듈은 로드될 때마다 동일하게 동작해야 합니다. DOMContentLoaded
이벤트는 webpack으로 동적으로 로드되는 /pages/* 디렉터리의 모듈에서만
허용됩니다.
XSS 방지#
콘텐츠를 설정할 때 innerHTML, append(), html()을 사용하지 않습니다. 너무 많은
취약점이 생깁니다.
ESLint#
ESLint의 동작은 도구 가이드에서 확인할 수 있습니다.
IIFEs#
IIFE(즉시 실행 함수 표현식) 사용을 피합니다. 파일 내용을 IIFE로 감싼 예시가 많이 남아 있지만, Sprockets에서 webpack으로 전환한 뒤로는 더 이상 필요하지 않습니다. 이제는 사용하지 않으며, 레거시 코드를 리팩터링할 때 자유롭게 제거해도 됩니다.
전역 네임스페이스#
전역 네임스페이스에 추가하지 않습니다.
// bad
window.MyClass = class { /* ... */ };
// good
export default class MyClass { /* ... */ }
사이드 이펙트#
최상위 레벨 사이드 이펙트#
export가 있는 스크립트에서는 최상위 레벨 사이드 이펙트가 금지됩니다.
// bad
export default class MyClass { /* ... */ }
document.addEventListener("DOMContentLoaded", function(event) {
new MyClass();
}
생성자에서의 사이드 이펙트 지양#
constructor에서 비동기 호출, API 요청, DOM 조작을 하지 않습니다.
대신 별도 함수로 옮깁니다. 이렇게 하면 테스트를 작성하기 쉬워지고
단일 책임 원칙을 위반하지 않게 됩니다.
// bad
class myClass {
constructor(config) {
this.config = config;
axios.get(this.config.endpoint)
}
}
// good
class myClass {
constructor(config) {
this.config = config;
}
makeRequest() {
axios.get(this.config.endpoint)
}
}
const instance = new myClass();
instance.makeRequest();
순수 함수와 데이터 변경#
작은 순수 함수를 많이 작성하고 변경이 일어나는 지점을 최소화합니다
// bad
const values = {foo: 1};
function impureFunction(items) {
const bar = 1;
items.foo = items.a * bar + 2;
return items.a;
}
const c = impureFunction(values);
// good
var values = {foo: 1};
function pureFunction (foo) {
var bar = 1;
foo = foo * bar + 2;
return foo;
}
var c = pureFunction(values.foo);
상수를 원시값으로 내보내기#
객체를 내보내는 대신 공통 네임스페이스를 가진 상수 원시값을 내보내는 방식을 우선합니다. 이렇게 하면 컴파일 시점 참조 검사가 더 잘 동작하고 런타임에 의도치 않은 undefined를 피할 수 있습니다. 또한 번들 크기를 줄이는 데도 도움이 됩니다.
prop 유효성 검사기처럼 상수를 순회해야 할 때만 상수를 컬렉션(배열 또는 객체)으로 내보냅니다.
// bad
export const VARIANT = {
WARNING: 'warning',
ERROR: 'error',
};
// good
export const VARIANT_WARNING = 'warning';
export const VARIANT_ERROR = 'error';
// good, if the constants need to be iterated over
export const VARIANTS = [VARIANT_WARNING, VARIANT_ERROR];
오류 처리#
서버가 500을 반환하는 내부 서버 오류에는 일반적인 오류 메시지를
반환해야 합니다.
백엔드가 오류를 반환할 때는 그 오류가 사용자에게 그대로 표시하기에 적합해야 합니다.
그렇게 하기 어려운 경우에는 최후의 수단으로 접두사를 붙여 특정 오류 메시지를 선별할 수 있습니다.
-
백엔드가 표시할 오류 메시지에 다음과 같이 접두사를 붙이는지 확인합니다.
Gitlab::Utils::ErrorMessage.to_user_facing('Example user-facing error-message') -
app/assets/javascripts/lib/utils/error_message.js에 있는 오류 메시지 유틸리티 함수를 사용합니다.
이 유틸리티는 매개변수 두 개를 받습니다. 서버 응답에서 받은 오류 객체와 기본 오류 메시지입니다. 유틸리티는 오류 객체의 메시지를 살펴 그 메시지가 사용자에게 표시할 것인지 나타내는 접두사가 있는지 확인합니다. 사용자에게 표시할 메시지이면 그대로 반환합니다. 그렇지 않으면 매개변수로 전달된 기본 오류 메시지를 반환합니다.
import { parseErrorMessage } from '~/lib/utils/error_message';
onError(error) {
const errorMessage = parseErrorMessage(error, genericErrorText);
}
이 접두사 방식은 API 응답에 사용하면 안 됩니다. 오류 객체를 다루는 방법은 REST API 또는 GraphQL 가이드를 따릅니다.