【问题标题】:Why React Component unmounts on each useEffect dependency change?为什么 React Component 在每次 useEffect 依赖项更改时都会卸载?
【发布时间】:2021-02-06 09:56:36
【问题描述】:

我正在尝试通过构建 Web 应用程序来学习 React。由于想一步一步学,所以暂时不用Redux,只用React状态,有问题。

这是我的组件架构:

           App.js
             |
    _________|_________
   |                   |
Main.js              Side.js
   |                   |
Game.js              Moves.js

如您所见,我有一个名为App.js 的主文件,在左侧我们有Main.js,它是包含Game.js 的应用程序的中心部分,实际上我的游戏正在这里发生。在右侧,我们有Side.js,这是我想要显示每个玩家在游戏中所做的动作的侧边栏。它们将显示在Moves.js

为了更清楚地思考国际象棋游戏。左边是你实际玩的游戏,右边会列出你的动作。

现在我将向您展示我的代码并解释问题所在。

// App.js

const App = React.memo(props => {     
    let [moveList, setMovesList] = useState([]);

    return (
        <React.Fragment>  
            <div className="col-8">                                            
                <Main setMovesList={setMovesList} />                        
            </div>
            <div className="col-4">  
                <Side moveList={moveList} />
            </div>    
        </React.Fragment>
    );
});

// Main.js

const Main = React.memo(props => { 
    return (
        <React.Fragment>                
            <Game setMovesList={props.setMovesList} />
        </React.Fragment>
    );
});

// Game.js

const Game= React.memo(props => { 
    useEffect(() => {
        function executeMove(e) {
            props.setMovesList(e.target);
        }

        document.getElementById('board').addEventListener('click', executeMove, false);    

        return () => {            
            document.getElementById('board').removeEventListener('click', executeMove, false);              
        };
    }) 
    
    return (
        // render the game board
    );
});

// Side.js

const Side= React.memo(props => { 
    return (
        <React.Fragment>                
            <Moves moveList={props.moveList} />
        </React.Fragment>
    );
});

// Moves.js

const Moves= React.memo(props => { 
    let [listItems, setListItems] = useState([]);

    useEffect(() => {
        let items = [];                    

        for (let i = 0; i < props.moveList.length; i++) {
            items.push(<div key={i+1}><div>{i+1}</div><div>{props.moveList[i]}</div></div>)                                          
        }

        setListItems(items);

        return () => { 
            console.log('why this is being triggered on each move?') 
        }; 

    }, [props.moveList]);

    return (
        <React.Fragment>                
            {listItems}  
        </React.Fragment>
    );
});

正如您在我的代码中看到的,我在App.js 中定义了状态。在左侧,我传递了根据玩家的动作更新状态的函数。在右侧,我传递状态以更新视图。

我的问题是,在Game.js 内的每个点击事件上,组件Moves.js 都会卸载,并且console.log 正在被触发,我没想到它会这样。我原以为只有当我将视图更改为另一个视图时它才会卸载。

知道为什么会这样吗?如果我写的东西没有意义,请随时问我。

【问题讨论】:

    标签: javascript reactjs state react-component-unmount


    【解决方案1】:

    感谢您将您的问题解释得这么好 - 这真的很容易理解。

    现在,问题是,您的组件实际上并没有卸载。您已将 props.movesList 作为 usEffect 的依赖项传递。现在第一次触发你的 useEffect 时,它会设置 return 语句。下次由于 props.movesList 的变化而触发 useEffect 时,将执行 return 语句。

    如果您打算在卸载组件时执行某些操作 - 将其转移到另一个使用空依赖数组的 useEffect。

    【讨论】:

    • 谢谢,我会试试看它是否得到修复。但是,您对项目的流程有何看法,它看起来很正常,或者我不明白 react 是如何工作的,我应该有不同的方法?
    • 嘿!看起来不错。我个人真的很喜欢你如何组织整个事情。然而,一个小的改进可能是对 React.memo 的更保留使用。在您的情况下, MovesList 的 React.memo 不必要地为 React 添加了更多工作,因为 props.movesList 经常更新。即使对于 Game 组件,React.memo 也不会那么有用,因为您正在通过 props 向下传递一个函数——并且不同的函数实例会导致重新渲染。这是一篇关于使用 React.memo dmitripavlutin.com/use-react-memo-wisely 的精彩文章
    【解决方案2】:

    回答您的问题

    您的问题的答案
    “为什么每次移动都会触发”
    将是:
    “因为 useEffect 想要更新具有更改状态的组件”

    但我倾向于说: “你不应该问这个问题,你不应该关心”

    了解useEffect

    您应该将useEffect 理解为确保状态是最新的东西,而不是作为一种生命周期挂钩

    想象一下useEffect 被调用一直,一遍又一遍,只是为了确保一切都是最新的。这不是真的,但这种心智模型可能有助于理解。

    您不关心是否以及何时调用useEffect,您只关心状态是否正确。

    useEffect 返回的函数应该再次清理它自己的东西(例如事件监听器),确保一切都是干净和最新的,但它不是一个 onUnmount 处理程序。

    了解 React 钩子

    您应该习惯这样的想法,即每个功能组件和每个钩子都被一遍又一遍地调用。 React 决定是否有必要。

    如果你真的有性能问题,你可以使用例如React.memouseCallback,但即便如此,也不要依赖于不再调用任何东西。 如果 React 认为有必要,它可能会调用你的函数。仅将React.memo 用作一种反应提示,以便在此处进行一些优化。

    更多 React 技巧

    • 工作状态
    • 显示状态

    例如不要像您那样创建&lt;div&gt; 的列表,而是创建一个列表,例如对象,并在视图中呈现该列表。你甚至可以创建一个自己的MovesView 组件,只显示列表。在您的示例中,这可能有点过多的分离,但您应该习惯这个想法,而且我假设您的真实组件最终会更大。

    Don’t be afraid to split components into smaller components.

    【讨论】:

      【解决方案3】:

      问题似乎是由游戏元素引起的。

      它在每次渲染时触发 addEventListener。

      为什么不使用onClick 事件处理程序

      /* remove this part
       useEffect(() => {
              function executeMove(e) {
                  props.setMovesList(e.target);
              }
      
              document.getElementById('board').addEventListener('click', executeMove, false);    
          }) 
      
      */
      
      const executeMove = (e) => {
           props.setMovesList(e.target);
      }
      
      return (
          <div id="board" onClick={executeMove}>
          ...
          </div>
      )
      

      如果要使用addEventListener,应该在组件挂载时添加。将空数组([])作为第二个参数传递给useEffect

       useEffect(() => {
              function executeMove(e) {
                  props.setMovesList(e.target);
              }
      
              document.getElementById('board').addEventListener('click', executeMove, false);    
          }, []) 
      

      【讨论】:

      • 你好。谢谢。我忘记在我的代码中添加一个事实,即我在返回时删除了该事件侦听器。即使我有移除事件侦听器,这是否会改变某些东西,或者问题仍然可能来自那个东西?
      猜你喜欢
      • 1970-01-01
      • 2020-02-21
      • 2022-11-17
      • 2020-06-03
      • 1970-01-01
      • 1970-01-01
      • 2021-09-08
      • 2022-11-25
      • 1970-01-01
      相关资源
      最近更新 更多