如何在 React 和 Typescript 中映射我的 Union 类型的 GraphQL 响应数组

Posted

技术标签:

【中文标题】如何在 React 和 Typescript 中映射我的 Union 类型的 GraphQL 响应数组【英文标题】:How do I map my Union typed GraphQL response Array in React and Typescript 【发布时间】:2022-01-06 07:13:45 【问题描述】:

我正在使用 React、Typescript 和 Apollo 客户端。

在我的 React 组件中,我使用 useQuery 钩子 NODES_TYPE_ONENODES_TYPE_TWO 基于值 blockData.myType 进行查询。这很好用。

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,
      s-s-r: 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;

现在我的商品只返回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,
          s-s-r: false,
        
      );

const  data: nodesTwo  = useQuery<NodesTwoGqlRes, NodesArgs>(NODES_TYPE_TWO,
        
          variables: 
            customKey: metadata.customKey || 0,
          ,
          errorPolicy: 'all',
          notifyOnNetworkStatusChange: true,
          s-s-r: 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 响应?在这种情况下,最好的方法是什么?

【问题讨论】:

等等,我很困惑。为什么你有两个包含完全相同实体的实体容器? 另外,当有人回答您的问题时,您可能希望扩大话题,而不仅仅是编辑自己的帖子。 你能提供你正在做的gql查询吗? @Urmzd 将查询添加到我的问题中。谢谢 @Urmzd 首先尝试让我的示例正常工作。我现在如何映射我的联合节点数组? 【参考方案1】:

如果我直接理解您的代码,那么根据 blockData.myType 的值,您要么执行一个查询,要么执行另一个查询,并且您想为这个逻辑重用相同的 useQuery 钩子。如果你想要,你需要确保 GqlRes 是 union type 的 getNodesTypeOnegetNodesTypeTwo

// 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 可能包含getNodesTypeOnegetNodesTypeTwo,我们需要将其更改为其他内容。一个快速的解决方法是只选择第一个具有值的:

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(...);

请注意,在第二个示例中,我们需要使用 type assertion 帮助 TypeScript 通过 narrowing 找出具体类型。在第一个示例中,这不是必需的,因为 TypeScript 足够聪明,可以确定第一个表达式将始终导致 GetNodesTypeOne,而第二个表达式将始终导致 GetNodesTypeOne


使用两个单独的查询来回答您的第二个问题:

添加一个新变量useQueryOne,即true,以防我们运行查询一,false,以防我们运行查询二。 将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,
        s-s-r: false,
        skip: !useQueryOne
    
);

const  data: nodesTwo  = useQuery<NodesTwoGqlRes, NodesArgs>(NODES_TYPE_TWO,
    
        variables: 
            customKey: metadata.customKey || 0,
        ,
        errorPolicy: 'all',
        notifyOnNetworkStatusChange: true,
        s-s-r: false,
        skip: useQueryOne
    
);

const nodes = useQueryOne
    ? nodesOne?.getNodesTypeOne?.nodes
    : nodesTwo?.getNodesTypeTwo?.nodes;
const items = (nodes || []).map(...);

【讨论】:

感谢您的回答。我更新了我的问题。 你现在知道如何映射这个联合了吗? 更新了答案:-) ok 方法很明确,谢谢。但我得到这个打字稿错误:Property 'getNodesTypesOne' does not exist on type 'GqlRes'.。好像我无法访问属性 getNodesTypesOnegetNodesTypesTwo @meez 不幸的是 Martin 的代码不正确,他应该使用 : 而不是 |。此外,它不是类型安全的。您需要使用类型谓词。同样,所有这些都可以通过拆分查询来解决。【参考方案2】:

我应该注意到您的查询是重复的,因此应该重构为单个查询(除非它们只是重复的命名空间而不是值)。

无论如何,您都可以使用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
)

如果您有不同的节点类型,您也可以在映射中使用谓词。

【讨论】:

@meez 已更新以回答您的其他问题。 我更新了我的问题。谢谢 @meez 看我的第一个答案(useEffect 和 useMemo) @meez 这解决了你的问题吗?

以上是关于如何在 React 和 Typescript 中映射我的 Union 类型的 GraphQL 响应数组的主要内容,如果未能解决你的问题,请参考以下文章

在 Flutter 中映射流

如何在 React 和 Typescript 中映射我的 Union 类型的 GraphQL 响应数组

如何在 React 和 TypeScript 项目中使用 types.d.ts 文件

如何使用 React 和 TypeScript 设置 Electron?

如何在提供者的值中传递数组和 setArray(React、Typescript、Context)

如何在 React 中正确使用 useState 钩子和 typescript?