首页 > 解决方案 > React 状态数组似乎恢复到原始状态

问题描述

我有一个像这样的状态数组:

const [menusActive, setMenusActive] = useState([
    "MainMenu"
]);

它跟踪可见的菜单。我的方法是向函数toggleArrayItem()发送一个菜单键/名称,如果它不存在则将键添加到数组中,如果存在则将其删除:

function toggleArrayItem(arr, item) {
    console.log('item: ', item);
    console.log('arr: ', arr);
    let returnArray = [];
    if (arr.includes(item) === true) {
        console.log('removing item: ', item);
        returnArray = arr.filter(i => i !== item) // remove item
    } else {
        console.log('adding item: ', item);
        returnArray = [ ...arr, item ]; // add item
    }
    console.log('returnArray: ', returnArray);
    return returnArray;
}

单击菜单项时,它会执行以下操作:

    let existingMenu = [...menusActive];
    console.log('menu were sending to toggleArrayItem is: ', existingMenu);
    let updatedMenu = toggleArrayItem(existingMenu, menuName);
    setMenusActive(updatedMenu);

在调试时,我通过以下方式检查menuActive更新:

useEffect(() => {
    console.log('menusActive after: ', menusActive);
}, [menusActive]);

控制台输出在第一次菜单单击时有意义...

menu were sending to toggleArrayItem is:  ["MainMenu"]
MenuDock.js:36 item:  SubMenu
MenuDock.js:37 arr:  ["MainMenu"]
MenuDock.js:43 adding item:  SubMenu
MenuDock.js:46 returnArray:  (2) ["MainMenu", "SubMenu"]
MenuDock.js:54 menusActive after:  (2) ["MainMenu", "SubMenu"]

但是当我再次单击相同的菜单项时不会:

menu were sending to toggleArrayItem is:  ["MainMenu"]
MenuDock.js:36 item:  SubMenu
MenuDock.js:37 arr:  ["MainMenu"]
MenuDock.js:43 adding item:  SubMenu
MenuDock.js:46 returnArray:  (2) ["MainMenu", "SubMenu"]
MenuDock.js:54 menusActive after:  (2) ["MainMenu", "SubMenu"]

第二个数组项“SubMenu”在我尝试第二次菜单单击之前被声明为存在于menusActive中发生了什么(我认为这会删除“SubMenu”而不是再次尝试添加它)?


全组件

我在上面总结了我认为相关的内容,以免压倒一切。如果我遗漏了任何有用的东西,这里是完整的组件:


export const MenuDock = ({ side, menuJSON }) => {

    const [menuListItemArray, setMenuListItemArray] = useState([]);
    const [menusActive, setMenusActive] = useState([
        "MainMenu"
    ]);

    function handleItemClick(menuName) {
        if (menuName !== "none") {
            let existingMenu = [...menusActive];
            console.log('menu were sending to toggleArrayItem is: ', existingMenu);
            let updatedMenu = toggleArrayItem(existingMenu, menuName);
            setMenusActive(updatedMenu);
            
        }
    }


    function toggleArrayItem(arr, item) {
        console.log('item: ', item);
        console.log('arr: ', arr);
        let returnArray = [];
        if (arr.includes(item) === true) {
            console.log('removing item: ', item);
            returnArray = arr.filter(i => i !== item) // remove item
        } else {
            console.log('adding item: ', item);
            returnArray = [ ...arr, item ]; // add item
        }
        console.log('returnArray: ', returnArray);
        return returnArray;
    }



    useEffect(() => {
        // Toggle visible menus
        console.log('menusActive after: ', menusActive);
    }, [menusActive]);



    useEffect(() => {

        let jsxArray = [];

        let json2Array = [...menuJSON.MainMenu];
        let menuSort = [];
        menuSort['MainMenu'] = [];

        for (const [key, element] of Object.entries(json2Array)) {
            menuSort['MainMenu'].push(element);
            if (element.submenu !== "none" && element.submenu !== undefined) {
                let submenuKeys = Object.keys(element.submenu);
                let subKey = submenuKeys[0];
                menuSort[subKey] = [];
                element.submenu[subKey].forEach(subelement => {
                    menuSort[subKey].push(subelement);
                });

            }
        };

        for (const [key, menus] of Object.entries(menuSort)) {

            let itemArray = [];
            for (const [k2, items] of Object.entries(menuSort[key])) {

                function pcbValues(e) {
                    handleItemClick(items.submenukey);
                }
                
                itemArray.push(<MenuItem label={items.label} action={items.action} submenuKey={items.submenukey} handleClick={pcbValues} />)
            }
            jsxArray.push(<MenuList menuArray={itemArray} key={key} />);
        }

        setMenuListItemArray(jsxArray)
        
    }, []);



    return (
        <div className='storybook-menudock'>
            {menuListItemArray}
        </div>
    );
};

标签: javascriptreactjsreact-hooks

解决方案


问题是menusActive处理函数在其范围内的数组只是初始渲染中的数组。看这里:

useEffect(() => {
    let jsxArray = [];
    // other code omitted
    for (const [key, menus] of Object.entries(menuSort)) {
        let itemArray = [];
        for (const [k2, items] of Object.entries(menuSort[key])) {
            function pcbValues(e) {
                handleItemClick(items.submenukey);
            }
            itemArray.push(<MenuItem label={items.label} action={items.action} submenuKey={items.submenukey} handleClick={pcbValues} />)
        }
        jsxArray.push(<MenuList menuArray={itemArray} key={key} />);
    }
    setMenuListItemArray(jsxArray)
}, []);

有状态的menuListItemArray变化只发生一次:在组件挂载之后,在效果挂钩中。在效果挂钩运行并填充状态后,它会永远保持这种状态。这意味着 a MenuItemhere 的这一部分也永远不会改变:

function pcbValues(e) {
    handleItemClick(items.submenukey);
}
itemArray.push(<MenuItem label={items.label} action={items.action} submenuKey={items.submenukey} handleClick={pcbValues} />)

并且,在效果挂钩运行时,handleItemClick在组件顶部声明的 引用初始数组 - 的["MainMenu"],仅此而已。稍后调用setMenusActive不会改变这样一个事实,menusActive即 JSX(在初始渲染后立即创建)具有范围的处理函数只是初始有状态数组。

这里的这段代码是让 JSX 进入状态很容易导致问题的原因之一。更好的方法是将(可序列化的)普通数组、对象和原语放入状态,然后在组件结束时返回时将状态转换为 JSX。像这样的东西:

export const MenuDock = ({ side, menuJSON }) => {
    const [menuListObj, setMenuListObj] = useState([]);
    // ...
    useEffect(() => {

        let jsxArray = [];

        let json2Array = [...menuJSON.MainMenu];
        let menuSort = [];
        menuSort['MainMenu'] = [];

        for (const [key, element] of Object.entries(json2Array)) {
            menuSort['MainMenu'].push(element);
            if (element.submenu !== "none" && element.submenu !== undefined) {
                let submenuKeys = Object.keys(element.submenu);
                let subKey = submenuKeys[0];
                menuSort[subKey] = [];
                element.submenu[subKey].forEach(subelement => {
                    menuSort[subKey].push(subelement);
                });

            }
        };
        setMenuListObj(menuSort);
    }, []);
    return (
        <div className='storybook-menudock'>
            {
                Object.entries(menuListObj).map((key, menuItems) => <MenuList {...{ menuItems, key, handleItemClick }} />)
            }
        </div>
    );
};

MenuList组件在哪里获取menuItems, key, handleItemClick道具(现在保证来自最近的更新!)并将它们变成 JSX 本身(替换以前的内容<MenuItem label={items.label} action={items.action} submenuKey={items.submenukey} handleClick={pcbValues} />


推荐阅读