React入门-Component

1,637 阅读8分钟

本文详细描述react组件类(component class)的API细节。

Overview

react通过类class来定义组件component,同时,该类需要继承extend React.Component

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

React.Component子类subclass中必须要有render属性,其他所有的属性都是可选的。

The Component Lifecycle

每个组件都有一些生命周期方法lifecycle methods,可以通过重写override来在特定时间被执行。

  • 常用的声明周期:

  • 较少使用的生命周期:

挂载阶段 Mounting

当一个组件实例component instance被创建并被插入到DOM中时,以下方法会按照顺序被调用:

  • contructor()
  • static getDerivedStateFromProps()
  • render()
  • componentDidMount()

更新阶段 Updating

组件的更新可能是setStateprops改变导致,以下方法会在组件被重新渲染 re-render时,依照顺序依次调用:

  • static getDerivedStateFromProps()
  • shouldComponentUpdate()
  • render()
  • getSnapshotBeforeUpdate()
  • componentDidUpdate()

销毁阶段 Unmounting

  • componentWillUnmount()

错误处理 Error Handling

组件渲染执行render /生命周期方法执行lifecycle methods/构造器contructor执行 中有出错时,下列方法会被调用:

  • static getDerivedStateFromError()
  • componentDidCatch()

Other APIs

每个组件实例都支持以下APIs:

  • setState()
  • forceUpdate()

Class Properties

  • static defaultProps
  • static displayName

Instance Properties

  • props
  • state

Reference

常用的生命周期

以下生命周期会覆盖绝大部分在创建react组件中会碰到的情况。

render()

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

  • React elements: 通常由JSX创建,用来通知react渲染Dom节点自定义组件
  • Arrays & fragments: 使你可以从render返回多个elements
  • Portals:允许将子元素渲染成一个不同的DOM子树subtree
  • String & numbers: 会被渲染成DOM文本节点
  • Boolean & null: 不渲染,常被用来作为条件渲染conditional render。比如return text && <Com/>

render()函数应该是一个pure function纯函数,这意味着它每次运行得到的结果应该是一样的。
render()并不直接与浏览器交互,如果有这种需求,请在生命周期函数中实现。

contructor

如果你不想要初始化state或者并不需要绑定方法,可以不用定义组件中的构造器contructor
constructor()构造器会在组件被挂载之前被调用,构造器中必须执行super(props),否则构造器中的的this不能指定为实例并且拥有this.props,导致报错。
通常,构造器常被用来:

  • 通过给this.state赋值一个对象来初始化本地state
  • 给实例绑定 事件处理器event handler

构造器是唯一可以直接赋值state的地方,其他情况下,都必须通过this.setState来修改state

componentDidMount()

componentDidMount()会在组件被挂载(mounted被插入Dom树中)后立即被执行,至此才算是完成了初始化initialization
适合在这里做网络请求 订阅subscribtion等操作(别忘了在componentWillUnmount里取消订阅unsubscribtion
可以在componentDidMount()中去调用setState,它会触发另外一次额外的render,但是在屏幕上我们只会看到一次(异步渲染,我们其实看不到中间状态的展示)。小心这种方式,可能会导致性能问题,最好是在初始化state的时完成。
换句话说,有些时候你又非用不可,比如渲染一个模态框/提示框时,需要提前知道Dom的尺寸这类的场景。

componentDidUpdate(prevProps,prevState,snapShot)

componentDidUpate()会在更新结束后立即执行,首次渲染(initial render)并不会执行。
这是一个操作Dom的好地方,也是一个发起网络请求的好地方(比如props发生了改变就发起网络请求的使用场景)

componentDidUpdate(prevProps) {
  // Typical usage (don't forget to compare props):
  if (this.props.userID !== prevProps.userID) {
    this.fetchData(this.props.userID);
  }
}

你可以在这里立即调用setState但是必须被包裹在 条件语句里,否则就会进入渲染的死循环。就像componentDidMount,这样也会导致重新渲染(虽然用户看不到中间?)而影响性能。
不要尝试用propsstate赋值,遇到这种场景,直接用props。
如果组件中定义了getSnapshotBeforeUpdate(),该方法返回的值会作为componentDidUpdate的第三个参数。

如果shouldComponentUpdate()返回false,后面的renderdidUpdate都不会执行

componentWillUnmount()

这个方法会在组件卸载和销毁前被调用,在这个方法里执行必须的清理工作,比如 取消网络请求 取消定时器 取消订阅等。

Rarely Used Lifecycle Methods

下列组件的生命周期方法不常被用到,有时候还是挺管用的:

shouldComponentUpdate(nextProps, nextState)

使用shouldComponentUpdate()可以让stateprops的改变不影响组件的展示。默认情况下,每次state/props的改变,都应该重新渲染。
state/props改变时,这个方法就会被调用,但是首次渲染或使用forceUpdate并不会触发此方法。
这个方法只被用来做 性能优化performance optimization,不要依赖它来阻止渲染,可以使用内嵌的 纯组件PureComponent而不是去手写shouldComponentUpdate()。纯组件会对propsstate进行 浅比较shallow comparison确保不会漏过一些关键的update
如果你对自己很自信,也可以手动去控制react更新(注意即使返回false,当子组件状态更新时,子组件还是会重新渲染)

static getDerivedStateFromProps(props, state)

getDerivedStateFromProps()是在render()之前被调用的,初始挂载和后续的更新都会触发。它会返回一个对象作为最终版本的state,返回null就不更新了。
这个方法常被用来处理 状态依赖于props的改变 的场景。
派生state会导致冗余的代码,并且让你的组件难以理解,我们可以尝试一下方法作为代替:

  • 如果你有带副作用的操作(比如数据获取或动画),改用componentDidUpdate()
  • 如果你想要在props发生改变后,重新计算数据,使用缓存memoization
  • 如果你想要在props发生改变后,重设state,考虑使用受控组件 component fully controlled带key的非受控组件

这个方法每次渲染都会执行

getSnapshotBeforeUpdate(prevProps, prevState)

getSnapshotBeforeUpdate()是最接近渲染结果的地方。方法返回的结果将作为componentDidUpdate()的第三个参数。

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

  getSnapshotBeforeUpdate(prevProps, prevState) {
    // Are we adding new items to the list?
    // Capture the scroll position so we can adjust scroll later.
    if (prevProps.list.length < this.props.list.length) {
      const list = this.listRef.current;
      return list.scrollHeight - list.scrollTop;
    }
    return null;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    // If we have a snapshot value, we've just added new items.
    // Adjust scroll so these new items don't push the old ones out of view.
    // (snapshot here is the value returned from getSnapshotBeforeUpdate)
    if (snapshot !== null) {
      const list = this.listRef.current;
      list.scrollTop = list.scrollHeight - snapshot;
    }
  }

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

getSnapshotBeforeUpdate()是 渲染阶段生命周期render phrase lifecycles和 提交阶段生命周期commit phrase lifecycles的中间地带。

Error Boundaries

Error Boundaries是react捕获JS异常的指称(包括 渲染/生命周期/构造器里的异常)。
类组件可以通过添加static getDerivedStateFromErrorcomponentDidCatch来捕获这些异常,并且提供一个兜底的UI。

static getDerivedStateFromError(error)

它在后代组件有异常时被触发,通过可以通过更新状态来处理异常。

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

  static getDerivedStateFromError(error) {
    // Update state so the next render will show the fallback UI.
    return { hasError: true };
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; 
  }
}

getDerivedStateFromError是渲染阶段的生命周期函数,不要在其中进行带副作用的操作。交给componentDidCatch来干这个。

componentDidCatch(error, info)

这个方法在 后代组件 descendant components报错时触发,接收的参数有:

  • error 错误对象
  • 错误组件栈信息

componentDidCatch是在 提交阶段commit phrase的生命周期函数,可以进行副作用操作,一般用来打印日志。

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

  static getDerivedStateFromError(error) {
    // Update state so the next render will show the fallback UI.
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    // Example "componentStack":
    //   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) {
      // You can render any custom fallback UI
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; 
  }
}

Legacy Lifecycle Methods

以下的生命周期方法虽然还能用,但是即将被废弃:

  • componentWillMount()
  • compoenntWillReceiveProps()
  • componentWillUpdate()

Other APIs

不同于生命周期方法是react调用的,下述的方法都是你自己在组件中调用:

setState(updater, [callback])

setState将组件状态的改变队列化,并且通知react用更新后的数据重新渲染视图。
setState视为一次更新请求更为贴切,为了获取更好的体验,react可能会在一轮中更新好几次组件,所以并不能保证setState执行后,更新是立即开始的。
第一个参数是一个更新器函数updater:

(state, props) => stateChange

state是一个组件当前状态的引用,不应该直接修改它,我们应该构建一个新的对象去替换它。
updator接收的stateprops都是最新的,更新器的返回值会被shallowly mergestate
第二个参数是一个在组件渲染完成以后的回调,当然这个逻辑可以放在componentDidUpdate里。
你也可以通过选择给setState传递一个对象这种形式来更新state,它会直接shallowly mergestate。这种形式的setState也是异步asynchronous的。如果想要基于最新的值来操作state,最好使用更新器的形式。

forceUpdate(callback)

如果组件依赖一些非props/state的数据,同时你也想实现更新,就可以调用forceUpdate()
调用forceUpdate()会触发render但是不会触发当前组件shouldComponentUpdate,子组件的生命周期函数都是完整触发的。

Class Properties

static defaultProps

默认属性defaultProps支持undefined类型的props,在props传递的是null时就不生效了。

class CustomButton extends React.Component {
  // ...
}

CustomButton.defaultProps = {
  color: 'blue'
};
render() {
  return <CustomButton color={null} /> ; // props.color will remain null
 }

displayName

通常displayName字符串常被用来在高阶组件high-order component debug信息。

Instance Properties

props

this.props是由组件调用者定义的属性。
特别地,this.props.children是一个特殊的属性,通常以子标签child tag的形式定义在由JSX表达式里。

state

state包含着组件中特定的那些可能会改变的数据,它是一个由用户定义的对象。
如果有一些值并不是被用来渲染或作为数据流使用(比如定时器ID),可以放在组件实例上而不是state里。