Reputation: 55
I'm creating a library with presentations in React. Each presentation component must have at least a few attributes set so I created a typescript file which can be extended to create a presentation:
import React from "react";
abstract class _Presentation<P,S> extends React.Component{
static title: string
static description: string
static date: Date
static uri: string
}
export type PresentationType = typeof _Presentation
export function Presentation<P,S>(mandatory: {
title: string
description: string
date: Date
uri: string
}) {
return class extends _Presentation<P, S> {
static title = mandatory.title
static description = mandatory.description
static date = mandatory.date
static uri = mandatory.uri
}
}
When I try to implement a presentation everything goes well and I can create a component like so:
import React from 'react';
class SomePresentation extends Presentation({
title: "My presentation's title",
description: "My presentation's description",
date: new Date(2021, 1, 22, 15, 30, 0),
uri: "/presentation-slug"
}) {
render = () => (
<div>
Test
</div>
);
}
export default SomePresentation;
Everything starts coming apart when I start using states. There seems to be something amiss in my abstract's components typing. I can't figure out how to properly make this work with the right types.
import React from 'react';
class SomePresentation extends Presentation<{}, {
width: number,
height: number
}>({
title: "My presentation's title",
description: "My presentation's description",
date: new Date(2021, 1, 22, 15, 30, 0),
uri: "/presentation-slug"
}) {
constructor(props: any) {
super(props);
this.state = {width: 0, height: 0}
this.updateWindowDimensions = this.updateWindowDimensions.bind(this);
}
componentDidMount() {
this.updateWindowDimensions();
window.addEventListener('resize', this.updateWindowDimensions);
}
componentWillUnmount() {
window.removeEventListener('resize', this.updateWindowDimensions);
}
updateWindowDimensions() {
this.setState({width: window.innerWidth, height: window.innerHeight});
}
render = () => (
<div style={{width: this.state.width, height:this.state.height}}>
</div>
);
}
export default SomePresentation;
Where I'm using this.state.width
I receive the following error:
Property 'width' does not exist on type 'Readonly<{}>'
I've already spent several hours search on how to correctly use the types here but I'm at a lost. Who can point me in the right direction?
EDIT:
To further clarify I'm using these static variables to load a list of presentations on my index pages. I have an index.tsx
which exports all presentation components. Then on the main page I have the following code:
import * as presentations from './presentations/index'
...
const presentationClasses = Object.values(presentations);
...
{presentationClasses.map((presentation, index) =>
<Route key={presentation.uri} path={presentation.uri}>
{React.createElement(presentation)}
</Route>
)}
Upvotes: 1
Views: 1724
Reputation: 701
TypeScript uses compiler and when the compiler generates JavaScript, all abstract properties are removed. All type aliases and interfaces. So when you have an abstract class with all abstract properties, it's slow down, TS generates empty class/function. So use interfaces, look at this example:
class A {
inherited = 1;
}
interface B {
abstract: string;
}
class C extends A implements B {
abstract = "Yes, I implement"
}
This much better, compile result:
"use strict";
class A {
constructor() {
this.inherited = 1;
}
}
class C extends A {
constructor() {
super(...arguments);
this.abstract = "Yes, I implement";
}
}
So no empty class. I fully recommend this:
interface _PresentatonBase {
title: string;
description: string;
date: Date;
uri: string;
}
interface _Presentation<P, S> extends React.ComponentClass<P, S>, _PresentatonBase {}
function Presentation<S, P = {}>(mandatory: _PresentatonBase, state: S): _Presentation<P, S> {
return class SyntheticPresentation extends React.Component<P, S> {
state = state;
static title = mandatory.title;
static description = mandatory.description;
static date = mandatory.date;
static uri = mandatory.uri;
};
}
class SomePresentation extends Presentation({
date: new Date(),
description: "",
title: "",
uri: ""
}, {
width: 0,
height: 0
}) {
constructor(props: any) {
super(props);
this.updateWindowDimensions = this.updateWindowDimensions.bind(this);
}
componentDidMount() {
this.updateWindowDimensions();
window.addEventListener('resize', this.updateWindowDimensions);
}
componentWillUnmount() {
window.removeEventListener('resize', this.updateWindowDimensions);
}
updateWindowDimensions() {
this.setState({width: window.innerWidth, height: window.innerHeight});
}
render() {
return <div style={{width: this.state.width, height: this.state.height}}>
</div>;
}
}
Upvotes: 1