TypeScript の Decorator (HOC も)を使って、React の部品を使い回したい!

React の描画内容を Decorator でラッピングしてみる!

React 書いてますか?

最近私は Vue の方がお好みですが、
やっぱり複雑な事をしようと思うと、
JavaScript を直接拡張しているような React の書き味はなんとも心地よいものですね。

で、先日 React を書いていたのですが、
Nuxt の layout プロパティのように、メニューなどの共通で使う部品を外部から差し込みたいなと思いまして。

TypeScript で書いていたので、 Decorator で書けば良いじゃん!
ということでやってみた記録です。

Decorator でコンテンツラッパーを作る

やりたいこと

やりたいことはこれ。

const menu: FC = ({ children }) => (
<div>
  <ul>
    <li>項目1</li>
  </ul>
  { children }
</div>
);

const main: FC = () => (
  <Menu>
    <p>main contents</p>
  </Menu>
);

こうすると、main が menu の children で展開されるので、
メインコンテンツにメニューをくっつけて表示することができますね。

ただ、これだとメニューの中にメインコンテンツがあるみたいな DOM 構成になって
ちょっと気持ち悪い感じ。

ということで、この menu を、デコレーターでどうにかくっつけたいと言うのが今回のお気持ち。

デコレーターで書き直した Ver

さっきは省略しましたけど、とりあえず HTML 側にこんな感じのタグが用意されている想定をして。

<div id=”app”></div>

実際のコードはこちら。

@menu
class App extends React.Component {
  render() {
    return (
      <h1>
        main
      </h1>
    );
  }
}

ReactDOM.render(<App />, document.getElementById(’app’));

function menu(target, name) {
    const originalRender = target.prototype.render;
  target.prototype.render = function() {
    return (
            <div>
                <ul>
                    <li>項目1</li>
                </ul>
                { originalRender() }
            </div>
        );
  };
}

menu 関数みてもらったら、とってもシンプルに作れることが分かりますね。

元々の render の内容は target.prototype.render に入ってくるので、
一旦それを originalRender 変数に待避させ、
target.prototype.render に新しく描画する内容を上書きしています。

元々の内容を originalRender に入れてあるので、
menu で元の内容を描画させたいところに { originalRender() } を置いておけば
そこに元々の内容が展開されますね。

とっても簡単。

Functional Component の場合どうするの?

実はデコレーターを使った Ver に書き直したときに、
しれっと元の方を class を使った Component に差し替えてました。

元のコードではどちらも Functional Component をつかってたはず…

では Functional Component にも対応できるようにデコレーターを書き直そう!となるのですが、
残念なことに、

@menu
const app = () => (
  <h1>App</h1>
);

みたいな、単純な変数にデコレーターはつけられないのです。
(クラスの中のプロパティならできるのですけどね。)

ということで、ここで HOC (Higher-order Component) に頼りましょう。

HOC で書き直す

とはいっても、結局やることはほぼ一緒で、クラスの場合は先ほどのようにデコレーターも使えます。
単純に、デコレーター内で関数を展開して render を走らせていたものを、
新しい render を含む Component を返すようにしてあげたらいいだけです。

ということで。

export menu function (TargetComponent: any) {
  return class Menu extends Component<any, any> {
    render() {
      return (
        <div>
          <Menu />
          <TargetComponent {...this.props}/>
        </div>
      );
    }
  }
};

こんな感じに。

実際に使うときは、

const dashboard: FC<HelloProps> = () => (
  <h1>
    Hello!
  </h1>
);

export default menu(dashboard);

こんな感じで、 Functional Component を HOC でラップしてあげたら OK です。

ちなみに、先ほど同様

@menu
class App extends React.Component {
...

みたいな使い方をしても、同じように動くようになっています。
(処理を追いかけてみたら、やっていることは同じであることが分かります!)

レイアウトのような、使い回せる部品は HOC に出すことを検討しよう!

以外に難しそうで、単純に関数をラッピングしてあげるだけなので
分かってしまえば簡単ですね。

HOC はちょっと手を出しづらい記法ですが、一度慣れるととても便利なものなので、
是非こういう簡単なところからつかっていってみたいですね。

Takato Ezaki

Takato Ezaki小中高の塾講師からエンジニア

投稿者プロフィール

福岡で Web 系のエンジニアをしています。

中高の理科教師免許を取り、起業に 2 年間トライした後エンジニアの道へ入りました。

化学反応の中では Belousov-Zhabotinsky 反応が大好きです。

この著者の最新の記事

関連記事

コメントは利用できません。

募集中!(o゜▽゜)o

エンジャパン
求む、社長!
follow us in feedly

コッチもヨロシク!



最近のネタ!

  1. 最高級牛ハラミ2.2kg

    2019-11-21

    屋外アグレッシ部~秋BBQ~

    2019/11/4(月・祝)に、記念すべき第1回屋外アグレッシ部の活動がありました! 今回は大井ふ…
  2. 2019-11-18

    第1回映画部開催@TOHOシネマズ日比谷 ~映画部始めました❤

    こんにちは😊😊とても久しぶりの投稿となりました、エージェントグロ…
  3. 2019-11-18

    1年半、毎週ブログを書き続けた私の、ブログのタネの探し方

    ちょっとした前置き こんにちは、江嵜です。 定期的にこのブログをご覧になってくださっている方はお…
  4. 2019-11-14

    2019年10月度社員総会&懇親会@Osaka

    2019年10月8日(火)、大阪社員総会&懇親会が開催されました! その様子をお届けします! 社員…
  5. 2019-11-11

    デザイン作成時に仮で画像を置いておきたい時は Placeholder.com を使おう!

    デザインの画像を用意するのが地味に面倒 皆様、デザインはしますか? 例えば、シンプルなギャラリー…
ページ上部へ戻る

当サイトに掲載されているコンテンツ(文書、画像等)は、許可なく複製・転用等する事を禁じます。

「フェアネス方式®」(登録6150741)は、日本国内における株式会社エージェントグローの登録商標です。

当サイトでは最低限必要と考えられる場合において、会社名/サービス名/商品名などを記載している場合があります。
これらはあくまでも説明の必要性に応じて用いているものであり、各社の権利等を侵害を目的とするものではございません。
不適切と考えられる場合には、当社お問い合わせフォームよりご連絡ください。

当サイトでは®や™などの表記を省略させていただいております。