Javascript 实施D3计划;“可重复使用图表”;打字脚本中的模式

Javascript 实施D3计划;“可重复使用图表”;打字脚本中的模式,javascript,typescript,d3.js,Javascript,Typescript,D3.js,下面第2节中的代码(工作示例)基于第1节中的代码,但改为使用箭头函数,并且基于中Mike Bostock的模式,即返回一个包含其他函数的函数 如果我尝试在typescript(演示)中运行第1节或第2节中的代码,它会说方法addToChart和stop在类型(选择:any)=>()=>void上不存在 如何让typescript识别添加到返回函数中的函数属性(addToChart和stop)? 第1节 const mychart = function (){ let stop = false

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

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

如何让typescript识别添加到返回函数中的函数属性(
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'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"

我想知道您是否可以使用接口/类:

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;
}; 
您可以定义一个接口,即描述函数签名及其属性的接口。根据您的代码,可能是这样的:

interface IChart {
    (selection: any): any;
    // Use overloading for D3 getter/setter pattern
    addToChart(): string;               // Getter
    addToChart(value: string): IChart;  // Setter
}
由于您应该避免任何类似瘟疫的,因此可能需要进一步改进,但这应该足以让您开始。此外,为了允许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"       
constmychart=():IChart=>{
//通过关闭暴露的私人价值
let值:字符串|未定义;
常量图表=((选择)=>{
//私有逻辑
});
//公共接口
//实现一个D3风格的getter/setter。
chart.addToChart=函数(val?:字符串):任意{
return arguments.length?(value=val,chart):值;
};
收益表;
} 
const chart=mychart();
console.log(chart.addToChart())/-->未定义
图表.addToChart(“添加”);//将私有值设置为“添加”。
console.log(chart.addToChart())/-->“添加”

查看可执行文件。

您可以使用
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()
注释

  • 虽然IntelliSense可以按预期工作,但如果您将鼠标悬停在
    d
    上并查看该类型,它比手工制作的版本要难看得多

  • 我单独定义了
    方法
    ,而不是内联在
    对象上。赋值
    ,因为如果我这样做,编译器会感到困惑

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

  • 虽然上面的示例有效,但在某些情况下,编译器会放弃推理,并将返回的
    mychart
    键入为任意值。一种情况是,我们在分配给
    方法的对象中定义的函数中引用
    图表


  • 我会试试看,然后再打给你。我已经给了你一个向上投票,一旦我在我的代码中测试它,我将接受。你的游乐场示例链接到我在我的OP中发布的示例。抱歉,缓冲区大小有问题,但你可以复制并粘贴我发布的代码作为游乐场的答案。我无法接受此答案,因为它对原始代码的更改太多。我希望在原始代码中添加一个类型,而不是更改原始代码以适应某个类型。值得注意的是,D3社区中似乎很少有人使用ES6类。必须完全重新实现使用ES6类的模式并不是答案。对于任何从我的推特上得知悬赏的人来说,我很确定问题中的普通函数和箭头函数的区别有点像是在转移视线;无论词法范围如何,TypeScript都很难使用这种类型的代码。更大的问题是如何在TS中注释“带有getter和setter的闭包”风格的代码。这非常好,可能是我见过的最接近这种工作方式的代码。但是,重载接口会导致一切中断-请尝试向接口添加以下内容,这将是addToChart的getter版本:
    addToChart():string
    如果您将两个addToChart方法组合成类似于
    yScale(scale?:any)的东西:这个|字符串有效,但这似乎扭曲了声明的含义-如果定义了
    scale
    ,则返回值将是
    this
    ;如果未定义,返回值将为
    string
    。在任何情况下,不提供任何参数都不会导致它返回
    this
    。你比我抢先一步。我只是在我的回答中包含了这一点。。。但是,正如您已经提到的,我对此并不完全满意。@aendrew请看一下编辑,它引入了
    addToChart
    函数的重载,以便更清楚地说明问题。我在尝试复制这一点时,认真地搔了十分钟的头,这是非常明智的-值得注意的
    const chart=((选择) => {});不等于
    const chart=(selection)=>{}!无论如何,这是一个很好的答案,它确实帮助我弄清楚这一切到底发生了什么。我会在我有能力的第一时间奖励赏金。这还不错,但我觉得@altocumulus'的回答更为地道和简洁。这也不能真正解决重载函数需要联合返回类型的问题。