动态分配给字符串键打字稿错误没有找到参数类型为 'string' 的索引签名

Dynamically assign to string key typescript error No index signature with a parameter of type 'string' was found

我很难处理 redux reducer 中的这个打字稿错误。我创建了一个简化版本来重现这个问题。

export const CATEGORY_TYPES = ['cars', 'ships'] as const;
export type CategoryTypes = typeof CATEGORY_TYPES[number] 
export interface ObjectArray {
  externalId: string
}
export interface CarFilters {
  arrayType: string[];
  objectType: ObjectArray[];
}

export interface AppState {
  appliedFilters: {
    cars: CarFilters;
    ships: {
      [filter: string]: string[];
    };
  };
}

export interface AssignValue {
  category: CategoryTypes;
  key: string;
  value: string[] | ObjectArray[];
}

const initialState = {
  appliedFilters: {
    cars: {
      arrayType: [],
      objectType: [],
    },
    ships: {},
  },
} as AppState;

const assignValue = (params: AssignValue)=>{
  initialState.appliedFilters[params.category][params.key] = params.value;
}

我遇到以下错误

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'CarFilters | { [filter: string]: string[]; }'.
  No index signature with a parameter of type 'string' was found on type 'CarFilters | { [filter: string]: string[]; }'.

AssignValue 是 reducer ( redux-toolkit ) 中代码的复制。问题似乎是 [params.key]。但是两个对象都有字符串键。

打字稿游乐场sample

这里可能有什么问题。

根据答案中的建议进行更新,因此以下内容完美无缺。

export const CATEGORY_TYPES = ['cars', 'ships'] as const;
export type CategoryTypes = typeof CATEGORY_TYPES[number];

export interface ObjectArray {
  externalId: string
}
export interface CarFilters {
  arrayType: string[];
  objectType: ObjectArray[];
}
export interface ShipFilters {
  [filter: string]: string;
}

export interface AppState {
  appliedFilters: {
    cars: CarFilters;
    ships: ShipFilters;
  };
}

const initialState = {
  appliedFilters: {
    cars: {
      arrayType: [],
      objectType: [],
    },
    ships: {},
  },
} as AppState;

type AppStateFilters = AppState["appliedFilters"]

type PathValue<T extends object, K extends keyof T> = T[K] extends unknown ? T[K] : never

function assignValue<
  Category extends keyof AppStateFilters, 
  Key extends keyof PathValue<AppStateFilters, Category>
>(params: {
    category: Category,
    key: Key,
    value: PathValue<AppStateFilters[Category], Key>
}): void {
  initialState.appliedFilters[params.category][params.key] = params.value;
}

然后我尝试将assignValue函数的类型定义为一个单独的类型(实际上这是一个redux状态更新场景的简化示例,所以我需要为动作单独定义传递参数类型。 ) 而这次我想用相应的值( CarFilters 或 ShipFilters )更新整个类别(汽车或轮船)

// It works type defined with the function
const updateCategoryFilterV1 = <
  Category extends keyof AppStateFilters, 
  Key extends PathValue<AppStateFilters, Category> = PathValue<AppStateFilters, Category>
>(params: {
  category: Category,
  value: Key
})=>{
  initialState.appliedFilters[params.category] = params.value;
}

// But when type is extracted, it does not.
type UpdateAppliedFilterType<Category extends keyof AppStateFilters = keyof AppStateFilters, 
  Filter extends PathValue<AppStateFilters, Category> = PathValue<AppStateFilters, Category>> = {
    category: Category,
    filter: Filter
  }

const updateCategoryFilterV2 = (params: UpdateAppliedFilterType) => {
  initialState.appliedFilters[params.category] = params.filter
}

Playground

错误

Type 'CarFilters | ShipFilters' is not assignable to type 'CarFilters & ShipFilters'.
  Type 'CarFilters' is not assignable to type 'CarFilters & ShipFilters'.
    Type 'CarFilters' is not assignable to type 'ShipFilters'.
      Index signature is missing in type 'CarFilters'.(2322)

The problem seems to be the [params.key]. But both objects have string keys.

AppState['ships']的定义说anystring都可以作为key。那是一个 index signature.

另一方面,CarFilters的定义说只有两个特定的字符串可以用作键。它没有索引签名。

如果 params.category'cars',那么 params.key 必须是 'arrayType' | 'objectType'。只是 string 不够具体。

同样,value: string[] | ObjectArray[]; 不够具体,因为它不能确保您将正确的值分配给正确的键。

以下示例与您的 AssignValue 类型匹配,但未将正确的值分配给正确的路径:

const bad1: AssignValue = {
    category: 'cars',
    key: 'objectType',
    value: ['a', 'b'] // assigns string[] where ObjectArray[] is expected
}

const bad2: AssignValue = {
    category: 'cars',
    key: 'someString', // unknown property key
    value: ['a', 'b']
}

我可以改进您的 AssignValue 类型,使您只能传入有效参数。上面的 bad1bad2 现在是错误。我正在使用映射类型、索引访问类型和联合类型。

type AssignCarFilter = {
    [K in keyof CarFilters]: {
        category: 'cars';
        key: K;
        value: CarFilters[K];
    }
}[keyof CarFilters];

type AssignShipsFilter = {
    category: 'ships';
    key: string;
    value: string[];
}

export type AssignValue = AssignCarFilter | AssignShipsFilter;

不幸的是,这并不能使错误消失。 TypeScript 很难理解分配键和分配值之间的关系。

所以现在至少我 as any 正在摆脱困境,并称之为改进。

const assignValue = (params: AssignValue) => {
  (initialState as any).appliedFilters[params.category][params.key] = params.value;
}

如前所述您的'AssignValue'界面过于模糊。它不适合表示您试图强加给您的函数的关于键值对应的不变量。

但是,通过一些仪式,正确输入 assignValue 函数绝对是可能的。使用 distributive conditional types:

type AppStateFilters = AppState["appliedFilters"]

type PathValue<T extends object, K extends keyof T> = T[K] extends unknown ? T[K] : never

function assignValue<
  Category extends keyof AppStateFilters, 
  Key extends keyof PathValue<AppStateFilters, Category>
>(params: {
    category: Category,
    key: Key,
    value: PathValue<AppStateFilters[Category], Key>
}): void {
  initialState.appliedFilters[params.category][params.key] = params.value;
}

TS playground