这个与Generics的代码片段完美无缺(Link to Simple and Working Code)
const state: Record<string, any> = {
isPending: false,
results: ['a', 'b', 'c']
}
const useValue = <T extends {}>(name: string): [T, Function] => {
const value: T = state[name]
const setValue: Function = (value: T): void => {
state[name] = value
}
return [value, setValue]
}
const [isPending, setIsPending] = useValue<boolean>('isPending')
const [results, setResults] = useValue<string[]>('results')
在这里我可以肯定isPending
是一个布尔值,setIsPending
接收一个布尔值作为参数。同样适用于results
和setResults
作为字符串数组。
然后我用另一种方法useStore
(Link to Extended Broken Code)包装代码
interface UseStore {
state: Record<string, any>
useValue: Function
}
const state: Record<string, any> = {
isPending: false,
results: ['a', 'b', 'c']
}
const useStore = (): UseStore => {
const useValue = <T extends {}>(name: string): [T, Function] => {
const value: T = state[name]
const setValue: Function = (value: T): void => {
state[name] = value
}
return [value, setValue]
}
return { state, useValue }
}
const { useValue } = useStore()
const [isPending, setIsPending] = useValue<boolean>('isPending')
const [results, setResults] = useValue<string[]>('results')
最后两行给出了打字稿错误:Untyped function calls may not accept type arguments.
我怀疑useStore
接口是有问题的,但由于它的动态性质,我想不出更好的解决方案。
如何在使用Generic类型获取正确的类型提示和代码完成时摆脱错误?
由于useValue
的类型是Function
,传递泛型类型参数是没有意义的。他们受益谁?运行时没有得到它们,它们在编译时被擦除。编译器不能使用它们,因为Function
只是一个非类型函数,因此没有任何好处。传递类型参数是无用的,可以说是一个错误(即用户不期望这是Function
并传递类型参数认为他们有一些影响)。
删除类型参数并删除以任何方式键入的假装:
const { useValue } = useStore()
const [isPending, setIsPending] = useValue('isPending')
const [results, setResults] = useValue('results')
更有趣的问题是为什么要编写这样的代码,因为有一种方法可以完全键入此代码中的所有内容:
const state = {
isPending: false,
results: ['a', 'b', 'c']
}
type State = typeof state;
const useStore = () => {
const useValue = <K extends keyof State>(name: K) => {
const value = state[name]
const setValue = (value: State[K]): void => {
state[name] = value
}
return [value, setValue] as const
}
return { state, useValue }
}
type UseStore = ReturnType<typeof useStore>;
const { useValue } = useStore()
const [isPending, setIsPending] = useValue('isPending')
const [results, setResults] = useValue('results')
上面的版本是完全类型安全的,不需要任何重复的名称或类型(您当然可以在多个文件中拆分它,但是根据您的要求,可能需要进行一些复制)。如果这不适用于您的情况,我很想知道原因。
编辑
如果您只想在最后一行处理类型并在那里有一些类型安全性,您只需要使用泛型指定函数的签名:
interface UseStore {
state: Record<string, any>
useValue: <T,>(name: string) => [T, (value: T)=> void]
}
const state: Record<string, any> = {
isPending: false,
results: ['a', 'b', 'c']
}
const useStore = (): UseStore => {
const useValue = <T,>(name: string): [T, (value: T)=> void] => {
const value: T = state[name]
const setValue = (value: T): void => {
state[name] = value
}
return [value, setValue]
}
return { state, useValue }
}
const { useValue } = useStore()
const [isPending, setIsPending] = useValue<boolean>('isPending')
const [results, setResults] = useValue<string[]>('results')
编辑 - 接口实现中的开放式结束
您可以将State
定义为接口,因为接口是开放式的,您可以在需要时添加成员。好处是,如果其他人定义了具有相同名称但属性不同的属性,则会出现错误
接口状态{
}
// Don't know what is in here, empty object for starters
const state : State = {
} as State
const useStore = () => {
const useValue = <K extends keyof State>(name: K) => {
const value = state[name]
const setValue = (value: State[K]): void => {
state[name] = value
}
return [value, setValue] as const
}
return { state, useValue }
}
type UseStore = ReturnType<typeof useStore>;
const { useValue } = useStore()
interface State { isPending: boolean }
state.isPending = false; // no guarantee people actually intialize, maybe initial value can be passed to useValue ?
const [isPending, setIsPending] = useValue('isPending')
interface State { results: string[] }
state.results = ['a', 'b', 'c'];
const [results, setResults] = useValue('results')
interface State { results: number[] } // some else wants to use results for something else, err
const [results2, setResults2] = useValue('results')