LoginSignup
15

More than 5 years have passed since last update.

HTML上の要素をReact.jsコンポーネントに置き換えるライブラリを作ったから見てくれ

Last updated at Posted at 2015-05-18

react-provider

github: https://github.com/morisuke/react-provider
npm: https://www.npmjs.com/package/react-provider

推奨コンパイル環境: Browserify + Babelify
動作環境: IE8+

Q. なにこれ?

A. HTML要素をReact.jsコンポーネントに置き換えるよ

app.js
// ES6 Class Component
class Component extends React.Component {
    render() {
        return (
            <div className={this.props.className}>
                {this.props.children}
            </div>
        );
    }
}

// Browserifyで読み込み
var provider = require('react-provider');

// selector: Reactコンポーネント
provider({
  '[data-component=component]': <Component />
});
index.html(実行前)
<body>

    <div data-component="component" class="wrapper">
        wrapper-text
        <div data-component="component" class="inner">inner-text</div>
    </div>

    <script src="./app.js" type="text/javascript">
</body>
index.html(実行後)
<body>

    <component>
        <div class="wrapper" data-reactid=".0">
            wrapper-text
            <component>
                <div class="inner" data-reactid=".1">inner-text</div>
            </component>
        </div>
    </component>

    <script src="./app.js" type="text/javascript">
</body>

Q. どうしておとなしくVue.jsにしなかったんだ

A. IE8の闇に呑まれた。

こんな人の役に立つかも

  • Vue.js大好きなんだけどIE8に対応しなければいけなくなった → なんとなく同じように使えるよ
  • Reactが好きでたまらない他のものは触りたくない、だがしかし作るものはSPAではない → 頭を冷やせ

開発の動機

React.jsはReact.renderを叩くことで画面上にコンポーネントを描画します。
しかしReact自体がSPAを構築する前提で組まれていることもあり、React.renderの第二引数に指定する描画対象要素は単独のnodeでしか渡せない仕様になっています。(nodeListを渡せない)

しかしPHPのビューテンプレートと併用する場合、どうしても複数のコンポーネントを同時に画面に描画したくなります。(フォームヘルパの機能を拡張したい場合など)
それならnodeListをquerySelectorAllで拾って回しながら描画し続ければ良くない? という発想から生まれたのがこのライブラリです。

実装の方針

やりたいことにほぼ合致しているreact-mountというライブラリがあったのですが、ほぼ正規表現による実装であるため手を加えにくく、またIE10で何故か動かなかったので、自分でイチから実装することにしました。
react-mountはJSXをHTML上に書けるようにしていましたが、こちらはあくまでロジックをReactコンポーネントに隠蔽したい考えであった為、this.props.childrenに子要素をReactElementで渡すのみに留めています。

コンポーネントをネストさせて記述した場合に、refsにツリー構造が格納されるようにしようとも思ったのですが、HTML上の記述とコンポーネントの実装に強い依存関係が生まれるため、途中でやめました。
なのでコンポーネント間の通信にはシングルトン化したEventEmitterなどを利用します。
ここらへんはVue.jsの$on / $broadcastのイメージで組んでいくとわかりやすいと思います。

無駄に力入れたところ

画面上からpropsに値やReactElementを渡せるようにしました。
data-props もしくは data-props-html 属性を指定した要素の中身をコンポーネントに吸い上げます。

index.html
<body>

    <component class="wrapper">

        <div data-props="element">
            textnodeです
        </div>

        <div data-props-html="element">
            <span class="style">htmlです</span>
        </div>

    </component>

  <script src="./app.js" type="text/javascript">
</body>

こうするとthis.props.elementに、textnodeです が、
this.props.html.elementに、<span class="style">htmlです</span> が格納されます。

component.js
import React from 'react';
class Component extends React.Component {
  render() {
    return (
        <section>
            {this.props.html.element}
        </section>
        );
  }
}

this.props.html に格納されるHTMLはReactElementの配列に変換されているので、renderメソッド内で{this.props.html.element}のように展開することができます。
サーバサイドから出力した値やDom構造をpropsに受け止めて使いまわすと色々と楽なので是非活用してみてください。

最後に

プルリクやissue、お待ちしております。
土日くらいしか直せないんですけど出来るだけ頑張りますよ。

あと@cognitomさんの「3分でできるnpmモジュール」を読んで初めてnpmを自作してみたのですが、とても楽しかったです。
npm installで自分の作ったモジュールが落ちてくる爽快感、たまりませんよ。
皆さんも是非挑戦してみてはいかがでしょうか。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15