Skip to content

Commit a965ef9

Browse files
committed
fix: 一些样式错误
1 parent eaee935 commit a965ef9

3 files changed

Lines changed: 46 additions & 22 deletions

File tree

app/components/GiscusComments.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import { useEffect, useMemo, useState } from "react";
3+
import { useEffect, useMemo, useState, useSyncExternalStore } from "react";
44
import Giscus from "@giscus/react";
55
import { useTheme } from "./ThemeProvider";
66

@@ -9,6 +9,10 @@ interface GiscusCommentsProps {
99
docId?: string | null;
1010
}
1111

12+
// 用 useSyncExternalStore 代替 useEffect+setState(true) 的 mounted 哨兵,
13+
// SSR 快照固定返回 false,客户端返回 true,避免 react-hooks/set-state-in-effect
14+
const emptySubscribe = () => () => {};
15+
1216
export function GiscusComments({ className, docId }: GiscusCommentsProps) {
1317
const { theme } = useTheme();
1418
const normalizedDocId = typeof docId === "string" ? docId.trim() : "";
@@ -18,16 +22,16 @@ export function GiscusComments({ className, docId }: GiscusCommentsProps) {
1822
// 如果这里直接渲染 Giscus,iframe 会以 SSR 的错主题加载;之后即使 key 变化触发
1923
// remount,@giscus/react 的 iframe 也可能残留旧 theme。延迟到 mount 后再渲染,
2024
// 用的就是客户端已经对齐过 localStorage 的主题。
21-
const [mounted, setMounted] = useState(false);
25+
const mounted = useSyncExternalStore(
26+
emptySubscribe,
27+
() => true,
28+
() => false,
29+
);
2230
const [isSystemDark, setIsSystemDark] = useState(() => {
2331
if (typeof window === "undefined") return false;
2432
return window.matchMedia("(prefers-color-scheme: dark)").matches;
2533
});
2634

27-
useEffect(() => {
28-
setMounted(true);
29-
}, []);
30-
3135
useEffect(() => {
3236
if (theme !== "system" || typeof window === "undefined") return;
3337
const media = window.matchMedia("(prefers-color-scheme: dark)");

app/components/LocaleToggle.tsx

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,15 @@
1515
* - 简单的 ZH / EN 双字母展示,当前语言高亮;button 尺寸与 ThemeToggle 对齐
1616
*/
1717

18-
import { useEffect, useState } from "react";
18+
import { useSyncExternalStore } from "react";
1919
import { useRouter } from "next/navigation";
2020
import { Button } from "@/components/ui/button";
2121

2222
type Locale = "zh" | "en";
2323

24+
// 自定义事件名:toggle 点击后 dispatch,通知 useSyncExternalStore 重新读取 cookie
25+
const LOCALE_EVENT = "locale-toggle-change";
26+
2427
function readLocaleCookie(): Locale {
2528
if (typeof document === "undefined") return "zh";
2629
const m = document.cookie.match(/(?:^|;\s*)locale=([^;]+)/);
@@ -33,21 +36,35 @@ function writeLocaleCookie(next: Locale) {
3336
document.cookie = `locale=${next};path=/;max-age=${60 * 60 * 24 * 365};samesite=lax`;
3437
}
3538

39+
// 订阅 LOCALE_EVENT,toggle 时主动 dispatch 让 useSyncExternalStore 重新读 cookie
40+
function subscribeLocale(callback: () => void) {
41+
if (typeof window === "undefined") return () => {};
42+
window.addEventListener(LOCALE_EVENT, callback);
43+
return () => window.removeEventListener(LOCALE_EVENT, callback);
44+
}
45+
46+
const emptySubscribe = () => () => {};
47+
3648
export function LocaleToggle() {
3749
const router = useRouter();
38-
// 初始 render 先给默认值避免 hydration 不一致,真实值由 useEffect 读 cookie 后覆盖
39-
const [locale, setLocale] = useState<Locale>("zh");
40-
const [ready, setReady] = useState(false);
41-
42-
useEffect(() => {
43-
setLocale(readLocaleCookie());
44-
setReady(true);
45-
}, []);
50+
// 用 useSyncExternalStore 替代 useEffect+setState:SSR 返回 "zh",客户端读 cookie
51+
const locale = useSyncExternalStore<Locale>(
52+
subscribeLocale,
53+
() => readLocaleCookie(),
54+
() => "zh",
55+
);
56+
// ready 表示已 hydrate 到客户端,可以按真实 locale 高亮
57+
const ready = useSyncExternalStore(
58+
emptySubscribe,
59+
() => true,
60+
() => false,
61+
);
4662

4763
const toggle = () => {
4864
const next: Locale = locale === "zh" ? "en" : "zh";
4965
writeLocaleCookie(next);
50-
setLocale(next);
66+
// 通知所有订阅者(当前组件)重新从 cookie 读取
67+
window.dispatchEvent(new Event(LOCALE_EVENT));
5168
// 刷新 server component 树,重新按 cookie 渲染各页面
5269
router.refresh();
5370
};

app/components/SidebarThemeToggle.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,21 @@
88
* 内置按钮点击无效;这里直接 setTheme 到自建 ThemeProvider 即可)。
99
*/
1010

11-
import { useEffect, useState } from "react";
11+
import { useSyncExternalStore } from "react";
1212
import { Moon, Sun } from "lucide-react";
1313
import { useTheme } from "./ThemeProvider";
1414
import { cn } from "@/lib/utils";
1515

16+
// 用 useSyncExternalStore 处理 SSR/CSR mounted 差异,避免 effect 内 setState
17+
const emptySubscribe = () => () => {};
18+
1619
export function SidebarThemeToggle() {
1720
const { theme, setTheme } = useTheme();
18-
const [mounted, setMounted] = useState(false);
19-
20-
useEffect(() => {
21-
setMounted(true);
22-
}, []);
21+
const mounted = useSyncExternalStore(
22+
emptySubscribe,
23+
() => true,
24+
() => false,
25+
);
2326

2427
// 把 "system" 折算成实际显示主题,决定哪个 icon 高亮
2528
const resolved = (() => {

0 commit comments

Comments
 (0)