りあクト! TypeScriptで始めるつらくないReact開発 第4版【① 言語・環境編】を読んだ記録
りあクト! TypeScriptで始めるつらくないReact開発 第4版【① 言語・環境編】
第1章 こんにちはReact
フロントエンド開発でNode.jsはなぜ必要なのか
┗ 依存関係のバージョン管理
┗ バンドル
┗ コンパイル
┗ テスト
┗ ローカルサーバーの稼働
┗ 静的解析・自動整形
<React.StrictMode>は何か
バージョンが進み非推奨になったAPIや意図しない副作用の発生など潜在的な問題点をみつけ警告してくれるReactのstrictモードを有効にするためのラッパー。
<StrictMode> を使うと、開発中の早い段階でコンポーネントによくあるバグを見つけることができます。
コンポーネントは、不純なレンダリングに起因するバグを見つけるために、余分に再レンダリングします。
コンポーネントは、エフェクトのクリーンアップ漏れによるバグを発見するために、エフェクトを余分に再実行します。
コンポーネントが非推奨APIを使用していないかチェックされます。
JSX
JavaScriptの構文拡張。
Yarn
npmと同様、パッケージ管理システム📦
npmがNode.js登場の翌年2010年リリースされ先行していたが、その後Yarn、pnpmなどが出てくる。
Yarnは2016年にMeta社によってリリースされた。
npmで解決できなかった問題をYarnが解決したことで脚光を浴びた。
yarn.lock
パッケージの各依存関係のどのバージョンがインストールされたのかを記録するファイル。
npmだとpackage-lock.json。
第 2 章 ライトでディープな JavaScript の世界
JavaとJavaScript
当初JavaScriptはMochaという名前でブラウザで動く言語が作られていたが
その後LiveScriptに改名。JavaScriptを開発していたNetscapeの経営陣は言語を普及させるため当時人気急上昇中だったJavaと構文を似せる必要があると判断。そして名前もJavaScriptに改名された。
ECMAScript
Netscape社が開発したJavaScriptは現在Mozilla Foundationが引き継いでいる。
JavaScriptはプロダクトの名前で標準仕様の名前はECMAScriptという。
ECMAScriptはEcma Internationalが定めている標準の仕様。
2015年に公開された第6版から年号付きの仕様書名(ES2015)で呼称することが推奨されている。
それまではES5のようにバージョン付きで呼ばれていた。
JavaScriptのデータ型
静的型付け言語・動的型付け言語
静的型付け言語は変数や関数の引数、戻り値の型がプログラムの実行前に決まってなければならないのに対し、動的型付け言語は実行時の値によって変化するということ。
抽象的な数値の羅列や数値の有限範囲において、コンピューター(CPU)が理解できるネイティブな命令に置き換えるとき、曖昧な数値の羅列であるデータに意味を持たせるのが「型」定義の意義になります。
つまり、抽象的なものであるデータに対して、プログラマーがその型の定義を行い機械語への変換時の指示をインタープリターやコンパイラーに対して予め指定するか、プログラマーが書いたコードを元に、インタープリターやコンパイラーが実行時に型の種類を解釈し、実行するかどうかの違いだけです。
https://qiita.com/toryuneko/items/c023031b61886cae2a99
JavaScript のデータ型とデータ構造
JavaScriptは動的型付け言語であらゆる型の値が再代入可能。
処理に不一致の型が含まれている場合暗黙の型変換を行う。
=> 意図しない挙動を生む可能性がある
プリミティブ型とオブジェクト型
プリミティブ型
オブジェクトではない、インスタンスメソッドを持たない値の型。
イミュータブルな特性を持つ。
- Boolean型
- Number型
- BigInt型
- String型
- Symbol型
- Null型
- Undefiend型
なぜ、プリミティブ型からメソッドを呼び出せるのか?
"hogehoge".split()
null、undefiendを除くプリミティブ値はそれらを包むラッパーオブジェクトが存在しさまざまなメソッドを提供している。
プリミティブ値にアクセスする際、対応するラッパーオブジェクトに暗黙的に変換する仕組みがある。
new String("hogehoge").split()
関数宣言と関数式
文(Statement)...何らかの処理を命令するもの
式(Expression)...評価された後に値として存在するもの
変数に代入できるのが式で代入できないものが文。
第一級オブジェクトとは
オブジェクト型と同様に変数へ代入したり、配列の要素、オブジェクトのプロパティ値(=メソッド)にしたり、他の関数に引数として渡したり、別の関数の戻り値として扱える。関数を第一級オブジェクトとして扱える性質を第一級関数ともいう。
オブジェクト指向(OOP)
オブジェクトの生成方法にはクラスベースとプロトタイプベースがある。
クラスベース
クラスを使用し、new演算子などを使用してインスタンスを作成する。
オブジェクトの元なるのがクラスのためクラスベースという。
プロトタイプベース
オブジェクトをもとにしてオブジェクトを生成する。継承元となったオブジェクトをプロトタイプと呼ぶ。
継承元を辿っていくと最終的にはからのオブジェクトを得て、nullに到達する。
JavaScriptはプロトタイプベース。
クラス構文も使用できるがそれはクラスベースになったわけではなくプロトタイプベースの上でクラスのように書けるだけ。
2-8. JavaScript の鬼門、this を理解する
JavaScriptにおけるthisとは、strictモードか否かでも挙動が異なり、
strictモードでない場合は殆どの場合、実行コンテキストであるオブジェクトへの参照である。
function f1() {
return this;
}
// ブラウザー上で
f1() === window; // true
// Node 上で
f1() === global; // true
引用元: https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/this#関数コンテキスト
strictモードの場合、thisに値が設定されていないとundefinedになる。
function f2() {
"use strict"; // strict モードにする
return this;
}
f2() === undefined; // true
引用元: https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/this#関数コンテキスト
関数呼び出し時にcall()
、bind()
を使用することで任意のthisを設定することもできる
アロー関数ではthisを囲むコンテキストのthisの値が設定される
var globalObject = this;
var foo = () => this;
console.log(foo() === globalObject); // true
2-9. モジュールを読み込む
JavaScriptは1995年の誕生から長い間モジュールシステムというものがなかった。
※モジュールシステムとは複数のパッケージやリソースを1つにまとめる仕組み
ファイルを分割する際は、<script>タグを使用していた。サードパーティライブラリを使用する際は依存関係に気をつけながら順に読み込む必要があった。
2009年にNode.jsが誕生しサーバーサイド言語としての体制を整えるためCommonJSという標準API仕様を定めるプロジェクトが発足。
CommonJSが提供したモジュールシステムによってさまざまなパッケージが公開され始めた。
それらをブラウザでも使いたいという欲求が高まってきたところBrowserifyが2011年に登場。
当初のBrowserifyはNodejsのコアモジュールやnpmパッケージをブラウザ上で使うためのツールだった。
次第にモジュール間の依存関係を解決し、ファイルを1つにまとめるバンドル機能(モジュールバンドラ)も備える様になった。
しかし、フロントエンド開発が大規模になるにつれCommonJSのモジュールシステムでは不都合が多く見えてきた。
- モジュールの読み込みが同期的のため、読み込みに時間がかかる
- reuquire/exporsがどこでも呼び出し可能なので静的解析が困難でファイルの最適化が難しい
これを解決するライブラリも登場したが、普及にまでは至らず...
2015年にECMAScriptの標準機能としてES Modulesが誕生。
第 3 章 関数型プログラミングでいこう
関数型プログラミングの関数はプログラミングの文脈ではなく数学的な意味合いが強く、
同じ入力に対して同じ作用をし、同じ出力が保証されている参照透過性を持つものである。
関数型プログラミングとは参照透過性のある関数を組み合わせて問題にアプローチしていく宣言型のプログラミングスタイルのこと。
パラダイムの整理
命令型プログラミング
┗ 最終的な出力を得るために状態を変化させる命令文によって記述されるプログラミングスタイルのこと。
手続き型プログラミングは命令型プログラミングに分類される。
グローバル変数としてまとめたデータを複数の手続き(命令文の組み合わせをまとめた感づうなど)を得て最終結果を得るプログラミングスタイル。
宣言型プログラミング
出力のあるべき姿、状態を宣言するプログラミングスタイル。
オブジェクト指向プログラミング
命令型や宣言型という部類からは独立している存在。
高階関数(Higher Order Function)
引数に関数を渡したり、戻り値として関数を返したりする関数のこと。
Reactでも高階関数を応用が使われる場面があり、コンポーネントを引数にとりコンポーネントを返すコンポーネントを高階コンポーネント(Heigher Order Component)という
カリー化
関数の部分適用
クロージャ
= 関数閉包
メモリのライフサイクル
- 必要なメモリを割り当てる
- 割り当てられたメモリを使用する
- 不要になったメモリを解放する
JavaScriptでは自動的に3. 不要になったメモリを解放するを行う機能が備わっている。
= ガベレージコレクタ(GC)
レキシカルスコープ
定義した時にスコープが決定されること
第 4 章 TypeScript で型をご安全に
4-2-3. Enum 型とリテラル型
Enum型
enumeratetの略。列挙する、数え上げるという意味をもち列挙型と呼ばれる。
列挙型は、JavaScriptの型レベルの拡張ではない、TypeScriptの数少ない機能のひとつである。
Enumを使うことで、開発者は一連の名前付き定数を定義することができる。列挙型を使用することで、意図を文書化したり、個別のケースを作成したりすることが容易になります。TypeScriptは数値ベースと文字列ベースの両方の列挙型を提供する。
https://www.typescriptlang.org/docs/handbook/enums.html
リテラル型
プリミティブ型をさらに細分化した型。
例)
let dog: "dog" = "dog";
dog = "cate"; // Type '"cate"' is not assignable to type '"dog"'.
ユニオン型と合わせて使用することで列挙型のように扱える
let animal : "cat" | "dog" = "cat";
animal = "panda"; //Type '"panda"' is not assignable to type '"cat" | "dog"'.
タプル型
const hogehoge : [number, string, boolean] = [1 , "foo", true];
ジェネリクス(Generics)
型引数(Type Parameter)を用いてデータ構造を表現する。
型エイリアスとインターフェース
- interface
型の宣言。宣言のマージが可能。
interface Food {
name: string
price: number
}
interface Food {
deadline: string
}
- type
型の参照のために別名をつけただけの型エイリアス。
type Food = {
name: string
price: number
}
ユニオン型とインターセクション型
ユニオン型
演算子( | )で複数の型を並べ、いずれかの型が適用される複合的な型を作れる。
let id: number | string = 1;
id = "hogehoge";
インターセクション型
演算子(&)で複数の型を並べ、複数の型を適用し一つに結合できる。
オブジェクトの場合、キーがおこなる場合は追加され
同じキーの場合はマージされる。
type T = { foo: number };
type U = { bar: string } ;
type V = { ba
type TnU = T & U; // {foo: number , bar: string}
4-5. さらに高度な型表現
typeof
変数から型を抽出してくれる
const array = ["りんご", "バナナ", "パイナップル"];
type Fruit = typeof array // string[]
const fruit :Fruit = [1,2,3]
// Type 'number' is not assignable to type 'string
in 演算子
列挙された型の中から各要素の型の値を抜き出してマップ型(Mapped Type)を作る
type Fruit = "apple" | "banana" | "orange"
type FruitMap = {[Key in Fruit] : number}
const obj:FruitMap = {
"apple": 100,
"banana": 80,
"orange": 30
}
keyof
オブジェクトの型からkeyを抜き出す
const obj = {
fafa: 100,
hogehoge: 200,
aaaa: 300
}
type ObjKey = keyof typeof obj // "fafa" | "hogehoge" | "aaaa
- valueを抜き出す方法
const obj = {
fafa: 100 as const,
hogehoge: 200 as const,
aaaa: 300 as const
}
type ObjKey = keyof typeof obj // "fafa" | "hogehoge" | "aaaa
type ObjValue = typeof obj[ObjKey] // 100 | 200 | 300
- 配列から型を作る
const fruit = ["apple", "banana", "orange"] as const;
type Fruit = typeof fruit[number] // "apple" | "banana" | "orange"
4-5-2. 条件付き型とテンプレートリテラル型
const mergeObj = <T , U extends T>(obj1: T , obj2:U): T & U =>({
...obj1,
...obj2
})
mergeObj({a: 1}, {a: 4,b:8})
mergeObj({a: 1}, {hogehoge: 4})
// Argument of type '{ hogehoge: number; }' is not assignable to parameter of type '{ a: number; }'.
// Object literal may only specify known properties, and 'hogehoge' does not exist in type '{ a: number; }
条件付き型
interface User { id: unknown }
type NewUser = User & { id: string };
type OldUser = User & { id: number };
type IdOf<T> = T extends User ? T['id'] : never;
type NewUserId = IdOf<NewUser>; // string
type OldUserId = IdOf<OldUser>; // number
引用: https://booth.pm/ja/items/2368045
infer
条件付き型においてマッチング中に取得した型を利用できる
type Each<T> = T extends Array<infer U> ? U :T;
const num = 20
const array = [2,6,9]
type ArrayEach = Each<typeof array> // number
type NumberEach = Each<typeof num> // number
4-5-3. 組み込みユーティリティ型
Partial<T>
Tのプロパティをすべてオプショナルにする
type Obj = {
fafa: 10,
hogehoge: 20
}
type PartialType = Partial<Obj>
// {
// fafa?: 10 | undefined;
// hogehoge?: 20 | undefined;
// }
Required<T>
Tのプロパティをすべて必須にする
type Obj = {
fafa: 10,
hogehoge: 20
}
type RequiredType = Required<Obj>
Readonly<T>
Tのプロパティをすべて読み取り専用にする
type Obj = {
fafa: 10,
hogehoge: 20
}
type ReadonlyType = Readonly<Obj>
const obj :Obj = {fafa: 10, hogehoge: 20}
const fafa = obj.fafa;
obj.hogehoge = 10 //Type '10' is not assignable to type '20'
Pick<T, K>
T から K が指定するキーのプロパティだけを抽出する
type Obj = {
fafa: 10,
hogehoge: 20
}
type PickType = Pick<Obj, "fafa">
// { fafa: 10;}
Omit<T, K>
T から K が指定するキーのプロパティを省く
type Obj = {
fafa: 10,
hogehoge: 20
}
type OmitType = Omit<Obj, "fafa">
// {hogehoge: 20;}
Extract<T, U>
T から U の要素だけを抽出する
type Permission = 'r' | 'w' | 'x';
type ExtractType = Extract<Permission, 'r' | 'w'>;
// "r" | "w"
Exclude<T, U>
T から U の要素を省く
type Permission = 'r' | 'w' | 'x';
type ExcludeType = Exclude<Permission, 'r' | 'w'>;
// "x"
NonNullable<T>
T から null と undefined を省く
type T1 = NonNullable<string | number | undefined>;
type T2 = NonNullable<number[] | null | undefined>;
const str: T1 = undefined; // Type 'undefined' is not assignable to type 'T1'
const arr: T2 = null; // Type 'null' is not assignable to type 'number[]'.
引用: https://booth.pm/ja/items/2368045
Record<K,T>
K の要素をキーとしプロパティ値の型を T としたオブジェクトの型を作成する
type RecordType = Record<number, {age: number, name: string}>
const user:RecordType= {
1: {age: 20, name: "taro"}
}
Parameters<T>
T の引数の型を抽出し、タプル型で返す
const f1 = (a: number, b: string) => { console.log(a, b); };
const f2 = () => ({ x: 'hello', y: true });
type P1 = Parameters<typeof f1>; // [number, string]
type P2 = Parameters<typeof f2>; // []
引用: https://booth.pm/ja/items/2368045
ReturnType<T>
T の戻り値の型を返す
const f1 = (a: number, b: string) => { console.log(a, b); };
const f2 = () => ({ x: 'hello', y: true });
type R1 = ReturnType<typeof f1>; // void
type R2 = ReturnType<typeof f2>; // { x: string; y: boolean }
引用: https://booth.pm/ja/items/2368045
Uppercase<T>
T の各要素の文字列をすべて大文字にする
type Fruit = "Apple" | "Banana" | "Orange"
type UpperCaseType = Uppercase<Fruit> // "APPLE" | "BANANA" | "ORANGE"
Lowercase<T>
T の各要素の文字列をすべて小文字にする
type Fruit = "Apple" | "Banana" | "Orange"
type LowerCaseType = Lowercase<Fruit> // "apple" | "banana" | "orange"
Capitalize<T>
T の各要素の文字列の頭を大文字にする
type Fruit = "Apple" | "Banana" | "Orange"
type CapitalizeType = Capitalize<Fruit> // "Apple" | "Banana" | "Orange"
Uncapitalize<T>
T の各要素の文字列の頭を小文字にする
type Fruit = "Apple" | "Banana" | "Orange"
type UncapitalizeType = Uncapitalize<Fruit> //"apple" | "banana" | "orange"
4-7. モジュールと型定義
TypeScriptはモジュール解決の際に独自のルールで検索するため、importするファイルの拡張子を省略できる(拡張子を含むとエラーになる)
An import path can only end with a '.tsx' extension when 'allowImportingTsExtensions' is enabled.
TypeScriptでは変数宣言空間と型宣言空間の2つの宣言空間が存在していて、名前の管理が別々になっていることで変数名や関数名と同一の名前を持つことが可能となる。(= Combinationコンビネーション)
4-7-2. JavaScript モジュールを TypeScript から読み込む
npmで提供されているパッケージの多くはTypeScriptで書かれているものでもJavaScriptにコンパイルした状態で配布されている場合が多い。型は宣言ファイルという型情報を定義したファイルに記載され配布される。
その理由として、相互運用がしやすい、.tsファイルをコンパイラが見つけ毎回プロジェクトコードと一緒にコンパイルしてしまう危険性もあるから。
declare
TypeScriptのコンパイラに変数や関数の型を教え宣言空間に定義するための構文のこと。
既存のJavaScriptモジュールに型情報を付加する形の宣言をアンビエント宣言という。
4-7-3. モジュールの型はどのように解決されるか
インポート元のモジュールを相対パスで記述しないのを非相対インポートという。
npmパッケージをインストールした際はこの書き方になる。
import { uniq } from 'lodash' の
- 公式が型定義ファイルを提供している
┗ パッケージに内包されている - 公式が型定義ファイルを提供していないが、第三者により Definitely Typed から提供されていてパッケージ名の頭に@types/がついている。
┗ ベットインストールが必要 - 公式が型定義ファイルを提供しておらず、自作や拾ってきた型定義ファイルを適用する
TSがアイコンが表示されているものは、型定義のファイルが同梱していることを示している。
DTアイコンが表示されているものは、Definitely Typedから型ファイルが提供されていることを示している。
参考記事