打字稿:键入一个返回对象子集的函数

时间:2018-07-01 08:47:00

标签: reactjs typescript generics types subset

我正在尝试键入一个函数,该函数返回给定对象的属性的子集。我不确定为什么以下方法不起作用。

const initialState = {
  count: 0,
  mounted: false,
}

type State = Readonly<typeof initialState>

type Updater<S> = <K extends keyof S>(prevState: S) => Pick<S, K> | null

const increment/* ERROR HERE */: Updater<State> = (prevState: State) => ({
  count: prevState.count + 1,
})

我收到以下错误消息。

Type '{ count: number; }' is not assignable to type 'Pick<Readonly<{ count: number; mounted: boolean; }>, K>'.

从react键入setState方法似乎很好用

class Component<P, S> {
    ...
    setState<K extends keyof S>(
        state: ((prevState: Readonly<S>, props: P) => (Pick<S, K> | S | null)) | (Pick<S, K> | S | null),
        callback?: () => void
    ): void;
    ...
}

setState(increment)

工作正常,没有任何错误。

这些类型看起来很相似,所以我不太确定出了什么问题。

1 个答案:

答案 0 :(得分:1)

函数increment返回State的键的特定子集。 Updater函数将需要返回State的键的子集,这不是基于它自己的逻辑而是基于传递给它的type参数。

如果我们不在乎返回了State的哪个子集,我们可以使用Partial<T>,但这将使该函数对setState无效,我想这就是重点:

type State = Readonly<typeof initialState>

type Updater<S> = (prevState: S) => Partial<S> | null

const increment: Updater<State> = (prevState: State) => ({
count: prevState.count + 1,
})

class Foo extends React.Component<{}, typeof initialState>
{
    foo(){
        this.setState(increment(this.state)); // Would be an error because count  is number | undefined
    }
}

另一种选择是通过将类型参数从函数移至Updater来固定Updater中需要返回的键:

type Updater<S, K extends keyof S> = (prevState: S) => Pick<S, K> | null

const increment: Updater<State, 'count'> = (prevState: State) => ({
    count: prevState.count + 1,
}) 

这允许我们使用setState中的函数,但是我们需要显式指定type参数。最好的选择是使用工厂函数为我们推断K

type Updater<S, K extends keyof S> = (prevState: S) => Pick<S, K> | null

function createUpdater<S>() {
    return function<K extends keyof S> (fn: (state: S) => Pick<S, K>) : Updater<S, K> {
        return fn;
    }
} 
const increment  = createUpdater<State>()((prevState: State) => ({
    count: prevState.count + 1,
}));

尽管最好的解决方案可能根本不指定increment的类型,而让编译器处理所有类型。