简体   繁体   中英

Change type of passed object in TypeScript void function

Suppose I have a function changeType that takes in a Foo and converts it into a Bar .

interface Foo {
    num: number;
}

interface Bar {
    str: string;
}

function changeType(foo: Foo): void {
    (foo as any).str = foo.num.toString();
    delete (foo as any).num;
}

let foobar: Foo | Bar = { num: 1 };
changeType(foobar);

Is there any way to tell the TypeScript compiler that running changeType will necessarily change the passed in argument to a different type? I thought maybe there might be something like:

function changeType(foo: Foo): foo as Bar { /*...*/ }

and then the function would have to return foo rather than not returning anything. Even better would be some strange syntax like function(...): void, foo => Bar { ... } .

I know this is a very strange case, but it is part of handling Object.freeze --the part that seems to go ignored when discussing how to implement it. Typically the conversation focuses on the returned value, and not the passed value itself, which is modified.

Another case revolves around inheritance. Suppose you have a Triangle and a Parallelogram that both inherit from Shape .

type Point = [number, number];

interface Shape {
    name: 'triangle' | 'square';
    points: Point[];
}

interface Triangle extends Shape {
    name: 'triangle';
    points: [Point, Point, Point];
}

interface Parallelogram extends Shape {
    name: 'parallelogram';
    points: [Point, Point, Point, Point];
}

Now you want to mutate the Triangle into a Parallelogram , rather than creating a new Shape object.

function makeParallelogram(triangle: Triangle): void;
function makeParallelogram(shape: Shape): void {
    Object.assign(shape, {
        name: 'parallelogram',
        points: (shape as Triangle).points.push(calcFourthPoint(shape as Triangle);
    });
}

function calcFourthPoint(triangle: Triangle): Point {
    /* some geometry here */
}

Exactly what you want is not possible with TypeScript. This issue from GitHub is very close to what you want. TypeScript cannot deeply analyze control flow of your code, so it will have troubles inferring correct types from what function does.

But pretty same effect can be achieved if using return statement, overloading and type guards .

function isFoo(f: Foo | Bar): f is Foo {
    return (f as Foo).num !== undefined;
}

function changeType(foo: Foo): Bar
function changeType(foo: Bar): Foo
function changeType(foo: Foo | Bar): Foo | Bar {
    if (isFoo(foo)) {
        // Convertion logic can be anything. But I suggest not to mutate original object but create new one and fill it with props.
        let res: Bar = { str: foo.num.toString() }
        return res;
    }
    else {
        let res: Foo = { num: +foo.str }
        return res;
    }
}

And the usage

let foobar: Foo | Bar = { num: 1 };   // Type is Foo
let r: Bar = changeType(foobar);      // Return is Bar

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