30歳からのプログラミング

30歳無職から独学でプログラミングを開始した人間の記録。

ReactのコンポーネントにFlowの型をつける

以前の記事でFlowの導入が完了し、Reactを使って書いたコードに型を付けることが出来るようになった。
ここからは実際に、Reactのコンポーネントに型を付けていく。

Flow導入の記事はこちら。

なお、この記事を書いている環境のFlowのバージョンは、0.46.0

対象となるコンポーネントを用意

以下のようなコンポーネントを作成した。
このコンポーネントに、型を付けていく。

class App extends React.Component {
  render() {
    return (
      <div>{this.props.name}</div>
    );
  }
}

<App name="Tom" />のようにnameをpropsとして渡して使うだけの、シンプルなコンポーネント

最初に、このファイルの一番上に// @flowと記述する。
この記述があるファイルのみが、Flowによるチェックの対象となる。

propsに型を付ける

まずはprops.nameに、型をつけてみる。
下記のようにprops:と記述すればいい。

class App extends React.Component {
  props: {
    name: string,
  };
  render() {
    return (
      <div>{this.props.name}</div>
    );
  }
}

プロパティの名前: 型と定義するのが、基本。
型の種類は多数あり、公式ドキュメントで確認できる。
Type Annotations | Flow

上記ではnameの型として文字列を指定しており、<App name={true} />のように文字列以外を渡してFlowを実行すると、エラーになる。

次に、Appを次のように書き換えてみる。

class App extends React.Component {
  props: {
    name: string,
  }
  render() {
    return (
      <div>
        {this.props.name}<br />
        {this.props.age}
      </div>
    );
  }
}

そして、<App name="Tom" age={30} />としてnameageを渡す。

この状態でFlowを実行するとどうなるか。
ageの型が定義されていないとして、エラーになる。
これを解消するには、ageの型も設定すればいい。

props: {
  name: string,
  age: number,
}

このように、型を指定すれば、エラーは消える。
ではこの状態で<App name="Tom" />としてageを渡さなかった場合、どうなるか。
やはりエラーになる。

上記の設定だと、「ageにはかならずnumberが渡される」ことを意味しており、他のデータ型が渡されたときはもちろん、値が渡されなかったときにもエラーとなる。

このような、値が渡されたときの型は決まっているが、値が渡されないこともある、というケースでは、以下のように設定すればよい。

props: {
  name: string,
  age?: number,
}

?がついているが、これはOptional Propertyというもので、このプロパティは文字通りオプショナルになる。
つまり、値を渡しても渡さなくてもよい。
これで、先程のエラーは消える。

Optional object type properties

論理和

要するにOR
これは何も難しいことはなく、プロパティの名前: 型 | 型とすればいい。

type Prop = {
  name: string,
  age: number | string,
};

このように書くと、ageは数値か文字列のいずれか、という指定になる。
そのため<App name="Tom" age="30" />でもエラーにならない。

独自の型定義

自分で型を定義することも出来る。
typeで宣言して、定義する。

下記では、Propという型を定義し、それをApppropsに設定している。

type Prop = {
  name: string,
  age: number,
};

class App extends React.Component {
  props: Prop;
  render() {
    return (
      <div>
        {this.props.name}<br />
        {this.props.age}
      </div>
    );
  }
}

型の定義が長くなるようであれば、このほうが可読性が増すかもしれない。

何より、型を再利用することが出来る。
同じデータ構造が何度も出てくるのなら当然、一箇所で定義して使いまわしたほうがいい。
exportすることも出来るので、ファイルをまたがって使うことが出来る。

stateに型を付ける

Appを拡張してstateやメソッドを追加し、stateに型を付けたのが、以下。

type Prop = {
  name: string,
  age: number,
};

type State = {
  isYouth: boolean,
};

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isYouth: this.isYouth() };
  }
  state: State;
  props: Prop;
  isYouth() {
    return this.props.age < 30;
  }
  render() {
    return (
      <div>
        {this.props.name}<br />
        {this.props.age}
      </div>
    );
  }
}

stateの場合も、propsの場合と何も変わらない。

メソッドにも型をつける

先程の状態でFlowを実行してもエラーにはならないが、メソッドにも型をつけたほうが望ましい。

まず、constructor
引数として渡されるpropsの型を、Propとして指定している。

constructor(props: Prop) {
  super(props);
  this.state = { isYouth: this.isYouth() };
}

次にisYouth。返り値は真偽値であると指定している。

isYouth(): boolean {
  return this.props.age < 30;
}

Stateless Functinal Componenens

Reactのコンポーネントは関数で定義することも出来るが、それに対しては、次のように型を付ける。

const App = (props: { name: string, age: number }) => (
  <div>
    {props.name}<br />
    {props.age}
  </div>
);

参考資料

配列の最大値を取得する ES2015版

JavaScriptで、配列の最大値を取得する方法について。
最初に答えを書いておくと、Math.max(...targetArray)

旧来はapplyを使うのが主流だった?

Math.max()を使うと、数値の中から最大のものを取得できる。

Math.max(6, 8, 9, 7); // 9

しかしこれは、一つずつ数を渡す必要があり、配列を渡すことは出来ない。
配列でMath.max()を使うにはどうすればいいのか。

applyを使えば解決する。

const array = [3, 8, 7, 2, 5];
Math.max.apply(null, array); // 8

applyの第二引数には配列を渡して使うが、この配列の要素が1つずつ引数として渡される。
そのため、配列を展開してそれを引数として渡す手段として、applyが有用なのだ。

このテクニックは有名らしく、Math.max.apply(null, array);の記法も、「イディオム」とすら言われている。
事実、「JavaScript 配列 最大値」などで調べると、大体これが出てくる。

スプレッド演算子を使う

だがES2015なら、もっとシンプルに書ける。

先程、「配列を展開してそれを引数として渡す手段」と書いたが、ES2015で使えるようになったスプレッド演算子は、まさにそういった使い方が出来る。

const array = [3, 8, 7, 2, 5];
Math.max(...array); // 8

スプレッド演算子そのものの説明はこちら。

オブジェクトの配列から、特定のプロパティの最大値を取得する

次のような、オブジェクトが要素として格納された配列があったとする。

const memberList = [
  { name: 'Tom', age: 20 },
  { name: 'Bob', age: 25 },
  { name: 'Cate', age: 23 },
  { name: 'John', age: 18 },
];

この中から、ageの最大値を取得したい。このようなケースでも、先程の書き方で対応できる。

Math.max(...memberList.map(m => m.age)); // 25

mapageの配列を作り、それをスプレッド演算子で展開してMath.max()に渡している。

プログラマとしての能力

得意気に書いてきたが、今回書いたものは、自分で考えたものでもなければ、自分で調べたものでもない。
職場のコードレビューで教えてもらったものである。

上記の、memberListからageの最大値を取得したい、というようなケースに遭遇した。
その際に自分が書いたのが、以下のようなコード。
Math.max()すら使っていない。

const ages = [];
let maxAge = 0;
for (let i = 0; i < memberList.length; i++) {
  ages.push(memberList[i].age);
}
for (let i = 0; i < ages.length; i++) {
  if (ages[i] > maxAge) maxAge = ages[i];
}
console.log(maxAge);

すぐに、最大値を取得するならMath.max()を使ったほうが分かりやすい、という指摘を受けた。
恥ずかしながらMath.max()を使ったことがなかったので、ネットで調べた。applyを使ったテクニックも、その時に知った。
以下のように書き換えることで、多少は見通しがよくなった。

const ages = [];
for (let i = 0; i < memberList.length; i++) {
  ages.push(memberList[i].age);
}
console.log(Math.max.apply(null, ages));

だがこれでも不十分だった。
mapとスプレッド演算子を使ったやり方を教えてもらい、それに書き換えた。

console.log(Math.max(...memberList.map(m => m.age)));

俺が9行もダラダラと書いてきたことを、たった1行で表現してしまっている。
可読性や保守性を犠牲にしてトリッキーなことをしている訳でもないのに。

いくらES2015の機能を知っていたとしても、使い方が分からなければ意味がない。
そのことを思い知らされた。
慣れもあるとは思う。場数を踏むことで、すぐに適切なコードが思い浮かぶようになる、というのはあるはず。
だがそれにしても、mapやスプレッド演算子を使えばいいなんて、ちょっと考えればすぐに分かりそうなものなのに。

知識がたくさんあるのは大事だし、いわゆるベストプラクティスを知っていることも大事。
でもそれだけじゃなく、使いこなす能力というか、解決すべき問題は何で、どのようにアプローチすべきで、そのためにどの道具を使えばいいのか、ということを考える能力が必要なんだろう。それこそが、プログラマの能力のような気がする。
思考力とか認識力のようなもの。

後は、やっぱり実務経験が大切というか、ネットで調べて得たものよりも遥かに妥当な内容がコードレビューで返ってきて、笑ってしまった。
いくらプログラミングはネットで独学しやすいとはいえ、優秀な人と一緒に働くほうが、はるかに成長効率がよい。