【某厂实习】任务管理系统开发-react框架Cloudscape云景开发2.md

196 阅读6分钟

【某厂实习】任务管理系统开发-react框架Cloudscape云景开发2.md

项目难点

  1. 理清思路比较麻烦
  2. 表格配置比较麻烦
  3. 在使用useLocation时出现问题

github地址

github.com/Shinkai007/…

这个基本算是完了吧, 还差几个页面和逻辑, 接下来十天我估计不会碰这个项目了, 写毕设去了.

uselocation问题复现

// tableConfig.js
{
        id: "id",
        header: "id",
        cell: e => {
            console.log("Link state:", { event: e });
            return(
                <Link
                    to={{
                        pathname: `/kpi/event/${e.id}`,
                        state: { event: e }
                    }}
                >
                    {e.id}
                </Link>
            )
        },
        width: 110,
        minWidth: 110,
        sortingField: "id",
    }
  // EventDetail.js
const location = useLocation();
    console.log("EventDetail location:", location);

    const eventData = location.state?.event;

    if (!eventData) {
        return <div>No data available</div>;
    }

在这个地方, 我的event是可以正常打印的.

但是我跳转过后去接受就出现了错误. 我的location的state是null.

我正确跳转了 因为线面的No data avilable正确显示

<Router>
            <div>
                <Link to="/kpi">Go to KPI Table</Link>
                <Routes>
                    <Route path="/kpi" element={<KPITableView />} />
                    <Route path="/kpi/event/:id" element={<EventDetail />} />
                </Routes>
            </div>
        </Router>

跳转也没有问题

我之前使用了传送id 然后通过useParams接受, 成功了.

找到问题了

  1. 我是这样解决的: 使用最小复现实例方法来追踪定位bug来源
  2. 我使用了codesandbox.io这个沙盒来实现(这个沙盒直接提供了CRA框架)
  3. 引入reactrouterV6
  4. 创建组件Homepage.js, TestPage.js, NavigationButton.js

内容如下:

//Homepage.js
import React from 'react';
import { Link } from 'react-router-dom';

function HomePage() {
    return (
        <div>
            <h1>Home Page</h1>
            <Link to="/test">Go to Test Page with Link</Link>
        </div>
    );
}

export default HomePage;

//TestPage.js

import React from 'react';
import { useLocation } from 'react-router-dom';

function TestPage() {
    const location = useLocation();

    return (
        <div>
            <h1>Test Page</h1>
            <p>Received state value: {location.state ? location.state.value : "No value passed"}</p>
        </div>
    );
}

export default TestPage;

// NavigationButton.js
// import React from 'react';
// import { useNavigate } from 'react-router-dom';

// function NavigationButton() {
//     const navigate = useNavigate();

//     const goToTestPage = () => {
//         navigate('/test', { state: { value: 'This is a test value from button' } });
//     };

//     return <button onClick={goToTestPage}>Go to Test Page with Navigate</button>;
// }

// export default NavigationButton;
// import React from 'react';
// import { Link } from 'react-router-dom';

// function NavigationButton() {
//     return (
//         <Link 
//             to={{
//                 pathname: "/test",
//                 state: { value: 'This is a test value from Link' }
//             }}
//         >
//             Go to Test Page with Link
//         </Link>
//     );
// }

// export default NavigationButton;
import React from 'react';
import { Link } from 'react-router-dom';

function NavigationButton() {
    return (
        <Link 
            to="/test"
            state={{ value: 'This is a test value from Link' }}
        >
            Go to Test Page with Link
        </Link>
    );
}

export default NavigationButton;


// App.js

import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import HomePage from './HomePage';
import TestPage from './TestPage';
import NavigationButton from './NavigationButton';

function App() {
    return (
        <Router>
            <div>
                <NavigationButton />
                <Routes>
                    <Route path="/" element={<HomePage />} />
                    <Route path="/test" element={<TestPage />} />
                </Routes>
            </div>
        </Router>
    );
}

export default App;

我来解释一下

  1. 在app.js中 点击NavigationButton来判断如何传递对象可以获取到值.
  2. 在 v6中 useNavigate必须放在BroserRouter的后代里 stackoverflow.com/questions/7… 这里有源码解释
  3. 我这里本身不知道, 我把他写在了子组件里, 也可以正常运行.
  4. 这里关注NavigationButton组件的内容
  5. 刚开始我先这样写
import React from 'react';
import { useNavigate } from 'react-router-dom';

function NavigationButton() {
    const navigate = useNavigate();

    const goToTestPage = () => {
        navigate('/test', { state: { value: 'This is a test value from button' } });
    };

    return <button onClick={goToTestPage}>Go to Test Page with Navigate</button>;
}

可以传值

  1. 后面我改成这样
import React from 'react';
import { Link } from 'react-router-dom';

function NavigationButton() {
    return (
        <Link 
            to={{
                pathname: "/test",
                state: { value: 'This is a test value from Link' }
            }}
        >
            Go to Test Page with Link
        </Link>
    );
}

无法传值

  1. 锁定原因, 寻找Link和navigate传值的不同.

  2. 但是结果是可以互相使用, 查看link文档. 写法变了~

  3. v6的写法改成了 state变成Link的属性, 写法如下

import React from 'react';
import { Link } from 'react-router-dom';

function NavigationButton() {
    return (
        <Link 
            to="/test"
            state={{ value: 'This is a test value from Link' }}
        >
            Go to Test Page with Link
        </Link>
    );
}

export default NavigationButton;

这里记录一下:

使用useNavigate的原因是 在点击事件之后, 还需要处理额外的逻辑. 如果只需要传值就使用Link就好

Navigate的作用类似, 但是他是用来在额外逻辑触发后, 触发导航的.

table修改选中信息

// 修改选中信息.
onSelectionChange={({detail}) =>
    setSelectedItems(detail.selectedItems)
}
selectedItems={selectedItems}
    onSelectionChange={({detail}) => setSelectedMembers(detail.selectedItems)}
    selectedItems={selectedMembers}

高阶函数来优化代码

因为我需要给三个相同的table来生成函数, 我这里直接用高阶函数即可

function addNewProject() {
        const newProject = { name: "New Project", alt: "Description for new project" };
        setProjects(prevProjects => [...prevProjects, newProject]);
    }

    function editSelectedProject() {
        // 这里只是一个示例,你可能需要提供一个编辑界面或模态窗口来获取新的数据。
        const newData = { name: "Edited Project", alt: "Edited Description" };
        const updatedProjects = projects.map(project =>
            project === selectedProjects[0] ? newData : project
        );
        setProjects(updatedProjects);
    }

    function deleteSelectedProject() {
        const updatedProjects = projects.filter(project => project !== selectedProjects[0]);
        setProjects(updatedProjects);
        setSelectedProjects([]); // 重置选中的项目
    }

//这里有个问题, 不能匹配对象, 要使用id匹配

所以这样的代码会好一些

function createTableHandlers(data, setData, selected, setSelected) {
    return {
        addNewItem: (newItem) => {
            setData(prevData => [...prevData, newItem]);
        },

        editSelectedItem: (editedItem) => {
            if (selected.length > 0) {
                setData(prevData => prevData.map(item => item.id === selected[0].id ? editedItem : item));
            }
        },

        deleteSelectedItem: () => {
            if (selected.length > 0) {
                setData(prevData => prevData.filter(item => item.id !== selected[0].id));
                setSelected([]);
            }
        }
    }
}

然后创建

//创建三个表格的handler
    const projectsHandlers = createTableHandlers(projects, setProjects, selectedProjects, setSelectedProjects);
    const stepsHandlers = createTableHandlers(steps, setSteps, selectedSteps, setSelectedSteps);
    const membersHandlers = createTableHandlers(members, setMembers, selectedMembers, setSelectedMembers);


button绑定

<SpaceBetween direction="horizontal" size="xs">
                                <Button onClick={() => projectsHandlers.addNewItem({
                                    id: uuidv4(),
                                    name: "New Project",
                                    alt: "Description for new project"
                                })}>Create Project</Button>
                                <Button onClick={() => projectsHandlers.editSelectedItem({
                                    id: uuidv4(),
                                    name: "Edited Project",
                                    alt: "Edited Description"
                                })} disabled={selectedProjects.length === 0}>Edit</Button>
                                <Button onClick={projectsHandlers.deleteSelectedItem}
                                        disabled={selectedProjects.length === 0}>Delete</Button>
                            </SpaceBetween>

还有一个最容易bug的点.

trackby是判断根据哪一个唯一值进行比较的, 这里要改成id

image-20230820063204260

ok实现了.

bug和优化

表格重新渲染会导致表格宽度变化

  1. 关于列宽度的问题:
    • 如果你想要使用固定列宽度,确保 resizableColumns 属性设置为 true。当启用此属性时,表格使用 table-layout: fixed,列宽度将匹配由 widthminWidth 定义的值。
    • 如果 resizableColumns 设置为 true,且列的 width 值未定义,则它们基于首次渲染的表格内容进行计算。随后的渲染(例如,异步加载)不会影响列宽度。因此,当使用可调整大小的列时,建议为所有列定义有意义的默认宽度,并在用户手动调整列大小时使用 onColumnWidthsChange 事件更新它们。
    • 如果 resizableColumns 未激活(默认设置),表格使用 table-layout: auto。在这种情况下,呈现的列宽度取决于显示的内容,并且可能不匹配 widthmaxWidth 的值。当显示的内容发生变化时,列宽度会自动更新。
  2. 关于重新渲染和性能的问题:
    • 使用 trackBy 属性为表格的每一行指定一个唯一键。这对于性能优化很重要,因为React使用这些键来确定哪些行需要重新渲染。
    • 如果可能的话,考虑使用客户端操作(如在客户端进行过滤、分页和排序)或服务器端操作,具体取决于要显示的数据量和需求。
  3. 关于状态管理:
    • 表格组件的选择和排序状态是受控的。你需要显式设置属性和相应的事件监听器。
    • 使用 selectedItems 属性和 onSelectionChange 事件监听器来管理选择状态。
    • 使用 sortingColumnsortingDescending 属性以及 onSortingChange 事件监听器来管理排序状态。
  4. 关于编辑:
    • 当用户提交内联编辑时,可以使用 submitEdit 函数。如果返回一个promise,将在提交请求进行时保持加载状态。
  5. 关于容器宽度和无限更新循环的问题:
    • 表格组件会有条件地测量其宽度以应用样式。如果父容器是灵活的,这可能会导致一个无限的更新循环,并在用户界面中明显地产生组件闪烁。建议在一个容器中渲染表格,其宽度不由其内容决定。

因此在表格列配置加上宽度,然后再table里写上resizableColumns 即可

...props
resizableColumns={true}
{
            id: "description",
            header: "Description",
            cell: item => item.alt,
            sortingField: "alt",
            width: 170,
            minWidth: 165,
        }

点击按钮不好点, 如何点击一行进行选择当前行

                onRowClick={({detail}) => detail && detail.item && setSelectedProjects([detail.item])}