Welcome to WuJiGu Developer Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
749 views
in Technique[技术] by (71.8m points)

typescript elegantly enforce a constraint while preserving types

so originally to enforce a constraint, i'd just apply an interface like this

// this is the constraint
interface Topic {
  [key: string]: (...args: any[]) => Promise<any>
}

// object that must pass the constraint
const topic: Topic = {

  // GOOD: topic methods must conform
  async actuate(a: boolean) {}
}

// BAD: type signature broken
topic.actuate(true)

but the problem was that the typings for topic became reduced to the lowest-common-denominator, which is the constraint Topic — instead, i want topic to retain its detailed type signatures for methods like actuate

so i found this crappy hacky way to get the best of both worlds:

interface Topic {
  [key: string]: (...args: any[]) => Promise<any>
}

type TopicConstraint<T extends Topic> = T

const topic = {

  // GOOD: topic methods must conform
  async actuate(a: boolean) {}
}

// BAD: weird ugly unused type variable constraint hack
// BAD: all topic constraint errors actually land here
type TopicCheck = TopicConstraint<typeof topic>

// GOOD: type signature preserved
topic.actuate(true)

is there some more proper way to accomplish the same more elegantly? i don't know how to express this constraint situation in typescript

and yet i can't help but feel there's something special about

type TopicConstraint<T extends Topic> = T

which might be key... it makes me want to do illegal typescript things like

// invalid, but i need something in the same spirit
const topic = TopicConstraint<{
  async actuate(a: boolean) {}
}>

// invalid, but i need something in the same spirit
const topic: TopicConstraint<@self> = {
  async actuate(a: boolean) {}
}

i'm searching for a syntax that i can't quite find.. is there a different approach? thanks!

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

The way to deal with this is not to annotate your variable at all, but let the compiler infer its type. Then, sometime later, when you use that variable somewhere that expects a Topic, you'll get the desired error there:

const topic = {
    async actuate(a: boolean) { }
};
topic.actuate(true);

const badTopic = {
    oops(a: boolean) { }
}
badTopic.oops(true);

// later ...

function topicTaker(t: Topic) {
    // do something with a Topic
}

topicTaker(topic); // okay

topicTaker(badTopic); // error!
// ------> ~~~~~~~~
// Property 'oops' is incompatible with index signature.

So this works, and in some use cases this is really sufficient: you don't actually care if the variable is of the right type until you try to use it.

But what if this error check is too far from the object declaration to be helpful to you?


Well, if you want to catch the error earlier, you can do what I usually do and make a helper function:

const asTopic = <T extends Topic>(topic: T) => topic;

This constrained generic identity function just returns its argument at runtime. But at compile time it will require that topic is assignable to Topic without actually widening it to topic. Instead of annotating your variables, just initialize them to the return value of the helper function:

const topic = asTopic({
    async actuate(a: boolean) { }
});

topic.actuate(true); // okay

const badTopic = asTopic({
    oops(a: boolean) { } // error!
//  ~~~~
// void is not assignable to type Promise<any>
})

You can look at asTopic() two ways: as a replacement for annotation to avoid widening (i.e., use asTopic() instead of : Topic), or as an early warning that your object is not of the type you want (i.e., asTopic() acts like a topicTaker() that you call immediately at the creation site instead of deferring it).

Re-reading the question: this asTopic() helper function is probably the thing you were feeling around for with your TopicConstraint.

Playground link to code


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to WuJiGu Developer Q&A Community for programmer and developer-Open, Learning and Share
...