Marvel-Site Marvel-Site
首页
  • Java

    • Java基础
    • Java进阶
    • Java容器
    • Java并发编程
    • Java虚拟机
  • 计算机基础

    • 数据结构与算法
    • 计算机网络
    • 操作系统
    • Linux
  • 框架|中间件

    • Spring
    • MySQL
    • Redis
    • MQ
    • Zookeeper
    • Git
  • 架构

    • 分布式
    • 高并发
    • 高可用
    • 架构
  • 框架

    • React
    • 其他
  • 实用工具
  • 安装配置

    • Linux
    • Windows
    • Mac
  • 开发工具

    • IDEA
    • VsCode
  • 关于
  • 收藏
  • 草稿
  • 索引

    • 分类
    • 标签
    • 归档
GitHub (opens new window)

Marvel

吾必当乘此羽葆盖车
首页
  • Java

    • Java基础
    • Java进阶
    • Java容器
    • Java并发编程
    • Java虚拟机
  • 计算机基础

    • 数据结构与算法
    • 计算机网络
    • 操作系统
    • Linux
  • 框架|中间件

    • Spring
    • MySQL
    • Redis
    • MQ
    • Zookeeper
    • Git
  • 架构

    • 分布式
    • 高并发
    • 高可用
    • 架构
  • 框架

    • React
    • 其他
  • 实用工具
  • 安装配置

    • Linux
    • Windows
    • Mac
  • 开发工具

    • IDEA
    • VsCode
  • 关于
  • 收藏
  • 草稿
  • 索引

    • 分类
    • 标签
    • 归档
GitHub (opens new window)
  • 基础

  • 框架

    • React

      • React入门
      • React组件
        • React插件
        • React组件介绍
          • 函数式组件
          • 类式组件
          • 类的知识点
          • 类式组件
        • 三大属性
          • 状态state
          • 属性Props
          • 传递 props
          • 限制 props
          • 引用refs
          • 字符串形式
          • 回调函数形式
          • 回掉执行次数的问题
          • createRef
        • 收集表单数据
          • 非受控组件
          • 受控组件
          • 高阶函数-函数柯里化
        • 组件的生命周期
          • 组件的生命周期(旧)
          • 组件的生命周期(新)
      • DOM的Diffing算法
      • React脚手架介绍
      • React简单案例
      • React路由
      • redux
      • 井字棋
    • 其他

  • Node

  • 前端
  • 框架
  • React
Marvel
2023-08-21
目录

React组件

# React组件

# React插件

网页还处于开发者模式,代码没有打包上线:

image.png

正常状态:

image.png

# React组件介绍

# 函数式组件

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <!-- 准备好“容器” -->
    <div id="test"></div>

    <script type="text/javascript" src="../React-js/react.development.js"></script>
    <script type="text/javascript" src="../React-js/react-dom.development.js"></script>
    <script type="text/javascript" src="../React-js/babel.min.js"></script>

    <script type="text/babel">
        // 创建组件
        function MyComponent() {
	        console.log(this) // undefined
            return <h2>我是用函数定义的组件</h2>
        }
        // 渲染组件
        ReactDOM.render(<MyComponent/>, document.getElementById("test"))
    </script>
  </body>
</html>
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

可以通过开发者插件查看: image.png

这里的this是undefined,代码是通过Babel编译的,开启了严格模式(ES5),禁止自定义函数里的this指向window,所以只能是undefined。

Babel试一试 (opens new window)

image.png

执行了ReactDOM.render(<MyComponent/>, document.getElementById("test"))后:

  1. React解析组件标签,找到了MyComponent组件
  2. 发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中。

# 类式组件

# 类的知识点

  • 类中的构造器不是必须写的,要对实例进行一些初始化的操作,如添加指定属性时。
  • 如果A类继承了B类,且A类中写了构造器,那么A类构造器中的super是必须要嗲用的。
  • 类中所定义的方法,都是放在了类的原型对象上。
// Create Class Person
class Person {
	// 构造器中的this指的是类的实例对象
	constructor(name,age){
		this.name = name
		this.age = age
	}
	speak(){ 
		// 该方法放在了类的原型对象上,供实例使用
		// 通过Person实例调用speak时,speak中的this就是Person实例
		console.log(`My name is ${this.name}, I am ${this.age} years old!`);
	}
}

class Student extends Person {
	constructor(name, age, grade){
		super(name, age)
		this.grade = grade
	}
	speak() {
		console.log("No speak!")
	}
	study() {
		console.log("Studing...")
	}
}

const p = new Person('Tom', 18)
const d = new Person('Jerry', 19)
const s = new Student('James', 10, 6)
console.log(p);
console.log(d);
console.log(s)
p.speak();
d.speak();
p.speak.call({a:1, b:2});  // call可以更改函数中this的指向
s.speak();
s.study();
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

image.png

# 类式组件

<script type="text/babel">
	// 创建组件
	class MyComponent extends React.Component {
		render() {
			// this是MyComponent的实例对象
			console.log(this);
			return <h2>我是类定义的组件</h2>
		}
	}
	// 渲染组件
	ReactDOM.render(<MyComponent/>, document.getElementById("test"))
</script>
1
2
3
4
5
6
7
8
9
10
11
12

执行了ReactDOM.render(<MyComponent/>, document.getElementById("test"))后:

  1. React解析组件标签,找到了MyComponent组件
  2. 发现组件是使用类定义的,随后new出来该类的实例,并通过实例调用到原型上的方法的render方法
  3. 将render返回的虚拟DOM转为真实DOM,随后呈坎在页面中。

# 三大属性

# 状态state

复杂组件指有状态state的组件,state在组件实例上。

人-状态-影响-行为

组件-状态-驱动-页面

image.png

状态初始化:

<script type="text/babel">
	// 创建组件
	class MyComponent extends React.Component {
		constructor(props){
			super(props)
			this.state = {isHot:true}
		}
		render() {
			// this是MyComponent的实例对象
			console.log(this);
			return <h2>Today is {this.state.isHot ? 'Hot' : 'Cool'}</h2>
		}
	}
	// 渲染组件
	ReactDOM.render(<MyComponent/>, document.getElementById("test"))
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

state是一个对象

image.png

原生事件绑定:JS的原生绑定方式

<body>
    <button id="btn1">Button1</button>
    <button id="btn2">Button2</button>
    <button id="btn3" onclick="demo()">Button3</button>

    <script>
        const btn1 = document.getElementById('btn1')
        btn1.addEventListener('click',()=>{
            alert('btn1 is clicked')
        })
        
        const btn2 = document.getElementById('btn2')
        btn2.onclick = () => {
            alert('btn2 is clicked')
        }

        function demo() {
            alert('btn3 is clicked')
        }
    </script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

React事件绑定

  • onclick 要换成 onClick
  • <h2 onClick={demo()}>表示onClick的值为demo()执行后的返回值
  • <h2 onClick={demo}>表示按键触发demo函数
  • 类中所有定义的方法,它在局部都开启了严格模式。
// 直接调用
function demo(){
	console.log(this)
}
// 直接调用
demo() // 输出window

function demo(){
	'use strcit'
	console.log(this)
}
// 直接调用
demo() // 输出undefined
1
2
3
4
5
6
7
8
9
10
11
12
13
  • bind()方法返回新的方法,并不会执调用
  • this.changeWeather = this.changeWeather.bind(this) 解决this指向问题
  • 状态里面的数据不可以直接修改,要使用内置API进行更改。
<script type="text/babel">
	const btn = document.getElementById('btn');
	// 创建组件
	class Weather extends React.Component {
		constructor(props){
			super(props)
			this.state = {isHot:true}
			// 拿原型上的生成后放在实例上
			this.changeWeather = this.changeWeather.bind(this)  // 解决this指向问题
		}
		render() {
			const {ishot} = this.state
			return <h2 onClick={this.changeWeather}>Today is {this.state.isHot ? 'Hot' : 'Cool'}</h2>
		}
		changeWeather() {
			// 通过Weather实例调用ChangeWeather时,changeWeather中的this就是Weater实例
			// 由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用。
			// 类中方法默认开启了局部严格模式,this也不是window
			// this.state.isHot = !this.state.isHot
			console.log(this)
		}
	}
	// 渲染组件
	ReactDOM.render(<Weather/>, document.getElementById("test"))

</script>
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
  • 构造器调用1次
  • render调用n+1次,1次初始化,n次状态更新的次数
  • 构造器:初始化状态、解决this指向问题
<script type="text/babel">
	// 创建组件
	class Weather extends React.Component {
		constructor(props){ // 构造器调用1次
			super(props)
			this.state = {isHot:true, wind:'wind'}
			// 拿原型上的生成后放在实例上
			this.changeWeather = this.changeWeather.bind(this)  // 解决this指向问题
		}
		render() { // render调用n+1次,1次初始化,n次状态更新的次数
			const {isHot, wind} = this.state
			return <h2 onClick={this.changeWeather}>Today is {isHot ? 'Hot' : 'Cool'}, {wind}</h2>
		}
		changeWeather() {
			// 通过Weather实例调用ChangeWeather时,changeWeather中的this就是Weater实例
			// 由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用。
			// 类中方法默认开启了局部严格模式,this也不是window
			const isHot = this.state.isHot
			this.setState({isHot:!isHot})
		}
	}
	// 渲染组件
	ReactDOM.render(<Weather/>, document.getElementById("test"))
0
</script>

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 Weather extends React.Component {
	// 初始化状态
	state = {isHot:true, wind:'wind'}
	render() { 
		const {isHot, wind} = this.state
		return <h2 onClick={this.changeWeather}>Today is {isHot ? 'Hot' : 'Cool'}, {wind}</h2>
	}
	// 自定义方法,要用赋值语句的形式+箭头函数
	changeWeather = () => {
		const isHot = this.state.isHot
		this.setState({isHot:!isHot})
	}
}
// 渲染页面
ReactDOM.render(<Weather/>, document.getElementById("test"))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  • 组件中render方法中的this为组件实例对象
  • 组件自定义的方法中this为undefined解决方法:
    1. 强制绑定this:通过函数对象的bind()
    2. 箭头函数
  • 状态数据,不能直接修改或更新

# 属性Props

# 传递 props

简单使用:属性可以直接在创建组件的时候传入

<body>
    <!-- 准备好“容器” -->
    <div id="test1"></div>
    <div id="test2"></div>
    <script type="text/javascript" src="../React-js/react.development.js"></script>
    <script type="text/javascript" src="../React-js/react-dom.development.js"></script>
    <script type="text/javascript" src="../React-js/babel.min.js"></script>

    <script type="text/babel">
        // 创建组件
        class Person extends React.Component {
            render() {
                const {name, age, sex} = this.props
                return (
                    <div>
                        <ul>
                            <li>姓名:{name}</li>    
                            <li>年龄:{age}</li>    
                            <li>性别:{sex}</li>    
                        </ul>  
                    </div>
                )
            }
        }
        // 渲染页面
        ReactDOM.render(<Person name="tom" age="18" sex="girl" />, document.getElementById("test1"))
        ReactDOM.render(<Person name="jerry" age="19" sex="boy" />, document.getElementById("test2"))
    </script>
</body>
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

当组件内属性太多时,可以这么【批量传递标签属性】或者说【批量传递属性】:

const p = {name:"xxx", age:"14", sex:"boy"}
ReactDOM.render(<Person {...p} />, document.getElementById("test1"))
1
2

{...p}

  • 在JS里,展开运算符不是展开对象,而是复制对象;
  • 在React里,花括号是分隔符,...p 表示展开对象,但不能随意使用,只能在标签属性的传递。

# 限制 props

添加规则:必要性、类别、默认值 需要引入 prop-types.js ,对组件标签进行限制,全局就会多了 PropTypes

// 创建组件
class Person extends React.Component {
	render() {
		const {name, age, sex} = this.props
		return (
			<div>
				<ul>
					<li>姓名:{name}</li>    
					<li>年龄:{age}</li>    
					<li>性别:{sex}</li>    
				</ul>  
			</div>
		)
	}
	speak() {
		console.log("xaxa");
	}
}
// 限制规则
Person.propTypes = {
	name:PropTypes.string.isRequired,  // name是字符串类型,且为必填项
	sex:PropTypes.string,
	age:PropTypes.number,
	speak:PropTypes.func,  // 限制为函数
}
// 默认值设置
Person.defaultProps = {
	sex:"none",
	age:18
}
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

props是只读的,不能修改,如this.props.name = "Jerry"

简写:限制条件从外侧转移到类里面

// 创建组件
class Person extends React.Component {
	// 限制规则
	static propTypes = {
		name:PropTypes.string.isRequired,  // name是字符串类型,且为必填项
		sex:PropTypes.string,
		age:PropTypes.number,
		speak:PropTypes.func,  // 限制为函数
	}
	// 默认值设置
	static defaultProps = {
		sex:"none",
		age:18
	}
	render() {
	}
	speak() {
	}
}

// 渲染页面
const p = {name:"xxx", age:14, sex:"boy"}
ReactDOM.render(<Person {...p} />, document.getElementById("test1"))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

构造器是否接收props,是否传递给super,取决于是否希望在构造器中通过this访问props,但是这种场景基本用不到。

constructor(props) {
	console.log(props)
	super(props)
	console.log(this.props)
}
1
2
3
4
5

函数式组件只能使用props,条件限制只能写在函数外侧,不能使用state和ref

function Person(props) {
	const {name, age, sex} = props
	return (
		<ul>
			<li>姓名:{name}</li>    
			<li>年龄:{age}</li>    
			<li>性别:{sex}</li>    
		</ul>  
	)
}
// 渲染页面
const p = {name:"Jerry", age:14, sex:"boy"}
ReactDOM.render(<Person {...p} />, document.getElementById("test1"))
1
2
3
4
5
6
7
8
9
10
11
12
13

# 引用refs

# 字符串形式

不推荐使用字符串类型的ref,因为它存在一些问题(效率问题),未来的版本可能移除。

class MyComponent extends React.Component {
	// 展示左侧输入框数据
	showData = () => {
		console.log(this.refs.input1.value);
	}
	// 展示右侧输入框数据
	showData2 = () => {
		console.log(this.refs.input2.value);
	}
	render() {
		return(
			<div>
				<input ref="input1" type="text" placeholder="Push button show data"/>&nbsp;
				<button ref="button" onClick={this.showData}>push show right data</button>&nbsp;
				<input ref="input2" onBlur={this.showData2} type="text" placeholder="Onblur show data"/>
			</div>
		)
	}
}
// 渲染页面
ReactDOM.render(<MyComponent />, document.getElementById("test1"))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 回调函数形式

class MyComponent extends React.Component {
	// 展示左侧输入框数据
	showData = () => {
		console.log(this.refs.input1.value);
	}
	// 展示右侧输入框数据
	showData2 = () => {
		console.log(this.refs.input2.value);
	}
	render() {
		return(
			<div>
				<input ref={c => this.input1 = c} type="text" placeholder="Push button show data"/>&nbsp;
				<button ref="button" onClick={this.showData}>push show right data</button>&nbsp;
				<input onBlur={this.showData2} ref={c => this.input3 = c} type="text" placeholder="Onblur show data"/>&nbsp;
			</div>
		)
	}
}
// 渲染页面
ReactDOM.render(<MyComponent />, document.getElementById("test1"))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

ref={(a) => {console.log(a)}}:输出的是当前节点,也是说ref中参数值得是当前节点。

# 回掉执行次数的问题

如果 ref 回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数null,然后第二次会传入参数DOM 元素。这是因为在每次清染时会创建一个新的函数实例,所以React 清空旧的ref 并且设置新的。

通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。

class MyComponent extends React.Component {
	state = {isHot:true}
	changeWeather = () => {
		const {isHot} = this.state
		this.setState({isHot:!isHot})
	}
	render() {
		return(
			<div>
				<h2>今天天气很{this.state.isHot ? 'Hot' : 'Cool '}</h2>
				<input ref={c => {this.input1 = c; console.log('@', c)}} type="text" placeholder="Push button show data"/>&nbsp;
				<button onClick={this.changeWeather}>Change Weather</button>
			</div>
		)
	}
}
// 渲染页面
ReactDOM.render(<MyComponent />, document.getElementById("test1"))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

image.png

写注释:{/* */}

通过调用类函数的方法改进,不过写成内联的也没关系。

class MyComponent extends React.Component {

	state = {isHot:true}
	showData = () => {
		const {input1} = this
		console.log(input1.value);
	}
	changeWeather = () => {
		const {isHot} = this.state
		this.setState({isHot:!isHot})
	}
	saveInput = (c) => { 
		this.input1 = c;
		console.log('@', c);
	}
	render() {
		return(
			<div>
				<h2>今天天气很{this.state.isHot ? 'Hot' : 'Cool '}</h2>
				{/*<input ref={c => {this.input1 = c; console.log('@', c)}} type="text" placeholder="Push button show data"/>&nbsp;*/}
				<input ref={this.saveInput} type="text"/>&nbsp;
				<button ref="button" onClick={this.showData}>push show right data</button>
				<button onClick={this.changeWeather}>Change Weather</button>
			</div>
		)
	}
}
// 渲染页面
ReactDOM.render(<MyComponent />, document.getElementById("test1"))

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
# createRef

React.createRef():调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是专人专用,里面只能存储一个。

class MyComponent extends React.Component {
	myRef = React.createRef()
	myRef2 = React.createRef()
	// 展示左侧输入框数据
	showData = () => {
		console.log(this.myRef.current.value);
	}
	showData2 = () => {
		console.log(this.myRef2.current.value);
	}
	render() {
		return(
			<div>
				<input ref={this.myRef} type="text" placeholder="Push button show data"/>&nbsp;
				<button onClick={this.showData}>push show right data</button>&nbsp;
				<input onBlur={this.showData2} ref={this.myRef2} type="text"/>
			</div>
		)
	}
}
// 渲染页面
ReactDOM.render(<MyComponent />, document.getElementById("test1"))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  1. 通过onXxx属性指定事件处理函数(注意大小写)
    1. React使用的是自定义(合成)时间,而不是使用原始DOM事件 —— 为了更好的兼容性
    2. React中的事件是通过事件委托方式处理的(委托给组件最外层的元素) —— 为了高效
  2. 通过event.target得到发生事件的DOM元素对象

避免过度使用ref,发生事件的元素正好是操作的元素,可以不使用ref。

showData2 = () => {
	console.log(event.target.value);
}

<input onBlur={this.showData2} type="text"/>
1
2
3
4
5

# 收集表单数据

# 非受控组件

页面中所有输入类DOM,现用现取。

class Login extends React.Component {
	handleSubmit = (event) => {
		event.preventDefault()  // 阻止表单提交
		const {username, password} = this
		console.log(username.value, password.value);
	}
	render(){
		return(
			<form action="www.baidu.com" onSubmit={this.handleSubmit}>
				用户名:<input ref={c => this.username = c}type="text" name="username" />
				密码:<input ref={c => this.password = c}type="text" name="password" />
				<button>登陆</button>
			</form>
		)
	}
}

ReactDOM.render(<Login/>, document.getElementById('test'))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 受控组件

页面内所有输入类的DOM,随着输入维护到状态state内,需要使用时直接从state内获取。(类似于vue的双向绑定)

现用现取就是非受控,随着输入维护状态就是受控。

class Login extends React.Component {
	state = {
		username:'',    // 用户名
		password:''     // 密码
	}
	handleSubmit = (event) => {
		event.preventDefault()  // 阻止表单提交
		const {username, password} = this.state
		alert
	}
	saveUsername = (evnet) => {
		this.setState({username:event.target.value})
	}
	savePassword = (event) => {
		this.setState({password:event.target.value})
	}
	render(){
		return(
			<form onSubmit={this.handleSubmit}>
				用户名:<input onChange={this.saveUsername} type="text" name="username" />
				密码:<input onChange={this.savePassword} type="password" name="password" />
				<button>登陆</button>
			</form>
		)
	}
}

ReactDOM.render(<Login/>, document.getElementById('test'))
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

# 高阶函数-函数柯里化

高阶函数:如果一个函数符合下面2个规范中的任何一个,那么该函数就是高阶函数。

  1. 若A函数,接收的参数是一个函数,那么A就可以称为高阶函数
  2. 若A函数,调用的返回值依然是一个函数,那么A就可以称为高阶函数。 常见的高阶函数:Promise、setTimeout、arr.map() 函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。

优化save函数,不能有一个变量就写一个save函数,希望写一个通用函数save所有变量。

class Login extends React.Component {
	state = {
		username:'',    // 用户名
		password:''     // 密码
	}
	handleSubmit = (event) => {
		event.preventDefault()  // 阻止表单提交
		const {username, password} = this.state
		alert
	}
	saveFormData = (dataType) => {
		// onChange将调用下面的回调函数
		return (event) => {
			this.setState({[dataType]:event.target.value})
			console.log(dataType, event.target.value);
		}
	}
	render(){
		return(
			<form onSubmit={this.handleSubmit}>
				{/* 必须把一个函数将给onChange作为回调 */} 
				用户名:<input onChange={this.saveFormData('username')} type="text" name="username" />
				密码:<input onChange={this.saveFormData('password')} type="password" name="password" />
				<button>登陆</button>
			</form>
		)
	}
}

ReactDOM.render(<Login/>, document.getElementById('test'))
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

如果不使用柯里化,可以这么写

saveFormData = (dataType, event) => {
	this.setState({[dataType]:event.target.value})
}

<input onChange={ event => this.saveFormData('username', event) } type="text" name="username" />
1
2
3
4
5

# 组件的生命周期

mount 挂载 unmount 卸载

生命周期回调函数 = 生命周期钩子函数 = 生命周期函数 = 生命周期钩子

理解:

  • 组件从创建到死亡会经历一些特定的阶段
  • React组件中包含一系列钩子函数,会在特定时刻调用
  • 在定义组件时,会在特定的生命周期回调函数中做特定的工作 常用的钩子函数:
  • render():初始化渲染或更新渲染调用
  • componentDidMout():开启监听,发送ajax请求
  • componentWillUnmount():做一些收尾工作,如:清理定时器

# 组件的生命周期(旧)

  • 初始化阶段:由ReactDOM.render()触发---初次渲染
    • constructor():构造器
    • componentWillMount():组件将要挂载钩子
    • render():渲染页面
    • componentDidMount():组件挂载完毕钩子,一般用于初始化,例如:开启定时器、发送网络请求、订阅消息
  • 更新阶段:由组件内部this.setState()或父组件render()触发
    • shouldComponentUpdate():组件是否应该被更新,阀门,返回true继续进行,返回false就停止了。改方法如果不写,永远返回true。
    • componentWillUpdate():组件将要更新钩子
    • render():渲染页面
    • componentDidUpdate():组件完成更新钩子
  • 卸载组件:由组件内部ReactDOM.unmountComponentAtNode()触发
    • componentWillUnmount():组件将要卸载钩子,常用于收尾工作,例如:关闭定时器、取消订阅消息
  • componentWillReceiveProps():组件将要接收新的 props 钩子,第一次传不算,后面更新的才算。
  • setState():更新 state 状态: shouldComponentUpdate -> componentWillUpdate -> render -> componentDidUpdate
  • forceUpdate():强制更新,不对状态作出修改,组件也能修改,绕过阀门,componentWillUpdate -> render -> componentDidUpdate

生命周期流程图(旧) image.png

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <!-- 准备好“容器” -->
    <div id="test1"></div> <br><br><br>
    <div id="test2"></div>

    <script type="text/javascript" src="../React-js/react.development.js"></script>
    <script type="text/javascript" src="../React-js/react-dom.development.js"></script>
    <script type="text/javascript" src="../React-js/babel.min.js"></script>
    <script type="text/javascript" src="../React-js/prop-types.js"></script>  
    <script type="text/babel">
        class Count extends React.Component {
            // 构造器
            constructor() {
                super()
                console.log("---constructor");
                this.state = {count:0}
            }
            // 组件将要挂载钩子
            componentWillMount() {
                console.log("---componentWillMount");
            }
            // 组件挂载完毕钩子
            componentDidMount() {
                console.log("---componentDidMount");
            }
            // 组件将要卸载钩子
            componentWillUnmount() {
                console.log("---componentWillUnmount");
            }
            // 控制组件更新阀门
            shouldComponentUpdate() {
                console.log("---shouldComponentUpdate");
                return true
            }
            // 组件将要更新钩子
            componentWillUpdate() {
                console.log("---componentWillUpdate");
            }
            // 组件更新完毕的钩子
            componentDidUpdate() {
                console.log("---componentDidUpdate");
            }

            add = () => {
                const {count} = this.state
                this.setState({count:count+1})
            }
            death = () => {  // 卸载组件
                ReactDOM.unmountComponentAtNode(document.getElementById('test1'))
            }
            force = () => {
                this.forceUpdate()
            }
            render() {  // 调用时机:初始化渲染、状态更新之后
                console.log("---render");
                const {count} = this.state
                return ( 
                    <div>
                        <h2>{count}</h2>
                        <h2>SUM: {count}</h2>
                        <button onClick={this.add}>Push +1</button>
                        <button onClick={this.death}>Unmount</button>
                        <button onClick={this.force}>Force Update</button>
                    </div>
                )
            }
        }
        
        class A extends React.Component {
            state = {carName:'Benz'}
            changeCar = () => {
                this.setState({carName: 'Auto'})
            }
            render() {
                return(
                    <div>
                        <div>A</div>
                        <button onClick={this.changeCar}>Change Car</button>
                        <B carName={this.state.carName}/>
                    </div>
                )
            }
        }
        class B extends React.Component {
            // 组件将要接收新的props钩子
            componentWillReceiveProps() {
                console.log("----componentWillReceiveProps");
            }
            render() {
                return(
                    <div>
                        <div>B,  {this.props.carName}</div>
                        <div>{this.props.carName}</div>
                    </div>
                    
                    
                )
            }
        }

        ReactDOM.render(<Count />, document.getElementById('test1'))
        ReactDOM.render(<A />, document.getElementById('test2'))
    </script>
</body>
</html>
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
103
104
105
106
107
108
109
110
111
112

# 组件的生命周期(新)

版本:17.0.1

  • 初始化阶段:由ReactDOM.render()触发---初次渲染
    • constructor()
    • getDerivedStateFromProps()
    • render()
    • componentDidMount()
  • 更新阶段:由组件内部this.setState()或父组件render()触发
    • getDerivedStateFromProps()
    • shouldComponentUpdate()
    • render()
    • getSnapshotBeforeUpdate()
    • componentDidUpdate()
  • 卸载组件:由组件内部ReactDOM.unmountComponentAtNode()触发
    • componentWillUnmount()

image.png

下面三个过时,需要加上UNSAFE,这些方法经常被滥用和误解,在异步渲染中,潜在的误用问题可能更大。

  • UNSAFE_componentWillMount
  • UNSAFE_componentWillUpdate
  • UNSAFE_componentWillReceiveProps 不过这三个方法很少使用。

新钩子:

  • getDerivedStateFromProps:不常用,state的值在任何时候都取决于props,但是难以维护,使用场景特别少。
  • getSnapshotBeforeUpdate: 不常用,在最近一次渲染输出(提交到DOM节点)之前调用。它使得组件能在发生更改之前从DOM中捕获一些信息(例如,滚动位置)。此生命周期的任何返回值将作为参数传递给 componentDidUpdate()
getSnapshotBeforeUpdate(){
	console.log("---getSnapshotBeforeUpdate");
	return "zqc"
}
// 组件更新完毕的钩子
componentDidUpdate(prevProps,prevState,snapshot) {
	console.log("---componentDidUpdate", prevProps,prevState,snapshot);
}
1
2
3
4
5
6
7
8

案例:每秒钟列表生成一条数据,但是显示框的高度不变,当数据超出显示框的范围时出现滚动条。当移动滚动条时,展示的数据固定位置,而不会跟随生成的数据而移动。

class NewsList extends React.Component{
	state = {newsArr:[]}
	componentDidMount() {
		setInterval(()=>{
			const {newsArr} = this.state
			const news = '新闻' +  (newsArr.length + 1)
			this.setState({newsArr:[news, ...newsArr]})
		},1000)
	}
	getSnapshotBeforeUpdate() {
		return this.refs.list.scrollHeight
	}
	componentDidUpdate(prevProps, preState, height) {
		this.refs.list.scrollTop += this.refs.list.scrollHeight - height
	}
	render(){
		return(
			<div className="list" ref="list">
				{
					this.state.newsArr.map((n, index) => {
						return <div className="news" key={index}>{n}</div>
					})
				}
			</div>
		)
	}
}

ReactDOM.render(<NewsList/>, document.getElementById('test'))
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
编辑 (opens new window)
#前端#React
上次更新: 2023/08/21, 20:05:58
React入门
DOM的Diffing算法

← React入门 DOM的Diffing算法→

最近更新
01
位运算
05-21
02
二叉树
05-12
03
Spring三级缓存解决循环依赖
03-25
更多文章>
Theme by Vdoing | Copyright © 2022-2024 Marvel
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式