在React Admin中嵌套<List>实现带分页文件浏览器可行吗?
你的方案完全可行,不过有几个细节和替代方向可以参考
首先,直接把动态拼接的resource传给<List>组件的思路是通顺的——React-Admin的<List>在resource属性变化时,会自动重新触发数据请求,刚好匹配你切换目录后刷新文件列表的需求。只要你的数据提供器能正确解析这种带动态路径的resource(比如/users/${username}/dir/${usrPath}),这个方案就能正常跑起来。
不过使用这个方案时,有几个小细节需要注意:
- 路径规范化:避免出现
//file这种重复斜杠,或者处理返回上级目录的逻辑(比如添加一个返回按钮,点击后把usrPath从/docs/images改成/docs)。可以写个小工具函数来处理路径拼接:const handleEnterDir = (record) => { if (record.type !== 'dir') return; // 确保只有目录能进入 const newPath = usrPath === '/' ? `/${record.name}` : `${usrPath}/${record.name}`; setUsrPath(newPath); }; - 分页自动适配:
<List>和<Datagrid>自带分页功能,只要你的数据提供器能返回total(总条目数)和data(当前页数据),分页会自动生效,不用额外写逻辑。 - 删除后刷新:删除文件后,需要触发当前目录的列表刷新。你可以在删除按钮的回调里使用React-Admin的
useRefresh钩子,或者直接修改usrPath(比如加个无意义的后缀再改回来)来触发<List>重新请求数据。
如果你觉得直接用<List>容易遇到钩子依赖冲突(比如你之前提到的嵌套混乱),还有一个更灵活的替代方案:手动用useDataProvider获取数据,配合MUI组件实现自定义列表。这种方式完全脱离<List>的内部机制,适合复杂的自定义场景:
import { useDataProvider, useQuery, Loading, Error, Pagination } from 'react-admin'; import { Table, TableHead, TableRow, TableCell, TableBody, Button } from '@mui/material'; function HomedirBrowser({ username }) { const [usrPath, setUsrPath] = React.useState("/"); const [page, setPage] = React.useState(1); const [perPage, setPerPage] = React.useState(50); const dataProvider = useDataProvider(); const queryClient = useQueryClient(); // 手动发起数据请求,支持分页 const { data, total, isLoading, error } = useQuery( ['user-files', username, usrPath, page, perPage], // 缓存键,变化时重新请求 () => dataProvider.getList(`/users/${username}/dir/${usrPath}`, { pagination: { page, perPage }, sort: { field: 'name', order: 'ASC' }, filter: {}, }), { keepPreviousData: true } // 切换目录时保留旧数据,避免页面闪烁 ); const handleEnterDir = (record) => { if (record.type === 'dir') { const newPath = usrPath === '/' ? `/${record.name}` : `${usrPath}/${record.name}`; setUsrPath(newPath); setPage(1); // 切换目录后回到第一页 } }; const handleDelete = (record) => { dataProvider.deleteOne(`/users/${username}/dir/${usrPath}`, { id: record.id }) .then(() => { // 删除后刷新当前目录的缓存 queryClient.invalidateQueries(['user-files', username, usrPath]); }); }; if (isLoading) return <Loading />; if (error) return <Error />; return ( <div> {/* 可以在这里加当前路径导航,点击路径片段回到对应目录 */} <div style={{ marginBottom: 16 }}> Current Path: {usrPath} {usrPath !== '/' && ( <Button onClick={() => setUsrPath(usrPath.split('/').slice(0, -1).join('/') || '/')}> Go Back </Button> )} </div> <Table> <TableHead> <TableRow> <TableCell>Type</TableCell> <TableCell>Name</TableCell> <TableCell>Last Edit</TableCell> <TableCell>Size</TableCell> <TableCell>Actions</TableCell> </TableRow> </TableHead> <TableBody> {data?.map(record => ( <TableRow key={record.id}> <TableCell>{record.type}</TableCell> <TableCell>{record.name}</TableCell> <TableCell>{record.last_edit}</TableCell> <TableCell>{record.size}</TableCell> <TableCell> {record.type === 'dir' && ( <Button onClick={() => handleEnterDir(record)}>Enter</Button> )} <Button onClick={() => handleDelete(record)} color="error">Delete</Button> </TableCell> </TableRow> ))} </TableBody> </Table> <Pagination page={page} perPage={perPage} total={total} setPage={setPage} setPerPage={setPerPage} style={{ marginTop: 16 }} /> </div> ); }
这种方案的优势是完全自主控制数据流程,不会和<List>的内部钩子、上下文产生冲突,适合需要高度自定义的场景;缺点是需要自己处理加载、错误状态和分页逻辑,代码量稍大。
总的来说,你最初的方案是可行的,只要处理好路径和刷新逻辑就能正常工作;如果后续遇到<List>的嵌套问题,再考虑切换到手动数据获取的方案即可。
内容的提问来源于stack exchange,提问作者Adam Jędrzejowski
相关产品推荐
相关产品推荐

