这是我参与「第五届青训营 」伴学笔记创作活动的第 14 天
引言
我们都知道SSR是什么。Server-side Rendering 我们也知道Nuxt3是一个基于Vue3的SSR框架,我们通常使用universal-rendering模式。 但当我们初学Nuxt3的时候,我们会发现Nuxt3文档并没有对universal-rendering部分有过多的解释。 翻看很多教程,也没有很好的解释。他没有很明确的解释,哪部分是客户端渲染、哪部分是服务端渲染。(当然,仔细翻确实能找到,但是笔者画了很久)
NextJs
相应的,笔者在接触Nuxt之前,先接触了NextJs。它的文档在这方面就非常好。刚开始学,就明白,我们如果想要代码在服务端处理,直接使用getServerSideProps即可。
但是,当我们回头看Nuxt3,我们发现,好像这么重要的内容,就没有很明确的解释。反而,文档花了很长的篇幅讲了它的一些约定式路由等功能。这直接让笔者误以为它的代码除了不特殊指定,都是在服务端渲染的。直到这一件事发生。
渲染问题
笔者在开发该组件的时候并没有遇到什么问题,直到一次网站部署,发现这个组件存在一个跳变问题。这个跳变极其影响体验,因此,笔者很重视。
跳变就是,刚开始加载网页,它会显示上午好,过一会又变成下午好,然而我们编写的代码理论上不可能出现这个问题。
<script setup lang="ts">
const hours = useDateFormat(useNow(), 'HH')
const state = ref('')
if (parseInt(hours.value) >= 0 && parseInt(hours.value) <= 11)
state.value = '早上好!'
else if (parseInt(hours.value) > 11 && parseInt(hours.value) <= 14)
state.value = '中午好!'
else if (parseInt(hours.value) > 14 && parseInt(hours.value) <= 18)
state.value = '下午好!'
else if (parseInt(hours.value) > 18 && parseInt(hours.value) <= 24)
state.value = '晚上好!'
</script>
仔细分析,我们得出了一个大胆猜想。这段代码在服务端和客户端都运行了一遍。我们部署的服务器在国外,因此,程序运行得到的时区不是北京时区。我们得到的结果也是会让他跳变。这一切都说得通了。于是乎,我在stackblitz进行了测试,最终证实了这个结论。
解决方案
1.
我们可以直接加一个判断,让程序,直接在服务端运行。(当然,需要获取北京时区的时间)这里,nuxt提供了process
<script setup lang="ts">
const hours = useDateFormat(useChineseNow(), 'HH')
const state = ref('')
if (process.server){
if (parseInt(hours.value) >= 0 && parseInt(hours.value) <= 11)
state.value = '早上好!'
else if (parseInt(hours.value) > 11 && parseInt(hours.value) <= 14)
state.value = '中午好!'
else if (parseInt(hours.value) > 14 && parseInt(hours.value) <= 18)
state.value = '下午好!'
else if (parseInt(hours.value) > 18 && parseInt(hours.value) <= 24)
state.value = '晚上好!'
}
</script>
2.
我们可以直接把计算的部分放入onBeforeMount生命周期中运行。但是这种方法有问题,我们其他地方都是在服务端都渲染好了,但是state只会在客户端执行。网页会直接把服务端渲染好的展示后再执行客户端的生命周期。这就导致用户会看到一段时间state为空的阶段。这还不如跳变问题呢。。。
<script setup lang="ts">
const hours = useDateFormat(useNow(), 'HH')
const state = ref('')
onBeforeMount(()=>{
if (parseInt(hours.value) >= 0 && parseInt(hours.value) <= 11)
state.value = '早上好!'
else if (parseInt(hours.value) > 11 && parseInt(hours.value) <= 14)
state.value = '中午好!'
else if (parseInt(hours.value) > 14 && parseInt(hours.value) <= 18)
state.value = '下午好!'
else if (parseInt(hours.value) > 18 && parseInt(hours.value) <= 24)
state.value = '晚上好!'
})
</script>
通用渲染
我们解决了问题,但不能知其然而不知所以然。笔者再一次翻阅全网,翻阅文档,终于理解了整件事。Nuxt3的渲染模式,不是跟Nextjs一样,有明确的函数去区分。我们重点要关注的是Nuxt3的生命周期。
我们可以从生命周期发现,Nuxt一部分在服务端渲染,一部分在客户端渲染,甚至,一部分生命阶段在服务端和客户端都执行。
由于我们使用的是Vue3 Setup,没有什么create生命阶段。
我们已经知道了,我们的setup阶段是客户端服务端共同执行的阶段。其他部分,可以参阅下面的表格。
Lifecycle Hooks
App Hooks (runtime)
| Hook | Arguments | Environment | Description |
|---|---|---|---|
| app:created | vueApp | Server & Client | Called when initial vueApp instance is created. |
| app:error | err | Server & Client | Called when a fatal error occurs. |
| app:error:cleared | { redirect? } | Server & Client | Called when a fatal error occurs. |
| app:data:refresh | keys? | Server & Client | (internal) |
| vue:setup | - | Server & Client | (internal) |
| vue:error | err, target, info | Server & Client | Called when a vue error propages to the root component. Learn More |
| . | |||
| app:rendered | renderContext | Server | Called when SSR rendering is done. |
| app:redirected | - | Server | Called before SSR redirection. |
| app:beforeMount | vueApp | Client | Called before mounting the app, called only on client side. |
| app:mounted | vueApp | Client | Called when Vue app is initialized and mounted in browser. |
| app:suspense:resolve | appComponent | Client | On Suspense |
| resolved event. | |||
| link:prefetch | to | Client | Called when a is observed to be prefetched. |
| page:start | pageComponent? | Client | Called on Suspense |
| pending event. | |||
| page:finish | pageComponent? | Client | Called on Suspense |
| resolved event. | |||
| page:transition:finish | pageComponent? | Client | After page transition onAfterLeave |
| event. |
FetchData
Nuxt告诉我们可以使用useFetch获取数据,而且这个方法即使在setup阶段执行,他也只会在服务端执行。只有我们设置server: false。 他才会在客户端执行。