이펙티브타입스크립트 20~31 답

공효은·2023년 11월 9일
0

1. 타입 추론의 강도를 직접 제어하려면 타입스크립트의 기본 동작을 재정의 해야한다. const 단언문을 사용하면 타입스크립트는 최대한 좁은 타입으로 추론한다. const 단언문을 사용했을때 타입스크립트가 타입을 어떻게 추론했을지 쓰시오!

1)

const v2 = {
  x: 1 as const,
  y: 2
}
{ x: 1; y: number; }

2)

const v3 = {
  x: 1,
  y: 2,
} as const;
{ readonly x: 1; readonly y: 2; }

2. 타입스크립트는 일반적으로 조건문에서 타입을 좁히는 데 매우 능숙하다. 하지만 실수를 저지르기 쉽다. 다음 예제는 유니온 타입에서 null을 제외하기 위해 잘못된 방법을 사용한 예시이다. 무엇이 잘못되었는지 설명하시오

const el = document.getElementbyId('foo'); // 타입이 HTMLElement | null
if (typeof el === 'object') {
  el; // 타입이 HTMLElement | null
}

답: 자바스크립트에서 typeof null 이 'object' 이기 때문에, if 구문에서 null이 제외되지 않았다.

3.

const jacksonn5 = ['Jackie', 'Tito', 'Jermaine', 'Marlon', 'Michael'];
const memebers = ['Janet', 'Michael'].map(
	who => jackson5.find(n => n === who)
).filter(who => who !== undefined); // 타입이 (string | undefined)[]

filter함수를 사용해 undefined를 걸러 내려고 해도 잘 동작 하지 않는다. 이럴때 어떻게 하면 타입을 좁힐 수 있을까? 예시 코드까지 작성하면 좋다~!
(string | undefined) [] => string[] 으로 만들고 싶다!

답: 타입 가드를 사용해서 타입을 좁히자
함수의 반환이 true인 경우, 타입 체커에게 매개변수의 타입을 좁힐 수 있다고 알려준다.

const isDefined<T>(x: T | undefined) x is T {
  return x !== undefined;
}
const jacksonn5 = ['Jackie', 'Tito', 'Jermaine', 'Marlon', 'Michael'];
const memebers = ['Janet', 'Michael'].map(
	who => jackson5.find(n => n === who)
).filter(isDefined); // 타입이 string[]

4. 다음코드에 대한 문제점으로 옳지 않은 것을 고르시오

interface State {
  pageText: string;
  isLoading: boolean;
  error?: string;
}
// 웹애플리케이션에서 페이지를 선택하면 페이지의 내용을 로드하고 화면에 표시하는 코드
function renderPage(state:State){
  if(state.error){
    return `Error! Unable to load ${currentPage} : ${state.error}`;
  } else if (state.isLoading){
    return `Loading ${currentPage}...`;
  }
  return `<h1>${currentPage}</h1>\n${state.pageText}`
}
// 페이지를 전환하는 함수
async function changePage(state: State, newPage: string){
  state.isloading = true;
  try {
    const response = await fetch(getUrlForPage(newPage));
    if(!response.ok){
      throw new Error(`Unable to load ${newPage}: ${response.statusText}`);
      const text = await response.text();
      state.isLoading = false;
      state.pageText = text;
    }
  } catche (e){
    state.error = '' + e;
  }
}
  1. State 타입은 isLoading이 true 이면서 동시에 error 값이 설정되는 무효한 상태를 허용하지 않는다.
  2. 오류가 발생했을 때 state.isLoading을 false로 설정하는 로직이 빠져있다.
  3. state.error를 초기화 하지 않아서 페이지 전환 중에 로딩 메시지 대신 과거의 오류 메시지를 보여준다.
  4. 페이지 로딩중에 사용자가 페이지를 바꿔 버리면 어떤 일이 벌어질 지 예상 하기 어렵다. 새 페이제에 오류가 뜨거나, 응답이 오는 순서에 따라 두번째 페이지가 아닌 첫 번째 페이지로 전환될 수 있다.

답:1. State 타입은 isLoading이 true 이면서 동시에 error 값이 설정되는 무효한 상태를 허용한다.
무효한 상태기 존재하면 render()와 chagnePage() 둘 다 제대로 구현할 수 없다.

문제는 상태 값의 두가지 속성이 동시에 정보가 부족하거나(요청이 실패한 것인지 여전히 로딩 중인지 알 수 없다.), 두가지 속성이 충돌(오류 이면서 동시에 로딩중 일 수 있다)할 수 있다는 것이다.

[개선]

interface RequestPending {
  state: 'pending';
}
interface RequestError {
  state: 'error';
  error: string;
}
interface RequestSuccess {
  state: 'ok';
  pageText: string;  
}
type RequestState = RequestPending | RequestError | RequestSuccess;

interface State {
  currentPage: string;
  request: {[page:string]:RequestState}
}
  1. 네트워크 요청 과정 각각의 상태를 명시적으로 모델링했다. => 무효한 상태를 허용하지 않음
  2. 현재 페이지는 발생하는 모든 요청의 상태로서, 명시적으로 모델링 되었다,
function renderPage(state: State){
  const {currentPage} = state;
  const requestState = state.requests[currentPage];
  switch (requestState.state){
      case: 'pending';
      	return `Loading ${currentPage}...`;
	  case: 'error';
		return `Error! Unable to load ${currentPage} : ${state.error}`;
	  case: 'ok';
		return `<h1>${currentPage}</h1>\n${state.pageText}`;
  }
}
async function chagePage(state: State, newPage:string){
  state.requests[newPage] = {state: 'pending'};
  state.currentPage = newPage;
  
  try{
    const response= await fetch(getUrlForPage(new))
    if(!response.ok){
      throw new Error(`Unable to load ${newPage}: ${response.statusText}`)
    }
    const pageText = await resposne.text();
    state.requests[newPage] = {state: 'ok', pageText}
  }
  catch(e) {
    state.requests[newPage] = {state: 'error', error:''+ e};
  }
}

5. 다음중 주석에 대한 문제점으로 틀린것을 고르시오

/**
* 전경색(foreground) 문자열을 반환한다.
* 0 개 또는 1개의 매개변수를 받는다.
* 매개변수가 없을 때는 표준 전경색을 반환한다.
* 매개변수가 있을 때는 특정 페이지의 전경색을 반환한다.
*/
function getForegroundColor(page?:string){
  return page === 'login' ? {r: 127, g:127, b:127}: {r:0, g:0, b:0};
}
  1. 함수가 string 형태의 색깔을 반환한다고 적혀있지만 실제로는 rgb 객체를 반환한다.
  2. 함수가 0개 또는 1개의 매개변수를 받는다고 설명하고 있지만, 타입 시그니처만봐도 명확히 알 수 있다.
  3. 불필요하게 장확하다. 함수 선언과 구현체보다 주석이 더 긹다.
  4. 누군가 강제하지 않는 이상 주석은 코드와 동기화 되지 않는다.
  5. 주석에 타입 정보를 적는것 은 좋다.

답: 주석에 타입 정보를 적는것은 피하자. 타입 선언이 중복되는 것으로 끝나면 다행이지만 최악의 경우 타입정보에 모순이 발생한다.
타입 구문은 타입스크립트 타입 체커가 타입 정보를 동기화하도록 강제한다. 주석대신 타입 정보를 작성한다면 코드가 변경된다 하더라도 정보가 정확히 동기화 된다.

개선

/**
애플리케이션 또는 특정 페이지의 전경색을 가져온다.
*/
function getForegroundColor(page?:string){
  return page === 'login' ? {r: 127, g:127, b:127}: {r:0, g:0, b:0};
}
profile
잼나게 코딩하면서 살고 싶어요 ^O^/

0개의 댓글