Nuxt Composition APIでuseFetchとcomputedを組合わて使っていたらトラブルが。。。。
使っている@nuxtjs/composition-apiのバージョンについて
こんな感じ
@nuxtjs/composition-api": "0.22.0"
何が起きていたのか?
https://codesandbox.io/s/vigilant-gould-45vg4?file=/pages/posts/_id.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
import {
defineComponent,
useFetch,
useContext,
ref,
computed,
} from "@nuxtjs/composition-api";
import Author from "~/components/Author.vue";
export default defineComponent({
components: {
Author,
},
setup() {
const post = ref();
const { $http, params } = useContext();
const computedTitle = computed(() => {
if (post.value) {
return `Computed: ${post.value.title}`;
}
return "Computed: ...";
});
useFetch(async () => {
post.value = await $http.$get(
`https://jsonplaceholder.typicode.com/posts/${params.value.id}`
);
});
return { post, computedTitle };
},
});
|
上記のようなcomputedとuseFetchを使う実装はは以下のような警告が出てしまう。
Write operation failed: computed value is readonly
何が問題なのか?
問題の箇所のコードを確認してみるとuseFetchのonBeforeMountでsetされてしまっているのでダメらしい。
https://github.com/nuxt-community/composition-api/blob/c394e35/src/composables/fetch.ts#L318-L332
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
onBeforeMount(() => {
// Merge data
for (const key in data) {
try {
if (key in vm && typeof vm[key as keyof typeof vm] === 'function') {
continue
}
set(vm, key, data[key])
} catch (e) {
if (process.env.NODE_ENV === 'development')
// eslint-disable-next-line
console.warn(`Could not hydrate ${key}.`)
}
}
})
|
この問題は1年近く放置されているようです…
どう解決するのか?
いくつか解決策あるのかなと思います。
reactiveを使う
一番既存のコードに修正方法としてはreactiveとかでラップしちゃうのが良いかなと思いました。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
import {
defineComponent,
useFetch,
useContext,
ref,
computed,
reactive,
} from "@nuxtjs/composition-api";
import Author from "~/components/Author.vue";
export default defineComponent({
components: {
Author,
},
setup() {
const post = ref();
const { $http, params } = useContext();
const title = computed(() => {
if (post.value) {
return `Computed: ${post.value.title}`;
}
return "Computed: ...";
});
useFetch(async () => {
post.value = await $http.$get(
`https://jsonplaceholder.typicode.com/posts/${params.value.id}`
);
});
const data = reactive({
title,
});
return { post, data };
},
});
|
良いアプローチなのかは正直わかりませんが…。
watchを使ってtitleの値を更新する
computedを止めてしまうという手もありかと。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
import {
defineComponent,
useFetch,
useContext,
ref,
watch,
} from "@nuxtjs/composition-api";
import Author from "~/components/Author.vue";
export default defineComponent({
components: {
Author,
},
setup() {
const post = ref();
const title = ref("");
const { $http, params } = useContext();
watch(post, (updatedPost) => {
title.value = updatedPost.title;
});
useFetch(async () => {
post.value = await $http.$get(
`https://jsonplaceholder.typicode.com/posts/${params.value.id}`
);
});
return { post, title };
},
});
|
useAysncを使う
useFetchがAPIリクエストに使っているのであれば、それはuseAsyncに置き換えることはできます。
ただ、SSGの場合でも毎回APIリクエストが走ってしまうので、SSGの場合はuseStatic使うほうがいいかも。
まとめ
- uesFetch, computedの組み合わせは死ぬ
- reactiveでラップしちゃえばなんとかなる
- そもそもcomputed使わなくていいならwatch使ってなんとかする方法もある
- 通信目的ならuseAsync or useStaticあたり使うのも良さそう。
最後に
ツイートを参考させていただいた@d_horiyama_webさん、@nazo大先生に感謝しています。
twitterまじで偉大。