はじめに
TypeScriptの基本文法についてのメモ
参考
TypeScriptの特徴
- JavaScriptに静的型付けシステムとクラスベースのオブジェクト指向を加えた上位互換の言語
- 型チェック、型安全性: 間違ったプログラムをコンパイラが型チェックで検出してくれる
- 型推論: 型を書かなくても言語処理系が文脈から型を予想して型付けしてくれる
設定ファイルtsconfig.json
- TypeScriptコンパイラに対する設定を記述したファイル
- tscはtsconfig.jsonを自動的に読み込み、それに従ってコンパイルを実行
TypeScriptの文法
型アノテーション(型注釈)
- 変数に型を指定すること
- 型の不整合があると、コンパイル時にチェックされる
const 変数: 型 = 式;
コンパイラがその文脈から型を推測できる場合は、型アノテーションを省略しても自動的に保管して解釈してくれる
TypeScript固有の特殊な型
-
unknown型
- 方が不明である
- 通常、型チェックしてから利用する
-
null許容型
- strictNullChecksオプションが有効の場合、null/undefinedを禁止できる
- その時に、明示的にstring | null を使うとnullを認めることになる
関数コンポーネントの型定義
最近の TypeScript + React のベストプラクティスとして、FC を使わずに明示的に型定義する 方法が推奨
children の型を不要な場合に含めないことで、より厳密な型定義が可能。
戻り値の型を JSX.Element で明示できる。
import React from 'react';
// childrenを使わない時
type Props = {
name: string;
};
// childrenを使うときは明示的に用意する
type Props = {
name: string;
children?: React.ReactNode;
};
const Greeting = ({ name }: Props): JSX.Element => {
return <h1>Hello, {name}!</h1>;
};
export default Greeting;
// FC型のコンポーネントを定義
// propsの定義も上記の用にすれば良いので、わざわざFC型定義をつかわない方を推奨しているm
const MyComponent: FC<Props> = ({ title, description, children }) => {
return (
<div>
<h1>{title}</h1>
{description && <p>{description}</p>}
{children}
</div>
);
};
// 追加プロパティの設定
// 使う用途なし
MyComponent.displayName = "CustomComponent"; // コンポーネント名の指定
MyComponent.defaultProps = {
description: "This is a default description"
};
export default MyComponent;
拡張子 .ts .tsx
- tsはJSXを含まない
- tsxはJSXを含む
明示的に区別したほうがよい
比較演算子
==
は異なる型の比較した場合、暗黙の型変換をおこなってから両者を比較するためtrueになることがある。厳密に比較は===
を使う。
console.log(str === 3)
論理演算子
x && y
x || y
x ?? y
-
x && y
- xを真偽値に変換した結果、trueならyを返す、falseならx
-
x || y
- xを真偽値に変換した結果、falseならyを返す、trueならx
-
x ?? y
- xがnullまたはundefinedのときのみyを返す
- (||は0、falseなどの値もないものとして扱ってしまう)
- データがない場合は代替の値を使う、というシチュエーションに適している
オブジェクト
ここではクラスに由来しないオブジェクトのこと
// 省略記法
// プロパティと変数名が同じ場合
{name}
//動的なプロパティの決定
// インデックスシグネチャ
//[変数名: 型]
const obj = {
[name: string]: 123
}
インデックスシグネチャを使うと存在しないプロパティへのアクセスなどはコンパイルエラーにならず、型安全性が壊れてしまう。なので極力使わないほうがよさそう。
- オブジェクト型
const obj: {
foo: number,
bar: string
}
= {
foo: 1,
bar: "abc"
}
- type文で型に別名をつける
type FoobarObj ={
foo: number,
bar: string
}
const obj: FoobarObj
= {
foo: 1,
bar: "abc"
}
- typofキーワード
* 型なし変数の型推論を抽出して使うことができる。 - ただ、型は明示的に記載するべきなので、あまりtypeofを利用しないほうがよい
型引数
型を作成するときにパラメータを付与することができる
type USer<T>={
name: string,
child: T
}
このときThahasName型の部分型でなければいけない
type USer<T extends hasName>={
name: string,
child: T
}
const
この場合はnameや、ageは再代入できるが、userに別のオブジェクトを再代入はできないということ
const user ={
nam: "aaa",
age: 25,
}
typeとinterface
- typeは任意の型に対して別名をつけることができる、interfaceはオブジェクト型のみ
- typeのほうがよく使われるみたい
const user ={
nam: "aaa",
age: 25,
}
type T = typeof user;
const obj2: T = ...
typeof
型推論の結果を型として抽出、再利用する
部分型関係
ある型をほかの型の代わりに使える、ある型を他の型とみなせる
分割代入
- オブジェクトから値を取り出して、変数に代入する
const {foo,bar} = obj;
- これはオブジェクトのプロパティの中身をプロパティと同名の変数に入れたい場合にしか使えない。が、多くの場合同名を使う
ネストしたパターン
const nested = {
num: 123,
obj: {
foo: "heelo",
}
}
const {num, obj:{foo}} = nested
console.log(foo) // hello
Enum
Enumを使うより、リテラル型でEnumを表現するほうがよく利用されている
type Direction = "NORTH" | "SOUTH" | "EAST" | "WEST";
function move(direction: Direction) {
// ...
}
move("NORTH"); // OK
move("SOUTHWEST"); // エラー: Argument of type '"SOUTHWEST"' is not assignable to parameter of type 'Direction'.
タプル型
固定数の要素を持ち、それぞれの要素が異なる型を持つことができる配列のような型です。タプルは配列と似ていますが、要素の数とその型が固定されている点が異なります。
let x: [string, number];
x = ["hello", 10]; // OK
関数の型定義
TypeScriptではコンパイラオプションにnoImplicitAny
が指定されていないと、引数の型定義がなくても暗黙のうちにany
があてがわれてコンパイルが通ってしまうので、tsconfig.jsonにnoImplicityAnyの設定が必要
引数と戻り値をまとめて定義する方法
関数の型定義
interface Greeter {
(message: string, recipient: string): void;
}
const greet: Greeter = (msg, person) => {
console.log(`${msg}, ${person}!`);
};
Reactの関数コンポーネントの型
type TodoPresenterProps ={
todos: Todo[]
}
// <>はpropsの型
export const TodoPresenter: React.FC<TodoPresenterProps> = ({todos,}) => {
return (
<div>
</div>
)
}
ジェネリクスと型引数
型を具体的に指定せずにコンポーネントを定義し、それを使用する際に具体的な型を指定することができます
//<T>がジェネリクス
function identity<T>(arg: T): T {
return arg;
}
//<string>が型引数
let output = identity<string>("hello");
型推論で以下のようにもかける
let output = identity("hello"); // string型が推論される
型引数
TypeScriptのジェネリクスの一部として提供される機能であり、型をパラメータとして関数やクラス、インタフェースなどに渡すことができます。これにより、汎用的で再利用可能なコードを記述することができるようになります。
特定の型に依存せず、かつ型の安全性を保持したままコードを実装することができます
function identity<T>(arg: T): T {
return arg;
}
let output1 = identity<string>("hello");
let output2 = identity<number>(123);
identity関数は、型引数Tを持っており、入力としてT型の値を受け取り、同じT型の値を返します。この関数を呼び出す際に、具体的な型(この場合はstringやnumber)を指定することで、関数の挙動をその型に合わせることができます。
ユニオン型とインターセクション型
ユニオン型
複数の型のうちのどれか一つの型を持つことができる構文
type StringOrNumber = string | number;
let value: StringOrNumber;
value = "hello"; // OK
value = 123; // OK
value = true; // Error: boolean型はStringOrNumber型に代入できない
インターセクション型
複数の型を組み合わせて、それらのすべての特性を持つ新しい型を作成するために使用
オブジェクト型を拡張した新しい型を作る
type Name = {
name: string;
};
type Age = {
age: number;
};
type Person = Name & Age;
let person: Person = {
name: "Alice",
age: 30
};
オプショナルチェイニングによるプロパティアクセス
obj?.propの場合、objがnull、undefinedの場合でもランタイムエラーは発生せず、結果はundefinedになる
メソッドの場合はobj?.method()。
要するに簡潔に書ける
リテラル型
文字、数値、真偽値、BigIntはリテラルをそのまま型として扱うことができる
const foo: "foo" = "foo";
const two: 2 = 2; //2型
const isTrue: true = true;
const bigIntValue: 123n = 123n;
//今までのように型指定してなくても、constは型推論でリテラル型にしていた
const name = "aaa" // aaa型。なので再代入不可
リテラル型のwidening
リテラル型が自動的に対応するプリミティブ型に変化する挙動
// これはfoo型
const foo = "foo";
// これはstring型。プリミティブ型に変換される
// letで宣言された変数はあとで再代入されることが期待されるから
let foo = "foo";
// オブジェクトもプロパティは再代入可能なので、プリミティブ型になる
型のNull安全とは
- デフォルトの設定ではすべての型にnullとundefinedを代入できてしまう
- tstrictNullChecksを設定して、nullやundfinedを代入しているとコンパイルチェックされる
- null、undefinedを代入するには、型をstring | nullのように明示的に指定する必要がある
型のtypeof
変数の型をキャプチャして、他の変数の型として再利用することができます
let person = {
name: "Alice",
age: 30
};
type PersonType = typeof person;
function greet(arg: PersonType) {
console.log(`Hello, my name is ${arg.name} and I am ${arg.age} years old.`);
}
typeofをオブジェクトに適用して、オブジェクトの型を取得できる。
const person = {
name: "Alice",
age: 30
};
type PersonType = typeof person; // type PersonType = { name: string; age: number; }
typeof
typeof 変数名で型を取り出す。型推論されている型を抽出、再利用など
演算子のtypeof
実行時に変数のデータ型を文字列として取得 する。
const value = "Hello";
console.log(typeof value); // "string"
console.log(typeof 123); // "number"
console.log(typeof true); // "boolean"
keyof型
keyof 型 で型が例えばオブジェクトである場合、オブジェクトのプロパティ名全てを受け入れる型になる。A | B | C。
type Human ={
name: string,
age: number
}
// "name" | "age"という型になる
type Humankeys = keof Human;
keyof typeof オブジェクトとして、オブジェクトの型から各プロパティ名を型として抽出するときによく使われる。
型ガード、型絞り込み
型ガードは、TypeScriptのコード内である変数やオブジェクトの型を確認または絞り込むためのパターンや構造です。型ガードを使用することで、特定のスコープ内でその変数の型が保証され、それに応じて安全にその変数を操作できます。
各条件分岐内では型が絞れていて、コンパイルエラーは起きない
function padLeft(value: string | number) {
if (typeof value === "string") {
return `-${value}-`;
} else {
return value.toString();
}
}
class Bird {
fly() {
console.log("bird flies");
}
}
class Dog {
bark() {
console.log("dog barks");
}
}
function move(animal: Bird | Dog) {
if (animal instanceof Bird) {
animal.fly();
} else {
animal.bark();
}
}
as const
文字列、数値、BigInt、真偽値リテラルに対してつけられるリテラル型がプリミティブにwidenningしないリテラル型=readonlyになる
// 中は書き換え可能なため、リテラル型がstringに置き換わる
// よって、string[]型
const names1 = ["aaa","bbb","ccc"]
// constのため、書き換え不可(readonly)、さらにリテラル型のままになる
// readonly["aaa","bbb","ccc"] 型
const names1 = ["aaa","bbb","ccc"] as const
モジュールとカプセル化
モジュール内で定義された変数はそのモジュール内をスコープとしてもつ。変数を変更できるのは、そのモジュール内だけから
import export
import { } from "モジュール名"
// export defaultの場合
import 変数名from "モジュール名"
@types
TypeScriptでもジュルーが書かれていない場合に、@typesパッケージをインストールして型情報をインストールする
部分型
DogはAnimalの部分型
Animalが持つプロパティはすべて存在する。そのプロパティの方はAnimalの方の部分型または同じ型
ある型を他の型の代わりに使える、ある方を他の型とみなせるという直感的な概念に基づくもの
type Animal = {
name: string;
age: number;
};
type Dog = {
name: string;
age: number;
breed: string; // `Dog` は `Animal` に `breed` を追加した型
};
const dog: Dog = { name: "Buddy", age: 3, breed: "Golden Retriever" };
const animal: Animal = dog; // ✅ `Dog` は `Animal` の部分型なので代入可能
// こうすることで制限できる
// DogはAnimalの部分型
Dog extends Animal
型引数
- 型を定義するときにパラメータをもたせることができる
- 型引数を持つ型はジェナリクス型と呼ばれる
// <>の中には型を指定する
// Parent、ChildはHasNameの部分型である
type Family<Parent extends HasName, Child extends HasName>
// Animal、HumanはHasnameの部分型
type T = Family<Animal, Human>
type Animal = {
name: string;
};
type Family<Parent = Animal, Child = Animal> = {
parent: Parent;
child: Child;
};
帰り値の型による部分型関係
引数の型が同じでないといけない
type Response = {
status: number;
message: string;
};
type SuccessResponse = {
status: 200;
message: "OK";
data: string;
};
function fetchResponse(): Response {
return { status: 500, message: "Server Error" };
}
function fetchSuccessResponse(): SuccessResponse {
return { status: 200, message: "OK", data: "All good!" };
}
let apiCall: () => Response;
// ✅ OK(SuccessResponse は Response の部分型)
apiCall = fetchSuccessResponse;
keyof typeof
typeofで型を抜き出して、keyofで型のプロパティ名を型として取り出す
const STATUS = {
SUCCESS: 200,
ERROR: 500,
} as const;
type Status = keyof typeof STATUS; // "SUCCESS" | "ERROR"
type Response = {
status: Status;
message: string;
};
const fetchResponse = (): Response => ({
status: "SUCCESS",
message: "Operation completed!",
});
let apiCall: () => Response;
// ✅ OK
apiCall = fetchResponse;
Lookup 型
T[K] のように書くことで、
型 T の中から特定のキー K の型を取得する仕組みです。
これは オブジェクト型の特定のプロパティの型を動的に参照する のに使われます。
同じことを2度書かないし。プロパティの型を指定することで、プロパティが変わったとしても修正が必要ないのd
type User = {
id: number;
name: string;
email: string;
};
// Lookup 型を使って `User["name"]` の型を取得
type UserName = User["name"]; // string
型コンテキストのIn演算子
if ("プロパティ名" in オブジェクト) {
// ここでは "プロパティ名" があることが保証される
}
type User = {
id: number;
name: string;
email: string;
};
// マップ型を使って全プロパティをオプショナルにする
type PartialUser = {
[K in keyof User]?: User[K];
};
// ✅ PartialUser の型
// {
// id?: number;
// name?: string;
// email?: string;
// }
const user: PartialUser = { name: "Alice" }; // ✅ OK(すべてオプショナル)