我正在尝试通过构建 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
正在被触发,我没想到它会这样。我原以为只有当我将一个视图更改为另一个视图时它才会卸载。
知道为什么会这样吗?如果我写的东西没有意义,请随时问我。
答案 0 :(得分:2)
感谢您将问题解释得如此清楚 - 这真的很容易理解。
现在,问题是,您的组件实际上并未卸载。您已将 props.movesList 作为 usEffect 的依赖项传递。现在第一次触发 useEffect 时,它会设置 return 语句。下一次由于 props.movesList 的变化而触发 useEffect 时,将执行 return 语句。
如果您打算在卸载组件时执行某些操作 - 将其转移到另一个具有空依赖项数组的 useEffect。
答案 1 :(得分:1)
你的问题的答案
“为什么每次移动都会触发这个”
将是:
“因为useEffect想要更新状态改变的组件”
但我倾向于说: “你不应该问这个问题,你不应该关心”
useEffect
您应该将 useEffect
理解为确保状态是最新的,不是作为一种生命周期挂钩。
想象一下,useEffect
总是被调用,一遍又一遍,只是为了确保一切都是最新的。这不是真的,但这种心理模型可能有助于理解。
您不在乎 useEffect
是否以及何时被调用,您只关心状态是否正确。
从 useEffect
返回的函数应该清理它自己的东西(例如事件监听器),再次确保一切都是干净的和最新的,但它不是onUnmount 处理程序。
您应该习惯于反复调用每个功能组件和每个钩子的想法。 React 决定是否可能不需要。
如果你真的有性能问题,你可以使用例如React.memo
和 useCallback
,但即便如此,也不要依赖于不再调用任何东西。
React 可能会调用你的函数,如果它认为有必要的话。使用 React.memo
仅作为一种反应的提示在这里做一些优化。
例如不要像您一样创建 <div>
的列表,而是创建一个列表,例如对象,并在视图中呈现该列表。您甚至可以创建自己的 MovesView
组件,只显示列表。在您的示例中,这可能有点过分分离,但您应该习惯这个想法,而且我认为您的实际组件最终会更大。
Don’t be afraid to split components into smaller components.
答案 2 :(得分:0)
问题似乎是由游戏元素引起的。
它在每次渲染时触发 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);
}, [])