在学习react时,需要了一个需要在useEffect里操作dom的用法。
一般不推荐这么干,如果是函数组件在一渲染已挂载后立即需要操作dom绑定事件等可以参考下面解决方法。
官网交错运动示例
这个示例中,usePointerPosition() Hook 追踪当前指针位置。尝试移动光标或你的手指到预览区域上方,可以看到有一个红点随着你移动。它的位置被保存在变量 pos1 中。
事实上,有 5(!)个正在被渲染的不同红点。你看不见是因为他们现在都显示在同一位置。这就是你需要修复的问题。你想要实现的是一个“交错”运动:每个圆点应该“跟随”它前一个点的路径。例如如果你快速移动光标,第一个点应该立刻跟着它,第二个应该在小小的延时后跟上第一个点,第三个点应该跟着第二个点等等。
你需要实现自定义 Hook useDelayedValue。它当前的实现返回的是提供给它的 value。而你想从 delay 毫秒之前返回 value。你可能需要一些 state 和一个 Effect 来完成这个任务。
实现 useDelayedValue 后,你应该看见这些点一个接一个运动。
解决答案:
app.jsx
import { useState, useEffect } from 'react';
import { usePointerPosition } from './usePointerPosition.js';
function useDelayedValue(value, delay) {
const [delayedValue, setDelayedValue] = useState(value);
useEffect(() => {
setTimeout(() => {
setDelayedValue(value);
}, delay);
}, [value, delay]);
return delayedValue;
}
export default function Canvas() {
const pos1 = usePointerPosition();
const pos2 = useDelayedValue(pos1, 100);
const pos3 = useDelayedValue(pos2, 200);
const pos4 = useDelayedValue(pos3, 100);
const pos5 = useDelayedValue(pos3, 50);
return (
<>
<Dot position={pos1} opacity={1} />
<Dot position={pos2} opacity={0.8} />
<Dot position={pos3} opacity={0.6} />
<Dot position={pos4} opacity={0.4} />
<Dot position={pos5} opacity={0.2} />
</>
);
}
function Dot({ position, opacity }) {
return (
<div style={{
position: 'absolute',
backgroundColor: 'pink',
borderRadius: '50%',
opacity,
transform: `translate(${position.x}px, ${position.y}px)`,
pointerEvents: 'none',
left: -20,
top: -20,
width: 40,
height: 40,
}} />
);
}
usePointerPosition.js
import { useState, useEffect } from 'react';
export function usePointerPosition() {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
function handleMove(e) {
setPosition({ x: e.clientX, y: e.clientY });
}
window.addEventListener('pointermove', handleMove);
return () => window.removeEventListener('pointermove', handleMove);
}, []);
return position;
}
这个是 react的一个hook函数,作用就是根据当前计算更新当前鼠标的位置。
如果是一个新的页面且只有这个示例是正常的。
不过示例前面还用元素就会出现偏差。
解决方案是在外面包括一个相对定位的盒子,然后在这个相对定位盒子里监听鼠标移动事件,更新小球偏离位置。
所以,这需要react挂载后就立即在这个相对定位盒子里监听鼠标移动事件
参数box:这个代表使用useRef绑定的盒子dom对象
hook作用:在挂载后立即给box.current绑定鼠标移动事件,事件会更新position对像的值,最后返回。
function usePointerPosition(box){
const [position,setPosition] = useState({x:0,y:0})
useEffect(()=>{
let dom = box.current
if(dom)
dom.addEventListener("mousemove",handleMove)
function handleMove(e){
// console.log(e.clientX,e.clientY)
setPosition({x:e.offsetX,y:e.offsetY});
}
return ()=>{
if(dom)
dom.removeEventListener("mousemove",handleMove)
}
},[box])
return position
}
改造好后,可以和以前一样使用,参数传递useRef的dom对象就行。
使用时,直接在回调函数内,给dom盒子绑定鼠标移动事件
function usePointerPosition2(callback){
const [position,setPosition] = useState({x:0,y:0})
useEffect(()=>{
// 回调函数传递移动事件要使用的函数,返回dom对象,
let dom = callback(handleMove)
function handleMove(e){
setPosition({x:e.offsetX,y:e.offsetY});
}
return ()=>{
if(dom)
dom.removeEventListener("mousemove",handleMove)
}
// 报错很正常,因为使用callback依赖项,却没在下面绑定,下一句是规避掉代码检查
// eslint-disable-next-line react-hooks/exhaustive-deps
},[])
return position
}
使用:
let pos1 = usePointerPosition(
(handleMove)=>{
console.log(11);
box.current.addEventListener("mousemove",handleMove)
return box.current
}
);
官方是极其不推荐使用 注释 // eslint-disable-next-line react-hooks/exhaustive-deps规避代码检查的。
我们又确定callback的内容只变化一次,如果不使用注释,而将callback函数作为依赖项的话,将会执行多次callback函数,每次渲染都会执行,因为函数本身在渲染时在变。
这时 我们可以使用useCallback将callback包裹一下,缓存住,这样再渲染就是原来的函数。
useCallback 是一个允许你在多次渲染中缓存函数的 React Hook。
改造 usePointerPosition.
function usePointerPosition3(callback){
const [position,setPosition] = useState({x:0,y:0})
useEffect(()=>{
// 回调函数返回dom对象
let dom = callback(handleMove)
function handleMove(e){
setPosition({x:e.offsetX,y:e.offsetY});
}
return ()=>{
if(dom)
dom.removeEventListener("mousemove",handleMove)
}
// 报错很正常,因为使用callback依赖项,却没在下面绑定,下一句是规避掉代码检查
},[callback])
return position
}```
使用
```javascript
import {useEffect,useState} from 'react'
import { useRef } from 'react';
import { useCallback } from "react";
const callback = useCallback(
(handleMove)=>{
console.log(11);
box.current.addEventListener("mousemove",handleMove)
return box.current
}
,[box])
let pos1 = usePointerPosition(callback);
这样callback就不会多次改变导致useEffect重复执行了。
在useEffect里操作dom是比较危险的。如果有其他更好的替代方案,请使用其他的。