您好,登录后才能下订单哦!
密码登录
登录注册
点击 登录注册 即表示同意《亿速云用户服务条款》
# React如何实现二级联动效果
## 一、前言:什么是二级联动
二级联动是指两个关联的下拉选择框,第一个选择框(父级)的选项变化会动态影响第二个选择框(子级)的可用选项。这种交互模式常见于:
- 省市级联选择
- 产品分类与子分类
- 学校与院系选择
- 日期时间选择器等
在React中实现这种效果需要综合运用状态管理、数据组织和事件处理等技术。本文将深入探讨5种实现方案,并提供完整的代码示例和性能优化建议。
## 二、基础实现方案
### 1. 使用useState管理状态
```jsx
import { useState } from 'react';
function CascadeSelect() {
// 原始数据
const data = [
{ id: 1, name: '电子产品', children: [
{ id: 11, name: '手机' },
{ id: 12, name: '电脑' }
]},
{ id: 2, name: '服装', children: [
{ id: 21, name: '男装' },
{ id: 22, name: '女装' }
]}
];
// 状态管理
const [selectedParent, setSelectedParent] = useState('');
const [childrenOptions, setChildrenOptions] = useState([]);
const [selectedChild, setSelectedChild] = useState('');
// 父级选择变化处理
const handleParentChange = (e) => {
const parentId = e.target.value;
setSelectedParent(parentId);
// 查找对应的子选项
const parentItem = data.find(item => item.id == parentId);
setChildrenOptions(parentItem ? parentItem.children : []);
setSelectedChild(''); // 重置子选择
};
return (
<div>
<select value={selectedParent} onChange={handleParentChange}>
<option value="">请选择父级</option>
{data.map(item => (
<option key={item.id} value={item.id}>{item.name}</option>
))}
</select>
<select
value={selectedChild}
onChange={(e) => setSelectedChild(e.target.value)}
disabled={!selectedParent}
>
<option value="">请选择子级</option>
{childrenOptions.map(item => (
<option key={item.id} value={item.id}>{item.name}</option>
))}
</select>
</div>
);
}
优点: - 实现简单直观 - 不依赖额外库 - 适合小型应用
缺点: - 状态分散管理 - 数据查找效率不高(O(n)) - 组件重新渲染次数较多
import { useReducer } from 'react';
const initialState = {
selectedParent: '',
selectedChild: '',
childrenOptions: []
};
function reducer(state, action) {
switch (action.type) {
case 'SELECT_PARENT':
const parentItem = action.data.find(item => item.id == action.payload);
return {
...state,
selectedParent: action.payload,
childrenOptions: parentItem ? parentItem.children : [],
selectedChild: ''
};
case 'SELECT_CHILD':
return { ...state, selectedChild: action.payload };
default:
return state;
}
}
function CascadeSelect({ data }) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<select
value={state.selectedParent}
onChange={(e) => dispatch({
type: 'SELECT_PARENT',
payload: e.target.value,
data
})}
>
{/* 选项渲染 */}
</select>
<select
value={state.selectedChild}
onChange={(e) => dispatch({
type: 'SELECT_CHILD',
payload: e.target.value
})}
>
{/* 子选项渲染 */}
</select>
</div>
);
}
import { createContext, useContext, useState } from 'react';
const CascadeContext = createContext();
function CascadeProvider({ children, data }) {
const [state, setState] = useState({
selectedParent: '',
selectedChild: '',
childrenOptions: []
});
const value = {
state,
setState,
data
};
return (
<CascadeContext.Provider value={value}>
{children}
</CascadeContext.Provider>
);
}
function ParentSelect() {
const { state, setState, data } = useContext(CascadeContext);
const handleChange = (e) => {
const parentId = e.target.value;
const parentItem = data.find(item => item.id == parentId);
setState(prev => ({
...prev,
selectedParent: parentId,
childrenOptions: parentItem?.children || [],
selectedChild: ''
}));
};
return (
<select value={state.selectedParent} onChange={handleChange}>
{/* 选项渲染 */}
</select>
);
}
function ChildSelect() {
const { state, setState } = useContext(CascadeContext);
return (
<select
value={state.selectedChild}
onChange={(e) => setState(prev => ({
...prev,
selectedChild: e.target.value
}))}
>
{/* 子选项渲染 */}
</select>
);
}
import { useMemo, useState } from 'react';
function useCascadeData(rawData) {
// 预处理数据:建立ID到数据的映射
const dataMap = useMemo(() => {
const map = new Map();
rawData.forEach(parent => {
map.set(parent.id, parent);
});
return map;
}, [rawData]);
const [state, setState] = useState({
selectedParent: '',
selectedChild: ''
});
// 记忆化子选项
const childrenOptions = useMemo(() => {
if (!state.selectedParent) return [];
const parent = dataMap.get(Number(state.selectedParent));
return parent?.children || [];
}, [state.selectedParent, dataMap]);
return { state, setState, childrenOptions };
}
import { FixedSizeList as List } from 'react-window';
const OptionList = ({ options, height = 200, onSelect }) => (
<List
height={height}
itemCount={options.length}
itemSize={35}
width={300}
>
{({ index, style }) => (
<div
style={style}
onClick={() => onSelect(options[index].id)}
>
{options[index].name}
</div>
)}
</List>
);
import axios from 'axios';
import { useEffect, useState } from 'react';
function RegionSelector() {
const [regions, setRegions] = useState([]);
const [cities, setCities] = useState([]);
const [districts, setDistricts] = useState([]);
const [selected, setSelected] = useState({
province: '',
city: '',
district: ''
});
useEffect(() => {
// 加载省级数据
axios.get('/api/provinces').then(res => {
setRegions(res.data);
});
}, []);
useEffect(() => {
if (!selected.province) return;
// 加载市级数据
axios.get(`/api/cities?province=${selected.province}`)
.then(res => {
setCities(res.data);
setSelected(prev => ({ ...prev, city: '' }));
});
}, [selected.province]);
useEffect(() => {
if (!selected.city) return;
// 加载区级数据
axios.get(`/api/districts?city=${selected.city}`)
.then(res => {
setDistricts(res.data);
setSelected(prev => ({ ...prev, district: '' }));
});
}, [selected.city]);
return (
<div className="region-selector">
<select
value={selected.province}
onChange={(e) => setSelected({
province: e.target.value,
city: '',
district: ''
})}
>
<option value="">选择省份</option>
{regions.map(province => (
<option key={province.code} value={province.code}>
{province.name}
</option>
))}
</select>
<select
value={selected.city}
onChange={(e) => setSelected(prev => ({
...prev,
city: e.target.value,
district: ''
}))}
disabled={!selected.province}
>
<option value="">选择城市</option>
{cities.map(city => (
<option key={city.code} value={city.code}>
{city.name}
</option>
))}
</select>
<select
value={selected.district}
onChange={(e) => setSelected(prev => ({
...prev,
district: e.target.value
}))}
disabled={!selected.city}
>
<option value="">选择区县</option>
{districts.map(district => (
<option key={district.code} value={district.code}>
{district.name}
</option>
))}
</select>
</div>
);
}
import { Formik, Field, Form } from 'formik';
function CascadeForm() {
const initialValues = {
category: '',
subcategory: '',
product: ''
};
return (
<Formik
initialValues={initialValues}
onSubmit={(values) => {
console.log('提交数据:', values);
}}
>
{({ values, setFieldValue }) => (
<Form>
<Field
name="category"
as="select"
onChange={(e) => {
setFieldValue('category', e.target.value);
setFieldValue('subcategory', '');
setFieldValue('product', '');
}}
>
{/* 分类选项 */}
</Field>
<Field
name="subcategory"
as="select"
disabled={!values.category}
onChange={(e) => {
setFieldValue('subcategory', e.target.value);
setFieldValue('product', '');
}}
>
{/* 子分类选项 */}
</Field>
<Field
name="product"
as="select"
disabled={!values.subcategory}
>
{/* 产品选项 */}
</Field>
<button type="submit">提交</button>
</Form>
)}
</Formik>
);
}
问题场景:子选项需要异步加载
const loadChildren = async (parentId) => {
try {
const response = await fetch(`/api/children?parent=${parentId}`);
return await response.json();
} catch (error) {
console.error('加载失败:', error);
return [];
}
};
// 在事件处理中使用
const handleParentChange = async (e) => {
const parentId = e.target.value;
setLoading(true);
const children = await loadChildren(parentId);
setChildrenOptions(children);
setLoading(false);
};
方案 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
useState | 简单联动、数据量小 | 实现简单 | 状态分散 |
useReducer | 复杂状态逻辑 | 状态集中管理 | 代码量稍大 |
Context | 跨组件通信 | 解耦组件 | 需要Provider包裹 |
数据预处理 | 大数据量 | 查找速度快 | 初始化耗时 |
虚拟滚动 | 超大数据量 | 渲染高效 | 实现复杂 |
数据组织建议:
状态管理原则:
用户体验优化:
可访问性考虑:
通过本文的详细讲解,相信您已经掌握了在React中实现二级联动的各种技术方案。根据实际项目需求选择最适合的实现方式,并注意性能优化和用户体验细节,就能构建出高效好用的联动选择组件。 “`
注:本文实际约4500字,完整达到4850字需要进一步扩展每个章节的详细说明和示例代码。如需完整版本,可以扩展以下内容: 1. 增加每种方案的适用场景分析 2. 添加更多实际业务场景案例 3. 深入性能优化章节的基准测试数据 4. 增加TypeScript实现版本 5. 添加可视化图表说明数据流动
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。