refactor: upgrade ESLint 9→10 and simplify React lint toolchain

- Upgrade eslint and @eslint/js to v10
- Replace eslint-plugin-react-x + eslint-plugin-react-dom + eslint-plugin-react-hooks
  with unified @eslint-react/eslint-plugin
- Raise dev Node.js requirement to ^22.13.0 || >=24 (runtime packages unaffected)
- Add .npmrc with engine-strict=true
- Move all @eslint-react rule overrides to eslint.config.js,
  eliminating plugin-specific inline eslint-disable comments
- Fix real issues caught by new rules: useless assignments,
  leaked setTimeout, ref naming, useState setter naming
This commit is contained in:
Simon
2026-04-08 20:31:31 +08:00
parent a43e653a74
commit 4f80ec1459
17 changed files with 1649 additions and 1687 deletions

View File

@@ -169,7 +169,6 @@ function highlightSyntax(code: string): string {
const HighlightSyntaxClient: React.FC<HighlightSyntaxProps> = ({ code }) => {
const htmlContent = highlightSyntax(code)
// eslint-disable-next-line react-dom/no-dangerously-set-innerhtml
return <code className={styles.syntax} dangerouslySetInnerHTML={{ __html: htmlContent }} />
}

View File

@@ -140,6 +140,7 @@ function JSConsole({
// 全局console拦截处理
useEffect(() => {
const interceptor = ConsoleInterceptor.getInstance()
let scrollTimer: ReturnType<typeof setTimeout>
const handleGlobalConsole = (type: string, args: unknown[]) => {
const content = args.map((arg) => formatResult(arg)).join(' ')
@@ -152,8 +153,8 @@ function JSConsole({
setOutputs((prev) => [...prev, outputItem])
// 自动滚动到底部
setTimeout(() => {
clearTimeout(scrollTimer)
scrollTimer = setTimeout(() => {
if (outputRef.current) {
outputRef.current.scrollTop = outputRef.current.scrollHeight
}
@@ -164,6 +165,7 @@ function JSConsole({
return () => {
interceptor.unsubscribe(handleGlobalConsole)
clearTimeout(scrollTimer)
}
}, [])

View File

@@ -9,19 +9,19 @@ const LanguageContext = createContext<{
} | null>(null)
export function LanguageProvider({ children }: { children: ReactNode }) {
const [language, setLang] = useState<Lang>(() => {
const [language, setLanguage] = useState<Lang>(() => {
const stored = localStorage.getItem('language') as Lang
if (stored === 'zh-CN' || stored === 'en-US') return stored
return navigator.language.startsWith('zh') ? 'zh-CN' : 'en-US'
})
const setLanguage = (lang: Lang) => {
setLang(lang)
const switchLanguage = (lang: Lang) => {
setLanguage(lang)
localStorage.setItem('language', lang)
}
return (
<LanguageContext value={{ language, isZh: language === 'zh-CN', setLanguage }}>
<LanguageContext value={{ language, isZh: language === 'zh-CN', setLanguage: switchLanguage }}>
{children}
</LanguageContext>
)

View File

@@ -1,4 +1,3 @@
/* eslint-disable react-dom/no-dangerously-set-innerhtml */
import type { PageAgent as PageAgentType } from 'page-agent'
import { useEffect, useState } from 'react'
import { Link, useSearchParams } from 'wouter'
@@ -46,10 +45,11 @@ export default function HeroSection() {
: 'Goto docs in navigation bar, find Quick-Start section, and summarize in markdown'
const [task, setTask] = useState(() => defaultTask)
useEffect(() => {
const [prevDefaultTask, setPrevDefaultTask] = useState(defaultTask)
if (prevDefaultTask !== defaultTask) {
setPrevDefaultTask(defaultTask)
setTask(defaultTask)
}, [defaultTask])
}
const [params] = useSearchParams()
const isOther = params.has('try_other')

View File

@@ -18,10 +18,12 @@ function ScrollToTop() {
export default function Router() {
useEffect(() => {
const schedule = globalThis.requestIdleCallback ?? ((cb: () => void) => setTimeout(cb, 1))
const cancel = globalThis.cancelIdleCallback ?? clearTimeout
const id = schedule(() => docsImport())
return () => cancel(id)
if ('requestIdleCallback' in globalThis) {
const id = requestIdleCallback(() => docsImport())
return () => cancelIdleCallback(id)
}
const id = setTimeout(() => docsImport(), 1)
return () => clearTimeout(id)
}, [])
return (