前言

版本:

  • Node:16.20.2
  • React:18.2.0

使用命令npx create-react-app react-basic创建一个名为react-basic的项目,并在App.js文件中编写代码。


JSX基础

介绍

JSX是JavaScript和XMl(HTML)的缩写,表示在JS代码中编写HTML模版结构,它是React中构建UI的方式。

JSX并不是标准的JS语法,它是 JS的语法扩展,浏览器本身不能识别,需要通过解析工具(BABEL)做解析之后才能在浏览器中使用:

语法

大括号语法

在JSX中可以通过大括号语法{}识别JavaScript中的表达式,具体包括:

  • JavaScript变量
  • 函数调用和方法调用
  • JavaScript对象

注:if语句、switch语句、变量声明不属于表达式,不能使用大括号语法{}表达。

  • JavaScript变量:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const message = 'this is the message'

    function App() {
    return (
    <div className="App">
    {message}
    </div>
    );
    }

    export default App;

  • 函数调用和方法调用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    function getName(){
    return '花猪'
    }

    function App() {
    return (
    <div className="App">
    {/* 函数调用 */}
    {getName()}
    <br/>
    {/* 方法调用 */}
    {new Date().getFullYear()}
    </div>
    );
    }

    export default App;

  • JavaScript对象:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    const person = {
    Name: '花猪',
    Age: 18,
    sayHi: function () {
    return '你好'
    }
    }

    function App() {
    return (
    <div className="App">
    <div>{person.Name}</div>
    <div>{person.Age}</div>
    <div style = {{color: 'blue'}}>{person.sayHi()}</div>
    </div>
    );
    }

    export default App;

    style的最外层括号表示JSX的大括号语法,内层的括号是js对象的包裹括号。

列表渲染

在JSX中可以使用原生js中的map()方法实现列表渲染:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const list = [
{id:1001, name:'Vue'},
{id:1002, name: 'React'},
{id:1003, name: 'Angular'}
]

function App() {
return (
<div className="App">
<ul>
{list.map(item => <li key = {item.id}> {item.name} </li>)}
</ul>
</div>
);
}

export default App;

React要求在渲染重复元素的时候需要给每个元素绑定一个独一无二的Key属性。否则会报错:

条件渲染

可以通过以下三种方式实现条件渲染:

  • 逻辑运算符

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const flag = true

    function App() {
    return (
    <div className="App">
    {flag && <span>this is span</span>}
    </div>
    );
    }

    export default App;

  • 三元表达式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const loading = true

    function App() {
    return (
    <div className="App">
    {loading ? <span>loading...</span> : <span>this is span</span>}
    </div>
    );
    }

    export default App;

  • 自定义函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    function getItem(number) {
    if(number === 1) {
    return <div>number === 1</div>
    }
    else if(number === 2) {
    return <div>number === 2</div>
    }
    else {
    return <div>else</div>
    }
    }

    function App() {
    return (
    <div className="App">
    {getItem(2)}
    </div>
    );
    }

    export default App;

事件绑定

基础实现

React中的事件绑定可以通过语法on + 事件名称 = { 事件处理程序 }实现,整体上遵循驼峰命名法。

1
2
3
4
5
6
7
8
9
10
11
12
13
const clickHandler = () => {
console.log('button按钮被点击了')
}

function App() {
return (
<div className="App">
<button onClick = {clickHandler}>click me</button>
</div>
);
}

export default App;

控制台输出结果:

1
button按钮被点击了

参数传递

事件参数

直接在回调函数中设置形参e:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const clickHandler = (e) => {
console.log('button按钮被点击了')
console.log(e)
}

function App() {
return (
<div className="App">
<button onClick = {clickHandler}>click me</button>
</div>
);
}

export default App;

控制台输出结果:

1
2
button按钮被点击了
SyntheticBaseEvent {_reactName: 'onClick', _targetInst: null, type: 'click', nativeEvent: PointerEvent, target: button, …}

自定义参数

使用自定义参数时,事件绑定的位置不能直接写函数调用,这里需要一个函数引用,可以改为箭头函数的写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const clickHandler = (name) => {
console.log('button按钮被点击了')
console.log(name)
}

function App() {
return (
<div className="App">
<button onClick = {() => clickHandler('花猪')}>click me</button>
</div>
);
}

export default App;

控制台输出结果:

1
2
button按钮被点击了
花猪

同时传递事件对象和自定义参数

在事件绑定的位置传递事件实参e和自定义参数,自定义的函数中声明形参,注意顺序对应:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const clickHandler = (name, e) => {
console.log('button按钮被点击了')
console.log(name)
console.log(e)
}

function App() {
return (
<div className="App">
<button onClick = {(e) => clickHandler('花猪', e)}>click me</button>
</div>
);
}

export default App;

控制台输出结果:

1
2
3
button按钮被点击了
花猪
SyntheticBaseEvent {_reactName: 'onClick', _targetInst: null, type: 'click', nativeEvent: PointerEvent, target: button, …}

组件

一个组件就是一个用户界面的一部分,它可以有自己的逻辑和外观,组件之间可以互相嵌套,也可以复用多次。

基础使用

在React中,一个组件就是首字母大写的函数,内部存放了组件的逻辑和视图UI, 渲染组件只需要把组件当成标签书写即可,可以通过自闭合标签以及成对标签两种方式实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 1. 自定义Button组件
function Button(){
return <button>click me</button>
}

// 2. 使用组件
function App() {
return (
<div className="App">
{/* 自闭和标签 */}
<Button/>
{/* 成对标签 */}
<Button></Button>
</div>
);
}

export default App;

组件状态管理-useState

useState是一个React Hook(函数),它允许向组件添加一个状态变量, 从而控制影响组件的渲染结果。

与普通JS变量不同的是,状态变量一旦发生变化组件的视图UI也会跟着变化(即数据驱动视图)

基础使用

  1. 首先需要从react中引入useState

    也可以不引入直接使用React.useState调用

  2. 使用useState会维护一个状态变量以及一个修改状态变量的set()方法,可以通过解构的方式得到它们

  3. 编写一个回调函数用以绑定事件,函数内部可以调用set()方法修改状态变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 1.引入useState
import { useState } from 'react'

function App() {
// 2.使用useState添加一个状态变量count,并赋初值0
// count:状态变量(一旦改变视图UI也会随之改变)
// setCount:修改状态变量的方法
const [ count, setCount ] = useState(0) // 也可以不引用直接调用 React.useState(0)

// 3.回调函数
const handleClick = () => {
setCount(count + 1) // 调用setCount()方法修改状态变量
}
return (
<div className="App">
<button onClick = {handleClick}>{count}</button>
</div>
);
}

export default App;

状态变量的修改规则:在React中状态被认为是只读的,应该始终“替换它而非修改它”, 直接修改状态不能引发视图更新:

1
2
3
4
5
const handleClick = () => {
// 直接修改,无法引发视图UI更新
count++
console.log(count)
}

修改对象状态

对于对象类型的状态变量,应该始终给set()方法一个新的对象来替换旧的对象进行修改:

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 { useState } from 'react'

// 定义一个初始的对象
const object = {
Name: '张三',
Age: 30,
}

function App() {

// 给useState传入初始对象object,并通过解构得到 状态变量item(对象) 和 set() 方法
const [ item, setItem ] = useState(object)

const handleClick = () => {
// 定义新的对象,用于替换状态变量item
const person = {
Name: '花猪',
Age: 18,
}
// 调用setItem()方法修改状态变量:用新的对象person去替换旧的对象object
setItem(person)
console.log(item)
}

return (
<div className="App">
<button onClick = {handleClick}>{item.Name}</button>
</div>
);
}

export default App;

控制台输出结果:

1
{Name: '花猪', Age: 18}

表单受控绑定

React可以通过useState控制表单的状态。

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
import { useState } from "react";

function App() {
const [value, setValue] = useState('')
return (
<div className="App">
{/* 绑定一个改变事件,回调函数直接调用set()方法,获取输入框中新的value值 */}
<input
value={value}
onChange={(e) => setValue(e.target.value)}
type="text"/>

<br/>
{value}
<br/>

{/* 绑定一个改变事件,回调函数直接调用set()方法,获取输入框中新的value值 */}
<input
value={value}
onChange={(e) => setValue(e.target.value)}
type="text"/>
</div>
);
}

export default App;

这里使用useState维护输入框的值value,并绑定一个改变事件,回调函数直接调用set()方法,获取输入框中新的value值,覆盖旧的value值。

DOM获取-useRef

useState是一个React Hook(函数),用于创建一个可变的ref对象。其常见的使用场景为:访问DOM元素,存储React元素。

基础使用

  1. 首先需要从react中引入uesRef
  2. 使用useRef创建一个ref对象,初始化参数可选
  3. 在DOM上通过ref属性将ref对象绑定到该DOM上
  4. 通过ref对象中的current属性可以查看获取到的DOM元素的属性

注:只有当渲染完毕之后生成的DOM才可用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { useRef } from "react";  // 从react中引入uesRef

function App() {
const inputRef = useRef(null) // 使用useRef创建一个ref对象,初值传入null
const showDOM = () => {
console.dir(inputRef.current) // 通过ref对象中的current属性可以查看获取到的DOM元素的属性
}

return (
<div className="App">
{/* 通过ref属性将ref对象绑定到指定的DOM上 */}
<input type="text" ref={inputRef}/>
<br/>
<button onClick={showDOM}>获取DOM</button>
</div>
);
}

export default App;

组件样式处理

React组件基础的样式控制有两种方式:

  • 行内样式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function App() {
    return (
    <div className="App">
    <span style = {{ color: 'red' }}> 样式处理 </span>
    </div>
    );
    }

    export default App;

  • class类名控制

    首先编写index.css文件:

    1
    2
    3
    4
    .foo {
    color: blue;
    font-weight: bolder;
    }

    然后在App.js文件中引入index.css,并通过className属性绑定样式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import './index.css'

    function App() {
    return (
    <div className="App">
    <span className = 'foo'> 样式处理 </span>
    </div>
    );
    }

    export default App;

    可以使用classnames优化类名控制:

    classnames是一个简单的JS库,可以非常方便的通过条件动态控制class类名的显示。

    官网:GitHub - JedWatson/classnames: A simple javascript utility for conditionally joining classNames together

    安装:npm install classnames --save

    使用案例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import classNames from "classnames";  // 引入classnames

    function App() {

    const flag = true

    return (
    <div className="App">
    <button className = {classNames('id', {disabled: false, active: flag})} >click me</button>
    </div>
    );
    }

    export default App;

    引入:import classNames from "classnames";

    使用格式:classNames('静态类名', {动态类名1: 条件1, 动态类名2: 条件2, ...})

    静态类名是始终存在的,动态类名会根据条件(boolean类型)的值进行判断,如果为true则显示该类名。

组件通信

组件通信就是组件之间的数据传递, 根据组件嵌套关系的不同,有不同的通信手段和方法,具体有以下几种形式:

  • A → B:父子通信
  • B → C:兄弟通信
  • A → E:跨层通信

父子通信

父传子

实现步骤:

  1. 父组件传递数据:在父组件中的子组件标签中绑定属性,进行数据传递

  2. 子组件接收数据:子组件通过props参数接收来自父组件的数据

    参数名并不固定,只是规范化写为props

    关于props参数:

    • props可以传递任意的合法数据:如数字、字符串、布尔值、数组、对象、函数、JSX。
    • props是只读对象,子组件只能读取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
import './index.css'

function App() {
const appMsg = '这是父组件(App)的消息' // 定义父组件数据appMsg
return (
<div className="App father">
<div>App(father)</div>

{/* 在子组件标签中绑定msg属性,传递父组件数据appMsg */}
<Son msg = {appMsg}/>

</div>
);
}

// 子组件通过props参数接收来自父组件的数据
function Son(props) {
return (
<div className="son">
<div>Son</div>
<div>{ props.msg }</div>
</div>
)
}

export default App;

index.css代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
.father {
padding: 10px;
display: inline-block;
background-color: rgb(123, 234, 237);
}

.son {
height: 100px;
width: 300px;
padding: 5px;
background-color: rgb(155, 248, 105);
}

除了在自闭合子组件标签中添加属性传递父组件数据,还可以在子组件的成对标签中传递父组件数据,子组件会通过prop中的children属性获取该内容:

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 './index.css'

function App() {
return (
<div className="App father">
<div>App(father)</div>

{/* 在子组件的成对标签中传递父组件数据 */}
<Son>
<span>这是父组件(App)的消息</span>
</Son>

</div>
);
}

// 子组件通过props参数接收来自父组件的数据,通过children属性获取
function Son(props) {
console.dir(props)
return (
<div className="son">
<div>Son</div>
<div>{ props.children }</div>
</div>
)
}

export default App;

子传父

子传父的核心思路:在子组件中调用父组件中的函数并传递参数。

实现步骤:

  1. 首先在父组件中创建一个函数用于获取子组件传来的数据
  2. 通过“父传子”的方式将该函数传递给子组件,以便子组件可以调用
  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
33
34
import './index.css'
import { useState } from 'react';

function App() {
const [msg, setMsg] = useState('') // 利用useState维护msg
// 在父组件中创建一个方法getSonMsg()用于获取子组件传递的数据
const getSonMsg = (msg) => {
setMsg(msg) // 通过set()方法更新msg
}
return (
<div className="App father">
<div>App(father)</div>
<div>{msg}</div>

{/* 向子组件传递getSonMsg()方法(“父传子”),以便子组件可以调用该方法 */}
<Son getMsg = {getSonMsg}/>

</div>
);
}


function Son(props) { // 接收父组件传来的方法getSonMsg()
const sonMsg = '这是子组件的消息' // 定义子组件数据sonMsg
return (
<div className="son">
<div>Son</div>
{/* 在子组件中调用从父组件传递来的方法,并将数据传入其中(“子传父”) */}
<button onClick={() => props.getMsg(sonMsg)}>向父组件传递数据</button>
</div>
)
}

export default App;

index.css代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
.father {
padding: 10px;
display: inline-block;
background-color: rgb(123, 234, 237);
}

.son {
height: 100px;
width: 300px;
padding: 5px;
background-color: rgb(155, 248, 105);
}

兄弟通信

子传父的核心思路:借助状态提升机制,通过共同的父组件进行兄弟之间的数据传递。

实现思路(兄弟组件A → 兄弟组件B):

  1. 组件A先通过“子传父”的方式把数据传递给父组件
  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
import './index.css'
import { useState } from 'react';

function App() {
const [msg, setMsg] = useState('') // 利用useState维护msg
// 在父组件中创建一个方法getSonAMsg()用于获取子组件A传递的数据
const getSonAMsg = (msg) => {
setMsg(msg) // 通过set()方法更新msg
}
return (
<div className="App father">
<div>App(father)</div>

{/* 向子组件A传递getSonAMsg()方法(“父传子”) */}
<SonA getMsg = {getSonAMsg}/>
{/* 向子组件B传递msg(“父传子”) */}
<SonB msg = {msg}/>

</div>
);
}


function SonA(props) {
const sonMsg = '这是子组件A的消息' // 定义子组件A的消息
return (
<div className="sonA">
<div>SonA</div>
{/* 在子组件A中调用从父组件传递来的方法,并将数据传入其中(“子传父”) */}
<button onClick={() => props.getMsg(sonMsg)}>向兄弟组件B传递数据</button>
</div>
)
}

function SonB(props) { // 子组件B通过props参数接收来自父组件的数据
return (
<div className="sonB">
<div>SonB</div>
<div>{ props.msg }</div>
</div>
)
}

export default App;

index.css代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.father {
padding: 10px;
display: inline-block;
background-color: rgb(123, 234, 237);
}

.sonA {
height: 100px;
width: 300px;
padding: 5px;
background-color: rgb(155, 248, 105);
}

.sonB {
height: 100px;
width: 300px;
padding: 5px;
background-color: rgb(247, 231, 87);
}

跨层通信

实现步骤:

  1. 通过createContext()方法创建一个上下文对象
  2. 在顶层组件中通过上下文对象.Provider组件创建一个传播域,将要传递的子组件包裹起来,通过value属性传递数据。
  3. 在底层组件中通过useContext()钩子函数获取传递来的数据

注:createContext、useContext需要从react中引入。

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
import './index.css'
import { createContext, useContext } from 'react'; // 引入createContext,useContext

const MsgContext = createContext() // 通过createContext()方法创建一个上下文对象MsgContext

function App() {
const appMsg = '这是父组件(App)的消息' // 定义顶层组件数据appMsg
return (
<div className="App father">
<div>App(father)</div>

{/* 建立上下文对象标签(利用Provider 组件提供数据),利用value关键字传递数据*/}
{/* 这实际上是一个传播域,把需要传递的组件包裹在其中 */}
<MsgContext.Provider value={appMsg}>
<SonA />
</MsgContext.Provider>

</div>
);
}


function SonA() {
return (
<div className="sonA">
<div>SonA</div>
{/* 组件A包裹组件B */}
<SonB />
</div>
)
}

function SonB() {
const msg = useContext(MsgContext) // 通过useContext()方法拿到数据,参数为创建的上下文对象
return (
<div className="sonB">
<div>SonB</div>
{/* 使用顶层组件传递来的数据 */}
<div>{msg}</div>
</div>
)
}

export default App;

index.css代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.father {
padding: 10px;
display: inline-block;
background-color: rgb(123, 234, 237);
}

.sonA {
padding: 10px;
display: inline-block;
background-color: rgb(155, 248, 105);
}

.sonB {
height: 100px;
width: 300px;
padding: 5px;
background-color: rgb(247, 231, 87);
}

副作用管理-useEffect

注:本小节的服务端使用express搭建

useEffect是一个React Hook函数,用于在React组件中创建不是由事件引起而是由渲染本身引起的操作(称为“副作用”), 比如发送AJAX请求,更改DOM等等 。

场景:组件渲染完毕后需要向服务器请求数据,但此时未发生任何用户事件,整个过程就属于“只由渲染引起的操作”,需要使用useEffect。

基础使用

  • 首先要从react中引入useEffect

  • 使用语法:useEffect(() => {}, [])

    说明:

    • 参数一是一个回调函数(也可称为副作用函数),在函数内部可以放置要执行的操作(如发送AJAX请求,更改DOM等等)

    • 参数二为一个数组,它是一个可选参数。可以在数组里放置依赖项,不同依赖项会影响第一个参数函数的执行。

      关于useEffect的依赖说明 :

      useEffect副作用函数的执行时机存在多种情况,根据传入依赖项的不同,会有不同的执行表现,具体见下表:

      依赖项 副作用功函数的执行时机
      没有依赖项 组件初始渲染 + 组件更新时执行
      空数组依赖 只在初始渲染时执行一次
      添加特定依赖项 组件初始渲染 + 依赖项变化时执行

案例一:利用useEffect在组件渲染完毕后,使用axios向服务器发送异步请求获取数据。

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
import axios from 'axios';  // 引入axios
import { useState, useEffect } from 'react'; // 引入useEffect

function App() {
const [list, setList] = useState([]) // 使用useState维护list列表

// 使用useEffect()函数,在内部使用axios向服务器发送请求
useEffect(() => {
axios({
url: 'http://localhost:11000/data',
method: 'GET'
}).then(result => {
console.log(result.data.message)
setList(result.data.list) // 返回服务器结果,使用set()函数更新list数据
}).catch(error => {
console.dir(error)
})
}, []) // 使用空数组依赖(参数二)

return (
<div className="App father">
<ul>
{/* 渲染list数据 */}
{list.map(item => <li key = {item.id}> {item.name} </li>)}
</ul>
</div>
);
}

export default App;

服务端代码如下:

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
let express = require('express')
let app = express()

app.all('*', function (req, res, next) {
//设置允许跨域的域名,*代表允许任意域名跨域
res.header('Access-Control-Allow-Origin', '*')
//允许的header类型
res.header('Access-Control-Allow-Headers', 'content-type')
//跨域允许的请求方式
res.header('Access-Control-Allow-Methods', 'DELETE,PUT,POST,GET,OPTIONS')
if (req.method.toLowerCase() == 'options') res.send(200)
//让options尝试请求快速结束
else next()
})

// 定义列表数据
const data = {
message: '获取数据成功',
list: [
{
"id": 0,
"name": "c++"
},
{
"id": 1,
"name": "go"
},
{
"id": 2,
"name": "php"
},
{
"id": 3,
"name": "javascript"
},
{
"id": 4,
"name": "python"
},
{
"id": 5,
"name": "java"
},
{
"id": 6,
"name": "js"
}
]
}

// GET请求(无参),返回列表数据
app.get('/data', (req, res) => {
res.json({
message: data.message,
list: data.list
})
console.log(data.message)
console.log(data.list)
})

app.listen(11000, () => {
console.log('http://localhost:11000')
})

注:建议使用空数组依赖(如果没有特定依赖项使用),因为这样只在初始渲染时执行一次。如果useEffect()的参数二为空,则组件更新的时候就会执行,因为该例使用axios向服务器发送请求并通过set()函数更新list列表,每更新一次useEffect()函数就会执行一次,重新向服务器发送请求,进而陷入循环,会无限次的一直发送请求。

案例二:演示设置依赖项的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { useState, useEffect } from 'react';  // 引入useEffect

function App() {
const [count, setCount] = useState(0)
const [count2, setCount2] = useState(10)

useEffect(() => {
console.log('副作用函数执行了')
}, [count, count2]) // 当count和count2发生变化时,执行该函数

return (
<div className="App father">
<button onClick={() => setCount(count + 1)}>{count}</button>
<button onClick={() => setCount2(count2 + 1)}>{count2}</button>
</div>
);
}

export default App;

清除副作用

在useEffect中编写的由渲染本身引起的对接组件外部的操作,社区也经常把它叫做副作用操作。如果想在组件卸载时将操作清理掉,这个过程就是清理副作用。(比如在useEffect中开启了一个定时器,如果在组件卸载时没有及时将定时器删除,就会造成内存泄漏)

使用语法:

1
2
3
4
5
6
useEffect(() => {
// 实现副作用的操作
return () => {
//清除副作用的操作
}
}, [])

说明:清除副作用的函数最常见的执行时机是在组件卸载时自动执行

案例:在子组件中使用useEffect开启一个定时器,并在该子组件卸载时及时删除定时器,避免内存泄漏。

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
import './index.css'
import { useEffect, useState } from 'react';

function App() {
const [count, setCount] = useState(0) // 在父组件中显示计时器数字
const [show, setShow] = useState(true) // show用于控制子组件显示和“定时器停止”语句显示
return (
<div className="App father">
<div>App(father)</div>
<div>定时器:{count}</div>

{/* 向子组件中传递setCount和setShow函数 */}
{show && <Son setCount = {setCount} setShow = {setShow}/>}
{!show && <div>定时器停止</div>}

</div>
);
}

function Son(props) {

useEffect(() => {
// 在子组件中设置一个定时器
const timer = setInterval(() => {
props.setCount(prevCount => prevCount + 1)
}, 1000)
// 清除副作用(组件卸载时)
return () => {
clearInterval(timer) // 如果该组件被删除,则删除定时器
}
}, [])

return (
<div className="son">
<div>Son</div>
{/* onClick绑定一个回调函数,将子组件显示设置为false */}
<button onClick={() => props.setShow(false)}>删除子组件</button>
</div>
)
}

export default App;

index.css代码如下:

1
2
3
4
5
6
7
8
9
10
11
.father {
padding: 10px;
display: inline-block;
background-color: rgb(123, 234, 237);
}

.son {
height: 100px;
width: 300px;
background-color: rgb(155, 248, 105);
}

自定义Hook实现

自定义Hook是以use打头的函数,通过自定义Hook函数可以用来实现逻辑的封装和复用

基础使用

实现思路类似于“闭包”的思想:

  1. 声明一个以use打头的函数
  2. 在函数体内封装可复用的逻辑代码
  3. 把组件中用到的状态或者回调函数return出去(以对象或者数组的形式)
  4. 在哪个组件中要用到这个逻辑,就执行这个函数,解构出来状态和回调进行使用

案例:通过自定义Hook函数实现控制组件显示与否的复用代码

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
import './index.css'
import { useState } from 'react';

// 自定义一个Hook函数useToggle()
function useToggle() {
// 可复用的逻辑代码
const [show, setShow] = useState(true)
const toggle = () => setShow(!show)

// 哪些状态和回调函数需要在其他组件中复用,就用return返回出去
return {
show,
toggle
}
}

function App() {
const { show, toggle} = useToggle() // 通过解构获取useToggle()中的内容
return (
<div className="App father">
<div>App(father)</div>

<button onClick={toggle}>子组件{show ? '关闭' : '显示'}</button>
{show && <Son/>}

</div>
);
}

function Son() {
return (
<div className="son">
<div>Son</div>
</div>
)
}

export default App;

index.css代码如下:

1
2
3
4
5
6
7
8
9
10
11
.father {
padding: 10px;
display: inline-block;
background-color: rgb(123, 234, 237);
}

.son {
height: 100px;
width: 300px;
background-color: rgb(155, 248, 105);
}

Hooks使用规则

在React中针对所有Hook函数的使用都必须遵循以下规则:

  • 只能在组件中或者其他自定义Hook函数中调用
  • 只能在组件的顶层调用,不能嵌套在if、for,以及其它的函数中

后记

重点掌握Hooks的使用,包括:useStateuseRefuseEffect