详解企业级使用Graphql开发项目

一、基本环境搭建

  • 1、创建一个react项目

    npx create-react-app react-graphql --template typescript
    
  • 2、根据文档在react中配置Graphql的环境,链接地址

    • 安装依赖包

      npm install @apollo/client graphql
      
    • 在index.ts中配置连接graphql的后端地址

      import { ApolloClient, InMemoryCache } from '@apollo/client';
      
      const client = new ApolloClient({
        uri: 'http://localhost:7001/graphql',
        cache: new InMemoryCache()
      });
      
    • 在index.ts文件中将apollo与react相连接起来

      import { ApolloProvider } from '@apollo/client/react';
      
      import React from 'react';
      import ReactDOM from 'react-dom';
      import App from './App';
      import { ApolloClient, InMemoryCache } from '@apollo/client';
      import { ApolloProvider } from '@apollo/client/react';
      
      const client = new ApolloClient({
        uri: 'http://localhost:7001/graphql',
        // 在这里配置请求头了
        headers: {
          token: storage.getItem(authToken),
        },
        cache: new InMemoryCache()
      });
      
      ReactDOM.render(
        <React.StrictMode>
          <ApolloProvider client={client}>
            <App />
          </ApolloProvider>
        </React.StrictMode>,
        document.getElementById('root')
      );
      
    • 运行项目看是否正常

  • 3、测试是否可以查询数据出来

    import React from 'react';
    import { useQuery, gql } from '@apollo/client';
    
    const AccountListGql = gql`
      query AccountList { # 定义查询方法(浏览器上显示的)
        accountList { # 定义与后端对接的方法名
          id
          username
          password
        }
      }
    `;
    
    export const Page1: React.FC = () => {
      const { loading, error, data } = useQuery(AccountListGql);
      console.log(loading);
      console.log(error);
      console.log(data);
      return <div>测试查询数据</div>
    }
    

二、查询数据

  • 1、简单的查询,上面使用useQuery就可以实现

  • 2、需要传递参数的查询

    import React from 'react';
    import { gql, useQuery } from '@apollo/client';
    
    const AccountGql = gql`
      query Account($id: ID!) {
        account(id: $id) {
          id
          username
          password
        }
      }
    `;
    export const Page2: React.FC = () => {
      const {  data } = useQuery(AccountGql, {
        variables: {
          id: 1
        }
      });
      console.log(data, '查询结果');
      return <div>根据条件来查询</div>
    }
    
  • 3、点击按钮才触发请求

    import React from 'react'
    import { gql, useLazyQuery } from '@apollo/client';
    
    const AccountListGql = gql`
      query AccountList { # 定义查询方法(浏览器上显示的)
        accountList { # 定义与后端对接的方法名
          id
          username
          password
        }
      }
    `;
    
    export const Page3: React.FC = () => {
      const [getAccount, { loading, data }] = useLazyQuery(AccountListGql);
      if (loading) return null;
      console.log(data, '请求回来的数据');
      return (
        <div>
          点击按钮请求数据
          <button onClick={() => getAccount()}>点击按钮</button>
        </div>
      )
    }
    
  • 4、点击按钮触发,需要传递参数进去

    const AccountGql = gql`
      query Account($id: ID!) {
        account(id: $id) {
          id
          username
          password
        }
      }
    `;
    
    export const Page3: React.FC = () => {
      const [getAccount, { loading: loading2, data: account }] = useLazyQuery(AccountGql);
      console.log(account, '根据条件返回的数据');
      return (
        <div>
          点击按钮请求数据
          <button onClick={() => getAccount({variables: {id:2}})}>传递参数按钮</button>
        </div>
      )
    }
    
  • 5、关于更多的使用请参考文档文档地址

三、对数据的增删改操作

  • 1、文档地址

  • 2、添加数据的操作

    import React from 'react';
    import {gql, useMutation} from '@apollo/client';
    
    const AddAccountGql = gql`
      mutation AddAccount($username: String!, $password: String!) {
        createAccount(username: $username, password: $password) {
          code
          message
        }
      }
    `;
    
    export const Page4: React.FC = () => {
      const [addTodo, { data }] = useMutation(AddAccountGql);
      console.log(data, '创建结果');
      const addAccountHandle = () => {
        addTodo({
          variables: {
            username: '王五',
            password: '123456',
          }
        })
      };
      return (
        <div>
          <h3>添加数据</h3>
          <button onClick={addAccountHandle}>添加数据</button>
        </div>
      )
    }
    

四、文件上传

上面的配置仅仅的对于简单的业务可以满足,如果你要对文件的上传操作上面的配置是不行的,下面介绍在graphql中上传文件的方式

  • 1、安装依赖包

    npm install apollo-upload-client@14.1
    npm install @types/apollo-upload-client -D
    
  • 2、修改graphql的配置项,必须要这样配置

    import { createUploadLink } from 'apollo-upload-client';
    ...
    const client = new ApolloClient({
      // uri: 'http://localhost:7001/graphql',
      link: {
        uri: 'http://localhost:7000/graphql',
        // 在这里配置请求头了
        headers: {
          token: storage.getItem(authToken),
        },
      },
      cache: new InMemoryCache()
    });
    
  • 3、在react中使用文件上传,这里使用自定义按钮来上传,因为html中自带的上传文件样式太丑了

    import { useMutation, gql, useQuery } from '@apollo/client';
    
    // 上传文件的gql
    const fileUploadGql = gql`
      mutation FileUpload($file: Upload!) {
        fileUpload(file: $file)
      }
    `;
    // 省去100行代码
    ...
    const [uploadFileApi, { data: uploadResult, error: uploadError }] = useMutation(fileUploadGql);
    ...
    // 省去100行代码
    
    const uploadHandler = () => {
      const fileNode: HTMLElement = document.getElementById('file') as HTMLElement;
      fileNode.click();
      fileNode.addEventListener<'change'>(
        'change',
        function ({
          target: {
            validity,
            files: [file],
          },
        }: // eslint-disable-next-line
                   any) {
          setIsShowFile(false);
          validity.valid && uploadFileApi({ variables: { file } });
          setTimeout(() => {
            setIsShowFile(true);
          });
        }
      );
    };
    
    // 省去100行代码
    {isShowFile && <input type="file" id="file" style={{ display: 'none' }} />}
    {imgUrl && (
      <img src={imgUrl} alt="" style={{ width: 100, height: 100, marginBottom: 10 }} />
    )}
    <div>
      <Button type="primary" onClick={uploadHandler}>上传封面图</Button>
    </div>
    

五、关于apollo错误处理

一般我们使用restfull api的时候会对axios二次封装,在里面统一处理错误,比如token失效的时候你要重定向到登录页面,上面介绍的方法中我们仅仅是使用apollo打通了前后接口,数据能增删改查,也能上传文件了,如果你的业务中没有登录的限制这里也可以不用关心了。但是作为有追求的码农,总要彻底的掌握一门技术,下面介绍如何对apollo二次简单封装,来处理错误

  • 1、在utils/initApollo.ts中对apollo简单的封装

    import {
      ApolloClient,
      ApolloLink,
      from as fromLinks,
      InMemoryCache,
      NormalizedCacheObject,
      QueryOptions,
      WatchQueryOptions,
    } from '@apollo/client';
    import { one rror } from '@apollo/client/link/error';
    import { createUploadLink } from 'apollo-upload-client';
    import { authToken } from 'src/config';
    import { storage } from './storage';
    
    let apolloClient: ApolloClient<NormalizedCacheObject> | null = null;
    
    const defaultOptions = {
      watchQuery: {
        fetchPolicy: 'no-cache',
        errorPolicy: 'ignore',
      } as WatchQueryOptions,
      query: {
        fetchPolicy: 'no-cache',
        errorPolicy: 'all',
      } as QueryOptions,
    };
    
    function create(): ApolloClient<NormalizedCacheObject> {
      const httpLink = createUploadLink({
        uri: 'http://localhost:7000/graphql',
        headers: {
          token: storage.getItem(authToken),
        },
      });
      const authMiddleware = new ApolloLink((operation, forward) => {
        const token = storage.getItem(authToken);
        if (token) {
          operation.setContext({
            headers: {
              token,
            },
          });
        }
        return forward(operation);
      });
      // 处理错误的时候
      const errorLink = one rror(({ graphQLErrors, networkError }) => {
        console.log(graphQLErrors, '错误');
        if (graphQLErrors) {
          graphQLErrors.map(({ message, locations, path }) => {
            // 根据错误处理业务,省去100行代码
            console.log(`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`);
            return false;
          });
        }
        if (networkError) {
          console.log(`[Network error]: ${networkError}`);
        }
      });
      return new ApolloClient({
        // errorLink 应在 httpLink 前
        link: fromLinks([errorLink, authMiddleware, httpLink]),
        cache: new InMemoryCache(),
        defaultOptions,
        connectToDevTools: true,
      });
    }
    
    export const initApollo = (): ApolloClient<NormalizedCacheObject> => {
      if (!apolloClient) {
        apolloClient = create();
      }
      return apolloClient;
    };
    
  • 2、在react的入口文件中使用

    import { initApollo } from './utils';
    
    const client = new ApolloClient(initApollo());
    
    ReactDOM.render(
      <ApolloProvider client={client}>
      	<Router />
      </ApolloProvider>,
      document.getElementById('root')
    );
    

六、使用后端的schema自动生成hooks

  • 1、参考文档

  • 2、安装依赖包

    npm install --save graphql
    npm install --save-dev @graphql-codegen/cli
    
  • 3、初始化,直接选择默认和Y就可以

    npx graphql-codegen init
    

    详解企业级使用Graphql开发项目

  • 4、默认生成的codegen.yml文件修改如下

    overwrite: true
    schema: "http://localhost:7000/graphql" # 根据自己后端服务器地址来写,
    # schema: "./schema.graphql" # 将后端的schema拷贝到前端项目中
    documents: "src/**/*.graphql" # 表示会去查找graphql结尾的文件
    generates:
      src/generated/graphql.ts:
        plugins:
          - "typescript"
          - "typescript-operations"
          - "typescript-react-apollo"
    
  • 5、我们在src文件夹下创建一个graphql的文件夹,创建两个login.graphql和register.graphql文件

    # login.graphql文件内容
    query Login($username: String!, $password: String!) {
      # 定义查询方法(浏览器上显示的)
      login(data: { username: $username, password: $password }) {
        id
        username
        token
      }
    }
    
    # register.graphql文件内容
    mutation registerUser($username: String!, $password: String!, $confirmPassword: String!) {
      register(data: { username: $username, password: $password, confirmPassword: $confirmPassword })
    }
    
  • 6、运行安装依赖包

    使用命令npx graphql-codegen init初始化的时候,并不会帮我们安装依赖包的,只是会添加依赖包到package.json中

  • 7、运行命令生成对应的hooks,注意这个要看自己根据第三小点生成的命令来运行,或者自己配置的命令

    npm run codegen
    
  • 8、在生成的src/generated/graphql.ts文件中最底部可以查看到生成了登录和注册的hooks,因为我们只写了这两个接口的graphql文件

  • 9、在App.tsx组件中调用

    import React, { useEffect } from 'react';
    import { useLoginLazyQuery, useRegisterUserMutation } from './generated/graphql';
    
    function App(): React.ReactElement {
      const [loginApi, { data: loginResult }] = useLoginLazyQuery();
      const [registerApi] = useRegisterUserMutation();
      const loginHandler = () => {
        loginApi({ variables: { username: 'admin', password: '123456' } });
      };
      const registerHandler = async () => {
        const result = await registerApi({ variables: { username: 'test1', password: '123456', confirmPassword: '123456' } });
        console.log(result, '注册结果');
      };
      useEffect(() => {
        if (loginResult) {
          console.log('登录信息', loginResult);
        }
      }, [loginResult]);
      return (
        <div className='App'>
          <button onClick={loginHandler}>登录</button>
          <button onClick={registerHandler}>注册</button>
        </div>
      );
    }
    
    export default App;
上一篇:javascript – 如何使用GraphQL将图像上传到AWS S3?


下一篇:GraphQL太香了!直接干掉RESTful!