Next.js 获取 Json 以在组件中显示
我试图将 MainMenu 和 getStaticProps 函数从同一页面 (index.js) 中取出,并将其分解为组件。下面是运行良好的 index.js 页面。
#index.js
import Link from 'next/link';
function MainMenu({ menuLists }) {
return (
<div>
{menuLists.map(menuItem => (
<div>
<Link href={menuItem.absolute}><a>{menuItem.title}</a></Link>
{menuItem.below && menuItem.below.map(childItem => (
<div>
<Link href={childItem.absolute}><a>{childItem.title}</a></Link>
</div>
))}
</div>
))}
</div>
)
}
export async function getStaticProps() {
const response = await fetch('http://localhost:8888/api/menu_items/main');
const menuLists = await response.json();
return {
props: {
menuLists: menuLists,
},
}
}
export default MainMenu
我已使用以下代码在 lib 目录中创建了 fetch-mainmenu.js。
#fetch-mainmenu.js
export async function loadMainMenu() {
const response = await fetch('http://localhost:8888/api/menu_items/main')
const menuLists = await response.json()
return {
props: {
menuLists: menuLists,
},
}
}
然后我创建了 sidebar.js 以显示来自 json 文件的菜单系统。sidebar.js 文件正在运行,因为硬编码菜单正在显示。
# sidebar.js
import Link from 'next/link'
import styles from './sidebar.module.css'
import { loadMainMenu } from '../lib/fetch-mainmenu'
export default function Sidebar( { menuLists } ) {
const menus = loadMainMenu()
return (
<nav className={styles.nav}>
<input className={styles.input} placeholder="Search..." />
<Link href="/">
<a>Home</a>
</Link>
<Link href="/about">
<a>About</a>
</Link>
<Link href="/contact">
<a>Contact</a>
</Link>
</nav>
)
}
出现以下错误“TypeError:无法获取”。 使用组件完成此操作的最佳方法是什么。
解决方案
1. Prop Drilling
简单。只需从
getStaticProps()
发送所有数据即可。这是当前阶段最安全的选择,但可能会创建一些多余的 props。
// I've omitted fetch().json() to ease the reading. just assume it's a proper code.
const MainMenuComponent = ({menuLists}) => {
return <div>{menuLists}</div>
}
const MainPage = ({menuLists}) => {
return <MainMenuComponent menuLists={menuLists} />
}
export async function getStaticProps() {
const req = await fetch('...');
return {
props: {
menuLists: req,
},
}
}
export default MainPage
2. React.useEffect
React 组件不能在渲染代码中包含异步代码。这在类组件中非常明显,但在功能组件中很难分辨
// I've omitted fetch().json() to ease the reading. just assume it's a proper code.
// class component
class SampleComponent extends React.Component {
constructor(props) {
super(props);
this.state = { data: {} };
}
async getData() {
// ✅ this works
const data = await fetch('...');
// data has to be put in state because it's asynchronous.
this.setState({ ...this.state, data });
}
componentDidMount() {
this.getData();
}
render() {
// ❌ this can't happen here because render is synchronous
await fetch('...');
// use state to display asynchronous data.
return <h1>Hello, {JSON.stringify(this.state.data)}</h1>;
}
}
// functional component
function SampleComponent = () => {
// everything outside `useEffect, useLayoutEffect` is mostly assumed as render function.
// ❌ thus, this does not work here
await fetch('...');
const [data, setData] = useState({});
useEffect(async () => {
// everything inside here treated as componentDidMount()
// not the exact same thing though.
// ✅ this works!
setData(await fetch('...'))
}, []);
return <h1>Hello, {JSON.stringify(data)}</h1>
}
警告
如果您的页面中有
getStaticProps
,则意味着该组件也必须是同步的。如果渲染的组件在很短的时间内(几分之一秒内)更改其内容,则可能会出现补水错误。它需要用
dynamic()
包装,以便 Next.js 在渲染服务器端和重新水化组件时可以忽略该组件。请参阅
Next.js 关于动态导入的官方文档
。
它确实有效,但代码似乎很长。
3. TanStack Query(或 React-Query)或 useSWR
有一些不错的第三方库可以帮助在 React 组件内编写异步数据获取代码; TanStack Query 和 SWR 是最著名的。这些库还实现了缓存和重新验证。它可以帮助处理由于异步请求而引起的复杂问题。
// example code from useSWR
import useSWR from 'swr'
function Profile() {
const { data, error } = useSWR('/api/user', fetcher)
if (error) return <div>failed to load</div>
if (!data) return <div>loading...</div>
return <div>hello {data.name}!</div>
}
4. 使用 Context 进行状态管理
大多数情况都可以通过 Query-SWR 解决方案轻松处理,但如果应用程序变得足够大,则可能需要同步数据。
在这种情况下,在服务器代码中获取数据并与中央状态管理库(又名存储库)共享数据。一个很好的例子是 Zustand + Next.js 的这个 github 仓库 。也可以使用裸 React.Context。
但是,这种方法以后可能会变得非常复杂,可能不适合没有经验的团队;这基本上类似于构建另一个与后端一样大的复杂层。这就是为什么现在的趋势已经转向 Query-SWR 解决方案。不过,在某些情况下,这还是很有用的。
import { useStore } from "../lib/store";
const SampleComponent = () => {
const { data } = useStore();
return <div>{JSON.stringify(data)}</div>
}
const MainPage() {
return <SampleComponent />
}
export async function getStaticProps() {
// refer to the github repo for this store structure
const zustandStore = initializeStore();
// this is a POC. the actual code could be different.
// store data is updated, and can be used globally in other components in a synchronized state.
const data = await useStore.setData(await fetch('...'));
return {
props: {
initialZustandState: JSON.parse(JSON.stringify({ ...zustandStore.getState(), data })),
},
};
}
5. 服务器端组件
随着 React 18 服务器端组件 的出现,Next.js 也在开发 Next.js 服务器组件 。
这个实现可能是问题中与代码最接近的实现。尽管如此,这项工作仍在进行中,而且非常不稳定。
我关注这种方法大约一年了,但其实现方式一直在变化。在我们获得稳定版本之前,这可以等一等。