如何用数组管理React状态(附代码示例)

330 阅读11分钟

在学习了如何在React中传递道具之后,接下来你将学习的是React中经常出现的状态。在React状态中管理字符串、布尔运算和整数等JavaScript基元是React中状态管理的基础。但更复杂的数据结构,如JavaScript数组,又该如何处理?对于React初学者来说,有很多关于如何在React状态中管理数组的问题冒出来。通常情况下,答案是抓住一个基本的JavaScript函数来为你做这个工作。这并不总是使用React来完成手头的任务。

本教程将带领你了解在React状态下管理数组的最常见场景。对于每一种情况,我想向你展示React状态中的数组例子,比如如何将一个项目推送到数组中,或者如何更新数组中的一个项目,当React状态被用来存储它。

React状态下的数组

在我们操作React状态中的JavaScript数组之前,让我们简单回顾一下React的状态。React中的状态可以在React组件的构造函数中被初始化,之后通过React组件的类实例与this 对象来使用它。

import React, { Component } from 'react';
class App extends Component {  constructor(props) {    super(props);
    this.state = {      list: [1, 2, 3],    };  }
  render() {    return (      <div>        <ul>          {this.state.list.map(item => (            <li key={item}>{item}</li>          ))}        </ul>      </div>    );  }}
export default App;

如前所述,初始数组状态是在React组件的构造函数中完成的,之后在render() 方法中使用来显示它。每当状态发生变化时,渲染方法将再次运行并在浏览器中显示正确的状态。然而,状态不应该通过直接突变this.state 来改变。相反,React组件上存在this.setState() 方法来更新React的状态。来自官方React文档的一段话说。

"永远不要直接突变this.state,因为事后调用setState()可能会取代你所做的突变。要把this.state当作不可变的。"

组件实例上的this.setState() 方法被用来更新React状态。它做了一个浅层的合并,这意味着当你更新状态中的一个属性(比如说列表)时,状态中的其他属性保持不变。

现在,让我们看看在React中用数组可以做什么样的状态操作,通过扩展前面的例子来看看不同的React状态数组例子。注意,在这种情况下,数组只是一个整数的列表。你可以用其他基元数组和对象数组来替代下面的例子。

React状态。空数组和初始数组状态

在谈到React的初始/空状态时,经常会出现以下三个问题:

  • 如何在React状态中初始化一个空数组?
  • 如何在React状态下推送一个空数组?
  • 如何创建一个初始数组React状态?

第一个问题可以用React的组件构造器来回答,只需将状态初始化为空数组即可。

import React, { Component } from 'react';
class App extends Component {  constructor(props) {    super(props);
    this.state = {      list: [],    };  }
  render() {    return (      <div>        <ul>          {this.state.list.map(item => (            <li key={item}>{item}</li>          ))}        </ul>      </div>    );  }}
export default App;

render() 方法中的map() 方法,用来显示数组的项目,确实有效,因为它在一个空数组上迭代,因此没有返回任何项目。map方法中的迭代器函数没有被调用。假设你的数组有一个空的状态,那么你就必须在你的渲染方法中为你的地图方法设置一个回退。

import React, { Component } from 'react';
class App extends Component {  constructor(props) {    super(props);
    this.state = {      list: null,    };  }
  render() {    return (      <div>        <ul>          {(this.state.list || []).map(item => (            <li key={item}>{item}</li>          ))}        </ul>      </div>    );  }}
export default App;

这只是为数组的空状态提供回退的一种方法。作为替代方案,你也可以使用React的条件渲染来实现。

第二个问题,问的是如何在React状态中推送一个空数组,涉及到用this.setState() 。假设你想在一个按钮点击时清空数组。那么你可以通过以下方式来实现。

import React, { Component } from 'react';
class App extends Component {  constructor(props) {    super(props);
    this.state = {      list: [1, 2, 3],    };  }
  onClearArray = () => {    this.setState({ list: [] });  };
  render() {    return (      <div>        <ul>          {this.state.list.map(item => (            <li key={item}>{item}</li>          ))}        </ul>
        <button type="button" onClick={this.onClearArray}>          Clear Array        </button>      </div>    );  }}
export default App;

你通过一个明确的方法来设置一个空数组作为组件的React状态。在这个例子的基础上,第三个问题,即如何创建一个初始数组状态,也可以得到回答。你已经看到了初始数组是如何在组件的构造函数中被设置为状态的。在你已经对状态进行了操作之后,你将如何再次重新初始化初始状态,基本上是对状态的重置?你可以提取初始状态,然后随时重新设置它。

import React, { Component } from 'react';
const list = [1, 2, 3];
class App extends Component {  constructor(props) {    super(props);
    this.state = {      list,    };  }
  onClearArray = () => {    this.setState({ list: [] });  };
  onResetArray = () => {    this.setState({ list });  };
  render() {    return (      <div>        <ul>          {this.state.list.map(item => (            <li key={item}>{item}</li>          ))}        </ul>
        <button type="button" onClick={this.onClearArray}>          Clear Array        </button>
        <button type="button" onClick={this.onResetArray}>          Reset Array        </button>      </div>    );  }}
export default App;

状态重置只适用于数组。但是你也可以通过提取整个初始状态对象,把它应用到你的整个状态。

import React, { Component } from 'react';
const INITIAL_STATE = {  list: [1, 2, 3],};
class App extends Component {  constructor(props) {    super(props);
    this.state = INITIAL_STATE;  }
  onClearArray = () => {    this.setState({ list: [] });  };
  onResetArray = () => {    this.setState({ ...INITIAL_STATE });  };
  render() {    return (      <div>        <ul>          {this.state.list.map(item => (            <li key={item}>{item}</li>          ))}        </ul>
        <button type="button" onClick={this.onClearArray}>          Clear Array        </button>
        <button type="button" onClick={this.onResetArray}>          Reset Array        </button>      </div>    );  }}
export default App;

通过使用JavaScript传播操作符,你可以将初始状态对象的所有键值对传播到组件的初始状态中。这也适用于状态对象中的多个属性。

React状态。添加项目到数组

最常见的问题之一是如何在React状态中向数组中添加一个项目。因为你不允许直接突变状态,你不能简单地推送一个项目到数组中。

import React, { Component } from 'react';
class App extends Component {  constructor(props) {    super(props);
    this.state = {      value: '',      list: ['a', 'b', 'c'],    };  }
  onChangeValue = event => {    this.setState({ value: event.target.value });  };
  onAddItem = () => {    // not allowed AND not working    this.setState(state => {      const list = state.list.push(state.value);
      return {        list,        value: '',      };    });  };
  render() {    return (      <div>        <ul>          {this.state.list.map(item => (            <li key={item}>{item}</li>          ))}        </ul>
        <input          type="text"          value={this.state.value}          onChange={this.onChangeValue}        />        <button          type="button"          onClick={this.onAddItem}          disabled={!this.state.value}        >          Add        </button>      </div>    );  }}
export default App;

首先,不允许使用数组推送方法,因为它是突变数组的。它不是让数组保持原样,而是改变它。相反,应该有一个新的数组被创建,用来更新状态。

第二,假设数组推送方法被允许,onAddItem 方法仍然不能工作。花点时间想一想为什么它不能这样工作。在我的研讨会上,我已经看到许多人在这个问题上犯了难。

答案就在这里。推送到数组的方法不会工作,因为它没有返回更新的数组。相反,它返回更新的数组的长度(在这个例子中是4)。

总而言之,数组推送方法对我们不起作用。幸运的是,存在一个很好的替代方法,它克服了数组推送方法的两个缺点。这就是数组连接法。它创建了一个新的数组,保留了旧的数组,但也从中返回一个新的数组。

import React, { Component } from 'react';
class App extends Component {  constructor(props) {    super(props);
    this.state = {      value: '',      list: ['a', 'b', 'c'],    };  }
  ...
  onAddItem = () => {    this.setState(state => {      const list = state.list.concat(state.value);
      return {        list,        value: '',      };    });  };
  ...}
export default App;

毕竟,当拥有不可变的数据结构(或者像React状态那样把它们当作不可变的)时,当涉及到数组时,concat是我们的朋友,而push是我们的敌人。

但仍有一个问题:如何在React状态中的数组的开头添加一个项目?和以前一样,这与React状态无关,而只是与执行正确的JavaScript功能有关。一种方法是交换之前的concat方法的位置(例如:const list = [state.value].concat(state.list); )。另一种方法是使用数组扩散运算符。让我们看看它是如何替代数组连接方法的。

import React, { Component } from 'react';
class App extends Component {  constructor(props) {    super(props);
    this.state = {      value: '',      list: ['a', 'b', 'c'],    };  }
  ...
  onAddItem = () => {    this.setState(state => {      const list = [...state.list, state.value];
      return {        list,        value: '',      };    });  };
  ...
}
export default App;

当前一个状态的数组被分散到一个新的数组中,所以前一个数组不会被改变,新的项目被添加到数组的末尾。现在,你可以通过使用const list = [state.value, ...state.list]; ,在数组的开始添加项目。

React状态。更新数组中的项目

在演练的这一部分,我们将通过两个案例来更新数组中的项目。

  • 如何在React状态下更新整个数组?
  • 如何在React状态下更新数组中的一个特定项?

在这两种情况下,数组映射方法是我们的朋友。数组协程用于向数组中添加一个项目,而数组映射方法则用于更新数组中的一个(多个)项目。它也会返回一个新的数组,因此不会改变之前的数组。让我们看看如何通过使用数组映射方法来更新整个数组。

import React, { Component } from 'react';
class App extends Component {  constructor(props) {    super(props);
    this.state = {      list: [42, 33, 68],    };  }
  onUpdateItems = () => {    this.setState(state => {      const list = state.list.map(item => item + 1);
      return {        list,      };    });  };
  render() {    return (      <div>        <ul>          {this.state.list.map(item => (            <li key={item}>The person is {item} years old.</li>          ))}        </ul>
        <button type="button" onClick={this.onUpdateItems}>          Make everyone one year older        </button>      </div>    );  }}
export default App;

数组映射方法接受一个函数作为参数。该函数被应用于数组的每一项,因此可以访问作为参数的每一项。在这种情况下,一个速记箭头函数被用来将数组中的整数增加1。一旦你点击按钮,数组中的每个项目都会受到这个更新的影响。正如你所看到的,数组映射方法在这里创造了奇迹。基本上它是对数组中的每个项目进行数组替换。

当只更新一个单项(人)的整数(年龄)时,你将如何实现同样的场景?你不需要更新整个数组,只需要更新其中的一个特定项目。让我们来看看这是怎么一回事。首先,你可以给每个项目(人)一个按钮来增加其整数(年龄)。

import React, { Component } from 'react';
class App extends Component {  constructor(props) {    super(props);
    this.state = {      list: [42, 33, 68],    };  }
  onUpdateItem = i => {    ...  };
  render() {    return (      <div>        <ul>          {this.state.list.map((item, index) => (            <li key={item}>              The person is {item} years old.              <button                type="button"                onClick={() => this.onUpdateItem(index)}              >                Make me one year older              </button>            </li>          ))}        </ul>      </div>    );  }}
export default App;

在这种情况下,你采取数组中项目的索引,以便以后在onUpdateItem() 类方法中识别它。通过在onClick 处理程序中使用一个包装箭头函数,你可以偷偷地把这个标识符放在类方法中使用。现在如何更新数组中的一个特定项目?我发现的最简单的方法是再次使用数组的map方法。

import React, { Component } from 'react';
class App extends Component {  constructor(props) {    super(props);
    this.state = {      list: [42, 33, 68],    };  }
  onUpdateItem = i => {    this.setState(state => {      const list = state.list.map((item, j) => {        if (j === i) {          return item + 1;        } else {          return item;        }      });
      return {        list,      };    });  };
  render() {    return (      <div>        <ul>          {this.state.list.map((item, index) => (            <li key={item}>              The person is {item} years old.              <button                type="button"                onClick={() => this.onUpdateItem(index)}              >                Make me one year older              </button>            </li>          ))}        </ul>      </div>    );  }}
export default App;

通过在map方法的迭代函数中添加条件逻辑,你可以修改单一的项目,但其他项目则保持原样。基本上,这是一个针对数组中特定项目的数组替换。

正如你所看到的,数组映射方法是改变数组中单个项目、多个项目或所有项目的完美选择。你不需要在之前通过索引提取一个项目,调整它,然后用它创建一个新的数组。相反,你只需遍历数组中的所有项目,并通过使用条件逻辑只调整需要的项目。

React状态。从数组中删除项目

当涉及到从数组中删除一个项目时,数组过滤器方法是你的朋友。

import React, { Component } from 'react';
class App extends Component {  constructor(props) {    super(props);
    this.state = {      list: [42, 33, 68],    };  }
  onRemoveItem = i => {    this.setState(state => {      const list = state.list.filter((item, j) => i !== j);
      return {        list,      };    });  };
  render() {    return (      <div>        <ul>          {this.state.list.map((item, index) => (            <li key={item}>              The person is {item} years old.              <button                type="button"                onClick={() => this.onRemoveItem(index)}              >                Remove              </button>            </li>          ))}        </ul>      </div>    );  }}
export default App;

与其他数组方法类似,过滤器方法使用一个函数作为参数,决定一个项目是留在数组中还是被移除。

对于一种情况,还有一个巧妙的小技巧。如果你想删除一个数组中的第一个项目,你可以用数组析构操作符来做。让我们看看如何在点击按钮时删除数组的第一项。

import React, { Component } from 'react';
class App extends Component {  constructor(props) {    super(props);
    this.state = {      list: [42, 33, 68],    };  }
  onRemoveFirstItem = () => {    this.setState(state => {      const [first, ...rest] = state.list;
      return {        list: rest,      };    });  };
  render() {    return (      <div>        <ul>          {this.state.list.map(item => (            <li key={item}>The person is {item} years old.</li>          ))}
          <button type="button" onClick={this.onRemoveFirstItem}>            Remove First Item          </button>        </ul>      </div>    );  }}
export default App;

就是这样。你对数组中的第一个项目和所有剩余的项目进行解构。然后你使用所有剩余的项目,将它们存储在React的状态中。第一个项目就不再使用了。

之前所有的数组例子都是在一个整数数组上工作的。让我们在一个更复杂的场景中看看如何从React状态数组中移除一个对象,而不是。这个例子更适合于强大的应用程序,因为我们可以在标识符上工作,而不是索引。

import React, { Component } from 'react';
class App extends Component {  constructor(props) {    super(props);
    this.state = {      list: [        { id: '1', age: 42 },        { id: '2', age: 33 },        { id: '3', age: 68 },      ],    };  }
  onRemoveItem = id => {    this.setState(state => {      const list = state.list.filter(item => item.id !== id);
      return {        list,      };    });  };
  render() {    return (      <div>        <ul>          {this.state.list.map(item => (            <li key={item.id}>              The person is {item.age} years old.              <button                type="button"                onClick={() => this.onRemoveItem(item.id)}              >                Remove              </button>            </li>          ))}        </ul>      </div>    );  }}
export default App;

这个例子告诉你,在使用之前应用的JavaScript数组方法时,对于你是在处理基元还是对象,并没有任何区别。你仍然会使用数组连接方法来添加一个项目,使用数组映射方法来更新项目,使用数组过滤方法来删除一个项目。然而,这次为每个项目设置一个标识符,使你对数组有更多的控制,而不是像以前那样只用索引来操作数组。


你已经了解了如何用数组来管理React状态的不同方法。你可以向数组中添加项目,更新数组中的一个项目或更新整个数组,以及从数组中删除项目。一切都可以用JavaScript实现,而不需要用React来实现。React最后只用于设置状态。这可以归结为了解基本的JavaScript数组方法,如concat, map, filter和reduce。