Vite/Vue 3 SSR组件水化防止组件闪烁
Prevent component flickering on Vite / Vue 3 SSR component hydration
我有一个组件在 SSR 中渲染得很好,但是当 Vue 3 进行水合作用时会闪烁(我认为)。
<template>
<ul class="grid grid-cols-1 gap-6 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
<li v-for="recipe in recipes" :key="recipe.id" class="col-span-1 flex flex-col text-center bg-white rounded-lg shadow divide-y divide-gray-200">
<div class="flex-1 flex flex-col">
<img class="w-full flex-shrink-0 mx-auto bg-black rounded-t-lg" :src="recipe.imageUrl" alt="" />
<h3 class="text-gray-900 text-sm font-medium p-4">
{{ recipe.title }}
</h3>
</div>
</li>
</ul>
<Pagination v-model="page" :records="totalRecords" :per-page="12" :options="paginationOptions" @paginate="fetchRecipes" />
</template>
<script>
import Pagination from 'v-pagination-3'
import axios from 'axios'
export default {
components: {
Pagination,
},
inject: ['paginationOptions'],
data() {
return {
section: 'home',
recipes: [],
page: 3,
totalRecords: 0,
}
},
created() {
},
mounted() {
this.fetchRecipes(this.page)
},
methods: {
async fetchRecipes(page) {
try {
const url = 'http://local-api.local/api/fakeApi'
const response = await axios.get(url, { params: { page } }).then((response) => {
this.recipes = response.data.data.map(recipe => ({
title: recipe.title,
imageUrl: `http://test-server.local${recipe.thumbnailUrl}`,
}))
this.totalRecords = response.data.total
})
}
catch (err) {
if (err.response) {
// client received an error response (5xx, 4xx)
console.log('Server Error:', err)
}
else if (err.request) {
// client never received a response, or request never left
console.log('Network Error:', err)
}
else {
console.log('Client Error:', err)
}
}
},
},
serverPrefetch() {
return this.fetchRecipes(this.page)
},
}
</script>
我做错了什么?我一定已经尝试了 50 种设置方法 this.recipes
(也在所有可能的生命周期挂钩中),但所有这些方法仍然会导致水合作用闪烁。
我正在使用 Vite(如果需要的话,使用 vite-ssr 插件)/Vue 3 SSR。请注意,闪烁后的组件似乎与页面加载时显示的 SSR 生成的版本相同(并且在源代码中)。
在示例中,您的映射函数 api 结果不包含 ID。
this.recipes = response.data.data.map(recipe => ({
title: recipe.title,
imageUrl: `http://test-server.local${recipe.thumbnailUrl}`,
}))
这意味着 :key=recipe.id
绑定到 undefined
并且 vue 无法跟踪每条记录的更改。更改映射函数以包含 id
this.recipes = response.data.data.map(recipe => ({
id: recipe.id,
title: recipe.title,
imageUrl: `http://test-server.local${recipe.thumbnailUrl}`,
}))
如果我们愿意,也可以只包含整个对象
this.recipes = response.data.data.map(recipe => ({
...recipe,
imageUrl: `http://test-server.local${recipe.thumbnailUrl}`,
}))
当然,前提是食谱上有 id
属性。
我找到了解决方案。
我必须将所有数据放入 Vuex Store(在组件中),然后在 main.ts 中,将其用作 initialState,并在加载客户端时,用 initialState 值填充存储。
在 main.ts 中创建商店:
import { createStore } from 'vuex'
const store = createStore({
state() {
return {}
},
})
在我的组件中,填写商店:
this.$store.state.pageContent = {
recipes: response.data.data,
totalRecords: response.data.total,
totalPages: response.data.last_page,
page,
}
然后在 main.ts,但在 export default viteSSR()
:
if (import.meta.env.SSR) {
initialState.initial = store.state
}
else {
for (const item of Object.entries(initialState.initial)) {
store.state[item[0]] = item[1]
}
//check if the store is identical as teh one generated by SSR
console.log(store.state)
}
如果您需要查看更多结构,请注意我使用了这个 Vite SSR / Vue 3 样板项目:https://github.com/frandiox/vitesse-ssr-template
我有一个组件在 SSR 中渲染得很好,但是当 Vue 3 进行水合作用时会闪烁(我认为)。
<template>
<ul class="grid grid-cols-1 gap-6 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
<li v-for="recipe in recipes" :key="recipe.id" class="col-span-1 flex flex-col text-center bg-white rounded-lg shadow divide-y divide-gray-200">
<div class="flex-1 flex flex-col">
<img class="w-full flex-shrink-0 mx-auto bg-black rounded-t-lg" :src="recipe.imageUrl" alt="" />
<h3 class="text-gray-900 text-sm font-medium p-4">
{{ recipe.title }}
</h3>
</div>
</li>
</ul>
<Pagination v-model="page" :records="totalRecords" :per-page="12" :options="paginationOptions" @paginate="fetchRecipes" />
</template>
<script>
import Pagination from 'v-pagination-3'
import axios from 'axios'
export default {
components: {
Pagination,
},
inject: ['paginationOptions'],
data() {
return {
section: 'home',
recipes: [],
page: 3,
totalRecords: 0,
}
},
created() {
},
mounted() {
this.fetchRecipes(this.page)
},
methods: {
async fetchRecipes(page) {
try {
const url = 'http://local-api.local/api/fakeApi'
const response = await axios.get(url, { params: { page } }).then((response) => {
this.recipes = response.data.data.map(recipe => ({
title: recipe.title,
imageUrl: `http://test-server.local${recipe.thumbnailUrl}`,
}))
this.totalRecords = response.data.total
})
}
catch (err) {
if (err.response) {
// client received an error response (5xx, 4xx)
console.log('Server Error:', err)
}
else if (err.request) {
// client never received a response, or request never left
console.log('Network Error:', err)
}
else {
console.log('Client Error:', err)
}
}
},
},
serverPrefetch() {
return this.fetchRecipes(this.page)
},
}
</script>
我做错了什么?我一定已经尝试了 50 种设置方法 this.recipes
(也在所有可能的生命周期挂钩中),但所有这些方法仍然会导致水合作用闪烁。
我正在使用 Vite(如果需要的话,使用 vite-ssr 插件)/Vue 3 SSR。请注意,闪烁后的组件似乎与页面加载时显示的 SSR 生成的版本相同(并且在源代码中)。
在示例中,您的映射函数 api 结果不包含 ID。
this.recipes = response.data.data.map(recipe => ({
title: recipe.title,
imageUrl: `http://test-server.local${recipe.thumbnailUrl}`,
}))
这意味着 :key=recipe.id
绑定到 undefined
并且 vue 无法跟踪每条记录的更改。更改映射函数以包含 id
this.recipes = response.data.data.map(recipe => ({
id: recipe.id,
title: recipe.title,
imageUrl: `http://test-server.local${recipe.thumbnailUrl}`,
}))
如果我们愿意,也可以只包含整个对象
this.recipes = response.data.data.map(recipe => ({
...recipe,
imageUrl: `http://test-server.local${recipe.thumbnailUrl}`,
}))
当然,前提是食谱上有 id
属性。
我找到了解决方案。
我必须将所有数据放入 Vuex Store(在组件中),然后在 main.ts 中,将其用作 initialState,并在加载客户端时,用 initialState 值填充存储。
在 main.ts 中创建商店:
import { createStore } from 'vuex'
const store = createStore({
state() {
return {}
},
})
在我的组件中,填写商店:
this.$store.state.pageContent = {
recipes: response.data.data,
totalRecords: response.data.total,
totalPages: response.data.last_page,
page,
}
然后在 main.ts,但在 export default viteSSR()
:
if (import.meta.env.SSR) {
initialState.initial = store.state
}
else {
for (const item of Object.entries(initialState.initial)) {
store.state[item[0]] = item[1]
}
//check if the store is identical as teh one generated by SSR
console.log(store.state)
}
如果您需要查看更多结构,请注意我使用了这个 Vite SSR / Vue 3 样板项目:https://github.com/frandiox/vitesse-ssr-template