Typescript Generics包装器:无类型函数调用可能不接受类型参数

问题描述 投票:1回答:1

这个与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接收一个布尔值作为参数。同样适用于resultssetResults作为字符串数组。

然后我用另一种方法useStoreLink 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类型获取正确的类型提示和代码完成时摆脱错误?

javascript typescript generics
1个回答
1
投票

由于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')
© www.soinside.com 2019 - 2024. All rights reserved.