Language-tools: TypeScript salah menganggap variabel sebagai kemungkinan nihil di dalam kondisi yang memeriksa variabel

Dibuat pada 18 Okt 2020  ·  20Komentar  ·  Sumber: sveltejs/language-tools

Jelaskan bugnya
Kesalahan TypeScript "Objek mungkin nihil" ditampilkan untuk pemeriksaan bersyarat bersarang.

Untuk Mereproduksi
Kasus uji sederhana:

<script lang="typescript">
    interface TestObject {
        author?: string;
    }
    export let test: TestObject | null;
</script>

{#if test}
    <div>
        {#if test.author}
            <div>Author: {test.author}</div>
        {/if}
    </div>
{/if}

TypeScript melempar kesalahan Object is possibly null pada baris {#if test.author} , tidak secara khusus pada test di dalam baris itu.

Perilaku yang diharapkan
Diharapkan tidak membuang kesalahan apapun.

Sistem (lengkapi informasi berikut):

  • OS: OSX 10.15.7
  • IDE: VSCode
  • Plugin / Paket: "Svelte untuk VSCode"

Konteks tambahan
Jika pernyataan bersyarat bersarang dihapus dan di antarmuka TestObject author? diganti dengan author (untuk membuatnya diperlukan), akan logis jika TypeScript membuat kesalahan yang sama untuk {test.author} , tapi ternyata tidak. Jadi sepertinya kesalahan dipicu oleh pernyataan bersyarat bersarang, tanpa itu TypeScript tahu bahwa test bukan null.

Fixed bug

Komentar yang paling membantu

Pembaruan / ringkasan kecil:

  • aliran kontrol disetel ulang segera setelah kode berada di dalam salah satu konstruksi berikut: #await , #each , let:X . Ini karena cara kita harus mengubah kode. Kami belum melupakan masalah ini dan masih mencari cara untuk menyelesaikannya dengan cara yang baik.
  • Jika-kondisi bersarang sekarang harus bekerja seperti yang diharapkan selama mereka tidak terganggu oleh salah satu hal yang disebutkan di poin pertama

Semua 20 komentar

Tidak tahu apa yang bisa kami lakukan untuk menghadapinya. svelte2tsx mengubahnya menjadi ini:

() => (<>

{() => {if (test){<>
    <div>
        {() => {if (test.author){<>
            <div>Author: {test.author}</div>
        </>}}}
    </div>
</>}}}</>);

karena digabungkan dalam fungsi sehingga analisis jenis aliran kontrol tidak akan diterapkan. Dan kita tidak dapat menghapus fungsi atau digabungkan ke dalam fungsi yang segera dipanggil karena ekspresi akan dijalankan kembali ketika ketergantungannya dihentikan, kita pasti perlu membatalkan beberapa tipe aliran kontrol.

Bisakah kita mengubahnya menjadi terner? Jika tidak ada blok lain kita hanya menggunakan string kosong untuk kasus lain.
Tetapi masalah yang lebih besar mungkin adalah kita tidak diizinkan menggunakan fungsi seperti itu dalam transformasi mana pun - tidak yakin apakah itu masalahnya sekarang.

Mungkin ijinkan! sintaksis? {#if test!.author} . Itu sintaks TypeScript yang valid, tetapi saat ini tidak dapat digunakan dalam template.

Saat ini Anda tidak dapat menggunakan skrip ketikan di markup. Tidak ada cara untuk memproses markup atau membiarkan compiler mengurai sintaks skrip ketikan. Jadi pernyataan non-null tidak dimungkinkan.
Belum mencoba viability buy mungkin kita bisa menyalin kondisi level atas dan menambahkannya sebelum kondisi bersarang

 {() => {if (test && test.author){<>
            <div>Author: {test.author}</div>
        </>}}}

Solusi sementara:

<script lang="typescript">
    interface TestObject {
        author?: string;
    }
    export let test: TestObject | null;

    let requitedTest: Required<TestObject>;
    $: {
        requiredTest = test as unknown as Required<TestObject>;
    }
</script>

{#if test}
    <div>
        {#if requiredTest.author}
            <div>Author: {requiredTest.author}</div>
        {/if}
    </div>
{/if}

Menyalin variabel, menetapkan jenis yang berbeda dan menggunakannya dalam kondisi bertingkat. Itu jelek dan perlu digunakan dengan hati-hati, tapi itu menghilangkan peringatan.

Solusi yang lebih pragmatis untuk saat ini adalah

{#if test}
    <div>
        {#if test?.author}
            <div>Author: {test.author}</div>
        {/if}
    </div>
{/if}

Ini cukup mengganggu, terutama ketika tidak hanya mengebor di dalam objek yang sama tetapi membuat kondisi terpisah:

{#if bigSwitch}
    <div>
        {#if smallSwitch}
            <MyComponent {bigSwitch} />
        {/if}
    </div>
{/if}

Itu tidak akan berhasil. TypeScript tidak diperbolehkan dalam template.

Sepertinya bekerja di sini. test && test.author lalu.

jika yang Anda maksud adalah rangkaian opsional, itu adalah fitur javascript baru, bukan hanya fitur skrip. Ini tersedia di langsing setelah 3.24.0

Terima kasih! Saya selalu berasumsi bahwa itu adalah fitur TS yang mirip dengan pernyataan non-null foo! . Itu membuatnya lebih sederhana.

Selalu belajar sesuatu yang baru :)

Belum mencoba viability buy mungkin kita bisa menyalin kondisi level atas dan menambahkannya sebelum kondisi bersarang

 {() => {if (test && test.author){<>
          <div>Author: {test.author}</div>
      </>}}}

Saya melakukan beberapa tes ke arah itu dan saya pikir ini tidak akan berhasil untuk semua kasus. Ini mungkin membungkam kesalahan untuk #if , tetapi kesalahan akan muncul kembali segera setelah Anda menggunakan blok #each atau #await di dalamnya. Kesalahan ini tidak akan muncul jika variabel adalah const , karena aliran kontrol TS kemudian mengetahui bahwa kondisi harus tetap benar karena variabel tidak dapat ditetapkan kembali. Jadi cara terbaik untuk menyelesaikan ini adalah dengan mengubah semuanya menjadi const variabel, atau menetapkan kembali semua variabel ke variabel sementara lainnya untuk template. Itu akan membawa masalah tersendiri meskipun untuk info hover, rename, go-to-declaration, dll. Mungkin itu bisa diselesaikan dengan menggunakan ekspresi koma dan mengubah {#if foo} menjadi {#if (foo, generatedConstFoo)} . Intinya: ini akan membutuhkan cukup banyak peretasan.

Mungkin "tambahkan blok-if tingkat atas" -semuanya benar. Tidaklah aman untuk mengasumsikan bahwa kondisi masih benar di dalam setiap blok / await. Misalnya, ini akan memunculkan error runtime ( toUpperCase is not a function ):

<script>
    let name = 'name';
    function setNameToNumber(){
        name = 1
        return ['a']
    }
</script>

{#if name}
    <h1>Hello {name.toUpperCase()}!</h1>
    {#each setNameToNumber() as item}
        item
    {/each}
{/if}

Hal yang sama dapat dibangun untuk blok menunggu.

Pembaruan / ringkasan kecil:

  • aliran kontrol disetel ulang segera setelah kode berada di dalam salah satu konstruksi berikut: #await , #each , let:X . Ini karena cara kita harus mengubah kode. Kami belum melupakan masalah ini dan masih mencari cara untuk menyelesaikannya dengan cara yang baik.
  • Jika-kondisi bersarang sekarang harus bekerja seperti yang diharapkan selama mereka tidak terganggu oleh salah satu hal yang disebutkan di poin pertama

Saat ini Anda tidak dapat menggunakan skrip ketikan di markup. Tidak ada cara untuk memproses markup atau membiarkan compiler mengurai sintaks skrip ketikan. Jadi pernyataan non-null tidak dimungkinkan.
Belum mencoba viability buy mungkin kita bisa menyalin kondisi level atas dan menambahkannya sebelum kondisi bersarang

 {() => {if (test && test.author){<>
          <div>Author: {test.author}</div>
      </>}}}

Sekarang # 493 telah diperbaiki dengan cara yang menyatakan $store -variabel, kita dapat meninjau kembali gagasan ini. Ide saya adalah menambahkan semua if-condition seperti ini ( #each sebagai contoh):

Di

{#if somecondition && anothercondition}
   {#each ..}
     ..
   {/each}
{/if}

Di luar

<>{__sveltets_each((true, items), (item) => (somecondition && anothercondition) && <>
    ...
</>)}</>

Bagian yang sulit adalah mendapatkan elseif / else dan logika bersarang dengan benar.

Ini harus diperbaiki dengan VS Code versi 104.6.3 / svelte-check versi 1.2.4. Tolong beri tahu saya jika ini berhasil untuk Anda sekarang.

Saya menemukan masalah - Ini berfungsi dengan benar di dalam template sekarang, tetapi tidak dalam event listener. Saya tidak yakin apakah saya harus membuka masalah baru untuk itu, tetapi inilah repro:

<script lang="ts">
  let value: string | null = null;

  function acceptsString(value: string) {
    console.log(value);
  }
</script>

{#if value}
  <!-- Argument of type 'string | null' is not assignable to parameter of type 'string'.
  Type 'null' is not assignable to type 'string'. -->
  <button on:click={() => acceptsString(value)} />
{/if}

Saya menemukan masalah - Ini berfungsi dengan benar di dalam template sekarang, tetapi tidak dalam event listener. Saya tidak yakin apakah saya harus membuka masalah baru untuk itu, tetapi inilah repro:

<script lang="ts">
  let value: string | null = null;

  function acceptsString(value: string) {
    console.log(value);
  }
</script>

{#if value}
  <!-- Argument of type 'string | null' is not assignable to parameter of type 'string'.
  Type 'null' is not assignable to type 'string'. -->
  <button on:click={() => acceptsString(value)} />
{/if}

Itu hanya perilaku TS biasa. Nilai Anda direferensikan dalam fungsi yang dapat dipanggil kapan saja di masa mendatang di mana nilainya mungkin kembali ke nol.

Keduanya adalah pandangan yang valid. Seseorang dapat mengatakan "karena value bukan konstanta, ia dapat berubah", tetapi di sisi lain orang juga dapat mengatakan "Saya tidak pernah sampai ke penangan klik itu jika value adalah null ". Rumit ..

Saya akan membuka masalah lain untuk itu sehingga kita dapat membahas ini secara terpisah.

Saya rasa tidak ada gunanya jika Anda mendiskusikan pilihan lama TS sendiri: p
Bukankah tombol akan bertahan dengan nilai === null jika ada transisi keluar di tingkat mereka atau orang tua misalnya?

Sebenarnya aliran kontrol sudah benar sebelum perubahan santai ini karena seseorang dapat memodifikasi variabel lain dalam setiap loop atau menunggu.

<script lang="ts">
  let foo: string | null = 'bar';
  function getItems() {
     foo = null;
     return [''];
  }
</script>

{#if foo}
  {#each getItems() as item}
    {foo.toUpperCase()} <!-- boom -->
  {/each}
{/if}

.. tapi siapa yang melakukan ini? Anda juga dapat membuat kesalahan runtime yang sama di TS, btw. Jadi ini lebih merupakan tradeoff di DX daripada "ini 100% benar".

Tetapi sebelum kita membahas ini lebih lanjut di sini, harap bawa diskusi ini ke # 876 😄

Apakah halaman ini membantu?
0 / 5 - 0 peringkat