0%

React-汇总

React学习笔记。

笔记摘自:视频博客

练习项目:react_demo

开发工具:vscode

vscode插件:Simple React Snippets

chrome开发者插件:React Developer Tools


Hello World

环境

安装node、npm(见vue笔记)。

创建项目

1
2
3
4
5
6
7
8
9
10
11
12
//安装react脚手架(全局安装)
npm install -g create-react-app

//进入要保存react项目的目录之下,执行下边的命令创建一个react项目
create-react-app [react项目名]

//进入新建的react项目,启动项目
cd 【react项目】
npm start

//项目启动后,会在浏览器中自动打开。当看到react的logo就说明项目创建成功了
http://localhost:3000

编写

编写Hello World项目。

建议使用vscode开发react项目,在编写Hello World项目之前可以先安装一个插件“Simple React Snippets”。

编写组件

将src目录下的文件全部删除,在其中新建一个“App.js”文件。第一个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
45
46
47
48
49
50
51
52
53
// import React from 'react'
// const Component = React.Component
//简写如下:
import React, { Component } from 'react'
import './style.css'//导入样式文件

//JSX语法:
//JSX javascript and xml
// < html
// { javascript

//大小写,区分自定义组件和普通html元素
// <App/> <div></div> <br/>
class App extends Component {//定义组件App
constructor(props) {
super(props);//被传入的数据可在组件中通过 this.props 在 render()中 访问
//上边为固定写法(构造器)。

//使用外部数据(父组件传递),通过 this.props 访问;组件还可以维护其内部的状态数据,通过 this.state 访问
this.state = {//在state中定义一个变量mystatus
mystatus: true
}

//尽管 this.props 和 this.state 是 React 本身设置的,且都拥有特殊的含义,但是其实你可以向 class 中随意添加不参与数据流的额外字段
//自定义一个变量mypunctuation,保存一个标点符号。在这个位置定义变量不参与数据流
this.mypunctuation = '!'
}

// state = {
// mystatus: true//写在constructor中或者写在外边
// }
// mypunctuation = '!';//写在constructor中或者写在外边
//还是建议写在constructor中,优先级高、

render() {//render() 方法,接收输入的数据并返回需要展示的内容
return (
//组件内容:
//这种类似 XML 的写法被称为 JSX
//样式类名使用className,避免与上边的组件class混淆
<div className='mydiv'>
{/* Hello {this.state.mystatus ? 'World' : 'qsdbl'}{this.mypunctuation} */}
Hello {this.getMystatus() ? 'World' : 'qsdbl'}{this.mypunctuation}
</div>
)
}

//自定义函数
getMystatus() {
return this.state.mystatus;
}
}

export default App

可以通过更改变量mystatus、mypunctuation的值,查看页面显示效果。

添加样式

组件中html元素直接添加样式,方式如下:

1
<div style={{margin:'10px'}}></div>

单独编写css样式文件,方式如下:

为组件App添加样式。在src目录下新建一个style.css文件。在其中添加css代码:

1
2
3
.mydiv{
background-color: aquamarine;
}

在组件App中使用样式文件,只需要在App.js中添加语句import './style.css'即可。

挂载组件

在src目录下新建一个“index.js”文件。用于挂载刚刚编写好的组件App。

1
2
3
4
5
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'// "./"表示当前目录。自定义组件,必须大写字母开头

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

测试

此时打开浏览器http://localhost:3000就会看到“Hello World!”并且背景色是青色的。

固定格式

编写组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React, { Component } from 'react';

class App extends Component {
constructor(props) {
super(props);
//定义变量
}
render() {
return (//组件内容
<h2>hello!</h2>
);
}
//定义函数
}

export default App;

扩展:组件只能有一个根元素。若不想只有一个根元素可使用Fragment标签包裹。Fragment是react提供的,可将import修改为import React, { Component, Fragment } from 'react'

挂载组件

1
2
3
4
5
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

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

函数定义

函数定义,涉及到this关键字的问题。若函数应用在事件绑定中,可能this就不是指向当前class,也就无法使用this.props.this.state.

要解决这个问题可以在绑定事件时使用bind(this)constructor中声明指向当前class

1
2
3
4
5
6
7
8
9
10
11
//constructor中声明
this.handleClick = this.handleClick.bind(this);//绑定 作用域
//render中:
{/* constructor中声明,this指向当前class */}
<li onClick={this.handleClick}>{this.props.myvalue}</li>

//绑定事件时使用bind
//render中:
<li onClick={this.handleClick.bind(this)}>{this.props.myvalue}</li>
{/* 使用bind,涉及到一个传参问题,可以写在this后边,this不算参数 */}
<li onClick={this.handleClick.bind(this,'1')}>{this.props.myvalue}</li>

数据更新

state中定义的变量,数据更新要使用setState函数。

  • 不建议直接更改state里面的数据,而是通过setState去改变参数(不会重新渲染组件;性能问题)。

  • 构造函数是唯一可以给 this.state 赋值的地方

1
2
3
4
5
this.setState({
//将inputValue的值和list组成一个新数组,赋值给list
list: [...this.state.list, this.state.inputValue],
inputValue: ''
})//dom渲染是异步的,如果想在dom渲染完成后执行某些操作可以在setState中添加第二个参数,一个回调函数

setState函数可以接收一个回调函数,在dom渲染后调用。

父子组件

父组件

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import React, { Component, Fragment } from 'react'
import './style.css'
import Xjjitem from './xjj_item';//导入子组件

//Fragment 标签,不会被渲染成HTML元素
//变量 建议所有小驼峰命名
export default class xjj extends Component {
constructor(props) {//固定写法。自定义变量写在constructor的state中
super(props);
//自定义变量,放在state中
this.state = {
inputValue: '',
list: ['头部按摩', '精油推背']
}
}
render() {
return (
// <div>
// {} javascript语法
<Fragment>
<div>
{/* htmlFor用于设置焦点,当label元素被点击时, id为my_input的元素将会获得焦点*/}
<label htmlFor='my_input'>添加服务:</label>
{/* 样式类名使用className,避免与上边的组件class混淆 */}
<input
id='my_input'
className='myinput'
value={this.state.inputValue}
onChange={this.inputChange.bind(this)}
// 使用 ref 将当前元素 添加/绑定 到this.myinput(当前定义的名为xjj的class的一个变量,但是不参与数据流)
ref={element=>{this.myinput = element}}
/>
<br /><br />
{/* .bind(this),绑定作用域 */}
<button onClick={this.clickBtn.bind(this)}>增加服务</button>
<br /><br />
</div>
<ul>{
// map遍历,会将结果以数组的形式返回。注意:遍历都要添加key
this.state.list.map((item, index) => {
return (//添加括号(),更方便的编写HTML
// <li key={index + item}
// onClick={this.deleteItem.bind(this, index)}
// dangerouslySetInnerHTML={
// // __html将内容解析为html
// { __html: item }
// }
// >
// {/*纯文本,则只需要 {item} */}
// </li>

//将item、index传递给子组件,在子组件中通过this.props.myvalue方式获取相关值
//将父组件中的方法deleteItem 传递(绑定)到子组件的方法mydeleteItem(可同名)
<Xjjitem key={index + item} myvalue={item} myindex={index} mydeleteItem={this.deleteItem.bind(this)} />
)
})
}</ul>
</Fragment>
// </div>
)
}
deleteItem(index) {//注意看参数
console.log('父组件-删除元素,index = ' + index)
// 不建议直接更改state里面的数据,而是通过setState去改变参数(不会重新渲染组件;性能)
//构造函数是唯一可以给 this.state 赋值的地方
//错误示例:
// this.state.list.splice(index, 1);
// this.setState({
// list: this.state.list
// })

//正确写法:
let mylist = this.state.list;
mylist.splice(index, 1);
this.setState({
list: mylist
})
}
//自定义函数,用于绑定input元素的change事件
inputChange(e) {//e,onChange事件传递过来的参数(react合成事件)
// console.log(e)
this.setState({
// inputValue: e.target.value//取出input元素中输入的值
inputValue: this.myinput.value//this.myinput绑定了input元素,直接“.value”获取输入框中的数据
})
//标准js代码:
// console.log(this.myinput)//this.myinput绑定了input元素
// console.log(this.myinput.value)//获取input元素的输入框内容
// console.log(this.myinput.className)//获取input元素所应用的class样式
// console.log(this.myinput.style.border)//获取input的border样式
}

clickBtn() {
// console.log(e)
// this.state.list.push(e.target.value)
this.setState({
//将inputValue的值和list组成一个新数组,赋值给list
list: [...this.state.list, this.state.inputValue],
inputValue: ''
})//dom渲染是异步的,如果想在dom渲染完成后执行某些操作可以在setState中添加第二个参数,一个回调函数
}
}

子组件

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';
class xjj_item extends Component {
constructor(props) {
super(props)
this.handleClick = this.handleClick.bind(this);//绑定 作用域
}
state = {}
render() {
return (
<div>
<li onClick={this.handleClick}>{this.props.myvalue}</li>
</div>
);
}
handleClick() {//子组件的点击事件(前边给函数handleClick绑定作用域)
//父组件中传递过来的值、函数,可以通过this.props.直接获取
//this.props.myvalue
//this.props.myindex
console.log('点击了子组件' + this.props.myindex)
//调用父组件中的方法 mydeleteItem = deleteItem
this.props.mydeleteItem(this.props.myindex);

//单向数据流,在子组件中不允许直接修改父组件的数据,只能通过调用父组件的函数实现
}
//函数式编程,清晰、方便测试
}

export default xjj_item;

传值

父传子

在父组件中,使用子组件时使用参数名={父组件变量/函数/数据}即可定义参数。在子组件中通过this.props.参数名的方式获取相关值。

1
2
3
4
5
6
7
8
9
//父组件的render()中:
//将item、index传递给子组件,在子组件中通过this.props.myvalue方式获取相关值(子组件中不需要额外定义/声明 myvalue、myindex、mydeleteItem,在这里写即可)
//将父组件中的方法deleteItem 传递(绑定)到子组件的方法mydeleteItem(可同名)
<Xjjitem key={index + item} myvalue={item} myindex={index} mydeleteItem={this.deleteItem.bind(this)} />


//子组件中:
<li onClick={this.handleClick}>{this.props.myvalue}</li>
//父组件中传递过来的值、函数,可以通过this.props.直接获取

子传父

子组件中不能操作父组件的数据(单向数据流),只能通过调用父组件提供的函数实现对数据的操作。

1
2
3
4
5
6
7
8
//子组件中:
handleClick() {
...
//调用父组件中的方法 mydeleteItem = deleteItem
this.props.mydeleteItem(this.props.myindex);
//单向数据流,在子组件中不允许直接修改父组件的数据,只能通过调用父组件的函数实现
...
}

ref

1
2
3
4
5
6
7
8
9
10
11
12
13
// 使用 ref 将当前元素 添加/绑定 到this.myinput(当前定义的名为xjj的class的一个变量,但是不参与数据流)
ref={element=>{this.myinput = element}}


//注意看使用ref绑定元素后 和 传递事件对象e 取值的不同:
inputChange(e) {
...
this.setState({
// inputValue: e.target.value//取出input元素中输入的值
inputValue: this.myinput.value//this.myinput绑定了input元素,直接“.value”获取输入框中的数据
})
...
}

PropTypes

给父组件传递过来的参数(通过this.props.来获取),指定其数据类型(字符串、数字、函数等),在传参时会校验,若不匹配会在控制台提示,但不影响程序运行。

在要校验参数数据类型的子组件中,导入prop-types

1
import myPropTypes from 'prop-types';

class外边定义父组件传递过来的参数类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class xjj_item extends Component {
...
}
//propTypes,是组件的一个属性,在这里定义参数类型
xjj_item.propTypes = {
//myvalue、myindex、mydeleteItem,为父组件传递过来的属性、函数
//使用前边导入的 myPropTypes 指定其数据类型
myvalue: myPropTypes.string,//字符串
myindex: myPropTypes.number,//数字
mydeleteItem: myPropTypes.func,//函数
myvalue02: myPropTypes.string.isRequired,//字符串,且必填(必须要传递这个参数)
myvalue03: myPropTypes.string

//数据类型校验不通过,会在控制台提示,但是程序依然可执行
}

//设置默认值
xjj_item.defaultProps = {
myvalue03: '0000'//当父组件不传该参数时,将使用此默认值
}

生命周期

组件挂载

组件挂载mount,分为:

  • 挂载前 - componentWillMount,已更名为UNSAFE_componentWillMount。已废弃,但依然可用(两个都可用)
  • 挂载中 - 可以把render函数的return之前看作是挂载中
  • 挂载后 - componentDidMount

若在首次渲染组件之前要获取一些数据,可以在componentDidMount生命周期函数中发起网络请求。避免组件更新时频繁获取数据。

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
export default class xjj extends Component {
constructor(props) {
...
}

UNSAFE_componentWillMount() {
console.log('组件挂载--前')
}
// componentWillMount() {
// //已废弃。但依然可用
// console.log('组件挂载--前')
// }
componentDidMount() {
console.log('组件挂载--后')
}

render() {
console.log('----执行render--组件正在挂载----')
return (
...
)
}
}

//打印的结果为:
组件挂载--前
----执行render--组件正在挂载----
组件挂载--后

组件更新

组件更新update,分为:

  • 更新前 - shouldComponentUpdate(nextProps, nextState),根据返回的boolean值决定是否更新组件
    • 接收两个参数,即将要重新渲染的组件中的props和state
  • 更新前 - componentWillUpdate,根据返回的boolean值决定是否更新组件 - 已废弃,建议使用上边的
  • 更新后 -componentDidUpdate
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
shouldComponentUpdate(){
console.log('--组件【更新】之前shouldComponentUpdate--')
return true;//若返回false,将阻止组件的渲染
}

UNSAFE_componentWillUpdate(){
console.log('--组件【更新】之前componentWillUpdate(已废弃)--')
return true;
}
// componentWillUpdate(){
// //已废弃,但依然可用。更名为了UNSAFE_componentWillUpdate
// console.log('--组件【更新】之前componentWillUpdate--')
// return true;
// }

componentDidUpdate(){
console.log('--组件【更新】之后componentDidUpdate--')
}



//运行结果:
//挂载阶段:
组件【挂载】(已废弃)--前
----执行render--组件正在【挂载】----
组件【挂载】--后

//数据更新阶段:
--组件【更新】之前shouldComponentUpdate--
--组件【更新】之前componentWillUpdate(已废弃)--
----执行render--组件正在【挂载】----
--组件【更新】之后componentDidUpdate--

组件卸载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
componentWillUnmount() {
console.log('组件被【卸载】之前componentWillUnmount--')
}

//运行结果:
//挂载阶段:
组件【挂载】(已废弃)--前
----执行render--组件正在【挂载】----
组件【挂载】--后

//删除子组件
点击了子组件0
父组件-删除元素,index = 0

//组件更新阶段:
--组件【更新】之前shouldComponentUpdate--
--组件【更新】之前componentWillUpdate(已废弃)--
----执行render--组件正在【挂载】----
组件被【卸载】之前componentWillUnmount--
--组件【更新】之后componentDidUpdate--

props

已废弃,不建议使用。

1
2
3
4
5
6
//组件第一次存在于dom中,函数是不会被执行
//如果已经存在dom中,函数才会被执行
componentWillReceiveProps(){
//已废弃,但依然可用
console.log('组件接收props之前,一般在子组件中使用--')
}

性能优化

使用生命周期钩子函数shouldComponentUpdate(组件更新前,可阻止组件重新渲染)进行性能优化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
shouldComponentUpdate(nextProps, nextState) {//组件渲染之前。接收两个参数
console.log('--子组件【更新】之前shouldComponentUpdate--')
if (nextProps.myvalue !== this.props.myvalue) {
//即将要渲染的组件的props.myvalue(nextProps.myvalue,已渲染过的组件,其中保存的myvalue),传递进来的props.myvalue(this.props.myvalue)
//两者进行比较,若数据不相同则需要重新渲染(父组件的数据发送了改变,则不同)
return true;
} else {
//若数据相同则不需要重新渲染
console.log('不允许渲染--')
return false;//若返回false,将阻止组件的渲染

}
}


//测试结果:
--子组件【更新】之前shouldComponentUpdate--
不允许渲染--

--子组件【更新】之前shouldComponentUpdate--
不允许渲染--
允许渲染--
-------渲染子组件

扩展:chrome开发者工具中打开React插件,在设置中勾选“Hightlight updates when components render”,可直观的看到哪些组件被渲染。

Ajax

使用Axios实现Ajax请求,Axios的安装和使用,见这篇笔记

一般在componentDidMount生命周期函数中发起网络请求较好,可避免组件更新时频繁发起Ajax请求。

后记

仅仅使用React还远不够用来开发项目,还需要借助Redux进行数据管理、React Router进行路由管理等。

草稿

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
  // useEffect可以看做 componentDidMount,componentDidUpdate和componentWillUnmount 这三个函数的组合

//useEffect,有两个参数:副效应函数、副效应函数的依赖项(指定props中的一个变量。填空数组则不依赖任何变量,只执行一次)
//只有依赖项(变量)发生变化时,副效应函数才会执行
useEffect(() => {
getStoreRec({ pagination }).then((res) => {
setStoreRec(res.data.list);
setPagination({
pageSize: res.data.pageSize,
currentPage: res.data.currentPage,
total: res.data.total,
});

console.log('更新数据');
});
}, []); //第二个参数为一个空数组,只会执行一次。等同于componentDidMount

// 那么我想通过useEffect模拟componentDidUpdate生命周期,而去掉初次渲染时调用的 componentDidMount
// useRef实现
// 因为初次渲染只会发生一次,可以使用useRef立一个flag解决:
const mounting = useRef(true);
useEffect(() => {
if (mounting.current) {
console.log('初次');
mounting.current = false;
return;
}
console.log('DidUpdated');
//。。。
});
/*
总结:
componentDidMount = useEffect,第二个参数为空数组
componentDidMount + componentDidUpdate = useEffect,不添加第二个参数
componentDidUpdate = useEffect,不添加第二个参数并配合useRef使用
*/

修改控件的默认样式

1
2
3
4
5
6
7
8
9
10
11
//使用.btn_box做限定,不想全局修改
//使用global
//修改的是Card控件中的.ant-card-body
.btn_box :global(.ant-card-body) {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
padding: 24px;
}

useState

在React的函数组件中引入state。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//定义一个sate,名为matList
//第一个为state变量名,第二个为更新此state的函数。初始值为空数组[]
const [matList, setMatList] = useState([]);

//给变量matList赋值,使用函数setMatList,而不是直接操作变量matList
//setMatList(['1','2','3'])

//注意:若要数据及时更新(渲染到页面)要使用“函数式更新”来更新state
//函数式更新:
//resMat.data为上文中的一个数组

...
setMatList((data) => {//data为最新的matList
return [...data, ...resMat.data];//将最新的matList和数组resMat.data拼接在一起,并更新到matList
});
...
若图片不能正常显示,请在浏览器中打开

欢迎关注我的其它发布渠道