gpt4 book ai didi

reactjs - 状态在钩子(Hook)返回的函数中没有改变

转载 作者:行者123 更新时间:2023-12-04 09:34:41 24 4
gpt4 key购买 nike

我在玩 React 和 Canvas。我想通过 requestAnimationFrame 为正方形的运动设置动画.所以我制作了主要组件来处理 Canvas 元素和更新正方形的位置:

function App() {
let canvas = useRef()
let square = useSquare(WIDTH / 2, HEIGHT, 2)
let update = () => {
square.update()
requestAnimationFrame(update)
}
useEffect(update, [])
return <>
<canvas ref={canvas} width={WIDTH} height={HEIGHT} />
<Renderer.Provider value={() => ref.canvas.getContext("2d")}>
<Square {...square} />
</Renderer.Provider>
</>
}
Square是一个组件,但它不返回 JSX。它在 useEffect 上运行绘图操作:
function Square({ x, y }) {
let getContext = useContext(Renderer)
useEffect(() => {
let ctx = getContext()
ctx.fillStyle = "#000"
ctx.fillRect(x, y, 10, 10)
}, [x, y])
return null;
}
我想用一个钩子(Hook)来封装正方形的状态和行为:
function useSquare(init_x, init_y) {
let [x, set_x] = useState(init_x)
let [y, set_y] = useState(init_y)
let [vx, set_vx] = useState(10)
let [vy, set_vy] = useState(-5)
let update = () => {
set_x(prev_x => prev_x + vx)
set_y(prev_y => prev_y + vy)
if (x <= 0 || x >= WIDTH) set_vx(prev_vx => -prev_vx)
else if (y <= 0 || y >= HEIGHT) set_vy(prev_vy => -prev_vy)
}
return { update, x, y }
}
对我来说似乎很合理。但是,它没有按预期工作。 Square 正在移动,但它不会响应与视口(viewport)边界的碰撞。
如果我把 console.log(x, y)进入 update 中的任何一个功能,我可以看到它以预期的速率触发,但状态没有改变。
我不明白如果状态没有改变,为什么组件能够正确呈现。坦率地说,我不知道我做错了什么。也许,有人帮我澄清一下。提前致谢。
PS:我用示例 https://codesandbox.io/s/xenodochial-jepsen-nfxv6做了一个codesanbox

最佳答案

问题是 updateuseSquare 返回的函数是一个闭包,其值为 xy它内部永远不会改变,因此 x <= 0 || x >= WIDTH 的条件和 y <= 0 || y >= HEIGHT从来都不是真的。下面是一个闭包的例子:

let x = 1;

function update(y) {
return function() {
console.log(y); // always 1
};
}
setInterval(update(x), 1000);
x = 2;

与闭包相关的错误很难被发现。虽然您可以在各种资源中了解更多关于闭包的信息,包括 MDN ,您可以使用 exhaustive-deps eslint-plugin-react-hooks 中的 linting 规则包以确保正确指定 Hook 中指定的依赖项数组并防止潜在的错误。 React docs (在 CodeSandbox 中默认启用此类规则。)
但在此之前,让我们简化任务以找到更好的方法来解决问题。假设有一个状态 x它将继续增加 3 直到达到 20。然后它将继续减少相同的值直到达到 0。
在这种情况下,我们使用 +1/-1 来表示递增/递减的方向。由于这种方向变化不应触发重新渲染,因此我们不必将其存储为状态变量。相反,我们将其存储为 ref,因为我们希望在计算新的 x 时访问其最新值.检测 x达到极限,我们设置了 useEffect钩。完整的实现将是:
import React, { useState, useRef, useEffect } from "react";
const MAX = 20;
const RATE = 3;

function App() {
let [x, setX] = useState(0);
const direction = useRef(1);
useEffect(() => {
const id = setInterval(() => {
setX(val => val + direction.current * RATE);
}, 500);
return () => clearInterval(id);
}, []);
useEffect(() => {
if (x >= MAX) direction.current = -1;
if (x <= 0) direction.current = 1;
}, [x]);
return <div>{x}</div>;
}
回到你的情况,应该更新的地方很少,主要部分在 useSquare里面.和上面的例子一样,我们不再存储 vxvy作为状态。相反,用 useRef 初始化它们.两个 useEffect Hook 也设置为根据 x 更改其值和 y值(value)观。
// useSquare.js    
import { useState, useRef, useEffect, useCallback } from "react";
import { WIDHT, HEIGHT } from "./settings";

export function useSquare(init_x, init_y) {
let [x, set_x] = useState(init_x);
let [y, set_y] = useState(init_y);
let vx = useRef(10);
let vy = useRef(-5);

let update = useCallback(() => {
set_x(prev_x => prev_x + vx.current);
set_y(prev_y => prev_y + vy.current);
}, []);

useEffect(() => {
if (x >= WIDHT) vx.current = -10;
if (x <= 0) vx.current = 10;
}, [x]);

useEffect(() => {
if (y <= 0) vy.current = 5;
if (y >= HEIGHT) vy.current = -5;
}, [y]);

useEffect(() => {
let id;
function cb() {
update();
id = requestAnimationFrame(cb);
}
cb();
return () => cancelAnimationFrame(id);
}, [update]);

return { x, y };
}
app.js变成:
function App() {
let canvas = useRef();
let square = useSquare(WIDHT / 2, HEIGHT / 2);
return (
<>
<canvas ref={canvas} width={WIDHT} height={HEIGHT} />
<Renderer.Provider value={() => canvas.current.getContext("2d")}>
<Square {...square} />
</Renderer.Provider>
</>
);
}
注意我用 useCallback 包裹了更新函数。钩子(Hook),这样它就不会随着 x 而改变和 y值(value)观。我还把动画功能放在 useSquare里面钩。如果将其移回 App.js ,请确保您正确设置了依赖项数组。
这是更新的沙箱:
Edit state-is-unchanged-from-within-a-function-returned-by-a-hook

关于reactjs - 状态在钩子(Hook)返回的函数中没有改变,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62648501/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com