1

我有一个反应组件,它在 useState 中存储一组水果。我有一个过滤水果的记忆函数(visibleFruits)。我将 visibleFruits 映射到 dom。

问题是,当我检查一个水果时,所有可见的水果都会重新渲染。

我期望只有选定的一个重新渲染,因为它是唯一一个正在改变的。

有没有办法让我使用这种模式,但防止所有人在检查时重新渲染?

在现实生活中,visibleFruits useMemo 中有一个复杂的功能。所以我不能简单地在地图之前附加过滤器。

编辑,这里是更新的编辑:


const Example = () => {
    const [fruits, setFruits] = React.useState([
        { id: 1, name: 'apple', visible: true, selected: false },
        { id: 2, name: 'banana', visible: false, selected: false },
        { id: 3, name: 'orange', visible: true, selected: false }
    ])

    const visibleFruits = React.useMemo(() => {
        return fruits.filter((f) => f.visible)
    }, [fruits])

    const handleCheck = (bool, id) => {
        setFruits((prev) => {
            return prev.map((f) => {
                if (f.id === id) {
                    f.selected = bool
                }
                return f
            })
        })
    }

    return (
        <div>
            {visibleFruits.map((fruit) => {
                return <FruitOption fruit={fruit} handleCheck={handleCheck} />
            })}
        </div>
    )
}

const FruitOption = ({ fruit, handleCheck }) => {
    console.log('** THIS RENDERS TWICE EVERY TIME USER SELECTS A FRUIT **')
    return (
        <div key={fruit.id}>
            <input
                checked={fruit.selected}
                onChange={(e) => handleCheck(e.target.checked, fruit.id)}
                type='checkbox'
            />
            <label>{fruit.name}</label>
        </div>
    )
}

export default Example
4

1 回答 1

1

首先,该handleCheck功能存在问题(但这与您要询问的内容无关)。你的代码直接修改一个fruit对象(相反,您需要复制对象并修改副本(就像您使用数组一样):f.selected = bool

const handleCheck = (bool, id) => {
    setFruits((prev) => {
        return prev.map((f) => {
            if (f.id === id) {
                return {...f, selected: bool}; // ***
            }
            return f;
        });
    });
};

但这不是您要问的,只是要解决的其他问题。:-)

您看到console.log执行两次之后的原因handleCheck是组件必须重新渲染(用于更改),并且有两个可见的结果,因此您会看到对FruitOption组件函数的两次调用。有两个原因:

  1. handleChange每次Example调用组件函数时都会发生变化,因此FruitOption每次都会看到一个新的道具;和

  2. FruitOption当它的道具没有改变时不会避免重新渲染,所以即使你修复了#1,你仍然会看到两个console.log调用;和

另外,元素上没有,这可能会导致渲染key问题。FruitOption渲染数组中的元素时始终包含有意义的键。(不要只使用索引,这是有问题的;但是你的水果对象有一个id,这是完美的。)

要解决这个问题:

  1. MemoizehandleChange以便它不会每次都重新创建,可能是通过useCallback, 和

  2. 如果它的道具没有改变,则使用React.memo它不会被调用(请参阅此答案的末尾以获取等效组件),并且FruitOptionclass

  3. 添加一个有意义keyFruitOption元素Example

把这些和handleChange上面的修复放在一起:

const Example = () => {
    const [fruits, setFruits] = React.useState([
        { id: 1, name: 'apple', visible: true, selected: false },
        { id: 2, name: 'banana', visible: false, selected: false },
        { id: 3, name: 'orange', visible: true, selected: false }
    ]);

    const visibleFruits = React.useMemo(() => {
        return fruits.filter((f) => f.visible);
    }, [fruits]);

    const handleCheck = React.useCallback(
        (bool, id) => {
            setFruits((prev) => {
                return prev.map((f) => {
                    if (f.id === id) {
                        return {...f, selected: bool}; // ***
                    }
                    return f;
                });
            });
        },
        []  // *** No dependencies since all it uses is `setFruits`, which is
            // stable for the lifetime of the component
    );

    return (
        <div>
            {visibleFruits.map((fruit) => {
                // *** Note the key
                return <FruitOption key={fruit.id} fruit={fruit} handleCheck={handleCheck} />
            })}
        </div>
    );
}

// *** `React.memo` will compare the props and skip the call if they're the same, reusing
// the previous call's result.
const FruitOption = React.memo(({ fruit, handleCheck }) => {
    console.log(`Rendering fruit ${fruit.id}`);
    return (
        <div key={fruit.id}>
            <input
                checked={fruit.selected}
                onChange={(e) => handleCheck(e.target.checked, fruit.id)}
                type='checkbox'
            />
            <label>{fruit.name}</label>
        </div>
    );
});

ReactDOM.render(<Example />, document.getElementById("root"));
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>

如您所见,所有这些都到位后,只有更改后的水果被重新渲染。

Re React.memo: 对于要求比较复杂的组件,可以提供一个函数作为第二个参数,判断两组props是否“相同”用于渲染目的。默认情况下,React.memo只进行浅层相等比较,这通常就足够了。


最后:对于class组件,React.memo不提供相等回调的等价物是扩展PureComponent而不是Component. 如果你想让你的 props 检查更细粒度,你可以实现shouldComponentUpdate

于 2021-12-09T16:31:38.177 回答