【react】使用useEffect操作dom

简言

在学习react时,需要了一个需要在useEffect里操作dom的用法。
一般不推荐这么干,如果是函数组件在一渲染已挂载后立即需要操作dom绑定事件等可以参考下面解决方法。

描述

官网交错运动示例

【react】使用useEffect操作dom_第1张图片

这个示例中,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挂载后就立即在这个相对定位盒子里监听鼠标移动事件
【react】使用useEffect操作dom_第2张图片

解决方法

方法1 usePointerPosition.js hook传递dom参数。

参数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对象就行。
【react】使用useEffect操作dom_第3张图片

方法2. usePointerPosition.js hook传递回调函数参数。

使用时,直接在回调函数内,给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
     }
);

方法3 使用useCallBack hook代替规避代码检查

官方是极其不推荐使用 注释 // eslint-disable-next-line react-hooks/exhaustive-deps规避代码检查的。
我们又确定callback的内容只变化一次,如果不使用注释,而将callback函数作为依赖项的话,将会执行多次callback函数,每次渲染都会执行,因为函数本身在渲染时在变。
这时 我们可以使用useCallback将callback包裹一下,缓存住,这样再渲染就是原来的函数。
useCallback 是一个允许你在多次渲染中缓存函数的 React Hook。
【react】使用useEffect操作dom_第4张图片
改造 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是比较危险的。如果有其他更好的替代方案,请使用其他的。

你可能感兴趣的:(react,react.js,前端,前端框架)