我有一个巨大的游戏对象,它在渲染时附着在画布上。我想在创建组件时只创建一次,而不是在组件被销毁之前删除它。
(如果游戏被周期性地创建和销毁,那么用户将失去他们的游戏状态。比方说,我不想编辑游戏以将存储放在Context
或Redux
或其他什么中,并希望保持游戏原样。(
我认为至少有三种方法可以做到这一点:
function Game1() {
const [world, _] = useState(createWorld) // unused setter
useEffect(() => world.destroy, [])
const attachCanvas = useCallback(canvas => world.start(canvas))
return <canvas ref={attachCanvas}/>
}
function Game2() {
const [world, setWorld] = useState()
useEffect(() => {
const newWorld = createWorld()
setWorld(newWorld)
return newWorld.destroy
}, []) // <- adding setWorld and newWorld dependencies can create loops
const attachCanvas = useCallback(canvas => world.start(canvas))
return <canvas ref={attachCanvas}/>
}
function Game3() {
// Works but docs say that a memo may be called twice if the engine feels like it
const world = useMemo(createWorld, [])
...
}
Game1
有什么问题吗?有没有第四种更好的方法来创建和摧毁一次状态?
您的第一个示例很好。
然而,有时编写一个基于类的React组件会更容易。您可以使用类构造函数将游戏世界初始化为类成员属性,然后使用componentWillUnmount生命周期挂钩将其销毁。
class Game extends React.Component {
constructor(props) {
super(props);
this.canvasRef = React.createRef();
this.world = createWorld();
}
componentDidMount() {
this.world.start(canvasRef.current);
}
componentWillUnmount() {
this.world.destroy();
}
render() {
return <canvas ref={canvasRef} />;
}
}
不,只创建一次永久状态而从不更新它是可以的。你也不需要解构setter,所以你的代码可以是const [world] = useState(createWorld);
我发现的最后一个选项是编写自定义挂钩或使用预先存在的挂钩,比如ahooks
的useCreation,它也允许依赖关系。一个简单的自定义挂钩可能看起来像:
function useOnce(factory, makeCleanup) {
const [x] = useState(factory)
useEffect(makeCleanup(x), [])
return x
}
这是在React钩子中创建和销毁变量的最佳方式。
function Game1(props) {
const [world, setWorld] = useState("");
// Similar to componentDidMount and componentDidUpdate
useEffect(() => {
setWorld("hello"); // Now world will be equals to "hello"
});
// Similar to componentWillUnmount
useEffect(() => {
// Specify how to clean up after this effect:
return function cleanup() {
setWorld(""); // Reset state here
}
});
}