How do I add Typescript definitions for Leaflet plugins

匿名 (未验证) 提交于 2019-12-03 09:02:45

问题:

I would like to use easy-buttons plugin with Typescript https://github.com/CliffCloud/Leaflet.EasyButton/blob/master/src/easy-button.js and but it doesn't come with Typescript annotations.

回答1:

Step 1 - light up the errors

The first step is to use the sample code as-is without Typescript annotations, and the errors will start lighting up in 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); 

To which we create a file called 'easy-button.d.ts' and refer to it in our Typescript file.

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

And there's nothing in easy-button.d.ts

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

The error says

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

Which is fair enough because we haven't defined these yet.

If you refer to definition of easyBar and easyButton here and here, you will find there's a bit of magic occuring in the original Javascript declarations. It appears that these two functions don't take any arguments, but in reality they do.

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

This function is going to call new on the L.Control.EasyButton class. The parameters are somewhat cryptic but you can infer them from this line that gives:

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

Step 2 - add the typings

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

and now we are a bit closer:

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

and that's quite obvious because we supplied 2 parameters 'fa-edit' and a callback to easyButton but we didn't declare any in our arguments. Our second attempt now looks like this:

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

and now all the Typescript warnings have gone away. But there's more that can be done. For one, easyButton actually takes 4 arguments. That's easy to fix - observe how optional arguments have a ? suffix:

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

Step 3 - provide return values

The easyButton method actually returns an L.Control.EasyButton instance. Currently, the Typescript definition implies the easyButton returns type any. We don't want that! Typescript is helpful only when we provide typings.

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 starts providing useful warnings again:

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

This is because EasyBar subclasses L.Control we need to bring that definition into our definition file.

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

Step 4 - provide constructor arguments to EasyButton and EasyBar

If you try to instantiate a new EasyButton, code completion suggests that you should pass in a L.ControlOptions object to configure this. Actually we need to define our own options.

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

Things look better now on code completion:

However, I cheated on the states option. I declared that as any. In actuality it ought to be

    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     } 

Step 5 - add jsdoc hints

Typescript will provide helpful comments to users of this plugin. Here's an example of how we might provide documentation for 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; 

Step 6 Make modifications to augment leaflet types definitions

(Starting with Leaflet 1.2.0) Remove the namespace declaration:

declare namespace L { 

and replace it with module augmentation:

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

your test code should now read like this:

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

Step 7 integrate into the original project sources

Open up node_modules\leaflet-easybutton\package.json and add the following line below the style entry:

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

Move our easy-button.d.ts into node_modules/leaflet-easybutton/src, and test that everything still works.

Then submit a pull request so that every one can benefit from the work!



回答2:

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

Your new L.EasyButton takes a 'EasyButtonOptions' object for the constructor. This does not match the 'L.easyButton' examples

A L.easyButton has the following options: function easyButton(icon: string, onClick: (btn: any, map: any)=>void, title?: string, id?: string) : Control.EasyButton;

An EasyButtonOptions doesn't have a 'icon', 'onClick', 'title' or 'id' instead it takes:

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

So therefore, what is the equivalent of new L.EasyButton() that matches L.easyButton('fa fa-icon', () => '..', 'title) ?



易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!