React-State & 生命周期

如何正确地使用 State

React 把组件看成是一个状态机(State Machines)。通过与用户的交互,实现不同状态,然后渲染 UI,让用户界面和数据保持一致。

也就是说,只需更新组件的 state,然后就可以根据新的 state 重新渲染用户界面。

State 与 props 类似,但是 state 是私有的,并且完全受控于当前组件。
https://codepen.io/gaearon/pen/amqdNA?editors=0010

不要直接修改 State

例如,此代码不会重新渲染组件:

// Wrong
this.state.comment = 'Hello';

应该使用 setState():

// Correct
this.setState({comment: 'Hello'});

构造函数是唯一可以给 this.state 赋值的地方。

State 的更新可能是异步的

出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用。

因为 this.props 和 this.state 可能会异步更新,所以你不要依赖他们的值来更新下一个状态。

例如,在计时器中此代码可能会无法更新counter:

// Wrong
this.setState({
  counter: this.state.counter + this.props.increment,
});

要解决这个问题,可以让 setState() 接收一个函数而不是一个对象。这个函数用上一个 state 作为第一个参数,将此次更新被应用时的 props 做为第二个参数:

// Correct
this.setState((state, props) => ({
  counter: state.counter + props.increment
}));

State 的更新会被合并

当你调用 setState() 的时候,React 会把你提供的对象合并到当前的 state。

所以当你的 state 包含多个变量时,你可以分别调用 setState() 来单独地更新它们。

constructor(props) {
    super(props);
    this.state = {
      posts: [],
      comments: []
    };
  }
componentDidMount() {
    fetchPosts().then(response => {
      this.setState({
        posts: response.posts
      });
    });

    fetchComments().then(response => {
      this.setState({
        comments: response.comments
      });
    });
  }

数据是向下流动的

不管是父组件或是子组件,都无法知道某个组件是有状态的还是无状态的,并且它们也并不关心它是函数组件还是 class 组件。

这就是为什么称 state 为局部的或是封装的的原因。除了拥有并设置了它的组件,其他组件都无法访问。

组件可以选择把它的 state 作为 props 向下传递到它的子组件中:

FormattedDate 组件会在其 props 中接收参数 date,但是组件本身无法知道它是来自于父组件的 state,或是props,还是手动输入的。

这通常会被叫做“自上而下”或是“单向”的数据流。任何的 state 总是所属于特定的组件,而且从该 state 派生的任何数据或 UI 只能影响树中“低于”它们的组件,只能向下流动。


组件的生命周期

我们可以为 class 组件声明一些特殊的方法,当组件挂载或卸载时就会去执行这些方法。

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {
  }

  componentWillUnmount() {
  }

  render() {
    return (
      

Hello, world!

It is {this.state.date.toLocaleTimeString()}.

); } }

每个组件都包含“生命周期方法”,你可以重写这些方法,以便于在运行过程中特定的阶段执行这些方法。

生命周期图谱

https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/

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

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

getDerivedStateFromProps(): 在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。
过时方法:componentWillMount()

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

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

render() 方法是 class 组件中唯一必须实现的方法,其他方法可以根据自己的需要来实现。

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

static getDerivedStateFromProps() :在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。

getDerivedStateFromProps返回一个对象来更新 state,或者返回 null 来表示新的 props 不需要任何 state 的更新。

class Example extends React.Component {
  static getDerivedStateFromProps(props, state) {
    // ...
    return null;
  }
}

getDerivedStateFromProps的存在只有一个目的:让组件在 props 变化时更新 state。
要保守使用派生 state。大部分使用派生 state 导致的问题,不外乎两个原因:1,直接复制 props 到 state 上;2,如果 props 和 state 不一致就更新 state。

class EmailInput extends Component {
  state = { email: this.props.email };

  render() {
    return ;
  }

  handleChange = event => {
    this.setState({ email: event.target.value });
  };

  componentWillReceiveProps(nextProps) {
    // 这会覆盖所有组件内的 state 更新!
    // 不要这样做。
    this.setState({ email: nextProps.email });
  }
}

state 的初始值是 props 传来的,当在 里输入时,修改 state。但是如果父组件重新渲染,我们输入的所有东西都会丢失!

class EmailInput extends Component {
  state = {
    email: this.props.email
  };

  componentWillReceiveProps(nextProps) {
    // 只要 props.email 改变,就改变 state
    if (nextProps.email !== this.props.email) {
      this.setState({
        email: nextProps.email
      });
    }
  }
  
  // ...
}

以上均为反面模式,建议的模式一个是使用完全可控的组件。从组件里删除 state,直接使用props.email,但是如果我们仍然想要保存临时的值,则需要父组件手动执行保存这个动作。
另外一个选择是让组件自己存储临时的 email state。在这种情况下,组件仍然可以从 prop 接收“初始值”,但是更改之后的值就和 prop 没关系了。

class EmailInput extends Component {
  state = { email: this.props.defaultEmail };

  handleChange = event => {
    this.setState({ email: event.target.value });
  };

  render() {
    return ;
  }
}

然后使用key这个特殊的 React 属性,当 key 变化时, React 会创建一个新的而不是更新一个既有的组件。每次 ID 更改,都会重新创建 EmailInput ,并将其状态重置为最新的 defaultEmail 值。


过时方法:componentWillReceiveProps()

shouldComponentUpdate() :当 props 或 state 发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用。根据 shouldComponentUpdate() 的返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响。

过时方法:componentWillUpdate()

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

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

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 (
      
{/* ...contents... */}
); } }

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

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

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

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

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

  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

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

  render() {
    if (this.state.hasError) {
      // 你可以渲染任何自定义的降级  UI
      return 

Something went wrong.

; } return this.props.children; }

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

  1. error —— 抛出的错误。
  2. info —— 带有 componentStack key 的对象,其中包含有关组件引发错误的栈信息
  componentDidCatch(error, info) {
    //
    logComponentStackToMyService(info.componentStack);
  }

逐步迁移过程

过时的组件生命周期往往会带来不安全的编码实践,具体函数如下:

componentWillMount()

componentWillReceiveProps()

componentWillUpdate()

这些生命周期方法经常被误解和滥用;此外,在异步渲染中,它们潜在的误用问题可能更大。

16.3:为不安全的生命周期引入别名,UNSAFE_componentWillMount、UNSAFE_componentWillReceiveProps 和 UNSAFE_componentWillUpdate。(旧的生命周期名称和新的别名都可以在此版本中使用。)

并且引入两个新的生命周期,静态的 getDerivedStateFromProps 和 getSnapshotBeforeUpdate。

**16.x **:为 componentWillMount、componentWillReceiveProps 和 componentWillUpdate 启用废弃告警。(旧的生命周期名称和新的别名都将在这个版本中工作,但是旧的名称在开发模式下会产生一个警告。)

17.0:删除 componentWillMount、componentWillReceiveProps 和 componentWillUpdate。(在此版本之后,只有新的 “UNSAFE_” 生命周期名称可以使用。)

你可能感兴趣的:(React-State & 生命周期)