我正在尝试通过wheel事件遍历数组。我有一个useState钩子,在安装组件时以及在触控板上滑动时,其初始值为0,如果向下滑动,则状态计数递减1,反之亦然。此值角色代表当前索引位置。
一切都很好,事件和状态都按我的预期进行。问题是,当我尝试执行一些条件操作来控制数组的位置不大于或小于这段代码中该数组的大小时:
if (Math.sign(e.deltaY) === -1) {
console.log('swipe DOWN');
if (persona > 0) {
setPersona(prevPers => prevPers - 1);
}
} else {
console.log('swipe UP');
if (persona <= team.length) {
setPersona(prevPers => prevPers + 1);
}
}
当我在Chrome调试器中观看角色值时,该值永远等于0,并且由于以下条件:
if (persona > 0) {
setPersona(prevPers => prevPers - 1);
}
该值曾经增加,因为角色值曾经是0。但是,当我用ReactDevTools观看角色值时,如果我触摸方向盘,该值就会改变。
这是我的代码:
export const Equipo = () => {
const container = useRef(null);
const [persona, setPersona] = useState(0);
const handleWheel = e => {
e.stopPropagation();
setEvent(false);
if (Math.sign(e.deltaY) === -1) {
console.log('swipe DOWN');
if (persona > 0) {
setPersona(prevPers => prevPers - 1);
}
} else {
console.log('swipe UP');
if (persona <= team.length) {
setPersona(prevPers => prevPers + 1);
}
}
setTimeout(() => {
console.log('available to scroll');
setEvent(true);
}, 3000);
};
const setEvent = status => {
status === true
? container.current.addEventListener('wheel', handleWheel, false)
: container.current.removeEventListener('wheel', handleWheel, false);
};
useEffect(() => {
setEvent(true);
}, []);
return (
<div
ref={container}
className="bg-white h-screen w-full p-header pb-4 px-4 h-full flex justify-center align-middle items-center"
>
<div className="flex flex-col justify-center items-center h-p70 w-10/12 bg-red-500 overflow-hidden">
{team[persona]}
</div>
</div>
);
};
export default Equipo;
我不知道为什么Chrome调试器中的值与ReactDevTools中的相同。这是我进行举报时的价值: Persona value in Chrome Debugger 如我们所见,角色控制台日志为2,但在悬停时仍显示0。
这是同一时间在ReactDevTools中角色的值: The same value in ReactDevTools
我不知道我是否遗漏了非常明显的东西,但我不明白为什么在这种情况下角色的价值仍然为0
答案 0 :(得分:0)
因此,setEvent
函数中的角色角色值始终为0的原因是,在首次安装组件并运行useEffect
钩子时围绕它创建了一个闭合。
以下是我将以与您当前相同的方式解决您的问题的方法:
我将handleWheel和setEvent函数移到useEffect函数中,以指示它们的值在效果的生命周期内没有改变。我还通过使它摆脱了对角色当前值的引用,以使setPersona调用不会超出其范围。
export const Equipo = () => {
const container = useRef(null);
const [persona, setPersona] = useState(0);
useEffect(() => {
const handleWheel = (e) => {
e.stopPropagation();
setEvent(false);
if (Math.sign(e.deltaY) === -1) {
console.log('swipe DOWN');
setPersona((prevPers) => {
Math.max(0, prevPers - 1);
});
} else {
console.log('swipe UP');
setPersona((prevPers) => Math.min(team.length, prevPers + 1));
}
setTimeout(() => {
console.log('available to scroll');
setEvent(true);
}, 3000);
};
const setEvent = (status) => {
status === true
? container.current.addEventListener('wheel', handleWheel, false)
: container.current.removeEventListener('wheel', handleWheel, false);
};
setEvent(true);
return () => {
setEvent(false);
};
}, []);
return (
<div
ref={container}
className="bg-white h-screen w-full p-header pb-4 px-4 h-full flex justify-center align-middle items-center"
>
<div className="flex flex-col justify-center items-center h-p70 w-10/12 bg-red-500 overflow-hidden">
{team[persona]}
</div>
</div>
);
};
export default Equipo;
这是我使用节流功能而不是超时的另一种方式。我认为这样比较干净,因为您不必担心引用次数过多和超时。这也是确保函数仅每t个时间段运行一次的更正常方法。
与其自己绑定事件侦听器,不如让反应来处理它更容易。
节流代码本身来自https://blog.bitsrc.io/understanding-throttling-and-debouncing-973131c1ba07。尽管您可以轻松使用lodash的节流功能。
export const Equipo = () => {
const [persona, setPersona] = useState(0);
const handleWheel = useCallback(
throttle((e) => {
e.stopPropagation();
if (Math.sign(e.deltaY) === -1) {
console.log('swipe DOWN');
setPersona((prevPers) => {
Math.max(0, prevPers - 1);
});
} else {
console.log('swipe UP');
setPersona((prevPers) => Math.min(team.length, prevPers + 1));
}
}, 3000),
[team.length]
);
return (
<div
onWheel={handleWheel}
className="bg-white h-screen w-full p-header pb-4 px-4 h-full flex justify-center align-middle items-center"
>
<div className="flex flex-col justify-center items-center h-p70 w-10/12 bg-red-500 overflow-hidden">
{team[persona]}
</div>
</div>
);
};
export default Equipo;
function throttle(f, t) {
let lastCall;
return function (args) {
let previousCall = lastCall;
lastCall = Date.now();
if (
previousCall === undefined || // function is being called for the first time
lastCall - previousCall > t
) {
// throttle time has elapsed
f(args);
}
};
}
人物角色为何始终为0的说明:
在Javascript中,创建函数时,它会创建所谓的闭包,并在其中提取作用域中存在的所有变量,并使该函数始终能够访问它们。MDN on closures
发生的事情是,当useEffect
挂载时,它将拉入setEvent
函数的当前版本,并且可以访问handleWheel
的当前版本功能。 setEvent
会将handleWheel
的原始版本绑定到div,并且永远没有机会更新绑定的handleWheel
的版本。由于闭包,原来的handleWheel
函数在persona为0时在persona周围有一个闭包。而且由于每次重新渲染总是有一个persona变量的新实例,因此闭包变量从未更新过。
基本上,这就是您的代码在useEffect中的样子。希望这可以帮助您了解为什么遇到问题。
useEffect(() => {
const setEvent = (status) => {
const handleWheel = (e) => {
e.stopPropagation();
setEvent(false);
if (Math.sign(e.deltaY) === -1) {
console.log('swipe DOWN');
if (persona > 0) {
setPersona((prevPers) => prevPers - 1);
}
} else {
console.log('swipe UP');
if (persona <= team.length) {
setPersona((prevPers) => prevPers + 1);
}
}
setTimeout(() => {
console.log('available to scroll');
setEvent(true);
}, 3000);
};
status === true
? container.current.addEventListener('wheel', handleWheel, false)
: container.current.removeEventListener('wheel', handleWheel, false);
};
setEvent(true);
}, []);