typescript学習メモ2

モジュール

パッケージ

ソフトウェアの最小単位の塊。package.jsonのdependeciesに記載されているやつ

モジュール

一般的にTypescript/Javascriptの1つのソースファイルを指す(.ts/.js)。パッケージ⊃モジュール。 バンドラー(webpackなど)を使用すると、import/exportを解析して単一のjsファイルを生成する場合がある。

export/import

export キーワードをつけると他のファイルから利用ができるようになる。 defaultキーワードをつけると、import時に自動で読み込まれる要素を選択することができる。

export const favorite = "小籠包";
export default name = "hoge"
export function fortune() {
const i = Math.floor(Math.random() * 2);
return ["小吉", "大凶"][i];
}
export class SmallAnimal {
}

import キーワードで取り込みたい要素を選択する。

// 名前を指定してimport
import { favorite, fortune, SmallAnimal } from "./smallanimal";
// リネーム
import { favorite as favoriteFood } from "./smallanimal";

//defaultを指定している場合、hogeがdefault要素になる。
import hoge from "./smallanimal";

importパス

  • 絶対パス
    • "./hoge"のピリオドから始まる形式。import文が書かれたファイルのフォルダを起点にしてファイルを探す。
  • 相対パス
    • ピリオド以外から始まる。typescriptの場合、以下の2つのパスを起点とする。
    • tsconfig.jsoncompilerOptions.baseDir起点
    • node_modulesディレクトリ以下。仮にnode_modulesが2つある場合は対象フォルダの親ディレクトリに近いパスが起点になる。(親を辿って行って最初に見つかったパスを起点にする。)

動的import

import/exportはコード実行時に全て解決している前提で処理される。しかし、起動を早くしたい場合動的にimportを行うことができる。 Promiseを返すimport()関数を利用する。(ES2018以降有効) 必要なタイミングで、const module = await import('./utils/create-zip-file');みたいに書けばよい。

まとめてexport

エントリーポイントとなるスクリプトに公開要素を1つのファイルにまとめることができる。 partA-C 3つのファイルに分けて、1つのファイルでまとめて公開する。

export {ele1, ele2} from "./partA";
export {ele3 as mainEle} from "./partB";
export {ele4 as default} from "./partC";

自動でimport

tsconfig.jsoncompilerOptions.typesで自動importするモジュールを指定できる。なんかよくわからないモジュールがあったら、ここから呼ばれているかも。

tsconfigのmodule

CommonJSとの違いの章でそういやCommonJSってなんだという感じになって調べてみた。 下記が分かりやすかった。 https://zenn.dev/naoki_mochizuki/articles/46928ccb420ee733f78f

javascriptには仕様が乱立してて、typescriptのコンパイル後にどの仕様にあわせて変換するかをtsconfig.jsのmoduleで選択できる。仕様の違いの一つにimport/exportの方法がある。 CommonJSの方法でもモジュールの読み込みなどを書くことはできるっぽいが、特に利点はないので気にしない。

console.log

console.log({object})でオブジェクトのキーと値が出力される。

console.tableでテーブル形式で表示される。 console.dirというツリー構造で表示する出力もあるが、Node.jsではサポートされていない。(console.logと同じ扱い。ブラウザの開発者ツールだとツリー形式で表示される。)

const ob = {
 test: "hoge",
 hoge: "muge"
}

console.table({ob});

┌─────────┬────────┬────────┐
│ (index) │  test  │  hoge  │
├─────────┼────────┼────────┤
│   ob    │ 'hoge''muge' │
└─────────┴────────┴────────┘

ジェネリクス

基本Javaと同じ

パラメータ制約もextendsでつけれる。型推論が効く。

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

//引数で型が分かるので、省略可
const value = multiply(-1, 10);
const hoge  = multiply<string>("number", 10);

console.table(value);
console.table(hoge);

extends

type Person = {
    getBirthDay(): Date;
}

function isTodayBirthday<T extends Person>(person: T): boolean {
    const today = new Date();
    // person の型は少なくともPerson を満たす型なのでgetBirthDay() メソッドが利用可能
    const birthDay = person.getBirthDay();
    return today.getMonth() === birthDay.getMonth() && today.getDate() === birthDay.
    getDate();
}

const p:Person = {
    getBirthDay: ()=> {
        return new Date("2020-01-01");
    }
}
const r = isTodayBirthday(p);
console.log(r);

下記のような型パラメータもおk。Tが決まれば、Kも決まる。T/Kが決まればUが決まるので全部解決可能。

  • T
    • オブジェクトの型
  • K
    • Tオブジェクトのプロパティ名の合併型
      • keyof T -> "Tプロパティ1" | "Tプロパティ2" | "Tプロパティ3"
  • U
    • オブジェクトのプロパティの型
function setValue<T, K extends keyof T, U extends T[K]>(obj: T, key: K, value: U) {
    obj[key] = value;
}

type ParkForm = {
    name:string,
    hasTako:boolean
}

const park: ParkForm = {
    name: "恵比寿東",
    hasTako: true
};

console.table({park});
setValue(park, "name", "神明児童遊園");
console.table({park});

オブジェクトに対するユーティリティ型がいくつか用意されている。オブジェクトのプロパティを必須にしたり、省略可能にしたりできる。下記一例。

type User = {
    name: string,
    organization:string;
}

const u:User = {
    name: "u",
    organization: "unknown"
}

//プロパティが省略可能に
const uPratial: Partial<User> = {
    name: "u"
}

type Client = {
    name?: string,
    organization?:string;
}

const c:Client = {}

//プロパティが必須に
const uRequired: Required<Client> = {
    name: "hoge",
    organization: "fuga"
}

//undefinedが抜ける
type year = NonNullable<string | number | undefined>
//重複した方が抜ける
type typeString   = Exclude<string | number | boolean, number| boolean>
//重複した型が選択される
type typeNumber   = Extract<string | number, number| boolean>

any/unkown

any/unkownを使用すると型情報がリセットされる。可能ならジェネリクスを使った方が良い。

一通り読み終わった。あまり目新しい機能はなかったけど、ジェネリクスで型を操作したり、プロパティの属性を操作したりするユーティリティは勉強になった。あまり使いどころが分からないが...

typescript学習メモ

代入型定義がないとany型になる。 let hoge;

enumよりunion型でenumを表現するのが今時らしい

jsのStringはUTF-16エンコードされる。サロゲートペア文字を扱う場合、一部だけを拾ってしまう可能性があることに注意する。

文字列を正規化するString.normalizeメソッドが存在する。 同様の意味だが、コードポイントが異なる文字を正規化する。 例) 平成㍻ 正規化には4つオプションが存在する。 normalize("NFKC")を使っておけばとりあえずおk

undefined値が入っていない状態。null明示的な値が存在しない場合。 union型で、 let hoge: string | null = "test"; みたいに定義することで、この値はnullになりうるよ。と伝える必要がある。

タプル... const movie:[string, number] = ['Gozila', 1954]; 異なる型を持つことができる。 movie[0]//Gozila indexアクセスしかできない。

まとめて配列要素の取り出し

const smalls = [
"hoge",
"fuga",
"hyoga"
];

const [A, B, C] = samlls;

残余構文

const [,...other] = smalls; console.log(other);//['fuga', 'hyoge']

const others = ["gyoe", "gyopi"]; const newSmalls = [...smalls.slice(0,2), "P", ...others] //"hoge", "fuga", "P", "fuga", "hyoge"

配列ソート hoge.sort中の文字列を辞書順でソートする。

hogeの中身が数値の場合、注意が必要。 const number = [30, 1, 200];

元の配列を破壊せずにソート指定場合は、コピーを作成してソートする。 const sorted = [...hoge].sort((a,b) => a - b));

for

indexが欲しい場合

for(const [i, value] of iterable.entries()) {
 console.log(i, value);
}

index不要の場合

for(const value of iterable)

イテレータを配列に変換

const names = Array.from(iterable);
const names = [...iterable];

読み込み専用の配列

constは再代入を禁止するが、配列をimmutableにはできない。 readonly or as constを使用する。

const a: readonly number[] = [1,2,3];
const b = [1,2,3] as const;

readonlyな配列は制約が強い。mutableな配列を引数に持つ関数・メソッドにreadonlyは渡せない。mutableな配列にreadonlyな配列を代入することはできない。readonlyを使用するなら徹底的に使用するか、使用しないか全体で決めた方が良い。

配列っぽいオブジェクト

HTMLのDOM操作時に取得されるオブジェクト(HTMLCollection/NodeList)これらはlengthで長さが取得でき、indexアクセスできるが、配列が持っているメソッドをほとんど持っていない。 どちらもイテレータではあるので、下記コードは利用可能。 - for ... ofループ - スプレッド構文 - Array.from()で配列に変換後、配列メソッド利用

オブジェクト

値の取り出し const smallAnimal = { name: "小動物", favorite: "小籠包" };

const {name, favorite, age=3} = smallAnimals; const {name, ...others} = smallAnimals;

オブジェクト自身がnullであったり、オブジェクトの要素がnull/undefinedだったりする場合がある。 このような場合、オブジェクト/要素のチェックが必要になる。 これを簡便に行うためにオプショナルチェイニングという方法が存在する。 const favorite = smallAnimals?.favorite?.toUppercase(); nullがundefinedな値があると評価を停止する。

オブジェクトとMap Key オブジェクト 文字列固定、ユニーク Map 可変の連想配列

値 オブジェクト 複数の型 Map 一定

配列のループにfor..inを使用すると劇遅い

=== 厳密な比較。配列やオブジェクトの比較時は同一インスタンスか確認する。 == 文字列に変換後、比較

[条件] && 真の場合の値 👆新出ない場合はfalseが変える

タグ付き合併型

typeof {変数}で型名を文字列で返す。プリミティブ型のみ有効 nullは"object"になるので、nullチェックには使えない。

interface と Typeどうちがうん?

拡張方法が違う

interface A {
 hoge: string;
}

interface A {
 fuga: string;
}

///最終的にこーなる。
inteface A {
    hoge: string;
    fuga: string;
}

意図しない型になっている可能性がある。 typeは重複定義となりエラー。typeで拡張子たければ別の型を宣言する。

type A = {
 hoge: string;
}


type B = {
 fuga: string;
}

type Aextends = A & B;

Typescriptでオーバーロードは使えない

returnの返り値が一つなら、返り値の型は推定される。

getter/setter

{
    _favorite: string
    get favorite() {
        return this._favorite;
    },

    set favorite(v) {
        this._favorite = v;
    },

    function c() {

    }
}

アロー関数

無明関数をコールバック関数に渡すとthisが分からなくなる。 アロー関数を使用した場合、アロー関数が定義された時点でのthisを保持することができる。

時刻

サーバー側ではエポック時刻で時刻を扱い、クライアント側でローカル時刻に変換するのが良い。

JSON

//any型
const a = JSON.parse('{"name": "Jhon Cleese"}');

//asで型情報を付与できる。
const p = JSON.parse('{"name": "Jhon Cleese"}') as Person;

コンストラクタの引数でプロパティ宣言

class SmallAnimal {
    
    constructor(private animaltype: string = "ポメラニアン") {
    }

    say() {
        console.log("hoge");
    }
}

//👆とだいたい同じ
class SmallAnimal {

    private animalType:string;
    
    constructor() {
        this.animalType = "ポメラニアン"
    }

    say() {
        console.log("hoge");
    }
}

非同期処理

import fetch from 'node-fetch';

async function test() {
    console.log("Start");
    const resp = await fetch("https://google.com")
    const json = await resp.text();
    console.log(json);
    console.log("End");    
    const v = await sleep(1000);
    //Hoge 1
    console.log(`Hoge ${v}`); 
}

test();

const sleep = async (time: number) : Promise<number> => {
    //resolver 成功時に呼ぶ関数。引数に渡した値が返る。
    //rejector 省略可能で、処理失敗時に呼ぶ関数
    return new Promise<number>((resolver, rejector ) => {
        setTimeout(() => {
            resolver(1);
        }, time)
    });
};

以下のforEach内のawaitは待たない。

iterable.forEach(async value => {
await hogehgoe();
}

for/of/if/while/switchは待つ。

    for (const value of urlList) {
        const resp = await fetch(value)
        const json = await resp.text();
        console.log(json);
    }    

Promise all

Promiseをforでawaitすると実行時間×要素数の時間がかかるため、思いがけないボトルネックになる可能性がある。 Promise.allを使用すると同時に実行されるため、このような問題が回避できる。 同時実行数が多すぎると負荷がかかるのでは?→かかる。ヒープを食いつぶす可能性もある? 以下のように書くと上限値を設けながら効率よく処理を行うことができる

async function verySlowAsync(num: number , index: number) {
    console.log(`${num} ${index}`);
    return new Promise((resolve) => setTimeout(resolve, Math.random() * 1000));
}

async function execute() {

    console.time('execute');

    const INIT = 0;
    const MAX = 100;
    const CONCURRENCY = 10; // 同時実行できる数を定義

    //cntは並列タスク内で共通のindex
    let cnt = INIT;
    let promises = [];

    for (let i = 0; i < CONCURRENCY; i++) {
        //executor(loop)はPromise生成時に実行を開始している。Promiseのexecutor自体は同期的に実行される。
        let p = new Promise((resolve) => {
            //以下は並列に実行される。
            (async function loop(index) {
                if (index < MAX) {
                    await verySlowAsync(i, index);
                    loop(cnt++); //indexだと並列タスクの足並みがそろわない。
                    return;
                }
                resolve(0);
            })(cnt++);
        });
        // ここではPromiseで定義されたタスクをpushしているだけ。
        promises.push(p);
        console.log("End Push",i);
    }

    console.log("Promise.all");
    await Promise.all(promises);

    console.timeEnd('execute');

}

execute();

https://qiita.com/tonio0720/items/6f9319e4cce53256b4c9

例外処理

基本Javaと一緒。ただ、型情報がないので、catch後にinstanceを調べて処理を分岐する。

try {
    // 何かしらの処理
} catch (e) {
    // instanceof を使ってエラーの種類を判別していく
    if (e instanceof NoNetworkError) {
    
    } else if (e instanceof NetworkAccessError) {
    
    } else {
    // その他の場合
    }
}

fetch APIのように例外ではなく、okのような確認メソッドを用意する方法もある。

jsの例外処理の弱点 - メソッドが投げる例外を明示できない。ドキュメント・ソースを確認するしかない。 - 型情報がないので、instanceofで区別するしかない - Promise/asyncで何がrejectに渡されるのか型定義に書く方法がない。

返り値でエラーを返す方法を採用することもできる。 下記合併型を使用する例。タプル・オブジェクトの中にエラーを含めて返す方法もある。 やり方は色々

// 合併型
function create3(name: string, age: number): User | Error {
    if (age < 0) {
        return new Error("before born");
    }
    return {name, age};
}

夏の賞与

去年より額面は60弱で、手取りが45くらいだった。 賞与所得税が去年に比べてプラス4万くらいとられていた。 どうやら先月の給与所得をベースに計算されるらしい。

5月10月は残業しない方がお得なのかもしれない。

入社してから賞与が上がる気配がないどころか年々さがっているような気もする。 会社として調子がいいわけではないので仕方ないのだが

yarn使用時のError: Failed to load plugin 'jsx-a11y' declared in '.eslintrc.js時の対処法...かも

問題

Oops! Something went wrong! :(
ESLint: 7.28.0

Error: Failed to load plugin 'jsx-a11y' declared in '.eslintrc.js': Cannot find module 'core-js-pure/stable/object/define-property'
Require stack:

対応

git clean -dfx
yarn cache clean

Keyhack(Ctrl + K) IMEの切り替え

KeyhackでCtrl+K(一行カット)を設定しているが、Ctrl+Kを押下するとIMEが切り替わってしまうショートカットに入力がとられてしまいこまっていた。下記設定を行うと問題が直った。 設定>キーボードの詳細設定>入力言語ホットキー>キーシーケンスの割り当てを解除する。

PowershellのRemoveChildでプチハマったこと

Foreach($node in $list) {
    if($node.Name -eq "hoge") {
           $target.RemoveChild($node) 
    }
}

みたいなことをやっていた。 これだと、nodeを含む情報自身をForeach内で破壊していたため、ループが最後まで走査されなくなっていた。お目当てのものを予めリストに保持して後で削除するとうまくいく。

別にPowershellに限った話ではないが、ついついこーいうことをやってしまう