简体   繁体   English

组件内部 Next.js 数据获取的最佳实践

[英]Best practice for Next.js data fetching inside a component

I have a menu component that appears globally.我有一个全局显示的菜单组件。 What is the best practice for getting data into that component?将数据导入该组件的最佳做法是什么?

I'm trying to take advantage of static generation that Next.js offers but all data fetching guidance from the Next.js team relates to pages.我正在尝试利用 Next.js 提供的 static 代,但 Next.js 团队的所有数据获取指南都与页面相关。 getStaticProps and getStaticPaths seem to pertain to page generation, not data for components. getStaticPropsgetStaticPaths似乎与页面生成有关,而不是组件数据。 Is their SWR package the right answer, or Apollo Client?他们的SWR package 是正确答案,还是 Apollo Client?

Typically in hooks-based React, I'd just put my data call into useEffect .通常在基于钩子的 React 中,我只是将我的数据调用放入useEffect中。 I'm not sure how to reason this out being that everything is rendered at build time with Next.我不确定如何推断出所有内容都是在构建时使用 Next 呈现的。

This is such a tricky problem, I think we need to lay out some background before a solution comes into focus.这是一个如此棘手的问题,我认为我们需要在解决方案成为焦点之前布置一些背景。 I'm focusing in the React.js world but a lot of this would apply to Vue/Nuxt I'd imagine.我专注于 React.js 世界,但我认为其中很多都适用于 Vue/Nuxt。

Background / Static Generation Benefits:背景/Static代福利:

Gatsby and Next are focused on generating static pages, which vastly improves performance and SEO in React.js sites. Gatsby 和 Next 专注于生成 static 个页面,这极大地提高了 React.js 站点的性能和 SEO。 There is a lot of technical overhead to both platforms beyond this simple insight but let's start with this idea of a digital machine pumping out fancy HTML pages for the browser.除了这个简单的洞察力之外,这两个平台都有很多技术开销,但让我们从这个数字机器为浏览器抽出精美的 HTML 页面的想法开始。

Data Fetching for Pages页面数据获取

In the case of Next.js (as of v9.5 ), their data fetching mechanism getStaticProps does most of the heavy lifting for you but it's sandboxed to the /pages/ directory.对于 Next.js(从v9.5开始),他们的数据获取机制getStaticProps为您完成了大部分繁重的工作,但它被沙盒化到/pages/目录。 The idea is that it does the data fetching for you and tells the Next.js page generator in Node about it during build time (instead of doing it component-side in a useEffect hook - or componentDidMount ).这个想法是它为您获取数据并在构建期间将其告诉 Node 中的 Next.js 页面生成器(而不是在useEffect挂钩 - 或componentDidMount中在组件端执行)。 Gatsby does much the same with their gatsby-node.js file, which orchestrates the data fetching for page building in concert with a Node server. Gatsby 对他们的gatsby-node.js文件做了很多相同的事情,该文件与 Node 服务器协调页面构建的数据获取。

What about Global Components that need data?需要数据的全局组件呢?

You can use both Gatsby and Next to produce any kind of website but a huge use case are CMS-driven websites, because so much of that content is static. These tools are an ideal fit to that use case.您可以同时使用 Gatsby 和 Next 来制作任何类型的网站,但一个巨大的用例是 CMS 驱动的网站,因为其中大部分内容都是 static。这些工具非常适合该用例。

In typical CMS sites, you will have elements that are global - header, footer, search, menu, etc. This is where static generation faces a big challenge: how do I get data into dynamic global components at build time?在典型的 CMS 站点中,您将拥有全局元素 - header、页脚、搜索、菜单等。这就是 static 生成面临的一大挑战:如何在构建时将数据放入动态全局组件中? The answer to this question is... you don't.这个问题的答案是……你不知道。 And if you think about this for a minute it makes sense.如果你想一想,这是有道理的。 If you had a 10K page site, would you want to trigger a site-wide rebuild if someone adds a new nav item to a menu?如果您有一个 10K 页面的站点,如果有人将新的导航项添加到菜单,您是否希望触发站点范围的重建?

Data Fetching for Global Components全局组件的数据获取

So how do we get around this?那么我们如何解决这个问题呢? The best answer I have is apollo-client and to do the fetch client side.我的最佳答案是apollo-client并执行获取客户端。 This helps us for a number of reasons:这对我们有帮助,原因有很多:

  • For small size queries, the performance impact is negligible.对于小型查询,性能影响可以忽略不计。
  • If we need to rebuild pages for changes at the CMS layer, this slides by Next/Gatsby's detection mechanisms, so we can make global changes without triggering gigantic site-wide rebuilds.如果我们需要为 CMS 层的更改重建页面,这会通过 Next/Gatsby 的检测机制进行滑动,因此我们可以在不触发巨大的站点范围重建的情况下进行全局更改。

So what does this actually look like?那么这实际上是什么样的呢? At the component level, it looks just like a regular Apollo-enhanced component would.在组件级别,它看起来就像一个常规的 Apollo 增强组件。 I usually use styled-components but I tried to strip that out so you can could better see what's going on.我通常使用styled-components但我试图将其剥离,以便您可以更好地了解发生了什么。

import React from 'react'
import { useQuery, gql } from '@apollo/client'
import close from '../public/close.svg'

/**
 * <NavMenu>
 * 
 * Just a typical menu you might see on a CMS-driven site. It takes in a couple of props to move state around.
 * 
 * @param { boolean } menuState - lifted state true/false toggle for menu opening/closing
 * @param { function } handleMenu - lifted state changer for menuState, handles click event
 */

const NAV_MENU_DATA = gql`
  query NavMenu($uid: String!, $lang: String!) {
    nav_menu(uid: $uid, lang: $lang) {
      main_menu_items {
        item {
          ... on Landing_page {
            title
            _linkType
            _meta {
              uid
              id
            }
          }
        }
      }
    }
  }
`

const NavMenu = ({ menuState, handleMenu }) => {
  // Query for nav menu from Apollo, this is where you pass in your GraphQL variables
  const { loading, error, data } = useQuery(NAV_MENU_DATA, {
    variables: {
      "uid": "nav-menu",
      "lang": "en-us"
    }
  })
  
  if (loading) return `<p>Loading...</p>`;
  if (error) return `Error! ${error}`;

  // Destructuring the data object
  const { nav_menu: { main_menu_items } } = data

  // `menuState` checks just make sure out menu was turned on
  if (data) return(
    <>
      <section menuState={ menuState }>
        <div>
          { menuState === true && (
            <div>Explore</div>
          )}
          <div onClick={ handleMenu }>
          { menuState === true && (
            <svg src={ close } />
          )}
          </div>
        </div>
        { menuState === true && (
          <ul>
            { data.map( (item) => {
              return (
                <li link={ item }>
                  { item.title }
                </li>
              )
            })}
          </ul>
        )}
      </section>
    </>
  )
}

export default NavMenu

Set Up for Next to Use Apollo设置以备下次使用 Apollo

This is actually really well documented by the Next.js team, which makes me feel like I'm not totally hacking the way this tool is supposed to work.实际上,Next.js 团队对此进行了很好的记录,这让我觉得我并没有完全破解这个工具的工作方式。 You can find great examples of using Apollo in their repo.你可以在他们的 repo 中找到使用 Apollo 的很好的例子

Steps to get Apollo into a Next app:让 Apollo 进入下一个应用程序的步骤:

  1. Make a custom useApollo hook that sets up the connection to your data source (I put mine in /lib/apollo/apolloClient.js within Next's hierarchy but I'm sure it could go elsewhere).创建一个自定义的useApollo挂钩来设置与您的数据源的连接(我将我的挂钩放在 Next 的层次结构中的/lib/apollo/apolloClient.js中,但我确信它可以在其他地方 go )。
import { useMemo } from 'react'
import { ApolloClient, InMemoryCache, SchemaLink, HttpLink } from '@apollo/client'

let apolloClient

// This is mostly from next.js official repo on how best to integrate Next and Apollo
function createIsomorphLink() {
  // only if you need to do auth
  if (typeof window === 'undefined') {
    // return new SchemaLink({ schema }) 
    return null
  } 
  // This sets up the connection to your endpoint, will vary widely.
  else {
    return new HttpLink({
      uri: `https://yourendpoint.io/graphql`
    })
  }
}

// Function that leverages ApolloClient setup, you could just use this and skip the above function if you aren't doing any authenticated routes
function createApolloClient() {
  return new ApolloClient({
    ssrMode: typeof window === 'undefined',
    link: createIsomorphLink(),
    cache: new InMemoryCache(),
  })
}


export function initializeApollo(initialState = null) {
  const _apolloClient = apolloClient ?? createApolloClient()

  // If your page has Next.js data fetching methods that use Apollo Client, the initial state
  // gets hydrated here
  if (initialState) {
    // Get existing cache, loaded during client side data fetching
    const existingCache = _apolloClient.extract()
    // Restore the cache using the data passed from getStaticProps/getServerSideProps
    // combined with the existing cached data
    _apolloClient.cache.restore({ ...existingCache, ...initialState })
  }
  // For SSG and SSR always create a new Apollo Client
  if (typeof window === 'undefined') return _apolloClient
  // Create the Apollo Client once in the client
  if (!apolloClient) apolloClient = _apolloClient

  return _apolloClient
}

// This is goal, now we have a custom hook we can use to set up Apollo across our app. Make sure to export this!
export function useApollo(initialState) {
  const store = useMemo(() => initializeApollo(initialState), [initialState])
  return store
}
  1. Modify _app.js in the /pages/ directory of Next.修改Next /pages/目录下的_app.js This is basically the wrapper that goes around every page in Next.这基本上是 Next 中每个页面的包装器。 We're going to add the Apollo provider to this, and now we can globally access Apollo from any component.我们将向其中添加 Apollo 提供程序,现在我们可以从任何组件全局访问 Apollo。
import { ApolloProvider } from '@apollo/react-hooks'
import { useApollo } from '../lib/apollo/apolloClient'

/**
 * <MyApp>
 * 
 * This is an override of the default _app.js setup Next.js uses
 * 
 * <ApolloProvider> gives components global access to GraphQL data fetched in the components (like menus)
 * 
 */
const MyApp = ({ Component, pageProps }) => {
  // Instantiates Apollo client, reads Next.js props and initialized Apollo with them - this caches data into Apollo.
  const apolloClient = useApollo(pageProps.initialApolloState)

  return (
    <ApolloProvider client={ apolloClient }>
      <Component {...pageProps} />
    </ApolloProvider>
  )
}

export default MyApp

And now you can get dynamic data inside of your components using Apollo;现在您可以使用 Apollo 获取组件内部的动态数据; So easy right ;) HA!太简单了吧 ;) 哈!

For global data fetching in NextJS, I use react-query and there is no need for a global state because it lets you to cache the data.对于 NextJS 中的全局数据获取,我使用react-query并且不需要全局 state 因为它可以让你缓存数据。 Let's say you have a blog with categories and you want to put the category names in the navbar as a dropdown menu.假设您有一个包含类别的博客,并且您希望将类别名称作为下拉菜单放在navbar栏中。 In this case you can call the API to fetch the data with react-query from the navbar component and cache it.在这种情况下,您可以调用 API 从navbar组件中使用react-query获取数据并将其缓存。 The navbar data will be available for all pages.导航栏数据将可用于所有页面。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM