写在前面
G2 4.0 的 API 文档将全部通过代码注释生成,以保证代码和文档的同步更新,降低维护成本。
采用方案
TypeDoc + typedoc-plugin-markdown,API 文档通过 markdown 文本格式展示。
具体的工程配置详见:https://github.com/antvis/g2/pull/1569/files
规范
因为 TypeDoc 会自动根据 ts 定义做很多的自动工作,所以在在注释时,我们就可以更多得关注于解释本身,不需要过多得使用各种标记。为了保证注释风格的统一,请按照以下注释风格编写代码注释,同时需要保证注释尽可能详尽。
🙎要求:
**
- 代码中的类型定义要完整
- 需要给每个参数提供解释说明,如下所示:
/*** 改变图表大小* @param width 宽度值* @param height 高度值*/public changeSize(width: number, height: number) {this.width = width;this.height = height;// 重新渲染this.render();}
下面整理一下常用的注释场景以及对应 TypeDoc 的用法:
- 除一些方法内的注释外,所有注释必须以
/**开头
/** The sweet and fleshy product of a tree or other plant. */class Fruit {}
- 在注释中写代码示例
/*** Codeblocks are great for examples**
Highlight JS will autodetect the language - ```
- ```typescript
- // Or you can specify the language explicitly
- const instance = new MyClass();
*/export class MyClass {}
Tags:支持的标签见:http://typedoc.org/guides/doccomments/
类和接口注释,对于每个类,需要在类开始定义的地方,注释该类的用途,同时可以提供该类使用的代码示例,展示用法。
注意:如果是面向接口编程,那么你只需要在接口上进行注释即可,具体的实现类不需要注释,默认 typedoc 会为你生成。
/*** A shape is the form of an object or its external boundary, outline* @param U The unit used to measure the properties of this type of shape*/interface Shape<U extends Unit > {area():number;}interface Unit {}interface Centimeter extends Unit {}/*** The Shape implementation used in Foo component to represent Bar** ```typescript* const myCustomRectangle = new MyCustomRectangle();*
/
class MyCustomRectangle implements Shape
5. 属性以及变量- 对于类型中的每一个属性都要提供详细的注释```typescriptinterface GameUnit {/** unique id of this unit in the board */id: string/*** Status of this unit in the board**/status: {/** must be between 0 and 1 */health: number,/** the ids of units killed by this unit */kills: number[]}}enum GameState {/** game started but user paused it */paused,/** victory condition reached */ended,/** game is being played right now */playing,/** game didn't started yet - player is choosing initial race? */notStarted,}class Game {/*** State of the game at this moment*/state: GameStateconfig: GameConfigurationprivate _currentTime: Date/** the actual current time elapsed since this game was started not counting when it was paused */get currentTime():Date { return this._currentTime; }set currentTime(value:Date) { this._currentTime = value; }/** default board with and height if none is provided */static DEFAULT_STATE = GameState.notStarted}declare type GameConfiguration = {/** number of columns the board haves */boardWith?: number/** number of rows the board haves */boardHeight?: number/** board fog of war initial configuration */fogOfWar?: string[]}/** the global game singleton */export const gameContainer = new GameContainer()
- 方法和函数
/*** @param T the type of access this file has on IO*/class File<T extends FileAccess> {public constructor(fs:number) {} // constructor, no docs/*** Creates a new file in given path and given content* @param path absolute path of the file to create* @param content content of the new file or empty file if nothing given* @param T the type of access created file will have on IO* @return a new description of the access type the new file has*/public static create<T extends FileAccess>(path: string, content?: string | Stream,permissions: string = '666'): T {return null;}/*** Internal method used by foobar* @param interval how often file is read in the polling* @param predicate polling will end when true*/private poll(interval:number, predicate: (t: T) => boolean):void {}}interface FileEmitter<T> extends EventEmitter {/** registers the listener to be notified **before** a file is about to change. The change will be hold until all listeners returned promises are resolved. If any listener reject the promise the file modification action will be canceled. */on(event: 'before-file-modification', listener: (f: File<T>) => Promise<boolean>): thison(event: 'after-file-modification', listener: (f: File<T>, previousContent: Buffer) => void): this}/*** List children of given folder* @param FAT target files access type* @param options.force force read operation even if files are busy* @param options.recursive list all files recursively* @return if given path points to a folder returns a list of direct children Files,. Returns null otherwise*/function listFiles<FAT extends FileAccess>(path: string,options?: {force: boolean, recursive: boolean}): File<FAT>[] | undefined { return [] }
- 事件使用
@event标签
class Readable2 extends EventEmitter {/*** Emitted whenever the stream is relinquishing ownership of a chunk of data to a consumer.* @event*/static EVENT_DATA:'data' = 'data';/*** Typically, this may occur if the underlying stream is unable to generate data due to an underlying internal failure* @event*/static EVENT_ERROR:'error' = 'error';/*** Typically, this may occur if the underlying stream is unable to generate data due to an underlying internal failure* @event*/addListener(event: typeof Readable2.EVENT_DATA, listener: (chunk: Buffer | string) => void): this;addListener(event: typeof Readable2.EVENT_ERROR, listener: (error: Error) => void): this;addListener(event: string , listener: any): this {return this; }}
- 引用,当在注释中需要引用其他接口时,可以使用
[[]]如下所示:
interface Car {/*** Once started the engine will turn off only when the* car travel all the [[Route.size]]*/engine:Enginesize: number/*** Put in march this cart by turning on its [[engine]]*/start()}interface Route {size:number[][]}interface Engine{}
@hidden/@ignore不想该段注释出现在 tydoc 的输出结果中,使用该标识符。最佳实践:将@hidden放置在注释的最前面。
interface SomeThingsAreIgnored {/*** @hidden* this comment should be ignored**/method1():Promise<void>;/*** this comment shouldn't be ignored*/method2():string;}/** @hidden */export class ClassTotallyIgnored {color:string;}
最后
相对来说 TypeDoc 的注释比较简单的,只要做到两点:
- 类型定义充分
- 提供变量描述
另外可以在 code 编辑器中安装 Comments in Typescript 插件,提高效率。
在项目下运行:
npm run doc
就会在 docs/api 目录下生成 api 文档。
