본문으로 건너뛰기

9월 프리온보딩 프론트엔드 챌린지

· 약 12분

9월 프리온보딩 프론트엔드 챌린지 수강 도중 애매하게 알고 있었거나 새로이 알게된 정보 정리합니다.

타입스크립트

타입스크립트 공식 문서

1. soundness

  • 건전성에 대한 참고 사항
  • 건전성. 형식 체계 내에서 증명 가능한 명제가 의미론 상으로도 참이 되는 성질.
  • 타입스크립트에는 soundness가 있는데 대표적으로 type assertion 이 있다.
  • const usersAge = ("23" as any) as number;
  • 타입스크립트는 런타임에 개입하지 않기 때문에 (이는 soundness 특성 때문) 개발자는 Narrowing으로 타입을 좁혀주어야 한다.

2. unknown

  • 이 타입이 어떤 타입인지 개발자도 아직 모를 때
  • any와 비슷하지만 unknown의 가장 큰 특징은 에러를 발생시킨다는 것에 있다.
  • 타입가드를 사용하자.
let num: unknown = 99;

if (typeof num === 'string') {
num.trim();
}

(num as string).trim();
const jsonParserUnknown = (jsonString: string): unknown =>
JSON.parse(jsonString);

type User = { name: string };
const myUserAccount = jsonParserUnknown(`{ "name": "Samuel" }`) as User;
myUserAccount.name;

3. 구조적 서브 타이핑을 기반으로 하는 타입스크립트

구조적 타입

  • 사실 타입스크립트의 타입은 집합의 일부일 수 있다. 즉 언어가 집합의 관계를 따진다.
  • 객체가 어떤 속성들을 가지는지 구조를 기준으로 타입을 따진다.
  • 타입의 생김새가 오리와 같다면 바로 오리의 타입임을 뜻한다.
    • 오리처럼 보이고, 오리처럼 수영하고, 오리처럼 꽥꽥거리면 그것은 오리일 것이다.
    • 거위가 오리와 같은 속성을 가지고 있다면 그것조차도 오리 타입
    • 일반적으로 동적 타입 시스템을 설명하는 데 덕 타이핑이 사용된다.

명목적 타입

  • 각 타입이 고유하다는 것을 의미.
  • 즉 동일한 타입이나 데이터가 있더라도 타입을 공유할 수 없다.
  • 이름 기반으로 타입을 따진다
  • 작성한 타입이 런타임에 존재한다.
  • C#, Java

타입스크립트 활용

1. TS 컨벤션 참조 사이트

2. 어떤 객체의 value가 false값만 들어온다면, 타입스크립트로도 최대한 좁혀서 사용한다.

  • 어떤 todo가 만들어질 때, 처음에 isCompleted는 무조건 false로 들어온다.
interface CreateTodo {
id: string;
content: string;
isCompleted: boolean; // <- 여기
category?: string;
tags?: string[];
}

const createTodo = ({ content, category, tags }: CreateTodo): CreateTodo => {
return {
id: String(new Date()),
isCompleted: false,
content,
category,
tags,
};
};
  • 위 코드의 isCompleted는 boolean으로 타입지정이 되어있지만 이는 좋은 코드가 아니다.
interface CreateTodo {
id: string;
content: string;
isCompleted: false;
category?: string;
tags?: string[];
}

const createTodo = ({ content, category, tags }: CreateTodo): CreateTodo => {
return {
id: String(new Date()),
isCompleted: false,
content,
category,
tags,
};
};
  • 확장을 사용하여 위 코드보다 더 좋게 만들어보자.
interface Todo {
id: string;
content: string;
isCompleted: boolean;
category: string;
tags: string[];
}

interface CreateTodo extends Todo {
isCompleted: false;
}

const createTodo = ({ content, category, tags }: CreateTodo): CreateTodo => {
return {
id: String(new Date()),
isCompleted: false,
content,
category,
tags,
};
};

3. 자주 사용하는 type은 최대한 재활용해서 쓰는 게 좋다.

interface Todo {
id: string;
content: string;
isCompleted: boolean;
category?: string;
tags: string[];
}

interface DeleteTagParamsType {
id: string;
tagIdx?: number;
}
  • 위 코드의 문제는, id가 string이 아닌 다른 타입으로 바뀔 때 일어난다. 아래처럼 바꾼다면 좀 더 안정적으로 타입을 지정할 수 있다.
interface Todo {
id: string;
content: string;
isCompleted: boolean;
category?: string;
tags: string[];
}

interface DeleteTagParamsType {
id: Todo['id'];
tagIdx?: number;
}
  • 이렇게 하면 id의 타입이 바뀌어도 손쉽게 수정할 수 있다.
  • 아래처럼 typescript 유틸리티를 사용할 수도 있다. (Omit, Pick...)
interface Todo {
id: string;
content: string;
isCompleted: boolean;
category?: string;
tags: string[];
tagIdx?: number;
}

type shoppingItem = Pick<Todo, 'tagIdx'>;
  • 확장을 사용할 수도 있다.
interface Todo {
id: string;
content: string;
isCompleted: boolean;
category?: string;
tags: string[];
}

interface DeleteTagParamsType extends Todo {
tagIdx?: number;
}

4. 함수형 type을 지정할때 void가 return 되는 type이 많다면, 한 번 의심해보는 게 좋다.

  • 함수가 너무 많은 일을 하고 있을 수 있으므로 점검해보자

5. 매개변수에 id 등을 넣을 때, 그냥 id를 쓰기보다 가능한 어떤 id인지 매개변수명으로 명시하면 좋다.

deleteTodo(selectedId) {
this.todoList = this.todoList.filter(({id})=> id !== selectedId)
}

6. any와 다를 바 없는 타입 지정을 피하자

const $app: HTMLElement = document.createElement('div'); // 좋지 않음

const $app: HTMLDivElement = document.createElement('div'); // 이렇게 하자

7. 타입을 좁힐 때 리터럴 타입을 적절히 이용한다.

  • 아래와 같이 Route를 상수로 정의한 객체가 있다고 하자.
const Route = {
ABOUT: '/about',
TOPICS: '/topics',
};

function changeRoute(newPath: string) {
const state = { page_id: 1, user_id: 5 };
const title = '';

history.pushState(state, title, newPath);
}

window.addEventListener('popstate', () => changeRoute('/auth'));
  • newPath를 string으로 받으면, changeRoute에 존재하지 않는 route를 넣어도 typescript는 잡아주지 못한다.
  • 이럴 때 리터럴 타입을 이용한다.
const Route = {
ABOUT: '/about',
TOPICS: '/topics',
};

type RoutePaths = 'about' | 'topics';

function changeRoute(newPath: RoutePaths) {
const state = { page_id: 1, user_id: 5 };
const title = '';

history.pushState(state, title, newPath);
}

window.addEventListener('popstate', () => changeRoute('/auth'));
// Argument of type '"/auth"' is not assignable to parameter of type 'RoutePaths'.(2345)
  • 라우트가 너무 많을 땐 다음처럼 응용할 수 있다.
const Route = {
ABOUT: '/about',
TOPICS: '/topics',
} as const;

type RoutePaths = typeof Route[keyof typeof Route];

function changeRoute(newPath: RoutePaths) {
const state = { page_id: 1, user_id: 5 };
const title = '';

history.pushState(state, title, newPath);
}

window.addEventListener('popstate', () => changeRoute('/about'));

자바스크립트

1. new.target

mdn

new.target 속성(property)은 함수 또는 생성자가 new 연산자를 사용하여 호출됐는지를 감지할 수 있습니다.

  • 우리는 사용자를 위해 일부러 에러를 낼 수 있지만, 동료 개발자를 위해 에러를 낼 줄도 알아야 한다. 예를 들어, 생성자를 사용하여 init 코드를 써야만 한다고 해보자. 동료 개발자가 이를 쓰지 않았을 때, 에러를 내주어야 한다. 이럴 때 쓸 수 있는 것이 new.target 이다.

    function Foo() {
    if (!new.target) {
    throw 'Foo() must be called with new';
    }
    }

    try {
    Foo();
    } catch (e) {
    console.log(e);
    // expected output: "Foo() must be called with new"
    }

2. TDZ

tdz mdn

  • 일시적인 사각지대

  • TDZ는 let, const, class 구문의 유효성을 관리한다.

  • TDZ는 선언 전에 변수를 사용하는 것을 허용하지 않는다.

  • var, function 선언은 TDZ에 영향을 받지 않는다. 이것들은 현재 스코프에서 호이스팅 된다.

  • var 변수는 선언 전에도 사용할 수 있기 때문에 var 사용은 피하는 것이 좋다.

    new Car('red'); // Uncaught ReferenceError: Car is not defined

    class Car {
    constructor(color) {
    this.color = color;
    }
    }
    greet('World'); // 'Hello, World!'

    function greet(who) {
    return `Hello, ${who}!`;
    }
    • 위 코드에서 new Car('red')는 사각지대에 있는 셈.

3. 매개변수가 3개 이상이 되면 객체로 넘겨주자

4. 조건부 요소로 배열을 초기화하기

  1. 삼항 연산자를 사용하여 배열 내부에 새로운 배열을 spread.
const myArray = ['a', 'b', 'c', 'd', ...(condition ? ['e'] : [])];
// condition이 true면 e가 추가되고, false면 배열에 아무것도 추가되지 않음
  1. 거짓값 필터링
const myArray = ['a', 'b', 'c', 'd', condition ? 'e' : null].filter(Boolean);
const myArray = ['a', 'b', 'c', 'd', condition ? 'e' : null].filter(
(x) => x !== null
);

5. shorthand properties (프로퍼티 축약)

  • 프로퍼티의 key 와 value 에 할당할 변수명이 동일한 경우 value 를 생략 가능합니다.

6. concise methods (간결한 메소드)

  • 콜론과 함수 키워드를 생략한 간결한 문법

7. 스위치문엔 default를 사용해 엣지케이스(방어코드)를 꼭 넣어주자.

엣지케이스 : 알고리즘이 처리하는 데이터의 값이 알고리즘의 특성에 따른 일정한 범위를 넘을 경우에 발생하는 문제

default: {
throw new Error ('what Id?')
}

8. if문을 너무 깊게 쓰기보다, 한 번 빼서 분리하는 게 좋다

9. if문으로 숫자의 boolean을 판별할 때 유의한다.

if(!id) { // dangerous!!!
...
}
!!0; //false
!!-1; //true

CS

1. 풀 사이클

  • 개발팀이 우수한 생산성 도구를 활용하여 소프트웨어 개발 수명주기(디자인, 개발, 테스트, 배포, 운영, 지원) 전체를 처리하는 모델

2. throw Error()를 쓰는 경우와 console.error()를 쓰는 경우가 있는데 어떻게 쓰는게 좋을지 잘 모르겠어요.

  • console은 애초에 webpack이나 빌드 시스템에서 걸러내야한다.
  • console은 시스템 메모리를 잡아먹기 때문에, console을 쓴다는 것은 클라이언트의 메모리를 잡아먹겠다는 것

Reference