ค้นหาเว็บไซต์

สร้างมิกซินของ TypeScript ที่ทั้งจำกัดและรู้เกี่ยวกับชนิดที่มันถูกมิกซ์เข้าไป


ผมพยายามที่จะแบ่งปันอ็อบเจ็กต์โมเดลบางส่วนระหว่างโค้ดฝั่งไคลเอนต์และเซิร์ฟเวอร์ (ทั้งหมดใน TypeScript) ผมอยากสร้างโมเดล "แชร์" ให้มันไม่รู้เรื่องเกี่ยวกับที่ที่มันถูกใช้งาน จากนั้นจึงเพิ่มมิกซินเพื่อเพิ่มฟังก์ชันของเซิร์ฟเวอร์ (เช่น ดึงข้อมูลจากฐานข้อมูล) ฝั่งเซิร์ฟเวอร์ และมิกซินอื่นเพื่อเพิ่มฟังก์ชันของไคลเอนต์ (เช่น ดึงข้อมูลจาก RESTful API) ฝั่งไคลเอนต์

นี่คือเวอร์ชันง่าย ๆ ของสิ่งที่ฉันมีจนถึงตอนนี้ (นี่คือลิงก์ไปยังสนามทดลองด้วย):

// 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>`
}

ฉันพยายามที่จะปรับตัวอย่าง constrained mixin แต่ฉันไม่สามารถหาสูตรใด ๆ ที่ให้ฉันเข้าถึงประเภทที่ถูกผสมเข้าไปได้ (เช่น Widget ในตัวอย่างของฉัน) เพื่อที่จะสามารถส่งไปยังประเภททั่วไปอื่น ๆ ได้

แต่กลับได้รับข้อผิดพลาดหลายอย่างเช่นนี้ในบรรทัด return result;:

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

ฉันใช้เวลาหลายชั่วโมงในการค้นหาข้อมูลบนเว็บและลองปรับแต่งสูตรต่างๆ ด้วยตัวเอง แต่ก็ยังไม่ได้อะไร มีคำแนะนำไหมว่าฉันควรประกาศมิกซ์อินของฉันอย่างไรเพื่อให้สามารถเข้าถึง M ได้?

คำตอบ #1

คอมไพเลอร์ไม่สามารถวิเคราะห์พฤติกรรมของชนิดที่ขึ้นอยู่กับเจนเนอริกที่ไม่ได้ระบุ เช่น B ภายในตัวของ ServerModel ได้ดีนัก โดยเฉพาะเมื่อชนิดนั้นเป็นชนิดตามเงื่อนไข เช่น ประเภทยูทิลิตี้ InstanceType<T> ดังนั้นโชคร้ายที่โค้ดเช่นต่อไปนี้จะทำให้เกิดข้อผิดพลาด:

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

รู้ว่า new ctor() เป็น object แต่ไม่ตระหนักว่ามันต้องเป็น InstanceType<T> ดู microsoft/TypeScript#37705 สำหรับปัญหาที่เกี่ยวข้อง

ถ้าคุณต้องการให้สิ่งนี้คอมไพล์ คุณจะต้องใช้สิ่งที่คล้ายกับการยืนยันชนิดเพื่อบอกคอมไพเลอร์ว่าคุณรู้ว่ามันถูกต้องแม้ว่ามันจะไม่ถูกต้อง

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

ดังนั้นคุณจะต้องมีบางสิ่งแบบนี้ในโค้ดของคุณ:

this.helper.insert(this as M)

ข้อจำกัดที่คล้ายกันมีอยู่กับประเภท this แบบพหุภาพ ภายในตัวเนื้อหาของคลาส ประเภท this เป็นโดยสาระสำคัญเป็นพารามิเตอร์ประเภททั่วไปที่ถูกจำกัดให้ตรงกับประเภทของคลาสปัจจุบัน ดังนั้นคอมไพเลอร์จะไม่สามารถมั่นใจเสมอว่าค่าบางค่าสามารถกำหนดให้กับ this ได้

ในกรณีของคุณ,

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

คอมไพเลอร์ถูกต้องทางเทคนิคที่จะแจ้งข้อผิดพลาด ประเภทที่คืนค่าของ insert() ควรเป็น Promise<this> โดยที่ this คือประเภทของ subclass ของ BaseWithServerModel ที่กำลังใช้งานอยู่ แต่ this.helper.insert() คืนค่าเพียง Promise<M> และมีความเป็นไปได้ที่ this จะเป็น subclass ที่ถูกต้องของ M

ถ้าคุณไม่สนใจความเป็นไปได้นั้นเพราะมันไม่น่าจะเกิดขึ้น คุณสามารถใช้การยืนยันประเภทแบบอื่นได้:

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

โค้ดนี้คอมไพล์ได้โดยไม่มีข้อผิดพลาดและให้คุณดำเนินชีวิตต่อไปได้ อาจมีวิธีแก้ปัญหาที่ดีและปลอดภัยต่อชนิดข้อมูลมากกว่านี้ แต่บางทีอาจไม่คุ้มค่า

ในความคิดเห็นของคุณคุณได้กล่าวถึง

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

ซึ่งอาจใช้ได้ดีเช่นกัน

ลิงก์ Playground ไปยังโค้ด