How the "Use Client" directive works in Next.js 13
P粉044526217
2023-08-26 21:55:31
<p>I'm trying to learn about server-side rendering using Next.js 13.4 and the new <code>app/</code> directory. From what I understand, every component is a <em>server</em> component by default (i.e. rendered on the server side). </p>
<p>However, we can use the <code>'use client'</code> directive to force the component to become an <em>client</em> component. </p>
<p>Below, I've set up a simple "Hello World" component, first as a server component and then as a client component. In each case, I compare the page source code.</p>
<h3><code>src/app/page.js</code>(服务器组件)</h3>
<pre class="brush:php;toolbar:false;">export default function Home() {
return (
<main>
<h1>Hello World</h1>
</main>
)
}</pre>
<p>Chrome > 查看页面源代码</p>
<pre class="brush:php;toolbar:false;"><!DOCTYPE html>
<html lang="en">
<head>
<meta charSet="utf-8" />
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script src="/_next/static/chunks/polyfills.js" noModule=""></script>
</head>
<body>
<main>
<h1>Hello World</h1>
</main>
<script src="/_next/static/chunks/webpack.js" async=""></script>
<script src="/_next/static/chunks/main-app.js" async=""></script>
<script>(self.__next_f = self.__next_f || []).push([0])</script>
<script>self.__next_f.push([1, "0:"$L1"n"])</script>
<script>self.__next_f.push([1, "2:I{"id":"(app-client)/./node_modules/next/dist/client/components/app-router.js","chunks":["webpack:static/chunks/webpack.js"],"name":"","async":false}n4:I{"id":"(app-client)/./node_modules/next/dist/client/components/error-boundary.js","chunks":["webpack:static/chunks/webpack.js"],"name":"","async":false}n6:I{"id":"(app-client)/./node_modules/next/dist/client/components/layout-router.js","chunks":["app-client-internals:static/chunks/app-client-internals.js"],"name":"","async":false}n7:I{"id":"(app-client)/"])</script>
<script>self.__next_f.push([1, "./node_modules/next/dist/client/components/render-from-template-context.js","chunks":["app-client-internals:static/chunks/app-client-internals.js"],"name":"","async":false}n"])</script>
<script>self.__next_f.push([1, "1:[[],["$","$L2",null,{"assetPrefix":"","initialCanonicalUrl":"/","initialTree":["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],"initialHead":["$L3",null],"globalErrorComponent":"$4","notFound":["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$L5","$undefined",[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]]]}]}],"asNotFound":false,"children":[["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L6",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","loading":"$undefined","loadingStyles":"$undefined","hasLoading":false,"template":["$","$L7",null,{}],"templateStyles":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined","childProp":{"current":[["$","main",null,{"children":["$","h1",null,{"children":"Hello World"}]}],null],"segment":"__PAGE__"},"styles":[]}]}]}],null]}]]n"])</script>
<script>self.__next_f.push([1, "5:[[["$","meta",null,{"charSet":"utf-8"}],null,null,null,null,null,null,null,null,null,null,["$","meta",null,{"name":"viewport","content":"width=device-width, initial-scale=1"}],null,null,null,null,null,null,null,null,null,null,[]],[null,null,null,null],null,null,[null,null,null,null,null],null,null,null,null,null]n3:[[["$","meta",null,{"charSet":"utf-8"}],["$","title",null,{"children":"Create Next App"}],["$","meta",null,{"name":"description","content":"Generated by create next app"}],null,null,null,null,n"])</script>
<script>self.__next_f.push([1, "ull,null,null,null,["$","meta",null,{"name":"viewport","content":"width=device-width, initial-scale=1"}],null,null,null,null,null,null,null,null,null,null,[]],[null,null,null,null],null,null,[null,null,null,null,null],null,null,null,null,null]n"])</script>
</body>
</html></pre>
<h3><code>src/app/page.js</code>(客户端组件)</h3>
<pre class="brush:php;toolbar:false;">'use client';
export default function Home() {
return (
<main>
<h1>Hello World</h1>
</main>
)
}</pre>
<p>Chrome > 查看页面源代码</p>
<pre class="brush:php;toolbar:false;"><!DOCTYPE html>
<html lang="en">
<head>
<meta charSet="utf-8" />
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script src="/_next/static/chunks/polyfills.js" noModule=""></script>
</head>
<body>
<main>
<h1>Hello World</h1>
</main>
<script src="/_next/static/chunks/webpack.js" async=""></script>
<script src="/_next/static/chunks/main-app.js" async=""></script>
<script>(self.__next_f = self.__next_f || []).push([0])</script>
<script>self.__next_f.push([1, "0:"$L1"n"])</script>
<script>self.__next_f.push([1, "2:I{"id":"(app-client)/./node_modules/next/dist/client/components/app-router.js","chunks":["webpack:static/chunks/webpack.js"],"name":"","async":false}n4:I{"id":"(app-client)/./node_modules/next/dist/client/components/error-boundary.js","chunks":["webpack:static/chunks/webpack.js"],"name":"","async":false}n6:I{"id":"(app-client)/./node_modules/next/dist/client/components/layout-router.js","chunks":["app-client-internals:static/chunks/app-client-internals.js"],"name":"","async":false}n7:I{"id":"(app-client)/"])</script>
<script>self.__next_f.push([1, "./node_modules/next/dist/client/components/render-from-template-context.js","chunks":["app-client-internals:static/chunks/app-client-internals.js"],"name":"","async":false}n8:I{"id":"(app-client)/./src/app/page.js","chunks":["app/page:static/chunks/app/page.js"],"name":"","async":false}n"])</script>
<script>self.__next_f.push([1, "1:[[],["$","$L2",null,{"assetPrefix":"","initialCanonicalUrl":"/","initialTree":["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],"initialHead":["$L3",null],"globalErrorComponent":"$4","notFound":["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$L5","$undefined",[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]]]}]}],"asNotFound":false,"children":[["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L6",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","loading":"$undefined","loadingStyles":"$undefined","hasLoading":false,"template":["$","$L7",null,{}],"templateStyles":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined","childProp":{"current":[["$","$L8",null,{"params":{},"searchParams":{}}],null],"segment":"__PAGE__"},"styles":[]}]}]}],null]}]]n"])</script>
<script>self.__next_f.push([1, "5:[[["$","meta",null,{"charSet":"utf-8"}],null,null,null,null,null,null,null,null,null,null,["$","meta",null,{"name":"viewport","content":"width=device-width, initial-scale=1"}],null,null,null,null,null,null,null,null,null,null,[]],[null,null,null,null],null,null,[null,null,null,null,null],null,null,null,null,null]n3:[[["$","meta",null,{"charSet":"utf-8"}],["$","title",null,{"children":"Create Next App"}],["$","meta",null,{"name":"description","content":"Generated by create next app"}],null,null,null,null,n"])</script>
<script>self.__next_f.push([1, "ull,null,null,null,["$","meta",null,{"name":"viewport","content":"width=device-width, initial-scale=1"}],null,null,null,null,null,null,null,null,null,null,[]],[null,null,null,null],null,null,[null,null,null,null,null],null,null,null,null,null]n"])</script>
</body>
</html></pre>
<hr />
<p>我很困惑,因为客户端组件似乎已预渲染了 HTML,但“Hello World”显然存在于源代码中。我期望看到类似于 React 根 DOM 节点的东西 - 基本上是一个空的 div 等待注入一些 HTML。我在这里缺少什么?</p>
from here
Server and client components are rendered differently during static rendering:
Now, with Server and Client Components , React can render on both the client and the server, which means you can choose the rendering environment at the component level.
By default,
app
Router uses server components, allowing you to easily render components on the server and reduce the amount of JavaScript sent to the client.