集成 Keystone、GraphQL 和 Nuxt
Keystone 的 GraphQL API 端点位于 localhost:4000/admin/api。与通常有多个端点的 REST API 不同,GraphQL API 通常只有一个用于所有查询的端点。因此,我们将使用此端点从 Nuxt 应用程序发送我们的 GraphQL 查询。最佳实践是始终先在 GraphQL Playground 上测试我们的查询,以确认我们获得了需要的结果,然后在我们的前端应用程序中使用这些经过测试的查询。此外,我们应该始终在前端应用程序的查询中使用 query 关键字,以从 GraphQL API 获取数据。
在本练习中,我们将重构为 WordPress API 构建的 Nuxt 应用程序。我们将查看 /pages/index.vue、/pages/projects/index.vue、/pages/projects/_slug.vue 和 /store/index.js 文件。我们将继续使用 Axios 来帮助我们发送 GraphQL 查询。让我们看看如何使 GraphQL 查询和 Axios 一起工作:
-
创建一个变量来存储
GraphQL
查询,以便获取主页的标题和附加到它的幻灯片图像:// pages/index.vue const GET_PAGE = ` query { allPages (search: "home") { title slideImages { alt link { name } file { publicUrl } } } } `
javascript我们只需要图像将链接到的项目页面的 slug,因此 name 字段是我们唯一要查询的字段。并且我们只需要图像文件的相对公共 URL,因此 publicUrl 字段是我们从图像文件对象中唯一想要的字段。此外,我们使用 allPages 查询而不是 Page,因为它更容易通过其 slug(在本例中为 home)获取页面。
-
使用 Axios 的 post 方法将查询发送到 GraphQL API 端点:
// pages/index.vue export default { async asyncData ({ $axios }) { let { data } = await $axios.post('/admin/api', { query: GET_PAGE }) return { post: data.data.allPages[0] } }, }
javascript请注意,我们只需要 GraphQL API 返回的数据数组中的第一个项目,因此我们使用 0 来定位第一个项目。
请注意,我们还应该按照重构此主页的相同模式重构 /pages/about.vue、/pages/contact.vue、/pages/projects/index.vue 和 /pages/projects/pages/_number.vue。你可以在本节末尾找到包含完整代码的本书 GitHub 存储库的路径。
-
创建一个变量来存储查询,并允许你从端点获取多个资源,如下所示:
// components/projects/project-items.vue const GET_PROJECTS = ` query { _allProjectsMeta { count } allProjects (orderBy: "name_DESC", skip: ${ skip }, first: ${ postsPerPage }) { name title excerpt featuredImage { alt file { publicUrl } } } } `
javascript正如你所见,我们通过 _allProjectsMeta 获取项目页面的总数,并通过带有 orderBy、skip 和 first 过滤器的 allProjects 获取项目页面列表。skip 和 first 过滤器的数据将作为变量传递;即分别为 skip 和 postsPerPage。
-
从路由参数计算 skip 变量的数据,将 postsPerPage 变量设置为 6,然后使用 Axios 的 post 方法将查询发送到 GraphQL API 端点:
// components/projects/project-items.vue data () { return { posts: [], totalPages: null, currentPage: null, nextPage: null, prevPage: null, } }, async fetch () { const postsPerPage = 6 const number = this.$route.params.number const pageNumber = number === undefined ? 1 : Math.abs( parseInt(number) ) const skip = number === undefined ? 0 : (pageNumber - 1) * postsPerPage const GET_PROJECTS = `... ` let { data } = await $axios.post('/admin/api', { query: GET_PROJECTS }) //... 在步骤 5 中继续。 }
javascript正如你所见,我们从路由参数计算 pageNumber 数据,我们只能通过 fetch 方法中的 this.$route.params 访问它。在我们将 skip 数据传递给 GraphQL 查询并获取数据之前,会根据 pageNumber 和 postsPerPage 计算 skip 数据。在这里,在 /projects 或 /projects/pages/1 路由上,我们将获得 pageNumber 为 1,skip 为 0;在 /projects/pages/2 路由上,我们将获得 pageNumber 为 2,skip 为 6,依此类推。此外,我们必须确保路由中任何故意的负数据(例如,/projects/pages/-100)都将通过使用 JavaScript Math.abs 函数转换为正数。
有关 JavaScript Math.abs 函数的更多信息,请访问 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/abs。
-
从服务器返回的 count 字段创建分页(下一页和上一页),然后像往常一样为 <template> 块返回数据,如下所示:
// components/projects/project-items.vue let totalPosts = data.data._allProjectsMeta.count let totalMaxPages = Math.ceil(totalPosts / postsPerPage) this.posts = data.data.allProjects this.totalPages = totalMaxPages this.currentPage = pageNumber this.nextPage = pageNumber === totalMaxPages ? null : pageNumber + 1 this.prevPage = pageNumber === 1 ? null : pageNumber - 1
javascript -
创建一个变量来存储通过
slug
从端点获取单个项目页面的查询,如下所示:// pages/projects/_slug.vue const GET_PAGE = ` query { allProjects (search: "${ params.slug }") { title content excerpt fullscreenImage { ... } projectImages { ... } } } `
javascript在这里,我们通过带有 search 过滤器的 allProjects 获取项目页面。search 过滤器的数据将从 params.slug 参数传递进来。fullscreenImage 和 fullscreenImage 中我们将查询的字段与 featuredImage 中的字段相同;你可以在步骤 3 中找到它们。
-
使用 Axios 的 post 方法将查询发送到 GraphQL API 端点:
// pages/projects/_slug.vue async asyncData ({ params, $axios }) { const GET_PAGE = `...` let { data: { data: result } } = await $axios.post('/admin/api', { query: GET_PAGE }) return { post: result.allProjects[0], } }
javascript请注意,你还可以解构嵌套的对象或数组,并将变量分配给该值。在上面的代码中,我们已将 result 分配为变量,以便存储 GraphQL 返回的 data 属性的值。
-
创建一个变量来存储通过带有 orderBy 过滤器的端点获取 NavLink 列表的查询,如下所示:
// store/index.js const GET_LINKS = ` query { allNavLinks (orderBy: "order_ASC") { title link { name } } } `
javascript -
使用 Axios 的 post 方法将查询发送到 GraphQL API 端点,然后将数据提交到 store 状态:
// store/index.js async nuxtServerInit({ commit }, { $axios }) { const GET_LINKS = `...` let { data } = await $axios.post('/admin/api', { query: GET_LINKS }) commit('setMenu', data.data.allNavLinks) }
javascript -
(可选)就像“与 Nuxt 集成并从 WordPress 流式传输图像”部分中的步骤 9 一样,如果 Nuxt 爬虫由于某些未知原因无法检测到动态路由,则在 Nuxt 配置文件中的 generate 选项中手动生成这些路由,如下所示:
// nuxt.config.js import axios from 'axios' export default { generate: { routes: async function () { const GET_PROJECTS = ` query { allProjects { name } } ` const { data } = await axios.post(remoteUrl + '/admin/api', { query: GET_PROJECTS }) const routesProjects = data.data.allProjects.map(project => { return { route: '/projects/' + project.name, payload: project } }) let totalMaxPages = Math.ceil(routesProjects.length / 6) let pagesProjects = [] Array(totalMaxPages).fill().map((item, index) => { pagesProjects.push({ route: '/projects/pages/' + (index + 1), payload: null }) }) const routes = [ ...routesProjects, ...pagesProjects ] return routes } }, }
javascript在这个可选步骤中,你可以看到我们使用了与“与 Nuxt 集成并从 WordPress 流式传输图像”部分相同的 JavaScript 内置对象和方法——Array、map、fill 和 push——来计算子页面的动态路由和分页,然后将它们作为单个数组返回给 Nuxt 以生成其动态路由。
-
运行以下脚本命令进行开发或生产:
$ npm run dev $ npm run build && npm run start $ npm run stream && npm run generate
bash
请记住,如果你想生成静态页面并将图像托管在同一位置,我们可以将远程图像流式传输到 /assets/ 目录,以便 webpack 可以为我们处理这些图像。因此,如果你想这样做,只需像我们之前所做的那样,首先运行 npm run stream 将远程图像流式传输到你的本地磁盘,然后运行 npm run generate 以使用这些图像重新生成静态页面,然后再将它们托管在某个地方。
你可以在本书 GitHub 存储库的 /chapter-18/crossdomain/frontend/nuxt-universal/nuxt-keystone 中找到此练习的代码。 除了使用 Axios,你还可以使用 Nuxt Apollo 模块向服务器发送 GraphQL 查询。有关此模块及其用法的更多信息,请访问 https://github.com/nuxt-community/apollo-module。 |
做得好!你已成功将 Nuxt 与 Keystone GraphQL API 集成,并为静态页面流式传输了远程资源——就像你对 WordPress REST API 所做的那样。我们希望 Keystone 和特别是 GraphQL 向你展示了另一个令人兴奋的 API 选项。你甚至可以进一步利用你在本章中学到的 GraphQL 知识,为你的 Nuxt 应用程序开发 GraphQL API。你还可以使用许多其他技术将 Nuxt 提升到一个新的水平,就像我们在本书中引导你了解的一些技术一样。这本书的旅程相当漫长。我们希望它对你的 Web 开发有所帮助,并且你可以尽可能地利用你从本书中学到的知识。现在,让我们总结一下你在本章中学到的内容。