react基础知识梳理

  1. 1. 案例引入
  2. 2. JSX语法
    1. 2.1. jsx语法和js语法创建虚拟dom:
    2. 2.2. 虚拟DOM的本质
    3. 2.3. JSX语法规则
      1. 2.3.1. 语法:
  3. 3. 模块化与组件化
    1. 3.1. 基本理解
      1. 3.1.1. 模块
      2. 3.1.2. 组件
      3. 3.1.3. 模块化
      4. 3.1.4. 组件化
    2. 3.2. 函数式组件
    3. 3.3. 类式组件
      1. 3.3.1. 类的基本知识
    4. 3.4. 类式组件案例
  4. 4. 起步预备知识
    1. 4.1. react中的事件绑定
    2. 4.2. 类中方法的this指向
  5. 5. 三大核心属性
    1. 5.1. state
      1. 5.1.1. 强烈注意
      2. 5.1.2. 简单案例
      3. 5.1.3. 关于this指向问题的解决
      4. 5.1.4. 简写方式
    2. 5.2. props
      1. 5.2.1. 理解
      2. 5.2.2. 对props进行限制
      3. 5.2.3. props的简写方式
      4. 5.2.4. 关于构造器
      5. 5.2.5. 函数式组件使用props
    3. 5.3. refs
      1. 5.3.1. 基本案例
      2. 5.3.2. 回调形式ref
      3. 5.3.3. 回调ref的调用次数
      4. 5.3.4. creatRef
    4. 5.4. 补充知识
      1. 5.4.1. react中的事件处理
      2. 5.4.2. 受控组件与非受控组件
      3. 5.4.3. 高阶函数&函数柯里化
    5. 5.5. 生命周期
      1. 5.5.1. 案例引入
      2. 5.5.2. 生命周期函数
        1. 5.5.2.1. 旧版本生命周期
        2. 5.5.2.2. 新生命周期
  6. 6. 虚拟DOM与DOM diff算法

案例引入

在原生html中引入react,使用script标签引入三个js文件:
react.development.js、react-dom.development.js、babel.min.js
在需要用到react的语法中,sciprt标签必须设置为babel,因为react中的jsx语法不能被js识别,需要babel转化为js。
html设置一个容器,使用react的render方法即可在原生html中使用react。

JSX语法

jsx语法和js语法创建虚拟dom:

1
2
3
4
// js
const VDOM = React.createElement('h1',{id:'title'},'hello,world')
// jsx
const VDOM = <h1 id='title'>hello,world</h1>

层级过深,使用js语法则会极其繁琐,而使用jsx更加清晰明了。

虚拟DOM的本质

console.log输出VDOM为一个一般的Object。可以得到:

  • 虚拟DOM本质是Object类型的对象(一般对象)

  • 虚拟DOM比较“轻”,真是DOM比较“重”,因为虚拟DOM是React内部再用,无需真是DOM上那么多的属性。

  • 虚拟DOM最终会被React转化为真实DOM,呈现在页面上。

JSX语法规则

简单模板

1
2
3
4
5
6
7
8
const id = "TeSt"
//1.创建虚拟DOM
const VDOM =(
<h2 id={id.toLowerCase()} className="title">
<span>hello,react</span>
</h2>
)
//2.渲染虚拟DOM到页面ReactDoM,render(VDOM,document.getElementById('test'))

语法:

  1. 定义虚拟DOM时,不需要写引号

  2. 标签中混入JS表达式时要用{}

  3. 样式的类名指定不要用class,要用className

  4. 内联样式,要用style={{key:value}}的形式去写

  5. 必须只有一个根标签

  6. 标签必须闭合

  7. 标签首字母

    1. 若小写字母开头,则将改标签转为html中同名元素,若html中无该标签对应的同名元素,则报错
    2. 若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错

注意:一定要区分js语句(代码)和js表达式:

  • 表达式:一个表达式回产生一个之,可以放在任何一个需要值的地方,如:a、a+b、demo(1)、arr.map()

  • 语句(代码),如:if(){}、for(){}、switch(case:xxx)

模块化与组件化

基本理解

模块

1.理解: 向外提供特定功能的js程序,一般就是一个js文件

2.为什么要拆成模块:随着业务逻辑增加,代码越来越多且复杂。

3.作用:复用js,简化js的编写,提高js 运行效率

组件

1.理解:用来实现局部功能效果的代码和资源的集合(html/css/js/image)等等

2.为什么:一个界面的功能更复杂

3.作用:复用编码,简化项目编码,提高运行效率

模块化

当应用的js都以模块来编写的,这个应用就是一个模块化的应用。

组件化

当应用是以多组件的方式实现,这个应用就是一个组件化的应用

函数式组件

创建函数式组件

1
2
3
4
5
6
7
// 1.创建函数式组件
function MyComponent(){
console.log(this)// 此处的this是undefined,因为babel编译后开启了严格模式
return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>
}
// 2.渲染组件到页面
ReactDoM.render(<MyComponent/>,document.getElementById('test'))

执行了ReactDoM.render(…….之后,发生了什么?

1.React解析组件标签,找到了MyComponent组件。

2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中。

类式组件

类的基本知识

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//创建一个Person类
class Person {
//构造器方法
constructor(name,age){//构造器中的this指向类的实例对象
this.name = name
this.age = age
}
// 一般方法
speak(){
console.log(`我叫${this.name},今年${this.age}岁`)
}

//创建一个student类,继承于Person类
class student extends Person {
constructor(name,age,grade){//构造器中的this指向类的实例对象
super(name,age) // 必须在开始就调用super
this.grade = grade
}
}

类的继承在js中是用原型链实现的,在此案例中,student实例的原型中存在Person里的方法,所以student实例是可以直接调用Person中的方法。

总结:

  1. 类中的构造器不是必须写的,要对实例进行一些初始化的操作,如添加指定属性时再写。

  2. 如果A类继承了B类,且A类中写了构造器,那么A类构造器中的super时必须要调用的。

  3. 类中嗦定义的方法,都是放在了类的原型对象上,供实例去使用。

类式组件案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 1.创建类式组件
class MyComponent extentds React.Component{
render(){
// render是放在哪里的?- 类的原型对象上,供实例使用
return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>
}
}
// 2.渲染组件到页面
ReactDoM.render(<MyComponent/>,document.getElementById('test'))
/*
执行了ReactDoM,render(<MyComponent/>.......之后,发生了什么?
1.React解析组件标签,找到了MyComponent组件
2.发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法。
3.将render返回的虚拟DOM转为真实DOM,随后呈现在页面中。
*/

起步预备知识

react中的事件绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//1.创建组件
class Weather extends React.Component{
constructor(props){
super(props)//初始化状态
this,state = {isHot:false}
}
render(){//读取状态
const {isHot}=this.state
return <h1 onclick={demo}>今天天气很{isHot ?'炎热':'凉爽'}</h1>
}
}
//2.渲染组件到页面
ReactDoM.render(<Weather/>,document.getElementById('test'))
function demo(){
console.log('标题被点击了')
};

类中方法的this指向

在原生js中,this指向window,如果开启严格模式,this为undefined。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Person {
constructor(name,age){
this.name = name
this.age = age
}
study(){
//study方法放在了哪里?-类的原型对象上,供实例使用
//通过Person实例调用study时,study中的this就是Person实例
console.log(this);
}
}
const pl= new Person('tom',18)
p1.study()//通过实例调用study方法,this指向person实例
const x= p1.study
x()// undefined,类中的方法会自动开启严格模式

解决:

在构造器中对直接调用而非通过实例调用的方法加上一句:this.xxx = this.xxx.bind(this)

这句话的本质就是:将原型上的方法,this改到自身实例上,然后赋给实例

三大核心属性

state

相当于vue中的data。

state 是组件对象最重要的属性,值是对象(可以包含多个key-value 的组合)

组件被称为”状态机”,通过更新组件的 state 来更新对应的页面显示(重新渲染组件)

强烈注意

  1. 组件中render 方法中的 this 为组件实例对象
  2. 组件自定义的方法中this为undefined,如何解决?
    1. 强制绑定 this: 通过函数对象的 bind()
    2. 箭头函数(赋值语句+箭头函数)
  3. 状态数据,不能直接修改或更新

简单案例

1
2
3
4
5
6
7
8
9
10
11
12
//1.创建组件
class Weather extends React.Component{{
constuctor(props){
super(props)
this.state = isHot:false
}
render(){
console.log(this);
return <h1>今天天气很{this.state.isHot?'炎热':'凉爽'}</h1>
}
//2.渲染组件到页面
ReactDoM.render(<Weather/>,document.getElementById('test'))

关于this指向问题的解决

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.创建组件
class Weather extends React.component{
//构造器调用几次?——一次
constructor(props){
super(props)//初始化状态
this.state ={isHot:false,wind:'微风'}
//解决changeWeather中this指向问题
this.changeWeather =this.changeWeather.bind(this)
}
render(){
//读取状态
const {isHot,wind} = this.state
return <h1 onclick={this.changeweather}>今天天气很{isHot ?'炎热':'凉爽'},{wind}</h1>
}
changeWeather(){
//changeWeather放在哪里?--Weather的原型对象上,供实例使用
//由于changeweather是作为onclick的回调,所以不是通过实例调用的,是直接调用
//类中的方法默认开启了局部的严格模式,所以changeweather中的this为undefined
//获取原来的isHot值
const isHot = this.state.isHot
//严重注意:状态必须通过setstate进行更新,且更新是一种合并,不是替换。
this.setstate({isHot:!isHot})
}

//2.渲染组件到页面
ReactDoM.render(<Weather/>,document.getElementById('test'))

简写方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//1.创建组件
class Weather extends React.Component{
//初始化状态
state ={isHot:false,wind:'微风'}
render(){
const {fisHot,wind} = this.state
return <h1 onclick={this.changeweather}>今天天气很{isHot ?'炎热':'凉爽'},{wind}</h1>
//自定义方法--要用赋值语句的形式
changeWeather = ()=>{
cosnt isHot = this.state.isHot
this.setstate({isHot:!isHot})
}
//2.渲染组件到页面
ReactDoM,render(<Weather/>,document.getElementById('test'))

props

理解

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

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//创建组件
class Person extends React.Component{render(){
console.log(this);
const {name,age,sex}= this.props
return(
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}

//渲染组件到页面
ReactDoM.render(<Person name="jerry" age="19" sex="男"/>,document.getElementById('test1')
ReactDoM,render(<Person name="tom" age="18" sex="女"/>,document.getElementById('test2'))
ReactDoM,render(<Person name="老刘" age="30" sex="女"/>,document.getElementById('test3'))

// 批量传递
constp={name:'老刘',age:18,sex:'女'}
ReactDoM.render(<Person {...p}/>,document.getelementById( 'test4'))

对props进行限制

React实现了限制props属性的工具类PropTypes,在15版本及以前,PropTypes内置在React核心库,可以直接React.PropTypes.xxx进行使用。但是从16版本开始,需要额外引入prop-type.js文件才能使用;引入后全局多了一个对象PropTypes,使用该规则规定props类型后,如果类型错误控制台会出现warning警告。

案例

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
//创建组件
class Person extends React.Component{render(){
render(){
console.log(this);
const {name,age,sex}= this.props
return(
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
}

// 限制属性类型
Person.propTypes = {
name:PropTypes.string.isRequired,
sex:PropTypes.string,
age:PropTypes.number,
speak: PropTypes.func
}
// 设置属性默认值
Person.defaultProps = {
sex:'不男不女',
age:18
}

constp={name:'老刘',age:18,sex:'女'}
ReactDoM.render(<Person {...p}/>,document.getelementById( 'test4'))

props的简写方式

props是只读的,无法修改,this.props.xxx = ‘xxx’此类代码会报错。

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
//创建组件
class Person extends React.Component{render(){
render(){
console.log(this);
const {name,age,sex}= this.props
return(
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
// 限制属性类型
static propTypes = {
name:PropTypes.string.isRequired,
sex:PropTypes.string,
age:PropTypes.number,
speak: PropTypes.func
}
// 设置属性默认值
static defaultProps = {
sex:'不男不女',
age:18
}
}


constp={name:'老刘',age:18,sex:'女'}
ReactDoM.render(<Person {...p}/>,document.getelementById( 'test4'))

关于构造器

constructor一般不用写,按文档,描述仅用于一i西安两种情况:

  • 通过this.state赋值对象来初始化内部state,如this.state={a:’b’}
  • 为事件处理函数绑定实例,如:this.changeWeather = this.changeWeather.bind(this)

构造器写了一定要调用super,且一定要传props。如果写了构造器又没有接收props,传递给super,则this.props无法获取到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
function Person(props){
const{name,age,sex}= props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</u1>
)
}
// 函数式组件只能写外边
Person.propTypes={
name:PropTypes.string.isRequired//限制name必传,且为字符串
sex:PropTypes.string,//限制sex为字符串
age:PropTypes.number,//限制age为数值
}
//指定默认标签属性值
Person.defaultProps= {
sex:男',//sex默认值为男
age:18 //age默认值为18
}
//渲染组件到页面
ReactDOM,render(<Person name="jerry"sex="女"
age={18}/>,document.getElementById('test1')

refs

用来获取真实DOM(虚拟DOM转为真是DOM之后真真实实的节点)。

基本案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//创建组件
class Demo extends React.Component{
//展示左侧输入框的数据
showData =()=>{
const {input1}= this.refs
alert(input1.value)
}
//展示右侧输入框的数据
showData2=()=>{
const {input2}= this.refs
alert(input2.value)
}
render(){return(
<div>
<input ref="input1" type="text" placeholder="点击按饥提示数据"/>&nbsp;
<button onclick={this,showData}>点我提示左侧的数据</button>&nbsp;
<input ref="input2" onBlur={this.showData2} type="text" placeholder="失去焦点提示数"/> </div>
}
}

//渲柒组件到页面
ReactDOM.render(<Demo/>,document.getElementById('test')

回调形式ref

字符串ref在官网上提到不建议使用,会出一些问题(没有明说,只给出了github的讨论页),建议使用回调形式ref。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//创建组件
class Demo extends React.Component{
//展示左侧输入框的数据
showData =()=>{
const {input1}= this
alert(input1.value)
}

render(){return(
<div>
<input ref={c => this.input1 = c} type="text" placeholder="点击按饥提示数据"/>&nbsp;
<button onclick={this.showData}>点我提示左侧的数据</button>
}
}

//渲柒组件到页面
ReactDOM.render(<Demo/>,document.getElementById('test')

回调ref的调用次数

官网描述:如果 ref 回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 nu11,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。

附:关于jsx里边写注释:{/* <input/> */}

解决上述问题:这种方式写ref能够解决每次页面更新都会重新设置ref的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//创建组件
class Demo extends React.Component{
state = {isHot:false}
saveInput =()=>{
const {input1}= this
alert(input1.value)
}
changeWeather = (c)=>{
this.ipnut1 = c
console.log('@',c)
}

render(){
const {isHot} = this.state
return(
<div>
<input ref={this.myRef} type="text"placeholder="点击按饥提示数据"/>&nbsp; <button onclick={this.showData}>点我提示左侧的数据</button>&nbsp;
<input onBlur={this.showData2} ref={this.myRef2} type="text" placeholder="点击按钏提示数据"/>&nbsp;
</div>)
}
}

//渲柒组件到页面
ReactDOM.render(<Demo/>,document.getElementById('test')

creatRef

React.createRef调用后可以返回一个容器,该容器可以存储被ref标识的节点。

注意:该容器时专人专用的,一个容器只能装一个ref,绑定多个ref,后绑定的会覆盖之前的。

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 Demo extends React.Component{
myRef = React.createRef()
myRef2 = React.createRef()
//展示左侧输入框的数据
showData=()=>{
alert(this.myRef.current.value);
}
//展示右侧输入框的数据
showData2=()=>{
alert(this.myRef2.current.value);
}

render(){
return(
<div>
<input ref={this.myRef} type="text" placeholder="点击按饥提示数"/>&nbsp;
<button onclick={this.showData}>点我提示左侧的数据</button>&nbsp;
<input onBlur={this.showData2} ref={this.myRef2} type="text" placeholder="失去焦点提示数据"/>&nbsp;
</div>
)
}
}

//渲柒组件到页面
ReactDOM.render(<Demo/>,document.getElementById('test')

补充知识

react中的事件处理

react中的事件均是经过封装,并没有直接使用原生的事件。

  1. 通过onXxx(驼峰)属性指定事件处理函数(注意大小写)
    1. React使用的是自定义(合成)事件,而不是使用的原生DOM事件——是为了更好的兼容性
    2. React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)——为了高效
  2. 通过event.target得到发生事件的DOM元素对象——不要过度使用ref

受控组件与非受控组件

非受控组件:现用现取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//创建组件
class Login extends React.Component{
handlesubmit=(event)=>{
event.preventDefault()//阳止表单提交
const {username,password}=this
alert(`你输入的用户名是:${username.value},你输入的密码是:${password.value}`)
}
render(){
return (
<form onSubmit=fthis.handleSubmit}>
川户名:<input ref={c =>this.username = c} type="text" name="username"/>
密码:<input ref={c =>this.password = c} type="password" name="password"/>
<button>登录</button>
</form>)
}
}
//渲柒组件
ReactDoM.render(<Login/>,document.getElementById('test'))

受控组件:维护状态(vue中的双向绑定)

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
//创建组件
class Login extends React.Component{
//初始化状态
state ={
username:''//用户名
password:'', //密码
}
//保存用户名到状态中
saveUsername =(event)=>{
this.setstate({username:event.target.value})
}
//保存密码到状态中
savePassword =(event)=>{
this.setstate({password:event.target.value})
}
//表单提交的回调
handleSubmit =(event)=>{
event.preventDefault()//阳止表单提交
const {username,password}=this.state
alert(`你输入的用户名是:${username},你输入的密码是:${password}`)
}
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'))

高阶函数&函数柯里化

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

  1. 若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。
  2. 若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。

常见的高阶函数有哪些:Promise、setTimeout、arr.map()等

函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。

对上一个案例进行优化:

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
//创建组件
class Login extends React.Component{
//初始化状态
state ={
username:''//用户名
password:'', //密码
}
//保存用户名到状态中(柯里化)
saveFormData=(dataType)=>{
return(event)=>{
this.setstate({[dataType]:event.target.value})
}
}
//表单提交的回调
handleSubmit =(event)=>{
event.preventDefault()//阳止表单提交
const {username,password}=this.state
alert(`你输入的用户名是:${username},你输入的密码是:${password}`)
}
render(){
return(
<form onsubmit={this.handlesubmit}>
用户名: <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. React组件中包含一系列钩子函数(生命周期回调函数),会在特定的时刻调用。
  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
class Life extends React.Component{
state ={opacity:1}
death=()=>{
//卸载组件
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
//组件挂完毕
componentDidMount(){
this.timer = setInterval(()=>{
let {opacity} = this.state
opacity -= 0.1
if(opacity <= 0) opacity=1
this.setState({opacity})
},200)
}
componentWillUnmount(){
clearInterval(this.timer) // 清除定时器
}

//初始化渲染、状态更新之后
render(){
console.log('render');
return(
<div>
<h2 style={{opacity:this.state.opacity}}>React学不会怎么办?</h2>
<button onclick=fthis.death}>不活了</button>
</div>
)
}
}
//渲染组件
ReactDoM.render(<Life/>,document.getElementById('test'))

生命周期函数

旧版本生命周期

挂载时:constructor -> componentWillMount -> render -> componentDidMount -> componentWillUnmount

父组件render:componentWillReceiveProps ->shouldComponentUpdate(setState) -> componentWillUpdate(forceUpdate) -> render ->componentDidUpdate -> componentWillUnmount

componentWillReceiveProps(组件将要收到props)存在一个坑,第一次调用时不算,第一次调用时不会执行,一般是手动传入props操作调用。

shouldComponentUpdate,控制组件更新的阀门,必须返回一个布尔值,为false则不会更新组件。

总结:

  1. 初始化阶段:有ReactDOM.render()触发—初次渲染
    1. constructor()
    2. componentWillMount()
    3. render()
    4. componentDidMount():常用,一般在这个钩子做一些初始化的事:如请求、订阅消息、开启定时器等
  2. 更新阶段:由组件内部this.setState()或父组件重新render触发
    1. shouldComponentUpdate()
    2. componentWillUpdate()
    3. render()
    4. componentDidUpdate (preProps,preState)
  3. 卸载组件:由ReactDOM.unmounComponentAtNode()触发
    1. componentWillUnmount():一般在这个钩子做一些收尾的事,如关闭定时器、取消订阅等。

新生命周期

在新版本中,这三个钩子要加上UNSAFE_前缀:componentWillMount、componentWillUpdate、render:componentWillReceiveProps 。不加前缀调用会报弃用警告。

getDerivedStateFromProps(props,state):必须加上static使之为静态方法;必须有返回值。使用场景:state的值在任何时候都取决于props。

getSnapshotBeforeUpdate(prevProps,prevState):必须返回一个null或者snapshot value。在最近一次渲染输出(提交到DOM系欸但)之前调用,使得组件能在发生更改之前从DOM中捕获一些信息。

总结:

  1. 初始化阶段:有ReactDOM.render()触发—初次渲染
    1. constructor()
    2. getDerivedStateFromProps()
    3. render()
    4. componentDidMount():常用,一般在这个钩子做一些初始化的事:如请求、订阅消息、开启定时器等
  2. 更新阶段:由组件内部this.setState()或父组件重新render触发
    1. getDerivedStateFromProps()
    2. shouldComponentUpdate()
    3. render()
    4. getSnapshotBeforeUpdate()
    5. componentDidUpdate (preProps,preState)
  3. 卸载组件:由ReactDOM.unmounComponentAtNode()触发
    1. componentWillUnmount():一般在这个钩子做一些收尾的事,如关闭定时器、取消订阅等。

虚拟DOM与DOM diff算法

虚拟DOM中key的作用:

  1. 简单的说:key是虚拟DOM对象的标识,在更新显示时key起着极其重要的作用。
  2. 详细的说:当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
    1. 旧虚拟DOM中找到了与新虚拟DOM相同的key:
      1. 若虚拟D0M中内容没变,直接使用之前的真实DOM
      2. 若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
    2. 旧虚拟DOM中未找到与新虚拟DOM相同的key
      1. 根据数据创建新的真实DOM,随后渲染到到页面

用index作为key可能会引发的问题:

  1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作:会产生没有必要的真实DOM更新==>界面效果没问题,但效率低。
  2. 如果结构中还包含输入类的DOM:会产生错误DOM更新 ==>界面有问题。
  3. 注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。

开发中如何选择key?

  1. 最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值。
  2. 如果确定只是简单的展示数据,用index也是可以的。