注意: この記事はかなり昔の Flow をもとに書かれました。当時と比べ Flow は特に React まわりで大きな変更が入っており、おそらくこの記事の賞味期限は切れていると思います。
React で大きなソフトウェアを書いている時に、複数のコンポーネントに共通する処理を抽象化するためのプラクティスに Higher-Order Components (HOC)というものがあります。HOCについて一言で言うなら「React コンポーネントを受け取って、それをラップした新しい React コンポーネントを返す関数」です。
HOC では引数に受け取るコンポーネントに特定のインタフェースを期待することが多いと思います。こう言う時こそ flow を使って引数の型を強制したくなりますね。
期待するのは例えば「Promise<*>
を返す update
メソッドを持っていること」などですが、その一方でその引数はコンポーネントでもなければなりません。このような型は次のように書くことができます。
// @flow
interface Updatable {
update(): Promise<*>;
}
type UpdatableComponent = Class<Updatable> & ReactClass<*>;
なぜ Class<Updatable>
などとしているかと言うと、マウントした後にそのインスタンスを扱うことを意図しているからです。
全体を合わせると HOC の型はこんな感じで書くことができます。
この HOC はコンポーネントを componentDidMount
の度に update()
を実行するものに変更します。ただし、 underscore の throttle
を使って interval に 1 度しか更新しません。また isMounted is an Antipattern - React Blog で紹介されている makeCancelable
関数を使ってアンマウント時に自動的にリクエストを停止し、メモリリークを防ぎます。
// @flow
import {Component} from "react";
import {throttle} from "underscore";
import {makeCancelable} from "./pat/to/file";
interface Updatable {
update(): Promise<*>;
}
export default function withThrottledUpdate(
WrappedComponent: Class<Updatable> & ReactClass<*>
): ReactClass<*> {
return class extends Component {
instance: ?Updatable;
throttledUpdate: ?Function;
request: ?{
promise: Promise<*>;
cancel: Function;
}
props: {
interval: number;
leading: boolean;
}
update(): Promise<*> {
if (this.instance) {
this.request = this.instance.update();
}
}
proc(wrappedComponentInstance: Updatable) {
this.instance = wrappedComponentInstance;
this.throttledUpdate = throttle(
this.update.bind(this),
this.props.interval,
{ leading: this.props.leading }
);
}
componentDidMount() {
this.update();
}
componentDidUpdate() {
if (this.throttledUpdate) {
this.throttledUpdate();
}
}
componentWillUnmount() {
if (this.request) {
this.request.cancel();
}
}
render() {
const { interval, leading, ...ThroughProps } = this.props;
return <WrappedComponent ref={this.proc.bind(this)} {...passThroughProps} />;
}
}
}