簡體   English   中英

組件內部 Next.js 數據獲取的最佳實踐

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

我有一個全局顯示的菜單組件。 將數據導入該組件的最佳做法是什么?

我正在嘗試利用 Next.js 提供的 static 代,但 Next.js 團隊的所有數據獲取指南都與頁面相關。 getStaticPropsgetStaticPaths似乎與頁面生成有關,而不是組件數據。 他們的SWR package 是正確答案,還是 Apollo Client?

通常在基於鈎子的 React 中,我只是將我的數據調用放入useEffect中。 我不確定如何推斷出所有內容都是在構建時使用 Next 呈現的。

這是一個如此棘手的問題,我認為我們需要在解決方案成為焦點之前布置一些背景。 我專注於 React.js 世界,但我認為其中很多都適用於 Vue/Nuxt。

背景/Static代福利:

Gatsby 和 Next 專注於生成 static 個頁面,這極大地提高了 React.js 站點的性能和 SEO。 除了這個簡單的洞察力之外,這兩個平台都有很多技術開銷,但讓我們從這個數字機器為瀏覽器抽出精美的 HTML 頁面的想法開始。

頁面數據獲取

對於 Next.js(從v9.5開始),他們的數據獲取機制getStaticProps為您完成了大部分繁重的工作,但它被沙盒化到/pages/目錄。 這個想法是它為您獲取數據並在構建期間將其告訴 Node 中的 Next.js 頁面生成器(而不是在useEffect掛鈎 - 或componentDidMount中在組件端執行)。 Gatsby 對他們的gatsby-node.js文件做了很多相同的事情,該文件與 Node 服務器協調頁面構建的數據獲取。

需要數據的全局組件呢?

您可以同時使用 Gatsby 和 Next 來制作任何類型的網站,但一個巨大的用例是 CMS 驅動的網站,因為其中大部分內容都是 static。這些工具非常適合該用例。

在典型的 CMS 站點中,您將擁有全局元素 - header、頁腳、搜索、菜單等。這就是 static 生成面臨的一大挑戰:如何在構建時將數據放入動態全局組件中? 這個問題的答案是……你不知道。 如果你想一想,這是有道理的。 如果您有一個 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 團隊對此進行了很好的記錄,這讓我覺得我並沒有完全破解這個工具的工作方式。 你可以在他們的 repo 中找到使用 Apollo 的很好的例子

讓 Apollo 進入下一個應用程序的步驟:

  1. 創建一個自定義的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. 修改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 獲取組件內部的動態數據; 太簡單了吧 ;) 哈!

對於 NextJS 中的全局數據獲取,我使用react-query並且不需要全局 state 因為它可以讓你緩存數據。 假設您有一個包含類別的博客,並且您希望將類別名稱作為下拉菜單放在navbar欄中。 在這種情況下,您可以調用 API 從navbar組件中使用react-query獲取數據並將其緩存。 導航欄數據將可用於所有頁面。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM