在许多网络应用中,我们经常会遇到一种形式,即在一个下拉菜单中选择一个选项会解锁另一个下拉菜单中的一组新选项。这些相互关联的下拉菜单通常被称为依赖或级联下拉菜单,在创建流畅且直观的表单填写体验中发挥着至关重要的作用。

无论是选择一个国家以显示相应的州,还是选择一个产品类别以显示特定项目,这些下拉菜单为每个人简化了复杂的选择。对于开发人员来说,实现依赖下拉菜单是一个将逻辑、可用性和动态数据处理结合起来的实际挑战。

在本教程中,您将学习如何在您的React应用程序中实现这种类型的下拉菜单。

目录

什么是依赖下拉菜单?

依赖下拉菜单是一种用户界面元素,在一个下拉菜单中的可用选项由另一个下拉菜单中的选择决定。例如,考虑这样一个情景,您有两个下拉菜单:

  1. 国家下拉菜单:用户选择一个国家。

  2. 城市下拉菜单:根据所选择的国家,第二个下拉菜单中可用城市的列表将相应地进行筛选。

这种交互对于需要复杂、上下文敏感的数据输入的表单至关重要。

依赖下拉菜单如何工作?

依赖下拉菜单通过根据在第一个下拉菜单中选择的值动态更新第二个下拉菜单的选项来工作。这种动态变化通常是通过:

  1. 监听用户输入:当用户在第一个下拉菜单中选择一个选项时,一个事件(通常是onChange)会触发一个函数来更新状态。

  2. 获取新数据: 这个更新后的状态可以用来过滤现有数据或者调用API来获取新的选项列表。

  3. 渲染新数据: 然后第二个下拉菜单会被更新为新的选项,为用户提供相关的选择。

在React中创建依赖下拉菜单的步骤

步骤1: 设置你的React项目

如果你是React的新手并希望跟着做,请看一下Vite文档,并按照步骤创建你的React项目。完成后回到这里,让我们继续构建。

如果你已经有一个React项目想要使用,那也很好。

步骤2: 构建组件结构

为简单起见,让我们假设我们正在构建一个两级依赖下拉菜单,第一个下拉菜单让你选择一个国家,第二个下拉菜单根据选择的国家显示城市。

此外,在国家下拉菜单中,我们将增加另一个选项,用于输入不包含在国家选项中的国家名称。用户随后可以在文本输入框中输入他们的国家名称。

首先,创建一个名为 DependentDropdown.jsDependentDropdown.jsx 的新文件。在这个文件中,定义一个名为 DependentDropdown 的函数组件。

现在我们将按照以下步骤构建我们的依赖下拉菜单:

声明用于存储数据的变量

我们需要创建用于国家和城市值的静态数据:

  // 静态国家数据
  const countries = [
    { id: 1, name: 'USA' },
    { id: 2, name: 'Canada' },
    { id: 3, name: 'Other' },
  ];

  // 对应国家的静态城市数据
  const cities = {
    USA: ['New York', 'Los Angeles', 'Chicago'],
    Canada: ['Toronto', 'Vancouver', 'Montreal'],
  };
  • countries 是一个包含对象的数组。每个对象具有 idname 属性。

  • cities 是一个对象,以国家名称作为键,值为城市数组。

声明状态变量

对于选择国家或城市中的任何一个,我们希望能够跟踪所选的值。我们还希望在选择国家后能够填充城市选项。为此,我们需要声明一些状态。

如果“状态”的概念对您来说是新的,您可以阅读我关于状态的文章这里

  const [selectedCountry, setSelectedCountry] = useState('');
  const [availableCities, setAvailableCities] = useState([]);
  const [selectedCity, setSelectedCity] = useState('');
  const [otherCountry, setOtherCountry] = useState('');
  • 声明了selectedCountry状态,并将其初始值设为一个空字符串。

  • 声明了availableCities状态,并将其初始值设为一个空数组。

  • 声明了selectedCity状态,并将其初始值设为一个空字符串。

  • 声明了otherCountry状态,并将其初始值设为一个空字符串。

处理事件

在下拉菜单中进行选择的过程中,我们希望执行一些操作。事件处理程序使我们能够在事件发生时执行这些操作,而在这种情况下,事件是onChange事件。

  const handleCountryChange = (e) => {
    const country = e.target.value;
    setSelectedCountry(country);
    setAvailableCities(cities[country] || []);
    setSelectedCity(''); 
     if (country !== 'Other') {
      setOtherCountry('');
    }
  };

以下是handleCountryChange函数的运行情况:

  • 获取下拉菜单中所选选项的值(选择的国家)。
  • setSelectedCountry 更新状态变量(selectedCountry)为新选择的国家。

  • cities[country]cities 对象中查找所选国家的城市列表。

    • 如果找到 cities[country],则将该城市列表设置为可用城市。

    • 如果未找到所选国家的城市(cities[country] 为未定义),则 || [] 确保使用空数组([])作为替代方案,避免在尝试显示城市时出现错误。

  • 当用户更改国家选择时,setSelectedCity 函数将selectedCity 重置为空字符串。

  • 如果所选国家不是“其他”,otherCountry 状态将重置为空字符串。这确保了如果用户之前在“其他”输入框中输入了内容,一旦他们选择不同的国家(例如,“美国”或“加拿大”),该文本将被清除。

对于“其他”国家选择,我们只需要跟踪输入框中输入的值。setOtherCountry 函数更新输入的值。 这就是它的实现方式:

  const handleOtherCountryChange = (e) => {
    setOtherCountry(e.target.value);
  };

在城市更改时,我们无需做太多工作,因为所选国家决定显示哪些城市。我们只需要将selectedCity 更新为下拉菜单中所选选项的值,即所选的城市。

在React中,更新函数负责更新状态变量,所以在这种情况下setSelectedCity处理这个。

handleCityChange函数将是:

  const handleCityChange = (e) => {
    setSelectedCity(e.target.value);
  };

返回JSX

DependentDropdown组件呈现三个主要元素:国家下拉菜单、城市下拉菜单和国家文本输入框。

在HTML中,下拉菜单是

为了控制国家

     <label htmlFor="country" className='font-bold'>Select Country: </label>
      <select id="country" value={selectedCountry} onChange={handleCountryChange}>
        <option value="">Select a country</option>
        {countries.map((country) => (
          <option key={country.id} value={country.name}>
            {country.name}
          </option>
        ))}
      </select>

另外,

  • 每个国家的name作为选项的文本显示。

  • 每个选项的key设置为国家的idvalue设置为国家的name

  • key属性有助于React在重新渲染时有效地管理列表。

根据选择的国家条件性地呈现城市下拉菜单。如果选择了“其他”国家选项,则显示一个文本输入字段,供用户指定国家。否则,如果选择了有效的国家,则显示具有相关选项的城市下拉菜单。

{selectedCountry === 'Other' ? (
        <>
          <label htmlFor="other-country" className='font-bold'>Please specify the country: </label>
          <input
            id="other-country"
            type="text"
            value={otherCountry}
            onChange={handleOtherCountryChange}
            placeholder="Enter country name"
          />
        </>
      ) : (
        selectedCountry && (
          <>
            <label htmlFor="city" className='font-bold'>Select City: </label>
            <select id="city" value={selectedCity} onChange={handleCityChange}>
              <option value="">Select a city</option>
              {availableCities.map((city, index) => (
                <option key={index} value={city}>
                  {city}
                </option>
              ))}
            </select>
          </>
        )
      )
}

另外:

  • 我们检查selectedCountry是否为“其他”选项,并显示一个文本输入框。

  • 文本输入框具有otherCountry状态,并附加了handleOtherCountryChange处理函数。

  • 我们使用value属性控制城市<select>元素,将其设置为selectedCity状态变量。还附加了事件处理程序handleCityChange来处理onChange事件。

  • 我们遍历availableCities数组,并为数组中的每个城市动态创建一个<option>

  • 每个选项的key设置为一个indexvalue设置为city

  • 每个城市显示为选项的文本。

这就是我们需要做的一切,就可以使用我们的静态数据创建一个功能性的依赖下拉菜单。

以下是整合在一起的所有代码:

import React, { useState } from 'react';

const DependentDropdown = () => {
  // 静态国家数据
  const countries = [
    { id: 1, name: 'USA' },
    { id: 2, name: 'Canada' },
    { id: 3, name: 'Other' },
  ];

  // 对应国家的静态城市数据
  const cities = {
    USA: ['New York', 'Los Angeles', 'Chicago'],
    Canada: ['Toronto', 'Vancouver', 'Montreal'],
  };

  // 用于保存所选国家、城市和其他国家文本的状态
  const [selectedCountry, setSelectedCountry] = useState('');
  const [availableCities, setAvailableCities] = useState([]);
  const [selectedCity, setSelectedCity] = useState('');
  const [otherCountry, setOtherCountry] = useState(''); 

  // 处理国家更改
  const handleCountryChange = (e) => {
    const country = e.target.value;
    setSelectedCountry(country);
    setAvailableCities(cities[country] || []);
    setSelectedCity(''); 
    if (country !== 'Other') {
      setOtherCountry('');
    }
  };

  // 处理城市更改
  const handleCityChange = (e) => {
    setSelectedCity(e.target.value);
  };

  // 处理其他国家输入更改
  const handleOtherCountryChange = (e) => {
    setOtherCountry(e.target.value);
  };

  return (
    <div className='text-center text-3xl'>
      <h1 className='font-extrabold text-5xl p-10'>Dependent Dropdown Example</h1>

      {/* 国家下拉菜单 */}
      <label htmlFor="country" className='font-bold'>Select Country: </label>
      <select id="country" value={selectedCountry} onChange={handleCountryChange}>
        <option value="">Select a country</option>
        {countries.map((country) => (
          <option key={country.id} value={country.name}>
            {country.name}
          </option>
        ))}
      </select>

      {/* 城市或其他国家输入 */}
      {selectedCountry === 'Other' ? (
        <>
          <label htmlFor="other-country" className='font-bold'>Please specify the country: </label>
          <input
            id="other-country"
            type="text"
            value={otherCountry}
            onChange={handleOtherCountryChange}
            placeholder="Enter country name"
          />
        </>
      ) : (
        selectedCountry && (
          <>
            <label htmlFor="city" className='font-bold'>Select City: </label>
            <select id="city" value={selectedCity} onChange={handleCityChange}>
              <option value="">Select a city</option>
              {availableCities.map((city, index) => (
                <option key={index} value={city}>
                  {city}
                </option>
              ))}
            </select>
          </>
        )
      )}
    </div>
  );
};

export default DependentDropdown;

步骤 3:使用组件

要获得最终结果,您需要将 DependentDropdown 组件导入到您的 App.jsApp.jsx 中,并将其放置在 App 组件的返回部分内。

import DependentDropdown from './DependentDropdown'

function App() {

  return (
    <DependentDropdown/>
  )
}

export default App

不要忘记通过输入以下任一命令来运行应用程序:

npm start // 用于 create react app
npm run dev // 用于 react vite app

最后,这将在您的浏览器上呈现:

处理动态数据(API 请求)

在实际应用中,下拉菜单的列表可能不是静态的。相反,它们可能是从 API 获取的,或者充当 API 的 JSON 文件获取的。

在这个例子中,我们将从一个 JSON 文件中读取数据来填充我们的依赖下拉菜单。这种做法有一些好处,它们是:

  • 减少数据库负载:通过使用静态JSON文件(或预加载文件),您可以减少填充下拉框所需的数据库查询次数。如果下拉框选项相对静态且不经常更改,则这一点尤为有用。

  • 更快的UI渲染:由于数据已经在客户端,用户与下拉框交互时无需每次都向服务器发送往返请求。这可以使界面看起来更加响应。

我们的JSON文件包含州和LGA(地方政府区),它们相当于国家和城市。

JSON文件中的数据表示为对象数组,每个对象都有statealiaslgas的键。’lgas’键包含一个数组。

这是它的表示方式:

[
  {
    "state": "Adamawa",
    "alias": "adamawa",
    "lgas": [
      "Demsa",
      "Fufure",
      "Toungo",
      "Yola North",
      "Yola South"
    ]
  },
  {
    "state": "Akwa Ibom",
    "alias": "akwa_ibom",
    "lgas": [
      "Abak",
      "Uruan",
      "Urue-Offong/Oruko",
      "Uyo"
    ]
  },
//其余的对象
]

通过API创建动态依赖下拉框的方法与之前的示例并没有太大不同,只是进行了一些小修改。

以下是我们如何从JSON文件中获取和使用数据:

import React, { useEffect, useState } from "react";

function DependentDropdown() {
//声明全局状态变量
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

//使用useEffect钩子获取数据
  useEffect(() => {
    fetch("nigeria-state-and-lgas.json") //将JSON文件设置为URL
      .then((res) => res.json())
      .then((data) => {
        setData(data);
        setLoading(false);
      })
      .catch((error) => {
        console.error("Error fetching data:", error);
        setLoading(false);
      });
  }, []);
  return loading ? <div>Loading...</div> : <Form data={data} />;

}
//表单接收数据作为props
function Form({ data }) {

//声明局部状态变量
  const [selectedState, setSelectedState] = useState("");
  const [selectedLga, setSelectedLga] = useState("");
  const [showList, setShowList] = useState(false);
  let sortedData = data.slice().sort((a, b) => a.state.localeCompare(b.state));
  const selectedData = sortedData.find((item) => item.state === selectedState);

//状态的处理函数
  function handleClickState(e) {
    setSelectedState(e.target.value);
    setShowList(true);
  }
//LGA的处理函数
  function handleClickLga(e) {
    setSelectedLga(e.target.value);
  }

  return (
    <div>
  <form onSubmit={handleFormSubmit}>
    <div>
      {/* 名字 */}
      <div>
        <label htmlFor="firstName">First Name</label>
        <input type="text"
          id="firstName"
          name="firstName"
          placeholder="Enter your first name"/>
      </div>

      {/* 姓 */}
      <div>
        <label htmlFor="lastName">
          Last Name
        </label>
        <input
          type="text"
          id="lastName"
          name="lastName"
          placeholder="Enter your last name"/>
      </div>
    </div>

    <div>
      <div>
        <select value={selectedState} onChange={handleClickState} name="state">
          <option value="" disabled>Choose your state</option>
          {sortedData.map((data) => (
            <option key={data.alias} value={data.state}>
              {data.state}
            </option>
          ))}
        </select>
      </div>
      {selectedData && showList && (
        <select value={selectedLga} onChange={handleClickLga} name="lga">
          <option value="" disabled>{`Choose your LGA in ${selectedState}`}</option>
          {selectedData.lgas.map((lgass) => (
            <option key={lgass} value={lgass}>
              {lgass}
            </option>
          ))}
        </select>
      )}

    </div>
    <div>
        <button type="submit">
          Submit
        </button>
      </div>
  </form>
</div>
  );
}

export default DependentDropdown;

这里的关键修改是使用useEffect钩子进行数据获取,它只在初始渲染时获取州和LGA数据

以下是在浏览器上的渲染效果:

结论

在本教程中,您学会了如何在React中使用静态和动态数据创建依赖下拉框。现在您可以在React应用程序中使用这种类型的下拉框。

如果您发现本文有用,欢迎在LinkedIn上关注我,获取更多与编程相关的文章和帖子。

我们下次见!