TypeScript基礎2

JavaScript

TypeScript基礎の二回目です。前回からの続き。

今回も参考動画が以下になります。

ジェネリック型(Generics)

型を変数にいれて、後で使うときに型を指定することができる。

形としては以下のように<型>をいれます。以下のものはstringと指定されたもの

let array1:Array<string> = ['Appel', 'Banana', 'Mango'];

●関数での使い方

function genericFunction<T>(arg:T):T{
    return arg;
}

const stringValue = genericFunction<string>('Hello');
const numberValue = genericFunction<number>(568);


function createArray<T>(length:number, value:T):Array<T>{
    let result:T[] = [];
    result = Array(length).fill(value);
    return result;
}

let arrayStrings = createArray<string>(5, 'Hello');

このように、実行するときに型を決めて関数を使うことができます。ちなみにTはただの慣例なのでAでもBでもtypeでもなんでもいい。

●インターフェースでの使い方

interface GenericInterface<T>{
    id:number;
    value:T;
    getValue:()=> T;
}
const genericString:GenericInterface<string>={
    id: 1,
    value:'Hello',
    getValue() {
        return this.value;
    },
}

●型が複数ある場合

function pair<T,U>(param1:T, param2:U):[T, U]{
    return [param1, param2];
}

let result = pair<number, string>(123, 'hello');

ジェネリック制約

ジェネリックにも型の制約をつけて、違う型が来たときはエラーが出るようにできる

以下、extendsに続けて指定するstringとnumber以外の型は受け付けない。

function processValue<T extends string | number>(value:T):T{
    console.log(value);
    return value;
}

オブジェクトの中の要素など、実際あるかわからないときでも、制約であるタイプを指定しておけばTypeScriptはそれを理解します。

type Student = {
    name: string;
    age:number;
}

function printName<T extends Student>(input:T):void {
    console.log(input.name);
}

以下のようにあるプロパティの値の型付だけを指定することもできる

const sara = {
    name: 'sara',
    age: 32,
}

function printName<T extends {name:string}>(input:T):void {
    console.log(input.name);
}

Fetch Data

apiを叩いて返ってくる値はPromise<any>やanyになる。それを関数で処理しようと思うと型にanyをつけないとエラーとなる。

const url = 'https://dummyjson.com/products';

async function fetchData(url:string){
    try{
        const response = await fetch(url);
        if(!response.ok){
            throw new Error(`Http error! status: ${response.status}`);
        }
        const { products } = await response.json();
        console.log(products);
        return products;
    }catch(err){
        const errmsg = err instanceof Error? err.message : 'there was an error';
        console.log(errmsg);
        return [];
    }
} 

const products = await fetchData(url);
products.map((product:any)=> {
    console.log(product.title);
})

返ってくるAPIデータの型をタイプで作って厳密に指定すれば後の処理で型付をする必要はありません。(ただし無いプロパティを型指定しててもチェックしてくれません)

const url = 'https://dummyjson.com/comments';

async function fetchData(url:string):Promise<Comment[]>{
    try{
        const response = await fetch(url);
        if(!response.ok){
            throw new Error(`Http error! status: ${response.status}`);
        }
        const result = await response.json();
        const comments:Comment[] = result.comments;
        console.log(comments);
        return comments;
    }catch(err){
        const errmsg = err instanceof Error? err.message : 'there was an error';
        console.log(errmsg);
        return [];
    }
} 

type Comment = {
    id:number;
    body:string;
    postId:number;
    likes:number;
    user:{
        id:number;
        username:string;
        fullName:string;
    }
}

const comments = await fetchData(url);
comments.map((comment)=> {
    console.log(comment.body);
})

クラスの型付

コンストラクター、プロパティそれぞれに型注釈をつける必要があります。

class Book {

    title:string;
    author: string;
    checked:boolean = false;

    constructor(title:string, author:string){
        this.title= title;
        this.author = author;
    }
}

const deepWork = new Book('Deep Work', 'Cal Newport');

readonly をつけたり、privateをつけることで外からアクセスできないようにできる

class Book {

    public readonly title:string;
    public author: string;
    private checked:boolean = false;

    constructor(title:string, author:string){
        this.title= title;
        this.author = author;
    }
    public check(){
        this.checked = true;
    }
}

const deepWork = new Book('Deep Work', 'Cal Newport');
deepWork.check();

引数に一気に指定することもできる

class Book {
    private checked:boolean = false;
    constructor( readonly title:string, public author:string, private someValue:number){
        
    } 
}

getとset

class Book {
    private checked:boolean = false;
    constructor( readonly title:string, public author:string){}
    
    get info(){
        return `${this.title} by ${this.author}`;
    }
        
    set checkOut(checked: boolean){
        this.checked = checked;
    } 
}

const deepWork = new Book('Deep Work', 'Cal Newport');
deepWork.checkOut = true;
console.log(deepWork);

インターフェース

インターフェースはオブジェクトだけでなくクラスの型も定義できる

interface IPerson {
    name:string;
    age: number;
    greet():void;
}

class Person implements IPerson {
    constructor(public name:string, public age:number){}
    greet():void {
        console.log(`Hello ${this.name} and I'm ${this.age} years old`);
    }
}

const hipster = new Person('bob', 100);
hipster.greet();

DOMの型付

非nullアサーション演算子

DOM操作するとき、querySelectorで返ってくるのはElement | nullになります。確実にElementがあると伝えるため非nullアサーション演算子「!」をつけるとオプショナルチェーンなどをつけることなくメソッドが使えます。

const btn = document.querySelector<HTMLButtonElement>(".test-btn")!;

btn.disabled = true;

ちなみに上記のbutton elementはそう伝えないとdisabledのようなその要素でしか使えないプロパティやメソッドが使えません。

const btn = document.querySelector(".test-btn") as HTMLButtonElement;

if(btn){
    btn.disabled = true;
}

他にも as でどの要素かを明示したり、ifでフォールバックすることもできます。

ちなみにaddEventlistenerのsubmitで発火するeventの型はSubmitEventだったりする。

function createTask(event:SubmitEvent){
    event.preventDefault();
    const taskDescription = formInput?.value;
    if(taskDescription){
        //add task to list
        const task:Task = {
            description:taskDescription,
            isCompleted: false,
        }
        addTask(task);
        
        //render tasks
        renderTask(task);

        //update local storage
        updateStorage();
        formInput.value = '';
        return;
    }
    alert('Please enter a task description');
}

taskForm?.addEventListener('submit', createTask);