Vyhledávání na webu

reactjs - Popište typ curryovaného správce stavu s React a TypeScript


Snažím se popsat typ funkce, která zpracovává událost změny, aby odrážela hodnotu ve stavu.

Požadovaná implementace by vypadala takto:

handleChange: ChangeHandler<State> = field => value =>
    this.setState({ [field]: value });

Daný stav:

interface State {
  title: string;
  description: string | null;
}

Tuto funkci by bylo možné předat komponentě takto:

<TextComponent onChange={this.handleChange('title')} />

Můj nejlepší pokus, jak se s tímto problémem vypořádat, byl vytvořit následující typ:

type ChangeHandler<S> = <K extends keyof S, V extends S[K]>(key: K) => (value: V) => void;

To se však zdá fungovat pouze tehdy, pokud jsou všechny vlastnosti stavu volitelné. Nemohu to dostat k fungování se stavem, který má jak volitelné, tak nevolitelné vlastnosti.

Chyba překladače:

[ts] Argument typu '{ [x: string]: V; }' nelze přiřadit k parametru typu 'State | ((prevState: Readonly, props: Readonly) => State | Pick | null) | Pick<...> | null'.
Typ '{ [x: string]: V; }' postrádá následující vlastnosti typu 'Pick': title, description [2345]

Odpověď č. 1

Kořenová příčina

To je způsobeno známým problémem v TypeScriptu, kdy se výpočtové názvy vlastností sestávající z unie literálů rozšiřují.

Ve handleChange se typ field rozšiřuje z 'title' | 'description' na string, což způsobuje selhání this.setState kvůli tomu, jak jsou definovány typy this.setState.

Níže je zjednodušená definice setState:

function setState<K extends keyof State>(s: Pick<State, K>): void {
    console.log(s);
}

Při použití této definice na handleChange se výše uvedené K stává 'title' | 'description' a parametr s se stává Pick<State, 'title' | 'description'>, což je v podstatě ekvivalentní rozhraní State.

Takže vzhledem k tomu, že naše field bylo rozšířeno na string a this.setState v tomto kontextu přijímá State, dostaneme chybu, kterou vidíte, protože voláme this.setState s následujícím typem:

{ [x: string]: V }

Který nelze přiřadit k State, protože mu chybí požadované parametry title a description.

Řešení

Naše nejlepší volba zde je použít typovou asertaci k potlačení chyby při zachování kontroly typů. Aktualizovaná definice je níže:

let handleChange: ChangeHandler<State> = field => value =>
    setState({ [field]: value } as unknown as State);

let x = handleChange('title');

x('test'); // OK

x(50); // Error: should be string

Podívejte se na tento odkaz na hřiště obsahující výše uvedený kód.

Podívejte se na diskusi o tomto konkrétním problému na DefinitelyTyped pro alternativní řešení/přístupy.

Odpověď č. 2

Nesmíte definovat V extends S[K], protože tuto deklaraci můžete přímo vložit následovně:

type ChangeHandler<S> = <K extends keyof S>(key: K) => (value: S[K]) => void;

Související články