Language-tools: TypeScript неправильно считает переменную, возможно, нулевой внутри условия, которое проверяет переменную

Созданный на 18 окт. 2020  ·  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 для 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, мы просто используем пустую строку для случая 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 тогда.

если вы имеете в виду необязательную цепочку, это новая функция javascript, а не просто функция машинописного текста. Он доступен в 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-блоки верхнего уровня" все-таки правильно. Небезопасно предполагать, что условия все еще верны внутри каждого блока / await. Например, это вызовет ошибку выполнения ( 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 -переменные, мы можем вернуться к этой идее. Моя идея состоит в том, чтобы добавить все такие if-условия (например, #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, если, например, есть переход out на их или их родительском уровне?

Строго говоря, поток управления был правильным до этих расслабляющих изменений, потому что можно было изменять другие переменные в каждом цикле или ожидании.

<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 рейтинги