ReactでサブツリーからDOMをレンダリングする

  • 20
    Like
  • 0
    Comment
More than 1 year has passed since last update.

GreasemonkeyやChrome Extensionsなんかで既存ページを書き換えるアプリを作っているとき、
ページの複数箇所にDOMを挿入したいことがある。
これをReactでやりたい。

<body>
  <nav>
    <div>
       <div>
          <!-- ここに挿入したい -->
       </div>
    </div>
  </nav>
  <article>
    <div>
       <div>
          <!-- ここにも挿入したい -->
       </div>
    </div>
  </article>
</body>

ReactDOM.renderを二回やってもできるが、Reactツリー間での連携が面倒。

// 現仕様ではコンテナ内の既存要素は削除されるため、要コンテナ生成
const subRoot1 = document.createElement('div');
const subRoot2 = document.createElement('div');

document.querySelector('.container.of.subtree1').appendChild(subRoot1);
document.querySelector('.container.of.subtree2').appendChild(subRoot2);

ReactDOM.render(<SubTree1 {...dataForSubTree1} />, subRoot1);
ReactDOM.render(<SubTree2 {...dataForSubTree2} />, subRoot2);

やっぱり全体のルートが欲しい。
ルートはレンダリングせず、サブツリーを好きなとこにレンダリングするReactコンポーネントを作れないか?

...

で、やってみたら簡単にできた。Reactすごい!
以下ES6版の例。他でも似たようにできるはず。

class MainTree extends React.Component {
  constructor(props, state) {
    super(props, state);

    this.subRoot1 = document.createElement('div');
    this.subRoot2 = document.createElement('div');

    document.querySelector('.container.of.subtree1').appendChild(this.subRoot1);
    document.querySelector('.container.of.subtree2').appendChild(this.subRoot2);

    this.state = { /* ... */ };
  }

  render() {
    return null; // renderしない
  }

  componentDidMount() {
    // マウント時にサブツリーをrenderする
    ReactDOM.render(<SubTree1 {...this.state} />, this.subRoot1);
    ReactDOM.render(<SubTree2 {...this.state} />, this.subRoot2);
  }

  componentDidUpdate() {
    // 更新時にはサブツリーも更新
    ReactDOM.render(<SubTree1 {...this.state} />, this.subRoot1);
    ReactDOM.render(<SubTree2 {...this.state} />, this.subRoot2);
  }

  componentWillUnmount() {
    // アンマウント時はサブツリー除去
    ReactDOM.unmountComponentAtNode(this.subRoot1);
    ReactDOM.unmountComponentAtNode(this.subRoot2);
  }

  componentWillReceiveProps(nextProps) {
    this.setState({ /* ... */ })
  }
}

const mainRoot = document.createElement('div');
ReactDOM.render(<MainTree {...dataForMainTree} />, mainRoot);

これが、

  • マウント時にサブツリーをrenderしてOK
    • ちなみにrender()内でReactDOM.renderしたら、componentDidUpdateでやれと警告される
  • ReactDOM.render複数回実行してもいい感じに更新してくれる
  • ルートのコンテナdivは文書内に挿入しなくても動いてくれる (警告出ないし別にいいよね?)

というわけでうまく動く。

これで実際のツリー構造に依存せず自由自在にReact使える。便利!