DEV Community

Ski
Ski

Posted on

Your code probably does not need TypeScript's Omit<>

What are you optimising your code for? Would it be readability or rather least amount of meaningful words used? If trying to optimize code for later then Omit<> might be the tool for the job. If however readability is is a concern then Omit<> might be making your code worse.

Take this example, a seemingly straight forward case for applying Omit<>

type HigherLevelProps = Omit<LowerLevelProps, 'something'>

const HigherLevelCode = (props: HigherLevelProps ) => {
   ...

   return LowerLevelCode({
     something: "...",
     ...props
   })
}
Enter fullscreen mode Exit fullscreen mode

When I am reading this code trying to understand how it works or what it needs to work, what would help me my understanding? Knowing what properties it needs - would be very helpful. Knowing what properties it doesn't need? Not really helpful at all. I cannot understand this code unless I dig deeper into LowerLevelCode - digging deeper will occupy more of my short term memory and/or will require me to manage more vertical code split windows before I can begin to understanding what the code is doing and how I could use it.

Let's check what Law of Demeter tells us. Principle talks about building code in a way that it can be successfully used and understood with least amount of knowledge. If I would try to apply it to my example above - I should be able to use HigherLevelCode without having to learn a lot about the LowerLevelCode. Yet applying Omit<> and spread operator (...props) achieves the exact opposite: my code needs to be coupled not just with HigherLevelCode but also with LowerLevelCode thus if I ever need to refactor LowerLevelCode it will not be enough to only change HigherLevelCode but also all the usage of it.

Let's try to improve the example to fix the problems we talked about:

type HigherLevelProps = {
   propA: ...,
   propB: ...
}

const HigherLevelCode = (props: HigherLevelProps) => {
   const {propA, propB} = props
   ...

   LowerLevelCode({
      something: "...",
      propA,
      propB,
   })
}
Enter fullscreen mode Exit fullscreen mode

It may not be the shortest possible way to write code. But it results in code that I can read and understand faster as I no longer have to jump into implementation of LowerLevelCode to learn about necessary properties. It also makes refactoring easier to do in smaller iterative chunks thanks to code being less coupled (code that uses HighLevelCode now knows nothing about LowLevelCode and so each piece can be easily changed independently in smaller pull requests).

This may look like going against 'DRY' (Do not repeat yourself) concept creating repetition. Yet this is not necessary a situation where some amount of repetition is bad. In a situations where it is hard to maintain consistency between the repeated concepts for example if implementation of an algorithm is repeated in a few places - that's a big problem. However repeating attribute names is not necessary a problem: it helps to communicate meaning as opposed to stuffing type algebra that may be meaningful to TypeScript but fora human it requires additional mental power to process it and to understand it.

Surely if TypeScript added Omit<> it must be useful somewhere? Yes in some situations it is useful or even necessary. Typically that would be framework or library level code - code that is designed to be very generic. For example, if I want to implement a function that allows to bind a single property key of a function input parameter, Omit<> can be helpful to create type declaration for such function:

/**
 * Usage example:
 *   const xy = ({x, y}) => { ... } 
 *   const y = bindProp(xy, 'x', 0)
 */
const bindProp = <Props, ReturnType, Key extends keyof Props>
  (
    f: (props: Props) => ReturnType,
    key: Key, 
    value: Props[Key]
): (props: Omit<Props, Key>) => Return => {
   return (props) => f({...props, [key]: value})
}
Enter fullscreen mode Exit fullscreen mode

notice how complicated TypeScript type declaration is, compared to the simple javascript function? When implementing libraries or frameworks and the code can simplify business logic in application's code it may be worthwhile to pay the price of complexity implementing very generic types.
If however code is in application level as opposed to a generic library - aiming for simplicity and avoiding cleverness is usually better bet. We can assume that library's code will seldom need to change and we can assume that if it needs to change it would be done by specialists who know TypeScript down to every details. On other hand when writing high level application code we should assume that it may be written and read by developers who are either less experienced (entry level roles) or might be coming from different background (a backend engineer trying to fix a simple UI bug).

Top comments (0)