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 指导都与页面相关。 getStaticProps
和 getStaticPaths
似乎与页面生成有关,而不是组件数据。他们的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
并执行获取客户端。这对我们有很多帮助:
那么这实际上是什么样子的呢?在组件级别,它看起来就像一个普通的 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 组件都遵循您使用 getStaticProps
或 getServerProps
设置的生成策略。它们几乎总是从服务器端交付。 Apollo 根据客户端请求获取数据,使其保持最新
@serraosays 谢谢!目前我正在使用另一种方法/解决方法,即通过将道具传递给组件。我不确定这是否是一种好习惯:github.com/markdost/next.js-wp-posts/blob/main/index.example.js
@serraosays 你说得对,它有点太多了,我已经在生产中对其进行了重构。我这样做是因为它是针对单页网站的。以上是关于Next.js 在组件内获取数据的最佳实践的主要内容,如果未能解决你的问题,请参考以下文章
使用 getServerSideProps 获取内部 API? (Next.js)