Tengo los siguientes tipos:
type OrBranch = { or: Branch[] } type AndBranch = { and: Branch[] } Me gustaría un tipo Branch que puede ser OrBranch o AndBranch . Así que primero probé:
type Branch = AndBrand | OrBranchFunciona muy bien, a menos que quiera hacer algo como:
let branch: Branch = ... let andOr = 'and'; // or 'or' let nodes = branch[andOr] Entonces obtengo que la branch no es indexable. OK, entonces pruebo un tipo indexable:
type AndOr = 'and' | 'or'; type Branch = Record<AndOr, Branch[]>; Pero eso requiere que AMBOS and y or existan, por lo que no puedo lanzar AndBranch a Branch en este caso.
Similarmente
type Branch = Record<AndOr, Branch[]> | AndBranch | OrBranchno funciona, por la misma razón.
Si bien podría usar guardias de tipo para determinar el tipo, tengo funciones largas que operan en estos objetos, en las que pueden tratarse de la misma manera que la propiedad. Tenía la esperanza de eliminar un montón de código duplicado usando la variable andOr , que los protectores de tipo realmente no previenen. Por ejemplo:
let retval = {} as Branch; if (isAnd(branch)) { // branch is a Branch parameter passed in (retval as AndBranch).and = [] as Branch[]; set = (retval as AndBranch).and; } else { (retval as OrBranch).or = [] as Branch[]; set = (retval as OrBranch).or; } set = _.reduce(set, (all, item: Branch)=> { if (isAnd(branch) && isAnd(item)) return _.union(all, item.and); else if (isOr(branch) && isOr(item)) return _.union(all, item.or); else return all; }, [] as Branch[]);contra
andOr = isAnd(branch) ? 'and' : 'or'; let retval = {} as Branch; retval[andOr] = _.reduce(set, (all, item: Branch) => { if (item[andOr]) return _.union(all, item[andOr]); else return all; }, [] as Branch[]); Sé que hay una manera de requerir exactamente uno de and or (como la respuesta al objeto Enforce Typescript tiene exactamente una clave de un conjunto ). Pero ese tipo no es indexable.
¿Es posible obtener ambos efectos?
Hacer que el tipo sea indexable no resolverá el problema básico, que es que está tratando de usar la propiedad and en algo que puede ser una rama and (y por lo tanto tener la propiedad) o puede ser una rama or (y por lo tanto no) . En su lugar, pregúntale a la rama qué tiene y úsalo, porque eso permite que TypeScript reduzca el tipo:
if ("and" in branch) { // ...use `branch.and`... } else { // ...use `branch.or`... } Puede combinar eso con su andOr si tiene un código que cree que sabe cuál es la rama, tal vez mediante el uso de una función de aserción de tipo:
function assertIsAndBranch(branch: Branch): asserts branch is AndBranch { if (!("and" in branch)) { throw new Error(`branch is not an AndBranch`); } } // (And `assertIsOrBranch`)Después:
if (andOr === "and") { assertIsAndBranch(branch); // ...here, TypeScript knows `branch` is an `AndBranch`... } Con respecto a su edición: si tiene un código significativo que necesita lidiar con los elementos contenidos en AndBranch and OrBranch or sin saber o preocuparse si son instancias de AndBranch u OrBranch , los rediseñaría como una unión discriminada donde todos los miembros del sindicato tienen los mismos items (o lo que sea) de propiedad:
type OrBranch = { type: "or"; items: Branch[]; }; type AndBranch = { type: "and"; items: Branch[]; }; De esa forma, el código al que no le importa qué tipo de rama sea puede funcionar con items . El código que mostraste sería:
const items = _.reduce(branch.items, (all, item) => { return _.union(all, item.items); }, [] as Branch[]); const retval = {type: branch.type, items};Tener propiedades tiene una doble función (indicar el tipo de rama y los elementos dentro de ella) como lo hacen en sus tipos actuales hace que escribir código seguro para manejar los elementos sin saber/importar qué tipo de rama es realmente difícil.