2017-02-12 66 views
1

我不确定这是可能的,但我非常接近。通用键约束:必须是特定类型对象的键

如果我有这样的对象/形状:

export const initialState: State = { 
    foods: { 
     filter: '', 
     someForm: { 
      name: '', 
      age: 2, 
     }, 
     someFormWithoutAnAge: { 
      name: '', 
     }, 
    } 
}; 

declare function pickAForm<R extends keyof State, S extends keyof State[R]>(key1: R, key2: S): void; 

而且该功能效果很好,我可以打电话pickAForm("foods", "someForm")与类型安全,如果我做pickAForm("foods", "somePropertyThatDoesntExist")

但是得到一个错误,我会喜欢增加额外的安全性,你只能挑选具有一定形状的物品。示例:someForm应该可以工作,但someFormWithoutAnAge应该失败,因为无论您选择哪种必须都有年龄属性。像这样的东西:

declare function pickAFormWithAge<R extends keyof State, S extends keyof State[R], T extends State[R][S] & {age: number}>(key1: R, key2: S): void; 

但我一点也不确定如何去做。总结:

pickAFormWithAge('foods', 'someForm') // Passes 
pickAFormWithAge('foods', 'someFormWithoutAge') // Should fail, does not look like {age: number} 
pickAFormWithAge('foods', 'blah') // Should fail, not a key 
+0

如果您将“State”的定义添加到您的问题中,将会有所帮助 –

+0

状态看起来就像initialState。所以认为'type State = typeof initialState' – silviogutierrez

+0

你说的那部分工作在操场上不起作用,它会产生这个错误:''someForm'类型的参数不能分配给类型'never''的参数 –

回答

1

我能做到这一点的唯一方法是:

一个。限制数据结构以匹配字符串文字,而不是相反。 b。将数据结构作为函数参数传递。

const state = { 
    foods: { 
     filter: '', 
     someForm: { 
      name: 'Some form', 
      age: 2 
     }, 
     someFormWithoutAnAge: { 
      name: 'other form', 
      priority: 10 
     } 
    } 
}; 

interface HasAge { age: number } 

// IMPLEMENTATION 
function getForm<O extends {[P in P1]: {[P in P2]: HasAge}}, P1 extends string, P2 extends string>(o: O, p1: P1, p2: P2) { 
    return (o[p1] as any)[p2] as any; 
} 

// USAGE 
const form1 = getForm(state, 'foods', 'someForm'); // GOOD 
const form2 = getForm(state, 'foods', 'someFormWithoutAnAge'); // ERROR 
const form3 = getForm(state, 'foods', 'blah'); // ERROR 

更简单和更灵活的解决方案是使用常规代码。 pickAForm采用函数而不是字符串文字。

const form1 = pickAForm((state) => state.foods.someForm); 
// ...or... 
const form1 = pickAForm(() => initialState.foods.someForm);