Jika Anda tahu cara memperbaiki masalah, buat permintaan tarik sebagai gantinya.
@types/react
dan mengalami masalah.Definitions by:
di index.d.ts
) agar mereka dapat menanggapi.Jika Anda tidak menyebutkan penulisnya, masalah tersebut akan diabaikan.
Saya tidak dapat memanggil setState
dengan objek yang dibuat dari nama properti yang dihitung dengan keamanan tipe:
type State = {
username: string,
password: string
};
type StateKeys = keyof State;
class A extends React.Component<{}, State> {
dynSetState(key: StateKeys, value: string) {
this.setState({
[key]: value // Error here. Pretty sure key is in StateKeys
});
}
}
Saya mengetahui #18365, dan solusinya di https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment -351868649 . Namun, saat menggunakan solusi, TypeScript tidak error saat seharusnya:
dynLooselySetState(key: string, value: string) {
this.setState(prevState => ({
...prevState,
[key]: value // No error here, but can't ensure that key is in StateKeys
}));
}
Ini adalah batasan dari kompiler itu sendiri, tipe yang disimpulkan melalui kunci properti yang dihitung tidak mendukung tipe gabungan, hanya mendukung string
, number
, symbol
atau satu literal dari itu 3, jika mendeteksi bahwa itu adalah gabungan dari semua itu, itu hanya akan memaksanya menjadi tipe string
. Solusinya di sini adalah dengan memaksa objek:
dynSetState(key: StateKeys, value: string) {
this.setState({
[key]: value
} as Pick<State, keyof State>)
}
Ini masih akan memberikan kesalahan dengan tepat jika nilainya tidak dalam kumpulan kemungkinan jenis nilai properti, tetapi tidak akan memberi Anda kesalahan jika kunci tidak berada dalam kumpulan kemungkinan kunci properti.
persis apa @ferdaber meskipun IMHO casting hal-hal seperti ini bukan "pola yang baik", secara keseluruhan Anda harus lebih suka memperbarui status melalui pola panggilan balik yang sekali lagi mematuhi praktik terbaik, seperti mengekstraksi panggilan balik itu di luar kelas ke fungsi murni yang mudah diuji :)
Bagus:
class C extends Component<{}, State> {
updateState(key: StateKeys, value: string) {
this.setState((prevState) => ({
...prevState,
[key]: value,
}));
}
}
Lebih baik:
const updateState = <T extends string>(key: keyof State, value: T) => (
prevState: State
): State => ({
...prevState,
[key]: value
})
class C extends Component<{}, State> {
doSomething(){
this.setState(updateState('password','123124'))
}
}
Saya belum menemukan masalah terkait tentang batasan ini di TypeScript repo.. Mungkin Anda tahu jika ini telah dibahas (dan di mana) di TypeScript repo? Pada dasarnya bertanya-tanya apakah ada beberapa rencana untuk mengatasi ini di versi TypeScript yang akan datang.
Terima kasih!
Berikut adalah diskusi, catatan desain dari tim TS, dan upaya untuk memperbaikinya (yang saya yakini telah ditarik untuk versi mendatang):
https://github.com/Microsoft/TypeScript/issues/13948
https://github.com/Microsoft/TypeScript/issues/18155
https://github.com/Microsoft/TypeScript/pull/21070
Memang, apa yang saya coba capai adalah melakukan sesuatu seperti ini: https://reactjs.org/docs/forms.html#handling -multiple-inputs
Yang akan membuatnya lebih mudah untuk menangani formulir dengan banyak input. Saya mungkin masih harus memastikan bahwa atribut "name" adalah yang kami harapkan, tetapi setelah itu type-safety akan berfungsi.
Saya harus menambahkan as unknown
ke solusi @ferdaber :
this.setState({
[key]: value
} as unknown as Pick<State, keyof State>)
Itu memperingatkan jika key
adalah boolean, tetapi tidak jika itu angka!
Jadi saya memilih solusi yang lebih pendek ini:
this.setState<never>({
[key]: value
})
Ini memperingatkan jika key
adalah boolean atau angka.
Mengapa https://github.com/DefinitelyTyped/DefinitelyTyped/issues/26635#issuecomment -400260278 berfungsi? Maksud saya kuncinya masih tipe serikat pekerja?
Mungkin ini akan membantu Anda
type IAction = {
[P in keyof IAppSettings]?: IAppSettings[P];
};
function reducer(state: IAppSettings, action: IAction) {
return {
...state,
...action,
};
}
Apakah ada kemajuan dalam masalah ini?
Saya bisa membuat ini berfungsi
handleTextChange(name: keyof State, value: any) {
this.setState({
...this.state,
[name]: value
});
}
Untuk orang lain yang melihat ini dan mencoba menerapkannya ke tipe status mereka sendiri, perhatikan bahwa tipe value
di kiriman asli dan tanggapan hanya string
karena semua tipe properti di State
adalah string
. Mungkin lebih baik untuk menghindari penggunaan any
atau casting tambahan saat tipe lain terlibat, dan alih-alih membatasi tipe sedemikian rupa sehingga tipe value
sesuai dengan tipenya yang cocok dengan tipe kunci.
interface State {
name: string;
age: number;
}
type StateKeys = keyof State;
function dynSetState<K extends StateKeys>(key: K, value: State[K]) {
this.setState({ [key]: value }); // fails; if only this worked...
this.setState({ [key]: value } as Pick<State, K>); // clean cast works
this.setState((s, _) => ({ ...s, [key]: value })); // avoids cast, but changes js
}
dynSetState("name", "a"); // works as expected
dynSetState("name", 1); // fails as expected
dynSetState("age", "a"); // fails as expected
dynSetState("age", 1); // works as expected
Mengapa #26635 (komentar) berfungsi? Maksud saya kuncinya masih tipe serikat pekerja?
Ini berfungsi karena tipe { ...prevState }
akan cukup cocok dengan State
, pada dasarnya karena kita memiliki setidaknya semua properti state. Mungkin membantu untuk memahami mengapa _tidak berfungsi_ tanpanya. Akar masalahnya adalah jenis objek yang dibuat: { [key: K]: State[K] }
tidak cukup spesifik; itu adalah { [x: string]: State[K] }
yang tidak dapat memberikan kunci string
ke keyof State
.
Hanya menambahkan ke @arinwt sebagai contoh lain.
TL;DR: Solusi Akhir Di Bawah
Solusi yang Diperluas Dengan Kesalahan Konsol:
type RegisterTypes = {
email: string;
password: string;
}
// ...
const [state, setState] = useState<RegisterTypes>({
email: "",
password: "",
});
// ...
const onChangeInput = (key: keyof RegisterTypes) => (event: React.ChangeEvent<HTMLInputElement>) => {
setState({
[key]: event.target.value
} as Pick<RegisterTypes, typeof key>);
};
// ...
<input type="email" onChange={onChangeInput('email')} value={state.email} />
Meskipun ini memberi saya kesalahan berikut di konsol:
Warning: A component is changing a controlled input of type password to be uncontrolled. Input elements should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component.
Solusi Alternatif (Kecurangan) Dengan Kesalahan Konsol:
Juga memberi peringatan.
type RegisterTypes = {
[key: string]: string;
}
// ...
const onChangeInput = (key: string) => (event: React.ChangeEvent<HTMLInputElement>) => {
setState({
[key]: event.target.value
});
};
Solusi Akhir Tidak Ada Kesalahan:
type RegisterTypes = {
email: string;
password: string;
}
// ...
const onChangeInput = (event: React.ChangeEvent<HTMLInputElement>) => {
const newState = { ...state };
newState[event.target.name as keyof RegisterTypes] = event.target.value;
setState(newState);
};
// ...
<input name="email" type="email" onChange={onChangeInput} value={state.email} />
Komentar yang paling membantu
Ini adalah batasan dari kompiler itu sendiri, tipe yang disimpulkan melalui kunci properti yang dihitung tidak mendukung tipe gabungan, hanya mendukung
string
,number
,symbol
atau satu literal dari itu 3, jika mendeteksi bahwa itu adalah gabungan dari semua itu, itu hanya akan memaksanya menjadi tipestring
. Solusinya di sini adalah dengan memaksa objek:Ini masih akan memberikan kesalahan dengan tepat jika nilainya tidak dalam kumpulan kemungkinan jenis nilai properti, tetapi tidak akan memberi Anda kesalahan jika kunci tidak berada dalam kumpulan kemungkinan kunci properti.