开发者问题收集

Next.js 获取 Json 以在组件中显示

2022-03-14
1609

我试图将 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个回答

解决方案

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 服务器组件

这个实现可能是问题中与代码最接近的实现。尽管如此,这项工作仍在进行中,而且非常不稳定。

我关注这种方法大约一年了,但其实现方式一直在变化。在我们获得稳定版本之前,这可以等一等。

sungryeol
2022-11-17