自主的20%るぅる

各々が自主的に好き勝手書くゆるふわ会社ブログ

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

Let’s share this article!

{ 関連記事 }

{ この記事を書いた人 }

Takato Ezaki
takato_ezaki
記事一覧