diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 52e67697..7a4c910e 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -1,104 +1,215 @@ module.exports = { - title: 'Vue Apollo', - description: '🚀 Integrate GraphQL in your Vue.js apps!', base: '/vue-apollo/', serviceWorker: true, head: [ ['link', { rel: 'icon', href: '/favicon.png' }], ], + locales: { + '/': { + lang: 'en-US', + title: 'Vue Apollo', + description: '🚀 Integrate GraphQL in your Vue.js apps!', + }, + '/zh-cn/': { + lang: 'zh-CN', + title: 'Vue Apollo', + description: '🚀 在你的 Vue.js 应用中集成 GraphQL!', + } + }, themeConfig: { repo: 'Akryum/vue-apollo', docsDir: 'docs', editLinks: true, - lastUpdated: 'Last Updated', - nav: [ - { - text: 'Guide', - link: '/guide/', - }, - { - text: 'API Reference', - link: '/api/', - }, - { - text: 'Migration', - link: '/migration/', - }, - { - text: 'CLI plugin', - link: 'https://github.com/Akryum/vue-cli-plugin-apollo', - }, - { - text: 'Patreon', - link: 'https://www.patreon.com/akryum', - }, - ], - sidebarDepth: 3, - sidebar: { - '/guide/': [ - '', - 'installation', - { - title: 'Basic Usage', - collapsable: false, - children: [ - 'apollo/', - 'apollo/queries', - 'apollo/mutations', - 'apollo/subscriptions', - 'apollo/pagination', + locales: { + '/': { + selectText: 'Languages', + label: 'English', + lastUpdated: 'Last Updated', + nav: [ + { + text: 'Guide', + link: '/guide/', + }, + { + text: 'API Reference', + link: '/api/', + }, + { + text: 'Migration', + link: '/migration/', + }, + { + text: 'CLI plugin', + link: 'https://github.com/Akryum/vue-cli-plugin-apollo', + }, + { + text: 'Patreon', + link: 'https://www.patreon.com/akryum', + }, + ], + sidebarDepth: 3, + sidebar: { + '/guide/': [ + '', + 'installation', + { + title: 'Basic Usage', + collapsable: false, + children: [ + 'apollo/', + 'apollo/queries', + 'apollo/mutations', + 'apollo/subscriptions', + 'apollo/pagination', + ], + }, + { + title: 'Components', + collapsable: false, + children: [ + 'components/', + 'components/query', + 'components/mutation', + 'components/subscribe-to-more', + ], + }, + { + title: 'Advanced topics', + collapsable: false, + children: [ + 'multiple-clients', + 'ssr', + 'local-state', + 'testing', + ], + }, ], - }, - { - title: 'Components', - collapsable: false, - children: [ - 'components/', - 'components/query', - 'components/mutation', - 'components/subscribe-to-more', + '/api/': [ + { + title: 'Vue Apollo', + collapsable: false, + children: [ + 'apollo-provider', + 'dollar-apollo', + 'ssr', + ], + }, + { + title: 'Smart Apollo', + collapsable: false, + children: [ + 'smart-query', + 'smart-subscription', + ], + }, + { + title: 'Apollo Components', + collapsable: false, + children: [ + 'apollo-query', + 'apollo-subscribe-to-more', + 'apollo-mutation', + ], + }, ], + '/migration/': [''], }, - { - title: 'Advanced topics', - collapsable: false, - children: [ - 'multiple-clients', - 'ssr', - 'local-state', - 'testing', - ], - }, - ], - '/api/': [ - { - title: 'Vue Apollo', - collapsable: false, - children: [ - 'apollo-provider', - 'dollar-apollo', - 'ssr', - ], - }, - { - title: 'Smart Apollo', - collapsable: false, - children: [ - 'smart-query', - 'smart-subscription', + }, + '/zh-cn/': { + selectText: '选择语言', + label: '简体中文', + editLinks: true, + lastUpdated: '上次更新时间', + nav: [ + { + text: '指南', + link: '/zh-cn/guide/', + }, + { + text: 'API 参考', + link: '/zh-cn/api/', + }, + { + text: '迁移', + link: '/zh-cn/migration/', + }, + { + text: 'CLI 插件', + link: 'https://github.com/Akryum/vue-cli-plugin-apollo', + }, + { + text: '赞助作者', + link: 'https://www.patreon.com/akryum', + }, + ], + sidebarDepth: 3, + sidebar: { + '/zh-cn/guide/': [ + '', + 'installation', + { + title: '基本使用', + collapsable: false, + children: [ + 'apollo/', + 'apollo/queries', + 'apollo/mutations', + 'apollo/subscriptions', + 'apollo/pagination', + ], + }, + { + title: '组件', + collapsable: false, + children: [ + 'components/', + 'components/query', + 'components/mutation', + 'components/subscribe-to-more', + ], + }, + { + title: '进阶', + collapsable: false, + children: [ + 'multiple-clients', + 'ssr', + 'local-state', + 'testing', + ], + }, ], - }, - { - title: 'Apollo Components', - collapsable: false, - children: [ - 'apollo-query', - 'apollo-subscribe-to-more', - 'apollo-mutation', + '/zh-cn/api/': [ + { + title: 'Vue Apollo', + collapsable: false, + children: [ + 'apollo-provider', + 'dollar-apollo', + 'ssr', + ], + }, + { + title: 'Smart Apollo', + collapsable: false, + children: [ + 'smart-query', + 'smart-subscription', + ], + }, + { + title: 'Apollo 组件', + collapsable: false, + children: [ + 'apollo-query', + 'apollo-subscribe-to-more', + 'apollo-mutation', + ], + }, ], + '/zh-cn/migration/': [''], }, - ], - '/migration/': [''], + }, }, }, } diff --git a/docs/zh-cn/README.md b/docs/zh-cn/README.md new file mode 100644 index 00000000..3b69ee15 --- /dev/null +++ b/docs/zh-cn/README.md @@ -0,0 +1,20 @@ +--- +home: true +heroImage: /logo.png +actionText: 由此起步 → +actionLink: /zh-cn/guide/ +features: +- title: 自动更新 + details: 无需考虑更新 UI 或重新获取查询的问题! +- title: 模板内组件 + details: 通过 Apollo 组件声明式地使用 Apollo +- title: 支持 SSR + details: 在渲染 HTML 页面之前在服务端运行你的查询 +footer: LICENCE ISC - Created by Guillaume CHAU (@Akryum) +--- + +

+ + Become a Patreon + +

diff --git a/docs/zh-cn/api/README.md b/docs/zh-cn/api/README.md new file mode 100644 index 00000000..020e8ff0 --- /dev/null +++ b/docs/zh-cn/api/README.md @@ -0,0 +1,7 @@ +# API 参考 + +欢迎查看 API 参考! + +::: warning Work-in-Progress +如果你发现缺少了某些东西,请创建一个代码合并请求! +::: diff --git a/docs/zh-cn/api/apollo-mutation.md b/docs/zh-cn/api/apollo-mutation.md new file mode 100644 index 00000000..8b987442 --- /dev/null +++ b/docs/zh-cn/api/apollo-mutation.md @@ -0,0 +1,23 @@ +# ApolloMutation 组件 + +## Props + +- `mutation`:GraphQL 查询(由 `graphql-tag` 转换) +- `variables`:GraphQL 变量对象 +- `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 中定义) +- `tag`:字符串,HTML 标签名(默认值:`div`);如果是 `undefined`,该组件将成为无渲染组件(内容不会被包装在标签中) + +## 作用域插槽 props + +- `mutate(options = undefined)`:调用变更的函数。你可以重载变更的选项(例如:`mutate({ variables: { foo: 'bar } })`) +- `loading`:布尔值,表明请求正在进行中 +- `error`:最后一次变更调用的最终错误 +- `gqlError`:第一个 GraphQL 错误(如果有) + +## 事件 + +- `done(resultObject)` +- `error(errorObject)` diff --git a/docs/zh-cn/api/apollo-provider.md b/docs/zh-cn/api/apollo-provider.md new file mode 100644 index 00000000..a22c9c3e --- /dev/null +++ b/docs/zh-cn/api/apollo-provider.md @@ -0,0 +1,47 @@ +# ApolloProvider + +## 构造函数 + +```js +const apolloProvider = new VueApollo({ + // 支持多客户端 + // 在查询中使用 'client' 选项 + // 或在 apollo 定义中使用 '$client' + clients: { + a: apolloClientA, + b: apolloClientB, + }, + // 默认客户端 + defaultClient: apolloClient, + // 'apollo' 对象的默认定义 + defaultOptions: { + // 详见 'apollo' 的定义 + // 例如:默认查询选项 + $query: { + loadingKey: 'loading', + fetchPolicy: 'cache-and-network', + }, + }, + // 查看所有查询的加载状态 + // 详见 '智能查询 > 选项 > watchLoading' + watchLoading (isLoading, countModifier) { + loading += countModifier + console.log('Global loading', loading, countModifier) + }, + // 所有智能查询和订阅的全局错误处理函数 + errorHandler (error) { + console.log('Global error handler') + console.error(error) + }, +}) +``` + +在你的 Vue 应用程序中使用 apollo provider: + +```js +new Vue({ + el: '#app', + apolloProvider, + render: h => h(App), +}) +``` diff --git a/docs/zh-cn/api/apollo-query.md b/docs/zh-cn/api/apollo-query.md new file mode 100644 index 00000000..2e14fa86 --- /dev/null +++ b/docs/zh-cn/api/apollo-query.md @@ -0,0 +1,33 @@ +# ApolloQuery 组件 + +## Props + +- `query`:GraphQL 查询(由 `graphql-tag` 转换) +- `variables`:GraphQL 变量对象 +- `fetchPolicy`:详见 [apollo fetchPolicy](https://www.apollographql.com/docs/react/basics/queries.html#graphql-config-options-fetchPolicy) +- `pollInterval`:详见 [apollo pollInterval](https://www.apollographql.com/docs/react/basics/queries.html#graphql-config-options-pollInterval) +- `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 中定义) +- `deep`:布尔值,使用深度 Vue 侦听器 +- `tag`:字符串,HTML 标签名(默认值:`div`);如果是 `undefined`,该组件将成为无渲染组件(内容不会被包装在标签中) +- `debounce`:对重新获取查询结果的防抖毫秒数(例如当变量更改时) +- `throttle`:对重新获取查询结果的节流毫秒数(例如当变量更改时) + +## 作用域插槽 + +- `result`:Apollo 查询结果 + - `result.data`:查询返回的数据 + - `result.loading`:布尔值,表明请求正在进行中 + - `result.error`:当前结果的最终错误 + - `result.networkStatus`:详见 [apollo networkStatus](https://www.apollographql.com/docs/react/basics/queries.html#graphql-query-data-networkStatus) +- `query`:与组件关联的智能查询 +- `isLoading`:智能查询加载状态 +- `gqlError`:第一个 GraphQL 错误(如果有) +- `times`:结果被更新的次数 + +## 事件 + +- `result(resultObject)` +- `error(errorObject)` diff --git a/docs/zh-cn/api/apollo-subscribe-to-more.md b/docs/zh-cn/api/apollo-subscribe-to-more.md new file mode 100644 index 00000000..4e1ceba9 --- /dev/null +++ b/docs/zh-cn/api/apollo-subscribe-to-more.md @@ -0,0 +1,7 @@ +# ApolloSubscribeToMore 组件 + +## Props + +- `document`:包含订阅的 GraphQL 文档。 +- `variables`:将自动更新订阅变量的对象。 +- `updateQuery`:可以根据需要更新查询结果的函数。 diff --git a/docs/zh-cn/api/dollar-apollo.md b/docs/zh-cn/api/dollar-apollo.md new file mode 100644 index 00000000..84bd3e2c --- /dev/null +++ b/docs/zh-cn/api/dollar-apollo.md @@ -0,0 +1,23 @@ +# Dollar Apollo + +这是添加到任何使用 Apollo 的组件中的 Apollo 管理器。它可以在一个组件内通过 `this.$apollo` 访问到。 + +## 属性 + +- `vm`:关联的组件。 +- `queries`:组件的智能查询的数组。 +- `subscriptions`:组件的智能订阅的数组。 +- `client`:组件当前使用的 Apollo 客户端。 +- `provider`:注入的 [Apollo Provider](./apollo-provider.md)。 +- `loading`:是否至少有一个查询正在加载。 +- `skipAllQueries`:(setter) 布尔值,用于暂停或取消暂停所有智能查询。 +- `skipAllSubscriptions`:(setter) 布尔值,用于暂停或取消暂停所有智能订阅。 +- `skipAll`:(setter) 布尔值,用于暂停或取消暂停所有智能查询和智能订阅。 + +## 方法 + +- `query`:执行一个查询(详见 [查询](../guide/apollo/queries.md))。 +- `mutate`:执行一个变更(详见 [变更](../guide/apollo/mutations.md))。 +- `subscribe`:标准的 Apollo 订阅方法(详见 [订阅](../guide/apollo/subscriptions.md))。 +- `addSmartQuery`:手动添加一个智能查询(不推荐使用)。 +- `addSmartSubscription`:添加一个智能订阅(详见 [订阅](../guide/apollo/subscriptions.md))。 \ No newline at end of file diff --git a/docs/zh-cn/api/smart-query.md b/docs/zh-cn/api/smart-query.md new file mode 100644 index 00000000..4e1947c4 --- /dev/null +++ b/docs/zh-cn/api/smart-query.md @@ -0,0 +1,246 @@ +# 智能查询 + +在组件的 `apollo` 定义中声明的每个查询(不以 `$` 字符开头)都会创建一个智能查询对象。 + +## 选项 + +- `query`:GraphQL 文档(可以是一个文件或一个 `gql` 字符串)。 +- `variables`:对象或返回对象的响应式函数。每个键将用 `'$'` 映射到 GraphQL 文档中,例如 `foo` 将变为 `$foo`。 +- `throttle`:变量更新节流时间(毫秒)。 +- `debounce`:变量更新防抖时间(毫秒)。 +- `update(data) {return ...}` 用来自定义设置到 vue 属性中的值,例如当字段名称不匹配时。 +- `result(ApolloQueryResult)` 是收到结果时调用的钩子(更多参见 [ApolloQueryResult](https://github.com/apollographql/apollo-client/blob/master/packages/apollo-client/src/core/types.ts) 的文档)。 +- `error(error)` 是有错误时调用的钩子。`error` 是一个具有 `graphQLErrors` 属性或 `networkError` 属性的 Apollo 错误对象。 +- `loadingKey` 将更新你传递的值所对应的组件数据属性。你应该在组件的 `data()` 钩子中将此属性初始化为 `0` 。当查询正在加载时,此属性将增加 1;当不再加载时,它将减去 1。这样,该属性可以表示当前正在加载中的查询的计数器。 +- `watchLoading(isLoading, countModifier)` 是一个在查询的加载状态发生变化时调用的钩子。`countModifier` 参数当查询正在加载时等于 `1`,不再加载时为 `-1`。 +- `manual` 是一个禁用自动属性更新的布尔值。如果使用它,你需要指定一个 `result` 回调函数(参见下面的示例)。 +- `deep` 是一个在 Vue 侦听器上使用 `deep: true` 的布尔值。 +- `subscribeToMore`:一个或一组 [subscribeToMore 选项](../guide/apollo/subscriptions.md#subscribetomore) 对象。 +- `prefetch` 是一个布尔值或函数来确定是否应该预取查询。详见 [服务端渲染](../guide/ssr.md)。 + +示例: + +```js +// Apollo 具体选项 +apollo: { + // 带参数的高级查询 + // vue 会侦听 'variables' 方法 + pingMessage: { + query: gql`query PingMessage($message: String!) { + ping(message: $message) + }`, + // 响应式参数 + variables() { + // 在这里使用 vue 的响应式属性 + return { + message: this.pingInput, + } + }, + // 变量:深度对象侦听 + deep: false, + // 我们使用自定义更新回调,因为字段名称不匹配 + // 默认情况下,将使用 'data' 结果对象上的 'pingMessage' 属性 + // 考虑到 apollo 服务端的工作方式,我们知道结果是在 'ping' 属性中 + update(data) { + console.log(data) + // 返回的值将更新 vue 属性 'pingMessage' + return data.ping + }, + // 可选结果钩子 + result({ data, loading, networkStatus }) { + console.log("We got some result!") + }, + // 错误处理 + error(error) { + console.error('We\'ve got an error!', error) + }, + // 加载状态 + // loadingKey 是数据属性的名称 + // 在查询正在加载时将递增,不再加载时递减 + loadingKey: 'loadingQueriesCount', + // 当加载状态发生变化时会调用 watchLoading + watchLoading(isLoading, countModifier) { + // isLoading 是一个布尔值 + // countModifier 为 1 或 -1 + }, + }, +}, +``` + +如果你使用 `ES2015`,`update` 也可以这样写: + +```js +update: data => data.ping +``` + +手动模式示例: + +```js +{ + query: gql`...`, + manual: true, + result ({ data, loading }) { + if (!loading) { + this.items = data.items + } + }, +} +``` + +## 属性 + +### Skip + +你可以使用 `skip` 来暂停或停止暂停: + +```js +this.$apollo.queries.users.skip = true +``` + +### loading + +查询是否正在加载中: + +```js +this.$apollo.queries.users.loading +``` + +## 方法 + +### refresh + +停止并重新启动查询: + +```js +this.$apollo.queries.users.refresh() +``` + +### start + +开始查询: + +```js +this.$apollo.queries.users.start() +``` + +### stop + +停止查询: + +```js +this.$apollo.queries.users.stop() +``` + +### fetchMore + +为分页加载更多数据: + +```js +this.page++ + +this.$apollo.queries.tagsPage.fetchMore({ + // 新的变量 + variables: { + page: this.page, + pageSize, + }, + // 用新数据转换之前的结果 + updateQuery: (previousResult, { fetchMoreResult }) => { + const newTags = fetchMoreResult.tagsPage.tags + const hasMore = fetchMoreResult.tagsPage.hasMore + + this.showMoreEnabled = hasMore + + return { + tagsPage: { + __typename: previousResult.tagsPage.__typename, + // 合并标签列表 + tags: [...previousResult.tagsPage.tags, ...newTags], + hasMore, + }, + } + }, +}) +``` + +### subscribeToMore + +使用 GraphQL 订阅来订阅更多数据: + +```js +// 我们需要在重新订阅之前取消订阅 +if (this.tagsSub) { + this.tagsSub.unsubscribe() +} +// 在查询上订阅 +this.tagsSub = this.$apollo.queries.tags.subscribeToMore({ + document: TAG_ADDED, + variables: { + type, + }, + // 变更之前的结果 + updateQuery: (previousResult, { subscriptionData }) => { + // 如果我们在没有做操作的情况下已经添加了标签 + // 这可能是由 addTag 变更上的 `updateQuery` 导致 + if (previousResult.tags.find(tag => tag.id === subscriptionData.data.tagAdded.id)) { + return previousResult + } + + return { + tags: [ + ...previousResult.tags, + // 添加新的标签 + subscriptionData.data.tagAdded, + ], + } + }, +}) +``` + +### refetch + +重新获取查询,可选择使用新变量: + +```js +this.$apollo.queries.users.refetch() +// 使用新变量 +this.$apollo.queries.users.refetch({ + friendsOf: 'id-user' +}) +``` + +### setVariables + +更新查询中的变量,如果发生了改变则重新获取查询。要强制重新获取,请使用 `refetch`。 + +```js +this.$apollo.queries.users.setVariables({ + friendsOf: 'id-user' +}) +``` + +### setOptions + +更新 Apollo [watchQuery](https://www.apollographql.com/docs/react/api/apollo-client.html#ApolloClient.watchQuery) 选项并重新获取: + +```js +this.$apollo.queries.users.setOptions({ + fetchPolicy: 'cache-and-network' +}) +``` + +### startPolling + +使用轮询启动自动更新(这意味着每隔 `x` ms 进行重新获取): + +```js +this.$apollo.queries.users.startPolling(2000) // ms +``` + +### stopPolling + +停止轮询: + +```js +this.$apollo.queries.users.stopPolling() +``` diff --git a/docs/zh-cn/api/smart-subscription.md b/docs/zh-cn/api/smart-subscription.md new file mode 100644 index 00000000..71ca7cab --- /dev/null +++ b/docs/zh-cn/api/smart-subscription.md @@ -0,0 +1,47 @@ +# 智能订阅 + +每个在组件中的 `apollo.$subscribe` 选项中声明的订阅都会创建一个智能订阅对象。 + +## 选项 + +- `query`:GraphQL 文档(可以是一个文件或一个 `gql` 字符串)。 +- `variables`:对象或返回对象的响应式函数。每个键将用 `'$'` 映射到 GraphQL 文档中,例如 `foo` 将变为 `$foo`。 +- `throttle`:变量更新节流时间(毫秒)。 +- `debounce`:变量更新防抖时间(毫秒)。 +- `result(data)` 是收到结果时调用的钩子。 + +## 属性 + +### Skip + +你可以使用 `skip` 来暂停或停止暂停: + +```js +this.$apollo.subscriptions.users.skip = true +``` + +## 方法 + +### refresh + +停止并重新启动查询: + +```js +this.$apollo.subscriptions.users.restart() +``` + +### start + +开始查询: + +```js +this.$apollo.subscriptions.users.start() +``` + +### stop + +停止查询: + +```js +this.$apollo.subscriptions.users.stop() +``` diff --git a/docs/zh-cn/api/ssr.md b/docs/zh-cn/api/ssr.md new file mode 100644 index 00000000..b8176a3e --- /dev/null +++ b/docs/zh-cn/api/ssr.md @@ -0,0 +1,55 @@ +# ApolloSSR + +## 用法 + +详见 [SSR 指南](../guide/ssr.md). + +## 方法 + +### prefetchAll + +预取所有队列中的组件定义,并在所有对应的 apollo 数据准备就绪时返回已解决的(resolved) promise。 + +```js +await ApolloSSR.prefetchAll (apolloProvider, componentDefs, context) +``` + +`context` 作为参数传递给智能查询中的 `prefetch` 选项。它可能包含路由和 store。 + +### getStates + +将 apollo store 状态作为 JavaScript 对象返回。 + +```js +const states = ApolloSSR.getStates(apolloProvider, options) +``` + +`options` 的默认值是: + +```js +{ + // 每个 apollo 客户端状态的 key 的前缀 + exportNamespace: '', +} +``` + +### exportStates + +将 apollo store 状态作为字符串内的 JavaScript 代码返回。该代码可以直接注入到页面 HTML 的 ` +``` + +你可以在 apollo provider 中为 `apollo` 定义一套默认选项。例如: + +```js +const apolloProvider = new VueApollo({ + defaultClient: apolloClient, + defaultOptions: { + // 将应用于组件中的所有查询的 apollo 选项 + $query: { + loadingKey: 'loading', + fetchPolicy: 'cache-and-network', + }, + }, +}) +``` + +## 全部跳过 + +你可以使用 `skipAllQueries` 禁用组件的所有查询,使用 `skipAllSubscriptions` 禁用所有订阅,或是使用 `skipAll` 将两者全部禁用: + +```js +this.$apollo.skipAllQueries = true +this.$apollo.skipAllSubscriptions = true +this.$apollo.skipAll = true +``` + +你也可以在组件的 `apollo` 选项中声明这些属性。它们可以是布尔值: + +```js +apollo: { + $skipAll: true +} +``` + +或是响应式函数: + +```js +apollo: { + $skipAll () { + return this.foo === 42 + } +} +``` \ No newline at end of file diff --git a/docs/zh-cn/guide/apollo/mutations.md b/docs/zh-cn/guide/apollo/mutations.md new file mode 100644 index 00000000..4372922c --- /dev/null +++ b/docs/zh-cn/guide/apollo/mutations.md @@ -0,0 +1,121 @@ +# 变更 + +变更是在你的 apollo 服务端更改你的数据状态的查询。 + +使用 `this.$apollo.mutate()` 来发送一个 GraphQL 变更。 + +想要了解更多信息,请访问 [apollo 文档](https://www.apollographql.com/docs/react/api/apollo-client.html#ApolloClient.mutate)。有一个以变更为重点的 [示例应用](https://github.com/Akryum/vue-apollo-todos),你可以看看。 + +::: warning +你不应当在 variables 中发送 `__typename` 字段,因此不建议直接发送 Apollo 结果对象。 +::: + +```js +methods: { + addTag() { + // 保存用户输入以防止错误 + const newTag = this.newTag + // 将其清除以尽早更新用户页面 + this.newTag = '' + // 调用 graphql 变更 + this.$apollo.mutate({ + // 查询语句 + mutation: gql`mutation ($label: String!) { + addTag(label: $label) { + id + label + } + }`, + // 参数 + variables: { + label: newTag, + }, + // 用结果更新缓存 + // 查询将先通过乐观响应、然后再通过真正的变更结果更新 + update: (store, { data: { newTag } }) => { + // 从缓存中读取这个查询的数据 + const data = store.readQuery({ query: TAGS_QUERY }) + // 将变更中的标签添加到最后 + data.tags.push(newTag) + // 将数据写回缓存 + store.writeQuery({ query: TAGS_QUERY, data }) + }, + // 乐观 UI + // 将在请求产生时作为“假”结果,使用户界面能够快速更新 + optimisticResponse: { + __typename: 'Mutation', + addTag: { + __typename: 'Tag', + id: -1, + label: newTag, + }, + }, + }).then((data) => { + // 结果 + console.log(data) + }).catch((error) => { + // 错误 + console.error(error) + // 恢复初始用户输入 + this.newTag = newTag + }) + }, +}, +``` + +## 服务端示例 + +```js +export const schema = ` +type Tag { + id: Int + label: String +} + +type Query { + tags: [Tag] +} + +type Mutation { + addTag(label: String!): Tag +} + +schema { + query: Query + mutation: Mutation +} +` + +// 假数据生成器 +import faker from 'faker' + +// 生成一些标签 +var id = 0 +var tags = [] +for (let i = 0; i < 42; i++) { + addTag(faker.random.word()) +} + +function addTag(label) { + let t = { + id: id++, + label, + } + tags.push(t) + return t +} + +export const resolvers = { + Query: { + tags(root, args, context) { + return tags + }, + }, + Mutation: { + addTag(root, { label }, context) { + console.log(`adding tag '${label}'`) + return addTag(label) + }, + }, +} +``` \ No newline at end of file diff --git a/docs/zh-cn/guide/apollo/pagination.md b/docs/zh-cn/guide/apollo/pagination.md new file mode 100644 index 00000000..bad872cf --- /dev/null +++ b/docs/zh-cn/guide/apollo/pagination.md @@ -0,0 +1,95 @@ +# 使用 `fetchMore` 实现分页 + +*[这里](https://github.com/Akryum/apollo-server-example/blob/master/schema.js#L21) 是服务端的一个简单示例。* + +有时候你的数据集非常大,你想分块加载它。 + +使用智能查询上的 `fetchMore()` 方法来加载更多数据。 + +::: warning +不要忘记将 `__typename` 包含到新结果中。 + +使用 `variables()` 时不要更改返回的初始变量,否则列表数据将丢失。 +::: + +示例: + +```vue + + + +``` diff --git a/docs/zh-cn/guide/apollo/queries.md b/docs/zh-cn/guide/apollo/queries.md new file mode 100644 index 00000000..23274bb3 --- /dev/null +++ b/docs/zh-cn/guide/apollo/queries.md @@ -0,0 +1,401 @@ +# 查询 + +为每个你需要通过 Apollo 的查询结果提供数据的 Vue 属性,在 `apollo` 对象中添加一个对应属性。每一个属性都将创建一个智能查询。 + +## 简单查询 + +使用 `gql` 编写你的 GraphQL 查询: + +```js +import gql from 'graphql-tag' +``` + +直接将 [gql](https://github.com/apollographql/graphql-tag) 查询作为值: + +```js +apollo: { + // 简单的查询,将更新 'hello' 这个 vue 属性 + hello: gql`{hello}`, +}, +``` + +接下来你可以通过 `this.$apollo.queries.` 访问这个查询。 + +你可以在 vue 组件的 `data` 钩子中初始化属性: + +```js +data () { + return { + // 初始化你的 apollo 数据 + hello: '', + }, +}, +``` + +在服务端添加相应的 schema 和解析器: + +```js +export const schema = ` +type Query { + hello: String +} + +schema { + query: Query +} +` + +export const resolvers = { + Query: { + hello(root, args, context) { + return "Hello world!" + }, + }, +} +``` + +更多信息请访问 [apollo 文档](https://www.apollographql.com/docs/apollo-server/)。 + +接下来你可以在 vue 组件中正常使用属性: + +```vue + +``` + +## 带参数的查询 + +你可以通过在对象中声明 `query` 和 `variables` 将变量(读取参数)添加到 `gql` 查询中: + +```js +// Apollo 具体选项 +apollo: { + // 带参数的查询 + ping: { + // gql 查询 + query: gql`query PingMessage($message: String!) { + ping(message: $message) + }`, + // 静态参数 + variables: { + message: 'Meow', + }, + }, +}, +``` + +你可以在这个对象中使用 apollo 的 `watchQuery` 中的选项,比如: + - `fetchPolicy` + - `pollInterval` + - ... + +更多细节请查看 [apollo 文档](https://www.apollographql.com/docs/react/api/apollo-client.html#ApolloClient.watchQuery)。 + +例如,你可以像这样添加 `fetchPolicy` apollo 选项: + +```js +apollo: { + // 带参数的查询 + ping: { + query: gql`query PingMessage($message: String!) { + ping(message: $message) + }`, + variables: { + message: 'Meow' + }, + // 在这里加入其他选项 + fetchPolicy: 'cache-and-network', + }, +}, +``` + +同样的,你可以在 vue 组件中初始化属性: + +```js +data () { + return { + // 初始化你的 apollo 数据 + ping: '', + } +}, +``` + +在服务端添加相应的 schema 和解析器: + +```js +export const schema = ` +type Query { + ping(message: String!): String +} + +schema { + query: Query +} +` + +export const resolvers = { + Query: { + ping(root, { message }, context) { + return `Answering ${message}` + }, + }, +} +``` + +然后在你的 vue 组件中使用它: + +```vue + +``` + +## 加载状态 + +你可以通过 `$apollo.loading` 属性显示加载状态: + +```vue +
Loading...
+``` + +或者针对这个特定的 `ping` 查询: + +```vue +
Loading...
+``` + +## 用函数作为选项 + +你可以使用将在创建组件时被调用一次的函数,并且它必须返回选项对象: + +```js +// Apollo 具体选项 +apollo: { + // 带参数的查询 + ping () { + // 它将在创建组件时被调用一次 + // 必须返回选项对象 + return { + // gql 查询 + query: gql`query PingMessage($message: String!) { + ping(message: $message) + }`, + // 静态参数 + variables: { + message: 'Meow', + }, + } + }, +}, +``` + +::: tip +同样适用于 [订阅](./subscriptions.md)。 +::: + +## 响应式查询定义 + +你可以使用函数定义 `query` 选项。这将自动更新 graphql 查询的定义: + +```js +// 特定标签可以是随机标签或最后添加的标签 +featuredTag: { + query () { + // 这里你可以用'this' 访问组件实例 + if (this.showTag === 'random') { + return gql`{ + randomTag { + id + label + type + } + }` + } else if (this.showTag === 'last') { + return gql`{ + lastTag { + id + label + type + } + }` + } + }, + // 为 'featuredTag' 这个组件属性赋值 + update: data => data.randomTag || data.lastTag, +}, +``` + +::: tip +同样适用于 [订阅](./subscriptions.md)。 +::: + +## 响应式参数 + +使用函数使 vue 属性能够响应式的提供给参数: + +```js +// Apollo 具体选项 +apollo: { + // 带参数的查询 + ping: { + query: gql`query PingMessage($message: String!) { + ping(message: $message) + }`, + // 响应式参数 + variables() { + // 在这里使用 vue 响应式属性 + return { + message: this.pingInput, + } + }, + }, +}, +``` + +在每次参数更改时,将重新获取查询,例如: + +```vue + +``` + +## 跳过查询 + +如果查询被跳过,它将被禁用且结果将不再被更新。你可以使用 `skip` 选项: + +```js +// Apollo 具体选项 +apollo: { + tags: { + // GraphQL 查询 + query: gql`query tagList ($type: String!) { + tags(type: $type) { + id + label + } + }`, + // 响应式变量 + variables() { + return { + type: this.type, + } + }, + // 禁用这个查询 + skip() { + return this.skipQuery + }, + }, +}, +``` + +在这里,当 `skipQuery` 组件属性改变时,`skip` 将被自动调用。 + +你也可以直接访问查询并设置 `skip` 属性: + +```js +this.$apollo.queries.tags.skip = true +``` + +## 响应式查询示例 + +这里是一个使用轮询的响应式查询示例: + +```js +// Apollo 具体选项 +apollo: { + // vue 实例上的 'tags' 数据属性 + tags: { + query: gql`query tagList { + tags { + id, + label + } + }`, + pollInterval: 300, // 毫秒 + }, +}, +``` + +这里是服务端的定义: + +```js +export const schema = ` +type Tag { + id: Int + label: String +} + +type Query { + tags: [Tag] +} + +schema { + query: Query +} +` + +// 假数据生成器 +import casual from 'casual' + +// 生成一些标签 +var id = 0 +var tags = [] +for (let i = 0; i < 42; i++) { + addTag(casual.word) +} + +function addTag(label) { + let t = { + id: id++, + label, + } + tags.push(t) + return t +} + +export const resolvers = { + Query: { + tags(root, args, context) { + return tags + }, + }, +} +``` + +## 手动添加智能查询 + +你可以使用 `$apollo.addSmartQuery(key, options)` 方法手动添加智能查询: + +```js +created () { + this.$apollo.addSmartQuery('comments', { + // 选项同上文 + }) +} +``` + +::: tip +组件 `apollo` 选项中的每个查询入口都在内部调用此方法。 +::: + +## 高级选项 + +还有更多专用于 vue-apollo 的选项,请查看 [API 参考](../../api/smart-query.md)。 diff --git a/docs/zh-cn/guide/apollo/subscriptions.md b/docs/zh-cn/guide/apollo/subscriptions.md new file mode 100644 index 00000000..28a803a0 --- /dev/null +++ b/docs/zh-cn/guide/apollo/subscriptions.md @@ -0,0 +1,310 @@ +# 订阅 + +## 设置 + +*关于服务端实现,你可以看看 [这个简单的示例](https://github.com/Akryum/apollo-server-example)。* + +要启用基于 websocket 的订阅,需要做一些额外的设置: + +``` +npm install --save apollo-link-ws apollo-utilities +``` + +```js +import Vue from 'vue' +import { ApolloClient } from 'apollo-client' +import { HttpLink } from 'apollo-link-http' +import { InMemoryCache } from 'apollo-cache-inmemory' +// 新的引入文件 +import { split } from 'apollo-link' +import { WebSocketLink } from 'apollo-link-ws' +import { getMainDefinition } from 'apollo-utilities' + +import VueApollo from 'vue-apollo' + +const httpLink = new HttpLink({ + // 你需要在这里使用绝对路径 + uri: 'http://localhost:3020/graphql', +}) + +// 创建订阅的 websocket 连接 +const wsLink = new WebSocketLink({ + uri: 'ws://localhost:3000/subscriptions', + options: { + reconnect: true, + }, +}) + +// 使用分割连接的功能 +// 你可以根据发送的操作类型将数据发送到不同的连接 +const link = split( + // 根据操作类型分割 + ({ query }) => { + const { kind, operation } = getMainDefinition(query) + return kind === 'OperationDefinition' && + operation === 'subscription' + }, + wsLink, + httpLink +) + +// 创建 apollo 客户端 +const apolloClient = new ApolloClient({ + link, + cache: new InMemoryCache(), + connectToDevTools: true, +}) + +// 像之前一样安装 vue 插件 +Vue.use(VueApollo) +``` + +## 订阅更多 + +如果你需要更新一个来自订阅的查询结果,最好的方式是使用 `subscribeToMore` 查询方法。它将创建链接到查询的 [智能订阅](../../api/smart-subscription.md)。你只需要将 `subscribeToMore` 添加到查询中: + +```js +apollo: { + tags: { + query: TAGS_QUERY, + subscribeToMore: { + document: gql`subscription name($param: String!) { + itemAdded(param: $param) { + id + label + } + }`, + // 传递给订阅的变量 + // 由于我们使用了函数,因此它们是响应式的 + variables () { + return { + param: this.param, + } + }, + // 变更之前的结果 + updateQuery: (previousResult, { subscriptionData }) => { + // 在这里用之前的结果和新数据组合成新的结果 + }, + } + } +} +``` + +::: tip +注意,你可以将一组订阅传递给 `subscribeToMore` 以将此查询关联到多个订阅。 +::: + +### Alternate usage + +你可以使用 `this.$apollo.queries.` 访问你在 `apollo` 选项中定义的查询,所以它看起来像这样: + +```js +this.$apollo.queries.tags.subscribeToMore({ + // GraphQL 文档 + document: gql`subscription name($param: String!) { + itemAdded(param: $param) { + id + label + } + }`, + // 传递给订阅的变量 + variables: { + param: '42', + }, + // 变更之前的结果 + updateQuery: (previousResult, { subscriptionData }) => { + // 在这里用之前的结果和新数据组合成新的结果 + }, +}) +``` + +如果相关查询停止,订阅将自动销毁。 + +这里是一个示例: + +```js +// 订阅的 GraphQL 文档 +const TAG_ADDED = gql`subscription tags($type: String!) { + tagAdded(type: $type) { + id + label + type + } +}` + +// SubscribeToMore 标签 +// 我们有不同类型的标签 +// 每种类型都有一个订阅 '频道' +this.$watch(() => this.type, (type, oldType) => { + if (type !== oldType || !this.tagsSub) { + // 我们需要在重新订阅之前取消订阅 + if (this.tagsSub) { + this.tagsSub.unsubscribe() + } + // 在查询上订阅 + this.tagsSub = this.$apollo.queries.tags.subscribeToMore({ + document: TAG_ADDED, + variables: { + type, + }, + // 变更之前的结果 + updateQuery: (previousResult, { subscriptionData }) => { + // 如果我们在没有做操作的情况下已经添加了标签 + // 这可能是由 addTag 变更上的 `updateQuery` 导致 + if (previousResult.tags.find(tag => tag.id === subscriptionData.data.tagAdded.id)) { + return previousResult + } + + return { + tags: [ + ...previousResult.tags, + // 添加新的标签 + subscriptionData.data.tagAdded, + ], + } + }, + }) + } +}, { + immediate: true, +}) +``` + +## 简单订阅 + +::: danger +如果要使用订阅的结果更新查询,请使用 `subscribeToMore`。 +以下的方法适用于 'notify' 用例 +::: + +你可以在 `apollo` 选项中使用 `$subscribe` 关键字来声明 [智能订阅](../../api/smart-subscription.md): + +```js +apollo: { + // 订阅 + $subscribe: { + // 当添加一个标签时 + tagAdded: { + query: gql`subscription tags($type: String!) { + tagAdded(type: $type) { + id + label + type + } + }`, + // 响应式变量 + variables() { + // 像常规查询一样运作 + // 在每次改变值时都会使用正确的变量重新订阅 + return { + type: this.type, + } + }, + // 结果钩子 + result(data) { + console.log(data) + }, + }, + }, +}, +``` + +你可以使用 `this.$apollo.subscriptions.` 访问这个订阅。 + +:::tip +和查询一样,你可以 [使用函数](./queries.md#option-function) 声明订阅,并且可以 [使用响应式函数](./queries.md#reactive-query-definition) 声明 `query` 选项。 +::: + +## 跳过订阅 + +如果订阅被跳过,它将被禁用且不再被更新。你可以使用 `skip` 选项: + +```js +// Apollo 具体选项 +apollo: { + // 订阅 + $subscribe: { + // 当添加一个标签时 + tags: { + query: gql`subscription tags($type: String!) { + tagAdded(type: $type) { + id + label + type + } + }`, + // 响应式变量 + variables() { + return { + type: this.type, + } + }, + // 结果钩子 + result(data) { + // 更新本地数据 + this.tags.push(data.tagAdded) + }, + // 跳过这个订阅 + skip() { + return this.skipSubscription + } + }, + }, +}, +``` + +在这里,当 `skipSubscription` 组件属性改变时,`skip` 将被自动调用。 + +你也可以直接访问订阅并设置 `skip` 属性: + +```js +this.$apollo.subscriptions.tags.skip = true +``` + +## 手动添加智能订阅 + +你可以使用 `$apollo.addSmartSubscription(key, options)` 方法手动添加智能订阅: + +```js +created () { + this.$apollo.addSmartSubscription('tagAdded', { + // 选项同 '$subscribe' + }) +} +``` + +:::tip +组件 `apollo` 选项中的每个 `$subscribe` 对象入口都在内部调用此方法。 +::: + +## 标准 Apollo 订阅 + +使用 `$apollo.subscribe()` 方法来创建一个 GraphQL 订阅,当组件被销毁时将自动终止。它**不会**创建智能订阅。 + +```js +mounted() { + const subQuery = gql`subscription tags($type: String!) { + tagAdded(type: $type) { + id + label + type + } + }` + + const observer = this.$apollo.subscribe({ + query: subQuery, + variables: { + type: 'City', + }, + }) + + observer.subscribe({ + next(data) { + console.log(data) + }, + error(error) { + console.error(error) + }, + }) +}, +``` diff --git a/docs/zh-cn/guide/components/README.md b/docs/zh-cn/guide/components/README.md new file mode 100644 index 00000000..0ebbbc00 --- /dev/null +++ b/docs/zh-cn/guide/components/README.md @@ -0,0 +1,37 @@ +# 什么是 Apollo 组件? + +这些组件就像其他组件一样。它们在 prop 中使用 GraphQL 文档,并使用 [作用域插槽功能](https://vuejs.org/v2/guide/components-slots.html#Scoped-Slots) 来传递结果。 + +这样做的好处是你可以直接在模板中使用这些组件,而不是使用组件的 `apollo` 选项。在某些情况下,你甚至不需要在 `.vue` 中添加脚本部分!这种代码会更加声明式。 + +这是一个简单的例子: + +```vue + + + + + +``` diff --git a/docs/zh-cn/guide/components/mutation.md b/docs/zh-cn/guide/components/mutation.md new file mode 100644 index 00000000..d4724865 --- /dev/null +++ b/docs/zh-cn/guide/components/mutation.md @@ -0,0 +1,23 @@ +# ApolloMutation + +你可以使用 `ApolloMutation`(或 `apollo-mutation`)组件直接在模板中调用 Apollo 变更。 + +这是一个简单的例子: + +```vue + + + +``` + +更多参见 [API 参考](../../api/apollo-mutation.md). diff --git a/docs/zh-cn/guide/components/query.md b/docs/zh-cn/guide/components/query.md new file mode 100644 index 00000000..62fa0b52 --- /dev/null +++ b/docs/zh-cn/guide/components/query.md @@ -0,0 +1,28 @@ +# ApolloQuery + +你可以使用 `ApolloQuery`(或 `apollo-query`)组件直接在模板中侦听 Apollo 查询。 + +这是一个简单的例子: + +```vue + + + +``` + +更多参见 [API 参考](../../api/apollo-query.md). diff --git a/docs/zh-cn/guide/components/subscribe-to-more.md b/docs/zh-cn/guide/components/subscribe-to-more.md new file mode 100644 index 00000000..f3b46397 --- /dev/null +++ b/docs/zh-cn/guide/components/subscribe-to-more.md @@ -0,0 +1,88 @@ +# ApolloSubscribeToMore + +你可以使用 `ApolloSubscribeToMore`(或 `apollo-subscribe-to-more`)组件订阅更多数据。你可以在一个 `` 组件中放置任意数量的订阅组件。 + +::: tip +如果更新关联到现有对象(例如更改某个字段的值),则不需要 `updateQuery`,因为 Apollo 客户端能够自动更新缓存。 +::: + +这是一个简单的例子: + +```vue + + + +``` + +更多参见 [API 参考](../../api/apollo-subscribe-to-more.md). + +## `updateQuery` 的示例 + +将新项添加到缓存中: + +```js +methods: { + onMessageAdded (previousResult, { subscriptionData }) { + // 之前的结果是不可变的 + const newResult = { + messages: [...previousResult.messages], + } + // 添加问题到列表中 + newResult.messages.push(subscriptionData.data.messageAdded) + return newResult + } +} +``` + +从缓存中删除一项: + +```js +methods: { + onMessageAdded (previousResult, { subscriptionData }) { + const removedMessage = subscriptionData.data.messageRemoved + const index = previousResult.messages.findIndex( + m => m.id === removedMessage.id + ) + + if (index === -1) return previousResult + + // 之前的结果是不可变的 + const newResult = { + messages: [...previousResult.messages], + } + // 从列表中移除问题 + newResult.messages.splice(index, 1) + return newResult + } +} +``` diff --git a/docs/zh-cn/guide/installation.md b/docs/zh-cn/guide/installation.md new file mode 100644 index 00000000..6fd303b9 --- /dev/null +++ b/docs/zh-cn/guide/installation.md @@ -0,0 +1,108 @@ +# 安装 + +## Vue CLI Plugin + +我为 [vue-cli](http://cli.vuejs.org) 制作了一个插件,因此仅用两分钟你就可以添加 Apollo(附带一个可选的 GraphQL 服务器)!✨🚀 + +在你的 vue-cli 3 项目中: + +```bash +vue add apollo +``` + +然后你可以跳到下一部分:[基本用法](./apollo/)。 + +[更多信息](https://github.com/Akryum/vue-cli-plugin-apollo) + +## Apollo Boost + +Apollo Boost 是一种零配置开始使用 Apollo Client 的方式。它包含一些实用的默认值,例如我们推荐的 `InMemoryCache` 和 `HttpLink`,它非常适合用于快速启动开发: + +安装: + +``` +npm install --save vue-apollo graphql apollo-boost +``` + +或: + +``` +yarn add vue-apollo graphql apollo-boost +``` + +### Apollo client + +在你的应用中创建一个 `ApolloClient` 实例并安装 `VueApollo` 插件: + +```js +import Vue from 'vue' +import ApolloClient from "apollo-boost" +import VueApollo from "vue-apollo" + +const apolloProvider = new VueApollo({ + defaultClient: new ApolloClient({ + uri: "https://api.graphcms.com/simple/v1/awesomeTalksClone" + }) +}) + +Vue.use(VueApollo) +``` + + + +## 手动操作 + +如果你想要更细的粒度控制,尝试在服务器端配置之前安装这些包,并且将 apollo 添加到 meteor.js 中。 + +``` +npm install --save vue-apollo graphql apollo-client apollo-link apollo-link-http apollo-cache-inmemory graphql-tag +``` + +或: + +``` +yarn add vue-apollo graphql apollo-client apollo-link apollo-link-http apollo-cache-inmemory graphql-tag +``` + +### Apollo 客户端 + +在你的应用中创建一个 `ApolloClient` 实例并安装 `VueApollo` 插件: + +```js +import Vue from 'vue' +import { ApolloClient } from 'apollo-client' +import { HttpLink } from 'apollo-link-http' +import { InMemoryCache } from 'apollo-cache-inmemory' +import VueApollo from 'vue-apollo' + +const httpLink = new HttpLink({ + // 你需要在这里使用绝对路径 + uri: 'http://localhost:3020/graphql', +}) + +// 创建 apollo 客户端 +const apolloClient = new ApolloClient({ + link: httpLink, + cache: new InMemoryCache(), + connectToDevTools: true, +}) + +const apolloProvider = new VueApollo({ + defaultClient: apolloClient, +}) + +// 安装 vue 插件 +Vue.use(VueApollo) +``` + +## Apollo provider + +Provider 保存了可以在接下来被所有子组件使用的 Apollo 客户端实例。通过 `provide` 属性将它注入你的组件: + +```js +new Vue({ + el: '#app', + apolloProvider, + render: h => h(App), +}) +``` \ No newline at end of file diff --git a/docs/zh-cn/guide/local-state.md b/docs/zh-cn/guide/local-state.md new file mode 100644 index 00000000..ff4d5e1a --- /dev/null +++ b/docs/zh-cn/guide/local-state.md @@ -0,0 +1,37 @@ +# 本地状态 + +如果你需要管理本地数据,你可以使用 [apollo-link-state](https://github.com/apollographql/apollo-link-state) 和 `@client` 指令来实现: + +```js +export default { + apollo: { + hello: gql` + query { + hello @client { + msg + } + } + ` + }, + mounted() { + // 变更 hello 消息 + this.$apollo + .mutate({ + mutation: gql` + mutation($msg: String!) { + updateHello(message: $msg) @client + } + `, + variables: { + msg: 'hello from link-state!' + } + }) + } +} +``` + +[示例项目](https://codesandbox.io/s/zqqj82396p) (感谢 @chriswingler) + +[Todo App](https://codesandbox.io/s/x2jr96r8pp) (感谢 @NikkitaFTW) + +--- \ No newline at end of file diff --git a/docs/zh-cn/guide/multiple-clients.md b/docs/zh-cn/guide/multiple-clients.md new file mode 100644 index 00000000..ecfcc219 --- /dev/null +++ b/docs/zh-cn/guide/multiple-clients.md @@ -0,0 +1,32 @@ +# 多客户端 + +如果你的应用需要连接到不同的 GraphQL 入口端点,你可以指定多个 apollo 客户端: + +```js +const apolloProvider = new VueApollo({ + clients: { + a: apolloClient, + b: otherApolloClient, + }, + defaultClient: apolloClient, +}) +``` + +在组件的 `apollo` 选项中,你可以使用 `$client` 为所有的查询、订阅和变更定义要使用的客户端(仅限在此组件内): + +```js +export default { + apollo: { + $client: 'b', + }, +} +``` + +你也可以在单个查询,订阅和变更的选项中使用 `client` 属性来指定客户端: + +```js +tags: { + query: gql`...`, + client: 'b', +} +``` \ No newline at end of file diff --git a/docs/zh-cn/guide/ssr.md b/docs/zh-cn/guide/ssr.md new file mode 100644 index 00000000..8eb42bf8 --- /dev/null +++ b/docs/zh-cn/guide/ssr.md @@ -0,0 +1,362 @@ +# 服务端渲染 + +## Vue CLI 插件 + +我为 [vue-cli](http://cli.vuejs.org) 制作了一个插件,因此仅用两分钟你就可以将你的 vue-apollo 应用转换为同构 SSR 应用!✨🚀 + +在你的 vue-cli 3 项目中: + +```bash +vue add @akryum/ssr +``` + +[更多信息](https://github.com/Akryum/vue-cli-plugin-ssr) + +## 预取组件 + +在要在服务端预取的查询上,添加 `prefetch` 选项。它可以是: + - 一个变量对象; + - 一个获取上下文对象(例如可以包含 URL)并返回一个变量对象的函数; + - `false` 禁用此查询的预取。 + +如果你在 `prefetch` 选项中返回一个变量对象,请确保它与 `variables` 选项的结果相匹配。如果它们不匹配,则在服务端渲染模板时,查询的数据属性将不会被填充。 + +::: danger +在服务端进行预取时,你无法访问组件实例。 +::: + +示例: + +```js +export default { + apollo: { + allPosts: { + // 此查询将被预取 + query: gql`query AllPosts { + allPosts { + id + imageUrl + description + } + }`, + prefetch: true, + } + } +} +``` + +示例 2: + +```js +export default { + apollo: { + post: { + query: gql`query Post($id: ID!) { + post (id: $id) { + id + imageUrl + description + } + }`, + prefetch: ({ route }) => { + return { + id: route.params.id, + } + }, + variables () { + return { + id: this.id, + } + }, + } + } +} +``` + +### 跳过预取 + +不预取查询的示例: + +```js +export default { + apollo: { + allPosts: { + query: gql`query AllPosts { + allPosts {} + id + imageUrl + description + } + }`, + // 不要预取 + prefetch: false, + } + } +} +``` + +如果要跳过特定组件的所有查询的预取,使用 `$prefetch` 选项: + +```js +export default { + apollo: { + // 不要预取任何查询 + $prefetch: false, + allPosts: { + query: gql`query AllPosts { + allPosts { + id + imageUrl + description + } + }`, + } + } +} +``` + +你也可以在任何组件上放置一个 `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) + +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 + +建议在一个带有 `ssr` 参数的函数内部创建 apollo 客户端,参数在服务端为 `true`,在客户端为 `false`。 + +这里是一个示例: + +```js +// src/api/apollo.js + +import Vue from 'vue' +import { ApolloClient } from 'apollo-client' +import { HttpLink } from 'apollo-link-http' +import { InMemoryCache } from 'apollo-cache-inmemory' +import VueApollo from 'vue-apollo' + +// 安装 vue 插件 +Vue.use(VueApollo) + +// 创建 apollo 客户端 +export function createApolloClient (ssr = false) { + const httpLink = new HttpLink({ + // 你需要在这里使用绝对路径 + uri: ENDPOINT + '/graphql', + }) + + const cache = new InMemoryCache() + + // 如果在客户端则恢复注入状态 + if (!ssr) { + if (typeof window !== 'undefined') { + const state = window.__APOLLO_STATE__ + if (state) { + // 如果你有多个客户端,使用 `state.` + cache.restore(state.defaultClient) + } + } + } + + const apolloClient = new ApolloClient({ + link: httpLink, + cache, + ...(ssr ? { + // 在服务端设置此选项以优化 SSR 时的查询 + ssrMode: true, + } : { + // 这将暂时禁用查询强制获取 + ssrForceFetchDelay: 100, + }), + }) + + return apolloClient +} +``` + +常见的 `CreateApp` 方法示例: + +```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 App from './ui/App.vue' +import routes from './routes' +import storeOptions from './store' + +Vue.use(VueRouter) +Vue.use(Vuex) + +function createApp (context) { + const router = new VueRouter({ + mode: 'history', + routes, + }) + + const store = new Vuex.Store(storeOptions) + + // 同步路由到 vuex store + // 将注册 `store.state.route` + sync(store, router) + + // Apollo + const apolloClient = createApolloClient(context.ssr) + const apolloProvider = new VueApollo({ + defaultClient: apolloClient, + }) + + return { + app: new Vue({ + el: '#app', + router, + store, + apolloProvider, + ...App, + }), + router, + store, + apolloProvider, + } +} + +export default createApp +``` + +在客户端: + +```js +import CreateApp from './app' + +CreateApp({ + ssr: false, +}) +``` + +在服务端: + +```js +import CreateApp from './app' + +export default () => new Promise((resolve, reject) => { + const { app, router, store, apolloProvider } = CreateApp({ + ssr: true, + }) + + // 设置 router 的位置 + router.push(context.url) + + // 等待 router 解析完可能的异步钩子 + router.onReady(() => { + // 预取,渲染 HTML(参见上文) + }) +}) +``` \ No newline at end of file diff --git a/docs/zh-cn/guide/testing.md b/docs/zh-cn/guide/testing.md new file mode 100644 index 00000000..2deb5fe7 --- /dev/null +++ b/docs/zh-cn/guide/testing.md @@ -0,0 +1,152 @@ +# 测试 + +要为 vue-apollo 查询和变更创建单元测试,你可以选择简单测试或使用模拟 GraqhQL schema 进行测试。所有的示例都使用了 [Jest](https://jestjs.io/) 和 [vue-test-utils](https://github.com/vuejs/vue-test-utils)。 + +## 简单测试 + +对于简单的查询测试,你只需要设置组件数据并检查组件如何使用 Jest 快照功能进行渲染。比如说,如果你有一个展示所有 Vue 英雄的查询,你可以添加一个包含单个英雄的模拟数组: + +```js +test('displayed heroes correctly with query data', () => { + const wrapper = shallowMount(App, { localVue }); + wrapper.setData({ + allHeroes: [ + { + id: 'some-id', + name: 'Evan You', + image: 'https://pbs.twimg.com/profile_images/888432310504370176/mhoGA4uj_400x400.jpg', + twitter: 'youyuxi', + github: 'yyx990803', + }, + ], + }); + expect(wrapper.element).toMatchSnapshot(); +}); +``` +对于简单的变更测试,你需要检查组件中是否调用了 `$apollo` 的 `mutate` 方法。接下来的示例在 `addHero` 方法中调用了变更: + +```js +test('called Apollo mutation in addHero() method', () => { + const mutate = jest.fn(); + const wrapper = mount(App, { + localVue, + mocks: { + $apollo: { + mutate, + }, + }, + }); + wrapper.vm.addHero(); + expect(mutate).toBeCalled(); +}); +``` + +## 使用模拟 GraqhQL schema 进行测试 + +你还可以使用 [模拟 GraphQL schema](https://www.apollographql.com/docs/graphql-tools/mocking.html) 进行更深入、更复杂的测试。这种方法并不包含 Apollo,但能够让你检查某些查询是否能够在给定的 schema 中正确执行。 + +为此,首先需要建立 schema: + +```js +const sourceSchema = ` + type VueHero { + id: ID! + name: String! + image: String + github: String + twitter: String + } + + input HeroInput { + name: String! + image: String + github: String + twitter: String + } + + + type Query { + allHeroes: [VueHero] + } + + type Mutation { + addHero(hero: HeroInput!): VueHero! + deleteHero(name: String!): Boolean + } +`; +``` +下一步是使用 `graphql-tools` 方法创建可执行的 schema: + +```js +import { makeExecutableSchema } from 'graphql-tools'; +... +const schema = makeExecutableSchema({ + typeDefs: sourceSchema, +}); +``` +之后你需要向 schema 添加模拟函数: + +```js +import { addMockFunctionsToSchema } from 'graphql-tools'; +... +addMockFunctionsToSchema({ + schema, +}); +``` +指定 GraphQL 查询字符串: + +```js +const query = ` + query { + allHeroes { + id + name + twitter + github + image + } + } +`; +``` +在测试用例中调用 GraphQL 查询,保存响应到组件数据中,然后检查渲染完成的组件是否与快照匹配: + +```js +graphql(schema, query).then(result => { + wrapper.setData(result.data); + expect(wrapper.element).toMatchSnapshot(); +}); +``` +在这个用例中,所有字符串字段将等于 `Hello World` 且所有数值都将为负数。如果你想要获得更贴近现实的响应,则应当为某些查询指定解析器: + +```js +const resolvers = { + Query: { + allHeroes: () => [ + { + id: '-pBE1JAyz', + name: 'Evan You', + image: + 'https://pbs.twimg.com/profile_images/888432310504370176/mhoGA4uj_400x400.jpg', + twitter: 'youyuxi', + github: 'yyx990803', + }, + ], + }, +}; +``` +然后你需要将解析器添加到可执行 schema,并在添加模拟函数时将 `preserveResolvers` 属性设置为 true: + +```js +const schema = makeExecutableSchema({ + typeDefs: sourceSchema, + resolvers, +}); + +addMockFunctionsToSchema({ + schema, + preserveResolvers: true, +}); +``` +你可以用同样的方法来测试变更。 + +--- \ No newline at end of file diff --git a/docs/zh-cn/migration/README.md b/docs/zh-cn/migration/README.md new file mode 100644 index 00000000..6ccaf0fa --- /dev/null +++ b/docs/zh-cn/migration/README.md @@ -0,0 +1,196 @@ +# From vue-apollo 2 and Apollo 1 + +主要的变动与 apollo 客户端的设置有关。你的组件代码不应该受到影响。Apollo 现在使用更灵活的 [apollo-link](https://github.com/apollographql/apollo-link) 系统,允许将多个连接组合在一起以添加更多功能(如批处理,离线支持等)。 + +## 安装 + +### 包 + +之前: + +``` +npm install --save vue-apollo apollo-client +``` + +之后: + +``` +npm install --save vue-apollo@next graphql apollo-client apollo-link apollo-link-http apollo-cache-inmemory graphql-tag +``` + +### 导入 + +之前: + +```js +import Vue from 'vue' +import { ApolloClient, createBatchingNetworkInterface } from 'apollo-client' +import VueApollo from 'vue-apollo' +``` + +之后: + +```js +import Vue from 'vue' +import { ApolloClient } from 'apollo-client' +import { HttpLink } from 'apollo-link-http' +import { InMemoryCache } from 'apollo-cache-inmemory' +import VueApollo from 'vue-apollo' +``` + +### Apollo 设置 + +之前: + +```js +// 创建网络接口 +const networkInterface = createNetworkInterface({ + uri: 'http://localhost:3000/graphql', + transportBatching: true, +}) + +// 创建订阅 websocket 客户端 +const wsClient = new SubscriptionClient('ws://localhost:3000/subscriptions', { + reconnect: true, +}) + +// 使用订阅客户端扩展网络接口 +const networkInterfaceWithSubscriptions = addGraphQLSubscriptions( + networkInterface, + wsClient, +) + +// 用新的网络接口创建 apollo 客户端 +const apolloClient = new ApolloClient({ + networkInterface: networkInterfaceWithSubscriptions, + connectToDevTools: true, +}) +``` + +之后: + +```js +const httpLink = new HttpLink({ + // 你需要在这里使用绝对路径 + uri: 'http://localhost:3020/graphql', +}) + +// 创建订阅 websocket 连接 +const wsLink = new WebSocketLink({ + uri: 'ws://localhost:3000/subscriptions', + options: { + reconnect: true, + }, +}) + +// 使用分割连接的功能 +// 你可以根据发送的操作类型将数据发送到不同的连接 +const link = split( + // 根据操作类型分割 + ({ query }) => { + const { kind, operation } = getMainDefinition(query) + return kind === 'OperationDefinition' && + operation === 'subscription' + }, + wsLink, + httpLink +) + +// 创建 apollo 客户端 +const apolloClient = new ApolloClient({ + link, + cache: new InMemoryCache(), + connectToDevTools: true, +}) +``` + +### 插件设置 + +之前: + +```js +// 创建 apollo 客户端 +const apolloClient = new ApolloClient({ + networkInterface: createBatchingNetworkInterface({ + uri: 'http://localhost:3020/graphql', + }), + connectToDevTools: true, +}) + +// 安装 vue 插件 +Vue.use(VueApollo, { + apolloClient, +}) + +new Vue({ + // ... +}) +``` + +之后: + +```js +const httpLink = new HttpLink({ + // 你需要在这里使用绝对路径 + uri: 'http://localhost:3020/graphql', +}) + +// 创建 apollo 客户端 +const apolloClient = new ApolloClient({ + link: httpLink, + cache: new InMemoryCache(), + connectToDevTools: true, +}) + +// 安装 vue 插件 +Vue.use(VueApollo) + +// 创建一个 provider +const apolloProvider = new VueApollo({ + defaultClient: apolloClient, +}) + +// 使用 provider +new Vue({ + provide: apolloProvider.provide(), + // ... +}) +``` + +## 变更 + +查询 reducer 已经被移除。现在使用 `update` API 来更新缓存。 + +## 订阅 + +### 包 + +之前: + +``` +npm install --save subscriptions-transport-ws +``` + +之后: + +``` +npm install --save apollo-link-ws apollo-utilities +``` + +### 导入 + +之前: + +```js +import { SubscriptionClient, addGraphQLSubscriptions } from 'subscriptions-transport-ws' +``` + +之后: + +```js +import { split } from 'apollo-link' +import { WebSocketLink } from 'apollo-link-ws' +import { getMainDefinition } from 'apollo-utilities' +``` + +了解更多请查看 [apollo 官方文档](https://www.apollographql.com/docs/react/2.0-migration.html)。 \ No newline at end of file