【问题标题】:Implementing the D3 "reusable chart" pattern in TypeScript在 TypeScript 中实现 D3“可重用图表”模式
【发布时间】:2017-12-03 19:41:44
【问题描述】:

下面第2节中的代码(工作示例here)基于第1节中的代码,但改为使用箭头函数,它基于Toward Resusable Charts中的Mike Bostock模式,即返回一个包含其他函数的函数。

如果我尝试在 typescript(演示 here)中运行第 1 节或第 2 节中的代码,它会说方法 addToChartstop 在类型 (selection: any) => () => void 上不存在。

如何让 typescript 识别添加到返回函数的函数属性(在本例中为 addToChartstop)?

第 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's 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"

【问题讨论】:

  • 对于任何从我的关于赏金的推文中获得的人,我很确定问题中的普通函数与箭头函数的区别有点牵强。无论词法范围如何,TypeScript 都难以处理这种代码风格。更大的问题是如何在 TS 中注释“带有 getter 和 setter 的闭包”风格的代码。

标签: javascript typescript d3.js


【解决方案1】:

您可以定义hybrid type,即描述函数签名及其属性的接口。鉴于您的代码,它可能是这样的:

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 模式,您可以在接口声明中 overload 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"       

看看可执行文件playground demo

【讨论】:

  • 这非常好,可能是我见过的最接近这项工作的。但是,重载接口会导致一切中断 — 尝试将以下内容添加到接口,这将是 addToChart 的 getter 版本:addToChart(): string;
  • 如果将两个 addToChart 方法组合成类似yScale(scale?: any): this | string; 的东西,它可以工作,但这似乎扭曲了声明的含义——如果定义了scale,则返回将是this;如果未定义,则返回string。没有任何情况下不提供任何参数会导致它返回 this
  • 你打败了我。我刚刚在我的回答中准确地包含了这一点......但是,正如你已经提到的,我对此并不完全满意。
  • @aendrew 看看编辑,为了清楚起见,它引入了addToChart 函数的重载。
  • 在尝试复制此内容时,我认真地挠了十分钟,这是非常明智的 — 值得注意的是 const chart = &lt;IChart&gt;((selection) =&gt; {}); 不等于 const chart = &lt;IChart&gt;(selection) =&gt; {};!无论如何,这是一个很好的答案,真的帮助我弄清楚这一切发生了什么。第一次有机会我会奖励赏金。
【解决方案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;
}; 

【讨论】:

  • 我会尝试并回复您。我已经给了你一个赞成票,一旦我在我的代码中测试它就会接受。
  • 您的游乐场示例链接到我在我的 OP 中发布的示例。
  • 抱歉,缓冲区大小有问题,但您可以复制并粘贴我发布的代码作为对操场的回答
  • 我不能接受这个答案,因为它改变了原始代码太多。我希望在原始代码中添加一个类型,而不是更改原始代码以适应类型。
  • 值得注意的是,D3 社区中似乎很少有人使用 ES6 类。必须完全重新实现模式以使用 ES6 类不是答案。
【解决方案3】:

您可以使用Object.assign 创建混合类型(具有额外属性的函数),而无需定义专用接口。您可以在原始内部单独定义函数,这样每个函数可以有多个签名,如果您想通过this而不是chart访问对象,您甚至可以输入this参数。

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. 虽然智能感知按预期工作,但如果将鼠标悬停在 d 上并查看类型,它比手工制作的版本要丑得多。

  2. 我单独定义了methods,而不是在Object.assign 上内联,因为如果我这样做编译器会感到困惑。

  3. 如果您不想在方法中使用this,则无需显式键入this。我展示了如何使用它,只是为了完整起见,使用图表可能更容易,它确保我们不必处理传入错误this 的人。

  4. 虽然上面的示例有效,但在某些情况下,编译器会放弃推理并将mychart 的返回值键入为any。一种这样的情况是当我们在分配给methods的对象中定义的函数内引用Chart

【讨论】:

  • 这还不错,但我觉得@altocumulus 的回答更加地道和简洁。这也不能真正解决重载函数需要联合返回类型的问题。
猜你喜欢
  • 2021-11-26
  • 1970-01-01
  • 2019-03-08
  • 1970-01-01
  • 2018-04-18
  • 2014-07-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多