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