如何在React Hook Form中对接Material UI Transfer List组件取值
实现方案
Material UI的Transfer List属于完全受控组件,更适合用react-hook-form的Controller组件来封装,而非register(register更适配原生表单元素)。完整实现步骤如下:
1. 依赖版本确认
确保项目已安装对应版本依赖:
react-hook-form任意稳定版本均可,以下示例以常用的v7版本为例@material-ui/corev4.3.3
2. 封装支持react-hook-form的受控Transfer组件
基于官方Transfer List示例改造,核心是通过Controller的field属性同步值和变更事件,直接接管左右侧数据状态:
import { Controller, useForm } from 'react-hook-form'; import React, { useState } from 'react'; import { List, ListItem, ListItemText, Checkbox, Button, Paper, Grid } from '@material-ui/core'; // 可复用的受控穿梭框组件 const ControlledTransfer = ({ allItems, field }) => { // 约定field.value结构为 { left: string[], right: string[] },分别存储左右侧选项值 const { left = allItems.map(i => i.value), right = [] } = field.value || {}; const [checked, setChecked] = useState([]); // 处理勾选逻辑 const handleToggle = (value) => { const currentIndex = checked.indexOf(value); const newChecked = [...checked]; currentIndex === -1 ? newChecked.push(value) : newChecked.splice(currentIndex, 1); setChecked(newChecked); }; // 全部移到右侧 const handleAllRight = () => { field.onChange({ left: [], right: [...right, ...left] }); setChecked([]); }; // 选中项移到右侧 const handleCheckedRight = () => { const moveItems = left.filter(item => checked.includes(item)); const newLeft = left.filter(item => !checked.includes(item)); const newRight = [...right, ...moveItems]; field.onChange({ left: newLeft, right: newRight }); setChecked([]); }; // 选中项移到左侧 const handleCheckedLeft = () => { const moveItems = right.filter(item => checked.includes(item)); const newRight = right.filter(item => !checked.includes(item)); const newLeft = [...left, ...moveItems]; field.onChange({ left: newLeft, right: newRight }); setChecked([]); }; // 全部移到左侧 const handleAllLeft = () => { field.onChange({ left: [...left, ...right], right: [] }); setChecked([]); }; // 列表渲染公共逻辑 const customList = (items) => ( <Paper style={{ height: 230, overflow: 'auto' }}> <List dense component="div" role="list"> {items.map((value) => { const label = allItems.find(i => i.value === value)?.label || value; const isChecked = checked.includes(value); return ( <ListItem key={value} role="listitem" button onClick={() => handleToggle(value)} > <Checkbox checked={isChecked} tabIndex={-1} disableRipple /> <ListItemText primary={label} /> </ListItem> ); })} </List> </Paper> ); return ( <Grid container spacing={2} justifyContent="center" alignItems="center"> <Grid item>{customList(left)}</Grid> <Grid item> <Grid container direction="column" alignItems="center"> <Button variant="outlined" size="small" onClick={handleAllRight} disabled={left.length === 0} aria-label="move all right" style={{ margin: 1 }} > ≫ </Button> <Button variant="outlined" size="small" onClick={handleCheckedRight} disabled={checked.filter(i => left.includes(i)).length === 0} aria-label="move selected right" style={{ margin: 1 }} > > </Button> <Button variant="outlined" size="small" onClick={handleCheckedLeft} disabled={checked.filter(i => right.includes(i)).length === 0} aria-label="move selected left" style={{ margin: 1 }} > < </Button> <Button variant="outlined" size="small" onClick={handleAllLeft} disabled={right.length === 0} aria-label="move all left" style={{ margin: 1 }} > ≪ </Button> </Grid> </Grid> <Grid item>{customList(right)}</Grid> </Grid> ); };
3. 表单内使用
通过Controller引用封装好的组件,提交时可直接拿到左右两侧的字符串数组:
const MyForm = () => { const { control, handleSubmit } = useForm({ defaultValues: { // 初始值默认所有选项在左侧 transferData: { left: [], right: [] } } }); const onSubmit = (data) => { // data.transferData 直接返回 { left: string[], right: string[] } 结构 console.log('表单提交数据', data); // 仅需要右侧列表时直接取 data.transferData.right 即可 }; // 所有可选项配置,label为显示文本,value为提交的字符串值 const transferOptions = [ { label: '选项1', value: 'opt1' }, { label: '选项2', value: 'opt2' }, { label: '选项3', value: 'opt3' }, { label: '选项4', value: 'opt4' }, ]; return ( <form onSubmit={handleSubmit(onSubmit)}> <Controller name="transferData" control={control} render={({ field }) => <ControlledTransfer allItems={transferOptions} field={field} />} /> <Button type="submit" variant="contained" color="primary" style={{ marginTop: 20 }}> 提交表单 </Button> </form> ); };
调整说明
- 如果不需要保留左侧列表的值,仅需要提交右侧选中列表,可以在
field.onChange时直接传入right数组,不需要包裹对象结构 - 若要添加校验规则,直接在Controller上添加
rules属性即可,和其他表单元素校验用法完全一致 - 如果使用的是react-hook-form v6及以下版本,把Controller render回调的
field参数改为input对应调整即可,其他逻辑不变
内容的提问来源于stack exchange,提问作者AM-314
相关产品推荐
相关产品推荐

