React的class组件笔记

377 阅读17分钟

React的class组件

创建方式

  • ES5方式(过时)
import React from 'react'
const A = React.createClass({
    render(){
        return(
        	<div>hai</div>
        )
    }
})
export default A
//由于ES5不支持class才这样写
  • ES6方式(推荐)
import React from 'react'
class B extends React.Component{
    constructor(props){
        super(props)
    }
    render(){
        return(
        	<div>hai</div>
        )
    }
}
export default B

props (外部数据)

class Praent extends React.Component{
    constuctor(props){
        super(props)
        tthis.state = {name:'xiaoming'}
    }
    onClick = ()=>{}
    render(){
        return<B name={this.state.name}
                  onClick={this.onClick}>hai</B>
    }
}

外部数据被包裹为一个对象

{name:'xiaoming',onClic:...,children:'hai'}

此时的onClick是一个回调

读取数据(通过this.props.xxx读取)

class B extends React.Component{
    constructor(props) {
        super(props);
    }
    UNSAFE_componentWillMount() {
        console.log("PORPS")
    }
    render() {
        return (
            <div className="div" onClick={()=>this.props.onClick()}>
                {this.props.name}
                <div>
                    {this.props.children}
                </div>
            </div>
        )
    }
}

注意:props只能读,不准写

改props的值(一个地址)

this.props={另一个对象}

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

理由:渐染是外部数据,就应该有外部跟改

改props的属性

this.props.xxx = "hi"

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

理由:渐染是外部数据,就应该有外部跟改

原则:外部数据应给由数据的主人对数据进行更改

相关钩子

  • componentWillRceiveProps钩子(也废弃)

但组件接受新的props是,会触发这个钩子函数

  • 更给为UNSAFE_componentWillMount

注意:不要使用这个钩子

Props的作用

接受外部数据

  • 只能读不能写
  • 外部数据由父组件传递

接收外部的函数

  • 在恰当的实际,调用该函数
  • 该函数一般是父组件的函数

State&setState(内部数据)

class B extends React.Component{
    constructor(props){
        super(props)
        this.state = {
            user:{name:"frank",age:25}
        }
    }
    render(){
        return(
        	<div>
            	姓名:{this.state.user.name}
                年龄:{this.state.user.gae}
            </div>
        )
    }
}

读写(state,setState)

读用(this.state)

...
reander(){
    return(
    	<div>{this.state.user.name}</div>
    )
}

写用(this.setState)

class  C extends React.Component{
    constructor(props){
        super(props)
        this.state={
            n:0
        }
    }
    render(){
        return(
            <div>
                n:{this.state.n}
                <button onClick={()=>this.setState({n:this.state.n+1})}>+1</button>
            </div>
        )
    }
}

注意:setState不会立即改变this.state,会在当前代码运行完后,在去更新this.state,从而触发UI更新

this.setState((state,porps)=>newState,fn)

这种方式的state反而更容易理解

fn会在写入成功后执行

生命周期函数

类比如下代码

let  div = document.createElement('div')
//这是div在create/constructor的过程
div.textContent="hai"
//这是初始化state
document.body.appendChild(div)
//这是div的mount的过程
div.textContent="good"
//这是div的update的过程
div.remove()
//这是div的unmount的过程

React组件也有这样的过程,我们称之为生命周期

生命周期(函数列表)

constructor()( 在 React 组件挂载之前,会调用它的构造函数只调用一次)

constructor(props)

如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。

在 React 组件挂载之前,会调用它的构造函数。在为 React.Component 子类实现构造函数时,应在其他语句之前调用 super(props)。否则,this.props 在构造函数中可能会出现未定义的 bug。

通常,在 React 中,构造函数仅用于以下两种情况:

constructor() 函数中不要调用 setState() 方法。如果你的组件需要使用内部 state,请直接在构造函数中为 this.state 赋值初始 state

constructor(props) {
  super(props);
  // 不要在这里调用 this.setState()
  this.state = { counter: 0 };
  this.handleClick = this.handleClick.bind(this);
}

只能在构造函数中直接为 this.state 赋值。如需在其他方法中赋值,你应使用 this.setState() 替代。

要避免在构造函数中引入任何副作用或订阅。如遇到此场景,请将对应的操作放置在 componentDidMount

注意

避免将 props 的值复制给 state!这是一个常见的错误:

constructor(props) {
 super(props);
 // 不要这样做
 this.state = { color: props.color };
}

如此做毫无必要(你可以直接使用 this.props.color),同时还产生了 bug(更新 prop 中的 color 时,并不会影响 state)。

**只有在你刻意忽略 prop 更新的情况下使用。**此时,应将 prop 重命名为 initialColordefaultColor。必要时,你可以修改它的 key,以强制“重置”其内部 state。

请参阅关于避免派生状态的博文,以了解出现 state 依赖 props 的情况该如何处理。

render()

render()

render() 方法是 class 组件中唯一必须实现的方法。

render 被调用时,它会检查 this.propsthis.state 的变化并返回以下类型之一:

  • React 元素。通常通过 JSX 创建。例如, 会被 React 渲染为 DOM 节点, 会被 React 渲染为自定义组件,无论是 还是 均为 React 元素。

  • 数组或 fragments。 使得 render 方法可以返回多个元素。欲了解更多详细信息,请参阅 fragments 文档。

  • Portals。可以渲染子节点到不同的 DOM 子树中。欲了解更多详细信息,请参阅有关 portals 的文档

  • 字符串或数值类型。它们在 DOM 中会被渲染为文本节点

  • 布尔类型或 null。什么都不渲染。(主要用于支持返回 test && 的模式,其中 test 为布尔类型。)

    render() 函数应该为纯函数,这意味着在不修改组件 state 的情况下,每次调用时都返回相同的结果,并且它不会直接与浏览器交互。

    如需与浏览器进行交互,请在 componentDidMount() 或其他生命周期方法中执行你的操作。保持 render() 为纯函数,可以使组件更容易思考。

注意

如果 shouldComponentUpdate() 返回 false,则不会调

render()

componentDidMount()(会在组件挂载后(插入 DOM 树中)立即调用)

componentDidMount()

componentDidMount() 会在组件挂载后(插入 DOM 树中)立即调用。依赖于 DOM 节点的初始化应该放在这里。如需通过网络请求获取数据,此处是实例化请求的好地方。

这个方法是比较适合添加订阅的地方。如果添加了订阅,请不要忘记在 componentWillUnmount() 里取消订阅

你可以在 componentDidMount()直接调用 setState()。它将触发额外渲染,但此渲染会发生在浏览器更新屏幕之前。如此保证了即使在 render() 两次调用的情况下,用户也不会看到中间状态。请谨慎使用该模式,因为它会导致性能问题。通常,你应该在 constructor() 中初始化 state。如果你的渲染依赖于 DOM 节点的大小或位置,比如实现 modals 和 tooltips 等情况下,你可以使用此方式处理

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

componentDidUpdate(prevProps, prevState, snapshot)

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

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

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

你也可以在 componentDidUpdate()直接调用 setState(),但请注意它必须被包裹在一个条件语句里,正如上述的例子那样进行处理,否则会导致死循环。它还会导致额外的重新渲染,虽然用户不可见,但会影响组件性能。不要将 props “镜像”给 state,请考虑直接使用 props。 欲了解更多有关内容,请参阅为什么 props 复制给 state 会产生 bug

如果组件实现了 getSnapshotBeforeUpdate() 生命周期(不常用),则它的返回值将作为 componentDidUpdate() 的第三个参数 “snapshot” 参数传递。否则此参数将为 undefined。

注意

如果 shouldComponentUpdate() 返回值为 false,则不会调用 componentDidUpdate()

componentWillUnmount()(会在组件卸载及销毁之前直接调用)

componentWillUnmount()

componentWillUnmount() 会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等。

componentWillUnmount()不应调用 setState(),因为该组件将永远不会重新渲染。组件实例卸载后,将永远不会再挂载它。

不常用的生命周期方法

shouldComponentUpdate()(手动控制组件渲染)

shouldComponentUpdate(nextProps, nextState)

根据 shouldComponentUpdate() 的返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响。默认行为是 state 每次发生变化组件都会重新渲染。大部分情况下,你应该遵循默认行为。

当 props 或 state 发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用。返回值默认为 true。首次渲染或使用 forceUpdate() 时不会调用该方法。

此方法仅作为**性能优化的方式而存在。不要企图依靠此方法来“阻止”渲染,因为这可能会产生 bug。你应该考虑使用内置的 PureComponent 组件**,而不是手动编写 shouldComponentUpdate()PureComponent 会对 props 和 state 进行浅层比较,并减少了跳过必要更新的可能性。

如果你一定要手动编写此函数,可以将 this.propsnextProps 以及 this.statenextState 进行比较,并返回 false 以告知 React 可以跳过更新。请注意,返回 false 并不会阻止子组件在 state 更改时重新渲染。

我们不建议在 shouldComponentUpdate() 中进行深层比较或使用 JSON.stringify()。这样非常影响效率,且会损害性能。

目前,如果 shouldComponentUpdate() 返回 false,则不会调用 UNSAFE_componentWillUpdate()render()componentDidUpdate()。后续版本,React 可能会将 shouldComponentUpdate 视为提示而不是严格的指令,并且,当返回 false 时,仍可能导致组件重新渲染。

static getDerivedStateFromProps()(会在调用 render 方法之前调用)

static getDerivedStateFromProps(props, state)

getDerivedStateFromProps 会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。

此方法适用于罕见的用例,即 state 的值在任何时候都取决于 props。例如,实现 `` 组件可能很方便,该组件会比较当前组件与下一组件,以决定针对哪些组件进行转场动画。

派生状态会导致代码冗余,并使组件难以维护。 确保你已熟悉这些简单的替代方案:

此方法无权访问组件实例。如果你需要,可以通过提取组件 props 的纯函数及 class 之外的状态,在getDerivedStateFromProps()和其他 class 方法之间重用代码。

请注意,不管原因是什么,都会在每次渲染前触发此方法。这与 UNSAFE_componentWillReceiveProps 形成对比,后者仅在父组件重新渲染时触发,而不是在内部调用 setState 时。

getSnapshotBeforeUpdate()(在最近一次渲染输出(提交到 DOM 节点)之前调用)

getSnapshotBeforeUpdate(prevProps, prevState)

getSnapshotBeforeUpdate() 在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期方法的任何返回值将作为参数传递给 componentDidUpdate()

此用法并不常见,但它可能出现在 UI 处理中,如需要以特殊方式处理滚动位置的聊天线程等。

应返回 snapshot 的值(或 null

示例

class ScrollingList extends React.Component {
  constructor(props) {
    super(props);
    this.listRef = React.createRef();
  }

  getSnapshotBeforeUpdate(prevProps, prevState) {
    // 我们是否在 list 中添加新的 items ?
    // 捕获滚动​​位置以便我们稍后调整滚动位置。
    if (prevProps.list.length < this.props.list.length) {
      const list = this.listRef.current;
      return list.scrollHeight - list.scrollTop;
    }
    return null;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    // 如果我们 snapshot 有值,说明我们刚刚添加了新的 items,
    // 调整滚动位置使得这些新 items 不会将旧的 items 推出视图。
    //(这里的 snapshot 是 getSnapshotBeforeUpdate 的返回值)
    if (snapshot !== null) {
      const list = this.listRef.current;
      list.scrollTop = list.scrollHeight - snapshot;
    }
  }

  render() {
    return (
      <div ref={this.listRef}>{/* ...contents... */}</div>
    );
  }
}

在上述示例中,重点是从 getSnapshotBeforeUpdate 读取 scrollHeight 属性,因为 “render” 阶段生命周期(如 render)和 “commit” 阶段生命周期(如 getSnapshotBeforeUpdatecomponentDidUpdate)之间可能存在延迟。

Error boundaries

Error boundaries 是 React 组件,它会在其子组件树中的任何位置捕获 JavaScript 错误,并记录这些错误,展示降级 UI 而不是崩溃的组件树。Error boundaries 组件会捕获在渲染期间,在生命周期方法以及其整个树的构造函数中发生的错误。

如果 class 组件定义了生命周期方法 static getDerivedStateFromError()componentDidCatch() 中的任何一个(或两者),它就成为了 Error boundaries。通过生命周期更新 state 可让组件捕获树中未处理的 JavaScript 错误并展示降级 UI。

仅使用 Error boundaries 组件来从意外异常中恢复的情况;不要将它们用于流程控制。

欲了解更多详细信息,请参阅 React 16 中的错误处理

注意

Error boundaries 仅捕获组件树中以下组件中的错误。但它本身的错误无法捕获。

static getDerivedStateFromError()

static getDerivedStateFromError(error)

此生命周期会在后代组件抛出错误后被调用。 它将抛出的错误作为参数,并返回一个值以更新 state

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // 更新 state 使下一次渲染可以显降级 UI
    return { hasError: true };
  }

  render() {
    if (this.state.hasError) {
      // 你可以渲染任何自定义的降级  UI
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children;
  }
}

注意

getDerivedStateFromError() 会在渲染阶段调用,因此不允许出现副作用。 如遇此类情况,请改用 componentDidCatch()

componentDidCatch()

componentDidCatch(error, info)

此生命周期在后代组件抛出错误后被调用。 它接收两个参数:

  1. error —— 抛出的错误。
  2. info —— 带有 componentStack key 的对象,其中包含有关组件引发错误的栈信息

componentDidCatch() 会在“提交”阶段被调用,因此允许执行副作用。 它应该用于记录错误之类的情况:

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // 更新 state 使下一次渲染可以显示降级 UI
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    // "组件堆栈" 例子:
    //   in ComponentThatThrows (created by App)
    //   in ErrorBoundary (created by App)
    //   in div (created by App)
    //   in App
    logComponentStackToMyService(info.componentStack);
  }

  render() {
    if (this.state.hasError) {
      // 你可以渲染任何自定义的降级 UI
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children;
  }
}

React 的开发和生产构建版本在 componentDidCatch() 的方式上有轻微差别。

在开发模式下,错误会冒泡至 window,这意味着任何 window.onerrorwindow.addEventListener('error', callback) 会中断这些已经被 componentDidCatch() 捕获的错误。

相反,在生产模式下,错误不会冒泡,这意味着任何根错误处理器只会接受那些没有显式地被 componentDidCatch() 捕获的错误。

注意

如果发生错误,你可以通过调用 setState 使用 componentDidCatch() 渲染降级 UI,但在未来的版本中将不推荐这样做。 可以使用静态 getDerivedStateFromError() 来处理降级渲染。

过时的生命周期方法

以下生命周期方法标记为“过时”。这些方法仍然有效,但不建议在新代码中使用它们。参阅此博客文章以了解更多有关迁移旧版生命周期方法的信息。

UNSAFE_componentWillMount()( 在挂载之前被调用)

UNSAFE_componentWillMount()

注意

此生命周期之前名为 componentWillMount。该名称将继续使用至 React 17。可以使用 rename-unsafe-lifecycles codemod 自动更新你的组件。

UNSAFE_componentWillMount() 在挂载之前被调用。它在 render() 之前调用,因此在此方法中同步调用 setState() 不会触发额外渲染。通常,我们建议使用 constructor() 来初始化 state。

避免在此方法中引入任何副作用或订阅。如遇此种情况,请改用 componentDidMount()

此方法是服务端渲染唯一会调用的生命周期函数。

UNSAFE_componentWillReceiveProps()( 会在已挂载的组件接收新的 props 之前被调用)

UNSAFE_componentWillReceiveProps(nextProps)

注意

此生命周期之前名为 componentWillReceiveProps。该名称将继续使用至 React 17。可以使用 rename-unsafe-lifecycles codemod 自动更新你的组件。

注意:

使用此生命周期方法通常会出现 bug 和不一致性:

对于其他使用场景,请遵循此博客文章中有关派生状态的建议

UNSAFE_componentWillReceiveProps() 会在已挂载的组件接收新的 props 之前被调用。如果你需要更新状态以响应 prop 更改(例如,重置它),你可以比较 this.propsnextProps 并在此方法中使用 this.setState() 执行 state 转换。

请注意,如果父组件导致组件重新渲染,即使 props 没有更改,也会调用此方法。如果只想处理更改,请确保进行当前值与变更值的比较。

挂载过程中,React 不会针对初始 props 调用 UNSAFE_componentWillReceiveProps()。组件只会在组件的 props 更新时调用此方法。调用 this.setState() 通常不会触发 UNSAFE_componentWillReceiveProps()

UNSAFE_componentWillUpdate()(会在渲染之前调用 )

UNSAFE_componentWillUpdate(nextProps, nextState)

注意

此生命周期之前名为 componentWillUpdate。该名称将继续使用至 React 17。可以使用 rename-unsafe-lifecycles codemod 自动更新你的组件。

当组件收到新的 props 或 state 时,会在渲染之前调用 UNSAFE_componentWillUpdate()。使用此作为在更新发生之前执行准备更新的机会。初始渲染不会调用此方法。

注意,你不能此方法中调用 this.setState();在 UNSAFE_componentWillUpdate() 返回之前,你也不应该执行任何其他操作(例如,dispatch Redux 的 action)触发对 React 组件的更新

通常,此方法可以替换为 componentDidUpdate()。如果你在此方法中读取 DOM 信息(例如,为了保存滚动位置),则可以将此逻辑移至 getSnapshotBeforeUpdate() 中。

注意

如果 shouldComponentUpdate() 返回 false,则不会调用 UNSAFE_componentWillUpdate()

必会——的生命周期函数

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

constructor

用途

  • 初始化props
  • 初始化state,但是此时不能调用setState
  • 用来写bing this
constructor(){
    /*其他代码略*/
    this.onClick = this.onClick.bing(this.)
}
//可以下面代码图带
onClick = ()=>{}
CONSTRUCTOR(){/*其他代码略*/}

shouldComponentUpdate

用途

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

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

示例

import React from "react";

class App extends React.Component{
    constructor(){
        super()
        this.state = {
            n:1
        }
    }
    onClick = ()=>{
        this.setState(state=>({n:state.n+1}))
        this.setState(state=>({n:state.n-1}))
    }
    shouldComponentUpdate(nextProps, nextState, nextContext) {
        if(this.state.n === nextState.n){
            return false
        }
    }

    render(){
        console.log('UI更新啦')
        return(
            <div>
                <p>n:{this.state.n}</p>
                <button onClick={()=>this.onClick()}>按钮</button>
            </div>
        )
    }
}

export default App

render

用途

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

用法(技巧)

  • render里面可以写if..else
  • render里面可以写?:表达式
  • render里面可不能直接写for循环,需要用数组
  • render里面可以写array.map(循环)

componentDidMount

用途

  • 在元素插入执行代码,这些代码依赖DOM
  • 不如你啊想获取div 的高度,就最好在这里写
  • 次吃可以发起加载数据 的AJAX请求(官方推荐)
  • 首次渲染会执行此钩子

componentDidUpdate

用途

  • 视图更新后执行代码

  • 此处可发起AJAX请求,用于更新数据

  • 首次渲染不会执行此钩子

  • 在此处setState可能会引起无线循环,除非放在if里

  • 若shouldComponentUpdate返回false,则不触发次钩子

componentWillUnmount

用途

  • 组件将要被移除页面然后被销毁时执行
  • unmount过的组件不会再次mount

注意

如果在componentDidMount里面监听了window scroll那么你就要在componentWillUnmount里面取消监听

如果在componentDidMount里面创建了Time那么你就要在componentWillUnmount里面取消Time

如果在componentDidMount里面创建了AJAX那么你就要在componentWillUnmount里面取消AJAX请求

示例代码

import React from "react";

class App extends React.Component{
    constructor(){
        super()
        this.state = {
            n:1,
            m:false
        }
    }
    onClick = ()=>{
        this.setState(state=>({n:state.n+1}))
        // this.setState(state=>({n:state.n-1}))
    }
    remove = ()=>{
        this.setState(state=>({...state,m:!state.m}))
        console.log(this.state)
        console.log('666')
    }
    // shouldComponentUpdate(nextProps, nextState, nextContext) {
    //     if(this.state.n === nextState.n){
    //         return false
    //     }
    //     return true
    // }
    componentDidMount() {
        console.log("组件挂载了")
    }

    componentDidUpdate(prevProps, prevState, snapshot) {
        console.log(prevState)
        console.log('数据更新了')
    }

    render(){
        console.log('UI更新啦')
        return(
            <div>
                <p>n:{this.state.n}</p>
                <p>"state.show"{this.state.m?<Div/>:''}</p>

                <button onClick={()=>this.onClick()}>按钮</button>
                <button onClick={()=>this.remove()}>消灭Div组件</button>
            </div>
        )
    }
}
class Div extends React.Component{
    constructor(props){
        super(props)
    }
    componentWillUnmount() {
        console.log('我要消灭了')
    }

    render(){
        console.log("Div渲染")
        return(
            <div>
                我是Div组件
            </div>
        )
    }
}
export default App