Definitelytyped: Tentang penggunaan Pick untuk @types/react's setState

Dibuat pada 25 Jul 2017  ·  53Komentar  ·  Sumber: DefinitelyTyped/DefinitelyTyped

Saya mengerti bahwa Pick digunakan untuk tipe setState karena mengembalikan undefined untuk kunci yang tidak boleh tidak ditentukan akan mengakibatkan kunci disetel ke tidak ditentukan oleh Bereaksi.

Namun, menggunakan Pick menyebabkan masalah lain. Pertama, pelengkapan otomatis layanan kompiler menjadi tidak berguna, karena menggunakan hasil Pick untuk pelengkapan otomatis, dan ketika Anda meminta penyelesaian, hasil Pick belum berisi kunci yang mungkin Anda miliki ingin melengkapi otomatis. Tapi masalahnya sangat buruk saat menulis setState dengan argumen panggilan balik:

  1. Daftar kunci berasal dari pernyataan return ; jika Anda tidak mengembalikan kunci tertentu dalam pernyataan pengembalian Anda, Anda juga tidak dapat membacanya dalam argumen tanpa memaksa daftar kunci untuk mengatur ulang ke never . Beberapa pernyataan pengembalian bisa sulit untuk ditulis jika mereka mengembalikan kunci yang berbeda, terutama jika Anda memiliki pengembalian yang tidak ditentukan di suatu tempat (misalnya if (state.busy) { return } ).

    • Ini dapat diatasi dengan selalu menggunakan input dalam spread (misalnya this.setState(input => ({ ...input, count: +input.count + 1 })) ) tetapi ini berlebihan dan deoptimisasi, terutama untuk negara bagian yang lebih besar, karena setState akan meneruskan nilai pengembalian dari panggilan balik ke Object.assign .

  2. Jika, karena alasan tertentu, tipe yang Anda kembalikan tidak kompatibel dengan tipe input, Pick akan memilih never untuk kuncinya, dan fungsi akan diizinkan untuk mengembalikan _anything_. Bahkan kunci yang bertepatan dengan kunci yang ada secara efektif memungkinkan any sebagai nilai -- jika tidak cocok, itu hanya tidak Pick ed, dan diperlakukan sebagai kelebihan properti untuk {} , yang tidak dicentang.
  3. Jika never dipilih sebagai argumen umum, untuk salah satu alasan yang tercantum di atas, argumen panggilan balik sebenarnya dapat diperlakukan sebagai argumen ke bentuk objek setState ; ini menyebabkan argumen panggilan balik diketik any alih-alih {} . Saya tidak yakin mengapa ini bukan kesalahan implisit.
interface State {
  count: string // (for demonstration purposes)
}

class Counter extends React.Component<{}, State> {
  readonly state: Readonly<State> = {
    count: '0'
  }

  render () {
    return React.createElement('span', { onClick: this.clicked }, this.state.count)
  }

  private readonly clicked = () => {
    this.setState(input => ({
      count: +input.count + 1 // not a type error
      // the setState<never>(input: Pick<State, never>) overload is being used
    }))
  }
}

Singkatnya, sementara penggunaan Pick , meskipun ada beberapa ketidaknyamanan, membantu menangkap kesalahan jenis dalam bentuk non-panggilan balik setState , itu benar-benar kontra-produktif dalam bentuk panggilan balik; di mana itu tidak hanya tidak melakukan tugas yang dimaksudkan untuk melarang undefined tetapi juga menonaktifkan semua jenis pemeriksaan sama sekali pada input atau output panggilan balik.

Mungkin itu harus diubah, setidaknya untuk formulir panggilan balik, menjadi Partial dan berharap pengguna tahu untuk tidak mengembalikan nilai undefined , seperti yang dilakukan dalam definisi yang lebih lama.

Komentar yang paling membantu

"Perbaikan" baru-baru ini menyebabkan masalah bagi saya sekarang dengan beberapa pernyataan pengembalian dalam panggilan balik setState() .

TypeScript: 2.6.2 dengan semua opsi "ketat" diaktifkan kecuali untuk "strictFunctionTypes"
jenis/reaksi: 16.0.30

Contoh kode:

interface TestState {
    a: boolean,
    b: boolean
}

class TestComponent extends React.Component<{}, TestState> {
    private foo(): void {
        this.setState((prevState) => {
            if (prevState.a) {
                return {
                    b: true
                };
            }

            return {
                a: true
            };
        });
    }
}

Kesalahan kompiler:

error TS2345: Argument of type '(prevState: Readonly<TestState>) => { b: true; } | { a: true; }' is not assignable to parameter of type '((prevState: Readonly<TestState>, props: {}) => Pick<TestState, "b"> & Partial<TestState>) | (Pick<TestState, "b"> & Partial<TestState>)'.
  Type '(prevState: Readonly<TestState>) => { b: true; } | { a: true; }' is not assignable to type 'Pick<TestState, "b"> & Partial<TestState>'.
    Type '(prevState: Readonly<TestState>) => { b: true; } | { a: true; }' is not assignable to type 'Pick<TestState, "b">'.
      Property 'b' is missing in type '(prevState: Readonly<TestState>) => { b: true; } | { a: true; }'.

522         this.setState((prevState) => {
                          ~~~~~~~~~~~~~~~~

Semua 53 komentar

Terima kasih atas tanggapan Anda. Ini adalah kasus yang sangat menarik yang Anda angkat.

Saya perlu memikirkan implikasinya sedikit sebelum saya berkomitmen untuk memberikan pendapat.

Pada satu titik @ahejlsberg ingin memperlakukan opsionalitas secara berbeda dari | undefined . Jadi foo?: string berarti foo tidak disetel atau berupa string.

Saat membaca nilai properti, perbedaannya 99,9% tidak relevan sepanjang waktu, tetapi untuk penulisan, terutama dalam kasus Partial<> , perbedaannya sangat penting.

Sayangnya, ini adalah perubahan bahasa yang melanggar sehingga kita harus menunggu 3.0 atau memilikinya di belakang bendera.

Jika kami telah mengatakan perubahan, Partial<> menjadi sangat berguna bagi banyak orang daripada dogma saya saat ini yang menolak penggunaannya saat terlihat.

@ahejlsberg Saya tahu Anda orang yang sibuk, seberapa

Baiklah, setelah menghabiskan beberapa waktu pagi ini memikirkan masalahnya, saya melihat beberapa "solusi" untuk masalah yang Anda ajukan, masing-masing dengan beberapa efek samping yang cukup berat.

1. Ubah opsionalitas ( interface State { foo?: string } ) menjadi string atau tidak disetel.

Contoh:

interface State {
  foo: string;
  bar: string;
}

const bleh: Partial<State> = { foo: "hi" }; // OKAY
const bleh: Partial<State> = { foo: undefined; } // BREAK

Ini secara teknis akan memecahkan masalah dan memungkinkan kita untuk menggunakan Partial<> , dengan mengorbankan kasus 98% menjadi lebih sulit. Ini menghancurkan dunia sehingga tidak mungkin dilakukan sampai 3.x dan umumnya, sangat sedikit perpustakaan/kasus penggunaan yang benar-benar peduli tentang perbedaan antara yang tidak disetel vs tidak ditentukan.

2. Cukup beralih ke parsial

Contoh:

interface State {
  foo: string;
  bar: string;
}

setState(prevState => {foo: "hi"}); // OKAY
setState(prevState => {foo: undefined}); // OKAY BUT BAD!

3. Tidak Melakukan Apa-apa

Contoh:

interface State {
  foo: string;
  bar: string;
}

// The following errors out because {foo: ""} can't be assigned to Pick<State, "foo" | "bar">
// Same with {bar: "" }
setState((prevState) => {
  if (randomThing) {
    return { foo: "" };
  }
  return { bar: "" };
});

// A work around for the few places where people need this type of thing, which works
setState((prevState) => {
  if (randomThing) {
    return { foo: "" } as State
  }
  return { bar: "" } as State
});

// This is fine because the following is still an error
const a = {oops: ""} as State

4. Tambahkan logika bersarang ke tipe literal yang digabungkan

Saat ini, ketika kami memiliki beberapa jalur pengembalian, kompiler hanya memasukkan semua kunci potensial menjadi satu Pick<State, "foo" | "bar"> raksasa.

Namun, perubahan yang kompatibel ke belakang adalah untuk memungkinkan literal dikelompokkan, seperti Pick<State, ("foo") | ("bar")> atau dalam kasus yang lebih kompleks: Pick<State, ("foo" | "bar") | ("baz")>

Kurangnya parens menunjukkan hanya satu set nilai yang merupakan fungsi yang ada.

Sekarang ketika kita mencoba untuk melemparkan {foo: number} ke Pick<State, ("foo") | ("bar")> , kita bisa berhasil. Sama dengan {bar: number} .

Pick<State, ("foo") | ("bar")> juga dapat dilemparkan ke Partial<State> dan ke {foo: number} | {bar: number} .

Kesimpulan pribadi saya

(1) dan (2) tidak bisa dilakukan. Satu memperkenalkan kompleksitas untuk hampir semua orang, apa pun yang terjadi, dan yang lain membantu orang menghasilkan kode yang dikompilasi tetapi jelas salah.

(3) benar-benar dapat digunakan hari ini dan meskipun saya tidak ingat mengapa saya menambahkan | S sebagai nilai pengembalian potensial ke fungsi di setState , saya tidak dapat memahami alasan lain untuk melakukannya.

(4) dapat meniadakan solusi di (3) tetapi terserah pada pengembang TypeScript dan mungkin jika kami cukup tertarik @ahejlsberg, kami dapat melihatnya lebih cepat daripada nanti.

Ini membuat saya berpikir jenisnya lebih benar hari ini daripada jika kita mengubahnya.

Masalah dengan pendekatan "tidak melakukan apa-apa" adalah bahwa kompiler tidak berperilaku seperti yang Anda gambarkan jika Anda benar-benar membuat kesalahan tipe nyata. Jika saya mengubah contoh Anda menjadi, katakanlah, setel nilainya ke 0 alih-alih string kosong:

interface State {
  foo: string;
  bar: string;
}

// The following does not error at all because the compiler picks `Pick<State, never>`
// The function overload of `setState` is not used -- the object overload is, and it
// accepts the function as it is accepting anything (`{}`).
setState((prevState) => {
  if (randomThing) {
    return { foo: 0 };
  }
  return { bar: 0 };
});

Saya melihat sekarang. Kembali ke papan gambar. Saya akan melihat apakah kita bisa membuat solusi cerdas untuk ini. Jika kami tidak dapat menemukan satu, maka kami perlu mengevaluasi solusi Partial<> Anda usulkan. Hal besar yang perlu dipertimbangkan adalah apakah undefined tidak terduga pada suatu nilai akan lebih umum/mengganggu daripada jenis yang salah yang tidak terduga.

Untuk memperjelas, kami memiliki dua kelebihan untuk ini ...

setState<K extends keyof S>(f: (prevState: Readonly<S>, props: P) => Pick<S, K>, callback?: () => any): void;
setState<K extends keyof S>(state: Pick<S, K>, callback?: () => any): void;

Menyetel salah satu atau keduanya ke Partial<S> alih-alih Pick<S, K> tidak memperbaiki masalah Anda. Itu masih turun ke versi objek, yang menerima tipe yang salah karena beberapa alasan:

```ts
antarmuka Negara {
batang: tali;
foo: nomor;
}

class Foo memperluas React.Component<{}, State> {
publik bla() {
this.setState((prevState) => ({bar: 1})); // Tidak salah :/
}
}```

Bisakah kita memaksanya untuk menolak suatu fungsi dengan menggunakan extends object
paksaan?
Pada Jumat, 28 Juli 2017 pukul 0:00 Eric Anderson [email protected] menulis:

Untuk memperjelas, kami memiliki dua kelebihan untuk ini ...

atur keadaan(f: (prevState: Readonly , props: P) => Pilih , callback?: () => any): void;setState(status: Pick , callback?: () => any): void;

Mengatur salah satu atau keduanya ke Parsial alih-alih Pilihtidak memperbaiki masalah Anda.

antarmuka Negara {
batang: tali;
foo: nomor;
}
class Foo memperluas React.Component<{}, State> {
publik bla() {
this.setState((prevState) => ({bar: 1})); // Tidak salah :/
}
}```


Anda menerima ini karena Anda yang menulis utas.
Balas email ini secara langsung, lihat di GitHub
https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment-318388471 ,
atau matikan utasnya
https://github.com/notifications/unsubscribe-auth/AAEdfeEJ_Ejfana14fRII1OZuS7qTuyuks5sSKX3gaJpZM4OiDrc
.

Kami mencoba itu. Fungsi adalah objek.

Eric L Anderson
dikirim dari iPhone saya

Saya datang ke sini untuk melaporkan masalah terkait, yaitu refactoring tipe status rusak dengan perubahan ini.

Misalnya, lihat https://github.com/tomduncalf/react-types-issue/blob/master/Test.tsx - jika Anda membuka ini di VS Code dan F2 untuk refactor/rename something pada baris 6 (https://github.com/tomduncalf/react-types-issue/blob/master/Test.tsx#L6), itu akan memperbarui baris itu dan https://github.com/tomduncalf/react-types-issue/ blob/master/Test.tsx#L14 , tetapi akan melewatkan penggunaan ini dalam panggilan setState di https://github.com/tomduncalf/react-types-issue/blob/master/Test.tsx #L20

Satu-satunya cara yang saya temukan untuk membuatnya berfungsi dengan benar adalah dengan mengetikkan objek saya secara eksplisit as IState saat memanggil setState , yang agak merepotkan dan cukup mudah dilupakan.

Saya tidak akrab dengan alasan perubahan itu secara tepat, tetapi tampaknya telah merusak keamanan tipe dengan cara yang cukup signifikan ketika bekerja dengan negara sehingga akan lebih bagus jika ada cara untuk menyelesaikannya!

Terima kasih,
tom

Ya, sekali lagi saya percaya akan lebih baik menggunakan Partial karena menyimpan informasi hubungan itu dengan lebih baik.

Diperdebatkan, Pick tidak di-refactor dengan benar adalah batasan/bug dari kode refactoring yang dapat ditingkatkan, tetapi saya masih tidak percaya bahwa Pick menyediakan keamanan jenis yang https:// github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment -318385945 menyebutkan. Sementara Partial akan mengizinkan undefined mana undefined tidak seharusnya, dan pengguna kemudian perlu berhati-hati untuk tidak melakukannya, Pick mengizinkan apa pun karena jenis apa pun yang tidak sesuai dengan antarmuka akan menyebabkan, alih-alih kesalahan jenis, Pick untuk menghasilkan antarmuka kosong sebagai gantinya, yang menerima apa pun.

Adapun https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment -318388471 , mungkinkah ini bug di pemeriksa properti berlebih? Atau apakah ini diuji sebelum pemeriksa properti berlebih dibuat lebih ketat di 2.3 atau 2.4 (saya lupa)?

Masalah lain yang baru saya perhatikan di sini adalah jika suatu komponen tidak memiliki tipe status (yaitu hanya satu parameter tipe ke React.Component ), atau tipe status kosong ( {} ), TypeScript masih akan mengizinkan panggilan ke setState tanpa membuat kesalahan, yang tampaknya salah bagi saya – lihat https://github.com/tomduncalf/react-types-issue/blob/master/Test2.tsx

Solusi menggunakan Partial terdengar lebih baik daripada saya – saya mungkin mencoba menambalnya sendiri dan melihat cara kerjanya.

Terima kasih,
tom

Halo semua!
Saya tidak tahu mengapa, tetapi jika saya menggabungkan deklarasi setState menjadi satu deklarasi:

setState<K extends keyof S>(state: ((prevState: Readonly<S>, props: P) => Pick<S, K>) | Pick<S, K>, callback?: () => any): void;

Ini berfungsi seperti yang diharapkan untuk saya:

import * as React from 'react';

export class Comp extends React.Component<{}, { foo: boolean, bar: boolean }> {
  public render() {
    this.handleSomething();
    return null;
  }

  private handleSomething = () => {
    this.setState({ foo: '' }); // Type '""' is not assignable to type 'boolean'.
    this.setState({ foo: true }); // ok!
    this.setState({ foo: true, bar: true }); // ok!
    this.setState({}); // ok!
    this.setState({ foo: true, foo2: true }); // Object literal may only specify
    // known properties, and 'foo2' does not exist in type
    this.setState(() => ({ foo: '' })); // Property 'foo' is missing in type '() => { foo: string; }'.
    this.setState(() => ({ foo: true })); // ok!
    this.setState(() => ({ foo: true, bar: true })); // ok!
    this.setState(() => ({ foo: true, foo2: true })); // Property 'foo' is missing in type
    // '() => { foo: true; foo2: boolean; }'
    this.setState(() => ({ foo: '', foo2: true })); // Property 'foo' is missing in
    // type '() => { foo: string; foo2: boolean; }'.
    this.setState(() => ({ })); // ok!
  };
}

Mungkinkah ini cukup berubah untuk memperbaiki masalah aslinya?

@mctep Temuan yang bagus.

Dan jika saya mengambil apa yang Anda lakukan dan memperluasnya sedikit, menjadi Partial<S> & Pick<S, K> alih-alih Pick<S, K> di beberapa tempat, intellisense menyarankan nama kunci untuk Anda. Sayangnya, nama kunci mengatakan nilai properti adalah "sesuatu | tidak terdefinisi" tetapi ketika Anda mengompilasinya, ia tahu lebih baik:

declare class Component<P, S> {
    setState<K extends keyof S>(state: ((prevState: Readonly<S>, props: P) => (Partial<S> & Pick<S, K>)) | (Partial<S> & Pick<S, K>), callback?: () => any): void;
}

interface State {
    foo: number;
    bar: string;
    baz?: string;
}

class Foo extends Component<{}, State> {
    constructor() {
        super();
        this.setState(() => { // error
            return {
                foo: undefined
            }
        });
        this.setState({ // error
            foo: undefined
        })
        this.setState({
            foo: 5,
            bar: "hi",
            baz: undefined
        })
    }
}

Saya akan memasukkan perubahan ini nanti hari ini

"Perbaikan" baru-baru ini menyebabkan masalah bagi saya sekarang dengan beberapa pernyataan pengembalian dalam panggilan balik setState() .

TypeScript: 2.6.2 dengan semua opsi "ketat" diaktifkan kecuali untuk "strictFunctionTypes"
jenis/reaksi: 16.0.30

Contoh kode:

interface TestState {
    a: boolean,
    b: boolean
}

class TestComponent extends React.Component<{}, TestState> {
    private foo(): void {
        this.setState((prevState) => {
            if (prevState.a) {
                return {
                    b: true
                };
            }

            return {
                a: true
            };
        });
    }
}

Kesalahan kompiler:

error TS2345: Argument of type '(prevState: Readonly<TestState>) => { b: true; } | { a: true; }' is not assignable to parameter of type '((prevState: Readonly<TestState>, props: {}) => Pick<TestState, "b"> & Partial<TestState>) | (Pick<TestState, "b"> & Partial<TestState>)'.
  Type '(prevState: Readonly<TestState>) => { b: true; } | { a: true; }' is not assignable to type 'Pick<TestState, "b"> & Partial<TestState>'.
    Type '(prevState: Readonly<TestState>) => { b: true; } | { a: true; }' is not assignable to type 'Pick<TestState, "b">'.
      Property 'b' is missing in type '(prevState: Readonly<TestState>) => { b: true; } | { a: true; }'.

522         this.setState((prevState) => {
                          ~~~~~~~~~~~~~~~~

Juga melihat masalah yang dijelaskan oleh @UselessPickles sebagai akibat dari perubahan ini.

Saya cukup yakin itu selalu menjadi masalah.

Saya jauh lebih dari cukup yakin bahwa itu tidak selalu menjadi masalah. Saya memiliki proyek dengan beberapa pernyataan pengembalian dalam panggilan balik setState() yang telah dikompilasi tanpa masalah selama 2+ bulan. Saya telah mengikuti peningkatan ketergantungan NPM setiap 2 minggu atau lebih, dan kesalahan kompiler ini baru mulai terjadi pada saya hari ini setelah memutakhirkan ke versi terbaru dari tipe/bereaksi. Versi sebelumnya tidak menghasilkan kesalahan kompiler ini.

Sudah menjadi masalah sejak perubahan dari Partial ke Pick. Solusinya adalah memberikan daftar kunci yang ingin Anda kembalikan ke parameter generik setState , tetapi kemudian Anda dipaksa untuk selalu mengembalikan semua kunci...

Apa yang saya lakukan dalam kasus tersebut adalah apakah saya selalu mengembalikan semua kunci, dengan kunci yang tidak dimodifikasi disetel ke key: prevState.key , atau dengan mengembalikan spread dengan prevState ( { ...prevState, newKey: newValue } ).

Mungkin saya benar-benar memiliki kasus tepi yang lebih spesifik dalam kode saya yang berfungsi sebelumnya secara kebetulan? Contoh aktual dari proyek saya lebih seperti ini, di mana ia mengembalikan objek kosong (untuk tidak mengubah status apa pun), atau mengembalikan objek yang tidak kosong:

interface TestState {
    a: boolean,
    b: boolean
}

class TestComponent extends React.Component<{}, TestState> {
    private foo(newValue: boolean): void {
        this.setState((prevState) => {
            if (prevState.a === newValue) {
                // do nothing if there's no change
                return { };
            }

            return {
                a: newValue,
                // force "b" to false if we're changing "a"
                b: false
            };
        });
    }
}

return null dan return undefined juga merupakan nilai pengembalian yang dapat diterima dengan baik untuk tidak mengubah status (mereka akan melalui Object.assign dan jadi tidak ada perubahan pada this.state ).

Mengingatkan saya, tanda tangan saat ini tidak mengizinkan keduanya. Mungkin null harus diizinkan sebagai nilai balik, setidaknya, karena itu bukan sesuatu yang bisa keluar dari lupa return secara tidak sengaja sama sekali.


Bagaimanapun, dalam kasus di mana tanda tangan ambigu, TypeScript tampaknya hanya memilih pernyataan return dalam urutan sumber dan menggunakannya untuk menurunkan parameter generik. Tampaknya dapat menggabungkan jenis untuk obat generik sederhana (misalnya Array atau Promise ), tetapi tidak pernah menggabungkan mereka jika jenis kontekstual adalah jenis yang dipetakan seperti Pick .

Saya melihat beberapa regresi dengan versi non-fungsi dengan tipe terbaru. Secara khusus, tipe argumen "status" saat meneruskan argumen telah berubah dari:

atur keadaan( state: Pick , callback?: () => any): void;

ke:

state: ((prevState: Readonly\ , props: P) => (Pilih & Sebagian\ )) |

https://github.com/DefinitelyTyped/DefinitelyTyped/commit/62c2219a6ed6dc34ea969b8c2c87a41d31002660#diff -96b72df8b13a8a590e4f160cbc51f40c

Penambahan & Partial<S> tampaknya merusak hal-hal yang sebelumnya berfungsi.

Berikut repro minimal:

export abstract class ComponentBaseClass<P, S = {}> extends React.Component<P, S & { baseProp: string }>
{
    foo()
    {
        this.setState( { baseProp: 'foobar' } );
    }
}

ini gagal dengan kesalahan:

(429,18): Argumen tipe '{ baseProp: "foobar"; }' tidak dapat ditetapkan ke parameter tipe '((prevState: Readonly Type '{ baseProp: "foobar"; }' tidak dapat ditetapkan untuk mengetik 'Pick & Partial Type '{ baseProp: "foobar"; }' tidak dapat ditetapkan ketik 'Sebagian'

Mengubah tipe status dari S &{ baseProp: string } menjadi hanya { baseProp: string } juga akan membuat kesalahan hilang (meskipun itu akan merusak kelas aktual yang menentukan tipe S).

Itu menarik. Saya senang untuk memutar kembali sebagian dan memilih bagian dari perubahan

Saya akan mengatakan bahwa apa yang Anda laporkan terdengar seperti bug di TS, khususnya:

Ketik '{ baseProp: "foobar"; }' tidak dapat ditetapkan untuk mengetik 'Sebagian

Baik. Mungkin TS tidak tahu ada apa karena tidak ada hubungan antara S dan baseProp.

Seseorang dapat melewati S yang bertipe { baseProp:number } dalam hal ini benar bahwa Anda tidak dapat menetapkan string ke baseProp.

Mungkin jika S memperpanjang versi baseProp?

Saya telah mencoba sesuatu seperti ini sebelumnya:

interface BaseState_t
{
    baseProp: string
}

export abstract class ComponentBaseClass<P, S extends BaseState_t> extends React.Component<P, S >
{
    foo()
    {
        this.state.baseProp;
        this.setState( { baseProp: 'foobar' } );
    }
}

saat aksesnya ok, panggilan setState memiliki kesalahan yang sama:

Argumen tipe '{ baseProp: "foobar"; }' tidak dapat ditetapkan ke parameter tipe '((prevState: Readonly\ , props: P) => Pick & Partial\ ) |
}' tidak dapat ditetapkan untuk mengetik 'Pilih & Sebagian\ '.Ketik '{ baseProp: "foobar";

Ini mungkin bukan cara yang baik untuk menyusunnya - lagi pula, beberapa kelas anak dapat mendeklarasikan variabel status yang bertabrakan dengan variabel di kelas dasar. Jadi TypeScript mungkin benar untuk mengeluh bahwa tidak pasti apa yang akan terjadi di sana. Aneh bahwa tidak ada masalah dengan mengakses alat peraga itu.

Hmm. Ini pasti terasa seperti bug di TS sekarang.

Saya akan bermain dengan ini hari ini dan mungkin mengambil & Partial.

Saya akan menambahkan ini sebagai kasus percobaan juga dan mencoba ide lain untuk membuat intellisense senang

Hai,
Masalah yang sama disini...

export interface ISomeComponent {
    field1: string;
    field2: string;
}

interface SomeComponentState {
    field: string;
}

export class SomeComponent<
    TProps extends ISomeComponent = ISomeComponent,
    TState extends SomeComponentState = SomeComponentState> extends React.Component<TProps, TState>{

    doSomething() {
        this.setState({ field: 'test' });
    }

    render() {
        return (
            <div onClick={this.doSomething.bind(this)}>
                {this.state.field}
            </div>
        );
    }
}

Kesalahan di setState:
TS2345 (TS) Argument of type '{ field: "test"; }' is not assignable to parameter of type '((prevState: Readonly<TState>, props: TProps) => Pick<TState, "field"> & Partial<TState>) | (Pick...'. Type '{ field: "test"; }' is not assignable to type 'Pick<TState, "field"> & Partial<TState>'. Type '{ field: "test"; }' is not assignable to type 'Partial<TState>'.

Kesalahan baru saja mulai terjadi setelah perubahan ini. Dalam versi 16.0.10 itu bekerja dengan baik.

TypeScript memiliki beberapa masalah terkait, mereka telah menutupnya sebagai bekerja seperti yang dirancang:

https://github.com/Microsoft/TypeScript/issues/19388

Saya membuat intisari dari beberapa contoh di sini: https://Gist.github.com/afarnsworth-valve/93d1096d1410b0f2efb2c94f86de9c84

Meski tingkahnya masih terlihat aneh. Secara khusus, Anda dapat menetapkan ke variabel yang diketik sebagai kelas dasar tanpa pemeran, dan kemudian melakukan panggilan yang sama dengan itu tanpa masalah. Masalah ini tampaknya menunjukkan penugasan dan perbandingan adalah dua hal yang berbeda.

Tidak melupakan kalian. Akan segera diperbaiki

PR yang dirujuk harus menyelesaikan masalah ini. Saya juga menambahkan tes yang seharusnya melindungi dari kerusakan casing tepi ini lagi.

@ericanderson Terima kasih atas tanggapan cepatnya :)

Apakah perbaikan baru memiliki batasan/kelemahan?

Tidak ada yang saya ketahui saat ini. Saya dapat menjaga intellisense (sebenarnya lebih baik dari sebelumnya, karena ini menyelesaikan beberapa kasus Edge).

Untuk menguraikan, solusi & Partial<S> adalah mengelabui intellisense untuk mengungkapkan kemungkinan params, tetapi ia melakukannya dengan menyatakan mereka X | undefined . Ini tentu saja akan gagal untuk dikompilasi tetapi agak membingungkan. Mendapatkan | S memperbaiki intellisense sehingga menyarankan semua parameter yang benar dan sekarang tidak menunjukkan tipe yang salah.

Saya mencoba mencari untuk menambahkan null sebagai nilai pengembalian yang mungkin dari panggilan balik setState (apa yang dapat Anda kembalikan ke status Anda tidak ingin mengubah status apa pun, tanpa membuat return; juga valid), tetapi itu malah membuat inferensi tipe benar-benar menyerah dan memilih never sebagai kuncinya

Untuk saat ini, dalam panggilan balik di mana saya benar-benar ingin melewati status pembaruan, saya telah menggunakan return null! . Tipe never dari pengembalian ini membuat TypeScript mengabaikan pengembalian untuk inferensi tipe generik.

Halo kawan-kawan...

Komit terakhir memperbaiki masalah saya.
Terima kasih atas respon cepatnya :)

Versi apa yang sudah diperbaiki? Apakah itu dipublikasikan ke npm? Saya menemukan kasus di mana versi panggilan balik dari setState memberi tahu saya bahwa ada ketidakcocokan properti dalam nilai pengembalian, seperti yang ditemukan @UselessPickles .

Harus bagus di @types/react terbaru

Saya memperbaikinya untuk seri 15 dan 16

import produce from 'immer';

interface IComponentState
{
    numberList: number[];
}

export class HomeComponent extends React.Component<ComponentProps, IComponentState>

Definisi tipe lama React..

// We MUST keep setState() as a unified signature because it allows proper checking of the method return type.
// See: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment-351013257
// Also, the ` | S` allows intellisense to not be dumbisense
setState<K extends keyof S>(
    state: ((prevState: Readonly<S>, props: P) => (Pick<S, K> | S)) | (Pick<S, K> | S),
    callback?: () => void
): void;

..menghasilkan kesalahan ini:

screen shot 2018-05-18 at 2 36 44 pm

Saya mencoba saran ini:

setState<K extends keyof S>(
    state:
        ((prevState: Readonly<S>, props: P) => (Partial<S> & Pick<S, K>))
        | (Partial<S> & Pick<S, K>),
    callback?: () => any
): void;

Intellisense sekarang dapat merasakan properti dari status React. Namun, properti yang dirasakan sekarang dianggap sebagai possibly undefined .

screen shot 2018-05-18 at 2 37 32 pm

Harus menggunakan operator pernyataan nol meskipun numberList tidak terdefinisi atau tidak dapat dibatalkan:

screen shot 2018-05-18 at 2 38 51 pm

Saya hanya akan tetap pada definisi tipe lama sampai penginderaan tipe ditingkatkan. Untuk sementara, saya hanya akan secara eksplisit menyatakan tipe pada parameter generik produk immer. produce<IComponentState> lebih mudah untuk dipikirkan daripada list! .

screen shot 2018-05-18 at 2 51 43 pm

Kesalahan pertama Anda adalah karena Anda tidak mengembalikan apa pun. Itu bukan cara kerja setState.

Ini berfungsi bahkan ketika tidak mengembalikan variabel dalam produksi. Saya baru saja mengikuti contoh di sini (non-TypeScript):

https://github.com/mweststrate/immer

onBirthDayClick2 = () => {
    this.setState(
        produce(draft => {
            draft.user.age += 1
            // no need to return draft
        })
    )
}

Satu-satunya hal yang mencegah TypeScript untuk dapat menjalankan kode itu adalah ia memiliki tipe yang salah disimpulkan dari definisi tipe React. Definisi tipe melaporkan kesalahan numberList does not exist on type Pick<IComponentState, never> . Hanya dapat membuat kesalahan kompilasi hilang dengan secara eksplisit meneruskan tipe pada parameter generik produk, yaitu produce<IComponentState> .

Saya bahkan mencoba mengembalikan variabel dalam produksi dan melihat apakah itu akan membantu definisi tipe React menyimpulkan tipe (masalah cewek-dan-telur), tetapi tetap tidak ada cara untuk definisi tipe React untuk mendeteksi tipe yang benar dari keadaan. Karenanya intellisense untuk draf tidak muncul:

screen shot 2018-05-18 at 10 38 04 pm

Atau mungkin saya memiliki harapan yang salah dari kompiler :) Kompilator tidak dapat membuat tipe untuk variabel draf berdasarkan tipe setState karena kompilator memproses kode dari dalam ke luar. Namun, definisi tipe yang disarankan entah bagaimana membuat saya berpikir bahwa kompiler dapat memproses kode dari luar ke dalam, bahwa ia dapat memilih tipe terbaik yang dapat diteruskan dari kode luar ( setState ) ke kode dalam ( produce ).

setState<K extends keyof S>(
    state:
        ((prevState: Readonly<S>, props: P) => (Partial<S> & Pick<S, K>))
        | (Partial<S> & Pick<S, K>),
    callback?: () => any
): void;

Dengan definisi tipe di atas, kompiler dapat mendeteksi bahwa draf memiliki properti numberList . Ini mendeteksinya sebagai possibly undefined meskipun:

image

Setelah mengutak-atik lebih lanjut, saya membuat kompiler dapat meneruskan tipe status untuk menghasilkan draf dengan menambahkan S ke definisi tipe setState:

setState<K extends keyof S>(
    state:
        ((prevState: Readonly<S>, props: P) => (Partial<S> & Pick<S, K> & S))
        | (Partial<S> & Pick<S, K>),
    callback?: () => any
): void;

Kode sedang dikompilasi sekarang :)

screen shot 2018-05-18 at 11 21 03 pm

Kesalahan Anda bukan dengan setState, kesalahan Anda ada di dalam produk. Apa definisi tipe Anda untuk produk?

PR saya di atas ada kesalahan pada skrip pengujian PastiTyped, saya tidak menguji secara lokal. Jadi saya mengujinya secara lokal sekarang.

Inilah definisi tipe immer/hasilkan.

/**
 * Immer takes a state, and runs a function against it.
 * That function can freely mutate the state, as it will create copies-on-write.
 * This means that the original state will stay unchanged, and once the function finishes, the modified state is returned.
 *
 * If the first argument is a function, this is interpreted as the recipe, and will create a curried function that will execute the recipe
 * any time it is called with the current state.
 *
 * <strong i="7">@param</strong> currentState - the state to start with
 * <strong i="8">@param</strong> recipe - function that receives a proxy of the current state as first argument and which can be freely modified
 * <strong i="9">@param</strong> initialState - if a curried function is created and this argument was given, it will be used as fallback if the curried function is called with a state of undefined
 * <strong i="10">@returns</strong> The next state: a new state, or the current state if nothing was modified
 */
export default function<S = any>(
    currentState: S,
    recipe: (this: S, draftState: S) => void | S
): S

// curried invocations with default initial state
// 0 additional arguments
export default function<S = any>(
    recipe: (this: S, draftState: S) => void | S,
    initialState: S
): (currentState: S | undefined) => S
// 1 additional argument of type A
export default function<S = any, A = any>(
    recipe: (this: S, draftState: S, a: A) => void | S,
    initialState: S
): (currentState: S | undefined, a: A) => S
// 2 additional arguments of types A and B
export default function<S = any, A = any, B = any>(
    recipe: (this: S, draftState: S, a: A, b: B) => void | S,
    initialState: S
): (currentState: S | undefined, a: A, b: B) => S
// 3 additional arguments of types A, B and C
export default function<S = any, A = any, B = any, C = any>(
    recipe: (this: S, draftState: S, a: A, b: B, c: C) => void | S,
    initialState: S
): (currentState: S | undefined, a: A, b: B, c: C) => S
// any number of additional arguments, but with loss of type safety
// this may be alleviated if "variadic kinds" makes it into Typescript:
// https://github.com/Microsoft/TypeScript/issues/5453
export default function<S = any>(
    recipe: (this: S, draftState: S, ...extraArgs: any[]) => void | S,
    initialState: S
): (currentState: S | undefined, ...extraArgs: any[]) => S

// curried invocations without default initial state
// 0 additional arguments
export default function<S = any>(
    recipe: (this: S, draftState: S) => void | S
): (currentState: S) => S
// 1 additional argument of type A
export default function<S = any, A = any>(
    recipe: (this: S, draftState: S, a: A) => void | S
): (currentState: S, a: A) => S
// 2 additional arguments of types A and B
export default function<S = any, A = any, B = any>(
    recipe: (this: S, draftState: S, a: A, b: B) => void | S
): (currentState: S, a: A, b: B) => S
// 3 additional arguments of types A, B and C
export default function<S = any, A = any, B = any, C = any>(
    recipe: (this: S, draftState: S, a: A, b: B, c: C) => void | S
): (currentState: S, a: A, b: B, c: C) => S
// any number of additional arguments, but with loss of type safety
// this may be alleviated if "variadic kinds" makes it into Typescript:
// https://github.com/Microsoft/TypeScript/issues/5453
export default function<S = any>(
    recipe: (this: S, draftState: S, ...extraArgs: any[]) => void | S
): (currentState: S, ...extraArgs: any[]) => S
/**
 * Automatically freezes any state trees generated by immer.
 * This protects against accidental modifications of the state tree outside of an immer function.
 * This comes with a performance impact, so it is recommended to disable this option in production.
 * It is by default enabled.
 */
export function setAutoFreeze(autoFreeze: boolean): void

/**
 * Manually override whether proxies should be used.
 * By default done by using feature detection
 */
export function setUseProxies(useProxies: boolean): void

@ericanderson bisakah Anda mengarahkan saya ke diskusi tentang mengapa Pick digunakan alih-alih Partial ? Ini telah menyebabkan saya berjam-jam berduka (menggunakan setState(obj) , bukan versi panggilan balik), dan untuk saat ini saya menggunakan this.setState(newState as State) sebagai solusinya. Saya hanya ingin mengerti mengapa itu diubah, karena saya pasti melewatkan sesuatu.

Halo @ericanderson ,

Saya memiliki beberapa masalah dengan definisi terbaru.

Kasus penggunaan saya secara singkat seperti ini:

interface AppState {
  valueA: string;
  valueB: string;
  // ... something else
} 
export default class App extends React.Component <{}, AppState> {
  onValueAChange (e:React.ChangeEvent<HTMLInputElement>) {
    const newState: Partial<AppState> = {valueA: e.target.value}
    if (this.shouldUpdateValueB()) {
      newState.valueB = e.target.value;
    }
    this.setState(newState); // <-- this leads to a compiling error
  }
  // ... other methods
}

Pesan kesalahannya seperti:

Argument of type 'Partial<AppState>' is not assignable to parameter of type 'AppState | ((prevState: Readonly<AppState>, props: {}) => AppState | Pick<AppState, "valueA" | "v...'.
  Type 'Partial<AppState>' is not assignable to type 'Pick<AppState, "valueA" | "valueB" | "somethingElse">'.
    Types of property 'valueA' are incompatible.
      Type 'string | undefined' is not assignable to type 'string'.
        Type 'undefined' is not assignable to type 'string'.

Tampaknya Partial<AppState> tidak kompatibel dengan tanda tangan setState . Tentu saja saya bisa menyelesaikan ini dengan mengetikkan pernyataan seperti

this.setState(newState as Pick<AppState, 'valueA' | 'valueB'>)

tapi itu tidak ideal, karena:
1) sintaks ini sangat verbose
2) yang lebih penting, jenis pernyataan dapat melanggar data saya yang sebenarnya. Misalnya, newState as Pick<AppState, 'somethingElse'> juga lolos pemeriksaan, meskipun tidak sesuai dengan data saya.

Saya pikir Sebagianentah bagaimana harus kompatibel dengan Pick, karena Parsialhanya mengambil jumlah kunci yang tidak pasti dari T. Saya tidak yakin pemahaman saya jika akurat. Bagaimanapun penggunaan yang ideal dari sudut pandang saya adalah saya dapat melewati variabel yang diketik Parsiallangsung ke setState.

Bisakah Anda mempertimbangkan saran saya atau menunjukkan kesalahpahaman saya jika ada? Terima kasih!

Ini adalah perubahan yang sangat lama. Jadi kecuali orang lain baru saja mengubah ini, Anda mungkin menggonggong pohon yang salah.

Yang mengatakan. Parsial memungkinkan nilai yang tidak ditentukan.

const a : Parsial<{foo: string}> = { foo: undefined }

a valid tetapi jelas hasil pembaruan ke status Anda membuat status Anda dengan foo tidak terdefinisi meskipun Anda menyatakan itu tidak mungkin.

Oleh karena itu sebagian tidak dapat ditetapkan ke Pick. Dan Pick adalah jawaban yang tepat untuk memastikan tipe Anda tidak berbohong

Saya merasa tidak mengizinkan:

setState((prevState) => {
  if (prevState.xyz) {
    return { foo: "" };
  }
  return { bar: "" };
});

super restriktif.

Solusi @Kovensky adalah satu-satunya solusi yang masuk akal yang saya ketahui, tetapi masih menyakitkan untuk ditulis.

Apakah ada yang bisa dilakukan untuk mendukung pola yang cukup umum ini (menurut saya)?

Satu-satunya hal yang bisa dilakukan adalah menghapus typesafety

Adakah yang bisa menjelaskan alasan Pick<S, K> | S | null ?

        setState<K extends keyof S>(
            state: ((prevState: Readonly<S>, props: Readonly<P>) => (Pick<S, K> | S | null)) | (Pick<S, K> | S | null),
            callback?: () => void
        ): void;

Tbh Saya bahkan tidak tahu sekarang mengapa tanda tangan di atas bahkan berfungsi untuk pembaruan status sebagian karena K didefinisikan sebagai keyof S jadi Pick<S, K> pada dasarnya membuat ulang S ?

bukankah seharusnya Partial<S> | null melakukan pekerjaan itu juga?

        setState(
            state: ((prevState: Readonly<S>, props: Readonly<P>) => (Partial<S> | null)) | (Partial<S> | null),
            callback?: () => void
        ): void;

Adakah yang bisa menjelaskan...

Sudah dijelaskan dengan jelas beberapa balasan up.

setState((prevState) => {
  if (prevState.xyz) {
    return { foo: "" };
  }
  return { bar: "" };
});

super restriktif.

Solusi @Kovensky adalah satu-satunya solusi yang masuk akal yang saya ketahui, tetapi masih menyakitkan untuk ditulis.

Saya baru saja menemukan masalah yang tepat ini, tetapi saya tidak dapat melihat referensi ke Kovensky di utas (mungkin seseorang mengubah nama pengguna mereka?). Adakah yang bisa mengarahkan saya ke solusi yang direkomendasikan saat ini?

@timrobinson33 Komentar ini menjelaskan solusinya https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment -351884578

Dan untungnya kita tidak perlu khawatir tentang ini lagi dengan kait

@timrobinson33 Komentar ini menjelaskan solusi #18365 (komentar)

Terima kasih banyak untuk itu. Pada akhirnya saya pikir kode saya terlihat lebih bagus karena beberapa panggilan setState kecil dengan pernyataan If luarnya, meskipun ini berarti beberapa jalur akan memanggil setState lebih dari sekali.

Saya kira ini sebenarnya mirip dengan cara kami bekerja dengan kait, melihat keadaan sebagai beberapa hal kecil yang kami perbarui secara mandiri.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat
bleepcoder.com menggunakan informasi GitHub berlisensi publik untuk menyediakan solusi bagi pengembang di seluruh dunia untuk masalah mereka. Kami tidak berafiliasi dengan GitHub, Inc. atau dengan pengembang mana pun yang menggunakan GitHub untuk proyek mereka. Kami tidak meng-host video atau gambar apa pun di server kami. Semua hak milik masing-masing pemiliknya.
Sumber untuk halaman ini: Sumber

Bahasa pemrograman populer
Lebih banyak proyek GitHub

© 2024 bleepcoder.com - Contact
Made with in the Dominican Republic.
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.