TypeScript Error (ts2345) when trying to collect property names in array - Stack Overflow

时间: 2025-01-06 admin 业界

The following updated simplified code illustrates the problem:

type BaseCol<TCol> = TCol & {
    syncProps?: Exclude<keyof BaseCol<TCol>, "syncProps">[];
    width?: number;
    text: string;
}

function createProperty<
    TColumn extends BaseCol<ExtraProps>,
    ExtraProps
>(column: TColumn, prop: Exclude<keyof TColumn, "syncProps">) {
    const x = column[prop];
    column.syncProps ??= [];
    column.syncProps.push(prop); // <--- TS2345 Error.
}

createProperty({
    a: 1,
    b: 2,
    width: 1,
    text: 'ABC'
}, 'a'); // <--- Intellisense works.

The base type BaseCol<TCol> is the type that consumers of a Svelte component use to define their list of columns. The TCol type parameter is intended to extend the properties of their columns to satisfy their business need.

On the other hand, the createProperty function intends to create a "synchronizable" version of any of the properties (implementation not shown), and then record the property name in the column's syncProps property.

From the definition above, the second parameter to createProperty is "all properties of the column, except the control properties" (of which I only show one, syncProps). Consequently, syncProps itself is typed the exact same way.

NOTE: The original code snippet in this question did not define the second type parameter (ExtraProps) in createProperty and instead the first one extended from BaseCol<Record<string, any>>. This is why the first comment refers to this (now gone) fact.

Playground Link

An example use-case:

type MyColumn = BaseCol<{ a: string; b: number; }>;

const cols: MyColumn[] = [
    ...
];

createProperty(cols[3], 'width');

The following updated simplified code illustrates the problem:

type BaseCol<TCol> = TCol & {
    syncProps?: Exclude<keyof BaseCol<TCol>, "syncProps">[];
    width?: number;
    text: string;
}

function createProperty<
    TColumn extends BaseCol<ExtraProps>,
    ExtraProps
>(column: TColumn, prop: Exclude<keyof TColumn, "syncProps">) {
    const x = column[prop];
    column.syncProps ??= [];
    column.syncProps.push(prop); // <--- TS2345 Error.
}

createProperty({
    a: 1,
    b: 2,
    width: 1,
    text: 'ABC'
}, 'a'); // <--- Intellisense works.

The base type BaseCol<TCol> is the type that consumers of a Svelte component use to define their list of columns. The TCol type parameter is intended to extend the properties of their columns to satisfy their business need.

On the other hand, the createProperty function intends to create a "synchronizable" version of any of the properties (implementation not shown), and then record the property name in the column's syncProps property.

From the definition above, the second parameter to createProperty is "all properties of the column, except the control properties" (of which I only show one, syncProps). Consequently, syncProps itself is typed the exact same way.

NOTE: The original code snippet in this question did not define the second type parameter (ExtraProps) in createProperty and instead the first one extended from BaseCol<Record<string, any>>. This is why the first comment refers to this (now gone) fact.

Playground Link

An example use-case:

type MyColumn = BaseCol<{ a: string; b: number; }>;

const cols: MyColumn[] = [
    ...
];

createProperty(cols[3], 'width');
Share Improve this question edited 12 hours ago José Ramírez asked 15 hours ago José RamírezJosé Ramírez 2,2708 silver badges19 bronze badges 7
  • Well, BaseCol<Record<string, any>> is essentially Record<string, any>, and TColumn extends BaseCol<Record<string, any>> doesn't constrain TColumn to lack any particular key, such as a symbol-valued one as shown in this playground link. You might have meant something different, but it's not what you're doing. It looks more like you need a recursive constraint like this playground link shows. Does that fully address the question? If so I'll write an answer explaining; if not, what's missing? – jcalz Commented 14 hours ago
  • Hello, @jcalz. Happy New Year. Thanks for the input. I see that your repro works, as per usual, but then I fail to adapt it to my need. You see, the idea of BaseCol<T> is to allow consumers to add their own propreties to the definition of column they are creating. I can't see how I can use your recursive solution with my use case. Any ideas? – José Ramírez Commented 14 hours ago
  • Is my current minimal not minimal? Basically, I cannot get rid of the type parameter in BaseCol. It is how users define their list of columns. type MyColumn = BaseCol<{ a: number; } >; – José Ramírez Commented 14 hours ago
  • Sure, no problem. I can advance, though, that repeating the properties provided by BaseCol is not really sustainable. It's probably a dozen properties. – José Ramírez Commented 14 hours ago
  • That playground repeats the properties of BaseCol when defining T in the function. The minimal repro shows only syncProps, but it's a lot more properties. – José Ramírez Commented 13 hours ago
 |  Show 2 more comments

1 Answer 1

Reset to default -1

Make sure syncProps is initialized properly to avoid issues,

function createProperty<T extends Record<string, any>>(
    column: BaseCol<T>,
    prop: keyof T
) {
    const x = column[prop];
    if (!column.syncProps) {
        column.syncProps = [];
    }
    column.syncProps.push(prop as keyof T);
}

createProperty({ a: 1, b: 2 }, 'b');