简体   繁体   中英

Calling methods in Typescript strict mode

I have this code, that I have converted from plain js in Typescript:

type Paragraph = { paragraph: (data: { text: string }) => string };
type Header = { header: (data: { level: string; text: string }) => string };
type List = { list: (data: { style: string; items: string[] }) => string };

type Block = {
    type: string;
    data: string;
};

export default class HTMLEngine {
    static get generators(): Paragraph & Header & List {
        return {
            paragraph: (data: { text: string }) => `<p>${data.text}</p>`,

            header: (data: { level: string; text: string }) => `<h${data.level}>${data.text}</h${data.level}>`,

            list: (data: { style: string; items: string[] }) => {
                const tagname = `${data.style.charAt(0)}l`;
                return `<${tagname}>${data.items.map(item => `<li>${item}</li>`)}</${tagname}>`;
            }
        };
    }

    render(ejsData: string): string {
        try {
            const parsed = JSON.parse(ejsData).blocks;
            return parsed.reduce((output: string, block: Block) => output + this.renderBlock(block), '');
        } catch (e) {
            return ejsData || '';
        }
    }

    renderBlock(block: Block): string {
        return `${HTMLEngine.generators[block.type](block.data)}\n`;
    }
}

It works perfectly but in strict mode I get this error on this line

return `${HTMLEngine.generators[block.type](block.data)}\n`;
(property) HTMLEngine.generators: Paragraph & Header & List
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Paragraph & Header & List'.
No index signature with a parameter of type 'string' was found on type 'Paragraph & Header & List'.ts(7053)

I have no clue on fixing this error. Could someone help?

I think you want Block to be a discriminated union type where its type property determines exactly what type the data property will be. You can write that manually, or, if you want, generate this programmatically from your Paragraph , Header , and List :

type AllBlocks = Paragraph & Header & List;
type Block = { [K in keyof AllBlocks]: { type: K, data: Parameters<AllBlocks[K]>[0] } }[keyof AllBlocks]
/* type Block = {
    type: "paragraph";
    data: {
        text: string;
    };
} | {
    type: "header";
    data: {
        level: string;
        text: string;
    };
} | {
    type: "list";
    data: {
        style: string;
        items: string[];
    };
} */

This doesn't exactly solve your problem though, due to TypeScript's lack of support for what I've been calling correlated record types (see Microsoft/TypeScript#30581 for more information). The compiler sees HTMLEngine.generators[block.type] and block.data as unrelated union types that might not be usable together, even though block.data is always appropriate for the function you get with block.type .

Currently you can work around this by writing redundant code to walk the compiler through the possibilities:

renderBlock(block: Block): string {
    switch (block.type) {
        case "header":
            return `${HTMLEngine.generators[block.type](block.data)}\n`;
        case "list":
            return `${HTMLEngine.generators[block.type](block.data)}\n`;
        case "paragraph":
            return `${HTMLEngine.generators[block.type](block.data)}\n`;
    }
}

or, (more reasonably), use a type assertion to tell the compiler not to worry about its inability to verify safety here:

renderBlock(block: Block): string {
    const generator = HTMLEngine.generators[block.type] as (x: Parameters<AllBlocks[keyof AllBlocks]>[0]) => string;
    return `${generator(block.data)}\n`;
}

or the simpler

renderBlock(block: Block): string {
    return `${HTMLEngine.generators[block.type](block.data as any)}\n`;
}

Playground link to code

Issue is that your Block type isn't precise enough. So the type of a Block shouldn't be just a string but 'paragraph' | 'header' | 'list' 'paragraph' | 'header' | 'list' 'paragraph' | 'header' | 'list' to match the types Paragraph , Header and List .

Same for the data attribute, it should be { text: string } & { level: string; text: string } & { style: string; items: string[] } { text: string } & { level: string; text: string } & { style: string; items: string[] } { text: string } & { level: string; text: string } & { style: string; items: string[] } .

So your Block type will look like that:

type Block = {
    type: 'paragraph' | 'header' | 'list';
    data: { text: string } & { level: string; text: string } & { style: string; items: string[] };
};

Playground Link

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM