Skip to content

概览

React.Component

React 的组件可以定义为class或函数的形式。class组件目前提供了更多的功能,这些功能将在此章节中详细介绍。如需定义class组件,需要继承React.Component

class Welcome extends React.Component{
    render(){
        return <h1>Hello,{this.props.name}</h1>
    }
}

React.Component的子类中有个必须定义的render()函数。其余为可选。 我们强烈建议你不要创建自己的组件基类。 在 React 组件中,代码重用的主要方式是组合而不是继承。

组件的生命周期

每个组件都包含"生命周期方法",你可以重写这些方法,以便于在运行过程中特定的阶段执行这些方法。你可以使用此生命周期图谱作为速查表。在下列表中,常用的生命周期方法会被加粗。其余生命周期函数的使用则相对罕见。

挂载

当组件实例被创建并插入DOM中时,其生命周期调用顺序如下:

  • constructor()
  • static getDerivedStateFromProps()
  • render()
  • componentDidMount()
  • UNSAFE_componentWillMount() // 即将过期

更新

当组件的props或state发生变化时会触发更新。组件更新的生命周期调用顺序如下:

  • static getDerivedStateFromProps()
  • shouldComponentUpdate()
  • render()
  • getSnapshotBeforeUpdate()
  • componentDidUpdate
  • UNSAFE_componentWillUpdate() // 即将过期
  • UNSAFE_componentWillReceiveProps() //即将过期

卸载

当组件从DOM中移除时会调用如下方法

  • componentWillUnmount()

错误处理

当渲染过程,生命周期,或子组件的构造函数中抛出错误时,会调用如下方法:

  • static getDerivedStateFromError()
  • componentDidCatch()

其他 APIs

组件还提供了一些额外的API:

  • setState()
  • forceUpdate()

class属性

  • defaultProps
  • displayName

实例属性

  • props
  • state

参考

常用的生命周期方法

render()

render方法是class组件中唯一必须实现的方法。 当render被调用时,它会检查this.propsthis.state的变化并返回以下类型之一:

  • React元素:通常通过JSX创建。例如,<div />会被React渲染为DOM节点。<MyComponent />会被React渲染为自定义组件,无论是<div />还是<div />还是<MyComponent />均为React元素。
  • 数组或fragment:使得render方法可以返回多个元素。
  • Portals:可以渲染子节点到不同得DOM子树中。
  • 字符串或数值类型:他们在DOM中会被渲染为文本节点
  • 布尔类型或null:什么都不渲染

render()函数应该为纯函数,这意味着在不修改组件state得情况下,每次调用时都返回相同得结果,并且它不会直接与浏览器交互。 如需与浏览器进行交互,请在componentDidMount或其他生命周期方法中执行你得操作。保持render()为纯函数,可以使得组件更容易思考。

constructor()

如果不初始化state或不进行方法绑定,则不需要为React组件实现构造函数。 在React组件挂载之前,会调用他的构造函数。在为React.Component子类实现构造函数时,应在其他语句之前调用super(props)。否则,this.props在构造函数中可能会出现未定义的bug。 通常,在React中,构造函数仅用于以下两种情况:

  • 通过给this.state赋值对象来初始化内部state。
  • 为事件处理函数绑定实例 在constructor函数中不要调用setState()方法。如果你的组件需要使用内部state,请直接在构造函数中为this.state赋值state:
constructor(props){
    super(props)
    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(更新props中的color时,并不会影响state)。 只有在你可以忽略prop更新的情况下使用**。此时,应将prop重新命名为initialColordefaultColor。必要时,你可以修改它的key,以强制重新其内部的state。

componentDidMount()

componentDidMount会在组件挂载后(插入DOM树中)立即调用。依赖于DOM节点的初始化应该放在这里。如需通过网络请求获取数据,此处是实例化请求得好地方。 这个方法是比较适合添加订阅得地方。如果添加了订阅,请不要忘记componentDidMount里取消订阅 你可以在componentDidMount里直接调用setState()。它将触发额外渲染,但此渲染会发生在浏览器更新屏幕之前。如此保证了即使在render两次调用的情况下,用户也不会看见中间状态。请谨慎的使用该模式,因为它会导致性能问题。通常你应该在constructor中初始化state。如果你的渲染依赖于DOM节点的大小和位置,比如实现了modals和tooltips等情况下,你可以使用此方式处理。

componentDidUpdate()

componentDidUpdate会在更新后会被立即调用。首次渲染不会执行此方法。 当组件更新后,可以在此处对DOM进行操作。如果你对更新前后的props进行了比较,也可以选择在此处进行网络请求。(例如,当props未发生变化,则不会执行网络请求)。

componentDidUpdate(prevProps){
    if(this.props.userID !== prevProps.userID){
        this.fetchData(this.props.userID)
    }
}

你也可以在componentDidUpdate中直接调用setState(),但请注意它必须被包裹在一个条件语句中,正如上述例子那样进行处理,否则会导致死循环。它还会导致额外的重新渲染,虽然用户不可见,但是会影响组件的性能。不能将props镜像给state,请考虑直接使用props。 如果组件实现了getSnapshotBeforeUpdate生命周期,则它的返回值将作为componentDidUpdate的第三个参数"snapshot"参数传递,否则为undefined。

componentWillUnmount

componentWillUnmount会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,例如,清理timer,取消网络请求或清除在componentDidMount中创建的订阅等。 componentWillUnmount中不应该调用setState,因为该组件将永远不会重新渲染。组件实例卸载后,将永远不会再挂载它。

不常用的生命周期方法

本节中的生命周期方法并不太常用。它们偶尔会很方便,但是大部分情况下组件可能都不需要它们。

shouldComponentUpdate

根据shouldComponentUpdate的返回值,判断React组件的输出是否受当前state或props更改的影响。默认行为是state每次发生变化的组件都会重新渲染。默认行为是state每次发生变化组件都会重新渲染。 当props或state发生变化时,shouldComponentUpdate会在渲染执行之前被调用。返回值默认为true。首次渲染或使用forceUpdate时不会调用该方法。 此方法仅作为性能优化的方式而存在。不要企图依靠此方法来阻止渲染,因为这可能会产生bug,你应该考虑使用内置的PureComponent组件,而不是手动编写shouldComponentUpdatePureComponent会对props和state进行浅层比较,并减少了跳过必要更新的可能性。 如果你一定要手动编写此函数,可以将this.propsnextProps以及this.statenextState进行比较,并返回false以告知React可以跳过更新。请注意,返回false并不会组织子组件在state更改时重新渲染。 我们不建议在shouldComponentUpdate中进行深层比较或使用JSON.stringify()。这样非常影响效率,且会损害性能。

static getDerivedStateFromProps()

getDerivedStateFromProps会在调用render方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新state,如果返回null则不更新任何内容。 此方法适用于罕见用例,即state的值在任何时候都取决于 props。例如,实现<Transition>组件可能很方便,该组件会比较当前组件与下一组件,以决定针对哪些组件进行转场动画。 派生状态会导致代码冗余,并使得组件难以维护。

  • 如果你需要执行副作用(例如,数据提取或动画)以响应props中的更改,请改用componentDidUpdate
  • 如果只想在prop更改时重新计算某些数据,请使用memoization helper代替。
  • 如果你想在props更改时重置某些state,请考虑使组件完全受控或使用key使组件完全不受控代替。 此方法无权访问组件实例。如果你需要,可以通过提取组件props的纯函数及class之外的状态,在getDerivedStateFromProps和其他class方法之间重用代码。 请注意,不管原因是什么,都会在每次渲染前触发此方法。这与UNSAFE_componentWillReceiveProps形成对比,后者仅在父组件重新渲染时候触发,而不是在内部调用setState时。

getSnapshotBeforeUpdate

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 getDerivedStateFromErrorcomponentDidCatch中的任何以下(或两者),它就成为了Error boundaries。通过生命周期更新state可让组件捕获树中未处理的javascript错误并展示降级UI。 仅使用Error boundaries组件来从意外异常中回复的情况;不要将它们用于流程控制。

static getDerivedStateFromError

此生命周期会在后台组件抛出错误后被调用。它将抛出的错误作为参数,并返回一个值以更新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()

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

  1. error ———— 抛出的错误。
  2. info ———— 带有componentStackkey的对象,其中包含有关组件引发的错误的信息栈信息。 componentDidCatch会在提交阶段被调用,因此允许执行副作用。它应该用于记录错误之类的情况。
class ErrorBoundary extends React.Component{
    constructor(props){
        super(props);
        this.state = { hasError:false }
    }
    static getDerivedStateFromError(error){
        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;
    }
}

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

过期的生命周期方法

以下生命周期方法标记为过时。这些方法仍然有效,但不建议在新代码中使用它们。

UNSAFE_componentWillMount()

UNSAFE_componentWillMount在挂载之前被调用。他在render()之前调用,因此在此方法中同步调用setState()不会触发额外的渲染。通常我们建议使用constructor来初始化state。 避免在此方法中引入任何副作用或订阅。如遇此种情况,请改用 componentDidMount()。 此方法是服务端渲染唯一会调用的生命周期函数。

UNSAFE_componentWillReceiveProps()

UNSAFE_componentWillReceiveProps会在已挂载的组件接收新的props之前被调用。如果你需要更新状态以响应prop更改(例如,重置它),你可以比较this.propsnextProps并在此方法中使用this.setState执行state转换。 请注意,如果父组件导致组件重新渲染,即视props没有更改,也会调用此方法。如果只想处理更改,请确保进行当前值和变更值得比较。 在挂载过程中,React不会针对初始props调用UNSAFE_componentWillReceiveProps。组件只会在组件得props更新时候调用此方法。调用this.setState()通常不会触发UNSAFE_componentWillReceiveProps

UNSAFE_componentWillUpdate()

当组件收到新得props和state时,会在渲染之前调用UNSAFE_componentWillUpdate。使用此作为在更新发生之前执行准备更新得机会。初始渲染不会调用此方法。 注意,你不能此方法中调用this.setState();在UNSAFE_componentWillMount返回之前,你也不应该执行任何其他操作(例如,dispatch Redux得action)触发对React组件得更新。 通常,此方法可以替换为componentDidUpdate。如果你在此方法中读取了DOM位置,则可以将此逻辑转移到getSnapshotBeforeUpdate中。

其他API

不同于上述声明周期方法(React主动调用),以下方法是你可以在组件中调用得方法。 只有两个方法:setState()forceUpdate()

setState()

setState()将对组件state的更改排入队列,并通知React需要使用更新后的state重新渲染此组件及其子组件。这是用于更新用户界面以响应事件处理器和处理服务器数据的主要方式 将setState()视为请求而不是立即更新组件的命令。为了更好的感知性能,React会延迟调用它,然后通过一次传递更新多个组件,React并不会保证state的变更会立即生效。 setState()并不总是立即更新组件。它会批量推迟更新。这使得在调用setState()后立即读取this.state成为了隐患。为了消除隐患,请使用componentDidUpdate或者setState的回调函数。(setState(updater,callback)),这两种方式都可以保证在应用更新后触发。如需基于之前的state来设置当前的state,请阅读下述关于参数updater的内容。 除非shouldComponentUpdate返回false,否则setState()将始终执行重新渲染操作。如果可变对象被使用,且无法在shouldComponentUpdate中实现条件渲染,那么仅在新旧状态不一时调用setState()可以避免不必要的重新渲染。

(state,props) => stateChange

state是对应用变化时组件状态的引用。当然,它不应直接被修改。你应该使用基于stateprops构建的新对象来标识变化。例如,假是我们想根据props.step来增加state:

    this.setState((state,props)=>{
        return {counter:state.counter + props.step}
    })

updater函数中接收的stateprops都保证为最新。updater的返回值会与state进行浅合并。 setState()的第二个参数为可选的回调函数,它将在setState完成合并并重新渲染组件后执行。通常,我们建议使用componentDidUpdate来代替此方式。 setState的第一个参数接收函数外,还可以接受对象类型:

    setState(setState[,callback])

stateChange会将传入的对象浅层合并到新的state中,例如,调整购物车商品数:

    this.setState({
        quantity:2
    })

这种形式的setState()也是异步的,并且在同一个周期内会对多个setState进行批处理。例如,如果在同一周期内多次设置商品数量的增减,相当于:

Object.assign(
  previousState,
  {quantity: state.quantity + 1},
  {quantity: state.quantity + 1},
  ...
)

后调用的setState()将覆盖同一周期内先调用得setState的值,因此商品数量仅仅增加一次。如果后续状态取决于当前状态,我们建议使用updater函数的形式代替。

forceUpdate()

默认情况下,当组件的state或props发生变化时,组件将重新渲染。如果render()方法依赖于其他数据,则可以调用forceUpdate()强制让组件重新渲染。 调用forceUpdate()将导致组件调用render()方法,此操作会跳过该组件的shouldComponentUpdate。但是其子组件会触发正常的声明周期方法,包括shouldComponentUpdate方法。如果标记发生了变化,React仍将只更新DOM。 通常你应该避免使用forceUpdate(),尽量在render()中使用this.propsthis.state

class属性

defaultProps

defaultProps可以为class组件添加默认props。这一般用于props未赋值,但又不能为null的情况。例如:

class CustomButton extends React.Component{

}
CustomButton.defaultProps = {
    color:'blue'
}

如果未提供props.color,则默认设置为blue

    render(){
        return <CustomButton  />
    }

如果props.color被设置为null,则它将保持为null

displayName

displayName字符串多用于调试消息。通常,你不需要设置它,因为它可以根据函数组件或class组件的名称推断出来。如果调试时需要显示不同的名称或创建高阶组件。

实例属性

props

this.props包括被该组件调用者定义的props。 需要特别注意,this.props.children是一个特殊的prop,通常由jsx表达式中的子组件组成,而非组件本身定义。

state

组件中的state包含了随时可能发生变化的数据。state由用户自定义,它是一个普通的JavaScript对象。 如果某些值未用于渲染或数据流(例如,计算器ID),则不必将其设置为state。此类值可以在组件实例上定义。