问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

Next.js 中关于'use client'的使用误解及解法

创作时间:
作者:
@小白创作中心

Next.js 中关于'use client'的使用误解及解法

引用
1
来源
1.
https://www.hansking.cn/blog/2024/nextjs-use-client

在Next.js中,'use client'指令的使用常常引发一些误解。本文将详细探讨server component和client component的区别,以及如何正确使用'use client'指令,帮助开发者避免常见的陷阱。

client component vs server component

我们在 page.tsx 中导入 Button 自定义组件

// app/page.tsx
import styles from './page.module.css'
import Button from './components/button'
export default function Home() {
    return (
        <main className={styles.main}>
            <h1>hello world</h1>
            <Button />
        </main>
    )
}

由于 Next.js 默认 src 之下的组件都是 server component,在 Button 组件(server component)中,如果直接调用 client component 才能使用的交互性(例如 onClickuseState)是会报错的。

可以简单按照报错提示修改为 client component 这样就没问题。但是如果你稍不注意直接在 page.tsx 加上 'use client' 也是没有报错的!

但是这样造成的问题是:导入 page.tsx 的所有组件都将是 'client component',那和直接写 React 有什么区别?

'use client'
import styles from './page.module.css'
import Button from './components/button'
import Post from './components/post'
export default function Home() {
    return (
        <main className={styles.main}>
            <h1>hello world</h1>
            <Button />
            <Post/>
        </main>
    )
}

假设 Post 组件中需要加载一个很大的第三方库,例如 sanitize-html。如果放在 client component 就不太适宜,可以看官方文档的第四条,将导入的较大第三方库尽量放在 server component 上,减少客户端的压力。

所以最优的解决办法就是:谁需要交互性就将最叶子节点变成客户端组件。


✔️

使用 context 封装 children

假设你需要使用一个上下文 context 传递一些数据,你可以这样写

// app/context/ThemeComtext.tsx
'use client'
import { useState, cloneElement, ReactNode, ReactElement } from 'react'
export default function ThemeContextProvider({ children }: { children: ReactNode }) {
    const [theme, setTheme] = useState('light')
    const toggleTheme = () => {
        setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light'))
    }
    return <div>{cloneElement(children as ReactElement, { theme, toggleTheme })}</div>
}

使用时在 layout 上封装一层 wrapper

import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import './globals.css'
import ThemeContextProvider from './context/ThemeContext'
const inter = Inter({ subsets: ['latin'] })
export default function RootLayout({ children }: { children: React.ReactNode }) {
    return (
        <html lang="en">
            <head />
            <body className={inter.className}>
                <ThemeContextProvider>{children}</ThemeContextProvider>
            </body>
        </html>
    )
}

ThemeContextProvider 其实是一个客户端组件,如果把 children 根组件 wrapper 在 这个 context 中,根据上面的说法岂不是之下的所有组件也都是客户端组件了?

正确思考方式是 : 关注 import tree 而不是 render tree

上一个例子中,我们在一个使用 'use client' 的客户端组件中 import 其他组件,那么这些组件都会变成客户端组件,而这里的例子其实只是传递组件 children,并不会将 children 中的服务端组件改成客户端组件

总结就是 改变组件和 jsx 渲染的结构无关,而是和 import 相关联

多次导入交互性组件

将一个交互性的组件分别导入至 client && server 组件会是什么情况?

交互性组件 Button

import style from './button.module.css'
export default function Button() {
    return (
        <div className={style.button} onClick={() => console.log('click me')}>
            Click me
        </div>
    )
}

server Component

import sanitizeHtml from 'sanitize-html'
import Button from './button'
export default function Post() {
    return (
        <div>
            Post 
            <Button />
        </div>
    )
}

Client component

'use client'
import Button from './button'
export default function Form() {
    return (
        <div>
            form <Button />
        </div>
    )
}

结果是符合直觉的

如果将 Post 组件删掉是不会报错的,也就是说在 client component 使用 Button 会表现为 client,而在 server component 不会被影响,各自独立

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号