Опишите ошибку
Ошибка 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
внутри этой строки.
Ожидаемое поведение
Ожидается, что никаких ошибок не будет.
Система (пожалуйста, заполните следующую информацию):
Дополнительный контекст
Если вложенный условный оператор удаляется и в интерфейсе TestObject author?
заменяется на author
(чтобы сделать это обязательным), для TypeScript было бы логично выдать ту же ошибку для {test.author}
, но это не так. Похоже, что ошибка вызвана вложенным условным оператором, без которого TypeScript знает, что test
не равно нулю.
Не знаю, что мы можем сделать, чтобы с этим справиться. 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 (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 😄
Самый полезный комментарий
Небольшое обновление / резюме:
#await
,#each
,let:X
. Это связано с тем, как нам нужно преобразовать код. Мы не забыли об этой проблеме и все еще ищем способы решить ее.