最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Typescript interface set values based on logic - Stack Overflow

programmeradmin5浏览0评论

I have a typescript interface which is defined as:

interface FutureProjection{
  month1: number;
  month2: number;
  .
  .
  .
  .
  .
  .
  month12: number;
}

This data is used to show the grid on the UI. I have a logic which is used to find out which month to set data:

calculateMonthNum(currentDate:String,futureDate: String):number{
 //Calculate the month difference between future and current.
return monthDiff;
}

functionToSetMonthData(currentDate:String,futureDate: String, projection: FutureProjection, amount: number): FutureProjection{
  const monthNum = calculateDiff(currentDate,futureDate);
  ****//How to do this?? vvvv **
  projection.month+monthNum = amount; //.  <---This assignment operation???
  return projection;
}

I have a typescript interface which is defined as:

interface FutureProjection{
  month1: number;
  month2: number;
  .
  .
  .
  .
  .
  .
  month12: number;
}

This data is used to show the grid on the UI. I have a logic which is used to find out which month to set data:

calculateMonthNum(currentDate:String,futureDate: String):number{
 //Calculate the month difference between future and current.
return monthDiff;
}

functionToSetMonthData(currentDate:String,futureDate: String, projection: FutureProjection, amount: number): FutureProjection{
  const monthNum = calculateDiff(currentDate,futureDate);
  ****//How to do this?? vvvv **
  projection.month+monthNum = amount; //.  <---This assignment operation???
  return projection;
}
Share Improve this question edited Aug 27, 2021 at 17:38 eko 40.7k11 gold badges78 silver badges101 bronze badges asked Aug 27, 2021 at 17:10 RV.RV. 3,0184 gold badges31 silver badges49 bronze badges
Add a ment  | 

5 Answers 5

Reset to default 4

You can always use bracket syntax to access a property of any javascript object.

// identical
projection['month1']
projection.month1

Which means you can build a string that is the property name and use that in brackets:

projection[`month${monthNum}`] = amount;

But in typescript that only get you half way there. This will get you a type error:

type 'string' can't be used to index type 'FutureProjection'.

The problem is that FutureProjection cannot be indexed by string, only specific strings are allowed. So typescript doesn't know that month${monthNum} is a specific string that is actually valid since monthNum could be any number (-5, 0, 100, 3.14159, etc).

The easiest way to solve that is cast the key string to a keyof FutureProjection. Note that in order for this to be safe you must ensure that calculateDiff() only ever returns integers 1 through 12.

That looks something like:

function setMonthData(currentDate:String,futureDate: String, projection: FutureProjection, amount: number): FutureProjection{
  const monthNum = calculateDiff(currentDate, futureDate);
  const monthKey = `month${monthNum}` as keyof FutureProjection
  projection[monthKey] = amount;
  return projection;
}

Working example on typescript playground


All that said, it may make your life easier to use an array instead.

You can achieve that like the following:

  projection[`month${monthNum}`] = amount;

FYI, as an alternative to a string index signature like

interface FutureProjection {
  [k: string]: number
}

which would accept any key whatsoever:

declare const f: FutureProjection
f.oopsiedaisy = 123; // no piler error

one might consider using a template string pattern index signature as introduced in TypeScript 4.4 to accept only strings that start with "month" and are followed by a numeric-like string:

interface FutureProjection {
  [k: `month${number}`]: number
}

declare const f: FutureProjection
f.oopsiedaisy = 123; // error
f.month11 = 456; // okay
f.month1234567 = 789; // this is also okay, any number is accepted

If you do it this way, then the piler will automatically allow you to index into a FutureProjection with an appropriately constructed template literal string:

function functionToSetMonthData(currentDate: string, futureDate: string,
  projection: FutureProjection, amount: number
): FutureProjection {
  const monthNum = calculateMonthNum(currentDate, futureDate);
  projection[`month${monthNum}`] = amount; // okay
  return projection;
}

Note that this doesn't quite serve the purpose of only accepting month1 through month12. You could decide to replace number with a union of numeric literal types,

type MonthNumber = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
interface FutureProjection extends Record<`month${MonthNumber}`, number> { }

declare const f: FutureProjection
f.oopsiedaisy = 123; // error
f.month11 = 456; // okay
f.month1234567 = 789; // error

which is great; unfortunately you'd have a hard time convincing the piler that any of your mathematical operations that produce number are really producing a MonthNumber, since it doesn't really do math at the type level (see microsoft/TypeScript#15645):

declare const i: MonthNumber;
const j: MonthNumber = 13 - i; // error, even though this must be safe
const k: MonthNumber = ((i + 6) % 12) || 12; // error, ditto

And so you'd find yourself doing unsafe assertions no matter what as well as some other workarounds:

function calculateMonthNum(currentDate: string, futureDate: string): MonthNumber {
  return (((((new Date(futureDate).getMonth() - new Date(currentDate).getMonth())
    % 12) + 12) % 12) || 12) as MonthNumber // <-- assert here
}

function functionToSetMonthData(currentDate: string, futureDate: string,
  projection: FutureProjection, amount: number
): FutureProjection {
  const monthNum = calculateMonthNum(currentDate, futureDate);
  projection[`month${monthNum}` as const] = amount; // <-- const assert here
  return projection;
}

const f = {} as FutureProjection; // assert here
for (let i = 1; i <= 12; i++) f[`month${i as MonthNumber}`] = 0; // assert here

So I'm kind of thinking you'd be better off with `month${number}`.

Playground link to code

I would do like this...

projection[`month${monthNum }`] = amount;

Try to define the interface like

interface FutureProjection {
  [month: string]: number
}

then set values into the object using template strings

projection[`month${monthNum}`] = amount;
发布评论

评论列表(0)

  1. 暂无评论