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里面取消请求