【问题标题】:Dynamically assign to string key typescript error No index signature with a parameter of type 'string' was found动态分配给字符串键打字稿错误找不到带有“字符串”类型参数的索引签名
【发布时间】:2021-08-16 16:55:15
【问题描述】:

我很难在 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状态更新场景的简化示例,所以我需要为action单独定义传递参数类型。)而这次我想用各自的值( 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)

【问题讨论】:

    标签: typescript redux redux-toolkit


    【解决方案1】:

    问题似乎是[params.key]。但是两个对象都有字符串键。

    AppState['ships'] 的定义表明 any string 可以用作密钥。那是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-ing 我的出路并称之为改进。

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

    【讨论】:

      【解决方案2】:

      in this answer 所述,您的“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

      【讨论】:

      • 这实际上是我想要的。我也浏览了 typescriptlang 文档,这很有意义。顺便说一句,我尝试提取函数参数类型并将其定义为单独的类型,然后我遇到了问题。问题随这些更改而更新。不胜感激,如果你能看看。
      • tsplay.dev/w8v3Pm 。这对你有用吗?不幸的是,不能使用包含联合的完全饱和类型来建立精确的关系。唯一的方法是在调用站点计算类型。不管怎样。如果您需要详尽的解释,请随时提出其他问题。
      • 感谢您的回复,但我的情况有些不同。我在这里创建了一个完整的例子codesandbox.io/s/react-redux-toolkit-gqthp。我需要更改的地方是过滤器/类型的UpdateAppliedFilterCategoryValueType 成为一种类型并摆脱减速器中的任何东西。为什么会这样是因为 redux typescript 项目开始使用 redux 工具包,所以新的 redux 切片不能使用工具包的所有好处。
      猜你喜欢
      • 2021-11-27
      • 2019-10-27
      • 1970-01-01
      • 2018-06-19
      • 1970-01-01
      • 1970-01-01
      • 2022-06-15
      • 2021-12-20
      • 2020-01-06
      相关资源
      最近更新 更多