写在前面

G2 4.0 的 API 文档将全部通过代码注释生成,以保证代码和文档的同步更新,降低维护成本。

采用方案

TypeDoc + typedoc-plugin-markdown,API 文档通过 markdown 文本格式展示。

具体的工程配置详见:https://github.com/antvis/g2/pull/1569/files

规范

因为 TypeDoc 会自动根据 ts 定义做很多的自动工作,所以在在注释时,我们就可以更多得关注于解释本身,不需要过多得使用各种标记。为了保证注释风格的统一,请按照以下注释风格编写代码注释,同时需要保证注释尽可能详尽。

🙎要求:
**

  1. 代码中的类型定义要完整
  2. 需要给每个参数提供解释说明,如下所示:
  1. /**
  2. * 改变图表大小
  3. * @param width 宽度值
  4. * @param height 高度值
  5. */
  6. public changeSize(width: number, height: number) {
  7. this.width = width;
  8. this.height = height;
  9. // 重新渲染
  10. this.render();
  11. }

下面整理一下常用的注释场景以及对应 TypeDoc 的用法:

  1. 除一些方法内的注释外,所有注释必须以 /** 开头
  1. /** The sweet and fleshy product of a tree or other plant. */
  2. class Fruit {
  3. }
  1. 在注释中写代码示例
  1. /**
  2. * Codeblocks are great for examples
  3. *
  4. *
  • Highlight JS will autodetect the language
  • ```
  • ```typescript
  • // Or you can specify the language explicitly
  • const instance = new MyClass();
    1. */
    2. export class MyClass {}
  1. Tags:支持的标签见:http://typedoc.org/guides/doccomments/

  2. 类和接口注释,对于每个类,需要在类开始定义的地方,注释该类的用途,同时可以提供该类使用的代码示例,展示用法。

    注意:如果是面向接口编程,那么你只需要在接口上进行注释即可,具体的实现类不需要注释,默认 typedoc 会为你生成。

  1. /**
  2. * A shape is the form of an object or its external boundary, outline
  3. * @param U The unit used to measure the properties of this type of shape
  4. */
  5. interface Shape<U extends Unit > {
  6. area():number;
  7. }
  8. interface Unit {}
  9. interface Centimeter extends Unit {}
  10. /**
  11. * The Shape implementation used in Foo component to represent Bar
  12. *
  13. * ```typescript
  14. * const myCustomRectangle = new MyCustomRectangle();
  15. *

/ class MyCustomRectangle implements Shape { private side:number; public area():number { return this.sidethis.side; } }

  1. 5. 属性以及变量
  2. - 对于类型中的每一个属性都要提供详细的注释
  3. ```typescript
  4. interface GameUnit {
  5. /** unique id of this unit in the board */
  6. id: string
  7. /**
  8. * Status of this unit in the board
  9. *
  10. */
  11. status: {
  12. /** must be between 0 and 1 */
  13. health: number,
  14. /** the ids of units killed by this unit */
  15. kills: number[]
  16. }
  17. }
  18. enum GameState {
  19. /** game started but user paused it */
  20. paused,
  21. /** victory condition reached */
  22. ended,
  23. /** game is being played right now */
  24. playing,
  25. /** game didn't started yet - player is choosing initial race? */
  26. notStarted,
  27. }
  28. class Game {
  29. /**
  30. * State of the game at this moment
  31. */
  32. state: GameState
  33. config: GameConfiguration
  34. private _currentTime: Date
  35. /** the actual current time elapsed since this game was started not counting when it was paused */
  36. get currentTime():Date { return this._currentTime; }
  37. set currentTime(value:Date) { this._currentTime = value; }
  38. /** default board with and height if none is provided */
  39. static DEFAULT_STATE = GameState.notStarted
  40. }
  41. declare type GameConfiguration = {
  42. /** number of columns the board haves */
  43. boardWith?: number
  44. /** number of rows the board haves */
  45. boardHeight?: number
  46. /** board fog of war initial configuration */
  47. fogOfWar?: string[]
  48. }
  49. /** the global game singleton */
  50. export const gameContainer = new GameContainer()
  1. 方法和函数
  1. /**
  2. * @param T the type of access this file has on IO
  3. */
  4. class File<T extends FileAccess> {
  5. public constructor(fs:number) {} // constructor, no docs
  6. /**
  7. * Creates a new file in given path and given content
  8. * @param path absolute path of the file to create
  9. * @param content content of the new file or empty file if nothing given
  10. * @param T the type of access created file will have on IO
  11. * @return a new description of the access type the new file has
  12. */
  13. public static create<T extends FileAccess>(path: string, content?: string | Stream,
  14. permissions: string = '666'): T {
  15. return null;
  16. }
  17. /**
  18. * Internal method used by foobar
  19. * @param interval how often file is read in the polling
  20. * @param predicate polling will end when true
  21. */
  22. private poll(interval:number, predicate: (t: T) => boolean):void {}
  23. }
  24. interface FileEmitter<T> extends EventEmitter {
  25. /** 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. */
  26. on(event: 'before-file-modification', listener: (f: File<T>) => Promise<boolean>): this
  27. on(event: 'after-file-modification', listener: (f: File<T>, previousContent: Buffer) => void): this
  28. }
  29. /**
  30. * List children of given folder
  31. * @param FAT target files access type
  32. * @param options.force force read operation even if files are busy
  33. * @param options.recursive list all files recursively
  34. * @return if given path points to a folder returns a list of direct children Files,. Returns null otherwise
  35. */
  36. function listFiles<FAT extends FileAccess>(path: string,
  37. options?: {force: boolean, recursive: boolean})
  38. : File<FAT>[] | undefined { return [] }
  1. 事件使用 @event 标签
  1. class Readable2 extends EventEmitter {
  2. /**
  3. * Emitted whenever the stream is relinquishing ownership of a chunk of data to a consumer.
  4. * @event
  5. */
  6. static EVENT_DATA:'data' = 'data';
  7. /**
  8. * Typically, this may occur if the underlying stream is unable to generate data due to an underlying internal failure
  9. * @event
  10. */
  11. static EVENT_ERROR:'error' = 'error';
  12. /**
  13. * Typically, this may occur if the underlying stream is unable to generate data due to an underlying internal failure
  14. * @event
  15. */
  16. addListener(event: typeof Readable2.EVENT_DATA, listener: (chunk: Buffer | string) => void): this;
  17. addListener(event: typeof Readable2.EVENT_ERROR, listener: (error: Error) => void): this;
  18. addListener(event: string , listener: any): this {return this; }
  19. }
  1. 引用,当在注释中需要引用其他接口时,可以使用 [[]] 如下所示:
  1. interface Car {
  2. /**
  3. * Once started the engine will turn off only when the
  4. * car travel all the [[Route.size]]
  5. */
  6. engine:Engine
  7. size: number
  8. /**
  9. * Put in march this cart by turning on its [[engine]]
  10. */
  11. start()
  12. }
  13. interface Route {
  14. size:number[][]
  15. }
  16. interface Engine{}
  1. @hidden / @ignore 不想该段注释出现在 tydoc 的输出结果中,使用该标识符。最佳实践:将 @hidden 放置在注释的最前面。
  1. interface SomeThingsAreIgnored {
  2. /**
  3. * @hidden
  4. * this comment should be ignored
  5. *
  6. */
  7. method1():Promise<void>;
  8. /**
  9. * this comment shouldn't be ignored
  10. */
  11. method2():string;
  12. }
  13. /** @hidden */
  14. export class ClassTotallyIgnored {
  15. color:string;
  16. }

最后

相对来说 TypeDoc 的注释比较简单的,只要做到两点:

  1. 类型定义充分
  2. 提供变量描述

另外可以在 code 编辑器中安装 Comments in Typescript 插件,提高效率。

在项目下运行:

npm run doc

就会在 docs/api 目录下生成 api 文档。