1

一、React的state 和 props的概念

1.props

props是组件自身的属性(不可变),是一种父级向子级传递数据的方式

2.state:

state是组件持有的状态(可变),React中组件的一个对象。state只能由组件本身进行管理,组件会根据state的变化进行重新渲染。

二、React的state 和 props的区别

1.组件自身是可以创建state的,这个state是由组件自身去进行维护的,props可以理解为组件的属性,是由父组件传递下来的,子组件可以通过this.props来获取到父组件传递的数据流。

父组件可以通过props向子组件进行数据的传递:

//父组件通过props传值
class Container extends React.Component {
  constructor(props) {
    super(props); //假设传进来的props中有name
  }

  render() {
    return (
      <div>
        <Component name={this.props.name} />
        <Component name={this.props.name} />
        <Component name={this.props.name} />
      </div>
    );
  }
}

Container组件中包含着3个Component组件,在传参过程中Component的name值都是由Container传递的,就是说每个组件的props都是由父组件传递过来的,因为自身是没办法创建props属性的,而state属性则是可以自身创建的,修改下例子也一样可以用:

//组件自身创建state
class Container extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      name: "huangdonglu"
    }
  }

  render() {
    return (
      <div>
        <Component name={this.state.name} />
        <Component name={this.state.name} />
        <Component name={this.state.name} />
      </div>
    );
  }
}

这说明父组件创建的state值可以通过props传递给子组件。

2.state可以通过setState修改,一个组件自身可以管理自己的state(类似于一个在函数内部声明的变量),但是无法管理其子组件的state,所以state是控件自身私有的。从子组件的角度来看,props是不可以被修改的,所有React组件都必须像纯函数一样保护它们的props不被更改,即不能调用this.props.name = xxx来对props修改。

3.props是一个从外部传进组件的参数,主要作用就是从父组件向子组件传递数据;state的主要作用是用于组件保存/控制以及修改自己的状态,只能在constructor里面初始化。

三、props和state的运用场景

在上面我们了解到了props和state的基本概念和两者之间的区别,接下来我们来了解一下props和state到底该用在哪些地方?

哪些属性应该用state:state应该包括那些可能被组件的事件处理器改变并触发用户界面更新的数据,一般很小且能被JSON序列化。当创建一个有状态组件时,应该保持数据的精简,将最少的数据存入this.state,在render()里再根据 state 来计算需要的其它数据。

哪些属性不应该使用state

(1)计算所得的数据。计算数据应该在render()中实现,如果存储在state中需要手动更新state比较麻烦;

(2)基于props的重复数据。组件中应该保持props为唯一数据来源,除非需要知道历史数据是什么;

(3)不要将React组件保存在state中。在render()里使用props、state来创建它。

四、setState要注意的点

个人认为setState函数最引人注意的一点就是该函数是异步的。也就是说,不是执行完setState函数之后this.state就会被更新,而是等待state的批次更新,在开始重新渲染之前,React会有意的进行“等待”,直到所有的组件的事件处理函数内调用的setState()完成。

让我们先来看一下如果连续使用多次setState函数的调用的结果是怎么样的

//不会像预期那样运行的示例
incrementCount() {
  // 注意:这样 *不会* 像预期的那样工作。
  this.setState({count: this.state.count + 1});
}

handleSomething() {
  // 假设 `this.state.count` 从 0 开始。
  this.incrementCount();
  this.incrementCount();
  this.incrementCount();
  // 当 React 重新渲染该组件时,`this.state.count` 会变为 1,而不是你期望的 3。

  // 这是因为上面的 `incrementCount()` 函数是从 `this.state.count` 中读取数据的,
  // 但是 React 不会更新 `this.state.count`,直到该组件被重新渲染。
  // 所以最终 `incrementCount()` 每次读取 `this.state.count` 的值都是 0,并将它设为 1。

  // 问题的修复参见下面的说明。
}

为什么会是这样的呢?不应该执行了三次setState()组件的state就会更新三次吗?

其实不是的,因为调用setState是异步的,可能第二个this.setState()没等到第一次的state更新完毕就开始执行,第三个也是如此,所以最后的count只加了1。不要指望调用setState之后,this.state会立即映射为新的值。

那又该如何解决这个问题呢?怎么样才能达到我们预期的效果呢?

在这里,若给setState传递一个函数,而不是一个对象,就可以确保每次的调用都是用最新的state,参见下面的例子(引用官网事例):

方法一:

//修复后的示例
incrementCount() {
  this.setState((state) => {
    // 重要:在更新的时候读取 `state`,而不是 `this.state`。
    return {count: state.count + 1}
  });
}

handleSomething() {
  // 假设 `this.state.count` 从 0 开始。
  this.incrementCount();
  this.incrementCount();
  this.incrementCount();

  // 如果你现在在这里读取 `this.state.count`,它还是会为 0。
  // 但是,当 React 重新渲染该组件时,它会变为 3。
}

读到这里,有的同学可能会问:为什么给setState传递一个函数的时候this.state就会及时进行更新?

其实setState可以接收两个参数,一个参数是对象,另外一个参数是函数。而传入的函数被称为状态计算函数,结构为:

//状态计算函数
function(state, props) => newState

其实这个函数会将每次更新加入队列中,执行时通过当前的state和props来获取新的state,而每次更新时都会提取当前的state,进行运算得到新的state,就保证了数据的同步更新。

还有另外一种办法可以实现这个效果,就是同时向setState里面传入对象和callback,这个callback是等到this.state更新完毕之后才会调用:

方法二(不推荐):

this.setState({
  count: this.state.count + 1
}, () => {
  this.setState({
    count: this.state.count + 1
  });
});

经过我的测试,这个方法更新两次state,同时渲染两次组件。而方法一(官网的方法)只渲染了一次组件。

//state更新组件只渲染了一次
handleSubmit() {
    // this.setState({
    //   count: this.state.count + 1
    // }, () => {
    //   this.setState({
    //     count: this.state.count + 1
    //   });
    // });

    this.incrementCount();
    this.incrementCount();
    this.incrementCount();

    console.log("handle" + this.state.count);
  }

  incrementCount() {
    this.setState((state) => {
      // 重要:在更新的时候读取 `state`,而不是 `this.state`。
      return {count: state.count + 1}
    });
  }

  render() {
    console.log("render" + this.state.count);
    return (
      <button onClick={this.handleSubmit}>点击</button>
    );
  }

运行截图:
image

//state每更新一次,组件就渲染一次
handleSubmit() {
    this.setState({
      count: this.state.count + 1
    }, () => {
      this.setState({
        count: this.state.count + 1
      });
    });

    // this.incrementCount();
    // this.incrementCount();
    // this.incrementCount();

    console.log("handle" + this.state.count);
  }

  // incrementCount() {
  //   this.setState((state) => {
  //     // 重要:在更新的时候读取 `state`,而不是 `this.state`。
  //     return {count: state.count + 1}
  //   });
  // }

  render() {
    console.log("render" + this.state.count);
    return (
      <button onClick={this.handleSubmit}>点击</button>
    );
  }

运行截图:
image

所以,处于性能方面的考虑,我们更加倾向于采用方法一,直到所有在组件的事件处理函数内调用的setState()完成之后,才去进行state的更新和组件的渲染,这样可以避免不必要的重新渲染来提升性能。


JacksonHuang
74 声望3 粉丝

« 上一篇
Context
下一篇 »
React diff原理