TypeScript
| Node.js | Angular | Google Cloud Platform | ブログカテゴリ(TypeScript) | JavaScript | React | Nextjs |
TypeScript
準備
install
> npm install -g typescirpt
init
tsconfig.json の生成
> tsc --init
Visual Studio Code タスク
F1 - task run - build tsconfig.json
Proejctテンプレート
- https://www.typescriptlang.org/docs/bootstrap
- TypeScriptはJavaScriptのスーパーセットであるため、デフォルトのテンプレートはありません -
- 代わりに、他のプロジェクトが独自のコンテキストを持つ独自のTypeScriptブートストラップ・テンプレートを用意しています。
Playground
- 動作確認や、どのようなJavascriptに変換されるか確認
- https://www.typescriptlang.org/play/
モジュール
- CommonJSのモジュール標準requreでは、同期的呼び出しになる、静的に解析可能でない場合がある。
- module.exportsもrequire もコードのどこでも使用可能で任意の文字列、式を含むことができる。
var hogeModule = require('hogeModule'); module.exports.hoge = function() {}
- ES2015(JavaScript 6thエディション)では静的に解決可能な簡潔な公文を導入
- TypeScriptでの使用標準
import hoge from 'hogeModule'; export function hoge() {}
インポート・エクスポート
ES2015のimport/export構文を使用すべき
- a.ts
export function foo() {} export function bar() {}
- b.ts
import { foo, bar } from './a'; foo(); export let result = bar();
ES2015ではデフォルトエクスポートをサポート
- c.ts
export default function hoge(param: number) {}
- d.ts
import hoge from './c'; hoge(12);
ワイルドカードで全てをインポート可能
- e.ts
import * as a from './a'; a.foo(); a.bar();
モジュールから再エクスポート可能
- f.ts
export * from './a'; export { result } from './b'; export hoge from './c';
TypeScriptでは型やインターフェースもエクスポート可能
- g.ts
export let X = 3; export type Y = { y: string };
名前空間
TypeScriptで名前空間はサポートされているが、カプセル化のための望ましい方法ではない。名前空間とモジュール化のどちらを選択するか確信を持てない場合、モジュール化を選択する。JavaScriptの標準により従うことになり、依存関係がより明示的になる。特に中、大規模プロジェクト
- namespace ブロックで定義
- exportで外側からアクセス可能なことを明示
- 完全修飾名で呼び出す
namespace NameA { export class Foo { name() { console.log("NameA.Foo"); } } export function bar(){ console.log("NameA.bar()"); } } let foo = new NameA.Foo(); foo.name(); NameA.bar();
名前空間のネスト
namespace NameB { export namespace NameC { export function bar(){ console.log("NameB.NameC.bar()"); } } } NameB.NameC.bar();
変数・型
変数・定数
変数宣言 | ブロックスコープ | 同じスコープでの重複宣言 | 備考 |
---|---|---|---|
var | 認識しない | 認める | |
let | 認識する | 認めない | |
const | 認識する | 認めない | 再代入不可 |
型
型 | 説明 |
---|---|
any | 任意、型がわからない場合。型チェックされずなんでもできてしまうため、できれば避ける。 |
unknown | any同様任意の型をあらわすが、前もって型がわからない場合、anyではなくこちらの利用を推奨 |
number | 数値 |
bigint | まるめ誤差なく大きな整数を扱うことができる(プラットフォームでサポートされているか確認が必要) let bi = 123n |
string | 文字列 |
boolean | 真偽 |
symbol | シンボル |
object | オブジェクト |
T[] | 配列 number[],string[] |
unknown型
- 値の比較、否定、typeof,instanceofが使用可能。以下のように利用する
let foo: unknown; foo = 1; // let bar = foo + 2; // unknown のためエラー if (typeof foo == 'number') { let bar = foo + 2; // OK console.log(bar); }
undefinedも許可
- string もしくは undefined
- aa 初期化時に必須だが、bbは指定しなくてもエラーとならない。
interface hoge { aa : string, bb? : string } var h: hoge = {aa:'aaa'};
演算子
演算子 | 説明 |
---|---|
?? | Null合体 左辺が null または undefined の場合に右の値を返し、それ以外の場合に左の値 |
文字列リテラル
`バッククオートで囲む(変数展開)
- 改行もそのまま
- ${...} ...に変数、式を埋め込み
class Program { static main() { let name: string = "Hiroto Yagi"; let age = 46; // 型推論 // age = "Hello"; // 数値型に文字列代入エラー let msg = null; // 初期値未指定、null指定など、any型と類推され型チェックは働かない msg = 100; // noImplicitAny:true とすると、暗黙的 anyは許容しない msg = "Hello"; // テンプレート文字列 console.log(` ${msg} my name is ${name} age ${age} ${new Date()}`); } } Program.main();
結果
> node ./src/app.js Hello my name is Hiroto Yagi age 46 toda Tue Nov 07 2017 18:59:23 GMT+0900 (東京 (標準時))
型アサーション
キャスト
function show(message: string) { console.log(`${message}`); } class TypeAssartion { static main() { // show(10); // ERROR show(<any>10); // OK show(10 as any); // as もOK } }
コレクション/インターフェース
- 配列
- 連想配列
- インデクスシグネチャはinterfaceとして事前定義可
interface StringMap { [index:string]:string; } class Collection { static main() { // 配列 let languages: string[] = ['java','C#','python']; console.log(languages); // 連想配列 let fwMap: {[key:string]:string} = { 'java':'Spring', 'C#':'Asp.net', 'python':'Django' }; console.log(fwMap); // インデクスシグネチャはinterfaceとして事前定義可 let fwMap2: StringMap = { 'java':'Spring', 'C#':'Asp.net', 'python':'Django' }; console.log(fwMap2); } }
列挙
enum Animal { DOG, CAT, MOUSE, ELEPHANT } enum Fruit { APPLE = "A", ORANGE = "O", PAINAPPLE = "P" } class EnumSample { static main() { let mypet: Animal = Animal.DOG; // デフォルトで0からの数値が割り当てられる console.log(mypet); console.log(Animal[mypet]); let myfavarit: Fruit = Fruit.PAINAPPLE; // 列挙子に値を割り当てることも可能 console.log(myfavarit); } }
結果
0 DOG P
タプル
- 配列のサブタイプ
- 固定長の配列を型づけ
let tpl: [string, number, boolean] = ['Yagi', 46, true]; console.log(`name:${tpl[0]} age:${tpl[1]} marriged:${tpl[2]}`);
合併型(共用型)と交差型
- 型の合併と交差を表現する特別な演算子が用意されている。
- 合併 | と交差 &
- 型エイリアスの例
type Sales = { dept: string, product: string}; type Engineer = { dept: string, project: string }; type SalesOrEngineewOrBoth = Sales | Engineer; // 合併 type DeptPerson = Sales & Engineer; // 交差
- 合併型(共用型)使用例
let data: string | boolean; data = 'foo';// OK data = true; // OK // data = 1; // NG let datas: (boolean | number)[] = [true, false, 1, 0];
型エイリアス
- 型の別名を定義できる
- ブロックスコープ
type Age = number; let myage: Age = 48;
- 主にタプル、共用型に別名を付与。インターフェースでできる場合に使用しないほうがよい
type ProfileTuple = [string, number, boolean]; let me = ['yagi', 46, true]; console.log(`name:${me[0]} age:${me[1]} marriged:${me[2]}`);
文字列リテラル
type Season = 'spring' | 'summer' | 'autumn' | 'winter'; class StringLiteralType { printSeason(s: Season) { console.log(s); } static main() { let me = new StringLiteralType(); me.printSeason('summer'); // OK // me.printSeason('hoge'); // NG } }
keyof演算子
関数
- function命令
- 関数リテラル
- アロー関数
関数、関数リテラル、アロー関数
- 関数リテラル(型推論)
- 関数リテラル(型明示)
- アロー関数(ラムダ式)
function squire(x: number, y: number) :number { return x * y; } class FunctionSample{ static main() { // 関数の使用 console.log(squire(8,8)); // 関数リテラル(型推論) let m3 = function(x :number, y: number, z: number): number { return x * y * z; } ; console.log(m3(8,8,8)); // 関数リテラル(型明示) let m2: (x: number, y: number) => number = function(x: number, y: number) { return x * y; }; console.log(m2(9,9)); // アロー関数(ラムダ式) // this は宣言された場所によって変わる let m22 = (x: number, y: number): number => x * y; console.log(m22(5,5)); } }
引数の省略、デフォルト引数、可変長引数
- TypeScriptでは宣言された引数はすべて必須
- 引数を省略したい場合、仮引数の名前の後ろに ? を付与
- 関数のデフォルト値を設定するには、仮引数の後に=デフォルト値
- デフォルト値には、式も利用できる
- 明示的に undefined を渡した場合もデフォルト値が利用される
- 可変長引数
- 仮引数の前に、... とすることで可変長引数となる
- 関数に1つ、最後のパラメータのみ
class FunctionApplySample { static main(){ // TypeScript]は宣言された引数はすべて必須 // 引数を省略したい場合、仮引数の名前の後ろに ? を付与 let greet = function(message?: string){ message = (message === undefined)?"hello":message; console.log(message); } greet(); greet("good evening"); // 関数のデフォルト値を設定するには、仮引数の後に=デフォルト値 // デフォルト値には、式も利用できる // 明示的に undefined を渡した場合もデフォルト値が利用される let greet2 = function(message: string=`hello ${new Date()}`) { console.log(message); } greet2(); greet2("good night"); greet2(undefined); // 可変長引数 // 仮引数の前に、... とすることで可変長引数となる let sum = function(...nums: number[]):number { let ttl: number = 0; nums.forEach((value,index,array) => ttl += value); return ttl; } console.log(sum(1,2,3,4,5)); } }
thisの型を明示
- thisが望むものであることを強制
- thisが関数シグネチャの一部として用いられる場合、予約語
function printMonth(this:Date) { console.log(`${this.getMonth()+1}`); } printMonth.call(new Date()); printMonth.call(1); // チェックでエラー表示される
関数のオーバーロード1
- シグネチャのみの関数を用意
- 具体的な実装を直後に定義
- 実装の中で、typeof/instanceof を用いて型を判定し処理を記述
function showMessage(value: number): void; function showMessage(value: boolean): void; function showMessage(value: any): void { let message: string | undefined; if (typeof value === 'number') { message = `${value} is number.`; } if (typeof value === 'boolean') { message = `${value} is boolean`; } console.log(message); } class FunctionApplySample2 { static main(){ console.log("*** 関数応用2 ***"); // 1.シグネチャのみの関数を用意 // 2.具体的な実装を直後に定義 // 3.実装の中で、typeof/instanceof を用いて型を判定し処理を記述 showMessage(123); showMessage(false); showMessage("Hello" as any); } }
ユーザー定義型ガード
- typeaof/instanceof は組み込みの型を絞り込むための機能
- is 演算子を使用して独自に定義可能
function hoge(val:string | number) { if (isString(val)) { console.log(val.toUpperCase()); } else { console.log(val); } } // boolean を返す関数の場合、型の絞り込みを行うことができる function isString(val: unknown): val is string { return typeof val === 'string'; } hoge(1); hoge('a'); *結果 1 A
関数のオーバーロード2
- オーバーロードされた関数2つのシグネチャを宣言
- 実装のシグネチャは2つの宣言を結合したもの
- 呼び出すときに結合されたシグネチャは見えない
// オーバーロードされた関数2つのシグネチャを宣言 type ShowMessage = { (value: number):void, (value: boolean):void } // 実装のシグネチャは2つの宣言を結合したもの let showMessage: ShowMessage = ( value : number | boolean ) => { let message: string | undefined; if (typeof value === 'number') { message = `${value} is number.`; } if (typeof value === 'boolean') { message = `${value} is boolean`; } console.log(message); }
- 実行
// 呼び出すときに結合されたシグネチャは見えない showMessage(12); showMessage(true);
*結果
12 is number. true is boolean
ジェネレーターとイテレーター
- ジェネレーター関数(function* )により生成
- 生成されたオブジェクトは、反復可能なイテレーター
- 利用者が要求したときだけ値を生成する
function* increament(start: number) { while (true) { start++; yield start; } } let i = increament(10); console.log(i.next().value); console.log(i.next().value); console.log(i.next().value);
- 結果
11 12 13
オブジェクト指向
クラス定義
class Person { // メンバー name: string; // デフォルト public private _age: number; // 静的メンバー public static CHEAT_AGES: number = 2; // コンストラクター constructor(name: string, age: number){ this.name = name; this._age = age; } // 以下のコンストラクターでメンバーの宣言と代入を省略できる // アクセス修飾子必須 // constructor(public name: string, private _age: number) { // } // アクセッサー get age(): number { return this._age - Person.CHEAT_AGES; } set age(value: number) { this._age = value; } // メソッド printProfile(){ console.log(`name=${this.name},age=${this.age}`); } } console.log(Person.CHEAT_AGES); let me = new Person('Yagi', 46); //console.log(me.age); // NG me.printProfile(); me.age = 50; me.printProfile();
結果
2 name=Yagi,age=44 name=Yagi,age=48
コンストラクタ
- コンストラクター
name: string; // デフォルト public private _age: number; constructor(name: string, age: number){ this.name = name; this._age = age; }
- 以下のコンストラクターでメンバーの宣言と代入を省略できる
- アクセス修飾子必須
constructor(public name: string, private _age: number) }
アクセス修飾子
修飾子 | 説明 |
---|---|
public | どこからでもアクセス可能。デフォルト。 |
protected | このクラスと、サブプラスのインスタンスからアクセス可能。 |
private | このクラスのインスタンスのみからアクセス可能 |
継承
class Guiter { constructor(public maker: string, public type: string){} print() { console.log(`${this.type} by ${this.maker}`); } } class Lespaul extends Guiter { constructor(){ super("Gibson","Lespaul"); } } (new Lespaul()).print();
抽象クラス
- 直接インスタンス化しようとするとエラー
// 抽象クラス abstract class Viecle { // 抽象メソッド abstract getEngineDisplacement(): number; printDisplacement() { console.log(`${this.getEngineDisplacement()}`); } } class Premacy extends Viecle { // 抽象メソッドのオーバーライド getEngineDisplacement(): number { return 2000; } // メソッドオーバーライド printDisplacement() { console.log("******"); super.printDisplacement(); console.log("******"); } } (new Premacy()).printDisplacement();
インターフェース
- 型エイリアスと同様、インターフェースは型に名前をつけるための方法
- ほぼ同じだが、細かな差異もある。
- インラインで型を定義する必要がなくなる。
interface Color { // メソッドはすべて抽象メソッド // abstract 指定は不要 getRGB(): string; } class Red implements Color { getRGB(): string { return "#FF0000"; } } console.log((new Red()).getRGB());
型とインターフェースの違い
- 型は右辺に任意の値をおけるがインターフェースはそうではない。
- 以下のようなコードはインターフェースに書き換えられない
type A = number; type B = A | string;
- インターフェースを拡張する場合、拡張元が拡張先に適用できるかチェックする。型エイリアスの場合拡張元と先を結合するために最善を尽くす
- 同じスコープに同じ名前のインターフェースが複数存在する場合、自動的にマージされる。
super
- 親クラスのメソッドを呼び出すことができる。super.hoge();
- コンストラクターとして呼び出す。super();
構造的部分型(Structual subtyping)
class Hoge { constructor(public name: string, public age: number){} } class Spam { constructor(public name: string, public age: number){} } // 継承による明示的な互換性(Nominal subtyping)がなくても構造が一致していれば代入可能 let h: Hoge = new Spam("spam",10); console.log(h);
型注釈としてのインターフェース
- インターフェースを型注釈として利用できる
interface Shape { // プロパティシグネチャ name: string; // メソッドシグネチャ draw():void; } let c : Shape = { name:'circle', draw() { console.log(`${this.name}:○`); } }; c.draw();
オブジェクトリテラル
- インターフェースを定義するまでもないが、型情報は明示しておきたい場合
let t : { name: string, sides: number} = {name: 'triangle', sides: 3};
型としてのthis
- 自分自身を返すことで、メソッドチェーンのような記述が可能となる
- 派生クラスなど、呼び出し元のクラスに応じ型が変化する
class StringBuilder { constructor(protected _value: string){ } toString(): string { return this._value; } wrap(value: string) :string{ return `${value}`; } // 自分自身を返すことで、メソッドチェーンのような記述が可能となる // 派生クラスなど、呼び出し元のクラスに応じ型が変化する append(value: string): this { this._value = this._value + this.wrap(value); return this; } } let buf = new StringBuilder('a'); buf.append('b').append('c').append('d'); console.log(buf.toString()); class CsvBuilder extends StringBuilder { constructor(protected _value: string){ super(_value); } wrap(value: string) { return `,${value}`; } } let csv = new CsvBuilder('a'); csv.append('b').append('c').append('d'); console.log(csv.toString());
出力
abcd a,b,c,d
ジェネリック
let numary: Array<number> = [1,2,3,5,8];
ジェネリック型の定義
- StringBuilder型は、上記で定義参照
- 型引数に制約を付与する場合、extends を使用する。
- 使用しなければ、あらゆる型を渡すことができる
class XmlBuilder extends StringBuilder { constructor(protected _value: string){ super(_value); } wrap(value: string) { return `<value>${value}</value>`; } toString(): string { return `<values>${this._value}</values>`; } } // 型引数に制約を付与する場合、extends を使用する。 // 使用しなければ、あらゆる型を渡すことができる class Report<T extends StringBuilder> { builder: T; getBuilder(): T { return this.builder; } addLine(line: string) { this.getBuilder().append(line); } report() { console.log(this.getBuilder().toString()); } } let rep = new Report<XmlBuilder>(); // ジェネリック型に具体的なインスタンスを紐づけ rep.builder = new XmlBuilder(); rep.addLine("this is lesson."); rep.addLine("of TypeScript.") rep.report();
出力例
<values><value>this is lesson.</value><value>of TypeScript.</value></values>
ジェネリックメソッド
function reverse<T>(data: T[]): T[] { let ary: T[] = new Array(); let j=0; for(let i=data.length-1; i>=0; i--) { ary[j++] = data[i]; } return ary; } console.log(reverse<number>([1,2,3,4])); console.log(reverse<string>(["a","b","c","d"]));
出力例
[ 4, 3, 2, 1 ] [ 'd', 'c', 'b', 'a' ]
サンプル
非同期プログラミングと並行・並列処理
ObservableとPromise
比較
- Observable は宣言型で、購読するまで処理が開始されない。Promise は作成時に直ちに実行される。
- 結果が必要なときにいつでも実行できるレシピを定義するために、Observable が役立つ。
- Observable は多くの値を提供します。Promise は1つです。
- 時間の経過とともに複数の値を取得するのには Observable が有効
- Observable は、連鎖とサブスクリプションを区別します。Promise は .then() 句しかありません。
- Observableは、作業を実行させることなく、システムの他の部分で使用される複雑な変換レシピを作成するのに便利
- Observable の subscribe() はエラーを処理します。Promise は子の Promise にエラーをプッシュします。
- Observable は集中管理され、予測可能なエラー処理に役立
Observable
- RxJsライブラリ
- https://angular.jp/guide/rx-library
リアクティブプログラミングは、データストリームと変更の伝播に関する非同期プログラミングのパラダイムです。 RxJS (Reactive Extensions for JavaScript) は、非同期またはコールバックベースのコード (RxJS Docs) の作成を容易にする observables を使用したリアクティブプログラミング用のライブラリ
- 非同期処理の既存のコードを observables に変換する
- ストリーム内の値を反復処理する
- 異なる型への値のマッピング
- ストリームのフィルタリング
- 複数のストリームの作成
Observable 作成関数
ベント、タイマー、promise などから observables を作成するプロセスを簡素化
promise から observable を作成
import { from } from 'rxjs'; // promise から Observable を作成 const data = from(fetch('/api/endpoint')); // 非同期の結果の購読を開始 data.subscribe({ next(response) { console.log(response); }, error(err) { console.error('Error: ' + err); }, complete() { console.log('Completed'); } });
カウンターから observable を作成する
import { interval } from 'rxjs'; // 一定間隔でで値を発行するObservable を作成 const secondsCounter = interval(1000); // 発行される値の購読を開始 secondsCounter.subscribe(n => console.log(`It's been ${n} seconds since subscribing!`));
イベントから observable を作成する
import { fromEvent } from 'rxjs'; const el = document.getElementById('my-element'); // マウスの移動を発行する Observable の作成 const mouseMoves = fromEvent(el, 'mousemove'); // マウス移動イベントを購読開始 const subscription = mouseMoves.subscribe((evt: MouseEvent) => { console.log(`Coords: ${evt.clientX} X ${evt.clientY}`); // スクリーンの左端をマウスが越えたら購読中止 if (evt.clientX < 40 && evt.clientY < 40) { subscription.unsubscribe(); } });
AJAX リクエストから observable を作成
import { ajax } from 'rxjs/ajax'; // AJAXリクエストを生成する Observable の作成 const apiData = ajax('/api/data'); // リクエストの生成を購読 apiData.subscribe(res => console.log(res.status, res.response));
Promise
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
- 非同期の作業を抽象化して、それらを組み立てたり順番に並べる方法
import { isNumber } from "util"; function getPromise(waittime:string): Promise<string> { return new Promise<string>((resolve, reject) => { if (isNaN(Number(waittime)) ) { reject("error"); } else { const wt = parseInt(waittime); setTimeout(() => { resolve(`WAIT ${wt}`); }, wt); } }); } function usePromise() { getPromise("2000") .then((v) => { console.log(v); }) .catch((e) => { console.log(e); }); getPromise("aaaaa") .then((v) => { console.log(v); }) .catch((e) => { console.log(e); }); } usePromise();
- 結果
非同期で呼び出すため、呼び出し結果と出力順序が入れ替わる
error WAIT 2000
async , await
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
- Promiseはとてもポピュラーなパターンなので、JavaScript(TypeScript)では独自の構文が用意されている。
- await は .then に対するシンタックスシュガーと言える
- await を使ってPromiseを待機する場合、async ブロックの中で行う
- ラムダの場合 hoge(async ()=> {})
import { isNumber } from "util"; function getPromise(waittime:string): Promise<string> { return new Promise<string>((resolve, reject) => { if (isNaN(Number(waittime)) ) { reject("error"); } else { const wt = parseInt(waittime); setTimeout(() => { resolve(`WAIT ${wt}`); }, wt); } }); } async function usePromise() { try { let result = await getPromise("2000") console.log(result); let result2 = await getPromise("aaaa") console.log(result2); } catch(e) { console.log(e); } } usePromise();
- 結果
await で同期されるため、呼び出し順に出力される
WAIT 2000 error
非同期ストリーム
- 一般的には、イベントエミッター(Node.jsでは、EventEmitter)、リアクティブプログラミングライブラリー(RxJS、MostJS など)を利用する
- 2つの違いはコールバックとPromiseに似ている。イベントエミッターは軽量、迅速であり、リアクティブプログラミングは強力でイベントストリームを組み立てたり並べたりが可能
マルチスレッディング
- 上記までは非同期処理
- マルチスレッドによる並列処理を行う
- TypeScript 並列処理
Node.jsを使う
環境構築
- 作業ディレクトリで実行
$ npm init -y Wrote to /Users/hirotoyagi/Workspaces/Node/sample/package.json: { "name": "sample", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }
- package.jsonを編集
"scripts": { "build": "tsc", "watch": "tsc --watch" },
- TypeScriptのインストールと確認
$ npm install --save-dev typescript $ node_modules/.bin/tsc --version Version 3.9.7
- 初期化
$ node_modules/.bin/tsc --init message TS6071: Successfully created a tsconfig.json file.
- Node.jsの型定義をインストール
$ npm install --save @types/node
- tsをTypeScriptで記述しjsにコンパイル
app.ts
console.log("test");
$ npm run build
- app.js が生成されるので、Nodeで実行
app.js
"use strict"; console.log("test");
$ node app
Tips
TypeDoc
- https://qiita.com/Mic-U/items/961ce4e0c2a1def1dbd3
- https://qiita.com/ConquestArrow/items/eb4a0dfb13497be4d6a3
インストール
> npm install typedoc -g
作成
> typedoc --out ./docs/ ./src/ --module commonjs
ASP.NET
GAE
配列
配列でなければ配列に、配列ならそのまま(もしくは追加)
© 2006 矢木浩人