代码之家  ›  专栏  ›  技术社区  ›  roomcayz

在角度继承组件时发生类型错误[重复]

  •  0
  • roomcayz  · 技术社区  · 7 年前

    我有一节课 A ,和一个类 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 ,但是应该有一些实践/模式来实现它。

    0 回复  |  直到 7 年前
        1
  •  17
  •   Titian Cernicova-Dragomir    7 年前

    这就是为什么在某些语言(咳嗽C#)中,代码分析工具会标记构造函数中虚拟成员的使用。

    在Typescript中,在调用基本构造函数之后,在构造函数中进行字段初始化。字段初始化是在字段附近编写的,这只是语法上的简化。如果我们看一下生成的代码,问题就很明显了:

    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 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 ,它将延迟初始化,直到当前帧完成。这将允许父构造函数调用完成,但在构造函数调用与对象尚未完成时超时过期之间将有一个中间过程 初始 预计起飞时间

    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 
    
        2
  •  5
  •   Estus Flask    7 年前

    因为 myMember 在父构造函数中访问属性( init() super() 调用),无法在子构造函数中定义它,而不会遇到竞争条件。

    有几种替代方法。

    init

    初始 被认为是不应该在类构造函数中调用的挂钩。相反,它被显式调用:

    new B();
    B.init();
    

    或者它被框架隐式调用,作为应用程序生命周期的一部分。

    静态特性

    如果一个属性应该是常量,那么它可以是静态属性。

    这是最有效的方法,因为这是静态成员的用途,但是语法可能没有那么吸引人,因为它需要使用 this.constructor 如果静态属性应在子类中正确引用,则不使用类名:

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

    getter/setter属性

    属性描述符可以在类原型上定义 get / set 语法。如果一个属性应该是基元常量,它可以只是一个getter:

    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);
        }
    }
    

    就地初始化

    属性可以在首先访问它的位置初始化。如果这发生在 初始 方法,其中 this 可在 B 类构造函数,这应该发生在:

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

    异步初始化

    初始 方法可能会变为异步。初始化状态应该是可跟踪的,因此类应该为此实现一些API,例如基于promise的:

    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
    })
    

    对于这种特殊情况,这种方法可能被认为是反模式,因为初始化例程本质上是同步的,但它可能适合于异步初始化例程。

    脱糖级

    因为ES6类在使用 之前 super ,可以将子类减为一个函数以避免此限制:

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

    这很少是一个好的选择,因为desugared类应该在TypeScript中另外键入。这也不适用于本机父类(TypeScript es6 esnext 目标)。

        3
  •  3
  •   Julien Grégoire    7 年前

    一种方法是使用getter/setter 我的会员 并管理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();
    
        4
  •  2
  •   Igor Dimchevski    7 年前

    试试这个:

    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();
    
        5
  •  2
  •   libik    7 年前

    超级必须是第一命令。请记住,typescript更多的是“带有类型文档的javascript”,而不是单独的语言。

    如果您查看transpiled code.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();
    
        6
  •  2
  •   Carlos Augusto Grispan    7 年前

    你必须在A类中调用init吗?

    很好,但我不知道你是否有不同的要求:

    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();
    
        7
  •  0
  •   Léo R.    7 年前

    这样地:

     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();