【问题标题】:React doesn't rerender on an array state updateReact 不会在数组状态更新时重新渲染
【发布时间】:2020-11-24 08:17:38
【问题描述】:

我正在尝试制作 3 个按钮控件来打开和关闭 3 个相应的菜单列表。 Material-UI 是我目前使用的 UI 框架。代码如下:


const Filters = () => {

    const [anchors, setAnchors] = useState([null, null, null])

    const handleClick = (event, index) => {
        const arr = anchors
        arr[index] = event.target
        setAnchors(arr)    
    }

    const handleClose = (index) => {
        const arr = anchors
        arr[index] = null
        setAnchors(arr)  
    }

    return(
        <div id="top-filters">
            <div className="controls">
                <Button onClick={(event) => handleClick(event, 0)}>Sort By</Button>
                <Button onClick={(event) => handleClick(event, 1)}>Price</Button>
                <Button onClick={(event) => handleClick(event, 2)}>Delivery Fees</Button>
            </div>
        {
            console.log({anchors})
        }
            <Menu key={1}
                anchorEl={anchors[0]} 
                open={Boolean(anchors[0])} 
                onClose={() => handleClose(0)} 
                keepMounted
            >
                <MenuItem>
                    <ListItemText primary="By Rating" />
                </MenuItem>
            </Menu>

            <Menu key={2}
                anchorEl={anchors[1]} 
                open={Boolean(anchors[1])} 
                onClose={() => handleClose(1)} 
                keepMounted            
            >
                <MenuItem>
                    <Slider defaultValue={25} marks={marks} step={25} />
                </MenuItem>
            </Menu>

            <Menu key={3}
                anchorEl={anchors[2]} 
                open={Boolean(anchors[2])} 
                onClose={() => handleClose(2)} 
                keepMounted            
            >
                <MenuItem>
                    <ListItemText primary="By Delivery Fees" />
                </MenuItem>
            </Menu>

            <FormControl className="search-bar-filter">
                <Icon>search</Icon>
                <StyledInput classes={{ root: classes.root }} name="search" type="search" placeholder="Search" disableUnderline />
            </FormControl>
        </div>
    )
}

export default Filters

我检查了控制台中的值更新,它们看起来不错,但我不确定 React 为何不会重新呈现页面(当我单击按钮时,没有任何反应,但锚点状态已更新)。感谢您的帮助。

【问题讨论】:

    标签: reactjs react-hooks material-ui


    【解决方案1】:

    你正在做的是一个突变:

    arr[index] = event.target
    

    更新状态时应避免突变。因为如果你改变你的状态,React 无法理解你的状态已经改变。您可以使用map、filter 之类的方法或任何其他不会改变您的状态的方法。

    const handleClick = (event, index) => {
      setAnchors((prev) =>
        prev.map((el, i) => {
          if (i !== index) {
            return el;
          }
    
          return event.target;
        })
      );
    };
    

    或者如果你喜欢简洁的:

    const handleClick = (event, index) => {
      setAnchors((prev) => prev.map((el, i) => (i !== index ? el : event.target)));
    };
    

    扩展语法可用于非嵌套数组,但如果您正在处理嵌套数组并更改嵌套值,请小心,因为扩展语法会创建浅拷贝。这就是为什么我大部分时间都喜欢使用map 之类的方法。

    如果您不想映射整个数组(有些人更喜欢这个)Object.assign 和扩展语法可以一起使用。

    const handleClick = (event, index) => {
      setAnchors((prev) => Object.assign([], { ...prev, [index]: event.target }));
    };
    

    更新:

    正如我之前解释的,扩展语法只会产生浅拷贝。

    注意:传播语法在复制一个 大批。因此,它可能不适合复制多维 数组,如下例所示。 (同样适用于 Object.assign() 和传播语法。)

    Source

    这意味着嵌套值保持与原始值相同的引用。因此,如果您为新数组(或对象)更改某些内容,它也会更改原始数组。

    const arr = [
      { id: 0, name: "foo" },
      { id: 1, name: "bar" },
      { id: 2, name: "baz" },
    ];
    
    const newArr = [...arr];
    
    newArr[0].name = "something else";
    
    console.log("newArr", newArr);
    console.log("original arr", arr);

    如您所见,我们的原始数组也发生了变化。实际上,这不是嵌套数组,但我们正在更改数组元素的嵌套属性。也许这是嵌套数组的一个更好的例子,但上面的例子更现实。

    const arr = [["foo", "bar"], [1,2]];
    
    const newArr = [...arr];
    
    newArr[0][0] = "fizz";
    
    console.log(newArr);
    console.log(arr);

    【讨论】:

    • 您好 devserkan,感谢您的帮助,您的代码也有效,我觉得采用您的方法更安全。但是您能否详细解释一下嵌套数组上的展开语法会出现什么问题?
    • 我添加了一个示例来展示扩展语法在嵌套数组中使用时如何改变原始语法。同样,对于您的情况,可以使用 spread syntax 。只知道它只制作浅拷贝,不要将它用于嵌套数组、对象。我根本不想使用它。
    • 非常感谢,你让我学到的比我刚才问的更多
    【解决方案2】:

    您错误地更新了状态。 const arr = anchors 不是克隆锚点的正确方法。您需要使用... 运算符。

        const handleClick = (event, index) => {
            const arr = [... anchors]
            arr[index] = event.target
            setAnchors(arr)    
        }
    
        const handleClose = (index) => {
            const arr = [... anchors]
            arr[index] = null
            setAnchors(arr)  
        }
    

    【讨论】:

    • 就是这个原因!谢谢你快速的回复。你能解释一下为什么我的代码不起作用吗?再次感谢
    • react 使用 state 和 props 的“浅”比较来确定组件是否必须重新渲染。数组是引用类型。当使用 const arr = anchors,然后将 state 设置为 arr 时,它与该数组的引用相同,因此它不会重新渲染。使用展开运算符 const arr = [...anchors] 会导致 arr 对锚点有不同的引用。然后,当为该数组引用设置状态时,浅比较将返回它们不同,并且 react 将重新渲染组件。如果您需要进一步帮助,我可以创建代码框来显示差异。
    • 正如我所提到的,const arr = anchors 不是正确的方法。一旦您尝试像 arr[index] = ... 那样更新 arr 值,这意味着您也在更新锚点。你知道你不能直接更新状态值
    • @Gandzal 你给了我很清楚的解释,我明白你的意思了,非常感谢!
    • 这通常会让人绊倒,至少像我这样不确定什么 javascript 或 typescript 处理引用和数组分配等的人。谢谢。
    猜你喜欢
    • 2020-09-05
    • 1970-01-01
    • 2021-05-19
    • 2022-01-26
    • 1970-01-01
    • 2019-02-27
    • 2018-03-17
    相关资源
    最近更新 更多