React类组件详解

两种方式创建Class组件

ES5方式(过时)

import React from 'react'

const A = React.createClass({
  render(){
    return (
      <div>hi</div>
    )
  }
})
export default A

// 由于 ES5 不支持 class,才会有这种方式

ES6 方式

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

  render() {
    return (
      <div>hi</div>
    )
  }
}
export default B
// extends,constructor,super 强行记忆

以后只用class方式创建类组件
webpack+babel将ES6翻译成ES5

Props-外部数据

一般外部数据都是来自父元素的属性
传入props给B组件

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

初始化

class B extends React.Component {
  constructor(props){
    super(props);
  }
  render(){}
}
  • 要么不初始化,即不写constructor
  • 要么初始化,必须写全,不写super直接报错
  • 这么做了之后,this.props就是外部数据对象的地址了
读取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 读取
不准写Props
  • 给props的值(一个地址)

  • this.props = {/另一个对象/}

  • 不要写这样的代码,没有意义

  • 理由: 既然是外部数据,就应该由外部更新

  • 该props的属性

  • this.props.xxx = 'hi'

  • 不要写这样的代码,没有意义

  • 理由: 既然是外部数据,就不应该从内部改值

  • 原则

  • 应该由数据的主人对数据进行更改

相关钩子

component WillReceiveProps钩子
当组件接受新的props时,会触发此钩子
钩子就是特殊函数,属于行业黑话
例子

import React from "react";
import ReactDOM from "react-dom";


class App extends React.Component {


  constructor(props) {
    super(props);
    this.state = {x: 1}
  }

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

  render(){
    return <div className="App">
      App <button onClick={this.onClick}>+1</button>
      <B name={this.state.x}></B>
    </div>
  }
}

class B extends React.Component {
  componentWillReceiveProps(newProps, nextContext) {
    console.log('旧的 props', this.props)
    console.log('props变化了')
    console.log('新的props')
    console.log(newProps)
  }
  render(){
    return <div>
      {this.props.name}
    </div>
  }
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
  • 该钩子已经被弃用,总之,不要使用这个钩子
props的作用
  • 接受外部-数据
  • 只能读不能写
  • 外部数据由父组件传递
  • 接受外部-函数
  • 在恰当的时机,调用该函数
  • 该函数一般是父组件的函数

内部数据State

  • State & setState
  • 初始化State,代码
class B extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      user: {name: 'frank', age: '18'}
    }
  }
  render() {/* ... */}
}
读写State

读用this.state, this.state.xxx.yyy.zzz
写用this.setState(???, fn)
注意setState不会立即改变this.state,会在当前代码运行完后,再去更新this.state,从而触发UI更新
this.setState((state, props)=> newState, fn)
以上的代码反而更容易理解state
fn会在写入成功后执行
写的时候会shallow merge, setState会自动将新state与旧state进行一级合并
改写+1的例子,set的时候尽量用函数的形式,能用就用

import React from "react";
import ReactDOM from "react-dom";


class App extends React.Component {

  constructor(props) {
    super(props);
    this.state = {x: 1}
  }

  onClick = ()=>{
    this.setState({
      x: this.state.x + 1
    })
    this.setState({
      x: this.state.x + 1
    })
    // 只会加1
  }

  onClick2 = ()=>{
    this.setState((state)=>({x: state.x + 1}))
    this.setState((state)=>({x: state.x + 1}))
    // 会加2
  }


  render(){
    return <div className="App">
      App <button onClick={this.onClick2}>+1</button>
      <B name={this.state.x}></B>
    </div>
  }
}

class B extends React.Component {
  componentWillReceiveProps(newProps, nextContext) {
    console.log('旧的 props', this.props)
    console.log('props变化了')
    console.log('新的props')
    console.log(newProps)
  }
  render(){
    return <div>
      {this.props.name}
    </div>
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

生命周期

  • 类比的如下代码
    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() //构造初始化
static getDerivedStateFromProps()
*shouldComponentUpdate() //组件确认更新修改
*render() //组件渲染
getSnapshotBeforeUpdate()
*componentDidMount() //组件挂载
*componentDidUpdate() //组件被更新之后
*componentWillUnmount() //组件将要卸载
static getDerivedStateFromError() 
componentDidCatch()

React常用生命周期

函数列表

constructor()-在这里初始化state
shouldComponentUpdate()-return false阻止更新
render()-创建虚拟dom
componentDidMount()-组件已出现在页面
componentDidUpdate()-组件已更新
componentWillUnmount()-组件将死
constructor
  • 用途:
    初始化props
    初始化state,但此时不能调用setState
    用来写bind this
constructor() {
  /* 其他代码略 */
  this.onClick = this.onClick.bind(this)
  可以用新语法代替
  onClick = ()=> {}
  constructor(){ /* 其他代码略 */ }
}
shouldComponentUpdate
  • 用途
    返回true表示不阻止UI更新
    返回false表示阻止UI更新
  • 面试常问: shouldComponentUpdate有什么用?
    答: 它允许我们手动判断是否要进行组件更新,我们可以根据应用场景灵活地设置返回值,以避免不必要的更新

这样写render会重复执行

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>
      App
      <div>
        {this.state.n}
        <button onClick={this.onClick}>+1</button>
      </div>
    </div>
  }
}

这样写render就不会重复执行

...以上代码省略
 shouldComponentUpdate(newProps, newState) {
    if(newState.n === this.state.n) {
      return false
    } else {
      return true
    }
  }

  render(){
    console.log('render了一次')
    return <div>
      App
      <div>
        {this.state.n}
        <button onClick={this.onClick}>+1</button>
      </div>
    </div>
  }
}
  • 启发,所有组件是否都要加这个功能
    React.PureComponent,新旧state做一层浅对比,以及新 props 和旧 props 的每一个 key。
    如果所有 key 的值全都一样,就不会 render;如果有任何一个 key 的值不同,就会 render。
    大多数情况下能用PureComponent,就尽量用PureComponent,除非遇到了bug
生命周期render用途
  • 展示视图,return(<div>...</div>),return一个虚拟dom
  • 只能有一个根元素
  • 如果有两个根元素,就要用<React.Fragment>包起来,<React.Fragment>可以缩写成<></>
生命周期render技巧
  • render里面可以写if...else
  • render里面可以写?:表达式
  • render里面不能写for循环,需要用数组
  • render里面可以写array.map(循环)
生命周期componentDidMount()用途(组件挂载之后的操作,也是请求ajax的地方)
  • 在元素插入页面后执行代码,这些代码依赖DOM
  • 比如获取div的高度
  • 此处可以发起加载数据的AJAX请求
  • 首次渲染会执行此钩子
  • 参数没有
react提供了更方便的方式获取div

this.divRef = React.createRef()
<div ref={this.divRef}>hi</div>

componentDidMount(){
  const div = this.divRef.current
}
生命周期componentDidUpdate()的用途
  • 在视图更新后执行代码
  • 此处也可以发起AJAX请求,用于更新数据
  • 首次渲染不会执行此钩子
  • 在此处setState可能会引起无限循环,除非放在if里
  • 若shouldComponentUpdate反回false,则不触发此钩子
  • 接受之前的props,之前的state,和snapshot参数一般不用
componentWillUnmount的用途
  • 组件将要被移出页面然后被销毁时执行的代码
  • unmount过的组件不会再次mount

componentWillUnmount举例
如果在componentDidMount里面监听了window scroll,那么就要在componentWillUnmount里面取消监听
如果在componentDidMount里面创建了Timer,就要在componentWillUnmount里面取Timer
如果在componentDidMount里面创建AJAX请求,就要在componentWillUnmount里面取消请求,请求没回来就关闭页面

总结钩子

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

钩子的渲染过程图


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

推荐阅读更多精彩内容