Комментарии 55
Уж кому как ни reddit стоит рассказывать об успехах frontend разработки :)
В тему frontednt,
а в reddit действительно нет пред просмотра комментариев или я что-то упускаю?
Мобильная версия у них вроде ничего.
Но с десктопа на нее не попасть, редиректит на большую.
— Flow лучше в этом и в этом, у TypeScript явных преимуществ как бы нет… Но все используют TypeScript, поэтому какого черта?! Ребята, будем использовать TypeScript!
Ну еще тулинг.
Смотрел, TypeScript — не понравилось. Пишем на ES2016. Команде нравится.
Что касамо типизации для входящих данных, то написал небольшой класс, который типизирует данные для последующей передачи в класс, валидатор или для сохранения. Хватает за глаза и незачем тянуть еще кучу всего. Что касамо чтения кода — то в команде договоренность — писать JSDoc для методов и классов.
И добавлю что перешли на async/await и код реально упростился и стал очень простым и читаемым.
Как будто хайп это что-то плохое — это означает комьюнити, библиотеки о чем автор в статьи таки сказал.
Так как TypeScript — компилируемый язык, с его помощью можно создавать типы, которые определяются во время выполнения программы.
Что это за типы, определяемые во время выполнения? Насколько мне известно, типы выводятся на этапе транспиляции.
Так как TypeScript — компилируемый язык, с его помощью можно создавать типы, которые определяются во время выполнения программы.
TypeScript не компилириуемый, а транслируемый. Кроме того вся информацию о типах на этапе трансляции удаляется. Итоговый JavaScript ничего о типах не знает.
Одно другому не мешает: компиляция есть разновидность трансляции. Раньше считалось что компиляция — это такая трансляция когда на выходе — машинные коды, но появление JVM, CLR и LLVM значительно размыли понятие компилятора.
Кроме того вся информацию о типах на этапе трансляции удаляется. Итоговый JavaScript ничего о типах не знает.
Смотря о каких. Автор же приводит конкретный пример: перечисления. Они остаются в итоговом Javascript.
Знает, через рефлексию. Специальный флаг компилятора позволяет добавлять немного информации о типах в рантайм. Довольно куцую информацию правда.
Утиная типизация же)
В typescript учебниках пишут об этом.
Все верно, просто структурная типизация не сразу в голове укладывается, вот люди и недоумевают.
К сожалению, смесь двух видов типизации до добра не доводит:
class A { foo: string }
class B { bar: string }
function test(x : A | B) {
if (x instanceof A) {
console.log(x.foo);
} else {
console.log(x.bar);
}
}
test({ foo: "Hello, world" }); // undefined
Компилятор мог бы заметить ошибку при проверке типов — но не заметил.
Дело в том, что такие проверки через instanceof
бессмысленны, так как проверяется не номинальное наследование, а наличие в цепочке прототипа конструктора. Так как A.prototype
не находится в цепочке прототипов {foo: '123'}
(что абсолютно верно), то выполняется else
-ветка.
Далее из-за, опять-таки, структурной типизации интерфейс класса A
идентичен интерфейсу { foo: string }
, поэтому test
в состоянии принять такой объект вместо инстанса.
Решением будет вместо обычного юниона использовать discriminated с общим ключом.
UPD: Номинальные типы пока на стадии обсуждения
Решение-то я знаю, но как заставить компилятор выругаться на некорректный код?
Можно вот так:
class A {
private __nominal_A: A; //или что еще покруче
foo: string;
}
Тогда в test
ничего не запихнуть, кроме new A()
, но как по мне лучше перестать пытаться писать на TS как на C#.
interface Object { __proto__ : this }
class A extends Object { foo: string }
class B extends Object { bar: string }
class C { foo: string }
class D extends Object { foo: string }
class E extends A {}
function test(x : A | B) {
if (x instanceof A) {
console.log(x.foo);
} else {
console.log(x.bar);
}
}
test( new C ); // error
test( new D ); // ok
test( new E ); // ok
test({ foo: "Hello, world" }); // error
Никакой смеси нет. В тайпскрипте только структурный сабтайпинг (на данный момент). В вашем примере по факту, баг occurence typing'а — чекер не должен уметь выводить тип B для х в негативной ветке, только А в позитивной. Впрочем, это могло быть сделано и специально (правильное поведение вынудило бы переписывать ооочень много подобных кейзов).
Почему не должен? Type narrowing вполне себе работает. Если ввести в юнион третий тип, например
class A { foo: string }
class B { bar: string }
class C { foobar: string }
function test(x : A | B | C) {
if (x instanceof A) {
console.log(x.foo);
} else {
console.log(x.bar); //error
}
}
то будет ошибка, так как bar
не существует в оставшемся юнионе B | C
От того что ошибку замаскировали, лучше не стало. Потому что на самом деле в ветке else должно было остаться объединение A | B | C.
С какой стати? instanceof type guards
Это не ошибка, а ожидаемое поведение при структурной типизации.
Потому что такая семантика у instanceof. Из x instanceof A следует, что в х есть поля А, но из того факта, что !(x instanceof A) не следует, что в х нет полей А, иными словами, у нас нету никаких корректных утверждений о типе значения в негативной ветке. Мы не можем утверждать, что в негативной ветке у нас тип !A, а значит, и не можем утверждать, что он A | B — A = B. По-этому narrowing должен быть только в позитивных ветках.
Из x instanceof A следует, что в х есть поля А, но из того факта, что !(x instanceof A) не следует, что в х нет полей А
Из этого следует, что в цепочке прототипов x
есть A.prototype
, то есть у x
есть поля, перечисленные именно в A. В негативную ветку тогда попадает утверждение, что в цепочке прототипов x
нет A.prototype
, соответственно в x
нет и полей, перечисленных именно в A конкретно для этой ветки. Хотя, если юнион включает в себя A
и что-то "еще", то при разнице останется это "еще", даже если оно включает в себя поля из A
(повторяющиеся).
Не понимаю, почему это вызывает столько вопросов, объясните, пожалуйста, подробнее?
Посмотрите еще раз на мой пример. Прототипа — нет, а поля там только из A, полей из B — нет.
Ну так это же другая проблема. instanceof
сам по себе не проверяет на структурное соответствие, он проверяет на наличие конструктора в прототипе. Соответствие выводится из того, что у вас юнион состоит из классов и вы используете instanceof, проверяющий как раз конструктор. Но я уже написал, что лучше instanceof вообще не использовать, так как снаружи вам всегда может прилететь объект такой же структуры, но не несущий в прототипе нужный конструктор.
Так о том и речь. Из положительной проверки instanceof следует структурное соответствие, а из отрицательной не следует ничего. В языке же почему-то из отрицательной проверки следует отсутствие структурного соответствия...
Эм. Если у меня юнион из 3х классов, значит объект с типом этого юниона содержит в цепочке какой-то из прототитипов этих конструкторов. Если он точно не содержит один из них (instanceof провалился), почему из этого не следует, что в негативной ветке он уже "содержит один из прототипов оставшихся конструкторов"?
Я правильно понимаю, что вы ведете к тому, что, будь в TS номинальная типизация, то можно было бы не беспокоиться внутри test
о том, что снаружи прилетит объект с той же структурой но без нужного прототипа?
Я не "веду к этому", я об этом сказал в первом же сообщении.
Если мы разрешаем передачу { foo: "Hello, world!" }
как значения типа A — то нельзя говорить о том, что в значении типа A | B есть хоть какие-то прототип и конструктор!
Но ведь это не совсем так… Мы разрешаем передачу объектов как значения интерфейса A
, тогда как instanceof
проверяет не интерфейс, а конструктор (то есть реальное js-значение — функцию).
И, понятно, что мы не можем использовать само значение для указания типа аргумента, только его интерфейс (который просто автоматически выводится).
Более того, если вы в юнион вместо классов объедините интерфейсы, вам даже instanceof выполнить не дадут.
Так в том-то и проблема, что из несоответствия конструктора не следует несоответствие интерфейса!
Правильно, но инстанс конструктора A
имеет интерфейс A
, но не всякий объект с интерфейсом A является инстансом конструктора A
.
Тут проблема несколько иная. x instaceof A
вам гарантирует наличие нужных полей, если в цепочке есть прототип A
. Более того, можно instanceof
использовать на любом object type
для любого конструктора, даже не из юниона, тогда тип в ветке условия будет расширен прототипом дополнительного конструктора:
class A { foo: string }
class B { bar: string }
function test(x: B) {
if (x instanceof A) {
x.bar; //ok
x.foo; //ok
}
}
С другой стороны, интерфейс B
(а в аргументах идет именно интерфейс, а не конструктор) не гарантирует наличия конструктора B
в прототипе, а лишь гарантирует наличие полей из B
где-то, либо на инстансе, либо в прототипе.
Из всего этого вытекает, что, даже если вы принимаете в аргументах объект с интерфейсом B
, вы не можете гарантировать, что где-то в прототипе этого объекта не сидит конструктор A
, и, соответственно, что нельзя применять instanceof A
для объекта с интерфейсом B
. Именно поэтому тайпчекер разрешает использовать классы не из юниона в instanceof
.
Но если интерфейс A
объединен в юнион с интерфейсом B
, то в ветке x instanceof A
вы уже рассмотрели интересующий вас случай, когда вам нужны поля из A
. А если это произошло, то можно спокойно "вычитать" из юниона поля из A
.
Но если интерфейс A объединен в юнион с интерфейсом B, то в ветке x instanceof A вы уже рассмотрели интересующий вас случай, когда вам нужны поля из A. А если это произошло, то можно спокойно "вычитать" из юниона поля из A.
Нет, в ветке x instanceof A
был рассмотрен только случай совпадения прототипа. Но остался случай когда поля из A есть — а прототипа A нету.
Ну так если в аргументах B
как выше, то в else
и будет только B
, так как ситуация, когда прототипа A
нет, а поля есть, запрещена типом аргумента.
А если там A | B
, то будет вычет, так как A | B
подразумевает наличие полей. В этом месте нет проблемы, код корректный и сужение типа тоже корректное.
Проблема в том, что компилятор позволяет запихнуть в функцию структурно идентичный объект. И то, это не проблема, а особенность структурной типизации.
Пока что же, сужение в ветках с instanceof
мне видится вполне валидным, так как тип аргумента функции test
явно указывает на наличие полей.
Но я согласен, что это неудобно, неочевидно и нужно всегда про это помнить. Поэтому я просто не использую юнионы из классов и instanceof.
В Тайпскрипте нет такого отношения. A < B => А содержит все поля B. Проверка типов для instanceof не может проверить что-либо кроме этого, потому что на уровне типов тайпскрипта ничто иное невыразимо. По-этому soundness чекер не должен выводить ничего для негативной ветки. Но в тайпскрипте намеренно жертвуют soundness ради удобства и более простой типизации существующего кода и привычных для жс паттернов, так что вполне возможно, что и тут оно — by design.
Ну как же нет, вон я выше показал пример, когда использование instanceof
на дополнительном конструкторе расширяет тип в ветке интерфейсом этого конструктора.
Вы придираетесь к словам, понятно же, что типизация структурная.
Окей, для ветки if (x instanceof A)
, x
содержит все поля интерфейса A. Так лучше?
Это не придирка к словам, это то как работает алгоритм тайпчека (и от этих «придирок» он будет работать с разным результатом)
> Окей, для ветки if (x instanceof A), x содержит все поля интерфейса A. Так лучше?
Да, именно это утверждение формулируется на уровне типов. Но, вот проблема, сам instanceof в рантайме поля не проверяет. По-этому мы можем быть уверены в том, что в позитивной ветке поля есть. Но не можем быть уверены в том, что их нету в негативной.
Почему мы выбрали TypeScript: история разработчиков из Reddit