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

2019-08-11  本文已影响0人  小黄人get徐先生

使用事件监听

如果你发现自己使用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. 监听流程代码的执行顺序如上。
上一篇下一篇

猜你喜欢

热点阅读