React中的class类组件详解

1. 两种创建class组件的方式

  1. ES5写法(已经过时了)
import React from 'react'
const A = React.createClass({
    render(){
        return (
            <div>hi</div>
        )
    }
})
export default A
  1. ES6 最新的写法
import React from 'react'
class B extends React.Component {
    constructor(props){
        super(props);
    }
    render(){
        return (
            <div>hi</div>
        )
    }
}
export default B

2. props 外部数据

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是一个回调

B组件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>
        )
    }
}
// 通过 this.props.xxx 获取外部数据
// 要么不初始化,即不写constructor
// 要么初始化,且必须写全套(不写super直接报错)

这样,this.props 就是外部数据对象的地址了

不推荐子组件对外部 props 进行修改!

如何写 props 值

  • 原则上应该由数据的主人进行更改

3. 组件的相关钩子

  1. componentWillReceiveProps钩子

    • 当组件接受新的props时,会触发此钩子
    • 该钩子已经被弃用
    • 更名为UNSAFE_componentWillReceiveProps
    • 总而言之,不要使用这个钩子
    componentWillReceiveProps(newProps){    // 参数为新的props
            console.log('旧的 props 为')
            console.log(this.props)
            console.log('新的 props 为')
            console.log(newProps)
            // 注意 console 的延迟计算 bug
        }
    

4. state & setState 内部数据

class B extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            user: {name:'frank',age:18}
        }
    }
    render(){/* ... */}
}
  1. 读数据 this.state
this.state.xxx.yyy.zzz
  1. 写数据 this.setState(???,fn)
this.setState(newState,fn)
// 注意setState不会立刻改变this.state,会在当前代码运行完后,再去更新this.state,从而触发U更新
//  fn函数会在写入成功时执行
this.setState(()=>newState,fn)
// 这种方式的state反而更易于理解
//  fn函数会在写入成功时执行
类组件setState 会自动将新state与旧state进行第一层级数据合并

5. 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组件也有这些过程,我们称之为生命周期

生命周期函数列表概览
  • constructor() 创建之后 可以来初始化 state
  • static getDerivedStateFromProps() 不常用
  • shouldComponentUpdate() 该更新组件吗? 返回布尔值 return false 阻止更新
  • render() 渲染了 创建虚拟DOM
  • getSnapshotBeforeUpdate() 不常用
  • componentDidMount() 组件已经挂载后
  • componentDidUpdate() 组件已经更新后
  • componentWillUnmount() 组件将要卸载时
  • static getDerivedStateFromError() 不常用
  • componentDidCatch() 不常用
  1. constructor() 创建之后

初始化 props ,state,但此时不能调用 setState ,用来写 bind this

constructor(){
    /* 其他代码略写 */
    this.onClick = this.onClick.bind(this)
}
// 可以用虚新语法代替
onClik = () => {}
constructor(){ /* 其他代码略写 */ }
  1. shouldComponentUpdate() 是否更新UI

返回 true 表示不阻止 UI 更新,返回 false 表示阻止 UI 更新

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

    shouldComponentUpdate(nextProps, nextState) {    // 判断 n值变化与否 是否该更新UI
        if (nextState.n === this.state.n) {
            return false;
        } else {
            return true;
        }
    }

    render() {
        console.log('渲染了')
        return (
            <div className='App'>
                <div>
                    {this.state.n}
                    <button onClick={this.onClick}>+1</button>
                </div>
            </div>
        )
    }
}

面试常问:shouldComponentUpdate有什么用?

答:它允许我们手动判断是否要进行组件更新,我们可以根据应用场景灵活地设置返回值,以避免不必要的更新

这个例子理解后我们会发现其实可以将newState和 this.state的每个属性都对比一下如果全都相等,就不更新如果有一个不等,就更新,确实,React也内置了这个功能

React.PureComponent (纯组件 ) 可以代替 React.Component
class App extends React.PureComponent {    // 只需改这一行便有了 上面的功能

PureComponent 会在 render 之前对比新 state 和旧 state 的每一个 key,以及新 props 和旧 props 的每一个 key。
如果所有 key 的值全都一样,就不会 render;如果有任何一个 key 的值不同,就会 render。

  1. render 渲染组件

用来展示视图

return (<div> ... </div>)
  • 只能有一个根元素
  • 如果有两个根元素,就要用<React.Fragment>包起
  • <React.Fragment/>可以缩写成<></>

技巧:

  • render 里面可以写if...else
  • render 里面可以写?:表达式
  • render 里面不能直接写for 循环,需要用数组
  • render 里面可以写array.map(循环)
render() {
    return this.state.array.map(n => <span key={n}>{n}</span>)
}
//  必须要有key
  1. componentDidMount() 组件已经挂载了
  • 在元素插入页面后执行代码,这些代码依赖DOM
  • 比如你想获取div的高度,就最好在这里写
  • 此处可以发起加载数据的AJAX请求(官方推荐)
  • 首次渲染会执行此钩子
componentDidMount(){
    const div = document.getElementById('xxx')
    const {width} = div.getBoundingClientRect()  // 获取宽度
    this.setState({width})
}    // 挂载后获取元素的宽度

可以使用使用 Creating refs 代替 document.getElementById

class App extends React.PureComponent {
    constructor(props) {
        super(props);
        this.state = {
            n: 1
        }
        this.divRef = React.createRef()    //  声明引用
    }

    onClick = () => {
        this.setState((state) => ({n: state.n + 1}))
        this.setState((state) => ({n: state.n - 1}))
    }

    componentDidMount() {
        const div = this.divRef.current    // 使用引用
        console.log(div)
    }

    render() {
        console.log('渲染了')
        return (
            <div className='App' ref={this.divRef}>   // 添加引用属性
                <div>
                    {this.state.n}
                    <button onClick={this.onClick}>+1</button>
                </div>
            </div>
        )
    }
}
  1. componentDisUpdate() 组件更新之后
  • 在视图更新后执行代码
  • 此处也可以发起AJAX请求,用于更新数据(看文档)
  • 首次渲染不会执行此钩子
  • 在此处setState可能会引起无限循环,除非放在if 里
  • 若shouldComponentUpdate返回false,则不触发此钩子
  1. componentWillUnmount 组件将要卸载时
  • 组件将要被移出页面然后被销毁时执行代码
  • unmount过的组件不会再次mount

举例子:

  • 如果你在c..DidMount里面监听了window scroll
  • 那么你就要在c..WillUnmount 里面取消监听
  • 如果你在c..DidMount里面创建了Timer
  • 那么你就要在c..WillUnmount 里面取消Timer
  • 如果你在c..DidMount里面创建了AJAX请求
  • 那么你就要在c..WillUnmount里面取消请求
  • 原则:谁污染谁治理
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容