我想根据对象的条件指定类型属性
I want to designate a type according to the conditions of the object property
存在具有层次结构的组织数据。
组织节点分为根、部门和用户。
为对象 属性 类型分配了适合节点数据的类型。
所以,希望有条件附上适合对象类型的节点接口
为了解决这个问题,我尝试了索引类型和条件类型,但都没有成功。
如有任何帮助,我们将不胜感激♂️
export enum NodeType {
root = "root",
dept = "dept",
user = "user",
}
interface Node {
readonly nodeId: string;
readonly type: NodeType;
title: string;
childNodes?: Node[];
}
interface RootNode extends Node {
type: NodeType.root;
childNodes?: (DeptNode | UserNode)[];
}
interface DeptNode extends Node {
type: NodeType.dept;
childNodes?: (DeptNode | UserNode)[];
deptId: string;
companyCode: string;
}
interface UserNode extends Node {
type: NodeType.user;
childNodes?: UserNode[];
employeeNumber: string;
userPrincipalName: string;
}
/**
* from hierarchy data to flatting data
*/
function flatting(st: Node) {
flatCompanyData.push(st);
if (st.childNodes) {
st.childNodes.forEach((x) => flatting(x));
}
}
const companyData: RootNode = {
nodeId: "0",
type: NodeType.root,
title: "Company 1",
childNodes: [
{
nodeId: "0.0",
type: NodeType.dept,
title: "Department 1",
deptId: "A1",
companyCode: "557",
childNodes: [
{
nodeId: "0.0.0",
type: NodeType.user,
title: "User 1",
employeeNumber: "201911",
userPrincipalName: "user01@test.com",
},
{
nodeId: "0.0.1",
type: NodeType.user,
title: "User 2",
employeeNumber: "201912",
userPrincipalName: "user02@test.com",
},
],
},
{
nodeId: "0.1",
type: NodeType.dept,
title: "Department 2",
deptId: "A2",
companyCode: "558",
},
],
};
let flatCompanyData: Node[] = [];
flatting(companyData);
// I want to be the type of DeptNode interface because the type of object property is NodeType.dept.
const selectDept = flatCompanyData.filter((x) => x.type === NodeType.dept);
selectDept.forEach((x) => {
console.log({
nodeId: x.nodeId,
type: x.type,
title: x.title,
// The type is not affirmed in the node that fits the object property type, so you have to affirm the type yourself.
deptId: (x as DeptNode).deptId,
companyCode: (x as DeptNode).companyCode,
});
});
// I want to be the type of UserNode interface because the type of object property is NodeType.user.
const selectUser = flatCompanyData.filter((x) => x.type === NodeType.user);
selectUser.forEach((x) => {
console.log({
nodeId: x.nodeId,
type: x.type,
title: x.title,
// The type is not affirmed in the node that fits the object property type, so you have to affirm the type yourself.
employeeNumber: (x as UserNode).employeeNumber,
userPrincipalName: (x as UserNode).userPrincipalName,
});
});
你的代码有两个问题:
首先,一个Node
类型的元素不一定是DeptNode
类型,因为它有NodeType.dept
。考虑以下对象:
{
nodeId: '0',
type: NodeType.dept,
title: 'Company 1'
}
这是一个完全有效的 Node
但不是有效的 DeptNode
,因为它缺少字段 deptId
和 companyCode
。因此,编译器不将其视为 DeptNode
实际上是正确的。但是有一个解决方案。您可以定义所有可能的节点类型的联合类型,并将其用作 flatCompanyData
的类型,而不是使用 Node
的数组,如下所示:
type NodeUnion = RootNode | DeptNode | UserNode;
let flatCompanyData: NodeUnion[];
// ...
const node = flatCompanyData[0];
if (node.type === NodeType.dept) {
// x is correctly inferred as DeptNode in here
}
对于这种类型,编译器知道,数组中的元素可能具有类型 NodeType.dept
的唯一方法是它是否是 DeptNode
,因此可以正确推断类型。
这还留给我们第二个问题。即使您将此联合类型用于 flatCompanyData
,您的代码仍然无法运行。原因是 Array.filter
函数。对于类型 T[]
的数组,它总是 return 类型 T[]
的数组,无论你在里面检查什么。
但幸运的是,typescript 为此提供了一个方便的解决方案:自定义类型保护。定义函数(包括匿名函数)时,您可以使用类型谓词作为其 return 类型来告诉编译器此函数充当类型保护。
function isDeptNode(node: NodeUnion): node is DeptNode {
return node.type === NodeType.dept;
}
现在你可以在过滤器中使用这个函数让打字稿意识到你在做什么。
const selectDept: DeptNode[] = flatCompanyData.filter(isDeptNode);
也可以将类型谓词指定为匿名函数的 return 类型,而无需先定义 isDeptNode
函数。
const selectDept: DeptNode[] = flatCompanyData.filter(
(x): x is DeptNode => x.type === NodeType.dept
);
存在具有层次结构的组织数据。
组织节点分为根、部门和用户。
为对象 属性 类型分配了适合节点数据的类型。
所以,希望有条件附上适合对象类型的节点接口
为了解决这个问题,我尝试了索引类型和条件类型,但都没有成功。
如有任何帮助,我们将不胜感激♂️
export enum NodeType {
root = "root",
dept = "dept",
user = "user",
}
interface Node {
readonly nodeId: string;
readonly type: NodeType;
title: string;
childNodes?: Node[];
}
interface RootNode extends Node {
type: NodeType.root;
childNodes?: (DeptNode | UserNode)[];
}
interface DeptNode extends Node {
type: NodeType.dept;
childNodes?: (DeptNode | UserNode)[];
deptId: string;
companyCode: string;
}
interface UserNode extends Node {
type: NodeType.user;
childNodes?: UserNode[];
employeeNumber: string;
userPrincipalName: string;
}
/**
* from hierarchy data to flatting data
*/
function flatting(st: Node) {
flatCompanyData.push(st);
if (st.childNodes) {
st.childNodes.forEach((x) => flatting(x));
}
}
const companyData: RootNode = {
nodeId: "0",
type: NodeType.root,
title: "Company 1",
childNodes: [
{
nodeId: "0.0",
type: NodeType.dept,
title: "Department 1",
deptId: "A1",
companyCode: "557",
childNodes: [
{
nodeId: "0.0.0",
type: NodeType.user,
title: "User 1",
employeeNumber: "201911",
userPrincipalName: "user01@test.com",
},
{
nodeId: "0.0.1",
type: NodeType.user,
title: "User 2",
employeeNumber: "201912",
userPrincipalName: "user02@test.com",
},
],
},
{
nodeId: "0.1",
type: NodeType.dept,
title: "Department 2",
deptId: "A2",
companyCode: "558",
},
],
};
let flatCompanyData: Node[] = [];
flatting(companyData);
// I want to be the type of DeptNode interface because the type of object property is NodeType.dept.
const selectDept = flatCompanyData.filter((x) => x.type === NodeType.dept);
selectDept.forEach((x) => {
console.log({
nodeId: x.nodeId,
type: x.type,
title: x.title,
// The type is not affirmed in the node that fits the object property type, so you have to affirm the type yourself.
deptId: (x as DeptNode).deptId,
companyCode: (x as DeptNode).companyCode,
});
});
// I want to be the type of UserNode interface because the type of object property is NodeType.user.
const selectUser = flatCompanyData.filter((x) => x.type === NodeType.user);
selectUser.forEach((x) => {
console.log({
nodeId: x.nodeId,
type: x.type,
title: x.title,
// The type is not affirmed in the node that fits the object property type, so you have to affirm the type yourself.
employeeNumber: (x as UserNode).employeeNumber,
userPrincipalName: (x as UserNode).userPrincipalName,
});
});
你的代码有两个问题:
首先,一个Node
类型的元素不一定是DeptNode
类型,因为它有NodeType.dept
。考虑以下对象:
{
nodeId: '0',
type: NodeType.dept,
title: 'Company 1'
}
这是一个完全有效的 Node
但不是有效的 DeptNode
,因为它缺少字段 deptId
和 companyCode
。因此,编译器不将其视为 DeptNode
实际上是正确的。但是有一个解决方案。您可以定义所有可能的节点类型的联合类型,并将其用作 flatCompanyData
的类型,而不是使用 Node
的数组,如下所示:
type NodeUnion = RootNode | DeptNode | UserNode;
let flatCompanyData: NodeUnion[];
// ...
const node = flatCompanyData[0];
if (node.type === NodeType.dept) {
// x is correctly inferred as DeptNode in here
}
对于这种类型,编译器知道,数组中的元素可能具有类型 NodeType.dept
的唯一方法是它是否是 DeptNode
,因此可以正确推断类型。
这还留给我们第二个问题。即使您将此联合类型用于 flatCompanyData
,您的代码仍然无法运行。原因是 Array.filter
函数。对于类型 T[]
的数组,它总是 return 类型 T[]
的数组,无论你在里面检查什么。
但幸运的是,typescript 为此提供了一个方便的解决方案:自定义类型保护。定义函数(包括匿名函数)时,您可以使用类型谓词作为其 return 类型来告诉编译器此函数充当类型保护。
function isDeptNode(node: NodeUnion): node is DeptNode {
return node.type === NodeType.dept;
}
现在你可以在过滤器中使用这个函数让打字稿意识到你在做什么。
const selectDept: DeptNode[] = flatCompanyData.filter(isDeptNode);
也可以将类型谓词指定为匿名函数的 return 类型,而无需先定义 isDeptNode
函数。
const selectDept: DeptNode[] = flatCompanyData.filter(
(x): x is DeptNode => x.type === NodeType.dept
);