我有一个具有primarysecondary字段的 struct ComboChartSpec.我正在定义一个util函数来处理这个ComboChartSpec.

如果我这样定义它,它是有效的

function process1() {
  const yAxisType = 'primary';
  const otherYAxisType = 'secondary';
  return {
    [yAxisType]: foo,
    [otherYAxisType]: bar
  }
}
function process2() {
  const yAxisType = 'secondary';
  const otherYAxisType = 'primary';
  return {
    [yAxisType]: foo,
    [otherYAxisType]: bar
  }
}

我想把这两个功能合并起来.但我一直收到TS错误.我怎么才能解决它呢?谢谢!

function process(yAxisType: 'primary' | 'secondary') {
  const otherYAxisType = yAxisType === 'primary' ? 'secondary' : 'primary';
  return {
    [yAxisType]: foo,
    [otherYAxisType]: bar
  }
}
Type '{ [x: string]: { fields: never[]; } | { type: "quantitative"; }; scale: { type: "quantitative"; }; }' is not assignable to type 'ComboChartAxisEncoding<"editor">'.
  Type '{ [x: string]: { fields: never[]; } | { type: "quantitative"; }; scale: { type: "quantitative"; }; }' is not assignable to type 'ComboChartSingleAxisEncoding<"editor">'.
    Type '{ [x: string]: { fields: never[]; } | { type: "quantitative"; }; scale: { type: "quantitative"; }; }' is missing the following properties from type '{ primary: ComboChartSingleAxisSectionEncoding<"editor">; secondary: ComboChartSingleAxisSectionEncoding<"editor">; scale: QuantitativeScale; }': primary, secondaryts(2322)
index.ts(85, 3): The expected type comes from property 'y' which is declared here on type 'ComboChartEncodingMap<"editor">'

请参阅Minimal repro on TS playground👈

export type ComboChartAxisEncoding = ComboChartSingleAxisEncoding | ComboChartDualAxisEncoding;

export type ComboChartSingleAxisEncoding = {
  primary: ComboChartSingleAxisSectionEncoding;
  secondary: ComboChartSingleAxisSectionEncoding;
  scale: 'quantitative';
};
export type ComboChartSingleAxisSectionEncoding = {
  fields: number[];
};
export type ComboChartDualAxisEncoding = {
  primary: ComboChartDualAxisSectionEncoding;
  secondary: ComboChartDualAxisSectionEncoding;
};
export type ComboChartDualAxisSectionEncoding = {
  fields: number[];
  scale: 'quantitative';
};

function process1(): ComboChartAxisEncoding {
  const yAxisType: 'primary' | 'secondary' = 'primary';
  const otherYAxisType: 'primary' | 'secondary' = 'secondary';

  return { // this works
    [yAxisType]: { fields: [] },
    [otherYAxisType]: { fields: [] },
    scale: 'quantitative',
  };
}
function process2<T extends 'primary' | 'secondary'>(yAxisType: T): ComboChartAxisEncoding {
  const otherYAxisType = (yAxisType === 'primary' ? 'secondary' : 'secondary') as (T extends 'primary' ? 'secondary' : 'primary');

  // this doesn't work. Note that in my case this function wraps lots of test cases and each of them has this structure.
  // So, if I don't have to change the structure of the JS objects, and just need to change the types, it would be better.
  return { 
    [yAxisType]: { fields: [] },
    [otherYAxisType]: { fields: [] },
    scale: 'quantitative',
  };
}

推荐答案

IIUC、foobar是主轴和次轴的可互换值(ComboChartSingleAxisSectionEncoding<"editor">型?).根据一些情况,你可以用computed property name的技巧来交换它们:

{
  [yAxisType]: foo, // yAxisType is either 'primary' or 'secondary'
  [otherYAxisType]: bar // otherYAxisType is the other one
}

只要计算的属性名称是dead simple个字符串文字,TypeScrip就可以正确推断结果对象的形状,就像您在初始情况下将'primary''secondary'个文字值直接赋给yAxisTypeotherYAxisType个变量一样.

不幸的是,一旦这些计算的属性名称之一是union的字面量,即如果它可以有不同的值,情况就不再是这样了.在这种情况下,TS会推断Record类型({ [x: string]: something; }),这可能不再满足您期望的对象形状.

function process1(): ExpectedType {
  const yAxisType = 'primary' as 'primary' | 'secondary'; // No longer a single literal, but a union of literals
  const otherYAxisType = 'secondary';
  return { // Error: Property 'primary' is missing in type '{ [x: string]: number; secondary: number; }' but required in type 'ExpectedType'.
    [yAxisType]: foo,
    [otherYAxisType]: bar
  }
}

在您的情况下,一个非常简单的解决方案是交换values(foobar),而不是密钥:

function process(yAxisType: 'primary' | 'secondary'): ExpectedType {
  return primarySecondarySwap(yAxisType, foo, bar)
}

function primarySecondarySwap<FooBar>(type: 'primary' | 'secondary', foo: FooBar, bar: FooBar) {
  switch (type) {
    case 'primary': return { primary: foo, secondary: bar };
    case 'secondary': return { primary: bar, secondary: foo };
  }
}

Playground Link

Typescript相关问答推荐

如何将单元测试中的私有订阅变量设置为空(或未定义)?

如何根据参数的值缩小函数内的签名范围?

如何传递基于TypScript中可变类型的类型?

具有映射返回类型的通用设置实用程序

如何防止TypeScript允许重新分配NTFS?

在将对象从一个对象转换成另一个对象时,可以缩小对象的键吗?

React router 6.22.3不只是在生产模式下显示,为什么?

如果请求是流,如何等待所有响应块(Axios)

如何使默认导出与CommonJS兼容

react 路由Use RouteLoaderData类型脚本错误

通过泛型函数本身推断受约束的泛型参数的类型脚本问题

我在Angular 17中的environment.ts文件中得到了一个属性端点错误

带投掷的收窄类型

如何在打字角react 式中补齐表格组

在类型脚本中创建显式不安全的私有类成员访问函数

两个名称不同的相同打字界面-如何使其干燥/避免重复?

如何正确键入泛型相对记录值类型?

将对象数组传递给 Typescript 中的导入函数

基于可选参数的动态类型泛型类型

是否可以从 TypeScript 的类型推断中排除this?