原文: React Tutorial – How to Work with Multiple Checkboxes

在 React 中处理多个复选框与使用常规 HTML 复选框的方式完全不同。

所以在本文中,我们将看到如何在 React 中使用多个复选框。

你将学习:

  • 如何在 React 中使用复选框作为受控输入
  • 如何使用数组 map 和 reduce 方法进行复杂计算
  • 如何创建预先填充了某些特定值的特定长度的数组

以及更多。

本文是我的 Mastering Redux 课程的一部分。这是我们将在课程中构建的应用程序的预览。

让我们开始吧。

如何使用单个复选框

让我们从单个复选框功能开始,然后再转到多个复选框。

在本文中,我将使用 React Hooks 语法来创建组件。因此,如果你不熟悉 React Hooks,请查看我的 React Hooks 简介文章

看看下面的代码:

<div className="App">
  Select your pizza topping:
  <div className="topping">
    <input type="checkbox" id="topping" name="topping" value="Paneer" />Paneer
  </div>
</div>

这是一个代码示例

在上面的代码中,我们只声明了一个复选框,类似于我们声明 HTML 复选框的方式。

因此,我们可以轻松地选中和取消选中复选框,如下所示:

check_uncheck-1

但是要在屏幕上显示它是否被选中,我们需要将其转换为受控输入。

在 React 中,受控输入由状态管理,因此只能通过更改与该输入相关的状态来更改输入值。

看看下面的代码:

export default function App() {
  const [isChecked, setIsChecked] = useState(false);

  const handleOnChange = () => {
    setIsChecked(!isChecked);
  };

  return (
    <div className="App">
      Select your pizza topping:
      <div className="topping">
        <input
          type="checkbox"
          id="topping"
          name="topping"
          value="Paneer"
          checked={isChecked}
          onChange={handleOnChange}
        />
        Paneer
      </div>
      <div className="result">
        Above checkbox is {isChecked ? "checked" : "un-checked"}.
      </div>
    </div>
  );
}

这是一个代码示例

在上面的代码中,我们使用 useState 钩子在组件中声明了 isChecked 状态,初始值为 false

const [isChecked, setIsChecked] = useState(false);

然后对于输入复选框,我们添加两个 prop,即 checkedonChange

<input
  ...
  checked={isChecked}
  onChange={handleOnChange}
/>

每当我们单击复选框时,将调用 handleOnChange 函数,我们使用它来设置 isChecked 状态的值。

const handleOnChange = () => {
  setIsChecked(!isChecked);
};

因此,如果复选框被选中,我们将 isChecked 值设置为 false。但是如果未选中该复选框,我们将使用 !isChecked 将值设置为 true。然后我们将该值传递给 prop checked 的输入复选框。

这样,输入复选框成为受控输入,其值由状态管理。

请注意,在 React 中,即使代码看起来很复杂,也始终建议对输入字段使用受控输入。这保证了输入更改仅发生在 onChange 程序内部。

输入的状态不会以任何其他方式更改,你将始终获得输入状态的正确的和更新的值。

只有在极少数情况下,你可以使用 React ref 以不受控制的方式使用输入。

如何处理多个复选框

现在,让我们看看如何处理多个复选框。

看一下这个代码示例

multiple_checkboxes-2

在这里,我们显示了配料列表及其相应的价格。根据选择的配料,我们需要显示总量。

以前,对于单个复选框,我们只有 isChecked 状态,我们根据它更改了复选框的状态。

但是现在我们有很多复选框,因此为每个复选框添加多个 useState 调用是不切实际的。

因此,让我们在 state 中声明一个数组,指示每个复选框的状态。

要创建一个等于复选框数量长度的数组,我们可以使用数组 fill 方法,如下所示:

const [checkedState, setCheckedState] = useState(
    new Array(toppings.length).fill(false)
);

在这里,我们将具有初始值的状态声明为填充值 false 的数组。

因此,如果我们有 5 个配料,那么 checkedState 状态数组将包含 5 个 false 值,如下所示:

[false, false, false, false, false]

一旦我们选中/取消选中复选框,我们会将相应的 false 更改为 true,并将 true 更改为 false

这是最终的代码示例

完整的 App.js 代码如下所示:

import { useState } from "react";
import { toppings } from "./utils/toppings";
import "./styles.css";

const getFormattedPrice = (price) => `$${price.toFixed(2)}`;

export default function App() {
  const [checkedState, setCheckedState] = useState(
    new Array(toppings.length).fill(false)
  );

  const [total, setTotal] = useState(0);

  const handleOnChange = (position) => {
    const updatedCheckedState = checkedState.map((item, index) =>
      index === position ? !item : item
    );

    setCheckedState(updatedCheckedState);

    const totalPrice = updatedCheckedState.reduce(
      (sum, currentState, index) => {
        if (currentState === true) {
          return sum + toppings[index].price;
        }
        return sum;
      },
      0
    );

    setTotal(totalPrice);
  };

  return (
    <div className="App">
      <h3>Select Toppings</h3>
      <ul className="toppings-list">
        {toppings.map(({ name, price }, index) => {
          return (
            <li key={index}>
              <div className="toppings-list-item">
                <div className="left-section">
                  <input
                    type="checkbox"
                    id={`custom-checkbox-${index}`}
                    name={name}
                    value={name}
                    checked={checkedState[index]}
                    onChange={() => handleOnChange(index)}
                  />
                  <label htmlFor={`custom-checkbox-${index}`}>{name}</label>
                </div>
                <div className="right-section">{getFormattedPrice(price)}</div>
              </div>
            </li>
          );
        })}
        <li>
          <div className="toppings-list-item">
            <div className="left-section">Total:</div>
            <div className="right-section">{getFormattedPrice(total)}</div>
          </div>
        </li>
      </ul>
    </div>
  );
}

让我们理解我们在这里做什么。

我们声明了输入复选框,如下所示:

<input
  type="checkbox"
  id={`custom-checkbox-${index}`}
  name={name}
  value={name}
  checked={checkedState[index]}
  onChange={() => handleOnChange(index)}
/>

在这里,我们从 checkedState 状态添加了一个 checked 属性,对应的值为 truefalse。因此,每个复选框都将具有其选中状态的正确值。

我们还添加了一个 onChange 程序,并将选中/取消选中的复选框的索引 index 传递给 handleOnChange 方法。

handleOnChange 程序方法如下所示:

const handleOnChange = (position) => {
  const updatedCheckedState = checkedState.map((item, index) =>
    index === position ? !item : item
  );

  setCheckedState(updatedCheckedState);

  const totalPrice = updatedCheckedState.reduce(
    (sum, currentState, index) => {
      if (currentState === true) {
        return sum + toppings[index].price;
      }
      return sum;
    },
    0
  );

  setTotal(totalPrice);
};

在这里,我们首先使用数组 map 方法遍历 checkedState 数组。如果传递的 position 参数的值与当前 index 匹配,那么我们反转它的值。然后,如果值为 true,则使用 !item 将其转换为 false,如果值为 false,则将其转换为 true

如果 index 与提供的 position 参数不匹配,那么我们不会反转它的值,而只是按原样返回值。

const updatedCheckedState = checkedState.map((item, index) =>
  index === position ? !item : item
);

// 上面的代码与下面的代码相同

const updatedCheckedState = checkedState.map((item, index) => {
  if (index === position) {
    return !item;
  } else {
    return item;
  }
});

我使用了三元运算符 ?:,因为它使代码更短,但你可以使用任何数组方法。

如果你不熟悉 mapreduce 等数组方法的工作原理,请查看我写的这篇文章

接下来,我们将 checkedState 数组设置为 updatedCheckedState 数组。这很重要,因为如果你不更新 handleOnChange 程序中的 checkedState 状态,那么你将无法选中/取消选中该复选框。

这是因为我们使用复选框的 checkedState 值来确定复选框是否被选中(因为它是一个受控输入,如下所示):

<input
  type="checkbox"
  ...
  checked={checkedState[index]}
  onChange={() => handleOnChange(index)}
/>

请注意,我们创建了一个单独的 updatedCheckedState 变量,并将该变量传递给 setCheckedState 函数。我们在 updatedCheckedState 上使用 reduce 方法,而不是在原始的 checkState 数组上。

这是因为默认情况下,用于更新状态的 setCheckedState 函数是异步的。

仅仅因为你调用了 setCheckedState 函数并不能保证你将在下一行中获得 checkedState 数组的更新值。

所以我们创建了一个单独的变量并在 reduce 方法中使用它。

如果你不熟悉 React 中状态的工作原理,可以阅读这篇文章

然后为了计算总价,我们使用数组 reduce 方法:

const totalPrice = updatedCheckedState.reduce(
  (sum, currentState, index) => {
    if (currentState === true) {
      return sum + toppings[index].price;
    }
    return sum;
  },
  0
);

数组 reduce 方法接收四个参数,其中我们只使用了三个:sumcurrentStateindex。如果需要,你可以使用不同的名称,因为它们只是参数。

我们还将 0 作为初始值传递,也称为 sum 参数的 accumulator 值。

然后在 reduce 函数内部,我们检查 checkedState 数组的当前值是否为 true

如果为 true,则表示复选框已选中,因此我们使用 sum + toppings[index].price 添加相应 price 的值。

如果 checkedState 数组的值为 false,那么我们不会添加它的价格,而只是返回计算出的之前的 sum 值。

然后我们使用 setTotal(totalPrice) 将该 totalPrice 值设置为 total 状态。

通过这种方式,我们能够正确计算所选配料的总价格,如下所示:

toppings-1

这是上述代码的预览链接,你可以自己尝试一下。

谢谢阅读

大多数开发人员都难以理解 Redux 的工作原理。但是每个 React 开发人员都应该知道如何使用 Redux,因为行业项目大多使用 Redux 来管理大型项目。

因此,为了方便你学习,我推出了 Mastering Redux 课程。

在本课程中,你将从零开始学习 Redux,你还将使用 Redux 从零开始​​构建一个完整的食品订购应用程序。

单击下图加入课程并获得限时折扣优惠,并免费获得我的 Mastering Modern JavaScript 书籍。

banner

想要及时了解有关 JavaScript、React、Node.js 的内容吗?在 LinkedIn 上关注我