React Class 组件详解

Rinsann 2021年10月15日 413次浏览

React Class 组件详解

两种方式创建Class组件

// ES5方式(过时)
import React from 'react'
const A = React.createClass({
	render(){
		return (
			<div>hi</div>
		)
	}
})
export default A
// 由于ES5不支持Class 才会有这种方式
// ES6方式
import React from 'react';
class B = extends React.Component{
	constructor(props){
		super(props);
	}
	render(){
		return (
			<div>hi</div>
		)
	}
}
export default B;

ES6的class方式好,一般只用这种方式创建类组件,浏览器不支持ES6的情况使用webpack+babel将ES6翻译成ES5。

Props(外部数据)

传入props给B组件

class Parent extends React.component{
	constructor(props){
		super(props)
		this.state = {name:'frank'}
	}
	onClick = ()=>{}
    render(){
    	return <B name={this.state.name} onClick={this.onClick}>hi</B>
    }
}
// 外部数据被包装成为一个对象 {name:'frank', onClick: ...,children:'hi'}
// 此处的 onClick 是一个回调

初始化

class B extends React.Component{
	constructor(props){
		super(props)
	}
	render(){}
}

要么不初始化,即不写constructor,初始化必须写全套(不写super报错)

效果:这么做了之后,this.props就是外部数据对象的地址了。

读取

class B extends React.Component{
	constructor(props){
		super(props)
	}
	render(){
		return <div onClick={this.props.onClick}>
		{this.props.name}
		<div>{this.props.children}</div>
	}
}

通过 this.props.xxx 读取。

props可以读取,但是不能直接对props进行改写。

Props的作用

接收外部数据

  • 只能读不能写
  • 外部数据由父组件传递

接收外部函数

  • 在恰当的时机,调用该函数
  • 该函数一般是父组件的函数

State & setState(内部数据)

初始化

class B extends React.Component{
	constructor(props){
		super(props);
		this.state = {
			user: {name:'frank',age:18}
		}
	}
	render(){/*...*/}
}

读写 State

  • 读使用:this.state.xxx.yyy.zzz
  • 写使用:this.setState(newState,fn)
  • 注意setState不会立即改变this.state,会在当前代码运行完成后,再去更新this.state,从而触发UI更新
  • this.setState((state,props)=>newState,fn) 这种方式的state更容易理解,fn会在写入成功后执行

写时会 shallow merge

  • setState 会自动将新的state与旧的state进行一级合并

修改this.state的属性值

  • this.state.n += 1
  • this.setState(this.state),不推荐,但是React没有阻止这么写。

React生命周期

// 类似如下代码
let div = document.createElement('div')//这是div的create/construct过程
div.textContent = 'hi'  //这是初始化state
document.body.appendChild(div) //这是div的mount过程
div.textContent = 'hi2' //这是div的update过程
div.remove() //这是div的unmount过程

同理

  • React组件也有这样的过程,我们称为生命周期

React函数列表

  • constructor() --- 在这里初始化state
  • static getDerivedStateFromProps()
  • shouldComponentUpdate() --- return false 阻止更新
  • render() --- 创建虚拟DOM
  • getSnapshotBeforeUpdate()
  • componentDidMount() --- 组件已出现在页面
  • componentDidUpdate() --- 组件已更新
  • componentWillUnmount() --- 组件将死
  • static getDerivedStateFromError()
  • componentDidCatch()

最后需 return true

constructor

用途

  • 初始化props
  • 初始化 state,但此时不能调用setState
  • 用来写bind this
constructor(){
	/*..略..*/
	this.onClick = this. onClick.bind(this)
}
// 可以用新语法代替
onClick = () => {}
constructor(){/*..略..*/}
  • 只初始化props可不写,初始化this时才必须写

生命周期之shouldComponentUpdate

用途

  • 返回true表示不阻止UI更新
  • 返回false表示阻止UI更新
import React from 'react'
// class App extends React.Component{
class App extends React.PureComponent{
	constructor(props){
		super(props)
        this.state = {
            n:1
        }
	}
    onClick = ()=>{
        this.setState(state=>({
            n:state.n+1
        }))
        this.setState(state=>({
            n:state.n-1
        }))
    }
    /* shouldComponentUpdate(newProps,newState){
        if(newState.n === this.state.n){
            return false
        }else{
            return true
        }
    } */
    
    render(){
        return(
        	<div>App
            	<div>
            		{{this.state.n}} 
					<button onClick={this.onclick}>+1</button>
            	</div>
            </div>
        )
    }
}
export default App;

新的对象和旧的对象地址不同 --> React认为数据变了 --> 重新执行render --> 得到新的虚拟DOM --> 拿新的与上次的虚拟DOM进行对比 --> 对比发现都是1,所有不更新UI,重新执行render这一步就多余了,我们要告诉react数据没有变 shouldComponentUpdate 返回 false

面试常问

  • 问题:shouldComponentUpdate有什么用?
  • 答:它允许我们手动判断是否要进行组件更新,我们可以根据用于场景灵活的设置返回值,以避免不必要的更新

思考1:其实可以将newState和this.state的每个属性都对比一下。如果全部相等,就不更新,如果有一个不等,就更新。

思考2:为什么React不内置此功能,react确实内置了 一个React.PureComponent,可以替代React.Component,PureComponent会在render之前对比新state和旧state的每一个key,以及新props和旧props的每一个key。如果所有key的值全部一样,就不会render;如果有任何一个key的值不同,就会render。

思考3:为什么要用新的对象?为什么不直接在this.state身上改呢。

生命周期之render

用途

  • 展示视图 return(

    ...
    ) 虚拟DOM

  • 只能有一个根元素,如果有两个根元素,就要用<React.Fragment>包起来

  • <React.Fragment> 可以缩写成 <></>

技巧

  • render 里面可以写 if...else
  • render 里面可以写 ?:表达式
  • render 里面不能直接写for循环,需要用数组
  • render 里面可以写array.map(循环)里面需加key

生命周期之 componentDidMount

用途

  • 在元素插入页面后执行代码,这些代码依赖DOM
  • 比如想获取div的高度,就最好在这里写 (Refs 方式)
class App extends React.PureComponent{
	divRef = undefined;
	constructor(props){
		super(props)
		this.state = {
			n:1,
			width:undefined
		}
		this.divRef = React.createRef();
	}
    componentDidMount(){
        const  div = this.divRef.current;
        const {width} = div.getBoundingClientRect();
        this.setState({width})
    }
    onClick = () = }{
    	tihs.srtState(state=>({
    		n: state.n + 1
    	}))
    }
    render({
        return <div ref={this.divRef}>Hello World,{this.state.width}px</div>
    })
}
export default App;
  • 此处可以发起加载数据的AJAX请求(官方推荐)
  • 首次渲染会执行此钩子

生命周期之componentDidUpdate()

用途

  • 在视图更新后执行代码
  • 此处也可以发起AJAX请求,用于更新数据
  • 首次渲染不会执行此钩子
  • 在此处setState可能会引起无限循环,除非放在if里
  • 若shouldComponentUpdate返回false ,则不会触发此钩子

生命周期之componentWillUNmount

用途

  • 组件将要被移出页面如何被销毁时执行代码
  • unmount 过的组件不会再次mount

举例

  • 如果在componentDidMount里面监听了window scroll
  • 那么就要在componentWillUnmount里面取消监听
  • 如果在componentDidMount里面创建了Timer
  • 那么基于在componentWillUnmount里面取消Timer
  • 如果在componentDidMount里面创建了AJAX请求,那么就要在componentWillUnmount里面取消请求

分阶段看钩子执行顺序

image20211015231846921.png