ここではTypeScriptの基本的な構文や気になったところをメモしていきます。
React学習のための通過点のためReactでよく使う部分を中心にメモしていく。
TypeScriptとは
TypeScriptはマイクロソフトがオープンソースで公開しているJavaScriptを拡張する形で設計されてたプログラミング言語のこと。
特徴としては以下が挙げられる。
- JavaScriptとは異なり型付けの言語であること
- コンパイルによるエラー検出やコードの安全性の向上
- JavaScriptの上位互換であること
またTypeScriptはビルドツールを使って最終的にJavaScriptのコードに変換(トランスパイル)されるため、JavaScriptで実装したものとパフォーマンスの差異はない。
型の定義
変数や引数名の後ろに:型名をつける。これを型注釈といい、この型情報を変数や引数に付与することで値を制限する。
ただ、TypeScriptには型推論の機能が備わっているため、型の定義をしなかった場合自動的に型が決定される。
変数
変数の宣言にはvar``let``constを用いる。
var:再宣言や再代入が可能
let:再代入ができて再宣言ができない
const:再宣言と再代入ができない
varは関数スコープであり、letはブロックスコープであるため影響が少ないletを使うのが好ましい。
var 変数:型 = 値
let 変数:型 = 値
const 変数:型 = 値
var num: number = 1000
let flug: boolean = true
const age: string = '21'
配列
配列に型を指定するには構成する型と[]表記を用いる。
const array; string[] = []
// arrayにString型の要素を追加
array.push('Hello')
// arrayにnumber型の要素を追加
array.push(1)
// 配列の型と異なるためコンパイルエラーになる。
オブジェクト型
オブジェクトはKeyとvalueによるデータ形式のインスタンスで、TypeScriptではkeyの後に:型として型を定義する。
var 変数:{キー名:型; キー名:型; ...} = オブジェクト
let 変数:{キー名:型; キー名:型; ...} = オブジェクト
const 変数:{キー名:型; キー名:型; ...} = オブジェクト
// オブジェクトの宣言
const user:{name:string; age: number} = {
name:'taro'
age:21
}
// 値の出力
console.log(user.name)
// taro
console.log(user.age)
// 21
オブジェクトの型はプロパティに?をつけることでプロパティを省略することができる。
function printName(obj; {firstName:string; lastName?:string}){
}
// どちらもコンパイル時にエラーは出ない
printName({firstName:'taro'})
printName({firstName:'taro';lastName:'sato'})
any
anyは全ての型を許容する型のこと。
let u:any = 32
// 他の型に代入してもエラーが起きない。
let n:string = u
anyを利用すると型チェックの機能が動作しなくなるためTypeScriptの恩恵がなくなるので基本的にはanyは使わない。
関数
TypeScriptの関数では引数と戻り値に型を定義できる。
function 関数名(引数:型,引数:型,...):戻り値の型{
// 関数の処理
}
// 関数の定義
function sayName(name:string):string{
return `Hello ${name}`
}
//
sayName('taro')
また引数に?をつけることで引数をオプション扱いにできる。
// 関数の定義
function sayName(name:string,greeting:string):string{
return `${greeting} ${name}`
}
//
sayName('taro') // taro
sayName('taro','Hello') // Hello taro
引数定義の際にデフォルト値を指定することで、引数を指定しない場合値がセットされる。
// 関数の定義
function sayName(name:string,greeting:string = 'Hello'):string{
return `${greeting} ${name}`
}
//
sayName('taro') // Hello taro
sayName('taro','Hey') // Hey taro
引数や戻り値には関数も指定できる。
function printName(firstName:string,fomatter:(name:sting) => string){
console.log(fommatter(firstName))
}
// 名前に「さん」をつける関数を定義
function formatName(name:string):string{
return `${name}さん`
}
printName('taro')
// taroさん
printName関数の引数であるfomatterでは**引数がstring型であるnameの戻り値がstring型である関数を示している。
実際fomatName関数は引数がstringで戻り値がstringの関数で、引数の条件を満たしている。
アロー関数
アロー関数の場合の型指定は以下の通りです。
(引数名:引数の型):戻り値の型 => JavaScriptの式
let sayHello = (name:string):string => `Hello ${name}`
基本的な型の機能
型アサーション
型アサーションでは既に決定済みの型を上書きする機能のこと。
TypeScript上でDOMを扱うときHTMLElement(もしくはnull)として扱われます。この時HTMLElementでもdivなのかcanvasなのかはTypeScript上では判断できない。
開発者側が型を知っている場合、この型アサーション機能を使って具体的な型に上書きすることができる。
対象となる型よりも具体的になる型、または汎化させる型に変更する場合に型アサーションを使う。
変数 = 値 as 型
// main_canvasというidを持つElementを取得し、HTMLCanvasElementという型に上書きしている。(元はHTMLElement)
const myCanvas = document.getElemetById('main_canvas') as HTMLCanvasElement
型エイリアス
型エイリアスは型の定義に別の名前をつけることができる機能のこと。
この名前を参照して同じ方を複数回利用することができる。
type 型名 = 型
型名は大文字始まりにすることが一般的
// stringをNameという名前でも使えるようにしている。
type Name = string
// number型のx,yのプロパティを持つPointという型エイリアスを定義
type Point = {
x:number;
y:number;
}
// Point型を引数にとる関数の定義
function printPoint(point:Point){
// Point型のxプロパティを出力
console.log(point.x)
// Point型のyプロパティを出力
console.log(point.y)
}
関数自体の型も型エイリアスで定義可能
// 引数がstringで戻り値がstringの関数
type Formatter = (a: string) => string
オブジェクトのキー名を明記しなくても型エイリアスは定義可能
// オブジェクトのkeyをstring、valueをstringの型をLabelという型名にしている
type Label = {
[key:string]: string
}
// 型エイリアスで定義した方でオブジェクトを作成
const labal:Label = {
topTitle:'トップページのタイトル',
topSubTitle:'トップページのサブタイトル',
topFeature:'トップページの機能'
}
インターフェイス
インターフェイスは型エイリアスと同じく型を定義する機能のこと。
型エイリアスよりも拡張性があり、クラスと一緒に用いることが多い。
interface 型名{
プロパティ1:型1;
プロパティ2:型2;
// ...
}
interface Point{
x:number;
y:number
}
型エイリアスと異なり情報を追加できる
interface Point{
x:number;
y:number
}
// プロパティの追加
interface Point{
z:number;
}
インターフェースを他のインターフェースに継承することもできる
// Colorful型を宣言
interface Colorful{
color:string;
}
// Circle型を宣言
interface Circle{
radius:number;
}
// Colorful型とCircle型を継承してColorfulCircle型を宣言
// Colorful型とCircle型のどちらも使える
interface ColorfulCircle extends Colorful, Circle{}
const cc: ColorfulCircle = {
color: '赤',
radius: 10
}
クラス
クラスの定義方法は以下の通り。
JavaScriptのクラス記法に型が宣言できる機能が追加されただけ。
class クラス名{
フィールド1:型1;
フィールド2:型2;
//...
メソッド名(変数:型名){
//メソッドの処理
}
}
戻り値がない場合はvoidを型名にする
class Point{
// フィールドの定義
x:number;
y:number;
// コンストラクタメソッドの定義
constructor(x:number = 0, y:number = 0){
this.x = x
this.y = y
}
// moveXメソッドの定義
moveX(n:number):void{
this.x +=n
}
// moveYメソッドの定義
moveY(n:number):void{
this.y +=n
}
}
const point = new Point()
point.moveX(10)
console.log(`${point.x}, ${point.y}`)// 10, 0
クラスもextendsを使って別のクラスを継承することができる
アクセス修飾子
アクセス修飾子でメソッドやフィールドのアクセスの範囲を制御することができる
| アクセス修飾子 | 説明 |
|---|---|
| 宣言なし | どこからでもアクセス可能 |
| public | どこからでもアクセス可能 |
| private | 自身のクラスからのみアクセス可能 |
| protected | 自身のクラスとサブクラスからアクセス可能 |
アクセス修飾子はフィールドの前に付与する。
class BasePoint{
public x: number;
private y: number;
protected z: number;
}
開発で重要な型
Enum型
Enum型は列挙型と呼ばれており、関連する定数まとめて定義することができる。
enum 型名 = {
定数名1,
定数名2,
//...
}
// Enum型のDirectionを定義
enum Direction = {
Up,
Down,
Left,
Right
}
//Direction型を参照
let direction: Direction = Direction.Left
// 2という数字が出力される
console.log(direction)
Enum型は定義された順番に0からインクリメントされた数字が代入される。
もちろん定数なので値を指定することもできる。
enum Direction = {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'LIGHT'
}
定数をまとめたオブジェクトを型として扱うことができるためバグやエラー解決が行いやすい。
ジェネリック型
ジェネリック型はクラスや関数で使う型を外部から指定できるようにして、クラスや関数に型の汎用性を持たせる機能のこと
型を引数に入れるイメージ。
// クラスの定義
class クラス名<型変数>{
//クラスの処理
}
// クラスの呼び出し
const インスタンス名 = new クラス名<型名>()
型変数を<>で囲むことで外部から型を指定できる。
class Queue<T>{
// T型の配列の初期化
private array: T[] = []
// 配列にT型の要素を追加
push(item: T):void{
this.array.push(item)
}
// 配列の一番初めの値を取り出す
pop():T | nudefined{
return this.array.shift()
}
}
// Queueクラスの呼び出し
// number型を指定
const queue = new Queue<number>()
queue.push(111)
queue.push(112)
// number型ではなのでエラー
queue.push('hogehoge')
Reactコンポーネントもジェネリック型のクラスで定義されており、コンポーネントが受け取るpropsの型を外部から定義することができる。
Union型・intersection型
|や&を用いて複数の型を組み合わせた型のこと。
変数や戻り値、型エイリアスに対して指定可能。
|は話集合を表すUnion型、&は積集合を表すintersection型のこと。
intersection型はそれぞれのデータ型がマージされた型として扱う。
// 変数の場合
let example : string | number
// 関数の引数に指定する場合
function printId(id: string | number){
console.log(id)
}
// 型エイリアスに使う場合
type Id = string | number
function printId(id: Id){
console.log(id)
}
type Contact = {
name: string;
email: string;
phone; string
}
type Identity = {
id: number | string;
name: string;
}
// intersection型
type Employee = Identity & Contact
// Employeeの中身
`
{
id: number | string;
name: string;
email: string;
phone; string;
}
`
リテラル型
リテラル型は決まった文字列や数値を|で区切り扱えるデータを制御することができる型。
変数: 許可するデータ1 | 許可するデータ2
let postStatus: 'draft' | 'published' | 'deleted'
postStatus = 'draft' // 許可されたデータなのでOK
postStatus = 'aaaa' //許可されていないデータなのでエラー
リテラル型で指定できるデータは論理型、数値型、文字列型である。
never型
never型は何も値を持たない型。
never型には何も値を代入することはできないが、唯一never型だけ代入できる。
例外や終了しない関数の戻り値がnever型となる。
戻り値がないvoid型との違いはundefineが代入できない点にある。
TypeScriptのテクニック
Optional Chaining
?をプロパティに入れることでプロパティがUndefinedornullとなり、値が返ってこなくても処理が行える。
// nullになりうるプロパティsocialを定義
interface user{
name: string
social?:{
facebook: boolean
twitter: boolean
}
}
// User型のuserを宣言
let user: User
user = {
name:"taro",
social: {
facebook: true
twitter: true
}
}
console.log(user.social?.facebook)
// true
// socialプロパティを定義しないuserを定義
user = {
name: "taro"
}
console.log(user.social?.facebook)
// 定義してないがエラーは起きない
Undefinedやnullのまま処理が進む可能性があるためあんま使わない方がいい。
Non-null Assersion Operator
明示的に型がnullではないことを示すことができる機能。
// userがnullの場合(引数に入れない)コンパイルエラーになるが、!(Non-null Assertion)を使うことでコンパイルエラーを抑制できる。
function processUser(str:string, user?:User){
let str = 'taro'
let s = user!.name
}
実際は型を事前に定義した方がいいためあんまり使わない。
型アサーションで型を用意することが多い。
Non-null Assertionが良くない場合
let myString = "Hello": String | null
// toUpperCaseは全部大文字にするメソッド
console.log(myString!.toUpperCase())
JavaScriptにトランスパイルすると以下のようになる。
let myString = "Hello"
console.log(myString.toUpperCase())
この場合myStringがnullになった時myString.toUpperCase()でエラーが出る。
型ガード
ifやswitch文の条件分岐に型チェックを行った場合、条件分岐のブロック移行は変数の型を絞り込む機能のこと。
変数の値の型を推論してくれる機能。
let x: string | number = "Hello"
// typeofで型ガード
// これによりstringの場合のみの処理を分けることができる
if(typeof x === 'string'){
// 以降xはstringとして扱われる。
console.log(x.toUpperCase())
}
keyofオペレーター
型に対して、その型持つプロパティをUnion型で返す。
オブジェクトにあるキーを使用して何かの関数を処理を行いたい場合に安全に実装できる。
// interfaceの定義
interface User{
name: string
age: number
email: string
}
type UserKey = keyof User
// 'name' | 'age' | 'email'
インデックス型
オブジェクトのプロパティが可変のとき、プロパティの型をまとめて定義できる
// number型のプロパティを設定可能
type SupportVersions = {
[env: number] = boolean
}
// string型のプロパティはエラーが起きる
let versions: SupportVersions = {
102: false
103: false
// Stringだとエラー
"104": true
}
そもそも可変のデータはあまり好まれないのであまり使わない
readonly
型エイリアス、インターフェース、クラスにおけるプロパティにreadonlyを指定すると、変更不可となる。
type User = {
readonly name: string
gender: string
}
let user: User = {
name: "taro"
gender: "male"
}
// nameプロパティの変更はできない
user.name = "jiro"
as constで型アサーションすれば全プロパティにreadonlyが適応される。
unknown型
どのような値にも代入できる型。anyとの違いは任意の関数やプロパティにアクセスできない。
unknown型はそもそも関数やメソッド、プロパティを持たないからアクセスもクソもない。
そのため関数やプロパティにアクセスするときにはtypeofやinstanceofを用いて型安全な状況を作り実行する。
// nuknown型を定義
const x: unknown = 123
const y: unknown = 'Hello'
// メソッドにはアクセスできない
console.log(x.toFixed(1))
// x自体はunknownだが値がnumberだからts側で型推論を行う(型ガード)
if(typeof x === 'number'){
// 以降xをnumber型として扱う
console.log(x.toFixed(1))
}
非同期のAsync/Await
TypeScriptでの非同期処理の書き方は以下の通り。
Async Awaitは簡単にいうと非同期処理を同期処理っぽくするやつ。
参考記事
// 非同期関数の定義
function fetchFromServer(id: string):Promise<{success: boolean}>{
return new Promise(resolve => {
setTimeout(() => {
resolve({success: true})
},100)
})
}
async function asyncFunc(): Promise<string>{
const result = await fetchFromServer('111')
return `The result: ${result.success}`
}
(async ()=> {
const result = await asyncFunc()
console.log(result)
})()
asyncFunc().then(result => console.log(result))
型定義ファイル
@types/ライブラリ名でJavaScriptライブラリに型情報を付与することができる。
$ npm install --save-dev @types/jquery
TypeScriptの開発時設定
tsconfig.json
tsconfig.jsonはコンパイルする際に必要なオプションを記述するためのファイルのこと。
詳しくは以下
Prettier
スペースやインデントの数を指定したり、ダブルクォートをシングルクォートに揃えたりと、コードのフォーマットを指定することができる。
特に複数人で開発するときにこのファイルを共有してコードフォーマットを揃えてコンフリクトを防ぐことができる。
Prettierはコードを整形するためのツール
$ npm install prettier --save-dev
作成できたら.prettierrcというファイルを作成して、そのファイル内にコードフォーマットの設定値を記載する。
{
// コードの末尾にセミコロンを入れるか
"semi":false,
// オブジェクト定義の最後のカンマを無しにするか
"trailingComma":"none",
// 文字列の定義などのクォートにシングルクォートを使用するか
"singleQuote":true,
// 行を開業する際の文字列の指定
"printWidth":80
}
そのほかのオプションについては以下公式ドキュメント参考
Prettierの適応にはpackage.jsonのscriptsの項目に以下を追加
{
"scripts":{
"prettier-format":"prettier --config .prettierrc 'src/**/*.ts' --write"
}
}
以下のコマンドを実行することでTypeScriptのソースコードに対してフォーマットが実行される。
$ npx run prettier-format
ESLint
JavaScriptやTypeScriptのコードを解析し、問題がある箇所を指摘するツール。
Prettierと併用して使うことが多い。
.eslintrc.jsというファイルで設定を記述する。
コンパイルオプション
noImplictAny
noImplictAnyは型定義がない場合コンパイルエラーを出すためのオプション
TypeScriptからJavaScriptにトランスパイルする際型定義がないとanyとして扱われる。anyはエラーの原因なので、トランスパイルする過程で定義されたanyを検出して、エラーを吐かせるようにする方がいい。
strictNullChecks
strictNullChecksは明示的にnullやundefinedを指定しない場合にエラーを吐くオプション
Union型などで、nullやundefinedを明示することでエラーを防げる。
target
targetオプションはどのバージョンのECMAScriptで出力するのかを指定できるオプション
※ECMAScriptはJavaScriptの標準規格のこと。
$ tsc --target es5 sample.ts
このようにすることでsample.tsをECMAScript 5準拠のJavaScriptにトランスパイルすることができる。