Language-tools: يعتبر TypeScript بشكل غير صحيح أن المتغير قد يكون فارغًا داخل الشرط الذي يتحقق من المتغير

تم إنشاؤها على ١٨ أكتوبر ٢٠٢٠  ·  20تعليقات  ·  مصدر: sveltejs/language-tools

صف الخلل
يظهر خطأ TypeScript "ربما يكون الكائن فارغًا" لعمليات التحقق الشرطية المتداخلة.

لإعادة إنتاج
حالة اختبار بسيطة:

<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 بإلقاء الخطأ Object is possibly null على السطر {#if test.author} ، وليس تحديدًا على test داخل هذا السطر.

سلوك متوقع
من المتوقع عدم إلقاء أي أخطاء.

النظام (يرجى استكمال المعلومات التالية):

  • نظام التشغيل: OSX 10.15.7
  • IDE: VSCode
  • المكون الإضافي / الحزمة: "Svelte for VSCode"

سياق إضافي
إذا تمت إزالة العبارة الشرطية المتداخلة وفي واجهة TestObject author? تم استبدالها بـ author (لجعلها مطلوبة) ، سيكون من المنطقي أن تقوم TypeScript بإلقاء نفس الخطأ لـ {test.author} ، لكنها لا تفعل ذلك. لذا يبدو أنه تم تشغيل الخطأ بواسطة عبارة شرطية متداخلة ، وبدون ذلك يعرف TypeScript أن test ليس فارغًا.

Fixed bug

التعليق الأكثر فائدة

تحديث / ملخص صغير:

  • تتم إعادة ضبط تدفق التحكم بمجرد أن يكون الرمز داخل أحد هذه التركيبات: #await ، #each ، let:X . هذا بسبب الطريقة التي يجب أن نحول بها الكود. لم ننس هذه المشكلة وما زلنا نبحث عن طرق لحلها بطريقة جيدة.
  • يجب أن تعمل شروط if المتداخلة الآن كما هو متوقع طالما لم يتم مقاطعتها بواسطة أحد الأشياء المذكورة في النقطة الأولى

ال 20 كومينتر

لا أعرف ماذا يمكننا أن نفعل للتعامل معها. svelte2tsx حوله إلى هذا:

() => (<>

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

لأنه ملفوف في دالة بحيث لا يدخل تحليل نوع تدفق التحكم حيز التنفيذ. ولا يمكننا إزالة الدالة أو لفها في دالة تم استدعاؤها فورًا لأن التعبير سيعاد تشغيله عندما يتم تجميع التبعية ، نحتاج بالتأكيد إلى إبطال بعض أنواع تدفق التحكم.

هل يمكننا تحويل هذا إلى ثلاثيات؟ إذا لم يكن هناك كتلة أخرى ، فإننا نستخدم سلسلة فارغة لحالة else.
لكن المشكلة الأكبر قد تكون أنه لا يُسمح لنا باستخدام وظائف مثل هذه في أي من التحولات - لست متأكدًا مما إذا كان هذا هو الحال الآن

ربما تسمح! بناء الجملة؟ {#if test!.author} . هذا بناء جملة صالح لـ TypeScript ، لكن لا يمكن استخدامه الآن في القالب.

الآن لا يمكنك استخدام الكتابة المطبوعة في الترميز. لا توجد طريقة للمعالجة المسبقة للترميز أو السماح للمترجم بتحليل بناء الجملة المطبوع. لذا فإن التأكيد غير الفارغ غير ممكن.
لم تجرب شراء الصلاحية ، فربما يمكننا نسخ حالة المستوى الأعلى وإلحاقها قبل الحالة المتداخلة

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

الحل المؤقت:

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

نسخ المتغير وتعيين نوع مختلف واستخدامه في حالة متداخلة. إنه قبيح ويجب استخدامه بعناية ، لكنه يخلصك من التحذيرات.

سيكون الحل الأكثر واقعية في الوقت الحالي هو

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

إنه أمر مزعج للغاية ، خاصة عندما لا يقتصر الأمر على الحفر داخل نفس الكائن ولكن في وضع شروط منفصلة:

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

هذا لن ينجح. TypeScript غير مسموح به في القوالب.

يبدو أنه يعمل هنا. test && test.author .

إذا كنت تقصد تسلسلًا اختياريًا ، فهذه ميزة جافا سكريبت جديدة ، وليست مجرد ميزة مطبوعة. إنه متاح في svelte بعد 3.24.0

شكرا! لطالما افترضت أنها ميزة TS مشابهة للبيان غير الفارغ foo! . هذا يجعلها أبسط بكثير.

تعلم شيئًا جديدًا دائمًا :)

لم تجرب شراء الصلاحية ، فربما يمكننا نسخ حالة المستوى الأعلى وإلحاقها قبل الحالة المتداخلة

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

لقد أجريت بعض الاختبارات في هذا الاتجاه وأعتقد أن هذا لن ينجح في جميع الحالات. قد يسكت أخطاء #if ، لكنها ستظهر مرة أخرى بمجرد استخدام كتلة #each أو #await بداخلها. لن تظهر هذه الأخطاء إذا كان المتغير const ، لأن تدفق التحكم TS يعرف أن الشرط يجب أن يظل صحيحًا لأنه لا يمكن إعادة تعيين المتغير. لذا فإن أفضل طريقة لحل هذه المشكلة هي تحويل كل شيء إلى متغيرات const ، أو إعادة تعيين جميع المتغيرات إلى متغيرات أخرى مؤقتة للقالب. سيؤدي ذلك إلى ظهور مشكلاته الخاصة على الرغم من معلومات التمرير وإعادة التسمية والإعلان عن الانتقال وما إلى ذلك. ربما يمكن حل ذلك باستخدام تعبيرات الفاصلة وتحويل {#if foo} إلى {#if (foo, generatedConstFoo)} . الخلاصة: سيتطلب هذا بعض القرصنة.

ربما يكون "إلحاق كتل if-المستوى الأعلى" - كل شيء صحيحًا بعد كل شيء. ليس من الآمن افتراض أن الظروف لا تزال صحيحة داخل كل كتلة انتظار. على سبيل المثال ، سيؤدي هذا إلى ظهور خطأ في وقت التشغيل ( 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}

يمكن إنشاء نفس الشيء لكتلة انتظار.

تحديث / ملخص صغير:

  • تتم إعادة ضبط تدفق التحكم بمجرد أن يكون الرمز داخل أحد هذه التركيبات: #await ، #each ، let:X . هذا بسبب الطريقة التي يجب أن نحول بها الكود. لم ننس هذه المشكلة وما زلنا نبحث عن طرق لحلها بطريقة جيدة.
  • يجب أن تعمل شروط if المتداخلة الآن كما هو متوقع طالما لم يتم مقاطعتها بواسطة أحد الأشياء المذكورة في النقطة الأولى

الآن لا يمكنك استخدام الكتابة المطبوعة في الترميز. لا توجد طريقة للمعالجة المسبقة للترميز أو السماح للمترجم بتحليل بناء الجملة المطبوع. لذا فإن التأكيد غير الفارغ غير ممكن.
لم تجرب شراء الصلاحية ، فربما يمكننا نسخ حالة المستوى الأعلى وإلحاقها قبل الحالة المتداخلة

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

الآن بعد أن تم إصلاح # 493 بطريقة توضح متغيرات $store -المتغيرات ، يمكننا إعادة النظر في هذه الفكرة. فكرتي هي إلحاق جميع شروط الشرط السابقة مثل هذا ( #each كمثال):

في

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

خارج

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

الجزء الصعب هو الحصول على elseif / else ومنطق التداخل بشكل صحيح.

يجب إصلاح ذلك بإصدار VS Code 104.6.3 / svelte-check الإصدار 1.2.4. واسمحوا لي أن أعرف إذا كان يعمل من أجلك الآن.

لقد وجدت مشكلة - هذا يعمل بشكل صحيح داخل القوالب الآن ، ولكن ليس في مستمعي الأحداث. لست متأكدًا مما إذا كان يجب أن أفتح مشكلة جديدة لها ، ولكن إليك إعادة:

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

لقد وجدت مشكلة - هذا يعمل بشكل صحيح داخل القوالب الآن ، ولكن ليس في مستمعي الأحداث. لست متأكدًا مما إذا كان يجب أن أفتح مشكلة جديدة لها ، ولكن إليك إعادة:

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

هذا مجرد سلوك TS المعتاد. تتم الإشارة إلى القيمة الخاصة بك في دالة يمكن استدعاؤها في أي وقت في المستقبل حيث قد تعود القيمة إلى الصفر.

كلاهما وجهات نظر صحيحة. يمكن للمرء أن يقول "نظرًا لأن value ليس ثابتًا ، فقد يتغير" ، ولكن من ناحية أخرى يمكن للمرء أن يقول أيضًا "لم أصل إلى معالج النقرات هذا إذا كان value هو null ". مخادع ..

سأفتح مشكلة مختلفة لذلك حتى نتمكن من مناقشة هذا بشكل منفصل.

لا أعتقد أن الأمر يستحق وقتك لمناقشة الخيارات القديمة الخاصة بـ TS: p
ألن يستمر الزر بالقيمة === null إذا كان هناك انتقال إلى الخارج على مستوىهم أو مستوى آبائهم على سبيل المثال؟

بالمعنى الدقيق للكلمة ، كان تدفق التحكم صحيحًا قبل هذه التغييرات المريحة لأنه يمكن للمرء تعديل المتغيرات الأخرى داخل كل حلقة أو انتظار.

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

.. ولكن من يفعل هذا؟ يمكنك اختلاق نفس أخطاء وقت التشغيل في TS أيضًا ، راجع للشغل. لذا فهي عبارة عن مقايضة في DX أكثر من "هذا صحيح بنسبة 100٪".

ولكن قبل أن نناقش هذا الأمر أكثر هنا ، يرجى أخذ هذه المناقشة إلى # 876 😄

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات