React を学ぶ
React を学ぶ
- SharePoint Framework の開発をキッカケに React を触り始めたが、しばらく触ってなかったので React の知識をアップデートするために勉強を始めました
- 体系的に React を学ぶために、とある本を購入して React を勉強します。
- Web アプリケーション開発は UNIX 環境でやるのが常識、という記載があって、Windows 環境で生きてきた身としては「へ~、そうなんだ!」と驚きを隠せませんでした。
- Windows でも WSL2 を使うことで UNIX 環境が構築できるので、手順に沿って Ubuntu をインストールしました。
環境構築編
- インストールする Node.js のバージョンマネージャーを nodenv で管理する。
- nodenv を anyenv 経由でインストールするために、anyenv を初めにインストールする。
- anyenv をインストールしたが、なぜか anyenv のパスが通ってなくてコマンドが実行できない。
- 以下のブログを参照に .bashrc にパスを設定するコマンドを実行する。
-
exec $SHELL -l
を実行するとANYENV_DEFINITION_ROOT(/home/shibatea/.config/anyenv/anyenv-install) doesn't exist. You can initialize it by: > anyenv install --init
という警告が表示されたので、anyenv install --init
を実行する。
プロジェクト作成編
-
create-react-app
コマンドでプロジェクトを作成する。 - 以下の3つのパッケージがインストールされる。
- react
- react-dom
- react-scripts
- 以下のコマンドで TypeScript をテンプレートとしたプロジェクトを作成する。
npx create-react-app hello-work --template=typescript
-
yarn start
を実行すると、デフォルトブラウザが立ち上がって、localhost のポート3000番のページが表示される。
npx コマンド
- npm パッケージで提供されているコマンドを実行するためのもので、最新版をダウンロード&実行した後、ダウンロードしたそれらのコマンドのパッケージを削除してくれるので、1度限りの実行には便利。
- プロジェクトを作成する
create-react-app
コマンドは、頻繁に実行されるわけではなく、都度最新コマンドを叩ければいいので、npx
コマンド経由で実行するのが推奨されている。(グローバルにインストールしてしまうと、コマンドのアップデートなど保守作業が必要になる)
おまけ
WLS2 (Ubuntu) のホームディレクトリは Windows で言うと、どこのフォルダかというと、以下の場所でした。
C:\Users\<User>\AppData\Local\Packages\CanonicalGroupLimited.UbuntuonWindows_79rhkp1fndgsc\LocalState\rootfs\home
yarn コマンド
-
yarn upgrade-interactive [--latest]
-
package.json
に記述してある各パッケージの更新情報をチェック、対話形式で一括更新 -
--latest
オプションは、強制的に安定版バージョンに一括更新 - 更新後の動作を保証するものではないため、バージョン更新後はテスト必須!!
-
npx create-react-app
コマンドは、それなりにパワーのあるメインPCでも数分掛かった…。
package.json
について
プロジェクトのルートフォルダに配置されている json ファイル
dependencies
プロパティ
yarn install
コマンド実行時にインストールされるパッケージ郡
scripts
プロパティ
このプロパティに記述されたコマンドを yarn xxxx
で実行できる。
ちなみに、npm コマンドを使う場合は npm run xxxx
となって、記述が少し多め。
スコープ
JavaScript は let
と const
で宣言された識別子は、ブロックスコープを持つ。
let x = 1;
{
let x = 2;
}
console.log(x); // 1 が出力されます
let
で宣言されているわけじゃなく、再代入であればそれが反映される。
let x = 1;
{
x = 2;
}
console.log(x); // 2 が出力されます
基本的には const
を使って、どうしても再代入が必要な場合だけ let
を使うようにするのが良し。
気になるワードはこのスクラップに書き込んでいく
- プリミティブ
- プリミティブとかオブジェクトというワードを目にするけど、プリミティブ is 何?
- 英単語の意味としては、原始的な(もの)、根源的な(もの)、初期の、太古の、未発達な、素朴な、粗野な、原形
- ITの分野では、複雑な構造を形つくる際の構成要素。単純な要素、といった意味。
- JavaScript のプリミティブ型といえば
- Boolean、String、Number、BigInt、Symbol、Null、Undefined
- プリミティブ型はインスタンスメソッドを持たない。
- プリミティブ型の値は暗黙的にラッパーオブジェクトに変換されているので、実際のコードではプリミティブ型の値からメソッドを生やすことができる。
- なるほど~、忘れがちだけど重要。。
- プリミティブ型の値は暗黙的にラッパーオブジェクトに変換されているので、実際のコードではプリミティブ型の値からメソッドを生やすことができる。
- NaN
- Number型だけど、数値ではない
- Not a Number
-
リテラル
- ソースコードの中で型宣言された変数の値を表現したもの
- Boolean 型で言えば
true
やfalse
のこと - Number 型で言えば
88
や-2
のこと - 配列リテラル
-
[]
や[1,2,3]
-
- オブジェクトリテラル
-
{}
や{key:value}
-
- 正規表現リテラル
-
/d(b+)d/g
という形で表現されたもの(/pattern/frags
)
-
- 関数リテラル
const add = function (n, m) { return n + m; };
- 文と式
- 関数宣言(文)と関数式
- 関数宣言
function hoge(n) {}
- 関数式
const hoge = function(n) {};
- 関数式の書き方が推奨
- 関数式は
Function
オブジェクトのインスタンス
- アロー関数式
const add = (n) => { return n +1; };
-
const add = n => n + 1;
(省略版)
- 関数宣言
- 詳しくはこちらの記事を参照
その言語の関数がその他の変数と同様に扱われることを表します。例えば、こうした言語では、関数を他の関数への引数として渡したり、他の関数から返却したり、変数の値として代入したりすることができます。
- デフォルト引数
const hoge = (n, m = 2) => n + m;
hoge(2,3) // 5
hoge(2) // 4
- レストパラメータ
...
const names = (first, second, ...other) => {
console.log(first);
console.log(second);
console.log(other);
};
names('taro', 'jiro', 'saburo', 'siro');
// taro
// jiro
// [ 'saburo', 'siro' ]
- クラスベースのオブジェクト指向
- クラスとは実体を持たない抽象概念である。
- クラスを具象化したものがオブジェクトである。
- 継承関係を考えたときに、あるオブジェクトの継承元は親クラス(=実体を持たない抽象概念)である。
それに対して、、、
- プロトタイプベースのオブジェクト指向
- あるオブジェクトの継承元は クラス ではなく、実体を持つオブジェクトになる。
- 継承元となったオブジェクトのことを プロトタイプ と呼ぶ。
> { foo: 'bar' }.__proto__
{} // __protp__ は継承元を指すプロパティで、その値はオブジェクトになる
- 理解を深めるための質問(のはずが、あまり腑に落ちない…)
- 例(クラスベース):ある親クラスを継承した子クラスのオブジェクトの継承元はなんですか?
- 回答は〇〇という親クラスです。
- 例(プロトタイプベース):ある親クラスを継承した子クラスのオブジェクトの継承元はなんですか?
- 回答は〇〇という親クラスのオブジェクトです。
- 例(クラスベース):ある親クラスを継承した子クラスのオブジェクトの継承元はなんですか?
class Human {}
class Taro extends Human {}
const taro = new Taro();
> taro.__proto__
Taro {}
> taro.__proto__.__proto__
Human {}
> taro.__proto__.__proto__.__proto__
{}
> taro.__proto__.__proto__.__proto__.__proto__
null
- Strict モード (直訳:厳格モード)
- エラーではないが落とし穴になる一部の事柄を、エラーが発生するように変更することで除去します。
- JavaScript エンジンによる最適化処理を困難にする誤りを修正します。Strict モードのコードは、非 Strict モードのコードより高速に実行できる可能性があります。
- 将来の ECMAScript で定義される予定の構文の使用を禁止します。
ショートハンド
const key = 'taro';
const value = 100;
const obj1 = { hoge: 123, [key]: 456, foo: value };
console.log(obj1); // { hoge: 123, taro: 456, foo: 100 }
// key 及び value の動的設定
const obj2 = { value };
console.log(obj2); // { value: 100 }
// 変数名が key に、変数の値がプロパティの値に設定される
分割代入
const hoge = { data: [{id:1},{id:2}] };
console.log(hoge);
{ data: [ { id: 1 }, { id: 2 } ] }
const { data:users } = hoge;
console.log(users);
[ { id: 1 }, { id: 2 } ]
シャローコピー(Shallow Copy)
分割代入でコピーされるオブジェクトの深さは1段階までしか有効じゃない。
const taro = { name: 'taro', age:20, address: { town: 'Tokyo' } };
const jiro = { ...taro, name: 'jiro' };
console.log(taro);
// { name: 'taro', age: 20, address: { town: 'Tokyo' } }
// この時点では taro に変化なし
jiro.address.town = 'Osaka'; // jiro のプロパティを変更
console.log(taro);
// { name: 'taro', age: 20, address: { town: 'Osaka' } }
// taro の address も一緒に変わってる
関数型プログラミング
-
命令形プログラミングと宣言型プログラミング
-
命令形プログラミング
- 命令(コード)に従って状態を変化させつつ処理して、最終結果を得る
- 例:手続き型プログラミングとオブジェクト指向プログラミング
-
宣言型プログラミング
- 出力の性質や状態を宣言する
- 例:SQLクエリ(SELECT句でどんなデータが欲しいかを宣言する)
-
命令形プログラミング
- 参照透過性
- 不変性(Immutablity)が担保される
- 手続き型が 文(Statement)、関数型が 式(Expression)
Array.prototype.sort()
するときは、シャローコピーしてくれるメソッド .slice()
を挟むと良い。
const list = [2, 1, 3];
// .slice() を挟む場合
console.log(list.slice().sort((n, m) => n < m ? -1 : 1)); // [1, 2, 3]
console.log(list); // [2, 1, 3]
// .slice() を挟まない場合
console.log(list.sort((n, m) => n < m ? -1 : 1)); // [1, 2, 3]
console.log(list); // [1, 2, 3] * list の中身が変わってる!!
配列の反復処理
-
.map()
- C# でいうところの
.Select()
- C# でいうところの
.filter()
.find()
.findIndex()
-
.every()
- C# でいうところの
.All()
- C# でいうところの
-
.some()
- C# でいうところの
.Any()
- C# でいうところの
.reduce()
.sort()
.slice()
.includes()
高階関数
- 関数の引数として関数を渡すことができる。
- 関数の戻り値として関数を返すことができる。
関数を引数として渡す例
.map()
の引数としてアロー関数式を記述すると思うが、それが無名関数という関数を引数に渡している例。
関数を戻り値として返す例
const person = (name) => {
const hello = () => {
console.log(`Hello, ${name}`);
};
return hello; // 関数 `hello` を戻り値として返している
// `hello`への不要な代入を避けた書き方
// return () => { console.log(`Hello, ${name}`); };
}
// アロー関数式で省略した書き方
// const person = (name) => () => console.log(`Hello, ${name}`);
const taro = person('taro'); // 代入された `taro` は関数なので、次行で `taro();` で呼び出している
taro(); // Hi, taro
カリー化
- 複数の引数を持つ関数を分解すること。
- 複数の引数を持つ関数をより少ない引数を取る関数に分割して入れ子にすること。
- 参考サイト:javascriptのcurry化とは何か
- 関数の部分適用
クロージャ
- クロージャ
- (関数
increment
から見た)外のスコープの自由変数count
を参照する関数increment
を、さらに関数で包み込んだ特殊なオブジェクトのこと
- (関数
- エンクロージャ
- 閉じ込めてる外側の関数
counter
のこと
- 閉じ込めてる外側の関数
// パターン1
const counter = () => {
let count = 0; // 自由変数(Free Variable)
const increment = () => {
return count += 1;
};
return increment;
};
// パターン2:自由変数をデフォルトパラメーターに設定
const counter = (count = 0) => {
const increment = () => {
return count += 1;
};
return increment;
}
// パターン3:アロー関数式で省略
const counter = (count = 0) => () => count += 1;
// 使用例:いずれのパターンでも同様
const increment = counter();
increment(); // 1
increment(); // 2
increment(); // 3
increment(); // 4
TypeScript
- オブジェクトの型はインタフェースを使って定義する。
- プロパティの修飾子に
readonly
や?
を使って、書き換え不可や省略可能を示すことができる。 - 後述のインデックスシグネチャを用いると、特定のインタフェースを実装した上で,任意のプロパティを追加することができる。
Index Signature (インデックスシグネチャ)
interface Status {
level: number;
maxHP: number;
maxMP: number;
[attr: string]: number; // 任意のキーのプロパティ値を定義。キーに使える型は、文字列と数値のみ。
}
const myStatus: Status = {
level: 99,
maxHP: 999,
maxMP: 999,
attack: 999,
defense: 999
}
コンパイラオプション
-
noImplicitAny
をtrue
にする。 - これが
tsconfig.json
に記載がないと、引数の型定義がないものはany
として扱われる。
呼び出し可能オブジェクト (Callable Object)
inerface NumOp {
(n: number, m: number): number;
// 引数に数値型`n`と数値型`m`を持ち、戻り値が数値型の関数を実装したインタフェース
}
const add: NumOp = function(n, m) {
retirm n + m;
}
const substract: NumOp = (n, m) => n - m;
console.log(add(1, 2)); // 3
console.log(asubstract(7, 2)); // 5
ジェネリックス
const toArray = <T>(arg1: T, arg2: T): T[] => [arg1, arg2]; // 同じ型の引数を2つ渡す
const toArrayVariably = <T>(...args:T[]): T[] => [...args]; // 引数を可変長に
enum 型より文字列リテラル型
enum 型はデフォルトでは数値型、且つ、型安全が保証されない。
enum Pet { Cat, Dog, Rabbit }
console.log(Pet.Cat); // 0
console.log(Pet.Dog); // 1
console.log(Pet.Rabbit); // 2
let pet: Pet = Pet.Cat;
pet = 10; // 0, 1, 2 以外でも代入できちゃう!!型安全が保証されていない
console.log(pet); // 10
type PetType = 'Cat' | 'Dog' | 'Rabbit';
let petType: PetType = 'Cat';
console.log(petType); // "Cat"
petType = 'Dog';
console.log(petType); // "Dog"
// petType = 'Bird'; // コンパイルエラー
const hoge = (petType: PetType) => {
switch(petType) {
case "Cat":
console.log(petType);
break;
case "Dog":
console.log(petType);
break;
case "Rabbit":
console.log(petType);
break;
default:
console.log(petType);
}
}
let petType: PetType = 'Dog';
hoge(petType);
インタフェースと型エイリアス
インタフェース
- 拡張に対してオープンである。
- オブジェクトやクラスの仕様を定めるためのもの。
- インタフェースで定義できるのは、オブジェクトとクラスの型だけ。
- インタフェースは型宣言なので、末尾にセミコロンは不要。
// オブジェクトの型をインタフェースで定義する
// const red: { rgb: string, opacity: number } = { rgb: 'ff0000', opacity: 1 };
interface Color {
readonly rbg: string;
opacity: number;
name?: string;
}
const red: Color = { rgb: 'ff0000', opacity: 1 };
// クラスの型をインタフェースで定義する
interface Shape {
readonly name: string;
getArea: () => number;
}
interface Quadrangle {
sideA: number;
sideB?: number;
sideC?: number;
sideD?: number;
}
class Rectangle implements Shape, Quadrangle {
readonly name = 'rectangle';
sideA: number;
sideB: number;
constructor(sideA: number, sideB: number) {
this.sideA = sideA;
this.sideB = sideB;
}
getArea = (): number => this.sideA * this.sideB;
}
型エイリアス
- 拡張に対してクローズドである。
- 任意の型に別名を与えて再利用できる。
- 型エイリアスは無名の構文に対する代入文(式)なので、末尾にセミコロンが必要。(
=
を使ってる) - インタフェースより型エイリアス
type
を使うほうが主流らしい。
// 文字列リテラルの共用体型を、型エイリアスとして別名をつけて代入している
type Unit = 'USD' | 'EUR' | 'JPY' | 'GBP';
// オブジェクトの型として使う
type TCurrency = {
unit: Unit;
amount: number;
};
const priceA: TCurrency = { unit: 'JPY', amount: 1000 };
class Product implements TCurrency {
unit: Unit;
amount: number;
constructor(unit: Unit, amount: number) {
this.unit = unit;
this.amount = amount;
}
}
const productA = new Product('JPY', 100);
Null 安全性
-
tsconfig.json
のcompilerOptions > strictNullChecks
にtrue
が設定されていると、型の Null 安全性が保証される。 -
strictNullChecks
はデフォルトで無効なので、型の Null 安全性を保証したければ、tsconfig.json
に追記すること。
typeof 演算子
- 変数の型を文字列で返す。
console.log(typeof 10); // "number"
- 変数の型を抽出する。(型コンテキストで使用した場合)
const arr = [1, 2, 3];
typeof numArr= typeof arr; // 変数の型を抽出
const val: numArr = [4, 5, 6]; // OK
const val: numArr = ["a", "b", "c"]; // NG
in 演算子
- オブジェクトのキーとして存在するか真偽値を返す。
const obj = { hoge: 1, foo: 2, bar:3 };
console.log("hoge" in obj); // true
-
for...in
でオブジェクトのキーを順次に取得する。
for (const key in obj) { console.log(key); } // hoge foo bar
- 列挙型の値をキーに変換してオブジェクトを定義する。(型コンテキストで使用した場合)
type Keys = "hoge" | "foo" | "bar";
type KeysMap = { [key in Keys]: number };
const keysMap: KeysMap = {
hoge: 1,
foo: 2,
bar: 3
};
keyof 演算子
- オブジェクトのキーを抽出して列挙型を作る。(型コンテキストのみで使用可)
const obj = { hoge: 1, foo: 2, bar: 3 };
type ObjKeys = keyof typeof obj; // 'hoge' | 'foo' | 'bar'
const a: ObjKeys = 'hoge'; // OK
const b: ObjKeys = 'aaaa'; // NG
組み込みユーティリティ
Partial<T>
型 T
のすべてのプロパティを省略可能にした新しい型を返す。
type Hoge = {
foo: number;
bar: boolean;
};
type PartialHoge = Partial<Hoge>;
// PartialHoge {
// foo?: number;
// bar?: boolean;
// }
Required<T>
型 T
のすべてのプロパティを必須にした新しい型を返す。
type Hoge = {
foo?: number;
bar: boolean;
};
type Required = Required<Hoge>;
// RequiredHoge {
// foo: number;
// bar: boolean;
// }
Readonly<T>
型 T
のすべてのプロパティに readonly
属性をつけた新しい型を返す。
type Hoge = {
foo: number;
bar: boolean;
};
type ReadonlyHoge = Readonly<Hoge>;
// ReadonlyHoge {
// readonly foo: number;
// readonly bar: boolean;
// }
Pick<T, K>
型 T
から型 K
と合致するプロパティを抽出した新しい型を返す。
type Sample = {
hoge: number;
foo: boolean;
bar: string;
};
type HogeAndFoo = Pick<Sample, "hoge" | "foo">;
// HogeAndFoo {
// hoge: number;
// foo: boolean;
// }
Omit<T, K>
型 T
から型 K
と合致するプロパティを除外した新しい型を返す。
type Sample = {
hoge: number;
foo: boolean;
bar: string;
};
type WithoutBar = Omit<Sample, "bar">;
// WithoutBar {
// hoge: number;
// foo: boolean;
// }
Extract<T, U>
型 T
から型 U
の要素だけを抽出した列挙型を返す。
type A = Extract<string, number>; // never
type B = Extract<string, any>; // string
type C = Extract<string | number | boolean, string | boolean>; // string | boolean
type Sample = "hoge" | "foo" | "bar";
type HogeAndFoo = Extract<Sample, "hoge" | "foo">; // "hoge" | "foo"
Exclude<T, U>
型 T
から型 U
の要素を省いた列挙型を返す。
type A = Exclude<string, number>; // string
type B = Exclude<string, any>; // never
type C = Exclude<string | number | boolean, string | boolean>; // number
type Sample = "hoge" | "foo" | "bar";
type Foo = Exclude<Sample, "hoge" | "foo">; // "foo"
NonNullable<T>
型 T
から null
と undefined
を省いた型を返す。
type A = NonNullable<string | null>; // string
type B = NonNullable<null>; // never
type C = NonNullable<string>; // string
Record<K, T>
型 K
の要素をキーとしたプロパティを持ち、そのプロパティの型を T
としたオブジェクトの型を返す。
type Animal = "cat" | "dog" | "pig";
type AnimalAmount = Record<Animal, number>;
const animalAmount: AnimalAmount = {
cat: 100,
dog: 200,
pig: 300
};
Parameters<T>
関数型 T
の引数の型を抽出し、タプルとして返す。
const f1 = (arg1: number, arg2: string) => { console.log(arg1, arg2); };
const f2 = () => {};
type P1 = Parameters<typeof f1>; // [number, string]
type P2 = Parameters<typeof f2>; // []
ReturnType<T>
関数型 T
の戻り値の型を返す。
const f1 = () => "foo";
type R1 = ReturnType<typeof f1>; // string
type R2 = ReturnType<typeof window.setTimeout>; // number
type R3 = ReturnType<() => void>; // void
Uppercase<T>
文字列リテラル型 T
の各要素の文字列をすべて大文字にする。
Lowercase<T>
文字列リテラル型 T
の各要素の文字列をすべて小文字にする。
Capitalize<T>
文字列リテラル型 T
の各要素の先頭文字列を大文字にする。
Uncapitalize<T>
文字列リテラル型 T
の各要素の先頭文字列を小文字にする。
type Animal = "Cat" | "dog" | "PIG";
type C1 = Uppercase<Animal>; // "CAT" | "DOG" | "PIG"
type C2 = Lowercase<Animal>; // "cat" | "dog" | "pig"
type C3 = Capitalize<Animal>; // "Cat" | "Dog" | "PIG"
type C4 = Uncapitalize<Animal>; // "cat" | "dog" | "pIG"