typescript - Angular 4, Egenskapen 'XX' saknas i typen '{xx, xx}'
Jag är ny på den här typen av skript. Här är min modellklass:
export class Project {
constructor(
public projectId: number,
public description: string,
public startDate: Date,
public endDate: Date
) {}
getDaysRemaining() {
var result = this.endDate.valueOf() - Date.now().valueOf();
result = Math.ceil(result / (1000 * 3600 * 24));
return result < 0 ? 0 : result;
}
}
Och nedan är min initialisering, hänvisad till Angular-handledningen:
let PROJECTS: Project[] = [
{ projectId: 1, description: "Sample", startDate: new Date("2016-12-12"), endDate: new Date("2017-1-13") },
{ projectId: 2, description: "Sample 2", startDate: new Date("2016-12-12"), endDate: new Date("2017-1-13") }
];
Modellen förväntas användas så här i mallen:
{{ project.getDaysRemaining() }}
Men jag får felet:
Egenskapen 'getDaysRemaining' saknas i typen '{ projectId: number; description: string; startDate: Date; endDate: Date; }'.
Varför måste jag initialisera funktionen i TypeScript? Eller missar jag något?
Svar #1
Ditt problem har att göra med hur en klass instansieras.
Istället för:
let PROJECTS: Project[] = [
{ projectId: 1, description: "Sample", startDate: new Date("2016-12-12"), endDate: new Date("2017-1-13") },
{ projectId: 2, description: "Sample 2", startDate: new Date("2016-12-12"), endDate: new Date("2017-1-13") }
];
du bör göra:
let PROJECTS: Project[] = [
new Project(1, "Sample", new Date("2016-12-12"), new Date("2017-1-13") ),
new Project(2, "Sample 2", new Date("2016-12-12"), new Date("2017-1-13")
];
Förklaring
Project är en klass. getDaysRemaining() är en medlem av den klassen. För att skapa en instans av en klass kan du använda new Project(...).
{ projectId: 1, ... } är ett objekt som inte är en instans av klassen Project. Därför är alla medlemmar definierade mellan klamrarna som egenskaper.
Du kan kontrollera om { projectId: 1, ... } är en instans av Project genom att helt enkelt använda { project1: 1, ... } instanceof Project - vilket resulterar i false.
ES6? TypeScript har mycket av syntaxen gemensamt med de senaste framstegen i JavaScript-språket. Du kan definiera klasser i ES6 på liknande sätt som du gör i TypeScript. Självklart, bortsett från typer och modifierare eftersom JavaScript inte är ett statiskt typat språk, utan ett dynamiskt typat.
class Project {
constructor(projectId, description, startDate, endDate) {
this.projectId = projectId;
this.description = description;
this.startDate = startDate;
this.endDate = endDate;
}
getDaysRemaining() {
var result = this.endDate.valueOf() - Date.now().valueOf();
result = Math.ceil(result / (1000 * 3600 * 24));
return result < 0 ? 0 : result;
}
}
I ES6 kan du göra new Project(), vilket ger undefined för alla argument, men i TypeScript går det inte eftersom det saknas konstruktörsargument, och detta kontrolleras vid kompileringstid, och om det finns en bra IDE som kontrollerar TypeScript-syntax, också vid design tid.
Ett alternativ för att tillåta detta i TypeScript är att definiera valfria argument på följande sätt (se frågetecknet nära argumentet):
constructor(
public projectId?: number,
public description?: string,
public startDate?: Date,
public endDate?: Date
) { }
Vanligt problem - hämta viss data från en källa, som en webbtjänst
Du kan stöta på situationen när du får dina Project-data från en webbservice. I det fallet kommer datan att deserialiseras till vanliga objekt { projectId: 1, ... }. Att erhålla Project-instanser från den datan kan inte göras implicit och ytterligare arbete behöver utföras.
Det enklaste är att skicka varje egenskap från ena sidan till den andra i en fabriksmetod:
class Project implements IProject {
...
static create(data: IProject) {
return new Project(data.projectId, data.description, data.startDate, data.endDate);
}
...
}
och du kan använda ett gränssnitt (istället för any) för dina data för att ge ytterligare hjälp vid inmatning, och även implementera det (class Project implements IProject):
interface IProject {
projectId: number;
description: string;
startDate: Date;
endDate: Date;
}
Ett annat alternativ är att använda valfria argument, men detta kanske inte gäller i alla fall:
class Project implements IProject {
...
constructor(
public projectId?: number,
public description?: string,
public startDate?: Date,
public endDate?: Date
) { }
static create(data: IProject) {
return Object.assign(new Project(), data);
}
...
}
så gränssnittet skulle ha valfria argument (kolla frågetecknet) om det ska implementeras som ovan:
interface IProject {
projectId?: number;
description?: string;
startDate?: Date;
endDate?: Date;
}
Komplilation och gränssnitt medan TypeScript transpileras till JavaScript kommer alla gränssnitt att tas bort från den slutliga koden. Det finns inget "interface"-koncept i JavaScript. Detta är en konstruktion i TypeScript vars syfte är att tillhandahålla ytterligare typinformation.
försök att vara SOLID - S=Enkelt Ansvar-principen
I koden ovan har Project två ansvar (inte ett). Det första är att hålla viss Project-data. Det andra är att räkna ut de återstående dagarna - getDaysRemaining. Detta kan vara för mycket för en enda klass.
En ren lösning skulle kunna vara att dela upp detta i två.
Ha ett gränssnitt för att representera data för Project:
interface IProject {
projectId: number;
description: string;
startDate: Date;
endDate: Date;
}
Och flytta getDaysRemaining() till en annan klass som:
class ProjectSchedulingService {
getDaysRemaining(project: IProject) {
var result = project.endDate.valueOf() - Date.now().valueOf();
result = Math.ceil(result / (1000 * 3600 * 24));
return result < 0 ? 0 : result;
}
}
Detta har fördelen att datan - IProject - är separat och ytterligare funktionalitet kan placeras i andra klasser som gör sitt eget arbete - som - ProjectSchedulingService.
En annan fördel är att IProject kan hjälpa till att arbeta med enkla objekt istället för att skapa ytterligare klasser. Du kan hämta data från en webbtjänst, kasta det till ett gränssnitt och arbeta på ett typat sätt.
En annan fördel är att verksamheten kan delas upp i flera klasser - som ProjectSchedulingService - och därmed kunna enhetstestas individuellt istället för att ha en tung monolitisk klass med data och all relaterad verksamhet i sig.
En annan fördel är att ha flera klasser hjälper till att definiera beroenden mellan klasser mer lätt, vilket behåller affärssemantiken, och möjliggör att mocka alla beroenden av en klass och enhetstesta affärslogiken i den aktuella klassen.
Ta Angular till exempel, du kan ha flera tjänster (klasser) som kan injiceras vid behov. Att ha en monolitisk tjänst skulle innebära att man skickar något där bara en del av det behövs. Detta bryter alltså också mot gränssnittsskillnad.
Detta kan låta dumt vid första anblicken, eftersom koden är så liten, men med tiden kommer detta att fortsätta växa och nästa utvecklare kanske fortfarande fortsätter utveckla på samma monolitiska sätt istället för att refaktorera. Efter ett tag kommer många uppskattningar att öka eller brytas på grund av svårigheten att underhålla koden. Även cyklomatisk komplexitet blir värre och även kodtäckningen för enhetstester.
