将使用脚本编辑器 Web 部件生成的 jQuery 和 DataTables 解决方案迁移到 SharePoint 框架

经常使用的 jQuery 插件之一是 DataTables。 使用 DataTables,可以轻松生成 SharePoint 和外部 API 中数据的强大数据概览。

使用脚本编辑器 Web 部件生成的 IT 请求的列表

为了说明将使用 DataTables 的 SharePoint 自定义迁移到 SharePoint 框架的过程,将使用以下解决方案,其中概览了从 SharePoint 列表中检索到的 IT 支持请求。

在 SharePoint 页上显示 IT 支持请求的概述

该解决方案是使用标准 SharePoint 脚本编辑器 Web 部件生成的。 下面是自定义项使用的代码。

<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://cdn.datatables.net/1.10.15/js/jquery.dataTables.js"></script>
<script src="https://momentjs.com/downloads/moment.min.js"></script>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.15/css/jquery.dataTables.min.css" />
<table id="requests" class="display" cellspacing="0" width="100%">
  <thead>
    <tr>
      <th>ID</th>
      <th>Business unit</th>
      <th>Category</th>
      <th>Status</th>
      <th>Due date</th>
      <th>Assigned to</th>
    </tr>
  </thead>
</table>
<script>
  // UMD
  (function(factory) {
    "use strict";

    if (typeof define === 'function' && define.amd) {
      // AMD
      define(['jquery'], function ($) {
        return factory( $, window, document );
      });
    }
    else if (typeof exports === 'object') {
      // CommonJS
      module.exports = function (root, $) {
        if (!root) {
          root = window;
        }

        if (!$) {
          $ = typeof window !== 'undefined'
            ? require('jquery')
            : require('jquery')( root );
        }

        return factory($, root, root.document);
      };
    } else {
      // Browser
      factory(jQuery, window, document);
    }
  }
  (function($, window, document) {
    $.fn.dataTable.render.moment = function (from, to, locale) {
      // Argument shifting
      if (arguments.length === 1) {
        locale = 'en';
        to = from;
        from = 'YYYY-MM-DD';
      } else if (arguments.length === 2) {
        locale = 'en';
      }

      return function (d, type, row) {
        var m = window.moment(d, from, locale, true);

        // Order and type get a number value from Moment, everything else
          // sees the rendered value
          return m.format(type === 'sort' || type === 'type' ? 'x' : to);
        };
      };
    }));
</script>
<script>
$(document).ready(function() {
  $('#requests').DataTable({
    'ajax': {
      'url': "../_api/web/lists/getbytitle('IT Requests')/items?$select=ID,BusinessUnit,Category,Status,DueDate,AssignedTo/Title&$expand=AssignedTo/Title",
      'headers': { 'Accept': 'application/json;odata=nometadata' },
      'dataSrc': function(data) {
        return data.value.map(function(item) {
          return [
            item.ID,
            item.BusinessUnit,
            item.Category,
            item.Status,
            new Date(item.DueDate),
            item.AssignedTo.Title
          ];
        });
      }
    },
    columnDefs: [{
      targets: 4,
      render: $.fn.dataTable.render.moment('YYYY/MM/DD')
    }]
  });
});
</script>

首先,自定义加载它使用的库:jQuery、DataTables 和 Moment.js。

接下来,它指定用于呈现数据的表结构。

创建表后,它将 Moment.js 包装到 DataTables 插件,以便能够设置表中日期的格式。

最后,自定义使用 DataTables 加载和呈现 IT 支持列表请求。 SharePoint 列表数据是通过使用 AJAX 加载的。

得益于使用 DataTables,最终用户会获得功能非常强大的解决方案,无需执行其他任何额外开发工作,即可对结果轻松执行筛选、排序和分页。

使用由分配给 Lidia 的请求筛选的 DataTables 显示的 IT 支持请求列表,按截止日期降序排序

将 IT 请求概览解决方案从脚本编辑器 Web 部件迁移到 SharePoint 框架

将此自定义转换为 SharePoint 框架带来了许多好处,如解决方案配置变得更用户友好、解决方案管理更集中化。 下面逐步介绍了如何将解决方案迁移到 SharePoint 框架。 首先,你会将解决方案迁移到 SharePoint 框架,并尽量不要更改原始代码。 稍后,你会将解决方案的代码转换为 TypeScript,以便受益于它的开发时间类型安全功能。

新建 SharePoint 框架项目

  1. 首先,为项目新建文件夹:

    md datatables-itrequests
    
  2. 导航到项目文件夹:

    cd datatables-itrequests
    
  3. 在项目文件夹中,运行 SharePoint Framework Yeoman 生成器,以搭建新的 SharePoint Framework 项目:

    yo @microsoft/sharepoint
    
  4. 出现提示时,请输入以下值(为下面省略的所有提示选择默认选项):

    • 你的解决方案名称是什么?:datatables-itrequests
    • 要创建哪种类型的客户端组件?:Web 部件
    • Web 部件的名称是什么?:IT 请求
    • Web 部件的说明是什么?:显示 IT 支持请求的概述
    • 要使用哪种框架?:没有 JavaScript 框架
  5. 在代码编辑器中,打开项目文件夹。 在本教程中,你将使用 Visual Studio Code。

加载 JavaScript 库

与使用脚本编辑器 Web 部件生成的原始解决方案类似,首先需要加载解决方案所需的 JavaScript 库。 在SharePoint 框架这通常包括两个步骤:指定应从中加载库的 URL,并在代码中引用库。

  1. 指定应从哪个库中加载 URL。 在代码编辑器中,打开 ./config/config.json 文件,并将 externals 部分更改为:

    {
      // ..
      "externals": {
        "jquery": {
          "path": "https://code.jquery.com/jquery-1.12.4.min.js",
          "globalName": "jQuery"
        },
        "datatables.net": {
          "path": "https://cdn.datatables.net/1.10.15/js/jquery.dataTables.min.js",
          "globalName": "jQuery",
          "globalDependencies": [
            "jquery"
          ]
        },
        "moment": "https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.27.0/moment.min.js"
      },
      // ..
    }
    

    这有两个用途:

    1. 当 SharePoint 框架编译工具链创建 Web 部件的捆绑包时,它将忽略这三个程序包的任何 importrequire 语句,并且不会将其源包括在捆绑包中。 没有这些功能,Webpack (用于创建捆绑包的工具)将在生成的 SPFx 组件捆绑包中导入这些 JavaScript 库。
    2. SharePoint 框架编译工具链会把这三个包作为依赖项,添加在组件的清单中。 这会告知 SharePoint 框架的模块加载程序确保这些库已加载到页面上,然后再加载组件的捆绑包。

    注意

    有关在 SharePoint 框架项目中引用外部库的详细信息,请参阅 向 SharePoint 客户端 Web 部件添加外部库

  2. 打开 “./src/webparts/itRequests/ItRequestsWebPart.ts” 文件,并在最后一个 import 语句后面添加:

    import 'jquery';
    import 'datatables.net';
    import 'moment';
    

定义数据表

和在原始解决方案中一样,下一步是定义用于显示数据的表结构。

在代码编辑器中,打开 “./src/webparts/itRequests/ItRequestsWebPart.ts” 文件,并将 render() 方法更改为:

export default class ItRequestsWebPart extends BaseClientSideWebPart<IItRequestsWebPartProps> {
  public render(): void {
    this.domElement.innerHTML = `
      <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.15/css/jquery.dataTables.min.css" />
      <table id="requests" class="display ${styles.itRequests}" cellspacing="0" width="100%">
        <thead>
          <tr>
            <th>ID</th>
            <th>Business unit</th>
            <th>Category</th>
            <th>Status</th>
            <th>Due date</th>
            <th>Assigned to</th>
          </tr>
        </thead>
      </table>`;
  }
  // ...
}

为 DataTables 注册 Moment.js 插件

下一步是为 DataTables 定义 Moment.js 插件,以便能够设置表中日期的格式。

  1. 在“./src/webparts/itRequests”文件夹中,新建“moment-plugin.js”文件,并粘贴以下代码:

    // UMD
    (
      function (factory) {
        "use strict";
    
        if (typeof define === 'function' && define.amd) {
          // AMD
          define(['jquery'], function ($) {
            return factory($, window, document);
          });
        } else if (typeof exports === 'object') {
          // CommonJS
          module.exports = function (root, $) {
            if (!root) {
              root = window;
            }
    
            if (!$) {
              $ = typeof window !== 'undefined'
                ? require('jquery')
                : require('jquery')(root);
            }
    
            return factory($, root, root.document);
          };
        } else {
          // Browser
          factory(jQuery, window, document);
        }
      }
    
      (function ($, window, document) {
        $.fn.dataTable.render.moment = function (from, to, locale) {
          // Argument shifting
          if (arguments.length === 1) {
            locale = 'en';
            to = from;
            from = 'YYYY-MM-DD';
          } else if (arguments.length === 2) {
            locale = 'en';
          }
    
          return function (d, type, row) {
            var moment = require('moment');
            var m = moment(d, from, locale, true);
    
            // Order and type get a number value from Moment, everything else
            // sees the rendered value
            return m.format(type === 'sort' || type === 'type' ? 'x' : to);
          };
        };
      })
    );
    
  2. 对于要加载此插件的 Web 部件,它必须引用新建的 moment-plugin.js 文件。 在代码编辑器中,打开 “./src/webparts/itRequests/ItRequestsWebPart.ts” 文件,并在最后一个import 语句后面添加:

    import './moment-plugin';
    

启动 DataTables 并加载数据

最后一步是包含启动数据表并从 SharePoint 加载数据的代码。

  1. “./src/webparts/itRequests” 文件夹中,新建 “script.js” 文件,并粘贴以下代码:

    $(document).ready(function () {
      $('#requests').DataTable({
        'ajax': {
          'url': "../../_api/web/lists/getbytitle('IT Requests')/items?$select=ID,BusinessUnit,Category,Status,DueDate,AssignedTo/Title&$expand=AssignedTo/Title",
          'headers': { 'Accept': 'application/json;odata=nometadata' },
          'dataSrc': function (data) {
            return data.value.map(function (item) {
              return [
                item.ID,
                item.BusinessUnit,
                item.Category,
                item.Status,
                new Date(item.DueDate),
                item.AssignedTo.Title
              ];
            });
          }
        },
        columnDefs: [{
          targets: 4,
          render: $.fn.dataTable.render.moment('YYYY/MM/DD')
        }]
      });
    });
    

注意

确保要在 $select$expend 参数中使用列的内部名称(或静态名称)。

  1. 为了能够在 Web 部件中引用此文件,在代码编辑器中打开 “./src/webparts/itRequests/ItRequestsWebPart.ts” 文件,并将 require('./script'); 添加到 render() 方法的结尾中。 此时,render() 方法应如下所示:

    export default class ItRequestsWebPart extends BaseClientSideWebPart<IItRequestsWebPartProps> {
      public render(): void {
        this.domElement.innerHTML = `
          <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.15/css/jquery.dataTables.min.css" />
          <table id="requests" class="display ${styles.itRequests}" cellspacing="0" width="100%">
            <thead>
              <tr>
                <th>ID</th>
                <th>Business unit</th>
                <th>Category</th>
                <th>Status</th>
                <th>Due date</th>
                <th>Assigned to</th>
              </tr>
            </thead>
          </table>`;
    
        require('./script');
      }
      // ...
    }
    
  2. 在命令行中运行以下命令,验证 Web 部件是否按预期正常运行:

    gulp serve --nobrowser
    

由于 Web 部件是从 SharePoint 加载数据,因此必须使用托管的 SharePoint 框架 Workbench 测试 Web 部件。 导航到 https://{your-tenant-name}.sharepoint.com/_layouts/workbench.aspx,并将 Web 部件添加到画布。 此时,应该会看到借助 DataTables jQuery 插件显示的 IT 请求。

SharePoint 框架客户端 Web 部件中显示的 IT 请求

支持通过 Web 部件属性配置 Web 部件

在前面的步骤中,已将 IT 请求解决方案从脚本编辑器 Web 部件迁移到了 SharePoint 框架。 虽然解决方案已按预期正常运行,但未受益于 SharePoint 框架的任何优势。 从中加载 IT 请求的列表的名称包含在代码中,而代码本身是纯 JavaScript,与 TypeScript 相比,更难重构。

下面逐步介绍了如何扩展现有解决方案,以便用户能够指定从中加载数据的列表的名称。 稍后,将代码转换为 TypeScript,以便受益于它的类型安全性功能。

定义用于存储列表名称的 Web 部件属性

  1. 定义 Web 部件属性,用于存储应从中加载 IT 请求的列表的名称。 在代码编辑器中,打开 “./src/webparts/itRequests/ItRequestsWebPart.manifest.json” 文件,将默认的 description 属性 重命名为 listName,并清除它的值。

    Visual Studio Code 中突出显示的 Web 部件清单中的 listName 属性

  2. 将 Web 部件属性接口更新为反映清单中的更改。 在代码编辑器中,打开“./src/webparts/itRequests/IItRequestsWebPartProps.ts”文件,并将它的内容更改为:

    export interface IItRequestsWebPartProps {
      listName: string;
    }
    
  3. 更新 listName 属性的显示标签。 打开“./src/webparts/itRequests/loc/mystrings.d.ts”文件,并将它的内容更改为:

    declare interface IItRequestsStrings {
      PropertyPaneDescription: string;
      BasicGroupName: string;
      ListNameFieldLabel: string;
    }
    
    declare module 'itRequestsStrings' {
      const strings: IItRequestsStrings;
      export = strings;
    }
    
  4. 打开“./src/webparts/itRequests/loc/en-us.js”文件,并将它的内容更改为:

    define([], function() {
      return {
        "PropertyPaneDescription": "IT Requests settings",
        "BasicGroupName": "Data",
        "ListNameFieldLabel": "List name"
      }
    });
    
  5. 将 Web 部件更新为使用新定义的属性。 在代码编辑器中,打开 “./src/webparts/itRequests/ItRequestsWebPart.ts” 文件,并将 getPropertyPaneConfiguration() 方法更改为:

    export default class ItRequestsWebPart extends BaseClientSideWebPart<IItRequestsWebPartProps> {
    // ...
      protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
        return {
          pages: [{
            header: {
              description: strings.PropertyPaneDescription
            },
            groups: [{
              groupName: strings.BasicGroupName,
              groupFields: [
                PropertyPaneTextField('listName', {
                  label: strings.ListNameFieldLabel
                })
              ]
            }]
          }]
        };
      }
    
      protected get disableReactivePropertyChanges(): boolean {
        return true;
      }
    }
    

为了防止 Web 部件在用户键入列表名称时重新加载,还将 Web 部件配置为使用非反应属性窗格,具体操作是添加 disableReactivePropertyChanges() 方法,并将它的返回值设置为 true

使用已配置的列表名称从中加载数据

最初,应从中加载数据的列表的名称嵌入到了 REST 查询中。 既然用户现在可以配置此名称,应先将已配置的值注入 REST 查询,再加载数据。 这样做的最简单方法是,将 script.js 文件的内容移到主 Web 部件文件。

  1. 在代码编辑器中,打开 “./src/webparts/itRequests/ItRequestsWebPart.ts” 文件,并将 render() 方法更改为:

    var $: any = (window as any).$;
    
    export default class ItRequestsWebPart extends BaseClientSideWebPart<IItRequestsWebPartProps> {
      public render(): void {
        this.domElement.innerHTML = `
          <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.15/css/jquery.dataTables.min.css" />
          <table class="display ${styles.itRequests}" cellspacing="0" width="100%">
            <thead>
              <tr>
                <th>ID</th>
                <th>Business unit</th>
                <th>Category</th>
                <th>Status</th>
                <th>Due date</th>
                <th>Assigned to</th>
              </tr>
            </thead>
          </table>`;
    
        $(document).ready(() => {
          $('table', this.domElement).DataTable({
            'ajax': {
              'url': `../../_api/web/lists/getbytitle('${escape(this.properties.listName)}')/items?$select=ID,BusinessUnit,Category,Status,DueDate,AssignedTo/Title&$expand=AssignedTo/Title`,
              'headers': { 'Accept': 'application/json;odata=nometadata' },
              'dataSrc': function (data) {
                return data.value.map(function (item) {
                  return [
                    item.ID,
                    item.BusinessUnit,
                    item.Category,
                    item.Status,
                    new Date(item.DueDate),
                    item.AssignedTo.Title
                  ];
                });
              }
            },
            columnDefs: [{
              targets: 4,
              render: $.fn.dataTable.render.moment('YYYY/MM/DD')
            }]
          });
        });
      }
    
      // ...
    }
    
  2. 不是从 script.js 文件中引用代码,而是所有内容都属于 Web 部件的 render 方法的一部分。 在 REST 查询中,现在可以将固定的列表名称替换为 listName 属性值,其中包含用户配置的列表名称。 使用此值前,它要使用 lodash 的 escape 函数进行转义,以禁止脚本注入。

    此时,代码块仍是使用纯 JavaScript 编写。 为了避免 $ jQuery 变量的生成问题,必须在类定义之前将它定义为 any 类型。 稍后,将代码转换为 TypeScript 时,将它替换为正确的类型定义。

    由于刚刚将 script.js 文件的内容移到了主 Web 部件文件中,因此不再需要 script.js 文件,可以将它从项目中删除。

  3. 若要验证 Web 部件是否按预期正常运行,请在命令行中运行以下命令:

    gulp serve --nobrowser
    
  4. 转到托管的 Workbench,并将 Web 部件添加到画布中。 打开 Web 部件属性窗格,指定包含 IT 请求的列表的名称,再选择“应用”按钮以确认更改。

    此时,应该会看到 Web 部件中显示有 IT 请求。

    IT 请求从已配置的列表中加载,并显示在 SharePoint 框架客户端 Web 部件中

将纯 JavaScript 代码转换为 TypeScript

与纯 JavaScript 相比,使用 TypeScript 带来了许多好处。 TypeScript 不仅更易于维护和重构,还能够尽早捕获错误。 下面逐步介绍了如何将原始 JavaScript 代码转换为 TypeScript。

添加已使用库的类型定义

若要正常运行,TypeScript 需要项目中使用的不同库的类型定义。 类型定义通常作为 npm 包在 命名空间中 @types 分布。

  1. 在命令行中运行以下命令,安装 jQuery 和 DataTables 的类型定义:

    npm install @types/jquery@1.10.34 @types/datatables.net@1.10.15 --save-dev --save-exact
    

    提示

    在此示例中,我们将指定要安装的 NPM 程序包的确切版本。 这将确保 NPM 安装的类型声明包与我们在项目中使用的 jQuery 和数据表库的版本相匹配。

    --save-dev 参数会告诉 NPM 将这两个包的引用保存在 package.json 文件中的 devDependencies 集合中。 仅在开发中需要 TypeScript 声明,这就是我们不要它们在 dependencies 集合中的原因。

    --save-exact 参数会告诉 NPM 在 package.json 文件中添加对特定版本的引用,而不会添加表示法来启用升级到较新版本的自动升级。

    Moment.js 的类型定义与 Moment.js 包一起分发。 尽管要通过 URL 加载 Moment.js 来使用它的类型定义,但仍需要在项目中安装 Moment.js 包。

  2. 在命令行中运行以下命令,安装 Moment.js 包:

    npm install moment@2.27.0 --save-exact
    

更新包引用

若要使用已安装类型定义中的类型,必须更改库的引用方式。

  1. 在代码编辑器中,打开 “./src/webparts/itRequests/ItRequestsWebPart.ts”文件,并将“import 'jquery';”语句更改为:

    import * as $ from 'jquery';
    
  2. $ 定义为 jQuery 之后,现在可以删除之前添加的 $ 局部定义:

    var $: any = (window as any).$;
    

将主 Web 部件文件更新为 TypeScript

至此,已添加项目中安装的所有库的类型定义,可以开始将纯 JavaScript 代码转换为 TypeScript 了。

  1. 定义一个接口,用于从 SharePoint 列表检索到的 IT 请求信息。 在代码编辑器中,打开“./src/webparts/itRequests/ItRequestsWebPart.ts”文件,并在 Web 部件类的正上方,添加以下代码片段:

    interface IRequestItem {
      ID: number;
      BusinessUnit: string;
      Category: string;
      Status: string;
      DueDate: string;
      AssignedTo: { Title: string; };
    }
    
  2. 接下来,在 Web 部件类中,将 render() 方法更改为:

    export default class ItRequestsWebPart extends BaseClientSideWebPart<IItRequestsWebPartProps> {
      public render(): void {
        this.domElement.innerHTML = `
          <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.15/css/jquery.dataTables.min.css" />
          <table class="display ${styles.itRequests}" cellspacing="0" width="100%">
            <thead>
              <tr>
                <th>ID</th>
                <th>Business unit</th>
                <th>Category</th>
                <th>Status</th>
                <th>Due date</th>
                <th>Assigned to</th>
              </tr>
            </thead>
          </table>`;
    
          $('table', this.domElement).DataTable({
            'ajax': {
              'url': `../../_api/web/lists/getbytitle('${escape(this.properties.listName)}')/items?$select=ID,BusinessUnit,Category,Status,DueDate,AssignedTo/Title&$expand=AssignedTo/Title`,
              'headers': { 'Accept': 'application/json;odata=nometadata' },
              'dataSrc': (data: { value: IRequestItem[] }): any[][] => {
                return data.value.map((item: IRequestItem): any[] => {
                  return [
                    item.ID,
                    item.BusinessUnit,
                    item.Category,
                    item.Status,
                    new Date(item.DueDate),
                    item.AssignedTo.Title
                  ];
                });
              }
            },
            columnDefs: [{
              targets: 4,
              render: ($.fn.dataTable.render as any).moment('YYYY/MM/DD')
            }]
          });
      }
    
      // ...
    }
    
  3. 请注意,用于从 SharePoint 列表检索数据的 AJAX 请求现已类型化,有助于确保将属性传递到 DataTables 数组时,引用的是正确属性。 DataTables 用来表示表行的数据结构为混合类型的数组。所以,为了简单起见,将它定义为了 any[] 在此上下文中使用 any 类型也不错,因为 dataSrc 属性内部返回的数据供 DataTables 在内部使用。

    由于要更新 render() 方法,因此还新增了另外两处更改。 首先,从表中删除了 id 属性。 这样一来,页面上可以有相同 Web 部件的多个实例。 此外,还删除了对 $(document).ready() 函数的引用,此引用不是必需的,因为在 DataTables 启动代码前面设置了元素的 DOM,数据表就是在其中呈现。

将 Moment.js DataTables 插件更新为 TypeScript

解决方案中需要转换为 TypeScript 的最后一部分是 Moment.js DataTables 插件。

  1. “./src/webparts/itRequests/moment-plugin.js” 文件重命名为 “./src/webparts/itRequests/moment-plugin.ts”,让它可供 TypeScript 编译器处理。

  2. 在代码编辑器中,打开“moment-plugin.ts”文件,并将它的内容替换为:

    import * as $ from 'jquery';
    import * as moment from 'moment';
    
    /* tslint:disable:no-function-expression */
    ($.fn.dataTable.render as any).moment = function (from: string, to: string, locale: string): (d: any, type: string, row: any) => string {
    /* tslint:enable */
      // Argument shifting
      if (arguments.length === 1) {
        locale = 'en';
        to = from;
        from = 'YYYY-MM-DD';
      } else if (arguments.length === 2) {
        locale = 'en';
      }
    
      return (d: any, type: string, row: any): string => {
        let m: moment.Moment = moment(d, from, locale, true);
    
        // Order and type get a number value from Moment, everything else
        // sees the rendered value
        return m.format(type === 'sort' || type === 'type' ? 'x' : to);
      };
    };
    
  3. 首先,加载对 jQuery 和 Moment.js 的引用,让 TypeScript 知道相应变量引用的是什么。 接下来,定义插件函数。 在 TypeScript 中,通常对函数使用箭头表示法 (=>)。 不过,在此示例中,因为需要访问 arguments 属性,所以必须使用常规函数定义。 为了防止 TSLint 警告未使用箭头表示法,可以对函数定义显式禁用 no-function-expression 规则。

  4. 若要确认一切是否按预期正常运行,请在命令行中运行以下命令:

    gulp serve --nobrowser
    
  5. 转到托管的 Workbench,并将 Web 部件添加到画布中。 尽管看上去没有变化,但新基准代码使用的是 TypeScript 及其类型定义,以帮助用户维护解决方案。