Búsqueda de sitios web

¿Cómo agrego definiciones de Typescript para los complementos de Leaflet?


Me gustaría usar el complemento easy-buttons con TypeScript https://github.com/CliffCloud/Leaflet.EasyButton/blob/master/src/easy-button.js pero no viene con anotaciones de TypeScript.

Respuesta #1

Paso 1 - resaltar los errores

El primer paso es usar el código de ejemplo tal como está, sin anotaciones de Typescript, y los errores comenzarán a aparecer en 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);

Para lo cual creamos un archivo llamado 'easy-button.d.ts' y lo referimos en nuestro archivo de Typescript.

// 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);

Y no hay nada en easy-button.d.ts

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

El error dice

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

Lo cual es bastante justo porque aún no hemos definido estos.

Si se refiere a la definición de easyBar y easyButton aquí y aquí, encontrará que hay un poco de magia ocurriendo en las declaraciones originales de Javascript. Parece que estas dos funciones no toman ningún argumento, pero en realidad sí lo hacen.

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));
};

Esta función va a llamar a new en la clase L.Control.EasyButton. Los parámetros son algo crípticos, pero puedes inferirlos de esta línea que indica:

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

Paso 2 - agregar los tipos

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

y ahora estamos un poco más cerca:

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

y eso es bastante obvio porque proporcionamos 2 parámetros 'fa-edit' y un callback a easyButton pero no declaramos ninguno en nuestros argumentos. Nuestro segundo intento ahora se ve así:

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

y ahora todas las advertencias de Typescript han desaparecido. Pero hay más que se puede hacer. Por un lado, easyButton en realidad toma 4 argumentos. Eso es fácil de arreglar: observa cómo los argumentos opcionales tienen un sufijo ?:

// 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);
}

Paso 3 - proporcionar valores de retorno

El método easyButton en realidad devuelve una instancia de L.Control.EasyButton. Actualmente, la definición de Typescript implica que easyButton devuelve un tipo any. ¡No queremos eso! Typescript solo es útil cuando proporcionamos tipos.

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 vuelve a proporcionar advertencias útiles:

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

Esto se debe a que EasyBar es una subclase de L.Control, necesitamos traer esa definición a nuestro archivo de definiciones.

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 { }
    }
}

Paso 4 - proporcionar argumentos de constructor a EasyButton y EasyBar

Si intentas instanciar un nuevo EasyButton, la finalización de código sugiere que deberías pasar un objeto L.ControlOptions para configurarlo. En realidad, necesitamos definir nuestras propias opciones.

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)
        }
    }
}

Las cosas se ven mejor ahora en la finalización de código:

Sin embargo, hice trampa con la opción states. La declaré como any. En realidad, debería ser

    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
    }

Paso 5 - agregar sugerencias de jsdoc

Typescript proporcionará comentarios útiles a los usuarios de este complemento. Aquí hay un ejemplo de cómo podríamos proporcionar documentación para 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;

Paso 6 Hacer modificaciones para aumentar las definiciones de tipos de folletos

(A partir de Leaflet 1.2.0) Elimine la declaración de espacio de nombres:

declare namespace L {

y reemplazarlo con una ampliación de módulo:

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

tu código de prueba ahora debería verse así:

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

Paso 7 integrar en las fuentes originales del proyecto

Abre node_modules\leaflet-easybutton\package.json y añade la siguiente línea debajo de la entrada style:

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

Mueve nuestro easy-button.d.ts a node_modules/leaflet-easybutton/src, y prueba que todo siga funcionando.

¡Luego envía una solicitud de extracción para que todos puedan beneficiarse del trabajo!

Respuesta #2

En realidad, lo hace ahora. Solo deberías

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

Artículos relacionados