Skip to content

[v1.3] 修正React重绘问题 (ScriptCard & ScriptTable) - #1182

Merged
CodFrm merged 31 commits into
scriptscat:release/v1.3from
cyfung1031:pr-973-04
Feb 12, 2026
Merged

CodFrm merged 31 commits into
scriptscat:release/v1.3from
cyfung1031:pr-973-04

Conversation

@cyfung1031

@cyfung1031 cyfung1031 commented Feb 1, 2026 •

Copy link
Copy Markdown
Collaborator

問題見 #1180 (comment)

hooks 和 index 的部份是 AI 重新合并再分开而成 (重构整体)
ScriptCard 和 ScriptTable 主要是人工修改+AI 评价 (重构 Draggable 相关)
主要针对 ScriptCard 修改。相关修改也放到 ScriptTable 了


问题概述(背景与动机)

在脚本列表页面(ScriptList)的实际使用中,存在较为明显的性能瓶颈,尤其在脚本数量较多(50+)的场景下,主要表现为:

  • 列表频繁发生闪烁和卡顿。例如在拖拽排序、启用/停用脚本、运行/停止脚本等操作时,整个列表(所有 ScriptCardItem 或表格行)会被重新渲染。
  • CPU 占用率较高,显著影响选项页的整体交互体验。
  • 该问题在 React 与 dnd-kit 组合使用时较为常见,但若不进行针对性优化,会直接导致页面流畅度下降,影响用户体验。

根因分析

经分析,问题主要由以下几方面共同导致:

  1. dnd-kit 的 Context 级联更新问题
    SortableContext 对 items 的变化高度敏感。当 scriptList 的数组引用发生变化(即使内容变化极小)时,SortableContext 会生成新的 items 数组,并触发所有使用 useSortable 的子组件重新计算位置。
    由于 Context 更新不受 React.memo 控制,最终导致整个列表被强制重渲染。

  2. 函数引用不稳定导致 memo 失效
    父组件向子组件传递的回调函数(如 handleRunStop 等)依赖于 t 或其他频繁变化的依赖项,导致每次渲染都会生成新的函数引用。
    在 props 对比阶段,React.memo 判定引用变化,从而触发不必要的组件重渲染。

  3. 状态更新缺乏精细化 diff 判断
    hooks 中的 updateScripts 等逻辑在更新状态时未区分“真实变化”和“无效更新”,即使仅修改单个脚本的局部字段(如 runStatus),也会创建新的列表对象并触发全量 setState 和 render。

  4. 其他影响 diff 效率的细节问题

    • 使用数组索引作为 key,导致 React 在列表变动时无法复用节点。
    • useEffect / useMemo / useCallback 依赖控制不够严格,增加了不必要的重新计算。

上述问题叠加,最终导致列表在高数据量场景下出现明显的性能劣化。

解决方案与关键改动说明

本次优化围绕 “稳定引用、减少 Context 级联影响、提升 diff 精度” 三个核心方向,对相关组件和 hooks 进行了重构。所有改动均为性能优化,不涉及功能行为变更。

1. ScriptCard.tsx(卡片视图优化)

改动原因
旧实现中,sortableIds 每次渲染都会通过 map 生成对象数组,增加了 dnd-kit 内部对比成本,并放大了 Context 更新的影响范围。

具体改动

  • 使用 useMemo 缓存 sortableIds,仅生成纯字符串 ID 数组(如 ['uuid1', 'uuid2']),避免传递对象引用。
    dnd-kit 对字符串数组的比较效率更高,可显著减少无效更新。
  • 使用 useCallback 包裹 handleDragEnd,并精简依赖项,仅依赖 scriptListSortOrder,保证函数引用稳定。
  • 强化 ScriptCardItem 的 React.memo 比较逻辑,仅比较影响 UI 渲染的关键字段(如 name、status、runStatus),忽略对视觉无影响的属性变化。
    - 列表 key 统一使用 item.uuid 作为稳定唯一标识,避免使用数组索引导致 DOM 重建。

效果
拖拽及常规操作时,仅相关节点发生更新;在移除 dnd-kit 的场景下,React.memo 可完全生效(当前仍保留拖拽能力)。

2. ScriptTable.tsx(表格视图优化)

改动原因
表格视图与卡片视图存在相同的问题:行组件依赖不稳定,在排序或操作单行时触发表格整体重渲染。

具体改动

  • 使用 useMemo 缓存排序结果和渲染数据,避免重复计算。
  • 所有事件处理函数(如 onClick、onToggle)统一使用 useCallback,确保引用稳定。
  • 规范 dnd-kit 的使用方式:
    • items 仅传递 ID 数组
      ~ - 使用 rectSortingStrategy,减少布局计算开销~
  • 表格行 key 同样统一使用 uuid。

效果
在大数据量表格中,对单行的操作不再触发整表重渲染。

3. hooks.tsx(状态管理与逻辑重构)

改动原因
原 hooks 实现缺乏变化判断和引用稳定机制,导致轻微状态更新被放大为全量更新,并沿组件树向下传播。

核心优化点

  • updateScripts
    新增精细化 diff 判断,仅在新旧值不一致时才更新状态,避免无意义的新对象创建和 setState。
  • 消息监听逻辑(如 scriptRunStatus、enableScripts)
    在更新前先比较状态是否发生真实变化,防止无效 render。
  • 回调函数稳定化
    handleDelete、handleConfig、handleRunStop、scriptListSortOrder 等全部使用 useCallback,并移除不必要的依赖(如稳定的 t)。
  • 搜索与过滤逻辑优化
    • 使用 active 标志防止异步搜索竞态问题
    • filterFuncs、统计数据等通过 useMemo 精准依赖,减少重复计算
  • 其他调整
    • 使用 map + filter(Boolean) 重建排序列表,逻辑更清晰
    • 标签及来源统计修正,all 项使用真实数量而非脚本总数

整体收益
hooks 逻辑更加模块化、可读性更强,类型安全性提升,同时显著减少无效渲染(80%+)。

4. 其他非核心优化

  • Sidebar:微调渲染逻辑,减少父子组件间的更新传染。
  • types/main.d.ts:细化类型定义,增强 TypeScript 严格性。

性能收益与测试建议

性能收益

  • 列表操作过程流畅,闪烁和卡顿问题消失
  • 渲染次数下降
  • 在大规模脚本场景下,CPU 占用显著降低

建议测试项

  • 50+ 脚本场景下:拖拽排序、运行/停止、搜索操作
    可通过 console.log("Rendered") 验证是否存在全量重渲染
  • 使用 React DevTools Profiler 对比优化前后的渲染树
  • 功能回归:
    • 拖拽排序结果持久化
    • 排序与服务端同步
    • 搜索防抖逻辑

@cyfung1031 cyfung1031 added P0 🚑 需要紧急处理的内容 hotfix 需要尽快更新到扩展商店 UI/UX 页面操作/显示相关 labels Feb 1, 2026
@CodFrm

CodFrm commented Feb 8, 2026 •

Copy link
Copy Markdown
Member

确实闪动,但是我记得我处理过一次,当时是Dnd的问题,不管了

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

该 PR 主要围绕选项页脚本列表(ScriptList)在大量脚本场景下的卡顿/闪烁问题,重构了列表的数据管理与拖拽(dnd-kit)集成方式,通过稳定引用与更细粒度的状态更新来减少不必要的 React 重绘。

Changes:

  • 重构 ScriptList 数据与过滤逻辑:拆分 hooks、引入更细的 diff 更新,减少无意义 setState。
  • 重构 ScriptCard / ScriptTable 的拖拽相关实现:items 传递纯 uuid 列表,尽量稳定 context/handler 引用。
  • 补充通用类型与 i18n 文案(operation_failed),并修复 favicon 加载相关的类型声明。

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/types/main.d.ts 新增 ReactStateSetter 全局类型别名,简化 state setter 的 props 类型声明
src/pages/store/favicons.ts 为 favicon 批处理栈补充类型,减少 any,并加强 shift() 非空断言
src/pages/options/routes/ScriptList/index.tsx 重构 ScriptList 逻辑:引入 MainContent memo、内联 updateScripts/业务操作/过滤逻辑与拖拽排序
src/pages/options/routes/ScriptList/hooks.tsx 拆分为 useScriptDataManagement + useScriptFilters,并重写消息监听与统计构建逻辑
src/pages/options/routes/ScriptList/Sidebar.tsx 适配新的 TSelectFilter / ReactStateSetter 类型
src/pages/options/routes/ScriptList/ScriptTable.tsx 重构表格拖拽与 memo 策略,减少 dnd-kit context 级联更新影响
src/pages/options/routes/ScriptList/ScriptCard.tsx 重构卡片拖拽实现(DragHandle/DraggableEntry)并对整体组件做 memo 优化
src/locales/*/translation.json 增加 operation_failed 多语言文案

Comment on lines +76 to +82
setScriptList((prev) => {
const altered = new Set<string>();
const newList = prev.map((s) => {
const item = chunkResults.find((r) => r.uuid === s.uuid);
if (item && s.favorite !== item.fav) {
altered.add(s.uuid);
return { ...s, favorite: item.fav };

Copilot AI Feb 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里对 prev 中每个脚本都做一次 chunkResults.find,会导致每个 favicon chunk 更新时变成 O(prev.length * chunkResults.length) 的查找;脚本数量较多时会明显放大 CPU 占用(与本 PR 优化目标相悖)。建议先把 chunkResults 预处理成 Map(uuid -> fav) 再单次遍历 prev 做 O(1) 更新判断。

Suggested change
setScriptList((prev) => {
const altered = new Set<string>();
const newList = prev.map((s) => {
const item = chunkResults.find((r) => r.uuid === s.uuid);
if (item && s.favorite !== item.fav) {
altered.add(s.uuid);
return { ...s, favorite: item.fav };
// 将 chunkResults 预处理为 Map,避免在列表遍历过程中重复执行 find
const favMap = new Map(chunkResults.map((r) => [r.uuid, r.fav]));
setScriptList((prev) => {
const altered = new Set<string>();
const newList = prev.map((s) => {
const fav = favMap.get(s.uuid);
if (fav !== undefined && s.favorite !== fav) {
altered.add(s.uuid);
return { ...s, favorite: fav };

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolved in e703f6c

不错。日后再看看其他 .find 处理有没有这个问题

icon={item.runStatus === SCRIPT_RUN_STATUS_RUNNING ? <RiStopFill /> : <RiPlayFill />}
loading={item.actionLoading}
size="mini"
onClick={() => handleRunStop(item, t)}

Copilot AI Feb 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handleRunStop 在 ScriptCardProps 中的类型是 (item) => Promise,但 ScriptCardItemProps 仍声明为 (item, t) => Promise,并在这里传入 t。额外参数虽然运行时会被忽略,但类型/调用约定不一致会增加维护成本。建议统一签名:去掉 ScriptCardItemProps 的 t 参数,并把这里改为 handleRunStop(item)。

Suggested change
onClick={() => handleRunStop(item, t)}
onClick={() => handleRunStop(item)}

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

对, AI 改了后就不用 传 t 值
它用 useCallback 了

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolved in 579a37b
统一签名:去掉 ScriptCardItemProps 的 t 参数,并把这里改为 handleRunStop(item)。

Comment on lines +185 to +194
let searchOrFilter = 0;
if (searchRequest.keyword) {
searchOrFilter = 1;
SearchFilter.requestFilterResult(searchRequest).then(() => {
if (searchOrFilter === 1) {
setFilterScriptList(list.filter((s) => SearchFilter.checkByUUID(s.uuid)));
} else {
setFilterScriptList(list);
}
});

Copilot AI Feb 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里的“取消/防竞态”逻辑有问题:effect cleanup 把 searchOrFilter 置 0 后,Promise resolve 仍会进入 else 分支并调用 setFilterScriptList(list),从而用旧的 list 覆盖新筛选结果(典型的 stale update)。建议改成 active 标志/AbortController:cleanup 时将 active=false,并在 then 内仅在 active 时更新;取消时不要再写回 list。

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolved in 421ebda

searchOrFilter -> enableKeywordSearch

Comment on lines +153 to +155
const next = arrayMove(prev, oldIdx, newIdx);
next.forEach((s, i) => (s.sort = i));
return next;

Copilot AI Feb 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里直接对 state 中的脚本对象做原地赋值(s.sort = i)。在当前 PR 通过 React.memo/引用稳定来减少重绘的前提下,这种 mutation 很容易导致依赖对象引用的 memo/比较逻辑失效或出现难以追踪的 UI 不更新问题。建议在更新 sort 时返回新对象(仅对 sort 变更的条目拷贝),或将 sort 作为派生数据而不是写回到已有对象上。

Suggested change
const next = arrayMove(prev, oldIdx, newIdx);
next.forEach((s, i) => (s.sort = i));
return next;
const moved = arrayMove(prev, oldIdx, newIdx);
return moved.map((s, i) => (s.sort === i ? s : { ...s, sort: i }));

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

其实在state 加个 .sort 不合适啦...
这个如果以 React 方式处理要全部 改 reference
我重新处理一下吧

@cyfung1031 cyfung1031 Feb 8, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

改这个太痛苦了
下回分解吧。这个PR先不理

sort 只影响整体排序而不排序单一元件
这样执行是没问题
但如果要找 AI agent 改代码的话肯定会改错

Comment on lines +184 to +185
selectedFilters: any,
searchRequest: SearchFilterRequest,

Copilot AI Feb 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useScriptFilters 的参数 selectedFilters、searchRequest 当前在函数体内未使用,会触发 @typescript-eslint/no-unused-vars(该仓库该规则为 error)。如果暂时不用,建议改名为 _selectedFilters / _searchRequest 或移除这些参数,并同步调整调用方。

Suggested change
selectedFilters: any,
searchRequest: SearchFilterRequest,
_selectedFilters: any,
_searchRequest: SearchFilterRequest,

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

先放著吧

Comment on lines +222 to +224
const tagItems = [
{ key: "all", label: t("script_list.sidebar.all"), icon: <IconTags />, count: Object.keys(tagMap).length },
...Object.keys(tagMap)

Copilot AI Feb 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tagItems 的“all”项 count 目前使用 Object.keys(tagMap).length(标签数量),这会在侧边栏 UI 上把“全部脚本”的数量显示成“标签数”,与 status/type 的 all=scriptList.length 也不一致。建议这里的 count 使用 scriptList.length(或至少与其它分组保持一致的语义)。

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个 all 不是真的 all
国人写的插件,英文差没办法啦

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

呀。我现在才理解 CodFrm 把 "all" 跟 string number 混在一起。那这样不行了
如果 tag 是 "all" 的话就会死掉

})),
];
const sourceItems = [
{ key: "all", label: t("script_list.sidebar.all"), icon: <IconLink />, count: Object.keys(originMap).length },

Copilot AI Feb 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sourceItems 的“all”项 count 目前使用 Object.keys(originMap).length(来源域名数量),会导致“全部脚本”的数量显示不正确/语义不一致。建议改为 scriptList.length(或保持与其它分组一致的计数口径)。

Suggested change
{ key: "all", label: t("script_list.sidebar.all"), icon: <IconLink />, count: Object.keys(originMap).length },
{ key: "all", label: t("script_list.sidebar.all"), icon: <IconLink />, count: scriptList.length },

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个 all 不是真的 all
国人写的插件,英文差没办法啦

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

呀。我现在才理解 CodFrm 把 "all" 跟 string number 混在一起。那这样不行了
如果 tag 是 "all" 的话就会死掉

@cyfung1031
cyfung1031 marked this pull request as draft February 8, 2026 02:39
@cyfung1031

cyfung1031 commented Feb 8, 2026 •

Copy link
Copy Markdown
Collaborator Author

Copilot 指出的问题我顺便处理一下吧
大多都是之前的写法不太理想

处理了
.sort 这个先不搞

@cyfung1031
cyfung1031 marked this pull request as ready for review February 8, 2026 03:03
@cyfung1031
cyfung1031 marked this pull request as draft February 8, 2026 04:11
@cyfung1031

Copy link
Copy Markdown
Collaborator Author

4080014

fix TypeScript, change "all" to null, reconstruct sortScript

fix TypeScript

  • AI重构时都把类写成 any, 修正类

change "all" to null

  • 你写 "all" 的话,如果 tag 也是 "all" 就会出错。现在 null 代表 没filter

reconstruct sortScript

  • 对 ScriptCard 来说,用原本的插入不合理。例如第 14 个插到 第2个时,后面通通会移位。
  • 现在改成 Swap。把拖拉效果改动一下配合 ScriptCard 的显示
  • 把原本的 ArrayMove 拆成 ArrayMove 和 ArraySwap
  • 后台不要重复这个做法。直接经前台的 sort order 去做储存更新
  • 后台 sort 完后会返回结果给前台,让前台其他画面/Tab的次序更新
  • 把这个结果更新的 sorted 重构了。统一这个 sort 方法,有效避免实际资料和 sort uuids 不一致时的处理
  • 把 updatetime 改动拿掉了。这个 sort 改变只是显示问题,不是脚本实际有任何更新。脚本的名字代码数据完全不变
  • 改成 Promise.all 避免大量 await 影响 CPU 处理

@cyfung1031
cyfung1031 marked this pull request as ready for review February 8, 2026 06:50
@cyfung1031

cyfung1031 commented Feb 8, 2026 •

Copy link
Copy Markdown
Collaborator Author

columns 和 components 如果整个包住做Memo的话,其中一个参考改变就会直影响所有子物件,然后作为 props 传至 Table 元件

现在把他们都分割出来,每一个 Render function 各自根据自己的参数做参考更新
因此除了 t 改变,其他都不会直接影响 columns 和 components

( Table 仅在 scriptList 更新时 重绘相应部份 )


TitleCell 是 <div> 所以要做 React.Memo


另外发现 filterDropdown 这玩意会不断呼叫,里面的 setFilterKeys 和 confirm 可能没做稳定,一直改动
但 ScriptFilterNode 元件其实只会在 function triggering 才会用到 setFilterKeys 和 confirm
因此他们直接抽出 React绘图以外,在需要时取最新值即可

@cyfung1031

cyfung1031 commented Feb 8, 2026 •

Copy link
Copy Markdown
Collaborator Author

结合

@CodFrm 大问题都修好了

好像还有一些小问题,例如明明是打开侧栏的动作但它的文字显示是关闭。但不重要,功能都正常能用。

0a00fbf ee0c839 修好了 打开/关闭侧栏 的显示

screen-capture.25.online-video-cutter.com.-00.00.00.000-00.00.28.559.mp4
screen-capture.25.online-video-cutter.com.-00.00.39.934-00.01.51.848.mp4
screen-capture.25.online-video-cutter.com.-00.02.01.963-00.04.11.790.mp4

@CodFrm

CodFrm commented Feb 11, 2026 •

Copy link
Copy Markdown
Member

为什么拿掉 transition ,感觉更奇怪了,我还以为又闪动了 76d4b1c

之前有动画,效果明显更好,直接恢复的效果也很奇怪,之前的动画效果很好的


二次编辑(不太重要,不想再添加一条评论了)

对 ScriptCard 来说,用原本的插入不合理。例如第 14 个插到 第2个时,后面通通会移位。

我觉得各有说法,不过你已经改了,无所谓吧

@cyfung1031

cyfung1031 commented Feb 11, 2026 •

Copy link
Copy Markdown
Collaborator Author

为什么拿掉 transition ,感觉更奇怪了,我还以为又闪动了 76d4b1c

之前有动画,效果明显更好,直接恢复的效果也很奇怪,之前的动画效果很好的

Css效果沒所謂。可能是我的瀏覽器問題吧。加回沒意見
看到排序那些你有改回。最初我改動是為了arraySwap
這個PR不計較排序實現方法。反正不會差很遠, 你喜歡就好
(如果有發現問題再另外PR)

@CodFrm CodFrm left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

感觉又有点滥用记忆化操作了

主要render性能问题在哪块呢?我结合情况看看

Comment thread src/pages/options/routes/ScriptList/hooks.tsx

// 语言改变 或 sidebarOpen 改变时,更新 columns
// eslint-disable-next-line react-hooks/exhaustive-deps
const columns = useMemo(() => columns0, [t, sidebarOpen]);

@CodFrm CodFrm Feb 11, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cf9d2a4

现在的写法如果 handleRunStop 之类的发生了变化,反而会出现问题,而且每次render都会产生大量的函数调用

正确的做法,我觉得应该是处理好相关的参考,原本也没有引用会经常变更的参考,主要还是我之前hooks实现的问题

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

columns 只有 t 和 sidebarOpen 的变化才会改动
handleRunStop 之类的改动是影响components 里单一 Render Function
不是整个 components 都要改

t 改 (例如语言)会导致components 的 title 文字改
sidebarOpen 改 (侧栏开关)会导致components 的 某一column的icon改

@CodFrm CodFrm Feb 12, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

那使用原来的写法,deps指定 [t, sidebarOpen] 不也可以么,还少了每次render的useCallback调用

https://github.com/scriptscat/scriptcat/blob/main/src/pages/options/routes/ScriptList/ScriptTable.tsx#L618-L631

大概明白你想法了,你不想要 Render Function 之类的引用也发生变动,控制得确实太精细了,也很累。。。而且这种控制,实际上有没有作用还要打个问号,所以我说有点滥用memo

@cyfung1031 cyfung1031 Feb 12, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

原本的很可能有小小改动就整个 components 改参考

我不是React的代码设计专家。我不知道那些性能坑有机会藏哪里
按个人理解,每个 render 做成稳定,这个 columns 就能保持不变。
但有两个情况 columns 要变。就是 t 和 sidebarOpen

然后按你的要求。东西不要分离。(不能写在UI外 - 不然那些 sort function 可以放在 UI 元件外面,就不用加 useMemo/useCallback 了)
代码要简单易懂易明易维护(每个 function 一个 callback 清楚 它们按什么 state 改变而改动)
最后加一个神奇的 const columns = useMemo(()=>columns0, [...])

我这样加了上去 AI也不会说什么。因为React就是玩魔法。一堆神奇代码

如果倒过来,你现在不写得这么细。日后你不知道要怎么改,或者为什么又有重绘/为什么没有UI更新

你要重构也行。东西全部用 Provider / Context 给 (之前我在搞ScriptEditor的UI有这么想过)
这样 columns 就不用 render 传东西
通通在元件入面开 useContext

但我不是专家我不知道这样做是好不好

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我引入了react-devtools,可以比较好的看到render原因和性能,你可以试试,我看了新版本和旧版本,性能上确实有一些提升,但是动画效果被破坏了

7d18bde

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

screen-capture.25.online-video-cutter.com.mp4

我这边的效果和你的不一样
所以之前直接移除了

你把 #1224 的修正先加入再测试吧
UI 不卡才能看到正确动画吧

你也可以把一些 useMemo 拿掉看看? 例如 columns / components

React 要修Bug要花大量时间。。。

我觉得修不了也可以接受吧。日后再处理

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CodFrm 修好了 f6e56e2

@CodFrm CodFrm Feb 12, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/scriptscat/scriptcat/blob/release/v1.3/src/pages/options/routes/ScriptList/hooks.tsx#L576-L602

找到问题点了,类似这个问题,使用的是filterList,但是sort的是list


哎呀,正准备改,你都提交了

@cyfung1031 cyfung1031 Feb 12, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我比你快3分钟~

我已经改了是 filteredList 不是 scriptlist

当arrayMove 执行时,会立即改 filteredList 而不是等 useEffect
后台在更新了再返回给前台时, 前台再更新本身的 scriptList


DragEnd -> FilterList Update
->. Backend update -> Notify Frontend -> Frontend update scriptlist -> end

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

呀。你 ScriptCard 又包了一层 list ...

Comment thread src/pages/options/routes/ScriptList/ScriptTable.tsx
Comment thread src/pages/options/routes/ScriptList/ScriptTable.tsx
Comment thread src/pages/options/routes/ScriptList/ScriptTable.tsx Outdated
Comment thread src/pages/options/routes/ScriptList/ScriptTable.tsx
Comment thread src/pages/store/features/script.ts Outdated
Comment thread src/pages/options/routes/ScriptList/ScriptCard.tsx
@cyfung1031

cyfung1031 commented Feb 12, 2026 •

Copy link
Copy Markdown
Collaborator Author

React的写法很麻烦。你的任何一个修改都需要仔细看。
但我也不能扔给AI
因为我提交的已经是好几个AI看过的
把你的修改扔过去它们一定说不好

我没这个时间看。所以你喜欢吧
这次我只看得出打勾拖拉的问题
如果因为你的改动而产生其他新问题,只能日后再分开提PR什么的

以往是不理重绘,全都跑一次
现在只避免重绘,所有有些改变可能没对应到而没了UI更新
这样的话针对性改就行

全部跑的话,只会感到很慢,但看不出是哪个部份有问题

再重申一次 useMemo useCallback 是 React必须的
如果是 Vue的话根本不用考虑这些

React 是把所有改动都做VDOM Diff再做DOM更新
而不是单一改动单一更新
因此只能这样搞


另外,这次PR做的useCallback 几乎都是在 Table 那里做
也就是它有一千行也好,都是只跑一次
我不认为这是一个滥用

@cyfung1031

cyfung1031 commented Feb 12, 2026 •

Copy link
Copy Markdown
Collaborator Author

Grok 观点

Screenshot 2026-02-12 at 10 14 24 Screenshot 2026-02-12 at 10 14 51

@CodFrm
CodFrm merged commit 55a6e93 into scriptscat:release/v1.3 Feb 12, 2026
3 checks passed
Comment on lines +635 to +636
sorter: useCallback((a: ListType, b: ListType) => a.updatetime! - b.updatetime!, []),
render: useCallback((col: number, script: ListType) => <UpdateTimeCell script={script} />, []),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

所以应该是写在上一层吧

    {
      key: "apply_to_run_status",
      dataIndex: "apply_to_run_status",
      title: t("apply_to_run_status"),
      width: t("script_list_apply_to_run_status_width"),
      className: "apply_to_run_status",
      render: useCallback(
        (col: any, item: ListType) => <ApplyToRunStatusCell item={item} navigate={navigate} t={t} />,
        [navigate, t]
      ),
    },
    

变成

useMemo(
  () => ({
    key: "apply_to_run_status",
    dataIndex: "apply_to_run_status",
    title: t("apply_to_run_status"),
    width: t("script_list_apply_to_run_status_width"),
    className: "apply_to_run_status",
    render: (col: any, item: ListType) => <ApplyToRunStatusCell item={item} navigate={navigate} t={t} />,
  }),
  [navigate, t]
),

这样的话,t 更新就会改变 column 然后触发 column.render

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这样的话就不用整个 columns 改变了

@cyfung1031
cyfung1031 deleted the pr-973-04 branch October 10, 2026 23:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

hotfix 需要尽快更新到扩展商店 P0 🚑 需要紧急处理的内容 UI/UX 页面操作/显示相关

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants