# 项目实战

# 1.生成项目

graphqlfront

create-react-app client --typescript
cd client
cnpm start
1
2
3

# 2.安装依赖

get-started
cnpm install apollo-boost @apollo/react-hooks graphql --save
cnpm i bootstrap@3 --save
1
2
3
模块名 含义
apollo-boost Package containing everything you need to set up Apollo Client
@apollo/react-hooks React hooks based view layer integration
graphql Also parses your GraphQL queries

# 3.连接接口

# 3.1 src\index.tsx

src\index.tsx

import React from "react"
import ReactDOM from "react-dom"
import ApolloClient from "apollo-boost"
import { gql } from "apollo-boost"
const client = new ApolloClient({
  uri: "http://localhost:4000/graphql",
})

client
  .query({
    query: gql`
      query {
        getCategories {
          id
          name
        }
      }
    `,
  })
  .then((result) => console.log(result))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 4.实现前台功能

# 4.1 src\App.tsx

import React from "react"
import ReactDOM from "react-dom"
import ApolloClient from "apollo-boost"
import { ApolloProvider } from "@apollo/react-hooks"
import "bootstrap/dist/css/bootstrap.css"
import App from "./App"
const client = new ApolloClient({ uri: "http://localhost:4000/graphql" })
ReactDOM.render(
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>,
  document.getElementById("root")
)
1
2
3
4
5
6
7
8
9
10
11
12
13

# 4.2 src\App.tsx

src\App.tsx

import React, { useState } from 'react';
import { CATEGORIES_PRODUCTS } from './query';
import { useQuery } from '@apollo/react-hooks';
import AddProduct from './AddProduct';
import ProductList from './ProductList';
import ProductDetail from './ProductDetail';
import { Product } from './types';
function App() {
    const [product, setProduct] = useState<Product>();
    const { loading, error, data } = useQuery(CATEGORIES_PRODUCTS);
    if (loading) {
        return <p>加载中...</p>;
    }
    if (error) {
        return <p>加载错误</p>;
    }
    let { getCategories, getProducts } = data;
    return (
        <div className="container">
            <div className="row" >
                <div className="col-md-6" >
                    <div className="panel panel-default" style={{ padding: 20 }}>
                        <div className="panel-header">
                            <AddProduct getCategories={getCategories} />
                        </div>
                        <div className="text-center" style={{ height: '400px', overflow: 'scroll' }}>
                            <ProductList getProducts={getProducts} setProduct={setProduct} />
                        </div>
                    </div>
                </div>
                <div className="col-md-6" >
                    <div className="panel panel-default" style={{ padding: 20 }}>
                        <div className="text-center">
                            <ProductDetail product={product} />
                        </div>
                    </div>
                </div>
            </div>
        </div>
    )
}
export default App;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

# 4.3 src\types.tsx

src\types.tsx

export interface Category {
  id?: string;
  name?: string;
}
export interface Product {
  id?: string;
  name?: string;
  categoryId?: string;
  category?: Category;
}
1
2
3
4
5
6
7
8
9
10

# 4.4 src\query.tsx

src\query.tsx

import { gql } from "apollo-boost"
export const CATEGORIES_PRODUCTS = gql`
  query {
    getCategories {
      id
      name
      products {
        id
        name
      }
    }
    getProducts {
      id
      name
      category {
        id
        name
        products {
          id
          name
        }
      }
    }
  }
`
export const CATEGORIES = gql`
  query {
    getCategories {
      id
      name
    }
  }
`
export const PRODUCTS = gql`
  query {
    getProducts {
      id
      name
      category {
        id
        name
      }
    }
  }
`
export const ADD_PRODUCT = gql`
  mutation($name: String!, $categoryId: String!) {
    addProduct(name: $name, category: $categoryId) {
      id
      name
      category {
        id
        name
      }
    }
  }
`
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

# 4.5 src\AddProduct.tsx

src\AddProduct.tsx

import React, { useState } from "react"
import { Category, Product } from "./types"
import { PRODUCTS, ADD_PRODUCT } from "./query"
import { useMutation } from "@apollo/react-hooks"
function AddProduct(props: any) {
  const [product, setProduct] =
    useState < Product > { name: "", categoryId: props.getCategories[0].id }
  const [addProduct] = useMutation(ADD_PRODUCT)
  function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault()
    addProduct({ variables: product, refetchQueries: [{ query: PRODUCTS }] })
  }
  return (
    <form onSubmit={handleSubmit}>
      <div className="form-group">
        <label htmlFor="product_name">商品名称</label>
        <input
          onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
            setProduct({ ...product, name: event.target.value })
          }
          className="form-control"
          id="product_name"
          placeholder="商品名称"
        />
      </div>
      <div className="form-group">
        <label htmlFor="categoryId">商品分类</label>
        <select
          onChange={(event: React.ChangeEvent<HTMLSelectElement>) =>
            setProduct({ ...product, categoryId: event.target.value })
          }
          className="form-control"
          id="categoryId"
        >
          <option>请选择分类</option>
          {props.getCategories.map((category: Category) => {
            return (
              <option key={category.id} value={category.id}>
                {category.name}
              </option>
            )
          })}
        </select>
      </div>
      <div className="form-group">
        <input className="btn btn-primary" type="submit" />
      </div>
    </form>
  )
}
export default AddProduct
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

# 4.6 ProductList.tsx

src\ProductList.tsx

import React from 'react';
import { Product } from './types';
function ProductList(props: any) {
    return (
        <table className="table table-striped">
            <caption className="text-center">产品列表</caption>
            <thead>
                <tr className="active">
                    <td>名称</td><td>分类</td>
                </tr>
            </thead>
            <tbody>
                {
                    props.getProducts.map((product: Product, index: number) => (
                        <tr key={product.id} onClick={() => props.setProduct(product)}>
                            <td>{product.name}</td><td>{product.category!.name}</td>
                        </tr>
                    ))
                }
            </tbody>
        </table>
    )
}
export default ProductList;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 4.7 ProductDetail.tsx

src\ProductDetail.tsx

import React from "react"
import { Product } from "./types"
function ProductDetail(props: any) {
  console.log(props.product)

  if (!props.product) return null
  return (
    <ul className="list-group">
      <li className="list-group-item">ID:{props.product.id}</li>
      <li className="list-group-item">名称:{props.product.name}</li>
      <li className="list-group-item">分类:{props.product.category.name}</li>
      <li className="list-group-item">
        此分类下所有产品:
        <ul className="list-group">
          {props.product.category.products.map(
            (product: Product, index: number) => (
              <li key={product.id} className="list-group-item">
                {product.name}
              </li>
            )
          )}
        </ul>
      </li>
    </ul>
  )
}
export default ProductDetail
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27