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
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容