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

Posted

技术标签:

【中文标题】Next.js 在组件内获取数据的最佳实践【英文标题】:Best practice for Next.js data fetching inside a component 【发布时间】: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 中全局获取数据,您只需要在客户端进行即可。但这有一些好处,请阅读文章并告诉我您的想法。 【参考方案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 应用的步骤:

    制作一个自定义的 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(
    s-s-rMode: 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 s-s-r 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

    修改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 还是 s-s-r 中的数据? @Mark - 您在 Next 中导入页面的所有 React 组件都遵循您使用 getStaticPropsgetServerProps 设置的生成策略。它们几乎总是从服务器端交付。 Apollo 根据客户端请求获取数据,使其保持最新 @serraosays 谢谢!目前我正在使用另一种方法/解决方法,即通过将道具传递给组件。我不确定这是否是一种好习惯:github.com/markdost/next.js-wp-posts/blob/main/index.example.js @serraosays 你说得对,它有点太多了,我已经在生产中对其进行了重构。我这样做是因为它是针对单页网站的。

以上是关于Next.js 在组件内获取数据的最佳实践的主要内容,如果未能解决你的问题,请参考以下文章

如何从 Next.js 中的服务器获取 HOC 中的数据?

使用 getServerSideProps 获取内部 API? (Next.js)

调度操作并从同一组件中的 redux 获取状态的最佳实践?

在 silex 中获取用户数据的最佳实践是啥?

如何在类组件中使用 next.js 中的“useRouter()”?

JSF 最佳实践:自定义组件和 JavaScript