[Type Script独学]#10機能型スクリプト(4)

60880 ワード

開始します。


今回は効果型シナリオの7,8話で終わり、すべての内容を整理しました.最後のセクションでは、既存のJavaScriptプロジェクトをタイプスクリプトに変換する際に注意すべき事項と、どの機能を使用してどのような順序で操作するかについて重点的に説明します.

第7章コードの作成と実行


タイプスクリプト機能ではなくECMAScript機能の使用

enum Flavor {
  VANILLA = 0,
  CHOCOLATE = 1,
  STRAWBERRY = 2,
}

let flavor = Flavor.CHOCOLATE;  // Type is Flavor

Flavor  // Autocomplete shows: VANILLA, CHOCOLATE, STRAWBERRY
Flavor[0]  // Value is "VANILLA"

//상수 enum은 런타임에 완전히 제거됨
let flavor = Flavor.CHOCOLATE;  // Type is Flavor
    flavor = 'strawberry';
 // ~~~~~~ Type '"strawberry"' is not assignable to type 'Flavor'

 //문자열 enum은 명목적 타이핑(타입의 이름이 같아야 할당이 허용됨)을 사용하여 타입 안전성을 제공함.
type Flavor = 'vanilla' | 'chocolate' | 'strawberry';

let flavor: Flavor = 'chocolate';  // OK
    flavor = 'mint chip';
 // ~~~~~~ Type '"mint chip"' is not assignable to type 'Flavor'

//타입스크립트와 자바스크립트의 동작이 다르기 때문에 문자열 enum 대신 리터럴 타입의 유니온을 사용하는 것이 좋음.
class Person {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}

//일반적으로 클래스르 초기화할때 속성을 할당하기 위해 생성자 함수의 매개변수를 사용함
class Person {
  constructor(public name: string) {}
}

//타입스크립트는 더 간단한 문법(매개변수 속성)으로 클래스의 프로퍼티를 초기화 할 수 있음
class Person {
  first: string;
  last: string;
  constructor(public name: string) {
    [this.first, this.last] = name.split(' ');
  }
}

//기존 생성자 함수 방식과 매개변수 속성을 혼용해서 사용하면 일관성이 없음
namespace foo {
  function bar() {}
}

//es6 이후 모듈 시스템과의 충돌을 방지하기 위해 typescript에서는 namespace를 도입함
//호환성을 위해 남아있을뿐 import와 export를 사용해야 함
// tsConfig: {"experimentalDecorators":true}

class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
  @logged
  greet() {
    return "Hello, " + this.greeting;
  }
}

function logged(target: any, name: string, descriptor: PropertyDescriptor) {
  const fn = target[name];
  descriptor.value = function() {
    console.log(`Calling ${name}`);
    return fn.apply(this, arguments);
  };
}

console.log(new Greeter('Dave').greet());
// Logs:
// Calling greet
// Hello, Dave

//데코레이터(@)는 클래스, 메서드 속성에 기능을 추가할 수 있는 문법으로 표준화가 완료되지 않아 타입스크립트에서는 사용하지 않는 것이 좋음
//데코레이터는 처음에 엥귤러 프레임워크를 지원하기 위해 추가됨
####オブジェクトを巡るテクニック
const obj = {
  one: 'uno',
  two: 'dos',
  three: 'tres',
};
for (const k in obj) {
  const v = obj[k];
         // ~~~~~~ Element implicitly has an 'any' type
         //        because type ... has no index signature
}

//obj내 인데스 시그니처가 없기 때문에 any로 인식됨
const obj = { /* ... */ };
// const obj: {
//     one: string;
//     two: string;
//     three: string;
// }
for (const k in obj) {  // const k: string
  // ...
}

//obj는 'one', 'two', 'three'  개의 키만 존재하지만 k는 string으로 인식하여 오류가 발생함
const obj = {
  one: 'uno',
  two: 'dos',
  three: 'tres',
};
for (const k in obj) {
  const v = obj[k];
         // ~~~~~~ Element implicitly has an 'any' type
         //        because type ... has no index signature
}
let k: keyof typeof obj;  // Type is "one" | "two" | "three"
for (k in obj) {
  const v = obj[k];  // OK
}

//k의 타입을 구체적으로 명시해주면 에러가 사라짐
function foo(abc: ABC) {
  for (const k in abc) {  // const k: string
    const v = abc[k];
           // ~~~~~~ Element implicitly has an 'any' type
           //        because type 'ABC' has no index signature
  }
}
const x = {a: 'a', b: 'b', c: 2, d: new Date()};
foo(x);  // OK

//foo 함수는 a,b,c 속성 외에 할당 가능한 어떠한 값이든 매개변수로 허용하기 때문에 d 속성을 가지고 있는 x를 매개변수로 전달할 수 있음
//그러므로 k의 타입이 'one', 'two', 'three'가 아닌 string으로 유추됨
function foo(abc: ABC) {
  for (const [k, v] of Object.entries(abc)) {
    k  // Type is string
    v  // Type is any
  }
}

//키와 값을 순회하고 싶다면 Object.entries를 사용하면 됨

DOM階層の理解

function handleDrag(eDown: Event) {
  const targetEl = eDown.currentTarget;
  targetEl.classList.add('dragging');
// ~~~~~~~           Object is possibly 'null'.
//         ~~~~~~~~~ Property 'classList' does not exist on type 'EventTarget'
  const dragStart = [
     eDown.clientX, eDown.clientY];
        // ~~~~~~~                Property 'clientX' does not exist on 'Event'
        //                ~~~~~~~ Property 'clientY' does not exist on 'Event'
  const handleUp = (eUp: Event) => {
    targetEl.classList.remove('dragging');
//  ~~~~~~~~           Object is possibly 'null'.
//           ~~~~~~~~~ Property 'classList' does not exist on type 'EventTarget'
    targetEl.removeEventListener('mouseup', handleUp);
//  ~~~~~~~~ Object is possibly 'null'
    const dragEnd = [
       eUp.clientX, eUp.clientY];
        // ~~~~~~~                Property 'clientX' does not exist on 'Event'
        //              ~~~~~~~   Property 'clientY' does not exist on 'Event'
    console.log('dx, dy = ', [0, 1].map(i => dragEnd[i] - dragStart[i]));
  }
  targetEl.addEventListener('mouseup', handleUp);
// ~~~~~~~ Object is possibly 'null'
}

   const div = document.getElementById('surface');
   div.addEventListener('mousedown', handleDrag);
// ~~~ Object is possibly 'null'

// 구체적이지 않은 타입 지정으로 인해 이벤트 핸들러 등록 시 에러가 발생함
function handleDrag(eDown: Event) {
  const targetEl = eDown.currentTarget;
  targetEl.classList.add('dragging');
// ~~~~~~~           Object is possibly 'null'
//         ~~~~~~~~~ Property 'classList' does not exist on type 'EventTarget'
  // ...
}

//Event.currentTarget의 타입은 EventTarget || null 이고 ClassList 속성이 없기 때문에 에러가 발생함.
document.getElementsByTagName('p')[0];  // HTMLParagraphElement
document.createElement('button');  // HTMLButtonElement
document.querySelector('div');  // HTMLDivElement

//구체적인 태그의 정보를 사용하면 정확한 타입을 지정할 수 있음
document.getElementById('my-div') as HTMLDivElement;

//getElementById를 사용하는 경우 as HTMLDivElement로 타입을 단언해주어야 함
//my-div는 div 태그라는 것을 알고 있기 때문에 단언문을 사용해도 문제가 되지 않음
function addDragHandler(el: HTMLElement) {
  el.addEventListener('mousedown', eDown => {
    const dragStart = [eDown.clientX, eDown.clientY];
    const handleUp = (eUp: MouseEvent) => {
      el.classList.remove('dragging');
      el.removeEventListener('mouseup', handleUp);
      const dragEnd = [eUp.clientX, eUp.clientY];
      console.log('dx, dy = ', [0, 1].map(i => dragEnd[i] - dragStart[i]));
    }
    el.addEventListener('mouseup', handleUp);
  });
}

const div = document.getElementById('surface');
if (div) {
  addDragHandler(div);
}

//Event 대신 MouseEvent로 타입을 선언하여 clientX, clientY 속성을 사용할 수 있음

privateを無効にして情報を非表示にする

class Diary {
  private secret = 'cheated on my English test';
}

const diary = new Diary();
diary.secret
   // ~~~~~~ Property 'secret' is private and only
   //        accessible within class 'Diary'

//pubilic, protected, private 같은 접근 제어자는 타입스크립트 키워드로 컴파일 이후 제거됨
class Diary {
  private secret = 'cheated on my English test';
}

const diary = new Diary();
(diary as any).secret  // OK

//private를 사용해도 실행 시 해당 속성에 접근 가능하고, 단언문을 사용하면 타입스크립트 상태에서도 접근이 가능하기 때문에
//속성을 숨기기 위한 목적으로 private 키워드르 사용해선 안됨
declare function hash(text: string): number;

class PasswordChecker {
  checkPassword: (password: string) => boolean;
  constructor(passwordHash: number) {
    this.checkPassword = (password: string) => {
      return hash(password) === passwordHash;
    }
  }
}

const checker = new PasswordChecker(hash('s3cret'));
checker.checkPassword('s3cret');  // Returns true

//javaScript에서는 클로저를 사용하여 속성을 숨길 수 있지만, 인스턴스를 생성할 때마다 메모리가 낭비되고
//개별 인스턴스간에 해당 속성에 대한 접근이 불가능하기 때문에 불편함
declare function hash(text: string): number;
class PasswordChecker {
  private password: string;

  constructor() {
    this.password = 's3cret';
  }

  checkPassword(password: string) {
    return password === this.password;
  }
}

const checker = new PasswordChecker();
const password = (checker as any).password;

//개별 인스턴스간 접근이 가능한 비공개 속성을 설정하려면 현재 표준화가 진행 중인 비공개 필드 기능(#)을 사용할 수 있음

ソースマッピングデバッグタイプスクリプトの使用

// 타입스크립트 코드
function addCounter(el: HTMLElement) {
  let clickCount = 0;
  const button = document.createElement('button');
  button.textContent = 'Click me';
  button.addEventListener('click', () => {
    clickCount++;
    button.textContent = `Click me (${clickCount})`;
  });
  el.appendChild(button);
}

addCounter(document.body);
//브라우저에서 컴파일된 코드
function addCounter(el: HTMLElement) {
  let clickCount = 0;
  const triviaEl = document.createElement('p');
  const button = document.createElement('button');
  button.textContent = 'Click me';
  button.addEventListener('click', async () => {
    clickCount++;
    const response = await fetch(`http://numbersapi.com/${clickCount}`);
    const trivia = await response.text();
    triviaEl.textContent = trivia;
    button.textContent = `Click me (${clickCount})`;
  });
  el.appendChild(triviaEl);
  el.appendChild(button);
}

//타입스크립트는 런타임시 자바스크립트로 변환되기 때문에 디버깅시 어느 부분에서 오류가 발생했는지 파악하기 쉽지 않음
//컴파일 옵션으로 "sourceMap": true를 설정하면 .js.map 파일이 생성되어 소스 코드를 사용하여 디버깅을 할 수 있음.

第8章タイプスクリプトへの移行


@ts-checkとJSDocを使用してタイプスクリプトをテスト

// @ts-check
const person = {first: 'Grace', last: 'Hopper'};
2 * person.first
 // ~~~~~~~~~~~~ The right-hand side of an arithmetic operation must be of type
 //              'any', 'number', 'bigint', or an enum type

//@ts-check를 사용하면 타입스크립트 전환 시 어떤 오류가 발생하는지 미리 확인할 수 있음
interface UserData {
  firstName: string;
  lastName: string;
}
declare let user: UserData;

//숨어 있는 변수를 제대로 인식할 수 있도록 별도의 타입 선언 파일을 만들어야 함interface UserData {
  firstName: string;
  lastName: string;
}
declare let user: UserData;

//숨어 있는 변수를 제대로 인식할 수 있도록 별도의 타입 선언 파일을 만들어야 함
// checkJs
// tsConfig: {"allowJs":true,"noEmit":true}
// requires node modules: @types/jquery, @types/sizzle

// @ts-check
$('#graph').style({'width': '100px', 'height': '100px'});
         // ~~~~~ Property 'style' does not exist on type 'JQuery<HTMLElement>'

//서드파티 라이브러리의 경우 @types를 설치하여 타입 선언을 해주어야 함
// @ts-check
const ageEl = /** @type {HTMLInputElement} */(document.getElementById('age'));
ageEl.value = '12';  // OK

//Dom에 접근할 때 @ts-check에서는 JSDoc을 사용하여 타입을 단언할 수 있음

依存関係に基づいてモジュールレベルに切り替える

// tsConfig: {"noImplicitAny":false,"strictNullChecks":false}

class Greeting {
  constructor(name) {
    this.greeting = 'Hello';
      // ~~~~~~~~ Property 'greeting' does not exist on type 'Greeting'
    this.name = name;
      // ~~~~ Property 'name' does not exist on type 'Greeting'
  }
  greet() {
    return this.greeting + ' ' + this.name;
             // ~~~~~~~~              ~~~~ Property ... does not exist
  }
}

//타입스크립트에서는 Class 멤버 변수의 타입을 명시적으로 선언해야 하고 quick fix 기능으로 간단히 수정할 수 있음
// tsConfig: {"noImplicitAny":false,"strictNullChecks":false}

const state = {};
state.name = 'New York';
   // ~~~~ Property 'name' does not exist on type '{}'
state.capital = 'Albany';
   // ~~~~~~~ Property 'capital' does not exist on type '{}'
const state = {
  name: 'New York',
  capital: 'Albany',
};  // OK

//한꺼번에 객체를 생성하면 타입 오류를 해결할 수 있음
interface State {
  name: string;
  capital: string;
}
const state = {} as State;
state.name = 'New York';  // OK
state.capital = 'Albany';  // OK

//한번에 생성하기 어려운 경우에는 단언문을 사용하면 됨
// @ts-check
/**
 * @param {number} num
 */
function double(num) {
  return 2 * num;
}

double('trouble');
    // ~~~~~~~~~ Argument of type '"trouble"' is not assignable to
    //           parameter of type 'number'

//JSDoc과 @ts-check를 사용하다가 타입스크립트로 변환하는 경우에는 num의 타입이 any로 추론되고 에러가 사라지므로 주의해야 함
function double(num: number) {
  return 2 * num;
}

double('trouble');
    // ~~~~~~~~~ Argument of type '"trouble"' is not assignable to
    //           parameter of type 'number'

//JSDoc의 타입 정보를 타입스크립트로 변환해주는 기능이 있음

の最後の部分


コード中心の内容を整理する際に漏れがあるので、実際の本を精読しながら整理した内容で復習する必要があると思います.本の内容は思ったより広いので、最初は理解できないこともあれば、javascriptの内容なので素早くめくることもあります.総じて、タイプスクリプトを正しく使用する方法について議論しているので、必要な内容を探すために横に置くべきだと思います.