jineecode

프리온보딩 프론트엔드 챌린지 2차 본문

챌린지

프리온보딩 프론트엔드 챌린지 2차

지니코딩 2022. 9. 6. 10:15

프리온보딩 프론트엔드 챌린지 2차 수강 도중 애매하게 알고 있었거나 새로이 알게된 정보 정리


1. new.target 

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

 

우리는 사용자를 위해 일부러 에러를 낼 수 있지만, 동료 개발자를 위해 에러를 낼 줄도 알아야 한다.

예를 들어, 생성자를 사용하여 init 코드를 써야만 한다고 해보자. 동료 개발자가 이를 쓰지 않았을 때, 에러를 내주어야 한다.

이럴 때 쓸 수 있는 것이 new.target 이다.

 

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/new.target

 

new.target - JavaScript | MDN

new.target 속성(property)은 함수 또는 생성자가 new 연산자를 사용하여 호출됐는지를 감지할 수 있습니다. new 연산자로 인스턴스화된 생성자 및 함수에서, new.target은 생성자 또는 함수 참조를 반환합

developer.mozilla.org

 

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?

풀어쓰면 일시적인 사각지대라는 뜻

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}!`;
}

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

위 코드에서 'new Car('red)'는 사각지대에 있는 셈이다.

https://dmitripavlutin.com/javascript-variables-and-temporal-dead-zone/

앞서 말했듯, tdz는 let, const, class 구문의 유효성을 관리한다. 반면, var, function 선언은 tdz에 영향을 받지 않는다. 이것들은 현재 스코프에서 호이스팅 된다.

 

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

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

 

참조:https://ui.toast.com/weekly-pick/ko_20191014

 

TDZ을 모른 채 자바스크립트 변수를 사용하지 말라

간단한 질문을 하나 하겠다. 아래 코드 스니펫에서 에러가 발생할까? 첫 번째 코드는 인스턴스를 생성한 다음 클래스를 선언한다.

ui.toast.com

 

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

4. 풀사이클?

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

 

 


 

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

https://codeburst.io/3-ways-to-initialize-an-array-with-conditional-elements-in-javascript-c95397615a7e

 

3 Ways to Initialize an Array with Conditional Elements in JavaScript

This blog will run you through 3 ways to initialize a JavaScript Array with conditional elements.

codeburst.io

1) 삼항 연산자를 사용하여 배열 내부에 새로운 배열을 spread 합니다.

const myArray = [
    'a', 'b', 'c', 'd',
    ... condition ? ['e'] : [],
];

condition이 true면 e가 추가되고, false면 배열에 아무것도 추가되지 않음 

 

2) 거짓값 필터링

const myArray = [
    'a', 'b', 'c', 'd',
    condition ? 'e' : null,
].filter(Boolean);
const myArray = [
    'a', 'b', 'c', 'd',
    condition ? 'e' : null,
].filter(x => x !== null);

 

 


 

1. TS 컨벤션 참조 사이트

1) google typescript style guide

https://google.github.io/styleguide/tsguide.html

 

Google TypeScript Style Guide

Google TypeScript Style Guide TypeScript style guide This is the external guide that's based on the internal Google version but has been adjusted for the broader audience. There is no automatic deployment process for this version as it's pushed on-demand b

google.github.io

2) typescript 코딩 지침 (기여자용)

https://github.com/microsoft/TypeScript/wiki/Coding-guidelines

 

GitHub - microsoft/TypeScript: TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

TypeScript is a superset of JavaScript that compiles to clean JavaScript output. - GitHub - microsoft/TypeScript: TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

github.com

 

2. soundness : 건전성. 형식 체계 내에서 증명 가능한 명제가 의미론 상으로도 참이 되는 성질

타입스크립트에는 soundness가 있는데 대표적으로 type assertion 이 있다.

const usersAge = ("23" as any) as number;

 

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

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

 

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

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

 

5. 어떤 객체의 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
  }
}

Good!

 

확장을 사용하여 위 코드보다 더 좋게 만들어보자.

 

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
  }
}

 

6. 자주 사용하는 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;
}

확장을 사용하는 방법도 있다.

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

interface DeleteTagParamsType extends Todo {
  tagIdx?: number;
}

typescript 유틸리티를 사용할 수도 있다. (Omit, Pick...)

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

type shoppingItem = Pick<Todo, "tagIdx">;

 

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

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

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

 

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

 

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

함수가 너무 많은 일을 하고 있을 수 있으므로...

 

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

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

 

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

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

 

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

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

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

 

13. 타입스크립트를 쓴다고 해서 코드가 완전히 안전하다고 할 수 없다. 

타입스크립트는 런타임에 개입하지 않기 때문에 (이는 soundness 특성 때문) 개발자는 Narrowing으로 타입을 좁혀주어야 한다.

 

14. unknown ? 

나도 이 타입이 뭔지 모르겠어~

any와 비슷하지만 unknown의 가장 큰 특징은 에러를 발생시킨다는 것에 있다.

let num: unknown = 99;

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

(num as string).trim()

해결법은 타입가드를 적용한다.

 

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

 

아래와 같이 Route를 상수로 정의한 객체가 있다고 하자.

newPath를 string으로 받으면, changeRoute에 존재하지 않는 route를 넣어도 typescript는 잡아주지 못한다.

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'))

 

리터럴 타입을 이용해본다.

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'))

 

Comments