りあクトを読むんだ(TypeScript で始めるつらくない React 開発編)
日本は中国と並んで、React より Vue.js のほうが勢いがある世界でもめずらしい国。
日本の Web 開発コミュニティが今なおサーバサイドに偏重しているため。
日本語のドキュメントもVueで提供されており、Railsなどのフレームワークに馴染んだ開発者が好みがち。
Node.jsのありがたみ
スクリプトでReactを読み込んだだけだとそれなりの規模のアプリケーションを作る場合
- サードパーティのライブラリをいくつも読み込むでくる必要がある
- 相互に特定のバージョンで依存しあってたりする
- ライブラリ間の依存関係を手で解決して、 ライブラリに新しいバージョンが出てアップデートするときにも最初からやり直す必要がある
Node.jsが必要な理由
- パフォーマンス最適化のために JavaScript や CSS ファイルを少数のファイルにまとめる
- ブラウザ実行時にpolyfill12 するのではなく 最初からコンパイルしておく
- 開発中、ブラウザにローカルファイルを直接読み込ませず、ローカル環境で開発用の HTTP サーバを起動してそれ経由でアプリケーションを稼働
- テストツールを用いてユニットテストや E2E テストを記述する
- ソフトウェアテストやコードの構文解析をローカル環境で実行する
DX・・・Developer Experience(開発者体験)」の略。そのシステムや環境、文化などが開発者に与える体験。
Yarn・・・Facebook 製の改良版 npm コマンド。より高速だったり、サブコマンドのタイピング数が少なかったり色々と使い勝手がいい。npmよりも編集されるファイル数が少ない(package.jsonが中心)のでyarnがオススメ。
$ node
対話環境でJSが実行できる。Rubyでいう irb
.load
コマンドで指定したファイルの内容を現在のセッションに読み込んでくれる
create-react-app
コマンド実行で次のパッケージのインストール
- react
- React 本体のパッケージ
- react-dom
- DOM を抽象化して React から操作できるように するレンダラーのパッケージ
- react-scripts
- CRA の魔法を一手に引き受けてるパッケージ
以上のインストールで以下が可能になる
- Babel
- 新しい仕様の JavaScript や JSX、TypeScript のコードを古いブラウザでも実行可能なレガシーな JavaScript にコンパイルする
- webpack
- コンパイラと連携しつつ大量のソースコードファイルをひとつにまとめ、種々の最適化を施す
- webpack-dev-server
- ファイルの変更を自動検知して再ビルド、リロードしてくれる開発用の HTTP サーバでのアプリ の稼働
- Jest
- オールインワンのテスティングフレームワークによるユニットテストの記述
- テスト記述の存在および、ファイルの差分を検知して自動で必要最小限のテストを走らせる
- dotenv
- .env(.*) ファイルによる環境ごとに異なった環境変数の設定
npxコマンド・・・該当パッケージがマシンにインストールされていればそれを実行。なければ最新版をダウンロードしてきてを実行する。ダウンロード実行の場合は、実行後、そのコマンドのパッケージをきれい に削除してくれる。
パスカルケース・・・「PascalCase」のように複合語をひとくくりにして、各単語の最初を大文字で書き表す記法。
yarn コマンドたち
"scripts": {
"start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject"
},
- start
- ローカルで開発用の HTTP サーバを起動 してそこでアプリを稼働させる
- build
- 本番環境にデプロイするためのファイルを作成する
- プロジェクトルートに build/ ディレクトリが作られ、その中に一連のビルド済みファイルが展開される
- test
- ソースディレクトリからテストファイルを抽出してテストを走らせる
- 起動しておくと、テストファイルの差分を検知して、変更のあったテストだけが実行される
- eject
- 隠蔽されてた 50 以上のパッケージが package.json の "dependencies" エントリに現れる
- config/ ディレクトリが作られ、webpack などの設定ファイルが置かれる
エッジでディープな JavaScript の世界
JSの評価ポイント
- 第一級関数とクロージャをサポート
- プロパティを随時追加できる柔軟なオブジェクト
- 表現力の高いリテラル記法
var
の問題点
- 再宣言および再代入が可能
- 変数の参照が巻き上げられる
- スコープ単位が関数
JavaScript のプリミティブ型
- Boolean 型
- true および false の 2 つの真偽値を扱うデータ型
- Number 型
- 数値を扱うためのデータ型。他の多くの言語と異なり、整数も小数も同じ Number 型にな る。
- BigInt 型
- Number 型では扱いきれない大きな数値を扱うためのデータ型。ES2020 で追加された。 Number 型と互換性がなく、相互に代入や計算、等値比較などは行えない。
- String 型
- 文字列(テキストを表す連続した文字)を扱うためのデータ型。
- Symbol 型
- 「シンボル値」という固有の識別子を表現する値。
- Null 型
- プリミティブ値 null は何のデータも存在しない状態を明示的に表す。
- Undefined 型
- プリミティブ値 undefined は宣言のみが行われた変数や、オブジェクト内の存在しないプ ロパティへのアクセスに割り当てられる。他の多くの言語と異なり、null と明確に区別される。
Jsの『オブジェクト』
- 狭義のオブジェクト
- JSON 形式の表現。(JavaScript Object Notationだから)
- 広義のオブジェクト
- プリミティブ値以外のすべてのものを包括して指す
- すべて Object という標準組み込みオブジェクトを最終的な継承元に持つ
シンタックスシュガー:和製英語!!!正しくは Syntactical(Syntactic)Sugar
スプレッド構文
スプレッド構文・・・配列やオブジェクトの名前の前に ...
をつければ中身が展開される
const baz = 65536;
const obj = { baz };
console.log(obj); //{baz:65536}
分割代入(Destructuring Assignment)
const obj = { name: 'Kanae', age: 24 };
const { name, age } = obj;
console.log(name, age); // Kanae 24
const user = { id: 1,
name: 'Patty Rabbit', email: 'patty@maple.town', age: 8,
};
const { id, ...userWithoutId } = user;
console.log(id, userWithoutId);
// 1 { name: 'Patty Rabbit', email: 'patty@maple.town', age: 8 }
??
:nullish coalescing
const foo = null ?? 'default string';
console.log(foo);
// expected output: "default string"
const baz = 0 ?? 42;
console.log(baz);
// expected output: 0
this を理解する
this: 関数が実行されるコンテキストであるオブジェクトへの 参照が格納されている『暗黙の引数』
関数型プログラミングでいこう
プログラミングのパラダイム
- 命令型プログラミング(Imperative Programming)
- オブジェクト指向言語などがこちら
- 手続き型プログラミング(Procedural Programming)
- オブジェクト指向プログラミング(Object-Oriented Programming)
- 宣言型プログラミング (Declarative Programming)
- SQLなど
- 「どんなデータが欲しいか」を宣言することで出力
サブルーチン・・・コンピュータプログラムの中で特定の機能や処理をひとまとまりの集合として定義し、他の箇所から呼び出して実行できるようにしたもの。
JSの残念ポイント・・・破壊的メソッドが見分けがつきにくく、記憶するしかない。slice()
を使って破壊を防ぐ。
Ruby のブロック構文・・・第一級関数をサポートしない Rubyで無名関数と同等の機能を表現するために生み出された苦肉の仕様。Rubyでもブロックを Proc オブジェクトにしたりラムダ式を用いるなどすれば、それを他の関数に引数として渡したり、戻り値として設定することは可能。ただそれは「第一級関数に近い機能を実現するもの」であって、第一級関数そのものではない
関数型プログラミングのパラダイムで行われること
i. 名前を持たないその場限りの関数(無名関数)を定義できる
ii. 変数に関数を代入できる
iii. 関数の引数として関数を渡したり、戻り値として関数を返すことができる(高階関数) iv. 関数に特定の引数を固定した新しい関数を作ることができる(部分適用)
v. 複数の高階関数を合成してひとつの関数にできる(関数合成)
高階関数
引数に関数を取ったり、戻り値として関数を返したりする関数
const greeter = (target) => { const sayHello = () => {
console.log(`Hi, ${target}!`); };
return sayHello; };
const greet = greeter('Step Jun');
greet(); //Hi,StepJun!
return sayHello() と記述してしまうとその場で関数を実行して、undefined を返してしまう
雑学・・・HaskellとかPascalってのは昔の高名な学者の名前
Closure
『関数閉包』、関数を関数で閉じて包むってこと
正確には、関数と『その関数が作られた環境』という 2 つのものが一体となった 特殊なオブジェクトのことを指す。
JavaScript での非同期処理
Promise・・・ ES2015 から導入された JavaScript の標準組み込みオブジェクトで、非同期処理の最終的な処理結果の値を文字通り『約束』するもの
TypeScript で型をご安全に
ts-node・・・ローカルマシンでの実行環境の代表格。https://github.com/TypeStrong/ts-node
TypeScript・・・大規模なアプリケーション開発のために作られた、JavaScript に静的型付けの型システムとクラスベースのオブジェクト指向を加えた完全上位互換の言語。
TSのenum
> enum Pet { Cat, Dog, Rabbit }
> console.log(Pet.Cat, Pet.Dog, Pet.Rabbit);
012
リテラル型
任意の文字列以外を許さない型
> let Mary: 'Cat' | 'Dog' | 'Rabbit' = 'Cat'; > Mary = 'Rabbit';
> Mary = 'Parrot';
[eval].ts:5:1 - error TS2322: Type '"Parrot"' is not assignable to type '"Cat" | "Dog" | "Rabbit"'.
タプル型
個々の要素の型と、その順番や要素数に制約を設けられる特殊な配列の型
const charAttrs: [number, string, boolean] = [1, 'patty', true];
Any
いかなる型の値でも受けつける
Unknown
any の型 安全版
never
何者も代入できない型。case 文の漏れを未然にチェックできたりする
const greet = (friend: 'Serval' | 'Caracal' | 'Cheetah') => {
switch (friend) {
case 'Serval':
return `Hello, ${friend}!`;
case 'Caracal':
return `Hi, ${friend}!`;
case 'Cheetah':
return `Hiya, ${friend}!`;
default: {
const check: never = friend;
}
}
};
関数とクラスの型
TypeScript ではコンパイラオプションに noImplicitAnyが指定されてないと、引数の型定義が なくても暗黙の内に any 型があてがわれてコンパイルが通ってしまう。
型引数(Type Parameter)
> const toArray = <T>(arg1: T, arg2: T): T[] => [arg1, arg2];
> toArray(8, 3);
[8,3]
> toArray('foo', 'bar'); [ 'foo', 'bar' ]
> toArray(5, 'bar');
[eval].ts:4:12 - error TS2345: Argument of type '"bar"' is not assignable to parameter of type
'number'.
データの型に束縛されないよう型を抽象化してコードの再利用性を向上させつつ、 静的型付け言語の持つ型安全性を維持するプログラミング手法を『ジェネリックプログラミング
(Generic Programming)』と呼ぶ。
型引数を用いて表現するデータ構造のことを『ジェネリク ス(Generics)』
TypeScript でのクラスの扱い
- public
- 自クラス、子クラス、インスタンスすべてからアクセス可能。デフォルトではすべ てのメンバーがこの public になる
- protected
- 自クラスおよび子クラスからアクセス可能。インスタンスからはアクセス不可
- private
- 自クラスからのみアクセス可能。子クラスおよびインスタンスからはアクセス不可
継承よりも合成
// XXX継承
class Square extends Rectangle {
readonly name = 'square'; side: number;
constructor(side: number) {
super(side, side);
}
}
// ◯◯◯合成
class Square {
readonly name = 'square'; side: number;
constructor(side: number) {
this.side = side;
}
getArea = (): number => new Rectangle(this.side, this.side).getArea();
}
型の名前と型合成
&
はオブ ジェクト型の合成に使われる
$ ts-node
> type Some = number & string;
> let id: Some;
>id=100;
[eval].ts:3:1 - error TS2322: Type '100' is not assignable to type 'never'.
> typeof id
マップ型
const permissions = { r: 0b100,
w: 0b010,
x: 0b001,
};
typePermsChar=keyoftypeofpermissions; //'r'|'w'|'x'
const readable: PermsChar = 'r';
const writable: PermsChar = 'z'; // compile error!
typeof と合わせると、既存のオブジェクトからキーの型を抽出できる
条件付き型とテンプレートリテラル型
条件付き型(Conditional Types)2
T extends U ? X : Y
type User = { id: unknown };
type NewUser = User & { id: string };
type OldUser = User & { id: number };
type Book = { isbn: string };
type IdOf<T> = T extends User ? T['id'] : never;
typeNewUserId=IdOf<NewUser>; //string
typeOldUserId=IdOf<OldUser>; //number
type BookId = IdOf<Book>; // never
infer・・・関数の型から任意の引数の型を抽出
type Flatten<T> = T extends Array<infer U> ? U : T;
const num = 5;
const arr = [3, 6, 9];
typeA=Flatten<typeofarr>; //number
typeN=Flatten<typeofnum>; //number
テンプレートリテラル型(Template Literal Types)
type DateFormat = `${number}-${number}-${number}`;
const date1: DateFormat = '2020-12-05';
constdate2:DateFormat='Dec.5,2020'; //compileerror!
constアサーション「as const」
オブジェクトリテラルの末尾にas constを記述すればプロパティがreadonlyでリテラルタイプで指定した物と同等の扱いになる。
const pikachu = {
name: "pikachu",
no: 25,
genre: "mouse pokémon",
height: 0.4,
weight: 6.0,
} as const;
pikachu.name = "raichu";
// Cannot assign to 'name' because it is a read-only property.
様々なユーティリティ型
組み込みユーティリティ型
- Partial<T> ...... T のプロパティをすべて省略可能にする
- Required<T> ...... T のプロパティをすべて必須にする
- Readonly<T> ...... T のプロパティをすべて読み取り専用にする
オブジェクトの型からプロパティを取捨選 択するユーティリティ型
- Pick<T,K> ...... T から K が指定するキーのプロパティだけを抽出する
- Omit<T,K> ...... T から K が指定するキーのプロパティを省く
type Todo = {
title: string;
description: string;
isDone: boolean;
};
type PickedTodo = Pick<Todo, 'title' | 'isDone'>;
type OmittedTodo = Omit<Todo, 'description'>;
列挙的な型を加工するユーティリティ型
- Extract<T,U> ...... T から U の要素だけを抽出する
- Exclude<T,U> ...... T から U の要素を省く
type Permission = 'r' | 'w' | 'x';
type RW1 = Extract<Permission, 'r' | 'w'>;
type RW2 = Exclude<Permission, 'x'>;
null 非許容にするためのユーティリティ型
- NonNullable<T> ...... T から null と undefined を省く
type T1 = NonNullable<string | number | undefined>;
type T2 = NonNullable<number[] | null | undefined>;
conststr:T1=undefined; //compileerror!
const arr: T2 = null; // compile error!
関数を扱うユーティリティ型
- Parameters<T> ...... T の引数の型を抽出し、タプル型で返す
- ReturnType<T> ...... T の戻り値の型を返す
const f1 = (a: number, b: string) => { console.log(a, b); };
const f2 = () => ({ x: 'hello', y: true });
typeP1=Parameters<typeoff1>; //[number,string]
typeP2=Parameters<typeoff2>; //[]
typeR1=ReturnType<typeoff1>; //void
typeR2=ReturnType<typeoff2>; //{x:string;y:boolean}
文字列リテラル型と組み合わせて使える型
- Uppercase<T> ...... T の各要素の文字列をすべて大文字にする
- Lowercase<T> ...... T の各要素の文字列をすべて小文字にする
- Capitalize<T> ...... T の各要素の文字列の頭を大文字にする
- Uncapitalize<T> ...... T の各要素の文字列の頭を小文字にする
type Company = 'Apple' | 'IBM' | 'GitHub';
type C1 = Lowercase<Company>; // 'apple' | 'ibm' | 'github'
type C2 = Uppercase<Company>; // 'APPLE' | 'IBM' | 'GITHUB'
typeC3=Uncapitalize<Company>; //'apple'|'iBM'|'gitHub'
type C4 = Capitalize<C3>; // 'Apple' | 'IBM' | 'GitHub';
関数のオーバーロード
void・・・TypeScriptで戻り値がない関数の戻り値を型注釈するにはvoid型を用いる。
型アサーシ ョンの抜け道・・・(someValue as unknown) as SomeType
のように一旦unkown型を挟む形で二重アサーションするとコンパイルは通る
型ガード(Type Guards)
const foo: unknown = '1,2,3,4';
if (typeof foo === 'string') {
console.log(foo.split(',')); //compile OK!
}
console.log(foo.split(',')); //compileerror!