【问题标题】:How do I map my Union typed GraphQL response Array in React and Typescript如何在 React 和 Typescript 中映射我的 Union 类型的 GraphQL 响应数组
【发布时间】: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,
      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;

现在我的商品只返回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 响应?在这种情况下,最好的方法是什么?

【问题讨论】:

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

标签: reactjs typescript apollo-client


【解决方案1】:

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

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

【讨论】:

  • 感谢您的回答。我更新了我的问题。
  • 你现在知道如何映射这个联合了吗?
  • 更新了答案:-)
  • 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 这解决了你的问题吗?
猜你喜欢
  • 2020-08-10
  • 1970-01-01
  • 2019-08-20
  • 2018-02-05
  • 2019-02-07
  • 2020-09-11
  • 1970-01-01
  • 2021-10-13
  • 2021-10-19
相关资源
最近更新 更多