diff --git a/docs/api/apollo-query.md b/docs/api/apollo-query.md index a5d5684..61d658c 100644 --- a/docs/api/apollo-query.md +++ b/docs/api/apollo-query.md @@ -11,7 +11,7 @@ - `skip`: Boolean disabling query fetching - `clientId`: id of the Apollo Client used by the query (defined in ApolloProvider `clients` option) - `deep`: Boolean to use deep Vue watchers -- `tag`: String HTML tag name (default: `div`); if falsy (for example `null` or `undefined`), the component will be renderless (the content won't be wrapped in a tag). In this case, only the first child will be rendered. +- `tag`: String HTML tag name (default: `div`); if falsy (for example `null` or `undefined`), the component will be renderless (the content won't be wrapped in a tag), in this case, only the first child will be rendered - `debounce`: Number of milliseconds for debouncing refetches (for example when the variables are changed) - `throttle`: Number of milliseconds for throttling refetches (for example when the variables are changed) diff --git a/docs/api/smart-subscription.md b/docs/api/smart-subscription.md index a3f5c43..fb53953 100644 --- a/docs/api/smart-subscription.md +++ b/docs/api/smart-subscription.md @@ -8,7 +8,7 @@ Each subscription declared in the `apollo.$subscribe` option in a component resu - `variables`: Object or reactive function that returns an object. Each key will be mapped with a `'$'` in the GraphQL document, for example `foo` will become `$foo`. - `throttle`: throttle variables updates (in ms). - `debounce`: debounce variables updates (in ms). -- `result(data)` is a hook called when a result is received +- `result(data, key)` is a hook called when a result is received ## Properties diff --git a/docs/guide/ssr.md b/docs/guide/ssr.md index 05c236f..0ce839e 100644 --- a/docs/guide/ssr.md +++ b/docs/guide/ssr.md @@ -121,7 +121,7 @@ If `ssr` is false, we try to restore the state of the Apollo cache with `cache.r Here is an example: -```js{21-31} +```js{21-30} // apollo.js import Vue from 'vue' @@ -144,7 +144,6 @@ export function createApolloClient (ssr = false) { // If on the client, recover the injected state if (!ssr) { - // If on the client, recover the injected state if (typeof window !== 'undefined') { const state = window.__APOLLO_STATE__ if (state) { diff --git a/docs/migration/README.md b/docs/migration/README.md index a61cb0e..330e4dd 100644 --- a/docs/migration/README.md +++ b/docs/migration/README.md @@ -152,7 +152,7 @@ const apolloProvider = new VueApollo({ // Use the provider new Vue({ - provide: apolloProvider.provide(), + apolloProvider, // ... }) ``` diff --git a/docs/zh-cn/README.md b/docs/zh-cn/README.md index f1f745b..1d1d3f0 100644 --- a/docs/zh-cn/README.md +++ b/docs/zh-cn/README.md @@ -32,11 +32,11 @@ footer: LICENCE MIT - Created by Guillaume CHAU (@Akryum) ### 铜牌赞助商

- - + + Vuetify logo

::: tip 当前版本 -中文文档现在同步至 v3.0.0-beta.26 +中文文档现在同步至 v3.0.0-beta.28 ::: diff --git a/docs/zh-cn/api/apollo-mutation.md b/docs/zh-cn/api/apollo-mutation.md index 8b98744..8b6a794 100644 --- a/docs/zh-cn/api/apollo-mutation.md +++ b/docs/zh-cn/api/apollo-mutation.md @@ -7,7 +7,7 @@ - `optimisticResponse`:详见 [乐观 UI](https://www.apollographql.com/docs/react/features/optimistic-ui.html) - `update`:详见 [变更后更新缓存](https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-mutation-options-update) - `refetchQueries`:详见 [变更后重新获取查询](https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-mutation-options-refetchQueries) -- `clientId`:用于解析使用的 Apollo 客户端(在 ApolloProvider 中定义) +- `clientId`:查询所使用的 Apollo 客户端 id(在 ApolloProvider 的 `clients` 选项中定义) - `tag`:字符串,HTML 标签名(默认值:`div`);如果是 `undefined`,该组件将成为无渲染组件(内容不会被包装在标签中) ## 作用域插槽 props diff --git a/docs/zh-cn/api/apollo-query.md b/docs/zh-cn/api/apollo-query.md index 2e14fa8..4b0b0e9 100644 --- a/docs/zh-cn/api/apollo-query.md +++ b/docs/zh-cn/api/apollo-query.md @@ -9,9 +9,9 @@ - `notifyOnNetworkStatusChange`:详见 [apollo notifyOnNetworkStatusChange](https://www.apollographql.com/docs/react/basics/queries.html#graphql-config-options-notifyOnNetworkStatusChange) - `context`:详见 [apollo context](https://www.apollographql.com/docs/react/basics/queries.html#graphql-config-options-context) - `skip`:布尔值,禁用查询获取 -- `clientId`:用于解析使用的 Apollo 客户端(在 ApolloProvider 中定义) +- `clientId`:查询所使用的 Apollo 客户端 id(在 ApolloProvider 的 `clients` 选项中定义) - `deep`:布尔值,使用深度 Vue 侦听器 -- `tag`:字符串,HTML 标签名(默认值:`div`);如果是 `undefined`,该组件将成为无渲染组件(内容不会被包装在标签中) +- `tag`:字符串,HTML 标签名(默认值:`div`);如果是假值(如 `null` 或 `undefined`),该组件将成为无渲染组件(内容不会被包装在标签中),在这种情况下,只有第一个子元素会被渲染 - `debounce`:对重新获取查询结果的防抖毫秒数(例如当变量更改时) - `throttle`:对重新获取查询结果的节流毫秒数(例如当变量更改时) diff --git a/docs/zh-cn/api/dollar-apollo.md b/docs/zh-cn/api/dollar-apollo.md index 84bd3e2..8f7b4b4 100644 --- a/docs/zh-cn/api/dollar-apollo.md +++ b/docs/zh-cn/api/dollar-apollo.md @@ -7,7 +7,6 @@ - `vm`:关联的组件。 - `queries`:组件的智能查询的数组。 - `subscriptions`:组件的智能订阅的数组。 -- `client`:组件当前使用的 Apollo 客户端。 - `provider`:注入的 [Apollo Provider](./apollo-provider.md)。 - `loading`:是否至少有一个查询正在加载。 - `skipAllQueries`:(setter) 布尔值,用于暂停或取消暂停所有智能查询。 diff --git a/docs/zh-cn/api/smart-query.md b/docs/zh-cn/api/smart-query.md index 730c4b8..528970c 100644 --- a/docs/zh-cn/api/smart-query.md +++ b/docs/zh-cn/api/smart-query.md @@ -10,7 +10,7 @@ - `debounce`:变量更新防抖时间(毫秒)。 - `pollInterval`:使用轮询自动更新的时间(表示每隔 `x` 毫秒重新获取一次)。 - `update(data) {return ...}` 用来自定义设置到 vue 属性中的值,例如当字段名称不匹配时。 -- `result(ApolloQueryResult)` 是收到结果时调用的钩子(更多参见 [ApolloQueryResult](https://github.com/apollographql/apollo-client/blob/master/packages/apollo-client/src/core/types.ts) 的文档)。 +- `result(ApolloQueryResult, key)` 是收到结果时调用的钩子(更多参见 [ApolloQueryResult](https://github.com/apollographql/apollo-client/blob/master/packages/apollo-client/src/core/types.ts) 的文档)。`key` 是在 `apollo` 选项中定义此查询时使用的键名。 - `error(error)` 是有错误时调用的钩子。`error` 是一个具有 `graphQLErrors` 属性或 `networkError` 属性的 Apollo 错误对象。 - `loadingKey` 将更新你传递的值所对应的组件数据属性。你应该在组件的 `data()` 钩子中将此属性初始化为 `0` 。当查询正在加载时,此属性将增加 1;当不再加载时,它将减去 1。这样,该属性可以表示当前正在加载中的查询的计数器。 - `watchLoading(isLoading, countModifier)` 是一个在查询的加载状态发生变化时调用的钩子。`countModifier` 参数当查询正在加载时等于 `1`,不再加载时为 `-1`。 diff --git a/docs/zh-cn/api/smart-subscription.md b/docs/zh-cn/api/smart-subscription.md index 71ca7ca..def6aa5 100644 --- a/docs/zh-cn/api/smart-subscription.md +++ b/docs/zh-cn/api/smart-subscription.md @@ -8,7 +8,7 @@ - `variables`:对象或返回对象的响应式函数。每个键将用 `'$'` 映射到 GraphQL 文档中,例如 `foo` 将变为 `$foo`。 - `throttle`:变量更新节流时间(毫秒)。 - `debounce`:变量更新防抖时间(毫秒)。 -- `result(data)` 是收到结果时调用的钩子。 +- `result(data, key)` 是收到结果时调用的钩子。 ## 属性 diff --git a/docs/zh-cn/api/ssr.md b/docs/zh-cn/api/ssr.md index 6c3ed04..1661b92 100644 --- a/docs/zh-cn/api/ssr.md +++ b/docs/zh-cn/api/ssr.md @@ -6,45 +6,6 @@ ## 方法 -### install - -仅在服务端安装 SSR 插件: - -```js -Vue.use(ApolloSSR) -``` - -你可以像这样传递附加选项: - -```js -Vue.use(ApolloSSR, { - fetchPolicy: 'network-only', - suppressRenderErrors: false, -}) -``` - -#### fetchPolicy - -当预取一个 Apollo 查询时,建议覆盖 `fetchPolicy` 以强制查询发送。 - -默认值:`'network-only'`。 - -#### suppressRenderErrors - -隐藏虚假渲染时的错误。 - -默认值:`false`。 - -### prefetchAll - -预取所有队列中的组件定义,并在所有对应的 apollo 数据准备就绪时返回已解决的(resolved) promise。 - -```js -await ApolloSSR.prefetchAll (apolloProvider, componentDefs, context) -``` - -`context` 作为参数传递给智能查询中的 `prefetch` 选项。它可能包含路由和 store。 - ### getStates 将 apollo store 状态作为 JavaScript 对象返回。 @@ -82,40 +43,3 @@ const js = ApolloSSR.exportStates(apolloProvider, options) exportNamespace: '', } ``` - -### globalPrefetch - -允许你注册一个需要显式预取的组件。 - -简单示例如下: - -```js -import MyComponent from '@/components/MyComponent.vue' - -ApolloSSR.globalPrefetch(() => MyComponent) -``` - -你可以根据上下文禁用预取: - -```js -ApolloSSR.globalPrefetch(context => { - if (context.route.name === 'foo'){ - return MyComponent - } -}) -``` - -### mockInstance - -在 `prefetchAll` 期间,应用程序组件树将使用虚假实例重新创建以使进程更快。如果你在模板或渲染函数中访问了像 `this.$http` 这样的辅助函数(通常为 `Undefined error`),你可以通过插件来修改虚假实例,以防止它们的渲染函数崩溃。建议 mock 这些辅助函数以提高性能。 - -```js -const noop = () => {} - -ApolloSSR.mockInstance({ - apply: vm => { - // Mock $http - vm.$http = noop - }, -}) -``` diff --git a/docs/zh-cn/guide/README.md b/docs/zh-cn/guide/README.md index b7597ac..6edaeb0 100644 --- a/docs/zh-cn/guide/README.md +++ b/docs/zh-cn/guide/README.md @@ -21,8 +21,8 @@ ### 铜牌赞助商

- - + + Vuetify logo

diff --git a/docs/zh-cn/guide/local-state.md b/docs/zh-cn/guide/local-state.md index cebf1b7..cc0746a 100644 --- a/docs/zh-cn/guide/local-state.md +++ b/docs/zh-cn/guide/local-state.md @@ -36,5 +36,6 @@ export default { - [示例项目](https://codesandbox.io/s/zqqj82396p) (by @chriswingler) - [Todo App](https://codesandbox.io/s/x2jr96r8pp) (by @NikkitaFTW) +- [Todo App - 扩展](https://codesandbox.io/s/k3621oko23) (by @ScottMolinari fork of @NikkitaFTW's) --- diff --git a/docs/zh-cn/guide/ssr.md b/docs/zh-cn/guide/ssr.md index fcf02fc..ca1be21 100644 --- a/docs/zh-cn/guide/ssr.md +++ b/docs/zh-cn/guide/ssr.md @@ -1,5 +1,9 @@ # 服务端渲染 +::: warning +**需要 Vue 2.6+ 对 `serverPrefetch` 的支持** +::: + ## Vue CLI 插件 我为 [vue-cli](http://cli.vuejs.org) 制作了一个插件,因此仅用两分钟你就可以将你的 vue-apollo 应用转换为同构 SSR 应用!✨🚀 @@ -12,17 +16,16 @@ vue add @akryum/ssr [更多信息](https://github.com/Akryum/vue-cli-plugin-ssr) -## 预取组件 +## 组件的预取 -在要在服务端预取的查询上,添加 `prefetch` 选项。它可以是: - - 一个变量对象; - - 一个获取上下文对象(例如可以包含 URL)并返回一个变量对象的函数; - - `false` 禁用此查询的预取。 +::: tip +按照 [官方 SSR 指南](https://ssr.vuejs.org) 了解有关 Vue 服务端渲染的更多信息。 +::: -如果你在 `prefetch` 选项中返回一个变量对象,请确保它与 `variables` 选项的结果相匹配。如果它们不匹配,则在服务端渲染模板时,查询的数据属性将不会被填充。 +在使用了 `vue-server-renderer` 的默认情况下,服务端渲染的组件中的所有 GraphQL 查询都将被自动预取。 -::: danger -在服务端进行预取时,你无法访问组件实例。 +::: tip +即使在服务端,你也能够在诸如 `variables` 等选项中使用 `this`! ::: 示例: @@ -31,7 +34,6 @@ vue add @akryum/ssr export default { apollo: { allPosts: { - // 此查询将被预取 query: gql`query AllPosts { allPosts { id @@ -39,7 +41,6 @@ export default { description } }`, - prefetch: true, } } } @@ -58,11 +59,6 @@ export default { description } }`, - prefetch: ({ route }) => { - return { - id: route.params.id, - } - }, variables () { return { id: this.id, @@ -73,11 +69,13 @@ export default { } ``` -### 跳过预取 +## 跳过预取 + +你可以通过将一个查询的 `prefetch` 选项设置为 `false` 来跳过对它的服务端预取。 不预取查询的示例: -```js +```js{12} export default { apollo: { allPosts: { @@ -97,7 +95,7 @@ export default { 如果要跳过特定组件的所有查询的预取,使用 `$prefetch` 选项: -```js +```js{4} export default { apollo: { // 不要预取任何查询 @@ -115,127 +113,16 @@ export default { } ``` -你也可以在任何组件上放置一个 `no-prefetch` 属性,以便在遍历树收集 Apollo 查询时忽略它: - -```vue - -``` - -## 在服务端 - -在服务端入口中,你需要在 Vue 中安装 `ApolloSSR` 插件: - -```js -import Vue from 'vue' -import ApolloSSR from 'vue-apollo/ssr' - -Vue.use(ApolloSSR) -``` - -使用 `ApolloSSR.prefetchAll` 方法来预取你已标记的所有 apollo 查询。第一个参数是 `apolloProvider`。第二个参数是要包含的组件定义数组(例如来自 `router.getMatchedComponents` 方法)。第三个参数是传递给 `prefetch` 钩子的上下文对象(参见上文),建议传入 vue-router 的 `currentRoute` 对象。当所有的 apollo 查询都被加载时,它返回已解决的(resolved) promise。 - -以下是一个使用了 vue-router 和 Vuex store 的示例: - -```js -import Vue from 'vue' -import ApolloSSR from 'vue-apollo/ssr' -import App from './App.vue' - -Vue.use(ApolloSSR, { - // SSR 配置 - fetchPolicy: 'network-only', - suppressRenderErrors: false, -}) - -export default () => new Promise((resolve, reject) => { - const { app, router, store, apolloProvider } = CreateApp({ - ssr: true, - }) - - // 设置 router 的位置 - router.push(context.url) - - // 等待 router 解析完可能的异步钩子 - router.onReady(() => { - const matchedComponents = router.getMatchedComponents() - - // 匹配不到的路由 - if (!matchedComponents.length) { - reject({ code: 404 }) - } - - let js = '' - - // 调用匹配到路由的组件的预取钩子 - // 每个 preFetch 钩子分配到一个 store action 并返回一个 Promise - // 当 action 操作完成且 store 状态已更新时解析这个 Promise - - // Vuex Store 预取 - Promise.all(matchedComponents.map(component => { - return component.asyncData && component.asyncData({ - store, - route: router.currentRoute, - }) - })) - // Apollo 预取 - // 这里将预取整个应用中的所有 Apollo 查询 - .then(() => ApolloSSR.prefetchAll(apolloProvider, [App, ...matchedComponents], { - store, - route: router.currentRoute, - })) - .then(() => { - // 将 Vuex 状态和 Apollo 缓存注入到页面 - // 这将防止不必要的查询 - - // Vuex - js += `window.__INITIAL_STATE__=${JSON.stringify(store.state)};` - - // Apollo - js += ApolloSSR.exportStates(apolloProvider) - - resolve({ - app, - js, - }) - }).catch(reject) - }) -}) -``` - -使用 `ApolloSSR.exportStates(apolloProvider, options)` 方法来获取你需要注入到生成出来页面的 JavaScript 代码,这些代码用于将 apollo 缓存数据传递给客户端。 - -它需要一个 `options` 参数,默认为: - -```js -{ - // 全局变量名 - globalName: '__APOLLO_STATE__', - // 变量设置到的全局对象 - attachTo: 'window', - // 每个 apollo 客户端状态的 key 的前缀 - exportNamespace: '', -} -``` - -你也可以使用 `ApolloSSR.getStates(apolloProvider, options)` 方法来获取 JS 对象而不是脚本字符串。 - -它需要一个 `options` 参数,默认为: - -```js -{ - // 每个 apollo 客户端状态的 key 的前缀 - exportNamespace: '', -} -``` - -### 创建 Apollo Client +## 创建 Apollo 客户端 建议在一个带有 `ssr` 参数的函数内部创建 apollo 客户端,参数在服务端为 `true`,在客户端为 `false`。 +如果 `ssr` 为 false,我们将在服务端的 SSR 阶段中将 `window.__APOLLO_STATE__` 变量注入到 HTML 页面中,并通过该变量尝试使用 `cache.restore` 来还原 Apollo 缓存的状态。 + 这里是一个示例: -```js -// src/api/apollo.js +```js{21-30} +// apollo.js import Vue from 'vue' import { ApolloClient } from 'apollo-client' @@ -282,16 +169,24 @@ export function createApolloClient (ssr = false) { } ``` -常见的 `CreateApp` 方法示例: +## 创建应用 + +我们并不立即创建根 Vue 实例,而代以一个接受 `context` 参数的 `createApp` 函数。 + +此函数将同时在客户端和服务端入口被使用,但在上下文中具有不同的 `ssr` 值。我们在之前编写的 `createApolloClient` 方法中使用此值。 + +常见的 `createApp` 方法示例: + +```js{9,37} +// app.js -```js import Vue from 'vue' import VueRouter from 'vue-router' import Vuex from 'vuex' import { sync } from 'vuex-router-sync' import VueApollo from 'vue-apollo' -import { createApolloClient } from './api/apollo' +import { createApolloClient } from './apollo' import App from './ui/App.vue' import routes from './routes' @@ -312,6 +207,12 @@ function createApp (context) { // 将注册 `store.state.route` sync(store, router) + // Vuex 状态恢复 + if (!context.ssr && window.__INITIAL_STATE__) { + // 我们使用服务端注入的数据来初始化 store 状态 + store.replaceState(window.__INITIAL_STATE__) + } + // Apollo const apolloClient = createApolloClient(context.ssr) const apolloProvider = new VueApollo({ @@ -335,23 +236,34 @@ function createApp (context) { export default createApp ``` -在客户端: +## 客户端入口 + +客户端入口非常简单——我们只需将 `ssr` 设为 `false` 来调用 `createApp`: ```js -import CreateApp from './app' +// client-entry.js -CreateApp({ +import createApp from './app' + +createApp({ ssr: false, }) ``` -在服务端: +## 服务端入口 -```js -import CreateApp from './app' +除了存储 Apollo 缓存并将其注入客户端 HTML 之外,不需要任何特殊内容。在官方 SSR 指南中了解有关 [带路由的服务端入口](https://ssr.vuejs.org/guide/routing.html#routing-with-vue-router) 和 [数据预取](https://ssr.vuejs.org/guide/data.html#data-store) 的更多信息。 + +这里是使用了 vue-router 和 Vuex 的一个示例: + +```js{3,26} +// server-entry.js + +import ApolloSSR from 'vue-apollo/ssr' +import createApp from './app' export default () => new Promise((resolve, reject) => { - const { app, router, store, apolloProvider } = CreateApp({ + const { app, router, store, apolloProvider } = createApp({ ssr: true, }) @@ -360,9 +272,50 @@ export default () => new Promise((resolve, reject) => { // 等待 router 解析完可能的异步钩子 router.onReady(() => { - // 预取,渲染 HTML(参见上文) + // 此 `rendered` 钩子将在应用完成渲染时被调用 + context.rendered = () => { + // 在应用完成渲染后, + // 我们的 store 现在已经填充入渲染应用程序所需的状态。 + // 当我们将状态附加到上下文, + // 并且 `template` 选项用于 renderer 时, + // 状态将自动序列化为 `window.__INITIAL_STATE__`,并注入 HTML。 + context.state = store.state + + // 同样注入 apollo 缓存状态 + context.apolloState = ApolloSSR.getStates(apolloProvider) + } + resolve(app) }) }) ``` -请查看 [SSR API](../api/ssr.md) 了解更多详情和其他功能。 +使用 [ApolloSSR.getStates](../api/ssr.md#getstates) 方法以获取需要注入到生成的页面、用来将 apollo 缓存数据传递给客户端的 JavaScript 代码。 + +在 [页面模板](https://ssr.vuejs.org/guide/#using-a-page-template) 中,使用 `renderState` 辅助函数: + +```html +{{{ renderState({ contextKey: 'apolloState', windowKey: '__APOLLO_STATE__' }) }}} +``` + +这里是一个完整的示例: + +```html{15} + + + + + + + + {{ title }} + {{{ renderResourceHints() }}} + {{{ renderStyles() }}} + + + + {{{ renderState() }}} + {{{ renderState({ contextKey: 'apolloState', windowKey: '__APOLLO_STATE__' }) }}} + {{{ renderScripts() }}} + + +``` diff --git a/docs/zh-cn/migration/README.md b/docs/zh-cn/migration/README.md index 6ccaf0f..2f854db 100644 --- a/docs/zh-cn/migration/README.md +++ b/docs/zh-cn/migration/README.md @@ -152,7 +152,7 @@ const apolloProvider = new VueApollo({ // 使用 provider new Vue({ - provide: apolloProvider.provide(), + apolloProvider, // ... }) ```