0w0

JavaScript 코드를 한 눈에 보기 좋기하기위해 의식해야하는 것 10선

평소에 코드를 작성하다보면 보기 좋음(가독성)을 좋게 하기 위해 의식해야하는 것을 10개 열거했습니다.

JavaScript(TypeScript)에 관련한 이야기입니다.

1. 변수 선언은 const만 사용한다

변수 선언할 때 const를 사용합니다. var은 사용하지 않습니다

let은 한정적으로 실행할 함수 안에 변수를 재대입해야 할 필요가 있을 때만 사용합니다.

// 함수 밖 let은 안티패턴
let strValue = '';

const method1 = () => {
  strValue = 'method1';
};
const method2 = () => {
  // 함수안에서 재대입 처리가 필요할 때 선언
  let setValue = 'foobar';
};
// 밑에서 변수 strValue가 이용될지도 모른다
// 어딘가에서 재대입될지 모른다...

let은 처리 도중에 변수 값이 변하는 것을 암묵적으로 가르키므로, 읽는 사람을 괴롭게 합니다.

재대입할 필요 없다면 괜히 let를 사용할 이유가 없으므로, 우선 const를 사용하도록 합시다.

2. 생략해 적는다

true, false 생략해서 적습니다.

// 좋지않음
if(checkBoolean === true)
if(checkBoolean === false)

// 생략가능
if(checkBoolean)
if(!checkBoolean)

비교대상이 undefined, null, ""라면 !!으로 생략가능합니다.

생략하지 않으면

const hoge = 'example';

const hogeFunc = (hoge) => {
  return hogehoge !== undefined && hogehoge !== null && hogehoge !== '';
};

console.log(hogeFunc(hoge));

생략하면

const hoge = 'example';

const hogeFunc = (hoge) => {
  return !!hoge;
};

console.log(hogeFunc(hoge));

!!를 붙이면 모든 자료형이 boolean이 됩니다(캐스트됩니다)

특히 undefinedhoge != undefined 같이 잘못된 판정을 할 가능성이 있으므로 권장되지 않습니다. !!를 사용하도록 합시다.

3. 삼항연산자

삼항연산자로 if 생략해서 작성할 수 있습니다.

조건식 ?  : 거짓;

물론, 삼항연산자 평가한 결과를 값을 대입도 가능합니다.

const 변수 = 조건식 ?  : 거짓;

삼항연산자는 작성 방법에 따라 2종류로 적을 수 있습니다.

  1. 평가한 결과를 각각에 대입하는 패턴
const TRUE_MESSAGE = 'true다';
const FALSE_MESSAGE = 'false다';
const isCheckBoolean = true;
let resultList = '';

isCheckBoolean ? (resultList = TRUE_MESSAGE) : (resultList = FALSE_MESSAGE);
  1. 평가한 결과를 좌변에 대입하는 패턴
const TRUE_MESSAGE = 'true다';
const FALSE_MESSAGE = 'false다';
const isCheckBoolean = true;
let resultList = '';

resultList = isCheckBoolean ? TRUE_MESSAGE : FALSE_MESSAGE;

어떻게하든 결과는 같지만, 가독성을 위해서라면 선언한 변수를 한 번만 적으면 되는 패턴2가 개인적으로 좋습니다.

4. Boolean()를 적는다

이하의 코드같이, 처리 중에 아무것도 하지 않고 단순히 return true 혹은 return false 반환하는 작성 방법은 장황합니다.

// 좋지않음
(() => {
  const checkString = '';

  if (checkString) {
    return true;
  } else {
    return false;
  }
})();

// 생략가능
(() => {
  const checkString = '';
  return Boolean(checkString);
})();

삼항연산자도 ~ ? true : false적는 것은 장황합니다.

const checkString = '';

// 좋지않음
const checkHoge = checkString ? true : false;

// Boolean을 적는다
const checkHoge = Boolean(checkString);

:::caution 댓글에 의견을 받았습니다

Boolean()은 이중부정 !!로 해도 결과가 같습니다

예를 들면 이렇습니다

const checkString = '';

// 좋지않음
const checkHoge = checkString ? true : false;

// 이중부정을 적는다
const checkHoge = !!checkString;

어느쪽이든 (문자열 등) 자료형을 Boolean 값으로 캐스트하는 성질이 있어서 차이는 없습니다

:::

5. return

함수 처리중에 return하면 그 이후는 시행되지 않습니다.

if로 조건을 충족하지 않으면 후속 처리하지 못하게끔할 때 사용됩니다.

(() => {
  const checkBoolean = true;

  if (checkBoolean) return; // true라면 후속처리하지 않는다.

  // false 일 때 실행
  console.log(checkBoolean);
})();

else ~ 를 적을 필요가 없어지므로 타이핑도 적어집니다.

6. 옵셔널체이닝

프로퍼티에 값이 없는데 접근하려하면 에러가 됩니다.

(종종 보는 Uncaught ReferenceError: 변수명 or 함수명 is not defined)

이 에러를 맞이하면, 프로그램은 멈추므로 상당히 거슬립니다.

정석적인 대처법으로 if로 프로퍼티를 확인하는 것이 있지만, 장황해집니다.

const freamWork = {
  name: 'react',
  version: '18',
};

// 옵셔널체이닝을 사용하지 않는 경우
if (freamWork && freamWork.version) {
  const getVersion = freamWork.version;
  console.log(getVersion);
}

옵셔널체이닝을 사용하면 설령 존재하지 않은 프로퍼티에 접근하려 하더라도 에러가 되지 않고, undefined를 반환할 뿐입니다.

const freamWork = {
  name: 'react',
  version: '18',
};

// 옵셔널체이닝
const check = freamWork?.manual;
console.log(check); // undifined

이용하는 곳으로는 서버사이드에서 fetch한 데이터에 자주 사용됩니다.

7. 스프레드 구문

스프레드구문은 배열이나 객체를 전개해줍니다.

이하와 같이 스프레드 구문을 사용한 객체를 전개함으로, 하나오 정리된 객체를 만들어줍니다.

const userInfo = {
  user_id: 1234,
  user_name: 'naru',
  user_email: 'naru@gmail.com',
};

const brouserInfo = {
  browser: 'chrome',
  version: '110',
};

const add = 'example';

// 하나의 객체로 정리해줍니다
const sumArray = { add, ...userInfo, ...brouserInfo };

console.log(sumArray);
// {add: 'example', user_id: 1234, user_name: 'naru', user_email: 'naru@gmail.com', browser: 'chrome', …}

이런 식으로 처리중에 객체를 하나로 만드는 경우나, 백엔드로 보낼 파라메터를 하나에 정리하고 싶은 경우 자주 사용합니다.

8. Set 구문

new Set를 사용하면, 값이 겹치지 않는 객체를 만들수 있습니다.

const numArray = ['2', '1', '3', '2', '1', '5', '6', '7', '8'];
const result = new Set(numArray);
console.log(result);

// 결과
// Set(7) {'2', '1', '3', '5', '6', …}

또 스프레드 구문과 잘 조합해서 배열로 만들 수 있습니다.

const numArray = ['2', '1', '3', '2', '1', '5', '6', '7', '8'];
const result = [...new Set(numArray)];
console.log(result);

// 결과
// (7) ['2', '1', '3', '5', '6', '7', '8']

new Set한 객체는 add() 메서드로 추가할 수 있습니다

중복처리를 위한 if도 필요없고, 같은 값을 추가해도 에러없이 고유값을 만들어주므로 꽤 사용하기도 편리합니다.

const labels = new Set();

labels.add('apple');

labels.add('apple');

const labelsList = labels; // Set(1) {'apple'}

// 배열로 만들고 싶다면 스프레드 구문을 쓰면 OK
const labelsArrayList = [...labels]; // ['apple']

응용으로 2개의 배열을 비교할 때도 편리합니다.

const list_A = [3, 5, 7];
const list_B = [2, 6, 9];

// 스프레드 구문을 이용해서 하나의 배열로 정리
const checkSumList = [...list_A, ...list_B];

// 고유하게 함(중복제거)
const resultSumList = [...new Set(checkSumList)];

// 중복이 있다면 배열 요소는 적어지므로 이걸로 확인 가능
if (resultSumList.length === checkSumList.length) {
  // 중복이 없을 때
  console.log('중복이 없습니다');
} else {
  // 중복이 있을 때
  console.log('중복이 있습니다');
}

9. includes()

includes()는 지정된 요소가 배열에 있는가 확인해서, 있다면 true, 없으면 false를 반환하는 메서드입니다.

예를 들어, mac과 iPhone일 때는 true 아닐 때는 false 판정하는 처리가 있다 칩니다.

이런 경우는 우선 mac과 iPhone를 배열에 담습니다.

/util 같은 곳에 정리해둡니다.

// export로 모듈화
export const DEVICE_LIST = ['mac', 'iPhone'];

사용할 때 import합니다

이하와 같이 includes를 사용해 변수에 정한 값이 있는가 확인할 수 있습니다.

// 상기한 파일 import
import DEVICE_LIST from '경로';

// includes 편리
const check_device = FRUITS_LIST.includes('mac');

if (check_device) {
  // true 일 때 처리
}

배열에 담아두면, 나중에 추가 삭제가 편해집니다.

이런 느낌으로 뭔가 정해진 것을 확인하고 싶은 경우에 includes()는 사용하기 쉽습니다.

10. map(), filter()

map(), filter()를 사용함으로 forEach를 사용하지 않아도 간결한 코드를 적을 수 있습니다

forEach는 딱 보면 이게 뭔 코드인가 알기 어렵기 십상입니다.

// forEach 사용하는 경우
const numbers = [1, 2, 3, 4, 5];
const doubledNumbers = [];
numbers.forEach((num) => {
  doubledNumbers.push(num * 2);
});
console.log(doubledNumbers); // [2, 4, 6, 8, 10]

// map 사용하는 경우
const numbers = [1, 2, 3, 4, 5];
const doubledNumbers = numbers.map((num) => {
  return num * 2;
});
console.log(doubledNumbers); // [2, 4, 6, 8, 10]

map()으로 구현할 수 있다면, forEach보다 우선시해야합니다.

또한 filter()의 편리한 사용방법으로, 값이 "" 같이 빈 값일 때 .filter(v => v);하면 빈 값이 제외됩니다.

filter() 사용하지 않는 경우

const numbers = [1, 2, '', 4];
const doubledNumbers = numbers.map((num) => {
  return num * 2;
});
console.log(doubledNumbers); // [2, 4, 0, 10] 공백처리로 0이 들어감

filter() 사용하는 경우

const numbers = [1, 2, '', 4];
const doubledNumbers = numbers
  .filter((v) => v)
  .map((num) => {
    return num * 2;
  });
console.log(doubledNumbers); // [2, 4, 10]

또한 map()이나 filter()로 작성한 배열은, 본래 배열과 다른 배열이 되므로, 본 배열에는 영향이 없습니다. forEach보다 사용하기 쉽습니다

(역주: forEach도 본 배열에는 영향이 없습니다 왜냐면 return undefined하는 사양이기 때문입니다)

다만 주의점으로 filter()에서 1번, map()에서 2번 반복하는 것이 되므로 어떤 의미로 낭비가 많은 처리가 될 수 있습니다

Set 등 다른 방법을 검토하며, 사용은 (처리량이 적은 곳에) 적극적으로 사용하면 좋겠다 생각합니다.

:::caution 댓글 받았습니다

ES2019부터 추가된 flatMap()를 사용하면, 2번 반복문 처리해야하는 것을 1번의 반복문으로 처리할 수 있습니다.

const numbers = [1, 2, '', 4];
const doubledNumbers2 = numbers.flatMap((num) => (num ? num * 2 : []));
console.log(doubledNumbers2); // [2, 4, 8]

알기 쉬움을 중시해서 filter()map()를 병행하는 방법을 소개했습니다

다만 위와 같이 flatMap()을 사용하면 한 줄 코드를 작성할 수 있습니다. :::

맺으며

이후에도 뭔가 있다면 추가하려 합니다.