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型で定義しておいて、改めて正しい型を定義する。みたいな使い方をしています。

ユニオン型

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

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("あいうえお") // あいうえお

ジェネリクス

ジェネリクスは、型が異なるだけの同じ処理を共通化するための仕組みです。

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

// 引数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<string>(1, "コンテンツ"); // [1, "コンテンツ"]
testFc<number>(1, 123); // [1, 123]

ポイントは引数の前にある<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]

引数の前の型<string><number>は省略することも可能です。
上記の場合、型推論が働いてそれぞれの型が自動的に設定されるので、可能でしたら省略します。

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

配列の型を定義する

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

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 PersonType1 = {name: string, age: number}
const personAry: PersonType1[] = [
     {name: "花子", age: 20},
     {name: "太郎", age: 10},
]

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

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

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

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

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

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

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

ageには暗黙的に number | 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); 

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

関数コンポーネントに型を定義するには、次のように定義します。

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の型を定義する

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で渡ってきたプロパティの値に合致する型を作成できることになります。

イベントの型を定義する

ここでのイベントとは、イベントに関数を設定する際、引数に渡ってくる値のことです。主にイベントに関する情報が格納されています。
なお、イベントの名前はなんでも良く「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の基本的な使い方をまとめました。他にも色々な使い方があるので、使い方を覚えたら都度追加していこうと思います!