我正在玩React和Canvas。我想通过requestAnimationFrame
为正方形的移动制作动画。因此,我制作了用于处理画布元素和更新正方形位置的主要组件:
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;
}
我想用钩子封装正方形的状态和行为:
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 }
}
对我来说似乎很合理。但是,它没有按预期工作。正方形正在移动,但对与视口边界的碰撞没有反应。
如果我将console.log(x, y)
放到update
两个函数中,我可以看到它以预期的速率触发,但是状态没有改变。
我不明白为什么状态不变时组件能够正确呈现。坦白说,我不知道我在做什么错。也许有人帮我澄清一下。预先感谢。
制作了一个代码编辑器答案 0 :(得分:1)
答案1 :问题是每次requestAnimationFrame
运行时,square.update()
都是您实际创建的新框。您需要先清除边框,然后再绘制新边框
import { useContext, useEffect } from "react";
import { Renderer } from "./ctx";
export function Square({ x, y }) {
let getContext = useContext(Renderer);
useEffect(() => {
let ctx = getContext();
// clear before drawing
ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
ctx.fillStyle = "#000";
ctx.fillRect(x, y, 10, 10);
}, [x, y]);
return null;
}
检查codesandbox是否可视化
这是我在网上找到的a very good series。看看
答案2::requestAnimationFrame
创建一个调用square.update()
的循环,这就是为什么您可以看到尺寸已更改的原因
答案3:您正在通过调用Square
set**
组件内的状态
答案 1 :(得分:1)
问题是从update
返回的useSquare
函数是一个闭包,并且其中的x
和y
的值永远不会改变,因此{{ 1}}和x <= 0 || x >= WIDTH
永远都不正确。这是关闭的示例:
y <= 0 || y >= HEIGHT
与关闭相关的错误很难被识别。虽然您可以在包括MDN的各种资源中了解有关闭包的更多信息,但是可以使用let x = 1;
function update(y) {
return function() {
console.log(y); // always 1
};
}
setInterval(update(x), 1000);
x = 2;
包中的exhaustive-deps
linting规则来确保正确指定了在挂钩中指定的依赖项数组,并防止了错误。 React docs(默认情况下,在CodeSandbox中启用了此规则。)
但是在此之前,让我们简化任务以找到解决问题的更好方法。假设有一个状态eslint-plugin-react-hooks
会一直递增3,直到达到20。然后它将保持相同的值递减,直到达到0。
在这种情况下,我们使用+ 1 / -1表示增量/减量的方向。由于此方向更改不应触发重新渲染,因此我们不必将其存储为状态变量。相反,我们将其存储为ref,因为我们希望在计算新的x
时访问其最新值。为了检测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
和vx
存储为状态。而是使用vy
初始化它们。还设置了两个useRef
挂钩,以根据useEffect
和x
的值更改其值。
y
// 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
和x
值而改变。我还将动画功能放在y
挂钩中。如果将其移回useSquare
,请确保您正确设置了依赖项数组。
这是更新的沙箱: