Typescript: Sugestão: o tipo primitivo `object`

Criado em 26 jan. 2015  ·  17Comentários  ·  Fonte: microsoft/TypeScript

O tipo primitivo object

Esta proposta descreve um novo tipo primitivo para o texto digitado object .

Caso de uso

A API do núcleo do JavaScript contém algumas funções que usam object como parâmetro:

  • Object.getPrototypeOf
  • Object.getOwnPropertyDescriptor
  • Object.create
  • ... etc

Atualmente não há como no texto digitado evitar a passagem de outro tipo primitivo ( string , number , etc ...) para essas funções.
Por exemplo, Object.create('string') é uma instrução typecript perfeitamente válida, mesmo que termine com um erro.
A criação de um novo tipo primitivo object permitiria modelar mais corretamente a assinatura dessas funções e de todas as funções que compartilham restrições semelhantes.

Verificação de tipo

Atribuições

O tipo object é o equivalente a {} menos a capacidade de atribuição de outro tipo básico, o que significa que:

  • quaisquer outros tipos básicos não são atribuíveis a object
  • qualquer tipo não básico pode ser atribuído a object
  • o objeto só pode ser atribuído a {} e any

Este comportamento é coerente com o funcionamento do javascript, o tipo representa todos os valores que respeitam a restrição typeof value === 'object' mais undefined .

Tipo de guarda

Um novo tipo de guarda deve ser introduzido para object : typeof value === 'object' .
Opcionalmente, o compilador também pode aceitar a comparação com o elenco de função Object : Object(value) === value .

Suggestion help wanted

Comentários muito úteis

+1 nesta proposta. Eu não consideraria WeakMap uma API "especialista", mas considero isso razão suficiente por si só para implementar isso. Uma vez que o tempo de execução faz uma distinção entre tipos de objetos e tipos primitivos, só faz sentido ter um recurso no TypeScript para fazer a distinção também.

Esta proposta também resolverá o problema de digitar "sacos de objetos" onde cada propriedade é opcional, mas outros tipos primitivos devem ser desabilitados.

Todos 17 comentários

Seria útil listar algumas APIs além das funções Object. que se beneficiariam disso.

No núcleo da API javascript, exceto Object apenas talvez algumas construções es6 se beneficiem disso (WeakMap, Proxy etc.).
Mas, por exemplo, cada função de coleção de sublinhado também ganharia uma melhor tipagem, estruturas como React usam um método 'setState' que aceita apenas objeto ou null , ImmutableJS, Mori etc.

Editar: trabalho de coleta de sublinhado com qualquer tipo

Discutido na reunião de revisão de sugestões. Tudo isso faz sentido, mas precisamos justificar a complexidade de um novo tipo primitivo com o número de erros / riqueza de descrição que ele é capaz de fornecer. Basicamente, mais exemplos de APIs que não podem operar em primitivas, especialmente aquelas que não são APIs "especializadas" (por exemplo, Proxies)

Postando mais como referência do que recomendação: wink:

interface String { _primitiveBrand?: string; }
interface Boolean { _primitiveBrand?: boolean; }
interface Number { _primitiveBrand?: number; }

interface ObjectOnly {  _primitiveBrand?: void; }

function fn(x: ObjectOnly) { }

// Error
fn(43);
// Error
fn('foo');
// OK
fn({});
// OK
fn(window);

+1 nesta proposta. Eu não consideraria WeakMap uma API "especialista", mas considero isso razão suficiente por si só para implementar isso. Uma vez que o tempo de execução faz uma distinção entre tipos de objetos e tipos primitivos, só faz sentido ter um recurso no TypeScript para fazer a distinção também.

Esta proposta também resolverá o problema de digitar "sacos de objetos" onde cada propriedade é opcional, mas outros tipos primitivos devem ser desabilitados.

Object.observe requer um destino não primitivo também: http://arv.github.io/ecmascript-object-observe/#Object.observe

Acho que o título é um pouco confuso, pois menciona a palavra "primitivo". Isso poderia ser chamado de _ "tipo de objeto ECMAScript" _ ou _ "Tipo de objeto Runtime" _. Que seria atribuível de qualquer coisa diferente de tipos primitivos (incluindo undefined tipo e símbolos), tipos de função ou construtor. Os critérios para o que é um tipo object legal devem ser baseados na seguinte proteção:

let isObject (x) => typeof x === "object";

De acordo com o MDN , esta é a saída de typeof :

Indefinido : "indefinido"
Nulo : "objeto" (veja abaixo)
Boolean : "boolean"
Número : "número"
String : "string"
Símbolo (novo no ECMAScript 2015) : "símbolo"
Objeto de host (fornecido pelo ambiente JS) : dependente da implementação
Objeto de função (implementa [[Call]] nos termos da ECMA-262) : "função"
Qualquer outro objeto : "objeto"

Sem este tipo, não há como combinar corretamente um tipo preciso para a proteção acima mencionada, nem mesmo como uma proteção definida pelo usuário. Isso também é essencial para uso com qualquer função que exija estritamente um objeto que tenha propriedades e permita for in loops e chamadas para o protótipo Object , e para implementar restrições genéricas como T extends object e, em seguida, referenciar propriedades com segurança ou usar as funções de protótipo.

Ter funções e construtores excluídos, entretanto, seria um pouco limitante em alguns casos, então poderia haver outro tipo que os incluiria também e chamado de _ "tipo de objeto não primitivo" _. Uma solução alternativa parcial pode ser a união object | Function embora sejam necessários casts ou protetores adicionais para lidar com isso corretamente.

Aceitando PRs. Este parece ser um problema comum; esta será uma mudança significativa para qualquer pessoa corajosa o suficiente para ter escrito interface object { ... } . A implementação deve ser direta e seguir as regras descritas acima.

Nota lateral: Estamos todos nos perguntando onde @fdecampredon esteve!

@RyanCavanaugh Mudança de cidade de trabalho e, infelizmente, não muitos

Ótimo. Quero usar esse tipo no TS1.8.

Eu também gostaria de ver algo como um tipo object . No entanto, eu questiono se ele deve estar em conformidade com typeof value === 'object' , pois isso excluiria funções. Em vez disso, parece-me que o tipo object potencial deve refletir o tipo de linguagem Object, que inclui funções. Existem alguns motivos para isso:

  • APIs que aceitam objetos simples sempre, pelo que eu sei, também aceitam funções.
  • As funções são herdadas do construtor Object .

Vou levar isso em ordem.

APIs

APIs como WeakMap que aceitam apenas objetos, aceitam o tipo de linguagem Object, não apenas objetos simples. O seguinte está ok:

function theAnswer() {}

let map = new WeakMap();

map.set(theAnswer, 42);
console.log(map.get(theAnswer)); // 42

Isso é verdadeiro tanto para APIs personalizadas quanto para APIs integradas. Se espero um objeto, geralmente só quero um pacote de propriedades. Funções são apenas pacotes de propriedades que podem ser chamados.

Herança

Como Function estende o construtor Object , podemos usar os métodos estáticos e de instância disponíveis em Object nas funções também. Por exemplo, o seguinte é possível:

class DeepThought {
  static getAnswer() {
    return 42;
  }
}

let computer = Object.create(DeepThought);

console.log(computer.getAnswer()); // 42
console.log(Object.getPrototypeOf(computer)); // [Function: DeepThought]

Embora o exemplo acima seja um pouco bobo, ele apenas ilustra que APIs que usam os métodos Object internamente também podem aceitar funções.

Então, eu sugeriria que o tipo object está de acordo com o seguinte, que corresponde ao tipo de linguagem Object (exceto que inclui null , mas não há proteção contra null no TypeScript em qualquer caso).

function isObject(value) {
  return typeof value === 'object' || typeof value === 'function';
}

Caso de uso

Tenho um projeto chamado Deep Map que recorre aos pares de valores-chave de objetos aninhados e transforma valores primitivos ao longo do caminho. Como tal, ele distingue entre tipos primitivos e não primitivos. Tive que definir uma interface NonPrimitive , da seguinte maneira:

interface NonPrimitive {
  [key: string]: any;
  [index: number]: any;
}

export class DeepMap {
  // ...
  private mapObject(obj: NonPrimitive): NonPrimitive { /* ... */ }
}

Esta não é a primeira vez que tenho que definir a interface NonPrimitive . Seria bom se eu pudesse apenas fazer isso:

export class DeepMap {
  // ...
  private mapObject(obj: object): object { /* ... */ }
}

Como sugeri acima, realmente não me importo se o parâmetro obj ser chamado ou não. Tudo o que importa é que seja do Object _tipo de idioma_, ou seja, que seja um pacote de propriedades cujos pares de valores-chave posso iterar.

Eu concordaria que as funções são object s. A intenção é excluir primitivos.

Ok, pensei que devia ser essa a intenção. Só pensei que talvez estivesse faltando alguma coisa com toda essa conversa de typeof value === 'object' .

este object type permitiria atribuição de propriedade ad-hoc (como any )? Por exemplo:

const foo: object = {};

foo.bar = 'baz';

Não

Qual é a diferença prática entre

export class DeepMap {
  // ...
  private mapObject(obj: object): object { /* ... */ }
}

e

export class DeepMap {
  // ...
  private mapObject(obj: Object): Object { /* ... */ }
}

? (Usando object vs Object ). Parece-me que Function se estende de Object , então não vejo por que ainda não podemos fazer isso. Você pode explicar?

@trusktr Todos os valores diferentes de null e undefined são atribuíveis ao tipo Object ; uma função que aceita um Object terá uma string, um número, um booleano ou um símbolo. Apenas valores não primitivos podem ser atribuídos a object ; passar uma string, etc, produzirá um erro em tempo de compilação. O tipo object é útil onde esperamos um valor não primitivo, mas onde não nos importamos quais são suas propriedades.

Esta página foi útil?
0 / 5 - 0 avaliações