为什么React.memo无法与useEffect一起使用?

时间:2020-06-26 03:59:03

标签: javascript reactjs

我是个新来的反应者,所以我想要的是,我有一个切换按钮来切换人员组件,还有一个驾驶舱组件。但是无论何时切换人员组件,我都不想总是重新渲染驾驶舱组件。

这是我的Cockpit.js组件文件。

import React, { useEffect } from 'react';

import classes from './Cockpit.css';

const cockpit = props => {
  useEffect(() => {
    console.log('[Cockpit.js] useEffect');
    // Http request...
    setTimeout(() => {
      alert('Saved data to cloud!');
    }, 1000);
    return () => {
      console.log('[Cockpit.js] cleanup work in useEffect');
    };
  }, []);

  useEffect(() => {
    console.log('[Cockpit.js] 2nd useEffect');
    return () => {
      console.log('[Cockpit.js] cleanup work in 2nd useEffect');
    };
  });

  // useEffect();

  const assignedClasses = [];
  let btnClass = '';
  if (props.showPersons) {
    btnClass = classes.Red;
  }

  if (props.personsLength <= 2) {
    assignedClasses.push(classes.red); // classes = ['red']
  }
  if (props.personsLength <= 1) {
    assignedClasses.push(classes.bold); // classes = ['red', 'bold']
  }

  return (
    <div className={classes.Cockpit}>
      <h1>{props.title}</h1>
      <p className={assignedClasses.join(' ')}>This is really working!</p>
      <button className={btnClass} onClick={props.clicked}>
        Toggle Persons
      </button>
    </div>
  );
};

export default React.memo(cockpit);

这是我的App.js

import React, { Component } from 'react';
import Persons from '../Components/Persons/Persons';
import classes from './App.css';
import Cockpit from '../Components/Cockpit/Cockpit'

class App extends Component {
  constructor(props) {
    super(props);
    console.log("[App.js] constructor");
  }

  state = {
    persons: [{id: "abc", name: "", age: 45}, 
    {id: "azz", name: "", age: 56},
    {id: "asq", name: "", age: 62}],
    showPersons: false,
    showCockpit: true
  }

  static getDerivedStateFromProps(props, state) {
    console.log("[App.js] getDerivedStateFromProps", props)
    return state; 
  }

  componentDidMount() {
    console.log('[App.js] componentDidMount')
  }

  shouldComponentUpdate(nextProps, nextState) {
    console.log('[App.js] shouldCompoentUpdate');
    return true;
  }

  componentDidUpdate() {
    console.log('[App.js] componentDidUpdate')
  }

  deletePersonHandler = (i) => {
    const persons = [...this.state.persons];
    persons.splice(i, 1);
    this.setState({persons: persons})
  }
  switchNameHandler = (newName) => {
    this.setState({persons: [{name: newName, age: 50}, {name: "Aysha", age: 56}, {name: "Momma", age: 62}]})
  }

  nameSwitchHandler = (event, id) => {
    const personIndex = this.state.persons.findIndex(p => {
      return p.id === id;
    })

    const person = {...this.state.persons[personIndex]}

    person.name = event.target.value;

    const persons = [...this.state.persons]
    persons[personIndex] = person;
    this.setState({persons: persons})
  }

  togglePersonHandler = () => {
    let doesChange = this.state.showPersons;
    this.setState({showPersons: !doesChange})
  }

  render() {

    console.log("[App.js] render");
    let person = null;

    if(this.state.showPersons) {
      person = (<Persons 
        persons={this.state.persons}
        clicked={this.deletePersonHandler}
        changed={this.nameSwitchHandler} />
      );
    }

    return (
      <div className={classes.App}> 
      <button onClick={() => this.setState({showCockpit: false})}>Remove Cockpit</button>
        {this.state.showCockpit ? (<Cockpit 
        title={this.props.appTitle}
        showPersons={this.state.showPersons}
        personsLength={this.state.persons.length}
        clicked={this.togglePersonHandler} />) : null}
        {person}
      </div>
    );
  }
}

export default App;

但是,即使我切换了它,驾驶舱组件中的useEffect仍然会在不应该的情况下在浏览器控制台中记录控制台日志。我似乎找不到我在做错什么。

您可以在此图像中看到,座舱中的useEffect组件仍在控制台中呈现...
Browser Console

3 个答案:

答案 0 :(得分:1)

单击“切换人”,即可更改应用程序组件中的状态。 这样可以重新渲染App和Cockpit组件。

 useEffect(() => {
    console.log('[Cockpit.js] 2nd useEffect');
    return () => {
      console.log('[Cockpit.js] cleanup work in 2nd useEffect');
    };
  });

上面的代码将触发每个渲染,因为您没有提供依赖关系。 要解决此问题,您需要在上面的代码中添加一个依赖项。

答案 1 :(得分:1)

React.memo默认情况下将对props对象进行浅等式比较。这意味着它将检查道具中的每个顶级项目是否相等,如果其中任何一项发生更改,它将重新渲染。

当您单击人员切换按钮时,它将在您的showPersons组件中更改App,这也是传递给<Cockpit>的一项道具。因此,即使使用React.memo,它也会重新渲染。如果不进行渲染,就不会正确地添加或删除classes.Red来更新Button类,因为这取决于showPersons属性。

它与useEffect内的cockpit无关,后者只会在重新渲染后才被调用,而不会导致它首先重新渲染。

答案 2 :(得分:0)

showPersons更改以来,它会将其检测为更改的道具。

您可以在React.memo中添加一个等于函数,该函数告诉作出反应何时考虑记忆陈旧:

// Will only rerender when someValue changes
export default React.memo(Cockpit, (oldProps, newProps) => oldProps.someValue === newProps.someValue)