设置 React 应用程序
设置一个将在课程后续部分使用的 React 应用程序。
我们不会在本节中讨论使用 GraphQL 的认证。相反,我们将重点放在前端和后端的认证实现上。
开发页面布局
在本节课中,我们将简要介绍如何设置一个包含本课程所需所有依赖项的 React 项目模板。我们只涵盖前端工作中必要的部分,但了解整个项目的结构非常重要。本课程的完整前端源代码可在 GitHub 仓库中获取。
我们不需要成为 React 专家才能学习本节内容,但了解 React 的基础知识(如组件和属性)会有所帮助。在课程后续部分,我们会简要回顾一些更高级的 React 主题,例如 React Hooks。
我们将使用的库
在本课程中,我们将构建一个单页应用(SPA),它将与我们已开发的 API 进行交互。这意味着所有 UI 变化(如页面之间的切换)都将在客户端实现。服务器仅响应查询和执行变更操作。
为了使应用的实现更加简单,我们将使用一些额外的库。这些库与 GraphQL 或 Apollo 没有直接关系,因此选择它们完全是个人偏好问题。
- Material UI:一个丰富的 React 组件 UI 库,可帮助更快地构建应用。
- React Router:一个客户端路由工具,可根据当前 URL 路径渲染不同的组件。
- Formik:这是在 React 中实现表单最流行的库之一,因为它很容易与 Material UI 集成。我们稍后将使用它来实现登录功能以及创建新产品的表单。
初始化命令
首先,我们需要创建一个 React 项目。我们不必手动创建,可以使用 init react-app 命令来自动生成一个基础项目。
1npm init react-app client
client 参数是命令应创建模板项目的文件夹名称。
为了启动这个基础项目,我们还需要安装 React 模板所使用的 web-vitals 依赖。
1npm install web-vitals
现在,我们的项目中包含了 client 和 server 文件夹。在本节课中,我们将主要在 client 文件夹中进行修改。
1├── LICENSE 2├── README.md 3├── client 4│ ├── README.md 5│ ├── node_modules 6│ ├── package-lock.json 7│ ├── package.json 8│ ├── public 9│ ├── src 10│ └── yarn.lock 11└── server 12 ├── data 13 ├── docker-compose.yml 14 ├── mongo-init 15 ├── node_modules 16 ├── nodemon.json 17 ├── package-lock.json 18 ├── package.json 19 └── src
现在我们可以运行前端应用了。只需在 client 文件夹中执行 npm start 命令即可。
1cd client 2npm start
该命令将在 localhost:3000 启动一个开发服务器,用于我们的客户端应用,并显示来自 React 模板项目的示例页面。注意,端口号与 GraphQL 服务器不同(GraphQL 服务器运行在 localhost:4000),因此我们可以同时运行客户端和服务器。
如果我们更新该项目中的任何代码,开发服务器将自动重新构建客户端,并将更新后的代码推送到浏览器。
我们的前端界面
在深入实现细节之前,让我们先看看我们的应用长什么样子。主页以单列形式展示来自 GraphQL API 的所有产品列表。

每个产品的标题都是一个链接,指向该产品的详情页。作者和分类名称也是链接,分别指向按分类或作者列出产品列表的页面。在本课程后续部分,我们将实现点赞按钮。
如果点击页面顶部的“添加新产品”,将跳转到创建新产品的表单页面。

安装依赖
在实现应用布局之前,我们需要安装所用的依赖项。首先需要安装 Material UI 库。
1npm install @mui/material @mui/icons-material @emotion/react @emotion/cache
我们还需要为客户端路由安装 React Router。
1npm install react-router-dom
目前我们还不需要 Formik 库,但可以提前安装,以便后续使用。
主页面
在学习如何在客户端使用 Apollo 之前,我们先来看一下应用的主要部分。应用的入口点位于由 init 命令生成的 index.js 文件中。
1// index.js 2 3import React from 'react' 4import ReactDOM from 'react-dom' 5import './index.css' 6import App from './App' 7import reportWebVitals from './reportWebVitals' 8 9ReactDOM.render( 10 <React.StrictMode> 11 <App /> 12 </React.StrictMode>, 13 document.getElementById('root'), 14)
注意,我们渲染的是 App 组件,它是应用的根组件。我们马上就会看到它的实现方式。
大部分应用代码被拆分为位于 components 文件夹中的组件,结构如下:
1├── App.css 2├── App.js 3├── App.test.js 4├── components 5│ ├── AllProducts.js 6│ ├── Header.js 7│ ├── Layout.css 8│ ├── Layout.js 9│ ├── NewProduct.css 10│ ├── NewProduct.js 11│ ├── Product.css 12│ ├── Product.js 13│ ├── ProductsByUser.js 14│ ├── ProductsInCategory.js 15│ ├── ProductsList.css 16│ ├── ProductsList.js 17│ └── test-data.js 18├── index.css 19├── index.js 20├── logo.svg 21├── reportWebVitals.js 22└── setupTests.js
CSS 文件包含与同名组件对应的样式。
App 组件
现在我们来看 App.js。虽然代码较长,但我们逐步解析它。
在一些导入和对象定义之后,App 组件将应用的其余部分包裹在两个顶层组件中:
ThemeProvider:用于定义应用使用的主题,并设置默认背景颜色。CacheProvider:用于简化定义组件样式的 CSS 文件。
1import React from 'react' 2import CssBaseline from '@mui/material/CssBaseline' 3 4import Header from './components/Header' 5import Layout from './components/Layout' 6import AllProducts from './components/AllProducts' 7import ProductsByUser from './components/ProductsByUser' 8import ProductsInCategory from './components/ProductsInCategory' 9import NewProduct from './components/NewProduct' 10import { createTheme, ThemeProvider } from '@mui/material/styles' 11import { CacheProvider } from '@emotion/react' 12import createCache from '@emotion/cache' 13import { 14 BrowserRouter as Router, 15 Switch, 16 Route, 17} from 'react-router-dom' 18 19const cache = createCache({ 20 key: 'css', 21 prepend: true, 22}) 23 24const theme = createTheme({ 25 palette: { 26 background: { 27 default: '#FAFAFA' 28 } 29 } 30}) 31 32function App() { 33 return ( 34 <ThemeProvider theme={theme}> 35 <CacheProvider value={cache}> 36 <Router> 37 ... 38 </Router> 39 </CacheProvider> 40 </ThemeProvider> 41 ) 42} 43 44export default App
Router 组件定义了应用中哪些组件会被渲染。Switch 组件放置在路由器内部,根据浏览器路径决定渲染哪个组件。例如,当用户访问 /create 路径时,将渲染 NewProduct 组件;当用户访问 /author/peter 时,将渲染 ProductsByUser 组件并传入 peter 作为参数。我们将在后续课程中学习如何访问这些参数并将其发送到 GraphQL API。
1<Router> 2 <div className="App"> 3 <CssBaseline /> 4 <Header /> 5 <Layout> 6 <Switch> 7 <Route path="/create"> 8 <NewProduct /> 9 </Route> 10 <Route path="/author/:userName"> 11 <ProductsByUser /> 12 </Route> 13 <Route path="/category/:slug"> 14 <ProductsInCategory /> 15 </Route> 16 <Route path="/"> 17 <AllProducts /> 18 </Route> 19 20 </Switch> 21 </Layout> 22 </div> 23</Router>
Switch 外部的内容将在应用的所有页面中通用,并始终显示:
CssBaseline:Material UI 提供的组件,用于跨浏览器统一样式。Layout:自定义组件,定义应用的一列布局。Header:实现页面顶部的导航栏,包含几个按钮。
让我们看一下 AllProducts 组件,因为我们将会频繁使用它。当用户访问主页时,该组件被渲染,并显示应用中所有产品的列表。其实现非常简单:它使用 Typography 组件显示标题,然后使用 ProductsList 组件来展示产品列表。
1import React from 'react' 2import Typography from '@mui/material/Typography' 3import allProducts from './test-data' 4import ProductsList from './ProductsList' 5 6export default function AllProducts() { 7 return ( 8 <> 9 <Typography variant="h3">Products</Typography> 10 <ProductsList products={allProducts} /> 11 </> 12 ) 13}
目前,我们不会从 GraphQL API 获取产品数据,而是直接渲染 test-data.js 文件中的一组硬编码对象,其结构与我们的 API 返回的对象一致。
ProductsList 为传入的每个产品渲染一个 Product 组件的实例。
1import React from 'react' 2import { Grid } from '@mui/material' 3import Product from './Product' 4import PropTypes from 'prop-types' 5import './ProductsList.css' 6 7function ProductsList(props) { 8 const {products} = props 9 return ( 10 <Grid container spacing={3} className="productsList"> 11 {products.map((product) => ( 12 <Product 13 product={product} 14 key={product.id} /> 15 ))} 16 </Grid> 17 ) 18} 19 20ProductsList.propTypes = { 21 products: PropTypes.array.isRequired, 22} 23 24export default ProductsList
在 React 中创建组件列表时,必须为每个组件分配唯一的 key 属性值。我们可以使用每个产品的 ID 作为键。
Product 组件显示产品对象中的所有信息,包括产品名称、发布时间、用户名和分类。
交互式演示
接下来,我们的交互式演示将同时包含前端和后端。与之前一样,点击“运行”按钮启动应用,但现在请等待以下消息出现:
1Compiled successfully! 2 3You can now view client in the browser.
当应用准备就绪后,点击下方链接打开我们的应用网页界面。目前它尚未连接到 GraphQL 后端,但我们在本章结束前会完成这一配置。
我们还可以在下面的代码窗口中探索整个 Web 应用的源代码。
No configuration found for ID: 6084ee424a835f4abbf77bc8438dc2ac6339793752e22cef81e786a8a478686c
源代码
要获取本章的源代码,请在项目仓库中运行以下命令:
1git checkout ui-setup
总结
在本节课中,我们花了不少时间来搭建 React 应用,而这些工作与 GraphQL 无关。但这段时间并非白费!现在我们已经理解了应用的整体结构,这将有助于我们更好地理解后续专注于 GraphQL 的课程内容和练习。