学习 React 17 系统精讲 结合TS打造旅游电商平台

React 17 系统精讲 结合TS打造旅游电商平台

勤学如春起之苗,不见其增,日有所长;
辍学如磨刀之石,不见其损,日有所亏。

每一节课都是单独提交到码云 想看具体某一节的代码内容 可以在码云上找到对应的提交记录 查看代码改动

码云仓库地址: https://gitee.com/WebJianHong/react-travel

2-2 【环境搭建】开始我们的第一个React项目

脚手架create-react-app
node版本: 14.15.0 LTS

npx create-react-app my-app

2-3 【项目启动】使用create-react-app快速搭建React

2-5 【TypeScript配置】tsconfig.json详解

TS的痛点

  • 学习成本非常高 学习曲线非常陡

使用create-react-app搭建TS的react项目

npx create-react-app my-app-ts --template typescript

TypeScript

  • TypeScript是JS的超集
  • 给原生JS添加静态类型检查
  • 与ES6一样目前还无法被主流浏览器直接读取

TypeScipt的编译

  • 编译器 babel-loader(create-react-app项目自带)
  • 配置文件: tsconfig.json

tsconfig.json

{
    
    
  "compilerOptions": {
    
    
    "noImplicitAny": false, // 不需要显示地声明变量的类型any
    "target": "es5", // 编译后目标JS版本
    "lib": [
      "dom", // document.getElementById("root")
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true, // 允许混合编译JS文件
    "skipLibCheck": true,
    "esModuleInterop": true, // 允许我们使用commonjs的方式import默认文件 import React from 'react'
    // "esModuleInterop": false, import * as React from 'react'
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext", // 配置的是我们代码的模块系统 常见的有Node.js的CommonJS ES6标准的esnext requestJs的AMD
    "moduleResolution": "node", // 决定了我们编译器的工作方式
    "resolveJsonModule": true,
    "isolatedModules": true, // 编译器会将每个文件作为单独的模块来使用
    "noEmit": true, // 表示当编译发生错误的时候 编译器不要生成ES5或者ES6 JS代码
    "jsx": "react-jsx" // 允许编译器编译react代码
  },
  "include": [
    "src"
  ]
}

2-6 【延伸阅读】tsconfig.json编译器配置文档

【延伸阅读】tsconfig.json编译器配置文档

一般来说,项目的TS编译器配置全部存储在项目根目录下的tsconfig.json文件中。

编译器启动时,首先会读取tsconfig.json,以获取有关如何编译项目的说明(例如,要编译哪些源文件,在哪里存储输出等)。

不过,如果编译器配置文档保存在其他特殊位置,我们也可以通过使用-p选项告诉编译器配置文档的具体位置:
rn8VAJ.png
现在,我们来研究一下上图出现的比较常见的编译器/tsconfig选项:

  1. “target”: “es5”

这个属性定义了你编译后的目标javascript版本

一般来说,我们需要让他编译为es5,这样就可以被主流浏览器解读了。

当然,你说我的react代码不是给浏览器看的,比如说,你使用的是react-native做作手机app,那么这里的选项可以选择es6

除了es5和es6,我们还有很多其他常见的选项,ES5, ES6/ES2015, ES2016, ES2017, ES2018, ES2019, ES2020, ESNext,等等等等

2. “lib”: [“dom”, “dom.iterable”, “esnext”]

这个属性列出了编译期间需要被包括进来的库文件,通过这些库文件,告诉typescript编译器可以使用哪些功能。

比如说,我们这里有一个dom的库文件,这个文件会告诉编译器dom api的接口,所以当我们在ts代码中使用dom的时候,比如说执行“document.getElementById("root")”这句话的时候,编译器就会知道该如何进行检查。

如果我们不设置这个选项,那么编译器也有自己默认的库文件列表,一般来说是["dom", "es6","DOM.Iterable"]等等

  1. “allowJs”: true

允许混合编译JavaScript文件

  1. “esModuleInterop”: true

这个选项允许我们使用ES6的方式import默认文件。比如说,在没有开启这个选项时,我们需要这样写才能引用react:

import * as React from 'react'

但是当我们开启了这个选项以后,import方式就与普通的JavaScript没有区别了,可以写为:

import React from 'react'

这样处理项目引入会更自然。

  1. “module”: “esnext”

这里配置的是我们代码的模块系统,比较常见的有Node.js的CommonJS系统ES6标准的esnext系统,以及requirejs的AMD系统。我们这里使用的是ES6标准的esnext系统,不过如果把这里换成CommonJS也是可以的。

  1. “moduleResolution”: “node”

这个选项决定了我们编译器的工作方式,也决定了我们各个文件之间调用import的工作流程。这里曾经有两个选项,“node” and “classic”,但是"classic"这个选项在2019年12月就已经废弃了。

  1. “isolatedModules”: “node”

开启这个选项以后,编译器会将每个文件作为单独的模块来使用。

  1. “noEmit”: true,
    开启这个选项表示当发生错误的时候,编译器不会生成 JavaScript 代码。

  2. “jsx”: “react”

显而易见,这个选项允许编译器支持编译react代码

  1. “include”: [“src/**/*”]

使用此选项列出我们需要编译的文件, “文件路径”选项需要文件的相对或绝对路径,例如:

  • “**” - 任意子目录
  • “*” - 任意文件名
    = “?” - 只要字符跟随“?”,这个字符就会被视为可忽略字符 (e.g., "src/*.tsx?"则同时指代"src/*.tsx"与"src/*.ts")
  1. “files”: [“./file1.ts”, “./file2.d.ts”, …]

使用此选项列出编译器应始终包含在编译中的文件。无论是否使用“exclude”选项,都将会编译使用此选项内包括的所有文件。

  1. “exclude”: [“node_modules”, “**//.test.ts”]

此选项将会列出从编译中排除的文件。它与“include”选项采取相同的模式,我们以使用此选项来过滤使用“include”选项指定的文件。 但是, “exclude”选项不会影响“files”选项。

通常,我们会排除node_modules测试文件、和编译输出目录

如果省略此选项,编译器将使用“outDir”选项指定的文件夹。

如果没有同时指定“files”和“include”这两个选项,则编译器将编译根目录和任何子目录中的所有TS文件,但不包括使用“exclude”选项指定的文件。

2-7 【TypeScript配置】深挖TS编译流程

视频讲了如何将js项目 改为 ts项目

3-1 带着问题来学习

3-2 章节总览

Typescipt + React

3-3 【概念理解】React的前世今生

React特点

单向数据流

  • 数据与界面绑定
  • 单向渲染
  • 就好像一个函数 同样的输入 同样的输出

虚拟DOM

组件化

3-4 【组件化】初识React函数式组件

新建一个项目

npx create-react-app robot-gallert --template typescript

tsconfig.json文件里

{
    
    
  "compilerOptions": {
    
    
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx"
  },
  "include": [
    "src"
  ]
}

有下面两个属性 在tsx文件里 import JSON文件进去 才能正常 不然会报错

"moduleResolution": "node",
"resolveJsonModule": true,

3-5 【概念理解】JSX 编程思维

React认为视图的本质就是渲染逻辑与UI视图表现的内在统一

React把HTML与渲染逻辑进行了耦合 形成了JSX

使用TSX文件后缀

  • 文件扩展名 .tsx
  • 在配置中 tsconfig.json启用jsx选项
{
    
    
    "compilerOptions": {
    
    
        "jsx": "react-jsx"
    }
}

3-6 【组件化】配置React的CSS模组

文件位置
CSS文件与component文件放在同一个目录下

命名规范
.module.css

react项目引入css 两种方式

  1. 直接引入整个css文件 (容易有全局样式污染)
import './index.css'
<div className="app" />
  1. JSS模块化引入组件
import style from './index.css'
<div className={
    
    style.app} />

需要另外配置

TS的定义声明

  • *.d.ts (.d.ts文件会自动被ts识别)
  • 只包含类型声明 不包含逻辑
  • 不会被编译 也不会被webpack打包

src路径下新建custom.d.ts文件

src/custom.d.ts

declare module "*.css" {
    
    
  const css: {
    
    
    [key: string]: string
  };
  export default css;
}

有了上面的文件 才可以正常使用

import style from './index.css'

这种方式是js动态注入样式

CSS in JS(JSS)

插件 typescript-plugin-css-modules

解析css文件 生成ts所对应的引用类型

tsconfig.json 配置

{
    
    
    "compilerOptions": {
    
    
        "plugins": [
              {
    
    
                "name": "typescript-plugin-css-modules"
              }
    ]
    }
}

vscode配置(引入ts sdk)

.vscode/settings.json

{
    
    
  "typescript.tsdk": "node_modules/typescript/lib",
  "typescript.enablePromptUseWorkspaceTsdk": true
}

配置完成后 访问styles后会出现智能提示 如styles.app

css文件名后缀规范.module.css

// App.tsx
import React from 'react';
// import './App.css';
import robots from './mockdata/robots.json'
import Robot from './components/Robots';
import styles from './App.module.css'
console.log('styles:', styles);

function App() {
    
    
  return (
    <div className={
    
    styles.app}>
      <div className={
    
    styles.robotList}>
      {
    
    
        robots.map(r => (
          <Robot id={
    
    r.id} name={
    
    r.name} key={
    
    r.id}  email={
    
    r.email}></Robot>
        ))
      }
    </div>
    </div>
    
  );
}
export default App;

styles类似于下面的对象

styles= {
    
    
    App: "App_App__2UtqW",
    App-header: "App_App-header__127PR",
    App-link: "App_App-link__1y4cp",
    App-logo: "App_App-logo__2zsEF",
    App-logo-spin: "App_App-logo-spin__2uRb4",
    app: "App_app__12zEe",
    robotList: "App_robotList__3DmDk"
}

3-7 【延伸阅读】CSS in JS (JSS)

JSS 是什么

简单来说,一句话概括CSS in JS (JSS),就是"行内样式"(inline style)和"行内脚本"(inline script)。

因为,自从React出现以后,基于组件化的要求,强制把HTML、CSS、JavaScript捆绑在一起,在同一个文件里面,封装了结构、样式、以及逻辑。

这虽然违背html发明初期的"关注点分离"的原则,但更有利于组件之间的隔离。而每个组件包含了所有需要用到的代码,不必依赖外部环境,组件之间没有耦合。所以,随着 React 的走红和组件模式深入人心,“关注点分离”原则越发淡出人们的视野,而React所带来的"关注点混合"的原则逐渐成为主流。[1]

React 对 CSS 封装非常简单,就是沿用了 DOM 的 style 属性对象。CSS-in-JS是一种技术(technique),而不是一个具体的库实现(library)。简单来说CSS-in-JS就是将应用的CSS样式写在JavaScript文件里面,而不是独立为一些.css,.scss或者less之类的文件,这样你就可以在CSS中使用一些属于JS的诸如模块声明,变量定义,函数调用和条件判断等语言特性来提供灵活的可扩展的样式定义。

值得一提的是,虽然CSS-in-JS不是一种很新的技术,可是它在国内普及度好像并不是很高,它当初的出现是因为一些component-based的Web框架(例如React,Vue和Angular)的逐渐流行,使得开发者也想将组件的CSS样式也一块封装到组件中去以解决原生CSS写法的一系列问题。还有就是CSS-in-JS在React社区的热度是最高的,这是因为React本身不会管用户怎么去为组件定义样式的问题,而Vue和Angular都有属于框架自己的一套定义样式的方案。[2]

css in JS的好与坏

JSS 的好处

    1. 局部样式 - Scoping Styles
    1. 避免无用的CSS样式堆积
  • 3 Critical CSS
    1. 基于状态的样式定义
    1. 封装得更好的组件库

JSS 的坏处

    1. 陡峭的学习曲线
    1. 运行时消耗
    1. 代码可读性差
    1. 没有统一的业界标准

3-8 【资源配置】加载媒体与字体文件

脚手架自动帮忙声明了不同类型的文件

node_modules\react-scripts\lib\react-app.d.ts

/// <reference types="node" />
/// <reference types="react" />
/// <reference types="react-dom" />

declare namespace NodeJS {
    
    
  interface ProcessEnv {
    
    
    readonly NODE_ENV: 'development' | 'production' | 'test';
    readonly PUBLIC_URL: string;
  }
}

declare module '*.avif' {
    
    
  const src: string;
  export default src;
}

declare module '*.bmp' {
    
    
  const src: string;
  export default src;
}

declare module '*.gif' {
    
    
  const src: string;
  export default src;
}

declare module '*.jpg' {
    
    
  const src: string;
  export default src;
}

declare module '*.jpeg' {
    
    
  const src: string;
  export default src;
}

declare module '*.png' {
    
    
  const src: string;
  export default src;
}

declare module '*.webp' {
    
    
    const src: string;
    export default src;
}

declare module '*.svg' {
    
    
  import * as React from 'react';

  export const ReactComponent: React.FunctionComponent<React.SVGProps<
    SVGSVGElement
  > & {
    
     title?: string }>;

  const src: string;
  export default src;
}

declare module '*.module.css' {
    
    
  const classes: {
    
     readonly [key: string]: string };
  export default classes;
}

declare module '*.module.scss' {
    
    
  const classes: {
    
     readonly [key: string]: string };
  export default classes;
}

declare module '*.module.sass' {
    
    
  const classes: {
    
     readonly [key: string]: string };
  export default classes;
}

3-10 【延伸阅读】React的行内样式与CSS

如何为组件添加 CSS 的 class?

render() {
    
    
  return <span className="menu navigation-menu">Menu</span>
}

CSS 的 class 依赖组件的 props 或 state 的情况很常见:

render() {
    
    
  let className = 'menu';
  if (this.props.isActive) {
    
    
    className += ' menu-active';
  }
  return <span className={
    
    className}>Menu</span>
}

React 会自动添加 ”px” 后缀到内联样式为数字的属性后。如需使用 ”px” 以外的单位,请将此值设为数字与所需单位组成的字符串。例如:

// Result style: '10px'
<div style={
    
    {
    
     height: 10 }}>
  Hello World!
</div>

// Result style: '10%'
<div style={
    
    {
    
     height: '10%' }}>
  Hello World!
</div>

但并非所有样式属性都转换为像素字符串。有些样式属性是没有单位的(例如 zoom,order,flex)。

从性能角度来说,CSS 的 class 通常比行内样式更好。

3-11 【概念理解】State vs Props

state的更新是异步的

  • 调用setState后 state不会立刻改变 是异步操作
  • 不要依赖当前的state 计算下个state

props是只读的

3-12 【事件驱动】React Event 事件处理

事件对象

  • target 描述的是事件发生的元素
  • currentTarget 描述的是 事件处理绑定的元素

3-13 【异步处理】获取网络API数据

可在下面地址获取接口数据
https://jsonplaceholder.typicode.com/users

interface Props  {
    
    }
interface State  {
    
    
  robotGallery: any[]
}

class App extends React.Component<Props, State> {
    
    
  constructor(props) {
    
    
    super(props)
    this.state = {
    
    
      robotGallery: []
    }
  }

  componentDidMount() {
    
    
    fetch("https://jsonplaceholder.typicode.com/users")
    .then((res) => res.json()).then((data) => {
    
    
      console.log('data:', data);
      this.setState({
    
    
        robotGallery: data
      })
      
    })
  }

    render(): React.ReactNode {
    
    
      return (
        <div className={
    
    styles.app}>
          <div>
            <img src={
    
    logo} className={
    
    styles.appLogo} alt=""  />
            <h1>这里是标题这里是标题这里是标题</h1>
          </div>
          
          <div className={
    
    styles.robotList}>
          {
    
    
            this.state.robotGallery.map(r => (
              <Robot id={
    
    r.id} name={
    
    r.name} key={
    
    r.id}  email={
    
    r.email}></Robot>
            ))
          }
        </div>
        </div>
        
      );
    }

}

export default App;

3-14 【异步处理】setState的异步开发

异步更新 同步执行

setState()本身并非异步 但对state的处理机制给人一种异步的假象 state处理一般发生在声明周期变化的时候

this.setState({
    
    count: this.state.count + 1})
// 此时this.state.count的值还是更新之前的值
console.log('this.state.count:', this.state.count);
<button onClick={
    
    ()=> {
    
    
    this.setState((prevState, prevProps)=> {
    
    
      console.log('prevProps:', prevProps)
      console.log('prevState:', prevState)
      return {
    
    count: prevState.count + 1}
    })
    }}按钮
</button>
<span>{
    
    this.state.count}</span>
 <button onClick={
    
    ()=> {
    
    
    this.setState({
    
    count: this.state.count + 1}, ()=> {
    
    
      // 更新state后的回调函数
      console.log('this.state.count:', this.state.count);
    })
    
  }}>按钮</button>
  <span>{
    
    this.state.count}</span>

3-15 【死与新生】探索React组件的生命周期

  • Mounting: 创建虚拟DOM 渲染UI
  • Updating: 更新虚拟DOM 重新渲染UI
  • Unmounting: 删除虚拟DOM 移除UI

初始化=>构建函数=>getDerivedStateFromProps=>render():渲染UI=>componentDidMount

getDerivedStateFromProps=>shouldComponentUpdate=>render:渲染UI=>更新=>componentDidUpdate

componentWillUnmount => 销毁

3-16 【概念理解】React 17 版本变化

  • 事件委托机制改变(旧版本在document上添加 新的在虚拟DOM上操作)
  • 向原生浏览器靠拢(onScroll onFocus …)
  • 删除事件池
  • useEffect清理操作改为异步操作
  • JSX不可返回undefined
  • 删除部分私有API(主要在React Native里使用)

4-1 带着问题来学习

4-2 【概念理解】 什么是钩子(hooks)

一类特殊的函数 为你的函数式组件注入特殊的功能

有些类组件冗长而且复杂 难以复用

Hooks的目的就是为了给函数式组件加上状态

4-3 【状态钩子】使用useState管理组件state

4-4 【概念理解】副作用 side effect

纯函数

给一个函数传入同样的参数 那么这个函数永远返回同样的数据

给react组件输入相同的参数(props) 渲染UI应该永远一样

副作用与纯函数相反 指一个函数处理了与返回值无关的事情

4-5 【副作用钩子】使用useEffect异步获取数据

4-6 【副作用钩子】useEffect 使用指南

在useEffect中使用async await

  useEffect(()=> {
    const fetchData = async () => {
      const res = await fetch("https://jsonplaceholder.typicode.com/users")
      console.log({res});
      const data = await res.json()
      console.log({data});
    }
    fetchData()
  })

4-7 【全局数据传递】Context 与 useContext

4-8 【全局数据传递】组件化Context Provider

4-9 【高阶组件HOC】withAddToCart()

HOC

const hoc = higherOrder(wrappedComponent)
  • 高级组件(HOC)就是一个返回了组件的函数
  • 通过组件嵌套的方法给子组件添加更多的功能
  • 接收一个组件作为参数并返回一个经过改造的新组建

命名规范
withAddToCard with开头

4-10 【自定义Hook】useAddToCart()

  • Hook是函数
  • 命名以"use"开头
  • 内部可调用其他Hook函数
  • 并非React的特性

5-2 【项目规划】网站开发设计指南

5-3 【项目启动】系统设计与项目初始化

使用脚手架创建项目

npx create-react-app react-travel --template typescript

复用测试项目的tsconfig.json vscode配置和custom.d.ts

tsconfig.json

{
  "compilerOptions": {
    "noImplicitAny": false,
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",
    "plugins": [
      {
        "name": "typescript-plugin-css-modules"
      }
    ]
  },
  "include": [
    "src"
, "custom.d.ts"  ]
}

custom.d.ts

declare module "*.css" {
  const css: {
    [key: string]: string
  };
  export default css;
}

setting.json

{
  "typescript.tsdk": "node_modules/typescript/lib",
  "typescript.enablePromptUseWorkspaceTsdk": true
}

安装一个css依赖 只在开发环境使用

 npm install typescript-plugin-css-modules --save-dev

将src/App.css 按规范 改为 src/App.module.css

模块化引用css

src\App.tsx

import style from './App.modulce.css';

function App() {
    
    
  return (
    <div className={
    
    style.App}>
      <header className={
    
    style["App-header"]}>
        <img src={
    
    logo} className={
    
    style["App-logo"]} alt="logo" />
        <p>
          Edit <code>src/App.tsx</code> and save to reload.
        </p>
        <a
          className={
    
    style["App-link"]}
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

5-4 【主页开发】Header、Footer

引入 ant Design UI框架

yarn add antd @ant-design/icons
//
cnpm install antd @ant-design/icons --save

5-5 【项目重构】组件化思想实践

5-6 【主页开发】走马灯与侧边栏多重菜单

5-7 【主页开发】热门产品推荐

5-9 【讨论题】软件开发生命周期

正确的项目启动思路永远都是从业务入手,总结业务需求、建立业务模型,通过业务模型确定网站的业务流程,最后业务流程出发,才能确定页面关系与数量,而业务分析的最后一步才是ui的布局。通过本章的学习,我们来讨论几个问题:

  • 一个新网站,需要做的第一件事是什么
  • 请同学们就课程项目分析一下业务需求?
  • 请同学们补充课程项目的BRD与SRS。

6-1 带着问题来学习

6-2 【概念理解】路由与SPA

SPA(单页网站应用)

  • JS CSS HTML打包为一个超级大的文件 一次性丢给浏览器
  • JS挟持浏览器路由 生成虚拟路由来动态渲染页面DOM元素
  • 符合前后端分离的趋势 服务器不负责UI输出 而专注于数据支持
  • 同时支持桌面App 手机App 网址App

React网站使用的路由系统都是虚拟的

  • 与后端服务器没有版毛球关系 与实际文件也没有一一对应的关系
  • 主页 http://localhost:3000
  • 搜索页面 http://localhost:3000/search
  • 旅游路线详情页面 localhost:3000/touristRoutes/{id}

React路由框架

  • 综合性路由框架 react-router
  • 浏览器路由框架 react-keeper
  • 手机app框架(react-native): react-navigation

本项目使用react-router
最主流 完整的React路由解决方案

6-3 【路由初始化】配置react-router

安装
版本5

cnpm install react-router-dom --save

ts支持

ts类型定义只会在开发过程中使用 编译上线不会用到

cnpm install @types/react-router-dom --save-dev

react-router

  • react-router-dom 用于浏览器 处理Web App的路由
  • react-router-native 用于React Native 处理手机app的路由
  • react-router-redux 提供了路由中间件 处理redux的集成
  • react-router-config 用来静态配置路由

安装react-router-dom后

  • 会自动安装react-router核心框架
  • 组件可以渲染出标签
    • 组件利用H5 Api实现路由切换
    • 组件则利用原生JS中的window.location.hash来实现路由切换

课程使用"react-router-dom": “^5.2.0”, 和最新版本"react-router-dom": “^6.0.2”,
语法差别有点大

这里还是按照课程的版本来安装 这样学习起来顺畅些

“react-router-dom”: “^5.2.0”,

import React from 'react'
import styles from './App.module.css'
import {BrowserRouter, Route} from 'react-router-dom'
import {HomePage} from './pages'

function App() {
  return (
    <div className={styles.App}>
      <BrowserRouter>
        <Route path="/" component={HomePage}/>
      </BrowserRouter>
    </div>
  )
}

export default App

“react-router-dom”: “^6.0.2”

可参考教程
https://github.com/remix-run/react-router/blob/main/docs/getting-started/tutorial.md

https://github.com/remix-run/react-router/blob/main/docs/getting-started/installation.md

import React from 'react'
import styles from './App.module.css'
import {BrowserRouter,Routes, Route} from 'react-router-dom'
import {HomePage} from './pages'



function App() {
  return (
    <div className={styles.App}>
      <BrowserRouter>
        <Routes>
        <Route path="/" element={<HomePage />}/>
        </Routes>
      </BrowserRouter>
    </div>
  )
}

export default App

6-4 【路由架构】基础路由系统

react-router官网

网站路由系统的要求

  • 路由导航与原生浏览器操作行为一致
  • 路由的路径解析原理与原生浏览器一致,可以自动识别url路径
  • 路径的切换以页面为单位 不要页面堆叠

6-5 【路由搭建】页面导航

路由参数

6-6 【路由搭建】withRouter 与 useRouter

不同路由间参数传递

跨组件的数据传递

  • 使用上下文关系对象context实现
  • HOC高阶组件
  • 函数式组件, 使用hooks钩子

第7章 【Redux 入门】实战项目架构设计

7-2 【概念理解】什么是redux?

React实际上只是Ui框架

  • 通过JSX生成动态dom渲染UI
  • 没有架构 没有模板 没有设计模式 没有路由 没有数据管理

设计模式

  • MVC MVVM MV*
  • 针对React项目 有Redux
  • Angular Observable(RxJS)
  • Vue: Vuex

Redux

  • 剥离组件数据(state)
  • 数据统一存放在store中
  • 组件订阅store获得数据
  • store同步推送数据更新

Redux统一保存数据 在隔离了数据与UI的同时 负责处理数据的绑定

什么时候需要使用Redux

  • 组件需要共享数据的时候
  • 某个状态需要在任何地方都可以被随时访问的时候
  • 某个组件需要改变另一个组件状态的时候
  • 语言切换 黑暗模式切换 用户登录全局数据共享

Redux工作流

  • React组件(UI)
  • Actions
  • Store
  • Reducer
    在这里插入图片描述

7-3 【创建state】createStore

cnpm install redux --save

7-4 【访问state】获取store数据

src/redux/store.ts

import {createStore} from 'redux'
import languageReducer from './languageReducer'

const store = createStore(languageReducer)

export default store;

src/redux/languageReducer.ts

export interface LanguageState {
  language: "en" | "zh",
  languageList: {
    name: string,
    code: string
  }[];
}

const defaultState:LanguageState = {
  language: 'zh',
  languageList: [
    {
      name: '中文',code: 'zh'
    },
    {
      name: 'English', code: 'en'
    }
  ]
}

export default (state=defaultState, action) => {
  return state
}

class组件

src\components\header\Header.class.tsx

import store from '../../redux/store'
import {
    
    LanguageState}  from '../../redux/languageReducer'

interface State extends LanguageState {
    
    }

class HeaderComponent extends React.Component<RouteComponentProps, LanguageState> {
    
    

  constructor(props) {
    
    
    super(props)
    const storeState = store.getState()
    this.state = {
    
    
      language: storeState.language,
      languageList: storeState.languageList
    }
  }

7-5 【更新state】Action与Reducer处理

7-6 【订阅state】store的连接与订阅

class 组件

import store from '../../redux/store'
import { LanguageState } from '../../redux/languageReducer'

interface State extends LanguageState { }

class HeaderComponent extends React.Component<RouteComponentProps, LanguageState> {
  constructor(props) {
    super(props)
    const storeState = store.getState()
    this.state = {
      language: storeState.language,
      languageList: storeState.languageList
    }
    store.subscribe(this.handleSubscribe)
  }

  handleSubscribe = ()=>  {
    const storeState = store.getState()
    this.setState(
      { 
        language: storeState.language, 
        languageList: storeState.languageList 
      }
    )
  }

  menuClick = (e)=> {
    if (e.key === "add_new") {
      const action = {
        type: 'add_language',
        payload: {
          name: '新语言', code: 'new'
        },
      }
      store.dispatch(action)
    } else {
      const action = {
        type: 'change_language',
        payload: e.key
      }
      store.dispatch(action)
    }

  }

src\redux\languageReducer.ts

export interface LanguageState {
  language: "en" | "zh",
  languageList: {
    name: string,
    code: string
  }[];
}

const defaultState:LanguageState = {
  language: 'zh',
  languageList: [
    {
      name: '中文',code: 'zh'
    },
    {
      name: 'English', code: 'en'
    }
  ]
}

export default (state=defaultState, action) => {
  switch (action.type) {
    case 'add_language':
      return {
        ...state, languageList: [...state.languageList, action.payload]
      }
    case 'change_language':
      return {...state, language: action.payload}
    default: 
      return state
  }
}

7-7 【i18n】完成网站语言切换

【i18n】
Internationalization 首尾字符i n 之间有18个字符

根据不同语言及地区显示相应的界面

原理

  • 语言包作为静态资源单独保存 xml json
  • 每种语言对应一个文件
  • 切换语言设置时 语言文件也会随之切换

I18n工具

// 安装工依赖

npm install react-i18next i18next --save

两个插件都支持原生TS

7-8 【redux重构】action 的拆分与统一

Action Creator(工厂模式)

https://www.runoob.com/design-pattern/factory-pattern.html

7-9 【redux封装】在类组件中使用react-redux

React-redux
React Redux 7.1+ requires React 16.8.3 or later, in order to make use of React Hooks.

cnpm install react-redux --save

cnpm install @types/react-redux --save-dev

connect

7-10 【redux封装】在函数式组建中使用react-redux

7-11 【讨论题】redux 深度分析

通过使用redux架构,我们可以剥离组件中的state,也就是剥离组件数据,所有数据统一管理。通过本章的学习,我们来讨论几个问题:

  • 如何实现redux state的订阅与推送?
  • 请描述redux的工作流程?
  • 请举例描述工厂模式是什么?
  • redux和react-redux有什么关系?

第8章 【进击的Redux】异步AJAX与redux中间件

8-1 带着问题来学习

8-2 【概念理解】RESTful

REST
REpresentational State Transfer
表征性状态转移

基本特点

  • 无状态

  • 面向"资源"
    路径一般不会有动词
    请添加图片描述

  • 使用HTTP的动词
    请添加图片描述

  • HATOAS 超媒体即应用状态引擎
    Hypertext As The En-gine Of Application State

好用: 面对对象(资源) 如增删改查

不好用: 面对过程 如登录

8-3 【API说明】课程后端在哪里?

请求头
x-icode: 12623592D652DDFC 三十天有效期

8-4 【API连接】AJAX 异步获取推荐数据

使用Axios

  • 简单易用 APi接近于Jquery 比原生fetch简单
  • 浏览器兼容性好 都能兼容IE7 使用fetch就得自己处理兼容
  • 通用性好 能在node和浏览器中使用 api一致
cnpm install axios --save

8-5 【概念理解】Redux vs MVC

目前的设计模式

  • 混搭
  • Header导航栏使用的是redux架构
  • 主页的内容 更倾向于MVC的架构

什么是MVC

  • 一种架构模式 同时也是一种思想
  • 模型Model 视图View 和 控制器Controller
  • 分离业务操作 UI显示 逻辑控制

视图View

  • 用户交互界面
  • 仅展示数据 不处理数据
  • React项目的JSX代码

模型Model

  • MVC架构的核心
  • 表示业务模型或数据模型
  • 业务逻辑 如算法实现 数据的管理 输出对象的封装等等

控制器Controller

  • 接受用户的输入 并调用模型和视图去完成用户的请求处理
  • 不处理数据
  • React -> MVVM 或 MV*(whatever)

React项目不提倡使用MVC架构
请添加图片描述

数据双向绑定 可以导致 死循环

请添加图片描述

Redux架构

在这里插入图片描述

在这里插入图片描述

8-6 【reducer管理】combineReducers

本课 实现MVC到Redux的转变

8-7 【中间件】使用redux-thunk中间价实现异步action

redux-thunk npm

在这里插入图片描述

8-8 【中间件】什么是中间件

函数式编程

符合函数代码

const hello = function(x) {
    
    
    return `Hello, ${
      
      x}`
}

const happy = function(x) {
    
    
    return `${
      
      x}:)`
}

const compose = function (f, g) {
    
    
    return function(x) {
    
    
        return f(g(x))
    }
}

const happyHello = compose(hello, happy)
happyHello('阿莱克斯') // 'Hello, 阿莱克斯:)'

柯里化

const curriedAdd = (a) => {
    
    
    return (b) => {
    
    return a + b}
}

const addTen = curriedAdd(10)

addTen(12) // 22

为什么需要中间件

  • redux的核心 就是控制和管理所有的数据输入输出
  • 因此有了store reducer以及action的dispatch

假设有以下需求

  • 要求我们记录每次dispatch的action信息
  • 每次dispatch还得记录一下当前store的数据state
  • 不仅要打印当前state 还得打印更新后的state

解决方案: 封装dispatch

let next = store.dispatch

store.dispatch = function dispatchAndLog(action) {
    
    
    console.log('dispatching', action)
    console.log('当前state', store.getState())
    next(action)
    console.log('更新后的state', store.getState())
}

未来的扩展封装

const applyMiddleware = function (middleware) {
    
    
    let next = store.dispatch;
    // 这里传入store 因为中间件有可能会用到getState方法获取数据 比如打印store
    store.dispatch = middleware(store)(next)
}

applyMiddleware(dispatcAndLog)

Redux中间件机制

  • 一共了一个分类处理action的机会

下面是没有异步操作的情况
在这里插入图片描述

增加中间件后 就可以对action做额外的处理

在这里插入图片描述

Redux的异步处理

  • redux-thunk
  • redux-promise
  • redux-saga

三个插件实现原理基本一样

Redux中间件公示

const middleware = (store) => (next) => (action) => {
    
    }

8-9 【中间件】自定义中间件 actionLog

8-10 【RESTful进阶(选修)】Richardson成熟度模型与HATOAS

马丁大神的博客 中文翻译
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

9-2 章节总览 (redux-toolkit)

作者非常推荐 redux-toolkit(RTK) 减少模板代码

  • RTK俨然成为了一个非常主流的redux架构模式
  • react官方和redux官方的支持

create-react-app 默认的架构就是RTK

在这里插入图片描述

在这里插入图片描述

9-3 【详情页面搭建 1】页面框架、详情与日期选择

9-4 【详情页面搭建 2】页面框架、详情与日期选择

JSX直接处理html字符串

<div dangerouslySetInnerHTML={
     
     {__html: product.features}}
            style={ 
        { 
        margin: 50}}
          >
          </div>

9-5 【概念理解】什么是redux-toolkit

redux-toolkit

9-6 【redux-toolkit】createSlice、reducer、与immer

cnpm install @reduxjs/toolkit

9-7 【概念理解】createAsyncThunk 理论基础

redux-toolkit的createAsyncThunk

9-8 【redux-toolkit】sotre配置(configureStore)与异步处理(createAsyncThunk)

9-9 【综合运用】搜索页面

10-2 【布局重构】页面布局

10-3 【注册页面】Antd + ts 表单处理

10-4 【注册页面】注册业务逻辑处理

10-5 【概念理解】Status Code 的重要性

HTTP状态码

  • 用户可以知道服务器端是正常处理了请求 还是出现了什么错误
  • 一个三位数字的状态码和一个字符串格式状态消息组成
  • 数字码便于程序进行处理 而消息字符串更方便程序员(人)理解

HTTP 状态码被分为5大类
在这里插入图片描述

常见的HTTP状态码
在这里插入图片描述

10-6 【登录页面】用户登录表单与布局

10-7 【概念理解】JWT原理剖析

  • JSON Web Token
  • JWT的作用是用户授权(Authorization) 而不是用户的身份认证(Authentication)

授权(Authorization) VS 认证(Authentication)

  • 用户认证是指使用用户名 密码来验证当前用户的身份(错误状态码: 401 Unauthorized 未授权)
  • 用户授权指当前用户有足够的权限访问特定的资源(错误状态码: 403 forbidden(禁止访问)

传统的Session登录(有状态的登录)

  • 用户登录后 服务器会保存登录的session信息
  • Session ID会通过cookie传递给前端
  • http请求会附带Cookie

JWT改变了用户授权与认证的过程

  • 替换Cookie
  • JWT信息只需要保存在客户端
  • 无状态登录

有状态登录(Session) VS 无状态登录(JWT)

有状态登录
在这里插入图片描述

无状态登录(JWT)
在这里插入图片描述

如果JWT有效 服务端则返回资源

Session VS JWT

  • Session需要保存在服务器上 而Session ID则保存在前端Cookie中
  • JWT信息只需要保存在客户端
  • 无状态登录优势: 分布式部署

10-8 【JWT实例】JWT与单点登录实例解释

JWT官网
在这里插入图片描述

非对称加密算法RSA 私钥在服务器

在这里插入图片描述

JWT优点

  • 无状态 简单 方便 完美支持分布式部署
  • 非对称加密 Token安全性高

JWT缺点

  • 无状态 token一经发布则无法取消(无解)
  • 明文传递 Token安全性低(使用https可以解决)

10-9 【登录页面】SignIn登录业务处理

10-10 【登录页面】SignOut登出业务处理

解码JWT(jwt-decode插件)

import jwt_decode, {
    
     JwtPayload as DefaultJwtPayload } from 'jwt-decode'

interface JwtPayload extends DefaultJwtPayload {
    
    
  username: string
}

useEffect(() => {
    
    
if (jwt) {
    
    
  const token = jwt_decode<JwtPayload>(jwt)
  console.log('jwt decode:', token)
  setUserName(token.username)
}
}, [jwt])

10-11 【redux-persist】登录持久化

redux-persist

cnpm install redux-persist --save

Cookie、session和web Storage
在这里插入图片描述

无状态登录: JWT

  • 只能选择使用cookie或者web storage来保存
  • 课程使用web storage做数据的持久化

Web Storage好处

  • 有效降低网络流量
  • 快速显示数据
  • 临时存储

Web Storage类型

  • SessionStorage: 仅在当前浏览器窗口关闭之前有效
  • localStorage: 始终有效 窗口或浏览器关闭也一直保存
const persistConfig = {
    
    
  key: 'root',
  storage,
  whitelist: ['user'] // 白名单 只存白名单列表里的  黑名单 除了黑名单列表 其他都存
}

在这里插入图片描述

11-1 带着问题来学习

11-2 【路由进阶】私有路由搭建

11-3 【UI搭建】购物车页面初始化

11-4 【Redux创建】购物车 Slice

11-5 【redux连接】加载购物车

11-6 【购物模块完成】购物车下单

11-7 【UI搭建】在线支付页面初始化

11-8 【Redux创建】订单 Slice

11-9 【redux连接】完成在线支付

12-2 【章节总览】部署方案介绍

部署方案

  • 方案一 使用本地服务器来托管网站(node)
  • 方案二 容器化部署

课程上线方案

  • 容器化方案
  • 使用阿里云来处理容器化服务

阿里云上线

  • ECS实例
  • Linux64
  • docker仓库

12-3 【静态部署】本地服务器托

Deployment – create-react-app-官网

Static Server

对于使用 Node 的环境,处理此问题的最简单方法是安装 serve 并让它处理其余部分:

npm install -g serve
serve -s build

PS: 执行serve -s build之前需要先npm run build打包,打包后生成的build文件夹就是本地服务器的根目录

serve -s build将在端口 3000 上为您的静态站点提供服务。像许多 serve 的内部设置一样,可以使用 -l 或 --listen 标志调整端口:

serve -s build -l 4000

运行此命令以获取可用选项的完整列表:

serve -h

12-4 【概念理解】5分钟带你认识Docker

  • 开源的容器化虚拟技术
  • 可移植(开发者可以打包他们的应用 依赖包等到一个可移植的镜像包中 可以自由发布到任何一个window或者linux或者Mac机器上)
  • 非常轻量级(容器本身使用沙盒机制 应用程序与主机系统分隔 仅分享内核)
  • 脱离硬件
  • docker容器直接运行在宿主主机内核上
    在这里插入图片描述

Docker的三个核心概念

  • Registry镜像仓库: 如Docker Hub: https://hub.docker.com/node
  • Image镜像: docker命令拉取镜像 docker pull node
  • Container容器: 容器启动命令 docker run (docker run -it /bin/base)

三者关系
在这里插入图片描述

Docker常用命令
在这里插入图片描述

12-5 【速查表】常用的docker命令

课程相关 Docker 命令

docker 安装及设置

#安装 CentOS已经将Docker软件包放在了Extras软件源中,直接利用即可
yum install docker-io -y
#查看docker的版本 version
docker -v
#开启Docker服务
systemctl start docker.service
#开机启动Docker服务
systemctl enable docker.service
#查看Docker服务启动状态
systemctl status docker.service
#重启Docker服务
systemctl restart docker.service

docker 镜像文件和容器命令

#查看所有镜像
docker images
#删除一个imageid的镜像
docker rmi [IMAE_ID] 
#删除所有镜像
sudo docker rmi $(docker images -q) 
#查看所有容器运行状态
docker ps -a 
docker container ls -all
#删除一个containerid的容器(实例)
docker rm 6f0c67de4b72 
#删除所有容器
docker rm $(sudo docker ps -a -q)
容器日志
#查看指定时间后的日志,只显示最后100行:
docker logs -f -t --since="2019-06-08" --tail=100 CONTAINER_ID
#查看某时间之后的日志:
docker logs -t --since="2019-06-08" CONTAINER_ID
#查看某时间段日志:
docker logs -t --since="2019-06-08" --until "2019-06-09" CONTAINER_ID
#查看最近30分钟的日志:
docker logs --since 30m CONTAINER_ID
# 设置启动策略, docker 容器自动启动(在容器退出或断电开机后,docker可以通过在容器创建时的 --restart 来指定重启策略)
#--restart 参数:
# no,不自动重启容器. (默认值)
# on-failure, 容器发生error而退出(容器退出状态不为0)重启容器,可以指定重启的最大次数,如:on-failure:10
# unless-stopped, 在容器已经stop掉或Docker stoped/restarted的时候才重启容器,手动stop的不算
# always, 在容器已经stop掉或Docker stoped/restarted的时候才重启容器
docker run --restart always -it -p {本机端口}:{容器端口} {镜像名称}
#如果容器已经被创建,但处于停止状态,重新启动:
docker start {容器ID}
#如果容器已经被创建,我们想要修改容器的重启策略
docker update --restart always {容器ID}

12-6 【环境搭建】容器化方案与Docker配置

12-7 【速查表】Dockerfile 语法

【速查表】Dockerfile 语法

Dockerfile语法由两部分构成,注释和命令+参数

简单的例子

#第一行必须指令基于的基础镜像
FROM ubutu
#维护者信息
MAINTAINER docker_user [email protected]
#镜像的操作指令
RUN apt-get update && apt-get install -y ngnix 
RUN echo "\ndaemon off;">>/etc/ngnix/nignix.conf
#容器启动时执行指令
CMD /usr/sbin/ngnix

Dockerfile 命令

  1. FROM

基础镜像可以为任意镜像。如果基础镜像没有被发现,Docker将试图从Docker image index来查找该镜像。FROM命令必须是Dockerfile的首个命令。如果同一个DockerFile创建多个镜像时,可使用多个FROM指令(每个镜像一次)

# Usage: FROM [image name]
FROM ubuntu 
  1. MAINTAINER

指定维护者的信息,并应该放在FROM的后面。

# Usage: MAINTAINER [name]
MAINTAINER authors_name 
  1. RUN

RUN命令是Dockerfile执行命令的核心部分。它接受命令作为参数并用于创建镜像。不像CMD命令,RUN命令用于创建镜像(在之前commit的层之上形成新的层)。
格式为Run 或者Run [“executable” ,”Param1”, “param2”]
前者在shell终端上运行,即/bin/sh -C,后者使用exec运行。例如:RUN [“/bin/bash”, “-c”,”echo hello”]
每条run指令在当前基础镜像执行,并且提交新镜像。当命令比较长时,可以使用“/”换行。

# Usage: RUN [command]
RUN apt-get update 
  1. USER

格式为 USER daemon 。
指定运行容器时的用户名或UID,后续的 RUN 也会使用指定用户。
当服务不需要管理员权限时,可以通过该命令指定运行用户。并且可以在之前创建所需要的用户,例如: RUN groupadd -r postgres && useradd -r -g postgres postgres 。要临时获取管理员权限可以使用 gosu ,而不推荐 sudo 。

# Usage: USER [UID]
USER 751
  1. VOLUME

VOLUME命令用于让你的容器访问宿主机上的目录。
格式为 VOLUME [“/data”] 。
创建一个可以从本地主机或其他容器挂载的挂载点,一般用来存放数据库和需要保持的数据等。

# Usage: VOLUME ["/dir_1", "/dir_2" ..]
VOLUME ["/my_files", "/app_files"]
  1. WORKDIR

WORKDIR命令用于设置CMD指明的命令的运行目录。
格式为 WORKDIR /path/to/workdir 。
为后续的 RUN 、 CMD 、 ENTRYPOINT 指令配置工作目录。
可以使用多个 WORKDIR 指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。例如:

WORKDIR /a 
WORKDIR b 
WORKDIR c 
RUN pwd 
# 最终路径为 /a/b/c 。
  1. CMD

支持三种格式:
CMD [“executable” ,”Param1”, “param2”]使用exec执行,推荐
CMD command param1 param2,在/bin/sh上执行
CMD [“Param1”, “param2”] 提供给ENTRYPOINT做默认参数。
每个容器只能执行一条CMD命令,多个CMD命令时,只最后一条被执行。

# Usage 1: CMD application "argument", "argument", ..
CMD "echo" "Hello docker!"
  1. ENV

格式为 ENV 。 指定一个环境变量,会被后续 RUN 指令使用,并在容器运行时保持。

ENV TZ "Asia/Shanghai"
ENV LANG en_US.UTF-8
ENV LC_ALL en_US.UTF-8
  1. ADD

ADD命令有两个参数,源和目标。它的基本作用是从源系统的文件系统上复制文件到目标容器的文件系统。如果源是一个URL,那该URL的内容将被下载并复制到容器中。如果文件是可识别的压缩格式,则docker会帮忙解压缩。

# Usage: ADD [source directory or URL] [destination directory]
ADD /my_app_folder /my_app_folder 
  1. COPY(几乎与ADD没有区别)

COPY 将文件从路径 <src复制添加到容器内部路径 。

COPY <src> <dest>
  1. EXPOSE

指定在docker允许时指定的端口进行转发

EXPOSE <port>[<port>...]
  1. ENTRYPOINT

两种格式:

  • ENTRYPOINT ["executable", "param1", "param2"]
  • ENTRYPOINT command param1 param2(shell中执行)

配置容器启动后执行的命令,并且不可被 docker run 提供的参数覆盖。
每个 Dockerfile 中只能有一个 ENTRYPOINT,当指定多个时,只有最后一个起效。

  1. ONBUILD

ONBUILD 指定的命令在构建镜像时并不执行,而是在它的子镜像中执行

资料来源:

https://juejin.cn/post/6844903889301422088
https://github.com/qianlei90/Blog/issues/35
https://www.jianshu.com/p/690844302df5
https://www.jianshu.com/p/5f4b1ade9dfc

12-8 【容器化改造】理解 Dockerfile 全过程

上节课

  • 创建了doker虚拟机 并安装了nginx托管服务器
  • 也可以构建定制内容的Docker镜像
  • 可直接下载官方提供的Nginx Docker镜像

两种方式构建Docker镜像

  • docker commit
  • docker build + Dockerfile(可重复性 标准化)

根目录下Dockerfile文件

Dockerfile文件是用来构建docker镜像的脚本

# 第一个阶段: 拉取node镜像来打包React项目
## 拉取node镜像 版本14 命名为build
FROM node:14 as build
## 设置docker命令运行目录
WORKDIR /app
## 复制对应文件到app文件夹中 app/
COPY package*.json ./
## 安装项目依赖
RUN npm install
## 复制对应文件到app文件夹中 app/
COPY tsconfig.json ./
## 复制public文件夹并创建public文件夹
COPY public public/
## 复制src文件夹并创建src文件夹
COPY src src/
## 构建react项目
RUN npm run build

# 第二个阶段: 创建并运行Ngnix服务器,并且把打包好的文件复制粘贴到服务器文件夹中
## 拉取nginx服务器镜像 :执行版本
FROM nginx:alpine
## 将第一阶段通过npm run build构建好的react项目复制到nginx服务器镜像中
COPY --from=build /app/build/ /usr/share/nginx/html
## 暴露nginx服务器端口
EXPOSE 80
## 使用CMD命令来启动nginx命令
CMD ["nginx", "-g", "daemon off;"]

构建镜像 在react项目根目录下执行命令

docker build -t react-web .

docer会根据Dockerfile文件一步一步执行

镜像构建完成后 可以开始部署

通过下面的命令 查看镜像是否存在

docker images

在这里插入图片描述

容器化react-web镜像

12231是本机端口 不限制 80是nginx服务器暴露出来的端口 react-web镜像

docker run -d -p 12231:80 react-web

下面的命令检查容器列表 可以验证是否成功部署

docker ps

在这里插入图片描述

这时访问localhost:12231 就可以正常访问了

12-9 【容器化上线】实现阿里云部署

每一节课都是单独提交到码云 想看具体某一节的代码内容 可以在码云上找到对应的提交记录 查看代码改动

码云仓库地址: https://gitee.com/WebJianHong/react-travel

推荐阅读

Vue源码学习目录

Vue源码学习完整目录

连点成线 - 前端成长之路

连点成线 - 前端成长之路


谢谢你阅读到了最后~
期待你关注、收藏、评论、点赞~
让我们一起 变得更强

请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述

猜你喜欢

转载自blog.csdn.net/weixin_42752574/article/details/122594056