Open30

React を学ぶ

Takashi ShibataTakashi Shibata

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 を実行する。
Takashi ShibataTakashi Shibata

プロジェクト作成編

  • 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 オプションは、強制的に安定版バージョンに一括更新
    • 更新後の動作を保証するものではないため、バージョン更新後はテスト必須!!
Takashi ShibataTakashi Shibata

npx create-react-app コマンドは、それなりにパワーのあるメインPCでも数分掛かった…。

Takashi ShibataTakashi Shibata

package.json について

プロジェクトのルートフォルダに配置されている json ファイル

dependencies プロパティ

yarn install コマンド実行時にインストールされるパッケージ郡

scripts プロパティ

このプロパティに記述されたコマンドを yarn xxxx で実行できる。
ちなみに、npm コマンドを使う場合は npm run xxxx となって、記述が少し多め。

Takashi ShibataTakashi Shibata

スコープ

JavaScript は letconst宣言された識別子は、ブロックスコープを持つ。

let x = 1;
{
  let x = 2;
}
console.log(x); // 1 が出力されます

let で宣言されているわけじゃなく、再代入であればそれが反映される。

let x = 1;
{
  x = 2;
}
console.log(x); // 2 が出力されます
Takashi ShibataTakashi Shibata

気になるワードはこのスクラップに書き込んでいく

Takashi ShibataTakashi Shibata
  • プリミティブ
    • プリミティブとかオブジェクトというワードを目にするけど、プリミティブ is 何?
    • 英単語の意味としては、原始的な(もの)、根源的な(もの)、初期の、太古の、未発達な、素朴な、粗野な、原形
    • ITの分野では、複雑な構造を形つくる際の構成要素。単純な要素、といった意味。
    • JavaScript のプリミティブ型といえば
      • Boolean、String、Number、BigInt、Symbol、Null、Undefined
    • プリミティブ型はインスタンスメソッドを持たない。
      • プリミティブ型の値は暗黙的にラッパーオブジェクトに変換されているので、実際のコードではプリミティブ型の値からメソッドを生やすことができる。
        • なるほど~、忘れがちだけど重要。。
Takashi ShibataTakashi Shibata
  • リテラル
    • ソースコードの中で型宣言された変数の値を表現したもの
    • Boolean 型で言えばtruefalseのこと
    • Number 型で言えば88-2のこと
    • 配列リテラル
      • [][1,2,3]
    • オブジェクトリテラル
      • {}{key:value}
    • 正規表現リテラル
      • /d(b+)d/gという形で表現されたもの(/pattern/frags
    • 関数リテラル
      • const add = function (n, m) { return n + m; };
Takashi ShibataTakashi Shibata
  • 文と式
  • 関数宣言(文)と関数式
    • 関数宣言 function hoge(n) {}
    • 関数式 const hoge = function(n) {};
      • 関数式の書き方が推奨
      • 関数式はFunctionオブジェクトのインスタンス
    • アロー関数式
      • const add = (n) => { return n +1; };
      • const add = n => n + 1; (省略版)
  • 詳しくはこちらの記事を参照
Takashi ShibataTakashi Shibata

その言語の関数がその他の変数と同様に扱われることを表します。例えば、こうした言語では、関数を他の関数への引数として渡したり、他の関数から返却したり、変数の値として代入したりすることができます。

Takashi ShibataTakashi Shibata
  • デフォルト引数
    • 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' ]
Takashi ShibataTakashi Shibata
  • クラスベースのオブジェクト指向
    • クラスとは実体を持たない抽象概念である。
    • クラスを具象化したものがオブジェクトである。
    • 継承関係を考えたときに、あるオブジェクトの継承元は親クラス(=実体を持たない抽象概念)である。

それに対して、、、

  • プロトタイプベースのオブジェクト指向
    • あるオブジェクトの継承元は クラス ではなく、実体を持つオブジェクトになる。
    • 継承元となったオブジェクトのことを プロトタイプ と呼ぶ。
> { 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
Takashi ShibataTakashi Shibata
  1. エラーではないが落とし穴になる一部の事柄を、エラーが発生するように変更することで除去します。
  2. JavaScript エンジンによる最適化処理を困難にする誤りを修正します。Strict モードのコードは、非 Strict モードのコードより高速に実行できる可能性があります。
  3. 将来の ECMAScript で定義される予定の構文の使用を禁止します。
Takashi ShibataTakashi Shibata

ショートハンド

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 } ]
Takashi ShibataTakashi Shibata

シャローコピー(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 も一緒に変わってる
Takashi ShibataTakashi Shibata

関数型プログラミング

  • 命令形プログラミング宣言型プログラミング
    • 命令形プログラミング
      • 命令(コード)に従って状態を変化させつつ処理して、最終結果を得る
      • 例:手続き型プログラミングとオブジェクト指向プログラミング
    • 宣言型プログラミング
      • 出力の性質や状態を宣言する
      • 例:SQLクエリ(SELECT句でどんなデータが欲しいかを宣言する)
  • 参照透過性
  • 不変性(Immutablity)が担保される
  • 手続き型が 文(Statement)、関数型が 式(Expression)
Takashi ShibataTakashi Shibata

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 の中身が変わってる!!
Takashi ShibataTakashi Shibata

高階関数

  • 関数の引数として関数を渡すことができる。
  • 関数の戻り値として関数を返すことができる。

関数を引数として渡す例

.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
Takashi ShibataTakashi Shibata

カリー化

  • 複数の引数を持つ関数を分解すること。
  • 複数の引数を持つ関数をより少ない引数を取る関数に分割して入れ子にすること。
  • 参考サイト: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
Takashi ShibataTakashi Shibata

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
}

コンパイラオプション

  • noImplicitAnytrue にする。
  • これが 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);
Takashi ShibataTakashi Shibata

インタフェースと型エイリアス

インタフェース

  • 拡張に対してオープンである。
  • オブジェクトやクラスの仕様を定めるためのもの。
  • インタフェースで定義できるのは、オブジェクトクラスの型だけ。
  • インタフェースは型宣言なので、末尾にセミコロンは不要。
// オブジェクトの型をインタフェースで定義する
// 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);
Takashi ShibataTakashi Shibata

Null 安全性

  • tsconfig.jsoncompilerOptions > strictNullCheckstrue が設定されていると、型の Null 安全性が保証される。
  • strictNullChecks はデフォルトで無効なので、型の Null 安全性を保証したければ、tsconfig.json に追記すること。
Takashi ShibataTakashi Shibata

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
Takashi ShibataTakashi Shibata

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
};
Takashi ShibataTakashi Shibata

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
Takashi ShibataTakashi Shibata

組み込みユーティリティ

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 から nullundefined を省いた型を返す。

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"