【问题标题】:Too many React components re-rendering, how to limit it?太多 React 组件重渲染,如何限制?
【发布时间】:2020-10-19 15:51:22
【问题描述】:

我正在屏幕上生成一个组件列表,如下所示:

const MessagesContainer = ({ messages, categories, addHandler }) => {
    const options = categories.map(category => (
        { value: category.name, label: category.name }
    ));

    return (
        <div className="d-flex flex-wrap justify-content-center">
            {messages.map(message =>
                <div key={message.id}>
                    <MessageEditor
                        message={message} 
                        options={options}
                        addHandler={addHandler}
                    />
                </div>
            )}
        </div>
    );
};

const MessageEditor = ({ message, options, addHandler }) => {
    const [modifedMessage, setModifiedMessage] = useState(message);
    const [isAdded, setIsAdded] = useState(false);
    
    const textClass = (charLimit - modifedMessage.text.length) > 0 ?
        'text-success' : 'text-danger';
    const buttonClass = isAdded ? 'danger' : 'primary';

    const ref = useRef(null);

    const textAreaHandler = textArea => {
        const copiedMessage = { ...modifedMessage };
        copiedMessage.text = textArea.target.value;
        setModifiedMessage(copiedMessage);
    };

    const addButtonHandler = () => {
        const add = !isAdded;
        setIsAdded(add);

        let selectedCategoires = ref.current.state.value;
        // Firing this handler results in ALL the MessageEditor
        // componets on the screen being re-rendered
        addHandler(modifedMessage, add, selectedCategoires);
    }

    return (
        <div className="d-flex flex-column message-view-container ml-5 mr-5 mb-5">
            <div className={`message-count-container ${textClass}`}>
                {charLimit - modifedMessage.text.length}
            </div>
            <Select
                ref={ref}
                placeholder="Tags"
                isMulti
                name="tags"
                options={options}
                defaultValue={[options[0]]}
                className="basic-multi-select select-container"
                classNamePrefix="select"
                isDisabled={isAdded}
            />
            <Form.Control
                style={{
                    width:350,
                    height:220,
                    resize:'none'
                }}
                className="mb-1"
                as="textarea"
                defaultValue={message.text}
                onChange={textAreaHandler}
                disabled={isAdded}
            />
            <Button variant={buttonClass} onClick={addButtonHandler}>
                {isAdded ? 'Remove' : 'Add'}
            </Button>
        </div>
    );
};

以及持有addHandler的父组件:

const { useState } = require("react");

const Messages = () => {
    const [messages, setMessages] = useState([]);
    const [saveMessages, setSaveMessages] = useState({});

    const addHandler = (modifiedMessage, add, selectedCategoires) => {
        const copiedSaveMessages = { ...saveMessages };
        if (add) {
            if (selectedCategoires) {
                selectedCategoires = selectedCategoires.map(item => item.value);
            }
            copiedSaveMessages[modifiedMessage.id] = {
                text: modifiedMessage.text,
                tags: selectedCategoires ? selectedCategoires : []
            }
        } else {
            delete copiedSaveMessages[modifiedMessage.id];
        }

        // This results in every single MessageEditor component being
        // re-rendered
        setSaveMessages(copiedSaveMessages);
    };

    return (
        <div>
            {categories &&
                <div>
                    <div className="ml-5 mr-5 mt-5">
                        <MessagesContainer
                            messages={messages}
                            categories={categories}
                            addHandler={addHandler}
                        />
                    </div>
                </div>
            }
            {Object.keys(saveMessages).length > 0 &&
                <div>
                    <Image 
                        className="upload-icon"
                        src={uploadIcon}
                    />
                    <div className="text-primary count-container">
                        <h2>{Object.keys(saveMessages).length}</h2>
                    </div>
                </div>
            }
        </div>
    );
};

问题是,如果我点击添加按钮触发addHandler,它会导致所有MessageEditor 组件重新渲染。如果屏幕上有几百个组件,性能会很慢。

我猜这是因为saveMessages 状态变量属于Messages 组件,而MessageEditorMessages 的子级,所以它也会重新渲染。

有没有一种方法可以在不导致所有其他组件重新渲染的情况下更新此状态?

【问题讨论】:

  • 你能创建一个沙盒问题吗?衡量有形性的表现会更好吗?

标签: javascript reactjs


【解决方案1】:

Messages 中,您应该将addHandler 包裹在useCallback 挂钩(React useCallback hook)中,这样它就不会在每次渲染时重新创建。

const addHandler = useCallback((modifiedMessage, add, selectedCategoires) => {
    // function body...
}, []);

此外,您还可以使用React.memo() (React memo) 记忆MessageEditor

const MessageEditor = React.memo(({ message, options, addHandler }) => {
    // component body...
});

【讨论】:

  • useCallBackaddHandler 的依赖是什么。它有一个依赖数组,我不知道该放什么。
  • @Kex 根据文档,您应该传递所有回调参数,因此[modifiedMessage, add, selectedCategoires]
  • 那行不通。考虑:const addHandlerCallback = useCallback(addHandler, [modifiedMessage]),得到错误modifedMessage is not defined
  • @kex,这不是正确的语法;你应该有:const addHandler = useCallback((modifiedMessage, add, selectedCategoires) =&gt; { // ... function body }, [modifiedMessage, add, selectedCategoires]);
  • 我仍然得到未定义的错误。您正在尝试将函数参数传递给依赖数组。你确定这是正确的吗?
猜你喜欢
  • 2020-04-14
  • 2021-02-26
  • 1970-01-01
  • 1970-01-01
  • 2021-07-01
  • 2020-09-22
  • 2020-08-07
相关资源
最近更新 更多