如何在 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_ONENODES_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 挂钩。如果你想要,你需要确保 GqlResgetNodesTypeOnegetNodesTypeTwo.

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 可能包含 getNodesTypeOnegetNodesTypeTwo 我们需要将其更改为其他内容。一个快速的解决方法是 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查询二.
  • 仅将适当的查询添加 skipuseQuery 到 运行。
  • 添加一个新变量 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
})

如果您有不同的节点类型,您还可以在地图中使用谓词。