Vue Fes Japan行って、知らなかったこと調べてみた
はじめに
こんにちは、きむです!
今回Vue Fes Japan2024に一人で行ってきました!!(誘った人みんな用事ありました😭)
一人で楽しめるかな...と不安だったのですが、めちゃめちゃ楽しかったです!知らない概念や今後活用できそうなことがたくさんあったので、ちょっとだけ深ぼってアウトプットしようと思います!(間違ってるところあったら教えてください🙇)
初めて知ったこと、今後活用できそうなこと
天元突破グレンラガン(Tengen Toppa Gurren Lagann)
はい、いきなりなんだって感じですよね。英語分かる人は会場でくすっと笑ってました。(僕は何に笑っているのかわからなかったです)
こちら、Vue3.5のコードネームらしいです。と、いうことでVue3.5のアップデート内容について簡単にまとめていきます!詳しく知りたい方は公式読んでみてください。
1. リアクティビティシステムの最適化リアクティビティシステムが最適化されたことによって
- メモリ使用率56%削減
- 大規模かつネストが深いリアクティブ配列に対するリアクティビティの追跡を最適化し、最大10倍速くなる
という効果があるそう。単純にバージョンアップデートすることでパフォーマンスの改善が見込めそうです。
2. useTemplateRef
Vue3.5以前はtemplateを参照する際に以下のように書く必要がありました。
<script setup>
import { ref, onMounted } from 'vue'
// 要素の参照を保持する ref を宣言します。
const input = ref(null)
onMounted(() => {
input.value.focus()
})
</script>
<template>
<input ref="input" />
</template>
これが、useTemplateRef
を使用すると以下のように書けます。
<script setup>
import { useTemplateRef, onMounted } from 'vue'
const input = useTemplateRef('my-input')
onMounted(() => {
input.value.focus()
})
</script>
<template>
<input ref="my-input" />
</template>
いつ使うんだ?て感じだったのですが、composableに切り出す際に使用できるみたいです!こちらの記事に分かりやすい説明がありました!!
3. Reactive Props Destructureの安定化
<script setup> 内で defineProps から分割代入された変数が自動的にリアクティブになる機能が追加されました。どういうことか、Vue3.5以前は、デフォルト値をpropsに設定する際はwithDefaults
を使用して、以下のように書く必要がありました。
const props = withDefaults(
defineProps<{
count?: number
msg?: string
}>(),
{
count: 0,
msg: 'hello'
}
)
// アクセスする場合はprops.countと書く
watch(() => props.count ...)
countというプロパティには0、msgにはhelloというデフォルト値を設定していますね。これがVue3.5以降は以下のように書けるようになりました。
const { count = 0, msg = 'hello' } = defineProps<{
count?: number
msg?: string
}>()
// countでアクセスできる(直接的)
watch(() => count ...)
propsを分割代入することができるようになり、かなり可読性が上がりました。
vueとsvelteランタイム比較
今回一番楽しみにしていたセッションでした!が、自分の知識が足りず半分くらいしか理解できなかった😭理解するには時間がかかりそうなので、また後日時間がある時にじっくりと記事をかけたらいいなと思ってます。
ちなみに、Vueのランタイムを理解するにはchibivueがおすすめなんだそうです!ランタイムだけでなく、仮想DOM、リアクティビティシステム、コンパイラ等が内部的にどのような処理が行われているのか学べるのでやってみようと思います。
DOMベースXSS
そもそもXSSとはWebアプリケーションの脆弱性の一つで、ユーザ入力に対して動的にページ生成するアプリ(例えば掲示板とか)において、悪意のあるスクリプトを挿入される攻撃手法のこと。と理解していたのですが...どうやらXSSには3つの種類があるみたいで、上記のXSSはPersistent XSS(持続型XSS)に当たるそう。
DOMベースXSSとは、サイト利⽤者のブラウザ上(クライアントサイド)で、JavaScriptがDOMを介してHTMLを操作する際に、意図しないスクリプトを出⼒してしまうXSSです。反射型XSSはURLのパラメーターに悪意のあるスクリプトが埋め込まれ、持続型XSSはDBに悪意のあるスクリプトが格納されるため、どちらもサーバーを介した攻撃手法ですが、DOMベースXSSは必ずしもサーバーを介す必要がないという点で異なります。
Vue.jsは、デフォルトでバインディングを行う際に自動的にHTMLエスケープを行うため、基本的なテキストバインディングでDOMベースXSSは発生しません。
<div>{{ userInput }}</div>
ただし、v-htmlディレクティブは、指定されたHTMLをそのままDOMに挿入するためXSSが起きる危険性があります。これは公式にも記載されています。
これに対して、Trusted Types APIという解決策を提示していました。Trusted Types APIとはXSSを攻撃を防ぐためのブラウザ機能で、XSS脆弱性の原因となる危険な操作(element.innerHTML
/location.href
/script.src
/script.textContent
など)を制限します。AngularやLitでは既にサポートされているんだそう。
コンパイラマクロ
コンパイラマクロとは、define○○
で表現されるもので、具体例としてdefineProps
, difineEmits
, difineModel
などが挙げられます。これらのコンパイラマクロは<script setup>
内でのみ使用可能で、importせずに使用することができます。確かにdefinePropsはimportせずに使用できるのなんでだろうと思った記憶はあります笑
このコンパイラマクロはコンパイル時に JavaScript に展開される擬似的な関数であり、内部的にはVueコンパイラが勝手に名前を探してjsに変換してくれます。詳細については以下の記事にわかりやく記載されていたので見てみてください!!
パフォーマンスチューニング
Vue3の一歩踏み込んだパフォーマンスチューニングというセッションの中で気になったものをいくつか調べてみます!発表していただいた資料をアップロードしてくれていました🙇
sharrowRef/triggerRef
sharrowRef
とは、ref
の浅いバージョンです。
const shallow = shallowRef({
greet: 'Hello, world'
})
watchEffect(() => {
console.log(shallow.value.greet)
})
// 変更をトリガーし、ログが出力される
shallow.value = { greet: 'Hello, universe' }
// 変更がトリガーされず、watchEffectが発動しないため、ログが出力されない
shallow.value.greet = 'Hello, universe'
refは本来ネストした層までリアクティブに監視していますが、shallowRefを使用すると、.valueへのアクセスだけを監視します。こうすることで、大きなデータ構造の場合にパフォーマンスの最適化することができます。
triggerRef
は、hallowRef
を使用する際に、リアクティブシステムが監視しない内部プロパティの変更を手動でトリガーするために使用します。これにより、通常反応しない変更を強制的に検知させることができます。以下は具体的な使用例です。
const shallow = shallowRef({
greet: 'Hello, world'
})
watchEffect(() => {
console.log(shallow.value.greet)
})
// 変更がトリガーされず、watchEffectが発動しないため、ログが出力されない
shallow.value.greet = 'Hello, universe'
// 強制的にsharrowRefをトリガーするため、'Hello, universe'と出力
triggerRef(shallow)
v-memo
v-memoディレクティブは、指定したすべての値が最後のレンダリング結果と同じであれば、サブツリー全体の再レンダリングをスキップします。
<div v-memo="[valueA, valueB]">
...
</div>
上記は具体例です。コンポーネントの再レンダリング時に、 valueA と valueB の両方が同じであれば、この <div> とその子のすべての更新はスキップされます。また、v-memo="[]"
とすることで、初回レンダリング以降のレンダリングをスキップすることができます。(これがv-once
でも実現可能)
基本的にはv-for
と併用することが一般的です。1000件以上の項目を持つ配列をv-forで回したときに効果があると公式に記載されています。
<div v-for="item in list" :key="item.id" v-memo="[item.id === selected]">
<p>ID: {{ item.id }} - selected: {{ item.id === selected }}</p>
<p>...more child nodes</p>
</div>
このように書くことで、item.id === selected
がtrueの要素はレンダリングがスキップされ、先程選択中だった要素と、新たに選択された要素の2つのみが更新されるということになります。
onWatchCleanup
vue3.5から新たに登場したonWatchCleanup。ウォッチャー(watch, watchEffectどちらでも使用可)が再実行される直前に実行されるクリーンアップ関数を登録します。ウォッチャーが再実行されるたびに不要な処理を残さず、クリーンな状態で次の実行に進むことが可能です。具体的な使用用途は以下のような場合です。
import { watch, onWatcherCleanup } from 'vue'
watch(id, (newId) => {
const controller = new AbortController()
fetch(`/api/${newId}`, { signal: controller.signal }).then(() => {
// コールバックのロジック
})
onWatcherCleanup(() => {
// 古くなったリクエストを中止する
controller.abort()
})
})
- id が変更されると、watch内のコールバックが実行される。(AbortControllerを使ってリクエストをキャンセル可能な状態にする)
- 1のリクエストが完了する前に idが再び変更されると、watchのコールバックが再実行される
- onWatchCleanupが呼ばれ、1の古くなったリクエストをキャンセルする
onWatchCleanup
を使用することで、未完了の古いリクエストを無駄に処理し続けることなく、不要なリクエストやリソースの無駄を防ぐことができます。また、3.5より前のバージョンでは、onCleanup関数が用意されています。これは、watch関数の第三引数として、またはwatchEffect関数の第一引数として受け取ることができます。実装に関しては以下の公式ドキュメントを見てみてください。
終わりに
改めてVue Fes Japanとっても楽しかったです!!
初めてのカンファレンス参加だったのですが、知らないことたくさんあって、とてもいい刺激でした。また来年も参加したいし、ほかの言語のカンファレンスにも参加してみたいなと思いました!
chibivueやりたい!
参考にさせていただいたレポート達
Discussion