在这篇文章中,我会介绍 React 各个生命周期在理论我的项目中的利用场景,并给出代码示例。
React 生命周期图谱:https://projects.wojtekmaj.pl...

挂载阶段(Mounting)

组件实例创立并插入到 DOM 中。这个阶段调用生命周期函数程序顺次是: coustructor()、static getDerivedStateFromProps()、render()、componentDidMount()。

更新阶段(Updating)

组件的 state 或者 props 发生变化时会触发更新。这个阶段调用生命周期函数程序顺次是:static getDerivedStateFromProps()、shouldComponentUpdate()、render()、getSnapshotBeforeUpdate()、componentDidUpdate()。

卸载阶段(UnMounting)

组件从 DOM 中移除时,会调用 componentWillUnmount() 办法。

错误处理(Error boundaries)

当渲染过程,生命周期,或子组件的构造函数中抛出谬误时,会调用如下办法:static getDerivedStateFromError()、componentDidCatch()。

render()

留神⚠️:
  1. render() 函数是 class 组件中惟一必须实现的办法;
  2. render 函数的返回值:React 元素、数组、Fragments、Portals、字符串或数值、布尔值或 null;
  3. render 应为纯函数,并且不会与浏览器产生交互,这样使组件更加容易了解。

constructor(props)

调用工夫:

React 组件在挂载之前,会调用它的构造函数。

应用场景:
  1. 初始化 state
  2. 给事件处理函数绑定实例
class Test extends React.Component {   constructor(props) {      super(props);      this.state = { counter: 0 };      this.handleClick = this.handleClick.bind(this);   }   handleClick() {      const { counter } = this.state;      this.setState({ counter: counter + 1 });   }   render() {      return (         <div>             {this.state.counter}             <button onClick={this.handleClick}>click</button>          </div>      );   }}ReactDOM.render(   <Test />, document.getElementById('root'));
留神⚠️:
  1. 不要在构造函数中调用 setState 办法;
  2. 在为 React.Component 子类实现构造函数时,应在其余语句之前调用 super(props);
  3. 防止在构造函数中引入副作用或者订阅;
  4. 防止将 props 的值赋值给 state(why?)。

componentDidMount()

调用工夫:

React 组件挂载后(插入DOM 树中)立刻调用。

应用场景:
  1. 依赖于 DOM 节点的初始化操作;
  2. 通过网络申请获取数据;
  3. 增加订阅;
留神⚠️:

在此处如果调用 setState(),将会触发渲染,这个渲染将会产生在浏览器更新屏幕之前,因而用户不会有感知,然而要审慎应用该模式,因为会导致性能问题。 如果你的渲染依赖于 DOM 节点的地位和大小,比方 Modal 和 Tooltip ,则能够应用该模式。

componentDidUpdate(prevProps, prevState, snapshot)

调用工夫:

React 组件更新后立刻调用,首次渲染不会调用此办法。

应用场景:
  1. 在组件更新后,在此处对 DOM 进行操作;
  2. 对更新前后的 props 进行比拟,如果有变动,执行操作(比方网络申请、执行 setState() 等);
componentDidUpdate(prevProps) {  // 典型用法(不要遗记比拟 props):  if (this.props.id !== prevProps.id) {    this.fetchData(this.props.id);  }}
留神⚠️:
  1. 不要将 props “镜像”给 state(why?)
  2. 如果组件实现了 getSnapshotBeforeUpdate() 生命周期,则它的返回值将作为 componentDidUpdate 的第三个参数(snapshot)传入,否则,第三个参数将为 undefined;
  3. 如果 shouldComponentUpdate() 返回值为 false,则不会调用 componentDidUpdate()。

componentWillUnmount()

调用工夫:

React 组件卸载或销毁之前调用

应用场景:
  1. 革除 timer(setInterval());
  2. 勾销网络申请;
  3. 勾销在 componentDidMount() 中创立的订阅。
留神⚠️:

在该生命周期中不要调用 setState(),因为组件卸载后,不会从新渲染。

shouldComponentUpdate(nextProps, nextState)

调用工夫:

当 state 或者 props 发生变化时,shouldComponentUpdate() 将会在 render() 之前调用,首次渲染或者应用 forceUpdate() 时不会调用。

应用场景:

能够通过手写笼罩 shouldComponentUpdate() 办法对 React 组件进行性能优化,然而大部分状况下,能够通过继承 React.pureComponent 代替手写 shouldComponentUpdate(),React.pureComponent 实现了 shouldComponentUpdate() 办法(用以后和之前的 props 和 state 进行浅比拟)。

class CounterButton extends React.Component {  constructor(props) {    super(props);    this.state = {count: 1};  }  shouldComponentUpdate(nextProps, nextState) {    if (this.props.color !== nextProps.color) {      return true;    }    if (this.state.count !== nextState.count) {      return true;    }    return false;  }  render() {    return (      <button        color={this.props.color}        onClick={() => this.setState(state => ({count: state.count + 1}))}> Count: {this.state.count} </button>    );  }}
留神⚠️:
  1. 此办法默认返回true,其遵循默认行为——“state 或者 props 发生变化时,组件从新渲染”;如果返回 false,则组件不会从新渲染,大部分状况下,你该遵循默认行为。
  2. 此办法为了性能优化而存在,不要试图应用此办法阻止渲染,因为这可能产生 bug;
  3. 如果你肯定要手动编写此办法,须要对 this.props 和 nextProps、this.state 和 nextState 进行比拟。
  4. 不倡议在 shouldComponentUpdate() 中应用 JSON.stringfy() 或者进行深层次比拟,这样会极其的消耗性能。

static getDerivedStateFromProps(props,state)

调用工夫:

render() 办法之前调用,在初始挂载和后续更新都会调用。

应用场景:

实用于罕用例,即 state 的值在任何时候都取决于 props。

// 例子1:class Example extends React.Component {  state = {     isScrollingDown: false,    lastRow: null  };  static getDerivedStateFromProps(props, state) {     if (props.currentRow !== state.lastRow) {       return {         isScrollingDown: props.currentRow > state.lastRow,         lastRow: props.currentRow      };     }    // 返回 null 示意无需更新 state。    return null;   }}
// 例子2:依据 props 获取 externalDataclass ExampleComponent extends React.Component {  state = {    externalData: null,  };  static getDerivedStateFromProps(props, state) {  // 保留 prevId 在 state 中,以便咱们在 props 变动时进行比照。   // 革除之前加载的数据(这样咱们就不会渲染旧的内容)。     if (props.id !== state.prevId) {       return {         externalData: null,        prevId: props.id      };    }     // 无需更新 state     return null;  }     componentDidMount() {    this._loadAsyncData(this.props.id);  }  componentDidUpdate(prevProps, prevState) {     if (this.state.externalData === null) {       this._loadAsyncData(this.props.id);    }   }    componentWillUnmount() {    if (this._asyncRequest) {      this._asyncRequest.cancel();    }  }  render() {    if (this.state.externalData === null) {      // 渲染加载状态 ...    } else {      // 渲染实在 UI ...    }  }  _loadAsyncData(id) {    this._asyncRequest = loadMyAsyncData(id).then(      externalData => {        this._asyncRequest = null;        this.setState({externalData});      }    );  }}
留神⚠️:

派生状态会导致代码冗余,并使组件难以保护,能够应用如下替换计划:

(1)memoization 模式:如果只是为了缓存基于以后 props 计算之后的后果的话,没有必要应用 getDerivedStateFromProps(),因为治理 state 的复杂度会随着须要治理的属性的增多而越来越宏大,比方,如果咱们想在组件 state 里增加第二个派生 state,那就须要写两份跟踪变动的逻辑。为了让代码变得简略和易于治理,能够尝试应用 memoization。

  // *******************************************************  // 留神:这个例子不是倡议的办法。  // 上面的例子才是倡议的办法。  // *******************************************************  static getDerivedStateFromProps(props, state) {    // 列表变动或者过滤文本变动时都从新过滤。    // 留神咱们要存储 prevFilterText 和 prevPropsList 来检测变动。    if (      props.list !== state.prevPropsList ||      state.prevFilterText !== state.filterText    ) {      return {        prevPropsList: props.list,        prevFilterText: state.filterText,        filteredList: props.list.filter(item => item.text.includes(state.filterText))      };    }    return null;  }
// 应用 memoizationimport memoize from "memoize-one";class Example extends Component {  // state 只须要保留以后的 filter 值:  state = { filterText: "" };  // 在 list 或者 filter 变动时,从新运行 filter:  filter = memoize(    (list, filterText) => list.filter(item => item.text.includes(filterText))  );  handleChange = event => {    this.setState({ filterText: event.target.value });  };  render() {    // 计算最新的过滤后的 list。    // 如果和上次 render 参数一样,`memoize-one` 会重复使用上一次的值。    const filteredList = this.filter(this.props.list, this.state.filterText);    return (      <Fragment>        <input onChange={this.handleChange} value={this.state.filterText} />         <ul>{filteredList.map(item => <li key={item.id}>{item.text}</li>)}</ul>      </Fragment>    );  }}

(2)应用齐全受控的组件:

function EmailInput(props) {  return <input onChange={props.onChange} value={props.email} />;}

(3)应用有 key 的非可控组件(当 key 变动的时候,React 就会创立一个新的而不是一个既有的组件),大部分时候,这是解决重置 state 的最好的方法。

class EmailInput extends Component {  state = { email: this.props.defaultEmail };  handleChange = event => {    this.setState({ email: event.target.value });  };  render() {    return <input onChange={this.handleChange} value={this.state.email} />;  }}<EmailInput  defaultEmail={this.props.user.email}  key={this.props.user.id}/>

然而在某些状况下,key 可能不起作用,这时候能够应用 getDerivedStateFromProps() 来察看属性变动。

class EmailInput extends Component {  state = {    email: this.props.defaultEmail,    prevPropsUserID: this.props.userID  };  static getDerivedStateFromProps(props, state) {    // 只有以后 user 变动,    // 重置所有跟 user 相干的状态。    // 这个例子中,只有 email 和 user 相干。    if (props.userID !== state.prevPropsUserID) {      return {        prevPropsUserID: props.userID,        email: props.defaultEmail      };    }    return null;  }  // ...}

getSnapshotBeforeUpdate(prevProps, prevState)

调用工夫:

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

应用场景:

组件在产生更改之前能够从 DOM 中获取一些信息,这个办法可能用在 UI 解决中,例如滚动地位。

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>    );  }}

static getDerivedStateFromError(error)

调用工夫:

该办法在组件抛出谬误时被调用。它将抛出的谬误作为参数,并且返回一个值以更新 state。

应用场景:

显示降级 UI

留神⚠️:

该办法会在渲染阶段调用,因而不容许呈现副作用,如遇此类情况,能够用 componentDidCatch()。

componentDidCatch(error, info)

调用工夫:

该办法在“提交”的阶段被调用。因而容许执行副作用。

应用场景:

用于记录谬误,该办法的第二个参数蕴含无关引发组件谬误的栈信息。

class ErrorBoundary extends React.Component {  constructor(props) {    super(props);    this.state = { error: null, errorInfo: null };  }    static getDerivedStateFromError(error) {    // 更新 state 使下一次渲染能够显示降级 UI    return { error: true };  }    componentDidCatch(error, errorInfo) {    this.setState({ errorInfo })  }    render() {    if (this.state.errorInfo) {      return (        <div>          <h2>Something went wrong.</h2>          <details style={{ whiteSpace: 'pre-wrap' }}>             {this.state.error && this.state.error.toString()}             <br />             {this.state.errorInfo.componentStack}          </details>        </div>       );    }    return this.props.children;  }}class BuggyCounter extends React.Component {  constructor(props) {    super(props);    this.state = { counter: 0 };    this.handleClick = this.handleClick.bind(this);  }      handleClick() {    this.setState(({counter}) => ({ counter: counter + 1 }));  }    render() {    if (this.state.counter === 5) {      // 此处渲染会产生谬误      return [1,2,3];    }    return <h1 onClick={this.handleClick}>{this.state.counter}</h1>;  }}function App() {  return (    <div>       <p>           <b> This is an example of error boundaries in React 16.               <br /><br />               Click on the numbers to increase the counters.               <br />               The counter is programmed to throw when it reaches 5. This simulates a JavaScript error in a component.           </b>       </p>       <hr />       <ErrorBoundary>           <p>These two counters are inside the same error boundary. If one crashes, the error boundary will replace both of them.           </p>       <BuggyCounter />            <BuggyCounter />       </ErrorBoundary>       <hr />       <p>These two counters are each inside of their own error boundary. So if one crashes, the other is not affected.       </p>       <ErrorBoundary>           <BuggyCounter />       </ErrorBoundary>        <ErrorBoundary>           <BuggyCounter />       </ErrorBoundary>     </div>  );}