GOOGLE ADS

jueves, 28 de abril de 2022

Cree una mezcla de TypeScript que restrinja y sepa en qué tipo se mezcla

Estoy tratando de compartir algunos objetos modelo entre el cliente y el código del servidor (todo en TypeScript). Me gustaría crear el modelo "compartido" para que no sepa nada sobre dónde se está utilizando, y luego proporcionar una combinación para agregar la funcionalidad del servidor (por ejemplo, buscar desde una base de datos) en el lado del servidor, y un diferente mixin para proporcionar la funcionalidad del cliente (por ejemplo, buscar desde una API RESTful) en el cliente.

Aquí hay una versión simplificada de lo que tengo hasta ahora (aquí hay un enlace de juegos también):

// generic declaration of a constructor type to make things easier later on
type Constructor<T> = new (...args: any[]) => T;
// this is the base model class which can be used by client or server code
class Model extends Object {
public id: number = 1;
}
// this is a specific model subclass, also usable on client or server
class Widget extends Model {
public length: number = 10;
}
// this class is only used on the server, but needs to know the type of model being acted upon
class ServerHelper<T> {
public async insert(model: T): Promise<T> { /* do the insert */ return Promise.resolve(model); }
}
// this is the public interface for the server-side mixin
interface ServerModel<M extends Model> {
helper: ServerHelper<M>;
}
// this is the server-side mixin which
function ServerModel<B extends Constructor<Model>>(Base: B): B & Constructor<ServerModel<InstanceType<B>>> {
type M = InstanceType<B>;
const result = class BaseWithServerModel extends Base {
public helper: ServerHelper<M> = new ServerHelper<M>();
public async insert(): Promise<this> {
return await this.helper.insert(this);
}
};
return result;
}
class SpecialWidget extends ServerModel(Widget) {
// this class needs this.helper to be a `Helper<Widget>`
}

He estado tratando de adaptar el ejemplo de mezcla restringida, pero no puedo encontrar ninguna formulación que me dé acceso al tipo que se mezcla (es decir, Widgeten mi ejemplo) para que pueda pasarse a otros tipos genéricos.

En cambio, recibo un montón de errores como este en la class BaseWith...línea:

Class 'BaseWithServerModel' incorrectly extends base class 'M'.
'BaseWithServerModel' is assignable to the constraint of type 'M', but 'M' could be instantiated with a different subtype of constraint 'Model'.

o este en la return result;línea:

'BaseWithServerModel' is assignable to the constraint of type 'M', but 'M' could be instantiated with a different subtype of constraint 'Model'.

He pasado horas investigando en la web y jugando con varios encantamientos por mi cuenta, pero no tengo nada. ¿Alguna sugerencia sobre cómo debo declarar mi mixin para tener acceso a M?


Solución del problema

El compilador no puede razonar muy bien sobre el comportamiento de los tipos que dependen de un genérico no especificado como Bdentro del cuerpo de ServerModel. Especialmente cuando el tipo es un tipo condicional como el InstanceType<T>tipo de utilidad. Desafortunadamente, un código como el siguiente solo da como resultado un error:

function foo<T extends new () => object>(ctor: T) {
const oops: InstanceType<T> = new ctor(); // error
}

El sabe que new ctor()es un object, pero no se da cuenta de que debe ser un InstanceType<T>. Consulte microsoft/TypeScript#37705 para un problema relacionado.

Si desea que esto se compile, deberá usar algo como una afirmación de tipo para decirle al compilador que sabe que es correcto aunque no sea así:

function foo2<T extends new () => object>(ctor: T) {
const oops = new ctor() as InstanceType<T>; // okay
}

Entonces necesitarás algo como esto en tu código:

this.helper.insert(this as M)

Una limitación similar está presente con el tipo polimórficothis. Dentro del cuerpo de una clase, el thistipo es esencialmente un parámetro de tipo genérico restringido al tipo de la clase actual. Por lo tanto, el compilador no siempre estará seguro de si se puede asignar algún valor a this.

En tu caso,

 public async insert(): Promise<this> {
return await this.helper.insert(this as M);
}

el compilador es técnicamente correcto para quejarse. El tipo de retorno de insert()debe ser un Promise<this>donde thises el tipo de cualquier subclase de BaseWithServerModelque se esté utilizando. Pero this.helper.insert()solo regresa Promise<M>. Y es posible que thissea una subclase adecuada de M.

Si no le importa esa posibilidad porque es poco probable, puede usar otro tipo de aserción:

 public async insert(): Promise<this> {
return await this.helper.insert(this as M) as this;
}

Esto compila sin errores y te permite seguir con tu vida. Tal vez haya algunas soluciones mejores y más seguras, pero puede que no valga la pena.

En tu comentario mencionaste

 public async insert2(): Promise<this> {
await this.helper.insert(this as M);
return this;
}

que también puede funcionar.

Enlace del patio de recreo al código

No hay comentarios.:

Publicar un comentario

Flutter: error de rango al acceder a la respuesta JSON

Estoy accediendo a una respuesta JSON con la siguiente estructura. { "fullName": "FirstName LastName", "listings...