Nuxtjs中,举例子一篇文章讲清楚:水合sop

108 阅读3分钟

水合的意思

水合:在客户端,将vue组件服务器渲染的静态html 进行"激活"的过程,使其成为完全交互式的单页应用(SPA)。

sop(工作流程)

1、服务器渲染:Nuxt在服务器生成页面的静态HTML

2、发送到客户端:html+vue组件的js代码一起发送到浏览器。

3、水合过程:Vue在客户端接管静态html,将其与Vue组件实例关联,添加事件监听器,恢复响应式状态。

代码例子

计数器

整个Counter.vue都是在服务器端渲染的,然后在客户端进行水合(也就是所谓的激活的意思)。

<!-- components/Counter.vue -->
<template>
  <div>
    <p>计数:{{ count }}</p>
    <button @click="increment">增加</button>
  </div>
</template>

<script setup>
const count = ref(0)
const increment = () => count.value++
</script>

sop(计数器水合过程)

  • 服务器渲染:<p>计数:0</p>(静态 HTML)
  • 发送到客户端:HTML + Vue组件代码
  • 水合:Vue找到这个DOM元素,将count响应式变量绑定,添加点击事件。
  • 结果:按钮可以交互,数字可以更新。

服务器

服务器渲染以下代码:

<-- Nuxt 服务器渲染的完整 html -->
<div>
  <p>计数:0</p>
  <button>增加</button>
</div>

服务器会做这些动作:

  • 1、执行<script setup>中的代码
  • 2、count.value初始化为0
  • 3、使用这个值渲染模版
  • 4、生成包含当前状态的静态html
  • 5、不渲染任何事件监听器(@click

发送到客户端的内容

发送到客户端的内容如下:

<!DOCTYPE html>
<html>
<head>...</head>
<body>
  <!-- 服务器渲染的静态 HTML -->
  <div>
    <p>计数: 0</p>
    <button>增加</button>
  </div>
  
  <!-- 打包的 JavaScript -->
  <script src="/_nuxt/entry.js"></script>
  <script>
    // 包含 Counter 组件的 Vue 代码
    const Counter = {
      setup() {
        const count = ref(0)
        const increment = () => count.value++
        return { count, increment }
      },
      template: `<div>...</div>`
    }
  </script>
</body>
</html>

客户端

然后客户端进行水合,

  • 1、加载html:浏览器显示静态的计数"0"
  • 2、加载js:下载包含Vue和组件代码的js
  • 3、进行水合激活

水合激活,我写步骤在下面

// Vue 内部执行:
// 1. 找到 <div> 元素
// 2. 将 count 响应式变量绑定到 DOM
// 3. 给按钮添加 click 事件监听器
// 4. 建立虚拟 DOM 与真实 DOM 的关联

组合例子 —— 条件渲染

<!-- components/Example.vue -->
<template>
  <div>
    <!-- 这个是在服务器渲染的 -->
    <p>服务器时间: {{ serverTime }}</p>
    
    <!-- 这个是在客户端水合后渲染的 -->
    <p v-if="isClient">客户端时间: {{ clientTime }}</p>
    
    <button @click="update">更新</button>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'

// 这部分代码服务器和客户端都会执行
const serverTime = ref(new Date().toISOString())

// 这部分只在客户端执行
const isClient = typeof window !== 'undefined'
const clientTime = ref('')

onMounted(() => {
  if (isClient) {
    clientTime.value = new Date().toISOString()
  }
})

const update = () => {
  clientTime.value = new Date().toISOString()
}
</script>

渲染出来的结果对比:

服务器渲染的html:

<div>
  <p>服务器时间: 2024-01-15T10:30:00.000Z</p>
  <!-- v-if="false",所以没有这个元素 -->
  <button>更新</button>
</div>

客户端水合后:

<div>
  <p>服务器时间: 2024-01-15T10:30:00.000Z</p>
  <p>客户端时间: 2024-01-15T10:30:05.123Z</p>
  <button>更新</button>
</div>
<!-- 现在按钮有点击事件了 -->
部分服务器渲染客户端渲染
初始 HTML 结构
初始数据 (count: 0)
事件监听器 (@click)
后续更新 (count++)
响应式系统❌(只是静态值)✅(激活响应式)

sop(水合过程)

水合过程:

  • 1、服务器端:读环境变量,然后,渲染到{{ config.public.apiBase }}html当中。
  • 2、客户端水合useRuntimeConfig()window全局变量中获取值,