Open8

Vue3のcomputedパフォーマンス考察

miyanokomiyamiyanokomiya

こういう状況のときに、

const map = ref<{ [key:string]: number }>({ a: 1, b: 2 })
const keys = computed(() => Object.keys(map.value))

こうすると、keysは再評価される。

map.value = { a: 1, b: 2 }

こうすると、keysは再評価されない。

map.value.b = 3
miyanokomiyamiyanokomiya

hooksでもこう書いたら結果は同様。

const keys = useMemo(() => Object.keys(map), [map])

こう書いたら再評価は回避できる?できる気がする
(実際は算出関数がもっと複雑かつkeysにだけ依存しているとして)

const keys = useMemo(() => Object.keys(map), [Object.keys(map)])
miyanokomiyamiyanokomiya

compositionで同様な再評価タイミングを実現させるにはキャッシュ用の状態をもってwatchを使うしかないか?
中身に興味はないけどキー一覧を使って重い算出をしたいような状況。

const keys = ref<string[]>([])
watch(() => Object.keys(map.value), () => {
  return Object.keys(map.value)
})
miyanokomiyamiyanokomiya

vue3って書いたけど多分このあたりの挙動はvue2から変わってないと思われる。

miyanokomiyamiyanokomiya

computedに無意味なデフォルト値を返させたい場合、primitiveでない場合は常にundefinedを返すようにしたほうがよい。

if (~~) return {}という風にその場で空objektを返してしまうと、そのcomputedはdirtyと見なされて後続がすべて再評価されていく。
undefined判定が増えてしまうがそれを補ってパフォーマンスが向上する。

miyanokomiyamiyanokomiya

https://zenn.dev/miyanokomiya/scraps/270dcfe3ef2a9c#comment-19cd10eba314a8

このwatchの書き方ではObject.keys(map.value)に変化がなくてもコールバックは実行されてしまうっぽかった。Object.keys(map.value).sort().join(',')とかにしてもだめ。
評価の関数はcomputedと同じく参照の更新を見ているのかもしれない(戻り値はcallbackへ渡すために必要なだけか?)。

onBeforeUpdateを使って自力で値を評価しないとだめそう。

let keys = ''
onBeforeUpdate(() => {
  const nextKeys = Object.keys(map.value).sort().join(',')
  if (keys !== nextKeys) {
    ~~ callback ~~
    keys = nextKeys
  }
})

onBeforeUpdateの中でref等を変更しても再度onBeforeUpdateが実行されることはないっぽい。

miyanokomiyamiyanokomiya

provideinjectを使うと、それを参照しない中間コンポーネントの再評価をスキップできる。
propsで受け渡していくと中間コンポーネントも再評価される。

miyanokomiyamiyanokomiya

v-memoという新たなdirectiveが3.2から導入された。
<template>内でしか使えないがProxy式ではなく値で評価してくれる。