Внедрение шаблона D3 "многоразовая диаграмма" в TypeScript

Код в разделе 2 ниже (рабочий пример здесь) основан на коде в разделе 1, но изменен на использование функций стрелок, и он основан на шаблоне Майка Бостока в " На пути к восстанавливаемым диаграммам", а именно, возвращает функцию, которая имеет другие функции на ней.

Если я попытаюсь запустить либо код в разделе 1 или 2 в машинописном тексте (здесь здесь), он говорит, что методы addToChart и stop не существуют в типе (selection: any) =>() => void.

Как я могу получить машинописный текст для распознавания свойств функций (addToChart и stop в этом случае), добавленных к возвращенной функции?

секция 1

const mychart = function (){
  let stop = false;
  const chart = function(selection){
    function tick(){
      console.log("tick");
    }
    return tick;
  };

  // Adding a function to the returned 
  // function as in Bostock reusable chart pattern
  chart.addToChart = function(value){ 
    console.log("addToChart");
    return chart;
  };

  chart.stop = function(){
    return stop = true;
  }

  return chart;
}

const a = mychart();
const tick = a();
tick(); //logs tick
a.addToChart(); //logs "addToChart"

раздел 2

const mychart = () => {
  let stop = false;

  const chart = (selection) => {
    function tick(){
      console.log("tick");
    }
    return tick;
  };

  chart.addToChart = (value) => {
    console.log("addToChart");
    return chart;
  };

  chart.stop = () => {
    return stop = true;
  }

  return chart;
} 

const a = mychart();
const tick = a();
tick(); //logs tick
a.addToChart(); //logs "addToChart"

Ответ 1

Вы можете определить гибридный тип, то есть интерфейс, описывающий как сигнатуру функции, так и ее свойства. Учитывая ваш код, это может быть примерно так:

interface IChart {
    (selection: any): any;
    // Use overloading for D3 getter/setter pattern
    addToChart(): string;               // Getter
    addToChart(value: string): IChart;  // Setter
}

Так как вам следует избегать any чумы, это может потребовать некоторой дополнительной доработки, но этого должно быть достаточно, чтобы вы начали. Кроме того, чтобы получить шаблон D3-ish getter/setter, вы можете перегрузить функцию addToChart в объявлении интерфейса.

Интеграция этого интерфейса как типа в шаблоне повторного использования кода становится довольно простой:

const mychart = (): IChart => {

  // Private value exposed via closure
  let value: string|undefined;

  const chart = <IChart>((selection) => {
    // Private logic
  });

  // Public interface
  // Implementing a  D3-style getter/setter.
  chart.addToChart = function(val?: string): any {
    return arguments.length ? (value = val, chart) : value;
  };

  return chart;
} 

const chart = mychart();

console.log(chart.addToChart())   // --> undefined       
chart.addToChart("Add");          // Sets private value to "Add".
console.log(chart.addToChart())   // --> "Add"       

Взгляните на { //Private value exposed via closure let value: string|undefined; const chart = ((selection) => { //Private logic }); //Public interface //Implementing a D3-style getter/setter. chart.addToChart = function(val?: string): any { return arguments.length? (value = val, chart): value; }; return chart; } const chart = mychart(); console.log(chart.addToChart()) //%20--> undefined chart.addToChart("Add"); //Sets private value to "Add". console.log(chart.addToChart()) //%20--> "Add" rel=noreferrer>демонстрационную версию исполняемой { //Private value exposed via closure let value: string|undefined; const chart = ((selection) => { //Private logic }); //Public interface //Implementing a D3-style getter/setter. chart.addToChart = function(val?: string): any { return arguments.length? (value = val, chart): value; }; return chart; } const chart = mychart(); console.log(chart.addToChart()) //%20--> undefined chart.addToChart("Add"); //Sets private value to "Add". console.log(chart.addToChart()) //%20--> "Add" rel=noreferrer>игровой площадки.

Ответ 2

Мне было интересно, можете ли вы использовать интерфейс/класс:

interface IChart {
    constructor: Function;
    addToChart?: (number) => Chart;
    stop: () => boolean;
}

class Chart implements IChart {

    private _stop = false;
    constructor( selection ) {
        // content of tick funciton here
    }

    public addToChart = function (n: number) {
        return this;
    }
    public stop = function () {
        return this._stop = true;
    }

}

let mychart = function () {
    let stop = false;
    let chartNew: Chart = new Chart(1);
    return chartNew;
}; 

Ответ 3

Вы можете использовать Object.assign для создания гибридного типа (функция, которая имеет дополнительные свойства), без необходимости определять выделенный интерфейс. Вы можете определить функции внутри оригинала отдельно, так что вы можете иметь несколько подписей для каждой функции, и вы даже можете ввести this параметр, если хотите получить доступ к объекту через this вместо chart

let mychart = function () {
    let isStopped = false;
    let value = "";


    type Chart = typeof chart;
    // Complex method with multiple signatures
    function addToChart(): string 
    function addToChart(newValue: string): Chart
    function addToChart(newValue?: string): string | Chart {
        if(newValue != undefined){
            value = newValue;
            chart.stop()
            return chart;
        }else{
            return value;
        }
    }
    // We can specify the type for this if we want to use this
    function stop(this: Chart) {
        isStopped = true;
        return this; // instead of chart, either is usable
    }
    var methods = {
        addToChart,
        stop,

        // inline function, we can return chart, but if we reference the Chart type explicitly the compiler explodes 
        stop2() {
            isStopped = true;
            return chart;
        }
    };
    let chart = Object.assign(function (selection) {
        function tick() {

        }
        return tick;
    }, methods);
    return chart;
}; 
let d = mychart();

d("");
d.addToChart("").addToChart();
d.addToChart();
d.stop();
d.stop().addToChart("").stop2().stop()

Заметки

  1. Хотя intelisense работает так, как ожидалось, если вы наведите курсор мыши на d и посмотрите на тип, он значительно уродливее, чем ручная версия.

  2. Я определил methods отдельно и не Object.assign потому что компилятор запутался, если я это сделаю.

  3. Если вы не хотите использовать this внутри методов, вам не нужно вводить this явно. Я показал, как использовать его, просто для полноты картины, используя график может быть проще и это гарантирует, что мы не имеем дело с кем - то проходя неправильно this.

  4. Хотя приведенный выше пример работает, есть определенные случаи, когда компилятор отказывается от вывода и mychart возврат mychart как любой. Один из таких случаев - это когда мы ссылаемся на Chart внутри функции, определенной в объекте, назначенном methods