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

使用事件监听

如果你发现自己使用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. 监听流程代码的执行顺序如上。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容