React Component in Vue.js
@_tohashi
自己紹介
Takumi Ohashi
freee
最近は Amazon Kinesis Data Streams と格闘しています
Vue.js の中で React Component を扱いたい
と同僚が言い出したらどうするか
否定から入る
本当に必要なのか
代替手段はないのか
前提条件を疑った方がいい
絶対つらい
というのは念頭に置いた上で考えてみた
どういうアプローチがあるか
どのくらいつらいのか
背景
社内向けのデザインシステムを作っている
その実装であるUIコンポーネントライブラリは React で実装
誰でもガイドラインに沿ったUIを構築できるようになることを目的として開発
必然的に React の使用を強制することになる
特に何か問題が起きているわけではない
が、多様性がない状態は気になる
Vue.js や Angular という選択肢が存在しない
かといって別途実装するコストも現状では払いづらい
Vue.js の中で React Component を扱う
対象は Presentational なコンポーネントで、Vuex の action などを直接触ったりはしない
Styled Component なども一旦考慮しない
code:ReactComponent.js
import React, { useState } from 'react';
export const ReactComponent = ({ value }) => {
return (
<div>
<p>This is React Component.</p>
count: {count}
<button onClick={() => setCount(count + 1)}>++</button>
<p>input value:{value}</p>
</div>
);
};
愚直にやる
Vue Component から React Component を自身の子要素としてレンダリングする
先駆者いた
vuera
Vue in React or React in Vue を実現する
Wrapper Component や HOC を介して使う
code:vuera_hoc.ts
<template>
<div id="app">
<div>
<ReactComponent :value="value" />
<input @input="(e) => value = e.target.value" :value="value" />
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import { ReactComponent } from "react-component";
import { ReactInVue } from "vuera";
@Component({
components: {
ReactComponent: ReactInVue(ReactComponent)
}
})
export default class App extends Vue {
value: string = "aaa";
}
</script>
vuera がやっていること
React Component をラップしたコンテナを作成
コンテナ自体も React Component
親の Vue Component の mounted のタイミングで ReactDOM.render を呼び、コンテナをレンダリング
プロパティの更新時は ref を介してコンテナの setState を呼び出す
vuera つらいところ
React, ReactDOM をバンドルするので asset size が肥大化する
個々に vdom の tree を構築することになるのでリストビューなどで要素が増えるとパフォーマンスがきつい
React.Portal を介して各 Vue Component にマウントする Proxy Component を書けばワンチャン...🤔🤔🤔
React をバンドルしない
React をネイティブのDOM操作に変換するPoCな babel plugin
React のランタイムを含まない(必要最低限にする)ので asset size 減らせる
rawact
アプリケーションで使うのが前提なので ReactDOM.render を実行する関数 renderRawactComponent を作って mounted で呼び出してみる
code:rawact.ts
<template>
<div id="app">
<div>
<div id="rawact" />
<input @input="(e) => value = e.target.value" :value="value" />
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import { renderRawactComponent, RawactComponent } from "rawact-component";
@Component
export default class App extends Vue {
value: string = "aaa";
mounted() {
renderRawactComponent(document.getElementById("rawact"), RawactComponent, {
value: this.value
});
}
}
rawact つらいところ
React のランタイムがないので、vuera のように React Component 内部の setState を呼び出すことができない😇
ので updated とかで都度マウントする必要がある
code:mounted.ts
updated() {
renderRawactComponent(document.getElementById("rawact"), RawactComponent, {
value: this.value
});
}
vdom の利点は死ぬ
当然 state も死ぬ
stateless限定ならワンチャン...🤔🤔🤔
力でねじ伏せる
@babel/parser で React -> Vue に置換
もはや Vue で実装するコストの方が安いのではという話は置いておく
先駆者いた(逆)
Vue -> React
つらそう
Hooks に限定すれば多少は楽?
v3 以降で入るらしい Vue版 Hooks API の PoC
もし React like に書けるようになるなら置換しやすい?
code:VueWithHooks.tsx
import { withHooks, useData } from 'vue-hooks';
const HooksComponent = withHooks((h, { value }) => {
const data = useData({
count: 0
});
return h(
<div>
<p>This is Vue Component with Hooks.</p>
count: {data.count}
<button onClick={() => data.count++}>++</button>
<p>input value:{value}</p>
</div>
);
});
export default HooksComponent;
現在の proposal 見る限りはもうちょっと違うものになりそう
setup の中で呼び出す方式に
React Hooks のように呼び出し順に依存しない
まだまだRFCの議論も続いているので、多分更に変わっていく
まとめ
調べただけでつらくなってきた
vuera (or 似たような仕組)以外の選択肢はなさそう
とはいえプロダクションで使えるかというと...
Parser 書くにしても v3 出てからの方が良いかも
同僚が Vue.js の中で React Component を扱いたいと言い出したら止めよう