尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Cocos Creator 引擎 TypeScript/JavaScript 编码规范全解:从命名规则到 ESLint 落地实践

Cocos Creator 引擎 TypeScript/JavaScript 编码规范全解:从命名规则到 ESLint 落地实践 Cocos Creator 引擎 TypeScript/JavaScript 编码规范全解从命名规则到 ESLint 落地实践【免费下载链接】cocos-engineCocos simplifies game creation and distribution with Cocos Creator, a free, open-source, cross-platform game engine. Empowering millions of developers to create high-performance, engaging 2D/3D games and instant web entertainment.项目地址: https://gitcode.com/GitHub_Trending/co/cocos-engine导读本文基于 Cocos Creator 引擎官方维护的 docs/TS_CODING_STYLE.md 编码规范文档系统梳理引擎开发团队在 TypeScript/JavaScript 开发中沉淀的一整套代码风格约定——包括命名规则、语法写法、缩进与分号、注释规范等。这些规范既是 Cocos Creator 引擎数以千计 TypeScript 源文件分布于 cocos/ 与 pal/ 目录的写作宪法也是任何希望向引擎提交代码、深度定制引擎或学习大型游戏引擎工程化经验的开发者必备的参考手册。读完本文你将掌握一套可直接复制到自身项目的 TS/JS 编码规范并理解如何通过 ESLint、EditorConfig 等工具把规范固化为可自动执行的工程约束。背景这套规范从何而来Cocos Creator 是一个跨平台 2D/3D 游戏引擎其 TypeScript 代码承担着引擎脚本层引擎逻辑、组件系统、渲染管线入口的全部职责。在进入具体规范之前先厘清引擎的工程背景这决定了规范中很多看似特例的约定例如允许下划线前缀、关闭部分严格规则TypeScript 用于引擎的脚本层代码即 cocos/ 与 pal/ 下的全部逻辑引擎使用Babel 与 Rollup将 TypeScript 编译为 JavaScript这一点可以从 package.json 的devDependencies中看到babel/core、babel/preset-env、cocos/babel-preset-cc等依赖引擎强烈推荐所有开发者使用 ESLint进行静态检查仓库根目录的 .eslintrc.yaml 正是这套规范的机器可读形态编辑器环境建议使用 VSCode进行 TypeScript 开发配合 .editorconfig 统一各编辑器的格式行为在Web 运行时中引擎完全运行编译后的 TypeScript 代码另有部分 WebAssembly 模块如物理引擎在原生运行时中TypeScript 代码运行在 C 代码与 JavaScript 绑定层之上参见 native/ 目录的实现。值得强调的是由于 TypeScript 编译器与 JavaScript 引擎仍在快速演进官方刻意让这份规范保持简单、可维护避免过度复杂化——规范的目的是统一协作而不是制造教条。命名规则Naming Rules命名是代码可读性的第一道门槛。Cocos 引擎的命名约定覆盖了变量、函数、类、常量、文件五个维度。对象、实例变量、属性、函数与命名空间使用 camelCase// bad let FOOBar {}; let foo_bar {}; function FOOBar () {} // good let fooBar {}; function fooBar () {}缩写词的大小写保持整体对齐当变量、属性或函数中包含缩写词时缩写词的所有字符大小写必须保持一致如果名字以缩写开头则缩写全部小写否则缩写全部大写。这一规则避免了Id、iD这类既不统一又容易与单词混淆的写法。// bad let Id 0; let iD 0; function requireId () {} // good let id 0; let uuid ; function requireID () {} class AssetUUID {}从引擎源码中可以大量看到这一约定的影子例如 cocos/core/geometry/plane.ts 等几何模块中大量使用Vec3、Quat这类缩写全部大写的命名。类或模块使用 PascalCase// bad let foobar cc.Class({ foo: foo, bar: bar, }); let foobar require(foo-bar); // good let FooBar cc.Class({ foo: foo, bar: bar, }); let FooBar require(foo-bar);访问器AccessorAPI 约定访问器getter/setter不是万能的引擎给出了非常务实的取舍原则当属性是直接值时直接声明public属性即可不要为了封装而封装// bad class A { get a ():number { return this._a; } set a (val:number) { this._a val; } private _a: number; } // good class A { public a: number; }当数据访问需要重量级逻辑例如内部需要计算结果时建议将属性设为 private并提供getXXX/setXXX方法// not recommended class A { get property ():number { // computeResult have some heavy logic return this.computeResult(); } } // recommended class A { getProperty ():number { // computeResult have some heavy logic return this.computeResult(); } }当数据访问会隐式影响其他数据时例如 setter 内部要联动更新其他字段同样建议使用getXXX/setXXX方法让副作用显式化// not recommended class A { get property ():number { return this._prop; } set property (val: number) { this._prop val; this._a this.computeA(); } private _prop: number; private _a: number; } // recommended class A { getProperty ():number { return this._prop; } setProperty (val: number) { this._prop val; this._a this.computeA(); } private _prop: number; private _a: number; }当数据访问是轻量级时则建议将属性设为 private 并提供 public 访问器。其余情况遵循文件上下文中已有的 API 设计风格保持一致性优先。这套轻量用 getter、重量用方法的取舍直接服务于游戏引擎对运行时性能的敏感getter 每次访问都会触发函数调用而在热点代码路径如每帧遍历组件、更新变换矩阵中隐式 getter 调用容易被 JIT 优化丢失或带来额外开销。常量全大写 下划线分隔// bad const PRIVATE_letIABLE should not be unnecessarily uppercased within a file; // bad let THING_TO_BE_CHANGED should obviously not be uppercased; // bad let REASSIGNABLE_letIABLE do not use let with uppercase letiables; // --- // allowed but does not supply semantic value export const apiKey SOMEKEY; // better in most cases export const API_KEY SOMEKEY; // --- // bad - unnecessarily uppercases key while adding no semantic value export const MAPPING { KEY: value }; // good export const Type { SIMPLE: value };注意这里的三个关键判断可重新赋值的变量不得大写大写必须带来语义价值const修饰的对象如果整体语义是一个类型命名空间则外层用 PascalCase如Type内部条目才用大写。私有属性下划线_前缀// bad class A { private __firstName__: string; private firstName_: string; } // good class A { private _firstName: string; }单下划线前缀、且只用于私有属性避免双下划线保留给语言内部和尾下划线易读性差。这一约定在 ESLint 中通过关闭no-underscore-dangle规则获得支持见 .eslintrc.yaml 的no-underscore-dangle: off及注释引擎源码中_x、_name、_lview等写法随处可见。文件名小写 短横线-// bad fooBar.ts FooBar.ts // good foo-bar.ts从仓库目录可以直观验证例如 cocos/2d/framework/、cocos/animation/ 下的源文件几乎全部采用kebab-case短横线分隔命名。语法参考Syntax References命名之外引擎对 TypeScript 语法细节给出了一系列性能敏感型建议。无初始值的类属性优先使用declare这是本规范中最具性能动机的一条。如果类属性没有初始化值TypeScript 编译时会自动在构造函数中注入this.a void 0;这类赋值语句当属性被反复赋值、类型不断变化时这会带来可感知的运行时开销。解决方案是给属性显式初始值或者使用declare声明该属性由外部如构造函数参数负责赋值// bad class A { public a:number; constructor (a:number) { // After compilation, the following line would be added to the constructor. // this.a void 0; // As the type of a is changing, this could cause performance issue this.a a; } } // good class A { public a:number 0; // Ok. constructor (a:number) { // After compilation, the following line would be added to the constructor. // this.a 0; // The type of a is constant this.a a; } } // best class A { public declare a:number; public b:undefined | object; // OK: b wont be reassigned in constructor public declare c:object|null; constructor (a:number, c:object) { this.a a; this.c c; } }declare在引擎源码中已被广泛采用例如 cocos/core/data/object.ts 中的public declare [editorExtrasTag]: unknown;与 cocos/core/data/utils/attribute.ts 中的public declare name: string;这些都是属性赋值完全由外部机制编辑器扩展 Tag、装饰器接管的典型场景。字典对象优先Object.create(null)当对象作为字典使用且内容会被频繁增删时建议使用Object.create(null)创建。这样做的好处是产出一个没有原型链污染的纯净对象__proto__、toString、hasOwnProperty等键不再冲突且for-in遍历更高效。// bad let map new Object(); // bad let map {}; // good let map Object.create(null);这一约定在引擎的资产管理模块中大量落地。例如 cocos/asset/asset-manager/config.ts 中的options.paths Object.create(null)以及 cocos/asset/asset-manager/depend-util.ts 中的const exclude: Recordstring, any Object.create(null)都是需要高频率增删键值的缓存/查找表场景。严格比较使用与!统一使用严格比较/!替代宽松比较/!避免隐式类型转换带来的意外结果。这一规则在 ESLint 中被配置为eqeqeq: warn见 .eslintrc.yaml。其他编码约定Other Coding Conventions缩进4 个空格// bad function () { ∙let name; } // bad function () { ∙∙let name; } // very bad function () { ∙∙tablet name; } // good function () { ∙∙∙∙let name; }4 空格缩进同时被写入了两个工具层配置ESLint 规则indent: [error, 4, { SwitchCase: 0, ... }].eslintrc.yaml以及 .editorconfig 中的indent_size 4、indent_style space。前者管代码检查后者管编辑器格式化双保险确保所有贡献者产出格式一致的代码。行尾与文件结尾不要留下行尾空格文件末尾保留一个空行即最后一行以换行符结束。// bad function () {∙ ∙∙∙∙let name;∙ } /* EOF */ // good function () { ∙∙∙∙let name; } /* EOF */这同样由 .editorconfig 的trim_trailing_whitespace true与insert_final_newline true固化。分号每条代码行末尾必须加分号// bad proto.foo function () { } // good proto.foo function () { }; // bad function foo () { return test } // very bad // returns undefined instead of the value on the next line, // always happens when return is on a line by itself because of Automatic Semicolon Insertion! function foo () { return test } // good function foo () { return test; } // bad function foo () { }; // good: this is not a code line function foo () { }文档特别强调了不要依赖自动分号插入ASI当return单独占一行时ASI 会把它解析成return undefined;而不是返回下一行的值这是 JS 中最经典的坑之一。同时注意规则的本意是代码行加分号函数声明的大括号本身不算代码行因此不需要在函数体后补分号。大括号与上一行同行// bad if ( isFoobar ) { } // good if ( isFoobar ) { } // bad function foobar () { } // good function foobar () { } // bad let obj { foo: foo, bar: bar, } // good let obj { foo: foo, bar: bar, }采用 KR 风格行尾大括号这是 C 系语言的主流风格也与 Google JS 风格一致。空格{前留一个空格// bad if (isJedi){ fight(); } else{ escape(); } // good if (isJedi) { fight(); } else { escape(); } // bad dog.set(attr,{ age: 1 year, breed: Bernese Mountain Dog, }); // good dog.set(attr, { age: 1 year, breed: Bernese Mountain Dog, });空格if、else、while、switch后留一个空格// bad if(isJedi) { fight (); } else{ escape(); } // good if (isJedi) { fight(); } else { escape(); }注意控制流关键字与左括号之间必须有空格而函数调用的fight()与括号之间不空格——这两条规则的边界在引擎源码和 ESLintspace-before-function-paren: [warn, always]、keyword-spacing: warn配置中得以区分。空格二元与三元运算符两侧留一个空格// bad let xy5; let left rotated? y: x; // good let x y 5; let left rotated ? y : x; // bad for (let i0; i 10; i) { } // good for (let i 0; i 10; i) { }函数声明写法// bad let test function () { console.log(test); }; // good function test () { console.log(test); } // bad function test () { console.log(test); }; // good function test () { console.log(test); } // bad function divisibleFunction () { return DEBUG ? foo : bar; } // best let divisibleFunction DEBUG ? function () { return foo; } : function () { return bar; }; // bad function test(){ } // good function test () { } // bad let obj { foo: function () { } }; // good let obj { foo () { } }; // bad array.map(xx 1); array.map(x { return x 1; }); // good array.map(x x 1);这组示例传达几个要点优先使用具名函数声明而非函数表达式赋值涉及条件分支选择不同实现时用赋值表达式 三元替代单一函数内的分支返回使选哪个实现在声明处就一目了然对象方法使用简写语法foo () {}箭头函数在表达式体expression body场景下应保持简洁的单行形式。代码块之间留空行// bad if (foo) { return bar; } return baz; // good if (foo) { return bar; } return baz; // bad const obj { x: 0, y: 0, foo () { }, bar () { }, }; return obj; // good const obj { x: 0, y: 0, foo () { }, bar () { }, }; return obj;分号对齐逗号在前是反模式// bad let story [ once , upon , aTime ]; // good let story [ once, upon, aTime, ]; // bad let hero { firstName: Ada , lastName: Lovelace , birthYear: 1815 , superPower: computers }; // good let hero { firstName: Ada, lastName: Lovelace, birthYear: 1815, superPower: computers, };数组与对象字面量统一采用逗号在行尾的写法且允许建议尾逗号方便后续追加条目时减少 diff 噪音。注释规范单行注释//bad // good多行注释/* * good */API 文档注释/** * good */除 API 文档外所有注释必须使用英文书写// bad // 中文注释不利于非中文开发者阅读代码 // good // Please write all in file comments in English这是开源协作的硬性要求Cocos Creator 引擎面向全球开发者非英文注释会直接阻断跨语言协作与自动化文档生成。引擎大量公共 API 采用/** ... */文档注释配合 TypeDoc 生成 typedoc-index.ts 与 typedoc.json 所描述的类型文档。工程落地规范如何被工具强制执行光有文档不够Cocos Creator 引擎将上述规范写进了三层工具链确保规范可检查、可自动修复、可统一编辑器行为。1. ESLint静态规则检查仓库根目录的 .eslintrc.yaml 是规范的机器化表达核心配置如下解析与继承使用typescript-eslint/parser继承eslint:recommended、airbnb-base与typescript-eslint/recommended等基础规则集与本文档呼应的关键规则indent: [error, 4]—— 4 空格缩进space-before-function-paren: [warn, always]—— 函数名与(之间留空格Some function declaration references一节的规则eqeqeq: warn—— 强制严格比较no-console: error—— 禁止裸console要求使用统一日志方法max-len: [warn, 150]—— 单行最长 150 字符quotes: [warn, single]—— 优先单引号允许模板字符串typescript-eslint/consistent-type-assertions: [error, { assertionStyle: as }]—— 类型断言统一使用as语法而非尖括号尖括号在isTSX开启时与 JSX 冲突typescript-eslint/consistent-type-definitions: [error, interface]—— 类型定义优先interfacetypescript-eslint/explicit-function-return-type: [error, { allowIIFEs: true }]—— 强制显式函数返回类型但允许 IIFEtypescript-eslint/no-unsafe-*系列被关闭注释中说明 we still rely heavily on legacyCCno-underscore-dangle: off、camelcase: off—— 为下划线前缀私有属性等引擎惯例让路typescript-eslint/ban-ts-comment: [error]—— 除ts-check外禁止使用ts-expect-error、ts-ignore、ts-nocheck注释逃避类型检查。环境与全局变量声明browser、node、es6、jest环境并将cc、wx、Editor、_Scene等引擎全局标识符列入 globals。值得注意的是配置中几乎所有off/warn都附有注释说明理由例如 BEFORE ADDING ANY RULES PLEASE EXPLAIN THE REASON IN THE COMMENT这与文档开篇保持简单的精神一脉相承每条规则都应有明确的动机而不是为规则而规则。2. EditorConfig统一编辑器格式化行为.editorconfig 为所有开发者提供跨编辑器VSCode、WebStorm、Sublime 等一致的格式基线root true [*] charset utf-8 end_of_line lf indent_size 4 indent_style space insert_final_newline true trim_trailing_whitespace true它与文档中4 空格缩进不留尾部空格文件末尾空行三条约定一一对应保证即使开发者不主动运行格式化命令编辑器也会产出合规代码。3. 构建与测试流水线规范最终通过 CI 化的检查形成闭环。在 package.json 中可以看到test脚本为tsc --noEmit jest先做全量类型检查再运行 Jest 单元测试测试配置见 jest.config.js测试用例集中在 tests/ 目录项目使用typescript^4.9.5、eslint^8.44.0并要求node 18.0.0tsconfig.json 开启了strict: true、experimentalDecorators: true、isolatedModules: true等严格编译选项与declare语法建议共同保障类型安全。也就是说一份合格的引擎代码提交要同时通过 ESLint风格、tsc --noEmit类型和 Jest行为三层校验编码规范文档、ESLint 配置、EditorConfig 与 CI 脚本构成了文档 → 配置 → 自动化的完整落地链路。延伸参考本文档的规则体系借鉴了业界两大主流 JS 风格指南的思想Google JavaScript Style Guide与Airbnb JavaScript Style Guide仓库中的eslint-config-airbnb-base依赖即来自后者。读者在制定自身团队规范时可以将 Cocos 的这份性能敏感型游戏引擎定制规范作为中间参照——它的价值不仅在于规则本身更在于展示了一套规则必须服务于工程目标运行时性能、协作效率、工具链一致性的取舍方法论。小结Cocos Creator 的 TypeScript/JavaScript 编码规范看似条目繁多实则围绕三条主线展开可读性camelCase/PascalCase、下划线私有属性、kebab-case 文件名、可靠性严格比较、强制分号、避免 ASI 陷阱、declare消除冗余初始化、性能Object.create(null)纯净字典、轻量访问用 getter、重量逻辑用方法。而在文档之外引擎已通过 .eslintrc.yaml、.editorconfig、package.json 的test脚本将规范固化为可自动执行的工程约束——这份文档 配置 CI的三位一体实践正是大型开源引擎项目最值得借鉴的工程化范本。【免费下载链接】cocos-engineCocos simplifies game creation and distribution with Cocos Creator, a free, open-source, cross-platform game engine. Empowering millions of developers to create high-performance, engaging 2D/3D games and instant web entertainment.项目地址: https://gitcode.com/GitHub_Trending/co/cocos-engine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表