Lets assume that I have
// Foo.js
type PropsType = { cool: boolean };
class Foo extends React.Component<PropsType> {}
// Bar.js
import Foo from './Foo';
type PropsBar = { temp: string };
class Bar extends Foo {
test() {
this.props.cool; // there is no error
this.props.temp;
^^^^ Property not found in object type
}
}
My question is, how to pass additional Props to Bar component?
You need to make your super class generic. Just as React.Component is generic, so can your classes and functions be as well.
You can make a declaration such as a class or function generic by introducing a type parameter.
Let us make Foo generic
export default class Foo<T> extends React.Component<FooProps & T> {}
Note the intersection type, written FooProps & T, that is passed to the generic super class React.Component. This means that Foo.prototype.props will have the properties declared in FooProps and also any properties declared by T.
Now when we use Foo, such as in an extends clause, we need to specify a type for T.
type BarProps = { temp: string };
export default class Bar extends Foo<BarProps> {
constructor(props, context) {
super(props, context);
console.log(this.props.temp);
}
}
If you want to maintain simplicity for consumers of Foo that do not add additional props, you can specify a default type for T as in
export default class Foo<T = {}> extends React.Component<FooProps & T> {}
export class Bar extends Foo {}
Note: All of the syntax above is valid in both Flow and TypeScript.
Related
I am trying to learn strict typing in Typescript.
I defined these classes:
export abstract class MyAbstractClass<TParam extends MyParamBaseType> {
private param: TParam;
setInitParams(init: TParam): void {
...
}
getInitParams(): TParam {
....
}
}
export class MyClass extends MyAbstractClass<AParamType> {
private param: AParamType;
...
}
The problem is that I get the error " Class 'MyClass' incorrectly extends base class 'MyAbstractClass'.
Types have separate declarations of a private property 'param'."
I don't understand why I get this error because AParamType type correctly extends MyParamBaseType
Can somebody helps me ? Thanks for your help.
The private keyword is just a compile time checked. This means that the field param will be stored at runtime in the instance of the class. MyAbstractClass declares it's member private, so if MyClass were allowed to redeclare the param field it would end up accessing the same field named param in the instance at runtime breaking privacy.
You can use the ES private class fields (with #). These ensure hard privacy even at runtime and name collisions in the sub class are not an issue since each declared field is distinct even if they share the same name:
type MyParamBaseType = {}
export abstract class MyAbstractClass<TParam extends MyParamBaseType> {
#param!: TParam;
setInitParams(init: TParam): void {
}
getInitParams(): TParam {
return this.#param;
}
}
type AParamType = {}
export class MyClass extends MyAbstractClass<AParamType> {
#param!: AParamType;
}
Playground Link
Or if you want to access the same field from the base class you might consider protected instead:
type MyParamBaseType = {}
export abstract class MyAbstractClass<TParam extends MyParamBaseType> {
protected param!: TParam;
setInitParams(init: TParam): void {
}
getInitParams(): TParam {
return this.param
}
}
type AParamType = {}
export class MyClass extends MyAbstractClass<AParamType> {
protected param!: AParamType;
}
Playground Link
It is possible to use keyof this in the signature of a class method but not in the constructor (error: A 'this' type is available only in a non-static member of a class or interface.). I'd like to extend the class and the type checking be intact in sub classes.
Why the error and how can i work around it?
The code:
class Foo {
constructor(k: keyof this/*Error 2526 here*/) {}
method(k: keyof this) {}
}
class Bar extends Foo{
constructor() {
super("prop");
}
prop = 12;
}
new Bar().method("prop");
code and error in typescript playgorund:
https://www.typescriptlang.org/v2/en/play?ts=3.8.3&ssl=1&ssc=1&pln=15&pc=26#code/MYGwhgzhAEBiD29oG8BQrrWPAdhALgE4Cuw+8hAFANYBc01ApgJ7wBm0+AFgJYQCUKAL7pMAW0bd4AExr0mrDtz6DkIkalCQYAITCFojAB75GOaTATw0mbHiKlyVVRkzQIxAA6MqAIk+E8J6+-ADcriKYAUHQALzQAIwATOEaOIwA7tB6zgB0ElKy-oHBYUA
I understand sub classing with extends for example
class Car extends Vehicle {}
class Dog extends Animal {}
But with React, you may see
class HelloMessage extends React.Component {}
What does the dot between React and Component mean? How does it work in React and in vanilla Javascript?
Classes do not have to be standalone variable names - they may be properties of objects as well. So extends React.Component, absent any other context of what React is, just means that React is an object with has a Component property which is a class.
For an example of how to emulate this in vanilla JS:
const obj = {
Foo: class Foo {
doThing() {
console.log('doing thing');
}
}
};
class MySubClass extends obj.Foo {
subMethod() {
console.log('submethod');
}
}
const s = new MySubClass();
s.doThing();
s.subMethod();
React is doing the same sort of thing. It's just a way to organize data as properties of objects.
I have a base class that I am trying to extend:
export class BaseClass<T extends SomeOtherClass> {
constructor(param: ParamType) {
}
doSomething(param1: Param1Type): BaseClass<T> {
// do something with param1;
return this;
}
}
My class:
export class MyClass<T extends SomeOtherClass> extends BaseClass<T> {
constructor(param: ParamType) {
super(param);
}
doSomething(param1: Param1Type, param2: Param2Type): MyClass<T> {
// super.doSomething(param1);
// do something with param2;
return this;
}
}
but I'm getting a warning:
Property 'doSomething' in type 'MyClass<T>' is not assignable to the same property in base type 'BaseClass<T>'.
Type '(param1: Param1Type, param2: Param2Type) => MyClass<T>' is not assignable to type '(param1: Param1Type) => BaseClass<T>'.
Is it not possible to extend method signatures in typescript? How do I extend the capabilities of the BaseClass if I need to add a parameter to the overridden method and is this the correct way of calling the parent method in es6 syntax. I'm aware that prior to es6 I could have called BaseClass.prototype.doSomething.call(this, param1).
The problem as pointed out by others is that if param2 is required it breaks polymorphism:
// We should be able to do this assignment
let baseRef: BaseClass<SomeOtherClass> = new MyClass<SomeOtherClass>("");
baseRef.doSomething("") // param2 is not required by the base class so MyClass will not receive it even though it NEEDS it
One solution, is to make the second parameter optional, so the call baseRef.doSomething("") is valid for the derived type as well :
export class MyClass<T extends SomeOtherClass> extends BaseClass<T> {
constructor(param: string) {
super(param);
}
doSomething(param1: string, param2?: string): MyClass<T> {
super.doSomething(param1);
return this;
}
}
A second solution, if we only want to share code between the classes, is to disallow the assignment let baseRef: BaseClass<SomeOtherClass> = new MyClass<SomeOtherClass>(""); by not really inheriting BaseClass but rather inherit a class that excludes the doSomething method:
type PartialBaseClass = new <T> (param: string) => { [P in Exclude<keyof BaseClass<T>, 'doSomething'>] : BaseClass<T>[P] }
const PartialBaseClass:PartialBaseClass = BaseClass
export class MyClass<T extends SomeOtherClass> extends PartialBaseClass<T> {
constructor(param: string) {
super(param);
}
doSomething(param1: string, param2: string): MyClass<T> {
BaseClass.prototype.doSomething.call(this, param1);
return this;
}
}
// This is now invalid !
let baseRef: BaseClass<SomeOtherClass> = new MyClass<SomeOtherClass>("") ;
This is a violation of OOP as it would break polymorphism. An example where you might typically use this could be with a base class Filter and a derived class FilterWithDelay. The parent class implements a method called setFilter. In OOP you might have an array of Filters which contains instances of both Filter and FilterWithDelay. You might typically want to iterate through the array and call setFilter on each object. For this to be possible the child method should implement the same function signature as the parent method. Otherwise the code, would need to check each instance to see if its a Filter or FilterWithDelay in order to pass in the additional parameters.
In order to implement the FilterWithDelay you can extend the parent class and pass the delay as a parameter to the constructor instead of the method. This way setFilter can implement the common interface.
export class BaseClass<T extends SomeOtherClass> {
constructor(param: ParamType) {
}
doSomething(param1: Param1Type): BaseClass<T> {
// do something with param1;
return this;
}
}
export class MyClass<T extends SomeOtherClass> extends BaseClass<T> {
constructor(param: ParamType, param2: Param2Type) {
super(param);
this.param2 = param2;
}
doSomething(param1: Param1Type): MyClass<T> {
return super.doSomething(param1 + this.param2);
}
}
I have TypeScript (JavaScript) class like this:
import * as React from 'react'
export default
class StyleableComponent<PROPS, STATE> extends React.Component<PROPS, STATE> {
protected classes: any
static style: any
someMethod() {
const ctor = this.constructor
console.log(this.constructor.style)
}
}
and TypeScript throws this error:
ERROR in ./src/StyleableComponent.ts
(11,38): error TS2339: Property 'style' does not exist on type 'Function'.
But, obviously, you can see that static style: any is declared in the class definition.
So, how do we work with this.constructor properly? Note, this.constructor can be the constructor of a class that extends StyleableComponent, so this.constructor may not be === StyleableComponent, but it should have the style property because it extends from StyleableComponent.
For example,
interface P {...}
interface S {...}
class Foo extends StyleableComponent<P,S> {...}
console.log(new Foo)
^ this.constructor will be Foo, and the StyleableComponent class needs to look at Foo.style.
So how do I do this? Do I need to use a extra template type parameter somehow?
Static Property V.S Instance Property Inheritance
If you want read static property from subclass,you can do it with 3 ways.for more details you can see Test section.
because Object.getPrototypeOf(..)'s return type is any, so you can access style directly,for example:
someMethod() {
let style = Object.getPrototypeOf(this).constructor.style;
}
because this.constructor's return type is a Function, so you must assign it to a any variable at first,for example:
someMethod() {
let cotr:any=this.constructor;
let style = cotr.style;
}
because Function is an interface you can expand it in typescript,for example:
declare global{
interface Function{
style:any;
}
}
someMethod() {
return this.constructor.style;
}
and you can also do it with replace static property with instance property instead.if you want read subclass style property you must define the property on constructor,then the subclass can choose define its style by pass the style to the superclass or not at all.for example:
constructor(protected style:any="default"){
}
the interesting is that the subclass behavior are all the same except the style property.In design view, if you use the static style properties you must define another subclass to achieve it,this will tends to many subclass with diff styles.but when use instance property style,you can do it by pass the style with optional for different style only.for example:
let bar=new Bar();//use parent's style
let baz=new Bar(null,null,"baz");//use it owned style
and you can also reject others to pass their style by pass the style in constructor of the subclass.for example:
constructor(){
super("style");
}
Tests
import * as React from 'react';
declare global {
interface Function {
style: any
}
}
describe('static inheritance', () => {
class StyleableComponent<P, S> extends React.Component<P, S> {
protected classes: any;
static style: any;
constructor(props?: P, context?: any, public style: any = "default") {
super(props, context);
}
someMethod() {
//dangerous if subclass not define static style property
//todo:the 1st approach
// let style = Object.getPrototypeOf(this).constructor.style;
// return style;
//todo:the 2nd approach
// let cotr: any = this.constructor;
// return cotr.style;
//todo:the 3nd approach,you must declare style in Function interface
return this.constructor.style;
}
}
class Foo extends StyleableComponent<any, any> {
static style = "foo";
constructor(props?: any, context?: any) {
super(props, context, Foo.style);
}
}
class Bar extends StyleableComponent<any, any> {
}
test('access style from subclass', function () {
let foo = new Foo();
expect(foo.someMethod()).toBe(Foo.style);
});
test('return undefined if subclass not define style', function () {
let bar = new Bar();
expect(bar.someMethod()).toBeUndefined();
});
test('replace static style with instance property', function () {
let foo = new Foo();
let bar = new Bar();
let baz = new Bar(null, null, "baz");
expect(foo.style).toBe("foo");
expect(bar.style).toBe("default");
expect(baz.style).toBe("baz");
});
});