跳转至

React

add_circle2025-03-06update2025-03-07

1. 前端工程化

前端工程化是大厂前端开发人员的必备技能

在了解web前端工程化之前,我给大家回顾下web前端的发展史:

以2011年左右这个时间点为界线,在此之前的web前端就是为了完成HTML网页开发(一般叫拆板侠、切图仔),日常工作就是拿到UI设计师的设计稿,然后基于HTML、CSS、javascript一张一张地完成静态化网页的制作,然后把静态网页交给服务端开发(java,php等),完成的项目基本都是前后端不分离的web项目。在此之后的web前端随着移动智能手机的普及,各种APP,微信开发随之蜂拥而来,场景也越来越丰富复杂了,不得不把前端独立出来,也是目前主流的前后端分离开发模式。2015年之后,前端开发更是进入了技术井喷期,使前端开发的开发形式产生了翻天覆地的变化。至今为止,Web前端业务日益复杂化和多元化,前端项目开发早已经不是过去的5-6个页面、复制几个jQuery插件就能完成的了。项目复杂了就会产生许多问题,比如:当开发团队的人数达到一定的规模以后(例如,30 人以上),如何进行高效的多人协作?如何保证项目的可维护性?如何提高项目的开发质量?

前端工程化是前端项目架构中重要的一环,它的出现就是为了解决上述大部分问题的。

  1. 声明式:小明,出去操场跑5圈。
  2. 命令式:小明,起来,滚出去,往东50米,看到操场了,跑1圈,跑完以后再跑一圈,再跑。。直到5圈。

2. React基础

React是一个用于构建UI(User Interface,用户界面)的JavaScript库,也是目前全世界最流行的web前端框架之一,由Facebook在2013年5月开源的前端项目,因为Facebook对市场上所有 JavaScript MVC框架都不满意,所以就自己写了一个前端框架用来架设Instagram网站。

官方网站:https://zh-hans.reactjs.org/

React官方并不认可MVC开发模式,所以React不是一个完整的MVC前端框架,只能说是一个轻量级的视图框架(MVC中的View)。React具有如下特点:

  • 声明式设计。为应用的每一个状态设计简洁的视图,当数据改变时 React 能有效地更新并正确地渲染组件。
  • 高效。React采用Virtual DOM(虚拟DOM), 极大的提升了UI渲染(更新)效率。
  • 灵活。React 允许你结合其他框架或库一起使用,而且有大量的开发者围绕着React去开发各种各样的工具库。
  • JSX。JSX 是 React框架基于JavaScript的语法扩展。JSX 可以很好地描述 UI 应该呈现出它应有交互的本质形式。
  • 组件 。通过 React 构建组件,使得代码更加容易得到复用,能够很好的应用在大项目的开发中。
  • 单向响应的数据流。React 采用了单向响应的数据流,使组件状态更容易维护, 组件模块化更易于快速开发。

虚拟DOM

image-20221115230940215

  • 用 JavaScript 对象模拟真实 DOM 树,对真实 DOM 进行抽象;
  • diff 算法 — 比较两棵虚拟 DOM 树的差异;
  • pach 算法 — 将两个虚拟 DOM 对象的差异应用到真正的 DOM 树。

2.1 快速上手

渲染内容

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!-- React核心包,提供了操作React的所有必要功能,但不提供DOM操作相关的部分功能   -->
    <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <!-- ReactDOM,提供了支持react操作DOM的相关功能 -->
    <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
</head>
<body>
    <div id="root"></div>
    <script>
        var msg = 'hello,React!';
        const root = ReactDOM.createRoot(document.querySelector('#root'));
        // 渲染,把msg变量的信息渲染到绑定的根节点中
        root.render(msg);
    </script>
</body>
</html>

创建虚拟节点

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!--
    React,实际上分 React-dom 网页开发  React-native App开发
    React核心包,提供了操作React的所有必要功能,但不提供DOM操作相关的部分功能   -->
    <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <!-- ReactDOM,提供了支持react操作DOM的相关功能 -->
    <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
</head>
<body>
    <div id="root">

    </div>

    <script>
        var msg = "hello, React"
        // 创建React的虚拟DOM节点,a元素
        const SVDOM = React.createElement("a", {href: "http://www.qq.com"}, msg)
        // 创建React的虚拟DOM节点,h1元素
        const VDOM = React.createElement("h1", {title: msg}, SVDOM)
        // React把数据展示到#root标签
        const root = ReactDOM.createRoot(document.querySelector('#root'))
        // 渲染,把msg变量的信息渲染到绑定的根节点中
        root.render(VDOM)
    </script>

</body>
</html>

2.1.1 JSX

JSX(JavaScript Xml)是 React框架基于JavaScript+XML实现的语法扩展,类似模板语言,但具有 JavaScript 的全部功能。

JSX提供了在 JavaScript 代码中写 XML(HTML)代码的功能,让项目中的用户界面代码变得更加直观、结构清晰,从而提升开发效率,所以React推荐开发者使用JSX来声明描述用户界面。

JSX本质上就是React.createElement(component,props,children)函数的语法糖,使用babel编译后,JSX会变成虚拟DOM对象。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    <!-- Babel一个JavaScript代码转译器可以把ES6代码转换成ES5代码的的语法转换工具也可以把JSX代码转换成javascript代码 -->
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
    <div id="root"></div>
    <!-- script标签声明内部的代码需要使用babel进行转译 -->
    <script type="text/babel">
        var msg = 'hello,React!';
        // JSX语法创建虚拟DOM
        var VDOM = <div id="son">
            {msg}<br/>
            <span>{msg}</span>
        </div>
        const root = ReactDOM.createRoot(document.querySelector('#root'));
        // 渲染,这次渲染的是虚拟DOM节点
        root.render(VDOM);
    </script>
</body>
</html>
2.1.1.1 语法规则
  1. 所有标签必须闭合,最外层必须有且只有一个根元素。遇到与js关键字同名的属性,要特殊处理。
/* 错误写法:最外层是2个元素 */
var VDOM = <span>1</span><span>2</span>

/* 正确写法 */
var VDOM = <span>1</span>
var VDOM = (<span>1</span>)
var VDOM = (<><span>1</span><span>1</span></>)
var VDOM = (<div><span>1</span><span>1</span></div>)

/* 错误写法:class与for都是js中的关键字 */
<p class="myele">!!!!!</p>
<label for="ooo">1111</label>
/* 正确写法 */
<p className="myele">!!!!!</p>
<label htmlFor="ooo">1111</label>


/* 错误写法:标签没有闭合 */
<br>
<input>

/* 正确写法 */
<br/>
<input></input>
<input/>
  1. 在单个花括号{}中编写js代码。当标签的属性值是js代码时,把引号换成单个花括号。
// 假设msg和num是一个js变量
/* 错误写法: 属性值加了引号,"msg"成了字符串。*/
<p title="msg"></p>
/* 错误写法: 内容中没有花括号,所以msg被当成了字符串。*/
<p>msg</p>

/* 正确写法 */
<p title={msg}>鼠标放上来</p>
<p>{msg}</p>
  1. 注释需要使用js多行注释,并且外面加上花括号。
// 错误写法:
<div>
    // 错误的注释写法
    <p>{ msg }</p>
    /* 错误的注释写法 */
    <p>{ msg }</p>
     {// 错误的注释写法}
</div>

// 正确写法
<div>
    {/* 正确的注释写法 */}
    { msg }
</div>
2.1.1.2 渲染数据
/* 错误写法:没有返回值的语句,不能嵌入JSX中 */
<div>{ var a = 10  }</div>
/* 错误写法:for语句或者if语句没有结果,不能嵌入JSX中 */
<ul>
for(var i = 0 ; i < 10 ; i ++){
<li>{i}</li>
}
</ul>

/* 正确写法:三元表达式可以代替if语句 */
<div>{ 1>0?10:20 }</div>
/* 正确写法:改用map遍历对象或数组 */
const user = {name: 'xiaoming', age: 16}
// JSX语法创建虚拟DOM
const VDOM = (
<ul>
    {
        Object.keys(user).map(key=>
            <li key={key}>{key}={user[key]}</li>
        )
    }
</ul>
)


/* 错误写法:大部分对象无法直接嵌入JSX中 */
<div>{ new Date() }</div>
/* 错误写法:匿名函数或函数名,无法直接嵌入JSX中 */
<div>{ ()=>2 }</div>
<div>{ functionName }</div>

/* 正确写法:表达式,可以嵌入JSX */
<div>{ a+=10  }</div>
/* 正确写法:单个变量属于表达式,可以嵌入JSX */
<div>{ a }</div>
/* 正确写法:字面量输入表达式,可以嵌入JSX */
<div>{ 10  }</div>
/* 正确写法:数组,可以嵌入JSX */
<div>{ [1,2,3]  }</div>

渲染列表数据

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
    <div id="root"></div>
    <script type="text/babel">
        let books_list = [
            {id: 10, title: "标题1", price: 38.50},
            {id: 13, title: "标题2", price: 28.50},
            {id: 15, title: "标题3", price: 68.50},
            {id: 21, title: "标题4", price: 58.50},
        ]
        class HelloComponent extends React.Component {
            render(){ // 所有视图代码,必须写在render方法中通过return返回给外界。
                return (
                    <table border='1'>
                        <tr>
                            <td>编号</td>
                            <td>价格</td>
                            <td>标题</td>
                        </tr>
                        {books_list.map((item,key)=>(
                            <tr key={key}>
                                <td>{item.id}</td>
                                <td>{item.price.toFixed(3)}</td>
                                <td>{item.title}</td>
                            </tr>
                        ))}
                    </table>
                )
            }
        }
        const root = ReactDOM.createRoot(document.querySelector('#root'));
        root.render(<HelloComponent/>);
    </script>
</body>
</html>

key属性说明,因为React的高性能是依赖于虚拟DOM的,所以React的JSX操作中都是尽量避免去操作DOM元素的。但是对于开发中的列表数据而言,因为列表元素在操作过程中会出现位置改变的情况,而React的虚拟DOM是并不知道的,此时,有可能React的虚拟DOM会把位置改变后的所有元素全部进行重新渲染,这就会大量增加DOM操作了,因此React会要求我们在遍历列表元素时给元素绑定一个唯一的key值,当列表中的元素在操作过程中出现位置改变时,React就可以通过预先设置好的key值来识别到了。

输出连续序数

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>

</head>
<body>
    <div id="root">

    </div>

    <script type="text/babel">
        // 使用map来循环数组即可
        const VDOM = <div>
            <ul>
                {Array(10).fill(null, 2).map((item, key)=>
                    <li key={key}>{key}</li>)}
            </ul>
        </div>

        // React把数据展示到#root标签
        const root = ReactDOM.createRoot(document.querySelector('#root'))
        // 渲染,把msg变量的信息渲染到绑定的根节点中
        root.render(VDOM)
    </script>

</body>
</html>
2.1.1.3 渲染样式

行内样式

<div style={{color: 'red',fontSize: '20px'}}></div>

var myStyle = {color: 'green',fontSize: '20px'};
<div style={myStyle}></div>

class样式

<!-- css代码如果是外部样式文件必须导入使用 -->
<style>
    .danger{
      color: red;
    }
    .info{
      color: blue;
    }
    .f24{
      font-size: 24px;
    }
</style>


// jsx代码
<div>
    <p className={'danger f24'}>危险</p>
    <p className={'info f24'}>安全</p>
</div>

基于组件化的整体性,React官方建议我们开发中使用行内样式,但在开发中大多数公司团队都是采取了基于目录的方式来保存不同组件的代码,往往一个页面就是一个组件,而属于这个页面的css样式文件和js组件文件则会保存当前目录下。

2.1.1.4 代码转义

在默认情况下,React DOM会将所有嵌入JSX的数据进行编码,HTML代码会进行实体转义。这样可以有效避免xss攻击。

// 默认会进行HTML转义
let content = '<script>console.log('helloReact!')<\/script>'
var VDOM = <div>{content}</div>


// 不进行HTML转义
let content = {
    __html: '<script>console.log('helloReact!')<\/script>'
};
var VDOM = <div dangerouslySetInnerHTML={content}></div>

2.2 基于项目构建工具来管理项目

在上面的学习中,我们一直把代码写在一个文件中,每次使用这个文件都要反复去引入react、react-dom、babel文件。这无疑很影响我们的学习和开发效率,所以接下来我们可以基于项目构建工具来搭建一个React项目,以工程化的方式来继续学习。

创建项目

# yarn+create-react-app
yarn create react-app yarn-react-basic

# npm + create-react-app
npm init react-app npm-react-basic

# npx+create-react-app
npx create-react-app npx-react-basic

# yarn+vite
yarn create vite

# npm+vite
npm init vite

yarn、npm就是包管理器,还可以项目进行构建管理。

React官方为了方便咱们学习React推出了一个脚手架create-react-app(100M),而vite则是另一个前端框架vue的作者尤雨溪开发的脚手架vite。

create-react-app搭建的React项目,默认入口是public/index.html,脚本文件的扩展名是js

vite搭建的React,默认入口是index.html,脚本文件的扩展名是jsx

启动项目

# create-react-app启动项目
yarn start  # 或者 npm start

# vite启动项目
yarn dev  # 或者 npm run dev 

2.3 组件化

在前端开发中经常出现多个网页的功能是重复的,而且很多不同的页面之间也存在同样的功能。在React中,我们可以按功能或业务来把UI界面进行拆分一个个包含模板(HTML)+样式(CSS)+逻辑(JS)的功能完备的结构单元。这些结构单元就可以设计成一个个组件方便开发人员进行代码复用,这种开发方式也就是组件化开发,前端人员在组件化开发时,只需要书写一次代码,随处引入即可使用。

React创建组件的两种方式:

  • 函数式组件

以函数格式来创建组件,直接在函数中return返回视图代码即可。

  • 类组件

以类的方式来创建组件,组件类必须直接或间接继承于React.Component类,而且视图代码必须通过render方法return返回给外界。

注意:

在React17以前,函数式组件也叫无状态(state)组件,而类组件则为有状态组件。所谓有无状态,指代的是组件内部是否能定义状态(state,状态就是组件内部定义和使用的私有数据),并通过state来保存数据或修改视图的界面效果。

在React17版本开始,函数式组件也可以通过Hooks来定义状态(state),来保存数据或修改视图了,函数式组件的应用也变得强大起来了。

2.3.1 类组件

src/App.jsx,代码:

import React from "react";
class App extends React.Component{
    constructor() {
        super();
        this.msg = 'Hello, Class Component!!'
    }
    render() {
        return (
            <div className="App">
                {this.msg}
            </div>
        )
    }
}

export default App

src/main.jsx,代码:

import React from 'react'
import ReactDOM from 'react-dom/client'
// 导入组件才能使用
import App from './App'

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
)

注意:

不管是函数式组件还是类组件,组件名必须首字母大写!!!否则报错!

2.3.2 函数式组件

src/Func.jsx,代码:

function Func(){
    let msg = 'Hello, Function Component!!'
    return (
        <div className="App">
            {msg}
        </div>
    )
}

export default Func

src/main.jsx,代码:

import React from 'react'
import ReactDOM from 'react-dom/client'
// 导入组件才能使用
import App from './App'
import Func from "./Func.jsx";

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
    <Func />
  </React.StrictMode>
)

pycharm/vscode快速生成snip代码的快捷键:

rcc 快速生成类组件
rsf  快速生成函数式组件
rsc  快速生成工具函数的代码片段

2.3.3 组件的嵌套

组件可以随意组合和嵌套的。在开发中,我们习惯把整个项目分成许多大大小小的组件,一个页面可以设计成一个组件,而一个页面组件下又可以包含多个功能组件,有些复杂的功能组件,还可以细化嵌套自己的子组件。

src/App.jsx,代码:

import React from "react";
import Header from "./Header.jsx";
import Footer from "./Footer.jsx";

class App extends React.Component{
    constructor() {
        super();
        this.msg = 'Hello, Class Component!!'
    }
    render() {
        return (
            <div className="App">
                <Header/>
                <h1>中间内容属于App的</h1>
                <Footer/>
            </div>
        )
    }
}

export default App

src/Header.jsx,代码:

import React from "react";

class Header extends React.Component{
    render() {
        return (
            <div>
               我是Header中定义的头部内容!!
            </div>
        )
    }
}

export default Header

src/Footer.jsx,代码:

import React from "react";

class Footer extends React.Component{
    render() {
        return (
            <div>
               我是Footer中定义的脚部内容!!
            </div>
        )
    }
}

export default Footer

src/main.jsx,代码:

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
)

一般我们会把被导入的小功能组件叫子组件,同理,导入其他组件的当前组件,叫父组件。属于嵌套关系的两个组件之间的关系,就是父子组件。当然,如果两个组件被同一个父组件所调用,则这两个组件则为兄弟组件,属于并列关系。

2.4 事件处理

React的主要功能就是渲染视图,而视图操作中事件的绑定和处理,肯定是必不可少的。

import React from "react";

class App extends React.Component{
    render() {
        return (
            <div className="App">
                <button onClick={this.func1.bind(this)}>点击按钮</button><br/>
                <button onClick={()=>this.func2('func2')}>点击按钮</button>
            </div>
        )
    }
    func1(){
        console.log("func1被点击了!!!")
    }
    func2(data){
        console.log(`${data}被点击了!!!`)
    }
}

export default App

修正事件中的this指向

import React from "react";
import Header from "./Header.jsx";
import Footer from "./Footer.jsx";

class App extends React.Component{
    constructor() {
        super();
        this.msg = 'hello, Component'
    }
    render() {
        return (
            <div className="App">
                <button onClick={this.func1.bind(this)}>点击按钮</button><br/>
                <button onClick={()=>this.func2('func2')}>点击按钮</button>
            </div>
        )
    }
    func1(){
        console.log("func1被点击了!!!", this.msg)
    }
    func2(data){
        console.log(`${data}被点击了!!!`, this.msg)
    }
}

export default App

在javascript中函数中的this的上下文切换可以采用三个方法来完成:call、apply、bind,但是在上面代码环境中,只有bind符合我们的使用需求。

2.4.1 了解React的事件机制

在React的事件处理中,React并没有把事件绑定到具体的dom节点上,而是通过事件代理(也叫事件委托)的方式将所有的事件绑定到了React注册的根节点(React17之前是Document)上,然后由统一的事件监听器(dispatchEvent)去监听事件的触发,这样的处理不仅减少页面的注册事件数量、减少事件处理和回收带来的内存开销、抹平浏览器之间的事件差异,还能在组件挂载销毁时统一订阅和移除事件,当然也达到了项目性能优化的目的。

React内部基于浏览器的事件机制实现了一套事件机制(SyntheticEvent,合成事件),包括事件的注册、事件的存储、事件的合成及执行等。合成事件是浏览器的原生事件的跨浏览器包装器。除兼容所有浏览器外,它还拥有和浏览器原生事件相同的接口,包括 stopPropagation()preventDefault()

React在内部维护了一个映射表(listenerBank)来记录事件与组件的事件处理函数的对应关系,当某个事件触发时,React根据会根据当前的组件ID和事件类型到映射表中将事件分派给指定的事件处理函数。当一个组件挂载与卸载时,相应的事件处理函数会自动被添加到事件监听器的内部映射表中或从表中删除。

因为React的合成事件,是在事件冒泡阶段执行,所以会比javascript原生事件对象要慢。

image-20221116064426970

import React from "react";

class App extends React.Component{
    msg = "hello"
    render() {
        return <div onClick={()=>{
            this.fn3()
        }}>
            <button onClick={this.fn1.bind(this)}>点击按钮</button><br/>
            <p onClick={(event)=>{
                this.fn2(event) // 建议大家写这个格式绑定事件处理
            }}>点击按钮</p><br/>
            <a href="http://www.baidu.com" onClick={(evenet)=>{
                this.fn4(event)
            }}>跳转页面</a>
        </div>
    }
    fn1(){
        console.log("this=", this);
        console.log("fn1被点击了!", this.msg);
    }
    fn2(event){
        // 阻止事件冒泡
        event.stopPropagation()
        console.log("fn2被点击了!!", this.msg)
    }
    fn3(){
        console.log("fn3执行了!")
    }
    fn4(event){
        // 组织元素标签的默认行为,例如:a标签的页面跳转,表单的submit提交数据进行页面
        event.preventDefault()
    }
}
export default App

2.5 三大属性

三大属性指代的是React中组件最重要的,也是最常用的三个主要属性:ref,props和 state

2.5.1 ref

React提供了Ref属性可以让开发者很方便快捷地拿到组件实例对象或DOM元素。

import React from "react";
import Header from "./Header.jsx";
import Footer from "./Footer.jsx";
class App extends React.Component{
    footer = React.createRef()
    p1 = React.createRef()
    render() {
        return (
            <div className="App">
                <Header ref="header"></Header>
                <input type="text" ref="username"/>
                <Footer ref={this.footer}/>
                <p ref={this.p1}>hello, p </p>
                <button onClick={()=>this.func1()}>点击按钮</button><br/>
            </div>
        )
    }
    func1(){
        console.log(this.refs.username.value)
        console.log(this.refs.header)
        console.log(this.footer.current)
        console.log(this.p1.current)
    }
}

export default App

监听数据变化

import React from "react";
class App extends React.Component{
    input = React.createRef()
    render() {
        return (
            <div>
                <input type="text" ref={this.input} onChange={()=>{
                    this.changeEvent()
                }}/>
            </div>
        )
    }
    changeEvent(){
        console.log(this.input.current.value);
    }
}
export default App

2.5.2 state

state(状态)就是每个组件中内部保存的私有数据,只能在组件内部声明和使用state的值是对象,一个组件中可以有多个state数据。组件的state如果发生变化,那么React就会自动重新渲染用户界面(不需要操作DOM),一句话就是,用户的界面会随着state状态的改变而自动发生改变,达到自动响应的目的。

import React from "react";
class App extends React.Component{
    // 初始化状态,里面的变量都是自定义的。
    state = {
        type: "password",
        tips: "显示密码",
    }
    render() {
        return (
            <div>
                { /* 读取state中的数据 */ }
                <input type={this.state.type} />
                <button onClick={()=>this.changeevt()}>{this.state.tips}</button>
            </div>
        )
    }
    changeevt(){
        if(this.state.type === "password"){
            // 修改状态
            this.setState({
                type: "text",
                tips: "隐藏密码",
            })
        }else{
            this.setState({
                type: "password",
                tips: "显示密码",
            })
        }
    }
}
export default App

2.5.3 案例-todolist-计划任务

2.5.3.1 基础代码

todolist.css,代码:

ul, li, input,button{
    padding: 0;
    margin: 0;
    font-family: Arial;
    list-style: none;
}
.clear:after{
    content: '';
    display: block;
    clear: both;
}
.todo-list{
    width: 600px;
    margin: 50px auto 0;
}
.todo-list li{
    height: 42px;
    line-height: 42px;
    text-indent: 5px;
    border-bottom: 1px solid #ccc;
    margin-bottom: 12px;
}
.todo-list li :nth-child(1){
    float: left;
}
.todo-list li :nth-child(n+2){
    float: right;
    margin-left: 10px;
    margin-right: 10px;
    cursor: pointer;
}
.todo-list li.no-tasks{
    border-bottom: none;
}
.todo-list li.no-tasks span{
    display: block;
    width: 100%;
    text-align: center;
}
input[type=text]{
    width: 500px;
    height: 32px;
    line-height: 32px;
    text-indent: 5px;
    outline: none;
    float: left;
    padding: 0;
    margin: 0;
}
button.add-btn{
    width: 96px;
    height: 36px;
    line-height: 36px;
    background-color: #888;
    float: right;
    color: white;
    border: 0;
}

TodoList.jsx,代码:

import React, {Component} from 'react';
import "./todolist.css"

class TodoList extends Component {
    render() {
        return (
            <div className={'todo-list'}>
                <div className={'clear'}>
                    <input type="text"/><button className={'add-btn'}>添加</button>
                </div>
                <ul>
                    <li><span>学习Javascript</span><span>删除</span><span></span><span></span></li>
                    <li><span>学习HTML</span><span>删除</span><span></span><span></span></li>
                    <li><span>学习CSS</span><span>删除</span><span></span><span></span></li>
                    <li><span>学习Java</span><span>删除</span><span></span><span></span></li>
                </ul>
            </div>
        );
    }
}

export default TodoList;
2.5.3.2 计划列表
import React, {Component} from 'react';
import "./todolist.css"

class TodoList extends Component {
    state = {
        tasks: [  // 计划任务列表
            // "学习HTML",
            // "学习Javascript",
            // "学习CSS",
            // "学习Java",
        ],
    }
    render() {
        return (
            <div className={'todo-list'}>
                <div className={'clear'}>
                    <input type="text"/><button className={'add-btn'}>添加</button>
                </div>
                <ul>
                    {
                        this.state.tasks.map((item, key)=>{
                            return <li key={key}><span>{item}</span><span>删除</span><span></span><span></span></li>
                        })
                    }
                    {/* 基于逻辑运算实现判断效果 */}
                    {this.state.tasks.length === 0 && <li className={'no-tasks'}><span>暂时没有任何计划</span></li>}
                </ul>
            </div>
        );
    }
}

export default TodoList;
2.5.3.3 添加计划
import React, {Component} from 'react';
import "./todolist.css"

class TodoList extends Component {
    state = {
        tasks: [  // 计划任务列表
            "学习HTML",
            "学习Javascript",
            "学习CSS",
            "学习Java",
        ],
    }
    textBtn = React.createRef()
    render() {
        return (
            <div className={'todo-list'}>
                <div className={'clear'}>
                    <input type="text" ref={this.textBtn}/>
                    <button className={'add-btn'} onClick={()=>{
                        this.addTask()
                    }}>添加</button>
                </div>
                <ul>
                    {
                        this.state.tasks.map((item, key)=>{
                            return <li key={key}><span>{item}</span><span>删除</span><span></span><span></span></li>
                        })
                    }
                    {/* 基于逻辑运算实现判断效果 */}
                    {this.state.tasks.length === 0 && <li className={'no-tasks'}><span>暂时没有任何计划</span></li>}
                </ul>
            </div>
        );
    }
    addTask(){
        // 添加计划任务
        console.log(this.textBtn.current.value);
        // 获取到tasks任务列表
        const tasks = this.state.tasks.concat();
        // 添加计划任务到列表
        tasks.unshift(this.textBtn.current.value);
        // tasks.push(this.textBtn.current.value);
        // 保存任务列表到state状态
        this.setState({
            tasks    //  tasks: tasks 的简写
        })
    }
}

export default TodoList;
2.5.3.4 删除计划
import React, {Component} from 'react';
import "./todolist.css"

class TodoList extends Component {
    state = {
        tasks: [  // 计划任务列表
            "学习HTML",
            "学习Javascript",
            "学习CSS",
            "学习Java",
        ],
    }
    textBtn = React.createRef()
    render() {
        return (
            <div className={'todo-list'}>
                <div className={'clear'}>
                    <input type="text" ref={this.textBtn}/>
                    <button className={'add-btn'} onClick={()=>{
                        this.addTask()
                    }}>添加</button>
                </div>
                <ul>
                    {
                        this.state.tasks.map((item, key)=>{
                            return (
                                <li key={key}>
                                    <span>{item}</span>
                                    <span onClick={()=>{
                                        this.delTask(key)
                                    }}>删除</span>
                                    <span></span>
                                    <span></span>
                                </li>
                            )
                        })
                    }
                    {/* 基于逻辑运算实现判断效果 */}
                    {this.state.tasks.length === 0 && <li className={'no-tasks'}><span>暂时没有任何计划</span></li>}
                </ul>
            </div>
        );
    }
    addTask(){
        // 添加计划任务
        console.log(this.textBtn.current.value);
        // 获取到tasks任务列表
        const tasks = this.state.tasks.concat();
        // 添加计划任务到列表
        tasks.unshift(this.textBtn.current.value);
        // tasks.push(this.textBtn.current.value);
        // 保存任务列表到state状态
        this.setState({
            tasks    //  tasks: tasks 的简写
        })
    }
    delTask(key){
        // 删除计划任务
        console.log(key, this.state.tasks[key]);
        // 获取到tasks任务列表
        const tasks = this.state.tasks.concat();
        // splice
        // 参数1:删除成员的开始下标
        // 参数2:删除成员的个数
        // 参数3....:在删除未知上,是否填充新的成员
        // 返回值:被剔除的数组成员
        tasks.splice(key, 1);
        // 保存任务列表到state状态
        this.setState({
            tasks    //  tasks: tasks 的简写
        })
    }
}

export default TodoList;
2.5.3.4 移动计划
import React, {Component} from 'react';
import "./todolist.css"

class TodoList extends Component {
    state = {
        tasks: [  // 计划任务列表
            "学习HTML",
            "学习Javascript",
            "学习CSS",
            "学习Java",
        ],
    }
    textBtn = React.createRef()
    render() {
        return (
            <div className={'todo-list'}>
                <div className={'clear'}>
                    <input type="text" ref={this.textBtn}/>
                    <button className={'add-btn'} onClick={()=>{
                        this.addTask()
                    }}>添加</button>
                </div>
                <ul>
                    {
                        this.state.tasks.map((item, key)=>{
                            return (
                                <li key={key}>
                                    <span>{item}</span>
                                    <span onClick={()=>{
                                        this.delTask(key)
                                    }}>删除</span>
                                    <span onClick={()=>{
                                        this.upTask(key)
                                    }}></span>
                                    <span onClick={()=>{
                                        this.downTask(key)
                                    }}></span>
                                </li>
                            )
                        })
                    }
                    {/* 基于逻辑运算实现判断效果 */}
                    {this.state.tasks.length === 0 && <li className={'no-tasks'}><span>暂时没有任何计划</span></li>}
                </ul>
            </div>
        );
    }
    addTask(){
        // 添加计划任务
        console.log(this.textBtn.current.value);
        // 获取到tasks任务列表
        const tasks = this.state.tasks.concat();
        // 添加计划任务到列表
        tasks.unshift(this.textBtn.current.value);
        // tasks.push(this.textBtn.current.value);
        // 保存任务列表到state状态
        this.setState({
            tasks    //  tasks: tasks 的简写
        })
        // 清除原有输入框中内容
        this.textBtn.current.value = ""
    }

    delTask(key){
        // 删除计划任务
        console.log(key, this.state.tasks[key]);
        // 获取到tasks任务列表
        const tasks = this.state.tasks.concat();
        // splice
        // 参数1:删除成员的开始下标
        // 参数2:删除成员的个数
        // 参数3....:在删除未知上,是否填充新的成员
        // 返回值:被剔除的数组成员
        tasks.splice(key, 1);
        // 保存任务列表到state状态
        this.setState({
            tasks    //  tasks: tasks 的简写
        })
    }
    upTask(key){
        // 任务向上移动
        // 如果当前任务已经在最顶层,则不需要移动
        if(key === 0) return
        // 从任务列表中把要移动的计划任务进行提取
        const tasks = this.state.tasks
        const task = tasks.splice(key, 1)[0]
        tasks.splice(key-1, 0, task)
        // 保存任务列表到state状态
        this.setState({
            tasks    //  tasks: tasks 的简写
        })
    }

    downTask(key){
        // 任务向下移动
        // 从任务列表中把要移动的计划任务进行提取
        const tasks = this.state.tasks
        const task = tasks.splice(key, 1)[0]
        tasks.splice(key+1, 0, task)
        // 保存任务列表到state状态
        this.setState({
            tasks    //  tasks: tasks 的简写
        })
    }

}

export default TodoList;

2.5.4 props

在前面我们已经学习了组件嵌套,所以组件会因为实现业务或者功能的需求,会出现嵌套或者并列的情况,甚至有时候会因为多个组件负责的业务是相关联的,此时就需要在多个组件之间进行数据的传递了。React组件提供了Props属性, 专门用来实现组件接受外部参数的传递。props是只读的,所以只能获取外部传递的数据,但不能修改该数据。同时React还提供了props数据类型和必要性的约束(React15版本以后,需要单独安装yarn add prop-types)。

2.5.4.1 父组件传递数据给子组件

src/App.jsx,代码:

import React from "react";
import Banner from "./components/Banner.jsx"

class App extends React.Component{
    state = {
        number: 0,
    }
    msg = 'hello, Message'
    render() {
        return (
            <div>
                <button onClick={()=>this.setState({number: this.state.number+1})}>number={this.state.number}</button>
                <Banner msg={this.msg} num={this.state.number}></Banner>
            </div>
        )
    }
}
export default App

src/Banner.jsx,代码:

import React, {Component} from 'react';
import PropTypes from 'prop-types';

class Banner extends Component {
    render() {
        return (
            <div>
                接收父组件传递来过的数据{this.props.msg}===> {this.props.num}
            </div>
        );
    }
}

// 对外界传递的参数可以设置约束要求:例如:必填,唯一,等其他的要求去。
Banner.propTypes = {
    msg: PropTypes.string.isRequired
}

export default Banner;

验证器

组件名.propTypes = {
  // 可以声明 prop 为指定的 JS 基本数据类型,默认情况,这些数据是可选的
  props属性名: PropTypes.array,  // 数组
  props属性名: PropTypes.bool,   // 布尔值
  props属性名: PropTypes.func,    // 函数
  props属性名: PropTypes.number,   // 数值
  props属性名: PropTypes.object,    // 对象
  props属性名: PropTypes.string,    // 字符串

  // 可以被渲染的对象 numbers, strings, elements 或 array
  props属性名: PropTypes.node,

  //  React 元素
  props属性名: PropTypes.element,

  // 用 JS 的 instanceof 操作符声明 prop 为类的实例。
  props属性名: PropTypes.instanceOf(Message),

  // 用 enum 来限制 prop 只接受指定的值。
  props属性名: PropTypes.oneOf(['News', 'Photos']),

  // 可以是多个对象类型中的一个
  props属性名: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number,
    PropTypes.instanceOf(Message)
  ]),

  // 指定类型组成的数组
  props属性名: PropTypes.arrayOf(PropTypes.number),

  // 指定类型的属性构成的对象
  props属性名: PropTypes.objectOf(PropTypes.number),

  // 特定 shape 参数的对象
  props属性名: PropTypes.shape({
    color: PropTypes.string,
    fontSize: PropTypes.number
  }),

    // 任意类型加上 `isRequired` 来使 prop 不可空。
    props属性名: PropTypes.func.isRequired,

    // 不可空的任意类型
    props属性名: PropTypes.any.isRequired,

    // 自定义验证器。如果验证失败需要返回一个 Error 对象。不要直接使用 `console.warn` 或抛异常,因为这样 `oneOfType` 会失效。
    props属性名: function(props, propName, componentName) {
    if (!/matchme/.test(props[propName])) {
      return new Error('Validation failed!');
    }
  }
}
2.5.4.2 子组件传递数据给父组件

React是单向数据流的。props是只读属性,所以父组件通过props传递进来的数据,子组件是无法修改,只能读取显示。而开发中总会遇到要修改父组件的数据的情况。那么我们就可以通过在父组件中定义一个修改数据(假设fn)的方法,然后通过props把fn方法传递给子组件调用,以此间接达到子组件修改父组件数据的目的。

src/App.jsx,代码:

import React, {Component} from 'react';
import Son1 from "./Son1.jsx";

class App extends Component {
    state = {
        num: 100,
    }
    render() {
        return (
            <div>
                <Son1 changeNum={this.changeNum.bind(this)} num={this.state.num}></Son1>
                <p>num={this.state.num}</p>
            </div>
        )
    }
    changeNum(num){
        this.setState({
            num: num
        })
    }
}

export default App;

子组件,src/Banner.jsx,代码:

import React, {Component} from 'react';

class Son1 extends Component {
    render() {
        return (
            <div>
                <button onClick={()=>this.props.changeNum(this.props.num+1)}>子组件, num={this.props.num}</button>
            </div>
        );
    }
}

export default Son1;

开发中,有时候也可以在父组件中,通过ref来引用子组件对象来调用/修改子组件的状态。但是并不推荐。

src/App.jsx,代码:

import React, {Component} from 'react';
import Son1 from "./Son1.jsx";

class App extends Component {
    state = {
        num: 100,
    }
    son = React.createRef()
    render() {
        return (
            <div>
                <Son1 ref={this.son} changeNum={this.changeNum.bind(this)} num={this.state.num}></Son1>
                <p>num={this.state.num}</p>
                <button onClick={()=>{
                    // 父组件也可以通过ref来引用子组件对象,通过引用对象修改/访问子组件的信息。
                    this.son.current.setState({
                        msg: "來自父组件的关怀!"
                    })
                    this.son.current.show()
                }}>点击修改子组件的数据</button>
            </div>
        )
    }
    changeNum(num){
        this.setState({
            num: num
        })
    }
}

export default App;

src/Son1.jsx,代码:

import React, {Component} from 'react';

class Son1 extends Component {
    state = {
        msg: "hello"
    }
    render() {
        return (
            <div>
                <p>{this.state.msg}</p>
                <button onClick={()=>this.props.changeNum(this.props.num+1)}>子组件, num={this.props.num}</button>
            </div>
        );
    }
    show(){
        console.log("show方法被执行!")
    }
}

export default Son1;
2.5.4.3 属性默认值
import React, {Component} from 'react';
import PropTypes from "prop-types";


class Son1 extends Component {
    // 属性的约束
    static propTypes = {
        msg: PropTypes.string.isRequired
    }
    // 属性的默认值
    static defaultProps = {
        message: "hello, React"
    }
    state = {
        msg: "hello"
    }
    render() {
        return (
            <div>
                <p>{this.state.msg}</p>
                <button onClick={()=>this.props.changeNum(this.props.num+1)}>子组件, num={this.props.num}</button>
                <p>message={this.props.message}</p>
            </div>
        );
    }
    show(){
        console.log("show????")
    }
}

// // 属性约束
// Son1.propTypes = {
//     msg: PropTypes.string.isRequired
// }
//
// // 属性默认值
// Son1.defaultProps = {
//     message: "hello, React"
// }


export default Son1;
2.5.4.4 函数中的props使用

src/App.jsx,代码:

import React, {Component} from 'react';
import Son2 from "./Son2.jsx";


class App extends Component {
    constructor(props) {
        super(props);
    }
    state = {
        num: 10,
    }
    render() {
        return (
            <div>
                <Son2 num={this.state.num}></Son2>
            </div>
        );
    }
}

export default App;

src/Son2.jsx,代码:

import React from 'react';
import PropTypes from "prop-types";

function Son2(props) {  // 实际上,不管函数式组件,还是类组件,第一个参数都是props
    const btn = React.createRef()
    return (
        <div>
            子组件num={props.num}msg={props.msg}
            <input type="text" ref={btn}/>
            <button onClick={()=>{
                console.log(btn.current.value)
            }}>点击按钮</button>
        </div>
    );
}

// 属性约束
Son2.propTypes = {
    num: PropTypes.number.isRequired
}

// 属性默认值
Son2.defaultProps = {
    msg: "msg的默认值"
}

export default Son2;

3. React进阶

3.1 受控与非受控

React中的组件根据是否受外界数据的控制可分为受控组件和非受控组件。

src/App.jsx,代码:

import React, {Component} from 'react';
import Banner from "./Banner.jsx";
class App extends Component {
    state = {
        num: 100
    }
    render() {
        return (
            <div>
                <div style={{border: "1px solid red"}}>
                    <p>num={this.state.num}</p>
                    <button onClick={()=>this.setState({
                        num: this.state.num+1
                    })}>num={this.state.num}</button>
                </div>
                <Banner num={this.state.num}></Banner>
            </div>
        );
    }
}

export default App;

src/Banner.jsx,代码:

import React, {Component} from 'react';

class Banner extends Component {
    state = {
        num: this.props.num
    }
    render() {
        return (
            <div style={{border: "1px solid red"}}>
                <p>轮播</p>
                <button>{this.state.num}</button>
            </div>
        );
    }
}

export default Banner;

受控指的是当前组件完全被 父组件的 state 进行管理的组件,通过 setState 触发组件更新。非受控就是不受父组件的state状态进行管理的组件。也可以换句话说,如果一个组件没有自己的状态,完全受外界调用者组件的props来控制,则该组件为受控组件,否则为非受控组件。开发中我们强调多使用受控组件,少使用非受控组件,尽量避免子组件中拥有自己的state,尽量通过父组件的props来控制。非受控带来的影响,src/Regster.jsx,代码:

import React, {Component} from 'react';

class Register extends Component {
    render() {
        return (
            <div>
                账户<input type="text" name="username" value="admin"/><br/>
                密码<input type="password" name="password" value=""/><br/>
                性别
                <label><input type="checkbox" name="sex" value="1" checked/></label>
                <label><input type="checkbox" name="sex" value="0"/></label>
            </div>
        );
    }
}

export default Register;

3.1.1 非受控

不受控表单的内容即然无法由 state 控制,那么取值就无法通过 state 去获取了。这种情况下也只能交给 refs 去处理了。

import React, {Component} from 'react';

class Register extends Component {
    username = React.createRef()
    password = React.createRef()
    remember = React.createRef()
    render() {
        return (
            <div>
                账号<input type="text" defaultValue="admin" ref={this.username}/><br/>
                密码<input type="password" ref={this.password}/><br/>
                <input type="checkbox" defaultChecked={false} ref={this.remember}/>记住登陆 <br/>
                <button onClick={()=>this.register()}>注册</button>
                <button onClick={()=>this.reset()}>重置</button>
            </div>
        );
    }
    register(){
        console.log("点击注册按钮了,获取数据,提交数据到服务端");
        console.log(`
                username=${this.username.current.value}, 
                password=${this.password.current.value}
                remember=${this.remember.current.checked}`
        )
    }
    reset(){
        this.username.current.value = ""
        this.password.current.value = ""
        this.remember.current.checked = false
    }
}

export default Register;

从上面可以看到,非受控组件并不适合用于处理表单。因此React推荐大多数情况下使用受控组件来处理表单数据。

3.1.2 受控

受控表单,就是采用组件的state状态、value属性与onChange来完成操作。

import React, {Component} from 'react';

class Register2 extends Component {
    state = {
        username: "admin",
        password: "123456",
        sex: true,
    }
    render() {
        return (
            <div>
                账号<input type="text" value={this.state.username} onChange={(event)=>this.setState({
                username: event.target.value
            })}/><br/>
                密码<input type="password" value={this.state.password} onChange={event=>this.setState({
                password: event.target.value
            })}/><br/>
                性别
                <label><input type="checkbox" name="sex" checked={this.state.sex} onChange={event=>this.setState({
                    sex: event.target.checked
                })}/></label>
                <label><input type="checkbox" name="sex" checked={!this.state.sex} onChange={event=>this.setState({
                    sex: !event.target.checked
                })}/></label><br/>
                <button onClick={()=>this.register()}>注册</button>
                <button onClick={()=>this.reset()}>重置</button>
            </div>
        );
    }
    register(){
        console.log("点击注册按钮了,获取数据,提交数据到服务端");
        console.log(this.state)
    }
    reset(){
        this.setState({
            username: "",
            password: "",
            sex: true,
        })
    }
}

export default Register2;

3.2 非父子组件之间的通信

3.2.1 状态提升

把相关联的状态,统一保存共同的父级组件中,通过调用父级组件传递进来的方法,来完成组件之间的通信。

src/App.jsx,代码:

import React, {Component} from 'react';
import Header from "./Header.jsx";
import Footer from "./Footer.jsx";

// 把要多个子组件要共享的数据,保存到这些子组件的公共父级组件,这就是状态提升
class App extends Component {
    state = {
        num: 100,
    }
    render() {
        return (
            <div>
                <Header num={this.state.num} update={this.updateNum.bind(this)}></Header>
                <Footer num={this.state.num} update={this.updateNum.bind(this)}></Footer>
            </div>
        );
    }
    updateNum(num){
        this.setState({
            num: num
        })
    }
}

export default App;

src/Header.jsx,代码:

import React, {Component} from 'react';

class Header extends Component {
    render() {
        return (
            <div>
                <button onClick={()=>this.props.update(this.props.num+1)}>修改数据</button>
                <p>头部组件num={this.props.num}</p>
            </div>
        );
    }
}

export default Header;

src/Footer.jsx,代码:

import React, {Component} from 'react';

class Footer extends Component {
    render() {
        return (
            <div>
                <button onClick={()=>this.props.update(this.props.num+1)}>修改数据</button>
                <p>脚部组件num={this.props.num}</p>
            </div>
        );
    }
}

export default Footer;

3.2.2 发布订阅

基于发布订阅的设计模式来实现。

src/bus.jsx,代码:

const bus = {
    list: [], // 订阅列表
    subscribe(callback){
        bus.list.push(callback)
    },
    publish(value){
        bus.list.forEach(fn=>fn && fn(value))
    }
}

export default bus;

src/App.jsx,代码:

import React, {Component} from 'react';
import Header from "./Header.jsx";
import Footer from "./Footer.jsx";

class App extends Component {
    render() {
        return (
            <div>
                <Header></Header>
                <Footer></Footer>
            </div>
        );
    }
}

export default App;

src/Header.jsx,代码:

import React, {Component} from 'react';
import bus from "./bus.jsx";

class Header extends Component {
    state = {
        num: 100
    }
    componentDidMount() {
        console.log("componentDidMount")
        // 先订阅
        bus.subscribe((value)=>{
            this.setState({
                num: value
            })
        })
    }
    render() {
        console.log("渲染")
        return (
            <div>
                <button onClick={()=>{
                    // 后发布
                    bus.publish(this.state.num+1);
                }}>修改数据</button>
                <p>头部组件num={this.state.num}</p>
            </div>
        );
    }
}

export default Header;

src/Footer.jsx,代码:

import React, {Component} from 'react';
import bus from "./bus.jsx";

class Footer extends Component {
    state = {
        num: 100
    }
    componentDidMount() {
    // 先订阅
        bus.subscribe((value)=>{
            this.setState({
                num: value
            })
        })
    }
    render() {
        return (
            <div>
                <button onClick={()=>{
                    // 后发布
                    bus.publish(this.state.num+1)
                }}>修改数据</button>
                <p>脚部组件num={this.state.num}</p>
            </div>
        );
    }
}

export default Footer;

3.2.3 Context

Context(执行上下文),是React基于生产者与消费者模式设计出来的一种跨组件通信解决方案,可以把Context理解为一个所有组件都可以使用的全局变量。官方推荐当我们不想在多层组件中通过逐层传递props或者state的方式来跨组件传递数据时,使用Context就对了。React context的API有两个版本,React16.x之前的是老版本的context,之后的是新版本的context。这里我们学习的是新版本的ContextAPI。

src/context.jsx,代码:

import React from "react";

const GlobalContext = React.createContext()  // createContext 也可以传递默认值 

export default GlobalContext

src/App.jsx,代码:

import React, {Component} from 'react';
import Header from "./Header.jsx";
import Footer from "./Footer.jsx";
import GlobalContext from "./GlobalContext.jsx";

class App extends Component {
    state = {
        num: 100,
    }
    render() {
        return (
            <GlobalContext.Provider value={{
                num: this.state.num,
                update_num: (value)=>{
                    this.setState({
                        num: value
                    })
                }
            }}>
                <div>
                    <Header></Header>
                    <Footer></Footer>
                </div>
            </GlobalContext.Provider>

        );
    }
}

export default App;

src/Header.jsx,代码:

import React, {Component} from 'react';
import GlobalContext from "./GlobalContext.jsx";

class Header extends Component {
    render() {
        return (
            <GlobalContext.Consumer>
                {
                    (context)=>{
                        return (
                            <div>
                                <button onClick={()=>{
                                    context.update_num(context.num+1);
                                }}>修改数据</button>
                                <p>头部组件num={context.num}</p>
                            </div>
                        )
                    }
                }
            </GlobalContext.Consumer>

        );
    }
}

export default Header;

src/Footer.jsx,代码:

import React, {Component} from 'react';
import GlobalContext from "./GlobalContext.jsx";

class Footer extends Component {
    render() {
        return (
            <GlobalContext.Consumer>
                {
                    (context)=>{
                        return (
                            <div>
                                <button onClick={()=>{
                                    context.update_num(context.num+1)
                                }}>修改数据</button>
                                <p>脚部组件num={context.num}</p>
                            </div>
                        )
                    }
                }
            </GlobalContext.Consumer>
        );
    }
}

export default Footer;

3.3 插槽

在上面的代码中,让GlobalContext.Consumer或者GlobalContext.Provider包含我们编写的代码,这种写法就是React提供的插槽功能。使用插槽,可以让React的组件代码更好的复用,同时也可以在一定程度上避免一些不必要的组件通信代码。

import React, {Component} from 'react';

class Box extends Component {
    render(){
        return (
            <div style={{
                height: "350px",
                width: "600px",
                display: "fiexd",
                top: "50px",
                margin: "auto",
                background: "#eee",
                borderRadius: "8px",
                padding: "15px 25px",
                boxShadow: "0px 15px 10px 5px gray",
            }}>
                {/*提取父组件传递进来的插槽内容*/}
                {this.props.children[0]}
                <hr/>
                {this.props.children.map((item,key)=>{
                    if(key !== 0){
                        return this.props.children[key]
                    }
                })}
            </div>
        )
    }
}

class App extends Component {
    state = {
        num: 100,
        show_box: false
    }
    render() {
        return (
                <div>
                    <button onClick={()=>this.setState({
                        show_box: !this.state.show_box
                    })}>按钮</button>
                    {this.state.show_box && <Box>
                            <h1>标题</h1>
                            <p>对不起您拨打的号码是空号{this.state.num}</p>
                            <p>对不起您拨打的号码是空号{this.state.num}</p>
                            <p>对不起您拨打的号码是空号{this.state.num}</p>
                            <p>对不起您拨打的号码是空号{this.state.num}</p>
                        </Box>
                    }
                </div>
        );
    }
}

export default App;

props.children的值有四种可能情况

  1. 当无内容时,为undefined
  2. 当只有一个组件时为object,即组件实例
  3. 当有多个组件时,返回数组array,即存放每个组件实例的数组。
  4. 当传入不是 react元素为字符串

3.4 生命周期

image-20221117073231165

挂载阶段(Mounting) 自动执行时机 描述
constructor(props) 在组件挂载之前 经常在构造函数里初始化state对象或者给自定义方法绑定this
componentWillMount() 在组件将要挂载 在render执行之前,最后一次修改数据的机会。
React16不再推荐使用。
static getDerivedStateFromProps(props,state) 在组件将要挂载 必须有返回值,返回值将会和state进行合并覆盖。
render() 组件渲染 组件中唯一必须实现的方法,当porps或state发生变化时,都会自动执行。
componentDidMount() 组件挂载完成以后,触发的方法 ajax一般写在这里,还有一些初始化调用,例如订阅,定时调用,监听,基于render渲染完的视图界面进行加工。
更新阶段(Updating) 自动执行时机 描述
componentWillReceiveProps(nextProps, nextContext) 接收到父组件的状态数据更新时 父组件传递的props最先被这个方法所接收,可以在render执行之前完成一些相对的响应逻辑,或者把父组件的props转换成当前组件的state。
React16不再推荐使用。
static getDerivedStateFromProps(props,state) 更新前 必须有返回值,返回值将会和state进行合并覆盖。
可以用于代替componentWillReceiveProps。
shouldComponentUpdate(nextProps, nextState, nextContext) 更新前判断 用于判断当前组件是否要更新,必须返回一个布尔结果,结果为false时,会阻止render执行。属于React提供的一个优化项目性能的方法。
componentWillUpdate(nextProps, nextState, nextContext) 更新前 在更新视图渲染之前会自动执行,基本不使用。
React16不再推荐使用。
getSnapshotBeforeUpdate(prevProps, prevState) render更新后,DOM渲染之前 替换componetnWillUpdate,React会把getSnapshotBeforeUpdate方法中写的DOM操作与之前的DOM操作进行合并比对完成以后,才一次性渲染,减少多余的无意义的渲染。此处无法获取this的指向,值是undefined。
render 组件渲染
componentDidUpdate(prevProps, prevState, snapshot) 更新完成以后 一般可以在这里,等待更新组件渲染完成以后,同步第三方模块的代码。
卸载阶段(Unmounting) 自动执行时机 描述
componentWillUnmount() 当组件要卸载时执行 可以在这里完成一些组件的收尾工作,例如事件解绑,数据同步或回收,关闭监听等操作。
import React, {Component} from 'react';

class Box extends Component{
    state = {
        name: "xiaoming",
        age: 16
    }

    constructor() { // 构造函数,是最早执行的生命周期函数
        super();
        // 最常见的情况:设置初始化state
        console.log("constructor执行了,组件已经创建了")
    }

    // UNSAFE_componentWillMount() { // 挂载前
    //     console.log("componentWillMount执行了,")
    // }

    // static getDerivedStateFromProps(nextProps, prevState) { // 挂载前,更新前
    //     // 注意:
    //     // 1. 必须当前组件设置state状态
    //     // 2. 必须有返回值,返回值会自动和当前的state进行合并覆盖
    //     // 用途:可以把props转换成state状态
    //     console.log("getDerivedStateFromProps执行了,")
    //     return {"name": nextProps.name}
    // }

    // componentDidMount() { // 挂载完成以后
    //     console.log("componentDidMount,组件渲染完成以后自动执行。");
    //     // 1. 页面渲染完成的初始化操作
    //     // 2. 请求服务端数据,ajax
    //     // 3. 监听,定时等等
    //     console.log("p标签=", document.querySelector("p"))
    // }
    //
    // UNSAFE_componentWillReceiveProps(nextProps) {
    //     // 当父组件的state或props发生改变时,都会自动执行,
    //     // 是组件中最早接收到props的方法
    //     console.log("componentWillReceiveProps执行了!!!")
    // }
    //
    // shouldComponentUpdate(nextProps, nextState) { // 更新前判断是否要渲染页面
    //     // React提供的一个优化代码的功能,用于判断当前本次state更新是否有必要进行页面渲染
    //     console.log("shouldComponentUpdate执行了")
    //     // 必须返回一个布尔值作为函数结果,如果返回false,则会阻止render执行
    //     // nextProps   新的属性
    //     // this.props  旧的属性
    //     // nextState   新的状态
    //     // this.state  旧的状态
    //     // 优化判断,避免不必要的子组件渲染行为
    //     if(JSON.stringify(this.state) === JSON.stringify(nextState)){
    //         return false
    //     }
    //
    //     return true
    // }

    // UNSAFE_componentWillUpdate(nextProps, nextState) { // 更新前
    //     console.log("componentWillUpdate,更新前操作")
    // }

    // getSnapshotBeforeUpdate(prevProps, prevState) { // DOM更新完成,渲染页面之前
    //     console.log("getSnapshotBeforeUpdate执行!")
    //     // 1. 必须有返回值,可以是null,或者Snapshot
    //     // 2. 是在render运行过程中,已经更新了DOM树以后,在渲染之前执行的。
    //     // 3. 作用:可以在渲染之前完成DOM操作,React则会帮当前DOM操作与之前的DOM进行合并比对完成以后,再一同进行渲染,避免无意义的渲染,达到优化的目的
    //     console.log(document.querySelector('#btn').innerHTML) // 此处可以看到页面没有渲染
    //     return null
    // }
    //
    // componentDidUpdate(prevProps, prevState) { // 更新完成
    //     console.log("componentDidUpdate渲染完成。。。")
    // }

    componentDidMount() {
        this.timer = setInterval(()=>console.log("AAA"), 500)
    }

    componentWillUnmount() { // 组件卸载之前
        console.log("componentWillUnmount执行了!")
        clearInterval(this.timer)
    }

    render() {
        // 尽量不要修改DOM或者修改state的操作,否则大概率出错
        // this.setState({ // 异步更新状态
        //     age: this.state.age+1
        // })
        console.log("render执行,页面渲染了")
        return (
            <div>
                <button id="btn" onClick={()=>this.setState({
                    age: this.state.age+1
                })}>age={this.state.age},num={this.props.num}</button>
                <p>{this.state.name}</p>
            </div>
        );
    }
}

class App extends Component {
    state = {
        num: 10,
    }
    render() {
        return (
            <div>
                <button onClick={()=>this.setState({
                    num: this.state.num+1
                })}>num={this.state.num}</button>
                { (this.state.num % 2) && <Box name="小白" num={this.state.num}></Box>}
            </div>
        )
    }
}

export default App;

注意:

函数式组件是没有生命周期的。

3.5 axios

是一个实现ajax前后端交互,发送http请求的一个开源模块。

yarn add axios@next
# npm i axios@next

3.5.1 基本使用

获取天气

import React, {Component} from 'react';
import axios from "axios";
import "./style.css"
// rest风格操作
// axios.post(url="",data={},options={})  // 创建、新增、上传
// axios.get(url="", options={})          // 读取、拉取、下载
// axios.put(url="",data={},options={})   // 更新数据[整体数据] 修改用户信息[age, nickname, avatar]
// axios.patch(url="",data={},options={}) // 更新数据[部分数据] 修改密码,更换头像[单一字段的修改]
// axios.delete(url="", options={})       // 删除、废弃、移除、清空
// 返回值 promise对象,异步回调对象

class App extends Component {
    state = {
        city: "北京",
        weather_data: [],
    }

    render() {
        return (
            <div>
                <input type="text" value={this.state.city} onChange={(event)=>this.setState({
                    city: event.target.value
                })}/>
                <button onClick={()=>this.get_weather()}>查询</button>
                <table style={{
                    width: "600px",
                    border: "1px solid red"
                }}>
                    <tbody>
                        <tr style={{border: "1px solid red"}}>
                            <td>日期</td>
                            <td>情报</td>
                        </tr>
                        {
                            this.state.weather_data.map((item, key)=>
                                <tr key={key}>
                                    <td>{item.date}</td>
                                    <td>{item.day.narrative}</td>
                                </tr>
                            )
                        }
                    </tbody>
                </table>
            </div>
        )
    }

    get_weather(){
        // 获取指定地区的天气信息
        axios.get(`https://v0.tianqiapi.com/?version=day&unit=m&language=zh&query=${this.state.city}&appid=43656176&appsecret=I42og6Lm`).then(response=>{
            console.log(response.data.month)
            this.setState({
                weather_data: response.data.month
            });
        })
    }
}

export default App;

基于生命周期和axios完成一个小案例

public/data.json,代码:

[
  {"id": 1, "name": "小红", "age":  17, "money": 640 },
  {"id": 2, "name": "小名", "age":  15, "money": 550 },
  {"id": 3, "name": "小里", "age":  14, "money": 600 },
  {"id": 5, "name": "小牛", "age":  17, "money": 300 },
  {"id": 6, "name": "小白", "age":  12, "money": 0 },
  {"id": 17, "name": "小辉", "age":  21, "money": 100 }
]

App.jsx,代码:

import React, {Component} from 'react';
import axios from "axios";
import "./style.css"

class App extends Component {
    state = {
        student_list: [],
    }
    componentDidMount() {
        // 获取数据
        this.get_data()
    }

    get_data(city){
        axios.get("/data.json").then(response=>{
            console.log(response.data)
            this.setState({
                student_list: response.data
            })
        })
    }

    render() {
        return (
            <div>
                <table style={{
                    width: "600px",
                    border: "1px solid red"
                }}>
                    <tbody>
                    <tr style={{border: "1px solid red"}}>
                        <td>编号</td>
                        <td>姓名</td>
                        <td>年龄</td>
                        <td>余额</td>
                    </tr>
                    {
                        this.state.student_list.map((student, key)=>
                            <tr key={key}>
                                <td>{student.id}</td>
                                <td>{student.name}</td>
                                <td>{student.age}</td>
                                <td>{student.money}</td>
                            </tr>
                        )
                    }
                    </tbody>
                </table>
            </div>
        )
    }
}

export default App;

3.6 hooks

在过往的React版本(16.8以前)中,组件之间复用状态逻辑很难,在hooks出现之前,实现组件复用一般采用高阶组件和 Render Props,它们本质是将复用逻辑提升到父组件中,很容易产生很多包装组件,带来嵌套地域。而且随着开发项目的不断完善,组件逻辑也会变得越来越复杂,尤其是生命周期函数中常常包含一些不相关的逻辑,完全不相关的代码却在同一个方法中组合在一起。如此很容易产生 bug,并且导致逻辑不一致。使用class组件,还需要开发人员随时注意 this 的指向,不能忘记绑定事件处理器等操作,代码复杂且冗余。除此之外,class组件也会让一些react优化措施失效。针对上面提到的问题,react团队研发了hooks。

Hooks(钩子),就是React在16.8版本以后推出的一类特殊的函数,只能用于函数式组件中,Hooks解决了类组件使用过程中的一些缺陷和性能问题,让开发者在不使用类组件的情况下使用更多react特性,主要有两方面作用:

  • 用于在函数式组件中引入状态管理和生命周期方法(不是真的生命周期,而是类似生命周期的功能,可以使用Hooks来完成)。
  • 取代高阶组件和render props来实现抽象和可重用性

3.6.1 useState

用于让函数式组件实现有状态组件。实现状态初始化。

import React,{useState} from 'react';

function App() {  // 函数式视图的函数体就相当于类组件的render
    // const [状态变量名, 修改状态的唯一函数] = useState(状态初始值)
    const [count, setCount] = useState(0)
    return (
        <div>
            {/* setCount(count+1) ==> setState({count: count+1})*/}
           <button onClick={()=>setCount(count+1)}>{count}</button>
        </div>
    );
}

export default App;

3.6.2 useEffect

useEffect可以用来处理具有副作用的操作,最常见的就是向服务器请求数据。useEffect接收2个参数,参数1为函数,参数2是数组,useEffect会在组件渲染到屏幕之后才执行。useEffect可以使用多次,所以不同的副作用操作,应该分多个useEffect来处理。

所谓的副作用,运行以下代码,查看浏览器的network可以发现异步操作中对属性/状态进行修改时都会产生副作用。

import React,{useState} from 'react';
import axios from "axios";

function App() { // render
    const [data, setData] = useState([]) // 参数是默认值

    // axios.get("/data.json").then(response=>{
    //     setData(response.data)
    // })

    // 异步代码中修改属性/state的操作,就是副作用操作
    setTimeout(()=>{
        setData([{"id":1, "name": "小黄"}])
        console.log("11111")
    }, 2000)

    return (
        <div>
            <ul>
                {data.map((item,key)=>
                    <li key={key}>id={item.id}, name={item.name}</li>
                )}
            </ul>
        </div>
    );
}

export default App;

没有任何依赖的副作用代码,useEffect只执行一次。

import React,{useState, useEffect} from 'react';
import axios from "axios";

function App() { // render
    const [data, setData] = useState([]) // 参数是默认值

    // 参数1,就是副作用代码,
    // 参数2,就医副作用依赖,
    // 如果参数2是一个空数组,则表示当前代码是没有任何外部依赖的副作用代码,只需要执行一次。
    useEffect(()=>{
        axios.get("/data.json").then(response=>{
            setData(response.data)
        })
    },[])

    useEffect(()=>{
        // 异步代码中修改属性/state的操作,就是副作用操作
        setTimeout(()=>{
            setData([{"id":1, "name": "小黄"}])
            console.log("11111")
        }, 500)
    },[])

    return (
        <div>
            <ul>
                {data.map((item,key)=>
                    <li key={key}>id={item.id}, name={item.name}</li>
                )}
            </ul>
        </div>
    );
}

export default App;

依赖状态的副作用代码,useEffect要根据依赖的变化来响应执行。

import React,{useState, useEffect} from 'react';
import axios from "axios";

function App() { // render
    const [city, setCity] = useState("北京") // 参数是默认值
    const [weatherData, setWeatherData] = useState([])

    useEffect(() => {
        axios.get(`https://v0.tianqiapi.com/?version=day&unit=m&language=zh&query=${city}&appid=43656176&appsecret=I42og6Lm`).then(response=>{
            console.log(response.data.city)
            if(response.data.month){
                setWeatherData(response.data.month)
            }
        }).catch(error=>{
            setWeatherData([])
        })
    }, [city]);

    return (
        <div>
            <input type="text" value={city} onChange={(event)=>setCity(event.target.value)}/>
            <ul>
                {weatherData.length > 0 && weatherData.map((item,key)=>
                    <li key={key}>{item.date} {item.dateOfWeek} {item.day.narrative}</li>
                )}
            </ul>
        </div>
    );
}

export default App;

设置useEffect的返回值可以实现类数组中的componentWillUnmount的作用。

import React,{useState, useEffect} from 'react';
function Box(){
    useEffect(() => {
        let num = 1
        let timer = setInterval(() => {
            console.log(num+=1)
        },500)

        return () => {
            clearInterval(timer)
        };
    }, []);

    return <div>box</div>
}
function App(props) {
    const [showBox, setShowBox] = useState(false);
    return (
        <div>
            <button onClick={()=>setShowBox(!showBox)}>点击</button>
            {showBox && <Box></Box>}
        </div>
    );
}

export default App;

副作用钩子还有另一个useLayoutEffect,两个用法一样,只是执行时机不同,useLayoutEffect在react完成DOM更新后立刻同步调用执行,会阻塞页面渲染,而useEffect则是在整个页面渲染完成以后才会执行。尽可能使用标准的 useEffect 以避免阻塞视图更新。

3.6.3 useMemo

记忆组件,防止因为组件重新渲染导致函数被反复创建或数据被反复计算,起到缓存的作用。只有当useMemo的第二个参数发生变化时,被缓存起来的函数才会被重新声明(同理,被缓存起来的数据结果也是一样)。同时还可以起到计算属性的作用(计算属性就是一个结果变量,变量会因为数据的变化而变化)。

要理解useMemo的使用,首先我们要清楚,父组件的重新渲染或当前组件的状态发生改变时,都会导致当前组件重新渲染

import React, {useState} from 'react';
function Box () {
    const [count, setCount] = useState(1);
    return (
        <div>
            <p>随机数{Math.random()}</p>
            <button onClick={() => setCount(count + 1)}>Box子组件的按钮count={count}</button>
        </div>
    )
}

function App(){
    const [count, setCount] = useState(10);
    return (
        <div>
            <button onClick={() => setCount(count + 1)}>App组件的按钮,count={count}</button>
            <Box></Box>
        </div>
    )
}

export default App;

所以React为了提升代码性能,避免不必要的重复计算或重复声明,提供了useState、useReducer、useCallback、useMemo这些钩子让我们可以在函数式组件中,对变量、函数、数据结果、组件对象进行缓存。

以下代码可以看到useState会缓存初始值的。

import React, {useState} from 'react';
function Box () {
    const [count, setCount] = useState(Math.random());
    return (
        <div>
            <p>随机数{Math.random()}</p>
            <button onClick={() => setCount(count + 1)}>Box子组件的按钮count={count}</button>
        </div>
    )
}

function App(){
    const [count, setCount] = useState(10);
    return (
        <div>
            <button onClick={() => setCount(count + 1)}>App组件的按钮,count={count}</button>
            <Box></Box>
        </div>
    )
}

export default App;

除了缓存变量以外,也可以使用useMemo或者useCallback缓存计算结果。

import React, {useState, useMemo} from 'react';
function Box () {
    const [count, setCount] = useState(Math.random());
    const [val, setVal] = useState("");
    const func = Math.random()
    const func_cache = useMemo(()=>Math.random(),[val])
    return (
        <div>
            <p>随机数{func}</p>
            <p>随机数[useMemo]{func_cache}</p>
            <p>val=<input type="text" value={val} onChange={(event=>setVal(event.target.value))}/></p>
            <button onClick={() => setCount(count + 1)}>Box子组件的按钮count={count}</button>
        </div>
    )
}

function App(){
    const [count, setCount] = useState(10);
    return (
        <div>
            <button onClick={() => setCount(count + 1)}>App组件的按钮,count={count}</button>
            <Box></Box>
        </div>
    )
}

export default App;

甚至可以使用useMemo或者useCallback缓存函数、组件等等。

import {useState, useMemo} from "react";

function App() {
    const [state, setState] = useState("初始值");
    //普通函数
    const fn = () => {
        console.log("普通函数:", state);
    };

    //记忆函数,这里第二个参数设置为[],表示不依赖任何值,只在组件初始化时创建mfn,组件更新时不更新mfn
    const mfn = useMemo(() => {
        return ()=>console.log("记忆函数:", state)
    }, []);

    //组件Home,mount 和 update时都执行
    fn();
    mfn();

    const update = () => {
        setState(Math.random())
    };

    return (
        <div>
            <div>state值:{state}</div>
            <button onClick={update}>改变state</button>
        </div>
    );
}

export default App;

实现计算属性效果,就是新声明一个保存计算结果的变量,这个变量会随着参数计算的数据的变化而改变。

import React, {useMemo, useState} from 'react';

// 父组件
function App(){
    const [num1, setNum1] = useState(0);
    const [num2, setNum2] = useState(0);

    // 计算属性
    const total = useMemo(() => {
        let n1 = isNaN(parseInt(num1))?0:parseInt(num1)
        let n2 = isNaN(parseInt(num2))?0:parseInt(num2)
        return n1+n2
    }, [num1, num2]);

    return (
        <div>
            <input type="text" value={num1} onChange={(event)=>setNum1(event.target.value)}/>
            +
            <input type="text" value={num2} onChange={(event)=>setNum2(event.target.value)}/>
            = {total}
        </div>
    )
}

export default App;

3.6.4 useCallback

useCallback 可以理解为 useMemo 的语法糖,两者的底层实现原理是一样的,主要区别是 React.useMemo 将调用 fn 函数并返回其结果,而 React.useCallback 将返回 fn 函数而不调用它。因此开发中,使用useMemo缓存变量或组件,而使用useCallback缓存函数。

import React, {useMemo, useState, useCallback} from 'react';

// 父组件
function App(){
    const [num1, setNum1] = useState(0);
    const [num2, setNum2] = useState(0);

    // 计算属性
    const total1 = useMemo(() => {
        let n1 = isNaN(parseFloat(num1))?0:parseFloat(num1)
        let n2 = isNaN(parseFloat(num2))?0:parseFloat(num2)
        return n1+n2
    }, [num1, num2]);

    const total2 = useCallback((dot) => {
        let n1 = isNaN(parseFloat(num1))?0:parseFloat(num1)
        let n2 = isNaN(parseFloat(num2))?0:parseInt(num2)
        return (n1+n2).toFixed(2)
    }, [num1, num2]);

    return (
        <div>
            <input type="text" value={num1} onChange={(event)=>setNum1(event.target.value)}/>
            +
            <input type="text" value={num2} onChange={(event)=>setNum2(event.target.value)}/>
            <br/>Memo = {total1.toFixed(2)}
            <br/>Callback = {total2(2)}
        </div>
    )
}

export default App;

缓存函数

import React, {useState} from 'react';
import {useCallback} from "react";

// 父组件
function App(){
    const [state, setState] = useState("初始值");
    //普通函数
    const fn = ()=> {
        console.log("普通函数:", state);
    };

    //记忆函数(缓存函数),这里第二个参数设置为[],表示不依赖任何值,只在组件初始化时创建mfn,组件更新时不更新mfn
    const mfn = useCallback(() => {
        console.log("记忆函数:", state);
    }, []);

    //组件Home,mount 和 update时都执行
    fn();
    mfn();

    return (
        <div>
            <div>state值:{state}</div>
            <button onClick={() => {
                setState(Math.random())
            }}>改变state</button>
        </div>
    )
}

export default App;

也可以实现计算属性的效果。

import React, {useState, useCallback} from 'react';
function App () {
    const [num1, setNum1] = useState(0);
    const [num2, setNum2] = useState(0);
    const total = useCallback(()=>{
        let n1 = isNaN(parseInt(num1))?0:parseInt(num1)
        let n2 = isNaN(parseInt(num2))?0:parseInt(num2)
        return n1+n2
    },[num1,num2])
    return (
        <div>
            <input type="text" size="1" value={num1} onChange={(event)=>setNum1(event.target.value)}/>
            +
            <input type="text" size="1" value={num2} onChange={(event)=>setNum2(event.target.value)}/>
            ={total()}
        </div>
    )
}

export default App;

3.6.5 useRef

实际上就是类组件的ref的实现,也是用于获取组件对象、DOM节点的引用。

import {useRef} from "react";
function App() {
    const username = useRef(null);
    return (
        <div>
            <input ref={username} type="text"/>
            <button onClick={()=>username.current.value=""}>重置</button>
        </div>)
}

export default App;

3.6.6 userContext

学习userContext之前,首先要清楚,我们学习过的Context在函数式组件中的用法与类组件的用法是一致的。

import React, {useState, createContext} from "react";

const GlobalContext = createContext()

function App() {
    const [num, setNum] = useState(100);
    return (
        <div>
            {
                <GlobalContext.Provider value={{
                    num,
                    setNum,
                }}>
                    <Header></Header>
                    <Footer></Footer>
                </GlobalContext.Provider>
            }
        </div>)
}

function Header(){
    return (
        <GlobalContext.Consumer>
            {
                (context)=>{
                    return (
                        <div>
                            <h1>头部组件</h1>
                            <button onClick={()=>context.setNum(context.num+1)}>{context.num}</button>
                        </div>
                    )
                }
            }
        </GlobalContext.Consumer>

    )
}

function Footer(){
    return (
        <GlobalContext.Consumer>
            {
                (context)=>{
                    return (
                        <div>
                            <h1>脚部组件</h1>
                            <button onClick={()=>context.setNum(context.num+1)}>{context.num}</button>
                        </div>
                    )
                }
            }
        </GlobalContext.Consumer>
    )
}

export default App;

上面的使用,可以看到Context提供的Provider(生产者)与Consumer(消费者)组件在函数式组件的用法是一模一样的。但是,上面的嵌套层级很多人感觉太深了,所以React提供了useContext 这个hooks钩子,它可以接受一个 context 对象(从 React.createContext 返回的值)并返回当前 context 值,所以useContext的目的就是减少子组件的代码嵌套

import React, {Component, useContext, useState} from 'react';

// 创建一个上下文对象
const GlobalContext = React.createContext();

function App() {
    const [num, setNum] = useState(100);
    return (
        <GlobalContext.Provider value={{
            num: num,
            update_num: (value)=>{
                setNum(value)
            }
        }}>
            <div>
                <Header></Header>
                <Footer></Footer>
            </div>
        </GlobalContext.Provider>

    );
}

function Footer() {
    const context = useContext(GlobalContext);
    // return (
    //     <GlobalContext.Consumer>
    //         {
    //             (context)=>{
                    return (
                        <div>
                            <button onClick={()=>{
                                context.update_num(context.num+1)
                            }}>修改数据</button>
                            <p>脚部组件num={context.num}</p>
                        </div>
                    )
    //             }
    //         }
    //     </GlobalContext.Consumer>
    // )
}

function Header() {
    const context = useContext(GlobalContext);
    // return (
    //     <GlobalContext.Consumer>
    //         {
    //             (context)=>{
                    return (
                        <div>
                            <button onClick={()=>{
                                context.update_num(context.num+1);
                            }}>修改数据</button>
                            <p>头部组件num={context.num}</p>
                        </div>
                    )
    //             }
    //         }
    //     </GlobalContext.Consumer>
    // );
}

export default App;

3.6.7 userReducer

userReducer相当于useState的升级版,useState的替代方案。功能作用上类似useState,都是保存状态,不同点在于可以定义一个reducer的函数,用来处理复杂数据。目的是为了解决函数式组件中逻辑代码与视图代码耦合性太高的情况。

import React, {useReducer, useState} from "react";
const reducer = (state, action)=>{
    // state状态, action动作、信号
    console.log("state=", state, "action=", action)
    const newState = {...state}
    switch (action.type) {
        case "add":
            newState.num+=action.val;
            break
        case "sub":
            newState.num-=action.val;
            break
    }
    // reducer必须要返回一个新状态
    return newState
}

// 状态的初始值
const initialState = 10

// 状态的包装函数[对状态的初始值进行加工处理]
const init = (initialState)=>{
    return {
        "num": initialState,
        "name": "xiaoming"
    }
}

function Header(){
    // 每个组件内部都可以使用一个或多个useReducer,每个组件的useReducer状态都是独立的。
    const [state, dispatch] = useReducer(reducer, initialState, init);
    return (
        <div>
            <h1>Header组件</h1>
            <button onClick={()=>dispatch({type: "add", val: 1})}>增加num={state.num}</button>
            <button onClick={()=>dispatch({type: "sub", val: 1})}>减少num={state.num}</button>
        </div>
    )
}

function App() {
    // const [state, setState] = useState(10);
    // useReducer 的返回值是数组,成员分别是状态,分发函数
    // reducer 修改状态的函数,函数必须提供2个参数
    // initialState 状态的初始化值
    // init  状态的初始化值的包装器函数,必须提供一个参数
    const [state, dispatch] = useReducer(reducer, initialState, init);
    return (
        <div>
            <h1>App组件</h1>
            <button onClick={()=>dispatch({type: "add", val: 1})}>增加num={state.num}</button>
            <button onClick={()=>dispatch({type: "sub", val: 1})}>减少num={state.num}</button>
            <hr/>
            <Header></Header>
        </div>
    )
}

export default App;

结合useContext完成跨组件传递数据

import React, {useReducer, createContext, useContext} from "react";

const GlobalContext = createContext()

const reducer = (state, action)=>{
    const newState = {...state}
    switch (action.type) {
        case "add":
            newState.num = action.val

    }
    return newState
}

const initialState = 100

const init = (initialState)=>{
    return {
        "num": initialState
    }
}

function App() {
    // const [num, setNum] = useState(100);
    const [state, dispatch] = useReducer(reducer, initialState, init);
    return (
        <div>
            {
                <GlobalContext.Provider value={{
                    state,
                    dispatch,
                }}>
                    <Header></Header>
                    <Footer></Footer>
                </GlobalContext.Provider>
            }
        </div>)
}

function Header(){
    const {state, dispatch} = useContext(GlobalContext)
    return (
        <div>
            <h1>头部组件</h1>
            <button onClick={()=>dispatch({type: "add", val: state.num+1})}>{state.num}</button>
        </div>
    )
}

function Footer(){
    const {state, dispatch} = useContext(GlobalContext)
    return (
        <div>
            <h1>脚部组件</h1>
            <button onClick={()=>dispatch({type: "add", val: state.num+1})}>{state.num}</button>
        </div>
    )
}

export default App;

3.6.8 自定义hook

自定义hook是一个函数,其名称以use 开头,自定义hook内部可以调用其他的hook(普通函数是不能调用hook),还可以将某些组件逻辑提取到可重用的hook函数中,让代码结构更加清晰,易维护。例如,可以把axios操作进行封装成自定义的hook进行使用。

import React, {useState, useEffect} from 'react';
import axios from "axios";

const useInput = (initialValue) => {
    const [value, setValue] = useState(initialValue)
    return {
        value,
        onChange: e => setValue(e.target.value)
    }
}

function useAxiosGet(url) {
    const [ data, setData ] = useState({})
    const [ error, setError ] = useState({})
    useEffect(() => {
        // get请求
        axios.get(url)
            .then(response => setData(response))
            .catch(error => setError(error))
    }, [url])  // 注意这里要传入参数url,代表url改变的时候才触发
    return [data, error] // 直接返回变量
}

function useTable(city, data){
    return (
        <>
            <input type="text" {...city}/>
            <table style={{
                width: "600px",
                border: "1px solid red"
            }}>
                <tbody>
                <tr style={{border: "1px solid red"}}>
                    <td>日期</td>
                    <td>情报</td>
                </tr>
                {
                    data?.data?.month?.map((item, key)=>
                        <tr key={key}>
                            <td>{item.date}</td>
                            <td>{item.day.narrative}</td>
                        </tr>
                    )
                }
                </tbody>
            </table>
        </>
    )
}

function App(){
    const city = useInput("北京")
    const [data, error] = useAxiosGet(`https://v0.tianqiapi.com/?version=day&unit=m&language=zh&query=${city.value}&appid=43656176&appsecret=I42og6Lm`)
    const ui = useTable(city, data)
    return (
        <div>
        {ui}
        </div>
    )
}


export default App;

3.7 Router

项目开发过程中,很多时候需要提供多个页面给用户展示,为了方便用户通过浏览切换不同的页面效果,往往需要使用路由把页面组件与不同的URL路径进行映射。用户访问不同的URL就可以切换到不同的用户界面。

官网:https://reactrouter.com/en/main

React Router库包含三个不同的npm包,以下每个包都有不同的用途。

  • react-router 核心组件
  • react-router-dom 应用于浏览器端的路由库(单独使用包含了react-router的核心部分)
  • react-router-native 应用于native端的路由
yarn add react-router-dom@next   # 目前最新版本是6.x

# npm i react-router-dom@next 

3.7.1 快速使用

src/main.jsx,代码:

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from "./App.jsx";
import {BrowserRouter} from "react-router-dom";

ReactDOM.createRoot(document.getElementById('root')).render(
    <BrowserRouter>
        <App />
    </BrowserRouter>
)

src/App.jsx,代码:

import React from 'react';
import {Routes, Route} from "react-router-dom";
import Home from "./views/Home.jsx";
import Login from "./views/Login.jsx";
import Register from "./views/Register.jsx";
import User from "./views/User.jsx";
function App() {
    return (
            <div>
                <Routes>
                    <Route path="/" element={<Home/>} />
                    <Route path="/home" element={<Home/>}/>
                    <Route path="/login" element={<Login/>}/>
                    <Route path="/register" element={<Register/>}/>
                    <Route path="/user" element={<User/>}/>
                </Routes>
            </div>
    )
}

export default App;

src/views/Home.jsx,代码:

import React from 'react';

function Home(props) {
    return (
        <div>
            <h1>首页组件</h1>
        </div>
    );
}

export default Home;

src/views/Login.jsxsrc/views/Register.jsxsrc/views/User.jsx,复制Home组件内容,修改下组件名和显示内容即可。

上面的方式虽然已经实现了路由功能,但是实际项目中,我们可以单独准备一个路由表出来把所有的路径与组件关系映射起来,然后再绑定到React中,React提供了useRouter hook可以帮我们实现这个操作。

注意:

如果项目使用了useRouter来提供路由服务,则这个React项目的组件最好都采用函数式组件来开发。因为react-router-dom在6.x版本中,提供了很多hook给我们进行路由操作。

src/routes.jsx,代码:

import Home from "./views/Home.jsx";
import Login from "./views/Login.jsx";
import Register from "./views/Register.jsx";
import User from "./views/User.jsx";

export const routes = [
    {
        path: "/",
        element: <Home/>,
    },
    {
        path: "/login",
        element: <Login/>,
    },
    {
        path: "/register",
        element: <Register/>,
    },
    {
        path: "/user",
        element: <User/>,
    },
];

src/App.jsx,代码:

import React from 'react';
import { useRoutes } from 'react-router-dom'
// 导入编写好的路由映射表
import {routes} from './routes.jsx'

function App() {
    return (
            <div>
                {useRoutes(routes)}
            </div>
    )
}

export default App;

3.7.2 页面跳转

src/App.jsx,代码:

import React from "react";
import {useRoutes, Link, NavLink, useNavigate} from "react-router-dom";
// 导入编写好的路由映射表
import {routes} from './routes.jsx'
import "./style.css"

// 推荐的主旨:低耦合
function App(){
    const navigate = useNavigate()
    return (
        <div>
            <div style={{
                background: "#eee",
                height: "50px",
                display: "flex",
                lineHeight: "50px",
            }}>
                {/*高亮路由链接组件*/}
                <NavLink to="/">站点首页</NavLink>
                <NavLink to="/user">用户中心</NavLink>
                <NavLink to="/login">登陆页面</NavLink>
                <NavLink to="/register">注册页面</NavLink>
            </div>
            <div style={{
                background: "#eee",
                height: "50px",
                display: "flex",
                lineHeight: "50px",
            }}>
                {/*普通路由链接组件*/}
                <Link to="/">站点首页</Link>
                <Link to="/user">用户中心</Link>
                <Link to="/login">登陆页面</Link>
                <Link to="/register">注册页面</Link>
            </div>
            <div style={{
                background: "#eee",
                height: "50px",
                display: "flex",
                lineHeight: "50px",
            }}>
                {/*普通路由链接组件*/}
                <a onClick={()=>navigate("/")}>站点首页</a>
                <a onClick={()=>navigate("/user")}>用户中心</a>
                <a onClick={()=>navigate("/login")}>登陆页面</a>
                <a onClick={()=>navigate("/register")}>注册页面</a>
            </div>
            {useRoutes(routes)}
        </div>
    )
}

export default App;

src/style.css,代码:

a {
    flex: 1;
    text-decoration: none;
    color: black;
    cursor: pointer;
}
a:hover, a.active{
    color: blue
}

当然,我们可以在用户跳转的时候进行判断拦截。

3.7.3 路由参数

Router组件提供了多种传递数据的方式给我们使用,开发中常用的方式有param(路径参数)、search(查询参数)。

3.7.3.1 param参数

路径参数,也叫路由参数,使用了URL路径的一部分作为动态参数传递到下一个路由中。

src/routes.jsx,代码:

import Home from "./views/Home.jsx";
import Login from "./views/Login.jsx";
import Register from "./views/Register.jsx";
import User from "./views/User.jsx";

export const routes = [
    {
        path: "/",
        element: <Home/>, // 5.x component
    },
    {
        path: "/home",
        element: <Home/>, // 5.x component
    },
    {
        path: "/login",
        element: <Login/>,
    },
    {
        path: "/register",
        element: <Register/>,
    },
    {
        path: "/user/:id/:info",
        element: <User/>,
    },
];

src/views/Home.jsx,代码:

import React from 'react';
import {Link} from "react-router-dom";

function Home(props) {
    return (
        <div>
            <h1>首页组件</h1>
            <Link to="/user/100/pic">用户: /user/100/pic</Link>
            <hr/>
            <Link to="/user/40/adderss">用户: /user/40/adderss</Link>
        </div>
    );
}

export default Home;

可以使用useParams 这个hook来接受路径参数。

src/views/User.jsx,代码:

import React from 'react';
import {useParams} from "react-router-dom";
function User(props) {
    const params = useParams()
    return (
        <div>
            <h1>用户中心</h1>
            <p>id= {params.id}</p>
            <p>info= {params.info}</p>
        </div>
    );
}

export default User;

查询参数,也叫查询字符串参数。

src/Home.jsx,代码:

import React from 'react';
import {Link} from "react-router-dom";

function Home(props) {
    return (
        <div>
            <h1>首页组件</h1>
            {/*路径参数*/}
            <Link to="/user/100/pic">用户: /user/100/pic</Link>
            <br/>
            <Link to="/user/40/adderss">用户: /user/40/adderss</Link>
            <hr/>
            {/*查询参数*/}
            <Link to="/login?state=/">登陆来源: Home</Link>
            <br/>
            <Link to="/login?state=/user/6/pic">登陆来源: User</Link>
        </div>
    );
}

export default Home;

src/Login.jsx,代码:

import React from 'react';
import {useSearchParams, Link, useLocation} from "react-router-dom";

function Login(props) {
    const [search, setSearch] = useSearchParams()
    const location = useLocation()
    console.log(location)
    return (
        <div>
            <h1>登陆页面</h1>
            <p>登陆成功以后页面进行跳转
                <Link to={search.get("state")}>{search.get("state")}</Link>
            </p>
        </div>
    );
}

export default Login;

3.7.4 嵌套路由

可以直接在路由映射表中使用 children 选项实现嵌套路由。

src/routes.jsx,代码:

import Home from "./views/Home.jsx";
import Login from "./views/Login.jsx";
import Register from "./views/Register.jsx";
import User from "./views/User.jsx";
import Info from "./views/Info.jsx";
import Pic from "./views/Pic.jsx";

export const routes = [
    {
        path: "/",
        element: <Home/>, // 5.x component
    },
    {
        path: "/home",
        element: <Home/>, // 5.x component
    },
    {
        path: "/login",
        element: <Login/>,
    },
    {
        path: "/register",
        element: <Register/>,
    },
    {
        path: "/user/:id",
        element: <User/>,
        children: [ // 被嵌套的路径的path路径不能以 / 开头
            {path: '', element: <Info/>},
            {path: 'info', element: <Info/>},
            {path: 'pic', element: <Pic/>}
        ]
    },
];

在父组件中,使用Outlet路由占位符组件,表示"路由映射表"中匹配的子路由应对的组件将在此处展示

src/User.jsx,代码:

import React from 'react';
import {NavLink, useParams, Outlet} from "react-router-dom";
function User(props) {
    const params = useParams()
    return (
        <div>
            <h1>用户中心</h1>
            <p>id= {params.id}</p>
            <div style={{
                background: "#eee",
                height: "50px",
                display: "flex",
                lineHeight: "50px",
            }}>
                <NavLink to={`/user/${params.id}/`}>基本信息</NavLink>
                <NavLink to={`/user/${params.id}/pic`}>相册中心</NavLink>
            </div>
            <Outlet></Outlet>
        </div>
    );
}

export default User;

src/views/Info.jsx,代码:

import React from 'react';

function Info(props) {
    return (
        <div>用户基本信息页面</div>
    );
}

export default Info;

src/views/Pic.jsx组件代码复制Info.jsx即可,修改组件名和显示内容。

3.8 Redux

官方:https://redux.js.org/introduction/getting-started

中文:https://cn.redux.js.org/

redux是一个专门用于做状态管理的纯js库(这意味redux并不依赖于react,也可以在其他框架中进行使用,例如vue等)。redux可以让我们集中式的管理recat应用中多个组件共享的状态。

image-20221118082647129

3.8.1 核心概念

  • action
  • 动作的对象,包含2个属性
    • type:标识属性,值为字符串,唯一,必要属性
    • data:数据属性,值为任意类型,可选属性
    • 例子:{type:'add_student',date:{name:'tom', age:18}}
  • reducer
  • 用于初始化状态、加工状态,加工时,根据旧的state和action,产生新的state纯函数
  • store
  • 将state、action、reducer联系在一起的对象,它内部维护着:state、reducer。
  • store就是把action和reducer联系到一起的对象,store本质上是一个状态树,保存了所有对象的状态。任何UI组件都可以直接从store访问特定对象的状态,其具有dispatch,subscribe,getState方法

安装

yarn add redux
# npm install redux

3.8.2 基本使用

src/store.jsx,代码:

import { createStore } from 'redux';

const reducer = (state=10, action) => {
    console.log("state=", state, "action=",action,)
    switch (action.type) {
        case 'INCREMENT':
            return state + 1;
        case 'DECREMENT':
            return state - 1;
        default:
            return state;
    }

};

let store = createStore(reducer);
export default store;

src/App.jsx,代码:

import React from "react";
import store from './store';

function App(){
    return (
        <div>
            <p>{store.getState()}</p>
            <button onClick={()=>{
                store.dispatch({type: "INCREMENT", val: 10});
            }}>点击自增</button>
            <button onClick={()=>{
                store.dispatch({type: "DECREMENT", val: 10});
            }}>点击自减</button>
        </div>
    )
}

export default App;

store.dispatch() 是 View 发出 Action 的唯一方法,这就需要在View中引入store然后调用dispatch派发Action,dispatch一调用就会调用reducer来改变state从而改变View。

store.dispatch({type:"INCREMENT"});

Store 允许使用store.subscribe方法设置监听函数,一旦 State 发生变化,就自动执行这个函数。

src/store.jsx,代码:

import { createStore } from 'redux';

const reducer = (state=10, action) => {
    console.log("state=", state, "action=",action,)
    switch (action.type) {
        case 'INCREMENT':
            return state + 1;
        case 'DECREMENT':
            return state - 1;
        default:
            return state;
    }

};

let store = createStore(reducer);

// subscribe 用于监听指定状态是否发生了变化,
// 返回值,就是用于在不需要监听的时候,可以实现解绑
let unsubscribe = store.subscribe(() =>
    console.log("state发生了变化:", store.getState())
);

// 如果要取消监听,则直接执行unsubscribe方法即可。
// unsubscribe()

export default store;

getState方法可以获取返回当前state的值,可以在任意组件中,任意位置打印state的值。

console.log(store.getState());

上面的代码中可以看到,我们已经通过dispatch让reducer对state状态进行修改了但是并没有对UI这边的状态进行同步,所以UI这边并没有任何变化。所以,我们需要安装一个react-redux插件,可以解决上面的问题。

安装

yarn add react-redux

开启同步服务,src/main.jsx,代码:

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from "./App.jsx";

import store from './store.jsx';
import {Provider} from "react-redux";
ReactDOM.createRoot(document.getElementById('root')).render(
    <Provider store={store}>
        <App />
    </Provider>,
)

App.jsx,代码:

import React from "react";
import store from './store';
import {connect} from 'react-redux';

function App(){
    return (
        <div>
            <p>{store.getState()}</p>
            <button onClick={()=>{
                store.dispatch({type: "INCREMENT", val: 10});
            }}>点击自增</button>
            <button onClick={()=>{
                store.dispatch({type: "DECREMENT", val: 10});
            }}>点击自减</button>
        </div>
    )
}

// 状态同步处理
const mapStateToProps = state => state;
const mapDispatchToProps = {
    INCREMENT: {
        type: 'INCREMENT',
    },
    DECREMENT: {
        type: 'DECREMENT',
    }
}

export default connect(mapStateToProps, mapDispatchToProps)(App);

3.9 Antd组件库

官网:https://ant.design/components/overview-cn/

antd 为 Web 应用提供了丰富的基础 UI 组件,我们还将持续探索企业级应用的最佳 UI 实践。

安装

yarn add antd

# npm install antd --save

3.9.1 基本使用

src/main.jsx,代码:

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from "./App.jsx";
import {BrowserRouter} from "react-router-dom";
// 如果使用Antd没有样式的话,则自己手动导入即可。
import 'antd/dist/antd.css';

ReactDOM.createRoot(document.getElementById('root')).render(
    <BrowserRouter>
        <App />
    </BrowserRouter>
)

src/App.jsx,代码:

import React from "react";
import {useRoutes} from "react-router-dom";
import {routes} from './routes.jsx';
import { DatePicker } from 'antd';

function App(){
    return (
        <div>
            <DatePicker></DatePicker>
            {useRoutes(routes)}
        </div>
    )
}

export default App;

src/routes.jsx,代码:

import Base from "./views/Base.jsx";
import Home from "./views/Home.jsx";
import Goods from "./views/Goods.jsx";
import Login from "./views/Login.jsx";
import User from "./views/User.jsx";

export const routes = [
    {
        path: "/",
        element: <Base/>,
        children: [
            {
                path: "",
                element: <Home/>,
            },
            {
                path: "user",
                element: <User/>,
            },
            {
                path: "goods",
                element: <Goods/>,
            },
        ]
    },
    {
        path: "/login",
        element: <Login/>,
    },
];

src/views/Base.jsx,代码:

import React from 'react';
import {Outlet} from "react-router-dom";

function Base(props) {
    return (
        <div>
            <Outlet></Outlet>
        </div>
    );
}

export default Base;

src/views/Home.jsx,代码:

import React from 'react';

function Home(props) {
    return (
        <div>
            <h1>站点首页</h1>
        </div>
    );
}

export default Home;

其他的子组件复制Home.jsx,即可。访问站点,如果出现上面代码中的时间选择器没有问题,则表示安装Antd成功。

src/views/Base.jsx,代码:

import {
    DesktopOutlined,
    FileOutlined,
    PieChartOutlined,
    TeamOutlined,
    UserOutlined,
} from '@ant-design/icons';
import { Breadcrumb, Layout, Menu } from 'antd';
import React, { useState } from 'react';
import {Link, Outlet, useNavigate} from "react-router-dom";

const { Header, Content, Footer, Sider } = Layout;
function getItem(label, key, icon, children) {
    return {
        key,
        icon,
        children,
        label,
    };
}


const items = [
    getItem('主页面板', '/', <PieChartOutlined />),
    getItem('用户中心', '/user', <DesktopOutlined />),
    getItem('商品管理', '/goods', <UserOutlined />,),
    // getItem('登陆', '9', <FileOutlined />),
    // getItem('Team', 'sub2', <TeamOutlined />, [getItem('Team 1', '6'), getItem('Team 2', '8')]),
];



const Base = () => {
    const navigate = useNavigate()
    const [collapsed, setCollapsed] = useState(false);
    return (
        <Layout
            style={{
                minHeight: '100vh',
            }}
        >
            <Sider collapsible collapsed={collapsed} onCollapse={(value) => setCollapsed(value)}>
                <div className="logo">HaDes</div>
                <Menu theme="dark" defaultSelectedKeys={['1']} mode="inline" items={items} onClick={(item)=>navigate(item.key)}/>
            </Sider>
            <Layout className="site-layout">
                <Header
                    className="site-layout-background"
                    style={{
                        padding: 0,
                    }}
                >
                    <Link to="/login" style={{
                        float: "right",
                        marginRight: "20px"
                    }}>登陆</Link>
                </Header>
                <Content
                    style={{
                        margin: '0 16px',
                    }}
                >
                    <Breadcrumb
                        style={{
                            margin: '16px 0',
                        }}
                    >
                        <Breadcrumb.Item>User</Breadcrumb.Item>
                        <Breadcrumb.Item>Bill</Breadcrumb.Item>
                    </Breadcrumb>
                    <div
                        className="site-layout-background"
                        style={{
                            padding: 24,
                            minHeight: 360,
                        }}
                    >
                        <Outlet></Outlet>
                    </div>
                </Content>
                <Footer
                    style={{
                        textAlign: 'center',
                    }}
                >
                    Ant Design ©2018 Created by Ant UED
                </Footer>
            </Layout>
        </Layout>
    );
};

export default Base;

src/style.css,代码:

body,p,ul,li,table,form,input, h1, h2,h3, h4{
    margin: 0;
    padding: 0;
    font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace
}
.logo {
    height: 32px;
    margin: 16px;
    background: rgba(255, 255, 255, 0.3);
    font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
    font-size: 28px;
    text-align: center;
    color: aliceblue;
}

.site-layout .site-layout-background {
    background: #fff;
}

src/views/Login.jsx,代码:

import { Button, Checkbox, Form, Input } from 'antd';
import React from 'react';
const Login = () => {
    const onFinish = (values) => {
        console.log('Success:', values);
    };
    const onFinishFailed = (errorInfo) => {
        console.log('Failed:', errorInfo);
    };
    return (
        <div style={{
            width:"100vw",
            height:"100vh",
            backgroundColor: "#123",
        }}>
            <div style={{
                width: "600px",
                height: "350px",
                position: "fixed",
                margin: "100px auto",
                backgroundColor: "#fff",
                left: 0,
                right: 0,
                paddingRight: "100px",
                paddingTop: "50px"
            }}>
                <Form
                    name="basic"
                    labelCol={{
                        span: 8,
                    }}
                    wrapperCol={{
                        span: 16,
                    }}
                    initialValues={{
                        remember: true,
                    }}
                    onFinish={onFinish}
                    onFinishFailed={onFinishFailed}
                    autoComplete="off"
                >
                    <Form.Item
                        label="Username"
                        name="username"
                        rules={[
                            {
                                required: true,
                                message: 'Please input your username!',
                            },
                        ]}
                    >
                        <Input />
                    </Form.Item>

                    <Form.Item
                        label="Password"
                        name="password"
                        rules={[
                            {
                                required: true,
                                message: 'Please input your password!',
                            },
                        ]}
                    >
                        <Input.Password />
                    </Form.Item>

                    <Form.Item
                        name="remember"
                        valuePropName="checked"
                        wrapperCol={{
                            offset: 8,
                            span: 16,
                        }}
                    >
                        <Checkbox>Remember me</Checkbox>
                    </Form.Item>

                    <Form.Item
                        wrapperCol={{
                            offset: 8,
                            span: 16,
                        }}
                    >
                        <Button type="primary" htmlType="submit">
                            登陆
                        </Button>
                    </Form.Item>
                </Form>
            </div>
        </div>
    );
};
export default Login;

src/viwes/Goods.jsx,代码:

import React from 'react';
import { Space, Button, Modal, Table, Tag } from 'antd';
import { ExclamationCircleFilled } from '@ant-design/icons';
const { confirm } = Modal;

const columns = [
    {
        title: 'ID',
        dataIndex: 'name',
        key: 'name',
        render: (text) => <a>{text}</a>,
    },
    {
        title: '标题',
        dataIndex: 'age',
        key: 'age',
    },
    {
        title: '图片',
        dataIndex: 'address',
        key: 'address',
    },
    {
        title: 'Tags',
        key: 'tags',
        dataIndex: 'tags',
        render: (_, { tags }) => (
            <>
                {tags.map((tag) => {
                    let color = tag.length > 5 ? 'geekblue' : 'green';
                    if (tag === 'loser') {
                        color = 'volcano';
                    }
                    return (
                        <Tag color={color} key={tag}>
                            {tag.toUpperCase()}
                        </Tag>
                    );
                })}
            </>
        ),
    },
    {
        title: 'Action',
        key: 'action',
        render: (_, record) => (
            <Space size="middle">
                <a>Invite {record.id}</a>
                <a onClick={()=>showConfirm()}>Delete</a>
            </Space>
        ),
    },
];

const showConfirm = () => {
    confirm({
        title: '您确定要删除这个数据吗?',
        icon: <ExclamationCircleFilled />,
        content: 'Some descriptions',
        onOk() {
            console.log('OK');
        },
        onCancel() {
            console.log('Cancel');
        },
    });
};

const data = [
    {
        id: '1',
        name: 'John Brown',
        age: 32,
        address: 'New York No. 1 Lake Park',
        tags: ['nice', 'developer'],
    },
    {
        id: '2',
        name: 'Jim Green',
        age: 42,
        address: 'London No. 1 Lake Park',
        tags: ['loser'],
    },
    {
        id: '3',
        name: 'Joe Black',
        age: 32,
        address: 'Sidney No. 1 Lake Park',
        tags: ['cool', 'teacher'],
    },
];
const Goosd = () => {
    // useEffect(() => {
    //     return () => {
    //         axios
    //     };
    // }, [input]);

    return (
        <Table columns={columns} dataSource={data} />
    )
}
export default Goosd;

src/views/User.jsx,代码:

import React from 'react';

function User(props) {
    return (
        <div>
            <h1>用户中心</h1>
        </div>
    );
}

export default User;

3.9.2 显示图表

Antd本身有一个周边的插件模块可以实现图表的展示。

官网:https://charts.ant.design/zh/docs/manual/getting-started

安装

yarn add @ant-design/charts 

# npm install @ant-design/charts --save

src/views/User.jsx,代码:

import React from 'react';

import { Pie } from '@ant-design/plots';

const DemoPie = () => {
    const data = [
        {
            type: '分类一',
            value: 27,
        },
        {
            type: '分类二',
            value: 25,
        },
        {
            type: '分类三',
            value: 18,
        },
        {
            type: '分类四',
            value: 15,
        },
        {
            type: '分类五',
            value: 10,
        },
        {
            type: '其他',
            value: 5,
        },
    ];
    const config = {
        appendPadding: 10,
        data,
        angleField: 'value',
        colorField: 'type',
        radius: 0.75,
        label: {
            type: 'spider',
            labelHeight: 28,
            content: '{name}\n{percentage}',
        },
        interactions: [
            {
                type: 'element-selected',
            },
            {
                type: 'element-active',
            },
        ],
    };
    return <Pie {...config} />;
};

function User(props) {
    return (
        <div>
            <h1>用户中心</h1>
            <div style={{
                width: "200px",
                height: "200px"
            }}>
                <DemoPie></DemoPie>
            </div>
        </div>
    );
}

export default User;
3.9.2.1 ECharts

ECharts是一款基于纯JavaScript开发的数据可视化图表库(并不依赖于react),提供直观,生动,可交互,可个性化定制的数据可视化图表。ECharts最初由百度团队开源,并于2018年初捐赠给Apache基金会。

官网:https://echarts.apache.org/zh/index.html

安装

yarn add echarts
# npm install echarts --save

基本使用

import React, {useRef, useEffect } from 'react';
import * as echarts from 'echarts'; // 导入所有 并命名为echarts


function Home(props) {
    const chartRef = useRef()

    const options = {
        // 标题
        title: {
            text: "柱状图"
        },
        // 提示框组件
        tooltip: {
            // trigger: 'axis'
        },
        // 图例组件
        legend:{
            //     data:['销量'],
            //     show:true
        },
        // x轴
        xAxis: {
            type: 'category',
            data: ['冬瓜', '茄子', '丝瓜', '玉米', '红薯', '西红柿', '芹菜']
        },
        // y轴
        yAxis: {
            type: 'value'
        },
        series: [{
            data: [20, 9, 39, 43, 60, 18, 50],
            // type: 'line' 折线图
            type:'bar', // 柱状图
            name:'销量'
        }]
    }

    useEffect(() => {
        // 创建一个echarts实例,返回echarts实例。不能在单个容器中创建多个echarts实例
        const chart = echarts.init(chartRef.current)

        // 设置图表实例的配置项和数据
        chart.setOption(options)

        // 组件卸载
        return () => {
            // myChart.dispose() 销毁实例。实例销毁后无法再被使用
            chart.dispose()
        }
    },[])

    return (
        <div>
            <h1>站点首页</h1>
            {/*宽度要大,不然y轴有些名称可能不会显示*/}
            <div style={{width: "600px", height: "400px"}} ref={chartRef}></div>
        </div>
    );
}

export default Home;