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;
