Typescript: ввод функции, которая возвращает подмножество объекта - PullRequest
0 голосов
/ 01 июля 2018

Я пытаюсь ввести функцию, которая возвращает подмножество свойств объекта, который ему дан. Я не уверен, почему следующее не работает.

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>'.

Кажется, что этот набор метода 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 голосов
/ 01 июля 2018

Функция increment возвращает определенный набор клавиш State. Функция Updater должна возвращать подмножество ключей State, основываясь не на собственной логике, а на параметре типа, переданного ей.

Если нам все равно, какое подмножество 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, но нам нужно явно указать параметр типа. Лучший вариант - использовать заводскую функцию, которая для нас выведет 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, а позволить компилятору работать со всеми типами.

...