Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React 生命周期 #21

Open
bigggge opened this issue Sep 16, 2018 · 0 comments
Open

React 生命周期 #21

bigggge opened this issue Sep 16, 2018 · 0 comments
Labels

Comments

@bigggge
Copy link
Member

bigggge commented Sep 16, 2018

React 组件的生命周期

Before React 16.3

img

After React 16.3

img

After React 16.4

无论更新的原因如何,每次呈现组件时都会调用 getDerivedStateFromProps。以前,只有在组件由其父组件重新呈现时才会调用它,并且不会因本地 setState 而触发。这是对最初实施的疏忽,现在已得到纠正。之前的行为更类似于 componentWillReceiveProps,但改进的行为确保了与React即将推出的异步呈现模式的兼容性。

React 声明周期可能会经历如下三个过程:

  • 装载过程(Mount)
  • 更新过程(Update)
  • 卸载过程(Unmount)

装配过程

constructor

constructor(props) {
  super(props);
  this.state = {
    color: props.initialColor
  };
}

构造函数是初始化状态的合适位置。若你不初始化状态且不绑定方法,那你也不需要为你的React组件定义一个构造函数。

static getDerivedStateFromProps

static getDerivedStateFromProps(nextProps, prevState)

组件实例化后和接受新属性时将会调用 getDerivedStateFromProps

UNSAFE_componentWillMount

UNSAFE_componentWillMount()

在装配发生前被立刻调用。其在render()之前被调用,因此在这方法里同步地设置状态将不会触发重渲。

这是唯一的会在服务端渲染调起的生命周期钩子函数。

  1. componentWillMount 是 SSR 里唯一执行的生命周期函数,如果你在这里 subscribe 了,那你写在 willUnmount 的 unsubscribe 方法就不会执行了。

  2. 有人经常假设 componentWillMountcomponentWillUnmount 是对应存在的,这其实不能保证。只有 componentDidMount 被调用之后,React 才能保证 componentWillUnmount 会在清除时被调用。

  3. Fiber Reconciler 有可能需要暂停这个函数提高 render 的优先级。 如果你写了异步任务,就没法暂停了。

  4. React 总是在 componentWillMount 之后立即执行 render。http 不管多快得到结果也赶不上首次 render,fetch 数据在 componentWillMount 调用的时候几乎是不可用,那么第一次 render 也仍然显示加载状态。这就是为什么将数据获取移到 componentDidMount 是感受不到差别的。

render

render()函数应该纯净,它不应该改变组件的状态,其每次调用都应返回相同的结果。保持render() 方法纯净使得组件更容易思考。

render()

componentDidMount

在组件被装配后立即调用。初始化使得DOM节点应该进行到这里。若你需要从远端加载数据,这是一个适合实现网络请求的地方。在该方法里设置状态将会触发重渲。

componentDidMount()

更新过程

UNSAFE_componentWillReceiveProps

UNSAFE_componentWillReceiveProps(nextProps)

在装配了的组件接收到新属性前调用。

只要是父组件的 render 函数被调用,在 render 函数中的子组件就会经历更新过程,不管传给子组件的 props 有没有改变,都会触发子组件的 componentWillReceiveProps 函数。

// Before
class ExampleComponent extends React.Component {
  state = {
    isScrollingDown: false,
  };

  componentWillReceiveProps(nextProps) {
    if (this.props.currentRow !== nextProps.currentRow) {
      this.setState({
        isScrollingDown:
          nextProps.currentRow > this.props.currentRow,
      });
    }
  }
}

虽然上面的代码本身没有问题,但 componentWillReceiveProps 经常被误用来解决当下 的一些问题。因此,这个方法将会被弃用。
从 16.3 版本开始,推荐使用新的 静态方法 getDerivedStateFromProps 来更新基于 props 的 state。(这个生命周期将在组件被创建后和每次接收到新 props 的时候被调用):

static getDerivedStateFromProps

// After
class ExampleComponent extends React.Component {
  // Initialize state in constructor,
  // Or with a property initializer.
  state = {
    isScrollingDown: false,
    lastRow: null,
  };

  static getDerivedStateFromProps(props, state) {
    if (props.currentRow !== state.lastRow) {
      return {
        isScrollingDown: props.currentRow > state.lastRow,
        lastRow: props.currentRow,
      };
    }

    // Return null to indicate no change to state.
    return null;
  }
}

你可能想知道为什么我们不简单地将先前的 props 作为参数传递给 getDerivedStateFromProps。我们在设计 API 时考虑过这个问题,但因为两点原因推翻这个想法:

  • prevProps 参数在第一次调用 getDerivedStateFromProps (实例化后) 时会是 null。 需要在每次访问 prevProps 时判断 if-not-null。
  • 在未来版本的 React 中,不传递先前的 props 到这个函数是释放内存的一步。(如果 react 不需要传递 prevProps 到生命周期,内存中就不需要保持 prevProps 的引用。)

shouldComponentUpdate

让React知道当前状态或属性的改变是否不影响组件的输出。

shouldComponentUpdate(nextProps, nextState)

UNSAFE_componentWillUpdate

UNSAFE_componentWillUpdate(nextProps, nextState)

当接收到新属性或状态时,UNSAFE_componentWillUpdate()为在渲染前被立即调用。

render

getSnapshotBeforeUpdate

  getSnapshotBeforeUpdate(prevProps, prevState)

在最新的渲染输出提交给DOM前将会立即调用。它让你的组件能在当前的值可能要改变前获得它们。

class ScrollingList extends React.Component {
  listRef = null;
  previousScrollOffset = null;

  componentWillUpdate(nextProps, nextState) {
    // Are we adding new items to the list?
    // Capture the scroll position so we can adjust scroll later.
    if (this.props.list.length < nextProps.list.length) {
      this.previousScrollOffset =
        this.listRef.scrollHeight - this.listRef.scrollTop;
    }
  }

  componentDidUpdate(prevProps, prevState) {
    // If previousScrollOffset is set, we've just added new items.
    // Adjust scroll so these new items don't push the old ones out of view.
    if (this.previousScrollOffset !== null) {
      this.listRef.scrollTop =
        this.listRef.scrollHeight -
        this.previousScrollOffset;
      this.previousScrollOffset = null;
    }
  }

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

  setListRef = ref => {
    this.listRef = ref;jsx
  };
}

在上面的示例中, 在 componentWillUpdate 中读取 DOM 属性。但是在异步渲染中,这可能被延迟到 “render” 阶段(像 componentWillUpdate and render)和 “commit” 阶段(像 componentDidUpdate)才执行。如果用户在这期间做了一些类似调整窗口大小,从 componentWillUpdate 中读取的 scrollHeight 值会是旧的。

这个问题的解决方案就是使用新的 “commit” 阶段的生命周期事件,getSnapshotBeforeUpdate。这个方当会在变更产生后立即被调用(比如在 DOM 被更新之前)。它可以返回一个值并作为参数传到 componentDidUpdate

class ScrollingList extends React.Component {
  listRef = null;

  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) {
      return (
        this.listRef.scrollHeight - this.listRef.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) {
      this.listRef.scrollTop =
        this.listRef.scrollHeight - snapshot;
    }
  }

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

  setListRef = ref => {
    this.listRef = ref;
  };
}

componentDidUpdate

componentDidUpdate(prevProps, prevState)

会在更新发生后立即被调用。该方法并不会在初始化渲染时调用。

卸载过程

componentWillUnmount

componentWillUnmount()

在组件被卸载和销毁之前立刻调用。可以在该方法里处理任何必要的清理工作,例如解绑定时器,取消网络请求,清理任何在componentDidMount环节创建的DOM元素。

参考资料:

React.Component
React v16.3.0: New lifecycles and context API

@bigggge bigggge added the React label Sep 16, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant