为什么 React 组件会在每次 useEffect 依赖项更改时卸载? 回答您的问题理解useEffect了解 React 钩子更多 React 技巧

问题描述

我正在尝试通过构建 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',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 正在被触发,我没想到它会这样。我原以为只有当我将一个视图更改为另一个视图时它才会卸载。

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

解决方法

感谢您将问题解释得如此清楚 - 这真的很容易理解。

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

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

,

回答您的问题

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

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

理解useEffect

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

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

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

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

了解 React 钩子

您应该习惯于反复调用每个功能组件和每个钩子的想法。 React 决定是否可能不需要。

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

更多 React 技巧

  • 处理状态
  • 显示状态

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

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

,

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

它在每次渲染时触发 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',false);    
    },[]) 

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...