React

2021/4/3 框架React前端javascript

# 1. React入门

  • React使用虚拟DOM,不总是直接操作页面的真实DOM,以至于会比较高效
  • 使用DOM Diffing算法,最小化页面重绘

# 1.1 React的基本使用

# 1.1.1 相关js库

  1. react.jsReact核心库
  2. react-dom.js:提供操作DOMreact扩展库
  3. babel.min.js:解析JSX语法转换为JS代码的库

# 1.1.2 创建虚拟DOM

jsx创建虚拟DOM⭐️

  1. 创建index.html并准备“容器”

    <div id="test"></div>
    
  2. 引入react依赖库

    <!-- 引入react和核心库 -->
    <script type="text/javascript" src="../js/16.x/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM -->
    <script type="text/javascript" src="../js/16.x/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转化为js -->
    <script type="text/javascript" src="../js/16.x/babel.min.js"></script>
    
  3. 编写jsx

    <script type="text/babel">
        // 1. 创建虚拟DOM
        const VDOM = <h1>Hello React</h1> // jsx不需要加引号
    
        // 2. 渲染虚拟DOM到页面 
        ReactDOM.render(VDOM, document.getElementById("test"))
    </script>
    

    render默认为覆盖不是追加

js创建虚拟DOM

<script type="text/javascript">
    const VDOM = React.createElement("h1", {id: "title"}, "Hello React") 

    ReactDOM.render(VDOM, document.getElementById("test"))
</script>

对于多层级的虚拟DOMjs就会显得十分繁琐,jsx挺身而出

# 1.1.3 虚拟DOM与真实DOM

  1. React提供了一些API来创建一种 “特别” 的一般js对象

    const VDOM = (
        <h1 id="test">
        <span>Hello React</span>
        </h1>
    )
    

    上面创建的就是一个简单的虚拟DOM对象

    虚拟DOM的本质是Object类型的对象

    虚拟DOM相比真实DOM属性会少很多

  2. 虚拟DOM对象最终都会被React转换为真实的DOM

  3. 我们编码时基本只需要操作react的虚拟DOM相关数据, react会转换为真实DOM变化而更新界。

# 1.2 React Jsx

# 1.2.1 简介

  1. jsx全称JavaScript XML

  2. react定义的一种类似于XMLJS扩展语法

  3. 本质是React.createElement(Component, props, ...children)方法的语法糖

  4. 作用:用来简化创建虚拟DOM

    写法

    var ele = <h1>Hello JSX</h1>
    

    它不是字符串,也不是HTML/XML标签

    它最终产生的就是一个js对象

  5. 标签名任意:HTML标签或其他标签

# 1.2.2 基本语法

  1. 定义DOM是,不要写引号

  2. 如果说标签内的内容是一个变量则需要{}进行取值

    <script type="text/babel">
        // 1. 创建虚拟DOM
        const id = "haha"
        const data = "Hello React"
        const VDOM = (
            <h2 id={id}>
            	<span>{data}</span>
            </h2>
        )
    
        // 2.渲染虚拟DOM到页面
        ReactDOM.render(VDOM, document.getElementById("test"))
    </script>
    
  3. 样式的类名指定不能用class,要用className

    <style>
        .title{
            background-color: orange;
            width: 200px;
        }
    </style>
    <script type="text/babel">
        const VDOM = (
            <h2 className="title">
            	<span>"Hello React"</span>
            </h2>
        )
    
        ReactDOM.render(VDOM, document.getElementById("test"))
    </script>
    
  4. 内敛样式需要使用{{}},且如果是font-size这样有两个单词组成的需要用驼峰命名法

    <script type="text/babel">
        const VDOM = (
            <h2>
            	<span style={{color: 'red', fontSize: '30px'}}>"Hello React"</span>
            </h2>
        )
    
        ReactDOM.render(VDOM, document.getElementById("test"))
    </script>
    
  5. jsx只能有一个根标签

  6. 标签必须闭合

    <script type="text/babel">
        const VDOM = (
            <h2>
            	<input type="text" />
            </h2>
        )
    
        ReactDOM.render(VDOM, document.getElementById("test"))
    </script>
    
  7. 首字符小写标签仅能为html的标准标签,首字符大写是一个自定义的组件

# 1.2.3 练习

jsx动态渲染数据

  • 表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方

    变量

    函数。。。

  • 语句(代码):

    if语句,for语句

  • 对于jsx内部的{}只能为表达式

<div id="test"></div>
<script type="text/babel">
// 模拟后端数据
const data = ["Angular", "React", "Vue"]
const VDOM = (
	<div>
		<h1>前端js框架列表</h1>    
		<ul>
			{
				data.map((item, index) => {
					return <li key={index}>{item}</li>
				})
			}   
    	</ul>
    </div>
    )
	ReactDOM.render(VDOM, document.getElementById("test"))
</script>

每一个虚拟DOM节点,都需要一个key用来作为唯一标识

# 1.3 模块与组件、模块化与组件化

# 1.3.1 模块

  1. 理解:向外部提供特定功能的js程序,一般就是一个js文件
  2. 为什么要拆分模块:随着业务逻辑的增加,代码越来越多且复杂
  3. 作用:复用js,简化js的编写,提高js运行效率

# 1.3.2 组件

  1. 理解:用来实现局部功能效果的代码和资源的集合html/css/js/image等等
  2. 为什么需要组件:一个界面的功能太过复杂
  3. 作用:复用编码,简化项目编码,提高运行效率

# 1.3.3 模块化

当应用的js都已模块来编写的,这个应用就是一个模块化的应用

# 1.3.4 组件化

当应用是以多组件的方式实现的,这个应用就是一个组件化的应用

# 2. React面向组件开发

# 2.1 基本理解和使用

# 2.1.1 使用React开发者工具调试

安装插件React Developer Tools

# 2.1.2 组件的创建方式

函数式组件

<div id="test"></div>
<script type="text/babel">
    // 1. 创建函数式组件
    function MyComponent() {
        return (
            <h1>函数式组件,适用于简单组件的定义</h1>
        )
    }

    // 2. 渲染组件到页面
    ReactDOM.render(<MyComponent />, document.getElementById("test"))
</script>

函数内部的thisubderfined,因为babel编译后开启了严格模式

render之后的过程

  1. React解析组件标签,找到了MyComponent组件
  2. 返现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中

类式组件

<div id="test"></div>
<script type="text/babel">
// 1. 创建类式组件
class MyComponent extends React.Component{
    render() {
        return (
            <h2>我是用类定义的组件</h2>
        )
    }
}

// 2. 渲染组件到页面
ReactDOM.render(<MyComponent />, document.getElementById("test"))
</script>

render是放在了原型对象上,供实例使用

render()函数内部的this指向MyComponent的实例对象

ReactDOM.render之后的过程

  1. React解析组件标签,找到了MyComponent组件
  2. 发现组件是类定义的,随后创建该类的实例,并通过该实例代用原型上的render方法
  3. render返回的虚拟DOM转换为真实DOM,随后呈现在页面中

总结

函数式组件适用于简单组件

类式组件适用于复杂组件

# 2.2 三大核心属性

三大核心属性仅仅针对类式组件

# 2.2.1 state

理解

  1. state是组件对象最重要的属性,值是对象(可以包含多个key-value的组合)
  2. 组件被称为''状态机“,通过更新组件的state来更新对应的页面显示(重新渲染组件)

强烈注意

  1. 组件中的render方法中的this为组件实例对象

  2. 组件自定义的方法中thisundefined

    强制绑定this通过函数对象的bind()

    箭头函数

  3. 状态数据,不能直接修改或更新

动态渲染数据

<div id="test"></div>
< type="text/babel">
    // 1. 创建类式组件
    class Weather extends React.Component{

        // 初始化状态
        state = {
            isHot: true
        }
        render() {
            return (
                <h2 id="title" onClick={this.changeWeather}>今天天气很{this.state.isHot ? "炎热" : "凉爽"}</h2>
            )
        }

        // 自动以方法要用赋值语句的形式+箭头函数
        changeWeather = () => {
            this.setState({isHot: !this.state.isHot})
        }
    }

    // 2. 渲染组件到页面
    ReactDOM.render(<Weather />, document.getElementById("test"))
</>

# 2.2.2 props

需求:自定义用来显示一个人员信息的组件

  • 名字必须指定,且为字符串类型
  • 性别为字符串类型,如果性别没有指定,默认为男
  • 年龄必须指定,且为数字类型

props的属性进行限制,属性的限制需要引入prop-types.js

// 对Person属性的类型进行限制
static propTypes = {
    // string类型为字符串,isRequired必须有值
    name: PropTypes.string.isRequired,
    sex: PropTypes.string,
    age: PropTypes.number,
    speak: PropTypes.func
}  

// 设置属性的默认值
static defaultProps = {
    sex: "待鉴定",
    age: 18
}

渲染组件

render() {
    return (
        <ul>
            <li>姓名:{this.props.name}</li>
            <li>性别:{this.props.age + 1}</li>
            <li>年龄:{this.props.sex}</li>
        </ul>
    )
}

传递props的两种方式

  1. 直接传递

    ReactDOM.render(<Person name="Tom" age={18} sex="男"/>, document.getElementById("test"))
    
  2. ...传递

    const p = {name: "老刘", age: 19}
    ReactDOM.render(<Person {...p}/>, document.getElementById("test"))
    

理解

  1. 每个组件对象都会有props(properties)属性
  2. 组件标签的所有属性都保存在props

注意

props的属性是只读的

函数式组件使用props

function Person(props) {
    return (
        <ul>
            <li>姓名:{props.name}</li>
            <li>性别:{props.age + 1}</li>
            <li>年龄:{props.sex}</li>
        </ul>
    )
}

ReactDOM.render(<Person name="Alcie" age={18} sex="女" />, document.getElementById("test"))

对于属性限制,限制只能写在函数的外部

# 2.2.3 refs

组件内的标签可以定义ref属性来标识自己,相当于id属性,ref指定的值都会被收集到refs属性中

  1. 字符串形式的ref:不推荐使用

    render

    <input type="text" ref="input1" placeholder="点击按钮提示数据"/>
    <button onClick={this.showData} >点我提示左侧的数据</button>
    

    方法

    showData = () => {
    	alert(this.refs.input1.value)
    }
    
  2. 回调函数形式的ref

    renderc为当前节点对象

    <input type="text" ref={(c) => {this.input1 = c}} placeholder="点击按钮提示数据"/>
    <button onClick={this.showData} ref="btn">点我提示左侧的数据</button>&nbsp;
    <input onBlur={this.showData2} type="text" ref={c => this.input2 = c} placeholder="失去焦点提示"/>
    

    方法

    showData = () => {
    	alert(this.input1.value)
    }
    
    showData2 = () => {
    	alert(this.input2.value)
    }
    

    对于内敛的ref会在组件更新时调用两次,第一的参数为null,第二次的参数为当前节点,如果不想要调用两次,需要将方法绑定在类上

    <div>
        <h2>今天天气很{this.state.isHot ? "炎热" : "凉爽"}</h2>
        <button onClick={this.showData}>点我获取天气</button>&nbsp;
    </div>
    

    方法

    state ={
    	isHot: false
    }
    showData = (c) => {
    	this.setState({isHot: !this.state.isHot})
    }
    
  3. createRef

    首先创建容器

    myRef = React.createRef()
    

    React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,一个容器只能存储一个节点

    创建jsx结构

    <div>
        <input type="text" ref={this.myRef} placeholder="点击按钮提示数据"/>
        <button onClick={this.showData} ref="btn">点我提示左侧的数据</button>&nbsp;
    </div>
    

    创建方法

    showData = () => {
    	alert(this.myRef.current.value)
    }
    

# 2.2.4 三大属性与事件处理

  1. 通过onXxx属性来指定事件处理函数

    React使用的是自定义(合成)事件,而不是使用原生的DOM事件----为了更好的兼容性

    React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)-----为了高效

  2. 当发生事件的DOM与要操作的DOM相同时,可以省略ref通过event.target得到发生事件的DOM元素对象

    <input onBlur={this.showData} type="text" placeholder="失去焦点提示数据"/>
    showData = (event) => {
    	alert(event.target.value)
    }
    

# 2.3 收集表单数据

包含表单组件的分类

  1. 非受控组件

    现用现取

    class Login extends React.Component{
    
        handleSubmit = (event) => {
        	event.prevenDefault() // 阻止表单提交
        	const {username, password} = this
        	alert(`用户名:${username.value},密码:${password.value}`)
        }
    
        render() {
            return (
                <form action="http://www.baidu.com" onSubmit={this.handleSubmit}>
                    用户名:<input ref={c => this.username = c} type="text" name="username"/>
                    密码:<input ref={c => this.password = c} type="password" name="password" />
                    	<button>登录</button>
                </form>
            )
        }
    }
    
  2. 受控组件:推荐使用

    将需要的信息存储在state

    class Login extends React.Component{
    
        // 初始化状态
        state = {
            username: "",
            password: ""
        }
    
        handleSubmit = (event) => {
            event.prevenDefault() // 阻止表单提交
    
            const {username, password} = this.state
            alert(`用户名:${username},密码:${password}`)
        }
    
        // 保存用户名密码到状态中
        saveFormData = (dataType) => {
            return (event) => {
                this.setState({[dataType]: event.target.value})
            }
        }
        render() {
            return (
                <form onSubmit={this.handleSubmit}>
                    用户名:<input onChange={this.saveFormData("username")} type="text" name="username"/>
                    密码:<input onChange={this.saveFormData("password")} type="password" name="password" />
                    <button>登录</button>
                </form>
            )
        }
    }
    

    对于saveFormData就是一个高阶函数

    高阶函数:

    • A函数,接受的参数是一个函数,那么A就可以成为高阶函数
    • A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数
    • 常见的高阶函数:PromisesetTimeoutarr.map()

    函数的柯里化:

    • 通过函数调用继续返回函数的方式,实现多次接受参数最后统一处理的函数编码形式

# 2.4 组件的生命周期

组件分为挂载和卸载

  • 当组件被渲染到页面上时,称为挂载

  • 当组件被从页面中移除时,称为卸载

    // 参数为指定的容器
    ReactDOM.unmountComponentAtNode(document.getElementById("test"))
    

# 2.4.1 生命周期理解

  1. 组件从创建到死亡它会经历一些特定的阶段
  2. React中包含了一系列钩子函数(生命周期回调函数),会在特定的时刻调用
  3. 我们定义组件时,会在特定的生命周期回调函数中做特定的工作

# 2.4.1 生命周期钩子

  1. 旧版本的钩子
react生命周期(旧)
  • 初始化阶段:由ReactDOM.render()触发

    constructor

    componentWillMount

    ⭐️render

    ⭐️compoonentDidMount:一般用于初始化的操作,订阅消息,会接受prePropspreState作为参数,新版本中还会接受getSnapshotBeforeUpdate的快照值

  • 更新阶段:由组件内部this.setState()或者父组件重新render触发

    shouldConponentUpdate:仅当返回值为true是,组件允许更新,如果是forceUpdate()将会跳过验证

    componentWillUpdate

    render

    componentDidUpdate

  • 卸载组件:由ReactDOM.unmountComponentAtNode:()触发

    ⭐️componentWillUnmount:一般用于收尾工作,取消订阅

  1. 新版本的钩子
react生命周期(新)

对于新版的钩子,由于以下三个钩子经常被误用,所以componentWillMountcomponentWillReceivePropscomponentWillUpdate需要加上前缀UNSAFE_

  • 初始化阶段:由ReactDOM.render触发

    constructor

    getDerivedStateFromProps

    render

    componentDidMount()

  • 更新阶段

    getDerivedStateFromProps

    shouldComponentUpdate

    render

    getSnapshotBeforeUpdate

    componentDidUpdate

  • 卸载组件:由ReactDOM.umountComponentAtNode触发

    componentWillUnmount

# 2.5 Diffing算法

Diffing算法的最小力度是标签

# 2.5.1 key的作用

  1. 虚拟DOMkey的作用

    • 简单来说:key是虚拟DOM对象的标识,在更新显示时key起着极其重要的作用

    • 详细的说:当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】,随后React进行了【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:

      旧虚拟DOM中找到了与新虚拟DOM相同的key

      • 若虚拟DOM内容没变,直接使用之前的真实DOM
      • 若虚拟DOM内容变了,则生成新的真实DOM,随后替换掉页面之前的真实DOM

      旧虚拟DOM中未找到与新虚拟DOM相同的key,根据数据创建新的真实DOM,随后渲染到页面

  2. index作用作为key可能会引发的问题

    • 若对数据进行:逆序添加、逆序删除等破坏顺序操作:会产生没有必要的真实DOM更新 ==> 界面效果没问题,但效率低
    • 如果结构中还包含输入类的DOM:会产生错误DOM更新 ==> 界面有问题
    • 如果不存在对数据的逆序添加、逆序删除等破坏顺序的操作,仅用于渲染列表用于展示,使用index作为key是没有问题的
  3. 开发中如何选择key

    • 最好使用每条数据的唯一标识作为key、比如id、手机号、身份证号、学号等唯一值
    • 如果确定只是简单的展示数据,用idnex也是可以的

# 3. React脚手架

# 3.1 react脚手架

  1. xxx脚手架: 用来帮助程序员快速创建一个基于xxx库的模板项目

    包含了所有需要的配置(语法检查、jsx编译、devServer…)

    下载好了所有相关的依赖

    可以直接运行一个简单效果

  2. react提供了一个用于创建react项目的脚手架库: create-react-app

  3. 项目的整体技术架构为: react + webpack+ ``es6+eslint`

  4. 使用脚手架开发的项目的特点: 模块化, 组件化, 工程化

# 3.2 创建项目并启动

  1. 全局安装

    npm install -g create-react-app
    
  2. 切换到想创项目的目录,使用命令

    create-react-app hello-react
    
  3. 进入项目文件夹

    cd hello-react
    
  4. 启动项目

    npm start
    
  5. 常用命令

    npm start # 启动项目
    npm build # 编译打包项目
    npm test # 测试
    npm eject # 显示配置文件
    

# 3.3 react脚手架项目结构

public ---- 静态资源文件夹
    favicon.icon ------ 网站页签图标
    index.html -------- 主页面 --重要
    logo192.png ------- logo图
    logo512.png ------- logo图
    manifest.json ----- 应用加壳的配置文件
    robots.txt -------- 爬虫协议文件
src ---- 源码文件夹
    App.css -------- App组件的样式
    App.js --------- App组件 --重要
    App.test.js ---- 用于给App做测试
    index.css ------ 样式
    index.js ------- 入口文件 --重要
    logo.svg ------- logo图
    reportWebVitals.js
    	--- 页面性能分析文件(需要web-vitals库的支持)
    setupTests.js
    	---- 组件单元测试的文件(需要jest-dom库的支持)

# 3.4 功能界面的组件化编码流程

  1. 拆分组件:拆分界面抽组件

  2. 实现静态组件:使用组件实现静态页面效果

  3. 实现动态组件

    • 动态显示初始化数据

      数据类型

      数据名称

      保存在哪个组价

    • 交互(从绑定事件监听开始)

# 3.5 常用库

  1. 唯一id

    安装

    npm install nanoid
    

    使用

    import {nanoid} from 'nanoid'
    
    // 返回一个字符串类型的id
    id = nanoid()
    
  2. 类型限制

    安装

    npm i prop-types
    

    使用

    import PropTypes from 'prop-types'
    
    static propTypes = {
        id: PropTypes.string.isRequired,
        name: PropTypes.string.isRequired,
        done: PropTypes.bool.isRequired,
        updateTodo: PropTypes.func.isRequired
    }
    

# 3.6 状态提升

如果对于A、B两兄弟组件,状态数据需要共享,那就需要把他们的状态统一放到其父组件中,通过父组件向其传递状态

  1. 父组件向子组件传递数据,通过props
  1. 子组件向父组件传递数据

    如果是子组件向父组件传递数据,就需要在父组件创建函数,然后将其传递给子组件,子组件进行调用时,将数据传递给父组件

# 4. React ajax

# 4.1 理解

# 4.1.1 前置说明

  1. React本身只关注界面,并不包含发送ajax请求的代码
  2. 前端应用需要通过ajax请求与后台进行交互(json数据)
  3. react应用中需要集成第三方ajax库(或自己封装)

# 4.1.2 常用的ajax请求库

  1. jQuery比较重,如果需要另外引用不建议使用
  2. axios轻量级,建议使用
    • 封装XmlHttpRequest对象的ajax
    • promis风格
    • 可以用在浏览器端和node服务器端

# 4.2 axios

# 4.2.1 安装使用

  1. 安装

    npm add axios
    
  2. 使用

    import React, { Component } from 'react'
    import axios from 'axios'
    
    export default class App extends Component {
        getStudentData = () => {
            axios.get('www.baidu.com')
            .then(
                res => {
                    console.log(res.data)
                },
                error => {
                    console.log(error)
                }
            )
        }
    
        render() {
            return (
                <div>
                    <button onClick={this.getStudentData}>点我获取数据</button>
                </div>
            )
        }
    }
    

# 4.3 配置代理解决跨域

# 4.3.1 方法一

在package.json中追加如下配置

"proxy":"http://localhost:8080"

说明:

  1. 优点:配置简单,前端请求资源时可以不加任何前缀。
  2. 缺点:不能配置多个代理。
  3. 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给8080(优先匹配前端资源)

# 4.3.2 方法二

  1. 第一步:创建代理配置文件

    在src下创建配置文件:src/setupProxy.js
    
  2. 编写setupProxy.js配置具体代理规则:

    const proxy = require('http-proxy-middleware')
    
    module.exports = function(app) {
      app.use(
        proxy('/api1', {  //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给8080)
          target: 'http://localhost:8080', //配置转发目标地址(能返回数据的服务器地址)
          changeOrigin: true, //控制服务器接收到的请求头中host字段的值
          /*
          	changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:8080
          	changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
          	changeOrigin默认值为false,但我们一般将changeOrigin值设为true
          */
          pathRewrite: {'^/api1': ''} //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
        }),
        proxy('/api2', { 
          target: 'http://localhost:8080',
          changeOrigin: true,
          pathRewrite: {'^/api2': ''}
        })
      )
    }
    

说明:

  1. 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
  2. 缺点:配置繁琐,前端请求资源时必须加前缀。

# 4.4 消息订阅-发布机制

用于解决兄弟组件传参的问题,当然也适用于所有的组件的传参

  1. 工具库PubSubJs

  2. 安装

    npm install pubsub-js
    
  3. 使用

    import PubSub from 'pubsub-js' //引入
    PubSub.subscribe('delete', function(data){ }); //订阅
    PubSub.publish('delete', data) //发布消息
    

假设有A组件想要向B组件发送数据,A组件与B组件为兄弟组件

  1. B组件挂载时订阅消息

    import PubSub from 'pubsub-js'
    
    // 订阅消息并保存token
    this.token = PubSub.subscribe('messageName', (msg, data) => {
    	console.log('收到数据')
    })
    
  2. A中发布消息

    import PubSub from 'pubsub-js'
    
    // 发布消息
    PubSub.publish('messageName', {data: 123})
    
  3. B组件卸载之前应该取消订阅

    componentWillUnmount() {
        // 取消消息订阅
        PubSub.unsubscribe(this.token)
    }
    

# 4.5 fetch

属于javascript的内置库,与xhr同级的内置对象

# 4.5.1 文档

  1. https://github.github.io/fetch/

  2. https://segmentfault.com/a/1190000003810652

# 4.5.2 特点

  1. fetch: 原生函数,不再使用XmlHttpRequest对象提交ajax请求

  2. 老版本浏览器可能不支持

# 4.5.3 相关API

  1. GET请求

    fetch(url).then(function(response) {
        return response.json()
    }).then(function(data) {
        console.log(data)
    }).catch(function(e) {
        console.log(e)
    });
    
  2. POST请求

    fetch(url, {
        method: "POST",
        body: JSON.stringify(data),
    }).then(function(data) {
    	console.log(data)
    }).catch(function(e) {
    	console.log(e)
    })
    

# 5. React路由

# 5.1 相关理解

# 5.1.1 SPA的理解

  1. 单页Web应用single page web application SPA
  2. 整个应用只有一个完整的页面
  3. 点击页面中的链接不会刷新页面,只会做页面的局部更新
  4. 数据都需要通过ajax请求获取,并在前端异步展现

# 5.1.2 路由的理解

  1. 什么是路由

    一个路由就是一个映射关系(key: value)

    key为路径,value可能是function或者component

  2. 路由分类

    后端路由

    • 理解:valuefunction,用来处理客户端提交的请求

    • 注册路由:router.get(path, function(req, res))

    • 工作过程中:当node接受到一个请求时,根据请求路径找到匹配的路由,调用路由中的函数来处理请求,返回响应数据

    前端路由:

    • 浏览器端路由,valuecomponent,用于战术页面内容
    • 注册路由:<Route path="/test" component={Test}>
    • 工作过程:当浏览器的path变为/test,当前路由组件就会变为Test组件

# 5.1.3 react-router

  1. react的一个插件库
  2. 专业用来实现一个SPA应用
  3. 基于react的项目基本都会用到此库

# 5.2 react-router-dom相关API

安装

npm install react-router-dom

# 5.2.1 内置组件

  1. BrowserRouterh5特有
  2. HashRouter
  3. Route
  4. Redirect
  5. Link
  6. NavLink:相比Link有一个activeClassName属性,即当NavLink被点击时会追加一个类名,比如说让标签高亮的样式类名
  7. Switch

# 5.2.2 其他

  1. history对象
  2. match对象
  3. withRouter函数

# 5.3 快速入门

  1. 明确好界面中的导航区、展示区
  2. 导航区的a标签Link标签
  3. <Link to='/xxx> Demo </Link>'
  4. 展示区写Route标签路径的匹配<Route path='/xxx' component={Demo} />
  5. <App />的最外层包裹一个<BrowserRouter><HashRouter>
import {Link, BrowserRouter, Route} from "react-router-dom"


<BrowserRouter>
    <div>
        {/* react路由链接切换组件 ---编写路由链接 */}
        <Link className="list-group-item" to="/about">About</Link>
        <Link className="list-group-item" to="/home">Home</Link>
    </div>
    <div className="panel-body">
    {/* 注册路由 */}
        <Route path="/about" component={About}/>
        <Route path="/home" component={Home}/>
    </div>
</BrowserRouter>

# 5.3.1 路由组件和一般组件

路由组件:注册路由中引用的组件,一般放在pages文件夹下

路由组件在渲染时会接受到的props

{
  "history": {
    "push": "ƒ push() {}",
    "replace": "ƒ replace() {}",
    "go": "ƒ go() {}",
    "goBack": "ƒ goBack() {}",
    "goForward": "ƒ goForward() {}",
  },
  "location": {
    "pathname": "/home",
    "search": "",
  },
  "match": {
    "path": "/home",
    "url": "/home",
    "params": "{}"
  }
}

# 5.3.2 标签体

对于组件的标签体被收集在children属性中

# 5.3.3 解决样式丢失

当路由的层级为多级时,此时去刷新页面样式就会丢失,解决办法如下

  1. 修改样式引入的链接

    <link rel="stylesheet" href="./css/bootstrap.css">
    
    <!-- 删除点 -->
    <link rel="stylesheet" href="/css/bootstrap.css">
    
  2. 修改样式引入链接

    <link rel="stylesheet" href="./css/bootstrap.css">
    
    <!-- 将点修改为%PUBLIC_URL% -->
    <link rel="stylesheet" href="%PUBLIC_URL%/css/bootstrap.css">
    
  3. 修改BrowserRouterHashRouter

# 5.3.4 路由匹配

模糊匹配

可以匹配成功

<NavLink to="/home/test">Home</NavLink>
<Route path="/home" component={Home}/>

不能匹配成功

<NavLink to="/home/test">Home</NavLink>
<Route path="/home/test" component={Home}/>

精准匹配

<Route path="/home/test" component={Home}/>

总结

  1. 模式使用的是模糊匹配
  2. 开启严格匹配使用exact={true}
  3. 严格匹配不要随便开启,需要时开启。有时开启会导致无法继续匹配二级路由

# 5.4 路由组件的使用

路由组件包括两种,react-dom-router提供的组件和Route属性中component指定的组件

  1. NavLink可以实现路由链接的高亮,通过activeClassName指定样式名
  2. 标签体的内容是一个特殊的标签属性
  3. 通过this.props.children可以获取标签体内容

# 5.4.2 Switch

Switch包裹的Route可以实现只匹配第一个成功的路由

<Switch>
    {/* 注册路由 */}
    <Route path="/about" component={About}/>
    <Route path="/home" component={Home}/>
    <Route path="/home" component={Test}/>
</Switch>

# 5.4.3 Redirect

指定路由不匹配时,所要使用的路由

<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Redirect to="/about" />

# 5.5 二级路由和向路由组件传递参数

# 5.5.1 二级路由

路由的匹配都是从第一个注册的路由开始,因为路由的匹配都是模糊匹配,所以二级路由一定要带上父路由的前缀

# 5.5.2 路由组件传递参数

  1. params传递参数

    路由路径使用:xxx来传递参数

    render() {
        return (
            <div>
                <ul>
                {
                    this.state.messageArr.map((msg) => {
                    return <li key={msg.id}>
                    	{/* 向路由组件传递params参数 */}
                    	<Link to={`/home/message/detail/${msg.id}/${msg.title}`} href="xxx">{msg.title}</Link>&nbsp;&nbsp;
                    </li>
                    })
                }
                </ul>
                <hr/>
                <Switch>
                    {/* 声明接受params参数 */}
                	<Route path="/home/message/detail/:id/:title" component={Detail} />
                </Switch>
            </div>
        )
    }
    

    在路由组件的this.props.match.params中会接到传递的参数

    const {id, title} = this.props.match.params
    
  2. search参数

    search参数就相当于query参数,以?开始,&符分割

    <Link to={`/home/message/detail/?id=${msg.id}&title=${msg.title}`}>{msg.title}</Link>
    
    {/* search参数无需声明接受 */}
    <Route path="/home/message/detail" component={Detail} />
    

    传递的参数被保存为this.props.location.search字符串,使用querystring库的parse解析为对象

    import qs from 'querystring'
    
    const {id, title} = qs.parse(this.props.location.search.slice(1))
    
  3. state参数

    state参数不会展示在地址栏中

    <Link to={{pathname: '/home/message/detail', state: {id: msg.id, title: msg.title}}}>{msg.title}</Link>
    
    {/* state参数无需声明接受 */}
    <Route path="/home/message/detail" component={Detail} />
    

    传递的参数会被保存到this.props.location.state

    const {id, title} = this.props.location.state
    

# 5.6 push与replace

react-router-dom利用的是浏览器的historypushreplace默认为pushLink中提供属性replace={true}来进行切换到replace模式

<Link replace={true} to='xxx'>xxx</Link>

# 5.7 编程式路由导航

# 5.7.1 push与replace导航

  1. replace手动导航

    <button onClick={this.replaceShow(msg.id, msg.title)}>replace查看</button>
    replaceShow = (id, title) => {
        // 实现跳转到Detail组件,且为replace跳转
        return () => {
            this.props.history.replace(`/home/message/detail/${id}/${title}`)
        }
    }
    
  2. push手动导航

    <button onClick={this.pushShow(msg.id, msg.title)}>push查看</button>
    pushShow = (id, title) => {
        // 实现跳转到Detail组件,且为push跳转
        return () => {
        	this.props.history.push(`/home/message/detail/${id}/${title}`)
        }
    }
    

对于paramssearch不同的参数传递,使用不同的路径即可,state参数需要在replacepush的第二个参数中传递

<button onClick={this.pushShow(msg.id, msg.title)}>push查看</button>
pushShow = (id, title) => {
    // push跳转,携带params参数
    return () => {
    	this.props.history.push(`/home/message/detail`, {id: id, title: title})
    }
}

# 5.7.2 goBack和goForward

  1. goBack回退

    <button onClick={this.back}>回退</button>
    back = () => {
    	this.props.history.goBack()
    }
    
  2. goForward前进

    <button onClick={this.forward}>前进</button>
    forward = () => {
    	this.props.history.goForward()
    }
    
  3. go根据传递的参数的值来进行回退或前进

    <button onClick={this.go}>go</button>
    go =() => {
        // 回退两步
    	this.props.history.go(-2)
    }
    

# 5.7.3 withRouter

对于路由组件this.props上的属性historylocationmatch属性只能在路由组件上使用,因为一般组件没有这些属性

withRouterreact-dom-router提供的一个函数,通过withRouter包裹的一般组件称为路由组件

import {withRouter} from 'react-router-dom'

class Header extends Component {

    back = () => {
        this.props.history.goBack()
    }
    
    render() {
        return (
            <div className="page-header">
                <button onClick={this.back}>回退</button>
            </div>
        )
    }
}
export default withRouter(Header)

# 5.8 history与hash

hsitoryhash的区别即是BrowserRouterHashRouter的区别

  1. 底层原理不一样

    BrowserRouter使用的是H5hsiroty API,不兼容IE9及以下的版本,HashRouter使用的是URL的哈希值

  2. path表现形式不一致

    BrowserRouter的路径中没有#,例如:localhost:3000/demo/test

    HashRouter的路径包含#,例如:localhost:3000/#/demo/test

  3. 刷新后对路由state参数的影响

    BrowserRouter没有任何影响,因为state保存在history对象中

    HashRouter刷新后会导致路由state参数的丢失

  4. HashRouter可以用于解决一些路径错误的问题,例如:样式丢失

# 6. ant-design组件

国内蚂蚁金服的开源react组件库

# 6.1 基本使用

  1. 安装

    npm install antd
    
  2. 使用

    import React, { Component } from 'react'
    
    // 导入需要的组件
    import { Button } from 'antd'
    import { WechatOutlined } from '@ant-design/icons'
    
    // 导入组件的样式
    import 'antd/dist/antd.css'
    export default class App extends Component {
        render() {
            return (
                <div>
                    <Button type="primary">Primary Button</Button>
    
                    <WechatOutlined />
                </div>
            )
        }
    }
    

# 6.2 按需引入样式

  1. 安装react-app-rewiredcustomize-cra

    npm install react-app-rewired customize-cra
    
  2. 安装babel-plugin-import

    npm install babel-plugin-import
    
  3. 修改package.json

    "scripts": {
    -   "start": "react-scripts start",
    -   "build": "react-scripts build",
    -   "test": "react-scripts test",
    +   "start": "react-app-rewired start",
    +   "build": "react-app-rewired build",
    +   "test": "react-app-rewired test",
    }
    
  4. 根目录新建config-overrides.js

    const { override, fixBabelImports } = require('customize-cra');
    
    module.exports = override(
    	fixBabelImports('import', {
    		libraryName: 'antd',
    		libraryDirectory: 'es',
    		style: 'css',
    	}),
    );
    
  5. 配置完成之后,组件不需要再引入样式

# 6.3 自定义样式

  1. 由于antd官方使用less编写样式,要想修改样式需要安装lessless-loader

    npm install less less-loader
    
  2. 修改config-overrides.js配置文件

    const { override, fixBabelImports, addLessLoader  } = require('customize-cra');
    
    module.exports = override(
    	fixBabelImports('import', {
    		libraryName: 'antd',
    		libraryDirectory: 'es',
    		style: true,
    	}),
    	addLessLoader({
    		lessOptions: {
    			javascriptEnabled: true, // 允许使用js修改主题颜色
    			modifyVars: { '@primary-color': 'orange' }, // 修改主题的颜色
    		}
    	}),
    );
    
    // 使用less的配置
    const {
        override,
        fixBabelImports,
        addLessLoader,
        adjustStyleLoaders,
    } = require('customize-cra');
      
    module.exports = override(
        // 针对antd 实现按需打包:根据import来打包 (使用babel-plugin-import)
        fixBabelImports('import', {
            libraryName: 'antd',
            libraryDirectory: 'es',
            style: true, //自动打包相关的样式 默认为 style:'css'
        }),
        // 使用less-loader对源码重的less的变量进行重新制定,设置antd自定义主题
        addLessLoader({  
            lessOptions: { 
                javascriptEnabled: true,
                modifyVars: { '@primary-color': '#1DA57A' },
            },
            sourceMap:true,
        }),
        adjustStyleLoaders(({ use: [ , css] }) => {
            css.options.sourceMap = true;
            css.options.modules = {
                // 配置默认的样式名称规则
                localIdentName:'[name]__[local]--[hash:base64:5]',
                getLocalIdent:(loaderContext, localIdentName, localName, options) => {
                    // 处理antd 的样式
                    if (loaderContext.resourcePath.includes('node_modules')) {
                        return localName;
                    }       
                }
            } 
        })
      )
    

# 7. redux

# 7.1 redux理解

  1. redux是一个专门用于做状态管理的JS库(不是react插件库)
  2. 它可以用在reactangularvue等项目中,但基本与react配合使用
  3. 作用:集中式管理react应用中多个组件共享的状态

# 7.1.1 使用redux的情况

  1. 某个组件的状态,需要让其他组件可以随时拿到
  2. 一个组件需要改变另一个组件的状态
  3. 总体原则:能不用就不用,如果不用比较吃力才考虑使用

# 7.1.2 redux工作流程

image-20210107212920419

# 7.2 redux的三个核心概念

# 7.2.1 action

  1. 动作的对象

  2. 包含2个属性

    • type:标识属性,值为字符串,唯一,必要属性
    • data:数据属性,值类型任意,可选属性
  3. 例子

    {type: 'ADD_STUDENT', data: {name: 'tom', age: 18}}
    

# 7.2.2 reducer

  1. 用于初始化状态、加工状态
  2. 加工时,根据旧的stateaction,产生新的state的纯函数

# 7.2.3 store

  1. stateactionreducer联系在一起的对象

  2. 如何得到此对象

    安装

    npm install redux
    

    使用

    import {createStore} from 'redux'
    import reducer from './reducers'
    const store = createStore(reducer)
    
  3. 此对象的功能?

    • getState(): 得到state

    • dispatch(action): 分发action, 触发reducer调用, 产生新的state

    • subscribe(listener): 注册监听, 当产生了新的state时, 自动调用

# 7.3 基本使用

# 7.3.1 redux精简版

  1. 去除Count组件的自身状态

  2. src下建立

    - src
    	- redux
    		- store.js
    		- count_reducer.js
    
  3. store.js

    1. 引入redux中的createStore函数,创建一个store

      import { createStore } from 'redux'
      import countReducer from './count_reducer'
      
    2. createStore调用时要传入一个为其服务的reducer,并将返回的结果暴露出去

      export default createStore(countReducer)
      
  4. count_reducer.js

    1. reducer的本质是一个函数,接受preStateaction返回加工后的状态

    2. reducer有两个作用:初始化状态,加工状态

    3. reducer被第一次调用时,是store自动触发的,传递的preStateundefinded

    4. 完整代码

      // 程序初始化时preState为undefined,赋初值为0
      export default function countReducer(preState = 0, action) {
      
          // 从action对象中获取:type, data
          const {type, data} = action
      
          // 根据type决定如何加工数据
          switch(type) {
              // 加
              case 'increment':
                  return preState + data
      
              // 减
              case 'decrement':
                  return preState - data
      
              default:
                  return preState
          }
      }
      
  5. index.js中检测store中状态的改变,一旦发生改变重新渲染<App />

    import React from 'react'
    import ReactDOM from 'react-dom'
    
    import store from './redux/store'
    import App from './App'
    
    ReactDOM.render(<App />, document.getElementById('root'))
    
    store.subscribe(() => {
        // 由于虚拟DOM的性质,状态发生改变的组件,才会重新调用render渲染
        ReactDOM.render(<App />, document.getElementById('root'))
    })
    

    redux只负责管理状态,至于状态的改变驱动着页面的展示,要自己通知react调用render

  6. Count组件

    export default class Count extends Component {
    
        // 加操作
        increment = () => {
            const {value} = this.selectNumber
            store.dispatch(createIncrementAction(value * 1))
        }
    
        // 减操作
        decrement = () => {
            const {value} = this.selectNumber
            store.dispatch(createDecrementAction(value * 1))
        }
    
        // 奇数加
        incrementIfOdd = () => {
            const count = store.getState()
            if(count % 2 !== 0) {
                const {value} = this.selectNumber
                store.dispatch(createIncrementAction(value * 1))
            }
        }
    
        // 异步加
        incrementAsync = () => {
            const {value} = this.selectNumber
            setTimeout(() => {
                store.dispatch(createIncrementAction(value * 1))
            }, 2000)
        }
        render() {
            return (
                <div>
                    <h1>当前求和为:{store.getState()}</h1>
                    <select ref={c => this.selectNumber = c}>
                        <option value="1">1</option>
                        <option value="2">2</option>
                        <option value="3">3</option>
                    </select>&nbsp;
                    <button onClick={this.increment}>+</button>&nbsp;
                    <button onClick={this.decrement}>-</button>&nbsp;
                    <button onClick={this.incrementIfOdd}>当前和为奇数再+</button>&nbsp;
                    <button onClick={this.incrementAsync}>异步+</button>
                </div>
            )
        }
    }
    

# 7.3.2 redux完整版

相对于精简版增加两个文件

  1. constant.js放置由于编码疏忽写错action中的type

    export const INCREMENT = 'increment'
    export const DECREMENT = 'decrement'
    
  2. count_action.js专门用于创建action对象

    import {INCREMENT, DECREMENT} from './constant'
    
    export const createIncrementAction = (data) => {
     return {type: INCREMENT, data: data}
    }
    
    export const createDecrementAction = (data) => {
     return {type: DECREMENT, data: data}
    }
    

# 7.3.3 同步和异步action

如果一个action返回的是一个普通对象,则为同步action;如果返回一个函数,则为异步action

对于异步action需要中间件redux-thunk的支持

  1. 明确:延迟的动作不想交给组件自身,想交给action

  2. 何时需要异步action:想要对状态进行操作,但是具体的数据靠异步任务返回

  3. 具体编码:

    安装redux-thunk

    npm install redux-thunk
    

    store.js中使用redux-thunk处理异步任务

    // 引入createStore,用于创建redux中最为核心的store对象
    import { createStore, applyMiddleware } from 'redux'
    
    // 引入redux-thunk,用于支持异步action
    import thunk from 'redux-thunk'
    
    import countReducer from './count_reducer'
    
    export default createStore(countReducer, applyMiddleware(thunk))
    

    创建action的函数不再返回一个一般对象,而是返回一个函数,该函数中写异步任务count_action.js

    export const createIncrementAsyncAction = (data, time) => {
        return (dispatch) => {
            setTimeout(() => {
                // 通知redux修改状态
                dispatch(createIncrementAction(data))
            }, time)
        }
    }
    

    组件调用

    incrementAsync = () => {
        const {value} = this.selectNumber
        store.dispatch(createIncrementAsyncAction(value * 1, 500))
    }
    

    等到异步任务有结果时,分发一个同步的action去真正操作数据

  4. 异步action不是必须的,完全可以自己等待异步任务的结果,再去分发同步action

# 7.4 react-redux

react-reduxreact官方出的一个类似于redux的状态管理插件

image-20210108215036857

# 7.4.1 基本理解

  1. 所有的UI组件都应该包裹在一个容器组件,它们是父子关系
  2. 容器组件是真正和redux打交道的,里面可以随意的使用reduxapi
  3. UI组件中不能使用任何reduxapi
  4. 容器组件会传递给UI组件:
    • redux中所保存的状态
    • 用于操作状态的方法
  5. 容器给UI传递:状态、操作状态的方法,均通过props传递

# 7.4.2 基本使用

  1. 安装

    npm install react-redux
    
  2. src下创建容器组件

    - src
      - containers
        - Count
          index.jsx
    
  3. 引入相关依赖

    // 引入Count的UI组件
    import CountUI from '../../components/Count'
    
    // 引入action
    import { 
        createIncrementAction, 
        createIncrementAsyncAction, 
        createDecrementAction 
    } from "../../redux/count_action";
    
    // 引入connect用于连接UI组件与redux
    import {connect} from 'react-redux'
    
  4. 编写mapStateToProps函数

    function mapStateToProps(state) {
        return {count: state}
    }
    

    mapStateToProps函数的返回的是一个对象

    对象中的key就作为传递给UI组件propskeyvalue就作为传递给UI组件propsvalue——状态

    mapStateToProps用于传递状态

  5. 编写mapDispatchToProps函数

    function mapDispatchToProps(dispatch) {
        return {
            increment: (data)=> {
                // 通知redux执行加法
                dispatch(createIncrementAction(data))
            },
            decrement: (data) => {
                dispatch(createDecrementAction(data))
            },
            incrementAsync: (data, time) => {
                dispatch(createIncrementAsyncAction(data, time))
            }
        }
    }
    

    mapDispatchToProps函数的返回的是一个对象

    对象中的key就作为传递给UI组件propskeyvalue就作为传递给UI组件propsvalue——操作状态的方法

    mapDispatchToProps用于传递操作状态的方法

  6. 创建并暴露一个Count组件

    export default connect(mapStateToProps, mapDispatchToProps)(CountUI)
    
  7. 在引入Count组件中改为,引入container中的Count并传入store

    import React, { Component } from 'react'
    
    // 引入容器组件
    import Count from './containers/Count'
    
    // react-redux需要在组件中使用props传递store
    import store from './redux/store'
    
    export default class App extends Component {
        render() {
            return (
                <div>
                    {/* 给容器组件传递store */}
                    <Count store={store}/>
                </div>
            )
        }
    }
    
  8. 容器组件优化

    // 引入Count的UI组件
    import CountUI from '../../components/Count'
    
    // 引入action
    import { 
        createIncrementAction, 
        createIncrementAsyncAction, 
        createDecrementAction 
    } from "../../redux/count_action";
    
    // 引入connect用于连接UI组件与redux
    import {connect} from 'react-redux'
    
    // 创建并暴露一个Count组件
    export default connect(
        state => ({count: state}),
        
        // mspDispatchToProps的一般写法
        // dispatch => ({
        //     increment: (data)=> {
        //         // 通知redux执行加法
        //         dispatch(createIncrementAction(data))
        //     },
        //     decrement: (data) => {
        //         dispatch(createDecrementAction(data))
        //     },
        //     incrementAsync: (data, time) => {
        //         dispatch(createIncrementAsyncAction(data, time))
        //     }
        // })
    
        // mapDispatchToProps的简写,传送action,react-redux自动dispatch
        {
            increment: createIncrementAction,
            decrement: createDecrementAction,
            incrementAsync: createIncrementAsyncAction
        }
    )(CountUI)
    

# 7.4.3 优化

  1. 如果使用了react-redux,那么在index.js中就不需要使用store.subscribe监控App组件的变化,因为容器组件会自动监控状态的变化

  2. Provider组件的使用

    使用Provider可以自动传递store而不需要在使用容器组件的地方自己传递store

    修改index.js

    import React from 'react'
    import ReactDOM from 'react-dom'
    
    import App from './App'
    
    import store from './redux/store'
    import { Provider } from 'react-redux'
    
    ReactDOM.render(
        <Provider store={store}>
            <App />
        </Provider>, 
        document.getElementById('root')
    )
    
  3. 一般将UI组件与容器组件整合为一个文件,UI组件不必暴露,暴露容器组件

# 7.4.4 多组件数据共享

  1. 每一个组件都需要定义一个actionreducer

  2. 多个reducer中需要在store.js中使用redux中的combineReducers合并成一个reducer

    // 引入createStore,用于创建redux中最为核心的store对象, combineReducers用于合并reducers
    import { createStore, applyMiddleware, combineReducers } from 'redux'
    
    // 引入redux-thunk,用于支持异步action
    import thunk from 'redux-thunk'
    
    // 引入reducers
    import countReducer from './reducers/count'
    import personReducer from './reducers/person'
    
    // 汇总reducers
    const allReducer = combineReducers({
        count: countReducer,
        persons: personReducer
    })
    export default createStore(allReducer, applyMiddleware(thunk))
    

# 7.4.5 redux调试工具

  1. 扩展商店安装Redux DevTools

  2. 项目安装支持库

    npm install redux-devtools-extension
    
  3. store.js中引入并使用

    import { composeWithDevTools } from 'redux-devtools-extension'
    
    //无异步的情况
    // export default createStore(allReducer, composeWithDevTools())
    
    // 有异步的情况
    export default createStore(allReducer, composeWithDevTools(applyMiddleware(thunk)))
    

# 8. 项目打包

  1. 执行npm build

    npm run build
    
  2. 安装临时查看服务器

    npm install -g serve
    
  3. 进入打包文件夹build

    serve
    

# 9. react扩展

# 9.1 setState

setState更新状态有2种写法

  1. setState(stateChange, [callback])------对象式的setState

    stateChange为状态改变对象(该对象可以体现出状态的更改)

    callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用

  2. setState(updater, [callback])------函数式的setState

    updater为返回stateChange对象的函数。

    updater可以接收到stateprops

    callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。

  3. 总结:

    对象式的setState是函数式的setState的简写方式(语法糖)

    使用原则:

    • 如果新状态不依赖于原状态 ===> 使用对象方式
    • 如果新状态依赖于原状态 ===> 使用函数方式
    • 如果需要在setState()执行后获取最新的状态数据, 要在第二个callback函数中读取

# 9.2 lazyLoad

路由组件的lazyLoad

  1. 通过Reactlazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包

    const Login = lazy(()=>import('@/pages/Login'))
    
  2. 通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面

    <Suspense fallback={<h1>loading.....</h1>}>
        <Switch>
            <Route path="/xxx" component={Xxxx}/>
            <Redirect to="/login"/>
        </Switch>
    </Suspense>
    

# 9.3 Hooks

  1. React Hook/Hooks是什么?

    • HookReact 16.8.0版本增加的新特性/新语法
  • 可以让你在函数组件中使用 state以及其他的 React特性
  1. 三个常用的Hook

    State Hook

    React.useState()
    

    Effect Hook

    React.useEffect()
    

    Ref Hook

    React.useRef()
    
  2. State Hook

    State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作

    语法:const [xxx, setXxx] = React.useState(initValue)

    useState()说明: 参数: 第一次初始化指定的值在内部作缓存 返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数

    setXxx()2种写法: setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值 setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值

  3. Effect Hook

    Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)

    React中的副作用操作: 发ajax请求数据获取 设置订阅 / 启动定时器 手动更改真实DOM

    语法和说明:

    useEffect(() => { 
        // 在此可以执行任何带副作用操作
        return () => { // 在组件卸载前执行
        	// 在此做一些收尾工作, 比如清除定时器/取消订阅等
        }
    }, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
    

    可以把 useEffect Hook 看做如下三个函数的组合

    componentDidMount()
    componentDidUpdate()
    componentWillUnmount() 
    
  4. Ref Hook

    Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据

    语法: const refContainer = useRef()

    作用:保存标签对象,功能与React.createRef()一样

# 9.4 Fragment

使用

<Fragment><Fragment>

<></>

作用

可以不用必须有一个真实的DOM根标签了


# 9.5 Context

理解

一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信

使用

  1. 创建Context容器对象:

    const XxxContext = React.createContext() 
    
  2. 渲染子组时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据

    <xxxContext.Provider value={数据}>
    	子组件
    </xxxContext.Provider>
    
  3. 后代组件读取数据

    第一种方式:仅适用于类组件

    static contextType = xxxContext  // 声明接收context
    this.context // 读取context中的value数据
    

    第二种方式: 函数组件与类组件都可以

    <xxxContext.Consumer>
        {
            value => ( // value就是context中的value数据
            	要显示的内容
            )
        }
    </xxxContext.Consumer>
    

注意

在应用开发中一般不用context, 一般都用它的封装react插件

# 9.6 组件优化

Component的2个问题

  1. 只要执行setState(),即使不改变状态数据, 组件也会重新render() ==> 效率低

  2. 只当前组件重新render(), 就会自动重新render子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低

效率高的做法

只有当组件的state或props数据发生改变时才重新render()

原因

Component中的shouldComponentUpdate()总是返回true

解决

办法1: 
	重写shouldComponentUpdate()方法
	比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false
办法2:  
	使用PureComponent
	PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true
	注意: 
		只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false  
		不要直接修改state数据, 而是要产生新数据
项目中一般使用PureComponent来优化

# 9.7 render props

如何向组件内部动态传入带内容的结构(标签)?

Vue中: 
	使用slot技术, 也就是通过组件标签体传入结构  <A><B/></A>
React中:
	使用children props: 通过组件标签体传入结构
	使用render props: 通过组件标签属性传入结构,而且可以携带数据,一般用render函数属性

children props

<A>
  <B>xxxx</B>
</A>
{this.props.children}
问题: 如果B组件需要A组件内的数据, ==> 做不到 

render props

<A render={(data) => <C data={data}></C>}></A>
A组件: {this.props.render(内部state数据)}
C组件: 读取A组件传入的数据显示 {this.props.data} 

# 9.8 错误边界

# 理解:

错误边界(Error boundary):用来捕获后代组件错误,渲染出备用页面

# 特点:

只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误

# 使用方式:

getDerivedStateFromError配合componentDidCatch

// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
    console.log(error);
    // 在render之前触发
    // 返回新的state
    return {
        hasError: true,
    }
}

componentDidCatch(error, info) {
    // 统计页面的错误。发送请求发送到后台去
    console.log(error, info);
}

# 9.9 组件通信方式总结

# 组件间的关系:

  • 父子组件
  • 兄弟组件(非嵌套组件)
  • 祖孙组件(跨级组件)

# 几种通信方式:

	1.props:
		(1).children props
		(2).render props
	2.消息订阅-发布:
		pubs-sub、event等等
	3.集中式管理:
		redux、dva等等
	4.conText:
		生产者-消费者模式

# 比较好的搭配方式:

	父子组件:props
	兄弟组件:消息订阅-发布、集中式管理
	祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)
最后修改时间: 5 minutes ago