はじめに
Vue.jsで開発をしている時に、タイトルみたいなことをしたくなったことが出てきました。
検索すると方法自体は結構出てきたのですが、<script setup>
構文だとどんな感じになるのかだと情報が少なかったので備忘録も兼ねて残しておきたいと思います。
なぜそんなことが必要なのか?
…と言われると若干詰まります。だって使用するのがわかっているのであれば、最初からそのコンポーネントを記述しておいてv-if
なりv-show
なりで制御すればいいだけの話。:is
とかも使えます。
となると用途としてはかなり限定的にはなるのですが、例えば何かしらのライブラリを使用していてそこで動的に生成された要素内にコンポーネントをマウントしたい時でしょうか。
自分もまさにWijmoというライブラリを使用していて、そこで追加された要素に対して共通デザインで使用しているコンポーネントを適用したい!という事があったので実装方法を調査しました。
方法
ということで実際のコードは下記のようになります。(色々簡略化しています)
<script setup>
import { createApp, ref } from 'vue';
import AddComponent from './any-add-component.vue';
// ~~ 色々と省略 ~~
/** 動的なコンポーネントの追加 */
function addComponentDynamically (props = {}) {
const app = createApp(AddComponent, props);
const wrapper = document.createElement('div');
app.mount(wrapper);
document.body.appendChild(wrapper);
}
/** 動的なコンポーネントの削除 */
function removeComponentDynamically (app, wrapper) {
app.unmount();
wrapper.remove();
}
</script>
<!-- 以下略 -->
簡易的に解説していきます。
createApp
でimportしたコンポーネントを第1引数に設定してアプリケーション(今回はapp
)を生成し、その後適当なラッピング要素を作成した後にapp.mount()
でその要素へマウントします。
その後、そのラッピング要素をDOMへ追加することで動的な追加が完了します。(今回はbodyですが、好きな場所へ追加してOKです)
また、createApp
の第2引数に渡すとそのコンポーネントのpropsを設定できます。
コンポーネントを削除したい場合は、生成したアプリケーションをapp.unmount()
でマウント解除した後、不要になったラッピング要素を削除します。
意外と簡単ですね。
実際に使用する際はクラスへ処理をまとめておくなどして、もう少し活用しやすい形に整形しておくとよいです。
追加したコンポーネントでのイベント
上記に応用して、追加したコンポーネントでのイベントを設定することもできます。
<script setup>
import { createApp, ref } from 'vue';
import AnyAddComponent from './any-add-component.vue';
// ~~ 色々と省略 ~~
/** 動的なコンポーネントの追加 */
function addComponentDynamically (props = {}) {
const app = createApp(AnyAddComponent, props);
const wrapper = document.createElement('div');
app.mount(wrapper);
document.body.appendChild(wrapper);
}
/** 動的に追加したコンポーネントのクリックイベント */
function onClickAddComponent () {
console.log('clicked!');
}
addComponentDynamically({ onClick: onClickAddComponent });
// 以下略
上記のように、on
を使用してイベントハンドラを設定すると可能になります。
当然、追加したコンポーネントでのroot elementなど気を付けるべき点はありますが、ある程度汎用的に使えます。
課題
通常の子コンポーネントを設定する方法と違って、下記の点が出来ません。
- 親コンポーネントで設定したリアクティブなデータを渡し、そのデータを変更しても追加したコンポーネントへその値を即時に反映させることが出来ない。(マウントした時点の
props
で固定化されてしまう)
→一応、ref
やreactive
などで宣言したオブジェクトをそのまま渡すことで実現は出来るようだが、何だか間違った方法にしか思えない… - 追加したコンポーネントで設定した
emits
を親で受け取ることが出来ない。
つまり、props
としてv-model
や@{emits}
を無理やり書いても動作しない。
そもそもアプリケーションとしてのインスタンスが異なっているから致し方ないのかもしれません。
ただ、自分が知らないだけかもしれないのでもしこういう方法があるよ!という方は是非お願いします。
余談
基本的に自分はVueで<script setup>
構文を使用しているのですが、調べているとヒットはしてもこの構文での情報が無いことが多々あります。
もちろん記述方法が少し変わっているだけなのでそれが分かっていればどうということは無いのですが、今回のようにちょっと特殊なことをしようとすると上手く変換出来るまで時間が掛かる事があります。
慣れていくしかないですね…
参考
- アプリケーション API | Vue.js
-
Vue.jsで動的にコンポーネントを生成・削除・マウントする方法
↑<script setup>
構文ではないので、そのままは使えないのですが参考にさせていただきました