
本文详细介绍了如何在react应用中实现级联选择器,即根据第一个下拉选择框(select)的选项变化,动态更新第二个select的选项。文章将通过`usestate`管理组件状态,并利用`useeffect`监听依赖项变化以触发异步数据请求,从而实现选项的动态加载和更新,提升用户交互体验。
在Web表单开发中,级联选择器是一种常见的交互模式,它允许用户在一个选择器中做出选择后,动态地影响另一个或多个选择器的可用选项。例如,选择一个“菜单类型”(如“主菜单”或“页脚菜单”)后,“父菜单”的选项列表应仅显示该类型下可用的父级菜单。
在您提供的React代码中,存在两个关键的select元素:
当前的问题是,table_id选择器的选项(通过menus.map(...)生成)是在组件首次加载时通过menuservice.getAll()一次性获取的。这意味着无论type选择什么,table_id的选项都不会随之变化。要实现级联效果,我们需要在type选择器值改变时,重新获取或过滤table_id的可用选项。
在React函数组件中,实现动态数据加载和状态管理的两个核心Hook是useState和useEffect。
为了实现根据type选择器动态更新table_id选择器的选项,我们将进行以下改造:
除了现有的type状态,我们需要一个新的状态来存储table_id选择器将展示的选项列表。
import { useEffect, useState } from "react";
import menuservice from "../../../services/MenuService";
function MenuCreate() {
// ... 其他状态
const [type, setType] = useState("");
const [table_id, setTable_id] = useState(0);
// 新增:用于存储根据type加载的父菜单选项
const [parentMenuOptions, setParentMenuOptions] = useState([]);
const [isLoadingParentMenus, setIsLoadingParentMenus] = useState(false); // 可选:加载状态
// ...
}type选择器的onChange事件处理器需要更新type状态。当type状态更新时,这将触发useEffect中的逻辑。
// ...
<select
name="type"
className="input"
value={type}
onChange={(e) => {
setType(e.target.value);
setTable_id(0); // 可选:当类型改变时,重置table_id为默认值
}}
>
<option disabled value="">--Chọn loại menu--</option> {/* 确保有空的value以便初始选择 */}
<option value="mainmenu">Menu chính</option>
<option value="footermenu">Menu footer</option>
</select>
// ...注意:为了确保select的value在初始时能够匹配option disabled,通常将option disabled的value设为""(空字符串),并确保type的初始状态也为""。
我们将创建一个异步函数来根据传入的菜单类型获取相应的父菜单数据。假设menuservice有一个方法getMenusByType(type)。
// ...
function MenuCreate() {
// ... 其他状态
const [type, setType] = useState("");
const [table_id, setTable_id] = useState(0);
const [parentMenuOptions, setParentMenuOptions] = useState([]);
const [isLoadingParentMenus, setIsLoadingParentMenus] = useState(false);
// 异步函数:根据类型获取父菜单选项
const fetchParentMenusByType = async (selectedType) => {
if (!selectedType) { // 如果没有选择类型,则清空选项
setParentMenuOptions([]);
return;
}
setIsLoadingParentMenus(true);
try {
// 假设menuservice有一个根据类型获取菜单的方法
// 如果没有,你可能需要修改后端API或在前端进行过滤
const result = await menuservice.getMenusByType(selectedType);
setParentMenuOptions(result.data);
} catch (error) {
console.error("Error fetching parent menus:", error);
setParentMenuOptions([]); // 发生错误时清空选项
} finally {
setIsLoadingParentMenus(false);
}
};
// ...
}重要提示:如果menuservice没有getMenusByType这样的方法,并且menuservice.getAll()返回所有菜单,你可以在fetchParentMenusByType函数内部对menus数组进行过滤。但这通常不如后端直接提供过滤接口效率高。
现在,我们使用useEffect来监听type状态。每当type的值改变时,fetchParentMenusByType函数就会被调用。
// ...
function MenuCreate() {
// ... 状态和 fetchParentMenusByType 函数
useEffect(() => {
fetchParentMenusByType(type);
}, [type]); // 依赖数组中包含type,当type变化时重新执行
// ...
}最后,修改table_id选择器,使其选项列表来源于parentMenuOptions状态。
// ...
<fieldset className="input-container">
<label htmlFor="table_id">Chọn menu cha</label>
<select
name="table_id"
className="input"
value={table_id}
onChange={(e) => setTable_id(e.target.value)}
>
<option disabled value="0">--Chọn menu cha--</option>
<option value="0">Không có cha</option>
{isLoadingParentMenus ? (
<option disabled>正在加载...</option>
) : (
parentMenuOptions.map((menu) => (
<option key={menu.id} value={menu.id}>
{menu.name}
</option>
))
)}
</select>
</fieldset>
// ...import { faBackward, faFloppyDisk } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, useNavigate } from "react-router-dom";
import { useEffect, useState, useCallback } from "react"; // 引入useCallback
import menuservice from "../../../services/MenuService";
function MenuCreate() {
const navigate = useNavigate();
const [name, setName] = useState("");
const [link, setLink] = useState("");
const [table_id, setTable_id] = useState(0);
const [type, setType] = useState(""); // 初始值为空字符串,匹配禁用选项
const [status, setStatus] = useState(1);
// 新增状态:用于存储根据type加载的父菜单选项
const [parentMenuOptions, setParentMenuOptions] = useState([]);
const [isLoadingParentMenus, setIsLoadingParentMenus] = useState(false); // 加载状态
// 异步函数:根据类型获取父菜单选项,使用 useCallback 避免不必要的重新创建
const fetchParentMenusByType = useCallback(async (selectedType) => {
if (!selectedType) {
setParentMenuOptions([]);
return;
}
setIsLoadingParentMenus(true);
try {
// 假设 menuservice 有一个根据类型获取菜单的方法
// 例如:await menuservice.getMenusByType(selectedType);
// 为演示目的,这里模拟数据或假设服务层有此功能
const result = await menuservice.getMenusByType(selectedType); // 请根据实际API调整
setParentMenuOptions(result.data);
} catch (error) {
console.error("Error fetching parent menus:", error.response?.data || error.message);
setParentMenuOptions([]); // 发生错误时清空选项
} finally {
setIsLoadingParentMenus(false);
}
}, []); // 依赖数组为空,函数只创建一次
// useEffect 监听 type 变化,并触发数据获取
useEffect(() => {
fetchParentMenusByType(type);
}, [type, fetchParentMenusByType]); // 依赖数组包含 type 和 fetchParentMenusByType
async function postStore(event) {
event.preventDefault();
const image = document.querySelector("#image");
var menu = new FormData();
menu.append("name", name);
menu.append("link", link);
menu.append("table_id", table_id);
menu.append("type", type);
menu.append("status", status);
// 检查 image.files[0] 是否存在,避免上传 undefined
if (image && image.files && image.files[0]) {
menu.append("image", image.files[0]);
}
try {
await menuservice.create(menu).then(function (res) {
alert(res.data.message);
navigate("../../admin/menu", { replace: true });
});
} catch (error) {
console.error(error.response.data);
}
}
return (
<section className="mainList">
<div className="wrapper">
<div className="card1">
<form method="post" onSubmit={postStore}>
<div className="card-header">
<strong className="title1">THÊM MENU</strong>
<div className="button">
<Link to="/admin/menu" className="backward">
<FontAwesomeIcon icon={faBackward} />
Go back
</Link>
<button type="submit" className="save">
<FontAwesomeIcon icon={faFloppyDisk} />
Save
</button>
</div>
</div>
<div className="form-container grid -bottom-3 ">
<div className="grid__item large--three-quarters">
<fieldset className="input-container">
<label htmlFor="name">Tên menu</label>
<input
name="name"
type="text"
className="input"
id="name"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Nhập tên menu..."
/>
</fieldset>
<fieldset className="input-container">
<label htmlFor="link">Đường dẫn menu</label>
<input
name="link"
type="text"
className="input"
id="link" // 修正id
value={link}
onChange={(e) => setLink(e.target.value)}
placeholder="Nhập đường dẫn..."
/>
</fieldset>
</div>
<div className="grid__item large--one-quarter">
<fieldset className="input-container">
<label htmlFor="type">Chọn loại menu</label>
<select
name="type"
className="input"
value={type}
onChange={(e) => {
setType(e.target.value);
setTable_id(0); // 当类型改变时,重置table_id为默认值
}}
>
<option disabled value="">--Chọn loại menu--</option>
<option value="mainmenu">Menu chính</option>
<option value="footermenu">Menu footer</option>
</select>
</fieldset>
<fieldset className="input-container">
<label htmlFor="table_id">Chọn menu cha</label>
<select
name="table_id"
className="input"
value={table_id}
onChange={(e) => setTable_id(parseInt(e.target.value))} // 确保table_id是数字类型
>
<option disabled value="0">--Chọn menu cha--</option>
<option value="0">Không có cha</option>
{isLoadingParentMenus ? (
<option disabled>Đang tải...</option>
) : (
parentMenuOptions.map((menu) => (
<option key={menu.id} value={menu.id}>
{menu.name}
</option>
))
)}
</select>
</fieldset>
<fieldset className="input-container">
<label htmlFor="status">Tình trạng xuất bản</label>
<select
name="status"
className="input"
value={status}
onChange={(e) => setStatus(parseInt(e.target.value))} // 确保status是数字类型
>
<option disabled value="">--Chọn tình trạng xuất bản--</option>
<option value="1">Xuất bản</option>
<option value="2">Không xuất bản</option>
</select>
</fieldset>
{/* 图像上传字段,如果需要保留 */}
<fieldset className="input-container">
<label htmlFor="image">Hình ảnh</label>
<input type="file" id="image" name="image" className="input" />
</fieldset>
</div>
</div>
</form>
</div>
</div>
</section>
);
}
export default MenuCreate;通过useState和useEffect的组合,我们成功实现了React中级联选择器的功能。当第一个选择器(type)的值发生变化时,useEffect会触发一个异步数据获取过程,根据新的type值从后端获取相应的选项,并更新第二个选择器(table_id)的选项列表。这种模式是处理React中动态数据加载和组件间交互的常用且高效的方法。
以上就是动态级联选择器:在React中根据一个Select改变另一个Select的选项的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号