Hur lägger jag till Typescript-definitioner för Leaflet-plugins


Jag skulle vilja använda easy-buttons-plugin med Typescript https://github.com/CliffCloud/Leaflet.EasyButton/blob/master/src/easy-button.js men det levereras inte med Typescript-anteckningar.

Svar #1

Steg 1 - lys upp felen

Det första steget är att använda exempel koden som den är utan TypeScript-anteckningar, och felen kommer att börja dyka upp i VS Code.

// sample.ts
L.easyBar([
  L.easyButton('fa-file', function(btn, map){ }),
  L.easyButton('fa-save', function(btn, map){ }),
  L.easyButton('fa-edit', function(btn, map){ }),
  L.easyButton('fa-dot-circle-o', function(btn, map){ })
]).addTo(map);

Till vilket vi skapar en fil kallad 'easy-button.d.ts' och hänvisar till den i vår Typescript-fil.

// sample.ts
import "./easy-button"
L.easyBar([
  L.easyButton('fa-file', function(btn, map){ }),
  L.easyButton('fa-save', function(btn, map){ }),
  L.easyButton('fa-edit', function(btn, map){ }),
  L.easyButton('fa-dot-circle-o', function(btn, map){ })
]).addTo(map);

Och det finns inget i easy-button.d.ts

// easy-button.d.ts
// empty for now

Felet säger

error TS2339: Property 'easyBar' does not exist on type 'typeof L'.
error TS2339: Property 'easyButton' does not exist on type 'typeof L'.

Vilket är rättvist eftersom vi inte har definierat dessa än.

Om du hänvisar till definitionen av easyBar och easyButton här och här, kommer du att se att det sker lite magi i de ursprungliga Javascript-deklarationerna. Det verkar som att dessa två funktioner inte tar några argument, men i verkligheten gör de det.

L.easyButton = function(/* args will pass automatically */){
  var args = Array.prototype.concat.apply([L.Control.EasyButton],arguments);
  return new (Function.prototype.bind.apply(L.Control.EasyButton, args));
};

Denna funktion kommer att anropa new på L.Control.EasyButton-klassen. Parametrarna är något kryptiska men du kan härleda dem från denna rad som ger:

initialize: function(icon, onClick, title, id)

Steg 2 - lägg till typerna

// easy-button.d.ts
declare namespace L {
    function easyBar();
    function easyButton();
}

och nu är vi lite närmare:

error TS2346: Supplied parameters do not match any signature of call target

och det är ganska uppenbart eftersom vi levererade 2 parametrar 'fa-edit' och en callback till easyButton men vi deklarerade inga i våra argument. Vårt andra försök ser nu ut så här:

// easy-button.d.ts
declare namespace L {
    function easyBar(buttons: any[]);
    function easyButton(icon: string, onClick: (btn: any, map: any)=>void);
}

och nu har alla Typescript-varningar försvunnit. Men det finns mer som kan göras. För det första tar easyButton faktiskt 4 argument. Det är lätt att åtgärda - notera hur valfria argument har suffixet ?:

// easy-button.d.ts
declare namespace L {
    function easyBar(buttons: any[]);
    function easyButton(icon: string, onClick: (btn: any, map: any)=>void, title?: string, id?: string);
}

Steg 3 - ange returvärden

Metoden easyButton returnerar faktiskt en instans av L.Control.EasyButton. För närvarande antyder Typescript-definitionen att easyButton returnerar typen any. Vi vill inte ha det! Typescript är bara användbart när vi tillhandahåller typer.

declare namespace L {
    function easyBar(buttons: Control.EasyButton[]): Control.EasyBar;
    function easyButton(icon: string, onClick: (btn: any, map: any)=>void, title?: string, id?: string) : Control.EasyButton;

    namespace Control {
        class EasyButton { };
        class EasyBar { };
    }
}

Typescript börjar ge användbara varningar igen:

error TS2339: Property 'addTo' does not exist on type 'EasyBar'.

Detta är eftersom EasyBar är en underklass av L.Control att vi behöver ta in den definitionen i vår definitionsfil.

declare namespace L {
    function easyBar(buttons: Control.EasyButton[]): Control.EasyBar;
    function easyButton(icon: string, onClick: (btn: any, map: any)=>void, title?: string, id?: string) : Control.EasyButton;

    namespace Control {
        class EasyButton extends L.Control { }
        class EasyBar extends L.Control { }
    }
}

Steg 4 - ange konstruktörsargument till EasyButton och EasyBar

Om du försöker skapa en ny EasyButton föreslår kodkomplettering att du bör skicka in ett L.ControlOptions-objekt för att konfigurera detta. Faktiskt behöver vi definiera våra egna alternativ.

declare namespace L {
    function easyBar(buttons: Control.EasyButton[], options?: EasyBarOptions): Control.EasyBar;
    function easyButton(icon: string, onClick: (btn: any, map: any)=>void, title?: string, id?: string) : Control.EasyButton;

    interface EasyBarOptions {
        position?: ControlPosition
        id?: string
        leafletClasses?: boolean
    }

    interface EasyButtonOptions {
        position?: ControlPosition
        id?: string
        type?: 'replace'|'animate'
        states?: any
        leafletClasses?: boolean
        tagName?: string
    }

    namespace Control {
        class EasyButton extends L.Control {
            constructor(options?: EasyButtonOptions)
        }
        class EasyBar extends L.Control {
            constructor(options?: EasyBarOptions)
        }
    }
}

Saker ser bättre ut nu vid kodkomplettering:

Men jag fuskade med states-alternativet. Jag deklarerade det som any. I verkligheten borde det vara

    interface EasyButtonOptions {
        position?: ControlPosition
        id?: string
        type?: 'replace'|'animate'
        states?: EasyButtonState[]
        leafletClasses?: boolean
        tagName?: string
    }

    interface EasyButtonState {
        stateName: string
        onClick: () => void
        title: string
        icon: string
    }

Steg 5 - lägg till jsdoc-ledtrådar

Typescript kommer att ge användbara kommentarer till användare av detta plugin. Här är ett exempel på hur vi kan tillhandahålla dokumentation för easyButton

   /**
     * Creates a easyButton
     * @param icon e.g. fa-globe
     * @param onClick the button click handler
     * @param label on the button
     * @param an id to tag the button with
     * @example
     * var helloPopup = L.popup().setContent('Hello World!');
     *
     * L.easyButton('fa-globe', function(btn, map){
     *      helloPopup.setLatLng(map.getCenter()).openOn(map);
     *  }).addTo( YOUR_LEAFLET_MAP );
     */
    function easyButton(
        icon: string,
        onClick: (btn: Control.EasyButton, map: L.Map) => void,
        title?: string,
        id?: string): Control.EasyButton;

Steg 6 Gör ändringar för att utöka definitioner av broschyrtyper

(Från och med Leaflet 1.2.0) Ta bort namnrymdeförklaringen:

declare namespace L {

och ersätt det med modulförstärkning:

import * as L from 'leaflet'
declare module 'leaflet' {

din testkod bör nu se ut så här:

import * as L from 'leaflet'
import 'easy-button'

Steg 7 integrera i de ursprungliga projektkällorna

Öppna node_modules\leaflet-easybutton\package.json och lägg till följande rad under style-posten:

  "main": "src/easy-button.js",
  "style": "src/easy-button.css",
  "typings": "src/easy-button.d.ts",

Flytta vår easy-button.d.ts till node_modules/leaflet-easybutton/src, och testa att allt fortfarande fungerar.

Skicka sedan in en pull-begäran så att alla kan dra nytta av arbetet!

Svar #2

Det gör det faktiskt nu. Du borde bara

import * as L from 'leaflet';
import 'leaflet-easybutton/src/easy-button';

Relaterade artiklar