React基础学习
yatbfm

1 jsx语法规则

  1. 定义虚拟dom时,不要写引号。

    const vdom = <h1>lishan</h1>

  2. 标签中引入js表达式要用{}

  3. 样式的类名指定不要用class,要用className

  4. 内联样式要用style={{key: value}}的形式,第一层括号表示引用js表达式,第二层括号表示是一个对象

  5. 只有一个根标签

  6. 标签必须闭合

  7. 标签首字母

    1. 若小写字母开头,则将该标签转为html同名元素,若html无同名元素,则报错
    2. 若大写字母开头,react渲染对应的组件,若组件没定义,则报错

2 组件

2.1 函数式组件

1
2
3
4
5
6
function MyComponent() {
console.log(this) // this是undefined,因为babel编译后开启了严格模式,不能指向window
return <h1>函数式定义的组件</h1>
}
// test为定义的id为test的容器
ReactDOM.render(<MyComponent/>, docutment.getElementById("test"))
  1. React解析组件标签,找到了MyCompoent组件。
  2. 发现组件是函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,然后渲染到页面中。

2.2 类式组件

1
2
3
4
5
6
7
8
class MyComponent extends React.Component {
render() {
// render中的this是指向MyComponent的实例对象
return <h2>类式组件</h2>
}
}
// 此render和类中的render不一样,只是名字相同
ReactDOM.render(<MyComponent/>, docutment.getElementById("test"))
  1. React解析组件标签,找到了MyCompoent组件。
  2. 发现组件是类定义的,随后new出该类的实例,并通过该实例调用原型的render方法。
  3. 将类中render返回的虚拟DOM转为真实DOM,然后渲染到页面中。

3 组件实例的三大核心属性

3.1 state

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Weather extends React.Component {
constructor(props) {
super(props)
// state必须是对象形式
this.state = {weather: '炎热'}
}
render() {
// 事件绑定onClick,和原生的onclick有所不同,同时要注意{}中传入的是js表达式,不能写demo(),因为这样会直接调用demo()将返回值传给onClick,应该写demo传函数名
return <h2 onClick={demo}>天气:{this.state.weather}</h2>
}
}
// 此render和类中的render不一样,只是名字相同
ReactDOM.render(<Weather/>, docutment.getElementById("test"))

function demo() {
console.log("标题被点击了")
}

解决changeWeatherthis指向问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Weather extends React.Component {
constructor(props) {
super(props)
// state必须是对象形式
this.state = {weather: '炎热'}
// 解决changeWeather中的this指向问题
this.changeWeather = this.changeWeather.bind(this)
}
// 调用1+n次,初始化调用1次,后续的是修改state后重新渲染
render() {
// this.changeWeather并不是调用函数,只是赋值,通过onClick调用函数中的this并不是指向实例对象
return <h2 onClick={this.changeWeather}>天气:{this.state.weather}</h2>
}
changeWeather() {
// 自定义方法中的this并不是指向Weather实例对象,因为不是通过实例对象调用
// changeWeather是作为onClick的回调,所以不是通过实例调用的,而是直接调用,类中的方法开启了局部严格模式,所以changeWeather中的this为undefined
console.log("标题被点击了")
// 这是错误写法,状态不可直接更改,直接修改不会影响到页面
// this.state.weather = "凉爽"
// 需要使用this中的setState函数修改,传入对象,指定需要修改的属性
this.setState({weather: "很热"})
}
}
// 此render和类中的render不一样,只是名字相同
ReactDOM.render(<Weather/>, docutment.getElementById("test"))

state简化方式

1
2
3
4
5
6
7
8
9
10
11
12
class Weather extends React.Component {
state = {weather: "炎热"}
render() {
return <h2 onClick={this.changeWeather}>天气:{this.state.weather}</h2>
}
// 箭头函数中没有this,如果使用this,会将this赋值给箭头函数外侧的函数的this指向
changeWeather = () => {
console.log("标题被点击了")
this.setState({weather: "很热"})
}
}
ReactDOM.render(<Weather/>, docutment.getElementById("test"))

3.2 props

在组件标签中写属性,会传入到对象中的props中。

1
2
3
4
5
6
class Weather extends React.Component {
render() {
return <h2>天气:{this.props.name}</h2>
}
}
ReactDOM.render(<Weather name="炎热"/>, docutment.getElementById("test"))

批量props传入

1
2
3
4
5
6
7
8
9
class Weather extends React.Component {
render() {
return <h2>天气:{this.props.name}</h2>
}
}
const w = {name: "凉爽"}
// 其中...w相当于name={w.name}
// ...展开运算符展开对象只适用于标签属性的传递,别的情况不适用
ReactDOM.render(<Weather {...w}/>, docutment.getElementById("test"))

props的限制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Person extends React.Component {
render() {
return (
<h2>年龄:{this.props.age}</h2>
<h2>名字:{this.props.name}</h2>
)
}
}
// 设置规则
Person.propTypes = {
// name: React.PropTypes.string // React16及以后新版本不支持这个写法
name: PropTypes.string.isRequired, // name属性为字符串类型且必须
age: PropTypes.number // age属性为number,如果不传,默认值为18
speak: PropTypes.func // 注意不要用function,因为function和js的function关键字冲突,因此改为func
}
Person.defaultProps = {
age: 18 // 默认值
}
// 传入的是18的字符串
// ReactDOM.render(<Person age="18"/>, docutment.getElementById("test"))
// 传入的是18的number类型
ReactDOM.render(<Person age={18} speak={speak}/>, docutment.getElementById("test"))
function speak() {
console.log("说话")
}

props的简写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Person extends React.Component {
render() {
return (
<h2>年龄:{this.props.age}</h2>
<h2>名字:{this.props.name}</h2>
)
}
// 使用static给类自身加属性,需要使用static,否则是给实例对象加属性
static propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
speak: PropTypes.func
}
static defaultProps = {
age: 18
}
}

ReactDOM.render(<Person age={18} speak={speak}/>, docutment.getElementById("test"))
function speak() {
console.log("说话")
}

函数式组件使用props

1
2
3
4
5
6
7
function MyComponent(props) {
return <h1>{props.name}</h1>
}
MyComponent.propTypes = {
name: PropTypes.string.isRequired
}
ReactDOM.render(<MyComponent name="lishan"/>, docutment.getElementById("test"))

3.3 refs

字符串形式的refs(不推荐使用,大量使用会有较大的效率问题)

1
2
3
4
5
6
7
8
9
10
class Demo extends React.Component {
testRef = () => {
console.log(this.refs.testh2)
}
render() {
return (
<h2 ref="testh2" onClick={this.testRef}>test</h2>
)
}
}

回调函数形式的refs

1
2
3
4
5
6
7
8
9
10
11
class Demo extends React.Component {
testRef = () => {
console.log(this.testh2)
}
render() {
return (
// ref中的是回调函数,React会自动调用
<h2 ref={c => this.testh2 = c} onClick={this.testRef}>test</h2>
)
}
}

回调函数形式的refs中的回调函数执行次数:

如果ref回调函数以内联函数方式定义,在更新过程中会执行两次,第一次传入参数为null,然后第二次传入参数DOM元素。通过将ref的回调函数定义成class的绑定函数的方式避免上述问题。但这种影响不是很大,简单起见可以写成内联函数样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Demo extends React.Component {
testRef = () => {
console.log(this.testh2)
}

saveTest = (c) => {
this.testh2 = c
}

render() {
return (
// jsx中注释标签,需要采用这种方式,转成js再进行注释
{/* <h2 ref={c => this.testh2 = c} onClick={this.testRef}>test</h2> */}
<h2 ref={this.saveTest} onClick={this.testRef}>test</h2>
)
}
}

createRef的使用(当前React官方最推荐的方式)

1
2
3
4
5
6
7
8
9
10
11
12
class Demo extends React.Component {
// 只能存储一个节点标签
testh2 = React.createRef()
testRef = () => {
console.log(this.testh2.current)
}
render() {
return (
<h2 ref={this.testh2} onClick={this.testRef}>test</h2>
)
}
}

不要过度使用refs

4 React事件处理

  1. 通过onXxx属性指定事件处理函数
    1. React使用的是自定义(合成)事件,而不是原生的DOM事件
    2. React中的事件是通过委托方式处理的(委托给组件最外层的元素)
  2. 通过event.target得到发生事件的DOM元素对象(可以省略部分ref的使用)
1
2
3
4
5
6
7
8
9
10
class Demo extends React.Component {
testRef = (event) => {
console.log(event.target)
}
render() {
return (
<h2 onClick={this.testRef}>test</h2>
)
}
}

5 受控组件和非受控组件

5.1 非受控组件

数据“现用现取”

1
2
3
4
5
6
7
8
9
class Demo extends React.Component {
testNode = React.createRef()
// 需要使用时通过ref获取节点从而获取值
render() {
return (
<input ref={this.testNode} type="text"/>
)
}
}

5.2 受控组件

例如,input输入的数据,每次改变时将数据维护到state中,当需要时直接从state中获取

1
2
3
4
5
6
7
8
9
10
11
12
13
class Demo extends React.Component {
state = {
username: "default"
}
saveInput = (event) => {
this.setState({username: event.target.value})
}
render() {
return (
<input onChange={this.saveInput} type="text"/>
)
}
}

🎉 推荐使用受控组件,减少ref的使用。

6 高阶函数和柯里化

6.1 高阶函数

如果需要实时保存很多输入的数据,并且保存的方式都相同,那么下面这种方式会很冗余麻烦。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Demo extends React.Component {
state = {
username: "default",
password: "password"
}
saveUsername = (event) => {
this.setState({username: event.target.value})
}
savePassword = (event) => {
this.setState({password: event.target.value})
}
render() {
return (
<div>
<input onChange={this.saveUsername} type="text"/>
<input onChange={this.savePassword} type="password"/>
</div>
)
}
}

可以采用给方法传参方式标识需要保存的数据是哪个类型(属性)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Demo extends React.Component {
state = {
username: "default",
password: "password"
}
// 柯里化
saveInfo = (dataInfo) => {
// 这里返回一个函数给React调用
return (event) => {
// 必须加上[]读出dataInfo变量的值,否则会认为dataInfo是一个新的属性等价于{"dataInfo": event.target.value}
this.setState({[dataInfo]: event.target.value})
}
}
render() {
return (
<div>
<input onChange={this.saveInfo("username")} type="text"/>
<input onChange={this.saveInfo("password")} type="password"/>
</div>
)
}
}

高阶函数的定义:

  1. 若函数接收的参数是一个函数,那该函数是高阶函数
  2. 若函数返回值是一个函数,该函数是高阶函数

6.2 柯里化

函数的柯里化:通过函数调用继续返回函数的形式,实现多次接收参数最后统一处理的函数编码形式。

1
2
3
4
5
6
7
8
// 柯里化
saveInfo = (dataInfo) => {
// 这里返回一个函数给React调用
return (event) => {
// 必须加上[]读出dataInfo变量的值,否则会认为dataInfo是一个新的属性等价于{"dataInfo": event.target.value}
this.setState({[dataInfo]: event.target.value})
}
}

不使用柯里化的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Demo extends React.Component {
state = {
username: "default",
password: "password"
}
saveInfo = (dataInfo, event) => {
this.setState({[dataInfo]: event.target.value})
}
render() {
return (
<div>
<input onChange={(event) => {this.saveInfo("username", event)}} type="text"/>
<input onChange={(event) => {this.saveInfo("password", event)}} type="password"/>
</div>
)
}
}

7 生命周期

7.1 卸载组件

1
ReactDOM.unmountComponentAtNode(document.getElementById("test"))

7.2 组件将要挂载周期

1
2
3
4
5
6
7
8
9
10
11
class Demo extends React.Component {
componentWillMount() {

}
// 初始化渲染,组件更新后调用
render() {
return (
<div>test</div>
)
}
}

7.3 组件完成挂载周期(常用)

1
2
3
4
5
6
7
8
9
10
11
12
class Demo extends React.Component {
// 初始化渲染,组件更新后调用
render() {
return (
<div>test</div>
)
}
// 组件挂载时只调用一次
componentDidMount() {

}
}

7.4 组件将要卸载周期(常用)

1
2
3
4
5
6
7
8
9
10
11
class Demo extends React.Component {
// 初始化渲染,组件更新后调用
render() {
return (
<div>test</div>
)
}
componentWillUnmount() {

}
}

7.5 是否应该组件更新周期

setState()直接触发该周期

1
2
3
4
5
6
7
8
9
10
11
12
13
class Demo extends React.Component {
// 该钩子必须返回true或者false,返回true表示页面应该更新,否则不更新。
// React默认实现该钩子返回true
shouldComponentUpdate() {
return true
}
// 初始化渲染,组件更新后调用
render() {
return (
<div>test</div>
)
}
}

7.6 组件将要更新周期

forceUpdate()直接触发该周期

1
2
3
4
5
6
7
8
9
10
class Demo extends React.Component {
componentWillUpdate() {
}
// 初始化渲染,组件更新后调用
render() {
return (
<div>test</div>
)
}
}

7.7 组件完成更新周期

1
2
3
4
5
6
7
8
9
10
11
class Demo extends React.Component {
// 初始化渲染,组件更新后调用
render() {
return (
<div>test</div>
)
}

componentDidUpdate(preProps, preState) {
}
}

7.8 组件将要接收Props周期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Demo extends React.Component {
state = {name: "lishan"}
change = () => {
// 执行后会触发state修改,父组件重新渲染(第一次渲染不会执行)后会执行子组件componentWillReceiveProps钩子及后续钩子
this.setState({name: "test"})
}
// 初始化渲染,组件更新后调用
render() {
return (
<div>
<B name={this.state.name}/>
<button onClick={this.change}>修改</button>
</div>
)
}
}

class B extends React.Component {
// 参数为传入的props()
componentWillReceiveProps(props) {

}
render() {
return <div>{this.props.name}</div>
}
}

7.9 新旧生命周期对比

新生命周期图:

image-20240515000505101

7.10 从Props获得派生状态周期

这个钩子使用极其罕见

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Demo extends React.Component {
state = {count: 0}
// 初始化渲染,组件更新后调用
render() {
return (
<div>test</div>
)
}
// 必须是静态方法,且要返回state对象或者null
// 如果state的值完全取决于props,可以使用该钩子,但是也可以使用构造函数替代,所以该钩子应当尽量避免使用
static getDerivedStateFromProps(props, state) {
return null
// 如果返回类似于state对象的值,会覆盖原有state对象中的属性
// return {count: 100}
}
}

7.11 更新前获得快照周期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Demo extends React.Component {
state = {count: 0}
// 初始化渲染,组件更新后调用
render() {
return (
<div>test</div>
)
}
// 返回的值(null或者快照值可以是数字、字符串,数组、对象)会作为参数传递给componentDidUpdate第三个参数
getSnapshotBeforeUpdate(preProps, preState) {
return null
}
componentDidUpdate(preProps, preState, snapshot) {

}
}

8 diffing算法

  1. 虚拟DOM中key的作用:

    1. 简单的说: key是虚拟DOM对象的标识, 在更新显示时key起着极其重要的作用。
    2. 详细的说: 当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】, 随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
      1. 旧虚拟DOM中找到了与新虚拟DOM相同的key:
        1. 若虚拟DOM中内容没变, 直接使用之前的真实DOM
        2. 若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM
      2. 旧虚拟DOM中未找到与新虚拟DOM相同的key根据数据创建新的真实DOM,随后渲染到到页面
  2. 用index作为key可能会引发的问题: 1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作: 会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。 2. 如果结构中还包含输入类的DOM: 会产生错误DOM更新 ==> 界面有问题。 3. 注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作, 仅用于渲染列表用于展示,使用index作为key是没有问题的。

  3. 开发中如何选择key?:

    1. 最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。

    2. 如果确定只是简单的展示数据,用index也是可以的。

9 脚手架

9.1 创建项目并启动

  1. 全局安装npm install -g create-react-app
  2. 在需要创建项目的目录下create-react-app hello-react
  3. 进入hello-react目录
  4. 命令行执行npm run start启动项目即可看到默认页面

9.2 样式的模块化

样式文件名要以xxx.module.css结尾

1
2
3
.color {
background-color: aqua;
}

jsx组件里引入

1
2
3
4
5
6
7
import hello from "./Hello.module.css"

function Hello() {
return <div className={hello.color}>Hello React! test</div>
}

export default Hello;

样式模块化主要是为了防止不同组件中样式名相同导致的样式覆盖问题。或者使用less嵌套编写样式。

9.3 vscode插件

vscode插件,提供一些代码片段,提高编码速度

9.4 组件化编码流程

  1. 拆分组件:拆分界面,抽取组件
  2. 实现静态组件:使用组件实现静态页面效果
  3. 实现动态组件:
    1. 动态显示初始化数据
      1. 数据类型
      2. 数据名称
      3. 保存在哪个组件
    2. 交互(从绑定事件监听开始)

10 消息订阅与发布pubsub

  1. 安装包

    npm install pubsub-js

  2. 引入

    import PubSub from "pubsub-js"

  3. 订阅消息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    import React, { Component } from "react"
    import PubSub from "pubsub-js"

    export default class Subscribe extends Component {
    state = { info: "lishan" }
    // 接收消息的方法参数为两个分别是消息名(主题名)和接受的实际数据
    receiveInfo = (msg, info) => {
    this.setState({ info })
    }
    // 组件挂载完成后订阅消息
    componentDidMount() {
    // 订阅哪个消息,以及用哪个方法处理该消息的数据,返回toekn表示该订阅的唯一标识符,方便后期取消订阅
    this.token = PubSub.subscribe("test", this.receiveInfo)
    }
    /*
    或者这么写,直接写在订阅里
    componentDidMount() {
    this.token = PubSub.subscribe("test", (msg, info) => {
    this.setState({ info })
    })
    }
    */

    // 组件将要卸载时取消订阅
    componentWillUnmount() {
    PubSub.unsubscribe(this.token)
    }

    render() {
    return <div>{this.state.info}</div>
    }
    }

  4. 发布消息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import PubSub from "pubsub-js"
    import React, { Component } from "react"

    export default class Publish extends Component {
    sendInfo = () => {
    // 发送消息,参数为消息名(主题名)和实际的数据
    PubSub.publish("test", "test PubSub")
    }

    render() {
    return (
    <div>
    <button onClick={this.sendInfo}>发送消息</button>
    </div>
    )
    }
    }

11 fetch(待更新)

12 React-Router 5

12.1 库安装

react-router总共分为三个版本,分别给web native和任意端使用,这里只安装web端,方便使用。

1
npm install react-router-dom

12.2 简单使用

当前版本为5版本,新版本会有不同

App.jsx代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React, { Component } from "react"
import { BrowserRouter as Router, Route, Link } from "react-router-dom"

import Home from "./components/Home"
import About from "./components/About"

export default class App extends Component {
render() {
return (
// 两个组件都需要包在Router标签里
<Router>
<div>
{/* Link组件控制跳转到哪个链接 */}
<Link to="/home">home</Link><br />
<Link to="/about">about</Link><br />
{/* Route组件控制链接对应的组件。当前是5版本的路由,6版本的路由会出现一些变化 */}
<Route path="/home" component={Home} />
<Route path="/about" component={About} />
</div>
</Router>
)
}
}

Home组件代码

1
2
3
4
5
6
7
import React, { Component } from "react"

export default class Home extends Component {
render() {
return <button>Home</button>
}
}

About组件代码

1
2
3
4
5
6
7
import React, { Component } from "react";

export default class About extends Component {
render() {
return <button>About</button>;
}
}

12.3 路由组件和一般组件

  1. 写法不同:

    一般组件:<Demo />

    路由组件:<Route path="/demo " component={Demo } />

  2. 存放位置不同

    一般组件:components

    路由组件:pages

  3. 接收到的props不同

    路由组件会收到以下内容(只列举出常用的):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    history: 
    action: "PUSH"
    block: ƒ block(prompt)
    createHref: ƒ createHref(location)
    go: ƒ go(n)
    goBack: ƒ goBack()
    goForward: ƒ goForward()
    length: 4
    listen: ƒ listen(listener)
    push: ƒ push(path, state)
    replace: ƒ replace(path, state)

    location:
    hash: ""
    key: "o2ti2g"
    pathname: "/home"
    search: ""
    state: undefined

    match:
    isExact: true
    params: {}
    path: "/home"
    url: "/home"

NavLink可以指定选中某个链接时使用哪个样式

<NavLink activeClassName="class-name" to="/home">home</NavLink>

封装NavLink组件

如果多个NavLink组件共用样式,会造成样式的代码冗余,对此进行封装为MyNavLink组件,代码为:

1
2
3
4
5
6
7
8
import React, { Component } from "react";
import { NavLink } from "react-router-dom";

export default class MyNavLink extends Component {
render() {
return <NavLink activeClassName="" className="" {...this.props} />
}
}

在其他组件使用时直接和NavLink组件一样使用即可:

<MyNavLink to="/home" otherParams={1}>Home</MyNavLink>

组件使用props传值时,除了可以自定义标签属性传值如:<Demo a={1} />,这样会将a传递到this.props.a中。也可以将标签体中的内容传递到props中,使用this.props.children属性保存标签体的内容,标签体存储的key这个是固定的名字无法修改。

自定义封装的MyNavLink组件,在标签体中写入内容会被传到this.props.children中,而children会传递给NavLink中的标签属性中的children,达到<NavLink>test</NavLink>的效果。

12.5 Switch组件

进行路由匹配时,会逐个比对匹配,如果有多个路径匹配则会展示多个组件页面,影响效率。此时在外层包裹Switch组件,保证匹配上一个路由后停止匹配。(因为多数情况下一个路径匹配一个组件)

1
2
3
4
<Switch>
<Route path="/home" component={Home} />
<Route path="/about" component={About} />
</Switch>

12.6 多级路由刷新页面央视丢失

  1. public/index.html中引入样式时不用.//
  2. public/index.html中引入样式时不用./%PUBLIC_URL%(常用),%PUBLIC_URL%脚手架提供的,表示绝对路径

12.7 路由模糊匹配与严格匹配

React默认开始模糊匹配

1
2
3
4
5
<Link to="/home">home</Link><br />
<Link to="/about/a/b">about</Link><br />
{/* Route组件控制链接对应的组件。当前是5版本的路由,6版本的路由会出现一些变化 */}
<Route path="/home" component={Home} />
<Route path="/about" component={About} />

如上所示的代码,默认模糊匹配时,链接设置的路径前缀包含路由的路径,则可以匹配上。

1
<Route exact path="/about" component={About} />

如果开启严格匹配则不能匹配上。

默认情况下不做修改,只用模糊匹配即可。如果出现使用模糊匹配导致页面有问题,再开启严格匹配。

12.8 Redirect重定向

1
2
3
4
<Route path="/home" component={Home} />
<Route path="/about" component={About} />
// 一般把Redirect组件写在所有路由组件最下方
<Redirect to="/home" />

12.9 嵌套路由

一级路由

1
2
3
4
<Switch>
<Route path="/home" component={Home} />
<Route path="/about" component={About} />
</Switch>

二级路由

1
2
3
4
5
6
7
// 注册子路由要写上父路由的path值
<MyNavLink to="/home/news">News</MyNavLink>
<MyNavLink to="/home/message">Message</MyNavLink>
<Switch>
<Route path="/home/news" component={News} />
<Route path="/home/message" component={Message} />
</Switch>

点击/home,路由匹配时,会按照注册顺序进行匹配,首先展示一级路由,路由按顺序匹配,匹配到了/home路径展示Home组件,该组件会有News Message两个路由组件。当点击/home/news时,根据路由的模糊匹配,首先会匹配到一级路由/home,渲染该组件到页面中,会把子组件也渲染,就会注册二级路由,进而进行路由匹配,会匹配到/home/news,展示News组件。

此时如果开启严格模式,则会导致不能使用二级路由,因为一级路由没有匹配上,二级路由无法注册,也无法匹配。

12.10 路由传参

  1. 传递params参数

    1
    2
    3
    4
    5
    6
    7
    8
    <MyNavLink to="/home/news">News</MyNavLink>
    // 通过在链接最后拼接的方式将参数携带到链接里
    <MyNavLink to={`/home/message/${infoId}`}>Message</MyNavLink>
    <Switch>
    <Route path="/home/news" component={News} />
    {/* 接收参数时使用 :infoId 的形式占位,表示接收参数并指定参数的名字 */}
    <Route path="/home/message/:infoId" component={Message} />
    </Switch>

    接收到的参数在组件里的this.props.match.params对象里。

  2. 传递search参数

    1
    2
    3
    4
    5
    6
    7
    8
    <MyNavLink to="/home/news">News</MyNavLink>
    // 通过?在链接最后拼接key=value&key=value的形式传参
    <MyNavLink to={`/home/message?infoId=${infoId}`}>Message</MyNavLink>
    <Switch>
    <Route path="/home/news" component={News} />
    {/* 注册路由无需改变 */}
    <Route path="/home/message" component={Message} />
    </Switch>

    接收到的参数保存在:this.props.location.search字符串里,是一个urlencoded编码的字符串,需要手动转成字典对象,可以使用第三方库querystring简称qs

  3. 传递state参数

    1
    2
    3
    4
    5
    6
    7
    <MyNavLink to="/home/news">News</MyNavLink>
    // 把to属性的值写成一个对象
    <MyNavLink to={{pathname: "/home/message", state: {infoId: 1}}}>Message</MyNavLink>
    <Switch>
    <Route path="/home/news" component={News} />
    <Route path="/home/message" component={Message} />
    </Switch>

    接收到的参数保存在this.props.location.state对象中。此种方式传递的参数不会显示在地址栏中,隐私性较高。刷新也会保留住参数。如果清除缓存,则不会保留住。

12.11 push和replace

默认使用push模式,切换路由会向历史栈中压栈,如果使用replace则会替换当前页面,不会压栈。

12.12 编程式路由导航

编程式代替Link组件。

1
2
3
4
5
6
7
8
9
// 传递params
this.props.history.push(`/home/message/${id}`)
this.props.history.replace(`/home/message/${id}`)
// 传递search
this.props.history.push(`/home/message?id=1`)
this.props.history.replace(`/home/message?id=1`)
// 传递state
this.props.history.push("/home/message", {id: 1})
this.props.history.replace("/home/message", {id: 1})

12.13 withRouter

主要用于一般组件中使用路由组件特有的方法如:push replace等。

1
2
3
4
5
6
7
import {withRouter} from "react-router-dom"

class Home extends Component {

}
// 到处前使用withRouter函数包装一下,即可使Home组件拥有路由组件的参数,即可使用push等方法
export default withRouter(Home)

13 redux(待更新)

14 扩展

14.1 setState

setState更新状态的2种写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
	(1). setState(stateChange, [callback])------对象式的setState
1.stateChange为状态改变对象(该对象可以体现出状态的更改)
2.callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用

(2). setState(updater, [callback])------函数式的setState
1.updater为返回stateChange对象的函数。
2.updater可以接收到state和props。
4.callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。
总结:
1.对象式的setState是函数式的setState的简写方式(语法糖)
2.使用原则:
(1).如果新状态不依赖于原状态 ===> 使用对象方式
(2).如果新状态依赖于原状态 ===> 使用函数方式
(3).如果需要在setState()执行后获取最新的状态数据,
要在第二个callback函数中读取

14.2 lazyLoad

路由组件的lazyLoad

1
2
3
4
5
6
7
8
9
10
//1.通过React的lazy函数配合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>

14.3 Hooks

  1. React Hook/Hooks是什么?
1
2
(1). Hook是React 16.8.0版本增加的新特性/新语法
(2). 可以让你在函数组件中使用 state 以及其他的 React 特性
  1. 三个常用的Hook
1
2
3
(1). State Hook: React.useState()
(2). Effect Hook: React.useEffect()
(3). Ref Hook: React.useRef()
  1. State Hook
1
2
3
4
5
6
7
8
(1). State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作
(2). 语法: const [xxx, setXxx] = React.useState(initValue)
(3). useState()说明:
参数: 第一次初始化指定的值在内部作缓存
返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
(4). setXxx()2种写法:
setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值
  1. Effect Hook
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(1). Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
(2). React中的副作用操作:
发ajax请求数据获取
设置订阅 / 启动定时器
手动更改真实DOM
(3). 语法和说明:
useEffect(() => {
// 在此可以执行任何带副作用操作
return () => { // 在组件卸载前执行
// 在此做一些收尾工作, 比如清除定时器/取消订阅等
}
}, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行

(4). 可以把 useEffect Hook 看做如下三个函数的组合
componentDidMount()
componentDidUpdate()
componentWillUnmount()
  1. Ref Hook
1
2
3
(1). Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
(2). 语法: const refContainer = useRef()
(3). 作用:保存标签对象,功能与React.createRef()一样

14.4 Fragment

使用

<Fragment>
</Fragment>
或者写<></>

作用

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


14.5 Context

理解

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

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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插件

14.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来优化

14.7 render props

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

Vue中: 
    使用slot技术, 也就是通过组件标签体传入结构  <AA><BB/></AA>
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} 

14.8 错误边界

理解:

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

特点:

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

使用方式:

getDerivedStateFromError配合componentDidCatch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
console.log(error);
// 在render之前触发
// 返回新的state
return {
hasError: true,
};
}

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

14.9 组件通信方式总结

方式:

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

组件间的关系

    父子组件:props
    兄弟组件(非嵌套组件):消息订阅-发布、集中式管理
    祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(用的少)

15 React-Router 6

15.1 概述

  1. React Router 以三个不同的包发布到 npm 上,它们分别为:

    1. react-router: 路由的核心库,提供了很多的:组件、钩子。
    2. react-router-dom:</strong > 包含react-router所有内容,并添加一些专门用于 DOM 的组件,例如 <BrowserRouter>
    3. react-router-native: 包括react-router所有内容,并添加一些专门用于ReactNative的API,例如:<NativeRouter>等。
  2. 与React Router 5.x 版本相比,改变了什么?

    1. 内置组件的变化:移除<Switch/> ,新增 <Routes/>等。

    2. 语法的变化:component={About} 变为 element={<About/>}等。

    3. 新增多个hook:useParamsuseNavigateuseMatch等。

    4. 官方明确推荐函数式组件了!!!

      ......

15.2 Component

  1. <BrowserRouter>

    1. 说明:<BrowserRouter>用于包裹整个应用。
    2. 示例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import React from "react";
    import ReactDOM from "react-dom";
    import { BrowserRouter } from "react-router-dom";

    ReactDOM.render(
    <BrowserRouter>
    {/* 整体结构(通常为App组件) */}
    </BrowserRouter>,root
    );

  2. <HashRouter>

    1. 说明:作用与<BrowserRouter>一样,但<HashRouter>修改的是地址栏的hash值。
    2. 备注:6.x版本中<HashRouter><BrowserRouter> 的用法与 5.x 相同。
  3. <Routes/> 与 <Route/>

    1. v6版本中移出了先前的<Switch>,引入了新的替代者:<Routes>
    2. <Routes><Route>要配合使用,且必须要用<Routes>包裹<Route>
    3. <Route> 相当于一个 if 语句,如果其路径与当前 URL 匹配,则呈现其对应的组件。
    4. <Route caseSensitive> 属性用于指定:匹配时是否区分大小写(默认为 false)。
    5. 当URL发生变化时,<Routes>都会查看其所有子<Route> 元素以找到最佳匹配并呈现组件 。
    6. <Route> 也可以嵌套使用,且可配合useRoutes()配置 “路由表” ,但需要通过 <Outlet> 组件来渲染其子路由。
    7. 示例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <Routes>
    {/* path属性用于定义路径,element属性用于定义当前路径所对应的组件 */}
    <Route path="/login" element={<Login />}></Route>

    {/* 用于定义嵌套路由,home是一级路由,对应的路径/home */}
    <Route path="home" element={<Home />}>
    {/* test1 和 test2 是二级路由,对应的路径是/home/test1 或 /home/test2 */}
    <Route path="test1" element={<Test/>}></Route>
    <Route path="test2" element={<Test2/>}></Route>
    </Route>

    {/* Route也可以不写element属性, 这时就是用于展示嵌套的路由 .所对应的路径是/users/xxx */}
    <Route path="users">
    <Route path="xxx" element={<Demo />} />
    </Route>
    </Routes>

  4. <Link>

    1. 作用: 修改URL,且不发送网络请求(路由链接)。
    2. 注意: 外侧需要用<BrowserRouter><HashRouter>包裹。
    3. 示例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import { Link } from "react-router-dom";

    function Test() {
    return (
    <div>
    <Link to="/路径">按钮</Link>
    </div>
    );
    }

  5. <NavLink>

    1. 作用: 与<Link>组件类似,且可实现导航的“高亮”效果。
    2. 示例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 注意: NavLink默认类名是active,下面是指定自定义的class

    //自定义样式,如果NavLink过多,可以将箭头函数单独定义,减少代码量
    <NavLink
    to="login"
    className={({ isActive }) => {
    console.log('home', isActive)
    return isActive ? 'base one' : 'base'
    }}
    >login</NavLink>

    /*
    默认情况下,当Home的子组件匹配成功,Home的导航也会高亮,
    当NavLink上添加了end属性后,若Home的子组件匹配成功,则Home的导航没有高亮效果。
    */
    <NavLink to="home" end >home</NavLink>

  6. <Navigate>

    可以把该组件理解为Redirect重定向

    1. 作用:只要<Navigate>组件被渲染,就会修改路径,切换视图。
    2. replace属性用于控制跳转模式(push 或 replace,默认是push)。
    3. 示例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import React,{useState} from 'react'
    import {Navigate} from 'react-router-dom'

    export default function Home() {
    const [sum,setSum] = useState(1)
    return (
    <div>
    <h3>我是Home的内容</h3>
    {/* 根据sum的值决定是否切换视图 */}
    {sum === 1 ? <h4>sum的值为{sum}</h4> : <Navigate to="/about" replace={true}/>}
    <button onClick={()=>setSum(2)}>点我将sum变为2</button>
    </div>
    )
    }

  7. <Outlet>

    1. <Route>产生嵌套时,渲染其对应的后续子路由。非嵌套时不用这个组件
    2. 示例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    //根据路由表生成对应的路由规则
    const element = useRoutes([
    {
    path:'/about',
    element:<About/>
    },
    {
    path:'/home',
    element:<Home/>,
    children:[
    {
    path:'news',
    element:<News/>
    },
    {
    path:'message',
    element:<Message/>,
    }
    ]
    }
    ])

    //Home.js
    import React from 'react'
    import {NavLink,Outlet} from 'react-router-dom'

    export default function Home() {
    return (
    <div>
    <h2>Home组件内容</h2>
    <div>
    <ul className="nav nav-tabs">
    <li>
    {/* to可以直接写子路由的path,注意不要加/,如果加了则表示从头开始匹配,不加则是在当前路由下匹配 */}
    <NavLink className="list-group-item" to="news">News</NavLink>
    </li>
    <li>
    <NavLink className="list-group-item" to="message">Message</NavLink>
    </li>
    </ul>
    {/* 指定路由组件呈现的位置 */}
    <Outlet />
    </div>
    </div>
    )
    }

15.3 Hooks

  1. useRoutes()

    1. 作用:根据路由表,动态创建<Routes><Route>
    2. 示例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    //路由表配置:src/routes/index.js
    import About from '../pages/About'
    import Home from '../pages/Home'
    import {Navigate} from 'react-router-dom'

    export default [
    {
    path:'/about',
    element:<About/>
    },
    {
    path:'/home',
    element:<Home/>
    },
    {
    path:'/',
    element:<Navigate to="/about"/>
    }
    ]

    //App.jsx
    import React from 'react'
    import {NavLink,useRoutes} from 'react-router-dom'
    import routes from './routes'

    export default function App() {
    //根据路由表生成对应的路由规则
    const element = useRoutes(routes)
    return (
    <div>
    ......
    {/* 注册路由 */}
    {element}
    ......
    </div>
    )
    }

  2. useNavigate()

    1. 作用:返回一个函数用来实现编程式导航。
    2. 示例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    import React from 'react'
    import {useNavigate} from 'react-router-dom'

    export default function Demo() {
    const navigate = useNavigate()
    const handle = () => {
    //第一种使用方式:指定具体的路径
    navigate('/login', {
    replace: false,
    state: {a:1, b:2}
    })
    //第二种使用方式:传入数值进行前进或后退,类似于5.x中的 history.go()方法
    navigate(-1) // 后退
    // navigate(1) // 前进
    }

    return (
    <div>
    <button onClick={handle}>按钮</button>
    </div>
    )
    }

  3. useParams()

    1. 作用:返回当前匹配路由的params参数,类似于5.x中的match.params
    2. 示例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import React from 'react';
    import { Routes, Route, useParams } from 'react-router-dom';
    import User from './pages/User.jsx'

    function ProfilePage() {
    // 获取URL中携带过来的params参数
    let { id } = useParams();
    }

    function App() {
    return (
    <Routes>
    <Route path="users/:id" element={<User />}/>
    </Routes>
    );
    }

  4. useSearchParams()

    1. 作用:用于读取和修改当前位置的 URL 中的查询字符串。
    2. 返回一个包含两个值的数组,内容分别为:当前的seaech参数、更新search的函数。
    3. 示例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    import React from 'react'
    import {useSearchParams} from 'react-router-dom'

    export default function Detail() {
    const [search,setSearch] = useSearchParams()
    const id = search.get('id')
    const title = search.get('title')
    const content = search.get('content')
    return (
    <ul>
    <li>
    <button onClick={()=>setSearch('id=008&title=哈哈&content=嘻嘻')}>点我更新一下收到的search参数</button>
    </li>
    <li>消息编号:{id}</li>
    <li>消息标题:{title}</li>
    <li>消息内容:{content}</li>
    </ul>
    )
    }

  5. useLocation()

    1. 作用:获取当前 location 信息,对标5.x中的路由组件的location属性。
    2. 示例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    // 传递state参数
    // <Link to="/home" state={{id: 1}} >主页</Link>
    // 接收使用useLocation()获取location对象,该对象的state存储的是参数

    import React from 'react'
    import {useLocation} from 'react-router-dom'

    export default function Detail() {
    const x = useLocation()
    console.log('@',x)
    // x就是location对象:
    /*
    {
    hash: "",
    key: "ah9nv6sz",
    pathname: "/login",
    search: "?name=zs&age=18",
    state: {a: 1, b: 2}
    }
    */
    return (
    <ul>
    <li>消息编号:{id}</li>
    <li>消息标题:{title}</li>
    <li>消息内容:{content}</li>
    </ul>
    )
    }

  6. useMatch()

    1. 作用:返回当前匹配信息,对标5.x中的路由组件的match属性。
    2. 示例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    <Route path="/login/:page/:pageSize" element={<Login />}/>
    <NavLink to="/login/1/10">登录</NavLink>

    export default function Login() {
    const match = useMatch('/login/:x/:y')
    console.log(match) //输出match对象
    //match对象内容如下:
    /*
    {
    params: {x: '1', y: '10'}
    pathname: "/LoGin/1/10"
    pathnameBase: "/LoGin/1/10"
    pattern: {
    path: '/login/:x/:y',
    caseSensitive: false,
    end: false
    }
    }
    */
    return (
    <div>
    <h1>Login</h1>
    </div>
    )
    }

  7. useInRouterContext()

​ 作用:如果组件在 <Router> 的上下文中呈现,则 useInRouterContext 钩子返回 true,否则返回 false。

  1. useNavigationType()

    1. 作用:返回当前的导航类型(用户是如何来到当前页面的)。
    2. 返回值:POPPUSHREPLACE
    3. 备注:POP是指在浏览器中直接打开了这个路由组件(刷新页面)。
  2. useOutlet()

    1. 作用:用来呈现当前组件中渲染的嵌套路由。
    2. 示例代码:

    1
    2
    3
    4
    const result = useOutlet()
    console.log(result)
    // 如果嵌套路由没有挂载,则result为null
    // 如果嵌套路由已经挂载,则展示嵌套的路由对象

  3. useResolvedPath()

    作用:给定一个 URL值,解析其中的:path、search、hash值。

由 Hexo 驱动 & 主题 Keep
访客数 访问量