【问题标题】:How to Prevent Child Component Re-Render when Parent state changes父状态更改时如何防止子组件重新渲染
【发布时间】:2019-04-19 00:31:09
【问题描述】:

我有两个无状态组件和一个有状态组件。在我的有状态组件中,我有一个人员列表,当我单击其中一个时,它会打开一个模式。我基本上将模态的状态存储在有状态的组件中,并将值作为道具发送给模态。因此,它会在打开模型并重新渲染所有内容的任何时候更改状态。

如何防止我的无状态类在更改状态后重新呈现?

我在我的无状态组件中尝试过备忘录,但没有成功

模态组件

const modal = React.memo((props) => {

    return (
    <Modal show={props.show} onHide={props.handleClose} aria-labelledby="contained-modal-title-vcenter"
           centered>
        <Modal.Header closeButton>
            <Modal.Title>Modal heading</Modal.Title>
        </Modal.Header>
        <Modal.Body>Woohoo, you're reading this text in a modal!</Modal.Body>
        <Modal.Footer>
            <Button variant="secondary" onClick={props.handleClose}>
                Close
            </Button>
        </Modal.Footer>
    </Modal>
    )
});

export default modal

人物组件

const person = React.memo((props) => {
    console.log("render");
    return (
        <article className="Person" onClick={props.clicked}>
            <Container>
                <Row>
                    <Col>
                        <p style={{marginBottom: 0 + 'px', marginTop: 1 + 'rem'}}><b>{props.name}</b></p>
                        <div className="Organization-wrapper">
                            <FontAwesomeIcon className="Organization-icon" icon={faBuilding}/><p>{props.company}</p>
                        </div>
                    </Col>
                    <Col>
                        <div>
                            {
                                props.image ?
                                    <img src={props.image} height="50" width="50" className="Person-image"
                                         alt="avatar"/>
                                    : <svg height="50" width="50" className="Person-image">
                                        <rect fill="cornflowerblue" x="0" y="0" height="50" width="50"/>
                                        <text
                                            fill="#ffffff"
                                            fontSize="20"
                                            textAnchor="middle"
                                            x="25"
                                            y="30">{props.first_char.toUpperCase()}</text>
                                    </svg>
                            }
                        </div>
                    </Col>
                </Row>
            </Container>
        </article>
    );
});

有状态的组件

class PersonList extends Component {

    state = {
        persons: [],
        show: false
    };


    componentDidMount() {
        axios.get('')
            .then(response => {
                const result = response.data.data.slice(0, 5);
                this.setState({persons: result});
            });
    }

    personSelectedHandler = (id) => {
        this.showModal();
    };

    showModal = () => {
        this.setState({ show: true });
    };

    hideModal = () => {
        this.setState({ show: false });
    };

    render() {
        const persons = this.state.persons.map(person => {

            let avatar;
            if (person.picture_id != null) {
                avatar = person.picture_id.pictures["128"];
            }
            return <ListGroup>
                <ListGroup.Item>
                    <Person
                        key={person.id}
                        name={person.name}
                        company={person.org_id.name}
                        first_char={person.first_char}
                        image={avatar}
                        clicked={() => this.personSelectedHandler(person.id)}
                    />
                </ListGroup.Item>
            </ListGroup>
        });

        return (

            <div className="Wrapper">
                <Modal show = {this.state.show} handleClose = {this.hideModal}/>
                <Header/>
                <div className="Breadcrumb">
                    <h4 className="Breadcrumb-text"><b>People's List</b></h4>
                    <hr/>
                </div>
                {persons}
            </div>
        );
    }

}

export default PersonList;

【问题讨论】:

  • 使用 shouldComponentUpdate 生命周期来防止重新渲染

标签: reactjs frontend


【解决方案1】:
<Person
    ...
    clicked={() => this.personSelectedHandler(person.id)}
/>

这将为 props.clicked 在父函数的每次重新渲染中定义一个新的函数引用。这将导致Person 的道具在每次渲染时都发生变化,从而导致Person 也重新渲染。

由于您当前没有在personSelectedHandler 中使用id,您可以省略参数并保持函数引用不变:

clicked={this.personSelectedHandler}

如果您最终需要 id 上下文,您可能需要考虑使用父母的状态来实现您所追求的,如果您真的想要避免重新-渲染。

您还可以将Person 转换为(纯)类组件,并在该组件内处理回调。

最后,如果迫在眉睫并且你绝对必须使用这个内联箭头函数,你可以覆盖React.memo 的比较函数,这大致类似于为类组件定义componentShouldUpdateFrom the docs:

function MyComponent(props) {
  /* render using props */
}
function areEqual(prevProps, nextProps) {
  /*
  return true if passing nextProps to render would return
  the same result as passing prevProps to render,
  otherwise return false
  */
}
export default React.memo(MyComponent, areEqual);

可能还有另一种我不知道的方法来完成这项工作,但希望这可以帮助您了解问题以探索替代解决方案。这里也有一些想法和讨论:Why shouldn't JSX props use arrow functions or bind?

【讨论】:

  • 感谢您的回答。我将我的无状态函数组件更改为 React.Component 并在其中使用了 shouldComponentUpdate。我无法让它与其他选项一起使用
  • 没问题,这是一个令人讨厌的用例。如果您想继续使用 React.memo 的功能组件,我澄清了我对覆盖 React.memo 比较函数的回答。它应该与定义componentDidUpdate 大致相同(尽管它应该返回相反的值;true 如果组件不应该重新渲染)。不过,我从未将它用于这种特殊情况,并且不确定是否有任何我不知道的陷阱。
【解决方案2】:

如果您使用钩子,您可以将useCallback() 方法与Recat.memo 一起使用。 在您的组件中:

const callBack = useCallback(() => this.personSelectedHandler(person.id),[person.id]);

useCallback((person.id) =&gt; personSelectedHandler(person.id),[person.id]); 总是返回相同的函数实例,只要person.id 相同。 那么:

<Person
    ...
    clicked={callBack}
/>

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-10-18
    • 2018-08-14
    • 1970-01-01
    • 2020-04-24
    • 1970-01-01
    • 2022-06-29
    • 2021-04-06
    相关资源
    最近更新 更多