Typcript: не может получить доступ к значению элемента в унаследованном конструкторе classа

У меня есть class A , а class B унаследован от него.

 class A { constructor(){ this.init(); } init(){} } class B extends A { private myMember = {value:1}; constructor(){ super(); } init(){ console.log(this.myMember.value); } } const x = new B(); 

Когда я запускаю этот код, я получаю следующую ошибку:

 Uncaught TypeError: Cannot read property 'value' of undefined 

Как я могу избежать этой ошибки?

Для меня ясно, что код JavaScript вызовет метод init до создания myMember , но для его работы должна быть какая-то практика / шаблон.

Вот почему на некоторых языках (cough C #) инструменты анализа кода используют использование виртуальных элементов внутри конструкторов.

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

 function B() { var _this = _super.call(this) || this; // base call here, field has not been set, init will be called _this.myMember = { value: 1 }; // field init here return _this; } 

Вы должны рассмотреть решение, в котором init вызывается извне экземпляра, а не в конструкторе:

 class A { constructor(){ } init(){} } class B extends A { private myMember = {value:1}; constructor(){ super(); } init(){ console.log(this.myMember.value); } } const x = new B(); x.init(); 

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

 class A { constructor() constructor(doInit: boolean) constructor(doInit?: boolean){ if(doInit || true)this.init(); } init(){} } class B extends A { private myMember = {value:1}; constructor() constructor(doInit: boolean) constructor(doInit?: boolean){ super(false); if(doInit || true)this.init(); } init(){ console.log(this.myMember.value); } } const x = new B(); 

Или очень очень грязное решение setTimeout , которое отложит инициализацию до завершения текущего кадра. Это позволит завершить вызов родительского конструктора, но между вызовом конструктора будет промежуток времени, и когда истечет время ожидания, когда объект не был init

 class A { constructor(){ setTimeout(()=> this.init(), 1); } init(){} } class B extends A { private myMember = {value:1}; constructor(){ super(); } init(){ console.log(this.myMember.value); } } const x = new B(); // x is not yet inited ! but will be soon 

Поскольку свойство myMember доступно в родительском конструкторе ( init() вызывается во время вызова super() ), нет способа, каким образом его можно определить в дочернем конструкторе, не попав в состояние гонки.

Существует несколько альтернативных подходов.

init hook

init считается крючком, который нельзя вызывать в конструкторе classа. Вместо этого он называется явно:

 new B(); B.init(); 

Или он называется неявно каркасом, как часть жизненного цикла приложения.

Статическое свойство

Если свойство должно быть константой, оно может быть статическим свойством.

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

 class B extends A { static readonly myMember = { value: 1 }; init() { console.log((this.constructor as typeof B).myMember.value); } } 

Геттер / сеттер недвижимости

Дескриптор свойства может быть определен в прототипе classа с синтаксисом get / set . Если свойство должно быть примитивным постоянным, оно может быть просто геттером:

 class B extends A { get myMember() { return 1; } init() { console.log(this.myMember); } } 

Он становится более взломанным, если свойство не является постоянным или примитивным:

 class B extends A { private _myMember?: { value: number }; get myMember() { if (!('_myMember' in this)) { this._myMember = { value: 1 }; } return this._myMember!; } set myMember(v) { this._myMember = v; } init() { console.log(this.myMember.value); } } 

Инициализация на месте

Свойство может быть инициализировано, когда оно открывается первым. Если это происходит в методе init где this можно получить до конструктора classа B , это должно произойти там:

 class B extends A { private myMember?: { value: number }; init() { this.myMember = { value: 1 }; console.log(this.myMember.value); } } 

Асинхронная инициализация

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

 class A { initialization = Promise.resolve(); constructor(){ this.init(); } init(){} } class B extends A { private myMember = {value:1}; init(){ this.initialization = this.initialization.then(() => { console.log(this.myMember.value); }); } } const x = new B(); x.initialization.then(() => { // class is initialized }) 

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

Класс Desugared

Поскольку classы ES6 имеют ограничения на использование this до super , дочерний class может быть освобожден от функции, чтобы избежать этого ограничения:

 interface B extends A {} interface BPrivate extends B { myMember: { value: number }; } interface BStatic extends A { new(): B; } const B = function B(this: BPrivate) { this.myMember = { value: 1 }; return A.call(this); } B.prototype.init = function () { console.log(this.myMember.value); } 

Это редко бывает хорошим вариантом, потому что class desugared должен быть дополнительно введен в TypeScript. Это также не будет работать с родными родительскими classами (TypeScript es6 и esnext target).

Один из подходов, который вы можете использовать, – использовать getter / setter для myMember и управлять значением по умолчанию в getter. Это предотвратит неопределенную проблему и позволит вам сохранить почти ту же структуру, что и у вас. Как это:

 class A { constructor(){ this.init(); } init(){} } class B extends A { private _myMember; constructor(){ super(); } init(){ console.log(this.myMember.value); } get myMember() { return this._myMember || { value: 1 }; } set myMember(val) { this._myMember = val; } } const x = new B(); 

Попробуй это:

 class A { constructor() { this.init(); } init() { } } class B extends A { private myMember = { 'value': 1 }; constructor() { super(); } init() { this.myMember = { 'value': 1 }; console.log(this.myMember.value); } } const x = new B(); 

Супер должен быть первой командой. Помните, что typescript является более «javascript с документацией типов», а не языком сама по себе.

Если вы посмотрите на переписанный код .js, он будет хорошо виден:

 class A { constructor() { this.init(); } init() { } } class B extends A { constructor() { super(); this.myMember = { value: 1 }; } init() { console.log(this.myMember.value); } } const x = new B(); 

Вам нужно вызвать init в classе A?

Это прекрасно работает, но я не знаю, есть ли у вас разные требования:

 class A { constructor(){} init(){} } class B extends A { private myMember = {value:1}; constructor(){ super(); this.init(); } init(){ console.log(this.myMember.value); } } const x = new B(); 

Как это :

  class A { myMember; constructor() { } show() { alert(this.myMember.value); } } class B extends A { public myMember = {value:1}; constructor() { super(); } } const test = new B; test.show(); 
  • получить и установить в TypeScript
  • Внедрение интерфейса TypeScript с сигнатурой голосовой функции и другими полями
  • Как инициализировать объект TypeScript с помощью объекта JSON
  • Получить имя classа объекта во время выполнения в TypeScript
  • Когда я обновляю свой сайт, я получаю 404. Это с Angular2 и firebase
  • Сбивание «дубликата идентификатора»
  • Перегрузка конструктора в TypeScript
  • Как предотвратить ошибку? Подпись индекса типа объекта неявно имеет тип «any» при компиляции машинописных файлов с флагом noImplicitAny?
  • Как отобразить объект json с помощью * ngFor
  • Что такое знак вопроса в имени параметра Typcript
  • Оператор ввода-вывода TypeScript
  • Давайте будем гением компьютера.