前言

前面两节实战中,我们已经完成了三个主要组件及其内部组件,但是我们展示的数据都是写死的内容,我们期望内容是实时更新的。

我们需要引入状态的概念,在 React 内组件可以通过维护 state 来维护组件内的状态,这意味着 state 只关心组件自己的内部状态,且这些状态只能在组件内改变。

state

定义 state 的方式很简单:

  1. class Demo extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. this.state = {
  5. name: 'frank',
  6. };
  7. }
  8. }

现在有新提案,对实例属性有了新的写法(需要 babel 转义)

  1. class Demo extends React.Component {
  2. state = {
  3. name: 'frank',
  4. };
  5. }

我们希望 state 是可以更改的,因为我们需要使用变化的 state 去实现动态可交互的组件。

怎么修改 state 呢?直接赋值?this.state.name = 'cxy'
no no no,永远不要直接修改 state,这样做 React 并不会做出你所期望的操作,还有可能带来一些意想不到的 bug。

正确的操作是使用 setState 修改 state。

setState

setState 是一个异步方法,例如:

  1. // state = {count: 0}
  2. console.log(this.state.count); // => 0
  3. this.setState({
  4. count: this.state.count + 1,
  5. });
  6. console.log(this.state.count); // => 0

出现这种情况是因为 react 并不会马上修改 state,而是将这个对象放入队列,且一个生命周期(生命周期相关内容可看 组件生命周期 章节)内所有的 setState 方法都会合并操作。
因此你并不需要担心写多个 setState 带来性能问题。

回过头来看上述例子,setState 允许我们传入第二个参数作为更新完 state 之后的回调,在这里我们可以获取到更新后的 state:

  1. console.log(this.state.count) // => 0
  2. this.setState({
  3. count: this.state.count + 1,
  4. }, () => {
  5. console.log(this.state.count) // => 1
  6. })

setState 接受函数参数

  1. // state = {count: 0}
  2. this.setState({
  3. count: this.state.count + 1,
  4. });
  5. this.setState({
  6. count: this.state.count + 2,
  7. });

看上述例子,我们期望得到结果3,实际上得到了2。
原因是第二个 setState 内的 count 依赖于 this.state.count,由于 setState 异步执行,this.state.count + 2 里的 count 仍然是0。

为了解决这种情况,我们将 setState 第一个参数改为函数返回对象的形式:

  1. this.setState(prevState => ({
  2. count: prevState.count + 1,
  3. }));
  4. this.setState(prevState => ({
  5. count: prevState.count + 2,
  6. }));

此时,prevState 即上一次 setState 的结果,这样我们就可以针对该结果进行操作,最后返回一个对象作为更新的 state。

总结

我们已经知道可以使用 state 配合 setState 维护组件内部状态,setState 可接受两个参数,且第一个参数可使用函数的形式 目的为获取上一次 setState 的结果。

使用 setState 可以使 React 变得充满想象力,然而在项目的复杂度不断提高的情况下,并不推荐滥用 setState,滥用 setState 将会是数据流变得混乱,程序维护成本增大。

实战

我们先改造 RealTime 组件。如下:

  1. class RealTime extends React.Component {
  2. constructor() {
  3. super();
  4. this.state = {
  5. temp: -5,
  6. weather: '晴',
  7. windType: '南风',
  8. windLevel: '3级',
  9. humidity: 48,
  10. };
  11. }
  12. render() {
  13. const {
  14. temp, weather, windType, windLevel, humidity,
  15. } = this.state;
  16. return (
  17. <div className="RealTime">
  18. <div className="temp">{temp}</div>
  19. <div className="weather">{weather}</div>
  20. <div className="wind">{`${windType} ${windLevel}`}</div>
  21. <div className="humidity">{`湿度 ${humidity}%`}</div>
  22. </div>
  23. );
  24. }
  25. }

现在 RealTime 组件变成了这样:

组件的 state 和 setState - 图1

好了组件已经能展示 state 的内容了,下一步我们来改变 state

首先我们希望能获取真实的数据,所以我们需要从真实的天气 api 取到数据并赋值给 state

注:此处使用网上获取的魅族天气的 api,仅供学习使用,严禁用于商业途径。

然后我们在组件上写一个方法 componentDidMount(此方法会在组件加载完毕之后立即执行,适合发送 http 请求) :

  1. componentDidMount() {
  2. // 如遇跨域问题 `create-react-app` 生成的项目可在 `package.json` 内新添
  3. // "proxy": "http://aider.meizu.com"
  4. fetch('/app/weather/listWeather?cityIds=101240101')
  5. .then(res => res.json())
  6. .then((res) => {
  7. if (res.code === '200' && res.value.length) {
  8. const { realtime } = res.value[0];
  9. const {
  10. temp, weather, wD: windType, wS: windLevel, sD: humidity,
  11. } = realtime;
  12. // 调用 setState 改变 state
  13. this.setState({
  14. temp,
  15. weather,
  16. windType,
  17. windLevel,
  18. humidity,
  19. });
  20. }
  21. });
  22. }

好了 现在已经可以从天气 api 取得数据并渲染到组件内。