React 概述

  1. 用于动态构建用户界面的 JavaScript 库(只关注于视图)
  2. 由Facebook开源

中文官网: https://react.docschina.org/

React的特点

  1. 声明式编码
  2. 组件化编码
  3. React Native 编写原生应用
  4. 高效(优秀的Diffing算法)

React高效的原因

  1. 使用虚拟(virtual)DOM, 不总是直接操作页面真实DOM。
  2. DOM Diffing算法, 最小化页面重绘。

React的基本使用

脚手架项目中使用

安装

1
npm i react react-dom

react 包是核心,提供创建元素、组件等功能

react-dom 包提供 DOM 相关功能等

1
2
3
4
5
6
7
// index.js

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App />)
1
2
3
4
5
6
7
8
// App.jsx
import React from 'react'

export default function App() {
return (
<div>App</div>
)
}

普通网页中使用

引入 react 和 react-dom 两个js文件

1
2
<script src="./node_modules/react/umd/react.development.js"></script>
<script src="./node_modules/react-dom/umd/react-dom.development.js"></script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
 <!-- 准备好一个“容器” -->
<div id="test"></div>

<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel"> /* 此处一定要写babel */
// 1.创建虚拟DOM
const VDOM = <h1>Hello,React</h1> /* 此处一定不要写引号,因为不是字符串 */
// 2.渲染虚拟DOM到页面
ReactDOM.render(VDOM,document.getElementById('test'))// 以前版本的使用方式

const root = ReactDOM.createRoot(document.getElementById('root')) // 现在的使用方式
root.render(VDOM)
</script>

相关js库

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

创建虚拟DOM的两种方式

React Api

  1. React提供了一些API来创建一种 “特别” 的一般js对象
1
2
3
4
5
6
7
8
9
// 参数一:元素名称
// 参数二:元素属性
// 参数三:元素的子节点
// React.createElement('component', props, ...children)
const title = React.createElement('h1', null, 'Hello React')

const title = React.createElement('h1', { title:'我是标题' }, 'Hello React')

const title = React.createElement('h1', { title:'我是标题' }, 'Hello React', React.createElement('span', null, '我是span节点'))
  1. 虚拟 DOM 对象最终都会被 React 转换为真实的 DOM
  2. 我们编码时基本只需要操作 react 的虚拟 DOM 相关数据, react 会转换为真实 DOM 变化而更新界

React JSX

  1. 全称: JavaScript XML

  2. react定义的一种类似于XML的JS扩展语法: JS + XML本质是React.createElement('component', props, ...children)方法的语法糖

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

    1. 写法:const ele =

      Hello JSX!

    2. 注意1:它不是字符串, 也不是HTML/XML标签

    3. 注意2:它最终产生的就是一个JS对象

  4. 标签名任意: HTML标签或其它标签

  5. 标签属性任意: HTML标签属性或其它

  6. 基本语法规则

    1. 遇到 <开头的代码, 以标签的语法解析: html同名标签转换为html同名元素, 其它标签需要特别解析
    2. 遇到以 { 开头的代码,以JS语法解析: 标签中的js表达式必须用{ }包含
  7. babel.js的作用

    1. 浏览器不能直接解析 JSX 代码, 需要babel转译为纯JS的代码才能运行

    2. 只要用了 JSX,都要加上 type=”text/babel”, 声明需要babel来处理

1
const ele = <h1>Hello JSX!</h1>

JSX

JSX 是 JavaScript XML的简写,表示在 JavaScript 代码中写 XML(HTML) 格式的代码。

优势:声明式语法更加直观、与HTML结构相同,降低了学习成本、提升开发效率

JSX 是 React 的核心内容

JSX的基本使用

  1. 使用 JSX 语法创建 react 元素

    1
    2
    // 使用 JSX 语法,创建 react 元素
    const title = <h1>Hello JSX</h1>
  2. 使用 ReactDOM.render() 方法渲染 react 元素到页面中

    1
    2
    // 渲染创建好的 React 元素
    ReactDOM.render(title,root)

    为什么脚手架中可以使用 JSX 语法?

    1. JSX 不是标准的 ECMAScript语法,它是 ECMAScript的语法扩展。
    2. 需要使用 babel 编译处理后,才能在浏览器环境中使用。
    3. create-react-app 脚手架中已经默认有该配置,无需手动配置。
    4. 编译 JSX 语法的包为:@babel/preset-react。

注意点:

  1. React元素的属性名使用驼峰命名法

  2. 特殊属性名:class -> className、for -> htmlFor、tabindex -> tabIndex。

  3. 没有子节点的React元素可以用/>结束。

  4. 推荐:使用小括号包裹 JSX,从而避免JS中的自动插入分号陷阱。

    1
    2
    3
    4
    // 使用小括号包裹 JSX
    const dv = (
    <div>Hello JSX</div>
    )

JSX 中的 JavaScript 表达式

注意点:

  • 单大括号中可以使用任意的 JavaScript 表达式
  • JSX 自身也是 JS 表达式
  • 注意:JS 中的对象是一个例外,一般只会出现在style属性中
  • 注意:不能在 {} 中出现语句 ( 比如:if/for 等 )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const name = 'Jack'
const sayHi = () => 'Hi~'
const dv = <div>我是一个div</div>
const title = (
<div>你好,我叫:{ name }</div>
<p>{ 3 > 5 ? '大于' : '小于等于' }</p>
<p>{ sayHi() }</p>
{ dv }

{/* 错误演示 */}
{/* <p>{ { a: '6' } }</p> */}
{/* { if (true) {} } */}
{/* { for (var i = 0; i < 10; i++) {} } */}
)

JSX 的条件渲染

根据条件渲染特定的 JSX 结构

可以用 if/else 或 三元运算符 或 逻辑与运算符来实现

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
const isLoading = true
// if-else
//const loadData = () => {
// if(isLoading) {
// return <div>loading...</div>
// }
// return <div>数据加载完成,此处显示加载后的数据</div>
//}

// 三元表达式
//const loadData = () => {
// return isLoading ? (<div>loading...</div>) : (<div>数据加载完成,此处显示加载后的数据</div>)
//}

// 逻辑与运算符:
const loadData = () => {
return isLoading && (<div>loading...</div>)
}

const title = {
<h1>
条件渲染:
{loadData()}
</h1>
}

JSX 的列表渲染

  • 如果要渲染一组数据,应该使用数组的 map() 方法
  • 注意:渲染列表时应该添加 key 属性,key 属性的值要保证唯一
  • 原则:map() 遍历谁,就给谁添加 key 属性
  • 注意:尽量避免使用索引号作为 key
1
2
3
4
5
6
7
8
9
10
11
const songs = [
{ id: 1, name: '痴心绝对' },
{ id: 2, name: '像我这样的人' },
{ id: 3, name: '南山南' },
]

const list = (
<ul>
{ songs.map(item => <li key={item.id}>{item.name}</li>) }
</ul>
)

JSX 的样式处理

  1. 行内样式

    1
    2
    3
    <h1 style={ { color: 'red', backgroundColor: 'skyblue' } }>
    JSX的样式处理
    </h1>
  2. 类名—className(推荐)

    1
    2
    3
    <h1 className="title">
    JSX的样式处理
    </h1>

Reacr组件

创建和使用方式

使用函数创建组件

  • 使用JS中的函数创建的组件叫做:函数组件
  • 函数组件必须有返回值
  • 组件名称必须以大写字母开头,React 据此区分 组件 和 普通的React元素
  • 使用函数名作为组件标签名
1
2
3
4
5
6
7
8
9
10
11
/*
函数组件:
*/

//function Hello() {
// return (
// <div>这是我的第一个函数组件!</div>
// )
//}

const Hello = () => <div>这是我的第一个函数组件!</div>

渲染函数组件:用函数名作为组件签名

组件标签可以是单标签也可以是双标签

1
ReactDOM.render(<Hello/>,document.getElementById('root'))

使用类创建组件

  • 类组件:使用ES6的 class 创建的组件
  • 约定1:类名称也必须以大写字母开头
  • 约定2:类组件应该继承 React.Component 父类,从而可以使用父类中提供的方法或属性
  • 约定3:类组件必须提供 render() 方法
  • 约定4:render() 方法必须有返回值,表示该组件的结构
1
2
3
4
5
6
7
8
9
10
11
12
13
/*
类组件:
*/
class Hello extends React.Component {
render() {
return (
<div>这是我的第一个类组件</div>
)
}
}

// 渲染组件
ReactDOM.render(<Hello/>,document.getElementById('root'))

抽离为独立 JS 文件

  1. 创建Hello.js
  2. 在Hello.js中导入React
  3. 创建组件( 函数 或 类 )
  4. 在 Hello.js 中导出该组件
  5. 在index.js中导入Hello组件
  6. 渲染组件
1
2
3
4
5
6
7
8
9
10
11
// Hello.js
import React from 'react'
class Hello extends React.Component {
render() {
return (
<div>Hello Class Componet!</div>
)
}
}
// 导出Hello组件
export default Hello
1
2
3
4
// index.js
import Hello from './Hello'
// 渲染导入的Hello组件
ReactDOM.render(<Hello />, root)

事件绑定

  • React 事件绑定语法与 DOM 事件语法相似

  • 语法:on+事件名称={事件处理程序},比如:onClick={ ()=>{} }

  • 注意:React 事件采用驼峰命名法,比如:onMouseEnter、onFocus

  • 在函数组件中绑定事件:

1
2
3
4
5
6
7
8
9
10
11
class App extends React.Component {
// 事件处理程序
handleClick() {
console.log('单击事件触发了')
}
render() {
return (
<button onClick={ this.handleClick }></button>
)
}
}
1
2
3
4
5
6
7
8
9
function App() {
function handleClick() {
console.log('单击事件触发了')
}

return (
<button onClick={ handleClick }>点我</button>
)
}

事件绑定 this 指向

  1. 箭头函数

    • 利用箭头函数自身不绑定this的特点
    • render() 方法中的 this 为组件实例,可以获取到 setState()
    1
    <button onClick={ () => this.onIncrement() }>+1</button>
  2. Function.prototype.bind()

    • 利用ES5中的bind方法,将事件处理程序中的this与组件实例绑定到一起
    1
    2
    3
    4
    5
    coustructor() {
    super()
    this.onIncrement = this.onIncrement.bind(this)
    }
    // ...省略 onIncrement
  3. class 的实例方法 (推荐)

    • 利用箭头函数形式的 class 实例方法
    • 注意:该语法是实验性语法,但是,由于babel的存在可以直接使用
    1
    2
    3
    onIncrement = () => {
    this.setState({ ... })
    }

事件对象

  • 可以通过事件处理程序的参数获取到事件对象
  • React 中的事件对象叫做:合成事件 ( 对象 )
  • 合成事件:兼容所有浏览器,无需担心跨浏览器兼容性问题
1
2
3
4
5
6
7
8
9
10
11
12
class App extends React.Component {
handleClick(e) {
// 阻止浏览器的默认行为
e.preventDefault()
console.log('a标签的单击事件触发了')
}
render() {
return (
<a href="http://www.baidu.com" onClick={ this.handleClick }>点击</a>
)
}
}

有状态组件和无状态组件

  • 函数组件又叫做无状态组件,类组件又叫做有状态组件
  • 状态 ( state ) 即数据
  • 函数组件没有自己的状态,只负责数据展示 ( 静 )
  • 类组件有自己的状态,负责更新UI,让页面 “动” 起来

state 的基本使用

  • 状态 ( state )即数据
  • 是组件内部的私有数据,只能在组件内部使用
  • state 的值是对象,表示一个组件中可以有多个数据
  • 通过 this.state 来获取状态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Hello extends React.Component {
constructor() {
super()

// 初始化state
this.state = {
count: 0
}
}
render() {
return (
<div>计数器,{ this.state.count }</div>
)
}
}
1
2
3
4
5
6
7
8
9
10
11
class Hello extends React.Component {
// 简化语法初始化state
state = {
count: 0
}
render() {
return (
<div>计数器,{ this.state.count }</div>
)
}
}

setState() 修改状态

  • 状态是可变的

  • 语法:this.setState({ 要修改的数据 })

  • 注意:不要直接修改 state 中的值,这是错误的 !!!

  • setState() 作用:

    1、修改 state

    2、更新UI

  • 思想:数据驱动视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Hello extends React.Component {
state = {
count: 0
}
render() {
return (
<div>
<h1>计数器,{ this.state.count }</h1>
<button onClick={ () => {
this.setState({
count: this.state.count + 1
})
} }>+1</button>
</div>
)
}
}

从 JSX 中抽离事件处理程序

  • JSX 中掺杂过多 JS 逻辑代码,会显得非常混乱
  • 推荐:将逻辑抽离到单独的方法中,保证 JSX 结构清晰
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Hello extends React.Component {
state = {
count: 0
}

// 事件处理程序
onIncrement() {
this.setState({ // this 为 underfiend
count: this.state.count + 1
})
}

render() {
return (
<div>
<h1>计数器,{ this.state.count }</h1>
<button onClick={ this.onIncrement }>+1</button>
</div>
)
}
}

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函数中读取

props 的基本使用

理解

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

作用

  1. 通过标签属性从组件外向组件内传递变化的数据
  2. 注意: 组件内部不要修改props数据

编码操作

  1. 内部读取某个属性值
1
this.props.name
  1. 对props中的属性值进行类型限制和必要性限制

第一种方式(React v15.5 开始已弃用):

1
2
3
4
Person.propTypes = {
name:React.PropTypes.string.isRequired,
age:React.PropTypes.number
}

第二种方式(新):使用prop-types库进限制(需要引入prop-types库)

1
2
3
4
Person.propTypes = {
name:PropTypes.string.isRequired,
age:PropTypes.number
}
  1. 扩展属性: 将对象的所有属性通过props传递
1
<Person {...person} />
  1. 默认属性值:
1
2
3
4
Person.defaultProps = {
age:18,
sex:'男'
}
  1. 组件类的构造函数
1
2
3
4
coustructor(props){
super(props)
console.log(props)
}

refs 的基本使用

1.字符串形式的ref

1
<input ref="input1" />

2.回调形式的ref

1
<input ref={(c)=>{this.input1=c}} />

3.createRef创建ref容器

1
2
myRef = React.createRef();
<input ref={myRef} />

表单处理

受控组件

  • HTML 中的表单元素是可输入的,也就是有自己的可变状态
  • 而,React 中可变状态通常保存在state中,并且只能通过 setState() 方法来修改
  • React 将 state 与表单元素值 value 绑定到一起,由 state 的值来控制表单元素的值
  • 受控组件:其值受到 React 控制的表单元素
1
<input type="text" value={ this.state.txt } />

步骤

  1. 在 state 中添加一个状态,作为表单元素的value值 ( 控制表单元素值的来源 )
  2. 给表单绑定 change 事件,将表单元素的值设置为 state 的值 ( 控制表单元素值的变化 )
1
state = { txt: '' }
1
<input type="text" value={ this.state.txt } onChange={ e => this.setState( { txt: e.target.value } ) } />

多表单元素优化:

  • 问题:每个表单元素都有一个单独的事件处理程序处理太繁琐
  • 优化:使用一个事件处理程序同时处理多个表单元素

多表单元素优化步骤:

  1. 给表单元素添加name属性,名称与state相同
  2. 根据表单元素获取对应值
1
<input type="text" name="txt" value={ this.state.txt } onChange={ this.handleForm } />
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
handleForm = e => {
// 获取当前DOM对象
const target = e.target

// 根据表单元素类型获取值
const value = target.type === 'checkbox' ? target.checked : target.value

// 获取name
const name = target.name

// 根据name设置对应state
this.setState({
[name]: value
})
}

非受控组件 (DOM方式)

  • 说明:借助于 ref,使用原生DOM方式来获取表单元素值
  • ref 的作用:获取 DOM 或组件

使用步骤:

  1. 调用 React.createRef() 方法创建一个 ref 对象

    1
    2
    3
    4
    constructor() {
    super()
    this.txtRef = React.createRef()
    }
  2. 将创建好的 ref 对象添加到文本框中

    1
    <input type="text" ref={ this.txtRef } />
  3. 通过 ref 对象获取到文本框的值

    1
    console.log(this.txtRef.current.value)

组件生命周期

生命周期的三个阶段(旧)

1. 初始化阶段: 由ReactDOM.render()触发—初次渲染

  1. constructor()
  2. componentWillMount()
  3. render()
  4. componentDidMount()

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

  1. shouldComponentUpdate()
  2. componentWillUpdate()
  3. render()
  4. componentDidUpdate()

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

  1. componentWillUnmount()

生命周期的三个阶段(新)

1. 初始化阶段: 由ReactDOM.render()触发—初次渲染

  1. constructor()

  2. getDerivedStateFromProps

  3. render()

  4. componentDidMount()

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

  1. getDerivedStateFromProps

  2. shouldComponentUpdate()

  3. render()

  4. getSnapshotBeforeUpdate

  5. componentDidUpdate()

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

  1. componentWillUnmount()

重要的勾子

  1. render:初始化渲染或更新渲染调用
  2. componentDidMount:开启监听, 发送ajax请求
  3. componentWillUnmount:做一些收尾工作, 如: 清理定时器

即将废弃的勾子

  1. componentWillMount
  2. componentWillReceiveProps
  3. componentWillUpdate

最新版本加上UNSAFE_前缀才能使用,和新钩子不能一起使用,以后可能会被彻底废弃,不建议使用。

组件通讯(props)

  • 组件是封闭的,要接收外部数据应该通过 props 来实现
  • props 的作用:接收传递给组件的数据
  • 传递数据:给组件标签添加属性
  • 接收数据:函数组件通过参数 props 接收数据,类组件通过 this.props 接收数据
1
2
// 传递数据
<Hello name="jack" age={ 19 } />
1
2
3
4
5
6
7
// 函数组件
function Hello(props){
console.log(props)
return (
<div>接收到数据:{ props.name }</div>
)
}
1
2
3
4
5
6
7
8
// 类组件
class Hello extends React.Component {
render() {
return (
<div>接收到的数据:{ this.props.age }</div>
)
}
}

组件的 props

特点:

  1. 可以给组件传递任意类型的数据

  2. props 是只读的对象,只能读取属性的值,无法修改对象

  3. 注意:使用类组件时,如果写了构造函数,应该将 props 传递给 super(),否则,无法在构造函数中获取到 props!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Hello extends React.Component {
    // 推荐使用props作为constructor的参数:
    constructor(porps) {
    // 推荐将props传递给父类构造函数
    super(props)
    }

    render() {
    return <div>接收到的数据:{ this.props.age }</div>
    }
    }

组件之间的通讯分为 3 种:

  1. 父组件 -> 子组件
  2. 子组件 -> 父组件
  3. 兄弟组件

父组件传递数据给子组件

  1. 父组件提供要传递的state数据
  2. 给子组件添加属性,值为 state 中的数据
  3. 子组件中通过 props 接收父组件中传递的数据
1
2
3
4
5
6
7
8
9
10
class Parent extends React.Component {
state = { lastName: '王' }
render() {
return (
<div>
传递数据给子组件:<Child name={ this.state.lastName } />
</div>
)
}
}
1
2
3
function Child(props) {
return <div>子组件接收到数据:{ props:name }</div>
}

子组件传递数据给父组件

思路:利用回调函数,父组件提供回调,子组件调用,将要传递的数据作为回调函数的参数。

  1. 父组件提供一个回调函数(用于接收数据)
  2. 将该函数作为属性的值,传递给子组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Parent extends React.Component {
state = {
parentMsg: ''
}

// 提供回调函数,用来接收数据
getChildMsg = (data) => {
console.log('接收到子组件中传递过来的数据', data)

this.setState({
parentMsg: data
})
}
render() {
return (
<div className="parent">
父组件:{ this.state.parentMsg }
<Child getMsg={ this.getChildMsg } />
</div>
)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Child extends React.Component {
state = {
msg: '刷抖音'
}

handleClick = () => {
// 子组件调用父组件中传递过来的回调函数
this.props.getMsg(this.state.msg)
}

return (
<button onClick={ this.handleClick }>点我,给父组件传递数据</button>
)
}

注意:回调函数中 this 的指向问题!

兄弟组件

  • 将共享状态提升到最近的公共父组件中,由公共父组件管理这个状态
  • 思想:状态提升
  • 公共父组件职责:
    1. 提供共享状态
    2. 提供操作共享状态的方法
  • 要通讯的子组件只需通过 props 接收状态或操作状态的方法

组件通讯(消息订阅-发布机制)

任意组件之间通讯(一般用于兄弟之间)

  1. 工具库: PubSubJS
  2. 下载: npm install pubsub-js –save
  3. 使用:
1
2
3
4
5
1. import PubSub from 'pubsub-js'    //引入

2. PubSub.subscribe('delete', function(data){ }); //订阅

3. PubSub.publish('delete', data) //发布消息

组件通讯(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>
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import React, { Component } from 'react'
import './index.css'

//创建Context对象
const MyContext = React.createContext()
const {Provider,Consumer} = MyContext
export default class A extends Component {

state = {username:'tom',age:18}

render() {
const {username,age} = this.state
return (
<div className="parent">
<h3>我是A组件</h3>
<h4>我的用户名是:{username}</h4>
<Provider value={{username,age}}>
<B/>
</Provider>
</div>
)
}
}

class B extends Component {
render() {
return (
<div className="child">
<h3>我是B组件</h3>
<C/>
</div>
)
}
}

/* class C extends Component {
//声明接收context
static contextType = MyContext
render() {
const {username,age} = this.context
return (
<div className="grand">
<h3>我是C组件</h3>
<h4>我从A组件接收到的用户名:{username},年龄是{age}</h4>
</div>
)
}
} */

function C(){
return (
<div className="grand">
<h3>我是C组件</h3>
<h4>我从A组件接收到的用户名:
<Consumer>
{value => `${value.username},年龄是${value.age}`}
</Consumer>
</h4>
</div>
)
}

注意

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

组件通讯(redux)

redux

组件通信方式总结

方式:

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

组件间的关系

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

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} 
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
48
import React, { Component } from 'react'
import './index.css'
// import C from '../1_setState'
export default class Demo7 extends Component {
state = { name1: 'tom1' }

render() {
const {name1} = this.state
return (
<div className="parent">
<h3>我是Parent组件,{name1}</h3>
{/* <A>
<B name={name} />
</A> */}
<A render={(name2)=><B name1={name1} name2={name2}/>} />
</div>
)
}
}

class A extends Component {
state = { name2: 'tom2' }
render() {
console.log('A---render', this.props)
const { children,render } = this.props
const { name2 } = this.state
return (
<div className="a">
<h3>我是A组件,{name2}</h3>
{/* <B/> */}
{/* {children} */}
{render(name2)}
</div>
)
}
}

class B extends Component {
render() {
console.log('B---render', this.props)
const {name1,name2} = this.props
return (
<div className="b">
<h3>我是B组件,Parent传递{name1},A传递{name2}</h3>
</div>
)
}
}

错误边界(只适用类组件)

理解:

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

特点:

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

使用方式:

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);
}
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
import React, { Component } from 'react'
import Child from './Child'

export default class Parent extends Component {

state = {
hasError:'' //用于标识子组件是否产生错误
}

//当Parent的子组件出现报错时候,会触发getDerivedStateFromError调用,并携带错误信息
static getDerivedStateFromError(error){
console.log('@@@',error);
return {hasError:error}
}

componentDidCatch(){
console.log('此处统计错误,反馈给服务器,用于通知编码人员进行bug的解决');
}

render() {
return (
<div>
<h2>我是Parent组件</h2>
{this.state.hasError ? <h2>当前网络不稳定,稍后再试</h2> : <Child/>}
</div>
)
}
}

组件优化(只适用类组件)

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来优化
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import React, { PureComponent } from 'react'
import './index.css'

export default class Parent extends PureComponent {

state = {carName:"奔驰c36",stus:['小张','小李','小王']}

addStu = ()=>{
/* const {stus} = this.state
stus.unshift('小刘')
this.setState({stus}) */

const {stus} = this.state
this.setState({stus:['小刘',...stus]})
}

changeCar = ()=>{
//this.setState({carName:'迈巴赫'})

const obj = this.state
obj.carName = '迈巴赫'
console.log(obj === this.state);
this.setState(obj)
}

/* shouldComponentUpdate(nextProps,nextState){
// console.log(this.props,this.state); //目前的props和state
// console.log(nextProps,nextState); //接下要变化的目标props,目标state
return !this.state.carName === nextState.carName
} */

render() {
console.log('Parent---render');
const {carName} = this.state
return (
<div className="parent">
<h3>我是Parent组件</h3>
{this.state.stus}&nbsp;
<span>我的车名字是:{carName}</span><br/>
<button onClick={this.changeCar}>点我换车</button>
<button onClick={this.addStu}>添加一个小刘</button>
<Child carName="奥拓"/>
</div>
)
}
}

class Child extends PureComponent {

/* shouldComponentUpdate(nextProps,nextState){
console.log(this.props,this.state); //目前的props和state
console.log(nextProps,nextState); //接下要变化的目标props,目标state
return !this.props.carName === nextProps.carName
} */

render() {
console.log('Child---render');
return (
<div className="child">
<h3>我是Child组件</h3>
<span>我接到的车是:{this.props.carName}</span>
</div>
)
}
}

React脚手架

  1. 脚手架是开发现代Web应用的必备。
  2. 充分利用 Webpack、Babel、ESLint 等工具辅助项目开发。
  3. 零配置,无需手动配置繁琐的工具即可使用。
  4. 关注业务,而不是工具配置。

使用create-react-app创建react应用

1
npx create-react-app 项目名称

npx 无需安装脚手架包,就可以直接使用这个

启动项目

1
npm start

入口文件

以前版本

  1. 导入 react 和 react-dom 两个包

  2. 调用 React.createElement() 方法创建react元素。

  3. 调用 ReactDOM.render() 方法渲染 react 元素到页面中。

1
2
3
4
5
6
7
8
// 1 导入react
import React from 'react'
import ReactDOM from 'react-dom'
// 2 引入App组件
import App from "./App";

// 3 渲染App组件
ReactDOM.render(<App />,document.getElementById('root'))

现在版本

1
2
3
4
5
6
7
import React from "react";
import ReactDOM from "react-dom/client";

import App from "./App";

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

Hooks

1. React Hook/Hooks是什么?

1
2
(1). Hook是React 16.8.0版本增加的新特性/新语法
(2). 可以让你在函数组件中使用 state 以及其他的 React 特性

2. 三个常用的Hook

1
2
3
(1). State Hook: React.useState()
(2). Effect Hook: React.useEffect()
(3). Ref Hook: React.useRef()

3. 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): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值

4. 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()

5. Ref Hook

1
2
3
(1). Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
(2). 语法: const refContainer = useRef()
(3). 作用:保存标签对象,功能与React.createRef()一样
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
import React from 'react'
import ReactDOM from 'react-dom'

function Demo(){
//console.log('Demo');

const [count,setCount] = React.useState(0)
const myRef = React.useRef() // 创建ref容器

React.useEffect(()=>{
// 监听数据更新执行
let timer = setInterval(()=>{
setCount(count => count+1 )
},1000)
return ()=>{// 在组件卸载前执行
clearInterval(timer)
}
},[])// 如果指定的是[], 回调函数只会在第一次render()后执行

//加的回调
function add(){
//setCount(count+1) //第一种写法
setCount(count => count+1 )
}

//提示输入的回调
function show(){
alert(myRef.current.value)
}

//卸载组件的回调
function unmount(){
ReactDOM.unmountComponentAtNode(document.getElementById('root'))
}

return (
<div>
<input type="text" ref={myRef}/>
<h2>当前求和为:{count}</h2>
<button onClick={add}>点我+1</button>
<button onClick={unmount}>卸载组件</button>
<button onClick={show}>点我提示数据</button>
</div>
)
}

export default Demo

Fragment

使用

Fragment可以传key

1
<Fragment key={1}><Fragment>

作用

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

<></> 效果一样但是不能传key

React路由

react-router6之前

react-router6