简体   繁体   中英

Extend and spread functions from another class

Below I have class E which extends W . Both have a property meow, which is an object of functions. I'd like to take the functions from W and spread them over E 's. However I would also like to access the original functions on W from within the classes on E. I assumed that I could do this through calling super directly but it's not working.

class W { 
    meow = {
        woof: () => {
            '2'
        }
    }
}

class E extends W { 
    meow = {
        ...super.meow,
        hello: () => {
            return super.meow.woof()
        }
    }
}

const x = new E()

console.log(x.meow.hello())

Is there any way this syntax could work?


Real world example of what I'm trying to do:

class Reads {
    one() {
        return '2'
    }

    byId() {
        return '2'
    }
}

class ReadsProp {
    read = new Reads()
}

class UserReads extends Reads {
    byEmail() {
        return this.one()
    }
}

class UserReadsProp extends ReadsProp {
    read = new UserReads()
}

class UserRepo { 
    read = new UserReadsProp().read
}

const userRepo = new UserRepo()

console.log(userRepo.read.byEmail())

Intro

I wouldn't do it that way (though I think I've found a way to force it to work). After the version forcing it to work, I've shown how I think I'd do it instead.

(You seem to prefer to rely on ASI avoid writing semicolons, so I've done that below.)

Why it isn't working

The reason what you have isn't working is that E 's meow replaces W 's meow . Properties are created with "define" semantics, and so when E 's properties are created, W 's meow is thrown away (before the initializer is evaluated) and E 's meow is created in its place on the object being created. super.meow is the same as this.meow in the initializer, and has the value undefined . Spreading undefined is a no-op.

Getting it to work

Here's the fairly ugly way I've forced it to work:

type PropType<TObj, TProp extends keyof TObj> = TObj[TProp]

class W {
    meow = {
        woof: () => '2'
    }
}

class E extends W {
    // @ts-ignore 2564 (not definitely assigned in ctor -- it is, in the super)
    meow: PropType<W, "meow"> & {
        hello: () => string
    }
    constructor() {
        super()
        // @ts-ignore 2565 (not assigned -- it is, in the super)
        this.meow.hello = () => {
            return this.meow.woof()
        }
    }
}

const x = new E()

console.log(x.meow.hello())

On the playground

As you can see, it involves suppressing a couple of TypeScript errors. Here's what that does:

  • I've declared meow for TypeScript only: meow: PropType & { hello: () => string }
  • Doing that requires extracting the type of W 's meow (so E 's has woof ), which I did by using PropType from this answer :

     type PropType<TObj, TProp extends keyof TObj> = TObj[TProp]
  • Then, since we know that W creates meow , I've extended the one W creates in E 's constructor:

     this.meow.hello = () => { return this.meow.woof() }

    If you had multiple functions to add, you could assign them via Object.assign .

  • I also updated woof so it actually returns the '2' , so we could see that hello successfully calls it.

What I'd do instead

I'd create classes for meow :

class WMeow {
    woof() {
        return '2'
    }
}

class W {
    meow = new WMeow()
}

class EMeow extends WMeow {
    hello() {
        return this.woof()
    }
}

class E extends W {
    meow = new EMeow()
}

const x = new E()

console.log(x.meow.hello())

On the playground

Now, that doesn't give meow 's methods access to the W or E instance. The fact you were using arrow function ssuggests to me that you wanted them to have access to this (the W or E instance).

If you wanted that access, WMeow and EMeow could both accept a parent instance:

class WMeow<T extends W> {
    constructor(protected parent: T) {
    }
    woof() {
        this.parent.wmethod()
        return '2'
    }
}

class W {
    meow = new WMeow<W>(this)

    wmethod() {
        console.log("wmethod")
    }
}

class EMeow<T extends E> extends WMeow<T> {
    hello() {
        this.parent.emethod()
        return this.woof()
    }
}

class E extends W {
    meow = new EMeow(this)

    emethod() {
        console.log("emethod")
    }
}

const x = new E()

console.log(x.meow.hello())

On the playground

The main issue is that you ask about "functions", but meow is a member variable. super is about calling functions.

JS

The super keyword is used to access and call functions on an object's parent.

TS

Super property accesses are used to access base class member functions from derived classes and are permitted in contexts where this (section 4.2) references a derived class instance or a derived class constructor function.

So it's not like Java for example.

A thing what you can do is making it a function, like a getter. That would work for the one level of inheritance shown in the question:

 class W { _meow = { woof: () => { return '2' } } get meow() { return this._meow; } } class E extends W { meow = { ...super.meow, hello: () => { return super.meow.woof() } } } const x = new E() console.log(x.meow.hello()) console.log(x.meow.woof())

Added the second log to show that the spread syntax is operational here (if you comment the ...super.meow , x.meow.woof() will die).
Side note: () => {'2'} is wrong. It either has to be () => '2' , or () => {return '2'} .
Side note2: it works as a snippet, because it (and the question too) is JavaScript, not TypeScript. meow is a completely ad-hoc object. TypeScript is what TJ Crowder shows, and it involves a lot more typing.


Apparently TS compiler produces code which in JS would be needed for supporting an inheritance chain only (moving the "extension code" into a constructor), that means a setter is required too for meow . This code uses Object.assign() to extend the already existing meow , and works both in JS and TS:

 class W { _meow = { woof: () => { return '2' } } get meow() { return this._meow; } set meow(m) { Object.assign(this._meow, m); } } class E extends W { meow = { ...super.meow, hello: () => { return super.meow.woof() } } } const x = new E() console.log(x.meow.hello()) console.log(x.meow.woof())

Fun fact: as this variant extends the already existing meow "property", it does not actually depend on the ...super.meow part, but without it TS starts complaining that the hello -only object is not assignable to the (implicit) type of meow in W ,

 Property 'woof' is missing in type '{ hello: () => string; }' but required in type '{ woof: () => string; }'.

The compiled code works nevertheless, as JS actually does not care.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM