一個removeEventListener引起的思考

原由

最近在看之前的代碼時,發現年初在熟悉react hooks新特性時寫下了這樣一段代碼:javascript

let i = 0;
function Test(props) {
  const { loading, error, startFetch } = props;
  useEffect(() => {
    const $btn = document.querySelector('.btn'); // .info-con 存在於外層的dom中
    $btn.addEventListener('click', () => {
      const action =`action data${i++}`;
      console.log('resbtn', action);
    }, false);
    return $btn.removeEventListener('click', () => {});
  });
  if (error) {
    return <div>{error.msg}</div>;
  }
  return (
    <div>
      {loading && <h3>loading</h3>}
      <h3 className="mt-10 mb-10">finished</h3>
      <button onClick={startFetch}>獲取數據</button>
      <h4 className="mt-10"><span className="btn">Saga模擬測試</span></h4>
    </div>
  );
}

我用了addEventListener和removeEventListener來嘗試useEffect的掛載和清除功能,細心的你,發現這段代碼有幾個錯誤呢?
自我觀察,自認爲是有以下幾個的:html

  1. 清除函數使用方式錯誤,應該返回的是一個函數,like:() => { $btn.removeEventListener('click', () => {}); },而我這裏是一個語句;
  2. removeEventListener使用語法錯誤;
  3. useEffect未使用第二個參數,致使addEventListener掛載會屢次執行(能夠優化)

第1個和第3個錯誤,是能夠原諒的,當時本身對hooks還不熟悉。但第二個錯誤,是不可原諒的,這是須要反省的。本文後面不會對useEffect作深刻講解,官方文檔已經足夠清楚,後面圍繞removeEventListener來剖析。前端

認清removeEventListener

第二個錯誤,拆開看,又可分爲兩個方面:removeEventListener使用方式多餘與語法錯誤。java

  • 使用方式多餘:雖然$btn添加了click監聽事件回調,但因爲這個節點屬於當前Test組件,因此組件銷燬時,其相關節點的監聽事件也會一併銷燬,這個在本身剛接觸前端時就作過這一方面的解析,因此這裏的removeEventListener使用是多餘的。但若是換成一個組件外的節點,好比我後面替換的.info-con節點,這是一直存在於Layout組件中的,使用removeEventListener是必要的。
  • 語法錯誤:指使用removeEventListener,前面兩個參數是必傳的,事件類型(type:click),事件回調函數(listener: callback),因爲使用addEventListener是爲事件添加的一個隊列(即同一個事件,可添加多個監聽回調),因此事件回調函數(listener)是必傳,且其引用與添加的事件監聽回調函數指向相同。詳細描述見官方文檔

關於語法錯誤,官方文檔中有這樣一段描述: react

clipboard.png

因爲我在添加監聽時,使用的是箭頭函數,因此刪除時沒法找到相同引用的監聽事件,因此第一件事就是改變監聽函數的寫法。完善後,寫法是下面這樣的:面試

let i = 0;
function Test(props) {
  const { loading, error, startFetch } = props;
  useEffect(() => {
    const $btn = document.querySelector('.info-con');
    const eventAction = () => {
      const action =`action data${i++}`;
      console.log('resbtn:', action);
    };
    $btn.addEventListener('click', eventAction);
    return () => {
      $btn.removeEventListener('click', eventAction);
    };
  }, []);
  if (error) {
    return <div>{error.msg}</div>;
  }
  return (
    <div>
      {loading && <h3>loading</h3>}
      <h3 className="mt-10 mb-10">finished</h3>
      <button onClick={startFetch}>獲取數據</button>
      <h4 className="mt-10"><span className="btn">Saga模擬測試</span></h4>
    </div>
  );
}

到此,看似已經結束了。但既然已經打開了,就深刻的學習一下這個api吧,經常使用的前兩個參數,咱們都很熟悉,第三個參數寫成布爾值也偶爾會有,可是否已足夠了解呢?api

第三個參數

addEventListener和removeEventListener傳參是同樣的,第三個可選參數都是一個對象或者一個布爾值:數組

target.addEventListener(type, listener[, options]);
target.addEventListener(type, listener[, useCapture]);
target.removeEventListener(type, listener[, options]);
target.removeEventListener(type, listener[, useCapture]);

當參數爲布爾值時,意指useCapture,是否在捕獲階段觸發監聽函數。而爲對象時,可用選項以下: 閉包

clipboard.png
之因此第三個參數有兩種形態,是在舊版本中只存在一個布爾值,即useCapture屬性;但隨着時間推移以及發展的須要,須要支持設置更多的特性設置,因此有了options選項這個對象傳參,又爲了兼容之前的老程序,因此對二者進行了兼容。once和passive屬性很是有趣,但我還沒想到合適的使用它們的場景。查看官方文檔,發現裏面確實有好多之前沒有關注到的東西,值得細細品味。
其實在js不少api中,咱們都只用了一些經常使用的用法,而忽略了一些存在且也很適用的不經常使用傳參,好比下面這些:框架

function test(){
         for (var i=0; i<5; i  ) {
            setTimeout( function timer() {
                console.log(new Date(),i);
            }, 1000);
        }
        console.log('end',new Date(),i);
    }
  • bind(thisArg, ...params): 爲函數預先傳參,在react中用的比較多;
  • replace(reg, str | function): replace第二參數也能夠爲一個函數,用於自定義替換
  • split(reg, length): split其實也是有第二個參數的,用以指定返回數組的長度不超過指定大小;
  • 暫時想不起來了...

思考

這一次關於對removeEventListener的使用錯誤,讓我不得不意識到,對於本身下一步技術提高的着重點仍是該多關注基礎,不要學的太寬泛,而成爲泛而不精的人,最後一無可取。最近這一年確實太癡迷(yilai)於框架(React),基礎關注的太少。框架與基礎,框架層出不窮,但基礎只是持續在演進。

相關文章
相關標籤/搜索