Next13,不同的服务器和客户端获取结果

huangapple go评论78阅读模式
英文:

Next13, different fetching results on server and client

问题

我已经开始使用 Next13 来启动一个项目。一切都进行得很顺利,直到现在。我是一个初学者,我想了解为什么从获取数据时会得到不同的结果。正如你可能知道的,尽管组件被设置为客户端,但它也会在服务器上渲染,这两种渲染应该匹配。

这是获取数据的函数:

export type CountryType = {
    name: string;
    population: number;
    region: string | undefined;
    flagUrl: string | undefined;
    capital: string | undefined;
}

export default async function fetchData() {
    return fetch('https://restcountries.com/v3.1/all')
        .then((response) => response.json())
        .then((countries) => {
            return countries.map((element: any): CountryType => {
                return {
                    name: element.name.common,
                    population: element.population,
                    region: element.region,
                    flagUrl: element.flags.png,
                    capital: element.capital && element.capital[0],
                }
            })
        })
}

以下是实际运行获取数据函数的组件 - 当指令 "use client" 丢失时,它可以完美运行,因为它不必在客户端再次获取数据:

'use client';

import CountryInput from '@/ui/CountryInput';
import CountryItem from '@/ui/CountryItem';
import SelectRegion from '@/ui/SelectRegion';
import React, { use } from 'react';
import fetchData, { CountryType } from './fetchData';

export default function Page(): JSX.Element {
    const countries: CountryType[] = use(fetchData());

    console.log(countries[0]);

    return (
        <>
            <div className='w-full min-w-[315px] h-fit flex flex-col gap-y-12 px-4 items-center md:flex-row md:justify-between md:gap-x-2 md:px-16'>
                <CountryInput />
                <SelectRegion />
            </div>
            <div className='w-full min-w-[360px] h-fit flex flex-col gap-y-8 px-14'>
                {countries.map((element) => {
                    return (
                        <CountryItem
                            key={element.name}
                            name={element.name}
                            population={element.population}
                            region={element.region}
                            flagUrl={element.flagUrl}
                            capital={element.capital}
                        />
                    );
                })}
            </div>
        </>
    )
}

正如你可能已经注意到的,这个组件中有一个控制台日志 - 现在我将向你展示在客户端和服务器端分别返回的数据:

服务器端 (在 cmd 中):

{
    "name": "Iceland",
    "population": 366425,
    "region": "Europe",
    "flagUrl": "https://flagcdn.com/w320/is.png",
    "capital": "Reykjavik"
}

客户端 (浏览器控制台):

{
    "name": "Turkey",
    "population": 84339067,
    "region": "Asia",
    "flagUrl": "https://flagcdn.com/w320/tr.png",
    "capital": "Ankara"
}

这两者不匹配导致错误不断生成:

警告:在水合期间发生错误。在 <#document> 中,服务器 HTML 被客户端内容替换。

页面运行时间越长,错误就会生成得越多。

我已经在互联网上搜索了解决方案,但没有找到任何能帮助我的东西。

我已经检查了这在不同浏览器、不同方法上的行为。我希望这两个控制台日志是相同的。

英文:

I've started a project using Next13. Everything went fine untill now. I'm beginner and I'd like to discover why I'm getting different results from fetching. As you may know - although the component is set to client, it renders on server as well and both of these two renders should match each other.
There is the fetching function:

export type CountryType = {
	name: string
	population: number
	region: string | undefined
	flagUrl: string | undefined
	capital: string | undefined
}

export default async function fetchData() {
	return fetch(&#39;https://restcountries.com/v3.1/all&#39;)
		.then((response) =&gt; response.json())
		.then((countries) =&gt; {
			return countries.map((element: any): CountryType =&gt; {
				return {
					name: element.name.common,
					population: element.population,
					region: element.region,
					flagUrl: element.flags.png,
					capital: element.capital &amp;&amp; element.capital[0],
				}
			})
		})
}

And here is the component that actually runs the function to fetch data - it works perfectly when the directive 'use client' is lost, 'cause it doesn't have to do fetching once again on client side.

&#39;use client&#39;

import CountryInput from &#39;@/ui/CountryInput&#39;
import CountryItem from &#39;@/ui/CountryItem&#39;
import SelectRegion from &#39;@/ui/SelectRegion&#39;
import React, { use } from &#39;react&#39;
import fetchData, { CountryType } from &#39;./fetchData&#39;

export default function Page(): JSX.Element {
	const countries: CountryType[] = use(fetchData())

	console.log(countries[0])

	return (
		&lt;&gt;
			&lt;div className=&#39;w-full min-w-[315px] h-fit flex flex-col gap-y-12 px-4 items-center md:flex-row md:justify-between md:gap-x-2 md:px-16&#39;&gt;
				&lt;CountryInput /&gt;
				&lt;SelectRegion /&gt;
			&lt;/div&gt;
			&lt;div className=&#39;w-full min-w-[360px] h-fit flex flex-col gap-y-8 px-14&#39;&gt;
				{countries.map((element) =&gt; {
					return (
						&lt;CountryItem
							key={element.name}
							name={element.name}
							population={element.population}
							region={element.region}
							flagUrl={element.flagUrl}
							capital={element.capital}
						/&gt;
					)
				})}
			&lt;/div&gt;
		&lt;/&gt;
	)
}

As you may have noticed, there is a console log in this component - now I'm going to present to you what it returns on both client and server:

Server (in cmd):

{
  name: &#39;Iceland&#39;,
  population: 366425,
  region: &#39;Europe&#39;,
  flagUrl: &#39;https://flagcdn.com/w320/is.png&#39;,
  capital: &#39;Reykjavik&#39;
}

Client (browser's console):

{
    &quot;name&quot;: &quot;Turkey&quot;,
    &quot;population&quot;: 84339067,
    &quot;region&quot;: &quot;Asia&quot;,
    &quot;flagUrl&quot;: &quot;https://flagcdn.com/w320/tr.png&quot;,
    &quot;capital&quot;: &quot;Ankara&quot;
}

The fact that these two things don't match each other causes the error generating endlessly:

Warning: An error occurred during hydration. The server HTML was replaced with client content in &lt;#document&gt;.

The longer page with this error runs, the more errors are generated.

I've searched on the Internet for the solution, I haven't found anything that could help me.

I've checked how this behaves on different browsers, different approches.
I expect these two console logs to be the same.

答案1

得分: 2

这是代码的翻译部分:

The issue is not related to a browser. According to [Data Fetching: use in Client Components][1] you should not use `use` with `fetch` or it will cause re-renders and unwanted results.

The problem is this attempted async call in your client component:

`const countries: CountryType[] = use(fetchData())`

If you want React to load the async call when the component mounts, you can simply use a `useEffect(() =&gt; [])` to run an async call on mount.

They also recommend using a third-party library such as [SWR][2] or [React Query][3].

I used your code in a barebones Next.js 13 app to demonstrate that both `client` and `server` components show the same first country using the same end-point.

Here&#39;s a [working sandbox][4] that uses your code modified to demonstrate this.

For example inside of a **Next.js 13** app:

**page.tsx**

import ClientFetch from &quot;./ClientFetch&quot;
import ServerFetch from &quot;./ServerFetch&quot;

const App = async () =&gt; {
  return (
    &lt;&gt;
      &lt;ClientFetch /&gt;
      &lt;ServerFetch /&gt;
    &lt;/&gt;
  );
};

export default App;

**ServerFetch.tsx**

async function getData() {
  const res = await fetch(&quot;https://restcountries.com/v3.1/all&quot;);
  if (!res.ok) {
    throw a Error(&quot;Failed to fetch data&quot;);
  }

  return res.json();
}

const ServerFetch: any = async () =&gt; {
  const data = await getData();
  const countries = data.map((c: any) =&gt; {
    return {
      name: c.name.common,
      population: c.population,
      region: c.region,
      flagUrl: c.flags.png,
      capital: c.capital &amp;&amp; c.capital[0],
    };
  });

  console.log(&quot;--- ServerFetch ---&quot;, countries[0]);

  return (
    &lt;main&gt;
      &lt;h1&gt;SERVER FETCH&lt;/h1&gt;
      {[countries[0]].map((c: any, key: number) =&gt; (
        &lt;div key={key}&gt;
          &lt;h3&gt;{c.name}&lt;/h3&gt;
          &lt;p&gt;{c.population}&lt;/p&gt;
          &lt;p&gt;{c.region}&lt;/p&gt;
          &lt;p&gt;{c.flagUrl}&lt;/p&gt;
          &lt;p&gt;{c.capital}&lt;/p&gt;
        &lt;/div&gt;
      ))}
    &lt;/main&gt;
  );
};

export default ServerFetch;

**ClientFetch.tsx**

&quot;use client&quot;

import { useState, useEffect } from &quot;react&quot;

// export default async function
const ClientFetch = () =&gt; {
  const [countries, setCountries] = useState([]);

  useEffect(() =&gt; {
    (async () =&gt; {
      try {
        const res = await fetch(&quot;https://restcountries.com/v3.1/all&quot;);
        const data = await res.json();
        setCountries(
          data.map((c: any) =&gt; {
            return {
              name: c.name.common,
              population: c.population,
              region: c.region,
              flagUrl: c.flags.png,
              capital: c.capital &amp;&amp; c.capital[0],
            };
          })
        );
        console.log(&quot;--- ClientFetch ---&quot;, data[0]);
      } catch (e) {
        console.log(e);
      }
    })();
  }, []);

  return (
    &lt;main&gt;
      &lt;h1&gt;CLIENT FETCH&lt;/h1&gt;
      {countries.length
        ? [countries[0]].map((c: any, key: number) =&gt; (
            &lt;div key={key}&gt;
              &lt;h3&gt;{c.name}&lt;/h3&gt;
              &lt;p&gt;{c.population}&lt;/p&gt;
              &lt;p&gt;{c.region}&lt;/p&gt;
              &lt;p&gt;{c.flagUrl}&lt;/p&gt;
              &lt;p&gt;{c.capital}&lt;/p&gt;
            &lt;/div&gt;
          ))
        : null}
    &lt;/main&gt;
  );
};

export default ClientFetch;

请注意,代码中的HTML标签和特殊字符已经被转义。如果您需要进一步的翻译或解释,请随时提出。

英文:

The issue is not related to a browser. According to Data Fetching: use in Client Components you should not use use with fetch or it will cause re-renders and unwanted results.

The problem is this attempted async call in your client component:

const countries: CountryType[] = use(fetchData())

If you want React to load the async call when the component mounts, you can simply use a useEffect(() =&gt; []) to run an async call on mount.

They also recommend using a third-party library such as SWR or React Query.

I used your code in a barebones Next.js 13 app to demonstrate that both client and server components show the same first country using the same end-point.

Here's a working sandbox that uses your code modified to demonstrate this.

For example inside of a Next.js 13 app:

page.tsx

import ClientFetch from &quot;./ClientFetch&quot;;
import ServerFetch from &quot;./ServerFetch&quot;;

const App = async () =&gt; {
  return (
    &lt;&gt;
      &lt;ClientFetch /&gt;
      &lt;ServerFetch /&gt;
    &lt;/&gt;
  );
};

export default App;

ServerFetch.tsx

async function getData() {
  const res = await fetch(&quot;https://restcountries.com/v3.1/all&quot;);
  if (!res.ok) {
    throw new Error(&quot;Failed to fetch data&quot;);
  }

  return res.json();
}

const ServerFetch: any = async () =&gt; {
  const data = await getData();
  const countries = data.map((c: any) =&gt; {
    return {
      name: c.name.common,
      population: c.population,
      region: c.region,
      flagUrl: c.flags.png,
      capital: c.capital &amp;&amp; c.capital[0],
    };
  });

  console.log(&quot;--- ServerFetch ---&quot;, countries[0]);

  return (
    &lt;main&gt;
      &lt;h1&gt;SERVER FETCH&lt;/h1&gt;
      {[countries[0]].map((c: any, key: number) =&gt; (
        &lt;div key={key}&gt;
          &lt;h3&gt;{c.name}&lt;/h3&gt;
          &lt;p&gt;{c.population}&lt;/p&gt;
          &lt;p&gt;{c.region}&lt;/p&gt;
          &lt;p&gt;{c.flagUrl}&lt;/p&gt;
          &lt;p&gt;{c.capital}&lt;/p&gt;
        &lt;/div&gt;
      ))}
    &lt;/main&gt;
  );
};

export default ServerFetch;

ClientFetch.tsx

&quot;use client&quot;;

import { useState, useEffect } from &quot;react&quot;;

// export default async function
const ClientFetch = () =&gt; {
  const [countries, setCountries] = useState([]);

  useEffect(() =&gt; {
    (async () =&gt; {
      try {
        const res = await fetch(&quot;https://restcountries.com/v3.1/all&quot;);
        const data = await res.json();
        setCountries(
          data.map((c: any) =&gt; {
            return {
              name: c.name.common,
              population: c.population,
              region: c.region,
              flagUrl: c.flags.png,
              capital: c.capital &amp;&amp; c.capital[0],
            };
          })
        );
        console.log(&quot;--- ClientFetch ---&quot;, data[0]);
      } catch (e) {
        console.log(e);
      }
    })();
  }, []);

  return (
    &lt;main&gt;
      &lt;h1&gt;CLIENT FETCH&lt;/h1&gt;
      {countries.length
        ? [countries[0]].map((c: any, key: number) =&gt; (
            &lt;div key={key}&gt;
              &lt;h3&gt;{c.name}&lt;/h3&gt;
              &lt;p&gt;{c.population}&lt;/p&gt;
              &lt;p&gt;{c.region}&lt;/p&gt;
              &lt;p&gt;{c.flagUrl}&lt;/p&gt;
              &lt;p&gt;{c.capital}&lt;/p&gt;
            &lt;/div&gt;
          ))
        : null}
    &lt;/main&gt;
  );
};

export default ClientFetch;

Results:

Next13,不同的服务器和客户端获取结果

huangapple
  • 本文由 发表于 2023年2月19日 00:30:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/75494744.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定