日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

詳解如何在React中優雅的使用addEventListener_React

作者:uccs ? 更新時間: 2023-03-27 編程語言

在?React Hooks?中使用第三方庫的事件時,很多人會寫成這樣(指的就是我):

const [count, setCount] = useState(0);
useEffect(() => {
  const library = new Library();
  library.on("click", () => {
    console.log(count); // 拿不到最新的 count
  });
}, []);

這樣寫會有問題:

它只會在這個組件加載時,綁定事件,如果這個事件中用到了其他的?state,那么這個狀態發生變化時事件中是拿不到最新的?state

你會想到,我把?state?放到依賴項中:

const [count, setCount] = useState(0);
useEffect(() => {
  const library = new Library();
  // click 事件會重復綁定
  library.on("click", () => {
    console.log(count);
  });
}, [count]);

這樣做又會有新問題:click?事件會重復綁定

這時候你說那我先卸載?click?事件,在綁定事件:

const [count, setCount] = useState(0);
useEffect(() => {
  const library = new Library();
  library.on("click", handleClick);
  return () => {
    // 卸載不掉事件,還是會重復綁定
    handleClick && library.un("click", handleClick);
  };
}, [count]);

const handleClick = () => {
  console.log(count);
};

你驚奇的發現,居然卸載不掉之前的事件,還是會重復綁定事件。

如何解決這個問題呢?

使用 addEventListener 代替第三方庫的事件

這里使用?addEventListener?代替第三方庫的事件,初始代碼

const Test = (props) => {
  const ref = useRef();
  const [count, setCount] = useState(0);

  useEffect(() => {
    const handleClick = (event) => {
      console.log("clicked");
      console.log("count", count);
    };
    const element = ref.current;
    element.addEventListener("click", handleClick);
    return () => {
      element.removeEventListener("click", handleClick);
    };
  }, []);

  const onClickIncrement = () => {
    setCount(count + 1);
  };
  return (
    <>
      <h2>Test</h2>
      <button onClick={onClickIncrement}>點擊 +1</button>
      <div>count: {count}</div>
      <button ref={ref}>Click Test Button</button>
    </>
  );
};

方法一:state 變化,卸載/綁定事件

將?state?放在依賴項中,就要解決?state?變化時,事件重復綁定的問題

解決事件重復綁定問題,首先想到的是事件卸載

你很容易就會想到這樣寫

useEffect(() => {
  handleClick && ref.current.removeEventListener("click", handleClick);
  ref.current.addEventListener("click", handleClick);
}, [count]);

const handleClick = () => {
  console.log(count);
};

這在?React Hooks?中是一個坑,state?變化后會?handleClick?事件函數會重新聲明,新的?handleClick?和之前的?handleClick?不是一個事件函數,導致?removeEventListener?移除的事件函數不是之前的事件函數

那你又會想到,我給?handleClick?加個?useCallback

useEffect(() => {
  handleClick && ref.current.removeEventListener("click", handleClick);
  ref.current.addEventListener("click", handleClick);
}, [count]);

const handleClick = useCallback(() => {
  console.log(count);
}, []);

這樣寫的話還是會有同一個問題:依賴項為空數組,就拿不到最新的?state;依賴項中放入?state,state?變化后就不是同一個事件函數了,無法移除事件

如何解決這個問題呢?

把事件函數保存為狀態:

  • 當?count?變化時,掛載事件,同時將事件函數保存為?state
  • 當?eventFn.fn?變化時,在?useEffect return?中卸載之前的事件函數(這里利用的是閉包)

具體的代碼:

const Test = () => {
  const ref = useRef();
  const [count, setCount] = useState(0);
  const [eventFn, setEventFn] = useState({ fn: null });

  useEffect(() => {
    mountEvent();
  }, [count]);

  const mountEvent = () => {
    if (!ref.current) return;
    //  eventFn.fn && ref.current.removeEventListener("click", eventFn.fn);  // 下面看不懂的話,也可以這樣寫
    ref.current.addEventListener("click", handleClick);
    setEventFn({ fn: handleClick });
  };

  useEffect(() => {
    return () => {
      eventFn.fn && ref.current.removeEventListener("click", eventFn.fn); // 這里用的是閉包,和上面注釋部分任選其一
    };
  }, [eventFn.fn]);

  const handleClick = () => {
    console.log(count);
  };

  const onClickIncrement = () => {
    setCount(count + 1);
  };

  return (
    <>
      <h2>Test</h2>
      <button onClick={onClickIncrement}>點擊 +1</button>
      <div>count: {count}</div>
      <button ref={ref}>Click Test Button</button>
    </>
  );
};

方法二:使用閉包的方式卸載事件

利用閉包,可以將方法一簡化

const Test = () => {
  const ref = useRef();
  const [count, setCount] = useState(0);

  useEffect(() => {
    const element = ref.current;
    element.addEventListener("click", handleClick);
    return () => {
      element.removeEventListener("click", handleClick);
    };
  }, [count]);

  const handleClick = () => {
    console.log(count);
  };

  const onClickIncrement = () => {
    setCount(count + 1);
  };

  return (
    <>
      <h2>Test</h2>
      <button onClick={onClickIncrement}>點擊 +1</button>
      <div>count: {count}</div>
      <button ref={ref}>Click Test Button</button>
    </>
  );
};

useEffect return?中的變量用的是閉包,這點剛開始學的時候不好理解

方法三:使用 ref 保存狀態

ref?保存的數據雖然不能用于頁面渲染,但可以作為?state?備份,在?state?變化時更新?ref

在事件函數中就能拿到最新的?stateRef

const Test = () => {
  const ref = useRef();
  const [count, setCount] = useState(0);

  const countRef = useRef(count);
  useEffect(() => {
    countRef.current = count;
  }, [count]);

  useEffect(() => {
    const element = ref.current;
    element.addEventListener("click", handleClick);
  }, []);

  const handleClick = () => {
    console.log(countRef.current);
  };

  const onClickIncrement = () => {
    setCount(count + 1);
  };

  return (
    <>
      <h2>Test</h2>
      <button onClick={onClickIncrement}>點擊 +1</button>
      <div>count: {count}</div>
      <button ref={ref}>Click Test Button</button>
    </>
  );
};

優化 state 手動維護

上面三種方法,都有個問題,state?需要手動維護

這一步如何優化呢?

方法一和方法二,優化的方式都一樣:將依賴項是?count?改為?state

const [state, setState] = useState({ count: 0 });

useEffect(() => {
  // ...
}, [state]);

方法三的優化是,用?stateRef?保存?ref?對象,當?state?變化時,遍歷?state?給?stateRef?賦值

事件函數中使用?stateRef

const [state, setState] = useState({ count: 0 });
const stateRef = useRef({});
useEffect(() => {
  Object.keys(state).forEach((key) => {
    stateRef.current[key] = state[key];
  });
}, [state]);

原文鏈接:https://segmentfault.com/a/1190000043365896

欄目分類
最近更新