安装 chroma-panel,导入一次样式表,就可以在 Next.js 的任意一种路由中渲染它。服务端组件可以直接导入选择器。只有当你自己的组件用到状态或传入事件处理函数时,才需要加 "use client"。
安装
npm install chroma-panel不需要再装其他包。dependencies 是空的,react 和 react-dom 是 peer 依赖,所以用的就是你应用里已有的那一份。
样式表
面板在 effect 中注入自己的样式表。这意味着在水合完成之前,服务端渲染出的标记是没有样式的。要避免这种闪烁,可以自己导入样式表,并关闭自动注入:
<ColorInput injectStyles={false} />导入写在哪里取决于你用的路由,下面各节分别给出了写法。injectStyles={false} 只是不再写入 <style> 标签,无论哪种方式,CSS 都会进入你的包。如果你使用 Tailwind,请改为在 CSS 文件中导入样式表,并声明层级顺序,具体见使用 Tailwind 样式。
配置页面
App Router
在根布局中导入一次样式表。布局仍然是服务端组件。
// app/layout.tsx
import "chroma-panel/style.css";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}只要传入的每个 prop 都可以序列化,服务端组件就能直接渲染选择器:
// app/brand/page.tsx
import { ColorInput } from "chroma-panel";
export default function BrandPage() {
return (
<main>
<h2>Brand color</h2>
<ColorInput defaultValue="#3366cc" injectStyles={false} />
</main>
);
}一旦由你自己保存颜色,这个文件就得是客户端组件。useState 和 onChange 处理函数都只能在客户端使用,所以要把它们放在你自己的 "use client" 文件里:
// app/brand/brand-picker.tsx
"use client";
import { useState } from "react";
import { ColorInput, type ColorChangeResult } from "chroma-panel";
export function BrandPicker() {
const [color, setColor] = useState<string>("#3366cc");
const handleChange = (result: ColorChangeResult): void => {
setColor(result.hex);
};
return (
<>
<ColorInput value={color} onChange={handleChange} injectStyles={false} />
<p style={{ color }}>{color}</p>
</>
);
}这个文件之所以是客户端组件,是因为你的状态,而不是因为 chroma-panel。哪些 prop 由你管理、哪些由面板自己保存,见受控与非受控。
Pages Router
Pages Router 没有服务端组件,所以任何地方都用不到 "use client"。Next.js 只允许在 pages/_app.tsx 中导入全局样式表,CSS 就放在这里:
// pages/_app.tsx
import type { AppProps } from "next/app";
import "chroma-panel/style.css";
export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}页面本身就是一个普通的 React 组件:
// pages/brand.tsx
import { useState } from "react";
import { ColorInput, type ColorChangeResult } from "chroma-panel";
export default function BrandPage() {
const [color, setColor] = useState<string>("#3366cc");
const handleChange = (result: ColorChangeResult): void => {
setColor(result.hex);
};
return (
<main>
<ColorInput value={color} onChange={handleChange} injectStyles={false} />
</main>
);
}哪里需要 "use client"
| 文件 | 是否需要 "use client" |
|---|---|
| 渲染选择器的服务端组件 | 否 |
用 useState 保存颜色的文件 | 是 |
传入 onChange 的文件 | 是 |
pages/ 下的任何文件 | 不适用 |
这条规则针对的是你自己的代码。服务端组件无法跨越边界传递函数,所以只要有处理函数,就必须是客户端组件。
无需动态导入
你不需要 next/dynamic,也不需要 ssr: false。这个包在模块加载时不会读取 window。在服务端会跳过样式注入,但选中的颜色依然会以 CSS 自定义属性的形式写入标记。
你不需要 ssr: false
用 ssr: false
的动态导入包裹选择器,只会让它晚一个往返才出现。它原样就能在服务端正确渲染。