Лучшая практика для Next.js выборка данных внутри компонента

#javascript #reactjs #next.js #apollo-client

#javascript #reactjs #next.js #apollo-клиент

Вопрос:

У меня есть компонент меню, который отображается глобально. Какова наилучшая практика для получения данных в этот компонент?

Я пытаюсь воспользоваться статической генерацией, которая Next.js предлагает только все рекомендации по выборке данных из Next.js команда относится к страницам. getStaticProps и getStaticPaths , похоже, относятся к генерации страницы, а не к данным для компонентов. Является ли их SWR пакет правильным ответом или клиент Apollo?

Обычно в React на основе хуков я просто помещаю свой вызов данных в useEffect . Я не уверен, как объяснить это тем, что все отображается во время сборки с помощью Next.

Комментарии:

1. Вы пытаетесь извлечь данные ДО загрузки страницы, а затем передать их в свой компонент (визуализируемый на стороне сервера), ИЛИ вы хотите иметь статические данные, которые отправляются компоненту во время сборки со статической генерацией? Если данные, которые вы хотите отправить в свой навигационный компонент, изменятся, то статическая генерация здесь на самом деле работать не будет, если только вы не захотите извлечь данные с помощью useEffect после того, как страница уже была отрисована. Если вы хотите получить данные до рендеринга страницы, таким образом, используя рендеринг на стороне сервера, то для этого есть возможный способ.

2. @Chris — это довольно простое навигационное меню, я думаю, статическая генерация была бы хороша. Просто не уверен, где выполнить вызов.

3. Насколько я знаю (все еще несколько новичок в next сам), единственный способ получить статические данные — с getStaticProps . Конечно, это может быть вызвано только с отдельных страниц, поэтому невозможно добавить его в макет верхнего уровня или в конкретные компоненты. Существует один вызов nextjs, который вы можете использовать в своем пользовательском _app.js , но вызов приведет к тому, что ВСЕ страницы будут отображаться на стороне сервера, а не статично.

4. Да, учитывая их текущую документацию, которую я прочитал, пользовательское _app — единственный способ обернуть весь макет. Это определенно неудачно, и это то, что я в конечном итоге сделал для своего самого последнего приложения. Надеюсь, это то, что можно решить в будущем.

5. @Chris — Я описал большой подход к этому. Спасибо, что помогли задать мой первоначальный вопрос, надеюсь, мой ответ принесет пользу — и не только. Вы можете получить данные глобально в Next, вам просто нужно сделать это на стороне клиента. Но это дает некоторые преимущества, прочитайте статью и дайте мне знать, что вы думаете.

Ответ №1:

Это такая сложная проблема, я думаю, нам нужно изложить некоторые предпосылки, прежде чем решение попадет в фокус. Я сосредотачиваюсь на React.js мир, но я бы предположил, что многое из этого применимо к Vue / Nuxt.

Преимущества фоновой / статической генерации:

Gatsby и Next сосредоточены на создании статических страниц, что значительно повышает производительность и SEO в React.js сайты. Помимо этого простого понимания, для обеих платформ существует много технических накладных расходов, но давайте начнем с идеи цифровой машины, загружающей причудливые HTML-страницы для браузера.

Выборка данных для страниц

В случае Next.js (по состоянию на v9.5 ), их механизм выборки данных getStaticProps выполняет большую часть тяжелой работы за вас, но он изолирован от /pages/ каталога. Идея в том, что он выполняет выборку данных за вас и сообщает Next.js генератор страниц в узле сообщает об этом во время сборки (вместо того, чтобы делать это на стороне компонента с помощью useEffect перехвата componentDidMount ). Gatsby делает почти то же самое со своим gatsby-node.js файлом, который организует выборку данных для построения страницы совместно с узловым сервером.

Как насчет глобальных компонентов, которым нужны данные?

Вы можете использовать Gatsby и Next для создания любого веб-сайта, но огромным примером использования являются веб-сайты, управляемые CMS, потому что большая часть этого контента статична. Эти инструменты идеально подходят для этого варианта использования.

На типичных сайтах CMS у вас будут глобальные элементы — верхний и нижний колонтитулы, поиск, меню и т.д. Здесь статическая генерация сталкивается с большой проблемой: как мне перенести данные в динамические глобальные компоненты во время сборки? Ответ на этот вопрос таков… вы этого не делаете. И если вы подумаете об этом на минуту, это имеет смысл. Если бы у вас был сайт на 10 тысяч страниц, хотели бы вы запустить перестройку всего сайта, если кто-то добавит новый элемент навигации в меню?

Выборка данных для глобальных компонентов

Итак, как нам обойти это? Лучший ответ, который у меня есть, это 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 amp;amp; (
            <div>Explore</div>
          )}
          <div onClick={ handleMenu }>
          { menuState === true amp;amp; (
            <svg src={ close } />
          )}
          </div>
        </div>
        { menuState === true amp;amp; (
          <ul>
            { data.map( (item) => {
              return (
                <li link={ item }>
                  { item.title }
                </li>
              )
            })}
          </ul>
        )}
      </section>
    </>
  )
}

export default NavMenu
  

Настройте для следующего использования Apollo

На самом деле это действительно хорошо задокументировано Next.js команда, которая заставляет меня чувствовать, что я не совсем разбираюсь в том, как должен работать этот инструмент. Вы можете найти отличные примеры использования Apollo в их репозитории.

Шаги по внедрению Apollo в следующее приложение:

  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. Измените _app.js в /pages/ каталоге Next. По сути, это оболочка, которая распространяется на каждую страницу в 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! Так просто, верно 😉 ХА!

Комментарии:

1. Почему SchemaLink здесь прокомментировано? Это довольно полезно.

2. Чего я не понимаю в вашем примере, так это данных на NavMenu стороне клиента, SSG или SSR?

3. @Mark — все компоненты React, которые вы импортируете на страницу в Next, следуют стратегии генерации, заданной с помощью getStaticProps или getServerProps . Они почти всегда будут доставляться со стороны сервера. Apollo извлекает данные по запросу клиента, чтобы они оставались свежими

4. @serraosays Спасибо вам! На данный момент я использую другой подход / обходной путь, и это путем передачи реквизитов компонентам. Я не уверен, что это хорошая практика, хотя: github.com/markdost/next.js-wp-posts/blob/main/index.example.js

5. @serraosays Вы правы в том, что этого слишком много, я переработал его в рабочей среде. Я сделал это таким образом, потому что это было для одностраничного веб-сайта.

Ответ №2:

Для глобальной выборки данных в NextJS я использую react-query и нет необходимости в глобальном состоянии, потому что это позволяет кэшировать данные. Допустим, у вас есть блог с категориями, и вы хотите поместить названия категорий в navbar выпадающее меню. В этом случае вы можете вызвать API для извлечения данных с помощью react-query из navbar компонента и кэшировать их. Данные навигационной панели будут доступны для всех страниц.