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里面取消请求
  • 原则:谁污染谁治理
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,653评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,321评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,833评论 0 324
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,472评论 1 266
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,306评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,274评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,658评论 3 385
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,335评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,638评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,697评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,454评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,311评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,699评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,986评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,254评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,647评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,847评论 2 335

推荐阅读更多精彩内容