《React类组件》

338 阅读7分钟

一. 创建类组件

import React from 'react';
class B extends React.Component {
    constructor(props) {
        super(props);
    }
    render() {
        return (
            <div>hi</div>
        )
    }
}
export default B;

二. 外部数据props

给B组件传props

一个父组件给子组件传props,通常是把自己的内部数据传进去

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>
    }
}

传进去的props会被包装成一个对象,{name:'frank',onClick:...,children:'hi'}

子组件接收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>
        </div>
    }
}

注意:constructor初始化函数里边,如果还需要写其他东西,比如this.state等,那就必须把这三行写全,必须把props参数写在那。

还有一种情况是,如果没有其他代码需要写在constructor里,那么初始化props可以不写,直接使用this.props.xxx

总结

props的作用:

接受外部数据,而且只能读,不能写。一般外部数据由父组件传递。

还可以接受外部函数,一般也是父组件的函数,会在恰当的时机,调用该函数。

三. 生命周期钩子

1. constructor

初始化props,初始化state.如果只需要初始化props,可以不写constructor

React组件在创建的时候,会调用这个函数

2. shouldComponentUpdate

用途:在里边做一些判断,返回false,就是告诉React阻止UI更新。返回true,就是不阻止更新,可以更新。

示例:

class App extends React.Component {
    constructor(props){
        super(props)
        this.state={
            n:1
        }
    }
    onClick=()=>{
        this.setState(state=>({n:state.n+1}))
        this.setState(state=>({n:state.n-1}))
    }
    render(){
        console.log('render被调用了');
        return (
            <div className="App">
                App n:{this.state.n}
                <button onClick={this.onClick}>+1-1</button>
            </div>
        );
    }
}

假设有这样一种情况:点击按钮后,对n做了一系列操作,最后发现n的值没变,还是1。(用先+1,再-1模拟)

点击按钮,发现页面上的n没变,还是1.这是没问题的。因为n的值没变。

但是,log打印了两次。为什么n的值没变,还是调用了render呢?

虽然n的值前后都是1,但是{n:1}{n:1}是两个不同的对象,地址不同。所以React认为数据变了,就去调用了render函数。然后生成了新的虚拟DOM。对比前后两个虚拟DOM,发现n的值都是1,没有不同的地方,就没有更新UI。但是render确是被调用了

所以其实从调用render往后,生成新DOM,对比,发现没有不同,停止更新,这几步都是多余的。是数据的地址变了,让React误以为数据变了,但是最后又发现没变,所以中间的那些步骤就多余了。

能不能在n的值没变的情况下,就不让render被调用了呢?shouldComponentUpdate 钩子可以做到

shouldComponentUpdate(nextProps, nextState, nextContext) {
        if(nextState.n===this.state.n){
            return false
        }else{
            return true
        }
}

直接在class下边定义。默认传进去三个参数,按顺序:新的props,新的state。

在这个函数里判断:如果新的n和当前的n一样,就返回false,不用更新了。(手动告诉React,数据没变,不要去render了)这时再点击按钮,就没有log了,说明render没有被调用了。

面试问:shouldComponentUpdate有什么用? 它让我们可以手动判断是否要进行组件更新。我们可以根据场景灵活地设置返回值,以避免不必要的更新。

React.PureComponent代替React.Component

上边的shoule钩子还要自己手动判断,有点麻烦,于是React内置了这个功能 ,叫做React.PureComponent。在class extends后边继承它,就相当于should的功能 。PureComponent 会在 render 之前对比新 state 和旧 state 的每一个 key,以及新 props 和旧 props 的每一个 key。 如果所有 key 的值全都一样,就不会 render;如果有任何一个 key 的值不同,就会 render。

class App extends React.PureComponent {}

3. render

创建虚拟DOM,展示视图 return <div></div>

return 的元素,只能有一个根元素。如果有两个根元素,要么用一个div包起来,要么用<React.Fragment></React.Fragment>包起来。

他们的区别是:<div>会被渲染到页面中,而<React.Fragment></React.Fragment>不会被渲染。

render(){
        return (
            <React.Fragment className="App">
                App n:{this.state.n}
                <button onClick={this.onClick}>+1-1</button>
                <div>hi</div>
            </React.Fragment>
        );
}

把这些渲染到root里,发现页面的html是这样的:

外边的root下,是没有React.Fragment这个标签的。

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

render里可以写任何JS代码,if...else...? :&&

但是render里不能直接写for循环,因为render是需要return 的,如果在for循环里写return ,就只能循环第一次,就直接退出循环了。

可以借助数组,循环的时候,把结果push进数组,循环结束后,return 数组。

或者array.map()

class App extends React.PureComponent {
    constructor(props){
        super(props)
        this.state={
            n:1,
            arr:[1,2,3]
        }
    }
    render(){
        return (
            this.state.arr.map(item=><div key={item}>{item}</div>)
        );
    }
}

所有循环都需要一个不重复的key

4. componentDidMount

componentDidMount()会在组件挂载后(插入 DOM 树中,出现在页面)立即调用。依赖于 DOM 节点的初始化应该放在这里。如需通过网络请求获取数据,此处是实例化请求的好地方(发起加载数据的AJAX请求)。当然,首次渲染会执行这个钩子

应用场景:在页面渲染一个div,同时展示这个div的宽度。

解析:要展示一个div的宽度,前提是它已经出现在页面里了,才能去获取。如果在constructor里获取,肯定是拿不到的,因为constructor是在元素出现在内存后被调用的,此时还没被挂载到页面里。所以需要使用该钩子,在组件挂载后,去获取div的宽度

class App extends React.PureComponent {
    constructor(props){
        super(props)
        this.state={
            n:1,
            width:undefined
        }
    }
    componentDidMount(){
        const div=document.querySelector('#xxx')
        const width=div.getBoundingClientRect().width
        this.setState({width:width})
    }
    render(){
        return (
            <div id={'xxx'}>
                hello world {this.state.width}px
            </div>
        );
    }
}

访问页面里的DOM节点的第二种方式-Refs

上边我们通过id访问DOM节点。React提供了一种访问页面里的DOM节点的方式-Refs

class App extends React.PureComponent {
    divRef=undefined                // 可以先声明一下,表示我之后要动态创建Ref
    constructor(props){
        super(props)
        this.state={
            width:undefined
        }
        this.divRef=React.createRef()   // 1. 创建Refs
    }
    componentDidMount(){
        const div=this.divRef.current   // 3. current属性访问节点
        const width=div.getBoundingClientRect().width
        this.setState({width:width})
    }
    render(){
        return (
            <div ref={this.divRef}>     // 2. 通过ref属性,关联到元素上
                hello world {this.state.width}px
            </div>
        );
    }
}

创建的时候,divRef是绑定到this上,也就是实例上的,而不是state上。

5. componentDidUpdate(prevProps, prevState, snapshot)

会在UI更新后会被立即调用。首次渲染不会执行此方法(因为没更新)。

如果要发起AJAX请求,除了可以在componentDidAmount里请求,还可以在这个钩子里也可以发起AJAX请求,不过是用于更新数据的。官方文档:

如果你对更新前后的 props 进行了比较,也可以选择在此处进行网络请求。(例如,当 props 未发生变化时,则不会执行网络请求)。

componentDidUpdate(prevProps) {
  // 典型用法(不要忘记比较 props):
  if (this.props.userID !== prevProps.userID) {
    this.fetchData(this.props.userID);
  }
}

就是说,假如用户的id变了,那就需要发起一个网络请求获取新的用户信息,这时网络请求就要在componentDidUpdate里做,因为视图更新了才知道用户信息变了。

6. componentWillUnmount()

组件将要被移出页面(取消挂载)然后销毁(从内存消失)时执行。在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等。

总结

分阶段看一下这些钩子会在什么时候执行,以及他们的执行顺序:

constructor() 初始化state,创建元素

shouldComponentUpdate() return false阻止更新

render() 会创建虚拟DOM

componentDidMount() 组件已经出现在页面

componentDidUpdate() 组件已经更新

componentWillUnmount() 组件将要死掉之前