Definitelytyped: [@types/react] tidak dapat mengatur State dengan nama kunci dinamis type-safe

Dibuat pada 18 Jun 2018  ·  12Komentar  ·  Sumber: DefinitelyTyped/DefinitelyTyped

Jika Anda tahu cara memperbaiki masalah, buat permintaan tarik sebagai gantinya.

  • [x] Saya mencoba menggunakan paket @types/react dan mengalami masalah.
  • [x] Saya mencoba menggunakan tsc versi stabil terbaru. https://www.npmjs.com/package/typescript
  • [x] Saya punya pertanyaan yang tidak pantas untuk StackOverflow . (Silakan ajukan pertanyaan yang sesuai di sana).
  • [x] [Sebutkan](https://github.com/blog/821-mention-somebody-they-re-notified) penulisnya (lihat Definitions by: di index.d.ts ) agar mereka dapat menanggapi.

    • Penulis: @johnnyreilly , @bbenezech , @pzavolinsky , @digiguru , @ericanderson , @morcerf , @tkrotoff , @DovydasNavickas , @onigoetz , @theruther4d , @guilhermehubner , @ferdaber , @jrakotoharisoa

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
    }));
  }

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

Semua 12 komentar

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} />
Apakah halaman ini membantu?
0 / 5 - 0 peringkat