Vue3 の JSX と vue ファイル
- カテゴリ:
- JavaScript
- コメント数:
- Comments: 0
◆ JSX を使うなら vue ファイルの必要無い気がする
◆ style タグが使えない問題があったけど CSS Modules や @scope でなんとかなりそう
◆ JSX の情報は思ったより少なくてそこまで使われてないのかも
◆ style タグが使えない問題があったけど CSS Modules や @scope でなんとかなりそう
◆ JSX の情報は思ったより少なくてそこまで使われてないのかも
個人的に Vue のテンプレートが好みでないので テンプレートを使わない方法で使っています
ほぼ JSX と同じですが 一応ビルド不要で使える htm です
Vue3 を htm で使う
1 つの HTML ファイルに全部詰め込むようなシンプルなものだと特に問題もなかったのですが 少し大きめのものになったので ちゃんとビルドしようかと思いました
そこで問題になったのが vue ファイルです
vue ファイルの中身ってこんな感じです (公式サイト Playground の初期状態)
JSX を使うなら template タグはなくなります
script タグだけなら jsx ファイルでもいいと思います
JSX を使って書くのなら このほうが不要な script タグがなくスッキリしていて好きです
ですが あまりこの形式は多くないみたいです
たしかに style タグは特殊な扱いになって vue ファイルのビルド時にスコープをつけるために変換されたりします
vue ファイルを使わず jsx ファイルから単純に CSS を読み込むだけでは スコープが使えません
ということもあって jsx ファイルを使ってるサンプルは基本グローバルなスタイルを 1 つ用意してる感じでした
[App.jsx]
[App.module.css]
Vite なら .module.css という拡張子にすると自動で CSS Modules として扱ってくれるので簡単です
ただ CSS Modules ってクラス名を一意になるよう変換してくれるだけなので 制限も多いです
例えば p タグ全部といったことができません
「.foo p」 としても p はそのコンポーネントに限らず 子コンポーネント内もすべて対象になります
タグ名全部はあまりないかもですが 属性セレクタを使いたいときなんかは不便でした
そういう問題があるせいで ここ数年は CSS Modules をやめて CSS-in-JS してるくらいです
これがあると有効な範囲をセレクタで指定できます
これを使うとタグ名をセレクタで使っても スコープ内でしか効果がないので CSS Modules のような制限を受けません
[App.jsx]
[App.css]
みたいにして 各コンポーネントのトップレベルに component-root というクラス名をつけておけば App コンポーネント内で Component1 などのコンポーネントを使ってもそこはスタイルの範囲外にできます
ただマルチルートの場合はすべてのルートに component-root をつけないといけないので少し面倒です
一見良さそうなのですが スロットを使う場合に問題があります
CSS Modules を使う場合は スロットとして渡す要素にも親側でクラスをつけているので親側のスタイルが有効になります
これは CustomElements と ShadowDOM を使う場合と同じ動きです
それに対して @scope の場合は 実際のツリー上の範囲がスコープになるので スロット機能で渡した部分は子コンポーネントのスタイルが適用されます
スロットとして渡す要素にも component-root とコンポーネント名のクラスをつければ対処はできますが コンポーネントというわけではないのに これらを設定するのはなにか違う感があります
それにスロットを受け取る側で追加のプロパティを渡す仕組みもあります
そういうことまで考えると複雑になります
スロットのことは使うときにどうしたいか次第なところもあるのであまり気にしないことにします
という関数を作っておいて
という感じで使います
CSS のインポートが古い assert 構文ですが まだ新しい with が動かないので仕方ないです
以下がコンポーネントのコードです
[App.js]
[App.css]
[Component1.js]
[Component1.css]
[Component2.js]
実際に動くページです
https://nexpr.gitlab.io/public-pages/vue3-no-sfc-scoped-style/
公式サイトでもシンプルな説明だけです
Vue のテンプレート構文は初心者やライトユーザーが主で 上級者になるほど自由度が高い JSX にいくものなのかなと思ってましたが そうでもないようですね
簡単なところで マルチルートを JSX で実現するだけでも困りました
結果としては React と同じように <></> や Fragment コンポーネントを使えばいいです
しかしドキュメント中には記載がないのですよね
h 関数を使う方法ではトップレベルを配列にすればいいとありますが JSX の説明では記載されていません
Vue3 と Fragment でググって出てくるページはマイグレーションガイドで template タグを使った方法しか書いていません
JSX を使うなら React と同じで <></> や Fragment だろうと察することはできるのですが API リファレンスで Fragment を探しても出てこないです
それなら <></> かなと思って Stackblitz とか Codepen で試してみると なぜか React が見つからないとエラーが出たりしました
最終的に Vite を使った環境では <></> で問題なく動いて 変換されたコードを見ると Fragment というコンポーネントが使われていました
Fragment は API リファレンスには記載がないだけで vue がエクスポートするものです
これを直接使っても動作しました
隠し機能があったり こういう部分がドキュメントになかったり JSX を使うのは思ったより大変なのかもしれないです
ほぼ JSX と同じですが 一応ビルド不要で使える htm です
Vue3 を htm で使う
1 つの HTML ファイルに全部詰め込むようなシンプルなものだと特に問題もなかったのですが 少し大きめのものになったので ちゃんとビルドしようかと思いました
そこで問題になったのが vue ファイルです
vue ファイル
テンプレートを書くならこれでいいのですが JSX を使う場合に vue ファイルっているのでしょうか?vue ファイルの中身ってこんな感じです (公式サイト Playground の初期状態)
<script setup>
import { ref } from "vue"
const msg = ref("Hello World!")
</script>
<template>
<h1>{{ msg }}</h1>
<input v-model="msg">
</template>
JSX を使うなら template タグはなくなります
script タグだけなら jsx ファイルでもいいと思います
jsx ファイル
import { ref } from "vue"
export default {
setup() {
const msg = ref("Hello World!")
return () => {
return (
<>
<h1>{msg.value}</h1>
<input
value={msg.value}
onInput={(event) => msg.value = event.target.value}
/>
</>
)
}
}
}
JSX を使って書くのなら このほうが不要な script タグがなくスッキリしていて好きです
ですが あまりこの形式は多くないみたいです
jsx 形式の問題
jsx 形式でなにか問題あるかなと探すと style タグが書けないようでしたたしかに style タグは特殊な扱いになって vue ファイルのビルド時にスコープをつけるために変換されたりします
vue ファイルを使わず jsx ファイルから単純に CSS を読み込むだけでは スコープが使えません
ということもあって jsx ファイルを使ってるサンプルは基本グローバルなスタイルを 1 つ用意してる感じでした
CSS Modules
でもスコープなら CSS Modules を使えばうまく動きそうです[App.jsx]
import styles from "./App.module.css"
export default {
setup() {
return () => {
return (
<div class={styles.root}>
<h1 class={styles.header}>title</h1>
</div>
)
}
}
}
[App.module.css]
.root {
padding: 10px;
}
.header {
font-size: 2rem;
}
Vite なら .module.css という拡張子にすると自動で CSS Modules として扱ってくれるので簡単です
ただ CSS Modules ってクラス名を一意になるよう変換してくれるだけなので 制限も多いです
例えば p タグ全部といったことができません
「.foo p」 としても p はそのコンポーネントに限らず 子コンポーネント内もすべて対象になります
タグ名全部はあまりないかもですが 属性セレクタを使いたいときなんかは不便でした
そういう問題があるせいで ここ数年は CSS Modules をやめて CSS-in-JS してるくらいです
@scope を使う
最近は @scope という CSS 機能が標準で使えますこれがあると有効な範囲をセレクタで指定できます
これを使うとタグ名をセレクタで使っても スコープ内でしか効果がないので CSS Modules のような制限を受けません
[App.jsx]
import "./App.css"
export default {
setup() {
return () => {
return (
<div class="component-root app">
<h1>title</h1>
</div>
)
}
}
}
[App.css]
@scope(.app) to (.component-root) {
h1 {
color: red;
}
}
みたいにして 各コンポーネントのトップレベルに component-root というクラス名をつけておけば App コンポーネント内で Component1 などのコンポーネントを使ってもそこはスタイルの範囲外にできます
ただマルチルートの場合はすべてのルートに component-root をつけないといけないので少し面倒です
一見良さそうなのですが スロットを使う場合に問題があります
CSS Modules を使う場合は スロットとして渡す要素にも親側でクラスをつけているので親側のスタイルが有効になります
これは CustomElements と ShadowDOM を使う場合と同じ動きです
それに対して @scope の場合は 実際のツリー上の範囲がスコープになるので スロット機能で渡した部分は子コンポーネントのスタイルが適用されます
スロットとして渡す要素にも component-root とコンポーネント名のクラスをつければ対処はできますが コンポーネントというわけではないのに これらを設定するのはなにか違う感があります
それにスロットを受け取る側で追加のプロパティを渡す仕組みもあります
そういうことまで考えると複雑になります
スロットのことは使うときにどうしたいか次第なところもあるのであまり気にしないことにします
ビルド無し
@scope を使う方法で実際に動く例を作ってみたのですが ビルド無しでそのまま動かしたかったので JSX を htm にして CSS のインポートを CSS Modules (ウェブ標準機能の方) にしましたexport const addCSS = cssss => {
document.adoptedStyleSheets.push(cssss)
}
という関数を作っておいて
import css from "./index.css" assert { type: "css" }
addCSS(css)
という感じで使います
CSS のインポートが古い assert 構文ですが まだ新しい with が動かないので仕方ないです
以下がコンポーネントのコードです
[App.js]
import { html, addCSS } from "./utils.js"
import Component1 from "./Component1.js"
import Component2 from "./Component2.js"
import css from "./App.css" assert { type: "css" }
addCSS(css)
const App = {
components: {
Component1,
Component2,
},
setup() {
return () => html`
<div class="component-root c-app">
App:
<p>ここは赤色</p>
<${Component1}></>
<${Component2}></>
</div>
`
},
}
export default App
[App.css]
scope (.c-app) to (.component-root) {
p {
color: red;
}
}
[Component1.js]
import { html, addCSS } from "./utils.js"
import css from "./Component1.css" assert { type: "css" }
addCSS(css)
const Component1 = {
setup(props, { slots }) {
return () => html`
<div class="component-root c-component1">
Component1:
<p class="blue">ここは青色</p>
<p>ここは黒色 (App の p タグのスタイルの影響は受けない)</p>
</div>
`
},
}
export default Component1
[Component1.css]
scope (.c-component1) to (.component-root) {
.blue {
color: blue;
}
}
[Component2.js]
import { html, addCSS } from "./utils.js"
const Component2 = {
setup(props, { slots }) {
return () => html`
<div class="component-root c-component2">
Component2:
<p class="blue">
このコンポーネントにスタイルは指定していないので
blue クラスだけど黒色
</p>
</div>
`
},
}
export default Component2
実際に動くページです
https://nexpr.gitlab.io/public-pages/vue3-no-sfc-scoped-style/
JSX はあまり使われてない?
調べていて思ったのが 思ったより情報が出てこないです公式サイトでもシンプルな説明だけです
Vue のテンプレート構文は初心者やライトユーザーが主で 上級者になるほど自由度が高い JSX にいくものなのかなと思ってましたが そうでもないようですね
簡単なところで マルチルートを JSX で実現するだけでも困りました
結果としては React と同じように <></> や Fragment コンポーネントを使えばいいです
しかしドキュメント中には記載がないのですよね
h 関数を使う方法ではトップレベルを配列にすればいいとありますが JSX の説明では記載されていません
Vue3 と Fragment でググって出てくるページはマイグレーションガイドで template タグを使った方法しか書いていません
JSX を使うなら React と同じで <></> や Fragment だろうと察することはできるのですが API リファレンスで Fragment を探しても出てこないです
それなら <></> かなと思って Stackblitz とか Codepen で試してみると なぜか React が見つからないとエラーが出たりしました
最終的に Vite を使った環境では <></> で問題なく動いて 変換されたコードを見ると Fragment というコンポーネントが使われていました
Fragment は API リファレンスには記載がないだけで vue がエクスポートするものです
これを直接使っても動作しました
隠し機能があったり こういう部分がドキュメントになかったり JSX を使うのは思ったより大変なのかもしれないです