Typescript: как с ним работать и чем он отличается от javascript

Здравствуй, мир обобщений!¶

Для начала создадим традиционную для знакомства c обобщениями первую функцию: функцию-тождество. Такая функция возвращает в точности то, что ей было передано. Можно расценивать ее так же, как команду .


Без использования обобщений пришлось бы задать такой функции определенный тип:

Или же описать ее, используя тип :

Хотя использование , без сомнения, представляет собой некоторого рода обобщение, поскольку позволяет использовать любого типа, в этом случае в момент возврата значения теряется информация о его типе. Если бы мы передали число, известно было бы лишь то, что это мог быть любой () тип.

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

Мы добавили типовую переменную к функции-тождеству. Эта позволяет сохранять тип, который указал пользователь (то есть ), так что позже его можно будет использовать. В данном случае используется в качестве типа возвращаемого значения. Можно увидеть, что теперь и аргумент, и возвращаемое значение имеют один и тот же тип. Такой способ позволяет направить информацию о типах со входа функции к ее выходу.

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

Функция написана, и теперь ее можно вызвать одним из двух способов. Первый способ — передать все аргументы, в том числе и типовый аргумент:

В этом примере явно устанавливается в , как один из аргументов функции, но окруженный угловыми скобками вместо круглых .

Второй способ, вероятно, наиболее популярен. Здесь используется выведение типового аргумента, и компилятор автоматически устанавливает на основании типа аргумента, который передается в функцию:

Обратите внимание, что тип не передается явно, в угловых скобках () — компилятор просто проанализировал значение и установил в значение его типа. Хотя выведение типового аргумента может быть полезно, чтобы сделать код более кратким и читаемым, иногда может понадобиться явно передавать типовый аргумент, если компилятору не удается автоматически вывести тип, что может произойти в более сложных случаях

Object Spread and Rest

TypeScript 2.1 brings support for ESnext Spread and Rest.

Similar to array spread, spreading an object can be handy to get a shallow copy:

ts

Similarly, you can merge several different objects. In the following example, will have properties from , , and .

ts

You can also override existing properties and add new ones:

ts

The order of specifying spread operations determines what properties end up in the resulting object; properties in later spreads “win out” over previously created properties.

Object rests are the dual of object spreads, in that they can extract any extra properties that don’t get picked up when destructuring an element:

ts

Variable capturing quirks

Take a quick second to guess what the output of the following snippet is:

ts

For those unfamiliar, will try to execute a function after a certain number of milliseconds (though waiting for anything else to stop running).

Ready? Take a look:

Many JavaScript developers are intimately familiar with this behavior, but if you’re surprised, you’re certainly not alone. Most people expect the output to be

Remember what we mentioned earlier about variable capturing? Every function expression we pass to actually refers to the same from the same scope.

Let’s take a minute to consider what that means. will run a function after some number of milliseconds, but only after the loop has stopped executing; By the time the loop has stopped executing, the value of is . So each time the given function gets called, it will print out !

A common work around is to use an IIFE — an Immediately Invoked Function Expression — to capture at each iteration:

ts

This odd-looking pattern is actually pretty common. The in the parameter list actually shadows the declared in the loop, but since we named them the same, we didn’t have to modify the loop body too much.

More Accurate Array Spread

In pre-ES2015 targets, the most faithful emit for constructs like / loops and array spreads can be a bit heavy. For this reason, TypeScript uses a simpler emit by default that only supports array types, and supports iterating on other types using the flag. The looser default without works fairly well; however, there were some common cases where the transformation of array spreads had observable differences. For example, the following array containing a spread

ts

can be rewritten as the following array literal

js

However, TypeScript would instead transform the original code into this code:

ts

which is slightly different. produces an array with a length of 5, but with no defined property slots.

TypeScript 3.6 introduces a new helper to accurately model what happens in ECMAScript 2015 in older targets outside of . is also available in tslib.

For more information, see the relevant pull request.

Rest Parameters

Required, optional, and default parameters all have one thing in common: they talk about one parameter at a time. Sometimes, you want to work with multiple parameters as a group, or you may not know how many parameters a function will ultimately take. In JavaScript, you can work with the arguments directly using the variable that is visible inside every function body.

In TypeScript, you can gather these arguments together into a variable:

ts

Rest parameters are treated as a boundless number of optional parameters. When passing arguments for a rest parameter, you can use as many as you want; you can even pass none. The compiler will build an array of the arguments passed in with the name given after the ellipsis (), allowing you to use it in your function.

The ellipsis is also used in the type of the function with rest parameters:

ts

Optional Chaining


Optional chaining is issue #16 on our issue tracker. For context, there have been over 23,000 issues on the TypeScript issue tracker since then.

At its core, optional chaining lets us write code where TypeScript can immediately stop running some expressions if we run into a or . The star of the show in optional chaining is the new operator for optional property accesses. When we write code like

ts

this is a way of saying that when is defined, will be computed; but when is or , stop what we’re doing and just return .”

More plainly, that code snippet is the same as writing the following.

ts

Note that if is or , our code will still hit an error accessing . Likewise, if is or , we’ll hit an error at the call site. only checks for whether the value on the left of it is or — not any of the subsequent properties.

You might find yourself using to replace a lot of code that performs repetitive nullish checks using the operator.

ts

Keep in mind that acts differently than those operations since will act specially on “falsy” values (e.g. the empty string, , , and, well, ), but this is an intentional feature of the construct. It doesn’t short-circuit on valid data like or empty strings.

Optional chaining also includes two other operations. First there’s the optional element access which acts similarly to optional property accesses, but allows us to access non-identifier properties (e.g. arbitrary strings, numbers, and symbols):

ts

There’s also optional call, which allows us to conditionally call expressions if they’re not or .

ts

The “short-circuiting” behavior that optional chains have is limited property accesses, calls, element accesses — it doesn’t expand any further out from these expressions. In other words,

ts

doesn’t stop the division or call from occurring. It’s equivalent to

ts

That might result in dividing , which is why in , the following is an error.

ts

More more details, you can read up on the proposal and view the original pull request.

Наш первый интерфейс¶

Самый простой способ увидеть, как работают интерфейсы — начать с простого примера:

Компилятор проверяет вызов . Эта функция принимает один параметр, который требует, чтобы у переданного объекта было свойство под именем , которое имело бы строковый тип. Обратите внимание, что у нашего объекта есть и другие свойства, однако компилятор проверяет лишь то, что у него есть, по крайней мере, необходимые свойства, и их типы совпадают с требуемыми. В некоторых случаях, которые мы рассмотрим чуть позже, TypeScript ведет себя не так снисходительно. Мы можем переписать этот пример, на этот раз используя интерфейс для того, чтобы отразить необходимость наличия свойства строкового типа:

Мы можем переписать этот пример, на этот раз используя интерфейс для того, чтобы отразить необходимость наличия свойства строкового типа:

Интерфейс — это имя, которое теперь можно использовать, чтобы задать требование из предыдущего примера. Он по-прежнему отражает необходимость того, что объект должен иметь свойство строкового типа с именем . Обратите внимание — совсем не обязательно явно указывать, что объект, который мы передаем в , реализует данный интерфейс, как пришлось бы делать в иных языках. В TypeScript имеет значение только форма объекта. Если объект, который передается в функцию, удовлетворяет перечисленным требованиям, то он считается подходящим

Стоит отметить, что проверка типов не требует, чтобы свойства шли в определенном порядке: важно лишь, что необходимые свойства присутствуют и имеют подходящий тип

Стоит отметить, что проверка типов не требует, чтобы свойства шли в определенном порядке: важно лишь, что необходимые свойства присутствуют и имеют подходящий тип

Optional Module Loading and Other Advanced Loading Scenarios

In some cases, you may want to only load a module under some conditions. In TypeScript, we can use the pattern shown below to implement this and other advanced loading scenarios to directly invoke the module loaders without losing type safety.

The compiler detects whether each module is used in the emitted JavaScript. If a module identifier is only ever used as part of a type annotations and never as an expression, then no call is emitted for that module. This elision of unused references is a good performance optimization, and also allows for optional loading of those modules.

The core idea of the pattern is that the statement gives us access to the types exposed by the module. The module loader is invoked (through ) dynamically, as shown in the blocks below. This leverages the reference-elision optimization so that the module is only loaded when needed. For this pattern to work, it’s important that the symbol defined via an is only used in type positions (i.e. never in a position that would be emitted into the JavaScript).

To maintain type safety, we can use the keyword. The keyword, when used in a type position, produces the type of a value, in this case the type of the module.

Union Exhaustiveness checking

We would like the compiler to tell us when we don’t cover all variants of the discriminated union. For example, if we add to , we need to update as well:

ts

There are two ways to do this. The first is to turn on and specify a return type:

ts

Because the is no longer exhaustive, TypeScript is aware that the function could sometimes return . If you have an explicit return type , then you will get an error that the return type is actually . However, this method is quite subtle and, besides, does not always work with old code.

The second method uses the type that the compiler uses to check for exhaustiveness:

ts

Here, checks that is of type — the type that’s left after all other cases have been removed. If you forget a case, then will have a real type and you will get a type error. This method requires you to define an extra function, but it’s much more obvious when you forget it because the error message includes the missing type name.

Excess Property Checks

In our first example using interfaces, TypeScript lets us pass to something that only expected a . We also just learned about optional properties, and how they’re useful when describing so-called “option bags”.

However, combining the two naively would allow an error to sneak in. For example, taking our last example using :

ts

Notice the given argument to is spelled instead of . In plain JavaScript, this sort of thing fails silently.

You could argue that this program is correctly typed, since the properties are compatible, there’s no property present, and the extra property is insignificant.

However, TypeScript takes the stance that there’s probably a bug in this code. Object literals get special treatment and undergo excess property checking when assigning them to other variables, or passing them as arguments. If an object literal has any properties that the “target type” doesn’t have, you’ll get an error:

ts

Getting around these checks is actually really simple. The easiest method is to just use a type assertion:

ts

However, a better approach might be to add a string index signature if you’re sure that the object can have some extra properties that are used in some special way. If can have and properties with the above types, but could also have any number of other properties, then we could define it like so:

ts

We’ll discuss index signatures in a bit, but here we’re saying a can have any number of properties, and as long as they aren’t or , their types don’t matter.

One final way to get around these checks, which might be a bit surprising, is to assign the object to another variable: Since won’t undergo excess property checks, the compiler won’t give you an error.

ts

The above workaround will work as long as you have a common property between and . In this example, it was the property . It will however, fail if the variable does not have any common object property. For example:

ts

Keep in mind that for simple code like above, you probably shouldn’t be trying to “get around” these checks. For more complex object literals that have methods and hold state, you might need to keep these techniques in mind, but a majority of excess property errors are actually bugs. That means if you’re running into excess property checking problems for something like option bags, you might need to revise some of your type declarations. In this instance, if it’s okay to pass an object with both a or property to , you should fix up the definition of to reflect that.

Параметра типа — значение по умолчанию = (generic parameter defaults)¶

В TypeScript существует возможность указывать значение по умолчанию в объявлении обобщенного типа.

Значение по умолчанию указывается с помощью оператора равно , слева от которого располагается параметр типа, а справа конкретный тип, либо другой параметр типа . Параметры, которым заданы значения по умолчанию, являются необязательными параметрами. Необязательные параметры типа должны быть перечислены строго после обязательных. Если параметр типа указывается в качестве типа по умолчанию, то ему самому должно быть задано значение по умолчанию, либо он должен расширять другой тип.

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

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

Чтобы было проще понять, нужно представить два класса, один из которых расширяет другой. В этом случае переменной с типом суперкласса можно в качестве значения присвоить объект его подкласса, но — не наоборот.

Тот же самый механизм используется для параметров типа.

Важным моментом является понимание того, как вывод типов обрабатывает значение по умолчанию. Но чтобы не запутаться, нужно разграничить поведение типа на внешнее (обозначим его как outside behavior), и внутреннее (inside behavior). Внешнее поведение обуславливается операциями, которые можно производить над значениями снаружи обобщенного типа. Соответственно, внутренним поведением обуславливаются операции, которые можно производить внутри обобщенного типа, то есть в области видимости параметров типа. В данном контексте слово «поведение» нужно понимать как «к какому типу данных вывод типов установит принадлежность для значения, чей тип указан с помощью параметра типа «. Но обо всем по порядку.

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

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


При этом значение по умолчанию можно переопределить.

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

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

Несмотря на то, что значения по умолчанию указываются в объявлении параметров типа, предназначаются они для аргументов типа. Простыми словами, с помощью оператора равно указывается значение по умолчанию для аргументов типа, а не для параметров типа.

Что такое TypeScript¶

TypeScript — это язык программирования со статической типизацией, позиционирующий себя как язык, расширяющий возможности JavaScript.

Typescript код компилируется в JavaScript код, который можно запускать как на клиентской стороне (браузер), так и на стороне сервера (nodejs). Качество сгенерированного кода сопоставимо с кодом, написанным профессиональным разработчиком с большим стажем. Мультиплатформенный компилятор TypeScript отличается высокой скоростью компиляции и распространяется по лицензии Apache, а его разработка ведётся с помощью разработчиков со всего мира.

@type

You can use the “@type” tag and reference a type name (either primitive, defined in a TypeScript declaration, or in a JSDoc “@typedef” tag). You can use most JSDoc types and any TypeScript type, from the most basic like to the most advanced, like conditional types.

js

can specify a union type — for example, something can be either a string or a boolean.

js

Note that parentheses are optional for union types.

js

You can specify array types using a variety of syntaxes:

js

You can also specify object literal types. For example, an object with properties ‘a’ (string) and ‘b’ (number) uses the following syntax:

js

You can specify map-like and array-like objects using string and number index signatures, using either standard JSDoc syntax or TypeScript syntax.

js

The preceding two types are equivalent to the TypeScript types and . The compiler understands both syntaxes.

You can specify function types using either TypeScript or Closure syntax:

js

Or you can just use the unspecified type:

js

Other types from Closure also work:

js

Casts

TypeScript borrows cast syntax from Closure. This lets you cast types to other types by adding a tag before any parenthesized expression.

js

Import types

You can also import declarations from other files using import types. This syntax is TypeScript-specific and differs from the JSDoc standard:

js

import types can also be used in type alias declarations:

js

import types can be used to get the type of a value from a module if you don’t know the type, or if it has a large type that is annoying to type:

js

Class Types

Implementing an interface

One of the most common uses of interfaces in languages like C# and Java, that of explicitly enforcing that a class meets a particular contract, is also possible in TypeScript.

ts

You can also describe methods in an interface that are implemented in the class, as we do with in the below example:

ts

Interfaces describe the public side of the class, rather than both the public and private side. This prohibits you from using them to check that a class also has particular types for the private side of the class instance.

Difference between the static and instance sides of classes

When working with classes and interfaces, it helps to keep in mind that a class has two types: the type of the static side and the type of the instance side. You may notice that if you create an interface with a construct signature and try to create a class that implements this interface you get an error:

ts

This is because when a class implements an interface, only the instance side of the class is checked. Since the constructor sits in the static side, it is not included in this check.

Instead, you would need to work with the static side of the class directly. In this example, we define two interfaces, for the constructor and for the instance methods. Then, for convenience, we define a constructor function that creates instances of the type that is passed to it:

ts

Because ’s first parameter is of type , in , it checks that has the correct constructor signature.

Another simple way is to use class expressions:

ts

Ограничения обобщений¶

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

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

Чтобы реализовать это, создадим интерфейс, который описывал бы такое ограничение. Создадим интерфейс с единственным свойством , и используем его с ключевым словом , чтобы обозначить ограничение:

Поскольку обобщенная функция теперь имеет ограничение, она не сможет работать с любым типом:

Вместо этого ей необходимо передавать значения тех типов, у которых есть все необходимые свойства:

Использование типовых параметров в ограничениях обобщений

Можно объявить типовый параметр, который ограничивается другим типовым параметром. К примеру, нужно принять два объекта и копировать свойства из одного в другой. Нужно удостовериться, что мы случайно не добавим какое-либо лишнее свойство, поэтому добавим ограничение между двумя типами:

Использование типов классов в обобщениях


Реализуя паттерн «фабрика» с использованием обобщений, необходимо указывать на тип класса с помощью его функции-конструктора. К примеру,

Более сложный пример использует свойство прототипа, чтобы вывести и ограничить отношения между конструктором и типом экземпляра класса.

Example

import{ActionContext,Store}from"vuex";import{getStoreAccessors}from"vuex-typescript";import{StateasRootState}from"../state";import{BasketState,Product,ProductInBasket}from"./basketState";type BasketContext = ActionContext<BasketState, RootState>;exportconstbasket={    namespacedtrue,    state{        items,        totalAmount,},    getters{getProductNames(stateBasketState){returnstate.items.map((item)=>item.product.name);},...},    mutations{appendItem(stateBasketState,item{ product Product; atTheEnd boolean }){state.items.push({ productitem.product, isSelectedfalse});},...},    actions{asyncupdateTotalAmount(context BasketContext, discount number)Promise<void>{consttotalBeforeDiscount=readTotalAmountWithoutDiscount(context);consttotalAfterDiscount=awaitcallServer(totalBeforeDiscount, discount);commitSetTotalAmount(context, totalAfterDiscount);},...},};const{commit,read,dispatch}=     getStoreAccessors<BasketState, RootState>("basket");exportconstreadProductNames=read(basket.getters.getProductNames);exportconstdispatchUpdateTotalAmount=dispatch(basket.actions.updateTotalAmount);exportconstcommitAppendItem=commit(basket.mutations.appendItem);

And then in your Vue component:

import*asbasketfrom"./store/basket";...getterResult =basket.readProductNames(this.$store);basket.dispatchUpdateTotalAmount(this.$store,.5);basket.commitAppendItem(this.$store, newItem);

Build-Free Editing with Project References

TypeScript’s project references provide us with an easy way to break codebases up to give us faster compiles. Unfortunately, editing a project whose dependencies hadn’t been built (or whose output was out of date) meant that the editing experience wouldn’t work well.

In TypeScript 3.7, when opening a project with dependencies, TypeScript will automatically use the source / files instead. This means projects using project references will now see an improved editing experience where semantic operations are up-to-date and “just work”. You can disable this behavior with the compiler option which may be appropriate when working in very large projects where this change may impact editing performance.

You can read up more about this change by reading up on its pull request.

Writing the function type

Now that we’ve typed the function, let’s write the full type of the function out by looking at each piece of the function type.

ts

A function’s type has the same two parts: the type of the arguments and the return type. When writing out the whole function type, both parts are required. We write out the parameter types just like a parameter list, giving each parameter a name and a type. This name is just to help with readability. We could have instead written:

ts

As long as the parameter types line up, it’s considered a valid type for the function, regardless of the names you give the parameters in the function type.

The second part is the return type. We make it clear which is the return type by using an arrow () between the parameters and the return type. As mentioned before, this is a required part of the function type, so if the function doesn’t return a value, you would use instead of leaving it off.

Of note, only the parameters and the return type make up the function type. Captured variables are not reflected in the type. In effect, captured variables are part of the “hidden state” of any function and do not make up its API.

Проверки на лишние свойства¶

В нашем первом примере использования интерфейсов TypeScript позволил передать там, где ожидалось всего лишь . Также мы узнали о необязательных свойствах, и о том, как они могут быть полезны при передаче аргументов в функции.

Однако, бездумное сочетание двух этих возможностей позволило бы выстрелить себе в ногу так же, как и в JavaScript. К примеру, если взять последний пример с :

Обратите внимание, что аргумент, передаваемый в , записан как вместо . В чистом JavaScript подобные вещи не выдают ошибок, но и не работают так, как хотел бы разработчик. Можно сказать, что данная программа корректна с точки зрения типов, так как типы свойств совместимы, отсутствует, а наличие дополнительного свойства не имеет никакого значения

Можно сказать, что данная программа корректна с точки зрения типов, так как типы свойств совместимы, отсутствует, а наличие дополнительного свойства не имеет никакого значения.

Однако TypeScript делает предположение, что в этом куске кода есть ошибка. Литералы объектов обрабатываются им по-особенному, и проходят проверку на наличие лишних свойств. Эта проверка делается, когда литералы либо присваиваются другим переменным, либо передаются в качестве аргументов. Если в литерале есть какие-либо свойства, которых нет в целевом типе, то это будет считаться ошибкой.

Обойти такую проверку очень легко. Самый простой способ — использовать приведение типов:

Если же вы уверены, что объект может иметь дополнительные свойства, которые будут использоваться каким-то особенным способом, то есть способ еще лучше — добавить строковый индекс. Если объекты могут иметь свойства и , а также любое количество иных свойств, то интерфейс можно описать следующим образом:

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

Последний способ обойти проверку на наличие избыточных свойств, который может показаться немножко неожиданным — присваивание объекта другой переменной. Так как не будет проходить проверку на избыточные свойства, компилятор не выдаст ошибки.

Не забывайте, что в простом коде, подобном приведенному выше, скорее всего, не нужно пытаться обойти эту проверку. Для более сложных объектных литералов, в которых есть методы, или которые имеют состояние, возможно, придется прибегнуть к помощи этой техники, однако большинство сообщений компилятора, связанных с проверкой на избыточные свойства, указывают на настоящие ошибки. Это значит, что когда вы сталкиваетесь с проблемами, которые порождает такая проверка (к примеру, при передаче в функцию объекта с аргументами), возможно, придется изменить объявления типов. В данном случае, если передача объекта, в котором могут быть одновременно свойства и , приемлема, нужно исправить определение , чтобы отразить это.

Зачем разработчику TypeScript¶

TypeScript значительно сокращает время на устранение ошибок и выявление багов, которые порой не так просто отыскать в динамической среде JavaScript. Кроме того, сокращается объем комментариев на которые тоже уходит немало времени.

В случае, если для разработчика TypeScript является первым типизированным языком, следует знать, что его изучение значительно ускорит процесс профессионального роста, так как типизированный мир открывает аспекты программирования, которые в динамических языках просто неочевидны.

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

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

Interfaces Extending Classes

When an interface type extends a class type it inherits the members of the class but not their implementations. It is as if the interface had declared all of the members of the class without providing an implementation. Interfaces inherit even the private and protected members of a base class. This means that when you create an interface that extends a class with private or protected members, that interface type can only be implemented by that class or a subclass of it.

This is useful when you have a large inheritance hierarchy, but want to specify that your code works with only subclasses that have certain properties. The subclasses don’t have to be related besides inheriting from the base class. For example:

ts

In the above example, contains all of the members of , including the private property. Since is a private member it is only possible for descendants of to implement . This is because only descendants of will have a private member that originates in the same declaration, which is a requirement for private members to be compatible.

Within the class it is possible to access the private member through an instance of . Effectively, a acts like a that is known to have a method. The and classes are subtypes of (because they both inherit from and have a method). The class has it’s own private member rather than extending , so it cannot implement .

The useDefineForClassFields Flag and The declare Property Modifier

Back when TypeScript implemented public class fields, we assumed to the best of our abilities that the following code

ts

would be equivalent to a similar assignment within a constructor body.

ts

Unfortunately, while this seemed to be the direction that the proposal moved towards in its earlier days, there is an extremely strong chance that public class fields will be standardized differently. Instead, the original code sample might need to de-sugar to something closer to the following:

ts

While TypeScript 3.7 isn’t changing any existing emit by default, we’ve been rolling out changes incrementally to help users mitigate potential future breakage. We’ve provided a new flag called to enable this emit mode with some new checking logic.

The two biggest changes are the following:

  • Declarations are initialized with .
  • Declarations are always initialized to , even if they have no initializer.

This can cause quite a bit of fallout for existing code that use inheritance. First of all, accessors from base classes won’t get triggered — they’ll be completely overwritten.

ts

Secondly, using class fields to specialize properties from base classes also won’t work.

ts

What these two boil down to is that mixing properties with accessors is going to cause issues, and so will re-declaring properties with no initializers.

To detect the issue around accessors, TypeScript 3.7 will now emit / accessors in files so that in TypeScript can check for overridden accessors.

Code that’s impacted by the class fields change can get around the issue by converting field initializers to assignments in constructor bodies.

ts

To help mitigate the second issue, you can either add an explicit initializer or add a modifier to indicate that a property should have no emit.

ts

Currently is only available when targeting ES5 and upwards, since doesn’t exist in ES3. To achieve similar checking for issues, you can create a seperate project that targets ES5 and uses to avoid a full build.

For more information, you can take a look at the original pull request for these changes.

We strongly encourage users to try the flag and report back on our issue tracker or in the comments below. This includes feedback on difficulty of adopting the flag so we can understand how we can make migration easier.


С этим читают