如何在 React 和 Typescript 中映射我的联合类型 GraphQL 响应数组
How do I map my Union typed GraphQL response Array in React and Typescript
我正在使用 React、Typescript 和 Apollo Client。
在我的 React 组件中,我根据值 blockData.myType
使用 useQuery
挂钩 NODES_TYPE_ONE
或 NODES_TYPE_TWO
进行查询。这很好用。
GraphQL 查询如下所示:
export const NODES_TYPE_ONE = gql`
query GetNodesOne($customKey: String!) {
getNodesTypeOne(customKey: $customKey) {
nodes {
id
title
}
}
}
`;
export const NODES_TYPE_TWO = gql`
query GetNodesTwo($customKey: String!) {
getNodesTypeTwo(customKey: $customKey) {
nodes {
id
title
}
}
}
`;
但是我如何在 GqlRes
类型中输入我的数据?
当我 console.log(data);
我得到:两个不同的对象:
getNodesTypeOne {
nodes[// array of objects]
}
和
getNodesTypeTwo {
nodes[// array of objects]
}
我的 GqlRes
类型:
export type GqlRes = {
getNodesTypeOne: {
nodes: NodeTeaser[];
};
};
/** @jsx jsx */
import { useQuery } from '@apollo/client';
import { jsx } from '@emotion/react';
import { Slides } from 'app/components';
import { NODES_TYPE_ONE, NODES_TYPE_TWO } from './MyBlock.gql';
import { Props, GqlRes, NodesArgs } from './MyBlock.types';
const MyBlock = ({ data: blockData, metadata }: Props) => {
const customKey = metadata.customKey;
const { data } = useQuery<GqlRes, NodesArgs>(
blockData.myType === 'type-one' ? NODES_TYPE_ONE : NODES_TYPE_TWO,
{
variables: {
customKey: metadata.customKey || 0,
},
errorPolicy: 'all',
notifyOnNetworkStatusChange: true,
ssr: false,
}
);
const items =
data?.getNodesTypeOne.nodes.map((video) => {
return {
id: video.uuid,
type: 'type-one',
title: title,
};
}) || [];
return <Slides items={items} /> : null;
};
export default MyBlock;
现在我的物品 returns 只有 getNodesTypeOne
但我如何同时获得它们?
更新:
我为 GqlRes
创建了一个联合类型:
type GetNodeTypeOne = {
getNodesTypeOne: {
nodes: Teaser[];
};
};
type GetNodeTypeTwo = {
getNodesTypeTwo: {
nodes: Teaser[];
};
};
export type GqlRes = GetNodeTypeOne | GetNodeTypeTwo;
但是我现在如何 map
节点数组?
更新 2
如@Urmzd 所述,我尝试了另一种方法。只需使用多个 useQuery
钩子:
const MyBlock = ({ data: blockData, metadata }: Props) => {
const customKey = metadata.customKey;
const { data: nodesOne } = useQuery<NodesOneGqlRes, NodesArgs>(NODES_TYPE_ONE,
{
variables: {
customKey: metadata.customKey || 0,
},
errorPolicy: 'all',
notifyOnNetworkStatusChange: true,
ssr: false,
}
);
const { data: nodesTwo } = useQuery<NodesTwoGqlRes, NodesArgs>(NODES_TYPE_TWO,
{
variables: {
customKey: metadata.customKey || 0,
},
errorPolicy: 'all',
notifyOnNetworkStatusChange: true,
ssr: false,
}
);
const items =
data?.// How do I get my nodes in a single variable?? .map((video) => {
return {
id: video.uuid,
type: 'type-one',
title: title,
};
}) || [];
return <Slides items={items} /> : null;
};
export default MyBlock;
但是我现在如何 map
我的数据,因为我有两个不同的 GraphQL 响应?在这种情况下最好的方法是什么?
如果我直接理解你的代码,那么根据 blockData.myType
的值,你要么执行一个查询,要么执行另一个查询,并且你想为这个逻辑重用相同的 useQuery
挂钩。如果你想要,你需要确保 GqlRes
是 getNodesTypeOne
和 getNodesTypeTwo
.
的 union type
// I don't know what NodeType is so I'm just using a string for this example
type NodeType = string
interface GetNodesTypeOne {
readonly getNodesTypeOne: {
readonly nodes: NodeType[]
}
}
interface GetNodesTypeTwo {
readonly getNodesTypeTwo: {
readonly nodes: NodeType[]
}
}
type GqlRes = GetNodesTypeOne | GetNodesTypeTwo
const resultOne:GqlRes = {
getNodesTypeOne: {
nodes: [ "test" ]
}
}
const resultTwo:GqlRes = {
getNodesTypeTwo: {
nodes: [ "test" ]
}
}
所以这将解决 TypeScript 问题。然后稍后在您的代码中执行此操作:
const items = data?.getNodesTypeOne.nodes.map(...)
由于 data
可能包含 getNodesTypeOne
或 getNodesTypeTwo
我们需要将其更改为其他内容。一个快速的解决方法是 select 第一个有值的:
const nodes = "getNodesTypeOne" in data
? data?.getNodesTypeOne?.nodes
: data?.getNodesTypeTwo?.nodes
const items = nodes.map(...);
或者如果您想使用相同的条件:
const nodes = blockData.myType === 'type-one'
? (data as GetNodesTypeOne)?.getNodesTypeOne?.nodes
: (data as GetNodesTypeTwo)?.getNodesTypeTwo?.nodes
const items = nodes.map(...);
请注意,在第二个示例中,我们需要通过narrowing it down using a type assertion帮助TypeScript 确定具体类型。在第一个示例中,这不是必需的,因为 TypeScript 足够聪明,可以计算出第一个表达式将始终导致 GetNodesTypeOne
而第二个表达式将始终导致 GetNodesTypeOne
.
使用两个单独的查询来回答您的第二个问题:
- 添加一个新变量
useQueryOne
,如果我们 运行 查询一,则为 true
,如果我们 运行,则为 false
ning查询二.
- 仅将适当的查询添加
skip
到 useQuery
到 运行。
- 添加一个新变量
nodes
,其中包含第一个或第二个查询的结果(基于 useQueryOne 条件)
const useQueryOne = blockData.myType === 'type-one';
const { data: nodesOne } = useQuery<NodesOneGqlRes, NodesArgs>(NODES_TYPE_ONE,
{
variables: {
customKey: metadata.customKey || 0,
},
errorPolicy: 'all',
notifyOnNetworkStatusChange: true,
ssr: false,
skip: !useQueryOne
}
);
const { data: nodesTwo } = useQuery<NodesTwoGqlRes, NodesArgs>(NODES_TYPE_TWO,
{
variables: {
customKey: metadata.customKey || 0,
},
errorPolicy: 'all',
notifyOnNetworkStatusChange: true,
ssr: false,
skip: useQueryOne
}
);
const nodes = useQueryOne
? nodesOne?.getNodesTypeOne?.nodes
: nodesTwo?.getNodesTypeTwo?.nodes;
const items = (nodes || []).map(...);
我应该注意到您的查询是重复的,因此应该重构为单个查询(除非它们只是重复的命名空间而不是值)。
无论如何,您可以使用 useLazyQuery
以更安全的方式实现您想要的结果
const [invokeQuery1, {loading, data, error}] = useLazyQuery<>(...)
const [invokeQuery2, {loading2, data2, error2}] = useLazyQuery<>(...)
// Run the query every time the condition changes.
useEffect(() => {
if (condition) {
invokeQuery1()
} else {
invokeQuery2()
}
}, [condition])
// Return the desired conditional daa
const {nodes} = useMemo(() => {
return condition ? data?.getNodesTypeOne : data2?.getNodesTypeTwo
} ,
[condition])
这也确保不会进行不必要的计算(您可以根据 events
调用您的查询,因为它们应该如此)。
-- 编辑
因为您坚持使用联合类型(并从源映射数据)。
这是一种可能的类型安全方法。
const isNodeTypeOne = (t: unknown): t is GetNodeTypeOne => {
return (t as GetNodeTypeOne).getNodesTypeOne !== undefined;
};
const { nodes } = isNodeTypeOne(data)
? data?.getNodesTypeOne
: data?.getNodesTypeTwo;
const items = nodes.map((val) => {
// Your mapping here
})
如果您有不同的节点类型,您还可以在地图中使用谓词。
我正在使用 React、Typescript 和 Apollo Client。
在我的 React 组件中,我根据值 blockData.myType
使用 useQuery
挂钩 NODES_TYPE_ONE
或 NODES_TYPE_TWO
进行查询。这很好用。
GraphQL 查询如下所示:
export const NODES_TYPE_ONE = gql`
query GetNodesOne($customKey: String!) {
getNodesTypeOne(customKey: $customKey) {
nodes {
id
title
}
}
}
`;
export const NODES_TYPE_TWO = gql`
query GetNodesTwo($customKey: String!) {
getNodesTypeTwo(customKey: $customKey) {
nodes {
id
title
}
}
}
`;
但是我如何在 GqlRes
类型中输入我的数据?
当我 console.log(data);
我得到:两个不同的对象:
getNodesTypeOne {
nodes[// array of objects]
}
和
getNodesTypeTwo {
nodes[// array of objects]
}
我的 GqlRes
类型:
export type GqlRes = {
getNodesTypeOne: {
nodes: NodeTeaser[];
};
};
/** @jsx jsx */
import { useQuery } from '@apollo/client';
import { jsx } from '@emotion/react';
import { Slides } from 'app/components';
import { NODES_TYPE_ONE, NODES_TYPE_TWO } from './MyBlock.gql';
import { Props, GqlRes, NodesArgs } from './MyBlock.types';
const MyBlock = ({ data: blockData, metadata }: Props) => {
const customKey = metadata.customKey;
const { data } = useQuery<GqlRes, NodesArgs>(
blockData.myType === 'type-one' ? NODES_TYPE_ONE : NODES_TYPE_TWO,
{
variables: {
customKey: metadata.customKey || 0,
},
errorPolicy: 'all',
notifyOnNetworkStatusChange: true,
ssr: false,
}
);
const items =
data?.getNodesTypeOne.nodes.map((video) => {
return {
id: video.uuid,
type: 'type-one',
title: title,
};
}) || [];
return <Slides items={items} /> : null;
};
export default MyBlock;
现在我的物品 returns 只有 getNodesTypeOne
但我如何同时获得它们?
更新:
我为 GqlRes
创建了一个联合类型:
type GetNodeTypeOne = {
getNodesTypeOne: {
nodes: Teaser[];
};
};
type GetNodeTypeTwo = {
getNodesTypeTwo: {
nodes: Teaser[];
};
};
export type GqlRes = GetNodeTypeOne | GetNodeTypeTwo;
但是我现在如何 map
节点数组?
更新 2
如@Urmzd 所述,我尝试了另一种方法。只需使用多个 useQuery
钩子:
const MyBlock = ({ data: blockData, metadata }: Props) => {
const customKey = metadata.customKey;
const { data: nodesOne } = useQuery<NodesOneGqlRes, NodesArgs>(NODES_TYPE_ONE,
{
variables: {
customKey: metadata.customKey || 0,
},
errorPolicy: 'all',
notifyOnNetworkStatusChange: true,
ssr: false,
}
);
const { data: nodesTwo } = useQuery<NodesTwoGqlRes, NodesArgs>(NODES_TYPE_TWO,
{
variables: {
customKey: metadata.customKey || 0,
},
errorPolicy: 'all',
notifyOnNetworkStatusChange: true,
ssr: false,
}
);
const items =
data?.// How do I get my nodes in a single variable?? .map((video) => {
return {
id: video.uuid,
type: 'type-one',
title: title,
};
}) || [];
return <Slides items={items} /> : null;
};
export default MyBlock;
但是我现在如何 map
我的数据,因为我有两个不同的 GraphQL 响应?在这种情况下最好的方法是什么?
如果我直接理解你的代码,那么根据 blockData.myType
的值,你要么执行一个查询,要么执行另一个查询,并且你想为这个逻辑重用相同的 useQuery
挂钩。如果你想要,你需要确保 GqlRes
是 getNodesTypeOne
和 getNodesTypeTwo
.
// I don't know what NodeType is so I'm just using a string for this example
type NodeType = string
interface GetNodesTypeOne {
readonly getNodesTypeOne: {
readonly nodes: NodeType[]
}
}
interface GetNodesTypeTwo {
readonly getNodesTypeTwo: {
readonly nodes: NodeType[]
}
}
type GqlRes = GetNodesTypeOne | GetNodesTypeTwo
const resultOne:GqlRes = {
getNodesTypeOne: {
nodes: [ "test" ]
}
}
const resultTwo:GqlRes = {
getNodesTypeTwo: {
nodes: [ "test" ]
}
}
所以这将解决 TypeScript 问题。然后稍后在您的代码中执行此操作:
const items = data?.getNodesTypeOne.nodes.map(...)
由于 data
可能包含 getNodesTypeOne
或 getNodesTypeTwo
我们需要将其更改为其他内容。一个快速的解决方法是 select 第一个有值的:
const nodes = "getNodesTypeOne" in data
? data?.getNodesTypeOne?.nodes
: data?.getNodesTypeTwo?.nodes
const items = nodes.map(...);
或者如果您想使用相同的条件:
const nodes = blockData.myType === 'type-one'
? (data as GetNodesTypeOne)?.getNodesTypeOne?.nodes
: (data as GetNodesTypeTwo)?.getNodesTypeTwo?.nodes
const items = nodes.map(...);
请注意,在第二个示例中,我们需要通过narrowing it down using a type assertion帮助TypeScript 确定具体类型。在第一个示例中,这不是必需的,因为 TypeScript 足够聪明,可以计算出第一个表达式将始终导致 GetNodesTypeOne
而第二个表达式将始终导致 GetNodesTypeOne
.
使用两个单独的查询来回答您的第二个问题:
- 添加一个新变量
useQueryOne
,如果我们 运行 查询一,则为true
,如果我们 运行,则为false
ning查询二. - 仅将适当的查询添加
skip
到useQuery
到 运行。 - 添加一个新变量
nodes
,其中包含第一个或第二个查询的结果(基于 useQueryOne 条件)
const useQueryOne = blockData.myType === 'type-one';
const { data: nodesOne } = useQuery<NodesOneGqlRes, NodesArgs>(NODES_TYPE_ONE,
{
variables: {
customKey: metadata.customKey || 0,
},
errorPolicy: 'all',
notifyOnNetworkStatusChange: true,
ssr: false,
skip: !useQueryOne
}
);
const { data: nodesTwo } = useQuery<NodesTwoGqlRes, NodesArgs>(NODES_TYPE_TWO,
{
variables: {
customKey: metadata.customKey || 0,
},
errorPolicy: 'all',
notifyOnNetworkStatusChange: true,
ssr: false,
skip: useQueryOne
}
);
const nodes = useQueryOne
? nodesOne?.getNodesTypeOne?.nodes
: nodesTwo?.getNodesTypeTwo?.nodes;
const items = (nodes || []).map(...);
我应该注意到您的查询是重复的,因此应该重构为单个查询(除非它们只是重复的命名空间而不是值)。
无论如何,您可以使用 useLazyQuery
以更安全的方式实现您想要的结果const [invokeQuery1, {loading, data, error}] = useLazyQuery<>(...)
const [invokeQuery2, {loading2, data2, error2}] = useLazyQuery<>(...)
// Run the query every time the condition changes.
useEffect(() => {
if (condition) {
invokeQuery1()
} else {
invokeQuery2()
}
}, [condition])
// Return the desired conditional daa
const {nodes} = useMemo(() => {
return condition ? data?.getNodesTypeOne : data2?.getNodesTypeTwo
} ,
[condition])
这也确保不会进行不必要的计算(您可以根据 events
调用您的查询,因为它们应该如此)。
-- 编辑
因为您坚持使用联合类型(并从源映射数据)。
这是一种可能的类型安全方法。
const isNodeTypeOne = (t: unknown): t is GetNodeTypeOne => {
return (t as GetNodeTypeOne).getNodesTypeOne !== undefined;
};
const { nodes } = isNodeTypeOne(data)
? data?.getNodesTypeOne
: data?.getNodesTypeTwo;
const items = nodes.map((val) => {
// Your mapping here
})
如果您有不同的节点类型,您还可以在地图中使用谓词。