数据获取

阅读此指南之前,我们推荐先看看 React 教程Vue 教程。 教程里解释了什么是 /renderer/_default.page.* 文件

onBeforeRender()

获取数据常用的方法是使用 onBeforeRender() hook函数

// /pages/movies.page.server.js
// Environment: Node.js

import fetch from "node-fetch";

export { onBeforeRender };

async function onBeforeRender(pageContext) {
  // `.page.server.js` 文件总是运行在 Node.js 环境中;在这里我们可以使用 SQL/ORM 查询
  const response = await fetch("https://movies.example.org/api");
  let movies = await response.json();

  // `movies` 会被序列化之后传递到浏览器端
  // 为了最小化传递给浏览器端的数据,我们把需要的数据筛选出来
  movies = movies.map(({ title, release_date }) => ({ title, release_date }));

  // 把 `movies` 传递到 `pageContext.pageProps` 中
  const pageProps = { movies };
  return {
    pageContext: {
      pageProps,
    },
  };
}
// /renderer/_default.page.server.js
// Environment: Node.js

import { escapeInject, dangerouslySkipEscape } from "vite-plugin-ssr";
import { renderToHtml, createElement } from "some-ui-framework";

// 告诉 `vite-plugin-ssr` 把 `pageContext.pageProps` 传递到浏览器端
export const passToClient = ["pageProps"];

export { render };

async function render(pageContext) {
  const { Page, pageProps } = pageContext;
  const pageHtml = await renderToHtml(
    // 把 `pageProps` 传给 `Page`
    createElement(Page, pageProps)
  );
  /* JSX:
  const pageHtml = await renderToHtml(<Page {...pageProps} />)
  */

  return escapeInject`<html>
    <div id='view-root'>
      ${dangerouslySkipEscape(pageHtml)}
    </div>
  </html>`;
}
// /renderer/_default.page.client.js
// Environment: Browser

export { render };

import { hydrateDom, createElement } from "some-ui-framework";

async function render(pageContext) {
  // `Page` 也可以在浏览器中使用了
  const { Page } = pageContext;
  // 多亏了 `passToClient = ['pageProps']`,`pageContext.pageProps` 被传递到了浏览器端(客户端)
  const { Page, pageProps } = pageContext;
  await hydrateDom(
    // 把 `pageProps` 传给 `Page`
    createElement(Page, pageProps),
    document.getElementById("view-root")
  );
  /* JSX:
  await hydrateDom(<Page {...pageProps} />, document.getElementById('view-root'))
  */
}
// /pages/movies.page.js
// Environment: Browser, Node.js

export { Page };

// 在上面的 `render()` 函数中,我们将 `pageContext.pageProps` 传递给了 `Page`
function Page(pageProps) {
  const { movies } = pageProps;
  // ...
}

需要注意的是,vite-plugin-ssr 并不知道 pageProps 是什么:它只是我们创建的一个普通对象,其目的是为了方便地一次性传递所有页面所需的 props

自定义Exports

为了使用更加方便,我们可以使用Custom Export/Hook,然后在 /renderer/_default.page.server.js 定义一次 onBeforeRender(),而不必在每个页面都定义一个 onBeforeRender() hook

例如:

// /pages/user.page.js
export const query = { modelName: "User", select: ["firstName", "lastName"] };
// /pages/product.page.js
export const query = { modelName: "Product", select: ["name", "price"] };
// /renderer/_default.page.server.js
// Environment: Node.js

// 使用单个全局 `onBeforeRender()` hook
export { onBeforeRender };

import { runQuery } from "some-sql-engine";

async function onBeforeRender(pageContext) {
  // 导出的 `query` 可在 `pageContext.exports.query` 中访问
  const { query } = pageContext.exports;
  const data = await runQuery(query);
  const pageProps = { data };
  return { pageContext: { pageProps } };
}

客户端路由

.page.server.js 文件仅在 Node.js 中加载执行; 所以获取数据的代码始终在 Node.js 环境中执行。这样做很方便,因为它使得我们写获取数据的代码更容易了 The .page.server.js files are loaded only in Node.js; our data fetching code is always executed in Node.js. This is convenient as it makes writing data fetching code easier.

如果我们使用客户端路由,那我们可以选择在 .page.js 中定义 onBeforeRender()。这种情况下,onBeforeRender() 在 Node.js 和 浏览器中(页面导航时)都会被调用

通常,我们建议在 .page.server.js 中定义 onBeforeRender()(即使是客户端路由)。但是如果我们想要最小化对 Node.js 服务的请求压力,可以在 .page.js 中定义 onBeforeRender()

GraphQL

使用 GraphQL 时,我们在组件级别定义 GraphQL queries/fragments,同时在单独全局的 onBeforeRender() hook中获取 GraphQL 数据

通常而言,我们可以在 vite-plugin-ssr 中完全掌控渲染方式,也就是说集成 GraphQL 只需遵循官方的 SSR 指南

状态管理 (Vuex/Redux...)

使用全局状态管理方案(Vuex, Redux, PullState, 等)时,我们的组件不会直接访问获取到的数据。相反,组件只访问 Store,获取到的数据仅确定 Store 的初始状态

通常而言,我们可以在 vite-plugin-ssr 中完全掌控渲染方式,也就是说集成全局状态管理方案只需遵循官方的 SSR 指南