記事の目的
Vue.jsコンポーネントの実際の開発で得た経験のまとめ。
開発の目的
自社で開発しているタブレットアプリの設定項目を1画面のWeb Formで登録更新する機能があるのですが、これが膨大な数に及ぶためFormのHTMLテンプレートだけで300kbを越えており、人力保守では限界に達しているためFormの自動生成をVue.js化することにしました。
どうせなら専用機能としてではなくFormの自動生成部分をOSS化して再利用できたらいいなと思い、作り始めたわけですが。いや、大変でした。
実際に開発したライブラリはこちらです。
- vue-json-form
課題
template上でHTML上のデザインと分離できない
通常、サーバーサイドでHTMLを生成する系のHTMLテンプレートの場合、固定部分のHTMLの一部にプログラムで動的に変化するところを部分的に挿入する形で作り込むと思います。
ところがVue.jsでは通常のHTMLタグの中にVue.jsで作ったコンポーネントのタグを直接埋め込む方式で、bootstrap等画面デザインに関わるHTMLタグと完全に混在した形での定義となってしまい、本質的にコンポーネントタグとHTMLタグが分離不可能となっています。
この問題はすぐには解決不能だと感じたため、OSSのパッケージとしてはForm画面全体のvueファイルは再利用しないことにしました。
親から見て子のコンポーネント上で指定する孫コンポーネントを動的変更することは可能
Vueでは汎用Vueタグとして <component> というタグが使用可能であり、<component :is="someComponent"/>と記述することで例えばsomeComponent='FoodPanel'の場合、実際には<FoodPanel /> タグを記述したのと同じレンダリングが行われます。
当然ながらレンダリングする際に子のコンポーネントが孫のコンポーネントを知っている(登録済み)必要があるので、親が子コンポーネントのcomponentsを書き換える必要があります。例えばこういう実装内容になるでしょう。
import MainForm from './components/MainForm.vue'
import RadioType from './components/InputTypes/RadioType.vue'
export default {
name: 'App',
components: {
MainForm,
},
setup() {
// 子のcomponentsを親が書き換える
MainForm.components = {
RadioType,
}
...
}
}
ただし欠点があって、一応全体としては動くのですが、yarn serve でモニターさせている場合、稼働中に MainForm を書き換えると MainForm が再初期化され、孫コンポーネントを知らない状態に戻ってしまうため稼働不能になります。仕方がないので毎回ブラウザをリロードしています。
親、子、孫と3世代のコンポーネントを定義した場合、子のコンポーネントの設計が困難
Vueでは v-modelの形で親コンポーネントのcontext(コンポーネントのプロパティまたはその一部)を引き渡します。
Vue2ではvalue、Vue3ではmodelValueという名称のプロパティ(props)です。
親子構築は特に問題なく実装できるのですが、子から孫に再びcontextを引き渡す場合、
この modelValue の一部を渡そうとするとエラーになってしまいました。
やむをえず、バッドノウハウではありますが data()実行時に modelValueのクローン(myvalue)を作成し、myvalueを孫の v-model として引き渡す形で実装せざるを得ませんでした。
当然ながら modelValue と myvalue は本来一体のものですから、子コンポーネントの責任で値の変更を同期する必要があります。
コンポーネントのロジック部分の外部化が困難
従来の Options API方式でコンポーネントを定義する限りでは、どのみちロジック部分の外部化はほぼ不可能です。
Vue3では新たに Composition APIによるコンポーネントの定義が可能になっており、こちらはPlain JavaScriptでロジック設計が可能になります。
コンポーネントの動的な部分を全部 setup() での定義に再実装したあと、外部の jsファイルに移動させてimportさせたところ、すんなりと移植が完了しました。ちょっと感動した。
終わりに
以上、Vue3 のコンポーネントを OSS化する際にぶつかったいくつかの課題と解決法についてまとめました。
vue-json-form については今後も開発を続けていきますので、また新たな課題を克服したときには記事を更新したいと思います。