美文网首页
[React Hooks] 样例学习---useEventLis

[React Hooks] 样例学习---useEventLis

作者: 小黄人get徐先生 | 来源:发表于2019-08-11 19:26 被阅读0次

    使用事件监听

    如果你发现自己使用useEffect添加了大量的事件监听,你也许应该考虑移动这些逻辑到一个自定义hook。下面这个样例,我们创建了一个useEventListenerhook来处理检查addEventListener方法是否支持,如果支持的话就添加事件监听,并且在清除的时候移除监听。

    import React, { useState, useRef, useEffect, useCallback } from 'react';
    
    // Usage
    function App() {
      // State 用来存储鼠标的坐标
      const [coords, setCoords] = useState({ x: 0, y:0 });
    
      // 使用 useCallback 的事件处理程序,这样引用就不会更改
      const handler = useCallback(
        ({ clientX, clientY }) => {
          // 更新坐标
          setCoords({ x: clientX, y: clientY });
        };
      );
    
      // 使用我们自己的 hook 添加事件监听
      useEventListener('mousemove', handler);
    
      return (
        <h1>
          The mouse position is ({coords.x}, {coords.y})
        </h1>
      );
    }
    
    // 下面使我们编写的自定义 hook
    function useEventListener(eventName, handler, element = window) {
      // 创建一个 ref 来存储处理程序
      const saveHandler = useRef();
    
      // 如果 handler 变化了,就更新 ref.current 的值。
      // 这个让我们下面的 effect 永远获取到最新的 handler
      useEffect(() => {
        saveHandler.current = handler;
      }, [handler]);
    
      useEffect(
        () => {
          // 确保元素支持 addEventListener
          const isSupported = element && element.addEventListener;
          if (!isSupported) return;
    
          // 创建事件监听调用存储在 ref 的处理方法
          const eventListener = event => saveHandler.current(event);
    
          // 添加事件监听
          element.addEventListener(eventName, eventListener);
    
          // 清除的时候移除事件监听
          return () => {
            element.removeEventListener(eventName, eventListener);
          };
        },
        [eventName, element] // 如果 eventName 或 element 变化,就再次运行
      ); 
    };
    
    export default App;
    

    这里使用 useCallback 的原因是防止引用发生变更,如果使用普通的函数声明方式,每次该函数组件再次执行时就会重新声明函数,导致函数引用发生变化,自定义hook里监听 handler 的 useEffect 方法就会重复执行(因为声明函数的引用发生变化)。

    const handler = useCallback(
      ({ clientX, clientY }) => {
      // Update coordinates
      setCoords({ x: clientX, y: clientY });
      }
    );
    
    useEffect(() => {
      savedHandler.current = handler;
      }, [handler]);
    

    useRef 类似于 class 的实例变量,这里的 savaHandler 主要用来存储最新的 handler

    const savedHandler = useRef();
    
    useEffect(() => {
      savedHandler.current = handler;
    }, [handler]);
    

    初次加载代码的执行流程:

    1. const [coords, setCoords] = useState({ x: 0, y: 0 });
    2. const handler = useCallback(...
    3. useEventListener('mousemove', handler); // 进入到自定义 hook
    4. const savedHandler = useRef(); // 到这里App 和 useEventListener 的同步代码都加载完了(App里面没有 useEffect),下面使用异步的方式按顺序加载 useEventListener 的两个 useEffect
    5. 同步代码加载完后,异步代码进入加载序列,在异步加载之前,完成 return 操作。此时 coords 为 {x:0,y:0}
    return (
      <h1>
        The mouse position is ({coords.x}, {coords.y})
      </h1>
    ); 
    
    1. 执行第一个 useEffect。目前 useEffect 闭包函数里面的 handler 变量为 undefined。传入的 handler 为上面声明的方法。两者不相等,所以这里执行内部代码,执行完毕后,saveHandler.curent 就存储了最新的 handler 方法。
     useEffect(() => {
            savedHandler.current = handler;
        }, [handler]);
    
    1. 下面执行第二个 useEffect。eventName 和 element 都发生了变化,由 undefined 到对应的赋值,所以执行内部的逻辑代码,为 element 上的 eventName 事件绑定对应的 handler。
    2. 以上步骤完成初始化流程。

    mousemove 事件监听阶段:

    1. 完成初始化步骤后,成功为 window 上的 mousemove 事件绑定对应的 handler。现在每当鼠标移动,就会触发对应的 hanlder。
    2. 该方法会调用 setCoords 设置 coords 的值。当值发生变化后,App 会重新执行整个逻辑流程。
    const handler = useCallback(
      ({ clientX, clientY }) => {
        // Update coordinates
        setCoords({ x: clientX, y: clientY });
      }
    );
    
    1. const [coords, setCoords] = useState({ x: 0, y: 0 }); // 再次执行的时候有些 hook 方法忽略执行。(这里是我猜测的,因为再次执行整的流程的时候,对应的值不可能对重新初始化)。const handler = useCallback(... 同。
    2. useEventListener('mousemove', handler); // 进入自定义 hook
    3. const savedHandler = useRef(); // 不执行
    4. 两个 useEffect 异步执行。所以这里优先执行 return 语句,此时 coords 的值已经发生改变,return 后页面会发生改变。
    return (
        <h1>
            The mouse position is ({coords.x}, {coords.y})
        </h1>
    );
    
    1. 执行两个 useEffect,他们的 deps 的值都没有发生改变,所以两个 useEffect 都不执行。
    2. 监听流程代码的执行顺序如上。

    相关文章

      网友评论

          本文标题:[React Hooks] 样例学习---useEventLis

          本文链接:https://www.haomeiwen.com/subject/jzpsjctx.html