TypeScriptの基本的な記述方法をまとめました【JavaScript/React】

JavaScript

どうしてもTypeScriptの書き方に慣れません、、。
React限定ですが、書き方の基本をまとめたのでメモしておきます!
変なところに気づいたら直します、、、。

プリミティブ型

TypeScriptとは、ざっくりな認識ですが、JavaScriptの値にひたすら型を定義していことです。
※それ以外にもTypeScript固有の機能もありますが、あまり詳しくない、、。

まず、もっとも基本的なプリミティブの型についてまとめます。

プリミティブとは、基本的な値のことです。具体的にあげると、文字列数字真偽値があります。
他にも値が存在しないことを表すnullや未定義な値であるundefinedも含まれます。

プリミティブの型を定義する

構文は次の通りで、変数名の後にコロン(:)で型を指定します。

let 変数名 : 型 = 値

以下は変数strValueの値が文字列型であることを表します。

// 文字列以外は代入できない
let strValue: string = "こんにちは";

文字列以外の値を設定することができないので、以下のように数値を代入しようとするとエラーになります。

// エラーになる
let strValue: string = 123;

これがTypeScriptの最大の特徴で、複数人が関わる大規模開発の場合に、バグを未然に防ぐことができるそうです。

プリミティブ型の定義例

それぞれの型の定義方法をまとめました。

// 文字列型 (string type)  
let str: string = 'Test';

// 数値型 (number type)
let num: number = 150;

// 真偽値型 (boolean type)
let bool: boolean = true;

// null型(null type)
let nullValue: null = null;

// undefined型(undefined type)
let undefinedValue: undefined = undefined;

この時点ではあまり難しいことはなさそうです!

リテラル型

今までは、文字列や数字など、値の種類で型を定義しましたが、リテラル型は、決まった値しか許容しません。
例えば、以下は”Hello”という文字列しか許容しない型です。

// 文字列"Hello"のリテラル型
let strHello: "Hello" = "Hello";

変数strHelloは、”Hello”という文字列しか許容していません。その為、例えばstrHelloに、同じ文字列である”Good bye”を代入しようとすると、次のようにエラーになります。

// 文字列"Hello"のリテラル型に"Good bye"を代入
let strHello: "Hello" = "Hello";
strHello = "Good bye"; 
//  Errorrメッセージ  Type '"Good bye"' is not assignable to type '"Hello"'.

ちなみに、英語で表示されたエラーを翻訳すると次のような意味になります。
タイプ「”Good bye”」はタイプ「”Hello”」に割り当てることができません。

このように、異なる値を設定しようとするとエラーになります。

any型

any型とは、すべての値が代入可能な型です。正確には型をチェックしません。
型エラーのチェックが行われないので、どんな型でも代入可能です。

// any型なのでどんな値も代入可能
let anyValue: any;
    anyValue  = "any型";  // 文字
    anyValue = 1234;  // 数字
    anyValue = true; // boolean
    anyValue ={item: "test"}; // オブジェクト
    anyValue =["item1", "item2"]; // 配列

基本的に、any型はさけるように推奨されていますが、とりあえず動くコードをany型で定義しておいて、改めて正しい型を定義する。みたいな使い方をしています。

列挙型(Enum)

列挙型(Enum)は定数をオブジェクトのように管理できる型で、文字列と数字を設定することができます。

enum 型の名前 {
   定数名1 = 値,
   定数名2 = 値,
   定数名3 = 値,
}

以下はDirectionという名前のEnum型が定義されています。
DirectionにUp、Down、Left、Rightの4つの定数を設定しています。

enum Direction {
  Up = 'UP',
  Down = 'DOWN',
  Left = 'LEFT',
  Right = 'RIGHT',
}

次のように値を取り出すことができます。

 // enum型から値を取り出す
let directionRight = Direction.Right;
console.log(directionRight) // 'RIGHT'

デフォルトだと、値は0から始まる数字が順番に割り振られます。

enum Direction {
  Up, // 0
  Down, // 1
  Left, // 2
  Right, // 3
}

デフォルトだと、最初の定数には「0」が割り振られますが、if判定で思った通りの挙動にならない時もあるので初期値を1にした方が良いかもしれないです。初期値を設定すれば以降の値も順番に割り振られます。

// 初期値を1に設定
enum Direction {
  Up = 1, // 1
  Down, // 2
  Left, // 3
  Right, // 4
}

ユニオン型

ユニオン型とは、複数の型を許容する型を表現したものです。
型はそれぞれパイプ記号(|)で区切ります。

let 変数名 : 型1 | 型2

例えば、string型とundefinded型を許容したい場合は次のように記述します。

// string型かundefinded型を許容する型
let textStr = string | undefined;

これで変数textStrは、string型とundefinded型を許容する変数になります。

ユニオン型とリテラル型を組み合わせる

リテラル型は、決まった値しか許容しない型です。
このリテラル型とユニオン型を組み合わせると、特定の値を複数許容する型が作成できます。

以下は、変数typeに文字列”ADD” または “DELETE” を許容する型です。

// 文字列"ADD" か "DELETE" を許容する型
let type: "ADD" | "DELETE" = "ADD";

useReducerのdispatchオブジェクトの型定義でよく使います。

型エイリアス

既存の型に別名をつけることを型エイリアスといいます。
構文は次の通りです。

type 型エイリアス名 = 型

通常、型エイリアスの名前の先頭は大文字にするのが一般的です。
型の名前は、管理のためにも分かりやすい名前をつけると良いです。

以下は、string型とnumber型に型エイリアスで名前をつけた例です。

// 型エイリアスでプリミティブ型に名前をつける
type PersonName = string;
type PersonAge = number;

型エイリアスはオブジェクトにも設定することもできます。
以下は、PersonTypeという名前の型エイリアスにオブジェクトを設定している例です。

// 型エイリアスでオブジェクトの型に名前をつける
type PersonType = {
  name: string,
  age: number
}

設定した型エイリアスのオブジェクトは次のように使用します。

const person: PersonType = { name: 'Yamada', age: 34 }

複数の型を許容する型エイリアス

複数の型を許容する型エイリアスも作成できます。
構文は次の通りです。

type 型の名前 = 型1 | 型2

以下は、文字列”taro”か”hanako”を許容する型エイリアスです。
※リテラル型の組み合わせです。

// 文字列"taro"か"hanako"を許容する型エイリアス
type NameType = "taro"  | "hanako"

こちらは、undefinedかstringを許容する型エイリアスです。
※プリミティブ型の組み合わせです。

// undefinedかstringを許容する型エイリアス
type NameType = string  | undefined

関数の型を定義する

関数にも型の定義が必要です。引数と戻り値に型を定義します。

function 関数名(引数名: 型 ): 戻り値の型  {
   処理….
   return 戻り値;
};

以下は、引数を足した値を戻り値として返している関数です。

// 戻り値は数値
function calc(num1: number, num2: number): number {
    return num1 + num2;
}
console.log(calc(1, 2));

戻り値に合わせた型を指定する必要があります。例えば戻り値が配列の場合、次のように値に合致する型を定義する必要があります。

// 戻り値は配列
function testFc(id: number, name: string) :[id:number,user: string] {
     return [id, name]
}

戻り値がない場合も型指定が必要で、その場合はvoidを設定します。

// 戻り値がない場合
function noResurnFc(text: string) :void {
  console.log(text)
}
noResurnFc("あいうえお") // あいうえお

コールバック関数

コールバック関数も考え方は同じで引数と戻り値に型を指定します。
以下はコールバック関数に型をした例です。

// コールバック関数cbに型を指定(引数はnumber、戻り値はなし)
function cbFunc(n1: number, n2: number, cb: (num: number) => void) {
  const result = n1 + n2;
  cb(result);
}
 // コールバック関数 ※コンソールを出力
cbFunc(10, 20, (result) => {
  console.log(result);
  return result;  // 呼び出し元関数で戻り値は使用しない
});

今回、cbFunc関数ではコールバック関数cbの戻り値の型をvoidにしています。これはコールバック関数の戻り値をcbFunc関数で使用しないことを宣言しています。
その為、上記のコードではコールバック関数に戻り値があってもエラーになりません。

ジェネリクス

ジェネリクスは、型が異なるだけの同じ処理を共通化するための仕組みです。ジェネリクスを使用すると呼び出し元の値で動的に型が変わる抽象的な型を設定できます。

以下はTypeScript組み込みのジェネリクスの例です。

const arrayStr = Array<string> = [];
// string[]と同じ意味

上記の<string>は、文字列型であることを表しますが、<number>や<any>に変更することもできます。Array<number>は数値型の配列になりますし、Array<any>は型制限のない配列になります。

今回の例のように配列の型だけを柔軟に変更できるのがジェネリクスです。

関数の引数の型を制限する

例えば、次のように受け取った引数を配列で返す関数があるとします。

// 引数contentは文字列型である
function testFcStr(id: number, content: string ) :[id:number,content: string] {
     return [id, content]
}
// 引数contentは数値型である
function testFcNum(id: number, content: number ) :[id:number,content: number] {
     return [id, content]
}

testFcStr(1, "コンテンツ"); // [1, "コンテンツ"]
testFcNum(1, 123); // [1, 123]

引数contentの型がstring型とnumber型である以外、同じ処理になっています。
型が異なるだけで関数を新たに作成するのは冗長であり、なるべくなら避けたいです。

そういった関数を共通化できるのがジェネリクスです。
先程の関数を共通化するには次のように記述します。

// ジェネリクスで異なる型の関数を共通化する
function testFc<T>(id: number, content: T ) :[id:number,content: T] {
  return [id, content]
}
// 関数を実行
testFc(1, "コンテンツ"); // [1, "コンテンツ"] // <T>はstring型になる
testFc(1, 123); // [1, 123] // <T>はnumber型になる

ポイントは引数の前にある<T>です。このTを型が変わる箇所に割り当てます。
Tは大文字1文字にすることが多く、他にUやKなどを設定するのが一般的で型引数と呼ばれます。

function testFc<T>(id: number, content: T ) :[id:number,content: T] {
     return [id, content]
}

これで先程の関数を共通化できました。
関数を使用する際、次のように引数の前に型を設定できますが、型推論が働いてそれぞれの型が自動的に判断されるので、可能でしたら省略します

// 型を設定する場合
testFc<string>(1, "コンテンツ"); // [1, "コンテンツ"]
testFc<number>(1, 123); // [1, 123]

型推論とは
コードの文脈から自動的に(暗黙的に)型を決定する機能のこと

extendsで型を制限する

先ほどは一部の型が異なる関数を共通化しました。
値によって型が自動的に決まるので便利ですが、処理によっては型を制限したいことがあります。

例えば、次の関数は引数として渡したオブジェクトを結合して返すだけの単純な関数です。

// ジェネリクスで異なるオブジェクトの型を設定する
function objFc<T, U>(obj1:T, obj2: U ): T& U {
  return { ...obj1, ...obj2 }; // 引数の値を連結
}

// 関数を実行
const result = objFc({name:"name1", age:10}, {hobby:["item1", "item2"]})
console.log(result); // {name: 'name1', age: 10, hobby: Array(2)}

2つの引数がオブジェクトの場合、結合されたオブジェクトが1つ返却されるので問題ない挙動です。
型推論が働くので次のように自動で型が設定されエラーも表示されません。

しかし次のようにオブジェクトと文字列を引数として渡した場合はどうなるでしょうか。
objFc<T, U>TUはそれぞれ異なる型という認識しかなく、objectや文字列といったことまではチェクしていません。

// ジェネリクスで異なるオブジェクトを設定する
function objFc<T, U>(obj1:T, obj2: U ) {
  return { ...obj1, ...obj2 }; // 引数の値を連結
}
// 引数にオブジェクトと文字列を設定する
const result = objFc({name:"name1", age:10}, "文字列")
// 文字列がオブジェクトとして結合される
console.log(result); // {0: '文', 1: '字', 2: '列', name: 'name1', age: 10}

実行結果は、文字列の文字が1文字ずつ分割されたオブジェクトとして連結されています。

// 実行結果 
{0: '文', 1: '字', 2: '列', name: 'name1', age: 10}

こういった思わぬ挙動を防ぐために、extendsで型を制限して明確にします。

function fc<T extends 制限する型>(arg: T) { }; 

以下はジェネリクスで型をobjectに限定しています。

// extendsで型をojbectのみに制限する
function objFc<T extends object, U extends object>(obj1:T, obj2: U ): T & U {
  return { ...obj1, ...obj2 }; // 引数の値を連結
}

これで引数にはオブジェクトしか設定できなくなります。

extendsにオブジェクトを渡す

先ほどのサンプルでは型をobjectに制限しましたが、objectであれば中身はなんでもOKの状態です。
関数の処理によっては不具合が発生するので、型をもう少し明確にします。
こちらがサンプルです。

// Typeで型を設定する
type ObjFc = {
  name: string;
  age:number;
}

// extendsでTの型をObjFcのみに制限する。
function objFc<T extends ObjFc>(obj:T) {
  // 引数objに対してObjFcで設定していないプロパティを使おうとするとエラーになる
  // obj.test; // error! Property 'test' does not exist on type 'T'.

  const addProp = "追加のプロパティ"; // objではないのでエラーにならない

  return {nameFc: obj.name, ageFc:obj.age, addFc:addProp }
}

// 引数に想定外のプロパティtestを設定する(この時点ではエラーが発生しない)
// ObjFcのnameとageプロパティは必須
const result = objFc({name:"こんにちは", age:10, test:"想定外のプロパティ"});

console.log(result) // {nameFc: 'こんにちは', ageFc: 10, addFc: '追加のプロパティ'}

上記はオブジェクト型の引数が渡ってくる関数です。
引数の型はType(ObjFc)で別途作成しておき、ジェネリクスのextendsで型制限をします。

type ObjFc = {
  name: string;
  age:number;
}
// 関数の引数にジェネリクスで型制限
function objFc<T extends ObjFc>(obj:T) {}

これで、この関数の引数にはObjFcの型が必須になります。
次のように直接ObjFcの型を設定すれば良さそうですが、少し挙動が異なります。

// 型をObjFcのみに制限する。
function objFc(obj:ObjFc){

ジェネリクスとの違いとして、型を直接ObjFcで設定した場合、関数実行時、引数にObjFcのプロパティ{ name: string; age:number; }以外を指定できないようにします。

// 型をobjFc(obj:ObjFc)で制限した場合
// 引数に想定外のプロパティtestを設定する(この時点でエラー発生)
// ObjFcのnameとageプロパティは必須
const result = objFc({name:"こんにちは", age:10, test:"想定外のプロパティ"});
// error: Argument of type '{ name: string; age: number; test: string; }' is not assignable to parameter of type 'ObjFc'.Object literal may only specify known properties, and 'test' does not exist in type 'ObjFc'.

対してジェネリクスで設定した場合、関数を実行する際の引数の制限はありませんが、{ name: string; age:number; }プロパティは必須です。

// 引数に想定外のプロパティtestを設定する(この時点ではエラーが発生しない)
// ObjFcのnameとageプロパティは必須
const result = objFc({name:"こんにちは", age:10, test:"想定外のプロパティ"});

配列の型を定義する

配列の型は、次のように定義します。

const 変数名: 型[]

型の後ろに配列を表す[]をつけるのがポイントです。

定義例は次のようになります。

// 配列に型を定義する
const arryStr: string[] = ["テキスト1", "テキスト2"]; //文字列型の配列
const arryNum: number[] = [1, 2];  //数値型の配列

ユニオン型を組み合わせると、複数の型を許容する配列ができます。
以下は、文字列型と数値型を許容する配列の型です。

// 文字列型と数値型を許容する配列の型
const arryStrNum: (string | number )[] = ["こんにちは", 1] // ユニオン型の配列

ジェネリクス型で定義する

ジェネリクスを使った書き方もあるのでメモしておきます。

const 変数名: Array<型>

書き方は異なるだけで、先程の型[]と意味は同じです。
ジェネリクスで書き換えたコードはこちらです。

const arryStr1: Array<string> = ["テキスト1", "テキスト2"]
const arryNum1: Array<number> = [1, 2]
const arryStrNum1: Array<string | number > = ["こんにちは", 1] // ユニオン型

オブジェクトの型を定義する

オブジェクトの型は、次のように定義します。

const 変数名: {プロパティ: 型, プロパティ: 型}

オブジェクトと同じように、型をプロパティとセットで記述するのがポイントです。
オブジェクトの型は冗長になりやすいので、次のように型エイリアスで記述することが多いです。

// 型エイリアスでオブジェクトの型を定義する
type PersonType = {name: string, age: number}
const person: PersonType = {name: "花子", age: 20}

配列の中にオブジェクトが含まれる場合

次のように、配列の中にオブジェクトが含まれる場合、

const personAry = [
     {name: "花子", age: 20},
     {name: "太郎", age: 10},
]

型の定義は次のようになります。型エイリアスの後ろに、配列を表す[]をつけます。

type PersonType = {name: string, age: number}
const personAry: PersonType[] = [
     {name: "花子", age: 20},
     {name: "太郎", age: 10},
]

ジェネリクス型で定義することもできます。

type PersonType = {name: string, age: number}
// ジェネリクス型
const personAry: Array<PersonType> = [
     {name: "花子", age: 20},
     {name: "太郎", age: 10},
]

?(はてな)で存在しない値を許容する

型を定義していると、この値がなくても処理に問題がない。ってことがあります。
その場合、変数やプロパティの後ろに「?」(はてな)をつけます。

以下は、ageがなくても処理に問題がないことを明示的に宣言しているのでエラーになりません。
この時の「?」(はてな)はオプション引数と呼ばれます。

// ageの値は無くても処理に問題がない
type DataType = {name: string, age?:number}

// ageがなくてもエラーにならない
const data:DataType = {name: "こんにちは"}

ageには暗黙的に number | undefined のユニオン型が定義されることになります。

オプショナルチェーン

オブジェクトの存在しない値にはアクセスできないようにしたい!ってことがあります。
特に外部から取得するプロパティは存在が把握できない事があります。

そんな時はプロパティの末尾に「?」(はてな)をつけると、存在しないプロパティ以降のアクセスをストップしてundefinedを返却します。

オプショナルチェーンを使わない記述

例えば次のようなオブジェクトがあり、addressの中にあるcountryにアクセスしたいとします。

type SampleObj = {
  id:number,
  name:string,
  address?: { // addressがなくてもOK
    country: string
  }
}

const sampleObj:SampleObj = {
  id:1,
  name:'user1',
  address: {
    country: "Japan"
  }
}

console.log(sampleObj.address.country) // Japan

上記はaddressのプロパティが存在しているので問題ないコードですが、以下のように何らかの理由でaddressが取得できない場合、

const sampleObj:SampleObj = {
  id:1,
  name:'user1',
  address: {
    // country: "Japan" addressの値がない!
  }
}

console.log(sampleObj.address.country) // エラー!

当然その中にあるcountryにもアクセスできないので未定義の可能性があるよ!ってエラーになります。

addressが存在している時だけcountryにアクセスするには次のように記述します。

console.log(sampleObj.address && sampleObj.address.country)

論理積(&&)で左辺の「sampleObj.address」が存在(true)していたら右辺の「sampleObj.address.country」を返します。

オプショナルチェーンを使った記述

上記と同じことをaddressプロパティの末尾に「?」(はてな)つけて表現できます。

console.log(sampleObj.address?.country)

存在があるか怪しいプロパティaddressの中にあるcountryにアクセスするには、次のように短く記述できます。

type SampleObj = {
  id:number,
  name:string,
  address?: {
    country: string
  }
}

const sampleObj:SampleObj = {
  id:1,
  name:'user1',
  address: {
    // country: "Japan" addressの値がない!
  }
}

// ?でsampleObj.addressがある時だけcountryにアクセス
console.log(sampleObj.address?.country) // undefined

!(感嘆符)で値の存在を宣言する

変数やプロパティの後ろに「!」(感嘆符)をつけると、値が絶対に存在すること、つまりnull | undefiendではないことを明示的に宣言します。

例えば、次のように単純に引数を戻り値で返す関数personFcがあるとします。

// エラーになる
function personFc(name?: string) {
  return name;
}
const resutName: string = personFc(); 
console.log(resutName); 

関数の引数がなくても 「?」(はてな)が設定されているのでエラーになりませんが、戻り値を代入する変数resutNameは文字列型の変数なので次のようなエラーになります。

name?の型は暗黙的に string | undefined のユニオン型が定義されているので、文字列型である変数resutNameの型と一致しない訳エラーになります。

単純にresutNameの型をstring | undefinedにすれば良さそうですが、他にも戻り値で返すnameの後ろに「!」(感嘆符)をつけることでもエラーが解消します。
多分ですが、undefinedでないことを宣言しているので、文字列型として処理されているのだと思います。

// !(感嘆符)でエラーを解消する
function personFc(name?: string) {
  return name!;
}
const resutName: string = personFc(); 
console.log(resutName); 

型キャスト

TypeScriptが型を完全に認識できない場合、型の情報を補完できる機能です。

例えば次のようなHTMLがあるとします。
inputタグにinput-elementというidが設定されています。

<input type="text" id="input-element" value="値" />

inputタグを基準にしてDOM要素を取得した場合

inputタグを基準にして変数inputElementにDOMを格納した場合、

const inputElement = document.querySelector("input");

inputElementの型推論はHTMLInputElementnullのユニオン型になります。

この場合は問題なくvalueの値を取得できます。

const inputElement = document.querySelector("input");
console.log(inputElement!.value) // 値

※上記のコードではinputElementは「!」でnullでないことを明示的に宣言しています。

id、クラス名を基準にしてDOM要素を取得した場合

id、クラス名を基準にしてinputElementにDOMを格納した場合、

const inputElement= document.getElementById("input-element");

型推論では、先ほどの例と異なりHTMLElementnullのユニオン型と表示されます。

この場合inputElementvalueを取得しようとするとエラーになります。

TypeScriptはinputElementがHTMLタグであることを理解できても、それがInputタグだと分からない為このようなエラーが出るようです。

型の情報を補完する

想定した型にならない時に型の情報を補完できるのが型キャストです。
以下のようにasで保管する型を指定します。

// 型キャストで型情報を補完
const inputElement = document.getElementById(
  "input-element"
  ) as HTMLInputElement | null

「!」でnullでないことを明示的に宣言することもできますが、次のようにif文で条件分けして記述する方法もあります。

const inputElement = document.getElementById("input-element")

// inputElementがnullでなければ
if(inputElement) {
  (inputElement as HTMLInputElement).value = "hogehoge"
}

型キャストは型情報を上書きするような機能なので、開発者が正しい型を指定しないとバグの原因になるので注意が必要です。

関数コンポーネントに型を定義する【React】

ここからはReact独自の型の設定についてメモしていきます。
関数コンポーネント型を定義するには、次のように定義します。

const App: React.FC = () => {
  return (
    <div>
        <p>テキスト</p>
    </div>
  );
};

関数コンポーネントの型はReact.FCになります。これはそのまま覚えます。

Propsに型を定義する

関数コンポーネントの引数Propsの型を定義するには、次のようにします。
※関係のある箇所に色をつけました。

// Propsの型エイリアス
type TestType = {
  text: string  // 文字列型
};
// コンポーネント 
const Test: React.FC<TestType> = ({ text }) => {
  return (
    <div>
      <p>{text}</p>
    </div>
  );
};

上記のコードは、Propsのtextプロパティの型が、文字列型であることを定義しています。

型エイリアスで定義したTestType を、関数コンポーネントのReact.FCにジェネリクスで渡しているのがポイントです。
ちなみに、型エイリアスで定義したTestType はオブジェクトで記述していますが、Propsはオブジェクトで渡ってくるので、型もオブジェクトで記述する必要がある為です。

childrenの型を定義する

Propsの特別な値、childrenの型は次のようにReact.ReactNodeを定義します。
以下は、childrenの値を表示するだけのコンポーネントです。

// Childrenの型エイリアス
type ChildrenType = {
    children: React.ReactNode
};
// コンポーネント
const Children: React.FC<ChildrenType> = ({ children }) => {
  return (
    <div>
      <p>{children}</p>
    </div>
  );
};

Propsで渡す関数を定義する(引数あり・戻り値あり)

Propsに関数が含まれる場合、型エイリアスは次のように定義します。

type 型エイリアス名 = {
   プロパティ名: (引数名: 型 ) => 戻り値の型;
};

以下は、ボタンをクリックしたら、引数を渡した関数の戻り値をアラートで表示するサンプルです。

// ReturnBtnの型エイリアス
type ReturnBtnType = {
  returnFc: (text: string) => string; // 引数、戻り値の型はstring
  children: React.ReactNode; // childrenの型
};
// 関数の戻り値を表示
const ReturnBtn: React.FC<ReturnBtnType> = ({ returnFc, children }) => {
  return (
    <div>
       // 戻り値をアラートで表示
      <button onClick={() => alert(returnFc("テスト"))}>{children}</button>
    </div>
  );
};

const App: React.FC = () => {
  // propsで渡す関数
  const returnFc = (text: string) => "出力:" + text;

  return (
    <div>
      // 関数をpropsで渡す
      <ReturnBtn returnFc={returnFc}>アラート表示</ReturnBtn>
    </div>
  );
};

ポイントだけメモしておきます。
以下は、returnFcの型が関数であることを表します。引数textは文字列型で、戻り値も文字列型です。

returnFc: (text: string) => string;

Propsで渡す関数を定義する(引数なし・戻り値なし)

引数がない場合、型の定義は必要ありませんが、戻り値はvoidを定義します。
以下は、ボタンをクリックしたらカウントアップするサンプルです。

// CountBtnの型エイリアス
type CountBtnType = {
  countFc: () => void; // 戻り値がない場合はvoid
  children: React.ReactNode; // childrenの型
};
// カウントアップ
const CountBtn: React.FC<CountBtnType> = ({ countFc, children }) => {
  return (
    <div>
      <button onClick={() => countFc()}>{children}</button>
    </div>
  );
};

const App: React.FC = () => {
  const [count, setCount] = useState(0);
  const countFc = () => setCount((prev) => prev + 1);

  return (
    <div>
      <p>カウント: {count}</p>
      <CountBtn countFc={countFc}>カウントアップ</CountBtn
    </div>
  );
};

以下は、countFcの型が関数であることを表します。引数はなしで、戻り値もありません。

countFc: () => void;  // 戻り値がないときはvoidを定義

useStateの型を定義する【React】

useStateの型について、基本的なことをまとめました。

useStateの型はジェネリクス型で定義する

useStateの型は、基本的に型推論が働くのであまり定義しません。
例えば次のStateは初期値が数値型の0なので、型を定義しなくても型推論が働くのでエラーになりません。

const [count, setCount] = useState(0);

もし明示的に型を定義したいときは、次のようにジェネリクス型で定義します。

const [count, setCount] = useState<number>(0);

配列やオブジェクトなどの複雑な型を定義する

型を定義するのは、主に配列やオブジェクトなどの複雑な値の場合です。
例えば、次のようにstateの値が、オブジェクトを含む配列の場合、

const [item, setItem] = useState([{id: 0, content: "テキスト1"}]);

次のように型エイリアスで型を定義します。

// Itemの型エイリアス
type ItemType = {
  id: number,
  content: string
};

const [item, setItem] = useState<ItemType[]>([{id: 0, content: "テキスト1"}]);

ここでのポイントは、stateの型エイリアスItemTypeはオブジェクト型を定義していることです。
実際のstateの値はオブジェクトを含む配列です。その場合、型の後ろに[]をつけます。

今回の場合、オブジェクトで定義した型エイリアスItemTypeの後ろに[]をつけるとオブジェクトを含む配列の型が作成できます。

型エイリアスで型を使い回す

このように型エイリアスを作っておけば、Propsで値を渡すときにも型を使い回すことができます。

型の使い回しとは、具体的に次のようになります。
以下は先程のstateの値を他のコンポーネントにPropsで渡して表示する例です。

// 型エイリアス ここから
type ItemType = {
  id: number;
  content: string;
};

type AllListType = {
  item: ItemType[];
};

// stateの値を表示するコンポーネント
const AllList: React.FC<AllListType> = ({ item }) => {
  return (
    <>
      <p>id: {item[0].id}</p>
      <p>content: {item[0].content}</p>
    </>
  );
};

// Appコンポーネント
const App: React.FC = () => {
  const [item, setItem] = useState<ItemType[]>([
    { id: 0, content: "テキスト1" }
  ]);
  return (
    <div>
      <AllList item={item} />
    </div>
  );
};

型の使い回しは、具体的には次の箇所です。

// 型エイリアス ここから
type ItemType = {
  id: number;
  content: string;
};

type AllListType = {
  item: ItemType[];
};

オブジェクトの型エイリアスItemTypeを、Propsの型エイリアスAllListTypeのitemプロパティの型にしています。
さらにItemTypeの後ろには[]があり、オブジェクトを含む配列を表します。

最終的には、Propsで渡ってきたプロパティの値に合致する型を作成できることになります。

イベントの型を定義する【React】

ここでのイベントとは、イベントに関数を設定する際、引数に渡ってくる値のことです。主にイベントに関する情報が格納されています。
なお、イベントの名前はなんでも良く「event」や「e」がよく使われます。※ここではeにしました。

 // Changeイベント
<input type="text" onChange={(e) => {
    console.log(e.target.value)
    }
  } />

イベントの型は、エディタアプリのVScode(Visual Studio Code)をホバーすれば表示されます。

eをマウスでホバーすると、型推論が働いて型が表示される

イベントに関数を直接設定すれば、上記のように型推論が働いて型を設定する必要はありませんが、別名で関数を設定する場合、イベントはany型になってしまいます。

そんな時は、型推論で表示されたイベントの型をコピーしておき、作成した関数に改めて貼り付けます。
最終的に、Changeイベントの型定義は次のようになりました。

 // Changeイベントに型を定義 
   const changeEvent = (e: React.ChangeEvent<HTMLInputElement>) => {
    console.log(e.target.value);
  };
  return (
    <input type="text" onChange={changeEvent} />
  )

まとめ

TypeScriptの基本的な使い方をまとめました。他にも色々な使い方があるので、使い方を覚えたら都度追加していこうと思います!