React-企业级实践指南-六-

120 阅读27分钟

React 企业级实践指南(六)

原文:Practical Enterprise React

协议:CC BY-NC-SA 4.0

十五、创建通知、安全性和订阅页面

本系列的最后一部分将使用 Redux、Formik 和 Yup 验证模式制作通知、安全性和订阅页面。

总的来说,我们的目标是用现实世界中的基本功能完成 React 应用的 UI,并巩固我们对 Redux 如何工作以及如何在我们的应用中使用 Formik 和 Yup 验证模式的了解。

创建通知页面

我们将首先处理通知页面。这个页面只是为了应用的设计或整体 UI 外观。

在 AccountView 下,我们需要创建一个名为 Notifications.tsx 的文件:

account ➤ AccountViewNotifications.tsx

下面是命名的导入组件,如清单 15-1 所示。

import React from 'react';
import clsx from 'clsx';
import {
  Box,
  Button,
  Card,
  CardContent,
  CardHeader,
  Checkbox,
  Divider,
  FormControlLabel,
  Grid,
  Typography,
  makeStyles,
} from '@material-ui/core';

Listing 15-1Adding the Named Components of the Notifications.tsx

以及通知组件的形状,如清单 15-2 所示。

type Props = {
  className?: string;
};

const Notifications = ({ className, ...rest }: Props) => {
  const classes = useStyles();

Listing 15-2Adding the Notifications Function Component

以及返回元素的 return 语句,如清单 15-3 所示。

return (
    <form>
      <Card className={clsx(classes.root, className)} {...rest}>
        <CardHeader title="Notifications" />
        <Divider />
        <CardContent>
          <Grid container spacing={6} wrap="wrap">
            <Grid item md={4} sm={6} xs={12}>
<Typography gutterBottom variant="h6" color="textPrimary">
                System
              </Typography>
              <Typography gutterBottom variant="body2" color="textSecondary">
                You will receive emails in your business email address
              </Typography>
              <div>
                <FormControlLabel
                  control={<Checkbox defaultChecked />}
                  label="Email alerts"
                />
              </div>
              <div>
                <FormControlLabel
                  control={<Checkbox />}
                  label="Push Notifications"
                />
              </div>

              <div>
                <FormControlLabel
                  control={<Checkbox defaultChecked />}
                  label="Text message"
                />
              </div>
              <div>
                <FormControlLabel
                  control={<Checkbox defaultChecked />}
                  label={
                     <>

                      <Typography variant="body1" color="textPrimary">
                        Phone calls
                      </Typography>
                      <Typography variant="caption">
                        Short voice phone updating you
                      </Typography>
                    </>

                  }
                />
              </div>
            </Grid>

            <Grid item md={4} sm={6} xs={12}>
              <Typography gutterBottom variant="h6" color="textPrimary">
                Chat App
              </Typography>
              <Typography gutterBottom variant="body2" color="textSecondary">
  You will receive emails in your business email address
              </Typography>

              <div>
                <FormControlLabel
                  control={<Checkbox defaultChecked />}
                  label="Email"
                />
              </div>
              <div>
                <FormControlLabel
                  control={<Checkbox defaultChecked />}
                  label="Push notifications"
                />
              </div>
            </Grid>
          </Grid>
        </CardContent>
        <Divider />
        <Box p={2} display="flex" justifyContent="flex-end">
 <Button color="secondary" type="submit" variant="contained">
            Save Settings
          </Button>
        </Box>
      </Card>
    </form>
  );
};

const useStyles = makeStyles(() => ({
  root: {},
}));

export default Notifications;

Listing 15-3Creating the Return Elements of the Notifications

我们这里的通知组件只是为了美观。在这一点上,我们不会使用任何额外的功能。一般设置应该有助于我们理解 Redux 的实现流程。

但是,您可以自己更新它,只是为了练习和巩固您的知识。

创建安全性页面

我们需要添加的另一个 React 组件是安全性。同样,在AccountView,下创建一个新文件,并将其命名为Security.tsx.

我们将在这里使用简单的 HTTP 请求,而不使用 Redux 工具包。我们创建这个是为了提醒你,我们不需要在每个 HTTP 请求中使用 Redux,就像我们在第六章中所做的那样。

img/506956_1_En_15_Figa_HTML.jpg

好了,现在让我们为安全性添加命名的组件,如清单 15-4 所示。

import React, { useState } from 'react';
import clsx from 'clsx';
import * as Yup from 'yup';
import { Formik } from 'formik';
import { useSnackbar } from 'notistack';
import { useSelector } from 'react-redux';
import {
  Box,
  Button,
  Card,
  CardContent,
  CardHeader,
  Divider,
  FormHelperText,
  Grid,
  TextField,
  makeStyles,
} from '@material-ui/core';

import { changePasswordAxios, ChangePasswordModel } from 'services/authService';
import { RootState } from 'store/reducers';

Listing 15-4Importing the Named Components in Security

我们使用的是 Yup 和 Formik 以及我们需要的其他常用组件。我们还从authService进口了changePasswordAxiosChangePasswordModel,从减速器进口了RootState。如您所见,没有任何操作会向存储发送请求或更新任何内容。

接下来,我们添加对象的类型并使用一些 React 钩子,如清单 15-5 所示。

type Props = {
  className?: string;
};

type PasswordType = {
  password: string;
  passwordConfirm: string;
};

const Security = ({ className, ...rest }: Props) => {
  const { claims } = useSelector((state: RootState) => state.auth);
  const classes = useStyles();
  const [error, setError] = useState('');
  const { enqueueSnackbar } = useSnackbar();

Listing 15-5Creating the Security Function Component

声明:我们通过 useSelector 从 auth reducer 获取声明。

以及安全组件的返回元素,如清单 15-6 所示。

return (
    <Formik
      initialValues={
        {
          password: '',
          passwordConfirm: '',
        } as PasswordType
      }

       {/*validation schema for the password */}

      validationSchema={Yup.object().shape({
        password: Yup.string()
          .min(7, 'Must be at least 7 characters')
          .max(255)
          .required('Required'),
        passwordConfirm: Yup.string()
          .oneOf([Yup.ref('password'), null], 'Passwords must match')
          .required('Required'),
      })}
      onSubmit={async (values, formikHelpers) => {
        try {

           {/*Checking if the password matches or not */}

          if (values.password !== values.passwordConfirm) {
            alert('Must match');
            return;
          }

          {/* If it matches, return this object with the
            following args to change password */}
          const args: ChangePasswordModel = {
            id: claims.sub,
            email: claims.email,
            password: values.password,
          };

          await changePasswordAxios(args);

          formikHelpers.resetForm();
          formikHelpers.setStatus({ success: true });
          formikHelpers.setSubmitting(false);
          enqueueSnackbar('Password updated', {
            variant: 'success',
          });
        } catch (err) {
          console.error(err);
          formikHelpers.setStatus({ success: false });
          formikHelpers.setSubmitting(false);
        }
      }}
    >
      {formikProps => (
        <form onSubmit={formikProps.handleSubmit}>
          <Card className={clsx(classes.root, className)} {...rest}>
            <CardHeader title="Change Password" />
            <Divider />
            <CardContent>
              <Grid container spacing={3}>
                <Grid item md={4} sm={6} xs={12}>
                  <TextField
                    error={Boolean(
                      formikProps.touched.password &&
                        formikProps.errors.password,
                    )}
                    fullWidth
                    helperText={
                      formikProps.touched.password &&
                      formikProps.errors.password
                    }
                    label="Password"
                    name="password"
                    onBlur={formikProps.handleBlur}
                    onChange={formikProps.handleChange}
                    type="password"
                    value={formikProps.values.password}
                    variant="outlined"
                  />
                </Grid>

                <Grid item md={4} sm={6} xs={12}>
                  <TextField
                    error={Boolean(
                      formikProps.touched.passwordConfirm &&
                        formikProps.errors.passwordConfirm,
                    )}
                    fullWidth
                    helperText={
                      formikProps.touched.passwordConfirm &&
                      formikProps.errors.passwordConfirm
                    }
                    label="Password Confirmation"
                    name="passwordConfirm"
                    onBlur={formikProps.handleBlur}
                    onChange={formikProps.handleChange}
                    type="password"
                    value={formikProps.values.passwordConfirm}
                    variant="outlined"
                  />
                </Grid>
              </Grid>
              {error && (
                <Box mt={3}>
                  <FormHelperText error>{error}</FormHelperText>
                </Box>
              )}
            </CardContent>
            <Divider />

            <Box p={2} display="flex" justifyContent="flex-end">
              <Button
                color="secondary"
                disabled={formikProps.isSubmitting}
                type="submit"
                variant="contained"
              >
                Change Password
              </Button>
            </Box>
          </Card>
        </form>
      )}
    </Formik>
  );
};

const useStyles = makeStyles(() => ({
  root: {},
}));

export default Security;

Listing 15-6Returning the Elements for the Security React Component

创建订阅页面

我们将在本章构建的最后一个组件是Subscription.tsx,,它仍然在AccountView文件夹下。

让我们首先添加命名的组件,如清单 15-7 所示。

import { Link as RouterLink } from 'react-router-dom';
import clsx from 'clsx';
import { useSelector } from 'react-redux';
import {
  Box,
  Button,
  Card,
  CardContent,
  CardHeader,
  Divider,
  Link,
  Paper,
  Typography,
  makeStyles,
} from '@material-ui/core';

import { RootState } from 'store/reducers';

Listing 15-7Adding the Named Components in the Subscription

接下来,让我们做对象的类型或形状,如清单 15-8 所示。

type Props = {
  className?: string;
};

const Subscription = ({ className, ...rest }: Props) => {
  const classes = useStyles();
  const {
    profile: { subscription },
  } = useSelector((state: RootState) => state.profile);

Listing 15-8Adding the Props and Using Hooks for Subscription.tsx

在清单 15-8 中,我们访问概要文件,然后在清单 15-9 中的 UI 中呈现管理您的订阅。

例如,我们可以访问subscription.currency, subscription.price,和subscription.name——因为subscription是剖析的对象。嵌套析构在这里是一种有效的语法。

清单 15-9 是订阅组件的返回语句。

return (
    <Card className={clsx(classes.root, className)} {...rest}>
      <CardHeader title="Manage your subscription" />
      <Divider />
      <CardContent>
        <Paper variant="outlined">
          <Box className={classes.overview}>
            <div>
              <Typography display="inline" variant="h4" color="textPrimary">
                {subscription.currency}
                {subscription.price}
              </Typography>
              <Typography display="inline" variant="subtitle1">
                /mo
              </Typography>
            </div>
            <Box display="flex" alignItems="center">
              <img
                alt="Product"
                className={classes.productImage}
                srcimg/product_premium.svg"
              />
              <Typography variant="overline" color="textSecondary">
                {subscription.name}
              </Typography>
            </Box>
          </Box>
          <Divider />

          <Box className={classes.details}>
            <div>
              <Typography variant="body2" color="textPrimary">
                {`${subscription.proposalsLeft} proposals left`}
              </Typography>
              <Typography variant="body2" color="textPrimary">
                {`${subscription.templatesLeft} templates`}
              </Typography>
            </div>
            <div>
              <Typography variant="body2" color="textPrimary">
                {`${subscription.invitesLeft} invites left`}
              </Typography>
              <Typography variant="body2" color="textPrimary">
                {`${subscription.adsLeft} ads left`}
              </Typography>
            </div>
            <div>
              {subscription.hasAnalytics && (
                <Typography variant="body2" color="textPrimary">
                  Analytics dashboard
                </Typography>
              )}
              {subscription.hasEmailAlerts && (
                <Typography variant="body2" color="textPrimary">
                  Email alerts
                </Typography>
              )}
            </div>
          </Box>
        </Paper>

        <Box mt={2} display="flex" justifyContent="flex-end">
          <Button size="small" color="secondary" variant="contained">
            Upgrade plan
          </Button>
        </Box>
        <Box mt={2}>
          <Typography variant="body2" color="textSecondary">
            The refunds don&apos;t work once you have the subscription, but you
            can always{' '}
            <Link color="secondary" component={RouterLink} to="#">
              Cancel your subscription
            </Link>
            .
          </Typography>
        </Box>
      </CardContent>
    </Card>
  );
};

Listing 15-9Adding the Return Statement of the Subscription Component

最后,这个组件的样式,如清单 15-10 所示。

const useStyles = makeStyles(theme => ({
  root: {},
  overview: {
    padding: theme.spacing(3),
    display: 'flex',
    alignItems: 'center',
    flexWrap: 'wrap',
    justifyContent: 'space-between',
    [theme.breakpoints.down('md')]: {
      flexDirection: 'column-reverse',
      alignItems: 'flex-start',
    },
  },
  productImage: {
    marginRight: theme.spacing(1),
    height: 48,
    width: 48,
  },
  details: {
    padding: theme.spacing(3),
    display: 'flex',
    alignItems: 'center',
    flexWrap: 'wrap',
    justifyContent: 'space-between',
    [theme.breakpoints.down('md')]: {
      flexDirection: 'column',
      alignItems: 'flex-start',
    },
  },
}));

export default Subscription;

Listing 15-10Styling Components for the Subscription.tsx

更新帐户视图

在我们结束本章之前,我们需要再次更新 AccountView。

首先,让我们更新导入命名组件,如清单 15-11 所示。

import React, { useState, ChangeEvent } from 'react';
import {
  Box,
  Container,
  Divider,
  Tab,
  Tabs,
  makeStyles,
} from '@material-ui/core';

import Header from './Header';
import General from './General';
import Subscription from './Subscription';
import Notifications from './Notifications';
import Security from './Security';
import Page from 'app/components/page';

Listing 15-11Import Named Components of AccountView

这里有什么新鲜事?标签页来自物料界面;具体来说,我们将使用简单的选项卡。

Tabs:允许我们在同一层级的相关内容组之间进行组织和导航。

接下来,让我们构建 AccountView React 函数组件,如清单 15-12 所示。

const AccountView = () => {
  const classes = useStyles();

  /*initialize the useState to 'general' - we will use that */

  const [currentTab, setCurrentTab] = useState('general');

 /*handleTabsChange -for setting or updating the value of the current tab */

  const handleTabsChange = (event: ChangeEvent<{}>, value: string): void => {
    setCurrentTab(value);
  };

  return (
    <Page className={classes.root} title="Settings">
      <Container maxWidth="lg">
        <Header />
        <Box mt={3}>
          <Tabs

            {/*handleTabsChange - for the clicking and selection of tabs */}

            onChange={handleTabsChange}
            scrollButtons="auto"
            value={currentTab}
            variant="scrollable"
            textColor="secondary"
          >

           {/*we're going to iterate or loop on the tabs here */}

            {tabs.map(tab => (
              <Tab key={tab.value} label={tab.label} value={tab.value} />
            ))}
          </Tabs>
        </Box>
        <Divider />
        <Box mt={3}>

          {/*current tab by default is the General component.
           The rest is not displayed until clicked or selected */}
          {currentTab === 'general' && <General />}
          {currentTab === 'subscription' && <Subscription />}
          {currentTab === 'notifications' && <Notifications />}
          {currentTab === 'security' && <Security />}
        </Box>
      </Container>
    </Page>
  );
};

const useStyles = makeStyles(theme => ({
  root: {
    minHeight: '100%',
    paddingTop: theme.spacing(3),
    paddingBottom: theme.spacing(3),
  },
}));

/* an array of objects with value. to be used in the
tabs for navigating between components*/
const tabs = [
  { value: 'general', label: 'General' },
  { value: 'subscription', label: 'Subscription' },
  { value: 'notifications', label: 'Notifications' },
  { value: 'security', label: 'Security' },
];

export default AccountView;

Listing 15-12Updating the index.tsx of the AccountView

恢复精神

现在是刷新浏览器的时候了。

在侧边栏导航中,单击 Account,您会看到默认设置页面是 General 页面。

单击其他选项卡,如订阅、通知和安全性。

要了解在我们的应用中使用 Redux 的强大功能,以及我们如何轻松地访问或共享一个组件到另一个组件的状态,请尝试在设置➤常规中编辑一些内容。

比如编辑名字改成 Mok Kuh JR 保存。一旦保存,你会在侧边栏导航中看到即时更新。

img/506956_1_En_15_Fig1_HTML.jpg

图 15-1

更新设置的屏幕截图

这就是 Redux 的强大之处——让整个应用对任何变化都做出 React。

摘要

这就是这个三部分系列的全部内容。在最后一章中,我们完成了应用的 UI,希望我们能够完全理解 Redux 是如何工作的,以及如何在必要的时候在应用中使用它。希望您也加深了对 Formik 和 Yup 验证模式的了解和理解,以便在应用中构建表单。

此外,请记住,Redux 是好的,但您不需要在应用中的每个地方都使用它,因为它仍然会增加复杂性。对于简单的 CRUD 应用或者不需要在其他组件中重用状态,就不需要使用 Redux。

但是,如果您打算构建一个大规模的应用或企业级应用,我建议从一开始就在您的应用中设置 Redux。

设置可能只需要一两个小时,这样做的好处是,只要你需要,它就可以随时使用。然后,您可以从任何组件访问 reducer 中的状态。

在下一章,我们将看到如何让我们的 React 应用对移动设备友好。

十六、让应用对移动设备友好

本章将在 2021 年第一季度把我们的 React 应用更新到最新的当前版本(在撰写本文时),然后使我们的应用对移动设备友好。这将确保我们的应用也能在移动设备或平板电脑等小屏幕上运行良好。

手机友好型应用是响应式设计或适应性设计应用的另一个术语。简而言之,显示页面会根据设备屏幕的大小自动调整。

入门指南

让我们转到 package.json,为了使用我们正在使用的每个库可用的最新版本,我们需要使用一个名为 Version Lens 的 VS 代码扩展。我们在前一章中安装了它,但是如果您还没有安装,现在可以随意安装。

img/506956_1_En_16_Fig1_HTML.jpg

图 16-1

使用版本镜头

那么如何查看我们正在使用的每个库的最新版本呢?打开 package.json,点击右上角的 V 图标,如图 16-2 所示。

img/506956_1_En_16_Fig2_HTML.jpg

图 16-2

版本镜头库

安全更新包库

我们将对 React 和我们正在使用的库进行安全升级。要升级,只需点击向上箭头符号,如图 16-3 所示。

img/506956_1_En_16_Fig3_HTML.jpg

图 16-3

库的安全更新

date-io/moment 的最新版本是 2.10.8,是我们正在使用的 1.3.13 版本的主要版本更新。我们不打算更新这个,因为主要版本意味着有潜在的突破性变化。 1.3.13 是与我们这里使用的日历组件兼容的版本。

  • 升级到主要版本时,请小心不要破坏更改。小版本和补丁升级通常没问题。

使用 Version Lens 升级你的应用中的所有次要版本和补丁,或者如果你想确定,首先将你的版本与我们在撰写本文时拥有的版本进行比较。您可以在我的 GitHub 中查看 package.json:

https://github.com/webmasterdevlin/practical-enterprise-react/blob/master/chapter-13/package.json

我们更新了这个 app 中的次要版本和补丁,包括以下主要版本,如清单 16-1 所示。

//major versions that were updated

@types/react
@types/react-dom
concurrently
prettier
react
react-dom
react-test-renderer
sanitize.css
ts-node
typescript

Listing 16-1Updated Major Versions

好的,完成之后,我们需要删除 package-lock.jsonNode 模块。然后做

npm install
npm start:fullstack

如果您在运行 npm start:fullstack 后遇到问题或错误,请检查您的 npm 版本。在撰写本文时,我们使用的是 NPM 版本 6,因为与版本 7 存在兼容性问题。

说到版本,React 17 中一个比较值得注意的变化是,在创建组件时,不需要显式地从' react' 键入 import React。试着删除其中一个组件,看看它是否还能工作。不过,目前我不建议删除它,因为一些 React 开发人员可能不熟悉这一变化。我只是提一下,这样如果您看到带有显式编写的 import React 语句的组件,就不会感到困惑。

此外,我们在 VS Code 或 WebStorm 中使用的代码片段仍然自动包含 import React 语句。然而,我们需要升级到 React 17,为 React 中即将到来的功能做准备。

一旦你检查你的应用仍然工作,我们现在可以开始使我们的应用移动友好。

更新主页

让我们从主页组件开始,让它具有响应性。我们将需要来自 Material-UI 核心的样式组件以及我们创建的页面模板,如清单 16-2 所示。

清单 16-2 制作移动友好的主页

import React from 'react';

import { Box, Container, Typography, useMediaQuery } from '@material-ui/core';

import Page from 'app/components/page';

const Home = () => {

const mobileDevice = useMediaQuery('(max-width:650px)');

return (

<Page title="Home">

<Container>

<Box

height={mobileDevice ? '50vh' : '100vh'}

display={'flex'}

flexDirection={'column'}

justifyContent={'center'}

alignItems={'center'}

>

<Typography variant={mobileDevice ? 'h4' : 'h1'}>

Welcome to Online Shop img/506956_1_En_16_Figa_HTML.gif

</Typography>

</Box>

</Container>

</Page>

);

};

export default Home;

这里有什么新鲜事?

useMediaQuery:React 的一个 CSS 媒体查询钩子。它将检测浏览器是否很小,如手机应用或平板电脑浏览器。我们将最大宽度设置为 650 像素,如果低于这个值,我们就将其设置为移动设备。

我们在返回元素中有来自 Material-UI 的页面模板、容器和盒子。

t:我们将高度设置如下:如果是移动设备,那么将高度设置为 50 vh(视图高度)或浏览器大小的 50 %;不然身高 100 vh。

排版:如果检测到移动设备,大小为 H4;否则,将其设置为 h1。

我们在这里也使用了一个购物袋表情符号。要获得这种表情符号并直接将其复制粘贴到您的代码中,请访问这个网站 emojipedia.org 。搜索“购物袋”,并将该表情符号复制并粘贴到您的代码中。

img/506956_1_En_16_Fig4_HTML.jpg

图 16-4

Emojis 来自血友病. org

刷新浏览器并拖动窗口使其变小。如果你在 Windows 上使用 Mac 或 Android Studio,你也可以从模拟器中检查它。

img/506956_1_En_16_Fig5_HTML.jpg

图 16-5

使用 MediaQuery 的主页的手机屏幕截图

更新“关于”页面

接下来,让我们更新 About 页面组件。我们将在这里做几乎相同的事情,如清单 16-3 所示。

清单 16-3 制作移动友好的关于页面

import React from 'react';

import { Box, Container, Typography, useMediaQuery } from '@material-ui/core';

import Page from 'app/components/page';

const AboutPage = () => {

const mobileDevice = useMediaQuery('(max-width:650px)');

return (

<Page title="About">

<Container>

<Box

height={mobileDevice ? '50vh' : '100vh'}

display={'flex'}

flexDirection={'column'}

justifyContent={'center'}

alignItems={'center'}

>

<Typography variant={mobileDevice ? 'h4' : 'h1'}>

About us img/506956_1_En_16_Figb_HTML.gif

</Typography>

</Box>

</Container>

</Page>

);

};

export default AboutPage;

除了表情符号之外,它实际上与主页相同,如图 16-6 所示。

img/506956_1_En_16_Fig6_HTML.jpg

图 16-6

使用 MediaQuery 的“关于”页面的手机屏幕截图

更新未找到的页面

我们将更新的下一个页面是“未找到”页面。同样,除了使用的表情符号,我们也在做同样的事情,如清单 16-4 所示。

import React from 'react';
import { Box, Container, Typography, useMediaQuery } from '@material-ui/core';
import Page from 'app/components/page';

const NotFoundPage = () => {
  const mobileDevice = useMediaQuery('(max-width:650px)');

  return (
    <Page title="Not Found Page">
      <Container>
        <Box
          height={mobileDevice ? '50vh' : '100vh'}
          display={'flex'}
          flexDirection={'column'}
          justifyContent={'center'}
          alignItems={'center'}
        >
          <Typography variant={mobileDevice ? 'h4' : 'h1'}>
            404 Page Not Found ☹
          </Typography>
        </Box>
      </Container>
    </Page>
  );
};

export default NotFoundPage;

Listing 16-4Updating the NotFoundPage Using Media Query

要测试它,只需进入一个不存在的localhost页面,例如“localhost:3000/not-found”,如图 16-7 所示。

img/506956_1_En_16_Fig7_HTML.jpg

图 16-7

使未找到的页面移动友好

使导航条对移动设备友好

我们将需要使仪表板侧边栏导航移动友好。在图 16-8 中,你会注意到侧边栏导航占据了超过 50%的屏幕大小。

img/506956_1_En_16_Fig8_HTML.jpg

图 16-8

使用媒体查询前用户界面的手机截图

让我们转到导航栏. tsx,我们将合并 useMediaQuery,如清单 16-5 所示。

//add the useMediaQuery
import { AppBar, Toolbar, Button, useMediaQuery, colors, } from '@material-ui/core';

...
//add the Media Query hooks
const mobileDevice = useMediaQuery('(max-width:650px)')

//return elements. we will hide the logo if its not a mobile device
return (
    <div className={classes.root}>
      <AppBar position="static">
        <Toolbar>
          <Link className={`${classes.link} ${classes.title}`} to={'/'}>
            {!mobileDevice && 'LOGO'}
          </Link

Listing 16-5Importing useMediaQuery in dashboard-sidebar-navigation

但是我们还没有完成。如果你在你的模拟器或移动设备上看这个应用,只有微小的变化。仪表板导航仍然没有响应,如图 16-9 所示,该图显示了移动设备中侧边栏的当前状态。

img/506956_1_En_16_Fig9_HTML.jpg

图 16-9

使用媒体查询的 UI 的移动屏幕截图

有几种方法可以让导航条有 React。首先,在移动设备上移除整个侧边栏导航,并在右上角创建一个下拉菜单列表或汉堡菜单,如图 16-10 所示。

img/506956_1_En_16_Fig10_HTML.jpg

图 16-10

下拉菜单列表

另一种方法是缩小仪表板侧边栏导航。我们将做第二个选择。

使仪表板侧边栏导航对移动设备友好

进入 dashboard-sidebar-navigation . tsx,从 Material-UI Core 添加 useMediaQuery,如清单 16-6 所示。

import {
  Collapse,
  Divider,
  Drawer,
  List,
  ListItem,
  ListItemIcon,
  ListItemText,
  ListSubheader,
  Avatar,
  Box,
  Typography,
  useMediaQuery,
} from '@material-ui/core';

...
//add the Media Query hooks

const mobileDevice = useMediaQuery('(max-width:650px)');

//add the styling components in the useStyles component
// mobile style
    drawerClose: {
      transition: theme.transitions.create('width', {
        easing: theme.transitions.easing.sharp,
        duration: theme.transitions.duration.leavingScreen,
      }),
      overflowX: 'hidden',
      width: theme.spacing(7) + 1,
      [theme.breakpoints.up('sm')]: {
        width: theme.spacing(9) + 1,
      },
    },

Listing 16-6Adding the Media Query Hooks and Style Components in Dashboard Sidebar Navigation

接下来,我们将更新 className 抽屉和 classes paper。同样,导入命名组件 clsx,如清单 16-7 所示。

...
import clsx from 'clsx';
...
<Drawer
          className={clsx(classes.drawer, mobileDevice && classes.drawerClose)}
          variant="permanent"
          classes={{
            paper: clsx(
              classes.drawerPaper,
              mobileDevice && classes.drawerClose,
            ),
          }}
          anchor="left"

Listing 16-7Updating the Drawer Elements

然后,我们将更改配置文件。姓名。如果头像在手机屏幕上,我们不会显示。我们为移动或更小的屏幕创建了另一个抽屉容器,如清单 16-8 所示。

{profile.name && !mobileDevice && (
            <Box p={2}>...

          )}
          <Divider />

          {/*drawer container for the mobile screen */}

          {mobileDevice ? (
           <div className={classes.drawerContainer}>
              <List>
             <Link className={classes.link} to={`${url}`}>
                  <ListItem button>
                    <ListItemIcon>
                      <PieChartIcon />
                    </ListItemIcon>
                  </ListItem>
                </Link>
                <Divider />
                <ListItem button onClick={handleClick}>
                  <ListItemIcon>
                    <ShoppingCartIcon />
                  </ListItemIcon>
                  {open ? <ChevronUpIcon /> : <ChevronDownIcon />}
                </ListItem>
                <Divider />
                <Collapse in={open} timeout="auto" unmountOnExit>
                  <List component="div" disablePadding>
                    <Link className={classes.link} to={`${url}/list-products`}>
                      <ListItem button className={classes.nested}>
                        <ListItemIcon>
                          <ListIcon />
                        </ListItemIcon>
                      </ListItem>
                    </Link>

                    <Link className={classes.link} to={`${url}/create-product`}>
                      <ListItem button className={classes.nested}>
                        <ListItemIcon>
                          <FilePlusIcon />
                        </ListItemIcon>
                      </ListItem>
                    </Link>
                  </List>
                </Collapse>
                <Divider />
                <Link className={classes.link} to={`${url}/calendar`}>
                  <ListItem button>
                    <ListItemIcon>
                      <CalendarIcon />
                    </ListItemIcon>
                  </ListItem>
                </Link>
                <Divider />
                <Link className={classes.link} to={`${url}/account`}>
                  <ListItem button>
                    <ListItemIcon>
                      <UserIcon />
                    </ListItemIcon>
                  </ListItem>
                </Link>
                <Divider />
                <Link className={classes.link} to={`/pricing`}>
                  <ListItem button>
                    <ListItemIcon>
                      <DollarSignIcon />
                    </ListItemIcon>
                  </ListItem>
                </Link>
                <Divider />

                <a className={classes.link} href={'/'}>
                  <ListItem button onClick={handleLogout}>
                    <ListItemIcon>
                      <LogOutIcon />
                    </ListItemIcon>
                  </ListItem>
                </a>
              </List>
              <Divider />
          ) : (

       {/*drawer container for the web browser */}

     <div className={classes.drawerContainer}>
              <List>
                ...
              </List>
            </div>
          )}
        </Drawer>
      </div>
    </>
  );
};

Listing 16-8Updating the Avatar Elements

现在我们有两个抽屉容器,一个用于移动屏幕,另一个用于网络浏览器。

我们将返回元素包装在 if-else 语句中。如果不是移动设备,就展示头像。否则,不要表现出来。

使仪表板布局移动友好

之后,我们还需要使用仪表板布局的 index.tsx 中的媒体查询挂钩。我们还需要导入 clsx。

最后,在 useStyles 中添加另一个样式属性,如清单 16-9 所示。

//import the useMediaQuery

import { Grid, useMediaQuery} from '@material-ui/core';
import clsx from 'clsx';
...

//add the Media Query hooks

const mobileDevice = useMediaQuery('(max-width:650px)')

//update the className
<DashboardSidebarNavigation />{' '}
      <div className={classes.wrapper}>
        <div className={classes.contentContainer}>
          <div
            className={clsx(classes.content, mobileDevice && classes.leftSpace)}
          >
            {children}
          </div>
        </div>
      </div>

//add a new style element

leftSpace: {
    paddingLeft: '3rem',
  },

Listing 16-9Making the Dashboard Layout Mobile-Friendly

现在重新检查你的手机屏幕。现在应该是手机友好了,如图 16-11 所示。

img/506956_1_En_16_Fig11_HTML.jpg

图 16-11

移动友好仪表板

FOR YOUR ACTIVITY

移动屏幕现在是移动友好的。然而,仍然有改进的余地。为了你的活动

  1. 使用 useMediaQuery 挂钩,在移动设备上查看时,可以调整导航栏和仪表板页面内容之间的足够空间。

  2. Figure 16-12 is a screenshot of the Dashboard (L) and the Dashboard Calendar (R).

    img/506956_1_En_16_Fig12_HTML.jpg

    图 16-12

    仪表板(L)和仪表板日历(R)的屏幕截图

摘要

总而言之,我们在 2021 年第一季度将我们的 React 应用更新到了最新的当前版本(在撰写本文时)。然后,在媒体查询挂钩的帮助下,我们使我们的应用对移动设备友好。

在下一章,我们将讨论 React 组件的各种流行的样式方法。

十七、React 组件的样式方法

在前一章中,我们借助媒体查询钩子使我们的应用对移动设备友好。现在我们的应用几乎可以部署了。然而,在我们开始之前,我认为我们应该简单地关注一下 React 组件的其他样式方法。

在整个应用中,我们使用 Material-UI 库作为我们的样式首选项。但是还有其他几种流行的方法。我们不会深入探讨每一种造型方法,而是告诉你你的其他选择。

我们可以用多种方式来设计 React 组件的样式。对于我们中的许多人来说,选择哪一个取决于不同的因素,例如我们当前项目的架构或设计目标、特定的用例,当然还有个人偏好。

例如,在某些情况下,当您只需要在特定文件中添加一些样式属性时,内联样式可能是最好的选择。如果您发现自己在同一个文件中重用了一些样式属性,那么样式化组件是完美的。对于其他复杂的应用,您可以查看 CSS 模块,甚至是常规的 CSS。

内嵌样式

开发人员通常使用内联样式来构建组件原型或测试组件的 CSS 样式。这种样式也是一种强力处理元素的方式,可以查看我们编写和删除的任何 CSS 内联样式的结果。内联样式可能是我们可以使用的最直接的样式方法,尽管不推荐用于大规模应用。

需要记住的一点是,在 React 中,内联样式被指定为对象,而不是字符串。键值是 CSS 属性名,应该用 camelCase 编写,样式的值通常是字符串。

所以让我们试试。

在 components 文件夹中,我们创建一个新文件,并将其命名为InlineStyle.tsx.

import React from "react";

const heading = {
  color: "orange",
  fontSize: "50px",
};

function InlineStyle() {
  return (
    <div>
      {/* style attribute to equal to the object it is calling */}
      <h1 style={heading}> Inline Style</h1>
    </div>
  );
}

export default InlineStyle;

Listing 17-1InlineStyle.tsx

普通 CSS

这只是你标准的普通 CSS。简单易用。没有依赖性,并具有本机浏览器支持。然而,这通常不用于 React 项目,尤其是大型项目。

让我们用这种样式方法做一个按钮的例子。我们正在创建一个具有以下属性的按钮类。我们还为 hover 类添加了背景色。

创建一个文件并将其命名为 Plain.css,如清单 17-2 所示。

.button {
    align-items: center;
    display: inline-flex;
    justify-content: center;
    padding: 6px 16px;
    border-radius: 3px;
    font-weight: 50;
    background: rgb(43, 128, 77);
    color: rgb(241, 240, 240);
    border: 1px solid rgb(249, 200, 200);
    box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 2px;
    width: auto;
    margin-top: 500px;
    margin-bottom: 1px;
    cursor: pointer;;
}
.button:hover {
    background-color: #d4bd54;
}

Listing 17-2Plain CSS Styling

出于美观的目的,我添加了一些样式属性,但主要目的是创建 CSS 文件。我们有使用类的命名元素。这允许我们重用组件中的元素。

然后让我们使用这个按钮样式。我们只是导入了 CSS 文件,然后使用了按钮类。由于这是 React,我们需要使用 className,因为单词 class 是 JavaScript 中的保留关键字。

import React from "react";
import "./Plain.css";

const Button = () => {
  return (
     <>

      <Container>
        <button className="button"> Log in </button>
      </Container>
    </>

  );
};

Listing 17-3Plain CSS Styling in your React component

全球 CSS

本质上,它们和普通的 CSS 写的一样。主要区别在于,全局 CSS 非常适合使用共享布局组件,如标题组件、导航栏、仪表板和其他共享站点。

我们还可以在我们的根索引目录中创建全局 CSS,如下例所示。我们制作了一个 index.css 并将其导入到我们的根索引文件中。

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',  'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans',
  'Helvetica Neue', sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
    monospace;
}

Listing 17-4Global CSS in index.css

然后将其导入 index.tsx 以供全局使用。

.GlobalButton-root {
    background-color: #792b78;
    box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
    padding: 7px 14px;
}
.GlobalButton-root:hover {
    background-color: #d49254;
}
.GlobalButton-label {
    color: #fff;
}

Listing 17-5Global CSS in index.tsx

在这种情况下,我们在所有应用组件上应用 index.css 的样式。

CSS 模块

默认情况下,所有的类名都是本地范围的,或者只适用于特定的组件。这意味着每个 React 组件都有自己的 CSS 文件,该文件的范围局限于该文件和组件,从而防止名称冲突或特殊性问题。

对于依赖项,使用 css-loader。Create-react-app 支持现成的 CSS 模块。

.button {
  background-color: #406040;
  box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
  padding: 7px 14px;
}
.button:hover {
  background-color: #a08884b;
}

Listing 17-6Button.module.css

让我们使用 button 创建另一个样式表,并将其命名为another-stylesheet.css,,如清单 17-7 所示。

.button {
    color: #f4f466;
}

Listing 17-7another-stylesheet.css

我们可以在应用的不同文件中使用相同的 CSS 类名,而不用担心名称冲突。让我们看看它是如何工作的,如 ButtonModule.tsx 中的清单 17-8 所示。

import React from 'react';

// import css modules stylesheets as styles

import styles from './Button.module.css';
import "./another-stylesheet.css";

export default function ButtonModule() {
  return (
    <div>

      <button className={`${styles.button} button`}> Button Module</button>
    </div>
  );
}

Listing 17-8ButtonModule.tsx

我们使用模板文字或反勾号加美元符号来放置 modules.css 中的样式对象。

见图 17-1 。

img/506956_1_En_17_Fig1_HTML.jpg

图 17-1

按钮模块界面截图

CSS-in-JS

样式化组件允许我们样式化 React 组件和重新样式化现有组件。如果我们想改变对象的样式,我们就使用属性。

另一方面,React 中的 Emotion 同时支持字符串和对象样式。语法也更像 CSS。

样式组件库

样式化组件允许我们编写常规的 CSS,并在应用中传递函数和属性。我们可以对任何组件使用样式化组件,只要它接受类名属性。

样式化组件使用带标签的模板文字 CSS 代码写在两个反斜线之间——来样式化组件。

import styled from "styled-components";

//you can rename your classes however you want.

export const ButtonStyled = styled("button")`
  align-items: center;
  display: inline-flex;
  justify-content: center;
  padding: 6px 16px;
  border-radius: 3px;
  font-weight: 50;
  background: rgb(49, 85, 77);
  color: rgb(241, 240, 240);
  border: 1px solid rgb(249, 200, 200);
  box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 2px;
  width: auto;
  margin-top: 500px;
  margin-bottom: 1px;
  cursor: pointer;

  &:hover {
    background-color: #95503a;
  }
`;

export const Container = styled.div`
  display: flex;
  justify-content: center;
  align-items: center;
  height: 1px;
  color: #5a3667;
`;

Listing 17-9ButtonStyled Style Class

接下来,使用按钮样式类。组件和样式之间的映射被移除;这意味着您只需创建一个标准的 React 组件并将您的样式附加到它上面——您只需直接使用而不是使用带有类名的

我们还可以使用属性来设计我们的样式化组件,比如属性如何被传递给常规的 React 组件。

import React from "react";
import { ButtonStyled, Container } from "./styles";

const Button = () => {
  return (
     <>

      <Container>
        <ButtonStyled>Log in</ButtonStyled>
      </Container>
     </>

  );
};

export default Button;

Listing 17-10Button.tsx Using ButtonStyled Styling Class

React 中的情绪

我必须在这里强调“React 中的情感”,因为有两种使用情感的方法——一种是框架不可知的,另一种是 React。

这意味着安装也不同。有关这方面的更多信息,您可以查看这里的官方文档:

https://emotion.sh/docs/introduction

当然,我们将关注 React 方法。

但是在 React 方法中,也有两种主要的样式化元素的方法——使用 CSS prop 或者使用样式化组件。我们将只举后者的一个例子。

在 Emotion 中使用 styled-components,这个包是@emotion/styled.

对于 CSS 属性,在这里阅读更多的文档: https://emotion.sh/docs/css-prop .

import styled from "@emotion/styled";

export const ButtonEmotion = styled("button")`
  align-items: center;
  display: inline-flex;
  justify-content: center;
  padding: 6px 16px;
  border-radius: 3px;
  font-weight: 50;
  background: rgb(85, 49, 74);
  color: rgb(241, 240, 240);
  border: 1px solid rgb(249, 200, 200);
  box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 2px;
  width: auto;
  margin-top: 500px;
  margin-bottom: 1px;
  cursor: pointer;

  &:hover {
    background-color: #134f0e;
  }
`;

export const Container = styled.div`
  display: flex;
  justify-content: center;
  align-items: center;
  height: 1px;
  color: #5a3667;
`;

Listing 17-11Emotion Styling Component

styled-component 使用styled.div样式 API 来创建组件。

摘要

正如我们在本章开始时所说的,有不同的方式或方法来设计 React 组件。选择其中一个有很多因素。我们在这里只概述了其中的一些,从最基本的如内联样式到 CSS 模块和流行的样式库,包括样式组件和情感。

下一章本质上是我们的项目应用的高潮,因为我们使用两种不同的方式部署它:Netlify 和 Docker。

十八、在 Netlify 和 Docker 中部署 React

在前一章中,在媒体查询钩子的帮助下,使我们的应用对移动设备友好,现在我们准备部署我们的前端应用。

我们将以两种不同的方式部署我们的应用。

首先,我们将使用 Netlify 来构建、部署和托管我们的静态站点或应用。开发人员喜欢 Netlify,因为它的拖放界面允许从 GitHub 或 Bitbucket 进行持续集成和交付。在这种情况下,我们将使用 GitHub 部署到 Netlify。

我们的下一个部署策略是使用一种叫做 Docker 的流行容器技术。使用 Docker 的最大优势之一是将我们的应用打包在“容器”中。因此,我们的应用对于任何运行 Windows 操作系统或 Linux 操作系统的系统都是“可移植的”。

请记住,我们只是在使用一个假的本地服务器(使用运行在终端中的外部 CLI 工具来创建一个假的服务器),所以我们实际上并没有为后端服务编译后端代码。这意味着我们一直使用的本地服务器或本地主机将无法与 Netlify 或 Docker 一起工作。

但是,我们仍然可以看到应用的实况。我们的目标是学习如何部署我们的前端应用,而不是后端,特别是使用 Netlify 和 Docker。

现在,让我们转到 package.json,因为我们需要更新我们的构建脚本。编辑构建脚本,如清单 18-1 所示。

"scripts": {
    "start": "react-scripts start",
    "build": "CI= react-scripts build NODE_ENV=production",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "test:generators": "ts-node --project=./internals/ts-node.tsconfig.json ./internals/testing/test-generators.ts",
    "cypress:open": "cypress open",
    "start:prod": "npm run build && serve -s build",
    "checkTs": "tsc --noEmit",
    "eslint": "eslint --ext js,ts,tsx",

Listing 18-1Updating the Build Script in package.json

现在,在开始部署我们的应用之前,让我们开始设置我们需要的工具。第一个上来的是 GitHub。

开源代码库

在开始之前,我们先来谈谈 GitHub 以及它的用途。

要了解 GitHub 是怎么回事,你应该知道 Git,这是一个开源的版本控制系统。版本控制系统允许我们有效地存储文件,更有效地与其他开发人员协作,进行更改,并上传最新的修订版。

那么 GitHub 是什么呢?Git 是一个命令行工具,但 GitHub 中的“中枢”是 Git 所关注的所有东西汇集在一起的地方,也是开发人员存储他们的项目和其他文件的地方。要部署到 Netlify,我们需要满足一些要求。我们可以有几种方法,但是我们会选择最直接的途径来通过我们的 GitHub。

如果您还没有帐户,请访问此网站 www.github.com 创建您的帐户。

img/506956_1_En_18_Fig1_HTML.jpg

图 18-1

GitHub 网站

您可以将所有示例代码或示例项目保存在您的 GitHub 帐户中;不需要将它们保存在本地机器中。

转到您的项目应用,并将其保存在 GitHub 帐户中。

为此,您需要单击初始化存储库。

img/506956_1_En_18_Fig2_HTML.jpg

图 18-2

初始化存储库或发布到 GitHub

提交任何更改;如果有,保存,然后发布到 GitHub。选择选项私有存储库。

img/506956_1_En_18_Fig3_HTML.jpg

图 18-3

提交 GitHub 私有存储库

接下来,进入你的 GitHub 账户,检查你的项目是否已经保存在你的私有存储库中。确认后,前往 www.netlify.com 并创建一个账户,如果你还没有的话。

网易

Netlify 是一个 web 开发托管平台,允许开发人员构建、测试和部署网站。主要功能包括托管、无服务器功能、表单、分割测试、持续部署和其他附加功能。

img/506956_1_En_18_Fig4_HTML.jpg

图 18-4

Netlify 网站

您也可以使用 GitHub 帐户登录。

img/506956_1_En_18_Fig5_HTML.jpg

图 18-5

Netlify 的登录选项

创建帐户后,我们将从 Git 创建一个新站点。

img/506956_1_En_18_Fig6_HTML.jpg

图 18-6

添加新站点

点击“从 Git 新建网站”按钮后,您将看到以下内容。选择 GitHub 进行我们的持续部署。

img/506956_1_En_18_Fig7_HTML.jpg

图 18-7

选择 GitHub 进行持续部署

然后,您将看到“创建新站点”页面。

img/506956_1_En_18_Fig8_HTML.jpg

图 18-8

搜索您的回购

搜索您的存储库的名称。

一旦你找到它,只需点击它,你会被引导到以下。检查您是否有相同的设置,然后单击“部署站点”按钮。

img/506956_1_En_18_Fig9_HTML.jpg

图 18-9

部署网站页面

点击按钮后,这个过程需要几分钟,所以只要坐下来放松一会儿。您将看到消息“部署您的站点”,这是第一步。

img/506956_1_En_18_Fig10_HTML.jpg

图 18-10

第一步:部署站点

如果部署成功,您应该会看到消息“您的站点已部署”

img/506956_1_En_18_Fig11_HTML.jpg

图 18-11

站点已成功部署

成功部署后,您应该会看到以下内容。

img/506956_1_En_18_Fig12_HTML.jpg

图 18-12

Netlify 上的应用

现在应用已经部署好了,点击 Netlify 给你的免费域名。是的,它是免费的,但你不能自定义或更改它,虽然有付费选项,其中您可以自定义您的网址。

码头工人

首先,我们讨论一下 Docker 是什么。简单地说,它是一个工具,使开发人员能够通过容器创建、部署和运行应用。在容器中,我们可以打包我们的应用及其所有部分——从库到依赖项——然后在一个包中部署它。

接下来,我们来试试 Docker。

我们将首先从 Windows 上的 Docker 桌面开始。您将在这里看到系统要求,并检查您的机器是否兼容。

img/506956_1_En_18_Fig13_HTML.jpg

图 18-13

在 Windows 上安装 Docker 桌面。来源: https://docs.docker.com/docker-for-windows/install/

点击从 Docker Hub 下载按钮后,您将被重定向到以下页面。单击 Get Docker 按钮开始安装过程。

img/506956_1_En_18_Fig14_HTML.jpg

图 18-14

获取 Docker 按钮。来源: https://hub.docker.com/editions/community/docker-ce-desktop-windows

对于 Mac,安装过程基本相同。

在 Mac 上安装 docker 桌面

img/506956_1_En_18_Fig16_HTML.jpg

图 18-16

获取 Mac 上的坞站桌面

img/506956_1_En_18_Fig15_HTML.jpg

图 18-15

在 Mac 上安装 Docker 桌面。来源: https://docs.docker.com/docker-for-mac/install/

下面是如何在 Ubuntu 上下载 Docker 引擎。

img/506956_1_En_18_Fig17_HTML.jpg

图 18-17

在 Ubuntu 上安装 Docker 引擎。来源: https://docs.docker.com/engine/install/ubuntu/

Ubuntu 和另外两个,Mac 和 Windows 的唯一区别就是 Ubuntu 没有 Docker 客户端。Docker 客户端是 Docker 管理容器的 GUI 或 UI。

安装 Docker 后,图 18-18 显示了 Docker 客户端在 Windows 或 Mac 上的仪表板外观,如果你有容器运行的话。否则,您将看到一条指示板消息:没有容器在运行。

img/506956_1_En_18_Fig18_HTML.jpg

图 18-18

Docker 客户端仪表板

Docker 忽略

接下来,让我们看看源代码,因为我们需要添加 Docker ignore 文件。在根目录中,创建。dockerignore.

img/506956_1_En_18_Fig19_HTML.jpg

图 18-19

Docker 忽略文件

我们忽略或者不提交测试和 Node 模块的 cypress。

nginx 配置

之后,我们需要创建 NGINX 服务器配置。我们需要 NGINX 服务器在 Docker 内部运行,并为浏览器提供 React 应用。

但是 NGINX 是什么?发音像“engine-ex”,NGINX 是一个免费、开源、高性能的 HTTP 或 web 服务器,也可以充当反向代理、电子邮件代理和负载平衡器。

本质上,它通过异步和事件驱动的方法提供了低内存使用、负载平衡和高并发性,允许同时处理许多请求。

在根目录中,创建 Nginx.conf 并添加以下配置。

server {

  listen 80;

  location / {
    root   /usr/share/nginx/html;
    index  index.html index.htm;
    try_files $uri $uri/ /index.html;
  }

  error_page   500 502 503 504  /50x.html;

  location = /50x.html {
    root   /usr/share/nginx/html;
  }

}

Listing 18-2NGINX Config

Dockerfile

接下来,让我们添加 Dockerfile 文件。

# Stage 1
FROM node:15-alpine as build
WORKDIR /app
ENV PATH /app/node_modules/.bin:$PATH
COPY package.json ./
COPY package-lock.json ./
RUN npm install
COPY . ./
RUN npm run build

# Stage 2
FROM nginx:stable-alpine
COPY --from=build /app/build /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Listing 18-3Dockerfile

在清单 18-3 中,我们使用 Alpine 版本 15 构建 Docker 图像和其他小型的类似容器的应用。 WORKDIR (工作目录)类似于“cdying”或者转到 Docker 容器中的 cd 目录。

我们还指定了 ENV 路径。我们正在复制 package.json 和包-lock.son,并将它们转储到 Docker 中。这是在副本中完成的。

左侧(package.json)是您的存储库的一部分,而右侧是 Docker 的一部分。我们只是复制 package.json 和 package-lock.json,并将它们转储到根文件中。/码头工人的。

运行 npm 安装

这不是在您的目录中运行,而是在 Docker 容器的应用目录中运行。app 目录与我们转储 package.json 和 package-lock.json 文件副本的根目录相同。

收到。。/

请注意点和点斜线之间的空格。第一个点与应用的整个存储库有关,它被复制并粘贴到。/或应用目录。

如果你要问我为什么我们要复制整个应用,为什么我们需要在早期复制 package.json 和 package-lock.json?

这是因为我们需要 npm 先运行它,另一个原因与应用的优化有关。

Docker 为每个命令创建层,如复制、添加、运行等。按照目前的配置,Docker 不需要 npm 安装或重建所有层,包括 package.json。每当我们的源代码发生变化时,这样做将花费大量的时间和资源,这可能会经常发生。

只有在 package.json 中有更改时,才会执行 npm 安装,比如删除或添加某些内容。

同时,在阶段 2 中,注意副本中的三个参数,前两个参数被复制或转储到第三个或最后一个参数中。

坞站集线器

现在,让我们试试 Docker 部署。但在此之前,请登录您的 Docker Hub 帐户,这样您就可以上传或推送您的 Docker 图片,并将其发布到您的 Docker Hub。

img/506956_1_En_18_Fig20_HTML.jpg

图 18-20

dock hub sign up/log in page-对接集线器登入/登入页面。资料来源: https://hub.docker.com/

Docker 命令

现在,让我们开始 Docker 部署过程。以下是从 docker 登录开始的 Docker 命令的屏幕截图。

img/506956_1_En_18_Fig21_HTML.jpg

图 18-21

Docker 命令

确保您的 Docker 正在运行,然后编写 Docker 构建。下面是一个成功的 Docker 构建的截图(已完成)。根据您的计算机,这可能需要几分钟才能完成。

img/506956_1_En_18_Fig22_HTML.jpg

图 18-22

码头工人建造

一旦你成功地构建了它并把它推送到 Docker Hub 的仓库,我认为最好先在我们的本地机器上运行它。运行 Docker 命令:

$ docker run -p 8080:80 yourDockerUserName/react-docker:1.0.0

以下是 Docker 在我机器上运行的截图。你的用户名会不同。

img/506956_1_En_18_Fig23_HTML.jpg

图 18-23

码头运行

检查 Docker 客户端,看看它是否在您指定的端口 8080 上运行。hungry_raman 这个名字是由两个单词随机生成的,中间用下划线连接。简而言之,它是容器的 UUID 或通用唯一标识符。

img/506956_1_En_18_Fig24_HTML.jpg

图 18-24

Docker 客户端仪表板中的容器

转到 localhost:8080 来检查您的应用。

img/506956_1_En_18_Fig25_HTML.jpg

图 18-25

在端口 8080 上运行

好了,现在我们已经看到一切都在工作,我们可以把它推到我们的 Docker Hub:

-$ docker push yourDockerUsername/react-docker:1.0.0

img/506956_1_En_18_Fig26_HTML.jpg

图 18-26

码头推送

之后,打开你自己的 Docker Hub,这样你就可以看到你的应用了。

img/506956_1_En_18_Fig27_HTML.jpg

图 18-27

成功部署后的 Docker Hub

你可以在任何地方下载或部署它,例如,在 Kubernetes、Azure 或 AWS 中使用它们的服务或容器实例。

如果你想了解更多关于 Kubernetes 的信息,可以去他们的网站: https://kubernetes.io/

如果你想了解更多关于 Azure 的信息,可以去他们的网站: https://azure.microsoft.com/

如果你想了解更多关于 AWS 的信息,可以去他们的网站: https://aws.amazon.com/

摘要

这就是我们的应用在 Netlify 和 Docker 中的部署过程。Netlify 非常适合静态网站,我们已经看到了将 Netlify 与我们的 GitHub 存储库连接以获取源代码并允许我们进行持续集成和部署是多么容易。

另一方面,如果我们需要在任何 Linux 机器上运行我们的应用,那么将我们的应用部署到 Docker 容器中就是一种方法。还有,记住 Docker 是开源的,所以任何人都可以贡献和扩展它来满足他们的需求,比如添加功能等等。

下一章只是额外的一章,在这一章中,我们将了解如何重用我们的 React 知识,以及如何将我们在构建项目应用时学到的概念和技能扩展到其他相关的平台和框架。