TypeScript5minutos de leitura

Funções Genéricas Inteligentes

Num refactor recente precisei devolver só name e age de um User sem perder a ligação com o tipo original. A primeira versão funcionava, e era descartável.

A versão que você não reutiliza

type User = {
    id: string;
    name: string;
    age: number;
    email: string;
}

function getNameAndAge(user: User): { name: string; age: number } {
  return {
    name: user.name,
    age: user.age,
  };
}

const user: User = { 
    id: '1234', 
    name: 'William', 
    age: 36, 
    email: 'iwilldev@outlook.com.br'
}

const userNameAndAge = getNameAndAge(user);
// valor: { "name": "William", "age": 36 }
// tipo:  { name: string, age: number }

O retorno é um objeto novo, sem relação com User. Se email virar obrigatório em outro formato, você reescreve a função ou inventa outro tipo.

Generic + inferência: um pick que presta

function pick<T, K extends keyof T>(
  obj: T, 
  keys: K[]
): Pick<T, K> {
  const result = {} as Pick<T, K>;
  for (const key of keys) {
    result[key] = obj[key];
  }
  return result;
}
const userNameAndAge = pick(user, ["name", "age"])
// valor: { "name": "William", "age": 36 }
// tipo: Pick<User, "name" | "age">

O TypeScript sabe quais chaves você pediu. Autocomplete e erro de compilação acompanham.

Dá para estender na mesma linha

omit: tira chaves e mantém o resto tipado:

type OmitKeys<T, K extends keyof T> = {
  [P in keyof T as P extends K ? never : P]: T[P];
};

function omit<T, K extends keyof T>(
  obj: T,
  keys: K[]
): OmitKeys<T, K> {
  const result = { ...obj };
  keys.forEach(key => delete result[key]);
  return result;
}

const userWithoutId = omit(user, ["id"])
// tipo: OmitKeys<User, "id">

merge: junta dois objetos sem perder os dois tipos:

function merge<T, U>(a: T, b: U): T & U {
  return { ...a, ...b };
}

type Role = {
  role: 'admin' | 'editor' | 'viewer';
  permissions: Array<'read' | 'write' | 'delete' | 'update'>;
};

const role: Role = {
    role: 'viewer',
    permissions: ['read']
}

const userWithRole = merge(user, role)
// tipo: User & Role

Lodash e similares já trazem isso. Eu uso helpers locais quando o escopo é pequeno e quero zero dependência extra, com tipos que refletem exatamente o que o projeto precisa.