quasar@v2 ssr改造遇到的问题
1.在ssr改造第一个页面加上preFetch之后发现页面没有数据,代码如下
Index.vue
import { useGoodsStore } from 'stores/goods';
computed: {
preFetch({ store, currentRoute, previousRoute, redirect, ssrContext, urlPath, publicPath }) { console.log('Index prefetch'); // fetch data, validate route and optionally redirect to some other route... // ssrContext is available only server-side in SSR mode // No access to "this" here // Return a Promise if you are running an async job // Example: const myStore = useGoodsStore(store); myStore.getItemList(currentRoute.path, currentRoute.query.q); },
stores/googs.js
import { defineStore } from 'pinia'; import { axios } from 'boot/axios'; export const useGoodsStore = defineStore('goods', { state: () => ({ items: [], maxPage: 0, isListEnd: false, }), getters: { doubleCount(state) { return state.counter * 2; }, }, actions: { getItemList(path, q, sortIndex) { // console.log('$$$$$$' + this.query); // this.$q.loading.show({ // delay: 400, // ms // }); return axios .post(`${global.config.domain}/goods/list`, { page: 1, path: path, query: q, sort: sortIndex, }) .then((res) => { // console.log(res.data.data); console.log('getItemList return'); this.items = res.data.data.records; // console.log(this.items); if (res.data.data.records.length < 20) { this.isListEnd = true; } this.maxPage = Math.ceil(res.data.data.total / res.data.data.size); console.log('this.maxPage ' + this.maxPage); // this.$q.loading.hide(); }); }, }, });
经过调试,问题出在 myStore.getItemList是个异步方法,preFetch执行完了,但数据还没有开始取。 then this data needs to be pre-fetched and resolved before we start the rendering process. 经实测preFetch这个hook不管时server端还是cclient端都会被call到。有异步数据正确做法是在preFetch返回一个Promise。
preFetch({ store, currentRoute, previousRoute, redirect, ssrContext, urlPath, publicPath }) { console.log('Index prefetch'); // fetch data, validate route and optionally redirect to some other route... // ssrContext is available only server-side in SSR mode // No access to "this" here // Return a Promise if you are running an async job // Example: const myStore = useGoodsStore(store); return myStore.getItemList(currentRoute.path, currentRoute.query.q); },
注意在actions里 getItemList 也要return axios.post
2. 改成ssr后Quasar的Screen插件失效,$q.screen.width永远=0,Screen.gt.sm 这些也失效。根据开发人员的回复是因为ssr时无法知道window的size,所以设为0,否则会发生hydration errors,留下空白页。
When rendering on the server-side, the width of the client's window is unknown (no way to know it; no HTTP header for this, nothing). So we must assume it's the lowest possible (otherwise hydration errors!), then on the client side, when it takes over, it can correct it...
Unfortunately, such an assumption cannot be accurate. And there's tons of screen sizes for mobiles. Then you also have tablets. And you can use custom breakpoints. And on desktop, the browser window width can be anything. If the assumption goes wrong, there's gonna be hydrating errors and the page left blank for those users, rendering the website useless.
$q.screen
test in a computed and return the proper class. Then use that in your html. There could be an issue with reactivity and inline code like the way you have it.don't use the Screen Plugin and use a CSS based solution (media queries)
还发现一个问题,$q.screen.gt.sm的反应失效
<div v-if="$q.screen.gt.sm" class="col-4"> <router-view name="hot"></router-view> </div> created() { console.log('MainLayout created'); // const $q = useQuasar(); console.log('v-if ' + this.$q.screen.gt.sm); // this.fabYoutube = fabYoutube; }, mounted() { console.log('v-if-' + this.$q.screen.gt.sm); }
MainLayout created MainLayout.vue?713b:239 v-if false MainLayout mounted MainLayout.vue?713b:245 v-if-false
无论是created()还是mounted(),$q.screen.gt.sm都是false,但 v-if="$q.screen.gt.sm" 却是true, 这里不清楚是$q的插件反应式出了啥问题,在github上项目有类似的问题
https://github.com/quasarframework/quasar/issues/9711
这个问题问的是 this.$q.screen.gt.xs的值正确,但class却没有更新。这个问题还是open状态。
<template> <q-input outlined bg-color="red" label="test" :class="$q.screen.gt.xs ? '' : 'fit'"/> </template> <script lang="ts"> import { defineComponent } from 'vue' export default defineComponent({ created() { console.log(this.$q.screen.gt.xs); }, mounted() { console.log(this.$q.screen.gt.xs); }, }) </script>
经测试,改成下面这样就可以
<div v-if="isBigScreen" class="col-4"> <router-view name="hot"></router-view> </div> data() { isBigScreen: Screen.gt.sm ? true : false, }
3.关于prefetch hook何时才会调用,经测试,下面的router
path: '/', component: () => import('layouts/MainLayout.vue'), children: [ { path: '', meta: { isGoodsList: true }, components: { default: () => import('pages/Index.vue'), // hot: () => import('components/HotList.vue'), hot: () => import('components/ResourceSideList.vue'), }, props: { default: (route) => ({ sort: route.query.sort }), hot: false, }, }, { path: 'list', meta: { isGoodsList: true }, components: { default: () => import('pages/Index.vue'), // hot: () => import('components/HotList.vue'), hot: () => import('components/ResourceSideList.vue'), }, props: { default: (route) => ({ sort: route.query.sort, query: route.query.q, page: route.query.page, }), hot: false, }, }, { path: 'list/:page', meta: { isGoodsList: true }, components: { default: () => import('pages/Index.vue'), // hot: () => import('components/HotList.vue'), hot: () => import('components/ResourceSideList.vue'), }, props: { default: (route) => ({ page: route.query.q, sort: route.query.sort }), hot: false, }, },
{
path: ':path',
meta: { isGoodsList: true },
components: {
default: () => import('pages/Index.vue'),
hot: () => import('components/ResourceSideList.vue'),
},
props: {
default: (route) => ({
query: route.query.q,
page: route.query.page,
sort: route.query.sort,
}),
hot: false,
},
},
从 路径'' route到 路径'list', Index组件在路径''时已生成,router到'list' 时prefetch hook不会触发,但route到':path'时却触发了。从':path'route到不同的':path'也会触发,但从':path'route到''或'list'就不会触发了。
Now, let’s see how the hooks are called when the user visits these routes in the order specified below, one after another.
Route being visited | Hooks called from | Observations |
---|---|---|
/ |
App.vue then LandingPage | App.vue hook is called since our app boots up. |
/shop/all |
ShopLayout then ShopAll | - |
/shop/new |
ShopNew | ShopNew is a child of ShopLayout, and ShopLayout is already rendered, so ShopLayout isn’t called again. |
/shop/product/pyjamas |
ShopProduct | - |
/shop/product/shoes |
ShopProduct | Quasar notices the same component is already rendered, but the route has been updated and it has route params, so it calls the hook again. |
/shop/product/shoes/overview |
ShopProduct then ShopProductOverview | ShopProduct has route params so it is called even though it’s already rendered. |
/ |
LandingPage | - |
Quasar notices the same component is already rendered, but the route has been updated and it has route params, so it calls the hook again. 看这句,这个文档只强调了 route params,看来没有route params的路径如果是相同的组件就不会触发prefetch了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人