我需要帮助弄清楚如何在typescript中正确断言类型。
我正在使用的代码让rn感到困惑(我想我让它变得比需要的更复杂了,哈哈:3),所以我只给你我试图实现的目标的概述。
因此,基本上我有一个实现IAbstractFactory接口的ConcreteFactory类。ConcreteFactory类可以创建/生成3个不同的Concrete类BaseDocument、DocumentA和DocumentB。
interface IAbstractFactory{
createBaseDocument(): Promise<IAbstractDocument>;
createDocumentA(): Promise<IAbstractDocument>;
createDocumentB(): Promise<IAbstractDocument>;
}
class ConcreteFactory implements IAbstractFactory{
public async createBaseDocument(): Promise<IAbstractDocument> {
return new BaseDocument();
}
public async createDocumentA(): Promise<IAbstractDocument> {
return new DocumentA();
}
public async createDocumentB(): Promise<IAbstractDocument> {
return new DocumentB();
}
}
请注意,只有Document类实现IAbstractDocument接口。DocumentA和DocumentB都扩展了BaseDocument。
interface IAbstractDocument{
methodBase(){};
}
class BaseDocument implements IAbstractDocument{
methodBase(){};
}
// uses all of BaseDocuments functions but has some extra functions that DocumentB does not have
class DocumentA extends BaseDocument{
methodBase(){};
methodA(){}; // not implemented in IAbstractDocument, throws error
}
// uses all of BaseDocuments functions but has some extra functions that DocumentA does not have
class DocumentB extends BaseDocument{
methodBase(){};
methodB(){}; // not implemented in IAbstractDocument, throws error
}
我还有一个名为Tree的类,它将构建一个Tree,每个节点的类型都是IAbstractDocument
class Tree{
rootNode:IAbstractDocument; // Realistically of type DocumentA or DocumentB, IAbstractDocument is throwing error, explained below
nodes: IAbstractDocument[]; // Realistically of type DocumentA or DocumentB, IAbstractDocument is throwing error, explained below
// this function will create factory and the factory will create DocumentA or Document B
createDocument(){}
addDocument()
}
我的目标是创建一个文档树。规则是DocumentA可以包含子节点,类型都是DocumentADocumentB,但 文档B问题是: 尽管DocumentA和DocumentB都扩展了BaseDocument。在Tree类中,它们是在类型IAbstractDocument下定义的(我这样做是因为我认为ConcreteFactory正在返回)。因此,现在我得到一个错误,DocumentA和DocumentB中的唯一函数(分别为方法A和方法B)没有在IAbstractDocument定义。所以我不能在Tree类中使用它们,我只能使用methodBase()。 我的问题是: 如何将DocumentA和DocumentB视为相同但不同的类型它们都应该被视为树中的文档节点,但它们有不同的相关功能。 任何帮助都将不胜感激,谢谢!
我认为您可能希望使用联合类型来表示"或者";,而不是超类或其他基类型,因为它们不容易检查。一般来说,您可能应该根据需要使用特定的类型,以便编译器知道您在做什么。
在接下来的内容中,我将名称DocumentA
更改为DocumentCollection
,DocumentB
更改为DocumentSingle
,因为我认为这就是您的意图。DocumentCollection
将具有children
属性,而DocumentSingle
则不会。这是ConcreteFactory
:
class ConcreteFactory {
public async createBaseDocument(): Promise<BaseDocument> {
return new BaseDocument();
}
public async createDocumentCollection(): Promise<DocumentCollection> {
return new DocumentCollection();
}
public async createDocumentSingle(): Promise<DocumentSingle> {
return new DocumentSingle();
}
}
查看它如何在返回签名中返回更具体的类型。现在让我们来看一下Document
类的可能实现:
class BaseDocument {
methodBase() { };
constructor(public docName: string = "") { }
}
class DocumentCollection extends BaseDocument {
methodBase() { };
methodCollection() { console.log("MethodCollection by " + this.docName) };
children: Array<DocumentCollection> | Array<DocumentSingle> = [];
}
class DocumentSingle extends BaseDocument {
methodBase() { };
methodSingle() { console.log("MethodSingle by " + this.docName) };
}
我为文档提供了一个docName
,这样我们以后可以看到一些有用的输出。注意DocumentCollection
的children
属性是Array<DocumentCollection>
还是Array<DocumentSingle>
。这将防止它成为一个混合的集合,如果我从你的问题中正确理解的话,你想禁止它。请注意,使用数组的并集比使用并集数组更难,但它就是这样。
让我们为DocumentSingle
和DocumentCollection
的并集命名:
type Document = DocumentSingle | DocumentCollection;
如果你能帮助的话,你可能会想用Document
而不是AbstractDocument
或BaseDocument
现在让我们看看Tree
:
class Tree {
nodes: Document[]
constructor(public rootNode: Document) {
this.nodes = [];
const addNodes = (x: Document): void => {
this.nodes.push(x)
if ("children" in x) {
x.children.forEach(addNodes)
}
}
addNodes(this.rootNode);
}
walk(
processSingle: (x: DocumentSingle) => void,
processCollection: (x: DocumentCollection) => void
) {
const walk = (x: Document): void => {
if ("methodSingle" in x) {
processSingle(x);
} else {
processCollection(x);
x.children.forEach(walk)
}
}
walk(this.rootNode);
}
}
CCD_ 21具有CCD_ 23类型的CCD_。它还具有Document[]
类型的nodes
阵列。当您用rootNode
构造Tree
时,如果重要的话,构造函数还会遍历树来构建nodes
数组。还有一个walk()
方法,它对DocumentSingle
和DocumentCollection
进行处理器回调,并遍历调用适当处理器的树。
注意,在addNodes()
和walk()
箭头函数的实现中,编译器使用DocumentSingle
或DocumentCollection
的特定属性之一的存在来将Document
缩小到两个成员之一。这是因为in
运算符可以用作类型保护。
所以让我们制作一个Tree
:
const leafOne = new DocumentSingle("leafOne");
const leafTwo = new DocumentSingle("leafTwo");
const leafThree = new DocumentSingle("leafThree");
const leafFour = new DocumentSingle("leafFour");
const branchOne = new DocumentCollection("branchOne");
branchOne.children = [leafOne, leafTwo];
const branchTwo = new DocumentCollection("branchTwo");
branchTwo.children = [leafThree, leafFour];
const root = new DocumentCollection("root");
// note how I'm not allowed to assign a mixed thing to root.children
root.children = [branchOne, leafOne];
root.children = [branchOne, branchTwo]; // okay
const tree = new Tree(root);
从上面可以看出,在DocumentCollection
的children
属性中,不允许使用DocumentSingle
和DocumentCollection
的混合数组。此外,您应该注意,我用一个新数组重新分配了children
属性,而不是尝试将push()
添加到它上。如果您尝试push()
,您会发现它不允许您添加任何内容,因为它不知道数组是包含DocumentSingle
s还是DocumentCollection
s,但如果是这样的话,我们可以改变它⏳)
太好了,我们有一个tree
。。。让我们walk()
它:
tree.walk(x => x.methodSingle(), y => y.methodCollection());
/* [LOG]: "MethodCollection by root"
[LOG]: "MethodCollection by branchOne"
[LOG]: "MethodSingle by leafOne"
[LOG]: "MethodSingle by leafTwo"
[LOG]: "MethodCollection by branchTwo"
[LOG]: "MethodSingle by leafThree"
[LOG]: "MethodSingle by leafFour" */
太棒了!或者,我们可以迭代其nodes
属性:
tree.nodes.forEach(x => "methodSingle" in x ? x.methodSingle() : x.methodCollection());
/* [LOG]: "MethodCollection by root"
[LOG]: "MethodCollection by branchOne"
[LOG]: "MethodSingle by leafOne"
[LOG]: "MethodSingle by leafTwo"
[LOG]: "MethodCollection by branchTwo"
[LOG]: "MethodSingle by leafThree"
[LOG]: "MethodSingle by leafFour" */
看起来不错。
到代码的游乐场链接