iT邦幫忙

2022 iThome 鐵人賽

DAY 21
0
Modern Web

如何用TypeScript水30天鐵人賽系列 第 21

[Day21]:自己做說明書 - 宣告檔案 part 2

  • 分享至 

  • xImage
  •  

Day21 Banner

自己做輪子

任何傻瓜都可以寫出電腦看得懂的程式,
但好的程式設計師會寫出人也看得懂的。
───────────────────────── By Kent Beck


目標:檔案宣告

昨天學會了怎麼全域宣告然後做成宣告檔案
但當今天你做的說明書想要讓別人使用時,
還是要遵守一些規則,讓其他人能順利使用,
今天來講講這些規範。


過程

由於是透過 import 匯入的模組,
所以宣告檔案存放的位置也有一定的規則,
一般有兩種方案:

  • 建立一個 node_modules/@types/foo/index.d.ts 檔案,存放 foo 模組的宣告檔案。
    這種方式不需要額外的設定,但是 node_modules 不穩定,程式碼也沒有被儲存到儲存庫中,
    無法回溯,有不小心被刪除的風險,一般只用作臨時測試。
  • 建立一個 types 目錄,專門用來管理自己寫的宣告檔案,將 foo 的宣告檔案放到 types/foo.d.ts 中。
    這種方式需要設定 tsconfig.json 裡,
    pathsbaseUrlfilesincludeexclude 等欄位。看需求:
 /project
 ├── ts
 |  ├── index.ts
 |  └── types
 |       └── foo.d.ts
 └── tsconfig.json

如上方的目錄結構,就會調整成這樣:

 {
 //... 省略一堆
   "include": [ // 指定要編譯的目錄
      "ts",
      "ts/types/*"
   ],
 //... 省略一堆
 }
  • npm 套件匯入、匯出

    npm 套件的宣告檔案與全域變數的宣告檔案有很大區別。
    在 npm 套件的宣告檔案中,使用declare不再會宣告一個全域變數,
    而只會在當前檔案中宣告一個區域性變數。
    只有在宣告檔案中使用 export 匯出,然後在使用import匯入後,
    才會應用到這些型別宣告。
    ※ 宣告檔案中無法實作功能,只能定義型別。


  • 1. export

    export的語法與普通的TS中的語法類似。

     // types/member/index.d.ts
     export const title: string;
     export function getTitle(): string;
     export class summary {
       constructor(title: string);
       sayHello(): string;
     }
     export enum team { Opshell, Bear, Patty }
     export interface Options {
       data: any;
     }
    

    而使用上:

     // ts/index.ts
     import { title, getTitle, summary, team, Options } from 'member';
    
     console.log(title);
     let myName = getTitle();
     let Opshell = new summary('Opshell');
     let team = [ team.Opshell, team.Bear, team.Patty ];
     let options: Options = {
       data: {
          age: 30
       }
     };
    

    我們也可以使用declare先宣告多個變數,最後再用export一次性匯出。
    namespace等方式同理,上例的宣告檔案可以等價的改為:

     // types/foo/index.d.ts
     export { name, getName, Animal, Directions, Options };
    
     declare const title: string;
     declare function getTitle(): string;
     declare class summary {
       constructor(title: string);
       sayHello(): string;
     }
     declare enum Team { Opshell, Bear, Patty }
     interface Options {
       data: any;
     }
    

    ES6 module模組系統中,使用export default可以匯出一個預設值,
    使用方可以用import foo from 'foo'而不是import { foo } from 'foo'來匯入這個預設值。

     export default function foo(): string;
    

    ※ 注意,只有functionclassinterface可以直接預設匯出,
    其他的變數需要先用declare定義出來,再預設匯出。
    ※ 針對先宣告再預設匯出,我們一般會將匯出語句放在整個宣告檔案的最前面。


  • 2. export =

    commonjs規範中,我們用以下方式來匯出一個模組:

     // 整體匯出
     module.exports = foo;
     // 單個匯出
     exports.bar = bar;
    

    在TS中,針對commonjs規範的模組匯出,有多種方式可以匯入:

    • 2-1 const ... = require
     // 整體匯入
     const foo = require('foo');
     // 單個匯入
     const bar = require('foo').bar;
    
    • 2-2 import ... from

    ※ 整體匯出,需要使用 import * as 來匯入:

     // 整體匯入
     import * as foo from 'foo';
     // 單個匯入
     import { bar } from 'foo';
    
    • 2-3 import ... require,這也是TS官方推薦的方式:
     // 整體匯入
     import foo = require('foo');
     // 單個匯入
     import bar = foo.bar;
    

    對於使用commonjs規範的套件,假如要為它寫宣告檔案的話,就需要使用到 export = 這種語法了

     // types/foo/index.d.ts
     export = foo;
    
     declare function foo(): string;
     declare namespace foo {
       const bar: number;
     }
    

    ※ 上例中使用了 export = 之後,就不能再單個匯出 export { bar } 了。
    所以我們透過宣告合併特性,使用 declare namespace 來將 bar 合併到 foo 裡。
    export = 不僅可以用在宣告檔案中,也可以用在普通的TS檔案中。
    實際上,import ... requireexport = 都是TS為了相容 AMD規範commonjs規範而創立的新語法,
    但不常、也不推薦使用,所以就不詳細介紹了,感興趣的可以看官方文件。
    但是很多第三方套件是commonjs規範,所以宣告檔案也就不得不用到 export = 這種語法了。
    ※ 題外話Ops更偏好 ES6 module標準的 export defaultexport


小結

今天講了皮毛的importexport
大概只學到了稍微看得懂大神在做什模的程度
但是TS的型別檔案,水可是很深的,
各位可以參考延伸閱讀,講的簡單明瞭又詳細。


延伸閱讀


上一篇
[Day20]:別人的輪子用起來 - 宣告檔案 Part 1
下一篇
[Day22]:Vite 環境最...咦裝好了 part1
系列文
如何用TypeScript水30天鐵人賽33
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言