a-kon
a-kon

Reputation: 131

Proper type for reducer's accumulator

How can I describe result and acc type for the following code?

const arr = ['a', 'b', 'c'] as const;

const result = arr.reduce((acc, item, idx) => {
    return {
      ...acc,
    [item]: idx,
  }
}, {});

Depends on an iteration acc can be:

  1. {}
  2. a: number
  3. a: number, b: number
  4. a: number, b: number, c: number

First I used this code:

type MyResult = Record<typeof arr[number], number>; //  
type MyAcc = Partial<MyResult>;

But it's incorrect:

  Types of property 'a' are incompatible.
    Type 'number | undefined' is not assignable to type 'number'.
      Type 'undefined' is not assignable to type 'number'.(2322)

Playground

Upvotes: 1

Views: 277

Answers (1)

Jacob
Jacob

Reputation: 78890

Here's my approach:

type ArrayItemType<A extends ReadonlyArray<any>> = A[number];

const arr = ['a', 'b', 'c'] as const;

type KeyIndexes = Record<ArrayItemType<typeof arr>, number>;

const result = arr.reduce((acc, item, idx) => {
    return {
      ...acc,
    [item]: idx,
  }
}, {} as KeyIndexes);

KeyIndexes is effectively:

{
  a: number,
  b: number,
  c: number
}

...as you can see at this playground

I realize the objection that technically the initializer and accumulator are not this type at all stages of the reduction, but TypeScript really doesn't have a way of figuring out how a partial result will become the full result, so you'll need casting at some point anyway.

Update

If you really care that the accumulator is of a different type than the result, you can adjust the types accordingly, but you'll have to do some more ugly casting:

type ArrayItemType<A extends ReadonlyArray<any>> = A[number];

const arr = ['a', 'b', 'c'] as const;

type KeyIndexes = Record<ArrayItemType<typeof arr>, number>;

const result = arr.reduce((acc: Partial<KeyIndexes>, item, idx) => {
    return {
      ...acc,
    [item]: idx,
  }
}, {}) as KeyIndexes;

(see playground)

Upvotes: 2

Related Questions