React学习-基础
前言
版本:
- 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
11const 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
17function 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
19const 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 | const list = [ |
React要求在渲染重复元素的时候需要给每个元素绑定一个独一无二的
Key
属性。否则会报错:
条件渲染
可以通过以下三种方式实现条件渲染:
-
逻辑运算符
1
2
3
4
5
6
7
8
9
10
11const 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
11const 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
21function 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 | const clickHandler = () => { |
控制台输出结果:
1 | button按钮被点击了 |
参数传递
事件参数
直接在回调函数中设置形参e:
1 | const clickHandler = (e) => { |
控制台输出结果:
1 | button按钮被点击了 |
自定义参数
使用自定义参数时,事件绑定的位置不能直接写函数调用,这里需要一个函数引用,可以改为箭头函数的写法:
1 | const clickHandler = (name) => { |
控制台输出结果:
1 | button按钮被点击了 |
同时传递事件对象和自定义参数
在事件绑定的位置传递事件实参e和自定义参数,自定义的函数中声明形参,注意顺序对应:
1 | const clickHandler = (name, e) => { |
控制台输出结果:
1 | button按钮被点击了 |
组件
一个组件就是一个用户界面的一部分,它可以有自己的逻辑和外观,组件之间可以互相嵌套,也可以复用多次。
基础使用
在React中,一个组件就是首字母大写的函数,内部存放了组件的逻辑和视图UI, 渲染组件只需要把组件当成标签书写即可,可以通过自闭合标签以及成对标签两种方式实现:
1 | // 1. 自定义Button组件 |
组件状态管理-useState
useState是一个React Hook(函数),它允许向组件添加一个状态变量, 从而控制影响组件的渲染结果。
与普通JS变量不同的是,状态变量一旦发生变化组件的视图UI也会跟着变化(即数据驱动视图)
基础使用
-
首先需要从react中引入useState
也可以不引入直接使用
React.useState
调用 -
使用useState会维护一个状态变量以及一个修改状态变量的
set()
方法,可以通过解构的方式得到它们 -
编写一个回调函数用以绑定事件,函数内部可以调用
set()
方法修改状态变量
1 | // 1.引入useState |
状态变量的修改规则:在React中状态被认为是只读的,应该始终“替换它而非修改它”, 直接修改状态不能引发视图更新:
1
2
3
4
5 const handleClick = () => {
// 直接修改,无法引发视图UI更新
count++
console.log(count)
}
修改对象状态
对于对象类型的状态变量,应该始终给set()
方法一个新的对象来替换旧的对象进行修改:
1 | import { useState } from 'react' |
控制台输出结果:
1 | {Name: '花猪', Age: 18} |
表单受控绑定
React可以通过useState控制表单的状态。
1 | import { useState } from "react"; |
这里使用useState维护输入框的值value,并绑定一个改变事件,回调函数直接调用set()方法,获取输入框中新的value值,覆盖旧的value值。
DOM获取-useRef
useState是一个React Hook(函数),用于创建一个可变的ref对象。其常见的使用场景为:访问DOM元素,存储React元素。
基础使用
- 首先需要从react中引入uesRef
- 使用useRef创建一个ref对象,初始化参数可选
- 在DOM上通过
ref
属性将ref对象绑定到该DOM上 - 通过ref对象中的
current
属性可以查看获取到的DOM元素的属性
注:只有当渲染完毕之后生成的DOM才可用。
1 | import { useRef } from "react"; // 从react中引入uesRef |
组件样式处理
React组件基础的样式控制有两种方式:
-
行内样式
1
2
3
4
5
6
7
8
9function 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
11import './index.css'
function App() {
return (
<div className="App">
<span className = 'foo'> 样式处理 </span>
</div>
);
}
export default App;可以使用classnames优化类名控制:
classnames是一个简单的JS库,可以非常方便的通过条件动态控制class类名的显示。
安装:
npm install classnames --save
使用案例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15import 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:跨层通信
父子通信
父传子
实现步骤:
-
父组件传递数据:在父组件中的子组件标签中绑定属性,进行数据传递
-
子组件接收数据:子组件通过
props
参数接收来自父组件的数据参数名并不固定,只是规范化写为
props
。关于
props
参数:- props可以传递任意的合法数据:如数字、字符串、布尔值、数组、对象、函数、JSX。
- props是只读对象,子组件只能读取props中的数据,不能直接进行修改, 父组件的数据只能由父组件修改。
1 | import './index.css' |
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 | import './index.css' |
子传父
子传父的核心思路:在子组件中调用父组件中的函数并传递参数。
实现步骤:
- 首先在父组件中创建一个函数用于获取子组件传来的数据
- 通过“父传子”的方式将该函数传递给子组件,以便子组件可以调用
- 子组件通过调用父组件传递来的方法并通过传递参数实现“子传父”
1 | import './index.css' |
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):
- 组件A先通过“子传父”的方式把数据传递给父组件
- 父组件再通过“父传子”的方式把数据传递给子组件
1 | import './index.css' |
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);
}
跨层通信
实现步骤:
- 通过
createContext()
方法创建一个上下文对象 - 在顶层组件中通过
上下文对象.Provider
组件创建一个传播域,将要传递的子组件包裹起来,通过value
属性传递数据。 - 在底层组件中通过
useContext()
钩子函数获取传递来的数据
注:createContext、useContext需要从react中引入。
1 | import './index.css' |
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 | import axios from 'axios'; // 引入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
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 | import { useState, useEffect } from 'react'; // 引入useEffect |
清除副作用
在useEffect中编写的由渲染本身引起的对接组件外部的操作,社区也经常把它叫做副作用操作。如果想在组件卸载时将操作清理掉,这个过程就是清理副作用。(比如在useEffect中开启了一个定时器,如果在组件卸载时没有及时将定时器删除,就会造成内存泄漏)
使用语法:
1 | useEffect(() => { |
说明:清除副作用的函数最常见的执行时机是在组件卸载时自动执行
案例:在子组件中使用useEffect开启一个定时器,并在该子组件卸载时及时删除定时器,避免内存泄漏。
1 | import './index.css' |
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函数可以用来实现逻辑的封装和复用。
基础使用
实现思路类似于“闭包”的思想:
- 声明一个以use打头的函数
- 在函数体内封装可复用的逻辑代码
- 把组件中用到的状态或者回调函数return出去(以对象或者数组的形式)
- 在哪个组件中要用到这个逻辑,就执行这个函数,解构出来状态和回调进行使用
案例:通过自定义Hook函数实现控制组件显示与否的复用代码
1 | import './index.css' |
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的使用,包括:useState
、useRef
、useEffect
。