2
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【TypeScript】データ型の定義・活用方法をまとめてみました

Posted at

はじめに

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 を使うことで、データ型を定義した変数を、別の変数に定義できます。

上記コードの例でいくと、msg2msgで定義されている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というファイルを作り、そこにペーストして使っていくという設定。

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",

    // 以下省略 //
App.tsx
import Data from './data.json';

type USERS = typeof Data; //data.jsonで型定義されているデータをUSERSの中に代入

これだけの量のデータ型を1から定義するのは大変。
JSON型推論からデータ型を取得することで、作業量を大幅に削減することができる。

#React Hooks Props型

App.tsx
const App: React.FC = () => { //Reactのfunctional componentに型定義するときの記述
  return (
    <div>
    
    </div>
  )
}
TestComponent.tsx
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;

App.tsx
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

TestComponent.tsx
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: データ型

TextComponent.tsx
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の講座から、コードなどを抜粋してまとめました。

大まかに概念や使い方は把握できたので、あとは実際の開発の中で使いながら、自分の”血肉”としていきたいです。

しっかりとデータ型を定義していくことで、エラーに強い堅牢なコードを書くことが出来るということが分かりました。
慣れないうちは大変かと思いますが、うまく活用できれば今後の自身の開発をスムーズに進めてくれるツールになると信じて頑張ります。

当記事は、今後も自身の成長に合わせてアップデートしていくつもりです。
何か修正箇所などございましたら、お知らせいただけると幸いです。

最後までお読みいただき、ありがとうございました!

#参考

2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?