📄
2020/7/26 Typescript書き方の速成まとめ
この記事は2020/7/26に書きました。
個人学習目的で作成していますが、間違ったところや捕捉などありましたがご指摘ください。
1. 概論・セットアップ
- transpile言語 (類似コンパイル言語)
- Javascriptと交換
- Typescriptの主な役目
- コンパイル時にタイプチェックを行うこと
Typescript setup & compile
# 初期化
npm init -y
npm -i typescript [-g]
%init tsconfig.json
./node_modules/.bin/tsc --init
# compile
tsc {filename}
# watch & auto compile, 実用的ではない(glup推奨)
tsc -w
typescript online play
2. tsconfig
● Top Level Properties
compileOptions ★
compileOnSave //boolean : セーブと同時にコンパイル(IDE)
extends //relative path
files //path(glob)
include //path(glob)
exclude //path(glob)
typeAcquisition
compileOptions : type
- TypeScript2からサポートするType Definition System関連オプション
- 何も設定しないと、自動で
./node_nodules/@types/*
をインポート- ex:
./node_nodules/@types/react/*
,./node_nodules/@types/babel__*/*
- ex:
- @typesは、コンパイル時のタイプチェックはもちろん、IDEのコードアシスト、シンタクスチェックなどでも使われる大事な定義ファイル
- typeRoots
- 設定すると、設定したパスだけインポート
- types
- 設定すると、配列内に指定したモジュール、または
./node_nodules/@types/
内のモジュール名から探す。 -
[]
だと使わない
- 設定すると、配列内に指定したモジュール、または
- typeRootsはtypesは、どっちか一つだけ使う。
"typeRoots": {
"description": "Specify list of directories for type definition files to be included. Requires TypeScript version 2.0 or later.",
"type": "array",
...
},
"types": {
"description": "Type declaration files to be included in compilation. Requires TypeScript version 2.0 or later.",
"type": "array",
...
},
compileOptions : target, lib
- target
- buildするバージョンを指定
- 指定しないと、基本esバージョン
- lib
- 基本type difinitionライブラリを指定
- 指定しないと、esバージョンに依存したライブラリを使用
- 指定すると、指定したライブラリのみを使う
"target": {
"description": "Specify ECMAScript target version: 'ES3', 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ESNext'",
"type": "string",
...
},
"lib": {
"description": "List of library files to be included in the compilation. Possible values are: 'ES5', 'ES6', 'ES2015', 'ES7', 'ES2016', 'ES2017', 'ES2018', 'ESNext', 'DOM', 'DOM.Iterable', 'WebWorker', 'ScriptHost', 'ES2015.Core', 'ES2015.Collection', 'ES2015.Generator', 'ES2015.Iterable', 'ES2015.Promise', 'ES2015.Proxy', 'ES2015.Reflect', 'ES2015.Symbol', 'ES2015.Symbol.WellKnown', 'ES2016.Array.Include', 'ES2017.object', 'ES2017.Intl', 'ES2017.SharedMemory', 'ES2017.String', 'ES2017.TypedArrays', 'ES2018.Intl', 'ES2018.Promise', 'ES2018.RegExp', 'ESNext.AsyncIterable', 'ESNext.Array', 'ESNext.Intl', 'ESNext.Symbol'. Requires TypeScript version 2.0 or later.",
"type": "array",
...
},
compileOptions : outDir, outFile
"outDir": {
"description": "Redirect output structure to the directory.",
"type": "string"
},
"outFile": {
"description": "Concatenate and emit output to single file.",
"type": "string"
},
compileOptions : module
- module
- compileされた結果物のモジュールシステムを指定
-
target
がes6だと、es6がデフォルト -
target
がes6じゃないとcommonjsがデフォルト
- moduleResolution
- tsソースで使用されるモジュールを指定
- CommonJSの場合
Node
、それ以外はだいたいClassic
-pathsと baseUrl - 指定すると、該当パスのモジュールをロード
- 普通使わなくてOK。(細かいモジュール連携に必要)
- rootDirs
- ロードするモジュールのルートパス配列
- 普通使わなくてOK。(細かいモジュール連携に必要)
"module": {
"description": "Specify module code generation: 'None', 'CommonJS', 'AMD', 'System', 'UMD', 'ES6', 'ES2015', 'ES2020' or 'ESNext'. Only 'AMD' and 'System' can be used in conjunction with --outFile.",
"type": "string",
...
},
"moduleResolution": {
"description": "Specifies module resolution strategy: 'node' (Node) or 'classic' (TypeScript pre 1.6) .",
"type": "string",
...
},
"baseUrl": {
"description": "Base directory to resolve non-relative module names.",
"type": "string"
},
"paths": {
"description": "Specify path mapping to be computed relative to baseUrl option.",
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "string",
"description": "Path mapping to be computed relative to baseUrl option."
}
}
},
"rootDirs": {
"description": "Specify list of root directories to be used when resolving modules.",
"type": "array",
...
},
3. TypeScript Basic Types
TypeScriptで定義した基本データタイプ
User Defined Typesも、基本データタイプからの拡張
superset (ECMAScript Standard)
- boolean, number, string, null, undefined
- array (object, non-primitive)
- symbol (ecma6)
- 固有で修正不可能なデータとしてアサイン
- primitive値を指定
Subtypes
- undefined & nullは、すべてのタイプに対してのサブタイプ
- すべてのタイプに、undefinedとnullはアサインできる。
- しかし、compileOptionで,
--strictNullChecks
を使うと、void
か、自分自身にだけアサインでできるようになる。
- その場合は、union typeで指定しなきゃいけない。
- ex :
let union: string | null | undefined = 'str'
Additional Type
-
void
- タイプがない、空の概念
- 関数のリターンタイプくらいで使う(リターンする値がない時)
-
any
- 何のタイプにもなれる
- Anyは非推奨、TypeScriptを使う意味が薄れる。
- compileOptionで、エラーになるように指定も可能(
noImplicitAny
)
-
never
- 結果を返さないため、タイプを持たない。
- あんまり使うところがないが、関数のリターンタイプくらいで使う
- infinitely loop function
- absolutely throw Error
- absolutely return error('message')
-
enum
- 列挙型(他の言語と同じ)
- 複数の変数に対して、一連の定数値をアサイン
enum Color {Red, Green, Blue}; let c: Color = Color.Red; let colorName: string = Color[c];
-
tuple (object, non-privitive)
- 複合タイプを持つ配列
let x: [string, number] =['hello', 10];
- 値を持って使うときに、どういうタイプかチェックしない限りわからないため、使うのに注意は必要
-
Union Type
- タイプの共用体
let someVar: string | number | boolean = false
Type Assigned by literal
- Literal値で、タイプを定義するのも可能。指定したLiteralのみ設定可能
let someVar: "a" | 5 | false = 5
-
Generic
パートで記述するkeyof (Indexed Type Query)
演算子の理解とつながる。
4. var, let, const
VS var, let, const
-
var
- ES5
- variable scope : function
- hoisting : O
- re-difinition : O
-
let, const
- ES6
- variable scope : block
- hoisting : X
- re-difinitio : X
-
varより、let, const推奨
- コード分析が直感的になる
-
letとconstのタイプ推論
let a: string = "str"; //明示的string type
let b = "str"; //タイプ推論によるstring type
const c: string = "str"; //明示的string type
const d = "str"; //タイプ推論によるLiteral type
5. Type Assertion
- とある変数を参照する時、タイプを明示的に絞ること
- type castingとは違い、データを変換したりしない
- とある変数を、指定タイプであることを前提に使うという宣言
- もし使う場合があるなら、宣言に対しての信頼性が大事
- 使い方
- someVar as TYPE
- <TYPE>someVar (jsxと紛らわしいので、非推奨)
// 主に曖昧なタイプから絞るときに使う。
// 曖昧なタイプである時点で、ベストプラクティスではないので、参考までに見ることs
let someVar: any = "some string";
let strLength: number =(someVar as string).length;
6. Type Alias
特定のタイプに別称をつけて使うことができる。
あくまで、作られたタイプの参照を持つだけで、タイプを作ることではない。
//alias to union type
let varA: string | number = 0;
varA = "A";
type StringOrNumber = string | number;
let varB = 0;
varB = "B";
7. Interface
実装を持たず、ステート(プロパティ)とビヘイビアの形式の定義のみを記述した抽象データタイプ。
- インタフェースの抽象というのは、インスタンス化観点から抽象的であり具体を持たいないため、単体では実態を持てないという意味を内包している。
- 継承するクラスたちに対してのプロトコル(約束)の役目を果たす。
interface basics
//interface
interface Person {
name: string;
//--optional type
age?: number;
//-- function interface
say(): string;
}
const person:Person = {
name: "Mark",
age: 34,
say: (): string {
return `hello. name=${this.name}`;
}
}
interface - indexable type
indexのタイプとしては、stringか、numberを指定可能
interface Person {
//--indexable type (number or string)
[index: string]: string;
}
const person:Person = {
name: "Mark"
}
person.age = "34";
person["age"] = "34";
//person.age = 34; //index type error
interface NumIndex {
//--indexable type (number or string)
[index: number]: object;
}
const queue:NumIndex = {}
queue[0] = new Object();
queue[1] = new Object();
//queue["abc"] = new Object(); //index type error
//queue.abc = new Object(); //index type error
class implements interface
interface IPerson {
name: string;
age?: number;
say(): void;
}
class Person implements IPerson {
name: string;
constructor(name: string) {
this.name = name;
}
say(): void {
console.log(`hello. myname is ${this.name}.`);
}
}
const person = new Person("Mark");
person.say();
interface extends interface
intrface Person {
name: string;
age: number;
}
interface SalaryMan extends Person {
job: string;
}
function with interface
interface funcPerson {
(name: string, age?: number): void;
}
const sayPerson: funcPerson = function (name: string) {
console.log(`hello. myname is ${name}.`);
}
sayPerson("Mark");
8. Class
オブジェクトの初期ステート(プロパティ)とビヘイビアを記述したテンプレートであり、User Defined Data Type.
class basics
class Person {
protected _name: string = null;
private _age: number = null;
set _age;
}
class SalaryMan extends Person {
private _job: string = null;
constructor(name: string) {
super();
this.name = name;
}
}
const man: SalaryMan = new SalaryMan("Mark");
Abstract class
abstract class APerson {
protected _name: string = "NoName";
abstract setName(name: string): void;
}
class Person extends APerson {
setName(name: string): void {
this._name = name;
}
}
const person = new Person();
readonly keyword & static keyword & private constructor
※ typescriptでは、anti-partternだという意見もあり
class Logger {
private static singletonInstance: Logger;
public readonly initTime: number;
private constructor(){
this.initTime = new Date().getTime();
}
public static getLogger = ():Logger => {
if (Logger.singletonInstance === undefined) {
Logger.singletonInstance = new Logger();
}
return Logger.singletonInstance;
}
logInfo = (msg: string):void => {
console.log(`logger-${this.initTime} : ${msg}`);
}
}
Logger.getLogger().logInfo("first log");
setTimeout(
(): void => {
Logger.getLogger().logInfo("after 2sec log");
},
2000
);
//Logger.getLogger().initTIme = 5; //error because readonly property
//--output
//logger-1590906992695 : first log
//logger-1590906992695 : after 2sec log
9. Generic
パラメータのデータタイプを、インスタンス化の後で明示するプログラミング手法(to-be-specified-later)
入力・出力のデータタイプを、任意のタイプに抽象化宣言する。
タイプチェックは、ランタイムの前にコンパイラでしてくれるが、内部動作メカニズムとしては、実際にどういうデータタイプで入力・出力されるかは、インスタンス化後、実際に呼び出されるときに確定される。
Generic basics
function doPingPong<T>(message: T): T {
return message;
}
console.log(doPingPong<string>("text"));
console.log(doPingPong<number>(10));
console.log(doPingPong<object>({key: "value"}));
Generic with class
class Code<T extends string | number, O> {
private _code: T;
private _data: O;
constructor(code: T, data: O) {
this._code = code;
this._data = data;
}
getCode = (): T => {
return this._code;
}
getData = (): O => {
return this._data;
}
}
const abc = new Code<string,string>("ABC", "data");
const oneTwoThree = new Code<number, object>(123, {});
10. keyof -Lookup Types-
keyof
basics
- Indexed Type Lookup Query 演算子
- オブジェクトで、アクセスが許容されているプロパティのインデックスを
Literal Type
として算出する。 -
Generic
と一緒に使うと有用
//-- indexed type query from Interface
interface IObj {
keyA: number;
keyB: number;
}
type restrictedAsKeysOfInterface = keyof IObj;
let v1: restrictedAsKeysOfInterface = "keyA"; //same >> let v1: "keyA" | "keyB" = "keyA";
// let v2: restrictedAsKeysOfInterface = "key?"; // error
//-- indexed type query from Object(typeof)
const obj = {keyA : 0, keyB : 1};
type restrictedAsKeysFromObject = keyof typeof obj; //same >> let v1: "keyA" | "keyB" = "keyA";
// let v2: restrictedAsKeysOfInterface = "key?"; // error
keyof
Generic with - ランタイム前に、間違ったプロパティアクセスなどが検出できる。
function getProperty<T, K extends keyof T>(obj:T, key:K) {
return obj[key];
}
interface Person {
name: string;
age: number;
}
const person: Person = {
name: "Mark",
age: 35
}
getProperty(person, "name");
getProperty(person, "age");
//getProperty(person, "unknown"); //unasignable type error
11. Iterator
今までの巡回
Array巡回
// es3
for (var i = 0; i < array.length; i++)
// es5
array.forEach() //breakができないので、anti pattern
// es6
for (const item of array) // arrayのみ使える
Object巡回
// -- for in
// 推奨されない。理由は以下
// - hello worldobject巡回時に使う。(arrayには柄はないはず)
// - indexがnumberじゃなくstringで出る
// - 配列内のプロパティも意図とは違って巡回できる可能性がある
// - prototype chainのプロパティを巡回できる可能性もある
// - 巡回の順序を保証しない
// for ofが推奨される。
// -- objectを巡回するときには、for ofで以下のように使うことも可能
for (const prop of Object.keys(obj))
example
const array = ['first', 'second'];
const obj = {
name: 'Mark',
age: 35
};
// use for..of on Array
for (const item of array) {
console.log(typeof item + ', ' + item);
}
// use for..in on Array
// item type is string. value is numeric string
for (const item in array) {
console.log(typeof item + ', ' + item);
}
// use for..of on Object => Error
/*
for (const item of obj) {
console.log(typeof item + ', ' + item);
}
*/
// use for..in on Object
for (const item in obj) {
console.log(typeof item + ', ' + item);
}
// use for..on using keys on Object
for (const item of Object.keys(obj)) {
console.log(typeof item + ', ' + item);
}
Symbol.iterator
概要
- プロパティ。巡回関数が具現されているとiterableなタイプになる。
- Array, Map, Set, String, Int32Array, Uint32Arrayなどには、内蔵された具現体があるので、iterableなタイプである。
- ただのobjectはiterableではない。
- Iteratorを使い、IterableなオブジェクトのSymbol.iterator関数を呼び出す。
target
- es3 or es5
- Arrayのみfor..ofを使える
- オブジェクトに使うとエラー
- es6
- SYmbol.iteratorを具現すると、どんなオブジェクトにもfor..ofを使える
typescriptのIteratorインタフェース
// lib.es6.d.ts
interface IteratorResult<T> {
done: boolean;
value: T;
}
interface Iterator<T> {
next(value?: any): IteratorResult<T>;
return?(value?: any): IteratorResult<T>;
throw?(e?: any): IteratorResult<T>;
}
interface Iterable<T> {
[Symbol.iterator](): Iterator<T>;
}
interface IterableIterator<T> extends Iterator<T> {
[Symbol.iterator](): IterableIterator<T>;
}
Iterable具現
class CustomIterable implements Iterable<string> {
private _array: Array<string> = ['first', 'second'];
[Symbol.iterator]() {
var nextIndex = 0;
return {
next: () => {
return {
value: this._array[nextIndex++],
done: nextIndex > this._array.length
}
}
}
}
}
const cIterable = new CustomIterable();
for (const item of cIterable) {
console.log(item);
}
//[LOG]: first
//[LOG]: second
12. Decorator
- Decoratorを使うためには、config設定必要
- 各Decoratorパターンに対するシグニチャーを見ておくこと
Setting
$ mkdir ts-decorator
$ cd ts-decorator
$ yarn init -y
$ yarn add typescript -D
# setting tsconfig
$ node_modules/.bin/tsc --init
-- tsconfig.jsonのexperimentalDecoratorsをtrueに設定
Class Decorator Example
function hello(constructFn: Function) {
console.log(constructFn);
}
function helloFactory(show: boolean) {
if (show) {
return hello;
} else {
return null;
}
}
@helloFactory(false)
class Person {}
@helloFactory(true)
class Person2 {}
//--output
//$ node dist/Test.js
//[Function: Person2]
function editable(canBeEdit: boolean) {
return function(target: any, propName: string, description: PropertyDescriptor) {
console.log(canBeEdit);
console.log(target);
console.log(propName);
console.log(description);
description.writable = canBeEdit;
}
}
class Person {
constructor() {
console.log('new Person()');
}
@editable(true)
hello() {
console.log('hello');
}
}
const person = new Person();
person.hello();
person.hello = function() {
console.log('world');
}
person.hello();
function addHello(constructorFn: Function) {
constructorFn.prototype.hello = function() {
console.log('hello');
}
}
@addHello
class Person {
constructor() {
console.log('new Person()');
}
}
const person = new Person();
(<any>person).hello(); //使い方に短所があるが、ライブラリやフレームワークなどの開発にはいいパターンかも
//--output
//$ node dist/Test.js
//hello
Method Decorator Example
function editable(canBeEdit: boolean) {
return function(target: any, propName: string, description: PropertyDescriptor) {
console.log(canBeEdit);
console.log(target);
console.log(propName);
console.log(description);
//descriptorのwritable属性がランタイム時に変わる
description.writable = canBeEdit;
}
}
class Person {
constructor() {
console.log('new Person()');
}
@editable(true)
hello() {
console.log('hello');
}
}
const person = new Person();
person.hello();
// editableをtrueにしていたので、上書きされる。
// ※ editableをfalseにした場合は上書きされない。
person.hello = function() {
console.log('world');
}
person.hello();
// --output
// true
// Person {}
// hello
// {
// value: [Function: hello],
// writable: true,
// enumerable: false,
// configurable: true
// }
// new Person()
// hello
// world
// ※上書きされた関数の結果。editableがfalseなら、結果は「hello」
Property Decorator
function writable(canBeWrite: boolean) {
return function(target: any, propName: string): any {
console.log(canBeWrite);
console.log(target);
console.log(propName);
return {
writable: canBeWrite
}
}
}
class Person {
@writable(false)
name: string = 'Mark';
constructor() {
console.log('new Person()');
}
}
const person = new Person();
console.log(person.name);
// --output
//TypeError: Cannot assign to read only property 'name' of object '#<Person>'
//※ writable(true)にすると、エラーなく動作する
Parameter Decorator
function printInfo(target: any, methodName: string, paramIndex: number) {
console.log(target);
console.log(methodName);
console.log(paramIndex);
}
class Person {
private _name: string;
private _age: number;
constructor(name: string, @printInfo age: number) {
this._name = name;
this._age = age;
}
hello(@printInfo message: string) {
console.log(message);
}
}
//-- output
// Person { hello: [Function] }
// hello
// 0
// [Function: Person]
// undefined
// 1
13. Type Inference
- タイプを明示しなかった場合のタイプ推論規則
- letは、基本データ・タイプで推論
- constはリタラル・タイプで推論
- objectタイプを使わないと、プロパティはletと同じように推論
- const person = {name:'Mark', age: 35}
- person => {name: string; age: number;}で推論される
- objectタイプを使わないと、プロパティはletと同じように推論
- 大体は推論自体は簡単
- 単純な変数
- structuring, destructuring
- array, 関数のリターンに対しては推論が難しい場合が多い
ArrayのType Inference
const array1 = [];
// any[]
const array2 = ['a', 'b', 'c'];
// string[]
const array3 = ['a', 1, false];
// (string|number|boolean)[] ※ inferenced as union type
class Animal {
name: string;
}
class Dog extends Animal {
dog: string;
}
class Cat extends Animal {
cat: string;
}
const array4 = [new Dog(), new Cat()];
// (Dog | Cat)[]
returnのType Inference
function hello(message: string | number) {
if (message === 'world') {
return 'world';
} else {
return 0;
}
}
// return type inference => ('world' | 0)
// literal union
Union TypeとType Guard
interface Person {
name: string;
age: number;
}
interface Car {
brand: string;
wheel: number;
}
//Type Guard
//tell this is Person when return
function isPerson(arg: any): arg is Person {
return arg.name !== undefined;
}
function hello(arg: Person | Car) {
if (isPerson(arg)) {
//this is Person. not Car.
console.log(arg.name);
// console.log(arg.brand); //error
} else {
//this is Not Person. so this is Car.
// console.log(arg.name); //error
console.log(arg.brand);
}
}
Discussion