Como agrupar uma família de tipos relacionados em TypeScript
Então, no meu aplicativo, eu tenho um componente de grade que é usado para alguns tipos diferentes de dados.
Todos os tipos de dados são obtidos através de diferentes endpoints de API, usando diferentes opções de filtragem, etc. Tudo isso é devidamente definido com tipos apropriados e funciona bem quando estou trabalhando com um tipo específico.
Como posso "agrupar" todos esses tipos juntos por "família"?
Eu gostaria de poder descrever a "forma" de uma família (por exemplo, ela tem um tipo Filtro, um tipo Resposta, etc.) e ser capaz de impor o relacionamento (por exemplo, quando temos esse tipo de dado, precisamos desse tipo de filtro).
Eu tentei várias abordagens usando interfaces e classes, mas não consigo especificar um tipo com um "." nele; isso é reservado para namespaces.
Namespaces são promissores, mas eu não posso impor a "forma" deles, então eu não posso fazer meu componente usar um namespace "abstrato" comum.
Alguma ideia sobre a abordagem que eu deveria usar?
EDITAR: Para esclarecimento, aqui está o que eu gostaria de fazer:
type View<D, F> = {
// these should be the actual types, not an instance of
Data: D;
Filter: F;
}
type TasksView = View<Task, TaskFilter>;
type ProjectsView = View<Project, ProjectFilter>;
interface GridProps<T extends View> {
filters: T.Filter;
}
function Grid<T>(props: GridProps<T>) {
const getData(props.filters): T.Data {
// return data
}
return (
<table>
</table>
)
}
function MyTasksGrid = Grid<TasksView>
function MyProjectsGrid = Grid<ProjectsView>
Não é 100% sintaticamente correto, mas espero que ilustre o conceito. Basicamente, eu quero "vincular" os tipos Task e TaskFilter através de uma estrutura comum.
No entanto, T.Data como descrito acima não funciona, pois T não é um namespace ('T' refere-se apenas a um tipo, mas está sendo usado como um namespace aqui).
Resposta #1
Quando você precisa criar uma interface "comum" para objetos que têm alguns campos em comum enquanto outros são diferentes, você pode usar a notação | e definir um novo type. Veja o exemplo abaixo:
//Two similar objects with partially common interfaces
interface Bird {
name: string
age: number
type: 'BIRD'
//Different
feathers_color: string
fly: ()=>void
}
interface Fish {
name: string
age: number
type: 'FISH'
//Different
fins: number
swim: ()=>void
}
type Animal = Fish | Bird;
let my_animal: Animal = {
name: 'Karl'
age: 8
type: 'FISH' as 'FISH'
fins: 3
swim: () => { console.log("I'm swimming...") }
}
//Properties that are accessible without verifying if the animal is a bird or a fish
console.log(my_animal.name);
console.log(my_animal.age);
console.log(my_animal.type);
if(my_animal.type === 'FISH'){
//Properties that are accessible only after checking the type
console.log(my_animal.fins);
my_animal.swim(); //Typescript will not complain here
}
Aviso: quando você criar um objeto, você deve especificar o parâmetro type manualmente.
Aviso 2: essa abordagem geralmente não funciona bem (em termos de manutenção) quando você precisa de múltiplos níveis de agrupamento. Você terá que definir vários campos type para cada grupo e isso pode se tornar bagunçado muito rapidamente.
ATUALIZAÇÃO Esta deve ser a implementação do que você pediu usando o método descrito acima
interface Task{}
interface TaskFilter{}
interface Project{}
interface ProjectFilter{}
interface TaskView {
Data: Task
Filter: TaskFilter
}
interface ProjectView{
Data: Project
Filter: ProjectFilter
}
type View = TaskView | ProjectView
interface GridProps<T extends View> {
filters: T['Filter'];
}
//Alternative 1: Without grid props, that wrapper seemed excessive for the definition of the type
function Grid<T extends View>(props: T) {
function getData(filter: T['Filter']): T['Data'] {
// return data
return {}
}
return (
<table>
</table>
)
}
//Alternative 2: maybe I'm missing something, so I add also a version with Grid Props
function Grid<T extends View, G extends GridProps<T>>(props: G) {
function getData(filter: G['filters']): T['Data'] {
// return data
return {}
}
return (
<table>
</table>
)
}
