• 【react】使用useEffect操作dom


    简言

    在学习react时,需要了一个需要在useEffect里操作dom的用法。
    一般不推荐这么干,如果是函数组件在一渲染已挂载后立即需要操作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,
        }} />
      );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    这个是 react的一个hook函数,作用就是根据当前计算更新当前鼠标的位置。
    如果是一个新的页面且只有这个示例是正常的。
    不过示例前面还用元素就会出现偏差。
    解决方案是在外面包括一个相对定位的盒子,然后在这个相对定位盒子里监听鼠标移动事件,更新小球偏离位置。
    所以,这需要react挂载后就立即在这个相对定位盒子里监听鼠标移动事件
    在这里插入图片描述

    解决方法

    方法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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    改造好后,可以和以前一样使用,参数传递useRef的dom对象就行。
    在这里插入图片描述

    方法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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    使用:

     let pos1 = usePointerPosition(
    (handleMove)=>{
      console.log(11);
          box.current.addEventListener("mousemove",handleMove)
        return box.current
         }
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

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

    官方是极其不推荐使用 注释 // 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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    这样callback就不会多次改变导致useEffect重复执行了。

    结语。

    在useEffect里操作dom是比较危险的。如果有其他更好的替代方案,请使用其他的。

  • 相关阅读:
    数据请求方式Fetch
    【排序26:有序矩阵中第 K 小的元素】
    nuxt3项目使用pdfjs-dist预览pdf
    OpenGauss数据库在 CentOS 上的实践,配置篇
    无叶风扇32位MCU单片机MM32SPIN0230
    CLR via C#-托管堆和垃圾回收
    react-router 如何在组件外路由跳转
    自动化测试中如何编写配置文件 ? 该使用什么工具 ? 一文详解使用ConfigParser读写配置文件
    api-ms-win-crt-runtime-l1-1-0.dll文件加载失败是怎么造成的?怎么修复?
    linux后台运行命令总结
  • 原文地址:https://blog.csdn.net/qq_43231248/article/details/133167872