1

Is it somehow possible to create a subtracted type in Typescript? I'm thinking of a user-case when a React component would expose just subset of if props to components user. React-redux connect example:

import {Component, ComponentType} from 'react';

export function connect<S, A>(state: () => S, actions: A){
  return function createConnected<P>(component: ComponentType<P>){
    return class Connect extends Component<P-S-A>{ // <-- 
      // ...
    }    
  }
}

After reading: Exclude property from type

Seems that I got this working...

import {Component, ComponentType} from 'react';

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

function connect<S, A>(state: () => S, actions: A) {
    return function createConnect<P extends S & A>(C: React.ComponentType<P>): ComponentType<Omit<P, keyof S | keyof A>> {
        return class Connect extends Component<Omit<P, keyof S | keyof A>> {
            // ...
        };
    };
}

.... But I don't understand how.


Update 2:

After playing with this some more, I discovered a cleaner way (in my opinion), to describe a subtracted type:

// LeftOuterJoin
type Subtract<T, V> = Pick<T, Exclude<keyof T, keyof V>>;

function connect<S, A>(state: () => S, actions: A) {
    return function createConnect<P>(C: ComponentType<P>) {
        return class Connect extends Component<Subtract<P, S & A>> {
                  // ...
        };
    };
}
Nabuska
  • 443
  • 9
  • 17
  • 2
    Possible duplicate of [Exclude property from type](https://stackoverflow.com/questions/48215950/exclude-property-from-type). Using the `Omit` type from that question, you could write your type as `Omit

    `

    – CRice Aug 06 '18 at 20:57

1 Answers1

4

.... But I don't understand how.

Built-in Exclude type is used here to operate on unions of string literals.

Exclude<keyof T, K> excludes all keys from keyof T which are in K, assuming that K is also a union of string literals - and the constraint K extends keyof T ensures that, declaring that K must be a subset of keys of T.

Another built-in type, Pick<T, K>, allows to create a new type from T which has only the keys that are in K - again, assuming that K is a union of string literals and a subset of keyof T.

Using Pick and Exclude as building blocks, you can express type subtraction of one type from another as "pick only these properties from one type which are not present in another", with Exclude performing "not present" operation on keys.

Here it is in expanded form, making ComponentProps from Props excluding the props in either S or A:

type ComponentProps = Pick<Props, Exclude<keyof Props, keyof S | keyof A>>
artem
  • 46,476
  • 8
  • 74
  • 78
  • It seems that `Exclude` works as well. The part `keyof S | keyof A`still does not make sense to me... – Nabuska Aug 07 '18 at 08:03
  • 1
    The two are equivalent. Imagine `{foo: string}` and `{bar: number}`, `keyof S -> 'foo'` and `keyof A -> 'bar'`, therefore, their union `keyof S | keyof A -> 'foo' | 'bar'`. The intersection `S & A -> {foo: string; bar: number}`, and the keyof of that is also `'foo' | 'bar'`. – Madara's Ghost Aug 07 '18 at 08:57
  • @Nabuska yes `keyof (S & A)` is exactly the same type as `keyof S | keyof A`. Intersection type must have properties of all its member types, that is, its properties are union of properties of intersection members' types. See also https://stackoverflow.com/a/38857724/43848 – artem Aug 07 '18 at 18:10