はじめに
TypeScriptを使ったReactのアプリを作るため、TypeScriptの基礎を学習しました。
データ型のさまざまな定義方法を学んだので、整理しつつアウトプットしていきます。
#TypeScriptのデータ型
//型推論
let greet = "hello"; //文字列リテラル
let number = 2; //number型
let bool = true; //boolean型
//アノテーション
let bool: boolean = true; //bool: booleanのように”明示的”に型を記述することも可能
//配列の型定義
let array1 = [true, false]; //boolean[]
let array2 = [0,1, "hello"]; //[number | string]
//オブジェクトの型定義
interface NAME {
firtst: string;
last: string | null; //このようにnullをデータ型として渡すことも出来る
}
// last?: string とすれば、下記nameObjで{first: "Taro"}飲みの記述でもエラーにならない
// オブジェクトの型を変数に定義する
let nameObj: NAME = {first: "Taro", last: "Yamada"}; //
// データ型を定義した関数
const func1 = (x: number, y: number) => {
return x + y;
};
let number = 2
のように、変数や定数を定義するとTypeScriptが自動で**”型推論”**といものを行ってくれます。その結果、エディタ上で変数にホバーするとnumber: number
のように、データ型を確認することができます。
interface NAME = {name: string}
のようにオブジェクトでデータ型を定義することも可能で、さらにそれを別の変数などに当てはめることもできます。
例えば、let nameObj: NAME = {name: "hoge"}
のような形です。
#Intersection Types
type PROFILE = { //type1の型を定義
age: number;
city: string;
};
type LOGIN = { //type2の型を定義
username: string;
password: string;
};
type USER = PROFILE & LOGIN; //type1と2をUSERというtypeに代入
const user: USER = { //userという定数にtype USERというオブジェクトのデータ型を定義して、それぞれのデータ型に実際のデータを定義する
age: 30,
city: "Tokyo",
username: "xxx",
password: "yyy",
}
type PROFILE ~
type LOGIN ~
のように、それぞれ別に作成した”オブジェクトのデータ型同士”を、一つに接続することも出来ます。
その際はtype USER = PROFILE & LOGIN
と記述します。+ではなく**"&(アンパサンド)"**を使用します。
一つにしたtypeを定数などに定義して使う場合はconst user: USER = {}
として、{}の中に、それぞれのtypeで使われていた属性と、そのデータ型の一致するstringやnumberなどを入力します。
#UnionTypes
// |(バーティカルバー)でつなぐUnion Types
let value: boolean | number; //変数へのデータ型定義。|でつなぐことで複数のデータ型を定義できる
value = true;
value = "hello"; //stringは定義していないのでエラーになる
value = 3;
// 配列への複数データ型定義
let arrayUni: (number | string)[]; //変数arrayUniという配列にデータ型を定義
arrayUni = [0, 1, 2, "hello", true] //booleanは定義していないのでエラーになる;
let value
というように、何かしら変数名を定義するとして、その変数に"Union Types"でデータ型を複数定義できます。
その際は ” | (バーティカルバー)” を用いてlet value: boolean | number
のように記述します。
配列にも同じようにUnion Typesを使用できて、let arrayUni: (number | boolean)[];
とすることでarrayUni = [1, 2, 3, true];
のように値を代入できます。
#Literal Types
// Literal TypesとUnion Typesの融合
let company : "Google" | "Facebook" | "Amazon";
company = "Facebook";
company = "Apple"; // Appleはリテラルとして定義されていないのでエラー
let memory: 256 | 512;
memory = 256;
memory = 555; // リテラルが定義されていないのでエラー
具体的な**”文字列リテラル”**も、あらかじめデータ型として変数や定数に定義できます。
" | "を使用してUnion Typesとして複数定義も可能です。
#typeof
let msg: string = "Hi"; //string型をmsgに定義
let msg2: typeof msg; //msg2にmsgのデータ型を代入
msg2 = "hello"; //msg2はmsgを継承しているのでstringの”hello”を定義できる
// オブジェクトで定義したデータ型を継承
let animal = {cat: "small cat"}; // {cat: string}として定義
let newAnimal: typeof animal = {cat: "big cat"}; // typeofでstring型を継承したので"big cat"を定義できる
typeof
を使うことで、データ型を定義した変数を、別の変数に定義できます。
上記コードの例でいくと、msg2
はmsg
で定義されているstring型を継承しているのでmsg2 = "hello"
というstring型の値を代入することが出来るようになります。
また、オブジェクトで定義したデータ型も同じ用に継承できます。
let animal = {cat: "small cat"};
は型推論で{cat: string}
となっているので、
let newAnimal: typeof animal = {cat: "big cat"};
のように別の変数に、strin型の"big cat"を代入できます。
#keyof
type KEYS = {
primary: string;
secondary: string;
}
let key: keyof KEYS; //KEYSの属性(primary, secondary)を継承
key = "primary"; //keyである"primary"もしくは"secondary"という「string」しか受け付けない
// keyofとtypeofを組み合わせる
const SPORTS ={
soccer: "Soccer", // key = soccer, type = stringという状態
baseball: "Baseball",
}
let keySports: keyof typeof SPORTS; // SPORTSのkeyとtypeを継承する
keySports = "soccer";
keySports = "baseball"; //baseballというkeyと、Baseball(string)それぞれの要素を持った"baseball"という文字列を定義できる
let key: kyeof KEYS
は”KEYSの中の、string型の各keyを変数keyに渡す”ということなので、変数keyにはkey = "primary"
もしくは key = "secondary"
が代入できるということになります。
#enum(列挙型)
enum OS {
Windows, // 0
Mac, // 1
Linux, // 2 ※enumで定義することで、それぞれに自動的に番号が割り当てられる
}
interface PC {
id: number;
OSType: OS; // enumで定義したデータを継承
}
const PC1: PC = {
id: 1,
OSType: OS.Windows, // OS.まで打つと、エディタではWindows, Mac, Linuxと予測が表示される。enumで定義してこのように使うほうがメンテナンス性の向上や、バグが起こりにくくなるというメリットがある。
}
const PC2: PC = {
id: 2,
OSType: OS.Mac,
}
const PC1: PC = {...省略}
でinterface
で定義したデータ型(enumも含む)を継承できる。
**”{}(波括弧)”**の中でOSType: OS.Windows
とすることで、このOSTyep:には"0"番が割り当てられていることになる。
#型の互換性
const comp1 = "test";
let comp2: string = comp1; //”抽象的”なstring型のcomp2に”具体的”な文字列リテラルを持つcomp1は代入可
let comp3: string = "test"
let comp4: "test" = comp3; //comp4: "test"は文字列リテラルで具体的なので、そこに抽象的な"string"型を持つcomp3は代入できない
let funcComp1 = {x: number} => {};
let funcComp2 = {x: string} => {};
funcComp1 = funcComp2; //xのデータ型が違うのでエラー
”抽象” → ”具体”は代入できるが、”具体” → ”抽象”は代入できない。
#Generics(ジェネリックス)
interface GEN<T> {
item: T;
}
const gen0: GEN<string> = {item: "hello"};
const gen1: GEN = {item: "hello"}; // GENのあとに<>を付けないとエラー
const gen2: GEN<number> = {item: 12};
interface GEN1<T = stirng> { // あらかじめTにstring型を定義
item: T;
}
const gen3: GEN1 = {item: "hello"}; //GEN1にはstring型が定義されているので、<>は付けなくても{item: "hello"}を定義できる
interface GEN2<T extends string | number> = {
item: T;
}
const gen4: GEN2<boolean> = {item: ture}; // エラー。extendsでbooleanは定義していない
function funcGen<T>(props: T) {
return {item: props};
}
const gen5 = funcGen("test") //明示的にfuncGen<string>("test")と書くことも可
const gen6 = funcGen<string | null>(null) // union typesでデータ型を指定することもできる
function funcGen1<T extneds string | null>(props: T) { //あらかじめデータ型を定義
retunr {value: props};
}
const gen7 = funcGen1("hello");
const gen8 = funcGen1(123); //extendsでnumberは定義されていないのでエラー
interface Props {
price: number;
}
function funcGen3<T extends Props>(props: T) { //prosに引数として受け取れるデータは{price: number}
return {value: props.price};
}
const gen9 = funcGen({price: 10});
// funcGen3をアロー関数で記述したら
const funcGen3 = <T extends Props>(props: T) => {
return {value: props.price};
}
GENの”T”は**「エイリアス」**と呼ばれ、その他には大文字の”U”もよく使われる。
また、ジェネリックスはReactでいうところの”props”のようにとして記述している箇所に、データ型や値を代入できる。
#JSON型推論
jsonplaceholder
でWeb検索して、そのページ内の/users
というリンクから下記データをコピー。
data.json
というファイルを作り、そこにペーストして使っていくという設定。
[
{
id: 1,
name: "Leanne Graham",
username: "Bret",
email: "Sincere@april.biz",
address: {
street: "Kulas Light",
suite: "Apt. 556",
city: "Gwenborough",
zipcode: "92998-3874",
geo: {
lat: "-37.3159",
lng: "81.1496"
}
},
phone: "1-770-736-8031 x56442",
website: "hildegard.org",
company: {
name: "Romaguera-Crona",
catchPhrase: "Multi-layered client-server neural-net",
bs: "harness real-time e-markets"
}
},
{
id: 2,
name: "Ervin Howell",
// 以下省略 //
import Data from './data.json';
type USERS = typeof Data; //data.jsonで型定義されているデータをUSERSの中に代入
これだけの量のデータ型を1から定義するのは大変。
JSON型推論からデータ型を取得することで、作業量を大幅に削減することができる。
#React Hooks Props型
const App: React.FC = () => { //Reactのfunctional componentに型定義するときの記述
return (
<div>
</div>
)
}
import React from 'react';
interface Props {
text: string;
}
const TestComponent: React.FC<Props> = (props) => { //React.FC<Props>とすることで引数のpropsの型を、interfaceで定義したPropsの型にすることができる
return (
<div>
<h1>{props.text}</h1>
</div>
)
}
export default TestComponent;
import TestComponent from './TestComponent';
const App: React.FC = () => { //Reactのfunctional componentに型定義するときの記述
return (
<div>
<TestComponent text="hello world" /> //propsにtext="hello world"を渡す。渡す属性はtextで、値はstring型である必要がある。
</div>
)
}
functional componentにReact.FC<>
と付け足すことで、TypeScriptのデータ型をpropsに渡す事ができる。
TestComponentのpropsには"text: string"がデータ型として定義されているので、App.tsxでTextComponentを使用する際は、そのpropsのtextに”hello world”
などの"string型"の値を代入する。それ以外のデータ型ではエラーになる。
#React Hooks useState
import React, {useState} from 'react'; //useStateをインポート
interface Props {
text: string;
}
interface UserData {
id: number;
name: string;
}
const TestComponent: React.FC<Props> = (props) => {
const [count, setCount] = useState<number | null>(null); //ジェネリックスを使ってunion typesで複数の型定義も出来る
const [user, setUser] = useState<UserData>({id: 1, name: "dummy"}); //UserDataで定義したデータ型しか受け付けない
return (
<div>
<h1>{props.text}</h1>
<p>{count}</p> //nullが返る
</div>
)
}
export default TestComponent;
useState()にもジェネリックスを使ってuseState<number>(3)
のようにデータ型を渡して、それに適合する値をstateの初期値として代入することができる。
#Event handler: データ型
import React, {useState} from 'react';
interface Props {
text: string;
}
interface UserData {
id: number;
name: string;
}
const TestComponent: React.FC<Props> = (props) => {
const [count, setCount] = useState<number | null>(null);
const [user, setUser] = useState<UserData>({id: 1, name: "dummy"});
const [inputData, setInputData] = useState("");
const handleInputChange = (e: React.changeEvent<HTMLElement>) => // e: のデータ型はonChange={handleInputChange}を”ホバー”した時に現れるデータ型をコピーする
setInputData(e.target.value); //inputタグに入力された値を取得してinputDataに代入
return (
<div>
<h1>{props.text}</h1>
<p>{count}</p>
<input type="text" value={inputData} onChange={handleInputChange} />
<h1>{inputData}</h1> //handleInputChange関数でinputDataにデータを代入して、それをリアルタイムでここに表示する
</div>
)
}
export default TestComponent;
e:
のデータ型はonChange={}
をホバーした時に現れるポップ?に表示されるので、それをコピペする。
#まとめ
下記、”参考”欄に掲載しているudemyの講座から、コードなどを抜粋してまとめました。
大まかに概念や使い方は把握できたので、あとは実際の開発の中で使いながら、自分の”血肉”としていきたいです。
しっかりとデータ型を定義していくことで、エラーに強い堅牢なコードを書くことが出来るということが分かりました。
慣れないうちは大変かと思いますが、うまく活用できれば今後の自身の開発をスムーズに進めてくれるツールになると信じて頑張ります。
当記事は、今後も自身の成長に合わせてアップデートしていくつもりです。
何か修正箇所などございましたら、お知らせいただけると幸いです。
最後までお読みいただき、ありがとうございました!
#参考