【问题标题】:Best practice for Next.js data fetching inside a componentNext.js 在组件内获取数据的最佳实践
【发布时间】:2020-12-09 03:57:31
【问题描述】:

我有一个全局显示的菜单组件。将数据导入该组件的最佳做法是什么?

我正在尝试利用 Next.js 提供的静态生成,但 Next.js 团队提供的所有 data fetching 指导都与页面相关。 getStaticPropsgetStaticPaths 似乎与页面生成有关,而不是组件数据。他们的SWR 包是正确答案,还是 Apollo Client?

通常在基于钩子的 React 中,我只是将我的数据调用放入 useEffect。我不确定如何推断出所有内容都是在构建时使用 Next 渲染的。

【问题讨论】:

  • 您是否尝试在页面加载之前获取数据,然后将其推送到您的组件(服务器端渲染),或者您是否希望在构建时将静态数据发送到组件静态生成?如果您要发送到导航组件的数据将发生变化,那么静态生成将不会在这里真正起作用,除非您想在页面已经呈现后使用 useEffect 获取数据。如果您想要在页面呈现之前的数据,从而使用服务器端呈现,那么有一种方法可以实现。
  • @Chris - 这是一个非常简单的导航菜单,我认为静态生成会很好。只是不知道在哪里打电话。
  • 据我所知(对 next 自己来说还是有点新意),获取静态数据的唯一方法是使用 getStaticProps。当然这只能从单独的页面调用,所以不可能在顶层布局或特定组件上添加它。您可以在自定义 _app.js 中使用一个 nextjs 调用,但该调用会导致所有页面都在服务器端呈现而不是静态呈现。
  • 是的,鉴于我已经阅读过他们当前的文档,自定义 _app 是包装整个布局的唯一方法。这绝对是不幸的,这就是我最终为我最近的申请所做的事情。希望这是将来可以解决的问题。
  • @Chris - 我为此写了一个大方法。感谢您帮助解决我最初的问题,希望我的回答能得到回报——然后是一些。您可以在 Next 中全局获取数据,您只需要在客户端进行即可。但这有一些好处,请阅读文章并告诉我您的想法。

标签: javascript reactjs next.js apollo-client


【解决方案1】:

对于 NextJS 中的全局数据获取,我使用 react-query 并且不需要全局状态,因为它可以让您缓存数据。假设您有一个包含类别的博客,并且您想将类别名称作为下拉菜单放在navbar 中。在这种情况下,您可以调用 API 从navbar 组件中获取带有react-query 的数据并缓存它。导航栏数据将适用于所有页面。

【讨论】:

    【解决方案2】:

    这是一个非常棘手的问题,我认为我们需要在解决方案成为焦点之前布置一些背景。我专注于 React.js 世界,但我想其中很多都适用于 Vue/Nuxt。

    背景/静态生成优势:

    Gatsby 和 Next 专注于生成静态页面,这极大地提高了 React.js 网站的性能和 SEO。除了这个简单的见解之外,这两个平台还有很多技术开销,但让我们从数字机器为浏览器输出精美的 HTML 页面的想法开始。

    页面数据获取

    对于 Next.js(截至 v9.5),他们的数据获取机制 getStaticProps 为您完成了大部分繁重的工作,但它被沙箱化到了 /pages/ 目录。这个想法是它为您获取数据并在构建期间告诉 Node 中的 Next.js 页面生成器(而不是在 useEffect 钩子或 componentDidMount 中进行组件端)。 Gatsby 对他们的 gatsby-node.js 文件做了很多相同的事情,该文件与 Node 服务器一起协调了用于页面构建的数据获取。

    需要数据的全局组件呢?

    您可以同时使用 Gatsby 和 Next 来制作任何类型的网站,但一个巨大的用例是 CMS 驱动的网站,因为其中大部分内容是静态的。这些工具非常适合该用例。

    在典型的 CMS 站点中,您将拥有全局元素 - 页眉、页脚、搜索、菜单等。这是静态生成面临的一大挑战:如何在构建时将数据获取到动态全局组件中?这个问题的答案是……你不知道。如果你想一分钟,这是有道理的。如果您有一个 10K 页的网站,如果有人向菜单添加新的导航项,您是否希望触发站点范围的重建?

    全局组件的数据获取

    那么我们如何解决这个问题?我得到的最佳答案是apollo-client 并执行获取客户端。这对我们有很多帮助:

    • 对于小型查询,性能影响可以忽略不计。
    • 如果我们需要为 CMS 层的更改重建页面,这会通过 Next/Gatsby 的检测机制滑动,因此我们可以进行全局更改而不会触发大规模的站点范围重建。

    那么这实际上是什么样子的呢?在组件级别,它看起来就像一个普通的 Apollo 增强组件。我通常使用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
    

    设置下一个使用 Apollo

    Next.js 团队实际上已经很好地记录了这一点,这让我觉得我并没有完全破解这个工具的工作方式。你可以在他们的仓库中找到great examples of using Apollo

    让 Apollo 进入 Next 应用的步骤:

    1. 制作一个自定义的 useApollo 挂钩,用于设置与您的数据源的连接(我将我的 /lib/apollo/apolloClient.js 放在 Next 的层次结构中,但我确信它可以放在其他地方)。
    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. 修改Next的/pages/目录下的_app.js。这基本上是 Next 中每个页面的包装器。我们将向其中添加 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
    

    现在您可以使用 Apollo 获取组件内部的动态数据!很容易吧;)哈!

    【讨论】:

    • 为什么SchemaLink在这里被注释掉了?挺好用的。
    • 我在您的示例中没有得到什么,是NavMenu 客户端、SSG 还是 SSR 中的数据?
    • @Mark - 您在 Next 中导入页面的所有 React 组件都遵循您使用 getStaticPropsgetServerProps 设置的生成策略。它们几乎总是从服务器端交付。 Apollo 根据客户端请求获取数据,使其保持最新
    • @serraosays 谢谢!目前我正在使用另一种方法/解决方法,即通过将道具传递给组件。我不确定这是否是一种好习惯:github.com/markdost/next.js-wp-posts/blob/main/index.example.js
    • @serraosays 你说得对,它有点太多了,我已经在生产中对其进行了重构。我这样做是因为它是针对单页网站的。
    猜你喜欢
    • 1970-01-01
    • 2021-12-24
    • 1970-01-01
    • 2015-04-21
    • 2011-12-13
    • 1970-01-01
    • 2020-08-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多