在学习了如何在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。