TL;DR
Higher-Order Componentsをaddon-infoで表示させようとするとバグる
Storybook? Storybook/addon-info?
みなさん、storybookを使ってますか?コンポーネントを作っていく時に便利すぎるので是非使ってください。説明だるいのでプロジェクトページのexamples見ればすぐわかりの取得ができます。
さて、storybookのプラグインであるaddon-infoも超便利です。コンポーネントがどのような役割を果たすのか、何を意図して作られたのかをmarkdownで記述すると表示してくれますし、実際にstoryのコード内でどのように使われているかを表示したり、Flowによる型チェックが(たぶんTypeScriptも)定義されていた場合、それも表示してくれます。
たとえば、以下のようなコンポーネントがあるとします。
// @flow
import React from 'react';
type Props = {
/* クエスチョンマークの前につく文字列です */
label: string,
/* クエスチョンマークの数 */
amount: number,
};
/* めっちゃクエスチョンマークをあれします */
const SuperQuestionLabel = ({label, amount}: Props) => (
<a>{label + '?'.repeat(amount)}</a>
);
SuperQuestionLabel.defaultProps = {
label: "は",
};
export default SuperQuestionLabel;
そして、story用のコードは次のようになります。
// @flow
import React from 'react';
import { storiesOf } from '@storybook/react';
import { withInfo } from '@storybook/addon-info';
import SuperQuestionLabel from "../SuperQuestionLabel";
storiesOf('何もわからん', module)
.add(
'basic usage',
withInfo(
'感動する文章'
)(() => (
<SuperQuestionLabel
label={"これからは飲酒の時代"}
amount={8} />
)))
すると、storybookの当該ページにはInfoボタンが表示され、クリックすると次のようなすばらしいインフォメーション情報が表示されます。
感動する文章、コンポーネントがどのように使われるのかの一例、そしてプロパティの詳細情報、感動ですね。Reactに限らず、今やReactコンポーネントを作るのであればstorybook、そしてaddon-infoは欠かせないものになりつつあります。(残念な点としては、今の所addon-infoはReactにしか対応してない点でしょうか)
Main Issue
そんなaddon-infoには天敵が存在します。Higher-Order Componentsです。
その例として、Stateless Functional Componentsで、recomposeの機能を使用している場合です。
うまくいかない例を見てみましょう。
// @flow
import React from 'react';
import {pure} from 'recompose';
type Props = {
/* クエスチョンマークの前につく文字列です */
label: string,
/* クエスチョンマークの数 */
amount: number,
};
/* めっちゃクエスチョンマークをあれします */
const SuperQuestionLabel = ({label, amount}: Props) => (
<a>{label + '?'.repeat(amount)}</a>
);
SuperQuestionLabel.defaultProps = {
label: "は",
};
export default pure(SuperQuestionLabel);
recomposeのpure
を用いてコンポーネントの再レンダリングを抑えています。高いパフォーマンスを求められるWebアプリケーションであれば、pure
やonlyUpdateForKeys
を用いてチューニングを行うことも多々あるでしょう。しかし、StorybookのInfoページは次のようになってしまいます。
感動どころか、失望してしまいます。
なぜこのようになるか?理由はこれらのpure
やonlyUpdateForKeys
はHigher-Order Componentsだからです。するとひとつコンポーネントにコンポーネントがラップされた形になってしまいますから、そのためにaddon-infoが各種情報を拾ってくれなくなるのです。
Solution
HoCしたコンポーネントであることが問題なのですから、素のコンポーネントをstoriesに記述すればいいわけです。
つまり、次のようなコードにします。
// @flow
import React from 'react';
import {pure} from 'recompose';
type Props = {
/* クエスチョンマークの前につく文字列です */
label: string,
/* クエスチョンマークの数 */
amount: number,
};
/* めっちゃクエスチョンマークをあれします */
const SuperQuestionLabel = ({label, amount}: Props) => (
<a>{label + '?'.repeat(amount)}</a>
);
SuperQuestionLabel.defaultProps = {
label: "は",
};
export const SuperQuestionLabel_ = SuperQuestionLabel;
export default pure(SuperQuestionLabel);
export const
を使用して、素のコンポーネントを追加で出力するようにしただけです。
そして、story用のコードも、importするものを変更すればいいのです。
// @flow
import React from 'react';
import { storiesOf } from '@storybook/react';
import { withInfo } from '@storybook/addon-info';
// ここを変更!
import {SuperQuestionLabel_} from "../SuperQuestionLabel";
storiesOf('すべてが理解できた', module)
.add(
'basic usage',
withInfo(
'感動する文章'
)(() => (
<SuperQuestionLabel_
label={"これからは飲酒の時代"}
amount={8} />
)))
少々(exportする名前が)雑ではありますが、これでaddon-infoは次のように、期待通りの表示をしてくれます。
Conclusion
Higher-Order Componentsをdefaultで出力しているコンポーネントは、少なくともStorybookのaddon-infoで概要を表示させたいのであれば、素のコンポーネントもexportしてあげよう
END OF FILE
最後の画像のAlt textは「感動!君も泣け」です。
そして私はこの問題におもいっきりハマり、2日無駄にしました。人生は短く、みなさんの人生も短い、だから私は人生の時間をさらに削り、みなさんの人生の時間を無駄にしないよう、記事を書いている…
Top comments (0)