本规范基于项目现有代码风格沉淀,每次coding时必须自动遵守 ✨
🔴 强制规则:所有 import/export 语句必须置于文件底部,而非顶部!
// ✅ 正确示例
const cssLoaderOptions = {
sourceMap: true,
modules: { ... }
};
export const webpackBaseConf:Configuration = { ... };
// import 语句统一放在文件底部
import WebpackLoader from './webpack-loader';
import { absolutelyPath_RepositoryRoot } from '../toolkit';
import babelConf from '../babel/conf';
import _ from 'lodash';
import path from 'path';
import webpack, {Configuration} from 'webpack';// ❌ 错误示例 - import 在顶部
import { app, BrowserWindow } from "electron";
const win = new BrowserWindow({ ... });从文件底部向上,按以下顺序排列:
- 相对路径 - 如
./components/*,../utils/* - 项目内部别名路径 - 如
#main/*,#generics/*,#project/*,#root/* - 第三方库 - 如
electron,lodash,react,antd等 - 样式文件 - 如
./index.less,./index.css
// 文件底部的 import 顺序示例
import { reaxel_SettingsView } from "#src/..."; // 相对/别名路径
import { Button, Form, Menu } from 'antd'; // 第三方库
import { reaxper } from 'reaxes-react'; // 第三方库
import './index.less'; // 最底部:样式文件在工具类文件中,导出语句放在业务逻辑之后:
// 业务逻辑代码
export const debounce = <F extends any[], T>(callback: T, wait: number) => { ... };
// 无依赖的导出放上面(如有注释标注)
/*无依赖@@utils的放上面*/
export * from './dayjs.utility';
export * from './isPromise.utility';
export * from './debounce.utility';-
reaxel 实例:使用
reaxel_前缀 + 帕斯卡命名const reaxel_MainWindowHub = reaxel(() => { ... }); const reaxel_storage = reaxel(() => { ... });
-
Store/State 命名:
const { store, setState, mutate } = createReaxable({ ... }); // 或 const { store, setState, mutate } = orzMobx({ ... });
-
返回值对象:使用有意义的名称
let rtn = { Core_Store: store, Core_SetState: setState, Core_Mutate: mutate, };
- React 组件:使用帕斯卡命名,导出时加
RC前缀(可选)export const App = reaxper(() => { ... }); export const RCAppearancePanel = () => { ... };
- 工具函数文件:使用
.utility.ts后缀debounce.utility.ts isPromise.utility.ts stringify.utility.ts
- Type/Interface:使用帕斯卡命名
type Layouts = Single | SplitInHorizontal | SplitInVertical; interface IpcStructure { ... }
export const reaxel_ModuleName = reaxel(() => {
// 1. 创建响应式状态
const { store, setState, mutate } = createReaxable({
mainWindow: null as BrowserWindow,
});
// 2. 观察反应(可选)
obsReaction(() => {
if(store.mainWindow){
useBeautifulDevtool(store.mainWindow);
}
}, () => [store.mainWindow]);
// 3. 业务逻辑函数
const createMainWindow = async (options) => {
// ...
};
createMainWindow();
// 4. 返回接口
const rtn = {
get mainWindow(){
return store.mainWindow;
},
};
return Object.assign(() => rtn, {
store,
setState,
mutate,
});
});
// import 语句放在文件底部
import { app, BrowserWindow } from 'electron';const { Item } = Form; // 解构外部组件
export const App = reaxper(() => {
// 1. 获取 store 和 setState
const store = reaxel_SettingsView.store.RootMenu;
const setState = reaxel_SettingsView.setState.RootMenu;
// 2. 获取方法
const { submitSettings, fetchSettings, exitSettings } = reaxel_SettingsView();
// 3. 计算属性/映射
const MenuContentComponent = {
net: RCNetworkPanel,
appearance: RCAppearancePanel,
}[store.current];
// 4. 副作用
useEffect(() => {
~async function () {
const settings = await fetchSettings();
}();
}, []);
// 5. 渲染 JSX
return <div>...</div>;
});
// import 放在底部
import { Button, Form, Menu } from 'antd';
import { reaxper } from 'reaxes-react';
import './index.less';/**
* @description 功能描述
* @param {type} paramName 参数说明
* @return {type} 返回值说明
*/
export const functionName = <T>(param: T): ReturnType => {
// 实现逻辑
};
// 无额外 import 或 import 在底部- 如果函数或工具可能被广泛复用、业务无关,应根据宿主环境放入相应目录的
utils或toolkits中,而不是散落在业务模块内。 - 工具函数文件使用
.utility.ts后缀;例如 ChatAIO 里的 IPC/observable plain clone 放在src/shared/utils/clone-for-ipc.utility.ts。
- 渲染进程只通过
window.api调用 preload 暴露的 API;主进程使用useIpcRpc/useIpcRendererToMain/useIpcMainToRenderer。完整规范见.qoder/rules/ipc-coding.md。 - 跨 IPC 前必须
cloneForIPC:参数若来自reaxel_*.store(含主进程曾以 plain JSON 下发、写入 store 后的数据),Renderer → Main 发送前一律克隆。勿因「源头是纯 JSON」而跳过。 - Store 往返:
Main plain JSON → store(observable) → api.xxx(...)的第二步仍要cloneForIPC。Menubar:openDropdownView的items、menuViewAction的action均为必检点。 - Code Review 时 IPC checklist 第一条即检查
cloneForIPC。
工具函数使用 JSDoc 格式:
/**
* @description 防抖功能
* @param {function} fn 要进行防抖处理的function
* @param {number} wait 间隔时间 ms为单位
* @param {boolean} immediate 开启后在最初的一次会立即执行
* @return {function} 进行防抖处理后的函数
*/
export const debounce = (callback, wait = 1000, immediate = false) => { ... };使用中文注释,简洁明了:
// 除了 macOS 外,当所有窗口都被关闭的时候退出程序
// app.on('window-all-closed', () => {
// if(process.platform !== 'darwin') app.quit();
// });
// 以下逻辑先假设用户将spore放置在上半边. 下半边的逻辑由useRevert劫持处理.
const useRevert = dropPosition === 'bottom';
// 原本的布局就是水平的,直接将另一边原本的还原
case prevLayout instanceof SplitInHorizontal: { ... }保留调试代码的注释形式:
//@ts-expect-error
window.core_store = store;
//debugger- 宽松模式:项目使用
strict: false,允许一定的类型灵活性 - 必要时使用
@ts-ignore或@ts-expect-error:/*@ts-ignore*/ if(!keys.includes(k)) { delete object[k]; }
工具函数广泛使用泛型:
export const debounce = <F extends any[], T extends ((...args: F) => any)>(
callback: T,
wait: number = 1000,
immediate: boolean = false
): T => { ... };
export const isPromise = <T = any>(target: any): target is Promise<T> => { ... };return object as Pick<O, ArrayElement<K>>;
return window.localStorage.getItem(key) as ret;在 tsconfig.json 和 webpack 配置中统一定义:
{
"paths": {
"#root/*": ["./*"],
"#root-projects/*": ["./projects/*"],
"#project/*": ["./当前项目src/*"],
"#generics/*": ["./generic-services/*"],
"#main/*": ["./当前项目src/Main/*"],
"#src/*": ["./当前项目src/*"]
}
}import { reaxel_SettingsView } from "#src/Views/SettingsView/reaxels/settings-view";
import { useBeautifulDevtool } from '#generics/modify-electron/beautiful-devtool';
import { reaxel_MainWindowHub } from '#main/reaxels/main-window-hub';try {
return JSON.parse(window.localStorage.getItem(key));
} catch(e) {
return window.localStorage.getItem(key) as ret;
}错误处理中使用 debugger 辅助调试:
try {
target.send('JSON', { channel }, ...args);
} catch(e) {
debugger;
throw e;
}if(!meta.channel) { throw new Error('channel is required') };
if(registered) { throw new Error('channel already registered'); }
throw `cannot find key '${key}' in storage`;- Tab 缩进:使用 Tab 而非空格
- 函数参数空格:参数前后加空格
app.whenReady().then(() => { ... }); obsReaction(() => { ... }, () => [store.mainWindow]);
语句末尾使用分号:
const win = new BrowserWindow({ ... });
win.loadURL("https://localhost:3111");优先使用单引号,特殊场景使用双引号:
import logger from 'electron-log/main';
const title = 'AI-WebTools-AIO';使用 ~ 或 () 包裹:
useEffect(() => {
~async function () {
const settings = await fetchSettings();
}();
}, []);export const App = reaxper(() => {
// 组件逻辑
return <div>...</div>;
});使用全局 ProvidePlugin 注入的 Hooks,无需显式 import:
// 无需 import { useState, useEffect } from 'react'
// 直接使用:
const [state, setState] = useState(initialValue);
useEffect(() => { ... }, []);const { Item } = Form;
const { submitSettings, fetchSettings } = reaxel_SettingsView();允许保留调试代码(注释状态或带条件):
// console.log('HDR support:', hdrSupported);
//@ts-expect-error
window.core_store = store;
//debugger暂时不用的代码注释保留,而非删除:
// if(false){
// Promise.all([chatGPTView,grokView]).then(([chatGPT, grok]) => {
// ...
// });
// }// 除了 macOS 外,当所有窗口都被关闭的时候退出程序
// app.on('window-all-closed', () => {
// if(process.platform !== 'darwin') app.quit();
// });- 禁止将 FloatingView 改为
setIgnoreMouseEvents(true, { forward: true })。 - Windows 上 Electron mouse forwarding 会干扰其它 BrowserWindow 的系统拖动,造成 Web menubar 抖动、闪烁和粘滞;FloatingView 即使 hidden 也可能触发。
- 当前必须保留
{ forward: false }。若未来确需转发mousemove,应在窗口移动/缩放期间关闭 forwarding,并完整回归。 - 修改 FloatingView、menubar drag region、透明窗口或鼠标穿透前,必须阅读
projects/ChatAIO/docs/issues/menubar-drag-investigation.md。
reaxels/
├── module-name/
│ └── index.ts(x) # reaxel 状态管理模块
│
views/
├── ViewName/
│ ├── index.tsx # 视图入口
│ ├── App.tsx # 主组件
│ └── components/ # 子组件
│
utils/
├── xxx.utility.ts # 工具函数
└── index.ts # 统一导出
- 主进程:
main.ts或index.tsx - 渲染进程:
index.tsx或App.tsx - 模块入口:统一使用
index.ts(x)
每次编写代码时,自动检查以下项:
- Import 是否放在文件底部?
- Import 是否按重要性排序?(第三方库 → 别名路径 → 相对路径 → 样式)
- reaxel 实例是否使用
reaxel_前缀? - 工具函数文件是否使用
.utility.ts后缀? - 通用、业务无关的工具是否放在对应
utils/toolkits目录? - 注释是否使用中文?
- 是否使用 Tab 缩进?
- 是否使用分号结尾?
- 是否优先使用路径别名(
#开头)? - 组件是否使用
reaxper包裹? - 错误处理是否包含
debugger(可选)? - ChatAIO FloatingView 是否保持
forward: false,并检查了 Windows 拖拽回归文档?
export const reaxel_ModuleName = reaxel(() => {
const { store, setState, mutate } = createReaxable({
// 状态定义
});
obsReaction(() => {
// 副作用
}, () => [/* 依赖 */]);
const someFunction = async () => {
// 业务逻辑
};
const rtn = {
// 公开接口
};
return Object.assign(() => rtn, {
store,
setState,
mutate,
});
});
import { ... } from '...';export const ComponentName = reaxper(() => {
const store = reaxel_ModuleName.store.xxx;
const setState = reaxel_ModuleName.setState.xxx;
const { method1, method2 } = reaxel_ModuleName();
useEffect(() => {
// 副作用
}, []);
return <div>...</div>;
});
import { ... } from '...';
import './index.less';🎉 遵守此规范,保持代码风格一致性!如有更新,及时同步本文档。 ✨